Repository: goldmansachs/gs-quant Branch: master Commit: e43334038e60 Files: 1679 Total size: 24.4 MB Directory structure: gitextract_7xksykym/ ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ └── workflows/ │ └── python-publish.yml ├── .gitignore ├── .gitlab-ci.yml ├── .gs-project.yml ├── .pre-commit-config.yaml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── NOTICE ├── NOTICE.txt ├── README.md ├── SKILL.md ├── conftest.py ├── dco/ │ ├── Bryan Galindo.dco │ ├── Grant McGovern.dco │ ├── Maverick Lin.dco │ └── Scott Weinstein.dco ├── docs/ │ ├── Makefile │ ├── README.md │ ├── _build/ │ │ ├── doctrees/ │ │ │ ├── classes/ │ │ │ │ ├── gs_quant.base.Priceable.doctree │ │ │ │ ├── gs_quant.data.DataContext.doctree │ │ │ │ ├── gs_quant.data.Dataset.doctree │ │ │ │ ├── gs_quant.data.Fields.doctree │ │ │ │ ├── gs_quant.instrument.CommodOTCSwap.doctree │ │ │ │ ├── gs_quant.instrument.CommodSwap.doctree │ │ │ │ ├── gs_quant.instrument.EqCliquet.doctree │ │ │ │ ├── gs_quant.instrument.EqForward.doctree │ │ │ │ ├── gs_quant.instrument.EqOption.doctree │ │ │ │ ├── gs_quant.instrument.EqSynthetic.doctree │ │ │ │ ├── gs_quant.instrument.EqVarianceSwap.doctree │ │ │ │ ├── gs_quant.instrument.FXBinary.doctree │ │ │ │ ├── gs_quant.instrument.FXForward.doctree │ │ │ │ ├── gs_quant.instrument.FXMultiCrossBinary.doctree │ │ │ │ ├── gs_quant.instrument.FXMultiCrossBinaryLeg.doctree │ │ │ │ ├── gs_quant.instrument.FXOption.doctree │ │ │ │ ├── gs_quant.instrument.FXVolatilitySwap.doctree │ │ │ │ ├── gs_quant.instrument.Forward.doctree │ │ │ │ ├── gs_quant.instrument.IRBasisSwap.doctree │ │ │ │ ├── gs_quant.instrument.IRCMSOption.doctree │ │ │ │ ├── gs_quant.instrument.IRCMSOptionStrip.doctree │ │ │ │ ├── gs_quant.instrument.IRCMSSpreadOption.doctree │ │ │ │ ├── gs_quant.instrument.IRCMSSpreadOptionStrip.doctree │ │ │ │ ├── gs_quant.instrument.IRCap.doctree │ │ │ │ ├── gs_quant.instrument.IRFloor.doctree │ │ │ │ ├── gs_quant.instrument.IRSwap.doctree │ │ │ │ ├── gs_quant.instrument.IRSwaption.doctree │ │ │ │ ├── gs_quant.instrument.IRXccySwap.doctree │ │ │ │ ├── gs_quant.instrument.IRXccySwapFixFix.doctree │ │ │ │ ├── gs_quant.instrument.IRXccySwapFixFlt.doctree │ │ │ │ ├── gs_quant.instrument.InflationSwap.doctree │ │ │ │ ├── gs_quant.instrument.Security.doctree │ │ │ │ ├── gs_quant.markets.HistoricalPricingContext.doctree │ │ │ │ ├── gs_quant.markets.PricingContext.doctree │ │ │ │ ├── gs_quant.markets.portfolio.Portfolio.doctree │ │ │ │ ├── gs_quant.markets.securities.Asset.doctree │ │ │ │ ├── gs_quant.markets.securities.AssetClass.doctree │ │ │ │ ├── gs_quant.markets.securities.AssetIdentifier.doctree │ │ │ │ ├── gs_quant.markets.securities.AssetType.doctree │ │ │ │ ├── gs_quant.markets.securities.Index.doctree │ │ │ │ ├── gs_quant.markets.securities.SecurityMaster.doctree │ │ │ │ ├── gs_quant.markets.securities.Stock.doctree │ │ │ │ ├── gs_quant.models.epidemiology.EpidemicModel.doctree │ │ │ │ ├── gs_quant.models.epidemiology.SEIR.doctree │ │ │ │ ├── gs_quant.models.epidemiology.SIR.doctree │ │ │ │ ├── gs_quant.timeseries.datetime.Window.doctree │ │ │ │ ├── gs_quant.timeseries.statistics.LinearRegression.doctree │ │ │ │ ├── gs_quant.timeseries.statistics.RollingLinearRegression.doctree │ │ │ │ ├── gs_quant.timeseries.statistics.SEIRModel.doctree │ │ │ │ └── gs_quant.timeseries.statistics.SIRModel.doctree │ │ │ ├── data.doctree │ │ │ ├── datetime.doctree │ │ │ ├── environment.pickle │ │ │ ├── functions/ │ │ │ │ ├── gs_quant.datetime.date.business_day_count.doctree │ │ │ │ ├── gs_quant.datetime.date.business_day_offset.doctree │ │ │ │ ├── gs_quant.datetime.date.date_range.doctree │ │ │ │ ├── gs_quant.datetime.date.is_business_day.doctree │ │ │ │ ├── gs_quant.datetime.date.prev_business_date.doctree │ │ │ │ ├── gs_quant.datetime.point.point_sort_order.doctree │ │ │ │ ├── gs_quant.timeseries.algebra.abs_.doctree │ │ │ │ ├── gs_quant.timeseries.algebra.add.doctree │ │ │ │ ├── gs_quant.timeseries.algebra.and_.doctree │ │ │ │ ├── gs_quant.timeseries.algebra.ceil.doctree │ │ │ │ ├── gs_quant.timeseries.algebra.divide.doctree │ │ │ │ ├── gs_quant.timeseries.algebra.exp.doctree │ │ │ │ ├── gs_quant.timeseries.algebra.filter_.doctree │ │ │ │ ├── gs_quant.timeseries.algebra.floor.doctree │ │ │ │ ├── gs_quant.timeseries.algebra.floordiv.doctree │ │ │ │ ├── gs_quant.timeseries.algebra.if_.doctree │ │ │ │ ├── gs_quant.timeseries.algebra.log.doctree │ │ │ │ ├── gs_quant.timeseries.algebra.multiply.doctree │ │ │ │ ├── gs_quant.timeseries.algebra.not_.doctree │ │ │ │ ├── gs_quant.timeseries.algebra.or_.doctree │ │ │ │ ├── gs_quant.timeseries.algebra.power.doctree │ │ │ │ ├── gs_quant.timeseries.algebra.repeat.doctree │ │ │ │ ├── gs_quant.timeseries.algebra.smooth_spikes.doctree │ │ │ │ ├── gs_quant.timeseries.algebra.sqrt.doctree │ │ │ │ ├── gs_quant.timeseries.algebra.subtract.doctree │ │ │ │ ├── gs_quant.timeseries.algebra.weighted_sum.doctree │ │ │ │ ├── gs_quant.timeseries.analysis.count.doctree │ │ │ │ ├── gs_quant.timeseries.analysis.diff.doctree │ │ │ │ ├── gs_quant.timeseries.analysis.first.doctree │ │ │ │ ├── gs_quant.timeseries.analysis.lag.doctree │ │ │ │ ├── gs_quant.timeseries.analysis.last.doctree │ │ │ │ ├── gs_quant.timeseries.analysis.last_value.doctree │ │ │ │ ├── gs_quant.timeseries.analysis.weighted_sum.doctree │ │ │ │ ├── gs_quant.timeseries.backtesting.basket.doctree │ │ │ │ ├── gs_quant.timeseries.datetime.align.doctree │ │ │ │ ├── gs_quant.timeseries.datetime.date_range.doctree │ │ │ │ ├── gs_quant.timeseries.datetime.day.doctree │ │ │ │ ├── gs_quant.timeseries.datetime.interpolate.doctree │ │ │ │ ├── gs_quant.timeseries.datetime.month.doctree │ │ │ │ ├── gs_quant.timeseries.datetime.prepend.doctree │ │ │ │ ├── gs_quant.timeseries.datetime.quarter.doctree │ │ │ │ ├── gs_quant.timeseries.datetime.union.doctree │ │ │ │ ├── gs_quant.timeseries.datetime.value.doctree │ │ │ │ ├── gs_quant.timeseries.datetime.weekday.doctree │ │ │ │ ├── gs_quant.timeseries.datetime.year.doctree │ │ │ │ ├── gs_quant.timeseries.econometrics.annualize.doctree │ │ │ │ ├── gs_quant.timeseries.econometrics.beta.doctree │ │ │ │ ├── gs_quant.timeseries.econometrics.change.doctree │ │ │ │ ├── gs_quant.timeseries.econometrics.correlation.doctree │ │ │ │ ├── gs_quant.timeseries.econometrics.excess_returns_.doctree │ │ │ │ ├── gs_quant.timeseries.econometrics.index.doctree │ │ │ │ ├── gs_quant.timeseries.econometrics.max_drawdown.doctree │ │ │ │ ├── gs_quant.timeseries.econometrics.prices.doctree │ │ │ │ ├── gs_quant.timeseries.econometrics.returns.doctree │ │ │ │ ├── gs_quant.timeseries.econometrics.sharpe_ratio.doctree │ │ │ │ ├── gs_quant.timeseries.econometrics.volatility.doctree │ │ │ │ ├── gs_quant.timeseries.statistics.cov.doctree │ │ │ │ ├── gs_quant.timeseries.statistics.exponential_std.doctree │ │ │ │ ├── gs_quant.timeseries.statistics.generate_series.doctree │ │ │ │ ├── gs_quant.timeseries.statistics.max_.doctree │ │ │ │ ├── gs_quant.timeseries.statistics.mean.doctree │ │ │ │ ├── gs_quant.timeseries.statistics.median.doctree │ │ │ │ ├── gs_quant.timeseries.statistics.min_.doctree │ │ │ │ ├── gs_quant.timeseries.statistics.mode.doctree │ │ │ │ ├── gs_quant.timeseries.statistics.percentile.doctree │ │ │ │ ├── gs_quant.timeseries.statistics.percentiles.doctree │ │ │ │ ├── gs_quant.timeseries.statistics.product.doctree │ │ │ │ ├── gs_quant.timeseries.statistics.range_.doctree │ │ │ │ ├── gs_quant.timeseries.statistics.std.doctree │ │ │ │ ├── gs_quant.timeseries.statistics.sum_.doctree │ │ │ │ ├── gs_quant.timeseries.statistics.var.doctree │ │ │ │ ├── gs_quant.timeseries.statistics.winsorize.doctree │ │ │ │ ├── gs_quant.timeseries.statistics.zscores.doctree │ │ │ │ ├── gs_quant.timeseries.technicals.bollinger_bands.doctree │ │ │ │ ├── gs_quant.timeseries.technicals.exponential_moving_average.doctree │ │ │ │ ├── gs_quant.timeseries.technicals.exponential_spread_volatility.doctree │ │ │ │ ├── gs_quant.timeseries.technicals.exponential_volatility.doctree │ │ │ │ ├── gs_quant.timeseries.technicals.moving_average.doctree │ │ │ │ ├── gs_quant.timeseries.technicals.relative_strength_index.doctree │ │ │ │ └── gs_quant.timeseries.technicals.smoothed_moving_average.doctree │ │ │ ├── index.doctree │ │ │ ├── instrument.doctree │ │ │ ├── market.doctree │ │ │ ├── markets.doctree │ │ │ ├── models.doctree │ │ │ ├── risk.doctree │ │ │ └── timeseries.doctree │ │ ├── html/ │ │ │ ├── .buildinfo │ │ │ ├── _modules/ │ │ │ │ ├── aenum.html │ │ │ │ ├── gs_quant/ │ │ │ │ │ ├── base.html │ │ │ │ │ ├── data/ │ │ │ │ │ │ ├── core.html │ │ │ │ │ │ ├── dataset.html │ │ │ │ │ │ └── fields.html │ │ │ │ │ ├── datetime/ │ │ │ │ │ │ ├── date.html │ │ │ │ │ │ └── point.html │ │ │ │ │ ├── instrument/ │ │ │ │ │ │ └── core.html │ │ │ │ │ ├── markets/ │ │ │ │ │ │ ├── core.html │ │ │ │ │ │ ├── historical.html │ │ │ │ │ │ ├── portfolio.html │ │ │ │ │ │ └── securities.html │ │ │ │ │ ├── models/ │ │ │ │ │ │ └── epidemiology.html │ │ │ │ │ ├── risk/ │ │ │ │ │ │ ├── core.html │ │ │ │ │ │ └── measures.html │ │ │ │ │ ├── target/ │ │ │ │ │ │ ├── common.html │ │ │ │ │ │ └── instrument.html │ │ │ │ │ └── timeseries/ │ │ │ │ │ ├── algebra.html │ │ │ │ │ ├── analysis.html │ │ │ │ │ ├── backtesting.html │ │ │ │ │ ├── datetime.html │ │ │ │ │ ├── econometrics.html │ │ │ │ │ ├── helper.html │ │ │ │ │ ├── statistics.html │ │ │ │ │ └── technicals.html │ │ │ │ └── index.html │ │ │ ├── _sources/ │ │ │ │ ├── classes/ │ │ │ │ │ ├── gs_quant.base.Priceable.rst.txt │ │ │ │ │ ├── gs_quant.data.DataContext.rst.txt │ │ │ │ │ ├── gs_quant.data.Dataset.rst.txt │ │ │ │ │ ├── gs_quant.data.Fields.rst.txt │ │ │ │ │ ├── gs_quant.instrument.CommodOTCSwap.rst.txt │ │ │ │ │ ├── gs_quant.instrument.CommodSwap.rst.txt │ │ │ │ │ ├── gs_quant.instrument.EqCliquet.rst.txt │ │ │ │ │ ├── gs_quant.instrument.EqForward.rst.txt │ │ │ │ │ ├── gs_quant.instrument.EqOption.rst.txt │ │ │ │ │ ├── gs_quant.instrument.EqSynthetic.rst.txt │ │ │ │ │ ├── gs_quant.instrument.EqVarianceSwap.rst.txt │ │ │ │ │ ├── gs_quant.instrument.FXBinary.rst.txt │ │ │ │ │ ├── gs_quant.instrument.FXForward.rst.txt │ │ │ │ │ ├── gs_quant.instrument.FXMultiCrossBinary.rst.txt │ │ │ │ │ ├── gs_quant.instrument.FXMultiCrossBinaryLeg.rst.txt │ │ │ │ │ ├── gs_quant.instrument.FXOption.rst.txt │ │ │ │ │ ├── gs_quant.instrument.FXVolatilitySwap.rst.txt │ │ │ │ │ ├── gs_quant.instrument.Forward.rst.txt │ │ │ │ │ ├── gs_quant.instrument.IRBasisSwap.rst.txt │ │ │ │ │ ├── gs_quant.instrument.IRCMSOption.rst.txt │ │ │ │ │ ├── gs_quant.instrument.IRCMSOptionStrip.rst.txt │ │ │ │ │ ├── gs_quant.instrument.IRCMSSpreadOption.rst.txt │ │ │ │ │ ├── gs_quant.instrument.IRCMSSpreadOptionStrip.rst.txt │ │ │ │ │ ├── gs_quant.instrument.IRCap.rst.txt │ │ │ │ │ ├── gs_quant.instrument.IRFloor.rst.txt │ │ │ │ │ ├── gs_quant.instrument.IRSwap.rst.txt │ │ │ │ │ ├── gs_quant.instrument.IRSwaption.rst.txt │ │ │ │ │ ├── gs_quant.instrument.IRXccySwap.rst.txt │ │ │ │ │ ├── gs_quant.instrument.IRXccySwapFixFix.rst.txt │ │ │ │ │ ├── gs_quant.instrument.IRXccySwapFixFlt.rst.txt │ │ │ │ │ ├── gs_quant.instrument.InflationSwap.rst.txt │ │ │ │ │ ├── gs_quant.instrument.Security.rst.txt │ │ │ │ │ ├── gs_quant.markets.HistoricalPricingContext.rst.txt │ │ │ │ │ ├── gs_quant.markets.PricingContext.rst.txt │ │ │ │ │ ├── gs_quant.markets.portfolio.Portfolio.rst.txt │ │ │ │ │ ├── gs_quant.markets.securities.Asset.rst.txt │ │ │ │ │ ├── gs_quant.markets.securities.AssetClass.rst.txt │ │ │ │ │ ├── gs_quant.markets.securities.AssetIdentifier.rst.txt │ │ │ │ │ ├── gs_quant.markets.securities.AssetType.rst.txt │ │ │ │ │ ├── gs_quant.markets.securities.Index.rst.txt │ │ │ │ │ ├── gs_quant.markets.securities.SecurityMaster.rst.txt │ │ │ │ │ ├── gs_quant.markets.securities.Stock.rst.txt │ │ │ │ │ ├── gs_quant.models.epidemiology.EpidemicModel.rst.txt │ │ │ │ │ ├── gs_quant.models.epidemiology.SEIR.rst.txt │ │ │ │ │ ├── gs_quant.models.epidemiology.SIR.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.datetime.Window.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.statistics.LinearRegression.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.statistics.RollingLinearRegression.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.statistics.SEIRModel.rst.txt │ │ │ │ │ └── gs_quant.timeseries.statistics.SIRModel.rst.txt │ │ │ │ ├── data.rst.txt │ │ │ │ ├── datetime.rst.txt │ │ │ │ ├── functions/ │ │ │ │ │ ├── gs_quant.datetime.date.business_day_count.rst.txt │ │ │ │ │ ├── gs_quant.datetime.date.business_day_offset.rst.txt │ │ │ │ │ ├── gs_quant.datetime.date.date_range.rst.txt │ │ │ │ │ ├── gs_quant.datetime.date.is_business_day.rst.txt │ │ │ │ │ ├── gs_quant.datetime.date.prev_business_date.rst.txt │ │ │ │ │ ├── gs_quant.datetime.point.point_sort_order.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.algebra.abs_.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.algebra.add.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.algebra.and_.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.algebra.ceil.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.algebra.divide.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.algebra.exp.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.algebra.filter_.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.algebra.floor.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.algebra.floordiv.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.algebra.if_.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.algebra.log.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.algebra.multiply.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.algebra.not_.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.algebra.or_.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.algebra.power.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.algebra.repeat.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.algebra.smooth_spikes.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.algebra.sqrt.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.algebra.subtract.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.algebra.weighted_sum.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.analysis.count.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.analysis.diff.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.analysis.first.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.analysis.lag.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.analysis.last.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.analysis.last_value.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.analysis.weighted_sum.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.backtesting.basket.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.datetime.align.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.datetime.date_range.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.datetime.day.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.datetime.interpolate.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.datetime.month.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.datetime.prepend.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.datetime.quarter.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.datetime.union.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.datetime.value.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.datetime.weekday.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.datetime.year.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.econometrics.annualize.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.econometrics.beta.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.econometrics.change.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.econometrics.correlation.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.econometrics.excess_returns_.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.econometrics.index.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.econometrics.max_drawdown.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.econometrics.prices.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.econometrics.returns.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.econometrics.sharpe_ratio.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.econometrics.volatility.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.statistics.cov.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.statistics.exponential_std.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.statistics.generate_series.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.statistics.max_.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.statistics.mean.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.statistics.median.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.statistics.min_.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.statistics.mode.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.statistics.percentile.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.statistics.percentiles.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.statistics.product.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.statistics.range_.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.statistics.std.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.statistics.sum_.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.statistics.var.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.statistics.winsorize.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.statistics.zscores.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.technicals.bollinger_bands.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.technicals.exponential_moving_average.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.technicals.exponential_spread_volatility.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.technicals.exponential_volatility.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.technicals.moving_average.rst.txt │ │ │ │ │ ├── gs_quant.timeseries.technicals.relative_strength_index.rst.txt │ │ │ │ │ └── gs_quant.timeseries.technicals.smoothed_moving_average.rst.txt │ │ │ │ ├── index.rst.txt │ │ │ │ ├── instrument.rst.txt │ │ │ │ ├── market.rst.txt │ │ │ │ ├── markets.rst.txt │ │ │ │ ├── models.rst.txt │ │ │ │ ├── risk.rst.txt │ │ │ │ └── timeseries.rst.txt │ │ │ ├── _static/ │ │ │ │ ├── basic.css │ │ │ │ ├── css/ │ │ │ │ │ └── theme.css │ │ │ │ ├── doctools.js │ │ │ │ ├── documentation_options.js │ │ │ │ ├── jquery-3.5.1.js │ │ │ │ ├── jquery.js │ │ │ │ ├── js/ │ │ │ │ │ └── theme.js │ │ │ │ ├── language_data.js │ │ │ │ ├── pygments.css │ │ │ │ ├── searchtools.js │ │ │ │ ├── underscore-1.3.1.js │ │ │ │ └── underscore.js │ │ │ ├── classes/ │ │ │ │ ├── gs_quant.base.Priceable.html │ │ │ │ ├── gs_quant.data.DataContext.html │ │ │ │ ├── gs_quant.data.Dataset.html │ │ │ │ ├── gs_quant.data.Fields.html │ │ │ │ ├── gs_quant.instrument.CommodOTCSwap.html │ │ │ │ ├── gs_quant.instrument.CommodSwap.html │ │ │ │ ├── gs_quant.instrument.EqCliquet.html │ │ │ │ ├── gs_quant.instrument.EqForward.html │ │ │ │ ├── gs_quant.instrument.EqOption.html │ │ │ │ ├── gs_quant.instrument.EqSynthetic.html │ │ │ │ ├── gs_quant.instrument.EqVarianceSwap.html │ │ │ │ ├── gs_quant.instrument.FXBinary.html │ │ │ │ ├── gs_quant.instrument.FXForward.html │ │ │ │ ├── gs_quant.instrument.FXMultiCrossBinary.html │ │ │ │ ├── gs_quant.instrument.FXMultiCrossBinaryLeg.html │ │ │ │ ├── gs_quant.instrument.FXOption.html │ │ │ │ ├── gs_quant.instrument.FXVolatilitySwap.html │ │ │ │ ├── gs_quant.instrument.Forward.html │ │ │ │ ├── gs_quant.instrument.IRBasisSwap.html │ │ │ │ ├── gs_quant.instrument.IRCMSOption.html │ │ │ │ ├── gs_quant.instrument.IRCMSOptionStrip.html │ │ │ │ ├── gs_quant.instrument.IRCMSSpreadOption.html │ │ │ │ ├── gs_quant.instrument.IRCMSSpreadOptionStrip.html │ │ │ │ ├── gs_quant.instrument.IRCap.html │ │ │ │ ├── gs_quant.instrument.IRFloor.html │ │ │ │ ├── gs_quant.instrument.IRSwap.html │ │ │ │ ├── gs_quant.instrument.IRSwaption.html │ │ │ │ ├── gs_quant.instrument.IRXccySwap.html │ │ │ │ ├── gs_quant.instrument.IRXccySwapFixFix.html │ │ │ │ ├── gs_quant.instrument.IRXccySwapFixFlt.html │ │ │ │ ├── gs_quant.instrument.InflationSwap.html │ │ │ │ ├── gs_quant.instrument.Security.html │ │ │ │ ├── gs_quant.markets.HistoricalPricingContext.html │ │ │ │ ├── gs_quant.markets.PricingContext.html │ │ │ │ ├── gs_quant.markets.portfolio.Portfolio.html │ │ │ │ ├── gs_quant.markets.securities.Asset.html │ │ │ │ ├── gs_quant.markets.securities.AssetClass.html │ │ │ │ ├── gs_quant.markets.securities.AssetIdentifier.html │ │ │ │ ├── gs_quant.markets.securities.AssetType.html │ │ │ │ ├── gs_quant.markets.securities.Index.html │ │ │ │ ├── gs_quant.markets.securities.SecurityMaster.html │ │ │ │ ├── gs_quant.markets.securities.Stock.html │ │ │ │ ├── gs_quant.models.epidemiology.EpidemicModel.html │ │ │ │ ├── gs_quant.models.epidemiology.SEIR.html │ │ │ │ ├── gs_quant.models.epidemiology.SIR.html │ │ │ │ ├── gs_quant.timeseries.datetime.Window.html │ │ │ │ ├── gs_quant.timeseries.statistics.LinearRegression.html │ │ │ │ ├── gs_quant.timeseries.statistics.RollingLinearRegression.html │ │ │ │ ├── gs_quant.timeseries.statistics.SEIRModel.html │ │ │ │ └── gs_quant.timeseries.statistics.SIRModel.html │ │ │ ├── data.html │ │ │ ├── datetime.html │ │ │ ├── functions/ │ │ │ │ ├── gs_quant.datetime.date.business_day_count.html │ │ │ │ ├── gs_quant.datetime.date.business_day_offset.html │ │ │ │ ├── gs_quant.datetime.date.date_range.html │ │ │ │ ├── gs_quant.datetime.date.is_business_day.html │ │ │ │ ├── gs_quant.datetime.date.prev_business_date.html │ │ │ │ ├── gs_quant.datetime.point.point_sort_order.html │ │ │ │ ├── gs_quant.timeseries.algebra.abs_.html │ │ │ │ ├── gs_quant.timeseries.algebra.add.html │ │ │ │ ├── gs_quant.timeseries.algebra.and_.html │ │ │ │ ├── gs_quant.timeseries.algebra.ceil.html │ │ │ │ ├── gs_quant.timeseries.algebra.divide.html │ │ │ │ ├── gs_quant.timeseries.algebra.exp.html │ │ │ │ ├── gs_quant.timeseries.algebra.filter_.html │ │ │ │ ├── gs_quant.timeseries.algebra.floor.html │ │ │ │ ├── gs_quant.timeseries.algebra.floordiv.html │ │ │ │ ├── gs_quant.timeseries.algebra.if_.html │ │ │ │ ├── gs_quant.timeseries.algebra.log.html │ │ │ │ ├── gs_quant.timeseries.algebra.multiply.html │ │ │ │ ├── gs_quant.timeseries.algebra.not_.html │ │ │ │ ├── gs_quant.timeseries.algebra.or_.html │ │ │ │ ├── gs_quant.timeseries.algebra.power.html │ │ │ │ ├── gs_quant.timeseries.algebra.repeat.html │ │ │ │ ├── gs_quant.timeseries.algebra.smooth_spikes.html │ │ │ │ ├── gs_quant.timeseries.algebra.sqrt.html │ │ │ │ ├── gs_quant.timeseries.algebra.subtract.html │ │ │ │ ├── gs_quant.timeseries.algebra.weighted_sum.html │ │ │ │ ├── gs_quant.timeseries.analysis.count.html │ │ │ │ ├── gs_quant.timeseries.analysis.diff.html │ │ │ │ ├── gs_quant.timeseries.analysis.first.html │ │ │ │ ├── gs_quant.timeseries.analysis.lag.html │ │ │ │ ├── gs_quant.timeseries.analysis.last.html │ │ │ │ ├── gs_quant.timeseries.analysis.last_value.html │ │ │ │ ├── gs_quant.timeseries.analysis.weighted_sum.html │ │ │ │ ├── gs_quant.timeseries.backtesting.basket.html │ │ │ │ ├── gs_quant.timeseries.datetime.align.html │ │ │ │ ├── gs_quant.timeseries.datetime.date_range.html │ │ │ │ ├── gs_quant.timeseries.datetime.day.html │ │ │ │ ├── gs_quant.timeseries.datetime.interpolate.html │ │ │ │ ├── gs_quant.timeseries.datetime.month.html │ │ │ │ ├── gs_quant.timeseries.datetime.prepend.html │ │ │ │ ├── gs_quant.timeseries.datetime.quarter.html │ │ │ │ ├── gs_quant.timeseries.datetime.union.html │ │ │ │ ├── gs_quant.timeseries.datetime.value.html │ │ │ │ ├── gs_quant.timeseries.datetime.weekday.html │ │ │ │ ├── gs_quant.timeseries.datetime.year.html │ │ │ │ ├── gs_quant.timeseries.econometrics.annualize.html │ │ │ │ ├── gs_quant.timeseries.econometrics.beta.html │ │ │ │ ├── gs_quant.timeseries.econometrics.change.html │ │ │ │ ├── gs_quant.timeseries.econometrics.correlation.html │ │ │ │ ├── gs_quant.timeseries.econometrics.excess_returns_.html │ │ │ │ ├── gs_quant.timeseries.econometrics.index.html │ │ │ │ ├── gs_quant.timeseries.econometrics.max_drawdown.html │ │ │ │ ├── gs_quant.timeseries.econometrics.prices.html │ │ │ │ ├── gs_quant.timeseries.econometrics.returns.html │ │ │ │ ├── gs_quant.timeseries.econometrics.sharpe_ratio.html │ │ │ │ ├── gs_quant.timeseries.econometrics.volatility.html │ │ │ │ ├── gs_quant.timeseries.statistics.cov.html │ │ │ │ ├── gs_quant.timeseries.statistics.exponential_std.html │ │ │ │ ├── gs_quant.timeseries.statistics.generate_series.html │ │ │ │ ├── gs_quant.timeseries.statistics.max_.html │ │ │ │ ├── gs_quant.timeseries.statistics.mean.html │ │ │ │ ├── gs_quant.timeseries.statistics.median.html │ │ │ │ ├── gs_quant.timeseries.statistics.min_.html │ │ │ │ ├── gs_quant.timeseries.statistics.mode.html │ │ │ │ ├── gs_quant.timeseries.statistics.percentile.html │ │ │ │ ├── gs_quant.timeseries.statistics.percentiles.html │ │ │ │ ├── gs_quant.timeseries.statistics.product.html │ │ │ │ ├── gs_quant.timeseries.statistics.range_.html │ │ │ │ ├── gs_quant.timeseries.statistics.std.html │ │ │ │ ├── gs_quant.timeseries.statistics.sum_.html │ │ │ │ ├── gs_quant.timeseries.statistics.var.html │ │ │ │ ├── gs_quant.timeseries.statistics.winsorize.html │ │ │ │ ├── gs_quant.timeseries.statistics.zscores.html │ │ │ │ ├── gs_quant.timeseries.technicals.bollinger_bands.html │ │ │ │ ├── gs_quant.timeseries.technicals.exponential_moving_average.html │ │ │ │ ├── gs_quant.timeseries.technicals.exponential_spread_volatility.html │ │ │ │ ├── gs_quant.timeseries.technicals.exponential_volatility.html │ │ │ │ ├── gs_quant.timeseries.technicals.moving_average.html │ │ │ │ ├── gs_quant.timeseries.technicals.relative_strength_index.html │ │ │ │ └── gs_quant.timeseries.technicals.smoothed_moving_average.html │ │ │ ├── genindex.html │ │ │ ├── index.html │ │ │ ├── instrument.html │ │ │ ├── market.html │ │ │ ├── markets.html │ │ │ ├── models.html │ │ │ ├── objects.inv │ │ │ ├── risk.html │ │ │ ├── search.html │ │ │ ├── searchindex.js │ │ │ └── timeseries.html │ │ └── search/ │ │ ├── search-index.json │ │ └── searchdata.json │ ├── _templates/ │ │ ├── contexts.rst │ │ ├── instruments.rst │ │ ├── models_class.rst │ │ ├── portfolio_manager.rst │ │ ├── processors.rst │ │ ├── report.rst │ │ └── timeseries_class.rst │ ├── _themes/ │ │ └── gs/ │ │ ├── LICENSE.txt │ │ ├── __init__.py │ │ ├── breadcrumbs.html │ │ ├── footer.html │ │ ├── layout.html │ │ ├── static/ │ │ │ ├── css/ │ │ │ │ └── theme.css │ │ │ └── js/ │ │ │ └── theme.js │ │ ├── theme.conf │ │ └── versions.html │ ├── analytics.rst │ ├── classes/ │ │ ├── gs_quant.analytics.datagrid.datagrid.DataGrid.rst │ │ ├── gs_quant.analytics.processors.AdditionProcessor.rst │ │ ├── gs_quant.analytics.processors.AppendProcessor.rst │ │ ├── gs_quant.analytics.processors.ChangeProcessor.rst │ │ ├── gs_quant.analytics.processors.CoordinateProcessor.rst │ │ ├── gs_quant.analytics.processors.CorrelationProcessor.rst │ │ ├── gs_quant.analytics.processors.DivisionProcessor.rst │ │ ├── gs_quant.analytics.processors.EntityProcessor.rst │ │ ├── gs_quant.analytics.processors.LastProcessor.rst │ │ ├── gs_quant.analytics.processors.MultiplicationProcessor.rst │ │ ├── gs_quant.analytics.processors.PercentilesProcessor.rst │ │ ├── gs_quant.analytics.processors.ReturnsProcessor.rst │ │ ├── gs_quant.analytics.processors.SharpeRatioProcessor.rst │ │ ├── gs_quant.analytics.processors.SubtractionProcessor.rst │ │ ├── gs_quant.analytics.processors.VolatilityProcessor.rst │ │ ├── gs_quant.analytics.workspaces.Component.rst │ │ ├── gs_quant.analytics.workspaces.Workspace.rst │ │ ├── gs_quant.base.Priceable.rst │ │ ├── gs_quant.data.DataContext.rst │ │ ├── gs_quant.data.Dataset.rst │ │ ├── gs_quant.data.Fields.rst │ │ ├── gs_quant.data.core.DataContext.rst │ │ ├── gs_quant.data.dataset.Dataset.rst │ │ ├── gs_quant.data.fields.Fields.rst │ │ ├── gs_quant.datetime.relative_date.RelativeDate.rst │ │ ├── gs_quant.instrument.CommodOTCSwap.rst │ │ ├── gs_quant.instrument.EqCliquet.rst │ │ ├── gs_quant.instrument.EqForward.rst │ │ ├── gs_quant.instrument.EqOption.rst │ │ ├── gs_quant.instrument.EqSynthetic.rst │ │ ├── gs_quant.instrument.EqVarianceSwap.rst │ │ ├── gs_quant.instrument.FXAccumulator.rst │ │ ├── gs_quant.instrument.FXAccumulatorScheduleLeg.rst │ │ ├── gs_quant.instrument.FXBinary.rst │ │ ├── gs_quant.instrument.FXCorrelationSwap.rst │ │ ├── gs_quant.instrument.FXCorrelationSwapLeg.rst │ │ ├── gs_quant.instrument.FXDoubleKnockout.rst │ │ ├── gs_quant.instrument.FXDoubleOneTouch.rst │ │ ├── gs_quant.instrument.FXDualDoubleKnockout.rst │ │ ├── gs_quant.instrument.FXDualDoubleKnockoutLeg.rst │ │ ├── gs_quant.instrument.FXEuropeanKnockout.rst │ │ ├── gs_quant.instrument.FXForward.rst │ │ ├── gs_quant.instrument.FXKnockout.rst │ │ ├── gs_quant.instrument.FXMultiCrossBinary.rst │ │ ├── gs_quant.instrument.FXMultiCrossBinaryLeg.rst │ │ ├── gs_quant.instrument.FXMultiCrossDoubleBinary.rst │ │ ├── gs_quant.instrument.FXMultiCrossDoubleBinaryLeg.rst │ │ ├── gs_quant.instrument.FXMultiCrossDoubleOneTouch.rst │ │ ├── gs_quant.instrument.FXMultiCrossDoubleOneTouchLeg.rst │ │ ├── gs_quant.instrument.FXOneTouch.rst │ │ ├── gs_quant.instrument.FXOption.rst │ │ ├── gs_quant.instrument.FXOptionLeg.rst │ │ ├── gs_quant.instrument.FXOptionStrategy.rst │ │ ├── gs_quant.instrument.FXPivot.rst │ │ ├── gs_quant.instrument.FXPivotScheduleLeg.rst │ │ ├── gs_quant.instrument.FXShiftingBermForward.rst │ │ ├── gs_quant.instrument.FXTarf.rst │ │ ├── gs_quant.instrument.FXTarfScheduleLeg.rst │ │ ├── gs_quant.instrument.FXVarianceSwap.rst │ │ ├── gs_quant.instrument.FXVolatilitySwap.rst │ │ ├── gs_quant.instrument.FXWorstOf.rst │ │ ├── gs_quant.instrument.FXWorstOfKO.rst │ │ ├── gs_quant.instrument.FXWorstOfKOLeg.rst │ │ ├── gs_quant.instrument.Forward.rst │ │ ├── gs_quant.instrument.IRBasisSwap.rst │ │ ├── gs_quant.instrument.IRCMSOption.rst │ │ ├── gs_quant.instrument.IRCMSOptionStrip.rst │ │ ├── gs_quant.instrument.IRCMSSpreadOption.rst │ │ ├── gs_quant.instrument.IRCMSSpreadOptionStrip.rst │ │ ├── gs_quant.instrument.IRCap.rst │ │ ├── gs_quant.instrument.IRFloor.rst │ │ ├── gs_quant.instrument.IRSwap.rst │ │ ├── gs_quant.instrument.IRSwaption.rst │ │ ├── gs_quant.instrument.IRXccySwap.rst │ │ ├── gs_quant.instrument.IRXccySwapFixFix.rst │ │ ├── gs_quant.instrument.IRXccySwapFixFlt.rst │ │ ├── gs_quant.instrument.InflationSwap.rst │ │ ├── gs_quant.instrument.Security.rst │ │ ├── gs_quant.markets.baskets.Basket.rst │ │ ├── gs_quant.markets.core.PricingContext.rst │ │ ├── gs_quant.markets.historical.HistoricalPricingContext.rst │ │ ├── gs_quant.markets.index.Index.rst │ │ ├── gs_quant.markets.indices_utils.BasketType.rst │ │ ├── gs_quant.markets.indices_utils.CorporateActionType.rst │ │ ├── gs_quant.markets.indices_utils.CustomBasketStyles.rst │ │ ├── gs_quant.markets.indices_utils.FundamentalMetricPeriod.rst │ │ ├── gs_quant.markets.indices_utils.FundamentalMetricPeriodDirection.rst │ │ ├── gs_quant.markets.indices_utils.FundamentalsMetrics.rst │ │ ├── gs_quant.markets.indices_utils.IndicesDatasets.rst │ │ ├── gs_quant.markets.indices_utils.Region.rst │ │ ├── gs_quant.markets.indices_utils.ResearchBasketStyles.rst │ │ ├── gs_quant.markets.indices_utils.ReturnType.rst │ │ ├── gs_quant.markets.indices_utils.WeightingStrategy.rst │ │ ├── gs_quant.markets.portfolio.Grid.rst │ │ ├── gs_quant.markets.portfolio.Portfolio.rst │ │ ├── gs_quant.markets.portfolio_manager.PortfolioManager.rst │ │ ├── gs_quant.markets.position_set.Position.rst │ │ ├── gs_quant.markets.position_set.PositionSet.rst │ │ ├── gs_quant.markets.report.FactorRiskReport.rst │ │ ├── gs_quant.markets.report.PerformanceReport.rst │ │ ├── gs_quant.markets.report.Report.rst │ │ ├── gs_quant.markets.report.ReportJobFuture.rst │ │ ├── gs_quant.markets.report.ThematicReport.rst │ │ ├── gs_quant.markets.securities.Asset.rst │ │ ├── gs_quant.markets.securities.AssetClass.rst │ │ ├── gs_quant.markets.securities.AssetIdentifier.rst │ │ ├── gs_quant.markets.securities.AssetType.rst │ │ ├── gs_quant.markets.securities.SecurityMaster.rst │ │ ├── gs_quant.markets.securities.Stock.rst │ │ ├── gs_quant.models.epidemiology.EpidemicModel.rst │ │ ├── gs_quant.models.epidemiology.SEIR.rst │ │ ├── gs_quant.models.epidemiology.SIR.rst │ │ ├── gs_quant.models.risk_model.FactorRiskModel.rst │ │ ├── gs_quant.models.risk_model.MacroRiskModel.rst │ │ ├── gs_quant.models.risk_model.MarqueeRiskModel.rst │ │ ├── gs_quant.models.risk_model.RiskModel.rst │ │ ├── gs_quant.models.risk_model.ThematicRiskModel.rst │ │ ├── gs_quant.timeseries.backtesting.Basket.rst │ │ ├── gs_quant.timeseries.datetime.Window.rst │ │ ├── gs_quant.timeseries.statistics.LinearRegression.rst │ │ ├── gs_quant.timeseries.statistics.RollingLinearRegression.rst │ │ ├── gs_quant.timeseries.statistics.SEIRModel.rst │ │ └── gs_quant.timeseries.statistics.SIRModel.rst │ ├── conf.py │ ├── data.rst │ ├── datetime.rst │ ├── entities.rst │ ├── functions/ │ │ ├── gs_quant.datetime.date.business_day_count.rst │ │ ├── gs_quant.datetime.date.business_day_offset.rst │ │ ├── gs_quant.datetime.date.date_range.rst │ │ ├── gs_quant.datetime.date.is_business_day.rst │ │ ├── gs_quant.datetime.date.prev_business_date.rst │ │ ├── gs_quant.datetime.point.point_sort_order.rst │ │ ├── gs_quant.markets.baskets.Basket.add_factor_risk_report.rst │ │ ├── gs_quant.markets.baskets.Basket.cancel_rebalance.rst │ │ ├── gs_quant.markets.baskets.Basket.clone.rst │ │ ├── gs_quant.markets.baskets.Basket.create.rst │ │ ├── gs_quant.markets.baskets.Basket.delete_factor_risk_report.rst │ │ ├── gs_quant.markets.baskets.Basket.entity_type.rst │ │ ├── gs_quant.markets.baskets.Basket.get.rst │ │ ├── gs_quant.markets.baskets.Basket.get_close_price_for_date.rst │ │ ├── gs_quant.markets.baskets.Basket.get_close_prices.rst │ │ ├── gs_quant.markets.baskets.Basket.get_corporate_actions.rst │ │ ├── gs_quant.markets.baskets.Basket.get_data_coordinate.rst │ │ ├── gs_quant.markets.baskets.Basket.get_data_series.rst │ │ ├── gs_quant.markets.baskets.Basket.get_details.rst │ │ ├── gs_quant.markets.baskets.Basket.get_entity.rst │ │ ├── gs_quant.markets.baskets.Basket.get_fundamentals.rst │ │ ├── gs_quant.markets.baskets.Basket.get_identifier.rst │ │ ├── gs_quant.markets.baskets.Basket.get_identifiers.rst │ │ ├── gs_quant.markets.baskets.Basket.get_latest_close_price.rst │ │ ├── gs_quant.markets.baskets.Basket.get_latest_position_set.rst │ │ ├── gs_quant.markets.baskets.Basket.get_latest_rebalance_data.rst │ │ ├── gs_quant.markets.baskets.Basket.get_latest_rebalance_date.rst │ │ ├── gs_quant.markets.baskets.Basket.get_live_date.rst │ │ ├── gs_quant.markets.baskets.Basket.get_marquee_id.rst │ │ ├── gs_quant.markets.baskets.Basket.get_position_set_for_date.rst │ │ ├── gs_quant.markets.baskets.Basket.get_position_sets.rst │ │ ├── gs_quant.markets.baskets.Basket.get_positions_data.rst │ │ ├── gs_quant.markets.baskets.Basket.get_rebalance_approval_status.rst │ │ ├── gs_quant.markets.baskets.Basket.get_type.rst │ │ ├── gs_quant.markets.baskets.Basket.get_unique_entity_key.rst │ │ ├── gs_quant.markets.baskets.Basket.get_url.rst │ │ ├── gs_quant.markets.baskets.Basket.poll_report.rst │ │ ├── gs_quant.markets.baskets.Basket.poll_status.rst │ │ ├── gs_quant.markets.baskets.Basket.update.rst │ │ ├── gs_quant.markets.baskets.Basket.upload_position_history.rst │ │ ├── gs_quant.markets.index.Index.get.rst │ │ ├── gs_quant.markets.index.Index.get_close_price_for_date.rst │ │ ├── gs_quant.markets.index.Index.get_close_prices.rst │ │ ├── gs_quant.markets.index.Index.get_constituent_instruments.rst │ │ ├── gs_quant.markets.index.Index.get_constituent_instruments_for_date.rst │ │ ├── gs_quant.markets.index.Index.get_constituents.rst │ │ ├── gs_quant.markets.index.Index.get_constituents_for_date.rst │ │ ├── gs_quant.markets.index.Index.get_currency.rst │ │ ├── gs_quant.markets.index.Index.get_data_coordinate.rst │ │ ├── gs_quant.markets.index.Index.get_data_series.rst │ │ ├── gs_quant.markets.index.Index.get_entitlements.rst │ │ ├── gs_quant.markets.index.Index.get_entity.rst │ │ ├── gs_quant.markets.index.Index.get_factor_risk_report.rst │ │ ├── gs_quant.markets.index.Index.get_fundamentals.rst │ │ ├── gs_quant.markets.index.Index.get_identifier.rst │ │ ├── gs_quant.markets.index.Index.get_identifiers.rst │ │ ├── gs_quant.markets.index.Index.get_latest_close_price.rst │ │ ├── gs_quant.markets.index.Index.get_latest_constituent_instruments.rst │ │ ├── gs_quant.markets.index.Index.get_latest_constituents.rst │ │ ├── gs_quant.markets.index.Index.get_latest_position_set.rst │ │ ├── gs_quant.markets.index.Index.get_marquee_id.rst │ │ ├── gs_quant.markets.index.Index.get_position_dates.rst │ │ ├── gs_quant.markets.index.Index.get_position_set_for_date.rst │ │ ├── gs_quant.markets.index.Index.get_position_sets.rst │ │ ├── gs_quant.markets.index.Index.get_positions_data.rst │ │ ├── gs_quant.markets.index.Index.get_return_type.rst │ │ ├── gs_quant.markets.index.Index.get_status_of_reports.rst │ │ ├── gs_quant.markets.index.Index.get_type.rst │ │ ├── gs_quant.markets.index.Index.get_underlier_attribution.rst │ │ ├── gs_quant.markets.index.Index.get_underlier_tree.rst │ │ ├── gs_quant.markets.index.Index.get_underlier_weights.rst │ │ ├── gs_quant.markets.index.Index.get_unique_entity_key.rst │ │ ├── gs_quant.markets.index.Index.get_url.rst │ │ ├── gs_quant.markets.index.Index.update_positions.rst │ │ ├── gs_quant.markets.index.Index.visualise_tree.rst │ │ ├── gs_quant.markets.indices_utils.get_flagship_baskets.rst │ │ ├── gs_quant.markets.indices_utils.get_flagships_constituents.rst │ │ ├── gs_quant.markets.indices_utils.get_flagships_performance.rst │ │ ├── gs_quant.markets.indices_utils.get_flagships_with_assets.rst │ │ ├── gs_quant.markets.indices_utils.get_my_baskets.rst │ │ ├── gs_quant.markets.position_set.PositionSet.equalize_position_weights.rst │ │ ├── gs_quant.markets.position_set.PositionSet.from_dicts.rst │ │ ├── gs_quant.markets.position_set.PositionSet.from_frame.rst │ │ ├── gs_quant.markets.position_set.PositionSet.from_list.rst │ │ ├── gs_quant.markets.position_set.PositionSet.get_positions.rst │ │ ├── gs_quant.markets.position_set.PositionSet.get_unpriced_positions.rst │ │ ├── gs_quant.markets.position_set.PositionSet.get_unresolved_positions.rst │ │ ├── gs_quant.markets.position_set.PositionSet.price.rst │ │ ├── gs_quant.markets.position_set.PositionSet.price_many.rst │ │ ├── gs_quant.markets.position_set.PositionSet.remove_unpriced_positions.rst │ │ ├── gs_quant.markets.position_set.PositionSet.remove_unresolved_positions.rst │ │ ├── gs_quant.markets.position_set.PositionSet.resolve.rst │ │ ├── gs_quant.markets.position_set.PositionSet.resolve_many.rst │ │ ├── gs_quant.markets.position_set.PositionSet.to_frame.rst │ │ ├── gs_quant.timeseries.algebra.abs_.rst │ │ ├── gs_quant.timeseries.algebra.add.rst │ │ ├── gs_quant.timeseries.algebra.and_.rst │ │ ├── gs_quant.timeseries.algebra.ceil.rst │ │ ├── gs_quant.timeseries.algebra.divide.rst │ │ ├── gs_quant.timeseries.algebra.exp.rst │ │ ├── gs_quant.timeseries.algebra.filter_.rst │ │ ├── gs_quant.timeseries.algebra.filter_dates.rst │ │ ├── gs_quant.timeseries.algebra.floor.rst │ │ ├── gs_quant.timeseries.algebra.floordiv.rst │ │ ├── gs_quant.timeseries.algebra.if_.rst │ │ ├── gs_quant.timeseries.algebra.log.rst │ │ ├── gs_quant.timeseries.algebra.multiply.rst │ │ ├── gs_quant.timeseries.algebra.not_.rst │ │ ├── gs_quant.timeseries.algebra.or_.rst │ │ ├── gs_quant.timeseries.algebra.power.rst │ │ ├── gs_quant.timeseries.algebra.sqrt.rst │ │ ├── gs_quant.timeseries.algebra.subtract.rst │ │ ├── gs_quant.timeseries.algebra.weighted_sum.rst │ │ ├── gs_quant.timeseries.analysis.compare.rst │ │ ├── gs_quant.timeseries.analysis.count.rst │ │ ├── gs_quant.timeseries.analysis.diff.rst │ │ ├── gs_quant.timeseries.analysis.first.rst │ │ ├── gs_quant.timeseries.analysis.lag.rst │ │ ├── gs_quant.timeseries.analysis.last.rst │ │ ├── gs_quant.timeseries.analysis.last_value.rst │ │ ├── gs_quant.timeseries.backtesting.basket_series.rst │ │ ├── gs_quant.timeseries.datetime.align.rst │ │ ├── gs_quant.timeseries.datetime.bucketize.rst │ │ ├── gs_quant.timeseries.datetime.date_range.rst │ │ ├── gs_quant.timeseries.datetime.day.rst │ │ ├── gs_quant.timeseries.datetime.interpolate.rst │ │ ├── gs_quant.timeseries.datetime.month.rst │ │ ├── gs_quant.timeseries.datetime.prepend.rst │ │ ├── gs_quant.timeseries.datetime.quarter.rst │ │ ├── gs_quant.timeseries.datetime.union.rst │ │ ├── gs_quant.timeseries.datetime.value.rst │ │ ├── gs_quant.timeseries.datetime.weekday.rst │ │ ├── gs_quant.timeseries.datetime.year.rst │ │ ├── gs_quant.timeseries.econometrics.annualize.rst │ │ ├── gs_quant.timeseries.econometrics.beta.rst │ │ ├── gs_quant.timeseries.econometrics.change.rst │ │ ├── gs_quant.timeseries.econometrics.correlation.rst │ │ ├── gs_quant.timeseries.econometrics.excess_returns_.rst │ │ ├── gs_quant.timeseries.econometrics.index.rst │ │ ├── gs_quant.timeseries.econometrics.max_drawdown.rst │ │ ├── gs_quant.timeseries.econometrics.prices.rst │ │ ├── gs_quant.timeseries.econometrics.returns.rst │ │ ├── gs_quant.timeseries.econometrics.sharpe_ratio.rst │ │ ├── gs_quant.timeseries.econometrics.volatility.rst │ │ ├── gs_quant.timeseries.statistics.cov.rst │ │ ├── gs_quant.timeseries.statistics.exponential_std.rst │ │ ├── gs_quant.timeseries.statistics.generate_series.rst │ │ ├── gs_quant.timeseries.statistics.max_.rst │ │ ├── gs_quant.timeseries.statistics.mean.rst │ │ ├── gs_quant.timeseries.statistics.median.rst │ │ ├── gs_quant.timeseries.statistics.min_.rst │ │ ├── gs_quant.timeseries.statistics.mode.rst │ │ ├── gs_quant.timeseries.statistics.percentile.rst │ │ ├── gs_quant.timeseries.statistics.percentiles.rst │ │ ├── gs_quant.timeseries.statistics.product.rst │ │ ├── gs_quant.timeseries.statistics.range_.rst │ │ ├── gs_quant.timeseries.statistics.std.rst │ │ ├── gs_quant.timeseries.statistics.sum_.rst │ │ ├── gs_quant.timeseries.statistics.var.rst │ │ ├── gs_quant.timeseries.statistics.winsorize.rst │ │ ├── gs_quant.timeseries.statistics.zscores.rst │ │ ├── gs_quant.timeseries.technicals.bollinger_bands.rst │ │ ├── gs_quant.timeseries.technicals.exponential_moving_average.rst │ │ ├── gs_quant.timeseries.technicals.exponential_spread_volatility.rst │ │ ├── gs_quant.timeseries.technicals.exponential_volatility.rst │ │ ├── gs_quant.timeseries.technicals.macd.rst │ │ ├── gs_quant.timeseries.technicals.moving_average.rst │ │ ├── gs_quant.timeseries.technicals.relative_strength_index.rst │ │ ├── gs_quant.timeseries.technicals.seasonally_adjusted.rst │ │ ├── gs_quant.timeseries.technicals.smoothed_moving_average.rst │ │ └── gs_quant.timeseries.technicals.trend.rst │ ├── html/ │ │ ├── .doctrees/ │ │ │ ├── environment.pickle │ │ │ ├── index.doctree │ │ │ └── timeseries.doctree │ │ ├── _sources/ │ │ │ └── timeseries.rst.txt │ │ ├── objects.inv │ │ └── searchindex.js │ ├── index.rst │ ├── instrument.rst │ ├── make.bat │ ├── markets.rst │ ├── models.rst │ ├── module/ │ │ └── models.rst │ ├── modules/ │ │ └── gs_quant.markets.indices_utils.rst │ ├── risk.rst │ ├── searchdataext/ │ │ └── __init__.py │ └── timeseries.rst ├── gs_quant/ │ ├── __init__.py │ ├── _version.py │ ├── analytics/ │ │ ├── __init__.py │ │ ├── common/ │ │ │ ├── __init__.py │ │ │ ├── constants.py │ │ │ ├── enumerators.py │ │ │ └── helpers.py │ │ ├── core/ │ │ │ ├── __init__.py │ │ │ ├── processor.py │ │ │ ├── processor_result.py │ │ │ └── query_helpers.py │ │ ├── datagrid/ │ │ │ ├── __init__.py │ │ │ ├── data_cell.py │ │ │ ├── data_column.py │ │ │ ├── data_row.py │ │ │ ├── datagrid.py │ │ │ ├── serializers.py │ │ │ └── utils.py │ │ ├── processors/ │ │ │ ├── __init__.py │ │ │ ├── analysis_processors.py │ │ │ ├── econometrics_processors.py │ │ │ ├── scale_processors.py │ │ │ ├── special_processors.py │ │ │ ├── statistics_processors.py │ │ │ └── utility_processors.py │ │ └── workspaces/ │ │ ├── __init__.py │ │ ├── components.py │ │ └── workspace.py │ ├── api/ │ │ ├── __init__.py │ │ ├── api_cache.py │ │ ├── api_session.py │ │ ├── data.py │ │ ├── fred/ │ │ │ ├── __init__.py │ │ │ ├── data.py │ │ │ └── fred_query.py │ │ ├── gs/ │ │ │ ├── __init__.py │ │ │ ├── assets.py │ │ │ ├── backtests.py │ │ │ ├── backtests_xasset/ │ │ │ │ ├── __init__.py │ │ │ │ ├── apis.py │ │ │ │ ├── json_encoders/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── request_encoders.py │ │ │ │ │ ├── response_datatypes/ │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ ├── generic_datatype_encoders.py │ │ │ │ │ │ ├── risk_result_datatype_encoders.py │ │ │ │ │ │ ├── risk_result_encoders.py │ │ │ │ │ │ └── test_backtest_datatypes_encoders.py │ │ │ │ │ └── response_encoders.py │ │ │ │ ├── request.py │ │ │ │ ├── response.py │ │ │ │ └── response_datatypes/ │ │ │ │ ├── __init__.py │ │ │ │ ├── backtest_datatypes.py │ │ │ │ ├── generic_backtest_datatypes.py │ │ │ │ ├── risk_result.py │ │ │ │ └── risk_result_datatypes.py │ │ │ ├── base_screener.py │ │ │ ├── carbon.py │ │ │ ├── content.py │ │ │ ├── countries.py │ │ │ ├── data.py │ │ │ ├── data_screen.py │ │ │ ├── datagrid.py │ │ │ ├── esg.py │ │ │ ├── federated_secmaster.py │ │ │ ├── groups.py │ │ │ ├── hedges.py │ │ │ ├── indices.py │ │ │ ├── monitors.py │ │ │ ├── parser.py │ │ │ ├── plots.py │ │ │ ├── portfolios.py │ │ │ ├── price.py │ │ │ ├── reports.py │ │ │ ├── risk.py │ │ │ ├── risk_models.py │ │ │ ├── scenarios.py │ │ │ ├── screens.py │ │ │ ├── secmaster.py │ │ │ ├── thematics.py │ │ │ ├── users.py │ │ │ └── workspaces.py │ │ ├── risk.py │ │ └── utils.py │ ├── backtests/ │ │ ├── SKILL.md │ │ ├── __init__.py │ │ ├── action_handler.py │ │ ├── actions.py │ │ ├── backtest_engine.py │ │ ├── backtest_objects.py │ │ ├── backtest_utils.py │ │ ├── core.py │ │ ├── data_handler.py │ │ ├── data_sources.py │ │ ├── decorator.py │ │ ├── equity_vol_engine.py │ │ ├── event.py │ │ ├── execution_engine.py │ │ ├── generic_engine.py │ │ ├── generic_engine_action_impls.py │ │ ├── order.py │ │ ├── predefined_asset_engine.py │ │ ├── strategy.py │ │ ├── strategy_systematic.py │ │ └── triggers.py │ ├── base.py │ ├── common.py │ ├── config/ │ │ ├── __init__.py │ │ └── options.py │ ├── config.ini │ ├── content/ │ │ ├── Contents.ipynb │ │ ├── Index.ipynb │ │ ├── README.md │ │ ├── __init__.py │ │ ├── events/ │ │ │ └── 00_gsquant_meets_markets/ │ │ │ ├── 00_us_election_analysis/ │ │ │ │ ├── 0000_macro_big_picture.ipynb │ │ │ │ ├── 0001_portfolios_and_var.ipynb │ │ │ │ ├── 0002_past_elections.ipynb │ │ │ │ └── 0003_trades.ipynb │ │ │ ├── 01_ideas_for_risk_re_rating/ │ │ │ │ ├── eq_sx5e_spx_vol_spread_trade.ipynb │ │ │ │ ├── fx_covid19_recovery_trade.ipynb │ │ │ │ └── inflation_covid19_recovery_trade.ipynb │ │ │ ├── 02_optimizing_equity_trading/ │ │ │ │ ├── qes_utils.py │ │ │ │ ├── quants_meet_markets_qes.ipynb │ │ │ │ ├── section_a_liquidity_and_clusters.ipynb │ │ │ │ ├── section_b_ownership.ipynb │ │ │ │ ├── section_c_apex.ipynb │ │ │ │ └── trade_list_world.csv │ │ │ └── 03_esg_basket_portfolio_optimisation/ │ │ │ └── quants_meet_markets_msci.ipynb │ │ ├── made_with_gs_quant/ │ │ │ ├── 1-Navigating Rates.ipynb │ │ │ ├── 10-Explaining Performance Drivers.ipynb │ │ │ ├── 11-FX Election Hedge.ipynb │ │ │ ├── 12-Structuring for Uncertaintly.ipynb │ │ │ ├── 13-Cyclicals.ipynb │ │ │ ├── 14-Curve Inversions.ipynb │ │ │ ├── 15-Hypothetical Lifetime Risk Calculator.ipynb │ │ │ ├── 2-Levels and Tails.ipynb │ │ │ ├── 3-Systematic Selling.ipynb │ │ │ ├── 4-Delta Hedging.ipynb │ │ │ ├── 5-What's New.ipynb │ │ │ ├── 6-Evolving Correlations.ipynb │ │ │ ├── 7-Predicting Performance and Live Risk.ipynb │ │ │ ├── 8-What's New Internal.ipynb │ │ │ └── 9-Hedging using Machine Learning Techniques.ipynb │ │ └── reports_and_screens/ │ │ ├── 00_fx/ │ │ │ ├── 0000_vol_screen.ipynb │ │ │ ├── 0001_vol_calendar_screen.ipynb │ │ │ ├── 0002_forward_vol_screen.ipynb │ │ │ ├── 0003_10x_binary_screen.ipynb │ │ │ ├── 0004_bear_put_spread_screen.ipynb │ │ │ ├── 0005_vol_skew.ipynb │ │ │ ├── 0006_vol_swap_spread.ipynb │ │ │ ├── 0007_eq_fx_hedges.ipynb │ │ │ ├── __init__.py │ │ │ └── vol_screen_app.py │ │ ├── 02_rates/ │ │ │ ├── 0000_vol_fixed_strike_grid.ipynb │ │ │ ├── 0001_vol_moneyness_screen.ipynb │ │ │ ├── 0002_swaption_carry_grid.ipynb │ │ │ └── 0003_spot_vol_shock.ipynb │ │ ├── 03_prime/ │ │ │ ├── 0001_gs_pb_data_case_study_sector_positioning.ipynb │ │ │ ├── 0002_gs_pb_data_case_study_flows.ipynb │ │ │ ├── 0003_gs_pb_data_case_study_factors.ipynb │ │ │ ├── 0004_gs_pb_data_case_study_country_positioning.ipynb │ │ │ └── 0005_gs_pb_data_case_study_leverage.ipynb │ │ ├── README.md │ │ └── __init__.py │ ├── context_base.py │ ├── data/ │ │ ├── __init__.py │ │ ├── coordinate.py │ │ ├── core.py │ │ ├── dataset.py │ │ ├── fields.py │ │ ├── log.py │ │ ├── query.py │ │ ├── stream.py │ │ └── utilities.py │ ├── datetime/ │ │ ├── __init__.py │ │ ├── date.py │ │ ├── gscalendar.py │ │ ├── point.py │ │ ├── relative_date.py │ │ ├── rules.py │ │ └── time.py │ ├── documentation/ │ │ ├── 00_data/ │ │ │ ├── 00_datasets/ │ │ │ │ └── examples/ │ │ │ │ ├── 0000_query_dataset.ipynb │ │ │ │ ├── 0001_get_dataset_coverage.ipynb │ │ │ │ ├── 0002_using_data_contexts.ipynb │ │ │ │ ├── 0003_get_data_using_coordinate.ipynb │ │ │ │ ├── 0004_get_all_assets_from_region.ipynb │ │ │ │ ├── 0005_get_vol_surface.ipynb │ │ │ │ ├── 0006_ptp_dataset.ipynb │ │ │ │ ├── 0007_query_fxvol.script.ipynb │ │ │ │ └── 0008_s3_partners_short_interest.ipynb │ │ │ ├── 01_analytics/ │ │ │ │ └── examples/ │ │ │ │ ├── 0000_charting_data.ipynb │ │ │ │ └── 0001_exporting_data.ipynb │ │ │ ├── 02_workspaces/ │ │ │ │ └── examples/ │ │ │ │ └── 0001_creating_a_workspace.ipynb │ │ │ ├── 03_visualizations/ │ │ │ │ └── examples/ │ │ │ │ └── 0001_creating_a_visualization.ipynb │ │ │ ├── 04_screens/ │ │ │ │ └── examples/ │ │ │ │ ├── 00_base_screener_api_tutorial.ipynb │ │ │ │ └── 01_screen_api_tutorial.ipynb │ │ │ └── covid/ │ │ │ └── Comparing, Reconciling, and Combining COVID-19 Data Sources.ipynb │ │ ├── 01_markets/ │ │ │ ├── 00_securities/ │ │ │ │ └── examples/ │ │ │ │ └── 0000_get_an_asset_from_identifier.ipynb │ │ │ └── 02_rdates/ │ │ │ └── examples/ │ │ │ ├── 0000_get_a_date_relative_from_today.ipynb │ │ │ ├── 0001_get_a_date_relative_from_a_base_date.ipynb │ │ │ ├── 0002_get_a_date_relative_using_pricing_context.ipynb │ │ │ ├── 0003_get_a_date_using_a_business_day_calendar.ipynb │ │ │ ├── 0004_chaining_relativedate_rules.ipynb │ │ │ └── 0005_passing_your_own_holiday_calendar.ipynb │ │ ├── 02_pricing_and_risk/ │ │ │ ├── 00_instruments_and_measures/ │ │ │ │ ├── examples/ │ │ │ │ │ ├── 00_instrument_basics/ │ │ │ │ │ │ ├── 01_view-trade-properties.ipynb │ │ │ │ │ │ ├── 02_get-a-property.ipynb │ │ │ │ │ │ ├── 03_set-a-property.ipynb │ │ │ │ │ │ ├── 04_enum-property.ipynb │ │ │ │ │ │ ├── 05_resolve-a-trade.ipynb │ │ │ │ │ │ ├── 06_market-data.ipynb │ │ │ │ │ │ └── 07_float-with-info.ipynb │ │ │ │ │ ├── 01_rates/ │ │ │ │ │ │ ├── 01_view_swap_definition.ipynb │ │ │ │ │ │ ├── 02_swap_trade_construction.ipynb │ │ │ │ │ │ ├── 03_calc_swap_price.ipynb │ │ │ │ │ │ ├── 04_calc_swap_risk_measures.ipynb │ │ │ │ │ │ ├── 05_calc_swap_price_in_pricing_context.ipynb │ │ │ │ │ │ ├── 06_calc_swap_price_historically.ipynb │ │ │ │ │ │ ├── 07_calc_swap_risks_in_pricing_context.ipynb │ │ │ │ │ │ ├── 08_calc_swap_risk_historically.ipynb │ │ │ │ │ │ ├── 09_swaption_trade_construction.ipynb │ │ │ │ │ │ ├── 10_straddle_price.ipynb │ │ │ │ │ │ ├── 11_midcurve_swaption_price.ipynb │ │ │ │ │ │ ├── 12_swap_future_cashflows.ipynb │ │ │ │ │ │ ├── 13_calc_xccy_swap_price.ipynb │ │ │ │ │ │ ├── 14_calc_xccy_swap_risk_measures.ipynb │ │ │ │ │ │ ├── 15_spread_option_grid_pricing.ipynb │ │ │ │ │ │ ├── 16_change_discount_curve.ipynb │ │ │ │ │ │ ├── 17_calc_xccy_swap_cashflows.ipynb │ │ │ │ │ │ ├── 18_solve_present_value.ipynb │ │ │ │ │ │ ├── 19_solve_delta.ipynb │ │ │ │ │ │ ├── 20_fix_float_legs_price.ipynb │ │ │ │ │ │ ├── 21_asset_swap_definition.ipynb │ │ │ │ │ │ ├── 22_cap_floor.ipynb │ │ │ │ │ │ └── 23_solve_vanna_&_volga.ipynb │ │ │ │ │ ├── 02_fx/ │ │ │ │ │ │ ├── 01_fx_fwd_trade_construction.ipynb │ │ │ │ │ │ ├── 02_fx_option_trade_construction.ipynb │ │ │ │ │ │ ├── 03_fx_vol_swap_trade_construction.ipynb │ │ │ │ │ │ ├── 04_fx_binary_trade_construction.ipynb │ │ │ │ │ │ ├── 05_calc_option_measures.ipynb │ │ │ │ │ │ ├── 06_fx_var_swap_trade_construction.ipynb │ │ │ │ │ │ ├── 07_fx_knockout_trade_construction.ipynb │ │ │ │ │ │ ├── 08_fx_double_touch_trade_construction.ipynb │ │ │ │ │ │ ├── 09_fx_delta_measures.ipynb │ │ │ │ │ │ └── 10_fx_gamma_measures.ipynb │ │ │ │ │ ├── 03_eq/ │ │ │ │ │ │ ├── 000306_calc_eq_option_greeks.ipynb │ │ │ │ │ │ ├── 01_calc_eq_option_price.ipynb │ │ │ │ │ │ ├── 02_calc_eq_option_risk_measures.ipynb │ │ │ │ │ │ ├── 03_calc_eq_option_price_and_risk_in_pricing_context.ipynb │ │ │ │ │ │ ├── 04_calc_eq_option_price_and_risk_historically.ipynb │ │ │ │ │ │ └── 05_calc_eq_option_portfolio_greeks.ipynb │ │ │ │ │ └── 04_credit/ │ │ │ │ │ ├── 01_cdindex_option_price.ipynb │ │ │ │ │ ├── 02_cdindex_option_xover_payer_spread.ipynb │ │ │ │ │ ├── 03_cdindex_option_risks.ipynb │ │ │ │ │ ├── 04_cdindex_price.ipynb │ │ │ │ │ └── 05_cdindex_roll_price.ipynb │ │ │ │ └── tutorials/ │ │ │ │ ├── Instruments.ipynb │ │ │ │ └── Measures.ipynb │ │ │ ├── 01_scenarios_and_contexts/ │ │ │ │ ├── examples/ │ │ │ │ │ ├── 00_market_objects/ │ │ │ │ │ │ ├── 010000_market_data_in_gs_quant.ipynb │ │ │ │ │ │ ├── 010001_market_objects.ipynb │ │ │ │ │ │ ├── 010002_marketdatapattern_basics.ipynb │ │ │ │ │ │ ├── 010003_marketdatapattern_construction_ir.ipynb │ │ │ │ │ │ ├── 010004_marketdatapattern_construction_ir_vol.ipynb │ │ │ │ │ │ ├── 010005_marketdatapattern_construction_ir_basis.ipynb │ │ │ │ │ │ ├── 010006_marketdatapattern_construction_ir_xc.ipynb │ │ │ │ │ │ ├── 010007_marketdatapattern_construction_fx.ipynb │ │ │ │ │ │ ├── 010008_marketdatapattern_construction_fx_fwd.ipynb │ │ │ │ │ │ ├── 010009_marketdatapattern_construction_fx_vol.ipynb │ │ │ │ │ │ └── 010010_marketdatapattern_construction_cd.ipynb │ │ │ │ │ ├── 01_rollfwd_shock/ │ │ │ │ │ │ ├── 01_rollfwd_for_trade.ipynb │ │ │ │ │ │ ├── 02_basic_use_of_rollfwd_scenario.ipynb │ │ │ │ │ │ ├── 03_rollfwd-showing-lifecycling-effects-on-swaps-and-swaptions.ipynb │ │ │ │ │ │ ├── 04_yield_curves_with_rollfwd.ipynb │ │ │ │ │ │ └── 05_rollfwd_swap_fwd_rates.ipynb │ │ │ │ │ ├── 02_market_shock/ │ │ │ │ │ │ ├── 010200_marketdatashockbasedscenario_basics.ipynb │ │ │ │ │ │ ├── 010201_rate_spot_shock.ipynb │ │ │ │ │ │ ├── 010202_rate_curve_shock.ipynb │ │ │ │ │ │ ├── 010203_rate_vol_shock.ipynb │ │ │ │ │ │ ├── 010204_delta_shock_equivalent.ipynb │ │ │ │ │ │ ├── 010205_vega_shock_equivalent.ipynb │ │ │ │ │ │ ├── 010206_eq_spot_shock.ipynb │ │ │ │ │ │ └── 010207_fx_shock_examples.ipynb │ │ │ │ │ ├── 03_composite_shocks/ │ │ │ │ │ │ ├── 010300_rate_composite_shocks.ipynb │ │ │ │ │ │ └── 010301_multiple_shocks.ipynb │ │ │ │ │ ├── 04_curve_shock/ │ │ │ │ │ │ ├── 010400_parallel_curve_shock.ipynb │ │ │ │ │ │ ├── 010401_curve_shock_midpoint.ipynb │ │ │ │ │ │ ├── 010402_bear_steepener_curve_shock.ipynb │ │ │ │ │ │ ├── 010403_bull_steepener_curve_shock.ipynb │ │ │ │ │ │ ├── 010404_bear_flattener_curve_shock.ipynb │ │ │ │ │ │ └── 010405_bull_flattener_curve_shock.ipynb │ │ │ │ │ ├── 05_market_override/ │ │ │ │ │ │ ├── 010501_eq_spot_override.ipynb │ │ │ │ │ │ └── 010502_eq_future_override.ipynb │ │ │ │ │ ├── 06_vol_market_override/ │ │ │ │ │ │ ├── 010601_eq_vol_override_eod.ipynb │ │ │ │ │ │ └── 010601_eq_vol_override_intraday.ipynb │ │ │ │ │ ├── 07_curve_overlay/ │ │ │ │ │ │ ├── 010700_discount_curve_overlay.ipynb │ │ │ │ │ │ └── CurveExample.csv │ │ │ │ │ ├── 08_multi_scenario/ │ │ │ │ │ │ └── 010801_multi_scenario_shock.ipynb │ │ │ │ │ └── 09_measure_scenario/ │ │ │ │ │ ├── 010901_fx_override_example.ipynb │ │ │ │ │ ├── 010902_rates_override_example.ipynb │ │ │ │ │ └── 010903_eq_override_example.ipynb │ │ │ │ └── tutorials/ │ │ │ │ ├── Pricing_Context.ipynb │ │ │ │ └── Scenarios.ipynb │ │ │ └── External Demo v1.ipynb │ │ ├── 03_portfolios/ │ │ │ ├── examples/ │ │ │ │ ├── .ipynb_checkpoints/ │ │ │ │ │ └── 030000_create_portfolio-checkpoint.ipynb │ │ │ │ ├── 030000_create_portfolio.ipynb │ │ │ │ ├── 030001_modify_instruments.ipynb │ │ │ │ ├── 030002_extracting_instruments_and_results.ipynb │ │ │ │ ├── 030003_resolve_portfolio.ipynb │ │ │ │ ├── 030004_price_portfolio.ipynb │ │ │ │ ├── 030005_calculate_portfolio_risk.ipynb │ │ │ │ ├── 030006_portfolio_grid_calc.ipynb │ │ │ │ ├── 030007_pnl_explain.ipynb │ │ │ │ ├── 030008_portflio_from_frame.ipynb │ │ │ │ ├── 030009_portfolio_risk_result_to_frame.ipynb │ │ │ │ ├── 030010_portfolio_intra_leg_dependencies.ipynb │ │ │ │ ├── 030011_portfolio_from_csv.ipynb │ │ │ │ └── my_excel_portfolio.csv │ │ │ └── tutorials/ │ │ │ ├── Create New Portfolio.ipynb │ │ │ ├── Portfolios.ipynb │ │ │ ├── Pull Portfolio Factor Risk Data.ipynb │ │ │ ├── Pull Portfolio Performance Data.ipynb │ │ │ ├── Pull Portfolio Risk Data.ipynb │ │ │ └── Update Historical Portfolio.ipynb │ │ ├── 04_backtesting/ │ │ │ ├── examples/ │ │ │ │ ├── 01_PredefinedAssetEngine/ │ │ │ │ │ └── 040100_simple_example.ipynb │ │ │ │ ├── 02_EquityVolEngine/ │ │ │ │ │ ├── 040200_strategy_simple.ipynb │ │ │ │ │ ├── 040201_strategy_delta_hedged.ipynb │ │ │ │ │ ├── 040202_strategy_pnl_decomposition.ipynb │ │ │ │ │ ├── 040203_strategy_with_signals.ipynb │ │ │ │ │ ├── 040204_strategy_market_model.ipynb │ │ │ │ │ └── 040205_strategy_scaled_add_action.ipynb │ │ │ │ └── 03_GenericEngine/ │ │ │ │ ├── 040300_strategy_periodic_trigger.ipynb │ │ │ │ ├── 040301_strategy_risk_trigger.ipynb │ │ │ │ ├── 040302_strategy_delta_hedge_FX.ipynb │ │ │ │ ├── 040303_strategy_delta_hedge_Rates.ipynb │ │ │ │ ├── 040304_strategy_mean_reversion.ipynb │ │ │ │ ├── 040305_strategy_exit_trade_action.ipynb │ │ │ │ ├── 040306_strategy_mkt_trigger.ipynb │ │ │ │ ├── 040307_strategy_seagull_bullish.ipynb │ │ │ │ ├── 040308_rebalance_action.ipynb │ │ │ │ ├── 040309_inflation_hedging_strategy.ipynb │ │ │ │ ├── 040310_initial_swap_hedging_strategy_varying_CSA.ipynb │ │ │ │ ├── 040311_handling_holidays.ipynb │ │ │ │ ├── 040312_strategy_scaled_add_action.ipynb │ │ │ │ ├── 040313_chained_actions.ipynb │ │ │ │ └── 040314_gradual_entries.ipynb │ │ │ └── tutorials/ │ │ │ ├── Backtesting.ipynb │ │ │ └── Basic backtest walkthrough.ipynb │ │ ├── 05_factor_models/ │ │ │ ├── 01_Factor_Models.ipynb │ │ │ └── 02_Upload_Factor_Models.ipynb │ │ ├── 06_baskets/ │ │ │ ├── examples/ │ │ │ │ ├── 01_basket_composition_data/ │ │ │ │ │ ├── 0000_get_latest_basket_composition.ipynb │ │ │ │ │ ├── 0001_get_basket_composition_for_date.ipynb │ │ │ │ │ ├── 0002_get_basket_composition_for_date_range.ipynb │ │ │ │ │ ├── 0003_get_full_basket_composition_history.ipynb │ │ │ │ │ ├── 0004_get_basket_composition_dates.ipynb │ │ │ │ │ └── 0005_get_basket_composition_dataset_coverage.ipynb │ │ │ │ ├── 02_basket_pricing_data/ │ │ │ │ │ ├── 0000_get_latest_basket_close_price.ipynb │ │ │ │ │ ├── 0001_get_basket_close_price_for_date.ipynb │ │ │ │ │ ├── 0002_get_basket_close_price_for_dates.ipynb │ │ │ │ │ └── 0003_get_full_basket_close_price_history.ipynb │ │ │ │ ├── 03_basket_creation/ │ │ │ │ │ ├── 0000_clone_basket_position_set.ipynb │ │ │ │ │ └── position_set/ │ │ │ │ │ ├── 0000_create_position_set_using_position_weights.ipynb │ │ │ │ │ ├── 0001_create_position_set_using_position_quantities.ipynb │ │ │ │ │ ├── 0002_create_position_set_of_equal_weight_from_list_of_identifiers.ipynb │ │ │ │ │ ├── 0003_equalize_position_set_weights.ipynb │ │ │ │ │ ├── 0004_add_position_to_existing_position_set.ipynb │ │ │ │ │ ├── 0005_create_position_set_from_list_of_dictionaries.ipynb │ │ │ │ │ ├── 0006_create_position_set_from_dataframe.ipynb │ │ │ │ │ ├── 0007_create_position_set_from_csv.ipynb │ │ │ │ │ ├── 0008_create_position_set_from_excel.ipynb │ │ │ │ │ ├── 0009_fetch_position_quantities_from_weights.ipynb │ │ │ │ │ ├── 0010_fetch_position_weights_from_quantities.ipynb │ │ │ │ │ ├── 0011_redistribute_position_weights.ipynb │ │ │ │ │ └── 0012_create_historical_position_sets_from_excel.ipynb │ │ │ │ ├── 04_basket_corporate_actions_data/ │ │ │ │ │ ├── 0000_get_full_basket_corporate_actions_history.ipynb │ │ │ │ │ ├── 0001_get_basket_corporate_actions_for_dates.ipynb │ │ │ │ │ ├── 0002_get_basket_upcoming_corporate_actions.ipynb │ │ │ │ │ └── 0003_get_specific_corporate_actions_types.ipynb │ │ │ │ ├── 05_basket_fundamentals_data/ │ │ │ │ │ ├── 0000_get_basket_fundamentals_history.ipynb │ │ │ │ │ ├── 0001_get_basket_fundamentals_for_dates.ipynb │ │ │ │ │ ├── 0001_get_basket_fundamentals_specific_period_and_direction.ipynb │ │ │ │ │ ├── 0002_get_basket_fundamentals_for_date_range.ipynb │ │ │ │ │ ├── 0002_get_basket_fundamentals_specific_period_and_direction.ipynb │ │ │ │ │ └── 0003_get_specific_basket_fundamentals_metrics.ipynb │ │ │ │ ├── 06_basket_reports/ │ │ │ │ │ ├── 0000_create_new_factor_risk_report.ipynb │ │ │ │ │ ├── 0001_delete_existing_factor_risk_report.ipynb │ │ │ │ │ ├── 0002_get_existing_factor_risk_report.ipynb │ │ │ │ │ ├── 0003_get_all_existing_basket_reports.ipynb │ │ │ │ │ ├── 0004_get_status_of_all_existing_basket_reports.ipynb │ │ │ │ │ ├── 0005_poll_status_of_most_recent_basket_create_report.ipynb │ │ │ │ │ └── 0006_poll_report_status_using_report_id.ipynb │ │ │ │ ├── 07_basket_permissions/ │ │ │ │ │ ├── 0000_get_basket_permissions.ipynb │ │ │ │ │ ├── 0001_permission_application_to_basket.ipynb │ │ │ │ │ ├── 0002_permission_user_to_basket_by_email.ipynb │ │ │ │ │ ├── 0003_permission_group_to_basket.ipynb │ │ │ │ │ ├── 0004_remove_basket_permissions.ipynb │ │ │ │ │ └── 0005_get_your_permissioned_baskets.ipynb │ │ │ │ └── 08_flagship_baskets/ │ │ │ │ ├── 0000_get_flagship_baskets.ipynb │ │ │ │ ├── 0001_get_flagship_baskets_on_date.ipynb │ │ │ │ ├── 0002_get_flagship_baskets_containing_assets.ipynb │ │ │ │ ├── 0003_get_flagship_baskets_containing_assets_on_date.ipynb │ │ │ │ ├── 0004_get_flagship_baskets_prices.ipynb │ │ │ │ ├── 0005_get_flagship_baskets_prices_for_date_range.ipynb │ │ │ │ ├── 0006_get_flagship_baskets_compositions.ipynb │ │ │ │ ├── 0007_get_flagship_baskets_compositions_for_date_range.ipynb │ │ │ │ ├── 0008_filter_flagship_baskets_by_basket_type.ipynb │ │ │ │ ├── 0009_filter_flagship_baskets_by_region.ipynb │ │ │ │ ├── 0010_filter_flagship_baskets_by_styles.ipynb │ │ │ │ └── 0011_filter_flagship_baskets_by_ticker.ipynb │ │ │ └── tutorials/ │ │ │ ├── Basket Backcast.ipynb │ │ │ ├── Basket Create.ipynb │ │ │ ├── Basket Edit.ipynb │ │ │ └── Basket Rebalance.ipynb │ │ ├── 07_index/ │ │ │ ├── README.md │ │ │ ├── examples/ │ │ │ │ ├── 0000_get_index_Marquee_url.ipynb │ │ │ │ ├── 0001_get_index_close_prices.ipynb │ │ │ │ ├── 0002_get_index_fundamentals_data.ipynb │ │ │ │ ├── 0003_get_index_constituents.ipynb │ │ │ │ ├── 0004_get_index_weights_and_attribution.ipynb │ │ │ │ └── 0005_get_index_as_a_tree.ipynb │ │ │ └── tutorials/ │ │ │ └── GS Quant STS Index Tutorial.ipynb │ │ ├── 08_tree_entity/ │ │ │ └── tree_entity.ipynb │ │ ├── 09_security_master/ │ │ │ ├── Security Master SDK.ipynb │ │ │ └── Security Master.ipynb │ │ ├── 10_one_delta/ │ │ │ ├── Hedger/ │ │ │ │ ├── 01_Performance_Hedger.ipynb │ │ │ │ ├── 02_Factor_Hedger_(Axioma_Portfolio_Optimizer).ipynb │ │ │ │ └── 03_Continuous_Optimization.ipynb │ │ │ ├── Portfolios/ │ │ │ │ ├── 01_Create_Backcasted_Portfolio.ipynb │ │ │ │ ├── 02_Create_New_Historical_Portfolio.ipynb │ │ │ │ ├── 03_Update_Historical_Portfolio.ipynb │ │ │ │ ├── 04_Get_Factor_Attribution_Data.ipynb │ │ │ │ ├── 05_Manage_a_Fund_of_Funds.ipynb │ │ │ │ ├── 06_Get_Factor_Risk_Data.ipynb │ │ │ │ ├── 07_Get_Portfolio_Performance_Analytics.ipynb │ │ │ │ ├── 08_Get_ESG_Analytics.ipynb │ │ │ │ ├── 09_Get_Carbon_Analytics.ipynb │ │ │ │ └── 10_Get_Thematic_Analytics.ipynb │ │ │ └── Reports/ │ │ │ ├── 01_Factor_Risk_Report.ipynb │ │ │ ├── 02_Performance_Report.ipynb │ │ │ └── 03_Thematic_Report.ipynb │ │ ├── 11_macro_models/ │ │ │ ├── 01_Query_Macro_Models.ipynb │ │ │ └── 02_Upload_Macro_Models.ipynb │ │ ├── 12_scenarios/ │ │ │ ├── 01_Custom_Factor_Shocks.ipynb │ │ │ └── 02_Historical_Scenarios.ipynb │ │ ├── Contents.ipynb │ │ ├── Index.ipynb │ │ ├── README.md │ │ └── git │ ├── entities/ │ │ ├── __init__.py │ │ ├── entitlements.py │ │ ├── entity.py │ │ ├── entity_utils.py │ │ └── tree_entity.py │ ├── errors.py │ ├── instrument/ │ │ ├── __init__.py │ │ ├── core.py │ │ └── overrides.py │ ├── interfaces/ │ │ ├── __init__.py │ │ └── algebra.py │ ├── json_convertors.py │ ├── json_convertors_common.py │ ├── json_encoder.py │ ├── markets/ │ │ ├── __init__.py │ │ ├── baskets.py │ │ ├── core.py │ │ ├── factor.py │ │ ├── factor_analytics.py │ │ ├── hedge.py │ │ ├── historical.py │ │ ├── index.py │ │ ├── indices_utils.py │ │ ├── markets.py │ │ ├── optimizer.py │ │ ├── portfolio.py │ │ ├── portfolio_manager.py │ │ ├── portfolio_manager_utils.py │ │ ├── position_set.py │ │ ├── position_set_utils.py │ │ ├── report.py │ │ ├── report_utils.py │ │ ├── scenario.py │ │ ├── screens.py │ │ └── securities.py │ ├── models/ │ │ ├── __init__.py │ │ ├── epidemiology.py │ │ ├── risk_model.py │ │ └── risk_model_utils.py │ ├── priceable.py │ ├── quote_reports/ │ │ ├── __init__.py │ │ └── core.py │ ├── risk/ │ │ ├── __init__.py │ │ ├── core.py │ │ ├── measures.py │ │ ├── result_handlers.py │ │ ├── results.py │ │ ├── scenario_utils.py │ │ ├── scenarios.py │ │ └── transform.py │ ├── session.py │ ├── target/ │ │ ├── __init__.py │ │ ├── assets.py │ │ ├── assets_screener.py │ │ ├── backtests.py │ │ ├── base_screener.py │ │ ├── charts.py │ │ ├── common.py │ │ ├── content.py │ │ ├── coordinates.py │ │ ├── countries.py │ │ ├── data.py │ │ ├── data_screen.py │ │ ├── groups.py │ │ ├── hedge.py │ │ ├── indices.py │ │ ├── instrument.py │ │ ├── measures.py │ │ ├── monitor.py │ │ ├── portfolios.py │ │ ├── positions_v2_pricing.py │ │ ├── price.py │ │ ├── reports.py │ │ ├── risk.py │ │ ├── risk_models.py │ │ ├── scenarios.py │ │ ├── screens.py │ │ ├── secmaster.py │ │ ├── trades.py │ │ ├── workflow_quote.py │ │ └── workspaces_markets.py │ ├── test/ │ │ ├── __init__.py │ │ ├── analytics/ │ │ │ ├── __init__.py │ │ │ ├── test_datagrid.py │ │ │ ├── test_sorting_and_filtering.py │ │ │ └── test_workspace.py │ │ ├── api/ │ │ │ ├── __init__.py │ │ │ ├── backtests_xasset/ │ │ │ │ ├── __init__.py │ │ │ │ ├── json_encoders/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── response_datatypes/ │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ ├── test_risk_result_datatype_encoders.py │ │ │ │ │ │ └── test_risk_result_encoders.py │ │ │ │ │ ├── test_request_encoders.py │ │ │ │ │ └── test_response_encoders.py │ │ │ │ ├── response_datatypes/ │ │ │ │ │ ├── test_backtest_datatypes.py │ │ │ │ │ ├── test_risk_result.py │ │ │ │ │ └── test_risk_result_datatypes.py │ │ │ │ ├── test_request.py │ │ │ │ └── test_response.py │ │ │ ├── test_assets.py │ │ │ ├── test_backtests.py │ │ │ ├── test_base_screener.py │ │ │ ├── test_cache.py │ │ │ ├── test_carbon.py │ │ │ ├── test_content.py │ │ │ ├── test_data.py │ │ │ ├── test_data_screen.py │ │ │ ├── test_esg.py │ │ │ ├── test_fred.py │ │ │ ├── test_groups.py │ │ │ ├── test_index.py │ │ │ ├── test_instruments.py │ │ │ ├── test_json.py │ │ │ ├── test_monitor.py │ │ │ ├── test_portfolios.py │ │ │ ├── test_reports.py │ │ │ ├── test_risk.py │ │ │ ├── test_risk_models.py │ │ │ ├── test_scenarios.py │ │ │ ├── test_target.py │ │ │ ├── test_thread_manager.py │ │ │ └── test_users.py │ │ ├── backtest/ │ │ │ ├── __init__.py │ │ │ ├── test_backtest_eq_vol_engine.py │ │ │ ├── test_backtest_flow_vol.py │ │ │ ├── test_backtest_predefined.py │ │ │ ├── test_generic_engine.py │ │ │ └── test_triggers.py │ │ ├── calc_cache/ │ │ │ ├── request00e305681f1f021b52ef57f22d4975f1.json │ │ │ ├── request02db37fadb8c59eb98457c277883b4af.json │ │ │ ├── request03cc64abac61cde741b4b5bf90fc3b0f.json │ │ │ ├── request04e5386077a564977740976627ab683d.json │ │ │ ├── request093e1ad35f25cb43fcad999f9adf8c50.json │ │ │ ├── request0b8e0fc36f0a438251dafabc0b82b1a7.json │ │ │ ├── request0c583177f98e8c0c7dba07ae2674c05a.json │ │ │ ├── request0f156bd153459340c4bdb832aa95a1d0.json │ │ │ ├── request102fef2814650c2f6ab945bb5f3cee73.json │ │ │ ├── request12e470a54f4aa7c362e1014136a5d35d.json │ │ │ ├── request14c45549336e6d4b159e517b21517d18.json │ │ │ ├── request159fa95af878447538185c20cedbd92c.json │ │ │ ├── request16d7d8f484a35cf5804f799fc6592d0b.json │ │ │ ├── request18e1caaae1f91d18c754005f495ade4a.json │ │ │ ├── request1bddc8fc32ddb8342e4bc48029eeddf1.json │ │ │ ├── request1c86709bb6a5113c228f95eba201756c.json │ │ │ ├── request1ce838a52895a8e0f92d5bb55bd34a39.json │ │ │ ├── request1d4539a3acd22c90869eab6aa89349e3.json │ │ │ ├── request21cb074ade64e87b2f347dcb5f3e1ca0.json │ │ │ ├── request2799d7b0273add0629fbc071aefb6009.json │ │ │ ├── request27dad63f70c50190d762787d5415fb1f.json │ │ │ ├── request286b0ac010bfdb78f33dfd318679886a.json │ │ │ ├── request2a045cde3970ab7b348df05e95e7b9c3.json │ │ │ ├── request2cf5ebd3b922341e1de6d9831f8f6c79.json │ │ │ ├── request2d4fd08fb9b3dcc46d38954586f8cab3.json │ │ │ ├── request2f3646d722cf8712bd4a452a2768482f.json │ │ │ ├── request363421aeeef6464692365989d32bb4f9.json │ │ │ ├── request36fb828c98daf78a60a1d02431710592.json │ │ │ ├── request3a4db3779266fe6fc7e31eead46d9996.json │ │ │ ├── request3ad3d9a8afd809af0b2b00617d46758d.json │ │ │ ├── request3ae2e32d798a86cd2605b23e17e8fb03.json │ │ │ ├── request3b59bb4f49f8e379b9b054736bc6fb94.json │ │ │ ├── request3ce9c60acb3eb164709a3f173c1fa3f2.json │ │ │ ├── request3d02eb9418db9237797173d4f68b4425.json │ │ │ ├── request404a1334920b60ed517c9383e2dcc32c.json │ │ │ ├── request4329d6a92dec3a15646a3298c22755b0.json │ │ │ ├── request43642a3bd1d558d1e0a4197c502075f5.json │ │ │ ├── request443b19a6e8283a3bb5063918acfa3d6f.json │ │ │ ├── request44b307c6f73d7e32328511a4bca9361f.json │ │ │ ├── request456dd772d92da1c02ed5ca0d1b3400f9.json │ │ │ ├── request46ded08f0698a95e461efdd890e29421.json │ │ │ ├── request472fd6073bc51283417f418843277e18.json │ │ │ ├── request4a0c2426dcbba307e6747466921d0ebb.json │ │ │ ├── request4be739b5768d699cb118e94e9c73a7d6.json │ │ │ ├── request4cc7e77d411ee5d32b4b68c559fd24eb.json │ │ │ ├── request4d50dbaed5c59d9f8ed4b824a5fa7b4c.json │ │ │ ├── request4e071d86397b95a0b0ad52aebb6b2e03.json │ │ │ ├── request4fbbe5f6fe580bd5e55bbd60e8302c07.json │ │ │ ├── request5010511210568f2494e7d715ddca3fe5.json │ │ │ ├── request535b30f63a7e89e8fa2e17fff4a92b2f.json │ │ │ ├── request53ced6371873432280aa8489c1b45f21.json │ │ │ ├── request5678213359b65da6a1851eeaa706ff24.json │ │ │ ├── request5711e73d9fdfbee2d67781cd43875f26.json │ │ │ ├── request58c9a938f95c4444a0923d7c4b80aedf.json │ │ │ ├── request5961d985671cd44ae28d46079a078af2.json │ │ │ ├── request5c61457b1908e376ae7737adc2e3b19c.json │ │ │ ├── request5d16500482e406ca27c4d57713fe3058.json │ │ │ ├── request5dd997fbddd11bb0c5408ddd86b647d1.json │ │ │ ├── request6314f68c62d451df39cff28d0105f250.json │ │ │ ├── request659985875ffd9309df838437a1a2763b.json │ │ │ ├── request65dc6b58f9c22777f12c1d2971b23523.json │ │ │ ├── request65e287923a4ebf427e6a646c99e200ca.json │ │ │ ├── request66b092215599bb11f8a03a3ff6a4eb3a.json │ │ │ ├── request678cb7e6ab71f2441e6c2f5a917d37be.json │ │ │ ├── request6942e2686a9fe4a5d47af94a897be7e6.json │ │ │ ├── request6a14f7b7044f04669b2c08775f555232.json │ │ │ ├── request6a7213dc0f42bdb7d52e861ee1ec6fc6.json │ │ │ ├── request6b1d7d732da252ae69acdc3a3c2f6eee.json │ │ │ ├── request6c6a6e62e91685350853b59ec792b962.json │ │ │ ├── request6d08be7030b4cb7b9037efcdeb81972a.json │ │ │ ├── request6d7cd2a1c692ce0886cdee9506101e89.json │ │ │ ├── request6eb85d48e09f538f150400127fd73054.json │ │ │ ├── request6efd87b3691c5bc5d90f6e638748dc98.json │ │ │ ├── request6f8d6b283e5fe8b79e66975b2ed587a7.json │ │ │ ├── request70a863d464f50abba7a86e6d46cb2873.json │ │ │ ├── request717adad42d5d576152b1f6d809a3c8da.json │ │ │ ├── request73d0ff7060259d01f1bdbbd7acda5686.json │ │ │ ├── request77e65ffbd857c39cf14a9ad74279c7ba.json │ │ │ ├── request7833cccde5be44847d2834c320efac2b.json │ │ │ ├── request78dbb0af61a1e23a0f2614eb98bb46c0.json │ │ │ ├── request78ee70188fa3e6dce58e75f8df9a701f.json │ │ │ ├── request7c05dd05540a5377e1487ef4f0c54c48.json │ │ │ ├── request7c0ed6f293c87c377c58c8d911aaa777.json │ │ │ ├── request7dde687744f571fd6650ad7399203ac5.json │ │ │ ├── request816449d2d775bc083cf300974bdc10af.json │ │ │ ├── request85a1eff8ed194c127249a682db2d27cf.json │ │ │ ├── request878bab10c211dd44cb4553f6dc484f83.json │ │ │ ├── request895686a32c6921f666fe79c10493bb1f.json │ │ │ ├── request8ac2bf9b09cc9383988a934fe691692a.json │ │ │ ├── request8b7a3ed64b4a972e90f48ad347670813.json │ │ │ ├── request8bc8f9c4da52c90d51b3e18e1300c6d4.json │ │ │ ├── request8bfa8a8950c0bdfc209c49e531739006.json │ │ │ ├── request8d2c2ca8df0fc30cb3152076a8af6016.json │ │ │ ├── request90eccd55b0448292d8511e73985542d7.json │ │ │ ├── request914b625be8999c92f5b0fb21cb5c39f4.json │ │ │ ├── request91c941704f6cfc578724277dbb79d456.json │ │ │ ├── request9228e718c64b256d661a1d93909708c7.json │ │ │ ├── request9484a9be138ecb74c7241db00fd05dcd.json │ │ │ ├── request95d170fa02a120482c2f0497c72d5716.json │ │ │ ├── request9a319113c6ca13dad7540c7fd90825ba.json │ │ │ ├── request9da53bdb54ed741dd7b73e22896e610a.json │ │ │ ├── requesta3af76c3bf560d090aeb79bc6cdde208.json │ │ │ ├── requesta473c17920ab76f366755aa775da6371.json │ │ │ ├── requesta58fc4bd517e4a3f1a277f68287609f3.json │ │ │ ├── requesta5eb6cf7f7a7e728b9137396b59150b0.json │ │ │ ├── requestaa5e55819e3a43cca2b284c2119e7525.json │ │ │ ├── requestaaedaa96766d75c664e8b3898af8365e.json │ │ │ ├── requestad0c6bd76338f1bf498b16070275d04b.json │ │ │ ├── requestaf28694ceb9c1d15492c7700c07306e6.json │ │ │ ├── requestb0b099f6cc16db5524cb336b6373371e.json │ │ │ ├── requestb0b66e7402bd021f6f6f81f69bf9ef18.json │ │ │ ├── requestb112a918a2ece9a875d1c8d0856e1e68.json │ │ │ ├── requestb2ea00c2300d04ad523903f37a562dff.json │ │ │ ├── requestb417b5930a480b4bb07a2793b782c32d.json │ │ │ ├── requestb90286425e6ad1f5a3e66eb9bfc5b528.json │ │ │ ├── requestba369474db70c4ca2f99d1236c773532.json │ │ │ ├── requestbac6a90eed6358050cc451b7346dbae5.json │ │ │ ├── requestbb865103df394a5176b211859dfca415.json │ │ │ ├── requestc236b51cddef2fe8297064984e04c805.json │ │ │ ├── requestc39de7d52670682c117b785d51c8b4a6.json │ │ │ ├── requestc61016e904bc4d53f10bf8a9d42b7c84.json │ │ │ ├── requestc61cdb727bd2ab602ece911831deb892.json │ │ │ ├── requestc64a55ad8b16703d13479ff3b11b70ec.json │ │ │ ├── requestc9114e54aa19a8b53536ffee44a47797.json │ │ │ ├── requestc9537f6814eae8f18ee5fc96495e50bb.json │ │ │ ├── requestca27b0fb75992d8bbf6e9c9f6f84ba73.json │ │ │ ├── requestcb0e3c415937bb510a07119d29d30f6c.json │ │ │ ├── requestcb5f0c0bf31052204b28b68fd4464ac2.json │ │ │ ├── requestcc779554821681f119c9f516e46e3572.json │ │ │ ├── requestcce834252e64fa6189dbf804832284d6.json │ │ │ ├── requestcea65b205ad7e24117413111443a5d21.json │ │ │ ├── requestcf7ff46417f372533eec9ff30e85e53a.json │ │ │ ├── requestcfd7a039b31a326a7f59903d43a0d82f.json │ │ │ ├── requestd5a11b24c7329de1b599df1a3e5dd3a5.json │ │ │ ├── requestd985fa8d172480fdad5e1d2593ad8bac.json │ │ │ ├── requestd9f1692ebd3ee6eeae81298c1e555c97.json │ │ │ ├── requestdafae46704002ad2c1a2e8bc9a8f6bd4.json │ │ │ ├── requestdb4285900a117c954874542ae9a61712.json │ │ │ ├── requestdba69f5aa7819199c2345b2e872bf817.json │ │ │ ├── requeste0b867518c8f5765d221460aa462b9a1.json │ │ │ ├── requeste0d1a0fc88ab2155bafe544f85f751bd.json │ │ │ ├── requeste114a9f5fd19a44b1ec99394536dd4e9.json │ │ │ ├── requeste335e05ef6904adc1c56772fef5b92c1.json │ │ │ ├── requeste6809f076a1e32621a6124e017302856.json │ │ │ ├── requeste6d7f90423e2ce1c3be3b513f67aed72.json │ │ │ ├── requestf097e66ab00cfd403830fe9718723934.json │ │ │ ├── requestf1a8064322aba584bed2936c03ef96e7.json │ │ │ ├── requestf3acb9755ef5e82153b1ee6c24deff72.json │ │ │ ├── requestf48c727eb8f636d8d5aec677e3379e98.json │ │ │ ├── requestf58b3a2ad19557b466036841eb110ff0.json │ │ │ ├── requestf6793717b5017cc15a746e90ce585cc3.json │ │ │ ├── requestfa39f59c007048d21f5fcebb516a55c3.json │ │ │ ├── requestfaea73719549e6087d5398b07d322976.json │ │ │ ├── requestfb80e8b3c71c17a77857b7df855d5b7c.json │ │ │ └── requestfce828ba2ccac19204c1194026c7390c.json │ │ ├── config/ │ │ │ └── test_options.py │ │ ├── data/ │ │ │ ├── __init__.py │ │ │ ├── test_data_caching.py │ │ │ ├── test_data_coordinate.py │ │ │ ├── test_dataset.py │ │ │ └── test_query.py │ │ ├── datetime_/ │ │ │ ├── __init__.py │ │ │ ├── test_date.py │ │ │ ├── test_gscalendar.py │ │ │ ├── test_point.py │ │ │ ├── test_relative_date.py │ │ │ └── test_time.py │ │ ├── entities/ │ │ │ ├── test_entitlements.py │ │ │ ├── test_group.py │ │ │ └── test_user.py │ │ ├── fixtures/ │ │ │ ├── __init__.py │ │ │ └── content.py │ │ ├── markets/ │ │ │ ├── __init__.py │ │ │ ├── test_baskets.py │ │ │ ├── test_close_market.py │ │ │ ├── test_hedger.py │ │ │ ├── test_instrument.py │ │ │ ├── test_portfolio.py │ │ │ ├── test_portfolio_manager.py │ │ │ ├── test_position_set.py │ │ │ ├── test_pricing_context.py │ │ │ ├── test_report.py │ │ │ ├── test_scenarios.py │ │ │ └── test_securities.py │ │ ├── mock_data_test_utils.py │ │ ├── models/ │ │ │ ├── __init__.py │ │ │ ├── test_epidemiology.py │ │ │ ├── test_risk_model.py │ │ │ └── test_risk_model_utils.py │ │ ├── resources/ │ │ │ ├── MA4B66MW5E27U8P32SB-xrefs.json │ │ │ ├── MA4B66MW5E27U8P32SB.json │ │ │ ├── MA4J1YB8XZP2BPT8-xrefs.json │ │ │ ├── MA4J1YB8XZP2BPT8.json │ │ │ ├── MA66CZBQJST05XKG-xrefs.json │ │ │ ├── MA66CZBQJST05XKG.json │ │ │ ├── MAJ6SEQH3GT0GA2Z-xrefs.json │ │ │ ├── MAJ6SEQH3GT0GA2Z.json │ │ │ ├── MAJNQPFGN1EBDHAE-xrefs.json │ │ │ ├── MAJNQPFGN1EBDHAE.json │ │ │ ├── MAK1FHKH5P5GJSHH-xrefs.json │ │ │ ├── MAK1FHKH5P5GJSHH.json │ │ │ ├── MARCRZHY163GQ4H3.json │ │ │ ├── MASGTFZYNA7PMQ3H.json │ │ │ ├── MATGYV0J9MPX534Z-xrefs.json │ │ │ ├── MATGYV0J9MPX534Z.json │ │ │ ├── MAYJPCVVF2RWXCES-xrefs.json │ │ │ ├── MAYJPCVVF2RWXCES.json │ │ │ ├── MAZ7RWC904JYHYPS-xrefs.json │ │ │ ├── MAZ7RWC904JYHYPS.json │ │ │ ├── MIDASER_SPX_USD.csv │ │ │ ├── SPX_50_icorr_in.csv │ │ │ ├── SPX_50_icorr_out.csv │ │ │ ├── SPX_50_rcorr_in.csv │ │ │ ├── SPX_50_rcorr_out.csv │ │ │ ├── SPX_50_weights.csv │ │ │ ├── Sharpe_SPX_0175.csv │ │ │ ├── asset-query-USDJPY.json │ │ │ ├── nobbid-xrefs.json │ │ │ └── nobbid.json │ │ ├── risk/ │ │ │ ├── test_measures.py │ │ │ └── test_results.py │ │ ├── test_base.py │ │ ├── test_session.py │ │ ├── timeseries/ │ │ │ ├── __init__.py │ │ │ ├── multi_measure/ │ │ │ │ ├── __init__.py │ │ │ │ ├── test_commod.py │ │ │ │ └── test_measure_registry.py │ │ │ ├── test_algebra.py │ │ │ ├── test_analysis.py │ │ │ ├── test_backtesting.py │ │ │ ├── test_datetime.py │ │ │ ├── test_econometrics.py │ │ │ ├── test_helper.py │ │ │ ├── test_measures.py │ │ │ ├── test_measures_countries.py │ │ │ ├── test_measures_factset.py │ │ │ ├── test_measures_fx_vol.py │ │ │ ├── test_measures_inflation.py │ │ │ ├── test_measures_portfolios.py │ │ │ ├── test_measures_rates.py │ │ │ ├── test_measures_reports.py │ │ │ ├── test_measures_risk_models.py │ │ │ ├── test_measures_xccy.py │ │ │ ├── test_rolling.py │ │ │ ├── test_statistics.py │ │ │ ├── test_tca.py │ │ │ ├── test_technicals.py │ │ │ ├── test_timeseries.py │ │ │ └── utils.py │ │ ├── tracing/ │ │ │ ├── __init__.py │ │ │ └── test_tracing.py │ │ └── utils/ │ │ ├── __init__.py │ │ ├── datagrid_test_utils.py │ │ ├── mock_calc.py │ │ ├── mock_data.py │ │ ├── mock_request.py │ │ ├── test_utilities.py │ │ └── test_utils.py │ ├── timeseries/ │ │ ├── __init__.py │ │ ├── algebra.py │ │ ├── analysis.py │ │ ├── backtesting.py │ │ ├── datetime.py │ │ ├── econometrics.py │ │ ├── helper.py │ │ ├── measure_registry.py │ │ ├── measures.py │ │ ├── measures_countries.py │ │ ├── measures_factset.py │ │ ├── measures_fx_vol.py │ │ ├── measures_helper.py │ │ ├── measures_inflation.py │ │ ├── measures_portfolios.py │ │ ├── measures_rates.py │ │ ├── measures_reports.py │ │ ├── measures_risk_models.py │ │ ├── measures_xccy.py │ │ ├── statistics.py │ │ ├── tca.py │ │ └── technicals.py │ ├── tracing/ │ │ ├── __init__.py │ │ └── tracing.py │ └── workflow/ │ ├── __init__.py │ └── workflow.py ├── pyproject.toml ├── requirements.txt ├── setup.cfg ├── setup.py └── versioneer.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ *.sh eol=lf gs_quant/_version.py export-subst ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: "[BUG]" labels: '' assignees: andyphillipsgs --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior with code example. **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Systems setup:** - OS: [e.g. Windows 10] - Python version - GS-Quant version [e.g. 0.7] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Describe the problem.** A clear and concise description of the problem. **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Are you willing to contribute** Yes/No **Additional context** Add any other context or screenshots about the feature request here ================================================ FILE: .github/workflows/python-publish.yml ================================================ # This workflows will upload a Python Package using Twine when a release is created # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries name: Upload Python Package on: release: types: [created] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v3 with: python-version: '3.10' - name: Install dependencies run: | python -m pip install --upgrade pip pip install build twine - name: Build and publish env: TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | python -m build twine upload dist/* ================================================ FILE: .gitignore ================================================ *.pyc gs_quant/.idea/ ================================================ FILE: .gitlab-ci.yml ================================================ include: - project: 'fp/open-source/os-ci' file: - '.gitlab-ci.yml' variables: CURL: /sw/gs/git-prod/dist/linux/bin/ GITLAB_SERVICES_HOME: /gns/area/certified/com/gs/gitlab/gitlab-services/gitlab-services-4.0 JAVA_HOME: /sw/external/jdk-11/ NODE: /gns/area/certified/external/org/nodejs/node.js/node_v12.21.0_linux_x64-12.21.0.gns/node_v12.21.0_linux_x64/node-v12.21.0-linux-x64/bin GS_PYPI_URL: "http://pypi.site.gs.com/simple" GS_PYPI_HOST: "pypi.site.gs.com" stages: - .pre - test - clmscan - etch_upload - .post cloud_etch_upload: stage: etch_upload tags: - linux - default - kubernetes image: registry.aws.site.gs.com:443/dx/ete/cloudetch-uploader:current script: - "$CLOUD_ETCH_UPLOAD" test:py39: image: "registry.aws.site.gs.com:443/dsml/python/python-raw-image/python3.9-rhel8:current" stage: test tags: [ linux, default, kubernetes ] before_script: - export PYTHONPATH=. - export PATH=$NODE:$CURL:$JAVA_HOME/bin:$GITLAB_SERVICES_HOME/scripts:$PATH - python3 -V - pip3 install -i $GS_PYPI_URL --trusted-host $GS_PYPI_HOST --upgrade pip - pip3 install -i $GS_PYPI_URL --trusted-host $GS_PYPI_HOST .[test] script: - pytest -rsx gs_quant/test --junitxml=pytest.xml artifacts: when: always paths: - "pytest.xml" ================================================ FILE: .gs-project.yml ================================================ productGuid: "product::820010" #etch section etch: testResultPaths: - type: "JUNIT" paths: - "pytest.xml" ================================================ FILE: .pre-commit-config.yaml ================================================ # run pip install .[develop] to install dependencies repos: - repo: local hooks: - id: ruff-lint name: Run ruff lint language: system types: [python] files: 'gs_quant/.*py$' entry: ruff check --fix - id: ruff-format name: Run ruff format language: system files: 'gs_quant/.*py$' entry: ruff format ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at gs-oss-abuse@gs.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to gs-quant This file contains information about reporting issues as well as contributing code. Make sure you read our [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md) before you start participating. # Development and Build Environment gs-quant's development environment relies on the following: ### 1) Python 3.9+ Available at [Python.org](https://www.python.org/downloads/) or from your OS's package manager. ### 2) IDE The main developers use [PyCharm](https://www.jetbrains.com/pycharm/) but you can use another IDE or editor. Please do not commit IDE files. # Issues Search the issue tracker for a relevant issue or create a new one. # Making changes Fork the repository in GitHub and make changes in your fork. Before you submit your first pull request, please first submit a DCO, per the instructions in the last section on this page. Finally, submit a pull request. In your pull requests: * Make sure you [rebase your fork](https://github.com/edx/edx-platform/wiki/How-to-Rebase-a-Pull-Request) so that pull requests can be fast-forward merges. * We generally prefer squashed commits, unless multi-commits add clarity or are required for mixed copyright commits. * Your commit message for your code must contain a `covered by: ` line. See above. * Every file you modify should contain a single line with copyright information after the Apache header: ``` //Portions copyright . Licensed under Apache 2.0 license ``` * New files must contain the standard Apache 2.0 header with appropriate copyright holder. * If you're going to contribute code from other open source projects, commit that code first with `covered by: ` where `` is license of the code being committed. Ensure the file retains its original copyright notice and add an appropriate line to NOTICE.txt in the same commit. You can then modify that code in subsequent commits with a reference to your DCO and copyright. # Licensing Please make sure that any new dependency licenses are listed in the following list of pre-approved licenses list: ``` MIT ASL (all versions) BSD BSD-like ``` # Coding Style Please see [PEP 8 - Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/). # Appendix: Contribution Prerequisite: Submitting a DCO If you have never contributed to gs-quant, or your copyright ownership has changed, you must first create a pull request that has a developer certificate of origin (DCO) in it. To create this file, follow these steps: For code you write, determine who the copyright owner is. If you are employed in the US, it's likely that your employer can exert copyright ownership over your work, even if the work was not done during regular working hours or using the employer's equipment. Copyright law is highly variable from jurisdiction to jurisdiction. Consult your employer or a lawyer if you are not sure. If you've determined that the copyright holder for the code you write is yourself, please fill out the following (replace all `<>` terms); place it in a file under `dco/.dco`. ``` 1) I, , certify that all work committed with the commit message "covered by: .dco" is my original work and I own the copyright to this work. I agree to contribute this code under the Apache 2.0 license. 2) I understand and agree all contribution including all personal information I submit with it is maintained indefinitely and may be redistributed consistent with the open source license(s) involved. This certification is effective for all code contributed from to 9999-01-01. ``` If you've determined that the copyright holder for the code you write is some other entity (e.g. your employer), you must ensure that you are authorized by the copyright holder to be able to license this code under the Apache 2.0 license for the purpose of contribution to gs-quant. Negotiating such authorization and administering the terms is entirely between you and the copyright holder. Please fill out the following (replace all `<>` terms); place it in a file under `dco/-.dco`. ``` 1) I, , certify that all work committed with the commit message "covered by: -.dco" is copyright and that I am authorized by to contribute this code under the Apache 2.0 license. 2) I understand and agree all contribution including all personal information I submit with it is maintained indefinitely and may be redistributed consistent with the open source license(s) involved. This certification is effective for all code contributed from to 9999-01-01. ``` `` must reference your real name; we will not accept aliases, pseudonyms or anonymous contributions. Issue a pull request with the appropriate DCO and a change to NOTICE.txt with one line `This product contains code copyright , licensed under Apache 2.0 license`. ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: MANIFEST.in ================================================ include gs_quant/config.ini include gs_quant/resources/asset-service.json include gs_quant/resources/data-service.json include gs_quant/resources/risk-service.json include gs_quant/resources/certificates/* include gs_quant/examples/* include gs_quant/guides/* include gs_quant/tutorials/* include gs_quant/docs/* include versioneer.py include gs_quant/_version.py ================================================ FILE: NOTICE ================================================ gs-quant Copyright 2018 Goldman Sachs ================================================ FILE: NOTICE.txt ================================================ gs-quant Copyright 2018-2019 Goldman Sachs This product contains code copyright Scott Weinstein, licensed under Apache 2.0 license ================================================ FILE: README.md ================================================ # GS Quant **GS Quant** is a Python toolkit for quantitative finance, created on top of one of the world’s most powerful risk transfer platforms. Designed to accelerate development of quantitative trading strategies and risk management solutions, crafted over 25 years of experience navigating global markets. It is created and maintained by quantitative developers (quants) at Goldman Sachs to enable the development of trading strategies and analysis of derivative products. GS Quant can be used to facilitate derivative structuring, trading, and risk management, or as a set of statistical packages for data analytics applications. In order to access the APIs you will need a client id and secret. These are available to institutional clients of Goldman Sachs. Please speak to your sales coverage or Marquee Sales for further information. Please refer to [Goldman Sachs Developer](https://developer.gs.com/docs/gsquant/) for additional information. ## Requirements * Python 3.9 or greater * Access to PIP package manager ## Installation ``` pip install gs-quant ``` ## Examples You can find examples, guides and tutorials in the respective folders on [Goldman Sachs Developer](https://developer.gs.com/docs/gsquant/). ## Help Please reach out to `gs-quant@gs.com` with any questions, comments or feedback. ================================================ FILE: SKILL.md ================================================ --- name: gs-quant description: This document covers the core workflows for using the `gs_quant` library: establishing a session, constructing and resolving instruments, building portfolios, pricing historically, and extracting results. For backtesting, see the dedicated guide at `gs_quant/backtests/SKILL.md`. # SKILL.md — gs_quant Usage Guide This document covers the core workflows for using the `gs_quant` library: establishing a session, constructing and resolving instruments, building portfolios, pricing historically, and extracting results. > **Backtesting:** For a comprehensive guide to the backtesting framework (engines, triggers, actions, strategies, and result extraction), see [`gs_quant/backtests/SKILL.md`](gs_quant/backtests/SKILL.md). --- ## 1. Creating a Session with `GsSession.use` All API communication in `gs_quant` flows through an authenticated `GsSession`. Before making any pricing or data calls you must initialise a session with `GsSession.use()`. ### OAuth2 (Application Credentials) ```python from gs_quant.session import GsSession, Environment GsSession.use( environment_or_domain=Environment.PROD, client_id='my_client_id', client_secret='my_client_secret', scopes=('run_analytics',) ) ``` - **`environment_or_domain`** — `Environment.PROD` (default), `Environment.QA`, or `Environment.DEV`. You can also pass a raw URL string. - **`client_id` / `client_secret`** — OAuth2 application credentials. When both are provided the library creates an `OAuth2Session`. - **`scopes`** — Optional iterable of `GsSession.Scopes` values. Usually when pricing trade you will need `RUN_ANALYTICS`. ### Kerberos / SSO (Internal GS) If no `client_id` is supplied, the library will attempt Kerberos or pass-through authentication automatically: This is for internal GS users only and requires appropriate network access and installation of gs_quant_internal. ```python GsSession.use(Environment.PROD) ``` ### Using as a Context Manager `GsSession` can also be used as a context manager so the session is cleaned up on exit: ```python with GsSession.get(Environment.PROD, client_id='...', client_secret='...') as session: # session is active inside this block ... ``` ### Verifying the Session After calling `GsSession.use(...)`, the active session is accessible as: ```python GsSession.current # the currently active GsSession instance ``` All subsequent API calls (instrument resolution, pricing, data queries) will use this session automatically. --- ## 2. Constructing Trades with `gs_quant.instrument` Instruments are the building blocks of gs_quant. Every tradeable product is represented as a dataclass in `gs_quant.instrument`. Construct an instrument by importing its class and supplying the key economic parameters — any parameter you omit will be resolved by the server later. ### Common Instrument Examples #### Interest Rate Swap ```python from gs_quant.instrument import IRSwap swap = IRSwap( pay_or_receive='Pay', # 'Pay' or 'Receive' the fixed leg termination_date='10y', # tenor or explicit date notional_currency='USD', # currency fixed_rate=0.03, # optional — leave None to resolve at market ) ``` #### Interest Rate Swaption ```python from gs_quant.instrument import IRSwaption swaption = IRSwaption( pay_or_receive='Receive', termination_date='10y', notional_currency='EUR', expiration_date='1y', strike='ATM', ) ``` #### Interest Rate Cap ```python from gs_quant.instrument import IRCap cap = IRCap( termination_date='1y', notional_currency='USD', ) ``` #### FX Option ```python from gs_quant.instrument import FXOption option = FXOption( pair='EURUSD', expiration_date='3m', option_type='Call', strike_price='ATMF', notional_amount=10e6, ) ``` #### FX Forward ```python from gs_quant.instrument import FXForward fwd = FXForward( pair='USDJPY', settlement_date='6m', notional_amount=10e6, ) ``` ### Important: FX Instrument Pitfalls There are two common mistakes when working with FX options that can lead to confusing results: #### 1. Always Set `premium=0` on FX Options When constructing FX options (FXOption, FXBinary, FXMultiCrossBinary, etc.), if you don't specify a `premium`, the instrument resolution will automatically set a premium such that the `DollarPrice` becomes zero. This is by design — it represents a "fair value" trade where the premium exactly offsets the option value. **Problem:** If you want to know the cost/value of the option, you'll always get 0. **Solution:** Always set `premium=0` explicitly when you want `DollarPrice` to return the actual option value: ```python from gs_quant.instrument import FXOption, FXBinary # WRONG - DollarPrice will be ~0 after resolution option_wrong = FXOption( pair='EURUSD', expiration_date='3m', option_type='Call', strike_price='ATMF', notional_amount=10e6, ) # CORRECT - DollarPrice will be the option value option_correct = FXOption( pair='EURUSD', expiration_date='3m', option_type='Call', strike_price='ATMF', notional_amount=10e6, premium=0, # <-- Important! ) # Same applies to FXBinary binary = FXBinary( pair='EURUSD', buy_sell=BuySell.Buy, option_type=OptionType.Call, strike_price='s', notional_amount=1e6, notional_currency=Currency.USD, expiration_date='3m', premium=0, # <-- Important! ) ``` #### 2. FXMultiCrossBinaryLeg Uses Different OptionType Values When constructing `FXMultiCrossBinaryLeg` objects (used within `FXMultiCrossBinary` for dual digital options), you must use `OptionType.Binary_Call` or `OptionType.Binary_Put` — **not** `OptionType.Call` or `OptionType.Put`. This is different from `FXBinary` which uses `OptionType.Call` / `OptionType.Put`. ```python from gs_quant.instrument import FXBinary, FXMultiCrossBinary, FXMultiCrossBinaryLeg from gs_quant.common import BuySell, OptionType, Currency # FXBinary uses OptionType.Call / OptionType.Put single_digital = FXBinary( pair='EURUSD', buy_sell=BuySell.Buy, option_type=OptionType.Call, # <-- Call or Put strike_price='s', notional_amount=1e6, notional_currency=Currency.USD, expiration_date='3m', premium=0, ) # FXMultiCrossBinaryLeg uses OptionType.Binary_Call / OptionType.Binary_Put dual_digital = FXMultiCrossBinary( legs=( FXMultiCrossBinaryLeg( pair='EURUSD', option_type=OptionType.Binary_Call, # <-- Binary_Call or Binary_Put strike_price='s', ), FXMultiCrossBinaryLeg( pair='USDJPY', option_type=OptionType.Binary_Call, # <-- Binary_Call or Binary_Put strike_price='s', ), ), buy_sell=BuySell.Buy, notional_amount=1e6, notional_currency=Currency.USD, expiration_date='3m', premium=0, # <-- Don't forget this too! ) ``` #### Equity Option ```python from gs_quant.instrument import EqOption eq_opt = EqOption( underlier='.SPX', expiration_date='3m', strike_price='ATMF', option_type='Call', option_style='European', ) ``` You can set the instrument `name` property for easy identification later: ```python swap.name = 'USD 10y Payer' ``` --- ## 3. Resolving an Instrument When you construct an instrument you typically only specify a subset of its parameters. **Resolving** fills in all remaining fields by sending the instrument to the GS pricing service, which returns a fully specified version based on current market data. ```python from gs_quant.instrument import IRSwap swap = IRSwap('Pay', '10y', 'USD') # Before resolve: swap.fixed_rate is None swap.resolve() # After resolve: swap.fixed_rate is now populated with the current par rate print(swap.fixed_rate) # e.g. 0.0345 ``` ### What `resolve()` Does 1. Sends the instrument to the GS analytics service along with the current `PricingContext` (pricing date and market). 2. The service computes any missing parameters — for example the fixed rate of a par swap, the premium of an option, or the forward points of an FX forward. 3. By default (`in_place=True`), the instrument is updated in place. Pass `in_place=False` to receive a new resolved copy instead. ### Resolve Under a Specific Pricing Date ```python from gs_quant.markets import PricingContext import datetime as dt with PricingContext(pricing_date=dt.date(2025, 1, 15)): resolved = swap.resolve(in_place=False) resolved_swap = resolved.result() ``` ### Historical Resolution Resolution can be done across multiple dates via `HistoricalPricingContext`, but `in_place` must be `False`: ```python from gs_quant.markets import HistoricalPricingContext import datetime as dt with HistoricalPricingContext(dt.date(2025, 1, 1), dt.date(2025, 1, 31)): resolved_by_date = swap.resolve(in_place=False) # resolved_by_date is a dict of {date: resolved_instrument} ``` --- ## 4. Combining Instruments in Portfolios The `Portfolio` class groups instruments together so you can price, resolve, and analyse them as a single unit. ### Creating a Portfolio ```python from gs_quant.instrument import IRSwap, IRSwaption from gs_quant.markets.portfolio import Portfolio swap = IRSwap('Pay', '10y', 'USD', name='USD 10y Payer') swaption = IRSwaption('Receive', '10y', 'EUR', expiration_date='1y', name='EUR 1y10y Receiver') portfolio = Portfolio([swap, swaption], name='My Portfolio') ``` You can also construct a portfolio from a dictionary (keys become instrument names): ```python portfolio = Portfolio({ 'USD 10y Payer': IRSwap('Pay', '10y', 'USD'), 'EUR 5y Receiver': IRSwap('Receive', '5y', 'EUR'), }) ``` ### Nesting Portfolios Portfolios can contain other portfolios, creating a hierarchy: ```python usd_book = Portfolio([IRSwap('Pay', '5y', 'USD'), IRSwap('Receive', '10y', 'USD')], name='USD Book') eur_book = Portfolio([IRSwap('Pay', '5y', 'EUR')], name='EUR Book') master = Portfolio([usd_book, eur_book], name='Master Book') ``` ### Portfolio Operations ```python # Add instruments portfolio.append(IRSwap('Pay', '2y', 'GBP')) # Iterate for instrument in portfolio: print(instrument) # Access by index first = portfolio[0] # Access by name usd_swap = portfolio['USD 10y Payer'] # Number of top-level priceables len(portfolio) # All instruments across nested portfolios portfolio.all_instruments ``` ### Resolving a Portfolio ```python portfolio.resolve() # resolves all instruments in place ``` ### Pricing a Portfolio ```python from gs_quant.risk import DollarPrice, IRDelta # Single risk measure prices = portfolio.calc(DollarPrice) # Multiple risk measures at once results = portfolio.calc([DollarPrice, IRDelta]) ``` The result is a `PortfolioRiskResult` which can be sliced by instrument, risk measure, or date (see section 6). --- ## 5. Historical Pricing with `HistoricalPricingContext` `HistoricalPricingContext` lets you compute risk measures across a range of dates using the close-of-business market for each date. ### Basic Usage ```python import datetime as dt from gs_quant.instrument import IRSwap from gs_quant.markets import HistoricalPricingContext from gs_quant.risk import DollarPrice swap = IRSwap('Pay', '10y', 'USD') with HistoricalPricingContext(dt.date(2025, 1, 2), dt.date(2025, 1, 31)): price_f = swap.price() price_series = price_f.result() # a pandas Series indexed by date ``` ### By Number of Business Days Pass an integer to price over the last N business days: ```python with HistoricalPricingContext(10): price_f = swap.price() price_series = price_f.result() ``` ### With Custom Date List ```python dates = [dt.date(2025, 1, 2), dt.date(2025, 3, 15), dt.date(2025, 6, 30)] with HistoricalPricingContext(dates=dates): price_f = swap.price() ``` ### Key Parameters | Parameter | Description | |---|---| | `start` | Start date (or number of business days back from today) | | `end` | End date (defaults to today) | | `calendars` | Holiday calendar(s) for date generation | | `dates` | Explicit iterable of dates (mutually exclusive with `start`) | | `is_batch` | Use batch mode for long-running calculations (avoids timeouts) | | `is_async` | Return immediately without blocking | | `show_progress` | Display a tqdm progress bar | | `market_data_location` | `'NYC'`, `'LDN'`, or `'HKG'` (defaults to `'LDN'`) | | `csa_term` | CSA term for discounting | ### Historical Pricing with Portfolios ```python with HistoricalPricingContext(dt.date(2025, 1, 2), dt.date(2025, 1, 31)): result = portfolio.calc(DollarPrice) # result is a PortfolioRiskResult with a date dimension ``` ## 6. Result Extraction Calculation results in gs_quant are rich typed objects that carry metadata (risk key, unit, error info) alongside the actual values. Understanding the result types and how to extract data from them is essential. ### Result Types | Type | Description | |---|---| | `FloatWithInfo` | Scalar result (e.g. present value). Behaves like a `float` but carries a `risk_key` and `unit`. | | `SeriesWithInfo` | Time series result (historical pricing). A `pandas.Series` with metadata. | | `DataFrameWithInfo` | Structured/bucketed result (e.g. delta ladder). A `pandas.DataFrame` with metadata. | | `ErrorValue` | Indicates a calculation error. Check `.error` for the message. | | `MultipleRiskMeasureResult` | Dict-like mapping of `RiskMeasure → result` when multiple measures are requested on a single instrument. | | `PortfolioRiskResult` | Result for a portfolio — can be sliced by instrument, risk measure, or date. | ### Single Instrument Results ```python from gs_quant.instrument import IRSwap from gs_quant.risk import DollarPrice, IRDelta swap = IRSwap('Pay', '10y', 'USD') # Scalar result price = swap.dollar_price() # FloatWithInfo print(float(price)) # the numeric value # Local currency price local_price = swap.price() # FloatWithInfo # Structured result delta = swap.calc(IRDelta) # DataFrameWithInfo — a bucketed delta ladder print(delta) # displays as a DataFrame with columns like mkt_type, mkt_asset, etc. ``` ### Using Futures (Async / Batched) Inside an entered `PricingContext`, calculations return `PricingFuture` objects. Call `.result()` after exiting the context: ```python from gs_quant.markets import PricingContext with PricingContext(): price_f = swap.dollar_price() delta_f = swap.calc(IRDelta) price = price_f.result() # FloatWithInfo delta = delta_f.result() # DataFrameWithInfo ``` ### Multiple Risk Measures on a Single Instrument ```python from gs_quant.risk import DollarPrice, IRDelta, IRVega result = swap.calc([DollarPrice, IRDelta, IRVega]) # MultipleRiskMeasureResult price = result[DollarPrice] # FloatWithInfo delta = result[IRDelta] # DataFrameWithInfo vega = result[IRVega] # DataFrameWithInfo ``` ### Portfolio Results `portfolio.calc()` returns a `PortfolioRiskResult` which supports flexible slicing: ```python from gs_quant.risk import DollarPrice, IRDelta result = portfolio.calc([DollarPrice, IRDelta]) # Slice by risk measure prices = result[DollarPrice] # PortfolioRiskResult for DollarPrice only # Slice by instrument (name or object) swap_result = result['USD 10y Payer'] # MultipleRiskMeasureResult for that instrument swap_price = swap_result[DollarPrice] # FloatWithInfo # Slice by index first_result = result[0] # Iterate over instruments for instrument_result in result: print(instrument_result) # Aggregate across all instruments total_price = result[DollarPrice].aggregate() # sums all instrument prices ``` ### Historical Results When using `HistoricalPricingContext`, scalar results become time series: ```python import datetime as dt from gs_quant.markets import HistoricalPricingContext with HistoricalPricingContext(dt.date(2025, 1, 2), dt.date(2025, 1, 31)): price_f = swap.price() price_series = price_f.result() # SeriesWithInfo indexed by date print(price_series) # Access value for a specific date jan_15_price = price_series[dt.date(2025, 1, 15)] ``` Historical portfolio results can also be sliced by date: ```python with HistoricalPricingContext(dt.date(2025, 1, 2), dt.date(2025, 1, 31)): result = portfolio.calc(DollarPrice) # Slice by date jan_15 = result[dt.date(2025, 1, 15)] # PortfolioRiskResult for that single date # Available dates result.dates # tuple of dt.date ``` ### Converting to DataFrames All result types support conversion to pandas DataFrames: ```python # Portfolio result to DataFrame (pivoted) df = result.to_frame() # Unpivoted (raw records) df_raw = result.to_frame(values=None, index=None, columns=None) # Custom pivoting df_custom = result.to_frame(values='value', index='dates', columns='instrument_name') ``` `MultipleRiskMeasureResult` also supports `.to_frame()`: ```python multi_result = swap.calc([DollarPrice, IRDelta]) df = multi_result.to_frame() ``` ### Checking for Errors ```python from gs_quant.risk import ErrorValue price = swap.dollar_price() if isinstance(price, ErrorValue): print(f'Calculation failed: {price.error}') else: print(f'Price: {float(price)}') ``` ### Arithmetic on Results Results support arithmetic operations, which is useful for computing P&L or scaling: ```python # Multiply portfolio result by a scalar scaled = result * 1000 # Add results from different portfolios combined = result_a + result_b ``` --- ## 7. Backtesting gs_quant includes a full backtesting framework under `gs_quant.backtests`. It lets you define trading strategies as combinations of **triggers** (when to act) and **actions** (what to do), then simulate them historically using one of several engines. For the complete backtesting guide — including all trigger types, action types, engine selection, and result extraction — see: 📄 **[`gs_quant/backtests/SKILL.md`](gs_quant/backtests/SKILL.md)** ### Quick Example — Monthly FX Option Roll ```python from datetime import date from gs_quant.instrument import FXOption from gs_quant.common import BuySell, OptionType from gs_quant.backtests.triggers import PeriodicTrigger, PeriodicTriggerRequirements from gs_quant.backtests.actions import AddTradeAction from gs_quant.backtests.generic_engine import GenericEngine from gs_quant.backtests.strategy import Strategy from gs_quant.risk import Price start_date = date(2023, 1, 3) end_date = date(2024, 12, 31) call = FXOption( buy_sell=BuySell.Buy, option_type=OptionType.Call, pair='USDJPY', strike_price='ATMF', expiration_date='2y', name='2y_call', premium=0, ) trig_req = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m') action = AddTradeAction(call, '1m') trigger = PeriodicTrigger(trig_req, action) strategy = Strategy(None, trigger) GE = GenericEngine() backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True) # View results backtest.result_summary['Total'].plot(title='Performance') ``` ================================================ FILE: conftest.py ================================================ # This import is needed to register the magic pylint hooks to make it skip the integration tests # flake8: noqa # pylint: disable=wildcard-import, unused-wildcard-import from gs_quant.test.mock_data_test_utils import * ================================================ FILE: dco/Bryan Galindo.dco ================================================ 1) I, Bryan Galindo, certify that all work committed with the commit message "covered by: Bryan Galindo.dco" is my original work and I own the copyright to this work. I agree to contribute this code under the Apache 2.0 license. 2) I understand and agree all contribution including all personal information I submit with it is maintained indefinitely and may be redistributed consistent with the open source license(s) involved. This certification is effective for all code contributed from 2022-08-30 to 9999-01-01. ================================================ FILE: dco/Grant McGovern.dco ================================================ 1) I, Grant McGovern, certify that all work committed with the commit message "covered by: Grant McGovern.dco" is my original work and I own the copyright to this work. I agree to contribute this code under the Apache 2.0 license. 2) I understand and agree all contribution including all personal information I submit with it is maintained indefinitely and may be redistributed consistent with the open source license(s) involved. This certification is effective for all code contributed from 2019-05-14 to 9999-01-01. ================================================ FILE: dco/Maverick Lin.dco ================================================ 1) I, Maverick Lin, certify that all work committed with the commit message "covered by: Maverick Lin.dco" is my original work and I own the copyright to this work. I agree to contribute this code under the Apache 2.0 license. 2) I understand and agree all contribution including all personal information I submit with it is maintained indefinitely and may be redistributed consistent with the open source license(s) involved. This certification is effective for all code contributed from 2019-05-14 to 9999-01-01. ================================================ FILE: dco/Scott Weinstein.dco ================================================ 1) I, Scott Weinstein, certify that all work committed with the commit message "covered by: Scott Weinstein.dco" is my original work and I own the copyright to this work. I agree to contribute this code under the Apache 2.0 license. 2) I understand and agree all contribution including all personal information I submit with it is maintained indefinitely and may be redistributed consistent with the open source license(s) involved. This certification is effective for all code contributed from 2019-05-08 to 9999-01-01. ================================================ FILE: docs/Makefile ================================================ # Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) ================================================ FILE: docs/README.md ================================================ # Overview Sphinx-generated documentation for gs-quant codebase If you're looking for tutorials and other developer docs, check out [the developer-site repo](https://gitlab.gs.com/marquee/aurora/developer-site/). # Important Links - [GS Developer Marketing](https://developer.gs.com/discover/products/gs-quant/) - [GS Developer Docs](https://developer.gs.com/docs/gsquant/) # Contacts - Symphony room: gs_quant, GS Developer Site - Distribution list: gs-quant@gs.com ================================================ FILE: docs/_build/html/.buildinfo ================================================ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. config: a31b8105bdf16238d68e5a89b0da3f8a tags: 645f666f9bcd5a90fca523b33c5a78b7 ================================================ FILE: docs/_build/html/_modules/aenum.html ================================================ aenum — gs_quant 0.1 documentation

Source code for aenum

"""Python Advanced Enumerations & NameTuples"""

import sys as _sys
pyver = float('%s.%s' % _sys.version_info[:2])

import re

try:
    from collections import OrderedDict
except ImportError:
    OrderedDict = dict
from collections import defaultdict
try:
    import sqlite3
except ImportError:
    sqlite3 = None

if pyver >= 3:
    from functools import reduce

from operator import or_ as _or_, and_ as _and_, xor as _xor_, inv as _inv_
from operator import abs as _abs_, add as _add_, floordiv as _floordiv_
from operator import lshift as _lshift_, rshift as _rshift_, mod as _mod_
from operator import mul as _mul_, neg as _neg_, pos as _pos_, pow as _pow_
from operator import truediv as _truediv_, sub as _sub_
if pyver < 3:
    from operator import div as _div_

if pyver >= 3:
    from inspect import getfullargspec
    def getargspec(method):
        args, varargs, keywords, defaults, _, _, _ = getfullargspec(method)
        return args, varargs, keywords, defaults
else:
    from inspect import getargspec


__all__ = [
        'NamedConstant', 'constant', 'skip', 'nonmember', 'member', 'no_arg',
        'Enum', 'IntEnum', 'AutoNumberEnum', 'OrderedEnum', 'UniqueEnum',
        'StrEnum', 'UpperStrEnum', 'LowerStrEnum',
        'Flag', 'IntFlag',
        'AutoNumber', 'MultiValue', 'NoAlias', 'Unique',
        'enum', 'extend_enum', 'unique', 'enum_property',
        'NamedTuple', 'SqliteEnum',
        ]
if sqlite3 is None:
    __all__.remove('SqliteEnum')

version = 2, 2, 4

try:
    any
except NameError:
    def any(iterable):
        for element in iterable:
            if element:
                return True
        return False

try:
    basestring
except NameError:
    # In Python 2 basestring is the ancestor of both str and unicode
    # in Python 3 it's just str, but was missing in 3.1
    basestring = str

try:
    unicode
except NameError:
    # In Python 3 unicode no longer exists (it's just str)
    unicode = str

try:
    long
    baseinteger = int, long
except NameError:
    baseinteger = int
# deprecated
baseint = baseinteger

try:
    NoneType
except NameError:
    NoneType = type(None)

try:
    # derive from stdlib enum if possible
    import enum
    if hasattr(enum, 'version'):
        StdlibEnumMeta = StdlibEnum = None
    else:
        from enum import EnumMeta as StdlibEnumMeta, Enum as StdlibEnum
    del enum
except ImportError:
    StdlibEnumMeta = StdlibEnum = None

# will be exported later
AutoValue = AutoNumber = MultiValue = NoAlias = Unique = None

class enum_property(object):
    """
    This is a descriptor, used to define attributes that act differently
    when accessed through an enum member and through an enum class.
    Instance access is the same as property(), but access to an attribute
    through the enum class will look in the class' _member_map_.
    """

    def __init__(self, fget=None, doc=None, name=None):
        self.fget = fget
        self.__doc__ = doc or fget.__doc__
        self.name = name

    def __call__(self, func, doc=None):
        self.fget = func
        self.__doc__ = self.__doc__ or doc or func.__doc__

    def __get__(self, instance, ownerclass=None):
        if instance is None:
            try:
                return ownerclass._member_map_[self.name]
            except KeyError:
                raise AttributeError('%r not found in %r' % (self.name, ownerclass.__name__))
        else:
            if self.fget is not None:
                return self.fget(instance)
            else:
                # search through mro
                for base in ownerclass.__mro__[1:]:
                    if self.name in base.__dict__:
                        attr = base.__dict__[self.name]
                        break
                else:
                    raise AttributeError('%r not found in %r' % (self.name, instance))
                if isinstance(attr, classmethod):
                    attr = attr.__func__
                    return lambda *args, **kwds: attr(ownerclass, *args, **kwds)
                elif isinstance(attr, staticmethod):
                    return attr.__func__
                elif isinstance(attr, (property, enum_property)):
                    return attr.__get__(instance, ownerclass)
                elif callable(attr):
                    return lambda *arg, **kwds: attr(instance, *arg, **kwds)
                else:
                    return attr

    def __set__(self, instance, value):
        ownerclass = instance.__class__
        for base in ownerclass.__mro__[1:]:
            if self.name in base.__dict__:
                attr = base.__dict__[self.name]
                if isinstance(attr, property):
                    setter = attr.__set__
                    if setter is not None:
                        return setter(instance, value)
        else:
            raise AttributeError("can't set attribute %r" % (self.name, ))

    def __delete__(self, instance):
        raise AttributeError("can't delete attribute %r" % (self.name, ))

_RouteClassAttributeToGetattr = enum_property

class NonMember(object):
    """
    Protects item from becaming an Enum member during class creation.
    """
    def __init__(self, value):
        self.value = value

    def __get__(self, instance, ownerclass=None):
        return self.value
skip = nonmember = NonMember

class Member(object):
    """
    Forces item to became an Enum member during class creation.
    """
    def __init__(self, value):
        self.value = value
member = Member


def _is_descriptor(obj):
    """Returns True if obj is a descriptor, False otherwise."""
    return (
            hasattr(obj, '__get__') or
            hasattr(obj, '__set__') or
            hasattr(obj, '__delete__'))


def _is_dunder(name):
    """Returns True if a __dunder__ name, False otherwise."""
    return (len(name) > 4 and
            name[:2] == name[-2:] == '__' and
            name[2] != '_' and
            name[-3] != '_')


def _is_sunder(name):
    """Returns True if a _sunder_ name, False otherwise."""
    return (len(name) > 2 and
            name[0] == name[-1] == '_' and
            name[1] != '_' and
            name[-2] != '_')

def _is_internal_class(cls_name, obj):
    # only 3.3 and up, always return False in 3.2 and below
    if pyver < 3.3:
        return False
    else:
        qualname = getattr(obj, '__qualname__', False)
        return not _is_descriptor(obj) and qualname and re.search(r"\.?%s\.\w+$" % cls_name, qualname)

def _make_class_unpicklable(cls):
    """Make the given class un-picklable."""
    def _break_on_call_reduce(self, protocol=None):
        raise TypeError('%r cannot be pickled' % (self, ))
    cls.__reduce_ex__ = _break_on_call_reduce
    cls.__module__ = '<unknown>'

def _check_auto_args(method):
    """check if new generate method supports *args and **kwds"""
    if isinstance(method, staticmethod):
        method = method.__get__(type)
    method = getattr(method, 'im_func', method)
    args, varargs, keywords, defaults = getargspec(method)
    return varargs is not None and keywords is not None

def _get_attr_from_chain(cls, attr):
    sentinel = object()
    for basecls in cls.mro():
        obj = basecls.__dict__.get(attr, sentinel)
        if obj is not sentinel:
            return obj

def _value(obj):
    if isinstance(obj, (auto, constant)):
        return obj.value
    else:
        return obj

################
# Constant stuff
################

# metaclass and class dict for NamedConstant

class constant(object):
    '''
    Simple constant descriptor for NamedConstant and Enum use.
    '''
    def __init__(self, value, doc=None):
        self.value = value
        self.__doc__ = doc

    def __get__(self, *args):
        return self.value

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, self.value)

    def __and__(self, other):
        return _and_(self.value, _value(other))

    def __rand__(self, other):
        return _and_(_value(other), self.value)

    def __invert__(self):
        return _inv_(self.value)

    def __or__(self, other):
        return _or_(self.value, _value(other))

    def __ror__(self, other):
        return _or_(_value(other), self.value)

    def __xor__(self, other):
        return _xor_(self.value, _value(other))

    def __rxor__(self, other):
        return _xor_(_value(other), self.value)

    def __abs__(self):
        return _abs_(self.value)

    def __add__(self, other):
        return _add_(self.value, _value(other))

    def __radd__(self, other):
        return _add_(_value(other), self.value)

    def __neg__(self):
        return _neg_(self.value)

    def __pos__(self):
        return _pos_(self.value)

    if pyver < 3:
        def __div__(self, other):
            return _div_(self.value, _value(other))

    def __rdiv__(self, other):
        return _div_(_value(other), (self.value))

    def __floordiv__(self, other):
        return _floordiv_(self.value, _value(other))

    def __rfloordiv__(self, other):
        return _floordiv_(_value(other), self.value)

    def __truediv__(self, other):
        return _truediv_(self.value, _value(other))

    def __rtruediv__(self, other):
        return _truediv_(_value(other), self.value)

    def __lshift__(self, other):
        return _lshift_(self.value, _value(other))

    def __rlshift__(self, other):
        return _lshift_(_value(other), self.value)

    def __rshift__(self, other):
        return _rshift_(self.value, _value(other))

    def __rrshift__(self, other):
        return _rshift_(_value(other), self.value)

    def __mod__(self, other):
        return _mod_(self.value, _value(other))

    def __rmod__(self, other):
        return _mod_(_value(other), self.value)

    def __mul__(self, other):
        return _mul_(self.value, _value(other))

    def __rmul__(self, other):
        return _mul_(_value(other), self.value)

    def __pow__(self, other):
        return _pow_(self.value, _value(other))

    def __rpow__(self, other):
        return _pow_(_value(other), self.value)

    def __sub__(self, other):
        return _sub_(self.value, _value(other))

    def __rsub__(self, other):
        return _sub_(_value(other), self.value)



NamedConstant = None

class _NamedConstantDict(dict):
    """Track constant order and ensure names are not reused.

    NamedConstantMeta will use the names found in self._names as the
    Constant names.
    """
    def __init__(self):
        super(_NamedConstantDict, self).__init__()
        self._names = []

    def __setitem__(self, key, value):
        """Changes anything not dundered or not a constant descriptor.

        If an constant name is used twice, an error is raised; duplicate
        values are not checked for.

        Single underscore (sunder) names are reserved.
        """
        if _is_sunder(key):
            raise ValueError('_names_ are reserved for future NamedConstant use')
        elif _is_dunder(key):
            pass
        elif key in self._names:
            # overwriting an existing constant?
            raise TypeError('attempt to reuse name: %r' % (key, ))
        elif isinstance(value, constant) or not _is_descriptor(value):
            if key in self:
                # overwriting a descriptor?
                raise TypeError('%s already defined as: %r' % (key, self[key]))
            self._names.append(key)
        super(_NamedConstantDict, self).__setitem__(key, value)


class NamedConstantMeta(type):
    """
    Block attempts to reassign NamedConstant attributes.
    """

    def __new__(metacls, cls, bases, clsdict):
        if type(clsdict) is dict:
            original_dict = clsdict
            clsdict = _NamedConstantDict()
            for k, v in original_dict.items():
                clsdict[k] = v
        newdict = {}
        constants = {}
        for name, obj in clsdict.items():
            if name in clsdict._names:
                constants[name] = obj
                continue
            elif isinstance(obj, nonmember):
                obj = obj.value
            newdict[name] = obj
        newcls = super(NamedConstantMeta, metacls).__new__(metacls, cls, bases, newdict)
        newcls._named_constant_cache_ = {}
        for name, obj in constants.items():
            newcls.__new__(newcls, name, obj)
        return newcls

    def __delattr__(cls, attr):
        cur_obj = cls.__dict__.get(attr)
        if NamedConstant is not None and isinstance(cur_obj, NamedConstant):
            raise AttributeError('cannot delete constant <%s.%s>' % (cur_obj.__class__.__name__, cur_obj._name_))
        super(NamedConstantMeta, cls).__delattr__(attr)

    def __setattr__(cls, name, value):
        """Block attempts to reassign NamedConstants.
        """
        cur_obj = cls.__dict__.get(name)
        if NamedConstant is not None and isinstance(cur_obj, NamedConstant):
            raise AttributeError('cannot rebind constant <%s.%s>' % (cur_obj.__class__.__name__, cur_obj._name_))
        super(NamedConstantMeta, cls).__setattr__(name, value)

temp_constant_dict = {}
temp_constant_dict['__doc__'] = "NamedConstants protection.\n\n    Derive from this class to lock NamedConstants.\n\n"

def __new__(cls, name, value=None, doc=None):
    if value is None:
        # lookup, name is value
        value = name
        for name, obj in cls.__dict__.items():
            if isinstance(obj, cls) and obj._value_ == value:
                return obj
        else:
            raise ValueError('%r does not exist in %r' % (value, cls.__name__))
    cur_obj = cls.__dict__.get(name)
    if isinstance(cur_obj, NamedConstant):
        raise AttributeError('cannot rebind constant <%s.%s>' % (cur_obj.__class__.__name__, cur_obj._name_))
    elif isinstance(value, constant):
        doc = doc or value.__doc__
        value = value.value
    metacls = cls.__class__
    if isinstance(value, NamedConstant):
        # constants from other classes are reduced to their actual value
        value = value._value_
    actual_type = type(value)
    value_type = cls._named_constant_cache_.get(actual_type)
    if value_type is None:
        value_type = type(cls.__name__, (cls, type(value)), {})
        cls._named_constant_cache_[type(value)] = value_type
    obj = actual_type.__new__(value_type, value)
    obj._name_ = name
    obj._value_ = value
    obj.__doc__ = doc
    metacls.__setattr__(cls, name, obj)
    return obj
temp_constant_dict['__new__'] = __new__
del __new__

def __repr__(self):
    return "<%s.%s: %r>" % (
            self.__class__.__name__, self._name_, self._value_)
temp_constant_dict['__repr__'] = __repr__
del __repr__

def __reduce_ex__(self, proto):
    return getattr, (self.__class__, self._name_)
temp_constant_dict['__reduce_ex__'] = __reduce_ex__
del __reduce_ex__


NamedConstant = NamedConstantMeta('NamedConstant', (object, ), temp_constant_dict)
Constant = NamedConstant
del temp_constant_dict

# now for a NamedTuple

class _NamedTupleDict(OrderedDict):
    """Track field order and ensure field names are not reused.

    NamedTupleMeta will use the names found in self._field_names to translate
    to indices.
    """
    def __init__(self, *args, **kwds):
        self._field_names = []
        super(_NamedTupleDict, self).__init__(*args, **kwds)

    def __setitem__(self, key, value):
        """Records anything not dundered or not a descriptor.

        If a field name is used twice, an error is raised.

        Single underscore (sunder) names are reserved.
        """
        if _is_sunder(key):
            if key not in ('_size_', '_order_'):
                raise ValueError('_names_ are reserved for future NamedTuple use')
        elif _is_dunder(key):
            if key == '__order__':
                key = '_order_'
        elif key in self._field_names:
            # overwriting a field?
            raise TypeError('attempt to reuse field name: %r' % (key, ))
        elif not _is_descriptor(value):
            if key in self:
                # field overwriting a descriptor?
                raise TypeError('%s already defined as: %r' % (key, self[key]))
            self._field_names.append(key)
        super(_NamedTupleDict, self).__setitem__(key, value)


class _TupleAttributeAtIndex(object):

    def __init__(self, name, index, doc, default):
        self.name = name
        self.index = index
        if doc is undefined:
            doc = None
        self.__doc__ = doc
        self.default = default

    def __get__(self, instance, owner):
        if instance is None:
            return self
        if len(instance) <= self.index:
            raise AttributeError('%s instance has no value for %s' % (instance.__class__.__name__, self.name))
        return instance[self.index]

    def __repr__(self):
        return '%s(%d)' % (self.__class__.__name__, self.index)


class undefined(object):
    def __repr__(self):
        return 'undefined'
    def __bool__(self):
        return False
    __nonzero__ = __bool__
undefined = undefined()


class TupleSize(NamedConstant):
    fixed = constant('fixed', 'tuple length is static')
    minimum = constant('minimum', 'tuple must be at least x long (x is calculated during creation')
    variable = constant('variable', 'tuple length can be anything')

class NamedTupleMeta(type):
    """Metaclass for NamedTuple"""

    @classmethod
    def __prepare__(metacls, cls, bases, size=undefined):
        return _NamedTupleDict()

    def __init__(cls, *args , **kwds):
        super(NamedTupleMeta, cls).__init__(*args)

    def __new__(metacls, cls, bases, clsdict, size=undefined):
        if bases == (object, ):
            bases = (tuple, object)
        elif tuple not in bases:
            if object in bases:
                index = bases.index(object)
                bases = bases[:index] + (tuple, ) + bases[index:]
            else:
                bases = bases + (tuple, )
        # include any fields from base classes
        base_dict = _NamedTupleDict()
        namedtuple_bases = []
        for base in bases:
            if isinstance(base, NamedTupleMeta):
                namedtuple_bases.append(base)
        i = 0
        if namedtuple_bases:
            for name, index, doc, default in metacls._convert_fields(*namedtuple_bases):
                base_dict[name] = index, doc, default
                i = max(i, index)
        # construct properly ordered dict with normalized indexes
        for k, v in clsdict.items():
            base_dict[k] = v
        original_dict = base_dict
        if size is not undefined and '_size_' in original_dict:
            raise TypeError('_size_ cannot be set if "size" is passed in header')
        add_order = isinstance(clsdict, _NamedTupleDict)
        clsdict = _NamedTupleDict()
        clsdict.setdefault('_size_', size or TupleSize.fixed)
        unnumbered = OrderedDict()
        numbered = OrderedDict()
        _order_ = original_dict.pop('_order_', [])
        if _order_ :
            _order_ = _order_.replace(',',' ').split()
            add_order = False
        # and process this class
        for k, v in original_dict.items():
            if k not in original_dict._field_names:
                clsdict[k] = v
            else:
                # TODO:normalize v here
                if isinstance(v, baseinteger):
                    # assume an offset
                    v = v, undefined, undefined
                    i = v[0] + 1
                    target = numbered
                elif isinstance(v, basestring):
                    # assume a docstring
                    if add_order:
                        v = i, v, undefined
                        i += 1
                        target = numbered
                    else:
                        v = undefined, v, undefined
                        target = unnumbered
                elif isinstance(v, tuple) and len(v) in (2, 3) and isinstance(v[0], baseinteger) and isinstance(v[1], (basestring, NoneType)):
                    # assume an offset, a docstring, and (maybe) a default
                    if len(v) == 2:
                        v = v + (undefined, )
                    v = v
                    i = v[0] + 1
                    target = numbered
                elif isinstance(v, tuple) and len(v) in (1, 2) and isinstance(v[0], (basestring, NoneType)):
                    # assume a docstring, and (maybe) a default
                    if len(v) == 1:
                        v = v + (undefined, )
                    if add_order:
                        v = (i, ) + v
                        i += 1
                        target = numbered
                    else:
                        v = (undefined, ) + v
                        target = unnumbered
                else:
                    # refuse to guess further
                    raise ValueError('not sure what to do with %s=%r (should be OFFSET [, DOC [, DEFAULT]])' % (k, v))
                target[k] = v
        # all index values have been normalized
        # deal with _order_ (or lack thereof)
        fields = []
        aliases = []
        seen = set()
        max_len = 0
        if not _order_:
            if unnumbered:
                raise ValueError("_order_ not specified and OFFSETs not declared for %r" % (unnumbered.keys(), ))
            for name, (index, doc, default) in sorted(numbered.items(), key=lambda nv: (nv[1][0], nv[0])):
                if index in seen:
                    aliases.append(name)
                else:
                    fields.append(name)
                    seen.add(index)
                    max_len = max(max_len, index + 1)
            offsets = numbered
        else:
            # check if any unnumbered not in _order_
            missing = set(unnumbered) - set(_order_)
            if missing:
                raise ValueError("unable to order fields: %s (use _order_ or specify OFFSET" % missing)
            offsets = OrderedDict()
            # if any unnumbered, number them from their position in _order_
            i = 0
            for k in _order_:
                try:
                    index, doc, default = unnumbered.pop(k, None) or numbered.pop(k)
                except IndexError:
                    raise ValueError('%s (from _order_) not found in %s' % (k, cls))
                if index is not undefined:
                    i = index
                if i in seen:
                    aliases.append(k)
                else:
                    fields.append(k)
                    seen.add(i)
                offsets[k] = i, doc, default
                i += 1
                max_len = max(max_len, i)
            # now handle anything in numbered
            for k, (index, doc, default) in sorted(numbered.items(), key=lambda nv: (nv[1][0], nv[0])):
                if index in seen:
                    aliases.append(k)
                else:
                    fields.append(k)
                    seen.add(index)
                offsets[k] = index, doc, default
                max_len = max(max_len, index+1)

        # at this point fields and aliases should be ordered lists, offsets should be an
        # OrdededDict with each value an int, str or None or undefined, default or None or undefined
        assert len(fields) + len(aliases) == len(offsets), "number of fields + aliases != number of offsets"
        assert set(fields) & set(offsets) == set(fields), "some fields are not in offsets: %s" % set(fields) & set(offsets)
        assert set(aliases) & set(offsets) == set(aliases), "some aliases are not in offsets: %s" % set(aliases) & set(offsets)
        for name, (index, doc, default) in offsets.items():
            assert isinstance(index, baseinteger), "index for %s is not an int (%s:%r)" % (name, type(index), index)
            assert isinstance(doc, (basestring, NoneType)) or doc is undefined, "doc is not a str, None, nor undefined (%s:%r)" % (name, type(doc), doc)

        # create descriptors for fields
        for name, (index, doc, default) in offsets.items():
            clsdict[name] = _TupleAttributeAtIndex(name, index, doc, default)
        clsdict['__slots__'] = ()

        # create our new NamedTuple type
        namedtuple_class = super(NamedTupleMeta, metacls).__new__(metacls, cls, bases, clsdict)
        namedtuple_class._fields_ = fields
        namedtuple_class._aliases_ = aliases
        namedtuple_class._defined_len_ = max_len
        return namedtuple_class

    @staticmethod
    def _convert_fields(*namedtuples):
        "create list of index, doc, default triplets for cls in namedtuples"
        all_fields = []
        for cls in namedtuples:
            base = len(all_fields)
            for field in cls._fields_:
                desc = getattr(cls, field)
                all_fields.append((field, base+desc.index, desc.__doc__, desc.default))
        return all_fields

    def __add__(cls, other):
        "A new NamedTuple is created by concatenating the _fields_ and adjusting the descriptors"
        if not isinstance(other, NamedTupleMeta):
            return NotImplemented
        return NamedTupleMeta('%s%s' % (cls.__name__, other.__name__), (cls, other), {})

    def __call__(cls, *args, **kwds):
        """Creates a new NamedTuple class or an instance of a NamedTuple subclass.

        NamedTuple should have args of (class_name, names, module)

            `names` can be:

                * A string containing member names, separated either with spaces or
                  commas.  Values are auto-numbered from 1.
                * An iterable of member names.  Values are auto-numbered from 1.
                * An iterable of (member name, value) pairs.
                * A mapping of member name -> value.

                `module`, if set, will be stored in the new class' __module__ attribute;

                Note: if `module` is not set this routine will attempt to discover the
                calling module by walking the frame stack; if this is unsuccessful
                the resulting class will not be pickleable.

        subclass should have whatever arguments and/or keywords will be used to create an
        instance of the subclass
        """
        if cls is NamedTuple:
            original_args = args
            original_kwds = kwds.copy()
            # create a new subclass
            try:
                if 'class_name' in kwds:
                    class_name = kwds.pop('class_name')
                else:
                    class_name, args = args[0], args[1:]
                if 'names' in kwds:
                    names = kwds.pop('names')
                else:
                    names, args = args[0], args[1:]
                if 'module' in kwds:
                    module = kwds.pop('module')
                elif args:
                    module, args = args[0], args[1:]
                else:
                    module = None
                if 'type' in kwds:
                    type = kwds.pop('type')
                elif args:
                    type, args = args[0], args[1:]
                else:
                    type = None

            except IndexError:
                raise TypeError('too few arguments to NamedTuple: %s, %s' % (original_args, original_kwds))
            if args or kwds:
                raise TypeError('too many arguments to NamedTuple: %s, %s' % (original_args, original_kwds))
            if pyver < 3.0:
                # if class_name is unicode, attempt a conversion to ASCII
                if isinstance(class_name, unicode):
                    try:
                        class_name = class_name.encode('ascii')
                    except UnicodeEncodeError:
                        raise TypeError('%r is not representable in ASCII' % (class_name, ))
            # quick exit if names is a NamedTuple
            if isinstance(names, NamedTupleMeta):
                names.__name__ = class_name
                if type is not None and type not in names.__bases__:
                    names.__bases__ = (type, ) + names.__bases__
                return names

            metacls = cls.__class__
            bases = (cls, )
            clsdict = metacls.__prepare__(class_name, bases)

            # special processing needed for names?
            if isinstance(names, basestring):
                names = names.replace(',', ' ').split()
            if isinstance(names, (tuple, list)) and isinstance(names[0], basestring):
                names = [(e, i) for (i, e) in enumerate(names)]
            # Here, names is either an iterable of (name, index) or (name, index, doc, default) or a mapping.
            item = None  # in case names is empty
            for item in names:
                if isinstance(item, basestring):
                    # mapping
                    field_name, field_index = item, names[item]
                else:
                    # non-mapping
                    if len(item) == 2:
                        field_name, field_index = item
                    else:
                        field_name, field_index = item[0], item[1:]
                clsdict[field_name] = field_index
            if type is not None:
                if not isinstance(type, tuple):
                    type = (type, )
                bases = type + bases
            namedtuple_class = metacls.__new__(metacls, class_name, bases, clsdict)

            # TODO: replace the frame hack if a blessed way to know the calling
            # module is ever developed
            if module is None:
                try:
                    module = _sys._getframe(1).f_globals['__name__']
                except (AttributeError, ValueError, KeyError):
                    pass
            if module is None:
                _make_class_unpicklable(namedtuple_class)
            else:
                namedtuple_class.__module__ = module

            return namedtuple_class
        else:
            # instantiate a subclass
            namedtuple_instance = cls.__new__(cls, *args, **kwds)
            if isinstance(namedtuple_instance, cls):
                namedtuple_instance.__init__(*args, **kwds)
            return namedtuple_instance

    @property
    def __fields__(cls):
        return list(cls._fields_)
    # collections.namedtuple compatibility
    _fields = __fields__

    @property
    def __aliases__(cls):
        return list(cls._aliases_)

    def __repr__(cls):
        return "<NamedTuple %r>" % (cls.__name__, )

temp_namedtuple_dict = {}
temp_namedtuple_dict['__doc__'] = "NamedTuple base class.\n\n    Derive from this class to define new NamedTuples.\n\n"

def __new__(cls, *args, **kwds):
    if cls._size_ is TupleSize.fixed and len(args) > cls._defined_len_:
        raise TypeError('%d fields expected, %d received' % (cls._defined_len_, len(args)))
    unknown = set(kwds) - set(cls._fields_) - set(cls._aliases_)
    if unknown:
        raise TypeError('unknown fields: %r' % (unknown, ))
    final_args = list(args) + [undefined] * (len(cls.__fields__) - len(args))
    for field, value in kwds.items():
        index = getattr(cls, field).index
        if final_args[index] != undefined:
            raise TypeError('field %s specified more than once' % field)
        final_args[index] = value
    missing = []
    for index, value in enumerate(final_args):
        if value is undefined:
            # look for default values
            name = cls.__fields__[index]
            default = getattr(cls, name).default
            if default is undefined:
                missing.append(name)
            else:
                final_args[index] = default
    if missing:
        if cls._size_ in (TupleSize.fixed, TupleSize.minimum):
            raise TypeError('values not provided for field(s): %s' % ', '.join(missing))
        while final_args and final_args[-1] is undefined:
            final_args.pop()
            missing.pop()
        if cls._size_ is not TupleSize.variable or undefined in final_args:
            raise TypeError('values not provided for field(s): %s' % ', '.join(missing))
    return tuple.__new__(cls, tuple(final_args))

temp_namedtuple_dict['__new__'] = __new__
del __new__

def __reduce_ex__(self, proto):
    return self.__class__, tuple(getattr(self, f) for f in self._fields_)
temp_namedtuple_dict['__reduce_ex__'] = __reduce_ex__
del __reduce_ex__

def __repr__(self):
    if len(self) == len(self._fields_):
        return "%s(%s)" % (
                self.__class__.__name__, ', '.join(['%s=%r' % (f, o) for f, o in zip(self._fields_, self)])
                )
    else:
        return '%s(%s)' % (self.__class__.__name__, ', '.join([repr(o) for o in self]))
temp_namedtuple_dict['__repr__'] = __repr__
del __repr__

def __str__(self):
    return "%s(%s)" % (
            self.__class__.__name__, ', '.join(['%r' % (getattr(self, f), ) for f in self._fields_])
            )
temp_namedtuple_dict['__str__'] = __str__
del __str__

## compatibility methods with stdlib namedtuple
@property
def __aliases__(self):
    return list(self.__class__._aliases_)
temp_namedtuple_dict['__aliases__'] = __aliases__
del __aliases__

@property
def __fields__(self):
    return list(self.__class__._fields_)
temp_namedtuple_dict['__fields__'] = __fields__
temp_namedtuple_dict['_fields'] = __fields__
del __fields__

def _make(cls, iterable, new=None, len=None):
    return cls.__new__(cls, *iterable)
temp_namedtuple_dict['_make'] = classmethod(_make)
del _make

def _asdict(self):
    return OrderedDict(zip(self._fields_, self))
temp_namedtuple_dict['_asdict'] = _asdict
del _asdict

def _replace(self, **kwds):
    current = self._asdict()
    current.update(kwds)
    return self.__class__(**current)
temp_namedtuple_dict['_replace'] = _replace
del _replace

NamedTuple = NamedTupleMeta('NamedTuple', (object, ), temp_namedtuple_dict)
del temp_namedtuple_dict

# defined now for immediate use

def enumsort(things):
    """
    sorts things by value if all same type; otherwise by name
    """
    if not things:
        return things
    sort_type = type(things[0])
    if not issubclass(sort_type, tuple):
        # direct sort or type error
        if not all((type(v) is sort_type) for v in things[1:]):
            raise TypeError('cannot sort items of different types')
        return sorted(things)
    else:
        # expecting list of (name, value) tuples
        sort_type = type(things[0][1])
        try:
            if all((type(v[1]) is sort_type) for v in things[1:]):
                return sorted(things, key=lambda i: i[1])
            else:
                raise TypeError('try name sort instead')
        except TypeError:
            return sorted(things, key=lambda i: i[0])

def export(collection, namespace=None):
    """
    export([collection,] namespace) -> Export members to target namespace.

    If collection is not given, act as a decorator.
    """
    if namespace is None:
        namespace = collection
        def export_decorator(collection):
            return export(collection, namespace)
        return export_decorator
    elif issubclass(collection, NamedConstant):
        for n, c in collection.__dict__.items():
            if isinstance(c, NamedConstant):
                namespace[n] = c
    elif issubclass(collection, Enum):
        data = collection.__members__.items()
        for n, m in data:
            namespace[n] = m
    else:
        raise TypeError('%r is not a supported collection' % (collection,) )
    return collection

# Constants used in Enum

@export(globals())
class EnumConstants(NamedConstant):
    AutoValue = constant('autovalue', 'values are automatically created from _generate_next_value_')
    AutoNumber = constant('autonumber', 'integer value is prepended to members, beginning from START')
    MultiValue = constant('multivalue', 'each member can have several values')
    NoAlias = constant('noalias', 'duplicate valued members are distinct, not aliased')
    Unique = constant('unique', 'duplicate valued members are not allowed')


############
# Enum stuff
############

# Dummy value for Enum as EnumMeta explicity checks for it, but of course until
# EnumMeta finishes running the first time the Enum class doesn't exist.  This
# is also why there are checks in EnumMeta like `if Enum is not None`
Enum = Flag = None

class enum(object):
    """
    Helper class to track args, kwds.
    """
    def __init__(self, *args, **kwds):
        self._args = args
        self._kwds = kwds.items()
        self._hash = hash(args)
        self.name = None

    @property
    def args(self):
        return self._args

    @property
    def kwds(self):
        return dict([(k, v) for k, v in self._kwds])

    def __hash__(self):
        return self._hash

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return self.args == other.args and self.kwds == other.kwds

    def __ne__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return self.args != other.args or self.kwds != other.kwds

    def __repr__(self):
        final = []
        args = ', '.join(['%r' % (a, ) for a in self.args])
        if args:
            final.append(args)
        kwds = ', '.join([('%s=%r') % (k, v) for k, v in enumsort(list(self.kwds.items()))])
        if kwds:
            final.append(kwds)
        return '%s(%s)' % (self.__class__.__name__, ', '.join(final))

_auto_null = object()
class auto(enum):
    """
    Instances are replaced with an appropriate value in Enum class suites.
    """
    _value = _auto_null
    _operations = []

    def __and__(self, other):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_and_, (self, other)))
        return new_auto

    def __rand__(self, other):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_and_, (other, self)))
        return new_auto

    def __invert__(self):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_inv_, (self,)))
        return new_auto

    def __or__(self, other):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_or_, (self, other)))
        return new_auto

    def __ror__(self, other):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_or_, (other, self)))
        return new_auto

    def __xor__(self, other):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_xor_, (self, other)))
        return new_auto

    def __rxor__(self, other):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_xor_, (other, self)))
        return new_auto

    def __abs__(self):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_abs_, (self, )))
        return new_auto

    def __add__(self, other):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_add_, (self, other)))
        return new_auto

    def __radd__(self, other):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_add_, (other, self)))
        return new_auto

    def __neg__(self):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_neg_, (self, )))
        return new_auto

    def __pos__(self):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_pos_, (self, )))
        return new_auto

    if pyver < 3:
        def __div__(self, other):
            new_auto = self.__class__()
            new_auto._operations = self._operations[:]
            new_auto._operations.append((_div_, (self, other)))
            return new_auto

    def __rdiv__(self, other):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_div_, (other, self)))
        return new_auto

    def __floordiv__(self, other):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_floordiv_, (self, other)))
        return new_auto

    def __rfloordiv__(self, other):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_floordiv_, (other, self)))
        return new_auto

    def __truediv__(self, other):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_truediv_, (self, other)))
        return new_auto

    def __rtruediv__(self, other):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_truediv_, (other, self)))
        return new_auto

    def __lshift__(self, other):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_lshift_, (self, other)))
        return new_auto

    def __rlshift__(self, other):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_lshift_, (other, self)))
        return new_auto

    def __rshift__(self, other):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_rshift_, (self, other)))
        return new_auto

    def __rrshift__(self, other):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_rshift_, (other, self)))
        return new_auto

    def __mod__(self, other):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_mod_, (self, other)))
        return new_auto

    def __rmod__(self, other):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_mod_, (other, self)))
        return new_auto

    def __mul__(self, other):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_mul_, (self, other)))
        return new_auto

    def __rmul__(self, other):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_mul_, (other, self)))
        return new_auto

    def __pow__(self, other):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_pow_, (self, other)))
        return new_auto

    def __rpow__(self, other):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_pow_, (other, self)))
        return new_auto

    def __sub__(self, other):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_sub_, (self, other)))
        return new_auto

    def __rsub__(self, other):
        new_auto = self.__class__()
        new_auto._operations = self._operations[:]
        new_auto._operations.append((_sub_, (other, self)))
        return new_auto



    @property
    def value(self):
        if self._value is not _auto_null and self._operations:
            raise TypeError('auto() object out of sync')
        elif self._value is _auto_null and not self._operations:
            return self._value
        elif self._value is not _auto_null:
            return self._value
        else:
            return self._resolve()

    @value.setter
    def value(self, value):
        if self._operations:
            value = self._resolve(value)
        self._value = value

    def _resolve(self, base_value=None):
            cls = self.__class__
            for op, params in self._operations:
                values = []
                for param in params:
                    if isinstance(param, cls):
                        if param.value is _auto_null:
                            if base_value is None:
                                return _auto_null
                            else:
                                values.append(base_value)
                        else:
                            values.append(param.value)
                    else:
                        values.append(param)
                value = op(*values)
            self._operations[:] = []
            self._value = value
            return value

class _EnumDict(dict):
    """Track enum member order and ensure member names are not reused.

    EnumMeta will use the names found in self._member_names as the
    enumeration member names.
    """
    def __init__(self, cls_name, settings, start, constructor_init, constructor_start):
        super(_EnumDict, self).__init__()
        self._cls_name = cls_name
        self._constructor_init = constructor_init
        self._constructor_start = constructor_start
        # for Flag enumerations, we may need to get the _init_ from __new__
        self._new_to_init = False
        # list of enum members
        self._member_names = []
        self._settings = settings
        autonumber = AutoNumber in settings
        autovalue = AutoValue in settings
        multivalue = MultiValue in settings
        if autonumber and start is None:
            # starting value for AutoNumber
            start = 1
        elif start is not None and not autonumber:
            autonumber = True
        if start is not None:
            self._value = start - 1
        else:
            self._value = None
        # when the magic turns off
        self._locked = not (autovalue or autonumber)
        # if auto or autonumber
        self._autovalue = autovalue
        self._autonumber = autonumber
        # if multiple values are allowed
        self._multivalue = multivalue
        # if init fields are specified
        self._init = None
        # list of temporary names
        self._ignore = []
        self._ignore_init_done = False
        # if _sunder_ values can be changed via the class body
        self._allow_init = True
        self._last_values = []

    def __getitem__(self, key):
        if key == self._cls_name and self._cls_name not in self:
            return enum
        elif key == '_auto_on_':
            self._locked = False
            if not self._autonumber:
                self._autovalue = True
            return None
        elif key == '_auto_off_':
            self._locked = True
            return None
        elif (
                self._locked
                or key in self
                or key in self._ignore
                or _is_sunder(key)
                or _is_dunder(key)
                ):
            return super(_EnumDict, self).__getitem__(key)
        elif self._autonumber:
            try:
                # try to generate the next value
                value = self._value + 1
                self._value += 1
            except:
                # couldn't work the magic, report error
                raise KeyError('%s not found' % (key,))
        elif self._autovalue:
            value = self._generate_next_value(key, 1, len(self._member_names), self._last_values[:])
        else:
            raise Exception('neither AutoNumber nor AutoValue set -- why am I here?')
        self.__setitem__(key, value)
        return value

    def __setitem__(self, key, value):
        """Changes anything not sundured, dundered, nor a descriptor.

        If an enum member name is used twice, an error is raised; duplicate
        values are not checked for.

        Single underscore (sunder) names are reserved.
        """
        if _is_internal_class(self._cls_name, value):
            pass
        elif _is_sunder(key):
            if key not in (
                    '_init_', '_settings_', '_order_', '_ignore_', '_start_',
                    '_create_pseudo_member_', '_create_pseudo_member_values_',
                    '_generate_next_value_',
                    '_missing_', '_missing_value_', '_missing_name_',
                    ):
                raise ValueError('_names_ are reserved for  Enum use')
            elif not self._allow_init and key not in (
                    'create_pseudo_member_', '_missing_', '_missing_value_', '_missing_name_',
                ):
                # sunder is used during creation, must be specified first
                raise ValueError('cannot set %r after init phase' % (key, ))
            elif key == '_ignore_':
                if self._ignore_init_done:
                    raise TypeError('ignore can only be specified once')
                if isinstance(value, basestring):
                    value = value.split()
                else:
                    value = list(value)
                self._ignore = value
                already = set(value) & set(self._member_names)
                if already:
                    raise ValueError('_ignore_ cannot specify already set names: %r' % (already, ))
                self._ignore_init_done = True
            elif key == '_start_':
                if self._constructor_start:
                    raise TypeError('start specified in constructor and class body')
                if value is None:
                    self._value = None
                    self._autonumber = False
                    if not self._autovalue:
                        self._locked = True
                else:
                    self._value = value - 1
                    self._locked = False
                    self._autonumber = True
            elif key == '_settings_':
                if not isinstance(value, (set, tuple)):
                    value = (value, )
                if not isinstance(value, set):
                    value = set(value)
                self._settings |= value
                if NoAlias in value and Unique in value:
                    raise TypeError('cannot specify both NoAlias and Unique')
                elif MultiValue in value and NoAlias in value:
                    raise TypeError('cannot specify both MultiValue and NoAlias')
                elif AutoValue in value and AutoNumber in value:
                    raise TypeError('cannot specify both AutoValue and AutoNumber')
                allowed_settings = dict.fromkeys(['autovalue', 'autonumber', 'noalias', 'unique', 'multivalue'])
                for arg in value:
                    if arg not in allowed_settings:
                        raise TypeError('unknown qualifier: %r (from %r)' % (arg, value))
                    allowed_settings[arg] = True
                self._multivalue = allowed_settings['multivalue']
                self._autovalue = allowed_settings['autovalue']
                self._autonumber = allowed_settings['autonumber']
                self._locked = not (self._autonumber or self._autovalue)
                if (self._autovalue or self._autonumber) and not self._ignore_init_done:
                    self._ignore = ['property', 'classmethod', 'staticmethod', 'aenum', 'auto']
                if self._autonumber and self._value is None:
                    self._value = 0
                if self._autonumber and self._init and self._init[0:1] == ['value']:
                    self._init.pop(0)
                value = tuple(self._settings)
            elif key == '_init_':
                if self._constructor_init:
                    raise TypeError('init specified in constructor and in class body')
                _init_ = value
                if isinstance(_init_, basestring):
                    _init_ = _init_.replace(',',' ').split()
                if _init_[0:1] == ['value'] and self._autonumber:
                    _init_.pop(0)
                self._init = _init_
            elif key == '_generate_next_value_':
                if isinstance(value, staticmethod):
                    gnv = value.__func__
                elif isinstance(value, classmethod):
                    raise TypeError('_generate_next_value should be a staticmethod, not a classmethod')
                else:
                    gnv = value
                    value = staticmethod(value)
                setattr(self, '_generate_next_value', gnv)
                self._auto_args = _check_auto_args(value)
        elif _is_dunder(key):
            if key == '__order__':
                key = '_order_'
                if not self._allow_init:
                    # _order_ is used during creation, must be specified first
                    raise ValueError('cannot set %r after init phase' % (key, ))
            elif key == '__new__' and self._new_to_init:
                # ArgSpec(args=[...], varargs=[...], keywords=[...], defaults=[...]
                if isinstance(value, staticmethod):
                    value = value.__func__
                new_args = getargspec(value)[0][1:]
                self._init = new_args
            if _is_descriptor(value):
                self._locked = True
        elif key in self._member_names:
            # descriptor overwriting an enum?
            raise TypeError('attempt to reuse name: %r' % (key, ))
        elif key in self._ignore:
            pass
        elif not _is_descriptor(value):
            self._allow_init = False
            if key in self:
                # enum overwriting a descriptor?
                raise TypeError('%s already defined as: %r' % (key, self[key]))
            if self._multivalue:
                # make sure it's a tuple
                if not isinstance(value, tuple):
                    value = (value, )
                # do we need to calculate the next value?
                if self._autonumber:
                    if self._init:
                        target_length = len(self._init)
                        if self._init[0] != 'value':
                            target_length += 1
                        if len(value) != target_length:
                            value = (self._value + 1, ) + value
                        if isinstance(value[0], auto):
                            value = (self._value + 1, ) + value[1:]
                    else:
                        try:
                            value = (self._value + 1, ) + value
                        except TypeError:
                            pass
                    self._value = value[0]
            elif self._autovalue and self._init and not isinstance(value, auto):
                # call generate iff init is specified and calls for more values than are present
                target_values = len(self._init)
                if not isinstance(value, tuple):
                    value = (value, )
                source_values = len(value)
                if target_values != source_values:
                    gnv = self._generate_next_value
                    if self._auto_args:
                        value = gnv(
                                key, 1,
                                len(self._member_names),
                                self._last_values[:],
                                *value
                                )
                    else:
                        value = gnv(
                                key,
                                1,
                                len(self._member_names),
                                self._last_values[:],
                                )

            elif self._autonumber and not self._locked:
                # convert any auto instances to integers
                if isinstance(value, auto):
                    value = self._value + 1
                elif isinstance(value, basestring):
                    pass
                else:
                    try:
                        new_value = []
                        for v in value:
                            if isinstance(v, auto):
                                new_value.append(self._value + 1)
                            else:
                                new_value.append(v)
                        value = tuple(new_value)
                    except TypeError:
                        # value wasn't iterable
                        pass
                if isinstance(value, int):
                    self._value = value
                elif isinstance(value, tuple):
                    if self._init is None:
                        # old behavior -> if first item is int, use it as value
                        #                 otherwise, generate a value and prepend it
                        if value and isinstance(value[0], baseinteger):
                            self._value = value[0]
                        else:
                            self._value += 1
                            value = (self._value, ) + value
                    elif len(value) == len(self._init):
                        # provide actual value for member
                        self._value += 1
                        value = (self._value, ) + value
                    elif 'value'  not in self._init and len(value) == len(self._init) + 1:
                        # actual value for member is provided
                        self._value = value[0]
                    elif 'value' in self._init and len(value) == len(self._init) - 1:
                        count = self._value + 1
                        value = count, value
                        self._value = count
                    else:
                        # mismatch
                        raise TypeError('%s: number of fields provided do not match init' % key)
                else:
                    if self._init is not None and (len(self._init) != 1 or 'value' in self._init):
                        raise TypeError('%s: number of fields provided do not match init' % key)
                    count = self._value + 1
                    value = count, value
                    self._value = count
            elif isinstance(value, auto):
                # if AutoNumber set use built-in value, not _generate_next_value_
                if self._autonumber:
                    value = self._value + 1
                    self._value = value
                else:
                    if value.value == _auto_null:
                        gnv = self._generate_next_value
                        prev_values = []
                        for v in self._last_values:
                            if isinstance(v, auto):
                                prev_values.append(v.value)
                            else:
                                prev_values.append(v)
                        if isinstance(gnv, staticmethod):
                            gnv = gnv.__func__
                        if self._auto_args:
                            value.value = gnv(
                                    key,
                                    1,
                                    len(self._member_names),
                                    prev_values,
                                    *value.args,
                                    **value.kwds
                                    )
                        else:
                            value.value = gnv(
                                    key,
                                    1,
                                    len(self._member_names),
                                    prev_values,
                                    )
            elif isinstance(value, enum):
                value.name = key
            else:
                pass
            self._member_names.append(key)
        else:
            # not a new member, turn off the autoassign magic
            self._locked = True
            self._allow_init = False
        if not _is_sunder(key) and not _is_dunder(key) and not _is_descriptor(value):
            if (self._autonumber or self._multivalue) and isinstance(value, tuple):
                self._last_values.append(value[0])
            else:
                self._last_values.append(value)
        super(_EnumDict, self).__setitem__(key, value)


no_arg = object()
class EnumMeta(StdlibEnumMeta or type):
    """Metaclass for Enum"""
    @classmethod
    def __prepare__(metacls, cls, bases, init=None, start=None, settings=()):
        # settings are a combination of current and all past settings
        constructor_init = init is not None
        constructor_start = start is not None
        if not isinstance(settings, tuple):
            settings = settings,
        settings = set(settings)
        generate = None
        order = None
        # inherit previous flags
        member_type, first_enum = metacls._get_mixins_(bases)
        if first_enum is not None:
            settings |= first_enum._settings_
            init = init or first_enum._auto_init_
            order = first_enum._order_function_
            if start is None:
                start = first_enum._start_
            generate = getattr(first_enum, '_generate_next_value_', None)
            generate = getattr(generate, 'im_func', generate)
        # check for custom settings
        if NoAlias in settings and Unique in settings:
            raise TypeError('cannot specify both NoAlias and Unique')
        elif MultiValue in settings and NoAlias in settings:
            raise TypeError('cannot specify both MultiValue and NoAlias')
        elif AutoValue in settings and AutoNumber in settings:
            raise TypeError('cannot specify both AutoValue and AutoNumber')
        allowed_settings = dict.fromkeys(['autovalue', 'autonumber', 'noalias', 'unique', 'multivalue'])
        for arg in settings:
            if arg not in allowed_settings:
                raise TypeError('unknown qualifier: %r' % (arg, ))
            allowed_settings[arg] = True
        enum_dict = _EnumDict(cls_name=cls, settings=settings, start=start, constructor_init=constructor_init, constructor_start=constructor_start)
        if settings & set([AutoValue, AutoNumber]) or start is not None:
            enum_dict['_ignore_'] = ['property', 'classmethod', 'staticmethod']
            enum_dict._ignore_init_done = False
        if generate:
            enum_dict['_generate_next_value_'] = generate
        if init is not None:
            if isinstance(init, basestring):
                init = init.replace(',',' ').split()
            if init[0:1] == ['value'] and AutoNumber in settings:
                init.pop(0)
            enum_dict._init = init
        elif Flag is not None and any(issubclass(b, Flag) for b in bases) and member_type not in (int, object):
            enum_dict._new_to_init = True
            if Flag in bases:
                # only happens on first mixin with Flag
                def _generate_next_value_(name, start, count, values, *args, **kwds):
                    return (2 ** count, ) + args
                enum_dict['_generate_next_value_'] = staticmethod(_generate_next_value_)
                def __new__(cls, flag_value, type_value):
                    obj = member_type.__new__(cls, type_value)
                    obj._value_ = flag_value
                    return obj
                enum_dict['__new__'] = __new__
            else:
                try:
                    new_args = getargspec(first_enum.__new_member__)[0][1:]
                    enum_dict._init = new_args
                except TypeError:
                    pass
        if order is not None:
            enum_dict['_order_'] = staticmethod(order)
        return enum_dict

    def __init__(cls, *args , **kwds):
        super(EnumMeta, cls).__init__(*args)

    def __new__(metacls, cls, bases, clsdict, init=None, start=None, settings=()):
        # handle py2 case first
        if type(clsdict) is not _EnumDict:
            # py2 ard/or functional API gyrations
            init = clsdict.pop('_init_', None)
            start = clsdict.pop('_start_', None)
            settings = clsdict.pop('_settings_', ())
            _order_ = clsdict.pop('_order_', clsdict.pop('__order__', None))
            _ignore_ = clsdict.pop('_ignore_', None)
            _create_pseudo_member_ = clsdict.pop('_create_pseudo_member_', None)
            _create_pseudo_member_values_ = clsdict.pop('_create_pseudo_member_values_', None)
            _generate_next_value_ = clsdict.pop('_generate_next_value_', None)
            _missing_ = clsdict.pop('_missing_', None)
            _missing_value_ = clsdict.pop('_missing_value_', None)
            _missing_name_ = clsdict.pop('_missing_name_', None)
            __new__ = clsdict.pop('__new__', None)
            enum_members = dict([
                    (k, v) for (k, v) in clsdict.items()
                    if not (_is_sunder(k) or _is_dunder(k) or _is_descriptor(v))
                    ])
            original_dict = clsdict
            clsdict = metacls.__prepare__(cls, bases, init=init, start=start, settings=settings)
            init = init or clsdict._init
            if _order_ is None:
                _order_ = clsdict.get('_order_')
                if _order_ is not None:
                    _order_ = _order_.__get__(cls)
            if isinstance(original_dict, OrderedDict):
                calced_order = original_dict
            elif _order_ is None:
                calced_order = [name for (name, value) in enumsort(list(enum_members.items()))]
            elif isinstance(_order_, basestring):
                calced_order = _order_ = _order_.replace(',', ' ').split()
            elif callable(_order_):
                if init:
                    if not isinstance(init, basestring):
                        init = ' '.join(init)
                member = NamedTuple('member', init and 'name ' + init or ['name', 'value'])
                calced_order = []
                for name, value in enum_members.items():
                    if init:
                        if not isinstance(value, tuple):
                            value = (value, )
                        name_value = (name, ) + value
                    else:
                        name_value = tuple((name, value))
                    if member._defined_len_ != len(name_value):
                        raise TypeError('%d values expected (%s), %d received (%s)' % (
                            member._defined_len_,
                            ', '.join(member._fields_),
                            len(name_value),
                            ', '.join([repr(v) for v in name_value]),
                            ))
                    calced_order.append(member(*name_value))
                calced_order = [m.name for m in sorted(calced_order, key=_order_)]
            else:
                calced_order = _order_
            for name in (
                    '_ignore_', '_create_pseudo_member_', '_create_pseudo_member_values_',
                    '_generate_next_value_', '_order_', '__new__',
                    '_missing_', '_missing_value_', '_missing_name_',
                ):
                attr = locals()[name]
                if attr is not None:
                    clsdict[name] = attr
            # now add members
            for k in calced_order:
                clsdict[k] = original_dict[k]
            for k, v in original_dict.items():
                if k not in calced_order:
                    clsdict[k] = v
            del _order_, _ignore_, _create_pseudo_member_, _create_pseudo_member_values_,
            del _generate_next_value_, _missing_, _missing_value_, _missing_name_

        # resume normal path
        if clsdict._new_to_init:
            # remove calculated _init_ as it's no longer needed
            clsdict._init = None
        clsdict._locked = True
        member_type, first_enum = metacls._get_mixins_(bases)
        _order_ = clsdict.pop('_order_', None)
        if isinstance(_order_, basestring):
            _order_ = _order_.replace(',',' ').split()
        init = clsdict._init
        start = clsdict._value
        settings = clsdict._settings
        if start is not None:
            start += 1
        creating_init = []
        auto_init = False
        if init is None and (AutoNumber in settings or start is not None):
                creating_init = ['value']
        elif init is not None:
            auto_init = True
            if (AutoNumber in settings or start is not None) and 'value' not in  init:
                creating_init = ['value'] + init
            else:
                creating_init = init[:]
        autonumber = AutoNumber in settings
        autovalue = AutoValue in settings
        multivalue = MultiValue in settings
        noalias = NoAlias in settings
        unique = Unique in settings
        # an Enum class cannot be mixed with other types (int, float, etc.) if
        #   it has an inherited __new__ unless a new __new__ is defined (or
        #   the resulting class will fail).
        # an Enum class is final once enumeration items have been defined;
        #
        # remove any keys listed in _ignore_
        clsdict.setdefault('_ignore_', []).append('_ignore_')
        ignore = clsdict['_ignore_']
        for key in ignore:
            clsdict.pop(key, None)
        # get the method to create enum members
        __new__, save_new, new_uses_args = metacls._find_new_(
                clsdict,
                member_type,
                first_enum,
                )
        # save enum items into separate mapping so they don't get baked into
        # the new class
        enum_members = dict((k, clsdict[k]) for k in clsdict._member_names)
        for name in clsdict._member_names:
            del clsdict[name]
        # move skipped values out of the descriptor, and add names to DynamicAttributes
        for name, obj in clsdict.items():
            if isinstance(obj, nonmember):
                dict.__setitem__(clsdict, name, obj.value)
            elif isinstance(obj, enum_property):
                obj.name = name
        # check for illegal enum names (any others?)
        invalid_names = set(enum_members) & set(['mro', ''])
        if invalid_names:
            raise ValueError('Invalid enum member name(s): %s' % (
                ', '.join(invalid_names), ))
        # create our new Enum type
        enum_class = type.__new__(metacls, cls, bases, clsdict)
        enum_class._member_names_ = []               # names in random order
        enum_class._member_map_ = OrderedDict()
        enum_class._member_type_ = member_type
        # save current flags for subclasses
        enum_class._settings_ = settings
        enum_class._start_ = start
        enum_class._auto_init_ = _auto_init_ = init
        enum_class._order_function_ = None
        if 'value' in creating_init and creating_init[0] != 'value':
            raise TypeError("'value', if specified, must be the first item in 'init'")
        # save attributes from super classes so we know if we can take
        # the shortcut of storing members in the class dict
        base_attributes = set([a for b in enum_class.mro() for a in b.__dict__])
        # Reverse value->name map for hashable values.
        enum_class._value2member_map_ = {}
        enum_class._value2member_seq_ = ()
        # instantiate them, checking for duplicates as we go
        # we instantiate first instead of checking for duplicates first in case
        # a custom __new__ is doing something funky with the values -- such as
        # auto-numbering ;)
        if __new__ is None:
            __new__ = enum_class.__new__
        for member_name in clsdict._member_names:
            value = enum_members[member_name]
            if isinstance(value, auto):
                value = value.value
            kwds = {}
            new_args = ()
            init_args = ()
            extra_mv_args = ()
            if isinstance(value, enum):
                args = value.args
                kwds = value.kwds
            elif isinstance(value, Member):
                value = value.value
                args = (value, )
            elif not isinstance(value, tuple):
                args = (value, )
            else:
                args = value
            # possibilities
            #
            # - no init, multivalue  -> __new__[0], __init__(*[:]), extra=[1:]
            # - init w/o value, multivalue  -> __new__[0], __init__(*[:]), extra=[1:]
            #
            # - init w/value, multivalue  -> __new__[0], __init__(*[1:]),  extra=[1:]
            #
            # - init w/value, no multivalue  -> __new__[0], __init__(*[1:]), extra=[]
            #
            # - init w/o value, no multivalue  -> __new__[:], __init__(*[:]), extra=[]
            # - no init, no multivalue  ->  __new__[:], __init__(*[:]), extra=[]
            if multivalue or 'value' in creating_init:
                if multivalue:
                    # when multivalue is True, creating_init can be anything
                    new_args = args[0:1]
                    extra_mv_args = args[1:]
                    if 'value' in creating_init:
                        init_args = args[1:]
                    else:
                        init_args = args
                else:
                    # 'value' is definitely in creating_init
                    new_args = args[0:1]
                    if auto_init:
                        # don't pass in value
                        init_args = args[1:]
                    else:
                        # keep the all args for user-defined __init__
                        init_args = args
                value = new_args[0]
            else:
                # either no creating_init, or it doesn't have 'value'
                new_args = args
                init_args = args
            if member_type is tuple:   # special case for tuple enums
                new_args = (new_args, )     # wrap it one more time
            if not new_uses_args:
                enum_member = __new__(enum_class)
                if not hasattr(enum_member, '_value_'):
                    enum_member._value_ = value
            else:
                enum_member = __new__(enum_class, *new_args, **kwds)
                if not hasattr(enum_member, '_value_'):
                    enum_member._value_ = member_type(*new_args, **kwds)
            value = enum_member._value_
            enum_member._name_ = member_name
            enum_member.__objclass__ = enum_class
            enum_member.__init__(*init_args, **kwds)
            # If another member with the same value was already defined, the
            # new member becomes an alias to the existing one.
            if noalias:
                # unless NoAlias was specified
                enum_class._member_names_.append(member_name)
            else:
                nonunique = defaultdict(list)
                for name, canonical_member in enum_class._member_map_.items():
                    if canonical_member.value == enum_member._value_:
                        if unique:
                            nonunique[name].append(member_name)
                            continue
                        enum_member = canonical_member
                        break
                else:
                    # Aliases don't appear in member names (only in __members__).
                    enum_class._member_names_.append(member_name)
                if nonunique:
                    # duplicates not allowed if Unique specified
                    message = []
                    for name, aliases in nonunique.items():
                        bad_aliases = ','.join(aliases)
                        message.append('%s --> %s [%r]' % (name, bad_aliases, enum_class[name].value))
                    raise ValueError(
                            'duplicate names found in %r: %s' %
                                (cls, ';  '.join(message))
                            )
            # members are added as enum_property's
            setattr(enum_class, member_name, enum_property(name=member_name))
            # now add to _member_map_
            enum_class._member_map_[member_name] = enum_member
            values = (value, ) + extra_mv_args
            enum_member._values_ = values
            for value in values:
                # first check if value has already been used
                if multivalue and (
                        value in enum_class._value2member_map_
                        or any(v == value for (v, m) in enum_class._value2member_seq_)
                        ):
                    raise ValueError('%r has already been used' % (value, ))
                try:
                    # This may fail if value is not hashable. We can't add the value
                    # to the map, and by-value lookups for this value will be
                    # linear.
                    if noalias:
                        raise TypeError('cannot use dict to store value')
                    enum_class._value2member_map_[value] = enum_member
                except TypeError:
                    enum_class._value2member_seq_ += ((value, enum_member), )
        # check for constants with auto() values
        for k, v in enum_class.__dict__.items():
            if isinstance(v, constant) and isinstance(v.value, auto):
                v.value = enum_class(v.value.value)
        # If a custom type is mixed into the Enum, and it does not know how
        # to pickle itself, pickle.dumps will succeed but pickle.loads will
        # fail.  Rather than have the error show up later and possibly far
        # from the source, sabotage the pickle protocol for this class so
        # that pickle.dumps also fails.
        #
        # However, if the new class implements its own __reduce_ex__, do not
        # sabotage -- it's on them to make sure it works correctly.  We use
        # __reduce_ex__ instead of any of the others as it is preferred by
        # pickle over __reduce__, and it handles all pickle protocols.
        unpicklable = False
        if '__reduce_ex__' not in clsdict:
            if member_type is not object:
                methods = ('__getnewargs_ex__', '__getnewargs__',
                        '__reduce_ex__', '__reduce__')
                if not any(m in member_type.__dict__ for m in methods):
                    _make_class_unpicklable(enum_class)
                    unpicklable = True

        # double check that repr and friends are not the mixin's or various
        # things break (such as pickle)

        for name in ('__repr__', '__str__', '__format__', '__reduce_ex__'):
            enum_class_method = enum_class.__dict__.get(name, None)
            if enum_class_method:
                # class has defined/imported/copied the method
                continue
            class_method = getattr(enum_class, name)
            obj_method = getattr(member_type, name, None)
            enum_method = getattr(first_enum, name, None)
            if obj_method is not None and obj_method == class_method:
                if name == '__reduce_ex__' and unpicklable:
                    continue
                setattr(enum_class, name, enum_method)

        # method resolution and int's are not playing nice
        # Python's less than 2.6 use __cmp__

        if pyver < 2.6:

            if issubclass(enum_class, int):
                setattr(enum_class, '__cmp__', getattr(int, '__cmp__'))

        elif pyver < 3.0:

            if issubclass(enum_class, int):
                for method in (
                        '__le__',
                        '__lt__',
                        '__gt__',
                        '__ge__',
                        '__eq__',
                        '__ne__',
                        '__hash__',
                        ):
                    setattr(enum_class, method, getattr(int, method))

        # replace any other __new__ with our own (as long as Enum is not None,
        # anyway) -- again, this is to support pickle
        if Enum is not None:
            # if the user defined their own __new__, save it before it gets
            # clobbered in case they subclass later
            if save_new:
                setattr(enum_class, '__new_member__', enum_class.__dict__['__new__'])
            setattr(enum_class, '__new__', Enum.__dict__['__new__'])

        # py3 support for definition order (helps keep py2/py3 code in sync)
        if _order_:
            if isinstance(_order_, staticmethod):
                # _order_ = staticmethod.__get__(enum_class)
                # _order_ = getattr(_order_, 'im_func', _order_)
                _order_ = _order_.__func__
            if callable(_order_):
                # save order for future subclasses
                enum_class._order_function_ = staticmethod(_order_)
                # create ordered list for comparison
                _order_ = [m.name for m in sorted(enum_class, key=_order_)]
            if _order_ != enum_class._member_names_:
                raise TypeError('member order does not match _order_: %r %r' % (enum_class._member_names_, enum_class._member_map_.items()))
        return enum_class

    def __bool__(cls):
        """
        classes/types should always be True.
        """
        return True

    def __call__(cls, value=no_arg, names=None, module=None, type=None, start=1):
        """Either returns an existing member, or creates a new enum class.

        This method is used both when an enum class is given a value to match
        to an enumeration member (i.e. Color(3)) and for the functional API
        (i.e. Color = Enum('Color', names='red green blue')).

        When used for the functional API: `module`, if set, will be stored in
        the new class' __module__ attribute; `type`, if set, will be mixed in
        as the first base class.

        Note: if `module` is not set this routine will attempt to discover the
        calling module by walking the frame stack; if this is unsuccessful
        the resulting class will not be pickleable.
        """
        if names is None:  # simple value lookup
            return cls.__new__(cls, value)
        # otherwise, functional API: we're creating a new Enum type
        return cls._create_(value, names, module=module, type=type, start=start)

    def __contains__(cls, member):
        if not isinstance(member, Enum):
            raise TypeError("%r (%r) is not an <aenum 'Enum'>" % (member, type(member)))
        if not isinstance(member, cls):
            return False
        return True

    def __delattr__(cls, attr):
        # nicer error message when someone tries to delete an attribute
        # (see issue19025).
        if attr in cls._member_map_:
            raise AttributeError(
                    "%s: cannot delete Enum member %r." % (cls.__name__, attr),
                    )
        if isinstance(_get_attr_from_chain(cls, attr), constant):
            raise AttributeError(
                    "%s: cannot delete constant %r" % (cls.__name__, attr),
                    )
        super(EnumMeta, cls).__delattr__(attr)

    def __dir__(self):
        return (['__class__', '__doc__', '__members__', '__module__'] +
                self._member_names_)

    @property
    def __members__(cls):
        """Returns a mapping of member name->value.

        This mapping lists all enum members, including aliases. Note that this
        is a copy of the internal mapping.
        """
        return cls._member_map_.copy()

    def __getitem__(cls, name):
        try:
            return cls._member_map_[name]
        except KeyError:
            exc = _sys.exc_info()[1]
        if issubclass(cls, Flag) and '|' in name:
            try:
                # may be an __or__ed name
                result = cls(0)
                for n in name.split('|'):
                    result |= cls[n]
                return result
            except KeyError:
                raise exc
        result = cls._missing_name_(name)
        if isinstance(result, cls):
            return result
        else:
            raise exc

    def __iter__(cls):
        return (cls._member_map_[name] for name in cls._member_names_)

    def __reversed__(cls):
        return (cls._member_map_[name] for name in reversed(cls._member_names_))

    def __len__(cls):
        return len(cls._member_names_)

    __nonzero__ = __bool__

    def __repr__(cls):
        return "<aenum %r>" % (cls.__name__, )

    def __setattr__(cls, name, value):
        """Block attempts to reassign Enum members/constants.

        A simple assignment to the class namespace only changes one of the
        several possible ways to get an Enum member from the Enum class,
        resulting in an inconsistent Enumeration.
        """
        member_map = cls.__dict__.get('_member_map_', {})
        if name in member_map:
            raise AttributeError(
                    '%s: cannot rebind member %r.' % (cls.__name__, name),
                    )
        cur_obj = cls.__dict__.get(name)
        if isinstance(cur_obj, constant):
            raise AttributeError(
                    '%s: cannot rebind constant %r' % (cls.__name__, name),
                    )
        super(EnumMeta, cls).__setattr__(name, value)

    def _create_(cls, class_name, names, module=None, type=None, start=1):
        """Convenience method to create a new Enum class.

        `names` can be:

        * A string containing member names, separated either with spaces or
          commas.  Values are auto-numbered from 1.
        * An iterable of member names.  Values are auto-numbered from 1.
        * An iterable of (member name, value) pairs.
        * A mapping of member name -> value.
        """
        if pyver < 3.0:
            # if class_name is unicode, attempt a conversion to ASCII
            if isinstance(class_name, unicode):
                try:
                    class_name = class_name.encode('ascii')
                except UnicodeEncodeError:
                    raise TypeError('%r is not representable in ASCII' % (class_name, ))
        metacls = cls.__class__
        if type is None:
            bases = (cls, )
        else:
            bases = (type, cls)
        _, first_enum = cls._get_mixins_(bases)
        generate = getattr(first_enum, '_generate_next_value_', None)
        generate = getattr(generate, 'im_func', generate)
        # special processing needed for names?
        if isinstance(names, basestring):
            names = names.replace(',', ' ').split()
        if isinstance(names, (tuple, list)) and names and isinstance(names[0], basestring):
            original_names, names = names, []
            last_values = []
            for count, name in enumerate(original_names):
                value = generate(name, start, count, last_values[:])
                last_values.append(value)
                names.append((name, value))
        # Here, names is either an iterable of (name, value) or a mapping.
        item = None  # in case names is empty
        clsdict = None
        for item in names:
            if clsdict is None:
                # first time initialization
                if isinstance(item, basestring):
                    clsdict = {}
                else:
                    # remember the order
                    clsdict = metacls.__prepare__(class_name, bases)
            if isinstance(item, basestring):
                member_name, member_value = item, names[item]
            else:
                member_name, member_value = item
            clsdict[member_name] = member_value
        if clsdict is None:
            # in case names was empty
            clsdict = metacls.__prepare__(class_name, bases)
        enum_class = metacls.__new__(metacls, class_name, bases, clsdict)
        # TODO: replace the frame hack if a blessed way to know the calling
        # module is ever developed
        if module is None:
            try:
                module = _sys._getframe(2).f_globals['__name__']
            except (AttributeError, KeyError):
                pass
        if module is None:
            _make_class_unpicklable(enum_class)
        else:
            enum_class.__module__ = module
        return enum_class

    @staticmethod
    def _get_mixins_(bases):
        """Returns the type for creating enum members, and the first inherited
        enum class.

        bases: the tuple of bases that was given to __new__
        """
        if not bases or Enum is None:
            return object, Enum
        def _find_data_type(bases):
            for chain in bases:
                for base in chain.__mro__:
                    if base is object or base is StdlibEnum:
                        continue
                    elif '__new__' in base.__dict__:
                        if issubclass(base, Enum):
                            continue
                        return base

        # ensure final parent class is an Enum derivative, find any concrete
        # data type, and check that Enum has no members
        first_enum = bases[-1]
        if not issubclass(first_enum, Enum):
            raise TypeError("new enumerations should be created as "
                    "`EnumName([mixin_type, ...] [data_type,] enum_type)`")
        member_type = _find_data_type(bases) or object
        if first_enum._member_names_:
            raise TypeError("cannot extend enumerations via subclassing")

        return member_type, first_enum

    if pyver < 3.0:
        @staticmethod
        def _find_new_(clsdict, member_type, first_enum):
            """Returns the __new__ to be used for creating the enum members.

            clsdict: the class dictionary given to __new__
            member_type: the data type whose __new__ will be used by default
            first_enum: enumeration to check for an overriding __new__
            """
            # now find the correct __new__, checking to see of one was defined
            # by the user; also check earlier enum classes in case a __new__ was
            # saved as __new_member__
            __new__ = clsdict.get('__new__', None)
            if __new__:
                return None, True, True      # __new__, save_new, new_uses_args

            N__new__ = getattr(None, '__new__')
            O__new__ = getattr(object, '__new__')
            if Enum is None:
                E__new__ = N__new__
            else:
                E__new__ = Enum.__dict__['__new__']
            # check all possibles for __new_member__ before falling back to
            # __new__
            for method in ('__new_member__', '__new__'):
                for possible in (member_type, first_enum):
                    try:
                        target = possible.__dict__[method]
                    except (AttributeError, KeyError):
                        target = getattr(possible, method, None)
                    if target not in [
                            None,
                            N__new__,
                            O__new__,
                            E__new__,
                            ]:
                        if method == '__new_member__':
                            clsdict['__new__'] = target
                            return None, False, True
                        if isinstance(target, staticmethod):
                            target = target.__get__(member_type)
                        __new__ = target
                        break
                if __new__ is not None:
                    break
            else:
                __new__ = object.__new__

            # if a non-object.__new__ is used then whatever value/tuple was
            # assigned to the enum member name will be passed to __new__ and to the
            # new enum member's __init__
            if __new__ is object.__new__:
                new_uses_args = False
            else:
                new_uses_args = True

            return __new__, False, new_uses_args
    else:
        @staticmethod
        def _find_new_(clsdict, member_type, first_enum):
            """Returns the __new__ to be used for creating the enum members.

            clsdict: the class dictionary given to __new__
            member_type: the data type whose __new__ will be used by default
            first_enum: enumeration to check for an overriding __new__
            """
            # now find the correct __new__, checking to see of one was defined
            # by the user; also check earlier enum classes in case a __new__ was
            # saved as __new_member__
            __new__ = clsdict.get('__new__', None)

            # should __new__ be saved as __new_member__ later?
            save_new = __new__ is not None

            if __new__ is None:
                # check all possibles for __new_member__ before falling back to
                # __new__
                for method in ('__new_member__', '__new__'):
                    for possible in (member_type, first_enum):
                        target = getattr(possible, method, None)
                        if target not in (
                                None,
                                None.__new__,
                                object.__new__,
                                Enum.__new__,
                                StdlibEnum.__new__
                                ):
                            __new__ = target
                            break
                    if __new__ is not None:
                        break
                else:
                    __new__ = object.__new__
            # if a non-object.__new__ is used then whatever value/tuple was
            # assigned to the enum member name will be passed to __new__ and to the
            # new enum member's __init__
            if __new__ is object.__new__:
                new_uses_args = False
            else:
                new_uses_args = True

            return __new__, save_new, new_uses_args


########################################################
# In order to support Python 2 and 3 with a single
# codebase we have to create the Enum methods separately
# and then use the `type(name, bases, dict)` method to
# create the class.
########################################################
temp_enum_dict = EnumMeta.__prepare__('Enum', (object, ))
temp_enum_dict['__doc__'] = "Generic enumeration.\n\n    Derive from this class to define new enumerations.\n\n"

def __init__(self, *args, **kwds):
    # auto-init method
    _auto_init_ = self._auto_init_
    if _auto_init_ is None:
        return
    if 'value' in _auto_init_:
        # remove 'value' from _auto_init_ as it has already been handled
        _auto_init_ = _auto_init_[1:]
    if _auto_init_:
        if len(_auto_init_) < len(args):
            raise TypeError('%d arguments expected (%s), %d received (%s)'
                    % (len(_auto_init_), _auto_init_, len(args), args))
        for name, arg in zip(_auto_init_, args):
            setattr(self, name, arg)
        if len(args) < len(_auto_init_):
            remaining_args = _auto_init_[len(args):]
            for name in remaining_args:
                value = kwds.pop(name, undefined)
                if value is undefined:
                    raise TypeError('missing value for: %r' % (name, ))
                setattr(self, name, value)
            if kwds:
                # too many keyword arguments
                raise TypeError('invalid keyword(s): %s' % ', '.join(kwds.keys()))
temp_enum_dict['__init__'] = __init__
del __init__

def __new__(cls, value):
    # all enum instances are actually created during class construction
    # without calling this method; this method is called by the metaclass'
    # __call__ (i.e. Color(3) ), and by pickle
    if NoAlias in cls._settings_:
        raise TypeError('NoAlias enumerations cannot be looked up by value')
    if type(value) is cls:
        # For lookups like Color(Color.red)
        # value = value.value
        return value
    # by-value search for a matching enum member
    # see if it's in the reverse mapping (for hashable values)
    try:
        if value in cls._value2member_map_:
            return cls._value2member_map_[value]
    except TypeError:
        # not there, now do long search -- O(n) behavior
        for name, member in cls._value2member_seq_:
            if name == value:
                return member
    # still not found -- try _missing_ hook
    try:
        exc = None
        result = cls._missing_value_(value)
    except Exception as e:
        exc = e
        result = None
    if isinstance(result, cls):
        return result
    else:
        if value is no_arg:
            ve_exc = ValueError('%s() should be called with a value' % (cls.__name__, ))
        else:
            ve_exc = ValueError("%r is not a valid %s" % (value, cls.__name__))
        if result is None and exc is None:
            raise ve_exc
        elif exc is None:
            exc = TypeError(
                    'error in %s._missing_: returned %r instead of None or a valid member'
                    % (cls.__name__, result)
                    )
        exc.__context__ = ve_exc
        raise exc
temp_enum_dict['__new__'] = __new__
del __new__

@staticmethod
def _generate_next_value_(name, start, count, last_values, *args, **kwds):
    for last_value in reversed(last_values):
        try:
            return last_value + 1
        except TypeError:
            pass
    else:
        return start
temp_enum_dict['_generate_next_value_'] = _generate_next_value_
del _generate_next_value_

@classmethod
def _missing_(cls, value):
    "deprecated, use _missing_value_ instead"
    return None
temp_enum_dict['_missing_'] = _missing_
del _missing_

@classmethod
def _missing_value_(cls, value):
    "used for failed value access"
    return cls._missing_(value)
temp_enum_dict['_missing_value_'] = _missing_value_
del _missing_value_

@classmethod
def _missing_name_(cls, name):
    "used for failed item access"
    return None
temp_enum_dict['_missing_name_'] = _missing_name_
del _missing_name_

def __repr__(self):
    return "<%s.%s: %r>" % (
            self.__class__.__name__, self._name_, self._value_)
temp_enum_dict['__repr__'] = __repr__
del __repr__

def __str__(self):
    return "%s.%s" % (self.__class__.__name__, self._name_)
temp_enum_dict['__str__'] = __str__
del __str__

if pyver >= 3.0:
    def __dir__(self):
        added_behavior = [
                m
                for cls in self.__class__.mro()
                for m in cls.__dict__
                if m[0] != '_' and m not in self._member_map_
                ]
        return (['__class__', '__doc__', '__module__', ] + added_behavior)
    temp_enum_dict['__dir__'] = __dir__
    del __dir__

def __format__(self, format_spec):
    # mixed-in Enums should use the mixed-in type's __format__, otherwise
    # we can get strange results with the Enum name showing up instead of
    # the value

    # pure Enum branch / overridden __str__ branch
    overridden_str = self.__class__.__str__ != Enum.__str__
    if self._member_type_ is object or overridden_str:
        cls = str
        val = str(self)
    # mix-in branch
    else:
        cls = self._member_type_
        val = self.value
    return cls.__format__(val, format_spec)
temp_enum_dict['__format__'] = __format__
del __format__

def __hash__(self):
    return hash(self._name_)
temp_enum_dict['__hash__'] = __hash__
del __hash__

def __reduce_ex__(self, proto):
    return self.__class__, (self._value_, )
temp_enum_dict['__reduce_ex__'] = __reduce_ex__
del __reduce_ex__


####################################
# Python's less than 2.6 use __cmp__

if pyver < 2.6:

    def __cmp__(self, other):
        if type(other) is self.__class__:
            if self is other:
                return 0
            return -1
        return NotImplemented
        raise TypeError("unorderable types: %s() and %s()" % (self.__class__.__name__, other.__class__.__name__))
    temp_enum_dict['__cmp__'] = __cmp__
    del __cmp__

else:

    def __le__(self, other):
        raise TypeError("unorderable types: %s() <= %s()" % (self.__class__.__name__, other.__class__.__name__))
    temp_enum_dict['__le__'] = __le__
    del __le__

    def __lt__(self, other):
        raise TypeError("unorderable types: %s() < %s()" % (self.__class__.__name__, other.__class__.__name__))
    temp_enum_dict['__lt__'] = __lt__
    del __lt__

    def __ge__(self, other):
        raise TypeError("unorderable types: %s() >= %s()" % (self.__class__.__name__, other.__class__.__name__))
    temp_enum_dict['__ge__'] = __ge__
    del __ge__

    def __gt__(self, other):
        raise TypeError("unorderable types: %s() > %s()" % (self.__class__.__name__, other.__class__.__name__))
    temp_enum_dict['__gt__'] = __gt__
    del __gt__


def __eq__(self, other):
    if type(other) is self.__class__:
        return self is other
    return NotImplemented
temp_enum_dict['__eq__'] = __eq__
del __eq__

def __ne__(self, other):
    if type(other) is self.__class__:
        return self is not other
    return NotImplemented
temp_enum_dict['__ne__'] = __ne__
del __ne__

def __hash__(self):
    return hash(self._name_)
temp_enum_dict['__hash__'] = __hash__
del __hash__

def __reduce_ex__(self, proto):
    return self.__class__, (self._value_, )
temp_enum_dict['__reduce_ex__'] = __reduce_ex__
del __reduce_ex__

def _convert(cls, name, module, filter, source=None):
    """
    Create a new Enum subclass that replaces a collection of global constants
    """
    # convert all constants from source (or module) that pass filter() to
    # a new Enum called name, and export the enum and its members back to
    # module;
    # also, replace the __reduce_ex__ method so unpickling works in
    # previous Python versions
    module_globals = vars(_sys.modules[module])
    if source:
        source = vars(source)
    else:
        source = module_globals
    members = [(key, source[key]) for key in source.keys() if filter(key)]
    try:
        # sort by value, name
        members.sort(key=lambda t: (t[1], t[0]))
    except TypeError:
        # unless some values aren't comparable, in which case sort by just name
        members.sort(key=lambda t: t[0])
    cls = cls(name, members, module=module)
    cls.__reduce_ex__ = _reduce_ex_by_name
    module_globals.update(cls.__members__)
    module_globals[name] = cls
    return cls
temp_enum_dict['_convert'] = classmethod(_convert)
del _convert

# enum_property is used to provide access to the `name`, `value', etc.,
# properties of enum members while keeping some measure of protection
# from modification, while still allowing for an enumeration to have
# members named `name`, `value`, etc..  This works because enumeration
# members are not set directly on the enum class -- enum_property will
# look them up in _member_map_.

@enum_property
def name(self):
    return self._name_
temp_enum_dict['name'] = name
del name

@enum_property
def value(self):
    return self._value_
temp_enum_dict['value'] = value
del value

@enum_property
def values(self):
    return self._values_
temp_enum_dict['values'] = values
del values

def _reduce_ex_by_name(self, proto):
    return self.name

if StdlibEnum is not None:
    Enum = EnumMeta('Enum', (StdlibEnum, ), temp_enum_dict)
else:
    Enum = EnumMeta('Enum', (object, ), temp_enum_dict)
del temp_enum_dict

# Enum has now been created
###########################

class IntEnum(int, Enum):
    """Enum where members are also (and must be) ints"""

class StrEnum(str, Enum): 
    """Enum where members are also (and must already be) strings
    
        default value is member name
    """
    def __new__(cls, value, *args, **kwds):
        if args or kwds:
            raise TypeError('only a single string value may be specified')
        if not isinstance(value, str):
            raise TypeError('values for StrEnum must be strings, not %r' % type(value))
        obj = str.__new__(cls, value)
        obj._value_ = value
        return obj
    def _generate_next_value_(name, start, count, last_values, *args, **kwds):
        return name

class LowerStrEnum(StrEnum):
    """Enum where members are also (and must already be) lower-case strings
    
        default value is member name, lower-cased
    """
    def __new__(cls, value, *args, **kwds):
        obj = StrEnum.__new_member__(cls, value, *args, **kwds)
        if value != value.lower():
            raise ValueError('%r is not lower-case' % value)
        return obj
    def _generate_next_value_(name, start, count, last_values, *args, **kwds):
        return name.lower()

class UpperStrEnum(StrEnum):
    """Enum where members are also (and must already be) upper-case strings
    
        default value is member name, upper-cased
    """
    def __new__(cls, value, *args, **kwds):
        obj = StrEnum.__new_member__(cls, value, *args, **kwds)
        if value != value.upper():
            raise ValueError('%r is not upper-case' % value)
        return obj
    def _generate_next_value_(name, start, count, last_values, *args, **kwds):
        return name.upper()

if pyver >= 3:
    class AutoEnum(Enum):
        """
        automatically use _generate_next_value_ when values are missing (Python 3 only)
        """
        _settings_ = AutoValue

class AutoNumberEnum(Enum):
    """
    Automatically assign increasing values to members.

    Py3: numbers match creation order
    Py2: numbers are assigned alphabetically by member name
    """
    def __new__(cls, *args, **kwds):
        value = len(cls.__members__) + 1
        obj = object.__new__(cls)
        obj._value_ = value
        return obj

class MultiValueEnum(Enum):
    """
    Multiple values can map to each member.
    """
    _settings_ = MultiValue

class NoAliasEnum(Enum):
    """
    Duplicate value members are distinct, but cannot be looked up by value.
    """
    _settings_ = NoAlias

class OrderedEnum(Enum):
    """
    Add ordering based on values of Enum members.
    """
    def __ge__(self, other):
        if self.__class__ is other.__class__:
            return self._value_ >= other._value_
        return NotImplemented

    def __gt__(self, other):
        if self.__class__ is other.__class__:
            return self._value_ > other._value_
        return NotImplemented

    def __le__(self, other):
        if self.__class__ is other.__class__:
            return self._value_ <= other._value_
        return NotImplemented

    def __lt__(self, other):
        if self.__class__ is other.__class__:
            return self._value_ < other._value_
        return NotImplemented

if sqlite3:
    class SqliteEnum(Enum):
        def __conform__(self, protocol):
            if protocol is sqlite3.PrepareProtocol:
                return self.name

class UniqueEnum(Enum):
    """
    Ensure no duplicate values exist.
    """
    _settings_ = Unique


def convert(enum, name, module, filter, source=None):
    """
    Create a new Enum subclass that replaces a collection of global constants

    enum: Enum, IntEnum, ...
    name: name of new Enum
    module: name of module (__name__ in global context)
    filter: function that returns True if name should be converted to Enum member
    source: namespace to check (defaults to 'module')
    """
    # convert all constants from source (or module) that pass filter() to
    # a new Enum called name, and export the enum and its members back to
    # module;
    # also, replace the __reduce_ex__ method so unpickling works in
    # previous Python versions
    module_globals = vars(_sys.modules[module])
    if source:
        source = vars(source)
    else:
        source = module_globals
    members = dict((name, value) for name, value in source.items() if filter(name))
    enum = enum(name, members, module=module)
    enum.__reduce_ex__ = _reduce_ex_by_name
    module_globals.update(enum.__members__)
    module_globals[name] = enum

def extend_enum(enumeration, name, *args, **_private_kwds):
    """
    Add a new member to an existing Enum.
    """
    try:
        _member_map_ = enumeration._member_map_
        _member_names_ = enumeration._member_names_
        _member_type_ = enumeration._member_type_
        _value2member_map_ = enumeration._value2member_map_
        base_attributes = set([a for b in enumeration.mro() for a in b.__dict__])
    except AttributeError:
        raise TypeError('%r is not a supported Enum' % (enumeration, ))
    try:
        _value2member_seq_ = enumeration._value2member_seq_
        # _auto_number_ = enumeration._auto_number_
        _multi_value_ = MultiValue in enumeration._settings_
        _no_alias_ = NoAlias in enumeration._settings_
        _unique_ = Unique in enumeration._settings_
        # _unique_ = Unique in enumeration._settings_
        _auto_init_ = enumeration._auto_init_ or []
    except AttributeError:
        # standard Enum
        _value2member_seq_ = []
        # _auto_number_ = False
        _multi_value_ = False
        _no_alias_ = False
        # _unique_ = False
        _auto_init_ = []
    mt_new = _member_type_.__new__
    _new = getattr(enumeration, '__new_member__', mt_new)
    if not args:
        _gnv = getattr(enumeration, '_generate_next_value_')
        if _gnv is None:
            raise TypeError('value not provided and _generate_next_value_ missing')
        last_values = [m.value for m in enumeration]
        count = len(enumeration)
        start = getattr(enumeration, '_start_')
        if start is None:
            start = last_values and last_values[0] or 1
        args = ( _gnv(name, start, count, last_values), )
    if _new is object.__new__:
        new_uses_args = False
    else:
        new_uses_args = True
    if len(args) == 1:
        [value] = args
    else:
        value = args
    more_values = ()
    kwds = {}
    if isinstance(value, enum):
        args = value.args
        kwds = value.kwds
    if not isinstance(value, tuple):
        args = (value, )
    else:
        args = value
    # tease value out of auto-init if specified
    if 'value' in _auto_init_:
        if 'value' in kwds:
            value = kwds.pop('value')
        else:
            value, args = args[0], args[1:]
    elif _multi_value_:
        value, more_values, args = args[0], args[1:], ()
    if _member_type_ is tuple:
        args = (args, )
    if not new_uses_args:
        new_member = _new(enumeration)
        if not hasattr(new_member, '_value_'):
            new_member._value_ = value
    else:
        new_member = _new(enumeration, *args, **kwds)
        if not hasattr(new_member, '_value_'):
            new_member._value_ = _member_type_(*args)
    value = new_member._value_
    new_member._name_ = name
    new_member.__objclass__ = enumeration.__class__
    new_member.__init__(*args)
    if _private_kwds.get('create_only'):
        return new_member
    # If another member with the same value was already defined, the
    # new member becomes an alias to the existing one.
    is_alias = False
    if _no_alias_:
        # unless NoAlias was specified
        _member_names_.append(name)
        _member_map_[name] = new_member
    else:
        for canonical_member in _member_map_.values():
            _values_ = getattr(canonical_member, '_values_', [canonical_member._value_])
            for canonical_value in _values_:
                if canonical_value == new_member._value_:
                    # name is an alias
                    if _unique_ or _multi_value_:
                        # aliases not allowed if Unique specified
                        raise ValueError('%s is a duplicate of %s' % (name, canonical_member.name))
                    if name not in base_attributes:
                        setattr(enumeration, name, canonical_member)
                    else:
                        # check type of name
                        for parent in enumeration.mro()[1:]:
                            if name in parent.__dict__:
                                obj = parent.__dict__[name]
                                if not isinstance(obj, enum_property):
                                    raise TypeError('%r already used: %r' % (name, obj))
                                break
                    # Aliases don't appear in member names (only in __members__ and _member_map_).
                    _member_map_[new_member._name_] = canonical_member
                    new_member = canonical_member
                    is_alias = True
                    break
            if is_alias:
                break
        else:
            # not an alias
            values = (value, ) + more_values
            new_member._values_ = values
            for value in (value, ) + more_values:
                # first check if value has already been used
                if _multi_value_ and (
                        value in _value2member_map_
                        or any(v == value for (v, m) in _value2member_seq_)
                        ):
                    raise ValueError('%r has already been used' % (value, ))
                try:
                    # This may fail if value is not hashable. We can't add the value
                    # to the map, and by-value lookups for this value will be
                    # linear.
                    if _no_alias_:
                        raise TypeError('cannot use dict to store value')
                    _value2member_map_[value] = new_member
                except TypeError:
                    _value2member_seq_ += ((value, new_member), )
            if name not in base_attributes:
                setattr(enumeration, name, new_member)
            else:
                # check type of name
                for parent in enumeration.mro()[1:]:
                    if name in parent.__dict__:
                        obj = parent.__dict__[name]
                        if not isinstance(obj, enum_property):
                            raise TypeError('%r already used: %r' % (name, obj))
                        break
            _member_names_.append(name)
            _member_map_[name] = new_member
            try:
                _value2member_map_[value] = new_member
            except TypeError:
                pass

def unique(enumeration):
    """
    Class decorator that ensures only unique members exist in an enumeration.
    """
    duplicates = []
    for name, member in enumeration.__members__.items():
        if name != member.name:
            duplicates.append((name, member.name))
    if duplicates:
        duplicate_names = ', '.join(
                ["%s -> %s" % (alias, name) for (alias, name) in duplicates]
                )
        raise ValueError('duplicate names found in %r: %s' %
                (enumeration, duplicate_names)
                )
    return enumeration

class Flag(Enum):
    """Support for flags"""

    def _generate_next_value_(name, start, count, last_values):
        """
        Generate the next value when not given.

        name: the name of the member
        start: the initital start value or None
        count: the number of existing members
        last_value: the last value assigned or None
        """
        if not count:
            return (1, start)[start is not None]
        error = False
        for last_value in reversed(last_values):
            if isinstance(last_value, auto):
                last_value = last_value.value
            try:
                high_bit = _high_bit(last_value)
                break
            except Exception:
                error = True
                break
        if error:
            raise TypeError('invalid Flag value: %r' % (last_value, ))
        return 2 ** (high_bit+1)

    @classmethod
    def _missing_(cls, value):
        original_value = value
        if value < 0:
            value = ~value
        possible_member = cls._create_pseudo_member_(value)
        if original_value < 0:
            possible_member = ~possible_member
        return possible_member

    @classmethod
    def _create_pseudo_member_(cls, *values):
        """
        Create a composite member iff value contains only members.
        """
        value = values[0]
        pseudo_member = cls._value2member_map_.get(value, None)
        if pseudo_member is None:
            # verify all bits are accounted for
            members, extra_flags = _decompose(cls, value)
            if extra_flags:
                raise ValueError("%r is not a valid %s" % (value, cls.__name__))
            # give subclasses a chance to modify values for new pseudo-member
            values = cls._create_pseudo_member_values_(members, *values)
            # construct a singleton enum pseudo-member
            pseudo_member = extend_enum(cls, None, *values, create_only=True)
            # use setdefault in case another thread already created a composite
            # with this value
            pseudo_member = cls._value2member_map_.setdefault(value, pseudo_member)
        return pseudo_member

    @classmethod
    def _create_pseudo_member_values_(cls, members, *values):
        return values

    def __contains__(self, other):
        if not isinstance(other, Flag):
            raise TypeError("%r (%r) is not an <aenum 'Flag'>" % (other, type(other)))
        if not isinstance(other, self.__class__):
            return False
        return other._value_ & self._value_ == other._value_

    def __repr__(self):
        cls = self.__class__
        if self._name_ is not None:
            return '<%s.%s: %r>' % (cls.__name__, self._name_, self._value_)
        members, uncovered = _decompose(cls, self._value_)
        return '<%s.%s: %r>' % (
                cls.__name__,
                '|'.join([str(m._name_ or m._value_) for m in members]),
                self._value_,
                )

    def __str__(self):
        cls = self.__class__
        if self._name_ is not None:
            return '%s.%s' % (cls.__name__, self._name_)
        members, uncovered = _decompose(cls, self._value_)
        if len(members) == 1 and members[0]._name_ is None:
            return '%s.%r' % (cls.__name__, members[0]._value_)
        else:
            return '%s.%s' % (
                    cls.__name__,
                    '|'.join([str(m._name_ or m._value_) for m in members]),
                    )

    def __bool__(self):
        return bool(self._value_)
    if pyver < 3:
        __nonzero__ = __bool__
        del __bool__

    def __or__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return self.__class__(self._value_ | other._value_)

    def __and__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return self.__class__(self._value_ & other._value_)

    def __xor__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return self.__class__(self._value_ ^ other._value_)

    def __invert__(self):
        members, uncovered = _decompose(self.__class__, self._value_)
        inverted_members = [
                m for m in self.__class__
                if m not in members and not m._value_ & self._value_
                ]
        inverted = reduce(_or_, inverted_members, self.__class__(0))
        return self.__class__(inverted)

    def __iter__(self):
        members, extra_flags = _decompose(self.__class__, self.value)
        return (m for m in members if m._value_ != 0)


class IntFlag(int, Flag):
    """Support for integer-based Flags"""

    @classmethod
    def _missing_(cls, value):
        if not isinstance(value, int):
            raise ValueError("%r is not a valid %s" % (value, cls.__name__))
        new_member = cls._create_pseudo_member_(value)
        return new_member

    @classmethod
    def _create_pseudo_member_(cls, value):
        pseudo_member = cls._value2member_map_.get(value, None)
        if pseudo_member is None:
            need_to_create = [value]
            # get unaccounted for bits
            _, extra_flags = _decompose(cls, value)
            while extra_flags:
                bit = _high_bit(extra_flags)
                flag_value = 2 ** bit
                if (flag_value not in cls._value2member_map_ and
                        flag_value not in need_to_create
                        ):
                    need_to_create.append(flag_value)
                if extra_flags == -flag_value:
                    extra_flags = 0
                else:
                    extra_flags ^= flag_value
            for value in reversed(need_to_create):
                # construct singleton pseudo-members
                pseudo_member = int.__new__(cls, value)
                pseudo_member._name_ = None
                pseudo_member._value_ = value
                # use setdefault in case another thread already created a composite
                # with this value
                pseudo_member = cls._value2member_map_.setdefault(value, pseudo_member)
        return pseudo_member

    def __or__(self, other):
        if not isinstance(other, (self.__class__, int)):
            return NotImplemented
        result = self.__class__(self._value_ | self.__class__(other)._value_)
        return result

    def __and__(self, other):
        if not isinstance(other, (self.__class__, int)):
            return NotImplemented
        return self.__class__(self._value_ & self.__class__(other)._value_)

    def __xor__(self, other):
        if not isinstance(other, (self.__class__, int)):
            return NotImplemented
        return self.__class__(self._value_ ^ self.__class__(other)._value_)

    __ror__ = __or__
    __rand__ = __and__
    __rxor__ = __xor__

    def __invert__(self):
        result = self.__class__(~self._value_)
        return result


def _high_bit(value):
    """returns index of highest bit, or -1 if value is zero or negative"""
    return value.bit_length() - 1

def _decompose(flag, value):
    """Extract all members from the value."""
    # _decompose is only called if the value is not named
    not_covered = value
    negative = value < 0
    # issue29167: wrap accesses to _value2member_map_ in a list to avoid race
    #             conditions between iterating over it and having more psuedo-
    #             members added to it
    if negative:
        # only check for named flags
        flags_to_check = [
                (m, v)
                for v, m in list(flag._value2member_map_.items())
                if m.name is not None
                ]
    else:
        # check for named flags and powers-of-two flags
        flags_to_check = [
                (m, v)
                for v, m in list(flag._value2member_map_.items())
                if m.name is not None or _power_of_two(v)
                ]
    members = []
    for member, member_value in flags_to_check:
        if member_value and member_value & value == member_value:
            members.append(member)
            not_covered &= ~member_value
    if not members and value in flag._value2member_map_:
        members.append(flag._value2member_map_[value])
    members.sort(key=lambda m: m._value_, reverse=True)
    if len(members) > 1 and members[0].value == value:
        # we have the breakdown, don't need the value member itself
        members.pop(0)
    return members, not_covered

def _power_of_two(value):
    if value < 1:
        return False
    return value == 2 ** _high_bit(value)


class module(object):

    def __init__(self, cls, *args):
        self.__name__ = cls.__name__
        self._parent_module = cls.__module__
        self.__all__ = []
        all_objects = cls.__dict__
        if not args:
            args = [k for k, v in all_objects.items() if isinstance(v, (NamedConstant, Enum))]
        for name in args:
            self.__dict__[name] = all_objects[name]
            self.__all__.append(name)

    def register(self):
        _sys.modules["%s.%s" % (self._parent_module, self.__name__)] = self



================================================ FILE: docs/_build/html/_modules/gs_quant/base.html ================================================ gs_quant.base — gs_quant 0.1 documentation

Source code for gs_quant.base

"""
Copyright 2019 Goldman Sachs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
"""
import builtins
import copy
import datetime as dt
import itertools
import keyword
import logging
from abc import ABCMeta, abstractmethod
from collections import namedtuple
from enum import EnumMeta
from functools import wraps
from inspect import signature, Parameter
from typing import Optional, Union, get_type_hints

import inflection
from dateutil.parser import isoparse
import numpy as np

from gs_quant.context_base import ContextBase, ContextMeta, do_not_serialise

_logger = logging.getLogger(__name__)

_valid_date_formats = ('%Y-%m-%d',  # '2020-07-28'
                       '%d%b%y',    # '28Jul20'
                       '%d-%b-%y',  # '28-Jul-20'
                       '%d/%m/%Y')  # '28/07/2020


def _normalise_arg(arg: str) -> str:
    if keyword.iskeyword(arg) or arg in dir(builtins):
        return arg + '_'
    else:
        return arg


def camel_case_translate(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        normalised_kwargs = {}
        for arg, value in kwargs.items():
            arg = _normalise_arg(arg)

            if not arg.isupper():
                snake_case_arg = inflection.underscore(arg)
                if snake_case_arg != arg:
                    if snake_case_arg in kwargs:
                        raise ValueError('{} and {} both specified'.format(arg, snake_case_arg))

                    normalised_kwargs[snake_case_arg] = value
                else:
                    normalised_kwargs[arg] = value
            else:
                normalised_kwargs[arg] = value

        return f(*args, **normalised_kwargs)

    return wrapper


def do_not_resolve(func):
    func.do_not_resolve = True
    return func


class RiskKey(namedtuple('RiskKey', ('provider', 'date', 'market', 'params', 'scenario', 'risk_measure'))):

    @property
    def ex_date_and_market(self):
        return RiskKey(self.provider, None, None, self.params, self.scenario, self.risk_measure)

    @property
    def ex_measure(self):
        return RiskKey(self.provider, self.date, self.market, self.params, self.scenario, None)

    @property
    def fields(self):
        return self._fields


class EnumBase:

    @classmethod
    def _missing_(cls: EnumMeta, key):
        return next((m for m in cls.__members__.values() if m.value.lower() == key.lower()), None)

    def __reduce_ex__(self, protocol):
        return self.__class__, (self.value,)

    def __lt__(self: EnumMeta, other):
        return self.value < other.value


class Base(metaclass=ABCMeta):
    """The base class for all generated classes"""

    __properties = set()

    def __init__(self, **kwargs):
        self.__calced_hash: Optional[int] = None
        self.__as_dict = {False: {}, True: {}}

        try:
            self.name: Optional[str] = None
        except AttributeError:
            # Regrettably, read-only properties called name exist
            pass

    def __getattr__(self, item):
        snake_case_item = inflection.underscore(item)
        if snake_case_item in super().__getattribute__('properties')():
            return super().__getattribute__(snake_case_item)
        else:
            return super().__getattribute__(item)

    def __setattr__(self, key, value):
        properties = super().__getattribute__('properties')()
        snake_case_key = inflection.underscore(key)
        key_is_property = key in properties
        snake_case_key_is_property = snake_case_key in properties

        if snake_case_key_is_property and not key_is_property:
            return super().__setattr__(snake_case_key, value)
        else:
            return super().__setattr__(key, value)

    def __repr__(self):
        if self.name is not None:
            return '{} ({})'.format(self.name, self.__class__.__name__)

        return super().__repr__()

    def _property_changed(self, prop: str):
        self.__calced_hash = None
        self.__as_dict = {False: {}, True: {}}

    @property
    def _hash_is_calced(self) -> bool:
        return self.__calced_hash is not None

    def __hash__(self) -> int:
        if not self._hash_is_calced:
            calced_hash = hash(self.name)
            for prop in self.properties():
                value = super().__getattribute__(prop)
                if isinstance(value, dict):
                    value = tuple(value.items())
                elif isinstance(value, list):
                    value = tuple(value)
                calced_hash ^= hash(value)

            self.__calced_hash = calced_hash

        return self.__calced_hash

    def __eq__(self, other) -> bool:
        return \
            type(self) == type(other) and (self.name is None or other.name is None or self.name == other.name) and \
            (self.__calced_hash is None or other.__calced_hash is None or self.__calced_hash == other.__calced_hash) \
            and all(super(Base, self).__getattribute__(p) == super(Base, other).__getattribute__(p)
                    for p in self.properties())

    def __ne__(self, other) -> bool:
        return not self.__eq__(other)

    def __lt__(self, other):
        if not isinstance(other, type(self)):
            return type(self).__name__ < type(other).__name__

        for prop in itertools.chain(('name',), sorted(self.properties())):
            val = super(Base, self).__getattribute__(prop)
            other_val = super(Base, other).__getattribute__(prop)

            if val != other_val:
                if val is None:
                    return False
                return val < other_val

        return False

    def clone(self, **kwargs):
        """
            Clone this object, overriding specified values

            :param kwargs: property names and values, e.g. swap.clone(fixed_rate=0.01)

            **Examples**

            To change the market data location of the default context:

            >>> from gs_quant.instrument import IRCap
            >>> cap = IRCap('5y', 'GBP')
            >>>
            >>> new_cap = cap.clone(cap_rate=0.01)
        """
        clone = copy.copy(self)
        properties = self.properties()
        for key, value in kwargs.items():
            if key in properties:
                setattr(clone, key, value)
            else:
                raise ValueError('Only properties may be passed as kwargs')

        return clone

    @classmethod
    def properties(cls) -> set:
        """The public property names of this class"""
        if not cls.__properties:
            cls.__properties = set(i for i in dir(cls) if isinstance(getattr(cls, i), property)
                                   and not i.startswith('_') and not
                                   getattr(getattr(cls, i).fget, 'do_not_serialise', False))
        return cls.__properties

    def as_dict(self, as_camel_case: bool = False) -> dict:
        """Dictionary of the public, non-null properties and values"""
        if not self.__as_dict[as_camel_case]:
            raw_properties = self.properties()
            properties = (inflection.camelize(p, uppercase_first_letter=False) for p in raw_properties) \
                if as_camel_case else raw_properties
            values = (super(Base, self).__getattribute__(p) for p in raw_properties)
            self.__as_dict[as_camel_case] = dict((p, v) for p, v in zip(properties, values) if v is not None)

        return copy.copy(self.__as_dict[as_camel_case])

    @classmethod
    def prop_type(cls, prop: str, additional: Optional[list] = None) -> type:
        return_hints = get_type_hints(getattr(cls, prop).fget).get('return')
        if hasattr(return_hints, '__origin__'):
            prop_type = return_hints.__origin__
        else:
            prop_type = return_hints

        if prop_type == Union:
            prop_type = next((a for a in return_hints.__args__ if issubclass(a, (Base, EnumBase))), None)
            if prop_type is None:
                for typ in (dt.datetime, dt.date):
                    if typ in return_hints.__args__:
                        prop_type = typ

                        if additional is not None:
                            additional.extend([a for a in return_hints.__args__ if a != prop_type])
                        break

            if prop_type is None and additional is not None:
                prop_type = return_hints.__args__[-1]
                additional.extend(return_hints.__args__[:-1])

        if prop_type is InstrumentBase:
            # TODO Fix this
            from .instrument import Instrument
            prop_type = Instrument

        return prop_type

    @classmethod
    def prop_item_type(cls, prop: str) -> type:
        return_hints = get_type_hints(getattr(cls, prop).fget).get('return')
        item_type = return_hints.__args__[0]

        item_args = [i for i in getattr(item_type, '__args__', ()) if isinstance(i, type)]
        if item_args:
            item_type = next((a for a in item_args if issubclass(a, (Base, EnumBase))), item_args[-1])

        return item_type

    def __from_dict(self, values: dict):
        for prop in self.properties():
            if getattr(type(self), prop).fset is None:
                continue

            prop_value = values.get(prop, values.get(inflection.camelize(prop, uppercase_first_letter=False)))

            if prop_value is not None:
                if isinstance(prop_value, np.generic):
                    prop_value = prop_value.item()

                additional_types = []
                prop_type = self.prop_type(prop, additional=additional_types)

                if prop_type is None:
                    # This shouldn't happen
                    setattr(self, prop, prop_value)
                elif issubclass(prop_type, dt.datetime):
                    if isinstance(prop_value, int):
                        setattr(self, prop, dt.datetime.fromtimestamp(prop_value / 1000).isoformat())
                    else:
                        import re
                        matcher = re.search('\\.([0-9]*)Z$', prop_value)
                        if matcher:
                            sub_seconds = matcher.group(1)
                            if len(sub_seconds) > 6:
                                prop_value = re.sub(matcher.re, '.{}Z'.format(sub_seconds[:6]), prop_value)

                        try:
                            setattr(self, prop, isoparse(prop_value))
                        except ValueError:
                            if str in additional_types:
                                setattr(self, prop, prop_value)
                elif issubclass(prop_type, dt.date) and type(prop_value) is not dt.date:
                    date_value = None

                    if isinstance(prop_value, float):
                        # Assume it's an Excel date
                        if prop_value > 59:
                            prop_value -= 1  # Excel leap year bug, 1900 is not a leap year!
                        date_value = dt.datetime(1899, 12, 31) + dt.timedelta(days=prop_value).date()
                    elif isinstance(prop_value, str):
                        for format in _valid_date_formats:
                            try:
                                date_value = dt.datetime.strptime(prop_value, format).date()
                                break
                            except ValueError:
                                pass

                    setattr(self, prop, date_value or prop_value)
                elif issubclass(prop_type, float) and isinstance(prop_value, str):
                    if prop_value.endswith('%'):
                        setattr(self, prop, float(prop_value[:-1]) / 100)
                    else:
                        setattr(self, prop, float(prop_value))
                elif issubclass(prop_type, EnumBase):
                    setattr(self, prop, get_enum_value(prop_type, prop_value))
                elif issubclass(prop_type, Base):
                    if isinstance(prop_value, Base):
                        setattr(self, prop, prop_value)
                    else:
                        setattr(self, prop, prop_type.from_dict(prop_value))
                elif issubclass(prop_type, (list, tuple)):
                    item_type = self.prop_item_type(prop)
                    if issubclass(item_type, Base):
                        item_values = tuple(v if isinstance(v, (Base, EnumBase)) else item_type.from_dict(v)
                                            for v in prop_value)
                    elif issubclass(item_type, EnumBase):
                        item_values = tuple(get_enum_value(item_type, v) for v in prop_value)
                    else:
                        item_values = tuple(prop_value)
                    setattr(self, prop, item_values)
                else:
                    setattr(self, prop, prop_value)

    @classmethod
    def _from_dict(cls, values: dict):
        args = [k for k, v in signature(cls.__init__).parameters.items() if k not in ('kwargs', '_kwargs')
                and v.default == Parameter.empty][1:]
        required = {}

        for arg in args:
            prop_name = arg[:-1] if arg.endswith('_') and not keyword.iskeyword(arg) else arg
            prop_type = cls.prop_type(prop_name)
            value = values.pop(arg, None)

            if prop_type:
                if issubclass(prop_type, Base) and isinstance(value, dict):
                    value = prop_type.from_dict(value)
                elif issubclass(prop_type, (list, tuple)) and isinstance(value, (list, tuple)):
                    item_type = cls.prop_item_type(prop_name)
                    if issubclass(item_type, Base):
                        value = tuple(v if isinstance(v, (Base, EnumBase)) else item_type.from_dict(v) for v in value)
                elif issubclass(prop_type, EnumBase):
                    value = get_enum_value(prop_type, value)

            required[arg] = value

        instance = cls(**required)
        instance.__from_dict(values)
        return instance

    @classmethod
    def from_dict(cls, values: dict):
        """
        Construct an instance of this type from a dictionary

        :param values: a dictionary (potentially nested)
        :return: an instance of this type, populated with values
        """
        return cls._from_dict(values)

    @classmethod
    def default_instance(cls):
        """
        Construct a default instance of this type
        """
        args = [k for k, v in signature(cls.__init__).parameters.items() if v.default == Parameter.empty][1:]
        required = {a: None for a in args}
        return cls(**required)

    def from_instance(self, instance):
        """
        Copy the values from an existing instance of the same type to our self
        :param instance: from which to copy:
        :return:
        """
        if not isinstance(instance, type(self)):
            raise ValueError('Can only use from_instance with an object of the same type')

        for prop in self.properties():
            attr = getattr(super().__getattribute__('__class__'), prop)
            if attr.fset:
                super(Base, self).__setattr__(prop, super(Base, instance).__getattribute__(prop))


[docs]class Priceable(Base, metaclass=ABCMeta): def resolve(self, in_place: bool = True): """ Resolve non-supplied properties of an instrument **Examples** >>> from gs_quant.instrument import IRSwap >>> >>> swap = IRSwap('Pay', '10y', 'USD') >>> rate = swap.fixedRate rate is None >>> swap.resolve() >>> rate = swap.fixedRate rates is now the solved fixed rate """ raise NotImplementedError def dollar_price(self): """ Present value in USD :return: a float or a future, depending on whether the current PricingContext is async, or has been entered **Examples** >>> from gs_quant.instrument import IRCap >>> >>> cap = IRCap('1y', 'EUR') >>> price = cap.dollar_price() price is the present value in USD (a float) >>> cap_usd = IRCap('1y', 'USD') >>> cap_eur = IRCap('1y', 'EUR') >>> >>> from gs_quant.markets import PricingContext >>> >>> with PricingContext(): >>> price_usd_f = cap_usd.dollar_price() >>> price_eur_f = cap_eur.dollar_price() >>> >>> price_usd = price_usd_f.result() >>> price_eur = price_eur_f.result() price_usd_f and price_eur_f are futures, price_usd and price_eur are floats """ raise NotImplementedError def price(self): """ Present value in local currency. Note that this is not yet supported on all instruments ***Examples** >>> from gs_quant.instrument import IRSwap >>> >>> swap = IRSwap('Pay', '10y', 'EUR') >>> price = swap.price() price is the present value in EUR (a float) """ raise NotImplementedError def calc(self, risk_measure, fn=None): """ Calculate the value of the risk_measure :param risk_measure: the risk measure to compute, e.g. IRDelta (from gs_quant.risk) :param fn: a function for post-processing results :return: a float or dataframe, depending on whether the value is scalar or structured, or a future thereof (depending on how PricingContext is being used) **Examples** >>> from gs_quant.instrument import IRCap >>> from gs_quant.risk import IRDelta >>> >>> cap = IRCap('1y', 'USD') >>> delta = cap.calc(IRDelta) delta is a dataframe >>> from gs_quant.instrument import EqOption >>> from gs_quant.risk import EqDelta >>> >>> option = EqOption('.SPX', '3m', 'ATMF', 'Call', 'European') >>> delta = option.calc(EqDelta) delta is a float >>> from gs_quant.markets import PricingContext >>> >>> cap_usd = IRCap('1y', 'USD') >>> cap_eur = IRCap('1y', 'EUR') >>> with PricingContext(): >>> usd_delta_f = cap_usd.calc(IRDelta) >>> eur_delta_f = cap_eur.calc(IRDelta) >>> >>> usd_delta = usd_delta_f.result() >>> eur_delta = eur_delta_f.result() usd_delta_f and eur_delta_f are futures, usd_delta and eur_delta are dataframes """ raise NotImplementedError
class __ScenarioMeta(ABCMeta, ContextMeta): pass class Scenario(Base, ContextBase, metaclass=__ScenarioMeta): pass class InstrumentBase(Base): def __init__(self, quantity: Optional[float] = 1): super().__init__() self.__instrument_quantity = quantity self.__resolution_key: Optional[RiskKey] = None self.__unresolved: Optional[InstrumentBase] = None @property @abstractmethod @do_not_serialise def provider(self): ... @property @do_not_serialise def instrument_quantity(self) -> float: return self.__instrument_quantity @property @do_not_serialise def resolution_key(self) -> RiskKey: return self.__resolution_key @property @do_not_serialise def unresolved(self): return self.__unresolved def _property_changed(self, prop: str): if self.__resolution_key: self.__resolution_key = None self.unresolve() super()._property_changed(prop) def from_instance(self, instance): self.__resolution_key = None super().from_instance(instance) self.__unresolved = instance.__unresolved self.__resolution_key = instance.__resolution_key def resolved(self, values: dict, resolution_key: RiskKey): new_instrument = self.from_dict(values) new_instrument.name = self.name new_instrument.__unresolved = copy.copy(self) new_instrument.__resolution_key = resolution_key return new_instrument def unresolve(self): if self.__resolution_key and self.__unresolved: self.from_instance(self.__unresolved) self.__resolution_key = None self.__unresolved = None class QuotableBuilder(Base, metaclass=ABCMeta): def __init__(self, valuation_overrides: Optional[dict] = None): super().__init__() self.valuation_overrides = valuation_overrides @property @do_not_resolve def valuation_overrides(self) -> Optional[dict]: return self.__valuation_overrides @valuation_overrides.setter def valuation_overrides(self, value: dict): self.__valuation_overrides = value class Market(Base): @property @abstractmethod def location(self): ... class Sentinel: def __init__(self, name: str): self.__name = name def __eq__(self, other): return self.__name == other.__name def get_enum_value(enum_type: EnumMeta, value: Union[EnumBase, str]): if value in (None,): return None if isinstance(value, enum_type): return value try: enum_value = enum_type(value) except ValueError: _logger.warning('Setting value to {}, which is not a valid entry in {}'.format(value, enum_type)) enum_value = value return enum_value def as_tuple(f): def wrap(*args): value = args[1] if value is not None and not isinstance(value, str): try: iter(value) except TypeError: value = (value,) return f(args[0], value) return wrap

================================================ FILE: docs/_build/html/_modules/gs_quant/data/core.html ================================================ gs_quant.data.core — gs_quant 0.1 documentation

Source code for gs_quant.data.core

"""
Copyright 2019 Goldman Sachs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
"""
import datetime
from enum import Enum

from gs_quant.context_base import ContextBaseWithDefault


def _now():
    return datetime.datetime.now(datetime.timezone.utc)


class DataFrequency(Enum):
    """Data Frequency enumeration

    Enumeration of different data frequencies for field subscription

    """

    #: Data subscription for series updating daily
    DAILY = 'daily'

    #: Data subscription for real-time or intraday series
    REAL_TIME = 'realTime'

    #: Data subscription for real-time or daily series
    ANY = 'any'


[docs]class DataContext(ContextBaseWithDefault):
[docs] def __init__(self, start=None, end=None): super().__init__() self.__start = start self.__end = end
@staticmethod def _get_date(o, default): if o is None: return default elif isinstance(o, datetime.datetime): # note that datetime objects are also instances of date return o.date() elif isinstance(o, datetime.date): return o elif isinstance(o, str): loc = o.find('T') ds = o[:loc] if loc != -1 else o return datetime.datetime.strptime(ds, '%Y-%m-%d').date() else: raise ValueError(f'{o} is not a valid date') @staticmethod def _get_datetime(o, default): if o is None: return default elif isinstance(o, datetime.datetime): return o elif isinstance(o, datetime.date): return datetime.datetime.combine(o, datetime.time(tzinfo=datetime.timezone.utc)) elif isinstance(o, str): tmp = datetime.datetime.strptime(o, '%Y-%m-%dT%H:%M:%SZ') return tmp.replace(tzinfo=datetime.timezone.utc) else: raise ValueError(f'{o} is not a valid date') @property def start_date(self): return self._get_date(self.__start, datetime.date.today() - datetime.timedelta(days=30)) @property def end_date(self): return self._get_date(self.__end, datetime.date.today()) @property def start_time(self): return self._get_datetime(self.__start, _now() - datetime.timedelta(days=1)) @property def end_time(self): return self._get_datetime(self.__end, _now())
if __name__ == '__main__': with DataContext(datetime.date(2019, 1, 1), datetime.datetime(2019, 2, 1, tzinfo=datetime.timezone.utc)) as dc: print(f'{dc.start_date}, {dc.end_date}') print(f'{dc.start_time}, {dc.end_time}') with DataContext(None, '2019-01-01T00:00:00Z') as dc2: print(f'{dc2.start_date}, {dc2.end_date}') print(f'{dc2.start_time}, {dc2.end_time}')

================================================ FILE: docs/_build/html/_modules/gs_quant/data/dataset.html ================================================ gs_quant.data.dataset — gs_quant 0.1 documentation

Source code for gs_quant.data.dataset

"""
Copyright 2019 Goldman Sachs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
"""
import datetime as dt
from enum import Enum
from typing import Iterable, Optional, Union, List

import pandas as pd

from gs_quant.api.data import DataApi
from gs_quant.data.fields import Fields
from gs_quant.errors import MqValueError


[docs]class Dataset: """A collection of related data""" class Vendor(Enum): pass class GS(Vendor): HOLIDAY = 'HOLIDAY' EDRVOL_PERCENT_INTRADAY = 'EDRVOL_PERCENT_INTRADAY' EDRVOL_PERCENT_STANDARD = 'EDRVOL_PERCENT_STANDARD' MA_RANK = 'MA_RANK' EDRVS_INDEX_SHORT = 'EDRVS_INDEX_SHORT' EDRVS_INDEX_LONG = 'EDRVS_INDEX_LONG' # Baskets CBGSSI = 'CBGSSI' CB = 'CB' # STS STSLEVELS = 'STSLEVELS' # Test Datasets WEATHER = 'WEATHER' class TR(Vendor): TREOD = 'TREOD' TR = 'TR' TR_FXSPOT = 'TR_FXSPOT' class FRED(Vendor): GDP = 'GDP'
[docs] def __init__(self, dataset_id: Union[str, Vendor], provider: DataApi = None): """ :param dataset_id: The dataset's identifier :param provider: The data provider """ self.__id = self._get_dataset_id_str(dataset_id) self.__provider = provider
def _get_dataset_id_str(self, dataset_id): return dataset_id.value if isinstance(dataset_id, Dataset.Vendor) else dataset_id @property def id(self) -> str: """ The dataset's identifier """ return self.__id @property def name(self): pass @property def provider(self): from gs_quant.api.gs.data import GsDataApi return self.__provider or GsDataApi def get_data( self, start: Optional[Union[dt.date, dt.datetime]] = None, end: Optional[Union[dt.date, dt.datetime]] = None, as_of: Optional[dt.datetime] = None, since: Optional[dt.datetime] = None, fields: Optional[Iterable[Union[str, Fields]]] = None, asset_id_type: str = None, **kwargs ) -> pd.DataFrame: """ Get data for the given range and parameters :param start: Requested start date/datetime for data :param end: Requested end date/datetime for data :param as_of: Request data as_of :param since: Request data since :param fields: DataSet fields to include :param kwargs: Extra query arguments, e.g. ticker='EDZ19' :return: A Dataframe of the requested data **Examples** >>> from gs_quant.data import Dataset >>> import datetime as dt >>> >>> weather = Dataset('WEATHER') >>> weather_data = weather.get_data(dt.date(2016, 1, 15), dt.date(2016, 1, 16), city=('Boston', 'Austin')) """ field_names = None if fields is None else list(map(lambda f: f if isinstance(f, str) else f.value, fields)) query = self.provider.build_query( start=start, end=end, as_of=as_of, since=since, fields=field_names, **kwargs ) data = self.provider.query_data(query, self.id, asset_id_type=asset_id_type) return self.provider.construct_dataframe_with_types(self.id, data) def get_data_series( self, field: Union[str, Fields], start: Optional[Union[dt.date, dt.datetime]] = None, end: Optional[Union[dt.date, dt.datetime]] = None, as_of: Optional[dt.datetime] = None, since: Optional[dt.datetime] = None, **kwargs ) -> pd.Series: """ Get a time series of data for a field of a dataset :param field: The DataSet field to use :param start: Requested start date/datetime for data :param end: Requested end date/datetime for data :param as_of: Request data as_of :param since: Request data since :param kwargs: Extra query arguments, e.g. ticker='EDZ19' :return: A Series of the requested data, indexed by date or time, depending on the DataSet **Examples** >>> from gs_quant.data import Dataset >>> import datetime as dt >>> >>> weather = Dataset('WEATHER') >>> dew_point = weather >>>> .get_data_series('dewPoint', dt.date(2016, 1, 15), dt.date(2016, 1, 16), city=('Boston', 'Austin')) """ field_value = field if isinstance(field, str) else field.value query = self.provider.build_query( start=start, end=end, as_of=as_of, since=since, fields=(field_value,), **kwargs ) symbol_dimensions = self.provider.symbol_dimensions(self.id) if len(symbol_dimensions) != 1: raise MqValueError('get_data_series only valid for symbol_dimensions of length 1') symbol_dimension = symbol_dimensions[0] data = self.provider.query_data(query, self.id) df = self.provider.construct_dataframe_with_types(self.id, data) from gs_quant.api.gs.data import GsDataApi if isinstance(self.provider, GsDataApi): gb = df.groupby(symbol_dimension) if len(gb.groups) > 1: raise MqValueError('Not a series for a single {}'.format(symbol_dimension)) if df.empty: return pd.Series() return pd.Series(index=df.index, data=df.loc[:, field_value].values) def get_data_last( self, as_of: Optional[Union[dt.date, dt.datetime]], start: Optional[Union[dt.date, dt.datetime]] = None, fields: Optional[Iterable[str]] = None, **kwargs ) -> pd.DataFrame: """ Get the last point for this DataSet, at or before as_of :param as_of: The date or time as of which to query :param start: The start of the range to query :param fields: The fields for which to query :param kwargs: Additional query parameters, e.g., city='Boston' :return: A Dataframe of values **Examples** >>> from gs_quant.data import Dataset >>> import datetime as dt >>> >>> weather = Dataset('WEATHER') >>> last = weather.get_data_last(dt.datetime.now()) """ query = self.provider.build_query( start=start, end=as_of, fields=fields, format='JSON', **kwargs ) query.format = None # "last" endpoint does not support MessagePack data = self.provider.last_data(query, self.id) return self.provider.construct_dataframe_with_types(self.id, data) def get_coverage( self, limit: int = None, offset: int = None, fields: List[str] = None, include_history: bool = False, **kwargs ) -> pd.DataFrame: """ Get the assets covered by this DataSet :param limit: The maximum number of assets to return :param offset: The offset :param fields: The fields to return, e.g. assetId :param include_history: Return column for historyStartDate :return: A Dataframe of the assets covered **Examples** >>> from gs_quant.data import Dataset >>> >>> weather = Dataset('WEATHER') >>> cities = weather.get_coverage() """ coverage = self.provider.get_coverage( self.id, limit=limit, offset=offset, fields=fields, include_history=include_history, **kwargs ) return pd.DataFrame(coverage)

================================================ FILE: docs/_build/html/_modules/gs_quant/data/fields.html ================================================ gs_quant.data.fields — gs_quant 0.1 documentation

Source code for gs_quant.data.fields

"""
Copyright 2019 Goldman Sachs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
"""

from aenum import Enum, extend_enum
from typing import Optional


class DataMeasure(Enum):
    """Data measure definitions

    Enumeration of measures available through data APIs. Measures are facts that are usually quantities and that can
    be aggregated over a defined period of time. For example: tradePrice and volume are measures

    """
    ASK_PRICE = 'askPrice'
    BID_PRICE = 'bidPrice'
    HIGH_PRICE = 'highPrice'
    MID_PRICE = 'midPrice'
    LOW_PRICE = 'lowPrice'
    OPEN_PRICE = 'openPrice'
    CLOSE_PRICE = 'closePrice'
    TRADE_PRICE = 'tradePrice'
    SPOT_PRICE = 'spot'
    VOLUME = 'volume'
    ADJUSTED_ASK_PRICE = 'adjustedAskPrice'
    ADJUSTED_BID_PRICE = 'adjustedBidPrice'
    ADJUSTED_HIGH_PRICE = 'adjustedHighPrice'
    ADJUSTED_LOW_PRICE = 'adjustedLowPrice'
    ADJUSTED_OPEN_PRICE = 'adjustedBidPrice'
    ADJUSTED_TRADE_PRICE = 'adjustedTradePrice'
    ADJUSTED_VOLUME = 'adjustedVolume'
    IMPLIED_VOLATILITY = 'impliedVolatility'
    VAR_SWAP = 'varSwap'


class DataDimension(Enum):
    """Data dimension definitions

    Enumeration of dimensions available through data APIs. Dimensions describe or provide context to measures, and can
    be used to select or group data. For example: ticker and exchange are dimensions

    """
    ASSET_ID = 'assetId'
    NAME = 'name'
    RIC = 'ric'
    TENOR = 'tenor'
    STRIKE_REFERENCE = 'strikeReference'
    RELATIVE_STRIKE = 'relativeStrike'
    EXPIRATION_DATE = 'expirationDate'
    UPDATE_TIME = 'updateTime'


[docs]class Fields(Enum): """Data field enumeration Enumeration of fields available through data APIs """ @property def unit(self) -> Optional[str]: # TODO: Define units and look up appropriate unit for self return None
for enum in DataMeasure: extend_enum(Fields, enum.name, enum.value) for enum in DataDimension: extend_enum(Fields, enum.name, enum.value)

================================================ FILE: docs/_build/html/_modules/gs_quant/datetime/date.html ================================================ gs_quant.datetime.date — gs_quant 0.1 documentation

Source code for gs_quant.datetime.date

"""
Copyright 2019 Goldman Sachs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
"""

import datetime as dt
import numpy as np
import calendar as cal
from enum import Enum, IntEnum
from typing import Iterable, Optional, Tuple, Union
from gs_quant.datetime.gscalendar import GsCalendar

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


class PaymentFrequency(IntEnum):
    """Payment frequency enumeration

    Provides an enumeration of different payment frequencies used to to discount cashflows and accrue interest

    """
    DAILY = 252
    WEEKLY = 52
    SEMI_MONTHLY = 26
    MONTHLY = 12
    SEMI_QUARTERLY = 6
    QUARTERLY = 4
    TRI_ANNUALLY = 3
    SEMI_ANNUALLY = 2
    ANNUALLY = 1


class DayCountConvention(Enum):
    """Day Count Convention enumeration

    Provides an enumeration of different day count conventions for determining how interest accrues over payment periods
    for financial securities

    """

    # Actual/360: Number of days between dates divided by 360
    ACTUAL_360 = "ACTUAL_360"

    # Actual/364: Number of days between dates divided by 364
    ACTUAL_364 = "ACTUAL_364"

    # Actual/365_25: Number of days between dates divided by 365.25
    ACTUAL_365_25 = "ACTUAL_365_25"

    # Actual/365 FIXED: Number of days between dates divided by 365
    ACTUAL_365F = "ACTUAL_365F"

    # Actual/365 LEAP: Number of days between dates divided by 365 or 366 in leap years
    ACTUAL_365L = "ACTUAL_365L"

    # ONE_ONE: Always returns a day count fraction of 1
    ONE_ONE = "ONE_ONE"


[docs]def is_business_day(dates: DateOrDates, calendars: Union[str, Tuple[str, ...]] = (), week_mask: Optional[str] = None)\ -> Union[bool, Tuple[bool]]: """ Determine whether each date in dates is a business day :param dates: The input date or dates :param calendars: Calendars to use for holidays :param week_mask: Which days are considered weekends (defaults to Saturday and Sunday) :return: True/False if dates is a single date. A tuple indicating True/False for each date if dates is an iterable **Examples** >>> import datetime as dt >>> is_business_day(dt.date.today()) >>> is_business_day(dt.date(2019, 7, 4), calendars=('NYSE',)) """ calendar = GsCalendar.get(calendars) res = np.is_busday(dates, busdaycal=calendar.business_day_calendar(week_mask)) return tuple(res) if isinstance(res, np.ndarray) else res
[docs]def business_day_offset( dates: DateOrDates, offsets: Union[int, Iterable[int]], roll: str = 'raise', calendars: Union[str, Tuple[str, ...]] = (), week_mask: Optional[str] = None) -> DateOrDates: """ Apply offsets to the dates and move to the nearest business date :param dates: The input date or dates :param offsets: The number of days by which to adjust the dates :param roll: Which direction to roll, in order to get to the nearest business date :param calendars: Calendars to use for holidays :param week_mask: Which days are considered weekends (defaults to Saturday and Sunday) :return: A date (if dates is a single date) or tuple of dates, adjusted by the offsets **Examples** >>> import datetime as dt >>> prev_bus_date = business_day_offset(dt.date.today(), -1, roll='forward') """ calendar = GsCalendar.get(calendars) res = np.busday_offset(dates, offsets, roll, busdaycal=calendar.business_day_calendar(week_mask)).astype(dt.date) return tuple(res) if isinstance(res, np.ndarray) else res
[docs]def prev_business_date( dates: DateOrDates = dt.date.today(), calendars: Union[str, Tuple[str, ...]] = (), week_mask: Optional[str] = None) -> DateOrDates: """ Returns the previous business date for a given date or date series, defaulting to today. :param dates: The input date or dates, defaults to today :param calendars: Calendars to use for holidays :param week_mask: Which days are considered weekends (defaults to Saturday and Sunday) :return: A date (if dates is a single date) or tuple of dates, adjusted by the offset of one day. **Example** >>> import datetime as dt >>> prev_bus_date = prev_business_date() """ return business_day_offset(dates, -1, roll='forward', calendars=calendars, week_mask=week_mask)
[docs]def business_day_count(begin_dates: DateOrDates, end_dates: DateOrDates, calendars: Union[str, Tuple[str, ...]] = ( ), week_mask: Optional[str] = None) -> Union[int, Tuple[int]]: """ Determine the number of business days between begin_dates and end_dates :param begin_dates: A date or collection of beginning dates :param end_dates: A date or collection of end dates :param calendars: Calendars to use for holidays :param week_mask: Which days are considered weekends (defaults to Saturday and Sunday) :return: An int or tuple of ints, representing the number of business days between begin_dates and end_dates **Examples** >>> import datetime as dt >>> today = dt.date.today() >>> bus_days = business_day_count(today, today + dt.timedelta(days=7)) """ calendar = GsCalendar.get(calendars) res = np.busday_count(begin_dates, end_dates, busdaycal=calendar.business_day_calendar(week_mask)) return tuple(res) if isinstance(res, np.ndarray) else res
[docs]def date_range(begin: Union[int, dt.date], end: Union[int, dt.date], calendars: Union[str, Tuple[str, ...]] = (), week_mask: Optional[str] = None) -> Iterable[dt.date]: """ Construct a range of dates :param begin: Beginning date or int. An int will be interpreted as the number of business days before end (which must be a date) :param end: End date or int. An int will be interpreted as the number of business days after begin (which must be a date) :param calendars: Calendars to use for holidays :param week_mask: Which days are considered weekends (defaults to Saturday and Sunday) :return: A generator of dates >>> import datetime as dt >>> today = dt.date.today() >>> dates = tuple(date_range(5, today)) >>> >>> for date in date_range(dt.date(2019, 1, 1), dt.date(2019, 2, 1)): >>> print(date) """ if isinstance(begin, dt.date): if isinstance(end, dt.date): def f(): prev = begin if prev > end: raise ValueError('begin must be <= end') while prev <= end: yield prev prev = business_day_offset(prev, 1, calendars=calendars, week_mask=week_mask) return (d for d in f()) elif isinstance(end, int): return (business_day_offset(begin, i, calendars=calendars, week_mask=week_mask) for i in range(end)) else: raise ValueError('end must be a date or int') elif isinstance(begin, int): if isinstance(end, dt.date): return (business_day_offset(end, -i, roll='preceding', calendars=calendars, week_mask=week_mask) for i in range(begin)) else: raise ValueError('end must be a date if begin is an int') else: raise ValueError('begin must be a date or int')
def has_feb_29(start: dt.date, end: dt.date): """ Determine if date range has a leap day (29Feb) :param start: first date :param end: second date **Usage** Determine if a given date range contains a leap day (Feb 29). Used for various day count convention calculations which alter behaviour for leap years. Start date is exclusive and end date is inclusive **Examples** Determine if a given date range contains 29Feb >>> start = date(2020, 1, 1) >>> end = date(2020, 3, 15) >>> has_feb_29(start, end) """ feb_29 = False for x in range(1, (end - start).days + 1): date = start + dt.timedelta(days=x) feb_29 = feb_29 | (date.month == 2 and date.day == 29) return feb_29 def day_count_fraction( start: dt.date, # First payment date end: dt.date, # Second payment date convention: DayCountConvention = DayCountConvention.ACTUAL_360, frequency: PaymentFrequency = PaymentFrequency.MONTHLY ): """ Compute day count fraction between dates :param start: first date :param end: second date :param convention: day count convention :param frequency: payment frequency of instrument :return: day count fraction between dates per convention **Usage** Compute day count fraction between dates, based on the value of *convention*. For more information on the available day count conventions, see the `Day Count Conventions <https://developer.gs.com/docs/gsquant/guides/Dates/1-day-count-conventions>`_ guide. **Examples** Compute day count fraction between two dates using Actual/360 convention: >>> start = date(2015, 11, 12) >>> end = date(2017, 12, 15) >>> day_count_fraction(start, end, DayCountConvention.ACTUAL_360) """ if convention == DayCountConvention.ACTUAL_360: return (end - start).days / 360 elif convention == DayCountConvention.ACTUAL_364: return (end - start).days / 364 elif convention == DayCountConvention.ACTUAL_365F: return (end - start).days / 365 elif convention == DayCountConvention.ACTUAL_365L: if frequency == PaymentFrequency.ANNUALLY: days_in_year = 366 if has_feb_29(start, end) else 365 else: days_in_year = 366 if cal.isleap(end.year) else 365 return (end - start).days / days_in_year elif convention == DayCountConvention.ACTUAL_365_25: return (end - start).days / 365.25 elif convention == DayCountConvention.ONE_ONE: return 1 else: raise ValueError('Unknown day count convention: ' + convention.value)

================================================ FILE: docs/_build/html/_modules/gs_quant/datetime/point.html ================================================ gs_quant.datetime.point — gs_quant 0.1 documentation

Source code for gs_quant.datetime.point

"""
Copyright 2019 Goldman Sachs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
"""
import datetime as dt
import re
import string

ConstPoints = {
    "O/N": 0,
    "T/N": 0.1,
    "OIS FIX": 1,
    "CASH STUB": 1.1,
    "CASHSTUB": 1.1,
    "DEFAULT": 0,
    "IN": 0.1,
    "OUT": 0.2
}

# Regular expression for different types of market data coordinate points
EuroOrFraReg = \
    r"^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)+([0-9][0-9])$"
NumberReg = r"^([0-9]*)$"
MMMYYYYReg = r"^([a-zA-Z]{3}[0-9]{4})$"
DDMMMYYYYReg = r"^([1-3]*[0-9]{1}[a-zA-Z]{3}[0-9]{4})$"
SpikeQEReg = r"^(QE[0-9])-([0-9]{4})$"
FRAxReg = r"^([0-9]+)x([0-9]+)$"
RDatePartReg = r"^([-]*[0-9]+[mydwbfMYDWBF])([-]*[0-9]+[mydwbfMYDWBF])?$"
CashFXReg = r"^([-]*[0-9]+[mydwbfMYDWBF])([-]*[0-9]+[mydwbfMYDWBF])? XC$"
PricerCoordRegI = r"^(No )([0-9]*)$"
PricerCoordRegII = r"^(Pricer )([0-9]*)$"
PricerBFReg = r"^([-]*[0-9]+[mydwbfMYDWBFM])([-]*[0-9]+[mydwbfMYDWBF])([-]*[0-9]+[mydwbfMYDWBF])?$"
PricerBondSpreadReg = r"^[0-9][SQHT]([0-9]{2})[/][0-9][SQHT]([0-9]{2})"
SeasonalFrontReg = r"(Front|Back)"
infl_volReg = r"(Caplet|ZCCap|Swaption|ZCSwo)"
MMMReg = r"^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)$"
MMMYYReg = r"^(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC) ([0-9]{2})"
DatePairReg = r"^([0-9]{8})/([0-9]{8})$"
DatePairReg2 = r"^([0-9]{8}) ([0-9]{8})$"
FXVolAddonParmsReg = \
    r"(Spread Addon|Spread Init|Spread Final|Rho Addon|Rho Init|Rho Final|Vol Addon|Vol Init|Vol Final|HL|Addon HL)"
CopulaReg = r"(Rho$|Rho Rate|Rho Vol|RR|BF|Alpha|Beta|KO$|K0=L|K0=S)"
BondCoordReg = r"^[0-9]* ([0-9.]*) ([0-9]{2}/[0-9]{2}/[0-9]{4})$"
BondFutReg = r"^[A-Z]{3}([FGHJKMNQUVXZ])([0-9])$"
FFFutReg = r"^FF([FGHJKMNQUVXZ])([0-9])$"
RepoGCReg = r"^(ON|SN|TN|[0-9]+) (|Month |Week |Year |Day )GC$"
FloatingYear = r"^([0-9]*\.[0-9])[yY]$"
RelativeReg = r"^([0-9]+) (day|week|month|year|DAY|WEEK|MONTH|YEAR)$"

LYYReg = r"([FGHJKMNQUVXZ])([0-9]{2})"
DateRuleReg = r"^([-]*[0-9]+[mydwbfMYDWBFM])+$"
DDMMMYYReg = r"^([0-3]*[0-9]{1}[a-zA-Z]{3}[0-9]{2})$"

FutMonth = r"FGHJKMNQUVXZ"

DictDayRule = {
    'Month': 30,
    'MONTH': 30,
    'Week': 7,
    'WEEK': 7,
    'Year': 365,
    'YEAR': 365,
    'Day': 1,
    'DAY': 1,
    'd': 1,
    'D': 1,
    'w': 7,
    'W': 7,
    'b': 1,
    'B': 1,
    'f': 30,
    'F': 30,
    'm': 30,
    'M': 30,
    'y': 365,
    'Y': 365
}


def relative_date_add(date_rule: str, strict: bool = False) -> float:
    """Change the string in date rule format to the number of days. E.g 1d to 1, 1y to 365, 1m to 30, -1w to -7"""
    days = ''

    if re.search(DateRuleReg, date_rule) is not None:
        res = re.search(DateRuleReg, date_rule)
        date_str = res.group(1)
        if date_str[0] == '-':
            num = float(date_str[1:-1])
            days = '-'
        else:
            num = float(date_str[:-1])
        rule = date_str[-1:]
        if rule in DictDayRule:
            scale = DictDayRule[rule]
            days = days + str(num * scale)
            d = float(days)
            return d
        else:
            raise ValueError('There are no valid day rule for the point provided.')

    if strict:
        raise ValueError(f'invalid date rule {date_rule}')
    return 0


[docs]def point_sort_order(point: str, ref_date: dt.date = dt.date.today()) -> float: """ Calculates a number that can be used to sort Mkt Points by it. :param point: The point string from MarketDataCoordinate. :param ref_date: Reference date, normally the pricing date. :return: The number of days from the reference date to the date specified by the point string **Examples** >>> import datetime as dt >>> days = point_sort_order(point = 'Dec20', ref_date=dt.date.today()) """ if not point or not isinstance(point, str): return 0 const_value = ConstPoints.get(point.upper()) if const_value is not None: return const_value parts = point.split(';') if len(parts) > 1: first = point_sort_order(parts[0]) if not first: return 0 return first + (0.1 * sum(point_sort_order(p, ref_date) for p in parts[1:]) / first) days = None if point == 'o/n': days = 0 elif point == 't/n': days = 0.1 elif point == 'Cash Stub': days = 1.1 elif point == 'CashStub': days = 1.1 elif point == 'Default': days = 0 elif point == 'In': days = 0.1 elif point == 'Out': days = 0.2 elif re.search(infl_volReg, point) is not None: res = re.search(infl_volReg, point) infl_vol = res.group(1) if infl_vol == 'Caplet': days = 0 elif infl_vol == 'ZCCap': days = 1 elif infl_vol == 'Swaption': days = 2 elif infl_vol == 'ZCSwo': days = 3 elif re.search(CopulaReg, point) is not None: pass elif re.search(SeasonalFrontReg, point) is not None: res = re.search(SeasonalFrontReg, point) if res.group(1) == 'Front': days = 0 else: days = 1 elif re.search(MMMReg, point) is not None: res = re.search(MMMReg, point) date_str = '1' + res.group(1) + '2000' format_str = '%d%b%Y' days = (dt.datetime.strptime(date_str, format_str).date() - ref_date).days elif re.search(EuroOrFraReg, point) is not None: res = re.search(EuroOrFraReg, point) date_str = '15' + res.group(1) + res.group(2) format_str = '%d%b%y' days = (dt.datetime.strptime(date_str, format_str).date() - ref_date).days elif re.search(RDatePartReg, point) is not None: res = re.search(RDatePartReg, point) date_str = res.group(1) days = relative_date_add(date_str) elif re.search(CashFXReg, point) is not None: res = re.search(CashFXReg, point) date_str = res.group(1) days = relative_date_add(date_str) elif re.search(PricerBFReg, point) is not None: res = re.search(PricerBFReg, point) date_str = res.group(1) days = relative_date_add(date_str) elif re.search(FRAxReg, point) is not None: res = re.search(FRAxReg, point) date_str = res.group(1) + 'm' days = relative_date_add(date_str) elif re.search(SpikeQEReg, point) is not None: res = re.search(SpikeQEReg, point) qe = res.group(1) if qe == 'QE1': month = "Mar" elif qe == 'QE2': month = "Jun" elif qe == 'QE3': month = "Sep" elif qe == 'QE4': month = 'Dec' else: month = 'Dec' date_str = "1" + month + res.group(2) format_str = '%d%b%Y' days = (dt.datetime.strptime(date_str, format_str).date() - ref_date).days elif re.search(MMMYYYYReg, point) is not None: res = re.search(MMMYYYYReg, point) date_str = '1' + res.group(1) format_str = '%d%b%Y' days = (dt.datetime.strptime(date_str, format_str).date() - ref_date).days elif re.search(DDMMMYYYYReg, point) is not None: res = re.search(DDMMMYYYYReg, point) date_str = res.group(1) format_str = '%d%b%Y' days = (dt.datetime.strptime(date_str, format_str).date() - ref_date).days elif re.search(NumberReg, point) is not None: res = re.search(NumberReg, point) days = float(res.group(1)) elif re.search(FloatingYear, point) is not None: res = re.search(FloatingYear, point) year = float(res.group(1)) days = 365 * year elif re.search(PricerCoordRegI, point) is not None: res = re.search(PricerCoordRegI, point) days = float(res.group(2)) elif re.search(PricerCoordRegII, point) is not None: res = re.search(PricerCoordRegII, point) days = float(res.group(2)) elif re.search(PricerBondSpreadReg, point) is not None: pass elif re.search(LYYReg, point) is not None: res = re.search(LYYReg, point) month = FutMonth.find(res.group(1)) + 1 year = res.group(2) date_str = year + '-' + str(month) + '-1' format_str = '%y-%m-%d' days = (dt.datetime.strptime(date_str, format_str).date() - ref_date).days elif re.search(DatePairReg, point) is not None: res = re.search(DatePairReg, point) date_str = res.group(2) format_str = '%Y%m%d' days = (dt.datetime.strptime(date_str, format_str).date() - ref_date).days elif re.search(DatePairReg2, point) is not None: res = re.search(DatePairReg2, point) date_str = res.group(2) format_str = '%Y%m%d' days = (dt.datetime.strptime(date_str, format_str).date() - ref_date).days elif re.search(MMMYYReg, point) is not None: res = re.search(MMMYYReg, point) date_str = '1' + res.group(1) + res.group(2) format_str = '%d%b%y' days = (dt.datetime.strptime(date_str, format_str).date() - ref_date).days elif re.search(FXVolAddonParmsReg, point) is not None: pass elif re.search(BondCoordReg, point) is not None: res = re.search(BondCoordReg, point) date_str = res.group(2) format_str = '%d/%m/%Y' days = (dt.datetime.strptime(date_str, format_str).date() - ref_date).days elif re.search(BondFutReg, point) is not None: res = re.search(BondFutReg, point) month = FutMonth.find(res.group(1)) + 1 date_str = str(ref_date.year) + '-' + str(month) + '-1' format_str = '%Y-%m-%d' days = (dt.datetime.strptime(date_str, format_str).date() - ref_date).days elif re.search(FFFutReg, point) is not None: res = re.search(FFFutReg, point) month = FutMonth.find(res.group(1)) + 1 date_str = str(ref_date.year) + '-' + str(month) + '-1' format_str = '%Y-%m-%d' days = (dt.datetime.strptime(date_str, format_str).date() - ref_date).days elif re.search(RepoGCReg, point) is not None: res = re.search(RepoGCReg, point) if point == 'ON GC': days = 0 elif point == 'TN GC': days = 1 elif point == 'SN GC': days = 2 elif res.group(2).strip() in DictDayRule: scale = DictDayRule[res.group(2).strip()] num = float(res.group(1)) days = num * scale elif re.search(RelativeReg, point) is not None: res = re.search(RelativeReg, point) rule = string.capwords(res.group(2)) if rule in DictDayRule: scale = DictDayRule[rule] num = float(res.group(1)) days = num * scale elif re.search(DDMMMYYReg, point) is not None: res = re.search(DDMMMYYReg, point) date_str = res.group(1) format_str = '%d%b%y' days = (dt.datetime.strptime(date_str, format_str).date() - ref_date).days return days

================================================ FILE: docs/_build/html/_modules/gs_quant/instrument/core.html ================================================ gs_quant.instrument.core — gs_quant 0.1 documentation

Source code for gs_quant.instrument.core

"""
Copyright 2019 Goldman Sachs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
"""
from gs_quant.api.gs.parser import GsParserApi
from gs_quant.api.gs.risk import GsRiskApi
from gs_quant.base import get_enum_value, InstrumentBase, QuotableBuilder
from gs_quant.common import AssetClass, AssetType, XRef
from gs_quant.context_base import do_not_serialise
from gs_quant.markets import HistoricalPricingContext, MarketDataCoordinate, PricingCache, PricingContext
from gs_quant.priceable import PriceableImpl
from gs_quant.risk import FloatWithInfo, DataFrameWithInfo, SeriesWithInfo, ResolvedInstrumentValues, RiskMeasure
from gs_quant.risk.results import ErrorValue, MultipleRiskMeasureFuture, PricingFuture
from gs_quant.session import GsSession

from abc import ABCMeta
import datetime as dt
import logging
from typing import Iterable, Optional, Tuple, Union
import inspect

_logger = logging.getLogger(__name__)


class Instrument(PriceableImpl, InstrumentBase, metaclass=ABCMeta):
    PROVIDER = GsRiskApi
    __instrument_mappings = {}

    def __getattribute__(self, name):
        ret = super().__getattribute__(name)
        if ret is not None or name not in self.properties():
            return ret

        attr = getattr(super().__getattribute__('__class__'), name, None)
        if getattr(attr.fget, 'do_not_resolve', False):
            return ret

        resolved = False

        try:
            resolved = super().__getattribute__('resolution_key') is not None
        except AttributeError:
            pass

        if GsSession.current_is_set and not resolved:
            if attr and isinstance(attr, property):
                resolved_inst = self.resolve(in_place=False)
                if isinstance(resolved_inst, PricingFuture):
                    ret = PricingFuture()
                    resolved_inst.add_done_callback(lambda inst_f: ret.set_result(
                        object.__getattribute__(inst_f.result(), name)))
                else:
                    ret = object.__getattribute__(resolved_inst, name)

        return ret

    def _property_changed(self, prop: str):
        if self._hash_is_calced:
            PricingCache.drop(self)

        super()._property_changed(prop)

    @classmethod
    def __asset_class_and_type_to_instrument(cls):
        if not cls.__instrument_mappings:
            import gs_quant.target.instrument as instrument_
            instrument_classes = [c for _, c in inspect.getmembers(instrument_, inspect.isclass) if
                                  issubclass(c, Instrument) and c is not Instrument]

            cls.__instrument_mappings[(AssetClass.Cash, AssetType.Currency)] = instrument_.Forward

            for clazz in instrument_classes:
                instrument = clazz.default_instance()
                cls.__instrument_mappings[(instrument.asset_class, instrument.type)] = clazz

        return cls.__instrument_mappings

    @property
    @do_not_serialise
    def provider(self):
        return self.PROVIDER

    def resolve(self, in_place: bool = True) -> Optional[Union[PriceableImpl, PricingFuture, dict]]:
        """
        Resolve non-supplied properties of an instrument

        **Examples**

        >>> from gs_quant.instrument import IRSwap
        >>>
        >>> swap = IRSwap('Pay', '10y', 'USD')
        >>> rate = swap.fixedRate

        rate is None

        >>> swap.resolve()
        >>> rate = swap.fixedRate

        rates is now the solved fixed rate
        """

        is_historical = isinstance(PricingContext.current, HistoricalPricingContext)

        def handle_result(result: Optional[Union[ErrorValue, InstrumentBase]]) -> Optional[PriceableImpl]:
            ret = None if in_place else result
            if isinstance(result, ErrorValue):
                _logger.error('Failed to resolve instrument fields: ' + result.error)
                ret = {result.risk_key.date: self} if is_historical else self
            elif result is None:
                _logger.error('Unknown error resolving instrument fields')
                ret = {dt.date.today(): self} if is_historical else self
            elif in_place:
                self.from_instance(result)

            return ret

        if in_place and is_historical:
            raise RuntimeError('Cannot resolve in place under a HistoricalPricingContext')

        return self.calc(ResolvedInstrumentValues, fn=handle_result)

    def calc(self, risk_measure: Union[RiskMeasure, Iterable[RiskMeasure]], fn=None) \
            -> Union[DataFrameWithInfo, ErrorValue, FloatWithInfo, PriceableImpl, PricingFuture,
                     SeriesWithInfo, Tuple[MarketDataCoordinate, ...]]:
        """
        Calculate the value of the risk_measure

        :param risk_measure: the risk measure to compute, e.g. IRDelta (from gs_quant.risk)
        :param fn: post-processing function (optional)
        :return: a float or dataframe, depending on whether the value is scalar or structured, or a future thereof
        (depending on how PricingContext is being used)

        **Examples**

        >>> from gs_quant.instrument import IRCap
        >>> from gs_quant.risk import IRDelta
        >>>
        >>> cap = IRCap('1y', 'USD')
        >>> delta = cap.calc(IRDelta)

        delta is a dataframe

        >>> from gs_quant.instrument import EqOption
        >>> from gs_quant.risk import EqDelta
        >>>
        >>> option = EqOption('.SPX', '3m', 'ATMF', 'Call', 'European')
        >>> delta = option.calc(EqDelta)

        delta is a float

        >>> from gs_quant.markets import PricingContext
        >>>
        >>> cap_usd = IRCap('1y', 'USD')
        >>> cap_eur = IRCap('1y', 'EUR')

        >>> with PricingContext():
        >>>     usd_delta_f = cap_usd.calc(IRDelta)
        >>>     eur_delta_f = cap_eur.calc(IRDelta)
        >>>
        >>> usd_delta = usd_delta_f.result()
        >>> eur_delta = eur_delta_f.result()

        usd_delta_f and eur_delta_f are futures, usd_delta and eur_delta are dataframes
        """
        futures = {r: r.pricing_context.calc(self, r)
                   for r in ((risk_measure,) if isinstance(risk_measure, RiskMeasure) else risk_measure)}
        future = MultipleRiskMeasureFuture(self, futures) if len(futures) > 1 else futures[
            risk_measure if isinstance(risk_measure, RiskMeasure) else risk_measure[0]]

        if fn is not None:
            ret = PricingFuture()

            def cb(f):
                try:
                    ret.set_result(fn(f.result()))
                except Exception as e:
                    ret.set_exception(e)

            future.add_done_callback(cb)
            future = ret

        return future.result() if not (PricingContext.current.is_entered or PricingContext.current.is_async) else future

    @classmethod
    def from_dict(cls, values: dict):
        if values:
            if issubclass(cls, QuotableBuilder):
                if 'properties' in values:
                    values.update(values.pop('properties'))
                return cls._from_dict(values)
            elif hasattr(cls, 'asset_class'):
                return cls._from_dict(values)
            else:
                if '$type' in values:
                    from gs_quant_internal import tdapi
                    tdapi_cls = getattr(tdapi, values['$type'].replace('Defn', 'Builder'))
                    if not tdapi_cls:
                        raise RuntimeError('Cannot resolve TDAPI type {}'.format(tdapi_cls))

                    return tdapi_cls.from_dict(values)

                asset_class_field = next((f for f in ('asset_class', 'assetClass') if f in values), None)
                if not asset_class_field:
                    raise ValueError('assetClass/asset_class not specified')
                if 'type' not in values:
                    raise ValueError('type not specified')

                asset_type = values.pop('type')
                asset_class = values.pop(asset_class_field)
                default_type = Security if asset_type in [None, "", "Security"] and asset_class in [None, "",
                                                                                                    "Security"] \
                    else None

                instrument = cls.__asset_class_and_type_to_instrument().get((
                    get_enum_value(AssetClass, asset_class),
                    get_enum_value(AssetType, asset_type)), default_type)

                if instrument is None:
                    raise ValueError('unable to build instrument')

                return instrument._from_dict(values)

    @classmethod
    def from_quick_entry(cls, text: str, asset_class: Optional[AssetClass] = None):
        if not asset_class:
            try:
                inst = cls.default_instance()
                asset_class = inst.asset_class
            except AttributeError:
                pass

        if not asset_class:
            res = GsParserApi.get_instrument_from_text(text)
            if len(res):  # multiple instruments returned
                instrument = res.pop(0)
            else:
                raise ValueError('Could not resolve instrument')
        else:
            instrument = GsParserApi.get_instrument_from_text_asset_class(text, asset_class.value)
        try:
            return cls.from_dict(instrument)
        except AttributeError:
            raise ValueError('Invalid instrument specification')

    @classmethod
    def from_asset_ids(cls, asset_ids: Tuple[str, ...]) -> Tuple[InstrumentBase, ...]:
        from gs_quant.api.gs.assets import GsAssetApi
        instruments = GsAssetApi.get_instruments_for_asset_ids(asset_ids)

        try:
            inst = cls.default_instance()
            asset_class = inst.asset_class
            asset_type = inst.type

            if not all(i.asset_class == asset_class and i.type == asset_type for i in instruments):
                raise ValueError(f'Instrument(s) not all of type {cls.__name__}')
        except AttributeError:
            pass

        return instruments

    @classmethod
    def from_asset_id(cls, asset_id: str) -> InstrumentBase:
        return cls.from_asset_ids((asset_id,))[0]

    @staticmethod
    def compose(components: Iterable):
        return {c.risk_key.date if isinstance(c, ErrorValue) else c.resolution_key.date: c for c in components}


[docs]class Security(XRef, Instrument): """A security, specified by a well-known identifier""" def __init__(self, ticker: str = None, bbid: str = None, ric: str = None, isin: str = None, cusip: str = None, prime_id: str = None, quantity: float = 1): """ Create a security by passing one identifier only and, optionally, a quantity :param ticker: Exchange ticker :param bbid: Bloomberg identifier :param isin: International Security Number :param cusip: CUSIP :param prime_id: Prime (GS internal) identifier :param quantity: Quantity (number of contracts for exchange-traded instruments, notional for bonds) """ if len(tuple(filter(None, (f is not None for f in (ticker, bbid, isin, cusip, prime_id))))) > 1: raise ValueError('Only specify one identifier') XRef.__init__(self, ticker=ticker, bbid=bbid, ric=ric, isin=isin, cusip=cusip, prime_id=prime_id) Instrument.__init__(self, quantity)

================================================ FILE: docs/_build/html/_modules/gs_quant/markets/core.html ================================================ gs_quant.markets.core — gs_quant 0.1 documentation

Source code for gs_quant.markets.core

"""
Copyright 2019 Goldman Sachs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
"""
import asyncio
import datetime as dt
import logging
import weakref
from abc import ABCMeta
from concurrent.futures import ThreadPoolExecutor
from inspect import signature
from itertools import zip_longest
from tqdm import tqdm
from typing import Optional, Union

from .markets import CloseMarket, LiveMarket, Market, close_market_date
from gs_quant.base import InstrumentBase, RiskKey, Scenario, get_enum_value
from gs_quant.common import PricingLocation
from gs_quant.context_base import ContextBaseWithDefault
from gs_quant.datetime.date import business_day_offset
from gs_quant.risk import CompositeScenario, DataFrameWithInfo, ErrorValue, FloatWithInfo, MarketDataScenario, \
    RiskMeasure, StringWithInfo
from gs_quant.risk.results import PricingFuture
from gs_quant.session import GsSession
from gs_quant.target.common import PricingDateAndMarketDataAsOf
from gs_quant.target.risk import RiskPosition, RiskRequest, RiskRequestParameters


_logger = logging.getLogger(__name__)

CacheResult = Union[DataFrameWithInfo, FloatWithInfo, StringWithInfo]


class PricingCache(metaclass=ABCMeta):
    """
    Weakref cache for instrument calcs
    """
    __cache = weakref.WeakKeyDictionary()

    @classmethod
    def clear(cls):
        __cache = weakref.WeakKeyDictionary()

    @classmethod
    def get(cls, risk_key: RiskKey, instrument: InstrumentBase) -> Optional[CacheResult]:
        return cls.__cache.get(instrument, {}).get(risk_key)

    @classmethod
    def put(cls, risk_key: RiskKey, instrument: InstrumentBase, result: CacheResult):
        if not isinstance(result, ErrorValue) and not isinstance(risk_key.market, LiveMarket):
            cls.__cache.setdefault(instrument, {})[risk_key] = result

    @classmethod
    def drop(cls, instrument: InstrumentBase):
        if instrument in cls.__cache:
            cls.__cache.pop(instrument)


[docs]class PricingContext(ContextBaseWithDefault): """ A context for controlling pricing and market data behaviour """
[docs] def __init__(self, pricing_date: Optional[dt.date] = None, market_data_location: Optional[Union[PricingLocation, str]] = None, is_async: bool = False, is_batch: bool = False, use_cache: bool = False, visible_to_gs: Optional[bool] = None, csa_term: Optional[str] = None, timeout: Optional[int] = None, market: Optional[Market] = None, show_progress: Optional[bool] = False): """ The methods on this class should not be called directly. Instead, use the methods on the instruments, as per the examples :param pricing_date: the date for pricing calculations. Default is today :param market_data_location: the location for sourcing market data ('NYC', 'LDN' or 'HKG' (defaults to LDN) :param is_async: if True, return (a future) immediately. If False, block (defaults to False) :param is_batch: use for calculations expected to run longer than 3 mins, to avoid timeouts. It can be used with is_async=True|False (defaults to False) :param use_cache: store results in the pricing cache (defaults to False) :param visible_to_gs: are the contents of risk requests visible to GS (defaults to False) :param csa_term: the csa under which the calculations are made. Default is local ccy ois index **Examples** To change the market data location of the default context: >>> from gs_quant.markets import PricingContext >>> import datetime as dt >>> >>> PricingContext.current = PricingContext(market_data_location='LDN') For a blocking, synchronous request: >>> from gs_quant.instrument import IRCap >>> cap = IRCap('5y', 'GBP') >>> >>> with PricingContext(): >>> price_f = cap.dollar_price() >>> >>> price = price_f.result() For an asynchronous request: >>> with PricingContext(is_async=True): >>> price_f = cap.dollar_price() >>> >>> while not price_f.done: >>> ... """ super().__init__() if market and market_data_location and market.location.value is not market_data_location: raise ValueError('market.location and market_data_location cannot be different') if not market_data_location: if not market: # use parent context's market_data_location if self != self.active_context: market_data_location = self.active_context.market_data_location # if no parent but there was a context set previously, use that elif self.prior_context: market_data_location = self.prior_context.market_data_location else: market_data_location = market.location self.__pricing_date = pricing_date or (self.prior_context.pricing_date if self.prior_context else business_day_offset(dt.date.today(), 0, roll='preceding')) self.__csa_term = csa_term self.__is_async = is_async self.__is_batch = is_batch self.__timeout = timeout self.__use_cache = use_cache self.__visible_to_gs = visible_to_gs self.__market_data_location = get_enum_value(PricingLocation, market_data_location) self.__market = market or CloseMarket( date=close_market_date(self.__market_data_location, self.__pricing_date) if pricing_date else None, location=self.__market_data_location if market_data_location else None) self.__pending = {} self.__show_progress = show_progress
def _on_exit(self, exc_type, exc_val, exc_tb): if exc_val: raise exc_val else: self.__calc() def __calc(self): def run_requests(requests_: list, provider_, create_event_loop: bool): if create_event_loop: asyncio.set_event_loop(asyncio.new_event_loop()) results = {} try: with session: results = provider_.run(requests_, 1000, progress_bar, timeout=self.__timeout) except Exception as e: results = {k: e for k in self.__pending.keys()} finally: while self.__pending: (risk_key_, priceable_), future = self.__pending.popitem() result = results.get((risk_key_, priceable_)) if result is not None: if self.__use_cache: PricingCache.put(risk_key_, priceable_, result) else: result = ErrorValue(risk_key_, 'No result returned') future.set_result(result) # Group requests optimally requests_by_provider = {} for (key, instrument) in self.__pending.keys(): requests_by_provider.setdefault(key.provider, {})\ .setdefault((key.params, key.scenario, key.date, key.market), {})\ .setdefault(instrument, set())\ .add(key.risk_measure) requests_for_provider = {} if requests_by_provider: session = GsSession.current request_visible_to_gs = session.is_internal() if self.__visible_to_gs is None else self.__visible_to_gs for provider, by_params_scenario_date_market in requests_by_provider.items(): grouped_requests = {} for (params, scenario, date, market), positions_by_measures in by_params_scenario_date_market.items(): for instrument, risk_measures in positions_by_measures.items(): grouped_requests.setdefault((params, scenario, date, market, tuple(sorted(risk_measures))), []).append(instrument) requests = [] for (params, scenario, date, market, risk_measures), instruments in grouped_requests.items(): for chunk in [tuple(filter(None, i)) for i in zip_longest(*[iter(instruments)] * 1000)]: requests.append(RiskRequest( tuple(RiskPosition(instrument=i, quantity=i.instrument_quantity) for i in chunk), risk_measures, parameters=self._parameters, wait_for_results=not self.__is_batch, scenario=scenario, pricing_and_market_data_as_of=(PricingDateAndMarketDataAsOf(pricing_date=date, market=market),), request_visible_to_gs=request_visible_to_gs, )) requests_for_provider[provider] = requests show_status = self.__show_progress and\ (len(requests_for_provider) > 1 or len(next(iter(requests_for_provider.values()))) > 1) request_pool = ThreadPoolExecutor(len(requests_for_provider))\ if len(requests_for_provider) > 1 or self.__is_async else None progress_bar = tqdm(total=len(self.__pending), position=0, maxinterval=1) if show_status else None completion_futures = [] for provider, requests in requests_for_provider.items(): if request_pool: completion_future = request_pool.submit(run_requests, requests, provider, True) if not self.__is_async: completion_futures.append(completion_future) else: run_requests(requests, provider, False) # Wait on results if not async, so exceptions are surfaced if request_pool: request_pool.shutdown(False) all(f.result() for f in completion_futures) def __risk_key(self, risk_measure: RiskMeasure, provider: type) -> RiskKey: return RiskKey(provider, self.__pricing_date, self.__market, self._parameters, self._scenario, risk_measure) @property def _parameters(self) -> RiskRequestParameters: return RiskRequestParameters(csa_term=self.__csa_term, raw_results=True) @property def _scenario(self) -> Optional[MarketDataScenario]: scenarios = Scenario.path if not scenarios: return None return MarketDataScenario(scenario=scenarios[0] if len(scenarios) == 1 else CompositeScenario(scenarios=tuple(reversed(scenarios)))) @property def active_context(self): return next((c for c in reversed(PricingContext.path) if c.is_entered), self) @property def prior_context(self): return PricingContext.path[-1] if len(PricingContext.path) else None @property def is_current(self) -> bool: return self == PricingContext.current @property def is_async(self) -> bool: return self.__is_async @property def is_batch(self) -> bool: return self.__is_batch @property def market(self) -> Market: return self.__market @property def market_data_location(self) -> PricingLocation: return self.__market_data_location @property def csa_term(self) -> str: return self._parameters.csa_term @property def pricing_date(self) -> dt.date: """Pricing date""" return self.__pricing_date @property def use_cache(self) -> bool: """Cache results""" return self.__use_cache @property def visible_to_gs(self) -> Optional[bool]: """Request contents visible to GS""" return self.__visible_to_gs def clone(self, **kwargs): clone_kwargs = {k: getattr(self, k, None) for k in signature(self.__init__).parameters.keys()} clone_kwargs.update(kwargs) return self.__class__(**clone_kwargs) def _calc(self, instrument: InstrumentBase, risk_key: RiskKey) -> PricingFuture: future = self.active_context.__pending.get((risk_key, instrument)) if future is None: future = PricingFuture() cached_result = PricingCache.get(risk_key, instrument) if self.use_cache else None if cached_result is not None: future.set_result(cached_result) else: self.active_context.__pending[(risk_key, instrument)] = future if not (self.is_entered or self.is_async): self.__calc() return future def calc(self, instrument: InstrumentBase, risk_measure: RiskMeasure) -> PricingFuture: """ Calculate the risk measure for the instrument. Do not use directly, use via instruments :param instrument: The instrument :param risk_measure: The measure we wish to calculate :return: A PricingFuture whose result will be the calculation result **Examples** >>> from gs_quant.instrument import IRSwap >>> from gs_quant.risk import IRDelta >>> >>> swap = IRSwap('Pay', '10y', 'USD', fixed_rate=0.01) >>> delta = swap.calc(IRDelta) """ return self._calc(instrument, self.__risk_key(risk_measure, instrument.provider))

================================================ FILE: docs/_build/html/_modules/gs_quant/markets/historical.html ================================================ gs_quant.markets.historical — gs_quant 0.1 documentation

Source code for gs_quant.markets.historical

"""
Copyright 2019 Goldman Sachs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
"""
import datetime as dt
from typing import Iterable, Optional, Tuple, Union

from gs_quant.base import InstrumentBase, RiskKey
from gs_quant.datetime.date import date_range
from gs_quant.risk import RiskMeasure, RollFwd, MarketDataScenario
from gs_quant.risk.results import HistoricalPricingFuture, PricingFuture

from .core import PricingContext
from .markets import CloseMarket, close_market_date


[docs]class HistoricalPricingContext(PricingContext): """ A context for producing valuations over multiple dates """
[docs] def __init__( self, start: Optional[Union[int, dt.date]] = None, end: Optional[Union[int, dt.date]] = None, calendars: Union[str, Tuple] = (), dates: Optional[Iterable[dt.date]] = None, is_async: bool = False, is_batch: bool = False, use_cache: bool = False, visible_to_gs: bool = False, csa_term: str = None, market_data_location: Optional[str] = None, timeout: Optional[int] = None, show_progress: Optional[bool] = False): """ A context for producing valuations over multiple dates :param start: start date :param end: end date (defaults to today) :param calendars: holiday calendars :param dates: a custom iterable of dates :param is_async: return immediately (True) or wait for results (False) (defaults to False) :param is_batch: use for calculations expected to run longer than 3 mins, to avoid timeouts. It can be used with is_async=True|False (defaults to False) :param use_cache: store results in the pricing cache (defaults to False) :param visible_to_gs: are the contents of risk requests visible to GS (defaults to False) :param csa_term: the csa under which the calculations are made. Default is local ccy ois index :param market_data_location: the location for sourcing market data ('NYC', 'LDN' or 'HKG' (defaults to LDN) **Examples** >>> from gs_quant.instrument import IRSwap >>> >>> ir_swap = IRSwap('Pay', '10y', 'DKK') >>> with HistoricalPricingContext(10): >>> price_f = ir_swap.price() >>> >>> price_series = price_f.result() """ super().__init__(is_async=is_async, is_batch=is_batch, use_cache=use_cache, visible_to_gs=visible_to_gs, csa_term=csa_term, market_data_location=market_data_location, timeout=timeout, show_progress=show_progress) if start is not None: if dates is not None: raise ValueError('Must supply start or dates, not both') if end is None: end = dt.date.today() self.__date_range = tuple(date_range(start, end, calendars=calendars)) elif dates is not None: self.__date_range = tuple(dates) else: raise ValueError('Must supply start or dates')
def calc(self, instrument: InstrumentBase, risk_measure: RiskMeasure) -> PricingFuture: futures = [] provider = instrument.provider scenario = self._scenario parameters = self._parameters location = self.market.location for date in self.__date_range: market = CloseMarket(location=location, date=close_market_date(location, date)) risk_key = RiskKey(provider, date, market, parameters, scenario, risk_measure) futures.append(self._calc(instrument, risk_key)) return HistoricalPricingFuture(futures)
class BackToTheFuturePricingContext(HistoricalPricingContext): """ A context for producing valuations over multiple dates both in the past and into the future """ def __init__( self, start: Optional[Union[int, dt.date]] = None, end: Optional[Union[int, dt.date]] = None, calendars: Union[str, Tuple] = (), dates: Optional[Iterable[dt.date]] = None, roll_to_fwds: bool = True, is_async: bool = False, is_batch: bool = False, use_cache: bool = False, visible_to_gs: bool = False, csa_term: str = None, market_data_location: Optional[str] = None, timeout: Optional[int] = None, show_progress: Optional[bool] = False): """ A context for producing valuations over multiple dates :param start: start date :param end: end date (defaults to today) :param calendars: holiday calendars :param dates: a custom iterable of dates :param roll_to_fwds: if True then for future dates assume fwd curve is realised. If False assume spot rates are realised. (defaults to True) :param is_async: return immediately (True) or wait for results (False) (defaults to False) :param is_batch: use for calculations expected to run longer than 3 mins, to avoid timeouts. It can be used with is_async=True|False (defaults to False) :param use_cache: store results in the pricing cache (defaults to False) :param visible_to_gs: are the contents of risk requests visible to GS (defaults to False) :param csa_term: the csa under which the calculations are made. Default is local ccy ois index :param market_data_location: the location for sourcing market data ('NYC', 'LDN' or 'HKG' (defaults to LDN) **Examples** assuming today is between dt.date(2020, 7, 6) and dt.date(2020, 7, 14) >>> from gs_quant.instrument import IRSwap >>> >>> ir_swap = IRSwap('Pay', '10y', 'DKK') >>> with BackToTheFuturePricingContext(dt.date(2020, 7, 6), dt.date(2020, 7, 14), roll_to_fwds=True): >>> price_f = ir_swap.price() >>> >>> price_series = price_f.result() """ super().__init__(start=start, end=end, calendars=calendars, dates=dates, is_async=is_async, is_batch=is_batch, use_cache=use_cache, visible_to_gs=visible_to_gs, csa_term=csa_term, market_data_location=market_data_location, timeout=timeout, show_progress=show_progress) self._roll_to_fwds = roll_to_fwds if start is not None: if dates is not None: raise ValueError('Must supply start or dates, not both') if end is None: end = dt.date.today() self.__date_range = tuple(date_range(start, end, calendars=calendars)) elif dates is not None: self.__date_range = tuple(dates) else: raise ValueError('Must supply start or dates') def calc(self, instrument: InstrumentBase, risk_measure: RiskMeasure) -> PricingFuture: futures = [] provider = instrument.provider base_scenario = self._scenario parameters = self._parameters location = self.market.location base_market = self.market for date in self.__date_range: if date > self.pricing_date: scenario = MarketDataScenario(RollFwd(date=date, realise_fwd=self._roll_to_fwds)) risk_key = RiskKey(provider, date, base_market, parameters, scenario, risk_measure) futures.append(self._calc(instrument, risk_key)) else: market = CloseMarket(location=location, date=close_market_date(location, date)) risk_key = RiskKey(provider, date, market, parameters, base_scenario, risk_measure) futures.append(self._calc(instrument, risk_key)) return HistoricalPricingFuture(futures)

================================================ FILE: docs/_build/html/_modules/gs_quant/markets/portfolio.html ================================================ gs_quant.markets.portfolio — gs_quant 0.1 documentation

Source code for gs_quant.markets.portfolio

"""
Copyright 2019 Goldman Sachs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicablNe law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
"""
import copy
import datetime as dt
import logging
from typing import Iterable, Optional, Tuple, Union

import numpy as np
import pandas as pd
from gs_quant.api.gs.assets import GsAssetApi
from gs_quant.api.gs.portfolios import GsPortfolioApi
from gs_quant.context_base import nullcontext
from gs_quant.instrument import Instrument
from gs_quant.markets import HistoricalPricingContext, OverlayMarket, PricingContext
from gs_quant.priceable import PriceableImpl
from gs_quant.risk import RiskMeasure, ResolvedInstrumentValues
from gs_quant.risk.results import CompositeResultFuture, PortfolioRiskResult, PortfolioPath, PricingFuture
from gs_quant.target.common import RiskPosition
from gs_quant.target.portfolios import Position, PositionSet, RiskRequest, PricingDateAndMarketDataAsOf
from more_itertools import unique_everseen
from itertools import chain

_logger = logging.getLogger(__name__)


[docs]class Portfolio(PriceableImpl): """A collection of instruments Portfolio holds a collection of instruments in order to run pricing and risk scenarios """
[docs] def __init__(self, priceables: Optional[Union[PriceableImpl, Iterable[PriceableImpl], dict]] = (), name: Optional[str] = None): """ Creates a portfolio object which can be used to hold instruments :param priceables: constructed with an instrument, portfolio, iterable of either, or a dictionary where key is name and value is a priceable """ super().__init__() if isinstance(priceables, dict): priceables_list = [] for name, priceable in priceables.items(): priceable.name = name priceables_list.append(priceable) self.priceables = priceables_list else: self.priceables = priceables self.__name = name self.__id = None
def __getitem__(self, item): if isinstance(item, (int, slice)): return self.__priceables[item] elif isinstance(item, PortfolioPath): return item(self, rename_to_parent=True) else: values = tuple(self[p] for it in item for p in self.paths(it)) if isinstance(item, list) else tuple( self[p] for p in self.paths(item)) return values[0] if len(values) == 1 else values def __contains__(self, item): if isinstance(item, PriceableImpl): return any(item in p.__priceables_lookup for p in self.all_portfolios + (self,)) elif isinstance(item, str): return any(item in p.__priceables_by_name for p in self.all_portfolios + (self,)) else: return False def __len__(self): return len(self.__priceables) def __iter__(self): return iter(self.__priceables) def __hash__(self): hash_code = hash(self.__name) ^ hash(self.__id) for priceable in self.__priceables: hash_code ^= hash(priceable) return hash_code def __eq__(self, other): if not isinstance(other, Portfolio): return False for path in self.all_paths: try: if path(self) != path(other): return False except IndexError: return False return True def __add__(self, other): if not isinstance(other, Portfolio): raise ValueError('Can only add instances of Portfolio') return Portfolio(self.__priceables + other.__priceables) @property def __pricing_context(self) -> PricingContext: return PricingContext.current if not PricingContext.current.is_entered else nullcontext() @property def id(self) -> str: return self.__id @property def name(self) -> str: return self.__name @name.setter def name(self, name): self.__name = name @property def priceables(self) -> Tuple[PriceableImpl, ...]: return self.__priceables @priceables.setter def priceables(self, priceables: Union[PriceableImpl, Iterable[PriceableImpl]]): self.__priceables = (priceables,) if isinstance(priceables, PriceableImpl) else tuple(priceables) self.__priceables_lookup = {} self.__priceables_by_name = {} for idx, i in enumerate(self.__priceables): self.__priceables_lookup.setdefault(copy.copy(i), []).append(idx) if i and i.name: self.__priceables_by_name.setdefault(i.name, []).append(idx) @priceables.deleter def priceables(self): self.__priceables = None self.__priceables_lookup = None self.__priceables_by_name = None @property def instruments(self) -> Tuple[Instrument, ...]: return tuple(unique_everseen(i for i in self.__priceables if isinstance(i, Instrument))) @property def all_instruments(self) -> Tuple[Instrument, ...]: instr = chain(self.instruments, chain.from_iterable(p.all_instruments for p in self.all_portfolios)) return tuple(unique_everseen(instr)) @property def portfolios(self) -> Tuple[PriceableImpl, ...]: return tuple(i for i in self.__priceables if isinstance(i, Portfolio)) @property def all_portfolios(self) -> Tuple[PriceableImpl, ...]: stack = list(self.portfolios) portfolios = list(unique_everseen(stack)) while stack: portfolio = stack.pop() if portfolio in portfolios: continue sub_portfolios = portfolio.portfolios portfolios.extend(sub_portfolios) stack.extend(sub_portfolios) return tuple(unique_everseen(portfolios)) def subset(self, paths: Iterable[PortfolioPath], name=None): return Portfolio(tuple(self[p] for p in paths), name=name) @staticmethod def __from_internal_positions(id_type: str, positions_id): instruments = GsPortfolioApi.get_instruments_by_position_type(id_type, positions_id) return Portfolio(instruments, name=positions_id) @staticmethod def from_eti(eti: str): return Portfolio.__from_internal_positions('ETI', eti.replace(',', '%2C')) @staticmethod def from_book(book: str, book_type: str = 'risk'): return Portfolio.__from_internal_positions(book_type, book) @staticmethod def from_asset_id(asset_id: str, date=None): asset = GsAssetApi.get_asset(asset_id) response = GsAssetApi.get_asset_positions_for_date(asset_id, date) if date else \ GsAssetApi.get_latest_positions(asset_id) response = response[0] if isinstance(response, tuple) else response positions = response.positions if isinstance(response, PositionSet) else response['positions'] instruments = GsAssetApi.get_instruments_for_positions(positions) ret = Portfolio(instruments, name=asset.name) ret.__id = asset_id return ret @staticmethod def from_asset_name(name: str): asset = GsAssetApi.get_asset_by_name(name) return Portfolio.load_from_portfolio_id(asset.id) @staticmethod def from_portfolio_id(portfolio_id: str, date=None): portfolio = GsPortfolioApi.get_portfolio(portfolio_id) response = GsPortfolioApi.get_positions_for_date(portfolio_id, date) if date else \ GsPortfolioApi.get_latest_positions(portfolio_id) response = response[0] if isinstance(response, tuple) else response positions = response.positions if isinstance(response, PositionSet) else response['positions'] instruments = GsAssetApi.get_instruments_for_positions(positions) ret = Portfolio(instruments, name=portfolio.name) ret.__id = portfolio_id return ret @staticmethod def from_portfolio_name(name: str): portfolio = GsPortfolioApi.get_portfolio_by_name(name) return Portfolio.load_from_portfolio_id(portfolio.id) @staticmethod def from_quote(quote_id: str): instruments = GsPortfolioApi.get_instruments_by_workflow_id(quote_id) return Portfolio(instruments, name=quote_id) def save(self, overwrite: Optional[bool] = False): if self.portfolios: raise ValueError('Cannot save portfolios with nested portfolios') if self.__id: if not overwrite: raise ValueError(f'Portfolio with id {id} already exists. Use overwrite=True to overwrite') else: if not self.__name: raise ValueError('name not set') try: self.__id = GsPortfolioApi.get_portfolio_by_name(self.__name).id if not overwrite: raise RuntimeError( f'Portfolio {self.__name} with id {self.__id} already exists. Use overwrite=True to overwrite') except ValueError: from gs_quant.target.portfolios import Portfolio as MarqueePortfolio self.__id = GsPortfolioApi.create_portfolio(MarqueePortfolio('USD', self.__name)).id _logger.info(f'Created Marquee portfolio {self.__name} with id {self.__id}') position_set = PositionSet( position_date=dt.date.today(), positions=tuple(Position(asset_id=GsAssetApi.get_or_create_asset_from_instrument(i)) for i in self.instruments)) GsPortfolioApi.update_positions(self.__id, (position_set,)) def save_as_quote(self, overwrite: Optional[bool] = False): if self.portfolios: raise ValueError('Cannot save portfolios with nested portfolios') pricing_context = self.__pricing_context request = RiskRequest( tuple(RiskPosition(instrument=i, quantity=i.instrument_quantity) for i in self.instruments), (ResolvedInstrumentValues,), pricing_and_market_data_as_of=(PricingDateAndMarketDataAsOf(pricing_date=pricing_context.pricing_date, market=pricing_context.market),) ) if self.__id: if not overwrite: raise ValueError(f'Portfolio with id {id} already exists. Use overwrite=True to overwrite') else: GsPortfolioApi.update_quote(self.__id, request) _logger.info(f'Updated Structuring with id {self.__id}') else: self.__id = GsPortfolioApi.save_quote(request) _logger.info(f'Created Structuring with id {self.__id}') @classmethod def from_frame(cls, data: pd.DataFrame, mappings: dict = None): def get_value(this_row: pd.Series, attribute: str): value = mappings.get(attribute, attribute) return value(this_row) if callable(value) else this_row.get(value) instruments = [] mappings = mappings or {} data = data.replace({np.nan: None}) for row in (r for _, r in data.iterrows() if any(v for v in r.values if v is not None)): instrument = None for init_keys in (('asset_class', 'type'), ('$type',)): init_values = tuple(filter(None, (get_value(row, k) for k in init_keys))) if len(init_keys) == len(init_values): instrument = Instrument.from_dict(dict(zip(init_keys, init_values))) instrument = instrument.from_dict({p: get_value(row, p) for p in instrument.properties()}) break if instrument: instruments.append(instrument) else: raise ValueError('Neither asset_class/type nor $type specified') return cls(instruments) @classmethod def from_csv( cls, csv_file: str, mappings: Optional[dict] = None ): data = pd.read_csv(csv_file, skip_blank_lines=True).replace({np.nan: None}) return cls.from_frame(data, mappings) def append(self, priceables: Union[PriceableImpl, Iterable[PriceableImpl]]): self.priceables += ((priceables,) if isinstance(priceables, PriceableImpl) else tuple(priceables)) def pop(self, item) -> PriceableImpl: priceable = self[item] self.priceables = [inst for inst in self.instruments if inst != priceable] return priceable def to_frame(self, mappings: Optional[dict] = None) -> pd.DataFrame: def to_records(portfolio: Portfolio) -> list: records = [] for priceable in portfolio.priceables: if isinstance(priceable, Portfolio): records.extend(to_records(priceable)) else: as_dict = priceable.as_dict() if hasattr(priceable, '_type'): as_dict['$type'] = priceable._type records.append(dict(chain(as_dict.items(), (('instrument', priceable), ('portfolio', portfolio.name))))) return records df = pd.DataFrame.from_records(to_records(self)).set_index(['portfolio', 'instrument']) all_columns = df.columns.to_list() columns = sorted(c for c in all_columns if c not in ('asset_class', 'type', '$type')) for asset_column in ('$type', 'type', 'asset_class'): if asset_column in all_columns: columns = [asset_column] + columns df = df[columns] mappings = mappings or {} for key, value in mappings.items(): if isinstance(value, str): df[key] = df[value] elif callable(value): df[key] = len(df) * [None] df[key] = df.apply(value, axis=1) return df def to_csv(self, csv_file: str, mappings: Optional[dict] = None, ignored_cols: Optional[list] = None): port_df = self.to_frame(mappings or {}) port_df = port_df[np.setdiff1d(port_df.columns, ignored_cols or [])] port_df.reset_index(drop=True, inplace=True) port_df.to_csv(csv_file) @property def all_paths(self) -> Tuple[PortfolioPath, ...]: paths = () stack = [(None, self)] while stack: parent, portfolio = stack.pop() for idx, priceable in enumerate(portfolio.__priceables): path = parent + PortfolioPath(idx) if parent is not None else PortfolioPath(idx) if isinstance(priceable, Portfolio): stack.insert(0, (path, priceable)) else: paths += (path,) return paths def paths(self, key: Union[str, PriceableImpl]) -> Tuple[PortfolioPath, ...]: if not isinstance(key, (str, Instrument, Portfolio)): raise ValueError('key must be a name or Instrument or Portfolio') idx = self.__priceables_by_name.get(key) if isinstance(key, str) else self.__priceables_lookup.get(key) paths = tuple(PortfolioPath(i) for i in idx) if idx else () for path, porfolio in ((PortfolioPath(i), p) for i, p in enumerate(self.__priceables) if isinstance(p, Portfolio)): paths += tuple(path + sub_path for sub_path in porfolio.paths(key)) return paths def resolve(self, in_place: bool = True) -> Optional[Union[PricingFuture, PriceableImpl, dict]]: pricing_context = self.__pricing_context with pricing_context: futures = [p.resolve(in_place) for p in self.__priceables] if not in_place: ret = {} if isinstance(PricingContext.current, HistoricalPricingContext) else Portfolio(name=self.name) result_future = PricingFuture() if not isinstance( pricing_context, PricingContext) or pricing_context.is_async or pricing_context.is_entered else None def cb(future: CompositeResultFuture): if isinstance(ret, Portfolio): ret.priceables = [f.result() for f in future.futures] else: priceables_by_date = {} for future in futures: for date, priceable in future.result().items(): priceables_by_date.setdefault(date, []).append(priceable) for date, priceables in priceables_by_date.items(): if any(p for p in priceables if not isinstance(p, PriceableImpl)): _logger.error(f'Error resolving on {date}, skipping that date') else: ret[date] = Portfolio(priceables, name=self.name) if result_future: result_future.set_result(ret) CompositeResultFuture(futures).add_done_callback(cb) return result_future or ret def market(self) -> Union[OverlayMarket, PricingFuture, dict]: """ Market Data map of coordinates and values. Note that this is not yet supported on all instruments ***Examples** >>> from gs_quant.markets.portfolio import Portfolio >>> >>> portfolio = Portfolio(...) >>> market = portfolio.market() """ pricing_context = self.__pricing_context with pricing_context: futures = [i.market() for i in self.all_instruments] result_future = PricingFuture() return_future = not isinstance(pricing_context, PricingContext) or pricing_context.is_async or pricing_context.is_entered def cb(future: CompositeResultFuture): def update_market_data(all_market_data, this_market_data): for item in this_market_data: existing_value = all_market_data.setdefault(item.coordinate, item.value) if abs(existing_value - item.value) > 1e-6: raise ValueError( f'Conflicting values for {item.coordinate}: {existing_value} vs {item.value}') results = [f.result() for f in future.futures] is_historical = isinstance(results[0], dict) market_data = None if is_historical else {} overlay_markets = {} if is_historical else None for result in results: if market_data is not None: update_market_data(market_data, result.market_data) else: for market in result.values(): update_market_data(overlay_markets.setdefault(market.base_market, {}), market.market_data) if market_data: ret = OverlayMarket(base_market=results[0].base_market, market_data=market_data) else: ret = {base_market.date: OverlayMarket(base_market=base_market, market_data=market_data) for base_market, market_data in overlay_markets.items()} if result_future: result_future.set_result(ret) CompositeResultFuture(futures).add_done_callback(cb) return result_future if return_future else result_future.result() def calc(self, risk_measure: Union[RiskMeasure, Iterable[RiskMeasure]], fn=None) -> PortfolioRiskResult: with self.__pricing_context: return PortfolioRiskResult(self, (risk_measure,) if isinstance(risk_measure, RiskMeasure) else risk_measure, [p.calc(risk_measure, fn=fn) for p in self.__priceables])

================================================ FILE: docs/_build/html/_modules/gs_quant/markets/securities.html ================================================ gs_quant.markets.securities — gs_quant 0.1 documentation

Source code for gs_quant.markets.securities

"""
Copyright 2019 Goldman Sachs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
"""

import datetime as dt
import json
import threading
from abc import ABCMeta, abstractmethod
from enum import Enum
from typing import Optional, Tuple, Union, Dict

import cachetools
import pandas as pd
import pytz
from pydash import get

from gs_quant.api.gs.assets import GsAssetApi, GsAsset, AssetClass, AssetParameters, \
    AssetType as GsAssetType, PositionSet, Currency
from gs_quant.base import get_enum_value
from gs_quant.common import PositionType
from gs_quant.data import DataMeasure, DataFrequency
from gs_quant.data.coordinate import DataDimensions
from gs_quant.data.coordinate import DateOrDatetime
from gs_quant.entities.entity import Entity, EntityIdentifier, EntityType
from gs_quant.errors import MqValueError
from gs_quant.json_encoder import JSONEncoder
from gs_quant.markets import PricingContext


class ExchangeCode(Enum):
    """Exchange enumeration

    Exchange codes representing global venues where Securities are listed and traded

    """

    NASDAQ = "NASD"  # Nasdaq Global Stock Market
    NYSE = "NYSE"  # New York Stock Exchange


[docs]class AssetType(Enum): """Asset type enumeration Enumeration of different types of asset or security. """ #: Index which tracks an evolving portfolio of securities, and can be traded through cash or derivatives markets INDEX = "Index" #: Exchange traded fund which tracks an evolving portfolio of securities and is listed on an exchange to be #: traded as a security ETF = "ETF" #: Bespoke basket which provides exposure to a customized collection of assets with levels published daily; can be #: traded on swap and rebalanced programmatically BASKET = "Custom Basket" #: Listed equities which provide access to equity holding in a company and participation in dividends and other #: distributions in common, preferred or other variants which provide different investor rights STOCK = "Single Stock" #: Standardized listed contract which provides delivery of an asset at a pre-defined forward date and can be #: settled in cash or physical form FUTURE = "Future" #: FX cross or currency pair CROSS = "Cross" #: Currency CURRENCY = "Currency" #: Rate RATE = "Rate" #: Cash CASH = "Cash" #: Weather Index WEATHER_INDEX = "Weather Index" #: Swap SWAP = "Swap" #: Option OPTION = "Option" #: Commodity Reference Price COMMODITY_REFERENCE_PRICE = "Commodity Reference Price" # COMMODITY NATURAL GAS Hub COMMODITY_NATURAL_GAS_HUB = "Commodity Natural Gas Hub" # COMMODITY EU NATURAL GAS Hub COMMODITY_EU_NATURAL_GAS_HUB = "Commodity EU Natural Gas Hub" #: Commodity Power Node COMMODITY_POWER_NODE = "Commodity Power Node" #: Commodity Power Aggregated Nodes COMMODITY_POWER_AGGREGATED_NODES = "Commodity Power Aggregated Nodes" #: Bond BOND = "Bond" #: Future Market FUTURE_MARKET = "Future Market" #: Future Contract FUTURE_CONTRACT = "Future Contract" #: Commodity COMMODITY = "Commodity" #: Crypto CRYPTO_CURRENCY = "Crypto Currency"
[docs]class AssetIdentifier(EntityIdentifier): """Asset type enumeration Enumeration of different security identifiers """ MARQUEE_ID = "MQID" #: Goldman Sachs Marquee identifier code (MA4B66MW5E27UAHKG34) REUTERS_ID = "RIC" #: Thompson Reuters Instrument Code (RIC), (GS.N) BLOOMBERG_ID = "BBID" #: Bloomberg identifier and exchange code (GS UN) BLOOMBERG_COMPOSITE_ID = "BCID" #: Bloomberg composite identifier and exchange code (GS US) CUSIP = "CUSIP" #: Committee on Uniform Security Identification Procedures code (38141G104) ISIN = "ISIN" #: International Securities Identification Number (US38141G1040) SEDOL = "SEDOL" #: LSE Stock Exchange Daily Official List code (2407966) TICKER = "TICKER" #: Exchange ticker (GS)
class ReturnType(Enum): """Index return type Represents different index return types or funding models """ EXCESS_RETURN = "Excess Return" # Returns are excess of funding rate in denominated currency TOTAL_RETURN = "Total Return" # Returns are inclusive of funding rate in denominated currency
[docs]class Asset(Entity, metaclass=ABCMeta):
[docs] def __init__(self, id_: str, asset_class: AssetClass, name: str, exchange: Optional[str] = None, currency: Optional[str] = None, parameters: AssetParameters = None, entity: Optional[Dict] = None): super().__init__(id_, EntityType.ASSET, entity=entity) self.__id = id_ self.asset_class = asset_class self.name = name self.exchange = exchange self.currency = currency self.parameters = parameters
def get_marquee_id(self): return self.__id def get_identifiers(self, as_of: dt.date = None) -> dict: """ Get asset identifiers :param as_of: As of date for query :return: dict of identifiers **Usage** Get asset identifiers as of a given date. Where the identifiers are temporal (and can change over time), this function will return the identifiers as of that point in time. If no date is provided as a parameter, will use the current PricingContext. **Examples** Get current asset identifiers: >>> gs = SecurityMaster.get_asset("GS", AssetIdentifier.TICKER) >>> gs.get_identifiers() Get identifiers as of 1Jan18: >>> gs.get_identifiers(dt.date(2018,1,1)) Use PricingContext to determine as of date: >>> with PricingContext(dt.date(2018,1,1)) as ctx: >>> gs.get_identifiers() **See also** :class:`AssetIdentifier` :func:`get_asset` """ if not as_of: as_of = PricingContext.current.pricing_date if isinstance(as_of, dt.datetime): as_of = as_of.date() valid_ids = set(item.value for item in AssetIdentifier) xrefs = GsAssetApi.get_asset_xrefs(self.__id) identifiers = {} for xref in xrefs: start_date = xref.startDate end_date = xref.endDate if start_date <= as_of <= end_date: identifiers = {k.upper(): v for k, v in xref.identifiers.as_dict().items() if k.upper() in valid_ids} return identifiers @cachetools.cached(cachetools.TTLCache(256, 600), lambda s, id_type, as_of=None: cachetools.keys.hashkey(s.get_marquee_id(), id_type, as_of), threading.RLock()) def get_identifier(self, id_type: AssetIdentifier, as_of: dt.date = None): """ Get asset identifier :param as_of: As of date for query :param id_type: requested id type :return: identifier value **Usage** Get asset identifier as of a given date. Where the identifiers are temporal (and can change over time), this function will return the identifier as of that point in time. If no date is provided as a parameter, will use the current PricingContext. **Examples** Get current SEDOL: >>> import datetime as dt >>> >>> gs = SecurityMaster.get_asset("GS", AssetIdentifier.TICKER) >>> gs.get_identifier(AssetIdentifier.SEDOL) Get SEDOL as of 1Jan18: >>> gs.get_identifier(AssetIdentifier.SEDOL, as_of=dt.date(2018,1,1)) Use PricingContext to determine as of date: >>> with PricingContext(dt.date(2018,1,1)) as ctx: >>> gs.get_identifier(AssetIdentifier.SEDOL) **See also** :class:`AssetIdentifier` :func:`get_asset_identifiers` """ if id_type == AssetIdentifier.MARQUEE_ID: return self.__id ids = self.get_identifiers(as_of=as_of) return ids.get(id_type.value) def get_data_series(self, measure: DataMeasure, dimensions: Optional[DataDimensions] = None, frequency: Optional[DataFrequency] = None, start: Optional[DateOrDatetime] = None, end: Optional[DateOrDatetime] = None) -> pd.Series: """ Get asset series :param measure: measure to get as series :param dimensions: dimensions to query (e.g. tenor) :param frequency: data frequency to query :param start: start of the series :param end: end of the series :return: timeseries of given measure **Usage** Get a given timeseries for the asset **Examples** Get close price series: >>> from gs_quant.markets.securities import SecurityMaster >>> from gs_quant.data import DataMeasure >>> >>> gs = SecurityMaster.get_asset("GS", AssetIdentifier.TICKER) >>> gs.get_data_series(DataMeasure.CLOSE_PRICE) **See also** :class:`DataMeasure` """ coordinate = self.get_data_coordinate(measure, dimensions, frequency) if coordinate is None: raise MqValueError(f"No data co-ordinate found for these parameters: {measure, dimensions, frequency}") return coordinate.get_series(start=start, end=end) def get_close_prices(self, start: Optional[dt.date] = None, end: Optional[dt.date] = None) -> pd.Series: """ Get close price series :return: timeseries of close prices **Usage** Get close prices for an asset **Examples** Get close price series: >>> from gs_quant.markets.securities import SecurityMaster >>> >>> gs = SecurityMaster.get_asset("GS", AssetIdentifier.TICKER) >>> gs.get_close_prices() **See also** :class:`DataMeasure` :func:`get_data_series` """ return self.get_data_series(DataMeasure.CLOSE_PRICE, None, DataFrequency.DAILY, start, end) @abstractmethod def get_type(self) -> AssetType: """Overridden by sub-classes to return security type""" @property def data_dimension(self) -> str: return 'assetId' @classmethod def entity_type(cls) -> EntityType: return EntityType.ASSET @classmethod def get(cls, id_value: str, id_type: AssetIdentifier, as_of: Union[dt.date, dt.datetime] = None, exchange_code: ExchangeCode = None, asset_type: AssetType = None, sort_by_rank: bool = False) -> Optional['Asset']: asset = SecurityMaster.get_asset(id_value, id_type, as_of, exchange_code, asset_type, sort_by_rank) return asset
[docs]class Stock(Asset): """Base Security Type Represents a financial asset which can be held in a portfolio, or has an observable price fixing which can be referenced in a derivative transaction """
[docs] def __init__(self, id_: str, name: str, exchange: Optional[str] = None, currency: Optional[Currency] = None, entity: Optional[Dict] = None): Asset.__init__(self, id_, AssetClass.Equity, name, exchange, currency, entity=entity)
def get_type(self) -> AssetType: return AssetType.STOCK def get_currency(self) -> Optional[Currency]: return self.currency
class Cross(Asset): """Base Security Type Represents a financial asset which can be held in a portfolio, or has an observable price fixing which can be referenced in a derivative transaction """ def __init__(self, id_: str, name: str, entity: Optional[Dict] = None): Asset.__init__(self, id_, AssetClass.FX, name, entity=entity) def get_type(self) -> AssetType: return AssetType.CROSS class Future(Asset): """Future Security Type Represents a standardized listed contract which provides delivery of an asset at a pre-defined forward date and can be settled in cash or physical form """ def __init__(self, id_: str, asset_class: Union[AssetClass, str], name: str, currency: Optional[Currency] = None, entity: Optional[Dict] = None): if isinstance(asset_class, str): asset_class = get_enum_value(AssetClass, asset_class) Asset.__init__(self, id_, asset_class, name, currency=currency, entity=entity) def get_type(self) -> AssetType: return AssetType.FUTURE def get_currency(self) -> Optional[Currency]: return self.currency class Currency(Asset): """Base Security Type Represents a financial asset which can be held in a portfolio, or has an observable price fixing which can be referenced in a derivative transaction """ def __init__(self, id_: str, name: str, entity: Optional[Dict] = None): Asset.__init__(self, id_, AssetClass.Cash, name, entity=entity) def get_type(self) -> AssetType: return AssetType.CURRENCY class Rate(Asset): """Base Security Type Represents a financial asset which can be held in a portfolio, or has an observable price fixing which can be referenced in a derivative transaction """ def __init__(self, id_: str, name: str, entity: Optional[Dict] = None): Asset.__init__(self, id_, AssetClass.Rates, name, entity=entity) def get_type(self) -> AssetType: return AssetType.RATE class Cash(Asset): """Cash Security Type Represents a financial asset which can be held in a portfolio, or has an observable price fixing which can be referenced in a derivative transaction """ def __init__(self, id_: str, name: str, entity: Optional[Dict] = None): Asset.__init__(self, id_, AssetClass.Cash, name, entity=entity) def get_type(self) -> AssetType: return AssetType.CASH class WeatherIndex(Asset): """Weather Index Type Represents an underlying index on a weather derivative, including where the data (e.g. CPD) has been collected, an actual physical reference point (weather station) and various fall back arrangements. """ def __init__(self, id_: str, name: str, entity: Optional[Dict] = None): Asset.__init__(self, id_, AssetClass.Commod, name, entity=entity) def get_type(self) -> AssetType: return AssetType.WEATHER_INDEX class CommodityReferencePrice(Asset): """Commodity Reference Price Represents an underlying index for commodities in the event that no ISDA Commodity Reference Price exists. Includes base, details, unit, currency and exchange id or publication etc. """ def __init__(self, id_: str, name: str, entity: Optional[Dict] = None): Asset.__init__(self, id_, AssetClass.Commod, name, entity=entity) def get_type(self) -> AssetType: return AssetType.COMMODITY_REFERENCE_PRICE class CommodityNaturalGasHub(Asset): """Commodity Natural Gas Hub Represents a distinct location in commodity Natural Gas markets """ def __init__(self, id_: str, name: str, entity: Optional[Dict] = None): Asset.__init__(self, id_, AssetClass.Commod, name, entity=entity) def get_type(self) -> AssetType: return AssetType.COMMODITY_NATURAL_GAS_HUB class CommodityEUNaturalGasHub(Asset): """Commodity EU Natural Gas Hub Represents a virtual/physical hub in EU Natural Gas markets """ def __init__(self, id_: str, name: str, entity: Optional[Dict] = None): Asset.__init__(self, id_, AssetClass.Commod, name, entity=entity) def get_type(self) -> AssetType: return AssetType.COMMODITY_EU_NATURAL_GAS_HUB class CryptoCurrency(Asset): """Crypto Currency Represents a cryptocurrency """ def __init__(self, id_: str, asset_class: Union[AssetClass, str], name: str, entity: Optional[Dict] = None): Asset.__init__(self, id_, asset_class, name, entity=entity) def get_type(self) -> AssetType: return AssetType.CRYPTO_CURRENCY class CommodityPowerNode(Asset): """Commodity Power Node Represents a distinct location in commodity power markets """ def __init__(self, id_: str, name: str, entity: Optional[Dict] = None): Asset.__init__(self, id_, AssetClass.Commod, name, entity=entity) def get_type(self) -> AssetType: return AssetType.COMMODITY_POWER_NODE class CommodityPowerAggregatedNodes(Asset): """Commodity Power Aggregated Nodes Represents a group of locations in commodity power markets """ def __init__(self, id_: str, name: str, entity: Optional[Dict] = None): Asset.__init__(self, id_, AssetClass.Commod, name, entity=entity) def get_type(self) -> AssetType: return AssetType.COMMODITY_POWER_AGGREGATED_NODES class Commodity(Asset): """Commodity Represents a commodity. """ def __init__(self, id_: str, name: str, entity: Optional[Dict] = None): Asset.__init__(self, id_, AssetClass.Commod, name, entity=entity) def get_type(self) -> AssetType: return AssetType.COMMODITY class Bond(Asset): """Bond Represents a bond. """ def __init__(self, id_: str, name: str, entity: Optional[Dict] = None): Asset.__init__(self, id_, AssetClass.Credit, name, entity=entity) def get_type(self) -> AssetType: return AssetType.BOND class FutureMarket(Asset): """Future Market Represents a future market """ def __init__(self, id_: str, asset_class: Union[AssetClass, str], name: str, entity: Optional[Dict] = None): if isinstance(asset_class, str): asset_class = get_enum_value(AssetClass, asset_class) Asset.__init__(self, id_, asset_class, name, entity=entity) def get_type(self) -> AssetType: return AssetType.FUTURE_MARKET class FutureContract(Asset): """Future Contract Represents a future contract """ def __init__(self, id_: str, asset_class: Union[AssetClass, str], name: Union[AssetClass, str], entity: Optional[Dict] = None): if isinstance(asset_class, str): asset_class = get_enum_value(AssetClass, asset_class) Asset.__init__(self, id_, asset_class, name, entity=entity) def get_type(self) -> AssetType: return AssetType.FUTURE_CONTRACT class Swap(Asset): """Swap Instrument Type Represents a Swap Instrument """ def __init__(self, id_: str, asset_class: Union[AssetClass, str], name: str, entity: Optional[Dict] = None): if isinstance(asset_class, str): asset_class = get_enum_value(AssetClass, asset_class) Asset.__init__(self, id_, asset_class, name, entity=entity) def get_type(self) -> AssetType: return AssetType.SWAP class Option(Asset): """Option Instrument Type Represents an Option Instrument """ def __init__(self, id_: str, asset_class: Union[AssetClass, str], name: str, entity: Optional[Dict] = None): if isinstance(asset_class, str): asset_class = get_enum_value(AssetClass, asset_class) Asset.__init__(self, id_, asset_class, name, entity=entity) def get_type(self) -> AssetType: return AssetType.OPTION class IndexConstituentProvider(metaclass=ABCMeta): def __init__(self, id_: str): self.__id = id_ def get_constituents(self, as_of: dt.date = None, position_type: PositionType = PositionType.CLOSE) -> Tuple[ PositionSet, ...]: """ Get asset constituents :param as_of: As of date for query :param position_type: :return: dict of identifiers **Usage** Get index constituents as of a given date. If no date is provided as a parameter, will use the current PricingContext. **Examples** Get current index constituents (defaults to close): >>> import datetime as dt >>> >>> gs = SecurityMaster.get_asset('GSTHHVIP', AssetIdentifier.TICKER) >>> gs.get_constituents() Get constituents as of market open on 3Jan18: >>> gs.get_constituents(dt.date(2018,1,3), PositionType.OPEN) Use PricingContext to determine as of date: >>> with PricingContext(dt.date(2018,1,1)) as ctx: >>> gs.get_constituents() **See also** :class:`AssetIdentifier` :func:`get_asset` """ if not as_of: as_of = PricingContext.current.pricing_date if isinstance(as_of, dt.datetime): as_of = as_of.date() return GsAssetApi.get_asset_positions_for_date(self.__id, as_of, position_type.value)
[docs]class Index(Asset, IndexConstituentProvider): """Index Asset Index which tracks an evolving portfolio of securities, and can be traded through cash or derivatives markets """
[docs] def __init__(self, id_: str, asset_class: AssetClass, name: str, exchange: Optional[str] = None, currency: Optional[Currency] = None, entity: Optional[Dict] = None): Asset.__init__(self, id_, asset_class, name, exchange, currency, entity=entity) IndexConstituentProvider.__init__(self, id_)
def get_type(self) -> AssetType: return AssetType.INDEX def get_currency(self) -> Optional[Currency]: return self.currency def get_return_type(self) -> ReturnType: if self.parameters is None or self.parameters.index_return_type is None: return ReturnType.TOTAL_RETURN return ReturnType(self.parameters.index_return_type)
class ETF(Asset, IndexConstituentProvider): """ETF Asset ETF which tracks an evolving portfolio of securities, and can be traded on exchange """ def __init__(self, id_: str, asset_class: AssetClass, name: str, exchange: Optional[str] = None, currency: Optional[Currency] = None, entity: Optional[Dict] = None): Asset.__init__(self, id_, asset_class, name, exchange, currency, entity=entity) IndexConstituentProvider.__init__(self, id_) def get_type(self) -> AssetType: return AssetType.ETF def get_currency(self) -> Optional[Currency]: return self.currency class Basket(Asset, IndexConstituentProvider): """Basket Asset Basket which tracks an evolving portfolio of securities, and can be traded through cash or derivatives markets """ def __init__(self, id_: str, asset_class: AssetClass, name: str, currency: Optional[Currency] = None, entity: Optional[Dict] = None): Asset.__init__(self, id_, asset_class, name, currency=currency, entity=entity) IndexConstituentProvider.__init__(self, id_) def get_type(self) -> AssetType: return AssetType.BASKET def get_currency(self) -> Optional[Currency]: return self.currency
[docs]class SecurityMaster: """Security Master The SecurityMaster class provides an interface to security lookup functions. This allows querying and retrieval of different security types (assets) based on a variety of different identifiers through point-in-time lookups. Uses the current PricingContext to provide as of dates if optional arguments are not provided. Will return the relevant asset subclass depending on the type of the security **See also** :class:`Asset` """ @classmethod def __gs_asset_to_asset(cls, gs_asset: GsAsset) -> Asset: asset_type = gs_asset.type.value asset_entity: Dict = json.loads(json.dumps(gs_asset.as_dict(), cls=JSONEncoder)) if asset_type in (GsAssetType.Single_Stock.value,): return Stock(gs_asset.id, gs_asset.name, gs_asset.exchange, gs_asset.currency, entity=asset_entity) if asset_type in (GsAssetType.ETF.value,): return ETF(gs_asset.id, gs_asset.assetClass, gs_asset.name, gs_asset.exchange, gs_asset.currency, entity=asset_entity) if asset_type in ( GsAssetType.Index.value, GsAssetType.Risk_Premia.value, GsAssetType.Access.value, GsAssetType.Multi_Asset_Allocation.value): return Index(gs_asset.id, gs_asset.assetClass, gs_asset.name, gs_asset.exchange, gs_asset.currency, entity=asset_entity) if asset_type in ( GsAssetType.Custom_Basket.value, GsAssetType.Research_Basket.value): return Basket(gs_asset.id, gs_asset.assetClass, gs_asset.name, gs_asset.currency, entity=asset_entity) if asset_type in (GsAssetType.Future.value,): return Future(gs_asset.id, gs_asset.assetClass, gs_asset.name, gs_asset.currency, entity=asset_entity) if asset_type in (GsAssetType.Cross.value,): return Cross(gs_asset.id, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.Currency.value,): return Currency(gs_asset.id, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.Rate.value,): return Rate(gs_asset.id, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.Cash.value,): return Cash(gs_asset.id, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.WeatherIndex.value,): return WeatherIndex(gs_asset.id, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.Swap.value,): return Swap(gs_asset.id, gs_asset.assetClass, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.Option.value,): return Option(gs_asset.id, gs_asset.assetClass, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.CommodityReferencePrice.value,): return CommodityReferencePrice(gs_asset.id, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.CommodityNaturalGasHub.value,): return CommodityNaturalGasHub(gs_asset.id, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.CommodityEUNaturalGasHub.value,): return CommodityEUNaturalGasHub(gs_asset.id, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.CommodityPowerNode.value,): return CommodityPowerNode(gs_asset.id, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.CommodityPowerAggregatedNodes.value,): return CommodityPowerAggregatedNodes(gs_asset.id, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.Bond.value,): return Bond(gs_asset.id, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.Commodity.value,): return Commodity(gs_asset.id, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.FutureMarket.value,): return FutureMarket(gs_asset.id, gs_asset.assetClass, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.FutureContract.value,): return FutureContract(gs_asset.id, gs_asset.assetClass, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.Crypto_Currency.value,): return CryptoCurrency(gs_asset.id, gs_asset.assetClass, gs_asset.name, entity=asset_entity) raise TypeError(f'unsupported asset type {asset_type}') @classmethod def __asset_type_to_gs_types(cls, asset_type: AssetType) -> Tuple[GsAssetType, ...]: asset_map = { AssetType.STOCK: (GsAssetType.Single_Stock,), AssetType.INDEX: ( GsAssetType.Index, GsAssetType.Multi_Asset_Allocation, GsAssetType.Risk_Premia, GsAssetType.Access), AssetType.ETF: (GsAssetType.ETF, GsAssetType.ETN), AssetType.BASKET: (GsAssetType.Custom_Basket, GsAssetType.Research_Basket), AssetType.FUTURE: (GsAssetType.Future,), AssetType.RATE: (GsAssetType.Rate,), } return asset_map.get(asset_type) @classmethod def get_asset(cls, id_value: str, id_type: AssetIdentifier, as_of: Union[dt.date, dt.datetime] = None, exchange_code: ExchangeCode = None, asset_type: AssetType = None, sort_by_rank: bool = False ) -> Asset: """ Get an asset by identifier and identifier type :param id_value: identifier value :param id_type: identifier type :param exchange_code: exchange code :param asset_type: asset type :param as_of: As of date for query :param sort_by_rank: whether to sort assets by rank. :return: Asset object or None **Usage** Get asset object using a specified identifier and identifier type. Where the identifiers are temporal (and can change over time), will use the current MarketContext to evaluate based on the specified date. **Examples** Get asset by bloomberg id: >>> gs = SecurityMaster.get_asset("GS UN", AssetIdentifier.BLOOMBERG_ID) Get asset by ticker and exchange code: >>> gs = SecurityMaster.get_asset("GS", AssetIdentifier.TICKER, exchange_code=ExchangeCode.NYSE) Get asset by ticker and asset type: >>> spx = SecurityMaster.get_asset("SPX", AssetIdentifier.TICKER, asset_type=AssetType.INDEX) **See also** :class:`AssetIdentifier` :func:`get_many_assets` """ if not as_of: as_of = PricingContext.current.pricing_date if isinstance(as_of, dt.date): as_of = dt.datetime.combine(as_of, dt.time(0, 0), pytz.utc) if id_type is AssetIdentifier.MARQUEE_ID: gs_asset = GsAssetApi.get_asset(id_value) return cls.__gs_asset_to_asset(gs_asset) query = {id_type.value.lower(): id_value} if exchange_code is not None: query['exchange'] = exchange_code.value if asset_type is not None: query['type'] = [t.value for t in cls.__asset_type_to_gs_types(asset_type)] if sort_by_rank: results = GsAssetApi.get_many_assets(as_of=as_of, return_type=dict, order_by=['>rank'], **query) result = get(results, '0') if result: result = GsAsset.from_dict(result) else: results = GsAssetApi.get_many_assets(as_of=as_of, **query) result = next(iter(results), None) if result: return cls.__gs_asset_to_asset(result)

================================================ FILE: docs/_build/html/_modules/gs_quant/models/epidemiology.html ================================================ gs_quant.models.epidemiology — gs_quant 0.1 documentation

Source code for gs_quant.models.epidemiology

"""
Copyright 2020 Goldman Sachs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
"""
from abc import ABC, abstractmethod
from typing import Type

import numpy as np
from scipy.integrate import odeint
from lmfit import minimize, Parameters, report_fit

"""
Statistical models for the transmission of infectious diseases
"""


class CompartmentalModel(ABC):

    @classmethod
    @abstractmethod
    def calibrate(cls, xs, t, parameters) -> tuple:
        """ Should be of form callable(y, t, ...)
            See https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.odeint.html """
        raise NotImplementedError

    @classmethod
    @abstractmethod
    def get_parameters(cls, *args, **kwargs) -> tuple:
        raise NotImplementedError


[docs]class SIR(CompartmentalModel): """SIR Model""" @classmethod def calibrate(cls, xs: tuple, t: float, parameters: [Parameters, tuple]) -> tuple: """ SIR model derivatives at t. :param xs: variables that we are solving for, i.e. [S]usceptible, [I]nfected, [R]emoved :param t: time parameter, inactive for this model :param parameters: parameters of the model (not including initial conditions), i.e. beta, gamma, N :return: tuple, the derivatives dSdt, dIdt, dRdt of each of the S, I, R variables """ s, i, r = xs if isinstance(parameters, Parameters): beta = parameters['beta'].value gamma = parameters['gamma'].value N = parameters['N'].value elif isinstance(parameters, tuple): beta, gamma, N = parameters else: raise ValueError("Cannot recognize parameter input") dSdt = - beta * s * i / N dIdt = beta * s * i / N - gamma * i dRdt = gamma * i return dSdt, dIdt, dRdt @classmethod def get_parameters(cls, S0: float, I0: float, R0: float, N: float, beta: float = 0.2, gamma: float = 0.1, beta_max: float = 10, gamma_max: float = 1, S0_fixed: bool = True, S0_max: float = 1e6, beta_fixed: bool = False, gamma_fixed: bool = False, R0_fixed: bool = True, R0_max: float = 1e6, I0_fixed: bool = True, I0_max: float = 1e6)\ -> tuple: """ Produce a set of parameters for the SIR model. :param S0: initial number of susceptible in the population :param I0: initial number of infected in the population, usually set to 1 :param R0: initial number of recovered/removed in the population, usually set to 0 :param N: size of the population :param beta: transmission rate parameter :param gamma: recovery rate parameter :param beta_max: maximum value to consider for beta during parameter fitting :param gamma_max: maximum value of gamma to consider during parameter fitting :param S0_fixed: whether to keep S0 fixed during fitting :param S0_max: maximum value of S0 to consider during parameter fitting :param R0_fixed: whether to keep R0 fixed during fitting :param R0_max: maximum value of R0 to consider during parameter fitting :param I0_fixed: whether to keep I0 fixed during fitting :param I0_max: maximum value of I0 to consider during parameter fitting :return: tuple[Parameters, list]: (parameters, a list of the names of the variables for initial conditions) """ parameters = Parameters() parameters.add('N', value=N, min=0, max=N, vary=False) parameters.add('S0', value=S0, min=0, max=S0_max, vary=not S0_fixed) parameters.add('I0', value=I0, min=0, max=I0_max, vary=not I0_fixed) parameters.add('R0', value=R0, min=0, max=R0_max, vary=not R0_fixed) parameters.add('beta', value=beta, min=0, max=beta_max, vary=not beta_fixed) parameters.add('gamma', value=gamma, min=0, max=gamma_max, vary=not gamma_fixed) initial_conditions = ['S0', 'I0', 'R0'] return parameters, initial_conditions
[docs]class SEIR(CompartmentalModel): """SEIR Model""" @classmethod def calibrate(cls, xs: tuple, t: float, parameters: [Parameters, tuple]) -> tuple: """ SEIR model derivatives at t. :param xs: variables that we are solving for, i.e. [S]usceptible, [E]xposed, [I]nfected, [R]emoved :param t: time parameter, inactive for this model :param parameters: parameters of the model (not including initial conditions), i.e. beta, gamma, sigma, N :return: tuple, the derivatives dSdt, dEdt, dIdt, dRdt of each of the S, E, I, R variables """ s, e, i, r = xs if isinstance(parameters, Parameters): beta = parameters['beta'].value gamma = parameters['gamma'].value sigma = parameters['sigma'].value N = parameters['N'].value elif isinstance(parameters, tuple): beta, gamma, sigma, N = parameters else: raise ValueError("Cannot recognize parameter input") dSdt = -beta * s * i / N dEdt = beta * s * i / N - sigma * e dIdt = sigma * e - gamma * i dRdt = gamma * i return dSdt, dEdt, dIdt, dRdt @classmethod def get_parameters(cls, S0: float, E0: float, I0: float, R0: float, N: float, beta: float = 0.2, gamma: float = 0.1, sigma: float = 0.2, beta_max: float = 10, gamma_max: float = 1, sigma_max: float = 1, beta_fixed: bool = False, gamma_fixed: bool = False, sigma_fixed: bool = False, S0_fixed: bool = True, S0_max: float = 1e6, R0_fixed: bool = True, R0_max: float = 1e6, I0_fixed: bool = True, I0_max: float = 1e6, E0_fixed: bool = True, E0_max: float = 1e6) -> tuple: """ Produce a set of parameters for the SIR model. :param S0: initial number of susceptible in the population :param E0: initial number of exposed in the population :param I0: initial number of infected in the population, usually set to 1 :param R0: initial number of recovered/removed in the population, usually set to 0 :param N: size of the population :param beta: transmission rate parameter :param gamma: recovery rate parameter :param sigma: parameter controlling transition from exposed to infectious :param beta_max: maximum value to consider for beta during parameter fitting :param gamma_max: maximum value of gamma to consider during parameter fitting :param sigma_max: maximum value of gamma to consider during parameter fitting :param S0_fixed: whether to keep S0 fixed during fitting :param S0_max: maximum value of S0 to consider during parameter fitting :param E0_fixed: whether to keep E0 fixed during fitting :param E0_max: maximum value of E0 to consider during parameter fitting :param R0_fixed: whether to keep R0 fixed during fitting :param R0_max: maximum value of R0 to consider during parameter fitting :param I0_fixed: whether to keep I0 fixed during fitting :param I0_max: maximum value of I0 to consider during parameter fitting :return: tuple[Parameters, list]: (parameters, a list of the names of the variables for initial conditions) """ parameters = Parameters() parameters.add('N', value=N, min=0, max=N, vary=False) parameters.add('S0', value=S0, min=0, max=S0_max, vary=not S0_fixed) parameters.add('E0', value=E0, min=0, max=E0_max, vary=not E0_fixed) parameters.add('I0', value=I0, min=0, max=I0_max, vary=not I0_fixed) parameters.add('R0', value=R0, min=0, max=R0_max, vary=not R0_fixed) parameters.add('beta', value=beta, min=0, max=beta_max, vary=not beta_fixed) parameters.add('gamma', value=gamma, min=0, max=gamma_max, vary=not gamma_fixed) parameters.add('sigma', value=sigma, min=0, max=sigma_max, vary=not sigma_fixed) initial_conditions = ['S0', 'E0', 'I0', 'R0'] return parameters, initial_conditions
def switch(t: float, T: float, eta: float = 0, xi: float = 0.1, nu: float = 0) -> float: """ Return a time-dependent factor that decreases exponentially, to scale parameters that are affected at some time point T. Ex. quarantine control measures were instated at some point T > t0, where t0 is the first day in available data. After this point, transmission rate was effectively reduced. This is modeled as a time-varying exponential decrease from 1 (no effect) to some fixed coefficient eta (the full effect) (ie. at time t0 it is 1 * beta, and by time T + nu, it is around eta * beta). If quarantine reduces transmission rate by 60%, eta would be 0.4. The paramater xi controls the steepness of this decrease, the nu controls the shift relative to time T at which the decrease peaks (ie. effects from quarantine measures at time T are visible at T+5 so nu=5). If nu=0, steepest point is at time T. To disable the 'switch', set eta = 1.0 :param t: current time step :param T: some fixed time step at which the regime 'switches' - T is relative to t0, which is positioned at 0 NOTE: this is not necessarily the steepest point of decrease. That is controlled by nu. :param eta: optional - the proportional reduction in transmission rate after control measures (ie. quarantine) :param xi: optional - the slope of the exponential decrease :param nu: optional - the shift of the exponential decrease :return: """ return eta + (1 - eta) / (1 + np.exp(xi * (t - T - nu))) class SEIRCM(CompartmentalModel): """ SEIR Model from https://www.medrxiv.org/content/10.1101/2020.03.04.20031104v1.full.pdf with cumulative cases (C) and cumulative fatalities (M) """ @classmethod def calibrate(cls, xs: tuple, t: float, parameters: [Parameters, tuple]) -> tuple: """ SEIRCM model derivatives at t. :param xs: variables that we are solving for i.e. [S]usceptible, [E]xposed, [I]nfected, [R]emoved, [C]ases, [M]ortality :param t: time parameter :param parameters: parameters of the model (not including initial conditions) i.e. beta, gamma, sigma, eta, epsilon :return: the derivatives of each of the S, E, I, R, C, M variables """ s, e, i, r, c, m = xs if isinstance(parameters, Parameters): beta = parameters['beta'].value # transmission rate gamma = parameters['gamma'].value # removal rate sigma = parameters['sigma'].value # infection rate eta = parameters['eta'].value # control measure redux epsilon = parameters['sigma'].value # case fatality rate T_quarantine = parameters['T'].value # time of quarantine policy, relative to t=0 (first reported case) elif isinstance(parameters, tuple): beta, gamma, sigma, eta, epsilon, T_quarantine = parameters else: raise ValueError("Cannot recognize parameter input") # total population N = s + e + i + r # if T_quarantine is 0, we are not considering effect of quarantine policy so scale factor is fixed at 1 quarantine_factor = switch(t, T_quarantine, eta=eta) if T_quarantine else 1 dSdt = -(quarantine_factor * beta) * s * i / N # susceptible -> exposed dEdt = (quarantine_factor * beta) * s * i / N - sigma * e # exposed -> infected AND recorded cases dIdt = sigma * e - gamma * i # infected -> removed (recovered AND dead) dRdt = (1 - epsilon) * gamma * i # recovered (from removed) dCdt = sigma * e # recorded cases (from infected) dMdt = epsilon * gamma * i # dead (from removed) return dSdt, dEdt, dIdt, dRdt, dCdt, dMdt @classmethod def get_parameters(cls, S0: float, E0: float, I0: float, R0: float, C0: float, M0: float, T_quarantine: float = 0, beta: float = 0.2, gamma: float = 0.1, sigma: float = 0.2, eta: float = 0.6, epsilon: float = 0.02, beta_max: float = 10, gamma_max: float = 1, sigma_max: float = 1, eta_max: float = 1, epsilon_max: float = 1, beta_fixed: bool = False, gamma_fixed: bool = False, sigma_fixed: bool = False, eta_fixed: bool = False, epsilon_fixed: bool = False, S0_fixed: bool = True, S0_max: float = 1e6, R0_fixed: bool = True, R0_max: float = 1e6, I0_fixed: bool = True, I0_max: float = 1e6, E0_fixed: bool = True, E0_max: float = 1e6, C0_fixed: bool = True, C0_max: float = 1e6, M0_fixed: bool = True, M0_max: float = 1e6) -> tuple: """ Produce a set of parameters for the SIERCM model. :param S0: initial number of susceptible in the population :param E0: initial number of exposed in the population :param I0: initial number of infected in the population, usually set to 1 :param R0: initial number of recovered/removed in the population, usually set to 0 :param C0: initial number of cumulative infected cases :param M0: initial number of cumulative fatalities :param T_quarantine: relative time at which quarantine policy goes in effect. If 0, not used. :param beta: transmission rate parameter :param gamma: recovery rate parameter :param sigma: parameter controlling transition from exposed to infectious :param eta: parameter controlling proportion by which quarantine measure reduces rate of transmission :param epsilon: parameter controlling rate of death :param beta_max: maximum value to consider for beta during parameter fitting :param gamma_max: maximum value of gamma to consider during parameter fitting :param sigma_max: maximum value to consider for sigma during parameter fitting :param eta_max: maximum value to consider for eta during parameter fitting :param epsilon_max: maximum value to consider for epsilon during parameter fitting :param beta_fixed: whether to keep beta fixed during fitting :param gamma_fixed: whether to keep gamma fixed during fitting :param sigma_fixed: whether to keep sigma fixed during fitting :param eta_fixed: whether to keep eta fixed during fitting :param epsilon_fixed: whether to keep epsilon fixed during fitting :param S0_fixed: whether to keep S0 fixed during fitting :param S0_max: maximum value of S0 to consider during parameter fitting :param E0_fixed: whether to keep E0 fixed during fitting :param E0_max: maximum value of E0 to consider during parameter fitting :param R0_fixed: whether to keep R0 fixed during fitting :param R0_max: maximum value of R0 to consider during parameter fitting :param I0_fixed: whether to keep I0 fixed during fitting :param I0_max: maximum value of I0 to consider during parameter fitting :param C0_fixed: whether to keep C0 fixed during fitting :param C0_max: maximum value of C0 to consider during parameter fitting :param M0_fixed: whether to keep M0 fixed during fitting :param M0_max: maximum value of M0 to consider during parameter fitting :return: tuple[Parameters, list]: (parameters, a list of the names of the variables for initial conditions) """ parameters = Parameters() parameters.add('T', value=T_quarantine, min=0, max=T_quarantine, vary=False) parameters.add('S0', value=S0, min=0, max=S0_max, vary=not S0_fixed) parameters.add('E0', value=E0, min=0, max=E0_max, vary=not E0_fixed) parameters.add('I0', value=I0, min=0, max=I0_max, vary=not I0_fixed) parameters.add('R0', value=R0, min=0, max=R0_max, vary=not R0_fixed) parameters.add('C0', value=C0, min=0, max=C0_max, vary=not C0_fixed) parameters.add('M0', value=M0, min=0, max=M0_max, vary=not M0_fixed) parameters.add('beta', value=beta, min=0, max=beta_max, vary=not beta_fixed) parameters.add('gamma', value=gamma, min=0, max=gamma_max, vary=not gamma_fixed) parameters.add('sigma', value=sigma, min=0, max=sigma_max, vary=not sigma_fixed) parameters.add('eta', value=eta, min=0, max=eta_max, vary=not eta_fixed) parameters.add('epsilon', value=epsilon, min=0, max=epsilon_max, vary=not epsilon_fixed) initial_conditions = ['S0', 'E0', 'I0', 'R0', 'C0', 'M0'] return parameters, initial_conditions class SEIRCMAgeStratified(CompartmentalModel): """ Age-structured SEIRCM Model from https://www.medrxiv.org/content/10.1101/2020.03.04.20031104v1.full.pdf """ @classmethod def calibrate(cls, y: list, t: float, parameters: Parameters) -> list: """ SEIR model derivatives at t. :param y: variables that we are solving for i.e. [S]usceptible, [E]xposed, [I]nfected, [R]emoved, [C]ases, [M]ortality :param t: time parameter :param parameters: parameters of the model (not including initial conditions) i.e. beta, gamma, sigma, eta, epsilon :return: the derivatives dydt of each of the variable in y """ beta = parameters['beta'].value # transmission rate gamma = parameters['gamma'].value # removal rate sigma = parameters['sigma'].value # infection rate eta = parameters['eta'].value # control measure redux T_quarantine = parameters['T'].value # time of quarantine policy, relative to t=0 (first reported case) K = parameters['K'].value # number of age groups # y is of dimension (6 * K) x 1, where the first K are 'S', next K are 'E', and so on for each age group... assert len(y) == 6 * K, f'Error: SEIRCM states not organized into {K} age groups!' dydt = [0] * len(y) def epsilon(k): # case fatality rate per age group return parameters[f'epsilon_{k}'].value N = sum(y[:4 * K]) # sum(S) + sum(E) + sum(I) + sum(R) where sum is over age groups I_total = sum(y[2 * K:3 * K]) # total number of infectious people across age groups s, e, i = lambda k: y[k], lambda k: y[K + k], lambda k: y[2 * K + k] # if T_quarantine is 0, we are not considering effect of quarantine policy so scale factor is fixed at 1 quarantine_factor = switch(t, T_quarantine, eta=eta) if T_quarantine else 1 for k in range(K): dydt[k] = -(quarantine_factor * beta) * s(k) * I_total / N # susceptible -> exposed dydt[K + k] = (quarantine_factor * beta) * s(k) * I_total / N - sigma * e(k) # exposed -> infected dydt[2 * K + k] = sigma * e(k) - gamma * i(k) # infected -> removed dydt[3 * K + k] = (1 - epsilon(k)) * gamma * i(k) # -> cumulative recovered (from removed) dydt[4 * K + k] = sigma * e(k) # -> cumulative recorded cases (from infected) dydt[5 * K + k] = epsilon(k) * gamma * i(k) # -> cumulative fatalities (from removed) return dydt @classmethod def get_parameters(cls, S0: np.ndarray, E0: np.ndarray, I0: np.ndarray, R0: np.ndarray, C0: np.ndarray, M0: np.ndarray, K: int, T_quarantine: float = 0, beta: float = 0.2, gamma: float = 0.1, sigma: float = 0.2, eta: float = 0.6, epsilon: np.ndarray = None, beta_max: float = 10, gamma_max: float = 1, sigma_max: float = 1, eta_max: float = 1, epsilon_max: float = 1, beta_fixed: bool = False, gamma_fixed: bool = False, sigma_fixed: bool = False, eta_fixed: bool = False, epsilon_fixed: bool = False, S0_fixed: bool = True, S0_max: float = 1e6, R0_fixed: bool = True, R0_max: float = 1e6, I0_fixed: bool = True, I0_max: float = 1e6, E0_fixed: bool = True, E0_max: float = 1e6, C0_fixed: bool = True, C0_max: float = 1e6, M0_fixed: bool = True, M0_max: float = 1e6) -> tuple: """ Produce a set of parameters for the age-stratified SIERCM model. :param S0: initial number of susceptible in the population per age group :param E0: initial number of exposed in the population per age group :param I0: initial number of infected in the population per age group :param R0: initial number of recovered/removed in the population per age group :param C0: initial number of cumulative infected cases per age group :param M0: initial number of cumulative fatalities per age group :param K: number of age groups :param T_quarantine: relative time at which quarantine policy goes in effect. If 0, not used. :param beta: transmission rate parameter :param gamma: removal rate parameter :param sigma: parameter controlling transition from exposed to infectious :param eta: parameter controlling proportion by which quarantine measure reduces rate of transmission :param epsilon: parameter controlling rate of death per age group. Recovery rate == 1 - epsilon :param beta_max: maximum value to consider for beta during parameter fitting :param gamma_max: maximum value of gamma to consider during parameter fitting :param sigma_max: maximum value to consider for sigma during parameter fitting :param eta_max: maximum value to consider for eta during parameter fitting :param epsilon_max: maximum value to consider for epsilon during parameter fitting :param beta_fixed: whether to keep beta fixed during fitting :param gamma_fixed: whether to keep gamma fixed during fitting :param sigma_fixed: whether to keep sigma fixed during fitting :param eta_fixed: whether to keep eta fixed during fitting :param epsilon_fixed: whether to keep epsilon fixed during fitting :param S0_fixed: whether to keep S0 fixed during fitting :param S0_max: maximum value of S0 to consider during parameter fitting :param E0_fixed: whether to keep E0 fixed during fitting :param E0_max: maximum value of E0 to consider during parameter fitting :param R0_fixed: whether to keep R0 fixed during fitting :param R0_max: maximum value of R0 to consider during parameter fitting :param I0_fixed: whether to keep I0 fixed during fitting :param I0_max: maximum value of I0 to consider during parameter fitting :param C0_fixed: whether to keep C0 fixed during fitting :param C0_max: maximum value of C0 to consider during parameter fitting :param M0_fixed: whether to keep M0 fixed during fitting :param M0_max: maximum value of M0 to consider during parameter fitting :return: tuple[Parameters, list]: (parameters, a list of the names of the variables for initial conditions) """ parameters = Parameters() parameters.add('K', value=K, min=0, max=K, vary=False) parameters.add('T', value=T_quarantine, min=-1, max=T_quarantine, vary=False) parameters.add('beta', value=beta, min=0, max=beta_max, vary=not beta_fixed) parameters.add('gamma', value=gamma, min=0, max=gamma_max, vary=not gamma_fixed) parameters.add('sigma', value=sigma, min=0, max=sigma_max, vary=not sigma_fixed) parameters.add('eta', value=eta, min=0, max=eta_max, vary=not eta_fixed) # add parameters that vary by age group for k in range(K): parameters.add(f'epsilon_{k}', value=epsilon[k], min=0, max=epsilon_max, vary=not epsilon_fixed) # add initial state conditions that vary by age group initial_conditions = [] for param in ['S0', 'E0', 'I0', 'R0', 'C0', 'M0']: for k in range(K): parameters.add(f'{param}_{k}', value=eval(param)[k], min=0, max=eval(f'{param}_max'), vary=not eval(f'{param}_fixed')) initial_conditions.append(f'{param}_{k}') return parameters, initial_conditions
[docs]class EpidemicModel: """Class to perform solutions and parameter-fitting of epidemic models"""
[docs] def __init__(self, model: Type[CompartmentalModel], parameters: tuple = None, data: np.array = None, initial_conditions: list = None, fit_method: str = 'leastsq', error: callable = None, fit_period: float = None): """ A class to standardize fitting and solving epidemiological models. :param model: the model to use, currently a class in the form of SIR, SEIR above :param parameters: tuple, parameters to use for the model, defaults to the output of [model].get_parameters :param data: np.array, data that can be used to calibrate the model :param initial_conditions: list, initial conditions for the model :param fit_method: str, the method to use to minimize the (given) error. Available methods are those in the lmfit.minimizer.minimize function. Default is Levenberg-Marquardt least squares minimization. :param error: callable, control which residuals (and in what form) to minimize for fitting. :param fit_period: float, how far back to fit the data, defaults to fitting all data """ self.model = model self.parameters = parameters self.data = data self.initial_conditions = initial_conditions self.fit_method = fit_method self.error = error self.fit_period = fit_period self.result = None self.fitted_parameters = None
def solve(self, time_range: np.ndarray, initial_conditions: [list, tuple], parameters) -> np.ndarray: """ Integrate the model ODEs to get a solution. :param time_range: the time range to solve for :param initial_conditions: the initial conditions for the solution :param parameters: the parameters for the solution :return: """ x = odeint(self.model.calibrate, initial_conditions, time_range, args=(parameters,)) x = np.array(x) return x def residual(self, parameters: Parameters, time_range: np.arange, data: np.ndarray) -> np.ndarray: """ Obtain fit error (to minimize). :param parameters: parameters to use (which we are usually minimizing the residual for) :param time_range: time range for solution (over which we obtain the residual) :param data: data to fit the models too (i.e. compute residuals in terms of) :return: """ initial_conditions = [] for variable in self.initial_conditions: initial_conditions.append(parameters[variable].value) # obtain solution given current initial conditions and parameters solution = self.solve(time_range, initial_conditions, parameters) # compute residual, using custom error function if it has been passed in residual = solution - data if self.error is None else self.error(solution, data, parameters) if self.fit_period is not None: residual = residual[-self.fit_period:] return residual.ravel() def fit(self, time_range: np.arange = None, parameters: [Parameters, tuple] = None, initial_conditions: list = None, residual=None, verbose: bool = False, data: np.array = None, fit_period: float = None): """ Fit the model based on data in the form np.array([X1,...,Xn]) """ if data is None: if self.data is None: raise ValueError("No data to fit the model on!") data = self.data if initial_conditions is not None: self.initial_conditions = initial_conditions if self.initial_conditions is None: raise ValueError("No initial conditions to fit the model with!") if parameters is None: if self.parameters is None: raise ValueError("No parameters to fit the model with!") parameters = self.parameters if time_range is None: time_range = np.arange(data.shape[0]) if fit_period is not None: self.fit_period = fit_period if residual is None: residual = self.residual result = minimize(residual, parameters, args=(time_range, data), method=self.fit_method) self.result = result self.fitted_parameters = result.params.valuesdict() if verbose: report_fit(result) return result

================================================ FILE: docs/_build/html/_modules/gs_quant/risk/core.html ================================================ gs_quant.risk.core — gs_quant 0.1 documentation

Source code for gs_quant.risk.core

"""
Copyright 2019 Goldman Sachs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
"""
from abc import ABCMeta, abstractmethod
from concurrent.futures import Future
from copy import copy
import itertools
import datetime as dt
from typing import Iterable, Optional, Tuple, Union, Any, Callable

import pandas as pd

from .measures import RiskMeasure
from gs_quant.base import RiskKey
from gs_quant.datetime import point_sort_order

__column_sort_fns = {
    'label1': point_sort_order,
    'mkt_point': point_sort_order,
    'point': point_sort_order
}
__risk_columns = ('date', 'time', 'mkt_type', 'mkt_asset', 'mkt_class', 'mkt_point')


class ResultInfo(metaclass=ABCMeta):

    def __init__(
            self,
            risk_key: RiskKey,
            unit: Optional[dict] = None,
            error: Optional[Union[str, dict]] = None
    ):
        self.__risk_key = risk_key
        self.__unit = unit
        self.__error = error

    @property
    @abstractmethod
    def raw_value(self):
        ...

    @property
    def risk_key(self) -> RiskKey:
        return self.__risk_key

    @property
    def unit(self) -> dict:
        """The units of this result"""
        return self.__unit

    @property
    def error(self) -> Union[str, dict]:
        """Any error associated with this result"""
        return self.__error

    @staticmethod
    def composition_info(components: Iterable):
        dates = []
        values = []
        errors = {}
        risk_key = None
        unit = None

        for component in components:
            date = component.risk_key.date
            risk_key = component.risk_key.ex_date_and_market if risk_key is None else risk_key

            if isinstance(component, (ErrorValue, Exception)):
                errors[date] = component
            else:
                values.append(component.raw_value)
                dates.append(date)
                unit = unit or component.unit

        return dates, values, errors, risk_key, unit


class ErrorValue(ResultInfo):

    def __init__(self, risk_key: RiskKey, error: Union[str, dict]):
        super().__init__(risk_key, error=error)

    def __repr__(self):
        return self.error

    @property
    def raw_value(self):
        return None


class ScalarWithInfo(ResultInfo, metaclass=ABCMeta):

    def __init__(
            self,
            risk_key: RiskKey,
            value: Union[float, str],
            unit: Optional[dict] = None,
            error: Optional[Union[str, dict]] = None):
        float.__init__(value)
        ResultInfo.__init__(self, risk_key, unit=unit, error=error)

    @property
    @abstractmethod
    def raw_value(self):
        ...

    @staticmethod
    def compose(components: Iterable):
        dates, values, errors, risk_key, unit = ResultInfo.composition_info(components)
        return SeriesWithInfo(pd.Series(index=pd.DatetimeIndex(dates).date, data=values),
                              risk_key=risk_key,
                              unit=unit,
                              error=errors)


class FloatWithInfo(ScalarWithInfo, float):

    def __new__(cls,
                risk_key: RiskKey,
                value: Union[float, str],
                unit: Optional[str] = None,
                error: Optional[str] = None):
        return float.__new__(cls, value)

    @property
    def raw_value(self) -> float:
        return float(self)

    def __repr__(self):

        return self.error if self.error else float.__repr__(self)

    def __add__(self, other):
        if isinstance(other, FloatWithInfo):
            if self.unit == other.unit:
                return FloatWithInfo(combine_risk_key(self.risk_key, other.risk_key), self.raw_value + other.raw_value,
                                     self.unit)
            else:
                raise ValueError('FloatWithInfo unit mismatch')
        return super(FloatWithInfo, self).__add__(other)

    def to_frame(self):
        return self


class StringWithInfo(ScalarWithInfo, str):

    def __new__(cls,
                risk_key: RiskKey,
                value: Union[float, str],
                unit: Optional[dict] = None,
                error: Optional[str] = None):
        return str.__new__(cls, value)

    @property
    def raw_value(self) -> str:
        return str(self)

    def __repr__(self):
        return self.error if self.error else str.__repr__(self)


class SeriesWithInfo(pd.Series, ResultInfo):
    _internal_names = pd.DataFrame._internal_names + \
                      ['_ResultInfo__' + i for i in dir(ResultInfo) if isinstance(getattr(ResultInfo, i), property)]
    _internal_names_set = set(_internal_names)

    def __init__(
            self,
            *args,
            risk_key: Optional[RiskKey] = None,
            unit: Optional[dict] = None,
            error: Optional[Union[str, dict]] = None,
            **kwargs
    ):
        pd.Series.__init__(self, *args, **kwargs)
        ResultInfo.__init__(self, risk_key, unit=unit, error=error)

    def __repr__(self):
        if self.error:
            return pd.Series.__repr__(self) + "\nErrors: " + str(self.error)
        return pd.Series.__repr__(self)

    @property
    def _constructor(self):
        return SeriesWithInfo

    @property
    def _constructor_expanddim(self):
        return DataFrameWithInfo

    @property
    def raw_value(self) -> pd.Series:
        return pd.Series(self)


class DataFrameWithInfo(pd.DataFrame, ResultInfo):
    _internal_names = pd.DataFrame._internal_names + \
                      ['_ResultInfo__' + i for i in dir(ResultInfo) if isinstance(getattr(ResultInfo, i), property)]
    _internal_names_set = set(_internal_names)

    def __init__(
            self,
            *args,
            risk_key: Optional[RiskKey] = None,
            unit: Optional[dict] = None,
            error: Optional[Union[str, dict]] = None,
            **kwargs
    ):
        pd.DataFrame.__init__(self, *args, **kwargs)
        ResultInfo.__init__(self, risk_key, unit=unit, error=error)

    def __repr__(self):
        if self.error:
            return pd.DataFrame.__repr__(self) + "\nErrors: " + str(self.errors)
        return pd.DataFrame.__repr__(self)

    @property
    def _constructor(self):
        return DataFrameWithInfo

    @property
    def _constructor_sliced(self):
        return SeriesWithInfo

    @property
    def raw_value(self) -> pd.DataFrame:
        return pd.DataFrame(self)

    @staticmethod
    def compose(components: Iterable):
        dates, values, errors, risk_key, unit = ResultInfo.composition_info(components)
        df = pd.concat(v.assign(date=d) for d, v in zip(dates, values)).set_index('date')

        return DataFrameWithInfo(df, risk_key=risk_key, unit=unit, error=errors)

    def to_frame(self):
        return self


[docs]def aggregate_risk(results: Iterable[Union[DataFrameWithInfo, Future]], threshold: Optional[float] = None) \ -> pd.DataFrame: """ Combine the results of multiple InstrumentBase.calc() calls, into a single result :param results: An iterable of Dataframes and/or Futures (returned by InstrumentBase.calc()) :param threshold: exclude values whose absolute value falls below this threshold :return: A Dataframe with the aggregated results **Examples** >>> from gs_quant.instrument import IRCap, IRFloor >>> from gs_quant.markets import PricingContext >>> from gs_quant.risk import IRDelta, IRVega >>> >>> cap = IRCap('5y', 'GBP') >>> floor = IRFloor('5y', 'GBP') >>> instruments = (cap, floor) >>> >>> with PricingContext(): >>> delta_f = [inst.calc(IRDelta) for inst in instruments] >>> vega_f = [inst.calc(IRVega) for inst in (cap, floor)] >>> >>> delta = aggregate_risk(delta_f, threshold=0.1) >>> vega = aggregate_risk(vega_f) delta_f and vega_f are lists of futures, where the result will be a Dataframe delta and vega are Dataframes, representing the merged risk of the individual instruments """ dfs = [r.result().raw_value if isinstance(r, Future) else r.raw_value for r in results] result = pd.concat(df.reset_index(level=0) if df.index.name == 'date' else df for df in dfs) result = result.groupby([c for c in result.columns if c != 'value'], as_index=False).sum() if threshold is not None: result = result[result.value.abs() > threshold] return sort_risk(result)
ResultType = Union[None, dict, tuple, DataFrameWithInfo, FloatWithInfo, SeriesWithInfo] def aggregate_results(results: Iterable[ResultType]) -> ResultType: unit = None risk_key = None results = tuple(results) if not len(results): return None for result in results: if isinstance(result, Exception): raise Exception if result.error: raise ValueError('Cannot aggregate results in error') if not isinstance(result, type(results[0])): raise ValueError('Cannot aggregate heterogeneous types') if result.unit: if unit and unit != result.unit: raise ValueError('Cannot aggregate results with different units') unit = unit or result.unit if risk_key and risk_key != result.risk_key: raise ValueError('Cannot aggregate results with different pricing keys') risk_key = risk_key or result.risk_key inst = next(iter(results)) if isinstance(inst, dict): return dict((k, aggregate_results([r[k] for r in results])) for k in inst.keys()) elif isinstance(inst, tuple): return tuple(set(itertools.chain.from_iterable(results))) elif isinstance(inst, FloatWithInfo): return FloatWithInfo(risk_key, sum(results), unit=unit) elif isinstance(inst, SeriesWithInfo): return SeriesWithInfo(sum(results), risk_key=risk_key, unit=unit) elif isinstance(inst, DataFrameWithInfo): return DataFrameWithInfo(aggregate_risk(results), risk_key=risk_key, unit=unit)
[docs]def subtract_risk(left: DataFrameWithInfo, right: DataFrameWithInfo) -> pd.DataFrame: """Subtract bucketed risk. Dimensions must be identical :param left: Results to substract from :param right: Results to substract **Examples** >>> from gs_quant.datetime.date import business_day_offset >>> from gs_quant.instrument IRSwap >>> from gs_quant.markets import PricingContext >>> from gs_quant.risk import IRDelta >>> import datetime as dt >>> >>> ir_swap = IRSwap('Pay', '10y', 'USD') >>> delta_today = ir_swap.calc(IRDelta) >>> >>> with PricingContext(pricing_date=business_day_offset(dt.date.today(), -1, roll='preceding')): >>> delta_yday_f = ir_swap.calc(IRDelta) >>> >>> delta_diff = subtract_risk(delta_today, delta_yday_f.result()) """ assert (left.columns.names == right.columns.names) assert ('value' in left.columns.names) right_negated = copy(right) right_negated.value *= -1 return aggregate_risk((left, right_negated))
[docs]def sort_risk(df: pd.DataFrame, by: Tuple[str, ...] = __risk_columns) -> pd.DataFrame: """ Sort bucketed risk :param df: Input Dataframe :param by: Columns to sort by :return: A sorted Dataframe """ columns = tuple(df.columns) indices = [columns.index(c) if c in columns else -1 for c in by] fns = [__column_sort_fns.get(c) for c in columns] def cmp(row) -> tuple: return tuple( (fns[i](row[i]) or 0) if fns[i] else row[i] for i in indices if i != -1) data = sorted((row for _, row in df.iterrows()), key=cmp) fields = [f for f in by if f in columns] fields.extend(f for f in columns if f not in fields) result = pd.DataFrame.from_records(data, columns=columns)[fields] if 'date' in result: result = result.set_index('date') return result
def combine_risk_key(key_1: RiskKey, key_2: RiskKey) -> RiskKey: """ Combine two risk keys (key_1, key_2) into a new RiskKey :type key_1: RiskKey :type key_2: RiskKey """ def get_field_value(field_name: str): getattr(key_1, field_name) \ if getattr(key_1, field_name) == getattr(key_2, field_name) else None return RiskKey(get_field_value("provider"), get_field_value("date"), get_field_value("market"), get_field_value("params"), get_field_value("scenario"), get_field_value("risk_measure"))

================================================ FILE: docs/_build/html/_modules/gs_quant/risk/measures.html ================================================ gs_quant.risk.measures — gs_quant 0.1 documentation

Source code for gs_quant.risk.measures

"""
Copyright 2019 Goldman Sachs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
"""
from typing import Optional, Union

from gs_quant.base import Market
from gs_quant.common import AssetClass
from gs_quant.context_base import do_not_serialise
from gs_quant.target.risk import RiskMeasure as __RiskMeasure, RiskMeasureType, RiskMeasureUnit


class RiskMeasure(__RiskMeasure):

    def __repr__(self):
        return self.name or self.measure_type.name

    @property
    @do_not_serialise
    def pricing_context(self):
        from gs_quant.markets import PricingContext
        return PricingContext.current


class __RelativeRiskMeasure(RiskMeasure):

    def __init__(self,
                 to_market: Market,
                 asset_class: Union[AssetClass, str] = None,
                 measure_type: Union[RiskMeasureType, str] = None,
                 unit: Union[RiskMeasureUnit, str] = None,
                 value: Union[float, str] = None,
                 name: str = None):
        super().__init__(asset_class=asset_class, measure_type=measure_type, unit=unit, value=value, name=name)
        self.__to_market = to_market

    @property
    @do_not_serialise
    def pricing_context(self):
        from gs_quant.markets import PricingContext, RelativeMarket
        current = PricingContext.current
        return current.clone(market=RelativeMarket(from_market=current.market, to_market=self.__to_market))


class PnlExplain(__RelativeRiskMeasure):

    """ Pnl Explained """

    def __init__(self, to_market: Market):
        super().__init__(to_market, measure_type=RiskMeasureType.PnlExplain, name=RiskMeasureType.PnlExplain.value)


class PnlExplainClose(PnlExplain):

    def __init__(self):
        from gs_quant.markets import CloseMarket
        super().__init__(CloseMarket())


class PnlExplainLive(PnlExplain):

    def __init__(self):
        from gs_quant.markets import LiveMarket
        super().__init__(LiveMarket())


class PnlPredictLive(__RelativeRiskMeasure):

    """ Pnl Predicted """

    def __init__(self):
        from gs_quant.markets import LiveMarket
        super().__init__(LiveMarket(), measure_type=RiskMeasureType.PnlPredict, name=RiskMeasureType.PnlPredict.value)


def __risk_measure_with_doc_string(name: str,
                                   doc: str,
                                   measure_type: RiskMeasureType,
                                   asset_class: Optional[AssetClass] = None,
                                   unit: Optional[RiskMeasureUnit] = None
                                   ) -> RiskMeasure:
    measure = RiskMeasure(measure_type=measure_type, asset_class=asset_class, unit=unit, name=name)
    measure.__doc__ = doc
    return measure


DollarPrice = __risk_measure_with_doc_string('DollarPrice', 'Present value in USD', RiskMeasureType.Dollar_Price)
Price = __risk_measure_with_doc_string('Price', 'Present value in local currency', RiskMeasureType.PV)
ForwardPrice = __risk_measure_with_doc_string(
    'ForwardPrice',
    'Forward price',
    RiskMeasureType.Forward_Price,
    unit=RiskMeasureUnit.BPS)
BaseCPI = __risk_measure_with_doc_string('BaseCPI', 'Base CPI level', RiskMeasureType.BaseCPI)
Theta = __risk_measure_with_doc_string('Theta', '1 day Theta', RiskMeasureType.Theta)
EqDelta = __risk_measure_with_doc_string(
    'EqDelta',
    'Equity Delta',
    RiskMeasureType.Delta,
    asset_class=AssetClass.Equity)
EqGamma = __risk_measure_with_doc_string(
    'EqGamma',
    'Equity Gamma',
    RiskMeasureType.Gamma,
    asset_class=AssetClass.Equity)
EqVega = __risk_measure_with_doc_string('EqVega', 'Equity Vega', RiskMeasureType.Vega, asset_class=AssetClass.Equity)
EqSpot = __risk_measure_with_doc_string(
    'EqSpot',
    'Equity Spot Level',
    RiskMeasureType.Spot, asset_class=AssetClass.Equity)
EqAnnualImpliedVol = __risk_measure_with_doc_string(
    'EqAnnualImpliedVol',
    'Equity Annual Implied Volatility (%)',
    RiskMeasureType.Annual_Implied_Volatility,
    asset_class=AssetClass.Equity,
    unit=RiskMeasureUnit.Percent)
CommodDelta = __risk_measure_with_doc_string(
    'CommodDelta',
    'Commodity Delta',
    RiskMeasureType.Delta,
    asset_class=AssetClass.Commod)
CommodTheta = __risk_measure_with_doc_string(
    'CommodTheta',
    'Commodity Theta',
    RiskMeasureType.Theta,
    asset_class=AssetClass.Commod)
CommodVega = __risk_measure_with_doc_string(
    'CommodVega',
    'Commodity Vega',
    RiskMeasureType.Vega,
    asset_class=AssetClass.Commod)
FairVolStrike = __risk_measure_with_doc_string(
    'FairVolStrike',
    'Fair Volatility Strike Value of a Variance Swap',
    RiskMeasureType.FairVolStrike)
FairVarStrike = __risk_measure_with_doc_string(
    'FairVarStrike',
    'Fair Variance Strike Value of a Variance Swap',
    RiskMeasureType.FairVarStrike)
FXDelta = __risk_measure_with_doc_string('FXDelta', 'FX Delta', RiskMeasureType.Delta, asset_class=AssetClass.FX)
FXGamma = __risk_measure_with_doc_string('FXGamma', 'FX Gamma', RiskMeasureType.Gamma, asset_class=AssetClass.FX)
FXVega = __risk_measure_with_doc_string('FXVega', 'FX Vega', RiskMeasureType.Vega, asset_class=AssetClass.FX)
FXSpot = __risk_measure_with_doc_string('FXSpot', 'FX Spot Rate', RiskMeasureType.Spot, asset_class=AssetClass.FX)
IRBasis = __risk_measure_with_doc_string(
    'IRBasis',
    'Interest Rate Basis',
    RiskMeasureType.Basis,
    asset_class=AssetClass.Rates)
InflationDelta = __risk_measure_with_doc_string(
    'InflationDelta',
    'Inflation Delta',
    RiskMeasureType.InflationDelta,
    asset_class=AssetClass.Rates)
InflationDeltaParallel = __risk_measure_with_doc_string(
    'InflationDeltaParallel',
    'Inflation Parallel Delta',
    RiskMeasureType.ParallelInflationDelta,
    asset_class=AssetClass.Rates)
InflationDeltaParallelLocalCcy = __risk_measure_with_doc_string(
    'InflationDeltaParallelLocalCcy',
    'Inflation Parallel Delta (Local Ccy)',
    RiskMeasureType.ParallelInflationDeltaLocalCcy,
    asset_class=AssetClass.Rates)
IRDelta = __risk_measure_with_doc_string(
    'IRDelta',
    'Interest Rate Delta',
    RiskMeasureType.Delta,
    asset_class=AssetClass.Rates)
IRDeltaParallel = __risk_measure_with_doc_string(
    'IRDeltaParallel',
    'Interest Rate Parallel Delta',
    RiskMeasureType.ParallelDelta,
    asset_class=AssetClass.Rates)
IRDeltaLocalCcy = __risk_measure_with_doc_string(
    'IRDeltaLocalCcy',
    'Interest Rate Delta (Local Ccy)',
    RiskMeasureType.DeltaLocalCcy,
    asset_class=AssetClass.Rates)
IRDeltaParallelLocalCcy = __risk_measure_with_doc_string(
    'IRDeltaParallelLocalCcy',
    'Interest Rate Parallel Delta (Local Ccy)',
    RiskMeasureType.ParallelDeltaLocalCcy,
    asset_class=AssetClass.Rates)
IRDiscountDeltaParallel = __risk_measure_with_doc_string(
    'IRDiscountDeltaParallel',
    'Parallel Discount Delta',
    RiskMeasureType.ParallelDiscountDelta,
    asset_class=AssetClass.Rates)
IRDiscountDeltaParallelLocalCcy = __risk_measure_with_doc_string(
    'IRDiscountDeltaParallelLocalCcy',
    'Parallel Discount Delta (Local Ccy)',
    RiskMeasureType.ParallelDiscountDeltaLocalCcy,
    asset_class=AssetClass.Rates)
IRXccyDelta = __risk_measure_with_doc_string(
    'IRXccyDelta',
    'Cross-ccy Delta',
    RiskMeasureType.XccyDelta,
    asset_class=AssetClass.Rates)
IRXccyDeltaParallel = __risk_measure_with_doc_string(
    'IRXccyDeltaParallel',
    'Cross-ccy Parallel Delta',
    RiskMeasureType.ParallelXccyDelta,
    asset_class=AssetClass.Rates)
IRXccyDeltaParallelLocalCurrency = __risk_measure_with_doc_string(
    'IRXccyDeltaParallelLocalCurrency',
    'Cross-ccy Parallel Delta (Local Ccy)',
    RiskMeasureType.ParallelXccyDeltaLocalCcy,
    asset_class=AssetClass.Rates)
IRGammaParallel = __risk_measure_with_doc_string(
    'IRGammaParallel',
    'Interest Rate Parallel Gamma',
    RiskMeasureType.ParallelGamma,
    asset_class=AssetClass.Rates)
IRGammaParallelLocalCcy = __risk_measure_with_doc_string(
    'IRGammaParallelLocalCcy',
    'Interest Rate Parallel Gamma (Local Ccy)',
    RiskMeasureType.ParallelGammaLocalCcy,
    asset_class=AssetClass.Rates)
IRVega = __risk_measure_with_doc_string(
    'IRVega',
    'Interest Rate Vega',
    RiskMeasureType.Vega,
    asset_class=AssetClass.Rates)
IRVegaParallel = __risk_measure_with_doc_string(
    'IRVegaParallel',
    'Interest Rate Parallel Vega',
    RiskMeasureType.ParallelVega,
    asset_class=AssetClass.Rates)
IRVegaLocalCcy = __risk_measure_with_doc_string(
    'IRVegaLocalCcy',
    'Interest Rate Vega (Local Ccy)',
    RiskMeasureType.VegaLocalCcy,
    asset_class=AssetClass.Rates)
IRVegaParallelLocalCcy = __risk_measure_with_doc_string(
    'IRVegaParallelLocalCcy',
    'Interest Rate Parallel Vega (Local Ccy)',
    RiskMeasureType.ParallelVegaLocalCcy,
    asset_class=AssetClass.Rates)
IRAnnualImpliedVol = __risk_measure_with_doc_string(
    'IRAnnualImpliedVol',
    'Interest Rate Annual Implied Volatility (%)',
    RiskMeasureType.Annual_Implied_Volatility,
    asset_class=AssetClass.Rates,
    unit=RiskMeasureUnit.Percent)
IRAnnualATMImpliedVol = __risk_measure_with_doc_string(
    'IRAnnualATMImpliedVol',
    'Interest Rate Annual Implied At-The-Money Volatility (%)',
    RiskMeasureType.Annual_ATMF_Implied_Volatility,
    asset_class=AssetClass.Rates,
    unit=RiskMeasureUnit.Percent)
IRDailyImpliedVol = __risk_measure_with_doc_string(
    'IRDailyImpliedVol',
    'Interest Rate Daily Implied Volatility (bps)',
    RiskMeasureType.Daily_Implied_Volatility,
    asset_class=AssetClass.Rates,
    unit=RiskMeasureUnit.BPS)
IRSpotRate = __risk_measure_with_doc_string(
    'IRSpotRate',
    'At-The-Money Spot Rate (%)',
    RiskMeasureType.Spot_Rate,
    asset_class=AssetClass.Rates,
    unit=RiskMeasureUnit.Percent)
IRFwdRate = __risk_measure_with_doc_string(
    'IRFwdRate',
    'Par Rate (%)',
    RiskMeasureType.Forward_Rate,
    asset_class=AssetClass.Rates,
    unit=RiskMeasureUnit.Percent)
CRIFIRCurve = __risk_measure_with_doc_string(
    'CRIFIRCurve',
    'CRIF IR Curve',
    RiskMeasureType.CRIF_IRCurve)
ResolvedInstrumentValues = __risk_measure_with_doc_string(
    'ResolvedInstrumentBaseValues',
    'Resolved InstrumentBase Values',
    RiskMeasureType.Resolved_Instrument_Values
)
Description = __risk_measure_with_doc_string(
    'Description',
    'Description',
    RiskMeasureType.Description
)
Cashflows = __risk_measure_with_doc_string(
    'Cashflows',
    'Cashflows',
    RiskMeasureType.Cashflows
)
MarketDataAssets = __risk_measure_with_doc_string(
    'MarketDataAssets',
    'MarketDataAssets',
    RiskMeasureType.Market_Data_Assets
)
MarketData = __risk_measure_with_doc_string(
    'Market Data',
    'Market Data map of coordinates and values',
    RiskMeasureType.Market_Data
)

================================================ FILE: docs/_build/html/_modules/gs_quant/target/common.html ================================================ gs_quant.target.common — gs_quant 0.1 documentation

Source code for gs_quant.target.common

"""
Copyright 2019 Goldman Sachs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
"""

import datetime
from typing import Tuple, Union
from enum import Enum
from gs_quant.base import Base, EnumBase, InstrumentBase, Priceable, Scenario, camel_case_translate, get_enum_value


[docs]class AssetClass(EnumBase, Enum): """Asset classification of security. Assets are classified into broad groups which exhibit similar characteristics and behave in a consistent way under different market conditions""" Cash = 'Cash' Commod = 'Commod' Credit = 'Credit' Cross_Asset = 'Cross Asset' Econ = 'Econ' Equity = 'Equity' Fund = 'Fund' FX = 'FX' Mortgage = 'Mortgage' Rates = 'Rates' Loan = 'Loan' Social = 'Social' Cryptocurrency = 'Cryptocurrency' def __repr__(self): return self.value
class AssetType(EnumBase, Enum): """Asset type differentiates the product categorization or contract type""" Access = 'Access' Any = 'Any' AveragePriceOption = 'AveragePriceOption' Basis = 'Basis' BasisSwap = 'BasisSwap' Benchmark = 'Benchmark' Benchmark_Rate = 'Benchmark Rate' Binary = 'Binary' Bond = 'Bond' BondFuture = 'BondFuture' BondFutureOption = 'BondFutureOption' BondOption = 'BondOption' Calendar_Spread = 'Calendar Spread' Cap = 'Cap' Cash = 'Cash' Certificate = 'Certificate' CD = 'CD' Cliquet = 'Cliquet' CMSOption = 'CMSOption' CMSOptionStrip = 'CMSOptionStrip' CMSSpreadOption = 'CMSSpreadOption' CMSSpreadOptionStrip = 'CMSSpreadOptionStrip' Commodity = 'Commodity' CommodityReferencePrice = 'CommodityReferencePrice' CommodVarianceSwap = 'CommodVarianceSwap' CommodityPowerNode = 'CommodityPowerNode' CommodityPowerAggregatedNodes = 'CommodityPowerAggregatedNodes' CommodityNaturalGasHub = 'CommodityNaturalGasHub' CommodityEUNaturalGasHub = 'CommodityEUNaturalGasHub' Company = 'Company' Convertible = 'Convertible' Credit_Basket = 'Credit Basket' Cross = 'Cross' Crypto_Currency = 'Crypto Currency' CSL = 'CSL' Currency = 'Currency' Custom_Basket = 'Custom Basket' Default_Swap = 'Default Swap' Economic = 'Economic' Endowment = 'Endowment' Equity_Basket = 'Equity Basket' ETF = 'ETF' ETN = 'ETN' Event = 'Event' FRA = 'FRA' Fixing = 'Fixing' FixedLeg = 'FixedLeg' FloatLeg = 'FloatLeg' Floor = 'Floor' Forward = 'Forward' Fund = 'Fund' Future = 'Future' FutureContract = 'FutureContract' FutureMarket = 'FutureMarket' FutureOption = 'FutureOption' FutureStrategy = 'FutureStrategy' Hedge_Fund = 'Hedge Fund' Index = 'Index' InflationSwap = 'InflationSwap' Inter_Commodity_Spread = 'Inter-Commodity Spread' InvoiceSpread = 'InvoiceSpread' Market_Location = 'Market Location' MLF = 'MLF' Multi_Asset_Allocation = 'Multi-Asset Allocation' MultiCrossBinary = 'MultiCrossBinary' MultiCrossBinaryLeg = 'MultiCrossBinaryLeg' Mutual_Fund = 'Mutual Fund' Note = 'Note' Option = 'Option' OptionLeg = 'OptionLeg' OptionStrategy = 'OptionStrategy' Pension_Fund = 'Pension Fund' Preferred_Stock = 'Preferred Stock' Physical = 'Physical' Precious_Metal = 'Precious Metal' Precious_Metal_Swap = 'Precious Metal Swap' Precious_Metal_RFQ = 'Precious Metal RFQ' Reference_Entity = 'Reference Entity' Research_Basket = 'Research Basket' Rate = 'Rate' Risk_Premia = 'Risk Premia' Roll = 'Roll' Securities_Lending_Loan = 'Securities Lending Loan' Share_Class = 'Share Class' Single_Stock = 'Single Stock' Swap = 'Swap' SwapLeg = 'SwapLeg' SwapStrategy = 'SwapStrategy' Swaption = 'Swaption' Synthetic = 'Synthetic' Systematic_Hedging = 'Systematic Hedging' VarianceSwap = 'VarianceSwap' VolatilitySwap = 'VolatilitySwap' WeatherIndex = 'WeatherIndex' XccySwap = 'XccySwap' XccySwapFixFix = 'XccySwapFixFix' XccySwapFixFlt = 'XccySwapFixFlt' XccySwapMTM = 'XccySwapMTM' def __repr__(self): return self.value class BasketAction(EnumBase, Enum): """Indicates what was the action taken on basket - create/edit/rebalance""" Create = 'Create' Edit = 'Edit' Rebalance = 'Rebalance' def __repr__(self): return self.value class BondStrikeType(EnumBase, Enum): """The type of the bond strike - price, yield etc""" Price = 'Price' Yield = 'Yield' def __repr__(self): return self.value class BusinessDayConvention(EnumBase, Enum): """Business Day Convention""" Following = 'Following' Modified_Following = 'Modified Following' Previous = 'Previous' Unadjusted = 'Unadjusted' def __repr__(self): return self.value class BuySell(EnumBase, Enum): """Buy or Sell side of contract""" Buy = 'Buy' Sell = 'Sell' def __repr__(self): return self.value class CommodMeanRule(EnumBase, Enum): """Commodity mean rule""" Do_Not_Remove = 'Do Not Remove' Remove_Calculated = 'Remove Calculated' Remove_Fixed = 'Remove Fixed' def __repr__(self): return self.value class CommodUnit(EnumBase, Enum): """A coding scheme value to identify the unit of measure (e.g. Therms) in which the undelryer is denominated.""" Lot = 'Lot' MegaWattHour = 'MegaWattHour' Metric_Ton = 'Metric Ton' Million_British_Thermal_Units = 'Million British Thermal Units' Oil_Barrel = 'Oil Barrel' Troy_Pound = 'Troy Pound' US_Gallon = 'US Gallon' def __repr__(self): return self.value class CountryCode(EnumBase, Enum): """ISO Country code""" AU = 'AU' CX = 'CX' CC = 'CC' HM = 'HM' NF = 'NF' NZ = 'NZ' CK = 'CK' NU = 'NU' TK = 'TK' JP = 'JP' JN = 'JN' EU = 'EU' ER = 'ER' EZ = 'EZ' AT = 'AT' BE = 'BE' FI = 'FI' FR = 'FR' GF = 'GF' PF = 'PF' TF = 'TF' GP = 'GP' MQ = 'MQ' YT = 'YT' NC = 'NC' RE = 'RE' SH = 'SH' PM = 'PM' WF = 'WF' DE = 'DE' GE = 'GE' GR = 'GR' IE = 'IE' IT = 'IT' LU = 'LU' NL = 'NL' AW = 'AW' AN = 'AN' PT = 'PT' ES = 'ES' BY = 'BY' CH = 'CH' SE = 'SE' SW = 'SW' DK = 'DK' FO = 'FO' NO = 'NO' BV = 'BV' SJ = 'SJ' LI = 'LI' GB = 'GB' UK = 'UK' AI = 'AI' IO = 'IO' KY = 'KY' FK = 'FK' GI = 'GI' MS = 'MS' PN = 'PN' GS = 'GS' TC = 'TC' VG = 'VG' JE = 'JE' _02 = '02' US = 'US' AS = 'AS' GU = 'GU' MP = 'MP' PR = 'PR' UM = 'UM' VI = 'VI' CA = 'CA' AR = 'AR' BA = 'BA' BD = 'BD' BG = 'BG' BS = 'BS' BM = 'BM' BO = 'BO' BR = 'BR' CL = 'CL' CN = 'CN' CO = 'CO' CR = 'CR' CZ = 'CZ' DO = 'DO' EC = 'EC' EG = 'EG' GA = 'GA' GT = 'GT' HK = 'HK' HR = 'HR' HU = 'HU' IL = 'IL' IM = 'IM' IR = 'IR' IS = 'IS' JO = 'JO' KE = 'KE' KR = 'KR' KZ = 'KZ' LB = 'LB' LK = 'LK' LT = 'LT' MA = 'MA' MH = 'MH' ML = 'ML' MO = 'MO' MT = 'MT' MX = 'MX' MY = 'MY' NI = 'NI' OM = 'OM' PA = 'PA' PD = 'PD' PE = 'PE' PH = 'PH' PK = 'PK' PL = 'PL' QA = 'QA' RO = 'RO' RU = 'RU' SA = 'SA' SG = 'SG' SI = 'SI' SK = 'SK' SV = 'SV' TH = 'TH' TN = 'TN' TP = 'TP' TR = 'TR' TW = 'TW' UA = 'UA' UY = 'UY' VE = 'VE' VN = 'VN' ZA = 'ZA' BH = 'BH' EE = 'EE' GH = 'GH' ME = 'ME' RS = 'RS' ZM = 'ZM' ZW = 'ZW' TT = 'TT' AE = 'AE' KW = 'KW' BB = 'BB' LV = 'LV' GG = 'GG' CY = 'CY' CI = 'CI' MU = 'MU' PY = 'PY' HN = 'HN' BZ = 'BZ' NA = 'NA' FJ = 'FJ' BW = 'BW' DZ = 'DZ' MN = 'MN' SN = 'SN' TZ = 'TZ' AD = 'AD' AG = 'AG' AL = 'AL' AM = 'AM' AO = 'AO' AZ = 'AZ' BF = 'BF' BI = 'BI' BJ = 'BJ' BN = 'BN' BT = 'BT' CD = 'CD' CF = 'CF' CG = 'CG' CM = 'CM' CU = 'CU' CV = 'CV' CS = 'CS' DJ = 'DJ' DM = 'DM' EH = 'EH' ET = 'ET' FM = 'FM' GD = 'GD' GL = 'GL' GM = 'GM' GN = 'GN' GQ = 'GQ' GW = 'GW' GY = 'GY' HT = 'HT' ID = 'ID' IN = 'IN' IQ = 'IQ' JM = 'JM' KG = 'KG' KH = 'KH' KI = 'KI' KM = 'KM' KN = 'KN' KP = 'KP' LA = 'LA' LC = 'LC' LR = 'LR' LS = 'LS' LY = 'LY' MC = 'MC' MD = 'MD' MG = 'MG' MK = 'MK' MM = 'MM' MR = 'MR' MV = 'MV' MW = 'MW' MZ = 'MZ' NE = 'NE' NG = 'NG' NP = 'NP' NR = 'NR' PG = 'PG' PW = 'PW' RW = 'RW' SB = 'SB' SC = 'SC' SD = 'SD' SL = 'SL' SM = 'SM' SO = 'SO' SR = 'SR' ST = 'ST' SY = 'SY' SZ = 'SZ' TD = 'TD' TG = 'TG' TJ = 'TJ' TL = 'TL' TM = 'TM' TO = 'TO' TV = 'TV' UG = 'UG' UZ = 'UZ' VA = 'VA' VC = 'VC' VU = 'VU' WS = 'WS' YE = 'YE' def __repr__(self): return self.value class CreditOptionStrikeType(EnumBase, Enum): Spread_Adj = 'Spread Adj' Delta = 'Delta' def __repr__(self): return self.value class CreditOptionType(EnumBase, Enum): Payer = 'Payer' Receiver = 'Receiver' def __repr__(self): return self.value class Currency(EnumBase, Enum): """Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)""" _ = '' ACU = 'ACU' ADP = 'ADP' AED = 'AED' AFA = 'AFA' ALL = 'ALL' AMD = 'AMD' ANG = 'ANG' AOA = 'AOA' AOK = 'AOK' AON = 'AON' ARA = 'ARA' ARS = 'ARS' ARZ = 'ARZ' ATS = 'ATS' AUD = 'AUD' AUZ = 'AUZ' AZM = 'AZM' B03 = 'B03' BAD = 'BAD' BAK = 'BAK' BAM = 'BAM' BBD = 'BBD' BDN = 'BDN' BDT = 'BDT' BEF = 'BEF' BGL = 'BGL' BGN = 'BGN' BHD = 'BHD' BIF = 'BIF' BMD = 'BMD' BND = 'BND' BR6 = 'BR6' BRE = 'BRE' BRF = 'BRF' BRL = 'BRL' BRR = 'BRR' BSD = 'BSD' BTC = 'BTC' BTN = 'BTN' BTR = 'BTR' BWP = 'BWP' BYR = 'BYR' BZD = 'BZD' C23 = 'C23' CAC = 'CAC' CAD = 'CAD' CAZ = 'CAZ' CCI = 'CCI' CDF = 'CDF' CFA = 'CFA' CHF = 'CHF' CHZ = 'CHZ' CLF = 'CLF' CLP = 'CLP' CLZ = 'CLZ' CNH = 'CNH' CNO = 'CNO' CNY = 'CNY' CNZ = 'CNZ' COP = 'COP' COZ = 'COZ' CPB = 'CPB' CPI = 'CPI' CRC = 'CRC' CUP = 'CUP' CVE = 'CVE' CYP = 'CYP' CZH = 'CZH' CZK = 'CZK' DAX = 'DAX' DEM = 'DEM' DIJ = 'DIJ' DJF = 'DJF' DKK = 'DKK' DOP = 'DOP' DZD = 'DZD' E51 = 'E51' E52 = 'E52' E53 = 'E53' E54 = 'E54' ECI = 'ECI' ECS = 'ECS' ECU = 'ECU' EEK = 'EEK' EF0 = 'EF0' EGP = 'EGP' ESP = 'ESP' ETB = 'ETB' EUR = 'EUR' EUZ = 'EUZ' F06 = 'F06' FED = 'FED' FIM = 'FIM' FJD = 'FJD' FKP = 'FKP' FRF = 'FRF' FT1 = 'FT1' GBP = 'GBP' GBZ = 'GBZ' GEK = 'GEK' GHC = 'GHC' GHS = 'GHS' GHY = 'GHY' GIP = 'GIP' GLR = 'GLR' GMD = 'GMD' GNF = 'GNF' GQE = 'GQE' GRD = 'GRD' GTQ = 'GTQ' GWP = 'GWP' GYD = 'GYD' HKB = 'HKB' HKD = 'HKD' HNL = 'HNL' HRK = 'HRK' HSI = 'HSI' HTG = 'HTG' HUF = 'HUF' IDB = 'IDB' IDO = 'IDO' IDR = 'IDR' IEP = 'IEP' IGP = 'IGP' ILS = 'ILS' INO = 'INO' INP = 'INP' INR = 'INR' IPA = 'IPA' IPX = 'IPX' IQD = 'IQD' IRR = 'IRR' IRS = 'IRS' ISI = 'ISI' ISK = 'ISK' ISO = 'ISO' ITL = 'ITL' J05 = 'J05' JMD = 'JMD' JNI = 'JNI' JOD = 'JOD' JPY = 'JPY' JPZ = 'JPZ' JZ9 = 'JZ9' KES = 'KES' KGS = 'KGS' KHR = 'KHR' KMF = 'KMF' KOR = 'KOR' KPW = 'KPW' KRW = 'KRW' KWD = 'KWD' KYD = 'KYD' KZT = 'KZT' LAK = 'LAK' LBA = 'LBA' LBP = 'LBP' LHY = 'LHY' LKR = 'LKR' LRD = 'LRD' LSL = 'LSL' LSM = 'LSM' LTL = 'LTL' LUF = 'LUF' LVL = 'LVL' LYD = 'LYD' MAD = 'MAD' MDL = 'MDL' MGF = 'MGF' MKD = 'MKD' MMK = 'MMK' MNT = 'MNT' MOP = 'MOP' MRO = 'MRO' MTP = 'MTP' MUR = 'MUR' MVR = 'MVR' MWK = 'MWK' MXB = 'MXB' MXN = 'MXN' MXP = 'MXP' MXW = 'MXW' MXZ = 'MXZ' MYO = 'MYO' MYR = 'MYR' MZM = 'MZM' MZN = 'MZN' NAD = 'NAD' ND3 = 'ND3' NGF = 'NGF' NGI = 'NGI' NGN = 'NGN' NIC = 'NIC' NLG = 'NLG' NOK = 'NOK' NOZ = 'NOZ' NPR = 'NPR' NZD = 'NZD' NZZ = 'NZZ' O08 = 'O08' OMR = 'OMR' PAB = 'PAB' PEI = 'PEI' PEN = 'PEN' PEZ = 'PEZ' PGK = 'PGK' PHP = 'PHP' PKR = 'PKR' PLN = 'PLN' PLZ = 'PLZ' PSI = 'PSI' PTE = 'PTE' PYG = 'PYG' QAR = 'QAR' R2K = 'R2K' ROL = 'ROL' RON = 'RON' RSD = 'RSD' RUB = 'RUB' RUF = 'RUF' RUR = 'RUR' RWF = 'RWF' SAR = 'SAR' SBD = 'SBD' SCR = 'SCR' SDP = 'SDP' SDR = 'SDR' SEK = 'SEK' SET = 'SET' SGD = 'SGD' SGS = 'SGS' SHP = 'SHP' SKK = 'SKK' SLL = 'SLL' SRG = 'SRG' SSI = 'SSI' STD = 'STD' SUR = 'SUR' SVC = 'SVC' SVT = 'SVT' SYP = 'SYP' SZL = 'SZL' T21 = 'T21' T51 = 'T51' T52 = 'T52' T53 = 'T53' T54 = 'T54' T55 = 'T55' T71 = 'T71' TE0 = 'TE0' TED = 'TED' TF9 = 'TF9' THB = 'THB' THO = 'THO' TMM = 'TMM' TND = 'TND' TNT = 'TNT' TOP = 'TOP' TPE = 'TPE' TPX = 'TPX' TRB = 'TRB' TRL = 'TRL' TRY = 'TRY' TRZ = 'TRZ' TTD = 'TTD' TWD = 'TWD' TZS = 'TZS' UAH = 'UAH' UCB = 'UCB' UDI = 'UDI' UFC = 'UFC' UFZ = 'UFZ' UGS = 'UGS' UGX = 'UGX' USB = 'USB' USD = 'USD' UVR = 'UVR' UYP = 'UYP' UYU = 'UYU' VAC = 'VAC' VEB = 'VEB' VEF = 'VEF' VES = 'VES' VND = 'VND' VUV = 'VUV' WST = 'WST' XAF = 'XAF' XAG = 'XAG' XAU = 'XAU' XPD = 'XPD' XPT = 'XPT' XCD = 'XCD' XDR = 'XDR' XEU = 'XEU' XOF = 'XOF' XPF = 'XPF' YDD = 'YDD' YER = 'YER' YUD = 'YUD' YUN = 'YUN' ZAL = 'ZAL' ZAR = 'ZAR' ZAZ = 'ZAZ' ZMK = 'ZMK' ZMW = 'ZMW' ZRN = 'ZRN' ZRZ = 'ZRZ' ZWD = 'ZWD' AUd = 'AUd' BWp = 'BWp' EUr = 'EUr' GBp = 'GBp' ILs = 'ILs' KWd = 'KWd' MWk = 'MWk' SGd = 'SGd' SZl = 'SZl' USd = 'USd' ZAr = 'ZAr' def __repr__(self): return self.value class CurrencyName(EnumBase, Enum): """Currency Names""" _ = '' United_States_Dollar = 'United States Dollar' Australian_Dollar = 'Australian Dollar' Canadian_Dollar = 'Canadian Dollar' Swiss_Franc = 'Swiss Franc' Yuan_Renminbi_Hong_Kong = 'Yuan Renminbi (Hong Kong)' Czech_Republic_Koruna = 'Czech Republic Koruna' Euro = 'Euro' Pound_Sterling = 'Pound Sterling' Japanese_Yen = 'Japanese Yen' South_Korean_Won = 'South Korean Won' Malasyan_Ringgit = 'Malasyan Ringgit' Norwegian_Krone = 'Norwegian Krone' New_Zealand_Dollar = 'New Zealand Dollar' Polish_Zloty = 'Polish Zloty' Russian_Rouble = 'Russian Rouble' Swedish_Krona = 'Swedish Krona' South_African_Rand = 'South African Rand' Yuan_Renminbi_Onshore = 'Yuan Renminbi (Onshore)' def __repr__(self): return self.value class DayCountFraction(EnumBase, Enum): """Day Count Fraction""" ACT_OVER_360 = 'ACT/360' ACT_OVER_365_Fixed = 'ACT/365 (Fixed)' ACT_OVER_365_ISDA = 'ACT/365 ISDA' ACT_OVER_ACT_ISDA = 'ACT/ACT ISDA' _30_OVER_360 = '30/360' _30E_OVER_360 = '30E/360' def __repr__(self): return self.value class Field(EnumBase, Enum): """Field to be returned""" investmentRate = 'investmentRate' startingEmmaLegalEntityId = 'startingEmmaLegalEntityId' mdapiClass = 'mdapiClass' totalNotionalUSD = 'totalNotionalUSD' bidUnadjusted = 'bidUnadjusted' aggressiveFillsPercentage = 'aggressiveFillsPercentage' vehicleType = 'vehicleType' totalFatalitiesByState = 'totalFatalitiesByState' newActive = 'newActive' dailyRisk = 'dailyRisk' energy = 'energy' sunshineDailyForecast = 'sunshineDailyForecast' sentimentScore = 'sentimentScore' _0 = '0' _1 = '1' _2 = '2' _3 = '3' correlation = 'correlation' exposure = 'exposure' size = 'size' _4 = '4' _5 = '5' _6 = '6' _7 = '7' marketDataAsset = 'marketDataAsset' _8 = '8' _9 = '9' buy75cents = 'buy75cents' unadjustedHigh = 'unadjustedHigh' sourceImportance = 'sourceImportance' closingYield = 'closingYield' wind = 'wind' sc16 = 'sc16' sc15 = 'sc15' sc12 = 'sc12' sc11 = 'sc11' primaryVwapInLimitUnrealizedBps = 'primaryVwapInLimitUnrealizedBps' displayName = 'displayName' minutesToTrade100Pct = 'minutesToTrade100Pct' sc14 = 'sc14' cumulativeVolumeInShares = 'cumulativeVolumeInShares' sc13 = 'sc13' newFatalities = 'newFatalities' buy50bps = 'buy50bps' numStaffedBeds = 'numStaffedBeds' upfrontPayment = 'upfrontPayment' arrivalMidRealizedCash = 'arrivalMidRealizedCash' sc10 = 'sc10' sc05 = 'sc05' a = 'a' sc04 = 'sc04' b = 'b' sc07 = 'sc07' c = 'c' yieldToMaturity = 'yieldToMaturity' sc06 = 'sc06' address = 'address' sc01 = 'sc01' leg2PaymentFrequency = 'leg2PaymentFrequency' sc03 = 'sc03' sc02 = 'sc02' geographyName = 'geographyName' borrower = 'borrower' settlePrice = 'settlePrice' performanceContribution = 'performanceContribution' sc09 = 'sc09' mktClass = 'mktClass' sc08 = 'sc08' collateralization = 'collateralization' futureMonthU26 = 'futureMonthU26' futureMonthU25 = 'futureMonthU25' futureMonthU24 = 'futureMonthU24' futureMonthU23 = 'futureMonthU23' futureMonthU22 = 'futureMonthU22' statementId = 'statementId' futureMonthU21 = 'futureMonthU21' modifiedDuration = 'modifiedDuration' shortRatesContribution = 'shortRatesContribution' impliedNormalVolatility = 'impliedNormalVolatility' solarGeneration = 'solarGeneration' mtmPrice = 'mtmPrice' swapSpreadChange = 'swapSpreadChange' realizedArrivalPerformanceUSD = 'realizedArrivalPerformanceUSD' portfolioAssets = 'portfolioAssets' pricingdate = 'pricingdate' tcmCostHorizon3Hour = 'tcmCostHorizon3Hour' exchangeRate = 'exchangeRate' potentialBedCapInc = 'potentialBedCapInc' numberCovered = 'numberCovered' numberOfPositions = 'numberOfPositions' openUnadjusted = 'openUnadjusted' strikeTime = 'strikeTime' askPrice = 'askPrice' eventId = 'eventId' sectors = 'sectors' additionalPriceNotationType = 'additionalPriceNotationType' grossInvestmentQtd = 'grossInvestmentQtd' annualizedRisk = 'annualizedRisk' estimatedHoldingTimeShort = 'estimatedHoldingTimeShort' midcurvePremium = 'midcurvePremium' volumeComposite = 'volumeComposite' sharpeQtd = 'sharpeQtd' estimatedHoldingTimeLong = 'estimatedHoldingTimeLong' external = 'external' trackerName = 'trackerName' sell50cents = 'sell50cents' tradePrice = 'tradePrice' cleared = 'cleared' primeIdNumeric = 'primeIdNumeric' buy8bps = 'buy8bps' totalNotionalLocal = 'totalNotionalLocal' cid = 'cid' totalConfirmedSeniorHome = 'totalConfirmedSeniorHome' ctdFwdPrice = 'ctdFwdPrice' sinkFactor = 'sinkFactor' temperatureForecast = 'temperatureForecast' bidHigh = 'bidHigh' pnlQtd = 'pnlQtd' buy50cents = 'buy50cents' sell4bps = 'sell4bps' receiverDayCountFraction = 'receiverDayCountFraction' auctionClosePercentage = 'auctionClosePercentage' targetPrice = 'targetPrice' bosInBpsDescription = 'bosInBpsDescription' lowPrice = 'lowPrice' adv22DayPct = 'adv22DayPct' matchedMaturitySwapSpread12m = 'matchedMaturitySwapSpread12m' priceRangeInTicksLabel = 'priceRangeInTicksLabel' ticker = 'ticker' notionalUnit = 'notionalUnit' tcmCostHorizon1Day = 'tcmCostHorizon1Day' approval = 'approval' testMeasure = 'testMeasure' optionLockOutPeriod = 'optionLockOutPeriod' executionTime = 'executionTime' sourceValueForecast = 'sourceValueForecast' leg2Spread = 'leg2Spread' shortConvictionLarge = 'shortConvictionLarge' ccgName = 'ccgName' dollarExcessReturn = 'dollarExcessReturn' gsn = 'gsn' tradeEndDate = 'tradeEndDate' receiverRateOption = 'receiverRateOption' gss = 'gss' percentOfMediandv1m = 'percentOfMediandv1m' lendables = 'lendables' sell75cents = 'sell75cents' optionAdjustedSpread = 'optionAdjustedSpread' optionAdjustedSwapSpread = 'optionAdjustedSwapSpread' bosInTicksLabel = 'bosInTicksLabel' positionSourceId = 'positionSourceId' buy1bps = 'buy1bps' buy3point5bps = 'buy3point5bps' gsSustainRegion = 'gsSustainRegion' absoluteReturnWtd = 'absoluteReturnWtd' deploymentId = 'deploymentId' assetParametersSeniority = 'assetParametersSeniority' askSpread = 'askSpread' flow = 'flow' futureMonthH26 = 'futureMonthH26' loanRebate = 'loanRebate' futureMonthH25 = 'futureMonthH25' period = 'period' indexCreateSource = 'indexCreateSource' futureMonthH24 = 'futureMonthH24' futureMonthH23 = 'futureMonthH23' futureMonthH22 = 'futureMonthH22' futureMonthH21 = 'futureMonthH21' nonUsdOis = 'nonUsdOis' realTWIContribution = 'realTWIContribution' mktAsset = 'mktAsset' leg2IndexLocation = 'leg2IndexLocation' twapUnrealizedBps = 'twapUnrealizedBps' lastUpdatedMessage = 'lastUpdatedMessage' loanValue = 'loanValue' optionAdjustedOISSpread = 'optionAdjustedOISSpread' totalReturnPrice = 'totalReturnPrice' weightedPercentInModel = 'weightedPercentInModel' initLoanSpreadRequired = 'initLoanSpreadRequired' electionPeriod = 'electionPeriod' fundingAskPrice = 'fundingAskPrice' historicalBeta = 'historicalBeta' bondRiskPremiumIndex = 'bondRiskPremiumIndex' hitRateYtd = 'hitRateYtd' girGsdeerGsfeer = 'girGsdeerGsfeer' numUnits = 'numUnits' assetParametersReceiverFrequency = 'assetParametersReceiverFrequency' expenseRatioGrossBps = 'expenseRatioGrossBps' relativePayoffWtd = 'relativePayoffWtd' ctdPrice = 'ctdPrice' paceOfRollNow = 'paceOfRollNow' product = 'product' leg2ReturnType = 'leg2ReturnType' agentLenderFee = 'agentLenderFee' disseminationId = 'disseminationId' optionStrikePrice = 'optionStrikePrice' precipitationType = 'precipitationType' lowerBound = 'lowerBound' arrivalMidNormalized = 'arrivalMidNormalized' underlyingAsset2 = 'underlyingAsset2' underlyingAsset1 = 'underlyingAsset1' legalEntity = 'legalEntity' performanceFee = 'performanceFee' orderState = 'orderState' actualDataQuality = 'actualDataQuality' indexRatio = 'indexRatio' queueInLotsLabel = 'queueInLotsLabel' adv10DayPct = 'adv10DayPct' longConvictionMedium = 'longConvictionMedium' relativeHitRateWtd = 'relativeHitRateWtd' dailyTrackingError = 'dailyTrackingError' sell140cents = 'sell140cents' sell10bps = 'sell10bps' aggressiveOffsetFromLast = 'aggressiveOffsetFromLast' longitude = 'longitude' newIcu = 'newIcu' marketCap = 'marketCap' weightedAverageMid = 'weightedAverageMid' clusterRegion = 'clusterRegion' valoren = 'valoren' averageExecutionPrice = 'averageExecutionPrice' proceedsAssetOISSwapSpread1m = 'proceedsAssetOISSwapSpread1m' payoffWtd = 'payoffWtd' basis = 'basis' investmentRateTrend = 'investmentRateTrend' grossInvestmentMtd = 'grossInvestmentMtd' _200 = '200' hedgeId = 'hedgeId' _201 = '201' sharpeMtd = 'sharpeMtd' _202 = '202' _203 = '203' tcmCostHorizon8Day = 'tcmCostHorizon8Day' _204 = '204' residualVariance = 'residualVariance' _205 = '205' restrictInternalDerivedData = 'restrictInternalDerivedData' _206 = '206' _207 = '207' _208 = '208' adv5DayPct = 'adv5DayPct' _209 = '209' midpointFillsPercentage = 'midpointFillsPercentage' openInterest = 'openInterest' turnoverCompositeUnadjusted = 'turnoverCompositeUnadjusted' fwdPoints = 'fwdPoints' relativeReturnWtd = 'relativeReturnWtd' units = 'units' payerRateOption = 'payerRateOption' assetClassificationsRiskCountryName = 'assetClassificationsRiskCountryName' extMktPoint3 = 'extMktPoint3' _210 = '210' _211 = '211' matchedMaturitySwapSpread = 'matchedMaturitySwapSpread' _212 = '212' cityName = 'cityName' _213 = '213' hourlyBucket = 'hourlyBucket' _214 = '214' averageImpliedVolatility = 'averageImpliedVolatility' totalHospitalizedWithSymptoms = 'totalHospitalizedWithSymptoms' _215 = '215' _216 = '216' _217 = '217' daysOpenRealizedCash = 'daysOpenRealizedCash' _218 = '218' _219 = '219' adjustedHighPrice = 'adjustedHighPrice' proceedsAssetOISSwapSpread = 'proceedsAssetOISSwapSpread' extMktPoint1 = 'extMktPoint1' direction = 'direction' extMktPoint2 = 'extMktPoint2' subRegionCode = 'subRegionCode' assetParametersFixedRate = 'assetParametersFixedRate' isEstimatedReturn = 'isEstimatedReturn' valueForecast = 'valueForecast' totalIcu = 'totalIcu' positionSourceType = 'positionSourceType' previousCloseUnrealizedCash = 'previousCloseUnrealizedCash' minimumDenomination = 'minimumDenomination' futureValueNotional = 'futureValueNotional' participationRate = 'participationRate' obfr = 'obfr' _220 = '220' _221 = '221' _222 = '222' buy9point5bps = 'buy9point5bps' _223 = '223' _224 = '224' _225 = '225' optionLockPeriod = 'optionLockPeriod' _226 = '226' esMomentumPercentile = 'esMomentumPercentile' _227 = '227' _228 = '228' advPercentage = 'advPercentage' _229 = '229' leg1AveragingMethod = 'leg1AveragingMethod' turnoverComposite = 'turnoverComposite' forecastDate = 'forecastDate' internalIndexCalcRegion = 'internalIndexCalcRegion' positionType = 'positionType' subAssetClass = 'subAssetClass' shortInterest = 'shortInterest' referencePeriod = 'referencePeriod' adjustedVolume = 'adjustedVolume' ctdFwdYield = 'ctdFwdYield' secDB = 'secDB' memoryUsed = 'memoryUsed' bpeQualityStars = 'bpeQualityStars' _230 = '230' _231 = '231' _232 = '232' ctd = 'ctd' _233 = '233' _234 = '234' _235 = '235' _236 = '236' _237 = '237' _238 = '238' _239 = '239' intendedParticipationRate = 'intendedParticipationRate' leg1PaymentType = 'leg1PaymentType' tradingPnl = 'tradingPnl' collateralValueRequired = 'collateralValueRequired' buy45bps = 'buy45bps' priceToEarningsPositive = 'priceToEarningsPositive' forecast = 'forecast' forecastValue = 'forecastValue' _240 = '240' pnl = 'pnl' _241 = '241' _242 = '242' _243 = '243' volumeInLimit = 'volumeInLimit' _244 = '244' isTerritory = 'isTerritory' _245 = '245' leg2DeliveryPoint = 'leg2DeliveryPoint' _246 = '246' _247 = '247' _248 = '248' _249 = '249' tcmCostHorizon4Day = 'tcmCostHorizon4Day' styles = 'styles' shortName = 'shortName' resetFrequency1 = 'resetFrequency1' buy4bps = 'buy4bps' resetFrequency2 = 'resetFrequency2' otherPriceTerm = 'otherPriceTerm' bidGspread = 'bidGspread' openPrice = 'openPrice' psId = 'psId' hitRateMtd = 'hitRateMtd' fairVolatility = 'fairVolatility' dollarCross = 'dollarCross' portfolioType = 'portfolioType' currency = 'currency' clusterClass = 'clusterClass' sell50bps = 'sell50bps' futureMonthM21 = 'futureMonthM21' bidSize = 'bidSize' arrivalMid = 'arrivalMid' assetParametersExchangeCurrency = 'assetParametersExchangeCurrency' candidateName = 'candidateName' impliedLognormalVolatility = 'impliedLognormalVolatility' vwapInLimitUnrealizedCash = 'vwapInLimitUnrealizedCash' ratingMoodys = 'ratingMoodys' futureMonthM26 = 'futureMonthM26' futureMonthM25 = 'futureMonthM25' futureMonthM24 = 'futureMonthM24' futureMonthM23 = 'futureMonthM23' futureMonthM22 = 'futureMonthM22' flowPct = 'flowPct' source = 'source' assetClassificationsCountryCode = 'assetClassificationsCountryCode' settleDrop = 'settleDrop' dataSetSubCategory = 'dataSetSubCategory' sell9point5bps = 'sell9point5bps' quantityBucket = 'quantityBucket' optionStyleSDR = 'optionStyleSDR' oeName = 'oeName' given = 'given' leg2DayCountConvention = 'leg2DayCountConvention' liquidityScoreSell = 'liquidityScoreSell' delistingDate = 'delistingDate' weight = 'weight' accruedInterest = 'accruedInterest' businessScope = 'businessScope' wtdDegreeDays = 'wtdDegreeDays' absoluteWeight = 'absoluteWeight' measure = 'measure' temperatureHourlyForecast = 'temperatureHourlyForecast' icebergTipRateType = 'icebergTipRateType' sharpeYtd = 'sharpeYtd' windSpeedForecast = 'windSpeedForecast' grossInvestmentYtd = 'grossInvestmentYtd' yieldPrice = 'yieldPrice' leg1TotalNotionalUnit = 'leg1TotalNotionalUnit' issuePrice = 'issuePrice' askHigh = 'askHigh' expectedDataQuality = 'expectedDataQuality' regionName = 'regionName' valueRevised = 'valueRevised' discretionUpperBound = 'discretionUpperBound' adjustedTradePrice = 'adjustedTradePrice' forecastTime = 'forecastTime' isoSubdivisionCodeAlpha2 = 'isoSubdivisionCodeAlpha2' ctdConversionFactor = 'ctdConversionFactor' proceedsAssetSwapSpread = 'proceedsAssetSwapSpread' isADR = 'isADR' issueDate = 'issueDate' serviceId = 'serviceId' yes = 'yes' gScore = 'gScore' marketValue = 'marketValue' entityId = 'entityId' notionalCurrency1 = 'notionalCurrency1' netDebtToEbitda = 'netDebtToEbitda' numUnitsUpper = 'numUnitsUpper' notionalCurrency2 = 'notionalCurrency2' inLimitParticipationRate = 'inLimitParticipationRate' pressureForecast = 'pressureForecast' paid = 'paid' fixedRate = 'fixedRate' short = 'short' time = 'time' buy4point5bps = 'buy4point5bps' sell30cents = 'sell30cents' eventEndDateTime = 'eventEndDateTime' leg1PaymentFrequency = 'leg1PaymentFrequency' cmId = 'cmId' taxonomy = 'taxonomy' buy45cents = 'buy45cents' measures = 'measures' seasonalAdjustment = 'seasonalAdjustment' rankWtd = 'rankWtd' underlyer = 'underlyer' createdTime = 'createdTime' identifier = 'identifier' priceUnit = 'priceUnit' tradeReportRefId = 'tradeReportRefId' subdivisionId = 'subdivisionId' unadjustedLow = 'unadjustedLow' buy160cents = 'buy160cents' portfolioId = 'portfolioId' zSpread = 'zSpread' capFloorAtmFwdRate = 'capFloorAtmFwdRate' esPercentile = 'esPercentile' tdapi = 'tdapi' locationCode = 'locationCode' rcic = 'rcic' nameRaw = 'nameRaw' simonAssetTags = 'simonAssetTags' hitRateQtd = 'hitRateQtd' primaryVolumeInLimit = 'primaryVolumeInLimit' precipitationDailyForecastPercent = 'precipitationDailyForecastPercent' aumEnd = 'aumEnd' premium = 'premium' low = 'low' crossGroup = 'crossGroup' reportRunTime = 'reportRunTime' fiveDayPriceChangeBps = 'fiveDayPriceChangeBps' holdings = 'holdings' precipitationDailyForecast = 'precipitationDailyForecast' priceMethod = 'priceMethod' assetParametersFixedRateFrequency = 'assetParametersFixedRateFrequency' oisXccy = 'oisXccy' daysOpen = 'daysOpen' buy110cents = 'buy110cents' averageSpreadBps = 'averageSpreadBps' buy55cents = 'buy55cents' futureMonthQ26 = 'futureMonthQ26' issueSize = 'issueSize' futureMonthQ25 = 'futureMonthQ25' futureMonthQ24 = 'futureMonthQ24' futureMonthQ23 = 'futureMonthQ23' futureMonthQ22 = 'futureMonthQ22' pendingLoanCount = 'pendingLoanCount' futureMonthQ21 = 'futureMonthQ21' priceSpotStopLossUnit = 'priceSpotStopLossUnit' priceRangeInTicksDescription = 'priceRangeInTicksDescription' tradeVolume = 'tradeVolume' primaryCountryRic = 'primaryCountryRic' optionExpirationFrequency = 'optionExpirationFrequency' isActive = 'isActive' useMachineLearning = 'useMachineLearning' growthScore = 'growthScore' bufferThreshold = 'bufferThreshold' buy120cents = 'buy120cents' matchedMaturitySwapRate = 'matchedMaturitySwapRate' primaryVwap = 'primaryVwap' exchangeTypeId = 'exchangeTypeId' basisSwapRate = 'basisSwapRate' exchangeCode = 'exchangeCode' group = 'group' assetParametersTerminationDate = 'assetParametersTerminationDate' estimatedSpread = 'estimatedSpread' yieldChangeOnDay = 'yieldChangeOnDay' created = 'created' autoTags = 'autoTags' tcmCost = 'tcmCost' sustainJapan = 'sustainJapan' historyStartDate = 'historyStartDate' bidSpread = 'bidSpread' percentageComplete = 'percentageComplete' hedgeTrackingError = 'hedgeTrackingError' windSpeedType = 'windSpeedType' strikePrice = 'strikePrice' parAssetSwapSpread12m = 'parAssetSwapSpread12m' tradeReportId = 'tradeReportId' adjustedOpenPrice = 'adjustedOpenPrice' countryId = 'countryId' point = 'point' pnlMtd = 'pnlMtd' totalReturns = 'totalReturns' lender = 'lender' annReturn1Year = 'annReturn1Year' ctdFwdDv01 = 'ctdFwdDv01' effYield7Day = 'effYield7Day' meetingDate = 'meetingDate' calendarSpreadMispricing = 'calendarSpreadMispricing' buy140cents = 'buy140cents' priceNotation2Type = 'priceNotation2Type' fundFocus = 'fundFocus' relativeStrike = 'relativeStrike' flagship = 'flagship' additionalPriceNotation = 'additionalPriceNotation' factorCategory = 'factorCategory' equityDelta = 'equityDelta' grossWeight = 'grossWeight' listed = 'listed' sell7bps = 'sell7bps' earningsRecordType = 'earningsRecordType' mean = 'mean' askYield = 'askYield' shockStyle = 'shockStyle' methodology = 'methodology' buy25cents = 'buy25cents' amountOutstanding = 'amountOutstanding' marketPnl = 'marketPnl' sustainAsiaExJapan = 'sustainAsiaExJapan' sell6point5bps = 'sell6point5bps' neighbourAssetId = 'neighbourAssetId' countIdeasYtd = 'countIdeasYtd' simonIntlAssetTags = 'simonIntlAssetTags' path = 'path' vwapUnrealizedCash = 'vwapUnrealizedCash' payoffMtd = 'payoffMtd' bosInBpsLabel = 'bosInBpsLabel' bosInBps = 'bosInBps' pointClass = 'pointClass' fxSpot = 'fxSpot' restrictNamedIndividuals = 'restrictNamedIndividuals' hedgeVolatility = 'hedgeVolatility' tags = 'tags' population = 'population' underlyingAssetId = 'underlyingAssetId' realLongRatesContribution = 'realLongRatesContribution' pctprices_return = 'pctprices_return' domain = 'domain' buy80cents = 'buy80cents' forwardTenor = 'forwardTenor' averagePrice = 'averagePrice' targetPriceRealizedBps = 'targetPriceRealizedBps' leg2FixedRate = 'leg2FixedRate' shareClassAssets = 'shareClassAssets' annuity = 'annuity' totalCount = 'totalCount' quoteType = 'quoteType' corporateActionStatus = 'corporateActionStatus' peggedTipSize = 'peggedTipSize' uid = 'uid' esPolicyPercentile = 'esPolicyPercentile' usdOis = 'usdOis' term = 'term' restrictInternalGsNtk = 'restrictInternalGsNtk' tcmCostParticipationRate100Pct = 'tcmCostParticipationRate100Pct' relativeUniverse = 'relativeUniverse' measureIdx = 'measureIdx' fredId = 'fredId' twiContribution = 'twiContribution' cloudCoverType = 'cloudCoverType' delisted = 'delisted' regionalFocus = 'regionalFocus' volumePrimary = 'volumePrimary' assetParametersPayerDesignatedMaturity = 'assetParametersPayerDesignatedMaturity' buy30cents = 'buy30cents' fundingBidPrice = 'fundingBidPrice' series = 'series' sell3bps = 'sell3bps' settlementPrice = 'settlementPrice' quarter = 'quarter' sell18bps = 'sell18bps' assetParametersFloatingRateOption = 'assetParametersFloatingRateOption' realizedVwapPerformanceBps = 'realizedVwapPerformanceBps' voteShare = 'voteShare' servicingCostShortPnl = 'servicingCostShortPnl' totalConfirmed = 'totalConfirmed' economicForecast = 'economicForecast' plotId = 'plotId' clusterDescription = 'clusterDescription' concentrationLimit = 'concentrationLimit' windSpeed = 'windSpeed' observationHour = 'observationHour' signal = 'signal' borrowerId = 'borrowerId' dataProduct = 'dataProduct' buy7point5bps = 'buy7point5bps' limitPrice = 'limitPrice' bmPrimeId = 'bmPrimeId' dataType = 'dataType' count = 'count' conviction = 'conviction' rfqstate = 'rfqstate' benchmarkMaturity = 'benchmarkMaturity' grossFlowNormalized = 'grossFlowNormalized' buy14bps = 'buy14bps' factorId = 'factorId' futureMonthV26 = 'futureMonthV26' stsFxCurrency = 'stsFxCurrency' futureMonthV25 = 'futureMonthV25' bidChange = 'bidChange' month = 'month' futureMonthV24 = 'futureMonthV24' investmentWtd = 'investmentWtd' futureMonthV23 = 'futureMonthV23' futureMonthV22 = 'futureMonthV22' futureMonthV21 = 'futureMonthV21' expiration = 'expiration' leg2ResetFrequency = 'leg2ResetFrequency' controversyScore = 'controversyScore' proceedAssetSwapSpread = 'proceedAssetSwapSpread' concentrationLevel = 'concentrationLevel' importance = 'importance' assetClassificationsGicsSector = 'assetClassificationsGicsSector' stsAssetName = 'stsAssetName' netExposureClassification = 'netExposureClassification' settlementMethod = 'settlementMethod' receiverDesignatedMaturity = 'receiverDesignatedMaturity' title = 'title' xRefTypeId = 'xRefTypeId' duration = 'duration' load = 'load' alpha = 'alpha' company = 'company' settlementFrequency = 'settlementFrequency' distAvg7Day = 'distAvg7Day' inRiskModel = 'inRiskModel' dailyNetShareholderFlowsPercent = 'dailyNetShareholderFlowsPercent' filledNotionalLocal = 'filledNotionalLocal' everHospitalized = 'everHospitalized' meetingNumber = 'meetingNumber' midGspread = 'midGspread' daysOpenUnrealizedBps = 'daysOpenUnrealizedBps' longLevel = 'longLevel' dataDescription = 'dataDescription' temperatureType = 'temperatureType' gsideid = 'gsideid' repoRate = 'repoRate' division = 'division' cloudCoverDailyForecast = 'cloudCoverDailyForecast' windSpeedDailyForecast = 'windSpeedDailyForecast' assetParametersFloatingRateDayCountFraction = 'assetParametersFloatingRateDayCountFraction' tradeAction = 'tradeAction' action = 'action' ctdYield = 'ctdYield' arrivalHaircutVwapNormalized = 'arrivalHaircutVwapNormalized' priceComponent = 'priceComponent' queueClockTimeDescription = 'queueClockTimeDescription' assetParametersReceiverDayCountFraction = 'assetParametersReceiverDayCountFraction' percentMidExecutionQuantity = 'percentMidExecutionQuantity' deltaStrike = 'deltaStrike' cloudCover = 'cloudCover' assetParametersNotionalCurrency = 'assetParametersNotionalCurrency' buy18bps = 'buy18bps' valueActual = 'valueActual' upi = 'upi' collateralCurrency = 'collateralCurrency' originalCountry = 'originalCountry' field = 'field' geographicFocus = 'geographicFocus' daysOpenRealizedBps = 'daysOpenRealizedBps' fxRiskPremiumIndex = 'fxRiskPremiumIndex' skew = 'skew' status = 'status' notionalCurrency = 'notionalCurrency' sustainEmergingMarkets = 'sustainEmergingMarkets' eventDateTime = 'eventDateTime' leg1DesignatedMaturity = 'leg1DesignatedMaturity' totalPrice = 'totalPrice' onBehalfOf = 'onBehalfOf' testType = 'testType' accruedInterestStandard = 'accruedInterestStandard' futureMonthZ26 = 'futureMonthZ26' futureMonthZ25 = 'futureMonthZ25' ccgCode = 'ccgCode' shortExposure = 'shortExposure' leg1FixedPaymentCurrency = 'leg1FixedPaymentCurrency' arrivalHaircutVwap = 'arrivalHaircutVwap' executionDays = 'executionDays' recallDueDate = 'recallDueDate' forward = 'forward' strike = 'strike' spreadLimit = 'spreadLimit' productScope = 'productScope' assetParametersIssuerType = 'assetParametersIssuerType' currency1 = 'currency1' currency2 = 'currency2' previousCloseRealizedBps = 'previousCloseRealizedBps' daysSinceReported = 'daysSinceReported' eventStatus = 'eventStatus' vwapInLimit = 'vwapInLimit' fwdDuration = 'fwdDuration' _return = 'return' isPairBasket = 'isPairBasket' notionalAmount = 'notionalAmount' payOrReceive = 'payOrReceive' totalSevere = 'totalSevere' unexecutedNotionalUSD = 'unexecutedNotionalUSD' expectedResidualPercentage = 'expectedResidualPercentage' maturityDate = 'maturityDate' traceAdvSell = 'traceAdvSell' eventName = 'eventName' addressLine2 = 'addressLine2' indicationOfOtherPriceAffectingTerm = 'indicationOfOtherPriceAffectingTerm' unadjustedBid = 'unadjustedBid' backtestType = 'backtestType' gsdeer = 'gsdeer' assetParametersIssuer = 'assetParametersIssuer' gRegionalPercentile = 'gRegionalPercentile' coverageChecked = 'coverageChecked' oisXccyExSpike = 'oisXccyExSpike' totalRisk = 'totalRisk' mnav = 'mnav' marketVolume = 'marketVolume' swapAnnuity = 'swapAnnuity' parAssetSwapSpread = 'parAssetSwapSpread' currYield7Day = 'currYield7Day' pressure = 'pressure' shortDescription = 'shortDescription' futureMonthZ24 = 'futureMonthZ24' feed = 'feed' futureMonthZ23 = 'futureMonthZ23' mktPoint1 = 'mktPoint1' futureMonthZ22 = 'futureMonthZ22' futureMonthZ21 = 'futureMonthZ21' futureMonthZ20 = 'futureMonthZ20' assetParametersCommoditySector = 'assetParametersCommoditySector' priceNotation2 = 'priceNotation2' marketBufferThreshold = 'marketBufferThreshold' priceNotation3 = 'priceNotation3' mktPoint3 = 'mktPoint3' mktPoint2 = 'mktPoint2' leg2Type = 'leg2Type' mktPoint4 = 'mktPoint4' degreeDaysType = 'degreeDaysType' sentiment = 'sentiment' investmentIncome = 'investmentIncome' groupType = 'groupType' forwardPointImm = 'forwardPointImm' twap = 'twap' clientShortName = 'clientShortName' groupCategory = 'groupCategory' bidPlusAsk = 'bidPlusAsk' foreignCcyRate = 'foreignCcyRate' electionOdds = 'electionOdds' windDirectionForecast = 'windDirectionForecast' requireAnonClientName = 'requireAnonClientName' pricingLocation = 'pricingLocation' beta = 'beta' lastReturnsEndDate = 'lastReturnsEndDate' upfrontPaymentDate = 'upfrontPaymentDate' sell1point5bps = 'sell1point5bps' longExposure = 'longExposure' sell4point5bps = 'sell4point5bps' tcmCostParticipationRate20Pct = 'tcmCostParticipationRate20Pct' venueType = 'venueType' multiAssetClassSwap = 'multiAssetClassSwap' deltaChangeId = 'deltaChangeId' implementationId = 'implementationId' leg1FixedPayment = 'leg1FixedPayment' esNumericScore = 'esNumericScore' inBenchmark = 'inBenchmark' actionSDR = 'actionSDR' countIdeasQtd = 'countIdeasQtd' knockOutPrice = 'knockOutPrice' ctdAssetId = 'ctdAssetId' buy10bps = 'buy10bps' precipitation = 'precipitation' valueType = 'valueType' betaAdjustedNetExposure = 'betaAdjustedNetExposure' estimatedRodVolume = 'estimatedRodVolume' sell14bps = 'sell14bps' _10 = '10' _11 = '11' _12 = '12' _13 = '13' excessReturnPrice = 'excessReturnPrice' _14 = '14' _15 = '15' _16 = '16' _17 = '17' _18 = '18' _19 = '19' fxPnl = 'fxPnl' assetClassificationsGicsIndustryGroup = 'assetClassificationsGicsIndustryGroup' lendingSecId = 'lendingSecId' dollarDuration = 'dollarDuration' equityTheta = 'equityTheta' dv01 = 'dv01' startDate = 'startDate' _20 = '20' _21 = '21' _22 = '22' _23 = '23' mixedSwap = 'mixedSwap' swaptionPremium = 'swaptionPremium' _24 = '24' _25 = '25' _26 = '26' snowfall = 'snowfall' liquidityBucketBuy = 'liquidityBucketBuy' _27 = '27' mic = 'mic' _28 = '28' latitude = 'latitude' _29 = '29' mid = 'mid' impliedRepo = 'impliedRepo' long = 'long' firstExecutionTime = 'firstExecutionTime' coveredBond = 'coveredBond' regionCode = 'regionCode' buy20cents = 'buy20cents' longWeight = 'longWeight' calculationTime = 'calculationTime' liquidityBucketSell = 'liquidityBucketSell' daysOpenUnrealizedCash = 'daysOpenUnrealizedCash' temperature = 'temperature' averageRealizedVariance = 'averageRealizedVariance' ratingFitch = 'ratingFitch' financialReturnsScore = 'financialReturnsScore' yearOrQuarter = 'yearOrQuarter' _30 = '30' _31 = '31' _32 = '32' nonSymbolDimensions = 'nonSymbolDimensions' _33 = '33' commoditiesForecast = 'commoditiesForecast' _34 = '34' _35 = '35' covid19ByState = 'covid19ByState' _36 = '36' _37 = '37' _38 = '38' _39 = '39' percentageExpectedResidual = 'percentageExpectedResidual' hospitalName = 'hospitalName' buy90cents = 'buy90cents' periodType = 'periodType' assetClassificationsCountryName = 'assetClassificationsCountryName' totalHospitalized = 'totalHospitalized' peggedRefillInterval = 'peggedRefillInterval' fatalitiesProbable = 'fatalitiesProbable' _40 = '40' administrativeRegion = 'administrativeRegion' _41 = '41' open = 'open' _42 = '42' _43 = '43' _44 = '44' _45 = '45' cusip = 'cusip' totalConfirmedByState = 'totalConfirmedByState' _46 = '46' ideaActivityTime = 'ideaActivityTime' _47 = '47' _48 = '48' _49 = '49' windAttribute = 'windAttribute' spreadOptionAtmFwdRate = 'spreadOptionAtmFwdRate' netExposure = 'netExposure' isLegacyPairBasket = 'isLegacyPairBasket' issuerType = 'issuerType' buy70cents = 'buy70cents' strikeReference = 'strikeReference' assetCount = 'assetCount' _50 = '50' _51 = '51' isOrderInLimit = 'isOrderInLimit' _52 = '52' _53 = '53' _54 = '54' fundamentalMetric = 'fundamentalMetric' _55 = '55' _56 = '56' quoteStatusId = 'quoteStatusId' _57 = '57' absoluteValue = 'absoluteValue' closingReport = 'closingReport' _58 = '58' previousTotalConfirmed = 'previousTotalConfirmed' _59 = '59' longTenor = 'longTenor' multiplier = 'multiplier' buy40cents = 'buy40cents' assetCountPriced = 'assetCountPriced' voteDirection = 'voteDirection' impliedRepoRate = 'impliedRepoRate' settlementCurrency = 'settlementCurrency' wtdDegreeDaysForecast = 'wtdDegreeDaysForecast' indicationOfCollateralization = 'indicationOfCollateralization' futureMonthN26 = 'futureMonthN26' _60 = '60' lendingPartnerFee = 'lendingPartnerFee' futureMonthN25 = 'futureMonthN25' _61 = '61' futureMonthN24 = 'futureMonthN24' _62 = '62' primaryVwapRealizedBps = 'primaryVwapRealizedBps' futureMonthN23 = 'futureMonthN23' _63 = '63' futureMonthN22 = 'futureMonthN22' _64 = '64' futureMonthN21 = 'futureMonthN21' _65 = '65' _66 = '66' _67 = '67' _68 = '68' _69 = '69' breakEvenInflation = 'breakEvenInflation' pnlYtd = 'pnlYtd' leg1ReturnType = 'leg1ReturnType' tenor2 = 'tenor2' resetFrequency = 'resetFrequency' assetParametersPayerFrequency = 'assetParametersPayerFrequency' degreeDaysForecast = 'degreeDaysForecast' isManuallySilenced = 'isManuallySilenced' buy3bps = 'buy3bps' lastUpdatedById = 'lastUpdatedById' legalEntityAcct = 'legalEntityAcct' targetShareholderMeetingDate = 'targetShareholderMeetingDate' _70 = '70' _71 = '71' _72 = '72' paceOfRollp0 = 'paceOfRollp0' _73 = '73' _74 = '74' controversyPercentile = 'controversyPercentile' leg1NotionalCurrency = 'leg1NotionalCurrency' _75 = '75' complianceEffectiveTime = 'complianceEffectiveTime' expirationDate = 'expirationDate' _76 = '76' _77 = '77' _78 = '78' _79 = '79' floatingRateDayCountFraction = 'floatingRateDayCountFraction' callLastDate = 'callLastDate' factorReturn = 'factorReturn' passiveFlowRatio = 'passiveFlowRatio' composite5DayAdv = 'composite5DayAdv' marginalContributionToRisk = 'marginalContributionToRisk' closeDate = 'closeDate' temperatureHourForecast = 'temperatureHourForecast' newIdeasWtd = 'newIdeasWtd' assetClassSDR = 'assetClassSDR' yieldToWorst = 'yieldToWorst' _80 = '80' closingPrice = 'closingPrice' _81 = '81' turnoverCompositeAdjusted = 'turnoverCompositeAdjusted' comment = 'comment' sourceSymbol = 'sourceSymbol' _82 = '82' _83 = '83' _84 = '84' askUnadjusted = 'askUnadjusted' _85 = '85' _86 = '86' restrictExternalDerivedData = 'restrictExternalDerivedData' _87 = '87' _88 = '88' _89 = '89' askChange = 'askChange' countIdeasMtd = 'countIdeasMtd' endDate = 'endDate' sunshine = 'sunshine' contractType = 'contractType' momentumType = 'momentumType' specificRisk = 'specificRisk' mdapi = 'mdapi' payoffQtd = 'payoffQtd' loss = 'loss' midcurveVol = 'midcurveVol' sell6bps = 'sell6bps' tradingCostPnl = 'tradingCostPnl' priceNotationType = 'priceNotationType' price = 'price' paymentQuantity = 'paymentQuantity' _90 = '90' _91 = '91' _92 = '92' _93 = '93' _94 = '94' _95 = '95' _96 = '96' _97 = '97' _98 = '98' redemptionDate = 'redemptionDate' _99 = '99' leg2NotionalCurrency = 'leg2NotionalCurrency' subRegion = 'subRegion' benchmark = 'benchmark' tcmCostParticipationRate15Pct = 'tcmCostParticipationRate15Pct' fiscalYear = 'fiscalYear' recallDate = 'recallDate' esgMetricValue = 'esgMetricValue' internal = 'internal' gender = 'gender' assetClassificationsGicsIndustry = 'assetClassificationsGicsIndustry' adjustedBidPrice = 'adjustedBidPrice' lowUnadjusted = 'lowUnadjusted' MACSSecondaryAssetClass = 'MACSSecondaryAssetClass' confirmedPerMillion = 'confirmedPerMillion' dataSourceId = 'dataSourceId' integratedScore = 'integratedScore' buy7bps = 'buy7bps' arrivalMidUnrealizedCash = 'arrivalMidUnrealizedCash' knockInPrice = 'knockInPrice' event = 'event' isIntradayAuction = 'isIntradayAuction' locationName = 'locationName' coupon = 'coupon' percentageAuctionExecutedQuantity = 'percentageAuctionExecutedQuantity' avgYield7Day = 'avgYield7Day' originalDisseminationId = 'originalDisseminationId' totalOnVent = 'totalOnVent' twapUnrealizedCash = 'twapUnrealizedCash' stsCreditMarket = 'stsCreditMarket' onsCode = 'onsCode' passiveTouchFillsPercentage = 'passiveTouchFillsPercentage' seniority = 'seniority' leg1Index = 'leg1Index' highUnadjusted = 'highUnadjusted' submissionEvent = 'submissionEvent' TVProductMnemonic = 'TVProductMnemonic' avgTradeRateLabel = 'avgTradeRateLabel' lastActivityDate = 'lastActivityDate' disseminationTime = 'disseminationTime' priceToCash = 'priceToCash' buy10cents = 'buy10cents' navSpread = 'navSpread' venueMIC = 'venueMIC' dollarTotalReturn = 'dollarTotalReturn' blockUnit = 'blockUnit' midSpread = 'midSpread' istatProvinceCode = 'istatProvinceCode' totalRecoveredByState = 'totalRecoveredByState' repurchaseRate = 'repurchaseRate' dataSource = 'dataSource' totalBeingTested = 'totalBeingTested' clearedOrBilateral = 'clearedOrBilateral' metricName = 'metricName' askGspread = 'askGspread' forecastHour = 'forecastHour' leg2PaymentType = 'leg2PaymentType' calSpreadMisPricing = 'calSpreadMisPricing' totalTestedNegative = 'totalTestedNegative' rate366 = 'rate366' platform = 'platform' rate365 = 'rate365' fixedRateFrequency = 'fixedRateFrequency' rate360 = 'rate360' isContinuous = 'isContinuous' value = 'value' payerDesignatedMaturity = 'payerDesignatedMaturity' productType = 'productType' mdv22Day = 'mdv22Day' twapRealizedBps = 'twapRealizedBps' testMeasureLabel = 'testMeasureLabel' quantity = 'quantity' reportId = 'reportId' indexWeight = 'indexWeight' MACSPrimaryAssetClass = 'MACSPrimaryAssetClass' trader = 'trader' leg2PriceType = 'leg2PriceType' totalActive = 'totalActive' gsid2 = 'gsid2' matchedMaturityOISSwapSpread = 'matchedMaturityOISSwapSpread' valuationDate = 'valuationDate' restrictGsFederation = 'restrictGsFederation' positionSource = 'positionSource' tcmCostHorizon6Hour = 'tcmCostHorizon6Hour' buy200cents = 'buy200cents' vwapUnrealizedBps = 'vwapUnrealizedBps' priceToBook = 'priceToBook' isin = 'isin' plId = 'plId' lastReturnsStartDate = 'lastReturnsStartDate' collateralValueVariance = 'collateralValueVariance' year = 'year' forecastPeriod = 'forecastPeriod' callFirstDate = 'callFirstDate' dataSetIds = 'dataSetIds' economicTermsHash = 'economicTermsHash' numBeds = 'numBeds' sell20bps = 'sell20bps' clientType = 'clientType' percentageCloseExecutedQuantity = 'percentageCloseExecutedQuantity' macaulayDuration = 'macaulayDuration' availableInventory = 'availableInventory' est1DayCompletePct = 'est1DayCompletePct' relativeHitRateYtd = 'relativeHitRateYtd' createdById = 'createdById' marketDataType = 'marketDataType' realShortRatesContribution = 'realShortRatesContribution' metricCategory = 'metricCategory' annualizedCarry = 'annualizedCarry' valuePrevious = 'valuePrevious' transmissionClassification = 'transmissionClassification' avgTradeRate = 'avgTradeRate' shortLevel = 'shortLevel' version = 'version' categoryType = 'categoryType' policyRateExpectation = 'policyRateExpectation' uploadDate = 'uploadDate' blockOffFacility = 'blockOffFacility' unrealizedVwapPerformanceUSD = 'unrealizedVwapPerformanceUSD' paceOfRollp75 = 'paceOfRollp75' earningsPerSharePositive = 'earningsPerSharePositive' numIcuBeds = 'numIcuBeds' bucketVolumeInPercentage = 'bucketVolumeInPercentage' estimatedTradingCost = 'estimatedTradingCost' eid = 'eid' relativeReturnQtd = 'relativeReturnQtd' assessedTestMeasure = 'assessedTestMeasure' mktQuotingStyle = 'mktQuotingStyle' expirationTenor = 'expirationTenor' priceLimit = 'priceLimit' marketModelId = 'marketModelId' receiverFrequency = 'receiverFrequency' realizedCorrelation = 'realizedCorrelation' issueStatus = 'issueStatus' collateralValueActual = 'collateralValueActual' atmFwdRate = 'atmFwdRate' tcmCostParticipationRate75Pct = 'tcmCostParticipationRate75Pct' close = 'close' esProductImpactScore = 'esProductImpactScore' equityVega = 'equityVega' executedFillQuantity = 'executedFillQuantity' lenderPayment = 'lenderPayment' fiveDayMove = 'fiveDayMove' valueFormat = 'valueFormat' windChillForecast = 'windChillForecast' targetNotional = 'targetNotional' fillLegId = 'fillLegId' rationale = 'rationale' realizedTwapPerformanceBps = 'realizedTwapPerformanceBps' lastUpdatedSince = 'lastUpdatedSince' totalTests = 'totalTests' equitiesContribution = 'equitiesContribution' simonId = 'simonId' congestion = 'congestion' notes = 'notes' totalProbableSeniorHome = 'totalProbableSeniorHome' eventCategory = 'eventCategory' averageFillRate = 'averageFillRate' unadjustedOpen = 'unadjustedOpen' criticality = 'criticality' bidAskSpread = 'bidAskSpread' arrivalMidUnrealizedBps = 'arrivalMidUnrealizedBps' optionType = 'optionType' terminationDate = 'terminationDate' queriesPerSecond = 'queriesPerSecond' liquidityType = 'liquidityType' creditLimit = 'creditLimit' rankQtd = 'rankQtd' combinedKey = 'combinedKey' girFxForecast = 'girFxForecast' effectiveTenor = 'effectiveTenor' girCommoditiesForecast = 'girCommoditiesForecast' relativeHumidityDailyForecast = 'relativeHumidityDailyForecast' std30DaysSubsidizedYield = 'std30DaysSubsidizedYield' annualizedTrackingError = 'annualizedTrackingError' futureMonthF26 = 'futureMonthF26' futureMonthF25 = 'futureMonthF25' volSwap = 'volSwap' futureMonthF24 = 'futureMonthF24' heatIndexDailyForecast = 'heatIndexDailyForecast' futureMonthF23 = 'futureMonthF23' realFCI = 'realFCI' blockTradesAndLargeNotionalOffFacilitySwaps = 'blockTradesAndLargeNotionalOffFacilitySwaps' futureMonthF22 = 'futureMonthF22' buy1point5bps = 'buy1point5bps' futureMonthF21 = 'futureMonthF21' expirationSettlementDate = 'expirationSettlementDate' absoluteReturnQtd = 'absoluteReturnQtd' grossExposure = 'grossExposure' volume = 'volume' adv = 'adv' shortConvictionMedium = 'shortConvictionMedium' completeTestMeasure = 'completeTestMeasure' exchange = 'exchange' esPolicyScore = 'esPolicyScore' rollVolumeStd = 'rollVolumeStd' temperatureDailyForecast = 'temperatureDailyForecast' relativePayoffQtd = 'relativePayoffQtd' onLoanPercentage = 'onLoanPercentage' twapRemainingSlices = 'twapRemainingSlices' fairVariance = 'fairVariance' hitRateWtd = 'hitRateWtd' previousCloseRealizedCash = 'previousCloseRealizedCash' realizedVolatility = 'realizedVolatility' unexecutedQuantity = 'unexecutedQuantity' proceedsAssetSwapSpread1m = 'proceedsAssetSwapSpread1m' cloneParentId = 'cloneParentId' windSpeedHourlyForecast = 'windSpeedHourlyForecast' etfFlowRatio = 'etfFlowRatio' assetParametersReceiverRateOption = 'assetParametersReceiverRateOption' buy60cents = 'buy60cents' securitySubTypeId = 'securitySubTypeId' message = 'message' stsRatesCountry = 'stsRatesCountry' sell65cents = 'sell65cents' horizon = 'horizon' wouldIfGoodLevel = 'wouldIfGoodLevel' bufferThresholdRequired = 'bufferThresholdRequired' faceValue = 'faceValue' rollVolumeHist = 'rollVolumeHist' counterPartyStatus = 'counterPartyStatus' composite22DayAdv = 'composite22DayAdv' percentageFarExecutedQuantity = 'percentageFarExecutedQuantity' loanSpreadRequired = 'loanSpreadRequired' assetClass = 'assetClass' sovereignSpreadContribution = 'sovereignSpreadContribution' ric = 'ric' bucketEndTime = 'bucketEndTime' rateType = 'rateType' totalFatalitiesSeniorHome = 'totalFatalitiesSeniorHome' loanStatus = 'loanStatus' shortWeight = 'shortWeight' geographyId = 'geographyId' sell7point5bps = 'sell7point5bps' nav = 'nav' fiscalQuarter = 'fiscalQuarter' versionString = 'versionString' payoffYtd = 'payoffYtd' marketImpact = 'marketImpact' eventType = 'eventType' fillPrice = 'fillPrice' assetCountLong = 'assetCountLong' sell180cents = 'sell180cents' spot = 'spot' applicationId = 'applicationId' indicativeClosePrice = 'indicativeClosePrice' swapSpread = 'swapSpread' tradingRestriction = 'tradingRestriction' assetParametersPayOrReceive = 'assetParametersPayOrReceive' priceSpotEntryUnit = 'priceSpotEntryUnit' unrealizedArrivalPerformanceBps = 'unrealizedArrivalPerformanceBps' city = 'city' pnlWtd = 'pnlWtd' covariance = 'covariance' bucketVolumeInShares = 'bucketVolumeInShares' commodityForecast = 'commodityForecast' valid = 'valid' stsCommodity = 'stsCommodity' initialPricingDate = 'initialPricingDate' indicationOfEndUserException = 'indicationOfEndUserException' windDirectionHourlyForecast = 'windDirectionHourlyForecast' esScore = 'esScore' _yield = 'yield' fatalitiesUnderlyingConditionsPresent = 'fatalitiesUnderlyingConditionsPresent' priceRangeInTicks = 'priceRangeInTicks' paceOfRollp25 = 'paceOfRollp25' dayCloseRealizedUSD = 'dayCloseRealizedUSD' pctChange = 'pctChange' brightnessType = 'brightnessType' futureMonth3M = 'futureMonth3M' numberOfRolls = 'numberOfRolls' isoCountryCodeNumeric = 'isoCountryCodeNumeric' priceType = 'priceType' realizedVwapPerformanceUSD = 'realizedVwapPerformanceUSD' fuelType = 'fuelType' bbid = 'bbid' vegaNotionalAmount = 'vegaNotionalAmount' fatalitiesUnderlyingConditionsAbsent = 'fatalitiesUnderlyingConditionsAbsent' effectiveDate = 'effectiveDate' capped = 'capped' rating = 'rating' optionCurrency = 'optionCurrency' isCloseAuction = 'isCloseAuction' volatility = 'volatility' avgVentUtil = 'avgVentUtil' underlyingAssetIds = 'underlyingAssetIds' buy6point5bps = 'buy6point5bps' vwapInLimitRealizedCash = 'vwapInLimitRealizedCash' estimatedClosingAuctionVolume = 'estimatedClosingAuctionVolume' sell2bps = 'sell2bps' annualRisk = 'annualRisk' eti = 'eti' vwapInLimitRealizedBps = 'vwapInLimitRealizedBps' rankMtd = 'rankMtd' marketBuffer = 'marketBuffer' futureMonthJ24 = 'futureMonthJ24' lastUploadedTime = 'lastUploadedTime' futureMonthJ23 = 'futureMonthJ23' oeId = 'oeId' futureMonthJ22 = 'futureMonthJ22' futureMonthJ21 = 'futureMonthJ21' bbidEquivalent = 'bbidEquivalent' initBufferThresholdRequired = 'initBufferThresholdRequired' leg2DesignatedMaturity = 'leg2DesignatedMaturity' matchedMaturityOISSwapRate = 'matchedMaturityOISSwapRate' fairPrice = 'fairPrice' participationRateInLimit = 'participationRateInLimit' extMktClass = 'extMktClass' priceCurrency = 'priceCurrency' failedCount = 'failedCount' leg1IndexLocation = 'leg1IndexLocation' supraStrategy = 'supraStrategy' dayCountConvention = 'dayCountConvention' roundedNotionalAmount1 = 'roundedNotionalAmount1' roundedNotionalAmount2 = 'roundedNotionalAmount2' factorSource = 'factorSource' futureMonthJ26 = 'futureMonthJ26' lendingSecType = 'lendingSecType' futureMonthJ25 = 'futureMonthJ25' leverage = 'leverage' forecastDay = 'forecastDay' optionFamily = 'optionFamily' generatorOutput = 'generatorOutput' priceSpotStopLossValue = 'priceSpotStopLossValue' kpiId = 'kpiId' windGeneration = 'windGeneration' percentageMidExecutedQuantity = 'percentageMidExecutedQuantity' borrowCost = 'borrowCost' knockOutDirection = 'knockOutDirection' riskModel = 'riskModel' assetParametersVendor = 'assetParametersVendor' fairValue = 'fairValue' openTime = 'openTime' pressureHourlyForecast = 'pressureHourlyForecast' localCcyRate = 'localCcyRate' endUserException = 'endUserException' sell90cents = 'sell90cents' executionVenue = 'executionVenue' primaryVwapInLimitRealizedBps = 'primaryVwapInLimitRealizedBps' approveRebalance = 'approveRebalance' adjustedClosePrice = 'adjustedClosePrice' lmsId = 'lmsId' rebateRate = 'rebateRate' sell130cents = 'sell130cents' sell32bps = 'sell32bps' paceOfRollp50 = 'paceOfRollp50' priceMoveVsArrival = 'priceMoveVsArrival' strikeRelative = 'strikeRelative' pressureType = 'pressureType' buy40bps = 'buy40bps' priceNotation = 'priceNotation' strategy = 'strategy' issueStatusDate = 'issueStatusDate' lenderIncome = 'lenderIncome' pbClientId = 'pbClientId' istatRegionCode = 'istatRegionCode' sell9bps = 'sell9bps' ownerId = 'ownerId' composite10DayAdv = 'composite10DayAdv' maxLoanBalance = 'maxLoanBalance' ideaActivityType = 'ideaActivityType' sell60cents = 'sell60cents' ideaSource = 'ideaSource' everOnVent = 'everOnVent' buy15cents = 'buy15cents' unadjustedAsk = 'unadjustedAsk' contributionName = 'contributionName' givenPlusPaid = 'givenPlusPaid' lastFillPrice = 'lastFillPrice' shortConvictionSmall = 'shortConvictionSmall' upfrontPaymentCurrency = 'upfrontPaymentCurrency' spotSettlementDate = 'spotSettlementDate' matrixOrder = 'matrixOrder' dateIndex = 'dateIndex' payerDayCountFraction = 'payerDayCountFraction' assetClassificationsIsPrimary = 'assetClassificationsIsPrimary' breakEvenInflationChange = 'breakEvenInflationChange' buy130cents = 'buy130cents' dwiContribution = 'dwiContribution' asset2Id = 'asset2Id' averageFillPrice = 'averageFillPrice' depthSpreadScore = 'depthSpreadScore' sell10cents = 'sell10cents' subAccount = 'subAccount' buy65cents = 'buy65cents' bondCdsBasis = 'bondCdsBasis' vendor = 'vendor' dataSet = 'dataSet' notionalAmount2 = 'notionalAmount2' notionalAmount1 = 'notionalAmount1' queueingTime = 'queueingTime' annReturn5Year = 'annReturn5Year' volumeStartOfDay = 'volumeStartOfDay' priceNotation3Type = 'priceNotation3Type' assetParametersFloatingRateDesignatedMaturity = 'assetParametersFloatingRateDesignatedMaturity' executedNotionalLocal = 'executedNotionalLocal' businessSponsor = 'businessSponsor' unexplained = 'unexplained' seasonalAdjustmentShort = 'seasonalAdjustmentShort' metric = 'metric' ask = 'ask' closePrice = 'closePrice' endTime = 'endTime' sell100cents = 'sell100cents' executionTimestamp = 'executionTimestamp' buy180cents = 'buy180cents' absoluteStrike = 'absoluteStrike' sell3point5bps = 'sell3point5bps' liquidityScoreBuy = 'liquidityScoreBuy' paymentFrequency = 'paymentFrequency' expenseRatioNetBps = 'expenseRatioNetBps' metricType = 'metricType' rankYtd = 'rankYtd' leg1Spread = 'leg1Spread' coverageRegion = 'coverageRegion' absoluteReturnYtd = 'absoluteReturnYtd' dayCountConvention2 = 'dayCountConvention2' fwdtier = 'fwdtier' degreeDays = 'degreeDays' turnoverAdjusted = 'turnoverAdjusted' priceSpotTargetValue = 'priceSpotTargetValue' marketDataPoint = 'marketDataPoint' numOfFunds = 'numOfFunds' tradeTime = 'tradeTime' executionId = 'executionId' turnoverUnadjusted = 'turnoverUnadjusted' leg1FloatingIndex = 'leg1FloatingIndex' hedgeAnnualizedVolatility = 'hedgeAnnualizedVolatility' benchmarkCurrency = 'benchmarkCurrency' futuresContract = 'futuresContract' name = 'name' aum = 'aum' leg1DayCountConvention = 'leg1DayCountConvention' cbsCode = 'cbsCode' folderName = 'folderName' apiUsage = 'apiUsage' twapInterval = 'twapInterval' uniqueId = 'uniqueId' optionExpirationDate = 'optionExpirationDate' swaptionAtmFwdRate = 'swaptionAtmFwdRate' liveDate = 'liveDate' corporateActionType = 'corporateActionType' primeId = 'primeId' description = 'description' assetClassificationsIsCountryPrimary = 'assetClassificationsIsCountryPrimary' rebateRateLimit = 'rebateRateLimit' factor = 'factor' daysOnLoan = 'daysOnLoan' longConvictionSmall = 'longConvictionSmall' sell40cents = 'sell40cents' relativePayoffYtd = 'relativePayoffYtd' gsfeer = 'gsfeer' relativeHitRateQtd = 'relativeHitRateQtd' wam = 'wam' wal = 'wal' quantityccy = 'quantityccy' backtestId = 'backtestId' dirtyPrice = 'dirtyPrice' corporateSpreadContribution = 'corporateSpreadContribution' relativeHumidityHourlyForecast = 'relativeHumidityHourlyForecast' multipleScore = 'multipleScore' betaAdjustedExposure = 'betaAdjustedExposure' dividendPoints = 'dividendPoints' brightness = 'brightness' assetParametersReceiverDesignatedMaturity = 'assetParametersReceiverDesignatedMaturity' bosInTicksDescription = 'bosInTicksDescription' testId = 'testId' impliedCorrelation = 'impliedCorrelation' normalizedPerformance = 'normalizedPerformance' overnightNewsEndTime = 'overnightNewsEndTime' bytesConsumed = 'bytesConsumed' swaptionVol = 'swaptionVol' estimatedClosingVolume = 'estimatedClosingVolume' issuer = 'issuer' dividendYield = 'dividendYield' marketType = 'marketType' numUnitsLower = 'numUnitsLower' sourceOrigin = 'sourceOrigin' proceedsAssetSwapSpread3m = 'proceedsAssetSwapSpread3m' totalQuantity = 'totalQuantity' internalUser = 'internalUser' sell40bps = 'sell40bps' redemptionOption = 'redemptionOption' notionalUnit2 = 'notionalUnit2' notionalUnit1 = 'notionalUnit1' sedol = 'sedol' roundingCostPnl = 'roundingCostPnl' midYield = 'midYield' unexecutedNotionalLocal = 'unexecutedNotionalLocal' sustainGlobal = 'sustainGlobal' endingDate = 'endingDate' proceedsAssetSwapSpread12m = 'proceedsAssetSwapSpread12m' grossInvestmentWtd = 'grossInvestmentWtd' annReturn3Year = 'annReturn3Year' sharpeWtd = 'sharpeWtd' discountFactor = 'discountFactor' relativeReturnMtd = 'relativeReturnMtd' priceChangeOnDay = 'priceChangeOnDay' buy100cents = 'buy100cents' forwardPoint = 'forwardPoint' fci = 'fci' recallQuantity = 'recallQuantity' fxPositioning = 'fxPositioning' gsidEquivalent = 'gsidEquivalent' categories = 'categories' extMktAsset = 'extMktAsset' quotingStyle = 'quotingStyle' errorMessage = 'errorMessage' midPrice = 'midPrice' proceedsAssetSwapSpread6m = 'proceedsAssetSwapSpread6m' stsEmDm = 'stsEmDm' embeddedOption = 'embeddedOption' tcmCostHorizon2Day = 'tcmCostHorizon2Day' ageBand = 'ageBand' returnsEnabled = 'returnsEnabled' runId = 'runId' queueInLots = 'queueInLots' tenderOfferExpirationDate = 'tenderOfferExpirationDate' midcurveAnnuity = 'midcurveAnnuity' lendingFundNavTrend = 'lendingFundNavTrend' cloudCoverForecast = 'cloudCoverForecast' tcmCostParticipationRate5Pct = 'tcmCostParticipationRate5Pct' defaultBackcast = 'defaultBackcast' newsOnIntensity = 'newsOnIntensity' priceFormingContinuationData = 'priceFormingContinuationData' adjustedShortInterest = 'adjustedShortInterest' newHospitalized = 'newHospitalized' assetParametersStrike = 'assetParametersStrike' buy35cents = 'buy35cents' leg2TotalNotional = 'leg2TotalNotional' assetParametersEffectiveDate = 'assetParametersEffectiveDate' annReturn10Year = 'annReturn10Year' numAdultIcuBeds = 'numAdultIcuBeds' daysToExpiration = 'daysToExpiration' continuationEvent = 'continuationEvent' wiId = 'wiId' marketCapCategory = 'marketCapCategory' historicalVolume = 'historicalVolume' buy5cents = 'buy5cents' eventStartDate = 'eventStartDate' leg1FixedRate = 'leg1FixedRate' equityGamma = 'equityGamma' rptId = 'rptId' grossIncome = 'grossIncome' emId = 'emId' assetCountInModel = 'assetCountInModel' stsCreditRegion = 'stsCreditRegion' minTemperature = 'minTemperature' bucketStartTime = 'bucketStartTime' fillType = 'fillType' closeTime = 'closeTime' failPct = 'failPct' isoCountryCodeAlpha2 = 'isoCountryCodeAlpha2' isoCountryCodeAlpha3 = 'isoCountryCodeAlpha3' amount = 'amount' lendingFundAcct = 'lendingFundAcct' rebate = 'rebate' electionType = 'electionType' relativeHitRateMtd = 'relativeHitRateMtd' impliedVolatility = 'impliedVolatility' spread = 'spread' variance = 'variance' wtdDegreeDaysDailyForecast = 'wtdDegreeDaysDailyForecast' swaptionAnnuity = 'swaptionAnnuity' buy6bps = 'buy6bps' g10Currency = 'g10Currency' humidityForecast = 'humidityForecast' relativePeriod = 'relativePeriod' user = 'user' customer = 'customer' leg1ResetFrequency = 'leg1ResetFrequency' queueClockTimeLabel = 'queueClockTimeLabel' paceOfRollp100 = 'paceOfRollp100' assetClassificationsGicsSubIndustry = 'assetClassificationsGicsSubIndustry' dewPointHourlyForecast = 'dewPointHourlyForecast' locationType = 'locationType' facetDivisionalReportingGroupId = 'facetDivisionalReportingGroupId' realizedTwapPerformanceUSD = 'realizedTwapPerformanceUSD' swapRate = 'swapRate' algoExecutionStyle = 'algoExecutionStyle' clientContact = 'clientContact' minTemperatureHour = 'minTemperatureHour' tradingCurrency = 'tradingCurrency' totalByOnset = 'totalByOnset' agencySwapSpread = 'agencySwapSpread' rank = 'rank' mixedSwapOtherReportedSDR = 'mixedSwapOtherReportedSDR' humidity = 'humidity' dataSetCategory = 'dataSetCategory' vwapRealizedBps = 'vwapRealizedBps' buy9bps = 'buy9bps' totalTested = 'totalTested' fatalitiesConfirmed = 'fatalitiesConfirmed' universeId1 = 'universeId1' assetParametersPayerDayCountFraction = 'assetParametersPayerDayCountFraction' universeId2 = 'universeId2' bidLow = 'bidLow' bucketizePrice = 'bucketizePrice' fairVarianceVolatility = 'fairVarianceVolatility' covid19 = 'covid19' clientExposure = 'clientExposure' leg2TotalNotionalUnit = 'leg2TotalNotionalUnit' sell45cents = 'sell45cents' gsSustainSubSector = 'gsSustainSubSector' sinkable = 'sinkable' isReal = 'isReal' maxTemperatureHour = 'maxTemperatureHour' leg2AveragingMethod = 'leg2AveragingMethod' jsn = 'jsn' sell160cents = 'sell160cents' knockInDirection = 'knockInDirection' dayCloseUnrealizedUSD = 'dayCloseUnrealizedUSD' tenor = 'tenor' pricingConvention = 'pricingConvention' popularity = 'popularity' floatingRateOption = 'floatingRateOption' hedgeValueType = 'hedgeValueType' assetParametersClearingHouse = 'assetParametersClearingHouse' disclaimer = 'disclaimer' payerFrequency = 'payerFrequency' loanFee = 'loanFee' deploymentVersion = 'deploymentVersion' buy16bps = 'buy16bps' tradeDayCount = 'tradeDayCount' priceToSales = 'priceToSales' newIdeasQtd = 'newIdeasQtd' subdivisionName = 'subdivisionName' adjustedAskPrice = 'adjustedAskPrice' factorUniverse = 'factorUniverse' arrivalRt = 'arrivalRt' internalIndexCalcAgent = 'internalIndexCalcAgent' excessMarginValue = 'excessMarginValue' transactionCost = 'transactionCost' centralBankSwapRate = 'centralBankSwapRate' previousNewConfirmed = 'previousNewConfirmed' unrealizedVwapPerformanceBps = 'unrealizedVwapPerformanceBps' degreeDaysDailyForecast = 'degreeDaysDailyForecast' positionAmount = 'positionAmount' heatIndexHourlyForecast = 'heatIndexHourlyForecast' maRank = 'maRank' fxPositioningSource = 'fxPositioningSource' eventStartDateTime = 'eventStartDateTime' impliedVolatilityByDeltaStrike = 'impliedVolatilityByDeltaStrike' mqSymbol = 'mqSymbol' numTotalUnits = 'numTotalUnits' corporateAction = 'corporateAction' leg1PriceType = 'leg1PriceType' assetParametersPayerRateOption = 'assetParametersPayerRateOption' sell20cents = 'sell20cents' leg2FixedPaymentCurrency = 'leg2FixedPaymentCurrency' gRegionalScore = 'gRegionalScore' hardToBorrow = 'hardToBorrow' sell5bps = 'sell5bps' rollVwap = 'rollVwap' wpk = 'wpk' bespokeSwap = 'bespokeSwap' assetParametersExpirationDate = 'assetParametersExpirationDate' countryName = 'countryName' carry = 'carry' startingDate = 'startingDate' loanId = 'loanId' onboarded = 'onboarded' liquidityScore = 'liquidityScore' longRatesContribution = 'longRatesContribution' sourceDateSpan = 'sourceDateSpan' annYield6Month = 'annYield6Month' underlyingDataSetId = 'underlyingDataSetId' closeUnadjusted = 'closeUnadjusted' valueUnit = 'valueUnit' quantityUnit = 'quantityUnit' adjustedLowPrice = 'adjustedLowPrice' isMomentum = 'isMomentum' longConvictionLarge = 'longConvictionLarge' oad = 'oad' rate = 'rate' couponType = 'couponType' client = 'client' convictionList = 'convictionList' passiveEtfRatio = 'passiveEtfRatio' futureMonthG26 = 'futureMonthG26' futureMonthG25 = 'futureMonthG25' futureMonthG24 = 'futureMonthG24' futureMonthG23 = 'futureMonthG23' typeOfReturn = 'typeOfReturn' futureMonthG22 = 'futureMonthG22' servicingCostLongPnl = 'servicingCostLongPnl' excessMarginPercentage = 'excessMarginPercentage' futureMonthG21 = 'futureMonthG21' totalMild = 'totalMild' realizedArrivalPerformanceBps = 'realizedArrivalPerformanceBps' precipitationDailyForecastInches = 'precipitationDailyForecastInches' exchangeId = 'exchangeId' leg2FixedPayment = 'leg2FixedPayment' tcmCostHorizon20Day = 'tcmCostHorizon20Day' realm = 'realm' bid = 'bid' hedgeValue = 'hedgeValue' orderStartTime = 'orderStartTime' isAggressive = 'isAggressive' floatingRateDesignatedMaturity = 'floatingRateDesignatedMaturity' percentageNearExecutedQuantity = 'percentageNearExecutedQuantity' orderId = 'orderId' hospitalType = 'hospitalType' dayCloseRealizedBps = 'dayCloseRealizedBps' precipitationHourlyForecast = 'precipitationHourlyForecast' marketCapUSD = 'marketCapUSD' auctionFillsPercentage = 'auctionFillsPercentage' highPrice = 'highPrice' absoluteShares = 'absoluteShares' fixedRateDayCountFraction = 'fixedRateDayCountFraction' model = 'model' unrealizedTwapPerformanceUSD = 'unrealizedTwapPerformanceUSD' id = 'id' maturity = 'maturity' deltaChange = 'deltaChange' index = 'index' unrealizedArrivalPerformanceUSD = 'unrealizedArrivalPerformanceUSD' icebergSlippage = 'icebergSlippage' sell120cents = 'sell120cents' futureMonthX26 = 'futureMonthX26' assetTypes = 'assetTypes' futureMonthX25 = 'futureMonthX25' bcid = 'bcid' mktPoint = 'mktPoint' futureMonthX24 = 'futureMonthX24' restrictionStartDate = 'restrictionStartDate' touchLiquidityScore = 'touchLiquidityScore' futureMonthX23 = 'futureMonthX23' futureMonthX22 = 'futureMonthX22' factorCategoryId = 'factorCategoryId' securityTypeId = 'securityTypeId' futureMonthX21 = 'futureMonthX21' investmentYtd = 'investmentYtd' leg2Notional = 'leg2Notional' sell1bps = 'sell1bps' sell200cents = 'sell200cents' expectedCompletionDate = 'expectedCompletionDate' spreadOptionVol = 'spreadOptionVol' sell80cents = 'sell80cents' inflationSwapRate = 'inflationSwapRate' activeQueries = 'activeQueries' sell45bps = 'sell45bps' embededOption = 'embededOption' eventSource = 'eventSource' qisPermNo = 'qisPermNo' settlement = 'settlement' shareclassId = 'shareclassId' feature2 = 'feature2' feature3 = 'feature3' stsCommoditySector = 'stsCommoditySector' exceptionStatus = 'exceptionStatus' overnightNewsIntensity = 'overnightNewsIntensity' salesCoverage = 'salesCoverage' feature1 = 'feature1' tcmCostParticipationRate10Pct = 'tcmCostParticipationRate10Pct' eventTime = 'eventTime' positionSourceName = 'positionSourceName' deliveryDate = 'deliveryDate' interestRate = 'interestRate' side = 'side' dynamicHybridAggressiveStyle = 'dynamicHybridAggressiveStyle' complianceRestrictedStatus = 'complianceRestrictedStatus' borrowFee = 'borrowFee' everIcu = 'everIcu' noWorseThanLevel = 'noWorseThanLevel' updateTime = 'updateTime' loanSpread = 'loanSpread' tcmCostHorizon12Hour = 'tcmCostHorizon12Hour' dewPoint = 'dewPoint' researchCommission = 'researchCommission' buy2bps = 'buy2bps' assetClassificationsRiskCountryCode = 'assetClassificationsRiskCountryCode' newIdeasMtd = 'newIdeasMtd' varSwapByExpiry = 'varSwapByExpiry' sellDate = 'sellDate' aumStart = 'aumStart' assetParametersSettlement = 'assetParametersSettlement' maxTemperature = 'maxTemperature' acquirerShareholderMeetingDate = 'acquirerShareholderMeetingDate' countIdeasWtd = 'countIdeasWtd' arrivalRtNormalized = 'arrivalRtNormalized' reportType = 'reportType' sourceURL = 'sourceURL' estimatedReturn = 'estimatedReturn' high = 'high' sourceLastUpdate = 'sourceLastUpdate' sunshineForecast = 'sunshineForecast' quantityMW = 'quantityMW' sell70cents = 'sell70cents' sell110cents = 'sell110cents' pnodeId = 'pnodeId' humidityType = 'humidityType' prevCloseAsk = 'prevCloseAsk' level = 'level' impliedVolatilityByExpiration = 'impliedVolatilityByExpiration' assetParametersFixedRateDayCountFraction = 'assetParametersFixedRateDayCountFraction' esMomentumScore = 'esMomentumScore' leg2Index = 'leg2Index' netWeight = 'netWeight' portfolioManagers = 'portfolioManagers' bosInTicks = 'bosInTicks' assetParametersCouponType = 'assetParametersCouponType' expectedResidualQuantity = 'expectedResidualQuantity' rollDate = 'rollDate' dynamicHybridSpeed = 'dynamicHybridSpeed' capFloorVol = 'capFloorVol' targetQuantity = 'targetQuantity' submitter = 'submitter' no = 'no' notional = 'notional' esDisclosurePercentage = 'esDisclosurePercentage' closeExecutedQuantityPercentage = 'closeExecutedQuantityPercentage' twapRealizedCash = 'twapRealizedCash' isOpenAuction = 'isOpenAuction' leg1Type = 'leg1Type' wetBulbTempHourlyForecast = 'wetBulbTempHourlyForecast' cleanupPrice = 'cleanupPrice' total = 'total' filledNotionalUSD = 'filledNotionalUSD' assetId = 'assetId' testStatus = 'testStatus' mktType = 'mktType' lastUpdatedTime = 'lastUpdatedTime' yield30Day = 'yield30Day' buy28bps = 'buy28bps' proportionOfRisk = 'proportionOfRisk' futureMonthK23 = 'futureMonthK23' futureMonthK22 = 'futureMonthK22' futureMonthK21 = 'futureMonthK21' primaryEntityId = 'primaryEntityId' cross = 'cross' ideaStatus = 'ideaStatus' contractSubtype = 'contractSubtype' sri = 'sri' fxForecast = 'fxForecast' fixingTimeLabel = 'fixingTimeLabel' isETF = 'isETF' _100 = '100' _101 = '101' _102 = '102' _103 = '103' _104 = '104' fillId = 'fillId' excessReturns = 'excessReturns' _105 = '105' _106 = '106' dollarReturn = 'dollarReturn' orderInLimit = 'orderInLimit' expiryTime = 'expiryTime' _107 = '107' returnOnEquity = 'returnOnEquity' _108 = '108' _109 = '109' futureMonthK26 = 'futureMonthK26' futureMonthK25 = 'futureMonthK25' futureMonthK24 = 'futureMonthK24' restrictionEndDate = 'restrictionEndDate' queueInLotsDescription = 'queueInLotsDescription' volumeLimit = 'volumeLimit' objective = 'objective' navPrice = 'navPrice' leg1UnderlyingAsset = 'leg1UnderlyingAsset' _110 = '110' _111 = '111' _112 = '112' _113 = '113' privatePlacementType = 'privatePlacementType' _114 = '114' hedgeNotional = 'hedgeNotional' _115 = '115' _116 = '116' askLow = 'askLow' intendedPRate = 'intendedPRate' _117 = '117' _118 = '118' _119 = '119' expiry = 'expiry' avgMonthlyYield = 'avgMonthlyYield' periodDirection = 'periodDirection' prevRptId = 'prevRptId' earningsPerShare = 'earningsPerShare' strikePercentage = 'strikePercentage' esProductImpactPercentile = 'esProductImpactPercentile' vwapRealizedCash = 'vwapRealizedCash' parAssetSwapSpread1m = 'parAssetSwapSpread1m' prevCloseBid = 'prevCloseBid' minimumIncrement = 'minimumIncrement' tcmCostHorizon16Day = 'tcmCostHorizon16Day' investmentMtd = 'investmentMtd' settlementDate = 'settlementDate' weightedAverageMidNormalized = 'weightedAverageMidNormalized' _120 = '120' _121 = '121' _122 = '122' salesPerShare = 'salesPerShare' _123 = '123' _124 = '124' _125 = '125' unadjustedClose = 'unadjustedClose' _126 = '126' _127 = '127' _128 = '128' _129 = '129' loanDate = 'loanDate' matchedMaturitySwapSpread1m = 'matchedMaturitySwapSpread1m' collateralPercentageActual = 'collateralPercentageActual' vwapInLimitUnrealizedBps = 'vwapInLimitUnrealizedBps' metricValue = 'metricValue' autoExecState = 'autoExecState' totalRecovered = 'totalRecovered' relativeReturnYtd = 'relativeReturnYtd' _130 = '130' tickServer = 'tickServer' _131 = '131' _132 = '132' _133 = '133' _134 = '134' cumulativeVolumeInPercentage = 'cumulativeVolumeInPercentage' _135 = '135' _136 = '136' _137 = '137' _138 = '138' _139 = '139' realTimeRestrictionStatus = 'realTimeRestrictionStatus' tradeType = 'tradeType' settlementType = 'settlementType' netChange = 'netChange' numberOfUnderliers = 'numberOfUnderliers' swapType = 'swapType' forecastType = 'forecastType' leg1Notional = 'leg1Notional' sellSettleDate = 'sellSettleDate' _140 = '140' _141 = '141' _142 = '142' _143 = '143' _144 = '144' _145 = '145' _146 = '146' _147 = '147' newIdeasYtd = 'newIdeasYtd' managementFee = 'managementFee' _148 = '148' _149 = '149' parAssetSwapSpread3m = 'parAssetSwapSpread3m' sell36bps = 'sell36bps' matchedMaturitySwapSpread3m = 'matchedMaturitySwapSpread3m' sourceId = 'sourceId' country = 'country' vwap = 'vwap' touchSpreadScore = 'touchSpreadScore' ratingSecondHighest = 'ratingSecondHighest' sell24bps = 'sell24bps' _150 = '150' _151 = '151' _152 = '152' frequency = 'frequency' _153 = '153' _154 = '154' activityId = 'activityId' _155 = '155' estimatedImpact = 'estimatedImpact' sell35cents = 'sell35cents' _156 = '156' loanSpreadBucket = 'loanSpreadBucket' _157 = '157' _158 = '158' coronavirusGlobalActivityTracker = 'coronavirusGlobalActivityTracker' _159 = '159' underlyers = 'underlyers' assetParametersPricingLocation = 'assetParametersPricingLocation' eventDescription = 'eventDescription' icebergMaxSize = 'icebergMaxSize' assetParametersCoupon = 'assetParametersCoupon' details = 'details' sector = 'sector' avgBedUtilRate = 'avgBedUtilRate' buy20bps = 'buy20bps' epidemic = 'epidemic' mctr = 'mctr' exchangeTime = 'exchangeTime' historicalClose = 'historicalClose' fipsCode = 'fipsCode' _160 = '160' _161 = '161' buy32bps = 'buy32bps' _162 = '162' _163 = '163' ideaId = 'ideaId' commentStatus = 'commentStatus' marginalCost = 'marginalCost' _164 = '164' _165 = '165' _166 = '166' _167 = '167' _168 = '168' clientWeight = 'clientWeight' _169 = '169' leg1DeliveryPoint = 'leg1DeliveryPoint' sell5cents = 'sell5cents' liqWkly = 'liqWkly' unrealizedTwapPerformanceBps = 'unrealizedTwapPerformanceBps' region = 'region' temperatureHour = 'temperatureHour' upperBound = 'upperBound' sell55cents = 'sell55cents' _170 = '170' _171 = '171' numPediIcuBeds = 'numPediIcuBeds' _172 = '172' bidYield = 'bidYield' _173 = '173' _174 = '174' expectedResidual = 'expectedResidual' _175 = '175' _176 = '176' optionPremium = 'optionPremium' _177 = '177' _178 = '178' _179 = '179' ownerName = 'ownerName' parAssetSwapSpread6m = 'parAssetSwapSpread6m' zScore = 'zScore' sell12bps = 'sell12bps' eventStartTime = 'eventStartTime' matchedMaturitySwapSpread6m = 'matchedMaturitySwapSpread6m' turnover = 'turnover' priceSpotTargetUnit = 'priceSpotTargetUnit' coverage = 'coverage' gPercentile = 'gPercentile' _180 = '180' _181 = '181' _182 = '182' cloudCoverHourlyForecast = 'cloudCoverHourlyForecast' _183 = '183' _184 = '184' lendingFundNav = 'lendingFundNav' sourceOriginalCategory = 'sourceOriginalCategory' percentCloseExecutionQuantity = 'percentCloseExecutionQuantity' _185 = '185' latestExecutionTime = 'latestExecutionTime' _186 = '186' _187 = '187' arrivalMidRealizedBps = 'arrivalMidRealizedBps' _188 = '188' _189 = '189' location = 'location' scenarioId = 'scenarioId' terminationTenor = 'terminationTenor' queueClockTime = 'queueClockTime' discretionLowerBound = 'discretionLowerBound' tcmCostParticipationRate50Pct = 'tcmCostParticipationRate50Pct' ratingLinear = 'ratingLinear' previousCloseUnrealizedBps = 'previousCloseUnrealizedBps' _190 = '190' _191 = '191' subAssetClassForOtherCommodity = 'subAssetClassForOtherCommodity' _192 = '192' forwardPrice = 'forwardPrice' _193 = '193' type = 'type' _194 = '194' strikeRef = 'strikeRef' _195 = '195' _196 = '196' _197 = '197' cumulativePnl = 'cumulativePnl' _198 = '198' shortTenor = 'shortTenor' sell28bps = 'sell28bps' fundClass = 'fundClass' _199 = '199' unadjustedVolume = 'unadjustedVolume' buy36bps = 'buy36bps' positionIdx = 'positionIdx' windChillHourlyForecast = 'windChillHourlyForecast' secName = 'secName' impliedVolatilityByRelativeStrike = 'impliedVolatilityByRelativeStrike' percentADV = 'percentADV' leg1TotalNotional = 'leg1TotalNotional' contract = 'contract' paymentFrequency1 = 'paymentFrequency1' paymentFrequency2 = 'paymentFrequency2' bespoke = 'bespoke' repoTenor = 'repoTenor' sell15cents = 'sell15cents' investmentQtd = 'investmentQtd' heatIndexForecast = 'heatIndexForecast' ratingStandardAndPoors = 'ratingStandardAndPoors' qualityStars = 'qualityStars' leg2FloatingIndex = 'leg2FloatingIndex' sourceTicker = 'sourceTicker' primaryVwapUnrealizedBps = 'primaryVwapUnrealizedBps' gsid = 'gsid' lendingFund = 'lendingFund' sensitivity = 'sensitivity' dayCount = 'dayCount' sell16bps = 'sell16bps' relativeBreakEvenInflationChange = 'relativeBreakEvenInflationChange' sell25cents = 'sell25cents' varSwap = 'varSwap' buy5point5bps = 'buy5point5bps' blockLargeNotional = 'blockLargeNotional' sell2point5bps = 'sell2point5bps' capacity = 'capacity' sectorsRaw = 'sectorsRaw' primaryVwapInLimit = 'primaryVwapInLimit' shareclassPrice = 'shareclassPrice' tradeSize = 'tradeSize' priceSpotEntryValue = 'priceSpotEntryValue' buy8point5bps = 'buy8point5bps' symbolDimensions = 'symbolDimensions' buy24bps = 'buy24bps' observation = 'observation' optionTypeSDR = 'optionTypeSDR' scenarioGroupId = 'scenarioGroupId' averageImpliedVariance = 'averageImpliedVariance' avgTradeRateDescription = 'avgTradeRateDescription' fraction = 'fraction' assetCountShort = 'assetCountShort' collateralPercentageRequired = 'collateralPercentageRequired' sell5point5bps = 'sell5point5bps' date = 'date' zipCode = 'zipCode' totalStdReturnSinceInception = 'totalStdReturnSinceInception' sourceCategory = 'sourceCategory' volumeUnadjusted = 'volumeUnadjusted' passiveRatio = 'passiveRatio' priceToEarnings = 'priceToEarnings' orderDepth = 'orderDepth' annYield3Month = 'annYield3Month' netFlowStd = 'netFlowStd' encodedStats = 'encodedStats' buy5bps = 'buy5bps' runTime = 'runTime' askSize = 'askSize' absoluteReturnMtd = 'absoluteReturnMtd' std30DaysUnsubsidizedYield = 'std30DaysUnsubsidizedYield' resource = 'resource' averageRealizedVolatility = 'averageRealizedVolatility' traceAdvBuy = 'traceAdvBuy' newConfirmed = 'newConfirmed' sell8bps = 'sell8bps' bidPrice = 'bidPrice' sell8point5bps = 'sell8point5bps' targetPriceUnrealizedBps = 'targetPriceUnrealizedBps' esNumericPercentile = 'esNumericPercentile' leg2UnderlyingAsset = 'leg2UnderlyingAsset' csaTerms = 'csaTerms' relativePayoffMtd = 'relativePayoffMtd' dailyNetShareholderFlows = 'dailyNetShareholderFlows' buy2point5bps = 'buy2point5bps' cai = 'cai' executedNotionalUSD = 'executedNotionalUSD' systemTime = 'systemTime' totalHomeIsolation = 'totalHomeIsolation' stationName = 'stationName' passPct = 'passPct' openingReport = 'openingReport' midcurveAtmFwdRate = 'midcurveAtmFwdRate' precipitationForecast = 'precipitationForecast' equityRiskPremiumIndex = 'equityRiskPremiumIndex' fatalitiesUnderlyingConditionsUnknown = 'fatalitiesUnderlyingConditionsUnknown' buy12bps = 'buy12bps' clearingHouse = 'clearingHouse' dayCloseUnrealizedBps = 'dayCloseUnrealizedBps' stsRatesMaturity = 'stsRatesMaturity' liqDly = 'liqDly' contributorRole = 'contributorRole' totalFatalities = 'totalFatalities' adjustedClose = 'adjustedClose' averageValue = 'averageValue' avgInterestRate = 'avgInterestRate' basisDuration = 'basisDuration' bestMonthDate = 'bestMonthDate' bloombergTicker = 'bloombergTicker' capexDepreciation = 'capexDepreciation' capexSales = 'capexSales' cashConversion = 'cashConversion' category = 'category' convexity = 'convexity' countryCode = 'countryCode' croci = 'croci' currentValue = 'currentValue' dacf = 'dacf' dailyVolatility = 'dailyVolatility' divYield = 'divYield' dpsGrowth = 'dpsGrowth' drawdownOverReturn = 'drawdownOverReturn' ebitdaGrowth = 'ebitdaGrowth' ebitdaMargin = 'ebitdaMargin' ebitGrowth = 'ebitGrowth' ebitMargin = 'ebitMargin' evGci = 'evGci' fcfConversion = 'fcfConversion' fcfYield = 'fcfYield' gci = 'gci' grossProfTotAssets = 'grossProfTotAssets' historicCPR = 'historicCPR' incrementalMargin = 'incrementalMargin' industry = 'industry' informationRatio = 'informationRatio' interestCover = 'interestCover' lastChange = 'lastChange' lastChangePct = 'lastChangePct' lastDate = 'lastDate' lastValue = 'lastValue' liborMatchedMaturitySwap = 'liborMatchedMaturitySwap' liborOAS = 'liborOAS' liborProceedsASW = 'liborProceedsASW' liborzSpread = 'liborzSpread' manEarningGrowthMeas = 'manEarningGrowthMeas' marginalRiskContribution = 'marginalRiskContribution' maxDrawdown = 'maxDrawdown' netDebtEbitda = 'netDebtEbitda' netDebtEquity = 'netDebtEquity' niGrowth = 'niGrowth' niMargin = 'niMargin' oisMatchedMaturitySwap = 'oisMatchedMaturitySwap' oisProceedsASW = 'oisProceedsASW' oiszSpread = 'oiszSpread' optionStyle = 'optionStyle' payup = 'payup' positionDate = 'positionDate' preTaxProfitGrowth = 'preTaxProfitGrowth' riskPremiaStyles = 'riskPremiaStyles' roce = 'roce' rolldown = 'rolldown' salesGrowth = 'salesGrowth' sharpeRatio = 'sharpeRatio' totalDebtCapital = 'totalDebtCapital' totalDebtTotalAsset = 'totalDebtTotalAsset' totalReturn = 'totalReturn' unleveredFcfYield = 'unleveredFcfYield' worstMonthDate = 'worstMonthDate' def __repr__(self): return self.value class Format(EnumBase, Enum): """Alternative format for data to be returned in""" Json = 'Json' Excel = 'Excel' MessagePack = 'MessagePack' Pdf = 'Pdf' def __repr__(self): return self.value class IndexNotTradingReasons(EnumBase, Enum): """Reasons the index was not traded""" Cost = 'Cost' Client_does_not_like_the_construction = 'Client does not like the construction' Basket_created_prematurely = 'Basket created prematurely' Economics_of_the_basket_changed__client_no_longer_interested_in_trading = 'Economics of the basket changed: client no longer interested in trading' GS_booking_OVER_operational_issues = 'GS booking/operational issues' _ = '' def __repr__(self): return self.value class LiquidityMeasure(EnumBase, Enum): """A list of the different liquidity measures to choose from.""" Summary = 'Summary' Constituent_Transaction_Costs = 'Constituent Transaction Costs' Constituents = 'Constituents' Largest_Holdings_By_Weight = 'Largest Holdings By Weight' Least_Liquid_Holdings = 'Least Liquid Holdings' ADV_Percent_Buckets = 'ADV Percent Buckets' Market_Cap_Buckets = 'Market Cap Buckets' Region_Buckets = 'Region Buckets' Country_Buckets = 'Country Buckets' Sector_Buckets = 'Sector Buckets' Industry_Buckets = 'Industry Buckets' Risk_Buckets = 'Risk Buckets' Factor_Risk_Buckets = 'Factor Risk Buckets' Exposure_Buckets = 'Exposure Buckets' Factor_Exposure_Buckets = 'Factor Exposure Buckets' Percent_Of_Trade_Complete_Over_Time = 'Percent Of Trade Complete Over Time' Execution_Cost_With_Different_Time_Horizons = 'Execution Cost With Different Time Horizons' Participation_Rate_With_Different_Time_Horizons = 'Participation Rate With Different Time Horizons' Risk_With_Different_Time_Horizons = 'Risk With Different Time Horizons' Historical_ADV_Percent_Curve = 'Historical ADV Percent Curve' Time_Series_Data = 'Time Series Data' def __repr__(self): return self.value class MarketDataFrequency(EnumBase, Enum): Real_Time = 'Real Time' End_Of_Day = 'End Of Day' def __repr__(self): return self.value class MarketDataShockType(EnumBase, Enum): """Market data shock type""" Absolute = 'Absolute' Proportional = 'Proportional' Invalid = 'Invalid' Override = 'Override' StdDev = 'StdDev' AutoDefault = 'AutoDefault' CSWFFR = 'CSWFFR' StdVolFactor = 'StdVolFactor' StdVolFactorProportional = 'StdVolFactorProportional' def __repr__(self): return self.value class MarketDataVendor(EnumBase, Enum): Goldman_Sachs = 'Goldman Sachs' Thomson_Reuters = 'Thomson Reuters' Solactive = 'Solactive' Bloomberg = 'Bloomberg' Axioma = 'Axioma' Goldman_Sachs_Prime_Services = 'Goldman Sachs Prime Services' Goldman_Sachs_Global_Investment_Research = 'Goldman Sachs Global Investment Research' National_Weather_Service = 'National Weather Service' WM = 'WM' Hedge_Fund_Research__Inc_ = 'Hedge Fund Research, Inc.' London_Stock_Exchange = 'London Stock Exchange' Goldman_Sachs_MDFarm = 'Goldman Sachs MDFarm' PredictIt = 'PredictIt' Iowa_Electronic_Markets = 'Iowa Electronic Markets' RealClearPolitics = 'RealClearPolitics' _538 = '538' FiveThirtyEight = 'FiveThirtyEight' Opinium = 'Opinium' YouGov = 'YouGov' MorningStar = 'MorningStar' Survation = 'Survation' Survation__YouGov = 'Survation, YouGov' European_Centre_for_Disease_Prevention_and_Control = 'European Centre for Disease Prevention and Control' Centers_for_Disease_Control_and_Prevention = 'Centers for Disease Control and Prevention' Johns_Hopkins_University = 'Johns Hopkins University' Google = 'Google' National_Health_Service = 'National Health Service' World_Health_Organization = 'World Health Organization' Wikipedia = 'Wikipedia' StarSchema = 'StarSchema' Covid_Working_Group = 'Covid Working Group' CovidTracking = 'CovidTracking' Bing = 'Bing' FRED = 'FRED' Institute_for_Health_Metrics_and_Evaluation = 'Institute for Health Metrics and Evaluation' Refinitiv = 'Refinitiv' Goldman_Sachs_Global_Investment_Research__Refinitiv = 'Goldman Sachs Global Investment Research, Refinitiv' EPFR = 'EPFR' def __repr__(self): return self.value class OptionExpiryType(EnumBase, Enum): _1m = '1m' _2m = '2m' _3m = '3m' _4m = '4m' _5m = '5m' _6m = '6m' def __repr__(self): return self.value class OptionSettlementMethod(EnumBase, Enum): """How the option is settled (e.g. Cash, Physical)""" Cash = 'Cash' Physical = 'Physical' def __repr__(self): return self.value class OptionStrikeType(EnumBase, Enum): Relative = 'Relative' Delta = 'Delta' def __repr__(self): return self.value class OptionStyle(EnumBase, Enum): """Option Exercise Style""" European = 'European' American = 'American' Bermudan = 'Bermudan' def __repr__(self): return self.value class OptionType(EnumBase, Enum): """Option Type""" Call = 'Call' Put = 'Put' Binary_Call = 'Binary Call' Binary_Put = 'Binary Put' def __repr__(self): return self.value class PCOCurrencyType(EnumBase, Enum): """Currency Type Options for PCO""" Exposure = 'Exposure' Base = 'Base' Local = 'Local' def __repr__(self): return self.value class PCOFundCalendar(EnumBase, Enum): """Fund Calendar Options for PCO""" New_York = 'New York' London = 'London' Hong_Kong = 'Hong Kong' Singapore = 'Singapore' def __repr__(self): return self.value class PayReceive(EnumBase, Enum): """Pay or receive fixed""" Pay = 'Pay' Receive = 'Receive' Straddle = 'Straddle' def __repr__(self): return self.value class Period(EnumBase, Enum): """A coding scheme to define a period corresponding to a quantity amount""" Month = 'Month' Quarter = 'Quarter' Hour = 'Hour' Day = 'Day' BusinessDay = 'BusinessDay' def __repr__(self): return self.value class PricingLocation(EnumBase, Enum): """Based on the location of the exchange. Called 'Native Region' in SecDB""" NYC = 'NYC' LDN = 'LDN' TKO = 'TKO' HKG = 'HKG' def __repr__(self): return self.value class PrincipalExchange(EnumBase, Enum): """How principal is exchanged""" _None = 'None' Both = 'Both' First = 'First' Last = 'Last' def __repr__(self): return self.value class ProductCode(EnumBase, Enum): """Override the clearing destination/symbol""" CME__BB = 'CME::BB' CME__BK = 'CME::BK' CME__BY = 'CME::BY' CME__BZ = 'CME::BZ' CME__CL = 'CME::CL' CME__CL_BZ = 'CME::CL-BZ' CME__CS = 'CME::CS' CME__CY = 'CME::CY' CME__HK = 'CME::HK' CME__HO = 'CME::HO' CME__HOB = 'CME::HOB' CME__HO_CL = 'CME::HO-CL' CME__MP = 'CME::MP' CME__NG = 'CME::NG' CME__NLS = 'CME::NLS' CME__RB = 'CME::RB' CME__RBB = 'CME::RBB' CME__RB_BZ = 'CME::RB-BZ' CME__RB_CL = 'CME::RB-CL' CME__RH = 'CME::RH' CME__RL = 'CME::RL' CME__RM = 'CME::RM' CME__WS = 'CME::WS' CME_ICE__RB_B = 'CME-ICE::RB-B' ICE__B = 'ICE::B' ICE__BNB = 'ICE::BNB' ICE__BTD = 'ICE::BTD' ICE__G = 'ICE::G' ICE__G_B = 'ICE::G-B' ICE__HBT = 'ICE::HBT' ICE__HNG = 'ICE::HNG' ICE__HOF = 'ICE::HOF' ICE__I = 'ICE::I' ICE__N = 'ICE::N' ICE__N_B = 'ICE::N-B' ICE__O = 'ICE::O' ICE__O_B = 'ICE::O-B' ICE__R = 'ICE::R' ICE__RBR = 'ICE::RBR' ICE__T = 'ICE::T' ICE__T_B = 'ICE::T-B' ICE__ULA = 'ICE::ULA' ICE__ULC = 'ICE::ULC' ICE__ULD = 'ICE::ULD' ICE__ULM = 'ICE::ULM' ICE__WTB = 'ICE::WTB' LME__MAL = 'LME::MAL' LME__MNI = 'LME::MNI' LME__MPB = 'LME::MPB' LME__MZN = 'LME::MZN' OTC__BRT = 'OTC::BRT' OTC__GO = 'OTC::GO' OTC__GO_BRT = 'OTC::GO-BRT' OTC__HO = 'OTC::HO' OTC__HO_BRT = 'OTC::HO-BRT' OTC__HO_GO = 'OTC::HO-GO' OTC__HO_WTI = 'OTC::HO-WTI' OTC__MAL = 'OTC::MAL' OTC__MNI = 'OTC::MNI' OTC__MPB = 'OTC::MPB' OTC__MZN = 'OTC::MZN' OTC__NG = 'OTC::NG' OTC__RB = 'OTC::RB' OTC__RB_BRT = 'OTC::RB-BRT' OTC__RB_HO = 'OTC::RB-HO' OTC__RB_WTI = 'OTC::RB-WTI' OTC__WTI = 'OTC::WTI' OTC__WTI_BRT = 'OTC::WTI-BRT' def __repr__(self): return self.value class Region(EnumBase, Enum): """Regional classification for the asset""" _ = '' Americas = 'Americas' Asia = 'Asia' EM = 'EM' Europe = 'Europe' Global = 'Global' def __repr__(self): return self.value class ReportJobPriority(EnumBase, Enum): """Report job priority.""" High = 'High' Normal = 'Normal' def __repr__(self): return self.value class RiskMeasureType(EnumBase, Enum): """The type of measure to perform risk on. e.g. Greeks""" Annual_ATM_Implied_Volatility = 'Annual ATM Implied Volatility' Annual_ATMF_Implied_Volatility = 'Annual ATMF Implied Volatility' Annual_Implied_Volatility = 'Annual Implied Volatility' AnnuityLocalCcy = 'AnnuityLocalCcy' BaseCPI = 'BaseCPI' Basis = 'Basis' BSPrice = 'BSPrice' CRIF_IRCurve = 'CRIF IRCurve' Cashflows = 'Cashflows' Daily_Implied_Volatility = 'Daily Implied Volatility' Delta = 'Delta' DeltaLocalCcy = 'DeltaLocalCcy' Description = 'Description' Dollar_Price = 'Dollar Price' DV01 = 'DV01' Fair_Price = 'Fair Price' FairVarStrike = 'FairVarStrike' FairVolStrike = 'FairVolStrike' Forward_Price = 'Forward Price' Forward_Rate = 'Forward Rate' Price = 'Price' Gamma = 'Gamma' GammaLocalCcy = 'GammaLocalCcy' InflationDelta = 'InflationDelta' Local_Currency_Accrual_in_Cents = 'Local Currency Accrual in Cents' Local_Currency_Annuity = 'Local Currency Annuity' Market_Data = 'Market Data' Market_Data_Assets = 'Market Data Assets' MV = 'MV' OAS = 'OAS' ParallelBasis = 'ParallelBasis' ParallelDelta = 'ParallelDelta' ParallelDeltaLocalCcy = 'ParallelDeltaLocalCcy' ParallelDiscountDelta = 'ParallelDiscountDelta' ParallelDiscountDeltaLocalCcy = 'ParallelDiscountDeltaLocalCcy' ParallelInflationDelta = 'ParallelInflationDelta' ParallelIndexDelta = 'ParallelIndexDelta' ParallelIndexDeltaLocalCcy = 'ParallelIndexDeltaLocalCcy' ParallelInflationDeltaLocalCcy = 'ParallelInflationDeltaLocalCcy' ParallelXccyDelta = 'ParallelXccyDelta' ParallelXccyDeltaLocalCcy = 'ParallelXccyDeltaLocalCcy' ParallelGamma = 'ParallelGamma' ParallelGammaLocalCcy = 'ParallelGammaLocalCcy' ParallelVega = 'ParallelVega' ParallelVegaLocalCcy = 'ParallelVegaLocalCcy' Premium_In_Cents = 'Premium In Cents' Premium = 'Premium' Resolved_Instrument_Values = 'Resolved Instrument Values' PNL = 'PNL' PnlExplain = 'PnlExplain' PnlExplainLocalCcy = 'PnlExplainLocalCcy' PnlPredict = 'PnlPredict' PV = 'PV' QuotedDelta = 'QuotedDelta' Spot = 'Spot' Spot_Rate = 'Spot Rate' Strike = 'Strike' Theta = 'Theta' Vanna = 'Vanna' Vega = 'Vega' VegaLocalCcy = 'VegaLocalCcy' Volga = 'Volga' Volatility = 'Volatility' XccyDelta = 'XccyDelta' def __repr__(self): return self.value class RiskMeasureUnit(EnumBase, Enum): """The unit of change of underlying in the risk computation.""" Percent = 'Percent' Dollar = 'Dollar' BPS = 'BPS' def __repr__(self): return self.value class ScenarioType(EnumBase, Enum): """Type of Scenario""" Spot_Vol = 'Spot Vol' Greeks = 'Greeks' def __repr__(self): return self.value class SettlementType(EnumBase, Enum): """Settlement Type""" Cash = 'Cash' Physical = 'Physical' def __repr__(self): return self.value class StrikeMethodType(EnumBase, Enum): Spread = 'Spread' Delta = 'Delta' Percentage_of_Price = 'Percentage of Price' Fixed = 'Fixed' def __repr__(self): return self.value class SwapClearingHouse(EnumBase, Enum): """Swap Clearing House""" LCH = 'LCH' EUREX = 'EUREX' JSCC = 'JSCC' CME = 'CME' NONE = 'NONE' def __repr__(self): return self.value class SwapSettlement(EnumBase, Enum): """Swap Settlement Type""" Phys_CLEARED = 'Phys.CLEARED' Physical = 'Physical' Cash_CollatCash = 'Cash.CollatCash' Cash_PYU = 'Cash.PYU' def __repr__(self): return self.value class TradeAs(EnumBase, Enum): """Option trade as (i.e. listed, otc, lookalike etc)""" Listed = 'Listed' Listed_Look_alike_OTC = 'Listed Look alike OTC' Flex = 'Flex' OTC = 'OTC' def __repr__(self): return self.value class TradeType(EnumBase, Enum): """Direction""" Buy = 'Buy' Sell = 'Sell' def __repr__(self): return self.value class UnderlierType(EnumBase, Enum): """Type of underlyer""" BBID = 'BBID' CUSIP = 'CUSIP' ISIN = 'ISIN' SEDOL = 'SEDOL' RIC = 'RIC' Ticker = 'Ticker' def __repr__(self): return self.value class ValuationTime(EnumBase, Enum): """The time of valuation, e.g. for an option""" MktClose = 'MktClose' MktOpen = 'MktOpen' SQ = 'SQ' def __repr__(self): return self.value class VarianceConvention(EnumBase, Enum): """Specifies whether the variance is Annualized or Total""" Annualized = 'Annualized' Total = 'Total' def __repr__(self): return self.value class AssetIdPriceable(Priceable): """An object to hold assetId when it can't be passed as a string.""" @camel_case_translate def __init__( self, asset_id: str = None, name: str = None ): super().__init__() self.asset_id = asset_id self.name = name @property def asset_id(self) -> str: """Marquee unique asset identifier.""" return self.__asset_id @asset_id.setter def asset_id(self, value: str): self._property_changed('asset_id') self.__asset_id = value class CSLDate(Base): """A date""" @camel_case_translate def __init__( self, date_value: datetime.date = None, name: str = None ): super().__init__() self.date_value = date_value self.name = name @property def date_value(self) -> datetime.date: """ISO 8601-formatted date""" return self.__date_value @date_value.setter def date_value(self, value: datetime.date): self._property_changed('date_value') self.__date_value = value class CSLDouble(Base): """A double""" @camel_case_translate def __init__( self, double_value: float = None, name: str = None ): super().__init__() self.double_value = double_value self.name = name @property def double_value(self) -> float: """The value""" return self.__double_value @double_value.setter def double_value(self, value: float): self._property_changed('double_value') self.__double_value = value class CSLFXCross(Base): """An FX cross""" @camel_case_translate def __init__( self, string_value: str = None, name: str = None ): super().__init__() self.string_value = string_value self.name = name @property def string_value(self) -> str: """Currency pair""" return self.__string_value @string_value.setter def string_value(self, value: str): self._property_changed('string_value') self.__string_value = value class CSLIndex(Base): """An index""" @camel_case_translate def __init__( self, string_value: str = None, name: str = None ): super().__init__() self.string_value = string_value self.name = name @property def string_value(self) -> str: """Display name of the asset""" return self.__string_value @string_value.setter def string_value(self, value: str): self._property_changed('string_value') self.__string_value = value class CSLSimpleSchedule(Base): """A fixing date, settlement date pair""" @camel_case_translate def __init__( self, fixing_date: datetime.date = None, settlement_date: datetime.date = None, name: str = None ): super().__init__() self.fixing_date = fixing_date self.settlement_date = settlement_date self.name = name @property def fixing_date(self) -> datetime.date: """ISO 8601-formatted date""" return self.__fixing_date @fixing_date.setter def fixing_date(self, value: datetime.date): self._property_changed('fixing_date') self.__fixing_date = value @property def settlement_date(self) -> datetime.date: """ISO 8601-formatted date""" return self.__settlement_date @settlement_date.setter def settlement_date(self, value: datetime.date): self._property_changed('settlement_date') self.__settlement_date = value class CSLStock(Base): """A stock""" @camel_case_translate def __init__( self, string_value: str = None, name: str = None ): super().__init__() self.string_value = string_value self.name = name @property def string_value(self) -> str: """Display name of the asset""" return self.__string_value @string_value.setter def string_value(self, value: str): self._property_changed('string_value') self.__string_value = value class CSLString(Base): """A string""" @camel_case_translate def __init__( self, string_value: str = None, name: str = None ): super().__init__() self.string_value = string_value self.name = name @property def string_value(self) -> str: """The value""" return self.__string_value @string_value.setter def string_value(self, value: str): self._property_changed('string_value') self.__string_value = value class CSLSymCaseNamedParam(Base): """A named case-sensitive string.""" @camel_case_translate def __init__( self, sym_case_value: str = None, name: str = None ): super().__init__() self.sym_case_value = sym_case_value self.name = name @property def sym_case_value(self) -> str: """A case-sensitive string""" return self.__sym_case_value @sym_case_value.setter def sym_case_value(self, value: str): self._property_changed('sym_case_value') self.__sym_case_value = value @property def name(self) -> str: """A name for the symbol""" return self.__name @name.setter def name(self, value: str): self._property_changed('name') self.__name = value class CompositeScenario(Base): """A scenario for composing scenarios""" @camel_case_translate def __init__( self, scenarios: tuple = None, name: str = None ): super().__init__() self.scenarios = scenarios self.name = name @property def scenario_type(self) -> str: """CompositeScenario""" return 'CompositeScenario' @property def scenarios(self) -> tuple: """The scenarios, in order""" return self.__scenarios @scenarios.setter def scenarios(self, value: tuple): self._property_changed('scenarios') self.__scenarios = value class EntitlementExclusions(Base): """Defines the exclusion entitlements of a given resource.""" @camel_case_translate def __init__( self, view: Tuple[Tuple[str, ...], ...] = None, edit: Tuple[Tuple[str, ...], ...] = None, admin: Tuple[Tuple[str, ...], ...] = None, rebalance: Tuple[Tuple[str, ...], ...] = None, execute: Tuple[Tuple[str, ...], ...] = None, trade: Tuple[Tuple[str, ...], ...] = None, upload: Tuple[Tuple[str, ...], ...] = None, query: Tuple[Tuple[str, ...], ...] = None, performance_details: Tuple[Tuple[str, ...], ...] = None, plot: Tuple[Tuple[str, ...], ...] = None, delete: Tuple[Tuple[str, ...], ...] = None, name: str = None ): super().__init__() self.view = view self.edit = edit self.admin = admin self.rebalance = rebalance self.execute = execute self.trade = trade self.upload = upload self.query = query self.performance_details = performance_details self.plot = plot self.delete = delete self.name = name @property def view(self) -> Tuple[Tuple[str, ...], ...]: return self.__view @view.setter def view(self, value: Tuple[Tuple[str, ...], ...]): self._property_changed('view') self.__view = value @property def edit(self) -> Tuple[Tuple[str, ...], ...]: return self.__edit @edit.setter def edit(self, value: Tuple[Tuple[str, ...], ...]): self._property_changed('edit') self.__edit = value @property def admin(self) -> Tuple[Tuple[str, ...], ...]: return self.__admin @admin.setter def admin(self, value: Tuple[Tuple[str, ...], ...]): self._property_changed('admin') self.__admin = value @property def rebalance(self) -> Tuple[Tuple[str, ...], ...]: return self.__rebalance @rebalance.setter def rebalance(self, value: Tuple[Tuple[str, ...], ...]): self._property_changed('rebalance') self.__rebalance = value @property def execute(self) -> Tuple[Tuple[str, ...], ...]: return self.__execute @execute.setter def execute(self, value: Tuple[Tuple[str, ...], ...]): self._property_changed('execute') self.__execute = value @property def trade(self) -> Tuple[Tuple[str, ...], ...]: return self.__trade @trade.setter def trade(self, value: Tuple[Tuple[str, ...], ...]): self._property_changed('trade') self.__trade = value @property def upload(self) -> Tuple[Tuple[str, ...], ...]: return self.__upload @upload.setter def upload(self, value: Tuple[Tuple[str, ...], ...]): self._property_changed('upload') self.__upload = value @property def query(self) -> Tuple[Tuple[str, ...], ...]: return self.__query @query.setter def query(self, value: Tuple[Tuple[str, ...], ...]): self._property_changed('query') self.__query = value @property def performance_details(self) -> Tuple[Tuple[str, ...], ...]: return self.__performance_details @performance_details.setter def performance_details(self, value: Tuple[Tuple[str, ...], ...]): self._property_changed('performance_details') self.__performance_details = value @property def plot(self) -> Tuple[Tuple[str, ...], ...]: return self.__plot @plot.setter def plot(self, value: Tuple[Tuple[str, ...], ...]): self._property_changed('plot') self.__plot = value @property def delete(self) -> Tuple[Tuple[str, ...], ...]: return self.__delete @delete.setter def delete(self, value: Tuple[Tuple[str, ...], ...]): self._property_changed('delete') self.__delete = value class Entitlements(Base): """Defines the entitlements of a given resource.""" @camel_case_translate def __init__( self, view: Tuple[str, ...] = None, edit: Tuple[str, ...] = None, admin: Tuple[str, ...] = None, rebalance: Tuple[str, ...] = None, execute: Tuple[str, ...] = None, trade: Tuple[str, ...] = None, upload: Tuple[str, ...] = None, query: Tuple[str, ...] = None, performance_details: Tuple[str, ...] = None, plot: Tuple[str, ...] = None, delete: Tuple[str, ...] = None, display: Tuple[str, ...] = None, name: str = None ): super().__init__() self.view = view self.edit = edit self.admin = admin self.rebalance = rebalance self.execute = execute self.trade = trade self.upload = upload self.query = query self.performance_details = performance_details self.plot = plot self.delete = delete self.display = display self.name = name @property def view(self) -> Tuple[str, ...]: """Permission to view the resource and its contents""" return self.__view @view.setter def view(self, value: Tuple[str, ...]): self._property_changed('view') self.__view = value @property def edit(self) -> Tuple[str, ...]: """Permission to edit details about the resource content, excluding entitlements. Can also delete the resource""" return self.__edit @edit.setter def edit(self, value: Tuple[str, ...]): self._property_changed('edit') self.__edit = value @property def admin(self) -> Tuple[str, ...]: """Permission to edit all details of the resource, including entitlements. Can also delete the resource""" return self.__admin @admin.setter def admin(self, value: Tuple[str, ...]): self._property_changed('admin') self.__admin = value @property def rebalance(self) -> Tuple[str, ...]: """Permission to rebalance the constituent weights of the resource""" return self.__rebalance @rebalance.setter def rebalance(self, value: Tuple[str, ...]): self._property_changed('rebalance') self.__rebalance = value @property def execute(self) -> Tuple[str, ...]: """Permission to execute functions and/or reports with the resource""" return self.__execute @execute.setter def execute(self, value: Tuple[str, ...]): self._property_changed('execute') self.__execute = value @property def trade(self) -> Tuple[str, ...]: """Permission to trade the resource""" return self.__trade @trade.setter def trade(self, value: Tuple[str, ...]): self._property_changed('trade') self.__trade = value @property def upload(self) -> Tuple[str, ...]: """Permission to upload data to the given resource""" return self.__upload @upload.setter def upload(self, value: Tuple[str, ...]): self._property_changed('upload') self.__upload = value @property def query(self) -> Tuple[str, ...]: """Permission to query data from the given resource""" return self.__query @query.setter def query(self, value: Tuple[str, ...]): self._property_changed('query') self.__query = value @property def performance_details(self) -> Tuple[str, ...]: """Permission to view the resource, it's entire contents, and related data""" return self.__performance_details @performance_details.setter def performance_details(self, value: Tuple[str, ...]): self._property_changed('performance_details') self.__performance_details = value @property def plot(self) -> Tuple[str, ...]: """Permission to plot data from the given resource""" return self.__plot @plot.setter def plot(self, value: Tuple[str, ...]): self._property_changed('plot') self.__plot = value @property def delete(self) -> Tuple[str, ...]: """Permission to delete the resource""" return self.__delete @delete.setter def delete(self, value: Tuple[str, ...]): self._property_changed('delete') self.__delete = value @property def display(self) -> Tuple[str, ...]: """Permission to query data for web router request so that it can prevent programmatic access (api access) to the licensed data.""" return self.__display @display.setter def display(self, value: Tuple[str, ...]): self._property_changed('display') self.__display = value class ISelectNewUnit(Base): @camel_case_translate def __init__( self, id_: str, new_units: float = None, name: str = None ): super().__init__() self.__id = id_ self.new_units = new_units self.name = name @property def id(self) -> str: return self.__id @id.setter def id(self, value: str): self._property_changed('id') self.__id = value @property def new_units(self) -> float: return self.__new_units @new_units.setter def new_units(self, value: float): self._property_changed('new_units') self.__new_units = value class ISelectNewWeight(Base): @camel_case_translate def __init__( self, id_: str, new_weight: float = None, name: str = None ): super().__init__() self.__id = id_ self.new_weight = new_weight self.name = name @property def id(self) -> str: return self.__id @id.setter def id(self, value: str): self._property_changed('id') self.__id = value @property def new_weight(self) -> float: return self.__new_weight @new_weight.setter def new_weight(self, value: float): self._property_changed('new_weight') self.__new_weight = value class Identifier(Base): @camel_case_translate def __init__( self, type_: str = None, value: str = None, name: str = None ): super().__init__() self.__type = type_ self.value = value self.name = name @property def type(self) -> str: """Identifier type code""" return self.__type @type.setter def type(self, value: str): self._property_changed('type') self.__type = value @property def value(self) -> str: """Identifier value""" return self.__value @value.setter def value(self, value: str): self._property_changed('value') self.__value = value class LiborFallbackScenario(Scenario): """A scenario to change the libor rate to RFR + Spread after Dec2021 in the index curve""" @camel_case_translate def __init__( self, date: datetime.date = None, fallback_type: str = 'RFR', discounting: bool = False, cash_flows: bool = True, name: str = None ): super().__init__() self.date = date self.fallback_type = fallback_type self.discounting = discounting self.cash_flows = cash_flows self.name = name @property def scenario_type(self) -> str: """LiborFallbackScenario""" return 'LiborFallbackScenario' @property def date(self) -> datetime.date: """Announce Date For the Libor Fallback""" return self.__date @date.setter def date(self, value: datetime.date): self._property_changed('date') self.__date = value @property def fallback_type(self) -> str: """Different Rules for Libor Fallback""" return self.__fallback_type @fallback_type.setter def fallback_type(self, value: str): self._property_changed('fallback_type') self.__fallback_type = value @property def discounting(self) -> bool: """If True, use RFR Discounting, otherwise continue to use libor discounting""" return self.__discounting @discounting.setter def discounting(self, value: bool): self._property_changed('discounting') self.__discounting = value @property def cash_flows(self) -> bool: """If True, use RFR + Spread when projecting cash flows, otherwise continue to use libor""" return self.__cash_flows @cash_flows.setter def cash_flows(self, value: bool): self._property_changed('cash_flows') self.__cash_flows = value class Link(Base): """Hyperlink""" @camel_case_translate def __init__( self, title: str = None, source: str = None, name: str = None ): super().__init__() self.title = title self.source = source self.name = name @property def title(self) -> str: """display text""" return self.__title @title.setter def title(self, value: str): self._property_changed('title') self.__title = value @property def source(self) -> str: """link""" return self.__source @source.setter def source(self, value: str): self._property_changed('source') self.__source = value class LiquidityReportParameters(Base): """Parameters to be used on liquidity reports""" @camel_case_translate def __init__( self, title: str = None, email: str = None, trading_desk: str = None, name: str = None ): super().__init__() self.title = title self.email = email self.trading_desk = trading_desk self.name = name @property def title(self) -> str: """Report title""" return self.__title @title.setter def title(self, value: str): self._property_changed('title') self.__title = value @property def email(self) -> str: return self.__email @email.setter def email(self, value: str): self._property_changed('email') self.__email = value @property def trading_desk(self) -> str: return self.__trading_desk @trading_desk.setter def trading_desk(self, value: str): self._property_changed('trading_desk') self.__trading_desk = value class MarketDataCoordinate(Base): """Object representation of a market data coordinate""" @camel_case_translate def __init__( self, mkt_type: str = None, mkt_asset: str = None, mkt_class: str = None, mkt_point: Tuple[str, ...] = None, mkt_quoting_style: str = None, name: str = None ): super().__init__() self.mkt_type = mkt_type self.mkt_asset = mkt_asset self.mkt_class = mkt_class self.mkt_point = mkt_point self.mkt_quoting_style = mkt_quoting_style self.name = name @property def mkt_type(self) -> str: """The MDAPI Type, e.g. IR, IR BASIS, FX, FX Vol""" return self.__mkt_type @mkt_type.setter def mkt_type(self, value: str): self._property_changed('mkt_type') self.__mkt_type = value @property def mkt_asset(self) -> str: """The MDAPI Asset, e.g. USD, EUR-EURIBOR-Telerate, WTI""" return self.__mkt_asset @mkt_asset.setter def mkt_asset(self, value: str): self._property_changed('mkt_asset') self.__mkt_asset = value @property def mkt_class(self) -> str: """The MDAPI Class, e.g. Swap, Cash.""" return self.__mkt_class @mkt_class.setter def mkt_class(self, value: str): self._property_changed('mkt_class') self.__mkt_class = value @property def mkt_point(self) -> Tuple[str, ...]: """The MDAPI Point, e.g. 3m, 10y, 11y, Dec19""" return self.__mkt_point @mkt_point.setter def mkt_point(self, value: Tuple[str, ...]): self._property_changed('mkt_point') self.__mkt_point = value @property def mkt_quoting_style(self) -> str: return self.__mkt_quoting_style @mkt_quoting_style.setter def mkt_quoting_style(self, value: str): self._property_changed('mkt_quoting_style') self.__mkt_quoting_style = value class Op(Base): """Operations for searches.""" @camel_case_translate def __init__( self, gte: Union[datetime.date, float] = None, lte: Union[datetime.date, float] = None, lt: Union[datetime.date, float] = None, gt: Union[datetime.date, float] = None, name: str = None ): super().__init__() self.gte = gte self.lte = lte self.lt = lt self.gt = gt self.name = name @property def gte(self) -> Union[datetime.date, float]: """search for values greater than or equal.""" return self.__gte @gte.setter def gte(self, value: Union[datetime.date, float]): self._property_changed('gte') self.__gte = value @property def lte(self) -> Union[datetime.date, float]: """search for values less than or equal to.""" return self.__lte @lte.setter def lte(self, value: Union[datetime.date, float]): self._property_changed('lte') self.__lte = value @property def lt(self) -> Union[datetime.date, float]: """search for values less than.""" return self.__lt @lt.setter def lt(self, value: Union[datetime.date, float]): self._property_changed('lt') self.__lt = value @property def gt(self) -> Union[datetime.date, float]: """search for values greater than.""" return self.__gt @gt.setter def gt(self, value: Union[datetime.date, float]): self._property_changed('gt') self.__gt = value class PCOBenchmarkOptions(Base): """Parameters required for PCO Benchmark""" @camel_case_translate def __init__( self, name: str = None, target_ratio: str = None ): super().__init__() self.name = name self.target_ratio = target_ratio @property def name(self) -> str: """Benchmark name""" return self.__name @name.setter def name(self, value: str): self._property_changed('name') self.__name = value @property def target_ratio(self) -> str: return self.__target_ratio @target_ratio.setter def target_ratio(self, value: str): self._property_changed('target_ratio') self.__target_ratio = value class PCOExposureAdjustments(Base): """Parameters required for PCO Exposure Adjustments""" @camel_case_translate def __init__( self, pco_number_as_string: str = None, net_subscription_redemption: str = None, net_subscription_redemption_limits: Tuple[str, ...] = None, adjustment_vs_subscription_redemption: str = None, adjustment_vs_subscription_redemption_limits: Tuple[str, ...] = None, name: str = None ): super().__init__() self.pco_number_as_string = pco_number_as_string self.net_subscription_redemption = net_subscription_redemption self.net_subscription_redemption_limits = net_subscription_redemption_limits self.adjustment_vs_subscription_redemption = adjustment_vs_subscription_redemption self.adjustment_vs_subscription_redemption_limits = adjustment_vs_subscription_redemption_limits self.name = name @property def pco_number_as_string(self) -> str: return self.__pco_number_as_string @pco_number_as_string.setter def pco_number_as_string(self, value: str): self._property_changed('pco_number_as_string') self.__pco_number_as_string = value @property def net_subscription_redemption(self) -> str: """net subscription and redemption""" return self.__net_subscription_redemption @net_subscription_redemption.setter def net_subscription_redemption(self, value: str): self._property_changed('net_subscription_redemption') self.__net_subscription_redemption = value @property def net_subscription_redemption_limits(self) -> Tuple[str, ...]: """Upper and lower limit of subscription and redemption adjustment""" return self.__net_subscription_redemption_limits @net_subscription_redemption_limits.setter def net_subscription_redemption_limits(self, value: Tuple[str, ...]): self._property_changed('net_subscription_redemption_limits') self.__net_subscription_redemption_limits = value @property def adjustment_vs_subscription_redemption(self) -> str: """subscription and redemption adjustment""" return self.__adjustment_vs_subscription_redemption @adjustment_vs_subscription_redemption.setter def adjustment_vs_subscription_redemption(self, value: str): self._property_changed('adjustment_vs_subscription_redemption') self.__adjustment_vs_subscription_redemption = value @property def adjustment_vs_subscription_redemption_limits(self) -> Tuple[str, ...]: """Upper and lower limit of subscription and redemption adjustment""" return self.__adjustment_vs_subscription_redemption_limits @adjustment_vs_subscription_redemption_limits.setter def adjustment_vs_subscription_redemption_limits(self, value: Tuple[str, ...]): self._property_changed('adjustment_vs_subscription_redemption_limits') self.__adjustment_vs_subscription_redemption_limits = value class PCOMtMHistoricalData(Base): """Parameters required for PCO Unrealised Mark to Market Historical Data""" @camel_case_translate def __init__( self, value: str = None, timestamp: datetime.datetime = None, name: str = None ): super().__init__() self.value = value self.timestamp = timestamp self.name = name @property def value(self) -> str: """Indicative unrealised mark to market value""" return self.__value @value.setter def value(self, value: str): self._property_changed('value') self.__value = value @property def timestamp(self) -> datetime.datetime: """Timestamp of unrealised mark to market of open trade for a currency""" return self.__timestamp @timestamp.setter def timestamp(self, value: datetime.datetime): self._property_changed('timestamp') self.__timestamp = value class PCOSettlementsData(Base): """Parameters required for a PCO Settlement""" @camel_case_translate def __init__( self, timestamp: datetime.datetime = None, settlement: str = None, name: str = None ): super().__init__() self.timestamp = timestamp self.settlement = settlement self.name = name @property def timestamp(self) -> datetime.datetime: """Timestamp of settlement""" return self.__timestamp @timestamp.setter def timestamp(self, value: datetime.datetime): self._property_changed('timestamp') self.__timestamp = value @property def settlement(self) -> str: """Settlement value""" return self.__settlement @settlement.setter def settlement(self, value: str): self._property_changed('settlement') self.__settlement = value class PCOTargetDeviationData(Base): """Parameters required for a Target Deviation data""" @camel_case_translate def __init__( self, value: str = None, timestamp: datetime.datetime = None, name: str = None ): super().__init__() self.value = value self.timestamp = timestamp self.name = name @property def value(self) -> str: """Target deviation value""" return self.__value @value.setter def value(self, value: str): self._property_changed('value') self.__value = value @property def timestamp(self) -> datetime.datetime: """Timestamp""" return self.__timestamp @timestamp.setter def timestamp(self, value: datetime.datetime): self._property_changed('timestamp') self.__timestamp = value class PerformanceStats(Base): """Performance statistics.""" @camel_case_translate def __init__( self, alpha: float = None, annualized_return: float = None, annualized_volatility: float = None, average_return: float = None, average_value: float = None, average_volume_last_month: float = None, best_month: float = None, best_month_date: datetime.date = None, beta: float = None, close_price: float = None, correlation: float = None, current_value: float = None, drawdown_over_return: float = None, high: float = None, high_eod: float = None, last_change: float = None, last_change_pct: float = None, last_date: datetime.date = None, last_value: float = None, low: float = None, low_eod: float = None, max_draw_down: float = None, max_draw_down_duration: int = None, open_price: float = None, positive_months: float = None, sharpe_ratio: float = None, sortino_ratio: float = None, worst_month: float = None, worst_month_date: datetime.date = None, total_return: float = None, volume: float = None, name: str = None ): super().__init__() self.alpha = alpha self.annualized_return = annualized_return self.annualized_volatility = annualized_volatility self.average_return = average_return self.average_value = average_value self.average_volume_last_month = average_volume_last_month self.best_month = best_month self.best_month_date = best_month_date self.beta = beta self.close_price = close_price self.correlation = correlation self.current_value = current_value self.drawdown_over_return = drawdown_over_return self.high = high self.high_eod = high_eod self.last_change = last_change self.last_change_pct = last_change_pct self.last_date = last_date self.last_value = last_value self.low = low self.low_eod = low_eod self.max_draw_down = max_draw_down self.max_draw_down_duration = max_draw_down_duration self.open_price = open_price self.positive_months = positive_months self.sharpe_ratio = sharpe_ratio self.sortino_ratio = sortino_ratio self.worst_month = worst_month self.worst_month_date = worst_month_date self.total_return = total_return self.volume = volume self.name = name @property def alpha(self) -> float: """Measure of performance compared to a market benchmark.""" return self.__alpha @alpha.setter def alpha(self, value: float): self._property_changed('alpha') self.__alpha = value @property def annualized_return(self) -> float: """Compounded Annual Growth Rate (CAGR).""" return self.__annualized_return @annualized_return.setter def annualized_return(self, value: float): self._property_changed('annualized_return') self.__annualized_return = value @property def annualized_volatility(self) -> float: """Standard deviation of daily returns, annualized.""" return self.__annualized_volatility @annualized_volatility.setter def annualized_volatility(self, value: float): self._property_changed('annualized_volatility') self.__annualized_volatility = value @property def average_return(self) -> float: """Average of the performance returns.""" return self.__average_return @average_return.setter def average_return(self, value: float): self._property_changed('average_return') self.__average_return = value @property def average_value(self) -> float: """Average value.""" return self.__average_value @average_value.setter def average_value(self, value: float): self._property_changed('average_value') self.__average_value = value @property def average_volume_last_month(self) -> float: """30 day average volume.""" return self.__average_volume_last_month @average_volume_last_month.setter def average_volume_last_month(self, value: float): self._property_changed('average_volume_last_month') self.__average_volume_last_month = value @property def best_month(self) -> float: """Best monthly return (first to last day of month).""" return self.__best_month @best_month.setter def best_month(self, value: float): self._property_changed('best_month') self.__best_month = value @property def best_month_date(self) -> datetime.date: """Best monthly return date (first to last day of month).""" return self.__best_month_date @best_month_date.setter def best_month_date(self, value: datetime.date): self._property_changed('best_month_date') self.__best_month_date = value @property def beta(self) -> float: """Measure of volatility compared to a market benchmark.""" return self.__beta @beta.setter def beta(self, value: float): self._property_changed('beta') self.__beta = value @property def close_price(self) -> float: """previous close price.""" return self.__close_price @close_price.setter def close_price(self, value: float): self._property_changed('close_price') self.__close_price = value @property def correlation(self) -> float: """Pearson correlation.""" return self.__correlation @correlation.setter def correlation(self, value: float): self._property_changed('correlation') self.__correlation = value @property def current_value(self) -> float: """Current value.""" return self.__current_value @current_value.setter def current_value(self, value: float): self._property_changed('current_value') self.__current_value = value @property def drawdown_over_return(self) -> float: """Maximum drawdown divided by annualized return.""" return self.__drawdown_over_return @drawdown_over_return.setter def drawdown_over_return(self, value: float): self._property_changed('drawdown_over_return') self.__drawdown_over_return = value @property def high(self) -> float: """Highest real time price for the previous 24 hours.""" return self.__high @high.setter def high(self, value: float): self._property_changed('high') self.__high = value @property def high_eod(self) -> float: """Highest end of day price.""" return self.__high_eod @high_eod.setter def high_eod(self, value: float): self._property_changed('high_eod') self.__high_eod = value @property def last_change(self) -> float: """Last published value.""" return self.__last_change @last_change.setter def last_change(self, value: float): self._property_changed('last_change') self.__last_change = value @property def last_change_pct(self) -> float: """Last change in percent.""" return self.__last_change_pct @last_change_pct.setter def last_change_pct(self, value: float): self._property_changed('last_change_pct') self.__last_change_pct = value @property def last_date(self) -> datetime.date: """Last publication date.""" return self.__last_date @last_date.setter def last_date(self, value: datetime.date): self._property_changed('last_date') self.__last_date = value @property def last_value(self) -> float: """Last published value.""" return self.__last_value @last_value.setter def last_value(self, value: float): self._property_changed('last_value') self.__last_value = value @property def low(self) -> float: """Lowest real time price for the previous 24 hours.""" return self.__low @low.setter def low(self, value: float): self._property_changed('low') self.__low = value @property def low_eod(self) -> float: """Lowest end of day price.""" return self.__low_eod @low_eod.setter def low_eod(self, value: float): self._property_changed('low_eod') self.__low_eod = value @property def max_draw_down(self) -> float: """Maximum peak to trough percentage drawdown.""" return self.__max_draw_down @max_draw_down.setter def max_draw_down(self, value: float): self._property_changed('max_draw_down') self.__max_draw_down = value @property def max_draw_down_duration(self) -> int: """Amount of time in days between beginning and end of drawdown.""" return self.__max_draw_down_duration @max_draw_down_duration.setter def max_draw_down_duration(self, value: int): self._property_changed('max_draw_down_duration') self.__max_draw_down_duration = value @property def open_price(self) -> float: """Open price.""" return self.__open_price @open_price.setter def open_price(self, value: float): self._property_changed('open_price') self.__open_price = value @property def positive_months(self) -> float: """Percentage of months that performed positively.""" return self.__positive_months @positive_months.setter def positive_months(self, value: float): self._property_changed('positive_months') self.__positive_months = value @property def sharpe_ratio(self) -> float: """Annualized return of the series minus risk free rate (accrued daily) divided by annual volatility.""" return self.__sharpe_ratio @sharpe_ratio.setter def sharpe_ratio(self, value: float): self._property_changed('sharpe_ratio') self.__sharpe_ratio = value @property def sortino_ratio(self) -> float: """Annualized return of the series minus risk free rate (accrued daily) divided by annual volatility of negative returns.""" return self.__sortino_ratio @sortino_ratio.setter def sortino_ratio(self, value: float): self._property_changed('sortino_ratio') self.__sortino_ratio = value @property def worst_month(self) -> float: """Worst monthly return (first to last day of month).""" return self.__worst_month @worst_month.setter def worst_month(self, value: float): self._property_changed('worst_month') self.__worst_month = value @property def worst_month_date(self) -> datetime.date: """Worst monthly return date (first to last day of month).""" return self.__worst_month_date @worst_month_date.setter def worst_month_date(self, value: datetime.date): self._property_changed('worst_month_date') self.__worst_month_date = value @property def total_return(self) -> float: """Total return.""" return self.__total_return @total_return.setter def total_return(self, value: float): self._property_changed('total_return') self.__total_return = value @property def volume(self) -> float: """volume.""" return self.__volume @volume.setter def volume(self, value: float): self._property_changed('volume') self.__volume = value class RiskPosition(Base): @camel_case_translate def __init__( self, instrument: Priceable, quantity: float = None, name: str = None ): super().__init__() self.instrument = instrument self.quantity = quantity self.name = name @property def instrument(self) -> Priceable: """Instrument or Id To specify a Marquee asset use the asset Id. For listed products use an XRef, e.g. { 'bid': 'NGZ19 Comdty' }, { 'isin': 'US912810SD19' }. To specify an instrument use one of the listed types""" return self.__instrument @instrument.setter def instrument(self, value: Priceable): self._property_changed('instrument') self.__instrument = value @property def quantity(self) -> float: """Quantity of instrument""" return self.__quantity @quantity.setter def quantity(self, value: float): self._property_changed('quantity') self.__quantity = value class RiskRequestParameters(Base): """Parameters for the risk request""" @camel_case_translate def __init__( self, csa_term: str = None, raw_results: bool = False, use_historical_diddles_only: bool = False, name: str = None ): super().__init__() self.csa_term = csa_term self.raw_results = raw_results self.use_historical_diddles_only = use_historical_diddles_only self.name = name @property def csa_term(self) -> str: """The CSA Term for CSA specific discounting, e.g. EUR-1""" return self.__csa_term @csa_term.setter def csa_term(self, value: str): self._property_changed('csa_term') self.__csa_term = value @property def raw_results(self) -> bool: return self.__raw_results @raw_results.setter def raw_results(self, value: bool): self._property_changed('raw_results') self.__raw_results = value @property def use_historical_diddles_only(self) -> bool: """Enables a backward compatible pricing mode""" return self.__use_historical_diddles_only @use_historical_diddles_only.setter def use_historical_diddles_only(self, value: bool): self._property_changed('use_historical_diddles_only') self.__use_historical_diddles_only = value class SimpleParty(Base): @camel_case_translate def __init__( self, party_type: str = None, party_name: str = None, party_book: str = None, name: str = None ): super().__init__() self.party_type = party_type self.party_name = party_name self.party_book = party_book self.name = name @property def party_type(self) -> str: return self.__party_type @party_type.setter def party_type(self, value: str): self._property_changed('party_type') self.__party_type = value @property def party_name(self) -> str: return self.__party_name @party_name.setter def party_name(self, value: str): self._property_changed('party_name') self.__party_name = value @property def party_book(self) -> str: return self.__party_book @party_book.setter def party_book(self, value: str): self._property_changed('party_book') self.__party_book = value class SocialDomain(Base): @camel_case_translate def __init__( self, onboarded: dict, returns_enabled: bool = None, name: str = None ): super().__init__() self.onboarded = onboarded self.returns_enabled = returns_enabled self.name = name @property def onboarded(self) -> dict: return self.__onboarded @onboarded.setter def onboarded(self, value: dict): self._property_changed('onboarded') self.__onboarded = value @property def returns_enabled(self) -> bool: """True if the fund has returns enabled""" return self.__returns_enabled @returns_enabled.setter def returns_enabled(self, value: bool): self._property_changed('returns_enabled') self.__returns_enabled = value class TimeFilter(Base): """Filter to restrict data to a range of hours per day.""" @camel_case_translate def __init__( self, start_hours: str, end_hours: str, time_zone: str, name: str = None ): super().__init__() self.start_hours = start_hours self.end_hours = end_hours self.time_zone = time_zone self.name = name @property def start_hours(self) -> str: """Start hours in the format HH::MM::SS after which the data will be shown. Data is inclusive of the startHours value.""" return self.__start_hours @start_hours.setter def start_hours(self, value: str): self._property_changed('start_hours') self.__start_hours = value @property def end_hours(self) -> str: """End hours in the format HH::MM::SS up to which the data will be shown. Data is exclusive of the endHours value with a precision of 1 second.""" return self.__end_hours @end_hours.setter def end_hours(self, value: str): self._property_changed('end_hours') self.__end_hours = value @property def time_zone(self) -> str: """The time zone with respect to which the start and end hours will be applied (must be a valid IANA TimeZone identifier).""" return self.__time_zone @time_zone.setter def time_zone(self, value: str): self._property_changed('time_zone') self.__time_zone = value class WeightedPosition(Base): @camel_case_translate def __init__( self, asset_id: str, weight: float, name: str = None ): super().__init__() self.asset_id = asset_id self.weight = weight self.name = name @property def asset_id(self) -> str: """Marquee unique identifier""" return self.__asset_id @asset_id.setter def asset_id(self, value: str): self._property_changed('asset_id') self.__asset_id = value @property def weight(self) -> float: """Relative net weight of the given position""" return self.__weight @weight.setter def weight(self, value: float): self._property_changed('weight') self.__weight = value class XRef(Priceable): @camel_case_translate def __init__( self, ric: str = None, rcic: str = None, eid: str = None, gsideid: str = None, gsid: str = None, gsid_equivalent: str = None, cid: str = None, bbid: str = None, bcid: str = None, delisted: str = None, bbid_equivalent: str = None, cusip: str = None, gss: str = None, isin: str = None, jsn: str = None, prime_id: str = None, sedol: str = None, ticker: str = None, valoren: str = None, wpk: str = None, gsn: str = None, sec_name: str = None, cross: str = None, simon_id: str = None, em_id: str = None, cm_id: str = None, lms_id: str = None, tdapi: str = None, mdapi: str = None, mdapi_class: str = None, mic: str = None, sf_id: str = None, dollar_cross: str = None, mq_symbol: str = None, primary_country_ric: str = None, pnode_id: str = None, wi_id: str = None, ps_id: str = None, pl_id: str = None, exchange_code: str = None, plot_id: str = None, name: str = None ): super().__init__() self.ric = ric self.rcic = rcic self.eid = eid self.gsideid = gsideid self.gsid = gsid self.gsid_equivalent = gsid_equivalent self.cid = cid self.bbid = bbid self.bcid = bcid self.delisted = delisted self.bbid_equivalent = bbid_equivalent self.cusip = cusip self.gss = gss self.isin = isin self.jsn = jsn self.prime_id = prime_id self.sedol = sedol self.ticker = ticker self.valoren = valoren self.wpk = wpk self.gsn = gsn self.sec_name = sec_name self.cross = cross self.simon_id = simon_id self.em_id = em_id self.cm_id = cm_id self.lms_id = lms_id self.tdapi = tdapi self.mdapi = mdapi self.mdapi_class = mdapi_class self.mic = mic self.sf_id = sf_id self.dollar_cross = dollar_cross self.mq_symbol = mq_symbol self.primary_country_ric = primary_country_ric self.pnode_id = pnode_id self.wi_id = wi_id self.ps_id = ps_id self.pl_id = pl_id self.exchange_code = exchange_code self.plot_id = plot_id self.name = name @property def ric(self) -> str: """Reuters Instrument Code identifier""" return self.__ric @ric.setter def ric(self, value: str): self._property_changed('ric') self.__ric = value @property def rcic(self) -> str: """Reuters Composite Instrument Code Identifier""" return self.__rcic @rcic.setter def rcic(self, value: str): self._property_changed('rcic') self.__rcic = value @property def eid(self) -> str: """EID Identifier""" return self.__eid @eid.setter def eid(self, value: str): self._property_changed('eid') self.__eid = value @property def gsideid(self) -> str: """GSID_EID Identifier""" return self.__gsideid @gsideid.setter def gsideid(self, value: str): self._property_changed('gsideid') self.__gsideid = value @property def gsid(self) -> str: """GSID Identifier""" return self.__gsid @gsid.setter def gsid(self, value: str): self._property_changed('gsid') self.__gsid = value @property def gsid_equivalent(self) -> str: """GSID Equivalent Identifier""" return self.__gsid_equivalent @gsid_equivalent.setter def gsid_equivalent(self, value: str): self._property_changed('gsid_equivalent') self.__gsid_equivalent = value @property def cid(self) -> str: """Company Id Identifier""" return self.__cid @cid.setter def cid(self, value: str): self._property_changed('cid') self.__cid = value @property def bbid(self) -> str: """Bloomberg Id Identifier""" return self.__bbid @bbid.setter def bbid(self, value: str): self._property_changed('bbid') self.__bbid = value @property def bcid(self) -> str: """Bloomberg Composite Identifier""" return self.__bcid @bcid.setter def bcid(self, value: str): self._property_changed('bcid') self.__bcid = value @property def delisted(self) -> str: """Whether an asset has been delisted""" return self.__delisted @delisted.setter def delisted(self, value: str): self._property_changed('delisted') self.__delisted = value @property def bbid_equivalent(self) -> str: """Bloomberg Equivalent Identifier""" return self.__bbid_equivalent @bbid_equivalent.setter def bbid_equivalent(self, value: str): self._property_changed('bbid_equivalent') self.__bbid_equivalent = value @property def cusip(self) -> str: """Cusip Identifier""" return self.__cusip @cusip.setter def cusip(self, value: str): self._property_changed('cusip') self.__cusip = value @property def gss(self) -> str: """GS Symbol identifier""" return self.__gss @gss.setter def gss(self, value: str): self._property_changed('gss') self.__gss = value @property def isin(self) -> str: """International Security Number""" return self.__isin @isin.setter def isin(self, value: str): self._property_changed('isin') self.__isin = value @property def jsn(self) -> str: """Japan Security Number""" return self.__jsn @jsn.setter def jsn(self, value: str): self._property_changed('jsn') self.__jsn = value @property def prime_id(self) -> str: """PrimeID Identifier""" return self.__prime_id @prime_id.setter def prime_id(self, value: str): self._property_changed('prime_id') self.__prime_id = value @property def sedol(self) -> str: """Sedol Identifier""" return self.__sedol @sedol.setter def sedol(self, value: str): self._property_changed('sedol') self.__sedol = value @property def ticker(self) -> str: """Ticker Identifier""" return self.__ticker @ticker.setter def ticker(self, value: str): self._property_changed('ticker') self.__ticker = value @property def valoren(self) -> str: """Valoren Identifier""" return self.__valoren @valoren.setter def valoren(self, value: str): self._property_changed('valoren') self.__valoren = value @property def wpk(self) -> str: """Wertpapier Kenn-Nummer""" return self.__wpk @wpk.setter def wpk(self, value: str): self._property_changed('wpk') self.__wpk = value @property def gsn(self) -> str: """Goldman Sachs internal product number""" return self.__gsn @gsn.setter def gsn(self, value: str): self._property_changed('gsn') self.__gsn = value @property def sec_name(self) -> str: """Internal Goldman Sachs security name""" return self.__sec_name @sec_name.setter def sec_name(self, value: str): self._property_changed('sec_name') self.__sec_name = value @property def cross(self) -> str: """Cross identifier""" return self.__cross @cross.setter def cross(self, value: str): self._property_changed('cross') self.__cross = value @property def simon_id(self) -> str: """SIMON product identifier""" return self.__simon_id @simon_id.setter def simon_id(self, value: str): self._property_changed('simon_id') self.__simon_id = value @property def em_id(self) -> str: """Entity Master Identifier""" return self.__em_id @em_id.setter def em_id(self, value: str): self._property_changed('em_id') self.__em_id = value @property def cm_id(self) -> str: """Client Master Party Id""" return self.__cm_id @cm_id.setter def cm_id(self, value: str): self._property_changed('cm_id') self.__cm_id = value @property def lms_id(self) -> str: """Listed Market Symbol""" return self.__lms_id @lms_id.setter def lms_id(self, value: str): self._property_changed('lms_id') self.__lms_id = value @property def tdapi(self) -> str: """TDAPI Description""" return self.__tdapi @tdapi.setter def tdapi(self, value: str): self._property_changed('tdapi') self.__tdapi = value @property def mdapi(self) -> str: """MDAPI Asset""" return self.__mdapi @mdapi.setter def mdapi(self, value: str): self._property_changed('mdapi') self.__mdapi = value @property def mdapi_class(self) -> str: """MDAPI Asset Class""" return self.__mdapi_class @mdapi_class.setter def mdapi_class(self, value: str): self._property_changed('mdapi_class') self.__mdapi_class = value @property def mic(self) -> str: """Market Identifier Code""" return self.__mic @mic.setter def mic(self, value: str): self._property_changed('mic') self.__mic = value @property def sf_id(self) -> str: """SalesForce ID""" return self.__sf_id @sf_id.setter def sf_id(self, value: str): self._property_changed('sf_id') self.__sf_id = value @property def dollar_cross(self) -> str: """USD cross identifier for a particular currency""" return self.__dollar_cross @dollar_cross.setter def dollar_cross(self, value: str): self._property_changed('dollar_cross') self.__dollar_cross = value @property def mq_symbol(self) -> str: """Marquee Symbol for generic MQ entities""" return self.__mq_symbol @mq_symbol.setter def mq_symbol(self, value: str): self._property_changed('mq_symbol') self.__mq_symbol = value @property def primary_country_ric(self) -> str: """Reuters Primary Country Instrument Code Identifier""" return self.__primary_country_ric @primary_country_ric.setter def primary_country_ric(self, value: str): self._property_changed('primary_country_ric') self.__primary_country_ric = value @property def pnode_id(self) -> str: """Pricing node identifier sourced from Morningstar""" return self.__pnode_id @pnode_id.setter def pnode_id(self, value: str): self._property_changed('pnode_id') self.__pnode_id = value @property def wi_id(self) -> str: """Weather Index Identifier""" return self.__wi_id @wi_id.setter def wi_id(self, value: str): self._property_changed('wi_id') self.__wi_id = value @property def ps_id(self) -> str: """Platts Symbol""" return self.__ps_id @ps_id.setter def ps_id(self, value: str): self._property_changed('ps_id') self.__ps_id = value @property def pl_id(self) -> str: """Platts Symbol Name""" return self.__pl_id @pl_id.setter def pl_id(self, value: str): self._property_changed('pl_id') self.__pl_id = value @property def exchange_code(self) -> str: """EEX Exchange Code""" return self.__exchange_code @exchange_code.setter def exchange_code(self, value: str): self._property_changed('exchange_code') self.__exchange_code = value @property def plot_id(self) -> str: """Plot Identifier""" return self.__plot_id @plot_id.setter def plot_id(self, value: str): self._property_changed('plot_id') self.__plot_id = value class CSLCurrency(Base): """A currency""" @camel_case_translate def __init__( self, string_value: Union[Currency, str] = None, name: str = None ): super().__init__() self.string_value = string_value self.name = name @property def string_value(self) -> Union[Currency, str]: """Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)""" return self.__string_value @string_value.setter def string_value(self, value: Union[Currency, str]): self._property_changed('string_value') self.__string_value = get_enum_value(Currency, value) class CSLDateArray(Base): """An array of dates""" @camel_case_translate def __init__( self, date_values: Tuple[CSLDate, ...] = None, name: str = None ): super().__init__() self.date_values = date_values self.name = name @property def date_values(self) -> Tuple[CSLDate, ...]: """A date""" return self.__date_values @date_values.setter def date_values(self, value: Tuple[CSLDate, ...]): self._property_changed('date_values') self.__date_values = value class CSLDateArrayNamedParam(Base): """A named array of dates""" @camel_case_translate def __init__( self, date_values: Tuple[CSLDate, ...] = None, name: str = None ): super().__init__() self.date_values = date_values self.name = name @property def date_values(self) -> Tuple[CSLDate, ...]: """A date""" return self.__date_values @date_values.setter def date_values(self, value: Tuple[CSLDate, ...]): self._property_changed('date_values') self.__date_values = value @property def name(self) -> str: """A name for the array""" return self.__name @name.setter def name(self, value: str): self._property_changed('name') self.__name = value class CSLDoubleArray(Base): """An array of doubles""" @camel_case_translate def __init__( self, double_values: Tuple[CSLDouble, ...] = None, name: str = None ): super().__init__() self.double_values = double_values self.name = name @property def double_values(self) -> Tuple[CSLDouble, ...]: """A double""" return self.__double_values @double_values.setter def double_values(self, value: Tuple[CSLDouble, ...]): self._property_changed('double_values') self.__double_values = value class CSLFXCrossArray(Base): """An array of FX crosses""" @camel_case_translate def __init__( self, fx_cross_values: Tuple[CSLFXCross, ...] = None, name: str = None ): super().__init__() self.fx_cross_values = fx_cross_values self.name = name @property def fx_cross_values(self) -> Tuple[CSLFXCross, ...]: """An FX cross""" return self.__fx_cross_values @fx_cross_values.setter def fx_cross_values(self, value: Tuple[CSLFXCross, ...]): self._property_changed('fx_cross_values') self.__fx_cross_values = value class CSLIndexArray(Base): """An array of indices""" @camel_case_translate def __init__( self, index_values: Tuple[CSLIndex, ...] = None, name: str = None ): super().__init__() self.index_values = index_values self.name = name @property def index_values(self) -> Tuple[CSLIndex, ...]: """An index""" return self.__index_values @index_values.setter def index_values(self, value: Tuple[CSLIndex, ...]): self._property_changed('index_values') self.__index_values = value class CSLSimpleScheduleArray(Base): """An array of simple schedules""" @camel_case_translate def __init__( self, simple_schedule_values: Tuple[CSLSimpleSchedule, ...] = None, name: str = None ): super().__init__() self.simple_schedule_values = simple_schedule_values self.name = name @property def simple_schedule_values(self) -> Tuple[CSLSimpleSchedule, ...]: """A fixing date, settlement date pair""" return self.__simple_schedule_values @simple_schedule_values.setter def simple_schedule_values(self, value: Tuple[CSLSimpleSchedule, ...]): self._property_changed('simple_schedule_values') self.__simple_schedule_values = value class CSLStockArray(Base): """An array of stocks""" @camel_case_translate def __init__( self, stock_values: Tuple[CSLStock, ...] = None, name: str = None ): super().__init__() self.stock_values = stock_values self.name = name @property def stock_values(self) -> Tuple[CSLStock, ...]: """A stock""" return self.__stock_values @stock_values.setter def stock_values(self, value: Tuple[CSLStock, ...]): self._property_changed('stock_values') self.__stock_values = value class CSLStringArray(Base): """An array of strings""" @camel_case_translate def __init__( self, string_values: Tuple[CSLString, ...] = None, name: str = None ): super().__init__() self.string_values = string_values self.name = name @property def string_values(self) -> Tuple[CSLString, ...]: """A string""" return self.__string_values @string_values.setter def string_values(self, value: Tuple[CSLString, ...]): self._property_changed('string_values') self.__string_values = value class CarryScenario(Scenario): """A scenario to manipulate time along the forward curve""" @camel_case_translate def __init__( self, date: Union[datetime.date, str] = None, time_shift: int = None, roll_to_fwds: bool = True, holiday_calendar: Union[PricingLocation, str] = None, name: str = None ): super().__init__() self.date = date self.time_shift = time_shift self.roll_to_fwds = roll_to_fwds self.holiday_calendar = holiday_calendar self.name = name @property def scenario_type(self) -> str: """CarryScenario""" return 'CarryScenario' @property def date(self) -> Union[datetime.date, str]: """Date to shift markets to (absolute or relative)""" return self.__date @date.setter def date(self, value: Union[datetime.date, str]): self._property_changed('date') self.__date = value @property def time_shift(self) -> int: """Number of days to shift market (in days)""" return self.__time_shift @time_shift.setter def time_shift(self, value: int): self._property_changed('time_shift') self.__time_shift = value @property def roll_to_fwds(self) -> bool: """Roll along the forward curve or roll in spot space""" return self.__roll_to_fwds @roll_to_fwds.setter def roll_to_fwds(self, value: bool): self._property_changed('roll_to_fwds') self.__roll_to_fwds = value @property def holiday_calendar(self) -> Union[PricingLocation, str]: """Calendar to use for relative dates""" return self.__holiday_calendar @holiday_calendar.setter def holiday_calendar(self, value: Union[PricingLocation, str]): self._property_changed('holiday_calendar') self.__holiday_calendar = get_enum_value(PricingLocation, value) class CloseMarket(Base): """Close market""" @camel_case_translate def __init__( self, date: datetime.date, location: Union[PricingLocation, str], name: str = None ): super().__init__() self.date = date self.location = location self.name = name @property def market_type(self) -> str: """CloseMarket""" return 'CloseMarket' @property def date(self) -> datetime.date: """Date for the market data""" return self.__date @date.setter def date(self, value: datetime.date): self._property_changed('date') self.__date = value @property def location(self) -> Union[PricingLocation, str]: """Location for the market data""" return self.__location @location.setter def location(self, value: Union[PricingLocation, str]): self._property_changed('location') self.__location = get_enum_value(PricingLocation, value) class CommodPrice(Base): """Commodity price in units and currency, used for quoting strike, premium, fixed price""" @camel_case_translate def __init__( self, price: Union[float, str] = None, unit: Union[CommodUnit, str] = None, currency: Union[CurrencyName, str] = None, name: str = None ): super().__init__() self.price = price self.unit = unit self.currency = currency self.name = name @property def price(self) -> Union[float, str]: """price""" return self.__price @price.setter def price(self, value: Union[float, str]): self._property_changed('price') self.__price = value @property def unit(self) -> Union[CommodUnit, str]: """A coding scheme value to identify the unit of measure (e.g. Therms) in which the undelryer is denominated.""" return self.__unit @unit.setter def unit(self, value: Union[CommodUnit, str]): self._property_changed('unit') self.__unit = get_enum_value(CommodUnit, value) @property def currency(self) -> Union[CurrencyName, str]: """Currency Names""" return self.__currency @currency.setter def currency(self, value: Union[CurrencyName, str]): self._property_changed('currency') self.__currency = get_enum_value(CurrencyName, value) class GIRDomain(Base): @camel_case_translate def __init__( self, document_links: Tuple[Link, ...] = None, name: str = None ): super().__init__() self.document_links = document_links self.name = name @property def document_links(self) -> Tuple[Link, ...]: """Documents related to this asset""" return self.__document_links @document_links.setter def document_links(self, value: Tuple[Link, ...]): self._property_changed('document_links') self.__document_links = value class ISelectNewParameter(Base): @camel_case_translate def __init__( self, early_unwind_after: float = None, early_unwind_applicable: str = None, expiry_date_rule: str = None, option_target_expiry_parameter: float = None, option_early_unwind_days: float = None, in_alpha: bool = None, is_fsr_target_factor: bool = None, fsr_max_ratio: float = None, fsr_min_ratio: float = None, module_enabled: bool = None, module_name: str = None, target_strike: float = None, strike_method: Union[StrikeMethodType, str] = None, option_expiry: Union[OptionExpiryType, str] = None, bloomberg_id: str = None, stock_id: str = None, ric: str = None, new_weight: float = None, notional: float = None, leverage: float = None, option_type: Union[OptionType, str] = None, option_strike_type: Union[OptionStrikeType, str] = None, credit_option_type: Union[CreditOptionType, str] = None, credit_option_strike_type: Union[CreditOptionStrikeType, str] = None, strike_relative: float = None, trade_type: Union[TradeType, str] = None, signal: float = None, new_signal: float = None, new_min_weight: float = None, new_max_weight: float = None, min_weight: float = None, max_weight: float = None, election: str = None, base_date: str = None, commodity: str = None, component_weight: float = None, contract_nearby_number: float = None, expiration_schedule: str = None, fixing_type: str = None, last_eligible_date: float = None, num_roll_days: float = None, roll_end: float = None, roll_start: float = None, roll_type: str = None, valid_contract_expiry: str = None, name: str = None ): super().__init__() self.early_unwind_after = early_unwind_after self.early_unwind_applicable = early_unwind_applicable self.expiry_date_rule = expiry_date_rule self.option_target_expiry_parameter = option_target_expiry_parameter self.option_early_unwind_days = option_early_unwind_days self.in_alpha = in_alpha self.is_fsr_target_factor = is_fsr_target_factor self.fsr_max_ratio = fsr_max_ratio self.fsr_min_ratio = fsr_min_ratio self.module_enabled = module_enabled self.module_name = module_name self.target_strike = target_strike self.strike_method = strike_method self.option_expiry = option_expiry self.bloomberg_id = bloomberg_id self.stock_id = stock_id self.ric = ric self.new_weight = new_weight self.notional = notional self.leverage = leverage self.option_type = option_type self.option_strike_type = option_strike_type self.credit_option_type = credit_option_type self.credit_option_strike_type = credit_option_strike_type self.strike_relative = strike_relative self.trade_type = trade_type self.signal = signal self.new_signal = new_signal self.new_min_weight = new_min_weight self.new_max_weight = new_max_weight self.min_weight = min_weight self.max_weight = max_weight self.election = election self.base_date = base_date self.commodity = commodity self.component_weight = component_weight self.contract_nearby_number = contract_nearby_number self.expiration_schedule = expiration_schedule self.fixing_type = fixing_type self.last_eligible_date = last_eligible_date self.num_roll_days = num_roll_days self.roll_end = roll_end self.roll_start = roll_start self.roll_type = roll_type self.valid_contract_expiry = valid_contract_expiry self.name = name @property def early_unwind_after(self) -> float: return self.__early_unwind_after @early_unwind_after.setter def early_unwind_after(self, value: float): self._property_changed('early_unwind_after') self.__early_unwind_after = value @property def early_unwind_applicable(self) -> str: """Indicates whether the module can be unwinded early""" return self.__early_unwind_applicable @early_unwind_applicable.setter def early_unwind_applicable(self, value: str): self._property_changed('early_unwind_applicable') self.__early_unwind_applicable = value @property def expiry_date_rule(self) -> str: """Free text description of asset. Description provided will be indexed in the search service for free text relevance match""" return self.__expiry_date_rule @expiry_date_rule.setter def expiry_date_rule(self, value: str): self._property_changed('expiry_date_rule') self.__expiry_date_rule = value @property def option_target_expiry_parameter(self) -> float: return self.__option_target_expiry_parameter @option_target_expiry_parameter.setter def option_target_expiry_parameter(self, value: float): self._property_changed('option_target_expiry_parameter') self.__option_target_expiry_parameter = value @property def option_early_unwind_days(self) -> float: return self.__option_early_unwind_days @option_early_unwind_days.setter def option_early_unwind_days(self, value: float): self._property_changed('option_early_unwind_days') self.__option_early_unwind_days = value @property def in_alpha(self) -> bool: return self.__in_alpha @in_alpha.setter def in_alpha(self, value: bool): self._property_changed('in_alpha') self.__in_alpha = value @property def is_fsr_target_factor(self) -> bool: return self.__is_fsr_target_factor @is_fsr_target_factor.setter def is_fsr_target_factor(self, value: bool): self._property_changed('is_fsr_target_factor') self.__is_fsr_target_factor = value @property def fsr_max_ratio(self) -> float: return self.__fsr_max_ratio @fsr_max_ratio.setter def fsr_max_ratio(self, value: float): self._property_changed('fsr_max_ratio') self.__fsr_max_ratio = value @property def fsr_min_ratio(self) -> float: return self.__fsr_min_ratio @fsr_min_ratio.setter def fsr_min_ratio(self, value: float): self._property_changed('fsr_min_ratio') self.__fsr_min_ratio = value @property def module_enabled(self) -> bool: """Enable to disable the module""" return self.__module_enabled @module_enabled.setter def module_enabled(self, value: bool): self._property_changed('module_enabled') self.__module_enabled = value @property def module_name(self) -> str: """Free text description of asset. Description provided will be indexed in the search service for free text relevance match""" return self.__module_name @module_name.setter def module_name(self, value: str): self._property_changed('module_name') self.__module_name = value @property def target_strike(self) -> float: return self.__target_strike @target_strike.setter def target_strike(self, value: float): self._property_changed('target_strike') self.__target_strike = value @property def strike_method(self) -> Union[StrikeMethodType, str]: return self.__strike_method @strike_method.setter def strike_method(self, value: Union[StrikeMethodType, str]): self._property_changed('strike_method') self.__strike_method = get_enum_value(StrikeMethodType, value) @property def option_expiry(self) -> Union[OptionExpiryType, str]: return self.__option_expiry @option_expiry.setter def option_expiry(self, value: Union[OptionExpiryType, str]): self._property_changed('option_expiry') self.__option_expiry = get_enum_value(OptionExpiryType, value) @property def bloomberg_id(self) -> str: return self.__bloomberg_id @bloomberg_id.setter def bloomberg_id(self, value: str): self._property_changed('bloomberg_id') self.__bloomberg_id = value @property def stock_id(self) -> str: return self.__stock_id @stock_id.setter def stock_id(self, value: str): self._property_changed('stock_id') self.__stock_id = value @property def ric(self) -> str: return self.__ric @ric.setter def ric(self, value: str): self._property_changed('ric') self.__ric = value @property def new_weight(self) -> float: return self.__new_weight @new_weight.setter def new_weight(self, value: float): self._property_changed('new_weight') self.__new_weight = value @property def notional(self) -> float: return self.__notional @notional.setter def notional(self, value: float): self._property_changed('notional') self.__notional = value @property def leverage(self) -> float: return self.__leverage @leverage.setter def leverage(self, value: float): self._property_changed('leverage') self.__leverage = value @property def option_type(self) -> Union[OptionType, str]: """Option Type""" return self.__option_type @option_type.setter def option_type(self, value: Union[OptionType, str]): self._property_changed('option_type') self.__option_type = get_enum_value(OptionType, value) @property def option_strike_type(self) -> Union[OptionStrikeType, str]: return self.__option_strike_type @option_strike_type.setter def option_strike_type(self, value: Union[OptionStrikeType, str]): self._property_changed('option_strike_type') self.__option_strike_type = get_enum_value(OptionStrikeType, value) @property def credit_option_type(self) -> Union[CreditOptionType, str]: return self.__credit_option_type @credit_option_type.setter def credit_option_type(self, value: Union[CreditOptionType, str]): self._property_changed('credit_option_type') self.__credit_option_type = get_enum_value(CreditOptionType, value) @property def credit_option_strike_type(self) -> Union[CreditOptionStrikeType, str]: return self.__credit_option_strike_type @credit_option_strike_type.setter def credit_option_strike_type(self, value: Union[CreditOptionStrikeType, str]): self._property_changed('credit_option_strike_type') self.__credit_option_strike_type = get_enum_value(CreditOptionStrikeType, value) @property def strike_relative(self) -> float: return self.__strike_relative @strike_relative.setter def strike_relative(self, value: float): self._property_changed('strike_relative') self.__strike_relative = value @property def trade_type(self) -> Union[TradeType, str]: """Direction""" return self.__trade_type @trade_type.setter def trade_type(self, value: Union[TradeType, str]): self._property_changed('trade_type') self.__trade_type = get_enum_value(TradeType, value) @property def signal(self) -> float: return self.__signal @signal.setter def signal(self, value: float): self._property_changed('signal') self.__signal = value @property def new_signal(self) -> float: return self.__new_signal @new_signal.setter def new_signal(self, value: float): self._property_changed('new_signal') self.__new_signal = value @property def new_min_weight(self) -> float: return self.__new_min_weight @new_min_weight.setter def new_min_weight(self, value: float): self._property_changed('new_min_weight') self.__new_min_weight = value @property def new_max_weight(self) -> float: return self.__new_max_weight @new_max_weight.setter def new_max_weight(self, value: float): self._property_changed('new_max_weight') self.__new_max_weight = value @property def min_weight(self) -> float: return self.__min_weight @min_weight.setter def min_weight(self, value: float): self._property_changed('min_weight') self.__min_weight = value @property def max_weight(self) -> float: return self.__max_weight @max_weight.setter def max_weight(self, value: float): self._property_changed('max_weight') self.__max_weight = value @property def election(self) -> str: return self.__election @election.setter def election(self, value: str): self._property_changed('election') self.__election = value @property def base_date(self) -> str: """The base date type to use for the nearby contract roll""" return self.__base_date @base_date.setter def base_date(self, value: str): self._property_changed('base_date') self.__base_date = value @property def commodity(self) -> str: """The commodity symbol for the module""" return self.__commodity @commodity.setter def commodity(self, value: str): self._property_changed('commodity') self.__commodity = value @property def component_weight(self) -> float: """The weight allocated to the specified module""" return self.__component_weight @component_weight.setter def component_weight(self, value: float): self._property_changed('component_weight') self.__component_weight = value @property def contract_nearby_number(self) -> float: """The nearby contract to roll into""" return self.__contract_nearby_number @contract_nearby_number.setter def contract_nearby_number(self, value: float): self._property_changed('contract_nearby_number') self.__contract_nearby_number = value @property def expiration_schedule(self) -> str: """The contract expiration schedule to be used""" return self.__expiration_schedule @expiration_schedule.setter def expiration_schedule(self, value: str): self._property_changed('expiration_schedule') self.__expiration_schedule = value @property def fixing_type(self) -> str: """Type of fixing used to determine the price""" return self.__fixing_type @fixing_type.setter def fixing_type(self, value: str): self._property_changed('fixing_type') self.__fixing_type = value @property def last_eligible_date(self) -> float: """The last eligible date for the roll""" return self.__last_eligible_date @last_eligible_date.setter def last_eligible_date(self, value: float): self._property_changed('last_eligible_date') self.__last_eligible_date = value @property def num_roll_days(self) -> float: """The number of days over which the roll is spread""" return self.__num_roll_days @num_roll_days.setter def num_roll_days(self, value: float): self._property_changed('num_roll_days') self.__num_roll_days = value @property def roll_end(self) -> float: """Day on which to end the roll""" return self.__roll_end @roll_end.setter def roll_end(self, value: float): self._property_changed('roll_end') self.__roll_end = value @property def roll_start(self) -> float: """Day on which to start the roll""" return self.__roll_start @roll_start.setter def roll_start(self, value: float): self._property_changed('roll_start') self.__roll_start = value @property def roll_type(self) -> str: """Type of contract roll""" return self.__roll_type @roll_type.setter def roll_type(self, value: str): self._property_changed('roll_type') self.__roll_type = value @property def valid_contract_expiry(self) -> str: """The valid contract expiry months""" return self.__valid_contract_expiry @valid_contract_expiry.setter def valid_contract_expiry(self, value: str): self._property_changed('valid_contract_expiry') self.__valid_contract_expiry = value class LiveMarket(Base): """Live market""" @camel_case_translate def __init__( self, location: Union[PricingLocation, str], name: str = None ): super().__init__() self.location = location self.name = name @property def market_type(self) -> str: """LiveMarket""" return 'LiveMarket' @property def location(self) -> Union[PricingLocation, str]: """Location for the market data""" return self.__location @location.setter def location(self, value: Union[PricingLocation, str]): self._property_changed('location') self.__location = get_enum_value(PricingLocation, value) class MarketDataCoordinateValue(Base): """Market data coordinate and value""" @camel_case_translate def __init__( self, coordinate: MarketDataCoordinate, value: float, name: str = None ): super().__init__() self.coordinate = coordinate self.value = value self.name = name @property def coordinate(self) -> MarketDataCoordinate: """Market data coordinate""" return self.__coordinate @coordinate.setter def coordinate(self, value: MarketDataCoordinate): self._property_changed('coordinate') self.__coordinate = value @property def value(self) -> float: """Value for the coordinate""" return self.__value @value.setter def value(self, value: float): self._property_changed('value') self.__value = value class MarketDataPattern(Base): """A pattern used to match market coordinates""" @camel_case_translate def __init__( self, mkt_type: str = None, mkt_asset: str = None, mkt_class: str = None, mkt_point: Tuple[str, ...] = None, mkt_quoting_style: str = None, is_active: bool = None, is_investment_grade: bool = None, currency: Union[Currency, str] = None, country_code: Union[CountryCode, str] = None, gics_sector: str = None, gics_industry_group: str = None, gics_industry: str = None, gics_sub_industry: str = None, name: str = None ): super().__init__() self.mkt_type = mkt_type self.mkt_asset = mkt_asset self.mkt_class = mkt_class self.mkt_point = mkt_point self.mkt_quoting_style = mkt_quoting_style self.is_active = is_active self.is_investment_grade = is_investment_grade self.currency = currency self.country_code = country_code self.gics_sector = gics_sector self.gics_industry_group = gics_industry_group self.gics_industry = gics_industry self.gics_sub_industry = gics_sub_industry self.name = name @property def mkt_type(self) -> str: """The Market Data Type, e.g. IR, IR_BASIS, FX, FX_Vol""" return self.__mkt_type @mkt_type.setter def mkt_type(self, value: str): self._property_changed('mkt_type') self.__mkt_type = value @property def mkt_asset(self) -> str: """The specific point, e.g. 3m, 10y, 11y, Dec19""" return self.__mkt_asset @mkt_asset.setter def mkt_asset(self, value: str): self._property_changed('mkt_asset') self.__mkt_asset = value @property def mkt_class(self) -> str: """The market data pointClass, e.g. Swap, Cash.""" return self.__mkt_class @mkt_class.setter def mkt_class(self, value: str): self._property_changed('mkt_class') self.__mkt_class = value @property def mkt_point(self) -> Tuple[str, ...]: """The specific point, e.g. 3m, 10y, 11y, Dec19""" return self.__mkt_point @mkt_point.setter def mkt_point(self, value: Tuple[str, ...]): self._property_changed('mkt_point') self.__mkt_point = value @property def mkt_quoting_style(self) -> str: return self.__mkt_quoting_style @mkt_quoting_style.setter def mkt_quoting_style(self, value: str): self._property_changed('mkt_quoting_style') self.__mkt_quoting_style = value @property def is_active(self) -> bool: """Is the asset active""" return self.__is_active @is_active.setter def is_active(self, value: bool): self._property_changed('is_active') self.__is_active = value @property def is_investment_grade(self) -> bool: """Is the asset investment grade""" return self.__is_investment_grade @is_investment_grade.setter def is_investment_grade(self, value: bool): self._property_changed('is_investment_grade') self.__is_investment_grade = value @property def currency(self) -> Union[Currency, str]: """Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)""" return self.__currency @currency.setter def currency(self, value: Union[Currency, str]): self._property_changed('currency') self.__currency = get_enum_value(Currency, value) @property def country_code(self) -> Union[CountryCode, str]: """ISO Country code""" return self.__country_code @country_code.setter def country_code(self, value: Union[CountryCode, str]): self._property_changed('country_code') self.__country_code = get_enum_value(CountryCode, value) @property def gics_sector(self) -> str: """GICS Sector classification (level 1)""" return self.__gics_sector @gics_sector.setter def gics_sector(self, value: str): self._property_changed('gics_sector') self.__gics_sector = value @property def gics_industry_group(self) -> str: """GICS Industry Group classification (level 2)""" return self.__gics_industry_group @gics_industry_group.setter def gics_industry_group(self, value: str): self._property_changed('gics_industry_group') self.__gics_industry_group = value @property def gics_industry(self) -> str: """GICS Industry classification (level 3)""" return self.__gics_industry @gics_industry.setter def gics_industry(self, value: str): self._property_changed('gics_industry') self.__gics_industry = value @property def gics_sub_industry(self) -> str: """GICS Sub Industry classification (level 4)""" return self.__gics_sub_industry @gics_sub_industry.setter def gics_sub_industry(self, value: str): self._property_changed('gics_sub_industry') self.__gics_sub_industry = value class MarketDataScenario(Base): """A market data scenario to apply to the calculation""" @camel_case_translate def __init__( self, scenario: dict, subtract_base: bool = False, name: str = None ): super().__init__() self.scenario = scenario self.subtract_base = subtract_base self.name = name @property def scenario(self) -> dict: """The scenario""" return self.__scenario @scenario.setter def scenario(self, value: dict): self._property_changed('scenario') self.__scenario = value @property def subtract_base(self) -> bool: """Subtract values computed under the base market data state, to return a diff, if true""" return self.__subtract_base @subtract_base.setter def subtract_base(self, value: bool): self._property_changed('subtract_base') self.__subtract_base = value class MarketDataShock(Base): """A shock to apply to market coordinate values""" @camel_case_translate def __init__( self, shock_type: Union[MarketDataShockType, str], value: float, precision: float = None, cap: float = None, floor: float = None, coordinate_cap: float = None, coordinate_floor: float = None, name: str = None ): super().__init__() self.shock_type = shock_type self.value = value self.precision = precision self.cap = cap self.floor = floor self.coordinate_cap = coordinate_cap self.coordinate_floor = coordinate_floor self.name = name @property def shock_type(self) -> Union[MarketDataShockType, str]: """Market data shock type""" return self.__shock_type @shock_type.setter def shock_type(self, value: Union[MarketDataShockType, str]): self._property_changed('shock_type') self.__shock_type = get_enum_value(MarketDataShockType, value) @property def value(self) -> float: """The amount by which to shock matching coordinates""" return self.__value @value.setter def value(self, value: float): self._property_changed('value') self.__value = value @property def precision(self) -> float: """The precision to which the shock will be rounded""" return self.__precision @precision.setter def precision(self, value: float): self._property_changed('precision') self.__precision = value @property def cap(self) -> float: """Upper bound on the shocked value""" return self.__cap @cap.setter def cap(self, value: float): self._property_changed('cap') self.__cap = value @property def floor(self) -> float: """Lower bound on the shocked value""" return self.__floor @floor.setter def floor(self, value: float): self._property_changed('floor') self.__floor = value @property def coordinate_cap(self) -> float: """Upper bound on the pre-shocked value of matching coordinates""" return self.__coordinate_cap @coordinate_cap.setter def coordinate_cap(self, value: float): self._property_changed('coordinate_cap') self.__coordinate_cap = value @property def coordinate_floor(self) -> float: """Lower bound on the pre-shocked value of matching coordinates""" return self.__coordinate_floor @coordinate_floor.setter def coordinate_floor(self, value: float): self._property_changed('coordinate_floor') self.__coordinate_floor = value class PCOBenchmark(Base): """Parameters required for PCO Benchmark""" @camel_case_translate def __init__( self, selected: str = None, options: Tuple[PCOBenchmarkOptions, ...] = None, name: str = None ): super().__init__() self.selected = selected self.options = options self.name = name @property def selected(self) -> str: return self.__selected @selected.setter def selected(self, value: str): self._property_changed('selected') self.__selected = value @property def options(self) -> Tuple[PCOBenchmarkOptions, ...]: """Parameters required for PCO Benchmark""" return self.__options @options.setter def options(self, value: Tuple[PCOBenchmarkOptions, ...]): self._property_changed('options') self.__options = value class PCOCashBalance(Base): """Parameters required for PCO Cash Balance""" @camel_case_translate def __init__( self, local_currency: Union[Currency, str] = None, cash_balance_limits: Tuple[str, ...] = None, cash_reserve: str = None, long_threshold: str = None, short_threshold: str = None, name: str = None ): super().__init__() self.local_currency = local_currency self.cash_balance_limits = cash_balance_limits self.cash_reserve = cash_reserve self.long_threshold = long_threshold self.short_threshold = short_threshold self.name = name @property def local_currency(self) -> Union[Currency, str]: """Local currency""" return self.__local_currency @local_currency.setter def local_currency(self, value: Union[Currency, str]): self._property_changed('local_currency') self.__local_currency = get_enum_value(Currency, value) @property def cash_balance_limits(self) -> Tuple[str, ...]: """Upper and lower limits for cash balance""" return self.__cash_balance_limits @cash_balance_limits.setter def cash_balance_limits(self, value: Tuple[str, ...]): self._property_changed('cash_balance_limits') self.__cash_balance_limits = value @property def cash_reserve(self) -> str: """Target cash reserve""" return self.__cash_reserve @cash_reserve.setter def cash_reserve(self, value: str): self._property_changed('cash_reserve') self.__cash_reserve = value @property def long_threshold(self) -> str: """Long cash threshold""" return self.__long_threshold @long_threshold.setter def long_threshold(self, value: str): self._property_changed('long_threshold') self.__long_threshold = value @property def short_threshold(self) -> str: """Short cash threshold""" return self.__short_threshold @short_threshold.setter def short_threshold(self, value: str): self._property_changed('short_threshold') self.__short_threshold = value class PCOParameterValues(Base): """Parameters required for a PCO Generic Value""" @camel_case_translate def __init__( self, currency: Union[Currency, str] = None, value: Union[float, str] = None, name: str = None ): super().__init__() self.currency = currency self.value = value self.name = name @property def currency(self) -> Union[Currency, str]: """Currency""" return self.__currency @currency.setter def currency(self, value: Union[Currency, str]): self._property_changed('currency') self.__currency = get_enum_value(Currency, value) @property def value(self) -> Union[float, str]: """Generic value for a PCO parameter""" return self.__value @value.setter def value(self, value: Union[float, str]): self._property_changed('value') self.__value = value class PCOSettlements(Base): """Parameters required for a PCO Settlement""" @camel_case_translate def __init__( self, currency: Union[Currency, str] = None, data: Tuple[PCOSettlementsData, ...] = None, name: str = None ): super().__init__() self.currency = currency self.data = data self.name = name @property def currency(self) -> Union[Currency, str]: """Currency""" return self.__currency @currency.setter def currency(self, value: Union[Currency, str]): self._property_changed('currency') self.__currency = get_enum_value(Currency, value) @property def data(self) -> Tuple[PCOSettlementsData, ...]: """Parameters required for a PCO Settlement""" return self.__data @data.setter def data(self, value: Tuple[PCOSettlementsData, ...]): self._property_changed('data') self.__data = value class PCOTargetDeviation(Base): """Parameters required for a Target Deviation""" @camel_case_translate def __init__( self, currency: Union[Currency, str] = None, data: Tuple[PCOTargetDeviationData, ...] = None, name: str = None ): super().__init__() self.currency = currency self.data = data self.name = name @property def currency(self) -> Union[Currency, str]: """Currency""" return self.__currency @currency.setter def currency(self, value: Union[Currency, str]): self._property_changed('currency') self.__currency = get_enum_value(Currency, value) @property def data(self) -> Tuple[PCOTargetDeviationData, ...]: """Parameters required for a Target Deviation data""" return self.__data @data.setter def data(self, value: Tuple[PCOTargetDeviationData, ...]): self._property_changed('data') self.__data = value class PCOUnrealisedMarkToMarket(Base): """Parameters required for a Unrealised mark to market.""" @camel_case_translate def __init__( self, total: str = None, next_settlement_date: datetime.datetime = None, next_settlement: str = None, next_roll_date: datetime.datetime = None, historical_data: Tuple[PCOMtMHistoricalData, ...] = None, name: str = None ): super().__init__() self.total = total self.next_settlement_date = next_settlement_date self.next_settlement = next_settlement self.next_roll_date = next_roll_date self.historical_data = historical_data self.name = name @property def total(self) -> str: """Total indicative unrealised mark to market value""" return self.__total @total.setter def total(self, value: str): self._property_changed('total') self.__total = value @property def next_settlement_date(self) -> datetime.datetime: """Next settlement date""" return self.__next_settlement_date @next_settlement_date.setter def next_settlement_date(self, value: datetime.datetime): self._property_changed('next_settlement_date') self.__next_settlement_date = value @property def next_settlement(self) -> str: """Next settlement value""" return self.__next_settlement @next_settlement.setter def next_settlement(self, value: str): self._property_changed('next_settlement') self.__next_settlement = value @property def next_roll_date(self) -> datetime.datetime: """Next roll date""" return self.__next_roll_date @next_roll_date.setter def next_roll_date(self, value: datetime.datetime): self._property_changed('next_roll_date') self.__next_roll_date = value @property def historical_data(self) -> Tuple[PCOMtMHistoricalData, ...]: """History of unrealised mark to market of open trades""" return self.__historical_data @historical_data.setter def historical_data(self, value: Tuple[PCOMtMHistoricalData, ...]): self._property_changed('historical_data') self.__historical_data = value class Position(Base): @camel_case_translate def __init__( self, asset_id: str = None, quantity: float = None, notional: float = None, party_to: SimpleParty = None, party_from: SimpleParty = None, external_ids: Tuple[dict, ...] = None, margin_ids: Tuple[dict, ...] = None, instrument: InstrumentBase = None, description: str = None, name: str = None ): super().__init__() self.asset_id = asset_id self.quantity = quantity self.notional = notional self.party_to = party_to self.party_from = party_from self.external_ids = external_ids self.margin_ids = margin_ids self.instrument = instrument self.description = description self.name = name @property def asset_id(self) -> str: """Marquee unique asset identifier.""" return self.__asset_id @asset_id.setter def asset_id(self, value: str): self._property_changed('asset_id') self.__asset_id = value @property def quantity(self) -> float: """Quantity of position""" return self.__quantity @quantity.setter def quantity(self, value: float): self._property_changed('quantity') self.__quantity = value @property def notional(self) -> float: """Notional of position""" return self.__notional @notional.setter def notional(self, value: float): self._property_changed('notional') self.__notional = value @property def party_to(self) -> SimpleParty: return self.__party_to @party_to.setter def party_to(self, value: SimpleParty): self._property_changed('party_to') self.__party_to = value @property def party_from(self) -> SimpleParty: return self.__party_from @party_from.setter def party_from(self, value: SimpleParty): self._property_changed('party_from') self.__party_from = value @property def external_ids(self) -> Tuple[dict, ...]: """A list of identifiers (external to Marquee) for this position""" return self.__external_ids @external_ids.setter def external_ids(self, value: Tuple[dict, ...]): self._property_changed('external_ids') self.__external_ids = value @property def margin_ids(self) -> Tuple[dict, ...]: """A list of margin identifiers (e.g. CSA) for this position""" return self.__margin_ids @margin_ids.setter def margin_ids(self, value: Tuple[dict, ...]): self._property_changed('margin_ids') self.__margin_ids = value @property def instrument(self) -> InstrumentBase: """Valid Instruments""" return self.__instrument @instrument.setter def instrument(self, value: InstrumentBase): self._property_changed('instrument') self.__instrument = value @property def description(self) -> str: """Description of a particular trade or position.""" return self.__description @description.setter def description(self, value: str): self._property_changed('description') self.__description = value class RiskMeasure(Base): """The measure to perform risk on. Each risk measure consists of an asset class, a measure type, and a unit.""" @camel_case_translate def __init__( self, asset_class: Union[AssetClass, str] = None, measure_type: Union[RiskMeasureType, str] = None, unit: Union[RiskMeasureUnit, str] = None, value: Union[float, str] = None, name: str = None ): super().__init__() self.asset_class = asset_class self.measure_type = measure_type self.unit = unit self.value = value self.name = name @property def asset_class(self) -> Union[AssetClass, str]: """Asset classification of security. Assets are classified into broad groups which exhibit similar characteristics and behave in a consistent way under different market conditions""" return self.__asset_class @asset_class.setter def asset_class(self, value: Union[AssetClass, str]): self._property_changed('asset_class') self.__asset_class = get_enum_value(AssetClass, value) @property def measure_type(self) -> Union[RiskMeasureType, str]: """The type of measure to perform risk on. e.g. Greeks""" return self.__measure_type @measure_type.setter def measure_type(self, value: Union[RiskMeasureType, str]): self._property_changed('measure_type') self.__measure_type = get_enum_value(RiskMeasureType, value) @property def unit(self) -> Union[RiskMeasureUnit, str]: """The unit of change of underlying in the risk computation.""" return self.__unit @unit.setter def unit(self, value: Union[RiskMeasureUnit, str]): self._property_changed('unit') self.__unit = get_enum_value(RiskMeasureUnit, value) @property def value(self) -> Union[float, str]: """Value of this measure""" return self.__value @value.setter def value(self, value: Union[float, str]): self._property_changed('value') self.__value = value class RollFwd(Scenario): """A scenario to manipulate time along the forward curve""" @camel_case_translate def __init__( self, date: Union[datetime.date, str] = None, realise_fwd: bool = True, holiday_calendar: Union[PricingLocation, str] = None, name: str = None ): super().__init__() self.date = date self.realise_fwd = realise_fwd self.holiday_calendar = holiday_calendar self.name = name @property def scenario_type(self) -> str: """RollFwd""" return 'RollFwd' @property def date(self) -> Union[datetime.date, str]: """Absolute or Relative Date to shift markets to""" return self.__date @date.setter def date(self, value: Union[datetime.date, str]): self._property_changed('date') self.__date = value @property def realise_fwd(self) -> bool: """Roll along the forward curve or roll in spot space""" return self.__realise_fwd @realise_fwd.setter def realise_fwd(self, value: bool): self._property_changed('realise_fwd') self.__realise_fwd = value @property def holiday_calendar(self) -> Union[PricingLocation, str]: """Calendar to use for relative dates""" return self.__holiday_calendar @holiday_calendar.setter def holiday_calendar(self, value: Union[PricingLocation, str]): self._property_changed('holiday_calendar') self.__holiday_calendar = get_enum_value(PricingLocation, value) class TimestampedMarket(Base): """Timestamped market""" @camel_case_translate def __init__( self, location: Union[PricingLocation, str], timestamp: datetime.datetime = None, name: str = None ): super().__init__() self.timestamp = timestamp self.location = location self.name = name @property def market_type(self) -> str: """TimestampedMarket""" return 'TimestampedMarket' @property def timestamp(self) -> datetime.datetime: """Timestamp for the market data""" return self.__timestamp @timestamp.setter def timestamp(self, value: datetime.datetime): self._property_changed('timestamp') self.__timestamp = value @property def location(self) -> Union[PricingLocation, str]: """Location for the market data""" return self.__location @location.setter def location(self, value: Union[PricingLocation, str]): self._property_changed('location') self.__location = get_enum_value(PricingLocation, value) class CSLCurrencyArray(Base): """An array of currencies""" @camel_case_translate def __init__( self, currency_values: Tuple[CSLCurrency, ...] = None, name: str = None ): super().__init__() self.currency_values = currency_values self.name = name @property def currency_values(self) -> Tuple[CSLCurrency, ...]: """A currency""" return self.__currency_values @currency_values.setter def currency_values(self, value: Tuple[CSLCurrency, ...]): self._property_changed('currency_values') self.__currency_values = value class CSLSchedule(Base): """A schedule""" @camel_case_translate def __init__( self, first_date: datetime.date = None, last_date: datetime.date = None, calendar_name: str = None, period: str = None, delay: str = None, business_day_convention: str = None, day_count_convention: str = None, days_per_term: str = None, delay_business_day_convention: str = None, delay_calendar_name: str = None, has_reset_date: bool = None, term_formula: str = None, extra_dates: Tuple[CSLDateArrayNamedParam, ...] = None, extra_dates_by_offset: Tuple[CSLSymCaseNamedParam, ...] = None, name: str = None ): super().__init__() self.first_date = first_date self.last_date = last_date self.calendar_name = calendar_name self.period = period self.delay = delay self.business_day_convention = business_day_convention self.day_count_convention = day_count_convention self.days_per_term = days_per_term self.delay_business_day_convention = delay_business_day_convention self.delay_calendar_name = delay_calendar_name self.has_reset_date = has_reset_date self.term_formula = term_formula self.extra_dates = extra_dates self.extra_dates_by_offset = extra_dates_by_offset self.name = name @property def first_date(self) -> datetime.date: """ISO 8601-formatted date""" return self.__first_date @first_date.setter def first_date(self, value: datetime.date): self._property_changed('first_date') self.__first_date = value @property def last_date(self) -> datetime.date: """ISO 8601-formatted date""" return self.__last_date @last_date.setter def last_date(self, value: datetime.date): self._property_changed('last_date') self.__last_date = value @property def calendar_name(self) -> str: """The name of the holiday calendar""" return self.__calendar_name @calendar_name.setter def calendar_name(self, value: str): self._property_changed('calendar_name') self.__calendar_name = value @property def period(self) -> str: """Tenor""" return self.__period @period.setter def period(self, value: str): self._property_changed('period') self.__period = value @property def delay(self) -> str: """The delay""" return self.__delay @delay.setter def delay(self, value: str): self._property_changed('delay') self.__delay = value @property def business_day_convention(self) -> str: return self.__business_day_convention @business_day_convention.setter def business_day_convention(self, value: str): self._property_changed('business_day_convention') self.__business_day_convention = value @property def day_count_convention(self) -> str: return self.__day_count_convention @day_count_convention.setter def day_count_convention(self, value: str): self._property_changed('day_count_convention') self.__day_count_convention = value @property def days_per_term(self) -> str: return self.__days_per_term @days_per_term.setter def days_per_term(self, value: str): self._property_changed('days_per_term') self.__days_per_term = value @property def delay_business_day_convention(self) -> str: return self.__delay_business_day_convention @delay_business_day_convention.setter def delay_business_day_convention(self, value: str): self._property_changed('delay_business_day_convention') self.__delay_business_day_convention = value @property def delay_calendar_name(self) -> str: """The name of the holiday calendar""" return self.__delay_calendar_name @delay_calendar_name.setter def delay_calendar_name(self, value: str): self._property_changed('delay_calendar_name') self.__delay_calendar_name = value @property def has_reset_date(self) -> bool: return self.__has_reset_date @has_reset_date.setter def has_reset_date(self, value: bool): self._property_changed('has_reset_date') self.__has_reset_date = value @property def term_formula(self) -> str: return self.__term_formula @term_formula.setter def term_formula(self, value: str): self._property_changed('term_formula') self.__term_formula = value @property def extra_dates(self) -> Tuple[CSLDateArrayNamedParam, ...]: """A named array of dates""" return self.__extra_dates @extra_dates.setter def extra_dates(self, value: Tuple[CSLDateArrayNamedParam, ...]): self._property_changed('extra_dates') self.__extra_dates = value @property def extra_dates_by_offset(self) -> Tuple[CSLSymCaseNamedParam, ...]: """A named case-sensitive string.""" return self.__extra_dates_by_offset @extra_dates_by_offset.setter def extra_dates_by_offset(self, value: Tuple[CSLSymCaseNamedParam, ...]): self._property_changed('extra_dates_by_offset') self.__extra_dates_by_offset = value class CurveScenario(Scenario): """A scenario to manipulate curve shape""" @camel_case_translate def __init__( self, market_data_pattern: MarketDataPattern = None, annualised_parallel_shift: float = None, annualised_slope_shift: float = None, pivot_point: float = None, cutoff: float = None, floor: float = None, denominated: str = None, tenor: str = None, rate_option: str = None, bucket_shift: float = None, bucket_start: str = None, bucket_end: str = None, style: str = 'MarketData', name: str = None ): super().__init__() self.market_data_pattern = market_data_pattern self.annualised_parallel_shift = annualised_parallel_shift self.annualised_slope_shift = annualised_slope_shift self.pivot_point = pivot_point self.cutoff = cutoff self.floor = floor self.denominated = denominated self.tenor = tenor self.rate_option = rate_option self.bucket_shift = bucket_shift self.bucket_start = bucket_start self.bucket_end = bucket_end self.style = style self.name = name @property def scenario_type(self) -> str: """CurveScenario""" return 'CurveScenario' @property def market_data_pattern(self) -> MarketDataPattern: """Market pattern for matching curve assets""" return self.__market_data_pattern @market_data_pattern.setter def market_data_pattern(self, value: MarketDataPattern): self._property_changed('market_data_pattern') self.__market_data_pattern = value @property def annualised_parallel_shift(self) -> float: """Size of the parallel shift (in bps/year)""" return self.__annualised_parallel_shift @annualised_parallel_shift.setter def annualised_parallel_shift(self, value: float): self._property_changed('annualised_parallel_shift') self.__annualised_parallel_shift = value @property def annualised_slope_shift(self) -> float: """Size of the slope shift (in bps/year)""" return self.__annualised_slope_shift @annualised_slope_shift.setter def annualised_slope_shift(self, value: float): self._property_changed('annualised_slope_shift') self.__annualised_slope_shift = value @property def pivot_point(self) -> float: """The pivot point (in years)""" return self.__pivot_point @pivot_point.setter def pivot_point(self, value: float): self._property_changed('pivot_point') self.__pivot_point = value @property def cutoff(self) -> float: """The cutoff point (in years)""" return self.__cutoff @cutoff.setter def cutoff(self, value: float): self._property_changed('cutoff') self.__cutoff = value @property def floor(self) -> float: """The floor value (in bps)""" return self.__floor @floor.setter def floor(self, value: float): self._property_changed('floor') self.__floor = value @property def denominated(self) -> str: """Currency to which shock is applied""" return self.__denominated @denominated.setter def denominated(self, value: str): self._property_changed('denominated') self.__denominated = value @property def tenor(self) -> str: """Tenor of rate option to which shock is applied""" return self.__tenor @tenor.setter def tenor(self, value: str): self._property_changed('tenor') self.__tenor = value @property def rate_option(self) -> str: """Rate option to which shock is applied""" return self.__rate_option @rate_option.setter def rate_option(self, value: str): self._property_changed('rate_option') self.__rate_option = value @property def bucket_shift(self) -> float: """Size of the bucket shift (in bps)""" return self.__bucket_shift @bucket_shift.setter def bucket_shift(self, value: float): self._property_changed('bucket_shift') self.__bucket_shift = value @property def bucket_start(self) -> str: """The start date of the custom bucket""" return self.__bucket_start @bucket_start.setter def bucket_start(self, value: str): self._property_changed('bucket_start') self.__bucket_start = value @property def bucket_end(self) -> str: """The end date of the custom bucket""" return self.__bucket_end @bucket_end.setter def bucket_end(self, value: str): self._property_changed('bucket_end') self.__bucket_end = value @property def style(self) -> str: """Different styles for risk calculation""" return self.__style @style.setter def style(self, value: str): self._property_changed('style') self.__style = value class DataSetFieldMap(Base): """The mapping between data set field and risk measure type""" @camel_case_translate def __init__( self, data_set_id: str, field: str, results_field: str, risk_measure: RiskMeasure, name: str = None ): super().__init__() self.data_set_id = data_set_id self.field = field self.results_field = results_field self.risk_measure = risk_measure self.name = name @property def data_set_id(self) -> str: """Unique id of dataset.""" return self.__data_set_id @data_set_id.setter def data_set_id(self, value: str): self._property_changed('data_set_id') self.__data_set_id = value @property def field(self) -> str: """The field for data set, e.g. rate""" return self.__field @field.setter def field(self, value: str): self._property_changed('field') self.__field = value @property def results_field(self) -> str: """The source field in the results, e.g. value or fixedRate""" return self.__results_field @results_field.setter def results_field(self, value: str): self._property_changed('results_field') self.__results_field = value @property def risk_measure(self) -> RiskMeasure: """The measure to perform risk on. Each risk measure consists of an asset class, a measure type, and a unit.""" return self.__risk_measure @risk_measure.setter def risk_measure(self, value: RiskMeasure): self._property_changed('risk_measure') self.__risk_measure = value class FieldValueMap(Base): @camel_case_translate def __init__( self, **kwargs ): super().__init__() self.investment_rate = kwargs.get('investment_rate') self.starting_emma_legal_entity_id = kwargs.get('starting_emma_legal_entity_id') self.mdapi_class = kwargs.get('mdapi_class') self.total_notional_usd = kwargs.get('total_notional_usd') self.bid_unadjusted = kwargs.get('bid_unadjusted') self.aggressive_fills_percentage = kwargs.get('aggressive_fills_percentage') self.vehicle_type = kwargs.get('vehicle_type') self.total_fatalities_by_state = kwargs.get('total_fatalities_by_state') self.new_active = kwargs.get('new_active') self.daily_risk = kwargs.get('daily_risk') self.energy = kwargs.get('energy') self.sunshine_daily_forecast = kwargs.get('sunshine_daily_forecast') self.sentiment_score = kwargs.get('sentiment_score') self.correlation = kwargs.get('correlation') self.exposure = kwargs.get('exposure') self.size = kwargs.get('size') self.market_data_asset = kwargs.get('market_data_asset') self.buy75cents = kwargs.get('buy75cents') self.unadjusted_high = kwargs.get('unadjusted_high') self.source_importance = kwargs.get('source_importance') self.closing_yield = kwargs.get('closing_yield') self.wind = kwargs.get('wind') self.sc16 = kwargs.get('sc16') self.sc15 = kwargs.get('sc15') self.sc12 = kwargs.get('sc12') self.sc11 = kwargs.get('sc11') self.primary_vwap_in_limit_unrealized_bps = kwargs.get('primary_vwap_in_limit_unrealized_bps') self.display_name = kwargs.get('display_name') self.minutes_to_trade100_pct = kwargs.get('minutes_to_trade100_pct') self.sc14 = kwargs.get('sc14') self.cumulative_volume_in_shares = kwargs.get('cumulative_volume_in_shares') self.sc13 = kwargs.get('sc13') self.new_fatalities = kwargs.get('new_fatalities') self.buy50bps = kwargs.get('buy50bps') self.num_staffed_beds = kwargs.get('num_staffed_beds') self.upfront_payment = kwargs.get('upfront_payment') self.arrival_mid_realized_cash = kwargs.get('arrival_mid_realized_cash') self.sc10 = kwargs.get('sc10') self.sc05 = kwargs.get('sc05') self.a = kwargs.get('a') self.sc04 = kwargs.get('sc04') self.b = kwargs.get('b') self.sc07 = kwargs.get('sc07') self.c = kwargs.get('c') self.yield_to_maturity = kwargs.get('yield_to_maturity') self.sc06 = kwargs.get('sc06') self.address = kwargs.get('address') self.sc01 = kwargs.get('sc01') self.leg2_payment_frequency = kwargs.get('leg2_payment_frequency') self.sc03 = kwargs.get('sc03') self.sc02 = kwargs.get('sc02') self.geography_name = kwargs.get('geography_name') self.borrower = kwargs.get('borrower') self.settle_price = kwargs.get('settle_price') self.performance_contribution = kwargs.get('performance_contribution') self.sc09 = kwargs.get('sc09') self.mkt_class = kwargs.get('mkt_class') self.sc08 = kwargs.get('sc08') self.collateralization = kwargs.get('collateralization') self.future_month_u26 = kwargs.get('future_month_u26') self.future_month_u25 = kwargs.get('future_month_u25') self.future_month_u24 = kwargs.get('future_month_u24') self.future_month_u23 = kwargs.get('future_month_u23') self.future_month_u22 = kwargs.get('future_month_u22') self.statement_id = kwargs.get('statement_id') self.future_month_u21 = kwargs.get('future_month_u21') self.modified_duration = kwargs.get('modified_duration') self.short_rates_contribution = kwargs.get('short_rates_contribution') self.implied_normal_volatility = kwargs.get('implied_normal_volatility') self.solar_generation = kwargs.get('solar_generation') self.mtm_price = kwargs.get('mtm_price') self.swap_spread_change = kwargs.get('swap_spread_change') self.realized_arrival_performance_usd = kwargs.get('realized_arrival_performance_usd') self.portfolio_assets = kwargs.get('portfolio_assets') self.pricingdate = kwargs.get('pricingdate') self.tcm_cost_horizon3_hour = kwargs.get('tcm_cost_horizon3_hour') self.exchange_rate = kwargs.get('exchange_rate') self.potential_bed_cap_inc = kwargs.get('potential_bed_cap_inc') self.number_covered = kwargs.get('number_covered') self.number_of_positions = kwargs.get('number_of_positions') self.open_unadjusted = kwargs.get('open_unadjusted') self.strike_time = kwargs.get('strike_time') self.ask_price = kwargs.get('ask_price') self.event_id = kwargs.get('event_id') self.sectors = kwargs.get('sectors') self.additional_price_notation_type = kwargs.get('additional_price_notation_type') self.gross_investment_qtd = kwargs.get('gross_investment_qtd') self.annualized_risk = kwargs.get('annualized_risk') self.estimated_holding_time_short = kwargs.get('estimated_holding_time_short') self.midcurve_premium = kwargs.get('midcurve_premium') self.volume_composite = kwargs.get('volume_composite') self.sharpe_qtd = kwargs.get('sharpe_qtd') self.estimated_holding_time_long = kwargs.get('estimated_holding_time_long') self.external = kwargs.get('external') self.tracker_name = kwargs.get('tracker_name') self.sell50cents = kwargs.get('sell50cents') self.trade_price = kwargs.get('trade_price') self.cleared = kwargs.get('cleared') self.prime_id_numeric = kwargs.get('prime_id_numeric') self.buy8bps = kwargs.get('buy8bps') self.total_notional_local = kwargs.get('total_notional_local') self.cid = kwargs.get('cid') self.total_confirmed_senior_home = kwargs.get('total_confirmed_senior_home') self.ctd_fwd_price = kwargs.get('ctd_fwd_price') self.sink_factor = kwargs.get('sink_factor') self.temperature_forecast = kwargs.get('temperature_forecast') self.bid_high = kwargs.get('bid_high') self.pnl_qtd = kwargs.get('pnl_qtd') self.buy50cents = kwargs.get('buy50cents') self.sell4bps = kwargs.get('sell4bps') self.receiver_day_count_fraction = kwargs.get('receiver_day_count_fraction') self.auction_close_percentage = kwargs.get('auction_close_percentage') self.target_price = kwargs.get('target_price') self.bos_in_bps_description = kwargs.get('bos_in_bps_description') self.low_price = kwargs.get('low_price') self.adv22_day_pct = kwargs.get('adv22_day_pct') self.matched_maturity_swap_spread12m = kwargs.get('matched_maturity_swap_spread12m') self.price_range_in_ticks_label = kwargs.get('price_range_in_ticks_label') self.ticker = kwargs.get('ticker') self.notional_unit = kwargs.get('notional_unit') self.tcm_cost_horizon1_day = kwargs.get('tcm_cost_horizon1_day') self.approval = kwargs.get('approval') self.test_measure = kwargs.get('test_measure') self.option_lock_out_period = kwargs.get('option_lock_out_period') self.execution_time = kwargs.get('execution_time') self.source_value_forecast = kwargs.get('source_value_forecast') self.leg2_spread = kwargs.get('leg2_spread') self.short_conviction_large = kwargs.get('short_conviction_large') self.ccg_name = kwargs.get('ccg_name') self.dollar_excess_return = kwargs.get('dollar_excess_return') self.gsn = kwargs.get('gsn') self.trade_end_date = kwargs.get('trade_end_date') self.receiver_rate_option = kwargs.get('receiver_rate_option') self.gss = kwargs.get('gss') self.percent_of_mediandv1m = kwargs.get('percent_of_mediandv1m') self.lendables = kwargs.get('lendables') self.sell75cents = kwargs.get('sell75cents') self.option_adjusted_spread = kwargs.get('option_adjusted_spread') self.option_adjusted_swap_spread = kwargs.get('option_adjusted_swap_spread') self.bos_in_ticks_label = kwargs.get('bos_in_ticks_label') self.position_source_id = kwargs.get('position_source_id') self.buy1bps = kwargs.get('buy1bps') self.buy3point5bps = kwargs.get('buy3point5bps') self.gs_sustain_region = kwargs.get('gs_sustain_region') self.absolute_return_wtd = kwargs.get('absolute_return_wtd') self.deployment_id = kwargs.get('deployment_id') self.asset_parameters_seniority = kwargs.get('asset_parameters_seniority') self.ask_spread = kwargs.get('ask_spread') self.flow = kwargs.get('flow') self.future_month_h26 = kwargs.get('future_month_h26') self.loan_rebate = kwargs.get('loan_rebate') self.future_month_h25 = kwargs.get('future_month_h25') self.period = kwargs.get('period') self.index_create_source = kwargs.get('index_create_source') self.future_month_h24 = kwargs.get('future_month_h24') self.future_month_h23 = kwargs.get('future_month_h23') self.future_month_h22 = kwargs.get('future_month_h22') self.future_month_h21 = kwargs.get('future_month_h21') self.non_usd_ois = kwargs.get('non_usd_ois') self.real_twi_contribution = kwargs.get('real_twi_contribution') self.mkt_asset = kwargs.get('mkt_asset') self.leg2_index_location = kwargs.get('leg2_index_location') self.twap_unrealized_bps = kwargs.get('twap_unrealized_bps') self.last_updated_message = kwargs.get('last_updated_message') self.loan_value = kwargs.get('loan_value') self.option_adjusted_ois_spread = kwargs.get('option_adjusted_ois_spread') self.total_return_price = kwargs.get('total_return_price') self.weighted_percent_in_model = kwargs.get('weighted_percent_in_model') self.init_loan_spread_required = kwargs.get('init_loan_spread_required') self.election_period = kwargs.get('election_period') self.funding_ask_price = kwargs.get('funding_ask_price') self.historical_beta = kwargs.get('historical_beta') self.bond_risk_premium_index = kwargs.get('bond_risk_premium_index') self.hit_rate_ytd = kwargs.get('hit_rate_ytd') self.gir_gsdeer_gsfeer = kwargs.get('gir_gsdeer_gsfeer') self.num_units = kwargs.get('num_units') self.asset_parameters_receiver_frequency = kwargs.get('asset_parameters_receiver_frequency') self.expense_ratio_gross_bps = kwargs.get('expense_ratio_gross_bps') self.relative_payoff_wtd = kwargs.get('relative_payoff_wtd') self.ctd_price = kwargs.get('ctd_price') self.pace_of_roll_now = kwargs.get('pace_of_roll_now') self.product = kwargs.get('product') self.leg2_return_type = kwargs.get('leg2_return_type') self.agent_lender_fee = kwargs.get('agent_lender_fee') self.dissemination_id = kwargs.get('dissemination_id') self.option_strike_price = kwargs.get('option_strike_price') self.precipitation_type = kwargs.get('precipitation_type') self.lower_bound = kwargs.get('lower_bound') self.arrival_mid_normalized = kwargs.get('arrival_mid_normalized') self.underlying_asset2 = kwargs.get('underlying_asset2') self.underlying_asset1 = kwargs.get('underlying_asset1') self.legal_entity = kwargs.get('legal_entity') self.performance_fee = kwargs.get('performance_fee') self.order_state = kwargs.get('order_state') self.actual_data_quality = kwargs.get('actual_data_quality') self.index_ratio = kwargs.get('index_ratio') self.queue_in_lots_label = kwargs.get('queue_in_lots_label') self.adv10_day_pct = kwargs.get('adv10_day_pct') self.long_conviction_medium = kwargs.get('long_conviction_medium') self.relative_hit_rate_wtd = kwargs.get('relative_hit_rate_wtd') self.daily_tracking_error = kwargs.get('daily_tracking_error') self.sell140cents = kwargs.get('sell140cents') self.sell10bps = kwargs.get('sell10bps') self.aggressive_offset_from_last = kwargs.get('aggressive_offset_from_last') self.longitude = kwargs.get('longitude') self.new_icu = kwargs.get('new_icu') self.market_cap = kwargs.get('market_cap') self.weighted_average_mid = kwargs.get('weighted_average_mid') self.cluster_region = kwargs.get('cluster_region') self.valoren = kwargs.get('valoren') self.average_execution_price = kwargs.get('average_execution_price') self.proceeds_asset_ois_swap_spread1m = kwargs.get('proceeds_asset_ois_swap_spread1m') self.payoff_wtd = kwargs.get('payoff_wtd') self.basis = kwargs.get('basis') self.investment_rate_trend = kwargs.get('investment_rate_trend') self.gross_investment_mtd = kwargs.get('gross_investment_mtd') self.hedge_id = kwargs.get('hedge_id') self.sharpe_mtd = kwargs.get('sharpe_mtd') self.tcm_cost_horizon8_day = kwargs.get('tcm_cost_horizon8_day') self.residual_variance = kwargs.get('residual_variance') self.restrict_internal_derived_data = kwargs.get('restrict_internal_derived_data') self.adv5_day_pct = kwargs.get('adv5_day_pct') self.midpoint_fills_percentage = kwargs.get('midpoint_fills_percentage') self.open_interest = kwargs.get('open_interest') self.turnover_composite_unadjusted = kwargs.get('turnover_composite_unadjusted') self.fwd_points = kwargs.get('fwd_points') self.relative_return_wtd = kwargs.get('relative_return_wtd') self.units = kwargs.get('units') self.payer_rate_option = kwargs.get('payer_rate_option') self.asset_classifications_risk_country_name = kwargs.get('asset_classifications_risk_country_name') self.ext_mkt_point3 = kwargs.get('ext_mkt_point3') self.matched_maturity_swap_spread = kwargs.get('matched_maturity_swap_spread') self.city_name = kwargs.get('city_name') self.hourly_bucket = kwargs.get('hourly_bucket') self.average_implied_volatility = kwargs.get('average_implied_volatility') self.total_hospitalized_with_symptoms = kwargs.get('total_hospitalized_with_symptoms') self.days_open_realized_cash = kwargs.get('days_open_realized_cash') self.adjusted_high_price = kwargs.get('adjusted_high_price') self.proceeds_asset_ois_swap_spread = kwargs.get('proceeds_asset_ois_swap_spread') self.ext_mkt_point1 = kwargs.get('ext_mkt_point1') self.direction = kwargs.get('direction') self.ext_mkt_point2 = kwargs.get('ext_mkt_point2') self.sub_region_code = kwargs.get('sub_region_code') self.asset_parameters_fixed_rate = kwargs.get('asset_parameters_fixed_rate') self.is_estimated_return = kwargs.get('is_estimated_return') self.value_forecast = kwargs.get('value_forecast') self.total_icu = kwargs.get('total_icu') self.position_source_type = kwargs.get('position_source_type') self.previous_close_unrealized_cash = kwargs.get('previous_close_unrealized_cash') self.minimum_denomination = kwargs.get('minimum_denomination') self.future_value_notional = kwargs.get('future_value_notional') self.participation_rate = kwargs.get('participation_rate') self.obfr = kwargs.get('obfr') self.buy9point5bps = kwargs.get('buy9point5bps') self.option_lock_period = kwargs.get('option_lock_period') self.es_momentum_percentile = kwargs.get('es_momentum_percentile') self.adv_percentage = kwargs.get('adv_percentage') self.leg1_averaging_method = kwargs.get('leg1_averaging_method') self.turnover_composite = kwargs.get('turnover_composite') self.forecast_date = kwargs.get('forecast_date') self.internal_index_calc_region = kwargs.get('internal_index_calc_region') self.position_type = kwargs.get('position_type') self.sub_asset_class = kwargs.get('sub_asset_class') self.short_interest = kwargs.get('short_interest') self.reference_period = kwargs.get('reference_period') self.adjusted_volume = kwargs.get('adjusted_volume') self.ctd_fwd_yield = kwargs.get('ctd_fwd_yield') self.sec_db = kwargs.get('sec_db') self.memory_used = kwargs.get('memory_used') self.bpe_quality_stars = kwargs.get('bpe_quality_stars') self.ctd = kwargs.get('ctd') self.intended_participation_rate = kwargs.get('intended_participation_rate') self.leg1_payment_type = kwargs.get('leg1_payment_type') self.trading_pnl = kwargs.get('trading_pnl') self.collateral_value_required = kwargs.get('collateral_value_required') self.buy45bps = kwargs.get('buy45bps') self.price_to_earnings_positive = kwargs.get('price_to_earnings_positive') self.forecast = kwargs.get('forecast') self.forecast_value = kwargs.get('forecast_value') self.pnl = kwargs.get('pnl') self.volume_in_limit = kwargs.get('volume_in_limit') self.is_territory = kwargs.get('is_territory') self.leg2_delivery_point = kwargs.get('leg2_delivery_point') self.tcm_cost_horizon4_day = kwargs.get('tcm_cost_horizon4_day') self.styles = kwargs.get('styles') self.short_name = kwargs.get('short_name') self.reset_frequency1 = kwargs.get('reset_frequency1') self.buy4bps = kwargs.get('buy4bps') self.reset_frequency2 = kwargs.get('reset_frequency2') self.other_price_term = kwargs.get('other_price_term') self.bid_gspread = kwargs.get('bid_gspread') self.open_price = kwargs.get('open_price') self.ps_id = kwargs.get('ps_id') self.hit_rate_mtd = kwargs.get('hit_rate_mtd') self.fair_volatility = kwargs.get('fair_volatility') self.dollar_cross = kwargs.get('dollar_cross') self.portfolio_type = kwargs.get('portfolio_type') self.currency = kwargs.get('currency') self.cluster_class = kwargs.get('cluster_class') self.sell50bps = kwargs.get('sell50bps') self.future_month_m21 = kwargs.get('future_month_m21') self.bid_size = kwargs.get('bid_size') self.arrival_mid = kwargs.get('arrival_mid') self.asset_parameters_exchange_currency = kwargs.get('asset_parameters_exchange_currency') self.candidate_name = kwargs.get('candidate_name') self.implied_lognormal_volatility = kwargs.get('implied_lognormal_volatility') self.vwap_in_limit_unrealized_cash = kwargs.get('vwap_in_limit_unrealized_cash') self.rating_moodys = kwargs.get('rating_moodys') self.future_month_m26 = kwargs.get('future_month_m26') self.future_month_m25 = kwargs.get('future_month_m25') self.future_month_m24 = kwargs.get('future_month_m24') self.future_month_m23 = kwargs.get('future_month_m23') self.future_month_m22 = kwargs.get('future_month_m22') self.flow_pct = kwargs.get('flow_pct') self.source = kwargs.get('source') self.asset_classifications_country_code = kwargs.get('asset_classifications_country_code') self.settle_drop = kwargs.get('settle_drop') self.data_set_sub_category = kwargs.get('data_set_sub_category') self.sell9point5bps = kwargs.get('sell9point5bps') self.quantity_bucket = kwargs.get('quantity_bucket') self.option_style_sdr = kwargs.get('option_style_sdr') self.oe_name = kwargs.get('oe_name') self.given = kwargs.get('given') self.leg2_day_count_convention = kwargs.get('leg2_day_count_convention') self.liquidity_score_sell = kwargs.get('liquidity_score_sell') self.delisting_date = kwargs.get('delisting_date') self.weight = kwargs.get('weight') self.accrued_interest = kwargs.get('accrued_interest') self.business_scope = kwargs.get('business_scope') self.wtd_degree_days = kwargs.get('wtd_degree_days') self.absolute_weight = kwargs.get('absolute_weight') self.measure = kwargs.get('measure') self.temperature_hourly_forecast = kwargs.get('temperature_hourly_forecast') self.iceberg_tip_rate_type = kwargs.get('iceberg_tip_rate_type') self.sharpe_ytd = kwargs.get('sharpe_ytd') self.wind_speed_forecast = kwargs.get('wind_speed_forecast') self.gross_investment_ytd = kwargs.get('gross_investment_ytd') self.yield_price = kwargs.get('yield_price') self.leg1_total_notional_unit = kwargs.get('leg1_total_notional_unit') self.issue_price = kwargs.get('issue_price') self.ask_high = kwargs.get('ask_high') self.expected_data_quality = kwargs.get('expected_data_quality') self.region_name = kwargs.get('region_name') self.value_revised = kwargs.get('value_revised') self.discretion_upper_bound = kwargs.get('discretion_upper_bound') self.adjusted_trade_price = kwargs.get('adjusted_trade_price') self.forecast_time = kwargs.get('forecast_time') self.iso_subdivision_code_alpha2 = kwargs.get('iso_subdivision_code_alpha2') self.ctd_conversion_factor = kwargs.get('ctd_conversion_factor') self.proceeds_asset_swap_spread = kwargs.get('proceeds_asset_swap_spread') self.is_adr = kwargs.get('is_adr') self.issue_date = kwargs.get('issue_date') self.service_id = kwargs.get('service_id') self.yes = kwargs.get('yes') self.g_score = kwargs.get('g_score') self.market_value = kwargs.get('market_value') self.entity_id = kwargs.get('entity_id') self.notional_currency1 = kwargs.get('notional_currency1') self.net_debt_to_ebitda = kwargs.get('net_debt_to_ebitda') self.num_units_upper = kwargs.get('num_units_upper') self.notional_currency2 = kwargs.get('notional_currency2') self.in_limit_participation_rate = kwargs.get('in_limit_participation_rate') self.pressure_forecast = kwargs.get('pressure_forecast') self.paid = kwargs.get('paid') self.fixed_rate = kwargs.get('fixed_rate') self.short = kwargs.get('short') self.time = kwargs.get('time') self.buy4point5bps = kwargs.get('buy4point5bps') self.sell30cents = kwargs.get('sell30cents') self.event_end_date_time = kwargs.get('event_end_date_time') self.leg1_payment_frequency = kwargs.get('leg1_payment_frequency') self.cm_id = kwargs.get('cm_id') self.taxonomy = kwargs.get('taxonomy') self.buy45cents = kwargs.get('buy45cents') self.measures = kwargs.get('measures') self.seasonal_adjustment = kwargs.get('seasonal_adjustment') self.rank_wtd = kwargs.get('rank_wtd') self.underlyer = kwargs.get('underlyer') self.created_time = kwargs.get('created_time') self.identifier = kwargs.get('identifier') self.price_unit = kwargs.get('price_unit') self.trade_report_ref_id = kwargs.get('trade_report_ref_id') self.subdivision_id = kwargs.get('subdivision_id') self.unadjusted_low = kwargs.get('unadjusted_low') self.buy160cents = kwargs.get('buy160cents') self.portfolio_id = kwargs.get('portfolio_id') self.z_spread = kwargs.get('z_spread') self.cap_floor_atm_fwd_rate = kwargs.get('cap_floor_atm_fwd_rate') self.es_percentile = kwargs.get('es_percentile') self.tdapi = kwargs.get('tdapi') self.location_code = kwargs.get('location_code') self.rcic = kwargs.get('rcic') self.name_raw = kwargs.get('name_raw') self.simon_asset_tags = kwargs.get('simon_asset_tags') self.hit_rate_qtd = kwargs.get('hit_rate_qtd') self.primary_volume_in_limit = kwargs.get('primary_volume_in_limit') self.precipitation_daily_forecast_percent = kwargs.get('precipitation_daily_forecast_percent') self.aum_end = kwargs.get('aum_end') self.premium = kwargs.get('premium') self.low = kwargs.get('low') self.cross_group = kwargs.get('cross_group') self.report_run_time = kwargs.get('report_run_time') self.five_day_price_change_bps = kwargs.get('five_day_price_change_bps') self.holdings = kwargs.get('holdings') self.precipitation_daily_forecast = kwargs.get('precipitation_daily_forecast') self.price_method = kwargs.get('price_method') self.asset_parameters_fixed_rate_frequency = kwargs.get('asset_parameters_fixed_rate_frequency') self.ois_xccy = kwargs.get('ois_xccy') self.days_open = kwargs.get('days_open') self.buy110cents = kwargs.get('buy110cents') self.average_spread_bps = kwargs.get('average_spread_bps') self.buy55cents = kwargs.get('buy55cents') self.future_month_q26 = kwargs.get('future_month_q26') self.issue_size = kwargs.get('issue_size') self.future_month_q25 = kwargs.get('future_month_q25') self.future_month_q24 = kwargs.get('future_month_q24') self.future_month_q23 = kwargs.get('future_month_q23') self.future_month_q22 = kwargs.get('future_month_q22') self.pending_loan_count = kwargs.get('pending_loan_count') self.future_month_q21 = kwargs.get('future_month_q21') self.price_spot_stop_loss_unit = kwargs.get('price_spot_stop_loss_unit') self.price_range_in_ticks_description = kwargs.get('price_range_in_ticks_description') self.trade_volume = kwargs.get('trade_volume') self.primary_country_ric = kwargs.get('primary_country_ric') self.option_expiration_frequency = kwargs.get('option_expiration_frequency') self.is_active = kwargs.get('is_active') self.use_machine_learning = kwargs.get('use_machine_learning') self.growth_score = kwargs.get('growth_score') self.buffer_threshold = kwargs.get('buffer_threshold') self.buy120cents = kwargs.get('buy120cents') self.matched_maturity_swap_rate = kwargs.get('matched_maturity_swap_rate') self.primary_vwap = kwargs.get('primary_vwap') self.exchange_type_id = kwargs.get('exchange_type_id') self.basis_swap_rate = kwargs.get('basis_swap_rate') self.exchange_code = kwargs.get('exchange_code') self.group = kwargs.get('group') self.asset_parameters_termination_date = kwargs.get('asset_parameters_termination_date') self.estimated_spread = kwargs.get('estimated_spread') self.yield_change_on_day = kwargs.get('yield_change_on_day') self.created = kwargs.get('created') self.auto_tags = kwargs.get('auto_tags') self.tcm_cost = kwargs.get('tcm_cost') self.sustain_japan = kwargs.get('sustain_japan') self.history_start_date = kwargs.get('history_start_date') self.bid_spread = kwargs.get('bid_spread') self.percentage_complete = kwargs.get('percentage_complete') self.hedge_tracking_error = kwargs.get('hedge_tracking_error') self.wind_speed_type = kwargs.get('wind_speed_type') self.strike_price = kwargs.get('strike_price') self.par_asset_swap_spread12m = kwargs.get('par_asset_swap_spread12m') self.trade_report_id = kwargs.get('trade_report_id') self.adjusted_open_price = kwargs.get('adjusted_open_price') self.country_id = kwargs.get('country_id') self.point = kwargs.get('point') self.pnl_mtd = kwargs.get('pnl_mtd') self.total_returns = kwargs.get('total_returns') self.lender = kwargs.get('lender') self.ann_return1_year = kwargs.get('ann_return1_year') self.ctd_fwd_dv01 = kwargs.get('ctd_fwd_dv01') self.eff_yield7_day = kwargs.get('eff_yield7_day') self.meeting_date = kwargs.get('meeting_date') self.calendar_spread_mispricing = kwargs.get('calendar_spread_mispricing') self.buy140cents = kwargs.get('buy140cents') self.price_notation2_type = kwargs.get('price_notation2_type') self.fund_focus = kwargs.get('fund_focus') self.relative_strike = kwargs.get('relative_strike') self.flagship = kwargs.get('flagship') self.additional_price_notation = kwargs.get('additional_price_notation') self.factor_category = kwargs.get('factor_category') self.equity_delta = kwargs.get('equity_delta') self.gross_weight = kwargs.get('gross_weight') self.listed = kwargs.get('listed') self.sell7bps = kwargs.get('sell7bps') self.earnings_record_type = kwargs.get('earnings_record_type') self.mean = kwargs.get('mean') self.ask_yield = kwargs.get('ask_yield') self.shock_style = kwargs.get('shock_style') self.methodology = kwargs.get('methodology') self.buy25cents = kwargs.get('buy25cents') self.amount_outstanding = kwargs.get('amount_outstanding') self.market_pnl = kwargs.get('market_pnl') self.sustain_asia_ex_japan = kwargs.get('sustain_asia_ex_japan') self.sell6point5bps = kwargs.get('sell6point5bps') self.neighbour_asset_id = kwargs.get('neighbour_asset_id') self.count_ideas_ytd = kwargs.get('count_ideas_ytd') self.simon_intl_asset_tags = kwargs.get('simon_intl_asset_tags') self.path = kwargs.get('path') self.vwap_unrealized_cash = kwargs.get('vwap_unrealized_cash') self.payoff_mtd = kwargs.get('payoff_mtd') self.bos_in_bps_label = kwargs.get('bos_in_bps_label') self.bos_in_bps = kwargs.get('bos_in_bps') self.point_class = kwargs.get('point_class') self.fx_spot = kwargs.get('fx_spot') self.restrict_named_individuals = kwargs.get('restrict_named_individuals') self.hedge_volatility = kwargs.get('hedge_volatility') self.tags = kwargs.get('tags') self.population = kwargs.get('population') self.underlying_asset_id = kwargs.get('underlying_asset_id') self.real_long_rates_contribution = kwargs.get('real_long_rates_contribution') self.pctprices_return = kwargs.get('pctprices_return') self.domain = kwargs.get('domain') self.buy80cents = kwargs.get('buy80cents') self.forward_tenor = kwargs.get('forward_tenor') self.average_price = kwargs.get('average_price') self.target_price_realized_bps = kwargs.get('target_price_realized_bps') self.leg2_fixed_rate = kwargs.get('leg2_fixed_rate') self.share_class_assets = kwargs.get('share_class_assets') self.annuity = kwargs.get('annuity') self.total_count = kwargs.get('total_count') self.quote_type = kwargs.get('quote_type') self.corporate_action_status = kwargs.get('corporate_action_status') self.pegged_tip_size = kwargs.get('pegged_tip_size') self.uid = kwargs.get('uid') self.es_policy_percentile = kwargs.get('es_policy_percentile') self.usd_ois = kwargs.get('usd_ois') self.term = kwargs.get('term') self.restrict_internal_gs_ntk = kwargs.get('restrict_internal_gs_ntk') self.tcm_cost_participation_rate100_pct = kwargs.get('tcm_cost_participation_rate100_pct') self.relative_universe = kwargs.get('relative_universe') self.measure_idx = kwargs.get('measure_idx') self.fred_id = kwargs.get('fred_id') self.twi_contribution = kwargs.get('twi_contribution') self.cloud_cover_type = kwargs.get('cloud_cover_type') self.delisted = kwargs.get('delisted') self.regional_focus = kwargs.get('regional_focus') self.volume_primary = kwargs.get('volume_primary') self.asset_parameters_payer_designated_maturity = kwargs.get('asset_parameters_payer_designated_maturity') self.buy30cents = kwargs.get('buy30cents') self.funding_bid_price = kwargs.get('funding_bid_price') self.series = kwargs.get('series') self.sell3bps = kwargs.get('sell3bps') self.settlement_price = kwargs.get('settlement_price') self.quarter = kwargs.get('quarter') self.sell18bps = kwargs.get('sell18bps') self.asset_parameters_floating_rate_option = kwargs.get('asset_parameters_floating_rate_option') self.realized_vwap_performance_bps = kwargs.get('realized_vwap_performance_bps') self.vote_share = kwargs.get('vote_share') self.servicing_cost_short_pnl = kwargs.get('servicing_cost_short_pnl') self.total_confirmed = kwargs.get('total_confirmed') self.economic_forecast = kwargs.get('economic_forecast') self.plot_id = kwargs.get('plot_id') self.cluster_description = kwargs.get('cluster_description') self.concentration_limit = kwargs.get('concentration_limit') self.wind_speed = kwargs.get('wind_speed') self.observation_hour = kwargs.get('observation_hour') self.signal = kwargs.get('signal') self.borrower_id = kwargs.get('borrower_id') self.data_product = kwargs.get('data_product') self.buy7point5bps = kwargs.get('buy7point5bps') self.limit_price = kwargs.get('limit_price') self.bm_prime_id = kwargs.get('bm_prime_id') self.data_type = kwargs.get('data_type') self.count = kwargs.get('count') self.conviction = kwargs.get('conviction') self.rfqstate = kwargs.get('rfqstate') self.benchmark_maturity = kwargs.get('benchmark_maturity') self.gross_flow_normalized = kwargs.get('gross_flow_normalized') self.buy14bps = kwargs.get('buy14bps') self.factor_id = kwargs.get('factor_id') self.future_month_v26 = kwargs.get('future_month_v26') self.sts_fx_currency = kwargs.get('sts_fx_currency') self.future_month_v25 = kwargs.get('future_month_v25') self.bid_change = kwargs.get('bid_change') self.month = kwargs.get('month') self.future_month_v24 = kwargs.get('future_month_v24') self.investment_wtd = kwargs.get('investment_wtd') self.future_month_v23 = kwargs.get('future_month_v23') self.future_month_v22 = kwargs.get('future_month_v22') self.future_month_v21 = kwargs.get('future_month_v21') self.expiration = kwargs.get('expiration') self.leg2_reset_frequency = kwargs.get('leg2_reset_frequency') self.controversy_score = kwargs.get('controversy_score') self.proceed_asset_swap_spread = kwargs.get('proceed_asset_swap_spread') self.concentration_level = kwargs.get('concentration_level') self.importance = kwargs.get('importance') self.asset_classifications_gics_sector = kwargs.get('asset_classifications_gics_sector') self.sts_asset_name = kwargs.get('sts_asset_name') self.net_exposure_classification = kwargs.get('net_exposure_classification') self.settlement_method = kwargs.get('settlement_method') self.receiver_designated_maturity = kwargs.get('receiver_designated_maturity') self.title = kwargs.get('title') self.x_ref_type_id = kwargs.get('x_ref_type_id') self.duration = kwargs.get('duration') self.load = kwargs.get('load') self.alpha = kwargs.get('alpha') self.company = kwargs.get('company') self.settlement_frequency = kwargs.get('settlement_frequency') self.dist_avg7_day = kwargs.get('dist_avg7_day') self.in_risk_model = kwargs.get('in_risk_model') self.daily_net_shareholder_flows_percent = kwargs.get('daily_net_shareholder_flows_percent') self.filled_notional_local = kwargs.get('filled_notional_local') self.ever_hospitalized = kwargs.get('ever_hospitalized') self.meeting_number = kwargs.get('meeting_number') self.mid_gspread = kwargs.get('mid_gspread') self.days_open_unrealized_bps = kwargs.get('days_open_unrealized_bps') self.long_level = kwargs.get('long_level') self.data_description = kwargs.get('data_description') self.temperature_type = kwargs.get('temperature_type') self.gsideid = kwargs.get('gsideid') self.repo_rate = kwargs.get('repo_rate') self.division = kwargs.get('division') self.cloud_cover_daily_forecast = kwargs.get('cloud_cover_daily_forecast') self.wind_speed_daily_forecast = kwargs.get('wind_speed_daily_forecast') self.asset_parameters_floating_rate_day_count_fraction = kwargs.get( 'asset_parameters_floating_rate_day_count_fraction') self.trade_action = kwargs.get('trade_action') self.action = kwargs.get('action') self.ctd_yield = kwargs.get('ctd_yield') self.arrival_haircut_vwap_normalized = kwargs.get('arrival_haircut_vwap_normalized') self.price_component = kwargs.get('price_component') self.queue_clock_time_description = kwargs.get('queue_clock_time_description') self.asset_parameters_receiver_day_count_fraction = kwargs.get('asset_parameters_receiver_day_count_fraction') self.percent_mid_execution_quantity = kwargs.get('percent_mid_execution_quantity') self.delta_strike = kwargs.get('delta_strike') self.cloud_cover = kwargs.get('cloud_cover') self.asset_parameters_notional_currency = kwargs.get('asset_parameters_notional_currency') self.buy18bps = kwargs.get('buy18bps') self.value_actual = kwargs.get('value_actual') self.upi = kwargs.get('upi') self.collateral_currency = kwargs.get('collateral_currency') self.original_country = kwargs.get('original_country') self.field = kwargs.get('field') self.geographic_focus = kwargs.get('geographic_focus') self.days_open_realized_bps = kwargs.get('days_open_realized_bps') self.fx_risk_premium_index = kwargs.get('fx_risk_premium_index') self.skew = kwargs.get('skew') self.status = kwargs.get('status') self.notional_currency = kwargs.get('notional_currency') self.sustain_emerging_markets = kwargs.get('sustain_emerging_markets') self.event_date_time = kwargs.get('event_date_time') self.leg1_designated_maturity = kwargs.get('leg1_designated_maturity') self.total_price = kwargs.get('total_price') self.on_behalf_of = kwargs.get('on_behalf_of') self.test_type = kwargs.get('test_type') self.accrued_interest_standard = kwargs.get('accrued_interest_standard') self.future_month_z26 = kwargs.get('future_month_z26') self.future_month_z25 = kwargs.get('future_month_z25') self.ccg_code = kwargs.get('ccg_code') self.short_exposure = kwargs.get('short_exposure') self.leg1_fixed_payment_currency = kwargs.get('leg1_fixed_payment_currency') self.arrival_haircut_vwap = kwargs.get('arrival_haircut_vwap') self.execution_days = kwargs.get('execution_days') self.recall_due_date = kwargs.get('recall_due_date') self.forward = kwargs.get('forward') self.strike = kwargs.get('strike') self.spread_limit = kwargs.get('spread_limit') self.product_scope = kwargs.get('product_scope') self.asset_parameters_issuer_type = kwargs.get('asset_parameters_issuer_type') self.currency1 = kwargs.get('currency1') self.currency2 = kwargs.get('currency2') self.previous_close_realized_bps = kwargs.get('previous_close_realized_bps') self.days_since_reported = kwargs.get('days_since_reported') self.event_status = kwargs.get('event_status') self.vwap_in_limit = kwargs.get('vwap_in_limit') self.fwd_duration = kwargs.get('fwd_duration') self.__return = kwargs.get('return_') self.is_pair_basket = kwargs.get('is_pair_basket') self.notional_amount = kwargs.get('notional_amount') self.pay_or_receive = kwargs.get('pay_or_receive') self.total_severe = kwargs.get('total_severe') self.unexecuted_notional_usd = kwargs.get('unexecuted_notional_usd') self.expected_residual_percentage = kwargs.get('expected_residual_percentage') self.maturity_date = kwargs.get('maturity_date') self.trace_adv_sell = kwargs.get('trace_adv_sell') self.event_name = kwargs.get('event_name') self.address_line2 = kwargs.get('address_line2') self.indication_of_other_price_affecting_term = kwargs.get('indication_of_other_price_affecting_term') self.unadjusted_bid = kwargs.get('unadjusted_bid') self.backtest_type = kwargs.get('backtest_type') self.gsdeer = kwargs.get('gsdeer') self.asset_parameters_issuer = kwargs.get('asset_parameters_issuer') self.g_regional_percentile = kwargs.get('g_regional_percentile') self.coverage_checked = kwargs.get('coverage_checked') self.ois_xccy_ex_spike = kwargs.get('ois_xccy_ex_spike') self.total_risk = kwargs.get('total_risk') self.mnav = kwargs.get('mnav') self.market_volume = kwargs.get('market_volume') self.swap_annuity = kwargs.get('swap_annuity') self.par_asset_swap_spread = kwargs.get('par_asset_swap_spread') self.curr_yield7_day = kwargs.get('curr_yield7_day') self.pressure = kwargs.get('pressure') self.short_description = kwargs.get('short_description') self.future_month_z24 = kwargs.get('future_month_z24') self.feed = kwargs.get('feed') self.future_month_z23 = kwargs.get('future_month_z23') self.mkt_point1 = kwargs.get('mkt_point1') self.future_month_z22 = kwargs.get('future_month_z22') self.future_month_z21 = kwargs.get('future_month_z21') self.future_month_z20 = kwargs.get('future_month_z20') self.asset_parameters_commodity_sector = kwargs.get('asset_parameters_commodity_sector') self.price_notation2 = kwargs.get('price_notation2') self.market_buffer_threshold = kwargs.get('market_buffer_threshold') self.price_notation3 = kwargs.get('price_notation3') self.mkt_point3 = kwargs.get('mkt_point3') self.mkt_point2 = kwargs.get('mkt_point2') self.leg2_type = kwargs.get('leg2_type') self.mkt_point4 = kwargs.get('mkt_point4') self.degree_days_type = kwargs.get('degree_days_type') self.sentiment = kwargs.get('sentiment') self.investment_income = kwargs.get('investment_income') self.group_type = kwargs.get('group_type') self.forward_point_imm = kwargs.get('forward_point_imm') self.twap = kwargs.get('twap') self.client_short_name = kwargs.get('client_short_name') self.group_category = kwargs.get('group_category') self.bid_plus_ask = kwargs.get('bid_plus_ask') self.foreign_ccy_rate = kwargs.get('foreign_ccy_rate') self.election_odds = kwargs.get('election_odds') self.wind_direction_forecast = kwargs.get('wind_direction_forecast') self.require_anon_client_name = kwargs.get('require_anon_client_name') self.pricing_location = kwargs.get('pricing_location') self.beta = kwargs.get('beta') self.last_returns_end_date = kwargs.get('last_returns_end_date') self.upfront_payment_date = kwargs.get('upfront_payment_date') self.sell1point5bps = kwargs.get('sell1point5bps') self.long_exposure = kwargs.get('long_exposure') self.sell4point5bps = kwargs.get('sell4point5bps') self.tcm_cost_participation_rate20_pct = kwargs.get('tcm_cost_participation_rate20_pct') self.venue_type = kwargs.get('venue_type') self.multi_asset_class_swap = kwargs.get('multi_asset_class_swap') self.delta_change_id = kwargs.get('delta_change_id') self.implementation_id = kwargs.get('implementation_id') self.leg1_fixed_payment = kwargs.get('leg1_fixed_payment') self.es_numeric_score = kwargs.get('es_numeric_score') self.in_benchmark = kwargs.get('in_benchmark') self.action_sdr = kwargs.get('action_sdr') self.count_ideas_qtd = kwargs.get('count_ideas_qtd') self.knock_out_price = kwargs.get('knock_out_price') self.ctd_asset_id = kwargs.get('ctd_asset_id') self.buy10bps = kwargs.get('buy10bps') self.precipitation = kwargs.get('precipitation') self.value_type = kwargs.get('value_type') self.beta_adjusted_net_exposure = kwargs.get('beta_adjusted_net_exposure') self.estimated_rod_volume = kwargs.get('estimated_rod_volume') self.sell14bps = kwargs.get('sell14bps') self.excess_return_price = kwargs.get('excess_return_price') self.fx_pnl = kwargs.get('fx_pnl') self.asset_classifications_gics_industry_group = kwargs.get('asset_classifications_gics_industry_group') self.lending_sec_id = kwargs.get('lending_sec_id') self.dollar_duration = kwargs.get('dollar_duration') self.equity_theta = kwargs.get('equity_theta') self.dv01 = kwargs.get('dv01') self.start_date = kwargs.get('start_date') self.mixed_swap = kwargs.get('mixed_swap') self.swaption_premium = kwargs.get('swaption_premium') self.snowfall = kwargs.get('snowfall') self.liquidity_bucket_buy = kwargs.get('liquidity_bucket_buy') self.mic = kwargs.get('mic') self.latitude = kwargs.get('latitude') self.mid = kwargs.get('mid') self.implied_repo = kwargs.get('implied_repo') self.long = kwargs.get('long') self.first_execution_time = kwargs.get('first_execution_time') self.covered_bond = kwargs.get('covered_bond') self.region_code = kwargs.get('region_code') self.buy20cents = kwargs.get('buy20cents') self.long_weight = kwargs.get('long_weight') self.calculation_time = kwargs.get('calculation_time') self.liquidity_bucket_sell = kwargs.get('liquidity_bucket_sell') self.days_open_unrealized_cash = kwargs.get('days_open_unrealized_cash') self.temperature = kwargs.get('temperature') self.average_realized_variance = kwargs.get('average_realized_variance') self.rating_fitch = kwargs.get('rating_fitch') self.financial_returns_score = kwargs.get('financial_returns_score') self.year_or_quarter = kwargs.get('year_or_quarter') self.non_symbol_dimensions = kwargs.get('non_symbol_dimensions') self.commodities_forecast = kwargs.get('commodities_forecast') self.covid19_by_state = kwargs.get('covid19_by_state') self.percentage_expected_residual = kwargs.get('percentage_expected_residual') self.hospital_name = kwargs.get('hospital_name') self.buy90cents = kwargs.get('buy90cents') self.period_type = kwargs.get('period_type') self.asset_classifications_country_name = kwargs.get('asset_classifications_country_name') self.total_hospitalized = kwargs.get('total_hospitalized') self.pegged_refill_interval = kwargs.get('pegged_refill_interval') self.fatalities_probable = kwargs.get('fatalities_probable') self.administrative_region = kwargs.get('administrative_region') self.__open = kwargs.get('open_') self.cusip = kwargs.get('cusip') self.total_confirmed_by_state = kwargs.get('total_confirmed_by_state') self.idea_activity_time = kwargs.get('idea_activity_time') self.wind_attribute = kwargs.get('wind_attribute') self.spread_option_atm_fwd_rate = kwargs.get('spread_option_atm_fwd_rate') self.net_exposure = kwargs.get('net_exposure') self.is_legacy_pair_basket = kwargs.get('is_legacy_pair_basket') self.issuer_type = kwargs.get('issuer_type') self.buy70cents = kwargs.get('buy70cents') self.strike_reference = kwargs.get('strike_reference') self.asset_count = kwargs.get('asset_count') self.is_order_in_limit = kwargs.get('is_order_in_limit') self.fundamental_metric = kwargs.get('fundamental_metric') self.quote_status_id = kwargs.get('quote_status_id') self.absolute_value = kwargs.get('absolute_value') self.closing_report = kwargs.get('closing_report') self.previous_total_confirmed = kwargs.get('previous_total_confirmed') self.long_tenor = kwargs.get('long_tenor') self.multiplier = kwargs.get('multiplier') self.buy40cents = kwargs.get('buy40cents') self.asset_count_priced = kwargs.get('asset_count_priced') self.vote_direction = kwargs.get('vote_direction') self.implied_repo_rate = kwargs.get('implied_repo_rate') self.settlement_currency = kwargs.get('settlement_currency') self.wtd_degree_days_forecast = kwargs.get('wtd_degree_days_forecast') self.indication_of_collateralization = kwargs.get('indication_of_collateralization') self.future_month_n26 = kwargs.get('future_month_n26') self.lending_partner_fee = kwargs.get('lending_partner_fee') self.future_month_n25 = kwargs.get('future_month_n25') self.future_month_n24 = kwargs.get('future_month_n24') self.primary_vwap_realized_bps = kwargs.get('primary_vwap_realized_bps') self.future_month_n23 = kwargs.get('future_month_n23') self.future_month_n22 = kwargs.get('future_month_n22') self.future_month_n21 = kwargs.get('future_month_n21') self.break_even_inflation = kwargs.get('break_even_inflation') self.pnl_ytd = kwargs.get('pnl_ytd') self.leg1_return_type = kwargs.get('leg1_return_type') self.tenor2 = kwargs.get('tenor2') self.reset_frequency = kwargs.get('reset_frequency') self.asset_parameters_payer_frequency = kwargs.get('asset_parameters_payer_frequency') self.degree_days_forecast = kwargs.get('degree_days_forecast') self.is_manually_silenced = kwargs.get('is_manually_silenced') self.buy3bps = kwargs.get('buy3bps') self.last_updated_by_id = kwargs.get('last_updated_by_id') self.legal_entity_acct = kwargs.get('legal_entity_acct') self.target_shareholder_meeting_date = kwargs.get('target_shareholder_meeting_date') self.pace_of_rollp0 = kwargs.get('pace_of_rollp0') self.controversy_percentile = kwargs.get('controversy_percentile') self.leg1_notional_currency = kwargs.get('leg1_notional_currency') self.compliance_effective_time = kwargs.get('compliance_effective_time') self.expiration_date = kwargs.get('expiration_date') self.floating_rate_day_count_fraction = kwargs.get('floating_rate_day_count_fraction') self.call_last_date = kwargs.get('call_last_date') self.factor_return = kwargs.get('factor_return') self.passive_flow_ratio = kwargs.get('passive_flow_ratio') self.composite5_day_adv = kwargs.get('composite5_day_adv') self.marginal_contribution_to_risk = kwargs.get('marginal_contribution_to_risk') self.close_date = kwargs.get('close_date') self.temperature_hour_forecast = kwargs.get('temperature_hour_forecast') self.new_ideas_wtd = kwargs.get('new_ideas_wtd') self.asset_class_sdr = kwargs.get('asset_class_sdr') self.yield_to_worst = kwargs.get('yield_to_worst') self.closing_price = kwargs.get('closing_price') self.turnover_composite_adjusted = kwargs.get('turnover_composite_adjusted') self.comment = kwargs.get('comment') self.source_symbol = kwargs.get('source_symbol') self.ask_unadjusted = kwargs.get('ask_unadjusted') self.restrict_external_derived_data = kwargs.get('restrict_external_derived_data') self.ask_change = kwargs.get('ask_change') self.count_ideas_mtd = kwargs.get('count_ideas_mtd') self.end_date = kwargs.get('end_date') self.sunshine = kwargs.get('sunshine') self.contract_type = kwargs.get('contract_type') self.momentum_type = kwargs.get('momentum_type') self.specific_risk = kwargs.get('specific_risk') self.mdapi = kwargs.get('mdapi') self.payoff_qtd = kwargs.get('payoff_qtd') self.loss = kwargs.get('loss') self.midcurve_vol = kwargs.get('midcurve_vol') self.sell6bps = kwargs.get('sell6bps') self.trading_cost_pnl = kwargs.get('trading_cost_pnl') self.price_notation_type = kwargs.get('price_notation_type') self.price = kwargs.get('price') self.payment_quantity = kwargs.get('payment_quantity') self.redemption_date = kwargs.get('redemption_date') self.leg2_notional_currency = kwargs.get('leg2_notional_currency') self.sub_region = kwargs.get('sub_region') self.benchmark = kwargs.get('benchmark') self.tcm_cost_participation_rate15_pct = kwargs.get('tcm_cost_participation_rate15_pct') self.fiscal_year = kwargs.get('fiscal_year') self.recall_date = kwargs.get('recall_date') self.esg_metric_value = kwargs.get('esg_metric_value') self.internal = kwargs.get('internal') self.gender = kwargs.get('gender') self.asset_classifications_gics_industry = kwargs.get('asset_classifications_gics_industry') self.adjusted_bid_price = kwargs.get('adjusted_bid_price') self.low_unadjusted = kwargs.get('low_unadjusted') self.macs_secondary_asset_class = kwargs.get('macs_secondary_asset_class') self.confirmed_per_million = kwargs.get('confirmed_per_million') self.data_source_id = kwargs.get('data_source_id') self.integrated_score = kwargs.get('integrated_score') self.buy7bps = kwargs.get('buy7bps') self.arrival_mid_unrealized_cash = kwargs.get('arrival_mid_unrealized_cash') self.knock_in_price = kwargs.get('knock_in_price') self.event = kwargs.get('event') self.is_intraday_auction = kwargs.get('is_intraday_auction') self.location_name = kwargs.get('location_name') self.coupon = kwargs.get('coupon') self.percentage_auction_executed_quantity = kwargs.get('percentage_auction_executed_quantity') self.avg_yield7_day = kwargs.get('avg_yield7_day') self.original_dissemination_id = kwargs.get('original_dissemination_id') self.total_on_vent = kwargs.get('total_on_vent') self.twap_unrealized_cash = kwargs.get('twap_unrealized_cash') self.sts_credit_market = kwargs.get('sts_credit_market') self.ons_code = kwargs.get('ons_code') self.passive_touch_fills_percentage = kwargs.get('passive_touch_fills_percentage') self.seniority = kwargs.get('seniority') self.leg1_index = kwargs.get('leg1_index') self.high_unadjusted = kwargs.get('high_unadjusted') self.submission_event = kwargs.get('submission_event') self.tv_product_mnemonic = kwargs.get('tv_product_mnemonic') self.avg_trade_rate_label = kwargs.get('avg_trade_rate_label') self.last_activity_date = kwargs.get('last_activity_date') self.dissemination_time = kwargs.get('dissemination_time') self.price_to_cash = kwargs.get('price_to_cash') self.buy10cents = kwargs.get('buy10cents') self.nav_spread = kwargs.get('nav_spread') self.venue_mic = kwargs.get('venue_mic') self.dollar_total_return = kwargs.get('dollar_total_return') self.block_unit = kwargs.get('block_unit') self.mid_spread = kwargs.get('mid_spread') self.istat_province_code = kwargs.get('istat_province_code') self.total_recovered_by_state = kwargs.get('total_recovered_by_state') self.repurchase_rate = kwargs.get('repurchase_rate') self.data_source = kwargs.get('data_source') self.total_being_tested = kwargs.get('total_being_tested') self.cleared_or_bilateral = kwargs.get('cleared_or_bilateral') self.metric_name = kwargs.get('metric_name') self.ask_gspread = kwargs.get('ask_gspread') self.forecast_hour = kwargs.get('forecast_hour') self.leg2_payment_type = kwargs.get('leg2_payment_type') self.cal_spread_mis_pricing = kwargs.get('cal_spread_mis_pricing') self.total_tested_negative = kwargs.get('total_tested_negative') self.rate366 = kwargs.get('rate366') self.platform = kwargs.get('platform') self.rate365 = kwargs.get('rate365') self.fixed_rate_frequency = kwargs.get('fixed_rate_frequency') self.rate360 = kwargs.get('rate360') self.is_continuous = kwargs.get('is_continuous') self.value = kwargs.get('value') self.payer_designated_maturity = kwargs.get('payer_designated_maturity') self.product_type = kwargs.get('product_type') self.mdv22_day = kwargs.get('mdv22_day') self.twap_realized_bps = kwargs.get('twap_realized_bps') self.test_measure_label = kwargs.get('test_measure_label') self.quantity = kwargs.get('quantity') self.report_id = kwargs.get('report_id') self.index_weight = kwargs.get('index_weight') self.macs_primary_asset_class = kwargs.get('macs_primary_asset_class') self.trader = kwargs.get('trader') self.leg2_price_type = kwargs.get('leg2_price_type') self.total_active = kwargs.get('total_active') self.gsid2 = kwargs.get('gsid2') self.matched_maturity_ois_swap_spread = kwargs.get('matched_maturity_ois_swap_spread') self.valuation_date = kwargs.get('valuation_date') self.restrict_gs_federation = kwargs.get('restrict_gs_federation') self.position_source = kwargs.get('position_source') self.tcm_cost_horizon6_hour = kwargs.get('tcm_cost_horizon6_hour') self.buy200cents = kwargs.get('buy200cents') self.vwap_unrealized_bps = kwargs.get('vwap_unrealized_bps') self.price_to_book = kwargs.get('price_to_book') self.isin = kwargs.get('isin') self.pl_id = kwargs.get('pl_id') self.last_returns_start_date = kwargs.get('last_returns_start_date') self.collateral_value_variance = kwargs.get('collateral_value_variance') self.year = kwargs.get('year') self.forecast_period = kwargs.get('forecast_period') self.call_first_date = kwargs.get('call_first_date') self.data_set_ids = kwargs.get('data_set_ids') self.economic_terms_hash = kwargs.get('economic_terms_hash') self.num_beds = kwargs.get('num_beds') self.sell20bps = kwargs.get('sell20bps') self.client_type = kwargs.get('client_type') self.percentage_close_executed_quantity = kwargs.get('percentage_close_executed_quantity') self.macaulay_duration = kwargs.get('macaulay_duration') self.available_inventory = kwargs.get('available_inventory') self.est1_day_complete_pct = kwargs.get('est1_day_complete_pct') self.relative_hit_rate_ytd = kwargs.get('relative_hit_rate_ytd') self.created_by_id = kwargs.get('created_by_id') self.market_data_type = kwargs.get('market_data_type') self.real_short_rates_contribution = kwargs.get('real_short_rates_contribution') self.metric_category = kwargs.get('metric_category') self.annualized_carry = kwargs.get('annualized_carry') self.value_previous = kwargs.get('value_previous') self.transmission_classification = kwargs.get('transmission_classification') self.avg_trade_rate = kwargs.get('avg_trade_rate') self.short_level = kwargs.get('short_level') self.version = kwargs.get('version') self.category_type = kwargs.get('category_type') self.policy_rate_expectation = kwargs.get('policy_rate_expectation') self.upload_date = kwargs.get('upload_date') self.block_off_facility = kwargs.get('block_off_facility') self.unrealized_vwap_performance_usd = kwargs.get('unrealized_vwap_performance_usd') self.pace_of_rollp75 = kwargs.get('pace_of_rollp75') self.earnings_per_share_positive = kwargs.get('earnings_per_share_positive') self.num_icu_beds = kwargs.get('num_icu_beds') self.bucket_volume_in_percentage = kwargs.get('bucket_volume_in_percentage') self.estimated_trading_cost = kwargs.get('estimated_trading_cost') self.eid = kwargs.get('eid') self.relative_return_qtd = kwargs.get('relative_return_qtd') self.assessed_test_measure = kwargs.get('assessed_test_measure') self.mkt_quoting_style = kwargs.get('mkt_quoting_style') self.expiration_tenor = kwargs.get('expiration_tenor') self.price_limit = kwargs.get('price_limit') self.market_model_id = kwargs.get('market_model_id') self.receiver_frequency = kwargs.get('receiver_frequency') self.realized_correlation = kwargs.get('realized_correlation') self.issue_status = kwargs.get('issue_status') self.collateral_value_actual = kwargs.get('collateral_value_actual') self.atm_fwd_rate = kwargs.get('atm_fwd_rate') self.tcm_cost_participation_rate75_pct = kwargs.get('tcm_cost_participation_rate75_pct') self.close = kwargs.get('close') self.es_product_impact_score = kwargs.get('es_product_impact_score') self.equity_vega = kwargs.get('equity_vega') self.executed_fill_quantity = kwargs.get('executed_fill_quantity') self.lender_payment = kwargs.get('lender_payment') self.five_day_move = kwargs.get('five_day_move') self.value_format = kwargs.get('value_format') self.wind_chill_forecast = kwargs.get('wind_chill_forecast') self.target_notional = kwargs.get('target_notional') self.fill_leg_id = kwargs.get('fill_leg_id') self.rationale = kwargs.get('rationale') self.realized_twap_performance_bps = kwargs.get('realized_twap_performance_bps') self.last_updated_since = kwargs.get('last_updated_since') self.total_tests = kwargs.get('total_tests') self.equities_contribution = kwargs.get('equities_contribution') self.simon_id = kwargs.get('simon_id') self.congestion = kwargs.get('congestion') self.notes = kwargs.get('notes') self.total_probable_senior_home = kwargs.get('total_probable_senior_home') self.event_category = kwargs.get('event_category') self.average_fill_rate = kwargs.get('average_fill_rate') self.unadjusted_open = kwargs.get('unadjusted_open') self.criticality = kwargs.get('criticality') self.bid_ask_spread = kwargs.get('bid_ask_spread') self.arrival_mid_unrealized_bps = kwargs.get('arrival_mid_unrealized_bps') self.option_type = kwargs.get('option_type') self.termination_date = kwargs.get('termination_date') self.queries_per_second = kwargs.get('queries_per_second') self.liquidity_type = kwargs.get('liquidity_type') self.credit_limit = kwargs.get('credit_limit') self.rank_qtd = kwargs.get('rank_qtd') self.combined_key = kwargs.get('combined_key') self.gir_fx_forecast = kwargs.get('gir_fx_forecast') self.effective_tenor = kwargs.get('effective_tenor') self.gir_commodities_forecast = kwargs.get('gir_commodities_forecast') self.relative_humidity_daily_forecast = kwargs.get('relative_humidity_daily_forecast') self.std30_days_subsidized_yield = kwargs.get('std30_days_subsidized_yield') self.annualized_tracking_error = kwargs.get('annualized_tracking_error') self.future_month_f26 = kwargs.get('future_month_f26') self.future_month_f25 = kwargs.get('future_month_f25') self.vol_swap = kwargs.get('vol_swap') self.future_month_f24 = kwargs.get('future_month_f24') self.heat_index_daily_forecast = kwargs.get('heat_index_daily_forecast') self.future_month_f23 = kwargs.get('future_month_f23') self.real_fci = kwargs.get('real_fci') self.block_trades_and_large_notional_off_facility_swaps = kwargs.get( 'block_trades_and_large_notional_off_facility_swaps') self.future_month_f22 = kwargs.get('future_month_f22') self.buy1point5bps = kwargs.get('buy1point5bps') self.future_month_f21 = kwargs.get('future_month_f21') self.expiration_settlement_date = kwargs.get('expiration_settlement_date') self.absolute_return_qtd = kwargs.get('absolute_return_qtd') self.gross_exposure = kwargs.get('gross_exposure') self.volume = kwargs.get('volume') self.adv = kwargs.get('adv') self.short_conviction_medium = kwargs.get('short_conviction_medium') self.complete_test_measure = kwargs.get('complete_test_measure') self.exchange = kwargs.get('exchange') self.es_policy_score = kwargs.get('es_policy_score') self.roll_volume_std = kwargs.get('roll_volume_std') self.temperature_daily_forecast = kwargs.get('temperature_daily_forecast') self.relative_payoff_qtd = kwargs.get('relative_payoff_qtd') self.on_loan_percentage = kwargs.get('on_loan_percentage') self.twap_remaining_slices = kwargs.get('twap_remaining_slices') self.fair_variance = kwargs.get('fair_variance') self.hit_rate_wtd = kwargs.get('hit_rate_wtd') self.previous_close_realized_cash = kwargs.get('previous_close_realized_cash') self.realized_volatility = kwargs.get('realized_volatility') self.unexecuted_quantity = kwargs.get('unexecuted_quantity') self.proceeds_asset_swap_spread1m = kwargs.get('proceeds_asset_swap_spread1m') self.clone_parent_id = kwargs.get('clone_parent_id') self.wind_speed_hourly_forecast = kwargs.get('wind_speed_hourly_forecast') self.etf_flow_ratio = kwargs.get('etf_flow_ratio') self.asset_parameters_receiver_rate_option = kwargs.get('asset_parameters_receiver_rate_option') self.buy60cents = kwargs.get('buy60cents') self.security_sub_type_id = kwargs.get('security_sub_type_id') self.message = kwargs.get('message') self.sts_rates_country = kwargs.get('sts_rates_country') self.sell65cents = kwargs.get('sell65cents') self.horizon = kwargs.get('horizon') self.would_if_good_level = kwargs.get('would_if_good_level') self.buffer_threshold_required = kwargs.get('buffer_threshold_required') self.face_value = kwargs.get('face_value') self.roll_volume_hist = kwargs.get('roll_volume_hist') self.counter_party_status = kwargs.get('counter_party_status') self.composite22_day_adv = kwargs.get('composite22_day_adv') self.percentage_far_executed_quantity = kwargs.get('percentage_far_executed_quantity') self.loan_spread_required = kwargs.get('loan_spread_required') self.asset_class = kwargs.get('asset_class') self.sovereign_spread_contribution = kwargs.get('sovereign_spread_contribution') self.ric = kwargs.get('ric') self.bucket_end_time = kwargs.get('bucket_end_time') self.rate_type = kwargs.get('rate_type') self.total_fatalities_senior_home = kwargs.get('total_fatalities_senior_home') self.loan_status = kwargs.get('loan_status') self.short_weight = kwargs.get('short_weight') self.geography_id = kwargs.get('geography_id') self.sell7point5bps = kwargs.get('sell7point5bps') self.nav = kwargs.get('nav') self.fiscal_quarter = kwargs.get('fiscal_quarter') self.version_string = kwargs.get('version_string') self.payoff_ytd = kwargs.get('payoff_ytd') self.market_impact = kwargs.get('market_impact') self.event_type = kwargs.get('event_type') self.fill_price = kwargs.get('fill_price') self.asset_count_long = kwargs.get('asset_count_long') self.sell180cents = kwargs.get('sell180cents') self.spot = kwargs.get('spot') self.application_id = kwargs.get('application_id') self.indicative_close_price = kwargs.get('indicative_close_price') self.swap_spread = kwargs.get('swap_spread') self.trading_restriction = kwargs.get('trading_restriction') self.asset_parameters_pay_or_receive = kwargs.get('asset_parameters_pay_or_receive') self.price_spot_entry_unit = kwargs.get('price_spot_entry_unit') self.unrealized_arrival_performance_bps = kwargs.get('unrealized_arrival_performance_bps') self.city = kwargs.get('city') self.pnl_wtd = kwargs.get('pnl_wtd') self.covariance = kwargs.get('covariance') self.bucket_volume_in_shares = kwargs.get('bucket_volume_in_shares') self.commodity_forecast = kwargs.get('commodity_forecast') self.valid = kwargs.get('valid') self.sts_commodity = kwargs.get('sts_commodity') self.initial_pricing_date = kwargs.get('initial_pricing_date') self.indication_of_end_user_exception = kwargs.get('indication_of_end_user_exception') self.wind_direction_hourly_forecast = kwargs.get('wind_direction_hourly_forecast') self.es_score = kwargs.get('es_score') self.__yield = kwargs.get('yield_') self.fatalities_underlying_conditions_present = kwargs.get('fatalities_underlying_conditions_present') self.price_range_in_ticks = kwargs.get('price_range_in_ticks') self.pace_of_rollp25 = kwargs.get('pace_of_rollp25') self.day_close_realized_usd = kwargs.get('day_close_realized_usd') self.pct_change = kwargs.get('pct_change') self.brightness_type = kwargs.get('brightness_type') self.future_month3_m = kwargs.get('future_month3_m') self.number_of_rolls = kwargs.get('number_of_rolls') self.iso_country_code_numeric = kwargs.get('iso_country_code_numeric') self.price_type = kwargs.get('price_type') self.realized_vwap_performance_usd = kwargs.get('realized_vwap_performance_usd') self.fuel_type = kwargs.get('fuel_type') self.bbid = kwargs.get('bbid') self.vega_notional_amount = kwargs.get('vega_notional_amount') self.fatalities_underlying_conditions_absent = kwargs.get('fatalities_underlying_conditions_absent') self.effective_date = kwargs.get('effective_date') self.capped = kwargs.get('capped') self.rating = kwargs.get('rating') self.option_currency = kwargs.get('option_currency') self.is_close_auction = kwargs.get('is_close_auction') self.volatility = kwargs.get('volatility') self.avg_vent_util = kwargs.get('avg_vent_util') self.underlying_asset_ids = kwargs.get('underlying_asset_ids') self.buy6point5bps = kwargs.get('buy6point5bps') self.vwap_in_limit_realized_cash = kwargs.get('vwap_in_limit_realized_cash') self.estimated_closing_auction_volume = kwargs.get('estimated_closing_auction_volume') self.sell2bps = kwargs.get('sell2bps') self.annual_risk = kwargs.get('annual_risk') self.eti = kwargs.get('eti') self.vwap_in_limit_realized_bps = kwargs.get('vwap_in_limit_realized_bps') self.rank_mtd = kwargs.get('rank_mtd') self.market_buffer = kwargs.get('market_buffer') self.future_month_j24 = kwargs.get('future_month_j24') self.last_uploaded_time = kwargs.get('last_uploaded_time') self.future_month_j23 = kwargs.get('future_month_j23') self.oe_id = kwargs.get('oe_id') self.future_month_j22 = kwargs.get('future_month_j22') self.future_month_j21 = kwargs.get('future_month_j21') self.bbid_equivalent = kwargs.get('bbid_equivalent') self.init_buffer_threshold_required = kwargs.get('init_buffer_threshold_required') self.leg2_designated_maturity = kwargs.get('leg2_designated_maturity') self.matched_maturity_ois_swap_rate = kwargs.get('matched_maturity_ois_swap_rate') self.fair_price = kwargs.get('fair_price') self.participation_rate_in_limit = kwargs.get('participation_rate_in_limit') self.ext_mkt_class = kwargs.get('ext_mkt_class') self.price_currency = kwargs.get('price_currency') self.failed_count = kwargs.get('failed_count') self.leg1_index_location = kwargs.get('leg1_index_location') self.supra_strategy = kwargs.get('supra_strategy') self.day_count_convention = kwargs.get('day_count_convention') self.rounded_notional_amount1 = kwargs.get('rounded_notional_amount1') self.rounded_notional_amount2 = kwargs.get('rounded_notional_amount2') self.factor_source = kwargs.get('factor_source') self.future_month_j26 = kwargs.get('future_month_j26') self.lending_sec_type = kwargs.get('lending_sec_type') self.future_month_j25 = kwargs.get('future_month_j25') self.leverage = kwargs.get('leverage') self.forecast_day = kwargs.get('forecast_day') self.option_family = kwargs.get('option_family') self.generator_output = kwargs.get('generator_output') self.price_spot_stop_loss_value = kwargs.get('price_spot_stop_loss_value') self.kpi_id = kwargs.get('kpi_id') self.wind_generation = kwargs.get('wind_generation') self.percentage_mid_executed_quantity = kwargs.get('percentage_mid_executed_quantity') self.borrow_cost = kwargs.get('borrow_cost') self.knock_out_direction = kwargs.get('knock_out_direction') self.risk_model = kwargs.get('risk_model') self.asset_parameters_vendor = kwargs.get('asset_parameters_vendor') self.fair_value = kwargs.get('fair_value') self.open_time = kwargs.get('open_time') self.pressure_hourly_forecast = kwargs.get('pressure_hourly_forecast') self.local_ccy_rate = kwargs.get('local_ccy_rate') self.end_user_exception = kwargs.get('end_user_exception') self.sell90cents = kwargs.get('sell90cents') self.execution_venue = kwargs.get('execution_venue') self.primary_vwap_in_limit_realized_bps = kwargs.get('primary_vwap_in_limit_realized_bps') self.approve_rebalance = kwargs.get('approve_rebalance') self.adjusted_close_price = kwargs.get('adjusted_close_price') self.lms_id = kwargs.get('lms_id') self.rebate_rate = kwargs.get('rebate_rate') self.sell130cents = kwargs.get('sell130cents') self.sell32bps = kwargs.get('sell32bps') self.pace_of_rollp50 = kwargs.get('pace_of_rollp50') self.price_move_vs_arrival = kwargs.get('price_move_vs_arrival') self.strike_relative = kwargs.get('strike_relative') self.pressure_type = kwargs.get('pressure_type') self.buy40bps = kwargs.get('buy40bps') self.price_notation = kwargs.get('price_notation') self.strategy = kwargs.get('strategy') self.issue_status_date = kwargs.get('issue_status_date') self.lender_income = kwargs.get('lender_income') self.pb_client_id = kwargs.get('pb_client_id') self.istat_region_code = kwargs.get('istat_region_code') self.sell9bps = kwargs.get('sell9bps') self.owner_id = kwargs.get('owner_id') self.composite10_day_adv = kwargs.get('composite10_day_adv') self.max_loan_balance = kwargs.get('max_loan_balance') self.idea_activity_type = kwargs.get('idea_activity_type') self.sell60cents = kwargs.get('sell60cents') self.idea_source = kwargs.get('idea_source') self.ever_on_vent = kwargs.get('ever_on_vent') self.buy15cents = kwargs.get('buy15cents') self.unadjusted_ask = kwargs.get('unadjusted_ask') self.contribution_name = kwargs.get('contribution_name') self.given_plus_paid = kwargs.get('given_plus_paid') self.last_fill_price = kwargs.get('last_fill_price') self.short_conviction_small = kwargs.get('short_conviction_small') self.upfront_payment_currency = kwargs.get('upfront_payment_currency') self.spot_settlement_date = kwargs.get('spot_settlement_date') self.matrix_order = kwargs.get('matrix_order') self.date_index = kwargs.get('date_index') self.payer_day_count_fraction = kwargs.get('payer_day_count_fraction') self.asset_classifications_is_primary = kwargs.get('asset_classifications_is_primary') self.break_even_inflation_change = kwargs.get('break_even_inflation_change') self.buy130cents = kwargs.get('buy130cents') self.dwi_contribution = kwargs.get('dwi_contribution') self.asset2_id = kwargs.get('asset2_id') self.average_fill_price = kwargs.get('average_fill_price') self.depth_spread_score = kwargs.get('depth_spread_score') self.sell10cents = kwargs.get('sell10cents') self.sub_account = kwargs.get('sub_account') self.buy65cents = kwargs.get('buy65cents') self.bond_cds_basis = kwargs.get('bond_cds_basis') self.vendor = kwargs.get('vendor') self.data_set = kwargs.get('data_set') self.notional_amount2 = kwargs.get('notional_amount2') self.notional_amount1 = kwargs.get('notional_amount1') self.queueing_time = kwargs.get('queueing_time') self.ann_return5_year = kwargs.get('ann_return5_year') self.volume_start_of_day = kwargs.get('volume_start_of_day') self.price_notation3_type = kwargs.get('price_notation3_type') self.asset_parameters_floating_rate_designated_maturity = kwargs.get( 'asset_parameters_floating_rate_designated_maturity') self.executed_notional_local = kwargs.get('executed_notional_local') self.business_sponsor = kwargs.get('business_sponsor') self.unexplained = kwargs.get('unexplained') self.seasonal_adjustment_short = kwargs.get('seasonal_adjustment_short') self.metric = kwargs.get('metric') self.ask = kwargs.get('ask') self.close_price = kwargs.get('close_price') self.end_time = kwargs.get('end_time') self.sell100cents = kwargs.get('sell100cents') self.execution_timestamp = kwargs.get('execution_timestamp') self.buy180cents = kwargs.get('buy180cents') self.absolute_strike = kwargs.get('absolute_strike') self.sell3point5bps = kwargs.get('sell3point5bps') self.liquidity_score_buy = kwargs.get('liquidity_score_buy') self.payment_frequency = kwargs.get('payment_frequency') self.expense_ratio_net_bps = kwargs.get('expense_ratio_net_bps') self.metric_type = kwargs.get('metric_type') self.rank_ytd = kwargs.get('rank_ytd') self.leg1_spread = kwargs.get('leg1_spread') self.coverage_region = kwargs.get('coverage_region') self.absolute_return_ytd = kwargs.get('absolute_return_ytd') self.day_count_convention2 = kwargs.get('day_count_convention2') self.fwdtier = kwargs.get('fwdtier') self.degree_days = kwargs.get('degree_days') self.turnover_adjusted = kwargs.get('turnover_adjusted') self.price_spot_target_value = kwargs.get('price_spot_target_value') self.market_data_point = kwargs.get('market_data_point') self.num_of_funds = kwargs.get('num_of_funds') self.trade_time = kwargs.get('trade_time') self.execution_id = kwargs.get('execution_id') self.turnover_unadjusted = kwargs.get('turnover_unadjusted') self.leg1_floating_index = kwargs.get('leg1_floating_index') self.hedge_annualized_volatility = kwargs.get('hedge_annualized_volatility') self.benchmark_currency = kwargs.get('benchmark_currency') self.futures_contract = kwargs.get('futures_contract') self.name = kwargs.get('name') self.aum = kwargs.get('aum') self.leg1_day_count_convention = kwargs.get('leg1_day_count_convention') self.cbs_code = kwargs.get('cbs_code') self.folder_name = kwargs.get('folder_name') self.api_usage = kwargs.get('api_usage') self.twap_interval = kwargs.get('twap_interval') self.unique_id = kwargs.get('unique_id') self.option_expiration_date = kwargs.get('option_expiration_date') self.swaption_atm_fwd_rate = kwargs.get('swaption_atm_fwd_rate') self.live_date = kwargs.get('live_date') self.corporate_action_type = kwargs.get('corporate_action_type') self.prime_id = kwargs.get('prime_id') self.description = kwargs.get('description') self.asset_classifications_is_country_primary = kwargs.get('asset_classifications_is_country_primary') self.rebate_rate_limit = kwargs.get('rebate_rate_limit') self.factor = kwargs.get('factor') self.days_on_loan = kwargs.get('days_on_loan') self.long_conviction_small = kwargs.get('long_conviction_small') self.sell40cents = kwargs.get('sell40cents') self.relative_payoff_ytd = kwargs.get('relative_payoff_ytd') self.gsfeer = kwargs.get('gsfeer') self.relative_hit_rate_qtd = kwargs.get('relative_hit_rate_qtd') self.wam = kwargs.get('wam') self.wal = kwargs.get('wal') self.quantityccy = kwargs.get('quantityccy') self.backtest_id = kwargs.get('backtest_id') self.dirty_price = kwargs.get('dirty_price') self.corporate_spread_contribution = kwargs.get('corporate_spread_contribution') self.relative_humidity_hourly_forecast = kwargs.get('relative_humidity_hourly_forecast') self.multiple_score = kwargs.get('multiple_score') self.beta_adjusted_exposure = kwargs.get('beta_adjusted_exposure') self.dividend_points = kwargs.get('dividend_points') self.brightness = kwargs.get('brightness') self.asset_parameters_receiver_designated_maturity = kwargs.get( 'asset_parameters_receiver_designated_maturity') self.bos_in_ticks_description = kwargs.get('bos_in_ticks_description') self.test_id = kwargs.get('test_id') self.implied_correlation = kwargs.get('implied_correlation') self.normalized_performance = kwargs.get('normalized_performance') self.overnight_news_end_time = kwargs.get('overnight_news_end_time') self.bytes_consumed = kwargs.get('bytes_consumed') self.swaption_vol = kwargs.get('swaption_vol') self.estimated_closing_volume = kwargs.get('estimated_closing_volume') self.issuer = kwargs.get('issuer') self.dividend_yield = kwargs.get('dividend_yield') self.market_type = kwargs.get('market_type') self.num_units_lower = kwargs.get('num_units_lower') self.source_origin = kwargs.get('source_origin') self.proceeds_asset_swap_spread3m = kwargs.get('proceeds_asset_swap_spread3m') self.total_quantity = kwargs.get('total_quantity') self.internal_user = kwargs.get('internal_user') self.sell40bps = kwargs.get('sell40bps') self.redemption_option = kwargs.get('redemption_option') self.notional_unit2 = kwargs.get('notional_unit2') self.notional_unit1 = kwargs.get('notional_unit1') self.sedol = kwargs.get('sedol') self.rounding_cost_pnl = kwargs.get('rounding_cost_pnl') self.mid_yield = kwargs.get('mid_yield') self.unexecuted_notional_local = kwargs.get('unexecuted_notional_local') self.sustain_global = kwargs.get('sustain_global') self.ending_date = kwargs.get('ending_date') self.proceeds_asset_swap_spread12m = kwargs.get('proceeds_asset_swap_spread12m') self.gross_investment_wtd = kwargs.get('gross_investment_wtd') self.ann_return3_year = kwargs.get('ann_return3_year') self.sharpe_wtd = kwargs.get('sharpe_wtd') self.discount_factor = kwargs.get('discount_factor') self.relative_return_mtd = kwargs.get('relative_return_mtd') self.price_change_on_day = kwargs.get('price_change_on_day') self.buy100cents = kwargs.get('buy100cents') self.forward_point = kwargs.get('forward_point') self.fci = kwargs.get('fci') self.recall_quantity = kwargs.get('recall_quantity') self.fx_positioning = kwargs.get('fx_positioning') self.gsid_equivalent = kwargs.get('gsid_equivalent') self.categories = kwargs.get('categories') self.ext_mkt_asset = kwargs.get('ext_mkt_asset') self.quoting_style = kwargs.get('quoting_style') self.error_message = kwargs.get('error_message') self.mid_price = kwargs.get('mid_price') self.proceeds_asset_swap_spread6m = kwargs.get('proceeds_asset_swap_spread6m') self.sts_em_dm = kwargs.get('sts_em_dm') self.embedded_option = kwargs.get('embedded_option') self.tcm_cost_horizon2_day = kwargs.get('tcm_cost_horizon2_day') self.age_band = kwargs.get('age_band') self.returns_enabled = kwargs.get('returns_enabled') self.run_id = kwargs.get('run_id') self.queue_in_lots = kwargs.get('queue_in_lots') self.tender_offer_expiration_date = kwargs.get('tender_offer_expiration_date') self.midcurve_annuity = kwargs.get('midcurve_annuity') self.lending_fund_nav_trend = kwargs.get('lending_fund_nav_trend') self.cloud_cover_forecast = kwargs.get('cloud_cover_forecast') self.tcm_cost_participation_rate5_pct = kwargs.get('tcm_cost_participation_rate5_pct') self.default_backcast = kwargs.get('default_backcast') self.news_on_intensity = kwargs.get('news_on_intensity') self.price_forming_continuation_data = kwargs.get('price_forming_continuation_data') self.adjusted_short_interest = kwargs.get('adjusted_short_interest') self.new_hospitalized = kwargs.get('new_hospitalized') self.asset_parameters_strike = kwargs.get('asset_parameters_strike') self.buy35cents = kwargs.get('buy35cents') self.leg2_total_notional = kwargs.get('leg2_total_notional') self.asset_parameters_effective_date = kwargs.get('asset_parameters_effective_date') self.ann_return10_year = kwargs.get('ann_return10_year') self.num_adult_icu_beds = kwargs.get('num_adult_icu_beds') self.days_to_expiration = kwargs.get('days_to_expiration') self.continuation_event = kwargs.get('continuation_event') self.wi_id = kwargs.get('wi_id') self.market_cap_category = kwargs.get('market_cap_category') self.historical_volume = kwargs.get('historical_volume') self.buy5cents = kwargs.get('buy5cents') self.event_start_date = kwargs.get('event_start_date') self.leg1_fixed_rate = kwargs.get('leg1_fixed_rate') self.equity_gamma = kwargs.get('equity_gamma') self.rpt_id = kwargs.get('rpt_id') self.gross_income = kwargs.get('gross_income') self.em_id = kwargs.get('em_id') self.asset_count_in_model = kwargs.get('asset_count_in_model') self.sts_credit_region = kwargs.get('sts_credit_region') self.min_temperature = kwargs.get('min_temperature') self.bucket_start_time = kwargs.get('bucket_start_time') self.fill_type = kwargs.get('fill_type') self.close_time = kwargs.get('close_time') self.fail_pct = kwargs.get('fail_pct') self.iso_country_code_alpha2 = kwargs.get('iso_country_code_alpha2') self.iso_country_code_alpha3 = kwargs.get('iso_country_code_alpha3') self.amount = kwargs.get('amount') self.lending_fund_acct = kwargs.get('lending_fund_acct') self.rebate = kwargs.get('rebate') self.election_type = kwargs.get('election_type') self.relative_hit_rate_mtd = kwargs.get('relative_hit_rate_mtd') self.implied_volatility = kwargs.get('implied_volatility') self.spread = kwargs.get('spread') self.variance = kwargs.get('variance') self.wtd_degree_days_daily_forecast = kwargs.get('wtd_degree_days_daily_forecast') self.swaption_annuity = kwargs.get('swaption_annuity') self.buy6bps = kwargs.get('buy6bps') self.g10_currency = kwargs.get('g10_currency') self.humidity_forecast = kwargs.get('humidity_forecast') self.relative_period = kwargs.get('relative_period') self.user = kwargs.get('user') self.customer = kwargs.get('customer') self.leg1_reset_frequency = kwargs.get('leg1_reset_frequency') self.queue_clock_time_label = kwargs.get('queue_clock_time_label') self.pace_of_rollp100 = kwargs.get('pace_of_rollp100') self.asset_classifications_gics_sub_industry = kwargs.get('asset_classifications_gics_sub_industry') self.dew_point_hourly_forecast = kwargs.get('dew_point_hourly_forecast') self.location_type = kwargs.get('location_type') self.facet_divisional_reporting_group_id = kwargs.get('facet_divisional_reporting_group_id') self.realized_twap_performance_usd = kwargs.get('realized_twap_performance_usd') self.swap_rate = kwargs.get('swap_rate') self.algo_execution_style = kwargs.get('algo_execution_style') self.client_contact = kwargs.get('client_contact') self.min_temperature_hour = kwargs.get('min_temperature_hour') self.trading_currency = kwargs.get('trading_currency') self.total_by_onset = kwargs.get('total_by_onset') self.agency_swap_spread = kwargs.get('agency_swap_spread') self.rank = kwargs.get('rank') self.mixed_swap_other_reported_sdr = kwargs.get('mixed_swap_other_reported_sdr') self.humidity = kwargs.get('humidity') self.data_set_category = kwargs.get('data_set_category') self.vwap_realized_bps = kwargs.get('vwap_realized_bps') self.buy9bps = kwargs.get('buy9bps') self.total_tested = kwargs.get('total_tested') self.fatalities_confirmed = kwargs.get('fatalities_confirmed') self.universe_id1 = kwargs.get('universe_id1') self.asset_parameters_payer_day_count_fraction = kwargs.get('asset_parameters_payer_day_count_fraction') self.universe_id2 = kwargs.get('universe_id2') self.bid_low = kwargs.get('bid_low') self.bucketize_price = kwargs.get('bucketize_price') self.fair_variance_volatility = kwargs.get('fair_variance_volatility') self.covid19 = kwargs.get('covid19') self.client_exposure = kwargs.get('client_exposure') self.leg2_total_notional_unit = kwargs.get('leg2_total_notional_unit') self.sell45cents = kwargs.get('sell45cents') self.gs_sustain_sub_sector = kwargs.get('gs_sustain_sub_sector') self.sinkable = kwargs.get('sinkable') self.is_real = kwargs.get('is_real') self.max_temperature_hour = kwargs.get('max_temperature_hour') self.leg2_averaging_method = kwargs.get('leg2_averaging_method') self.jsn = kwargs.get('jsn') self.sell160cents = kwargs.get('sell160cents') self.knock_in_direction = kwargs.get('knock_in_direction') self.day_close_unrealized_usd = kwargs.get('day_close_unrealized_usd') self.tenor = kwargs.get('tenor') self.pricing_convention = kwargs.get('pricing_convention') self.popularity = kwargs.get('popularity') self.floating_rate_option = kwargs.get('floating_rate_option') self.hedge_value_type = kwargs.get('hedge_value_type') self.asset_parameters_clearing_house = kwargs.get('asset_parameters_clearing_house') self.disclaimer = kwargs.get('disclaimer') self.payer_frequency = kwargs.get('payer_frequency') self.loan_fee = kwargs.get('loan_fee') self.deployment_version = kwargs.get('deployment_version') self.buy16bps = kwargs.get('buy16bps') self.trade_day_count = kwargs.get('trade_day_count') self.price_to_sales = kwargs.get('price_to_sales') self.new_ideas_qtd = kwargs.get('new_ideas_qtd') self.subdivision_name = kwargs.get('subdivision_name') self.adjusted_ask_price = kwargs.get('adjusted_ask_price') self.factor_universe = kwargs.get('factor_universe') self.arrival_rt = kwargs.get('arrival_rt') self.internal_index_calc_agent = kwargs.get('internal_index_calc_agent') self.excess_margin_value = kwargs.get('excess_margin_value') self.transaction_cost = kwargs.get('transaction_cost') self.central_bank_swap_rate = kwargs.get('central_bank_swap_rate') self.previous_new_confirmed = kwargs.get('previous_new_confirmed') self.unrealized_vwap_performance_bps = kwargs.get('unrealized_vwap_performance_bps') self.degree_days_daily_forecast = kwargs.get('degree_days_daily_forecast') self.position_amount = kwargs.get('position_amount') self.heat_index_hourly_forecast = kwargs.get('heat_index_hourly_forecast') self.ma_rank = kwargs.get('ma_rank') self.fx_positioning_source = kwargs.get('fx_positioning_source') self.event_start_date_time = kwargs.get('event_start_date_time') self.implied_volatility_by_delta_strike = kwargs.get('implied_volatility_by_delta_strike') self.mq_symbol = kwargs.get('mq_symbol') self.num_total_units = kwargs.get('num_total_units') self.corporate_action = kwargs.get('corporate_action') self.leg1_price_type = kwargs.get('leg1_price_type') self.asset_parameters_payer_rate_option = kwargs.get('asset_parameters_payer_rate_option') self.sell20cents = kwargs.get('sell20cents') self.leg2_fixed_payment_currency = kwargs.get('leg2_fixed_payment_currency') self.g_regional_score = kwargs.get('g_regional_score') self.hard_to_borrow = kwargs.get('hard_to_borrow') self.sell5bps = kwargs.get('sell5bps') self.roll_vwap = kwargs.get('roll_vwap') self.wpk = kwargs.get('wpk') self.bespoke_swap = kwargs.get('bespoke_swap') self.asset_parameters_expiration_date = kwargs.get('asset_parameters_expiration_date') self.country_name = kwargs.get('country_name') self.carry = kwargs.get('carry') self.starting_date = kwargs.get('starting_date') self.loan_id = kwargs.get('loan_id') self.onboarded = kwargs.get('onboarded') self.liquidity_score = kwargs.get('liquidity_score') self.long_rates_contribution = kwargs.get('long_rates_contribution') self.source_date_span = kwargs.get('source_date_span') self.ann_yield6_month = kwargs.get('ann_yield6_month') self.underlying_data_set_id = kwargs.get('underlying_data_set_id') self.close_unadjusted = kwargs.get('close_unadjusted') self.value_unit = kwargs.get('value_unit') self.quantity_unit = kwargs.get('quantity_unit') self.adjusted_low_price = kwargs.get('adjusted_low_price') self.is_momentum = kwargs.get('is_momentum') self.long_conviction_large = kwargs.get('long_conviction_large') self.oad = kwargs.get('oad') self.rate = kwargs.get('rate') self.coupon_type = kwargs.get('coupon_type') self.client = kwargs.get('client') self.conviction_list = kwargs.get('conviction_list') self.passive_etf_ratio = kwargs.get('passive_etf_ratio') self.future_month_g26 = kwargs.get('future_month_g26') self.future_month_g25 = kwargs.get('future_month_g25') self.future_month_g24 = kwargs.get('future_month_g24') self.future_month_g23 = kwargs.get('future_month_g23') self.type_of_return = kwargs.get('type_of_return') self.future_month_g22 = kwargs.get('future_month_g22') self.servicing_cost_long_pnl = kwargs.get('servicing_cost_long_pnl') self.excess_margin_percentage = kwargs.get('excess_margin_percentage') self.future_month_g21 = kwargs.get('future_month_g21') self.total_mild = kwargs.get('total_mild') self.realized_arrival_performance_bps = kwargs.get('realized_arrival_performance_bps') self.precipitation_daily_forecast_inches = kwargs.get('precipitation_daily_forecast_inches') self.exchange_id = kwargs.get('exchange_id') self.leg2_fixed_payment = kwargs.get('leg2_fixed_payment') self.tcm_cost_horizon20_day = kwargs.get('tcm_cost_horizon20_day') self.realm = kwargs.get('realm') self.bid = kwargs.get('bid') self.hedge_value = kwargs.get('hedge_value') self.order_start_time = kwargs.get('order_start_time') self.is_aggressive = kwargs.get('is_aggressive') self.floating_rate_designated_maturity = kwargs.get('floating_rate_designated_maturity') self.percentage_near_executed_quantity = kwargs.get('percentage_near_executed_quantity') self.order_id = kwargs.get('order_id') self.hospital_type = kwargs.get('hospital_type') self.day_close_realized_bps = kwargs.get('day_close_realized_bps') self.precipitation_hourly_forecast = kwargs.get('precipitation_hourly_forecast') self.market_cap_usd = kwargs.get('market_cap_usd') self.auction_fills_percentage = kwargs.get('auction_fills_percentage') self.high_price = kwargs.get('high_price') self.absolute_shares = kwargs.get('absolute_shares') self.fixed_rate_day_count_fraction = kwargs.get('fixed_rate_day_count_fraction') self.model = kwargs.get('model') self.unrealized_twap_performance_usd = kwargs.get('unrealized_twap_performance_usd') self.__id = kwargs.get('id_') self.maturity = kwargs.get('maturity') self.delta_change = kwargs.get('delta_change') self.index = kwargs.get('index') self.unrealized_arrival_performance_usd = kwargs.get('unrealized_arrival_performance_usd') self.iceberg_slippage = kwargs.get('iceberg_slippage') self.sell120cents = kwargs.get('sell120cents') self.future_month_x26 = kwargs.get('future_month_x26') self.asset_types = kwargs.get('asset_types') self.future_month_x25 = kwargs.get('future_month_x25') self.bcid = kwargs.get('bcid') self.mkt_point = kwargs.get('mkt_point') self.future_month_x24 = kwargs.get('future_month_x24') self.restriction_start_date = kwargs.get('restriction_start_date') self.touch_liquidity_score = kwargs.get('touch_liquidity_score') self.future_month_x23 = kwargs.get('future_month_x23') self.future_month_x22 = kwargs.get('future_month_x22') self.factor_category_id = kwargs.get('factor_category_id') self.security_type_id = kwargs.get('security_type_id') self.future_month_x21 = kwargs.get('future_month_x21') self.investment_ytd = kwargs.get('investment_ytd') self.leg2_notional = kwargs.get('leg2_notional') self.sell1bps = kwargs.get('sell1bps') self.sell200cents = kwargs.get('sell200cents') self.expected_completion_date = kwargs.get('expected_completion_date') self.spread_option_vol = kwargs.get('spread_option_vol') self.sell80cents = kwargs.get('sell80cents') self.inflation_swap_rate = kwargs.get('inflation_swap_rate') self.active_queries = kwargs.get('active_queries') self.sell45bps = kwargs.get('sell45bps') self.embeded_option = kwargs.get('embeded_option') self.event_source = kwargs.get('event_source') self.qis_perm_no = kwargs.get('qis_perm_no') self.settlement = kwargs.get('settlement') self.shareclass_id = kwargs.get('shareclass_id') self.feature2 = kwargs.get('feature2') self.feature3 = kwargs.get('feature3') self.sts_commodity_sector = kwargs.get('sts_commodity_sector') self.exception_status = kwargs.get('exception_status') self.overnight_news_intensity = kwargs.get('overnight_news_intensity') self.sales_coverage = kwargs.get('sales_coverage') self.feature1 = kwargs.get('feature1') self.tcm_cost_participation_rate10_pct = kwargs.get('tcm_cost_participation_rate10_pct') self.event_time = kwargs.get('event_time') self.position_source_name = kwargs.get('position_source_name') self.delivery_date = kwargs.get('delivery_date') self.interest_rate = kwargs.get('interest_rate') self.side = kwargs.get('side') self.dynamic_hybrid_aggressive_style = kwargs.get('dynamic_hybrid_aggressive_style') self.compliance_restricted_status = kwargs.get('compliance_restricted_status') self.borrow_fee = kwargs.get('borrow_fee') self.ever_icu = kwargs.get('ever_icu') self.no_worse_than_level = kwargs.get('no_worse_than_level') self.update_time = kwargs.get('update_time') self.loan_spread = kwargs.get('loan_spread') self.tcm_cost_horizon12_hour = kwargs.get('tcm_cost_horizon12_hour') self.dew_point = kwargs.get('dew_point') self.research_commission = kwargs.get('research_commission') self.buy2bps = kwargs.get('buy2bps') self.asset_classifications_risk_country_code = kwargs.get('asset_classifications_risk_country_code') self.new_ideas_mtd = kwargs.get('new_ideas_mtd') self.var_swap_by_expiry = kwargs.get('var_swap_by_expiry') self.sell_date = kwargs.get('sell_date') self.aum_start = kwargs.get('aum_start') self.asset_parameters_settlement = kwargs.get('asset_parameters_settlement') self.max_temperature = kwargs.get('max_temperature') self.acquirer_shareholder_meeting_date = kwargs.get('acquirer_shareholder_meeting_date') self.count_ideas_wtd = kwargs.get('count_ideas_wtd') self.arrival_rt_normalized = kwargs.get('arrival_rt_normalized') self.report_type = kwargs.get('report_type') self.source_url = kwargs.get('source_url') self.estimated_return = kwargs.get('estimated_return') self.high = kwargs.get('high') self.source_last_update = kwargs.get('source_last_update') self.sunshine_forecast = kwargs.get('sunshine_forecast') self.quantity_mw = kwargs.get('quantity_mw') self.sell70cents = kwargs.get('sell70cents') self.sell110cents = kwargs.get('sell110cents') self.pnode_id = kwargs.get('pnode_id') self.humidity_type = kwargs.get('humidity_type') self.prev_close_ask = kwargs.get('prev_close_ask') self.level = kwargs.get('level') self.implied_volatility_by_expiration = kwargs.get('implied_volatility_by_expiration') self.asset_parameters_fixed_rate_day_count_fraction = kwargs.get( 'asset_parameters_fixed_rate_day_count_fraction') self.es_momentum_score = kwargs.get('es_momentum_score') self.leg2_index = kwargs.get('leg2_index') self.net_weight = kwargs.get('net_weight') self.portfolio_managers = kwargs.get('portfolio_managers') self.bos_in_ticks = kwargs.get('bos_in_ticks') self.asset_parameters_coupon_type = kwargs.get('asset_parameters_coupon_type') self.expected_residual_quantity = kwargs.get('expected_residual_quantity') self.roll_date = kwargs.get('roll_date') self.dynamic_hybrid_speed = kwargs.get('dynamic_hybrid_speed') self.cap_floor_vol = kwargs.get('cap_floor_vol') self.target_quantity = kwargs.get('target_quantity') self.submitter = kwargs.get('submitter') self.no = kwargs.get('no') self.notional = kwargs.get('notional') self.es_disclosure_percentage = kwargs.get('es_disclosure_percentage') self.close_executed_quantity_percentage = kwargs.get('close_executed_quantity_percentage') self.twap_realized_cash = kwargs.get('twap_realized_cash') self.is_open_auction = kwargs.get('is_open_auction') self.leg1_type = kwargs.get('leg1_type') self.wet_bulb_temp_hourly_forecast = kwargs.get('wet_bulb_temp_hourly_forecast') self.cleanup_price = kwargs.get('cleanup_price') self.total = kwargs.get('total') self.filled_notional_usd = kwargs.get('filled_notional_usd') self.asset_id = kwargs.get('asset_id') self.test_status = kwargs.get('test_status') self.mkt_type = kwargs.get('mkt_type') self.last_updated_time = kwargs.get('last_updated_time') self.yield30_day = kwargs.get('yield30_day') self.buy28bps = kwargs.get('buy28bps') self.proportion_of_risk = kwargs.get('proportion_of_risk') self.future_month_k23 = kwargs.get('future_month_k23') self.future_month_k22 = kwargs.get('future_month_k22') self.future_month_k21 = kwargs.get('future_month_k21') self.primary_entity_id = kwargs.get('primary_entity_id') self.cross = kwargs.get('cross') self.idea_status = kwargs.get('idea_status') self.contract_subtype = kwargs.get('contract_subtype') self.sri = kwargs.get('sri') self.fx_forecast = kwargs.get('fx_forecast') self.fixing_time_label = kwargs.get('fixing_time_label') self.is_etf = kwargs.get('is_etf') self.fill_id = kwargs.get('fill_id') self.excess_returns = kwargs.get('excess_returns') self.dollar_return = kwargs.get('dollar_return') self.order_in_limit = kwargs.get('order_in_limit') self.expiry_time = kwargs.get('expiry_time') self.return_on_equity = kwargs.get('return_on_equity') self.future_month_k26 = kwargs.get('future_month_k26') self.future_month_k25 = kwargs.get('future_month_k25') self.future_month_k24 = kwargs.get('future_month_k24') self.restriction_end_date = kwargs.get('restriction_end_date') self.queue_in_lots_description = kwargs.get('queue_in_lots_description') self.volume_limit = kwargs.get('volume_limit') self.objective = kwargs.get('objective') self.nav_price = kwargs.get('nav_price') self.leg1_underlying_asset = kwargs.get('leg1_underlying_asset') self.private_placement_type = kwargs.get('private_placement_type') self.hedge_notional = kwargs.get('hedge_notional') self.ask_low = kwargs.get('ask_low') self.intended_p_rate = kwargs.get('intended_p_rate') self.expiry = kwargs.get('expiry') self.avg_monthly_yield = kwargs.get('avg_monthly_yield') self.period_direction = kwargs.get('period_direction') self.prev_rpt_id = kwargs.get('prev_rpt_id') self.earnings_per_share = kwargs.get('earnings_per_share') self.strike_percentage = kwargs.get('strike_percentage') self.es_product_impact_percentile = kwargs.get('es_product_impact_percentile') self.vwap_realized_cash = kwargs.get('vwap_realized_cash') self.par_asset_swap_spread1m = kwargs.get('par_asset_swap_spread1m') self.prev_close_bid = kwargs.get('prev_close_bid') self.minimum_increment = kwargs.get('minimum_increment') self.tcm_cost_horizon16_day = kwargs.get('tcm_cost_horizon16_day') self.investment_mtd = kwargs.get('investment_mtd') self.settlement_date = kwargs.get('settlement_date') self.weighted_average_mid_normalized = kwargs.get('weighted_average_mid_normalized') self.sales_per_share = kwargs.get('sales_per_share') self.unadjusted_close = kwargs.get('unadjusted_close') self.loan_date = kwargs.get('loan_date') self.matched_maturity_swap_spread1m = kwargs.get('matched_maturity_swap_spread1m') self.collateral_percentage_actual = kwargs.get('collateral_percentage_actual') self.vwap_in_limit_unrealized_bps = kwargs.get('vwap_in_limit_unrealized_bps') self.metric_value = kwargs.get('metric_value') self.auto_exec_state = kwargs.get('auto_exec_state') self.total_recovered = kwargs.get('total_recovered') self.relative_return_ytd = kwargs.get('relative_return_ytd') self.tick_server = kwargs.get('tick_server') self.cumulative_volume_in_percentage = kwargs.get('cumulative_volume_in_percentage') self.real_time_restriction_status = kwargs.get('real_time_restriction_status') self.trade_type = kwargs.get('trade_type') self.settlement_type = kwargs.get('settlement_type') self.net_change = kwargs.get('net_change') self.number_of_underliers = kwargs.get('number_of_underliers') self.swap_type = kwargs.get('swap_type') self.forecast_type = kwargs.get('forecast_type') self.leg1_notional = kwargs.get('leg1_notional') self.sell_settle_date = kwargs.get('sell_settle_date') self.new_ideas_ytd = kwargs.get('new_ideas_ytd') self.management_fee = kwargs.get('management_fee') self.par_asset_swap_spread3m = kwargs.get('par_asset_swap_spread3m') self.sell36bps = kwargs.get('sell36bps') self.matched_maturity_swap_spread3m = kwargs.get('matched_maturity_swap_spread3m') self.source_id = kwargs.get('source_id') self.country = kwargs.get('country') self.vwap = kwargs.get('vwap') self.touch_spread_score = kwargs.get('touch_spread_score') self.rating_second_highest = kwargs.get('rating_second_highest') self.sell24bps = kwargs.get('sell24bps') self.frequency = kwargs.get('frequency') self.activity_id = kwargs.get('activity_id') self.estimated_impact = kwargs.get('estimated_impact') self.sell35cents = kwargs.get('sell35cents') self.loan_spread_bucket = kwargs.get('loan_spread_bucket') self.coronavirus_global_activity_tracker = kwargs.get('coronavirus_global_activity_tracker') self.underlyers = kwargs.get('underlyers') self.asset_parameters_pricing_location = kwargs.get('asset_parameters_pricing_location') self.event_description = kwargs.get('event_description') self.iceberg_max_size = kwargs.get('iceberg_max_size') self.asset_parameters_coupon = kwargs.get('asset_parameters_coupon') self.details = kwargs.get('details') self.sector = kwargs.get('sector') self.avg_bed_util_rate = kwargs.get('avg_bed_util_rate') self.buy20bps = kwargs.get('buy20bps') self.epidemic = kwargs.get('epidemic') self.mctr = kwargs.get('mctr') self.exchange_time = kwargs.get('exchange_time') self.historical_close = kwargs.get('historical_close') self.fips_code = kwargs.get('fips_code') self.buy32bps = kwargs.get('buy32bps') self.idea_id = kwargs.get('idea_id') self.comment_status = kwargs.get('comment_status') self.marginal_cost = kwargs.get('marginal_cost') self.client_weight = kwargs.get('client_weight') self.leg1_delivery_point = kwargs.get('leg1_delivery_point') self.sell5cents = kwargs.get('sell5cents') self.liq_wkly = kwargs.get('liq_wkly') self.unrealized_twap_performance_bps = kwargs.get('unrealized_twap_performance_bps') self.region = kwargs.get('region') self.temperature_hour = kwargs.get('temperature_hour') self.upper_bound = kwargs.get('upper_bound') self.sell55cents = kwargs.get('sell55cents') self.num_pedi_icu_beds = kwargs.get('num_pedi_icu_beds') self.bid_yield = kwargs.get('bid_yield') self.expected_residual = kwargs.get('expected_residual') self.option_premium = kwargs.get('option_premium') self.owner_name = kwargs.get('owner_name') self.par_asset_swap_spread6m = kwargs.get('par_asset_swap_spread6m') self.z_score = kwargs.get('z_score') self.sell12bps = kwargs.get('sell12bps') self.event_start_time = kwargs.get('event_start_time') self.matched_maturity_swap_spread6m = kwargs.get('matched_maturity_swap_spread6m') self.turnover = kwargs.get('turnover') self.price_spot_target_unit = kwargs.get('price_spot_target_unit') self.coverage = kwargs.get('coverage') self.g_percentile = kwargs.get('g_percentile') self.cloud_cover_hourly_forecast = kwargs.get('cloud_cover_hourly_forecast') self.lending_fund_nav = kwargs.get('lending_fund_nav') self.source_original_category = kwargs.get('source_original_category') self.percent_close_execution_quantity = kwargs.get('percent_close_execution_quantity') self.latest_execution_time = kwargs.get('latest_execution_time') self.arrival_mid_realized_bps = kwargs.get('arrival_mid_realized_bps') self.location = kwargs.get('location') self.scenario_id = kwargs.get('scenario_id') self.termination_tenor = kwargs.get('termination_tenor') self.queue_clock_time = kwargs.get('queue_clock_time') self.discretion_lower_bound = kwargs.get('discretion_lower_bound') self.tcm_cost_participation_rate50_pct = kwargs.get('tcm_cost_participation_rate50_pct') self.rating_linear = kwargs.get('rating_linear') self.previous_close_unrealized_bps = kwargs.get('previous_close_unrealized_bps') self.sub_asset_class_for_other_commodity = kwargs.get('sub_asset_class_for_other_commodity') self.forward_price = kwargs.get('forward_price') self.__type = kwargs.get('type_') self.strike_ref = kwargs.get('strike_ref') self.cumulative_pnl = kwargs.get('cumulative_pnl') self.short_tenor = kwargs.get('short_tenor') self.sell28bps = kwargs.get('sell28bps') self.fund_class = kwargs.get('fund_class') self.unadjusted_volume = kwargs.get('unadjusted_volume') self.buy36bps = kwargs.get('buy36bps') self.position_idx = kwargs.get('position_idx') self.wind_chill_hourly_forecast = kwargs.get('wind_chill_hourly_forecast') self.sec_name = kwargs.get('sec_name') self.implied_volatility_by_relative_strike = kwargs.get('implied_volatility_by_relative_strike') self.percent_adv = kwargs.get('percent_adv') self.leg1_total_notional = kwargs.get('leg1_total_notional') self.contract = kwargs.get('contract') self.payment_frequency1 = kwargs.get('payment_frequency1') self.payment_frequency2 = kwargs.get('payment_frequency2') self.bespoke = kwargs.get('bespoke') self.repo_tenor = kwargs.get('repo_tenor') self.sell15cents = kwargs.get('sell15cents') self.investment_qtd = kwargs.get('investment_qtd') self.heat_index_forecast = kwargs.get('heat_index_forecast') self.rating_standard_and_poors = kwargs.get('rating_standard_and_poors') self.quality_stars = kwargs.get('quality_stars') self.leg2_floating_index = kwargs.get('leg2_floating_index') self.source_ticker = kwargs.get('source_ticker') self.primary_vwap_unrealized_bps = kwargs.get('primary_vwap_unrealized_bps') self.gsid = kwargs.get('gsid') self.lending_fund = kwargs.get('lending_fund') self.sensitivity = kwargs.get('sensitivity') self.day_count = kwargs.get('day_count') self.sell16bps = kwargs.get('sell16bps') self.relative_break_even_inflation_change = kwargs.get('relative_break_even_inflation_change') self.sell25cents = kwargs.get('sell25cents') self.var_swap = kwargs.get('var_swap') self.buy5point5bps = kwargs.get('buy5point5bps') self.block_large_notional = kwargs.get('block_large_notional') self.sell2point5bps = kwargs.get('sell2point5bps') self.capacity = kwargs.get('capacity') self.sectors_raw = kwargs.get('sectors_raw') self.primary_vwap_in_limit = kwargs.get('primary_vwap_in_limit') self.shareclass_price = kwargs.get('shareclass_price') self.trade_size = kwargs.get('trade_size') self.price_spot_entry_value = kwargs.get('price_spot_entry_value') self.buy8point5bps = kwargs.get('buy8point5bps') self.symbol_dimensions = kwargs.get('symbol_dimensions') self.buy24bps = kwargs.get('buy24bps') self.observation = kwargs.get('observation') self.option_type_sdr = kwargs.get('option_type_sdr') self.scenario_group_id = kwargs.get('scenario_group_id') self.average_implied_variance = kwargs.get('average_implied_variance') self.avg_trade_rate_description = kwargs.get('avg_trade_rate_description') self.fraction = kwargs.get('fraction') self.asset_count_short = kwargs.get('asset_count_short') self.collateral_percentage_required = kwargs.get('collateral_percentage_required') self.sell5point5bps = kwargs.get('sell5point5bps') self.date = kwargs.get('date') self.zip_code = kwargs.get('zip_code') self.total_std_return_since_inception = kwargs.get('total_std_return_since_inception') self.source_category = kwargs.get('source_category') self.volume_unadjusted = kwargs.get('volume_unadjusted') self.passive_ratio = kwargs.get('passive_ratio') self.price_to_earnings = kwargs.get('price_to_earnings') self.order_depth = kwargs.get('order_depth') self.ann_yield3_month = kwargs.get('ann_yield3_month') self.net_flow_std = kwargs.get('net_flow_std') self.encoded_stats = kwargs.get('encoded_stats') self.buy5bps = kwargs.get('buy5bps') self.run_time = kwargs.get('run_time') self.ask_size = kwargs.get('ask_size') self.absolute_return_mtd = kwargs.get('absolute_return_mtd') self.std30_days_unsubsidized_yield = kwargs.get('std30_days_unsubsidized_yield') self.resource = kwargs.get('resource') self.average_realized_volatility = kwargs.get('average_realized_volatility') self.trace_adv_buy = kwargs.get('trace_adv_buy') self.new_confirmed = kwargs.get('new_confirmed') self.sell8bps = kwargs.get('sell8bps') self.bid_price = kwargs.get('bid_price') self.sell8point5bps = kwargs.get('sell8point5bps') self.target_price_unrealized_bps = kwargs.get('target_price_unrealized_bps') self.es_numeric_percentile = kwargs.get('es_numeric_percentile') self.leg2_underlying_asset = kwargs.get('leg2_underlying_asset') self.csa_terms = kwargs.get('csa_terms') self.relative_payoff_mtd = kwargs.get('relative_payoff_mtd') self.daily_net_shareholder_flows = kwargs.get('daily_net_shareholder_flows') self.buy2point5bps = kwargs.get('buy2point5bps') self.cai = kwargs.get('cai') self.executed_notional_usd = kwargs.get('executed_notional_usd') self.system_time = kwargs.get('system_time') self.total_home_isolation = kwargs.get('total_home_isolation') self.station_name = kwargs.get('station_name') self.pass_pct = kwargs.get('pass_pct') self.opening_report = kwargs.get('opening_report') self.midcurve_atm_fwd_rate = kwargs.get('midcurve_atm_fwd_rate') self.precipitation_forecast = kwargs.get('precipitation_forecast') self.equity_risk_premium_index = kwargs.get('equity_risk_premium_index') self.fatalities_underlying_conditions_unknown = kwargs.get('fatalities_underlying_conditions_unknown') self.buy12bps = kwargs.get('buy12bps') self.clearing_house = kwargs.get('clearing_house') self.day_close_unrealized_bps = kwargs.get('day_close_unrealized_bps') self.sts_rates_maturity = kwargs.get('sts_rates_maturity') self.liq_dly = kwargs.get('liq_dly') self.contributor_role = kwargs.get('contributor_role') self.total_fatalities = kwargs.get('total_fatalities') @property def investment_rate(self) -> float: """The rate of return on an investment. In the context of securities lending, it is the rate being earned on the reinvested collateral received from the borrower.""" return self.__investment_rate @investment_rate.setter def investment_rate(self, value: float): self._property_changed('investment_rate') self.__investment_rate = value @property def starting_emma_legal_entity_id(self) -> str: """Starting EMMA Legal Entity ID (LE) for which the SCR client agreement was signed.""" return self.__starting_emma_legal_entity_id @starting_emma_legal_entity_id.setter def starting_emma_legal_entity_id(self, value: str): self._property_changed('starting_emma_legal_entity_id') self.__starting_emma_legal_entity_id = value @property def mdapi_class(self) -> str: """MDAPI Asset Class.""" return self.__mdapi_class @mdapi_class.setter def mdapi_class(self, value: str): self._property_changed('mdapi_class') self.__mdapi_class = value @property def total_notional_usd(self) -> float: """Total notional on order in dollars.""" return self.__total_notional_usd @total_notional_usd.setter def total_notional_usd(self, value: float): self._property_changed('total_notional_usd') self.__total_notional_usd = value @property def bid_unadjusted(self) -> float: """Unadjusted bid level of an asset based on official exchange fixing or calculation agent marked level.""" return self.__bid_unadjusted @bid_unadjusted.setter def bid_unadjusted(self, value: float): self._property_changed('bid_unadjusted') self.__bid_unadjusted = value @property def aggressive_fills_percentage(self) -> float: """Percent of total order filled at aggressive touch.""" return self.__aggressive_fills_percentage @aggressive_fills_percentage.setter def aggressive_fills_percentage(self, value: float): self._property_changed('aggressive_fills_percentage') self.__aggressive_fills_percentage = value @property def vehicle_type(self) -> str: """Type of investment vehicle. Only viewable after having been granted additional access to asset information.""" return self.__vehicle_type @vehicle_type.setter def vehicle_type(self, value: str): self._property_changed('vehicle_type') self.__vehicle_type = value @property def total_fatalities_by_state(self) -> float: """Total number of fatalities by state.""" return self.__total_fatalities_by_state @total_fatalities_by_state.setter def total_fatalities_by_state(self, value: float): self._property_changed('total_fatalities_by_state') self.__total_fatalities_by_state = value @property def new_active(self) -> float: """Change in number of active cases from previous day.""" return self.__new_active @new_active.setter def new_active(self, value: float): self._property_changed('new_active') self.__new_active = value @property def daily_risk(self) -> float: """Daily Risk Value.""" return self.__daily_risk @daily_risk.setter def daily_risk(self, value: float): self._property_changed('daily_risk') self.__daily_risk = value @property def energy(self) -> float: """Energy price component.""" return self.__energy @energy.setter def energy(self, value: float): self._property_changed('energy') self.__energy = value @property def sunshine_daily_forecast(self) -> float: """The forecast value for sunshine in percent.""" return self.__sunshine_daily_forecast @sunshine_daily_forecast.setter def sunshine_daily_forecast(self, value: float): self._property_changed('sunshine_daily_forecast') self.__sunshine_daily_forecast = value @property def sentiment_score(self) -> float: """A value representing a sentiment indicator.""" return self.__sentiment_score @sentiment_score.setter def sentiment_score(self, value: float): self._property_changed('sentiment_score') self.__sentiment_score = value @property def correlation(self) -> float: """Market implied correlation between two tenors.""" return self.__correlation @correlation.setter def correlation(self, value: float): self._property_changed('correlation') self.__correlation = value @property def exposure(self) -> float: """Exposure of a given asset or portfolio in the denominated currency of the asset or portfolio.""" return self.__exposure @exposure.setter def exposure(self, value: float): self._property_changed('exposure') self.__exposure = value @property def size(self) -> float: """Size of market execution.""" return self.__size @size.setter def size(self, value: float): self._property_changed('size') self.__size = value @property def market_data_asset(self) -> str: """The market data asset (e.g. USD, USD/EUR).""" return self.__market_data_asset @market_data_asset.setter def market_data_asset(self, value: str): self._property_changed('market_data_asset') self.__market_data_asset = value @property def buy75cents(self) -> float: """The amount GS would buy for 75 cents charge.""" return self.__buy75cents @buy75cents.setter def buy75cents(self, value: float): self._property_changed('buy75cents') self.__buy75cents = value @property def unadjusted_high(self) -> float: """Unadjusted high level of an asset based on official exchange fixing or calculation agent marked level.""" return self.__unadjusted_high @unadjusted_high.setter def unadjusted_high(self, value: float): self._property_changed('unadjusted_high') self.__unadjusted_high = value @property def source_importance(self) -> float: """Source importance.""" return self.__source_importance @source_importance.setter def source_importance(self, value: float): self._property_changed('source_importance') self.__source_importance = value @property def closing_yield(self) -> float: """Closing yield.""" return self.__closing_yield @closing_yield.setter def closing_yield(self, value: float): self._property_changed('closing_yield') self.__closing_yield = value @property def wind(self) -> float: """Value of the wind attribute selected.""" return self.__wind @wind.setter def wind(self, value: float): self._property_changed('wind') self.__wind = value @property def sc16(self) -> bool: """Document Ongoing SCR Compliance.""" return self.__sc16 @sc16.setter def sc16(self, value: bool): self._property_changed('sc16') self.__sc16 = value @property def sc15(self) -> bool: """Restrict Access To Specified Named Personnel.""" return self.__sc15 @sc15.setter def sc15(self, value: bool): self._property_changed('sc15') self.__sc15 = value @property def sc12(self) -> bool: """Exclude From Indications Of Interest.""" return self.__sc12 @sc12.setter def sc12(self, value: bool): self._property_changed('sc12') self.__sc12 = value @property def sc11(self) -> bool: """Replace Client True Name With Alias Name.""" return self.__sc11 @sc11.setter def sc11(self, value: bool): self._property_changed('sc11') self.__sc11 = value @property def primary_vwap_in_limit_unrealized_bps(self) -> float: """Unrealised performance vs Primary VWAP In Limit, in bps.""" return self.__primary_vwap_in_limit_unrealized_bps @primary_vwap_in_limit_unrealized_bps.setter def primary_vwap_in_limit_unrealized_bps(self, value: float): self._property_changed('primary_vwap_in_limit_unrealized_bps') self.__primary_vwap_in_limit_unrealized_bps = value @property def display_name(self) -> str: """Display Name.""" return self.__display_name @display_name.setter def display_name(self, value: str): self._property_changed('display_name') self.__display_name = value @property def minutes_to_trade100_pct(self) -> float: """Minutes to trade 100 percent.""" return self.__minutes_to_trade100_pct @minutes_to_trade100_pct.setter def minutes_to_trade100_pct(self, value: float): self._property_changed('minutes_to_trade100_pct') self.__minutes_to_trade100_pct = value @property def sc14(self) -> bool: """Notify Strat Supervisors Of Personnel Assignment Limitation.""" return self.__sc14 @sc14.setter def sc14(self, value: bool): self._property_changed('sc14') self.__sc14 = value @property def cumulative_volume_in_shares(self) -> float: """Forecast of the cumulative volume of shares from start of day up to the end of the bucket interval.""" return self.__cumulative_volume_in_shares @cumulative_volume_in_shares.setter def cumulative_volume_in_shares(self, value: float): self._property_changed('cumulative_volume_in_shares') self.__cumulative_volume_in_shares = value @property def sc13(self) -> bool: """Restrict Access In Client Relationship Management Platforms To Sales Personnel Covering Client.""" return self.__sc13 @sc13.setter def sc13(self, value: bool): self._property_changed('sc13') self.__sc13 = value @property def new_fatalities(self) -> float: """New fatal cases.""" return self.__new_fatalities @new_fatalities.setter def new_fatalities(self, value: float): self._property_changed('new_fatalities') self.__new_fatalities = value @property def buy50bps(self) -> float: """The amount GS would buy for 50 bps charge.""" return self.__buy50bps @buy50bps.setter def buy50bps(self, value: float): self._property_changed('buy50bps') self.__buy50bps = value @property def num_staffed_beds(self) -> float: """Number of adult bed, pediatric bed, birthing room, or newborn ICU bed (excluding newborn bassinets) maintained in a patient care area for lodging patients in acute, long term, or domiciliary areas of the hospital.""" return self.__num_staffed_beds @num_staffed_beds.setter def num_staffed_beds(self, value: float): self._property_changed('num_staffed_beds') self.__num_staffed_beds = value @property def upfront_payment(self) -> float: """Upfront payment fee.""" return self.__upfront_payment @upfront_payment.setter def upfront_payment(self, value: float): self._property_changed('upfront_payment') self.__upfront_payment = value @property def arrival_mid_realized_cash(self) -> float: """Consolidated realised performance vs Arrival Mid, in USD.""" return self.__arrival_mid_realized_cash @arrival_mid_realized_cash.setter def arrival_mid_realized_cash(self, value: float): self._property_changed('arrival_mid_realized_cash') self.__arrival_mid_realized_cash = value @property def sc10(self) -> bool: """Exclude From Calibration And Research.""" return self.__sc10 @sc10.setter def sc10(self, value: bool): self._property_changed('sc10') self.__sc10 = value @property def sc05(self) -> bool: """Restrict Access To Trade Execution Mandate Personnel.""" return self.__sc05 @sc05.setter def sc05(self, value: bool): self._property_changed('sc05') self.__sc05 = value @property def a(self) -> float: """Stock specific coefficient.""" return self.__a @a.setter def a(self, value: float): self._property_changed('a') self.__a = value @property def sc04(self) -> bool: """Restrict Access To Sales Personnel Covering Both Client And Executed Product.""" return self.__sc04 @sc04.setter def sc04(self, value: bool): self._property_changed('sc04') self.__sc04 = value @property def b(self) -> float: """Stock specific coefficient.""" return self.__b @b.setter def b(self, value: float): self._property_changed('b') self.__b = value @property def sc07(self) -> bool: """Exclude From Market Share Survey Submissions.""" return self.__sc07 @sc07.setter def sc07(self, value: bool): self._property_changed('sc07') self.__sc07 = value @property def c(self) -> float: """Stock specific coefficient.""" return self.__c @c.setter def c(self, value: float): self._property_changed('c') self.__c = value @property def yield_to_maturity(self) -> float: """The return an investor realizes on a bond sold at the mid price at maturity.""" return self.__yield_to_maturity @yield_to_maturity.setter def yield_to_maturity(self, value: float): self._property_changed('yield_to_maturity') self.__yield_to_maturity = value @property def sc06(self) -> bool: """Restrict Access To Agency Personnel.""" return self.__sc06 @sc06.setter def sc06(self, value: bool): self._property_changed('sc06') self.__sc06 = value @property def address(self) -> str: """Street address.""" return self.__address @address.setter def address(self, value: str): self._property_changed('address') self.__address = value @property def sc01(self) -> bool: """Define Information Barriers Policy.""" return self.__sc01 @sc01.setter def sc01(self, value: bool): self._property_changed('sc01') self.__sc01 = value @property def leg2_payment_frequency(self) -> str: """Same as Leg 1 Payment Frequency.""" return self.__leg2_payment_frequency @leg2_payment_frequency.setter def leg2_payment_frequency(self, value: str): self._property_changed('leg2_payment_frequency') self.__leg2_payment_frequency = value @property def sc03(self) -> bool: """Educate Sales And Traders About SCR Agreements.""" return self.__sc03 @sc03.setter def sc03(self, value: bool): self._property_changed('sc03') self.__sc03 = value @property def sc02(self) -> bool: """Train On Information Barriers Policy.""" return self.__sc02 @sc02.setter def sc02(self, value: bool): self._property_changed('sc02') self.__sc02 = value @property def geography_name(self) -> str: """Name of the country or region for which metric is calculated.""" return self.__geography_name @geography_name.setter def geography_name(self, value: str): self._property_changed('geography_name') self.__geography_name = value @property def borrower(self) -> str: """Name of the borrowing entity on a securities lending agreement.""" return self.__borrower @borrower.setter def borrower(self, value: str): self._property_changed('borrower') self.__borrower = value @property def settle_price(self) -> float: """Settling level of an asset based on official exchange fixing or calculation agent marked level.""" return self.__settle_price @settle_price.setter def settle_price(self, value: float): self._property_changed('settle_price') self.__settle_price = value @property def performance_contribution(self) -> float: """The contribution of an underlying asset to the overall performance.""" return self.__performance_contribution @performance_contribution.setter def performance_contribution(self, value: float): self._property_changed('performance_contribution') self.__performance_contribution = value @property def sc09(self) -> bool: """Exclude From Market Color Commentary By Sales And Trading.""" return self.__sc09 @sc09.setter def sc09(self, value: bool): self._property_changed('sc09') self.__sc09 = value @property def mkt_class(self) -> str: """The MDAPI Class (e.g. Swap, Cash).""" return self.__mkt_class @mkt_class.setter def mkt_class(self, value: str): self._property_changed('mkt_class') self.__mkt_class = value @property def sc08(self) -> bool: """Exclude From Systematic Market Color Reporting And Advertised Volumes.""" return self.__sc08 @sc08.setter def sc08(self, value: bool): self._property_changed('sc08') self.__sc08 = value @property def collateralization(self) -> str: """If an SB swap is not cleared, an indication of whether a swap is Uncollateralized (UC), Partially Collateralized (PC), One-Way Collateralized (OC), or Fully Collateralized (FC).""" return self.__collateralization @collateralization.setter def collateralization(self, value: str): self._property_changed('collateralization') self.__collateralization = value @property def future_month_u26(self) -> float: """Commods future month code.""" return self.__future_month_u26 @future_month_u26.setter def future_month_u26(self, value: float): self._property_changed('future_month_u26') self.__future_month_u26 = value @property def future_month_u25(self) -> float: """Commods future month code.""" return self.__future_month_u25 @future_month_u25.setter def future_month_u25(self, value: float): self._property_changed('future_month_u25') self.__future_month_u25 = value @property def future_month_u24(self) -> float: """Commods future month code.""" return self.__future_month_u24 @future_month_u24.setter def future_month_u24(self, value: float): self._property_changed('future_month_u24') self.__future_month_u24 = value @property def future_month_u23(self) -> float: """Commods future month code.""" return self.__future_month_u23 @future_month_u23.setter def future_month_u23(self, value: float): self._property_changed('future_month_u23') self.__future_month_u23 = value @property def future_month_u22(self) -> float: """Commods future month code.""" return self.__future_month_u22 @future_month_u22.setter def future_month_u22(self, value: float): self._property_changed('future_month_u22') self.__future_month_u22 = value @property def statement_id(self) -> str: """Statement UUID.""" return self.__statement_id @statement_id.setter def statement_id(self, value: str): self._property_changed('statement_id') self.__statement_id = value @property def future_month_u21(self) -> float: """Commods future month code.""" return self.__future_month_u21 @future_month_u21.setter def future_month_u21(self, value: float): self._property_changed('future_month_u21') self.__future_month_u21 = value @property def modified_duration(self) -> float: """Measure of a bond's price sensitivity to changes in interest rates.""" return self.__modified_duration @modified_duration.setter def modified_duration(self, value: float): self._property_changed('modified_duration') self.__modified_duration = value @property def short_rates_contribution(self) -> float: """Contribution of short rate component to FCI.""" return self.__short_rates_contribution @short_rates_contribution.setter def short_rates_contribution(self, value: float): self._property_changed('short_rates_contribution') self.__short_rates_contribution = value @property def implied_normal_volatility(self) -> float: """Market implied volatility measured using a normal model in bps/day.""" return self.__implied_normal_volatility @implied_normal_volatility.setter def implied_normal_volatility(self, value: float): self._property_changed('implied_normal_volatility') self.__implied_normal_volatility = value @property def solar_generation(self) -> float: """Solar generation value provided by source.""" return self.__solar_generation @solar_generation.setter def solar_generation(self, value: float): self._property_changed('solar_generation') self.__solar_generation = value @property def mtm_price(self) -> float: """Amount of profit or loss realized over statement period.""" return self.__mtm_price @mtm_price.setter def mtm_price(self, value: float): self._property_changed('mtm_price') self.__mtm_price = value @property def swap_spread_change(self) -> float: """Swap spread change.""" return self.__swap_spread_change @swap_spread_change.setter def swap_spread_change(self, value: float): self._property_changed('swap_spread_change') self.__swap_spread_change = value @property def realized_arrival_performance_usd(self) -> float: """Average execution price vs Consolidated Arrival (in limit) since order inception ??? realized in $.""" return self.__realized_arrival_performance_usd @realized_arrival_performance_usd.setter def realized_arrival_performance_usd(self, value: float): self._property_changed('realized_arrival_performance_usd') self.__realized_arrival_performance_usd = value @property def portfolio_assets(self) -> float: """Total amount of assets under management across all share classes.""" return self.__portfolio_assets @portfolio_assets.setter def portfolio_assets(self, value: float): self._property_changed('portfolio_assets') self.__portfolio_assets = value @property def pricingdate(self) -> str: """Pricing Date.""" return self.__pricingdate @pricingdate.setter def pricingdate(self, value: str): self._property_changed('pricingdate') self.__pricingdate = value @property def tcm_cost_horizon3_hour(self) -> float: """TCM cost with a 3 hour time horizon.""" return self.__tcm_cost_horizon3_hour @tcm_cost_horizon3_hour.setter def tcm_cost_horizon3_hour(self, value: float): self._property_changed('tcm_cost_horizon3_hour') self.__tcm_cost_horizon3_hour = value @property def exchange_rate(self) -> float: """Amount that an exchange charges in fees, in mils. Negative numbers denote rebates.""" return self.__exchange_rate @exchange_rate.setter def exchange_rate(self, value: float): self._property_changed('exchange_rate') self.__exchange_rate = value @property def potential_bed_cap_inc(self) -> float: """The number of beds by which a hospital can increase bed capacity, derived from the difference between number of allowed licensed beds and number of currently staffed beds.""" return self.__potential_bed_cap_inc @potential_bed_cap_inc.setter def potential_bed_cap_inc(self, value: float): self._property_changed('potential_bed_cap_inc') self.__potential_bed_cap_inc = value @property def number_covered(self) -> float: """Number of underlyers covered by risk model.""" return self.__number_covered @number_covered.setter def number_covered(self, value: float): self._property_changed('number_covered') self.__number_covered = value @property def number_of_positions(self) -> float: """Number of positions.""" return self.__number_of_positions @number_of_positions.setter def number_of_positions(self, value: float): self._property_changed('number_of_positions') self.__number_of_positions = value @property def open_unadjusted(self) -> float: """Unadjusted open level of an asset based on official exchange fixing or calculation agent marked level.""" return self.__open_unadjusted @open_unadjusted.setter def open_unadjusted(self, value: float): self._property_changed('open_unadjusted') self.__open_unadjusted = value @property def strike_time(self) -> datetime.datetime: """Time at which order began executing.""" return self.__strike_time @strike_time.setter def strike_time(self, value: datetime.datetime): self._property_changed('strike_time') self.__strike_time = value @property def ask_price(self) -> float: """Latest Ask Price (price offering to sell).""" return self.__ask_price @ask_price.setter def ask_price(self, value: float): self._property_changed('ask_price') self.__ask_price = value @property def event_id(self) -> str: """Goldman Sachs internal event identifier.""" return self.__event_id @event_id.setter def event_id(self, value: str): self._property_changed('event_id') self.__event_id = value @property def sectors(self) -> Tuple[str, ...]: """Sector classifications of an asset.""" return self.__sectors @sectors.setter def sectors(self, value: Tuple[str, ...]): self._property_changed('sectors') self.__sectors = value @property def additional_price_notation_type(self) -> str: """Basis points, Price, Yield, Spread, Coupon, etc., depending on the type of SB swap, which is calculated at affirmation.""" return self.__additional_price_notation_type @additional_price_notation_type.setter def additional_price_notation_type(self, value: str): self._property_changed('additional_price_notation_type') self.__additional_price_notation_type = value @property def gross_investment_qtd(self) -> float: """Total gross investment Quarter to date.""" return self.__gross_investment_qtd @gross_investment_qtd.setter def gross_investment_qtd(self, value: float): self._property_changed('gross_investment_qtd') self.__gross_investment_qtd = value @property def annualized_risk(self) -> float: """Annualized risk.""" return self.__annualized_risk @annualized_risk.setter def annualized_risk(self, value: float): self._property_changed('annualized_risk') self.__annualized_risk = value @property def estimated_holding_time_short(self) -> float: """The estimated holding time for a short position.""" return self.__estimated_holding_time_short @estimated_holding_time_short.setter def estimated_holding_time_short(self, value: float): self._property_changed('estimated_holding_time_short') self.__estimated_holding_time_short = value @property def midcurve_premium(self) -> float: """Midcurve premium.""" return self.__midcurve_premium @midcurve_premium.setter def midcurve_premium(self, value: float): self._property_changed('midcurve_premium') self.__midcurve_premium = value @property def volume_composite(self) -> float: """Accumulated number of shares, lots or contracts traded according to the market convention at all exchanges.""" return self.__volume_composite @volume_composite.setter def volume_composite(self, value: float): self._property_changed('volume_composite') self.__volume_composite = value @property def sharpe_qtd(self) -> float: """Sharpe ratio Quarter to date.""" return self.__sharpe_qtd @sharpe_qtd.setter def sharpe_qtd(self, value: float): self._property_changed('sharpe_qtd') self.__sharpe_qtd = value @property def estimated_holding_time_long(self) -> float: """The estimated holding time for a long position.""" return self.__estimated_holding_time_long @estimated_holding_time_long.setter def estimated_holding_time_long(self, value: float): self._property_changed('estimated_holding_time_long') self.__estimated_holding_time_long = value @property def external(self) -> bool: """Whether entity was created by an external user.""" return self.__external @external.setter def external(self, value: bool): self._property_changed('external') self.__external = value @property def tracker_name(self) -> str: """Name of the tracker.""" return self.__tracker_name @tracker_name.setter def tracker_name(self, value: str): self._property_changed('tracker_name') self.__tracker_name = value @property def sell50cents(self) -> float: """The amount GS would sell for 50 cents charge.""" return self.__sell50cents @sell50cents.setter def sell50cents(self, value: float): self._property_changed('sell50cents') self.__sell50cents = value @property def trade_price(self) -> float: """Last trade price or value.""" return self.__trade_price @trade_price.setter def trade_price(self, value: float): self._property_changed('trade_price') self.__trade_price = value @property def cleared(self) -> str: """An indication of whether or not an SB swap transaction is going to be cleared by a derivatives clearing organization.""" return self.__cleared @cleared.setter def cleared(self, value: str): self._property_changed('cleared') self.__cleared = value @property def prime_id_numeric(self) -> float: """Prime ID as a number.""" return self.__prime_id_numeric @prime_id_numeric.setter def prime_id_numeric(self, value: float): self._property_changed('prime_id_numeric') self.__prime_id_numeric = value @property def buy8bps(self) -> float: """The amount GS would buy for 8 bps charge.""" return self.__buy8bps @buy8bps.setter def buy8bps(self, value: float): self._property_changed('buy8bps') self.__buy8bps = value @property def total_notional_local(self) -> float: """Total notional on order in local currency.""" return self.__total_notional_local @total_notional_local.setter def total_notional_local(self, value: float): self._property_changed('total_notional_local') self.__total_notional_local = value @property def cid(self) -> str: """Goldman Sachs internal company identifier.""" return self.__cid @cid.setter def cid(self, value: str): self._property_changed('cid') self.__cid = value @property def total_confirmed_senior_home(self) -> float: """Confirmed cases in Senior homes and care facilities.""" return self.__total_confirmed_senior_home @total_confirmed_senior_home.setter def total_confirmed_senior_home(self, value: float): self._property_changed('total_confirmed_senior_home') self.__total_confirmed_senior_home = value @property def ctd_fwd_price(self) -> float: """Cheapest to deliver bond fwd price.""" return self.__ctd_fwd_price @ctd_fwd_price.setter def ctd_fwd_price(self, value: float): self._property_changed('ctd_fwd_price') self.__ctd_fwd_price = value @property def sink_factor(self) -> float: """The level to which a sinkable bond has currently sunk.""" return self.__sink_factor @sink_factor.setter def sink_factor(self, value: float): self._property_changed('sink_factor') self.__sink_factor = value @property def temperature_forecast(self) -> float: """The forecast temperature of diff types of given units.""" return self.__temperature_forecast @temperature_forecast.setter def temperature_forecast(self, value: float): self._property_changed('temperature_forecast') self.__temperature_forecast = value @property def bid_high(self) -> float: """The highest bid (price willing to buy).""" return self.__bid_high @bid_high.setter def bid_high(self, value: float): self._property_changed('bid_high') self.__bid_high = value @property def pnl_qtd(self) -> float: """Total PnL Quarter to date.""" return self.__pnl_qtd @pnl_qtd.setter def pnl_qtd(self, value: float): self._property_changed('pnl_qtd') self.__pnl_qtd = value @property def buy50cents(self) -> float: """The amount GS would buy for 50 cents charge.""" return self.__buy50cents @buy50cents.setter def buy50cents(self, value: float): self._property_changed('buy50cents') self.__buy50cents = value @property def sell4bps(self) -> float: """The amount GS would sell for 4 bps charge.""" return self.__sell4bps @sell4bps.setter def sell4bps(self, value: float): self._property_changed('sell4bps') self.__sell4bps = value @property def receiver_day_count_fraction(self) -> str: """Day Count Fraction""" return self.__receiver_day_count_fraction @receiver_day_count_fraction.setter def receiver_day_count_fraction(self, value: str): self._property_changed('receiver_day_count_fraction') self.__receiver_day_count_fraction = value @property def auction_close_percentage(self) -> float: """Percentage of total order being reserved for the Closing Auction.""" return self.__auction_close_percentage @auction_close_percentage.setter def auction_close_percentage(self, value: float): self._property_changed('auction_close_percentage') self.__auction_close_percentage = value @property def target_price(self) -> float: """Target Price.""" return self.__target_price @target_price.setter def target_price(self, value: float): self._property_changed('target_price') self.__target_price = value @property def bos_in_bps_description(self) -> str: """Description of the Stock's Bid-Offer Spread in Basis points on the particular date.""" return self.__bos_in_bps_description @bos_in_bps_description.setter def bos_in_bps_description(self, value: str): self._property_changed('bos_in_bps_description') self.__bos_in_bps_description = value @property def low_price(self) -> float: """Low level of an asset based on official exchange fixing or calculation agent marked level.""" return self.__low_price @low_price.setter def low_price(self, value: float): self._property_changed('low_price') self.__low_price = value @property def adv22_day_pct(self) -> float: """Median number of shares or units of a given asset traded over a 21 day period.""" return self.__adv22_day_pct @adv22_day_pct.setter def adv22_day_pct(self, value: float): self._property_changed('adv22_day_pct') self.__adv22_day_pct = value @property def matched_maturity_swap_spread12m(self) -> float: """Matched maturity swap spread vs 12m tenor.""" return self.__matched_maturity_swap_spread12m @matched_maturity_swap_spread12m.setter def matched_maturity_swap_spread12m(self, value: float): self._property_changed('matched_maturity_swap_spread12m') self.__matched_maturity_swap_spread12m = value @property def price_range_in_ticks_label(self): return self.__price_range_in_ticks_label @price_range_in_ticks_label.setter def price_range_in_ticks_label(self, value): self._property_changed('price_range_in_ticks_label') self.__price_range_in_ticks_label = value @property def ticker(self) -> str: """Ticker.""" return self.__ticker @ticker.setter def ticker(self, value: str): self._property_changed('ticker') self.__ticker = value @property def notional_unit(self) -> str: """Unit of reported notional price.""" return self.__notional_unit @notional_unit.setter def notional_unit(self, value: str): self._property_changed('notional_unit') self.__notional_unit = value @property def tcm_cost_horizon1_day(self) -> float: """TCM cost with a 1 day time horizon.""" return self.__tcm_cost_horizon1_day @tcm_cost_horizon1_day.setter def tcm_cost_horizon1_day(self, value: float): self._property_changed('tcm_cost_horizon1_day') self.__tcm_cost_horizon1_day = value @property def approval(self) -> float: """Approval rating.""" return self.__approval @approval.setter def approval(self, value: float): self._property_changed('approval') self.__approval = value @property def test_measure(self) -> float: """Generic field to hold aggregatable numeric measure for test result. For e.g delay for timeliness or variance for correctness.""" return self.__test_measure @test_measure.setter def test_measure(self, value: float): self._property_changed('test_measure') self.__test_measure = value @property def option_lock_out_period(self) -> str: """An indication of the first allowable exercise date of the option.""" return self.__option_lock_out_period @option_lock_out_period.setter def option_lock_out_period(self, value: str): self._property_changed('option_lock_out_period') self.__option_lock_out_period = value @property def execution_time(self) -> datetime.datetime: """Arrival execution time in seconds from the epoch.""" return self.__execution_time @execution_time.setter def execution_time(self, value: datetime.datetime): self._property_changed('execution_time') self.__execution_time = value @property def source_value_forecast(self) -> str: """TE own projections.""" return self.__source_value_forecast @source_value_forecast.setter def source_value_forecast(self, value: str): self._property_changed('source_value_forecast') self.__source_value_forecast = value @property def leg2_spread(self) -> float: """Spread of leg.""" return self.__leg2_spread @leg2_spread.setter def leg2_spread(self, value: float): self._property_changed('leg2_spread') self.__leg2_spread = value @property def short_conviction_large(self) -> float: """The count of short ideas with large conviction.""" return self.__short_conviction_large @short_conviction_large.setter def short_conviction_large(self, value: float): self._property_changed('short_conviction_large') self.__short_conviction_large = value @property def ccg_name(self) -> str: """Name of the clinical commissioning group.""" return self.__ccg_name @ccg_name.setter def ccg_name(self, value: str): self._property_changed('ccg_name') self.__ccg_name = value @property def dollar_excess_return(self) -> float: """The dollar excess return of an instrument.""" return self.__dollar_excess_return @dollar_excess_return.setter def dollar_excess_return(self, value: float): self._property_changed('dollar_excess_return') self.__dollar_excess_return = value @property def gsn(self) -> str: """Goldman Sachs internal product number.""" return self.__gsn @gsn.setter def gsn(self, value: str): self._property_changed('gsn') self.__gsn = value @property def trade_end_date(self) -> datetime.date: """End date of the trade.""" return self.__trade_end_date @trade_end_date.setter def trade_end_date(self, value: datetime.date): self._property_changed('trade_end_date') self.__trade_end_date = value @property def receiver_rate_option(self) -> str: """The underlying benchmark for the payer, e.g. USD-LIBOR-BBA, EUR-EURIBOR- TELERATE.""" return self.__receiver_rate_option @receiver_rate_option.setter def receiver_rate_option(self, value: str): self._property_changed('receiver_rate_option') self.__receiver_rate_option = value @property def gss(self) -> str: """Goldman Sachs internal product symbol.""" return self.__gss @gss.setter def gss(self, value: str): self._property_changed('gss') self.__gss = value @property def percent_of_mediandv1m(self) -> float: """Percentage of median daily volume calculated using 1 month period (last 22 trading days).""" return self.__percent_of_mediandv1m @percent_of_mediandv1m.setter def percent_of_mediandv1m(self, value: float): self._property_changed('percent_of_mediandv1m') self.__percent_of_mediandv1m = value @property def lendables(self) -> float: """Market value of holdings available to a securities lending program for lending.""" return self.__lendables @lendables.setter def lendables(self, value: float): self._property_changed('lendables') self.__lendables = value @property def sell75cents(self) -> float: """The amount GS would sell for 75 cents charge.""" return self.__sell75cents @sell75cents.setter def sell75cents(self, value: float): self._property_changed('sell75cents') self.__sell75cents = value @property def option_adjusted_spread(self) -> float: """Spread of a fixed-income security rate and the risk-free rate of return, which is then adjusted to take into account an embedded option.""" return self.__option_adjusted_spread @option_adjusted_spread.setter def option_adjusted_spread(self, value: float): self._property_changed('option_adjusted_spread') self.__option_adjusted_spread = value @property def option_adjusted_swap_spread(self) -> float: """Callability Option Adjusted Spread to yield curve.""" return self.__option_adjusted_swap_spread @option_adjusted_swap_spread.setter def option_adjusted_swap_spread(self, value: float): self._property_changed('option_adjusted_swap_spread') self.__option_adjusted_swap_spread = value @property def bos_in_ticks_label(self): return self.__bos_in_ticks_label @bos_in_ticks_label.setter def bos_in_ticks_label(self, value): self._property_changed('bos_in_ticks_label') self.__bos_in_ticks_label = value @property def position_source_id(self) -> str: """Marquee unique identifier""" return self.__position_source_id @position_source_id.setter def position_source_id(self, value: str): self._property_changed('position_source_id') self.__position_source_id = value @property def buy1bps(self) -> float: """The amount GS would buy for 1 bps charge.""" return self.__buy1bps @buy1bps.setter def buy1bps(self, value: float): self._property_changed('buy1bps') self.__buy1bps = value @property def buy3point5bps(self) -> float: """The amount GS would buy for 3.5 bps charge.""" return self.__buy3point5bps @buy3point5bps.setter def buy3point5bps(self, value: float): self._property_changed('buy3point5bps') self.__buy3point5bps = value @property def gs_sustain_region(self) -> str: """Region assigned by GIR ESG SUSTAIN team.""" return self.__gs_sustain_region @gs_sustain_region.setter def gs_sustain_region(self, value: str): self._property_changed('gs_sustain_region') self.__gs_sustain_region = value @property def absolute_return_wtd(self) -> float: """Absolute Return Week to Date.""" return self.__absolute_return_wtd @absolute_return_wtd.setter def absolute_return_wtd(self, value: float): self._property_changed('absolute_return_wtd') self.__absolute_return_wtd = value @property def deployment_id(self) -> float: """Deployment ID.""" return self.__deployment_id @deployment_id.setter def deployment_id(self, value: float): self._property_changed('deployment_id') self.__deployment_id = value @property def asset_parameters_seniority(self) -> str: """The seniority of the bond.""" return self.__asset_parameters_seniority @asset_parameters_seniority.setter def asset_parameters_seniority(self, value: str): self._property_changed('asset_parameters_seniority') self.__asset_parameters_seniority = value @property def ask_spread(self) -> float: """Spread between the yields of a debt security and its benchmark when both are purchased at ask price.""" return self.__ask_spread @ask_spread.setter def ask_spread(self, value: float): self._property_changed('ask_spread') self.__ask_spread = value @property def flow(self) -> float: """Flow of the fund.""" return self.__flow @flow.setter def flow(self, value: float): self._property_changed('flow') self.__flow = value @property def future_month_h26(self) -> float: """Commods future month code.""" return self.__future_month_h26 @future_month_h26.setter def future_month_h26(self, value: float): self._property_changed('future_month_h26') self.__future_month_h26 = value @property def loan_rebate(self) -> float: """Rebate paid back to a securities lending borrower.""" return self.__loan_rebate @loan_rebate.setter def loan_rebate(self, value: float): self._property_changed('loan_rebate') self.__loan_rebate = value @property def future_month_h25(self) -> float: """Commods future month code.""" return self.__future_month_h25 @future_month_h25.setter def future_month_h25(self, value: float): self._property_changed('future_month_h25') self.__future_month_h25 = value @property def period(self) -> str: """Period for the relevant metric, such as 1y (1 year).""" return self.__period @period.setter def period(self, value: str): self._property_changed('period') self.__period = value @property def index_create_source(self) -> str: """Source of basket creation""" return self.__index_create_source @index_create_source.setter def index_create_source(self, value: str): self._property_changed('index_create_source') self.__index_create_source = value @property def future_month_h24(self) -> float: """Commods future month code.""" return self.__future_month_h24 @future_month_h24.setter def future_month_h24(self, value: float): self._property_changed('future_month_h24') self.__future_month_h24 = value @property def future_month_h23(self) -> float: """Commods future month code.""" return self.__future_month_h23 @future_month_h23.setter def future_month_h23(self, value: float): self._property_changed('future_month_h23') self.__future_month_h23 = value @property def future_month_h22(self) -> float: """Commods future month code.""" return self.__future_month_h22 @future_month_h22.setter def future_month_h22(self, value: float): self._property_changed('future_month_h22') self.__future_month_h22 = value @property def future_month_h21(self) -> float: """Commods future month code.""" return self.__future_month_h21 @future_month_h21.setter def future_month_h21(self, value: float): self._property_changed('future_month_h21') self.__future_month_h21 = value @property def non_usd_ois(self) -> float: """Non USD Currency Ois rate. Feature applied only to Xccy curves.""" return self.__non_usd_ois @non_usd_ois.setter def non_usd_ois(self, value: float): self._property_changed('non_usd_ois') self.__non_usd_ois = value @property def real_twi_contribution(self) -> float: """Contribution of real trade weighted exchange rate index component to real FCI.""" return self.__real_twi_contribution @real_twi_contribution.setter def real_twi_contribution(self, value: float): self._property_changed('real_twi_contribution') self.__real_twi_contribution = value @property def mkt_asset(self) -> str: """The MDAPI Asset (e.g. USD, USD/EUR).""" return self.__mkt_asset @mkt_asset.setter def mkt_asset(self, value: str): self._property_changed('mkt_asset') self.__mkt_asset = value @property def leg2_index_location(self) -> str: """Location of leg.""" return self.__leg2_index_location @leg2_index_location.setter def leg2_index_location(self, value: str): self._property_changed('leg2_index_location') self.__leg2_index_location = value @property def twap_unrealized_bps(self) -> float: """Consolidated unrealised performance vs TWAP In Limit, in bps.""" return self.__twap_unrealized_bps @twap_unrealized_bps.setter def twap_unrealized_bps(self, value: float): self._property_changed('twap_unrealized_bps') self.__twap_unrealized_bps = value @property def last_updated_message(self) -> str: """Last Updated Message.""" return self.__last_updated_message @last_updated_message.setter def last_updated_message(self, value: str): self._property_changed('last_updated_message') self.__last_updated_message = value @property def loan_value(self) -> float: """The value of the securities or cash delivered by a borrower to a lender to support a loan of securities.""" return self.__loan_value @loan_value.setter def loan_value(self, value: float): self._property_changed('loan_value') self.__loan_value = value @property def option_adjusted_ois_spread(self) -> float: """Option adjusted OIS spread.""" return self.__option_adjusted_ois_spread @option_adjusted_ois_spread.setter def option_adjusted_ois_spread(self, value: float): self._property_changed('option_adjusted_ois_spread') self.__option_adjusted_ois_spread = value @property def total_return_price(self) -> float: """The total return price of an instrument.""" return self.__total_return_price @total_return_price.setter def total_return_price(self, value: float): self._property_changed('total_return_price') self.__total_return_price = value @property def weighted_percent_in_model(self) -> float: """Weighted percent of constituent in risk model.""" return self.__weighted_percent_in_model @weighted_percent_in_model.setter def weighted_percent_in_model(self, value: float): self._property_changed('weighted_percent_in_model') self.__weighted_percent_in_model = value @property def init_loan_spread_required(self) -> float: """The minimum spread requirement for a securities lending loan on the day of loan initiation.""" return self.__init_loan_spread_required @init_loan_spread_required.setter def init_loan_spread_required(self, value: float): self._property_changed('init_loan_spread_required') self.__init_loan_spread_required = value @property def election_period(self) -> str: """Period of election.""" return self.__election_period @election_period.setter def election_period(self, value: str): self._property_changed('election_period') self.__election_period = value @property def funding_ask_price(self) -> float: """Latest Ask Price (price offering to sell).""" return self.__funding_ask_price @funding_ask_price.setter def funding_ask_price(self, value: float): self._property_changed('funding_ask_price') self.__funding_ask_price = value @property def historical_beta(self) -> float: """Historical beta.""" return self.__historical_beta @historical_beta.setter def historical_beta(self, value: float): self._property_changed('historical_beta') self.__historical_beta = value @property def bond_risk_premium_index(self) -> float: """Bond risk premium index: difference between growth rate forecast and 10y treasury yield.""" return self.__bond_risk_premium_index @bond_risk_premium_index.setter def bond_risk_premium_index(self, value: float): self._property_changed('bond_risk_premium_index') self.__bond_risk_premium_index = value @property def hit_rate_ytd(self) -> float: """Hit Rate Ratio Year to Date.""" return self.__hit_rate_ytd @hit_rate_ytd.setter def hit_rate_ytd(self, value: float): self._property_changed('hit_rate_ytd') self.__hit_rate_ytd = value @property def gir_gsdeer_gsfeer(self) -> float: """GSDEER and GSFEER quarterly estimates for currency fair values made by Global Investment Research (GIR) macro analysts.""" return self.__gir_gsdeer_gsfeer @gir_gsdeer_gsfeer.setter def gir_gsdeer_gsfeer(self, value: float): self._property_changed('gir_gsdeer_gsfeer') self.__gir_gsdeer_gsfeer = value @property def num_units(self) -> float: """Units.""" return self.__num_units @num_units.setter def num_units(self, value: float): self._property_changed('num_units') self.__num_units = value @property def asset_parameters_receiver_frequency(self) -> str: """Tenor""" return self.__asset_parameters_receiver_frequency @asset_parameters_receiver_frequency.setter def asset_parameters_receiver_frequency(self, value: str): self._property_changed('asset_parameters_receiver_frequency') self.__asset_parameters_receiver_frequency = value @property def expense_ratio_gross_bps(self) -> float: """Gives basis point measure of management fee.""" return self.__expense_ratio_gross_bps @expense_ratio_gross_bps.setter def expense_ratio_gross_bps(self, value: float): self._property_changed('expense_ratio_gross_bps') self.__expense_ratio_gross_bps = value @property def relative_payoff_wtd(self) -> float: """Total win divided by total loss Week to date.""" return self.__relative_payoff_wtd @relative_payoff_wtd.setter def relative_payoff_wtd(self, value: float): self._property_changed('relative_payoff_wtd') self.__relative_payoff_wtd = value @property def ctd_price(self) -> float: """Price of cheapest to deliver bond.""" return self.__ctd_price @ctd_price.setter def ctd_price(self, value: float): self._property_changed('ctd_price') self.__ctd_price = value @property def pace_of_roll_now(self) -> float: """The pace of the roll as of the pricing date.""" return self.__pace_of_roll_now @pace_of_roll_now.setter def pace_of_roll_now(self, value: float): self._property_changed('pace_of_roll_now') self.__pace_of_roll_now = value @property def product(self) -> str: """The asset, reference asset, or reference obligation for payments of a party???s obligations under the SB swap transaction reference.""" return self.__product @product.setter def product(self, value: str): self._property_changed('product') self.__product = value @property def leg2_return_type(self) -> str: """Indication if return of leg 2 is total.""" return self.__leg2_return_type @leg2_return_type.setter def leg2_return_type(self, value: str): self._property_changed('leg2_return_type') self.__leg2_return_type = value @property def agent_lender_fee(self) -> float: """Fee earned by the Agent Lender for facilitating a securities lending agreement.""" return self.__agent_lender_fee @agent_lender_fee.setter def agent_lender_fee(self, value: float): self._property_changed('agent_lender_fee') self.__agent_lender_fee = value @property def dissemination_id(self) -> str: """DDR generated unique and random ID for reconciliation purpose.""" return self.__dissemination_id @dissemination_id.setter def dissemination_id(self, value: str): self._property_changed('dissemination_id') self.__dissemination_id = value @property def option_strike_price(self) -> float: """Strike price of the option. Also called option level.""" return self.__option_strike_price @option_strike_price.setter def option_strike_price(self, value: float): self._property_changed('option_strike_price') self.__option_strike_price = value @property def precipitation_type(self) -> str: """The type of precipitation required: Rain or snow etc.""" return self.__precipitation_type @precipitation_type.setter def precipitation_type(self, value: str): self._property_changed('precipitation_type') self.__precipitation_type = value @property def lower_bound(self) -> float: """Lower bound value.""" return self.__lower_bound @lower_bound.setter def lower_bound(self, value: float): self._property_changed('lower_bound') self.__lower_bound = value @property def arrival_mid_normalized(self) -> float: """Performance against Benchmark in pip.""" return self.__arrival_mid_normalized @arrival_mid_normalized.setter def arrival_mid_normalized(self, value: float): self._property_changed('arrival_mid_normalized') self.__arrival_mid_normalized = value @property def underlying_asset2(self) -> str: """Same as Underlying Asset 1 if populated.""" return self.__underlying_asset2 @underlying_asset2.setter def underlying_asset2(self, value: str): self._property_changed('underlying_asset2') self.__underlying_asset2 = value @property def underlying_asset1(self) -> str: """The asset, reference asset, or reference obligation for payments of a party???s obligations under the SB swap transaction reference.""" return self.__underlying_asset1 @underlying_asset1.setter def underlying_asset1(self, value: str): self._property_changed('underlying_asset1') self.__underlying_asset1 = value @property def legal_entity(self) -> str: """Entity that has legal rights to the fund.""" return self.__legal_entity @legal_entity.setter def legal_entity(self, value: str): self._property_changed('legal_entity') self.__legal_entity = value @property def performance_fee(self) -> Union[Op, float]: return self.__performance_fee @performance_fee.setter def performance_fee(self, value: Union[Op, float]): self._property_changed('performance_fee') self.__performance_fee = value @property def order_state(self) -> str: """State of an order.""" return self.__order_state @order_state.setter def order_state(self, value: str): self._property_changed('order_state') self.__order_state = value @property def actual_data_quality(self) -> float: """Actual data quality level.""" return self.__actual_data_quality @actual_data_quality.setter def actual_data_quality(self, value: float): self._property_changed('actual_data_quality') self.__actual_data_quality = value @property def index_ratio(self) -> float: """Latest Bid Price (price willing to buy).""" return self.__index_ratio @index_ratio.setter def index_ratio(self, value: float): self._property_changed('index_ratio') self.__index_ratio = value @property def queue_in_lots_label(self): return self.__queue_in_lots_label @queue_in_lots_label.setter def queue_in_lots_label(self, value): self._property_changed('queue_in_lots_label') self.__queue_in_lots_label = value @property def adv10_day_pct(self) -> float: """Median number of shares or units of a given asset traded over a 10 day period.""" return self.__adv10_day_pct @adv10_day_pct.setter def adv10_day_pct(self, value: float): self._property_changed('adv10_day_pct') self.__adv10_day_pct = value @property def long_conviction_medium(self) -> float: """The count of long ideas with medium conviction.""" return self.__long_conviction_medium @long_conviction_medium.setter def long_conviction_medium(self, value: float): self._property_changed('long_conviction_medium') self.__long_conviction_medium = value @property def relative_hit_rate_wtd(self) -> float: """Hit Rate Ratio Week to Date.""" return self.__relative_hit_rate_wtd @relative_hit_rate_wtd.setter def relative_hit_rate_wtd(self, value: float): self._property_changed('relative_hit_rate_wtd') self.__relative_hit_rate_wtd = value @property def daily_tracking_error(self) -> float: """Daily tracking error.""" return self.__daily_tracking_error @daily_tracking_error.setter def daily_tracking_error(self, value: float): self._property_changed('daily_tracking_error') self.__daily_tracking_error = value @property def sell140cents(self) -> float: """The amount GS would sell for 140 cents charge.""" return self.__sell140cents @sell140cents.setter def sell140cents(self, value: float): self._property_changed('sell140cents') self.__sell140cents = value @property def sell10bps(self) -> float: """The amount GS would sell for 10 bps charge.""" return self.__sell10bps @sell10bps.setter def sell10bps(self, value: float): self._property_changed('sell10bps') self.__sell10bps = value @property def aggressive_offset_from_last(self) -> float: """TBD.""" return self.__aggressive_offset_from_last @aggressive_offset_from_last.setter def aggressive_offset_from_last(self, value: float): self._property_changed('aggressive_offset_from_last') self.__aggressive_offset_from_last = value @property def longitude(self) -> float: """Longitude.""" return self.__longitude @longitude.setter def longitude(self, value: float): self._property_changed('longitude') self.__longitude = value @property def new_icu(self) -> float: """Number of new patients hospitalized in ICU.""" return self.__new_icu @new_icu.setter def new_icu(self, value: float): self._property_changed('new_icu') self.__new_icu = value @property def market_cap(self) -> float: """Market capitalization of a given asset in denominated currency.""" return self.__market_cap @market_cap.setter def market_cap(self, value: float): self._property_changed('market_cap') self.__market_cap = value @property def weighted_average_mid(self) -> float: """Weighted Average Mid.""" return self.__weighted_average_mid @weighted_average_mid.setter def weighted_average_mid(self, value: float): self._property_changed('weighted_average_mid') self.__weighted_average_mid = value @property def cluster_region(self): return self.__cluster_region @cluster_region.setter def cluster_region(self, value): self._property_changed('cluster_region') self.__cluster_region = value @property def valoren(self) -> str: """Valoren or VALOR number, Swiss primary security identifier (subject to licensing).""" return self.__valoren @valoren.setter def valoren(self, value: str): self._property_changed('valoren') self.__valoren = value @property def average_execution_price(self) -> float: """The average execution price of the order.""" return self.__average_execution_price @average_execution_price.setter def average_execution_price(self, value: float): self._property_changed('average_execution_price') self.__average_execution_price = value @property def proceeds_asset_ois_swap_spread1m(self) -> float: """Proceeds Asset OIS Swap Spread 1m.""" return self.__proceeds_asset_ois_swap_spread1m @proceeds_asset_ois_swap_spread1m.setter def proceeds_asset_ois_swap_spread1m(self, value: float): self._property_changed('proceeds_asset_ois_swap_spread1m') self.__proceeds_asset_ois_swap_spread1m = value @property def payoff_wtd(self) -> float: """Total win divided by total loss Week to date.""" return self.__payoff_wtd @payoff_wtd.setter def payoff_wtd(self, value: float): self._property_changed('payoff_wtd') self.__payoff_wtd = value @property def basis(self) -> float: """Spread to be added to the shorter tenor leg for the swap to be ATM.""" return self.__basis @basis.setter def basis(self, value: float): self._property_changed('basis') self.__basis = value @property def investment_rate_trend(self) -> float: """The day over day trend of the rate of return on an investment.""" return self.__investment_rate_trend @investment_rate_trend.setter def investment_rate_trend(self, value: float): self._property_changed('investment_rate_trend') self.__investment_rate_trend = value @property def gross_investment_mtd(self) -> float: """Total gross investment Month to date.""" return self.__gross_investment_mtd @gross_investment_mtd.setter def gross_investment_mtd(self, value: float): self._property_changed('gross_investment_mtd') self.__gross_investment_mtd = value @property def hedge_id(self) -> str: """Marquee unique identifier for a hedge.""" return self.__hedge_id @hedge_id.setter def hedge_id(self, value: str): self._property_changed('hedge_id') self.__hedge_id = value @property def sharpe_mtd(self) -> float: """Sharpe ratio Month to date.""" return self.__sharpe_mtd @sharpe_mtd.setter def sharpe_mtd(self, value: float): self._property_changed('sharpe_mtd') self.__sharpe_mtd = value @property def tcm_cost_horizon8_day(self) -> float: """TCM cost with a 8 day time horizon.""" return self.__tcm_cost_horizon8_day @tcm_cost_horizon8_day.setter def tcm_cost_horizon8_day(self, value: float): self._property_changed('tcm_cost_horizon8_day') self.__tcm_cost_horizon8_day = value @property def residual_variance(self) -> float: """Residual variance.""" return self.__residual_variance @residual_variance.setter def residual_variance(self, value: float): self._property_changed('residual_variance') self.__residual_variance = value @property def restrict_internal_derived_data(self) -> bool: """Restricts Ability to Use Internally as Part of Derived Data.""" return self.__restrict_internal_derived_data @restrict_internal_derived_data.setter def restrict_internal_derived_data(self, value: bool): self._property_changed('restrict_internal_derived_data') self.__restrict_internal_derived_data = value @property def adv5_day_pct(self) -> float: """Median number of shares or units of a given asset traded over a 5 day period.""" return self.__adv5_day_pct @adv5_day_pct.setter def adv5_day_pct(self, value: float): self._property_changed('adv5_day_pct') self.__adv5_day_pct = value @property def midpoint_fills_percentage(self) -> float: """Percent of total order filled at midpoint .""" return self.__midpoint_fills_percentage @midpoint_fills_percentage.setter def midpoint_fills_percentage(self, value: float): self._property_changed('midpoint_fills_percentage') self.__midpoint_fills_percentage = value @property def open_interest(self) -> float: """Measure of level of activity in market.""" return self.__open_interest @open_interest.setter def open_interest(self, value: float): self._property_changed('open_interest') self.__open_interest = value @property def turnover_composite_unadjusted(self) -> float: """Turnover composite not adjusted for corporate actions.""" return self.__turnover_composite_unadjusted @turnover_composite_unadjusted.setter def turnover_composite_unadjusted(self, value: float): self._property_changed('turnover_composite_unadjusted') self.__turnover_composite_unadjusted = value @property def fwd_points(self) -> float: """Forward points.""" return self.__fwd_points @fwd_points.setter def fwd_points(self, value: float): self._property_changed('fwd_points') self.__fwd_points = value @property def relative_return_wtd(self) -> float: """Relative Return Week to Date.""" return self.__relative_return_wtd @relative_return_wtd.setter def relative_return_wtd(self, value: float): self._property_changed('relative_return_wtd') self.__relative_return_wtd = value @property def units(self) -> str: """Units for series.""" return self.__units @units.setter def units(self, value: str): self._property_changed('units') self.__units = value @property def payer_rate_option(self) -> str: """The underlying benchmark for the payer, e.g. USD-LIBOR-BBA, EUR-EURIBOR- TELERATE.""" return self.__payer_rate_option @payer_rate_option.setter def payer_rate_option(self, value: str): self._property_changed('payer_rate_option') self.__payer_rate_option = value @property def asset_classifications_risk_country_name(self) -> str: """Risk country.""" return self.__asset_classifications_risk_country_name @asset_classifications_risk_country_name.setter def asset_classifications_risk_country_name(self, value: str): self._property_changed('asset_classifications_risk_country_name') self.__asset_classifications_risk_country_name = value @property def ext_mkt_point3(self) -> str: """Third dimension of external MDAPI Point.""" return self.__ext_mkt_point3 @ext_mkt_point3.setter def ext_mkt_point3(self, value: str): self._property_changed('ext_mkt_point3') self.__ext_mkt_point3 = value @property def matched_maturity_swap_spread(self) -> float: """Spread between a Matched Maturity Swap Rate and the yield of the bond.""" return self.__matched_maturity_swap_spread @matched_maturity_swap_spread.setter def matched_maturity_swap_spread(self, value: float): self._property_changed('matched_maturity_swap_spread') self.__matched_maturity_swap_spread = value @property def city_name(self) -> str: """Name of city.""" return self.__city_name @city_name.setter def city_name(self, value: str): self._property_changed('city_name') self.__city_name = value @property def hourly_bucket(self) -> str: """Bucket denoting hour of the day.""" return self.__hourly_bucket @hourly_bucket.setter def hourly_bucket(self, value: str): self._property_changed('hourly_bucket') self.__hourly_bucket = value @property def average_implied_volatility(self) -> float: """Average volatility of an asset implied by observations of market prices.""" return self.__average_implied_volatility @average_implied_volatility.setter def average_implied_volatility(self, value: float): self._property_changed('average_implied_volatility') self.__average_implied_volatility = value @property def total_hospitalized_with_symptoms(self) -> float: """Total number of hospitalized cases due to symptoms, but not in ICU.""" return self.__total_hospitalized_with_symptoms @total_hospitalized_with_symptoms.setter def total_hospitalized_with_symptoms(self, value: float): self._property_changed('total_hospitalized_with_symptoms') self.__total_hospitalized_with_symptoms = value @property def days_open_realized_cash(self) -> float: """Realised performance vs Day Open, in USD.""" return self.__days_open_realized_cash @days_open_realized_cash.setter def days_open_realized_cash(self, value: float): self._property_changed('days_open_realized_cash') self.__days_open_realized_cash = value @property def adjusted_high_price(self) -> float: """Adjusted high level of an asset based on official exchange fixing or calculation agent marked level.""" return self.__adjusted_high_price @adjusted_high_price.setter def adjusted_high_price(self, value: float): self._property_changed('adjusted_high_price') self.__adjusted_high_price = value @property def proceeds_asset_ois_swap_spread(self) -> float: """Proceeds asset OIS swap spread.""" return self.__proceeds_asset_ois_swap_spread @proceeds_asset_ois_swap_spread.setter def proceeds_asset_ois_swap_spread(self, value: float): self._property_changed('proceeds_asset_ois_swap_spread') self.__proceeds_asset_ois_swap_spread = value @property def ext_mkt_point1(self) -> str: """First dimension of external MDAPI Point.""" return self.__ext_mkt_point1 @ext_mkt_point1.setter def ext_mkt_point1(self, value: str): self._property_changed('ext_mkt_point1') self.__ext_mkt_point1 = value @property def direction(self) -> str: """Indicates whether exposure of a given position is long or short.""" return self.__direction @direction.setter def direction(self, value: str): self._property_changed('direction') self.__direction = value @property def ext_mkt_point2(self) -> str: """Second dimension of external MDAPI Point.""" return self.__ext_mkt_point2 @ext_mkt_point2.setter def ext_mkt_point2(self, value: str): self._property_changed('ext_mkt_point2') self.__ext_mkt_point2 = value @property def sub_region_code(self) -> str: """ISO 3166 Sub Region Code.""" return self.__sub_region_code @sub_region_code.setter def sub_region_code(self, value: str): self._property_changed('sub_region_code') self.__sub_region_code = value @property def asset_parameters_fixed_rate(self) -> Union[float, str]: """Strike as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF, 10/vol, 100k/pv, p=10000, p=10000USD, $200K/BP, or multiple strikes 65.4/-45.8""" return self.__asset_parameters_fixed_rate @asset_parameters_fixed_rate.setter def asset_parameters_fixed_rate(self, value: Union[float, str]): self._property_changed('asset_parameters_fixed_rate') self.__asset_parameters_fixed_rate = value @property def is_estimated_return(self) -> bool: """Whether the return of asset over a given period is an estimate or not.""" return self.__is_estimated_return @is_estimated_return.setter def is_estimated_return(self, value: bool): self._property_changed('is_estimated_return') self.__is_estimated_return = value @property def value_forecast(self) -> str: """Average forecast among a representative group of economists.""" return self.__value_forecast @value_forecast.setter def value_forecast(self, value: str): self._property_changed('value_forecast') self.__value_forecast = value @property def total_icu(self) -> float: """Total number of cases in intensive care.""" return self.__total_icu @total_icu.setter def total_icu(self, value: float): self._property_changed('total_icu') self.__total_icu = value @property def position_source_type(self) -> str: """Source object for position data""" return self.__position_source_type @position_source_type.setter def position_source_type(self, value: str): self._property_changed('position_source_type') self.__position_source_type = value @property def previous_close_unrealized_cash(self) -> float: """Unrealised performance vs Previous Close, in USD.""" return self.__previous_close_unrealized_cash @previous_close_unrealized_cash.setter def previous_close_unrealized_cash(self, value: float): self._property_changed('previous_close_unrealized_cash') self.__previous_close_unrealized_cash = value @property def minimum_denomination(self) -> float: """The lowest denomination of an issue that can be purchased as authorized by the bond documents.""" return self.__minimum_denomination @minimum_denomination.setter def minimum_denomination(self, value: float): self._property_changed('minimum_denomination') self.__minimum_denomination = value @property def future_value_notional(self) -> float: """The future notional value of the asset.""" return self.__future_value_notional @future_value_notional.setter def future_value_notional(self, value: float): self._property_changed('future_value_notional') self.__future_value_notional = value @property def participation_rate(self) -> float: """Executed quantity over market volume (e.g. 5, 10, 20).""" return self.__participation_rate @participation_rate.setter def participation_rate(self, value: float): self._property_changed('participation_rate') self.__participation_rate = value @property def obfr(self) -> float: """The overnight bank funding rate.""" return self.__obfr @obfr.setter def obfr(self, value: float): self._property_changed('obfr') self.__obfr = value @property def buy9point5bps(self) -> float: """The amount GS would buy for 9.5 bps charge.""" return self.__buy9point5bps @buy9point5bps.setter def buy9point5bps(self, value: float): self._property_changed('buy9point5bps') self.__buy9point5bps = value @property def option_lock_period(self) -> str: """An indication of the first allowable exercise date of the option.""" return self.__option_lock_period @option_lock_period.setter def option_lock_period(self, value: str): self._property_changed('option_lock_period') self.__option_lock_period = value @property def es_momentum_percentile(self) -> float: """A percentile that captures a company's E&S momentum ranking within its subsector.""" return self.__es_momentum_percentile @es_momentum_percentile.setter def es_momentum_percentile(self, value: float): self._property_changed('es_momentum_percentile') self.__es_momentum_percentile = value @property def adv_percentage(self) -> float: """Percentage of ADV.""" return self.__adv_percentage @adv_percentage.setter def adv_percentage(self, value: float): self._property_changed('adv_percentage') self.__adv_percentage = value @property def leg1_averaging_method(self) -> str: """Averaging method of leg.""" return self.__leg1_averaging_method @leg1_averaging_method.setter def leg1_averaging_method(self, value: str): self._property_changed('leg1_averaging_method') self.__leg1_averaging_method = value @property def turnover_composite(self) -> float: """Turnover composite.""" return self.__turnover_composite @turnover_composite.setter def turnover_composite(self, value: float): self._property_changed('turnover_composite') self.__turnover_composite = value @property def forecast_date(self) -> datetime.date: """Date of forecasted electricity loads.""" return self.__forecast_date @forecast_date.setter def forecast_date(self, value: datetime.date): self._property_changed('forecast_date') self.__forecast_date = value @property def internal_index_calc_region(self) -> str: """Classification for the asset based on index calculation region""" return self.__internal_index_calc_region @internal_index_calc_region.setter def internal_index_calc_region(self, value: str): self._property_changed('internal_index_calc_region') self.__internal_index_calc_region = value @property def position_type(self) -> str: """Type of positions.""" return self.__position_type @position_type.setter def position_type(self, value: str): self._property_changed('position_type') self.__position_type = value @property def sub_asset_class(self) -> str: """An indication of the sub asset class.""" return self.__sub_asset_class @sub_asset_class.setter def sub_asset_class(self, value: str): self._property_changed('sub_asset_class') self.__sub_asset_class = value @property def short_interest(self) -> float: """Short interest value.""" return self.__short_interest @short_interest.setter def short_interest(self, value: float): self._property_changed('short_interest') self.__short_interest = value @property def reference_period(self) -> str: """The period for which released data refers to.""" return self.__reference_period @reference_period.setter def reference_period(self, value: str): self._property_changed('reference_period') self.__reference_period = value @property def adjusted_volume(self) -> float: """Accumulated number of shares, lots or contracts traded according to the market convention adjusted for corporate actions.""" return self.__adjusted_volume @adjusted_volume.setter def adjusted_volume(self, value: float): self._property_changed('adjusted_volume') self.__adjusted_volume = value @property def ctd_fwd_yield(self) -> float: """Cheapest to deliver bond fwd yield.""" return self.__ctd_fwd_yield @ctd_fwd_yield.setter def ctd_fwd_yield(self, value: float): self._property_changed('ctd_fwd_yield') self.__ctd_fwd_yield = value @property def sec_db(self) -> str: """Internal Goldman Sachs security database location for the asset.""" return self.__sec_db @sec_db.setter def sec_db(self, value: str): self._property_changed('sec_db') self.__sec_db = value @property def memory_used(self) -> float: """Total memory used by tickserver.""" return self.__memory_used @memory_used.setter def memory_used(self, value: float): self._property_changed('memory_used') self.__memory_used = value @property def bpe_quality_stars(self) -> float: """Confidence in the BPE.""" return self.__bpe_quality_stars @bpe_quality_stars.setter def bpe_quality_stars(self, value: float): self._property_changed('bpe_quality_stars') self.__bpe_quality_stars = value @property def ctd(self) -> str: """Description of cheapest to deliver bond.""" return self.__ctd @ctd.setter def ctd(self, value: str): self._property_changed('ctd') self.__ctd = value @property def intended_participation_rate(self) -> float: """Intended participation rate.""" return self.__intended_participation_rate @intended_participation_rate.setter def intended_participation_rate(self, value: float): self._property_changed('intended_participation_rate') self.__intended_participation_rate = value @property def leg1_payment_type(self) -> str: """Type of payment stream on leg 1.""" return self.__leg1_payment_type @leg1_payment_type.setter def leg1_payment_type(self, value: str): self._property_changed('leg1_payment_type') self.__leg1_payment_type = value @property def trading_pnl(self) -> float: """Trading Profit and Loss (PNL).""" return self.__trading_pnl @trading_pnl.setter def trading_pnl(self, value: float): self._property_changed('trading_pnl') self.__trading_pnl = value @property def collateral_value_required(self) -> float: """Value of collateral required to cover a given position.""" return self.__collateral_value_required @collateral_value_required.setter def collateral_value_required(self, value: float): self._property_changed('collateral_value_required') self.__collateral_value_required = value @property def buy45bps(self) -> float: """The amount GS would buy for 45 bps charge.""" return self.__buy45bps @buy45bps.setter def buy45bps(self, value: float): self._property_changed('buy45bps') self.__buy45bps = value @property def price_to_earnings_positive(self) -> float: """Price to earnings positive.""" return self.__price_to_earnings_positive @price_to_earnings_positive.setter def price_to_earnings_positive(self, value: float): self._property_changed('price_to_earnings_positive') self.__price_to_earnings_positive = value @property def forecast(self) -> float: """Forward FX forecast.""" return self.__forecast @forecast.setter def forecast(self, value: float): self._property_changed('forecast') self.__forecast = value @property def forecast_value(self) -> float: """Forecast value.""" return self.__forecast_value @forecast_value.setter def forecast_value(self, value: float): self._property_changed('forecast_value') self.__forecast_value = value @property def pnl(self) -> float: """Profit and Loss.""" return self.__pnl @pnl.setter def pnl(self, value: float): self._property_changed('pnl') self.__pnl = value @property def volume_in_limit(self) -> float: """Consolidated Volume In Limit.""" return self.__volume_in_limit @volume_in_limit.setter def volume_in_limit(self, value: float): self._property_changed('volume_in_limit') self.__volume_in_limit = value @property def is_territory(self) -> bool: """Whether or not a territory.""" return self.__is_territory @is_territory.setter def is_territory(self, value: bool): self._property_changed('is_territory') self.__is_territory = value @property def leg2_delivery_point(self) -> str: """Delivery point of leg.""" return self.__leg2_delivery_point @leg2_delivery_point.setter def leg2_delivery_point(self, value: str): self._property_changed('leg2_delivery_point') self.__leg2_delivery_point = value @property def tcm_cost_horizon4_day(self) -> float: """TCM cost with a 4 day time horizon.""" return self.__tcm_cost_horizon4_day @tcm_cost_horizon4_day.setter def tcm_cost_horizon4_day(self, value: float): self._property_changed('tcm_cost_horizon4_day') self.__tcm_cost_horizon4_day = value @property def styles(self) -> Tuple[Tuple[str, ...], ...]: """Styles or themes associated with the asset (max 50)""" return self.__styles @styles.setter def styles(self, value: Tuple[Tuple[str, ...], ...]): self._property_changed('styles') self.__styles = value @property def short_name(self) -> str: """Short name.""" return self.__short_name @short_name.setter def short_name(self, value: str): self._property_changed('short_name') self.__short_name = value @property def reset_frequency1(self) -> str: """An integer multiplier of a period describing how often the parties to an SB swap transaction shall evaluate and, when applicable, change the price used for the underlying assets of the swap transaction. Such reset frequency may be described as one letter preceded by an integer.""" return self.__reset_frequency1 @reset_frequency1.setter def reset_frequency1(self, value: str): self._property_changed('reset_frequency1') self.__reset_frequency1 = value @property def buy4bps(self) -> float: """The amount GS would buy for 4 bps charge.""" return self.__buy4bps @buy4bps.setter def buy4bps(self, value: float): self._property_changed('buy4bps') self.__buy4bps = value @property def reset_frequency2(self) -> str: """Same as Reset Frequency 1.""" return self.__reset_frequency2 @reset_frequency2.setter def reset_frequency2(self, value: str): self._property_changed('reset_frequency2') self.__reset_frequency2 = value @property def other_price_term(self) -> str: """An indication that the publicly reportable SB swap transaction has one or more additional term(s) or provision(s), other than those listed in the required real-time data fields, that materially affect(s) the price of the swap transaction.""" return self.__other_price_term @other_price_term.setter def other_price_term(self, value: str): self._property_changed('other_price_term') self.__other_price_term = value @property def bid_gspread(self) -> float: """Bid G spread.""" return self.__bid_gspread @bid_gspread.setter def bid_gspread(self, value: float): self._property_changed('bid_gspread') self.__bid_gspread = value @property def open_price(self) -> float: """Opening level of an asset based on official exchange fixing or calculation agent marked level.""" return self.__open_price @open_price.setter def open_price(self, value: float): self._property_changed('open_price') self.__open_price = value @property def ps_id(self) -> str: """Platts Symbol.""" return self.__ps_id @ps_id.setter def ps_id(self, value: str): self._property_changed('ps_id') self.__ps_id = value @property def hit_rate_mtd(self) -> float: """Hit Rate Ratio Month to Date.""" return self.__hit_rate_mtd @hit_rate_mtd.setter def hit_rate_mtd(self, value: float): self._property_changed('hit_rate_mtd') self.__hit_rate_mtd = value @property def fair_volatility(self) -> float: """Strike in volatility terms, calculated as square root of fair variance.""" return self.__fair_volatility @fair_volatility.setter def fair_volatility(self, value: float): self._property_changed('fair_volatility') self.__fair_volatility = value @property def dollar_cross(self) -> str: """USD cross symbol for a particular currency.""" return self.__dollar_cross @dollar_cross.setter def dollar_cross(self, value: str): self._property_changed('dollar_cross') self.__dollar_cross = value @property def portfolio_type(self) -> str: """Portfolio type differentiates the portfolio categorization""" return self.__portfolio_type @portfolio_type.setter def portfolio_type(self, value: str): self._property_changed('portfolio_type') self.__portfolio_type = value @property def currency(self) -> str: """Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)""" return self.__currency @currency.setter def currency(self, value: str): self._property_changed('currency') self.__currency = value @property def cluster_class(self) -> str: """The Cluster the stock belongs to on the particular date. The cluster class will be assigned to a value between 1 and 13 (inclusive).""" return self.__cluster_class @cluster_class.setter def cluster_class(self, value: str): self._property_changed('cluster_class') self.__cluster_class = value @property def sell50bps(self) -> float: """The amount GS would sell for 50 bps charge.""" return self.__sell50bps @sell50bps.setter def sell50bps(self, value: float): self._property_changed('sell50bps') self.__sell50bps = value @property def future_month_m21(self) -> float: """Commods future month code.""" return self.__future_month_m21 @future_month_m21.setter def future_month_m21(self, value: float): self._property_changed('future_month_m21') self.__future_month_m21 = value @property def bid_size(self) -> float: """The number of shares, lots, or contracts willing to buy at the Bid price.""" return self.__bid_size @bid_size.setter def bid_size(self, value: float): self._property_changed('bid_size') self.__bid_size = value @property def arrival_mid(self) -> float: """Arrival Mid Price.""" return self.__arrival_mid @arrival_mid.setter def arrival_mid(self, value: float): self._property_changed('arrival_mid') self.__arrival_mid = value @property def asset_parameters_exchange_currency(self) -> str: """Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)""" return self.__asset_parameters_exchange_currency @asset_parameters_exchange_currency.setter def asset_parameters_exchange_currency(self, value: str): self._property_changed('asset_parameters_exchange_currency') self.__asset_parameters_exchange_currency = value @property def candidate_name(self) -> str: """Name of candidate in election.""" return self.__candidate_name @candidate_name.setter def candidate_name(self, value: str): self._property_changed('candidate_name') self.__candidate_name = value @property def implied_lognormal_volatility(self) -> float: """Market implied volatility measured using a lognormal model in percent/year.""" return self.__implied_lognormal_volatility @implied_lognormal_volatility.setter def implied_lognormal_volatility(self, value: float): self._property_changed('implied_lognormal_volatility') self.__implied_lognormal_volatility = value @property def vwap_in_limit_unrealized_cash(self) -> float: """Consolidated unrealised performance vs VWAP In Limit, in USD.""" return self.__vwap_in_limit_unrealized_cash @vwap_in_limit_unrealized_cash.setter def vwap_in_limit_unrealized_cash(self, value: float): self._property_changed('vwap_in_limit_unrealized_cash') self.__vwap_in_limit_unrealized_cash = value @property def rating_moodys(self) -> str: """Bond rating from Moody's.""" return self.__rating_moodys @rating_moodys.setter def rating_moodys(self, value: str): self._property_changed('rating_moodys') self.__rating_moodys = value @property def future_month_m26(self) -> float: """Commods future month code.""" return self.__future_month_m26 @future_month_m26.setter def future_month_m26(self, value: float): self._property_changed('future_month_m26') self.__future_month_m26 = value @property def future_month_m25(self) -> float: """Commods future month code.""" return self.__future_month_m25 @future_month_m25.setter def future_month_m25(self, value: float): self._property_changed('future_month_m25') self.__future_month_m25 = value @property def future_month_m24(self) -> float: """Commods future month code.""" return self.__future_month_m24 @future_month_m24.setter def future_month_m24(self, value: float): self._property_changed('future_month_m24') self.__future_month_m24 = value @property def future_month_m23(self) -> float: """Commods future month code.""" return self.__future_month_m23 @future_month_m23.setter def future_month_m23(self, value: float): self._property_changed('future_month_m23') self.__future_month_m23 = value @property def future_month_m22(self) -> float: """Commods future month code.""" return self.__future_month_m22 @future_month_m22.setter def future_month_m22(self, value: float): self._property_changed('future_month_m22') self.__future_month_m22 = value @property def flow_pct(self) -> float: """Percent of flow of the fund.""" return self.__flow_pct @flow_pct.setter def flow_pct(self, value: float): self._property_changed('flow_pct') self.__flow_pct = value @property def source(self) -> str: """Source of data.""" return self.__source @source.setter def source(self, value: str): self._property_changed('source') self.__source = value @property def asset_classifications_country_code(self) -> str: """Country code (ISO 3166).""" return self.__asset_classifications_country_code @asset_classifications_country_code.setter def asset_classifications_country_code(self, value: str): self._property_changed('asset_classifications_country_code') self.__asset_classifications_country_code = value @property def settle_drop(self) -> float: """Latest Bid Price (price willing to buy).""" return self.__settle_drop @settle_drop.setter def settle_drop(self, value: float): self._property_changed('settle_drop') self.__settle_drop = value @property def data_set_sub_category(self) -> str: """Second level grouping of dataset.""" return self.__data_set_sub_category @data_set_sub_category.setter def data_set_sub_category(self, value: str): self._property_changed('data_set_sub_category') self.__data_set_sub_category = value @property def sell9point5bps(self) -> float: """The amount GS would sell for 9.5 bps charge.""" return self.__sell9point5bps @sell9point5bps.setter def sell9point5bps(self, value: float): self._property_changed('sell9point5bps') self.__sell9point5bps = value @property def quantity_bucket(self) -> str: """Range of pricing hours.""" return self.__quantity_bucket @quantity_bucket.setter def quantity_bucket(self, value: str): self._property_changed('quantity_bucket') self.__quantity_bucket = value @property def option_style_sdr(self) -> str: """Style of the option.""" return self.__option_style_sdr @option_style_sdr.setter def option_style_sdr(self, value: str): self._property_changed('option_style_sdr') self.__option_style_sdr = value @property def oe_name(self) -> str: """Name of user's organization.""" return self.__oe_name @oe_name.setter def oe_name(self, value: str): self._property_changed('oe_name') self.__oe_name = value @property def given(self) -> float: """Number of trades given.""" return self.__given @given.setter def given(self, value: float): self._property_changed('given') self.__given = value @property def leg2_day_count_convention(self) -> str: """The determination of how interest accrues over time for the SB swap.""" return self.__leg2_day_count_convention @leg2_day_count_convention.setter def leg2_day_count_convention(self, value: str): self._property_changed('leg2_day_count_convention') self.__leg2_day_count_convention = value @property def liquidity_score_sell(self) -> float: """The liquidity score assigned to selling the bond.""" return self.__liquidity_score_sell @liquidity_score_sell.setter def liquidity_score_sell(self, value: float): self._property_changed('liquidity_score_sell') self.__liquidity_score_sell = value @property def delisting_date(self) -> str: """Date at which the entity is delisted.""" return self.__delisting_date @delisting_date.setter def delisting_date(self, value: str): self._property_changed('delisting_date') self.__delisting_date = value @property def weight(self) -> float: """Weight of a given position within a portfolio, by default calcualted as netWeight.""" return self.__weight @weight.setter def weight(self, value: float): self._property_changed('weight') self.__weight = value @property def accrued_interest(self) -> float: """The accrued interest paid on the bond.""" return self.__accrued_interest @accrued_interest.setter def accrued_interest(self, value: float): self._property_changed('accrued_interest') self.__accrued_interest = value @property def business_scope(self) -> str: """Business/Product Scope.""" return self.__business_scope @business_scope.setter def business_scope(self, value: str): self._property_changed('business_scope') self.__business_scope = value @property def wtd_degree_days(self) -> float: """The actual/forecast value for weighted degree days.""" return self.__wtd_degree_days @wtd_degree_days.setter def wtd_degree_days(self, value: float): self._property_changed('wtd_degree_days') self.__wtd_degree_days = value @property def absolute_weight(self) -> float: """Weight in terms of absolute notional.""" return self.__absolute_weight @absolute_weight.setter def absolute_weight(self, value: float): self._property_changed('absolute_weight') self.__absolute_weight = value @property def measure(self) -> str: """A calculated metric in the risk scenario.""" return self.__measure @measure.setter def measure(self, value: str): self._property_changed('measure') self.__measure = value @property def temperature_hourly_forecast(self) -> float: """The hourly forecast value for temperature in Fahrenheit.""" return self.__temperature_hourly_forecast @temperature_hourly_forecast.setter def temperature_hourly_forecast(self, value: float): self._property_changed('temperature_hourly_forecast') self.__temperature_hourly_forecast = value @property def iceberg_tip_rate_type(self) -> str: """The unit associated with an Iceberg tip rate.""" return self.__iceberg_tip_rate_type @iceberg_tip_rate_type.setter def iceberg_tip_rate_type(self, value: str): self._property_changed('iceberg_tip_rate_type') self.__iceberg_tip_rate_type = value @property def sharpe_ytd(self) -> float: """Sharpe ratio Year to date.""" return self.__sharpe_ytd @sharpe_ytd.setter def sharpe_ytd(self, value: float): self._property_changed('sharpe_ytd') self.__sharpe_ytd = value @property def wind_speed_forecast(self) -> float: """The forecast value for wind speed.""" return self.__wind_speed_forecast @wind_speed_forecast.setter def wind_speed_forecast(self, value: float): self._property_changed('wind_speed_forecast') self.__wind_speed_forecast = value @property def gross_investment_ytd(self) -> float: """Total gross investment Year to date.""" return self.__gross_investment_ytd @gross_investment_ytd.setter def gross_investment_ytd(self, value: float): self._property_changed('gross_investment_ytd') self.__gross_investment_ytd = value @property def yield_price(self) -> float: """Yield price.""" return self.__yield_price @yield_price.setter def yield_price(self, value: float): self._property_changed('yield_price') self.__yield_price = value @property def leg1_total_notional_unit(self) -> str: """Unit of reported notional price.""" return self.__leg1_total_notional_unit @leg1_total_notional_unit.setter def leg1_total_notional_unit(self, value: str): self._property_changed('leg1_total_notional_unit') self.__leg1_total_notional_unit = value @property def issue_price(self) -> float: """The price for which the instrument is issued.""" return self.__issue_price @issue_price.setter def issue_price(self, value: float): self._property_changed('issue_price') self.__issue_price = value @property def ask_high(self) -> float: """The highest Ask Price (price offering to sell).""" return self.__ask_high @ask_high.setter def ask_high(self, value: float): self._property_changed('ask_high') self.__ask_high = value @property def expected_data_quality(self) -> float: """Expected data quality level.""" return self.__expected_data_quality @expected_data_quality.setter def expected_data_quality(self, value: float): self._property_changed('expected_data_quality') self.__expected_data_quality = value @property def region_name(self) -> str: """Name of the region for which FCI is calculated ??? Developed Markets, Emerging Markets, Euro Area, Global.""" return self.__region_name @region_name.setter def region_name(self, value: str): self._property_changed('region_name') self.__region_name = value @property def value_revised(self) -> str: """Revised value.""" return self.__value_revised @value_revised.setter def value_revised(self, value: str): self._property_changed('value_revised') self.__value_revised = value @property def discretion_upper_bound(self) -> float: """TThe upper bound of the discretion band as published from the algo.""" return self.__discretion_upper_bound @discretion_upper_bound.setter def discretion_upper_bound(self, value: float): self._property_changed('discretion_upper_bound') self.__discretion_upper_bound = value @property def adjusted_trade_price(self) -> float: """Last trade price or value adjusted for corporate actions.""" return self.__adjusted_trade_price @adjusted_trade_price.setter def adjusted_trade_price(self, value: float): self._property_changed('adjusted_trade_price') self.__adjusted_trade_price = value @property def forecast_time(self) -> datetime.datetime: """Time of forecasted electricity loads (in UTC).""" return self.__forecast_time @forecast_time.setter def forecast_time(self, value: datetime.datetime): self._property_changed('forecast_time') self.__forecast_time = value @property def iso_subdivision_code_alpha2(self) -> str: """ISO 3166-2 code for identifying the principal subdivisions of all countries coded in ISO 3166-1 (up to three alphanumeric characters).""" return self.__iso_subdivision_code_alpha2 @iso_subdivision_code_alpha2.setter def iso_subdivision_code_alpha2(self, value: str): self._property_changed('iso_subdivision_code_alpha2') self.__iso_subdivision_code_alpha2 = value @property def ctd_conversion_factor(self) -> float: """Cheapest to deliver bond's conversion factor.""" return self.__ctd_conversion_factor @ctd_conversion_factor.setter def ctd_conversion_factor(self, value: float): self._property_changed('ctd_conversion_factor') self.__ctd_conversion_factor = value @property def proceeds_asset_swap_spread(self) -> float: """Proceeds asset swap spread.""" return self.__proceeds_asset_swap_spread @proceeds_asset_swap_spread.setter def proceeds_asset_swap_spread(self, value: float): self._property_changed('proceeds_asset_swap_spread') self.__proceeds_asset_swap_spread = value @property def is_adr(self) -> bool: """Is ADR or not.""" return self.__is_adr @is_adr.setter def is_adr(self, value: bool): self._property_changed('is_adr') self.__is_adr = value @property def issue_date(self) -> datetime.date: """ISO 8601-formatted date""" return self.__issue_date @issue_date.setter def issue_date(self, value: datetime.date): self._property_changed('issue_date') self.__issue_date = value @property def service_id(self) -> str: """Service ID.""" return self.__service_id @service_id.setter def service_id(self, value: str): self._property_changed('service_id') self.__service_id = value @property def yes(self) -> float: """Price of yes contract.""" return self.__yes @yes.setter def yes(self, value: float): self._property_changed('yes') self.__yes = value @property def g_score(self) -> float: """A company's score for G metrics relative to the entire ESG universe.""" return self.__g_score @g_score.setter def g_score(self, value: float): self._property_changed('g_score') self.__g_score = value @property def market_value(self) -> float: """Marketable value of a given position, generally the market price for a given date.""" return self.__market_value @market_value.setter def market_value(self, value: float): self._property_changed('market_value') self.__market_value = value @property def entity_id(self) -> str: """Identifies an entity uniquely; in use for Data Quality Checker.""" return self.__entity_id @entity_id.setter def entity_id(self, value: str): self._property_changed('entity_id') self.__entity_id = value @property def notional_currency1(self) -> str: """An indication of the type of currency of the notional or principal amount.""" return self.__notional_currency1 @notional_currency1.setter def notional_currency1(self, value: str): self._property_changed('notional_currency1') self.__notional_currency1 = value @property def net_debt_to_ebitda(self) -> float: """Net Debt to EBITDA.""" return self.__net_debt_to_ebitda @net_debt_to_ebitda.setter def net_debt_to_ebitda(self, value: float): self._property_changed('net_debt_to_ebitda') self.__net_debt_to_ebitda = value @property def num_units_upper(self) -> float: """Units Upper.""" return self.__num_units_upper @num_units_upper.setter def num_units_upper(self, value: float): self._property_changed('num_units_upper') self.__num_units_upper = value @property def notional_currency2(self) -> str: """Same as Notional Currency 1.""" return self.__notional_currency2 @notional_currency2.setter def notional_currency2(self, value: str): self._property_changed('notional_currency2') self.__notional_currency2 = value @property def in_limit_participation_rate(self) -> float: """Average in-limit participation rate of the order, including principal fills.""" return self.__in_limit_participation_rate @in_limit_participation_rate.setter def in_limit_participation_rate(self, value: float): self._property_changed('in_limit_participation_rate') self.__in_limit_participation_rate = value @property def pressure_forecast(self) -> float: """The pressure forecast of a given unit.""" return self.__pressure_forecast @pressure_forecast.setter def pressure_forecast(self, value: float): self._property_changed('pressure_forecast') self.__pressure_forecast = value @property def paid(self) -> float: """Number of trades paid.""" return self.__paid @paid.setter def paid(self, value: float): self._property_changed('paid') self.__paid = value @property def fixed_rate(self) -> Union[float, str]: """Strike as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF, 10/vol, 100k/pv, p=10000, p=10000USD, $200K/BP, or multiple strikes 65.4/-45.8""" return self.__fixed_rate @fixed_rate.setter def fixed_rate(self, value: Union[float, str]): self._property_changed('fixed_rate') self.__fixed_rate = value @property def short(self) -> float: """Short exposure.""" return self.__short @short.setter def short(self, value: float): self._property_changed('short') self.__short = value @property def time(self) -> datetime.datetime: """ISO 8601 formatted date and time.""" return self.__time @time.setter def time(self, value: datetime.datetime): self._property_changed('time') self.__time = value @property def buy4point5bps(self) -> float: """The amount GS would buy for 4.5 bps charge.""" return self.__buy4point5bps @buy4point5bps.setter def buy4point5bps(self, value: float): self._property_changed('buy4point5bps') self.__buy4point5bps = value @property def sell30cents(self) -> float: """The amount GS would sell for 30 cents charge.""" return self.__sell30cents @sell30cents.setter def sell30cents(self, value: float): self._property_changed('sell30cents') self.__sell30cents = value @property def event_end_date_time(self) -> datetime.datetime: """The end time of the event if the event occurs during a time window and the event has a specific end time.""" return self.__event_end_date_time @event_end_date_time.setter def event_end_date_time(self, value: datetime.datetime): self._property_changed('event_end_date_time') self.__event_end_date_time = value @property def leg1_payment_frequency(self) -> str: """An integer multiplier of a time period describing how often the parties to the SB swap transaction exchange payments associated with each party???s obligation. Such payment frequency may be described as one letter preceded by an integer.""" return self.__leg1_payment_frequency @leg1_payment_frequency.setter def leg1_payment_frequency(self, value: str): self._property_changed('leg1_payment_frequency') self.__leg1_payment_frequency = value @property def cm_id(self) -> str: """Prime Client Master Party Id.""" return self.__cm_id @cm_id.setter def cm_id(self, value: str): self._property_changed('cm_id') self.__cm_id = value @property def taxonomy(self) -> str: """An indication of the product taxonomy.""" return self.__taxonomy @taxonomy.setter def taxonomy(self, value: str): self._property_changed('taxonomy') self.__taxonomy = value @property def buy45cents(self) -> float: """The amount GS would buy for 45 cents charge.""" return self.__buy45cents @buy45cents.setter def buy45cents(self, value: float): self._property_changed('buy45cents') self.__buy45cents = value @property def measures(self) -> Tuple[str, ...]: """Fields that are nullable.""" return self.__measures @measures.setter def measures(self, value: Tuple[str, ...]): self._property_changed('measures') self.__measures = value @property def seasonal_adjustment(self) -> str: """Indicates if and how the forecast is seasonally adjusted.""" return self.__seasonal_adjustment @seasonal_adjustment.setter def seasonal_adjustment(self, value: str): self._property_changed('seasonal_adjustment') self.__seasonal_adjustment = value @property def rank_wtd(self) -> float: """Rank of the Contributor Week to Date.""" return self.__rank_wtd @rank_wtd.setter def rank_wtd(self, value: float): self._property_changed('rank_wtd') self.__rank_wtd = value @property def underlyer(self) -> str: """The underlyer of the security. The cross for FX forwards, for example.""" return self.__underlyer @underlyer.setter def underlyer(self, value: str): self._property_changed('underlyer') self.__underlyer = value @property def created_time(self) -> datetime.datetime: """Time created. ISO 8601 formatted string.""" return self.__created_time @created_time.setter def created_time(self, value: datetime.datetime): self._property_changed('created_time') self.__created_time = value @property def identifier(self) -> str: """Filter by any identifier of an asset like ticker, bloomberg id etc.""" return self.__identifier @identifier.setter def identifier(self, value: str): self._property_changed('identifier') self.__identifier = value @property def price_unit(self) -> str: """Unit of reported price.""" return self.__price_unit @price_unit.setter def price_unit(self, value: str): self._property_changed('price_unit') self.__price_unit = value @property def trade_report_ref_id(self) -> str: """On cancellations and corrections, this ID will hold the Dissemination ID of the originally published real-time message.""" return self.__trade_report_ref_id @trade_report_ref_id.setter def trade_report_ref_id(self, value: str): self._property_changed('trade_report_ref_id') self.__trade_report_ref_id = value @property def subdivision_id(self) -> str: """Marquee subdivision object identifier.""" return self.__subdivision_id @subdivision_id.setter def subdivision_id(self, value: str): self._property_changed('subdivision_id') self.__subdivision_id = value @property def unadjusted_low(self) -> float: """Unadjusted low level of an asset based on official exchange fixing or calculation agent marked level.""" return self.__unadjusted_low @unadjusted_low.setter def unadjusted_low(self, value: float): self._property_changed('unadjusted_low') self.__unadjusted_low = value @property def buy160cents(self) -> float: """The amount GS would buy for 160 cents charge.""" return self.__buy160cents @buy160cents.setter def buy160cents(self, value: float): self._property_changed('buy160cents') self.__buy160cents = value @property def portfolio_id(self) -> str: """Marquee unique identifier for a portfolio.""" return self.__portfolio_id @portfolio_id.setter def portfolio_id(self, value: str): self._property_changed('portfolio_id') self.__portfolio_id = value @property def z_spread(self) -> float: """Zero Volatility Spread to yield curve.""" return self.__z_spread @z_spread.setter def z_spread(self, value: float): self._property_changed('z_spread') self.__z_spread = value @property def cap_floor_atm_fwd_rate(self) -> float: """Cap Floor ATM forward rate.""" return self.__cap_floor_atm_fwd_rate @cap_floor_atm_fwd_rate.setter def cap_floor_atm_fwd_rate(self, value: float): self._property_changed('cap_floor_atm_fwd_rate') self.__cap_floor_atm_fwd_rate = value @property def es_percentile(self) -> float: """An aggregate percentile that captures a company's E&S ranking within its subsector.""" return self.__es_percentile @es_percentile.setter def es_percentile(self, value: float): self._property_changed('es_percentile') self.__es_percentile = value @property def tdapi(self) -> str: """TDAPI Description.""" return self.__tdapi @tdapi.setter def tdapi(self, value: str): self._property_changed('tdapi') self.__tdapi = value @property def location_code(self) -> str: """The unique government-designated code for locations (of varying granularity).""" return self.__location_code @location_code.setter def location_code(self, value: str): self._property_changed('location_code') self.__location_code = value @property def rcic(self) -> str: """Reuters composite instrument code (subject to licensing).""" return self.__rcic @rcic.setter def rcic(self, value: str): self._property_changed('rcic') self.__rcic = value @property def name_raw(self) -> str: """Legal or published name for the asset.""" return self.__name_raw @name_raw.setter def name_raw(self, value: str): self._property_changed('name_raw') self.__name_raw = value @property def simon_asset_tags(self) -> Tuple[str, ...]: """SIMON Asset Tags.""" return self.__simon_asset_tags @simon_asset_tags.setter def simon_asset_tags(self, value: Tuple[str, ...]): self._property_changed('simon_asset_tags') self.__simon_asset_tags = value @property def hit_rate_qtd(self) -> float: """Hit Rate Ratio Quarter to Date.""" return self.__hit_rate_qtd @hit_rate_qtd.setter def hit_rate_qtd(self, value: float): self._property_changed('hit_rate_qtd') self.__hit_rate_qtd = value @property def primary_volume_in_limit(self) -> float: """Primary volume in limit as specified on the order.""" return self.__primary_volume_in_limit @primary_volume_in_limit.setter def primary_volume_in_limit(self, value: float): self._property_changed('primary_volume_in_limit') self.__primary_volume_in_limit = value @property def precipitation_daily_forecast_percent(self) -> float: """The forecast value for precipitation in Percent.""" return self.__precipitation_daily_forecast_percent @precipitation_daily_forecast_percent.setter def precipitation_daily_forecast_percent(self, value: float): self._property_changed('precipitation_daily_forecast_percent') self.__precipitation_daily_forecast_percent = value @property def aum_end(self) -> float: """Assets under management at the end of the period.""" return self.__aum_end @aum_end.setter def aum_end(self, value: float): self._property_changed('aum_end') self.__aum_end = value @property def premium(self) -> float: """An indication of the market value at the time of execution.""" return self.__premium @premium.setter def premium(self, value: float): self._property_changed('premium') self.__premium = value @property def low(self) -> float: """Low level of an asset based on official exchange fixing or calculation agent marked level.""" return self.__low @low.setter def low(self, value: float): self._property_changed('low') self.__low = value @property def cross_group(self) -> str: """Economic cross groupings.""" return self.__cross_group @cross_group.setter def cross_group(self, value: str): self._property_changed('cross_group') self.__cross_group = value @property def report_run_time(self) -> datetime.datetime: """Time that the report was run.""" return self.__report_run_time @report_run_time.setter def report_run_time(self, value: datetime.datetime): self._property_changed('report_run_time') self.__report_run_time = value @property def five_day_price_change_bps(self) -> float: """The five day movement in price measured in basis points.""" return self.__five_day_price_change_bps @five_day_price_change_bps.setter def five_day_price_change_bps(self, value: float): self._property_changed('five_day_price_change_bps') self.__five_day_price_change_bps = value @property def holdings(self) -> float: """Number of units of a given asset held within a portfolio.""" return self.__holdings @holdings.setter def holdings(self, value: float): self._property_changed('holdings') self.__holdings = value @property def precipitation_daily_forecast(self) -> float: """The forecast value for precipitation.""" return self.__precipitation_daily_forecast @precipitation_daily_forecast.setter def precipitation_daily_forecast(self, value: float): self._property_changed('precipitation_daily_forecast') self.__precipitation_daily_forecast = value @property def price_method(self) -> str: """Method used to calculate net price.""" return self.__price_method @price_method.setter def price_method(self, value: str): self._property_changed('price_method') self.__price_method = value @property def asset_parameters_fixed_rate_frequency(self) -> str: """Tenor""" return self.__asset_parameters_fixed_rate_frequency @asset_parameters_fixed_rate_frequency.setter def asset_parameters_fixed_rate_frequency(self, value: str): self._property_changed('asset_parameters_fixed_rate_frequency') self.__asset_parameters_fixed_rate_frequency = value @property def ois_xccy(self) -> float: """Xccy OIS Spread rate.""" return self.__ois_xccy @ois_xccy.setter def ois_xccy(self, value: float): self._property_changed('ois_xccy') self.__ois_xccy = value @property def days_open(self) -> float: """Day Open price.""" return self.__days_open @days_open.setter def days_open(self, value: float): self._property_changed('days_open') self.__days_open = value @property def buy110cents(self) -> float: """The amount GS would buy for 110 cents charge.""" return self.__buy110cents @buy110cents.setter def buy110cents(self, value: float): self._property_changed('buy110cents') self.__buy110cents = value @property def average_spread_bps(self) -> float: """Time-weighted average bid/ask spread during the life of the order.""" return self.__average_spread_bps @average_spread_bps.setter def average_spread_bps(self, value: float): self._property_changed('average_spread_bps') self.__average_spread_bps = value @property def buy55cents(self) -> float: """The amount GS would buy for 55 cents charge.""" return self.__buy55cents @buy55cents.setter def buy55cents(self, value: float): self._property_changed('buy55cents') self.__buy55cents = value @property def future_month_q26(self) -> float: """Commods future month code.""" return self.__future_month_q26 @future_month_q26.setter def future_month_q26(self, value: float): self._property_changed('future_month_q26') self.__future_month_q26 = value @property def issue_size(self) -> float: """The notional issue size of the bond.""" return self.__issue_size @issue_size.setter def issue_size(self, value: float): self._property_changed('issue_size') self.__issue_size = value @property def future_month_q25(self) -> float: """Commods future month code.""" return self.__future_month_q25 @future_month_q25.setter def future_month_q25(self, value: float): self._property_changed('future_month_q25') self.__future_month_q25 = value @property def future_month_q24(self) -> float: """Commods future month code.""" return self.__future_month_q24 @future_month_q24.setter def future_month_q24(self, value: float): self._property_changed('future_month_q24') self.__future_month_q24 = value @property def future_month_q23(self) -> float: """Commods future month code.""" return self.__future_month_q23 @future_month_q23.setter def future_month_q23(self, value: float): self._property_changed('future_month_q23') self.__future_month_q23 = value @property def future_month_q22(self) -> float: """Commods future month code.""" return self.__future_month_q22 @future_month_q22.setter def future_month_q22(self, value: float): self._property_changed('future_month_q22') self.__future_month_q22 = value @property def pending_loan_count(self) -> float: """The number of pending loans that exist on a given date.""" return self.__pending_loan_count @pending_loan_count.setter def pending_loan_count(self, value: float): self._property_changed('pending_loan_count') self.__pending_loan_count = value @property def future_month_q21(self) -> float: """Commods future month code.""" return self.__future_month_q21 @future_month_q21.setter def future_month_q21(self, value: float): self._property_changed('future_month_q21') self.__future_month_q21 = value @property def price_spot_stop_loss_unit(self) -> str: """Unit in which the stop loss price is reported.""" return self.__price_spot_stop_loss_unit @price_spot_stop_loss_unit.setter def price_spot_stop_loss_unit(self, value: str): self._property_changed('price_spot_stop_loss_unit') self.__price_spot_stop_loss_unit = value @property def price_range_in_ticks_description(self) -> str: """Description of the Stock's Price Range in Ticks on the particular date.""" return self.__price_range_in_ticks_description @price_range_in_ticks_description.setter def price_range_in_ticks_description(self, value: str): self._property_changed('price_range_in_ticks_description') self.__price_range_in_ticks_description = value @property def trade_volume(self) -> float: """Market trade volume.""" return self.__trade_volume @trade_volume.setter def trade_volume(self, value: float): self._property_changed('trade_volume') self.__trade_volume = value @property def primary_country_ric(self) -> str: """Reuters primary country instrument code (subject to licensing).""" return self.__primary_country_ric @primary_country_ric.setter def primary_country_ric(self, value: str): self._property_changed('primary_country_ric') self.__primary_country_ric = value @property def option_expiration_frequency(self) -> str: """Option Expiration Frequency provided by Participant (e.g., Daily, Monthly).""" return self.__option_expiration_frequency @option_expiration_frequency.setter def option_expiration_frequency(self, value: str): self._property_changed('option_expiration_frequency') self.__option_expiration_frequency = value @property def is_active(self) -> bool: """Whether this entry is active.""" return self.__is_active @is_active.setter def is_active(self, value: bool): self._property_changed('is_active') self.__is_active = value @property def use_machine_learning(self) -> bool: """Use Machine learning.""" return self.__use_machine_learning @use_machine_learning.setter def use_machine_learning(self, value: bool): self._property_changed('use_machine_learning') self.__use_machine_learning = value @property def growth_score(self) -> float: """Growth percentile relative to Americas coverage universe (a higher score means faster growth).""" return self.__growth_score @growth_score.setter def growth_score(self, value: float): self._property_changed('growth_score') self.__growth_score = value @property def buffer_threshold(self) -> float: """The buffer between holdings and on loan quantity for an asset.""" return self.__buffer_threshold @buffer_threshold.setter def buffer_threshold(self, value: float): self._property_changed('buffer_threshold') self.__buffer_threshold = value @property def buy120cents(self) -> float: """The amount GS would buy for 120 cents charge.""" return self.__buy120cents @buy120cents.setter def buy120cents(self, value: float): self._property_changed('buy120cents') self.__buy120cents = value @property def matched_maturity_swap_rate(self) -> float: """Matched maturity swap rate.""" return self.__matched_maturity_swap_rate @matched_maturity_swap_rate.setter def matched_maturity_swap_rate(self, value: float): self._property_changed('matched_maturity_swap_rate') self.__matched_maturity_swap_rate = value @property def primary_vwap(self) -> float: """Primary VWAP benchmark price.""" return self.__primary_vwap @primary_vwap.setter def primary_vwap(self, value: float): self._property_changed('primary_vwap') self.__primary_vwap = value @property def exchange_type_id(self) -> str: """PadMaster unique exchange type identifier.""" return self.__exchange_type_id @exchange_type_id.setter def exchange_type_id(self, value: str): self._property_changed('exchange_type_id') self.__exchange_type_id = value @property def basis_swap_rate(self) -> float: """Fixed spread or basis that equates the two floating legs of a basis swap at the time of the trade.""" return self.__basis_swap_rate @basis_swap_rate.setter def basis_swap_rate(self, value: float): self._property_changed('basis_swap_rate') self.__basis_swap_rate = value @property def exchange_code(self) -> str: """EEX Exchange Code.""" return self.__exchange_code @exchange_code.setter def exchange_code(self, value: str): self._property_changed('exchange_code') self.__exchange_code = value @property def group(self) -> str: """Region or sector following the MSCI Global Industry Classification Standard (GICS).""" return self.__group @group.setter def group(self, value: str): self._property_changed('group') self.__group = value @property def asset_parameters_termination_date(self) -> str: """Relative termination date.""" return self.__asset_parameters_termination_date @asset_parameters_termination_date.setter def asset_parameters_termination_date(self, value: str): self._property_changed('asset_parameters_termination_date') self.__asset_parameters_termination_date = value @property def estimated_spread(self) -> float: """Average bid-ask quoted spread of the stock (bps) over the execution horizon (1 day).""" return self.__estimated_spread @estimated_spread.setter def estimated_spread(self, value: float): self._property_changed('estimated_spread') self.__estimated_spread = value @property def yield_change_on_day(self) -> float: """Yield change on day.""" return self.__yield_change_on_day @yield_change_on_day.setter def yield_change_on_day(self, value: float): self._property_changed('yield_change_on_day') self.__yield_change_on_day = value @property def created(self) -> datetime.datetime: """Created time.""" return self.__created @created.setter def created(self, value: datetime.datetime): self._property_changed('created') self.__created = value @property def auto_tags(self) -> Tuple[str, ...]: """Metadata associated with the object""" return self.__auto_tags @auto_tags.setter def auto_tags(self, value: Tuple[str, ...]): self._property_changed('auto_tags') self.__auto_tags = value @property def tcm_cost(self) -> float: """Pretrade computation of trading out cost.""" return self.__tcm_cost @tcm_cost.setter def tcm_cost(self, value: float): self._property_changed('tcm_cost') self.__tcm_cost = value @property def sustain_japan(self) -> bool: """True if the stock is on the SUSTAIN Japan list as of the corresponding date. False if the stock is removed from the SUSTAIN Japan list on the corresponding date.""" return self.__sustain_japan @sustain_japan.setter def sustain_japan(self, value: bool): self._property_changed('sustain_japan') self.__sustain_japan = value @property def history_start_date(self) -> datetime.date: """Asset history start date.""" return self.__history_start_date @history_start_date.setter def history_start_date(self, value: datetime.date): self._property_changed('history_start_date') self.__history_start_date = value @property def bid_spread(self) -> float: """Spread between the yields of a debt security and its benchmark when both are purchased at bid price.""" return self.__bid_spread @bid_spread.setter def bid_spread(self, value: float): self._property_changed('bid_spread') self.__bid_spread = value @property def percentage_complete(self) -> float: """Percent Complete.""" return self.__percentage_complete @percentage_complete.setter def percentage_complete(self, value: float): self._property_changed('percentage_complete') self.__percentage_complete = value @property def hedge_tracking_error(self) -> float: """Standard deviation of the difference in the portfolio and benchmark returns over time.""" return self.__hedge_tracking_error @hedge_tracking_error.setter def hedge_tracking_error(self, value: float): self._property_changed('hedge_tracking_error') self.__hedge_tracking_error = value @property def wind_speed_type(self) -> str: """The hourly or average speed of wind.""" return self.__wind_speed_type @wind_speed_type.setter def wind_speed_type(self, value: str): self._property_changed('wind_speed_type') self.__wind_speed_type = value @property def strike_price(self) -> float: """Strike price.""" return self.__strike_price @strike_price.setter def strike_price(self, value: float): self._property_changed('strike_price') self.__strike_price = value @property def par_asset_swap_spread12m(self) -> float: """Par asset swap spread vs 12m tenor.""" return self.__par_asset_swap_spread12m @par_asset_swap_spread12m.setter def par_asset_swap_spread12m(self, value: float): self._property_changed('par_asset_swap_spread12m') self.__par_asset_swap_spread12m = value @property def trade_report_id(self) -> str: """DDR generated unique and random ID for reconciliation purpose.""" return self.__trade_report_id @trade_report_id.setter def trade_report_id(self, value: str): self._property_changed('trade_report_id') self.__trade_report_id = value @property def adjusted_open_price(self) -> float: """Opening level of an asset based on official exchange fixing or calculation agent marked level adjusted for corporate actions.""" return self.__adjusted_open_price @adjusted_open_price.setter def adjusted_open_price(self, value: float): self._property_changed('adjusted_open_price') self.__adjusted_open_price = value @property def country_id(self) -> str: """Marquee country object identifier.""" return self.__country_id @country_id.setter def country_id(self, value: str): self._property_changed('country_id') self.__country_id = value @property def point(self) -> str: """MDAPI point.""" return self.__point @point.setter def point(self, value: str): self._property_changed('point') self.__point = value @property def pnl_mtd(self) -> float: """Total PnL Month to date.""" return self.__pnl_mtd @pnl_mtd.setter def pnl_mtd(self, value: float): self._property_changed('pnl_mtd') self.__pnl_mtd = value @property def total_returns(self) -> float: """Total returns for backtest.""" return self.__total_returns @total_returns.setter def total_returns(self, value: float): self._property_changed('total_returns') self.__total_returns = value @property def lender(self) -> str: """Name of the lending entity on a securities lending agreement.""" return self.__lender @lender.setter def lender(self, value: str): self._property_changed('lender') self.__lender = value @property def ann_return1_year(self) -> float: """Total return representing past performance, used for GS Money Market onshore funds over one year.""" return self.__ann_return1_year @ann_return1_year.setter def ann_return1_year(self, value: float): self._property_changed('ann_return1_year') self.__ann_return1_year = value @property def ctd_fwd_dv01(self) -> float: """DV01 of cheapest to deliver bond.""" return self.__ctd_fwd_dv01 @ctd_fwd_dv01.setter def ctd_fwd_dv01(self, value: float): self._property_changed('ctd_fwd_dv01') self.__ctd_fwd_dv01 = value @property def eff_yield7_day(self) -> float: """Average income return over the previous 7 days reduced by any capital gains that may have been included in rate calculation, assuming the rate stays the same for one year and that dividends are reinvested.""" return self.__eff_yield7_day @eff_yield7_day.setter def eff_yield7_day(self, value: float): self._property_changed('eff_yield7_day') self.__eff_yield7_day = value @property def meeting_date(self) -> str: """Date the Central bank Meeting took place.""" return self.__meeting_date @meeting_date.setter def meeting_date(self, value: str): self._property_changed('meeting_date') self.__meeting_date = value @property def calendar_spread_mispricing(self) -> float: """The richness/cheapness of the calendar spread relative to Libor.""" return self.__calendar_spread_mispricing @calendar_spread_mispricing.setter def calendar_spread_mispricing(self, value: float): self._property_changed('calendar_spread_mispricing') self.__calendar_spread_mispricing = value @property def buy140cents(self) -> float: """The amount GS would buy for 140 cents charge.""" return self.__buy140cents @buy140cents.setter def buy140cents(self, value: float): self._property_changed('buy140cents') self.__buy140cents = value @property def price_notation2_type(self) -> str: """Basis points, Price, Yield, Spread, Coupon, etc., depending on the type of SB swap, which is calculated at affirmation.""" return self.__price_notation2_type @price_notation2_type.setter def price_notation2_type(self, value: str): self._property_changed('price_notation2_type') self.__price_notation2_type = value @property def fund_focus(self) -> str: """Focus of the fund. For example whether it is ESG or Non-ESG.""" return self.__fund_focus @fund_focus.setter def fund_focus(self, value: str): self._property_changed('fund_focus') self.__fund_focus = value @property def relative_strike(self) -> float: """Strike relative to spot or forward level in terms of percent of either spot or forward level.""" return self.__relative_strike @relative_strike.setter def relative_strike(self, value: float): self._property_changed('relative_strike') self.__relative_strike = value @property def flagship(self) -> bool: """Whether or not it is a flagship basket.""" return self.__flagship @flagship.setter def flagship(self, value: bool): self._property_changed('flagship') self.__flagship = value @property def additional_price_notation(self) -> float: """The additional price notation value includes execution events, the presence of collateral, frontend payments, back-end payments, or other noneconomic characteristics (e.g. counterparty credit risk) not illustrated in the reporting field for pricing characteristic.""" return self.__additional_price_notation @additional_price_notation.setter def additional_price_notation(self, value: float): self._property_changed('additional_price_notation') self.__additional_price_notation = value @property def factor_category(self) -> str: """The text to display when representing a factor category. Must match the regex ^[\w -]{0,32}$.""" return self.__factor_category @factor_category.setter def factor_category(self, value: str): self._property_changed('factor_category') self.__factor_category = value @property def equity_delta(self) -> float: """Delta exposure to equity products.""" return self.__equity_delta @equity_delta.setter def equity_delta(self, value: float): self._property_changed('equity_delta') self.__equity_delta = value @property def gross_weight(self) -> float: """Sum of the absolute weight values, which equals the sum of absolute long and short weights. If you have IBM stock with shortWeight 0.2 and also IBM stock with longWeight 0.4, then the grossWeight would be 0.6 (0.2+0.4).""" return self.__gross_weight @gross_weight.setter def gross_weight(self, value: float): self._property_changed('gross_weight') self.__gross_weight = value @property def listed(self) -> bool: """Whether the asset is listed or not.""" return self.__listed @listed.setter def listed(self, value: bool): self._property_changed('listed') self.__listed = value @property def sell7bps(self) -> float: """The amount GS would sell for 7 bps charge.""" return self.__sell7bps @sell7bps.setter def sell7bps(self, value: float): self._property_changed('sell7bps') self.__sell7bps = value @property def earnings_record_type(self) -> str: """The violation status for this particular line item.""" return self.__earnings_record_type @earnings_record_type.setter def earnings_record_type(self, value: str): self._property_changed('earnings_record_type') self.__earnings_record_type = value @property def mean(self) -> float: """Calculated average.""" return self.__mean @mean.setter def mean(self, value: float): self._property_changed('mean') self.__mean = value @property def ask_yield(self) -> float: """The return an investor realizes on a bond sold at the ask price.""" return self.__ask_yield @ask_yield.setter def ask_yield(self, value: float): self._property_changed('ask_yield') self.__ask_yield = value @property def shock_style(self) -> str: """Style of shocks to be used.""" return self.__shock_style @shock_style.setter def shock_style(self, value: str): self._property_changed('shock_style') self.__shock_style = value @property def methodology(self) -> str: """Methodology of dataset.""" return self.__methodology @methodology.setter def methodology(self, value: str): self._property_changed('methodology') self.__methodology = value @property def buy25cents(self) -> float: """The amount GS would buy for 25 cents charge.""" return self.__buy25cents @buy25cents.setter def buy25cents(self, value: float): self._property_changed('buy25cents') self.__buy25cents = value @property def amount_outstanding(self) -> float: """The aggregate principal amount of the total number of bonds not redeemed or otherwise discharged.""" return self.__amount_outstanding @amount_outstanding.setter def amount_outstanding(self, value: float): self._property_changed('amount_outstanding') self.__amount_outstanding = value @property def market_pnl(self) -> float: """Market Profit and Loss (PNL).""" return self.__market_pnl @market_pnl.setter def market_pnl(self, value: float): self._property_changed('market_pnl') self.__market_pnl = value @property def sustain_asia_ex_japan(self) -> bool: """True if the stock is on the SUSTAIN Asia Ex Japan list as of the corresponding date. False if the stock is removed from the SUSTAIN Asia Ex Japan list on the corresponding date.""" return self.__sustain_asia_ex_japan @sustain_asia_ex_japan.setter def sustain_asia_ex_japan(self, value: bool): self._property_changed('sustain_asia_ex_japan') self.__sustain_asia_ex_japan = value @property def sell6point5bps(self) -> float: """The amount GS would sell for 6.5 bps charge.""" return self.__sell6point5bps @sell6point5bps.setter def sell6point5bps(self, value: float): self._property_changed('sell6point5bps') self.__sell6point5bps = value @property def neighbour_asset_id(self) -> str: """Marquee identifier for the corresponding neighbour.""" return self.__neighbour_asset_id @neighbour_asset_id.setter def neighbour_asset_id(self, value: str): self._property_changed('neighbour_asset_id') self.__neighbour_asset_id = value @property def count_ideas_ytd(self) -> float: """Ideas alive at a time Year to date.""" return self.__count_ideas_ytd @count_ideas_ytd.setter def count_ideas_ytd(self, value: float): self._property_changed('count_ideas_ytd') self.__count_ideas_ytd = value @property def simon_intl_asset_tags(self) -> Tuple[str, ...]: """SIMON International Asset Tags.""" return self.__simon_intl_asset_tags @simon_intl_asset_tags.setter def simon_intl_asset_tags(self, value: Tuple[str, ...]): self._property_changed('simon_intl_asset_tags') self.__simon_intl_asset_tags = value @property def path(self) -> str: """Path to value.""" return self.__path @path.setter def path(self, value: str): self._property_changed('path') self.__path = value @property def vwap_unrealized_cash(self) -> float: """Consolidated unrealised performance vs VWAP, in USD.""" return self.__vwap_unrealized_cash @vwap_unrealized_cash.setter def vwap_unrealized_cash(self, value: float): self._property_changed('vwap_unrealized_cash') self.__vwap_unrealized_cash = value @property def payoff_mtd(self) -> float: """Total win divided by total loss Month to date.""" return self.__payoff_mtd @payoff_mtd.setter def payoff_mtd(self, value: float): self._property_changed('payoff_mtd') self.__payoff_mtd = value @property def bos_in_bps_label(self): return self.__bos_in_bps_label @bos_in_bps_label.setter def bos_in_bps_label(self, value): self._property_changed('bos_in_bps_label') self.__bos_in_bps_label = value @property def bos_in_bps(self) -> float: """The Bid-Offer Spread of the stock in Basis points on the particular date.""" return self.__bos_in_bps @bos_in_bps.setter def bos_in_bps(self, value: float): self._property_changed('bos_in_bps') self.__bos_in_bps = value @property def point_class(self) -> str: """MDAPI Class.""" return self.__point_class @point_class.setter def point_class(self, value: str): self._property_changed('point_class') self.__point_class = value @property def fx_spot(self) -> float: """FX spot rate as determined by fixing source.""" return self.__fx_spot @fx_spot.setter def fx_spot(self, value: float): self._property_changed('fx_spot') self.__fx_spot = value @property def restrict_named_individuals(self) -> bool: """Restricts Visbility to Named Individuals.""" return self.__restrict_named_individuals @restrict_named_individuals.setter def restrict_named_individuals(self, value: bool): self._property_changed('restrict_named_individuals') self.__restrict_named_individuals = value @property def hedge_volatility(self) -> float: """Standard deviation of the annualized returns.""" return self.__hedge_volatility @hedge_volatility.setter def hedge_volatility(self, value: float): self._property_changed('hedge_volatility') self.__hedge_volatility = value @property def tags(self) -> Tuple[str, ...]: """Metadata associated with the object""" return self.__tags @tags.setter def tags(self, value: Tuple[str, ...]): self._property_changed('tags') self.__tags = value @property def population(self) -> float: """Total population of a country, state or subdivision.""" return self.__population @population.setter def population(self, value: float): self._property_changed('population') self.__population = value @property def underlying_asset_id(self) -> str: """Marquee identifier for constituents of an index or portfolio.""" return self.__underlying_asset_id @underlying_asset_id.setter def underlying_asset_id(self, value: str): self._property_changed('underlying_asset_id') self.__underlying_asset_id = value @property def real_long_rates_contribution(self) -> float: """Contribution of long rate component to real FCI.""" return self.__real_long_rates_contribution @real_long_rates_contribution.setter def real_long_rates_contribution(self, value: float): self._property_changed('real_long_rates_contribution') self.__real_long_rates_contribution = value @property def pctprices_return(self) -> float: """The pctprices(return) function.""" return self.__pctprices_return @pctprices_return.setter def pctprices_return(self, value: float): self._property_changed('pctprices_return') self.__pctprices_return = value @property def domain(self) -> str: """Domain that request came from.""" return self.__domain @domain.setter def domain(self, value: str): self._property_changed('domain') self.__domain = value @property def buy80cents(self) -> float: """The amount GS would buy for 80 cents charge.""" return self.__buy80cents @buy80cents.setter def buy80cents(self, value: float): self._property_changed('buy80cents') self.__buy80cents = value @property def forward_tenor(self) -> str: """Start of swap after option expiry.""" return self.__forward_tenor @forward_tenor.setter def forward_tenor(self, value: str): self._property_changed('forward_tenor') self.__forward_tenor = value @property def average_price(self) -> float: """Average execution price.""" return self.__average_price @average_price.setter def average_price(self, value: float): self._property_changed('average_price') self.__average_price = value @property def target_price_realized_bps(self) -> float: """Realised performance vs Target Price, in bps.""" return self.__target_price_realized_bps @target_price_realized_bps.setter def target_price_realized_bps(self, value: float): self._property_changed('target_price_realized_bps') self.__target_price_realized_bps = value @property def leg2_fixed_rate(self) -> float: """If fixed rate leg, the fixed rate of leg 2.""" return self.__leg2_fixed_rate @leg2_fixed_rate.setter def leg2_fixed_rate(self, value: float): self._property_changed('leg2_fixed_rate') self.__leg2_fixed_rate = value @property def share_class_assets(self) -> float: """Total amount of assets under management in this shareclass.""" return self.__share_class_assets @share_class_assets.setter def share_class_assets(self, value: float): self._property_changed('share_class_assets') self.__share_class_assets = value @property def annuity(self) -> float: """Local currency annuity.""" return self.__annuity @annuity.setter def annuity(self, value: float): self._property_changed('annuity') self.__annuity = value @property def total_count(self) -> float: """Number of entities in the test.""" return self.__total_count @total_count.setter def total_count(self, value: float): self._property_changed('total_count') self.__total_count = value @property def quote_type(self) -> str: """Quote Type.""" return self.__quote_type @quote_type.setter def quote_type(self, value: str): self._property_changed('quote_type') self.__quote_type = value @property def corporate_action_status(self) -> str: """Different statuses for corporate actions from solactive""" return self.__corporate_action_status @corporate_action_status.setter def corporate_action_status(self, value: str): self._property_changed('corporate_action_status') self.__corporate_action_status = value @property def pegged_tip_size(self) -> float: """Pegged order tip size in terms of the quantity unit.""" return self.__pegged_tip_size @pegged_tip_size.setter def pegged_tip_size(self, value: float): self._property_changed('pegged_tip_size') self.__pegged_tip_size = value @property def uid(self) -> str: """Two-digit code for countries and regions for which FCI numbers are represented. For countries it is the ISO 3166 2-digit country code. Regions are denoted as DM (Developed Markets), EM (Emerging Markets), EA (Euro Area) and GL (Global).""" return self.__uid @uid.setter def uid(self, value: str): self._property_changed('uid') self.__uid = value @property def es_policy_percentile(self) -> float: """A percentile that captures a company's E&S policy ranking within its subsector.""" return self.__es_policy_percentile @es_policy_percentile.setter def es_policy_percentile(self, value: float): self._property_changed('es_policy_percentile') self.__es_policy_percentile = value @property def usd_ois(self) -> float: """USD Ois rate.""" return self.__usd_ois @usd_ois.setter def usd_ois(self, value: float): self._property_changed('usd_ois') self.__usd_ois = value @property def term(self) -> str: """Allowed risk model terms""" return self.__term @term.setter def term(self, value: str): self._property_changed('term') self.__term = value @property def restrict_internal_gs_ntk(self) -> bool: """Restricts Internal Visibility to Data Beyond GS NTK.""" return self.__restrict_internal_gs_ntk @restrict_internal_gs_ntk.setter def restrict_internal_gs_ntk(self, value: bool): self._property_changed('restrict_internal_gs_ntk') self.__restrict_internal_gs_ntk = value @property def tcm_cost_participation_rate100_pct(self) -> float: """TCM cost with a 100 percent participation rate.""" return self.__tcm_cost_participation_rate100_pct @tcm_cost_participation_rate100_pct.setter def tcm_cost_participation_rate100_pct(self, value: float): self._property_changed('tcm_cost_participation_rate100_pct') self.__tcm_cost_participation_rate100_pct = value @property def relative_universe(self) -> str: """Universe relative to which the metric is computed.""" return self.__relative_universe @relative_universe.setter def relative_universe(self, value: str): self._property_changed('relative_universe') self.__relative_universe = value @property def measure_idx(self) -> int: """The index of the corresponding measure in the risk request.""" return self.__measure_idx @measure_idx.setter def measure_idx(self, value: int): self._property_changed('measure_idx') self.__measure_idx = value @property def fred_id(self) -> str: """FRED series identifier.""" return self.__fred_id @fred_id.setter def fred_id(self, value: str): self._property_changed('fred_id') self.__fred_id = value @property def twi_contribution(self) -> float: """Contribution of trade weighted exchange rate index component to FCI.""" return self.__twi_contribution @twi_contribution.setter def twi_contribution(self, value: float): self._property_changed('twi_contribution') self.__twi_contribution = value @property def cloud_cover_type(self) -> str: """The type of cloud cover: Average etc.""" return self.__cloud_cover_type @cloud_cover_type.setter def cloud_cover_type(self, value: str): self._property_changed('cloud_cover_type') self.__cloud_cover_type = value @property def delisted(self) -> str: """Whether the security has been delisted.""" return self.__delisted @delisted.setter def delisted(self, value: str): self._property_changed('delisted') self.__delisted = value @property def regional_focus(self) -> str: """Section of the world a fund is focused on from an investment perspective. Same view permissions as the asset.""" return self.__regional_focus @regional_focus.setter def regional_focus(self, value: str): self._property_changed('regional_focus') self.__regional_focus = value @property def volume_primary(self) -> float: """Accumulated number of shares, lots or contracts traded according to the market convention at the primary exchange.""" return self.__volume_primary @volume_primary.setter def volume_primary(self, value: float): self._property_changed('volume_primary') self.__volume_primary = value @property def asset_parameters_payer_designated_maturity(self) -> str: """Tenor""" return self.__asset_parameters_payer_designated_maturity @asset_parameters_payer_designated_maturity.setter def asset_parameters_payer_designated_maturity(self, value: str): self._property_changed('asset_parameters_payer_designated_maturity') self.__asset_parameters_payer_designated_maturity = value @property def buy30cents(self) -> float: """The amount GS would buy for 30 cents charge.""" return self.__buy30cents @buy30cents.setter def buy30cents(self, value: float): self._property_changed('buy30cents') self.__buy30cents = value @property def funding_bid_price(self) -> float: """Latest Bid Price (price willing to buy).""" return self.__funding_bid_price @funding_bid_price.setter def funding_bid_price(self, value: float): self._property_changed('funding_bid_price') self.__funding_bid_price = value @property def series(self) -> str: """Series.""" return self.__series @series.setter def series(self, value: str): self._property_changed('series') self.__series = value @property def sell3bps(self) -> float: """The amount GS would sell for 3 bps charge.""" return self.__sell3bps @sell3bps.setter def sell3bps(self, value: float): self._property_changed('sell3bps') self.__sell3bps = value @property def settlement_price(self) -> float: """Settlement Price.""" return self.__settlement_price @settlement_price.setter def settlement_price(self, value: float): self._property_changed('settlement_price') self.__settlement_price = value @property def quarter(self) -> str: """Quarter of forecast.""" return self.__quarter @quarter.setter def quarter(self, value: str): self._property_changed('quarter') self.__quarter = value @property def sell18bps(self) -> float: """The amount GS would sell for 18 bps charge.""" return self.__sell18bps @sell18bps.setter def sell18bps(self, value: float): self._property_changed('sell18bps') self.__sell18bps = value @property def asset_parameters_floating_rate_option(self) -> str: """The underlying benchmark for the floating rate, e.g. USD-LIBOR-BBA, EUR-EURIBOR- TELERATE.""" return self.__asset_parameters_floating_rate_option @asset_parameters_floating_rate_option.setter def asset_parameters_floating_rate_option(self, value: str): self._property_changed('asset_parameters_floating_rate_option') self.__asset_parameters_floating_rate_option = value @property def realized_vwap_performance_bps(self) -> float: """Average execution price vs Consolidated VWAP (in limit) since order inception ??? realized.""" return self.__realized_vwap_performance_bps @realized_vwap_performance_bps.setter def realized_vwap_performance_bps(self, value: float): self._property_changed('realized_vwap_performance_bps') self.__realized_vwap_performance_bps = value @property def vote_share(self) -> float: """Vote share (predicted) in election.""" return self.__vote_share @vote_share.setter def vote_share(self, value: float): self._property_changed('vote_share') self.__vote_share = value @property def servicing_cost_short_pnl(self) -> float: """Servicing Cost Short Profit and Loss.""" return self.__servicing_cost_short_pnl @servicing_cost_short_pnl.setter def servicing_cost_short_pnl(self, value: float): self._property_changed('servicing_cost_short_pnl') self.__servicing_cost_short_pnl = value @property def total_confirmed(self) -> float: """Total number of confirmed cases.""" return self.__total_confirmed @total_confirmed.setter def total_confirmed(self, value: float): self._property_changed('total_confirmed') self.__total_confirmed = value @property def economic_forecast(self) -> float: """Forecast value.""" return self.__economic_forecast @economic_forecast.setter def economic_forecast(self, value: float): self._property_changed('economic_forecast') self.__economic_forecast = value @property def plot_id(self) -> str: """Plot Identifier.""" return self.__plot_id @plot_id.setter def plot_id(self, value: str): self._property_changed('plot_id') self.__plot_id = value @property def cluster_description(self) -> str: """Description of the Cluster characteristics.""" return self.__cluster_description @cluster_description.setter def cluster_description(self, value: str): self._property_changed('cluster_description') self.__cluster_description = value @property def concentration_limit(self) -> float: """The allowable percentage of the outstanding loan balance of a lenders book that can be lent to/in a particular borrower/market/etc.""" return self.__concentration_limit @concentration_limit.setter def concentration_limit(self, value: float): self._property_changed('concentration_limit') self.__concentration_limit = value @property def wind_speed(self) -> float: """Average wind speed in knots.""" return self.__wind_speed @wind_speed.setter def wind_speed(self, value: float): self._property_changed('wind_speed') self.__wind_speed = value @property def observation_hour(self) -> str: """The observation hour for hourly weather forecast.""" return self.__observation_hour @observation_hour.setter def observation_hour(self, value: str): self._property_changed('observation_hour') self.__observation_hour = value @property def signal(self) -> float: """Signal.""" return self.__signal @signal.setter def signal(self, value: float): self._property_changed('signal') self.__signal = value @property def borrower_id(self) -> str: """Id of the borrowing entity on a securities lending agreement.""" return self.__borrower_id @borrower_id.setter def borrower_id(self, value: str): self._property_changed('borrower_id') self.__borrower_id = value @property def data_product(self) -> str: """Product that dataset belongs to.""" return self.__data_product @data_product.setter def data_product(self, value: str): self._property_changed('data_product') self.__data_product = value @property def buy7point5bps(self) -> float: """The amount GS would buy for 7.5 bps charge.""" return self.__buy7point5bps @buy7point5bps.setter def buy7point5bps(self, value: float): self._property_changed('buy7point5bps') self.__buy7point5bps = value @property def limit_price(self) -> float: """Limit price of the order.""" return self.__limit_price @limit_price.setter def limit_price(self, value: float): self._property_changed('limit_price') self.__limit_price = value @property def bm_prime_id(self) -> float: """Benchmark prime ID of the treasury.""" return self.__bm_prime_id @bm_prime_id.setter def bm_prime_id(self, value: float): self._property_changed('bm_prime_id') self.__bm_prime_id = value @property def data_type(self) -> str: """The type of data for Power or Load. Values may include Actual, Forecast.""" return self.__data_type @data_type.setter def data_type(self, value: str): self._property_changed('data_type') self.__data_type = value @property def count(self) -> float: """Total number of a collection.""" return self.__count @count.setter def count(self, value: float): self._property_changed('count') self.__count = value @property def conviction(self) -> str: """Confidence level in the trade idea.""" return self.__conviction @conviction.setter def conviction(self, value: str): self._property_changed('conviction') self.__conviction = value @property def rfqstate(self) -> str: """RFQ State.""" return self.__rfqstate @rfqstate.setter def rfqstate(self, value: str): self._property_changed('rfqstate') self.__rfqstate = value @property def benchmark_maturity(self) -> str: """The benchmark tenor.""" return self.__benchmark_maturity @benchmark_maturity.setter def benchmark_maturity(self, value: str): self._property_changed('benchmark_maturity') self.__benchmark_maturity = value @property def gross_flow_normalized(self) -> float: """Gross flow for the asset divided by average in defined history.""" return self.__gross_flow_normalized @gross_flow_normalized.setter def gross_flow_normalized(self, value: float): self._property_changed('gross_flow_normalized') self.__gross_flow_normalized = value @property def buy14bps(self) -> float: """The amount GS would buy for 14 bps charge.""" return self.__buy14bps @buy14bps.setter def buy14bps(self, value: float): self._property_changed('buy14bps') self.__buy14bps = value @property def factor_id(self) -> str: """Id for Factors.""" return self.__factor_id @factor_id.setter def factor_id(self, value: str): self._property_changed('factor_id') self.__factor_id = value @property def future_month_v26(self) -> float: """Commods future month code.""" return self.__future_month_v26 @future_month_v26.setter def future_month_v26(self, value: float): self._property_changed('future_month_v26') self.__future_month_v26 = value @property def sts_fx_currency(self) -> str: """Currency of underlying FX risk for STS assets.""" return self.__sts_fx_currency @sts_fx_currency.setter def sts_fx_currency(self, value: str): self._property_changed('sts_fx_currency') self.__sts_fx_currency = value @property def future_month_v25(self) -> float: """Commods future month code.""" return self.__future_month_v25 @future_month_v25.setter def future_month_v25(self, value: float): self._property_changed('future_month_v25') self.__future_month_v25 = value @property def bid_change(self) -> float: """Change in BID price.""" return self.__bid_change @bid_change.setter def bid_change(self, value: float): self._property_changed('bid_change') self.__bid_change = value @property def month(self) -> str: """Month in YYYYMM format.""" return self.__month @month.setter def month(self, value: str): self._property_changed('month') self.__month = value @property def future_month_v24(self) -> float: """Commods future month code.""" return self.__future_month_v24 @future_month_v24.setter def future_month_v24(self, value: float): self._property_changed('future_month_v24') self.__future_month_v24 = value @property def investment_wtd(self) -> float: """Total net investment Week to date.""" return self.__investment_wtd @investment_wtd.setter def investment_wtd(self, value: float): self._property_changed('investment_wtd') self.__investment_wtd = value @property def future_month_v23(self) -> float: """Commods future month code.""" return self.__future_month_v23 @future_month_v23.setter def future_month_v23(self, value: float): self._property_changed('future_month_v23') self.__future_month_v23 = value @property def future_month_v22(self) -> float: """Commods future month code.""" return self.__future_month_v22 @future_month_v22.setter def future_month_v22(self, value: float): self._property_changed('future_month_v22') self.__future_month_v22 = value @property def future_month_v21(self) -> float: """Commods future month code.""" return self.__future_month_v21 @future_month_v21.setter def future_month_v21(self, value: float): self._property_changed('future_month_v21') self.__future_month_v21 = value @property def expiration(self) -> str: """The expiration date of the associated contract and the last date it trades.""" return self.__expiration @expiration.setter def expiration(self, value: str): self._property_changed('expiration') self.__expiration = value @property def leg2_reset_frequency(self) -> str: """An integer multiplier of a period describing how often the parties to an SB swap transaction shall evaluate and, when applicable, change the price used for the underlying assets of the swap transaction (leg 2). Such reset frequency may be described as one letter preceded by an integer.""" return self.__leg2_reset_frequency @leg2_reset_frequency.setter def leg2_reset_frequency(self, value: str): self._property_changed('leg2_reset_frequency') self.__leg2_reset_frequency = value @property def controversy_score(self) -> float: """A company's score for controversy metrics.""" return self.__controversy_score @controversy_score.setter def controversy_score(self, value: float): self._property_changed('controversy_score') self.__controversy_score = value @property def proceed_asset_swap_spread(self) -> float: """Spread between a Proceed Swap Rate and the yield of the bond.""" return self.__proceed_asset_swap_spread @proceed_asset_swap_spread.setter def proceed_asset_swap_spread(self, value: float): self._property_changed('proceed_asset_swap_spread') self.__proceed_asset_swap_spread = value @property def concentration_level(self) -> float: """The percentage of the outstanding loan balance of a lenders book that is being lent to/in a particular borrower/market/etc.""" return self.__concentration_level @concentration_level.setter def concentration_level(self, value: float): self._property_changed('concentration_level') self.__concentration_level = value @property def importance(self) -> float: """Importance.""" return self.__importance @importance.setter def importance(self, value: float): self._property_changed('importance') self.__importance = value @property def asset_classifications_gics_sector(self) -> str: """GICS Sector classification (level 1).""" return self.__asset_classifications_gics_sector @asset_classifications_gics_sector.setter def asset_classifications_gics_sector(self, value: str): self._property_changed('asset_classifications_gics_sector') self.__asset_classifications_gics_sector = value @property def sts_asset_name(self) -> str: """Name of risk asset for STS underliers.""" return self.__sts_asset_name @sts_asset_name.setter def sts_asset_name(self, value: str): self._property_changed('sts_asset_name') self.__sts_asset_name = value @property def net_exposure_classification(self) -> str: """Classification for net exposure of fund.""" return self.__net_exposure_classification @net_exposure_classification.setter def net_exposure_classification(self, value: str): self._property_changed('net_exposure_classification') self.__net_exposure_classification = value @property def settlement_method(self) -> str: """Settlement method of the swap.""" return self.__settlement_method @settlement_method.setter def settlement_method(self, value: str): self._property_changed('settlement_method') self.__settlement_method = value @property def receiver_designated_maturity(self) -> str: """Tenor""" return self.__receiver_designated_maturity @receiver_designated_maturity.setter def receiver_designated_maturity(self, value: str): self._property_changed('receiver_designated_maturity') self.__receiver_designated_maturity = value @property def title(self) -> str: """Title of series.""" return self.__title @title.setter def title(self, value: str): self._property_changed('title') self.__title = value @property def x_ref_type_id(self) -> str: """PadMaster unique xref type identifier.""" return self.__x_ref_type_id @x_ref_type_id.setter def x_ref_type_id(self, value: str): self._property_changed('x_ref_type_id') self.__x_ref_type_id = value @property def duration(self) -> str: """Candle duration. Supported values are??1m, 1h, 1d etc.""" return self.__duration @duration.setter def duration(self, value: str): self._property_changed('duration') self.__duration = value @property def load(self) -> float: """Quantity of power in the electrical grid.""" return self.__load @load.setter def load(self, value: float): self._property_changed('load') self.__load = value @property def alpha(self) -> float: """Alpha.""" return self.__alpha @alpha.setter def alpha(self, value: float): self._property_changed('alpha') self.__alpha = value @property def company(self) -> str: """Activity user company.""" return self.__company @company.setter def company(self, value: str): self._property_changed('company') self.__company = value @property def settlement_frequency(self) -> str: """Settlement Frequency provided by Participant (e.g., Monthly, Daily).""" return self.__settlement_frequency @settlement_frequency.setter def settlement_frequency(self, value: str): self._property_changed('settlement_frequency') self.__settlement_frequency = value @property def dist_avg7_day(self) -> float: """Goldman custom calculated value, only used for GS onshore Money Market Funds, assumes sum of the past 7 days divided by 7 and expressed as a percent.""" return self.__dist_avg7_day @dist_avg7_day.setter def dist_avg7_day(self, value: float): self._property_changed('dist_avg7_day') self.__dist_avg7_day = value @property def in_risk_model(self) -> bool: """Whether or not the asset is in the risk model universe.""" return self.__in_risk_model @in_risk_model.setter def in_risk_model(self, value: bool): self._property_changed('in_risk_model') self.__in_risk_model = value @property def daily_net_shareholder_flows_percent(self) -> float: """Percent of assets paid daily.""" return self.__daily_net_shareholder_flows_percent @daily_net_shareholder_flows_percent.setter def daily_net_shareholder_flows_percent(self, value: float): self._property_changed('daily_net_shareholder_flows_percent') self.__daily_net_shareholder_flows_percent = value @property def filled_notional_local(self) -> float: """Executed notional in Local.""" return self.__filled_notional_local @filled_notional_local.setter def filled_notional_local(self, value: float): self._property_changed('filled_notional_local') self.__filled_notional_local = value @property def ever_hospitalized(self) -> float: """The total number of patients ever hospitalized.""" return self.__ever_hospitalized @ever_hospitalized.setter def ever_hospitalized(self, value: float): self._property_changed('ever_hospitalized') self.__ever_hospitalized = value @property def meeting_number(self) -> float: """Central bank meeting number.""" return self.__meeting_number @meeting_number.setter def meeting_number(self, value: float): self._property_changed('meeting_number') self.__meeting_number = value @property def mid_gspread(self) -> float: """Mid G spread.""" return self.__mid_gspread @mid_gspread.setter def mid_gspread(self, value: float): self._property_changed('mid_gspread') self.__mid_gspread = value @property def days_open_unrealized_bps(self) -> float: """Unrealised performance vs Day Open, in BPS.""" return self.__days_open_unrealized_bps @days_open_unrealized_bps.setter def days_open_unrealized_bps(self, value: float): self._property_changed('days_open_unrealized_bps') self.__days_open_unrealized_bps = value @property def long_level(self) -> float: """Level of the 5-day normalized flow for long selling/buying.""" return self.__long_level @long_level.setter def long_level(self, value: float): self._property_changed('long_level') self.__long_level = value @property def data_description(self) -> str: """Description of data that client is requesting.""" return self.__data_description @data_description.setter def data_description(self, value: str): self._property_changed('data_description') self.__data_description = value @property def temperature_type(self) -> str: """The type of temperature required: max or min or average etc.""" return self.__temperature_type @temperature_type.setter def temperature_type(self, value: str): self._property_changed('temperature_type') self.__temperature_type = value @property def gsideid(self) -> str: """Goldman Sachs internal composite equity and exchange identifier.""" return self.__gsideid @gsideid.setter def gsideid(self, value: str): self._property_changed('gsideid') self.__gsideid = value @property def repo_rate(self) -> float: """Repurchase Rate.""" return self.__repo_rate @repo_rate.setter def repo_rate(self, value: float): self._property_changed('repo_rate') self.__repo_rate = value @property def division(self) -> str: """Division that owns the data.""" return self.__division @division.setter def division(self, value: str): self._property_changed('division') self.__division = value @property def cloud_cover_daily_forecast(self) -> float: """The forecast value for cloud cover in percentage.""" return self.__cloud_cover_daily_forecast @cloud_cover_daily_forecast.setter def cloud_cover_daily_forecast(self, value: float): self._property_changed('cloud_cover_daily_forecast') self.__cloud_cover_daily_forecast = value @property def wind_speed_daily_forecast(self) -> float: """The forecast value for wind speed in Miles per hour.""" return self.__wind_speed_daily_forecast @wind_speed_daily_forecast.setter def wind_speed_daily_forecast(self, value: float): self._property_changed('wind_speed_daily_forecast') self.__wind_speed_daily_forecast = value @property def asset_parameters_floating_rate_day_count_fraction(self) -> str: """Day Count Fraction""" return self.__asset_parameters_floating_rate_day_count_fraction @asset_parameters_floating_rate_day_count_fraction.setter def asset_parameters_floating_rate_day_count_fraction(self, value: str): self._property_changed('asset_parameters_floating_rate_day_count_fraction') self.__asset_parameters_floating_rate_day_count_fraction = value @property def trade_action(self) -> str: """An indication that a publicly reportable securitybased (SB) swap transaction has been incorrectly or erroneously publicly disseminated and is canceled or corrected or a new transaction.""" return self.__trade_action @trade_action.setter def trade_action(self, value: str): self._property_changed('trade_action') self.__trade_action = value @property def action(self) -> str: """The activity action. For example: Viewed""" return self.__action @action.setter def action(self, value: str): self._property_changed('action') self.__action = value @property def ctd_yield(self) -> float: """Yield of cheapest to deliver bond.""" return self.__ctd_yield @ctd_yield.setter def ctd_yield(self, value: float): self._property_changed('ctd_yield') self.__ctd_yield = value @property def arrival_haircut_vwap_normalized(self) -> float: """Performance against Benchmark in pip.""" return self.__arrival_haircut_vwap_normalized @arrival_haircut_vwap_normalized.setter def arrival_haircut_vwap_normalized(self, value: float): self._property_changed('arrival_haircut_vwap_normalized') self.__arrival_haircut_vwap_normalized = value @property def price_component(self) -> str: """Component of total price.""" return self.__price_component @price_component.setter def price_component(self, value: str): self._property_changed('price_component') self.__price_component = value @property def queue_clock_time_description(self) -> str: """Description of the Stock's Queue Clock Time on the particular date.""" return self.__queue_clock_time_description @queue_clock_time_description.setter def queue_clock_time_description(self, value: str): self._property_changed('queue_clock_time_description') self.__queue_clock_time_description = value @property def asset_parameters_receiver_day_count_fraction(self) -> str: """Day Count Fraction""" return self.__asset_parameters_receiver_day_count_fraction @asset_parameters_receiver_day_count_fraction.setter def asset_parameters_receiver_day_count_fraction(self, value: str): self._property_changed('asset_parameters_receiver_day_count_fraction') self.__asset_parameters_receiver_day_count_fraction = value @property def percent_mid_execution_quantity(self) -> float: """Percentage of order filled at mid-touch.""" return self.__percent_mid_execution_quantity @percent_mid_execution_quantity.setter def percent_mid_execution_quantity(self, value: float): self._property_changed('percent_mid_execution_quantity') self.__percent_mid_execution_quantity = value @property def delta_strike(self) -> str: """Option strike price expressed in terms of delta * 100.""" return self.__delta_strike @delta_strike.setter def delta_strike(self, value: str): self._property_changed('delta_strike') self.__delta_strike = value @property def cloud_cover(self) -> float: """Value of the cloud cover for a given unit.""" return self.__cloud_cover @cloud_cover.setter def cloud_cover(self, value: float): self._property_changed('cloud_cover') self.__cloud_cover = value @property def asset_parameters_notional_currency(self) -> str: """Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)""" return self.__asset_parameters_notional_currency @asset_parameters_notional_currency.setter def asset_parameters_notional_currency(self, value: str): self._property_changed('asset_parameters_notional_currency') self.__asset_parameters_notional_currency = value @property def buy18bps(self) -> float: """The amount GS would buy for 18 bps charge.""" return self.__buy18bps @buy18bps.setter def buy18bps(self, value: float): self._property_changed('buy18bps') self.__buy18bps = value @property def value_actual(self) -> str: """Latest released value.""" return self.__value_actual @value_actual.setter def value_actual(self, value: str): self._property_changed('value_actual') self.__value_actual = value @property def upi(self) -> str: """Unique product identifier for product.""" return self.__upi @upi.setter def upi(self, value: str): self._property_changed('upi') self.__upi = value @property def collateral_currency(self) -> str: """Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)""" return self.__collateral_currency @collateral_currency.setter def collateral_currency(self, value: str): self._property_changed('collateral_currency') self.__collateral_currency = value @property def original_country(self) -> str: """Country in source dataset.""" return self.__original_country @original_country.setter def original_country(self, value: str): self._property_changed('original_country') self.__original_country = value @property def field(self) -> str: """The market data field (e.g. rate, price). This can be resolved into a dataset when combined with vendor and intraday=true/false.""" return self.__field @field.setter def field(self, value: str): self._property_changed('field') self.__field = value @property def geographic_focus(self) -> str: """Geographic region of focus.""" return self.__geographic_focus @geographic_focus.setter def geographic_focus(self, value: str): self._property_changed('geographic_focus') self.__geographic_focus = value @property def days_open_realized_bps(self) -> float: """Realised performance vs Day Open, in BPS.""" return self.__days_open_realized_bps @days_open_realized_bps.setter def days_open_realized_bps(self, value: float): self._property_changed('days_open_realized_bps') self.__days_open_realized_bps = value @property def fx_risk_premium_index(self) -> float: """FX risk premium index: percentage difference between average spot rate and OECD PPP exchange rate.""" return self.__fx_risk_premium_index @fx_risk_premium_index.setter def fx_risk_premium_index(self, value: float): self._property_changed('fx_risk_premium_index') self.__fx_risk_premium_index = value @property def skew(self) -> float: """Volatility skew.""" return self.__skew @skew.setter def skew(self, value: float): self._property_changed('skew') self.__skew = value @property def status(self) -> str: """Status of report run""" return self.__status @status.setter def status(self, value: str): self._property_changed('status') self.__status = value @property def notional_currency(self) -> str: """An indication of the type of currency of the notional or principal amount.""" return self.__notional_currency @notional_currency.setter def notional_currency(self, value: str): self._property_changed('notional_currency') self.__notional_currency = value @property def sustain_emerging_markets(self) -> bool: """True if the stock is on the SUSTAIN Emerging Markets list as of the corresponding date. False if the stock is removed from the SUSTAIN Emerging Markets list on the corresponding date.""" return self.__sustain_emerging_markets @sustain_emerging_markets.setter def sustain_emerging_markets(self, value: bool): self._property_changed('sustain_emerging_markets') self.__sustain_emerging_markets = value @property def event_date_time(self) -> datetime.datetime: """The time of the event if the event has a specific time, using UTC convention, or the end time of the event if the event occurs during a time window (optional).""" return self.__event_date_time @event_date_time.setter def event_date_time(self, value: datetime.datetime): self._property_changed('event_date_time') self.__event_date_time = value @property def leg1_designated_maturity(self) -> str: """Floating rate index designated maturity period of leg 1.""" return self.__leg1_designated_maturity @leg1_designated_maturity.setter def leg1_designated_maturity(self, value: str): self._property_changed('leg1_designated_maturity') self.__leg1_designated_maturity = value @property def total_price(self) -> float: """Net price of the asset.""" return self.__total_price @total_price.setter def total_price(self, value: float): self._property_changed('total_price') self.__total_price = value @property def on_behalf_of(self) -> str: """Marquee unique identifier""" return self.__on_behalf_of @on_behalf_of.setter def on_behalf_of(self, value: str): self._property_changed('on_behalf_of') self.__on_behalf_of = value @property def test_type(self) -> str: """Semantic category that test belongs to e.g. Timeliness, Completeness etc.""" return self.__test_type @test_type.setter def test_type(self, value: str): self._property_changed('test_type') self.__test_type = value @property def accrued_interest_standard(self) -> float: """The accrued interest paid on the bond if it is settled two business days after the trade date.""" return self.__accrued_interest_standard @accrued_interest_standard.setter def accrued_interest_standard(self, value: float): self._property_changed('accrued_interest_standard') self.__accrued_interest_standard = value @property def future_month_z26(self) -> float: """Commods future month code.""" return self.__future_month_z26 @future_month_z26.setter def future_month_z26(self, value: float): self._property_changed('future_month_z26') self.__future_month_z26 = value @property def future_month_z25(self) -> float: """Commods future month code.""" return self.__future_month_z25 @future_month_z25.setter def future_month_z25(self, value: float): self._property_changed('future_month_z25') self.__future_month_z25 = value @property def ccg_code(self) -> str: """Derived CCG code based on location of the patient at the time of triage.""" return self.__ccg_code @ccg_code.setter def ccg_code(self, value: str): self._property_changed('ccg_code') self.__ccg_code = value @property def short_exposure(self) -> float: """Exposure of a given portfolio to securities which are short in direction. If you are $60 short and $40 long, shortExposure would be $60.""" return self.__short_exposure @short_exposure.setter def short_exposure(self, value: float): self._property_changed('short_exposure') self.__short_exposure = value @property def leg1_fixed_payment_currency(self) -> str: """If fixed payment leg, the unit of fixed payment.""" return self.__leg1_fixed_payment_currency @leg1_fixed_payment_currency.setter def leg1_fixed_payment_currency(self, value: str): self._property_changed('leg1_fixed_payment_currency') self.__leg1_fixed_payment_currency = value @property def arrival_haircut_vwap(self) -> float: """Arrival Haircut VWAP.""" return self.__arrival_haircut_vwap @arrival_haircut_vwap.setter def arrival_haircut_vwap(self, value: float): self._property_changed('arrival_haircut_vwap') self.__arrival_haircut_vwap = value @property def execution_days(self) -> float: """Number of days to used to execute.""" return self.__execution_days @execution_days.setter def execution_days(self, value: float): self._property_changed('execution_days') self.__execution_days = value @property def recall_due_date(self) -> datetime.date: """Date in which the recall of securities in a stock loan recall activity must be complete.""" return self.__recall_due_date @recall_due_date.setter def recall_due_date(self, value: datetime.date): self._property_changed('recall_due_date') self.__recall_due_date = value @property def forward(self) -> float: """Forward value.""" return self.__forward @forward.setter def forward(self, value: float): self._property_changed('forward') self.__forward = value @property def strike(self) -> float: """Strike level relative to at the money in basis points.""" return self.__strike @strike.setter def strike(self, value: float): self._property_changed('strike') self.__strike = value @property def spread_limit(self) -> float: """Spread limit (in pips) for which order slices will be skipped.""" return self.__spread_limit @spread_limit.setter def spread_limit(self, value: float): self._property_changed('spread_limit') self.__spread_limit = value @property def product_scope(self) -> str: """Asset Class(es) for which the restriction is required.""" return self.__product_scope @product_scope.setter def product_scope(self, value: str): self._property_changed('product_scope') self.__product_scope = value @property def asset_parameters_issuer_type(self) -> str: """The type of the bond issuer.""" return self.__asset_parameters_issuer_type @asset_parameters_issuer_type.setter def asset_parameters_issuer_type(self, value: str): self._property_changed('asset_parameters_issuer_type') self.__asset_parameters_issuer_type = value @property def currency1(self) -> str: """An indication of the type of currency of the notional or principal amount.""" return self.__currency1 @currency1.setter def currency1(self, value: str): self._property_changed('currency1') self.__currency1 = value @property def currency2(self) -> str: """Same as currency 1.""" return self.__currency2 @currency2.setter def currency2(self, value: str): self._property_changed('currency2') self.__currency2 = value @property def previous_close_realized_bps(self) -> float: """Realised performance vs Previous Close, in bps.""" return self.__previous_close_realized_bps @previous_close_realized_bps.setter def previous_close_realized_bps(self, value: float): self._property_changed('previous_close_realized_bps') self.__previous_close_realized_bps = value @property def days_since_reported(self) -> float: """Days since last reported.""" return self.__days_since_reported @days_since_reported.setter def days_since_reported(self, value: float): self._property_changed('days_since_reported') self.__days_since_reported = value @property def event_status(self) -> str: """Included if there is additional information about an event, such as the event being cancelled.""" return self.__event_status @event_status.setter def event_status(self, value: str): self._property_changed('event_status') self.__event_status = value @property def vwap_in_limit(self) -> float: """VWAP In Limit benchmark price.""" return self.__vwap_in_limit @vwap_in_limit.setter def vwap_in_limit(self, value: float): self._property_changed('vwap_in_limit') self.__vwap_in_limit = value @property def fwd_duration(self) -> float: """Duration of the forward.""" return self.__fwd_duration @fwd_duration.setter def fwd_duration(self, value: float): self._property_changed('fwd_duration') self.__fwd_duration = value @property def return_(self) -> float: """Return of asset over a given period (e.g. close-to-close).""" return self.__return @return_.setter def return_(self, value: float): self._property_changed('return_') self.__return = value @property def is_pair_basket(self) -> bool: """Whether or not it is a pair basket.""" return self.__is_pair_basket @is_pair_basket.setter def is_pair_basket(self, value: bool): self._property_changed('is_pair_basket') self.__is_pair_basket = value @property def notional_amount(self) -> float: """Only applicable on Commodity Index products.""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: float): self._property_changed('notional_amount') self.__notional_amount = value @property def pay_or_receive(self) -> str: """Pay or receive fixed""" return self.__pay_or_receive @pay_or_receive.setter def pay_or_receive(self, value: str): self._property_changed('pay_or_receive') self.__pay_or_receive = value @property def total_severe(self) -> float: """Total number of active cases with severe symptoms.""" return self.__total_severe @total_severe.setter def total_severe(self, value: float): self._property_changed('total_severe') self.__total_severe = value @property def unexecuted_notional_usd(self) -> float: """Notional that has not bee executed, in dollars.""" return self.__unexecuted_notional_usd @unexecuted_notional_usd.setter def unexecuted_notional_usd(self, value: float): self._property_changed('unexecuted_notional_usd') self.__unexecuted_notional_usd = value @property def expected_residual_percentage(self) -> float: """Percent of order that will be left as residual.""" return self.__expected_residual_percentage @expected_residual_percentage.setter def expected_residual_percentage(self, value: float): self._property_changed('expected_residual_percentage') self.__expected_residual_percentage = value @property def maturity_date(self) -> datetime.date: """The maturity, termination, or end date of the reportable SB swap transaction.""" return self.__maturity_date @maturity_date.setter def maturity_date(self, value: datetime.date): self._property_changed('maturity_date') self.__maturity_date = value @property def trace_adv_sell(self) -> float: """TRACE ADV for the sell side.""" return self.__trace_adv_sell @trace_adv_sell.setter def trace_adv_sell(self, value: float): self._property_changed('trace_adv_sell') self.__trace_adv_sell = value @property def event_name(self) -> str: """Event name.""" return self.__event_name @event_name.setter def event_name(self, value: str): self._property_changed('event_name') self.__event_name = value @property def address_line2(self) -> str: """Second line of the street address, typically reserved for apartment, suite or space number.""" return self.__address_line2 @address_line2.setter def address_line2(self, value: str): self._property_changed('address_line2') self.__address_line2 = value @property def indication_of_other_price_affecting_term(self) -> str: """An indication that the publicly reportable SB swap transaction has one or more additional term(s) or provision(s), other than those listed in the required real-time data fields, that materially affect(s) the price of the swap transaction.""" return self.__indication_of_other_price_affecting_term @indication_of_other_price_affecting_term.setter def indication_of_other_price_affecting_term(self, value: str): self._property_changed('indication_of_other_price_affecting_term') self.__indication_of_other_price_affecting_term = value @property def unadjusted_bid(self) -> float: """Unadjusted bid level of an asset based on official exchange fixing or calculation agent marked level.""" return self.__unadjusted_bid @unadjusted_bid.setter def unadjusted_bid(self, value: float): self._property_changed('unadjusted_bid') self.__unadjusted_bid = value @property def backtest_type(self) -> str: """Backtest type differentiates the backtest type.""" return self.__backtest_type @backtest_type.setter def backtest_type(self, value: str): self._property_changed('backtest_type') self.__backtest_type = value @property def gsdeer(self) -> float: """Goldman Sachs Dynamic Equilibrium Exchange Rate.""" return self.__gsdeer @gsdeer.setter def gsdeer(self, value: float): self._property_changed('gsdeer') self.__gsdeer = value @property def asset_parameters_issuer(self) -> str: """The issuer of this bond.""" return self.__asset_parameters_issuer @asset_parameters_issuer.setter def asset_parameters_issuer(self, value: str): self._property_changed('asset_parameters_issuer') self.__asset_parameters_issuer = value @property def g_regional_percentile(self) -> float: """A percentile that captures a company's G ranking relative to its region.""" return self.__g_regional_percentile @g_regional_percentile.setter def g_regional_percentile(self, value: float): self._property_changed('g_regional_percentile') self.__g_regional_percentile = value @property def coverage_checked(self) -> float: """Number of entities checked in a run of the checker.""" return self.__coverage_checked @coverage_checked.setter def coverage_checked(self, value: float): self._property_changed('coverage_checked') self.__coverage_checked = value @property def ois_xccy_ex_spike(self) -> float: """Xccy OIS Spread rate excluding Xccy Spikes impacts.""" return self.__ois_xccy_ex_spike @ois_xccy_ex_spike.setter def ois_xccy_ex_spike(self, value: float): self._property_changed('ois_xccy_ex_spike') self.__ois_xccy_ex_spike = value @property def total_risk(self) -> float: """Total risk.""" return self.__total_risk @total_risk.setter def total_risk(self, value: float): self._property_changed('total_risk') self.__total_risk = value @property def mnav(self) -> float: """Net asset value, assets of a fund (ex dividend) divided by total number of shares.""" return self.__mnav @mnav.setter def mnav(self, value: float): self._property_changed('mnav') self.__mnav = value @property def market_volume(self) -> float: """Volume of the stock throughout the whole day across primaries and MTFs.""" return self.__market_volume @market_volume.setter def market_volume(self, value: float): self._property_changed('market_volume') self.__market_volume = value @property def swap_annuity(self) -> float: """Annuity for a benchmark tenor on a currency's fixed-floating swap curve.""" return self.__swap_annuity @swap_annuity.setter def swap_annuity(self, value: float): self._property_changed('swap_annuity') self.__swap_annuity = value @property def par_asset_swap_spread(self) -> float: """Spread between a Par Swap Rate and the yield of the bond.""" return self.__par_asset_swap_spread @par_asset_swap_spread.setter def par_asset_swap_spread(self, value: float): self._property_changed('par_asset_swap_spread') self.__par_asset_swap_spread = value @property def curr_yield7_day(self) -> float: """Average income return over previous 7 days reduced by any capital gains that may have been included in rate calculation, according to the current amount.""" return self.__curr_yield7_day @curr_yield7_day.setter def curr_yield7_day(self, value: float): self._property_changed('curr_yield7_day') self.__curr_yield7_day = value @property def pressure(self) -> float: """Average barometric pressure on a given day in inches of mercury.""" return self.__pressure @pressure.setter def pressure(self, value: float): self._property_changed('pressure') self.__pressure = value @property def short_description(self) -> str: """Short description of dataset.""" return self.__short_description @short_description.setter def short_description(self, value: str): self._property_changed('short_description') self.__short_description = value @property def future_month_z24(self) -> float: """Commods future month code.""" return self.__future_month_z24 @future_month_z24.setter def future_month_z24(self, value: float): self._property_changed('future_month_z24') self.__future_month_z24 = value @property def feed(self) -> str: """Indicates the source feed of the data.""" return self.__feed @feed.setter def feed(self, value: str): self._property_changed('feed') self.__feed = value @property def future_month_z23(self) -> float: """Commods future month code.""" return self.__future_month_z23 @future_month_z23.setter def future_month_z23(self, value: float): self._property_changed('future_month_z23') self.__future_month_z23 = value @property def mkt_point1(self) -> str: """The first dimension of Mkt Point, e.g. 3M, 11Y, DEC19.""" return self.__mkt_point1 @mkt_point1.setter def mkt_point1(self, value: str): self._property_changed('mkt_point1') self.__mkt_point1 = value @property def future_month_z22(self) -> float: """Commods future month code.""" return self.__future_month_z22 @future_month_z22.setter def future_month_z22(self, value: float): self._property_changed('future_month_z22') self.__future_month_z22 = value @property def future_month_z21(self) -> float: """Commods future month code.""" return self.__future_month_z21 @future_month_z21.setter def future_month_z21(self, value: float): self._property_changed('future_month_z21') self.__future_month_z21 = value @property def future_month_z20(self) -> float: """Commods future month code.""" return self.__future_month_z20 @future_month_z20.setter def future_month_z20(self, value: float): self._property_changed('future_month_z20') self.__future_month_z20 = value @property def asset_parameters_commodity_sector(self) -> str: """The sector of the commodity""" return self.__asset_parameters_commodity_sector @asset_parameters_commodity_sector.setter def asset_parameters_commodity_sector(self, value: str): self._property_changed('asset_parameters_commodity_sector') self.__asset_parameters_commodity_sector = value @property def price_notation2(self) -> float: """The Basis points, Price, Yield, Spread, Coupon, etc., value depending on the type of SB swap, which is calculated at affirmation.""" return self.__price_notation2 @price_notation2.setter def price_notation2(self, value: float): self._property_changed('price_notation2') self.__price_notation2 = value @property def market_buffer_threshold(self) -> float: """The required buffer between holdings and on loan quantity for a market.""" return self.__market_buffer_threshold @market_buffer_threshold.setter def market_buffer_threshold(self, value: float): self._property_changed('market_buffer_threshold') self.__market_buffer_threshold = value @property def price_notation3(self) -> float: """The Basis points, Price, Yield, Spread, Coupon, etc., value depending on the type of SB swap, which is calculated at affirmation.""" return self.__price_notation3 @price_notation3.setter def price_notation3(self, value: float): self._property_changed('price_notation3') self.__price_notation3 = value @property def mkt_point3(self) -> str: """The third dimension of Mkt Point, e.g. 3M, 11Y, DEC19.""" return self.__mkt_point3 @mkt_point3.setter def mkt_point3(self, value: str): self._property_changed('mkt_point3') self.__mkt_point3 = value @property def mkt_point2(self) -> str: """The second dimension of Mkt Point, e.g. 3M, 11Y, DEC19.""" return self.__mkt_point2 @mkt_point2.setter def mkt_point2(self, value: str): self._property_changed('mkt_point2') self.__mkt_point2 = value @property def leg2_type(self) -> str: """Indication if leg 2 is fixed or floating or Physical.""" return self.__leg2_type @leg2_type.setter def leg2_type(self, value: str): self._property_changed('leg2_type') self.__leg2_type = value @property def mkt_point4(self) -> str: """The fourth dimension of Mkt Point, e.g. 3M, 11Y, DEC19.""" return self.__mkt_point4 @mkt_point4.setter def mkt_point4(self, value: str): self._property_changed('mkt_point4') self.__mkt_point4 = value @property def degree_days_type(self) -> str: """The type of degree days required: Heating or Cooling etc.""" return self.__degree_days_type @degree_days_type.setter def degree_days_type(self, value: str): self._property_changed('degree_days_type') self.__degree_days_type = value @property def sentiment(self) -> float: """A measure of the overall sentiment of overnight news.""" return self.__sentiment @sentiment.setter def sentiment(self, value: float): self._property_changed('sentiment') self.__sentiment = value @property def investment_income(self) -> float: """The income earned by the reinvested collateral.""" return self.__investment_income @investment_income.setter def investment_income(self, value: float): self._property_changed('investment_income') self.__investment_income = value @property def group_type(self) -> str: """Type of the group.""" return self.__group_type @group_type.setter def group_type(self, value: str): self._property_changed('group_type') self.__group_type = value @property def forward_point_imm(self) -> float: """Immutable outright forward minus spot.""" return self.__forward_point_imm @forward_point_imm.setter def forward_point_imm(self, value: float): self._property_changed('forward_point_imm') self.__forward_point_imm = value @property def twap(self) -> float: """Time weighted average price.""" return self.__twap @twap.setter def twap(self, value: float): self._property_changed('twap') self.__twap = value @property def client_short_name(self) -> str: """The short name of a client.""" return self.__client_short_name @client_short_name.setter def client_short_name(self, value: str): self._property_changed('client_short_name') self.__client_short_name = value @property def group_category(self) -> str: """The type of group: region or sector.""" return self.__group_category @group_category.setter def group_category(self, value: str): self._property_changed('group_category') self.__group_category = value @property def bid_plus_ask(self) -> float: """Sum of bid & ask.""" return self.__bid_plus_ask @bid_plus_ask.setter def bid_plus_ask(self, value: float): self._property_changed('bid_plus_ask') self.__bid_plus_ask = value @property def foreign_ccy_rate(self) -> float: """The interest rate of the foreign currency of the associated FX contract.""" return self.__foreign_ccy_rate @foreign_ccy_rate.setter def foreign_ccy_rate(self, value: float): self._property_changed('foreign_ccy_rate') self.__foreign_ccy_rate = value @property def election_odds(self) -> float: """Odds of winning election.""" return self.__election_odds @election_odds.setter def election_odds(self, value: float): self._property_changed('election_odds') self.__election_odds = value @property def wind_direction_forecast(self) -> float: """The forecast wind direction of given units.""" return self.__wind_direction_forecast @wind_direction_forecast.setter def wind_direction_forecast(self, value: float): self._property_changed('wind_direction_forecast') self.__wind_direction_forecast = value @property def require_anon_client_name(self) -> bool: """Requires Anonymization of Client Name.""" return self.__require_anon_client_name @require_anon_client_name.setter def require_anon_client_name(self, value: bool): self._property_changed('require_anon_client_name') self.__require_anon_client_name = value @property def pricing_location(self) -> str: """Quill pricing location.""" return self.__pricing_location @pricing_location.setter def pricing_location(self, value: str): self._property_changed('pricing_location') self.__pricing_location = value @property def beta(self) -> float: """Beta.""" return self.__beta @beta.setter def beta(self, value: float): self._property_changed('beta') self.__beta = value @property def last_returns_end_date(self) -> datetime.date: """Hedge fund last returns before end date.""" return self.__last_returns_end_date @last_returns_end_date.setter def last_returns_end_date(self, value: datetime.date): self._property_changed('last_returns_end_date') self.__last_returns_end_date = value @property def upfront_payment_date(self) -> datetime.date: """Date of upront payment.""" return self.__upfront_payment_date @upfront_payment_date.setter def upfront_payment_date(self, value: datetime.date): self._property_changed('upfront_payment_date') self.__upfront_payment_date = value @property def sell1point5bps(self) -> float: """The amount GS would sell for 1.5 bps charge.""" return self.__sell1point5bps @sell1point5bps.setter def sell1point5bps(self, value: float): self._property_changed('sell1point5bps') self.__sell1point5bps = value @property def long_exposure(self) -> float: """Exposure of a given portfolio to securities which are long in direction. If you are $60 short and $40 long, longExposure would be $40.""" return self.__long_exposure @long_exposure.setter def long_exposure(self, value: float): self._property_changed('long_exposure') self.__long_exposure = value @property def sell4point5bps(self) -> float: """The amount GS would sell for 4.5 bps charge.""" return self.__sell4point5bps @sell4point5bps.setter def sell4point5bps(self, value: float): self._property_changed('sell4point5bps') self.__sell4point5bps = value @property def tcm_cost_participation_rate20_pct(self) -> float: """TCM cost with a 20 percent participation rate.""" return self.__tcm_cost_participation_rate20_pct @tcm_cost_participation_rate20_pct.setter def tcm_cost_participation_rate20_pct(self, value: float): self._property_changed('tcm_cost_participation_rate20_pct') self.__tcm_cost_participation_rate20_pct = value @property def venue_type(self) -> str: """The type of venue (Exchange, BD, Dark Pool).""" return self.__venue_type @venue_type.setter def venue_type(self, value: str): self._property_changed('venue_type') self.__venue_type = value @property def multi_asset_class_swap(self) -> str: """Indicates if the swap falls under multiple asset classes.""" return self.__multi_asset_class_swap @multi_asset_class_swap.setter def multi_asset_class_swap(self, value: str): self._property_changed('multi_asset_class_swap') self.__multi_asset_class_swap = value @property def delta_change_id(self) -> float: """PadMaster unique delta change identifier.""" return self.__delta_change_id @delta_change_id.setter def delta_change_id(self, value: float): self._property_changed('delta_change_id') self.__delta_change_id = value @property def implementation_id(self) -> str: """Marquee unique Implementation identifier.""" return self.__implementation_id @implementation_id.setter def implementation_id(self, value: str): self._property_changed('implementation_id') self.__implementation_id = value @property def leg1_fixed_payment(self) -> float: """If fixed payment leg, the fixed payment amount, which is price*number of contracts bought*contract unit.""" return self.__leg1_fixed_payment @leg1_fixed_payment.setter def leg1_fixed_payment(self, value: float): self._property_changed('leg1_fixed_payment') self.__leg1_fixed_payment = value @property def es_numeric_score(self) -> float: """A company's score for E&S subsector-relative numeric metrics.""" return self.__es_numeric_score @es_numeric_score.setter def es_numeric_score(self, value: float): self._property_changed('es_numeric_score') self.__es_numeric_score = value @property def in_benchmark(self) -> bool: """Whether or not the asset is in the benchmark.""" return self.__in_benchmark @in_benchmark.setter def in_benchmark(self, value: bool): self._property_changed('in_benchmark') self.__in_benchmark = value @property def action_sdr(self) -> str: """An indication that a publicly reportable securitybased (SB) swap transaction has been incorrectly or erroneously publicly disseminated and is canceled or corrected or a new transaction.""" return self.__action_sdr @action_sdr.setter def action_sdr(self, value: str): self._property_changed('action_sdr') self.__action_sdr = value @property def count_ideas_qtd(self) -> float: """Ideas alive at a time Quarter to date.""" return self.__count_ideas_qtd @count_ideas_qtd.setter def count_ideas_qtd(self, value: float): self._property_changed('count_ideas_qtd') self.__count_ideas_qtd = value @property def knock_out_price(self) -> float: """Market level required for an order to knock out.""" return self.__knock_out_price @knock_out_price.setter def knock_out_price(self, value: float): self._property_changed('knock_out_price') self.__knock_out_price = value @property def ctd_asset_id(self) -> str: """Id of cheapest to deliver bond.""" return self.__ctd_asset_id @ctd_asset_id.setter def ctd_asset_id(self, value: str): self._property_changed('ctd_asset_id') self.__ctd_asset_id = value @property def buy10bps(self) -> float: """The amount GS would buy for 10 bps charge.""" return self.__buy10bps @buy10bps.setter def buy10bps(self, value: float): self._property_changed('buy10bps') self.__buy10bps = value @property def precipitation(self) -> float: """Amount of rainfall in inches.""" return self.__precipitation @precipitation.setter def precipitation(self, value: float): self._property_changed('precipitation') self.__precipitation = value @property def value_type(self) -> str: """Value type.""" return self.__value_type @value_type.setter def value_type(self, value: str): self._property_changed('value_type') self.__value_type = value @property def beta_adjusted_net_exposure(self) -> float: """Beta adjusted net exposure.""" return self.__beta_adjusted_net_exposure @beta_adjusted_net_exposure.setter def beta_adjusted_net_exposure(self, value: float): self._property_changed('beta_adjusted_net_exposure') self.__beta_adjusted_net_exposure = value @property def estimated_rod_volume(self) -> float: """Consolidated rest of day volume in continuous trading and closing auction.""" return self.__estimated_rod_volume @estimated_rod_volume.setter def estimated_rod_volume(self, value: float): self._property_changed('estimated_rod_volume') self.__estimated_rod_volume = value @property def sell14bps(self) -> float: """The amount GS would sell for 14 bps charge.""" return self.__sell14bps @sell14bps.setter def sell14bps(self, value: float): self._property_changed('sell14bps') self.__sell14bps = value @property def excess_return_price(self) -> float: """The excess return price of an instrument.""" return self.__excess_return_price @excess_return_price.setter def excess_return_price(self, value: float): self._property_changed('excess_return_price') self.__excess_return_price = value @property def fx_pnl(self) -> float: """Foreign Exchange Profit and Loss (PNL).""" return self.__fx_pnl @fx_pnl.setter def fx_pnl(self, value: float): self._property_changed('fx_pnl') self.__fx_pnl = value @property def asset_classifications_gics_industry_group(self) -> str: """GICS Industry Group classification (level 2).""" return self.__asset_classifications_gics_industry_group @asset_classifications_gics_industry_group.setter def asset_classifications_gics_industry_group(self, value: str): self._property_changed('asset_classifications_gics_industry_group') self.__asset_classifications_gics_industry_group = value @property def lending_sec_id(self) -> str: """Securities lending identifiter for the security on loan.""" return self.__lending_sec_id @lending_sec_id.setter def lending_sec_id(self, value: str): self._property_changed('lending_sec_id') self.__lending_sec_id = value @property def dollar_duration(self) -> float: """Measure of a bond's price sensitivity to changes in interest rates.""" return self.__dollar_duration @dollar_duration.setter def dollar_duration(self, value: float): self._property_changed('dollar_duration') self.__dollar_duration = value @property def equity_theta(self) -> float: """Theta exposure to equity products.""" return self.__equity_theta @equity_theta.setter def equity_theta(self, value: float): self._property_changed('equity_theta') self.__equity_theta = value @property def dv01(self) -> float: """Measure of the dollar change in a bond's value to a change in the market interest rate.""" return self.__dv01 @dv01.setter def dv01(self, value: float): self._property_changed('dv01') self.__dv01 = value @property def start_date(self) -> datetime.date: """Start date specific to an asset. For example start date for the swap.""" return self.__start_date @start_date.setter def start_date(self, value: datetime.date): self._property_changed('start_date') self.__start_date = value @property def mixed_swap(self) -> str: """Indicates if the swap falls under both the CFTC and SEC jurisdictions.""" return self.__mixed_swap @mixed_swap.setter def mixed_swap(self, value: str): self._property_changed('mixed_swap') self.__mixed_swap = value @property def swaption_premium(self) -> float: """Swaption premium.""" return self.__swaption_premium @swaption_premium.setter def swaption_premium(self, value: float): self._property_changed('swaption_premium') self.__swaption_premium = value @property def snowfall(self) -> float: """Amount of snowfall in inches.""" return self.__snowfall @snowfall.setter def snowfall(self, value: float): self._property_changed('snowfall') self.__snowfall = value @property def liquidity_bucket_buy(self) -> float: """Liquidity bucket for buying the bond.""" return self.__liquidity_bucket_buy @liquidity_bucket_buy.setter def liquidity_bucket_buy(self, value: float): self._property_changed('liquidity_bucket_buy') self.__liquidity_bucket_buy = value @property def mic(self) -> str: """Market identifier code.""" return self.__mic @mic.setter def mic(self, value: str): self._property_changed('mic') self.__mic = value @property def latitude(self) -> float: """Latitude.""" return self.__latitude @latitude.setter def latitude(self, value: float): self._property_changed('latitude') self.__latitude = value @property def mid(self) -> float: """Mid.""" return self.__mid @mid.setter def mid(self, value: float): self._property_changed('mid') self.__mid = value @property def implied_repo(self) -> float: """Rate of return that can be earned by simultaneously selling a bond futures and then buying that actual bond of equal amount in the cash market using borrowed money.""" return self.__implied_repo @implied_repo.setter def implied_repo(self, value: float): self._property_changed('implied_repo') self.__implied_repo = value @property def long(self) -> float: """Long exposure.""" return self.__long @long.setter def long(self, value: float): self._property_changed('long') self.__long = value @property def first_execution_time(self) -> datetime.datetime: """Arrival execution time.""" return self.__first_execution_time @first_execution_time.setter def first_execution_time(self, value: datetime.datetime): self._property_changed('first_execution_time') self.__first_execution_time = value @property def covered_bond(self) -> bool: """Whether the debt security is collateralized against a pool of assets that, in case of failure of the issuer, can cover claims at any point of time.""" return self.__covered_bond @covered_bond.setter def covered_bond(self, value: bool): self._property_changed('covered_bond') self.__covered_bond = value @property def region_code(self) -> str: """ISO 3166 Region Code. Generally a three digit code.""" return self.__region_code @region_code.setter def region_code(self, value: str): self._property_changed('region_code') self.__region_code = value @property def buy20cents(self) -> float: """The amount GS would buy for 20 cents charge.""" return self.__buy20cents @buy20cents.setter def buy20cents(self, value: float): self._property_changed('buy20cents') self.__buy20cents = value @property def long_weight(self) -> float: """Long weight of a position in a given portfolio. Equivalent to position long exposure / total long exposure. If you have a position with a longExposure of $20, and your portfolio longExposure is $100, longWeight would be 0.2 (20/100).""" return self.__long_weight @long_weight.setter def long_weight(self, value: float): self._property_changed('long_weight') self.__long_weight = value @property def calculation_time(self) -> int: """Time taken to calculate risk metric (ms).""" return self.__calculation_time @calculation_time.setter def calculation_time(self, value: int): self._property_changed('calculation_time') self.__calculation_time = value @property def liquidity_bucket_sell(self) -> float: """Liquidity bucket for selling the bond.""" return self.__liquidity_bucket_sell @liquidity_bucket_sell.setter def liquidity_bucket_sell(self, value: float): self._property_changed('liquidity_bucket_sell') self.__liquidity_bucket_sell = value @property def days_open_unrealized_cash(self) -> float: """Unrealised performance vs Day Open, in USD.""" return self.__days_open_unrealized_cash @days_open_unrealized_cash.setter def days_open_unrealized_cash(self, value: float): self._property_changed('days_open_unrealized_cash') self.__days_open_unrealized_cash = value @property def temperature(self) -> float: """Value of the temperature of given type.""" return self.__temperature @temperature.setter def temperature(self, value: float): self._property_changed('temperature') self.__temperature = value @property def average_realized_variance(self) -> float: """Average variance of an asset realized by observations of market prices.""" return self.__average_realized_variance @average_realized_variance.setter def average_realized_variance(self, value: float): self._property_changed('average_realized_variance') self.__average_realized_variance = value @property def rating_fitch(self) -> str: """Bond rating from Fitch.""" return self.__rating_fitch @rating_fitch.setter def rating_fitch(self, value: str): self._property_changed('rating_fitch') self.__rating_fitch = value @property def financial_returns_score(self) -> float: """Financial Returns percentile relative to Americas coverage universe (a higher score means stronger financial returns).""" return self.__financial_returns_score @financial_returns_score.setter def financial_returns_score(self, value: float): self._property_changed('financial_returns_score') self.__financial_returns_score = value @property def year_or_quarter(self) -> str: """Year or quarter for which the forecast holds.""" return self.__year_or_quarter @year_or_quarter.setter def year_or_quarter(self, value: str): self._property_changed('year_or_quarter') self.__year_or_quarter = value @property def non_symbol_dimensions(self) -> Tuple[str, ...]: """Fields that are not nullable.""" return self.__non_symbol_dimensions @non_symbol_dimensions.setter def non_symbol_dimensions(self, value: Tuple[str, ...]): self._property_changed('non_symbol_dimensions') self.__non_symbol_dimensions = value @property def commodities_forecast(self) -> float: """Forecast value.""" return self.__commodities_forecast @commodities_forecast.setter def commodities_forecast(self, value: float): self._property_changed('commodities_forecast') self.__commodities_forecast = value @property def covid19_by_state(self) -> float: """Total number of people affected by covid19 for a given state country.""" return self.__covid19_by_state @covid19_by_state.setter def covid19_by_state(self, value: float): self._property_changed('covid19_by_state') self.__covid19_by_state = value @property def percentage_expected_residual(self) -> float: """Percentage expected residual quantity.""" return self.__percentage_expected_residual @percentage_expected_residual.setter def percentage_expected_residual(self, value: float): self._property_changed('percentage_expected_residual') self.__percentage_expected_residual = value @property def hospital_name(self) -> str: """Name of hospital, where hospital is defined as a healthcare institution providing at inpatient, therapeutic, and/or rehabilitation services under the supervision of physicians.""" return self.__hospital_name @hospital_name.setter def hospital_name(self, value: str): self._property_changed('hospital_name') self.__hospital_name = value @property def buy90cents(self) -> float: """The amount GS would buy for 90 cents charge.""" return self.__buy90cents @buy90cents.setter def buy90cents(self, value: float): self._property_changed('buy90cents') self.__buy90cents = value @property def period_type(self) -> str: """Period type.""" return self.__period_type @period_type.setter def period_type(self, value: str): self._property_changed('period_type') self.__period_type = value @property def asset_classifications_country_name(self) -> str: """Country name of asset.""" return self.__asset_classifications_country_name @asset_classifications_country_name.setter def asset_classifications_country_name(self, value: str): self._property_changed('asset_classifications_country_name') self.__asset_classifications_country_name = value @property def total_hospitalized(self) -> float: """Total number of hospitalized cases.""" return self.__total_hospitalized @total_hospitalized.setter def total_hospitalized(self, value: float): self._property_changed('total_hospitalized') self.__total_hospitalized = value @property def pegged_refill_interval(self) -> float: """Time interval (in seconds) between a tranche getting filled and the next tranche being shown on the order book.""" return self.__pegged_refill_interval @pegged_refill_interval.setter def pegged_refill_interval(self, value: float): self._property_changed('pegged_refill_interval') self.__pegged_refill_interval = value @property def fatalities_probable(self) -> float: """Total number of probable fatalities.""" return self.__fatalities_probable @fatalities_probable.setter def fatalities_probable(self, value: float): self._property_changed('fatalities_probable') self.__fatalities_probable = value @property def administrative_region(self) -> str: """Name of the county or province.""" return self.__administrative_region @administrative_region.setter def administrative_region(self, value: str): self._property_changed('administrative_region') self.__administrative_region = value @property def open(self) -> float: """Opening level of an asset based on official exchange fixing or calculation agent marked level.""" return self.__open @open.setter def open(self, value: float): self._property_changed('open') self.__open = value @property def cusip(self) -> str: """CUSIP - Committee on Uniform Securities Identification Procedures number (subject to licensing).""" return self.__cusip @cusip.setter def cusip(self, value: str): self._property_changed('cusip') self.__cusip = value @property def total_confirmed_by_state(self) -> float: """Total number of confirmed cases by state.""" return self.__total_confirmed_by_state @total_confirmed_by_state.setter def total_confirmed_by_state(self, value: float): self._property_changed('total_confirmed_by_state') self.__total_confirmed_by_state = value @property def idea_activity_time(self) -> datetime.datetime: """The time the idea activity took place. If ideaStatus is open, the time reflects the Idea creation time. If ideaStatus is closed, the time reflects the time the idea was closed.""" return self.__idea_activity_time @idea_activity_time.setter def idea_activity_time(self, value: datetime.datetime): self._property_changed('idea_activity_time') self.__idea_activity_time = value @property def wind_attribute(self) -> str: """The attribute of wind required: direction or speed etc.""" return self.__wind_attribute @wind_attribute.setter def wind_attribute(self, value: str): self._property_changed('wind_attribute') self.__wind_attribute = value @property def spread_option_atm_fwd_rate(self) -> float: """Spread Option ATM forward rate.""" return self.__spread_option_atm_fwd_rate @spread_option_atm_fwd_rate.setter def spread_option_atm_fwd_rate(self, value: float): self._property_changed('spread_option_atm_fwd_rate') self.__spread_option_atm_fwd_rate = value @property def net_exposure(self) -> float: """The difference between long and short exposure in the portfolio. If you are $60 short and $40 long, then the netExposure would be -$20 (-60+40).""" return self.__net_exposure @net_exposure.setter def net_exposure(self, value: float): self._property_changed('net_exposure') self.__net_exposure = value @property def is_legacy_pair_basket(self) -> bool: """Whether or not it is a legacy pair baskets which are not tradabale as don't have secdb asset.""" return self.__is_legacy_pair_basket @is_legacy_pair_basket.setter def is_legacy_pair_basket(self, value: bool): self._property_changed('is_legacy_pair_basket') self.__is_legacy_pair_basket = value @property def issuer_type(self) -> str: """The type of the bond issuer.""" return self.__issuer_type @issuer_type.setter def issuer_type(self, value: str): self._property_changed('issuer_type') self.__issuer_type = value @property def buy70cents(self) -> float: """The amount GS would buy for 70 cents charge.""" return self.__buy70cents @buy70cents.setter def buy70cents(self, value: float): self._property_changed('buy70cents') self.__buy70cents = value @property def strike_reference(self) -> str: """Reference for strike level (enum: spot, forward).""" return self.__strike_reference @strike_reference.setter def strike_reference(self, value: str): self._property_changed('strike_reference') self.__strike_reference = value @property def asset_count(self) -> float: """Number of assets in a portfolio or index.""" return self.__asset_count @asset_count.setter def asset_count(self, value: float): self._property_changed('asset_count') self.__asset_count = value @property def is_order_in_limit(self) -> bool: """Is the order???s passive leg in limit.""" return self.__is_order_in_limit @is_order_in_limit.setter def is_order_in_limit(self, value: bool): self._property_changed('is_order_in_limit') self.__is_order_in_limit = value @property def fundamental_metric(self) -> float: """Fundamental metric value.""" return self.__fundamental_metric @fundamental_metric.setter def fundamental_metric(self, value: float): self._property_changed('fundamental_metric') self.__fundamental_metric = value @property def quote_status_id(self) -> str: """PadMaster unique quote status identifier.""" return self.__quote_status_id @quote_status_id.setter def quote_status_id(self, value: str): self._property_changed('quote_status_id') self.__quote_status_id = value @property def absolute_value(self) -> float: """The notional value of the asset.""" return self.__absolute_value @absolute_value.setter def absolute_value(self, value: float): self._property_changed('absolute_value') self.__absolute_value = value @property def closing_report(self) -> str: """Report that was published when the trade idea was closed.""" return self.__closing_report @closing_report.setter def closing_report(self, value: str): self._property_changed('closing_report') self.__closing_report = value @property def previous_total_confirmed(self) -> float: """The previous day's number of total confirmed cases.""" return self.__previous_total_confirmed @previous_total_confirmed.setter def previous_total_confirmed(self, value: float): self._property_changed('previous_total_confirmed') self.__previous_total_confirmed = value @property def long_tenor(self) -> str: """Tenor of instrument.""" return self.__long_tenor @long_tenor.setter def long_tenor(self, value: str): self._property_changed('long_tenor') self.__long_tenor = value @property def multiplier(self) -> float: """Underlying unit per asset multiplier.""" return self.__multiplier @multiplier.setter def multiplier(self, value: float): self._property_changed('multiplier') self.__multiplier = value @property def buy40cents(self) -> float: """The amount GS would buy for 40 cents charge.""" return self.__buy40cents @buy40cents.setter def buy40cents(self, value: float): self._property_changed('buy40cents') self.__buy40cents = value @property def asset_count_priced(self) -> float: """Number of assets in a portfolio which could be priced.""" return self.__asset_count_priced @asset_count_priced.setter def asset_count_priced(self, value: float): self._property_changed('asset_count_priced') self.__asset_count_priced = value @property def vote_direction(self) -> float: """Direction of vote.""" return self.__vote_direction @vote_direction.setter def vote_direction(self, value: float): self._property_changed('vote_direction') self.__vote_direction = value @property def implied_repo_rate(self) -> float: """Rate of return that can be earned by simultaneously selling a bond futures or forward contract, and then buying that actual bond of equal amount in the cash market using borrowed money.""" return self.__implied_repo_rate @implied_repo_rate.setter def implied_repo_rate(self, value: float): self._property_changed('implied_repo_rate') self.__implied_repo_rate = value @property def settlement_currency(self) -> str: """The settlement currency type for SB swap transactions in the FX asset class.""" return self.__settlement_currency @settlement_currency.setter def settlement_currency(self, value: str): self._property_changed('settlement_currency') self.__settlement_currency = value @property def wtd_degree_days_forecast(self) -> float: """The forecast for weighted degree days of diff types of given units.""" return self.__wtd_degree_days_forecast @wtd_degree_days_forecast.setter def wtd_degree_days_forecast(self, value: float): self._property_changed('wtd_degree_days_forecast') self.__wtd_degree_days_forecast = value @property def indication_of_collateralization(self) -> str: """If an SB swap is not cleared, an indication of whether a swap is Uncollateralized (UC), Partially Collateralized (PC), One-Way Collateralized (OC), or Fully Collateralized (FC).""" return self.__indication_of_collateralization @indication_of_collateralization.setter def indication_of_collateralization(self, value: str): self._property_changed('indication_of_collateralization') self.__indication_of_collateralization = value @property def future_month_n26(self) -> float: """Commods future month code.""" return self.__future_month_n26 @future_month_n26.setter def future_month_n26(self, value: float): self._property_changed('future_month_n26') self.__future_month_n26 = value @property def lending_partner_fee(self) -> float: """Fee earned by the Lending Partner in a securities lending agreement.""" return self.__lending_partner_fee @lending_partner_fee.setter def lending_partner_fee(self, value: float): self._property_changed('lending_partner_fee') self.__lending_partner_fee = value @property def future_month_n25(self) -> float: """Commods future month code.""" return self.__future_month_n25 @future_month_n25.setter def future_month_n25(self, value: float): self._property_changed('future_month_n25') self.__future_month_n25 = value @property def future_month_n24(self) -> float: """Commods future month code.""" return self.__future_month_n24 @future_month_n24.setter def future_month_n24(self, value: float): self._property_changed('future_month_n24') self.__future_month_n24 = value @property def primary_vwap_realized_bps(self) -> float: """Realised performance vs Primary VWAP, in bps.""" return self.__primary_vwap_realized_bps @primary_vwap_realized_bps.setter def primary_vwap_realized_bps(self, value: float): self._property_changed('primary_vwap_realized_bps') self.__primary_vwap_realized_bps = value @property def future_month_n23(self) -> float: """Commods future month code.""" return self.__future_month_n23 @future_month_n23.setter def future_month_n23(self, value: float): self._property_changed('future_month_n23') self.__future_month_n23 = value @property def future_month_n22(self) -> float: """Commods future month code.""" return self.__future_month_n22 @future_month_n22.setter def future_month_n22(self, value: float): self._property_changed('future_month_n22') self.__future_month_n22 = value @property def future_month_n21(self) -> float: """Commods future month code.""" return self.__future_month_n21 @future_month_n21.setter def future_month_n21(self, value: float): self._property_changed('future_month_n21') self.__future_month_n21 = value @property def break_even_inflation(self) -> float: """Break even inflation.""" return self.__break_even_inflation @break_even_inflation.setter def break_even_inflation(self, value: float): self._property_changed('break_even_inflation') self.__break_even_inflation = value @property def pnl_ytd(self) -> float: """Total PnL Year to date.""" return self.__pnl_ytd @pnl_ytd.setter def pnl_ytd(self, value: float): self._property_changed('pnl_ytd') self.__pnl_ytd = value @property def leg1_return_type(self) -> str: """Indication if return of leg 1 is total.""" return self.__leg1_return_type @leg1_return_type.setter def leg1_return_type(self, value: str): self._property_changed('leg1_return_type') self.__leg1_return_type = value @property def tenor2(self) -> str: """Tenor of instrument.""" return self.__tenor2 @tenor2.setter def tenor2(self, value: str): self._property_changed('tenor2') self.__tenor2 = value @property def reset_frequency(self) -> str: """An integer multiplier of a period describing how often the parties to an SB swap transaction shall evaluate and, when applicable, change the price used for the underlying assets of the swap transaction. Such reset frequency may be described as one letter preceded by an integer.""" return self.__reset_frequency @reset_frequency.setter def reset_frequency(self, value: str): self._property_changed('reset_frequency') self.__reset_frequency = value @property def asset_parameters_payer_frequency(self) -> str: """Tenor""" return self.__asset_parameters_payer_frequency @asset_parameters_payer_frequency.setter def asset_parameters_payer_frequency(self, value: str): self._property_changed('asset_parameters_payer_frequency') self.__asset_parameters_payer_frequency = value @property def degree_days_forecast(self) -> float: """The forecast value for degree days.""" return self.__degree_days_forecast @degree_days_forecast.setter def degree_days_forecast(self, value: float): self._property_changed('degree_days_forecast') self.__degree_days_forecast = value @property def is_manually_silenced(self) -> str: """Indicates if an entity was silenced at time of check.""" return self.__is_manually_silenced @is_manually_silenced.setter def is_manually_silenced(self, value: str): self._property_changed('is_manually_silenced') self.__is_manually_silenced = value @property def buy3bps(self) -> float: """The amount GS would buy for 3 bps charge.""" return self.__buy3bps @buy3bps.setter def buy3bps(self, value: float): self._property_changed('buy3bps') self.__buy3bps = value @property def last_updated_by_id(self) -> str: """Unique identifier of user who last updated the object.""" return self.__last_updated_by_id @last_updated_by_id.setter def last_updated_by_id(self, value: str): self._property_changed('last_updated_by_id') self.__last_updated_by_id = value @property def legal_entity_acct(self) -> str: """Account assoicated with the entity that has legal rights to the fund.""" return self.__legal_entity_acct @legal_entity_acct.setter def legal_entity_acct(self, value: str): self._property_changed('legal_entity_acct') self.__legal_entity_acct = value @property def target_shareholder_meeting_date(self) -> str: """Target acquisition entity shareholder meeting date.""" return self.__target_shareholder_meeting_date @target_shareholder_meeting_date.setter def target_shareholder_meeting_date(self, value: str): self._property_changed('target_shareholder_meeting_date') self.__target_shareholder_meeting_date = value @property def pace_of_rollp0(self) -> float: """The 0th-percentile of the pace of roll compared to previous quarters (over the last 5 years) at the same point in time, i.e. cross-sectional.""" return self.__pace_of_rollp0 @pace_of_rollp0.setter def pace_of_rollp0(self, value: float): self._property_changed('pace_of_rollp0') self.__pace_of_rollp0 = value @property def controversy_percentile(self) -> float: """A percentile that captures a company's controversy ranking.""" return self.__controversy_percentile @controversy_percentile.setter def controversy_percentile(self, value: float): self._property_changed('controversy_percentile') self.__controversy_percentile = value @property def leg1_notional_currency(self) -> str: """An indication of the type of currency of the notional or principal amount.""" return self.__leg1_notional_currency @leg1_notional_currency.setter def leg1_notional_currency(self, value: str): self._property_changed('leg1_notional_currency') self.__leg1_notional_currency = value @property def compliance_effective_time(self) -> datetime.datetime: """Time that the compliance status became effective.""" return self.__compliance_effective_time @compliance_effective_time.setter def compliance_effective_time(self, value: datetime.datetime): self._property_changed('compliance_effective_time') self.__compliance_effective_time = value @property def expiration_date(self) -> datetime.date: """The expiration date of the associated contract and the last date it trades.""" return self.__expiration_date @expiration_date.setter def expiration_date(self, value: datetime.date): self._property_changed('expiration_date') self.__expiration_date = value @property def floating_rate_day_count_fraction(self) -> str: """Day Count Fraction""" return self.__floating_rate_day_count_fraction @floating_rate_day_count_fraction.setter def floating_rate_day_count_fraction(self, value: str): self._property_changed('floating_rate_day_count_fraction') self.__floating_rate_day_count_fraction = value @property def call_last_date(self) -> datetime.date: """ISO 8601-formatted date""" return self.__call_last_date @call_last_date.setter def call_last_date(self, value: datetime.date): self._property_changed('call_last_date') self.__call_last_date = value @property def factor_return(self) -> float: """Factor return.""" return self.__factor_return @factor_return.setter def factor_return(self, value: float): self._property_changed('factor_return') self.__factor_return = value @property def passive_flow_ratio(self) -> float: """Ratio of signed passive dollar flow to market cap of global stocks, i.e. proportion of company's market cap that is part of passive fund flows observed weekly. Normalised between -3 (low level) and +3 (high level).""" return self.__passive_flow_ratio @passive_flow_ratio.setter def passive_flow_ratio(self, value: float): self._property_changed('passive_flow_ratio') self.__passive_flow_ratio = value @property def composite5_day_adv(self) -> float: """Composite 5 day ADV.""" return self.__composite5_day_adv @composite5_day_adv.setter def composite5_day_adv(self, value: float): self._property_changed('composite5_day_adv') self.__composite5_day_adv = value @property def marginal_contribution_to_risk(self) -> float: """Marginal contribution of a given asset to portfolio variance, is dependent on covariance matrix.""" return self.__marginal_contribution_to_risk @marginal_contribution_to_risk.setter def marginal_contribution_to_risk(self, value: float): self._property_changed('marginal_contribution_to_risk') self.__marginal_contribution_to_risk = value @property def close_date(self) -> datetime.date: """Date the trade idea was closed.""" return self.__close_date @close_date.setter def close_date(self, value: datetime.date): self._property_changed('close_date') self.__close_date = value @property def temperature_hour_forecast(self) -> float: """The hour forecast of max/min temperature of a given day.""" return self.__temperature_hour_forecast @temperature_hour_forecast.setter def temperature_hour_forecast(self, value: float): self._property_changed('temperature_hour_forecast') self.__temperature_hour_forecast = value @property def new_ideas_wtd(self) -> float: """Ideas received by clients Week to date.""" return self.__new_ideas_wtd @new_ideas_wtd.setter def new_ideas_wtd(self, value: float): self._property_changed('new_ideas_wtd') self.__new_ideas_wtd = value @property def asset_class_sdr(self) -> str: """An indication of one of the broad categories. For our use case will typically be CO.""" return self.__asset_class_sdr @asset_class_sdr.setter def asset_class_sdr(self, value: str): self._property_changed('asset_class_sdr') self.__asset_class_sdr = value @property def yield_to_worst(self) -> float: """The return an investor realizes on a bond sold at the mid price at the worst call date.""" return self.__yield_to_worst @yield_to_worst.setter def yield_to_worst(self, value: float): self._property_changed('yield_to_worst') self.__yield_to_worst = value @property def closing_price(self) -> float: """Closing price.""" return self.__closing_price @closing_price.setter def closing_price(self, value: float): self._property_changed('closing_price') self.__closing_price = value @property def turnover_composite_adjusted(self) -> float: """Turnover composite adjusted for corporate actions.""" return self.__turnover_composite_adjusted @turnover_composite_adjusted.setter def turnover_composite_adjusted(self, value: float): self._property_changed('turnover_composite_adjusted') self.__turnover_composite_adjusted = value @property def comment(self) -> str: """The comment associated with the trade idea in URL encoded format.""" return self.__comment @comment.setter def comment(self, value: str): self._property_changed('comment') self.__comment = value @property def source_symbol(self) -> str: """Source symbol.""" return self.__source_symbol @source_symbol.setter def source_symbol(self, value: str): self._property_changed('source_symbol') self.__source_symbol = value @property def ask_unadjusted(self) -> float: """Unadjusted ask level of an asset based on official exchange fixing or calculation agent marked level.""" return self.__ask_unadjusted @ask_unadjusted.setter def ask_unadjusted(self, value: float): self._property_changed('ask_unadjusted') self.__ask_unadjusted = value @property def restrict_external_derived_data(self) -> bool: """Restricts Ability to Use Externally as Part of Derived Data.""" return self.__restrict_external_derived_data @restrict_external_derived_data.setter def restrict_external_derived_data(self, value: bool): self._property_changed('restrict_external_derived_data') self.__restrict_external_derived_data = value @property def ask_change(self) -> float: """Change in the ask price.""" return self.__ask_change @ask_change.setter def ask_change(self, value: float): self._property_changed('ask_change') self.__ask_change = value @property def count_ideas_mtd(self) -> float: """Ideas alive at a time Month to date.""" return self.__count_ideas_mtd @count_ideas_mtd.setter def count_ideas_mtd(self, value: float): self._property_changed('count_ideas_mtd') self.__count_ideas_mtd = value @property def end_date(self) -> datetime.date: """The maturity, termination, or end date of the reportable SB swap transaction.""" return self.__end_date @end_date.setter def end_date(self, value: datetime.date): self._property_changed('end_date') self.__end_date = value @property def sunshine(self) -> float: """The percent of sunshine for a given location.""" return self.__sunshine @sunshine.setter def sunshine(self, value: float): self._property_changed('sunshine') self.__sunshine = value @property def contract_type(self) -> str: """Contract type.""" return self.__contract_type @contract_type.setter def contract_type(self, value: str): self._property_changed('contract_type') self.__contract_type = value @property def momentum_type(self) -> str: """The mode/gear of an algo order running in range bound or directional mode.""" return self.__momentum_type @momentum_type.setter def momentum_type(self, value: str): self._property_changed('momentum_type') self.__momentum_type = value @property def specific_risk(self) -> float: """Specific Risk.""" return self.__specific_risk @specific_risk.setter def specific_risk(self, value: float): self._property_changed('specific_risk') self.__specific_risk = value @property def mdapi(self) -> str: """MDAPI Asset.""" return self.__mdapi @mdapi.setter def mdapi(self, value: str): self._property_changed('mdapi') self.__mdapi = value @property def payoff_qtd(self) -> float: """Total win divided by total loss Quarter to date.""" return self.__payoff_qtd @payoff_qtd.setter def payoff_qtd(self, value: float): self._property_changed('payoff_qtd') self.__payoff_qtd = value @property def loss(self) -> float: """Loss price component.""" return self.__loss @loss.setter def loss(self, value: float): self._property_changed('loss') self.__loss = value @property def midcurve_vol(self) -> float: """Historical implied normal volatility for a liquid point on midcurve vol surface.""" return self.__midcurve_vol @midcurve_vol.setter def midcurve_vol(self, value: float): self._property_changed('midcurve_vol') self.__midcurve_vol = value @property def sell6bps(self) -> float: """The amount GS would sell for 6 bps charge.""" return self.__sell6bps @sell6bps.setter def sell6bps(self, value: float): self._property_changed('sell6bps') self.__sell6bps = value @property def trading_cost_pnl(self) -> float: """Trading cost profit and loss (PNL).""" return self.__trading_cost_pnl @trading_cost_pnl.setter def trading_cost_pnl(self, value: float): self._property_changed('trading_cost_pnl') self.__trading_cost_pnl = value @property def price_notation_type(self) -> str: """Basis points, Price, Yield, Spread, Coupon, etc., depending on the type of SB swap, which is calculated at affirmation.""" return self.__price_notation_type @price_notation_type.setter def price_notation_type(self, value: str): self._property_changed('price_notation_type') self.__price_notation_type = value @property def price(self) -> float: """Price of instrument.""" return self.__price @price.setter def price(self, value: float): self._property_changed('price') self.__price = value @property def payment_quantity(self) -> float: """Quantity in the payment currency.""" return self.__payment_quantity @payment_quantity.setter def payment_quantity(self, value: float): self._property_changed('payment_quantity') self.__payment_quantity = value @property def redemption_date(self) -> datetime.date: """ISO 8601-formatted date""" return self.__redemption_date @redemption_date.setter def redemption_date(self, value: datetime.date): self._property_changed('redemption_date') self.__redemption_date = value @property def leg2_notional_currency(self) -> str: """An indication of the type of currency of the notional or principal amount.""" return self.__leg2_notional_currency @leg2_notional_currency.setter def leg2_notional_currency(self, value: str): self._property_changed('leg2_notional_currency') self.__leg2_notional_currency = value @property def sub_region(self) -> str: """Filter by any identifier of an asset like ticker, bloomberg id etc.""" return self.__sub_region @sub_region.setter def sub_region(self, value: str): self._property_changed('sub_region') self.__sub_region = value @property def benchmark(self) -> str: """Benchmark index of a contributor.""" return self.__benchmark @benchmark.setter def benchmark(self, value: str): self._property_changed('benchmark') self.__benchmark = value @property def tcm_cost_participation_rate15_pct(self) -> float: """TCM cost with a 15 percent participation rate.""" return self.__tcm_cost_participation_rate15_pct @tcm_cost_participation_rate15_pct.setter def tcm_cost_participation_rate15_pct(self, value: float): self._property_changed('tcm_cost_participation_rate15_pct') self.__tcm_cost_participation_rate15_pct = value @property def fiscal_year(self) -> str: """The Calendar Year.""" return self.__fiscal_year @fiscal_year.setter def fiscal_year(self, value: str): self._property_changed('fiscal_year') self.__fiscal_year = value @property def recall_date(self) -> datetime.date: """The date at which the securities on loan were recalled.""" return self.__recall_date @recall_date.setter def recall_date(self, value: datetime.date): self._property_changed('recall_date') self.__recall_date = value @property def esg_metric_value(self) -> float: """Value of the metric corresponding to the parameter metricName.""" return self.__esg_metric_value @esg_metric_value.setter def esg_metric_value(self, value: float): self._property_changed('esg_metric_value') self.__esg_metric_value = value @property def internal(self) -> bool: """Whether request came from internal or external.""" return self.__internal @internal.setter def internal(self, value: bool): self._property_changed('internal') self.__internal = value @property def gender(self) -> str: """Gender of the patient.""" return self.__gender @gender.setter def gender(self, value: str): self._property_changed('gender') self.__gender = value @property def asset_classifications_gics_industry(self) -> str: """GICS Industry classification (level 3).""" return self.__asset_classifications_gics_industry @asset_classifications_gics_industry.setter def asset_classifications_gics_industry(self, value: str): self._property_changed('asset_classifications_gics_industry') self.__asset_classifications_gics_industry = value @property def adjusted_bid_price(self) -> float: """Latest Bid Price (price willing to buy) adjusted for corporate actions.""" return self.__adjusted_bid_price @adjusted_bid_price.setter def adjusted_bid_price(self, value: float): self._property_changed('adjusted_bid_price') self.__adjusted_bid_price = value @property def low_unadjusted(self) -> float: """Unadjusted low level of an asset based on official exchange fixing or calculation agent marked level.""" return self.__low_unadjusted @low_unadjusted.setter def low_unadjusted(self, value: float): self._property_changed('low_unadjusted') self.__low_unadjusted = value @property def macs_secondary_asset_class(self) -> str: """Indicates the secondary asset class the multi asset class swap falls under.""" return self.__macs_secondary_asset_class @macs_secondary_asset_class.setter def macs_secondary_asset_class(self, value: str): self._property_changed('macs_secondary_asset_class') self.__macs_secondary_asset_class = value @property def confirmed_per_million(self) -> float: """Total number of confirmed cases per population of one million.""" return self.__confirmed_per_million @confirmed_per_million.setter def confirmed_per_million(self, value: float): self._property_changed('confirmed_per_million') self.__confirmed_per_million = value @property def data_source_id(self) -> str: """PadMaster unique data source identifier.""" return self.__data_source_id @data_source_id.setter def data_source_id(self, value: str): self._property_changed('data_source_id') self.__data_source_id = value @property def integrated_score(self) -> float: """Average of the Growth, Financial Returns and (1-Multiple) percentile (a higher score means more attractive).""" return self.__integrated_score @integrated_score.setter def integrated_score(self, value: float): self._property_changed('integrated_score') self.__integrated_score = value @property def buy7bps(self) -> float: """The amount GS would buy for 7 bps charge.""" return self.__buy7bps @buy7bps.setter def buy7bps(self, value: float): self._property_changed('buy7bps') self.__buy7bps = value @property def arrival_mid_unrealized_cash(self) -> float: """Consolidated unrealised performance vs Arrival Mid, in USD.""" return self.__arrival_mid_unrealized_cash @arrival_mid_unrealized_cash.setter def arrival_mid_unrealized_cash(self, value: float): self._property_changed('arrival_mid_unrealized_cash') self.__arrival_mid_unrealized_cash = value @property def knock_in_price(self) -> float: """Market level required for an order to knock in.""" return self.__knock_in_price @knock_in_price.setter def knock_in_price(self, value: float): self._property_changed('knock_in_price') self.__knock_in_price = value @property def event(self) -> str: """An indication of whether an SB swap transaction is a post-execution event that affects the price of the swap transaction, e.g. terminations, assignments, novations, exchanges, transfers, amendments, conveyances or extinguishing of rights that change the price of the SB swap.""" return self.__event @event.setter def event(self, value: str): self._property_changed('event') self.__event = value @property def is_intraday_auction(self) -> bool: """Flag indicating whether the bucket is within the intraday auction or not.""" return self.__is_intraday_auction @is_intraday_auction.setter def is_intraday_auction(self, value: bool): self._property_changed('is_intraday_auction') self.__is_intraday_auction = value @property def location_name(self) -> str: """Name of geographical region (of varying granularity).""" return self.__location_name @location_name.setter def location_name(self, value: str): self._property_changed('location_name') self.__location_name = value @property def coupon(self) -> float: """The fixed coupon for this bond.""" return self.__coupon @coupon.setter def coupon(self, value: float): self._property_changed('coupon') self.__coupon = value @property def percentage_auction_executed_quantity(self) -> float: """Percentage of total order filled at auction.""" return self.__percentage_auction_executed_quantity @percentage_auction_executed_quantity.setter def percentage_auction_executed_quantity(self, value: float): self._property_changed('percentage_auction_executed_quantity') self.__percentage_auction_executed_quantity = value @property def avg_yield7_day(self) -> float: """Only used for GS Money Market funds, assumes sum of the past 7 days, divided by 7, and expressed as a percent.""" return self.__avg_yield7_day @avg_yield7_day.setter def avg_yield7_day(self, value: float): self._property_changed('avg_yield7_day') self.__avg_yield7_day = value @property def original_dissemination_id(self) -> str: """On cancellations and corrections, this ID will hold the Dissemination ID of the originally published real-time message.""" return self.__original_dissemination_id @original_dissemination_id.setter def original_dissemination_id(self, value: str): self._property_changed('original_dissemination_id') self.__original_dissemination_id = value @property def total_on_vent(self) -> float: """Total number of patients on ventilator for COVID.""" return self.__total_on_vent @total_on_vent.setter def total_on_vent(self, value: float): self._property_changed('total_on_vent') self.__total_on_vent = value @property def twap_unrealized_cash(self) -> float: """Consolidated unrealised performance vs TWAP In Limit, in USD.""" return self.__twap_unrealized_cash @twap_unrealized_cash.setter def twap_unrealized_cash(self, value: float): self._property_changed('twap_unrealized_cash') self.__twap_unrealized_cash = value @property def sts_credit_market(self) -> str: """Credit risk market.""" return self.__sts_credit_market @sts_credit_market.setter def sts_credit_market(self, value: str): self._property_changed('sts_credit_market') self.__sts_credit_market = value @property def ons_code(self) -> str: """ONS Region code in the UK.""" return self.__ons_code @ons_code.setter def ons_code(self, value: str): self._property_changed('ons_code') self.__ons_code = value @property def passive_touch_fills_percentage(self) -> float: """Percent of total order filled at passive touch.""" return self.__passive_touch_fills_percentage @passive_touch_fills_percentage.setter def passive_touch_fills_percentage(self, value: float): self._property_changed('passive_touch_fills_percentage') self.__passive_touch_fills_percentage = value @property def seniority(self) -> str: """The seniority of the bond.""" return self.__seniority @seniority.setter def seniority(self, value: str): self._property_changed('seniority') self.__seniority = value @property def leg1_index(self) -> str: """If floating index leg, the index.""" return self.__leg1_index @leg1_index.setter def leg1_index(self, value: str): self._property_changed('leg1_index') self.__leg1_index = value @property def high_unadjusted(self) -> float: """Unadjusted high level of an asset based on official exchange fixing or calculation agent marked level.""" return self.__high_unadjusted @high_unadjusted.setter def high_unadjusted(self, value: float): self._property_changed('high_unadjusted') self.__high_unadjusted = value @property def submission_event(self) -> str: """Describes the status of the submission.""" return self.__submission_event @submission_event.setter def submission_event(self, value: str): self._property_changed('submission_event') self.__submission_event = value @property def tv_product_mnemonic(self) -> str: """Unique by Trade Vault Product based on Product Taxonomy.""" return self.__tv_product_mnemonic @tv_product_mnemonic.setter def tv_product_mnemonic(self, value: str): self._property_changed('tv_product_mnemonic') self.__tv_product_mnemonic = value @property def avg_trade_rate_label(self): return self.__avg_trade_rate_label @avg_trade_rate_label.setter def avg_trade_rate_label(self, value): self._property_changed('avg_trade_rate_label') self.__avg_trade_rate_label = value @property def last_activity_date(self) -> datetime.date: """Last date an idea was entered in Alpha Capture.""" return self.__last_activity_date @last_activity_date.setter def last_activity_date(self, value: datetime.date): self._property_changed('last_activity_date') self.__last_activity_date = value @property def dissemination_time(self) -> datetime.datetime: """Time of dissemination.""" return self.__dissemination_time @dissemination_time.setter def dissemination_time(self, value: datetime.datetime): self._property_changed('dissemination_time') self.__dissemination_time = value @property def price_to_cash(self) -> float: """Price to cash.""" return self.__price_to_cash @price_to_cash.setter def price_to_cash(self, value: float): self._property_changed('price_to_cash') self.__price_to_cash = value @property def buy10cents(self) -> float: """The amount GS would buy for 10 cents charge.""" return self.__buy10cents @buy10cents.setter def buy10cents(self, value: float): self._property_changed('buy10cents') self.__buy10cents = value @property def nav_spread(self) -> float: """Net asset value spread. Quoted (running) spread (mid) of the underlying basket of single name CDS. (Theoretical Index value). In basis points.""" return self.__nav_spread @nav_spread.setter def nav_spread(self, value: float): self._property_changed('nav_spread') self.__nav_spread = value @property def venue_mic(self) -> str: """MIC code of the venue.""" return self.__venue_mic @venue_mic.setter def venue_mic(self, value: str): self._property_changed('venue_mic') self.__venue_mic = value @property def dollar_total_return(self) -> float: """The dollar total return of an instrument.""" return self.__dollar_total_return @dollar_total_return.setter def dollar_total_return(self, value: float): self._property_changed('dollar_total_return') self.__dollar_total_return = value @property def block_unit(self) -> str: """Unit of measure used for Block trades.""" return self.__block_unit @block_unit.setter def block_unit(self, value: str): self._property_changed('block_unit') self.__block_unit = value @property def mid_spread(self) -> float: """Spread between the yields of a debt security and its benchmark when both are purchased at mid price.""" return self.__mid_spread @mid_spread.setter def mid_spread(self, value: float): self._property_changed('mid_spread') self.__mid_spread = value @property def istat_province_code(self) -> float: """Italian province code (ISTAT 2019).""" return self.__istat_province_code @istat_province_code.setter def istat_province_code(self, value: float): self._property_changed('istat_province_code') self.__istat_province_code = value @property def total_recovered_by_state(self) -> float: """Total number of recovered cases by state.""" return self.__total_recovered_by_state @total_recovered_by_state.setter def total_recovered_by_state(self, value: float): self._property_changed('total_recovered_by_state') self.__total_recovered_by_state = value @property def repurchase_rate(self) -> float: """Repurchase Rate.""" return self.__repurchase_rate @repurchase_rate.setter def repurchase_rate(self, value: float): self._property_changed('repurchase_rate') self.__repurchase_rate = value @property def data_source(self) -> str: """The source of data from the feeds.""" return self.__data_source @data_source.setter def data_source(self, value: str): self._property_changed('data_source') self.__data_source = value @property def total_being_tested(self) -> float: """Total number of cases waiting for test results.""" return self.__total_being_tested @total_being_tested.setter def total_being_tested(self, value: float): self._property_changed('total_being_tested') self.__total_being_tested = value @property def cleared_or_bilateral(self) -> str: """An indication of whether or not an SB swap transaction is going to be cleared by a derivatives clearing organization.""" return self.__cleared_or_bilateral @cleared_or_bilateral.setter def cleared_or_bilateral(self, value: str): self._property_changed('cleared_or_bilateral') self.__cleared_or_bilateral = value @property def metric_name(self) -> str: """Name of the metric.""" return self.__metric_name @metric_name.setter def metric_name(self, value: str): self._property_changed('metric_name') self.__metric_name = value @property def ask_gspread(self) -> float: """Ask G spread.""" return self.__ask_gspread @ask_gspread.setter def ask_gspread(self, value: float): self._property_changed('ask_gspread') self.__ask_gspread = value @property def forecast_hour(self) -> float: """The hour forecast for maximum or minumum temperature of given location.""" return self.__forecast_hour @forecast_hour.setter def forecast_hour(self, value: float): self._property_changed('forecast_hour') self.__forecast_hour = value @property def leg2_payment_type(self) -> str: """Type of payment stream on leg 2.""" return self.__leg2_payment_type @leg2_payment_type.setter def leg2_payment_type(self, value: str): self._property_changed('leg2_payment_type') self.__leg2_payment_type = value @property def cal_spread_mis_pricing(self) -> float: """Futures implied funding rate relative to interest rate benchmark (usually Libor- based). Represents dividend-adjusted rate at which investor is borrowing (lending) when long (short) future.""" return self.__cal_spread_mis_pricing @cal_spread_mis_pricing.setter def cal_spread_mis_pricing(self, value: float): self._property_changed('cal_spread_mis_pricing') self.__cal_spread_mis_pricing = value @property def total_tested_negative(self) -> float: """Total number of test cases that are negative.""" return self.__total_tested_negative @total_tested_negative.setter def total_tested_negative(self, value: float): self._property_changed('total_tested_negative') self.__total_tested_negative = value @property def rate366(self) -> float: """Rate with interest calculated according to the number of days in a leap year, 366.""" return self.__rate366 @rate366.setter def rate366(self, value: float): self._property_changed('rate366') self.__rate366 = value @property def platform(self) -> str: """Originating platform.""" return self.__platform @platform.setter def platform(self, value: str): self._property_changed('platform') self.__platform = value @property def rate365(self) -> float: """Rate with interest calculated according to a normal number of days in the total year, 365.""" return self.__rate365 @rate365.setter def rate365(self, value: float): self._property_changed('rate365') self.__rate365 = value @property def fixed_rate_frequency(self) -> str: """Tenor""" return self.__fixed_rate_frequency @fixed_rate_frequency.setter def fixed_rate_frequency(self, value: str): self._property_changed('fixed_rate_frequency') self.__fixed_rate_frequency = value @property def rate360(self) -> float: """Rate with interest calculated according to the discount method, using the number of days used by banks, 360.""" return self.__rate360 @rate360.setter def rate360(self, value: float): self._property_changed('rate360') self.__rate360 = value @property def is_continuous(self) -> bool: """Flag indicating whether the bucket is within continuous or not.""" return self.__is_continuous @is_continuous.setter def is_continuous(self, value: bool): self._property_changed('is_continuous') self.__is_continuous = value @property def value(self) -> float: """The given value.""" return self.__value @value.setter def value(self, value: float): self._property_changed('value') self.__value = value @property def payer_designated_maturity(self) -> str: """Tenor""" return self.__payer_designated_maturity @payer_designated_maturity.setter def payer_designated_maturity(self, value: str): self._property_changed('payer_designated_maturity') self.__payer_designated_maturity = value @property def product_type(self) -> str: """Product type of basket""" return self.__product_type @product_type.setter def product_type(self, value: str): self._property_changed('product_type') self.__product_type = value @property def mdv22_day(self) -> float: """Twenty two day median of shares traded per day.""" return self.__mdv22_day @mdv22_day.setter def mdv22_day(self, value: float): self._property_changed('mdv22_day') self.__mdv22_day = value @property def twap_realized_bps(self) -> float: """Consolidated realised performance vs TWAP In Limit, in bps.""" return self.__twap_realized_bps @twap_realized_bps.setter def twap_realized_bps(self, value: float): self._property_changed('twap_realized_bps') self.__twap_realized_bps = value @property def test_measure_label(self) -> str: """Semantic label for the test measure.""" return self.__test_measure_label @test_measure_label.setter def test_measure_label(self, value: str): self._property_changed('test_measure_label') self.__test_measure_label = value @property def quantity(self) -> float: """Number of units of a given asset held within a portfolio.""" return self.__quantity @quantity.setter def quantity(self, value: float): self._property_changed('quantity') self.__quantity = value @property def report_id(self) -> str: """Report Identifier.""" return self.__report_id @report_id.setter def report_id(self, value: str): self._property_changed('report_id') self.__report_id = value @property def index_weight(self) -> float: """Weight of MSCI World positions in the region or sector (%).""" return self.__index_weight @index_weight.setter def index_weight(self, value: float): self._property_changed('index_weight') self.__index_weight = value @property def macs_primary_asset_class(self) -> str: """Indicates the primary asset class the multi asset class swap falls under.""" return self.__macs_primary_asset_class @macs_primary_asset_class.setter def macs_primary_asset_class(self, value: str): self._property_changed('macs_primary_asset_class') self.__macs_primary_asset_class = value @property def trader(self) -> str: """Trader name.""" return self.__trader @trader.setter def trader(self, value: str): self._property_changed('trader') self.__trader = value @property def leg2_price_type(self) -> str: """Price denomination and unit of leg 2.""" return self.__leg2_price_type @leg2_price_type.setter def leg2_price_type(self, value: str): self._property_changed('leg2_price_type') self.__leg2_price_type = value @property def total_active(self) -> float: """Total number of confirmed cases minus recovered and fatalities.""" return self.__total_active @total_active.setter def total_active(self, value: float): self._property_changed('total_active') self.__total_active = value @property def gsid2(self) -> str: """GSID identifier for datasets which have two gsids in the measures.""" return self.__gsid2 @gsid2.setter def gsid2(self, value: str): self._property_changed('gsid2') self.__gsid2 = value @property def matched_maturity_ois_swap_spread(self) -> float: """Spread between a Matched Maturity OIS Swap Rate and the yield of the bond.""" return self.__matched_maturity_ois_swap_spread @matched_maturity_ois_swap_spread.setter def matched_maturity_ois_swap_spread(self, value: float): self._property_changed('matched_maturity_ois_swap_spread') self.__matched_maturity_ois_swap_spread = value @property def valuation_date(self) -> str: """Specific to rates, the date a valuation is recorded.""" return self.__valuation_date @valuation_date.setter def valuation_date(self, value: str): self._property_changed('valuation_date') self.__valuation_date = value @property def restrict_gs_federation(self) -> bool: """Restricts GS Federation Visibility.""" return self.__restrict_gs_federation @restrict_gs_federation.setter def restrict_gs_federation(self, value: bool): self._property_changed('restrict_gs_federation') self.__restrict_gs_federation = value @property def position_source(self) -> str: """Specifies position data location for the portfolio""" return self.__position_source @position_source.setter def position_source(self, value: str): self._property_changed('position_source') self.__position_source = value @property def tcm_cost_horizon6_hour(self) -> float: """TCM cost with a 6 hour time horizon.""" return self.__tcm_cost_horizon6_hour @tcm_cost_horizon6_hour.setter def tcm_cost_horizon6_hour(self, value: float): self._property_changed('tcm_cost_horizon6_hour') self.__tcm_cost_horizon6_hour = value @property def buy200cents(self) -> float: """The amount GS would buy for 200 cents charge.""" return self.__buy200cents @buy200cents.setter def buy200cents(self, value: float): self._property_changed('buy200cents') self.__buy200cents = value @property def vwap_unrealized_bps(self) -> float: """Consolidated unrealised performance vs VWAP, in bps.""" return self.__vwap_unrealized_bps @vwap_unrealized_bps.setter def vwap_unrealized_bps(self, value: float): self._property_changed('vwap_unrealized_bps') self.__vwap_unrealized_bps = value @property def price_to_book(self) -> float: """Price to book.""" return self.__price_to_book @price_to_book.setter def price_to_book(self, value: float): self._property_changed('price_to_book') self.__price_to_book = value @property def isin(self) -> str: """ISIN - International securities identifier number (subect to licensing).""" return self.__isin @isin.setter def isin(self, value: str): self._property_changed('isin') self.__isin = value @property def pl_id(self) -> str: """Platts Symbol Name or description.""" return self.__pl_id @pl_id.setter def pl_id(self, value: str): self._property_changed('pl_id') self.__pl_id = value @property def last_returns_start_date(self) -> datetime.date: """Hedge fund last returns after start date.""" return self.__last_returns_start_date @last_returns_start_date.setter def last_returns_start_date(self, value: datetime.date): self._property_changed('last_returns_start_date') self.__last_returns_start_date = value @property def collateral_value_variance(self) -> float: """Diffrence between actual and required collateral levels.""" return self.__collateral_value_variance @collateral_value_variance.setter def collateral_value_variance(self, value: float): self._property_changed('collateral_value_variance') self.__collateral_value_variance = value @property def year(self) -> str: """Year of forecast.""" return self.__year @year.setter def year(self, value: str): self._property_changed('year') self.__year = value @property def forecast_period(self) -> str: """Year, quarter or horizon for which the forecast holds.""" return self.__forecast_period @forecast_period.setter def forecast_period(self, value: str): self._property_changed('forecast_period') self.__forecast_period = value @property def call_first_date(self) -> datetime.date: """ISO 8601-formatted date""" return self.__call_first_date @call_first_date.setter def call_first_date(self, value: datetime.date): self._property_changed('call_first_date') self.__call_first_date = value @property def data_set_ids(self) -> Tuple[Tuple[str, ...], ...]: """The dataset Ids.""" return self.__data_set_ids @data_set_ids.setter def data_set_ids(self, value: Tuple[Tuple[str, ...], ...]): self._property_changed('data_set_ids') self.__data_set_ids = value @property def economic_terms_hash(self) -> str: """Hash code for an asset.""" return self.__economic_terms_hash @economic_terms_hash.setter def economic_terms_hash(self, value: str): self._property_changed('economic_terms_hash') self.__economic_terms_hash = value @property def num_beds(self) -> float: """Maximum number of beds for which a hospital holds a license to operate; however, many hospitals do not operate all the beds for which they are licensed.""" return self.__num_beds @num_beds.setter def num_beds(self, value: float): self._property_changed('num_beds') self.__num_beds = value @property def sell20bps(self) -> float: """The amount GS would sell for 20 bps charge.""" return self.__sell20bps @sell20bps.setter def sell20bps(self, value: float): self._property_changed('sell20bps') self.__sell20bps = value @property def client_type(self) -> str: """Client type, ex: Hedge Fund, Real Money, Corporate, etc.""" return self.__client_type @client_type.setter def client_type(self, value: str): self._property_changed('client_type') self.__client_type = value @property def percentage_close_executed_quantity(self) -> float: """Percent of order filled in closing Auction.""" return self.__percentage_close_executed_quantity @percentage_close_executed_quantity.setter def percentage_close_executed_quantity(self, value: float): self._property_changed('percentage_close_executed_quantity') self.__percentage_close_executed_quantity = value @property def macaulay_duration(self) -> float: """Measure of a bond's price sensitivity to changes in interest rates.""" return self.__macaulay_duration @macaulay_duration.setter def macaulay_duration(self, value: float): self._property_changed('macaulay_duration') self.__macaulay_duration = value @property def available_inventory(self) -> float: """An estimated indication of the share quantity potentially available to borrow in the relevant asset.""" return self.__available_inventory @available_inventory.setter def available_inventory(self, value: float): self._property_changed('available_inventory') self.__available_inventory = value @property def est1_day_complete_pct(self) -> float: """Estimated 1 day completion percentage.""" return self.__est1_day_complete_pct @est1_day_complete_pct.setter def est1_day_complete_pct(self, value: float): self._property_changed('est1_day_complete_pct') self.__est1_day_complete_pct = value @property def relative_hit_rate_ytd(self) -> float: """Hit Rate Ratio Year to Date.""" return self.__relative_hit_rate_ytd @relative_hit_rate_ytd.setter def relative_hit_rate_ytd(self, value: float): self._property_changed('relative_hit_rate_ytd') self.__relative_hit_rate_ytd = value @property def created_by_id(self) -> str: """Unique identifier of user who created the object.""" return self.__created_by_id @created_by_id.setter def created_by_id(self, value: str): self._property_changed('created_by_id') self.__created_by_id = value @property def market_data_type(self) -> str: """The market data type (e.g. IR BASIS, FX Vol). This can be resolved into a dataset when combined with vendor and intraday=true/false.""" return self.__market_data_type @market_data_type.setter def market_data_type(self, value: str): self._property_changed('market_data_type') self.__market_data_type = value @property def real_short_rates_contribution(self) -> float: """Contribution of short rate component to real FCI.""" return self.__real_short_rates_contribution @real_short_rates_contribution.setter def real_short_rates_contribution(self, value: float): self._property_changed('real_short_rates_contribution') self.__real_short_rates_contribution = value @property def metric_category(self) -> str: """Category of the metric.""" return self.__metric_category @metric_category.setter def metric_category(self, value: str): self._property_changed('metric_category') self.__metric_category = value @property def annualized_carry(self) -> float: """Annualized carry of the bond.""" return self.__annualized_carry @annualized_carry.setter def annualized_carry(self, value: float): self._property_changed('annualized_carry') self.__annualized_carry = value @property def value_previous(self) -> str: """Value for the previous period after the revision (if revision is applicable).""" return self.__value_previous @value_previous.setter def value_previous(self, value: str): self._property_changed('value_previous') self.__value_previous = value @property def transmission_classification(self) -> str: """Classification of the transmission.""" return self.__transmission_classification @transmission_classification.setter def transmission_classification(self, value: str): self._property_changed('transmission_classification') self.__transmission_classification = value @property def avg_trade_rate(self) -> float: """The Average Trading Rate of the stock on the particular date.""" return self.__avg_trade_rate @avg_trade_rate.setter def avg_trade_rate(self, value: float): self._property_changed('avg_trade_rate') self.__avg_trade_rate = value @property def short_level(self) -> float: """Level of the 5-day normalized flow for short selling/covering.""" return self.__short_level @short_level.setter def short_level(self, value: float): self._property_changed('short_level') self.__short_level = value @property def version(self) -> float: """Version number.""" return self.__version @version.setter def version(self, value: float): self._property_changed('version') self.__version = value @property def category_type(self) -> str: """Type of the category.""" return self.__category_type @category_type.setter def category_type(self, value: str): self._property_changed('category_type') self.__category_type = value @property def policy_rate_expectation(self) -> float: """Returns the historical policy rate expectations for a given meeting date.""" return self.__policy_rate_expectation @policy_rate_expectation.setter def policy_rate_expectation(self, value: float): self._property_changed('policy_rate_expectation') self.__policy_rate_expectation = value @property def upload_date(self) -> datetime.date: """Index Position Upload Date.""" return self.__upload_date @upload_date.setter def upload_date(self, value: datetime.date): self._property_changed('upload_date') self.__upload_date = value @property def block_off_facility(self) -> str: """An indication of whether this is a block trade or off-facility swap.""" return self.__block_off_facility @block_off_facility.setter def block_off_facility(self, value: str): self._property_changed('block_off_facility') self.__block_off_facility = value @property def unrealized_vwap_performance_usd(self) -> float: """Average execution price vs Consolidated VWAP (in limit) since order inception ??? unrealized in $.""" return self.__unrealized_vwap_performance_usd @unrealized_vwap_performance_usd.setter def unrealized_vwap_performance_usd(self, value: float): self._property_changed('unrealized_vwap_performance_usd') self.__unrealized_vwap_performance_usd = value @property def pace_of_rollp75(self) -> float: """The 75th-percentile of the pace of roll compared to previous quarters (over the last 5 years) at the same point in time, i.e. cross-sectional..""" return self.__pace_of_rollp75 @pace_of_rollp75.setter def pace_of_rollp75(self, value: float): self._property_changed('pace_of_rollp75') self.__pace_of_rollp75 = value @property def earnings_per_share_positive(self) -> float: """Earnings per share positive.""" return self.__earnings_per_share_positive @earnings_per_share_positive.setter def earnings_per_share_positive(self, value: float): self._property_changed('earnings_per_share_positive') self.__earnings_per_share_positive = value @property def num_icu_beds(self) -> float: """All qualified Intensive Care Unit beds, including psychiatric ICU beds and Detox ICU beds.""" return self.__num_icu_beds @num_icu_beds.setter def num_icu_beds(self, value: float): self._property_changed('num_icu_beds') self.__num_icu_beds = value @property def bucket_volume_in_percentage(self) -> float: """Forecast of the percentage volume of shares in the bucket relative to the total volume forecasted for the day.""" return self.__bucket_volume_in_percentage @bucket_volume_in_percentage.setter def bucket_volume_in_percentage(self, value: float): self._property_changed('bucket_volume_in_percentage') self.__bucket_volume_in_percentage = value @property def estimated_trading_cost(self) -> float: """The estimated transaction cost of the order.""" return self.__estimated_trading_cost @estimated_trading_cost.setter def estimated_trading_cost(self, value: float): self._property_changed('estimated_trading_cost') self.__estimated_trading_cost = value @property def eid(self) -> str: """Goldman Sachs internal exchange identifier.""" return self.__eid @eid.setter def eid(self, value: str): self._property_changed('eid') self.__eid = value @property def relative_return_qtd(self) -> float: """Relative Return Quarter to Date.""" return self.__relative_return_qtd @relative_return_qtd.setter def relative_return_qtd(self, value: float): self._property_changed('relative_return_qtd') self.__relative_return_qtd = value @property def assessed_test_measure(self) -> float: """Generic field to hold aggregated assessed numeric measure for test result. This field stores the aggregated test measures of entities which are not temporarily ignored by silencing (like max delay across unsilenced assets). This is further used to perform any follow up actions like alerting.""" return self.__assessed_test_measure @assessed_test_measure.setter def assessed_test_measure(self, value: float): self._property_changed('assessed_test_measure') self.__assessed_test_measure = value @property def mkt_quoting_style(self) -> str: """Quoting style.""" return self.__mkt_quoting_style @mkt_quoting_style.setter def mkt_quoting_style(self, value: str): self._property_changed('mkt_quoting_style') self.__mkt_quoting_style = value @property def expiration_tenor(self) -> str: """Tenor""" return self.__expiration_tenor @expiration_tenor.setter def expiration_tenor(self, value: str): self._property_changed('expiration_tenor') self.__expiration_tenor = value @property def price_limit(self) -> float: """Limit price of the order.""" return self.__price_limit @price_limit.setter def price_limit(self, value: float): self._property_changed('price_limit') self.__price_limit = value @property def market_model_id(self) -> str: """Marquee unique market model identifier""" return self.__market_model_id @market_model_id.setter def market_model_id(self, value: str): self._property_changed('market_model_id') self.__market_model_id = value @property def receiver_frequency(self) -> str: """Tenor""" return self.__receiver_frequency @receiver_frequency.setter def receiver_frequency(self, value: str): self._property_changed('receiver_frequency') self.__receiver_frequency = value @property def realized_correlation(self) -> float: """Correlation of an asset realized by observations of market prices.""" return self.__realized_correlation @realized_correlation.setter def realized_correlation(self, value: float): self._property_changed('realized_correlation') self.__realized_correlation = value @property def issue_status(self) -> str: """Status of the issue.""" return self.__issue_status @issue_status.setter def issue_status(self, value: str): self._property_changed('issue_status') self.__issue_status = value @property def collateral_value_actual(self) -> float: """Value of collateral covering the given position.""" return self.__collateral_value_actual @collateral_value_actual.setter def collateral_value_actual(self, value: float): self._property_changed('collateral_value_actual') self.__collateral_value_actual = value @property def atm_fwd_rate(self) -> float: """ATM forward rate.""" return self.__atm_fwd_rate @atm_fwd_rate.setter def atm_fwd_rate(self, value: float): self._property_changed('atm_fwd_rate') self.__atm_fwd_rate = value @property def tcm_cost_participation_rate75_pct(self) -> float: """TCM cost with a 75 percent participation rate.""" return self.__tcm_cost_participation_rate75_pct @tcm_cost_participation_rate75_pct.setter def tcm_cost_participation_rate75_pct(self, value: float): self._property_changed('tcm_cost_participation_rate75_pct') self.__tcm_cost_participation_rate75_pct = value @property def close(self) -> float: """Closing level of an asset based on official exchange fixing or calculation agent marked level.""" return self.__close @close.setter def close(self, value: float): self._property_changed('close') self.__close = value @property def es_product_impact_score(self) -> float: """A company's score for E&S subsector-relative product impact metrics.""" return self.__es_product_impact_score @es_product_impact_score.setter def es_product_impact_score(self, value: float): self._property_changed('es_product_impact_score') self.__es_product_impact_score = value @property def equity_vega(self) -> float: """Vega exposure to equity products.""" return self.__equity_vega @equity_vega.setter def equity_vega(self, value: float): self._property_changed('equity_vega') self.__equity_vega = value @property def executed_fill_quantity(self) -> float: """Executed quantity - on executions.""" return self.__executed_fill_quantity @executed_fill_quantity.setter def executed_fill_quantity(self, value: float): self._property_changed('executed_fill_quantity') self.__executed_fill_quantity = value @property def lender_payment(self) -> float: """Payment made to lender's bank in support of the income accrued from securities lending.""" return self.__lender_payment @lender_payment.setter def lender_payment(self, value: float): self._property_changed('lender_payment') self.__lender_payment = value @property def five_day_move(self) -> float: """Five day move in the price.""" return self.__five_day_move @five_day_move.setter def five_day_move(self, value: float): self._property_changed('five_day_move') self.__five_day_move = value @property def value_format(self) -> str: """Value format.""" return self.__value_format @value_format.setter def value_format(self, value: str): self._property_changed('value_format') self.__value_format = value @property def wind_chill_forecast(self) -> float: """The forecast wind chill of given units.""" return self.__wind_chill_forecast @wind_chill_forecast.setter def wind_chill_forecast(self, value: float): self._property_changed('wind_chill_forecast') self.__wind_chill_forecast = value @property def target_notional(self) -> float: """Notional value of the hedge target.""" return self.__target_notional @target_notional.setter def target_notional(self, value: float): self._property_changed('target_notional') self.__target_notional = value @property def fill_leg_id(self) -> str: """Unique identifier for the leg on which the fill executed.""" return self.__fill_leg_id @fill_leg_id.setter def fill_leg_id(self, value: str): self._property_changed('fill_leg_id') self.__fill_leg_id = value @property def rationale(self) -> str: """Reason for changing the status of a trade idea.""" return self.__rationale @rationale.setter def rationale(self, value: str): self._property_changed('rationale') self.__rationale = value @property def realized_twap_performance_bps(self) -> float: """Average execution price vs Consolidated TWAP since order inception ??? realized.""" return self.__realized_twap_performance_bps @realized_twap_performance_bps.setter def realized_twap_performance_bps(self, value: float): self._property_changed('realized_twap_performance_bps') self.__realized_twap_performance_bps = value @property def last_updated_since(self) -> datetime.datetime: """ISO 8601-formatted timestamp""" return self.__last_updated_since @last_updated_since.setter def last_updated_since(self, value: datetime.datetime): self._property_changed('last_updated_since') self.__last_updated_since = value @property def total_tests(self) -> float: """Total number of tests administered.""" return self.__total_tests @total_tests.setter def total_tests(self, value: float): self._property_changed('total_tests') self.__total_tests = value @property def equities_contribution(self) -> float: """Contribution of equity component to FCI.""" return self.__equities_contribution @equities_contribution.setter def equities_contribution(self, value: float): self._property_changed('equities_contribution') self.__equities_contribution = value @property def simon_id(self) -> str: """SIMON application asset identifier.""" return self.__simon_id @simon_id.setter def simon_id(self, value: str): self._property_changed('simon_id') self.__simon_id = value @property def congestion(self) -> float: """Congestion price component.""" return self.__congestion @congestion.setter def congestion(self, value: float): self._property_changed('congestion') self.__congestion = value @property def notes(self) -> str: """Notes for series.""" return self.__notes @notes.setter def notes(self, value: str): self._property_changed('notes') self.__notes = value @property def total_probable_senior_home(self) -> float: """Probable cases in Senior homes and care facilities.""" return self.__total_probable_senior_home @total_probable_senior_home.setter def total_probable_senior_home(self, value: float): self._property_changed('total_probable_senior_home') self.__total_probable_senior_home = value @property def event_category(self) -> str: """Category.""" return self.__event_category @event_category.setter def event_category(self, value: str): self._property_changed('event_category') self.__event_category = value @property def average_fill_rate(self) -> float: """Percent complete.""" return self.__average_fill_rate @average_fill_rate.setter def average_fill_rate(self, value: float): self._property_changed('average_fill_rate') self.__average_fill_rate = value @property def unadjusted_open(self) -> float: """Unadjusted open level of an asset based on official exchange fixing or calculation agent marked level.""" return self.__unadjusted_open @unadjusted_open.setter def unadjusted_open(self, value: float): self._property_changed('unadjusted_open') self.__unadjusted_open = value @property def criticality(self) -> float: """The upgrade criticality of a deployment.""" return self.__criticality @criticality.setter def criticality(self, value: float): self._property_changed('criticality') self.__criticality = value @property def bid_ask_spread(self) -> float: """Bid ask spread.""" return self.__bid_ask_spread @bid_ask_spread.setter def bid_ask_spread(self, value: float): self._property_changed('bid_ask_spread') self.__bid_ask_spread = value @property def arrival_mid_unrealized_bps(self) -> float: """Consolidated unrealised performance vs Arrival Mid, in bps.""" return self.__arrival_mid_unrealized_bps @arrival_mid_unrealized_bps.setter def arrival_mid_unrealized_bps(self, value: float): self._property_changed('arrival_mid_unrealized_bps') self.__arrival_mid_unrealized_bps = value @property def option_type(self) -> str: """One of two option types.""" return self.__option_type @option_type.setter def option_type(self, value: str): self._property_changed('option_type') self.__option_type = value @property def termination_date(self) -> datetime.date: """The date at which the measure becomes terminated.""" return self.__termination_date @termination_date.setter def termination_date(self, value: datetime.date): self._property_changed('termination_date') self.__termination_date = value @property def queries_per_second(self) -> float: """Queries per second.""" return self.__queries_per_second @queries_per_second.setter def queries_per_second(self, value: float): self._property_changed('queries_per_second') self.__queries_per_second = value @property def liquidity_type(self) -> str: """Taking/Providing.""" return self.__liquidity_type @liquidity_type.setter def liquidity_type(self, value: str): self._property_changed('liquidity_type') self.__liquidity_type = value @property def credit_limit(self) -> float: """The allowed credit limit.""" return self.__credit_limit @credit_limit.setter def credit_limit(self, value: float): self._property_changed('credit_limit') self.__credit_limit = value @property def rank_qtd(self) -> float: """Rank of the Contributor Quarter to Date.""" return self.__rank_qtd @rank_qtd.setter def rank_qtd(self, value: float): self._property_changed('rank_qtd') self.__rank_qtd = value @property def combined_key(self) -> str: """Key for epidemic data.""" return self.__combined_key @combined_key.setter def combined_key(self, value: str): self._property_changed('combined_key') self.__combined_key = value @property def gir_fx_forecast(self) -> float: """FX forecast value for the relative period.""" return self.__gir_fx_forecast @gir_fx_forecast.setter def gir_fx_forecast(self, value: float): self._property_changed('gir_fx_forecast') self.__gir_fx_forecast = value @property def effective_tenor(self) -> str: """Tenor""" return self.__effective_tenor @effective_tenor.setter def effective_tenor(self, value: str): self._property_changed('effective_tenor') self.__effective_tenor = value @property def gir_commodities_forecast(self) -> float: """Forecast value.""" return self.__gir_commodities_forecast @gir_commodities_forecast.setter def gir_commodities_forecast(self, value: float): self._property_changed('gir_commodities_forecast') self.__gir_commodities_forecast = value @property def relative_humidity_daily_forecast(self) -> float: """The forecast value for humidity in Percentage.""" return self.__relative_humidity_daily_forecast @relative_humidity_daily_forecast.setter def relative_humidity_daily_forecast(self, value: float): self._property_changed('relative_humidity_daily_forecast') self.__relative_humidity_daily_forecast = value @property def std30_days_subsidized_yield(self) -> float: """Average annual total returns as of most recent calendar quarter-end, does not account for any fee waivers or expense reimbursements.""" return self.__std30_days_subsidized_yield @std30_days_subsidized_yield.setter def std30_days_subsidized_yield(self, value: float): self._property_changed('std30_days_subsidized_yield') self.__std30_days_subsidized_yield = value @property def annualized_tracking_error(self) -> float: """Annualized tracking error.""" return self.__annualized_tracking_error @annualized_tracking_error.setter def annualized_tracking_error(self, value: float): self._property_changed('annualized_tracking_error') self.__annualized_tracking_error = value @property def future_month_f26(self) -> float: """Commods future month code.""" return self.__future_month_f26 @future_month_f26.setter def future_month_f26(self, value: float): self._property_changed('future_month_f26') self.__future_month_f26 = value @property def future_month_f25(self) -> float: """Commods future month code.""" return self.__future_month_f25 @future_month_f25.setter def future_month_f25(self, value: float): self._property_changed('future_month_f25') self.__future_month_f25 = value @property def vol_swap(self) -> float: """The strike in volatility terms, calculated as square root of fair variance.""" return self.__vol_swap @vol_swap.setter def vol_swap(self, value: float): self._property_changed('vol_swap') self.__vol_swap = value @property def future_month_f24(self) -> float: """Commods future month code.""" return self.__future_month_f24 @future_month_f24.setter def future_month_f24(self, value: float): self._property_changed('future_month_f24') self.__future_month_f24 = value @property def heat_index_daily_forecast(self) -> float: """The daily forecast value for heat index in Fahrenheit.""" return self.__heat_index_daily_forecast @heat_index_daily_forecast.setter def heat_index_daily_forecast(self, value: float): self._property_changed('heat_index_daily_forecast') self.__heat_index_daily_forecast = value @property def future_month_f23(self) -> float: """Commods future month code.""" return self.__future_month_f23 @future_month_f23.setter def future_month_f23(self, value: float): self._property_changed('future_month_f23') self.__future_month_f23 = value @property def real_fci(self) -> float: """Real FCI value.""" return self.__real_fci @real_fci.setter def real_fci(self, value: float): self._property_changed('real_fci') self.__real_fci = value @property def block_trades_and_large_notional_off_facility_swaps(self) -> str: """An indication of whether this is a block trade or off-facility swap.""" return self.__block_trades_and_large_notional_off_facility_swaps @block_trades_and_large_notional_off_facility_swaps.setter def block_trades_and_large_notional_off_facility_swaps(self, value: str): self._property_changed('block_trades_and_large_notional_off_facility_swaps') self.__block_trades_and_large_notional_off_facility_swaps = value @property def future_month_f22(self) -> float: """Commods future month code.""" return self.__future_month_f22 @future_month_f22.setter def future_month_f22(self, value: float): self._property_changed('future_month_f22') self.__future_month_f22 = value @property def buy1point5bps(self) -> float: """The amount GS would buy for 1.5 bps charge.""" return self.__buy1point5bps @buy1point5bps.setter def buy1point5bps(self, value: float): self._property_changed('buy1point5bps') self.__buy1point5bps = value @property def future_month_f21(self) -> float: """Commods future month code.""" return self.__future_month_f21 @future_month_f21.setter def future_month_f21(self, value: float): self._property_changed('future_month_f21') self.__future_month_f21 = value @property def expiration_settlement_date(self) -> datetime.date: """Date on which cash/stocks are settled after expiration date.""" return self.__expiration_settlement_date @expiration_settlement_date.setter def expiration_settlement_date(self, value: datetime.date): self._property_changed('expiration_settlement_date') self.__expiration_settlement_date = value @property def absolute_return_qtd(self) -> float: """Absolute Return Quarter to Date.""" return self.__absolute_return_qtd @absolute_return_qtd.setter def absolute_return_qtd(self, value: float): self._property_changed('absolute_return_qtd') self.__absolute_return_qtd = value @property def gross_exposure(self) -> float: """Sum of absolute long and short exposures in the portfolio. If you are $60 short and $40 long, then the grossExposure would be $100 (60+40).""" return self.__gross_exposure @gross_exposure.setter def gross_exposure(self, value: float): self._property_changed('gross_exposure') self.__gross_exposure = value @property def volume(self) -> float: """Accumulated number of shares, lots or contracts traded according to the market convention.""" return self.__volume @volume.setter def volume(self, value: float): self._property_changed('volume') self.__volume = value @property def adv(self) -> float: """Average number of shares or units of a given asset traded over a defined period.""" return self.__adv @adv.setter def adv(self, value: float): self._property_changed('adv') self.__adv = value @property def short_conviction_medium(self) -> float: """The count of short ideas with medium conviction.""" return self.__short_conviction_medium @short_conviction_medium.setter def short_conviction_medium(self, value: float): self._property_changed('short_conviction_medium') self.__short_conviction_medium = value @property def complete_test_measure(self) -> float: """Generic field to hold aggregated complete numeric measure for test result. This field stores the aggregated test measures of all the entities (like max delay across all the assets, either silenced or unsilenced).""" return self.__complete_test_measure @complete_test_measure.setter def complete_test_measure(self, value: float): self._property_changed('complete_test_measure') self.__complete_test_measure = value @property def exchange(self) -> str: """Name of marketplace where security, derivative or other instrument is traded""" return self.__exchange @exchange.setter def exchange(self, value: str): self._property_changed('exchange') self.__exchange = value @property def es_policy_score(self) -> float: """A company's score for E&S subsector-relative policy metrics.""" return self.__es_policy_score @es_policy_score.setter def es_policy_score(self, value: float): self._property_changed('es_policy_score') self.__es_policy_score = value @property def roll_volume_std(self) -> float: """Cross-sectional roll volume in standard deviations.""" return self.__roll_volume_std @roll_volume_std.setter def roll_volume_std(self, value: float): self._property_changed('roll_volume_std') self.__roll_volume_std = value @property def temperature_daily_forecast(self) -> float: """The forecast temperature of diff types in Fahrenheit.""" return self.__temperature_daily_forecast @temperature_daily_forecast.setter def temperature_daily_forecast(self, value: float): self._property_changed('temperature_daily_forecast') self.__temperature_daily_forecast = value @property def relative_payoff_qtd(self) -> float: """Total win divided by total loss Quarter to date.""" return self.__relative_payoff_qtd @relative_payoff_qtd.setter def relative_payoff_qtd(self, value: float): self._property_changed('relative_payoff_qtd') self.__relative_payoff_qtd = value @property def on_loan_percentage(self) -> float: """On loan ratio as compared net asset value of lending fund.""" return self.__on_loan_percentage @on_loan_percentage.setter def on_loan_percentage(self, value: float): self._property_changed('on_loan_percentage') self.__on_loan_percentage = value @property def twap_remaining_slices(self) -> float: """The number of slices remaining on a TWAP.""" return self.__twap_remaining_slices @twap_remaining_slices.setter def twap_remaining_slices(self, value: float): self._property_changed('twap_remaining_slices') self.__twap_remaining_slices = value @property def fair_variance(self) -> float: """Strike such that the price of an uncapped variance swap on the underlying index is zero at inception.""" return self.__fair_variance @fair_variance.setter def fair_variance(self, value: float): self._property_changed('fair_variance') self.__fair_variance = value @property def hit_rate_wtd(self) -> float: """Hit Rate Ratio Week to Date.""" return self.__hit_rate_wtd @hit_rate_wtd.setter def hit_rate_wtd(self, value: float): self._property_changed('hit_rate_wtd') self.__hit_rate_wtd = value @property def previous_close_realized_cash(self) -> float: """Realised performance vs Previous Close, in USD.""" return self.__previous_close_realized_cash @previous_close_realized_cash.setter def previous_close_realized_cash(self, value: float): self._property_changed('previous_close_realized_cash') self.__previous_close_realized_cash = value @property def realized_volatility(self) -> float: """Volatility of an asset realized by observations of market prices.""" return self.__realized_volatility @realized_volatility.setter def realized_volatility(self, value: float): self._property_changed('realized_volatility') self.__realized_volatility = value @property def unexecuted_quantity(self) -> float: """Un-executed quantity, in shares.""" return self.__unexecuted_quantity @unexecuted_quantity.setter def unexecuted_quantity(self, value: float): self._property_changed('unexecuted_quantity') self.__unexecuted_quantity = value @property def proceeds_asset_swap_spread1m(self) -> float: """Proceeds asset swap spread vs 1m tenor.""" return self.__proceeds_asset_swap_spread1m @proceeds_asset_swap_spread1m.setter def proceeds_asset_swap_spread1m(self, value: float): self._property_changed('proceeds_asset_swap_spread1m') self.__proceeds_asset_swap_spread1m = value @property def clone_parent_id(self) -> str: """Marquee unique identifier""" return self.__clone_parent_id @clone_parent_id.setter def clone_parent_id(self, value: str): self._property_changed('clone_parent_id') self.__clone_parent_id = value @property def wind_speed_hourly_forecast(self) -> float: """The hourly forecast value for wind speed in Mph.""" return self.__wind_speed_hourly_forecast @wind_speed_hourly_forecast.setter def wind_speed_hourly_forecast(self, value: float): self._property_changed('wind_speed_hourly_forecast') self.__wind_speed_hourly_forecast = value @property def etf_flow_ratio(self) -> float: """Ratio of signed ETF dollar flow to market cap of global stocks, i.e. proportion of company's market cap that is part of ETF flows observed weekly. Normalised between -3 (low level) and +3 (high level).""" return self.__etf_flow_ratio @etf_flow_ratio.setter def etf_flow_ratio(self, value: float): self._property_changed('etf_flow_ratio') self.__etf_flow_ratio = value @property def asset_parameters_receiver_rate_option(self) -> str: """The underlying benchmark for the payer, e.g. USD-LIBOR-BBA, EUR-EURIBOR- TELERATE.""" return self.__asset_parameters_receiver_rate_option @asset_parameters_receiver_rate_option.setter def asset_parameters_receiver_rate_option(self, value: str): self._property_changed('asset_parameters_receiver_rate_option') self.__asset_parameters_receiver_rate_option = value @property def buy60cents(self) -> float: """The amount GS would buy for 60 cents charge.""" return self.__buy60cents @buy60cents.setter def buy60cents(self, value: float): self._property_changed('buy60cents') self.__buy60cents = value @property def security_sub_type_id(self) -> str: """PadMaster unique security sub type identifier.""" return self.__security_sub_type_id @security_sub_type_id.setter def security_sub_type_id(self, value: str): self._property_changed('security_sub_type_id') self.__security_sub_type_id = value @property def message(self) -> str: """Miscellaneous remarks.""" return self.__message @message.setter def message(self, value: str): self._property_changed('message') self.__message = value @property def sts_rates_country(self) -> str: """Country of interest rate risk for STS assets.""" return self.__sts_rates_country @sts_rates_country.setter def sts_rates_country(self, value: str): self._property_changed('sts_rates_country') self.__sts_rates_country = value @property def sell65cents(self) -> float: """The amount GS would sell for 65 cents charge.""" return self.__sell65cents @sell65cents.setter def sell65cents(self, value: float): self._property_changed('sell65cents') self.__sell65cents = value @property def horizon(self) -> str: """Time period indicating the validity of the idea. Eg. 2d (2 days), 1w (1 week), 3m (3 months), 1y (1 year).""" return self.__horizon @horizon.setter def horizon(self, value: str): self._property_changed('horizon') self.__horizon = value @property def would_if_good_level(self) -> float: """If specified, allows the remaining quantity of an algo to be filled like a limit order.""" return self.__would_if_good_level @would_if_good_level.setter def would_if_good_level(self, value: float): self._property_changed('would_if_good_level') self.__would_if_good_level = value @property def buffer_threshold_required(self) -> float: """The required buffer between holdings and on loan quantity for an asset.""" return self.__buffer_threshold_required @buffer_threshold_required.setter def buffer_threshold_required(self, value: float): self._property_changed('buffer_threshold_required') self.__buffer_threshold_required = value @property def face_value(self) -> float: """The face value of the instrument.""" return self.__face_value @face_value.setter def face_value(self, value: float): self._property_changed('face_value') self.__face_value = value @property def roll_volume_hist(self) -> float: """Five Year historical roll volume.""" return self.__roll_volume_hist @roll_volume_hist.setter def roll_volume_hist(self, value: float): self._property_changed('roll_volume_hist') self.__roll_volume_hist = value @property def counter_party_status(self) -> str: """The lending status of a counterparty for a particular portfolio.""" return self.__counter_party_status @counter_party_status.setter def counter_party_status(self, value: str): self._property_changed('counter_party_status') self.__counter_party_status = value @property def composite22_day_adv(self) -> float: """Composite 22 day ADV.""" return self.__composite22_day_adv @composite22_day_adv.setter def composite22_day_adv(self, value: float): self._property_changed('composite22_day_adv') self.__composite22_day_adv = value @property def percentage_far_executed_quantity(self) -> float: """Percentage of total order filled at aggressive touch.""" return self.__percentage_far_executed_quantity @percentage_far_executed_quantity.setter def percentage_far_executed_quantity(self, value: float): self._property_changed('percentage_far_executed_quantity') self.__percentage_far_executed_quantity = value @property def loan_spread_required(self) -> float: """The minimum spread requirement for a securities lending loan.""" return self.__loan_spread_required @loan_spread_required.setter def loan_spread_required(self, value: float): self._property_changed('loan_spread_required') self.__loan_spread_required = value @property def asset_class(self) -> str: """Asset classification of security. Assets are classified into broad groups which exhibit similar characteristics and behave in a consistent way under different market conditions""" return self.__asset_class @asset_class.setter def asset_class(self, value: str): self._property_changed('asset_class') self.__asset_class = value @property def sovereign_spread_contribution(self) -> float: """Contribution of sovereign spread component to FCI. Only applicable to Euro countries.""" return self.__sovereign_spread_contribution @sovereign_spread_contribution.setter def sovereign_spread_contribution(self, value: float): self._property_changed('sovereign_spread_contribution') self.__sovereign_spread_contribution = value @property def ric(self) -> str: """Reuters instrument code (subject to licensing).""" return self.__ric @ric.setter def ric(self, value: str): self._property_changed('ric') self.__ric = value @property def bucket_end_time(self) -> datetime.datetime: """The end time of the bucket in ISO 8601 formatted date and time.""" return self.__bucket_end_time @bucket_end_time.setter def bucket_end_time(self, value: datetime.datetime): self._property_changed('bucket_end_time') self.__bucket_end_time = value @property def rate_type(self) -> str: """Type of swap structured for a Central Bank swap: rate type = Meeting Forward, swaps structured trough End of Year, rateType = EOY Forward and for Spot OIS value rateType = Spot.""" return self.__rate_type @rate_type.setter def rate_type(self, value: str): self._property_changed('rate_type') self.__rate_type = value @property def total_fatalities_senior_home(self) -> float: """Confirmed fatalities in Senior homes and care facilities.""" return self.__total_fatalities_senior_home @total_fatalities_senior_home.setter def total_fatalities_senior_home(self, value: float): self._property_changed('total_fatalities_senior_home') self.__total_fatalities_senior_home = value @property def loan_status(self) -> str: """Notes which point of the lifecyle a securities lending loan is in.""" return self.__loan_status @loan_status.setter def loan_status(self, value: str): self._property_changed('loan_status') self.__loan_status = value @property def short_weight(self) -> float: """Short weight of a position in a given portfolio. Equivalent to position short exposure / total short exposure. If you have a position with a shortExposure of $20, and your portfolio shortExposure is $100, then your asset shortWeight would be 0.2 (20/100).""" return self.__short_weight @short_weight.setter def short_weight(self, value: float): self._property_changed('short_weight') self.__short_weight = value @property def geography_id(self) -> str: """Region or Country code for which the numbers are represented. For countries, it is the ISO 3166 2-digit country code.""" return self.__geography_id @geography_id.setter def geography_id(self, value: str): self._property_changed('geography_id') self.__geography_id = value @property def sell7point5bps(self) -> float: """The amount GS would sell for 7.5 bps charge.""" return self.__sell7point5bps @sell7point5bps.setter def sell7point5bps(self, value: float): self._property_changed('sell7point5bps') self.__sell7point5bps = value @property def nav(self) -> float: """Net asset value.""" return self.__nav @nav.setter def nav(self, value: float): self._property_changed('nav') self.__nav = value @property def fiscal_quarter(self) -> str: """One of the four three-month periods that make up the fiscal year.""" return self.__fiscal_quarter @fiscal_quarter.setter def fiscal_quarter(self, value: str): self._property_changed('fiscal_quarter') self.__fiscal_quarter = value @property def version_string(self) -> str: """String representing the version number.""" return self.__version_string @version_string.setter def version_string(self, value: str): self._property_changed('version_string') self.__version_string = value @property def payoff_ytd(self) -> float: """Total win divided by total loss Year to date.""" return self.__payoff_ytd @payoff_ytd.setter def payoff_ytd(self, value: float): self._property_changed('payoff_ytd') self.__payoff_ytd = value @property def market_impact(self) -> float: """Market impact is based on the Goldman Sachs Shortfall Model where available alongside best estimates from the desk.""" return self.__market_impact @market_impact.setter def market_impact(self, value: float): self._property_changed('market_impact') self.__market_impact = value @property def event_type(self) -> str: """Equals Analyst Meeting if the event indicates an analyst meeting. Equals Earnings Release if the event indicates an earnings release. Equals Sales Release when the event indicates a sales release. Indicates Drug Data when the event indicates an event related to drugs data. Equals Other for any other events.""" return self.__event_type @event_type.setter def event_type(self, value: str): self._property_changed('event_type') self.__event_type = value @property def fill_price(self) -> float: """Last execution price.""" return self.__fill_price @fill_price.setter def fill_price(self, value: float): self._property_changed('fill_price') self.__fill_price = value @property def asset_count_long(self) -> float: """Number of assets in a portfolio with long exposure.""" return self.__asset_count_long @asset_count_long.setter def asset_count_long(self, value: float): self._property_changed('asset_count_long') self.__asset_count_long = value @property def sell180cents(self) -> float: """The amount GS would sell for 180 cents charge.""" return self.__sell180cents @sell180cents.setter def sell180cents(self, value: float): self._property_changed('sell180cents') self.__sell180cents = value @property def spot(self) -> float: """Spot price.""" return self.__spot @spot.setter def spot(self, value: float): self._property_changed('spot') self.__spot = value @property def application_id(self) -> str: """Client application identifier.""" return self.__application_id @application_id.setter def application_id(self, value: str): self._property_changed('application_id') self.__application_id = value @property def indicative_close_price(self) -> float: """Indicative close price of an asset forseen at end of the day.""" return self.__indicative_close_price @indicative_close_price.setter def indicative_close_price(self, value: float): self._property_changed('indicative_close_price') self.__indicative_close_price = value @property def swap_spread(self) -> float: """Swap spread.""" return self.__swap_spread @swap_spread.setter def swap_spread(self, value: float): self._property_changed('swap_spread') self.__swap_spread = value @property def trading_restriction(self) -> bool: """Whether or not the asset has trading restrictions.""" return self.__trading_restriction @trading_restriction.setter def trading_restriction(self, value: bool): self._property_changed('trading_restriction') self.__trading_restriction = value @property def asset_parameters_pay_or_receive(self) -> str: """Pay or receive fixed""" return self.__asset_parameters_pay_or_receive @asset_parameters_pay_or_receive.setter def asset_parameters_pay_or_receive(self, value: str): self._property_changed('asset_parameters_pay_or_receive') self.__asset_parameters_pay_or_receive = value @property def price_spot_entry_unit(self) -> str: """Unit in which the opening price is reported.""" return self.__price_spot_entry_unit @price_spot_entry_unit.setter def price_spot_entry_unit(self, value: str): self._property_changed('price_spot_entry_unit') self.__price_spot_entry_unit = value @property def unrealized_arrival_performance_bps(self) -> float: """Average execution price vs Consolidated Arrival (in limit) since order inception ??? unrealized.""" return self.__unrealized_arrival_performance_bps @unrealized_arrival_performance_bps.setter def unrealized_arrival_performance_bps(self, value: float): self._property_changed('unrealized_arrival_performance_bps') self.__unrealized_arrival_performance_bps = value @property def city(self) -> str: """City for which the weather data was gathered.""" return self.__city @city.setter def city(self, value: str): self._property_changed('city') self.__city = value @property def pnl_wtd(self) -> float: """Total PnL Week to date.""" return self.__pnl_wtd @pnl_wtd.setter def pnl_wtd(self, value: float): self._property_changed('pnl_wtd') self.__pnl_wtd = value @property def covariance(self) -> float: """Covariance between two .""" return self.__covariance @covariance.setter def covariance(self, value: float): self._property_changed('covariance') self.__covariance = value @property def bucket_volume_in_shares(self) -> float: """Forecast of the total volume of shares in the bucket.""" return self.__bucket_volume_in_shares @bucket_volume_in_shares.setter def bucket_volume_in_shares(self, value: float): self._property_changed('bucket_volume_in_shares') self.__bucket_volume_in_shares = value @property def commodity_forecast(self) -> float: """Commodity forecast value for the tenor, year or quarter.""" return self.__commodity_forecast @commodity_forecast.setter def commodity_forecast(self, value: float): self._property_changed('commodity_forecast') self.__commodity_forecast = value @property def valid(self) -> float: """Valid.""" return self.__valid @valid.setter def valid(self, value: float): self._property_changed('valid') self.__valid = value @property def sts_commodity(self) -> str: """Commodity name for STS assets.""" return self.__sts_commodity @sts_commodity.setter def sts_commodity(self, value: str): self._property_changed('sts_commodity') self.__sts_commodity = value @property def initial_pricing_date(self) -> datetime.date: """Initial pricing date for basket actions.""" return self.__initial_pricing_date @initial_pricing_date.setter def initial_pricing_date(self, value: datetime.date): self._property_changed('initial_pricing_date') self.__initial_pricing_date = value @property def indication_of_end_user_exception(self) -> str: """If buyer or seller or both is electing the End User Exception.""" return self.__indication_of_end_user_exception @indication_of_end_user_exception.setter def indication_of_end_user_exception(self, value: str): self._property_changed('indication_of_end_user_exception') self.__indication_of_end_user_exception = value @property def wind_direction_hourly_forecast(self) -> float: """The hourly forecast value for wind direction.""" return self.__wind_direction_hourly_forecast @wind_direction_hourly_forecast.setter def wind_direction_hourly_forecast(self, value: float): self._property_changed('wind_direction_hourly_forecast') self.__wind_direction_hourly_forecast = value @property def es_score(self) -> float: """A company's score for E&S subsector-relative metrics.""" return self.__es_score @es_score.setter def es_score(self, value: float): self._property_changed('es_score') self.__es_score = value @property def yield_(self) -> float: """The return an investor realizes on a bond sold at the mid price.""" return self.__yield @yield_.setter def yield_(self, value: float): self._property_changed('yield_') self.__yield = value @property def fatalities_underlying_conditions_present(self) -> float: """Total number of fatalities of people with underlying conditions.""" return self.__fatalities_underlying_conditions_present @fatalities_underlying_conditions_present.setter def fatalities_underlying_conditions_present(self, value: float): self._property_changed('fatalities_underlying_conditions_present') self.__fatalities_underlying_conditions_present = value @property def price_range_in_ticks(self) -> float: """The Price Range of the stock in Ticks on the particular date.""" return self.__price_range_in_ticks @price_range_in_ticks.setter def price_range_in_ticks(self, value: float): self._property_changed('price_range_in_ticks') self.__price_range_in_ticks = value @property def pace_of_rollp25(self) -> float: """The 25th-percentile of the pace of roll compared to previous quarters (over the last 5 years) at the same point in time, i.e. cross-sectional.""" return self.__pace_of_rollp25 @pace_of_rollp25.setter def pace_of_rollp25(self, value: float): self._property_changed('pace_of_rollp25') self.__pace_of_rollp25 = value @property def day_close_realized_usd(self) -> float: """Realised performance vs Previous Close, in $.""" return self.__day_close_realized_usd @day_close_realized_usd.setter def day_close_realized_usd(self, value: float): self._property_changed('day_close_realized_usd') self.__day_close_realized_usd = value @property def pct_change(self) -> float: """Percentage change of the latest trade price or value from the adjusted historical close.""" return self.__pct_change @pct_change.setter def pct_change(self, value: float): self._property_changed('pct_change') self.__pct_change = value @property def brightness_type(self) -> str: """The type of measure required like visibility or sunshine.""" return self.__brightness_type @brightness_type.setter def brightness_type(self, value: str): self._property_changed('brightness_type') self.__brightness_type = value @property def future_month3_m(self) -> float: """Commods future month code.""" return self.__future_month3_m @future_month3_m.setter def future_month3_m(self, value: float): self._property_changed('future_month3_m') self.__future_month3_m = value @property def number_of_rolls(self) -> int: """Contract's number of rolls per year.""" return self.__number_of_rolls @number_of_rolls.setter def number_of_rolls(self, value: int): self._property_changed('number_of_rolls') self.__number_of_rolls = value @property def iso_country_code_numeric(self) -> float: """Iso numeric code.""" return self.__iso_country_code_numeric @iso_country_code_numeric.setter def iso_country_code_numeric(self, value: float): self._property_changed('iso_country_code_numeric') self.__iso_country_code_numeric = value @property def price_type(self) -> str: """Price denomination and unit.""" return self.__price_type @price_type.setter def price_type(self, value: str): self._property_changed('price_type') self.__price_type = value @property def realized_vwap_performance_usd(self) -> float: """Average execution price vs Consolidated VWAP (in limit) since order inception ??? realized in $.""" return self.__realized_vwap_performance_usd @realized_vwap_performance_usd.setter def realized_vwap_performance_usd(self, value: float): self._property_changed('realized_vwap_performance_usd') self.__realized_vwap_performance_usd = value @property def fuel_type(self) -> str: """The type of fuel used to generate electricity""" return self.__fuel_type @fuel_type.setter def fuel_type(self, value: str): self._property_changed('fuel_type') self.__fuel_type = value @property def bbid(self) -> str: """Bloomberg identifier (ticker and exchange code).""" return self.__bbid @bbid.setter def bbid(self, value: str): self._property_changed('bbid') self.__bbid = value @property def vega_notional_amount(self) -> float: """The notional for a variance swap.""" return self.__vega_notional_amount @vega_notional_amount.setter def vega_notional_amount(self, value: float): self._property_changed('vega_notional_amount') self.__vega_notional_amount = value @property def fatalities_underlying_conditions_absent(self) -> float: """Total number of fatalities of people with no underlying conditions.""" return self.__fatalities_underlying_conditions_absent @fatalities_underlying_conditions_absent.setter def fatalities_underlying_conditions_absent(self, value: float): self._property_changed('fatalities_underlying_conditions_absent') self.__fatalities_underlying_conditions_absent = value @property def effective_date(self) -> datetime.date: """The date at which the measure becomes effective.""" return self.__effective_date @effective_date.setter def effective_date(self, value: datetime.date): self._property_changed('effective_date') self.__effective_date = value @property def capped(self) -> str: """Whether a trade is capped at the block notional threshold.""" return self.__capped @capped.setter def capped(self, value: str): self._property_changed('capped') self.__capped = value @property def rating(self) -> str: """Analyst Rating, which may take on the following values.""" return self.__rating @rating.setter def rating(self, value: str): self._property_changed('rating') self.__rating = value @property def option_currency(self) -> str: """An indication of type of currency on the option premium.""" return self.__option_currency @option_currency.setter def option_currency(self, value: str): self._property_changed('option_currency') self.__option_currency = value @property def is_close_auction(self) -> bool: """Flag indicating whether the bucket is within the close auction or not.""" return self.__is_close_auction @is_close_auction.setter def is_close_auction(self, value: bool): self._property_changed('is_close_auction') self.__is_close_auction = value @property def volatility(self) -> float: """Market implied correlation between two tenors.""" return self.__volatility @volatility.setter def volatility(self, value: float): self._property_changed('volatility') self.__volatility = value @property def avg_vent_util(self) -> float: """Average number of patients on a ventilator per week.""" return self.__avg_vent_util @avg_vent_util.setter def avg_vent_util(self, value: float): self._property_changed('avg_vent_util') self.__avg_vent_util = value @property def underlying_asset_ids(self) -> Tuple[str, ...]: """Marquee IDs of the underlying assets.""" return self.__underlying_asset_ids @underlying_asset_ids.setter def underlying_asset_ids(self, value: Tuple[str, ...]): self._property_changed('underlying_asset_ids') self.__underlying_asset_ids = value @property def buy6point5bps(self) -> float: """The amount GS would buy for 6.5 bps charge.""" return self.__buy6point5bps @buy6point5bps.setter def buy6point5bps(self, value: float): self._property_changed('buy6point5bps') self.__buy6point5bps = value @property def vwap_in_limit_realized_cash(self) -> float: """Consolidated realised performance vs VWAP In Limit, in USD.""" return self.__vwap_in_limit_realized_cash @vwap_in_limit_realized_cash.setter def vwap_in_limit_realized_cash(self, value: float): self._property_changed('vwap_in_limit_realized_cash') self.__vwap_in_limit_realized_cash = value @property def estimated_closing_auction_volume(self) -> float: """Estimated closing auction volume.""" return self.__estimated_closing_auction_volume @estimated_closing_auction_volume.setter def estimated_closing_auction_volume(self, value: float): self._property_changed('estimated_closing_auction_volume') self.__estimated_closing_auction_volume = value @property def sell2bps(self) -> float: """The amount GS would sell for 2 bps charge.""" return self.__sell2bps @sell2bps.setter def sell2bps(self, value: float): self._property_changed('sell2bps') self.__sell2bps = value @property def annual_risk(self) -> float: """Annualized risk of a given portfolio, position or asset. Generally computed as annualized daily standard deviation of returns.""" return self.__annual_risk @annual_risk.setter def annual_risk(self, value: float): self._property_changed('annual_risk') self.__annual_risk = value @property def eti(self) -> str: """External Trade Identifier.""" return self.__eti @eti.setter def eti(self, value: str): self._property_changed('eti') self.__eti = value @property def vwap_in_limit_realized_bps(self) -> float: """Consolidated realised performance vs VWAP In Limit, in bps.""" return self.__vwap_in_limit_realized_bps @vwap_in_limit_realized_bps.setter def vwap_in_limit_realized_bps(self, value: float): self._property_changed('vwap_in_limit_realized_bps') self.__vwap_in_limit_realized_bps = value @property def rank_mtd(self) -> float: """Rank of the Contributor Month to Date.""" return self.__rank_mtd @rank_mtd.setter def rank_mtd(self, value: float): self._property_changed('rank_mtd') self.__rank_mtd = value @property def market_buffer(self) -> float: """The actual buffer between holdings and on loan quantity for a market.""" return self.__market_buffer @market_buffer.setter def market_buffer(self, value: float): self._property_changed('market_buffer') self.__market_buffer = value @property def future_month_j24(self) -> float: """Commods future month code.""" return self.__future_month_j24 @future_month_j24.setter def future_month_j24(self, value: float): self._property_changed('future_month_j24') self.__future_month_j24 = value @property def last_uploaded_time(self) -> datetime.datetime: """Timestamp noting last uploaded datapoint.""" return self.__last_uploaded_time @last_uploaded_time.setter def last_uploaded_time(self, value: datetime.datetime): self._property_changed('last_uploaded_time') self.__last_uploaded_time = value @property def future_month_j23(self) -> float: """Commods future month code.""" return self.__future_month_j23 @future_month_j23.setter def future_month_j23(self, value: float): self._property_changed('future_month_j23') self.__future_month_j23 = value @property def oe_id(self) -> str: """Marquee unique identifier""" return self.__oe_id @oe_id.setter def oe_id(self, value: str): self._property_changed('oe_id') self.__oe_id = value @property def future_month_j22(self) -> float: """Commods future month code.""" return self.__future_month_j22 @future_month_j22.setter def future_month_j22(self, value: float): self._property_changed('future_month_j22') self.__future_month_j22 = value @property def future_month_j21(self) -> float: """Commods future month code.""" return self.__future_month_j21 @future_month_j21.setter def future_month_j21(self, value: float): self._property_changed('future_month_j21') self.__future_month_j21 = value @property def bbid_equivalent(self) -> str: """Bloomberg identifier (ticker and country code) equivalent - i.e. for OTCs options, the equivalent BBID on exchange.""" return self.__bbid_equivalent @bbid_equivalent.setter def bbid_equivalent(self, value: str): self._property_changed('bbid_equivalent') self.__bbid_equivalent = value @property def init_buffer_threshold_required(self) -> float: """The required buffer between holdings and on loan quantity for an asset on the date of loan initiation.""" return self.__init_buffer_threshold_required @init_buffer_threshold_required.setter def init_buffer_threshold_required(self, value: float): self._property_changed('init_buffer_threshold_required') self.__init_buffer_threshold_required = value @property def leg2_designated_maturity(self) -> str: """Floating rate index designated maturity period of leg 2.""" return self.__leg2_designated_maturity @leg2_designated_maturity.setter def leg2_designated_maturity(self, value: str): self._property_changed('leg2_designated_maturity') self.__leg2_designated_maturity = value @property def matched_maturity_ois_swap_rate(self) -> float: """Matched Maturity OIS swap rate.""" return self.__matched_maturity_ois_swap_rate @matched_maturity_ois_swap_rate.setter def matched_maturity_ois_swap_rate(self, value: float): self._property_changed('matched_maturity_ois_swap_rate') self.__matched_maturity_ois_swap_rate = value @property def fair_price(self) -> float: """Fair Price for a swap.""" return self.__fair_price @fair_price.setter def fair_price(self, value: float): self._property_changed('fair_price') self.__fair_price = value @property def participation_rate_in_limit(self) -> float: """Average in limit participation rate of the order, including principal fills.""" return self.__participation_rate_in_limit @participation_rate_in_limit.setter def participation_rate_in_limit(self, value: float): self._property_changed('participation_rate_in_limit') self.__participation_rate_in_limit = value @property def ext_mkt_class(self) -> str: """External MDAPI Class.""" return self.__ext_mkt_class @ext_mkt_class.setter def ext_mkt_class(self, value: str): self._property_changed('ext_mkt_class') self.__ext_mkt_class = value @property def price_currency(self) -> str: """Denominated pricing currency.""" return self.__price_currency @price_currency.setter def price_currency(self, value: str): self._property_changed('price_currency') self.__price_currency = value @property def failed_count(self) -> float: """Number of entities that failed the test.""" return self.__failed_count @failed_count.setter def failed_count(self, value: float): self._property_changed('failed_count') self.__failed_count = value @property def leg1_index_location(self) -> str: """Location of leg.""" return self.__leg1_index_location @leg1_index_location.setter def leg1_index_location(self, value: str): self._property_changed('leg1_index_location') self.__leg1_index_location = value @property def supra_strategy(self) -> str: """Broad descriptor of a fund's investment approach. Same view permissions as the asset""" return self.__supra_strategy @supra_strategy.setter def supra_strategy(self, value: str): self._property_changed('supra_strategy') self.__supra_strategy = value @property def day_count_convention(self) -> str: """The determination of how interest accrues over time for the SB swap.""" return self.__day_count_convention @day_count_convention.setter def day_count_convention(self, value: str): self._property_changed('day_count_convention') self.__day_count_convention = value @property def rounded_notional_amount1(self) -> float: """The total Notional amount or quantity of units of the underlying asset.""" return self.__rounded_notional_amount1 @rounded_notional_amount1.setter def rounded_notional_amount1(self, value: float): self._property_changed('rounded_notional_amount1') self.__rounded_notional_amount1 = value @property def rounded_notional_amount2(self) -> float: """Same as Rounded Notional Amount 1.""" return self.__rounded_notional_amount2 @rounded_notional_amount2.setter def rounded_notional_amount2(self, value: float): self._property_changed('rounded_notional_amount2') self.__rounded_notional_amount2 = value @property def factor_source(self) -> str: """Factor source. One of: Axioma, Prime.""" return self.__factor_source @factor_source.setter def factor_source(self, value: str): self._property_changed('factor_source') self.__factor_source = value @property def future_month_j26(self) -> float: """Commods future month code.""" return self.__future_month_j26 @future_month_j26.setter def future_month_j26(self, value: float): self._property_changed('future_month_j26') self.__future_month_j26 = value @property def lending_sec_type(self) -> str: """Securities lending identifiter for the security on loan.""" return self.__lending_sec_type @lending_sec_type.setter def lending_sec_type(self, value: str): self._property_changed('lending_sec_type') self.__lending_sec_type = value @property def future_month_j25(self) -> float: """Commods future month code.""" return self.__future_month_j25 @future_month_j25.setter def future_month_j25(self, value: float): self._property_changed('future_month_j25') self.__future_month_j25 = value @property def leverage(self) -> float: """Leverage.""" return self.__leverage @leverage.setter def leverage(self, value: float): self._property_changed('leverage') self.__leverage = value @property def forecast_day(self) -> str: """The offset for forecast day for weather from today.""" return self.__forecast_day @forecast_day.setter def forecast_day(self, value: str): self._property_changed('forecast_day') self.__forecast_day = value @property def option_family(self) -> str: """Style of the option.""" return self.__option_family @option_family.setter def option_family(self, value: str): self._property_changed('option_family') self.__option_family = value @property def generator_output(self) -> float: """The output of an electricity generator in MW.""" return self.__generator_output @generator_output.setter def generator_output(self, value: float): self._property_changed('generator_output') self.__generator_output = value @property def price_spot_stop_loss_value(self) -> float: """Stop loss price value of the trade idea.""" return self.__price_spot_stop_loss_value @price_spot_stop_loss_value.setter def price_spot_stop_loss_value(self, value: float): self._property_changed('price_spot_stop_loss_value') self.__price_spot_stop_loss_value = value @property def kpi_id(self) -> str: """Marquee unique KPI identifier.""" return self.__kpi_id @kpi_id.setter def kpi_id(self, value: str): self._property_changed('kpi_id') self.__kpi_id = value @property def wind_generation(self) -> float: """Wind generation value provided by source.""" return self.__wind_generation @wind_generation.setter def wind_generation(self, value: float): self._property_changed('wind_generation') self.__wind_generation = value @property def percentage_mid_executed_quantity(self) -> float: """Percentage of total order filled at midpoint.""" return self.__percentage_mid_executed_quantity @percentage_mid_executed_quantity.setter def percentage_mid_executed_quantity(self, value: float): self._property_changed('percentage_mid_executed_quantity') self.__percentage_mid_executed_quantity = value @property def borrow_cost(self) -> float: """An indication of the rate one would be charged for borrowing/shorting the relevant asset on that day, expressed in bps. Rates may change daily.""" return self.__borrow_cost @borrow_cost.setter def borrow_cost(self, value: float): self._property_changed('borrow_cost') self.__borrow_cost = value @property def knock_out_direction(self) -> str: """The side of the knock-out price the market level must reach to trigger the knock out.""" return self.__knock_out_direction @knock_out_direction.setter def knock_out_direction(self, value: str): self._property_changed('knock_out_direction') self.__knock_out_direction = value @property def risk_model(self) -> str: """Model used to compute risk or performance attribution. Defines universe, factors, calibration period etc.""" return self.__risk_model @risk_model.setter def risk_model(self, value: str): self._property_changed('risk_model') self.__risk_model = value @property def asset_parameters_vendor(self) -> str: """Marquee unique identifier""" return self.__asset_parameters_vendor @asset_parameters_vendor.setter def asset_parameters_vendor(self, value: str): self._property_changed('asset_parameters_vendor') self.__asset_parameters_vendor = value @property def fair_value(self) -> float: """Fair Value.""" return self.__fair_value @fair_value.setter def fair_value(self, value: float): self._property_changed('fair_value') self.__fair_value = value @property def open_time(self) -> datetime.datetime: """Time opened. ISO 8601 formatted string.""" return self.__open_time @open_time.setter def open_time(self, value: datetime.datetime): self._property_changed('open_time') self.__open_time = value @property def pressure_hourly_forecast(self) -> float: """The hourly forecast value for pressure in Inch.""" return self.__pressure_hourly_forecast @pressure_hourly_forecast.setter def pressure_hourly_forecast(self, value: float): self._property_changed('pressure_hourly_forecast') self.__pressure_hourly_forecast = value @property def local_ccy_rate(self) -> float: """The interest rate of the local currency of the associated FX contract.""" return self.__local_ccy_rate @local_ccy_rate.setter def local_ccy_rate(self, value: float): self._property_changed('local_ccy_rate') self.__local_ccy_rate = value @property def end_user_exception(self) -> str: """If buyer or seller or both is electing the End User Exception.""" return self.__end_user_exception @end_user_exception.setter def end_user_exception(self, value: str): self._property_changed('end_user_exception') self.__end_user_exception = value @property def sell90cents(self) -> float: """The amount GS would sell for 90 cents charge.""" return self.__sell90cents @sell90cents.setter def sell90cents(self, value: float): self._property_changed('sell90cents') self.__sell90cents = value @property def execution_venue(self) -> str: """An indication of whether the SB swap transaction was executed on a registered swap execution facility or designated contract market or was executed as an off-facility swap.""" return self.__execution_venue @execution_venue.setter def execution_venue(self, value: str): self._property_changed('execution_venue') self.__execution_venue = value @property def primary_vwap_in_limit_realized_bps(self) -> float: """Realised performance vs Primary VWAP In Limit, in bps.""" return self.__primary_vwap_in_limit_realized_bps @primary_vwap_in_limit_realized_bps.setter def primary_vwap_in_limit_realized_bps(self, value: float): self._property_changed('primary_vwap_in_limit_realized_bps') self.__primary_vwap_in_limit_realized_bps = value @property def approve_rebalance(self) -> bool: """An approved basket.""" return self.__approve_rebalance @approve_rebalance.setter def approve_rebalance(self, value: bool): self._property_changed('approve_rebalance') self.__approve_rebalance = value @property def adjusted_close_price(self) -> float: """Closing Price adjusted for corporate actions.""" return self.__adjusted_close_price @adjusted_close_price.setter def adjusted_close_price(self, value: float): self._property_changed('adjusted_close_price') self.__adjusted_close_price = value @property def lms_id(self) -> str: """Market identifier code.""" return self.__lms_id @lms_id.setter def lms_id(self, value: str): self._property_changed('lms_id') self.__lms_id = value @property def rebate_rate(self) -> float: """Defines the rate of the cash-back payment to an investor who puts up collateral in borrowing a stock. A rebate rate of interest implies a fee for the loan of securities.""" return self.__rebate_rate @rebate_rate.setter def rebate_rate(self, value: float): self._property_changed('rebate_rate') self.__rebate_rate = value @property def sell130cents(self) -> float: """The amount GS would sell for 130 cents charge.""" return self.__sell130cents @sell130cents.setter def sell130cents(self, value: float): self._property_changed('sell130cents') self.__sell130cents = value @property def sell32bps(self) -> float: """The amount GS would sell for 32 bps charge.""" return self.__sell32bps @sell32bps.setter def sell32bps(self, value: float): self._property_changed('sell32bps') self.__sell32bps = value @property def pace_of_rollp50(self) -> float: """The 50th-percentile of the pace of roll compared to previous quarters (over the last 5 years) at the same point in time, i.e. cross-sectional.""" return self.__pace_of_rollp50 @pace_of_rollp50.setter def pace_of_rollp50(self, value: float): self._property_changed('pace_of_rollp50') self.__pace_of_rollp50 = value @property def price_move_vs_arrival(self) -> float: """Stock price return versus arrival price return (in bps).""" return self.__price_move_vs_arrival @price_move_vs_arrival.setter def price_move_vs_arrival(self, value: float): self._property_changed('price_move_vs_arrival') self.__price_move_vs_arrival = value @property def strike_relative(self) -> Union[float, str]: """Strike as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF, 10/vol, 100k/pv, p=10000, p=10000USD, $200K/BP, or multiple strikes 65.4/-45.8""" return self.__strike_relative @strike_relative.setter def strike_relative(self, value: Union[float, str]): self._property_changed('strike_relative') self.__strike_relative = value @property def pressure_type(self) -> str: """The type of pressure required like sea level or barometric.""" return self.__pressure_type @pressure_type.setter def pressure_type(self, value: str): self._property_changed('pressure_type') self.__pressure_type = value @property def buy40bps(self) -> float: """The amount GS would buy for 40 bps charge.""" return self.__buy40bps @buy40bps.setter def buy40bps(self, value: float): self._property_changed('buy40bps') self.__buy40bps = value @property def price_notation(self) -> float: """The Basis points, Price, Yield, Spread, Coupon, etc., value depending on the type of SB swap, which is calculated at affirmation.""" return self.__price_notation @price_notation.setter def price_notation(self, value: float): self._property_changed('price_notation') self.__price_notation = value @property def strategy(self) -> str: """More specific descriptor of a fund's investment approach. Same view permissions as the asset.""" return self.__strategy @strategy.setter def strategy(self, value: str): self._property_changed('strategy') self.__strategy = value @property def issue_status_date(self) -> datetime.date: """ISO 8601-formatted date""" return self.__issue_status_date @issue_status_date.setter def issue_status_date(self, value: datetime.date): self._property_changed('issue_status_date') self.__issue_status_date = value @property def lender_income(self) -> float: """Income earned by the Lender for the loan of securities to a borrower.""" return self.__lender_income @lender_income.setter def lender_income(self, value: float): self._property_changed('lender_income') self.__lender_income = value @property def pb_client_id(self) -> str: """Prime Brokerage client identifier.""" return self.__pb_client_id @pb_client_id.setter def pb_client_id(self, value: str): self._property_changed('pb_client_id') self.__pb_client_id = value @property def istat_region_code(self) -> float: """Italian region code (ISTAT 2019).""" return self.__istat_region_code @istat_region_code.setter def istat_region_code(self, value: float): self._property_changed('istat_region_code') self.__istat_region_code = value @property def sell9bps(self) -> float: """The amount GS would sell for 9 bps charge.""" return self.__sell9bps @sell9bps.setter def sell9bps(self, value: float): self._property_changed('sell9bps') self.__sell9bps = value @property def owner_id(self) -> str: """Marquee unique identifier for user who owns the object.""" return self.__owner_id @owner_id.setter def owner_id(self, value: str): self._property_changed('owner_id') self.__owner_id = value @property def composite10_day_adv(self) -> float: """Composite 10 day ADV.""" return self.__composite10_day_adv @composite10_day_adv.setter def composite10_day_adv(self, value: float): self._property_changed('composite10_day_adv') self.__composite10_day_adv = value @property def max_loan_balance(self) -> float: """The maximum allowable balance that can be lent out for a lender.""" return self.__max_loan_balance @max_loan_balance.setter def max_loan_balance(self, value: float): self._property_changed('max_loan_balance') self.__max_loan_balance = value @property def idea_activity_type(self) -> str: """Equals CorporateAction if the activity originates as a result of a corporate action. Equals GovernanceAction if the activity originates as a result of a control measure. Equals UserAction if the activity is user driven.""" return self.__idea_activity_type @idea_activity_type.setter def idea_activity_type(self, value: str): self._property_changed('idea_activity_type') self.__idea_activity_type = value @property def sell60cents(self) -> float: """The amount GS would sell for 60 cents charge.""" return self.__sell60cents @sell60cents.setter def sell60cents(self, value: float): self._property_changed('sell60cents') self.__sell60cents = value @property def idea_source(self) -> str: """Equals User if the idea activity originates from a sales person. Equals System if the idea activity is system generated.""" return self.__idea_source @idea_source.setter def idea_source(self, value: str): self._property_changed('idea_source') self.__idea_source = value @property def ever_on_vent(self) -> float: """Total number of patients ever on ventilator for COVID.""" return self.__ever_on_vent @ever_on_vent.setter def ever_on_vent(self, value: float): self._property_changed('ever_on_vent') self.__ever_on_vent = value @property def buy15cents(self) -> float: """The amount GS would buy for 15 cents charge.""" return self.__buy15cents @buy15cents.setter def buy15cents(self, value: float): self._property_changed('buy15cents') self.__buy15cents = value @property def unadjusted_ask(self) -> float: """Unadjusted ask level of an asset based on official exchange fixing or calculation agent marked level.""" return self.__unadjusted_ask @unadjusted_ask.setter def unadjusted_ask(self, value: float): self._property_changed('unadjusted_ask') self.__unadjusted_ask = value @property def contribution_name(self) -> str: """Sector or type group name for the corresponding geography when metricName is either CAI_CONTRIBUTION_SECTOR or CAI_CONTRIBUTION_TYPE.""" return self.__contribution_name @contribution_name.setter def contribution_name(self, value: str): self._property_changed('contribution_name') self.__contribution_name = value @property def given_plus_paid(self) -> float: """Total of given & paid.""" return self.__given_plus_paid @given_plus_paid.setter def given_plus_paid(self, value: float): self._property_changed('given_plus_paid') self.__given_plus_paid = value @property def last_fill_price(self) -> float: """Last execution price.""" return self.__last_fill_price @last_fill_price.setter def last_fill_price(self, value: float): self._property_changed('last_fill_price') self.__last_fill_price = value @property def short_conviction_small(self) -> float: """The count of short ideas with small conviction.""" return self.__short_conviction_small @short_conviction_small.setter def short_conviction_small(self, value: float): self._property_changed('short_conviction_small') self.__short_conviction_small = value @property def upfront_payment_currency(self) -> str: """Currency of upfront payment.""" return self.__upfront_payment_currency @upfront_payment_currency.setter def upfront_payment_currency(self, value: str): self._property_changed('upfront_payment_currency') self.__upfront_payment_currency = value @property def spot_settlement_date(self) -> datetime.date: """The settlement date for a spot FX trade.""" return self.__spot_settlement_date @spot_settlement_date.setter def spot_settlement_date(self, value: datetime.date): self._property_changed('spot_settlement_date') self.__spot_settlement_date = value @property def matrix_order(self) -> float: """The matrix col/row order. Number from 0-249.""" return self.__matrix_order @matrix_order.setter def matrix_order(self, value: float): self._property_changed('matrix_order') self.__matrix_order = value @property def date_index(self) -> float: """For a rates asset, represents the proxy for a meeting number.""" return self.__date_index @date_index.setter def date_index(self, value: float): self._property_changed('date_index') self.__date_index = value @property def payer_day_count_fraction(self) -> str: """Day Count Fraction""" return self.__payer_day_count_fraction @payer_day_count_fraction.setter def payer_day_count_fraction(self, value: str): self._property_changed('payer_day_count_fraction') self.__payer_day_count_fraction = value @property def asset_classifications_is_primary(self) -> bool: """Whether or not it is the primary exchange asset.""" return self.__asset_classifications_is_primary @asset_classifications_is_primary.setter def asset_classifications_is_primary(self, value: bool): self._property_changed('asset_classifications_is_primary') self.__asset_classifications_is_primary = value @property def break_even_inflation_change(self) -> float: """Break even inflation change.""" return self.__break_even_inflation_change @break_even_inflation_change.setter def break_even_inflation_change(self, value: float): self._property_changed('break_even_inflation_change') self.__break_even_inflation_change = value @property def buy130cents(self) -> float: """The amount GS would buy for 130 cents charge.""" return self.__buy130cents @buy130cents.setter def buy130cents(self, value: float): self._property_changed('buy130cents') self.__buy130cents = value @property def dwi_contribution(self) -> float: """Contribution of debt weighted exchange rate index to FCI. Only applicable to EM countries.""" return self.__dwi_contribution @dwi_contribution.setter def dwi_contribution(self, value: float): self._property_changed('dwi_contribution') self.__dwi_contribution = value @property def asset2_id(self) -> str: """Marquee unique asset identifier.""" return self.__asset2_id @asset2_id.setter def asset2_id(self, value: str): self._property_changed('asset2_id') self.__asset2_id = value @property def average_fill_price(self) -> float: """Average fill price for the order since it started.""" return self.__average_fill_price @average_fill_price.setter def average_fill_price(self, value: float): self._property_changed('average_fill_price') self.__average_fill_price = value @property def depth_spread_score(self) -> float: """Z-score of the difference between the mid price and the best price an order to buy or sell a specific notional can be filled at.""" return self.__depth_spread_score @depth_spread_score.setter def depth_spread_score(self, value: float): self._property_changed('depth_spread_score') self.__depth_spread_score = value @property def sell10cents(self) -> float: """The amount GS would sell for 10 cents charge.""" return self.__sell10cents @sell10cents.setter def sell10cents(self, value: float): self._property_changed('sell10cents') self.__sell10cents = value @property def sub_account(self) -> str: """Subaccount.""" return self.__sub_account @sub_account.setter def sub_account(self, value: str): self._property_changed('sub_account') self.__sub_account = value @property def buy65cents(self) -> float: """The amount GS would buy for 65 cents charge.""" return self.__buy65cents @buy65cents.setter def buy65cents(self, value: float): self._property_changed('buy65cents') self.__buy65cents = value @property def bond_cds_basis(self) -> float: """Difference between bond spread and underlying cds of the bond.""" return self.__bond_cds_basis @bond_cds_basis.setter def bond_cds_basis(self, value: float): self._property_changed('bond_cds_basis') self.__bond_cds_basis = value @property def vendor(self) -> Union[Union[MarketDataVendor, str], str]: return self.__vendor @vendor.setter def vendor(self, value: Union[Union[MarketDataVendor, str], str]): self._property_changed('vendor') self.__vendor = get_enum_value(MarketDataVendor, value) @property def data_set(self) -> str: """Unique id of dataset.""" return self.__data_set @data_set.setter def data_set(self, value: str): self._property_changed('data_set') self.__data_set = value @property def notional_amount2(self) -> float: """The total Notional amount or quantity of units of the underlying asset.""" return self.__notional_amount2 @notional_amount2.setter def notional_amount2(self, value: float): self._property_changed('notional_amount2') self.__notional_amount2 = value @property def notional_amount1(self) -> float: """The total Notional amount or quantity of units of the underlying asset.""" return self.__notional_amount1 @notional_amount1.setter def notional_amount1(self, value: float): self._property_changed('notional_amount1') self.__notional_amount1 = value @property def queueing_time(self) -> int: """Time for which risk calculation was queued (ms).""" return self.__queueing_time @queueing_time.setter def queueing_time(self, value: int): self._property_changed('queueing_time') self.__queueing_time = value @property def ann_return5_year(self) -> float: """Total return representing past performance, used for GS Money Market onshore funds, over five years.""" return self.__ann_return5_year @ann_return5_year.setter def ann_return5_year(self, value: float): self._property_changed('ann_return5_year') self.__ann_return5_year = value @property def volume_start_of_day(self) -> float: """Start of day forecast of daily volume.""" return self.__volume_start_of_day @volume_start_of_day.setter def volume_start_of_day(self, value: float): self._property_changed('volume_start_of_day') self.__volume_start_of_day = value @property def price_notation3_type(self) -> str: """Basis points, Price, Yield, Spread, Coupon, etc., depending on the type of SB swap, which is calculated at affirmation.""" return self.__price_notation3_type @price_notation3_type.setter def price_notation3_type(self, value: str): self._property_changed('price_notation3_type') self.__price_notation3_type = value @property def asset_parameters_floating_rate_designated_maturity(self) -> str: """Tenor""" return self.__asset_parameters_floating_rate_designated_maturity @asset_parameters_floating_rate_designated_maturity.setter def asset_parameters_floating_rate_designated_maturity(self, value: str): self._property_changed('asset_parameters_floating_rate_designated_maturity') self.__asset_parameters_floating_rate_designated_maturity = value @property def executed_notional_local(self) -> float: """Executed notional in local currency.""" return self.__executed_notional_local @executed_notional_local.setter def executed_notional_local(self, value: float): self._property_changed('executed_notional_local') self.__executed_notional_local = value @property def business_sponsor(self) -> str: """Business Sponsor.""" return self.__business_sponsor @business_sponsor.setter def business_sponsor(self, value: str): self._property_changed('business_sponsor') self.__business_sponsor = value @property def unexplained(self) -> float: """PNL unexplained by risk model.""" return self.__unexplained @unexplained.setter def unexplained(self, value: float): self._property_changed('unexplained') self.__unexplained = value @property def seasonal_adjustment_short(self) -> str: """Seasonal adjustment of series.""" return self.__seasonal_adjustment_short @seasonal_adjustment_short.setter def seasonal_adjustment_short(self, value: str): self._property_changed('seasonal_adjustment_short') self.__seasonal_adjustment_short = value @property def metric(self) -> str: """Metric for the associated asset.""" return self.__metric @metric.setter def metric(self, value: str): self._property_changed('metric') self.__metric = value @property def ask(self) -> float: """Latest Ask Price (price offering to sell).""" return self.__ask @ask.setter def ask(self, value: float): self._property_changed('ask') self.__ask = value @property def close_price(self) -> float: """Closing level of an asset based on official exchange fixing or calculation agent marked level.""" return self.__close_price @close_price.setter def close_price(self, value: float): self._property_changed('close_price') self.__close_price = value @property def end_time(self) -> datetime.datetime: """End time.""" return self.__end_time @end_time.setter def end_time(self, value: datetime.datetime): self._property_changed('end_time') self.__end_time = value @property def sell100cents(self) -> float: """The amount GS would sell for 100 cents charge.""" return self.__sell100cents @sell100cents.setter def sell100cents(self, value: float): self._property_changed('sell100cents') self.__sell100cents = value @property def execution_timestamp(self) -> datetime.datetime: """The time and date of execution of the publicly reportable swap transaction in Coordinated Universal Time (UTC - CCYY-MMDDThh:mm:ss).""" return self.__execution_timestamp @execution_timestamp.setter def execution_timestamp(self, value: datetime.datetime): self._property_changed('execution_timestamp') self.__execution_timestamp = value @property def buy180cents(self) -> float: """The amount GS would buy for 180 cents charge.""" return self.__buy180cents @buy180cents.setter def buy180cents(self, value: float): self._property_changed('buy180cents') self.__buy180cents = value @property def absolute_strike(self) -> float: """Actual strike value specified in outright terms.""" return self.__absolute_strike @absolute_strike.setter def absolute_strike(self, value: float): self._property_changed('absolute_strike') self.__absolute_strike = value @property def sell3point5bps(self) -> float: """The amount GS would sell for 3.5 bps charge.""" return self.__sell3point5bps @sell3point5bps.setter def sell3point5bps(self, value: float): self._property_changed('sell3point5bps') self.__sell3point5bps = value @property def liquidity_score_buy(self) -> float: """The liquidity score assigned to buying the bond.""" return self.__liquidity_score_buy @liquidity_score_buy.setter def liquidity_score_buy(self, value: float): self._property_changed('liquidity_score_buy') self.__liquidity_score_buy = value @property def payment_frequency(self) -> str: """An integer multiplier of a time period describing how often the parties to the SB swap transaction exchange payments associated with each party???s obligation. Such payment frequency may be described as one letter preceded by an integer.""" return self.__payment_frequency @payment_frequency.setter def payment_frequency(self, value: str): self._property_changed('payment_frequency') self.__payment_frequency = value @property def expense_ratio_net_bps(self) -> float: """Gives basis point measure of management fee, net.""" return self.__expense_ratio_net_bps @expense_ratio_net_bps.setter def expense_ratio_net_bps(self, value: float): self._property_changed('expense_ratio_net_bps') self.__expense_ratio_net_bps = value @property def metric_type(self) -> str: """Type of the metric.""" return self.__metric_type @metric_type.setter def metric_type(self, value: str): self._property_changed('metric_type') self.__metric_type = value @property def rank_ytd(self) -> float: """Rank of the Contributor Year to Date.""" return self.__rank_ytd @rank_ytd.setter def rank_ytd(self, value: float): self._property_changed('rank_ytd') self.__rank_ytd = value @property def leg1_spread(self) -> float: """Spread of leg.""" return self.__leg1_spread @leg1_spread.setter def leg1_spread(self, value: float): self._property_changed('leg1_spread') self.__leg1_spread = value @property def coverage_region(self) -> str: """Coverage Region of a contributor.""" return self.__coverage_region @coverage_region.setter def coverage_region(self, value: str): self._property_changed('coverage_region') self.__coverage_region = value @property def absolute_return_ytd(self) -> float: """Absolute Return Year to Date.""" return self.__absolute_return_ytd @absolute_return_ytd.setter def absolute_return_ytd(self, value: float): self._property_changed('absolute_return_ytd') self.__absolute_return_ytd = value @property def day_count_convention2(self) -> str: """Day count convention for leg 2.""" return self.__day_count_convention2 @day_count_convention2.setter def day_count_convention2(self, value: str): self._property_changed('day_count_convention2') self.__day_count_convention2 = value @property def fwdtier(self) -> str: """Forward tier.""" return self.__fwdtier @fwdtier.setter def fwdtier(self, value: str): self._property_changed('fwdtier') self.__fwdtier = value @property def degree_days(self) -> float: """Value of the temperature of given type.""" return self.__degree_days @degree_days.setter def degree_days(self, value: float): self._property_changed('degree_days') self.__degree_days = value @property def turnover_adjusted(self) -> float: """Turnover adjusted for corporate actions.""" return self.__turnover_adjusted @turnover_adjusted.setter def turnover_adjusted(self, value: float): self._property_changed('turnover_adjusted') self.__turnover_adjusted = value @property def price_spot_target_value(self) -> float: """Target price value of the trade idea.""" return self.__price_spot_target_value @price_spot_target_value.setter def price_spot_target_value(self, value: float): self._property_changed('price_spot_target_value') self.__price_spot_target_value = value @property def market_data_point(self) -> Tuple[Tuple[str, ...], ...]: """The market data point (e.g. 3m, 10y, 11y, Dec19).""" return self.__market_data_point @market_data_point.setter def market_data_point(self, value: Tuple[Tuple[str, ...], ...]): self._property_changed('market_data_point') self.__market_data_point = value @property def num_of_funds(self) -> float: """Number of funds used for aggregation for a given period.""" return self.__num_of_funds @num_of_funds.setter def num_of_funds(self, value: float): self._property_changed('num_of_funds') self.__num_of_funds = value @property def trade_time(self) -> datetime.datetime: """Trade Time.""" return self.__trade_time @trade_time.setter def trade_time(self, value: datetime.datetime): self._property_changed('trade_time') self.__trade_time = value @property def execution_id(self) -> str: """Execution ID.""" return self.__execution_id @execution_id.setter def execution_id(self, value: str): self._property_changed('execution_id') self.__execution_id = value @property def turnover_unadjusted(self) -> float: """Turnover not adjusted for corporate actions.""" return self.__turnover_unadjusted @turnover_unadjusted.setter def turnover_unadjusted(self, value: float): self._property_changed('turnover_unadjusted') self.__turnover_unadjusted = value @property def leg1_floating_index(self) -> str: """If floating index leg, the index.""" return self.__leg1_floating_index @leg1_floating_index.setter def leg1_floating_index(self, value: str): self._property_changed('leg1_floating_index') self.__leg1_floating_index = value @property def hedge_annualized_volatility(self) -> float: """Standard deviation of the annualized returns.""" return self.__hedge_annualized_volatility @hedge_annualized_volatility.setter def hedge_annualized_volatility(self, value: float): self._property_changed('hedge_annualized_volatility') self.__hedge_annualized_volatility = value @property def benchmark_currency(self) -> str: """Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)""" return self.__benchmark_currency @benchmark_currency.setter def benchmark_currency(self, value: str): self._property_changed('benchmark_currency') self.__benchmark_currency = value @property def futures_contract(self) -> str: """The related futures contract code if applicable.""" return self.__futures_contract @futures_contract.setter def futures_contract(self, value: str): self._property_changed('futures_contract') self.__futures_contract = value @property def name(self) -> str: """Legal or published name for the asset.""" return self.__name @name.setter def name(self, value: str): self._property_changed('name') self.__name = value @property def aum(self) -> Union[Op, float]: return self.__aum @aum.setter def aum(self, value: Union[Op, float]): self._property_changed('aum') self.__aum = value @property def leg1_day_count_convention(self) -> str: """The determination of how interest accrues over time for the SB swap.""" return self.__leg1_day_count_convention @leg1_day_count_convention.setter def leg1_day_count_convention(self, value: str): self._property_changed('leg1_day_count_convention') self.__leg1_day_count_convention = value @property def cbs_code(self) -> str: """CBS Code for Municipality in Netherlands.""" return self.__cbs_code @cbs_code.setter def cbs_code(self, value: str): self._property_changed('cbs_code') self.__cbs_code = value @property def folder_name(self) -> str: """Folder Name of a chart.""" return self.__folder_name @folder_name.setter def folder_name(self, value: str): self._property_changed('folder_name') self.__folder_name = value @property def api_usage(self) -> float: """Usage Metrics for a given client entity based on specified service and metrics feature.""" return self.__api_usage @api_usage.setter def api_usage(self, value: float): self._property_changed('api_usage') self.__api_usage = value @property def twap_interval(self) -> float: """Time interval (in milliseconds) at which a TWAP attempts to fill.""" return self.__twap_interval @twap_interval.setter def twap_interval(self, value: float): self._property_changed('twap_interval') self.__twap_interval = value @property def unique_id(self) -> float: """A unique identifier.""" return self.__unique_id @unique_id.setter def unique_id(self, value: float): self._property_changed('unique_id') self.__unique_id = value @property def option_expiration_date(self) -> datetime.date: """An indication of the date that the option is no longer available for exercise.""" return self.__option_expiration_date @option_expiration_date.setter def option_expiration_date(self, value: datetime.date): self._property_changed('option_expiration_date') self.__option_expiration_date = value @property def swaption_atm_fwd_rate(self) -> float: """Swaption ATM forward rate.""" return self.__swaption_atm_fwd_rate @swaption_atm_fwd_rate.setter def swaption_atm_fwd_rate(self, value: float): self._property_changed('swaption_atm_fwd_rate') self.__swaption_atm_fwd_rate = value @property def live_date(self) -> Union[Op, datetime.date]: return self.__live_date @live_date.setter def live_date(self, value: Union[Op, datetime.date]): self._property_changed('live_date') self.__live_date = value @property def corporate_action_type(self) -> str: """Different types of corporate actions from solactive""" return self.__corporate_action_type @corporate_action_type.setter def corporate_action_type(self, value: str): self._property_changed('corporate_action_type') self.__corporate_action_type = value @property def prime_id(self) -> str: """Prime Id.""" return self.__prime_id @prime_id.setter def prime_id(self, value: str): self._property_changed('prime_id') self.__prime_id = value @property def description(self) -> str: """Description of asset.""" return self.__description @description.setter def description(self, value: str): self._property_changed('description') self.__description = value @property def asset_classifications_is_country_primary(self) -> bool: """Whether or not it is the primary exchange asset for the exchange country.""" return self.__asset_classifications_is_country_primary @asset_classifications_is_country_primary.setter def asset_classifications_is_country_primary(self, value: bool): self._property_changed('asset_classifications_is_country_primary') self.__asset_classifications_is_country_primary = value @property def rebate_rate_limit(self) -> float: """The maximum allowable rebate rate that can be paid out to the borrower of a stock.""" return self.__rebate_rate_limit @rebate_rate_limit.setter def rebate_rate_limit(self, value: float): self._property_changed('rebate_rate_limit') self.__rebate_rate_limit = value @property def factor(self) -> str: """Name of the factor. i.e. Momentum""" return self.__factor @factor.setter def factor(self, value: str): self._property_changed('factor') self.__factor = value @property def days_on_loan(self) -> float: """The number of days this loan as been on our books.""" return self.__days_on_loan @days_on_loan.setter def days_on_loan(self, value: float): self._property_changed('days_on_loan') self.__days_on_loan = value @property def long_conviction_small(self) -> float: """The count of long ideas with small conviction.""" return self.__long_conviction_small @long_conviction_small.setter def long_conviction_small(self, value: float): self._property_changed('long_conviction_small') self.__long_conviction_small = value @property def sell40cents(self) -> float: """The amount GS would sell for 40 cents charge.""" return self.__sell40cents @sell40cents.setter def sell40cents(self, value: float): self._property_changed('sell40cents') self.__sell40cents = value @property def relative_payoff_ytd(self) -> float: """Total win divided by total loss Year to date.""" return self.__relative_payoff_ytd @relative_payoff_ytd.setter def relative_payoff_ytd(self, value: float): self._property_changed('relative_payoff_ytd') self.__relative_payoff_ytd = value @property def gsfeer(self) -> float: """Goldman Sachs Fundamental Equilibrium Exchange Rate.""" return self.__gsfeer @gsfeer.setter def gsfeer(self, value: float): self._property_changed('gsfeer') self.__gsfeer = value @property def relative_hit_rate_qtd(self) -> float: """Hit Rate Ratio Quarter to Date.""" return self.__relative_hit_rate_qtd @relative_hit_rate_qtd.setter def relative_hit_rate_qtd(self, value: float): self._property_changed('relative_hit_rate_qtd') self.__relative_hit_rate_qtd = value @property def wam(self) -> float: """Weighted average maturity, average of effective maturities of all securities held in portfolio, weighted.""" return self.__wam @wam.setter def wam(self, value: float): self._property_changed('wam') self.__wam = value @property def wal(self) -> float: """Weighted average life, measures sensitivity to changes in liquidity.""" return self.__wal @wal.setter def wal(self, value: float): self._property_changed('wal') self.__wal = value @property def quantityccy(self) -> str: """Quantity unit.""" return self.__quantityccy @quantityccy.setter def quantityccy(self, value: str): self._property_changed('quantityccy') self.__quantityccy = value @property def backtest_id(self) -> str: """Marquee unique backtest identifier.""" return self.__backtest_id @backtest_id.setter def backtest_id(self, value: str): self._property_changed('backtest_id') self.__backtest_id = value @property def dirty_price(self) -> float: """Price of the Bond including accrued interest.""" return self.__dirty_price @dirty_price.setter def dirty_price(self, value: float): self._property_changed('dirty_price') self.__dirty_price = value @property def corporate_spread_contribution(self) -> float: """Contribution of corporate spread component to FCI.""" return self.__corporate_spread_contribution @corporate_spread_contribution.setter def corporate_spread_contribution(self, value: float): self._property_changed('corporate_spread_contribution') self.__corporate_spread_contribution = value @property def relative_humidity_hourly_forecast(self) -> float: """The hourly forecast value for relative humidity in percentage.""" return self.__relative_humidity_hourly_forecast @relative_humidity_hourly_forecast.setter def relative_humidity_hourly_forecast(self, value: float): self._property_changed('relative_humidity_hourly_forecast') self.__relative_humidity_hourly_forecast = value @property def multiple_score(self) -> float: """Multiple percentile relative to Americas coverage universe (a higher score means more expensive).""" return self.__multiple_score @multiple_score.setter def multiple_score(self, value: float): self._property_changed('multiple_score') self.__multiple_score = value @property def beta_adjusted_exposure(self) -> float: """Beta adjusted exposure.""" return self.__beta_adjusted_exposure @beta_adjusted_exposure.setter def beta_adjusted_exposure(self, value: float): self._property_changed('beta_adjusted_exposure') self.__beta_adjusted_exposure = value @property def dividend_points(self) -> float: """Expected Dividend in index points.""" return self.__dividend_points @dividend_points.setter def dividend_points(self, value: float): self._property_changed('dividend_points') self.__dividend_points = value @property def brightness(self) -> float: """Value of the measure type selected.""" return self.__brightness @brightness.setter def brightness(self, value: float): self._property_changed('brightness') self.__brightness = value @property def asset_parameters_receiver_designated_maturity(self) -> str: """Tenor""" return self.__asset_parameters_receiver_designated_maturity @asset_parameters_receiver_designated_maturity.setter def asset_parameters_receiver_designated_maturity(self, value: str): self._property_changed('asset_parameters_receiver_designated_maturity') self.__asset_parameters_receiver_designated_maturity = value @property def bos_in_ticks_description(self) -> str: """Description of the Stock's Bid-Offer Spread in Ticks on the particular date.""" return self.__bos_in_ticks_description @bos_in_ticks_description.setter def bos_in_ticks_description(self, value: str): self._property_changed('bos_in_ticks_description') self.__bos_in_ticks_description = value @property def test_id(self) -> str: """Unique ID for test configuration.""" return self.__test_id @test_id.setter def test_id(self, value: str): self._property_changed('test_id') self.__test_id = value @property def implied_correlation(self) -> float: """Correlation of an asset implied by observations of market prices.""" return self.__implied_correlation @implied_correlation.setter def implied_correlation(self, value: float): self._property_changed('implied_correlation') self.__implied_correlation = value @property def normalized_performance(self) -> float: """Performance that is normalized to 1.""" return self.__normalized_performance @normalized_performance.setter def normalized_performance(self, value: float): self._property_changed('normalized_performance') self.__normalized_performance = value @property def overnight_news_end_time(self) -> datetime.datetime: """The overnight news end time.""" return self.__overnight_news_end_time @overnight_news_end_time.setter def overnight_news_end_time(self, value: datetime.datetime): self._property_changed('overnight_news_end_time') self.__overnight_news_end_time = value @property def bytes_consumed(self) -> float: """Bytes of raw data fetched in a run of the checker.""" return self.__bytes_consumed @bytes_consumed.setter def bytes_consumed(self, value: float): self._property_changed('bytes_consumed') self.__bytes_consumed = value @property def swaption_vol(self) -> float: """Historical implied normal volatility for a liquid point on swaption vol surface.""" return self.__swaption_vol @swaption_vol.setter def swaption_vol(self, value: float): self._property_changed('swaption_vol') self.__swaption_vol = value @property def estimated_closing_volume(self) -> float: """Estimated closing auction volume (in shares).""" return self.__estimated_closing_volume @estimated_closing_volume.setter def estimated_closing_volume(self, value: float): self._property_changed('estimated_closing_volume') self.__estimated_closing_volume = value @property def issuer(self) -> str: """The issuer of this bond.""" return self.__issuer @issuer.setter def issuer(self, value: str): self._property_changed('issuer') self.__issuer = value @property def dividend_yield(self) -> float: """Annualized Dividend Yield.""" return self.__dividend_yield @dividend_yield.setter def dividend_yield(self, value: float): self._property_changed('dividend_yield') self.__dividend_yield = value @property def market_type(self) -> str: """The type of market for Power. Values may include Physical Bilateral, Day Ahead or Real Time.""" return self.__market_type @market_type.setter def market_type(self, value: str): self._property_changed('market_type') self.__market_type = value @property def num_units_lower(self) -> float: """Units Lower.""" return self.__num_units_lower @num_units_lower.setter def num_units_lower(self, value: float): self._property_changed('num_units_lower') self.__num_units_lower = value @property def source_origin(self) -> str: """Source origin.""" return self.__source_origin @source_origin.setter def source_origin(self, value: str): self._property_changed('source_origin') self.__source_origin = value @property def proceeds_asset_swap_spread3m(self) -> float: """Proceeds asset swap spread vs 3m tenor.""" return self.__proceeds_asset_swap_spread3m @proceeds_asset_swap_spread3m.setter def proceeds_asset_swap_spread3m(self, value: float): self._property_changed('proceeds_asset_swap_spread3m') self.__proceeds_asset_swap_spread3m = value @property def total_quantity(self) -> float: """Rounded total quantity.""" return self.__total_quantity @total_quantity.setter def total_quantity(self, value: float): self._property_changed('total_quantity') self.__total_quantity = value @property def internal_user(self) -> bool: """Whether user is internal or not.""" return self.__internal_user @internal_user.setter def internal_user(self, value: bool): self._property_changed('internal_user') self.__internal_user = value @property def sell40bps(self) -> float: """The amount GS would sell for 40 bps charge.""" return self.__sell40bps @sell40bps.setter def sell40bps(self, value: float): self._property_changed('sell40bps') self.__sell40bps = value @property def redemption_option(self) -> str: """Indicates the calculation convention for callable instruments.""" return self.__redemption_option @redemption_option.setter def redemption_option(self, value: str): self._property_changed('redemption_option') self.__redemption_option = value @property def notional_unit2(self) -> str: """Unit of reported notional price.""" return self.__notional_unit2 @notional_unit2.setter def notional_unit2(self, value: str): self._property_changed('notional_unit2') self.__notional_unit2 = value @property def notional_unit1(self) -> str: """Unit of reported notional price.""" return self.__notional_unit1 @notional_unit1.setter def notional_unit1(self, value: str): self._property_changed('notional_unit1') self.__notional_unit1 = value @property def sedol(self) -> str: """SEDOL - Stock Exchange Daily Official List (subject to licensing).""" return self.__sedol @sedol.setter def sedol(self, value: str): self._property_changed('sedol') self.__sedol = value @property def rounding_cost_pnl(self) -> float: """Rounding Cost Profit and Loss.""" return self.__rounding_cost_pnl @rounding_cost_pnl.setter def rounding_cost_pnl(self, value: float): self._property_changed('rounding_cost_pnl') self.__rounding_cost_pnl = value @property def mid_yield(self) -> float: """The return an investor realizes on a bond sold at the mid price.""" return self.__mid_yield @mid_yield.setter def mid_yield(self, value: float): self._property_changed('mid_yield') self.__mid_yield = value @property def unexecuted_notional_local(self) -> float: """Total un-executed notional in local currency.""" return self.__unexecuted_notional_local @unexecuted_notional_local.setter def unexecuted_notional_local(self, value: float): self._property_changed('unexecuted_notional_local') self.__unexecuted_notional_local = value @property def sustain_global(self) -> bool: """True if the stock is on the SUSTAIN (Global) 50 list as of the corresponding date. False if the stock is removed from the SUSTAIN (Global) 50 list on the corresponding date.""" return self.__sustain_global @sustain_global.setter def sustain_global(self, value: bool): self._property_changed('sustain_global') self.__sustain_global = value @property def ending_date(self) -> str: """End date of the period the valuation refers to.""" return self.__ending_date @ending_date.setter def ending_date(self, value: str): self._property_changed('ending_date') self.__ending_date = value @property def proceeds_asset_swap_spread12m(self) -> float: """Proceeds asset swap spread vs 12m tenor.""" return self.__proceeds_asset_swap_spread12m @proceeds_asset_swap_spread12m.setter def proceeds_asset_swap_spread12m(self, value: float): self._property_changed('proceeds_asset_swap_spread12m') self.__proceeds_asset_swap_spread12m = value @property def gross_investment_wtd(self) -> float: """Total gross investment Week to date.""" return self.__gross_investment_wtd @gross_investment_wtd.setter def gross_investment_wtd(self, value: float): self._property_changed('gross_investment_wtd') self.__gross_investment_wtd = value @property def ann_return3_year(self) -> float: """Total return representing past performance, used for GS Money Market onshore funds, over three years.""" return self.__ann_return3_year @ann_return3_year.setter def ann_return3_year(self, value: float): self._property_changed('ann_return3_year') self.__ann_return3_year = value @property def sharpe_wtd(self) -> float: """Sharpe ratio Week to date.""" return self.__sharpe_wtd @sharpe_wtd.setter def sharpe_wtd(self, value: float): self._property_changed('sharpe_wtd') self.__sharpe_wtd = value @property def discount_factor(self) -> float: """Present Value of 1 unit of currency on an earlier date.""" return self.__discount_factor @discount_factor.setter def discount_factor(self, value: float): self._property_changed('discount_factor') self.__discount_factor = value @property def relative_return_mtd(self) -> float: """Relative Return Month to Date.""" return self.__relative_return_mtd @relative_return_mtd.setter def relative_return_mtd(self, value: float): self._property_changed('relative_return_mtd') self.__relative_return_mtd = value @property def price_change_on_day(self) -> float: """Price change on day.""" return self.__price_change_on_day @price_change_on_day.setter def price_change_on_day(self, value: float): self._property_changed('price_change_on_day') self.__price_change_on_day = value @property def buy100cents(self) -> float: """The amount GS would buy for 100 cents charge.""" return self.__buy100cents @buy100cents.setter def buy100cents(self, value: float): self._property_changed('buy100cents') self.__buy100cents = value @property def forward_point(self) -> float: """Outright forward minus spot.""" return self.__forward_point @forward_point.setter def forward_point(self, value: float): self._property_changed('forward_point') self.__forward_point = value @property def fci(self) -> float: """Nominal FCI value.""" return self.__fci @fci.setter def fci(self, value: float): self._property_changed('fci') self.__fci = value @property def recall_quantity(self) -> float: """Defines the amount of shares being recalled in a stock loan recall activity.""" return self.__recall_quantity @recall_quantity.setter def recall_quantity(self, value: float): self._property_changed('recall_quantity') self.__recall_quantity = value @property def fx_positioning(self) -> float: """Positioning data in the FX market.""" return self.__fx_positioning @fx_positioning.setter def fx_positioning(self, value: float): self._property_changed('fx_positioning') self.__fx_positioning = value @property def gsid_equivalent(self) -> str: """Goldman Sachs internal equity identifier equivalent - i.e. - for Corporate Bonds, the equivalent GSID of the underlying security.""" return self.__gsid_equivalent @gsid_equivalent.setter def gsid_equivalent(self, value: str): self._property_changed('gsid_equivalent') self.__gsid_equivalent = value @property def categories(self) -> Tuple[str, ...]: """Categories for series.""" return self.__categories @categories.setter def categories(self, value: Tuple[str, ...]): self._property_changed('categories') self.__categories = value @property def ext_mkt_asset(self) -> str: """External MDAPI Asset.""" return self.__ext_mkt_asset @ext_mkt_asset.setter def ext_mkt_asset(self, value: str): self._property_changed('ext_mkt_asset') self.__ext_mkt_asset = value @property def quoting_style(self) -> str: """Quoting style.""" return self.__quoting_style @quoting_style.setter def quoting_style(self, value: str): self._property_changed('quoting_style') self.__quoting_style = value @property def error_message(self) -> str: """Error message to correspond to error in factor field.""" return self.__error_message @error_message.setter def error_message(self, value: str): self._property_changed('error_message') self.__error_message = value @property def mid_price(self) -> float: """The mid price.""" return self.__mid_price @mid_price.setter def mid_price(self, value: float): self._property_changed('mid_price') self.__mid_price = value @property def proceeds_asset_swap_spread6m(self) -> float: """Proceeds asset swap spread vs 6m tenor.""" return self.__proceeds_asset_swap_spread6m @proceeds_asset_swap_spread6m.setter def proceeds_asset_swap_spread6m(self, value: float): self._property_changed('proceeds_asset_swap_spread6m') self.__proceeds_asset_swap_spread6m = value @property def sts_em_dm(self) -> str: """Emerging or developed market classification.""" return self.__sts_em_dm @sts_em_dm.setter def sts_em_dm(self, value: str): self._property_changed('sts_em_dm') self.__sts_em_dm = value @property def embedded_option(self) -> str: """An indication of whether or not the option fields are for an embedded option.""" return self.__embedded_option @embedded_option.setter def embedded_option(self, value: str): self._property_changed('embedded_option') self.__embedded_option = value @property def tcm_cost_horizon2_day(self) -> float: """TCM cost with a 2 day time horizon.""" return self.__tcm_cost_horizon2_day @tcm_cost_horizon2_day.setter def tcm_cost_horizon2_day(self, value: float): self._property_changed('tcm_cost_horizon2_day') self.__tcm_cost_horizon2_day = value @property def age_band(self) -> str: """The age band of the caller based on the date of birth provided.""" return self.__age_band @age_band.setter def age_band(self, value: str): self._property_changed('age_band') self.__age_band = value @property def returns_enabled(self) -> bool: """Whether or not returns are enabled for the Hedge Fund.""" return self.__returns_enabled @returns_enabled.setter def returns_enabled(self, value: bool): self._property_changed('returns_enabled') self.__returns_enabled = value @property def run_id(self) -> str: """Unique id for check runs, used to query entity data corresponding to dataset level data.""" return self.__run_id @run_id.setter def run_id(self, value: str): self._property_changed('run_id') self.__run_id = value @property def queue_in_lots(self) -> float: """The Queue size in Lots (if applicable) of the stock on the particular date.""" return self.__queue_in_lots @queue_in_lots.setter def queue_in_lots(self, value: float): self._property_changed('queue_in_lots') self.__queue_in_lots = value @property def tender_offer_expiration_date(self) -> str: """Expiration date of the tender offer.""" return self.__tender_offer_expiration_date @tender_offer_expiration_date.setter def tender_offer_expiration_date(self, value: str): self._property_changed('tender_offer_expiration_date') self.__tender_offer_expiration_date = value @property def midcurve_annuity(self) -> float: """Midcurve annuity.""" return self.__midcurve_annuity @midcurve_annuity.setter def midcurve_annuity(self, value: float): self._property_changed('midcurve_annuity') self.__midcurve_annuity = value @property def lending_fund_nav_trend(self) -> float: """Net Asset Value trend of a securities lending fund.""" return self.__lending_fund_nav_trend @lending_fund_nav_trend.setter def lending_fund_nav_trend(self, value: float): self._property_changed('lending_fund_nav_trend') self.__lending_fund_nav_trend = value @property def cloud_cover_forecast(self) -> float: """The forecast value for cloud cover.""" return self.__cloud_cover_forecast @cloud_cover_forecast.setter def cloud_cover_forecast(self, value: float): self._property_changed('cloud_cover_forecast') self.__cloud_cover_forecast = value @property def tcm_cost_participation_rate5_pct(self) -> float: """TCM cost with a 5 percent participation rate.""" return self.__tcm_cost_participation_rate5_pct @tcm_cost_participation_rate5_pct.setter def tcm_cost_participation_rate5_pct(self, value: float): self._property_changed('tcm_cost_participation_rate5_pct') self.__tcm_cost_participation_rate5_pct = value @property def default_backcast(self) -> bool: """Is basket backcasted using initial positions.""" return self.__default_backcast @default_backcast.setter def default_backcast(self, value: bool): self._property_changed('default_backcast') self.__default_backcast = value @property def news_on_intensity(self) -> float: """A measure of the intensity of media attention to each stock compared with other stocks in the index.""" return self.__news_on_intensity @news_on_intensity.setter def news_on_intensity(self, value: float): self._property_changed('news_on_intensity') self.__news_on_intensity = value @property def price_forming_continuation_data(self) -> str: """An indication of whether an SB swap transaction is a post-execution event that affects the price of the swap transaction, e.g. terminations, assignments, novations, exchanges, transfers, amendments, conveyances or extinguishing of rights that change the price of the SB swap.""" return self.__price_forming_continuation_data @price_forming_continuation_data.setter def price_forming_continuation_data(self, value: str): self._property_changed('price_forming_continuation_data') self.__price_forming_continuation_data = value @property def adjusted_short_interest(self) -> float: """Adjusted Short Interest rate.""" return self.__adjusted_short_interest @adjusted_short_interest.setter def adjusted_short_interest(self, value: float): self._property_changed('adjusted_short_interest') self.__adjusted_short_interest = value @property def new_hospitalized(self) -> float: """Number of new patients hospitalized.""" return self.__new_hospitalized @new_hospitalized.setter def new_hospitalized(self, value: float): self._property_changed('new_hospitalized') self.__new_hospitalized = value @property def asset_parameters_strike(self) -> Union[float, str]: """Strike as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF, 10/vol, 100k/pv, p=10000, p=10000USD, $200K/BP, or multiple strikes 65.4/-45.8""" return self.__asset_parameters_strike @asset_parameters_strike.setter def asset_parameters_strike(self, value: Union[float, str]): self._property_changed('asset_parameters_strike') self.__asset_parameters_strike = value @property def buy35cents(self) -> float: """The amount GS would buy for 35 cents charge.""" return self.__buy35cents @buy35cents.setter def buy35cents(self, value: float): self._property_changed('buy35cents') self.__buy35cents = value @property def leg2_total_notional(self) -> float: """The total Notional amount or quantity of units of the leg 2 underlying asset.""" return self.__leg2_total_notional @leg2_total_notional.setter def leg2_total_notional(self, value: float): self._property_changed('leg2_total_notional') self.__leg2_total_notional = value @property def asset_parameters_effective_date(self) -> str: """Relative effective date.""" return self.__asset_parameters_effective_date @asset_parameters_effective_date.setter def asset_parameters_effective_date(self, value: str): self._property_changed('asset_parameters_effective_date') self.__asset_parameters_effective_date = value @property def ann_return10_year(self) -> float: """Total return representing past performance, used for GS Money Market onshore funds, over ten years.""" return self.__ann_return10_year @ann_return10_year.setter def ann_return10_year(self, value: float): self._property_changed('ann_return10_year') self.__ann_return10_year = value @property def num_adult_icu_beds(self) -> float: """All ICU beds, burn ICU beds, surgical ICU beds, or trauma ICU beds meant for emergency situations where hospitals may use additional intensive care beds to supplement an influx of patients. Does not include neonatal, pediatric or premature ICU beds.""" return self.__num_adult_icu_beds @num_adult_icu_beds.setter def num_adult_icu_beds(self, value: float): self._property_changed('num_adult_icu_beds') self.__num_adult_icu_beds = value @property def days_to_expiration(self) -> float: """Days to Expiration for the contract.""" return self.__days_to_expiration @days_to_expiration.setter def days_to_expiration(self, value: float): self._property_changed('days_to_expiration') self.__days_to_expiration = value @property def continuation_event(self) -> str: """An indication of whether an SB swap transaction is a post-execution event that affects the price of the swap transaction, e.g. terminations, assignments, novations, exchanges, transfers, amendments, conveyances or extinguishing of rights that change the price of the SB swap.""" return self.__continuation_event @continuation_event.setter def continuation_event(self, value: str): self._property_changed('continuation_event') self.__continuation_event = value @property def wi_id(self) -> str: """Weather Index Identifier.""" return self.__wi_id @wi_id.setter def wi_id(self, value: str): self._property_changed('wi_id') self.__wi_id = value @property def market_cap_category(self) -> str: """Category of market capitalizations a fund is focused on from an investment perspective. Same view permissions as the asset.""" return self.__market_cap_category @market_cap_category.setter def market_cap_category(self, value: str): self._property_changed('market_cap_category') self.__market_cap_category = value @property def historical_volume(self) -> float: """One month rolling average.""" return self.__historical_volume @historical_volume.setter def historical_volume(self, value: float): self._property_changed('historical_volume') self.__historical_volume = value @property def buy5cents(self) -> float: """The amount GS would buy for 5 cents charge.""" return self.__buy5cents @buy5cents.setter def buy5cents(self, value: float): self._property_changed('buy5cents') self.__buy5cents = value @property def event_start_date(self) -> datetime.date: """The start date of the event if the event occurs during a time window, in the time zone of the exchange where the company is listed (optional).""" return self.__event_start_date @event_start_date.setter def event_start_date(self, value: datetime.date): self._property_changed('event_start_date') self.__event_start_date = value @property def leg1_fixed_rate(self) -> float: """If fixed rate leg, the fixed rate of leg 1.""" return self.__leg1_fixed_rate @leg1_fixed_rate.setter def leg1_fixed_rate(self, value: float): self._property_changed('leg1_fixed_rate') self.__leg1_fixed_rate = value @property def equity_gamma(self) -> float: """Gamma exposure to equity products.""" return self.__equity_gamma @equity_gamma.setter def equity_gamma(self, value: float): self._property_changed('equity_gamma') self.__equity_gamma = value @property def rpt_id(self) -> str: """DDR generated unique and random ID for reconciliation purpose.""" return self.__rpt_id @rpt_id.setter def rpt_id(self, value: str): self._property_changed('rpt_id') self.__rpt_id = value @property def gross_income(self) -> float: """The income earned by the reinvested collateral including the rebate or fee, excluding lender or partner fees.""" return self.__gross_income @gross_income.setter def gross_income(self, value: float): self._property_changed('gross_income') self.__gross_income = value @property def em_id(self) -> str: """Entity Master identifier.""" return self.__em_id @em_id.setter def em_id(self, value: str): self._property_changed('em_id') self.__em_id = value @property def asset_count_in_model(self) -> float: """Number of assets in a portfolio in a given risk model.""" return self.__asset_count_in_model @asset_count_in_model.setter def asset_count_in_model(self, value: float): self._property_changed('asset_count_in_model') self.__asset_count_in_model = value @property def sts_credit_region(self) -> str: """Credit risk region.""" return self.__sts_credit_region @sts_credit_region.setter def sts_credit_region(self, value: str): self._property_changed('sts_credit_region') self.__sts_credit_region = value @property def min_temperature(self) -> float: """Minimum temperature observed on a given day in fahrenheit.""" return self.__min_temperature @min_temperature.setter def min_temperature(self, value: float): self._property_changed('min_temperature') self.__min_temperature = value @property def bucket_start_time(self) -> datetime.datetime: """The start time of the bucket in ISO 8601 formatted date and time.""" return self.__bucket_start_time @bucket_start_time.setter def bucket_start_time(self, value: datetime.datetime): self._property_changed('bucket_start_time') self.__bucket_start_time = value @property def fill_type(self) -> str: """Dark/Lit etc.""" return self.__fill_type @fill_type.setter def fill_type(self, value: str): self._property_changed('fill_type') self.__fill_type = value @property def close_time(self) -> datetime.datetime: """Time closed. ISO 8601 formatted string.""" return self.__close_time @close_time.setter def close_time(self, value: datetime.datetime): self._property_changed('close_time') self.__close_time = value @property def fail_pct(self) -> float: """Fail percentage.""" return self.__fail_pct @fail_pct.setter def fail_pct(self, value: float): self._property_changed('fail_pct') self.__fail_pct = value @property def iso_country_code_alpha2(self) -> str: """Iso2 country codes (2-digit).""" return self.__iso_country_code_alpha2 @iso_country_code_alpha2.setter def iso_country_code_alpha2(self, value: str): self._property_changed('iso_country_code_alpha2') self.__iso_country_code_alpha2 = value @property def iso_country_code_alpha3(self) -> str: """Iso3 country codes (3-digit).""" return self.__iso_country_code_alpha3 @iso_country_code_alpha3.setter def iso_country_code_alpha3(self, value: str): self._property_changed('iso_country_code_alpha3') self.__iso_country_code_alpha3 = value @property def amount(self) -> float: """Amount corporate actions pay out.""" return self.__amount @amount.setter def amount(self, value: float): self._property_changed('amount') self.__amount = value @property def lending_fund_acct(self) -> str: """Account associated with the securities lending fund.""" return self.__lending_fund_acct @lending_fund_acct.setter def lending_fund_acct(self, value: str): self._property_changed('lending_fund_acct') self.__lending_fund_acct = value @property def rebate(self) -> float: """Amount of the payment to an investor who puts up collateral in borrowing a stock.""" return self.__rebate @rebate.setter def rebate(self, value: float): self._property_changed('rebate') self.__rebate = value @property def election_type(self) -> str: """Type of election e.g. presidential.""" return self.__election_type @election_type.setter def election_type(self, value: str): self._property_changed('election_type') self.__election_type = value @property def relative_hit_rate_mtd(self) -> float: """Hit Rate Ratio Month to Date.""" return self.__relative_hit_rate_mtd @relative_hit_rate_mtd.setter def relative_hit_rate_mtd(self, value: float): self._property_changed('relative_hit_rate_mtd') self.__relative_hit_rate_mtd = value @property def implied_volatility(self) -> float: """Volatility of an asset implied by observations of market prices.""" return self.__implied_volatility @implied_volatility.setter def implied_volatility(self, value: float): self._property_changed('implied_volatility') self.__implied_volatility = value @property def spread(self) -> float: """Quoted (running) spread (mid) of buying / selling protection on an index. (Equally weighted CDS basket). In basis points.""" return self.__spread @spread.setter def spread(self, value: float): self._property_changed('spread') self.__spread = value @property def variance(self) -> float: """Market implied variance between two tenors.""" return self.__variance @variance.setter def variance(self, value: float): self._property_changed('variance') self.__variance = value @property def wtd_degree_days_daily_forecast(self) -> float: """The forecast value for weighted degree day in Fahrenheit.""" return self.__wtd_degree_days_daily_forecast @wtd_degree_days_daily_forecast.setter def wtd_degree_days_daily_forecast(self, value: float): self._property_changed('wtd_degree_days_daily_forecast') self.__wtd_degree_days_daily_forecast = value @property def swaption_annuity(self) -> float: """Swaption annuity.""" return self.__swaption_annuity @swaption_annuity.setter def swaption_annuity(self, value: float): self._property_changed('swaption_annuity') self.__swaption_annuity = value @property def buy6bps(self) -> float: """The amount GS would buy for 6 bps charge.""" return self.__buy6bps @buy6bps.setter def buy6bps(self, value: float): self._property_changed('buy6bps') self.__buy6bps = value @property def g10_currency(self) -> bool: """Is a G10 asset.""" return self.__g10_currency @g10_currency.setter def g10_currency(self, value: bool): self._property_changed('g10_currency') self.__g10_currency = value @property def humidity_forecast(self) -> float: """The forecast value for humidity.""" return self.__humidity_forecast @humidity_forecast.setter def humidity_forecast(self, value: float): self._property_changed('humidity_forecast') self.__humidity_forecast = value @property def relative_period(self) -> str: """The relative period forward for which the forecast is available.""" return self.__relative_period @relative_period.setter def relative_period(self, value: str): self._property_changed('relative_period') self.__relative_period = value @property def user(self) -> str: """Originating platform.""" return self.__user @user.setter def user(self, value: str): self._property_changed('user') self.__user = value @property def customer(self) -> str: """Customer kerberos.""" return self.__customer @customer.setter def customer(self, value: str): self._property_changed('customer') self.__customer = value @property def leg1_reset_frequency(self) -> str: """An integer multiplier of a period describing how often the parties to an SB swap transaction shall evaluate and, when applicable, change the price used for the underlying assets of the swap transaction (leg 1). Such reset frequency may be described as one letter preceded by an integer.""" return self.__leg1_reset_frequency @leg1_reset_frequency.setter def leg1_reset_frequency(self, value: str): self._property_changed('leg1_reset_frequency') self.__leg1_reset_frequency = value @property def queue_clock_time_label(self): return self.__queue_clock_time_label @queue_clock_time_label.setter def queue_clock_time_label(self, value): self._property_changed('queue_clock_time_label') self.__queue_clock_time_label = value @property def pace_of_rollp100(self) -> float: """The 100th-percentile of the pace of roll compared to previous quarters (over the last 5 years) at the same point in time, i.e. cross-sectional.""" return self.__pace_of_rollp100 @pace_of_rollp100.setter def pace_of_rollp100(self, value: float): self._property_changed('pace_of_rollp100') self.__pace_of_rollp100 = value @property def asset_classifications_gics_sub_industry(self) -> str: """GICS Sub Industry classification (level 4).""" return self.__asset_classifications_gics_sub_industry @asset_classifications_gics_sub_industry.setter def asset_classifications_gics_sub_industry(self, value: str): self._property_changed('asset_classifications_gics_sub_industry') self.__asset_classifications_gics_sub_industry = value @property def dew_point_hourly_forecast(self) -> float: """The hourly forecast value for dew point in Fahrenheit.""" return self.__dew_point_hourly_forecast @dew_point_hourly_forecast.setter def dew_point_hourly_forecast(self, value: float): self._property_changed('dew_point_hourly_forecast') self.__dew_point_hourly_forecast = value @property def location_type(self) -> str: """The type of location (of varying granularity), e.g. Nation, Region, etc.""" return self.__location_type @location_type.setter def location_type(self, value: str): self._property_changed('location_type') self.__location_type = value @property def facet_divisional_reporting_group_id(self) -> str: """FACET Divisional Reporting Group ID (DRG) most relevant to the Starting EMMA LE ID which contains all associated OE IDs.""" return self.__facet_divisional_reporting_group_id @facet_divisional_reporting_group_id.setter def facet_divisional_reporting_group_id(self, value: str): self._property_changed('facet_divisional_reporting_group_id') self.__facet_divisional_reporting_group_id = value @property def realized_twap_performance_usd(self) -> float: """Average execution price vs Consolidated TWAP since order inception ??? realized in $.""" return self.__realized_twap_performance_usd @realized_twap_performance_usd.setter def realized_twap_performance_usd(self, value: float): self._property_changed('realized_twap_performance_usd') self.__realized_twap_performance_usd = value @property def swap_rate(self) -> float: """ATM fixed rate for a benchmark tenor on a currency's fixed-floating swap curve.""" return self.__swap_rate @swap_rate.setter def swap_rate(self, value: float): self._property_changed('swap_rate') self.__swap_rate = value @property def algo_execution_style(self) -> str: """Algo execution style. Dictates how an algo order is being filled.""" return self.__algo_execution_style @algo_execution_style.setter def algo_execution_style(self, value: str): self._property_changed('algo_execution_style') self.__algo_execution_style = value @property def client_contact(self) -> str: """Name of client(s) requesting data.""" return self.__client_contact @client_contact.setter def client_contact(self, value: str): self._property_changed('client_contact') self.__client_contact = value @property def min_temperature_hour(self) -> float: """The forecast value of hour with minimum temperature in a day.""" return self.__min_temperature_hour @min_temperature_hour.setter def min_temperature_hour(self, value: float): self._property_changed('min_temperature_hour') self.__min_temperature_hour = value @property def trading_currency(self) -> str: """Execution currency.""" return self.__trading_currency @trading_currency.setter def trading_currency(self, value: str): self._property_changed('trading_currency') self.__trading_currency = value @property def total_by_onset(self) -> float: """Total number, by onset of infection, as opposed to confirmation. The date of onset is calculated as illness onset date if known. If not, an estimated illness onset date was calculated using specimen collection date.""" return self.__total_by_onset @total_by_onset.setter def total_by_onset(self, value: float): self._property_changed('total_by_onset') self.__total_by_onset = value @property def agency_swap_spread(self) -> float: """Agency swap spread.""" return self.__agency_swap_spread @agency_swap_spread.setter def agency_swap_spread(self, value: float): self._property_changed('agency_swap_spread') self.__agency_swap_spread = value @property def rank(self) -> float: """Rank to determine most relevant asset.""" return self.__rank @rank.setter def rank(self, value: float): self._property_changed('rank') self.__rank = value @property def mixed_swap_other_reported_sdr(self) -> str: """Indicates the other SDR to which a mixed swap is reported.""" return self.__mixed_swap_other_reported_sdr @mixed_swap_other_reported_sdr.setter def mixed_swap_other_reported_sdr(self, value: str): self._property_changed('mixed_swap_other_reported_sdr') self.__mixed_swap_other_reported_sdr = value @property def humidity(self) -> float: """The humidity forecast for given location.""" return self.__humidity @humidity.setter def humidity(self, value: float): self._property_changed('humidity') self.__humidity = value @property def data_set_category(self) -> str: """Top level grouping of dataset.""" return self.__data_set_category @data_set_category.setter def data_set_category(self, value: str): self._property_changed('data_set_category') self.__data_set_category = value @property def vwap_realized_bps(self) -> float: """Consolidated realised performance vs VWAP, in bps.""" return self.__vwap_realized_bps @vwap_realized_bps.setter def vwap_realized_bps(self, value: float): self._property_changed('vwap_realized_bps') self.__vwap_realized_bps = value @property def buy9bps(self) -> float: """The amount GS would buy for 9 bps charge.""" return self.__buy9bps @buy9bps.setter def buy9bps(self, value: float): self._property_changed('buy9bps') self.__buy9bps = value @property def total_tested(self) -> float: """Total number of tested cases.""" return self.__total_tested @total_tested.setter def total_tested(self, value: float): self._property_changed('total_tested') self.__total_tested = value @property def fatalities_confirmed(self) -> float: """Total number of confirmed fatalities.""" return self.__fatalities_confirmed @fatalities_confirmed.setter def fatalities_confirmed(self, value: float): self._property_changed('fatalities_confirmed') self.__fatalities_confirmed = value @property def universe_id1(self) -> str: """The risk model universe identifier value.""" return self.__universe_id1 @universe_id1.setter def universe_id1(self, value: str): self._property_changed('universe_id1') self.__universe_id1 = value @property def asset_parameters_payer_day_count_fraction(self) -> str: """Day Count Fraction""" return self.__asset_parameters_payer_day_count_fraction @asset_parameters_payer_day_count_fraction.setter def asset_parameters_payer_day_count_fraction(self, value: str): self._property_changed('asset_parameters_payer_day_count_fraction') self.__asset_parameters_payer_day_count_fraction = value @property def universe_id2(self) -> str: """The risk model universe identifier value.""" return self.__universe_id2 @universe_id2.setter def universe_id2(self, value: str): self._property_changed('universe_id2') self.__universe_id2 = value @property def bid_low(self) -> float: """Lowest Bid Price (price willing to buy).""" return self.__bid_low @bid_low.setter def bid_low(self, value: float): self._property_changed('bid_low') self.__bid_low = value @property def bucketize_price(self) -> float: """Aggregated price of power for given quantity bucket.""" return self.__bucketize_price @bucketize_price.setter def bucketize_price(self, value: float): self._property_changed('bucketize_price') self.__bucketize_price = value @property def fair_variance_volatility(self) -> float: """The strike in volatility terms, calculated as square root of fair variance.""" return self.__fair_variance_volatility @fair_variance_volatility.setter def fair_variance_volatility(self, value: float): self._property_changed('fair_variance_volatility') self.__fair_variance_volatility = value @property def covid19(self) -> float: """Total number of people affected by covid19 for agiven country.""" return self.__covid19 @covid19.setter def covid19(self, value: float): self._property_changed('covid19') self.__covid19 = value @property def client_exposure(self) -> float: """Exposure of client positions to the factor in percent of equity.""" return self.__client_exposure @client_exposure.setter def client_exposure(self, value: float): self._property_changed('client_exposure') self.__client_exposure = value @property def leg2_total_notional_unit(self) -> str: """Unit of reported notional price.""" return self.__leg2_total_notional_unit @leg2_total_notional_unit.setter def leg2_total_notional_unit(self, value: str): self._property_changed('leg2_total_notional_unit') self.__leg2_total_notional_unit = value @property def sell45cents(self) -> float: """The amount GS would sell for 45 cents charge.""" return self.__sell45cents @sell45cents.setter def sell45cents(self, value: float): self._property_changed('sell45cents') self.__sell45cents = value @property def gs_sustain_sub_sector(self) -> str: """GS SUSTAIN sector.""" return self.__gs_sustain_sub_sector @gs_sustain_sub_sector.setter def gs_sustain_sub_sector(self, value: str): self._property_changed('gs_sustain_sub_sector') self.__gs_sustain_sub_sector = value @property def sinkable(self) -> bool: """A bond that is protected by a fund (called a sinking fund) that sets aside money to ensure principal and interest payments are made by the issuer as promised.""" return self.__sinkable @sinkable.setter def sinkable(self, value: bool): self._property_changed('sinkable') self.__sinkable = value @property def is_real(self) -> bool: """Flag indicating whether the value is real or nominal.""" return self.__is_real @is_real.setter def is_real(self, value: bool): self._property_changed('is_real') self.__is_real = value @property def max_temperature_hour(self) -> float: """The forecast value of hour with max temperature in a day.""" return self.__max_temperature_hour @max_temperature_hour.setter def max_temperature_hour(self, value: float): self._property_changed('max_temperature_hour') self.__max_temperature_hour = value @property def leg2_averaging_method(self) -> str: """Averaging method of leg.""" return self.__leg2_averaging_method @leg2_averaging_method.setter def leg2_averaging_method(self, value: str): self._property_changed('leg2_averaging_method') self.__leg2_averaging_method = value @property def jsn(self) -> str: """Japan security number (subject to licensing).""" return self.__jsn @jsn.setter def jsn(self, value: str): self._property_changed('jsn') self.__jsn = value @property def sell160cents(self) -> float: """The amount GS would sell for 160 cents charge.""" return self.__sell160cents @sell160cents.setter def sell160cents(self, value: float): self._property_changed('sell160cents') self.__sell160cents = value @property def knock_in_direction(self) -> str: """The side of the knock-in price the market level must reach to trigger the knock in.""" return self.__knock_in_direction @knock_in_direction.setter def knock_in_direction(self, value: str): self._property_changed('knock_in_direction') self.__knock_in_direction = value @property def day_close_unrealized_usd(self) -> float: """Unrealized performance vs Close, in $.""" return self.__day_close_unrealized_usd @day_close_unrealized_usd.setter def day_close_unrealized_usd(self, value: float): self._property_changed('day_close_unrealized_usd') self.__day_close_unrealized_usd = value @property def tenor(self) -> str: """Tenor of instrument.""" return self.__tenor @tenor.setter def tenor(self, value: str): self._property_changed('tenor') self.__tenor = value @property def pricing_convention(self) -> str: """Pricing convention that is used.""" return self.__pricing_convention @pricing_convention.setter def pricing_convention(self, value: str): self._property_changed('pricing_convention') self.__pricing_convention = value @property def popularity(self) -> float: """Popularity of series.""" return self.__popularity @popularity.setter def popularity(self, value: float): self._property_changed('popularity') self.__popularity = value @property def floating_rate_option(self) -> str: """The underlying benchmark for the floating rate, e.g. USD-LIBOR-BBA, EUR-EURIBOR- TELERATE.""" return self.__floating_rate_option @floating_rate_option.setter def floating_rate_option(self, value: str): self._property_changed('floating_rate_option') self.__floating_rate_option = value @property def hedge_value_type(self) -> str: """The type of value for which is applied to value the hedge in the comparison.""" return self.__hedge_value_type @hedge_value_type.setter def hedge_value_type(self, value: str): self._property_changed('hedge_value_type') self.__hedge_value_type = value @property def asset_parameters_clearing_house(self) -> str: """Clearing house.""" return self.__asset_parameters_clearing_house @asset_parameters_clearing_house.setter def asset_parameters_clearing_house(self, value: str): self._property_changed('asset_parameters_clearing_house') self.__asset_parameters_clearing_house = value @property def disclaimer(self) -> str: """The legal disclaimer associated with the record.""" return self.__disclaimer @disclaimer.setter def disclaimer(self, value: str): self._property_changed('disclaimer') self.__disclaimer = value @property def payer_frequency(self) -> str: """Tenor""" return self.__payer_frequency @payer_frequency.setter def payer_frequency(self, value: str): self._property_changed('payer_frequency') self.__payer_frequency = value @property def loan_fee(self) -> float: """Fee charged for the loan of securities to a borrower in a securities lending agreement.""" return self.__loan_fee @loan_fee.setter def loan_fee(self, value: float): self._property_changed('loan_fee') self.__loan_fee = value @property def deployment_version(self) -> str: """Deployment version.""" return self.__deployment_version @deployment_version.setter def deployment_version(self, value: str): self._property_changed('deployment_version') self.__deployment_version = value @property def buy16bps(self) -> float: """The amount GS would buy for 16 bps charge.""" return self.__buy16bps @buy16bps.setter def buy16bps(self, value: float): self._property_changed('buy16bps') self.__buy16bps = value @property def trade_day_count(self) -> str: """The determination of how interest accrues over time for the SB swap.""" return self.__trade_day_count @trade_day_count.setter def trade_day_count(self, value: str): self._property_changed('trade_day_count') self.__trade_day_count = value @property def price_to_sales(self) -> float: """Price to sales.""" return self.__price_to_sales @price_to_sales.setter def price_to_sales(self, value: float): self._property_changed('price_to_sales') self.__price_to_sales = value @property def new_ideas_qtd(self) -> float: """Ideas received by clients Quarter to date.""" return self.__new_ideas_qtd @new_ideas_qtd.setter def new_ideas_qtd(self, value: float): self._property_changed('new_ideas_qtd') self.__new_ideas_qtd = value @property def subdivision_name(self) -> str: """Name of the region or subdivision.""" return self.__subdivision_name @subdivision_name.setter def subdivision_name(self, value: str): self._property_changed('subdivision_name') self.__subdivision_name = value @property def adjusted_ask_price(self) -> float: """Latest Ask Price (price offering to sell) adjusted for corporate actions.""" return self.__adjusted_ask_price @adjusted_ask_price.setter def adjusted_ask_price(self, value: float): self._property_changed('adjusted_ask_price') self.__adjusted_ask_price = value @property def factor_universe(self) -> str: """Factor universe.""" return self.__factor_universe @factor_universe.setter def factor_universe(self, value: str): self._property_changed('factor_universe') self.__factor_universe = value @property def arrival_rt(self) -> float: """Arrival Realtime.""" return self.__arrival_rt @arrival_rt.setter def arrival_rt(self, value: float): self._property_changed('arrival_rt') self.__arrival_rt = value @property def internal_index_calc_agent(self) -> bool: """Calculation agent of the index.""" return self.__internal_index_calc_agent @internal_index_calc_agent.setter def internal_index_calc_agent(self, value: bool): self._property_changed('internal_index_calc_agent') self.__internal_index_calc_agent = value @property def excess_margin_value(self) -> float: """Available credit value.""" return self.__excess_margin_value @excess_margin_value.setter def excess_margin_value(self, value: float): self._property_changed('excess_margin_value') self.__excess_margin_value = value @property def transaction_cost(self) -> float: """Transaction cost.""" return self.__transaction_cost @transaction_cost.setter def transaction_cost(self, value: float): self._property_changed('transaction_cost') self.__transaction_cost = value @property def central_bank_swap_rate(self) -> float: """Returns the OIS swap rate for a swap structured between consecutive meeting dates.""" return self.__central_bank_swap_rate @central_bank_swap_rate.setter def central_bank_swap_rate(self, value: float): self._property_changed('central_bank_swap_rate') self.__central_bank_swap_rate = value @property def previous_new_confirmed(self) -> float: """The previous day's number of new confirmed cases.""" return self.__previous_new_confirmed @previous_new_confirmed.setter def previous_new_confirmed(self, value: float): self._property_changed('previous_new_confirmed') self.__previous_new_confirmed = value @property def unrealized_vwap_performance_bps(self) -> float: """Average execution price vs Consolidated VWAP (in limit) since order inception ??? unrealized.""" return self.__unrealized_vwap_performance_bps @unrealized_vwap_performance_bps.setter def unrealized_vwap_performance_bps(self, value: float): self._property_changed('unrealized_vwap_performance_bps') self.__unrealized_vwap_performance_bps = value @property def degree_days_daily_forecast(self) -> float: """The forecast value for degree days in Fahrenheit.""" return self.__degree_days_daily_forecast @degree_days_daily_forecast.setter def degree_days_daily_forecast(self, value: float): self._property_changed('degree_days_daily_forecast') self.__degree_days_daily_forecast = value @property def position_amount(self) -> float: """Corporate actions amount * shares.""" return self.__position_amount @position_amount.setter def position_amount(self, value: float): self._property_changed('position_amount') self.__position_amount = value @property def heat_index_hourly_forecast(self) -> float: """The hourly forecast value for heat index in Fahrenheit.""" return self.__heat_index_hourly_forecast @heat_index_hourly_forecast.setter def heat_index_hourly_forecast(self, value: float): self._property_changed('heat_index_hourly_forecast') self.__heat_index_hourly_forecast = value @property def ma_rank(self) -> float: """M&A Rank, which may take on the following values: 1 represents high (at least 30%, but less than 50%) probability of the company becoming an acquisition target, 2 represents medium (at least 15%, but less than 30%) probability and 3 represents low (less than 15%) probability.""" return self.__ma_rank @ma_rank.setter def ma_rank(self, value: float): self._property_changed('ma_rank') self.__ma_rank = value @property def fx_positioning_source(self) -> str: """Source of positioning data in the FX market.""" return self.__fx_positioning_source @fx_positioning_source.setter def fx_positioning_source(self, value: str): self._property_changed('fx_positioning_source') self.__fx_positioning_source = value @property def event_start_date_time(self) -> datetime.datetime: """The start time of the event if the event occurs during a time window and the event has a specific start time, using UTC convention (optional).""" return self.__event_start_date_time @event_start_date_time.setter def event_start_date_time(self, value: datetime.datetime): self._property_changed('event_start_date_time') self.__event_start_date_time = value @property def implied_volatility_by_delta_strike(self) -> float: """Volatility of an asset implied by observations of market prices.""" return self.__implied_volatility_by_delta_strike @implied_volatility_by_delta_strike.setter def implied_volatility_by_delta_strike(self, value: float): self._property_changed('implied_volatility_by_delta_strike') self.__implied_volatility_by_delta_strike = value @property def mq_symbol(self) -> str: """Goldman Sachs Marquee Symbol applied to entities such as Backtester.""" return self.__mq_symbol @mq_symbol.setter def mq_symbol(self, value: str): self._property_changed('mq_symbol') self.__mq_symbol = value @property def num_total_units(self) -> float: """Total Units.""" return self.__num_total_units @num_total_units.setter def num_total_units(self, value: float): self._property_changed('num_total_units') self.__num_total_units = value @property def corporate_action(self) -> bool: """Whether or not it is a corporate action.""" return self.__corporate_action @corporate_action.setter def corporate_action(self, value: bool): self._property_changed('corporate_action') self.__corporate_action = value @property def leg1_price_type(self) -> str: """Price denomination and unit of leg 1.""" return self.__leg1_price_type @leg1_price_type.setter def leg1_price_type(self, value: str): self._property_changed('leg1_price_type') self.__leg1_price_type = value @property def asset_parameters_payer_rate_option(self) -> str: """The underlying benchmark for the payer, e.g. USD-LIBOR-BBA, EUR-EURIBOR- TELERATE.""" return self.__asset_parameters_payer_rate_option @asset_parameters_payer_rate_option.setter def asset_parameters_payer_rate_option(self, value: str): self._property_changed('asset_parameters_payer_rate_option') self.__asset_parameters_payer_rate_option = value @property def sell20cents(self) -> float: """The amount GS would sell for 20 cents charge.""" return self.__sell20cents @sell20cents.setter def sell20cents(self, value: float): self._property_changed('sell20cents') self.__sell20cents = value @property def leg2_fixed_payment_currency(self) -> str: """If fixed payment leg, the unit of fixed payment.""" return self.__leg2_fixed_payment_currency @leg2_fixed_payment_currency.setter def leg2_fixed_payment_currency(self, value: str): self._property_changed('leg2_fixed_payment_currency') self.__leg2_fixed_payment_currency = value @property def g_regional_score(self) -> float: """A company's score for G metrics within its region.""" return self.__g_regional_score @g_regional_score.setter def g_regional_score(self, value: float): self._property_changed('g_regional_score') self.__g_regional_score = value @property def hard_to_borrow(self) -> bool: """Whether or not an asset is hard to borrow.""" return self.__hard_to_borrow @hard_to_borrow.setter def hard_to_borrow(self, value: bool): self._property_changed('hard_to_borrow') self.__hard_to_borrow = value @property def sell5bps(self) -> float: """The amount GS would sell for 5 bps charge.""" return self.__sell5bps @sell5bps.setter def sell5bps(self, value: float): self._property_changed('sell5bps') self.__sell5bps = value @property def roll_vwap(self) -> float: """Calendar spread vwap.""" return self.__roll_vwap @roll_vwap.setter def roll_vwap(self, value: float): self._property_changed('roll_vwap') self.__roll_vwap = value @property def wpk(self) -> str: """Wertpapierkennnummer (WKN, WPKN, Wert), German security identifier code (subject to licensing).""" return self.__wpk @wpk.setter def wpk(self, value: str): self._property_changed('wpk') self.__wpk = value @property def bespoke_swap(self) -> str: """Indication if the trade is bespoke.""" return self.__bespoke_swap @bespoke_swap.setter def bespoke_swap(self, value: str): self._property_changed('bespoke_swap') self.__bespoke_swap = value @property def asset_parameters_expiration_date(self) -> str: """Relative expiration date.""" return self.__asset_parameters_expiration_date @asset_parameters_expiration_date.setter def asset_parameters_expiration_date(self, value: str): self._property_changed('asset_parameters_expiration_date') self.__asset_parameters_expiration_date = value @property def country_name(self) -> str: """Country name for which FCI is calculated.""" return self.__country_name @country_name.setter def country_name(self, value: str): self._property_changed('country_name') self.__country_name = value @property def carry(self) -> float: """Carry.""" return self.__carry @carry.setter def carry(self, value: float): self._property_changed('carry') self.__carry = value @property def starting_date(self) -> str: """Start date of the period the valuation refers to.""" return self.__starting_date @starting_date.setter def starting_date(self, value: str): self._property_changed('starting_date') self.__starting_date = value @property def loan_id(self) -> str: """Loan reference for a securities lending loan.""" return self.__loan_id @loan_id.setter def loan_id(self, value: str): self._property_changed('loan_id') self.__loan_id = value @property def onboarded(self) -> bool: """Whether or not social domain has been onboarded.""" return self.__onboarded @onboarded.setter def onboarded(self, value: bool): self._property_changed('onboarded') self.__onboarded = value @property def liquidity_score(self) -> float: """Liquidity conditions in the aggregate market, calculated as the average of touch liquidity score, touch spread score, and depth spread score.""" return self.__liquidity_score @liquidity_score.setter def liquidity_score(self, value: float): self._property_changed('liquidity_score') self.__liquidity_score = value @property def long_rates_contribution(self) -> float: """Contribution of long rate component to FCI.""" return self.__long_rates_contribution @long_rates_contribution.setter def long_rates_contribution(self, value: float): self._property_changed('long_rates_contribution') self.__long_rates_contribution = value @property def source_date_span(self) -> float: """Date span for event in days.""" return self.__source_date_span @source_date_span.setter def source_date_span(self, value: float): self._property_changed('source_date_span') self.__source_date_span = value @property def ann_yield6_month(self) -> float: """Calculates the total return for 6 months, representing past performance.""" return self.__ann_yield6_month @ann_yield6_month.setter def ann_yield6_month(self, value: float): self._property_changed('ann_yield6_month') self.__ann_yield6_month = value @property def underlying_data_set_id(self) -> str: """Dataset on which this (virtual) dataset is based.""" return self.__underlying_data_set_id @underlying_data_set_id.setter def underlying_data_set_id(self, value: str): self._property_changed('underlying_data_set_id') self.__underlying_data_set_id = value @property def close_unadjusted(self) -> float: """Unadjusted Close level of an asset based on official exchange fixing or calculation agent marked level.""" return self.__close_unadjusted @close_unadjusted.setter def close_unadjusted(self, value: float): self._property_changed('close_unadjusted') self.__close_unadjusted = value @property def value_unit(self) -> str: """Value unit.""" return self.__value_unit @value_unit.setter def value_unit(self, value: str): self._property_changed('value_unit') self.__value_unit = value @property def quantity_unit(self) -> str: """Unit of measure for trade quantity.""" return self.__quantity_unit @quantity_unit.setter def quantity_unit(self, value: str): self._property_changed('quantity_unit') self.__quantity_unit = value @property def adjusted_low_price(self) -> float: """Adjusted low level of an asset based on official exchange fixing or calculation agent marked level.""" return self.__adjusted_low_price @adjusted_low_price.setter def adjusted_low_price(self, value: float): self._property_changed('adjusted_low_price') self.__adjusted_low_price = value @property def is_momentum(self) -> bool: """Flag indicating whether the value is a momentum value or not.""" return self.__is_momentum @is_momentum.setter def is_momentum(self, value: bool): self._property_changed('is_momentum') self.__is_momentum = value @property def long_conviction_large(self) -> float: """The count of long ideas with large conviction.""" return self.__long_conviction_large @long_conviction_large.setter def long_conviction_large(self, value: float): self._property_changed('long_conviction_large') self.__long_conviction_large = value @property def oad(self) -> float: """Option-adjusted duration.""" return self.__oad @oad.setter def oad(self, value: float): self._property_changed('oad') self.__oad = value @property def rate(self) -> float: """Rate of the asset for the time period in percent.""" return self.__rate @rate.setter def rate(self, value: float): self._property_changed('rate') self.__rate = value @property def coupon_type(self) -> str: """The coupon type of the bond.""" return self.__coupon_type @coupon_type.setter def coupon_type(self, value: str): self._property_changed('coupon_type') self.__coupon_type = value @property def client(self) -> str: """Entity name.""" return self.__client @client.setter def client(self, value: str): self._property_changed('client') self.__client = value @property def conviction_list(self) -> bool: """Conviction List, which is true if the security is on the Conviction Buy List or false otherwise. Securities with a convictionList value equal to true are by definition a subset of the securities with a rating equal to Buy.""" return self.__conviction_list @conviction_list.setter def conviction_list(self, value: bool): self._property_changed('conviction_list') self.__conviction_list = value @property def passive_etf_ratio(self) -> float: """Ratio of passive ETF ownership, i.e the proportion of a company's marketcap that is held by passive ETFs. Normalised between -3 (low level) and +3 (high level).""" return self.__passive_etf_ratio @passive_etf_ratio.setter def passive_etf_ratio(self, value: float): self._property_changed('passive_etf_ratio') self.__passive_etf_ratio = value @property def future_month_g26(self) -> float: """Commods future month code.""" return self.__future_month_g26 @future_month_g26.setter def future_month_g26(self, value: float): self._property_changed('future_month_g26') self.__future_month_g26 = value @property def future_month_g25(self) -> float: """Commods future month code.""" return self.__future_month_g25 @future_month_g25.setter def future_month_g25(self, value: float): self._property_changed('future_month_g25') self.__future_month_g25 = value @property def future_month_g24(self) -> float: """Commods future month code.""" return self.__future_month_g24 @future_month_g24.setter def future_month_g24(self, value: float): self._property_changed('future_month_g24') self.__future_month_g24 = value @property def future_month_g23(self) -> float: """Commods future month code.""" return self.__future_month_g23 @future_month_g23.setter def future_month_g23(self, value: float): self._property_changed('future_month_g23') self.__future_month_g23 = value @property def type_of_return(self) -> str: """The type of return for the commodity index. Only applicable for commodity indices.""" return self.__type_of_return @type_of_return.setter def type_of_return(self, value: str): self._property_changed('type_of_return') self.__type_of_return = value @property def future_month_g22(self) -> float: """Commods future month code.""" return self.__future_month_g22 @future_month_g22.setter def future_month_g22(self, value: float): self._property_changed('future_month_g22') self.__future_month_g22 = value @property def servicing_cost_long_pnl(self) -> float: """Servicing Cost Long Profit and Loss.""" return self.__servicing_cost_long_pnl @servicing_cost_long_pnl.setter def servicing_cost_long_pnl(self, value: float): self._property_changed('servicing_cost_long_pnl') self.__servicing_cost_long_pnl = value @property def excess_margin_percentage(self) -> float: """Available credit percentage.""" return self.__excess_margin_percentage @excess_margin_percentage.setter def excess_margin_percentage(self, value: float): self._property_changed('excess_margin_percentage') self.__excess_margin_percentage = value @property def future_month_g21(self) -> float: """Commods future month code.""" return self.__future_month_g21 @future_month_g21.setter def future_month_g21(self, value: float): self._property_changed('future_month_g21') self.__future_month_g21 = value @property def total_mild(self) -> float: """Total number of active cases with mild symptoms.""" return self.__total_mild @total_mild.setter def total_mild(self, value: float): self._property_changed('total_mild') self.__total_mild = value @property def realized_arrival_performance_bps(self) -> float: """Average execution price vs Consolidated Arrival (in limit) since order inception ??? realized.""" return self.__realized_arrival_performance_bps @realized_arrival_performance_bps.setter def realized_arrival_performance_bps(self, value: float): self._property_changed('realized_arrival_performance_bps') self.__realized_arrival_performance_bps = value @property def precipitation_daily_forecast_inches(self) -> float: """The forecast value for precipitation in Inch.""" return self.__precipitation_daily_forecast_inches @precipitation_daily_forecast_inches.setter def precipitation_daily_forecast_inches(self, value: float): self._property_changed('precipitation_daily_forecast_inches') self.__precipitation_daily_forecast_inches = value @property def exchange_id(self) -> str: """Unique identifier for an exchange.""" return self.__exchange_id @exchange_id.setter def exchange_id(self, value: str): self._property_changed('exchange_id') self.__exchange_id = value @property def leg2_fixed_payment(self) -> float: """If fixed payment leg, the fixed payment amount, which is price*number of contracts bought*contract unit.""" return self.__leg2_fixed_payment @leg2_fixed_payment.setter def leg2_fixed_payment(self, value: float): self._property_changed('leg2_fixed_payment') self.__leg2_fixed_payment = value @property def tcm_cost_horizon20_day(self) -> float: """TCM cost with a 20 day time horizon.""" return self.__tcm_cost_horizon20_day @tcm_cost_horizon20_day.setter def tcm_cost_horizon20_day(self, value: float): self._property_changed('tcm_cost_horizon20_day') self.__tcm_cost_horizon20_day = value @property def realm(self) -> str: """Realm.""" return self.__realm @realm.setter def realm(self, value: str): self._property_changed('realm') self.__realm = value @property def bid(self) -> float: """Latest Bid Price (price willing to buy).""" return self.__bid @bid.setter def bid(self, value: float): self._property_changed('bid') self.__bid = value @property def hedge_value(self) -> float: return self.__hedge_value @hedge_value.setter def hedge_value(self, value: float): self._property_changed('hedge_value') self.__hedge_value = value @property def order_start_time(self) -> datetime.datetime: """Order start time.""" return self.__order_start_time @order_start_time.setter def order_start_time(self, value: datetime.datetime): self._property_changed('order_start_time') self.__order_start_time = value @property def is_aggressive(self) -> float: """Indicates if the fill was aggressive or passive.""" return self.__is_aggressive @is_aggressive.setter def is_aggressive(self, value: float): self._property_changed('is_aggressive') self.__is_aggressive = value @property def floating_rate_designated_maturity(self) -> str: """Tenor""" return self.__floating_rate_designated_maturity @floating_rate_designated_maturity.setter def floating_rate_designated_maturity(self, value: str): self._property_changed('floating_rate_designated_maturity') self.__floating_rate_designated_maturity = value @property def percentage_near_executed_quantity(self) -> float: """Percentage of total order filled at passive touch.""" return self.__percentage_near_executed_quantity @percentage_near_executed_quantity.setter def percentage_near_executed_quantity(self, value: float): self._property_changed('percentage_near_executed_quantity') self.__percentage_near_executed_quantity = value @property def order_id(self) -> str: """The unique ID of the order.""" return self.__order_id @order_id.setter def order_id(self, value: str): self._property_changed('order_id') self.__order_id = value @property def hospital_type(self) -> str: """Categorical type of hospital as defined by the last four digits of the hospital???s Medicare Provider Number, ie. Critical Access Hospital, Short Term Acute Care Hospital, etc.""" return self.__hospital_type @hospital_type.setter def hospital_type(self, value: str): self._property_changed('hospital_type') self.__hospital_type = value @property def day_close_realized_bps(self) -> float: """Realised performance vs Previous Close, in bps.""" return self.__day_close_realized_bps @day_close_realized_bps.setter def day_close_realized_bps(self, value: float): self._property_changed('day_close_realized_bps') self.__day_close_realized_bps = value @property def precipitation_hourly_forecast(self) -> float: """The hourly forecast value for precipitation in Inch.""" return self.__precipitation_hourly_forecast @precipitation_hourly_forecast.setter def precipitation_hourly_forecast(self, value: float): self._property_changed('precipitation_hourly_forecast') self.__precipitation_hourly_forecast = value @property def market_cap_usd(self) -> float: """Market capitalization of a given asset denominated in USD.""" return self.__market_cap_usd @market_cap_usd.setter def market_cap_usd(self, value: float): self._property_changed('market_cap_usd') self.__market_cap_usd = value @property def auction_fills_percentage(self) -> float: """Percent of total order filled in auction.""" return self.__auction_fills_percentage @auction_fills_percentage.setter def auction_fills_percentage(self, value: float): self._property_changed('auction_fills_percentage') self.__auction_fills_percentage = value @property def high_price(self) -> float: """High level of an asset based on official exchange fixing or calculation agent marked level.""" return self.__high_price @high_price.setter def high_price(self, value: float): self._property_changed('high_price') self.__high_price = value @property def absolute_shares(self) -> float: """The number of shares without adjusting for side.""" return self.__absolute_shares @absolute_shares.setter def absolute_shares(self, value: float): self._property_changed('absolute_shares') self.__absolute_shares = value @property def fixed_rate_day_count_fraction(self) -> str: """Day Count Fraction""" return self.__fixed_rate_day_count_fraction @fixed_rate_day_count_fraction.setter def fixed_rate_day_count_fraction(self, value: str): self._property_changed('fixed_rate_day_count_fraction') self.__fixed_rate_day_count_fraction = value @property def model(self) -> str: """Model.""" return self.__model @model.setter def model(self, value: str): self._property_changed('model') self.__model = value @property def unrealized_twap_performance_usd(self) -> float: """Average execution price vs Consolidated TWAP since order inception ??? unrealized in $.""" return self.__unrealized_twap_performance_usd @unrealized_twap_performance_usd.setter def unrealized_twap_performance_usd(self, value: float): self._property_changed('unrealized_twap_performance_usd') self.__unrealized_twap_performance_usd = value @property def id(self) -> str: """Marquee unique identifier""" return self.__id @id.setter def id(self, value: str): self._property_changed('id') self.__id = value @property def maturity(self) -> str: """Maturity of the instrument.""" return self.__maturity @maturity.setter def maturity(self, value: str): self._property_changed('maturity') self.__maturity = value @property def delta_change(self) -> str: """PadMaster delta changes.""" return self.__delta_change @delta_change.setter def delta_change(self, value: str): self._property_changed('delta_change') self.__delta_change = value @property def index(self) -> float: """Mid point or volume weighted average of all transactions.""" return self.__index @index.setter def index(self, value: float): self._property_changed('index') self.__index = value @property def unrealized_arrival_performance_usd(self) -> float: """Average execution price vs Consolidated Arrival (in limit) since order inception ??? unrealized in $.""" return self.__unrealized_arrival_performance_usd @unrealized_arrival_performance_usd.setter def unrealized_arrival_performance_usd(self, value: float): self._property_changed('unrealized_arrival_performance_usd') self.__unrealized_arrival_performance_usd = value @property def iceberg_slippage(self) -> float: """Iceberg slippage represented as pips.""" return self.__iceberg_slippage @iceberg_slippage.setter def iceberg_slippage(self, value: float): self._property_changed('iceberg_slippage') self.__iceberg_slippage = value @property def sell120cents(self) -> float: """The amount GS would sell for 120 cents charge.""" return self.__sell120cents @sell120cents.setter def sell120cents(self, value: float): self._property_changed('sell120cents') self.__sell120cents = value @property def future_month_x26(self) -> float: """Commods future month code.""" return self.__future_month_x26 @future_month_x26.setter def future_month_x26(self, value: float): self._property_changed('future_month_x26') self.__future_month_x26 = value @property def asset_types(self) -> Tuple[Tuple[Union[AssetType, str], ...], ...]: """Asset Types supported.""" return self.__asset_types @asset_types.setter def asset_types(self, value: Tuple[Tuple[Union[AssetType, str], ...], ...]): self._property_changed('asset_types') self.__asset_types = value @property def future_month_x25(self) -> float: """Commods future month code.""" return self.__future_month_x25 @future_month_x25.setter def future_month_x25(self, value: float): self._property_changed('future_month_x25') self.__future_month_x25 = value @property def bcid(self) -> str: """Bloomberg composite identifier (ticker and country code).""" return self.__bcid @bcid.setter def bcid(self, value: str): self._property_changed('bcid') self.__bcid = value @property def mkt_point(self) -> Tuple[Tuple[str, ...], ...]: """The MDAPI Point (e.g. 3m, 10y, 11y, Dec19).""" return self.__mkt_point @mkt_point.setter def mkt_point(self, value: Tuple[Tuple[str, ...], ...]): self._property_changed('mkt_point') self.__mkt_point = value @property def future_month_x24(self) -> float: """Commods future month code.""" return self.__future_month_x24 @future_month_x24.setter def future_month_x24(self, value: float): self._property_changed('future_month_x24') self.__future_month_x24 = value @property def restriction_start_date(self) -> datetime.date: """The date at which the security restriction was enacted.""" return self.__restriction_start_date @restriction_start_date.setter def restriction_start_date(self, value: datetime.date): self._property_changed('restriction_start_date') self.__restriction_start_date = value @property def touch_liquidity_score(self) -> float: """Z-score of the amount available to trade at the top of the aggregated order book.""" return self.__touch_liquidity_score @touch_liquidity_score.setter def touch_liquidity_score(self, value: float): self._property_changed('touch_liquidity_score') self.__touch_liquidity_score = value @property def future_month_x23(self) -> float: """Commods future month code.""" return self.__future_month_x23 @future_month_x23.setter def future_month_x23(self, value: float): self._property_changed('future_month_x23') self.__future_month_x23 = value @property def future_month_x22(self) -> float: """Commods future month code.""" return self.__future_month_x22 @future_month_x22.setter def future_month_x22(self, value: float): self._property_changed('future_month_x22') self.__future_month_x22 = value @property def factor_category_id(self) -> str: """Id for Factor Categories. Note that 'CUR' should be used to represent the currency category. Must match regex pattern ^[a-zA-Z0-9]{0,20}$.""" return self.__factor_category_id @factor_category_id.setter def factor_category_id(self, value: str): self._property_changed('factor_category_id') self.__factor_category_id = value @property def security_type_id(self) -> str: """PadMaster unique security type identifier.""" return self.__security_type_id @security_type_id.setter def security_type_id(self, value: str): self._property_changed('security_type_id') self.__security_type_id = value @property def future_month_x21(self) -> float: """Commods future month code.""" return self.__future_month_x21 @future_month_x21.setter def future_month_x21(self, value: float): self._property_changed('future_month_x21') self.__future_month_x21 = value @property def investment_ytd(self) -> float: """Total net investment Year to date.""" return self.__investment_ytd @investment_ytd.setter def investment_ytd(self, value: float): self._property_changed('investment_ytd') self.__investment_ytd = value @property def leg2_notional(self) -> float: """The total Notional amount or quantity of units of the leg 2 underlying asset.""" return self.__leg2_notional @leg2_notional.setter def leg2_notional(self, value: float): self._property_changed('leg2_notional') self.__leg2_notional = value @property def sell1bps(self) -> float: """The amount GS would sell for 1 bps charge.""" return self.__sell1bps @sell1bps.setter def sell1bps(self, value: float): self._property_changed('sell1bps') self.__sell1bps = value @property def sell200cents(self) -> float: """The amount GS would sell for 200 cents charge.""" return self.__sell200cents @sell200cents.setter def sell200cents(self, value: float): self._property_changed('sell200cents') self.__sell200cents = value @property def expected_completion_date(self) -> str: """Expected day of acquisition completion.""" return self.__expected_completion_date @expected_completion_date.setter def expected_completion_date(self, value: str): self._property_changed('expected_completion_date') self.__expected_completion_date = value @property def spread_option_vol(self) -> float: """Historical implied normal volatility for a liquid point on spread option vol surface.""" return self.__spread_option_vol @spread_option_vol.setter def spread_option_vol(self, value: float): self._property_changed('spread_option_vol') self.__spread_option_vol = value @property def sell80cents(self) -> float: """The amount GS would sell for 80 cents charge.""" return self.__sell80cents @sell80cents.setter def sell80cents(self, value: float): self._property_changed('sell80cents') self.__sell80cents = value @property def inflation_swap_rate(self) -> float: """Zero coupon inflation swap break-even rate for a given currency.""" return self.__inflation_swap_rate @inflation_swap_rate.setter def inflation_swap_rate(self, value: float): self._property_changed('inflation_swap_rate') self.__inflation_swap_rate = value @property def active_queries(self) -> float: """Active Queries.""" return self.__active_queries @active_queries.setter def active_queries(self, value: float): self._property_changed('active_queries') self.__active_queries = value @property def sell45bps(self) -> float: """The amount GS would sell for 45 bps charge.""" return self.__sell45bps @sell45bps.setter def sell45bps(self, value: float): self._property_changed('sell45bps') self.__sell45bps = value @property def embeded_option(self) -> str: """An indication of whether or not the option fields are for an embedded option.""" return self.__embeded_option @embeded_option.setter def embeded_option(self, value: str): self._property_changed('embeded_option') self.__embeded_option = value @property def event_source(self) -> str: """Equals GS if the event is sourced from Goldman Sachs Global Investment Research analysts. Equals TR if the event is sourced from Refinitive StreetEvents.""" return self.__event_source @event_source.setter def event_source(self, value: str): self._property_changed('event_source') self.__event_source = value @property def qis_perm_no(self) -> str: """QIS Permanent Security Number.""" return self.__qis_perm_no @qis_perm_no.setter def qis_perm_no(self, value: str): self._property_changed('qis_perm_no') self.__qis_perm_no = value @property def settlement(self) -> str: """Swap Settlement Type""" return self.__settlement @settlement.setter def settlement(self, value: str): self._property_changed('settlement') self.__settlement = value @property def shareclass_id(self) -> str: """Identifies shareclass with a unique code.""" return self.__shareclass_id @shareclass_id.setter def shareclass_id(self, value: str): self._property_changed('shareclass_id') self.__shareclass_id = value @property def feature2(self) -> str: """Second metrics feature.""" return self.__feature2 @feature2.setter def feature2(self, value: str): self._property_changed('feature2') self.__feature2 = value @property def feature3(self) -> str: """Third metrics feature.""" return self.__feature3 @feature3.setter def feature3(self, value: str): self._property_changed('feature3') self.__feature3 = value @property def sts_commodity_sector(self) -> str: """Commodity sector for STS assets.""" return self.__sts_commodity_sector @sts_commodity_sector.setter def sts_commodity_sector(self, value: str): self._property_changed('sts_commodity_sector') self.__sts_commodity_sector = value @property def exception_status(self) -> str: """The violation status for this particular line item.""" return self.__exception_status @exception_status.setter def exception_status(self, value: str): self._property_changed('exception_status') self.__exception_status = value @property def overnight_news_intensity(self) -> float: """A measure of the intensity/extent of media attention to each stock compared with other stocks in the index. It is based on the number of overnight news articles that are highly relevant for each stock. Overnight is between previous day???s close and current day???s open.""" return self.__overnight_news_intensity @overnight_news_intensity.setter def overnight_news_intensity(self, value: float): self._property_changed('overnight_news_intensity') self.__overnight_news_intensity = value @property def sales_coverage(self) -> str: """Primary or secondary sales coverage.""" return self.__sales_coverage @sales_coverage.setter def sales_coverage(self, value: str): self._property_changed('sales_coverage') self.__sales_coverage = value @property def feature1(self) -> str: """First metrics feature.""" return self.__feature1 @feature1.setter def feature1(self, value: str): self._property_changed('feature1') self.__feature1 = value @property def tcm_cost_participation_rate10_pct(self) -> float: """TCM cost with a 10 percent participation rate.""" return self.__tcm_cost_participation_rate10_pct @tcm_cost_participation_rate10_pct.setter def tcm_cost_participation_rate10_pct(self, value: float): self._property_changed('tcm_cost_participation_rate10_pct') self.__tcm_cost_participation_rate10_pct = value @property def event_time(self) -> str: """The time of the event if the event has a specific time or the end time of the event if the event occurs during a time window (optional). It is represented in HH:MM 24 hour format in the time zone of the exchange where the company is listed.""" return self.__event_time @event_time.setter def event_time(self, value: str): self._property_changed('event_time') self.__event_time = value @property def position_source_name(self) -> str: """Position source name for quick access.""" return self.__position_source_name @position_source_name.setter def position_source_name(self, value: str): self._property_changed('position_source_name') self.__position_source_name = value @property def delivery_date(self) -> datetime.date: """The final date by which the underlying commodity for a futures contract must be delivered in order for the terms of the contract to be fulfilled.""" return self.__delivery_date @delivery_date.setter def delivery_date(self, value: datetime.date): self._property_changed('delivery_date') self.__delivery_date = value @property def interest_rate(self) -> float: """Interest rate.""" return self.__interest_rate @interest_rate.setter def interest_rate(self, value: float): self._property_changed('interest_rate') self.__interest_rate = value @property def side(self) -> str: """Long or short.""" return self.__side @side.setter def side(self, value: str): self._property_changed('side') self.__side = value @property def dynamic_hybrid_aggressive_style(self) -> str: """Aggressive style of a Dynamic Hybrid order.""" return self.__dynamic_hybrid_aggressive_style @dynamic_hybrid_aggressive_style.setter def dynamic_hybrid_aggressive_style(self, value: str): self._property_changed('dynamic_hybrid_aggressive_style') self.__dynamic_hybrid_aggressive_style = value @property def compliance_restricted_status(self) -> str: """Restricted status as set by compliance.""" return self.__compliance_restricted_status @compliance_restricted_status.setter def compliance_restricted_status(self, value: str): self._property_changed('compliance_restricted_status') self.__compliance_restricted_status = value @property def borrow_fee(self) -> float: """An indication of the rate one would be charged for borrowing/shorting the relevant asset on that day, expressed in annualized percent terms. Rates may change daily.""" return self.__borrow_fee @borrow_fee.setter def borrow_fee(self, value: float): self._property_changed('borrow_fee') self.__borrow_fee = value @property def ever_icu(self) -> float: """Total number of patients ever in intensive care for COVID.""" return self.__ever_icu @ever_icu.setter def ever_icu(self, value: float): self._property_changed('ever_icu') self.__ever_icu = value @property def no_worse_than_level(self) -> float: """Prevents execution if the market level is worse than the specified level.""" return self.__no_worse_than_level @no_worse_than_level.setter def no_worse_than_level(self, value: float): self._property_changed('no_worse_than_level') self.__no_worse_than_level = value @property def update_time(self) -> datetime.datetime: """Update time of the data element, which allows historical as-of query.""" return self.__update_time @update_time.setter def update_time(self, value: datetime.datetime): self._property_changed('update_time') self.__update_time = value @property def loan_spread(self) -> float: """The difference between the investment rate on cash collateral and the rebate rate of a loan.""" return self.__loan_spread @loan_spread.setter def loan_spread(self, value: float): self._property_changed('loan_spread') self.__loan_spread = value @property def tcm_cost_horizon12_hour(self) -> float: """TCM cost with a 12 hour time horizon.""" return self.__tcm_cost_horizon12_hour @tcm_cost_horizon12_hour.setter def tcm_cost_horizon12_hour(self, value: float): self._property_changed('tcm_cost_horizon12_hour') self.__tcm_cost_horizon12_hour = value @property def dew_point(self) -> float: """Temperature in fahrenheit below which water condenses.""" return self.__dew_point @dew_point.setter def dew_point(self, value: float): self._property_changed('dew_point') self.__dew_point = value @property def research_commission(self) -> float: """The dollar amount of commissions received from clients.""" return self.__research_commission @research_commission.setter def research_commission(self, value: float): self._property_changed('research_commission') self.__research_commission = value @property def buy2bps(self) -> float: """The amount GS would buy for 2 bps charge.""" return self.__buy2bps @buy2bps.setter def buy2bps(self, value: float): self._property_changed('buy2bps') self.__buy2bps = value @property def asset_classifications_risk_country_code(self) -> str: """Risk Country code (ISO 3166).""" return self.__asset_classifications_risk_country_code @asset_classifications_risk_country_code.setter def asset_classifications_risk_country_code(self, value: str): self._property_changed('asset_classifications_risk_country_code') self.__asset_classifications_risk_country_code = value @property def new_ideas_mtd(self) -> float: """Ideas received by clients Month to date.""" return self.__new_ideas_mtd @new_ideas_mtd.setter def new_ideas_mtd(self, value: float): self._property_changed('new_ideas_mtd') self.__new_ideas_mtd = value @property def var_swap_by_expiry(self) -> float: """Strike such that the price of an uncapped variance swap on the underlying index is zero at inception.""" return self.__var_swap_by_expiry @var_swap_by_expiry.setter def var_swap_by_expiry(self, value: float): self._property_changed('var_swap_by_expiry') self.__var_swap_by_expiry = value @property def sell_date(self) -> datetime.date: """Sell date of the securities triggering the stock loan recall activity.""" return self.__sell_date @sell_date.setter def sell_date(self, value: datetime.date): self._property_changed('sell_date') self.__sell_date = value @property def aum_start(self) -> float: """Assets under management at the start of the period.""" return self.__aum_start @aum_start.setter def aum_start(self, value: float): self._property_changed('aum_start') self.__aum_start = value @property def asset_parameters_settlement(self) -> str: """Settlement type.""" return self.__asset_parameters_settlement @asset_parameters_settlement.setter def asset_parameters_settlement(self, value: str): self._property_changed('asset_parameters_settlement') self.__asset_parameters_settlement = value @property def max_temperature(self) -> float: """Maximum temperature observed on a given day in fahrenheit.""" return self.__max_temperature @max_temperature.setter def max_temperature(self, value: float): self._property_changed('max_temperature') self.__max_temperature = value @property def acquirer_shareholder_meeting_date(self) -> str: """Shareholders meeting date for acquiring entity.""" return self.__acquirer_shareholder_meeting_date @acquirer_shareholder_meeting_date.setter def acquirer_shareholder_meeting_date(self, value: str): self._property_changed('acquirer_shareholder_meeting_date') self.__acquirer_shareholder_meeting_date = value @property def count_ideas_wtd(self) -> float: """Ideas alive at a time Week to date.""" return self.__count_ideas_wtd @count_ideas_wtd.setter def count_ideas_wtd(self, value: float): self._property_changed('count_ideas_wtd') self.__count_ideas_wtd = value @property def arrival_rt_normalized(self) -> float: """Performance against Benchmark in pip.""" return self.__arrival_rt_normalized @arrival_rt_normalized.setter def arrival_rt_normalized(self, value: float): self._property_changed('arrival_rt_normalized') self.__arrival_rt_normalized = value @property def report_type(self) -> str: """Type of report to execute""" return self.__report_type @report_type.setter def report_type(self, value: str): self._property_changed('report_type') self.__report_type = value @property def source_url(self) -> str: """Source URL.""" return self.__source_url @source_url.setter def source_url(self, value: str): self._property_changed('source_url') self.__source_url = value @property def estimated_return(self) -> float: """Estimated return of asset over a given period (e.g. close-to-close).""" return self.__estimated_return @estimated_return.setter def estimated_return(self, value: float): self._property_changed('estimated_return') self.__estimated_return = value @property def high(self) -> float: """High level of an asset based on official exchange fixing or calculation agent marked level.""" return self.__high @high.setter def high(self, value: float): self._property_changed('high') self.__high = value @property def source_last_update(self) -> str: """Source last update.""" return self.__source_last_update @source_last_update.setter def source_last_update(self, value: str): self._property_changed('source_last_update') self.__source_last_update = value @property def sunshine_forecast(self) -> float: """The forecast value for sunshine.""" return self.__sunshine_forecast @sunshine_forecast.setter def sunshine_forecast(self, value: float): self._property_changed('sunshine_forecast') self.__sunshine_forecast = value @property def quantity_mw(self) -> float: """Quantity of electricity in megawatts.""" return self.__quantity_mw @quantity_mw.setter def quantity_mw(self, value: float): self._property_changed('quantity_mw') self.__quantity_mw = value @property def sell70cents(self) -> float: """The amount GS would sell for 70 cents charge.""" return self.__sell70cents @sell70cents.setter def sell70cents(self, value: float): self._property_changed('sell70cents') self.__sell70cents = value @property def sell110cents(self) -> float: """The amount GS would sell for 110 cents charge.""" return self.__sell110cents @sell110cents.setter def sell110cents(self, value: float): self._property_changed('sell110cents') self.__sell110cents = value @property def pnode_id(self) -> str: """Pricing node identifier sourced from Morningstar.""" return self.__pnode_id @pnode_id.setter def pnode_id(self, value: str): self._property_changed('pnode_id') self.__pnode_id = value @property def humidity_type(self) -> str: """The humidity type: Relative, average etc.""" return self.__humidity_type @humidity_type.setter def humidity_type(self, value: str): self._property_changed('humidity_type') self.__humidity_type = value @property def prev_close_ask(self) -> float: """Previous business day's close ask price.""" return self.__prev_close_ask @prev_close_ask.setter def prev_close_ask(self, value: float): self._property_changed('prev_close_ask') self.__prev_close_ask = value @property def level(self) -> float: """Level of the 5-day normalized flow in a given factor.""" return self.__level @level.setter def level(self, value: float): self._property_changed('level') self.__level = value @property def implied_volatility_by_expiration(self) -> float: """Volatility of an asset implied by observations of market prices.""" return self.__implied_volatility_by_expiration @implied_volatility_by_expiration.setter def implied_volatility_by_expiration(self, value: float): self._property_changed('implied_volatility_by_expiration') self.__implied_volatility_by_expiration = value @property def asset_parameters_fixed_rate_day_count_fraction(self) -> str: """Day Count Fraction""" return self.__asset_parameters_fixed_rate_day_count_fraction @asset_parameters_fixed_rate_day_count_fraction.setter def asset_parameters_fixed_rate_day_count_fraction(self, value: str): self._property_changed('asset_parameters_fixed_rate_day_count_fraction') self.__asset_parameters_fixed_rate_day_count_fraction = value @property def es_momentum_score(self) -> float: """A company's score for E&S subsector-relative momentum.""" return self.__es_momentum_score @es_momentum_score.setter def es_momentum_score(self, value: float): self._property_changed('es_momentum_score') self.__es_momentum_score = value @property def leg2_index(self) -> str: """If floating index leg, the index.""" return self.__leg2_index @leg2_index.setter def leg2_index(self, value: str): self._property_changed('leg2_index') self.__leg2_index = value @property def net_weight(self) -> float: """Difference between the longWeight and shortWeight. If you have IBM stock with shortWeight 0.2 and also IBM stock with longWeight 0.4, then the netWeight would be 0.2 (-0.2+0.4).""" return self.__net_weight @net_weight.setter def net_weight(self, value: float): self._property_changed('net_weight') self.__net_weight = value @property def portfolio_managers(self) -> Tuple[str, ...]: """Portfolio managers of asset.""" return self.__portfolio_managers @portfolio_managers.setter def portfolio_managers(self, value: Tuple[str, ...]): self._property_changed('portfolio_managers') self.__portfolio_managers = value @property def bos_in_ticks(self) -> float: """The Bid-Offer Spread of the stock in Ticks on the particular date.""" return self.__bos_in_ticks @bos_in_ticks.setter def bos_in_ticks(self, value: float): self._property_changed('bos_in_ticks') self.__bos_in_ticks = value @property def asset_parameters_coupon_type(self) -> str: """The coupon type of the bond.""" return self.__asset_parameters_coupon_type @asset_parameters_coupon_type.setter def asset_parameters_coupon_type(self, value: str): self._property_changed('asset_parameters_coupon_type') self.__asset_parameters_coupon_type = value @property def expected_residual_quantity(self) -> float: """Expected Residual Quantity.""" return self.__expected_residual_quantity @expected_residual_quantity.setter def expected_residual_quantity(self, value: float): self._property_changed('expected_residual_quantity') self.__expected_residual_quantity = value @property def roll_date(self) -> datetime.date: """Roll Dates for Equity Index Quarterly Futures.""" return self.__roll_date @roll_date.setter def roll_date(self, value: datetime.date): self._property_changed('roll_date') self.__roll_date = value @property def dynamic_hybrid_speed(self) -> str: """Execution speed of a Dynamic Hybrid order.""" return self.__dynamic_hybrid_speed @dynamic_hybrid_speed.setter def dynamic_hybrid_speed(self, value: str): self._property_changed('dynamic_hybrid_speed') self.__dynamic_hybrid_speed = value @property def cap_floor_vol(self) -> float: """Historical implied normal volatility for a liquid point on cap and floor vol surface.""" return self.__cap_floor_vol @cap_floor_vol.setter def cap_floor_vol(self, value: float): self._property_changed('cap_floor_vol') self.__cap_floor_vol = value @property def target_quantity(self) -> float: """The target executed quantity as published from the algo.""" return self.__target_quantity @target_quantity.setter def target_quantity(self, value: float): self._property_changed('target_quantity') self.__target_quantity = value @property def submitter(self) -> str: """Name of person submitting request.""" return self.__submitter @submitter.setter def submitter(self, value: str): self._property_changed('submitter') self.__submitter = value @property def no(self) -> float: """Price of no contract.""" return self.__no @no.setter def no(self, value: float): self._property_changed('no') self.__no = value @property def notional(self) -> float: """Notional.""" return self.__notional @notional.setter def notional(self, value: float): self._property_changed('notional') self.__notional = value @property def es_disclosure_percentage(self) -> float: """The percentage of E&S metrics the company discloses relative to the number of E&S metrics relevant for its subsector.""" return self.__es_disclosure_percentage @es_disclosure_percentage.setter def es_disclosure_percentage(self, value: float): self._property_changed('es_disclosure_percentage') self.__es_disclosure_percentage = value @property def close_executed_quantity_percentage(self) -> float: """Percent of order executed in closing Auction.""" return self.__close_executed_quantity_percentage @close_executed_quantity_percentage.setter def close_executed_quantity_percentage(self, value: float): self._property_changed('close_executed_quantity_percentage') self.__close_executed_quantity_percentage = value @property def twap_realized_cash(self) -> float: """Consolidated realised performance vs TWAP In Limit, in USD.""" return self.__twap_realized_cash @twap_realized_cash.setter def twap_realized_cash(self, value: float): self._property_changed('twap_realized_cash') self.__twap_realized_cash = value @property def is_open_auction(self) -> bool: """Flag indicating whether the bucket is within the open auction or not.""" return self.__is_open_auction @is_open_auction.setter def is_open_auction(self, value: bool): self._property_changed('is_open_auction') self.__is_open_auction = value @property def leg1_type(self) -> str: """Indication if leg 1 is fixed or floating or Physical.""" return self.__leg1_type @leg1_type.setter def leg1_type(self, value: str): self._property_changed('leg1_type') self.__leg1_type = value @property def wet_bulb_temp_hourly_forecast(self) -> float: """The hourly forecast value for wet bulb temperature in Fahrenheit.""" return self.__wet_bulb_temp_hourly_forecast @wet_bulb_temp_hourly_forecast.setter def wet_bulb_temp_hourly_forecast(self, value: float): self._property_changed('wet_bulb_temp_hourly_forecast') self.__wet_bulb_temp_hourly_forecast = value @property def cleanup_price(self) -> float: """The WIG price used by the algo.""" return self.__cleanup_price @cleanup_price.setter def cleanup_price(self, value: float): self._property_changed('cleanup_price') self.__cleanup_price = value @property def total(self) -> float: """Total exposure.""" return self.__total @total.setter def total(self, value: float): self._property_changed('total') self.__total = value @property def filled_notional_usd(self) -> float: """Executed Notional in USD.""" return self.__filled_notional_usd @filled_notional_usd.setter def filled_notional_usd(self, value: float): self._property_changed('filled_notional_usd') self.__filled_notional_usd = value @property def asset_id(self) -> str: """Marquee unique asset identifier.""" return self.__asset_id @asset_id.setter def asset_id(self, value: str): self._property_changed('asset_id') self.__asset_id = value @property def test_status(self) -> str: """Result of the quality test.""" return self.__test_status @test_status.setter def test_status(self, value: str): self._property_changed('test_status') self.__test_status = value @property def mkt_type(self) -> str: """The MDAPI Type (e.g. IR BASIS, FX Vol).""" return self.__mkt_type @mkt_type.setter def mkt_type(self, value: str): self._property_changed('mkt_type') self.__mkt_type = value @property def last_updated_time(self) -> datetime.datetime: """Timestamp of when the object was last updated.""" return self.__last_updated_time @last_updated_time.setter def last_updated_time(self, value: datetime.datetime): self._property_changed('last_updated_time') self.__last_updated_time = value @property def yield30_day(self) -> float: """Net income per share for last 30 days/NAV.""" return self.__yield30_day @yield30_day.setter def yield30_day(self, value: float): self._property_changed('yield30_day') self.__yield30_day = value @property def buy28bps(self) -> float: """The amount GS would buy for 28 bps charge.""" return self.__buy28bps @buy28bps.setter def buy28bps(self, value: float): self._property_changed('buy28bps') self.__buy28bps = value @property def proportion_of_risk(self) -> float: """Proportion of risk with respect to the portfolio's total risk.""" return self.__proportion_of_risk @proportion_of_risk.setter def proportion_of_risk(self, value: float): self._property_changed('proportion_of_risk') self.__proportion_of_risk = value @property def future_month_k23(self) -> float: """Commods future month code.""" return self.__future_month_k23 @future_month_k23.setter def future_month_k23(self, value: float): self._property_changed('future_month_k23') self.__future_month_k23 = value @property def future_month_k22(self) -> float: """Commods future month code.""" return self.__future_month_k22 @future_month_k22.setter def future_month_k22(self, value: float): self._property_changed('future_month_k22') self.__future_month_k22 = value @property def future_month_k21(self) -> float: """Commods future month code.""" return self.__future_month_k21 @future_month_k21.setter def future_month_k21(self, value: float): self._property_changed('future_month_k21') self.__future_month_k21 = value @property def primary_entity_id(self) -> str: """Primary Key for an entity; in use for Data Quality Checker.""" return self.__primary_entity_id @primary_entity_id.setter def primary_entity_id(self, value: str): self._property_changed('primary_entity_id') self.__primary_entity_id = value @property def cross(self) -> str: """FX cross symbol.""" return self.__cross @cross.setter def cross(self, value: str): self._property_changed('cross') self.__cross = value @property def idea_status(self) -> str: """The activity status of the idea.""" return self.__idea_status @idea_status.setter def idea_status(self, value: str): self._property_changed('idea_status') self.__idea_status = value @property def contract_subtype(self) -> str: """Contract subtype.""" return self.__contract_subtype @contract_subtype.setter def contract_subtype(self, value: str): self._property_changed('contract_subtype') self.__contract_subtype = value @property def sri(self) -> bool: """Whether or not an asset is classified as socially responsible investing.""" return self.__sri @sri.setter def sri(self, value: bool): self._property_changed('sri') self.__sri = value @property def fx_forecast(self) -> float: """FX forecast value for the relative period.""" return self.__fx_forecast @fx_forecast.setter def fx_forecast(self, value: float): self._property_changed('fx_forecast') self.__fx_forecast = value @property def fixing_time_label(self) -> str: """Time at which the fixing was taken.""" return self.__fixing_time_label @fixing_time_label.setter def fixing_time_label(self, value: str): self._property_changed('fixing_time_label') self.__fixing_time_label = value @property def is_etf(self) -> bool: """Whether or not assetType is an ETF.""" return self.__is_etf @is_etf.setter def is_etf(self, value: bool): self._property_changed('is_etf') self.__is_etf = value @property def fill_id(self) -> str: """Unique identifier for a fill.""" return self.__fill_id @fill_id.setter def fill_id(self, value: str): self._property_changed('fill_id') self.__fill_id = value @property def excess_returns(self) -> float: """Excess returns for backtest.""" return self.__excess_returns @excess_returns.setter def excess_returns(self, value: float): self._property_changed('excess_returns') self.__excess_returns = value @property def dollar_return(self) -> float: """Dollar return of asset over a given period (e.g. close-to-close).""" return self.__dollar_return @dollar_return.setter def dollar_return(self, value: float): self._property_changed('dollar_return') self.__dollar_return = value @property def order_in_limit(self) -> bool: """Is the order???s passive leg in limit.""" return self.__order_in_limit @order_in_limit.setter def order_in_limit(self, value: bool): self._property_changed('order_in_limit') self.__order_in_limit = value @property def expiry_time(self) -> datetime.datetime: """Order expiration date.""" return self.__expiry_time @expiry_time.setter def expiry_time(self, value: datetime.datetime): self._property_changed('expiry_time') self.__expiry_time = value @property def return_on_equity(self) -> float: """Return on equity.""" return self.__return_on_equity @return_on_equity.setter def return_on_equity(self, value: float): self._property_changed('return_on_equity') self.__return_on_equity = value @property def future_month_k26(self) -> float: """Commods future month code.""" return self.__future_month_k26 @future_month_k26.setter def future_month_k26(self, value: float): self._property_changed('future_month_k26') self.__future_month_k26 = value @property def future_month_k25(self) -> float: """Commods future month code.""" return self.__future_month_k25 @future_month_k25.setter def future_month_k25(self, value: float): self._property_changed('future_month_k25') self.__future_month_k25 = value @property def future_month_k24(self) -> float: """Commods future month code.""" return self.__future_month_k24 @future_month_k24.setter def future_month_k24(self, value: float): self._property_changed('future_month_k24') self.__future_month_k24 = value @property def restriction_end_date(self) -> datetime.date: """The date at which the security restriction was lifted.""" return self.__restriction_end_date @restriction_end_date.setter def restriction_end_date(self, value: datetime.date): self._property_changed('restriction_end_date') self.__restriction_end_date = value @property def queue_in_lots_description(self) -> str: """Description of the Stock's Queue size in Lots (if applicable) on the particular date.""" return self.__queue_in_lots_description @queue_in_lots_description.setter def queue_in_lots_description(self, value: str): self._property_changed('queue_in_lots_description') self.__queue_in_lots_description = value @property def volume_limit(self) -> float: """The allowed percentage of ATV that can be placed on loan for a lender.""" return self.__volume_limit @volume_limit.setter def volume_limit(self, value: float): self._property_changed('volume_limit') self.__volume_limit = value @property def objective(self) -> str: """The objective of the hedge.""" return self.__objective @objective.setter def objective(self, value: str): self._property_changed('objective') self.__objective = value @property def nav_price(self) -> float: """Net asset value price. Quoted price (mid, 100 ??? Upfront) of the underlying basket of single name CDS. (Theoretical Index value). In percent.""" return self.__nav_price @nav_price.setter def nav_price(self, value: float): self._property_changed('nav_price') self.__nav_price = value @property def leg1_underlying_asset(self) -> str: """The asset, reference asset, or reference obligation for payments of a party???s obligations under the SB swap transaction reference.""" return self.__leg1_underlying_asset @leg1_underlying_asset.setter def leg1_underlying_asset(self, value: str): self._property_changed('leg1_underlying_asset') self.__leg1_underlying_asset = value @property def private_placement_type(self) -> str: """Regulation that applies to a bond.""" return self.__private_placement_type @private_placement_type.setter def private_placement_type(self, value: str): self._property_changed('private_placement_type') self.__private_placement_type = value @property def hedge_notional(self) -> float: """Notional value of the hedge.""" return self.__hedge_notional @hedge_notional.setter def hedge_notional(self, value: float): self._property_changed('hedge_notional') self.__hedge_notional = value @property def ask_low(self) -> float: """The lowest ask Price (price offering to sell).""" return self.__ask_low @ask_low.setter def ask_low(self, value: float): self._property_changed('ask_low') self.__ask_low = value @property def intended_p_rate(self) -> float: """The intended participation rate from the algo.""" return self.__intended_p_rate @intended_p_rate.setter def intended_p_rate(self, value: float): self._property_changed('intended_p_rate') self.__intended_p_rate = value @property def expiry(self) -> str: """The time period before the option expires.""" return self.__expiry @expiry.setter def expiry(self, value: str): self._property_changed('expiry') self.__expiry = value @property def avg_monthly_yield(self) -> float: """Only used for GS Money Market funds, assumes sum of the past 30 days, divided by 30, and expressed as a percent.""" return self.__avg_monthly_yield @avg_monthly_yield.setter def avg_monthly_yield(self, value: float): self._property_changed('avg_monthly_yield') self.__avg_monthly_yield = value @property def period_direction(self) -> str: """Direction of the outlook period.""" return self.__period_direction @period_direction.setter def period_direction(self, value: str): self._property_changed('period_direction') self.__period_direction = value @property def prev_rpt_id(self) -> str: """On cancellations and corrections, this ID will hold the Dissemination ID of the originally published real-time message.""" return self.__prev_rpt_id @prev_rpt_id.setter def prev_rpt_id(self, value: str): self._property_changed('prev_rpt_id') self.__prev_rpt_id = value @property def earnings_per_share(self) -> float: """Earnings per share.""" return self.__earnings_per_share @earnings_per_share.setter def earnings_per_share(self, value: float): self._property_changed('earnings_per_share') self.__earnings_per_share = value @property def strike_percentage(self) -> float: """Strike compared to market value.""" return self.__strike_percentage @strike_percentage.setter def strike_percentage(self, value: float): self._property_changed('strike_percentage') self.__strike_percentage = value @property def es_product_impact_percentile(self) -> float: """A percentile that captures a company's E&S product impact ranking within its subsector.""" return self.__es_product_impact_percentile @es_product_impact_percentile.setter def es_product_impact_percentile(self, value: float): self._property_changed('es_product_impact_percentile') self.__es_product_impact_percentile = value @property def vwap_realized_cash(self) -> float: """Consolidated realised performance vs VWAP, in USD.""" return self.__vwap_realized_cash @vwap_realized_cash.setter def vwap_realized_cash(self, value: float): self._property_changed('vwap_realized_cash') self.__vwap_realized_cash = value @property def par_asset_swap_spread1m(self) -> float: """Par asset swap spread vs 1m tenor.""" return self.__par_asset_swap_spread1m @par_asset_swap_spread1m.setter def par_asset_swap_spread1m(self, value: float): self._property_changed('par_asset_swap_spread1m') self.__par_asset_swap_spread1m = value @property def prev_close_bid(self) -> float: """Previous close BID price.""" return self.__prev_close_bid @prev_close_bid.setter def prev_close_bid(self, value: float): self._property_changed('prev_close_bid') self.__prev_close_bid = value @property def minimum_increment(self) -> float: """The minimum increment size of the bond purchase allowed above the minimum denomination as authorized by the bond documents.""" return self.__minimum_increment @minimum_increment.setter def minimum_increment(self, value: float): self._property_changed('minimum_increment') self.__minimum_increment = value @property def tcm_cost_horizon16_day(self) -> float: """TCM cost with a 16 day time horizon.""" return self.__tcm_cost_horizon16_day @tcm_cost_horizon16_day.setter def tcm_cost_horizon16_day(self, value: float): self._property_changed('tcm_cost_horizon16_day') self.__tcm_cost_horizon16_day = value @property def investment_mtd(self) -> float: """Total net investment Month to date.""" return self.__investment_mtd @investment_mtd.setter def investment_mtd(self, value: float): self._property_changed('investment_mtd') self.__investment_mtd = value @property def settlement_date(self) -> datetime.date: """The settlement date of the associated FX contract.""" return self.__settlement_date @settlement_date.setter def settlement_date(self, value: datetime.date): self._property_changed('settlement_date') self.__settlement_date = value @property def weighted_average_mid_normalized(self) -> float: """Performance against Benchmark in pip.""" return self.__weighted_average_mid_normalized @weighted_average_mid_normalized.setter def weighted_average_mid_normalized(self, value: float): self._property_changed('weighted_average_mid_normalized') self.__weighted_average_mid_normalized = value @property def sales_per_share(self) -> float: """Sales per share.""" return self.__sales_per_share @sales_per_share.setter def sales_per_share(self, value: float): self._property_changed('sales_per_share') self.__sales_per_share = value @property def unadjusted_close(self) -> float: """Unadjusted Close level of an asset based on official exchange fixing or calculation agent marked level.""" return self.__unadjusted_close @unadjusted_close.setter def unadjusted_close(self, value: float): self._property_changed('unadjusted_close') self.__unadjusted_close = value @property def loan_date(self) -> datetime.date: """The date at which the securities loan was enacted.""" return self.__loan_date @loan_date.setter def loan_date(self, value: datetime.date): self._property_changed('loan_date') self.__loan_date = value @property def matched_maturity_swap_spread1m(self) -> float: """Matched maturity swap spread vs 1m tenor.""" return self.__matched_maturity_swap_spread1m @matched_maturity_swap_spread1m.setter def matched_maturity_swap_spread1m(self, value: float): self._property_changed('matched_maturity_swap_spread1m') self.__matched_maturity_swap_spread1m = value @property def collateral_percentage_actual(self) -> float: """Collateral percentage covering contractual the given position.""" return self.__collateral_percentage_actual @collateral_percentage_actual.setter def collateral_percentage_actual(self, value: float): self._property_changed('collateral_percentage_actual') self.__collateral_percentage_actual = value @property def vwap_in_limit_unrealized_bps(self) -> float: """Consolidated unrealised performance vs VWAP In Limit, in bps.""" return self.__vwap_in_limit_unrealized_bps @vwap_in_limit_unrealized_bps.setter def vwap_in_limit_unrealized_bps(self, value: float): self._property_changed('vwap_in_limit_unrealized_bps') self.__vwap_in_limit_unrealized_bps = value @property def metric_value(self) -> float: """Value of the metric calculated for the given geographyName and date.""" return self.__metric_value @metric_value.setter def metric_value(self, value: float): self._property_changed('metric_value') self.__metric_value = value @property def auto_exec_state(self) -> str: """Auto Execution State.""" return self.__auto_exec_state @auto_exec_state.setter def auto_exec_state(self, value: str): self._property_changed('auto_exec_state') self.__auto_exec_state = value @property def total_recovered(self) -> float: """Total number of recovered cases.""" return self.__total_recovered @total_recovered.setter def total_recovered(self, value: float): self._property_changed('total_recovered') self.__total_recovered = value @property def relative_return_ytd(self) -> float: """Relative Return Year to Date.""" return self.__relative_return_ytd @relative_return_ytd.setter def relative_return_ytd(self, value: float): self._property_changed('relative_return_ytd') self.__relative_return_ytd = value @property def tick_server(self) -> str: """Tickserver Symbol.""" return self.__tick_server @tick_server.setter def tick_server(self, value: str): self._property_changed('tick_server') self.__tick_server = value @property def cumulative_volume_in_percentage(self) -> float: """Forecast of the percentage of the cumulative volume of shares from start of day to the end of the bucket interval, relative to the total daily volume.""" return self.__cumulative_volume_in_percentage @cumulative_volume_in_percentage.setter def cumulative_volume_in_percentage(self, value: float): self._property_changed('cumulative_volume_in_percentage') self.__cumulative_volume_in_percentage = value @property def real_time_restriction_status(self) -> Tuple[Tuple[str, ...], ...]: """Real Time Restricted status as set by compliance.""" return self.__real_time_restriction_status @real_time_restriction_status.setter def real_time_restriction_status(self, value: Tuple[Tuple[str, ...], ...]): self._property_changed('real_time_restriction_status') self.__real_time_restriction_status = value @property def trade_type(self) -> str: """Trade type.""" return self.__trade_type @trade_type.setter def trade_type(self, value: str): self._property_changed('trade_type') self.__trade_type = value @property def settlement_type(self) -> str: """Swap Settlement Type""" return self.__settlement_type @settlement_type.setter def settlement_type(self, value: str): self._property_changed('settlement_type') self.__settlement_type = value @property def net_change(self) -> float: """Difference between the lastest trading price or value and the adjusted historical closing value or settlement price.""" return self.__net_change @net_change.setter def net_change(self, value: float): self._property_changed('net_change') self.__net_change = value @property def number_of_underliers(self) -> float: """Total number of underliers.""" return self.__number_of_underliers @number_of_underliers.setter def number_of_underliers(self, value: float): self._property_changed('number_of_underliers') self.__number_of_underliers = value @property def swap_type(self) -> str: """Swap type of position.""" return self.__swap_type @swap_type.setter def swap_type(self, value: str): self._property_changed('swap_type') self.__swap_type = value @property def forecast_type(self) -> str: """Type of return for commodity indices. Spot for individual commodities.""" return self.__forecast_type @forecast_type.setter def forecast_type(self, value: str): self._property_changed('forecast_type') self.__forecast_type = value @property def leg1_notional(self) -> float: """The total Notional amount or quantity of units of the leg 1 underlying asset.""" return self.__leg1_notional @leg1_notional.setter def leg1_notional(self, value: float): self._property_changed('leg1_notional') self.__leg1_notional = value @property def sell_settle_date(self) -> datetime.date: """Data that the sell of securities will settle.""" return self.__sell_settle_date @sell_settle_date.setter def sell_settle_date(self, value: datetime.date): self._property_changed('sell_settle_date') self.__sell_settle_date = value @property def new_ideas_ytd(self) -> float: """Ideas received by clients Year to date.""" return self.__new_ideas_ytd @new_ideas_ytd.setter def new_ideas_ytd(self, value: float): self._property_changed('new_ideas_ytd') self.__new_ideas_ytd = value @property def management_fee(self) -> Union[Op, float]: return self.__management_fee @management_fee.setter def management_fee(self, value: Union[Op, float]): self._property_changed('management_fee') self.__management_fee = value @property def par_asset_swap_spread3m(self) -> float: """Par asset swap spread vs 3m tenor.""" return self.__par_asset_swap_spread3m @par_asset_swap_spread3m.setter def par_asset_swap_spread3m(self, value: float): self._property_changed('par_asset_swap_spread3m') self.__par_asset_swap_spread3m = value @property def sell36bps(self) -> float: """The amount GS would sell for 36 bps charge.""" return self.__sell36bps @sell36bps.setter def sell36bps(self, value: float): self._property_changed('sell36bps') self.__sell36bps = value @property def matched_maturity_swap_spread3m(self) -> float: """Matched maturity swap spread vs 3m tenor.""" return self.__matched_maturity_swap_spread3m @matched_maturity_swap_spread3m.setter def matched_maturity_swap_spread3m(self, value: float): self._property_changed('matched_maturity_swap_spread3m') self.__matched_maturity_swap_spread3m = value @property def source_id(self) -> str: """Unique id of data provider.""" return self.__source_id @source_id.setter def source_id(self, value: str): self._property_changed('source_id') self.__source_id = value @property def country(self) -> str: """Country of incorporation of asset.""" return self.__country @country.setter def country(self, value: str): self._property_changed('country') self.__country = value @property def vwap(self) -> float: """VWAP benchmark price.""" return self.__vwap @vwap.setter def vwap(self, value: float): self._property_changed('vwap') self.__vwap = value @property def touch_spread_score(self) -> float: """Z-score of the difference between highest bid and lowest offer.""" return self.__touch_spread_score @touch_spread_score.setter def touch_spread_score(self, value: float): self._property_changed('touch_spread_score') self.__touch_spread_score = value @property def rating_second_highest(self) -> str: """Second highest bond rating between Moody's, Fitch, and Standard and Poor's.""" return self.__rating_second_highest @rating_second_highest.setter def rating_second_highest(self, value: str): self._property_changed('rating_second_highest') self.__rating_second_highest = value @property def sell24bps(self) -> float: """The amount GS would sell for 24 bps charge.""" return self.__sell24bps @sell24bps.setter def sell24bps(self, value: float): self._property_changed('sell24bps') self.__sell24bps = value @property def frequency(self) -> str: """Requested frequency of data delivery.""" return self.__frequency @frequency.setter def frequency(self, value: str): self._property_changed('frequency') self.__frequency = value @property def activity_id(self) -> str: """Marquee unique Activity identifier.""" return self.__activity_id @activity_id.setter def activity_id(self, value: str): self._property_changed('activity_id') self.__activity_id = value @property def estimated_impact(self) -> float: """Likely impact of a proposed trade on the price of an asset (bps). The model's shortfall estimates reflect how much it cost to execute similar trades in the past, as opposed to providing a hypothetical cost derived using tick data.""" return self.__estimated_impact @estimated_impact.setter def estimated_impact(self, value: float): self._property_changed('estimated_impact') self.__estimated_impact = value @property def sell35cents(self) -> float: """The amount GS would sell for 35 cents charge.""" return self.__sell35cents @sell35cents.setter def sell35cents(self, value: float): self._property_changed('sell35cents') self.__sell35cents = value @property def loan_spread_bucket(self) -> str: """The difference between the investment rate on cash collateral and the rebate rate of a loan.""" return self.__loan_spread_bucket @loan_spread_bucket.setter def loan_spread_bucket(self, value: str): self._property_changed('loan_spread_bucket') self.__loan_spread_bucket = value @property def coronavirus_global_activity_tracker(self) -> float: """Value for the global activity tracker.""" return self.__coronavirus_global_activity_tracker @coronavirus_global_activity_tracker.setter def coronavirus_global_activity_tracker(self, value: float): self._property_changed('coronavirus_global_activity_tracker') self.__coronavirus_global_activity_tracker = value @property def underlyers(self) -> str: """Top underlyers. Supported values are 'all' or 'top_10', 'top_15', etc..""" return self.__underlyers @underlyers.setter def underlyers(self, value: str): self._property_changed('underlyers') self.__underlyers = value @property def asset_parameters_pricing_location(self) -> str: """The location in which the asset was priced.""" return self.__asset_parameters_pricing_location @asset_parameters_pricing_location.setter def asset_parameters_pricing_location(self, value: str): self._property_changed('asset_parameters_pricing_location') self.__asset_parameters_pricing_location = value @property def event_description(self) -> str: """Short description of the event, providing additional information beyond eventType.""" return self.__event_description @event_description.setter def event_description(self, value: str): self._property_changed('event_description') self.__event_description = value @property def iceberg_max_size(self) -> float: """Iceberg max size in terms of the quantity unit.""" return self.__iceberg_max_size @iceberg_max_size.setter def iceberg_max_size(self, value: float): self._property_changed('iceberg_max_size') self.__iceberg_max_size = value @property def asset_parameters_coupon(self) -> float: """The fixed coupon for this bond.""" return self.__asset_parameters_coupon @asset_parameters_coupon.setter def asset_parameters_coupon(self, value: float): self._property_changed('asset_parameters_coupon') self.__asset_parameters_coupon = value @property def details(self) -> str: """Corporate action details.""" return self.__details @details.setter def details(self, value: str): self._property_changed('details') self.__details = value @property def sector(self) -> str: """The risk model sector of the stock.""" return self.__sector @sector.setter def sector(self, value: str): self._property_changed('sector') self.__sector = value @property def avg_bed_util_rate(self) -> float: """Average rate of bed utilization, computed as Total Patient Days (excluding nursery days) / Bed Days Available.""" return self.__avg_bed_util_rate @avg_bed_util_rate.setter def avg_bed_util_rate(self, value: float): self._property_changed('avg_bed_util_rate') self.__avg_bed_util_rate = value @property def buy20bps(self) -> float: """The amount GS would buy for 20 bps charge.""" return self.__buy20bps @buy20bps.setter def buy20bps(self, value: float): self._property_changed('buy20bps') self.__buy20bps = value @property def epidemic(self) -> float: """Total number of people affected by an epidemic.""" return self.__epidemic @epidemic.setter def epidemic(self, value: float): self._property_changed('epidemic') self.__epidemic = value @property def mctr(self) -> float: """Marginal contribution of a given asset to portfolio variance, is dependent on covariance matrix.""" return self.__mctr @mctr.setter def mctr(self, value: float): self._property_changed('mctr') self.__mctr = value @property def exchange_time(self) -> datetime.datetime: """Local time at the executing venue.""" return self.__exchange_time @exchange_time.setter def exchange_time(self, value: datetime.datetime): self._property_changed('exchange_time') self.__exchange_time = value @property def historical_close(self) -> float: """Historical Close Price.""" return self.__historical_close @historical_close.setter def historical_close(self, value: float): self._property_changed('historical_close') self.__historical_close = value @property def fips_code(self) -> float: """County FIPS code.""" return self.__fips_code @fips_code.setter def fips_code(self, value: float): self._property_changed('fips_code') self.__fips_code = value @property def buy32bps(self) -> float: """The amount GS would buy for 32 bps charge.""" return self.__buy32bps @buy32bps.setter def buy32bps(self, value: float): self._property_changed('buy32bps') self.__buy32bps = value @property def idea_id(self) -> str: """Marquee unique trade idea identifier.""" return self.__idea_id @idea_id.setter def idea_id(self, value: str): self._property_changed('idea_id') self.__idea_id = value @property def comment_status(self) -> str: """Corporate action comment status.""" return self.__comment_status @comment_status.setter def comment_status(self, value: str): self._property_changed('comment_status') self.__comment_status = value @property def marginal_cost(self) -> float: """Marginal cost.""" return self.__marginal_cost @marginal_cost.setter def marginal_cost(self, value: float): self._property_changed('marginal_cost') self.__marginal_cost = value @property def client_weight(self) -> float: """Weight of client positions in the region or sector (%).""" return self.__client_weight @client_weight.setter def client_weight(self, value: float): self._property_changed('client_weight') self.__client_weight = value @property def leg1_delivery_point(self) -> str: """Delivery point of leg.""" return self.__leg1_delivery_point @leg1_delivery_point.setter def leg1_delivery_point(self, value: str): self._property_changed('leg1_delivery_point') self.__leg1_delivery_point = value @property def sell5cents(self) -> float: """The amount GS would sell for 5 cents charge.""" return self.__sell5cents @sell5cents.setter def sell5cents(self, value: float): self._property_changed('sell5cents') self.__sell5cents = value @property def liq_wkly(self) -> float: """Percent of assets that could be quickly and easily converted into investable cash without loss of value within a week.""" return self.__liq_wkly @liq_wkly.setter def liq_wkly(self, value: float): self._property_changed('liq_wkly') self.__liq_wkly = value @property def unrealized_twap_performance_bps(self) -> float: """Average execution price vs Consolidated TWAP since order inception ??? unrealized.""" return self.__unrealized_twap_performance_bps @unrealized_twap_performance_bps.setter def unrealized_twap_performance_bps(self, value: float): self._property_changed('unrealized_twap_performance_bps') self.__unrealized_twap_performance_bps = value @property def region(self) -> str: """Regional classification for the asset""" return self.__region @region.setter def region(self, value: str): self._property_changed('region') self.__region = value @property def temperature_hour(self) -> float: """The forecast value of hour with min/max temperature in a day.""" return self.__temperature_hour @temperature_hour.setter def temperature_hour(self, value: float): self._property_changed('temperature_hour') self.__temperature_hour = value @property def upper_bound(self) -> float: """Upper bound value.""" return self.__upper_bound @upper_bound.setter def upper_bound(self, value: float): self._property_changed('upper_bound') self.__upper_bound = value @property def sell55cents(self) -> float: """The amount GS would sell for 55 cents charge.""" return self.__sell55cents @sell55cents.setter def sell55cents(self, value: float): self._property_changed('sell55cents') self.__sell55cents = value @property def num_pedi_icu_beds(self) -> float: """All neonatal, pediatric and premature ICU beds.""" return self.__num_pedi_icu_beds @num_pedi_icu_beds.setter def num_pedi_icu_beds(self, value: float): self._property_changed('num_pedi_icu_beds') self.__num_pedi_icu_beds = value @property def bid_yield(self) -> float: """The return an investor realizes on a bond sold at the bid price.""" return self.__bid_yield @bid_yield.setter def bid_yield(self, value: float): self._property_changed('bid_yield') self.__bid_yield = value @property def expected_residual(self) -> float: """Expected residual quantity.""" return self.__expected_residual @expected_residual.setter def expected_residual(self, value: float): self._property_changed('expected_residual') self.__expected_residual = value @property def option_premium(self) -> float: """An indication of the market value of the option at the time of execution.""" return self.__option_premium @option_premium.setter def option_premium(self, value: float): self._property_changed('option_premium') self.__option_premium = value @property def owner_name(self) -> str: """Name of person submitting request.""" return self.__owner_name @owner_name.setter def owner_name(self, value: str): self._property_changed('owner_name') self.__owner_name = value @property def par_asset_swap_spread6m(self) -> float: """Par asset swap spread vs 6m tenor.""" return self.__par_asset_swap_spread6m @par_asset_swap_spread6m.setter def par_asset_swap_spread6m(self, value: float): self._property_changed('par_asset_swap_spread6m') self.__par_asset_swap_spread6m = value @property def z_score(self) -> float: """Z Score.""" return self.__z_score @z_score.setter def z_score(self, value: float): self._property_changed('z_score') self.__z_score = value @property def sell12bps(self) -> float: """The amount GS would sell for 12 bps charge.""" return self.__sell12bps @sell12bps.setter def sell12bps(self, value: float): self._property_changed('sell12bps') self.__sell12bps = value @property def event_start_time(self) -> str: """The start time of the event if the event occurs during a time window and the event has a specific start time. It is represented in HH:MM 24 hour format in the time zone of the exchange where the company is listed.""" return self.__event_start_time @event_start_time.setter def event_start_time(self, value: str): self._property_changed('event_start_time') self.__event_start_time = value @property def matched_maturity_swap_spread6m(self) -> float: """Matched maturity swap spread vs 6m tenor.""" return self.__matched_maturity_swap_spread6m @matched_maturity_swap_spread6m.setter def matched_maturity_swap_spread6m(self, value: float): self._property_changed('matched_maturity_swap_spread6m') self.__matched_maturity_swap_spread6m = value @property def turnover(self) -> float: """Turnover.""" return self.__turnover @turnover.setter def turnover(self, value: float): self._property_changed('turnover') self.__turnover = value @property def price_spot_target_unit(self) -> str: """Unit in which the target price is reported.""" return self.__price_spot_target_unit @price_spot_target_unit.setter def price_spot_target_unit(self, value: str): self._property_changed('price_spot_target_unit') self.__price_spot_target_unit = value @property def coverage(self) -> str: """Coverage of dataset.""" return self.__coverage @coverage.setter def coverage(self, value: str): self._property_changed('coverage') self.__coverage = value @property def g_percentile(self) -> float: """A percentile that captures a company's G ranking relative to the entire ESG universe.""" return self.__g_percentile @g_percentile.setter def g_percentile(self, value: float): self._property_changed('g_percentile') self.__g_percentile = value @property def cloud_cover_hourly_forecast(self) -> float: """The hourly forecast value for cloud cover.""" return self.__cloud_cover_hourly_forecast @cloud_cover_hourly_forecast.setter def cloud_cover_hourly_forecast(self, value: float): self._property_changed('cloud_cover_hourly_forecast') self.__cloud_cover_hourly_forecast = value @property def lending_fund_nav(self) -> float: """Net Asset Value of a securities lending fund.""" return self.__lending_fund_nav @lending_fund_nav.setter def lending_fund_nav(self, value: float): self._property_changed('lending_fund_nav') self.__lending_fund_nav = value @property def source_original_category(self) -> str: """Source category's original name.""" return self.__source_original_category @source_original_category.setter def source_original_category(self, value: str): self._property_changed('source_original_category') self.__source_original_category = value @property def percent_close_execution_quantity(self) -> float: """Percentage of order filled at auction close.""" return self.__percent_close_execution_quantity @percent_close_execution_quantity.setter def percent_close_execution_quantity(self, value: float): self._property_changed('percent_close_execution_quantity') self.__percent_close_execution_quantity = value @property def latest_execution_time(self) -> datetime.datetime: """ISO 8601-formatted timestamp""" return self.__latest_execution_time @latest_execution_time.setter def latest_execution_time(self, value: datetime.datetime): self._property_changed('latest_execution_time') self.__latest_execution_time = value @property def arrival_mid_realized_bps(self) -> float: """Consolidated realised performance vs Arrival Mid, in bps.""" return self.__arrival_mid_realized_bps @arrival_mid_realized_bps.setter def arrival_mid_realized_bps(self, value: float): self._property_changed('arrival_mid_realized_bps') self.__arrival_mid_realized_bps = value @property def location(self) -> str: """The location at which a price fixing has been taken.""" return self.__location @location.setter def location(self, value: str): self._property_changed('location') self.__location = value @property def scenario_id(self) -> str: """Marquee unique scenario identifier""" return self.__scenario_id @scenario_id.setter def scenario_id(self, value: str): self._property_changed('scenario_id') self.__scenario_id = value @property def termination_tenor(self) -> str: """Tenor""" return self.__termination_tenor @termination_tenor.setter def termination_tenor(self, value: str): self._property_changed('termination_tenor') self.__termination_tenor = value @property def queue_clock_time(self) -> float: """The Queue Clock Time of the stock on the particular date.""" return self.__queue_clock_time @queue_clock_time.setter def queue_clock_time(self, value: float): self._property_changed('queue_clock_time') self.__queue_clock_time = value @property def discretion_lower_bound(self) -> float: """The lower bound of the discretion band as published from the algo.""" return self.__discretion_lower_bound @discretion_lower_bound.setter def discretion_lower_bound(self, value: float): self._property_changed('discretion_lower_bound') self.__discretion_lower_bound = value @property def tcm_cost_participation_rate50_pct(self) -> float: """TCM cost with a 50 percent participation rate.""" return self.__tcm_cost_participation_rate50_pct @tcm_cost_participation_rate50_pct.setter def tcm_cost_participation_rate50_pct(self, value: float): self._property_changed('tcm_cost_participation_rate50_pct') self.__tcm_cost_participation_rate50_pct = value @property def rating_linear(self) -> float: """Rating of the bond in linear form.""" return self.__rating_linear @rating_linear.setter def rating_linear(self, value: float): self._property_changed('rating_linear') self.__rating_linear = value @property def previous_close_unrealized_bps(self) -> float: """Unrealised performance vs Previous Close, in bps.""" return self.__previous_close_unrealized_bps @previous_close_unrealized_bps.setter def previous_close_unrealized_bps(self, value: float): self._property_changed('previous_close_unrealized_bps') self.__previous_close_unrealized_bps = value @property def sub_asset_class_for_other_commodity(self) -> str: """An indication of the sub asset class.""" return self.__sub_asset_class_for_other_commodity @sub_asset_class_for_other_commodity.setter def sub_asset_class_for_other_commodity(self, value: str): self._property_changed('sub_asset_class_for_other_commodity') self.__sub_asset_class_for_other_commodity = value @property def forward_price(self) -> float: """Trader's estimate for the price of power in MWh.""" return self.__forward_price @forward_price.setter def forward_price(self, value: float): self._property_changed('forward_price') self.__forward_price = value @property def type(self) -> str: """Asset type differentiates the product categorization or contract type""" return self.__type @type.setter def type(self, value: str): self._property_changed('type') self.__type = value @property def strike_ref(self) -> str: """Reference for strike level (enum: spot, forward,delta_call, delta_put, delta_neutral).""" return self.__strike_ref @strike_ref.setter def strike_ref(self, value: str): self._property_changed('strike_ref') self.__strike_ref = value @property def cumulative_pnl(self) -> float: """Cumulative PnL from the start date to the current date.""" return self.__cumulative_pnl @cumulative_pnl.setter def cumulative_pnl(self, value: float): self._property_changed('cumulative_pnl') self.__cumulative_pnl = value @property def short_tenor(self) -> str: """Tenor of instrument.""" return self.__short_tenor @short_tenor.setter def short_tenor(self, value: str): self._property_changed('short_tenor') self.__short_tenor = value @property def sell28bps(self) -> float: """The amount GS would sell for 28 bps charge.""" return self.__sell28bps @sell28bps.setter def sell28bps(self, value: float): self._property_changed('sell28bps') self.__sell28bps = value @property def fund_class(self) -> str: """Class of the fund.""" return self.__fund_class @fund_class.setter def fund_class(self, value: str): self._property_changed('fund_class') self.__fund_class = value @property def unadjusted_volume(self) -> float: """Unadjusted volume traded.""" return self.__unadjusted_volume @unadjusted_volume.setter def unadjusted_volume(self, value: float): self._property_changed('unadjusted_volume') self.__unadjusted_volume = value @property def buy36bps(self) -> float: """The amount GS would buy for 36 bps charge.""" return self.__buy36bps @buy36bps.setter def buy36bps(self, value: float): self._property_changed('buy36bps') self.__buy36bps = value @property def position_idx(self) -> int: """The index of the corresponding position in the risk request.""" return self.__position_idx @position_idx.setter def position_idx(self, value: int): self._property_changed('position_idx') self.__position_idx = value @property def wind_chill_hourly_forecast(self) -> float: """The hourly forecast value for wind chill.""" return self.__wind_chill_hourly_forecast @wind_chill_hourly_forecast.setter def wind_chill_hourly_forecast(self, value: float): self._property_changed('wind_chill_hourly_forecast') self.__wind_chill_hourly_forecast = value @property def sec_name(self) -> str: """Internal Goldman Sachs security name.""" return self.__sec_name @sec_name.setter def sec_name(self, value: str): self._property_changed('sec_name') self.__sec_name = value @property def implied_volatility_by_relative_strike(self) -> float: """Volatility of an asset implied by observations of market prices.""" return self.__implied_volatility_by_relative_strike @implied_volatility_by_relative_strike.setter def implied_volatility_by_relative_strike(self, value: float): self._property_changed('implied_volatility_by_relative_strike') self.__implied_volatility_by_relative_strike = value @property def percent_adv(self) -> float: """Size of trade as percentage of average daily volume (e.g. .05, 1, 2, ..., 20).""" return self.__percent_adv @percent_adv.setter def percent_adv(self, value: float): self._property_changed('percent_adv') self.__percent_adv = value @property def leg1_total_notional(self) -> float: """The total Notional amount or quantity of units of the leg 1 underlying asset.""" return self.__leg1_total_notional @leg1_total_notional.setter def leg1_total_notional(self, value: float): self._property_changed('leg1_total_notional') self.__leg1_total_notional = value @property def contract(self) -> str: """Contract month code and year (e.g. F18).""" return self.__contract @contract.setter def contract(self, value: str): self._property_changed('contract') self.__contract = value @property def payment_frequency1(self) -> str: """An integer multiplier of a time period describing how often the parties to the SB swap transaction exchange payments associated with each party???s obligation. Such payment frequency may be described as one letter preceded by an integer.""" return self.__payment_frequency1 @payment_frequency1.setter def payment_frequency1(self, value: str): self._property_changed('payment_frequency1') self.__payment_frequency1 = value @property def payment_frequency2(self) -> str: """Same as Payment Frequency 1.""" return self.__payment_frequency2 @payment_frequency2.setter def payment_frequency2(self, value: str): self._property_changed('payment_frequency2') self.__payment_frequency2 = value @property def bespoke(self) -> str: """Indication if the trade is bespoke.""" return self.__bespoke @bespoke.setter def bespoke(self, value: str): self._property_changed('bespoke') self.__bespoke = value @property def repo_tenor(self) -> str: """Maturity of repurchase agreement.""" return self.__repo_tenor @repo_tenor.setter def repo_tenor(self, value: str): self._property_changed('repo_tenor') self.__repo_tenor = value @property def sell15cents(self) -> float: """The amount GS would sell for 15 cents charge.""" return self.__sell15cents @sell15cents.setter def sell15cents(self, value: float): self._property_changed('sell15cents') self.__sell15cents = value @property def investment_qtd(self) -> float: """Total net investment Quarter to date.""" return self.__investment_qtd @investment_qtd.setter def investment_qtd(self, value: float): self._property_changed('investment_qtd') self.__investment_qtd = value @property def heat_index_forecast(self) -> float: """The heat index forecast in a given unit.""" return self.__heat_index_forecast @heat_index_forecast.setter def heat_index_forecast(self, value: float): self._property_changed('heat_index_forecast') self.__heat_index_forecast = value @property def rating_standard_and_poors(self) -> str: """Bond rating from Standard And Poor's.""" return self.__rating_standard_and_poors @rating_standard_and_poors.setter def rating_standard_and_poors(self, value: str): self._property_changed('rating_standard_and_poors') self.__rating_standard_and_poors = value @property def quality_stars(self) -> float: """Confidence in the BPE.""" return self.__quality_stars @quality_stars.setter def quality_stars(self, value: float): self._property_changed('quality_stars') self.__quality_stars = value @property def leg2_floating_index(self) -> str: """If floating index leg, the index.""" return self.__leg2_floating_index @leg2_floating_index.setter def leg2_floating_index(self, value: str): self._property_changed('leg2_floating_index') self.__leg2_floating_index = value @property def source_ticker(self) -> str: """Source ticker.""" return self.__source_ticker @source_ticker.setter def source_ticker(self, value: str): self._property_changed('source_ticker') self.__source_ticker = value @property def primary_vwap_unrealized_bps(self) -> float: """Unrealised performance vs Primary VWAP, in bps.""" return self.__primary_vwap_unrealized_bps @primary_vwap_unrealized_bps.setter def primary_vwap_unrealized_bps(self, value: float): self._property_changed('primary_vwap_unrealized_bps') self.__primary_vwap_unrealized_bps = value @property def gsid(self) -> str: """Goldman Sachs internal equity identifier.""" return self.__gsid @gsid.setter def gsid(self, value: str): self._property_changed('gsid') self.__gsid = value @property def lending_fund(self) -> str: """Name of the lending fund on a securities lending agreement.""" return self.__lending_fund @lending_fund.setter def lending_fund(self, value: str): self._property_changed('lending_fund') self.__lending_fund = value @property def sensitivity(self) -> float: """Sensitivity.""" return self.__sensitivity @sensitivity.setter def sensitivity(self, value: float): self._property_changed('sensitivity') self.__sensitivity = value @property def day_count(self) -> str: """The determination of how interest accrues over time for the SB swap.""" return self.__day_count @day_count.setter def day_count(self, value: str): self._property_changed('day_count') self.__day_count = value @property def sell16bps(self) -> float: """The amount GS would sell for 16 bps charge.""" return self.__sell16bps @sell16bps.setter def sell16bps(self, value: float): self._property_changed('sell16bps') self.__sell16bps = value @property def relative_break_even_inflation_change(self) -> float: """Relative break even inflation change.""" return self.__relative_break_even_inflation_change @relative_break_even_inflation_change.setter def relative_break_even_inflation_change(self, value: float): self._property_changed('relative_break_even_inflation_change') self.__relative_break_even_inflation_change = value @property def sell25cents(self) -> float: """The amount GS would sell for 25 cents charge.""" return self.__sell25cents @sell25cents.setter def sell25cents(self, value: float): self._property_changed('sell25cents') self.__sell25cents = value @property def var_swap(self) -> float: """Strike such that the price of an uncapped variance swap on the underlying index is zero at inception.""" return self.__var_swap @var_swap.setter def var_swap(self, value: float): self._property_changed('var_swap') self.__var_swap = value @property def buy5point5bps(self) -> float: """The amount GS would buy for 5.5 bps charge.""" return self.__buy5point5bps @buy5point5bps.setter def buy5point5bps(self, value: float): self._property_changed('buy5point5bps') self.__buy5point5bps = value @property def block_large_notional(self) -> str: """An indication of whether this is a block trade or off-facility swap.""" return self.__block_large_notional @block_large_notional.setter def block_large_notional(self, value: str): self._property_changed('block_large_notional') self.__block_large_notional = value @property def sell2point5bps(self) -> float: """The amount GS would sell for 2.5 bps charge.""" return self.__sell2point5bps @sell2point5bps.setter def sell2point5bps(self, value: float): self._property_changed('sell2point5bps') self.__sell2point5bps = value @property def capacity(self) -> str: """Risk/agency execution.""" return self.__capacity @capacity.setter def capacity(self, value: str): self._property_changed('capacity') self.__capacity = value @property def sectors_raw(self) -> Tuple[str, ...]: """Sector classifications of an asset.""" return self.__sectors_raw @sectors_raw.setter def sectors_raw(self, value: Tuple[str, ...]): self._property_changed('sectors_raw') self.__sectors_raw = value @property def primary_vwap_in_limit(self) -> float: """Primary VWAP In Limit benchmark price.""" return self.__primary_vwap_in_limit @primary_vwap_in_limit.setter def primary_vwap_in_limit(self, value: float): self._property_changed('primary_vwap_in_limit') self.__primary_vwap_in_limit = value @property def shareclass_price(self) -> float: """Price of the shareclass on a certain day.""" return self.__shareclass_price @shareclass_price.setter def shareclass_price(self, value: float): self._property_changed('shareclass_price') self.__shareclass_price = value @property def trade_size(self) -> float: """Size of trade ($mm).""" return self.__trade_size @trade_size.setter def trade_size(self, value: float): self._property_changed('trade_size') self.__trade_size = value @property def price_spot_entry_value(self) -> float: """Opening price value of the trade idea.""" return self.__price_spot_entry_value @price_spot_entry_value.setter def price_spot_entry_value(self, value: float): self._property_changed('price_spot_entry_value') self.__price_spot_entry_value = value @property def buy8point5bps(self) -> float: """The amount GS would buy for 8.5 bps charge.""" return self.__buy8point5bps @buy8point5bps.setter def buy8point5bps(self, value: float): self._property_changed('buy8point5bps') self.__buy8point5bps = value @property def symbol_dimensions(self) -> Tuple[str, ...]: """Set of fields that determine database table name.""" return self.__symbol_dimensions @symbol_dimensions.setter def symbol_dimensions(self, value: Tuple[str, ...]): self._property_changed('symbol_dimensions') self.__symbol_dimensions = value @property def buy24bps(self) -> float: """The amount GS would buy for 24 bps charge.""" return self.__buy24bps @buy24bps.setter def buy24bps(self, value: float): self._property_changed('buy24bps') self.__buy24bps = value @property def observation(self) -> str: """Value of observation.""" return self.__observation @observation.setter def observation(self, value: str): self._property_changed('observation') self.__observation = value @property def option_type_sdr(self) -> str: """An indication of the type of the option.""" return self.__option_type_sdr @option_type_sdr.setter def option_type_sdr(self, value: str): self._property_changed('option_type_sdr') self.__option_type_sdr = value @property def scenario_group_id(self) -> str: """Marquee unique scenario group identifier""" return self.__scenario_group_id @scenario_group_id.setter def scenario_group_id(self, value: str): self._property_changed('scenario_group_id') self.__scenario_group_id = value @property def average_implied_variance(self) -> float: """Average variance of an asset implied by observations of market prices.""" return self.__average_implied_variance @average_implied_variance.setter def average_implied_variance(self, value: float): self._property_changed('average_implied_variance') self.__average_implied_variance = value @property def avg_trade_rate_description(self) -> str: """Description of the Stock's Average Trading Rate on the particular date.""" return self.__avg_trade_rate_description @avg_trade_rate_description.setter def avg_trade_rate_description(self, value: str): self._property_changed('avg_trade_rate_description') self.__avg_trade_rate_description = value @property def fraction(self) -> float: """Fraction.""" return self.__fraction @fraction.setter def fraction(self, value: float): self._property_changed('fraction') self.__fraction = value @property def asset_count_short(self) -> float: """Number of assets in a portfolio with short exposure.""" return self.__asset_count_short @asset_count_short.setter def asset_count_short(self, value: float): self._property_changed('asset_count_short') self.__asset_count_short = value @property def collateral_percentage_required(self) -> float: """Collateral percentage requied to cover the given position.""" return self.__collateral_percentage_required @collateral_percentage_required.setter def collateral_percentage_required(self, value: float): self._property_changed('collateral_percentage_required') self.__collateral_percentage_required = value @property def sell5point5bps(self) -> float: """The amount GS would sell for 5.5 bps charge.""" return self.__sell5point5bps @sell5point5bps.setter def sell5point5bps(self, value: float): self._property_changed('sell5point5bps') self.__sell5point5bps = value @property def date(self) -> datetime.date: """ISO 8601 formatted date.""" return self.__date @date.setter def date(self, value: datetime.date): self._property_changed('date') self.__date = value @property def zip_code(self) -> float: """Postal code.""" return self.__zip_code @zip_code.setter def zip_code(self, value: float): self._property_changed('zip_code') self.__zip_code = value @property def total_std_return_since_inception(self) -> float: """Average annual total returns as of most recent calendar quarter-end.""" return self.__total_std_return_since_inception @total_std_return_since_inception.setter def total_std_return_since_inception(self, value: float): self._property_changed('total_std_return_since_inception') self.__total_std_return_since_inception = value @property def source_category(self) -> str: """Source category of event.""" return self.__source_category @source_category.setter def source_category(self, value: str): self._property_changed('source_category') self.__source_category = value @property def volume_unadjusted(self) -> float: """Unadjusted volume traded.""" return self.__volume_unadjusted @volume_unadjusted.setter def volume_unadjusted(self, value: float): self._property_changed('volume_unadjusted') self.__volume_unadjusted = value @property def passive_ratio(self) -> float: """Ratio of passive ownership (including ETFs and mutual funds), i.e the proportion of a company's marketcap that is held by passive ETFs and mutual funds. Normalised between -3 (low level) and +3 (high level).""" return self.__passive_ratio @passive_ratio.setter def passive_ratio(self, value: float): self._property_changed('passive_ratio') self.__passive_ratio = value @property def price_to_earnings(self) -> float: """Price to earnings.""" return self.__price_to_earnings @price_to_earnings.setter def price_to_earnings(self, value: float): self._property_changed('price_to_earnings') self.__price_to_earnings = value @property def order_depth(self) -> float: """Order book depth level of the corresponding ask or bid.""" return self.__order_depth @order_depth.setter def order_depth(self, value: float): self._property_changed('order_depth') self.__order_depth = value @property def ann_yield3_month(self) -> float: """Calculates the total return for 3 months, representing past performance.""" return self.__ann_yield3_month @ann_yield3_month.setter def ann_yield3_month(self, value: float): self._property_changed('ann_yield3_month') self.__ann_yield3_month = value @property def net_flow_std(self) -> float: """Net flow for the asset in standard deviations.""" return self.__net_flow_std @net_flow_std.setter def net_flow_std(self, value: float): self._property_changed('net_flow_std') self.__net_flow_std = value @property def encoded_stats(self) -> str: """Asset stats object in json format.""" return self.__encoded_stats @encoded_stats.setter def encoded_stats(self, value: str): self._property_changed('encoded_stats') self.__encoded_stats = value @property def buy5bps(self) -> float: """The amount GS would buy for 5 bps charge.""" return self.__buy5bps @buy5bps.setter def buy5bps(self, value: float): self._property_changed('buy5bps') self.__buy5bps = value @property def run_time(self) -> float: """Time that a run of the QA checker took.""" return self.__run_time @run_time.setter def run_time(self, value: float): self._property_changed('run_time') self.__run_time = value @property def ask_size(self) -> float: """The number of shares, lots, or contracts willing to sell at the Ask price.""" return self.__ask_size @ask_size.setter def ask_size(self, value: float): self._property_changed('ask_size') self.__ask_size = value @property def absolute_return_mtd(self) -> float: """Absolute Return Month to Date.""" return self.__absolute_return_mtd @absolute_return_mtd.setter def absolute_return_mtd(self, value: float): self._property_changed('absolute_return_mtd') self.__absolute_return_mtd = value @property def std30_days_unsubsidized_yield(self) -> float: """Average annual total returns as of most recent calendar quarter-end.""" return self.__std30_days_unsubsidized_yield @std30_days_unsubsidized_yield.setter def std30_days_unsubsidized_yield(self, value: float): self._property_changed('std30_days_unsubsidized_yield') self.__std30_days_unsubsidized_yield = value @property def resource(self) -> str: """The event resource. For example: Asset""" return self.__resource @resource.setter def resource(self, value: str): self._property_changed('resource') self.__resource = value @property def average_realized_volatility(self) -> float: """Average volatility of an asset realized by observations of market prices.""" return self.__average_realized_volatility @average_realized_volatility.setter def average_realized_volatility(self, value: float): self._property_changed('average_realized_volatility') self.__average_realized_volatility = value @property def trace_adv_buy(self) -> float: """TRACE ADV for the buy side.""" return self.__trace_adv_buy @trace_adv_buy.setter def trace_adv_buy(self, value: float): self._property_changed('trace_adv_buy') self.__trace_adv_buy = value @property def new_confirmed(self) -> float: """New cofirmed cases.""" return self.__new_confirmed @new_confirmed.setter def new_confirmed(self, value: float): self._property_changed('new_confirmed') self.__new_confirmed = value @property def sell8bps(self) -> float: """The amount GS would sell for 8 bps charge.""" return self.__sell8bps @sell8bps.setter def sell8bps(self, value: float): self._property_changed('sell8bps') self.__sell8bps = value @property def bid_price(self) -> float: """Latest Bid Price (price willing to buy).""" return self.__bid_price @bid_price.setter def bid_price(self, value: float): self._property_changed('bid_price') self.__bid_price = value @property def sell8point5bps(self) -> float: """The amount GS would sell for 8.5 bps charge.""" return self.__sell8point5bps @sell8point5bps.setter def sell8point5bps(self, value: float): self._property_changed('sell8point5bps') self.__sell8point5bps = value @property def target_price_unrealized_bps(self) -> float: """Unrealised performance vs Target Price, in bps.""" return self.__target_price_unrealized_bps @target_price_unrealized_bps.setter def target_price_unrealized_bps(self, value: float): self._property_changed('target_price_unrealized_bps') self.__target_price_unrealized_bps = value @property def es_numeric_percentile(self) -> float: """A percentile that captures a company's E&S numeric ranking within its subsector.""" return self.__es_numeric_percentile @es_numeric_percentile.setter def es_numeric_percentile(self, value: float): self._property_changed('es_numeric_percentile') self.__es_numeric_percentile = value @property def leg2_underlying_asset(self) -> str: """The asset, reference asset, or reference obligation for payments of a party???s obligations under the SB swap transaction reference.""" return self.__leg2_underlying_asset @leg2_underlying_asset.setter def leg2_underlying_asset(self, value: str): self._property_changed('leg2_underlying_asset') self.__leg2_underlying_asset = value @property def csa_terms(self) -> str: """Identifier of terms or rules under which collateral is posted or transferred between swap counterparties (e.g. ccy-1 means LCH clearing with collateral in currency ccy).""" return self.__csa_terms @csa_terms.setter def csa_terms(self, value: str): self._property_changed('csa_terms') self.__csa_terms = value @property def relative_payoff_mtd(self) -> float: """Total win divided by total loss Month to date.""" return self.__relative_payoff_mtd @relative_payoff_mtd.setter def relative_payoff_mtd(self, value: float): self._property_changed('relative_payoff_mtd') self.__relative_payoff_mtd = value @property def daily_net_shareholder_flows(self) -> float: """Cash dividends paid daily.""" return self.__daily_net_shareholder_flows @daily_net_shareholder_flows.setter def daily_net_shareholder_flows(self, value: float): self._property_changed('daily_net_shareholder_flows') self.__daily_net_shareholder_flows = value @property def buy2point5bps(self) -> float: """The amount GS would buy for 2.5 bps charge.""" return self.__buy2point5bps @buy2point5bps.setter def buy2point5bps(self, value: float): self._property_changed('buy2point5bps') self.__buy2point5bps = value @property def cai(self) -> float: """Current Activity Indicator (CAI) and Current Activity Indicator Innovations (CAII) for each of the world's large economies and many smaller ones, as well as aggregate CAIs and CAIIs for regions.""" return self.__cai @cai.setter def cai(self, value: float): self._property_changed('cai') self.__cai = value @property def executed_notional_usd(self) -> float: """Executed notional in USD.""" return self.__executed_notional_usd @executed_notional_usd.setter def executed_notional_usd(self, value: float): self._property_changed('executed_notional_usd') self.__executed_notional_usd = value @property def system_time(self) -> datetime.datetime: """Time at which an event took place in the system. ISO 8601 formatted string.""" return self.__system_time @system_time.setter def system_time(self, value: datetime.datetime): self._property_changed('system_time') self.__system_time = value @property def total_home_isolation(self) -> float: """Total number of cases in home isolation.""" return self.__total_home_isolation @total_home_isolation.setter def total_home_isolation(self, value: float): self._property_changed('total_home_isolation') self.__total_home_isolation = value @property def station_name(self) -> str: """The name of weather station where data is recorded/forecast.""" return self.__station_name @station_name.setter def station_name(self, value: str): self._property_changed('station_name') self.__station_name = value @property def pass_pct(self) -> float: """Pass percentage.""" return self.__pass_pct @pass_pct.setter def pass_pct(self, value: float): self._property_changed('pass_pct') self.__pass_pct = value @property def opening_report(self) -> str: """Report that was published when the trade idea was opened.""" return self.__opening_report @opening_report.setter def opening_report(self, value: str): self._property_changed('opening_report') self.__opening_report = value @property def midcurve_atm_fwd_rate(self) -> float: """Midcurve ATM forward rate.""" return self.__midcurve_atm_fwd_rate @midcurve_atm_fwd_rate.setter def midcurve_atm_fwd_rate(self, value: float): self._property_changed('midcurve_atm_fwd_rate') self.__midcurve_atm_fwd_rate = value @property def precipitation_forecast(self) -> float: """The forecast value for precipitation.""" return self.__precipitation_forecast @precipitation_forecast.setter def precipitation_forecast(self, value: float): self._property_changed('precipitation_forecast') self.__precipitation_forecast = value @property def equity_risk_premium_index(self) -> float: """Equity risk premium index: difference between cost of equity and 10y treasury yield.""" return self.__equity_risk_premium_index @equity_risk_premium_index.setter def equity_risk_premium_index(self, value: float): self._property_changed('equity_risk_premium_index') self.__equity_risk_premium_index = value @property def fatalities_underlying_conditions_unknown(self) -> float: """Total number of fatalities of people that it is unknown whether they had underlying conditions.""" return self.__fatalities_underlying_conditions_unknown @fatalities_underlying_conditions_unknown.setter def fatalities_underlying_conditions_unknown(self, value: float): self._property_changed('fatalities_underlying_conditions_unknown') self.__fatalities_underlying_conditions_unknown = value @property def buy12bps(self) -> float: """The amount GS would buy for 12 bps charge.""" return self.__buy12bps @buy12bps.setter def buy12bps(self, value: float): self._property_changed('buy12bps') self.__buy12bps = value @property def clearing_house(self) -> str: """Swap Clearing House""" return self.__clearing_house @clearing_house.setter def clearing_house(self, value: str): self._property_changed('clearing_house') self.__clearing_house = value @property def day_close_unrealized_bps(self) -> float: """Unrealized performance vs Close, in bps.""" return self.__day_close_unrealized_bps @day_close_unrealized_bps.setter def day_close_unrealized_bps(self, value: float): self._property_changed('day_close_unrealized_bps') self.__day_close_unrealized_bps = value @property def sts_rates_maturity(self) -> str: """Risk maturity bucket for STS assets.""" return self.__sts_rates_maturity @sts_rates_maturity.setter def sts_rates_maturity(self, value: str): self._property_changed('sts_rates_maturity') self.__sts_rates_maturity = value @property def liq_dly(self) -> float: """Percent of assets that could be quickly and easily converted into investable cash without loss of value within a day.""" return self.__liq_dly @liq_dly.setter def liq_dly(self, value: float): self._property_changed('liq_dly') self.__liq_dly = value @property def contributor_role(self) -> str: """Role (specialist / generalist..) of a contributor.""" return self.__contributor_role @contributor_role.setter def contributor_role(self, value: str): self._property_changed('contributor_role') self.__contributor_role = value @property def total_fatalities(self) -> float: """Total number of fatalities.""" return self.__total_fatalities @total_fatalities.setter def total_fatalities(self, value: float): self._property_changed('total_fatalities') self.__total_fatalities = value class IndexCurveShift(Scenario): """A scenario to manipulate index curve shape""" @camel_case_translate def __init__( self, market_data_pattern: MarketDataPattern = None, annualised_parallel_shift: float = None, annualised_slope_shift: float = None, cutoff: float = None, floor: float = None, tenor: str = None, rate_option: str = None, bucket_shift: float = None, bucket_start: datetime.date = None, bucket_end: datetime.date = None, name: str = None ): super().__init__() self.market_data_pattern = market_data_pattern self.annualised_parallel_shift = annualised_parallel_shift self.annualised_slope_shift = annualised_slope_shift self.cutoff = cutoff self.floor = floor self.tenor = tenor self.rate_option = rate_option self.bucket_shift = bucket_shift self.bucket_start = bucket_start self.bucket_end = bucket_end self.name = name @property def scenario_type(self) -> str: """IndexCurveShift""" return 'IndexCurveShift' @property def market_data_pattern(self) -> MarketDataPattern: """Market pattern for matching curve assets""" return self.__market_data_pattern @market_data_pattern.setter def market_data_pattern(self, value: MarketDataPattern): self._property_changed('market_data_pattern') self.__market_data_pattern = value @property def annualised_parallel_shift(self) -> float: """Size of the parallel shift (in bps/year)""" return self.__annualised_parallel_shift @annualised_parallel_shift.setter def annualised_parallel_shift(self, value: float): self._property_changed('annualised_parallel_shift') self.__annualised_parallel_shift = value @property def annualised_slope_shift(self) -> float: """Size of the slope shift (in bps/year)""" return self.__annualised_slope_shift @annualised_slope_shift.setter def annualised_slope_shift(self, value: float): self._property_changed('annualised_slope_shift') self.__annualised_slope_shift = value @property def cutoff(self) -> float: """The cutoff point (in years)""" return self.__cutoff @cutoff.setter def cutoff(self, value: float): self._property_changed('cutoff') self.__cutoff = value @property def floor(self) -> float: """The floor value (in bps)""" return self.__floor @floor.setter def floor(self, value: float): self._property_changed('floor') self.__floor = value @property def tenor(self) -> str: """Tenor of rate option to which shock is applied""" return self.__tenor @tenor.setter def tenor(self, value: str): self._property_changed('tenor') self.__tenor = value @property def rate_option(self) -> str: """Rate option to which shock is applied""" return self.__rate_option @rate_option.setter def rate_option(self, value: str): self._property_changed('rate_option') self.__rate_option = value @property def bucket_shift(self) -> float: """Size of the bucket shift (in bps)""" return self.__bucket_shift @bucket_shift.setter def bucket_shift(self, value: float): self._property_changed('bucket_shift') self.__bucket_shift = value @property def bucket_start(self) -> datetime.date: """The start date of the custom bucket""" return self.__bucket_start @bucket_start.setter def bucket_start(self, value: datetime.date): self._property_changed('bucket_start') self.__bucket_start = value @property def bucket_end(self) -> datetime.date: """The end date of the custom bucket""" return self.__bucket_end @bucket_end.setter def bucket_end(self, value: datetime.date): self._property_changed('bucket_end') self.__bucket_end = value class MarketDataPatternAndShock(Base): """A shock to apply to market coordinate values matching the supplied pattern""" @camel_case_translate def __init__( self, pattern: MarketDataPattern, shock: MarketDataShock, name: str = None ): super().__init__() self.pattern = pattern self.shock = shock self.name = name @property def pattern(self) -> MarketDataPattern: """A pattern used to match market coordinates""" return self.__pattern @pattern.setter def pattern(self, value: MarketDataPattern): self._property_changed('pattern') self.__pattern = value @property def shock(self) -> MarketDataShock: """A shock to apply to market coordinate values""" return self.__shock @shock.setter def shock(self, value: MarketDataShock): self._property_changed('shock') self.__shock = value class PCOExposureLeg(Base): """Parameters required for PCO Exposure Leg""" @camel_case_translate def __init__( self, local_to_base_rate: str = None, local_nav_limits: Tuple[str, ...] = None, base_nav_limits: Tuple[str, ...] = None, all_approved_hedge_ratio: str = None, show_all_approved_hedge_ratio: bool = None, hedge_ratio: str = None, exposure_ratio: str = None, local_currency: Union[Currency, str] = None, target_ratio: str = None, benchmark: PCOBenchmark = None, long_rebalance_threshold: str = None, short_rebalance_threshold: str = None, auto_roll: bool = None, name: str = None ): super().__init__() self.local_to_base_rate = local_to_base_rate self.local_nav_limits = local_nav_limits self.base_nav_limits = base_nav_limits self.all_approved_hedge_ratio = all_approved_hedge_ratio self.show_all_approved_hedge_ratio = show_all_approved_hedge_ratio self.hedge_ratio = hedge_ratio self.exposure_ratio = exposure_ratio self.local_currency = local_currency self.target_ratio = target_ratio self.benchmark = benchmark self.long_rebalance_threshold = long_rebalance_threshold self.short_rebalance_threshold = short_rebalance_threshold self.auto_roll = auto_roll self.name = name @property def local_to_base_rate(self) -> str: """Previously day FX spot rates for each currency pair""" return self.__local_to_base_rate @local_to_base_rate.setter def local_to_base_rate(self, value: str): self._property_changed('local_to_base_rate') self.__local_to_base_rate = value @property def local_nav_limits(self) -> Tuple[str, ...]: """Net Asset Value limits for local currency""" return self.__local_nav_limits @local_nav_limits.setter def local_nav_limits(self, value: Tuple[str, ...]): self._property_changed('local_nav_limits') self.__local_nav_limits = value @property def base_nav_limits(self) -> Tuple[str, ...]: """Net Asset Value limits for base currency""" return self.__base_nav_limits @base_nav_limits.setter def base_nav_limits(self, value: Tuple[str, ...]): self._property_changed('base_nav_limits') self.__base_nav_limits = value @property def all_approved_hedge_ratio(self) -> str: """Projected hedge ratio""" return self.__all_approved_hedge_ratio @all_approved_hedge_ratio.setter def all_approved_hedge_ratio(self, value: str): self._property_changed('all_approved_hedge_ratio') self.__all_approved_hedge_ratio = value @property def show_all_approved_hedge_ratio(self) -> bool: """If UI displays projected hedge ratio""" return self.__show_all_approved_hedge_ratio @show_all_approved_hedge_ratio.setter def show_all_approved_hedge_ratio(self, value: bool): self._property_changed('show_all_approved_hedge_ratio') self.__show_all_approved_hedge_ratio = value @property def hedge_ratio(self) -> str: """Ratio of target exposure that is intended to be hedged""" return self.__hedge_ratio @hedge_ratio.setter def hedge_ratio(self, value: str): self._property_changed('hedge_ratio') self.__hedge_ratio = value @property def exposure_ratio(self) -> str: """Exposure ratio""" return self.__exposure_ratio @exposure_ratio.setter def exposure_ratio(self, value: str): self._property_changed('exposure_ratio') self.__exposure_ratio = value @property def local_currency(self) -> Union[Currency, str]: """Local currency""" return self.__local_currency @local_currency.setter def local_currency(self, value: Union[Currency, str]): self._property_changed('local_currency') self.__local_currency = get_enum_value(Currency, value) @property def target_ratio(self) -> str: """Target hedge ratio for each currency""" return self.__target_ratio @target_ratio.setter def target_ratio(self, value: str): self._property_changed('target_ratio') self.__target_ratio = value @property def benchmark(self) -> PCOBenchmark: """Benchmark used for each currency""" return self.__benchmark @benchmark.setter def benchmark(self, value: PCOBenchmark): self._property_changed('benchmark') self.__benchmark = value @property def long_rebalance_threshold(self) -> str: """Long threshold for TNA adjustment for each currency""" return self.__long_rebalance_threshold @long_rebalance_threshold.setter def long_rebalance_threshold(self, value: str): self._property_changed('long_rebalance_threshold') self.__long_rebalance_threshold = value @property def short_rebalance_threshold(self) -> str: """Short threshold for TNA adjustment for each currency""" return self.__short_rebalance_threshold @short_rebalance_threshold.setter def short_rebalance_threshold(self, value: str): self._property_changed('short_rebalance_threshold') self.__short_rebalance_threshold = value @property def auto_roll(self) -> bool: """Whether roll orders will be automatically generated for each currency""" return self.__auto_roll @auto_roll.setter def auto_roll(self, value: bool): self._property_changed('auto_roll') self.__auto_roll = value class CSLScheduleArray(Base): """An array of schedules""" @camel_case_translate def __init__( self, schedule_values: Tuple[CSLSchedule, ...] = None, name: str = None ): super().__init__() self.schedule_values = schedule_values self.name = name @property def schedule_values(self) -> Tuple[CSLSchedule, ...]: """A schedule""" return self.__schedule_values @schedule_values.setter def schedule_values(self, value: Tuple[CSLSchedule, ...]): self._property_changed('schedule_values') self.__schedule_values = value class LiquidityRequest(Base): """Required parameters in order to get liquidity information on a set of positions""" @camel_case_translate def __init__( self, notional: float = None, positions: dict = None, risk_model: str = None, date: datetime.date = None, currency: Union[Currency, str] = None, participation_rate: float = None, execution_horizon: float = None, execution_start_time: datetime.datetime = None, execution_end_time: datetime.datetime = None, benchmark_id: str = None, measures: Tuple[Union[LiquidityMeasure, str], ...] = None, time_series_benchmark_ids: Tuple[str, ...] = None, time_series_start_date: datetime.date = None, time_series_end_date: datetime.date = None, format_: Union[Format, str] = None, report_parameters: LiquidityReportParameters = None, explode_positions: bool = False, name: str = None ): super().__init__() self.notional = notional self.positions = positions self.risk_model = risk_model self.date = date self.currency = currency self.participation_rate = participation_rate self.execution_horizon = execution_horizon self.execution_start_time = execution_start_time self.execution_end_time = execution_end_time self.benchmark_id = benchmark_id self.measures = measures self.time_series_benchmark_ids = time_series_benchmark_ids self.time_series_start_date = time_series_start_date self.time_series_end_date = time_series_end_date self.__format = get_enum_value(Format, format_) self.report_parameters = report_parameters self.explode_positions = explode_positions self.name = name @property def notional(self) -> float: """Notional value of the positions.""" return self.__notional @notional.setter def notional(self, value: float): self._property_changed('notional') self.__notional = value @property def positions(self) -> dict: """A set of quantity or weighted positions.""" return self.__positions @positions.setter def positions(self, value: dict): self._property_changed('positions') self.__positions = value @property def risk_model(self) -> str: """Marquee unique risk model identifier""" return self.__risk_model @risk_model.setter def risk_model(self, value: str): self._property_changed('risk_model') self.__risk_model = value @property def date(self) -> datetime.date: """ISO 8601-formatted date""" return self.__date @date.setter def date(self, value: datetime.date): self._property_changed('date') self.__date = value @property def currency(self) -> Union[Currency, str]: """Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)""" return self.__currency @currency.setter def currency(self, value: Union[Currency, str]): self._property_changed('currency') self.__currency = get_enum_value(Currency, value) @property def participation_rate(self) -> float: return self.__participation_rate @participation_rate.setter def participation_rate(self, value: float): self._property_changed('participation_rate') self.__participation_rate = value @property def execution_horizon(self) -> float: return self.__execution_horizon @execution_horizon.setter def execution_horizon(self, value: float): self._property_changed('execution_horizon') self.__execution_horizon = value @property def execution_start_time(self) -> datetime.datetime: """ISO 8601-formatted timestamp""" return self.__execution_start_time @execution_start_time.setter def execution_start_time(self, value: datetime.datetime): self._property_changed('execution_start_time') self.__execution_start_time = value @property def execution_end_time(self) -> datetime.datetime: """ISO 8601-formatted timestamp""" return self.__execution_end_time @execution_end_time.setter def execution_end_time(self, value: datetime.datetime): self._property_changed('execution_end_time') self.__execution_end_time = value @property def benchmark_id(self) -> str: """Marquee unique asset identifier of the benchmark.""" return self.__benchmark_id @benchmark_id.setter def benchmark_id(self, value: str): self._property_changed('benchmark_id') self.__benchmark_id = value @property def measures(self) -> Tuple[Union[LiquidityMeasure, str], ...]: return self.__measures @measures.setter def measures(self, value: Tuple[Union[LiquidityMeasure, str], ...]): self._property_changed('measures') self.__measures = value @property def time_series_benchmark_ids(self) -> Tuple[str, ...]: """Marquee unique identifiers of assets to be used as benchmarks.""" return self.__time_series_benchmark_ids @time_series_benchmark_ids.setter def time_series_benchmark_ids(self, value: Tuple[str, ...]): self._property_changed('time_series_benchmark_ids') self.__time_series_benchmark_ids = value @property def time_series_start_date(self) -> datetime.date: """ISO 8601-formatted date""" return self.__time_series_start_date @time_series_start_date.setter def time_series_start_date(self, value: datetime.date): self._property_changed('time_series_start_date') self.__time_series_start_date = value @property def time_series_end_date(self) -> datetime.date: """ISO 8601-formatted date""" return self.__time_series_end_date @time_series_end_date.setter def time_series_end_date(self, value: datetime.date): self._property_changed('time_series_end_date') self.__time_series_end_date = value @property def format(self) -> Union[Format, str]: """Alternative format for data to be returned in""" return self.__format @format.setter def format(self, value: Union[Format, str]): self._property_changed('format') self.__format = get_enum_value(Format, value) @property def report_parameters(self) -> LiquidityReportParameters: """Parameters to be used on liquidity reports""" return self.__report_parameters @report_parameters.setter def report_parameters(self, value: LiquidityReportParameters): self._property_changed('report_parameters') self.__report_parameters = value @property def explode_positions(self) -> bool: """Flag determining whether the positions should be exploded before doing calculations.""" return self.__explode_positions @explode_positions.setter def explode_positions(self, value: bool): self._property_changed('explode_positions') self.__explode_positions = value class MarketDataShockBasedScenario(Scenario): """A scenario comprised of user-defined market data shocks""" @camel_case_translate def __init__( self, shocks: Tuple[MarketDataPatternAndShock, ...], name: str = None ): super().__init__() self.shocks = shocks self.name = name @property def scenario_type(self) -> str: """MarketDataShockBasedScenario""" return 'MarketDataShockBasedScenario' @property def shocks(self) -> Tuple[MarketDataPatternAndShock, ...]: """A shock to apply to market coordinate values matching the supplied pattern""" return self.__shocks @shocks.setter def shocks(self, value: Tuple[MarketDataPatternAndShock, ...]): self._property_changed('shocks') self.__shocks = value class OverlayMarket(Base): """A market with explicit coordinate values overlayed over a base market""" @camel_case_translate def __init__( self, base_market: Union[CloseMarket, LiveMarket, TimestampedMarket], market_data: Tuple[MarketDataCoordinateValue, ...], name: str = None ): super().__init__() self.base_market = base_market self.market_data = market_data self.name = name @property def market_type(self) -> str: """OverlayMarket""" return 'OverlayMarket' @property def base_market(self) -> Union[CloseMarket, LiveMarket, TimestampedMarket]: """The base market""" return self.__base_market @base_market.setter def base_market(self, value: Union[CloseMarket, LiveMarket, TimestampedMarket]): self._property_changed('base_market') self.__base_market = value @property def market_data(self) -> Tuple[MarketDataCoordinateValue, ...]: """Market data to overlay over the base market""" return self.__market_data @market_data.setter def market_data(self, value: Tuple[MarketDataCoordinateValue, ...]): self._property_changed('market_data') self.__market_data = value class PCOExposure(Base): """Parameters required for PCO Exposure""" @camel_case_translate def __init__( self, last_data_updated_date_time: datetime.datetime = None, nav_includes_fx_hedges: bool = None, use_fx_rate_on_base_fx_forward: bool = None, last_generate_orders_date_time: datetime.datetime = None, legs: Tuple[PCOExposureLeg, ...] = None, adjustments: PCOExposureAdjustments = None, ratio_mode: str = None, hedge_calc_currency: Union[PCOCurrencyType, str] = None, name: str = None ): super().__init__() self.last_data_updated_date_time = last_data_updated_date_time self.nav_includes_fx_hedges = nav_includes_fx_hedges self.use_fx_rate_on_base_fx_forward = use_fx_rate_on_base_fx_forward self.last_generate_orders_date_time = last_generate_orders_date_time self.legs = legs self.adjustments = adjustments self.ratio_mode = ratio_mode self.hedge_calc_currency = hedge_calc_currency self.name = name @property def last_data_updated_date_time(self) -> datetime.datetime: """Last time when data was updated""" return self.__last_data_updated_date_time @last_data_updated_date_time.setter def last_data_updated_date_time(self, value: datetime.datetime): self._property_changed('last_data_updated_date_time') self.__last_data_updated_date_time = value @property def nav_includes_fx_hedges(self) -> bool: """Whether Net Asset Value includes FX hedges""" return self.__nav_includes_fx_hedges @nav_includes_fx_hedges.setter def nav_includes_fx_hedges(self, value: bool): self._property_changed('nav_includes_fx_hedges') self.__nav_includes_fx_hedges = value @property def use_fx_rate_on_base_fx_forward(self) -> bool: """Use open hedge in notional of base currency or local currency""" return self.__use_fx_rate_on_base_fx_forward @use_fx_rate_on_base_fx_forward.setter def use_fx_rate_on_base_fx_forward(self, value: bool): self._property_changed('use_fx_rate_on_base_fx_forward') self.__use_fx_rate_on_base_fx_forward = value @property def last_generate_orders_date_time(self) -> datetime.datetime: """Last time when orders are generated""" return self.__last_generate_orders_date_time @last_generate_orders_date_time.setter def last_generate_orders_date_time(self, value: datetime.datetime): self._property_changed('last_generate_orders_date_time') self.__last_generate_orders_date_time = value @property def legs(self) -> Tuple[PCOExposureLeg, ...]: """Exposure details for each leg""" return self.__legs @legs.setter def legs(self, value: Tuple[PCOExposureLeg, ...]): self._property_changed('legs') self.__legs = value @property def adjustments(self) -> PCOExposureAdjustments: """Exposure adjustments""" return self.__adjustments @adjustments.setter def adjustments(self, value: PCOExposureAdjustments): self._property_changed('adjustments') self.__adjustments = value @property def ratio_mode(self) -> str: """One of hedge ratio or exposure ratio""" return self.__ratio_mode @ratio_mode.setter def ratio_mode(self, value: str): self._property_changed('ratio_mode') self.__ratio_mode = value @property def hedge_calc_currency(self) -> Union[PCOCurrencyType, str]: """One of Local and Base""" return self.__hedge_calc_currency @hedge_calc_currency.setter def hedge_calc_currency(self, value: Union[PCOCurrencyType, str]): self._property_changed('hedge_calc_currency') self.__hedge_calc_currency = get_enum_value(PCOCurrencyType, value) class PositionSet(Base): @camel_case_translate def __init__( self, positions: Tuple[Position, ...], position_date: datetime.date, id_: str = None, last_update_time: datetime.datetime = None, type_: str = None, divisor: float = None, last_updated_time: datetime.datetime = None, name: str = None ): super().__init__() self.__id = id_ self.position_date = position_date self.last_update_time = last_update_time self.positions = positions self.__type = type_ self.divisor = divisor self.last_updated_time = last_updated_time self.name = name @property def id(self) -> str: """Unique identifier""" return self.__id @id.setter def id(self, value: str): self._property_changed('id') self.__id = value @property def position_date(self) -> datetime.date: """ISO 8601-formatted date""" return self.__position_date @position_date.setter def position_date(self, value: datetime.date): self._property_changed('position_date') self.__position_date = value @property def last_update_time(self) -> datetime.datetime: """ISO 8601-formatted timestamp""" return self.__last_update_time @last_update_time.setter def last_update_time(self, value: datetime.datetime): self._property_changed('last_update_time') self.__last_update_time = value @property def positions(self) -> Tuple[Position, ...]: """Array of quantity position objects.""" return self.__positions @positions.setter def positions(self, value: Tuple[Position, ...]): self._property_changed('positions') self.__positions = value @property def type(self) -> str: """The composition type of a Portfolio""" return self.__type @type.setter def type(self, value: str): self._property_changed('type') self.__type = value @property def divisor(self) -> float: """optional index divisor for a position set""" return self.__divisor @divisor.setter def divisor(self, value: float): self._property_changed('divisor') self.__divisor = value @property def last_updated_time(self) -> datetime.datetime: """Timestamp of when the object was last updated.""" return self.__last_updated_time @last_updated_time.setter def last_updated_time(self, value: datetime.datetime): self._property_changed('last_updated_time') self.__last_updated_time = value class RelativeMarket(Base): """Market for pricing between two states (e.g. for PnlExplain)""" @camel_case_translate def __init__( self, from_market: Union[OverlayMarket, Union[CloseMarket, LiveMarket, TimestampedMarket]], to_market: Union[OverlayMarket, Union[CloseMarket, LiveMarket, TimestampedMarket]], name: str = None ): super().__init__() self.from_market = from_market self.to_market = to_market self.name = name @property def market_type(self) -> str: """RelativeMarket""" return 'RelativeMarket' @property def from_market(self) -> Union[OverlayMarket, Union[CloseMarket, LiveMarket, TimestampedMarket]]: """The base market""" return self.__from_market @from_market.setter def from_market(self, value: Union[OverlayMarket, Union[CloseMarket, LiveMarket, TimestampedMarket]]): self._property_changed('from_market') self.__from_market = value @property def to_market(self) -> Union[OverlayMarket, Union[CloseMarket, LiveMarket, TimestampedMarket]]: """The target market""" return self.__to_market @to_market.setter def to_market(self, value: Union[OverlayMarket, Union[CloseMarket, LiveMarket, TimestampedMarket]]): self._property_changed('to_market') self.__to_market = value class PricingDateAndMarketDataAsOf(Base): """Pricing date and market data as of (date or time)""" @camel_case_translate def __init__( self, pricing_date: datetime.date, market_data_as_of: Union[datetime.date, datetime.datetime] = None, market: dict = None, name: str = None ): super().__init__() self.pricing_date = pricing_date self.market_data_as_of = market_data_as_of self.market = market self.name = name @property def pricing_date(self) -> datetime.date: """The date for which to perform the calculation""" return self.__pricing_date @pricing_date.setter def pricing_date(self, value: datetime.date): self._property_changed('pricing_date') self.__pricing_date = value @property def market_data_as_of(self) -> Union[datetime.date, datetime.datetime]: """The date or time to source market data""" return self.__market_data_as_of @market_data_as_of.setter def market_data_as_of(self, value: Union[datetime.date, datetime.datetime]): self._property_changed('market_data_as_of') self.__market_data_as_of = value @property def market(self) -> dict: """The market used for pricing""" return self.__market @market.setter def market(self, value: dict): self._property_changed('market') self.__market = value class RiskRequest(Base): """Object representation of a risk calculation request""" @camel_case_translate def __init__( self, positions: Tuple[RiskPosition, ...], measures: Tuple[RiskMeasure, ...], pricing_and_market_data_as_of: Tuple[PricingDateAndMarketDataAsOf, ...] = None, pricing_location: Union[PricingLocation, str] = None, wait_for_results: bool = False, scenario: MarketDataScenario = None, parameters: RiskRequestParameters = None, request_visible_to_gs: bool = False, name: str = None ): super().__init__() self.positions = positions self.measures = measures self.pricing_and_market_data_as_of = pricing_and_market_data_as_of self.pricing_location = pricing_location self.wait_for_results = wait_for_results self.scenario = scenario self.parameters = parameters self.request_visible_to_gs = request_visible_to_gs self.name = name @property def positions(self) -> Tuple[RiskPosition, ...]: """The positions on which to run the risk calculation""" return self.__positions @positions.setter def positions(self, value: Tuple[RiskPosition, ...]): self._property_changed('positions') self.__positions = value @property def measures(self) -> Tuple[RiskMeasure, ...]: """A collection of risk measures to compute. E.g. { 'measureType': 'Delta', 'assetClass': 'Equity'""" return self.__measures @measures.setter def measures(self, value: Tuple[RiskMeasure, ...]): self._property_changed('measures') self.__measures = value @property def pricing_and_market_data_as_of(self) -> Tuple[PricingDateAndMarketDataAsOf, ...]: """Pricing date and market data as of (date or time)""" return self.__pricing_and_market_data_as_of @pricing_and_market_data_as_of.setter def pricing_and_market_data_as_of(self, value: Tuple[PricingDateAndMarketDataAsOf, ...]): self._property_changed('pricing_and_market_data_as_of') self.__pricing_and_market_data_as_of = value @property def pricing_location(self) -> Union[PricingLocation, str]: """The location for pricing and market data""" return self.__pricing_location @pricing_location.setter def pricing_location(self, value: Union[PricingLocation, str]): self._property_changed('pricing_location') self.__pricing_location = get_enum_value(PricingLocation, value) @property def wait_for_results(self) -> bool: """For short-running requests this may be set to true and the results will be returned directly. If false, the response will contain the Id to retrieve the results""" return self.__wait_for_results @wait_for_results.setter def wait_for_results(self, value: bool): self._property_changed('wait_for_results') self.__wait_for_results = value @property def scenario(self) -> MarketDataScenario: """A market data scenario to apply to the calculation""" return self.__scenario @scenario.setter def scenario(self, value: MarketDataScenario): self._property_changed('scenario') self.__scenario = value @property def parameters(self) -> RiskRequestParameters: """Parameters for the risk request""" return self.__parameters @parameters.setter def parameters(self, value: RiskRequestParameters): self._property_changed('parameters') self.__parameters = value @property def request_visible_to_gs(self) -> bool: return self.__request_visible_to_gs @request_visible_to_gs.setter def request_visible_to_gs(self, value: bool): self._property_changed('request_visible_to_gs') self.__request_visible_to_gs = value class ReportParameters(Base): """Parameters specific to the report type""" @camel_case_translate def __init__( self, asset_class: Union[AssetClass, str] = None, transaction_cost_model: str = None, trading_cost: float = None, servicing_cost_long: float = None, servicing_cost_short: float = None, region: str = None, risk_model: str = None, fx_hedged: bool = None, publish_to_bloomberg: bool = None, publish_to_reuters: bool = None, publish_to_factset: bool = None, include_price_history: bool = None, index_update: bool = None, index_rebalance: bool = None, basket_action: Union[BasketAction, str] = None, api_domain: bool = None, initial_price: float = None, stock_level_exposures: bool = None, explode_positions: bool = None, scenario_id: str = None, scenario_ids: Tuple[str, ...] = None, scenario_group_id: str = None, scenario_type: Union[ScenarioType, str] = None, market_model_id: str = None, risk_measures: Tuple[RiskMeasure, ...] = None, initial_pricing_date: datetime.date = None, backcast: bool = None, risk_request: RiskRequest = None, participation_rate: float = None, approve_rebalance: bool = None, use_risk_request_batch_mode: bool = False, limited_access_assets: Tuple[str, ...] = None, corporate_action_restricted_assets: Tuple[str, ...] = None, backcast_dates: Tuple[datetime.date, ...] = None, base_currency: Union[Currency, str] = None, local_currency: Union[Currency, str] = None, fund_calendar: Union[PCOFundCalendar, str] = None, calculation_currency: Union[PCOCurrencyType, str] = None, hedge_settlement_interval: Tuple[PCOParameterValues, ...] = None, hedge_settlement_day: Tuple[PCOParameterValues, ...] = None, roll_horizon: Tuple[PCOParameterValues, ...] = None, pnl_currency: Tuple[PCOParameterValues, ...] = None, nav_publication_period: Tuple[PCOParameterValues, ...] = None, roll_date_zero_threshold: bool = None, unrealised_mark_to_market: PCOUnrealisedMarkToMarket = None, target_deviation: Tuple[PCOTargetDeviation, ...] = None, cash_balances: Tuple[PCOCashBalance, ...] = None, exposure: PCOExposure = None, settlements: Tuple[PCOSettlements, ...] = None, show_cash: bool = None, show_exposure: bool = None, name: str = None ): super().__init__() self.asset_class = asset_class self.transaction_cost_model = transaction_cost_model self.trading_cost = trading_cost self.servicing_cost_long = servicing_cost_long self.servicing_cost_short = servicing_cost_short self.region = region self.risk_model = risk_model self.fx_hedged = fx_hedged self.publish_to_bloomberg = publish_to_bloomberg self.publish_to_reuters = publish_to_reuters self.publish_to_factset = publish_to_factset self.include_price_history = include_price_history self.index_update = index_update self.index_rebalance = index_rebalance self.basket_action = basket_action self.api_domain = api_domain self.initial_price = initial_price self.stock_level_exposures = stock_level_exposures self.explode_positions = explode_positions self.scenario_id = scenario_id self.scenario_ids = scenario_ids self.scenario_group_id = scenario_group_id self.scenario_type = scenario_type self.market_model_id = market_model_id self.risk_measures = risk_measures self.initial_pricing_date = initial_pricing_date self.backcast = backcast self.risk_request = risk_request self.participation_rate = participation_rate self.approve_rebalance = approve_rebalance self.use_risk_request_batch_mode = use_risk_request_batch_mode self.limited_access_assets = limited_access_assets self.corporate_action_restricted_assets = corporate_action_restricted_assets self.backcast_dates = backcast_dates self.base_currency = base_currency self.local_currency = local_currency self.fund_calendar = fund_calendar self.calculation_currency = calculation_currency self.hedge_settlement_interval = hedge_settlement_interval self.hedge_settlement_day = hedge_settlement_day self.roll_horizon = roll_horizon self.pnl_currency = pnl_currency self.nav_publication_period = nav_publication_period self.roll_date_zero_threshold = roll_date_zero_threshold self.unrealised_mark_to_market = unrealised_mark_to_market self.target_deviation = target_deviation self.cash_balances = cash_balances self.exposure = exposure self.settlements = settlements self.show_cash = show_cash self.show_exposure = show_exposure self.name = name @property def asset_class(self) -> Union[AssetClass, str]: """Asset classification of security. Assets are classified into broad groups which exhibit similar characteristics and behave in a consistent way under different market conditions""" return self.__asset_class @asset_class.setter def asset_class(self, value: Union[AssetClass, str]): self._property_changed('asset_class') self.__asset_class = get_enum_value(AssetClass, value) @property def transaction_cost_model(self) -> str: """Determines which model to use""" return self.__transaction_cost_model @transaction_cost_model.setter def transaction_cost_model(self, value: str): self._property_changed('transaction_cost_model') self.__transaction_cost_model = value @property def trading_cost(self) -> float: """bps cost to execute delta""" return self.__trading_cost @trading_cost.setter def trading_cost(self, value: float): self._property_changed('trading_cost') self.__trading_cost = value @property def servicing_cost_long(self) -> float: """bps cost to fund long positions""" return self.__servicing_cost_long @servicing_cost_long.setter def servicing_cost_long(self, value: float): self._property_changed('servicing_cost_long') self.__servicing_cost_long = value @property def servicing_cost_short(self) -> float: """bps cost to fund short positions""" return self.__servicing_cost_short @servicing_cost_short.setter def servicing_cost_short(self, value: float): self._property_changed('servicing_cost_short') self.__servicing_cost_short = value @property def region(self) -> str: """The region of the report""" return self.__region @region.setter def region(self, value: str): self._property_changed('region') self.__region = value @property def risk_model(self) -> str: """Marquee unique risk model identifier""" return self.__risk_model @risk_model.setter def risk_model(self, value: str): self._property_changed('risk_model') self.__risk_model = value @property def fx_hedged(self) -> bool: """Assume portfolio is FX Hedged""" return self.__fx_hedged @fx_hedged.setter def fx_hedged(self, value: bool): self._property_changed('fx_hedged') self.__fx_hedged = value @property def publish_to_bloomberg(self) -> bool: """Publish Basket to Bloomberg""" return self.__publish_to_bloomberg @publish_to_bloomberg.setter def publish_to_bloomberg(self, value: bool): self._property_changed('publish_to_bloomberg') self.__publish_to_bloomberg = value @property def publish_to_reuters(self) -> bool: """Publish Basket to Reuters""" return self.__publish_to_reuters @publish_to_reuters.setter def publish_to_reuters(self, value: bool): self._property_changed('publish_to_reuters') self.__publish_to_reuters = value @property def publish_to_factset(self) -> bool: """Publish Basket to Factset""" return self.__publish_to_factset @publish_to_factset.setter def publish_to_factset(self, value: bool): self._property_changed('publish_to_factset') self.__publish_to_factset = value @property def include_price_history(self) -> bool: """Include full price history""" return self.__include_price_history @include_price_history.setter def include_price_history(self, value: bool): self._property_changed('include_price_history') self.__include_price_history = value @property def index_update(self) -> bool: """Update the basket""" return self.__index_update @index_update.setter def index_update(self, value: bool): self._property_changed('index_update') self.__index_update = value @property def index_rebalance(self) -> bool: """Rebalance the basket""" return self.__index_rebalance @index_rebalance.setter def index_rebalance(self, value: bool): self._property_changed('index_rebalance') self.__index_rebalance = value @property def basket_action(self) -> Union[BasketAction, str]: """Indicates which basket action triggered the report""" return self.__basket_action @basket_action.setter def basket_action(self, value: Union[BasketAction, str]): self._property_changed('basket_action') self.__basket_action = get_enum_value(BasketAction, value) @property def api_domain(self) -> bool: """Indicates if report is triggered from ui/api call""" return self.__api_domain @api_domain.setter def api_domain(self, value: bool): self._property_changed('api_domain') self.__api_domain = value @property def initial_price(self) -> float: """Initial price for the position set""" return self.__initial_price @initial_price.setter def initial_price(self, value: float): self._property_changed('initial_price') self.__initial_price = value @property def stock_level_exposures(self) -> bool: """Publish stock level exposures""" return self.__stock_level_exposures @stock_level_exposures.setter def stock_level_exposures(self, value: bool): self._property_changed('stock_level_exposures') self.__stock_level_exposures = value @property def explode_positions(self) -> bool: """Whether to explode positions during risk run""" return self.__explode_positions @explode_positions.setter def explode_positions(self, value: bool): self._property_changed('explode_positions') self.__explode_positions = value @property def scenario_id(self) -> str: """Marquee unique scenario identifier""" return self.__scenario_id @scenario_id.setter def scenario_id(self, value: str): self._property_changed('scenario_id') self.__scenario_id = value @property def scenario_ids(self) -> Tuple[str, ...]: """Array of scenario identifiers related to the object""" return self.__scenario_ids @scenario_ids.setter def scenario_ids(self, value: Tuple[str, ...]): self._property_changed('scenario_ids') self.__scenario_ids = value @property def scenario_group_id(self) -> str: """Marquee unique scenario group identifier""" return self.__scenario_group_id @scenario_group_id.setter def scenario_group_id(self, value: str): self._property_changed('scenario_group_id') self.__scenario_group_id = value @property def scenario_type(self) -> Union[ScenarioType, str]: """Type of Scenario""" return self.__scenario_type @scenario_type.setter def scenario_type(self, value: Union[ScenarioType, str]): self._property_changed('scenario_type') self.__scenario_type = get_enum_value(ScenarioType, value) @property def market_model_id(self) -> str: """Marquee unique market model identifier""" return self.__market_model_id @market_model_id.setter def market_model_id(self, value: str): self._property_changed('market_model_id') self.__market_model_id = value @property def risk_measures(self) -> Tuple[RiskMeasure, ...]: """An array of risk measures to get from the risk calculation.""" return self.__risk_measures @risk_measures.setter def risk_measures(self, value: Tuple[RiskMeasure, ...]): self._property_changed('risk_measures') self.__risk_measures = value @property def initial_pricing_date(self) -> datetime.date: """ISO 8601-formatted date""" return self.__initial_pricing_date @initial_pricing_date.setter def initial_pricing_date(self, value: datetime.date): self._property_changed('initial_pricing_date') self.__initial_pricing_date = value @property def backcast(self) -> bool: """Use backcasted portfolio derived from positions on the end date.""" return self.__backcast @backcast.setter def backcast(self, value: bool): self._property_changed('backcast') self.__backcast = value @property def risk_request(self) -> RiskRequest: """A request for a risk calculation""" return self.__risk_request @risk_request.setter def risk_request(self, value: RiskRequest): self._property_changed('risk_request') self.__risk_request = value @property def participation_rate(self) -> float: """Liquidity analytics participation rate.""" return self.__participation_rate @participation_rate.setter def participation_rate(self, value: float): self._property_changed('participation_rate') self.__participation_rate = value @property def approve_rebalance(self) -> bool: """An approved basket""" return self.__approve_rebalance @approve_rebalance.setter def approve_rebalance(self, value: bool): self._property_changed('approve_rebalance') self.__approve_rebalance = value @property def use_risk_request_batch_mode(self) -> bool: """Switch to enable RiskRequest batching""" return self.__use_risk_request_batch_mode @use_risk_request_batch_mode.setter def use_risk_request_batch_mode(self, value: bool): self._property_changed('use_risk_request_batch_mode') self.__use_risk_request_batch_mode = value @property def limited_access_assets(self) -> Tuple[str, ...]: """List of constituents in the basket that GS has limited access to""" return self.__limited_access_assets @limited_access_assets.setter def limited_access_assets(self, value: Tuple[str, ...]): self._property_changed('limited_access_assets') self.__limited_access_assets = value @property def corporate_action_restricted_assets(self) -> Tuple[str, ...]: """List of constituents in the basket that will not be adjusted for corporate actions in the future""" return self.__corporate_action_restricted_assets @corporate_action_restricted_assets.setter def corporate_action_restricted_assets(self, value: Tuple[str, ...]): self._property_changed('corporate_action_restricted_assets') self.__corporate_action_restricted_assets = value @property def backcast_dates(self) -> Tuple[datetime.date, ...]: """List of dates user upload to backcast basket""" return self.__backcast_dates @backcast_dates.setter def backcast_dates(self, value: Tuple[datetime.date, ...]): self._property_changed('backcast_dates') self.__backcast_dates = value @property def base_currency(self) -> Union[Currency, str]: """Base currency""" return self.__base_currency @base_currency.setter def base_currency(self, value: Union[Currency, str]): self._property_changed('base_currency') self.__base_currency = get_enum_value(Currency, value) @property def local_currency(self) -> Union[Currency, str]: """Local currency""" return self.__local_currency @local_currency.setter def local_currency(self, value: Union[Currency, str]): self._property_changed('local_currency') self.__local_currency = get_enum_value(Currency, value) @property def fund_calendar(self) -> Union[PCOFundCalendar, str]: """Holiday Calendar of Fund""" return self.__fund_calendar @fund_calendar.setter def fund_calendar(self, value: Union[PCOFundCalendar, str]): self._property_changed('fund_calendar') self.__fund_calendar = get_enum_value(PCOFundCalendar, value) @property def calculation_currency(self) -> Union[PCOCurrencyType, str]: """Calculation currency type""" return self.__calculation_currency @calculation_currency.setter def calculation_currency(self, value: Union[PCOCurrencyType, str]): self._property_changed('calculation_currency') self.__calculation_currency = get_enum_value(PCOCurrencyType, value) @property def hedge_settlement_interval(self) -> Tuple[PCOParameterValues, ...]: """Default tenor of hedging for each currency""" return self.__hedge_settlement_interval @hedge_settlement_interval.setter def hedge_settlement_interval(self, value: Tuple[PCOParameterValues, ...]): self._property_changed('hedge_settlement_interval') self.__hedge_settlement_interval = value @property def hedge_settlement_day(self) -> Tuple[PCOParameterValues, ...]: """Settlement date of each currency""" return self.__hedge_settlement_day @hedge_settlement_day.setter def hedge_settlement_day(self, value: Tuple[PCOParameterValues, ...]): self._property_changed('hedge_settlement_day') self.__hedge_settlement_day = value @property def roll_horizon(self) -> Tuple[PCOParameterValues, ...]: """Number of days to roll before settlement for each currency""" return self.__roll_horizon @roll_horizon.setter def roll_horizon(self, value: Tuple[PCOParameterValues, ...]): self._property_changed('roll_horizon') self.__roll_horizon = value @property def pnl_currency(self) -> Tuple[PCOParameterValues, ...]: """One of Local and Base""" return self.__pnl_currency @pnl_currency.setter def pnl_currency(self, value: Tuple[PCOParameterValues, ...]): self._property_changed('pnl_currency') self.__pnl_currency = value @property def nav_publication_period(self) -> Tuple[PCOParameterValues, ...]: """Days it takes for a subscription or redemption show up in NAV after it happens""" return self.__nav_publication_period @nav_publication_period.setter def nav_publication_period(self, value: Tuple[PCOParameterValues, ...]): self._property_changed('nav_publication_period') self.__nav_publication_period = value @property def roll_date_zero_threshold(self) -> bool: """If true, rebalance this program when rolling""" return self.__roll_date_zero_threshold @roll_date_zero_threshold.setter def roll_date_zero_threshold(self, value: bool): self._property_changed('roll_date_zero_threshold') self.__roll_date_zero_threshold = value @property def unrealised_mark_to_market(self) -> PCOUnrealisedMarkToMarket: """History of unrealised mark to market of open trades for each currency""" return self.__unrealised_mark_to_market @unrealised_mark_to_market.setter def unrealised_mark_to_market(self, value: PCOUnrealisedMarkToMarket): self._property_changed('unrealised_mark_to_market') self.__unrealised_mark_to_market = value @property def target_deviation(self) -> Tuple[PCOTargetDeviation, ...]: """History of target deviation for each currency""" return self.__target_deviation @target_deviation.setter def target_deviation(self, value: Tuple[PCOTargetDeviation, ...]): self._property_changed('target_deviation') self.__target_deviation = value @property def cash_balances(self) -> Tuple[PCOCashBalance, ...]: """Cash flows for each currency""" return self.__cash_balances @cash_balances.setter def cash_balances(self, value: Tuple[PCOCashBalance, ...]): self._property_changed('cash_balances') self.__cash_balances = value @property def exposure(self) -> PCOExposure: """Total exposure for portfolio""" return self.__exposure @exposure.setter def exposure(self, value: PCOExposure): self._property_changed('exposure') self.__exposure = value @property def settlements(self) -> Tuple[PCOSettlements, ...]: """History of settlements for each currency""" return self.__settlements @settlements.setter def settlements(self, value: Tuple[PCOSettlements, ...]): self._property_changed('settlements') self.__settlements = value @property def show_cash(self) -> bool: """If cash table is shown in UI""" return self.__show_cash @show_cash.setter def show_cash(self, value: bool): self._property_changed('show_cash') self.__show_cash = value @property def show_exposure(self) -> bool: """If exposure table is shown in UI""" return self.__show_exposure @show_exposure.setter def show_exposure(self, value: bool): self._property_changed('show_exposure') self.__show_exposure = value class ReportScheduleRequest(Base): """Parameters in order to schedule a report""" @camel_case_translate def __init__( self, parameters: ReportParameters = None, end_date: datetime.date = None, start_date: datetime.date = None, priority: Union[ReportJobPriority, str] = None, name: str = None ): super().__init__() self.parameters = parameters self.end_date = end_date self.start_date = start_date self.priority = priority self.name = name @property def parameters(self) -> ReportParameters: """Parameters specific to the report type""" return self.__parameters @parameters.setter def parameters(self, value: ReportParameters): self._property_changed('parameters') self.__parameters = value @property def end_date(self) -> datetime.date: """ISO 8601-formatted date""" return self.__end_date @end_date.setter def end_date(self, value: datetime.date): self._property_changed('end_date') self.__end_date = value @property def start_date(self) -> datetime.date: """ISO 8601-formatted date""" return self.__start_date @start_date.setter def start_date(self, value: datetime.date): self._property_changed('start_date') self.__start_date = value @property def priority(self) -> Union[ReportJobPriority, str]: """Report job priority.""" return self.__priority @priority.setter def priority(self, value: Union[ReportJobPriority, str]): self._property_changed('priority') self.__priority = get_enum_value(ReportJobPriority, value)

================================================ FILE: docs/_build/html/_modules/gs_quant/target/instrument.html ================================================ gs_quant.target.instrument — gs_quant 0.1 documentation

Source code for gs_quant.target.instrument

"""
Copyright 2019 Goldman Sachs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
"""

from gs_quant.target.common import *
import datetime
from typing import Tuple, Union
from gs_quant.instrument import Instrument
from gs_quant.base import InstrumentBase, camel_case_translate, get_enum_value


class CSLPython(Instrument):
        
    """Object representation of an arbitrary payoff defined in Python"""

    @camel_case_translate
    def __init__(
        self,
        class_name: str = None,
        denominated: Union[Currency, str] = None,
        double_params: Tuple[CSLDouble, ...] = None,
        date_params: Tuple[CSLDate, ...] = None,
        string_params: Tuple[CSLString, ...] = None,
        simple_schedule_params: Tuple[CSLSimpleSchedule, ...] = None,
        schedule_params: Tuple[CSLSchedule, ...] = None,
        currency_params: Tuple[CSLCurrency, ...] = None,
        stock_params: Tuple[CSLStock, ...] = None,
        index_params: Tuple[CSLIndex, ...] = None,
        fx_cross_params: Tuple[CSLFXCross, ...] = None,
        double_array_params: Tuple[CSLDoubleArray, ...] = None,
        date_array_params: Tuple[CSLDateArray, ...] = None,
        string_array_params: Tuple[CSLStringArray, ...] = None,
        simple_schedule_array_params: Tuple[CSLSimpleScheduleArray, ...] = None,
        schedule_array_params: Tuple[CSLScheduleArray, ...] = None,
        currency_array_params: Tuple[CSLCurrencyArray, ...] = None,
        stock_array_params: Tuple[CSLStockArray, ...] = None,
        index_array_params: Tuple[CSLIndexArray, ...] = None,
        fx_cross_array_params: Tuple[CSLFXCrossArray, ...] = None,
        name: str = None
    ):        
        super().__init__()
        self.class_name = class_name
        self.denominated = denominated
        self.double_params = double_params
        self.date_params = date_params
        self.string_params = string_params
        self.simple_schedule_params = simple_schedule_params
        self.schedule_params = schedule_params
        self.currency_params = currency_params
        self.stock_params = stock_params
        self.index_params = index_params
        self.fx_cross_params = fx_cross_params
        self.double_array_params = double_array_params
        self.date_array_params = date_array_params
        self.string_array_params = string_array_params
        self.simple_schedule_array_params = simple_schedule_array_params
        self.schedule_array_params = schedule_array_params
        self.currency_array_params = currency_array_params
        self.stock_array_params = stock_array_params
        self.index_array_params = index_array_params
        self.fx_cross_array_params = fx_cross_array_params
        self.name = name

    @property
    def asset_class(self) -> AssetClass:
        """Cross Asset"""
        return AssetClass.Cross_Asset        

    @property
    def type(self) -> AssetType:
        """CSL"""
        return AssetType.CSL        

    @property
    def class_name(self) -> str:
        """A reference to the Python script defining this payoff class"""
        return self.__class_name

    @class_name.setter
    def class_name(self, value: str):
        self._property_changed('class_name')
        self.__class_name = value        

    @property
    def denominated(self) -> Union[Currency, str]:
        """Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)"""
        return self.__denominated

    @denominated.setter
    def denominated(self, value: Union[Currency, str]):
        self._property_changed('denominated')
        self.__denominated = get_enum_value(Currency, value)        

    @property
    def double_params(self) -> Tuple[CSLDouble, ...]:
        """A double"""
        return self.__double_params

    @double_params.setter
    def double_params(self, value: Tuple[CSLDouble, ...]):
        self._property_changed('double_params')
        self.__double_params = value        

    @property
    def date_params(self) -> Tuple[CSLDate, ...]:
        """A date"""
        return self.__date_params

    @date_params.setter
    def date_params(self, value: Tuple[CSLDate, ...]):
        self._property_changed('date_params')
        self.__date_params = value        

    @property
    def string_params(self) -> Tuple[CSLString, ...]:
        """A string"""
        return self.__string_params

    @string_params.setter
    def string_params(self, value: Tuple[CSLString, ...]):
        self._property_changed('string_params')
        self.__string_params = value        

    @property
    def simple_schedule_params(self) -> Tuple[CSLSimpleSchedule, ...]:
        """A fixing date, settlement date pair"""
        return self.__simple_schedule_params

    @simple_schedule_params.setter
    def simple_schedule_params(self, value: Tuple[CSLSimpleSchedule, ...]):
        self._property_changed('simple_schedule_params')
        self.__simple_schedule_params = value        

    @property
    def schedule_params(self) -> Tuple[CSLSchedule, ...]:
        """A schedule"""
        return self.__schedule_params

    @schedule_params.setter
    def schedule_params(self, value: Tuple[CSLSchedule, ...]):
        self._property_changed('schedule_params')
        self.__schedule_params = value        

    @property
    def currency_params(self) -> Tuple[CSLCurrency, ...]:
        """A currency"""
        return self.__currency_params

    @currency_params.setter
    def currency_params(self, value: Tuple[CSLCurrency, ...]):
        self._property_changed('currency_params')
        self.__currency_params = value        

    @property
    def stock_params(self) -> Tuple[CSLStock, ...]:
        """A stock"""
        return self.__stock_params

    @stock_params.setter
    def stock_params(self, value: Tuple[CSLStock, ...]):
        self._property_changed('stock_params')
        self.__stock_params = value        

    @property
    def index_params(self) -> Tuple[CSLIndex, ...]:
        """An index"""
        return self.__index_params

    @index_params.setter
    def index_params(self, value: Tuple[CSLIndex, ...]):
        self._property_changed('index_params')
        self.__index_params = value        

    @property
    def fx_cross_params(self) -> Tuple[CSLFXCross, ...]:
        """An FX cross"""
        return self.__fx_cross_params

    @fx_cross_params.setter
    def fx_cross_params(self, value: Tuple[CSLFXCross, ...]):
        self._property_changed('fx_cross_params')
        self.__fx_cross_params = value        

    @property
    def double_array_params(self) -> Tuple[CSLDoubleArray, ...]:
        """An array of doubles"""
        return self.__double_array_params

    @double_array_params.setter
    def double_array_params(self, value: Tuple[CSLDoubleArray, ...]):
        self._property_changed('double_array_params')
        self.__double_array_params = value        

    @property
    def date_array_params(self) -> Tuple[CSLDateArray, ...]:
        """An array of dates"""
        return self.__date_array_params

    @date_array_params.setter
    def date_array_params(self, value: Tuple[CSLDateArray, ...]):
        self._property_changed('date_array_params')
        self.__date_array_params = value        

    @property
    def string_array_params(self) -> Tuple[CSLStringArray, ...]:
        """An array of strings"""
        return self.__string_array_params

    @string_array_params.setter
    def string_array_params(self, value: Tuple[CSLStringArray, ...]):
        self._property_changed('string_array_params')
        self.__string_array_params = value        

    @property
    def simple_schedule_array_params(self) -> Tuple[CSLSimpleScheduleArray, ...]:
        """An array of simple schedules"""
        return self.__simple_schedule_array_params

    @simple_schedule_array_params.setter
    def simple_schedule_array_params(self, value: Tuple[CSLSimpleScheduleArray, ...]):
        self._property_changed('simple_schedule_array_params')
        self.__simple_schedule_array_params = value        

    @property
    def schedule_array_params(self) -> Tuple[CSLScheduleArray, ...]:
        """An array of schedules"""
        return self.__schedule_array_params

    @schedule_array_params.setter
    def schedule_array_params(self, value: Tuple[CSLScheduleArray, ...]):
        self._property_changed('schedule_array_params')
        self.__schedule_array_params = value        

    @property
    def currency_array_params(self) -> Tuple[CSLCurrencyArray, ...]:
        """An array of currencies"""
        return self.__currency_array_params

    @currency_array_params.setter
    def currency_array_params(self, value: Tuple[CSLCurrencyArray, ...]):
        self._property_changed('currency_array_params')
        self.__currency_array_params = value        

    @property
    def stock_array_params(self) -> Tuple[CSLStockArray, ...]:
        """An array of stocks"""
        return self.__stock_array_params

    @stock_array_params.setter
    def stock_array_params(self, value: Tuple[CSLStockArray, ...]):
        self._property_changed('stock_array_params')
        self.__stock_array_params = value        

    @property
    def index_array_params(self) -> Tuple[CSLIndexArray, ...]:
        """An array of indices"""
        return self.__index_array_params

    @index_array_params.setter
    def index_array_params(self, value: Tuple[CSLIndexArray, ...]):
        self._property_changed('index_array_params')
        self.__index_array_params = value        

    @property
    def fx_cross_array_params(self) -> Tuple[CSLFXCrossArray, ...]:
        """An array of FX crosses"""
        return self.__fx_cross_array_params

    @fx_cross_array_params.setter
    def fx_cross_array_params(self, value: Tuple[CSLFXCrossArray, ...]):
        self._property_changed('fx_cross_array_params')
        self.__fx_cross_array_params = value        


class Cash(Instrument):
        
    """Cash payment"""

    @camel_case_translate
    def __init__(
        self,
        currency: Union[Currency, str] = None,
        payment_date: datetime.date = None,
        notional_amount: Union[float, str] = None,
        name: str = None
    ):        
        super().__init__()
        self.currency = currency
        self.payment_date = payment_date
        self.notional_amount = notional_amount
        self.name = name

    @property
    def asset_class(self) -> AssetClass:
        """Cash"""
        return AssetClass.Cash        

    @property
    def type(self) -> AssetType:
        """Cash"""
        return AssetType.Cash        

    @property
    def currency(self) -> Union[Currency, str]:
        """Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)"""
        return self.__currency

    @currency.setter
    def currency(self, value: Union[Currency, str]):
        self._property_changed('currency')
        self.__currency = get_enum_value(Currency, value)        

    @property
    def payment_date(self) -> datetime.date:
        """ISO 8601-formatted date"""
        return self.__payment_date

    @payment_date.setter
    def payment_date(self, value: datetime.date):
        self._property_changed('payment_date')
        self.__payment_date = value        

    @property
    def notional_amount(self) -> Union[float, str]:
        """Notional amount"""
        return self.__notional_amount

    @notional_amount.setter
    def notional_amount(self, value: Union[float, str]):
        self._property_changed('notional_amount')
        self.__notional_amount = value        


class CommodOTCOptionLeg(Instrument):
        
    """Commodities OTC option leg"""

    @camel_case_translate
    def __init__(
        self,
        underlier: str = None,
        contract: str = None,
        leg_description: str = None,
        fixing_currency: Union[CurrencyName, str] = None,
        fixing_currency_source: str = None,
        option_type: Union[OptionType, str] = None,
        quantity_multiplier: int = None,
        premium: CommodPrice = None,
        premium_settlement: str = None,
        strike: Union[float, str] = None,
        name: str = None
    ):        
        super().__init__()
        self.underlier = underlier
        self.contract = contract
        self.leg_description = leg_description
        self.fixing_currency = fixing_currency
        self.fixing_currency_source = fixing_currency_source
        self.option_type = option_type
        self.quantity_multiplier = quantity_multiplier
        self.premium = premium
        self.premium_settlement = premium_settlement
        self.strike = strike
        self.name = name

    @property
    def asset_class(self) -> AssetClass:
        """Commod"""
        return AssetClass.Commod        

    @property
    def type(self) -> AssetType:
        """OptionLeg"""
        return AssetType.OptionLeg        

    @property
    def underlier(self) -> str:
        """Commodity asset"""
        return self.__underlier

    @underlier.setter
    def underlier(self, value: str):
        self._property_changed('underlier')
        self.__underlier = value        

    @property
    def contract(self) -> str:
        """The observed contract at each pricing date e.g First Nearby, Second Nearby"""
        return self.__contract

    @contract.setter
    def contract(self, value: str):
        self._property_changed('contract')
        self.__contract = value        

    @property
    def leg_description(self) -> str:
        """The description of the averaging style"""
        return self.__leg_description

    @leg_description.setter
    def leg_description(self, value: str):
        self._property_changed('leg_description')
        self.__leg_description = value        

    @property
    def fixing_currency(self) -> Union[CurrencyName, str]:
        """Currency Names"""
        return self.__fixing_currency

    @fixing_currency.setter
    def fixing_currency(self, value: Union[CurrencyName, str]):
        self._property_changed('fixing_currency')
        self.__fixing_currency = get_enum_value(CurrencyName, value)        

    @property
    def fixing_currency_source(self) -> str:
        """fixing currency conversion rate source"""
        return self.__fixing_currency_source

    @fixing_currency_source.setter
    def fixing_currency_source(self, value: str):
        self._property_changed('fixing_currency_source')
        self.__fixing_currency_source = value        

    @property
    def option_type(self) -> Union[OptionType, str]:
        """Option Type"""
        return self.__option_type

    @option_type.setter
    def option_type(self, value: Union[OptionType, str]):
        self._property_changed('option_type')
        self.__option_type = get_enum_value(OptionType, value)        

    @property
    def quantity_multiplier(self) -> int:
        """quantity multiplier for driving the long/short direction of the leg"""
        return self.__quantity_multiplier

    @quantity_multiplier.setter
    def quantity_multiplier(self, value: int):
        self._property_changed('quantity_multiplier')
        self.__quantity_multiplier = value        

    @property
    def premium(self) -> CommodPrice:
        """Option premium"""
        return self.__premium

    @premium.setter
    def premium(self, value: CommodPrice):
        self._property_changed('premium')
        self.__premium = value        

    @property
    def premium_settlement(self) -> str:
        """read only description in plain English of settlement terms"""
        return self.__premium_settlement

    @premium_settlement.setter
    def premium_settlement(self, value: str):
        self._property_changed('premium_settlement')
        self.__premium_settlement = value        

    @property
    def strike(self) -> Union[float, str]:
        """Size of some value, i.e. notional like 1.3b, 1.5, 1000"""
        return self.__strike

    @strike.setter
    def strike(self, value: Union[float, str]):
        self._property_changed('strike')
        self.__strike = value        


class CommodOTCSwapLeg(Instrument):
        
    """Commodities OTC swap leg"""

    @camel_case_translate
    def __init__(
        self,
        underlier: str = None,
        contract: str = None,
        leg_description: str = None,
        fixing_currency: Union[CurrencyName, str] = None,
        fixing_currency_source: str = None,
        quantity_multiplier: int = None,
        fixed_price: Union[float, str] = None,
        name: str = None
    ):        
        super().__init__()
        self.underlier = underlier
        self.contract = contract
        self.leg_description = leg_description
        self.fixing_currency = fixing_currency
        self.fixing_currency_source = fixing_currency_source
        self.quantity_multiplier = quantity_multiplier
        self.fixed_price = fixed_price
        self.name = name

    @property
    def asset_class(self) -> AssetClass:
        """Commod"""
        return AssetClass.Commod        

    @property
    def type(self) -> AssetType:
        """SwapLeg"""
        return AssetType.SwapLeg        

    @property
    def underlier(self) -> str:
        """Commodity asset"""
        return self.__underlier

    @underlier.setter
    def underlier(self, value: str):
        self._property_changed('underlier')
        self.__underlier = value        

    @property
    def contract(self) -> str:
        """The observed contract at each pricing date e.g First Nearby, Second Nearby"""
        return self.__contract

    @contract.setter
    def contract(self, value: str):
        self._property_changed('contract')
        self.__contract = value        

    @property
    def leg_description(self) -> str:
        """The description of the averaging style"""
        return self.__leg_description

    @leg_description.setter
    def leg_description(self, value: str):
        self._property_changed('leg_description')
        self.__leg_description = value        

    @property
    def fixing_currency(self) -> Union[CurrencyName, str]:
        """Currency Names"""
        return self.__fixing_currency

    @fixing_currency.setter
    def fixing_currency(self, value: Union[CurrencyName, str]):
        self._property_changed('fixing_currency')
        self.__fixing_currency = get_enum_value(CurrencyName, value)        

    @property
    def fixing_currency_source(self) -> str:
        """fixing currency conversion rate source"""
        return self.__fixing_currency_source

    @fixing_currency_source.setter
    def fixing_currency_source(self, value: str):
        self._property_changed('fixing_currency_source')
        self.__fixing_currency_source = value        

    @property
    def quantity_multiplier(self) -> int:
        """quantity multiplier for driving the long/short direction of the leg"""
        return self.__quantity_multiplier

    @quantity_multiplier.setter
    def quantity_multiplier(self, value: int):
        self._property_changed('quantity_multiplier')
        self.__quantity_multiplier = value        

    @property
    def fixed_price(self) -> Union[float, str]:
        """Size of some value, i.e. notional like 1.3b, 1.5, 1000"""
        return self.__fixed_price

    @fixed_price.setter
    def fixed_price(self, value: Union[float, str]):
        self._property_changed('fixed_price')
        self.__fixed_price = value        


class CommodOption(Instrument):
        
    """Flat object representation of a commodities option"""

    @camel_case_translate
    def __init__(
        self,
        buy_sell: Union[BuySell, str] = None,
        commodity: str = None,
        commodity_reference_price: str = None,
        underlier_short_name: str = None,
        start: Union[datetime.date, str] = None,
        end: Union[datetime.date, str] = None,
        number_of_periods: int = None,
        strategy: str = None,
        quantity: Union[float, str] = None,
        quantity_unit: str = None,
        quantity_period: str = None,
        settlement: str = None,
        contract: str = None,
        floating_type: str = None,
        fixing_currency: Union[CurrencyName, str] = None,
        fixing_currency_source: str = None,
        strike: str = None,
        strike_currency: Union[CurrencyName, str] = None,
        currency_summary: Union[CurrencyName, str] = None,
        strike_unit: str = None,
        option_type: str = None,
        name: str = None
    ):        
        super().__init__()
        self.buy_sell = buy_sell
        self.commodity = commodity
        self.commodity_reference_price = commodity_reference_price
        self.underlier_short_name = underlier_short_name
        self.start = start
        self.end = end
        self.number_of_periods = number_of_periods
        self.strategy = strategy
        self.quantity = quantity
        self.quantity_unit = quantity_unit
        self.quantity_period = quantity_period
        self.settlement = settlement
        self.contract = contract
        self.floating_type = floating_type
        self.fixing_currency = fixing_currency
        self.fixing_currency_source = fixing_currency_source
        self.strike = strike
        self.strike_currency = strike_currency
        self.currency_summary = currency_summary
        self.strike_unit = strike_unit
        self.option_type = option_type
        self.name = name

    @property
    def asset_class(self) -> AssetClass:
        """Commod"""
        return AssetClass.Commod        

    @property
    def type(self) -> AssetType:
        """Option"""
        return AssetType.Option        

    @property
    def buy_sell(self) -> Union[BuySell, str]:
        """Buy or Sell side of contract"""
        return self.__buy_sell

    @buy_sell.setter
    def buy_sell(self, value: Union[BuySell, str]):
        self._property_changed('buy_sell')
        self.__buy_sell = get_enum_value(BuySell, value)        

    @property
    def commodity(self) -> str:
        """Commodity asset"""
        return self.__commodity

    @commodity.setter
    def commodity(self, value: str):
        self._property_changed('commodity')
        self.__commodity = value        

    @property
    def commodity_reference_price(self) -> str:
        """The ISDA reference price"""
        return self.__commodity_reference_price

    @commodity_reference_price.setter
    def commodity_reference_price(self, value: str):
        self._property_changed('commodity_reference_price')
        self.__commodity_reference_price = value        

    @property
    def underlier_short_name(self) -> str:
        """Plain-English underlier short name"""
        return self.__underlier_short_name

    @underlier_short_name.setter
    def underlier_short_name(self, value: str):
        self._property_changed('underlier_short_name')
        self.__underlier_short_name = value        

    @property
    def start(self) -> Union[datetime.date, str]:
        """Date or Contract Month"""
        return self.__start

    @start.setter
    def start(self, value: Union[datetime.date, str]):
        self._property_changed('start')
        self.__start = value        

    @property
    def end(self) -> Union[datetime.date, str]:
        """Date or Contract Month"""
        return self.__end

    @end.setter
    def end(self, value: Union[datetime.date, str]):
        self._property_changed('end')
        self.__end = value        

    @property
    def number_of_periods(self) -> int:
        """The number of settlement periods"""
        return self.__number_of_periods

    @number_of_periods.setter
    def number_of_periods(self, value: int):
        self._property_changed('number_of_periods')
        self.__number_of_periods = value        

    @property
    def strategy(self) -> str:
        """Option Strategy"""
        return self.__strategy

    @strategy.setter
    def strategy(self, value: str):
        self._property_changed('strategy')
        self.__strategy = value        

    @property
    def quantity(self) -> Union[float, str]:
        """Size of some value, i.e. notional like 1.3b, 1.5, 1000"""
        return self.__quantity

    @quantity.setter
    def quantity(self, value: Union[float, str]):
        self._property_changed('quantity')
        self.__quantity = value        

    @property
    def quantity_unit(self) -> str:
        """Commodity asset"""
        return self.__quantity_unit

    @quantity_unit.setter
    def quantity_unit(self, value: str):
        self._property_changed('quantity_unit')
        self.__quantity_unit = value        

    @property
    def quantity_period(self) -> str:
        """period corresponding to a quantity amount"""
        return self.__quantity_period

    @quantity_period.setter
    def quantity_period(self, value: str):
        self._property_changed('quantity_period')
        self.__quantity_period = value        

    @property
    def settlement(self) -> str:
        """read only description in plain English of settlement terms"""
        return self.__settlement

    @settlement.setter
    def settlement(self, value: str):
        self._property_changed('settlement')
        self.__settlement = value        

    @property
    def contract(self) -> str:
        """The observed contract at each pricing date e.g First Nearby, Second Nearby"""
        return self.__contract

    @contract.setter
    def contract(self, value: str):
        self._property_changed('contract')
        self.__contract = value        

    @property
    def floating_type(self) -> str:
        """The description of the averaging style"""
        return self.__floating_type

    @floating_type.setter
    def floating_type(self, value: str):
        self._property_changed('floating_type')
        self.__floating_type = value        

    @property
    def fixing_currency(self) -> Union[CurrencyName, str]:
        """Currency Names"""
        return self.__fixing_currency

    @fixing_currency.setter
    def fixing_currency(self, value: Union[CurrencyName, str]):
        self._property_changed('fixing_currency')
        self.__fixing_currency = get_enum_value(CurrencyName, value)        

    @property
    def fixing_currency_source(self) -> str:
        """fixing currency conversion rate source"""
        return self.__fixing_currency_source

    @fixing_currency_source.setter
    def fixing_currency_source(self, value: str):
        self._property_changed('fixing_currency_source')
        self.__fixing_currency_source = value        

    @property
    def strike(self) -> str:
        """strike price (e.g. 50 or 50qd)"""
        return self.__strike

    @strike.setter
    def strike(self, value: str):
        self._property_changed('strike')
        self.__strike = value        

    @property
    def strike_currency(self) -> Union[CurrencyName, str]:
        """Currency Names"""
        return self.__strike_currency

    @strike_currency.setter
    def strike_currency(self, value: Union[CurrencyName, str]):
        self._property_changed('strike_currency')
        self.__strike_currency = get_enum_value(CurrencyName, value)        

    @property
    def currency_summary(self) -> Union[CurrencyName, str]:
        """Currency Names"""
        return self.__currency_summary

    @currency_summary.setter
    def currency_summary(self, value: Union[CurrencyName, str]):
        self._property_changed('currency_summary')
        self.__currency_summary = get_enum_value(CurrencyName, value)        

    @property
    def strike_unit(self) -> str:
        """Commodity asset"""
        return self.__strike_unit

    @strike_unit.setter
    def strike_unit(self, value: str):
        self._property_changed('strike_unit')
        self.__strike_unit = value        

    @property
    def option_type(self) -> str:
        """e.g. call or put"""
        return self.__option_type

    @option_type.setter
    def option_type(self, value: str):
        self._property_changed('option_type')
        self.__option_type = value        


class CommodSwap(Instrument):
        
    """Flat representation of a commodities swap"""

    @camel_case_translate
    def __init__(
        self,
        commodity: str = None,
        commodity_reference_price: str = None,
        start: Union[datetime.date, str] = None,
        end: Union[datetime.date, str] = None,
        contract: str = None,
        number_of_periods: int = None,
        strategy: str = None,
        quantity: Union[float, str] = None,
        quantity_unit: str = None,
        quantity_period: Union[Period, str] = None,
        settlement: str = None,
        floating_type: str = None,
        fixing_currency: Union[CurrencyName, str] = None,
        fixing_currency_source: str = None,
        fixed_price: Union[float, str] = None,
        fixed_price_unit: str = None,
        name: str = None
    ):        
        super().__init__()
        self.commodity = commodity
        self.commodity_reference_price = commodity_reference_price
        self.start = start
        self.end = end
        self.contract = contract
        self.number_of_periods = number_of_periods
        self.strategy = strategy
        self.quantity = quantity
        self.quantity_unit = quantity_unit
        self.quantity_period = quantity_period
        self.settlement = settlement
        self.floating_type = floating_type
        self.fixing_currency = fixing_currency
        self.fixing_currency_source = fixing_currency_source
        self.fixed_price = fixed_price
        self.fixed_price_unit = fixed_price_unit
        self.name = name

    @property
    def asset_class(self) -> AssetClass:
        """Commod"""
        return AssetClass.Commod        

    @property
    def type(self) -> AssetType:
        """Swap"""
        return AssetType.Swap        

    @property
    def commodity(self) -> str:
        """Commodity asset"""
        return self.__commodity

    @commodity.setter
    def commodity(self, value: str):
        self._property_changed('commodity')
        self.__commodity = value        

    @property
    def commodity_reference_price(self) -> str:
        """The ISDA reference price"""
        return self.__commodity_reference_price

    @commodity_reference_price.setter
    def commodity_reference_price(self, value: str):
        self._property_changed('commodity_reference_price')
        self.__commodity_reference_price = value        

    @property
    def start(self) -> Union[datetime.date, str]:
        """Date or Contract Month"""
        return self.__start

    @start.setter
    def start(self, value: Union[datetime.date, str]):
        self._property_changed('start')
        self.__start = value        

    @property
    def end(self) -> Union[datetime.date, str]:
        """Date or Contract Month"""
        return self.__end

    @end.setter
    def end(self, value: Union[datetime.date, str]):
        self._property_changed('end')
        self.__end = value        

    @property
    def contract(self) -> str:
        """The observed contract at each pricing date e.g First Nearby, Second Nearby"""
        return self.__contract

    @contract.setter
    def contract(self, value: str):
        self._property_changed('contract')
        self.__contract = value        

    @property
    def number_of_periods(self) -> int:
        """The number of settlement periods"""
        return self.__number_of_periods

    @number_of_periods.setter
    def number_of_periods(self, value: int):
        self._property_changed('number_of_periods')
        self.__number_of_periods = value        

    @property
    def strategy(self) -> str:
        """Swap Strategy : Strip and Commodity Spread"""
        return self.__strategy

    @strategy.setter
    def strategy(self, value: str):
        self._property_changed('strategy')
        self.__strategy = value        

    @property
    def quantity(self) -> Union[float, str]:
        """Size of some value, i.e. notional like 1.3b, 1.5, 1000"""
        return self.__quantity

    @quantity.setter
    def quantity(self, value: Union[float, str]):
        self._property_changed('quantity')
        self.__quantity = value        

    @property
    def quantity_unit(self) -> str:
        """Commodity asset"""
        return self.__quantity_unit

    @quantity_unit.setter
    def quantity_unit(self, value: str):
        self._property_changed('quantity_unit')
        self.__quantity_unit = value        

    @property
    def quantity_period(self) -> Union[Period, str]:
        """A coding scheme to define a period corresponding to a quantity amount"""
        return self.__quantity_period

    @quantity_period.setter
    def quantity_period(self, value: Union[Period, str]):
        self._property_changed('quantity_period')
        self.__quantity_period = get_enum_value(Period, value)        

    @property
    def settlement(self) -> str:
        """read only description in plain English of settlement terms"""
        return self.__settlement

    @settlement.setter
    def settlement(self, value: str):
        self._property_changed('settlement')
        self.__settlement = value        

    @property
    def floating_type(self) -> str:
        """The description of the averaging style"""
        return self.__floating_type

    @floating_type.setter
    def floating_type(self, value: str):
        self._property_changed('floating_type')
        self.__floating_type = value        

    @property
    def fixing_currency(self) -> Union[CurrencyName, str]:
        """Currency Names"""
        return self.__fixing_currency

    @fixing_currency.setter
    def fixing_currency(self, value: Union[CurrencyName, str]):
        self._property_changed('fixing_currency')
        self.__fixing_currency = get_enum_value(CurrencyName, value)        

    @property
    def fixing_currency_source(self) -> str:
        """fixing currency conversion rate source"""
        return self.__fixing_currency_source

    @fixing_currency_source.setter
    def fixing_currency_source(self, value: str):
        self._property_changed('fixing_currency_source')
        self.__fixing_currency_source = value        

    @property
    def fixed_price(self) -> Union[float, str]:
        """Size of some value, i.e. notional like 1.3b, 1.5, 1000"""
        return self.__fixed_price

    @fixed_price.setter
    def fixed_price(self, value: Union[float, str]):
        self._property_changed('fixed_price')
        self.__fixed_price = value        

    @property
    def fixed_price_unit(self) -> str:
        """Commodity asset"""
        return self.__fixed_price_unit

    @fixed_price_unit.setter
    def fixed_price_unit(self, value: str):
        self._property_changed('fixed_price_unit')
        self.__fixed_price_unit = value        


class CommodVarianceSwap(Instrument):
        
    """Object representation of a commodities volitility / variance swap"""

    @camel_case_translate
    def __init__(
        self,
        side: Union[BuySell, str] = None,
        notional: float = 1,
        notional_currency: Union[Currency, str] = None,
        asset: str = None,
        asset_fixing_source: str = None,
        contract: str = None,
        fixing_currency: Union[Currency, str] = None,
        fx_fixing_source: str = None,
        settlement_date: Union[datetime.date, str] = None,
        strike: Union[float, str] = None,
        variance_convention: Union[VarianceConvention, str] = None,
        annualization_factor: float = None,
        divisor: str = None,
        start_date: datetime.date = None,
        end_date: Union[datetime.date, str] = None,
        mean_rule: Union[CommodMeanRule, str] = None,
        fixed_mean: float = None,
        first_fixing: float = None,
        name: str = None
    ):        
        super().__init__()
        self.side = side
        self.notional = notional
        self.notional_currency = notional_currency
        self.asset = asset
        self.asset_fixing_source = asset_fixing_source
        self.contract = contract
        self.fixing_currency = fixing_currency
        self.fx_fixing_source = fx_fixing_source
        self.settlement_date = settlement_date
        self.strike = strike
        self.variance_convention = variance_convention
        self.annualization_factor = annualization_factor
        self.divisor = divisor
        self.start_date = start_date
        self.end_date = end_date
        self.mean_rule = mean_rule
        self.fixed_mean = fixed_mean
        self.first_fixing = first_fixing
        self.name = name

    @property
    def asset_class(self) -> AssetClass:
        """Commod"""
        return AssetClass.Commod        

    @property
    def type(self) -> AssetType:
        """VarianceSwap"""
        return AssetType.VarianceSwap        

    @property
    def side(self) -> Union[BuySell, str]:
        """Buy or Sell side of contract"""
        return self.__side

    @side.setter
    def side(self, value: Union[BuySell, str]):
        self._property_changed('side')
        self.__side = get_enum_value(BuySell, value)        

    @property
    def notional(self) -> float:
        """The notional amount of the variance swap"""
        return self.__notional

    @notional.setter
    def notional(self, value: float):
        self._property_changed('notional')
        self.__notional = value        

    @property
    def notional_currency(self) -> Union[Currency, str]:
        """The currency of the notional amount"""
        return self.__notional_currency

    @notional_currency.setter
    def notional_currency(self, value: Union[Currency, str]):
        self._property_changed('notional_currency')
        self.__notional_currency = get_enum_value(Currency, value)        

    @property
    def asset(self) -> str:
        """Commodity asset"""
        return self.__asset

    @asset.setter
    def asset(self, value: str):
        self._property_changed('asset')
        self.__asset = value        

    @property
    def asset_fixing_source(self) -> str:
        return self.__asset_fixing_source

    @asset_fixing_source.setter
    def asset_fixing_source(self, value: str):
        self._property_changed('asset_fixing_source')
        self.__asset_fixing_source = value        

    @property
    def contract(self) -> str:
        """The contract we are observing (e.g. Z24)"""
        return self.__contract

    @contract.setter
    def contract(self, value: str):
        self._property_changed('contract')
        self.__contract = value        

    @property
    def fixing_currency(self) -> Union[Currency, str]:
        """The currency in which we observe the fix"""
        return self.__fixing_currency

    @fixing_currency.setter
    def fixing_currency(self, value: Union[Currency, str]):
        self._property_changed('fixing_currency')
        self.__fixing_currency = get_enum_value(Currency, value)        

    @property
    def fx_fixing_source(self) -> str:
        """The source to use in the condition that the fixing currency is different from
           the underlying currency"""
        return self.__fx_fixing_source

    @fx_fixing_source.setter
    def fx_fixing_source(self, value: str):
        self._property_changed('fx_fixing_source')
        self.__fx_fixing_source = value        

    @property
    def settlement_date(self) -> Union[datetime.date, str]:
        """Settlement date of the trade"""
        return self.__settlement_date

    @settlement_date.setter
    def settlement_date(self, value: Union[datetime.date, str]):
        self._property_changed('settlement_date')
        self.__settlement_date = value        

    @property
    def strike(self) -> Union[float, str]:
        """The strike in variance, default to ATM"""
        return self.__strike

    @strike.setter
    def strike(self, value: Union[float, str]):
        self._property_changed('strike')
        self.__strike = value        

    @property
    def variance_convention(self) -> Union[VarianceConvention, str]:
        """'Annualized' to annualize the variance (using Annualization Factor) or 'total'
           for no annualization'"""
        return self.__variance_convention

    @variance_convention.setter
    def variance_convention(self, value: Union[VarianceConvention, str]):
        self._property_changed('variance_convention')
        self.__variance_convention = get_enum_value(VarianceConvention, value)        

    @property
    def annualization_factor(self) -> float:
        """Annualization factor used to compute variance, defaults to 252"""
        return self.__annualization_factor

    @annualization_factor.setter
    def annualization_factor(self, value: float):
        self._property_changed('annualization_factor')
        self.__annualization_factor = value        

    @property
    def divisor(self) -> str:
        """Number of returns or Number of returns - 1"""
        return self.__divisor

    @divisor.setter
    def divisor(self, value: str):
        self._property_changed('divisor')
        self.__divisor = value        

    @property
    def start_date(self) -> datetime.date:
        """The start date of the observation"""
        return self.__start_date

    @start_date.setter
    def start_date(self, value: datetime.date):
        self._property_changed('start_date')
        self.__start_date = value        

    @property
    def end_date(self) -> Union[datetime.date, str]:
        """The end date of the observation"""
        return self.__end_date

    @end_date.setter
    def end_date(self, value: Union[datetime.date, str]):
        self._property_changed('end_date')
        self.__end_date = value        

    @property
    def mean_rule(self) -> Union[CommodMeanRule, str]:
        """Commodity mean rule"""
        return self.__mean_rule

    @mean_rule.setter
    def mean_rule(self, value: Union[CommodMeanRule, str]):
        self._property_changed('mean_rule')
        self.__mean_rule = get_enum_value(CommodMeanRule, value)        

    @property
    def fixed_mean(self) -> float:
        """True if we want to specify the mean to be used in variance computation"""
        return self.__fixed_mean

    @fixed_mean.setter
    def fixed_mean(self, value: float):
        self._property_changed('fixed_mean')
        self.__fixed_mean = value        

    @property
    def first_fixing(self) -> float:
        """By default there is none, if populate would use it as the first fixing"""
        return self.__first_fixing

    @first_fixing.setter
    def first_fixing(self, value: float):
        self._property_changed('first_fixing')
        self.__first_fixing = value        


[docs]class EqCliquet(Instrument): """Object representation of an Equity Cliquet""" @camel_case_translate def __init__( self, underlier: Union[float, str] = None, underlier_type: Union[UnderlierType, str] = None, expiration_date: Union[datetime.date, str] = None, strike_price: float = None, currency: Union[Currency, str] = None, first_valuation_date: datetime.date = None, global_floor: float = -1000000, global_cap: float = 1000000, last_valuation_date: datetime.date = None, notional_amount: Union[float, str] = None, payment_frequency: str = 'Maturity', return_style: str = 'Rate of Return', return_type: str = 'Sum', valuation_period: str = None, name: str = None ): super().__init__() self.underlier = underlier self.underlier_type = underlier_type self.expiration_date = expiration_date self.strike_price = strike_price self.currency = currency self.first_valuation_date = first_valuation_date self.global_floor = global_floor self.global_cap = global_cap self.last_valuation_date = last_valuation_date self.notional_amount = notional_amount self.payment_frequency = payment_frequency self.return_style = return_style self.return_type = return_type self.valuation_period = valuation_period self.name = name @property def asset_class(self) -> AssetClass: """Equity""" return AssetClass.Equity @property def type(self) -> AssetType: """Cliquet""" return AssetType.Cliquet @property def underlier(self) -> Union[float, str]: """Underlier security identifier""" return self.__underlier @underlier.setter def underlier(self, value: Union[float, str]): self._property_changed('underlier') self.__underlier = value @property def underlier_type(self) -> Union[UnderlierType, str]: """Type of underlyer""" return self.__underlier_type @underlier_type.setter def underlier_type(self, value: Union[UnderlierType, str]): self._property_changed('underlier_type') self.__underlier_type = get_enum_value(UnderlierType, value) @property def expiration_date(self) -> Union[datetime.date, str]: """Date or tenor, e.g. 2018-09-03, 3m, Dec21""" return self.__expiration_date @expiration_date.setter def expiration_date(self, value: Union[datetime.date, str]): self._property_changed('expiration_date') self.__expiration_date = value @property def strike_price(self) -> float: """Strike price as value""" return self.__strike_price @strike_price.setter def strike_price(self, value: float): self._property_changed('strike_price') self.__strike_price = value @property def currency(self) -> Union[Currency, str]: """Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)""" return self.__currency @currency.setter def currency(self, value: Union[Currency, str]): self._property_changed('currency') self.__currency = get_enum_value(Currency, value) @property def first_valuation_date(self) -> datetime.date: """ISO 8601-formatted date""" return self.__first_valuation_date @first_valuation_date.setter def first_valuation_date(self, value: datetime.date): self._property_changed('first_valuation_date') self.__first_valuation_date = value @property def global_floor(self) -> float: """Global Floor of return, relevant only if paying at maturity""" return self.__global_floor @global_floor.setter def global_floor(self, value: float): self._property_changed('global_floor') self.__global_floor = value @property def global_cap(self) -> float: """Global Cap of return, relevant only if paying at maturity""" return self.__global_cap @global_cap.setter def global_cap(self, value: float): self._property_changed('global_cap') self.__global_cap = value @property def last_valuation_date(self) -> datetime.date: """ISO 8601-formatted date""" return self.__last_valuation_date @last_valuation_date.setter def last_valuation_date(self, value: datetime.date): self._property_changed('last_valuation_date') self.__last_valuation_date = value @property def notional_amount(self) -> Union[float, str]: """Notional of this position""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: Union[float, str]): self._property_changed('notional_amount') self.__notional_amount = value @property def payment_frequency(self) -> str: return self.__payment_frequency @payment_frequency.setter def payment_frequency(self, value: str): self._property_changed('payment_frequency') self.__payment_frequency = value @property def return_style(self) -> str: """Return calculation style""" return self.__return_style @return_style.setter def return_style(self, value: str): self._property_changed('return_style') self.__return_style = value @property def return_type(self) -> str: """Sum or Product of periodic return, relevant only if paying at maturity""" return self.__return_type @return_type.setter def return_type(self, value: str): self._property_changed('return_type') self.__return_type = value @property def valuation_period(self) -> str: """Tenor""" return self.__valuation_period @valuation_period.setter def valuation_period(self, value: str): self._property_changed('valuation_period') self.__valuation_period = value
[docs]class EqForward(Instrument): """Object representation of an equity forward""" @camel_case_translate def __init__( self, underlier: Union[float, str] = None, underlier_type: Union[UnderlierType, str] = None, expiration_date: Union[datetime.date, str] = None, forward_price: float = None, number_of_shares: int = 1, name: str = None ): super().__init__() self.underlier = underlier self.underlier_type = underlier_type self.expiration_date = expiration_date self.forward_price = forward_price self.number_of_shares = number_of_shares self.name = name @property def asset_class(self) -> AssetClass: """Equity""" return AssetClass.Equity @property def type(self) -> AssetType: """Forward""" return AssetType.Forward @property def underlier(self) -> Union[float, str]: """Underlier security identifier""" return self.__underlier @underlier.setter def underlier(self, value: Union[float, str]): self._property_changed('underlier') self.__underlier = value @property def underlier_type(self) -> Union[UnderlierType, str]: """Type of underlyer""" return self.__underlier_type @underlier_type.setter def underlier_type(self, value: Union[UnderlierType, str]): self._property_changed('underlier_type') self.__underlier_type = get_enum_value(UnderlierType, value) @property def expiration_date(self) -> Union[datetime.date, str]: """Date or tenor, e.g. 2018-09-03, 3m, Dec21""" return self.__expiration_date @expiration_date.setter def expiration_date(self, value: Union[datetime.date, str]): self._property_changed('expiration_date') self.__expiration_date = value @property def forward_price(self) -> float: """Forward price""" return self.__forward_price @forward_price.setter def forward_price(self, value: float): self._property_changed('forward_price') self.__forward_price = value @property def number_of_shares(self) -> int: """Number of shares""" return self.__number_of_shares @number_of_shares.setter def number_of_shares(self, value: int): self._property_changed('number_of_shares') self.__number_of_shares = value
[docs]class EqOption(Instrument): """Instrument definition for equity option""" @camel_case_translate def __init__( self, underlier: Union[float, str] = None, expiration_date: Union[datetime.date, str] = None, strike_price: Union[float, str] = None, option_type: Union[OptionType, str] = None, option_style: Union[OptionStyle, str] = None, number_of_options: float = None, exchange: str = None, multiplier: float = None, settlement_date: Union[datetime.date, str] = None, settlement_currency: Union[Currency, str] = None, premium: float = 0, premium_payment_date: Union[datetime.date, str] = None, valuation_time: Union[ValuationTime, str] = None, method_of_settlement: Union[OptionSettlementMethod, str] = None, underlier_type: Union[UnderlierType, str] = None, buy_sell: Union[BuySell, str] = None, premium_currency: Union[Currency, str] = None, trade_as: Union[TradeAs, str] = None, name: str = None ): super().__init__() self.underlier = underlier self.expiration_date = expiration_date self.strike_price = strike_price self.option_type = option_type self.option_style = option_style self.number_of_options = number_of_options self.exchange = exchange self.multiplier = multiplier self.settlement_date = settlement_date self.settlement_currency = settlement_currency self.premium = premium self.premium_payment_date = premium_payment_date self.valuation_time = valuation_time self.method_of_settlement = method_of_settlement self.underlier_type = underlier_type self.buy_sell = buy_sell self.premium_currency = premium_currency self.trade_as = trade_as self.name = name @property def asset_class(self) -> AssetClass: """Equity""" return AssetClass.Equity @property def type(self) -> AssetType: """Option""" return AssetType.Option @property def underlier(self) -> Union[float, str]: """Underlier security identifier""" return self.__underlier @underlier.setter def underlier(self, value: Union[float, str]): self._property_changed('underlier') self.__underlier = value @property def expiration_date(self) -> Union[datetime.date, str]: """Date or tenor, e.g. 2018-09-03, 3m, Dec21""" return self.__expiration_date @expiration_date.setter def expiration_date(self, value: Union[datetime.date, str]): self._property_changed('expiration_date') self.__expiration_date = value @property def strike_price(self) -> Union[float, str]: """Strike as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF, 10/vol, 100k/pv, p=10000, p=10000USD, $200K/BP, or multiple strikes 65.4/-45.8""" return self.__strike_price @strike_price.setter def strike_price(self, value: Union[float, str]): self._property_changed('strike_price') self.__strike_price = value @property def option_type(self) -> Union[OptionType, str]: """Option Type""" return self.__option_type @option_type.setter def option_type(self, value: Union[OptionType, str]): self._property_changed('option_type') self.__option_type = get_enum_value(OptionType, value) @property def option_style(self) -> Union[OptionStyle, str]: """Option Exercise Style""" return self.__option_style @option_style.setter def option_style(self, value: Union[OptionStyle, str]): self._property_changed('option_style') self.__option_style = get_enum_value(OptionStyle, value) @property def number_of_options(self) -> float: """Number of options""" return self.__number_of_options @number_of_options.setter def number_of_options(self, value: float): self._property_changed('number_of_options') self.__number_of_options = value @property def exchange(self) -> str: """Name of marketplace where security, derivative or other instrument is traded""" return self.__exchange @exchange.setter def exchange(self, value: str): self._property_changed('exchange') self.__exchange = value @property def multiplier(self) -> float: """Number of stock units per option contract""" return self.__multiplier @multiplier.setter def multiplier(self, value: float): self._property_changed('multiplier') self.__multiplier = value @property def settlement_date(self) -> Union[datetime.date, str]: """Date or tenor, e.g. 2018-09-03, 3m, Dec21""" return self.__settlement_date @settlement_date.setter def settlement_date(self, value: Union[datetime.date, str]): self._property_changed('settlement_date') self.__settlement_date = value @property def settlement_currency(self) -> Union[Currency, str]: """Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)""" return self.__settlement_currency @settlement_currency.setter def settlement_currency(self, value: Union[Currency, str]): self._property_changed('settlement_currency') self.__settlement_currency = get_enum_value(Currency, value) @property def premium(self) -> float: """Option premium""" return self.__premium @premium.setter def premium(self, value: float): self._property_changed('premium') self.__premium = value @property def premium_payment_date(self) -> Union[datetime.date, str]: """Option premium""" return self.__premium_payment_date @premium_payment_date.setter def premium_payment_date(self, value: Union[datetime.date, str]): self._property_changed('premium_payment_date') self.__premium_payment_date = value @property def valuation_time(self) -> Union[ValuationTime, str]: """Valuation time (e.g. MktClose, MktOpen) of the underlying level for exercise""" return self.__valuation_time @valuation_time.setter def valuation_time(self, value: Union[ValuationTime, str]): self._property_changed('valuation_time') self.__valuation_time = get_enum_value(ValuationTime, value) @property def method_of_settlement(self) -> Union[OptionSettlementMethod, str]: """How the option is settled (e.g. Cash, Physical)""" return self.__method_of_settlement @method_of_settlement.setter def method_of_settlement(self, value: Union[OptionSettlementMethod, str]): self._property_changed('method_of_settlement') self.__method_of_settlement = get_enum_value(OptionSettlementMethod, value) @property def underlier_type(self) -> Union[UnderlierType, str]: """Type of underlyer""" return self.__underlier_type @underlier_type.setter def underlier_type(self, value: Union[UnderlierType, str]): self._property_changed('underlier_type') self.__underlier_type = get_enum_value(UnderlierType, value) @property def buy_sell(self) -> Union[BuySell, str]: """Buy or Sell side of contract""" return self.__buy_sell @buy_sell.setter def buy_sell(self, value: Union[BuySell, str]): self._property_changed('buy_sell') self.__buy_sell = get_enum_value(BuySell, value) @property def premium_currency(self) -> Union[Currency, str]: """Currency of the option premium""" return self.__premium_currency @premium_currency.setter def premium_currency(self, value: Union[Currency, str]): self._property_changed('premium_currency') self.__premium_currency = get_enum_value(Currency, value) @property def trade_as(self) -> Union[TradeAs, str]: """Option trade as (i.e. listed, otc, lookalike etc)""" return self.__trade_as @trade_as.setter def trade_as(self, value: Union[TradeAs, str]): self._property_changed('trade_as') self.__trade_as = get_enum_value(TradeAs, value)
class EqOptionLeg(Instrument): """Instrument definition for equity option leg""" @camel_case_translate def __init__( self, expiration_date: Union[datetime.date, str] = None, strike_price: Union[float, str] = None, option_type: Union[OptionType, str] = None, option_style: Union[OptionStyle, str] = None, number_of_options: float = None, exchange: str = None, multiplier: float = None, settlement_date: Union[datetime.date, str] = None, settlement_currency: Union[Currency, str] = None, premium: float = None, premium_payment_date: Union[datetime.date, str] = None, valuation_time: Union[ValuationTime, str] = None, method_of_settlement: Union[OptionSettlementMethod, str] = None, buy_sell: Union[BuySell, str] = None, premium_currency: Union[Currency, str] = None, trade_as: Union[TradeAs, str] = None, name: str = None ): super().__init__() self.expiration_date = expiration_date self.strike_price = strike_price self.option_type = option_type self.option_style = option_style self.number_of_options = number_of_options self.exchange = exchange self.multiplier = multiplier self.settlement_date = settlement_date self.settlement_currency = settlement_currency self.premium = premium self.premium_payment_date = premium_payment_date self.valuation_time = valuation_time self.method_of_settlement = method_of_settlement self.buy_sell = buy_sell self.premium_currency = premium_currency self.trade_as = trade_as self.name = name @property def asset_class(self) -> AssetClass: """Equity""" return AssetClass.Equity @property def type(self) -> AssetType: """OptionLeg""" return AssetType.OptionLeg @property def expiration_date(self) -> Union[datetime.date, str]: """Date or tenor, e.g. 2018-09-03, 3m, Dec21""" return self.__expiration_date @expiration_date.setter def expiration_date(self, value: Union[datetime.date, str]): self._property_changed('expiration_date') self.__expiration_date = value @property def strike_price(self) -> Union[float, str]: """Strike as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF, 10/vol, 100k/pv, p=10000, p=10000USD, $200K/BP, or multiple strikes 65.4/-45.8""" return self.__strike_price @strike_price.setter def strike_price(self, value: Union[float, str]): self._property_changed('strike_price') self.__strike_price = value @property def option_type(self) -> Union[OptionType, str]: """Option Type""" return self.__option_type @option_type.setter def option_type(self, value: Union[OptionType, str]): self._property_changed('option_type') self.__option_type = get_enum_value(OptionType, value) @property def option_style(self) -> Union[OptionStyle, str]: """Option Exercise Style""" return self.__option_style @option_style.setter def option_style(self, value: Union[OptionStyle, str]): self._property_changed('option_style') self.__option_style = get_enum_value(OptionStyle, value) @property def number_of_options(self) -> float: """Number of options""" return self.__number_of_options @number_of_options.setter def number_of_options(self, value: float): self._property_changed('number_of_options') self.__number_of_options = value @property def exchange(self) -> str: """Name of marketplace where security, derivative or other instrument is traded""" return self.__exchange @exchange.setter def exchange(self, value: str): self._property_changed('exchange') self.__exchange = value @property def multiplier(self) -> float: """Number of stock units per option contract""" return self.__multiplier @multiplier.setter def multiplier(self, value: float): self._property_changed('multiplier') self.__multiplier = value @property def settlement_date(self) -> Union[datetime.date, str]: """Date or tenor, e.g. 2018-09-03, 3m, Dec21""" return self.__settlement_date @settlement_date.setter def settlement_date(self, value: Union[datetime.date, str]): self._property_changed('settlement_date') self.__settlement_date = value @property def settlement_currency(self) -> Union[Currency, str]: """Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)""" return self.__settlement_currency @settlement_currency.setter def settlement_currency(self, value: Union[Currency, str]): self._property_changed('settlement_currency') self.__settlement_currency = get_enum_value(Currency, value) @property def premium(self) -> float: """Option premium""" return self.__premium @premium.setter def premium(self, value: float): self._property_changed('premium') self.__premium = value @property def premium_payment_date(self) -> Union[datetime.date, str]: """Option premium""" return self.__premium_payment_date @premium_payment_date.setter def premium_payment_date(self, value: Union[datetime.date, str]): self._property_changed('premium_payment_date') self.__premium_payment_date = value @property def valuation_time(self) -> Union[ValuationTime, str]: """Valuation time (e.g. MktClose, MktOpen) of the underlying level for exercise""" return self.__valuation_time @valuation_time.setter def valuation_time(self, value: Union[ValuationTime, str]): self._property_changed('valuation_time') self.__valuation_time = get_enum_value(ValuationTime, value) @property def method_of_settlement(self) -> Union[OptionSettlementMethod, str]: """How the option is settled (e.g. Cash, Physical)""" return self.__method_of_settlement @method_of_settlement.setter def method_of_settlement(self, value: Union[OptionSettlementMethod, str]): self._property_changed('method_of_settlement') self.__method_of_settlement = get_enum_value(OptionSettlementMethod, value) @property def buy_sell(self) -> Union[BuySell, str]: """Buy or Sell side of contract""" return self.__buy_sell @buy_sell.setter def buy_sell(self, value: Union[BuySell, str]): self._property_changed('buy_sell') self.__buy_sell = get_enum_value(BuySell, value) @property def premium_currency(self) -> Union[Currency, str]: """Currency of the option premium""" return self.__premium_currency @premium_currency.setter def premium_currency(self, value: Union[Currency, str]): self._property_changed('premium_currency') self.__premium_currency = get_enum_value(Currency, value) @property def trade_as(self) -> Union[TradeAs, str]: """Option trade as (i.e. listed, otc, lookalike etc)""" return self.__trade_as @trade_as.setter def trade_as(self, value: Union[TradeAs, str]): self._property_changed('trade_as') self.__trade_as = get_enum_value(TradeAs, value) class EqStock(Instrument): """Instrument definition for equities""" @camel_case_translate def __init__( self, asset_name: str = None, buy_sell: Union[BuySell, str] = None, premium: float = None, premium_currency: str = None, quantity: float = None, settlement_date: datetime.date = None, name: str = None ): super().__init__() self.asset_name = asset_name self.buy_sell = buy_sell self.premium = premium self.premium_currency = premium_currency self.quantity = quantity self.settlement_date = settlement_date self.name = name @property def asset_class(self) -> AssetClass: """Equity""" return AssetClass.Equity @property def type(self) -> AssetType: """Single Stock""" return AssetType.Single_Stock @property def asset_name(self) -> str: return self.__asset_name @asset_name.setter def asset_name(self, value: str): self._property_changed('asset_name') self.__asset_name = value @property def buy_sell(self) -> Union[BuySell, str]: """Buy or Sell side of contract""" return self.__buy_sell @buy_sell.setter def buy_sell(self, value: Union[BuySell, str]): self._property_changed('buy_sell') self.__buy_sell = get_enum_value(BuySell, value) @property def premium(self) -> float: return self.__premium @premium.setter def premium(self, value: float): self._property_changed('premium') self.__premium = value @property def premium_currency(self) -> str: return self.__premium_currency @premium_currency.setter def premium_currency(self, value: str): self._property_changed('premium_currency') self.__premium_currency = value @property def quantity(self) -> float: return self.__quantity @quantity.setter def quantity(self, value: float): self._property_changed('quantity') self.__quantity = value @property def settlement_date(self) -> datetime.date: return self.__settlement_date @settlement_date.setter def settlement_date(self, value: datetime.date): self._property_changed('settlement_date') self.__settlement_date = value
[docs]class EqSynthetic(Instrument): """Instrument definition for equity synthetics""" @camel_case_translate def __init__( self, underlier: Union[float, str], expiry: str, currency: Union[Currency, str] = None, swap_type: str = 'Eq Swap', buy_sell: Union[BuySell, str] = None, underlier_type: Union[UnderlierType, str] = None, effective_date: datetime.date = None, num_of_underlyers: float = None, name: str = None ): super().__init__() self.underlier = underlier self.currency = currency self.swap_type = swap_type self.buy_sell = buy_sell self.underlier_type = underlier_type self.effective_date = effective_date self.num_of_underlyers = num_of_underlyers self.expiry = expiry self.name = name @property def asset_class(self) -> AssetClass: """Equity""" return AssetClass.Equity @property def type(self) -> AssetType: """Synthetic""" return AssetType.Synthetic @property def underlier(self) -> Union[float, str]: """Underlier security identifier""" return self.__underlier @underlier.setter def underlier(self, value: Union[float, str]): self._property_changed('underlier') self.__underlier = value @property def currency(self) -> Union[Currency, str]: """Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)""" return self.__currency @currency.setter def currency(self, value: Union[Currency, str]): self._property_changed('currency') self.__currency = get_enum_value(Currency, value) @property def swap_type(self) -> str: return self.__swap_type @swap_type.setter def swap_type(self, value: str): self._property_changed('swap_type') self.__swap_type = value @property def buy_sell(self) -> Union[BuySell, str]: """Buy or Sell side of contract""" return self.__buy_sell @buy_sell.setter def buy_sell(self, value: Union[BuySell, str]): self._property_changed('buy_sell') self.__buy_sell = get_enum_value(BuySell, value) @property def underlier_type(self) -> Union[UnderlierType, str]: """Type of underlyer""" return self.__underlier_type @underlier_type.setter def underlier_type(self, value: Union[UnderlierType, str]): self._property_changed('underlier_type') self.__underlier_type = get_enum_value(UnderlierType, value) @property def effective_date(self) -> datetime.date: """The date on which the synthetic becomes effective""" return self.__effective_date @effective_date.setter def effective_date(self, value: datetime.date): self._property_changed('effective_date') self.__effective_date = value @property def num_of_underlyers(self) -> float: """number of underlyers referenced in synthetic contract""" return self.__num_of_underlyers @num_of_underlyers.setter def num_of_underlyers(self, value: float): self._property_changed('num_of_underlyers') self.__num_of_underlyers = value @property def expiry(self) -> str: """Tenor""" return self.__expiry @expiry.setter def expiry(self, value: str): self._property_changed('expiry') self.__expiry = value
[docs]class EqVarianceSwap(Instrument): """Instrument definition for equity variance swap""" @camel_case_translate def __init__( self, underlier: Union[float, str] = None, underlier_type: Union[UnderlierType, str] = None, expiration_date: Union[datetime.date, str] = None, strike_price: Union[float, str] = None, variance_cap: float = None, settlement_date: Union[datetime.date, str] = None, premium: Union[float, str] = None, name: str = None ): super().__init__() self.underlier = underlier self.underlier_type = underlier_type self.expiration_date = expiration_date self.strike_price = strike_price self.variance_cap = variance_cap self.settlement_date = settlement_date self.premium = premium self.name = name @property def asset_class(self) -> AssetClass: """Equity""" return AssetClass.Equity @property def type(self) -> AssetType: """VarianceSwap""" return AssetType.VarianceSwap @property def underlier(self) -> Union[float, str]: """Underlier security identifier""" return self.__underlier @underlier.setter def underlier(self, value: Union[float, str]): self._property_changed('underlier') self.__underlier = value @property def underlier_type(self) -> Union[UnderlierType, str]: """Type of underlyer""" return self.__underlier_type @underlier_type.setter def underlier_type(self, value: Union[UnderlierType, str]): self._property_changed('underlier_type') self.__underlier_type = get_enum_value(UnderlierType, value) @property def expiration_date(self) -> Union[datetime.date, str]: """Date or tenor, e.g. 2018-09-03, 3m""" return self.__expiration_date @expiration_date.setter def expiration_date(self, value: Union[datetime.date, str]): self._property_changed('expiration_date') self.__expiration_date = value @property def strike_price(self) -> Union[float, str]: """Variance strike as value or percentage string e.g. 62.5, 95%""" return self.__strike_price @strike_price.setter def strike_price(self, value: Union[float, str]): self._property_changed('strike_price') self.__strike_price = value @property def variance_cap(self) -> float: """Variance Cap as absolute value""" return self.__variance_cap @variance_cap.setter def variance_cap(self, value: float): self._property_changed('variance_cap') self.__variance_cap = value @property def settlement_date(self) -> Union[datetime.date, str]: """Settlement date""" return self.__settlement_date @settlement_date.setter def settlement_date(self, value: Union[datetime.date, str]): self._property_changed('settlement_date') self.__settlement_date = value @property def premium(self) -> Union[float, str]: """VarSwap premium""" return self.__premium @premium.setter def premium(self, value: Union[float, str]): self._property_changed('premium') self.__premium = value
class FRA(Instrument): """A forward rate agreement""" @camel_case_translate def __init__( self, buy_sell: Union[BuySell, str] = None, clearing_house: Union[SwapClearingHouse, str] = None, clearing_legally_binding: float = None, day_count_fraction: Union[DayCountFraction, str] = None, fee: float = 0, fee_currency: Union[Currency, str] = None, fee_payment_date: Union[datetime.date, str] = None, fixed_rate: Union[float, str] = None, frequency: str = None, calendar: str = None, rate_option: str = None, maturity: Union[Union[datetime.date, str], str] = None, notional_currency: Union[Currency, str] = None, payment_delay: str = None, roll_convention: str = None, notional_amount: Union[float, str] = None, spread: Union[float, str] = None, effective_date: Union[Union[datetime.date, str], str] = None, name: str = None ): super().__init__() self.buy_sell = buy_sell self.clearing_house = clearing_house self.clearing_legally_binding = clearing_legally_binding self.day_count_fraction = day_count_fraction self.fee = fee self.fee_currency = fee_currency self.fee_payment_date = fee_payment_date self.fixed_rate = fixed_rate self.frequency = frequency self.calendar = calendar self.rate_option = rate_option self.maturity = maturity self.notional_currency = notional_currency self.payment_delay = payment_delay self.roll_convention = roll_convention self.notional_amount = notional_amount self.spread = spread self.effective_date = effective_date self.name = name @property def asset_class(self) -> AssetClass: """Rates""" return AssetClass.Rates @property def type(self) -> AssetType: """FRA""" return AssetType.FRA @property def buy_sell(self) -> Union[BuySell, str]: """Buy or Sell side of contract""" return self.__buy_sell @buy_sell.setter def buy_sell(self, value: Union[BuySell, str]): self._property_changed('buy_sell') self.__buy_sell = get_enum_value(BuySell, value) @property def clearing_house(self) -> Union[SwapClearingHouse, str]: """Swap Clearing House""" return self.__clearing_house @clearing_house.setter def clearing_house(self, value: Union[SwapClearingHouse, str]): self._property_changed('clearing_house') self.__clearing_house = get_enum_value(SwapClearingHouse, value) @property def clearing_legally_binding(self) -> float: return self.__clearing_legally_binding @clearing_legally_binding.setter def clearing_legally_binding(self, value: float): self._property_changed('clearing_legally_binding') self.__clearing_legally_binding = value @property def day_count_fraction(self) -> Union[DayCountFraction, str]: """The day count fraction""" return self.__day_count_fraction @day_count_fraction.setter def day_count_fraction(self, value: Union[DayCountFraction, str]): self._property_changed('day_count_fraction') self.__day_count_fraction = get_enum_value(DayCountFraction, value) @property def fee(self) -> float: """The fee""" return self.__fee @fee.setter def fee(self, value: float): self._property_changed('fee') self.__fee = value @property def fee_currency(self) -> Union[Currency, str]: """Currency of the fee""" return self.__fee_currency @fee_currency.setter def fee_currency(self, value: Union[Currency, str]): self._property_changed('fee_currency') self.__fee_currency = get_enum_value(Currency, value) @property def fee_payment_date(self) -> Union[datetime.date, str]: """Payment date of the fee""" return self.__fee_payment_date @fee_payment_date.setter def fee_payment_date(self, value: Union[datetime.date, str]): self._property_changed('fee_payment_date') self.__fee_payment_date = value @property def fixed_rate(self) -> Union[float, str]: """The forward rate""" return self.__fixed_rate @fixed_rate.setter def fixed_rate(self, value: Union[float, str]): self._property_changed('fixed_rate') self.__fixed_rate = value @property def frequency(self) -> str: """The frequency of floating payments, e.g. 3m""" return self.__frequency @frequency.setter def frequency(self, value: str): self._property_changed('frequency') self.__frequency = value @property def calendar(self) -> str: """The calendar""" return self.__calendar @calendar.setter def calendar(self, value: str): self._property_changed('calendar') self.__calendar = value @property def rate_option(self) -> str: """The underlying benchmark for the floating rate, e.g. USD-LIBOR-BBA, EUR-EURIBOR- TELERATE""" return self.__rate_option @rate_option.setter def rate_option(self, value: str): self._property_changed('rate_option') self.__rate_option = value @property def maturity(self) -> Union[Union[datetime.date, str], str]: """The maturity of the FRA, e.g. 2050-04-01, 10y""" return self.__maturity @maturity.setter def maturity(self, value: Union[Union[datetime.date, str], str]): self._property_changed('maturity') self.__maturity = value @property def notional_currency(self) -> Union[Currency, str]: """Notional currency""" return self.__notional_currency @notional_currency.setter def notional_currency(self, value: Union[Currency, str]): self._property_changed('notional_currency') self.__notional_currency = get_enum_value(Currency, value) @property def payment_delay(self) -> str: """The delay of payments""" return self.__payment_delay @payment_delay.setter def payment_delay(self, value: str): self._property_changed('payment_delay') self.__payment_delay = value @property def roll_convention(self) -> str: """The roll convention""" return self.__roll_convention @roll_convention.setter def roll_convention(self, value: str): self._property_changed('roll_convention') self.__roll_convention = value @property def notional_amount(self) -> Union[float, str]: """Notional amount""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: Union[float, str]): self._property_changed('notional_amount') self.__notional_amount = value @property def spread(self) -> Union[float, str]: """The spread over the floating rate""" return self.__spread @spread.setter def spread(self, value: Union[float, str]): self._property_changed('spread') self.__spread = value @property def effective_date(self) -> Union[Union[datetime.date, str], str]: """The date on which the FRA becomes effective""" return self.__effective_date @effective_date.setter def effective_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('effective_date') self.__effective_date = value
[docs]class FXBinary(Instrument): """Object representation of a FX binary option""" @camel_case_translate def __init__( self, pair: str = None, buy_sell: Union[BuySell, str] = None, option_type: Union[OptionType, str] = None, notional_amount: Union[float, str] = None, notional_currency: Union[Currency, str] = None, strike_price: Union[float, str] = None, settlement_date: Union[datetime.date, str] = None, expiration_date: Union[datetime.date, str] = None, expiration_time: str = None, premium: Union[float, str] = None, premium_currency: Union[Currency, str] = None, premium_payment_date: str = None, fixing_source: str = None, name: str = None ): super().__init__() self.pair = pair self.buy_sell = buy_sell self.option_type = option_type self.notional_amount = notional_amount self.notional_currency = notional_currency self.strike_price = strike_price self.settlement_date = settlement_date self.expiration_date = expiration_date self.expiration_time = expiration_time self.premium = premium self.premium_currency = premium_currency self.premium_payment_date = premium_payment_date self.fixing_source = fixing_source self.name = name @property def asset_class(self) -> AssetClass: """FX""" return AssetClass.FX @property def type(self) -> AssetType: """Binary""" return AssetType.Binary @property def pair(self) -> str: """A currency pair, e.g.: EURUSD or EUR USD""" return self.__pair @pair.setter def pair(self, value: str): self._property_changed('pair') self.__pair = value @property def buy_sell(self) -> Union[BuySell, str]: """Buy or Sell side of contract""" return self.__buy_sell @buy_sell.setter def buy_sell(self, value: Union[BuySell, str]): self._property_changed('buy_sell') self.__buy_sell = get_enum_value(BuySell, value) @property def option_type(self) -> Union[OptionType, str]: """Option Type""" return self.__option_type @option_type.setter def option_type(self, value: Union[OptionType, str]): self._property_changed('option_type') self.__option_type = get_enum_value(OptionType, value) @property def notional_amount(self) -> Union[float, str]: """Notional amount""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: Union[float, str]): self._property_changed('notional_amount') self.__notional_amount = value @property def notional_currency(self) -> Union[Currency, str]: """Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)""" return self.__notional_currency @notional_currency.setter def notional_currency(self, value: Union[Currency, str]): self._property_changed('notional_currency') self.__notional_currency = get_enum_value(Currency, value) @property def strike_price(self) -> Union[float, str]: """Strike as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF, 10/vol, 100k/pv, p=10000, p=10000USD, $200K/BP, or multiple strikes 65.4/-45.8""" return self.__strike_price @strike_price.setter def strike_price(self, value: Union[float, str]): self._property_changed('strike_price') self.__strike_price = value @property def settlement_date(self) -> Union[datetime.date, str]: """Settlement date of the option, after expiration""" return self.__settlement_date @settlement_date.setter def settlement_date(self, value: Union[datetime.date, str]): self._property_changed('settlement_date') self.__settlement_date = value @property def expiration_date(self) -> Union[datetime.date, str]: """Date or tenor, e.g. 2018-09-03, 3m, Dec21""" return self.__expiration_date @expiration_date.setter def expiration_date(self, value: Union[datetime.date, str]): self._property_changed('expiration_date') self.__expiration_date = value @property def expiration_time(self) -> str: """The location and (optionally) time of spot for expiration""" return self.__expiration_time @expiration_time.setter def expiration_time(self, value: str): self._property_changed('expiration_time') self.__expiration_time = value @property def premium(self) -> Union[float, str]: """Option premium""" return self.__premium @premium.setter def premium(self, value: Union[float, str]): self._property_changed('premium') self.__premium = value @property def premium_currency(self) -> Union[Currency, str]: """Currency of the option premium""" return self.__premium_currency @premium_currency.setter def premium_currency(self, value: Union[Currency, str]): self._property_changed('premium_currency') self.__premium_currency = get_enum_value(Currency, value) @property def premium_payment_date(self) -> str: """Payment date of the option premium""" return self.__premium_payment_date @premium_payment_date.setter def premium_payment_date(self, value: str): self._property_changed('premium_payment_date') self.__premium_payment_date = value @property def fixing_source(self) -> str: """The data source to be used for observation of FX spot on the fixing date""" return self.__fixing_source @fixing_source.setter def fixing_source(self, value: str): self._property_changed('fixing_source') self.__fixing_source = value
[docs]class FXForward(Instrument): """Object representation of an FX forward""" @camel_case_translate def __init__( self, pair: str = None, settlement_date: Union[datetime.date, str] = None, forward_rate: Union[float, str] = None, notional_amount: Union[float, str] = None, name: str = None ): super().__init__() self.pair = pair self.settlement_date = settlement_date self.forward_rate = forward_rate self.notional_amount = notional_amount self.name = name @property def asset_class(self) -> AssetClass: """FX""" return AssetClass.FX @property def type(self) -> AssetType: """Forward""" return AssetType.Forward @property def pair(self) -> str: """A currency pair, e.g.: EURUSD or EUR USD""" return self.__pair @pair.setter def pair(self, value: str): self._property_changed('pair') self.__pair = value @property def settlement_date(self) -> Union[datetime.date, str]: """Date or tenor, e.g. 2018-09-03, 3m, Dec21""" return self.__settlement_date @settlement_date.setter def settlement_date(self, value: Union[datetime.date, str]): self._property_changed('settlement_date') self.__settlement_date = value @property def forward_rate(self) -> Union[float, str]: """Forward FX rate""" return self.__forward_rate @forward_rate.setter def forward_rate(self, value: Union[float, str]): self._property_changed('forward_rate') self.__forward_rate = value @property def notional_amount(self) -> Union[float, str]: """Notional amount""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: Union[float, str]): self._property_changed('notional_amount') self.__notional_amount = value
[docs]class FXMultiCrossBinaryLeg(Instrument): """Object representation of a single leg of a multi-cross binary option""" @camel_case_translate def __init__( self, pair: str = None, option_type: Union[OptionType, str] = None, strike_price: Union[float, str] = None, fixing_source: str = None, name: str = None ): super().__init__() self.pair = pair self.option_type = option_type self.strike_price = strike_price self.fixing_source = fixing_source self.name = name @property def asset_class(self) -> AssetClass: """FX""" return AssetClass.FX @property def type(self) -> AssetType: """MultiCrossBinaryLeg""" return AssetType.MultiCrossBinaryLeg @property def pair(self) -> str: """A currency pair, e.g.: EURUSD or EUR USD""" return self.__pair @pair.setter def pair(self, value: str): self._property_changed('pair') self.__pair = value @property def option_type(self) -> Union[OptionType, str]: """Option Type""" return self.__option_type @option_type.setter def option_type(self, value: Union[OptionType, str]): self._property_changed('option_type') self.__option_type = get_enum_value(OptionType, value) @property def strike_price(self) -> Union[float, str]: """Strike as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF, 10/vol, 100k/pv, p=10000, p=10000USD, $200K/BP, or multiple strikes 65.4/-45.8""" return self.__strike_price @strike_price.setter def strike_price(self, value: Union[float, str]): self._property_changed('strike_price') self.__strike_price = value @property def fixing_source(self) -> str: """The data source to be used for observation of FX spot on the fixing date""" return self.__fixing_source @fixing_source.setter def fixing_source(self, value: str): self._property_changed('fixing_source') self.__fixing_source = value
[docs]class FXOption(Instrument): """Object representation of an FX option""" @camel_case_translate def __init__( self, pair: str = None, buy_sell: Union[BuySell, str] = None, option_type: Union[OptionType, str] = None, notional_amount: Union[float, str] = None, notional_currency: Union[Currency, str] = None, notional_amount_other_currency: Union[float, str] = None, strike_price: Union[float, str] = None, settlement_date: Union[datetime.date, str] = None, settlement_currency: Union[Currency, str] = None, settlement_rate_option: str = None, method_of_settlement: Union[OptionSettlementMethod, str] = None, expiration_date: Union[datetime.date, str] = None, expiration_time: str = None, premium: Union[float, str] = None, premium_currency: Union[Currency, str] = None, premium_payment_date: str = None, name: str = None ): super().__init__() self.pair = pair self.buy_sell = buy_sell self.option_type = option_type self.notional_amount = notional_amount self.notional_currency = notional_currency self.notional_amount_other_currency = notional_amount_other_currency self.strike_price = strike_price self.settlement_date = settlement_date self.settlement_currency = settlement_currency self.settlement_rate_option = settlement_rate_option self.method_of_settlement = method_of_settlement self.expiration_date = expiration_date self.expiration_time = expiration_time self.premium = premium self.premium_currency = premium_currency self.premium_payment_date = premium_payment_date self.name = name @property def asset_class(self) -> AssetClass: """FX""" return AssetClass.FX @property def type(self) -> AssetType: """Option""" return AssetType.Option @property def pair(self) -> str: """A currency pair, e.g.: EURUSD or EUR USD""" return self.__pair @pair.setter def pair(self, value: str): self._property_changed('pair') self.__pair = value @property def buy_sell(self) -> Union[BuySell, str]: """Buy or Sell side of contract""" return self.__buy_sell @buy_sell.setter def buy_sell(self, value: Union[BuySell, str]): self._property_changed('buy_sell') self.__buy_sell = get_enum_value(BuySell, value) @property def option_type(self) -> Union[OptionType, str]: """Option Type""" return self.__option_type @option_type.setter def option_type(self, value: Union[OptionType, str]): self._property_changed('option_type') self.__option_type = get_enum_value(OptionType, value) @property def notional_amount(self) -> Union[float, str]: """Notional amount""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: Union[float, str]): self._property_changed('notional_amount') self.__notional_amount = value @property def notional_currency(self) -> Union[Currency, str]: """Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)""" return self.__notional_currency @notional_currency.setter def notional_currency(self, value: Union[Currency, str]): self._property_changed('notional_currency') self.__notional_currency = get_enum_value(Currency, value) @property def notional_amount_other_currency(self) -> Union[float, str]: """Notional amount in currency other than NotionalCurrency from the pair""" return self.__notional_amount_other_currency @notional_amount_other_currency.setter def notional_amount_other_currency(self, value: Union[float, str]): self._property_changed('notional_amount_other_currency') self.__notional_amount_other_currency = value @property def strike_price(self) -> Union[float, str]: """Strike as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF, 10/vol, 100k/pv, p=10000, p=10000USD, $200K/BP, or multiple strikes 65.4/-45.8""" return self.__strike_price @strike_price.setter def strike_price(self, value: Union[float, str]): self._property_changed('strike_price') self.__strike_price = value @property def settlement_date(self) -> Union[datetime.date, str]: """Settlement date of the option, after expiration""" return self.__settlement_date @settlement_date.setter def settlement_date(self, value: Union[datetime.date, str]): self._property_changed('settlement_date') self.__settlement_date = value @property def settlement_currency(self) -> Union[Currency, str]: """Currency of settlement""" return self.__settlement_currency @settlement_currency.setter def settlement_currency(self, value: Union[Currency, str]): self._property_changed('settlement_currency') self.__settlement_currency = get_enum_value(Currency, value) @property def settlement_rate_option(self) -> str: """The source of spot for settlement""" return self.__settlement_rate_option @settlement_rate_option.setter def settlement_rate_option(self, value: str): self._property_changed('settlement_rate_option') self.__settlement_rate_option = value @property def method_of_settlement(self) -> Union[OptionSettlementMethod, str]: """How the option is settled (e.g. Cash, Physical)""" return self.__method_of_settlement @method_of_settlement.setter def method_of_settlement(self, value: Union[OptionSettlementMethod, str]): self._property_changed('method_of_settlement') self.__method_of_settlement = get_enum_value(OptionSettlementMethod, value) @property def expiration_date(self) -> Union[datetime.date, str]: """Date or tenor, e.g. 2018-09-03, 3m, Dec21""" return self.__expiration_date @expiration_date.setter def expiration_date(self, value: Union[datetime.date, str]): self._property_changed('expiration_date') self.__expiration_date = value @property def expiration_time(self) -> str: """The location and (optionally) time of spot for expiration""" return self.__expiration_time @expiration_time.setter def expiration_time(self, value: str): self._property_changed('expiration_time') self.__expiration_time = value @property def premium(self) -> Union[float, str]: """Option premium""" return self.__premium @premium.setter def premium(self, value: Union[float, str]): self._property_changed('premium') self.__premium = value @property def premium_currency(self) -> Union[Currency, str]: """Currency of the option premium""" return self.__premium_currency @premium_currency.setter def premium_currency(self, value: Union[Currency, str]): self._property_changed('premium_currency') self.__premium_currency = get_enum_value(Currency, value) @property def premium_payment_date(self) -> str: """Payment date of the option premium""" return self.__premium_payment_date @premium_payment_date.setter def premium_payment_date(self, value: str): self._property_changed('premium_payment_date') self.__premium_payment_date = value
class FXOptionLeg(Instrument): """Object representation of a FX option leg used in FXOptionStrategy""" @camel_case_translate def __init__( self, buy_sell: Union[BuySell, str] = None, option_type: Union[OptionType, str] = None, notional_amount: Union[float, str] = None, notional_currency: Union[Currency, str] = None, notional_amount_other_currency: Union[float, str] = None, strike_price: Union[float, str] = None, expiration_date: Union[datetime.date, str] = None, settlement_date: Union[datetime.date, str] = None, premium: Union[float, str] = None, premium_currency: Union[Currency, str] = None, premium_payment_date: str = None, settlement_currency: Union[Currency, str] = None, settlement_rate_option: str = None, method_of_settlement: Union[OptionSettlementMethod, str] = None, expiration_time: str = None, name: str = None ): super().__init__() self.buy_sell = buy_sell self.option_type = option_type self.notional_amount = notional_amount self.notional_currency = notional_currency self.notional_amount_other_currency = notional_amount_other_currency self.strike_price = strike_price self.expiration_date = expiration_date self.settlement_date = settlement_date self.premium = premium self.premium_currency = premium_currency self.premium_payment_date = premium_payment_date self.settlement_currency = settlement_currency self.settlement_rate_option = settlement_rate_option self.method_of_settlement = method_of_settlement self.expiration_time = expiration_time self.name = name @property def asset_class(self) -> AssetClass: """FX""" return AssetClass.FX @property def type(self) -> AssetType: """OptionLeg""" return AssetType.OptionLeg @property def buy_sell(self) -> Union[BuySell, str]: """Buy or Sell side of contract""" return self.__buy_sell @buy_sell.setter def buy_sell(self, value: Union[BuySell, str]): self._property_changed('buy_sell') self.__buy_sell = get_enum_value(BuySell, value) @property def option_type(self) -> Union[OptionType, str]: """Option Type""" return self.__option_type @option_type.setter def option_type(self, value: Union[OptionType, str]): self._property_changed('option_type') self.__option_type = get_enum_value(OptionType, value) @property def notional_amount(self) -> Union[float, str]: """Notional amount""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: Union[float, str]): self._property_changed('notional_amount') self.__notional_amount = value @property def notional_currency(self) -> Union[Currency, str]: """Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)""" return self.__notional_currency @notional_currency.setter def notional_currency(self, value: Union[Currency, str]): self._property_changed('notional_currency') self.__notional_currency = get_enum_value(Currency, value) @property def notional_amount_other_currency(self) -> Union[float, str]: """Notional amount in currency other than NotionalCurrency from the pair""" return self.__notional_amount_other_currency @notional_amount_other_currency.setter def notional_amount_other_currency(self, value: Union[float, str]): self._property_changed('notional_amount_other_currency') self.__notional_amount_other_currency = value @property def strike_price(self) -> Union[float, str]: """Strike as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF, 10/vol, 100k/pv, p=10000, p=10000USD, $200K/BP, or multiple strikes 65.4/-45.8""" return self.__strike_price @strike_price.setter def strike_price(self, value: Union[float, str]): self._property_changed('strike_price') self.__strike_price = value @property def expiration_date(self) -> Union[datetime.date, str]: """Date or tenor, e.g. 2018-09-03, 3m, Dec21""" return self.__expiration_date @expiration_date.setter def expiration_date(self, value: Union[datetime.date, str]): self._property_changed('expiration_date') self.__expiration_date = value @property def settlement_date(self) -> Union[datetime.date, str]: """Settlement date of the option, after expiration""" return self.__settlement_date @settlement_date.setter def settlement_date(self, value: Union[datetime.date, str]): self._property_changed('settlement_date') self.__settlement_date = value @property def premium(self) -> Union[float, str]: """Option premium""" return self.__premium @premium.setter def premium(self, value: Union[float, str]): self._property_changed('premium') self.__premium = value @property def premium_currency(self) -> Union[Currency, str]: """Currency of the option premium""" return self.__premium_currency @premium_currency.setter def premium_currency(self, value: Union[Currency, str]): self._property_changed('premium_currency') self.__premium_currency = get_enum_value(Currency, value) @property def premium_payment_date(self) -> str: """Payment date of the option premium""" return self.__premium_payment_date @premium_payment_date.setter def premium_payment_date(self, value: str): self._property_changed('premium_payment_date') self.__premium_payment_date = value @property def settlement_currency(self) -> Union[Currency, str]: """Currency of settlement""" return self.__settlement_currency @settlement_currency.setter def settlement_currency(self, value: Union[Currency, str]): self._property_changed('settlement_currency') self.__settlement_currency = get_enum_value(Currency, value) @property def settlement_rate_option(self) -> str: """The source of spot for settlement""" return self.__settlement_rate_option @settlement_rate_option.setter def settlement_rate_option(self, value: str): self._property_changed('settlement_rate_option') self.__settlement_rate_option = value @property def method_of_settlement(self) -> Union[OptionSettlementMethod, str]: """How the option is settled (e.g. Cash, Physical)""" return self.__method_of_settlement @method_of_settlement.setter def method_of_settlement(self, value: Union[OptionSettlementMethod, str]): self._property_changed('method_of_settlement') self.__method_of_settlement = get_enum_value(OptionSettlementMethod, value) @property def expiration_time(self) -> str: """The location and (optionally) time of spot for expiration""" return self.__expiration_time @expiration_time.setter def expiration_time(self, value: str): self._property_changed('expiration_time') self.__expiration_time = value
[docs]class FXVolatilitySwap(Instrument): """Object representation of an FX Vol Swap""" @camel_case_translate def __init__( self, pair: str = None, buy_sell: Union[BuySell, str] = None, strike_vol: Union[float, str] = None, notional_currency: Union[Currency, str] = None, notional_amount: Union[float, str] = None, first_fixing_date: Union[datetime.date, str] = None, last_fixing_date: Union[datetime.date, str] = None, settlement_date: Union[datetime.date, str] = None, fixing_source: str = None, fixing_frequency: str = None, annualization_factor: float = None, calculate_mean_return: float = 0, name: str = None ): super().__init__() self.pair = pair self.buy_sell = buy_sell self.strike_vol = strike_vol self.notional_currency = notional_currency self.notional_amount = notional_amount self.first_fixing_date = first_fixing_date self.last_fixing_date = last_fixing_date self.settlement_date = settlement_date self.fixing_source = fixing_source self.fixing_frequency = fixing_frequency self.annualization_factor = annualization_factor self.calculate_mean_return = calculate_mean_return self.name = name @property def asset_class(self) -> AssetClass: """FX""" return AssetClass.FX @property def type(self) -> AssetType: """VolatilitySwap""" return AssetType.VolatilitySwap @property def pair(self) -> str: """A currency pair, e.g.: EURUSD or EUR USD""" return self.__pair @pair.setter def pair(self, value: str): self._property_changed('pair') self.__pair = value @property def buy_sell(self) -> Union[BuySell, str]: """Buy or Sell side of contract""" return self.__buy_sell @buy_sell.setter def buy_sell(self, value: Union[BuySell, str]): self._property_changed('buy_sell') self.__buy_sell = get_enum_value(BuySell, value) @property def strike_vol(self) -> Union[float, str]: """Volatility strike""" return self.__strike_vol @strike_vol.setter def strike_vol(self, value: Union[float, str]): self._property_changed('strike_vol') self.__strike_vol = value @property def notional_currency(self) -> Union[Currency, str]: """Notional currency""" return self.__notional_currency @notional_currency.setter def notional_currency(self, value: Union[Currency, str]): self._property_changed('notional_currency') self.__notional_currency = get_enum_value(Currency, value) @property def notional_amount(self) -> Union[float, str]: """ Notional amount in dollar terms""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: Union[float, str]): self._property_changed('notional_amount') self.__notional_amount = value @property def first_fixing_date(self) -> Union[datetime.date, str]: """First averaging date or observation date""" return self.__first_fixing_date @first_fixing_date.setter def first_fixing_date(self, value: Union[datetime.date, str]): self._property_changed('first_fixing_date') self.__first_fixing_date = value @property def last_fixing_date(self) -> Union[datetime.date, str]: """Last averaging date or valuation date""" return self.__last_fixing_date @last_fixing_date.setter def last_fixing_date(self, value: Union[datetime.date, str]): self._property_changed('last_fixing_date') self.__last_fixing_date = value @property def settlement_date(self) -> Union[datetime.date, str]: """Date or tenor, e.g. 2018-09-03, 3m, Dec21""" return self.__settlement_date @settlement_date.setter def settlement_date(self, value: Union[datetime.date, str]): self._property_changed('settlement_date') self.__settlement_date = value @property def fixing_source(self) -> str: """The data source to be used for observations of FX spot on each fixing""" return self.__fixing_source @fixing_source.setter def fixing_source(self, value: str): self._property_changed('fixing_source') self.__fixing_source = value @property def fixing_frequency(self) -> str: """Fixing frequency (ex. Daily / Business Days)""" return self.__fixing_frequency @fixing_frequency.setter def fixing_frequency(self, value: str): self._property_changed('fixing_frequency') self.__fixing_frequency = value @property def annualization_factor(self) -> float: """Annualization factor is the number of days used per year to compute volatility""" return self.__annualization_factor @annualization_factor.setter def annualization_factor(self, value: float): self._property_changed('annualization_factor') self.__annualization_factor = value @property def calculate_mean_return(self) -> float: """Indicates whether the mean return is calculated (true) or taken as zero (false) in the realized volatility computation""" return self.__calculate_mean_return @calculate_mean_return.setter def calculate_mean_return(self, value: float): self._property_changed('calculate_mean_return') self.__calculate_mean_return = value
[docs]class Forward(Instrument): """Forward cash payment""" @camel_case_translate def __init__( self, currency: Union[Currency, str] = None, expiration_date: Union[datetime.date, str] = None, notional_amount: Union[float, str] = None, name: str = None ): super().__init__() self.currency = currency self.expiration_date = expiration_date self.notional_amount = notional_amount self.name = name @property def asset_class(self) -> AssetClass: """Cash""" return AssetClass.Cash @property def type(self) -> AssetType: """Forward""" return AssetType.Forward @property def currency(self) -> Union[Currency, str]: """Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)""" return self.__currency @currency.setter def currency(self, value: Union[Currency, str]): self._property_changed('currency') self.__currency = get_enum_value(Currency, value) @property def expiration_date(self) -> Union[datetime.date, str]: """Date or tenor, e.g. 2018-09-03, 3m, Dec21""" return self.__expiration_date @expiration_date.setter def expiration_date(self, value: Union[datetime.date, str]): self._property_changed('expiration_date') self.__expiration_date = value @property def notional_amount(self) -> Union[float, str]: """Notional amount""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: Union[float, str]): self._property_changed('notional_amount') self.__notional_amount = value
[docs]class IRBasisSwap(Instrument): """A single currency exchange of cashflows from different interest rate indices""" @camel_case_translate def __init__( self, termination_date: Union[Union[datetime.date, str], str] = None, notional_currency: Union[Currency, str] = None, notional_amount: Union[float, str] = None, effective_date: Union[Union[datetime.date, str], str] = None, principal_exchange: Union[PrincipalExchange, str] = None, payer_spread: Union[float, str] = None, payer_rate_option: str = None, payer_designated_maturity: str = None, payer_frequency: str = None, payer_day_count_fraction: Union[DayCountFraction, str] = None, payer_business_day_convention: Union[BusinessDayConvention, str] = None, receiver_spread: Union[float, str] = None, receiver_rate_option: str = None, receiver_designated_maturity: str = None, receiver_frequency: str = None, receiver_day_count_fraction: Union[DayCountFraction, str] = None, receiver_business_day_convention: Union[BusinessDayConvention, str] = None, fee: float = 0, fee_currency: Union[Currency, str] = None, fee_payment_date: Union[datetime.date, str] = None, clearing_house: Union[SwapClearingHouse, str] = None, name: str = None ): super().__init__() self.termination_date = termination_date self.notional_currency = notional_currency self.notional_amount = notional_amount self.effective_date = effective_date self.principal_exchange = principal_exchange self.payer_spread = payer_spread self.payer_rate_option = payer_rate_option self.payer_designated_maturity = payer_designated_maturity self.payer_frequency = payer_frequency self.payer_day_count_fraction = payer_day_count_fraction self.payer_business_day_convention = payer_business_day_convention self.receiver_spread = receiver_spread self.receiver_rate_option = receiver_rate_option self.receiver_designated_maturity = receiver_designated_maturity self.receiver_frequency = receiver_frequency self.receiver_day_count_fraction = receiver_day_count_fraction self.receiver_business_day_convention = receiver_business_day_convention self.fee = fee self.fee_currency = fee_currency self.fee_payment_date = fee_payment_date self.clearing_house = clearing_house self.name = name @property def asset_class(self) -> AssetClass: """Rates""" return AssetClass.Rates @property def type(self) -> AssetType: """BasisSwap""" return AssetType.BasisSwap @property def termination_date(self) -> Union[Union[datetime.date, str], str]: """The termination of the swap, e.g. 2050-04-01, 10y""" return self.__termination_date @termination_date.setter def termination_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('termination_date') self.__termination_date = value @property def notional_currency(self) -> Union[Currency, str]: """Notional currency""" return self.__notional_currency @notional_currency.setter def notional_currency(self, value: Union[Currency, str]): self._property_changed('notional_currency') self.__notional_currency = get_enum_value(Currency, value) @property def notional_amount(self) -> Union[float, str]: """Notional amount""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: Union[float, str]): self._property_changed('notional_amount') self.__notional_amount = value @property def effective_date(self) -> Union[Union[datetime.date, str], str]: """The date on which the swap becomes effective""" return self.__effective_date @effective_date.setter def effective_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('effective_date') self.__effective_date = value @property def principal_exchange(self) -> Union[PrincipalExchange, str]: """The date on which the swap becomes effective""" return self.__principal_exchange @principal_exchange.setter def principal_exchange(self, value: Union[PrincipalExchange, str]): self._property_changed('principal_exchange') self.__principal_exchange = get_enum_value(PrincipalExchange, value) @property def payer_spread(self) -> Union[float, str]: """Spread over the payer rate""" return self.__payer_spread @payer_spread.setter def payer_spread(self, value: Union[float, str]): self._property_changed('payer_spread') self.__payer_spread = value @property def payer_rate_option(self) -> str: """The underlying benchmark for the payer, e.g. USD-LIBOR-BBA, EUR-EURIBOR-TELERATE""" return self.__payer_rate_option @payer_rate_option.setter def payer_rate_option(self, value: str): self._property_changed('payer_rate_option') self.__payer_rate_option = value @property def payer_designated_maturity(self) -> str: """Tenor of the payerRateOption, e.g. 3m, 6m""" return self.__payer_designated_maturity @payer_designated_maturity.setter def payer_designated_maturity(self, value: str): self._property_changed('payer_designated_maturity') self.__payer_designated_maturity = value @property def payer_frequency(self) -> str: """The frequency of payer payments, e.g. 6m""" return self.__payer_frequency @payer_frequency.setter def payer_frequency(self, value: str): self._property_changed('payer_frequency') self.__payer_frequency = value @property def payer_day_count_fraction(self) -> Union[DayCountFraction, str]: """The day count fraction for the payer""" return self.__payer_day_count_fraction @payer_day_count_fraction.setter def payer_day_count_fraction(self, value: Union[DayCountFraction, str]): self._property_changed('payer_day_count_fraction') self.__payer_day_count_fraction = get_enum_value(DayCountFraction, value) @property def payer_business_day_convention(self) -> Union[BusinessDayConvention, str]: """The business day convention for the payer""" return self.__payer_business_day_convention @payer_business_day_convention.setter def payer_business_day_convention(self, value: Union[BusinessDayConvention, str]): self._property_changed('payer_business_day_convention') self.__payer_business_day_convention = get_enum_value(BusinessDayConvention, value) @property def receiver_spread(self) -> Union[float, str]: """Spread over the receiver rate""" return self.__receiver_spread @receiver_spread.setter def receiver_spread(self, value: Union[float, str]): self._property_changed('receiver_spread') self.__receiver_spread = value @property def receiver_rate_option(self) -> str: """The underlying benchmark for the receiver, e.g. USD-LIBOR-BBA, EUR-EURIBOR- TELERATE""" return self.__receiver_rate_option @receiver_rate_option.setter def receiver_rate_option(self, value: str): self._property_changed('receiver_rate_option') self.__receiver_rate_option = value @property def receiver_designated_maturity(self) -> str: """Tenor of the receiverRateOption, e.g. 3m, 6m""" return self.__receiver_designated_maturity @receiver_designated_maturity.setter def receiver_designated_maturity(self, value: str): self._property_changed('receiver_designated_maturity') self.__receiver_designated_maturity = value @property def receiver_frequency(self) -> str: """The frequency of receiver payments, e.g. 6m""" return self.__receiver_frequency @receiver_frequency.setter def receiver_frequency(self, value: str): self._property_changed('receiver_frequency') self.__receiver_frequency = value @property def receiver_day_count_fraction(self) -> Union[DayCountFraction, str]: """The day count fraction for the receiver""" return self.__receiver_day_count_fraction @receiver_day_count_fraction.setter def receiver_day_count_fraction(self, value: Union[DayCountFraction, str]): self._property_changed('receiver_day_count_fraction') self.__receiver_day_count_fraction = get_enum_value(DayCountFraction, value) @property def receiver_business_day_convention(self) -> Union[BusinessDayConvention, str]: """The business day convention for the receiver""" return self.__receiver_business_day_convention @receiver_business_day_convention.setter def receiver_business_day_convention(self, value: Union[BusinessDayConvention, str]): self._property_changed('receiver_business_day_convention') self.__receiver_business_day_convention = get_enum_value(BusinessDayConvention, value) @property def fee(self) -> float: """The fee""" return self.__fee @fee.setter def fee(self, value: float): self._property_changed('fee') self.__fee = value @property def fee_currency(self) -> Union[Currency, str]: """Currency of the fee""" return self.__fee_currency @fee_currency.setter def fee_currency(self, value: Union[Currency, str]): self._property_changed('fee_currency') self.__fee_currency = get_enum_value(Currency, value) @property def fee_payment_date(self) -> Union[datetime.date, str]: """Payment date of the fee""" return self.__fee_payment_date @fee_payment_date.setter def fee_payment_date(self, value: Union[datetime.date, str]): self._property_changed('fee_payment_date') self.__fee_payment_date = value @property def clearing_house(self) -> Union[SwapClearingHouse, str]: """Swap Clearing House""" return self.__clearing_house @clearing_house.setter def clearing_house(self, value: Union[SwapClearingHouse, str]): self._property_changed('clearing_house') self.__clearing_house = get_enum_value(SwapClearingHouse, value)
class IRBondFuture(Instrument): """A future on a (treasury) bond""" @camel_case_translate def __init__( self, buy_sell: Union[BuySell, str] = None, notional_amount: Union[float, str] = None, underlier: Union[float, str] = None, currency: Union[Currency, str] = None, expiration_date: Union[datetime.date, str] = None, exchange: str = None, name: str = None ): super().__init__() self.buy_sell = buy_sell self.notional_amount = notional_amount self.underlier = underlier self.currency = currency self.expiration_date = expiration_date self.exchange = exchange self.name = name @property def asset_class(self) -> AssetClass: """Rates""" return AssetClass.Rates @property def type(self) -> AssetType: """BondFuture""" return AssetType.BondFuture @property def buy_sell(self) -> Union[BuySell, str]: """Buy or Sell side of contract""" return self.__buy_sell @buy_sell.setter def buy_sell(self, value: Union[BuySell, str]): self._property_changed('buy_sell') self.__buy_sell = get_enum_value(BuySell, value) @property def notional_amount(self) -> Union[float, str]: """Notional amount""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: Union[float, str]): self._property_changed('notional_amount') self.__notional_amount = value @property def underlier(self) -> Union[float, str]: """Underlier security identifier""" return self.__underlier @underlier.setter def underlier(self, value: Union[float, str]): self._property_changed('underlier') self.__underlier = value @property def currency(self) -> Union[Currency, str]: """Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)""" return self.__currency @currency.setter def currency(self, value: Union[Currency, str]): self._property_changed('currency') self.__currency = get_enum_value(Currency, value) @property def expiration_date(self) -> Union[datetime.date, str]: """Date or tenor, e.g. 2018-09-03, 3m, Dec21""" return self.__expiration_date @expiration_date.setter def expiration_date(self, value: Union[datetime.date, str]): self._property_changed('expiration_date') self.__expiration_date = value @property def exchange(self) -> str: """Name of marketplace where security, derivative or other instrument is traded""" return self.__exchange @exchange.setter def exchange(self, value: str): self._property_changed('exchange') self.__exchange = value class IRBondOption(Instrument): """Object representation of a bond option""" @camel_case_translate def __init__( self, underlier: Union[float, str] = None, notional_amount: Union[float, str] = None, expiration_date: Union[Union[datetime.date, str], str] = None, option_type: Union[OptionType, str] = None, effective_date: Union[Union[datetime.date, str], str] = None, strike: Union[float, str] = None, strike_type: Union[BondStrikeType, str] = None, premium: Union[float, str] = None, premium_payment_date: Union[datetime.date, str] = None, fee: float = 0, fee_currency: Union[Currency, str] = None, fee_payment_date: Union[datetime.date, str] = None, settlement: Union[SettlementType, str] = None, underlier_type: Union[UnderlierType, str] = None, name: str = None ): super().__init__() self.underlier = underlier self.notional_amount = notional_amount self.expiration_date = expiration_date self.option_type = option_type self.effective_date = effective_date self.strike = strike self.strike_type = strike_type self.premium = premium self.premium_payment_date = premium_payment_date self.fee = fee self.fee_currency = fee_currency self.fee_payment_date = fee_payment_date self.settlement = settlement self.underlier_type = underlier_type self.name = name @property def asset_class(self) -> AssetClass: """Rates""" return AssetClass.Rates @property def type(self) -> AssetType: """BondOption""" return AssetType.BondOption @property def underlier(self) -> Union[float, str]: """Underlier security identifier""" return self.__underlier @underlier.setter def underlier(self, value: Union[float, str]): self._property_changed('underlier') self.__underlier = value @property def notional_amount(self) -> Union[float, str]: """Notional amount""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: Union[float, str]): self._property_changed('notional_amount') self.__notional_amount = value @property def expiration_date(self) -> Union[Union[datetime.date, str], str]: """Bond option expiration date, 2020-05-01, 3m""" return self.__expiration_date @expiration_date.setter def expiration_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('expiration_date') self.__expiration_date = value @property def option_type(self) -> Union[OptionType, str]: """Option Type""" return self.__option_type @option_type.setter def option_type(self, value: Union[OptionType, str]): self._property_changed('option_type') self.__option_type = get_enum_value(OptionType, value) @property def effective_date(self) -> Union[Union[datetime.date, str], str]: """Bond option effective date, e.g. 2019-01-01, 10y""" return self.__effective_date @effective_date.setter def effective_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('effective_date') self.__effective_date = value @property def strike(self) -> Union[float, str]: """The strike of the option""" return self.__strike @strike.setter def strike(self, value: Union[float, str]): self._property_changed('strike') self.__strike = value @property def strike_type(self) -> Union[BondStrikeType, str]: """The type of the bond strike - price, yield etc""" return self.__strike_type @strike_type.setter def strike_type(self, value: Union[BondStrikeType, str]): self._property_changed('strike_type') self.__strike_type = get_enum_value(BondStrikeType, value) @property def premium(self) -> Union[float, str]: """The premium""" return self.__premium @premium.setter def premium(self, value: Union[float, str]): self._property_changed('premium') self.__premium = value @property def premium_payment_date(self) -> Union[datetime.date, str]: """Payment date of the premium""" return self.__premium_payment_date @premium_payment_date.setter def premium_payment_date(self, value: Union[datetime.date, str]): self._property_changed('premium_payment_date') self.__premium_payment_date = value @property def fee(self) -> float: """The fee""" return self.__fee @fee.setter def fee(self, value: float): self._property_changed('fee') self.__fee = value @property def fee_currency(self) -> Union[Currency, str]: """Currency of the fee""" return self.__fee_currency @fee_currency.setter def fee_currency(self, value: Union[Currency, str]): self._property_changed('fee_currency') self.__fee_currency = get_enum_value(Currency, value) @property def fee_payment_date(self) -> Union[datetime.date, str]: """Payment date of the fee""" return self.__fee_payment_date @fee_payment_date.setter def fee_payment_date(self, value: Union[datetime.date, str]): self._property_changed('fee_payment_date') self.__fee_payment_date = value @property def settlement(self) -> Union[SettlementType, str]: """Settlement Type""" return self.__settlement @settlement.setter def settlement(self, value: Union[SettlementType, str]): self._property_changed('settlement') self.__settlement = get_enum_value(SettlementType, value) @property def underlier_type(self) -> Union[UnderlierType, str]: """Type of underlyer""" return self.__underlier_type @underlier_type.setter def underlier_type(self, value: Union[UnderlierType, str]): self._property_changed('underlier_type') self.__underlier_type = get_enum_value(UnderlierType, value)
[docs]class IRCMSOption(Instrument): """Object representation of a constant maturity option (cap, floor, straddle)""" @camel_case_translate def __init__( self, cap_floor: str = None, termination_date: Union[Union[datetime.date, str], str] = None, notional_currency: Union[Currency, str] = None, notional_amount: Union[float, str] = None, effective_date: Union[Union[datetime.date, str], str] = None, strike: Union[float, str] = None, index: str = None, multiplier: float = None, premium: Union[float, str] = None, premium_payment_date: Union[datetime.date, str] = None, fee: float = 0, fee_currency: Union[Currency, str] = None, fee_payment_date: Union[datetime.date, str] = None, buy_sell: Union[BuySell, str] = None, name: str = None ): super().__init__() self.cap_floor = cap_floor self.termination_date = termination_date self.notional_currency = notional_currency self.notional_amount = notional_amount self.effective_date = effective_date self.strike = strike self.index = index self.multiplier = multiplier self.premium = premium self.premium_payment_date = premium_payment_date self.fee = fee self.fee_currency = fee_currency self.fee_payment_date = fee_payment_date self.buy_sell = buy_sell self.name = name @property def asset_class(self) -> AssetClass: """Rates""" return AssetClass.Rates @property def type(self) -> AssetType: """CMSOption""" return AssetType.CMSOption @property def cap_floor(self) -> str: """Structure type, e.g. Cap, Floor, Straddle, Binary Cap""" return self.__cap_floor @cap_floor.setter def cap_floor(self, value: str): self._property_changed('cap_floor') self.__cap_floor = value @property def termination_date(self) -> Union[Union[datetime.date, str], str]: """Swap termination date, e.g. 2030-05-01, 10y""" return self.__termination_date @termination_date.setter def termination_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('termination_date') self.__termination_date = value @property def notional_currency(self) -> Union[Currency, str]: """Notional currency""" return self.__notional_currency @notional_currency.setter def notional_currency(self, value: Union[Currency, str]): self._property_changed('notional_currency') self.__notional_currency = get_enum_value(Currency, value) @property def notional_amount(self) -> Union[float, str]: """Notional amount""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: Union[float, str]): self._property_changed('notional_amount') self.__notional_amount = value @property def effective_date(self) -> Union[Union[datetime.date, str], str]: """CMS option effective date, e.g. 2019-01-01, 10y""" return self.__effective_date @effective_date.setter def effective_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('effective_date') self.__effective_date = value @property def strike(self) -> Union[float, str]: """Strike as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF, 10/vol, 100k/pv, p=10000, p=10000USD, $200K/BP, or multiple strikes 65.4/-45.8""" return self.__strike @strike.setter def strike(self, value: Union[float, str]): self._property_changed('strike') self.__strike = value @property def index(self) -> str: """The underlying benchmark i.e. 30yUSD""" return self.__index @index.setter def index(self, value: str): self._property_changed('index') self.__index = value @property def multiplier(self) -> float: """Multiplier""" return self.__multiplier @multiplier.setter def multiplier(self, value: float): self._property_changed('multiplier') self.__multiplier = value @property def premium(self) -> Union[float, str]: """The premium""" return self.__premium @premium.setter def premium(self, value: Union[float, str]): self._property_changed('premium') self.__premium = value @property def premium_payment_date(self) -> Union[datetime.date, str]: """Payment date of the premium""" return self.__premium_payment_date @premium_payment_date.setter def premium_payment_date(self, value: Union[datetime.date, str]): self._property_changed('premium_payment_date') self.__premium_payment_date = value @property def fee(self) -> float: """The fee""" return self.__fee @fee.setter def fee(self, value: float): self._property_changed('fee') self.__fee = value @property def fee_currency(self) -> Union[Currency, str]: """Currency of the fee""" return self.__fee_currency @fee_currency.setter def fee_currency(self, value: Union[Currency, str]): self._property_changed('fee_currency') self.__fee_currency = get_enum_value(Currency, value) @property def fee_payment_date(self) -> Union[datetime.date, str]: """Payment date of the fee""" return self.__fee_payment_date @fee_payment_date.setter def fee_payment_date(self, value: Union[datetime.date, str]): self._property_changed('fee_payment_date') self.__fee_payment_date = value @property def buy_sell(self) -> Union[BuySell, str]: """Buy or Sell side of contract""" return self.__buy_sell @buy_sell.setter def buy_sell(self, value: Union[BuySell, str]): self._property_changed('buy_sell') self.__buy_sell = get_enum_value(BuySell, value)
[docs]class IRCMSOptionStrip(Instrument): """Object representation of a constant maturity option strip (cap, floor, straddle)""" @camel_case_translate def __init__( self, cap_floor: str = None, termination_date: Union[Union[datetime.date, str], str] = None, notional_currency: Union[Currency, str] = None, notional_amount: Union[float, str] = None, effective_date: Union[Union[datetime.date, str], str] = None, strike: Union[float, str] = None, index: str = None, floating_rate_frequency: str = None, floating_rate_day_count_fraction: Union[DayCountFraction, str] = None, floating_rate_business_day_convention: Union[BusinessDayConvention, str] = None, reset_delay: str = None, multiplier: float = None, premium: Union[float, str] = None, premium_payment_date: Union[datetime.date, str] = None, fee: float = 0, fee_currency: Union[Currency, str] = None, fee_payment_date: Union[datetime.date, str] = None, buy_sell: Union[BuySell, str] = None, name: str = None ): super().__init__() self.cap_floor = cap_floor self.termination_date = termination_date self.notional_currency = notional_currency self.notional_amount = notional_amount self.effective_date = effective_date self.strike = strike self.index = index self.floating_rate_frequency = floating_rate_frequency self.floating_rate_day_count_fraction = floating_rate_day_count_fraction self.floating_rate_business_day_convention = floating_rate_business_day_convention self.reset_delay = reset_delay self.multiplier = multiplier self.premium = premium self.premium_payment_date = premium_payment_date self.fee = fee self.fee_currency = fee_currency self.fee_payment_date = fee_payment_date self.buy_sell = buy_sell self.name = name @property def asset_class(self) -> AssetClass: """Rates""" return AssetClass.Rates @property def type(self) -> AssetType: """CMSOptionStrip""" return AssetType.CMSOptionStrip @property def cap_floor(self) -> str: """Structure type, e.g. Cap, Floor, Straddle, Binary Cap""" return self.__cap_floor @cap_floor.setter def cap_floor(self, value: str): self._property_changed('cap_floor') self.__cap_floor = value @property def termination_date(self) -> Union[Union[datetime.date, str], str]: """Swap termination date, e.g. 2030-05-01, 10y""" return self.__termination_date @termination_date.setter def termination_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('termination_date') self.__termination_date = value @property def notional_currency(self) -> Union[Currency, str]: """Notional currency""" return self.__notional_currency @notional_currency.setter def notional_currency(self, value: Union[Currency, str]): self._property_changed('notional_currency') self.__notional_currency = get_enum_value(Currency, value) @property def notional_amount(self) -> Union[float, str]: """Notional amount""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: Union[float, str]): self._property_changed('notional_amount') self.__notional_amount = value @property def effective_date(self) -> Union[Union[datetime.date, str], str]: """CMS option effective date, e.g. 2019-01-01, 10y""" return self.__effective_date @effective_date.setter def effective_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('effective_date') self.__effective_date = value @property def strike(self) -> Union[float, str]: """Strike as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF, 10/vol, 100k/pv, p=10000, p=10000USD, $200K/BP, or multiple strikes 65.4/-45.8""" return self.__strike @strike.setter def strike(self, value: Union[float, str]): self._property_changed('strike') self.__strike = value @property def index(self) -> str: """The underlying benchmark i.e. 30yUSD""" return self.__index @index.setter def index(self, value: str): self._property_changed('index') self.__index = value @property def floating_rate_frequency(self) -> str: """Period e.g. 3m, 1y""" return self.__floating_rate_frequency @floating_rate_frequency.setter def floating_rate_frequency(self, value: str): self._property_changed('floating_rate_frequency') self.__floating_rate_frequency = value @property def floating_rate_day_count_fraction(self) -> Union[DayCountFraction, str]: """The day count fraction of the floating rate""" return self.__floating_rate_day_count_fraction @floating_rate_day_count_fraction.setter def floating_rate_day_count_fraction(self, value: Union[DayCountFraction, str]): self._property_changed('floating_rate_day_count_fraction') self.__floating_rate_day_count_fraction = get_enum_value(DayCountFraction, value) @property def floating_rate_business_day_convention(self) -> Union[BusinessDayConvention, str]: """The business day convention of the floating rate""" return self.__floating_rate_business_day_convention @floating_rate_business_day_convention.setter def floating_rate_business_day_convention(self, value: Union[BusinessDayConvention, str]): self._property_changed('floating_rate_business_day_convention') self.__floating_rate_business_day_convention = get_enum_value(BusinessDayConvention, value) @property def reset_delay(self) -> str: """Delay of the reset e.g. 2d""" return self.__reset_delay @reset_delay.setter def reset_delay(self, value: str): self._property_changed('reset_delay') self.__reset_delay = value @property def multiplier(self) -> float: """Multiplier""" return self.__multiplier @multiplier.setter def multiplier(self, value: float): self._property_changed('multiplier') self.__multiplier = value @property def premium(self) -> Union[float, str]: """The premium""" return self.__premium @premium.setter def premium(self, value: Union[float, str]): self._property_changed('premium') self.__premium = value @property def premium_payment_date(self) -> Union[datetime.date, str]: """Payment date of the premium""" return self.__premium_payment_date @premium_payment_date.setter def premium_payment_date(self, value: Union[datetime.date, str]): self._property_changed('premium_payment_date') self.__premium_payment_date = value @property def fee(self) -> float: """The fee""" return self.__fee @fee.setter def fee(self, value: float): self._property_changed('fee') self.__fee = value @property def fee_currency(self) -> Union[Currency, str]: """Currency of the fee""" return self.__fee_currency @fee_currency.setter def fee_currency(self, value: Union[Currency, str]): self._property_changed('fee_currency') self.__fee_currency = get_enum_value(Currency, value) @property def fee_payment_date(self) -> Union[datetime.date, str]: """Payment date of the fee""" return self.__fee_payment_date @fee_payment_date.setter def fee_payment_date(self, value: Union[datetime.date, str]): self._property_changed('fee_payment_date') self.__fee_payment_date = value @property def buy_sell(self) -> Union[BuySell, str]: """Buy or Sell side of contract""" return self.__buy_sell @buy_sell.setter def buy_sell(self, value: Union[BuySell, str]): self._property_changed('buy_sell') self.__buy_sell = get_enum_value(BuySell, value)
[docs]class IRCMSSpreadOption(Instrument): """Object representation of a constant maturity spread option (cap, floor, straddle)""" @camel_case_translate def __init__( self, cap_floor: str = None, termination_date: Union[Union[datetime.date, str], str] = None, notional_currency: Union[Currency, str] = None, notional_amount: Union[float, str] = None, effective_date: Union[Union[datetime.date, str], str] = None, strike: Union[float, str] = None, index1_tenor: str = None, index2_tenor: str = None, premium: Union[float, str] = None, premium_payment_date: Union[datetime.date, str] = None, fee: float = 0, fee_currency: Union[Currency, str] = None, fee_payment_date: Union[datetime.date, str] = None, buy_sell: Union[BuySell, str] = None, name: str = None ): super().__init__() self.cap_floor = cap_floor self.termination_date = termination_date self.notional_currency = notional_currency self.notional_amount = notional_amount self.effective_date = effective_date self.strike = strike self.index1_tenor = index1_tenor self.index2_tenor = index2_tenor self.premium = premium self.premium_payment_date = premium_payment_date self.fee = fee self.fee_currency = fee_currency self.fee_payment_date = fee_payment_date self.buy_sell = buy_sell self.name = name @property def asset_class(self) -> AssetClass: """Rates""" return AssetClass.Rates @property def type(self) -> AssetType: """CMSSpreadOption""" return AssetType.CMSSpreadOption @property def cap_floor(self) -> str: """Structure type, e.g. Cap, Floor, Straddle""" return self.__cap_floor @cap_floor.setter def cap_floor(self, value: str): self._property_changed('cap_floor') self.__cap_floor = value @property def termination_date(self) -> Union[Union[datetime.date, str], str]: """Swap termination date, e.g. 2030-05-01, 10y""" return self.__termination_date @termination_date.setter def termination_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('termination_date') self.__termination_date = value @property def notional_currency(self) -> Union[Currency, str]: """Notional currency""" return self.__notional_currency @notional_currency.setter def notional_currency(self, value: Union[Currency, str]): self._property_changed('notional_currency') self.__notional_currency = get_enum_value(Currency, value) @property def notional_amount(self) -> Union[float, str]: """Notional amount""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: Union[float, str]): self._property_changed('notional_amount') self.__notional_amount = value @property def effective_date(self) -> Union[Union[datetime.date, str], str]: """CMS option effective date, e.g. 2019-01-01, 10y""" return self.__effective_date @effective_date.setter def effective_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('effective_date') self.__effective_date = value @property def strike(self) -> Union[float, str]: """Strike as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF, 10/vol, 100k/pv, p=10000, p=10000USD, $200K/BP, or multiple strikes 65.4/-45.8""" return self.__strike @strike.setter def strike(self, value: Union[float, str]): self._property_changed('strike') self.__strike = value @property def index1_tenor(self) -> str: """The tenor of the underlying benchmark to be the first element i.e. 30y""" return self.__index1_tenor @index1_tenor.setter def index1_tenor(self, value: str): self._property_changed('index1_tenor') self.__index1_tenor = value @property def index2_tenor(self) -> str: """The tenor of the underlying benchmark to be the second element i.e. 5y""" return self.__index2_tenor @index2_tenor.setter def index2_tenor(self, value: str): self._property_changed('index2_tenor') self.__index2_tenor = value @property def premium(self) -> Union[float, str]: """The premium""" return self.__premium @premium.setter def premium(self, value: Union[float, str]): self._property_changed('premium') self.__premium = value @property def premium_payment_date(self) -> Union[datetime.date, str]: """Payment date of the premium""" return self.__premium_payment_date @premium_payment_date.setter def premium_payment_date(self, value: Union[datetime.date, str]): self._property_changed('premium_payment_date') self.__premium_payment_date = value @property def fee(self) -> float: """The fee""" return self.__fee @fee.setter def fee(self, value: float): self._property_changed('fee') self.__fee = value @property def fee_currency(self) -> Union[Currency, str]: """Currency of the fee""" return self.__fee_currency @fee_currency.setter def fee_currency(self, value: Union[Currency, str]): self._property_changed('fee_currency') self.__fee_currency = get_enum_value(Currency, value) @property def fee_payment_date(self) -> Union[datetime.date, str]: """Payment date of the fee""" return self.__fee_payment_date @fee_payment_date.setter def fee_payment_date(self, value: Union[datetime.date, str]): self._property_changed('fee_payment_date') self.__fee_payment_date = value @property def buy_sell(self) -> Union[BuySell, str]: """Buy or Sell side of contract""" return self.__buy_sell @buy_sell.setter def buy_sell(self, value: Union[BuySell, str]): self._property_changed('buy_sell') self.__buy_sell = get_enum_value(BuySell, value)
[docs]class IRCMSSpreadOptionStrip(Instrument): """Object representation of a constant maturity spread option strip (cap, floor, straddle)""" @camel_case_translate def __init__( self, cap_floor: str = None, termination_date: Union[Union[datetime.date, str], str] = None, notional_currency: Union[Currency, str] = None, notional_amount: Union[float, str] = None, effective_date: Union[Union[datetime.date, str], str] = None, strike: Union[float, str] = None, index1: str = None, index2: str = None, floating_rate_frequency: str = None, floating_rate_day_count_fraction: Union[DayCountFraction, str] = None, floating_rate_business_day_convention: Union[BusinessDayConvention, str] = None, reset_delay: str = None, premium: Union[float, str] = None, premium_payment_date: Union[datetime.date, str] = None, fee: float = 0, fee_currency: Union[Currency, str] = None, fee_payment_date: Union[datetime.date, str] = None, buy_sell: Union[BuySell, str] = None, name: str = None ): super().__init__() self.cap_floor = cap_floor self.termination_date = termination_date self.notional_currency = notional_currency self.notional_amount = notional_amount self.effective_date = effective_date self.strike = strike self.index1 = index1 self.index2 = index2 self.floating_rate_frequency = floating_rate_frequency self.floating_rate_day_count_fraction = floating_rate_day_count_fraction self.floating_rate_business_day_convention = floating_rate_business_day_convention self.reset_delay = reset_delay self.premium = premium self.premium_payment_date = premium_payment_date self.fee = fee self.fee_currency = fee_currency self.fee_payment_date = fee_payment_date self.buy_sell = buy_sell self.name = name @property def asset_class(self) -> AssetClass: """Rates""" return AssetClass.Rates @property def type(self) -> AssetType: """CMSSpreadOptionStrip""" return AssetType.CMSSpreadOptionStrip @property def cap_floor(self) -> str: """Structure type, e.g. Cap, Floor, Straddle, Binary Cap""" return self.__cap_floor @cap_floor.setter def cap_floor(self, value: str): self._property_changed('cap_floor') self.__cap_floor = value @property def termination_date(self) -> Union[Union[datetime.date, str], str]: """Swap termination date, e.g. 2030-05-01, 10y""" return self.__termination_date @termination_date.setter def termination_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('termination_date') self.__termination_date = value @property def notional_currency(self) -> Union[Currency, str]: """Notional currency""" return self.__notional_currency @notional_currency.setter def notional_currency(self, value: Union[Currency, str]): self._property_changed('notional_currency') self.__notional_currency = get_enum_value(Currency, value) @property def notional_amount(self) -> Union[float, str]: """Notional amount""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: Union[float, str]): self._property_changed('notional_amount') self.__notional_amount = value @property def effective_date(self) -> Union[Union[datetime.date, str], str]: """CMS option effective date, e.g. 2019-01-01, 10y""" return self.__effective_date @effective_date.setter def effective_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('effective_date') self.__effective_date = value @property def strike(self) -> Union[float, str]: """Strike as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF, 10/vol, 100k/pv, p=10000, p=10000USD, $200K/BP, or multiple strikes 65.4/-45.8""" return self.__strike @strike.setter def strike(self, value: Union[float, str]): self._property_changed('strike') self.__strike = value @property def index1(self) -> str: """The underlying benchmark to be the first element from i.e. 30yUSD""" return self.__index1 @index1.setter def index1(self, value: str): self._property_changed('index1') self.__index1 = value @property def index2(self) -> str: """The underlying benchmark to be the second element from i.e. 5yUSD""" return self.__index2 @index2.setter def index2(self, value: str): self._property_changed('index2') self.__index2 = value @property def floating_rate_frequency(self) -> str: """Period e.g. 3m, 1y""" return self.__floating_rate_frequency @floating_rate_frequency.setter def floating_rate_frequency(self, value: str): self._property_changed('floating_rate_frequency') self.__floating_rate_frequency = value @property def floating_rate_day_count_fraction(self) -> Union[DayCountFraction, str]: """The day count fraction of the floating rate""" return self.__floating_rate_day_count_fraction @floating_rate_day_count_fraction.setter def floating_rate_day_count_fraction(self, value: Union[DayCountFraction, str]): self._property_changed('floating_rate_day_count_fraction') self.__floating_rate_day_count_fraction = get_enum_value(DayCountFraction, value) @property def floating_rate_business_day_convention(self) -> Union[BusinessDayConvention, str]: """The business day convention of the floating rate""" return self.__floating_rate_business_day_convention @floating_rate_business_day_convention.setter def floating_rate_business_day_convention(self, value: Union[BusinessDayConvention, str]): self._property_changed('floating_rate_business_day_convention') self.__floating_rate_business_day_convention = get_enum_value(BusinessDayConvention, value) @property def reset_delay(self) -> str: """Delay of the reset e.g. 2d""" return self.__reset_delay @reset_delay.setter def reset_delay(self, value: str): self._property_changed('reset_delay') self.__reset_delay = value @property def premium(self) -> Union[float, str]: """The premium""" return self.__premium @premium.setter def premium(self, value: Union[float, str]): self._property_changed('premium') self.__premium = value @property def premium_payment_date(self) -> Union[datetime.date, str]: """Payment date of the premium""" return self.__premium_payment_date @premium_payment_date.setter def premium_payment_date(self, value: Union[datetime.date, str]): self._property_changed('premium_payment_date') self.__premium_payment_date = value @property def fee(self) -> float: """The fee""" return self.__fee @fee.setter def fee(self, value: float): self._property_changed('fee') self.__fee = value @property def fee_currency(self) -> Union[Currency, str]: """Currency of the fee""" return self.__fee_currency @fee_currency.setter def fee_currency(self, value: Union[Currency, str]): self._property_changed('fee_currency') self.__fee_currency = get_enum_value(Currency, value) @property def fee_payment_date(self) -> Union[datetime.date, str]: """Payment date of the fee""" return self.__fee_payment_date @fee_payment_date.setter def fee_payment_date(self, value: Union[datetime.date, str]): self._property_changed('fee_payment_date') self.__fee_payment_date = value @property def buy_sell(self) -> Union[BuySell, str]: """Buy or Sell side of contract""" return self.__buy_sell @buy_sell.setter def buy_sell(self, value: Union[BuySell, str]): self._property_changed('buy_sell') self.__buy_sell = get_enum_value(BuySell, value)
[docs]class IRCap(Instrument): """Object representation of an interest rate cap""" @camel_case_translate def __init__( self, termination_date: Union[datetime.date, str] = None, notional_currency: Union[Currency, str] = None, notional_amount: Union[float, str] = None, effective_date: Union[datetime.date, str] = None, floating_rate_option: str = None, floating_rate_designated_maturity: str = None, floating_rate_frequency: str = None, floating_rate_day_count_fraction: Union[DayCountFraction, str] = None, floating_rate_business_day_convention: Union[BusinessDayConvention, str] = None, cap_rate: Union[float, str] = None, premium: Union[float, str] = None, premium_payment_date: Union[datetime.date, str] = None, fee: float = 0, fee_currency: Union[Currency, str] = None, fee_payment_date: Union[datetime.date, str] = None, name: str = None ): super().__init__() self.termination_date = termination_date self.notional_currency = notional_currency self.notional_amount = notional_amount self.effective_date = effective_date self.floating_rate_option = floating_rate_option self.floating_rate_designated_maturity = floating_rate_designated_maturity self.floating_rate_frequency = floating_rate_frequency self.floating_rate_day_count_fraction = floating_rate_day_count_fraction self.floating_rate_business_day_convention = floating_rate_business_day_convention self.cap_rate = cap_rate self.premium = premium self.premium_payment_date = premium_payment_date self.fee = fee self.fee_currency = fee_currency self.fee_payment_date = fee_payment_date self.name = name @property def asset_class(self) -> AssetClass: """Rates""" return AssetClass.Rates @property def type(self) -> AssetType: """Cap""" return AssetType.Cap @property def termination_date(self) -> Union[datetime.date, str]: """The termination of the cap, e.g. 2025-04-01, 2y""" return self.__termination_date @termination_date.setter def termination_date(self, value: Union[datetime.date, str]): self._property_changed('termination_date') self.__termination_date = value @property def notional_currency(self) -> Union[Currency, str]: """Notional currency""" return self.__notional_currency @notional_currency.setter def notional_currency(self, value: Union[Currency, str]): self._property_changed('notional_currency') self.__notional_currency = get_enum_value(Currency, value) @property def notional_amount(self) -> Union[float, str]: """Notional amount""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: Union[float, str]): self._property_changed('notional_amount') self.__notional_amount = value @property def effective_date(self) -> Union[datetime.date, str]: """The date on which the cap becomes effective""" return self.__effective_date @effective_date.setter def effective_date(self, value: Union[datetime.date, str]): self._property_changed('effective_date') self.__effective_date = value @property def floating_rate_option(self) -> str: """The underlying benchmark for the floating rate, e.g. USD-LIBOR-BBA, EUR-EURIBOR- TELERATE""" return self.__floating_rate_option @floating_rate_option.setter def floating_rate_option(self, value: str): self._property_changed('floating_rate_option') self.__floating_rate_option = value @property def floating_rate_designated_maturity(self) -> str: """Tenor of the floatingRateOption, e.g. 3m, 6m""" return self.__floating_rate_designated_maturity @floating_rate_designated_maturity.setter def floating_rate_designated_maturity(self, value: str): self._property_changed('floating_rate_designated_maturity') self.__floating_rate_designated_maturity = value @property def floating_rate_frequency(self) -> str: """The frequency of floating payments, e.g. 3m""" return self.__floating_rate_frequency @floating_rate_frequency.setter def floating_rate_frequency(self, value: str): self._property_changed('floating_rate_frequency') self.__floating_rate_frequency = value @property def floating_rate_day_count_fraction(self) -> Union[DayCountFraction, str]: """The day count fraction of the floating rate""" return self.__floating_rate_day_count_fraction @floating_rate_day_count_fraction.setter def floating_rate_day_count_fraction(self, value: Union[DayCountFraction, str]): self._property_changed('floating_rate_day_count_fraction') self.__floating_rate_day_count_fraction = get_enum_value(DayCountFraction, value) @property def floating_rate_business_day_convention(self) -> Union[BusinessDayConvention, str]: """The business day convention of the floating rate""" return self.__floating_rate_business_day_convention @floating_rate_business_day_convention.setter def floating_rate_business_day_convention(self, value: Union[BusinessDayConvention, str]): self._property_changed('floating_rate_business_day_convention') self.__floating_rate_business_day_convention = get_enum_value(BusinessDayConvention, value) @property def cap_rate(self) -> Union[float, str]: """The rate of this cap, as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF""" return self.__cap_rate @cap_rate.setter def cap_rate(self, value: Union[float, str]): self._property_changed('cap_rate') self.__cap_rate = value @property def premium(self) -> Union[float, str]: """The premium""" return self.__premium @premium.setter def premium(self, value: Union[float, str]): self._property_changed('premium') self.__premium = value @property def premium_payment_date(self) -> Union[datetime.date, str]: """Payment date of the premium""" return self.__premium_payment_date @premium_payment_date.setter def premium_payment_date(self, value: Union[datetime.date, str]): self._property_changed('premium_payment_date') self.__premium_payment_date = value @property def fee(self) -> float: """The fee""" return self.__fee @fee.setter def fee(self, value: float): self._property_changed('fee') self.__fee = value @property def fee_currency(self) -> Union[Currency, str]: """Currency of the fee""" return self.__fee_currency @fee_currency.setter def fee_currency(self, value: Union[Currency, str]): self._property_changed('fee_currency') self.__fee_currency = get_enum_value(Currency, value) @property def fee_payment_date(self) -> Union[datetime.date, str]: """Payment date of the fee""" return self.__fee_payment_date @fee_payment_date.setter def fee_payment_date(self, value: Union[datetime.date, str]): self._property_changed('fee_payment_date') self.__fee_payment_date = value
class IRFixedLeg(Instrument): """A strip of vanilla fixed rate cashflows""" @camel_case_translate def __init__( self, buy_sell: Union[BuySell, str] = None, fixed_rate_day_count_fraction: Union[DayCountFraction, str] = None, fixed_first_stub: Union[Union[datetime.date, str], str] = None, fixed_rate_frequency: str = None, fixed_holidays: str = None, fixed_last_stub: Union[Union[datetime.date, str], str] = None, fixed_rate_business_day_convention: Union[BusinessDayConvention, str] = None, fixed_rate: Union[float, str] = None, termination_date: Union[Union[datetime.date, str], str] = None, notional_currency: Union[Currency, str] = None, principal_exchange: Union[PrincipalExchange, str] = None, roll_convention: str = None, notional_amount: Union[float, str] = None, effective_date: Union[Union[datetime.date, str], str] = None, name: str = None ): super().__init__() self.buy_sell = buy_sell self.fixed_rate_day_count_fraction = fixed_rate_day_count_fraction self.fixed_first_stub = fixed_first_stub self.fixed_rate_frequency = fixed_rate_frequency self.fixed_holidays = fixed_holidays self.fixed_last_stub = fixed_last_stub self.fixed_rate_business_day_convention = fixed_rate_business_day_convention self.fixed_rate = fixed_rate self.termination_date = termination_date self.notional_currency = notional_currency self.principal_exchange = principal_exchange self.roll_convention = roll_convention self.notional_amount = notional_amount self.effective_date = effective_date self.name = name @property def asset_class(self) -> AssetClass: """Rates""" return AssetClass.Rates @property def type(self) -> AssetType: """FixedLeg""" return AssetType.FixedLeg @property def buy_sell(self) -> Union[BuySell, str]: """Buy or Sell side of contract""" return self.__buy_sell @buy_sell.setter def buy_sell(self, value: Union[BuySell, str]): self._property_changed('buy_sell') self.__buy_sell = get_enum_value(BuySell, value) @property def fixed_rate_day_count_fraction(self) -> Union[DayCountFraction, str]: """The day count fraction of the fixed rate""" return self.__fixed_rate_day_count_fraction @fixed_rate_day_count_fraction.setter def fixed_rate_day_count_fraction(self, value: Union[DayCountFraction, str]): self._property_changed('fixed_rate_day_count_fraction') self.__fixed_rate_day_count_fraction = get_enum_value(DayCountFraction, value) @property def fixed_first_stub(self) -> Union[Union[datetime.date, str], str]: """The date of the first stub for fixed leg""" return self.__fixed_first_stub @fixed_first_stub.setter def fixed_first_stub(self, value: Union[Union[datetime.date, str], str]): self._property_changed('fixed_first_stub') self.__fixed_first_stub = value @property def fixed_rate_frequency(self) -> str: """The frequency of fixed payments, e.g. 6m""" return self.__fixed_rate_frequency @fixed_rate_frequency.setter def fixed_rate_frequency(self, value: str): self._property_changed('fixed_rate_frequency') self.__fixed_rate_frequency = value @property def fixed_holidays(self) -> str: """The accrual calendar for fixed leg""" return self.__fixed_holidays @fixed_holidays.setter def fixed_holidays(self, value: str): self._property_changed('fixed_holidays') self.__fixed_holidays = value @property def fixed_last_stub(self) -> Union[Union[datetime.date, str], str]: """The date of the last stub for fixed leg""" return self.__fixed_last_stub @fixed_last_stub.setter def fixed_last_stub(self, value: Union[Union[datetime.date, str], str]): self._property_changed('fixed_last_stub') self.__fixed_last_stub = value @property def fixed_rate_business_day_convention(self) -> Union[BusinessDayConvention, str]: """The business day convention for the fixed rate""" return self.__fixed_rate_business_day_convention @fixed_rate_business_day_convention.setter def fixed_rate_business_day_convention(self, value: Union[BusinessDayConvention, str]): self._property_changed('fixed_rate_business_day_convention') self.__fixed_rate_business_day_convention = get_enum_value(BusinessDayConvention, value) @property def fixed_rate(self) -> Union[float, str]: """The coupon of the fixed leg""" return self.__fixed_rate @fixed_rate.setter def fixed_rate(self, value: Union[float, str]): self._property_changed('fixed_rate') self.__fixed_rate = value @property def termination_date(self) -> Union[Union[datetime.date, str], str]: """The termination of the leg, e.g. 2050-04-01, 10y""" return self.__termination_date @termination_date.setter def termination_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('termination_date') self.__termination_date = value @property def notional_currency(self) -> Union[Currency, str]: """Notional currency""" return self.__notional_currency @notional_currency.setter def notional_currency(self, value: Union[Currency, str]): self._property_changed('notional_currency') self.__notional_currency = get_enum_value(Currency, value) @property def principal_exchange(self) -> Union[PrincipalExchange, str]: """When the exchange of principal is done""" return self.__principal_exchange @principal_exchange.setter def principal_exchange(self, value: Union[PrincipalExchange, str]): self._property_changed('principal_exchange') self.__principal_exchange = get_enum_value(PrincipalExchange, value) @property def roll_convention(self) -> str: """The roll convention""" return self.__roll_convention @roll_convention.setter def roll_convention(self, value: str): self._property_changed('roll_convention') self.__roll_convention = value @property def notional_amount(self) -> Union[float, str]: """Notional amount""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: Union[float, str]): self._property_changed('notional_amount') self.__notional_amount = value @property def effective_date(self) -> Union[Union[datetime.date, str], str]: """The date on which the instrument becomes effective""" return self.__effective_date @effective_date.setter def effective_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('effective_date') self.__effective_date = value class IRFloatLeg(Instrument): """A strip of vanilla floating rate cashflows""" @camel_case_translate def __init__( self, buy_sell: Union[BuySell, str] = None, floating_rate_for_the_initial_calculation_period: float = None, floating_rate_day_count_fraction: Union[DayCountFraction, str] = None, floating_first_stub: Union[Union[datetime.date, str], str] = None, floating_rate_frequency: str = None, floating_holidays: str = None, floating_last_stub: Union[Union[datetime.date, str], str] = None, floating_rate_business_day_convention: Union[BusinessDayConvention, str] = None, floating_rate_option: str = None, floating_rate_designated_maturity: str = None, termination_date: Union[Union[datetime.date, str], str] = None, notional_currency: Union[Currency, str] = None, principal_exchange: Union[PrincipalExchange, str] = None, roll_convention: str = None, notional_amount: Union[float, str] = None, floating_rate_spread: Union[float, str] = None, effective_date: Union[Union[datetime.date, str], str] = None, name: str = None ): super().__init__() self.buy_sell = buy_sell self.floating_rate_for_the_initial_calculation_period = floating_rate_for_the_initial_calculation_period self.floating_rate_day_count_fraction = floating_rate_day_count_fraction self.floating_first_stub = floating_first_stub self.floating_rate_frequency = floating_rate_frequency self.floating_holidays = floating_holidays self.floating_last_stub = floating_last_stub self.floating_rate_business_day_convention = floating_rate_business_day_convention self.floating_rate_option = floating_rate_option self.floating_rate_designated_maturity = floating_rate_designated_maturity self.termination_date = termination_date self.notional_currency = notional_currency self.principal_exchange = principal_exchange self.roll_convention = roll_convention self.notional_amount = notional_amount self.floating_rate_spread = floating_rate_spread self.effective_date = effective_date self.name = name @property def asset_class(self) -> AssetClass: """Rates""" return AssetClass.Rates @property def type(self) -> AssetType: """FloatLeg""" return AssetType.FloatLeg @property def buy_sell(self) -> Union[BuySell, str]: """Buy or Sell side of contract""" return self.__buy_sell @buy_sell.setter def buy_sell(self, value: Union[BuySell, str]): self._property_changed('buy_sell') self.__buy_sell = get_enum_value(BuySell, value) @property def floating_rate_for_the_initial_calculation_period(self) -> float: """First fixing""" return self.__floating_rate_for_the_initial_calculation_period @floating_rate_for_the_initial_calculation_period.setter def floating_rate_for_the_initial_calculation_period(self, value: float): self._property_changed('floating_rate_for_the_initial_calculation_period') self.__floating_rate_for_the_initial_calculation_period = value @property def floating_rate_day_count_fraction(self) -> Union[DayCountFraction, str]: """The day count fraction of the floating rate""" return self.__floating_rate_day_count_fraction @floating_rate_day_count_fraction.setter def floating_rate_day_count_fraction(self, value: Union[DayCountFraction, str]): self._property_changed('floating_rate_day_count_fraction') self.__floating_rate_day_count_fraction = get_enum_value(DayCountFraction, value) @property def floating_first_stub(self) -> Union[Union[datetime.date, str], str]: """The date of the first stub for floating leg""" return self.__floating_first_stub @floating_first_stub.setter def floating_first_stub(self, value: Union[Union[datetime.date, str], str]): self._property_changed('floating_first_stub') self.__floating_first_stub = value @property def floating_rate_frequency(self) -> str: """The frequency of floating payments, e.g. 6m""" return self.__floating_rate_frequency @floating_rate_frequency.setter def floating_rate_frequency(self, value: str): self._property_changed('floating_rate_frequency') self.__floating_rate_frequency = value @property def floating_holidays(self) -> str: """The accrual calendar for floating leg""" return self.__floating_holidays @floating_holidays.setter def floating_holidays(self, value: str): self._property_changed('floating_holidays') self.__floating_holidays = value @property def floating_last_stub(self) -> Union[Union[datetime.date, str], str]: """The date of the last stub for floating leg""" return self.__floating_last_stub @floating_last_stub.setter def floating_last_stub(self, value: Union[Union[datetime.date, str], str]): self._property_changed('floating_last_stub') self.__floating_last_stub = value @property def floating_rate_business_day_convention(self) -> Union[BusinessDayConvention, str]: """The business day convention for the floating rate""" return self.__floating_rate_business_day_convention @floating_rate_business_day_convention.setter def floating_rate_business_day_convention(self, value: Union[BusinessDayConvention, str]): self._property_changed('floating_rate_business_day_convention') self.__floating_rate_business_day_convention = get_enum_value(BusinessDayConvention, value) @property def floating_rate_option(self) -> str: """The underlying benchmark for the floating rate, e.g. USD-LIBOR-BBA, EUR-EURIBOR- TELERATE""" return self.__floating_rate_option @floating_rate_option.setter def floating_rate_option(self, value: str): self._property_changed('floating_rate_option') self.__floating_rate_option = value @property def floating_rate_designated_maturity(self) -> str: """Tenor of the floatingRateOption, e.g. 3m, 6m""" return self.__floating_rate_designated_maturity @floating_rate_designated_maturity.setter def floating_rate_designated_maturity(self, value: str): self._property_changed('floating_rate_designated_maturity') self.__floating_rate_designated_maturity = value @property def termination_date(self) -> Union[Union[datetime.date, str], str]: """The termination of the leg, e.g. 2050-04-01, 10y""" return self.__termination_date @termination_date.setter def termination_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('termination_date') self.__termination_date = value @property def notional_currency(self) -> Union[Currency, str]: """Notional currency""" return self.__notional_currency @notional_currency.setter def notional_currency(self, value: Union[Currency, str]): self._property_changed('notional_currency') self.__notional_currency = get_enum_value(Currency, value) @property def principal_exchange(self) -> Union[PrincipalExchange, str]: """When the exchange of principals is done""" return self.__principal_exchange @principal_exchange.setter def principal_exchange(self, value: Union[PrincipalExchange, str]): self._property_changed('principal_exchange') self.__principal_exchange = get_enum_value(PrincipalExchange, value) @property def roll_convention(self) -> str: """The roll convention""" return self.__roll_convention @roll_convention.setter def roll_convention(self, value: str): self._property_changed('roll_convention') self.__roll_convention = value @property def notional_amount(self) -> Union[float, str]: """Notional amount""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: Union[float, str]): self._property_changed('notional_amount') self.__notional_amount = value @property def floating_rate_spread(self) -> Union[float, str]: """The spread over the floating rate""" return self.__floating_rate_spread @floating_rate_spread.setter def floating_rate_spread(self, value: Union[float, str]): self._property_changed('floating_rate_spread') self.__floating_rate_spread = value @property def effective_date(self) -> Union[Union[datetime.date, str], str]: """The date on which the instrument becomes effective""" return self.__effective_date @effective_date.setter def effective_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('effective_date') self.__effective_date = value
[docs]class IRFloor(Instrument): """Object representation of an interest rate floor""" @camel_case_translate def __init__( self, termination_date: Union[datetime.date, str] = None, notional_currency: Union[Currency, str] = None, notional_amount: Union[float, str] = None, effective_date: Union[datetime.date, str] = None, floating_rate_option: str = None, floating_rate_designated_maturity: str = None, floating_rate_frequency: str = None, floating_rate_day_count_fraction: Union[DayCountFraction, str] = None, floating_rate_business_day_convention: Union[BusinessDayConvention, str] = None, floor_rate: Union[float, str] = None, premium: Union[float, str] = None, premium_payment_date: Union[datetime.date, str] = None, fee: float = 0, fee_currency: Union[Currency, str] = None, fee_payment_date: Union[datetime.date, str] = None, name: str = None ): super().__init__() self.termination_date = termination_date self.notional_currency = notional_currency self.notional_amount = notional_amount self.effective_date = effective_date self.floating_rate_option = floating_rate_option self.floating_rate_designated_maturity = floating_rate_designated_maturity self.floating_rate_frequency = floating_rate_frequency self.floating_rate_day_count_fraction = floating_rate_day_count_fraction self.floating_rate_business_day_convention = floating_rate_business_day_convention self.floor_rate = floor_rate self.premium = premium self.premium_payment_date = premium_payment_date self.fee = fee self.fee_currency = fee_currency self.fee_payment_date = fee_payment_date self.name = name @property def asset_class(self) -> AssetClass: """Rates""" return AssetClass.Rates @property def type(self) -> AssetType: """Floor""" return AssetType.Floor @property def termination_date(self) -> Union[datetime.date, str]: """The termination of the floor, e.g. 2025-04-01, 2y""" return self.__termination_date @termination_date.setter def termination_date(self, value: Union[datetime.date, str]): self._property_changed('termination_date') self.__termination_date = value @property def notional_currency(self) -> Union[Currency, str]: """Notional currency""" return self.__notional_currency @notional_currency.setter def notional_currency(self, value: Union[Currency, str]): self._property_changed('notional_currency') self.__notional_currency = get_enum_value(Currency, value) @property def notional_amount(self) -> Union[float, str]: """Notional amount""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: Union[float, str]): self._property_changed('notional_amount') self.__notional_amount = value @property def effective_date(self) -> Union[datetime.date, str]: """The date on which the floor becomes effective""" return self.__effective_date @effective_date.setter def effective_date(self, value: Union[datetime.date, str]): self._property_changed('effective_date') self.__effective_date = value @property def floating_rate_option(self) -> str: """The underlying benchmark for the floating rate, e.g. USD-LIBOR-BBA, EUR-EURIBOR- TELERATE""" return self.__floating_rate_option @floating_rate_option.setter def floating_rate_option(self, value: str): self._property_changed('floating_rate_option') self.__floating_rate_option = value @property def floating_rate_designated_maturity(self) -> str: """Tenor of the floatingRateOption, e.g. 3m, 6m""" return self.__floating_rate_designated_maturity @floating_rate_designated_maturity.setter def floating_rate_designated_maturity(self, value: str): self._property_changed('floating_rate_designated_maturity') self.__floating_rate_designated_maturity = value @property def floating_rate_frequency(self) -> str: """The frequency of floating payments, e.g. 3m""" return self.__floating_rate_frequency @floating_rate_frequency.setter def floating_rate_frequency(self, value: str): self._property_changed('floating_rate_frequency') self.__floating_rate_frequency = value @property def floating_rate_day_count_fraction(self) -> Union[DayCountFraction, str]: """The day count fraction of the floating rate""" return self.__floating_rate_day_count_fraction @floating_rate_day_count_fraction.setter def floating_rate_day_count_fraction(self, value: Union[DayCountFraction, str]): self._property_changed('floating_rate_day_count_fraction') self.__floating_rate_day_count_fraction = get_enum_value(DayCountFraction, value) @property def floating_rate_business_day_convention(self) -> Union[BusinessDayConvention, str]: """The business day convention of the floating rate""" return self.__floating_rate_business_day_convention @floating_rate_business_day_convention.setter def floating_rate_business_day_convention(self, value: Union[BusinessDayConvention, str]): self._property_changed('floating_rate_business_day_convention') self.__floating_rate_business_day_convention = get_enum_value(BusinessDayConvention, value) @property def floor_rate(self) -> Union[float, str]: """The rate of this floor, as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF""" return self.__floor_rate @floor_rate.setter def floor_rate(self, value: Union[float, str]): self._property_changed('floor_rate') self.__floor_rate = value @property def premium(self) -> Union[float, str]: """The premium""" return self.__premium @premium.setter def premium(self, value: Union[float, str]): self._property_changed('premium') self.__premium = value @property def premium_payment_date(self) -> Union[datetime.date, str]: """Payment date of the premium""" return self.__premium_payment_date @premium_payment_date.setter def premium_payment_date(self, value: Union[datetime.date, str]): self._property_changed('premium_payment_date') self.__premium_payment_date = value @property def fee(self) -> float: """The fee""" return self.__fee @fee.setter def fee(self, value: float): self._property_changed('fee') self.__fee = value @property def fee_currency(self) -> Union[Currency, str]: """Currency of the fee""" return self.__fee_currency @fee_currency.setter def fee_currency(self, value: Union[Currency, str]): self._property_changed('fee_currency') self.__fee_currency = get_enum_value(Currency, value) @property def fee_payment_date(self) -> Union[datetime.date, str]: """Payment date of the fee""" return self.__fee_payment_date @fee_payment_date.setter def fee_payment_date(self, value: Union[datetime.date, str]): self._property_changed('fee_payment_date') self.__fee_payment_date = value
[docs]class IRSwap(Instrument): """A vanilla interest rate swap of fixed vs floating cashflows""" @camel_case_translate def __init__( self, pay_or_receive: Union[PayReceive, str] = None, termination_date: Union[Union[datetime.date, str], str] = None, notional_currency: Union[Currency, str] = None, notional_amount: Union[float, str] = None, effective_date: Union[Union[datetime.date, str], str] = None, principal_exchange: Union[PrincipalExchange, str] = None, floating_rate_for_the_initial_calculation_period: float = None, floating_rate_option: str = None, floating_rate_designated_maturity: str = None, floating_rate_spread: Union[float, str] = None, floating_rate_frequency: str = None, floating_rate_day_count_fraction: Union[DayCountFraction, str] = None, floating_rate_business_day_convention: Union[BusinessDayConvention, str] = None, fixed_rate: Union[float, str] = None, fixed_rate_frequency: str = None, fixed_rate_day_count_fraction: Union[DayCountFraction, str] = None, fixed_rate_business_day_convention: Union[BusinessDayConvention, str] = None, fee: float = 0, fee_currency: Union[Currency, str] = None, fee_payment_date: Union[datetime.date, str] = None, clearing_house: Union[SwapClearingHouse, str] = None, fixed_first_stub: Union[Union[datetime.date, str], str] = None, floating_first_stub: Union[Union[datetime.date, str], str] = None, fixed_last_stub: Union[Union[datetime.date, str], str] = None, floating_last_stub: Union[Union[datetime.date, str], str] = None, fixed_holidays: str = None, floating_holidays: str = None, roll_convention: str = None, name: str = None ): super().__init__() self.pay_or_receive = pay_or_receive self.termination_date = termination_date self.notional_currency = notional_currency self.notional_amount = notional_amount self.effective_date = effective_date self.principal_exchange = principal_exchange self.floating_rate_for_the_initial_calculation_period = floating_rate_for_the_initial_calculation_period self.floating_rate_option = floating_rate_option self.floating_rate_designated_maturity = floating_rate_designated_maturity self.floating_rate_spread = floating_rate_spread self.floating_rate_frequency = floating_rate_frequency self.floating_rate_day_count_fraction = floating_rate_day_count_fraction self.floating_rate_business_day_convention = floating_rate_business_day_convention self.fixed_rate = fixed_rate self.fixed_rate_frequency = fixed_rate_frequency self.fixed_rate_day_count_fraction = fixed_rate_day_count_fraction self.fixed_rate_business_day_convention = fixed_rate_business_day_convention self.fee = fee self.fee_currency = fee_currency self.fee_payment_date = fee_payment_date self.clearing_house = clearing_house self.fixed_first_stub = fixed_first_stub self.floating_first_stub = floating_first_stub self.fixed_last_stub = fixed_last_stub self.floating_last_stub = floating_last_stub self.fixed_holidays = fixed_holidays self.floating_holidays = floating_holidays self.roll_convention = roll_convention self.name = name @property def asset_class(self) -> AssetClass: """Rates""" return AssetClass.Rates @property def type(self) -> AssetType: """Swap""" return AssetType.Swap @property def pay_or_receive(self) -> Union[PayReceive, str]: """Pay or receive fixed""" return self.__pay_or_receive @pay_or_receive.setter def pay_or_receive(self, value: Union[PayReceive, str]): self._property_changed('pay_or_receive') self.__pay_or_receive = get_enum_value(PayReceive, value) @property def termination_date(self) -> Union[Union[datetime.date, str], str]: """The termination of the swap, e.g. 2050-04-01, 10y""" return self.__termination_date @termination_date.setter def termination_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('termination_date') self.__termination_date = value @property def notional_currency(self) -> Union[Currency, str]: """Notional currency""" return self.__notional_currency @notional_currency.setter def notional_currency(self, value: Union[Currency, str]): self._property_changed('notional_currency') self.__notional_currency = get_enum_value(Currency, value) @property def notional_amount(self) -> Union[float, str]: """Notional amount""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: Union[float, str]): self._property_changed('notional_amount') self.__notional_amount = value @property def effective_date(self) -> Union[Union[datetime.date, str], str]: """The date on which the swap becomes effective""" return self.__effective_date @effective_date.setter def effective_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('effective_date') self.__effective_date = value @property def principal_exchange(self) -> Union[PrincipalExchange, str]: """The date on which the swap becomes effective""" return self.__principal_exchange @principal_exchange.setter def principal_exchange(self, value: Union[PrincipalExchange, str]): self._property_changed('principal_exchange') self.__principal_exchange = get_enum_value(PrincipalExchange, value) @property def floating_rate_for_the_initial_calculation_period(self) -> float: """First fixing""" return self.__floating_rate_for_the_initial_calculation_period @floating_rate_for_the_initial_calculation_period.setter def floating_rate_for_the_initial_calculation_period(self, value: float): self._property_changed('floating_rate_for_the_initial_calculation_period') self.__floating_rate_for_the_initial_calculation_period = value @property def floating_rate_option(self) -> str: """The underlying benchmark for the floating rate, e.g. USD-LIBOR-BBA, EUR-EURIBOR- TELERATE""" return self.__floating_rate_option @floating_rate_option.setter def floating_rate_option(self, value: str): self._property_changed('floating_rate_option') self.__floating_rate_option = value @property def floating_rate_designated_maturity(self) -> str: """Tenor of the floatingRateOption, e.g. 3m, 6m""" return self.__floating_rate_designated_maturity @floating_rate_designated_maturity.setter def floating_rate_designated_maturity(self, value: str): self._property_changed('floating_rate_designated_maturity') self.__floating_rate_designated_maturity = value @property def floating_rate_spread(self) -> Union[float, str]: """The spread over the floating rate""" return self.__floating_rate_spread @floating_rate_spread.setter def floating_rate_spread(self, value: Union[float, str]): self._property_changed('floating_rate_spread') self.__floating_rate_spread = value @property def floating_rate_frequency(self) -> str: """The frequency of floating payments, e.g. 3m""" return self.__floating_rate_frequency @floating_rate_frequency.setter def floating_rate_frequency(self, value: str): self._property_changed('floating_rate_frequency') self.__floating_rate_frequency = value @property def floating_rate_day_count_fraction(self) -> Union[DayCountFraction, str]: """The day count fraction of the floating rate""" return self.__floating_rate_day_count_fraction @floating_rate_day_count_fraction.setter def floating_rate_day_count_fraction(self, value: Union[DayCountFraction, str]): self._property_changed('floating_rate_day_count_fraction') self.__floating_rate_day_count_fraction = get_enum_value(DayCountFraction, value) @property def floating_rate_business_day_convention(self) -> Union[BusinessDayConvention, str]: """The business day convention of the floating rate""" return self.__floating_rate_business_day_convention @floating_rate_business_day_convention.setter def floating_rate_business_day_convention(self, value: Union[BusinessDayConvention, str]): self._property_changed('floating_rate_business_day_convention') self.__floating_rate_business_day_convention = get_enum_value(BusinessDayConvention, value) @property def fixed_rate(self) -> Union[float, str]: """The coupon of the fixed leg""" return self.__fixed_rate @fixed_rate.setter def fixed_rate(self, value: Union[float, str]): self._property_changed('fixed_rate') self.__fixed_rate = value @property def fixed_rate_frequency(self) -> str: """The frequency of fixed payments, e.g. 6m""" return self.__fixed_rate_frequency @fixed_rate_frequency.setter def fixed_rate_frequency(self, value: str): self._property_changed('fixed_rate_frequency') self.__fixed_rate_frequency = value @property def fixed_rate_day_count_fraction(self) -> Union[DayCountFraction, str]: """The day count fraction for the fixed rate""" return self.__fixed_rate_day_count_fraction @fixed_rate_day_count_fraction.setter def fixed_rate_day_count_fraction(self, value: Union[DayCountFraction, str]): self._property_changed('fixed_rate_day_count_fraction') self.__fixed_rate_day_count_fraction = get_enum_value(DayCountFraction, value) @property def fixed_rate_business_day_convention(self) -> Union[BusinessDayConvention, str]: """The business day convention for the fixed rate""" return self.__fixed_rate_business_day_convention @fixed_rate_business_day_convention.setter def fixed_rate_business_day_convention(self, value: Union[BusinessDayConvention, str]): self._property_changed('fixed_rate_business_day_convention') self.__fixed_rate_business_day_convention = get_enum_value(BusinessDayConvention, value) @property def fee(self) -> float: """The fee""" return self.__fee @fee.setter def fee(self, value: float): self._property_changed('fee') self.__fee = value @property def fee_currency(self) -> Union[Currency, str]: """Currency of the fee""" return self.__fee_currency @fee_currency.setter def fee_currency(self, value: Union[Currency, str]): self._property_changed('fee_currency') self.__fee_currency = get_enum_value(Currency, value) @property def fee_payment_date(self) -> Union[datetime.date, str]: """Payment date of the fee""" return self.__fee_payment_date @fee_payment_date.setter def fee_payment_date(self, value: Union[datetime.date, str]): self._property_changed('fee_payment_date') self.__fee_payment_date = value @property def clearing_house(self) -> Union[SwapClearingHouse, str]: """Swap Clearing House""" return self.__clearing_house @clearing_house.setter def clearing_house(self, value: Union[SwapClearingHouse, str]): self._property_changed('clearing_house') self.__clearing_house = get_enum_value(SwapClearingHouse, value) @property def fixed_first_stub(self) -> Union[Union[datetime.date, str], str]: """The date of the first stub for fixed leg""" return self.__fixed_first_stub @fixed_first_stub.setter def fixed_first_stub(self, value: Union[Union[datetime.date, str], str]): self._property_changed('fixed_first_stub') self.__fixed_first_stub = value @property def floating_first_stub(self) -> Union[Union[datetime.date, str], str]: """The date of the first stub for floating leg""" return self.__floating_first_stub @floating_first_stub.setter def floating_first_stub(self, value: Union[Union[datetime.date, str], str]): self._property_changed('floating_first_stub') self.__floating_first_stub = value @property def fixed_last_stub(self) -> Union[Union[datetime.date, str], str]: """The date of the last stub for fixed leg""" return self.__fixed_last_stub @fixed_last_stub.setter def fixed_last_stub(self, value: Union[Union[datetime.date, str], str]): self._property_changed('fixed_last_stub') self.__fixed_last_stub = value @property def floating_last_stub(self) -> Union[Union[datetime.date, str], str]: """The date of the last stub for floating leg""" return self.__floating_last_stub @floating_last_stub.setter def floating_last_stub(self, value: Union[Union[datetime.date, str], str]): self._property_changed('floating_last_stub') self.__floating_last_stub = value @property def fixed_holidays(self) -> str: """The accrual calendar for fixed leg""" return self.__fixed_holidays @fixed_holidays.setter def fixed_holidays(self, value: str): self._property_changed('fixed_holidays') self.__fixed_holidays = value @property def floating_holidays(self) -> str: """The accrual calendar for floating leg""" return self.__floating_holidays @floating_holidays.setter def floating_holidays(self, value: str): self._property_changed('floating_holidays') self.__floating_holidays = value @property def roll_convention(self) -> str: """The roll convention""" return self.__roll_convention @roll_convention.setter def roll_convention(self, value: str): self._property_changed('roll_convention') self.__roll_convention = value
[docs]class IRSwaption(Instrument): """Object representation of a swaption""" @camel_case_translate def __init__( self, pay_or_receive: str = None, termination_date: Union[Union[datetime.date, str], str] = None, notional_currency: Union[Currency, str] = None, effective_date: Union[Union[datetime.date, str], str] = None, notional_amount: Union[float, str] = None, expiration_date: Union[Union[datetime.date, str], str] = None, floating_rate_option: str = None, floating_rate_designated_maturity: str = None, floating_rate_spread: float = None, floating_rate_frequency: str = None, floating_rate_day_count_fraction: Union[DayCountFraction, str] = None, floating_rate_business_day_convention: Union[BusinessDayConvention, str] = None, fixed_rate_frequency: str = None, fixed_rate_day_count_fraction: Union[DayCountFraction, str] = None, fixed_rate_business_day_convention: Union[BusinessDayConvention, str] = None, strike: Union[float, str] = None, premium: Union[float, str] = None, premium_payment_date: Union[datetime.date, str] = None, fee: float = 0, fee_currency: Union[Currency, str] = None, fee_payment_date: Union[datetime.date, str] = None, clearing_house: Union[SwapClearingHouse, str] = None, settlement: Union[SwapSettlement, str] = None, buy_sell: Union[BuySell, str] = None, name: str = None ): super().__init__() self.pay_or_receive = pay_or_receive self.termination_date = termination_date self.notional_currency = notional_currency self.effective_date = effective_date self.notional_amount = notional_amount self.expiration_date = expiration_date self.floating_rate_option = floating_rate_option self.floating_rate_designated_maturity = floating_rate_designated_maturity self.floating_rate_spread = floating_rate_spread self.floating_rate_frequency = floating_rate_frequency self.floating_rate_day_count_fraction = floating_rate_day_count_fraction self.floating_rate_business_day_convention = floating_rate_business_day_convention self.fixed_rate_frequency = fixed_rate_frequency self.fixed_rate_day_count_fraction = fixed_rate_day_count_fraction self.fixed_rate_business_day_convention = fixed_rate_business_day_convention self.strike = strike self.premium = premium self.premium_payment_date = premium_payment_date self.fee = fee self.fee_currency = fee_currency self.fee_payment_date = fee_payment_date self.clearing_house = clearing_house self.settlement = settlement self.buy_sell = buy_sell self.name = name @property def asset_class(self) -> AssetClass: """Rates""" return AssetClass.Rates @property def type(self) -> AssetType: """Swaption""" return AssetType.Swaption @property def pay_or_receive(self) -> str: """Pay or receive fixed""" return self.__pay_or_receive @pay_or_receive.setter def pay_or_receive(self, value: str): self._property_changed('pay_or_receive') self.__pay_or_receive = value @property def termination_date(self) -> Union[Union[datetime.date, str], str]: """Swaption termination date, e.g. 2030-05-01, 10y""" return self.__termination_date @termination_date.setter def termination_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('termination_date') self.__termination_date = value @property def notional_currency(self) -> Union[Currency, str]: """Notional currency""" return self.__notional_currency @notional_currency.setter def notional_currency(self, value: Union[Currency, str]): self._property_changed('notional_currency') self.__notional_currency = get_enum_value(Currency, value) @property def effective_date(self) -> Union[Union[datetime.date, str], str]: """Swaption effective date, e.g. 2019-01-01, 10y""" return self.__effective_date @effective_date.setter def effective_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('effective_date') self.__effective_date = value @property def notional_amount(self) -> Union[float, str]: """Notional amount""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: Union[float, str]): self._property_changed('notional_amount') self.__notional_amount = value @property def expiration_date(self) -> Union[Union[datetime.date, str], str]: """Swaption expiration date, 2020-05-01, 3m""" return self.__expiration_date @expiration_date.setter def expiration_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('expiration_date') self.__expiration_date = value @property def floating_rate_option(self) -> str: """The underlying benchmark for the floating rate, e.g. USD-LIBOR-BBA, EUR-EURIBOR- TELERATE""" return self.__floating_rate_option @floating_rate_option.setter def floating_rate_option(self, value: str): self._property_changed('floating_rate_option') self.__floating_rate_option = value @property def floating_rate_designated_maturity(self) -> str: """Tenor""" return self.__floating_rate_designated_maturity @floating_rate_designated_maturity.setter def floating_rate_designated_maturity(self, value: str): self._property_changed('floating_rate_designated_maturity') self.__floating_rate_designated_maturity = value @property def floating_rate_spread(self) -> float: """The spread over the floating rate""" return self.__floating_rate_spread @floating_rate_spread.setter def floating_rate_spread(self, value: float): self._property_changed('floating_rate_spread') self.__floating_rate_spread = value @property def floating_rate_frequency(self) -> str: """The frequency of floating payments, e.g. 3m""" return self.__floating_rate_frequency @floating_rate_frequency.setter def floating_rate_frequency(self, value: str): self._property_changed('floating_rate_frequency') self.__floating_rate_frequency = value @property def floating_rate_day_count_fraction(self) -> Union[DayCountFraction, str]: """The day count fraction of the floating rate""" return self.__floating_rate_day_count_fraction @floating_rate_day_count_fraction.setter def floating_rate_day_count_fraction(self, value: Union[DayCountFraction, str]): self._property_changed('floating_rate_day_count_fraction') self.__floating_rate_day_count_fraction = get_enum_value(DayCountFraction, value) @property def floating_rate_business_day_convention(self) -> Union[BusinessDayConvention, str]: """The business day convention of the floating rate""" return self.__floating_rate_business_day_convention @floating_rate_business_day_convention.setter def floating_rate_business_day_convention(self, value: Union[BusinessDayConvention, str]): self._property_changed('floating_rate_business_day_convention') self.__floating_rate_business_day_convention = get_enum_value(BusinessDayConvention, value) @property def fixed_rate_frequency(self) -> str: """The frequency of fixed payments, e.g. 6m""" return self.__fixed_rate_frequency @fixed_rate_frequency.setter def fixed_rate_frequency(self, value: str): self._property_changed('fixed_rate_frequency') self.__fixed_rate_frequency = value @property def fixed_rate_day_count_fraction(self) -> Union[DayCountFraction, str]: """The day count fraction for the fixed rate""" return self.__fixed_rate_day_count_fraction @fixed_rate_day_count_fraction.setter def fixed_rate_day_count_fraction(self, value: Union[DayCountFraction, str]): self._property_changed('fixed_rate_day_count_fraction') self.__fixed_rate_day_count_fraction = get_enum_value(DayCountFraction, value) @property def fixed_rate_business_day_convention(self) -> Union[BusinessDayConvention, str]: """The business day convention for the fixed rate""" return self.__fixed_rate_business_day_convention @fixed_rate_business_day_convention.setter def fixed_rate_business_day_convention(self, value: Union[BusinessDayConvention, str]): self._property_changed('fixed_rate_business_day_convention') self.__fixed_rate_business_day_convention = get_enum_value(BusinessDayConvention, value) @property def strike(self) -> Union[float, str]: """Strike as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF, 10/vol, 100k/pv, p=10000, p=10000USD, $200K/BP, or multiple strikes 65.4/-45.8""" return self.__strike @strike.setter def strike(self, value: Union[float, str]): self._property_changed('strike') self.__strike = value @property def premium(self) -> Union[float, str]: """The premium""" return self.__premium @premium.setter def premium(self, value: Union[float, str]): self._property_changed('premium') self.__premium = value @property def premium_payment_date(self) -> Union[datetime.date, str]: """Payment date of the premium""" return self.__premium_payment_date @premium_payment_date.setter def premium_payment_date(self, value: Union[datetime.date, str]): self._property_changed('premium_payment_date') self.__premium_payment_date = value @property def fee(self) -> float: """The fee""" return self.__fee @fee.setter def fee(self, value: float): self._property_changed('fee') self.__fee = value @property def fee_currency(self) -> Union[Currency, str]: """Currency of the fee""" return self.__fee_currency @fee_currency.setter def fee_currency(self, value: Union[Currency, str]): self._property_changed('fee_currency') self.__fee_currency = get_enum_value(Currency, value) @property def fee_payment_date(self) -> Union[datetime.date, str]: """Payment date of the fee""" return self.__fee_payment_date @fee_payment_date.setter def fee_payment_date(self, value: Union[datetime.date, str]): self._property_changed('fee_payment_date') self.__fee_payment_date = value @property def clearing_house(self) -> Union[SwapClearingHouse, str]: """Swap Clearing House""" return self.__clearing_house @clearing_house.setter def clearing_house(self, value: Union[SwapClearingHouse, str]): self._property_changed('clearing_house') self.__clearing_house = get_enum_value(SwapClearingHouse, value) @property def settlement(self) -> Union[SwapSettlement, str]: """Swap Settlement Type""" return self.__settlement @settlement.setter def settlement(self, value: Union[SwapSettlement, str]): self._property_changed('settlement') self.__settlement = get_enum_value(SwapSettlement, value) @property def buy_sell(self) -> Union[BuySell, str]: """Buy or Sell side of contract""" return self.__buy_sell @buy_sell.setter def buy_sell(self, value: Union[BuySell, str]): self._property_changed('buy_sell') self.__buy_sell = get_enum_value(BuySell, value)
[docs]class IRXccySwap(Instrument): """An exchange of cashflows from different interest rate indices""" @camel_case_translate def __init__( self, termination_date: Union[Union[datetime.date, str], str] = None, notional_amount: float = None, effective_date: Union[Union[datetime.date, str], str] = None, principal_exchange: Union[PrincipalExchange, str] = None, payer_currency: Union[Currency, str] = None, payer_spread: Union[float, str] = None, payer_rate_option: str = None, payer_designated_maturity: str = None, payer_frequency: str = None, payer_day_count_fraction: Union[DayCountFraction, str] = None, payer_business_day_convention: Union[BusinessDayConvention, str] = None, receiver_currency: Union[Currency, str] = None, receiver_spread: Union[float, str] = None, receiver_rate_option: str = None, receiver_designated_maturity: str = None, receiver_frequency: str = None, receiver_day_count_fraction: Union[DayCountFraction, str] = None, receiver_business_day_convention: Union[BusinessDayConvention, str] = None, fee: float = 0, fee_currency: Union[Currency, str] = None, fee_payment_date: Union[datetime.date, str] = None, initial_fx_rate: float = None, payer_first_stub: Union[Union[datetime.date, str], str] = None, receiver_first_stub: Union[Union[datetime.date, str], str] = None, payer_last_stub: Union[Union[datetime.date, str], str] = None, receiver_last_stub: Union[Union[datetime.date, str], str] = None, payer_holidays: str = None, receiver_holidays: str = None, notional_reset_side: Union[PayReceive, str] = None, name: str = None ): super().__init__() self.termination_date = termination_date self.notional_amount = notional_amount self.effective_date = effective_date self.principal_exchange = principal_exchange self.payer_currency = payer_currency self.payer_spread = payer_spread self.payer_rate_option = payer_rate_option self.payer_designated_maturity = payer_designated_maturity self.payer_frequency = payer_frequency self.payer_day_count_fraction = payer_day_count_fraction self.payer_business_day_convention = payer_business_day_convention self.receiver_currency = receiver_currency self.receiver_spread = receiver_spread self.receiver_rate_option = receiver_rate_option self.receiver_designated_maturity = receiver_designated_maturity self.receiver_frequency = receiver_frequency self.receiver_day_count_fraction = receiver_day_count_fraction self.receiver_business_day_convention = receiver_business_day_convention self.fee = fee self.fee_currency = fee_currency self.fee_payment_date = fee_payment_date self.initial_fx_rate = initial_fx_rate self.payer_first_stub = payer_first_stub self.receiver_first_stub = receiver_first_stub self.payer_last_stub = payer_last_stub self.receiver_last_stub = receiver_last_stub self.payer_holidays = payer_holidays self.receiver_holidays = receiver_holidays self.notional_reset_side = notional_reset_side self.name = name @property def asset_class(self) -> AssetClass: """Rates""" return AssetClass.Rates @property def type(self) -> AssetType: """XccySwapMTM""" return AssetType.XccySwapMTM @property def termination_date(self) -> Union[Union[datetime.date, str], str]: """The termination of the swap, e.g. 2050-04-01, 10y""" return self.__termination_date @termination_date.setter def termination_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('termination_date') self.__termination_date = value @property def notional_amount(self) -> float: """Notional amount""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: float): self._property_changed('notional_amount') self.__notional_amount = value @property def effective_date(self) -> Union[Union[datetime.date, str], str]: """The date on which the swap becomes effective""" return self.__effective_date @effective_date.setter def effective_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('effective_date') self.__effective_date = value @property def principal_exchange(self) -> Union[PrincipalExchange, str]: """The date on which the swap becomes effective""" return self.__principal_exchange @principal_exchange.setter def principal_exchange(self, value: Union[PrincipalExchange, str]): self._property_changed('principal_exchange') self.__principal_exchange = get_enum_value(PrincipalExchange, value) @property def payer_currency(self) -> Union[Currency, str]: """Payer currency""" return self.__payer_currency @payer_currency.setter def payer_currency(self, value: Union[Currency, str]): self._property_changed('payer_currency') self.__payer_currency = get_enum_value(Currency, value) @property def payer_spread(self) -> Union[float, str]: """Spread over the payer rate""" return self.__payer_spread @payer_spread.setter def payer_spread(self, value: Union[float, str]): self._property_changed('payer_spread') self.__payer_spread = value @property def payer_rate_option(self) -> str: """The underlying benchmark for the payer, e.g. USD-LIBOR-BBA, EUR-EURIBOR-TELERATE""" return self.__payer_rate_option @payer_rate_option.setter def payer_rate_option(self, value: str): self._property_changed('payer_rate_option') self.__payer_rate_option = value @property def payer_designated_maturity(self) -> str: """Tenor of the payerRateOption, e.g. 3m, 6m""" return self.__payer_designated_maturity @payer_designated_maturity.setter def payer_designated_maturity(self, value: str): self._property_changed('payer_designated_maturity') self.__payer_designated_maturity = value @property def payer_frequency(self) -> str: """The frequency of payer payments, e.g. 6m""" return self.__payer_frequency @payer_frequency.setter def payer_frequency(self, value: str): self._property_changed('payer_frequency') self.__payer_frequency = value @property def payer_day_count_fraction(self) -> Union[DayCountFraction, str]: """The day count fraction for the payer""" return self.__payer_day_count_fraction @payer_day_count_fraction.setter def payer_day_count_fraction(self, value: Union[DayCountFraction, str]): self._property_changed('payer_day_count_fraction') self.__payer_day_count_fraction = get_enum_value(DayCountFraction, value) @property def payer_business_day_convention(self) -> Union[BusinessDayConvention, str]: """The business day convention for the payer""" return self.__payer_business_day_convention @payer_business_day_convention.setter def payer_business_day_convention(self, value: Union[BusinessDayConvention, str]): self._property_changed('payer_business_day_convention') self.__payer_business_day_convention = get_enum_value(BusinessDayConvention, value) @property def receiver_currency(self) -> Union[Currency, str]: """Receiver currency""" return self.__receiver_currency @receiver_currency.setter def receiver_currency(self, value: Union[Currency, str]): self._property_changed('receiver_currency') self.__receiver_currency = get_enum_value(Currency, value) @property def receiver_spread(self) -> Union[float, str]: """Spread over the receiver rate""" return self.__receiver_spread @receiver_spread.setter def receiver_spread(self, value: Union[float, str]): self._property_changed('receiver_spread') self.__receiver_spread = value @property def receiver_rate_option(self) -> str: """The underlying benchmark for the receiver, e.g. USD-LIBOR-BBA, EUR-EURIBOR- TELERATE""" return self.__receiver_rate_option @receiver_rate_option.setter def receiver_rate_option(self, value: str): self._property_changed('receiver_rate_option') self.__receiver_rate_option = value @property def receiver_designated_maturity(self) -> str: """Tenor of the receiverRateOption, e.g. 3m, 6m""" return self.__receiver_designated_maturity @receiver_designated_maturity.setter def receiver_designated_maturity(self, value: str): self._property_changed('receiver_designated_maturity') self.__receiver_designated_maturity = value @property def receiver_frequency(self) -> str: """The frequency of receiver payments, e.g. 6m""" return self.__receiver_frequency @receiver_frequency.setter def receiver_frequency(self, value: str): self._property_changed('receiver_frequency') self.__receiver_frequency = value @property def receiver_day_count_fraction(self) -> Union[DayCountFraction, str]: """The day count fraction for the receiver""" return self.__receiver_day_count_fraction @receiver_day_count_fraction.setter def receiver_day_count_fraction(self, value: Union[DayCountFraction, str]): self._property_changed('receiver_day_count_fraction') self.__receiver_day_count_fraction = get_enum_value(DayCountFraction, value) @property def receiver_business_day_convention(self) -> Union[BusinessDayConvention, str]: """The business day convention for the receiver""" return self.__receiver_business_day_convention @receiver_business_day_convention.setter def receiver_business_day_convention(self, value: Union[BusinessDayConvention, str]): self._property_changed('receiver_business_day_convention') self.__receiver_business_day_convention = get_enum_value(BusinessDayConvention, value) @property def fee(self) -> float: """The fee""" return self.__fee @fee.setter def fee(self, value: float): self._property_changed('fee') self.__fee = value @property def fee_currency(self) -> Union[Currency, str]: """Currency of the fee""" return self.__fee_currency @fee_currency.setter def fee_currency(self, value: Union[Currency, str]): self._property_changed('fee_currency') self.__fee_currency = get_enum_value(Currency, value) @property def fee_payment_date(self) -> Union[datetime.date, str]: """Payment date of the fee""" return self.__fee_payment_date @fee_payment_date.setter def fee_payment_date(self, value: Union[datetime.date, str]): self._property_changed('fee_payment_date') self.__fee_payment_date = value @property def initial_fx_rate(self) -> float: """Payment date of the fee""" return self.__initial_fx_rate @initial_fx_rate.setter def initial_fx_rate(self, value: float): self._property_changed('initial_fx_rate') self.__initial_fx_rate = value @property def payer_first_stub(self) -> Union[Union[datetime.date, str], str]: """The date of the first stub for payer leg""" return self.__payer_first_stub @payer_first_stub.setter def payer_first_stub(self, value: Union[Union[datetime.date, str], str]): self._property_changed('payer_first_stub') self.__payer_first_stub = value @property def receiver_first_stub(self) -> Union[Union[datetime.date, str], str]: """The date of the first stub for receiver leg""" return self.__receiver_first_stub @receiver_first_stub.setter def receiver_first_stub(self, value: Union[Union[datetime.date, str], str]): self._property_changed('receiver_first_stub') self.__receiver_first_stub = value @property def payer_last_stub(self) -> Union[Union[datetime.date, str], str]: """The date of the last stub for payer leg""" return self.__payer_last_stub @payer_last_stub.setter def payer_last_stub(self, value: Union[Union[datetime.date, str], str]): self._property_changed('payer_last_stub') self.__payer_last_stub = value @property def receiver_last_stub(self) -> Union[Union[datetime.date, str], str]: """The date of the last stub for receiver leg""" return self.__receiver_last_stub @receiver_last_stub.setter def receiver_last_stub(self, value: Union[Union[datetime.date, str], str]): self._property_changed('receiver_last_stub') self.__receiver_last_stub = value @property def payer_holidays(self) -> str: """The accrual calendar for payer leg""" return self.__payer_holidays @payer_holidays.setter def payer_holidays(self, value: str): self._property_changed('payer_holidays') self.__payer_holidays = value @property def receiver_holidays(self) -> str: """The accrual calendar for receiver leg""" return self.__receiver_holidays @receiver_holidays.setter def receiver_holidays(self, value: str): self._property_changed('receiver_holidays') self.__receiver_holidays = value @property def notional_reset_side(self) -> Union[PayReceive, str]: """Pay or Rec leg resetting""" return self.__notional_reset_side @notional_reset_side.setter def notional_reset_side(self, value: Union[PayReceive, str]): self._property_changed('notional_reset_side') self.__notional_reset_side = get_enum_value(PayReceive, value)
[docs]class IRXccySwapFixFix(Instrument): """An exchange of fixed cashflows in different currencies""" @camel_case_translate def __init__( self, termination_date: Union[Union[datetime.date, str], str] = None, notional_amount: float = None, receiver_notional_amount: float = None, effective_date: Union[Union[datetime.date, str], str] = None, principal_exchange: Union[PrincipalExchange, str] = None, payer_currency: Union[Currency, str] = None, payer_rate: Union[float, str] = None, payer_frequency: str = None, payer_day_count_fraction: Union[DayCountFraction, str] = None, payer_business_day_convention: Union[BusinessDayConvention, str] = None, receiver_currency: Union[Currency, str] = None, receiver_rate: Union[float, str] = None, receiver_frequency: str = None, receiver_day_count_fraction: Union[DayCountFraction, str] = None, receiver_business_day_convention: Union[BusinessDayConvention, str] = None, fee: float = 0, fee_currency: Union[Currency, str] = None, fee_payment_date: Union[datetime.date, str] = None, name: str = None ): super().__init__() self.termination_date = termination_date self.notional_amount = notional_amount self.receiver_notional_amount = receiver_notional_amount self.effective_date = effective_date self.principal_exchange = principal_exchange self.payer_currency = payer_currency self.payer_rate = payer_rate self.payer_frequency = payer_frequency self.payer_day_count_fraction = payer_day_count_fraction self.payer_business_day_convention = payer_business_day_convention self.receiver_currency = receiver_currency self.receiver_rate = receiver_rate self.receiver_frequency = receiver_frequency self.receiver_day_count_fraction = receiver_day_count_fraction self.receiver_business_day_convention = receiver_business_day_convention self.fee = fee self.fee_currency = fee_currency self.fee_payment_date = fee_payment_date self.name = name @property def asset_class(self) -> AssetClass: """Rates""" return AssetClass.Rates @property def type(self) -> AssetType: """XccySwapFixFix""" return AssetType.XccySwapFixFix @property def termination_date(self) -> Union[Union[datetime.date, str], str]: """The termination of the swap, e.g. 2050-04-01, 10y""" return self.__termination_date @termination_date.setter def termination_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('termination_date') self.__termination_date = value @property def notional_amount(self) -> float: """Notional amount""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: float): self._property_changed('notional_amount') self.__notional_amount = value @property def receiver_notional_amount(self) -> float: """Receiver notional amount""" return self.__receiver_notional_amount @receiver_notional_amount.setter def receiver_notional_amount(self, value: float): self._property_changed('receiver_notional_amount') self.__receiver_notional_amount = value @property def effective_date(self) -> Union[Union[datetime.date, str], str]: """The date on which the swap becomes effective""" return self.__effective_date @effective_date.setter def effective_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('effective_date') self.__effective_date = value @property def principal_exchange(self) -> Union[PrincipalExchange, str]: """The date on which the swap becomes effective""" return self.__principal_exchange @principal_exchange.setter def principal_exchange(self, value: Union[PrincipalExchange, str]): self._property_changed('principal_exchange') self.__principal_exchange = get_enum_value(PrincipalExchange, value) @property def payer_currency(self) -> Union[Currency, str]: """Payer currency""" return self.__payer_currency @payer_currency.setter def payer_currency(self, value: Union[Currency, str]): self._property_changed('payer_currency') self.__payer_currency = get_enum_value(Currency, value) @property def payer_rate(self) -> Union[float, str]: """Payer rate""" return self.__payer_rate @payer_rate.setter def payer_rate(self, value: Union[float, str]): self._property_changed('payer_rate') self.__payer_rate = value @property def payer_frequency(self) -> str: """The frequency of payer payments, e.g. 6m""" return self.__payer_frequency @payer_frequency.setter def payer_frequency(self, value: str): self._property_changed('payer_frequency') self.__payer_frequency = value @property def payer_day_count_fraction(self) -> Union[DayCountFraction, str]: """The day count fraction for the payer""" return self.__payer_day_count_fraction @payer_day_count_fraction.setter def payer_day_count_fraction(self, value: Union[DayCountFraction, str]): self._property_changed('payer_day_count_fraction') self.__payer_day_count_fraction = get_enum_value(DayCountFraction, value) @property def payer_business_day_convention(self) -> Union[BusinessDayConvention, str]: """The business day convention for the payer""" return self.__payer_business_day_convention @payer_business_day_convention.setter def payer_business_day_convention(self, value: Union[BusinessDayConvention, str]): self._property_changed('payer_business_day_convention') self.__payer_business_day_convention = get_enum_value(BusinessDayConvention, value) @property def receiver_currency(self) -> Union[Currency, str]: """Receiver currency""" return self.__receiver_currency @receiver_currency.setter def receiver_currency(self, value: Union[Currency, str]): self._property_changed('receiver_currency') self.__receiver_currency = get_enum_value(Currency, value) @property def receiver_rate(self) -> Union[float, str]: """Receiver rate""" return self.__receiver_rate @receiver_rate.setter def receiver_rate(self, value: Union[float, str]): self._property_changed('receiver_rate') self.__receiver_rate = value @property def receiver_frequency(self) -> str: """The frequency of receiver payments, e.g. 6m""" return self.__receiver_frequency @receiver_frequency.setter def receiver_frequency(self, value: str): self._property_changed('receiver_frequency') self.__receiver_frequency = value @property def receiver_day_count_fraction(self) -> Union[DayCountFraction, str]: """The day count fraction for the receiver""" return self.__receiver_day_count_fraction @receiver_day_count_fraction.setter def receiver_day_count_fraction(self, value: Union[DayCountFraction, str]): self._property_changed('receiver_day_count_fraction') self.__receiver_day_count_fraction = get_enum_value(DayCountFraction, value) @property def receiver_business_day_convention(self) -> Union[BusinessDayConvention, str]: """The business day convention for the receiver""" return self.__receiver_business_day_convention @receiver_business_day_convention.setter def receiver_business_day_convention(self, value: Union[BusinessDayConvention, str]): self._property_changed('receiver_business_day_convention') self.__receiver_business_day_convention = get_enum_value(BusinessDayConvention, value) @property def fee(self) -> float: """The fee""" return self.__fee @fee.setter def fee(self, value: float): self._property_changed('fee') self.__fee = value @property def fee_currency(self) -> Union[Currency, str]: """Currency of the fee""" return self.__fee_currency @fee_currency.setter def fee_currency(self, value: Union[Currency, str]): self._property_changed('fee_currency') self.__fee_currency = get_enum_value(Currency, value) @property def fee_payment_date(self) -> Union[datetime.date, str]: """Payment date of the fee""" return self.__fee_payment_date @fee_payment_date.setter def fee_payment_date(self, value: Union[datetime.date, str]): self._property_changed('fee_payment_date') self.__fee_payment_date = value
[docs]class IRXccySwapFixFlt(Instrument): """An exchange of fixed vs floating cashflows in different currencies""" @camel_case_translate def __init__( self, pay_or_receive: Union[PayReceive, str] = None, termination_date: Union[Union[datetime.date, str], str] = None, notional_amount: Union[float, str] = None, effective_date: Union[Union[datetime.date, str], str] = None, principal_exchange: Union[PrincipalExchange, str] = None, floating_rate_currency: Union[Currency, str] = None, floating_rate_for_the_initial_calculation_period: float = None, floating_rate_option: str = None, floating_rate_designated_maturity: str = None, floating_rate_spread: Union[float, str] = None, floating_rate_frequency: str = None, floating_rate_day_count_fraction: Union[DayCountFraction, str] = None, floating_rate_business_day_convention: Union[BusinessDayConvention, str] = None, fixed_rate_currency: Union[Currency, str] = None, fixed_rate: Union[float, str] = None, fixed_rate_frequency: str = None, fixed_rate_day_count_fraction: Union[DayCountFraction, str] = None, fixed_rate_business_day_convention: Union[BusinessDayConvention, str] = None, fee: float = 0, fee_currency: Union[Currency, str] = None, fee_payment_date: Union[datetime.date, str] = None, fixed_first_stub: Union[Union[datetime.date, str], str] = None, floating_first_stub: Union[Union[datetime.date, str], str] = None, fixed_last_stub: Union[Union[datetime.date, str], str] = None, floating_last_stub: Union[Union[datetime.date, str], str] = None, fixed_holidays: str = None, floating_holidays: str = None, name: str = None ): super().__init__() self.pay_or_receive = pay_or_receive self.termination_date = termination_date self.notional_amount = notional_amount self.effective_date = effective_date self.principal_exchange = principal_exchange self.floating_rate_currency = floating_rate_currency self.floating_rate_for_the_initial_calculation_period = floating_rate_for_the_initial_calculation_period self.floating_rate_option = floating_rate_option self.floating_rate_designated_maturity = floating_rate_designated_maturity self.floating_rate_spread = floating_rate_spread self.floating_rate_frequency = floating_rate_frequency self.floating_rate_day_count_fraction = floating_rate_day_count_fraction self.floating_rate_business_day_convention = floating_rate_business_day_convention self.fixed_rate_currency = fixed_rate_currency self.fixed_rate = fixed_rate self.fixed_rate_frequency = fixed_rate_frequency self.fixed_rate_day_count_fraction = fixed_rate_day_count_fraction self.fixed_rate_business_day_convention = fixed_rate_business_day_convention self.fee = fee self.fee_currency = fee_currency self.fee_payment_date = fee_payment_date self.fixed_first_stub = fixed_first_stub self.floating_first_stub = floating_first_stub self.fixed_last_stub = fixed_last_stub self.floating_last_stub = floating_last_stub self.fixed_holidays = fixed_holidays self.floating_holidays = floating_holidays self.name = name @property def asset_class(self) -> AssetClass: """Rates""" return AssetClass.Rates @property def type(self) -> AssetType: """XccySwapFixFlt""" return AssetType.XccySwapFixFlt @property def pay_or_receive(self) -> Union[PayReceive, str]: """Pay or receive fixed""" return self.__pay_or_receive @pay_or_receive.setter def pay_or_receive(self, value: Union[PayReceive, str]): self._property_changed('pay_or_receive') self.__pay_or_receive = get_enum_value(PayReceive, value) @property def termination_date(self) -> Union[Union[datetime.date, str], str]: """The termination of the swap, e.g. 2050-04-01, 10y""" return self.__termination_date @termination_date.setter def termination_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('termination_date') self.__termination_date = value @property def notional_amount(self) -> Union[float, str]: """Notional amount""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: Union[float, str]): self._property_changed('notional_amount') self.__notional_amount = value @property def effective_date(self) -> Union[Union[datetime.date, str], str]: """The date on which the swap becomes effective""" return self.__effective_date @effective_date.setter def effective_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('effective_date') self.__effective_date = value @property def principal_exchange(self) -> Union[PrincipalExchange, str]: """The date on which the swap becomes effective""" return self.__principal_exchange @principal_exchange.setter def principal_exchange(self, value: Union[PrincipalExchange, str]): self._property_changed('principal_exchange') self.__principal_exchange = get_enum_value(PrincipalExchange, value) @property def floating_rate_currency(self) -> Union[Currency, str]: """Floating rate currency""" return self.__floating_rate_currency @floating_rate_currency.setter def floating_rate_currency(self, value: Union[Currency, str]): self._property_changed('floating_rate_currency') self.__floating_rate_currency = get_enum_value(Currency, value) @property def floating_rate_for_the_initial_calculation_period(self) -> float: """First fixing""" return self.__floating_rate_for_the_initial_calculation_period @floating_rate_for_the_initial_calculation_period.setter def floating_rate_for_the_initial_calculation_period(self, value: float): self._property_changed('floating_rate_for_the_initial_calculation_period') self.__floating_rate_for_the_initial_calculation_period = value @property def floating_rate_option(self) -> str: """The underlying benchmark for the floating rate, e.g. USD-LIBOR-BBA, EUR-EURIBOR- TELERATE""" return self.__floating_rate_option @floating_rate_option.setter def floating_rate_option(self, value: str): self._property_changed('floating_rate_option') self.__floating_rate_option = value @property def floating_rate_designated_maturity(self) -> str: """Tenor of the floatingRateOption, e.g. 3m, 6m""" return self.__floating_rate_designated_maturity @floating_rate_designated_maturity.setter def floating_rate_designated_maturity(self, value: str): self._property_changed('floating_rate_designated_maturity') self.__floating_rate_designated_maturity = value @property def floating_rate_spread(self) -> Union[float, str]: """The spread over the floating rate""" return self.__floating_rate_spread @floating_rate_spread.setter def floating_rate_spread(self, value: Union[float, str]): self._property_changed('floating_rate_spread') self.__floating_rate_spread = value @property def floating_rate_frequency(self) -> str: """The frequency of floating payments, e.g. 3m""" return self.__floating_rate_frequency @floating_rate_frequency.setter def floating_rate_frequency(self, value: str): self._property_changed('floating_rate_frequency') self.__floating_rate_frequency = value @property def floating_rate_day_count_fraction(self) -> Union[DayCountFraction, str]: """The day count fraction of the floating rate""" return self.__floating_rate_day_count_fraction @floating_rate_day_count_fraction.setter def floating_rate_day_count_fraction(self, value: Union[DayCountFraction, str]): self._property_changed('floating_rate_day_count_fraction') self.__floating_rate_day_count_fraction = get_enum_value(DayCountFraction, value) @property def floating_rate_business_day_convention(self) -> Union[BusinessDayConvention, str]: """The business day convention of the floating rate""" return self.__floating_rate_business_day_convention @floating_rate_business_day_convention.setter def floating_rate_business_day_convention(self, value: Union[BusinessDayConvention, str]): self._property_changed('floating_rate_business_day_convention') self.__floating_rate_business_day_convention = get_enum_value(BusinessDayConvention, value) @property def fixed_rate_currency(self) -> Union[Currency, str]: """Fixed rate currency""" return self.__fixed_rate_currency @fixed_rate_currency.setter def fixed_rate_currency(self, value: Union[Currency, str]): self._property_changed('fixed_rate_currency') self.__fixed_rate_currency = get_enum_value(Currency, value) @property def fixed_rate(self) -> Union[float, str]: """The coupon of the fixed leg""" return self.__fixed_rate @fixed_rate.setter def fixed_rate(self, value: Union[float, str]): self._property_changed('fixed_rate') self.__fixed_rate = value @property def fixed_rate_frequency(self) -> str: """The frequency of fixed payments, e.g. 6m""" return self.__fixed_rate_frequency @fixed_rate_frequency.setter def fixed_rate_frequency(self, value: str): self._property_changed('fixed_rate_frequency') self.__fixed_rate_frequency = value @property def fixed_rate_day_count_fraction(self) -> Union[DayCountFraction, str]: """The day count fraction for the fixed rate""" return self.__fixed_rate_day_count_fraction @fixed_rate_day_count_fraction.setter def fixed_rate_day_count_fraction(self, value: Union[DayCountFraction, str]): self._property_changed('fixed_rate_day_count_fraction') self.__fixed_rate_day_count_fraction = get_enum_value(DayCountFraction, value) @property def fixed_rate_business_day_convention(self) -> Union[BusinessDayConvention, str]: """The business day convention for the fixed rate""" return self.__fixed_rate_business_day_convention @fixed_rate_business_day_convention.setter def fixed_rate_business_day_convention(self, value: Union[BusinessDayConvention, str]): self._property_changed('fixed_rate_business_day_convention') self.__fixed_rate_business_day_convention = get_enum_value(BusinessDayConvention, value) @property def fee(self) -> float: """The fee""" return self.__fee @fee.setter def fee(self, value: float): self._property_changed('fee') self.__fee = value @property def fee_currency(self) -> Union[Currency, str]: """Currency of the fee""" return self.__fee_currency @fee_currency.setter def fee_currency(self, value: Union[Currency, str]): self._property_changed('fee_currency') self.__fee_currency = get_enum_value(Currency, value) @property def fee_payment_date(self) -> Union[datetime.date, str]: """Payment date of the fee""" return self.__fee_payment_date @fee_payment_date.setter def fee_payment_date(self, value: Union[datetime.date, str]): self._property_changed('fee_payment_date') self.__fee_payment_date = value @property def fixed_first_stub(self) -> Union[Union[datetime.date, str], str]: """The date of the first stub for fixed leg""" return self.__fixed_first_stub @fixed_first_stub.setter def fixed_first_stub(self, value: Union[Union[datetime.date, str], str]): self._property_changed('fixed_first_stub') self.__fixed_first_stub = value @property def floating_first_stub(self) -> Union[Union[datetime.date, str], str]: """The date of the first stub for floating leg""" return self.__floating_first_stub @floating_first_stub.setter def floating_first_stub(self, value: Union[Union[datetime.date, str], str]): self._property_changed('floating_first_stub') self.__floating_first_stub = value @property def fixed_last_stub(self) -> Union[Union[datetime.date, str], str]: """The date of the last stub for fixed leg""" return self.__fixed_last_stub @fixed_last_stub.setter def fixed_last_stub(self, value: Union[Union[datetime.date, str], str]): self._property_changed('fixed_last_stub') self.__fixed_last_stub = value @property def floating_last_stub(self) -> Union[Union[datetime.date, str], str]: """The date of the last stub for floating leg""" return self.__floating_last_stub @floating_last_stub.setter def floating_last_stub(self, value: Union[Union[datetime.date, str], str]): self._property_changed('floating_last_stub') self.__floating_last_stub = value @property def fixed_holidays(self) -> str: """The accrual calendar for fixed leg""" return self.__fixed_holidays @fixed_holidays.setter def fixed_holidays(self, value: str): self._property_changed('fixed_holidays') self.__fixed_holidays = value @property def floating_holidays(self) -> str: """The accrual calendar for floating leg""" return self.__floating_holidays @floating_holidays.setter def floating_holidays(self, value: str): self._property_changed('floating_holidays') self.__floating_holidays = value
class IRXccySwapFltFlt(Instrument): """An exchange of cashflows from different interest rate indices, non-resetting""" @camel_case_translate def __init__( self, termination_date: Union[Union[datetime.date, str], str] = None, notional_amount: Union[float, str] = None, effective_date: Union[Union[datetime.date, str], str] = None, principal_exchange: Union[PrincipalExchange, str] = None, payer_currency: Union[Currency, str] = None, payer_spread: Union[float, str] = None, payer_rate_option: str = None, payer_designated_maturity: str = None, payer_frequency: str = None, payer_day_count_fraction: Union[DayCountFraction, str] = None, payer_business_day_convention: Union[BusinessDayConvention, str] = None, receiver_currency: Union[Currency, str] = None, receiver_spread: Union[float, str] = None, receiver_rate_option: str = None, receiver_designated_maturity: str = None, receiver_frequency: str = None, receiver_day_count_fraction: Union[DayCountFraction, str] = None, receiver_business_day_convention: Union[BusinessDayConvention, str] = None, fee: float = 0, fee_currency: Union[Currency, str] = None, fee_payment_date: Union[datetime.date, str] = None, payer_first_stub: Union[Union[datetime.date, str], str] = None, receiver_first_stub: Union[Union[datetime.date, str], str] = None, payer_last_stub: Union[Union[datetime.date, str], str] = None, receiver_last_stub: Union[Union[datetime.date, str], str] = None, payer_holidays: str = None, receiver_holidays: str = None, name: str = None ): super().__init__() self.termination_date = termination_date self.notional_amount = notional_amount self.effective_date = effective_date self.principal_exchange = principal_exchange self.payer_currency = payer_currency self.payer_spread = payer_spread self.payer_rate_option = payer_rate_option self.payer_designated_maturity = payer_designated_maturity self.payer_frequency = payer_frequency self.payer_day_count_fraction = payer_day_count_fraction self.payer_business_day_convention = payer_business_day_convention self.receiver_currency = receiver_currency self.receiver_spread = receiver_spread self.receiver_rate_option = receiver_rate_option self.receiver_designated_maturity = receiver_designated_maturity self.receiver_frequency = receiver_frequency self.receiver_day_count_fraction = receiver_day_count_fraction self.receiver_business_day_convention = receiver_business_day_convention self.fee = fee self.fee_currency = fee_currency self.fee_payment_date = fee_payment_date self.payer_first_stub = payer_first_stub self.receiver_first_stub = receiver_first_stub self.payer_last_stub = payer_last_stub self.receiver_last_stub = receiver_last_stub self.payer_holidays = payer_holidays self.receiver_holidays = receiver_holidays self.name = name @property def asset_class(self) -> AssetClass: """Rates""" return AssetClass.Rates @property def type(self) -> AssetType: """XccySwap""" return AssetType.XccySwap @property def termination_date(self) -> Union[Union[datetime.date, str], str]: """The termination of the swap, e.g. 2050-04-01, 10y""" return self.__termination_date @termination_date.setter def termination_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('termination_date') self.__termination_date = value @property def notional_amount(self) -> Union[float, str]: """Notional amount""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: Union[float, str]): self._property_changed('notional_amount') self.__notional_amount = value @property def effective_date(self) -> Union[Union[datetime.date, str], str]: """The date on which the swap becomes effective""" return self.__effective_date @effective_date.setter def effective_date(self, value: Union[Union[datetime.date, str], str]): self._property_changed('effective_date') self.__effective_date = value @property def principal_exchange(self) -> Union[PrincipalExchange, str]: """Principal exchanges at inception, termination or both""" return self.__principal_exchange @principal_exchange.setter def principal_exchange(self, value: Union[PrincipalExchange, str]): self._property_changed('principal_exchange') self.__principal_exchange = get_enum_value(PrincipalExchange, value) @property def payer_currency(self) -> Union[Currency, str]: """Payer currency""" return self.__payer_currency @payer_currency.setter def payer_currency(self, value: Union[Currency, str]): self._property_changed('payer_currency') self.__payer_currency = get_enum_value(Currency, value) @property def payer_spread(self) -> Union[float, str]: """Spread over the payer rate""" return self.__payer_spread @payer_spread.setter def payer_spread(self, value: Union[float, str]): self._property_changed('payer_spread') self.__payer_spread = value @property def payer_rate_option(self) -> str: """The underlying benchmark for the payer, e.g. USD-LIBOR-BBA, EUR-EURIBOR-TELERATE""" return self.__payer_rate_option @payer_rate_option.setter def payer_rate_option(self, value: str): self._property_changed('payer_rate_option') self.__payer_rate_option = value @property def payer_designated_maturity(self) -> str: """Tenor of the payerRateOption, e.g. 3m, 6m""" return self.__payer_designated_maturity @payer_designated_maturity.setter def payer_designated_maturity(self, value: str): self._property_changed('payer_designated_maturity') self.__payer_designated_maturity = value @property def payer_frequency(self) -> str: """The frequency of payer payments, e.g. 6m""" return self.__payer_frequency @payer_frequency.setter def payer_frequency(self, value: str): self._property_changed('payer_frequency') self.__payer_frequency = value @property def payer_day_count_fraction(self) -> Union[DayCountFraction, str]: """The day count fraction for the payer""" return self.__payer_day_count_fraction @payer_day_count_fraction.setter def payer_day_count_fraction(self, value: Union[DayCountFraction, str]): self._property_changed('payer_day_count_fraction') self.__payer_day_count_fraction = get_enum_value(DayCountFraction, value) @property def payer_business_day_convention(self) -> Union[BusinessDayConvention, str]: """The business day convention for the payer""" return self.__payer_business_day_convention @payer_business_day_convention.setter def payer_business_day_convention(self, value: Union[BusinessDayConvention, str]): self._property_changed('payer_business_day_convention') self.__payer_business_day_convention = get_enum_value(BusinessDayConvention, value) @property def receiver_currency(self) -> Union[Currency, str]: """Receiver currency""" return self.__receiver_currency @receiver_currency.setter def receiver_currency(self, value: Union[Currency, str]): self._property_changed('receiver_currency') self.__receiver_currency = get_enum_value(Currency, value) @property def receiver_spread(self) -> Union[float, str]: """Spread over the receiver rate""" return self.__receiver_spread @receiver_spread.setter def receiver_spread(self, value: Union[float, str]): self._property_changed('receiver_spread') self.__receiver_spread = value @property def receiver_rate_option(self) -> str: """The underlying benchmark for the receiver, e.g. USD-LIBOR-BBA, EUR-EURIBOR- TELERATE""" return self.__receiver_rate_option @receiver_rate_option.setter def receiver_rate_option(self, value: str): self._property_changed('receiver_rate_option') self.__receiver_rate_option = value @property def receiver_designated_maturity(self) -> str: """Tenor of the receiverRateOption, e.g. 3m, 6m""" return self.__receiver_designated_maturity @receiver_designated_maturity.setter def receiver_designated_maturity(self, value: str): self._property_changed('receiver_designated_maturity') self.__receiver_designated_maturity = value @property def receiver_frequency(self) -> str: """The frequency of receiver payments, e.g. 6m""" return self.__receiver_frequency @receiver_frequency.setter def receiver_frequency(self, value: str): self._property_changed('receiver_frequency') self.__receiver_frequency = value @property def receiver_day_count_fraction(self) -> Union[DayCountFraction, str]: """The day count fraction for the receiver""" return self.__receiver_day_count_fraction @receiver_day_count_fraction.setter def receiver_day_count_fraction(self, value: Union[DayCountFraction, str]): self._property_changed('receiver_day_count_fraction') self.__receiver_day_count_fraction = get_enum_value(DayCountFraction, value) @property def receiver_business_day_convention(self) -> Union[BusinessDayConvention, str]: """The business day convention for the receiver""" return self.__receiver_business_day_convention @receiver_business_day_convention.setter def receiver_business_day_convention(self, value: Union[BusinessDayConvention, str]): self._property_changed('receiver_business_day_convention') self.__receiver_business_day_convention = get_enum_value(BusinessDayConvention, value) @property def fee(self) -> float: """The fee""" return self.__fee @fee.setter def fee(self, value: float): self._property_changed('fee') self.__fee = value @property def fee_currency(self) -> Union[Currency, str]: """Currency of the fee""" return self.__fee_currency @fee_currency.setter def fee_currency(self, value: Union[Currency, str]): self._property_changed('fee_currency') self.__fee_currency = get_enum_value(Currency, value) @property def fee_payment_date(self) -> Union[datetime.date, str]: """Payment date of the fee""" return self.__fee_payment_date @fee_payment_date.setter def fee_payment_date(self, value: Union[datetime.date, str]): self._property_changed('fee_payment_date') self.__fee_payment_date = value @property def payer_first_stub(self) -> Union[Union[datetime.date, str], str]: """The date of the first stub for payer leg""" return self.__payer_first_stub @payer_first_stub.setter def payer_first_stub(self, value: Union[Union[datetime.date, str], str]): self._property_changed('payer_first_stub') self.__payer_first_stub = value @property def receiver_first_stub(self) -> Union[Union[datetime.date, str], str]: """The date of the first stub for receiver leg""" return self.__receiver_first_stub @receiver_first_stub.setter def receiver_first_stub(self, value: Union[Union[datetime.date, str], str]): self._property_changed('receiver_first_stub') self.__receiver_first_stub = value @property def payer_last_stub(self) -> Union[Union[datetime.date, str], str]: """The date of the last stub for payer leg""" return self.__payer_last_stub @payer_last_stub.setter def payer_last_stub(self, value: Union[Union[datetime.date, str], str]): self._property_changed('payer_last_stub') self.__payer_last_stub = value @property def receiver_last_stub(self) -> Union[Union[datetime.date, str], str]: """The date of the last stub for receiver leg""" return self.__receiver_last_stub @receiver_last_stub.setter def receiver_last_stub(self, value: Union[Union[datetime.date, str], str]): self._property_changed('receiver_last_stub') self.__receiver_last_stub = value @property def payer_holidays(self) -> str: """The accrual calendar for payer leg""" return self.__payer_holidays @payer_holidays.setter def payer_holidays(self, value: str): self._property_changed('payer_holidays') self.__payer_holidays = value @property def receiver_holidays(self) -> str: """The accrual calendar for receiver leg""" return self.__receiver_holidays @receiver_holidays.setter def receiver_holidays(self, value: str): self._property_changed('receiver_holidays') self.__receiver_holidays = value
[docs]class InflationSwap(Instrument): """A vanilla inflation swap of fixed vs floating cashflows adjusted to an inflation rate""" @camel_case_translate def __init__( self, pay_or_receive: Union[PayReceive, str] = None, termination_date: Union[datetime.date, str] = None, notional_currency: Union[Currency, str] = None, effective_date: Union[datetime.date, str] = None, notional_amount: Union[float, str] = None, index: str = None, floating_rate_business_day_convention: Union[BusinessDayConvention, str] = None, fixed_rate: Union[float, str] = None, fixed_rate_business_day_convention: Union[BusinessDayConvention, str] = None, fee: float = 0, base_cpi: float = None, clearing_house: Union[SwapClearingHouse, str] = None, name: str = None ): super().__init__() self.pay_or_receive = pay_or_receive self.termination_date = termination_date self.notional_currency = notional_currency self.effective_date = effective_date self.notional_amount = notional_amount self.index = index self.floating_rate_business_day_convention = floating_rate_business_day_convention self.fixed_rate = fixed_rate self.fixed_rate_business_day_convention = fixed_rate_business_day_convention self.fee = fee self.base_cpi = base_cpi self.clearing_house = clearing_house self.name = name @property def asset_class(self) -> AssetClass: """Rates""" return AssetClass.Rates @property def type(self) -> AssetType: """InflationSwap""" return AssetType.InflationSwap @property def pay_or_receive(self) -> Union[PayReceive, str]: """Pay or receive fixed""" return self.__pay_or_receive @pay_or_receive.setter def pay_or_receive(self, value: Union[PayReceive, str]): self._property_changed('pay_or_receive') self.__pay_or_receive = get_enum_value(PayReceive, value) @property def termination_date(self) -> Union[datetime.date, str]: """The termination of the swap, e.g. 2050-04-01, 10y""" return self.__termination_date @termination_date.setter def termination_date(self, value: Union[datetime.date, str]): self._property_changed('termination_date') self.__termination_date = value @property def notional_currency(self) -> Union[Currency, str]: """Notional currency""" return self.__notional_currency @notional_currency.setter def notional_currency(self, value: Union[Currency, str]): self._property_changed('notional_currency') self.__notional_currency = get_enum_value(Currency, value) @property def effective_date(self) -> Union[datetime.date, str]: """The date on which the swap becomes effective""" return self.__effective_date @effective_date.setter def effective_date(self, value: Union[datetime.date, str]): self._property_changed('effective_date') self.__effective_date = value @property def notional_amount(self) -> Union[float, str]: """Notional amount""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: Union[float, str]): self._property_changed('notional_amount') self.__notional_amount = value @property def index(self) -> str: """The underlying benchmark for the floating rate, e.g. CPI-U""" return self.__index @index.setter def index(self, value: str): self._property_changed('index') self.__index = value @property def floating_rate_business_day_convention(self) -> Union[BusinessDayConvention, str]: """The business day convention of the floating rate""" return self.__floating_rate_business_day_convention @floating_rate_business_day_convention.setter def floating_rate_business_day_convention(self, value: Union[BusinessDayConvention, str]): self._property_changed('floating_rate_business_day_convention') self.__floating_rate_business_day_convention = get_enum_value(BusinessDayConvention, value) @property def fixed_rate(self) -> Union[float, str]: """The coupon of the fixed leg""" return self.__fixed_rate @fixed_rate.setter def fixed_rate(self, value: Union[float, str]): self._property_changed('fixed_rate') self.__fixed_rate = value @property def fixed_rate_business_day_convention(self) -> Union[BusinessDayConvention, str]: """The business day convention for the fixed rate""" return self.__fixed_rate_business_day_convention @fixed_rate_business_day_convention.setter def fixed_rate_business_day_convention(self, value: Union[BusinessDayConvention, str]): self._property_changed('fixed_rate_business_day_convention') self.__fixed_rate_business_day_convention = get_enum_value(BusinessDayConvention, value) @property def fee(self) -> float: """The fee""" return self.__fee @fee.setter def fee(self, value: float): self._property_changed('fee') self.__fee = value @property def base_cpi(self) -> float: """Base CPI level""" return self.__base_cpi @base_cpi.setter def base_cpi(self, value: float): self._property_changed('base_cpi') self.__base_cpi = value @property def clearing_house(self) -> Union[SwapClearingHouse, str]: """Swap Clearing House""" return self.__clearing_house @clearing_house.setter def clearing_house(self, value: Union[SwapClearingHouse, str]): self._property_changed('clearing_house') self.__clearing_house = get_enum_value(SwapClearingHouse, value)
class InstrumentsAnyAssetRef(Instrument): """An instrument that references an asset ID (from the asset service)""" @camel_case_translate def __init__( self, asset_id: str = None, buy_sell: Union[BuySell, str] = None, size: float = None, product_code: Union[ProductCode, str] = None, name: str = None ): super().__init__() self.asset_id = asset_id self.buy_sell = buy_sell self.size = size self.product_code = product_code self.name = name @property def asset_class(self) -> AssetClass: """Cross Asset""" return AssetClass.Cross_Asset @property def type(self) -> AssetType: """Any""" return AssetType.Any @property def asset_id(self) -> str: return self.__asset_id @asset_id.setter def asset_id(self, value: str): self._property_changed('asset_id') self.__asset_id = value @property def buy_sell(self) -> Union[BuySell, str]: """Buy or Sell side of contract""" return self.__buy_sell @buy_sell.setter def buy_sell(self, value: Union[BuySell, str]): self._property_changed('buy_sell') self.__buy_sell = get_enum_value(BuySell, value) @property def size(self) -> float: return self.__size @size.setter def size(self, value: float): self._property_changed('size') self.__size = value @property def product_code(self) -> Union[ProductCode, str]: """Override the clearing destination/symbol""" return self.__product_code @product_code.setter def product_code(self, value: Union[ProductCode, str]): self._property_changed('product_code') self.__product_code = get_enum_value(ProductCode, value) class InstrumentsXassetBond(Instrument): """A bond""" @camel_case_translate def __init__( self, buy_sell: Union[BuySell, str] = None, identifier: str = None, identifier_type: Union[UnderlierType, str] = None, size: float = None, settlement_date: Union[datetime.date, str] = None, settlement_currency: Union[Currency, str] = None, name: str = None ): super().__init__() self.buy_sell = buy_sell self.identifier = identifier self.identifier_type = identifier_type self.size = size self.settlement_date = settlement_date self.settlement_currency = settlement_currency self.name = name @property def asset_class(self) -> AssetClass: """Cross Asset""" return AssetClass.Cross_Asset @property def type(self) -> AssetType: """Bond""" return AssetType.Bond @property def buy_sell(self) -> Union[BuySell, str]: """Buy or Sell side of contract""" return self.__buy_sell @buy_sell.setter def buy_sell(self, value: Union[BuySell, str]): self._property_changed('buy_sell') self.__buy_sell = get_enum_value(BuySell, value) @property def identifier(self) -> str: return self.__identifier @identifier.setter def identifier(self, value: str): self._property_changed('identifier') self.__identifier = value @property def identifier_type(self) -> Union[UnderlierType, str]: """Type of underlyer""" return self.__identifier_type @identifier_type.setter def identifier_type(self, value: Union[UnderlierType, str]): self._property_changed('identifier_type') self.__identifier_type = get_enum_value(UnderlierType, value) @property def size(self) -> float: return self.__size @size.setter def size(self, value: float): self._property_changed('size') self.__size = value @property def settlement_date(self) -> Union[datetime.date, str]: """Date or tenor, e.g. 2018-09-03, 3m, Dec21""" return self.__settlement_date @settlement_date.setter def settlement_date(self, value: Union[datetime.date, str]): self._property_changed('settlement_date') self.__settlement_date = value @property def settlement_currency(self) -> Union[Currency, str]: """Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)""" return self.__settlement_currency @settlement_currency.setter def settlement_currency(self, value: Union[Currency, str]): self._property_changed('settlement_currency') self.__settlement_currency = get_enum_value(Currency, value) class CommodOTCOption(Instrument): """Object representation of a commodities OTC option strategies""" @camel_case_translate def __init__( self, buy_sell: Union[BuySell, str] = None, start: Union[datetime.date, str] = None, end: Union[datetime.date, str] = None, number_of_periods: int = None, strategy: str = None, quantity: Union[float, str] = None, quantity_unit: str = None, quantity_period: Union[Period, str] = None, legs: Tuple[CommodOTCOptionLeg, ...] = None, settlement: str = None, premium_summary: Union[float, str] = None, name: str = None ): super().__init__() self.buy_sell = buy_sell self.start = start self.end = end self.number_of_periods = number_of_periods self.strategy = strategy self.quantity = quantity self.quantity_unit = quantity_unit self.quantity_period = quantity_period self.legs = legs self.settlement = settlement self.premium_summary = premium_summary self.name = name @property def asset_class(self) -> AssetClass: """Commod""" return AssetClass.Commod @property def type(self) -> AssetType: """OptionStrategy""" return AssetType.OptionStrategy @property def buy_sell(self) -> Union[BuySell, str]: """Buy or Sell side of contract""" return self.__buy_sell @buy_sell.setter def buy_sell(self, value: Union[BuySell, str]): self._property_changed('buy_sell') self.__buy_sell = get_enum_value(BuySell, value) @property def start(self) -> Union[datetime.date, str]: """Date or Contract Month""" return self.__start @start.setter def start(self, value: Union[datetime.date, str]): self._property_changed('start') self.__start = value @property def end(self) -> Union[datetime.date, str]: """Date or Contract Month""" return self.__end @end.setter def end(self, value: Union[datetime.date, str]): self._property_changed('end') self.__end = value @property def number_of_periods(self) -> int: """The number of settlement periods""" return self.__number_of_periods @number_of_periods.setter def number_of_periods(self, value: int): self._property_changed('number_of_periods') self.__number_of_periods = value @property def strategy(self) -> str: """Option Strategy""" return self.__strategy @strategy.setter def strategy(self, value: str): self._property_changed('strategy') self.__strategy = value @property def quantity(self) -> Union[float, str]: """Size of some value, i.e. notional like 1.3b, 1.5, 1000""" return self.__quantity @quantity.setter def quantity(self, value: Union[float, str]): self._property_changed('quantity') self.__quantity = value @property def quantity_unit(self) -> str: """Commodity asset""" return self.__quantity_unit @quantity_unit.setter def quantity_unit(self, value: str): self._property_changed('quantity_unit') self.__quantity_unit = value @property def quantity_period(self) -> Union[Period, str]: """A coding scheme to define a period corresponding to a quantity amount""" return self.__quantity_period @quantity_period.setter def quantity_period(self, value: Union[Period, str]): self._property_changed('quantity_period') self.__quantity_period = get_enum_value(Period, value) @property def legs(self) -> Tuple[CommodOTCOptionLeg, ...]: """Commodities OTC option leg""" return self.__legs @legs.setter def legs(self, value: Tuple[CommodOTCOptionLeg, ...]): self._property_changed('legs') self.__legs = value @property def settlement(self) -> str: """read only description in plain English of settlement terms""" return self.__settlement @settlement.setter def settlement(self, value: str): self._property_changed('settlement') self.__settlement = value @property def premium_summary(self) -> Union[float, str]: """TBD : Overall Option premium for all Legs""" return self.__premium_summary @premium_summary.setter def premium_summary(self, value: Union[float, str]): self._property_changed('premium_summary') self.__premium_summary = value
[docs]class CommodOTCSwap(Instrument): """Object representation of a commodities swap""" @camel_case_translate def __init__( self, start: Union[datetime.date, str] = None, end: Union[datetime.date, str] = None, number_of_periods: int = None, strategy: str = None, quantity: Union[float, str] = None, quantity_unit: str = None, quantity_period: Union[Period, str] = None, legs: Tuple[CommodOTCSwapLeg, ...] = None, settlement: str = None, name: str = None ): super().__init__() self.start = start self.end = end self.number_of_periods = number_of_periods self.strategy = strategy self.quantity = quantity self.quantity_unit = quantity_unit self.quantity_period = quantity_period self.legs = legs self.settlement = settlement self.name = name @property def asset_class(self) -> AssetClass: """Commod""" return AssetClass.Commod @property def type(self) -> AssetType: """SwapStrategy""" return AssetType.SwapStrategy @property def start(self) -> Union[datetime.date, str]: """Date or Contract Month""" return self.__start @start.setter def start(self, value: Union[datetime.date, str]): self._property_changed('start') self.__start = value @property def end(self) -> Union[datetime.date, str]: """Date or Contract Month""" return self.__end @end.setter def end(self, value: Union[datetime.date, str]): self._property_changed('end') self.__end = value @property def number_of_periods(self) -> int: """The number of settlement periods""" return self.__number_of_periods @number_of_periods.setter def number_of_periods(self, value: int): self._property_changed('number_of_periods') self.__number_of_periods = value @property def strategy(self) -> str: """Swap Strategy : Strip and Commodity Spread""" return self.__strategy @strategy.setter def strategy(self, value: str): self._property_changed('strategy') self.__strategy = value @property def quantity(self) -> Union[float, str]: """Size of some value, i.e. notional like 1.3b, 1.5, 1000""" return self.__quantity @quantity.setter def quantity(self, value: Union[float, str]): self._property_changed('quantity') self.__quantity = value @property def quantity_unit(self) -> str: """Commodity asset""" return self.__quantity_unit @quantity_unit.setter def quantity_unit(self, value: str): self._property_changed('quantity_unit') self.__quantity_unit = value @property def quantity_period(self) -> Union[Period, str]: """A coding scheme to define a period corresponding to a quantity amount""" return self.__quantity_period @quantity_period.setter def quantity_period(self, value: Union[Period, str]): self._property_changed('quantity_period') self.__quantity_period = get_enum_value(Period, value) @property def legs(self) -> Tuple[CommodOTCSwapLeg, ...]: """Commodities OTC swap leg""" return self.__legs @legs.setter def legs(self, value: Tuple[CommodOTCSwapLeg, ...]): self._property_changed('legs') self.__legs = value @property def settlement(self) -> str: """read only description in plain English of settlement terms""" return self.__settlement @settlement.setter def settlement(self, value: str): self._property_changed('settlement') self.__settlement = value
class EqOptionStrategy(Instrument): """Instrument definition for equity option strategy""" @camel_case_translate def __init__( self, underlier: Union[float, str], strategy: str, legs: Tuple[EqOptionLeg, ...], underlier_type: Union[UnderlierType, str] = None, expiration_date: Union[datetime.date, str] = None, strike_price: Union[float, str] = None, option_type: Union[OptionType, str] = None, option_style: Union[OptionStyle, str] = None, number_of_options: float = None, multiplier: float = None, settlement_date: Union[datetime.date, str] = None, settlement_currency: Union[Currency, str] = None, premium: float = None, premium_payment_date: Union[datetime.date, str] = None, valuation_time: Union[ValuationTime, str] = None, method_of_settlement: Union[OptionSettlementMethod, str] = None, buy_sell: Union[BuySell, str] = None, premium_currency: Union[Currency, str] = None, exchange: str = None, trade_as: Union[TradeAs, str] = None, name: str = None ): super().__init__() self.underlier = underlier self.underlier_type = underlier_type self.strategy = strategy self.legs = legs self.expiration_date = expiration_date self.strike_price = strike_price self.option_type = option_type self.option_style = option_style self.number_of_options = number_of_options self.multiplier = multiplier self.settlement_date = settlement_date self.settlement_currency = settlement_currency self.premium = premium self.premium_payment_date = premium_payment_date self.valuation_time = valuation_time self.method_of_settlement = method_of_settlement self.buy_sell = buy_sell self.premium_currency = premium_currency self.exchange = exchange self.trade_as = trade_as self.name = name @property def asset_class(self) -> AssetClass: """Equity""" return AssetClass.Equity @property def type(self) -> AssetType: """OptionStrategy""" return AssetType.OptionStrategy @property def underlier(self) -> Union[float, str]: """Underlier security identifier""" return self.__underlier @underlier.setter def underlier(self, value: Union[float, str]): self._property_changed('underlier') self.__underlier = value @property def underlier_type(self) -> Union[UnderlierType, str]: """Type of underlyer""" return self.__underlier_type @underlier_type.setter def underlier_type(self, value: Union[UnderlierType, str]): self._property_changed('underlier_type') self.__underlier_type = get_enum_value(UnderlierType, value) @property def strategy(self) -> str: """Option Strategy""" return self.__strategy @strategy.setter def strategy(self, value: str): self._property_changed('strategy') self.__strategy = value @property def legs(self) -> Tuple[EqOptionLeg, ...]: """Instrument definition for equity option leg""" return self.__legs @legs.setter def legs(self, value: Tuple[EqOptionLeg, ...]): self._property_changed('legs') self.__legs = value @property def expiration_date(self) -> Union[datetime.date, str]: """Date or tenor, e.g. 2018-09-03, 3m, Dec21""" return self.__expiration_date @expiration_date.setter def expiration_date(self, value: Union[datetime.date, str]): self._property_changed('expiration_date') self.__expiration_date = value @property def strike_price(self) -> Union[float, str]: """Strike as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF, 10/vol, 100k/pv, p=10000, p=10000USD, $200K/BP, or multiple strikes 65.4/-45.8""" return self.__strike_price @strike_price.setter def strike_price(self, value: Union[float, str]): self._property_changed('strike_price') self.__strike_price = value @property def option_type(self) -> Union[OptionType, str]: """Option Type""" return self.__option_type @option_type.setter def option_type(self, value: Union[OptionType, str]): self._property_changed('option_type') self.__option_type = get_enum_value(OptionType, value) @property def option_style(self) -> Union[OptionStyle, str]: """Option Exercise Style""" return self.__option_style @option_style.setter def option_style(self, value: Union[OptionStyle, str]): self._property_changed('option_style') self.__option_style = get_enum_value(OptionStyle, value) @property def number_of_options(self) -> float: """Number of options""" return self.__number_of_options @number_of_options.setter def number_of_options(self, value: float): self._property_changed('number_of_options') self.__number_of_options = value @property def multiplier(self) -> float: """Number of stock units per option contract""" return self.__multiplier @multiplier.setter def multiplier(self, value: float): self._property_changed('multiplier') self.__multiplier = value @property def settlement_date(self) -> Union[datetime.date, str]: """Date or tenor, e.g. 2018-09-03, 3m, Dec21""" return self.__settlement_date @settlement_date.setter def settlement_date(self, value: Union[datetime.date, str]): self._property_changed('settlement_date') self.__settlement_date = value @property def settlement_currency(self) -> Union[Currency, str]: """Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)""" return self.__settlement_currency @settlement_currency.setter def settlement_currency(self, value: Union[Currency, str]): self._property_changed('settlement_currency') self.__settlement_currency = get_enum_value(Currency, value) @property def premium(self) -> float: """Option premium""" return self.__premium @premium.setter def premium(self, value: float): self._property_changed('premium') self.__premium = value @property def premium_payment_date(self) -> Union[datetime.date, str]: """Option premium""" return self.__premium_payment_date @premium_payment_date.setter def premium_payment_date(self, value: Union[datetime.date, str]): self._property_changed('premium_payment_date') self.__premium_payment_date = value @property def valuation_time(self) -> Union[ValuationTime, str]: """Valuation time (e.g. MktClose, MktOpen) of the underlying level for exercise""" return self.__valuation_time @valuation_time.setter def valuation_time(self, value: Union[ValuationTime, str]): self._property_changed('valuation_time') self.__valuation_time = get_enum_value(ValuationTime, value) @property def method_of_settlement(self) -> Union[OptionSettlementMethod, str]: """How the option is settled (e.g. Cash, Physical)""" return self.__method_of_settlement @method_of_settlement.setter def method_of_settlement(self, value: Union[OptionSettlementMethod, str]): self._property_changed('method_of_settlement') self.__method_of_settlement = get_enum_value(OptionSettlementMethod, value) @property def buy_sell(self) -> Union[BuySell, str]: """Buy or Sell side of contract""" return self.__buy_sell @buy_sell.setter def buy_sell(self, value: Union[BuySell, str]): self._property_changed('buy_sell') self.__buy_sell = get_enum_value(BuySell, value) @property def premium_currency(self) -> Union[Currency, str]: """Currency of the option premium""" return self.__premium_currency @premium_currency.setter def premium_currency(self, value: Union[Currency, str]): self._property_changed('premium_currency') self.__premium_currency = get_enum_value(Currency, value) @property def exchange(self) -> str: """Name of marketplace where security, derivative or other instrument is traded""" return self.__exchange @exchange.setter def exchange(self, value: str): self._property_changed('exchange') self.__exchange = value @property def trade_as(self) -> Union[TradeAs, str]: """Option trade as (i.e. listed, otc, lookalike etc)""" return self.__trade_as @trade_as.setter def trade_as(self, value: Union[TradeAs, str]): self._property_changed('trade_as') self.__trade_as = get_enum_value(TradeAs, value)
[docs]class FXMultiCrossBinary(Instrument): """Object representation of an FX multi-cross binary""" @camel_case_translate def __init__( self, buy_sell: Union[BuySell, str] = None, legs: Tuple[FXMultiCrossBinaryLeg, ...] = None, notional_amount: Union[float, str] = None, notional_currency: Union[Currency, str] = None, settlement_date: Union[datetime.date, str] = None, expiration_date: Union[datetime.date, str] = None, expiration_time: str = None, premium: Union[float, str] = None, premium_currency: Union[Currency, str] = None, premium_payment_date: str = None, name: str = None ): super().__init__() self.buy_sell = buy_sell self.legs = legs self.notional_amount = notional_amount self.notional_currency = notional_currency self.settlement_date = settlement_date self.expiration_date = expiration_date self.expiration_time = expiration_time self.premium = premium self.premium_currency = premium_currency self.premium_payment_date = premium_payment_date self.name = name @property def asset_class(self) -> AssetClass: """FX""" return AssetClass.FX @property def type(self) -> AssetType: """MultiCrossBinary""" return AssetType.MultiCrossBinary @property def buy_sell(self) -> Union[BuySell, str]: """Buy or Sell side of contract""" return self.__buy_sell @buy_sell.setter def buy_sell(self, value: Union[BuySell, str]): self._property_changed('buy_sell') self.__buy_sell = get_enum_value(BuySell, value) @property def legs(self) -> Tuple[FXMultiCrossBinaryLeg, ...]: """Object representation of a single leg of a multi-cross binary option""" return self.__legs @legs.setter def legs(self, value: Tuple[FXMultiCrossBinaryLeg, ...]): self._property_changed('legs') self.__legs = value @property def notional_amount(self) -> Union[float, str]: """Notional amount""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: Union[float, str]): self._property_changed('notional_amount') self.__notional_amount = value @property def notional_currency(self) -> Union[Currency, str]: """Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)""" return self.__notional_currency @notional_currency.setter def notional_currency(self, value: Union[Currency, str]): self._property_changed('notional_currency') self.__notional_currency = get_enum_value(Currency, value) @property def settlement_date(self) -> Union[datetime.date, str]: """Settlement date of the option, after expiration""" return self.__settlement_date @settlement_date.setter def settlement_date(self, value: Union[datetime.date, str]): self._property_changed('settlement_date') self.__settlement_date = value @property def expiration_date(self) -> Union[datetime.date, str]: """Date or tenor, e.g. 2018-09-03, 3m, Dec21""" return self.__expiration_date @expiration_date.setter def expiration_date(self, value: Union[datetime.date, str]): self._property_changed('expiration_date') self.__expiration_date = value @property def expiration_time(self) -> str: """The location and (optionally) time of spot for expiration""" return self.__expiration_time @expiration_time.setter def expiration_time(self, value: str): self._property_changed('expiration_time') self.__expiration_time = value @property def premium(self) -> Union[float, str]: """Option premium""" return self.__premium @premium.setter def premium(self, value: Union[float, str]): self._property_changed('premium') self.__premium = value @property def premium_currency(self) -> Union[Currency, str]: """Currency of the option premium""" return self.__premium_currency @premium_currency.setter def premium_currency(self, value: Union[Currency, str]): self._property_changed('premium_currency') self.__premium_currency = get_enum_value(Currency, value) @property def premium_payment_date(self) -> str: """Payment date of the option premium""" return self.__premium_payment_date @premium_payment_date.setter def premium_payment_date(self, value: str): self._property_changed('premium_payment_date') self.__premium_payment_date = value
class FXOptionStrategy(Instrument): """Object representation of a FX option Strategy""" @camel_case_translate def __init__( self, pair: str = None, buy_sell: Union[BuySell, str] = None, strategy_name: str = None, legs: Tuple[FXOptionLeg, ...] = None, option_type: Union[OptionType, str] = None, notional_amount: Union[float, str] = None, notional_currency: Union[Currency, str] = None, notional_amount_other_currency: Union[float, str] = None, strike_price: Union[float, str] = None, settlement_date: Union[datetime.date, str] = None, settlement_currency: Union[Currency, str] = None, settlement_rate_option: str = None, method_of_settlement: Union[OptionSettlementMethod, str] = None, expiration_date: Union[datetime.date, str] = None, expiration_time: str = None, premium: Union[float, str] = None, premium_currency: Union[Currency, str] = None, premium_payment_date: str = None, name: str = None ): super().__init__() self.pair = pair self.buy_sell = buy_sell self.strategy_name = strategy_name self.legs = legs self.option_type = option_type self.notional_amount = notional_amount self.notional_currency = notional_currency self.notional_amount_other_currency = notional_amount_other_currency self.strike_price = strike_price self.settlement_date = settlement_date self.settlement_currency = settlement_currency self.settlement_rate_option = settlement_rate_option self.method_of_settlement = method_of_settlement self.expiration_date = expiration_date self.expiration_time = expiration_time self.premium = premium self.premium_currency = premium_currency self.premium_payment_date = premium_payment_date self.name = name @property def asset_class(self) -> AssetClass: """FX""" return AssetClass.FX @property def type(self) -> AssetType: """OptionStrategy""" return AssetType.OptionStrategy @property def pair(self) -> str: """A currency pair, e.g.: EURUSD or EUR USD""" return self.__pair @pair.setter def pair(self, value: str): self._property_changed('pair') self.__pair = value @property def buy_sell(self) -> Union[BuySell, str]: """Buy or Sell side of contract""" return self.__buy_sell @buy_sell.setter def buy_sell(self, value: Union[BuySell, str]): self._property_changed('buy_sell') self.__buy_sell = get_enum_value(BuySell, value) @property def strategy_name(self) -> str: return self.__strategy_name @strategy_name.setter def strategy_name(self, value: str): self._property_changed('strategy_name') self.__strategy_name = value @property def legs(self) -> Tuple[FXOptionLeg, ...]: """Object representation of a FX option leg used in FXOptionStrategy""" return self.__legs @legs.setter def legs(self, value: Tuple[FXOptionLeg, ...]): self._property_changed('legs') self.__legs = value @property def option_type(self) -> Union[OptionType, str]: """Option Type""" return self.__option_type @option_type.setter def option_type(self, value: Union[OptionType, str]): self._property_changed('option_type') self.__option_type = get_enum_value(OptionType, value) @property def notional_amount(self) -> Union[float, str]: """Notional amount""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: Union[float, str]): self._property_changed('notional_amount') self.__notional_amount = value @property def notional_currency(self) -> Union[Currency, str]: """Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)""" return self.__notional_currency @notional_currency.setter def notional_currency(self, value: Union[Currency, str]): self._property_changed('notional_currency') self.__notional_currency = get_enum_value(Currency, value) @property def notional_amount_other_currency(self) -> Union[float, str]: """Notional amount in currency other than NotionalCurrency from the pair""" return self.__notional_amount_other_currency @notional_amount_other_currency.setter def notional_amount_other_currency(self, value: Union[float, str]): self._property_changed('notional_amount_other_currency') self.__notional_amount_other_currency = value @property def strike_price(self) -> Union[float, str]: """Strike as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF, 10/vol, 100k/pv, p=10000, p=10000USD, $200K/BP, or multiple strikes 65.4/-45.8""" return self.__strike_price @strike_price.setter def strike_price(self, value: Union[float, str]): self._property_changed('strike_price') self.__strike_price = value @property def settlement_date(self) -> Union[datetime.date, str]: """Settlement date of the option, after expiration""" return self.__settlement_date @settlement_date.setter def settlement_date(self, value: Union[datetime.date, str]): self._property_changed('settlement_date') self.__settlement_date = value @property def settlement_currency(self) -> Union[Currency, str]: """Currency of settlement""" return self.__settlement_currency @settlement_currency.setter def settlement_currency(self, value: Union[Currency, str]): self._property_changed('settlement_currency') self.__settlement_currency = get_enum_value(Currency, value) @property def settlement_rate_option(self) -> str: """The source of spot for settlement""" return self.__settlement_rate_option @settlement_rate_option.setter def settlement_rate_option(self, value: str): self._property_changed('settlement_rate_option') self.__settlement_rate_option = value @property def method_of_settlement(self) -> Union[OptionSettlementMethod, str]: """How the option is settled (e.g. Cash, Physical)""" return self.__method_of_settlement @method_of_settlement.setter def method_of_settlement(self, value: Union[OptionSettlementMethod, str]): self._property_changed('method_of_settlement') self.__method_of_settlement = get_enum_value(OptionSettlementMethod, value) @property def expiration_date(self) -> Union[datetime.date, str]: """Date or tenor, e.g. 2018-09-03, 3m, Dec21""" return self.__expiration_date @expiration_date.setter def expiration_date(self, value: Union[datetime.date, str]): self._property_changed('expiration_date') self.__expiration_date = value @property def expiration_time(self) -> str: """The location and (optionally) time of spot for expiration""" return self.__expiration_time @expiration_time.setter def expiration_time(self, value: str): self._property_changed('expiration_time') self.__expiration_time = value @property def premium(self) -> Union[float, str]: """Option premium""" return self.__premium @premium.setter def premium(self, value: Union[float, str]): self._property_changed('premium') self.__premium = value @property def premium_currency(self) -> Union[Currency, str]: """Currency of the option premium""" return self.__premium_currency @premium_currency.setter def premium_currency(self, value: Union[Currency, str]): self._property_changed('premium_currency') self.__premium_currency = get_enum_value(Currency, value) @property def premium_payment_date(self) -> str: """Payment date of the option premium""" return self.__premium_payment_date @premium_payment_date.setter def premium_payment_date(self, value: str): self._property_changed('premium_payment_date') self.__premium_payment_date = value class InvoiceSpread(Instrument): """An interest swap vs a bond future""" @camel_case_translate def __init__( self, buy_sell: Union[BuySell, str] = None, notional_amount: Union[float, str] = None, underlier: Union[float, str] = None, swap: IRSwap = None, future: IRBondFuture = None, name: str = None ): super().__init__() self.buy_sell = buy_sell self.notional_amount = notional_amount self.underlier = underlier self.swap = swap self.future = future self.name = name @property def asset_class(self) -> AssetClass: """Rates""" return AssetClass.Rates @property def type(self) -> AssetType: """InvoiceSpread""" return AssetType.InvoiceSpread @property def buy_sell(self) -> Union[BuySell, str]: """Buy or Sell side of contract""" return self.__buy_sell @buy_sell.setter def buy_sell(self, value: Union[BuySell, str]): self._property_changed('buy_sell') self.__buy_sell = get_enum_value(BuySell, value) @property def notional_amount(self) -> Union[float, str]: """Notional amount""" return self.__notional_amount @notional_amount.setter def notional_amount(self, value: Union[float, str]): self._property_changed('notional_amount') self.__notional_amount = value @property def underlier(self) -> Union[float, str]: """Underlier of the bond future""" return self.__underlier @underlier.setter def underlier(self, value: Union[float, str]): self._property_changed('underlier') self.__underlier = value @property def swap(self) -> IRSwap: """A vanilla interest rate swap of fixed vs floating cashflows""" return self.__swap @swap.setter def swap(self, value: IRSwap): self._property_changed('swap') self.__swap = value @property def future(self) -> IRBondFuture: """A future on a (treasury) bond""" return self.__future @future.setter def future(self, value: IRBondFuture): self._property_changed('future') self.__future = value

================================================ FILE: docs/_build/html/_modules/gs_quant/timeseries/algebra.html ================================================ gs_quant.timeseries.algebra — gs_quant 0.1 documentation

Source code for gs_quant.timeseries.algebra

# Copyright 2018 Goldman Sachs.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#   http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#
#
# Chart Service will attempt to make public functions (not prefixed with _) from this module available. Such functions
# should be fully documented: docstrings should describe parameters and the return value, and provide a 1-line
# description. Type annotations should be provided for parameters.


import math

from .datetime import *
from .helper import plot_function
from functools import reduce
from gs_quant.errors import MqTypeError

"""
Algebra library contains basic numerical and algebraic operations, including addition, division, multiplication,
division and other functions on timeseries
"""


class FilterOperator(Enum):
    LESS = 'less_than'
    GREATER = 'greater_than'
    L_EQUALS = 'l_equals'
    G_EQUALS = 'g_equals'
    EQUALS = 'equals'
    N_EQUALS = 'not_equals'


[docs]@plot_function def add(x: Union[pd.Series, Real], y: Union[pd.Series, Real], method: Interpolate = Interpolate.STEP) \ -> Union[pd.Series, Real]: """ Add two series or scalars :param x: timeseries or scalar :param y: timeseries or scalar :param method: interpolation method (default: step). Only used when both x and y are timeseries :return: timeseries of x + y or sum of the given real numbers **Usage** Add two series or scalar variables with the given interpolation method :math:`R_t = X_t + Y_t` Alignment operators: ========= ======================================================================== Method Behavior ========= ======================================================================== intersect Resultant series only has values on the intersection of dates. Values for dates present in only one series will be ignored nan Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as nan in the other series, and therefore in the resultant series zero Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as zero in the other series step Resultant series has values on the union of dates in both series. Values for dates only available in one series will be interpolated via step function in the other series time Resultant series have values on the union of dates / times. Missing values surrounded by valid values will be interpolated given length of interval. Input series must use DateTimeIndex. ========= ======================================================================== **Examples** Add two series: >>> a = generate_series(100) >>> b = generate_series(100) >>> add(a, b, Interpolate.STEP) **See also** :func:`subtract` """ if isinstance(x, Real) and isinstance(y, Real): return x + y [x_align, y_align] = align(x, y, method) return x_align.add(y_align)
[docs]@plot_function def subtract(x: Union[pd.Series, Real], y: Union[pd.Series, Real], method: Interpolate = Interpolate.STEP) \ -> Union[pd.Series, Real]: """ Add two series or scalars :param x: timeseries or scalar :param y: timeseries or scalar :param method: index alignment operator (default: intersect). Only used when both x and y are timeseries :return: timeseries of x - y or difference between the given real numbers **Usage** Subtracts one series or scalar from another applying the given interpolation method :math:`R_t = X_t - Y_t` Alignment operators: ========= ======================================================================== Method Behavior ========= ======================================================================== intersect Resultant series only has values on the intersection of dates. Values for dates present in only one series will be ignored nan Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as nan in the other series, and therefore in the resultant series zero Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as zero in the other series step Resultant series has values on the union of dates in both series. Values for dates only available in one series will be interpolated via step function in the other series time Resultant series have values on the union of dates / times. Missing values surrounded by valid values will be interpolated given length of interval. Input series must use DateTimeIndex. ========= ======================================================================== **Examples** Subtract one series from another: >>> a = generate_series(100) >>> b = generate_series(100) >>> subtract(a, b, Interpolate.STEP) **See also** :func:`add` """ # Determine how we want to handle observations prior to start date if isinstance(x, Real) and isinstance(y, Real): return x - y [x_align, y_align] = align(x, y, method) return x_align.subtract(y_align)
[docs]@plot_function def multiply(x: Union[pd.Series, Real], y: Union[pd.Series, Real], method: Interpolate = Interpolate.STEP) \ -> Union[pd.Series, Real]: """ Multiply two series or scalars :param x: timeseries or scalar :param y: timeseries or scalar :param method: interpolation method (default: step). Only used when both x and y are timeseries :return: timeseries of x * y or product of the given real numbers **Usage** Multiply two series or scalar variables applying the given interpolation method :math:`R_t = X_t \\times Y_t` Alignment operators: ========= ======================================================================== Method Behavior ========= ======================================================================== intersect Resultant series only has values on the intersection of dates. Values for dates present in only one series will be ignored nan Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as nan in the other series, and therefore in the resultant series zero Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as zero in the other series step Resultant series has values on the union of dates in both series. Values for dates only available in one series will be interpolated via step function in the other series time Resultant series have values on the union of dates / times. Missing values surrounded by valid values will be interpolated given length of interval. Input series must use DateTimeIndex. ========= ======================================================================== **Examples** Multiply two series: >>> a = generate_series(100) >>> b = generate_series(100) >>> multiply(a, b, Interpolate.STEP) **See also** :func:`divide` """ if isinstance(x, Real) and isinstance(y, Real): return x * y [x_align, y_align] = align(x, y, method) return x_align.multiply(y_align)
[docs]@plot_function def divide(x: Union[pd.Series, Real], y: Union[pd.Series, Real], method: Interpolate = Interpolate.STEP) \ -> Union[pd.Series, Real]: """ Divide two series or scalars :param x: timeseries or scalar :param y: timeseries or scalar :param method: interpolation method (default: step). Only used when both x and y are timeseries :return: timeseries of x / y or quotient of the given real numbers **Usage** Divide two series or scalar variables applying the given interpolation method :math:`R_t = X_t / Y_t` Alignment operators: ========= ======================================================================== Method Behavior ========= ======================================================================== intersect Resultant series only has values on the intersection of dates. Values for dates present in only one series will be ignored nan Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as nan in the other series, and therefore in the resultant series zero Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as zero in the other series step Resultant series has values on the union of dates in both series. Values for dates only available in one series will be interpolated via step function in the other series time Resultant series have values on the union of dates / times. Missing values surrounded by valid values will be interpolated given length of interval. Input series must use DateTimeIndex. ========= ======================================================================== **Examples** Divide two series: >>> a = generate_series(100) >>> b = generate_series(100) >>> divide(a, b, Interpolate.STEP) **See also** :func:`multiply` """ if isinstance(x, Real) and isinstance(y, Real): return x / y [x_align, y_align] = align(x, y, method) return x_align.divide(y_align)
[docs]@plot_function def floordiv(x: Union[pd.Series, Real], y: Union[pd.Series, Real], method: Interpolate = Interpolate.STEP) \ -> Union[pd.Series, Real]: """ Floor divide two series or scalars :param x: timeseries or scalar :param y: timeseries or scalar :param method: interpolation method (default: step). Only used for operating two series :return: timeseries of x // y or quotient of the floor division of the given real numbers **Usage** Divide two series or scalar variables applying the given interpolation method :math:`R_t = X_t / Y_t` Alignment operators: ========= ======================================================================== Method Behavior ========= ======================================================================== intersect Resultant series only has values on the intersection of dates. Values for dates present in only one series will be ignored nan Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as nan in the other series, and therefore in the resultant series zero Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as zero in the other series step Resultant series has values on the union of dates in both series. Values for dates only available in one series will be interpolated via step function in the other series ========= ======================================================================== **Examples** Floor divide two series: >>> a = generate_series(100) >>> b = generate_series(100) >>> floordiv(a, b, Interpolate.STEP) **See also** :func:`divide` """ if isinstance(x, Real) and isinstance(y, Real): return x // y [x_align, y_align] = align(x, y, method) return x_align.floordiv(y_align)
[docs]@plot_function def exp(x: pd.Series) -> pd.Series: """ Exponential of series :param x: timeseries :return: exponential of each element **Usage** For each element in the series, :math:`X_t`, raise :math:`e` (Euler's number) to the power of :math:`X_t`. Euler's number is the base of the natural logarithm, :math:`ln`. :math:`R_t = e^{X_t}` **Examples** Raise :math:`e` to the power :math:`1`. Returns Euler's number, approximately 2.71828 >>> exp(1) **See also** :func:`log` """ return np.exp(x)
[docs]@plot_function def log(x: pd.Series) -> pd.Series: """ Natural logarithm of series :param x: timeseries :return: series with exponential of each element **Usage** For each element in the series, :math:`X_t`, return the natural logarithm :math:`ln` of :math:`X_t` The natural logarithm is the logarithm in base :math:`e`. :math:`R_t = log(X_t)` This function is the inverse of the exponential function. More information on `logarithms <https://en.wikipedia.org/wiki/Logarithm>`_ **Examples** Take natural logarithm of 3 >>> log(3) **See also** :func:`exp` """ return np.log(x)
[docs]@plot_function def power(x: pd.Series, y: float = 1) -> pd.Series: """ Raise each element in series to power :param x: timeseries :param y: value :return: date-based time series of square roots **Usage** Raise each value in time series :math:`X_t` to the power :math:`y`: :math:`R_t = X_t^{y}` **Examples** Generate price series and raise each value to the power 2: >>> prices = generate_series(100) >>> power(prices, 2) **See also** :func:`sqrt` """ return np.power(x, y)
[docs]@plot_function def sqrt(x: Union[Real, pd.Series]) -> Union[Real, pd.Series]: """ Square root of (a) each element in a series or (b) a real number :param x: date-based time series of prices or real number :return: date-based time series of square roots or square root of given number **Usage** Return the square root of each value in time series :math:`X_t`: :math:`R_t = \\sqrt{X_t}` **Examples** Generate price series and take square root of each value: >>> prices = generate_series(100) >>> sqrt(prices) **See also** :func:`pow` """ if isinstance(x, pd.Series): return np.sqrt(x) result = math.sqrt(x) # return int if result is integral (should work for values up to 2**53) return round(result) if round(result) == result else result
[docs]@plot_function def abs_(x: pd.Series) -> pd.Series: """ Absolute value of each element in series :param x: date-based time series of prices :return: date-based time series of absolute value **Usage** Return the absolute value of :math:`X`. For each value in time series :math:`X_t`, return :math:`X_t` if :math:`X_t` is greater than or equal to 0; otherwise return :math:`-X_t`: :math:`R_t = |X_t|` Equivalent to :math:`R_t = \sqrt{X_t^2}` **Examples** Generate price series and take absolute value of :math:`X_t-100` >>> prices = generate_series(100) - 100 >>> abs_(prices) **See also** :func:`exp` :func:`sqrt` """ return abs(x)
[docs]@plot_function def floor(x: pd.Series, value: float = 0) -> pd.Series: """ Floor series at minimum value :param x: date-based time series of prices :param value: minimum value :return: date-based time series of maximum value **Usage** Returns series where all values are greater than or equal to the minimum value. :math:`R_t = max(X_t, value)` See `Floor and Ceil functions <https://en.wikipedia.org/wiki/Floor_and_ceiling_functions>`_ for more details **Examples** Generate price series and floor all values at 100 >>> prices = generate_series(100) >>> floor(prices, 100) **See also** :func:`ceil` """ assert x.index.is_monotonic_increasing return x.apply(lambda y: max(y, value))
[docs]@plot_function def ceil(x: pd.Series, value: float = 0) -> pd.Series: """ Cap series at maximum value :param x: date-based time series of prices :param value: maximum value :return: date-based time series of maximum value **Usage** Returns series where all values are less than or equal to the maximum value. :math:`R_t = min(X_t, value)` See `Floor and Ceil functions <https://en.wikipedia.org/wiki/Floor_and_ceiling_functions>`_ for more details **Examples** Generate price series and floor all values at 100 >>> prices = generate_series(100) >>> floor(prices, 100) **See also** :func:`floor` """ assert x.index.is_monotonic_increasing return x.apply(lambda y: min(y, value))
[docs]@plot_function def filter_(x: pd.Series, operator: Optional[FilterOperator] = None, value: Optional[Real] = None) -> pd.Series: """ Removes values where comparison with the operator and value combination results in true, defaults to removing missing values from the series :param x: timeseries :param operator: FilterOperator describing logic for value removal, e.g 'less_than' :param value: number indicating value(s) to remove from the series :return: timeseries with specified values removed **Usage** Remove each value determined by operator and value from timeseries where that expression yields true **Examples** Remove 0 from time series >>> prices = generate_series(100) >>> filter_(prices, FilterOperator.EQUALS, 0) Remove positive numbers from time series >>> prices = generate_series(100) >>> filter_(prices, FilterOperator.GREATER, 0) Remove missing values from time series >>> prices = generate_series(100) >>> filter_(prices) """ if value is None and operator is None: x = x.dropna(axis=0, how='any') elif value is None: raise MqValueError('No value is specified for the operator') else: if operator == FilterOperator.EQUALS: remove = x == value elif operator == FilterOperator.GREATER: remove = x > value elif operator == FilterOperator.LESS: remove = x < value elif operator == FilterOperator.L_EQUALS: remove = x <= value elif operator == FilterOperator.G_EQUALS: remove = x >= value elif operator == FilterOperator.N_EQUALS: remove = x != value else: if not isinstance(operator, str): operator = str(operator) raise MqValueError('Unexpected operator: ' + operator) x = x.drop(x[remove].index) return x
[docs]@plot_function def smooth_spikes(x: pd.Series, threshold: float) -> pd.Series: """ Smooth out the spikes of a series. If a point is larger/smaller than (1 +/- threshold) times both neighbors, replace it with the average of those neighbours. Note: the first and last points in the input series are dropped. :param x: timeseries :param threshold: minimum increment to trigger filter :return: smoothed timeseries **Usage** Returns series where values that exceed the threshold relative to both neighbors are replaced. **Examples** Generate price series and smooth spikes over a threshold of 0.5. >>> prices = generate_series(100) >>> smooth_spikes(prices, 0.5) **See also** :func:`exponential_moving_average` """ if len(x) < 3: return pd.Series() result = x.copy() multiplier = (1 + threshold) current, next_ = x.iloc[0:2] for i in range(1, len(x) - 1): previous = current current = next_ next_ = x.iloc[i + 1] scaled = current * multiplier if (current > previous * multiplier and current > next_ * multiplier) or (previous > scaled and next_ > scaled): result.iloc[i] = (previous + next_) / 2 return result[1:-1]
[docs]@plot_function def repeat(x: pd.Series, n: int = 1) -> pd.Series: """ Repeats values for days where data is missing. For any date with missing data, the last recorded value is used. Optionally downsamples the result such that there are data points every n days. :param x: date-based timeseries :param n: desired frequency of output :return: a timeseries that has been forward-filled, and optionally downsampled **Usage** Fill missing values with last seen value e.g. to combine daily with weekly or monthly data. """ if not 0 < n < 367: raise MqValueError('n must be between 0 and 367') index = pd.date_range(freq=f'{n}D', start=x.index[0], end=x.index[-1]) return x.reindex(index, method='ffill')
def _sum_boolean_series(*series): if not 2 <= len(series) <= 100: raise MqValueError('expected between 2 and 100 arguments') for s in series: if not isinstance(s, pd.Series): raise MqTypeError('all arguments must be series') if not all(map(lambda a: a in (0, 1), s.values)): raise MqValueError(f'cannot perform operation on series with value(s) other than 1 and 0: {s.values}') current = series[0].add(series[1], fill_value=0) for s in series[2:]: current = current.add(s, fill_value=0) return current
[docs]@plot_function def and_(*series: pd.Series) -> pd.Series: """ Logical "and" of two or more boolean series. :param series: input series :return: result series (of numeric type, with booleans represented as 1s and 0s) """ s = _sum_boolean_series(*series) return (s == len(series)).astype(int)
[docs]@plot_function def or_(*series: pd.Series) -> pd.Series: """ Logical "or" of two or more boolean series. :param series: input series :return: result series (of numeric type, with booleans represented as 1s and 0s) """ s = _sum_boolean_series(*series) return (s > 0).astype(int)
[docs]@plot_function def not_(series: pd.Series) -> pd.Series: """ Logical negation of a single boolean series. :param series: single input series :return: result series (of numeric type, with booleans represented as 1s and 0s) """ if not all(map(lambda a: a in (0, 1), series.values)): raise MqValueError(f'cannot negate series with value(s) other than 1 and 0: {series.values}') return series.replace([0, 1], [1, 0])
[docs]@plot_function def if_(flags: pd.Series, x: Union[pd.Series, float], y: Union[pd.Series, float]) -> pd.Series: """ Returns a series s. For i in the index of flags, s[i] = x[i] if flags[i] == 1 else y[i]. :param flags: series of 1s and 0s :param x: values to use when flag is 1 :param y: values to use when flag is 0 :return: result series """ if not all(map(lambda a: a in (0, 1), flags.values)): raise MqValueError(f'cannot perform "if" on series with value(s) other than 1 and 0: {flags.values}') def ensure_series(s): if isinstance(s, (float, int)): return flags, pd.Series([s] * flags.shape[0], index=flags.index) elif isinstance(s, pd.Series): return flags.align(s) else: raise MqTypeError('expected a number or series') x_flags, x = ensure_series(x) y_flags, y = ensure_series(y) return pd.concat([x[x_flags == 1], y[y_flags == 0]]).sort_index()
[docs]@plot_function def weighted_sum(series: List[pd.Series], weights: list) -> pd.Series: """ Calculate a weighted sum. :param series: list of time series :param weights: list of weights :return: time series of weighted average **Usage** Calculate a weighted sum e.g. for a basket. **Examples** Generate price series and get a sum (weights 70%/30%). >>> prices1 = generate_series(100) >>> prices2 = generate_series(100) >>> mybasket = weighted_sum([prices1, prices2], [0.7, 0.3]) **See also** :func:`basket` """ if not all(isinstance(x, pd.Series) for x in series): raise MqTypeError("expected a list of time series") if not all(isinstance(y, (float, int)) for y in weights): raise MqTypeError("expected a list of number for weights") if len(weights) != len(series): raise MqValueError("must have one weight for each time series") # for input series, get the intersection of their calendars cal = pd.DatetimeIndex( reduce( np.intersect1d, ( curve.index for curve in series ), ) ) # reindex inputs and calculate series = [s.reindex(cal) for s in series] weights = [pd.Series(w, index=cal) for w in weights] return sum(series[i] * weights[i] for i in range(len(series))) / sum(weights)

================================================ FILE: docs/_build/html/_modules/gs_quant/timeseries/analysis.html ================================================ gs_quant.timeseries.analysis — gs_quant 0.1 documentation

Source code for gs_quant.timeseries.analysis

# Copyright 2018 Goldman Sachs.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#   http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#
#
# Chart Service will attempt to make public functions (not prefixed with _) from this module available. Such functions
# should be fully documented: docstrings should describe parameters and the return value, and provide a 1-line
# description. Type annotations should be provided for parameters.
import re

from gs_quant.datetime import relative_date_add
from gs_quant.timeseries.datetime import *
from .helper import plot_function

"""
Timeseries analysis library contains functions used to analyze properties of timeseries, including laging, differencing,
autocorrelation, co-integration and other operations
"""


[docs]@plot_function def first(x: pd.Series) -> pd.Series: """ First value of series :param x: time series :return: time series of first value **Usage** Return series with first value of `X` for all dates: :math:`R_t = X_0` where :math:`X_0` is the first value in the series **Examples** Last value of series: >>> series = generate_series(100) >>> returns = first(series) **See also** :func:`last` """ return pd.Series(x[0], x.index)
[docs]@plot_function def last(x: pd.Series) -> pd.Series: """ Last value of series (as a series) :param x: time series :return: time series of last value **Usage** Return series with last value of `X` for all dates: :math:`R_t = X_T` where :math:`X_T` is the last value in the series **Examples** Last value of series: >>> series = generate_series(100) >>> returns = last(series) **See also** :func:`first` """ return pd.Series(x[-1], x.index)
[docs]@plot_function def last_value(x: pd.Series) -> Union[int, float]: """ Last value of series (as a scalar) :param x: time series :return: last value **Usage** Return a scalar value :math:`X_T` where T is the last index value in the series. **Examples** Last value of series: >>> series = generate_series(100) >>> lv = last_value(series) **See also** :func:`last` """ if x.empty: raise MqValueError("cannot get last value of an empty series") return x.iloc[-1]
[docs]@plot_function def count(x: pd.Series) -> pd.Series: """ Count observations in series :param x: time series :return: number of observations **Usage** Count the number of valid observations in a series: :math:`R_t = R_{t-1} + 1` if :math:`X_t` is not NaN, and :math:`R_t = R_{t-1} + 0` if :math:`X_t` is NaN **Examples** Count observations in series: >>> series = generate_series(100) >>> count = count(series) **See also** :func:`sum` """ return x.rolling(x.size, 0).count()
[docs]@plot_function def diff(x: pd.Series, obs: int = 1) -> pd.Series: """ Diff observations with given lag :param x: time series of prices :param obs: number of observations to lag :return: date-based time series of return **Usage** Compute the difference in series values over a given lag: :math:`R_t = X_t - X_{t-obs}` where :math:`obs` is the number of observations to lag series in diff function **Examples** Diff prices levels: >>> series = generate_series(100) >>> returns = diff(series) **See also** :func:`lag` """ if x.size < 1: return x ret_series = x - x.shift(obs) return ret_series
class LagMode(Enum): TRUNCATE = "truncate" EXTEND = "extend"
[docs]@plot_function def lag(x: pd.Series, obs: Union[Window, int, str] = 1, mode: LagMode = LagMode.EXTEND) -> pd.Series: """ Lag timeseries by a number of observations or a relative date. :param x: timeseries of prices :param obs: non-zero integer (number of observations) or relative date e.g. "-90d", "1d", "1m", "1y" :param mode: whether to extend series index (into the future) :return: date-based time series of return **Usage** Shift the series backwards by a specified number of observations: :math:`R_t = X_{t-obs}` where :math:`obs` is the number of observations to lag series **Examples** Lag series by 2 observations: >>> prices = generate_series(100) >>> lagged = lag(prices, 2) Lag series by 1 year: >>> prices = generate_series(100) >>> lagged = lag(prices, '1y') **See also** :func:`diff` """ if isinstance(obs, str): end = x.index[-1] y = x.copy() # avoid mutating the provided series match = re.fullmatch('(\\d+)y', obs) if match: y.index += pd.DateOffset(years=int(match.group(1))) y = y.groupby(y.index).first() else: y.index += pd.DateOffset(relative_date_add(obs)) if mode == LagMode.EXTEND: return y return y[:end] obs = getattr(obs, 'w', obs) # Determine how we want to handle observations prior to start date if mode == LagMode.EXTEND: if x.index.resolution != 'day': raise MqValueError(f'unable to extend index with resolution {x.index.resolution}') kwargs = {'periods': abs(obs) + 1, 'freq': 'D'} if obs > 0: kwargs['start'] = x.index[-1] else: kwargs['end'] = x.index[0] x = x.reindex(x.index.union(pd.date_range(**kwargs))) return x.shift(obs)

================================================ FILE: docs/_build/html/_modules/gs_quant/timeseries/backtesting.html ================================================ gs_quant.timeseries.backtesting — gs_quant 0.1 documentation

Source code for gs_quant.timeseries.backtesting

"""
Copyright 2020 Goldman Sachs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
"""

from dateutil.relativedelta import relativedelta as rdelta
from functools import reduce

from gs_quant.timeseries.helper import _create_enum
from .statistics import *

RebalFreq = _create_enum('RebalFreq', ['Daily', 'Monthly'])
ReturnType = _create_enum('ReturnType', ['excess_return'])


[docs]@plot_function def basket( series: list, weights: list, costs: list = None, rebal_freq: RebalFreq = RebalFreq.DAILY, return_type: ReturnType = ReturnType.EXCESS_RETURN, ) -> pd.Series: """ Calculates a basket return series. :param series: list of time series of instrument prices :param weights: list of weights :param costs: list of execution costs in decimal; defaults to costs of 0 :param rebal_freq: rebalancing frequency - Daily or Monthly :param return_type: return type of underlying instruments - only excess return is supported :return: time series of the resulting basket **Usage** Calculates a basket return series. **Examples** Generate price series and combine them in a basket (weights 70%/30%) which rebalances monthly and assumes execution costs 5bps and 10bps each time the constituents are traded. >>> prices1 = generate_series(100) >>> prices2 = generate_series(100) >>> mybasket = basket([prices1, prices2], [0.7, 0.3], [0.0005, 0.001], monthly) **See also** :func:`prices` """ num_assets = len(series) costs = costs or [0] * num_assets if not all(isinstance(x, pd.Series) for x in series): raise MqTypeError("expected a list of series") if len(weights) != num_assets or len(weights) != len(costs): raise MqValueError("series, weights, and cost lists must have the same length") # For all inputs which are Pandas series, get the intersection of their calendars cal = pd.DatetimeIndex( reduce( np.intersect1d, ( curve.index for curve in series + weights + costs if isinstance(curve, pd.Series) ), ) ) # Reindex inputs and convert to pandas dataframes series = pd.concat([curve.reindex(cal) for curve in series], axis=1) weights = pd.concat([pd.Series(w, index=cal) for w in weights], axis=1) costs = pd.concat([pd.Series(c, index=cal) for c in costs], axis=1) if rebal_freq == RebalFreq.DAILY: rebal_dates = cal else: # Get hypothetical monthly rebalances num_rebals = (cal[-1].year - cal[0].year) * 12 + cal[-1].month - cal[0].month rebal_dates = [cal[0] + i * rdelta(months=1) for i in range(num_rebals + 1)] # Convert these to actual calendar days rebal_dates = [d for d in rebal_dates if d < max(cal)] rebal_dates = [min(cal[cal >= date]) for date in rebal_dates] # Create Units dataframe units = pd.DataFrame(index=cal, columns=series.columns) output = pd.Series(index=cal) # Initialize backtest output.values[0] = 100 units.values[0, ] = ( output.values[0] * weights.values[0, ] / series.values[0, ] ) # Run backtest prev_rebal = 0 for i, date in enumerate(cal[1:], 1): # Update performance output.values[i] = output.values[i - 1] + np.dot( units.values[i - 1, ], series.values[i, ] - series.values[i - 1, ] ) # Rebalance on rebal_dates if date in rebal_dates: # Compute costs actual_weights = ( weights.values[prev_rebal, ] * (series.values[i, ] / series.values[prev_rebal, ]) * (output.values[prev_rebal] / output.values[i]) ) output.values[i] -= ( np.dot(costs.values[i, ], np.abs(weights.values[i, ] - actual_weights)) * output.values[i] ) # Rebalance units.values[i, ] = ( output.values[i] * weights.values[i, ] / series.values[i, ] ) prev_rebal = i else: units.values[i, ] = units.values[ i - 1, ] return output

================================================ FILE: docs/_build/html/_modules/gs_quant/timeseries/datetime.html ================================================ gs_quant.timeseries.datetime — gs_quant 0.1 documentation

Source code for gs_quant.timeseries.datetime

# Copyright 2018 Goldman Sachs.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#   http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#
#
# Chart Service will attempt to make public functions (not prefixed with _) from this module available. Such functions
# should be fully documented: docstrings should describe parameters and the return value, and provide a 1-line
# description. Type annotations should be provided for parameters.
from datetime import date, time, timedelta
from numbers import Real
import numpy as np
from .helper import *
from ..errors import MqValueError
from ..datetime.date import DayCountConvention, PaymentFrequency, day_count_fraction
from ..datetime.date import date_range as _date_range

"""
Date and time manipulation for timeseries, including date or time shifting, calendar operations, curve alignment and
interpolation operations. Includes sampling operations based on daif dates[0]te or time manipulation
"""


def __interpolate_step(x: pd.Series, dates: pd.Series = None) -> pd.Series:
    if x.empty:
        raise MqValueError('Cannot perform step interpolation on an empty series')

    first_date = pd.Timestamp(dates.index[0]) if isinstance(x.index[0], pd.Timestamp) else dates.index[0]

    # locate previous valid date or take first value from series
    prev = x.index[0] if first_date < x.index[0] else x.index[x.index.get_loc(first_date, 'pad')]

    current = x[prev]

    curve = x.align(dates, 'right', )[0]                  # only need values from dates

    for knot in curve.iteritems():
        if np.isnan(knot[1]):
            curve[knot[0]] = current
        else:
            current = knot[1]
    return curve


[docs]@plot_function def align(x: Union[pd.Series, Real], y: Union[pd.Series, Real], method: Interpolate = Interpolate.INTERSECT) -> \ Union[List[pd.Series], List[Real]]: """ Align dates of two series or scalars :param x: first timeseries or scalar :param y: second timeseries or scalar :param method: interpolation method (default: intersect). Only used when both x and y are timeseries :return: timeseries with specified dates or two scalars from the input **Usage** Align the dates of two series using the specified interpolation method. Returns two series with dates based on the method of interpolation, for example, can be used to intersect the dates of two series, union dates with a defined manner to compute missing values. Interpolation methods: ========= ======================================================================== Type Behavior ========= ======================================================================== intersect Resultant series only have values on the intersection of dates /times. nan Resultant series have values on the union of dates /times. Values will be NaN for dates or times only present in the other series zero Resultant series have values on the union of dates / times. Values will be zero for dates or times only present in the other series step Resultant series have values on the union of dates / times. Each series will use the value of the previous valid point if requested date does not exist. Values prior to the first date will be equivalent to the first available value time Resultant series have values on the union of dates / times. Missing values surrounded by valid values will be interpolated given length of interval. Input series must use DateTimeIndex. ========= ======================================================================== **Examples** Stepwize interpolation of series based on dates in second series: >>> a = generate_series(100) >>> b = generate_series(100) >>> align(a, b) **See also** :func:`sub` """ if isinstance(x, Real) and isinstance(y, Real): return [x, y] if isinstance(x, Real): return [pd.Series(x, index=y.index), y] if isinstance(y, Real): return [x, pd.Series(y, index=x.index)] if method == Interpolate.INTERSECT: return x.align(y, 'inner') if method == Interpolate.NAN: return x.align(y, 'outer') if method == Interpolate.ZERO: return x.align(y, 'outer', fill_value=0) if method == Interpolate.TIME: new_x, new_y = x.align(y, 'outer') new_x.interpolate('time', limit_area='inside', inplace=True) new_y.interpolate('time', limit_area='inside', inplace=True) return [new_x, new_y] if method == Interpolate.STEP: new_x, new_y = x.align(y, 'outer') new_x.fillna(method='ffill', inplace=True) new_y.fillna(method='ffill', inplace=True) new_x.fillna(method='bfill', inplace=True) new_y.fillna(method='bfill', inplace=True) return [new_x, new_y] else: raise MqValueError('Unknown intersection type: ' + method)
[docs]@plot_function def interpolate(x: pd.Series, dates: Union[List[date], List[time], pd.Series] = None, method: Interpolate = Interpolate.INTERSECT) -> pd.Series: """ Interpolate over specified dates or times :param x: timeseries to interpolate :param dates: array of dates/times or another series to interpolate :param method: interpolation method (default: intersect) :return: timeseries with specified dates **Usage** Interpolate the series X over the dates specified by the dates parameter. This can be an array of dates or another series, in which case the index of the series will be used to specify dates Interpolation methods: ========= ======================================================================== Type Behavior ========= ======================================================================== intersect Resultant series only has values on the intersection of dates /times. Will only contain intersection of valid dates / times in the series nan Resultant series only has values on the intersection of dates /times. Value will be NaN for dates not present in the series zero Resultant series has values on all requested dates / times. The series will have a value of zero where the requested date or time was not present in the series step Resultant series has values on all requested dates / times. The series will use the value of the previous valid point if requested date does not exist. Values prior to the first date will be equivalent to the first available value ========= ======================================================================== **Examples** Stepwize interpolation of series based on dates in second series: >>> a = generate_series(100) >>> b = generate_series(100) >>> interpolate(a, b, Interpolate.INTERSECT) **See also** :func:`sub` """ if dates is None: dates = x if isinstance(dates, pd.Series): align_series = dates else: align_series = pd.Series(np.nan, dates) if method == Interpolate.INTERSECT: return x.align(align_series, 'inner')[0] if method == Interpolate.NAN: return x.align(align_series, 'right')[0] if method == Interpolate.ZERO: align_series = pd.Series(0.0, dates) return x.align(align_series, 'right', fill_value=0)[0] if method == Interpolate.STEP: return __interpolate_step(x, align_series) else: raise MqValueError('Unknown intersection type: ' + method)
[docs]@plot_function def value(x: pd.Series, date: Union[date, time], method: Interpolate = Interpolate.STEP) -> pd.Series: """ Value at specified date or time :param x: timeseries :param date: requested date or time :param method: interpolation method (default: step) :return: value at specified date or time **Usage** Returns the value of series X at the specified date: :math:`Y_t = X_{date}` If the requested date or time is not present in the series, the value function will return the value from the previous available date or time by default. Caller can specify other interpolation styles via the method param: Interpolation methods: ========= ======================================================================== Type Behavior ========= ======================================================================== intersect Only returns a value for valid dates nan Value will be NaN for dates not present in the series zero Value will be zero for dates not present in the series step Value of the previous valid point if requested date does not exist. Values prior to the first date will be equivalent to the first available value ========= ======================================================================== **Examples** Value of series on 5Mar18: >>> a = generate_series(100) >>> value(a, date(2019, 1, 3) **See also** :func:`interpolate` """ values = interpolate(x, [date], method) return values.get(0)
[docs]@plot_function def day(x: pd.Series) -> pd.Series: """ Day of each value in series :param x: time series :return: day of observations **Usage** Returns the day as a numeric value for each observation in the series: :math:`Y_t = day(t)` Day of the time or date is the integer day number within the month, e.g. 1-31 **Examples** Day for observations in series: >>> series = generate_series(100) >>> days = day(series) **See also** :func:`month` :func:`year` """ return pd.to_datetime(x.index.to_series()).dt.day
[docs]@plot_function def month(x: pd.Series) -> pd.Series: """ Month of each value in series :param x: time series :return: month of observations **Usage** Returns the month as a numeric value for each observation in the series: :math:`Y_t = month(t)` Month of the time or date is the integer month number, e.g. 1-12 **Examples** Day for observations in series: >>> series = generate_series(100) >>> days = month(series) **See also** :func:`day` :func:`year` """ return pd.to_datetime(x.index.to_series()).dt.month
[docs]@plot_function def year(x: pd.Series) -> pd.Series: """ Year of each value in series :param x: time series :return: year of observations **Usage** Returns the year as a numeric value for each observation in the series: :math:`Y_t = year(t)` Year of the time or date is the integer year number, e.g. 2019, 2020 **Examples** Year for observations in series: >>> series = generate_series(100) >>> days = year(series) **See also** :func:`day` :func:`month` """ return pd.to_datetime(x.index.to_series()).dt.year
[docs]@plot_function def quarter(x: pd.Series) -> pd.Series: """ Quarter of each value in series :param x: time series :return: quarter of observations **Usage** Returns the quarter as a numeric value for each observation in the series: :math:`Y_t = quarter(t)` Quarter of the time or date is the integer quarter number, e.g. 1, 2, 3, 4 **Examples** Quarter for observations in series: >>> series = generate_series(100) >>> days = quarter(series) **See also** :func:`day` :func:`month` """ return pd.to_datetime(x.index.to_series()).dt.quarter
[docs]@plot_function def weekday(x: pd.Series) -> pd.Series: """ Weekday of each value in series :param x: time series :return: weekday of observations **Usage** Returns the weekday as a numeric value for each observation in the series: :math:`Y_t = weekday(t)` Weekday of the time or date is the integer day of the week, e.g. 0-6, where 0 represents Monday **Examples** Weekday for observations in series: >>> series = generate_series(100) >>> days = weekday(series) **See also** :func:`day` :func:`month` """ return pd.to_datetime(x.index.to_series()).dt.weekday
@plot_function def day_count_fractions( dates: Union[List[date], pd.Series], convention: DayCountConvention = DayCountConvention.ACTUAL_360, frequency: PaymentFrequency = PaymentFrequency.MONTHLY ) -> pd.Series: """ Day count fractions between dates in series :param dates: time series or array of dates :param convention: day count convention (default: Actual/360 ISDA) :param frequency: payment frequency of instrument (optional) :return: series of day count fractions **Usage** Returns the day count fraction between dates in the series :math:`Y_t = DCF(t_{-1}, t)` Default is Actual/360 per ISDA specification: :math:`Y_t = \frac{Days(t_{-1}, t)}{360}` For a full list of available conventions, see `Day Count Conventions <https://developer.gs.com/docs/gsquant/guides/Dates/1-day-count-conventions>`_. For more information on day count conventions, see the `day count conventions <https://en.wikipedia.org/wiki/Day_count_convention>`_ page on Wikipedia **Examples** Weekday for observations in series: >>> series = generate_series(100) >>> days = day_count_fractions(series) **See also** :func:`day` :func:`month` :func:`year` """ if isinstance(dates, pd.Series): date_list = list(dates.index) else: date_list = dates if len(date_list) < 2: return pd.Series([]) start_dates = date_list[0:-1] end_dates = date_list[1:len(date_list)] dcfs = map(lambda a, b: day_count_fraction(a, b, convention, frequency), start_dates, end_dates) return pd.Series(data=[np.NaN] + list(dcfs), index=date_list[0:len(date_list)])
[docs]@plot_function def date_range(x: pd.Series, start_date: Union[date, int], end_date: Union[date, int], weekdays_only: bool = False) -> pd.Series: """ Create a time series from a (sub-)range of dates in an existing time series. :param x: time series :param start_date: start date for the sliced time series. If integer, number of observations after the first :param end_date: end date for the sliced time series. If integer, number of observations before the last :param weekdays_only: whether to include only weekdays in the sliced ranges :return: sliced time series **Usage** Returns a restricted ("sliced") time series based on start and end dates: :math:`Y_t = R_t |_{start < t < end}` **Examples** Slice the first and last week of a time series: >>> series = generate_series(100) >>> sliced_series = date_range(series,7,7) **See also** :func:`day` :func: `lag` """ if not (x.index.is_all_dates or all(map(lambda a: isinstance(a, date), x.index.values))): raise MqValueError('input is not a time series') if isinstance(start_date, int): start_date = x.index[start_date] if isinstance(end_date, int): end_date = x.index[- (1 + end_date)] try: start_date = start_date.date() end_date = end_date.date() except AttributeError: pass if weekdays_only: week_mask = None wd = start_date.weekday() if wd > 4: start_date += timedelta(days=7 - wd) else: week_mask = tuple([True] * 7) return x.loc[x.index.intersection(list(_date_range(start_date, end_date, week_mask=week_mask)))]
[docs]@plot_function def prepend(x: List[pd.Series]) -> pd.Series: """ Prepend data series :param x: an array of timeseries :return: concatenated timeseries **Usage** For input series [:math:`x_1`, :math:`x_2`, ... , :math:`X_n`], takes data from series :math:`X_i` until the first date for which :math:`X_{i+1}` has data, useful when a higher quality series has a shorter history than a lower quality series. **Examples** Prepend two series: >>> x = generate_series(100) >>> y = generate_series(100) >>> prepend([x, y]) **See also** :func:`union` """ res = pd.Series(dtype='float64') for i in range(len(x)): this = x[i] if i == len(x) - 1: return res.append(this) end = x[i + 1].index[0] res = res.append(this.loc[this.index < end]) return res
[docs]@plot_function def union(x: List[pd.Series]) -> pd.Series: """ Fill in missing dates or times of one series with another :param x: an array of timeseries :return: combined series **Usage** Starting from :math:`i=1`, takes points from series :math:`x_i`. Where points are missing from :math:`x_i`, returns points from :math:`x_{i+1}`. **Examples** Union of two series: >>> x = generate_series(100) >>> y = generate_series(100) >>> union([x, y]) **See also** :func:`prepend` """ res = pd.Series(dtype='float64') for series in x: res = res.combine_first(series) return res

================================================ FILE: docs/_build/html/_modules/gs_quant/timeseries/econometrics.html ================================================ gs_quant.timeseries.econometrics — gs_quant 0.1 documentation

Source code for gs_quant.timeseries.econometrics

# Copyright 2018 Goldman Sachs.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#   http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#
#
# Chart Service will attempt to make public functions (not prefixed with _) from this module available. Such functions
# should be fully documented: docstrings should describe parameters and the return value, and provide a 1-line
# description. Type annotations should be provided for parameters.
from .statistics import *
from ..errors import *
import itertools
from dataclasses import dataclass
from typing import Union, Tuple
import numpy as np
import pandas as pd
from statsmodels.tsa.arima_model import ARIMA
from statsmodels.tools.eval_measures import mse
from gs_quant.api.gs.data import GsDataApi
from gs_quant.data import DataContext
from gs_quant.datetime.date import DayCountConvention
from gs_quant.markets.securities import Asset
from gs_quant.target.common import Currency
from gs_quant.timeseries.datetime import align

"""
Econometrics timeseries library is for standard economic and time series analytics operations, including returns,
diffs, lags, volatilities and other numerical operations which are generally finance-oriented
"""


class AnnualizationFactor(IntEnum):
    DAILY = 252
    WEEKLY = 52
    SEMI_MONTHLY = 26
    MONTHLY = 12
    QUARTERLY = 4
    ANNUALLY = 1


class SharpeAssets(Enum):
    USD = 'MAP35DA6K5B1YXGX'
    AUD = 'MAFRZWJ790MQY0EW'
    CHF = 'MAS0NN4ZX7NYXB36'
    EUR = 'MA95W0N1214395N8'
    GBP = 'MA41ZEFTWR8Q7HBM'
    JPY = 'MA8GXV3SJ0TXH1JV'
    SEK = 'MAGNZZY0GJ4TATNG'


def excess_returns_pure(price_series: pd.Series, spot_curve: pd.Series) -> pd.Series:
    curve, bench_curve = align(price_series, spot_curve, Interpolate.INTERSECT)

    e_returns = [curve.iloc[0]]
    for i in range(1, len(curve)):
        multiplier = 1 + curve.iloc[i] / curve.iloc[i - 1] - bench_curve.iloc[i] / bench_curve.iloc[i - 1]
        e_returns.append(e_returns[-1] * multiplier)
    return pd.Series(e_returns, index=curve.index)


def excess_returns(price_series: pd.Series, benchmark_or_rate: Union[Asset, Currency, float], *,
                   day_count_convention=DayCountConvention.ACTUAL_360) -> pd.Series:
    if isinstance(benchmark_or_rate, float):
        er = [price_series.iloc[0]]
        for j in range(1, len(price_series)):
            fraction = day_count_fraction(price_series.index[j - 1], price_series.index[j], day_count_convention)
            er.append(er[-1] + price_series.iloc[j] - price_series.iloc[j - 1] * (1 + benchmark_or_rate * fraction))
        return pd.Series(er, index=price_series.index)

    if isinstance(benchmark_or_rate, Currency):
        try:
            marquee_id = SharpeAssets[benchmark_or_rate.value].value
        except KeyError:
            raise MqValueError(f"unsupported currency {benchmark_or_rate}")
    else:
        marquee_id = benchmark_or_rate.get_marquee_id()

    with DataContext(price_series.index[0], price_series.index[-1]):
        q = GsDataApi.build_market_data_query([marquee_id], QueryType.SPOT)
        df = GsDataApi.get_market_data(q)
    if df.empty:
        raise MqValueError(f'could not retrieve risk-free rate {marquee_id}')

    return excess_returns_pure(price_series, df['spot'])


def _annualized_return(levels: pd.Series, rolling: int) -> pd.Series:
    starting = [0] * rolling
    starting.extend([a for a in range(1, len(levels) - rolling + 1)])
    points = list(
        map(lambda d, v, i: pow(v / levels[i], 365.25 / (d - levels.index[i]).days) - 1, levels.index[1:],
            levels.values[1:], starting[1:]))
    points.insert(0, 0)
    return pd.Series(points, index=levels.index)


def get_ratio_pure(er: pd.Series, w: Union[Window, int, str]) -> pd.Series:
    w = normalize_window(er, w or None)  # continue to support 0 as an input for window
    ann_return = _annualized_return(er, w.w)
    ann_vol = volatility(er, w.w).iloc[1:] if w.w < len(er) else volatility(er)
    result = ann_return / ann_vol * 100
    return apply_ramp(result, w)


def _get_ratio(input_series: pd.Series, benchmark_or_rate: Union[Asset, float, str], w: Union[Window, int, str], *,
               day_count_convention: DayCountConvention, curve_type: CurveType = CurveType.PRICES) -> pd.Series:
    if curve_type == CurveType.PRICES:
        er = excess_returns(input_series, benchmark_or_rate, day_count_convention=day_count_convention)
    else:
        assert curve_type == CurveType.EXCESS_RETURNS
        er = input_series

    return get_ratio_pure(er, w)


class RiskFreeRateCurrency(Enum):
    USD = "USD"
    AUD = "AUD"
    CHF = "CFH"
    EUR = "EUR"
    GBP = "GBP"
    JPY = "JPY"
    SEK = "SEK"


[docs]@plot_session_function def excess_returns_(price_series: pd.Series, currency: RiskFreeRateCurrency = RiskFreeRateCurrency.USD) -> pd.Series: """ Calculate excess returns :param price_series: price series :param currency: currency for risk-free rate, defaults to USD :return: excess returns **Usage** Given a price series P and risk-free rate R, excess returns E are defined as: :math:`E_t = E_{t-1} + P_t - P_{t-1} * (1 + R * (D_t - D_{t-1}) / 360)` The `Actual/360 <https://en.wikipedia.org/wiki/Day_count_convention#Actual/360>`_ day count convention is used. **Examples** Get excess returns from a price series. >>> er = excess_returns(generate_series(100), USD) """ return excess_returns(price_series, Currency(currency.value), day_count_convention=DayCountConvention.ACTUAL_360)
[docs]@plot_session_function def sharpe_ratio(series: pd.Series, currency: RiskFreeRateCurrency = RiskFreeRateCurrency.USD, w: Union[Window, int] = None, curve_type: CurveType = CurveType.PRICES) -> pd.Series: """ Calculate Sharpe ratio :param series: series of prices or excess returns for an asset :param currency: currency for risk-free rate, defaults to USD :param curve_type: whether input series is of prices or excess returns, defaults to prices :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. :return: Sharpe ratio **Usage** Given a price series P, risk-free rate R, and window of size w returns the rolling `Sharpe ratio <https://en.wikipedia.org/wiki/Sharpe_ratio>`_ S: :math:`S_t = \\frac{(E_t / E_{t-w+1})^{365.25 / (D_t - D_{t-w} - 1)}}{volatility(E, w)_t}` Excess returns E are defined as: :math:`E_t = E_{t-1} + P_t - P_{t-1} * (1 + R * (D_t - D_{t-1}) / 360)` where D is the date for a data point. The `Actual/360 <https://en.wikipedia.org/wiki/Day_count_convention#Actual/360>`_ day count convention is used. **Examples** Get rolling sharpe ratio of a price series (with window of 252). >>> sr = sharpe_ratio(generate_series(100), USD, 252, CurveType.PRICES) **See also** :func:`volatility` """ return _get_ratio(series, Currency(currency.value), w, day_count_convention=DayCountConvention.ACTUAL_360, curve_type=curve_type)
[docs]@plot_function def returns(series: pd.Series, obs: int = 1, type: Returns = Returns.SIMPLE) -> pd.Series: """ Calculate returns from price series :param series: time series of prices :param obs: number of observations :param type: returns type: simple, logarithmic or absolute :return: date-based time series of return **Usage** Compute returns series from price levels, based on the value of *type*: =========== ============================= Type Description =========== ============================= simple Simple arithmetic returns logarithmic Logarithmic returns absolute Absolute returns =========== ============================= *Simple* Simple geometric change in asset prices, which can be aggregated across assets :math:`Y_t = \\frac{X_t}{X_{t-obs}} - 1` where :math:`X_t` is the asset price at time :math:`t` *Logarithmic* Natural logarithm of asset price changes, which can be aggregated through time :math:`Y_t = log(X_t) - log(X_{t-obs})` where :math:`X_t` is the asset price at time :math:`t` *Absolute* Absolute change in asset prices :math:`Y_t = X_t - X_{t-obs}` where :math:`X_t` is the asset price at time :math:`t` **Examples** Generate price series and take compute returns >>> prices = generate_series(100) >>> returns = returns(prices) **See also** :func:`prices` """ if series.size < 1: return series if type == Returns.SIMPLE: ret_series = series / series.shift(obs) - 1 elif type == Returns.LOGARITHMIC: log_s = series.apply(math.log) ret_series = log_s - log_s.shift(obs) elif type == Returns.ABSOLUTE: ret_series = series - series.shift(obs) else: raise MqValueError('Unknown returns type (use simple / logarithmic / absolute)') return ret_series
[docs]@plot_function def prices(series: pd.Series, initial: int = 1, type: Returns = Returns.SIMPLE) -> pd.Series: """ Calculate price levels from returns series :param series: time series of returns :param initial: initial price level :param type: returns type: simple, logarithmic or absolute :return: date-based time series of return **Usage** Compute price levels from returns series, based on the value of *type*: =========== ============================= Type Description =========== ============================= simple Simple arithmetic returns logarithmic Logarithmic returns absolute Absolute returns =========== ============================= *Simple* Compute asset price series from simple returns: :math:`Y_t = (1 + X_{t-1}) Y_{t-1}` where :math:`X_t` is the asset price at time :math:`t` and :math:`Y_0 = initial` *Logarithmic* Compute asset price series from logarithmic returns: :math:`Y_t = e^{X_{t-1}} Y_{t-1}` where :math:`X_t` is the asset price at time :math:`t` and :math:`Y_0 = initial` *Absolute* Compute asset price series from absolute returns: :math:`Y_t = X_{t-1} + Y_{t-1}` where :math:`X_t` is the asset price at time :math:`t` and :math:`Y_0 = initial` **Examples** Generate price series and take compute returns >>> series = generate_series(100) >>> returns = prices(returns(series)) **See also** :func:`returns` :func:`product` :func:`exp` """ if series.size < 1: return series if type == Returns.SIMPLE: return product(1 + series) * initial elif type == Returns.LOGARITHMIC: return product(series.apply(math.exp)) * initial elif type == Returns.ABSOLUTE: return sum_(series) + initial else: raise MqValueError('Unknown returns type (use simple / Logarithmic / absolute)')
[docs]@plot_function def index(x: pd.Series, initial: int = 1) -> pd.Series: """ Geometric series normalization :param x: time series :param initial: initial value :return: normalized time series **Usage** Divides every value in x by the initial value of x: :math:`Y_t = initial * X_t / X_0` where :math:`X_0` is the first value in the series **Examples** Normalize series to 1: >>> series = generate_series(100) >>> returns = index(series) **See also** :func:`returns` """ i = x.first_valid_index() return pd.Series() if i is None else initial * x / x[i]
[docs]@plot_function def change(x: pd.Series) -> pd.Series: """ Arithmetic series normalization :param x: time series :return: normalized time series **Usage** Compute difference of every value from the initial value of x: :math:`Y_t = X_t - X_0` where :math:`X_0` is the first value in the series **Examples** Change in level from initial value: >>> series = generate_series(100) >>> returns = change(series) **See also** :func:`index` """ return x - x[0]
def _get_annualization_factor(x): prev_idx = x.index[0] distances = [] for idx, value in x.iloc[1:].iteritems(): d = (idx - prev_idx).days if d == 0: raise MqValueError('multiple data points on same date') distances.append(d) prev_idx = idx average_distance = numpy.average(distances) if average_distance < 2.1: factor = AnnualizationFactor.DAILY elif 6 <= average_distance < 8: factor = AnnualizationFactor.WEEKLY elif 14 <= average_distance < 17: factor = AnnualizationFactor.SEMI_MONTHLY elif 25 <= average_distance < 35: factor = AnnualizationFactor.MONTHLY elif 85 <= average_distance < 97: factor = AnnualizationFactor.QUARTERLY elif 360 <= average_distance < 386: factor = AnnualizationFactor.ANNUALLY else: raise MqValueError('Cannot infer annualization factor, average distance: ' + str(average_distance)) return factor
[docs]@plot_function def annualize(x: pd.Series) -> pd.Series: """ Annualize series based on sample observation frequency :param x: time series of prices :return: date-based time series of annualized values **Usage** Based on number of days between observations, will determine an annualization factor and then adjust values accordingly. Useful for annualizing daily or monthly returns :math:`Y_t = X_t * \sqrt{F}` Annualization factors as follows, based on period implied by observations: ========= ============================= Period Annualization Factor (F) ========= ============================= Daily :math:`252` Weekly :math:`52` Bi-Weekly :math:`26` Monthly :math:`12` Quarterly :math:`4` Annually :math:`1` ========= ============================= **Examples** Annualize daily returns series: >>> prices = generate_series(100) >>> ann = annualize(returns(prices)) **See also** :func:`returns` """ factor: int = _get_annualization_factor(x) return x * math.sqrt(factor)
[docs]@plot_function def volatility(x: pd.Series, w: Union[Window, int, str] = Window(None, 0), returns_type: Returns = Returns.SIMPLE) -> pd.Series: """ Realized volatility of price series :param x: time series of prices :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :param returns_type: returns type: simple, logarithmic or absolute :return: date-based time series of return **Usage** Calculate rolling annualized realized volatility of a price series over a given window. Annual volatility of 20% is returned as 20.0: :math:`Y_t = \sqrt{\\frac{1}{N-1} \sum_{i=t-w+1}^t (R_t - \overline{R_t})^2} * \sqrt{252} * 100` where N is the number of observations in each rolling window :math:`w`, :math:`R_t` is the return on time :math:`t` based on *returns_type* =========== ======================================================= Type Description =========== ======================================================= simple Simple geometric change in asset prices: :math:`R_t = \\frac{X_t}{X_{t-1}} - 1` where :math:`X_t` is the asset price at time :math:`t` logarithmic Natural logarithm of asset price changes: :math:`R_t = log(X_t) - log(X_{t-1})` where :math:`X_t` is the asset price at time :math:`t` absolute Absolute change in asset prices: :math:`Y_t = X_t - X_{t-obs}` where :math:`X_t` is the asset price at time :math:`t` =========== ======================================================= and :math:`\overline{R_t}` is the mean value over the same window: :math:`\overline{R_t} = \\frac{\sum_{i=t-w+1}^{t} R_t}{N}` If window is not provided, computes realized volatility over the full series **Examples** Compute rolling :math:`1` month (:math:`22` business day) annualized volatility of price series >>> series = generate_series(100) >>> vol_series = volatility(series, 22) >>> vol_series = volatility(series, Window(22, 30)) **See also** :func:`std` :func:`annualize` :func:`returns` """ w = normalize_window(x, w) if x.size < 1: return x return apply_ramp(annualize(std(returns(x, type=returns_type), Window(w.w, 0))).mul(100), w)
[docs]@plot_function def correlation(x: pd.Series, y: pd.Series, w: Union[Window, int, str] = Window(None, 0), type_: SeriesType = SeriesType.PRICES) -> pd.Series: """ Rolling correlation of two price series :param x: price series :param y: price series :param w: Window, int, or str: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :param type_: type of both input series: prices or returns :return: date-based time series of correlation **Usage** Calculate rolling `realized correlation <https://en.wikipedia.org/wiki/Correlation_and_dependence>`_, :math:`\\rho_t` of two price series over a given window: :math:`\\rho_t = \\frac{\sum_{i=t-w+1}^t (R_t - \overline{R_t})(Y_t - \overline{S_t})}{(N-1)\sigma R_t\sigma S_t}` where N is the number of observations in each rolling window, :math:`w`, and :math:`R_t` and :math:`S_t` are the simple returns for each series on time :math:`t` If prices are provided: :math:`R_t = \\frac{X_t}{X_{t-1}} - 1` and :math:`S_t = \\frac{Y_t}{Y_{t-1}} - 1` If returns are provided: :math:`R_t = X_t` and :math:`S_t = Y_t` :math:`\overline{R_t}`, :math:`\overline{S_t}` are the mean values, and :math:`\sigma R_{t}` and :math:`\sigma S_{t}` are the sample standard deviations, of series :math:`R_t` and :math:`S_t` over the same window If window is not provided, computes realized correlation over the full series **Examples** Compute rolling :math:`1` month (:math:`22` business day) correlation of price series >>> series1 = generate_series(100) >>> series2 = generate_series(100) >>> corr = correlation(series1, series2, 22) **See also** :func:`std` :func:`returns` """ w = normalize_window(x, w) if x.size < 1: return x given_prices = type_ == SeriesType.PRICES ret_1 = returns(x) if given_prices else x ret_2 = returns(y) if given_prices else y clean_ret1 = ret_1.dropna() clean_ret2 = ret_2.dropna() if isinstance(w.w, pd.DateOffset): values = [clean_ret1.loc[(clean_ret1.index > idx - w.w) & (clean_ret1.index <= idx)].corr(clean_ret2) for idx in clean_ret1.index] corr = pd.Series(values, index=clean_ret1.index) else: corr = clean_ret1.rolling(w.w, 0).corr(clean_ret2) return apply_ramp(interpolate(corr, x, Interpolate.NAN), w)
[docs]@plot_function def beta(x: pd.Series, b: pd.Series, w: Union[Window, int, str] = Window(None, 0), prices: bool = True) -> pd.Series: """ Rolling beta of price series and benchmark :param x: time series of prices :param b: time series of benchmark prices :param w: Window, int, or str: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :param prices: True if input series are prices, False if they are returns :return: date-based time series of beta **Usage** Calculate rolling `beta <https://en.wikipedia.org/wiki/Beta_(finance)>`_, :math:`\\beta_t` of a series to a benchmark over a given window: :math:`R_t = \\alpha_t + \\beta S_t + \epsilon_t` Calculated as: :math:`\\beta_t = \\frac{\sum_{i=t-w+1}^t Cov(R_t, S_t)}{Var(S_t)}` where N is the number of observations in each rolling window, :math:`w`, and :math:`R_t` and :math:`S_t` are the simple returns for each series on time :math:`t`: :math:`R_t = \\frac{X_t}{X_{t-1}} - 1` and :math:`S_t = \\frac{b_t}{b_{t-1}} - 1` If prices = False, assumes returns are provided: :math:`R_t = X_t` and :math:`S_t = b_t` :math:`Cov(R_t, S_t)` and :math:`Var(S_t)` are the mean and variance of series :math:`R_t` and :math:`S_t` over the same window If window is not provided, computes beta over the full series **Examples** Compute rolling :math:`1` month (:math:`22` business day) beta of two price series >>> series = generate_series(100) >>> benchmark = generate_series(100) >>> b = beta(series, benchmark, 22) **See also** :func:`var` :func:`cov` :func:`correlation` :func:`returns` """ w = normalize_window(x, w) ret_series = returns(x) if prices else x ret_benchmark = returns(b) if prices else b if isinstance(w.w, pd.DateOffset): result = pd.Series([ret_series.loc[(ret_series.index > idx - w.w) & (ret_series.index <= idx)].cov( ret_benchmark.loc[(ret_benchmark.index > idx - w.w) & (ret_benchmark.index <= idx)] ) / ret_benchmark.loc[(ret_benchmark.index > idx - w.w) & (ret_benchmark.index <= idx)].var() for idx in ret_series.index], index=ret_series.index) else: cov = ret_series.rolling(w.w, 0).cov(ret_benchmark.rolling(w.w, 0)) result = cov / ret_benchmark.rolling(w.w, 0).var() # do not compute initial values as they may be extreme when sample size is small result[0:3] = np.nan return apply_ramp(interpolate(result, x, Interpolate.NAN), w)
[docs]@plot_function def max_drawdown(x: pd.Series, w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Compute the maximum peak to trough drawdown over a rolling window. :param x: time series :param w: Window, int, or str: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: time series of rolling maximum drawdown **Examples** Compute the maximum peak to trough `drawdown <https://en.wikipedia.org/wiki/Drawdown_(economics)>`_ >>> series = generate_series(100) >>> max_drawdown(series) **See also** :func:`returns` """ w = normalize_window(x, w) if isinstance(w.w, pd.DateOffset): scores = pd.Series([x[idx] / x.loc[(x.index > idx - w.w) & (x.index <= idx)].max() - 1 for idx in x.index], index=x.index) result = pd.Series([scores.loc[(scores.index > idx - w.w) & (scores.index <= idx)].min() for idx in scores.index], index=scores.index) else: rolling_max = x.rolling(w.w, 0).max() result = (x / rolling_max - 1).rolling(w.w, 0).min() return apply_ramp(result, w)
@dataclass class ARIMABestParams: freq: str = '' p: int = None d: int = None q: int = None const: float = None ar_coef: List[float] = None ma_coef: List[float] = None resid: List[float] = None series: pd.Series = None class Arima: """ ARIMA is the Autoregressive Integrated Moving Average Model and is used to normalize and forecast time series data. ARIMA has 3 parameters: (p, d, q) where: :p is the number of autoregressive terms :d is the number of nonseasonal differences :q is the number of lagged forecast errors in the prediction equation An ARIMA model is selected from the Cartesian product of sets p, q, and d. The time series is split into train and test sets and an ARIMA model is fit for every combination on the training set. The model with the lowest mean-squared error (MSE) on the test set is selected as the best model. The original times series can then be transformed by the best model. **Examples** >>> series = generate_series(100) >>> ar = econometrics.Arima() >>> ar.fit(series, train_size=0.8) >>> transformed_time_series = ar.transform(series) """ def __init__(self): self.best_params = {} @staticmethod def _evaluate_arima_model(X: Union[pd.Series, pd.DataFrame], arima_order: Tuple[int, int, int], train_size: Union[float, int, None], freq: str) -> Tuple[float, dict]: train_size = int(len(X) * 0.75) if train_size is None else int(len(X) * train_size) \ if isinstance(train_size, float) else train_size train, test = X[:train_size].astype(float), X[train_size:].astype(float) model = ARIMA(train, order=arima_order, freq=freq) model_fit = model.fit(disp=False, method='css', trend='nc') # calculate test error yhat = model_fit.forecast(len(test))[0] error = mse(test, yhat) return error, model_fit def fit(self, X: Union[pd.Series, pd.DataFrame], train_size: Union[float, int, None] = None, p_vals: list = (0, 1, 2), d_vals: list = (0, 1, 2), q_vals: list = (0, 1, 2), freq: str = None) -> 'arima': """ Train a combination of ARIMA models. If pandas DataFrame, finds the best arima model parameters for each column. If pandas Series, finds the best arima model parameters for the series. :param X: time series to be operated on; required parameter :param train_size: if float, should be between 0.0 and 1.0 and represent the proportion of the dataset to include in the train split. If int, represents the absolute number of train samples. If None, the value is automatically set 0.75 :p_vals: number of autoregressive terms to search; default is [0,1,2] :d_vals: number of differences to search; default is [0,1,2] :q_vals: number of lagged forecast to search; always [0,1,2] :freq: frequency of time series, default is None :return: self """ if isinstance(X, pd.Series): X = X.to_frame() for series_id in X.columns: series = X[series_id] best_score = float('inf') best_order = None best_const = None best_ar_coef = None best_ma_coef = None best_resid = None for order in list(itertools.product(*[p_vals, d_vals, q_vals])): try: error, model_fit = self._evaluate_arima_model(series, order, train_size, freq) if error < best_score: best_score = error best_order = order best_const = model_fit.params.to_dict().get('const', 0) best_ar_coef = model_fit.arparams best_ma_coef = model_fit.maparams best_resid = model_fit.resid except Exception as e: print(' {}'.format(e)) continue p, d, q = best_order self.best_params[series_id] = ARIMABestParams(freq, p, d, q, best_const, best_ar_coef, best_ma_coef, best_resid, series) return self def _difference(self, X: pd.Series, d: int): """Helper Function to Difference Time Series n Times""" return X if d == 0 else X.diff() if d == 1 else self._difference(X.diff(), d - 1) @staticmethod def _lagged_values(X: pd.Series, p: int, ar_coef: list): """Helper Function to Calculate AutoRegressive(AR) Component""" return X if p == 0 else pd.concat([X.copy().shift(periods=i) for i in range(1, p + 1)], axis=1).dot(ar_coef) @staticmethod def _calculate_residuals(X_ar: pd.Series, X_diff: pd.Series, p: int, d: int, q: int, ma_coef: list): """Helper Function to Calculate Residuals/MA Component""" ma_coef = ma_coef[::-1] resid = X_ar.copy(deep=True) resid[:] = 0 X_ma = X_ar.copy(deep=True) X_ma[:] = np.nan for x in range(p + d, len(X_ar)): ma_component = resid[x - q: x].dot(ma_coef) prediction = X_ar[x] + ma_component residual = X_diff[x] - prediction resid[x] = residual X_ma[x] = prediction return resid, X_ma def _arima_transform_series(self, s: ARIMABestParams) -> pd.Series: """Helper Function to Transform Series""" # Difference first X_diff = self._difference(s.series, s.d) # Calculate Autoregressive Component X_diff_ar = self._lagged_values(X_diff, s.p, s.ar_coef) # Calculate Residuals and Moving Average Component _, X_diff_ar_ma = self._calculate_residuals(X_diff_ar, X_diff, s.p, s.d, s.q, s.ma_coef) return X_diff_ar_ma def transform(self, X: Union[pd.Series, pd.DataFrame]) -> pd.DataFrame: """ Transform a series based on the best ARIMA found from fit(). Does not support tranformation using MA components. :param X: time series to be operated on; required parameter :return: DataFrame """ X = X.to_frame() if isinstance(X, pd.Series) else X return pd.DataFrame({s_id: self._arima_transform_series(self.best_params[s_id]) for s_id in X.columns})

================================================ FILE: docs/_build/html/_modules/gs_quant/timeseries/helper.html ================================================ gs_quant.timeseries.helper — gs_quant 0.1 documentation

Source code for gs_quant.timeseries.helper

"""
Copyright 2019 Goldman Sachs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
"""
import inspect
import logging
from enum import Enum, IntEnum
from functools import wraps
from typing import Optional, Union, List

import pandas as pd

from gs_quant.api.gs.data import QueryType


def _create_enum(name, members):
    return Enum(name, {n.upper(): n.lower() for n in members}, module=__name__)


def _create_int_enum(name, mappings):
    return IntEnum(name, {k.upper(): v for k, v in mappings.items()})


def _to_offset(tenor: str) -> pd.DateOffset:
    import re
    matcher = re.fullmatch('(\\d+)([dwmy])', tenor)
    if not matcher:
        raise ValueError('invalid tenor ' + tenor)

    ab = matcher.group(2)
    if ab == 'd':
        name = 'days'
    elif ab == 'w':
        name = 'weeks'
    elif ab == 'm':
        name = 'months'
    else:
        assert ab == 'y'
        name = 'years'

    kwarg = {name: int(matcher.group(1))}
    return pd.DateOffset(**kwarg)


Interpolate = _create_enum('Interpolate', ['intersect', 'step', 'nan', 'zero', 'time'])
Returns = _create_enum('Returns', ['simple', 'logarithmic', 'absolute'])
SeriesType = _create_enum('SeriesType', ['prices', 'returns'])
CurveType = _create_enum('CurveType', ['prices', 'excess_returns'])


[docs]class Window: """ Create a Window with size and ramp up to use. :param w: window size :param r: ramp up value. Defaults to the window size. :return: new window object **Usage** The window size and ramp up value can either the number of observations or a string representation of the time period. **Examples** Window size is :math:`22` obversations and the ramp up value is :math:`10`: >>> Window(22, 10) Window size is one month and the ramp up size is one week: >>> Window('1m', '1w') """ def __init__(self, w: Union[int, str, None] = None, r: Union[int, str, None] = None): self.w = w self.r = w if r is None else r def as_dict(self): return { 'w': self.w, 'r': self.r } @classmethod def from_dict(cls, obj): return Window(w=obj.get('w'), r=obj.get('r'))
def _check_window(series_length: int, window: Window): if series_length > 0 and isinstance(window.w, int) and isinstance(window.r, int): if window.w <= 0: raise ValueError('Window value must be greater than zero.') if window.r > series_length or window.r < 0: raise ValueError('Ramp value must be less than the length of the series and greater than zero.') def apply_ramp(x: pd.Series, window: Window) -> pd.Series: _check_window(len(x), window) if isinstance(window.w, int) and window.w > len(x): # does not restrict window size when it is a DataOffset return pd.Series([]) if isinstance(window.r, pd.DateOffset): return x.loc[x.index[0] + window.r:] else: return x[window.r:] def normalize_window(x: Union[pd.Series, pd.DataFrame], window: Union[Window, int, str, None], default_window: int = None) -> Window: if default_window is None: default_window = len(x) if isinstance(window, int): window = Window(window, window) elif isinstance(window, str): window = Window(_to_offset(window), _to_offset(window)) else: if window is None: window = Window(default_window, 0) else: if isinstance(window.w, str): window = Window(_to_offset(window.w), window.r) if isinstance(window.r, str): window = Window(window.w, _to_offset(window.r)) if window.w is None: window = Window(default_window, window.r) _check_window(default_window, window) return window def plot_function(fn): # Indicates that fn should be exported to plottool as a pure function. fn.plot_function = True return fn def plot_session_function(fn): fn.plot_function = True fn.requires_session = True return fn def plot_measure(asset_class: Optional[tuple] = None, asset_type: Optional[tuple] = None, dependencies: Optional[List[QueryType]] = []): # Indicates that fn should be exported to plottool as a member function / pseudo-measure. # Set category to None for no restrictions, else provide a tuple of allowed values. def decorator(fn): assert asset_class is None or isinstance(asset_class, tuple) assert asset_type is None or isinstance(asset_type, tuple) fn.plot_measure = True fn.asset_class = asset_class fn.asset_type = asset_type fn.dependencies = dependencies return fn return decorator def plot_method(fn): # Indicates that fn should be exported to plottool as a method. fn.plot_method = True # Allows fn to accept and ignore real_time argument even if it is not defined in the signature @wraps(fn) def ignore_extra_argument(*args, **kwargs): for arg in ('real_time', 'interval', 'time_filter'): if arg not in inspect.signature(fn).parameters: kwargs.pop(arg, None) return fn(*args, **kwargs) return ignore_extra_argument def log_return(logger: logging.Logger, message): def outer(fn): @wraps(fn) def inner(*args, **kwargs): response = fn(*args, **kwargs) logger.debug('%s: %s', message, response) return response return inner return outer

================================================ FILE: docs/_build/html/_modules/gs_quant/timeseries/statistics.html ================================================ gs_quant.timeseries.statistics — gs_quant 0.1 documentation

Source code for gs_quant.timeseries.statistics

# Copyright 2018 Goldman Sachs.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#  http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#
#
# Marquee Plot Service will attempt to make public functions (not prefixed with _) from this module available.
# Such functions should be fully documented: docstrings should describe parameters and the return value, and provide
# a 1-line description. Type annotations should be provided for parameters.

import datetime
import numpy
import scipy.stats.mstats as stats
from scipy.stats import percentileofscore
from statsmodels.regression.rolling import RollingOLS
from .algebra import *
import statsmodels.api as sm
from ..models.epidemiology import SIR, SEIR, EpidemicModel
from ..data import DataContext


"""
Stats library is for basic arithmetic and statistical operations on timeseries.
These include basic algebraic operations, probability and distribution analysis.
Generally not finance-specific routines.
"""


def _concat_series(series: List[pd.Series]):
    curves = []
    constants = {}
    k = 0
    for s in series:
        if s.min() != s.max():
            curves.append(s)
        else:
            constants[f'temp{k}'] = s.min()
            k += 1
    return pd.concat(curves, axis=1).assign(**constants)


[docs]@plot_function def min_(x: Union[pd.Series, List[pd.Series]], w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Minimum value of series over given window :param x: series: a timeseries or an array of timeseries :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: timeseries of minimum value **Usage** Returns the minimum value of the series over each window. If :math:`x` is a series: :math:`R_t = min(X_{t-w+1}:X_t)` where :math:`w` is the size of the rolling window. If :math:`x` is an array of series: :math:`R_t = min(X_{1, t-w+1}:X_{n, t})` where :math:`w` is the size of the rolling window, and :math:`n` is the number of series. If window is not provided, returns the minimum value over the full series. If the window size is greater than the available data, will return minimum of available values. **Examples** Minimum value of price series over the last :math:`22` observations: >>> prices = generate_series(100) >>> min_(prices, 22) **See also** :func:`max_` """ if isinstance(x, list): x = _concat_series(x).min(axis=1) w = normalize_window(x, w) assert x.index.is_monotonic_increasing, "series index is monotonic increasing" if isinstance(w.w, pd.DateOffset): values = [x.loc[(x.index > idx - w.w) & (x.index <= idx)].min() for idx in x.index] return apply_ramp(pd.Series(values, index=x.index, dtype=np.dtype(float)), w) else: return apply_ramp(x.rolling(w.w, 0).min(), w)
[docs]@plot_function def max_(x: Union[pd.Series, List[pd.Series]], w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Maximum value of series over given window :param x: series: a timeseries or an array of timeseries :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: timeseries of maximum value **Usage** Returns the maximum value of the series over each window. If :math:`x` is a series: :math:`R_t = max(X_{t-w+1}:X_t)` where :math:`w` is the size of the rolling window. If :math:`x` is an array of series: :math:`R_t = max(X_{1, t-w+1}:X_{n, t})` where :math:`w` is the size of the rolling window, and :math:`n` is the number of series. If window is not provided, returns the maximum value over the full series. If the window size is greater than the available data, will return maximum of available values. **Examples** Maximum value of price series over the last :math:`22` observations: >>> prices = generate_series(100) >>> max_(prices, 22) **See also** :func:`min_` """ if isinstance(x, list): x = _concat_series(x).max(axis=1) w = normalize_window(x, w) assert x.index.is_monotonic_increasing, "series index is monotonic increasing" if isinstance(w.w, pd.DateOffset): values = [x.loc[(x.index > idx - w.w) & (x.index <= idx)].max() for idx in x.index] return apply_ramp(pd.Series(values, index=x.index, dtype=np.dtype(float)), w) else: return apply_ramp(x.rolling(w.w, 0).max(), w)
[docs]@plot_function def range_(x: pd.Series, w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Range of series over given window :param x: series: timeseries :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: timeseries of range **Usage** Returns the range of the series (max - min) over rolling window: :math:`R_t = max(X_{t-w+1}:X_t) - min(X_{t-w+1}:X_t)` where :math:`w` is the size of the rolling window. If window is not provided, returns the range over the full series. If the window size is greater than the available data, will return range of all available values. **Examples** Range of price series over the last :math:`22` observations: >>> prices = generate_series(100) >>> range_(prices, 22) **See also** :func:`min_` :func:`max_` """ w = normalize_window(x, w) assert x.index.is_monotonic_increasing, "series index is monotonic increasing" max = max_(x, Window(w.w, 0)) min = min_(x, Window(w.w, 0)) return apply_ramp(max - min, w)
[docs]@plot_function def mean(x: Union[pd.Series, List[pd.Series]], w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Arithmetic mean of series over given window :param x: series: a timeseries or an array of timeseries :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: timeseries of mean value **Usage** Calculates `arithmetic mean <https://en.wikipedia.org/wiki/Arithmetic_mean>`_ of the series over a rolling window If a timeseries is provided: :math:`R_t = \\frac{\sum_{i=t-w+1}^{t} X_i}{N}` where :math:`N` is the number of observations in each rolling window, :math:`w`. If an array of timeseries is provided: :math:`R_t = \\frac{\sum_{i=t-w+1}^{t} {\sum_{j=1}^{n}} X_{ij}}{N}` where :math:`n` is the number of series, and :math:`N` is the number of observations in each rolling window, :math:`w`. If window is not provided, computes rolling mean over the full series. If the window size is greater than the available data, will return mean of available values. **Examples** Generate price series and compute mean over :math:`22` observations >>> prices = generate_series(100) >>> mean(prices, 22) **See also** :func:`median` :func:`mode` """ if isinstance(x, list): x = pd.concat(x, axis=1) w = normalize_window(x, w) assert x.index.is_monotonic_increasing, "series index is monotonic increasing" if isinstance(w.w, pd.DateOffset): values = [np.nanmean(x.loc[(x.index > idx - w.w) & (x.index <= idx)]) for idx in x.index] else: values = [np.nanmean(x.iloc[max(idx - w.w + 1, 0): idx + 1]) for idx in range(0, len(x))] return apply_ramp(pd.Series(values, index=x.index, dtype=np.dtype(float)), w)
[docs]@plot_function def median(x: pd.Series, w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Median value of series over given window :param x: series: timeseries :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: timeseries of median value **Usage** Computes the `median <https://en.wikipedia.org/wiki/Median>`_ value over a given window. For each window, this function will return the middle value when all elements in the window are sorted. If the number of observations in the window is even, will return the average of the middle two values. If the window size is greater than the available data, will return median of available values: :math:`d = \\frac{w-1}{2}` :math:`R_t = \\frac{X_{\lfloor t-d \\rfloor} + X_{\lceil t-d \\rceil}}{2}` where :math:`w` is the size of the rolling window. If window is not provided, computes median over the full series **Examples** Generate price series and compute median over :math:`22` observations >>> prices = generate_series(100) >>> median(prices, 22) **See also** :func:`mean` :func:`mode` """ w = normalize_window(x, w) assert x.index.is_monotonic_increasing, "series index is monotonic increasing" if isinstance(w.w, pd.DateOffset): values = [x.loc[(x.index > idx - w.w) & (x.index <= idx)].median() for idx in x.index] return apply_ramp(pd.Series(values, index=x.index, dtype=np.dtype(float)), w) else: return apply_ramp(x.rolling(w.w, 0).median(), w)
[docs]@plot_function def mode(x: pd.Series, w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Most common value in series over given window :param x: series: timeseries :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: timeseries of mode value **Usage** Computes the `mode <https://en.wikipedia.org/wiki/Mode_(statistics)>`_ over a given window. For each window, this function will return the most common value of all elements in the window. If there are multiple values with the same frequency of occurrence, will return the smallest value. If window is not provided, computes mode over the full series. **Examples** Generate price series and compute mode over :math:`22` observations >>> prices = generate_series(100) >>> mode(prices, 22) **See also** :func:`mean` :func:`median` """ w = normalize_window(x, w) assert x.index.is_monotonic_increasing, "series index is monotonic increasing" if isinstance(w.w, pd.DateOffset): values = [stats.mode(x.loc[(x.index > idx - w.w) & (x.index <= idx)]).mode[0] for idx in x.index] return apply_ramp(pd.Series(values, index=x.index, dtype=np.dtype(float)), w) else: return apply_ramp(x.rolling(w.w, 0).apply(lambda y: stats.mode(y).mode, raw=True), w)
[docs]@plot_function def sum_(x: Union[pd.Series, List[pd.Series]], w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Rolling sum of series over given window :param x: series: a timeseries or an array of timeseries :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: timeseries of rolling sum **Usage** Calculate the sum of observations over a given rolling window. If :math:`x` is a series: :math:`R_t = \sum_{i=t-w+1}^{t} X_i` where :math:`w` is the size of the rolling window. If :math:`x` is an array of series: :math:`R_t = \sum_{i=t-w+1}^{t} \sum_{j=1}^{n} X_{ij}` where :math:`w` is the size of the rolling window and :math:`n` is the number of series If window is not provided, computes sum over the full series. If the window size is greater than the available data, will return sum of available values. **Examples** Generate price series and compute rolling sum over :math:`22` observations >>> prices = generate_series(100) >>> sum_(prices, 22) **See also** :func:`product` """ if isinstance(x, list): x = pd.concat(x, axis=1).sum(axis=1) w = normalize_window(x, w) assert x.index.is_monotonic_increasing if isinstance(w.w, pd.DateOffset): values = [x.loc[(x.index > idx - w.w) & (x.index <= idx)].sum() for idx in x.index] return apply_ramp(pd.Series(values, index=x.index, dtype=np.dtype(float)), w) else: return apply_ramp(x.rolling(w.w, 0).sum(), w)
[docs]@plot_function def product(x: pd.Series, w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Rolling product of series over given window :param x: series: timeseries :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: timeseries of rolling product **Usage** Calculate the product of observations over a given rolling window. For each time, :math:`t`, returns the value of all observations from :math:`t-w+1` to :math:`t` multiplied together: :math:`R_t = \prod_{i=t-w+1}^{t} X_i` where :math:`w` is the size of the rolling window. If window is not provided, computes product over the full series **Examples** Generate price series and compute rolling sum over :math:`22` observations >>> prices = generate_series(100) >>> product(1+returns(prices)) **See also** :func:`sum_` """ w = normalize_window(x, w) assert x.index.is_monotonic_increasing if isinstance(w.w, pd.DateOffset): values = [x.loc[(x.index > idx - w.w) & (x.index <= idx)].agg(pd.Series.prod) for idx in x.index] return apply_ramp(pd.Series(values, index=x.index, dtype=np.dtype(float)), w) else: return apply_ramp(x.rolling(w.w, 0).agg(pd.Series.prod), w)
[docs]@plot_function def std(x: pd.Series, w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Rolling standard deviation of series over given window :param x: series: timeseries :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: timeseries of standard deviation **Usage** Provides `unbiased estimator <https://en.wikipedia.org/wiki/Unbiased_estimation_of_standard_deviation>`_ of sample `standard deviation <https://en.wikipedia.org/wiki/Standard_deviation>`_ over a rolling window: :math:`R_t = \sqrt{\\frac{1}{N-1} \sum_{i=t-w+1}^t (X_i - \overline{X_t})^2}` where :math:`N` is the number of observations in each rolling window, :math:`w`, and :math:`\overline{X_t}` is the mean value over the same window: :math:`\overline{X_t} = \\frac{\sum_{i=t-w+1}^{t} X_i}{N}` If window is not provided, computes standard deviation over the full series **Examples** Generate price series and compute standard deviation of returns over :math:`22` observations >>> prices = generate_series(100) >>> std(returns(prices), 22) **See also** :func:`sum` :func:`mean` :func:`var` """ w = normalize_window(x, w) assert x.index.is_monotonic_increasing, "series index is monotonic increasing" if isinstance(w.w, pd.DateOffset): values = [x.loc[(x.index > idx - w.w) & (x.index <= idx)].std() for idx in x.index] return apply_ramp(pd.Series(values, index=x.index, dtype=np.dtype(float)), w) else: return apply_ramp(x.rolling(w.w, 0).std(), w)
[docs]@plot_function def exponential_std(x: pd.Series, beta: float = 0.75) -> pd.Series: """ Exponentially weighted standard deviation :param x: time series :param beta: how much to weigh the previous price in the time series, thus controlling how much importance we place on the (more distant) past. Must be between 0 (inclusive) and 1 (exclusive) :return: time series of standard deviation of the input series **Usage** Provides an unbiased estimator of `exponentially weighted standard deviation <https://en.wikipedia.org/wiki/Moving_average#Exponentially_weighted_moving_variance_and_standard_deviation>`_ of a series [:math:`X_0`, :math:`X_1`, :math:`X_2`, ...]: :math:`S_t = \\sqrt{[EWMA(X_t^2) - EWMA(X_t)^2] * DF_t}` where :math:`EWMA(X_t)` is the `exponential moving average <https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average>`_ at :math:`t` (see function :func:`exponential_moving_average`), :math:`DF_t` is the debiasing factor (see `Weighted sample variance <https://en.wikipedia.org/wiki/Weighted_arithmetic_mean#Weighted_sample_variance>`_ for further details): :math:`DF_t = \\frac{(\sum_{i=0}^t w_i)^2} {(\sum_{i=0}^t w_i)^2 - \sum_{i=0}^t w_i^2}` where :math:`w_i` is the weight assigned to :math:`i` th observation: :math:`w_i = (1-\\beta)\\beta^i` for i<t; :math:`\\beta^i` for i=t **Examples** Generate price series and compute exponentially weighted standard deviation of returns >>> prices = generate_series(100) >>> exponential_std(returns(prices), 0.9) **See also** :func:`std` :func:`var` :func:`exponential_moving_average` """ return x.ewm(alpha=1 - beta, adjust=False).std()
[docs]@plot_function def var(x: pd.Series, w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Rolling variance of series over given window :param x: series: timeseries :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: timeseries of variance **Usage** Provides `unbiased estimator <https://en.wikipedia.org/wiki/Unbiased_estimation_of_standard_deviation>`_ of sample `variance <https://en.wikipedia.org/wiki/Variance>`_ over a rolling window: :math:`R_t = \\frac{1}{N-1} \sum_{i=t-w+1}^t (X_i - \overline{X_t})^2` where :math:`N` is the number of observations in each rolling window, :math:`w`, and :math:`\overline{X_t}` is the mean value over the same window: :math:`\overline{X_t} = \\frac{\sum_{i=t-w+1}^{t} X_i}{N}` If window is not provided, computes variance over the full series **Examples** Generate price series and compute variance of returns over :math:`22` observations >>> prices = generate_series(100) >>> var(returns(prices), 22) **See also** :func:`var` :func:`mean` :func:`std` """ w = normalize_window(x, w) assert x.index.is_monotonic_increasing, "series index is monotonic increasing" if isinstance(w.w, pd.DateOffset): values = [x.loc[(x.index > idx - w.w) & (x.index <= idx)].var() for idx in x.index] return apply_ramp(pd.Series(values, index=x.index, dtype=np.dtype(float)), w) else: return apply_ramp(x.rolling(w.w, 0).var(), w)
[docs]@plot_function def cov(x: pd.Series, y: pd.Series, w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Rolling co-variance of series over given window :param x: series: timeseries :param y: series: timeseries :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: timeseries of covariance **Usage** Provides `unbiased estimator <https://en.wikipedia.org/wiki/Unbiased_estimation_of_standard_deviation>`_ of sample `co-variance <https://en.wikipedia.org/wiki/Covariance>`_ over a rolling window: :math:`R_t = \\frac{1}{N-1} \sum_{i=t-w+1}^t (X_i - \overline{X_t}) (Y_i - \overline{Y_t})` where :math:`N` is the number of observations in each rolling window, :math:`w`, and :math:`\overline{X_t}` and :math:`\overline{Y_t}` represent the sample mean of series :math:`X_t` and :math:`Y_t` over the same window: :math:`\overline{X_t} = \\frac{\sum_{i=t-w+1}^{t} X_i}{N}` and :math:`\overline{Y_t} = \\frac{\sum_{i=t-w+1}^{t} Y_i}{N}` If window is not provided, computes variance over the full series **Examples** Generate price series and compute variance of returns over :math:`22` observations >>> prices_x = generate_series(100) >>> prices_y = generate_series(100) >>> cov(returns(prices_x) returns(prices_y), 22) **See also** :func:`sum` :func:`mean` :func:`var` """ w = normalize_window(x, w) assert x.index.is_monotonic_increasing, "series index is monotonic increasing" if isinstance(w.w, pd.DateOffset): values = [x.loc[(x.index > idx - w.w) & (x.index <= idx)].cov(y) for idx in x.index] return apply_ramp(pd.Series(values, index=x.index, dtype=np.dtype(float)), w) else: return apply_ramp(x.rolling(w.w, 0).cov(y), w)
def _zscore(x): if x.size == 1: return 0 return stats.zscore(x, ddof=1)[-1]
[docs]@plot_function def zscores(x: pd.Series, w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Rolling z-scores over a given window :param x: time series of prices :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: timeseries of z-scores **Usage** Calculate `standard score <https://en.wikipedia.org/wiki/Standard_score>`_ of each value in series over given window. Standard deviation and sample mean are computed over the specified rolling window, then element is normalized to provide a rolling z-score: :math:`R_t = \\frac { X_t - \mu }{ \sigma }` Where :math:`\mu` and :math:`\sigma` are sample mean and standard deviation over the given window If window is not provided, computes z-score relative to mean and standard deviation over the full series **Examples** Generate price series and compute z-score of returns over :math:`22` observations >>> prices = generate_series(100) >>> zscores(returns(prices), 22) **See also** :func:`mean` :func:`std` """ if x.size < 1: return x if isinstance(w, int): w = normalize_window(x, w) elif isinstance(w, str): if not (isinstance(x.index, pd.DatetimeIndex) or isinstance(x.index[0], datetime.date)): raise MqValueError("When string is passed window index must be a DatetimeIndex or of type datetime.date") w = normalize_window(x, w) if not w.w: if x.size == 1: return pd.Series([0.0], index=x.index, dtype=np.dtype(float)) clean_series = x.dropna() zscore_series = pd.Series(stats.zscore(clean_series, ddof=1), clean_series.index, dtype=np.dtype(float)) return interpolate(zscore_series, x, Interpolate.NAN) if not isinstance(w.w, int): w = normalize_window(x, w) values = [_zscore(x.loc[(x.index > idx - w.w) & (x.index <= idx)]) for idx in x.index] return apply_ramp(pd.Series(values, index=x.index, dtype=np.dtype(float)), w) else: return apply_ramp(x.rolling(w.w, 0).apply(_zscore, raw=False), w)
[docs]@plot_function def winsorize(x: pd.Series, limit: float = 2.5, w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Limit extreme values in series :param x: time series of prices :param limit: max z-score of values :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: timeseries of winsorized values **Usage** Cap and floor values in the series which have a z-score greater or less than provided value. This function will restrict the distribution of values. Calculates the sample standard deviation and adjusts values which fall outside the specified range to be equal to the upper or lower limits Lower and upper limits are defined as: :math:`upper = \mu + \sigma \\times limit` :math:`lower = \mu - \sigma \\times limit` Where :math:`\mu` and :math:`\sigma` are sample mean and standard deviation. The series is restricted by: :math:`R_t = max( min( X_t, upper), lower )` See `winsorising <https://en.wikipedia.org/wiki/Winsorizing>`_ for additional information **Examples** Generate price series and winsorize z-score of returns over :math:`22` observations >>> prices = generate_series(100) >>> winsorize(zscore(returns(prices), 22)) **See also** :func:`zscore` :func:`mean` :func:`std` """ w = normalize_window(x, w) if x.size < 1: return x assert w.w, "window is not 0" mu = x.mean() sigma = x.std() high = mu + sigma * limit low = mu - sigma * limit ret = ceil(x, high) ret = floor(ret, low) return apply_ramp(ret, w)
[docs]@plot_function def generate_series(length: int) -> pd.Series: """ Generate sample timeseries :param length: number of observations :return: date-based time series of randomly generated prices **Usage** Create timeseries from returns generated from a normally distributed random variables (IDD). Length determines the number of observations to be generated. Assume random variables :math:`R` which follow a normal distribution with mean :math:`0` and standard deviation of :math:`1` :math:`R \sim N(0, 1)` The timeseries is generated from these random numbers through: :math:`X_t = (1 + R)X_{t-1}` **Examples** Generate price series with 100 observations starting from today's date: >>> prices = generate_series(100) **See also** :func:`numpy.random.normal()` """ levels = [100] dates = [datetime.date.today()] for i in range(length - 1): levels.append(levels[i] * 1 + numpy.random.normal()) dates.append(datetime.date.fromordinal(dates[i].toordinal() + 1)) return pd.Series(data=levels, index=dates, dtype=np.dtype(float))
[docs]@plot_function def percentiles(x: pd.Series, y: pd.Series = None, w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Rolling percentiles over given window :param x: value series :param y: distribution series :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: timeseries of percentiles **Usage** Calculate `percentile rank <https://en.wikipedia.org/wiki/Percentile_rank>`_ of :math:`y` in the sample distribution of :math:`x` over a rolling window of length :math:`w`: :math:`R_t = \\frac{\sum_{i=t-N+1}^{t}{[X_i<{Y_t}]}+0.5\sum_{i=t-N+1}^{t}{[X_i={Y_t}]}}{N}\\times100\%` Where :math:`N` is the number of observations in a rolling window. If :math:`y` is not provided, calculates percentiles of :math:`x` over its historical values. If window length :math:`w` is not provided, uses an ever-growing history of values. If :math:`w` is greater than the available data size, returns empty. **Examples** Compute percentile ranks of a series in the sample distribution of a second series over :math:`22` observations >>> a = generate_series(100) >>> b = generate_series(100) >>> percentiles(a, b, 22) **See also** :func:`zscores` """ w = normalize_window(x, w) if x.empty: return x if y is None: y = x.copy() if isinstance(w.r, int) and w.r > len(y): raise ValueError('Ramp value must be less than the length of the series y.') if isinstance(w.w, int) and w.w > len(x): return pd.Series() res = pd.Series(dtype=np.dtype(float)) for idx, val in y.iteritems(): sample = x.loc[(x.index > idx - w.w) & (x.index <= idx)] if isinstance(w.w, pd.DateOffset) else x[:idx][-w.w:] res.loc[idx] = percentileofscore(sample, val, kind='mean') if isinstance(w.r, pd.DateOffset): return res.loc[res.index[0] + w.r:] else: return res[w.r:]
[docs]@plot_function def percentile(x: pd.Series, n: float, w: Union[Window, int, str] = None) -> Union[pd.Series, float]: """ Returns the nth percentile of a series. :param x: series :param n: percentile :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. :return: nth percentile **Usage** Calculates the `nth percentile rank <https://en.wikipedia.org/wiki/Percentile_rank>`_ of :math:`x`. Rolling nth percentile is returned if a window is specified, else a scalar for nth percentile over the entire series. **Example** Compute the 90th percentile of a series. >>> a = generate_series(100) >>> percentile(a, 90) """ if not 0 <= n <= 100: raise MqValueError('percentile must be in range [0, 100]') x = x.dropna() if x.size < 1: return x if w is None: return numpy.percentile(x.values, n) n /= 100 w = normalize_window(x, w) if isinstance(w.w, pd.DateOffset): try: values = [x.loc[(x.index > idx - w.w) & (x.index <= idx)].quantile(n) for idx in x.index] except TypeError: raise MqTypeError(f'cannot use relative dates with index {x.index}') res = pd.Series(values, index=x.index, dtype=np.dtype(float)) else: res = x.rolling(w.w, 0).quantile(n) return apply_ramp(res, w)
[docs]class LinearRegression: """ Fit an Ordinary least squares (OLS) linear regression model. :param X: observations of the explanatory variable(s) :param y: observations of the dependant variable :param fit_intercept: whether to calculate intercept in the model **Usage** Fit `OLS Model <https://en.wikipedia.org/wiki/Ordinary_least_squares>`_ based on observations of the explanatory variables(s) X and the dependant variable y. If X and y are not aligned, only use the intersection of dates/times **Examples** R Squared of an OLS model: >>> x = generate_series(100) >>> y = generate_series(100) >>> r = LinearRegression(x, y) >>> r.r_squared() """ def __init__(self, X: Union[pd.Series, List[pd.Series]], y: pd.Series, fit_intercept: bool = True): df = pd.concat(X, axis=1) if isinstance(X, list) else X.to_frame() df = sm.add_constant(df) if fit_intercept else df df.columns = range(len(df.columns)) if fit_intercept else range(1, len(df.columns) + 1) df = df[~df.isin([np.nan, np.inf, -np.inf]).any(1)] # filter out nan and inf y = y[~y.isin([np.nan, np.inf, -np.inf])] df_aligned, y_aligned = df.align(y, 'inner', axis=0) # align series self._index_scope = range(0, len(df.columns)) if fit_intercept else range(1, len(df.columns) + 1) self._res = sm.OLS(y_aligned, df_aligned).fit() self._fit_intercept = fit_intercept @plot_method def coefficient(self, i: int) -> float: """ Estimated coefficient :param i: coefficient of which predictor to get. If intercept is used, start from 0, else start from 1 :return: estimated coefficient of the i-th predictor """ return self._res.params[i] @plot_method def r_squared(self) -> float: """ Coefficient of determination (R Squared) :return: R Squared """ return self._res.rsquared @plot_method def fitted_values(self) -> pd.Series: """ Fitted values :return: fitted values """ return self._res.fittedvalues @plot_method def predict(self, X_predict: Union[pd.Series, List[pd.Series]]) -> pd.Series: """ Use the model for prediction :param X_predict: the values for which to predict :return: predicted values """ df = pd.concat(X_predict, axis=1) if isinstance(X_predict, list) else X_predict.to_frame() return self._res.predict(sm.add_constant(df) if self._fit_intercept else df) @plot_method def standard_deviation_of_errors(self) -> float: """ Standard deviation of the error term :return: standard deviation of the error term """ return np.sqrt(self._res.mse_resid)
[docs]class RollingLinearRegression: """ Fit a rolling ordinary least squares (OLS) linear regression model. :param X: observations of the explanatory variable(s) :param y: observations of the dependant variable :param w: number of observations in each rolling window. Must be larger than the number of observations or explanatory variables :param fit_intercept: whether to calculate intercept in the model **Usage** Fit `OLS Model <https://en.wikipedia.org/wiki/Ordinary_least_squares>`_ based on observations of the explanatory variables(s) X and the dependant variable y across a rolling window with fixed number of observations. The parameters of each rolling window are stored at the end of each window. If X and y are not aligned, only use the intersection of dates/times **Examples** R Squared of a rolling OLS model: >>> x = generate_series(100) >>> y = generate_series(100) >>> r = RollingLinearRegression(x, y, 5) >>> r.r_squared() """ def __init__(self, X: Union[pd.Series, List[pd.Series]], y: pd.Series, w: int, fit_intercept: bool = True): df = pd.concat(X, axis=1) if isinstance(X, list) else X.to_frame() df = sm.add_constant(df) if fit_intercept else df df.columns = range(len(df.columns)) if fit_intercept else range(1, len(df.columns) + 1) if w <= len(df.columns): raise MqValueError('Window length must be larger than the number of explanatory variables') df = df[~df.isin([np.nan, np.inf, -np.inf]).any(1)] # filter out nan and inf y = y[~y.isin([np.nan, np.inf, -np.inf])] df_aligned, y_aligned = df.align(y, 'inner', axis=0) # align series self._X = df_aligned.copy() self._res = RollingOLS(y_aligned, df_aligned, w).fit() @plot_method def coefficient(self, i: int) -> pd.Series: """ Estimated coefficients :param i: coefficients of which predictor to get. If intercept is used, start from 0, else start from 1 :return: estimated coefficients of the i-th predictor """ return self._res.params[i] @plot_method def r_squared(self) -> pd.Series: """ Coefficients of determination (R Squared) of rolling regressions :return: R Squared """ return self._res.rsquared @plot_method def fitted_values(self) -> pd.Series: """ Fitted values at the end of each rolling window :return: fitted values """ comp = self._X.mul(self._res.params.values) return comp.sum(axis=1, min_count=len(comp.columns)) @plot_method def standard_deviation_of_errors(self) -> pd.Series: """ Standard deviations of the error terms :return: standard deviations of the error terms """ return np.sqrt(self._res.mse_resid)
[docs]class SIRModel: """SIR Compartmental model for transmission of infectious disease :param beta: transmission rate of the infection :param gamma: recovery rate of the infection :param s: number of susceptible individuals in population :param i: number of infectious individuals in population :param r: number of recovered individuals in population :param n: total population size :param end_date: end date for the evolution of the model :param fit: whether to fit the model to the data :param fit_period: on how many days back to fit the model **Usage** Fit `SIR Model <https://en.wikipedia.org/wiki/Compartmental_models_in_epidemiology#The_SIR_model>`_ based on the population in each compartment over a given time period. The SIR models the movement of individuals between three compartments: susceptible (S), infected (I), and resistant (R). The model calibrates parameters : =========== ======================================================= Parameter Description =========== ======================================================= S0 initial susceptible individuals I0 initial infected individuals R0 initial recovered individuals beta Transmission rate from susceptible to infected gamma Immunity rate from infected to resistant =========== ======================================================= The parameters beta and gamma model how fast people move from being susceptible to infected (beta), and subsequently from infected to resistant (gamma). This model can be used to forecast the populations of each compartment once calibrated """ def __init__(self, beta: float = None, gamma: float = None, s: Union[pd.Series, float] = None, i: Union[pd.Series, float] = None, r: Union[pd.Series, float] = None, n: Union[pd.Series, float] = None, fit: bool = True, fit_period: int = None): n = n.dropna()[0] if isinstance(n, pd.Series) else n n = 100 if n is None else n fit = False if s is None and i is None and r is None else fit s = n if s is None else s i = 1 if i is None else i r = 0 if r is None else r data_start = [ts.index.min().date() for ts in [s, i, r] if isinstance(ts, pd.Series)] data_start.append(DataContext.current.start_date) start_date = max(data_start) data_end = [ts.index.max().date() for ts in [s, i, r] if isinstance(ts, pd.Series)] data_end.append(DataContext.current.end_date) end_date = max(data_end) self.s = s if isinstance(s, pd.Series) else [s] self.i = i if isinstance(i, pd.Series) else [i] self.r = r if isinstance(r, pd.Series) else [r] self.n = n self.beta_init = beta self.gamma_init = gamma self.fit = fit self.fit_period = fit_period self.beta_fixed = not (self.fit or (self.beta_init is None)) self.gamma_fixed = not (self.fit or (self.gamma_init is None)) data = np.array([self.s, self.i, self.r]).T beta_init = self.beta_init if self.beta_init is not None else 0.9 gamma_init = self.gamma_init if self.gamma_init is not None else 0.01 parameters, initial_conditions = SIR.get_parameters(self.s[0], self.i[0], self.r[0], n, beta=beta_init, gamma=gamma_init, beta_fixed=self.beta_fixed, gamma_fixed=self.gamma_fixed, S0_fixed=True, I0_fixed=True, R0_fixed=True) self.parameters = parameters self._model = EpidemicModel(SIR, parameters=parameters, data=data, initial_conditions=initial_conditions, fit_period=self.fit_period) if self.fit: self._model.fit(verbose=False) t = np.arange((end_date - start_date).days + 1) predict = self._model.solve(t, (self.s0(), self.i0(), self.r0()), (self.beta(), self.gamma(), n)) predict_dates = pd.date_range(start_date, end_date) self._model.s_predict = pd.Series(predict[:, 0], predict_dates) self._model.i_predict = pd.Series(predict[:, 1], predict_dates) self._model.r_predict = pd.Series(predict[:, 2], predict_dates) @plot_method def s0(self) -> float: """ Model calibration for initial susceptible individuals :return: initial susceptible individuals """ if self.fit: return self._model.fitted_parameters['S0'] return self.parameters['S0'].value @plot_method def i0(self) -> float: """ Model calibration for initial infectious individuals :return: initial infectious individuals """ if self.fit: return self._model.fitted_parameters['I0'] return self.parameters['I0'].value @plot_method def r0(self) -> float: """ Model calibration for initial recovered individuals :return: initial recovered individuals """ if self.fit: return self._model.fitted_parameters['R0'] return self.parameters['R0'].value @plot_method def beta(self) -> float: """ Model calibration for transmission rate (susceptible to infected) :return: beta """ if self.fit: return self._model.fitted_parameters['beta'] return self.parameters['beta'].value @plot_method def gamma(self) -> float: """ Model calibration for immunity (infected to resistant) :return: beta """ if self.fit: return self._model.fitted_parameters['gamma'] return self.parameters['gamma'].value @plot_method def predict_s(self) -> pd.Series: """ Model calibration for susceptible individuals through time :return: susceptible predict """ return self._model.s_predict @plot_method def predict_i(self) -> pd.Series: """ Model calibration for infected individuals through time :return: infected predict """ return self._model.i_predict @plot_method def predict_r(self) -> pd.Series: """ Model calibration for recovered individuals through time :return: infected predict """ return self._model.r_predict
[docs]class SEIRModel(SIRModel): """SEIR Compartmental model for transmission of infectious disease :param beta: transmission rate of the infection :param gamma: recovery rate of the infection :param sigma: immunity rate from exposed to infected :param s: number of susceptible individuals in population :param e: number of exposed individuals in population :param i: number of infectious individuals in population :param r: number of recovered individuals in population :param n: total population size :param end_date: end date for the evolution of the model :param fit: whether to fit the model to the data :param fit_period: on how many days back to fit the model **Usage** Fit `SEIR Model <https://en.wikipedia.org/wiki/Compartmental_models_in_epidemiology#The_SEIR_model>`_ based on the population in each compartment over a given time period. The SEIR models the movement of individuals between four compartments: susceptible (S), exposed (E), infected (I), and resistant (R). The model calibrates parameters : =========== ======================================================= Parameter Description =========== ======================================================= S0 initial susceptible individuals E0 initial exposed individuals I0 initial infected individuals R0 initial recovered individuals beta Transmission rate from susceptible to exposed gamma Immunity rate from infected to resistant sigma Immunity rate from exposed to infected =========== ======================================================= The parameters beta, gamma, and sigma, model how fast people move from being susceptible to exposed (beta), from exposed to infected (sigma), and subsequently from infected to resistant (gamma). This model can be used to predict the populations of each compartment once calibrated. """ def __init__(self, beta: float = None, gamma: float = None, sigma: float = None, s: Union[pd.Series, float] = None, e: Union[pd.Series, float] = None, i: Union[pd.Series, float] = None, r: Union[pd.Series, float] = None, n: Union[pd.Series, float] = None, fit: bool = True, fit_period: int = None): n = n.dropna()[0] if isinstance(n, pd.Series) else n n = 100 if n is None else n fit = False if all(state is None for state in (s, e, i, r)) else fit s = n if s is None else s e = 1 if e is None else e i = 1 if i is None else i r = 0 if r is None else r data_start = [ts.index.min().date() for ts in [s, i, r] if isinstance(ts, pd.Series)] data_start.append(DataContext.current.start_date) start_date = max(data_start) data_end = [ts.index.max().date() for ts in [s, i, r] if isinstance(ts, pd.Series)] data_end.append(DataContext.current.end_date) end_date = max(data_end) self.s = s if isinstance(s, pd.Series) else [s] self.e = e if isinstance(e, pd.Series) else [e] self.i = i if isinstance(i, pd.Series) else [i] self.r = r if isinstance(r, pd.Series) else [r] self.n = n self.beta_init = beta self.gamma_init = gamma self.sigma_init = sigma self.fit = fit self.fit_period = fit_period self.beta_fixed = not (self.fit or (self.beta is None)) self.gamma_fixed = not (self.fit or (self.gamma is None)) self.sigma_fixed = not (self.fit or (self.sigma is None)) data = np.array([self.s, self.e, self.i, self.r]).T beta_init = self.beta_init if self.beta_init is not None else 0.9 gamma_init = self.gamma_init if self.gamma_init is not None else 0.01 sigma_init = self.sigma_init if self.sigma_init is not None else 0.2 parameters, initial_conditions = SEIR.get_parameters(self.s[0], self.e[0], self.i[0], self.r[0], n, beta=beta_init, gamma=gamma_init, sigma=sigma_init, beta_fixed=self.beta_fixed, gamma_fixed=self.gamma_fixed, sigma_fixed=self.sigma_fixed, S0_fixed=True, I0_fixed=True, R0_fixed=True, E0_fixed=True, S0_max=5e6, I0_max=5e6, E0_max=10e6, R0_max=10e6) self.parameters = parameters self._model = EpidemicModel(SEIR, parameters=parameters, data=data, initial_conditions=initial_conditions, fit_period=self.fit_period) if self.fit: self._model.fit(verbose=False) t = np.arange((end_date - start_date).days + 1) predict = self._model.solve(t, (self.s0(), self.e0(), self.i0(), self.r0()), (self.beta(), self.gamma(), self.sigma(), n)) predict_dates = pd.date_range(start_date, end_date) self._model.s_predict = pd.Series(predict[:, 0], predict_dates) self._model.e_predict = pd.Series(predict[:, 1], predict_dates) self._model.i_predict = pd.Series(predict[:, 2], predict_dates) self._model.r_predict = pd.Series(predict[:, 3], predict_dates) @plot_method def e0(self) -> float: """ Model calibration for initial exposed individuals :return: initial exposed individuals """ if self.fit: return self._model.fitted_parameters['E0'] return self.parameters['E0'].value @plot_method def beta(self) -> float: """ Model calibration for transmission rate (susceptible to exposed) :return: beta """ if self.fit: return self._model.fitted_parameters['beta'] return self.parameters['beta'].value @plot_method def gamma(self) -> float: """ Model calibration for immunity (infected to resistant) :return: gamma """ if self.fit: return self._model.fitted_parameters['gamma'] return self.parameters['gamma'].value @plot_method def sigma(self) -> float: """ Model calibration for infection rate (exposed to infected) :return: sigma """ if self.fit: return self._model.fitted_parameters['sigma'] return self.parameters['sigma'].value @plot_method def predict_e(self) -> pd.Series: """ Model calibration for exposed individuals through time :return: exposed predict """ return self._model.e_predict

================================================ FILE: docs/_build/html/_modules/gs_quant/timeseries/technicals.html ================================================ gs_quant.timeseries.technicals — gs_quant 0.1 documentation

Source code for gs_quant.timeseries.technicals

# Copyright 2018 Goldman Sachs.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#  http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#
#
# Marquee Plot Service will attempt to make public functions (not prefixed with _) from this module available.
# Such functions should be fully documented: docstrings should describe parameters and the return value, and provide
# a 1-line description. Type annotations should be provided for parameters.
from gs_quant.timeseries import diff, annualize, returns
from .statistics import *

"""
Technicals library is for technical analysis functions on timeseries, including moving averages,
volatility indicators and and other numerical operations which are finance-oriented for analyzing
statistical properties of trading activity, such as price movement and volume changes
"""


[docs]@plot_function def moving_average(x: pd.Series, w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Moving average over specified window :param x: time series of prices :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: date-based time series of return **Usage** Simple arithmetic moving average over the specified window (number of observations). Shorter windows will be more reactive to changes in the asset price, but more volatile. Larger windows will be smoother but less reactive to near term changes in asset prices. :math:`R_t = \\frac{\sum_{i=t-w+1}^{t} X_t}{N}` where N is the number of observations in each rolling window, :math:`w`. If window is not provided, computes rolling mean over the full series Equivalent to ``mean`` **Examples** Generate price series with 100 observations starting from today's date: >>> prices = generate_series(100) >>> moving_average(prices, 22) **See also** :func:`mean` """ w = normalize_window(x, w) return apply_ramp(mean(x, Window(w.w, 0)), w)
[docs]@plot_function def bollinger_bands(x: pd.Series, w: Union[Window, int, str] = Window(None, 0), k: float = 2) -> pd.DataFrame: """ Bollinger bands with given window and width :param x: time series of prices :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :param k: band width in standard deviations (default: 2) :return: date-based time series of return **Usage** Standard deviation bands around the moving average of asset price level. Bollinger bands can be used to determine a range around the price level which responds to local volatility changes. Returns two series, upper, :math:`u_t` and lower, :math:`l_t` :math:`u_t = \\bar{X_t} + k\sigma_t` :math:`l_t = \\bar{X_t} - k\sigma_t` where :math:`\\bar{X_t}` is the moving average over specified window, and :math:`\\sigma_t` is the rolling standard deviation over the specified window See `Bollinger Bands <https://en.wikipedia.org/wiki/Bollinger_Bands>`_ for more information **Examples** Compute bollinger bands around :math:`20` day moving average at :math:`2` standard deviations: >>> prices = generate_series(100) >>> bollinger_bands(prices, 20, 2) **See also** :func:`moving_average` :func:`std` """ w = normalize_window(x, w) avg = moving_average(x, w) sigma_t = std(x, w) upper = avg + k * sigma_t lower = avg - k * sigma_t return pd.concat([lower, upper], axis=1)
[docs]@plot_function def smoothed_moving_average(x: pd.Series, w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Smoothed moving average over specified window :param x: time series of prices :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: date-based time series of return **Usage** A modified moving average (MMA), running moving average (RMA), or smoothed moving average (SMMA) is defined as: :math:`P_{MM,today} = \\frac{(N-1)P_{MM,yesterday} + P_today}{N}` where N is the number of observations in each rolling window, :math:`w`. If window is not provided, computes rolling mean over the full series See `Modified moving average <https://en.wikipedia.org/wiki/Moving_average#Modified_moving_average>`_ for more information **Examples** Generate price series with 100 observations starting from today's date: >>> prices = generate_series(100) >>> smoothed_moving_average(prices, 22) **See also** :func:`mean` :func:'moving_average' """ w = normalize_window(x, w) window_size = w.w ramp = w.r means = apply_ramp(mean(x, Window(window_size, 0)), w) if means.size < 1: return pd.Series() initial_moving_average = means[0] if (isinstance(ramp, int) and ramp > 0) or isinstance(ramp, pd.DateOffset): x = apply_ramp(x, w) smoothed_moving_averages = x.copy() smoothed_moving_averages *= 0 smoothed_moving_averages[0] = initial_moving_average for i in range(1, len(x)): if isinstance(window_size, int): window_num_elem = window_size else: window_num_elem = len(x[(x.index > (x.index[i] - window_size)) & (x.index <= x.index[i])]) smoothed_moving_averages[i] = ((window_num_elem - 1) * smoothed_moving_averages[i - 1] + x[i]) / window_num_elem return smoothed_moving_averages
[docs]@plot_function def relative_strength_index(x: pd.Series, w: Union[Window, int, str] = 14) -> pd.DataFrame: """ Relative Strength Index :param x: time series of prices :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: date-based time series of RSI **Usage** The RSI computes momentum as the ratio of higher closes to lower closes: stocks which have had more or stronger positive changes have a higher RSI than stocks which have had more or stronger negative changes. See `RSI <https://en.wikipedia.org/wiki/Relative_strength_index>`_ for more information **Examples** Compute relative strength index over a :math:`14` day window: >>> prices = generate_series(100) >>> relative_strength_index(prices, 14) **See also** :func:`moving_average` :func:`std` :func:`smoothed_moving_average` """ w = normalize_window(x, w) one_period_change = diff(x, 1)[1:] gains = one_period_change.copy() losses = one_period_change.copy() gains[gains < 0] = 0 losses[losses > 0] = 0 losses[losses < 0] *= -1 moving_avg_gains = smoothed_moving_average(gains, w) moving_avg_losses = smoothed_moving_average(losses, w) rsi_len = len(moving_avg_gains) rsi = moving_avg_gains.copy() rsi *= 0 for index in range(0, rsi_len): if moving_avg_losses[index] == 0: rsi[index] = 100 else: relative_strength = moving_avg_gains[index] / moving_avg_losses[index] rsi[index] = 100 - (100 / (1 + relative_strength)) return rsi
[docs]@plot_function def exponential_moving_average(x: pd.Series, beta: float = 0.75) -> pd.Series: """ Exponentially weighted moving average :param x: time series of prices :param beta: how much to weigh the previous observations in the time series, thus controlling how much importance we place on the (more distant) past. Must be between 0 (inclusive) and 1 (exclusive) :return: date-based time series of return **Usage** The exponential(ly weighted) moving average (EMA) of a series [:math:`X_0`, :math:`X_1`, :math:`X_2`, ...], is defined as: :math:`Y_0 = X_0` :math:`Y_t = \\beta \cdot Y_{t-1} + (1 - \\beta) \cdot X_t` where :math:`\\beta` is the weight we place on the previous average. See `Exponential moving average <https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average>`_ for more information **Examples** Generate price series with 100 observations starting from today's date: >>> prices = generate_series(100) >>> exponential_moving_average(prices, 0.9) **See also** :func:`mean` :func:`moving_average` :func:`smoothed_moving_average` """ return x.ewm(alpha=1 - beta, adjust=False).mean()
[docs]@plot_function def exponential_volatility(x: pd.Series, beta: float = 0.75) -> pd.Series: """ Exponentially weighted volatility :param x: time series of prices :param beta: how much to weigh the previous price in the time series, thus controlling how much importance we place on the (more distant) past. Must be between 0 (inclusive) and 1 (exclusive) :return: date-based time series of exponential volatility of the input series **Usage** Calculates the exponentially weighted standard deviation of the return of the input series, and annualizes the standard deviation **Examples** Generate price series and compute exponentially weighted standard deviation of returns >>> prices = generate_series(100) >>> exponential_volatility(prices, 0.9) The above is equivalent to >>> annualize(exponential_std(returns(prices), 0.9)) * 100 **See also** :func:`volatility` :func:`exponential_std` :func:`exponential_spread_volatility` """ return annualize(exponential_std(returns(x), beta)).mul(100)
[docs]@plot_function def exponential_spread_volatility(x: pd.Series, beta: float = 0.75) -> pd.Series: """ Exponentially weighted spread volatility :param x: time series of prices :param beta: how much to weigh the previous price in the time series, thus controlling how much importance we place on the (more distant) past. Must be between 0 (inclusive) and 1 (exclusive) :return: date-based time series of exponential spread volatility of the input series **Usage** Exponentially weights the daily differences of the input series, calculates the annualized standard deviation **Examples** Generate price series and compute exponentially weighted standard deviation of returns >>> prices = generate_series(100) >>> exponential_volatility(prices, 0.9) The above is equivalent to >>> annualize(exponential_std(diff(prices, 1), 0.9)) **See also** :func:`volatility` :func:`exponential_std` :func:`exponential_volatility` """ return annualize(exponential_std(diff(x, 1), beta))

================================================ FILE: docs/_build/html/_modules/index.html ================================================ Overview: module code — gs_quant 0.1 documentation
================================================ FILE: docs/_build/html/_sources/classes/gs_quant.base.Priceable.rst.txt ================================================ gs\_quant.base.Priceable ======================== .. currentmodule:: gs_quant.base .. autoclass:: Priceable .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~Priceable.__init__ ~Priceable.as_dict ~Priceable.calc ~Priceable.clone ~Priceable.default_instance ~Priceable.dollar_price ~Priceable.from_dict ~Priceable.from_instance ~Priceable.price ~Priceable.prop_item_type ~Priceable.prop_type ~Priceable.properties ~Priceable.resolve ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.data.DataContext.rst.txt ================================================ gs\_quant.data.DataContext ========================== .. currentmodule:: gs_quant.data .. autoclass:: DataContext .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~DataContext.__init__ ~DataContext.default_value .. rubric:: Attributes .. autosummary:: ~DataContext.end_date ~DataContext.end_time ~DataContext.is_entered ~DataContext.start_date ~DataContext.start_time ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.data.Dataset.rst.txt ================================================ gs\_quant.data.Dataset ====================== .. currentmodule:: gs_quant.data .. autoclass:: Dataset .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~Dataset.__init__ ~Dataset.get_coverage ~Dataset.get_data ~Dataset.get_data_last ~Dataset.get_data_series .. rubric:: Attributes .. autosummary:: ~Dataset.id ~Dataset.name ~Dataset.provider ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.data.Fields.rst.txt ================================================ gs\_quant.data.Fields ===================== .. currentmodule:: gs_quant.data .. autoclass:: Fields .. automethod:: __init__ .. rubric:: Attributes .. autosummary:: ~Fields.ADJUSTED_ASK_PRICE ~Fields.ADJUSTED_BID_PRICE ~Fields.ADJUSTED_HIGH_PRICE ~Fields.ADJUSTED_LOW_PRICE ~Fields.ADJUSTED_TRADE_PRICE ~Fields.ADJUSTED_VOLUME ~Fields.ASK_PRICE ~Fields.ASSET_ID ~Fields.BID_PRICE ~Fields.CLOSE_PRICE ~Fields.EXPIRATION_DATE ~Fields.HIGH_PRICE ~Fields.IMPLIED_VOLATILITY ~Fields.LOW_PRICE ~Fields.MID_PRICE ~Fields.NAME ~Fields.OPEN_PRICE ~Fields.RELATIVE_STRIKE ~Fields.RIC ~Fields.SPOT_PRICE ~Fields.STRIKE_REFERENCE ~Fields.TENOR ~Fields.TRADE_PRICE ~Fields.UPDATE_TIME ~Fields.VAR_SWAP ~Fields.VOLUME ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.instrument.CommodOTCSwap.rst.txt ================================================ CommodOTCSwap ============= For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: CommodOTCSwap .. rubric:: Properties .. autoattribute:: end .. autoattribute:: instrument_quantity .. autoattribute:: legs .. autoattribute:: number_of_periods .. autoattribute:: provider .. autoattribute:: quantity .. autoattribute:: quantity_period .. autoattribute:: quantity_unit .. autoattribute:: resolution_key .. autoattribute:: settlement .. autoattribute:: start .. autoattribute:: strategy .. autoattribute:: unresolved ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.instrument.CommodSwap.rst.txt ================================================ CommodSwap ========== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: CommodSwap .. rubric:: Properties .. autoattribute:: calculation_period_frequency .. autoattribute:: calculation_periods .. autoattribute:: commodity .. autoattribute:: commodity_reference_price .. autoattribute:: currency .. autoattribute:: notional_amount .. autoattribute:: start ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.instrument.EqCliquet.rst.txt ================================================ EqCliquet ========= For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: EqCliquet .. rubric:: Properties .. autoattribute:: currency .. autoattribute:: expiration_date .. autoattribute:: first_valuation_date .. autoattribute:: global_cap .. autoattribute:: global_floor .. autoattribute:: instrument_quantity .. autoattribute:: last_valuation_date .. autoattribute:: notional_amount .. autoattribute:: payment_frequency .. autoattribute:: provider .. autoattribute:: resolution_key .. autoattribute:: return_style .. autoattribute:: return_type .. autoattribute:: strike_price .. autoattribute:: underlier .. autoattribute:: underlier_type .. autoattribute:: unresolved .. autoattribute:: valuation_period ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.instrument.EqForward.rst.txt ================================================ EqForward ========= For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: EqForward .. rubric:: Properties .. autoattribute:: expiration_date .. autoattribute:: forward_price .. autoattribute:: instrument_quantity .. autoattribute:: number_of_shares .. autoattribute:: provider .. autoattribute:: resolution_key .. autoattribute:: underlier .. autoattribute:: underlier_type .. autoattribute:: unresolved ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.instrument.EqOption.rst.txt ================================================ EqOption ======== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: EqOption .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: exchange .. autoattribute:: expiration_date .. autoattribute:: instrument_quantity .. autoattribute:: method_of_settlement .. autoattribute:: multiplier .. autoattribute:: number_of_options .. autoattribute:: option_style .. autoattribute:: option_type .. autoattribute:: premium .. autoattribute:: premium_currency .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: resolution_key .. autoattribute:: settlement_currency .. autoattribute:: settlement_date .. autoattribute:: strike_price .. autoattribute:: trade_as .. autoattribute:: underlier .. autoattribute:: underlier_type .. autoattribute:: unresolved .. autoattribute:: valuation_time ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.instrument.EqSynthetic.rst.txt ================================================ EqSynthetic =========== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: EqSynthetic .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: currency .. autoattribute:: effective_date .. autoattribute:: expiry .. autoattribute:: instrument_quantity .. autoattribute:: num_of_underlyers .. autoattribute:: provider .. autoattribute:: resolution_key .. autoattribute:: swap_type .. autoattribute:: underlier .. autoattribute:: underlier_type .. autoattribute:: unresolved ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.instrument.EqVarianceSwap.rst.txt ================================================ EqVarianceSwap ============== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: EqVarianceSwap .. rubric:: Properties .. autoattribute:: expiration_date .. autoattribute:: instrument_quantity .. autoattribute:: premium .. autoattribute:: provider .. autoattribute:: resolution_key .. autoattribute:: settlement_date .. autoattribute:: strike_price .. autoattribute:: underlier .. autoattribute:: underlier_type .. autoattribute:: unresolved .. autoattribute:: variance_cap ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.instrument.FXBinary.rst.txt ================================================ FXBinary ======== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXBinary .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: expiration_date .. autoattribute:: expiration_time .. autoattribute:: fixing_source .. autoattribute:: instrument_quantity .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: option_type .. autoattribute:: pair .. autoattribute:: premium .. autoattribute:: premium_currency .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: resolution_key .. autoattribute:: settlement_date .. autoattribute:: strike_price .. autoattribute:: unresolved ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.instrument.FXForward.rst.txt ================================================ FXForward ========= For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXForward .. rubric:: Properties .. autoattribute:: forward_rate .. autoattribute:: instrument_quantity .. autoattribute:: notional_amount .. autoattribute:: pair .. autoattribute:: provider .. autoattribute:: resolution_key .. autoattribute:: settlement_date .. autoattribute:: unresolved ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.instrument.FXMultiCrossBinary.rst.txt ================================================ FXMultiCrossBinary ================== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXMultiCrossBinary .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: expiration_date .. autoattribute:: expiration_time .. autoattribute:: instrument_quantity .. autoattribute:: legs .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: premium .. autoattribute:: premium_currency .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: resolution_key .. autoattribute:: settlement_date .. autoattribute:: unresolved ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.instrument.FXMultiCrossBinaryLeg.rst.txt ================================================ FXMultiCrossBinaryLeg ===================== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXMultiCrossBinaryLeg .. rubric:: Properties .. autoattribute:: fixing_source .. autoattribute:: instrument_quantity .. autoattribute:: option_type .. autoattribute:: pair .. autoattribute:: provider .. autoattribute:: resolution_key .. autoattribute:: strike_price .. autoattribute:: unresolved ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.instrument.FXOption.rst.txt ================================================ FXOption ======== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXOption .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: expiration_date .. autoattribute:: expiration_time .. autoattribute:: instrument_quantity .. autoattribute:: method_of_settlement .. autoattribute:: notional_amount .. autoattribute:: notional_amount_other_currency .. autoattribute:: notional_currency .. autoattribute:: option_type .. autoattribute:: pair .. autoattribute:: premium .. autoattribute:: premium_currency .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: resolution_key .. autoattribute:: settlement_currency .. autoattribute:: settlement_date .. autoattribute:: settlement_rate_option .. autoattribute:: strike_price .. autoattribute:: unresolved ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.instrument.FXVolatilitySwap.rst.txt ================================================ FXVolatilitySwap ================ For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXVolatilitySwap .. rubric:: Properties .. autoattribute:: annualization_factor .. autoattribute:: buy_sell .. autoattribute:: calculate_mean_return .. autoattribute:: first_fixing_date .. autoattribute:: fixing_frequency .. autoattribute:: fixing_source .. autoattribute:: instrument_quantity .. autoattribute:: last_fixing_date .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: pair .. autoattribute:: provider .. autoattribute:: resolution_key .. autoattribute:: settlement_date .. autoattribute:: strike_vol .. autoattribute:: unresolved ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.instrument.Forward.rst.txt ================================================ Forward ======= For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: Forward .. rubric:: Properties .. autoattribute:: currency .. autoattribute:: expiration_date .. autoattribute:: instrument_quantity .. autoattribute:: notional_amount .. autoattribute:: provider .. autoattribute:: resolution_key .. autoattribute:: unresolved ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.instrument.IRBasisSwap.rst.txt ================================================ IRBasisSwap =========== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: IRBasisSwap .. rubric:: Properties .. autoattribute:: clearing_house .. autoattribute:: effective_date .. autoattribute:: fee .. autoattribute:: fee_currency .. autoattribute:: fee_payment_date .. autoattribute:: instrument_quantity .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: payer_business_day_convention .. autoattribute:: payer_day_count_fraction .. autoattribute:: payer_designated_maturity .. autoattribute:: payer_frequency .. autoattribute:: payer_rate_option .. autoattribute:: payer_spread .. autoattribute:: principal_exchange .. autoattribute:: provider .. autoattribute:: receiver_business_day_convention .. autoattribute:: receiver_day_count_fraction .. autoattribute:: receiver_designated_maturity .. autoattribute:: receiver_frequency .. autoattribute:: receiver_rate_option .. autoattribute:: receiver_spread .. autoattribute:: resolution_key .. autoattribute:: termination_date .. autoattribute:: unresolved ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.instrument.IRCMSOption.rst.txt ================================================ IRCMSOption =========== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: IRCMSOption .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: cap_floor .. autoattribute:: effective_date .. autoattribute:: fee .. autoattribute:: fee_currency .. autoattribute:: fee_payment_date .. autoattribute:: index .. autoattribute:: instrument_quantity .. autoattribute:: multiplier .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: premium .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: resolution_key .. autoattribute:: strike .. autoattribute:: termination_date .. autoattribute:: unresolved ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.instrument.IRCMSOptionStrip.rst.txt ================================================ IRCMSOptionStrip ================ For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: IRCMSOptionStrip .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: cap_floor .. autoattribute:: effective_date .. autoattribute:: fee .. autoattribute:: fee_currency .. autoattribute:: fee_payment_date .. autoattribute:: floating_rate_business_day_convention .. autoattribute:: floating_rate_day_count_fraction .. autoattribute:: floating_rate_frequency .. autoattribute:: index .. autoattribute:: instrument_quantity .. autoattribute:: multiplier .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: premium .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: reset_delay .. autoattribute:: resolution_key .. autoattribute:: strike .. autoattribute:: termination_date .. autoattribute:: unresolved ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.instrument.IRCMSSpreadOption.rst.txt ================================================ IRCMSSpreadOption ================= For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: IRCMSSpreadOption .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: cap_floor .. autoattribute:: effective_date .. autoattribute:: fee .. autoattribute:: fee_currency .. autoattribute:: fee_payment_date .. autoattribute:: index1_tenor .. autoattribute:: index2_tenor .. autoattribute:: instrument_quantity .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: premium .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: resolution_key .. autoattribute:: strike .. autoattribute:: termination_date .. autoattribute:: unresolved ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.instrument.IRCMSSpreadOptionStrip.rst.txt ================================================ IRCMSSpreadOptionStrip ====================== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: IRCMSSpreadOptionStrip .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: cap_floor .. autoattribute:: effective_date .. autoattribute:: fee .. autoattribute:: fee_currency .. autoattribute:: fee_payment_date .. autoattribute:: floating_rate_business_day_convention .. autoattribute:: floating_rate_day_count_fraction .. autoattribute:: floating_rate_frequency .. autoattribute:: index1 .. autoattribute:: index2 .. autoattribute:: instrument_quantity .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: premium .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: reset_delay .. autoattribute:: resolution_key .. autoattribute:: strike .. autoattribute:: termination_date .. autoattribute:: unresolved ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.instrument.IRCap.rst.txt ================================================ IRCap ===== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: IRCap .. rubric:: Properties .. autoattribute:: cap_rate .. autoattribute:: effective_date .. autoattribute:: fee .. autoattribute:: fee_currency .. autoattribute:: fee_payment_date .. autoattribute:: floating_rate_business_day_convention .. autoattribute:: floating_rate_day_count_fraction .. autoattribute:: floating_rate_designated_maturity .. autoattribute:: floating_rate_frequency .. autoattribute:: floating_rate_option .. autoattribute:: instrument_quantity .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: premium .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: resolution_key .. autoattribute:: termination_date .. autoattribute:: unresolved ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.instrument.IRFloor.rst.txt ================================================ IRFloor ======= For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: IRFloor .. rubric:: Properties .. autoattribute:: effective_date .. autoattribute:: fee .. autoattribute:: fee_currency .. autoattribute:: fee_payment_date .. autoattribute:: floating_rate_business_day_convention .. autoattribute:: floating_rate_day_count_fraction .. autoattribute:: floating_rate_designated_maturity .. autoattribute:: floating_rate_frequency .. autoattribute:: floating_rate_option .. autoattribute:: floor_rate .. autoattribute:: instrument_quantity .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: premium .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: resolution_key .. autoattribute:: termination_date .. autoattribute:: unresolved ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.instrument.IRSwap.rst.txt ================================================ IRSwap ====== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: IRSwap .. rubric:: Properties .. autoattribute:: clearing_house .. autoattribute:: effective_date .. autoattribute:: fee .. autoattribute:: fee_currency .. autoattribute:: fee_payment_date .. autoattribute:: fixed_first_stub .. autoattribute:: fixed_holidays .. autoattribute:: fixed_last_stub .. autoattribute:: fixed_rate .. autoattribute:: fixed_rate_business_day_convention .. autoattribute:: fixed_rate_day_count_fraction .. autoattribute:: fixed_rate_frequency .. autoattribute:: floating_first_stub .. autoattribute:: floating_holidays .. autoattribute:: floating_last_stub .. autoattribute:: floating_rate_business_day_convention .. autoattribute:: floating_rate_day_count_fraction .. autoattribute:: floating_rate_designated_maturity .. autoattribute:: floating_rate_for_the_initial_calculation_period .. autoattribute:: floating_rate_frequency .. autoattribute:: floating_rate_option .. autoattribute:: floating_rate_spread .. autoattribute:: instrument_quantity .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: pay_or_receive .. autoattribute:: principal_exchange .. autoattribute:: provider .. autoattribute:: resolution_key .. autoattribute:: roll_convention .. autoattribute:: termination_date .. autoattribute:: unresolved ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.instrument.IRSwaption.rst.txt ================================================ IRSwaption ========== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: IRSwaption .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: clearing_house .. autoattribute:: effective_date .. autoattribute:: expiration_date .. autoattribute:: fee .. autoattribute:: fee_currency .. autoattribute:: fee_payment_date .. autoattribute:: fixed_rate_business_day_convention .. autoattribute:: fixed_rate_day_count_fraction .. autoattribute:: fixed_rate_frequency .. autoattribute:: floating_rate_business_day_convention .. autoattribute:: floating_rate_day_count_fraction .. autoattribute:: floating_rate_designated_maturity .. autoattribute:: floating_rate_frequency .. autoattribute:: floating_rate_option .. autoattribute:: floating_rate_spread .. autoattribute:: instrument_quantity .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: pay_or_receive .. autoattribute:: premium .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: resolution_key .. autoattribute:: settlement .. autoattribute:: strike .. autoattribute:: termination_date .. autoattribute:: unresolved ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.instrument.IRXccySwap.rst.txt ================================================ IRXccySwap ========== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: IRXccySwap .. rubric:: Properties .. autoattribute:: effective_date .. autoattribute:: fee .. autoattribute:: fee_currency .. autoattribute:: fee_payment_date .. autoattribute:: initial_fx_rate .. autoattribute:: instrument_quantity .. autoattribute:: notional_amount .. autoattribute:: notional_reset_side .. autoattribute:: payer_business_day_convention .. autoattribute:: payer_currency .. autoattribute:: payer_day_count_fraction .. autoattribute:: payer_designated_maturity .. autoattribute:: payer_first_stub .. autoattribute:: payer_frequency .. autoattribute:: payer_holidays .. autoattribute:: payer_last_stub .. autoattribute:: payer_rate_option .. autoattribute:: payer_spread .. autoattribute:: principal_exchange .. autoattribute:: provider .. autoattribute:: receiver_business_day_convention .. autoattribute:: receiver_currency .. autoattribute:: receiver_day_count_fraction .. autoattribute:: receiver_designated_maturity .. autoattribute:: receiver_first_stub .. autoattribute:: receiver_frequency .. autoattribute:: receiver_holidays .. autoattribute:: receiver_last_stub .. autoattribute:: receiver_rate_option .. autoattribute:: receiver_spread .. autoattribute:: resolution_key .. autoattribute:: termination_date .. autoattribute:: unresolved ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.instrument.IRXccySwapFixFix.rst.txt ================================================ IRXccySwapFixFix ================ For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: IRXccySwapFixFix .. rubric:: Properties .. autoattribute:: effective_date .. autoattribute:: fee .. autoattribute:: fee_currency .. autoattribute:: fee_payment_date .. autoattribute:: instrument_quantity .. autoattribute:: notional_amount .. autoattribute:: payer_business_day_convention .. autoattribute:: payer_currency .. autoattribute:: payer_day_count_fraction .. autoattribute:: payer_frequency .. autoattribute:: payer_rate .. autoattribute:: principal_exchange .. autoattribute:: provider .. autoattribute:: receiver_business_day_convention .. autoattribute:: receiver_currency .. autoattribute:: receiver_day_count_fraction .. autoattribute:: receiver_frequency .. autoattribute:: receiver_notional_amount .. autoattribute:: receiver_rate .. autoattribute:: resolution_key .. autoattribute:: termination_date .. autoattribute:: unresolved ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.instrument.IRXccySwapFixFlt.rst.txt ================================================ IRXccySwapFixFlt ================ For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: IRXccySwapFixFlt .. rubric:: Properties .. autoattribute:: effective_date .. autoattribute:: fee .. autoattribute:: fee_currency .. autoattribute:: fee_payment_date .. autoattribute:: fixed_first_stub .. autoattribute:: fixed_holidays .. autoattribute:: fixed_last_stub .. autoattribute:: fixed_rate .. autoattribute:: fixed_rate_business_day_convention .. autoattribute:: fixed_rate_currency .. autoattribute:: fixed_rate_day_count_fraction .. autoattribute:: fixed_rate_frequency .. autoattribute:: floating_first_stub .. autoattribute:: floating_holidays .. autoattribute:: floating_last_stub .. autoattribute:: floating_rate_business_day_convention .. autoattribute:: floating_rate_currency .. autoattribute:: floating_rate_day_count_fraction .. autoattribute:: floating_rate_designated_maturity .. autoattribute:: floating_rate_for_the_initial_calculation_period .. autoattribute:: floating_rate_frequency .. autoattribute:: floating_rate_option .. autoattribute:: floating_rate_spread .. autoattribute:: instrument_quantity .. autoattribute:: notional_amount .. autoattribute:: pay_or_receive .. autoattribute:: principal_exchange .. autoattribute:: provider .. autoattribute:: resolution_key .. autoattribute:: termination_date .. autoattribute:: unresolved ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.instrument.InflationSwap.rst.txt ================================================ InflationSwap ============= For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: InflationSwap .. rubric:: Properties .. autoattribute:: base_cpi .. autoattribute:: clearing_house .. autoattribute:: effective_date .. autoattribute:: fee .. autoattribute:: fixed_rate .. autoattribute:: fixed_rate_business_day_convention .. autoattribute:: floating_rate_business_day_convention .. autoattribute:: index .. autoattribute:: instrument_quantity .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: pay_or_receive .. autoattribute:: provider .. autoattribute:: resolution_key .. autoattribute:: termination_date .. autoattribute:: unresolved ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.instrument.Security.rst.txt ================================================ Security ======== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: Security .. rubric:: Properties .. autoattribute:: bbid .. autoattribute:: bbid_equivalent .. autoattribute:: bcid .. autoattribute:: cid .. autoattribute:: cm_id .. autoattribute:: cross .. autoattribute:: cusip .. autoattribute:: delisted .. autoattribute:: dollar_cross .. autoattribute:: eid .. autoattribute:: em_id .. autoattribute:: exchange_code .. autoattribute:: gsid .. autoattribute:: gsid_equivalent .. autoattribute:: gsideid .. autoattribute:: gsn .. autoattribute:: gss .. autoattribute:: instrument_quantity .. autoattribute:: isin .. autoattribute:: jsn .. autoattribute:: lms_id .. autoattribute:: mdapi .. autoattribute:: mdapi_class .. autoattribute:: mic .. autoattribute:: mq_symbol .. autoattribute:: pl_id .. autoattribute:: plot_id .. autoattribute:: pnode_id .. autoattribute:: primary_country_ric .. autoattribute:: prime_id .. autoattribute:: provider .. autoattribute:: ps_id .. autoattribute:: rcic .. autoattribute:: resolution_key .. autoattribute:: ric .. autoattribute:: sec_name .. autoattribute:: sedol .. autoattribute:: sf_id .. autoattribute:: simon_id .. autoattribute:: tdapi .. autoattribute:: ticker .. autoattribute:: unresolved .. autoattribute:: valoren .. autoattribute:: wi_id .. autoattribute:: wpk ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.markets.HistoricalPricingContext.rst.txt ================================================ gs\_quant.markets.HistoricalPricingContext ========================================== .. currentmodule:: gs_quant.markets .. autoclass:: HistoricalPricingContext .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~HistoricalPricingContext.__init__ ~HistoricalPricingContext.calc ~HistoricalPricingContext.clone ~HistoricalPricingContext.default_value .. rubric:: Attributes .. autosummary:: ~HistoricalPricingContext.active_context ~HistoricalPricingContext.csa_term ~HistoricalPricingContext.is_async ~HistoricalPricingContext.is_batch ~HistoricalPricingContext.is_current ~HistoricalPricingContext.is_entered ~HistoricalPricingContext.market ~HistoricalPricingContext.market_data_location ~HistoricalPricingContext.pricing_date ~HistoricalPricingContext.prior_context ~HistoricalPricingContext.use_cache ~HistoricalPricingContext.visible_to_gs ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.markets.PricingContext.rst.txt ================================================ gs\_quant.markets.PricingContext ================================ .. currentmodule:: gs_quant.markets .. autoclass:: PricingContext .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~PricingContext.__init__ ~PricingContext.calc ~PricingContext.clone ~PricingContext.default_value .. rubric:: Attributes .. autosummary:: ~PricingContext.active_context ~PricingContext.csa_term ~PricingContext.is_async ~PricingContext.is_batch ~PricingContext.is_current ~PricingContext.is_entered ~PricingContext.market ~PricingContext.market_data_location ~PricingContext.pricing_date ~PricingContext.prior_context ~PricingContext.use_cache ~PricingContext.visible_to_gs ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.markets.portfolio.Portfolio.rst.txt ================================================ gs\_quant.markets.portfolio.Portfolio ===================================== .. currentmodule:: gs_quant.markets.portfolio .. autoclass:: Portfolio .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~Portfolio.__init__ ~Portfolio.append ~Portfolio.as_dict ~Portfolio.calc ~Portfolio.clone ~Portfolio.default_instance ~Portfolio.dollar_price ~Portfolio.from_asset_id ~Portfolio.from_asset_name ~Portfolio.from_book ~Portfolio.from_csv ~Portfolio.from_dict ~Portfolio.from_eti ~Portfolio.from_frame ~Portfolio.from_instance ~Portfolio.from_portfolio_id ~Portfolio.from_portfolio_name ~Portfolio.from_quote ~Portfolio.market ~Portfolio.paths ~Portfolio.pop ~Portfolio.price ~Portfolio.prop_item_type ~Portfolio.prop_type ~Portfolio.properties ~Portfolio.resolve ~Portfolio.save ~Portfolio.save_as_quote ~Portfolio.subset ~Portfolio.to_csv ~Portfolio.to_frame .. rubric:: Attributes .. autosummary:: ~Portfolio.all_instruments ~Portfolio.all_paths ~Portfolio.all_portfolios ~Portfolio.id ~Portfolio.instruments ~Portfolio.name ~Portfolio.portfolios ~Portfolio.priceables ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.markets.securities.Asset.rst.txt ================================================ gs\_quant.markets.securities.Asset ================================== .. currentmodule:: gs_quant.markets.securities .. autoclass:: Asset .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~Asset.__init__ ~Asset.entity_type ~Asset.get ~Asset.get_close_prices ~Asset.get_data_coordinate ~Asset.get_data_series ~Asset.get_entity ~Asset.get_identifier ~Asset.get_identifiers ~Asset.get_marquee_id ~Asset.get_type ~Asset.get_unique_entity_key .. rubric:: Attributes .. autosummary:: ~Asset.data_dimension ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.markets.securities.AssetClass.rst.txt ================================================ gs\_quant.markets.securities.AssetClass ======================================= .. currentmodule:: gs_quant.markets.securities .. autoclass:: AssetClass .. automethod:: __init__ .. rubric:: Attributes .. autosummary:: ~AssetClass.Cash ~AssetClass.Commod ~AssetClass.Credit ~AssetClass.Cross_Asset ~AssetClass.Cryptocurrency ~AssetClass.Econ ~AssetClass.Equity ~AssetClass.FX ~AssetClass.Fund ~AssetClass.Loan ~AssetClass.Mortgage ~AssetClass.Rates ~AssetClass.Social ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.markets.securities.AssetIdentifier.rst.txt ================================================ gs\_quant.markets.securities.AssetIdentifier ============================================ .. currentmodule:: gs_quant.markets.securities .. autoclass:: AssetIdentifier .. automethod:: __init__ .. rubric:: Attributes .. autosummary:: ~AssetIdentifier.BLOOMBERG_COMPOSITE_ID ~AssetIdentifier.BLOOMBERG_ID ~AssetIdentifier.CUSIP ~AssetIdentifier.ISIN ~AssetIdentifier.MARQUEE_ID ~AssetIdentifier.REUTERS_ID ~AssetIdentifier.SEDOL ~AssetIdentifier.TICKER ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.markets.securities.AssetType.rst.txt ================================================ gs\_quant.markets.securities.AssetType ====================================== .. currentmodule:: gs_quant.markets.securities .. autoclass:: AssetType .. automethod:: __init__ .. rubric:: Attributes .. autosummary:: ~AssetType.BASKET ~AssetType.BOND ~AssetType.CASH ~AssetType.COMMODITY ~AssetType.COMMODITY_EU_NATURAL_GAS_HUB ~AssetType.COMMODITY_NATURAL_GAS_HUB ~AssetType.COMMODITY_POWER_AGGREGATED_NODES ~AssetType.COMMODITY_POWER_NODE ~AssetType.COMMODITY_REFERENCE_PRICE ~AssetType.CROSS ~AssetType.CRYPTO_CURRENCY ~AssetType.CURRENCY ~AssetType.ETF ~AssetType.FUTURE ~AssetType.FUTURE_CONTRACT ~AssetType.FUTURE_MARKET ~AssetType.INDEX ~AssetType.OPTION ~AssetType.RATE ~AssetType.STOCK ~AssetType.SWAP ~AssetType.WEATHER_INDEX ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.markets.securities.Index.rst.txt ================================================ gs\_quant.markets.securities.Index ================================== .. currentmodule:: gs_quant.markets.securities .. autoclass:: Index .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~Index.__init__ ~Index.entity_type ~Index.get ~Index.get_close_prices ~Index.get_constituents ~Index.get_currency ~Index.get_data_coordinate ~Index.get_data_series ~Index.get_entity ~Index.get_identifier ~Index.get_identifiers ~Index.get_marquee_id ~Index.get_return_type ~Index.get_type ~Index.get_unique_entity_key .. rubric:: Attributes .. autosummary:: ~Index.data_dimension ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.markets.securities.SecurityMaster.rst.txt ================================================ gs\_quant.markets.securities.SecurityMaster =========================================== .. currentmodule:: gs_quant.markets.securities .. autoclass:: SecurityMaster .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~SecurityMaster.__init__ ~SecurityMaster.get_asset ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.markets.securities.Stock.rst.txt ================================================ gs\_quant.markets.securities.Stock ================================== .. currentmodule:: gs_quant.markets.securities .. autoclass:: Stock .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~Stock.__init__ ~Stock.entity_type ~Stock.get ~Stock.get_close_prices ~Stock.get_currency ~Stock.get_data_coordinate ~Stock.get_data_series ~Stock.get_entity ~Stock.get_identifier ~Stock.get_identifiers ~Stock.get_marquee_id ~Stock.get_type ~Stock.get_unique_entity_key .. rubric:: Attributes .. autosummary:: ~Stock.data_dimension ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.models.epidemiology.EpidemicModel.rst.txt ================================================ gs\_quant.models.epidemiology.EpidemicModel =========================================== .. currentmodule:: gs_quant.models.epidemiology .. autoclass:: EpidemicModel .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~EpidemicModel.__init__ ~EpidemicModel.fit ~EpidemicModel.residual ~EpidemicModel.solve ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.models.epidemiology.SEIR.rst.txt ================================================ gs\_quant.models.epidemiology.SEIR ================================== .. currentmodule:: gs_quant.models.epidemiology .. autoclass:: SEIR .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~SEIR.__init__ ~SEIR.calibrate ~SEIR.get_parameters ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.models.epidemiology.SIR.rst.txt ================================================ gs\_quant.models.epidemiology.SIR ================================= .. currentmodule:: gs_quant.models.epidemiology .. autoclass:: SIR .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~SIR.__init__ ~SIR.calibrate ~SIR.get_parameters ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.timeseries.datetime.Window.rst.txt ================================================ gs\_quant.timeseries.datetime.Window ==================================== .. currentmodule:: gs_quant.timeseries.datetime .. autoclass:: Window .. rubric:: Methods .. autosummary:: ~Window.as_dict ~Window.from_dict ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.timeseries.statistics.LinearRegression.rst.txt ================================================ gs\_quant.timeseries.statistics.LinearRegression ================================================ .. currentmodule:: gs_quant.timeseries.statistics .. autoclass:: LinearRegression .. rubric:: Methods .. autosummary:: ~LinearRegression.coefficient ~LinearRegression.fitted_values ~LinearRegression.predict ~LinearRegression.r_squared ~LinearRegression.standard_deviation_of_errors ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.timeseries.statistics.RollingLinearRegression.rst.txt ================================================ gs\_quant.timeseries.statistics.RollingLinearRegression ======================================================= .. currentmodule:: gs_quant.timeseries.statistics .. autoclass:: RollingLinearRegression .. rubric:: Methods .. autosummary:: ~RollingLinearRegression.coefficient ~RollingLinearRegression.fitted_values ~RollingLinearRegression.r_squared ~RollingLinearRegression.standard_deviation_of_errors ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.timeseries.statistics.SEIRModel.rst.txt ================================================ gs\_quant.timeseries.statistics.SEIRModel ========================================= .. currentmodule:: gs_quant.timeseries.statistics .. autoclass:: SEIRModel .. rubric:: Methods .. autosummary:: ~SEIRModel.beta ~SEIRModel.e0 ~SEIRModel.gamma ~SEIRModel.i0 ~SEIRModel.predict_e ~SEIRModel.predict_i ~SEIRModel.predict_r ~SEIRModel.predict_s ~SEIRModel.r0 ~SEIRModel.s0 ~SEIRModel.sigma ================================================ FILE: docs/_build/html/_sources/classes/gs_quant.timeseries.statistics.SIRModel.rst.txt ================================================ gs\_quant.timeseries.statistics.SIRModel ======================================== .. currentmodule:: gs_quant.timeseries.statistics .. autoclass:: SIRModel .. rubric:: Methods .. autosummary:: ~SIRModel.beta ~SIRModel.gamma ~SIRModel.i0 ~SIRModel.predict_i ~SIRModel.predict_r ~SIRModel.predict_s ~SIRModel.r0 ~SIRModel.s0 ================================================ FILE: docs/_build/html/_sources/data.rst.txt ================================================ Data Package ============ .. currentmodule:: gs_quant.data .. autosummary:: :toctree: classes DataContext Dataset Fields ================================================ FILE: docs/_build/html/_sources/datetime.rst.txt ================================================ Datetime Package ================ Date ---- .. currentmodule:: gs_quant.datetime.date .. autosummary:: :toctree: functions business_day_count business_day_offset date_range is_business_day prev_business_date Point ----- .. currentmodule:: gs_quant.datetime.point .. autosummary:: :toctree: functions point_sort_order ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.datetime.date.business_day_count.rst.txt ================================================ gs\_quant.datetime.date.business\_day\_count ============================================ .. currentmodule:: gs_quant.datetime.date .. autofunction:: business_day_count ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.datetime.date.business_day_offset.rst.txt ================================================ gs\_quant.datetime.date.business\_day\_offset ============================================= .. currentmodule:: gs_quant.datetime.date .. autofunction:: business_day_offset ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.datetime.date.date_range.rst.txt ================================================ gs\_quant.datetime.date.date\_range =================================== .. currentmodule:: gs_quant.datetime.date .. autofunction:: date_range ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.datetime.date.is_business_day.rst.txt ================================================ gs\_quant.datetime.date.is\_business\_day ========================================= .. currentmodule:: gs_quant.datetime.date .. autofunction:: is_business_day ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.datetime.date.prev_business_date.rst.txt ================================================ gs\_quant.datetime.date.prev\_business\_date ============================================ .. currentmodule:: gs_quant.datetime.date .. autofunction:: prev_business_date ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.datetime.point.point_sort_order.rst.txt ================================================ gs\_quant.datetime.point.point\_sort\_order =========================================== .. currentmodule:: gs_quant.datetime.point .. autofunction:: point_sort_order ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.algebra.abs_.rst.txt ================================================ gs\_quant.timeseries.algebra.abs\_ ================================== .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: abs_ ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.algebra.add.rst.txt ================================================ gs\_quant.timeseries.algebra.add ================================ .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: add ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.algebra.and_.rst.txt ================================================ gs\_quant.timeseries.algebra.and\_ ================================== .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: and_ ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.algebra.ceil.rst.txt ================================================ gs\_quant.timeseries.algebra.ceil ================================= .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: ceil ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.algebra.divide.rst.txt ================================================ gs\_quant.timeseries.algebra.divide =================================== .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: divide ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.algebra.exp.rst.txt ================================================ gs\_quant.timeseries.algebra.exp ================================ .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: exp ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.algebra.filter_.rst.txt ================================================ gs\_quant.timeseries.algebra.filter\_ ===================================== .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: filter_ ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.algebra.floor.rst.txt ================================================ gs\_quant.timeseries.algebra.floor ================================== .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: floor ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.algebra.floordiv.rst.txt ================================================ gs\_quant.timeseries.algebra.floordiv ===================================== .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: floordiv ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.algebra.if_.rst.txt ================================================ gs\_quant.timeseries.algebra.if\_ ================================= .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: if_ ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.algebra.log.rst.txt ================================================ gs\_quant.timeseries.algebra.log ================================ .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: log ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.algebra.multiply.rst.txt ================================================ gs\_quant.timeseries.algebra.multiply ===================================== .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: multiply ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.algebra.not_.rst.txt ================================================ gs\_quant.timeseries.algebra.not\_ ================================== .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: not_ ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.algebra.or_.rst.txt ================================================ gs\_quant.timeseries.algebra.or\_ ================================= .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: or_ ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.algebra.power.rst.txt ================================================ gs\_quant.timeseries.algebra.power ================================== .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: power ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.algebra.repeat.rst.txt ================================================ gs\_quant.timeseries.algebra.repeat =================================== .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: repeat ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.algebra.smooth_spikes.rst.txt ================================================ gs\_quant.timeseries.algebra.smooth\_spikes =========================================== .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: smooth_spikes ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.algebra.sqrt.rst.txt ================================================ gs\_quant.timeseries.algebra.sqrt ================================= .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: sqrt ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.algebra.subtract.rst.txt ================================================ gs\_quant.timeseries.algebra.subtract ===================================== .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: subtract ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.algebra.weighted_sum.rst.txt ================================================ gs\_quant.timeseries.algebra.weighted\_sum ========================================== .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: weighted_sum ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.analysis.count.rst.txt ================================================ gs\_quant.timeseries.analysis.count =================================== .. currentmodule:: gs_quant.timeseries.analysis .. autofunction:: count ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.analysis.diff.rst.txt ================================================ gs\_quant.timeseries.analysis.diff ================================== .. currentmodule:: gs_quant.timeseries.analysis .. autofunction:: diff ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.analysis.first.rst.txt ================================================ gs\_quant.timeseries.analysis.first =================================== .. currentmodule:: gs_quant.timeseries.analysis .. autofunction:: first ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.analysis.lag.rst.txt ================================================ gs\_quant.timeseries.analysis.lag ================================= .. currentmodule:: gs_quant.timeseries.analysis .. autofunction:: lag ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.analysis.last.rst.txt ================================================ gs\_quant.timeseries.analysis.last ================================== .. currentmodule:: gs_quant.timeseries.analysis .. autofunction:: last ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.analysis.last_value.rst.txt ================================================ gs\_quant.timeseries.analysis.last\_value ========================================= .. currentmodule:: gs_quant.timeseries.analysis .. autofunction:: last_value ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.analysis.weighted_sum.rst.txt ================================================ weighted_sum ================================== .. currentmodule:: gs_quant.timeseries.analysis .. autofunction:: weighted_sum ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.backtesting.basket.rst.txt ================================================ gs\_quant.timeseries.backtesting.basket ======================================= .. currentmodule:: gs_quant.timeseries.backtesting .. autofunction:: basket ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.datetime.align.rst.txt ================================================ gs\_quant.timeseries.datetime.align =================================== .. currentmodule:: gs_quant.timeseries.datetime .. autofunction:: align ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.datetime.date_range.rst.txt ================================================ gs\_quant.timeseries.datetime.date\_range ========================================= .. currentmodule:: gs_quant.timeseries.datetime .. autofunction:: date_range ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.datetime.day.rst.txt ================================================ gs\_quant.timeseries.datetime.day ================================= .. currentmodule:: gs_quant.timeseries.datetime .. autofunction:: day ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.datetime.interpolate.rst.txt ================================================ gs\_quant.timeseries.datetime.interpolate ========================================= .. currentmodule:: gs_quant.timeseries.datetime .. autofunction:: interpolate ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.datetime.month.rst.txt ================================================ gs\_quant.timeseries.datetime.month =================================== .. currentmodule:: gs_quant.timeseries.datetime .. autofunction:: month ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.datetime.prepend.rst.txt ================================================ gs\_quant.timeseries.datetime.prepend ===================================== .. currentmodule:: gs_quant.timeseries.datetime .. autofunction:: prepend ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.datetime.quarter.rst.txt ================================================ gs\_quant.timeseries.datetime.quarter ===================================== .. currentmodule:: gs_quant.timeseries.datetime .. autofunction:: quarter ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.datetime.union.rst.txt ================================================ gs\_quant.timeseries.datetime.union =================================== .. currentmodule:: gs_quant.timeseries.datetime .. autofunction:: union ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.datetime.value.rst.txt ================================================ gs\_quant.timeseries.datetime.value =================================== .. currentmodule:: gs_quant.timeseries.datetime .. autofunction:: value ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.datetime.weekday.rst.txt ================================================ gs\_quant.timeseries.datetime.weekday ===================================== .. currentmodule:: gs_quant.timeseries.datetime .. autofunction:: weekday ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.datetime.year.rst.txt ================================================ gs\_quant.timeseries.datetime.year ================================== .. currentmodule:: gs_quant.timeseries.datetime .. autofunction:: year ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.econometrics.annualize.rst.txt ================================================ gs\_quant.timeseries.econometrics.annualize =========================================== .. currentmodule:: gs_quant.timeseries.econometrics .. autofunction:: annualize ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.econometrics.beta.rst.txt ================================================ gs\_quant.timeseries.econometrics.beta ====================================== .. currentmodule:: gs_quant.timeseries.econometrics .. autofunction:: beta ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.econometrics.change.rst.txt ================================================ gs\_quant.timeseries.econometrics.change ======================================== .. currentmodule:: gs_quant.timeseries.econometrics .. autofunction:: change ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.econometrics.correlation.rst.txt ================================================ gs\_quant.timeseries.econometrics.correlation ============================================= .. currentmodule:: gs_quant.timeseries.econometrics .. autofunction:: correlation ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.econometrics.excess_returns_.rst.txt ================================================ gs\_quant.timeseries.econometrics.excess\_returns\_ =================================================== .. currentmodule:: gs_quant.timeseries.econometrics .. autofunction:: excess_returns_ ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.econometrics.index.rst.txt ================================================ gs\_quant.timeseries.econometrics.index ======================================= .. currentmodule:: gs_quant.timeseries.econometrics .. autofunction:: index ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.econometrics.max_drawdown.rst.txt ================================================ gs\_quant.timeseries.econometrics.max\_drawdown =============================================== .. currentmodule:: gs_quant.timeseries.econometrics .. autofunction:: max_drawdown ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.econometrics.prices.rst.txt ================================================ gs\_quant.timeseries.econometrics.prices ======================================== .. currentmodule:: gs_quant.timeseries.econometrics .. autofunction:: prices ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.econometrics.returns.rst.txt ================================================ gs\_quant.timeseries.econometrics.returns ========================================= .. currentmodule:: gs_quant.timeseries.econometrics .. autofunction:: returns ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.econometrics.sharpe_ratio.rst.txt ================================================ gs\_quant.timeseries.econometrics.sharpe\_ratio =============================================== .. currentmodule:: gs_quant.timeseries.econometrics .. autofunction:: sharpe_ratio ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.econometrics.volatility.rst.txt ================================================ gs\_quant.timeseries.econometrics.volatility ============================================ .. currentmodule:: gs_quant.timeseries.econometrics .. autofunction:: volatility ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.statistics.cov.rst.txt ================================================ gs\_quant.timeseries.statistics.cov =================================== .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: cov ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.statistics.exponential_std.rst.txt ================================================ gs\_quant.timeseries.statistics.exponential\_std ================================================ .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: exponential_std ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.statistics.generate_series.rst.txt ================================================ gs\_quant.timeseries.statistics.generate\_series ================================================ .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: generate_series ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.statistics.max_.rst.txt ================================================ gs\_quant.timeseries.statistics.max\_ ===================================== .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: max_ ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.statistics.mean.rst.txt ================================================ gs\_quant.timeseries.statistics.mean ==================================== .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: mean ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.statistics.median.rst.txt ================================================ gs\_quant.timeseries.statistics.median ====================================== .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: median ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.statistics.min_.rst.txt ================================================ gs\_quant.timeseries.statistics.min\_ ===================================== .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: min_ ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.statistics.mode.rst.txt ================================================ gs\_quant.timeseries.statistics.mode ==================================== .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: mode ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.statistics.percentile.rst.txt ================================================ gs\_quant.timeseries.statistics.percentile ========================================== .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: percentile ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.statistics.percentiles.rst.txt ================================================ gs\_quant.timeseries.statistics.percentiles =========================================== .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: percentiles ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.statistics.product.rst.txt ================================================ gs\_quant.timeseries.statistics.product ======================================= .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: product ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.statistics.range_.rst.txt ================================================ gs\_quant.timeseries.statistics.range\_ ======================================= .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: range_ ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.statistics.std.rst.txt ================================================ gs\_quant.timeseries.statistics.std =================================== .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: std ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.statistics.sum_.rst.txt ================================================ gs\_quant.timeseries.statistics.sum\_ ===================================== .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: sum_ ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.statistics.var.rst.txt ================================================ gs\_quant.timeseries.statistics.var =================================== .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: var ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.statistics.winsorize.rst.txt ================================================ gs\_quant.timeseries.statistics.winsorize ========================================= .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: winsorize ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.statistics.zscores.rst.txt ================================================ gs\_quant.timeseries.statistics.zscores ======================================= .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: zscores ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.technicals.bollinger_bands.rst.txt ================================================ gs\_quant.timeseries.technicals.bollinger\_bands ================================================ .. currentmodule:: gs_quant.timeseries.technicals .. autofunction:: bollinger_bands ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.technicals.exponential_moving_average.rst.txt ================================================ gs\_quant.timeseries.technicals.exponential\_moving\_average ============================================================ .. currentmodule:: gs_quant.timeseries.technicals .. autofunction:: exponential_moving_average ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.technicals.exponential_spread_volatility.rst.txt ================================================ gs\_quant.timeseries.technicals.exponential\_spread\_volatility =============================================================== .. currentmodule:: gs_quant.timeseries.technicals .. autofunction:: exponential_spread_volatility ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.technicals.exponential_volatility.rst.txt ================================================ gs\_quant.timeseries.technicals.exponential\_volatility ======================================================= .. currentmodule:: gs_quant.timeseries.technicals .. autofunction:: exponential_volatility ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.technicals.moving_average.rst.txt ================================================ gs\_quant.timeseries.technicals.moving\_average =============================================== .. currentmodule:: gs_quant.timeseries.technicals .. autofunction:: moving_average ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.technicals.relative_strength_index.rst.txt ================================================ gs\_quant.timeseries.technicals.relative\_strength\_index ========================================================= .. currentmodule:: gs_quant.timeseries.technicals .. autofunction:: relative_strength_index ================================================ FILE: docs/_build/html/_sources/functions/gs_quant.timeseries.technicals.smoothed_moving_average.rst.txt ================================================ gs\_quant.timeseries.technicals.smoothed\_moving\_average ========================================================= .. currentmodule:: gs_quant.timeseries.technicals .. autofunction:: smoothed_moving_average ================================================ FILE: docs/_build/html/_sources/index.rst.txt ================================================ .. gs_quant documentation master file, created by sphinx-quickstart on Sat Jan 5 20:53:00 2019. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. GS Quant API ============ .. toctree:: :maxdepth: 2 :caption: Packages data datetime instrument markets models risk timeseries :ref:`genindex` ================================================ FILE: docs/_build/html/_sources/instrument.rst.txt ================================================ Instrument Package ================== .. currentmodule:: gs_quant.base .. autosummary:: :toctree: classes Priceable Instruments ----------- .. currentmodule:: gs_quant.instrument .. autosummary:: :toctree: classes :template: instruments.rst CommodOTCSwap EqCliquet EqForward EqOption EqSynthetic EqVarianceSwap Forward FXBinary FXForward FXMultiCrossBinary FXMultiCrossBinaryLeg FXOption FXVolatilitySwap InflationSwap IRBasisSwap IRCap IRCMSOption IRCMSOptionStrip IRCMSSpreadOption IRCMSSpreadOptionStrip IRFloor IRSwap IRSwaption IRXccySwap IRXccySwapFixFix IRXccySwapFixFlt Security ================================================ FILE: docs/_build/html/_sources/market.rst.txt ================================================ Market Package ============== .. currentmodule:: gs_quant.markets .. autosummary:: :toctree: classes PricingContext HistoricalPricingContext Securities ---------- .. currentmodule:: gs_quant.markets.securities .. autosummary:: :toctree: classes Asset AssetClass AssetIdentifier AssetType Index Stock SecurityMaster ================================================ FILE: docs/_build/html/_sources/markets.rst.txt ================================================ Markets Package =============== .. currentmodule:: gs_quant.markets .. autosummary:: :toctree: classes PricingContext HistoricalPricingContext Portfolio --------- .. currentmodule:: gs_quant.markets.portfolio .. autosummary:: :toctree: classes Portfolio Securities ---------- .. currentmodule:: gs_quant.markets.securities .. autosummary:: :toctree: classes Asset AssetClass AssetIdentifier AssetType Index Stock SecurityMaster ================================================ FILE: docs/_build/html/_sources/models.rst.txt ================================================ Models Package ============== Epidemiology ------------ .. currentmodule:: gs_quant.models.epidemiology .. autosummary:: :toctree: classes SIR SEIR EpidemicModel ================================================ FILE: docs/_build/html/_sources/risk.rst.txt ================================================ Risk Package ============ Functions --------- .. currentmodule:: gs_quant.risk .. autofunction:: aggregate_risk .. autofunction:: subtract_risk .. autofunction:: sort_risk Measures -------- .. autodata:: DollarPrice .. autodata:: Price .. autodata:: ForwardPrice .. autodata:: Theta .. autodata:: BaseCPI .. autodata:: CommodDelta .. autodata:: CommodTheta .. autodata:: CommodVega .. autodata:: EqDelta .. autodata:: EqGamma .. autodata:: EqVega .. autodata:: EqSpot .. autodata:: EqAnnualImpliedVol .. autodata:: FairVarStrike .. autodata:: FairVolStrike .. autodata:: FXDelta .. autodata:: FXGamma .. autodata:: FXVega .. autodata:: FXSpot .. autodata:: InflationDelta .. autodata:: InflationDeltaParallel .. autodata:: InflationDeltaParallelLocalCcy .. autodata:: IRBasis .. autodata:: IRDelta .. autodata:: IRDeltaParallel .. autodata:: IRDeltaLocalCcy .. autodata:: IRDeltaParallelLocalCcy .. autodata:: IRXccyDelta .. autodata:: IRXccyDeltaParallel .. autodata:: IRXccyDeltaParallelLocalCurrency .. autodata:: IRGammaParallel .. autodata:: IRGammaParallelLocalCcy .. autodata:: IRVega .. autodata:: IRVegaParallel .. autodata:: IRVegaLocalCcy .. autodata:: IRVegaParallelLocalCcy .. autodata:: IRAnnualImpliedVol .. autodata:: IRAnnualATMImpliedVol .. autodata:: IRDailyImpliedVol .. autodata:: IRSpotRate .. autodata:: IRFwdRate ================================================ FILE: docs/_build/html/_sources/timeseries.rst.txt ================================================ Timeseries Package ================== Algebra ------- .. currentmodule:: gs_quant.timeseries.algebra .. autosummary:: :toctree: functions abs_ add and_ ceil divide exp filter_ floor floordiv if_ log multiply not_ or_ power repeat smooth_spikes sqrt subtract weighted_sum Analysis -------- .. currentmodule:: gs_quant.timeseries.analysis .. autosummary:: :toctree: functions diff first last last_value count lag Backtesting ----------- .. currentmodule:: gs_quant.timeseries.backtesting .. autosummary:: :toctree: functions basket Date / Time ----------- .. currentmodule:: gs_quant.timeseries.datetime .. autosummary:: :toctree: functions align interpolate value day weekday month year quarter date_range prepend union .. autosummary:: :toctree: classes :template: timeseries_class.rst Window Econometrics ------------ .. currentmodule:: gs_quant.timeseries.econometrics .. autosummary:: :toctree: functions annualize beta change correlation excess_returns_ index max_drawdown prices returns sharpe_ratio volatility Statistics ---------- .. currentmodule:: gs_quant.timeseries.statistics .. autosummary:: :toctree: functions cov exponential_std generate_series max_ mean median min_ mode percentile percentiles product range_ std sum_ var winsorize zscores .. autosummary:: :toctree: classes :template: timeseries_class.rst LinearRegression RollingLinearRegression SIRModel SEIRModel Technical Analysis ------------------ .. currentmodule:: gs_quant.timeseries.technicals .. autosummary:: :toctree: functions bollinger_bands moving_average exponential_moving_average exponential_volatility exponential_spread_volatility smoothed_moving_average relative_strength_index ================================================ FILE: docs/_build/html/_static/basic.css ================================================ /* * basic.css * ~~~~~~~~~ * * Sphinx stylesheet -- basic theme. * * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ /* -- main layout ----------------------------------------------------------- */ div.clearer { clear: both; } div.section::after { display: block; content: ''; clear: left; } /* -- relbar ---------------------------------------------------------------- */ div.related { width: 100%; font-size: 90%; } div.related h3 { display: none; } div.related ul { margin: 0; padding: 0 0 0 10px; list-style: none; } div.related li { display: inline; } div.related li.right { float: right; margin-right: 5px; } /* -- sidebar --------------------------------------------------------------- */ div.sphinxsidebarwrapper { padding: 10px 5px 0 10px; } div.sphinxsidebar { float: left; width: 230px; margin-left: -100%; font-size: 90%; word-wrap: break-word; overflow-wrap : break-word; } div.sphinxsidebar ul { list-style: none; } div.sphinxsidebar ul ul, div.sphinxsidebar ul.want-points { margin-left: 20px; list-style: square; } div.sphinxsidebar ul ul { margin-top: 0; margin-bottom: 0; } div.sphinxsidebar form { margin-top: 10px; } div.sphinxsidebar input { border: 1px solid #98dbcc; font-family: sans-serif; font-size: 1em; } div.sphinxsidebar #searchbox form.search { overflow: hidden; } div.sphinxsidebar #searchbox input[type="text"] { float: left; width: 80%; padding: 0.25em; box-sizing: border-box; } div.sphinxsidebar #searchbox input[type="submit"] { float: left; width: 20%; border-left: none; padding: 0.25em; box-sizing: border-box; } img { border: 0; max-width: 100%; } /* -- search page ----------------------------------------------------------- */ ul.search { margin: 10px 0 0 20px; padding: 0; } ul.search li { padding: 5px 0 5px 20px; background-image: url(file.png); background-repeat: no-repeat; background-position: 0 7px; } ul.search li a { font-weight: bold; } ul.search li div.context { color: #888; margin: 2px 0 0 30px; text-align: left; } ul.keywordmatches li.goodmatch a { font-weight: bold; } /* -- index page ------------------------------------------------------------ */ table.contentstable { width: 90%; margin-left: auto; margin-right: auto; } table.contentstable p.biglink { line-height: 150%; } a.biglink { font-size: 1.3em; } span.linkdescr { font-style: italic; padding-top: 5px; font-size: 90%; } /* -- general index --------------------------------------------------------- */ table.indextable { width: 100%; } table.indextable td { text-align: left; vertical-align: top; } table.indextable ul { margin-top: 0; margin-bottom: 0; list-style-type: none; } table.indextable > tbody > tr > td > ul { padding-left: 0em; } table.indextable tr.pcap { height: 10px; } table.indextable tr.cap { margin-top: 10px; background-color: #f2f2f2; } img.toggler { margin-right: 3px; margin-top: 3px; cursor: pointer; } div.modindex-jumpbox { border-top: 1px solid #ddd; border-bottom: 1px solid #ddd; margin: 1em 0 1em 0; padding: 0.4em; } div.genindex-jumpbox { border-top: 1px solid #ddd; border-bottom: 1px solid #ddd; margin: 1em 0 1em 0; padding: 0.4em; } /* -- domain module index --------------------------------------------------- */ table.modindextable td { padding: 2px; border-collapse: collapse; } /* -- general body styles --------------------------------------------------- */ div.body { min-width: 0; max-width: 100%; } div.body p, div.body dd, div.body li, div.body blockquote { -moz-hyphens: auto; -ms-hyphens: auto; -webkit-hyphens: auto; hyphens: auto; } a.headerlink { visibility: hidden; } a.brackets:before, span.brackets > a:before{ content: "["; } a.brackets:after, span.brackets > a:after { content: "]"; } h1:hover > a.headerlink, h2:hover > a.headerlink, h3:hover > a.headerlink, h4:hover > a.headerlink, h5:hover > a.headerlink, h6:hover > a.headerlink, dt:hover > a.headerlink, caption:hover > a.headerlink, p.caption:hover > a.headerlink, div.code-block-caption:hover > a.headerlink { visibility: visible; } div.body p.caption { text-align: inherit; } div.body td { text-align: left; } .first { margin-top: 0 !important; } p.rubric { margin-top: 30px; font-weight: bold; } img.align-left, .figure.align-left, object.align-left { clear: left; float: left; margin-right: 1em; } img.align-right, .figure.align-right, object.align-right { clear: right; float: right; margin-left: 1em; } img.align-center, .figure.align-center, object.align-center { display: block; margin-left: auto; margin-right: auto; } img.align-default, .figure.align-default { display: block; margin-left: auto; margin-right: auto; } .align-left { text-align: left; } .align-center { text-align: center; } .align-default { text-align: center; } .align-right { text-align: right; } /* -- sidebars -------------------------------------------------------------- */ div.sidebar { margin: 0 0 0.5em 1em; border: 1px solid #ddb; padding: 7px; background-color: #ffe; width: 40%; float: right; clear: right; overflow-x: auto; } p.sidebar-title { font-weight: bold; } div.admonition, div.topic, blockquote { clear: left; } /* -- topics ---------------------------------------------------------------- */ div.topic { border: 1px solid #ccc; padding: 7px; margin: 10px 0 10px 0; } p.topic-title { font-size: 1.1em; font-weight: bold; margin-top: 10px; } /* -- admonitions ----------------------------------------------------------- */ div.admonition { margin-top: 10px; margin-bottom: 10px; padding: 7px; } div.admonition dt { font-weight: bold; } p.admonition-title { margin: 0px 10px 5px 0px; font-weight: bold; } div.body p.centered { text-align: center; margin-top: 25px; } /* -- content of sidebars/topics/admonitions -------------------------------- */ div.sidebar > :last-child, div.topic > :last-child, div.admonition > :last-child { margin-bottom: 0; } div.sidebar::after, div.topic::after, div.admonition::after, blockquote::after { display: block; content: ''; clear: both; } /* -- tables ---------------------------------------------------------------- */ table.docutils { margin-top: 10px; margin-bottom: 10px; border: 0; border-collapse: collapse; } table.align-center { margin-left: auto; margin-right: auto; } table.align-default { margin-left: auto; margin-right: auto; } table caption span.caption-number { font-style: italic; } table caption span.caption-text { } table.docutils td, table.docutils th { padding: 1px 8px 1px 5px; border-top: 0; border-left: 0; border-right: 0; border-bottom: 1px solid #aaa; } table.footnote td, table.footnote th { border: 0 !important; } th { text-align: left; padding-right: 5px; } table.citation { border-left: solid 1px gray; margin-left: 1px; } table.citation td { border-bottom: none; } th > :first-child, td > :first-child { margin-top: 0px; } th > :last-child, td > :last-child { margin-bottom: 0px; } /* -- figures --------------------------------------------------------------- */ div.figure { margin: 0.5em; padding: 0.5em; } div.figure p.caption { padding: 0.3em; } div.figure p.caption span.caption-number { font-style: italic; } div.figure p.caption span.caption-text { } /* -- field list styles ----------------------------------------------------- */ table.field-list td, table.field-list th { border: 0 !important; } .field-list ul { margin: 0; padding-left: 1em; } .field-list p { margin: 0; } .field-name { -moz-hyphens: manual; -ms-hyphens: manual; -webkit-hyphens: manual; hyphens: manual; } /* -- hlist styles ---------------------------------------------------------- */ table.hlist { margin: 1em 0; } table.hlist td { vertical-align: top; } /* -- other body styles ----------------------------------------------------- */ ol.arabic { list-style: decimal; } ol.loweralpha { list-style: lower-alpha; } ol.upperalpha { list-style: upper-alpha; } ol.lowerroman { list-style: lower-roman; } ol.upperroman { list-style: upper-roman; } :not(li) > ol > li:first-child > :first-child, :not(li) > ul > li:first-child > :first-child { margin-top: 0px; } :not(li) > ol > li:last-child > :last-child, :not(li) > ul > li:last-child > :last-child { margin-bottom: 0px; } ol.simple ol p, ol.simple ul p, ul.simple ol p, ul.simple ul p { margin-top: 0; } ol.simple > li:not(:first-child) > p, ul.simple > li:not(:first-child) > p { margin-top: 0; } ol.simple p, ul.simple p { margin-bottom: 0; } dl.footnote > dt, dl.citation > dt { float: left; margin-right: 0.5em; } dl.footnote > dd, dl.citation > dd { margin-bottom: 0em; } dl.footnote > dd:after, dl.citation > dd:after { content: ""; clear: both; } dl.field-list { display: grid; grid-template-columns: fit-content(30%) auto; } dl.field-list > dt { font-weight: bold; word-break: break-word; padding-left: 0.5em; padding-right: 5px; } dl.field-list > dt:after { content: ":"; } dl.field-list > dd { padding-left: 0.5em; margin-top: 0em; margin-left: 0em; margin-bottom: 0em; } dl { margin-bottom: 15px; } dd > :first-child { margin-top: 0px; } dd ul, dd table { margin-bottom: 10px; } dd { margin-top: 3px; margin-bottom: 10px; margin-left: 30px; } dl > dd:last-child, dl > dd:last-child > :last-child { margin-bottom: 0; } dt:target, span.highlighted { background-color: #fbe54e; } rect.highlighted { fill: #fbe54e; } dl.glossary dt { font-weight: bold; font-size: 1.1em; } .optional { font-size: 1.3em; } .sig-paren { font-size: larger; } .versionmodified { font-style: italic; } .system-message { background-color: #fda; padding: 5px; border: 3px solid red; } .footnote:target { background-color: #ffa; } .line-block { display: block; margin-top: 1em; margin-bottom: 1em; } .line-block .line-block { margin-top: 0; margin-bottom: 0; margin-left: 1.5em; } .guilabel, .menuselection { font-family: sans-serif; } .accelerator { text-decoration: underline; } .classifier { font-style: oblique; } .classifier:before { font-style: normal; margin: 0.5em; content: ":"; } abbr, acronym { border-bottom: dotted 1px; cursor: help; } /* -- code displays --------------------------------------------------------- */ pre { overflow: auto; overflow-y: hidden; /* fixes display issues on Chrome browsers */ } pre, div[class*="highlight-"] { clear: both; } span.pre { -moz-hyphens: none; -ms-hyphens: none; -webkit-hyphens: none; hyphens: none; } div[class*="highlight-"] { margin: 1em 0; } td.linenos pre { border: 0; background-color: transparent; color: #aaa; } table.highlighttable { display: block; } table.highlighttable tbody { display: block; } table.highlighttable tr { display: flex; } table.highlighttable td { margin: 0; padding: 0; } table.highlighttable td.linenos { padding-right: 0.5em; } table.highlighttable td.code { flex: 1; overflow: hidden; } .highlight .hll { display: block; } div.highlight pre, table.highlighttable pre { margin: 0; } div.code-block-caption + div { margin-top: 0; } div.code-block-caption { margin-top: 1em; padding: 2px 5px; font-size: small; } div.code-block-caption code { background-color: transparent; } table.highlighttable td.linenos, span.linenos, div.doctest > div.highlight span.gp { /* gp: Generic.Prompt */ user-select: none; } div.code-block-caption span.caption-number { padding: 0.1em 0.3em; font-style: italic; } div.code-block-caption span.caption-text { } div.literal-block-wrapper { margin: 1em 0; } code.descname { background-color: transparent; font-weight: bold; font-size: 1.2em; } code.descclassname { background-color: transparent; } code.xref, a code { background-color: transparent; font-weight: bold; } h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { background-color: transparent; } .viewcode-link { float: right; } .viewcode-back { float: right; font-family: sans-serif; } div.viewcode-block:target { margin: -1px -10px; padding: 0 10px; } /* -- math display ---------------------------------------------------------- */ img.math { vertical-align: middle; } div.body div.math p { text-align: center; } span.eqno { float: right; } span.eqno a.headerlink { position: absolute; z-index: 1; } div.math:hover a.headerlink { visibility: visible; } /* -- printout stylesheet --------------------------------------------------- */ @media print { div.document, div.documentwrapper, div.bodywrapper { margin: 0 !important; width: 100%; } div.sphinxsidebar, div.related, div.footer, #top-link { display: none; } } ================================================ FILE: docs/_build/html/_static/css/theme.css ================================================ /* sphinx_rtd_theme version 0.4.3 | MIT license */ /* Built 20190212 16:02 */ * { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } @font-face { font-family: "Roboto-Regular"; src: url("/resources/fonts/Roboto-Regular.ttf"), url("/resources/fonts/Roboto-Regular.woff"), url("/resources/fonts/Roboto-Regular.woff2"); } @font-face { font-family: "Roboto-Medium"; src: url("/resources/fonts/Roboto-Medium.ttf"), url("/resources/fonts/Roboto-Medium.woff"), url("/resources/fonts/Roboto-Medium.woff2"); } @font-face { font-family: "Roboto Mono"; src: url("/resources/fonts/Roboto-Mono-Regular.ttf"), url("/resources/fonts/Roboto-Mono-Regular.woff"), url("/resources/fonts/Roboto-Mono-Regular.woff2"); } @font-face { font-family: "Material-Icons"; src: url("/resources/fonts/MaterialIcons-Regular.ttf"), url("/resources/fonts/MaterialIcons-Regular.woff"), url("/resources/fonts/MaterialIcons-Regular.woff2"); } article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { display: block; } audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; } audio:not([controls]) { display: none; } [hidden] { display: none; } * { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } html { font-size: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } body { margin: 0; } a:hover, a:active { outline: 0; } abbr[title] { border-bottom: 1px dotted; } b, strong { font-weight: bold; } blockquote { margin: 0; } dfn { font-style: italic; } ins { background: #ff9; color: #000; text-decoration: none; } mark { background: #ff0; color: #000; font-style: italic; font-weight: bold; } pre, code, .rst-content tt, .rst-content code, kbd, samp { font-family: "Roboto Mono", serif; _font-family: "courier new", monospace; font-size: 1em; } pre { white-space: pre; } q { quotes: none; } q:before, q:after { content: ""; content: none; } small { font-size: 85%; } sub, sup { /* font-size: 75%; */ line-height: 0; position: relative; vertical-align: baseline; } sup { top: -0.5em; } sub { bottom: -0.25em; } ul, ol, dl { margin: 0; padding: 0; list-style: none; list-style-image: none; } li { list-style: none; } dd { margin: 0; } img { border: 0; -ms-interpolation-mode: bicubic; vertical-align: middle; max-width: 100%; } svg:not(:root) { overflow: hidden; } figure { margin: 0; } form { margin: 0; } fieldset { border: 0; margin: 0; padding: 0; } label { cursor: pointer; } legend { border: 0; *margin-left: -7px; padding: 0; white-space: normal; } button, input, select, textarea { font-size: 100%; margin: 0; vertical-align: baseline; *vertical-align: middle; } button, input { line-height: normal; } button, input[type="button"], input[type="reset"], input[type="submit"] { cursor: pointer; -webkit-appearance: button; *overflow: visible; } button[disabled], input[disabled] { cursor: default; } input[type="checkbox"], input[type="radio"] { box-sizing: border-box; padding: 0; *width: 13px; *height: 13px; } input[type="search"] { -webkit-appearance: textfield; -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box; } input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; } button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } textarea { overflow: auto; vertical-align: top; resize: vertical; } table { border-collapse: collapse; border-spacing: 0; } td { vertical-align: top; } .chromeframe { margin: 0.2em 0; background: #ccc; color: #000; padding: 0.2em 0; } .ir { display: block; border: 0; text-indent: -999em; overflow: hidden; background-color: transparent; background-repeat: no-repeat; text-align: left; direction: ltr; *line-height: 0; } .ir br { display: none; } .hidden { display: none !important; visibility: hidden; } .visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } .visuallyhidden.focusable:active, .visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; } .invisible { visibility: hidden; } .relative { position: relative; } big, small { font-size: 100%; } @media print { html, body, section { background: none !important; } * { box-shadow: none !important; text-shadow: none !important; filter: none !important; -ms-filter: none !important; } a, a:visited { text-decoration: underline; } .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } pre, blockquote { page-break-inside: avoid; } thead { display: table-header-group; } tr, img { page-break-inside: avoid; } img { max-width: 100% !important; } @page { margin: 0.5cm; } p, h2, .rst-content .toctree-wrapper p.caption, h3 { orphans: 3; widows: 3; } h2, .rst-content .toctree-wrapper p.caption, h3 { page-break-after: avoid; } } .fa:before, .wy-menu-vertical li span.toctree-expand:before, .wy-menu-vertical li.on a span.toctree-expand:before, .wy-menu-vertical li.current > a span.toctree-expand:before, .rst-content .admonition-title:before, .rst-content h1 .headerlink:before, .rst-content h2 .headerlink:before, .rst-content h3 .headerlink:before, .rst-content h4 .headerlink:before, .rst-content h5 .headerlink:before, .rst-content h6 .headerlink:before, .rst-content dl dt .headerlink:before, .rst-content p.caption .headerlink:before, .rst-content table > caption .headerlink:before, .rst-content .code-block-caption .headerlink:before, .rst-content tt.download span:first-child:before, .rst-content code.download span:first-child:before, .icon:before, .wy-dropdown .caret:before, .wy-inline-validate.wy-inline-validate-success .wy-input-context:before, .wy-inline-validate.wy-inline-validate-danger .wy-input-context:before, .wy-inline-validate.wy-inline-validate-warning .wy-input-context:before, .wy-inline-validate.wy-inline-validate-info .wy-input-context:before, .wy-alert, .rst-content .note, .rst-content .attention, .rst-content .caution, .rst-content .danger, .rst-content .error, .rst-content .hint, .rst-content .important, .rst-content .tip, .rst-content .warning, .rst-content .seealso, .rst-content .admonition-todo, .rst-content .admonition, .btn, input[type="text"], input[type="password"], input[type="email"], input[type="url"], input[type="date"], input[type="month"], input[type="time"], input[type="datetime"], input[type="datetime-local"], input[type="week"], input[type="number"], input[type="search"], input[type="tel"], input[type="color"], select, textarea, .wy-menu-vertical li.on a, .wy-menu-vertical li.current > a, .wy-side-nav-search > a, .wy-side-nav-search .wy-dropdown > a, .wy-nav-top a { -webkit-font-smoothing: antialiased; } .clearfix { *zoom: 1; } .clearfix:before, .clearfix:after { display: table; content: ""; } .clearfix:after { clear: both; } .fa-border { padding: 0.2em 0.25em 0.15em; border: solid 0.08em #eee; border-radius: 0.1em; } .fa-pull-left { float: left; } .fa-pull-right { float: right; } .fa.fa-pull-left, .wy-menu-vertical li span.fa-pull-left.toctree-expand, .wy-menu-vertical li.on a span.fa-pull-left.toctree-expand, .wy-menu-vertical li.current > a span.fa-pull-left.toctree-expand, .rst-content .fa-pull-left.admonition-title, .rst-content h1 .fa-pull-left.headerlink, .rst-content h2 .fa-pull-left.headerlink, .rst-content h3 .fa-pull-left.headerlink, .rst-content h4 .fa-pull-left.headerlink, .rst-content h5 .fa-pull-left.headerlink, .rst-content h6 .fa-pull-left.headerlink, .rst-content dl dt .fa-pull-left.headerlink, .rst-content p.caption .fa-pull-left.headerlink, .rst-content table > caption .fa-pull-left.headerlink, .rst-content .code-block-caption .fa-pull-left.headerlink, .rst-content tt.download span.fa-pull-left:first-child, .rst-content code.download span.fa-pull-left:first-child, .fa-pull-left.icon { margin-right: 0.3em; } .fa.fa-pull-right, .wy-menu-vertical li span.fa-pull-right.toctree-expand, .wy-menu-vertical li.on a span.fa-pull-right.toctree-expand, .wy-menu-vertical li.current > a span.fa-pull-right.toctree-expand, .rst-content .fa-pull-right.admonition-title, .rst-content h1 .fa-pull-right.headerlink, .rst-content h2 .fa-pull-right.headerlink, .rst-content h3 .fa-pull-right.headerlink, .rst-content h4 .fa-pull-right.headerlink, .rst-content h5 .fa-pull-right.headerlink, .rst-content h6 .fa-pull-right.headerlink, .rst-content dl dt .fa-pull-right.headerlink, .rst-content p.caption .fa-pull-right.headerlink, .rst-content table > caption .fa-pull-right.headerlink, .rst-content .code-block-caption .fa-pull-right.headerlink, .rst-content tt.download span.fa-pull-right:first-child, .rst-content code.download span.fa-pull-right:first-child, .fa-pull-right.icon { margin-left: 0.3em; } .pull-right { float: right; } .pull-left { float: left; } .fa.pull-left, .wy-menu-vertical li span.pull-left.toctree-expand, .wy-menu-vertical li.on a span.pull-left.toctree-expand, .wy-menu-vertical li.current > a span.pull-left.toctree-expand, .rst-content .pull-left.admonition-title, .rst-content h1 .pull-left.headerlink, .rst-content h2 .pull-left.headerlink, .rst-content h3 .pull-left.headerlink, .rst-content h4 .pull-left.headerlink, .rst-content h5 .pull-left.headerlink, .rst-content h6 .pull-left.headerlink, .rst-content dl dt .pull-left.headerlink, .rst-content p.caption .pull-left.headerlink, .rst-content table > caption .pull-left.headerlink, .rst-content .code-block-caption .pull-left.headerlink, .rst-content tt.download span.pull-left:first-child, .rst-content code.download span.pull-left:first-child, .pull-left.icon { margin-right: 0.3em; } .fa.pull-right, .wy-menu-vertical li span.pull-right.toctree-expand, .wy-menu-vertical li.on a span.pull-right.toctree-expand, .wy-menu-vertical li.current > a span.pull-right.toctree-expand, .rst-content .pull-right.admonition-title, .rst-content h1 .pull-right.headerlink, .rst-content h2 .pull-right.headerlink, .rst-content h3 .pull-right.headerlink, .rst-content h4 .pull-right.headerlink, .rst-content h5 .pull-right.headerlink, .rst-content h6 .pull-right.headerlink, .rst-content dl dt .pull-right.headerlink, .rst-content p.caption .pull-right.headerlink, .rst-content table > caption .pull-right.headerlink, .rst-content .code-block-caption .pull-right.headerlink, .rst-content tt.download span.pull-right:first-child, .rst-content code.download span.pull-right:first-child, .pull-right.icon { margin-left: 0.3em; } .fa-arrow-circle-left:before, .icon-circle-arrow-left:before { content: url(../images/gs-arrow-left.svg); position: relative; top: 5px; right: 4px; } .fa-arrow-circle-right:before, .icon-circle-arrow-right:before { content: url(../images/gs-arrow-right.svg); position: relative; top: 5px; left: 0px; } .fa-minus-square:before { content: url(../images/gs-menu-collapse.svg); } .fa-minus-square-o:before, .wy-menu-vertical li.on a span.toctree-expand:before, .wy-menu-vertical li.current > a span.toctree-expand:before { content: url(../images/gs-menu-collapse.svg); } .fa-plus-square-o:before, .wy-menu-vertical li span.toctree-expand:before { content: url(../images/gs-menu-expand.svg); } .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); border: 0; } .sr-only-focusable:active, .sr-only-focusable:focus { position: static; width: auto; height: auto; margin: 0; overflow: visible; clip: auto; } .fa, .wy-menu-vertical li span.toctree-expand, .wy-menu-vertical li.on a span.toctree-expand, .wy-menu-vertical li.current > a span.toctree-expand, .rst-content .admonition-title, .rst-content h1 .headerlink, .rst-content h2 .headerlink, .rst-content h3 .headerlink, .rst-content h4 .headerlink, .rst-content h5 .headerlink, .rst-content h6 .headerlink, .rst-content dl dt .headerlink, .rst-content p.caption .headerlink, .rst-content table > caption .headerlink, .rst-content .code-block-caption .headerlink, .rst-content tt.download span:first-child, .rst-content code.download span:first-child, .icon, .wy-dropdown .caret, .wy-inline-validate.wy-inline-validate-success .wy-input-context, .wy-inline-validate.wy-inline-validate-danger .wy-input-context, .wy-inline-validate.wy-inline-validate-warning .wy-input-context, .wy-inline-validate.wy-inline-validate-info .wy-input-context { font-family: inherit; } .fa:before, .wy-menu-vertical li span.toctree-expand:before, .wy-menu-vertical li.on a span.toctree-expand:before, .wy-menu-vertical li.current > a span.toctree-expand:before, .rst-content .admonition-title:before, .rst-content h1 .headerlink:before, .rst-content h2 .headerlink:before, .rst-content h3 .headerlink:before, .rst-content h4 .headerlink:before, .rst-content h5 .headerlink:before, .rst-content h6 .headerlink:before, .rst-content dl dt .headerlink:before, .rst-content p.caption .headerlink:before, .rst-content table > caption .headerlink:before, .rst-content .code-block-caption .headerlink:before, .rst-content tt.download span:first-child:before, .rst-content code.download span:first-child:before, .icon:before, .wy-dropdown .caret:before, .wy-inline-validate.wy-inline-validate-success .wy-input-context:before, .wy-inline-validate.wy-inline-validate-danger .wy-input-context:before, .wy-inline-validate.wy-inline-validate-warning .wy-input-context:before, .wy-inline-validate.wy-inline-validate-info .wy-input-context:before { display: inline-block; font-style: normal; font-weight: normal; line-height: 1; text-decoration: inherit; width: 12px; height: 16px; margin-top: -2px; margin-left: -8px; } a .fa, a .wy-menu-vertical li span.toctree-expand, .wy-menu-vertical li a span.toctree-expand, .wy-menu-vertical li.on a span.toctree-expand, .wy-menu-vertical li.current > a span.toctree-expand, a .rst-content .admonition-title, .rst-content a .admonition-title, a .rst-content h1 .headerlink, .rst-content h1 a .headerlink, a .rst-content h2 .headerlink, .rst-content h2 a .headerlink, a .rst-content h3 .headerlink, .rst-content h3 a .headerlink, a .rst-content h4 .headerlink, .rst-content h4 a .headerlink, a .rst-content h5 .headerlink, .rst-content h5 a .headerlink, a .rst-content h6 .headerlink, .rst-content h6 a .headerlink, a .rst-content dl dt .headerlink, .rst-content dl dt a .headerlink, a .rst-content p.caption .headerlink, .rst-content p.caption a .headerlink, a .rst-content table > caption .headerlink, .rst-content table > caption a .headerlink, a .rst-content .code-block-caption .headerlink, .rst-content .code-block-caption a .headerlink, a .rst-content tt.download span:first-child, .rst-content tt.download a span:first-child, a .rst-content code.download span:first-child, .rst-content code.download a span:first-child, a .icon { display: inline-block; text-decoration: inherit; } .btn .fa, .btn .wy-menu-vertical li span.toctree-expand, .wy-menu-vertical li .btn span.toctree-expand, .btn .wy-menu-vertical li.on a span.toctree-expand, .wy-menu-vertical li.on a .btn span.toctree-expand, .btn .wy-menu-vertical li.current > a span.toctree-expand, .wy-menu-vertical li.current > a .btn span.toctree-expand, .btn .rst-content .admonition-title, .rst-content .btn .admonition-title, .btn .rst-content h1 .headerlink, .rst-content h1 .btn .headerlink, .btn .rst-content h2 .headerlink, .rst-content h2 .btn .headerlink, .btn .rst-content h3 .headerlink, .rst-content h3 .btn .headerlink, .btn .rst-content h4 .headerlink, .rst-content h4 .btn .headerlink, .btn .rst-content h5 .headerlink, .rst-content h5 .btn .headerlink, .btn .rst-content h6 .headerlink, .rst-content h6 .btn .headerlink, .btn .rst-content dl dt .headerlink, .rst-content dl dt .btn .headerlink, .btn .rst-content p.caption .headerlink, .rst-content p.caption .btn .headerlink, .btn .rst-content table > caption .headerlink, .rst-content table > caption .btn .headerlink, .btn .rst-content .code-block-caption .headerlink, .rst-content .code-block-caption .btn .headerlink, .btn .rst-content tt.download span:first-child, .rst-content tt.download .btn span:first-child, .btn .rst-content code.download span:first-child, .rst-content code.download .btn span:first-child, .btn .icon, .nav .fa, .nav .wy-menu-vertical li span.toctree-expand, .wy-menu-vertical li .nav span.toctree-expand, .nav .wy-menu-vertical li.on a span.toctree-expand, .wy-menu-vertical li.on a .nav span.toctree-expand, .nav .wy-menu-vertical li.current > a span.toctree-expand, .wy-menu-vertical li.current > a .nav span.toctree-expand, .nav .rst-content .admonition-title, .rst-content .nav .admonition-title, .nav .rst-content h1 .headerlink, .rst-content h1 .nav .headerlink, .nav .rst-content h2 .headerlink, .rst-content h2 .nav .headerlink, .nav .rst-content h3 .headerlink, .rst-content h3 .nav .headerlink, .nav .rst-content h4 .headerlink, .rst-content h4 .nav .headerlink, .nav .rst-content h5 .headerlink, .rst-content h5 .nav .headerlink, .nav .rst-content h6 .headerlink, .rst-content h6 .nav .headerlink, .nav .rst-content dl dt .headerlink, .rst-content dl dt .nav .headerlink, .nav .rst-content p.caption .headerlink, .rst-content p.caption .nav .headerlink, .nav .rst-content table > caption .headerlink, .rst-content table > caption .nav .headerlink, .nav .rst-content .code-block-caption .headerlink, .rst-content .code-block-caption .nav .headerlink, .nav .rst-content tt.download span:first-child, .rst-content tt.download .nav span:first-child, .nav .rst-content code.download span:first-child, .rst-content code.download .nav span:first-child, .nav .icon { display: inline; } .btn .fa.fa-large, .btn .wy-menu-vertical li span.fa-large.toctree-expand, .wy-menu-vertical li .btn span.fa-large.toctree-expand, .btn .rst-content .fa-large.admonition-title, .rst-content .btn .fa-large.admonition-title, .btn .rst-content h1 .fa-large.headerlink, .rst-content h1 .btn .fa-large.headerlink, .btn .rst-content h2 .fa-large.headerlink, .rst-content h2 .btn .fa-large.headerlink, .btn .rst-content h3 .fa-large.headerlink, .rst-content h3 .btn .fa-large.headerlink, .btn .rst-content h4 .fa-large.headerlink, .rst-content h4 .btn .fa-large.headerlink, .btn .rst-content h5 .fa-large.headerlink, .rst-content h5 .btn .fa-large.headerlink, .btn .rst-content h6 .fa-large.headerlink, .rst-content h6 .btn .fa-large.headerlink, .btn .rst-content dl dt .fa-large.headerlink, .rst-content dl dt .btn .fa-large.headerlink, .btn .rst-content p.caption .fa-large.headerlink, .rst-content p.caption .btn .fa-large.headerlink, .btn .rst-content table > caption .fa-large.headerlink, .rst-content table > caption .btn .fa-large.headerlink, .btn .rst-content .code-block-caption .fa-large.headerlink, .rst-content .code-block-caption .btn .fa-large.headerlink, .btn .rst-content tt.download span.fa-large:first-child, .rst-content tt.download .btn span.fa-large:first-child, .btn .rst-content code.download span.fa-large:first-child, .rst-content code.download .btn span.fa-large:first-child, .btn .fa-large.icon, .nav .fa.fa-large, .nav .wy-menu-vertical li span.fa-large.toctree-expand, .wy-menu-vertical li .nav span.fa-large.toctree-expand, .nav .rst-content .fa-large.admonition-title, .rst-content .nav .fa-large.admonition-title, .nav .rst-content h1 .fa-large.headerlink, .rst-content h1 .nav .fa-large.headerlink, .nav .rst-content h2 .fa-large.headerlink, .rst-content h2 .nav .fa-large.headerlink, .nav .rst-content h3 .fa-large.headerlink, .rst-content h3 .nav .fa-large.headerlink, .nav .rst-content h4 .fa-large.headerlink, .rst-content h4 .nav .fa-large.headerlink, .nav .rst-content h5 .fa-large.headerlink, .rst-content h5 .nav .fa-large.headerlink, .nav .rst-content h6 .fa-large.headerlink, .rst-content h6 .nav .fa-large.headerlink, .nav .rst-content dl dt .fa-large.headerlink, .rst-content dl dt .nav .fa-large.headerlink, .nav .rst-content p.caption .fa-large.headerlink, .rst-content p.caption .nav .fa-large.headerlink, .nav .rst-content table > caption .fa-large.headerlink, .rst-content table > caption .nav .fa-large.headerlink, .nav .rst-content .code-block-caption .fa-large.headerlink, .rst-content .code-block-caption .nav .fa-large.headerlink, .nav .rst-content tt.download span.fa-large:first-child, .rst-content tt.download .nav span.fa-large:first-child, .nav .rst-content code.download span.fa-large:first-child, .rst-content code.download .nav span.fa-large:first-child, .nav .fa-large.icon { line-height: 0.9em; } .btn .fa.fa-spin, .btn .wy-menu-vertical li span.fa-spin.toctree-expand, .wy-menu-vertical li .btn span.fa-spin.toctree-expand, .btn .rst-content .fa-spin.admonition-title, .rst-content .btn .fa-spin.admonition-title, .btn .rst-content h1 .fa-spin.headerlink, .rst-content h1 .btn .fa-spin.headerlink, .btn .rst-content h2 .fa-spin.headerlink, .rst-content h2 .btn .fa-spin.headerlink, .btn .rst-content h3 .fa-spin.headerlink, .rst-content h3 .btn .fa-spin.headerlink, .btn .rst-content h4 .fa-spin.headerlink, .rst-content h4 .btn .fa-spin.headerlink, .btn .rst-content h5 .fa-spin.headerlink, .rst-content h5 .btn .fa-spin.headerlink, .btn .rst-content h6 .fa-spin.headerlink, .rst-content h6 .btn .fa-spin.headerlink, .btn .rst-content dl dt .fa-spin.headerlink, .rst-content dl dt .btn .fa-spin.headerlink, .btn .rst-content p.caption .fa-spin.headerlink, .rst-content p.caption .btn .fa-spin.headerlink, .btn .rst-content table > caption .fa-spin.headerlink, .rst-content table > caption .btn .fa-spin.headerlink, .btn .rst-content .code-block-caption .fa-spin.headerlink, .rst-content .code-block-caption .btn .fa-spin.headerlink, .btn .rst-content tt.download span.fa-spin:first-child, .rst-content tt.download .btn span.fa-spin:first-child, .btn .rst-content code.download span.fa-spin:first-child, .rst-content code.download .btn span.fa-spin:first-child, .btn .fa-spin.icon, .nav .fa.fa-spin, .nav .wy-menu-vertical li span.fa-spin.toctree-expand, .wy-menu-vertical li .nav span.fa-spin.toctree-expand, .nav .rst-content .fa-spin.admonition-title, .rst-content .nav .fa-spin.admonition-title, .nav .rst-content h1 .fa-spin.headerlink, .rst-content h1 .nav .fa-spin.headerlink, .nav .rst-content h2 .fa-spin.headerlink, .rst-content h2 .nav .fa-spin.headerlink, .nav .rst-content h3 .fa-spin.headerlink, .rst-content h3 .nav .fa-spin.headerlink, .nav .rst-content h4 .fa-spin.headerlink, .rst-content h4 .nav .fa-spin.headerlink, .nav .rst-content h5 .fa-spin.headerlink, .rst-content h5 .nav .fa-spin.headerlink, .nav .rst-content h6 .fa-spin.headerlink, .rst-content h6 .nav .fa-spin.headerlink, .nav .rst-content dl dt .fa-spin.headerlink, .rst-content dl dt .nav .fa-spin.headerlink, .nav .rst-content p.caption .fa-spin.headerlink, .rst-content p.caption .nav .fa-spin.headerlink, .nav .rst-content table > caption .fa-spin.headerlink, .rst-content table > caption .nav .fa-spin.headerlink, .nav .rst-content .code-block-caption .fa-spin.headerlink, .rst-content .code-block-caption .nav .fa-spin.headerlink, .nav .rst-content tt.download span.fa-spin:first-child, .rst-content tt.download .nav span.fa-spin:first-child, .nav .rst-content code.download span.fa-spin:first-child, .rst-content code.download .nav span.fa-spin:first-child, .nav .fa-spin.icon { display: inline-block; } .btn.fa:before, .wy-menu-vertical li span.btn.toctree-expand:before, .rst-content .btn.admonition-title:before, .rst-content h1 .btn.headerlink:before, .rst-content h2 .btn.headerlink:before, .rst-content h3 .btn.headerlink:before, .rst-content h4 .btn.headerlink:before, .rst-content h5 .btn.headerlink:before, .rst-content h6 .btn.headerlink:before, .rst-content dl dt .btn.headerlink:before, .rst-content p.caption .btn.headerlink:before, .rst-content table > caption .btn.headerlink:before, .rst-content .code-block-caption .btn.headerlink:before, .rst-content tt.download span.btn:first-child:before, .rst-content code.download span.btn:first-child:before, .btn.icon:before { opacity: 0.5; -webkit-transition: opacity 0.05s ease-in; -moz-transition: opacity 0.05s ease-in; transition: opacity 0.05s ease-in; } .btn.fa:hover:before, .wy-menu-vertical li span.btn.toctree-expand:hover:before, .rst-content .btn.admonition-title:hover:before, .rst-content h1 .btn.headerlink:hover:before, .rst-content h2 .btn.headerlink:hover:before, .rst-content h3 .btn.headerlink:hover:before, .rst-content h4 .btn.headerlink:hover:before, .rst-content h5 .btn.headerlink:hover:before, .rst-content h6 .btn.headerlink:hover:before, .rst-content dl dt .btn.headerlink:hover:before, .rst-content p.caption .btn.headerlink:hover:before, .rst-content table > caption .btn.headerlink:hover:before, .rst-content .code-block-caption .btn.headerlink:hover:before, .rst-content tt.download span.btn:first-child:hover:before, .rst-content code.download span.btn:first-child:hover:before, .btn.icon:hover:before { opacity: 1; } .btn-mini .fa:before, .btn-mini .wy-menu-vertical li span.toctree-expand:before, .wy-menu-vertical li .btn-mini span.toctree-expand:before, .btn-mini .rst-content .admonition-title:before, .rst-content .btn-mini .admonition-title:before, .btn-mini .rst-content h1 .headerlink:before, .rst-content h1 .btn-mini .headerlink:before, .btn-mini .rst-content h2 .headerlink:before, .rst-content h2 .btn-mini .headerlink:before, .btn-mini .rst-content h3 .headerlink:before, .rst-content h3 .btn-mini .headerlink:before, .btn-mini .rst-content h4 .headerlink:before, .rst-content h4 .btn-mini .headerlink:before, .btn-mini .rst-content h5 .headerlink:before, .rst-content h5 .btn-mini .headerlink:before, .btn-mini .rst-content h6 .headerlink:before, .rst-content h6 .btn-mini .headerlink:before, .btn-mini .rst-content dl dt .headerlink:before, .rst-content dl dt .btn-mini .headerlink:before, .btn-mini .rst-content p.caption .headerlink:before, .rst-content p.caption .btn-mini .headerlink:before, .btn-mini .rst-content table > caption .headerlink:before, .rst-content table > caption .btn-mini .headerlink:before, .btn-mini .rst-content .code-block-caption .headerlink:before, .rst-content .code-block-caption .btn-mini .headerlink:before, .btn-mini .rst-content tt.download span:first-child:before, .rst-content tt.download .btn-mini span:first-child:before, .btn-mini .rst-content code.download span:first-child:before, .rst-content code.download .btn-mini span:first-child:before, .btn-mini .icon:before { font-size: 14px; vertical-align: -15%; } .wy-alert, .rst-content .note, .rst-content .attention, .rst-content .caution, .rst-content .danger, .rst-content .error, .rst-content .hint, .rst-content .important, .rst-content .tip, .rst-content .warning, .rst-content .seealso, .rst-content .admonition-todo, .rst-content .admonition { padding: 12px; line-height: 24px; margin-bottom: 24px; background: #e7f2fa; } .wy-alert-title, .rst-content .admonition-title { color: #fff; font-weight: bold; display: block; color: #fff; background: #6ab0de; margin: -12px; padding: 6px 12px; margin-bottom: 12px; } .wy-alert.wy-alert-danger, .rst-content .wy-alert-danger.note, .rst-content .wy-alert-danger.attention, .rst-content .wy-alert-danger.caution, .rst-content .danger, .rst-content .error, .rst-content .wy-alert-danger.hint, .rst-content .wy-alert-danger.important, .rst-content .wy-alert-danger.tip, .rst-content .wy-alert-danger.warning, .rst-content .wy-alert-danger.seealso, .rst-content .wy-alert-danger.admonition-todo, .rst-content .wy-alert-danger.admonition { background: #fdf3f2; } .wy-alert.wy-alert-danger .wy-alert-title, .rst-content .wy-alert-danger.note .wy-alert-title, .rst-content .wy-alert-danger.attention .wy-alert-title, .rst-content .wy-alert-danger.caution .wy-alert-title, .rst-content .danger .wy-alert-title, .rst-content .error .wy-alert-title, .rst-content .wy-alert-danger.hint .wy-alert-title, .rst-content .wy-alert-danger.important .wy-alert-title, .rst-content .wy-alert-danger.tip .wy-alert-title, .rst-content .wy-alert-danger.warning .wy-alert-title, .rst-content .wy-alert-danger.seealso .wy-alert-title, .rst-content .wy-alert-danger.admonition-todo .wy-alert-title, .rst-content .wy-alert-danger.admonition .wy-alert-title, .wy-alert.wy-alert-danger .rst-content .admonition-title, .rst-content .wy-alert.wy-alert-danger .admonition-title, .rst-content .wy-alert-danger.note .admonition-title, .rst-content .wy-alert-danger.attention .admonition-title, .rst-content .wy-alert-danger.caution .admonition-title, .rst-content .danger .admonition-title, .rst-content .error .admonition-title, .rst-content .wy-alert-danger.hint .admonition-title, .rst-content .wy-alert-danger.important .admonition-title, .rst-content .wy-alert-danger.tip .admonition-title, .rst-content .wy-alert-danger.warning .admonition-title, .rst-content .wy-alert-danger.seealso .admonition-title, .rst-content .wy-alert-danger.admonition-todo .admonition-title, .rst-content .wy-alert-danger.admonition .admonition-title { background: #f29f97; } .wy-alert.wy-alert-warning, .rst-content .wy-alert-warning.note, .rst-content .attention, .rst-content .caution, .rst-content .wy-alert-warning.danger, .rst-content .wy-alert-warning.error, .rst-content .wy-alert-warning.hint, .rst-content .wy-alert-warning.important, .rst-content .wy-alert-warning.tip, .rst-content .warning, .rst-content .wy-alert-warning.seealso, .rst-content .admonition-todo, .rst-content .wy-alert-warning.admonition { background: #ffedcc; } .wy-alert.wy-alert-warning .wy-alert-title, .rst-content .wy-alert-warning.note .wy-alert-title, .rst-content .attention .wy-alert-title, .rst-content .caution .wy-alert-title, .rst-content .wy-alert-warning.danger .wy-alert-title, .rst-content .wy-alert-warning.error .wy-alert-title, .rst-content .wy-alert-warning.hint .wy-alert-title, .rst-content .wy-alert-warning.important .wy-alert-title, .rst-content .wy-alert-warning.tip .wy-alert-title, .rst-content .warning .wy-alert-title, .rst-content .wy-alert-warning.seealso .wy-alert-title, .rst-content .admonition-todo .wy-alert-title, .rst-content .wy-alert-warning.admonition .wy-alert-title, .wy-alert.wy-alert-warning .rst-content .admonition-title, .rst-content .wy-alert.wy-alert-warning .admonition-title, .rst-content .wy-alert-warning.note .admonition-title, .rst-content .attention .admonition-title, .rst-content .caution .admonition-title, .rst-content .wy-alert-warning.danger .admonition-title, .rst-content .wy-alert-warning.error .admonition-title, .rst-content .wy-alert-warning.hint .admonition-title, .rst-content .wy-alert-warning.important .admonition-title, .rst-content .wy-alert-warning.tip .admonition-title, .rst-content .warning .admonition-title, .rst-content .wy-alert-warning.seealso .admonition-title, .rst-content .admonition-todo .admonition-title, .rst-content .wy-alert-warning.admonition .admonition-title { background: #f0b37e; } .wy-alert.wy-alert-info, .rst-content .note, .rst-content .wy-alert-info.attention, .rst-content .wy-alert-info.caution, .rst-content .wy-alert-info.danger, .rst-content .wy-alert-info.error, .rst-content .wy-alert-info.hint, .rst-content .wy-alert-info.important, .rst-content .wy-alert-info.tip, .rst-content .wy-alert-info.warning, .rst-content .seealso, .rst-content .wy-alert-info.admonition-todo, .rst-content .wy-alert-info.admonition { background: #e7f2fa; } .wy-alert.wy-alert-info .wy-alert-title, .rst-content .note .wy-alert-title, .rst-content .wy-alert-info.attention .wy-alert-title, .rst-content .wy-alert-info.caution .wy-alert-title, .rst-content .wy-alert-info.danger .wy-alert-title, .rst-content .wy-alert-info.error .wy-alert-title, .rst-content .wy-alert-info.hint .wy-alert-title, .rst-content .wy-alert-info.important .wy-alert-title, .rst-content .wy-alert-info.tip .wy-alert-title, .rst-content .wy-alert-info.warning .wy-alert-title, .rst-content .seealso .wy-alert-title, .rst-content .wy-alert-info.admonition-todo .wy-alert-title, .rst-content .wy-alert-info.admonition .wy-alert-title, .wy-alert.wy-alert-info .rst-content .admonition-title, .rst-content .wy-alert.wy-alert-info .admonition-title, .rst-content .note .admonition-title, .rst-content .wy-alert-info.attention .admonition-title, .rst-content .wy-alert-info.caution .admonition-title, .rst-content .wy-alert-info.danger .admonition-title, .rst-content .wy-alert-info.error .admonition-title, .rst-content .wy-alert-info.hint .admonition-title, .rst-content .wy-alert-info.important .admonition-title, .rst-content .wy-alert-info.tip .admonition-title, .rst-content .wy-alert-info.warning .admonition-title, .rst-content .seealso .admonition-title, .rst-content .wy-alert-info.admonition-todo .admonition-title, .rst-content .wy-alert-info.admonition .admonition-title { background: #6ab0de; } .wy-alert.wy-alert-success, .rst-content .wy-alert-success.note, .rst-content .wy-alert-success.attention, .rst-content .wy-alert-success.caution, .rst-content .wy-alert-success.danger, .rst-content .wy-alert-success.error, .rst-content .hint, .rst-content .important, .rst-content .tip, .rst-content .wy-alert-success.warning, .rst-content .wy-alert-success.seealso, .rst-content .wy-alert-success.admonition-todo, .rst-content .wy-alert-success.admonition { background: #dbfaf4; } .wy-alert.wy-alert-success .wy-alert-title, .rst-content .wy-alert-success.note .wy-alert-title, .rst-content .wy-alert-success.attention .wy-alert-title, .rst-content .wy-alert-success.caution .wy-alert-title, .rst-content .wy-alert-success.danger .wy-alert-title, .rst-content .wy-alert-success.error .wy-alert-title, .rst-content .hint .wy-alert-title, .rst-content .important .wy-alert-title, .rst-content .tip .wy-alert-title, .rst-content .wy-alert-success.warning .wy-alert-title, .rst-content .wy-alert-success.seealso .wy-alert-title, .rst-content .wy-alert-success.admonition-todo .wy-alert-title, .rst-content .wy-alert-success.admonition .wy-alert-title, .wy-alert.wy-alert-success .rst-content .admonition-title, .rst-content .wy-alert.wy-alert-success .admonition-title, .rst-content .wy-alert-success.note .admonition-title, .rst-content .wy-alert-success.attention .admonition-title, .rst-content .wy-alert-success.caution .admonition-title, .rst-content .wy-alert-success.danger .admonition-title, .rst-content .wy-alert-success.error .admonition-title, .rst-content .hint .admonition-title, .rst-content .important .admonition-title, .rst-content .tip .admonition-title, .rst-content .wy-alert-success.warning .admonition-title, .rst-content .wy-alert-success.seealso .admonition-title, .rst-content .wy-alert-success.admonition-todo .admonition-title, .rst-content .wy-alert-success.admonition .admonition-title { background: #1abc9c; } .wy-alert.wy-alert-neutral, .rst-content .wy-alert-neutral.note, .rst-content .wy-alert-neutral.attention, .rst-content .wy-alert-neutral.caution, .rst-content .wy-alert-neutral.danger, .rst-content .wy-alert-neutral.error, .rst-content .wy-alert-neutral.hint, .rst-content .wy-alert-neutral.important, .rst-content .wy-alert-neutral.tip, .rst-content .wy-alert-neutral.warning, .rst-content .wy-alert-neutral.seealso, .rst-content .wy-alert-neutral.admonition-todo, .rst-content .wy-alert-neutral.admonition { background: #f3f6f6; } .wy-alert.wy-alert-neutral .wy-alert-title, .rst-content .wy-alert-neutral.note .wy-alert-title, .rst-content .wy-alert-neutral.attention .wy-alert-title, .rst-content .wy-alert-neutral.caution .wy-alert-title, .rst-content .wy-alert-neutral.danger .wy-alert-title, .rst-content .wy-alert-neutral.error .wy-alert-title, .rst-content .wy-alert-neutral.hint .wy-alert-title, .rst-content .wy-alert-neutral.important .wy-alert-title, .rst-content .wy-alert-neutral.tip .wy-alert-title, .rst-content .wy-alert-neutral.warning .wy-alert-title, .rst-content .wy-alert-neutral.seealso .wy-alert-title, .rst-content .wy-alert-neutral.admonition-todo .wy-alert-title, .rst-content .wy-alert-neutral.admonition .wy-alert-title, .wy-alert.wy-alert-neutral .rst-content .admonition-title, .rst-content .wy-alert.wy-alert-neutral .admonition-title, .rst-content .wy-alert-neutral.note .admonition-title, .rst-content .wy-alert-neutral.attention .admonition-title, .rst-content .wy-alert-neutral.caution .admonition-title, .rst-content .wy-alert-neutral.danger .admonition-title, .rst-content .wy-alert-neutral.error .admonition-title, .rst-content .wy-alert-neutral.hint .admonition-title, .rst-content .wy-alert-neutral.important .admonition-title, .rst-content .wy-alert-neutral.tip .admonition-title, .rst-content .wy-alert-neutral.warning .admonition-title, .rst-content .wy-alert-neutral.seealso .admonition-title, .rst-content .wy-alert-neutral.admonition-todo .admonition-title, .rst-content .wy-alert-neutral.admonition .admonition-title { color: #404040; background: #e1e4e5; } .wy-alert.wy-alert-neutral a, .rst-content .wy-alert-neutral.note a, .rst-content .wy-alert-neutral.attention a, .rst-content .wy-alert-neutral.caution a, .rst-content .wy-alert-neutral.danger a, .rst-content .wy-alert-neutral.error a, .rst-content .wy-alert-neutral.hint a, .rst-content .wy-alert-neutral.important a, .rst-content .wy-alert-neutral.tip a, .rst-content .wy-alert-neutral.warning a, .rst-content .wy-alert-neutral.seealso a, .rst-content .wy-alert-neutral.admonition-todo a, .rst-content .wy-alert-neutral.admonition a { color: #2980b9; } .wy-alert p:last-child, .rst-content .note p:last-child, .rst-content .attention p:last-child, .rst-content .caution p:last-child, .rst-content .danger p:last-child, .rst-content .error p:last-child, .rst-content .hint p:last-child, .rst-content .important p:last-child, .rst-content .tip p:last-child, .rst-content .warning p:last-child, .rst-content .seealso p:last-child, .rst-content .admonition-todo p:last-child, .rst-content .admonition p:last-child { margin-bottom: 0; } .wy-tray-container { position: fixed; bottom: 0px; left: 0; z-index: 600; } .wy-tray-container li { display: block; width: 300px; background: transparent; color: #fff; text-align: center; box-shadow: 0 5px 5px 0 rgba(0, 0, 0, 0.1); padding: 0 24px; min-width: 20%; opacity: 0; height: 0; line-height: 56px; overflow: hidden; -webkit-transition: all 0.3s ease-in; -moz-transition: all 0.3s ease-in; transition: all 0.3s ease-in; } .wy-tray-container li.wy-tray-item-success { background: #27ae60; } .wy-tray-container li.wy-tray-item-info { background: #2980b9; } .wy-tray-container li.wy-tray-item-warning { background: #e67e22; } .wy-tray-container li.wy-tray-item-danger { background: #e74c3c; } .wy-tray-container li.on { opacity: 1; height: 56px; } @media screen and (max-width: 768px) { .wy-tray-container { bottom: auto; top: 0; width: 100%; } .wy-tray-container li { width: 100%; } } button { font-size: 100%; margin: 0; vertical-align: baseline; *vertical-align: middle; cursor: pointer; line-height: normal; -webkit-appearance: button; *overflow: visible; } button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } button[disabled] { cursor: default; } .btn { display: inline-block; line-height: normal; white-space: nowrap; text-align: center; cursor: pointer; font-size: 16px; padding: 24px; color: #fff; background-color: #fafafa; text-decoration: none; font-weight: normal; font-family: "Roboto-Regular", sans-serif; outline-none: false; vertical-align: middle; *display: inline; zoom: 1; -webkit-user-drag: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; width: 50%; } .btn-hover { background: #2e8ece; color: #fff; } .btn:hover { background: #2cc36b; color: #fff; } .btn:focus { background: #2cc36b; outline: 0; } .btn:active { box-shadow: 0px -1px 0px 0px rgba(0, 0, 0, 0.05) inset, 0px 2px 0px 0px rgba(0, 0, 0, 0.1) inset; padding: 8px 12px 6px 12px; } .btn:visited { color: #fff; } .btn:disabled { background-image: none; filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); filter: alpha(opacity=40); opacity: 0.4; cursor: not-allowed; box-shadow: none; } .btn-disabled { background-image: none; filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); filter: alpha(opacity=40); opacity: 0.4; cursor: not-allowed; box-shadow: none; } .btn-disabled:hover, .btn-disabled:focus, .btn-disabled:active { background-image: none; filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); filter: alpha(opacity=40); opacity: 0.4; cursor: not-allowed; box-shadow: none; } .btn::-moz-focus-inner { padding: 0; border: 0; } .btn-small { font-size: 80%; } .btn-info { background-color: #2980b9 !important; } .btn-info:hover { background-color: #2e8ece !important; } .btn-neutral { background-color: #fafafa !important; color: #404040 !important; } .btn-neutral:hover { background-color: #fafafa !important; color: #404040; } .btn-neutral:visited { color: #404040 !important; } .btn-success { background-color: #27ae60 !important; } .btn-success:hover { background-color: #295 !important; } .btn-danger { background-color: #e74c3c !important; } .btn-danger:hover { background-color: #ea6153 !important; } .btn-warning { background-color: #e67e22 !important; } .btn-warning:hover { background-color: #e98b39 !important; } .btn-invert { background-color: #222; } .btn-invert:hover { background-color: #2f2f2f !important; } .btn-link { background-color: transparent !important; color: #2980b9; box-shadow: none; border-color: transparent !important; } .btn-link:hover { background-color: transparent !important; color: #409ad5 !important; box-shadow: none; } .btn-link:active { background-color: transparent !important; color: #409ad5 !important; box-shadow: none; } .btn-link:visited { color: #9b59b6; } .wy-btn-group .btn, .wy-control .btn { vertical-align: middle; } .wy-btn-group { margin-bottom: 24px; *zoom: 1; } .wy-btn-group:before, .wy-btn-group:after { display: table; content: ""; } .wy-btn-group:after { clear: both; } .wy-dropdown { position: relative; display: inline-block; } .wy-dropdown-active .wy-dropdown-menu { display: block; } .wy-dropdown-menu { position: absolute; left: 0; display: none; float: left; top: 100%; min-width: 100%; background: #fcfcfc; z-index: 100; border: solid 1px #cfd7dd; box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.1); padding: 12px; } .wy-dropdown-menu > dd > a { display: block; clear: both; color: #404040; white-space: nowrap; font-size: 90%; padding: 0 12px; cursor: pointer; } .wy-dropdown-menu > dd > a:hover { background: #2980b9; color: #fff; } .wy-dropdown-menu > dd.divider { border-top: solid 1px #cfd7dd; margin: 6px 0; } .wy-dropdown-menu > dd.search { padding-bottom: 12px; } .wy-dropdown-menu > dd.search input[type="search"] { width: 100%; } .wy-dropdown-menu > dd.call-to-action { background: #e3e3e3; text-transform: uppercase; font-weight: 500; font-size: 80%; } .wy-dropdown-menu > dd.call-to-action:hover { background: #e3e3e3; } .wy-dropdown-menu > dd.call-to-action .btn { color: #fff; } .wy-dropdown.wy-dropdown-up .wy-dropdown-menu { bottom: 100%; top: auto; left: auto; right: 0; } .wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu { background: #fcfcfc; margin-top: 2px; } .wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a { padding: 6px 12px; } .wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover { background: #2980b9; color: #fff; } .wy-dropdown.wy-dropdown-left .wy-dropdown-menu { right: 0; left: auto; text-align: right; } .wy-dropdown-arrow:before { content: " "; border-bottom: 5px solid #f5f5f5; border-left: 5px solid transparent; border-right: 5px solid transparent; position: absolute; display: block; top: -4px; left: 50%; margin-left: -3px; } .wy-dropdown-arrow.wy-dropdown-arrow-left:before { left: 11px; } .wy-form-stacked select { display: block; } .wy-form-aligned input, .wy-form-aligned textarea, .wy-form-aligned select, .wy-form-aligned .wy-help-inline, .wy-form-aligned label { display: inline-block; *display: inline; *zoom: 1; vertical-align: middle; } .wy-form-aligned .wy-control-group > label { display: inline-block; vertical-align: middle; width: 10em; margin: 6px 12px 0 0; float: left; } .wy-form-aligned .wy-control { float: left; } .wy-form-aligned .wy-control label { display: block; } .wy-form-aligned .wy-control select { margin-top: 6px; } fieldset { border: 0; margin: 0; padding: 0; } legend { display: block; width: 100%; border: 0; padding: 0; white-space: normal; margin-bottom: 24px; font-size: 150%; *margin-left: -7px; } label { display: block; margin: 0 0 0.3125em 0; color: #333; font-size: 90%; } input, select, textarea { font-size: 100%; margin: 0; vertical-align: baseline; *vertical-align: middle; } .wy-control-group { margin-bottom: 24px; *zoom: 1; max-width: 68em; margin-left: auto; margin-right: auto; *zoom: 1; } .wy-control-group:before, .wy-control-group:after { display: table; content: ""; } .wy-control-group:after { clear: both; } .wy-control-group:before, .wy-control-group:after { display: table; content: ""; } .wy-control-group:after { clear: both; } .wy-control-group.wy-control-group-required > label:after { content: " *"; color: #e74c3c; } .wy-control-group .wy-form-full, .wy-control-group .wy-form-halves, .wy-control-group .wy-form-thirds { padding-bottom: 12px; } .wy-control-group .wy-form-full select, .wy-control-group .wy-form-halves select, .wy-control-group .wy-form-thirds select { width: 100%; } .wy-control-group .wy-form-full input[type="text"], .wy-control-group .wy-form-full input[type="password"], .wy-control-group .wy-form-full input[type="email"], .wy-control-group .wy-form-full input[type="url"], .wy-control-group .wy-form-full input[type="date"], .wy-control-group .wy-form-full input[type="month"], .wy-control-group .wy-form-full input[type="time"], .wy-control-group .wy-form-full input[type="datetime"], .wy-control-group .wy-form-full input[type="datetime-local"], .wy-control-group .wy-form-full input[type="week"], .wy-control-group .wy-form-full input[type="number"], .wy-control-group .wy-form-full input[type="search"], .wy-control-group .wy-form-full input[type="tel"], .wy-control-group .wy-form-full input[type="color"], .wy-control-group .wy-form-halves input[type="text"], .wy-control-group .wy-form-halves input[type="password"], .wy-control-group .wy-form-halves input[type="email"], .wy-control-group .wy-form-halves input[type="url"], .wy-control-group .wy-form-halves input[type="date"], .wy-control-group .wy-form-halves input[type="month"], .wy-control-group .wy-form-halves input[type="time"], .wy-control-group .wy-form-halves input[type="datetime"], .wy-control-group .wy-form-halves input[type="datetime-local"], .wy-control-group .wy-form-halves input[type="week"], .wy-control-group .wy-form-halves input[type="number"], .wy-control-group .wy-form-halves input[type="search"], .wy-control-group .wy-form-halves input[type="tel"], .wy-control-group .wy-form-halves input[type="color"], .wy-control-group .wy-form-thirds input[type="text"], .wy-control-group .wy-form-thirds input[type="password"], .wy-control-group .wy-form-thirds input[type="email"], .wy-control-group .wy-form-thirds input[type="url"], .wy-control-group .wy-form-thirds input[type="date"], .wy-control-group .wy-form-thirds input[type="month"], .wy-control-group .wy-form-thirds input[type="time"], .wy-control-group .wy-form-thirds input[type="datetime"], .wy-control-group .wy-form-thirds input[type="datetime-local"], .wy-control-group .wy-form-thirds input[type="week"], .wy-control-group .wy-form-thirds input[type="number"], .wy-control-group .wy-form-thirds input[type="search"], .wy-control-group .wy-form-thirds input[type="tel"], .wy-control-group .wy-form-thirds input[type="color"] { width: 100%; } .wy-control-group .wy-form-full { float: left; display: block; margin-right: 2.3576515979%; width: 100%; margin-right: 0; } .wy-control-group .wy-form-full:last-child { margin-right: 0; } .wy-control-group .wy-form-halves { float: left; display: block; margin-right: 2.3576515979%; width: 48.821174201%; } .wy-control-group .wy-form-halves:last-child { margin-right: 0; } .wy-control-group .wy-form-halves:nth-of-type(2n) { margin-right: 0; } .wy-control-group .wy-form-halves:nth-of-type(2n + 1) { clear: left; } .wy-control-group .wy-form-thirds { float: left; display: block; margin-right: 2.3576515979%; width: 31.7615656014%; } .wy-control-group .wy-form-thirds:last-child { margin-right: 0; } .wy-control-group .wy-form-thirds:nth-of-type(3n) { margin-right: 0; } .wy-control-group .wy-form-thirds:nth-of-type(3n + 1) { clear: left; } .wy-control-group.wy-control-group-no-input .wy-control { margin: 6px 0 0 0; font-size: 90%; } .wy-control-no-input { display: inline-block; margin: 6px 0 0 0; font-size: 90%; } .wy-control-group.fluid-input input[type="text"], .wy-control-group.fluid-input input[type="password"], .wy-control-group.fluid-input input[type="email"], .wy-control-group.fluid-input input[type="url"], .wy-control-group.fluid-input input[type="date"], .wy-control-group.fluid-input input[type="month"], .wy-control-group.fluid-input input[type="time"], .wy-control-group.fluid-input input[type="datetime"], .wy-control-group.fluid-input input[type="datetime-local"], .wy-control-group.fluid-input input[type="week"], .wy-control-group.fluid-input input[type="number"], .wy-control-group.fluid-input input[type="search"], .wy-control-group.fluid-input input[type="tel"], .wy-control-group.fluid-input input[type="color"] { width: 100%; } .wy-form-message-inline { display: inline-block; padding-left: 0.3em; color: #666; vertical-align: middle; font-size: 90%; } .wy-form-message { display: block; color: #999; font-size: 70%; margin-top: 0.3125em; font-style: italic; } .wy-form-message p { font-size: inherit; font-style: italic; margin-bottom: 6px; } .wy-form-message p:last-child { margin-bottom: 0; } input { line-height: normal; } input[type="button"], input[type="reset"], input[type="submit"] { -webkit-appearance: button; cursor: pointer; font-family: "Roboto-Regular", sans-serif; *overflow: visible; } input[type="text"], input[type="password"], input[type="email"], input[type="url"], input[type="date"], input[type="month"], input[type="time"], input[type="datetime"], input[type="datetime-local"], input[type="week"], input[type="number"], input[type="search"], input[type="tel"], input[type="color"] { -webkit-appearance: none; padding: 6px; display: inline-block; border: 1px solid #ccc; font-size: 80%; font-family: "Roboto-Regular", sans-serif; box-shadow: inset 0 1px 3px #ddd; border-radius: 0; -webkit-transition: border 0.3s linear; -moz-transition: border 0.3s linear; transition: border 0.3s linear; } input[type="datetime-local"] { padding: 0.34375em 0.625em; } input[disabled] { cursor: default; } input[type="checkbox"], input[type="radio"] { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding: 0; margin-right: 0.3125em; *height: 13px; *width: 13px; } input[type="search"] { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } input[type="text"]:focus, input[type="password"]:focus, input[type="email"]:focus, input[type="url"]:focus, input[type="date"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="week"]:focus, input[type="number"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="color"]:focus { outline: 0; outline: thin dotted \9; border-color: #333; } input.no-focus:focus { border-color: #ccc !important; } input[type="file"]:focus, input[type="radio"]:focus, input[type="checkbox"]:focus { outline: thin dotted #333; outline: 1px auto #129fea; } input[type="text"][disabled], input[type="password"][disabled], input[type="email"][disabled], input[type="url"][disabled], input[type="date"][disabled], input[type="month"][disabled], input[type="time"][disabled], input[type="datetime"][disabled], input[type="datetime-local"][disabled], input[type="week"][disabled], input[type="number"][disabled], input[type="search"][disabled], input[type="tel"][disabled], input[type="color"][disabled] { cursor: not-allowed; background-color: #fafafa; } input:focus:invalid, textarea:focus:invalid, select:focus:invalid { color: #e74c3c; border: 1px solid #e74c3c; } input:focus:invalid:focus, textarea:focus:invalid:focus, select:focus:invalid:focus { border-color: #e74c3c; } input[type="file"]:focus:invalid:focus, input[type="radio"]:focus:invalid:focus, input[type="checkbox"]:focus:invalid:focus { outline-color: #e74c3c; } input.wy-input-large { padding: 12px; font-size: 100%; } textarea { overflow: auto; vertical-align: top; width: 100%; font-family: "Roboto-Regular", sans-serif; } select, textarea { padding: 0.5em 0.625em; display: inline-block; border: 1px solid #ccc; font-size: 80%; box-shadow: inset 0 1px 3px #ddd; -webkit-transition: border 0.3s linear; -moz-transition: border 0.3s linear; transition: border 0.3s linear; } select { border: 1px solid #ccc; background-color: #fff; } select[multiple] { height: auto; } select:focus, textarea:focus { outline: 0; } select[disabled], textarea[disabled], input[readonly], select[readonly], textarea[readonly] { cursor: not-allowed; background-color: #fafafa; } input[type="radio"][disabled], input[type="checkbox"][disabled] { cursor: not-allowed; } .wy-checkbox, .wy-radio { margin: 6px 0; color: #404040; display: block; } .wy-checkbox input, .wy-radio input { vertical-align: baseline; } .wy-form-message-inline { display: inline-block; *display: inline; *zoom: 1; vertical-align: middle; } .wy-input-prefix, .wy-input-suffix { white-space: nowrap; padding: 6px; } .wy-input-prefix .wy-input-context, .wy-input-suffix .wy-input-context { line-height: 27px; padding: 0 8px; display: inline-block; font-size: 80%; background: #fafafa; border: solid 1px #ccc; color: #999; } .wy-input-suffix .wy-input-context { border-left: 0; } .wy-input-prefix .wy-input-context { border-right: 0; } .wy-switch { position: relative; display: block; height: 24px; margin-top: 12px; cursor: pointer; } .wy-switch:before { position: absolute; content: ""; display: block; left: 0; top: 0; width: 36px; height: 12px; border-radius: 4px; background: #ccc; -webkit-transition: all 0.2s ease-in-out; -moz-transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out; } .wy-switch:after { position: absolute; content: ""; display: block; width: 18px; height: 18px; border-radius: 4px; background: #999; left: -3px; top: -3px; -webkit-transition: all 0.2s ease-in-out; -moz-transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out; } .wy-switch span { position: absolute; left: 48px; display: block; font-size: 12px; color: #ccc; line-height: 1; } .wy-switch.active:before { background: #1e8449; } .wy-switch.active:after { left: 24px; background: #27ae60; } .wy-switch.disabled { cursor: not-allowed; opacity: 0.8; } .wy-control-group.wy-control-group-error .wy-form-message, .wy-control-group.wy-control-group-error > label { color: #e74c3c; } .wy-control-group.wy-control-group-error input[type="text"], .wy-control-group.wy-control-group-error input[type="password"], .wy-control-group.wy-control-group-error input[type="email"], .wy-control-group.wy-control-group-error input[type="url"], .wy-control-group.wy-control-group-error input[type="date"], .wy-control-group.wy-control-group-error input[type="month"], .wy-control-group.wy-control-group-error input[type="time"], .wy-control-group.wy-control-group-error input[type="datetime"], .wy-control-group.wy-control-group-error input[type="datetime-local"], .wy-control-group.wy-control-group-error input[type="week"], .wy-control-group.wy-control-group-error input[type="number"], .wy-control-group.wy-control-group-error input[type="search"], .wy-control-group.wy-control-group-error input[type="tel"], .wy-control-group.wy-control-group-error input[type="color"] { border: solid 1px #e74c3c; } .wy-control-group.wy-control-group-error textarea { border: solid 1px #e74c3c; } .wy-inline-validate { white-space: nowrap; } .wy-inline-validate .wy-input-context { padding: 0.5em 0.625em; display: inline-block; font-size: 80%; } .wy-inline-validate.wy-inline-validate-success .wy-input-context { color: #27ae60; } .wy-inline-validate.wy-inline-validate-danger .wy-input-context { color: #e74c3c; } .wy-inline-validate.wy-inline-validate-warning .wy-input-context { color: #e67e22; } .wy-inline-validate.wy-inline-validate-info .wy-input-context { color: #2980b9; } .rotate-90 { -webkit-transform: rotate(90deg); -moz-transform: rotate(90deg); -ms-transform: rotate(90deg); -o-transform: rotate(90deg); transform: rotate(90deg); } .rotate-180 { -webkit-transform: rotate(180deg); -moz-transform: rotate(180deg); -ms-transform: rotate(180deg); -o-transform: rotate(180deg); transform: rotate(180deg); } .rotate-270 { -webkit-transform: rotate(270deg); -moz-transform: rotate(270deg); -ms-transform: rotate(270deg); -o-transform: rotate(270deg); transform: rotate(270deg); } .mirror { -webkit-transform: scaleX(-1); -moz-transform: scaleX(-1); -ms-transform: scaleX(-1); -o-transform: scaleX(-1); transform: scaleX(-1); } .mirror.rotate-90 { -webkit-transform: scaleX(-1) rotate(90deg); -moz-transform: scaleX(-1) rotate(90deg); -ms-transform: scaleX(-1) rotate(90deg); -o-transform: scaleX(-1) rotate(90deg); transform: scaleX(-1) rotate(90deg); } .mirror.rotate-180 { -webkit-transform: scaleX(-1) rotate(180deg); -moz-transform: scaleX(-1) rotate(180deg); -ms-transform: scaleX(-1) rotate(180deg); -o-transform: scaleX(-1) rotate(180deg); transform: scaleX(-1) rotate(180deg); } .mirror.rotate-270 { -webkit-transform: scaleX(-1) rotate(270deg); -moz-transform: scaleX(-1) rotate(270deg); -ms-transform: scaleX(-1) rotate(270deg); -o-transform: scaleX(-1) rotate(270deg); transform: scaleX(-1) rotate(270deg); } @media only screen and (max-width: 480px) { .wy-form button[type="submit"] { margin: 0.7em 0 0; } .wy-form input[type="text"], .wy-form input[type="password"], .wy-form input[type="email"], .wy-form input[type="url"], .wy-form input[type="date"], .wy-form input[type="month"], .wy-form input[type="time"], .wy-form input[type="datetime"], .wy-form input[type="datetime-local"], .wy-form input[type="week"], .wy-form input[type="number"], .wy-form input[type="search"], .wy-form input[type="tel"], .wy-form input[type="color"] { margin-bottom: 0.3em; display: block; } .wy-form label { margin-bottom: 0.3em; display: block; } .wy-form input[type="password"], .wy-form input[type="email"], .wy-form input[type="url"], .wy-form input[type="date"], .wy-form input[type="month"], .wy-form input[type="time"], .wy-form input[type="datetime"], .wy-form input[type="datetime-local"], .wy-form input[type="week"], .wy-form input[type="number"], .wy-form input[type="search"], .wy-form input[type="tel"], .wy-form input[type="color"] { margin-bottom: 0; } .wy-form-aligned .wy-control-group label { margin-bottom: 0.3em; text-align: left; display: block; width: 100%; } .wy-form-aligned .wy-control { margin: 1.5em 0 0 0; } .wy-form .wy-help-inline, .wy-form-message-inline, .wy-form-message { display: block; font-size: 80%; padding: 6px 0; } } @media screen and (max-width: 768px) { .tablet-hide { display: none; } } @media screen and (max-width: 480px) { .mobile-hide { display: none; } } .float-left { float: left; } .float-right { float: right; } .full-width { width: 100%; } .wy-table, .rst-content table.docutils, .rst-content table.field-list { border-collapse: collapse; border-spacing: 0; empty-cells: show; margin-bottom: 24px; } .wy-table caption, .rst-content table.docutils caption, .rst-content table.field-list caption { color: #000; font: italic 85%/1 arial, sans-serif; padding: 1em 0; text-align: center; } .wy-table td, .rst-content table.docutils td, .rst-content table.field-list td, .wy-table th, .rst-content table.docutils th, .rst-content table.field-list th { font-size: 14px; margin: 0; overflow: visible; /* padding: 8px 16px; */ } .wy-table-responsive .wy-table td:first-child, .rst-content table.docutils td:first-child, .rst-content table.field-list td:first-child, .wy-table th:first-child, .rst-content table.docutils th:first-child, .rst-content table.field-list th:first-child { border: none; } .wy-table thead, .rst-content table.docutils thead, .rst-content table.field-list thead { color: #000; text-align: left; vertical-align: bottom; white-space: nowrap; } .wy-table thead th, .rst-content table.docutils thead th, .rst-content table.field-list thead th { font-weight: bold; border-bottom: solid 2px #e1e4e5; } .wy-table td, .rst-content table.docutils td, .rst-content table.field-list td { background-color: transparent; vertical-align: middle; } .wy-table td p, .rst-content table.docutils td p, .rst-content table.field-list td p { line-height: 18px; } .wy-table td p:last-child, .rst-content table.docutils td p:last-child, .rst-content table.field-list td p:last-child { margin-bottom: 0; } .wy-table .wy-table-cell-min, .rst-content table.docutils .wy-table-cell-min, .rst-content table.field-list .wy-table-cell-min { width: 1%; padding-right: 0; } .wy-table .wy-table-cell-min input[type="checkbox"], .rst-content table.docutils .wy-table-cell-min input[type="checkbox"], .rst-content table.field-list .wy-table-cell-min input[type="checkbox"], .wy-table .wy-table-cell-min input[type="checkbox"], .rst-content table.docutils .wy-table-cell-min input[type="checkbox"], .rst-content table.field-list .wy-table-cell-min input[type="checkbox"] { margin: 0; } .wy-table-secondary { color: gray; font-size: 90%; } .wy-table-tertiary { color: gray; font-size: 80%; } .wy-table-odd td, .wy-table-striped tr:nth-child(2n-1) td, .rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td { border: none; } .wy-table-backed { background-color: #f3f6f6; } tr.row-odd { background: #fafafa; /* border: 1px solid #e6e6e6; */ border-radius: 8px; } .wy-table-bordered-all, .rst-content table.docutils { border: none; } .wy-table-bordered-all td, .rst-content table.docutils td { padding: 24px 16px; border: none; } .wy-table-bordered-all tbody > tr:last-child td, .rst-content table.docutils tbody > tr:last-child td { border-bottom-width: 0; } .wy-table-bordered { border: 1px solid #e1e4e5; } .wy-table-bordered-rows td { border-bottom: 1px solid #e1e4e5; } .wy-table-bordered-rows tbody > tr:last-child td { border-bottom-width: 0; } .wy-table-horizontal tbody > tr:last-child td { border-bottom-width: 0; } .wy-table-horizontal td, .wy-table-horizontal th { border-width: 0 0 1px 0; border-bottom: 1px solid #e1e4e5; } .wy-table-horizontal tbody > tr:last-child td { border-bottom-width: 0; } .wy-table-responsive { margin-bottom: 24px; max-width: 100%; overflow: auto; } .wy-table-responsive table { margin-bottom: 0 !important; } .wy-table-responsive table td, .wy-table-responsive table th { white-space: nowrap; } a { color: #0f6dee; text-decoration: none; cursor: pointer; } a:hover { color: #0f6dee; } html { height: 100%; overflow-x: hidden; } body { font-family: "Roboto-Regular", sans-serif; font-weight: normal; color: #404040; min-height: 100%; background: #edf0f2; } .wy-text-left { text-align: left; } .wy-text-center { text-align: center; } .wy-text-right { text-align: right; } .wy-text-large { font-size: 120%; } .wy-text-normal { font-size: 100%; } .wy-text-small, small { font-size: 80%; } .wy-text-strike { text-decoration: line-through; } .wy-text-warning { color: #e67e22 !important; } a.wy-text-warning:hover { color: #eb9950 !important; } .wy-text-info { color: #2980b9 !important; } a.wy-text-info:hover { color: #409ad5 !important; } .wy-text-success { color: #27ae60 !important; } a.wy-text-success:hover { color: #36d278 !important; } .wy-text-danger { color: #e74c3c !important; } a.wy-text-danger:hover { color: #ed7669 !important; } .wy-text-neutral { color: #404040 !important; } a.wy-text-neutral:hover { color: #595959 !important; } h1, h2, .rst-content .toctree-wrapper p.caption, h3, h4, h5, h6, legend { margin-top: 0; font-weight: normal; font-family: "Roboto-Regular", sans-serif; } p { line-height: 20px; margin: 0; font-size: 14px; margin-bottom: 24px; } h1 { font-family: "Roboto Mono", monospace; font-size: 34px; line-height: 54px; margin-bottom: 16px; font-weight: 700; } h2 { font-family: "Roboto Mono", monospace; font-size: 24px; line-height: 36px; font-weight: 700; } .rst-content .toctree-wrapper p.caption { font-family: Roboto-Medium; font-weight: 400; font-size: 20px; color: rgba(0, 0, 0, 0.87); line-height: 32px; margin-bottom: 8px; } h3 { font-size: 125%; } h4 { font-family: "Roboto Mono", monospace; font-size: 16px; line-height: 24px; color: rgba(0, 0, 0, 0.8); font-weight: 700; } h5 { font-size: 110%; } h6 { font-size: 100%; } hr { margin-left: 0; margin-right: 0; margin-top: 3rem; margin-bottom: 3rem; padding-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; background: hsla(0, 0%, 0%, 0.2); border: none; height: 1px; } code, .rst-content tt, .rst-content code { white-space: nowrap; max-width: 100%; background: #fafafa; border: 1px solid #e6e6e6; border-radius: 8px; /* font-size: 75%; */ padding: 0 5px; font-family: "Roboto Mono", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; color: #e74c3c; overflow-x: auto; } code.code-large, .rst-content tt.code-large { font-size: 90%; } .wy-plain-list-disc, .rst-content .section ul, .rst-content .toctree-wrapper ul, article ul { list-style: disc; font-size: 14px; line-height: 24px; margin-bottom: 24px; } .wy-plain-list-disc li, .rst-content .section ul li, .rst-content .toctree-wrapper ul li, article ul li { /* list-style: disc; margin-left: 24px; */ } .wy-plain-list-disc li p:last-child, .rst-content .section ul li p:last-child, .rst-content .toctree-wrapper ul li p:last-child, article ul li p:last-child { margin-bottom: 0; } .wy-plain-list-disc li ul, .rst-content .section ul li ul, .rst-content .toctree-wrapper ul li ul, article ul li ul { margin-bottom: 0; margin-left: 24px; } .wy-plain-list-disc li li, .rst-content .section ul li li, .rst-content .toctree-wrapper ul li li, article ul li li { list-style: none; } .wy-plain-list-disc li li li, .rst-content .section ul li li li, .rst-content .toctree-wrapper ul li li li, article ul li li li { list-style: none; } .wy-plain-list-disc li ol li, .rst-content .section ul li ol li, .rst-content .toctree-wrapper ul li ol li, article ul li ol li { list-style: decimal; } .wy-plain-list-decimal, .rst-content .section ol, .rst-content ol.arabic, article ol { list-style: decimal; line-height: 24px; margin-bottom: 24px; } .wy-plain-list-decimal li, .rst-content .section ol li, .rst-content ol.arabic li, article ol li { list-style: decimal; margin-left: 24px; } .wy-plain-list-decimal li p:last-child, .rst-content .section ol li p:last-child, .rst-content ol.arabic li p:last-child, article ol li p:last-child { margin-bottom: 0; } .wy-plain-list-decimal li ul, .rst-content .section ol li ul, .rst-content ol.arabic li ul, article ol li ul { margin-bottom: 0; } .wy-plain-list-decimal li ul li, .rst-content .section ol li ul li, .rst-content ol.arabic li ul li, article ol li ul li { list-style: disc; } .wy-breadcrumbs { *zoom: 1; font-size: 12px; text-transform: uppercase; color: rgba(0, 0, 0, 0.54); line-height: 16px; margin-bottom: 4px; font-weight: 700; } .wy-breadcrumbs:before, .wy-breadcrumbs:after { display: table; content: ""; } .wy-breadcrumbs:after { clear: both; } .wy-breadcrumbs li { display: inline-block; } .wy-breadcrumbs li.wy-breadcrumbs-aside { float: right; } .wy-breadcrumbs li a { display: inline-block; color: rgba(0, 0, 0, 0.54); } .wy-breadcrumbs li a:hover { display: inline-block; color: rgba(15, 109, 238, 0.87); text-decoration: underline; } .wy-breadcrumbs li a:first-child { padding-left: 0; } .wy-breadcrumbs li code, .wy-breadcrumbs li .rst-content tt, .rst-content .wy-breadcrumbs li tt { /* padding: 5px; */ border: none; background: none; } .wy-breadcrumbs li code.literal, .wy-breadcrumbs li .rst-content tt.literal, .rst-content .wy-breadcrumbs li tt.literal { color: #404040; } .wy-breadcrumbs-extra { margin-bottom: 0; color: #b3b3b3; font-size: 80%; display: inline-block; } @media screen and (max-width: 480px) { .wy-breadcrumbs-extra { display: none; } .wy-breadcrumbs li.wy-breadcrumbs-aside { display: none; } } @media print { .wy-breadcrumbs li.wy-breadcrumbs-aside { display: none; } } html { font: 112.5%/1.45em "Roboto-Regular"; -webkit-font-smoothing: antialiased; } .wy-affix { position: fixed; top: 1.618em; } .wy-menu a:hover { text-decoration: none; } .wy-menu-horiz { *zoom: 1; } .wy-menu-horiz:before, .wy-menu-horiz:after { display: table; content: ""; } .wy-menu-horiz:after { clear: both; } .wy-menu-horiz ul, .wy-menu-horiz li { display: inline-block; } .wy-menu-horiz li:hover { background: rgba(255, 255, 255, 0.1); } .wy-menu-horiz li.divide-left { border-left: solid 1px #404040; } .wy-menu-horiz li.divide-right { border-right: solid 1px #404040; } .wy-menu-horiz a { height: 32px; display: inline-block; line-height: 32px; padding: 0 16px; } .wy-menu-vertical { width: 100%; } .wy-menu-vertical header, .wy-menu-vertical p.caption { font-family: "Roboto-Regular", sans-serif; font-size: 12px; line-height: 16px; color: rgba(0, 0, 0, 0.54); text-transform: uppercase; margin-bottom: 4px; color: rgba(0, 0, 0, 0.54); height: 32px; display: inline-block; line-height: 32px; padding: 0 32px; margin: 48px 0 0 0; white-space: nowrap; font-weight: bold; } .wy-menu-vertical ul { margin-bottom: 48px; } .wy-menu-vertical li.divide-top { border-top: solid 1px #404040; } .wy-menu-vertical li.divide-bottom { border-bottom: solid 1px #404040; } .wy-menu-vertical li.current { /* background: #e3e3e3; */ } .wy-menu-vertical li.current a { /* color: gray; */ /* border-right: solid 1px #c9c9c9; */ padding: 0.4045em 2.427em; padding-left: 48px; } .wy-menu-vertical li.current a:hover { /* background: #d6d6d6; */ } .wy-menu-vertical li code, .wy-menu-vertical li .rst-content tt, .rst-content .wy-menu-vertical li tt { border: none; background: inherit; color: inherit; padding-left: 0; padding-right: 0; } .wy-menu-vertical li span.toctree-expand { display: block; float: left; margin-left: -1.2em; font-size: 0.8em; line-height: 1.6em; color: #4d4d4d; } .wy-menu-vertical li.on a, .wy-menu-vertical li.current > a { padding: 0.4045em 32px; font-weight: bold; position: relative; /* background: #fcfcfc; */ border: none; padding-left: 1.618em -4px; } .wy-menu-vertical li.on a:hover, .wy-menu-vertical li.current > a:hover { /* background: #fcfcfc; */ } .wy-menu-vertical li.on a:hover span.toctree-expand, .wy-menu-vertical li.current > a:hover span.toctree-expand { /* color: gray; */ } .wy-menu-vertical li.on a span.toctree-expand, .wy-menu-vertical li.current > a span.toctree-expand { display: block; font-size: 0.8em; line-height: 1.6em; /* color: #333; */ } .wy-menu-vertical li.toctree-l1.current > a { /* border-bottom: solid 1px #c9c9c9; */ /* border-top: solid 1px #c9c9c9; */ } .wy-menu-vertical li.toctree-l2 a, .wy-menu-vertical li.toctree-l3 a, .wy-menu-vertical li.toctree-l4 a { color: rgba(15, 109, 238, 0.87); } .wy-menu-vertical li.toctree-l1.current li.toctree-l2 > ul, .wy-menu-vertical li.toctree-l2.current li.toctree-l3 > ul { display: none; } .wy-menu-vertical li.toctree-l1.current li.toctree-l2.current > ul, .wy-menu-vertical li.toctree-l2.current li.toctree-l3.current > ul { display: block; } .wy-menu-vertical li.toctree-l2.current > a { /* background: #c9c9c9; */ padding: 0.4045em 2.427em; padding-left: 48px; } .wy-menu-vertical li.toctree-l2.current li.toctree-l3 > a { display: block; /* background: #c9c9c9; */ padding: 0.4045em 4.045em; } .wy-menu-vertical li.toctree-l2 a:hover span.toctree-expand { /* color: gray; */ } .wy-menu-vertical li.toctree-l2 span.toctree-expand { /* color: #a3a3a3; */ } .wy-menu-vertical li.toctree-l3 { font-size: 0.9em; } .wy-menu-vertical li.toctree-l3.current > a { /* background: #bdbdbd; */ padding: 0.4045em 32px; } .wy-menu-vertical li.toctree-l3.current li.toctree-l4 > a { display: block; background: #bdbdbd; padding: 0.4045em 5.663em; } .wy-menu-vertical li.toctree-l3 a:hover span.toctree-expand { color: gray; } .wy-menu-vertical li.toctree-l3 span.toctree-expand { color: #969696; } .wy-menu-vertical li.toctree-l4 { font-size: 0.9em; } .wy-menu-vertical li.current ul { display: block; } .wy-menu-vertical li ul { margin-bottom: 0; display: none; } .wy-menu-vertical li ul li a { margin-bottom: 0; /* color: #d9d9d9; */ font-weight: normal; } .wy-menu-vertical a { display: inline-block; line-height: 18px; padding: 0.4045em 32px; display: block; position: relative; font-size: 14px; color: rgba(15, 109, 238, 0.87); } .wy-menu-vertical a:hover { /* background-color: #4e4a4a; */ cursor: pointer; } .wy-menu-vertical a:hover span.toctree-expand { /* color: #d9d9d9; */ } .wy-menu-vertical a:active { /* background-color: #2980b9; */ cursor: pointer; /* color: #fff; */ } .wy-menu-vertical a:active span.toctree-expand { /* color: #fff; */ } .wy-side-nav-search { display: none; width: 100%; padding: 0.809em; margin-bottom: 0.809em; /* z-index: 200; */ background-color: #2980b9; text-align: center; padding: 0.809em; color: #fcfcfc; margin-bottom: 0.809em; } .wy-side-nav-search input[type="text"] { width: 100%; border-radius: 50px; padding: 6px 12px; border-color: #2472a4; } .wy-side-nav-search img { display: block; margin: auto auto 0.809em auto; height: 45px; width: 45px; background-color: #2980b9; padding: 5px; border-radius: 100%; } .wy-side-nav-search > a, .wy-side-nav-search .wy-dropdown > a { color: #fcfcfc; font-size: 100%; font-weight: bold; display: inline-block; padding: 4px 6px; margin-bottom: 0.809em; } .wy-side-nav-search > a:hover, .wy-side-nav-search .wy-dropdown > a:hover { background: rgba(255, 255, 255, 0.1); } .wy-side-nav-search > a img.logo, .wy-side-nav-search .wy-dropdown > a img.logo { display: block; margin: 0 auto; height: auto; width: auto; border-radius: 0; max-width: 100%; background: transparent; } .wy-side-nav-search > a.icon img.logo, .wy-side-nav-search .wy-dropdown > a.icon img.logo { margin-top: 0.85em; } .wy-side-nav-search > div.version { margin-top: -0.4045em; margin-bottom: 0.809em; font-weight: normal; color: rgba(255, 255, 255, 0.3); } .wy-nav .wy-menu-vertical header { color: #2980b9; } .wy-nav .wy-menu-vertical a { color: #b3b3b3; } .wy-nav .wy-menu-vertical a:hover { background-color: #2980b9; color: #fff; } [data-menu-wrap] { -webkit-transition: all 0.2s ease-in; -moz-transition: all 0.2s ease-in; transition: all 0.2s ease-in; position: absolute; opacity: 1; width: 100%; opacity: 0; } [data-menu-wrap].move-center { left: 0; right: auto; opacity: 1; } [data-menu-wrap].move-left { right: auto; left: -100%; opacity: 0; } [data-menu-wrap].move-right { right: -100%; left: auto; opacity: 0; } .wy-body-for-nav { background: #fff; } .wy-grid-for-nav { display: flex; width: 100%; height: 100%; min-height: 100vh; } .wy-nav-side { /* padding-bottom: 2em; */ width: 320px; /* overflow-x: hidden; overflow-y: hidden; */ min-height: 100%; height: 100vh; color: #9b9b9b; border-right: 0.5px solid lightgrey; background-color: #fafafa; z-index: 200; visibility: visible; /* margin-top: 111px; */ /* transform: translate3D(0, 0, 0); transition: ease all 0.5s; */ position: sticky; top: 0; } .wy-side-scroll { width: 320px; color: #9b9b9b; border-right: 0.5px solid lightgrey; background-color: #fafafa; position: relative; overflow-x: hidden; overflow-y: scroll; height: 100%; } .wy-nav-top { display: none; background: #2980b9; color: #fff; padding: 0.4045em 0.809em; position: relative; line-height: 50px; text-align: center; font-size: 100%; *zoom: 1; } .wy-nav-top:before, .wy-nav-top:after { display: table; content: ""; } .wy-nav-top:after { clear: both; } .wy-nav-top a { color: #fff; font-weight: bold; } .wy-nav-top img { margin-right: 12px; height: 45px; width: 45px; background-color: #2980b9; padding: 5px; border-radius: 100%; } .wy-nav-top i { font-size: 30px; float: left; cursor: pointer; padding-top: inherit; } .wy-nav-content-wrap { width: 100%; min-height: 100%; margin: 0px 32px; } .wy-nav-content { padding: 64px 0px; height: 100%; max-width: 1140px; margin: auto; } .wy-body-mask { position: fixed; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.2); display: none; z-index: 499; } .wy-body-mask.on { display: block; } footer { color: gray; } footer p { margin-bottom: 12px; } footer span.commit code, footer span.commit .rst-content tt, .rst-content footer span.commit tt { padding: 0px; font-family: "Roboto Mono", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; font-size: 1em; background: none; border: none; color: gray; } .rst-footer-buttons { display: flex; } .rst-footer-buttons:before, .rst-footer-buttons:after { width: 100%; } .rst-footer-buttons:before, .rst-footer-buttons:after { display: none; content: ""; } .rst-footer-buttons:after { clear: both; } .rst-breadcrumbs-buttons { margin-top: 12px; *zoom: 1; } .rst-breadcrumbs-buttons:before, .rst-breadcrumbs-buttons:after { display: none; content: ""; } .rst-breadcrumbs-buttons:after { clear: both; } #search-results .search li { margin-bottom: 24px; border-bottom: solid 1px #e1e4e5; padding-bottom: 24px; } #search-results .search li:first-child { border-top: solid 1px #e1e4e5; padding-top: 24px; } #search-results .search li a { font-size: 120%; margin-bottom: 12px; display: inline-block; } #search-results .context { color: gray; font-size: 90%; } .genindextable li > ul { margin-left: 24px; } /* GS Developer Action Button */ #gs-developer-action-button-container { -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -webkit-tap-highlight-color: transparent; user-select: none; /* transform: translate3D(0, 0, 0); transition: ease all 0.5s; */ z-index: 999; justify-content: center; align-items: center; position: fixed; display: flex; right: 32px; bottom: 32px; background: #0f6dee; color: white; width: 64px; height: 64px; border-radius: 50%; visibility: hidden; opacity: 0; box-shadow: 0 10px 20px 0 rgba(48, 48, 50, 0.19), 0 6px 6px 0 rgba(48, 48, 50, 0.23); } #gs-developer-action-button-container:active { transform: scale(0.95); background: #0a5cce; } #gs-nav-action { margin-top: 4px; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -webkit-tap-highlight-color: transparent; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } #gs-nav-action:before { -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -webkit-tap-highlight-color: transparent; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; content: url(../images/menu-open.svg); } #gs-nav-action.active:before { -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -webkit-tap-highlight-color: transparent; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; content: url(../images/menu-close.svg); } @media screen and (max-width: 80rem) { .wy-body-for-nav { background: #fff; } #gs-developer-action-button-container { /* transform: translate3D(0, 0, 0); transition: ease all 0.5s; */ opacity: 1; visibility: visible; } .wy-nav-top { display: block; } .wy-nav-side { /* transform: translate3D(0, 0, 0); transition: ease all 0.5s; */ visibility: hidden; max-width: 0px; height: 100vh; } .wy-nav-side.shift { /* transform: translate3D(0, 0, 0); transition: ease all 0.5s; */ visibility: visible; max-width: 320px; height: 100vh; } .wy-side-scroll { width: 320px; } .wy-side-nav-search { width: auto; } .wy-menu.wy-menu-vertical { width: auto; } .wy-nav-content-wrap { /* transform: translate3D(0, 0, 0); transition: ease all 0.5s; */ /* margin-left: 0; */ } .wy-nav-content-wrap .wy-nav-content { /* padding: 1.618em; */ } .wy-nav-content-wrap.shift { /* transform: translate3D(0, 0, 0); transition: ease all 0.5s; */ /* position: fixed; */ min-width: 100%; /* left: 85%; */ top: 0; height: 100%; overflow: hidden; } } @media screen and (min-width: 1100px) { .wy-nav-content-wrap { background: #fff; } .wy-nav-content { margin: 0 auto; /* background: #fcfcfc; */ } } @media print { .rst-versions, footer, .wy-nav-side { display: none; } .wy-nav-content-wrap { margin-left: 0; } } .rst-versions { position: fixed; bottom: 0; left: 0; width: 300px; color: #fcfcfc; background: #1f1d1d; font-family: "Roboto-Regular", sans-serif; z-index: 400; } .rst-versions a { color: #2980b9; text-decoration: none; } .rst-versions .rst-badge-small { display: none; } .rst-versions .rst-current-version { padding: 12px; background-color: #272525; display: block; text-align: right; font-size: 90%; cursor: pointer; color: #27ae60; *zoom: 1; } .rst-versions .rst-current-version:before, .rst-versions .rst-current-version:after { display: table; content: ""; } .rst-versions .rst-current-version:after { clear: both; } .rst-versions .rst-current-version .fa, .rst-versions .rst-current-version .wy-menu-vertical li span.toctree-expand, .wy-menu-vertical li .rst-versions .rst-current-version span.toctree-expand, .rst-versions .rst-current-version .rst-content .admonition-title, .rst-content .rst-versions .rst-current-version .admonition-title, .rst-versions .rst-current-version .rst-content h1 .headerlink, .rst-content h1 .rst-versions .rst-current-version .headerlink, .rst-versions .rst-current-version .rst-content h2 .headerlink, .rst-content h2 .rst-versions .rst-current-version .headerlink, .rst-versions .rst-current-version .rst-content h3 .headerlink, .rst-content h3 .rst-versions .rst-current-version .headerlink, .rst-versions .rst-current-version .rst-content h4 .headerlink, .rst-content h4 .rst-versions .rst-current-version .headerlink, .rst-versions .rst-current-version .rst-content h5 .headerlink, .rst-content h5 .rst-versions .rst-current-version .headerlink, .rst-versions .rst-current-version .rst-content h6 .headerlink, .rst-content h6 .rst-versions .rst-current-version .headerlink, .rst-versions .rst-current-version .rst-content dl dt .headerlink, .rst-content dl dt .rst-versions .rst-current-version .headerlink, .rst-versions .rst-current-version .rst-content p.caption .headerlink, .rst-content p.caption .rst-versions .rst-current-version .headerlink, .rst-versions .rst-current-version .rst-content table > caption .headerlink, .rst-content table > caption .rst-versions .rst-current-version .headerlink, .rst-versions .rst-current-version .rst-content .code-block-caption .headerlink, .rst-content .code-block-caption .rst-versions .rst-current-version .headerlink, .rst-versions .rst-current-version .rst-content tt.download span:first-child, .rst-content tt.download .rst-versions .rst-current-version span:first-child, .rst-versions .rst-current-version .rst-content code.download span:first-child, .rst-content code.download .rst-versions .rst-current-version span:first-child, .rst-versions .rst-current-version .icon { color: #fcfcfc; } .rst-versions .rst-current-version .fa-book, .rst-versions .rst-current-version .icon-book { float: left; } .rst-versions .rst-current-version .icon-book { float: left; } .rst-versions .rst-current-version.rst-out-of-date { background-color: #e74c3c; color: #fff; } .rst-versions .rst-current-version.rst-active-old-version { background-color: #f1c40f; color: #000; } .rst-versions.shift-up { height: auto; max-height: 100%; overflow-y: scroll; } .rst-versions.shift-up .rst-other-versions { display: block; } .rst-versions .rst-other-versions { font-size: 90%; padding: 12px; color: gray; display: none; } .rst-versions .rst-other-versions hr { display: block; height: 1px; border: 0; margin: 20px 0; padding: 0; border-top: solid 1px #413d3d; } .rst-versions .rst-other-versions dd { display: inline-block; margin: 0; } .rst-versions .rst-other-versions dd a { display: inline-block; padding: 6px; color: #fcfcfc; } .rst-versions.rst-badge { width: auto; bottom: 20px; right: 20px; left: auto; border: none; max-width: 300px; max-height: 90%; } .rst-versions.rst-badge .icon-book { float: none; } .rst-versions.rst-badge .fa-book, .rst-versions.rst-badge .icon-book { float: none; } .rst-versions.rst-badge.shift-up .rst-current-version { text-align: right; } .rst-versions.rst-badge.shift-up .rst-current-version .fa-book, .rst-versions.rst-badge.shift-up .rst-current-version .icon-book { float: left; } .rst-versions.rst-badge.shift-up .rst-current-version .icon-book { float: left; } .rst-versions.rst-badge .rst-current-version { width: auto; height: 30px; line-height: 30px; padding: 0 6px; display: block; text-align: center; } @media screen and (max-width: 768px) { .rst-versions { width: 85%; display: none; } .rst-versions.shift { display: block; } } .rst-content img { max-width: 100%; height: auto; } .rst-content div.figure { margin-bottom: 24px; } .rst-content div.figure p.caption { font-style: italic; } .rst-content div.figure p:last-child.caption { margin-bottom: 0px; } .rst-content div.figure.align-center { text-align: center; } .rst-content .section > img, .rst-content .section > a > img { margin-bottom: 24px; } .rst-content abbr[title] { text-decoration: none; } .rst-content.style-external-links a.reference.external:after { content: ""; color: #b3b3b3; vertical-align: super; font-size: 60%; margin: 0 0.2em; } .rst-content blockquote { margin-left: 24px; line-height: 24px; margin-bottom: 24px; } .rst-content pre.literal-block { white-space: pre; margin: 0; padding: 12px 12px; font-family: "Roboto Mono", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; display: block; overflow: auto; } .rst-content pre.literal-block, .rst-content div[class^="highlight"] { font-family: "Roboto Mono"; display: flex; flex-wrap: wrap; margin: 16px 0; font-size: 90%; line-height: normal; background: #e7f2fa; color: #2980b9; padding: 16px 12px; position: relative; background: #fafafa; border: 1px solid #e6e6e6; border-radius: 8px; } .rst-content pre.literal-block div[class^="highlight"], .rst-content div[class^="highlight"] div[class^="highlight"] { padding: 0px; border: none; margin: 0; } .rst-content div[class^="highlight"] td.code { width: 100%; } .rst-content .linenodiv pre { border-right: solid 1px #e6e9ea; margin: 0; padding: 12px 12px; font-family: "Roboto Mono", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; user-select: none; pointer-events: none; } .rst-content div[class^="highlight"] pre { white-space: pre; margin: 0; /* padding: 12px 12px; */ display: block; overflow: auto; } .rst-content div[class^="highlight"] pre .hll { display: block; margin: 0 -12px; padding: 0 12px; } .rst-content pre.literal-block, .rst-content div[class^="highlight"] pre, .rst-content .linenodiv pre { font-family: "Roboto Mono", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; font-size: 14px; line-height: 1.5; } .rst-content .code-block-caption { font-style: italic; line-height: 1; padding: 1em 0; text-align: center; } @media print { .rst-content .codeblock, .rst-content div[class^="highlight"], .rst-content div[class^="highlight"] pre { white-space: pre-wrap; } } .rst-content .note .last, .rst-content .attention .last, .rst-content .caution .last, .rst-content .danger .last, .rst-content .error .last, .rst-content .hint .last, .rst-content .important .last, .rst-content .tip .last, .rst-content .warning .last, .rst-content .seealso .last, .rst-content .admonition-todo .last, .rst-content .admonition .last { margin-bottom: 0; } .rst-content .admonition-title:before { margin-right: 4px; } .rst-content .admonition table { border-color: rgba(0, 0, 0, 0.1); } .rst-content .admonition table td, .rst-content .admonition table th { background: transparent !important; border-color: rgba(0, 0, 0, 0.1) !important; } .rst-content .section ol.loweralpha, .rst-content .section ol.loweralpha li { list-style: lower-alpha; } .rst-content .section ol.upperalpha, .rst-content .section ol.upperalpha li { list-style: upper-alpha; } .rst-content .section ol p, .rst-content .section ul p { margin-bottom: 12px; } .rst-content .section ol p:last-child, .rst-content .section ul p:last-child { margin-bottom: 24px; } .rst-content .line-block { margin-left: 0px; margin-bottom: 24px; line-height: 24px; } .rst-content .line-block .line-block { margin-left: 24px; margin-bottom: 0px; } .rst-content .topic-title { font-weight: bold; margin-bottom: 12px; } .rst-content .toc-backref { color: #404040; } .rst-content .align-right { float: right; margin: 0px 0px 24px 24px; } .rst-content .align-left { float: left; margin: 0px 24px 24px 0px; } .rst-content .align-center { margin: auto; } .rst-content .align-center:not(table) { display: block; } .rst-content h1 .headerlink, .rst-content h2 .headerlink, .rst-content .toctree-wrapper p.caption .headerlink, .rst-content h3 .headerlink, .rst-content h4 .headerlink, .rst-content h5 .headerlink, .rst-content h6 .headerlink, .rst-content dl dt .headerlink, .rst-content p.caption .headerlink, .rst-content table > caption .headerlink, .rst-content .code-block-caption .headerlink { visibility: hidden; font-size: 14px; } .rst-content h1 .headerlink:after, .rst-content h2 .headerlink:after, .rst-content .toctree-wrapper p.caption .headerlink:after, .rst-content h3 .headerlink:after, .rst-content h4 .headerlink:after, .rst-content h5 .headerlink:after, .rst-content h6 .headerlink:after, .rst-content dl dt .headerlink:after, .rst-content p.caption .headerlink:after, .rst-content table > caption .headerlink:after, .rst-content .code-block-caption .headerlink:after { content: url(../images/gs-link.svg); position: relative; top: 2px; } .rst-content h1:hover .headerlink:after, .rst-content h2:hover .headerlink:after, .rst-content .toctree-wrapper p.caption:hover .headerlink:after, .rst-content h3:hover .headerlink:after, .rst-content h4:hover .headerlink:after, .rst-content h5:hover .headerlink:after, .rst-content h6:hover .headerlink:after, .rst-content dl dt:hover .headerlink:after, .rst-content p.caption:hover .headerlink:after, .rst-content table > caption:hover .headerlink:after, .rst-content .code-block-caption:hover .headerlink:after { visibility: visible; } .rst-content table > caption .headerlink:after { font-size: 12px; } .rst-content .centered { text-align: center; } .rst-content .sidebar { float: right; width: 40%; display: block; margin: 0 0 24px 24px; padding: 24px; background: #f3f6f6; border: solid 1px #e1e4e5; } .rst-content .sidebar p, .rst-content .sidebar ul, .rst-content .sidebar dl { font-size: 90%; } .rst-content .sidebar .last { margin-bottom: 0; } .rst-content .sidebar .sidebar-title { display: block; font-family: "Roboto-Regular", sans-serif; font-weight: bold; background: #e1e4e5; padding: 6px 12px; margin: -24px; margin-bottom: 24px; font-size: 100%; } .rst-content .highlighted { background: #f1c40f; display: inline-block; font-weight: bold; padding: 0 6px; } .rst-content .footnote-reference, .rst-content .citation-reference { vertical-align: baseline; position: relative; top: -0.4em; line-height: 0; font-size: 90%; } .rst-content table.docutils.citation, .rst-content table.docutils.footnote { background: none; border: none; color: gray; } .rst-content table.docutils.citation td, .rst-content table.docutils.citation tr, .rst-content table.docutils.footnote td, .rst-content table.docutils.footnote tr { border: none; background-color: transparent !important; white-space: normal; } .rst-content table.docutils.citation td.label, .rst-content table.docutils.footnote td.label { padding-left: 0; padding-right: 0; vertical-align: top; } .rst-content table.docutils.citation tt, .rst-content table.docutils.citation code, .rst-content table.docutils.footnote tt, .rst-content table.docutils.footnote code { color: #555; } .rst-content .wy-table-responsive.citation, .rst-content .wy-table-responsive.footnote { margin-bottom: 0; } .rst-content .wy-table-responsive.citation + :not(.citation), .rst-content .wy-table-responsive.footnote + :not(.footnote) { margin-top: 24px; } .rst-content .wy-table-responsive.citation:last-child, .rst-content .wy-table-responsive.footnote:last-child { margin-bottom: 24px; } .rst-content table.docutils th { border-color: #e1e4e5; } .rst-content table.docutils td .last, .rst-content table.docutils td .last :last-child { margin-bottom: 0; } .rst-content table.field-list { border: none; } .rst-content table.field-list td { border: none; } .rst-content table.field-list td p { font-size: inherit; line-height: inherit; } .rst-content table.field-list td > strong { display: inline-block; } .rst-content table.field-list .field-name { padding-right: 10px; text-align: left; white-space: nowrap; } .rst-content table.field-list .field-body { text-align: left; } .rst-content tt, .rst-content tt, .rst-content code { color: #000; font-family: "Roboto Mono", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; padding: 2px 5px; } .rst-content tt big, .rst-content tt em, .rst-content tt big, .rst-content code big, .rst-content tt em, .rst-content code em { font-size: 100% !important; line-height: normal; } .rst-content tt.literal, .rst-content tt.literal, .rst-content code.literal { color: #e74c3c; } .rst-content tt.xref, a .rst-content tt, .rst-content tt.xref, .rst-content code.xref, a .rst-content tt, a .rst-content code { font-weight: bold; color: #404040; } .rst-content pre, .rst-content kbd, .rst-content samp { font-family: "Roboto Mono", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; } .rst-content a tt, .rst-content a tt, .rst-content a code { color: #2980b9; } .rst-content dl { margin-bottom: 24px; } .rst-content dl dt { font-weight: normal; margin-bottom: 12px; } .rst-content dl p, .rst-content dl table, .rst-content dl ul, .rst-content dl ol { margin-bottom: 12px !important; } .rst-content dl dd { margin: 0px; line-height: 24px; } .rst-content dl:not(.docutils) { margin-bottom: 56px; } .rst-content dl:not(.docutils) dt { font-family: "Roboto Mono"; display: flex; flex-wrap: wrap; margin: 16px 0; font-size: 14px; background: #e7f2fa; color: #0f6dee; padding: 16px 12px; position: relative; background: #fafafa; border: 1px solid #e6e6e6; border-radius: 8px; } .rst-content dl:not(.docutils) dt:before { color: #6ab0de; } .rst-content dl:not(.docutils) dt .headerlink { color: #404040; font-size: 100% !important; } .rst-content dl:not(.docutils) dl dt { font-family: "Roboto Mono"; display: flex; flex-wrap: wrap; align-items: center; margin: 16px 0; font-size: 14px; line-height: normal; background: #e7f2fa; color: #2980b9; padding: 16px 12px; position: relative; background: #fafafa; border: 1px solid #e6e6e6; border-radius: 8px; } .rst-content dl:not(.docutils) dl dt .headerlink { color: #404040; font-size: 100% !important; } .rst-content dl:not(.docutils) dt:first-child { margin-top: 0; } .rst-content dl:not(.docutils) tt, .rst-content dl:not(.docutils) tt, .rst-content dl:not(.docutils) code { font-weight: bold; } .rst-content dl:not(.docutils) tt.descname, .rst-content dl:not(.docutils) tt.descclassname, .rst-content dl:not(.docutils) tt.descname, .rst-content dl:not(.docutils) code.descname, .rst-content dl:not(.docutils) tt.descclassname, .rst-content dl:not(.docutils) code.descclassname { background-color: transparent; border: none; padding: 0; font-size: 100% !important; } .rst-content dl:not(.docutils) tt.descname, .rst-content dl:not(.docutils) tt.descname, .rst-content dl:not(.docutils) code.descname { font-weight: bold; } .rst-content dl:not(.docutils) .optional { display: inline-block; padding: 0 4px; color: #000; font-weight: bold; } .rst-content dl:not(.docutils) .property { display: inline-block; padding-right: 8px; } .rst-content .viewcode-link, .rst-content .viewcode-back { display: inline-block; color: #27ae60; padding-left: 24px; } .rst-content .viewcode-back { display: block; float: right; } .rst-content p.rubric { font-family: "Roboto Mono", monospace; font-size: 24px; line-height: 36px; margin-bottom: 12px; margin-top: 48px; font-weight: bold; } .rst-content tt.download, .rst-content code.download { background: inherit; padding: inherit; font-weight: normal; font-family: inherit; font-size: inherit; color: inherit; border: inherit; white-space: inherit; } .rst-content tt.download span:first-child, .rst-content code.download span:first-child { -webkit-font-smoothing: subpixel-antialiased; } .rst-content tt.download span:first-child:before, .rst-content code.download span:first-child:before { margin-right: 4px; } .rst-content .guilabel { border: 1px solid #7fbbe3; background: #e7f2fa; font-size: 80%; font-weight: 700; border-radius: 4px; padding: 2.4px 6px; margin: auto 2px; } .rst-content .versionmodified { font-style: italic; } @media screen and (max-width: 480px) { .rst-content .sidebar { width: 100%; } } span[id*="MathJax-Span"] { color: #404040; } .math { text-align: center; } em { font-style: normal; } /* GS Developer Overrides */ td.field-body { padding: 0 !important; } /* GS Developer Global */ #gs-developer-global { background: #0c1521; position: fixed; height: auto; width: 100%; z-index: 1000; } #gs-developer-global p { color: white; font-size: 24px; margin: 0; } /* GS Developer Components */ .container-block-module { display: flex; flex-direction: row; flex-wrap: wrap; margin: -16px; } .link-block-module { display: flex; flex-direction: row; background-color: #fafafa; flex-basis: 240px; cursor: pointer; flex-grow: 1; margin: 16px; } .link-block-module-container { display: flex; color: unset; } .link-block-module-content { display: flex; flex-direction: column; justify-content: center; margin: 24px; width: 100%; } .link-block-module-title-label { margin: 0; margin-bottom: 4px; } .link-block-module-body-label { margin: 0; margin-bottom: 0; color: rgba(0, 0, 0, 0.8); } i { font-family: "Material-Icons"; } ================================================ FILE: docs/_build/html/_static/doctools.js ================================================ /* * doctools.js * ~~~~~~~~~~~ * * Sphinx JavaScript utilities for all documentation. * * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ /** * select a different prefix for underscore */ $u = _.noConflict(); /** * make the code below compatible with browsers without * an installed firebug like debugger if (!window.console || !console.firebug) { var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"]; window.console = {}; for (var i = 0; i < names.length; ++i) window.console[names[i]] = function() {}; } */ /** * small helper function to urldecode strings */ jQuery.urldecode = function(x) { return decodeURIComponent(x).replace(/\+/g, ' '); }; /** * small helper function to urlencode strings */ jQuery.urlencode = encodeURIComponent; /** * This function returns the parsed url parameters of the * current request. Multiple values per key are supported, * it will always return arrays of strings for the value parts. */ jQuery.getQueryParameters = function(s) { if (typeof s === 'undefined') s = document.location.search; var parts = s.substr(s.indexOf('?') + 1).split('&'); var result = {}; for (var i = 0; i < parts.length; i++) { var tmp = parts[i].split('=', 2); var key = jQuery.urldecode(tmp[0]); var value = jQuery.urldecode(tmp[1]); if (key in result) result[key].push(value); else result[key] = [value]; } return result; }; /** * highlight a given string on a jquery object by wrapping it in * span elements with the given class name. */ jQuery.fn.highlightText = function(text, className) { function highlight(node, addItems) { if (node.nodeType === 3) { var val = node.nodeValue; var pos = val.toLowerCase().indexOf(text); if (pos >= 0 && !jQuery(node.parentNode).hasClass(className) && !jQuery(node.parentNode).hasClass("nohighlight")) { var span; var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); if (isInSVG) { span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); } else { span = document.createElement("span"); span.className = className; } span.appendChild(document.createTextNode(val.substr(pos, text.length))); node.parentNode.insertBefore(span, node.parentNode.insertBefore( document.createTextNode(val.substr(pos + text.length)), node.nextSibling)); node.nodeValue = val.substr(0, pos); if (isInSVG) { var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); var bbox = node.parentElement.getBBox(); rect.x.baseVal.value = bbox.x; rect.y.baseVal.value = bbox.y; rect.width.baseVal.value = bbox.width; rect.height.baseVal.value = bbox.height; rect.setAttribute('class', className); addItems.push({ "parent": node.parentNode, "target": rect}); } } } else if (!jQuery(node).is("button, select, textarea")) { jQuery.each(node.childNodes, function() { highlight(this, addItems); }); } } var addItems = []; var result = this.each(function() { highlight(this, addItems); }); for (var i = 0; i < addItems.length; ++i) { jQuery(addItems[i].parent).before(addItems[i].target); } return result; }; /* * backward compatibility for jQuery.browser * This will be supported until firefox bug is fixed. */ if (!jQuery.browser) { jQuery.uaMatch = function(ua) { ua = ua.toLowerCase(); var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || /(webkit)[ \/]([\w.]+)/.exec(ua) || /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || /(msie) ([\w.]+)/.exec(ua) || ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || []; return { browser: match[ 1 ] || "", version: match[ 2 ] || "0" }; }; jQuery.browser = {}; jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; } /** * Small JavaScript module for the documentation. */ var Documentation = { init : function() { this.fixFirefoxAnchorBug(); this.highlightSearchWords(); this.initIndexTable(); if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) { this.initOnKeyListeners(); } }, /** * i18n support */ TRANSLATIONS : {}, PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; }, LOCALE : 'unknown', // gettext and ngettext don't access this so that the functions // can safely bound to a different name (_ = Documentation.gettext) gettext : function(string) { var translated = Documentation.TRANSLATIONS[string]; if (typeof translated === 'undefined') return string; return (typeof translated === 'string') ? translated : translated[0]; }, ngettext : function(singular, plural, n) { var translated = Documentation.TRANSLATIONS[singular]; if (typeof translated === 'undefined') return (n == 1) ? singular : plural; return translated[Documentation.PLURALEXPR(n)]; }, addTranslations : function(catalog) { for (var key in catalog.messages) this.TRANSLATIONS[key] = catalog.messages[key]; this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); this.LOCALE = catalog.locale; }, /** * add context elements like header anchor links */ addContextElements : function() { $('div[id] > :header:first').each(function() { $('\u00B6'). attr('href', '#' + this.id). attr('title', _('Permalink to this headline')). appendTo(this); }); $('dt[id]').each(function() { $('\u00B6'). attr('href', '#' + this.id). attr('title', _('Permalink to this definition')). appendTo(this); }); }, /** * workaround a firefox stupidity * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 */ fixFirefoxAnchorBug : function() { if (document.location.hash && $.browser.mozilla) window.setTimeout(function() { document.location.href += ''; }, 10); }, /** * highlight the search words provided in the url in the text */ highlightSearchWords : function() { var params = $.getQueryParameters(); var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; if (terms.length) { var body = $('div.body'); if (!body.length) { body = $('body'); } window.setTimeout(function() { $.each(terms, function() { body.highlightText(this.toLowerCase(), 'highlighted'); }); }, 10); $('') .appendTo($('#searchbox')); } }, /** * init the domain index toggle buttons */ initIndexTable : function() { var togglers = $('img.toggler').click(function() { var src = $(this).attr('src'); var idnum = $(this).attr('id').substr(7); $('tr.cg-' + idnum).toggle(); if (src.substr(-9) === 'minus.png') $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); else $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); }).css('display', ''); if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { togglers.click(); } }, /** * helper function to hide the search marks again */ hideSearchWords : function() { $('#searchbox .highlight-link').fadeOut(300); $('span.highlighted').removeClass('highlighted'); }, /** * make the url absolute */ makeURL : function(relativeURL) { return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; }, /** * get the current relative url */ getCurrentURL : function() { var path = document.location.pathname; var parts = path.split(/\//); $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { if (this === '..') parts.pop(); }); var url = parts.join('/'); return path.substring(url.lastIndexOf('/') + 1, path.length - 1); }, initOnKeyListeners: function() { $(document).keydown(function(event) { var activeElementType = document.activeElement.tagName; // don't navigate when in search box, textarea, dropdown or button if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT' && activeElementType !== 'BUTTON' && !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) { switch (event.keyCode) { case 37: // left var prevHref = $('link[rel="prev"]').prop('href'); if (prevHref) { window.location.href = prevHref; return false; } case 39: // right var nextHref = $('link[rel="next"]').prop('href'); if (nextHref) { window.location.href = nextHref; return false; } } } }); } }; // quick alias for translations _ = Documentation.gettext; $(document).ready(function() { Documentation.init(); }); ================================================ FILE: docs/_build/html/_static/documentation_options.js ================================================ var DOCUMENTATION_OPTIONS = { URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), VERSION: '0.1', LANGUAGE: 'None', COLLAPSE_INDEX: false, BUILDER: 'html', FILE_SUFFIX: '.html', LINK_SUFFIX: '.html', HAS_SOURCE: true, SOURCELINK_SUFFIX: '.txt', NAVIGATION_WITH_KEYS: false }; ================================================ FILE: docs/_build/html/_static/jquery-3.5.1.js ================================================ /*! * jQuery JavaScript Library v3.5.1 * https://jquery.com/ * * Includes Sizzle.js * https://sizzlejs.com/ * * Copyright JS Foundation and other contributors * Released under the MIT license * https://jquery.org/license * * Date: 2020-05-04T22:49Z */ ( function( global, factory ) { "use strict"; if ( typeof module === "object" && typeof module.exports === "object" ) { // For CommonJS and CommonJS-like environments where a proper `window` // is present, execute the factory and get jQuery. // For environments that do not have a `window` with a `document` // (such as Node.js), expose a factory as module.exports. // This accentuates the need for the creation of a real `window`. // e.g. var jQuery = require("jquery")(window); // See ticket #14549 for more info. module.exports = global.document ? factory( global, true ) : function( w ) { if ( !w.document ) { throw new Error( "jQuery requires a window with a document" ); } return factory( w ); }; } else { factory( global ); } // Pass this if window is not defined yet } )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { // Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 // throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode // arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common // enough that all such attempts are guarded in a try block. "use strict"; var arr = []; var getProto = Object.getPrototypeOf; var slice = arr.slice; var flat = arr.flat ? function( array ) { return arr.flat.call( array ); } : function( array ) { return arr.concat.apply( [], array ); }; var push = arr.push; var indexOf = arr.indexOf; var class2type = {}; var toString = class2type.toString; var hasOwn = class2type.hasOwnProperty; var fnToString = hasOwn.toString; var ObjectFunctionString = fnToString.call( Object ); var support = {}; var isFunction = function isFunction( obj ) { // Support: Chrome <=57, Firefox <=52 // In some browsers, typeof returns "function" for HTML elements // (i.e., `typeof document.createElement( "object" ) === "function"`). // We don't want to classify *any* DOM node as a function. return typeof obj === "function" && typeof obj.nodeType !== "number"; }; var isWindow = function isWindow( obj ) { return obj != null && obj === obj.window; }; var document = window.document; var preservedScriptAttributes = { type: true, src: true, nonce: true, noModule: true }; function DOMEval( code, node, doc ) { doc = doc || document; var i, val, script = doc.createElement( "script" ); script.text = code; if ( node ) { for ( i in preservedScriptAttributes ) { // Support: Firefox 64+, Edge 18+ // Some browsers don't support the "nonce" property on scripts. // On the other hand, just using `getAttribute` is not enough as // the `nonce` attribute is reset to an empty string whenever it // becomes browsing-context connected. // See https://github.com/whatwg/html/issues/2369 // See https://html.spec.whatwg.org/#nonce-attributes // The `node.getAttribute` check was added for the sake of // `jQuery.globalEval` so that it can fake a nonce-containing node // via an object. val = node[ i ] || node.getAttribute && node.getAttribute( i ); if ( val ) { script.setAttribute( i, val ); } } } doc.head.appendChild( script ).parentNode.removeChild( script ); } function toType( obj ) { if ( obj == null ) { return obj + ""; } // Support: Android <=2.3 only (functionish RegExp) return typeof obj === "object" || typeof obj === "function" ? class2type[ toString.call( obj ) ] || "object" : typeof obj; } /* global Symbol */ // Defining this global in .eslintrc.json would create a danger of using the global // unguarded in another place, it seems safer to define global only for this module var version = "3.5.1", // Define a local copy of jQuery jQuery = function( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced' // Need init if jQuery is called (just allow error to be thrown if not included) return new jQuery.fn.init( selector, context ); }; jQuery.fn = jQuery.prototype = { // The current version of jQuery being used jquery: version, constructor: jQuery, // The default length of a jQuery object is 0 length: 0, toArray: function() { return slice.call( this ); }, // Get the Nth element in the matched element set OR // Get the whole matched element set as a clean array get: function( num ) { // Return all the elements in a clean array if ( num == null ) { return slice.call( this ); } // Return just the one element from the set return num < 0 ? this[ num + this.length ] : this[ num ]; }, // Take an array of elements and push it onto the stack // (returning the new matched element set) pushStack: function( elems ) { // Build a new jQuery matched element set var ret = jQuery.merge( this.constructor(), elems ); // Add the old object onto the stack (as a reference) ret.prevObject = this; // Return the newly-formed element set return ret; }, // Execute a callback for every element in the matched set. each: function( callback ) { return jQuery.each( this, callback ); }, map: function( callback ) { return this.pushStack( jQuery.map( this, function( elem, i ) { return callback.call( elem, i, elem ); } ) ); }, slice: function() { return this.pushStack( slice.apply( this, arguments ) ); }, first: function() { return this.eq( 0 ); }, last: function() { return this.eq( -1 ); }, even: function() { return this.pushStack( jQuery.grep( this, function( _elem, i ) { return ( i + 1 ) % 2; } ) ); }, odd: function() { return this.pushStack( jQuery.grep( this, function( _elem, i ) { return i % 2; } ) ); }, eq: function( i ) { var len = this.length, j = +i + ( i < 0 ? len : 0 ); return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); }, end: function() { return this.prevObject || this.constructor(); }, // For internal use only. // Behaves like an Array's method, not like a jQuery method. push: push, sort: arr.sort, splice: arr.splice }; jQuery.extend = jQuery.fn.extend = function() { var options, name, src, copy, copyIsArray, clone, target = arguments[ 0 ] || {}, i = 1, length = arguments.length, deep = false; // Handle a deep copy situation if ( typeof target === "boolean" ) { deep = target; // Skip the boolean and the target target = arguments[ i ] || {}; i++; } // Handle case when target is a string or something (possible in deep copy) if ( typeof target !== "object" && !isFunction( target ) ) { target = {}; } // Extend jQuery itself if only one argument is passed if ( i === length ) { target = this; i--; } for ( ; i < length; i++ ) { // Only deal with non-null/undefined values if ( ( options = arguments[ i ] ) != null ) { // Extend the base object for ( name in options ) { copy = options[ name ]; // Prevent Object.prototype pollution // Prevent never-ending loop if ( name === "__proto__" || target === copy ) { continue; } // Recurse if we're merging plain objects or arrays if ( deep && copy && ( jQuery.isPlainObject( copy ) || ( copyIsArray = Array.isArray( copy ) ) ) ) { src = target[ name ]; // Ensure proper type for the source value if ( copyIsArray && !Array.isArray( src ) ) { clone = []; } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { clone = {}; } else { clone = src; } copyIsArray = false; // Never move original objects, clone them target[ name ] = jQuery.extend( deep, clone, copy ); // Don't bring in undefined values } else if ( copy !== undefined ) { target[ name ] = copy; } } } } // Return the modified object return target; }; jQuery.extend( { // Unique for each copy of jQuery on the page expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), // Assume jQuery is ready without the ready module isReady: true, error: function( msg ) { throw new Error( msg ); }, noop: function() {}, isPlainObject: function( obj ) { var proto, Ctor; // Detect obvious negatives // Use toString instead of jQuery.type to catch host objects if ( !obj || toString.call( obj ) !== "[object Object]" ) { return false; } proto = getProto( obj ); // Objects with no prototype (e.g., `Object.create( null )`) are plain if ( !proto ) { return true; } // Objects with prototype are plain iff they were constructed by a global Object function Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; }, isEmptyObject: function( obj ) { var name; for ( name in obj ) { return false; } return true; }, // Evaluates a script in a provided context; falls back to the global one // if not specified. globalEval: function( code, options, doc ) { DOMEval( code, { nonce: options && options.nonce }, doc ); }, each: function( obj, callback ) { var length, i = 0; if ( isArrayLike( obj ) ) { length = obj.length; for ( ; i < length; i++ ) { if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { break; } } } else { for ( i in obj ) { if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { break; } } } return obj; }, // results is for internal usage only makeArray: function( arr, results ) { var ret = results || []; if ( arr != null ) { if ( isArrayLike( Object( arr ) ) ) { jQuery.merge( ret, typeof arr === "string" ? [ arr ] : arr ); } else { push.call( ret, arr ); } } return ret; }, inArray: function( elem, arr, i ) { return arr == null ? -1 : indexOf.call( arr, elem, i ); }, // Support: Android <=4.0 only, PhantomJS 1 only // push.apply(_, arraylike) throws on ancient WebKit merge: function( first, second ) { var len = +second.length, j = 0, i = first.length; for ( ; j < len; j++ ) { first[ i++ ] = second[ j ]; } first.length = i; return first; }, grep: function( elems, callback, invert ) { var callbackInverse, matches = [], i = 0, length = elems.length, callbackExpect = !invert; // Go through the array, only saving the items // that pass the validator function for ( ; i < length; i++ ) { callbackInverse = !callback( elems[ i ], i ); if ( callbackInverse !== callbackExpect ) { matches.push( elems[ i ] ); } } return matches; }, // arg is for internal usage only map: function( elems, callback, arg ) { var length, value, i = 0, ret = []; // Go through the array, translating each of the items to their new values if ( isArrayLike( elems ) ) { length = elems.length; for ( ; i < length; i++ ) { value = callback( elems[ i ], i, arg ); if ( value != null ) { ret.push( value ); } } // Go through every key on the object, } else { for ( i in elems ) { value = callback( elems[ i ], i, arg ); if ( value != null ) { ret.push( value ); } } } // Flatten any nested arrays return flat( ret ); }, // A global GUID counter for objects guid: 1, // jQuery.support is not used in Core but other projects attach their // properties to it so it needs to exist. support: support } ); if ( typeof Symbol === "function" ) { jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; } // Populate the class2type map jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), function( _i, name ) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); } ); function isArrayLike( obj ) { // Support: real iOS 8.2 only (not reproducible in simulator) // `in` check used to prevent JIT error (gh-2145) // hasOwn isn't used here due to false negatives // regarding Nodelist length in IE var length = !!obj && "length" in obj && obj.length, type = toType( obj ); if ( isFunction( obj ) || isWindow( obj ) ) { return false; } return type === "array" || length === 0 || typeof length === "number" && length > 0 && ( length - 1 ) in obj; } var Sizzle = /*! * Sizzle CSS Selector Engine v2.3.5 * https://sizzlejs.com/ * * Copyright JS Foundation and other contributors * Released under the MIT license * https://js.foundation/ * * Date: 2020-03-14 */ ( function( window ) { var i, support, Expr, getText, isXML, tokenize, compile, select, outermostContext, sortInput, hasDuplicate, // Local document vars setDocument, document, docElem, documentIsHTML, rbuggyQSA, rbuggyMatches, matches, contains, // Instance-specific data expando = "sizzle" + 1 * new Date(), preferredDoc = window.document, dirruns = 0, done = 0, classCache = createCache(), tokenCache = createCache(), compilerCache = createCache(), nonnativeSelectorCache = createCache(), sortOrder = function( a, b ) { if ( a === b ) { hasDuplicate = true; } return 0; }, // Instance methods hasOwn = ( {} ).hasOwnProperty, arr = [], pop = arr.pop, pushNative = arr.push, push = arr.push, slice = arr.slice, // Use a stripped-down indexOf as it's faster than native // https://jsperf.com/thor-indexof-vs-for/5 indexOf = function( list, elem ) { var i = 0, len = list.length; for ( ; i < len; i++ ) { if ( list[ i ] === elem ) { return i; } } return -1; }, booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + "ismap|loop|multiple|open|readonly|required|scoped", // Regular expressions // http://www.w3.org/TR/css3-selectors/#whitespace whitespace = "[\\x20\\t\\r\\n\\f]", // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + // Operator (capture 2) "*([*^$|!~]?=)" + whitespace + // "Attribute values must be CSS identifiers [capture 5] // or strings [capture 3 or capture 4]" "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + "*\\]", pseudos = ":(" + identifier + ")(?:\\((" + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: // 1. quoted (capture 3; capture 4 or capture 5) "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + // 2. simple (capture 6) "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + // 3. anything else (capture 2) ".*" + ")\\)|)", // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter rwhitespace = new RegExp( whitespace + "+", "g" ), rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), rdescend = new RegExp( whitespace + "|>" ), rpseudo = new RegExp( pseudos ), ridentifier = new RegExp( "^" + identifier + "$" ), matchExpr = { "ID": new RegExp( "^#(" + identifier + ")" ), "CLASS": new RegExp( "^\\.(" + identifier + ")" ), "TAG": new RegExp( "^(" + identifier + "|[*])" ), "ATTR": new RegExp( "^" + attributes ), "PSEUDO": new RegExp( "^" + pseudos ), "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), // For use in libraries implementing .is() // We use this for POS matching in `select` "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) }, rhtml = /HTML$/i, rinputs = /^(?:input|select|textarea|button)$/i, rheader = /^h\d$/i, rnative = /^[^{]+\{\s*\[native \w/, // Easily-parseable/retrievable ID or TAG or CLASS selectors rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, rsibling = /[+~]/, // CSS escapes // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), funescape = function( escape, nonHex ) { var high = "0x" + escape.slice( 1 ) - 0x10000; return nonHex ? // Strip the backslash prefix from a non-hex escape sequence nonHex : // Replace a hexadecimal escape sequence with the encoded Unicode code point // Support: IE <=11+ // For values outside the Basic Multilingual Plane (BMP), manually construct a // surrogate pair high < 0 ? String.fromCharCode( high + 0x10000 ) : String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); }, // CSS string/identifier serialization // https://drafts.csswg.org/cssom/#common-serializing-idioms rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, fcssescape = function( ch, asCodePoint ) { if ( asCodePoint ) { // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER if ( ch === "\0" ) { return "\uFFFD"; } // Control characters and (dependent upon position) numbers get escaped as code points return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; } // Other potentially-special ASCII characters get backslash-escaped return "\\" + ch; }, // Used for iframes // See setDocument() // Removing the function wrapper causes a "Permission Denied" // error in IE unloadHandler = function() { setDocument(); }, inDisabledFieldset = addCombinator( function( elem ) { return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; }, { dir: "parentNode", next: "legend" } ); // Optimize for push.apply( _, NodeList ) try { push.apply( ( arr = slice.call( preferredDoc.childNodes ) ), preferredDoc.childNodes ); // Support: Android<4.0 // Detect silently failing push.apply // eslint-disable-next-line no-unused-expressions arr[ preferredDoc.childNodes.length ].nodeType; } catch ( e ) { push = { apply: arr.length ? // Leverage slice if possible function( target, els ) { pushNative.apply( target, slice.call( els ) ); } : // Support: IE<9 // Otherwise append directly function( target, els ) { var j = target.length, i = 0; // Can't trust NodeList.length while ( ( target[ j++ ] = els[ i++ ] ) ) {} target.length = j - 1; } }; } function Sizzle( selector, context, results, seed ) { var m, i, elem, nid, match, groups, newSelector, newContext = context && context.ownerDocument, // nodeType defaults to 9, since context defaults to document nodeType = context ? context.nodeType : 9; results = results || []; // Return early from calls with invalid selector or context if ( typeof selector !== "string" || !selector || nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { return results; } // Try to shortcut find operations (as opposed to filters) in HTML documents if ( !seed ) { setDocument( context ); context = context || document; if ( documentIsHTML ) { // If the selector is sufficiently simple, try using a "get*By*" DOM method // (excepting DocumentFragment context, where the methods don't exist) if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { // ID selector if ( ( m = match[ 1 ] ) ) { // Document context if ( nodeType === 9 ) { if ( ( elem = context.getElementById( m ) ) ) { // Support: IE, Opera, Webkit // TODO: identify versions // getElementById can match elements by name instead of ID if ( elem.id === m ) { results.push( elem ); return results; } } else { return results; } // Element context } else { // Support: IE, Opera, Webkit // TODO: identify versions // getElementById can match elements by name instead of ID if ( newContext && ( elem = newContext.getElementById( m ) ) && contains( context, elem ) && elem.id === m ) { results.push( elem ); return results; } } // Type selector } else if ( match[ 2 ] ) { push.apply( results, context.getElementsByTagName( selector ) ); return results; // Class selector } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && context.getElementsByClassName ) { push.apply( results, context.getElementsByClassName( m ) ); return results; } } // Take advantage of querySelectorAll if ( support.qsa && !nonnativeSelectorCache[ selector + " " ] && ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && // Support: IE 8 only // Exclude object elements ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { newSelector = selector; newContext = context; // qSA considers elements outside a scoping root when evaluating child or // descendant combinators, which is not what we want. // In such cases, we work around the behavior by prefixing every selector in the // list with an ID selector referencing the scope context. // The technique has to be used as well when a leading combinator is used // as such selectors are not recognized by querySelectorAll. // Thanks to Andrew Dupont for this technique. if ( nodeType === 1 && ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { // Expand context for sibling selectors newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; // We can use :scope instead of the ID hack if the browser // supports it & if we're not changing the context. if ( newContext !== context || !support.scope ) { // Capture the context ID, setting it first if necessary if ( ( nid = context.getAttribute( "id" ) ) ) { nid = nid.replace( rcssescape, fcssescape ); } else { context.setAttribute( "id", ( nid = expando ) ); } } // Prefix every selector in the list groups = tokenize( selector ); i = groups.length; while ( i-- ) { groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + toSelector( groups[ i ] ); } newSelector = groups.join( "," ); } try { push.apply( results, newContext.querySelectorAll( newSelector ) ); return results; } catch ( qsaError ) { nonnativeSelectorCache( selector, true ); } finally { if ( nid === expando ) { context.removeAttribute( "id" ); } } } } } // All others return select( selector.replace( rtrim, "$1" ), context, results, seed ); } /** * Create key-value caches of limited size * @returns {function(string, object)} Returns the Object data after storing it on itself with * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) * deleting the oldest entry */ function createCache() { var keys = []; function cache( key, value ) { // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) if ( keys.push( key + " " ) > Expr.cacheLength ) { // Only keep the most recent entries delete cache[ keys.shift() ]; } return ( cache[ key + " " ] = value ); } return cache; } /** * Mark a function for special use by Sizzle * @param {Function} fn The function to mark */ function markFunction( fn ) { fn[ expando ] = true; return fn; } /** * Support testing using an element * @param {Function} fn Passed the created element and returns a boolean result */ function assert( fn ) { var el = document.createElement( "fieldset" ); try { return !!fn( el ); } catch ( e ) { return false; } finally { // Remove from its parent by default if ( el.parentNode ) { el.parentNode.removeChild( el ); } // release memory in IE el = null; } } /** * Adds the same handler for all of the specified attrs * @param {String} attrs Pipe-separated list of attributes * @param {Function} handler The method that will be applied */ function addHandle( attrs, handler ) { var arr = attrs.split( "|" ), i = arr.length; while ( i-- ) { Expr.attrHandle[ arr[ i ] ] = handler; } } /** * Checks document order of two siblings * @param {Element} a * @param {Element} b * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b */ function siblingCheck( a, b ) { var cur = b && a, diff = cur && a.nodeType === 1 && b.nodeType === 1 && a.sourceIndex - b.sourceIndex; // Use IE sourceIndex if available on both nodes if ( diff ) { return diff; } // Check if b follows a if ( cur ) { while ( ( cur = cur.nextSibling ) ) { if ( cur === b ) { return -1; } } } return a ? 1 : -1; } /** * Returns a function to use in pseudos for input types * @param {String} type */ function createInputPseudo( type ) { return function( elem ) { var name = elem.nodeName.toLowerCase(); return name === "input" && elem.type === type; }; } /** * Returns a function to use in pseudos for buttons * @param {String} type */ function createButtonPseudo( type ) { return function( elem ) { var name = elem.nodeName.toLowerCase(); return ( name === "input" || name === "button" ) && elem.type === type; }; } /** * Returns a function to use in pseudos for :enabled/:disabled * @param {Boolean} disabled true for :disabled; false for :enabled */ function createDisabledPseudo( disabled ) { // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable return function( elem ) { // Only certain elements can match :enabled or :disabled // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled if ( "form" in elem ) { // Check for inherited disabledness on relevant non-disabled elements: // * listed form-associated elements in a disabled fieldset // https://html.spec.whatwg.org/multipage/forms.html#category-listed // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled // * option elements in a disabled optgroup // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled // All such elements have a "form" property. if ( elem.parentNode && elem.disabled === false ) { // Option elements defer to a parent optgroup if present if ( "label" in elem ) { if ( "label" in elem.parentNode ) { return elem.parentNode.disabled === disabled; } else { return elem.disabled === disabled; } } // Support: IE 6 - 11 // Use the isDisabled shortcut property to check for disabled fieldset ancestors return elem.isDisabled === disabled || // Where there is no isDisabled, check manually /* jshint -W018 */ elem.isDisabled !== !disabled && inDisabledFieldset( elem ) === disabled; } return elem.disabled === disabled; // Try to winnow out elements that can't be disabled before trusting the disabled property. // Some victims get caught in our net (label, legend, menu, track), but it shouldn't // even exist on them, let alone have a boolean value. } else if ( "label" in elem ) { return elem.disabled === disabled; } // Remaining elements are neither :enabled nor :disabled return false; }; } /** * Returns a function to use in pseudos for positionals * @param {Function} fn */ function createPositionalPseudo( fn ) { return markFunction( function( argument ) { argument = +argument; return markFunction( function( seed, matches ) { var j, matchIndexes = fn( [], seed.length, argument ), i = matchIndexes.length; // Match elements found at the specified indexes while ( i-- ) { if ( seed[ ( j = matchIndexes[ i ] ) ] ) { seed[ j ] = !( matches[ j ] = seed[ j ] ); } } } ); } ); } /** * Checks a node for validity as a Sizzle context * @param {Element|Object=} context * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value */ function testContext( context ) { return context && typeof context.getElementsByTagName !== "undefined" && context; } // Expose support vars for convenience support = Sizzle.support = {}; /** * Detects XML nodes * @param {Element|Object} elem An element or a document * @returns {Boolean} True iff elem is a non-HTML XML node */ isXML = Sizzle.isXML = function( elem ) { var namespace = elem.namespaceURI, docElem = ( elem.ownerDocument || elem ).documentElement; // Support: IE <=8 // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes // https://bugs.jquery.com/ticket/4833 return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); }; /** * Sets document-related variables once based on the current document * @param {Element|Object} [doc] An element or document object to use to set the document * @returns {Object} Returns the current document */ setDocument = Sizzle.setDocument = function( node ) { var hasCompare, subWindow, doc = node ? node.ownerDocument || node : preferredDoc; // Return early if doc is invalid or already selected // Support: IE 11+, Edge 17 - 18+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing // two documents; shallow comparisons work. // eslint-disable-next-line eqeqeq if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { return document; } // Update global variables document = doc; docElem = document.documentElement; documentIsHTML = !isXML( document ); // Support: IE 9 - 11+, Edge 12 - 18+ // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) // Support: IE 11+, Edge 17 - 18+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing // two documents; shallow comparisons work. // eslint-disable-next-line eqeqeq if ( preferredDoc != document && ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { // Support: IE 11, Edge if ( subWindow.addEventListener ) { subWindow.addEventListener( "unload", unloadHandler, false ); // Support: IE 9 - 10 only } else if ( subWindow.attachEvent ) { subWindow.attachEvent( "onunload", unloadHandler ); } } // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, // Safari 4 - 5 only, Opera <=11.6 - 12.x only // IE/Edge & older browsers don't support the :scope pseudo-class. // Support: Safari 6.0 only // Safari 6.0 supports :scope but it's an alias of :root there. support.scope = assert( function( el ) { docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); return typeof el.querySelectorAll !== "undefined" && !el.querySelectorAll( ":scope fieldset div" ).length; } ); /* Attributes ---------------------------------------------------------------------- */ // Support: IE<8 // Verify that getAttribute really returns attributes and not properties // (excepting IE8 booleans) support.attributes = assert( function( el ) { el.className = "i"; return !el.getAttribute( "className" ); } ); /* getElement(s)By* ---------------------------------------------------------------------- */ // Check if getElementsByTagName("*") returns only elements support.getElementsByTagName = assert( function( el ) { el.appendChild( document.createComment( "" ) ); return !el.getElementsByTagName( "*" ).length; } ); // Support: IE<9 support.getElementsByClassName = rnative.test( document.getElementsByClassName ); // Support: IE<10 // Check if getElementById returns elements by name // The broken getElementById methods don't pick up programmatically-set names, // so use a roundabout getElementsByName test support.getById = assert( function( el ) { docElem.appendChild( el ).id = expando; return !document.getElementsByName || !document.getElementsByName( expando ).length; } ); // ID filter and find if ( support.getById ) { Expr.filter[ "ID" ] = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { return elem.getAttribute( "id" ) === attrId; }; }; Expr.find[ "ID" ] = function( id, context ) { if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { var elem = context.getElementById( id ); return elem ? [ elem ] : []; } }; } else { Expr.filter[ "ID" ] = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode( "id" ); return node && node.value === attrId; }; }; // Support: IE 6 - 7 only // getElementById is not reliable as a find shortcut Expr.find[ "ID" ] = function( id, context ) { if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { var node, i, elems, elem = context.getElementById( id ); if ( elem ) { // Verify the id attribute node = elem.getAttributeNode( "id" ); if ( node && node.value === id ) { return [ elem ]; } // Fall back on getElementsByName elems = context.getElementsByName( id ); i = 0; while ( ( elem = elems[ i++ ] ) ) { node = elem.getAttributeNode( "id" ); if ( node && node.value === id ) { return [ elem ]; } } } return []; } }; } // Tag Expr.find[ "TAG" ] = support.getElementsByTagName ? function( tag, context ) { if ( typeof context.getElementsByTagName !== "undefined" ) { return context.getElementsByTagName( tag ); // DocumentFragment nodes don't have gEBTN } else if ( support.qsa ) { return context.querySelectorAll( tag ); } } : function( tag, context ) { var elem, tmp = [], i = 0, // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too results = context.getElementsByTagName( tag ); // Filter out possible comments if ( tag === "*" ) { while ( ( elem = results[ i++ ] ) ) { if ( elem.nodeType === 1 ) { tmp.push( elem ); } } return tmp; } return results; }; // Class Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { return context.getElementsByClassName( className ); } }; /* QSA/matchesSelector ---------------------------------------------------------------------- */ // QSA and matchesSelector support // matchesSelector(:active) reports false when true (IE9/Opera 11.5) rbuggyMatches = []; // qSa(:focus) reports false when true (Chrome 21) // We allow this because of a bug in IE8/9 that throws an error // whenever `document.activeElement` is accessed on an iframe // So, we allow :focus to pass through QSA all the time to avoid the IE error // See https://bugs.jquery.com/ticket/13378 rbuggyQSA = []; if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { // Build QSA regex // Regex strategy adopted from Diego Perini assert( function( el ) { var input; // Select is set to empty string on purpose // This is to test IE's treatment of not explicitly // setting a boolean content attribute, // since its presence should be enough // https://bugs.jquery.com/ticket/12359 docElem.appendChild( el ).innerHTML = "" + ""; // Support: IE8, Opera 11-12.16 // Nothing should be selected when empty strings follow ^= or $= or *= // The test attribute must be unknown in Opera but "safe" for WinRT // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); } // Support: IE8 // Boolean attributes and "value" are not treated correctly if ( !el.querySelectorAll( "[selected]" ).length ) { rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); } // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { rbuggyQSA.push( "~=" ); } // Support: IE 11+, Edge 15 - 18+ // IE 11/Edge don't find elements on a `[name='']` query in some cases. // Adding a temporary attribute to the document before the selection works // around the issue. // Interestingly, IE 10 & older don't seem to have the issue. input = document.createElement( "input" ); input.setAttribute( "name", "" ); el.appendChild( input ); if ( !el.querySelectorAll( "[name='']" ).length ) { rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + whitespace + "*(?:''|\"\")" ); } // Webkit/Opera - :checked should return selected option elements // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked // IE8 throws error here and will not see later tests if ( !el.querySelectorAll( ":checked" ).length ) { rbuggyQSA.push( ":checked" ); } // Support: Safari 8+, iOS 8+ // https://bugs.webkit.org/show_bug.cgi?id=136851 // In-page `selector#id sibling-combinator selector` fails if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { rbuggyQSA.push( ".#.+[+~]" ); } // Support: Firefox <=3.6 - 5 only // Old Firefox doesn't throw on a badly-escaped identifier. el.querySelectorAll( "\\\f" ); rbuggyQSA.push( "[\\r\\n\\f]" ); } ); assert( function( el ) { el.innerHTML = "" + ""; // Support: Windows 8 Native Apps // The type and name attributes are restricted during .innerHTML assignment var input = document.createElement( "input" ); input.setAttribute( "type", "hidden" ); el.appendChild( input ).setAttribute( "name", "D" ); // Support: IE8 // Enforce case-sensitivity of name attribute if ( el.querySelectorAll( "[name=d]" ).length ) { rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); } // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) // IE8 throws error here and will not see later tests if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { rbuggyQSA.push( ":enabled", ":disabled" ); } // Support: IE9-11+ // IE's :disabled selector does not pick up the children of disabled fieldsets docElem.appendChild( el ).disabled = true; if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { rbuggyQSA.push( ":enabled", ":disabled" ); } // Support: Opera 10 - 11 only // Opera 10-11 does not throw on post-comma invalid pseudos el.querySelectorAll( "*,:x" ); rbuggyQSA.push( ",.*:" ); } ); } if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || docElem.webkitMatchesSelector || docElem.mozMatchesSelector || docElem.oMatchesSelector || docElem.msMatchesSelector ) ) ) ) { assert( function( el ) { // Check to see if it's possible to do matchesSelector // on a disconnected node (IE 9) support.disconnectedMatch = matches.call( el, "*" ); // This should fail with an exception // Gecko does not error, returns false instead matches.call( el, "[s!='']:x" ); rbuggyMatches.push( "!=", pseudos ); } ); } rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); /* Contains ---------------------------------------------------------------------- */ hasCompare = rnative.test( docElem.compareDocumentPosition ); // Element contains another // Purposefully self-exclusive // As in, an element does not contain itself contains = hasCompare || rnative.test( docElem.contains ) ? function( a, b ) { var adown = a.nodeType === 9 ? a.documentElement : a, bup = b && b.parentNode; return a === bup || !!( bup && bup.nodeType === 1 && ( adown.contains ? adown.contains( bup ) : a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 ) ); } : function( a, b ) { if ( b ) { while ( ( b = b.parentNode ) ) { if ( b === a ) { return true; } } } return false; }; /* Sorting ---------------------------------------------------------------------- */ // Document order sorting sortOrder = hasCompare ? function( a, b ) { // Flag for duplicate removal if ( a === b ) { hasDuplicate = true; return 0; } // Sort on method existence if only one input has compareDocumentPosition var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; if ( compare ) { return compare; } // Calculate position if both inputs belong to the same document // Support: IE 11+, Edge 17 - 18+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing // two documents; shallow comparisons work. // eslint-disable-next-line eqeqeq compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? a.compareDocumentPosition( b ) : // Otherwise we know they are disconnected 1; // Disconnected nodes if ( compare & 1 || ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { // Choose the first element that is related to our preferred document // Support: IE 11+, Edge 17 - 18+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing // two documents; shallow comparisons work. // eslint-disable-next-line eqeqeq if ( a == document || a.ownerDocument == preferredDoc && contains( preferredDoc, a ) ) { return -1; } // Support: IE 11+, Edge 17 - 18+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing // two documents; shallow comparisons work. // eslint-disable-next-line eqeqeq if ( b == document || b.ownerDocument == preferredDoc && contains( preferredDoc, b ) ) { return 1; } // Maintain original order return sortInput ? ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : 0; } return compare & 4 ? -1 : 1; } : function( a, b ) { // Exit early if the nodes are identical if ( a === b ) { hasDuplicate = true; return 0; } var cur, i = 0, aup = a.parentNode, bup = b.parentNode, ap = [ a ], bp = [ b ]; // Parentless nodes are either documents or disconnected if ( !aup || !bup ) { // Support: IE 11+, Edge 17 - 18+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing // two documents; shallow comparisons work. /* eslint-disable eqeqeq */ return a == document ? -1 : b == document ? 1 : /* eslint-enable eqeqeq */ aup ? -1 : bup ? 1 : sortInput ? ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : 0; // If the nodes are siblings, we can do a quick check } else if ( aup === bup ) { return siblingCheck( a, b ); } // Otherwise we need full lists of their ancestors for comparison cur = a; while ( ( cur = cur.parentNode ) ) { ap.unshift( cur ); } cur = b; while ( ( cur = cur.parentNode ) ) { bp.unshift( cur ); } // Walk down the tree looking for a discrepancy while ( ap[ i ] === bp[ i ] ) { i++; } return i ? // Do a sibling check if the nodes have a common ancestor siblingCheck( ap[ i ], bp[ i ] ) : // Otherwise nodes in our document sort first // Support: IE 11+, Edge 17 - 18+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing // two documents; shallow comparisons work. /* eslint-disable eqeqeq */ ap[ i ] == preferredDoc ? -1 : bp[ i ] == preferredDoc ? 1 : /* eslint-enable eqeqeq */ 0; }; return document; }; Sizzle.matches = function( expr, elements ) { return Sizzle( expr, null, null, elements ); }; Sizzle.matchesSelector = function( elem, expr ) { setDocument( elem ); if ( support.matchesSelector && documentIsHTML && !nonnativeSelectorCache[ expr + " " ] && ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { try { var ret = matches.call( elem, expr ); // IE 9's matchesSelector returns false on disconnected nodes if ( ret || support.disconnectedMatch || // As well, disconnected nodes are said to be in a document // fragment in IE 9 elem.document && elem.document.nodeType !== 11 ) { return ret; } } catch ( e ) { nonnativeSelectorCache( expr, true ); } } return Sizzle( expr, document, null, [ elem ] ).length > 0; }; Sizzle.contains = function( context, elem ) { // Set document vars if needed // Support: IE 11+, Edge 17 - 18+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing // two documents; shallow comparisons work. // eslint-disable-next-line eqeqeq if ( ( context.ownerDocument || context ) != document ) { setDocument( context ); } return contains( context, elem ); }; Sizzle.attr = function( elem, name ) { // Set document vars if needed // Support: IE 11+, Edge 17 - 18+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing // two documents; shallow comparisons work. // eslint-disable-next-line eqeqeq if ( ( elem.ownerDocument || elem ) != document ) { setDocument( elem ); } var fn = Expr.attrHandle[ name.toLowerCase() ], // Don't get fooled by Object.prototype properties (jQuery #13807) val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? fn( elem, name, !documentIsHTML ) : undefined; return val !== undefined ? val : support.attributes || !documentIsHTML ? elem.getAttribute( name ) : ( val = elem.getAttributeNode( name ) ) && val.specified ? val.value : null; }; Sizzle.escape = function( sel ) { return ( sel + "" ).replace( rcssescape, fcssescape ); }; Sizzle.error = function( msg ) { throw new Error( "Syntax error, unrecognized expression: " + msg ); }; /** * Document sorting and removing duplicates * @param {ArrayLike} results */ Sizzle.uniqueSort = function( results ) { var elem, duplicates = [], j = 0, i = 0; // Unless we *know* we can detect duplicates, assume their presence hasDuplicate = !support.detectDuplicates; sortInput = !support.sortStable && results.slice( 0 ); results.sort( sortOrder ); if ( hasDuplicate ) { while ( ( elem = results[ i++ ] ) ) { if ( elem === results[ i ] ) { j = duplicates.push( i ); } } while ( j-- ) { results.splice( duplicates[ j ], 1 ); } } // Clear input after sorting to release objects // See https://github.com/jquery/sizzle/pull/225 sortInput = null; return results; }; /** * Utility function for retrieving the text value of an array of DOM nodes * @param {Array|Element} elem */ getText = Sizzle.getText = function( elem ) { var node, ret = "", i = 0, nodeType = elem.nodeType; if ( !nodeType ) { // If no nodeType, this is expected to be an array while ( ( node = elem[ i++ ] ) ) { // Do not traverse comment nodes ret += getText( node ); } } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { // Use textContent for elements // innerText usage removed for consistency of new lines (jQuery #11153) if ( typeof elem.textContent === "string" ) { return elem.textContent; } else { // Traverse its children for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { ret += getText( elem ); } } } else if ( nodeType === 3 || nodeType === 4 ) { return elem.nodeValue; } // Do not include comment or processing instruction nodes return ret; }; Expr = Sizzle.selectors = { // Can be adjusted by the user cacheLength: 50, createPseudo: markFunction, match: matchExpr, attrHandle: {}, find: {}, relative: { ">": { dir: "parentNode", first: true }, " ": { dir: "parentNode" }, "+": { dir: "previousSibling", first: true }, "~": { dir: "previousSibling" } }, preFilter: { "ATTR": function( match ) { match[ 1 ] = match[ 1 ].replace( runescape, funescape ); // Move the given value to match[3] whether quoted or unquoted match[ 3 ] = ( match[ 3 ] || match[ 4 ] || match[ 5 ] || "" ).replace( runescape, funescape ); if ( match[ 2 ] === "~=" ) { match[ 3 ] = " " + match[ 3 ] + " "; } return match.slice( 0, 4 ); }, "CHILD": function( match ) { /* matches from matchExpr["CHILD"] 1 type (only|nth|...) 2 what (child|of-type) 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) 4 xn-component of xn+y argument ([+-]?\d*n|) 5 sign of xn-component 6 x of xn-component 7 sign of y-component 8 y of y-component */ match[ 1 ] = match[ 1 ].toLowerCase(); if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { // nth-* requires argument if ( !match[ 3 ] ) { Sizzle.error( match[ 0 ] ); } // numeric x and y parameters for Expr.filter.CHILD // remember that false/true cast respectively to 0/1 match[ 4 ] = +( match[ 4 ] ? match[ 5 ] + ( match[ 6 ] || 1 ) : 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); // other types prohibit arguments } else if ( match[ 3 ] ) { Sizzle.error( match[ 0 ] ); } return match; }, "PSEUDO": function( match ) { var excess, unquoted = !match[ 6 ] && match[ 2 ]; if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { return null; } // Accept quoted arguments as-is if ( match[ 3 ] ) { match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; // Strip excess characters from unquoted arguments } else if ( unquoted && rpseudo.test( unquoted ) && // Get excess from tokenize (recursively) ( excess = tokenize( unquoted, true ) ) && // advance to the next closing parenthesis ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { // excess is a negative index match[ 0 ] = match[ 0 ].slice( 0, excess ); match[ 2 ] = unquoted.slice( 0, excess ); } // Return only captures needed by the pseudo filter method (type and argument) return match.slice( 0, 3 ); } }, filter: { "TAG": function( nodeNameSelector ) { var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); return nodeNameSelector === "*" ? function() { return true; } : function( elem ) { return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; }; }, "CLASS": function( className ) { var pattern = classCache[ className + " " ]; return pattern || ( pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" ) ) && classCache( className, function( elem ) { return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute( "class" ) || "" ); } ); }, "ATTR": function( name, operator, check ) { return function( elem ) { var result = Sizzle.attr( elem, name ); if ( result == null ) { return operator === "!="; } if ( !operator ) { return true; } result += ""; /* eslint-disable max-len */ return operator === "=" ? result === check : operator === "!=" ? result !== check : operator === "^=" ? check && result.indexOf( check ) === 0 : operator === "*=" ? check && result.indexOf( check ) > -1 : operator === "$=" ? check && result.slice( -check.length ) === check : operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : false; /* eslint-enable max-len */ }; }, "CHILD": function( type, what, _argument, first, last ) { var simple = type.slice( 0, 3 ) !== "nth", forward = type.slice( -4 ) !== "last", ofType = what === "of-type"; return first === 1 && last === 0 ? // Shortcut for :nth-*(n) function( elem ) { return !!elem.parentNode; } : function( elem, _context, xml ) { var cache, uniqueCache, outerCache, node, nodeIndex, start, dir = simple !== forward ? "nextSibling" : "previousSibling", parent = elem.parentNode, name = ofType && elem.nodeName.toLowerCase(), useCache = !xml && !ofType, diff = false; if ( parent ) { // :(first|last|only)-(child|of-type) if ( simple ) { while ( dir ) { node = elem; while ( ( node = node[ dir ] ) ) { if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { return false; } } // Reverse direction for :only-* (if we haven't yet done so) start = dir = type === "only" && !start && "nextSibling"; } return true; } start = [ forward ? parent.firstChild : parent.lastChild ]; // non-xml :nth-child(...) stores cache data on `parent` if ( forward && useCache ) { // Seek `elem` from a previously-cached index // ...in a gzip-friendly way node = parent; outerCache = node[ expando ] || ( node[ expando ] = {} ); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || ( outerCache[ node.uniqueID ] = {} ); cache = uniqueCache[ type ] || []; nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; diff = nodeIndex && cache[ 2 ]; node = nodeIndex && parent.childNodes[ nodeIndex ]; while ( ( node = ++nodeIndex && node && node[ dir ] || // Fallback to seeking `elem` from the start ( diff = nodeIndex = 0 ) || start.pop() ) ) { // When found, cache indexes on `parent` and break if ( node.nodeType === 1 && ++diff && node === elem ) { uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; break; } } } else { // Use previously-cached element index if available if ( useCache ) { // ...in a gzip-friendly way node = elem; outerCache = node[ expando ] || ( node[ expando ] = {} ); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || ( outerCache[ node.uniqueID ] = {} ); cache = uniqueCache[ type ] || []; nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; diff = nodeIndex; } // xml :nth-child(...) // or :nth-last-child(...) or :nth(-last)?-of-type(...) if ( diff === false ) { // Use the same loop as above to seek `elem` from the start while ( ( node = ++nodeIndex && node && node[ dir ] || ( diff = nodeIndex = 0 ) || start.pop() ) ) { if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { // Cache the index of each encountered element if ( useCache ) { outerCache = node[ expando ] || ( node[ expando ] = {} ); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || ( outerCache[ node.uniqueID ] = {} ); uniqueCache[ type ] = [ dirruns, diff ]; } if ( node === elem ) { break; } } } } } // Incorporate the offset, then check against cycle size diff -= last; return diff === first || ( diff % first === 0 && diff / first >= 0 ); } }; }, "PSEUDO": function( pseudo, argument ) { // pseudo-class names are case-insensitive // http://www.w3.org/TR/selectors/#pseudo-classes // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters // Remember that setFilters inherits from pseudos var args, fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || Sizzle.error( "unsupported pseudo: " + pseudo ); // The user may use createPseudo to indicate that // arguments are needed to create the filter function // just as Sizzle does if ( fn[ expando ] ) { return fn( argument ); } // But maintain support for old signatures if ( fn.length > 1 ) { args = [ pseudo, pseudo, "", argument ]; return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? markFunction( function( seed, matches ) { var idx, matched = fn( seed, argument ), i = matched.length; while ( i-- ) { idx = indexOf( seed, matched[ i ] ); seed[ idx ] = !( matches[ idx ] = matched[ i ] ); } } ) : function( elem ) { return fn( elem, 0, args ); }; } return fn; } }, pseudos: { // Potentially complex pseudos "not": markFunction( function( selector ) { // Trim the selector passed to compile // to avoid treating leading and trailing // spaces as combinators var input = [], results = [], matcher = compile( selector.replace( rtrim, "$1" ) ); return matcher[ expando ] ? markFunction( function( seed, matches, _context, xml ) { var elem, unmatched = matcher( seed, null, xml, [] ), i = seed.length; // Match elements unmatched by `matcher` while ( i-- ) { if ( ( elem = unmatched[ i ] ) ) { seed[ i ] = !( matches[ i ] = elem ); } } } ) : function( elem, _context, xml ) { input[ 0 ] = elem; matcher( input, null, xml, results ); // Don't keep the element (issue #299) input[ 0 ] = null; return !results.pop(); }; } ), "has": markFunction( function( selector ) { return function( elem ) { return Sizzle( selector, elem ).length > 0; }; } ), "contains": markFunction( function( text ) { text = text.replace( runescape, funescape ); return function( elem ) { return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; }; } ), // "Whether an element is represented by a :lang() selector // is based solely on the element's language value // being equal to the identifier C, // or beginning with the identifier C immediately followed by "-". // The matching of C against the element's language value is performed case-insensitively. // The identifier C does not have to be a valid language name." // http://www.w3.org/TR/selectors/#lang-pseudo "lang": markFunction( function( lang ) { // lang value must be a valid identifier if ( !ridentifier.test( lang || "" ) ) { Sizzle.error( "unsupported lang: " + lang ); } lang = lang.replace( runescape, funescape ).toLowerCase(); return function( elem ) { var elemLang; do { if ( ( elemLang = documentIsHTML ? elem.lang : elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { elemLang = elemLang.toLowerCase(); return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; } } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); return false; }; } ), // Miscellaneous "target": function( elem ) { var hash = window.location && window.location.hash; return hash && hash.slice( 1 ) === elem.id; }, "root": function( elem ) { return elem === docElem; }, "focus": function( elem ) { return elem === document.activeElement && ( !document.hasFocus || document.hasFocus() ) && !!( elem.type || elem.href || ~elem.tabIndex ); }, // Boolean properties "enabled": createDisabledPseudo( false ), "disabled": createDisabledPseudo( true ), "checked": function( elem ) { // In CSS3, :checked should return both checked and selected elements // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked var nodeName = elem.nodeName.toLowerCase(); return ( nodeName === "input" && !!elem.checked ) || ( nodeName === "option" && !!elem.selected ); }, "selected": function( elem ) { // Accessing this property makes selected-by-default // options in Safari work properly if ( elem.parentNode ) { // eslint-disable-next-line no-unused-expressions elem.parentNode.selectedIndex; } return elem.selected === true; }, // Contents "empty": function( elem ) { // http://www.w3.org/TR/selectors/#empty-pseudo // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), // but not by others (comment: 8; processing instruction: 7; etc.) // nodeType < 6 works because attributes (2) do not appear as children for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { if ( elem.nodeType < 6 ) { return false; } } return true; }, "parent": function( elem ) { return !Expr.pseudos[ "empty" ]( elem ); }, // Element/input types "header": function( elem ) { return rheader.test( elem.nodeName ); }, "input": function( elem ) { return rinputs.test( elem.nodeName ); }, "button": function( elem ) { var name = elem.nodeName.toLowerCase(); return name === "input" && elem.type === "button" || name === "button"; }, "text": function( elem ) { var attr; return elem.nodeName.toLowerCase() === "input" && elem.type === "text" && // Support: IE<8 // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" ( ( attr = elem.getAttribute( "type" ) ) == null || attr.toLowerCase() === "text" ); }, // Position-in-collection "first": createPositionalPseudo( function() { return [ 0 ]; } ), "last": createPositionalPseudo( function( _matchIndexes, length ) { return [ length - 1 ]; } ), "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { return [ argument < 0 ? argument + length : argument ]; } ), "even": createPositionalPseudo( function( matchIndexes, length ) { var i = 0; for ( ; i < length; i += 2 ) { matchIndexes.push( i ); } return matchIndexes; } ), "odd": createPositionalPseudo( function( matchIndexes, length ) { var i = 1; for ( ; i < length; i += 2 ) { matchIndexes.push( i ); } return matchIndexes; } ), "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { var i = argument < 0 ? argument + length : argument > length ? length : argument; for ( ; --i >= 0; ) { matchIndexes.push( i ); } return matchIndexes; } ), "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { var i = argument < 0 ? argument + length : argument; for ( ; ++i < length; ) { matchIndexes.push( i ); } return matchIndexes; } ) } }; Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; // Add button/input type pseudos for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { Expr.pseudos[ i ] = createInputPseudo( i ); } for ( i in { submit: true, reset: true } ) { Expr.pseudos[ i ] = createButtonPseudo( i ); } // Easy API for creating new setFilters function setFilters() {} setFilters.prototype = Expr.filters = Expr.pseudos; Expr.setFilters = new setFilters(); tokenize = Sizzle.tokenize = function( selector, parseOnly ) { var matched, match, tokens, type, soFar, groups, preFilters, cached = tokenCache[ selector + " " ]; if ( cached ) { return parseOnly ? 0 : cached.slice( 0 ); } soFar = selector; groups = []; preFilters = Expr.preFilter; while ( soFar ) { // Comma and first run if ( !matched || ( match = rcomma.exec( soFar ) ) ) { if ( match ) { // Don't consume trailing commas as valid soFar = soFar.slice( match[ 0 ].length ) || soFar; } groups.push( ( tokens = [] ) ); } matched = false; // Combinators if ( ( match = rcombinators.exec( soFar ) ) ) { matched = match.shift(); tokens.push( { value: matched, // Cast descendant combinators to space type: match[ 0 ].replace( rtrim, " " ) } ); soFar = soFar.slice( matched.length ); } // Filters for ( type in Expr.filter ) { if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || ( match = preFilters[ type ]( match ) ) ) ) { matched = match.shift(); tokens.push( { value: matched, type: type, matches: match } ); soFar = soFar.slice( matched.length ); } } if ( !matched ) { break; } } // Return the length of the invalid excess // if we're just parsing // Otherwise, throw an error or return tokens return parseOnly ? soFar.length : soFar ? Sizzle.error( selector ) : // Cache the tokens tokenCache( selector, groups ).slice( 0 ); }; function toSelector( tokens ) { var i = 0, len = tokens.length, selector = ""; for ( ; i < len; i++ ) { selector += tokens[ i ].value; } return selector; } function addCombinator( matcher, combinator, base ) { var dir = combinator.dir, skip = combinator.next, key = skip || dir, checkNonElements = base && key === "parentNode", doneName = done++; return combinator.first ? // Check against closest ancestor/preceding element function( elem, context, xml ) { while ( ( elem = elem[ dir ] ) ) { if ( elem.nodeType === 1 || checkNonElements ) { return matcher( elem, context, xml ); } } return false; } : // Check against all ancestor/preceding elements function( elem, context, xml ) { var oldCache, uniqueCache, outerCache, newCache = [ dirruns, doneName ]; // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching if ( xml ) { while ( ( elem = elem[ dir ] ) ) { if ( elem.nodeType === 1 || checkNonElements ) { if ( matcher( elem, context, xml ) ) { return true; } } } } else { while ( ( elem = elem[ dir ] ) ) { if ( elem.nodeType === 1 || checkNonElements ) { outerCache = elem[ expando ] || ( elem[ expando ] = {} ); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ elem.uniqueID ] || ( outerCache[ elem.uniqueID ] = {} ); if ( skip && skip === elem.nodeName.toLowerCase() ) { elem = elem[ dir ] || elem; } else if ( ( oldCache = uniqueCache[ key ] ) && oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { // Assign to newCache so results back-propagate to previous elements return ( newCache[ 2 ] = oldCache[ 2 ] ); } else { // Reuse newcache so results back-propagate to previous elements uniqueCache[ key ] = newCache; // A match means we're done; a fail means we have to keep checking if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { return true; } } } } } return false; }; } function elementMatcher( matchers ) { return matchers.length > 1 ? function( elem, context, xml ) { var i = matchers.length; while ( i-- ) { if ( !matchers[ i ]( elem, context, xml ) ) { return false; } } return true; } : matchers[ 0 ]; } function multipleContexts( selector, contexts, results ) { var i = 0, len = contexts.length; for ( ; i < len; i++ ) { Sizzle( selector, contexts[ i ], results ); } return results; } function condense( unmatched, map, filter, context, xml ) { var elem, newUnmatched = [], i = 0, len = unmatched.length, mapped = map != null; for ( ; i < len; i++ ) { if ( ( elem = unmatched[ i ] ) ) { if ( !filter || filter( elem, context, xml ) ) { newUnmatched.push( elem ); if ( mapped ) { map.push( i ); } } } } return newUnmatched; } function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { if ( postFilter && !postFilter[ expando ] ) { postFilter = setMatcher( postFilter ); } if ( postFinder && !postFinder[ expando ] ) { postFinder = setMatcher( postFinder, postSelector ); } return markFunction( function( seed, results, context, xml ) { var temp, i, elem, preMap = [], postMap = [], preexisting = results.length, // Get initial elements from seed or context elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), // Prefilter to get matcher input, preserving a map for seed-results synchronization matcherIn = preFilter && ( seed || !selector ) ? condense( elems, preMap, preFilter, context, xml ) : elems, matcherOut = matcher ? // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, postFinder || ( seed ? preFilter : preexisting || postFilter ) ? // ...intermediate processing is necessary [] : // ...otherwise use results directly results : matcherIn; // Find primary matches if ( matcher ) { matcher( matcherIn, matcherOut, context, xml ); } // Apply postFilter if ( postFilter ) { temp = condense( matcherOut, postMap ); postFilter( temp, [], context, xml ); // Un-match failing elements by moving them back to matcherIn i = temp.length; while ( i-- ) { if ( ( elem = temp[ i ] ) ) { matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); } } } if ( seed ) { if ( postFinder || preFilter ) { if ( postFinder ) { // Get the final matcherOut by condensing this intermediate into postFinder contexts temp = []; i = matcherOut.length; while ( i-- ) { if ( ( elem = matcherOut[ i ] ) ) { // Restore matcherIn since elem is not yet a final match temp.push( ( matcherIn[ i ] = elem ) ); } } postFinder( null, ( matcherOut = [] ), temp, xml ); } // Move matched elements from seed to results to keep them synchronized i = matcherOut.length; while ( i-- ) { if ( ( elem = matcherOut[ i ] ) && ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { seed[ temp ] = !( results[ temp ] = elem ); } } } // Add elements to results, through postFinder if defined } else { matcherOut = condense( matcherOut === results ? matcherOut.splice( preexisting, matcherOut.length ) : matcherOut ); if ( postFinder ) { postFinder( null, results, matcherOut, xml ); } else { push.apply( results, matcherOut ); } } } ); } function matcherFromTokens( tokens ) { var checkContext, matcher, j, len = tokens.length, leadingRelative = Expr.relative[ tokens[ 0 ].type ], implicitRelative = leadingRelative || Expr.relative[ " " ], i = leadingRelative ? 1 : 0, // The foundational matcher ensures that elements are reachable from top-level context(s) matchContext = addCombinator( function( elem ) { return elem === checkContext; }, implicitRelative, true ), matchAnyContext = addCombinator( function( elem ) { return indexOf( checkContext, elem ) > -1; }, implicitRelative, true ), matchers = [ function( elem, context, xml ) { var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( ( checkContext = context ).nodeType ? matchContext( elem, context, xml ) : matchAnyContext( elem, context, xml ) ); // Avoid hanging onto element (issue #299) checkContext = null; return ret; } ]; for ( ; i < len; i++ ) { if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; } else { matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); // Return special upon seeing a positional matcher if ( matcher[ expando ] ) { // Find the next relative operator (if any) for proper handling j = ++i; for ( ; j < len; j++ ) { if ( Expr.relative[ tokens[ j ].type ] ) { break; } } return setMatcher( i > 1 && elementMatcher( matchers ), i > 1 && toSelector( // If the preceding token was a descendant combinator, insert an implicit any-element `*` tokens .slice( 0, i - 1 ) .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) ).replace( rtrim, "$1" ), matcher, i < j && matcherFromTokens( tokens.slice( i, j ) ), j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), j < len && toSelector( tokens ) ); } matchers.push( matcher ); } } return elementMatcher( matchers ); } function matcherFromGroupMatchers( elementMatchers, setMatchers ) { var bySet = setMatchers.length > 0, byElement = elementMatchers.length > 0, superMatcher = function( seed, context, xml, results, outermost ) { var elem, j, matcher, matchedCount = 0, i = "0", unmatched = seed && [], setMatched = [], contextBackup = outermostContext, // We must always have either seed elements or outermost context elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), // Use integer dirruns iff this is the outermost matcher dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), len = elems.length; if ( outermost ) { // Support: IE 11+, Edge 17 - 18+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing // two documents; shallow comparisons work. // eslint-disable-next-line eqeqeq outermostContext = context == document || context || outermost; } // Add elements passing elementMatchers directly to results // Support: IE<9, Safari // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { if ( byElement && elem ) { j = 0; // Support: IE 11+, Edge 17 - 18+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing // two documents; shallow comparisons work. // eslint-disable-next-line eqeqeq if ( !context && elem.ownerDocument != document ) { setDocument( elem ); xml = !documentIsHTML; } while ( ( matcher = elementMatchers[ j++ ] ) ) { if ( matcher( elem, context || document, xml ) ) { results.push( elem ); break; } } if ( outermost ) { dirruns = dirrunsUnique; } } // Track unmatched elements for set filters if ( bySet ) { // They will have gone through all possible matchers if ( ( elem = !matcher && elem ) ) { matchedCount--; } // Lengthen the array for every element, matched or not if ( seed ) { unmatched.push( elem ); } } } // `i` is now the count of elements visited above, and adding it to `matchedCount` // makes the latter nonnegative. matchedCount += i; // Apply set filters to unmatched elements // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` // equals `i`), unless we didn't visit _any_ elements in the above loop because we have // no element matchers and no seed. // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that // case, which will result in a "00" `matchedCount` that differs from `i` but is also // numerically zero. if ( bySet && i !== matchedCount ) { j = 0; while ( ( matcher = setMatchers[ j++ ] ) ) { matcher( unmatched, setMatched, context, xml ); } if ( seed ) { // Reintegrate element matches to eliminate the need for sorting if ( matchedCount > 0 ) { while ( i-- ) { if ( !( unmatched[ i ] || setMatched[ i ] ) ) { setMatched[ i ] = pop.call( results ); } } } // Discard index placeholder values to get only actual matches setMatched = condense( setMatched ); } // Add matches to results push.apply( results, setMatched ); // Seedless set matches succeeding multiple successful matchers stipulate sorting if ( outermost && !seed && setMatched.length > 0 && ( matchedCount + setMatchers.length ) > 1 ) { Sizzle.uniqueSort( results ); } } // Override manipulation of globals by nested matchers if ( outermost ) { dirruns = dirrunsUnique; outermostContext = contextBackup; } return unmatched; }; return bySet ? markFunction( superMatcher ) : superMatcher; } compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { var i, setMatchers = [], elementMatchers = [], cached = compilerCache[ selector + " " ]; if ( !cached ) { // Generate a function of recursive functions that can be used to check each element if ( !match ) { match = tokenize( selector ); } i = match.length; while ( i-- ) { cached = matcherFromTokens( match[ i ] ); if ( cached[ expando ] ) { setMatchers.push( cached ); } else { elementMatchers.push( cached ); } } // Cache the compiled function cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); // Save selector and tokenization cached.selector = selector; } return cached; }; /** * A low-level selection function that works with Sizzle's compiled * selector functions * @param {String|Function} selector A selector or a pre-compiled * selector function built with Sizzle.compile * @param {Element} context * @param {Array} [results] * @param {Array} [seed] A set of elements to match against */ select = Sizzle.select = function( selector, context, results, seed ) { var i, tokens, token, type, find, compiled = typeof selector === "function" && selector, match = !seed && tokenize( ( selector = compiled.selector || selector ) ); results = results || []; // Try to minimize operations if there is only one selector in the list and no seed // (the latter of which guarantees us context) if ( match.length === 1 ) { // Reduce context if the leading compound selector is an ID tokens = match[ 0 ] = match[ 0 ].slice( 0 ); if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { context = ( Expr.find[ "ID" ]( token.matches[ 0 ] .replace( runescape, funescape ), context ) || [] )[ 0 ]; if ( !context ) { return results; // Precompiled matchers will still verify ancestry, so step up a level } else if ( compiled ) { context = context.parentNode; } selector = selector.slice( tokens.shift().value.length ); } // Fetch a seed set for right-to-left matching i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; while ( i-- ) { token = tokens[ i ]; // Abort if we hit a combinator if ( Expr.relative[ ( type = token.type ) ] ) { break; } if ( ( find = Expr.find[ type ] ) ) { // Search, expanding context for leading sibling combinators if ( ( seed = find( token.matches[ 0 ].replace( runescape, funescape ), rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || context ) ) ) { // If seed is empty or no tokens remain, we can return early tokens.splice( i, 1 ); selector = seed.length && toSelector( tokens ); if ( !selector ) { push.apply( results, seed ); return results; } break; } } } } // Compile and execute a filtering function if one is not provided // Provide `match` to avoid retokenization if we modified the selector above ( compiled || compile( selector, match ) )( seed, context, !documentIsHTML, results, !context || rsibling.test( selector ) && testContext( context.parentNode ) || context ); return results; }; // One-time assignments // Sort stability support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; // Support: Chrome 14-35+ // Always assume duplicates if they aren't passed to the comparison function support.detectDuplicates = !!hasDuplicate; // Initialize against the default document setDocument(); // Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) // Detached nodes confoundingly follow *each other* support.sortDetached = assert( function( el ) { // Should return 1, but returns 4 (following) return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; } ); // Support: IE<8 // Prevent attribute/property "interpolation" // https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx if ( !assert( function( el ) { el.innerHTML = ""; return el.firstChild.getAttribute( "href" ) === "#"; } ) ) { addHandle( "type|href|height|width", function( elem, name, isXML ) { if ( !isXML ) { return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); } } ); } // Support: IE<9 // Use defaultValue in place of getAttribute("value") if ( !support.attributes || !assert( function( el ) { el.innerHTML = ""; el.firstChild.setAttribute( "value", "" ); return el.firstChild.getAttribute( "value" ) === ""; } ) ) { addHandle( "value", function( elem, _name, isXML ) { if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { return elem.defaultValue; } } ); } // Support: IE<9 // Use getAttributeNode to fetch booleans when getAttribute lies if ( !assert( function( el ) { return el.getAttribute( "disabled" ) == null; } ) ) { addHandle( booleans, function( elem, name, isXML ) { var val; if ( !isXML ) { return elem[ name ] === true ? name.toLowerCase() : ( val = elem.getAttributeNode( name ) ) && val.specified ? val.value : null; } } ); } return Sizzle; } )( window ); jQuery.find = Sizzle; jQuery.expr = Sizzle.selectors; // Deprecated jQuery.expr[ ":" ] = jQuery.expr.pseudos; jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; jQuery.text = Sizzle.getText; jQuery.isXMLDoc = Sizzle.isXML; jQuery.contains = Sizzle.contains; jQuery.escapeSelector = Sizzle.escape; var dir = function( elem, dir, until ) { var matched = [], truncate = until !== undefined; while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { if ( elem.nodeType === 1 ) { if ( truncate && jQuery( elem ).is( until ) ) { break; } matched.push( elem ); } } return matched; }; var siblings = function( n, elem ) { var matched = []; for ( ; n; n = n.nextSibling ) { if ( n.nodeType === 1 && n !== elem ) { matched.push( n ); } } return matched; }; var rneedsContext = jQuery.expr.match.needsContext; function nodeName( elem, name ) { return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); }; var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); // Implement the identical functionality for filter and not function winnow( elements, qualifier, not ) { if ( isFunction( qualifier ) ) { return jQuery.grep( elements, function( elem, i ) { return !!qualifier.call( elem, i, elem ) !== not; } ); } // Single element if ( qualifier.nodeType ) { return jQuery.grep( elements, function( elem ) { return ( elem === qualifier ) !== not; } ); } // Arraylike of elements (jQuery, arguments, Array) if ( typeof qualifier !== "string" ) { return jQuery.grep( elements, function( elem ) { return ( indexOf.call( qualifier, elem ) > -1 ) !== not; } ); } // Filtered directly for both simple and complex selectors return jQuery.filter( qualifier, elements, not ); } jQuery.filter = function( expr, elems, not ) { var elem = elems[ 0 ]; if ( not ) { expr = ":not(" + expr + ")"; } if ( elems.length === 1 && elem.nodeType === 1 ) { return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; } return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { return elem.nodeType === 1; } ) ); }; jQuery.fn.extend( { find: function( selector ) { var i, ret, len = this.length, self = this; if ( typeof selector !== "string" ) { return this.pushStack( jQuery( selector ).filter( function() { for ( i = 0; i < len; i++ ) { if ( jQuery.contains( self[ i ], this ) ) { return true; } } } ) ); } ret = this.pushStack( [] ); for ( i = 0; i < len; i++ ) { jQuery.find( selector, self[ i ], ret ); } return len > 1 ? jQuery.uniqueSort( ret ) : ret; }, filter: function( selector ) { return this.pushStack( winnow( this, selector || [], false ) ); }, not: function( selector ) { return this.pushStack( winnow( this, selector || [], true ) ); }, is: function( selector ) { return !!winnow( this, // If this is a positional/relative selector, check membership in the returned set // so $("p:first").is("p:last") won't return true for a doc with two "p". typeof selector === "string" && rneedsContext.test( selector ) ? jQuery( selector ) : selector || [], false ).length; } } ); // Initialize a jQuery object // A central reference to the root jQuery(document) var rootjQuery, // A simple way to check for HTML strings // Prioritize #id over to avoid XSS via location.hash (#9521) // Strict HTML recognition (#11290: must start with <) // Shortcut simple #id case for speed rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, init = jQuery.fn.init = function( selector, context, root ) { var match, elem; // HANDLE: $(""), $(null), $(undefined), $(false) if ( !selector ) { return this; } // Method init() accepts an alternate rootjQuery // so migrate can support jQuery.sub (gh-2101) root = root || rootjQuery; // Handle HTML strings if ( typeof selector === "string" ) { if ( selector[ 0 ] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) { // Assume that strings that start and end with <> are HTML and skip the regex check match = [ null, selector, null ]; } else { match = rquickExpr.exec( selector ); } // Match html or make sure no context is specified for #id if ( match && ( match[ 1 ] || !context ) ) { // HANDLE: $(html) -> $(array) if ( match[ 1 ] ) { context = context instanceof jQuery ? context[ 0 ] : context; // Option to run scripts is true for back-compat // Intentionally let the error be thrown if parseHTML is not present jQuery.merge( this, jQuery.parseHTML( match[ 1 ], context && context.nodeType ? context.ownerDocument || context : document, true ) ); // HANDLE: $(html, props) if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { for ( match in context ) { // Properties of context are called as methods if possible if ( isFunction( this[ match ] ) ) { this[ match ]( context[ match ] ); // ...and otherwise set as attributes } else { this.attr( match, context[ match ] ); } } } return this; // HANDLE: $(#id) } else { elem = document.getElementById( match[ 2 ] ); if ( elem ) { // Inject the element directly into the jQuery object this[ 0 ] = elem; this.length = 1; } return this; } // HANDLE: $(expr, $(...)) } else if ( !context || context.jquery ) { return ( context || root ).find( selector ); // HANDLE: $(expr, context) // (which is just equivalent to: $(context).find(expr) } else { return this.constructor( context ).find( selector ); } // HANDLE: $(DOMElement) } else if ( selector.nodeType ) { this[ 0 ] = selector; this.length = 1; return this; // HANDLE: $(function) // Shortcut for document ready } else if ( isFunction( selector ) ) { return root.ready !== undefined ? root.ready( selector ) : // Execute immediately if ready is not present selector( jQuery ); } return jQuery.makeArray( selector, this ); }; // Give the init function the jQuery prototype for later instantiation init.prototype = jQuery.fn; // Initialize central reference rootjQuery = jQuery( document ); var rparentsprev = /^(?:parents|prev(?:Until|All))/, // Methods guaranteed to produce a unique set when starting from a unique set guaranteedUnique = { children: true, contents: true, next: true, prev: true }; jQuery.fn.extend( { has: function( target ) { var targets = jQuery( target, this ), l = targets.length; return this.filter( function() { var i = 0; for ( ; i < l; i++ ) { if ( jQuery.contains( this, targets[ i ] ) ) { return true; } } } ); }, closest: function( selectors, context ) { var cur, i = 0, l = this.length, matched = [], targets = typeof selectors !== "string" && jQuery( selectors ); // Positional selectors never match, since there's no _selection_ context if ( !rneedsContext.test( selectors ) ) { for ( ; i < l; i++ ) { for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { // Always skip document fragments if ( cur.nodeType < 11 && ( targets ? targets.index( cur ) > -1 : // Don't pass non-elements to Sizzle cur.nodeType === 1 && jQuery.find.matchesSelector( cur, selectors ) ) ) { matched.push( cur ); break; } } } } return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); }, // Determine the position of an element within the set index: function( elem ) { // No argument, return index in parent if ( !elem ) { return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; } // Index in selector if ( typeof elem === "string" ) { return indexOf.call( jQuery( elem ), this[ 0 ] ); } // Locate the position of the desired element return indexOf.call( this, // If it receives a jQuery object, the first element is used elem.jquery ? elem[ 0 ] : elem ); }, add: function( selector, context ) { return this.pushStack( jQuery.uniqueSort( jQuery.merge( this.get(), jQuery( selector, context ) ) ) ); }, addBack: function( selector ) { return this.add( selector == null ? this.prevObject : this.prevObject.filter( selector ) ); } } ); function sibling( cur, dir ) { while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} return cur; } jQuery.each( { parent: function( elem ) { var parent = elem.parentNode; return parent && parent.nodeType !== 11 ? parent : null; }, parents: function( elem ) { return dir( elem, "parentNode" ); }, parentsUntil: function( elem, _i, until ) { return dir( elem, "parentNode", until ); }, next: function( elem ) { return sibling( elem, "nextSibling" ); }, prev: function( elem ) { return sibling( elem, "previousSibling" ); }, nextAll: function( elem ) { return dir( elem, "nextSibling" ); }, prevAll: function( elem ) { return dir( elem, "previousSibling" ); }, nextUntil: function( elem, _i, until ) { return dir( elem, "nextSibling", until ); }, prevUntil: function( elem, _i, until ) { return dir( elem, "previousSibling", until ); }, siblings: function( elem ) { return siblings( ( elem.parentNode || {} ).firstChild, elem ); }, children: function( elem ) { return siblings( elem.firstChild ); }, contents: function( elem ) { if ( elem.contentDocument != null && // Support: IE 11+ // elements with no `data` attribute has an object // `contentDocument` with a `null` prototype. getProto( elem.contentDocument ) ) { return elem.contentDocument; } // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only // Treat the template element as a regular one in browsers that // don't support it. if ( nodeName( elem, "template" ) ) { elem = elem.content || elem; } return jQuery.merge( [], elem.childNodes ); } }, function( name, fn ) { jQuery.fn[ name ] = function( until, selector ) { var matched = jQuery.map( this, fn, until ); if ( name.slice( -5 ) !== "Until" ) { selector = until; } if ( selector && typeof selector === "string" ) { matched = jQuery.filter( selector, matched ); } if ( this.length > 1 ) { // Remove duplicates if ( !guaranteedUnique[ name ] ) { jQuery.uniqueSort( matched ); } // Reverse order for parents* and prev-derivatives if ( rparentsprev.test( name ) ) { matched.reverse(); } } return this.pushStack( matched ); }; } ); var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); // Convert String-formatted options into Object-formatted ones function createOptions( options ) { var object = {}; jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { object[ flag ] = true; } ); return object; } /* * Create a callback list using the following parameters: * * options: an optional list of space-separated options that will change how * the callback list behaves or a more traditional option object * * By default a callback list will act like an event callback list and can be * "fired" multiple times. * * Possible options: * * once: will ensure the callback list can only be fired once (like a Deferred) * * memory: will keep track of previous values and will call any callback added * after the list has been fired right away with the latest "memorized" * values (like a Deferred) * * unique: will ensure a callback can only be added once (no duplicate in the list) * * stopOnFalse: interrupt callings when a callback returns false * */ jQuery.Callbacks = function( options ) { // Convert options from String-formatted to Object-formatted if needed // (we check in cache first) options = typeof options === "string" ? createOptions( options ) : jQuery.extend( {}, options ); var // Flag to know if list is currently firing firing, // Last fire value for non-forgettable lists memory, // Flag to know if list was already fired fired, // Flag to prevent firing locked, // Actual callback list list = [], // Queue of execution data for repeatable lists queue = [], // Index of currently firing callback (modified by add/remove as needed) firingIndex = -1, // Fire callbacks fire = function() { // Enforce single-firing locked = locked || options.once; // Execute callbacks for all pending executions, // respecting firingIndex overrides and runtime changes fired = firing = true; for ( ; queue.length; firingIndex = -1 ) { memory = queue.shift(); while ( ++firingIndex < list.length ) { // Run callback and check for early termination if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && options.stopOnFalse ) { // Jump to end and forget the data so .add doesn't re-fire firingIndex = list.length; memory = false; } } } // Forget the data if we're done with it if ( !options.memory ) { memory = false; } firing = false; // Clean up if we're done firing for good if ( locked ) { // Keep an empty list if we have data for future add calls if ( memory ) { list = []; // Otherwise, this object is spent } else { list = ""; } } }, // Actual Callbacks object self = { // Add a callback or a collection of callbacks to the list add: function() { if ( list ) { // If we have memory from a past run, we should fire after adding if ( memory && !firing ) { firingIndex = list.length - 1; queue.push( memory ); } ( function add( args ) { jQuery.each( args, function( _, arg ) { if ( isFunction( arg ) ) { if ( !options.unique || !self.has( arg ) ) { list.push( arg ); } } else if ( arg && arg.length && toType( arg ) !== "string" ) { // Inspect recursively add( arg ); } } ); } )( arguments ); if ( memory && !firing ) { fire(); } } return this; }, // Remove a callback from the list remove: function() { jQuery.each( arguments, function( _, arg ) { var index; while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { list.splice( index, 1 ); // Handle firing indexes if ( index <= firingIndex ) { firingIndex--; } } } ); return this; }, // Check if a given callback is in the list. // If no argument is given, return whether or not list has callbacks attached. has: function( fn ) { return fn ? jQuery.inArray( fn, list ) > -1 : list.length > 0; }, // Remove all callbacks from the list empty: function() { if ( list ) { list = []; } return this; }, // Disable .fire and .add // Abort any current/pending executions // Clear all callbacks and values disable: function() { locked = queue = []; list = memory = ""; return this; }, disabled: function() { return !list; }, // Disable .fire // Also disable .add unless we have memory (since it would have no effect) // Abort any pending executions lock: function() { locked = queue = []; if ( !memory && !firing ) { list = memory = ""; } return this; }, locked: function() { return !!locked; }, // Call all callbacks with the given context and arguments fireWith: function( context, args ) { if ( !locked ) { args = args || []; args = [ context, args.slice ? args.slice() : args ]; queue.push( args ); if ( !firing ) { fire(); } } return this; }, // Call all the callbacks with the given arguments fire: function() { self.fireWith( this, arguments ); return this; }, // To know if the callbacks have already been called at least once fired: function() { return !!fired; } }; return self; }; function Identity( v ) { return v; } function Thrower( ex ) { throw ex; } function adoptValue( value, resolve, reject, noValue ) { var method; try { // Check for promise aspect first to privilege synchronous behavior if ( value && isFunction( ( method = value.promise ) ) ) { method.call( value ).done( resolve ).fail( reject ); // Other thenables } else if ( value && isFunction( ( method = value.then ) ) ) { method.call( value, resolve, reject ); // Other non-thenables } else { // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: // * false: [ value ].slice( 0 ) => resolve( value ) // * true: [ value ].slice( 1 ) => resolve() resolve.apply( undefined, [ value ].slice( noValue ) ); } // For Promises/A+, convert exceptions into rejections // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in // Deferred#then to conditionally suppress rejection. } catch ( value ) { // Support: Android 4.0 only // Strict mode functions invoked without .call/.apply get global-object context reject.apply( undefined, [ value ] ); } } jQuery.extend( { Deferred: function( func ) { var tuples = [ // action, add listener, callbacks, // ... .then handlers, argument index, [final state] [ "notify", "progress", jQuery.Callbacks( "memory" ), jQuery.Callbacks( "memory" ), 2 ], [ "resolve", "done", jQuery.Callbacks( "once memory" ), jQuery.Callbacks( "once memory" ), 0, "resolved" ], [ "reject", "fail", jQuery.Callbacks( "once memory" ), jQuery.Callbacks( "once memory" ), 1, "rejected" ] ], state = "pending", promise = { state: function() { return state; }, always: function() { deferred.done( arguments ).fail( arguments ); return this; }, "catch": function( fn ) { return promise.then( null, fn ); }, // Keep pipe for back-compat pipe: function( /* fnDone, fnFail, fnProgress */ ) { var fns = arguments; return jQuery.Deferred( function( newDefer ) { jQuery.each( tuples, function( _i, tuple ) { // Map tuples (progress, done, fail) to arguments (done, fail, progress) var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; // deferred.progress(function() { bind to newDefer or newDefer.notify }) // deferred.done(function() { bind to newDefer or newDefer.resolve }) // deferred.fail(function() { bind to newDefer or newDefer.reject }) deferred[ tuple[ 1 ] ]( function() { var returned = fn && fn.apply( this, arguments ); if ( returned && isFunction( returned.promise ) ) { returned.promise() .progress( newDefer.notify ) .done( newDefer.resolve ) .fail( newDefer.reject ); } else { newDefer[ tuple[ 0 ] + "With" ]( this, fn ? [ returned ] : arguments ); } } ); } ); fns = null; } ).promise(); }, then: function( onFulfilled, onRejected, onProgress ) { var maxDepth = 0; function resolve( depth, deferred, handler, special ) { return function() { var that = this, args = arguments, mightThrow = function() { var returned, then; // Support: Promises/A+ section 2.3.3.3.3 // https://promisesaplus.com/#point-59 // Ignore double-resolution attempts if ( depth < maxDepth ) { return; } returned = handler.apply( that, args ); // Support: Promises/A+ section 2.3.1 // https://promisesaplus.com/#point-48 if ( returned === deferred.promise() ) { throw new TypeError( "Thenable self-resolution" ); } // Support: Promises/A+ sections 2.3.3.1, 3.5 // https://promisesaplus.com/#point-54 // https://promisesaplus.com/#point-75 // Retrieve `then` only once then = returned && // Support: Promises/A+ section 2.3.4 // https://promisesaplus.com/#point-64 // Only check objects and functions for thenability ( typeof returned === "object" || typeof returned === "function" ) && returned.then; // Handle a returned thenable if ( isFunction( then ) ) { // Special processors (notify) just wait for resolution if ( special ) { then.call( returned, resolve( maxDepth, deferred, Identity, special ), resolve( maxDepth, deferred, Thrower, special ) ); // Normal processors (resolve) also hook into progress } else { // ...and disregard older resolution values maxDepth++; then.call( returned, resolve( maxDepth, deferred, Identity, special ), resolve( maxDepth, deferred, Thrower, special ), resolve( maxDepth, deferred, Identity, deferred.notifyWith ) ); } // Handle all other returned values } else { // Only substitute handlers pass on context // and multiple values (non-spec behavior) if ( handler !== Identity ) { that = undefined; args = [ returned ]; } // Process the value(s) // Default process is resolve ( special || deferred.resolveWith )( that, args ); } }, // Only normal processors (resolve) catch and reject exceptions process = special ? mightThrow : function() { try { mightThrow(); } catch ( e ) { if ( jQuery.Deferred.exceptionHook ) { jQuery.Deferred.exceptionHook( e, process.stackTrace ); } // Support: Promises/A+ section 2.3.3.3.4.1 // https://promisesaplus.com/#point-61 // Ignore post-resolution exceptions if ( depth + 1 >= maxDepth ) { // Only substitute handlers pass on context // and multiple values (non-spec behavior) if ( handler !== Thrower ) { that = undefined; args = [ e ]; } deferred.rejectWith( that, args ); } } }; // Support: Promises/A+ section 2.3.3.3.1 // https://promisesaplus.com/#point-57 // Re-resolve promises immediately to dodge false rejection from // subsequent errors if ( depth ) { process(); } else { // Call an optional hook to record the stack, in case of exception // since it's otherwise lost when execution goes async if ( jQuery.Deferred.getStackHook ) { process.stackTrace = jQuery.Deferred.getStackHook(); } window.setTimeout( process ); } }; } return jQuery.Deferred( function( newDefer ) { // progress_handlers.add( ... ) tuples[ 0 ][ 3 ].add( resolve( 0, newDefer, isFunction( onProgress ) ? onProgress : Identity, newDefer.notifyWith ) ); // fulfilled_handlers.add( ... ) tuples[ 1 ][ 3 ].add( resolve( 0, newDefer, isFunction( onFulfilled ) ? onFulfilled : Identity ) ); // rejected_handlers.add( ... ) tuples[ 2 ][ 3 ].add( resolve( 0, newDefer, isFunction( onRejected ) ? onRejected : Thrower ) ); } ).promise(); }, // Get a promise for this deferred // If obj is provided, the promise aspect is added to the object promise: function( obj ) { return obj != null ? jQuery.extend( obj, promise ) : promise; } }, deferred = {}; // Add list-specific methods jQuery.each( tuples, function( i, tuple ) { var list = tuple[ 2 ], stateString = tuple[ 5 ]; // promise.progress = list.add // promise.done = list.add // promise.fail = list.add promise[ tuple[ 1 ] ] = list.add; // Handle state if ( stateString ) { list.add( function() { // state = "resolved" (i.e., fulfilled) // state = "rejected" state = stateString; }, // rejected_callbacks.disable // fulfilled_callbacks.disable tuples[ 3 - i ][ 2 ].disable, // rejected_handlers.disable // fulfilled_handlers.disable tuples[ 3 - i ][ 3 ].disable, // progress_callbacks.lock tuples[ 0 ][ 2 ].lock, // progress_handlers.lock tuples[ 0 ][ 3 ].lock ); } // progress_handlers.fire // fulfilled_handlers.fire // rejected_handlers.fire list.add( tuple[ 3 ].fire ); // deferred.notify = function() { deferred.notifyWith(...) } // deferred.resolve = function() { deferred.resolveWith(...) } // deferred.reject = function() { deferred.rejectWith(...) } deferred[ tuple[ 0 ] ] = function() { deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); return this; }; // deferred.notifyWith = list.fireWith // deferred.resolveWith = list.fireWith // deferred.rejectWith = list.fireWith deferred[ tuple[ 0 ] + "With" ] = list.fireWith; } ); // Make the deferred a promise promise.promise( deferred ); // Call given func if any if ( func ) { func.call( deferred, deferred ); } // All done! return deferred; }, // Deferred helper when: function( singleValue ) { var // count of uncompleted subordinates remaining = arguments.length, // count of unprocessed arguments i = remaining, // subordinate fulfillment data resolveContexts = Array( i ), resolveValues = slice.call( arguments ), // the master Deferred master = jQuery.Deferred(), // subordinate callback factory updateFunc = function( i ) { return function( value ) { resolveContexts[ i ] = this; resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; if ( !( --remaining ) ) { master.resolveWith( resolveContexts, resolveValues ); } }; }; // Single- and empty arguments are adopted like Promise.resolve if ( remaining <= 1 ) { adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, !remaining ); // Use .then() to unwrap secondary thenables (cf. gh-3000) if ( master.state() === "pending" || isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { return master.then(); } } // Multiple arguments are aggregated like Promise.all array elements while ( i-- ) { adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); } return master.promise(); } } ); // These usually indicate a programmer mistake during development, // warn about them ASAP rather than swallowing them by default. var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; jQuery.Deferred.exceptionHook = function( error, stack ) { // Support: IE 8 - 9 only // Console exists when dev tools are open, which can happen at any time if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); } }; jQuery.readyException = function( error ) { window.setTimeout( function() { throw error; } ); }; // The deferred used on DOM ready var readyList = jQuery.Deferred(); jQuery.fn.ready = function( fn ) { readyList .then( fn ) // Wrap jQuery.readyException in a function so that the lookup // happens at the time of error handling instead of callback // registration. .catch( function( error ) { jQuery.readyException( error ); } ); return this; }; jQuery.extend( { // Is the DOM ready to be used? Set to true once it occurs. isReady: false, // A counter to track how many items to wait for before // the ready event fires. See #6781 readyWait: 1, // Handle when the DOM is ready ready: function( wait ) { // Abort if there are pending holds or we're already ready if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { return; } // Remember that the DOM is ready jQuery.isReady = true; // If a normal DOM Ready event fired, decrement, and wait if need be if ( wait !== true && --jQuery.readyWait > 0 ) { return; } // If there are functions bound, to execute readyList.resolveWith( document, [ jQuery ] ); } } ); jQuery.ready.then = readyList.then; // The ready event handler and self cleanup method function completed() { document.removeEventListener( "DOMContentLoaded", completed ); window.removeEventListener( "load", completed ); jQuery.ready(); } // Catch cases where $(document).ready() is called // after the browser event has already occurred. // Support: IE <=9 - 10 only // Older IE sometimes signals "interactive" too soon if ( document.readyState === "complete" || ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { // Handle it asynchronously to allow scripts the opportunity to delay ready window.setTimeout( jQuery.ready ); } else { // Use the handy event callback document.addEventListener( "DOMContentLoaded", completed ); // A fallback to window.onload, that will always work window.addEventListener( "load", completed ); } // Multifunctional method to get and set values of a collection // The value/s can optionally be executed if it's a function var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { var i = 0, len = elems.length, bulk = key == null; // Sets many values if ( toType( key ) === "object" ) { chainable = true; for ( i in key ) { access( elems, fn, i, key[ i ], true, emptyGet, raw ); } // Sets one value } else if ( value !== undefined ) { chainable = true; if ( !isFunction( value ) ) { raw = true; } if ( bulk ) { // Bulk operations run against the entire set if ( raw ) { fn.call( elems, value ); fn = null; // ...except when executing function values } else { bulk = fn; fn = function( elem, _key, value ) { return bulk.call( jQuery( elem ), value ); }; } } if ( fn ) { for ( ; i < len; i++ ) { fn( elems[ i ], key, raw ? value : value.call( elems[ i ], i, fn( elems[ i ], key ) ) ); } } } if ( chainable ) { return elems; } // Gets if ( bulk ) { return fn.call( elems ); } return len ? fn( elems[ 0 ], key ) : emptyGet; }; // Matches dashed string for camelizing var rmsPrefix = /^-ms-/, rdashAlpha = /-([a-z])/g; // Used by camelCase as callback to replace() function fcamelCase( _all, letter ) { return letter.toUpperCase(); } // Convert dashed to camelCase; used by the css and data modules // Support: IE <=9 - 11, Edge 12 - 15 // Microsoft forgot to hump their vendor prefix (#9572) function camelCase( string ) { return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); } var acceptData = function( owner ) { // Accepts only: // - Node // - Node.ELEMENT_NODE // - Node.DOCUMENT_NODE // - Object // - Any return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); }; function Data() { this.expando = jQuery.expando + Data.uid++; } Data.uid = 1; Data.prototype = { cache: function( owner ) { // Check if the owner object already has a cache var value = owner[ this.expando ]; // If not, create one if ( !value ) { value = {}; // We can accept data for non-element nodes in modern browsers, // but we should not, see #8335. // Always return an empty object. if ( acceptData( owner ) ) { // If it is a node unlikely to be stringify-ed or looped over // use plain assignment if ( owner.nodeType ) { owner[ this.expando ] = value; // Otherwise secure it in a non-enumerable property // configurable must be true to allow the property to be // deleted when data is removed } else { Object.defineProperty( owner, this.expando, { value: value, configurable: true } ); } } } return value; }, set: function( owner, data, value ) { var prop, cache = this.cache( owner ); // Handle: [ owner, key, value ] args // Always use camelCase key (gh-2257) if ( typeof data === "string" ) { cache[ camelCase( data ) ] = value; // Handle: [ owner, { properties } ] args } else { // Copy the properties one-by-one to the cache object for ( prop in data ) { cache[ camelCase( prop ) ] = data[ prop ]; } } return cache; }, get: function( owner, key ) { return key === undefined ? this.cache( owner ) : // Always use camelCase key (gh-2257) owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; }, access: function( owner, key, value ) { // In cases where either: // // 1. No key was specified // 2. A string key was specified, but no value provided // // Take the "read" path and allow the get method to determine // which value to return, respectively either: // // 1. The entire cache object // 2. The data stored at the key // if ( key === undefined || ( ( key && typeof key === "string" ) && value === undefined ) ) { return this.get( owner, key ); } // When the key is not a string, or both a key and value // are specified, set or extend (existing objects) with either: // // 1. An object of properties // 2. A key and value // this.set( owner, key, value ); // Since the "set" path can have two possible entry points // return the expected data based on which path was taken[*] return value !== undefined ? value : key; }, remove: function( owner, key ) { var i, cache = owner[ this.expando ]; if ( cache === undefined ) { return; } if ( key !== undefined ) { // Support array or space separated string of keys if ( Array.isArray( key ) ) { // If key is an array of keys... // We always set camelCase keys, so remove that. key = key.map( camelCase ); } else { key = camelCase( key ); // If a key with the spaces exists, use it. // Otherwise, create an array by matching non-whitespace key = key in cache ? [ key ] : ( key.match( rnothtmlwhite ) || [] ); } i = key.length; while ( i-- ) { delete cache[ key[ i ] ]; } } // Remove the expando if there's no more data if ( key === undefined || jQuery.isEmptyObject( cache ) ) { // Support: Chrome <=35 - 45 // Webkit & Blink performance suffers when deleting properties // from DOM nodes, so set to undefined instead // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) if ( owner.nodeType ) { owner[ this.expando ] = undefined; } else { delete owner[ this.expando ]; } } }, hasData: function( owner ) { var cache = owner[ this.expando ]; return cache !== undefined && !jQuery.isEmptyObject( cache ); } }; var dataPriv = new Data(); var dataUser = new Data(); // Implementation Summary // // 1. Enforce API surface and semantic compatibility with 1.9.x branch // 2. Improve the module's maintainability by reducing the storage // paths to a single mechanism. // 3. Use the same single mechanism to support "private" and "user" data. // 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) // 5. Avoid exposing implementation details on user objects (eg. expando properties) // 6. Provide a clear path for implementation upgrade to WeakMap in 2014 var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, rmultiDash = /[A-Z]/g; function getData( data ) { if ( data === "true" ) { return true; } if ( data === "false" ) { return false; } if ( data === "null" ) { return null; } // Only convert to a number if it doesn't change the string if ( data === +data + "" ) { return +data; } if ( rbrace.test( data ) ) { return JSON.parse( data ); } return data; } function dataAttr( elem, key, data ) { var name; // If nothing was found internally, try to fetch any // data from the HTML5 data-* attribute if ( data === undefined && elem.nodeType === 1 ) { name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); data = elem.getAttribute( name ); if ( typeof data === "string" ) { try { data = getData( data ); } catch ( e ) {} // Make sure we set the data so it isn't changed later dataUser.set( elem, key, data ); } else { data = undefined; } } return data; } jQuery.extend( { hasData: function( elem ) { return dataUser.hasData( elem ) || dataPriv.hasData( elem ); }, data: function( elem, name, data ) { return dataUser.access( elem, name, data ); }, removeData: function( elem, name ) { dataUser.remove( elem, name ); }, // TODO: Now that all calls to _data and _removeData have been replaced // with direct calls to dataPriv methods, these can be deprecated. _data: function( elem, name, data ) { return dataPriv.access( elem, name, data ); }, _removeData: function( elem, name ) { dataPriv.remove( elem, name ); } } ); jQuery.fn.extend( { data: function( key, value ) { var i, name, data, elem = this[ 0 ], attrs = elem && elem.attributes; // Gets all values if ( key === undefined ) { if ( this.length ) { data = dataUser.get( elem ); if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { i = attrs.length; while ( i-- ) { // Support: IE 11 only // The attrs elements can be null (#14894) if ( attrs[ i ] ) { name = attrs[ i ].name; if ( name.indexOf( "data-" ) === 0 ) { name = camelCase( name.slice( 5 ) ); dataAttr( elem, name, data[ name ] ); } } } dataPriv.set( elem, "hasDataAttrs", true ); } } return data; } // Sets multiple values if ( typeof key === "object" ) { return this.each( function() { dataUser.set( this, key ); } ); } return access( this, function( value ) { var data; // The calling jQuery object (element matches) is not empty // (and therefore has an element appears at this[ 0 ]) and the // `value` parameter was not undefined. An empty jQuery object // will result in `undefined` for elem = this[ 0 ] which will // throw an exception if an attempt to read a data cache is made. if ( elem && value === undefined ) { // Attempt to get data from the cache // The key will always be camelCased in Data data = dataUser.get( elem, key ); if ( data !== undefined ) { return data; } // Attempt to "discover" the data in // HTML5 custom data-* attrs data = dataAttr( elem, key ); if ( data !== undefined ) { return data; } // We tried really hard, but the data doesn't exist. return; } // Set the data... this.each( function() { // We always store the camelCased key dataUser.set( this, key, value ); } ); }, null, value, arguments.length > 1, null, true ); }, removeData: function( key ) { return this.each( function() { dataUser.remove( this, key ); } ); } } ); jQuery.extend( { queue: function( elem, type, data ) { var queue; if ( elem ) { type = ( type || "fx" ) + "queue"; queue = dataPriv.get( elem, type ); // Speed up dequeue by getting out quickly if this is just a lookup if ( data ) { if ( !queue || Array.isArray( data ) ) { queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); } else { queue.push( data ); } } return queue || []; } }, dequeue: function( elem, type ) { type = type || "fx"; var queue = jQuery.queue( elem, type ), startLength = queue.length, fn = queue.shift(), hooks = jQuery._queueHooks( elem, type ), next = function() { jQuery.dequeue( elem, type ); }; // If the fx queue is dequeued, always remove the progress sentinel if ( fn === "inprogress" ) { fn = queue.shift(); startLength--; } if ( fn ) { // Add a progress sentinel to prevent the fx queue from being // automatically dequeued if ( type === "fx" ) { queue.unshift( "inprogress" ); } // Clear up the last queue stop function delete hooks.stop; fn.call( elem, next, hooks ); } if ( !startLength && hooks ) { hooks.empty.fire(); } }, // Not public - generate a queueHooks object, or return the current one _queueHooks: function( elem, type ) { var key = type + "queueHooks"; return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { empty: jQuery.Callbacks( "once memory" ).add( function() { dataPriv.remove( elem, [ type + "queue", key ] ); } ) } ); } } ); jQuery.fn.extend( { queue: function( type, data ) { var setter = 2; if ( typeof type !== "string" ) { data = type; type = "fx"; setter--; } if ( arguments.length < setter ) { return jQuery.queue( this[ 0 ], type ); } return data === undefined ? this : this.each( function() { var queue = jQuery.queue( this, type, data ); // Ensure a hooks for this queue jQuery._queueHooks( this, type ); if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { jQuery.dequeue( this, type ); } } ); }, dequeue: function( type ) { return this.each( function() { jQuery.dequeue( this, type ); } ); }, clearQueue: function( type ) { return this.queue( type || "fx", [] ); }, // Get a promise resolved when queues of a certain type // are emptied (fx is the type by default) promise: function( type, obj ) { var tmp, count = 1, defer = jQuery.Deferred(), elements = this, i = this.length, resolve = function() { if ( !( --count ) ) { defer.resolveWith( elements, [ elements ] ); } }; if ( typeof type !== "string" ) { obj = type; type = undefined; } type = type || "fx"; while ( i-- ) { tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); if ( tmp && tmp.empty ) { count++; tmp.empty.add( resolve ); } } resolve(); return defer.promise( obj ); } } ); var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; var documentElement = document.documentElement; var isAttached = function( elem ) { return jQuery.contains( elem.ownerDocument, elem ); }, composed = { composed: true }; // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only // Check attachment across shadow DOM boundaries when possible (gh-3504) // Support: iOS 10.0-10.2 only // Early iOS 10 versions support `attachShadow` but not `getRootNode`, // leading to errors. We need to check for `getRootNode`. if ( documentElement.getRootNode ) { isAttached = function( elem ) { return jQuery.contains( elem.ownerDocument, elem ) || elem.getRootNode( composed ) === elem.ownerDocument; }; } var isHiddenWithinTree = function( elem, el ) { // isHiddenWithinTree might be called from jQuery#filter function; // in that case, element will be second argument elem = el || elem; // Inline style trumps all return elem.style.display === "none" || elem.style.display === "" && // Otherwise, check computed style // Support: Firefox <=43 - 45 // Disconnected elements can have computed display: none, so first confirm that elem is // in the document. isAttached( elem ) && jQuery.css( elem, "display" ) === "none"; }; function adjustCSS( elem, prop, valueParts, tween ) { var adjusted, scale, maxIterations = 20, currentValue = tween ? function() { return tween.cur(); } : function() { return jQuery.css( elem, prop, "" ); }, initial = currentValue(), unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), // Starting value computation is required for potential unit mismatches initialInUnit = elem.nodeType && ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && rcssNum.exec( jQuery.css( elem, prop ) ); if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { // Support: Firefox <=54 // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) initial = initial / 2; // Trust units reported by jQuery.css unit = unit || initialInUnit[ 3 ]; // Iteratively approximate from a nonzero starting point initialInUnit = +initial || 1; while ( maxIterations-- ) { // Evaluate and update our best guess (doubling guesses that zero out). // Finish if the scale equals or crosses 1 (making the old*new product non-positive). jQuery.style( elem, prop, initialInUnit + unit ); if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { maxIterations = 0; } initialInUnit = initialInUnit / scale; } initialInUnit = initialInUnit * 2; jQuery.style( elem, prop, initialInUnit + unit ); // Make sure we update the tween properties later on valueParts = valueParts || []; } if ( valueParts ) { initialInUnit = +initialInUnit || +initial || 0; // Apply relative offset (+=/-=) if specified adjusted = valueParts[ 1 ] ? initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : +valueParts[ 2 ]; if ( tween ) { tween.unit = unit; tween.start = initialInUnit; tween.end = adjusted; } } return adjusted; } var defaultDisplayMap = {}; function getDefaultDisplay( elem ) { var temp, doc = elem.ownerDocument, nodeName = elem.nodeName, display = defaultDisplayMap[ nodeName ]; if ( display ) { return display; } temp = doc.body.appendChild( doc.createElement( nodeName ) ); display = jQuery.css( temp, "display" ); temp.parentNode.removeChild( temp ); if ( display === "none" ) { display = "block"; } defaultDisplayMap[ nodeName ] = display; return display; } function showHide( elements, show ) { var display, elem, values = [], index = 0, length = elements.length; // Determine new display value for elements that need to change for ( ; index < length; index++ ) { elem = elements[ index ]; if ( !elem.style ) { continue; } display = elem.style.display; if ( show ) { // Since we force visibility upon cascade-hidden elements, an immediate (and slow) // check is required in this first loop unless we have a nonempty display value (either // inline or about-to-be-restored) if ( display === "none" ) { values[ index ] = dataPriv.get( elem, "display" ) || null; if ( !values[ index ] ) { elem.style.display = ""; } } if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { values[ index ] = getDefaultDisplay( elem ); } } else { if ( display !== "none" ) { values[ index ] = "none"; // Remember what we're overwriting dataPriv.set( elem, "display", display ); } } } // Set the display of the elements in a second loop to avoid constant reflow for ( index = 0; index < length; index++ ) { if ( values[ index ] != null ) { elements[ index ].style.display = values[ index ]; } } return elements; } jQuery.fn.extend( { show: function() { return showHide( this, true ); }, hide: function() { return showHide( this ); }, toggle: function( state ) { if ( typeof state === "boolean" ) { return state ? this.show() : this.hide(); } return this.each( function() { if ( isHiddenWithinTree( this ) ) { jQuery( this ).show(); } else { jQuery( this ).hide(); } } ); } } ); var rcheckableType = ( /^(?:checkbox|radio)$/i ); var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); ( function() { var fragment = document.createDocumentFragment(), div = fragment.appendChild( document.createElement( "div" ) ), input = document.createElement( "input" ); // Support: Android 4.0 - 4.3 only // Check state lost if the name is set (#11217) // Support: Windows Web Apps (WWA) // `name` and `type` must use .setAttribute for WWA (#14901) input.setAttribute( "type", "radio" ); input.setAttribute( "checked", "checked" ); input.setAttribute( "name", "t" ); div.appendChild( input ); // Support: Android <=4.1 only // Older WebKit doesn't clone checked state correctly in fragments support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; // Support: IE <=11 only // Make sure textarea (and checkbox) defaultValue is properly cloned div.innerHTML = ""; support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; // Support: IE <=9 only // IE <=9 replaces "; support.option = !!div.lastChild; } )(); // We have to close these tags to support XHTML (#13200) var wrapMap = { // XHTML parsers do not magically insert elements in the // same way that tag soup parsers do. So we cannot shorten // this by omitting or other required elements. thead: [ 1, "", "
" ], col: [ 2, "", "
" ], tr: [ 2, "", "
" ], td: [ 3, "", "
" ], _default: [ 0, "", "" ] }; wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; wrapMap.th = wrapMap.td; // Support: IE <=9 only if ( !support.option ) { wrapMap.optgroup = wrapMap.option = [ 1, "" ]; } function getAll( context, tag ) { // Support: IE <=9 - 11 only // Use typeof to avoid zero-argument method invocation on host objects (#15151) var ret; if ( typeof context.getElementsByTagName !== "undefined" ) { ret = context.getElementsByTagName( tag || "*" ); } else if ( typeof context.querySelectorAll !== "undefined" ) { ret = context.querySelectorAll( tag || "*" ); } else { ret = []; } if ( tag === undefined || tag && nodeName( context, tag ) ) { return jQuery.merge( [ context ], ret ); } return ret; } // Mark scripts as having already been evaluated function setGlobalEval( elems, refElements ) { var i = 0, l = elems.length; for ( ; i < l; i++ ) { dataPriv.set( elems[ i ], "globalEval", !refElements || dataPriv.get( refElements[ i ], "globalEval" ) ); } } var rhtml = /<|&#?\w+;/; function buildFragment( elems, context, scripts, selection, ignored ) { var elem, tmp, tag, wrap, attached, j, fragment = context.createDocumentFragment(), nodes = [], i = 0, l = elems.length; for ( ; i < l; i++ ) { elem = elems[ i ]; if ( elem || elem === 0 ) { // Add nodes directly if ( toType( elem ) === "object" ) { // Support: Android <=4.0 only, PhantomJS 1 only // push.apply(_, arraylike) throws on ancient WebKit jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); // Convert non-html into a text node } else if ( !rhtml.test( elem ) ) { nodes.push( context.createTextNode( elem ) ); // Convert html into DOM nodes } else { tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); // Deserialize a standard representation tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); wrap = wrapMap[ tag ] || wrapMap._default; tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; // Descend through wrappers to the right content j = wrap[ 0 ]; while ( j-- ) { tmp = tmp.lastChild; } // Support: Android <=4.0 only, PhantomJS 1 only // push.apply(_, arraylike) throws on ancient WebKit jQuery.merge( nodes, tmp.childNodes ); // Remember the top-level container tmp = fragment.firstChild; // Ensure the created nodes are orphaned (#12392) tmp.textContent = ""; } } } // Remove wrapper from fragment fragment.textContent = ""; i = 0; while ( ( elem = nodes[ i++ ] ) ) { // Skip elements already in the context collection (trac-4087) if ( selection && jQuery.inArray( elem, selection ) > -1 ) { if ( ignored ) { ignored.push( elem ); } continue; } attached = isAttached( elem ); // Append to fragment tmp = getAll( fragment.appendChild( elem ), "script" ); // Preserve script evaluation history if ( attached ) { setGlobalEval( tmp ); } // Capture executables if ( scripts ) { j = 0; while ( ( elem = tmp[ j++ ] ) ) { if ( rscriptType.test( elem.type || "" ) ) { scripts.push( elem ); } } } } return fragment; } var rkeyEvent = /^key/, rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, rtypenamespace = /^([^.]*)(?:\.(.+)|)/; function returnTrue() { return true; } function returnFalse() { return false; } // Support: IE <=9 - 11+ // focus() and blur() are asynchronous, except when they are no-op. // So expect focus to be synchronous when the element is already active, // and blur to be synchronous when the element is not already active. // (focus and blur are always synchronous in other supported browsers, // this just defines when we can count on it). function expectSync( elem, type ) { return ( elem === safeActiveElement() ) === ( type === "focus" ); } // Support: IE <=9 only // Accessing document.activeElement can throw unexpectedly // https://bugs.jquery.com/ticket/13393 function safeActiveElement() { try { return document.activeElement; } catch ( err ) { } } function on( elem, types, selector, data, fn, one ) { var origFn, type; // Types can be a map of types/handlers if ( typeof types === "object" ) { // ( types-Object, selector, data ) if ( typeof selector !== "string" ) { // ( types-Object, data ) data = data || selector; selector = undefined; } for ( type in types ) { on( elem, type, selector, data, types[ type ], one ); } return elem; } if ( data == null && fn == null ) { // ( types, fn ) fn = selector; data = selector = undefined; } else if ( fn == null ) { if ( typeof selector === "string" ) { // ( types, selector, fn ) fn = data; data = undefined; } else { // ( types, data, fn ) fn = data; data = selector; selector = undefined; } } if ( fn === false ) { fn = returnFalse; } else if ( !fn ) { return elem; } if ( one === 1 ) { origFn = fn; fn = function( event ) { // Can use an empty set, since event contains the info jQuery().off( event ); return origFn.apply( this, arguments ); }; // Use same guid so caller can remove using origFn fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); } return elem.each( function() { jQuery.event.add( this, types, fn, data, selector ); } ); } /* * Helper functions for managing events -- not part of the public interface. * Props to Dean Edwards' addEvent library for many of the ideas. */ jQuery.event = { global: {}, add: function( elem, types, handler, data, selector ) { var handleObjIn, eventHandle, tmp, events, t, handleObj, special, handlers, type, namespaces, origType, elemData = dataPriv.get( elem ); // Only attach events to objects that accept data if ( !acceptData( elem ) ) { return; } // Caller can pass in an object of custom data in lieu of the handler if ( handler.handler ) { handleObjIn = handler; handler = handleObjIn.handler; selector = handleObjIn.selector; } // Ensure that invalid selectors throw exceptions at attach time // Evaluate against documentElement in case elem is a non-element node (e.g., document) if ( selector ) { jQuery.find.matchesSelector( documentElement, selector ); } // Make sure that the handler has a unique ID, used to find/remove it later if ( !handler.guid ) { handler.guid = jQuery.guid++; } // Init the element's event structure and main handler, if this is the first if ( !( events = elemData.events ) ) { events = elemData.events = Object.create( null ); } if ( !( eventHandle = elemData.handle ) ) { eventHandle = elemData.handle = function( e ) { // Discard the second event of a jQuery.event.trigger() and // when an event is called after a page has unloaded return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? jQuery.event.dispatch.apply( elem, arguments ) : undefined; }; } // Handle multiple events separated by a space types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; t = types.length; while ( t-- ) { tmp = rtypenamespace.exec( types[ t ] ) || []; type = origType = tmp[ 1 ]; namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); // There *must* be a type, no attaching namespace-only handlers if ( !type ) { continue; } // If event changes its type, use the special event handlers for the changed type special = jQuery.event.special[ type ] || {}; // If selector defined, determine special event api type, otherwise given type type = ( selector ? special.delegateType : special.bindType ) || type; // Update special based on newly reset type special = jQuery.event.special[ type ] || {}; // handleObj is passed to all event handlers handleObj = jQuery.extend( { type: type, origType: origType, data: data, handler: handler, guid: handler.guid, selector: selector, needsContext: selector && jQuery.expr.match.needsContext.test( selector ), namespace: namespaces.join( "." ) }, handleObjIn ); // Init the event handler queue if we're the first if ( !( handlers = events[ type ] ) ) { handlers = events[ type ] = []; handlers.delegateCount = 0; // Only use addEventListener if the special events handler returns false if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { if ( elem.addEventListener ) { elem.addEventListener( type, eventHandle ); } } } if ( special.add ) { special.add.call( elem, handleObj ); if ( !handleObj.handler.guid ) { handleObj.handler.guid = handler.guid; } } // Add to the element's handler list, delegates in front if ( selector ) { handlers.splice( handlers.delegateCount++, 0, handleObj ); } else { handlers.push( handleObj ); } // Keep track of which events have ever been used, for event optimization jQuery.event.global[ type ] = true; } }, // Detach an event or set of events from an element remove: function( elem, types, handler, selector, mappedTypes ) { var j, origCount, tmp, events, t, handleObj, special, handlers, type, namespaces, origType, elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); if ( !elemData || !( events = elemData.events ) ) { return; } // Once for each type.namespace in types; type may be omitted types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; t = types.length; while ( t-- ) { tmp = rtypenamespace.exec( types[ t ] ) || []; type = origType = tmp[ 1 ]; namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); // Unbind all events (on this namespace, if provided) for the element if ( !type ) { for ( type in events ) { jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); } continue; } special = jQuery.event.special[ type ] || {}; type = ( selector ? special.delegateType : special.bindType ) || type; handlers = events[ type ] || []; tmp = tmp[ 2 ] && new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); // Remove matching events origCount = j = handlers.length; while ( j-- ) { handleObj = handlers[ j ]; if ( ( mappedTypes || origType === handleObj.origType ) && ( !handler || handler.guid === handleObj.guid ) && ( !tmp || tmp.test( handleObj.namespace ) ) && ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { handlers.splice( j, 1 ); if ( handleObj.selector ) { handlers.delegateCount--; } if ( special.remove ) { special.remove.call( elem, handleObj ); } } } // Remove generic event handler if we removed something and no more handlers exist // (avoids potential for endless recursion during removal of special event handlers) if ( origCount && !handlers.length ) { if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { jQuery.removeEvent( elem, type, elemData.handle ); } delete events[ type ]; } } // Remove data and the expando if it's no longer used if ( jQuery.isEmptyObject( events ) ) { dataPriv.remove( elem, "handle events" ); } }, dispatch: function( nativeEvent ) { var i, j, ret, matched, handleObj, handlerQueue, args = new Array( arguments.length ), // Make a writable jQuery.Event from the native event object event = jQuery.event.fix( nativeEvent ), handlers = ( dataPriv.get( this, "events" ) || Object.create( null ) )[ event.type ] || [], special = jQuery.event.special[ event.type ] || {}; // Use the fix-ed jQuery.Event rather than the (read-only) native event args[ 0 ] = event; for ( i = 1; i < arguments.length; i++ ) { args[ i ] = arguments[ i ]; } event.delegateTarget = this; // Call the preDispatch hook for the mapped type, and let it bail if desired if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { return; } // Determine handlers handlerQueue = jQuery.event.handlers.call( this, event, handlers ); // Run delegates first; they may want to stop propagation beneath us i = 0; while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { event.currentTarget = matched.elem; j = 0; while ( ( handleObj = matched.handlers[ j++ ] ) && !event.isImmediatePropagationStopped() ) { // If the event is namespaced, then each handler is only invoked if it is // specially universal or its namespaces are a superset of the event's. if ( !event.rnamespace || handleObj.namespace === false || event.rnamespace.test( handleObj.namespace ) ) { event.handleObj = handleObj; event.data = handleObj.data; ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || handleObj.handler ).apply( matched.elem, args ); if ( ret !== undefined ) { if ( ( event.result = ret ) === false ) { event.preventDefault(); event.stopPropagation(); } } } } } // Call the postDispatch hook for the mapped type if ( special.postDispatch ) { special.postDispatch.call( this, event ); } return event.result; }, handlers: function( event, handlers ) { var i, handleObj, sel, matchedHandlers, matchedSelectors, handlerQueue = [], delegateCount = handlers.delegateCount, cur = event.target; // Find delegate handlers if ( delegateCount && // Support: IE <=9 // Black-hole SVG instance trees (trac-13180) cur.nodeType && // Support: Firefox <=42 // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click // Support: IE 11 only // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) !( event.type === "click" && event.button >= 1 ) ) { for ( ; cur !== this; cur = cur.parentNode || this ) { // Don't check non-elements (#13208) // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { matchedHandlers = []; matchedSelectors = {}; for ( i = 0; i < delegateCount; i++ ) { handleObj = handlers[ i ]; // Don't conflict with Object.prototype properties (#13203) sel = handleObj.selector + " "; if ( matchedSelectors[ sel ] === undefined ) { matchedSelectors[ sel ] = handleObj.needsContext ? jQuery( sel, this ).index( cur ) > -1 : jQuery.find( sel, this, null, [ cur ] ).length; } if ( matchedSelectors[ sel ] ) { matchedHandlers.push( handleObj ); } } if ( matchedHandlers.length ) { handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); } } } } // Add the remaining (directly-bound) handlers cur = this; if ( delegateCount < handlers.length ) { handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); } return handlerQueue; }, addProp: function( name, hook ) { Object.defineProperty( jQuery.Event.prototype, name, { enumerable: true, configurable: true, get: isFunction( hook ) ? function() { if ( this.originalEvent ) { return hook( this.originalEvent ); } } : function() { if ( this.originalEvent ) { return this.originalEvent[ name ]; } }, set: function( value ) { Object.defineProperty( this, name, { enumerable: true, configurable: true, writable: true, value: value } ); } } ); }, fix: function( originalEvent ) { return originalEvent[ jQuery.expando ] ? originalEvent : new jQuery.Event( originalEvent ); }, special: { load: { // Prevent triggered image.load events from bubbling to window.load noBubble: true }, click: { // Utilize native event to ensure correct state for checkable inputs setup: function( data ) { // For mutual compressibility with _default, replace `this` access with a local var. // `|| data` is dead code meant only to preserve the variable through minification. var el = this || data; // Claim the first handler if ( rcheckableType.test( el.type ) && el.click && nodeName( el, "input" ) ) { // dataPriv.set( el, "click", ... ) leverageNative( el, "click", returnTrue ); } // Return false to allow normal processing in the caller return false; }, trigger: function( data ) { // For mutual compressibility with _default, replace `this` access with a local var. // `|| data` is dead code meant only to preserve the variable through minification. var el = this || data; // Force setup before triggering a click if ( rcheckableType.test( el.type ) && el.click && nodeName( el, "input" ) ) { leverageNative( el, "click" ); } // Return non-false to allow normal event-path propagation return true; }, // For cross-browser consistency, suppress native .click() on links // Also prevent it if we're currently inside a leveraged native-event stack _default: function( event ) { var target = event.target; return rcheckableType.test( target.type ) && target.click && nodeName( target, "input" ) && dataPriv.get( target, "click" ) || nodeName( target, "a" ); } }, beforeunload: { postDispatch: function( event ) { // Support: Firefox 20+ // Firefox doesn't alert if the returnValue field is not set. if ( event.result !== undefined && event.originalEvent ) { event.originalEvent.returnValue = event.result; } } } } }; // Ensure the presence of an event listener that handles manually-triggered // synthetic events by interrupting progress until reinvoked in response to // *native* events that it fires directly, ensuring that state changes have // already occurred before other listeners are invoked. function leverageNative( el, type, expectSync ) { // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add if ( !expectSync ) { if ( dataPriv.get( el, type ) === undefined ) { jQuery.event.add( el, type, returnTrue ); } return; } // Register the controller as a special universal handler for all event namespaces dataPriv.set( el, type, false ); jQuery.event.add( el, type, { namespace: false, handler: function( event ) { var notAsync, result, saved = dataPriv.get( this, type ); if ( ( event.isTrigger & 1 ) && this[ type ] ) { // Interrupt processing of the outer synthetic .trigger()ed event // Saved data should be false in such cases, but might be a leftover capture object // from an async native handler (gh-4350) if ( !saved.length ) { // Store arguments for use when handling the inner native event // There will always be at least one argument (an event object), so this array // will not be confused with a leftover capture object. saved = slice.call( arguments ); dataPriv.set( this, type, saved ); // Trigger the native event and capture its result // Support: IE <=9 - 11+ // focus() and blur() are asynchronous notAsync = expectSync( this, type ); this[ type ](); result = dataPriv.get( this, type ); if ( saved !== result || notAsync ) { dataPriv.set( this, type, false ); } else { result = {}; } if ( saved !== result ) { // Cancel the outer synthetic event event.stopImmediatePropagation(); event.preventDefault(); return result.value; } // If this is an inner synthetic event for an event with a bubbling surrogate // (focus or blur), assume that the surrogate already propagated from triggering the // native event and prevent that from happening again here. // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the // bubbling surrogate propagates *after* the non-bubbling base), but that seems // less bad than duplication. } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { event.stopPropagation(); } // If this is a native event triggered above, everything is now in order // Fire an inner synthetic event with the original arguments } else if ( saved.length ) { // ...and capture the result dataPriv.set( this, type, { value: jQuery.event.trigger( // Support: IE <=9 - 11+ // Extend with the prototype to reset the above stopImmediatePropagation() jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), saved.slice( 1 ), this ) } ); // Abort handling of the native event event.stopImmediatePropagation(); } } } ); } jQuery.removeEvent = function( elem, type, handle ) { // This "if" is needed for plain objects if ( elem.removeEventListener ) { elem.removeEventListener( type, handle ); } }; jQuery.Event = function( src, props ) { // Allow instantiation without the 'new' keyword if ( !( this instanceof jQuery.Event ) ) { return new jQuery.Event( src, props ); } // Event object if ( src && src.type ) { this.originalEvent = src; this.type = src.type; // Events bubbling up the document may have been marked as prevented // by a handler lower down the tree; reflect the correct value. this.isDefaultPrevented = src.defaultPrevented || src.defaultPrevented === undefined && // Support: Android <=2.3 only src.returnValue === false ? returnTrue : returnFalse; // Create target properties // Support: Safari <=6 - 7 only // Target should not be a text node (#504, #13143) this.target = ( src.target && src.target.nodeType === 3 ) ? src.target.parentNode : src.target; this.currentTarget = src.currentTarget; this.relatedTarget = src.relatedTarget; // Event type } else { this.type = src; } // Put explicitly provided properties onto the event object if ( props ) { jQuery.extend( this, props ); } // Create a timestamp if incoming event doesn't have one this.timeStamp = src && src.timeStamp || Date.now(); // Mark it as fixed this[ jQuery.expando ] = true; }; // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding // https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html jQuery.Event.prototype = { constructor: jQuery.Event, isDefaultPrevented: returnFalse, isPropagationStopped: returnFalse, isImmediatePropagationStopped: returnFalse, isSimulated: false, preventDefault: function() { var e = this.originalEvent; this.isDefaultPrevented = returnTrue; if ( e && !this.isSimulated ) { e.preventDefault(); } }, stopPropagation: function() { var e = this.originalEvent; this.isPropagationStopped = returnTrue; if ( e && !this.isSimulated ) { e.stopPropagation(); } }, stopImmediatePropagation: function() { var e = this.originalEvent; this.isImmediatePropagationStopped = returnTrue; if ( e && !this.isSimulated ) { e.stopImmediatePropagation(); } this.stopPropagation(); } }; // Includes all common event props including KeyEvent and MouseEvent specific props jQuery.each( { altKey: true, bubbles: true, cancelable: true, changedTouches: true, ctrlKey: true, detail: true, eventPhase: true, metaKey: true, pageX: true, pageY: true, shiftKey: true, view: true, "char": true, code: true, charCode: true, key: true, keyCode: true, button: true, buttons: true, clientX: true, clientY: true, offsetX: true, offsetY: true, pointerId: true, pointerType: true, screenX: true, screenY: true, targetTouches: true, toElement: true, touches: true, which: function( event ) { var button = event.button; // Add which for key events if ( event.which == null && rkeyEvent.test( event.type ) ) { return event.charCode != null ? event.charCode : event.keyCode; } // Add which for click: 1 === left; 2 === middle; 3 === right if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { if ( button & 1 ) { return 1; } if ( button & 2 ) { return 3; } if ( button & 4 ) { return 2; } return 0; } return event.which; } }, jQuery.event.addProp ); jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { jQuery.event.special[ type ] = { // Utilize native event if possible so blur/focus sequence is correct setup: function() { // Claim the first handler // dataPriv.set( this, "focus", ... ) // dataPriv.set( this, "blur", ... ) leverageNative( this, type, expectSync ); // Return false to allow normal processing in the caller return false; }, trigger: function() { // Force setup before trigger leverageNative( this, type ); // Return non-false to allow normal event-path propagation return true; }, delegateType: delegateType }; } ); // Create mouseenter/leave events using mouseover/out and event-time checks // so that event delegation works in jQuery. // Do the same for pointerenter/pointerleave and pointerover/pointerout // // Support: Safari 7 only // Safari sends mouseenter too often; see: // https://bugs.chromium.org/p/chromium/issues/detail?id=470258 // for the description of the bug (it existed in older Chrome versions as well). jQuery.each( { mouseenter: "mouseover", mouseleave: "mouseout", pointerenter: "pointerover", pointerleave: "pointerout" }, function( orig, fix ) { jQuery.event.special[ orig ] = { delegateType: fix, bindType: fix, handle: function( event ) { var ret, target = this, related = event.relatedTarget, handleObj = event.handleObj; // For mouseenter/leave call the handler if related is outside the target. // NB: No relatedTarget if the mouse left/entered the browser window if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { event.type = handleObj.origType; ret = handleObj.handler.apply( this, arguments ); event.type = fix; } return ret; } }; } ); jQuery.fn.extend( { on: function( types, selector, data, fn ) { return on( this, types, selector, data, fn ); }, one: function( types, selector, data, fn ) { return on( this, types, selector, data, fn, 1 ); }, off: function( types, selector, fn ) { var handleObj, type; if ( types && types.preventDefault && types.handleObj ) { // ( event ) dispatched jQuery.Event handleObj = types.handleObj; jQuery( types.delegateTarget ).off( handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, handleObj.selector, handleObj.handler ); return this; } if ( typeof types === "object" ) { // ( types-object [, selector] ) for ( type in types ) { this.off( type, selector, types[ type ] ); } return this; } if ( selector === false || typeof selector === "function" ) { // ( types [, fn] ) fn = selector; selector = undefined; } if ( fn === false ) { fn = returnFalse; } return this.each( function() { jQuery.event.remove( this, types, fn, selector ); } ); } } ); var // Support: IE <=10 - 11, Edge 12 - 13 only // In IE/Edge using regex groups here causes severe slowdowns. // See https://connect.microsoft.com/IE/feedback/details/1736512/ rnoInnerhtml = /\s*$/g; // Prefer a tbody over its parent table for containing new rows function manipulationTarget( elem, content ) { if ( nodeName( elem, "table" ) && nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { return jQuery( elem ).children( "tbody" )[ 0 ] || elem; } return elem; } // Replace/restore the type attribute of script elements for safe DOM manipulation function disableScript( elem ) { elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; return elem; } function restoreScript( elem ) { if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { elem.type = elem.type.slice( 5 ); } else { elem.removeAttribute( "type" ); } return elem; } function cloneCopyEvent( src, dest ) { var i, l, type, pdataOld, udataOld, udataCur, events; if ( dest.nodeType !== 1 ) { return; } // 1. Copy private data: events, handlers, etc. if ( dataPriv.hasData( src ) ) { pdataOld = dataPriv.get( src ); events = pdataOld.events; if ( events ) { dataPriv.remove( dest, "handle events" ); for ( type in events ) { for ( i = 0, l = events[ type ].length; i < l; i++ ) { jQuery.event.add( dest, type, events[ type ][ i ] ); } } } } // 2. Copy user data if ( dataUser.hasData( src ) ) { udataOld = dataUser.access( src ); udataCur = jQuery.extend( {}, udataOld ); dataUser.set( dest, udataCur ); } } // Fix IE bugs, see support tests function fixInput( src, dest ) { var nodeName = dest.nodeName.toLowerCase(); // Fails to persist the checked state of a cloned checkbox or radio button. if ( nodeName === "input" && rcheckableType.test( src.type ) ) { dest.checked = src.checked; // Fails to return the selected option to the default selected state when cloning options } else if ( nodeName === "input" || nodeName === "textarea" ) { dest.defaultValue = src.defaultValue; } } function domManip( collection, args, callback, ignored ) { // Flatten any nested arrays args = flat( args ); var fragment, first, scripts, hasScripts, node, doc, i = 0, l = collection.length, iNoClone = l - 1, value = args[ 0 ], valueIsFunction = isFunction( value ); // We can't cloneNode fragments that contain checked, in WebKit if ( valueIsFunction || ( l > 1 && typeof value === "string" && !support.checkClone && rchecked.test( value ) ) ) { return collection.each( function( index ) { var self = collection.eq( index ); if ( valueIsFunction ) { args[ 0 ] = value.call( this, index, self.html() ); } domManip( self, args, callback, ignored ); } ); } if ( l ) { fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); first = fragment.firstChild; if ( fragment.childNodes.length === 1 ) { fragment = first; } // Require either new content or an interest in ignored elements to invoke the callback if ( first || ignored ) { scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); hasScripts = scripts.length; // Use the original fragment for the last item // instead of the first because it can end up // being emptied incorrectly in certain situations (#8070). for ( ; i < l; i++ ) { node = fragment; if ( i !== iNoClone ) { node = jQuery.clone( node, true, true ); // Keep references to cloned scripts for later restoration if ( hasScripts ) { // Support: Android <=4.0 only, PhantomJS 1 only // push.apply(_, arraylike) throws on ancient WebKit jQuery.merge( scripts, getAll( node, "script" ) ); } } callback.call( collection[ i ], node, i ); } if ( hasScripts ) { doc = scripts[ scripts.length - 1 ].ownerDocument; // Reenable scripts jQuery.map( scripts, restoreScript ); // Evaluate executable scripts on first document insertion for ( i = 0; i < hasScripts; i++ ) { node = scripts[ i ]; if ( rscriptType.test( node.type || "" ) && !dataPriv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) { if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { // Optional AJAX dependency, but won't run scripts if not present if ( jQuery._evalUrl && !node.noModule ) { jQuery._evalUrl( node.src, { nonce: node.nonce || node.getAttribute( "nonce" ) }, doc ); } } else { DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); } } } } } } return collection; } function remove( elem, selector, keepData ) { var node, nodes = selector ? jQuery.filter( selector, elem ) : elem, i = 0; for ( ; ( node = nodes[ i ] ) != null; i++ ) { if ( !keepData && node.nodeType === 1 ) { jQuery.cleanData( getAll( node ) ); } if ( node.parentNode ) { if ( keepData && isAttached( node ) ) { setGlobalEval( getAll( node, "script" ) ); } node.parentNode.removeChild( node ); } } return elem; } jQuery.extend( { htmlPrefilter: function( html ) { return html; }, clone: function( elem, dataAndEvents, deepDataAndEvents ) { var i, l, srcElements, destElements, clone = elem.cloneNode( true ), inPage = isAttached( elem ); // Fix IE cloning issues if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && !jQuery.isXMLDoc( elem ) ) { // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 destElements = getAll( clone ); srcElements = getAll( elem ); for ( i = 0, l = srcElements.length; i < l; i++ ) { fixInput( srcElements[ i ], destElements[ i ] ); } } // Copy the events from the original to the clone if ( dataAndEvents ) { if ( deepDataAndEvents ) { srcElements = srcElements || getAll( elem ); destElements = destElements || getAll( clone ); for ( i = 0, l = srcElements.length; i < l; i++ ) { cloneCopyEvent( srcElements[ i ], destElements[ i ] ); } } else { cloneCopyEvent( elem, clone ); } } // Preserve script evaluation history destElements = getAll( clone, "script" ); if ( destElements.length > 0 ) { setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); } // Return the cloned set return clone; }, cleanData: function( elems ) { var data, elem, type, special = jQuery.event.special, i = 0; for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { if ( acceptData( elem ) ) { if ( ( data = elem[ dataPriv.expando ] ) ) { if ( data.events ) { for ( type in data.events ) { if ( special[ type ] ) { jQuery.event.remove( elem, type ); // This is a shortcut to avoid jQuery.event.remove's overhead } else { jQuery.removeEvent( elem, type, data.handle ); } } } // Support: Chrome <=35 - 45+ // Assign undefined instead of using delete, see Data#remove elem[ dataPriv.expando ] = undefined; } if ( elem[ dataUser.expando ] ) { // Support: Chrome <=35 - 45+ // Assign undefined instead of using delete, see Data#remove elem[ dataUser.expando ] = undefined; } } } } } ); jQuery.fn.extend( { detach: function( selector ) { return remove( this, selector, true ); }, remove: function( selector ) { return remove( this, selector ); }, text: function( value ) { return access( this, function( value ) { return value === undefined ? jQuery.text( this ) : this.empty().each( function() { if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { this.textContent = value; } } ); }, null, value, arguments.length ); }, append: function() { return domManip( this, arguments, function( elem ) { if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { var target = manipulationTarget( this, elem ); target.appendChild( elem ); } } ); }, prepend: function() { return domManip( this, arguments, function( elem ) { if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { var target = manipulationTarget( this, elem ); target.insertBefore( elem, target.firstChild ); } } ); }, before: function() { return domManip( this, arguments, function( elem ) { if ( this.parentNode ) { this.parentNode.insertBefore( elem, this ); } } ); }, after: function() { return domManip( this, arguments, function( elem ) { if ( this.parentNode ) { this.parentNode.insertBefore( elem, this.nextSibling ); } } ); }, empty: function() { var elem, i = 0; for ( ; ( elem = this[ i ] ) != null; i++ ) { if ( elem.nodeType === 1 ) { // Prevent memory leaks jQuery.cleanData( getAll( elem, false ) ); // Remove any remaining nodes elem.textContent = ""; } } return this; }, clone: function( dataAndEvents, deepDataAndEvents ) { dataAndEvents = dataAndEvents == null ? false : dataAndEvents; deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; return this.map( function() { return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); } ); }, html: function( value ) { return access( this, function( value ) { var elem = this[ 0 ] || {}, i = 0, l = this.length; if ( value === undefined && elem.nodeType === 1 ) { return elem.innerHTML; } // See if we can take a shortcut and just use innerHTML if ( typeof value === "string" && !rnoInnerhtml.test( value ) && !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { value = jQuery.htmlPrefilter( value ); try { for ( ; i < l; i++ ) { elem = this[ i ] || {}; // Remove element nodes and prevent memory leaks if ( elem.nodeType === 1 ) { jQuery.cleanData( getAll( elem, false ) ); elem.innerHTML = value; } } elem = 0; // If using innerHTML throws an exception, use the fallback method } catch ( e ) {} } if ( elem ) { this.empty().append( value ); } }, null, value, arguments.length ); }, replaceWith: function() { var ignored = []; // Make the changes, replacing each non-ignored context element with the new content return domManip( this, arguments, function( elem ) { var parent = this.parentNode; if ( jQuery.inArray( this, ignored ) < 0 ) { jQuery.cleanData( getAll( this ) ); if ( parent ) { parent.replaceChild( elem, this ); } } // Force callback invocation }, ignored ); } } ); jQuery.each( { appendTo: "append", prependTo: "prepend", insertBefore: "before", insertAfter: "after", replaceAll: "replaceWith" }, function( name, original ) { jQuery.fn[ name ] = function( selector ) { var elems, ret = [], insert = jQuery( selector ), last = insert.length - 1, i = 0; for ( ; i <= last; i++ ) { elems = i === last ? this : this.clone( true ); jQuery( insert[ i ] )[ original ]( elems ); // Support: Android <=4.0 only, PhantomJS 1 only // .get() because push.apply(_, arraylike) throws on ancient WebKit push.apply( ret, elems.get() ); } return this.pushStack( ret ); }; } ); var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); var getStyles = function( elem ) { // Support: IE <=11 only, Firefox <=30 (#15098, #14150) // IE throws on elements created in popups // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" var view = elem.ownerDocument.defaultView; if ( !view || !view.opener ) { view = window; } return view.getComputedStyle( elem ); }; var swap = function( elem, options, callback ) { var ret, name, old = {}; // Remember the old values, and insert the new ones for ( name in options ) { old[ name ] = elem.style[ name ]; elem.style[ name ] = options[ name ]; } ret = callback.call( elem ); // Revert the old values for ( name in options ) { elem.style[ name ] = old[ name ]; } return ret; }; var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); ( function() { // Executing both pixelPosition & boxSizingReliable tests require only one layout // so they're executed at the same time to save the second computation. function computeStyleTests() { // This is a singleton, we need to execute it only once if ( !div ) { return; } container.style.cssText = "position:absolute;left:-11111px;width:60px;" + "margin-top:1px;padding:0;border:0"; div.style.cssText = "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + "margin:auto;border:1px;padding:1px;" + "width:60%;top:1%"; documentElement.appendChild( container ).appendChild( div ); var divStyle = window.getComputedStyle( div ); pixelPositionVal = divStyle.top !== "1%"; // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 // Some styles come back with percentage values, even though they shouldn't div.style.right = "60%"; pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; // Support: IE 9 - 11 only // Detect misreporting of content dimensions for box-sizing:border-box elements boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; // Support: IE 9 only // Detect overflow:scroll screwiness (gh-3699) // Support: Chrome <=64 // Don't get tricked when zoom affects offsetWidth (gh-4029) div.style.position = "absolute"; scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; documentElement.removeChild( container ); // Nullify the div so it wouldn't be stored in the memory and // it will also be a sign that checks already performed div = null; } function roundPixelMeasures( measure ) { return Math.round( parseFloat( measure ) ); } var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, reliableTrDimensionsVal, reliableMarginLeftVal, container = document.createElement( "div" ), div = document.createElement( "div" ); // Finish early in limited (non-browser) environments if ( !div.style ) { return; } // Support: IE <=9 - 11 only // Style of cloned element affects source element cloned (#8908) div.style.backgroundClip = "content-box"; div.cloneNode( true ).style.backgroundClip = ""; support.clearCloneStyle = div.style.backgroundClip === "content-box"; jQuery.extend( support, { boxSizingReliable: function() { computeStyleTests(); return boxSizingReliableVal; }, pixelBoxStyles: function() { computeStyleTests(); return pixelBoxStylesVal; }, pixelPosition: function() { computeStyleTests(); return pixelPositionVal; }, reliableMarginLeft: function() { computeStyleTests(); return reliableMarginLeftVal; }, scrollboxSize: function() { computeStyleTests(); return scrollboxSizeVal; }, // Support: IE 9 - 11+, Edge 15 - 18+ // IE/Edge misreport `getComputedStyle` of table rows with width/height // set in CSS while `offset*` properties report correct values. // Behavior in IE 9 is more subtle than in newer versions & it passes // some versions of this test; make sure not to make it pass there! reliableTrDimensions: function() { var table, tr, trChild, trStyle; if ( reliableTrDimensionsVal == null ) { table = document.createElement( "table" ); tr = document.createElement( "tr" ); trChild = document.createElement( "div" ); table.style.cssText = "position:absolute;left:-11111px"; tr.style.height = "1px"; trChild.style.height = "9px"; documentElement .appendChild( table ) .appendChild( tr ) .appendChild( trChild ); trStyle = window.getComputedStyle( tr ); reliableTrDimensionsVal = parseInt( trStyle.height ) > 3; documentElement.removeChild( table ); } return reliableTrDimensionsVal; } } ); } )(); function curCSS( elem, name, computed ) { var width, minWidth, maxWidth, ret, // Support: Firefox 51+ // Retrieving style before computed somehow // fixes an issue with getting wrong values // on detached elements style = elem.style; computed = computed || getStyles( elem ); // getPropertyValue is needed for: // .css('filter') (IE 9 only, #12537) // .css('--customProperty) (#3144) if ( computed ) { ret = computed.getPropertyValue( name ) || computed[ name ]; if ( ret === "" && !isAttached( elem ) ) { ret = jQuery.style( elem, name ); } // A tribute to the "awesome hack by Dean Edwards" // Android Browser returns percentage for some values, // but width seems to be reliably pixels. // This is against the CSSOM draft spec: // https://drafts.csswg.org/cssom/#resolved-values if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { // Remember the original values width = style.width; minWidth = style.minWidth; maxWidth = style.maxWidth; // Put in the new values to get a computed value out style.minWidth = style.maxWidth = style.width = ret; ret = computed.width; // Revert the changed values style.width = width; style.minWidth = minWidth; style.maxWidth = maxWidth; } } return ret !== undefined ? // Support: IE <=9 - 11 only // IE returns zIndex value as an integer. ret + "" : ret; } function addGetHookIf( conditionFn, hookFn ) { // Define the hook, we'll check on the first run if it's really needed. return { get: function() { if ( conditionFn() ) { // Hook not needed (or it's not possible to use it due // to missing dependency), remove it. delete this.get; return; } // Hook needed; redefine it so that the support test is not executed again. return ( this.get = hookFn ).apply( this, arguments ); } }; } var cssPrefixes = [ "Webkit", "Moz", "ms" ], emptyStyle = document.createElement( "div" ).style, vendorProps = {}; // Return a vendor-prefixed property or undefined function vendorPropName( name ) { // Check for vendor prefixed names var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), i = cssPrefixes.length; while ( i-- ) { name = cssPrefixes[ i ] + capName; if ( name in emptyStyle ) { return name; } } } // Return a potentially-mapped jQuery.cssProps or vendor prefixed property function finalPropName( name ) { var final = jQuery.cssProps[ name ] || vendorProps[ name ]; if ( final ) { return final; } if ( name in emptyStyle ) { return name; } return vendorProps[ name ] = vendorPropName( name ) || name; } var // Swappable if display is none or starts with table // except "table", "table-cell", or "table-caption" // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display rdisplayswap = /^(none|table(?!-c[ea]).+)/, rcustomProp = /^--/, cssShow = { position: "absolute", visibility: "hidden", display: "block" }, cssNormalTransform = { letterSpacing: "0", fontWeight: "400" }; function setPositiveNumber( _elem, value, subtract ) { // Any relative (+/-) values have already been // normalized at this point var matches = rcssNum.exec( value ); return matches ? // Guard against undefined "subtract", e.g., when used as in cssHooks Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : value; } function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { var i = dimension === "width" ? 1 : 0, extra = 0, delta = 0; // Adjustment may not be necessary if ( box === ( isBorderBox ? "border" : "content" ) ) { return 0; } for ( ; i < 4; i += 2 ) { // Both box models exclude margin if ( box === "margin" ) { delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); } // If we get here with a content-box, we're seeking "padding" or "border" or "margin" if ( !isBorderBox ) { // Add padding delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); // For "border" or "margin", add border if ( box !== "padding" ) { delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); // But still keep track of it otherwise } else { extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); } // If we get here with a border-box (content + padding + border), we're seeking "content" or // "padding" or "margin" } else { // For "content", subtract padding if ( box === "content" ) { delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); } // For "content" or "padding", subtract border if ( box !== "margin" ) { delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); } } } // Account for positive content-box scroll gutter when requested by providing computedVal if ( !isBorderBox && computedVal >= 0 ) { // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border // Assuming integer scroll gutter, subtract the rest and round down delta += Math.max( 0, Math.ceil( elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - computedVal - delta - extra - 0.5 // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter // Use an explicit zero to avoid NaN (gh-3964) ) ) || 0; } return delta; } function getWidthOrHeight( elem, dimension, extra ) { // Start with computed style var styles = getStyles( elem ), // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). // Fake content-box until we know it's needed to know the true value. boxSizingNeeded = !support.boxSizingReliable() || extra, isBorderBox = boxSizingNeeded && jQuery.css( elem, "boxSizing", false, styles ) === "border-box", valueIsBorderBox = isBorderBox, val = curCSS( elem, dimension, styles ), offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); // Support: Firefox <=54 // Return a confounding non-pixel value or feign ignorance, as appropriate. if ( rnumnonpx.test( val ) ) { if ( !extra ) { return val; } val = "auto"; } // Support: IE 9 - 11 only // Use offsetWidth/offsetHeight for when box sizing is unreliable. // In those cases, the computed value can be trusted to be border-box. if ( ( !support.boxSizingReliable() && isBorderBox || // Support: IE 10 - 11+, Edge 15 - 18+ // IE/Edge misreport `getComputedStyle` of table rows with width/height // set in CSS while `offset*` properties report correct values. // Interestingly, in some cases IE 9 doesn't suffer from this issue. !support.reliableTrDimensions() && nodeName( elem, "tr" ) || // Fall back to offsetWidth/offsetHeight when value is "auto" // This happens for inline elements with no explicit setting (gh-3571) val === "auto" || // Support: Android <=4.1 - 4.3 only // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && // Make sure the element is visible & connected elem.getClientRects().length ) { isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; // Where available, offsetWidth/offsetHeight approximate border box dimensions. // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the // retrieved value as a content box dimension. valueIsBorderBox = offsetProp in elem; if ( valueIsBorderBox ) { val = elem[ offsetProp ]; } } // Normalize "" and auto val = parseFloat( val ) || 0; // Adjust for the element's box model return ( val + boxModelAdjustment( elem, dimension, extra || ( isBorderBox ? "border" : "content" ), valueIsBorderBox, styles, // Provide the current computed size to request scroll gutter calculation (gh-3589) val ) ) + "px"; } jQuery.extend( { // Add in style property hooks for overriding the default // behavior of getting and setting a style property cssHooks: { opacity: { get: function( elem, computed ) { if ( computed ) { // We should always get a number back from opacity var ret = curCSS( elem, "opacity" ); return ret === "" ? "1" : ret; } } } }, // Don't automatically add "px" to these possibly-unitless properties cssNumber: { "animationIterationCount": true, "columnCount": true, "fillOpacity": true, "flexGrow": true, "flexShrink": true, "fontWeight": true, "gridArea": true, "gridColumn": true, "gridColumnEnd": true, "gridColumnStart": true, "gridRow": true, "gridRowEnd": true, "gridRowStart": true, "lineHeight": true, "opacity": true, "order": true, "orphans": true, "widows": true, "zIndex": true, "zoom": true }, // Add in properties whose names you wish to fix before // setting or getting the value cssProps: {}, // Get and set the style property on a DOM Node style: function( elem, name, value, extra ) { // Don't set styles on text and comment nodes if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { return; } // Make sure that we're working with the right name var ret, type, hooks, origName = camelCase( name ), isCustomProp = rcustomProp.test( name ), style = elem.style; // Make sure that we're working with the right name. We don't // want to query the value if it is a CSS custom property // since they are user-defined. if ( !isCustomProp ) { name = finalPropName( origName ); } // Gets hook for the prefixed version, then unprefixed version hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; // Check if we're setting a value if ( value !== undefined ) { type = typeof value; // Convert "+=" or "-=" to relative numbers (#7345) if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { value = adjustCSS( elem, name, ret ); // Fixes bug #9237 type = "number"; } // Make sure that null and NaN values aren't set (#7116) if ( value == null || value !== value ) { return; } // If a number was passed in, add the unit (except for certain CSS properties) // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append // "px" to a few hardcoded values. if ( type === "number" && !isCustomProp ) { value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); } // background-* props affect original clone's values if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { style[ name ] = "inherit"; } // If a hook was provided, use that value, otherwise just set the specified value if ( !hooks || !( "set" in hooks ) || ( value = hooks.set( elem, value, extra ) ) !== undefined ) { if ( isCustomProp ) { style.setProperty( name, value ); } else { style[ name ] = value; } } } else { // If a hook was provided get the non-computed value from there if ( hooks && "get" in hooks && ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { return ret; } // Otherwise just get the value from the style object return style[ name ]; } }, css: function( elem, name, extra, styles ) { var val, num, hooks, origName = camelCase( name ), isCustomProp = rcustomProp.test( name ); // Make sure that we're working with the right name. We don't // want to modify the value if it is a CSS custom property // since they are user-defined. if ( !isCustomProp ) { name = finalPropName( origName ); } // Try prefixed name followed by the unprefixed name hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; // If a hook was provided get the computed value from there if ( hooks && "get" in hooks ) { val = hooks.get( elem, true, extra ); } // Otherwise, if a way to get the computed value exists, use that if ( val === undefined ) { val = curCSS( elem, name, styles ); } // Convert "normal" to computed value if ( val === "normal" && name in cssNormalTransform ) { val = cssNormalTransform[ name ]; } // Make numeric if forced or a qualifier was provided and val looks numeric if ( extra === "" || extra ) { num = parseFloat( val ); return extra === true || isFinite( num ) ? num || 0 : val; } return val; } } ); jQuery.each( [ "height", "width" ], function( _i, dimension ) { jQuery.cssHooks[ dimension ] = { get: function( elem, computed, extra ) { if ( computed ) { // Certain elements can have dimension info if we invisibly show them // but it must have a current display style that would benefit return rdisplayswap.test( jQuery.css( elem, "display" ) ) && // Support: Safari 8+ // Table columns in Safari have non-zero offsetWidth & zero // getBoundingClientRect().width unless display is changed. // Support: IE <=11 only // Running getBoundingClientRect on a disconnected node // in IE throws an error. ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? swap( elem, cssShow, function() { return getWidthOrHeight( elem, dimension, extra ); } ) : getWidthOrHeight( elem, dimension, extra ); } }, set: function( elem, value, extra ) { var matches, styles = getStyles( elem ), // Only read styles.position if the test has a chance to fail // to avoid forcing a reflow. scrollboxSizeBuggy = !support.scrollboxSize() && styles.position === "absolute", // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) boxSizingNeeded = scrollboxSizeBuggy || extra, isBorderBox = boxSizingNeeded && jQuery.css( elem, "boxSizing", false, styles ) === "border-box", subtract = extra ? boxModelAdjustment( elem, dimension, extra, isBorderBox, styles ) : 0; // Account for unreliable border-box dimensions by comparing offset* to computed and // faking a content-box to get border and padding (gh-3699) if ( isBorderBox && scrollboxSizeBuggy ) { subtract -= Math.ceil( elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - parseFloat( styles[ dimension ] ) - boxModelAdjustment( elem, dimension, "border", false, styles ) - 0.5 ); } // Convert to pixels if value adjustment is needed if ( subtract && ( matches = rcssNum.exec( value ) ) && ( matches[ 3 ] || "px" ) !== "px" ) { elem.style[ dimension ] = value; value = jQuery.css( elem, dimension ); } return setPositiveNumber( elem, value, subtract ); } }; } ); jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, function( elem, computed ) { if ( computed ) { return ( parseFloat( curCSS( elem, "marginLeft" ) ) || elem.getBoundingClientRect().left - swap( elem, { marginLeft: 0 }, function() { return elem.getBoundingClientRect().left; } ) ) + "px"; } } ); // These hooks are used by animate to expand properties jQuery.each( { margin: "", padding: "", border: "Width" }, function( prefix, suffix ) { jQuery.cssHooks[ prefix + suffix ] = { expand: function( value ) { var i = 0, expanded = {}, // Assumes a single number if not a string parts = typeof value === "string" ? value.split( " " ) : [ value ]; for ( ; i < 4; i++ ) { expanded[ prefix + cssExpand[ i ] + suffix ] = parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; } return expanded; } }; if ( prefix !== "margin" ) { jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; } } ); jQuery.fn.extend( { css: function( name, value ) { return access( this, function( elem, name, value ) { var styles, len, map = {}, i = 0; if ( Array.isArray( name ) ) { styles = getStyles( elem ); len = name.length; for ( ; i < len; i++ ) { map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); } return map; } return value !== undefined ? jQuery.style( elem, name, value ) : jQuery.css( elem, name ); }, name, value, arguments.length > 1 ); } } ); function Tween( elem, options, prop, end, easing ) { return new Tween.prototype.init( elem, options, prop, end, easing ); } jQuery.Tween = Tween; Tween.prototype = { constructor: Tween, init: function( elem, options, prop, end, easing, unit ) { this.elem = elem; this.prop = prop; this.easing = easing || jQuery.easing._default; this.options = options; this.start = this.now = this.cur(); this.end = end; this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); }, cur: function() { var hooks = Tween.propHooks[ this.prop ]; return hooks && hooks.get ? hooks.get( this ) : Tween.propHooks._default.get( this ); }, run: function( percent ) { var eased, hooks = Tween.propHooks[ this.prop ]; if ( this.options.duration ) { this.pos = eased = jQuery.easing[ this.easing ]( percent, this.options.duration * percent, 0, 1, this.options.duration ); } else { this.pos = eased = percent; } this.now = ( this.end - this.start ) * eased + this.start; if ( this.options.step ) { this.options.step.call( this.elem, this.now, this ); } if ( hooks && hooks.set ) { hooks.set( this ); } else { Tween.propHooks._default.set( this ); } return this; } }; Tween.prototype.init.prototype = Tween.prototype; Tween.propHooks = { _default: { get: function( tween ) { var result; // Use a property on the element directly when it is not a DOM element, // or when there is no matching style property that exists. if ( tween.elem.nodeType !== 1 || tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { return tween.elem[ tween.prop ]; } // Passing an empty string as a 3rd parameter to .css will automatically // attempt a parseFloat and fallback to a string if the parse fails. // Simple values such as "10px" are parsed to Float; // complex values such as "rotate(1rad)" are returned as-is. result = jQuery.css( tween.elem, tween.prop, "" ); // Empty strings, null, undefined and "auto" are converted to 0. return !result || result === "auto" ? 0 : result; }, set: function( tween ) { // Use step hook for back compat. // Use cssHook if its there. // Use .style if available and use plain properties where available. if ( jQuery.fx.step[ tween.prop ] ) { jQuery.fx.step[ tween.prop ]( tween ); } else if ( tween.elem.nodeType === 1 && ( jQuery.cssHooks[ tween.prop ] || tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); } else { tween.elem[ tween.prop ] = tween.now; } } } }; // Support: IE <=9 only // Panic based approach to setting things on disconnected nodes Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { set: function( tween ) { if ( tween.elem.nodeType && tween.elem.parentNode ) { tween.elem[ tween.prop ] = tween.now; } } }; jQuery.easing = { linear: function( p ) { return p; }, swing: function( p ) { return 0.5 - Math.cos( p * Math.PI ) / 2; }, _default: "swing" }; jQuery.fx = Tween.prototype.init; // Back compat <1.8 extension point jQuery.fx.step = {}; var fxNow, inProgress, rfxtypes = /^(?:toggle|show|hide)$/, rrun = /queueHooks$/; function schedule() { if ( inProgress ) { if ( document.hidden === false && window.requestAnimationFrame ) { window.requestAnimationFrame( schedule ); } else { window.setTimeout( schedule, jQuery.fx.interval ); } jQuery.fx.tick(); } } // Animations created synchronously will run synchronously function createFxNow() { window.setTimeout( function() { fxNow = undefined; } ); return ( fxNow = Date.now() ); } // Generate parameters to create a standard animation function genFx( type, includeWidth ) { var which, i = 0, attrs = { height: type }; // If we include width, step value is 1 to do all cssExpand values, // otherwise step value is 2 to skip over Left and Right includeWidth = includeWidth ? 1 : 0; for ( ; i < 4; i += 2 - includeWidth ) { which = cssExpand[ i ]; attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; } if ( includeWidth ) { attrs.opacity = attrs.width = type; } return attrs; } function createTween( value, prop, animation ) { var tween, collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), index = 0, length = collection.length; for ( ; index < length; index++ ) { if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { // We're done with this property return tween; } } } function defaultPrefilter( elem, props, opts ) { var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, isBox = "width" in props || "height" in props, anim = this, orig = {}, style = elem.style, hidden = elem.nodeType && isHiddenWithinTree( elem ), dataShow = dataPriv.get( elem, "fxshow" ); // Queue-skipping animations hijack the fx hooks if ( !opts.queue ) { hooks = jQuery._queueHooks( elem, "fx" ); if ( hooks.unqueued == null ) { hooks.unqueued = 0; oldfire = hooks.empty.fire; hooks.empty.fire = function() { if ( !hooks.unqueued ) { oldfire(); } }; } hooks.unqueued++; anim.always( function() { // Ensure the complete handler is called before this completes anim.always( function() { hooks.unqueued--; if ( !jQuery.queue( elem, "fx" ).length ) { hooks.empty.fire(); } } ); } ); } // Detect show/hide animations for ( prop in props ) { value = props[ prop ]; if ( rfxtypes.test( value ) ) { delete props[ prop ]; toggle = toggle || value === "toggle"; if ( value === ( hidden ? "hide" : "show" ) ) { // Pretend to be hidden if this is a "show" and // there is still data from a stopped show/hide if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { hidden = true; // Ignore all other no-op show/hide data } else { continue; } } orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); } } // Bail out if this is a no-op like .hide().hide() propTween = !jQuery.isEmptyObject( props ); if ( !propTween && jQuery.isEmptyObject( orig ) ) { return; } // Restrict "overflow" and "display" styles during box animations if ( isBox && elem.nodeType === 1 ) { // Support: IE <=9 - 11, Edge 12 - 15 // Record all 3 overflow attributes because IE does not infer the shorthand // from identically-valued overflowX and overflowY and Edge just mirrors // the overflowX value there. opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; // Identify a display type, preferring old show/hide data over the CSS cascade restoreDisplay = dataShow && dataShow.display; if ( restoreDisplay == null ) { restoreDisplay = dataPriv.get( elem, "display" ); } display = jQuery.css( elem, "display" ); if ( display === "none" ) { if ( restoreDisplay ) { display = restoreDisplay; } else { // Get nonempty value(s) by temporarily forcing visibility showHide( [ elem ], true ); restoreDisplay = elem.style.display || restoreDisplay; display = jQuery.css( elem, "display" ); showHide( [ elem ] ); } } // Animate inline elements as inline-block if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { if ( jQuery.css( elem, "float" ) === "none" ) { // Restore the original display value at the end of pure show/hide animations if ( !propTween ) { anim.done( function() { style.display = restoreDisplay; } ); if ( restoreDisplay == null ) { display = style.display; restoreDisplay = display === "none" ? "" : display; } } style.display = "inline-block"; } } } if ( opts.overflow ) { style.overflow = "hidden"; anim.always( function() { style.overflow = opts.overflow[ 0 ]; style.overflowX = opts.overflow[ 1 ]; style.overflowY = opts.overflow[ 2 ]; } ); } // Implement show/hide animations propTween = false; for ( prop in orig ) { // General show/hide setup for this element animation if ( !propTween ) { if ( dataShow ) { if ( "hidden" in dataShow ) { hidden = dataShow.hidden; } } else { dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); } // Store hidden/visible for toggle so `.stop().toggle()` "reverses" if ( toggle ) { dataShow.hidden = !hidden; } // Show elements before animating them if ( hidden ) { showHide( [ elem ], true ); } /* eslint-disable no-loop-func */ anim.done( function() { /* eslint-enable no-loop-func */ // The final step of a "hide" animation is actually hiding the element if ( !hidden ) { showHide( [ elem ] ); } dataPriv.remove( elem, "fxshow" ); for ( prop in orig ) { jQuery.style( elem, prop, orig[ prop ] ); } } ); } // Per-property setup propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); if ( !( prop in dataShow ) ) { dataShow[ prop ] = propTween.start; if ( hidden ) { propTween.end = propTween.start; propTween.start = 0; } } } } function propFilter( props, specialEasing ) { var index, name, easing, value, hooks; // camelCase, specialEasing and expand cssHook pass for ( index in props ) { name = camelCase( index ); easing = specialEasing[ name ]; value = props[ index ]; if ( Array.isArray( value ) ) { easing = value[ 1 ]; value = props[ index ] = value[ 0 ]; } if ( index !== name ) { props[ name ] = value; delete props[ index ]; } hooks = jQuery.cssHooks[ name ]; if ( hooks && "expand" in hooks ) { value = hooks.expand( value ); delete props[ name ]; // Not quite $.extend, this won't overwrite existing keys. // Reusing 'index' because we have the correct "name" for ( index in value ) { if ( !( index in props ) ) { props[ index ] = value[ index ]; specialEasing[ index ] = easing; } } } else { specialEasing[ name ] = easing; } } } function Animation( elem, properties, options ) { var result, stopped, index = 0, length = Animation.prefilters.length, deferred = jQuery.Deferred().always( function() { // Don't match elem in the :animated selector delete tick.elem; } ), tick = function() { if ( stopped ) { return false; } var currentTime = fxNow || createFxNow(), remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), // Support: Android 2.3 only // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) temp = remaining / animation.duration || 0, percent = 1 - temp, index = 0, length = animation.tweens.length; for ( ; index < length; index++ ) { animation.tweens[ index ].run( percent ); } deferred.notifyWith( elem, [ animation, percent, remaining ] ); // If there's more to do, yield if ( percent < 1 && length ) { return remaining; } // If this was an empty animation, synthesize a final progress notification if ( !length ) { deferred.notifyWith( elem, [ animation, 1, 0 ] ); } // Resolve the animation and report its conclusion deferred.resolveWith( elem, [ animation ] ); return false; }, animation = deferred.promise( { elem: elem, props: jQuery.extend( {}, properties ), opts: jQuery.extend( true, { specialEasing: {}, easing: jQuery.easing._default }, options ), originalProperties: properties, originalOptions: options, startTime: fxNow || createFxNow(), duration: options.duration, tweens: [], createTween: function( prop, end ) { var tween = jQuery.Tween( elem, animation.opts, prop, end, animation.opts.specialEasing[ prop ] || animation.opts.easing ); animation.tweens.push( tween ); return tween; }, stop: function( gotoEnd ) { var index = 0, // If we are going to the end, we want to run all the tweens // otherwise we skip this part length = gotoEnd ? animation.tweens.length : 0; if ( stopped ) { return this; } stopped = true; for ( ; index < length; index++ ) { animation.tweens[ index ].run( 1 ); } // Resolve when we played the last frame; otherwise, reject if ( gotoEnd ) { deferred.notifyWith( elem, [ animation, 1, 0 ] ); deferred.resolveWith( elem, [ animation, gotoEnd ] ); } else { deferred.rejectWith( elem, [ animation, gotoEnd ] ); } return this; } } ), props = animation.props; propFilter( props, animation.opts.specialEasing ); for ( ; index < length; index++ ) { result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); if ( result ) { if ( isFunction( result.stop ) ) { jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = result.stop.bind( result ); } return result; } } jQuery.map( props, createTween, animation ); if ( isFunction( animation.opts.start ) ) { animation.opts.start.call( elem, animation ); } // Attach callbacks from options animation .progress( animation.opts.progress ) .done( animation.opts.done, animation.opts.complete ) .fail( animation.opts.fail ) .always( animation.opts.always ); jQuery.fx.timer( jQuery.extend( tick, { elem: elem, anim: animation, queue: animation.opts.queue } ) ); return animation; } jQuery.Animation = jQuery.extend( Animation, { tweeners: { "*": [ function( prop, value ) { var tween = this.createTween( prop, value ); adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); return tween; } ] }, tweener: function( props, callback ) { if ( isFunction( props ) ) { callback = props; props = [ "*" ]; } else { props = props.match( rnothtmlwhite ); } var prop, index = 0, length = props.length; for ( ; index < length; index++ ) { prop = props[ index ]; Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; Animation.tweeners[ prop ].unshift( callback ); } }, prefilters: [ defaultPrefilter ], prefilter: function( callback, prepend ) { if ( prepend ) { Animation.prefilters.unshift( callback ); } else { Animation.prefilters.push( callback ); } } } ); jQuery.speed = function( speed, easing, fn ) { var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { complete: fn || !fn && easing || isFunction( speed ) && speed, duration: speed, easing: fn && easing || easing && !isFunction( easing ) && easing }; // Go to the end state if fx are off if ( jQuery.fx.off ) { opt.duration = 0; } else { if ( typeof opt.duration !== "number" ) { if ( opt.duration in jQuery.fx.speeds ) { opt.duration = jQuery.fx.speeds[ opt.duration ]; } else { opt.duration = jQuery.fx.speeds._default; } } } // Normalize opt.queue - true/undefined/null -> "fx" if ( opt.queue == null || opt.queue === true ) { opt.queue = "fx"; } // Queueing opt.old = opt.complete; opt.complete = function() { if ( isFunction( opt.old ) ) { opt.old.call( this ); } if ( opt.queue ) { jQuery.dequeue( this, opt.queue ); } }; return opt; }; jQuery.fn.extend( { fadeTo: function( speed, to, easing, callback ) { // Show any hidden elements after setting opacity to 0 return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() // Animate to the value specified .end().animate( { opacity: to }, speed, easing, callback ); }, animate: function( prop, speed, easing, callback ) { var empty = jQuery.isEmptyObject( prop ), optall = jQuery.speed( speed, easing, callback ), doAnimation = function() { // Operate on a copy of prop so per-property easing won't be lost var anim = Animation( this, jQuery.extend( {}, prop ), optall ); // Empty animations, or finishing resolves immediately if ( empty || dataPriv.get( this, "finish" ) ) { anim.stop( true ); } }; doAnimation.finish = doAnimation; return empty || optall.queue === false ? this.each( doAnimation ) : this.queue( optall.queue, doAnimation ); }, stop: function( type, clearQueue, gotoEnd ) { var stopQueue = function( hooks ) { var stop = hooks.stop; delete hooks.stop; stop( gotoEnd ); }; if ( typeof type !== "string" ) { gotoEnd = clearQueue; clearQueue = type; type = undefined; } if ( clearQueue ) { this.queue( type || "fx", [] ); } return this.each( function() { var dequeue = true, index = type != null && type + "queueHooks", timers = jQuery.timers, data = dataPriv.get( this ); if ( index ) { if ( data[ index ] && data[ index ].stop ) { stopQueue( data[ index ] ); } } else { for ( index in data ) { if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { stopQueue( data[ index ] ); } } } for ( index = timers.length; index--; ) { if ( timers[ index ].elem === this && ( type == null || timers[ index ].queue === type ) ) { timers[ index ].anim.stop( gotoEnd ); dequeue = false; timers.splice( index, 1 ); } } // Start the next in the queue if the last step wasn't forced. // Timers currently will call their complete callbacks, which // will dequeue but only if they were gotoEnd. if ( dequeue || !gotoEnd ) { jQuery.dequeue( this, type ); } } ); }, finish: function( type ) { if ( type !== false ) { type = type || "fx"; } return this.each( function() { var index, data = dataPriv.get( this ), queue = data[ type + "queue" ], hooks = data[ type + "queueHooks" ], timers = jQuery.timers, length = queue ? queue.length : 0; // Enable finishing flag on private data data.finish = true; // Empty the queue first jQuery.queue( this, type, [] ); if ( hooks && hooks.stop ) { hooks.stop.call( this, true ); } // Look for any active animations, and finish them for ( index = timers.length; index--; ) { if ( timers[ index ].elem === this && timers[ index ].queue === type ) { timers[ index ].anim.stop( true ); timers.splice( index, 1 ); } } // Look for any animations in the old queue and finish them for ( index = 0; index < length; index++ ) { if ( queue[ index ] && queue[ index ].finish ) { queue[ index ].finish.call( this ); } } // Turn off finishing flag delete data.finish; } ); } } ); jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { var cssFn = jQuery.fn[ name ]; jQuery.fn[ name ] = function( speed, easing, callback ) { return speed == null || typeof speed === "boolean" ? cssFn.apply( this, arguments ) : this.animate( genFx( name, true ), speed, easing, callback ); }; } ); // Generate shortcuts for custom animations jQuery.each( { slideDown: genFx( "show" ), slideUp: genFx( "hide" ), slideToggle: genFx( "toggle" ), fadeIn: { opacity: "show" }, fadeOut: { opacity: "hide" }, fadeToggle: { opacity: "toggle" } }, function( name, props ) { jQuery.fn[ name ] = function( speed, easing, callback ) { return this.animate( props, speed, easing, callback ); }; } ); jQuery.timers = []; jQuery.fx.tick = function() { var timer, i = 0, timers = jQuery.timers; fxNow = Date.now(); for ( ; i < timers.length; i++ ) { timer = timers[ i ]; // Run the timer and safely remove it when done (allowing for external removal) if ( !timer() && timers[ i ] === timer ) { timers.splice( i--, 1 ); } } if ( !timers.length ) { jQuery.fx.stop(); } fxNow = undefined; }; jQuery.fx.timer = function( timer ) { jQuery.timers.push( timer ); jQuery.fx.start(); }; jQuery.fx.interval = 13; jQuery.fx.start = function() { if ( inProgress ) { return; } inProgress = true; schedule(); }; jQuery.fx.stop = function() { inProgress = null; }; jQuery.fx.speeds = { slow: 600, fast: 200, // Default speed _default: 400 }; // Based off of the plugin by Clint Helfers, with permission. // https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ jQuery.fn.delay = function( time, type ) { time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; type = type || "fx"; return this.queue( type, function( next, hooks ) { var timeout = window.setTimeout( next, time ); hooks.stop = function() { window.clearTimeout( timeout ); }; } ); }; ( function() { var input = document.createElement( "input" ), select = document.createElement( "select" ), opt = select.appendChild( document.createElement( "option" ) ); input.type = "checkbox"; // Support: Android <=4.3 only // Default value for a checkbox should be "on" support.checkOn = input.value !== ""; // Support: IE <=11 only // Must access selectedIndex to make default options select support.optSelected = opt.selected; // Support: IE <=11 only // An input loses its value after becoming a radio input = document.createElement( "input" ); input.value = "t"; input.type = "radio"; support.radioValue = input.value === "t"; } )(); var boolHook, attrHandle = jQuery.expr.attrHandle; jQuery.fn.extend( { attr: function( name, value ) { return access( this, jQuery.attr, name, value, arguments.length > 1 ); }, removeAttr: function( name ) { return this.each( function() { jQuery.removeAttr( this, name ); } ); } } ); jQuery.extend( { attr: function( elem, name, value ) { var ret, hooks, nType = elem.nodeType; // Don't get/set attributes on text, comment and attribute nodes if ( nType === 3 || nType === 8 || nType === 2 ) { return; } // Fallback to prop when attributes are not supported if ( typeof elem.getAttribute === "undefined" ) { return jQuery.prop( elem, name, value ); } // Attribute hooks are determined by the lowercase version // Grab necessary hook if one is defined if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { hooks = jQuery.attrHooks[ name.toLowerCase() ] || ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); } if ( value !== undefined ) { if ( value === null ) { jQuery.removeAttr( elem, name ); return; } if ( hooks && "set" in hooks && ( ret = hooks.set( elem, value, name ) ) !== undefined ) { return ret; } elem.setAttribute( name, value + "" ); return value; } if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { return ret; } ret = jQuery.find.attr( elem, name ); // Non-existent attributes return null, we normalize to undefined return ret == null ? undefined : ret; }, attrHooks: { type: { set: function( elem, value ) { if ( !support.radioValue && value === "radio" && nodeName( elem, "input" ) ) { var val = elem.value; elem.setAttribute( "type", value ); if ( val ) { elem.value = val; } return value; } } } }, removeAttr: function( elem, value ) { var name, i = 0, // Attribute names can contain non-HTML whitespace characters // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 attrNames = value && value.match( rnothtmlwhite ); if ( attrNames && elem.nodeType === 1 ) { while ( ( name = attrNames[ i++ ] ) ) { elem.removeAttribute( name ); } } } } ); // Hooks for boolean attributes boolHook = { set: function( elem, value, name ) { if ( value === false ) { // Remove boolean attributes when set to false jQuery.removeAttr( elem, name ); } else { elem.setAttribute( name, name ); } return name; } }; jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { var getter = attrHandle[ name ] || jQuery.find.attr; attrHandle[ name ] = function( elem, name, isXML ) { var ret, handle, lowercaseName = name.toLowerCase(); if ( !isXML ) { // Avoid an infinite loop by temporarily removing this function from the getter handle = attrHandle[ lowercaseName ]; attrHandle[ lowercaseName ] = ret; ret = getter( elem, name, isXML ) != null ? lowercaseName : null; attrHandle[ lowercaseName ] = handle; } return ret; }; } ); var rfocusable = /^(?:input|select|textarea|button)$/i, rclickable = /^(?:a|area)$/i; jQuery.fn.extend( { prop: function( name, value ) { return access( this, jQuery.prop, name, value, arguments.length > 1 ); }, removeProp: function( name ) { return this.each( function() { delete this[ jQuery.propFix[ name ] || name ]; } ); } } ); jQuery.extend( { prop: function( elem, name, value ) { var ret, hooks, nType = elem.nodeType; // Don't get/set properties on text, comment and attribute nodes if ( nType === 3 || nType === 8 || nType === 2 ) { return; } if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { // Fix name and attach hooks name = jQuery.propFix[ name ] || name; hooks = jQuery.propHooks[ name ]; } if ( value !== undefined ) { if ( hooks && "set" in hooks && ( ret = hooks.set( elem, value, name ) ) !== undefined ) { return ret; } return ( elem[ name ] = value ); } if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { return ret; } return elem[ name ]; }, propHooks: { tabIndex: { get: function( elem ) { // Support: IE <=9 - 11 only // elem.tabIndex doesn't always return the // correct value when it hasn't been explicitly set // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ // Use proper attribute retrieval(#12072) var tabindex = jQuery.find.attr( elem, "tabindex" ); if ( tabindex ) { return parseInt( tabindex, 10 ); } if ( rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ) { return 0; } return -1; } } }, propFix: { "for": "htmlFor", "class": "className" } } ); // Support: IE <=11 only // Accessing the selectedIndex property // forces the browser to respect setting selected // on the option // The getter ensures a default option is selected // when in an optgroup // eslint rule "no-unused-expressions" is disabled for this code // since it considers such accessions noop if ( !support.optSelected ) { jQuery.propHooks.selected = { get: function( elem ) { /* eslint no-unused-expressions: "off" */ var parent = elem.parentNode; if ( parent && parent.parentNode ) { parent.parentNode.selectedIndex; } return null; }, set: function( elem ) { /* eslint no-unused-expressions: "off" */ var parent = elem.parentNode; if ( parent ) { parent.selectedIndex; if ( parent.parentNode ) { parent.parentNode.selectedIndex; } } } }; } jQuery.each( [ "tabIndex", "readOnly", "maxLength", "cellSpacing", "cellPadding", "rowSpan", "colSpan", "useMap", "frameBorder", "contentEditable" ], function() { jQuery.propFix[ this.toLowerCase() ] = this; } ); // Strip and collapse whitespace according to HTML spec // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace function stripAndCollapse( value ) { var tokens = value.match( rnothtmlwhite ) || []; return tokens.join( " " ); } function getClass( elem ) { return elem.getAttribute && elem.getAttribute( "class" ) || ""; } function classesToArray( value ) { if ( Array.isArray( value ) ) { return value; } if ( typeof value === "string" ) { return value.match( rnothtmlwhite ) || []; } return []; } jQuery.fn.extend( { addClass: function( value ) { var classes, elem, cur, curValue, clazz, j, finalValue, i = 0; if ( isFunction( value ) ) { return this.each( function( j ) { jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); } ); } classes = classesToArray( value ); if ( classes.length ) { while ( ( elem = this[ i++ ] ) ) { curValue = getClass( elem ); cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); if ( cur ) { j = 0; while ( ( clazz = classes[ j++ ] ) ) { if ( cur.indexOf( " " + clazz + " " ) < 0 ) { cur += clazz + " "; } } // Only assign if different to avoid unneeded rendering. finalValue = stripAndCollapse( cur ); if ( curValue !== finalValue ) { elem.setAttribute( "class", finalValue ); } } } } return this; }, removeClass: function( value ) { var classes, elem, cur, curValue, clazz, j, finalValue, i = 0; if ( isFunction( value ) ) { return this.each( function( j ) { jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); } ); } if ( !arguments.length ) { return this.attr( "class", "" ); } classes = classesToArray( value ); if ( classes.length ) { while ( ( elem = this[ i++ ] ) ) { curValue = getClass( elem ); // This expression is here for better compressibility (see addClass) cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); if ( cur ) { j = 0; while ( ( clazz = classes[ j++ ] ) ) { // Remove *all* instances while ( cur.indexOf( " " + clazz + " " ) > -1 ) { cur = cur.replace( " " + clazz + " ", " " ); } } // Only assign if different to avoid unneeded rendering. finalValue = stripAndCollapse( cur ); if ( curValue !== finalValue ) { elem.setAttribute( "class", finalValue ); } } } } return this; }, toggleClass: function( value, stateVal ) { var type = typeof value, isValidValue = type === "string" || Array.isArray( value ); if ( typeof stateVal === "boolean" && isValidValue ) { return stateVal ? this.addClass( value ) : this.removeClass( value ); } if ( isFunction( value ) ) { return this.each( function( i ) { jQuery( this ).toggleClass( value.call( this, i, getClass( this ), stateVal ), stateVal ); } ); } return this.each( function() { var className, i, self, classNames; if ( isValidValue ) { // Toggle individual class names i = 0; self = jQuery( this ); classNames = classesToArray( value ); while ( ( className = classNames[ i++ ] ) ) { // Check each className given, space separated list if ( self.hasClass( className ) ) { self.removeClass( className ); } else { self.addClass( className ); } } // Toggle whole class name } else if ( value === undefined || type === "boolean" ) { className = getClass( this ); if ( className ) { // Store className if set dataPriv.set( this, "__className__", className ); } // If the element has a class name or if we're passed `false`, // then remove the whole classname (if there was one, the above saved it). // Otherwise bring back whatever was previously saved (if anything), // falling back to the empty string if nothing was stored. if ( this.setAttribute ) { this.setAttribute( "class", className || value === false ? "" : dataPriv.get( this, "__className__" ) || "" ); } } } ); }, hasClass: function( selector ) { var className, elem, i = 0; className = " " + selector + " "; while ( ( elem = this[ i++ ] ) ) { if ( elem.nodeType === 1 && ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { return true; } } return false; } } ); var rreturn = /\r/g; jQuery.fn.extend( { val: function( value ) { var hooks, ret, valueIsFunction, elem = this[ 0 ]; if ( !arguments.length ) { if ( elem ) { hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; if ( hooks && "get" in hooks && ( ret = hooks.get( elem, "value" ) ) !== undefined ) { return ret; } ret = elem.value; // Handle most common string cases if ( typeof ret === "string" ) { return ret.replace( rreturn, "" ); } // Handle cases where value is null/undef or number return ret == null ? "" : ret; } return; } valueIsFunction = isFunction( value ); return this.each( function( i ) { var val; if ( this.nodeType !== 1 ) { return; } if ( valueIsFunction ) { val = value.call( this, i, jQuery( this ).val() ); } else { val = value; } // Treat null/undefined as ""; convert numbers to string if ( val == null ) { val = ""; } else if ( typeof val === "number" ) { val += ""; } else if ( Array.isArray( val ) ) { val = jQuery.map( val, function( value ) { return value == null ? "" : value + ""; } ); } hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; // If set returns undefined, fall back to normal setting if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { this.value = val; } } ); } } ); jQuery.extend( { valHooks: { option: { get: function( elem ) { var val = jQuery.find.attr( elem, "value" ); return val != null ? val : // Support: IE <=10 - 11 only // option.text throws exceptions (#14686, #14858) // Strip and collapse whitespace // https://html.spec.whatwg.org/#strip-and-collapse-whitespace stripAndCollapse( jQuery.text( elem ) ); } }, select: { get: function( elem ) { var value, option, i, options = elem.options, index = elem.selectedIndex, one = elem.type === "select-one", values = one ? null : [], max = one ? index + 1 : options.length; if ( index < 0 ) { i = max; } else { i = one ? index : 0; } // Loop through all the selected options for ( ; i < max; i++ ) { option = options[ i ]; // Support: IE <=9 only // IE8-9 doesn't update selected after form reset (#2551) if ( ( option.selected || i === index ) && // Don't return options that are disabled or in a disabled optgroup !option.disabled && ( !option.parentNode.disabled || !nodeName( option.parentNode, "optgroup" ) ) ) { // Get the specific value for the option value = jQuery( option ).val(); // We don't need an array for one selects if ( one ) { return value; } // Multi-Selects return an array values.push( value ); } } return values; }, set: function( elem, value ) { var optionSet, option, options = elem.options, values = jQuery.makeArray( value ), i = options.length; while ( i-- ) { option = options[ i ]; /* eslint-disable no-cond-assign */ if ( option.selected = jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 ) { optionSet = true; } /* eslint-enable no-cond-assign */ } // Force browsers to behave consistently when non-matching value is set if ( !optionSet ) { elem.selectedIndex = -1; } return values; } } } } ); // Radios and checkboxes getter/setter jQuery.each( [ "radio", "checkbox" ], function() { jQuery.valHooks[ this ] = { set: function( elem, value ) { if ( Array.isArray( value ) ) { return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); } } }; if ( !support.checkOn ) { jQuery.valHooks[ this ].get = function( elem ) { return elem.getAttribute( "value" ) === null ? "on" : elem.value; }; } } ); // Return jQuery for attributes-only inclusion support.focusin = "onfocusin" in window; var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, stopPropagationCallback = function( e ) { e.stopPropagation(); }; jQuery.extend( jQuery.event, { trigger: function( event, data, elem, onlyHandlers ) { var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, eventPath = [ elem || document ], type = hasOwn.call( event, "type" ) ? event.type : event, namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; cur = lastElement = tmp = elem = elem || document; // Don't do events on text and comment nodes if ( elem.nodeType === 3 || elem.nodeType === 8 ) { return; } // focus/blur morphs to focusin/out; ensure we're not firing them right now if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { return; } if ( type.indexOf( "." ) > -1 ) { // Namespaced trigger; create a regexp to match event type in handle() namespaces = type.split( "." ); type = namespaces.shift(); namespaces.sort(); } ontype = type.indexOf( ":" ) < 0 && "on" + type; // Caller can pass in a jQuery.Event object, Object, or just an event type string event = event[ jQuery.expando ] ? event : new jQuery.Event( type, typeof event === "object" && event ); // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) event.isTrigger = onlyHandlers ? 2 : 3; event.namespace = namespaces.join( "." ); event.rnamespace = event.namespace ? new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : null; // Clean up the event in case it is being reused event.result = undefined; if ( !event.target ) { event.target = elem; } // Clone any incoming data and prepend the event, creating the handler arg list data = data == null ? [ event ] : jQuery.makeArray( data, [ event ] ); // Allow special events to draw outside the lines special = jQuery.event.special[ type ] || {}; if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { return; } // Determine event propagation path in advance, per W3C events spec (#9951) // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { bubbleType = special.delegateType || type; if ( !rfocusMorph.test( bubbleType + type ) ) { cur = cur.parentNode; } for ( ; cur; cur = cur.parentNode ) { eventPath.push( cur ); tmp = cur; } // Only add window if we got to document (e.g., not plain obj or detached DOM) if ( tmp === ( elem.ownerDocument || document ) ) { eventPath.push( tmp.defaultView || tmp.parentWindow || window ); } } // Fire handlers on the event path i = 0; while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { lastElement = cur; event.type = i > 1 ? bubbleType : special.bindType || type; // jQuery handler handle = ( dataPriv.get( cur, "events" ) || Object.create( null ) )[ event.type ] && dataPriv.get( cur, "handle" ); if ( handle ) { handle.apply( cur, data ); } // Native handler handle = ontype && cur[ ontype ]; if ( handle && handle.apply && acceptData( cur ) ) { event.result = handle.apply( cur, data ); if ( event.result === false ) { event.preventDefault(); } } } event.type = type; // If nobody prevented the default action, do it now if ( !onlyHandlers && !event.isDefaultPrevented() ) { if ( ( !special._default || special._default.apply( eventPath.pop(), data ) === false ) && acceptData( elem ) ) { // Call a native DOM method on the target with the same name as the event. // Don't do default actions on window, that's where global variables be (#6170) if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { // Don't re-trigger an onFOO event when we call its FOO() method tmp = elem[ ontype ]; if ( tmp ) { elem[ ontype ] = null; } // Prevent re-triggering of the same event, since we already bubbled it above jQuery.event.triggered = type; if ( event.isPropagationStopped() ) { lastElement.addEventListener( type, stopPropagationCallback ); } elem[ type ](); if ( event.isPropagationStopped() ) { lastElement.removeEventListener( type, stopPropagationCallback ); } jQuery.event.triggered = undefined; if ( tmp ) { elem[ ontype ] = tmp; } } } } return event.result; }, // Piggyback on a donor event to simulate a different one // Used only for `focus(in | out)` events simulate: function( type, elem, event ) { var e = jQuery.extend( new jQuery.Event(), event, { type: type, isSimulated: true } ); jQuery.event.trigger( e, null, elem ); } } ); jQuery.fn.extend( { trigger: function( type, data ) { return this.each( function() { jQuery.event.trigger( type, data, this ); } ); }, triggerHandler: function( type, data ) { var elem = this[ 0 ]; if ( elem ) { return jQuery.event.trigger( type, data, elem, true ); } } } ); // Support: Firefox <=44 // Firefox doesn't have focus(in | out) events // Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 // // Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 // focus(in | out) events fire after focus & blur events, // which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order // Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 if ( !support.focusin ) { jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { // Attach a single capturing handler on the document while someone wants focusin/focusout var handler = function( event ) { jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); }; jQuery.event.special[ fix ] = { setup: function() { // Handle: regular nodes (via `this.ownerDocument`), window // (via `this.document`) & document (via `this`). var doc = this.ownerDocument || this.document || this, attaches = dataPriv.access( doc, fix ); if ( !attaches ) { doc.addEventListener( orig, handler, true ); } dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); }, teardown: function() { var doc = this.ownerDocument || this.document || this, attaches = dataPriv.access( doc, fix ) - 1; if ( !attaches ) { doc.removeEventListener( orig, handler, true ); dataPriv.remove( doc, fix ); } else { dataPriv.access( doc, fix, attaches ); } } }; } ); } var location = window.location; var nonce = { guid: Date.now() }; var rquery = ( /\?/ ); // Cross-browser xml parsing jQuery.parseXML = function( data ) { var xml; if ( !data || typeof data !== "string" ) { return null; } // Support: IE 9 - 11 only // IE throws on parseFromString with invalid input. try { xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); } catch ( e ) { xml = undefined; } if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { jQuery.error( "Invalid XML: " + data ); } return xml; }; var rbracket = /\[\]$/, rCRLF = /\r?\n/g, rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, rsubmittable = /^(?:input|select|textarea|keygen)/i; function buildParams( prefix, obj, traditional, add ) { var name; if ( Array.isArray( obj ) ) { // Serialize array item. jQuery.each( obj, function( i, v ) { if ( traditional || rbracket.test( prefix ) ) { // Treat each array item as a scalar. add( prefix, v ); } else { // Item is non-scalar (array or object), encode its numeric index. buildParams( prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", v, traditional, add ); } } ); } else if ( !traditional && toType( obj ) === "object" ) { // Serialize object item. for ( name in obj ) { buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); } } else { // Serialize scalar item. add( prefix, obj ); } } // Serialize an array of form elements or a set of // key/values into a query string jQuery.param = function( a, traditional ) { var prefix, s = [], add = function( key, valueOrFunction ) { // If value is a function, invoke it and use its return value var value = isFunction( valueOrFunction ) ? valueOrFunction() : valueOrFunction; s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value == null ? "" : value ); }; if ( a == null ) { return ""; } // If an array was passed in, assume that it is an array of form elements. if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { // Serialize the form elements jQuery.each( a, function() { add( this.name, this.value ); } ); } else { // If traditional, encode the "old" way (the way 1.3.2 or older // did it), otherwise encode params recursively. for ( prefix in a ) { buildParams( prefix, a[ prefix ], traditional, add ); } } // Return the resulting serialization return s.join( "&" ); }; jQuery.fn.extend( { serialize: function() { return jQuery.param( this.serializeArray() ); }, serializeArray: function() { return this.map( function() { // Can add propHook for "elements" to filter or add form elements var elements = jQuery.prop( this, "elements" ); return elements ? jQuery.makeArray( elements ) : this; } ) .filter( function() { var type = this.type; // Use .is( ":disabled" ) so that fieldset[disabled] works return this.name && !jQuery( this ).is( ":disabled" ) && rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && ( this.checked || !rcheckableType.test( type ) ); } ) .map( function( _i, elem ) { var val = jQuery( this ).val(); if ( val == null ) { return null; } if ( Array.isArray( val ) ) { return jQuery.map( val, function( val ) { return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; } ); } return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; } ).get(); } } ); var r20 = /%20/g, rhash = /#.*$/, rantiCache = /([?&])_=[^&]*/, rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, // #7653, #8125, #8152: local protocol detection rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, rnoContent = /^(?:GET|HEAD)$/, rprotocol = /^\/\//, /* Prefilters * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) * 2) These are called: * - BEFORE asking for a transport * - AFTER param serialization (s.data is a string if s.processData is true) * 3) key is the dataType * 4) the catchall symbol "*" can be used * 5) execution will start with transport dataType and THEN continue down to "*" if needed */ prefilters = {}, /* Transports bindings * 1) key is the dataType * 2) the catchall symbol "*" can be used * 3) selection will start with transport dataType and THEN go to "*" if needed */ transports = {}, // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression allTypes = "*/".concat( "*" ), // Anchor tag for parsing the document origin originAnchor = document.createElement( "a" ); originAnchor.href = location.href; // Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport function addToPrefiltersOrTransports( structure ) { // dataTypeExpression is optional and defaults to "*" return function( dataTypeExpression, func ) { if ( typeof dataTypeExpression !== "string" ) { func = dataTypeExpression; dataTypeExpression = "*"; } var dataType, i = 0, dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; if ( isFunction( func ) ) { // For each dataType in the dataTypeExpression while ( ( dataType = dataTypes[ i++ ] ) ) { // Prepend if requested if ( dataType[ 0 ] === "+" ) { dataType = dataType.slice( 1 ) || "*"; ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); // Otherwise append } else { ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); } } } }; } // Base inspection function for prefilters and transports function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { var inspected = {}, seekingTransport = ( structure === transports ); function inspect( dataType ) { var selected; inspected[ dataType ] = true; jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); if ( typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) { options.dataTypes.unshift( dataTypeOrTransport ); inspect( dataTypeOrTransport ); return false; } else if ( seekingTransport ) { return !( selected = dataTypeOrTransport ); } } ); return selected; } return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); } // A special extend for ajax options // that takes "flat" options (not to be deep extended) // Fixes #9887 function ajaxExtend( target, src ) { var key, deep, flatOptions = jQuery.ajaxSettings.flatOptions || {}; for ( key in src ) { if ( src[ key ] !== undefined ) { ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; } } if ( deep ) { jQuery.extend( true, target, deep ); } return target; } /* Handles responses to an ajax request: * - finds the right dataType (mediates between content-type and expected dataType) * - returns the corresponding response */ function ajaxHandleResponses( s, jqXHR, responses ) { var ct, type, finalDataType, firstDataType, contents = s.contents, dataTypes = s.dataTypes; // Remove auto dataType and get content-type in the process while ( dataTypes[ 0 ] === "*" ) { dataTypes.shift(); if ( ct === undefined ) { ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); } } // Check if we're dealing with a known content-type if ( ct ) { for ( type in contents ) { if ( contents[ type ] && contents[ type ].test( ct ) ) { dataTypes.unshift( type ); break; } } } // Check to see if we have a response for the expected dataType if ( dataTypes[ 0 ] in responses ) { finalDataType = dataTypes[ 0 ]; } else { // Try convertible dataTypes for ( type in responses ) { if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { finalDataType = type; break; } if ( !firstDataType ) { firstDataType = type; } } // Or just use first one finalDataType = finalDataType || firstDataType; } // If we found a dataType // We add the dataType to the list if needed // and return the corresponding response if ( finalDataType ) { if ( finalDataType !== dataTypes[ 0 ] ) { dataTypes.unshift( finalDataType ); } return responses[ finalDataType ]; } } /* Chain conversions given the request and the original response * Also sets the responseXXX fields on the jqXHR instance */ function ajaxConvert( s, response, jqXHR, isSuccess ) { var conv2, current, conv, tmp, prev, converters = {}, // Work with a copy of dataTypes in case we need to modify it for conversion dataTypes = s.dataTypes.slice(); // Create converters map with lowercased keys if ( dataTypes[ 1 ] ) { for ( conv in s.converters ) { converters[ conv.toLowerCase() ] = s.converters[ conv ]; } } current = dataTypes.shift(); // Convert to each sequential dataType while ( current ) { if ( s.responseFields[ current ] ) { jqXHR[ s.responseFields[ current ] ] = response; } // Apply the dataFilter if provided if ( !prev && isSuccess && s.dataFilter ) { response = s.dataFilter( response, s.dataType ); } prev = current; current = dataTypes.shift(); if ( current ) { // There's only work to do if current dataType is non-auto if ( current === "*" ) { current = prev; // Convert response if prev dataType is non-auto and differs from current } else if ( prev !== "*" && prev !== current ) { // Seek a direct converter conv = converters[ prev + " " + current ] || converters[ "* " + current ]; // If none found, seek a pair if ( !conv ) { for ( conv2 in converters ) { // If conv2 outputs current tmp = conv2.split( " " ); if ( tmp[ 1 ] === current ) { // If prev can be converted to accepted input conv = converters[ prev + " " + tmp[ 0 ] ] || converters[ "* " + tmp[ 0 ] ]; if ( conv ) { // Condense equivalence converters if ( conv === true ) { conv = converters[ conv2 ]; // Otherwise, insert the intermediate dataType } else if ( converters[ conv2 ] !== true ) { current = tmp[ 0 ]; dataTypes.unshift( tmp[ 1 ] ); } break; } } } } // Apply converter (if not an equivalence) if ( conv !== true ) { // Unless errors are allowed to bubble, catch and return them if ( conv && s.throws ) { response = conv( response ); } else { try { response = conv( response ); } catch ( e ) { return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current }; } } } } } } return { state: "success", data: response }; } jQuery.extend( { // Counter for holding the number of active queries active: 0, // Last-Modified header cache for next request lastModified: {}, etag: {}, ajaxSettings: { url: location.href, type: "GET", isLocal: rlocalProtocol.test( location.protocol ), global: true, processData: true, async: true, contentType: "application/x-www-form-urlencoded; charset=UTF-8", /* timeout: 0, data: null, dataType: null, username: null, password: null, cache: null, throws: false, traditional: false, headers: {}, */ accepts: { "*": allTypes, text: "text/plain", html: "text/html", xml: "application/xml, text/xml", json: "application/json, text/javascript" }, contents: { xml: /\bxml\b/, html: /\bhtml/, json: /\bjson\b/ }, responseFields: { xml: "responseXML", text: "responseText", json: "responseJSON" }, // Data converters // Keys separate source (or catchall "*") and destination types with a single space converters: { // Convert anything to text "* text": String, // Text to html (true = no transformation) "text html": true, // Evaluate text as a json expression "text json": JSON.parse, // Parse text as xml "text xml": jQuery.parseXML }, // For options that shouldn't be deep extended: // you can add your own custom options here if // and when you create one that shouldn't be // deep extended (see ajaxExtend) flatOptions: { url: true, context: true } }, // Creates a full fledged settings object into target // with both ajaxSettings and settings fields. // If target is omitted, writes into ajaxSettings. ajaxSetup: function( target, settings ) { return settings ? // Building a settings object ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : // Extending ajaxSettings ajaxExtend( jQuery.ajaxSettings, target ); }, ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), ajaxTransport: addToPrefiltersOrTransports( transports ), // Main method ajax: function( url, options ) { // If url is an object, simulate pre-1.5 signature if ( typeof url === "object" ) { options = url; url = undefined; } // Force options to be an object options = options || {}; var transport, // URL without anti-cache param cacheURL, // Response headers responseHeadersString, responseHeaders, // timeout handle timeoutTimer, // Url cleanup var urlAnchor, // Request state (becomes false upon send and true upon completion) completed, // To know if global events are to be dispatched fireGlobals, // Loop variable i, // uncached part of the url uncached, // Create the final options object s = jQuery.ajaxSetup( {}, options ), // Callbacks context callbackContext = s.context || s, // Context for global events is callbackContext if it is a DOM node or jQuery collection globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ? jQuery( callbackContext ) : jQuery.event, // Deferreds deferred = jQuery.Deferred(), completeDeferred = jQuery.Callbacks( "once memory" ), // Status-dependent callbacks statusCode = s.statusCode || {}, // Headers (they are sent all at once) requestHeaders = {}, requestHeadersNames = {}, // Default abort message strAbort = "canceled", // Fake xhr jqXHR = { readyState: 0, // Builds headers hashtable if needed getResponseHeader: function( key ) { var match; if ( completed ) { if ( !responseHeaders ) { responseHeaders = {}; while ( ( match = rheaders.exec( responseHeadersString ) ) ) { responseHeaders[ match[ 1 ].toLowerCase() + " " ] = ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) .concat( match[ 2 ] ); } } match = responseHeaders[ key.toLowerCase() + " " ]; } return match == null ? null : match.join( ", " ); }, // Raw string getAllResponseHeaders: function() { return completed ? responseHeadersString : null; }, // Caches the header setRequestHeader: function( name, value ) { if ( completed == null ) { name = requestHeadersNames[ name.toLowerCase() ] = requestHeadersNames[ name.toLowerCase() ] || name; requestHeaders[ name ] = value; } return this; }, // Overrides response content-type header overrideMimeType: function( type ) { if ( completed == null ) { s.mimeType = type; } return this; }, // Status-dependent callbacks statusCode: function( map ) { var code; if ( map ) { if ( completed ) { // Execute the appropriate callbacks jqXHR.always( map[ jqXHR.status ] ); } else { // Lazy-add the new callbacks in a way that preserves old ones for ( code in map ) { statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; } } } return this; }, // Cancel the request abort: function( statusText ) { var finalText = statusText || strAbort; if ( transport ) { transport.abort( finalText ); } done( 0, finalText ); return this; } }; // Attach deferreds deferred.promise( jqXHR ); // Add protocol if not provided (prefilters might expect it) // Handle falsy url in the settings object (#10093: consistency with old signature) // We also use the url parameter if available s.url = ( ( url || s.url || location.href ) + "" ) .replace( rprotocol, location.protocol + "//" ); // Alias method option to type as per ticket #12004 s.type = options.method || options.type || s.method || s.type; // Extract dataTypes list s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; // A cross-domain request is in order when the origin doesn't match the current origin. if ( s.crossDomain == null ) { urlAnchor = document.createElement( "a" ); // Support: IE <=8 - 11, Edge 12 - 15 // IE throws exception on accessing the href property if url is malformed, // e.g. http://example.com:80x/ try { urlAnchor.href = s.url; // Support: IE <=8 - 11 only // Anchor's host property isn't correctly set when s.url is relative urlAnchor.href = urlAnchor.href; s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== urlAnchor.protocol + "//" + urlAnchor.host; } catch ( e ) { // If there is an error parsing the URL, assume it is crossDomain, // it can be rejected by the transport if it is invalid s.crossDomain = true; } } // Convert data if not already a string if ( s.data && s.processData && typeof s.data !== "string" ) { s.data = jQuery.param( s.data, s.traditional ); } // Apply prefilters inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); // If request was aborted inside a prefilter, stop there if ( completed ) { return jqXHR; } // We can fire global events as of now if asked to // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) fireGlobals = jQuery.event && s.global; // Watch for a new set of requests if ( fireGlobals && jQuery.active++ === 0 ) { jQuery.event.trigger( "ajaxStart" ); } // Uppercase the type s.type = s.type.toUpperCase(); // Determine if request has content s.hasContent = !rnoContent.test( s.type ); // Save the URL in case we're toying with the If-Modified-Since // and/or If-None-Match header later on // Remove hash to simplify url manipulation cacheURL = s.url.replace( rhash, "" ); // More options handling for requests with no content if ( !s.hasContent ) { // Remember the hash so we can put it back uncached = s.url.slice( cacheURL.length ); // If data is available and should be processed, append data to url if ( s.data && ( s.processData || typeof s.data === "string" ) ) { cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; // #9682: remove data so that it's not used in an eventual retry delete s.data; } // Add or update anti-cache param if needed if ( s.cache === false ) { cacheURL = cacheURL.replace( rantiCache, "$1" ); uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + uncached; } // Put hash and anti-cache on the URL that will be requested (gh-1732) s.url = cacheURL + uncached; // Change '%20' to '+' if this is encoded form body content (gh-2658) } else if ( s.data && s.processData && ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { s.data = s.data.replace( r20, "+" ); } // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. if ( s.ifModified ) { if ( jQuery.lastModified[ cacheURL ] ) { jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); } if ( jQuery.etag[ cacheURL ] ) { jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); } } // Set the correct header, if data is being sent if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { jqXHR.setRequestHeader( "Content-Type", s.contentType ); } // Set the Accepts header for the server, depending on the dataType jqXHR.setRequestHeader( "Accept", s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? s.accepts[ s.dataTypes[ 0 ] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : s.accepts[ "*" ] ); // Check for headers option for ( i in s.headers ) { jqXHR.setRequestHeader( i, s.headers[ i ] ); } // Allow custom headers/mimetypes and early abort if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { // Abort if not done already and return return jqXHR.abort(); } // Aborting is no longer a cancellation strAbort = "abort"; // Install callbacks on deferreds completeDeferred.add( s.complete ); jqXHR.done( s.success ); jqXHR.fail( s.error ); // Get transport transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); // If no transport, we auto-abort if ( !transport ) { done( -1, "No Transport" ); } else { jqXHR.readyState = 1; // Send global event if ( fireGlobals ) { globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); } // If request was aborted inside ajaxSend, stop there if ( completed ) { return jqXHR; } // Timeout if ( s.async && s.timeout > 0 ) { timeoutTimer = window.setTimeout( function() { jqXHR.abort( "timeout" ); }, s.timeout ); } try { completed = false; transport.send( requestHeaders, done ); } catch ( e ) { // Rethrow post-completion exceptions if ( completed ) { throw e; } // Propagate others as results done( -1, e ); } } // Callback for when everything is done function done( status, nativeStatusText, responses, headers ) { var isSuccess, success, error, response, modified, statusText = nativeStatusText; // Ignore repeat invocations if ( completed ) { return; } completed = true; // Clear timeout if it exists if ( timeoutTimer ) { window.clearTimeout( timeoutTimer ); } // Dereference transport for early garbage collection // (no matter how long the jqXHR object will be used) transport = undefined; // Cache response headers responseHeadersString = headers || ""; // Set readyState jqXHR.readyState = status > 0 ? 4 : 0; // Determine if successful isSuccess = status >= 200 && status < 300 || status === 304; // Get response data if ( responses ) { response = ajaxHandleResponses( s, jqXHR, responses ); } // Use a noop converter for missing script if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) { s.converters[ "text script" ] = function() {}; } // Convert no matter what (that way responseXXX fields are always set) response = ajaxConvert( s, response, jqXHR, isSuccess ); // If successful, handle type chaining if ( isSuccess ) { // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. if ( s.ifModified ) { modified = jqXHR.getResponseHeader( "Last-Modified" ); if ( modified ) { jQuery.lastModified[ cacheURL ] = modified; } modified = jqXHR.getResponseHeader( "etag" ); if ( modified ) { jQuery.etag[ cacheURL ] = modified; } } // if no content if ( status === 204 || s.type === "HEAD" ) { statusText = "nocontent"; // if not modified } else if ( status === 304 ) { statusText = "notmodified"; // If we have data, let's convert it } else { statusText = response.state; success = response.data; error = response.error; isSuccess = !error; } } else { // Extract error from statusText and normalize for non-aborts error = statusText; if ( status || !statusText ) { statusText = "error"; if ( status < 0 ) { status = 0; } } } // Set data for the fake xhr object jqXHR.status = status; jqXHR.statusText = ( nativeStatusText || statusText ) + ""; // Success/Error if ( isSuccess ) { deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); } else { deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); } // Status-dependent callbacks jqXHR.statusCode( statusCode ); statusCode = undefined; if ( fireGlobals ) { globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", [ jqXHR, s, isSuccess ? success : error ] ); } // Complete completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); if ( fireGlobals ) { globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); // Handle the global AJAX counter if ( !( --jQuery.active ) ) { jQuery.event.trigger( "ajaxStop" ); } } } return jqXHR; }, getJSON: function( url, data, callback ) { return jQuery.get( url, data, callback, "json" ); }, getScript: function( url, callback ) { return jQuery.get( url, undefined, callback, "script" ); } } ); jQuery.each( [ "get", "post" ], function( _i, method ) { jQuery[ method ] = function( url, data, callback, type ) { // Shift arguments if data argument was omitted if ( isFunction( data ) ) { type = type || callback; callback = data; data = undefined; } // The url can be an options object (which then must have .url) return jQuery.ajax( jQuery.extend( { url: url, type: method, dataType: type, data: data, success: callback }, jQuery.isPlainObject( url ) && url ) ); }; } ); jQuery.ajaxPrefilter( function( s ) { var i; for ( i in s.headers ) { if ( i.toLowerCase() === "content-type" ) { s.contentType = s.headers[ i ] || ""; } } } ); jQuery._evalUrl = function( url, options, doc ) { return jQuery.ajax( { url: url, // Make this explicit, since user can override this through ajaxSetup (#11264) type: "GET", dataType: "script", cache: true, async: false, global: false, // Only evaluate the response if it is successful (gh-4126) // dataFilter is not invoked for failure responses, so using it instead // of the default converter is kludgy but it works. converters: { "text script": function() {} }, dataFilter: function( response ) { jQuery.globalEval( response, options, doc ); } } ); }; jQuery.fn.extend( { wrapAll: function( html ) { var wrap; if ( this[ 0 ] ) { if ( isFunction( html ) ) { html = html.call( this[ 0 ] ); } // The elements to wrap the target around wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); if ( this[ 0 ].parentNode ) { wrap.insertBefore( this[ 0 ] ); } wrap.map( function() { var elem = this; while ( elem.firstElementChild ) { elem = elem.firstElementChild; } return elem; } ).append( this ); } return this; }, wrapInner: function( html ) { if ( isFunction( html ) ) { return this.each( function( i ) { jQuery( this ).wrapInner( html.call( this, i ) ); } ); } return this.each( function() { var self = jQuery( this ), contents = self.contents(); if ( contents.length ) { contents.wrapAll( html ); } else { self.append( html ); } } ); }, wrap: function( html ) { var htmlIsFunction = isFunction( html ); return this.each( function( i ) { jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); } ); }, unwrap: function( selector ) { this.parent( selector ).not( "body" ).each( function() { jQuery( this ).replaceWith( this.childNodes ); } ); return this; } } ); jQuery.expr.pseudos.hidden = function( elem ) { return !jQuery.expr.pseudos.visible( elem ); }; jQuery.expr.pseudos.visible = function( elem ) { return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); }; jQuery.ajaxSettings.xhr = function() { try { return new window.XMLHttpRequest(); } catch ( e ) {} }; var xhrSuccessStatus = { // File protocol always yields status code 0, assume 200 0: 200, // Support: IE <=9 only // #1450: sometimes IE returns 1223 when it should be 204 1223: 204 }, xhrSupported = jQuery.ajaxSettings.xhr(); support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); support.ajax = xhrSupported = !!xhrSupported; jQuery.ajaxTransport( function( options ) { var callback, errorCallback; // Cross domain only allowed if supported through XMLHttpRequest if ( support.cors || xhrSupported && !options.crossDomain ) { return { send: function( headers, complete ) { var i, xhr = options.xhr(); xhr.open( options.type, options.url, options.async, options.username, options.password ); // Apply custom fields if provided if ( options.xhrFields ) { for ( i in options.xhrFields ) { xhr[ i ] = options.xhrFields[ i ]; } } // Override mime type if needed if ( options.mimeType && xhr.overrideMimeType ) { xhr.overrideMimeType( options.mimeType ); } // X-Requested-With header // For cross-domain requests, seeing as conditions for a preflight are // akin to a jigsaw puzzle, we simply never set it to be sure. // (it can always be set on a per-request basis or even using ajaxSetup) // For same-domain requests, won't change header if already provided. if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { headers[ "X-Requested-With" ] = "XMLHttpRequest"; } // Set headers for ( i in headers ) { xhr.setRequestHeader( i, headers[ i ] ); } // Callback callback = function( type ) { return function() { if ( callback ) { callback = errorCallback = xhr.onload = xhr.onerror = xhr.onabort = xhr.ontimeout = xhr.onreadystatechange = null; if ( type === "abort" ) { xhr.abort(); } else if ( type === "error" ) { // Support: IE <=9 only // On a manual native abort, IE9 throws // errors on any property access that is not readyState if ( typeof xhr.status !== "number" ) { complete( 0, "error" ); } else { complete( // File: protocol always yields status 0; see #8605, #14207 xhr.status, xhr.statusText ); } } else { complete( xhrSuccessStatus[ xhr.status ] || xhr.status, xhr.statusText, // Support: IE <=9 only // IE9 has no XHR2 but throws on binary (trac-11426) // For XHR2 non-text, let the caller handle it (gh-2498) ( xhr.responseType || "text" ) !== "text" || typeof xhr.responseText !== "string" ? { binary: xhr.response } : { text: xhr.responseText }, xhr.getAllResponseHeaders() ); } } }; }; // Listen to events xhr.onload = callback(); errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); // Support: IE 9 only // Use onreadystatechange to replace onabort // to handle uncaught aborts if ( xhr.onabort !== undefined ) { xhr.onabort = errorCallback; } else { xhr.onreadystatechange = function() { // Check readyState before timeout as it changes if ( xhr.readyState === 4 ) { // Allow onerror to be called first, // but that will not handle a native abort // Also, save errorCallback to a variable // as xhr.onerror cannot be accessed window.setTimeout( function() { if ( callback ) { errorCallback(); } } ); } }; } // Create the abort callback callback = callback( "abort" ); try { // Do send the request (this may raise an exception) xhr.send( options.hasContent && options.data || null ); } catch ( e ) { // #14683: Only rethrow if this hasn't been notified as an error yet if ( callback ) { throw e; } } }, abort: function() { if ( callback ) { callback(); } } }; } } ); // Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) jQuery.ajaxPrefilter( function( s ) { if ( s.crossDomain ) { s.contents.script = false; } } ); // Install script dataType jQuery.ajaxSetup( { accepts: { script: "text/javascript, application/javascript, " + "application/ecmascript, application/x-ecmascript" }, contents: { script: /\b(?:java|ecma)script\b/ }, converters: { "text script": function( text ) { jQuery.globalEval( text ); return text; } } } ); // Handle cache's special case and crossDomain jQuery.ajaxPrefilter( "script", function( s ) { if ( s.cache === undefined ) { s.cache = false; } if ( s.crossDomain ) { s.type = "GET"; } } ); // Bind script tag hack transport jQuery.ajaxTransport( "script", function( s ) { // This transport only deals with cross domain or forced-by-attrs requests if ( s.crossDomain || s.scriptAttrs ) { var script, callback; return { send: function( _, complete ) { script = jQuery( "

Priceable

class Priceable(**kwargs)[source]
__init__(**kwargs)

Initialize self. See help(type(self)) for accurate signature.

Methods

__init__(**kwargs)

Initialize self.

as_dict([as_camel_case])

Dictionary of the public, non-null properties and values

calc(risk_measure[, fn])

Calculate the value of the risk_measure

clone(**kwargs)

Clone this object, overriding specified values

default_instance()

Construct a default instance of this type

dollar_price()

Present value in USD

from_dict(values)

Construct an instance of this type from a dictionary

from_instance(instance)

Copy the values from an existing instance of the same type to our self :param instance: from which to copy: :return:

price()

Present value in local currency.

prop_item_type(prop)

rtype

type

prop_type(prop[, additional])

rtype

type

properties()

The public property names of this class

resolve([in_place])

Resolve non-supplied properties of an instrument

================================================ FILE: docs/_build/html/classes/gs_quant.data.DataContext.html ================================================ DataContext — gs_quant 0.1 documentation

DataContext

class DataContext(start=None, end=None)[source]
__init__(start=None, end=None)[source]

Initialize self. See help(type(self)) for accurate signature.

Methods

__init__([start, end])

Initialize self.

default_value()

rtype

object

Attributes

end_date

end_time

is_entered

rtype

bool

start_date

start_time

================================================ FILE: docs/_build/html/classes/gs_quant.data.Dataset.html ================================================ Dataset — gs_quant 0.1 documentation

Dataset

class Dataset(dataset_id, provider=None)[source]

A collection of related data

__init__(dataset_id, provider=None)[source]
Parameters
  • dataset_id (Union[str, Vendor]) – The dataset’s identifier

  • provider (Optional[DataApi]) – The data provider

Methods

__init__(dataset_id[, provider])

type dataset_id

Union[str, Vendor]

get_coverage([limit, offset, fields, …])

Get the assets covered by this DataSet

get_data([start, end, as_of, since, fields, …])

Get data for the given range and parameters

get_data_last(as_of[, start, fields])

Get the last point for this DataSet, at or before as_of

get_data_series(field[, start, end, as_of, …])

Get a time series of data for a field of a dataset

Attributes

id

The dataset’s identifier

name

provider

================================================ FILE: docs/_build/html/classes/gs_quant.data.Fields.html ================================================ Fields — gs_quant 0.1 documentation

Fields

class Fields(value=<object object>, names=None, module=None, type=None, start=1)[source]

Data field enumeration

Enumeration of fields available through data APIs

__init__(*args, **kwds)

Initialize self. See help(type(self)) for accurate signature.

Attributes

ADJUSTED_ASK_PRICE

ADJUSTED_BID_PRICE

ADJUSTED_HIGH_PRICE

ADJUSTED_LOW_PRICE

ADJUSTED_TRADE_PRICE

ADJUSTED_VOLUME

ASK_PRICE

ASSET_ID

BID_PRICE

CLOSE_PRICE

EXPIRATION_DATE

HIGH_PRICE

IMPLIED_VOLATILITY

LOW_PRICE

MID_PRICE

NAME

OPEN_PRICE

RELATIVE_STRIKE

RIC

SPOT_PRICE

STRIKE_REFERENCE

TENOR

TRADE_PRICE

UPDATE_TIME

VAR_SWAP

VOLUME

================================================ FILE: docs/_build/html/classes/gs_quant.instrument.CommodOTCSwap.html ================================================ CommodOTCSwap — gs_quant 0.1 documentation

CommodOTCSwap

For methods of this class, see gs_quant.base.Priceable

class CommodOTCSwap(start=None, end=None, number_of_periods=None, strategy=None, quantity=None, quantity_unit=None, quantity_period=None, legs=None, settlement=None, name=None)[source]

Object representation of a commodities swap

Properties

end

Date or Contract Month

Return type

Union[date, str]

instrument_quantity
Return type

float

legs

Commodities OTC swap leg

Return type

Tuple[CommodOTCSwapLeg, …]

number_of_periods

The number of settlement periods

Return type

int

provider
quantity

Size of some value, i.e. notional like 1.3b, 1.5, 1000

Return type

Union[float, str]

quantity_period

A coding scheme to define a period corresponding to a quantity amount

Return type

Union[Period, str]

quantity_unit

Commodity asset

Return type

str

resolution_key
Return type

RiskKey

settlement

read only description in plain English of settlement terms

Return type

str

start

Date or Contract Month

Return type

Union[date, str]

strategy

Strip and Commodity Spread

Type

Swap Strategy

Return type

str

unresolved
================================================ FILE: docs/_build/html/classes/gs_quant.instrument.CommodSwap.html ================================================ CommodSwap — gs_quant 0.1 documentation

CommodSwap

For methods of this class, see gs_quant.base.Priceable

class CommodSwap(commodity, start, commodity_reference_price=None, notional_amount=1000000.0, currency=None, calculation_periods=None, calculation_period_frequency=None)[source]

Object representation of a commodities swap

Properties

calculation_period_frequency

The frequency of the calculation periods

Return type

Union[Frequency, str]

calculation_periods

The number of calculation periods

Return type

int

commodity

Commodity asset

Return type

Union[CommodityAsset, str]

commodity_reference_price
Return type

str

currency

Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)

Return type

Union[Currency, str]

notional_amount

Notional amount

Return type

float

start
Return type

Union[date, str]

================================================ FILE: docs/_build/html/classes/gs_quant.instrument.EqCliquet.html ================================================ EqCliquet — gs_quant 0.1 documentation

EqCliquet

For methods of this class, see gs_quant.base.Priceable

class EqCliquet(underlier=None, underlier_type=None, expiration_date=None, strike_price=None, currency=None, first_valuation_date=None, global_floor=- 1000000, global_cap=1000000, last_valuation_date=None, notional_amount=None, payment_frequency='Maturity', return_style='Rate of Return', return_type='Sum', valuation_period=None, name=None)[source]

Object representation of an Equity Cliquet

Properties

currency

Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)

Return type

Union[Currency, str]

expiration_date

Date or tenor, e.g. 2018-09-03, 3m, Dec21

Return type

Union[date, str]

first_valuation_date

ISO 8601-formatted date

Return type

date

global_cap

Global Cap of return, relevant only if paying at maturity

Return type

float

global_floor

Global Floor of return, relevant only if paying at maturity

Return type

float

instrument_quantity
Return type

float

last_valuation_date

ISO 8601-formatted date

Return type

date

notional_amount

Notional of this position

Return type

Union[float, str]

payment_frequency
Return type

str

provider
resolution_key
Return type

RiskKey

return_style

Return calculation style

Return type

str

return_type

Sum or Product of periodic return, relevant only if paying at maturity

Return type

str

strike_price

Strike price as value

Return type

float

underlier

Underlier security identifier

Return type

Union[float, str]

underlier_type

Type of underlyer

Return type

Union[UnderlierType, str]

unresolved
valuation_period

Tenor

Return type

str

================================================ FILE: docs/_build/html/classes/gs_quant.instrument.EqForward.html ================================================ EqForward — gs_quant 0.1 documentation

EqForward

For methods of this class, see gs_quant.base.Priceable

class EqForward(underlier=None, underlier_type=None, expiration_date=None, forward_price=None, number_of_shares=1, name=None)[source]

Object representation of an equity forward

Properties

expiration_date

Date or tenor, e.g. 2018-09-03, 3m, Dec21

Return type

Union[date, str]

forward_price

Forward price

Return type

float

instrument_quantity
Return type

float

number_of_shares

Number of shares

Return type

int

provider
resolution_key
Return type

RiskKey

underlier

Underlier security identifier

Return type

Union[float, str]

underlier_type

Type of underlyer

Return type

Union[UnderlierType, str]

unresolved
================================================ FILE: docs/_build/html/classes/gs_quant.instrument.EqOption.html ================================================ EqOption — gs_quant 0.1 documentation

EqOption

For methods of this class, see gs_quant.base.Priceable

class EqOption(underlier=None, expiration_date=None, strike_price=None, option_type=None, option_style=None, number_of_options=None, exchange=None, multiplier=None, settlement_date=None, settlement_currency=None, premium=0, premium_payment_date=None, valuation_time=None, method_of_settlement=None, underlier_type=None, buy_sell=None, premium_currency=None, trade_as=None, name=None)[source]

Instrument definition for equity option

Properties

buy_sell

Buy or Sell side of contract

Return type

Union[BuySell, str]

exchange

Name of marketplace where security, derivative or other instrument is traded

Return type

str

expiration_date

Date or tenor, e.g. 2018-09-03, 3m, Dec21

Return type

Union[date, str]

instrument_quantity
Return type

float

method_of_settlement

How the option is settled (e.g. Cash, Physical)

Return type

Union[OptionSettlementMethod, str]

multiplier

Number of stock units per option contract

Return type

float

number_of_options

Number of options

Return type

float

option_style

Option Exercise Style

Return type

Union[OptionStyle, str]

option_type

Option Type

Return type

Union[OptionType, str]

premium

Option premium

Return type

float

premium_currency

Currency of the option premium

Return type

Union[Currency, str]

premium_payment_date

Option premium

Return type

Union[date, str]

provider
resolution_key
Return type

RiskKey

settlement_currency

Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)

Return type

Union[Currency, str]

settlement_date

Date or tenor, e.g. 2018-09-03, 3m, Dec21

Return type

Union[date, str]

strike_price

Strike as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF, 10/vol, 100k/pv, p=10000, p=10000USD, $200K/BP, or multiple strikes 65.4/-45.8

Return type

Union[float, str]

trade_as

Option trade as (i.e. listed, otc, lookalike etc)

Return type

Union[TradeAs, str]

underlier

Underlier security identifier

Return type

Union[float, str]

underlier_type

Type of underlyer

Return type

Union[UnderlierType, str]

unresolved
valuation_time

Valuation time (e.g. MktClose, MktOpen) of the underlying level for exercise

Return type

Union[ValuationTime, str]

================================================ FILE: docs/_build/html/classes/gs_quant.instrument.EqSynthetic.html ================================================ EqSynthetic — gs_quant 0.1 documentation

EqSynthetic

For methods of this class, see gs_quant.base.Priceable

class EqSynthetic(underlier, expiry, currency=None, swap_type='Eq Swap', buy_sell=None, underlier_type=None, effective_date=None, num_of_underlyers=None, name=None)[source]

Instrument definition for equity synthetics

Properties

buy_sell

Buy or Sell side of contract

Return type

Union[BuySell, str]

currency

Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)

Return type

Union[Currency, str]

effective_date

The date on which the synthetic becomes effective

Return type

date

expiry

Tenor

Return type

str

instrument_quantity
Return type

float

num_of_underlyers

number of underlyers referenced in synthetic contract

Return type

float

provider
resolution_key
Return type

RiskKey

swap_type
Return type

str

underlier

Underlier security identifier

Return type

Union[float, str]

underlier_type

Type of underlyer

Return type

Union[UnderlierType, str]

unresolved
================================================ FILE: docs/_build/html/classes/gs_quant.instrument.EqVarianceSwap.html ================================================ EqVarianceSwap — gs_quant 0.1 documentation

EqVarianceSwap

For methods of this class, see gs_quant.base.Priceable

class EqVarianceSwap(underlier=None, underlier_type=None, expiration_date=None, strike_price=None, variance_cap=None, settlement_date=None, premium=None, name=None)[source]

Instrument definition for equity variance swap

Properties

expiration_date

Date or tenor, e.g. 2018-09-03, 3m

Return type

Union[date, str]

instrument_quantity
Return type

float

premium

VarSwap premium

Return type

Union[float, str]

provider
resolution_key
Return type

RiskKey

settlement_date

Settlement date

Return type

Union[date, str]

strike_price

Variance strike as value or percentage string e.g. 62.5, 95%

Return type

Union[float, str]

underlier

Underlier security identifier

Return type

Union[float, str]

underlier_type

Type of underlyer

Return type

Union[UnderlierType, str]

unresolved
variance_cap

Variance Cap as absolute value

Return type

float

================================================ FILE: docs/_build/html/classes/gs_quant.instrument.FXBinary.html ================================================ FXBinary — gs_quant 0.1 documentation

FXBinary

For methods of this class, see gs_quant.base.Priceable

class FXBinary(pair=None, buy_sell=None, option_type=None, notional_amount=None, notional_currency=None, strike_price=None, settlement_date=None, expiration_date=None, expiration_time=None, premium=None, premium_currency=None, premium_payment_date=None, fixing_source=None, name=None)[source]

Object representation of a FX binary option

Properties

buy_sell

Buy or Sell side of contract

Return type

Union[BuySell, str]

expiration_date

Date or tenor, e.g. 2018-09-03, 3m, Dec21

Return type

Union[date, str]

expiration_time

The location and (optionally) time of spot for expiration

Return type

str

fixing_source

The data source to be used for observation of FX spot on the fixing date

Return type

str

instrument_quantity
Return type

float

notional_amount

Notional amount

Return type

Union[float, str]

notional_currency

Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)

Return type

Union[Currency, str]

option_type

Option Type

Return type

Union[OptionType, str]

pair

EURUSD or EUR USD

Type

A currency pair, e.g.

Return type

str

premium

Option premium

Return type

Union[float, str]

premium_currency

Currency of the option premium

Return type

Union[Currency, str]

premium_payment_date

Payment date of the option premium

Return type

str

provider
resolution_key
Return type

RiskKey

settlement_date

Settlement date of the option, after expiration

Return type

Union[date, str]

strike_price

Strike as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF, 10/vol, 100k/pv, p=10000, p=10000USD, $200K/BP, or multiple strikes 65.4/-45.8

Return type

Union[float, str]

unresolved
================================================ FILE: docs/_build/html/classes/gs_quant.instrument.FXForward.html ================================================ FXForward — gs_quant 0.1 documentation

FXForward

For methods of this class, see gs_quant.base.Priceable

class FXForward(pair=None, settlement_date=None, forward_rate=None, notional_amount=None, name=None)[source]

Object representation of an FX forward

Properties

forward_rate

Forward FX rate

Return type

Union[float, str]

instrument_quantity
Return type

float

notional_amount

Notional amount

Return type

Union[float, str]

pair

EURUSD or EUR USD

Type

A currency pair, e.g.

Return type

str

provider
resolution_key
Return type

RiskKey

settlement_date

Date or tenor, e.g. 2018-09-03, 3m, Dec21

Return type

Union[date, str]

unresolved
================================================ FILE: docs/_build/html/classes/gs_quant.instrument.FXMultiCrossBinary.html ================================================ FXMultiCrossBinary — gs_quant 0.1 documentation

FXMultiCrossBinary

For methods of this class, see gs_quant.base.Priceable

class FXMultiCrossBinary(buy_sell=None, legs=None, notional_amount=None, notional_currency=None, settlement_date=None, expiration_date=None, expiration_time=None, premium=None, premium_currency=None, premium_payment_date=None, name=None)[source]

Object representation of an FX multi-cross binary

Properties

buy_sell

Buy or Sell side of contract

Return type

Union[BuySell, str]

expiration_date

Date or tenor, e.g. 2018-09-03, 3m, Dec21

Return type

Union[date, str]

expiration_time

The location and (optionally) time of spot for expiration

Return type

str

instrument_quantity
Return type

float

legs

Object representation of a single leg of a multi-cross binary option

Return type

Tuple[FXMultiCrossBinaryLeg, …]

notional_amount

Notional amount

Return type

Union[float, str]

notional_currency

Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)

Return type

Union[Currency, str]

premium

Option premium

Return type

Union[float, str]

premium_currency

Currency of the option premium

Return type

Union[Currency, str]

premium_payment_date

Payment date of the option premium

Return type

str

provider
resolution_key
Return type

RiskKey

settlement_date

Settlement date of the option, after expiration

Return type

Union[date, str]

unresolved
================================================ FILE: docs/_build/html/classes/gs_quant.instrument.FXMultiCrossBinaryLeg.html ================================================ FXMultiCrossBinaryLeg — gs_quant 0.1 documentation

FXMultiCrossBinaryLeg

For methods of this class, see gs_quant.base.Priceable

class FXMultiCrossBinaryLeg(pair=None, option_type=None, strike_price=None, fixing_source=None, name=None)[source]

Object representation of a single leg of a multi-cross binary option

Properties

fixing_source

The data source to be used for observation of FX spot on the fixing date

Return type

str

instrument_quantity
Return type

float

option_type

Option Type

Return type

Union[OptionType, str]

pair

EURUSD or EUR USD

Type

A currency pair, e.g.

Return type

str

provider
resolution_key
Return type

RiskKey

strike_price

Strike as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF, 10/vol, 100k/pv, p=10000, p=10000USD, $200K/BP, or multiple strikes 65.4/-45.8

Return type

Union[float, str]

unresolved
================================================ FILE: docs/_build/html/classes/gs_quant.instrument.FXOption.html ================================================ FXOption — gs_quant 0.1 documentation

FXOption

For methods of this class, see gs_quant.base.Priceable

class FXOption(pair=None, buy_sell=None, option_type=None, notional_amount=None, notional_currency=None, notional_amount_other_currency=None, strike_price=None, settlement_date=None, settlement_currency=None, settlement_rate_option=None, method_of_settlement=None, expiration_date=None, expiration_time=None, premium=None, premium_currency=None, premium_payment_date=None, name=None)[source]

Object representation of an FX option

Properties

buy_sell

Buy or Sell side of contract

Return type

Union[BuySell, str]

expiration_date

Date or tenor, e.g. 2018-09-03, 3m, Dec21

Return type

Union[date, str]

expiration_time

The location and (optionally) time of spot for expiration

Return type

str

instrument_quantity
Return type

float

method_of_settlement

How the option is settled (e.g. Cash, Physical)

Return type

Union[OptionSettlementMethod, str]

notional_amount

Notional amount

Return type

Union[float, str]

notional_amount_other_currency

Notional amount in currency other than NotionalCurrency from the pair

Return type

Union[float, str]

notional_currency

Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)

Return type

Union[Currency, str]

option_type

Option Type

Return type

Union[OptionType, str]

pair

EURUSD or EUR USD

Type

A currency pair, e.g.

Return type

str

premium

Option premium

Return type

Union[float, str]

premium_currency

Currency of the option premium

Return type

Union[Currency, str]

premium_payment_date

Payment date of the option premium

Return type

str

provider
resolution_key
Return type

RiskKey

settlement_currency

Currency of settlement

Return type

Union[Currency, str]

settlement_date

Settlement date of the option, after expiration

Return type

Union[date, str]

settlement_rate_option

The source of spot for settlement

Return type

str

strike_price

Strike as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF, 10/vol, 100k/pv, p=10000, p=10000USD, $200K/BP, or multiple strikes 65.4/-45.8

Return type

Union[float, str]

unresolved
================================================ FILE: docs/_build/html/classes/gs_quant.instrument.FXVolatilitySwap.html ================================================ FXVolatilitySwap — gs_quant 0.1 documentation

FXVolatilitySwap

For methods of this class, see gs_quant.base.Priceable

class FXVolatilitySwap(pair=None, buy_sell=None, strike_vol=None, notional_currency=None, notional_amount=None, first_fixing_date=None, last_fixing_date=None, settlement_date=None, fixing_source=None, fixing_frequency=None, annualization_factor=None, calculate_mean_return=0, name=None)[source]

Object representation of an FX Vol Swap

Properties

annualization_factor

Annualization factor is the number of days used per year to compute volatility

Return type

float

buy_sell

Buy or Sell side of contract

Return type

Union[BuySell, str]

calculate_mean_return

Indicates whether the mean return is calculated (true) or taken as zero (false) in the realized volatility computation

Return type

float

first_fixing_date

First averaging date or observation date

Return type

Union[date, str]

fixing_frequency

Fixing frequency (ex. Daily / Business Days)

Return type

str

fixing_source

The data source to be used for observations of FX spot on each fixing

Return type

str

instrument_quantity
Return type

float

last_fixing_date

Last averaging date or valuation date

Return type

Union[date, str]

notional_amount

Notional amount in dollar terms

Return type

Union[float, str]

notional_currency

Notional currency

Return type

Union[Currency, str]

pair

EURUSD or EUR USD

Type

A currency pair, e.g.

Return type

str

provider
resolution_key
Return type

RiskKey

settlement_date

Date or tenor, e.g. 2018-09-03, 3m, Dec21

Return type

Union[date, str]

strike_vol

Volatility strike

Return type

Union[float, str]

unresolved
================================================ FILE: docs/_build/html/classes/gs_quant.instrument.Forward.html ================================================ Forward — gs_quant 0.1 documentation

Forward

For methods of this class, see gs_quant.base.Priceable

class Forward(currency=None, expiration_date=None, notional_amount=None, name=None)[source]

Forward cash payment

Properties

currency

Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)

Return type

Union[Currency, str]

expiration_date

Date or tenor, e.g. 2018-09-03, 3m, Dec21

Return type

Union[date, str]

instrument_quantity
Return type

float

notional_amount

Notional amount

Return type

Union[float, str]

provider
resolution_key
Return type

RiskKey

unresolved
================================================ FILE: docs/_build/html/classes/gs_quant.instrument.IRBasisSwap.html ================================================ IRBasisSwap — gs_quant 0.1 documentation

IRBasisSwap

For methods of this class, see gs_quant.base.Priceable

class IRBasisSwap(termination_date=None, notional_currency=None, notional_amount=None, effective_date=None, principal_exchange=None, payer_spread=None, payer_rate_option=None, payer_designated_maturity=None, payer_frequency=None, payer_day_count_fraction=None, payer_business_day_convention=None, receiver_spread=None, receiver_rate_option=None, receiver_designated_maturity=None, receiver_frequency=None, receiver_day_count_fraction=None, receiver_business_day_convention=None, fee=0, fee_currency=None, fee_payment_date=None, clearing_house=None, name=None)[source]

A single currency exchange of cashflows from different interest rate indices

Properties

clearing_house

Swap Clearing House

Return type

Union[SwapClearingHouse, str]

effective_date

The date on which the swap becomes effective

Return type

Union[date, str]

fee

The fee

Return type

float

fee_currency

Currency of the fee

Return type

Union[Currency, str]

fee_payment_date

Payment date of the fee

Return type

Union[date, str]

instrument_quantity
Return type

float

notional_amount

Notional amount

Return type

Union[float, str]

notional_currency

Notional currency

Return type

Union[Currency, str]

payer_business_day_convention

The business day convention for the payer

Return type

Union[BusinessDayConvention, str]

payer_day_count_fraction

The day count fraction for the payer

Return type

Union[DayCountFraction, str]

payer_designated_maturity

Tenor of the payerRateOption, e.g. 3m, 6m

Return type

str

payer_frequency

The frequency of payer payments, e.g. 6m

Return type

str

payer_rate_option

The underlying benchmark for the payer, e.g. USD-LIBOR-BBA, EUR-EURIBOR-TELERATE

Return type

str

payer_spread

Spread over the payer rate

Return type

Union[float, str]

principal_exchange

The date on which the swap becomes effective

Return type

Union[PrincipalExchange, str]

provider
receiver_business_day_convention

The business day convention for the receiver

Return type

Union[BusinessDayConvention, str]

receiver_day_count_fraction

The day count fraction for the receiver

Return type

Union[DayCountFraction, str]

receiver_designated_maturity

Tenor of the receiverRateOption, e.g. 3m, 6m

Return type

str

receiver_frequency

The frequency of receiver payments, e.g. 6m

Return type

str

receiver_rate_option

The underlying benchmark for the receiver, e.g. USD-LIBOR-BBA, EUR-EURIBOR- TELERATE

Return type

str

receiver_spread

Spread over the receiver rate

Return type

Union[float, str]

resolution_key
Return type

RiskKey

termination_date

The termination of the swap, e.g. 2050-04-01, 10y

Return type

Union[date, str]

unresolved
================================================ FILE: docs/_build/html/classes/gs_quant.instrument.IRCMSOption.html ================================================ IRCMSOption — gs_quant 0.1 documentation

IRCMSOption

For methods of this class, see gs_quant.base.Priceable

class IRCMSOption(cap_floor=None, termination_date=None, notional_currency=None, notional_amount=None, effective_date=None, strike=None, index=None, multiplier=None, premium=None, premium_payment_date=None, fee=0, fee_currency=None, fee_payment_date=None, buy_sell=None, name=None)[source]

Object representation of a constant maturity option (cap, floor, straddle)

Properties

buy_sell

Buy or Sell side of contract

Return type

Union[BuySell, str]

cap_floor

Structure type, e.g. Cap, Floor, Straddle, Binary Cap

Return type

str

effective_date

CMS option effective date, e.g. 2019-01-01, 10y

Return type

Union[date, str]

fee

The fee

Return type

float

fee_currency

Currency of the fee

Return type

Union[Currency, str]

fee_payment_date

Payment date of the fee

Return type

Union[date, str]

index

The underlying benchmark i.e. 30yUSD

Return type

str

instrument_quantity
Return type

float

multiplier

Multiplier

Return type

float

notional_amount

Notional amount

Return type

Union[float, str]

notional_currency

Notional currency

Return type

Union[Currency, str]

premium

The premium

Return type

Union[float, str]

premium_payment_date

Payment date of the premium

Return type

Union[date, str]

provider
resolution_key
Return type

RiskKey

strike

Strike as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF, 10/vol, 100k/pv, p=10000, p=10000USD, $200K/BP, or multiple strikes 65.4/-45.8

Return type

Union[float, str]

termination_date

Swap termination date, e.g. 2030-05-01, 10y

Return type

Union[date, str]

unresolved
================================================ FILE: docs/_build/html/classes/gs_quant.instrument.IRCMSOptionStrip.html ================================================ IRCMSOptionStrip — gs_quant 0.1 documentation

IRCMSOptionStrip

For methods of this class, see gs_quant.base.Priceable

class IRCMSOptionStrip(cap_floor=None, termination_date=None, notional_currency=None, notional_amount=None, effective_date=None, strike=None, index=None, floating_rate_frequency=None, floating_rate_day_count_fraction=None, floating_rate_business_day_convention=None, reset_delay=None, multiplier=None, premium=None, premium_payment_date=None, fee=0, fee_currency=None, fee_payment_date=None, buy_sell=None, name=None)[source]

Object representation of a constant maturity option strip (cap, floor, straddle)

Properties

buy_sell

Buy or Sell side of contract

Return type

Union[BuySell, str]

cap_floor

Structure type, e.g. Cap, Floor, Straddle, Binary Cap

Return type

str

effective_date

CMS option effective date, e.g. 2019-01-01, 10y

Return type

Union[date, str]

fee

The fee

Return type

float

fee_currency

Currency of the fee

Return type

Union[Currency, str]

fee_payment_date

Payment date of the fee

Return type

Union[date, str]

floating_rate_business_day_convention

The business day convention of the floating rate

Return type

Union[BusinessDayConvention, str]

floating_rate_day_count_fraction

The day count fraction of the floating rate

Return type

Union[DayCountFraction, str]

floating_rate_frequency

Period e.g. 3m, 1y

Return type

str

index

The underlying benchmark i.e. 30yUSD

Return type

str

instrument_quantity
Return type

float

multiplier

Multiplier

Return type

float

notional_amount

Notional amount

Return type

Union[float, str]

notional_currency

Notional currency

Return type

Union[Currency, str]

premium

The premium

Return type

Union[float, str]

premium_payment_date

Payment date of the premium

Return type

Union[date, str]

provider
reset_delay

Delay of the reset e.g. 2d

Return type

str

resolution_key
Return type

RiskKey

strike

Strike as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF, 10/vol, 100k/pv, p=10000, p=10000USD, $200K/BP, or multiple strikes 65.4/-45.8

Return type

Union[float, str]

termination_date

Swap termination date, e.g. 2030-05-01, 10y

Return type

Union[date, str]

unresolved
================================================ FILE: docs/_build/html/classes/gs_quant.instrument.IRCMSSpreadOption.html ================================================ IRCMSSpreadOption — gs_quant 0.1 documentation

IRCMSSpreadOption

For methods of this class, see gs_quant.base.Priceable

class IRCMSSpreadOption(cap_floor=None, termination_date=None, notional_currency=None, notional_amount=None, effective_date=None, strike=None, index1_tenor=None, index2_tenor=None, premium=None, premium_payment_date=None, fee=0, fee_currency=None, fee_payment_date=None, buy_sell=None, name=None)[source]

Object representation of a constant maturity spread option (cap, floor, straddle)

Properties

buy_sell

Buy or Sell side of contract

Return type

Union[BuySell, str]

cap_floor

Structure type, e.g. Cap, Floor, Straddle

Return type

str

effective_date

CMS option effective date, e.g. 2019-01-01, 10y

Return type

Union[date, str]

fee

The fee

Return type

float

fee_currency

Currency of the fee

Return type

Union[Currency, str]

fee_payment_date

Payment date of the fee

Return type

Union[date, str]

index1_tenor

The tenor of the underlying benchmark to be the first element i.e. 30y

Return type

str

index2_tenor

The tenor of the underlying benchmark to be the second element i.e. 5y

Return type

str

instrument_quantity
Return type

float

notional_amount

Notional amount

Return type

Union[float, str]

notional_currency

Notional currency

Return type

Union[Currency, str]

premium

The premium

Return type

Union[float, str]

premium_payment_date

Payment date of the premium

Return type

Union[date, str]

provider
resolution_key
Return type

RiskKey

strike

Strike as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF, 10/vol, 100k/pv, p=10000, p=10000USD, $200K/BP, or multiple strikes 65.4/-45.8

Return type

Union[float, str]

termination_date

Swap termination date, e.g. 2030-05-01, 10y

Return type

Union[date, str]

unresolved
================================================ FILE: docs/_build/html/classes/gs_quant.instrument.IRCMSSpreadOptionStrip.html ================================================ IRCMSSpreadOptionStrip — gs_quant 0.1 documentation

IRCMSSpreadOptionStrip

For methods of this class, see gs_quant.base.Priceable

class IRCMSSpreadOptionStrip(cap_floor=None, termination_date=None, notional_currency=None, notional_amount=None, effective_date=None, strike=None, index1=None, index2=None, floating_rate_frequency=None, floating_rate_day_count_fraction=None, floating_rate_business_day_convention=None, reset_delay=None, premium=None, premium_payment_date=None, fee=0, fee_currency=None, fee_payment_date=None, buy_sell=None, name=None)[source]

Object representation of a constant maturity spread option strip (cap, floor, straddle)

Properties

buy_sell

Buy or Sell side of contract

Return type

Union[BuySell, str]

cap_floor

Structure type, e.g. Cap, Floor, Straddle, Binary Cap

Return type

str

effective_date

CMS option effective date, e.g. 2019-01-01, 10y

Return type

Union[date, str]

fee

The fee

Return type

float

fee_currency

Currency of the fee

Return type

Union[Currency, str]

fee_payment_date

Payment date of the fee

Return type

Union[date, str]

floating_rate_business_day_convention

The business day convention of the floating rate

Return type

Union[BusinessDayConvention, str]

floating_rate_day_count_fraction

The day count fraction of the floating rate

Return type

Union[DayCountFraction, str]

floating_rate_frequency

Period e.g. 3m, 1y

Return type

str

index1

The underlying benchmark to be the first element from i.e. 30yUSD

Return type

str

index2

The underlying benchmark to be the second element from i.e. 5yUSD

Return type

str

instrument_quantity
Return type

float

notional_amount

Notional amount

Return type

Union[float, str]

notional_currency

Notional currency

Return type

Union[Currency, str]

premium

The premium

Return type

Union[float, str]

premium_payment_date

Payment date of the premium

Return type

Union[date, str]

provider
reset_delay

Delay of the reset e.g. 2d

Return type

str

resolution_key
Return type

RiskKey

strike

Strike as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF, 10/vol, 100k/pv, p=10000, p=10000USD, $200K/BP, or multiple strikes 65.4/-45.8

Return type

Union[float, str]

termination_date

Swap termination date, e.g. 2030-05-01, 10y

Return type

Union[date, str]

unresolved
================================================ FILE: docs/_build/html/classes/gs_quant.instrument.IRCap.html ================================================ IRCap — gs_quant 0.1 documentation

IRCap

For methods of this class, see gs_quant.base.Priceable

class IRCap(termination_date=None, notional_currency=None, notional_amount=None, effective_date=None, floating_rate_option=None, floating_rate_designated_maturity=None, floating_rate_frequency=None, floating_rate_day_count_fraction=None, floating_rate_business_day_convention=None, cap_rate=None, premium=None, premium_payment_date=None, fee=0, fee_currency=None, fee_payment_date=None, name=None)[source]

Object representation of an interest rate cap

Properties

cap_rate

The rate of this cap, as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF

Return type

Union[float, str]

effective_date

The date on which the cap becomes effective

Return type

Union[date, str]

fee

The fee

Return type

float

fee_currency

Currency of the fee

Return type

Union[Currency, str]

fee_payment_date

Payment date of the fee

Return type

Union[date, str]

floating_rate_business_day_convention

The business day convention of the floating rate

Return type

Union[BusinessDayConvention, str]

floating_rate_day_count_fraction

The day count fraction of the floating rate

Return type

Union[DayCountFraction, str]

floating_rate_designated_maturity

Tenor of the floatingRateOption, e.g. 3m, 6m

Return type

str

floating_rate_frequency

The frequency of floating payments, e.g. 3m

Return type

str

floating_rate_option

The underlying benchmark for the floating rate, e.g. USD-LIBOR-BBA, EUR-EURIBOR- TELERATE

Return type

str

instrument_quantity
Return type

float

notional_amount

Notional amount

Return type

Union[float, str]

notional_currency

Notional currency

Return type

Union[Currency, str]

premium

The premium

Return type

Union[float, str]

premium_payment_date

Payment date of the premium

Return type

Union[date, str]

provider
resolution_key
Return type

RiskKey

termination_date

The termination of the cap, e.g. 2025-04-01, 2y

Return type

Union[date, str]

unresolved
================================================ FILE: docs/_build/html/classes/gs_quant.instrument.IRFloor.html ================================================ IRFloor — gs_quant 0.1 documentation

IRFloor

For methods of this class, see gs_quant.base.Priceable

class IRFloor(termination_date=None, notional_currency=None, notional_amount=None, effective_date=None, floating_rate_option=None, floating_rate_designated_maturity=None, floating_rate_frequency=None, floating_rate_day_count_fraction=None, floating_rate_business_day_convention=None, floor_rate=None, premium=None, premium_payment_date=None, fee=0, fee_currency=None, fee_payment_date=None, name=None)[source]

Object representation of an interest rate floor

Properties

effective_date

The date on which the floor becomes effective

Return type

Union[date, str]

fee

The fee

Return type

float

fee_currency

Currency of the fee

Return type

Union[Currency, str]

fee_payment_date

Payment date of the fee

Return type

Union[date, str]

floating_rate_business_day_convention

The business day convention of the floating rate

Return type

Union[BusinessDayConvention, str]

floating_rate_day_count_fraction

The day count fraction of the floating rate

Return type

Union[DayCountFraction, str]

floating_rate_designated_maturity

Tenor of the floatingRateOption, e.g. 3m, 6m

Return type

str

floating_rate_frequency

The frequency of floating payments, e.g. 3m

Return type

str

floating_rate_option

The underlying benchmark for the floating rate, e.g. USD-LIBOR-BBA, EUR-EURIBOR- TELERATE

Return type

str

floor_rate

The rate of this floor, as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF

Return type

Union[float, str]

instrument_quantity
Return type

float

notional_amount

Notional amount

Return type

Union[float, str]

notional_currency

Notional currency

Return type

Union[Currency, str]

premium

The premium

Return type

Union[float, str]

premium_payment_date

Payment date of the premium

Return type

Union[date, str]

provider
resolution_key
Return type

RiskKey

termination_date

The termination of the floor, e.g. 2025-04-01, 2y

Return type

Union[date, str]

unresolved
================================================ FILE: docs/_build/html/classes/gs_quant.instrument.IRSwap.html ================================================ IRSwap — gs_quant 0.1 documentation

IRSwap

For methods of this class, see gs_quant.base.Priceable

class IRSwap(pay_or_receive=None, termination_date=None, notional_currency=None, notional_amount=None, effective_date=None, principal_exchange=None, floating_rate_for_the_initial_calculation_period=None, floating_rate_option=None, floating_rate_designated_maturity=None, floating_rate_spread=None, floating_rate_frequency=None, floating_rate_day_count_fraction=None, floating_rate_business_day_convention=None, fixed_rate=None, fixed_rate_frequency=None, fixed_rate_day_count_fraction=None, fixed_rate_business_day_convention=None, fee=0, fee_currency=None, fee_payment_date=None, clearing_house=None, fixed_first_stub=None, floating_first_stub=None, fixed_last_stub=None, floating_last_stub=None, fixed_holidays=None, floating_holidays=None, roll_convention=None, name=None)[source]

A vanilla interest rate swap of fixed vs floating cashflows

Properties

clearing_house

Swap Clearing House

Return type

Union[SwapClearingHouse, str]

effective_date

The date on which the swap becomes effective

Return type

Union[date, str]

fee

The fee

Return type

float

fee_currency

Currency of the fee

Return type

Union[Currency, str]

fee_payment_date

Payment date of the fee

Return type

Union[date, str]

fixed_first_stub

The date of the first stub for fixed leg

Return type

Union[date, str]

fixed_holidays

The accrual calendar for fixed leg

Return type

str

fixed_last_stub

The date of the last stub for fixed leg

Return type

Union[date, str]

fixed_rate

The coupon of the fixed leg

Return type

Union[float, str]

fixed_rate_business_day_convention

The business day convention for the fixed rate

Return type

Union[BusinessDayConvention, str]

fixed_rate_day_count_fraction

The day count fraction for the fixed rate

Return type

Union[DayCountFraction, str]

fixed_rate_frequency

The frequency of fixed payments, e.g. 6m

Return type

str

floating_first_stub

The date of the first stub for floating leg

Return type

Union[date, str]

floating_holidays

The accrual calendar for floating leg

Return type

str

floating_last_stub

The date of the last stub for floating leg

Return type

Union[date, str]

floating_rate_business_day_convention

The business day convention of the floating rate

Return type

Union[BusinessDayConvention, str]

floating_rate_day_count_fraction

The day count fraction of the floating rate

Return type

Union[DayCountFraction, str]

floating_rate_designated_maturity

Tenor of the floatingRateOption, e.g. 3m, 6m

Return type

str

floating_rate_for_the_initial_calculation_period

First fixing

Return type

float

floating_rate_frequency

The frequency of floating payments, e.g. 3m

Return type

str

floating_rate_option

The underlying benchmark for the floating rate, e.g. USD-LIBOR-BBA, EUR-EURIBOR- TELERATE

Return type

str

floating_rate_spread

The spread over the floating rate

Return type

Union[float, str]

instrument_quantity
Return type

float

notional_amount

Notional amount

Return type

Union[float, str]

notional_currency

Notional currency

Return type

Union[Currency, str]

pay_or_receive

Pay or receive fixed

Return type

Union[PayReceive, str]

principal_exchange

The date on which the swap becomes effective

Return type

Union[PrincipalExchange, str]

provider
resolution_key
Return type

RiskKey

roll_convention

The roll convention

Return type

str

termination_date

The termination of the swap, e.g. 2050-04-01, 10y

Return type

Union[date, str]

unresolved
================================================ FILE: docs/_build/html/classes/gs_quant.instrument.IRSwaption.html ================================================ IRSwaption — gs_quant 0.1 documentation

IRSwaption

For methods of this class, see gs_quant.base.Priceable

class IRSwaption(pay_or_receive=None, termination_date=None, notional_currency=None, effective_date=None, notional_amount=None, expiration_date=None, floating_rate_option=None, floating_rate_designated_maturity=None, floating_rate_spread=None, floating_rate_frequency=None, floating_rate_day_count_fraction=None, floating_rate_business_day_convention=None, fixed_rate_frequency=None, fixed_rate_day_count_fraction=None, fixed_rate_business_day_convention=None, strike=None, premium=None, premium_payment_date=None, fee=0, fee_currency=None, fee_payment_date=None, clearing_house=None, settlement=None, buy_sell=None, name=None)[source]

Object representation of a swaption

Properties

buy_sell

Buy or Sell side of contract

Return type

Union[BuySell, str]

clearing_house

Swap Clearing House

Return type

Union[SwapClearingHouse, str]

effective_date

Swaption effective date, e.g. 2019-01-01, 10y

Return type

Union[date, str]

expiration_date

Swaption expiration date, 2020-05-01, 3m

Return type

Union[date, str]

fee

The fee

Return type

float

fee_currency

Currency of the fee

Return type

Union[Currency, str]

fee_payment_date

Payment date of the fee

Return type

Union[date, str]

fixed_rate_business_day_convention

The business day convention for the fixed rate

Return type

Union[BusinessDayConvention, str]

fixed_rate_day_count_fraction

The day count fraction for the fixed rate

Return type

Union[DayCountFraction, str]

fixed_rate_frequency

The frequency of fixed payments, e.g. 6m

Return type

str

floating_rate_business_day_convention

The business day convention of the floating rate

Return type

Union[BusinessDayConvention, str]

floating_rate_day_count_fraction

The day count fraction of the floating rate

Return type

Union[DayCountFraction, str]

floating_rate_designated_maturity

Tenor

Return type

str

floating_rate_frequency

The frequency of floating payments, e.g. 3m

Return type

str

floating_rate_option

The underlying benchmark for the floating rate, e.g. USD-LIBOR-BBA, EUR-EURIBOR- TELERATE

Return type

str

floating_rate_spread

The spread over the floating rate

Return type

float

instrument_quantity
Return type

float

notional_amount

Notional amount

Return type

Union[float, str]

notional_currency

Notional currency

Return type

Union[Currency, str]

pay_or_receive

Pay or receive fixed

Return type

str

premium

The premium

Return type

Union[float, str]

premium_payment_date

Payment date of the premium

Return type

Union[date, str]

provider
resolution_key
Return type

RiskKey

settlement

Swap Settlement Type

Return type

Union[SwapSettlement, str]

strike

Strike as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF, 10/vol, 100k/pv, p=10000, p=10000USD, $200K/BP, or multiple strikes 65.4/-45.8

Return type

Union[float, str]

termination_date

Swaption termination date, e.g. 2030-05-01, 10y

Return type

Union[date, str]

unresolved
================================================ FILE: docs/_build/html/classes/gs_quant.instrument.IRXccySwap.html ================================================ IRXccySwap — gs_quant 0.1 documentation

IRXccySwap

For methods of this class, see gs_quant.base.Priceable

class IRXccySwap(termination_date=None, notional_amount=None, effective_date=None, principal_exchange=None, payer_currency=None, payer_spread=None, payer_rate_option=None, payer_designated_maturity=None, payer_frequency=None, payer_day_count_fraction=None, payer_business_day_convention=None, receiver_currency=None, receiver_spread=None, receiver_rate_option=None, receiver_designated_maturity=None, receiver_frequency=None, receiver_day_count_fraction=None, receiver_business_day_convention=None, fee=0, fee_currency=None, fee_payment_date=None, initial_fx_rate=None, payer_first_stub=None, receiver_first_stub=None, payer_last_stub=None, receiver_last_stub=None, payer_holidays=None, receiver_holidays=None, notional_reset_side=None, name=None)[source]

An exchange of cashflows from different interest rate indices

Properties

effective_date

The date on which the swap becomes effective

Return type

Union[date, str]

fee

The fee

Return type

float

fee_currency

Currency of the fee

Return type

Union[Currency, str]

fee_payment_date

Payment date of the fee

Return type

Union[date, str]

initial_fx_rate

Payment date of the fee

Return type

float

instrument_quantity
Return type

float

notional_amount

Notional amount

Return type

float

notional_reset_side

Pay or Rec leg resetting

Return type

Union[PayReceive, str]

payer_business_day_convention

The business day convention for the payer

Return type

Union[BusinessDayConvention, str]

payer_currency

Payer currency

Return type

Union[Currency, str]

payer_day_count_fraction

The day count fraction for the payer

Return type

Union[DayCountFraction, str]

payer_designated_maturity

Tenor of the payerRateOption, e.g. 3m, 6m

Return type

str

payer_first_stub

The date of the first stub for payer leg

Return type

Union[date, str]

payer_frequency

The frequency of payer payments, e.g. 6m

Return type

str

payer_holidays

The accrual calendar for payer leg

Return type

str

payer_last_stub

The date of the last stub for payer leg

Return type

Union[date, str]

payer_rate_option

The underlying benchmark for the payer, e.g. USD-LIBOR-BBA, EUR-EURIBOR-TELERATE

Return type

str

payer_spread

Spread over the payer rate

Return type

Union[float, str]

principal_exchange

The date on which the swap becomes effective

Return type

Union[PrincipalExchange, str]

provider
receiver_business_day_convention

The business day convention for the receiver

Return type

Union[BusinessDayConvention, str]

receiver_currency

Receiver currency

Return type

Union[Currency, str]

receiver_day_count_fraction

The day count fraction for the receiver

Return type

Union[DayCountFraction, str]

receiver_designated_maturity

Tenor of the receiverRateOption, e.g. 3m, 6m

Return type

str

receiver_first_stub

The date of the first stub for receiver leg

Return type

Union[date, str]

receiver_frequency

The frequency of receiver payments, e.g. 6m

Return type

str

receiver_holidays

The accrual calendar for receiver leg

Return type

str

receiver_last_stub

The date of the last stub for receiver leg

Return type

Union[date, str]

receiver_rate_option

The underlying benchmark for the receiver, e.g. USD-LIBOR-BBA, EUR-EURIBOR- TELERATE

Return type

str

receiver_spread

Spread over the receiver rate

Return type

Union[float, str]

resolution_key
Return type

RiskKey

termination_date

The termination of the swap, e.g. 2050-04-01, 10y

Return type

Union[date, str]

unresolved
================================================ FILE: docs/_build/html/classes/gs_quant.instrument.IRXccySwapFixFix.html ================================================ IRXccySwapFixFix — gs_quant 0.1 documentation

IRXccySwapFixFix

For methods of this class, see gs_quant.base.Priceable

class IRXccySwapFixFix(termination_date=None, notional_amount=None, receiver_notional_amount=None, effective_date=None, principal_exchange=None, payer_currency=None, payer_rate=None, payer_frequency=None, payer_day_count_fraction=None, payer_business_day_convention=None, receiver_currency=None, receiver_rate=None, receiver_frequency=None, receiver_day_count_fraction=None, receiver_business_day_convention=None, fee=0, fee_currency=None, fee_payment_date=None, name=None)[source]

An exchange of fixed cashflows in different currencies

Properties

effective_date

The date on which the swap becomes effective

Return type

Union[date, str]

fee

The fee

Return type

float

fee_currency

Currency of the fee

Return type

Union[Currency, str]

fee_payment_date

Payment date of the fee

Return type

Union[date, str]

instrument_quantity
Return type

float

notional_amount

Notional amount

Return type

float

payer_business_day_convention

The business day convention for the payer

Return type

Union[BusinessDayConvention, str]

payer_currency

Payer currency

Return type

Union[Currency, str]

payer_day_count_fraction

The day count fraction for the payer

Return type

Union[DayCountFraction, str]

payer_frequency

The frequency of payer payments, e.g. 6m

Return type

str

payer_rate

Payer rate

Return type

Union[float, str]

principal_exchange

The date on which the swap becomes effective

Return type

Union[PrincipalExchange, str]

provider
receiver_business_day_convention

The business day convention for the receiver

Return type

Union[BusinessDayConvention, str]

receiver_currency

Receiver currency

Return type

Union[Currency, str]

receiver_day_count_fraction

The day count fraction for the receiver

Return type

Union[DayCountFraction, str]

receiver_frequency

The frequency of receiver payments, e.g. 6m

Return type

str

receiver_notional_amount

Receiver notional amount

Return type

float

receiver_rate

Receiver rate

Return type

Union[float, str]

resolution_key
Return type

RiskKey

termination_date

The termination of the swap, e.g. 2050-04-01, 10y

Return type

Union[date, str]

unresolved
================================================ FILE: docs/_build/html/classes/gs_quant.instrument.IRXccySwapFixFlt.html ================================================ IRXccySwapFixFlt — gs_quant 0.1 documentation

IRXccySwapFixFlt

For methods of this class, see gs_quant.base.Priceable

class IRXccySwapFixFlt(pay_or_receive=None, termination_date=None, notional_amount=None, effective_date=None, principal_exchange=None, floating_rate_currency=None, floating_rate_for_the_initial_calculation_period=None, floating_rate_option=None, floating_rate_designated_maturity=None, floating_rate_spread=None, floating_rate_frequency=None, floating_rate_day_count_fraction=None, floating_rate_business_day_convention=None, fixed_rate_currency=None, fixed_rate=None, fixed_rate_frequency=None, fixed_rate_day_count_fraction=None, fixed_rate_business_day_convention=None, fee=0, fee_currency=None, fee_payment_date=None, fixed_first_stub=None, floating_first_stub=None, fixed_last_stub=None, floating_last_stub=None, fixed_holidays=None, floating_holidays=None, name=None)[source]

An exchange of fixed vs floating cashflows in different currencies

Properties

effective_date

The date on which the swap becomes effective

Return type

Union[date, str]

fee

The fee

Return type

float

fee_currency

Currency of the fee

Return type

Union[Currency, str]

fee_payment_date

Payment date of the fee

Return type

Union[date, str]

fixed_first_stub

The date of the first stub for fixed leg

Return type

Union[date, str]

fixed_holidays

The accrual calendar for fixed leg

Return type

str

fixed_last_stub

The date of the last stub for fixed leg

Return type

Union[date, str]

fixed_rate

The coupon of the fixed leg

Return type

Union[float, str]

fixed_rate_business_day_convention

The business day convention for the fixed rate

Return type

Union[BusinessDayConvention, str]

fixed_rate_currency

Fixed rate currency

Return type

Union[Currency, str]

fixed_rate_day_count_fraction

The day count fraction for the fixed rate

Return type

Union[DayCountFraction, str]

fixed_rate_frequency

The frequency of fixed payments, e.g. 6m

Return type

str

floating_first_stub

The date of the first stub for floating leg

Return type

Union[date, str]

floating_holidays

The accrual calendar for floating leg

Return type

str

floating_last_stub

The date of the last stub for floating leg

Return type

Union[date, str]

floating_rate_business_day_convention

The business day convention of the floating rate

Return type

Union[BusinessDayConvention, str]

floating_rate_currency

Floating rate currency

Return type

Union[Currency, str]

floating_rate_day_count_fraction

The day count fraction of the floating rate

Return type

Union[DayCountFraction, str]

floating_rate_designated_maturity

Tenor of the floatingRateOption, e.g. 3m, 6m

Return type

str

floating_rate_for_the_initial_calculation_period

First fixing

Return type

float

floating_rate_frequency

The frequency of floating payments, e.g. 3m

Return type

str

floating_rate_option

The underlying benchmark for the floating rate, e.g. USD-LIBOR-BBA, EUR-EURIBOR- TELERATE

Return type

str

floating_rate_spread

The spread over the floating rate

Return type

Union[float, str]

instrument_quantity
Return type

float

notional_amount

Notional amount

Return type

Union[float, str]

pay_or_receive

Pay or receive fixed

Return type

Union[PayReceive, str]

principal_exchange

The date on which the swap becomes effective

Return type

Union[PrincipalExchange, str]

provider
resolution_key
Return type

RiskKey

termination_date

The termination of the swap, e.g. 2050-04-01, 10y

Return type

Union[date, str]

unresolved
================================================ FILE: docs/_build/html/classes/gs_quant.instrument.InflationSwap.html ================================================ InflationSwap — gs_quant 0.1 documentation

InflationSwap

For methods of this class, see gs_quant.base.Priceable

class InflationSwap(pay_or_receive=None, termination_date=None, notional_currency=None, effective_date=None, notional_amount=None, index=None, floating_rate_business_day_convention=None, fixed_rate=None, fixed_rate_business_day_convention=None, fee=0, base_cpi=None, clearing_house=None, name=None)[source]

A vanilla inflation swap of fixed vs floating cashflows adjusted to an inflation rate

Properties

base_cpi

Base CPI level

Return type

float

clearing_house

Swap Clearing House

Return type

Union[SwapClearingHouse, str]

effective_date

The date on which the swap becomes effective

Return type

Union[date, str]

fee

The fee

Return type

float

fixed_rate

The coupon of the fixed leg

Return type

Union[float, str]

fixed_rate_business_day_convention

The business day convention for the fixed rate

Return type

Union[BusinessDayConvention, str]

floating_rate_business_day_convention

The business day convention of the floating rate

Return type

Union[BusinessDayConvention, str]

index

The underlying benchmark for the floating rate, e.g. CPI-U

Return type

str

instrument_quantity
Return type

float

notional_amount

Notional amount

Return type

Union[float, str]

notional_currency

Notional currency

Return type

Union[Currency, str]

pay_or_receive

Pay or receive fixed

Return type

Union[PayReceive, str]

provider
resolution_key
Return type

RiskKey

termination_date

The termination of the swap, e.g. 2050-04-01, 10y

Return type

Union[date, str]

unresolved
================================================ FILE: docs/_build/html/classes/gs_quant.instrument.Security.html ================================================ Security — gs_quant 0.1 documentation

Security

For methods of this class, see gs_quant.base.Priceable

class Security(ticker=None, bbid=None, ric=None, isin=None, cusip=None, prime_id=None, quantity=1)[source]

A security, specified by a well-known identifier

Properties

bbid

Bloomberg Id Identifier

Return type

str

bbid_equivalent

Bloomberg Equivalent Identifier

Return type

str

bcid

Bloomberg Composite Identifier

Return type

str

cid

Company Id Identifier

Return type

str

cm_id

Client Master Party Id

Return type

str

cross

Cross identifier

Return type

str

cusip

Cusip Identifier

Return type

str

delisted

Whether an asset has been delisted

Return type

str

dollar_cross

USD cross identifier for a particular currency

Return type

str

eid

EID Identifier

Return type

str

em_id

Entity Master Identifier

Return type

str

exchange_code

EEX Exchange Code

Return type

str

gsid

GSID Identifier

Return type

str

gsid_equivalent

GSID Equivalent Identifier

Return type

str

gsideid

GSID_EID Identifier

Return type

str

gsn

Goldman Sachs internal product number

Return type

str

gss

GS Symbol identifier

Return type

str

instrument_quantity
Return type

float

isin

International Security Number

Return type

str

jsn

Japan Security Number

Return type

str

lms_id

Listed Market Symbol

Return type

str

mdapi

MDAPI Asset

Return type

str

mdapi_class

MDAPI Asset Class

Return type

str

mic

Market Identifier Code

Return type

str

mq_symbol

Marquee Symbol for generic MQ entities

Return type

str

pl_id

Platts Symbol Name

Return type

str

plot_id

Plot Identifier

Return type

str

pnode_id

Pricing node identifier sourced from Morningstar

Return type

str

primary_country_ric

Reuters Primary Country Instrument Code Identifier

Return type

str

prime_id

PrimeID Identifier

Return type

str

provider
ps_id

Platts Symbol

Return type

str

rcic

Reuters Composite Instrument Code Identifier

Return type

str

resolution_key
Return type

RiskKey

ric

Reuters Instrument Code identifier

Return type

str

sec_name

Internal Goldman Sachs security name

Return type

str

sedol

Sedol Identifier

Return type

str

sf_id

SalesForce ID

Return type

str

simon_id

SIMON product identifier

Return type

str

tdapi

TDAPI Description

Return type

str

ticker

Ticker Identifier

Return type

str

unresolved
valoren

Valoren Identifier

Return type

str

wi_id

Weather Index Identifier

Return type

str

wpk

Wertpapier Kenn-Nummer

Return type

str

================================================ FILE: docs/_build/html/classes/gs_quant.markets.HistoricalPricingContext.html ================================================ HistoricalPricingContext — gs_quant 0.1 documentation

HistoricalPricingContext

class HistoricalPricingContext(start=None, end=None, calendars=(), dates=None, is_async=False, is_batch=False, use_cache=False, visible_to_gs=False, csa_term=None, market_data_location=None, timeout=None, show_progress=False)[source]

A context for producing valuations over multiple dates

__init__(start=None, end=None, calendars=(), dates=None, is_async=False, is_batch=False, use_cache=False, visible_to_gs=False, csa_term=None, market_data_location=None, timeout=None, show_progress=False)[source]

A context for producing valuations over multiple dates

Parameters
  • start (Union[int, date, None]) – start date

  • end (Union[int, date, None]) – end date (defaults to today)

  • calendars (Union[str, Tuple]) – holiday calendars

  • dates (Optional[Iterable[date]]) – a custom iterable of dates

  • is_async (bool) – return immediately (True) or wait for results (False) (defaults to False)

  • is_batch (bool) – use for calculations expected to run longer than 3 mins, to avoid timeouts. It can be used with is_async=True|False (defaults to False)

  • use_cache (bool) – store results in the pricing cache (defaults to False)

  • visible_to_gs (bool) – are the contents of risk requests visible to GS (defaults to False)

  • csa_term (Optional[str]) – the csa under which the calculations are made. Default is local ccy ois index

  • market_data_location (Optional[str]) – the location for sourcing market data (‘NYC’, ‘LDN’ or ‘HKG’ (defaults to LDN)

Examples

>>> from gs_quant.instrument import IRSwap
>>>
>>> ir_swap = IRSwap('Pay', '10y', 'DKK')
>>> with HistoricalPricingContext(10):
>>>     price_f = ir_swap.price()
>>>
>>> price_series = price_f.result()

Methods

__init__([start, end, calendars, dates, …])

A context for producing valuations over multiple dates

calc(instrument, risk_measure)

Calculate the risk measure for the instrument.

clone(**kwargs)

default_value()

rtype

object

Attributes

active_context

csa_term

rtype

str

is_async

rtype

bool

is_batch

rtype

bool

is_current

rtype

bool

is_entered

rtype

bool

market

rtype

Market

market_data_location

rtype

PricingLocation

pricing_date

Pricing date

prior_context

use_cache

Cache results

visible_to_gs

Request contents visible to GS

================================================ FILE: docs/_build/html/classes/gs_quant.markets.PricingContext.html ================================================ PricingContext — gs_quant 0.1 documentation

PricingContext

class PricingContext(pricing_date=None, market_data_location=None, is_async=False, is_batch=False, use_cache=False, visible_to_gs=None, csa_term=None, timeout=None, market=None, show_progress=False)[source]

A context for controlling pricing and market data behaviour

__init__(pricing_date=None, market_data_location=None, is_async=False, is_batch=False, use_cache=False, visible_to_gs=None, csa_term=None, timeout=None, market=None, show_progress=False)[source]

The methods on this class should not be called directly. Instead, use the methods on the instruments, as per the examples

Parameters
  • pricing_date (Optional[date]) – the date for pricing calculations. Default is today

  • market_data_location (Union[PricingLocation, str, None]) – the location for sourcing market data (‘NYC’, ‘LDN’ or ‘HKG’ (defaults to LDN)

  • is_async (bool) – if True, return (a future) immediately. If False, block (defaults to False)

  • is_batch (bool) – use for calculations expected to run longer than 3 mins, to avoid timeouts. It can be used with is_async=True|False (defaults to False)

  • use_cache (bool) – store results in the pricing cache (defaults to False)

  • visible_to_gs (Optional[bool]) – are the contents of risk requests visible to GS (defaults to False)

  • csa_term (Optional[str]) – the csa under which the calculations are made. Default is local ccy ois index

Examples

To change the market data location of the default context:

>>> from gs_quant.markets import PricingContext
>>> import datetime as dt
>>>
>>> PricingContext.current = PricingContext(market_data_location='LDN')

For a blocking, synchronous request:

>>> from gs_quant.instrument import IRCap
>>> cap = IRCap('5y', 'GBP')
>>>
>>> with PricingContext():
>>>     price_f = cap.dollar_price()
>>>
>>> price = price_f.result()

For an asynchronous request:

>>> with PricingContext(is_async=True):
>>>     price_f = cap.dollar_price()
>>>
>>> while not price_f.done:
>>>     ...

Methods

__init__([pricing_date, …])

The methods on this class should not be called directly.

calc(instrument, risk_measure)

Calculate the risk measure for the instrument.

clone(**kwargs)

default_value()

rtype

object

Attributes

active_context

csa_term

rtype

str

is_async

rtype

bool

is_batch

rtype

bool

is_current

rtype

bool

is_entered

rtype

bool

market

rtype

Market

market_data_location

rtype

PricingLocation

pricing_date

Pricing date

prior_context

use_cache

Cache results

visible_to_gs

Request contents visible to GS

================================================ FILE: docs/_build/html/classes/gs_quant.markets.portfolio.Portfolio.html ================================================ Portfolio — gs_quant 0.1 documentation

Portfolio

class Portfolio(priceables=(), name=None)[source]

A collection of instruments

Portfolio holds a collection of instruments in order to run pricing and risk scenarios

__init__(priceables=(), name=None)[source]

Creates a portfolio object which can be used to hold instruments

Parameters

priceables (Union[PriceableImpl, Iterable[PriceableImpl], dict, None]) – constructed with an instrument, portfolio, iterable of either, or a dictionary where key is name and value is a priceable

Methods

__init__([priceables, name])

Creates a portfolio object which can be used to hold instruments

append(priceables)

as_dict([as_camel_case])

Dictionary of the public, non-null properties and values

calc(risk_measure[, fn])

Calculate the value of the risk_measure

clone(**kwargs)

Clone this object, overriding specified values

default_instance()

Construct a default instance of this type

dollar_price()

Present value in USD

from_asset_id(asset_id[, date])

from_asset_name(name)

from_book(book[, book_type])

from_csv(csv_file[, mappings])

from_dict(values)

Construct an instance of this type from a dictionary

from_eti(eti)

from_frame(data[, mappings])

from_instance(instance)

Copy the values from an existing instance of the same type to our self :param instance: from which to copy: :return:

from_portfolio_id(portfolio_id[, date])

from_portfolio_name(name)

from_quote(quote_id)

market()

Market Data map of coordinates and values.

paths(key)

rtype

Tuple[PortfolioPath, …]

pop(item)

rtype

PriceableImpl

price()

Present value in local currency.

prop_item_type(prop)

rtype

type

prop_type(prop[, additional])

rtype

type

properties()

The public property names of this class

resolve([in_place])

Resolve non-supplied properties of an instrument

save([overwrite])

save_as_quote([overwrite])

subset(paths[, name])

to_csv(csv_file[, mappings, ignored_cols])

to_frame([mappings])

rtype

DataFrame

Attributes

all_instruments

rtype

Tuple[Instrument, …]

all_paths

rtype

Tuple[PortfolioPath, …]

all_portfolios

rtype

Tuple[PriceableImpl, …]

id

rtype

str

instruments

rtype

Tuple[Instrument, …]

name

rtype

str

portfolios

rtype

Tuple[PriceableImpl, …]

priceables

rtype

Tuple[PriceableImpl, …]

================================================ FILE: docs/_build/html/classes/gs_quant.markets.securities.Asset.html ================================================ gs_quant.markets.securities.Asset — gs_quant 0.1 documentation

gs_quant.markets.securities.Asset

class Asset(id_, asset_class, name, exchange=None, currency=None, parameters=None, entity=None)[source]
__init__(id_, asset_class, name, exchange=None, currency=None, parameters=None, entity=None)[source]

Initialize self. See help(type(self)) for accurate signature.

Methods

__init__(id_, asset_class, name[, exchange, …])

Initialize self.

entity_type()

rtype

EntityType

get(id_value, id_type[, as_of, …])

rtype

Optional[Asset]

get_close_prices([start, end])

Get close price series

get_data_coordinate(measure[, dimensions, …])

rtype

DataCoordinate

get_data_series(measure[, dimensions, …])

Get asset series

get_entity()

rtype

Optional[Dict]

get_identifier(id_type[, as_of])

Get asset identifier

get_identifiers([as_of])

Get asset identifiers

get_marquee_id()

get_type()

Overridden by sub-classes to return security type

get_unique_entity_key()

rtype

EntityKey

Attributes

data_dimension

rtype

str

================================================ FILE: docs/_build/html/classes/gs_quant.markets.securities.AssetClass.html ================================================ gs_quant.markets.securities.AssetClass — gs_quant 0.1 documentation

gs_quant.markets.securities.AssetClass

class AssetClass(value)[source]

Asset classification of security. Assets are classified into broad groups which exhibit similar characteristics and behave in a consistent way under different market conditions

__init__()

Initialize self. See help(type(self)) for accurate signature.

Attributes

Cash

Commod

Credit

Cross_Asset

Cryptocurrency

Econ

Equity

FX

Fund

Loan

Mortgage

Rates

Social

================================================ FILE: docs/_build/html/classes/gs_quant.markets.securities.AssetIdentifier.html ================================================ gs_quant.markets.securities.AssetIdentifier — gs_quant 0.1 documentation

gs_quant.markets.securities.AssetIdentifier

class AssetIdentifier(value)[source]

Asset type enumeration

Enumeration of different security identifiers

__init__()

Initialize self. See help(type(self)) for accurate signature.

Attributes

BLOOMBERG_COMPOSITE_ID

Bloomberg composite identifier and exchange code (GS US)

BLOOMBERG_ID

Bloomberg identifier and exchange code (GS UN)

CUSIP

Committee on Uniform Security Identification Procedures code (38141G104)

ISIN

International Securities Identification Number (US38141G1040)

MARQUEE_ID

Goldman Sachs Marquee identifier code (MA4B66MW5E27UAHKG34)

REUTERS_ID

Thompson Reuters Instrument Code (RIC), (GS.N)

SEDOL

LSE Stock Exchange Daily Official List code (2407966)

TICKER

Exchange ticker (GS)

================================================ FILE: docs/_build/html/classes/gs_quant.markets.securities.AssetType.html ================================================ gs_quant.markets.securities.AssetType — gs_quant 0.1 documentation

gs_quant.markets.securities.AssetType

class AssetType(value)[source]

Asset type enumeration

Enumeration of different types of asset or security.

__init__()

Initialize self. See help(type(self)) for accurate signature.

Attributes

BASKET

Bespoke basket which provides exposure to a customized collection of assets with levels published daily; can be traded on swap and rebalanced programmatically

BOND

Bond

CASH

Cash

COMMODITY

Commodity

COMMODITY_EU_NATURAL_GAS_HUB

COMMODITY_NATURAL_GAS_HUB

COMMODITY_POWER_AGGREGATED_NODES

Commodity Power Aggregated Nodes

COMMODITY_POWER_NODE

Commodity Power Node

COMMODITY_REFERENCE_PRICE

Commodity Reference Price

CROSS

FX cross or currency pair

CRYPTO_CURRENCY

Crypto

CURRENCY

Currency

ETF

Exchange traded fund which tracks an evolving portfolio of securities and is listed on an exchange to be traded as a security

FUTURE

Standardized listed contract which provides delivery of an asset at a pre-defined forward date and can be settled in cash or physical form

FUTURE_CONTRACT

Future Contract

FUTURE_MARKET

Future Market

INDEX

Index which tracks an evolving portfolio of securities, and can be traded through cash or derivatives markets

OPTION

Option

RATE

Rate

STOCK

Listed equities which provide access to equity holding in a company and participation in dividends and other distributions in common, preferred or other variants which provide different investor rights

SWAP

Swap

WEATHER_INDEX

Weather Index

================================================ FILE: docs/_build/html/classes/gs_quant.markets.securities.Index.html ================================================ gs_quant.markets.securities.Index — gs_quant 0.1 documentation

gs_quant.markets.securities.Index

class Index(id_, asset_class, name, exchange=None, currency=None, entity=None)[source]

Index Asset

Index which tracks an evolving portfolio of securities, and can be traded through cash or derivatives markets

__init__(id_, asset_class, name, exchange=None, currency=None, entity=None)[source]

Initialize self. See help(type(self)) for accurate signature.

Methods

__init__(id_, asset_class, name[, exchange, …])

Initialize self.

entity_type()

rtype

EntityType

get(id_value, id_type[, as_of, …])

rtype

Optional[Asset]

get_close_prices([start, end])

Get close price series

get_constituents([as_of, position_type])

Get asset constituents

get_currency()

rtype

Optional[Currency]

get_data_coordinate(measure[, dimensions, …])

rtype

DataCoordinate

get_data_series(measure[, dimensions, …])

Get asset series

get_entity()

rtype

Optional[Dict]

get_identifier(id_type[, as_of])

Get asset identifier

get_identifiers([as_of])

Get asset identifiers

get_marquee_id()

get_return_type()

rtype

ReturnType

get_type()

Overridden by sub-classes to return security type

get_unique_entity_key()

rtype

EntityKey

Attributes

data_dimension

rtype

str

================================================ FILE: docs/_build/html/classes/gs_quant.markets.securities.SecurityMaster.html ================================================ gs_quant.markets.securities.SecurityMaster — gs_quant 0.1 documentation

gs_quant.markets.securities.SecurityMaster

class SecurityMaster[source]

Security Master

The SecurityMaster class provides an interface to security lookup functions. This allows querying and retrieval of different security types (assets) based on a variety of different identifiers through point-in-time lookups.

Uses the current PricingContext to provide as of dates if optional arguments are not provided. Will return the relevant asset subclass depending on the type of the security

See also

Asset

__init__()

Initialize self. See help(type(self)) for accurate signature.

Methods

__init__()

Initialize self.

get_asset(id_value, id_type[, as_of, …])

Get an asset by identifier and identifier type

================================================ FILE: docs/_build/html/classes/gs_quant.markets.securities.Stock.html ================================================ gs_quant.markets.securities.Stock — gs_quant 0.1 documentation

gs_quant.markets.securities.Stock

class Stock(id_, name, exchange=None, currency=None, entity=None)[source]

Base Security Type

Represents a financial asset which can be held in a portfolio, or has an observable price fixing which can be referenced in a derivative transaction

__init__(id_, name, exchange=None, currency=None, entity=None)[source]

Initialize self. See help(type(self)) for accurate signature.

Methods

__init__(id_, name[, exchange, currency, entity])

Initialize self.

entity_type()

rtype

EntityType

get(id_value, id_type[, as_of, …])

rtype

Optional[Asset]

get_close_prices([start, end])

Get close price series

get_currency()

rtype

Optional[Currency]

get_data_coordinate(measure[, dimensions, …])

rtype

DataCoordinate

get_data_series(measure[, dimensions, …])

Get asset series

get_entity()

rtype

Optional[Dict]

get_identifier(id_type[, as_of])

Get asset identifier

get_identifiers([as_of])

Get asset identifiers

get_marquee_id()

get_type()

Overridden by sub-classes to return security type

get_unique_entity_key()

rtype

EntityKey

Attributes

data_dimension

rtype

str

================================================ FILE: docs/_build/html/classes/gs_quant.models.epidemiology.EpidemicModel.html ================================================ EpidemicModel — gs_quant 0.1 documentation

gs_quant.models.epidemiology.EpidemicModel

class EpidemicModel(model, parameters=None, data=None, initial_conditions=None, fit_method='leastsq', error=None, fit_period=None)[source]

Class to perform solutions and parameter-fitting of epidemic models

__init__(model, parameters=None, data=None, initial_conditions=None, fit_method='leastsq', error=None, fit_period=None)[source]

A class to standardize fitting and solving epidemiological models.

Parameters
  • model (Type[CompartmentalModel]) – the model to use, currently a class in the form of SIR, SEIR above

  • parameters (Optional[tuple]) – tuple, parameters to use for the model, defaults to the output of [model].get_parameters

  • data (Optional[array]) – np.array, data that can be used to calibrate the model

  • initial_conditions (Optional[list]) – list, initial conditions for the model

  • fit_method (str) – str, the method to use to minimize the (given) error. Available methods are those in the lmfit.minimizer.minimize function. Default is Levenberg-Marquardt least squares minimization.

  • error (Optional[callable]) – callable, control which residuals (and in what form) to minimize for fitting.

  • fit_period (Optional[float]) – float, how far back to fit the data, defaults to fitting all data

Methods

__init__(model[, parameters, data, …])

A class to standardize fitting and solving epidemiological models.

fit([time_range, parameters, …])

Fit the model based on data in the form np.array([X1,…,Xn])

residual(parameters, time_range, data)

Obtain fit error (to minimize).

solve(time_range, initial_conditions, parameters)

Integrate the model ODEs to get a solution.

================================================ FILE: docs/_build/html/classes/gs_quant.models.epidemiology.SEIR.html ================================================ gs_quant.models.epidemiology.SEIR — gs_quant 0.1 documentation

gs_quant.models.epidemiology.SEIR

class SEIR[source]

SEIR Model

__init__()

Initialize self. See help(type(self)) for accurate signature.

Methods

__init__()

Initialize self.

calibrate(xs, t, parameters)

SEIR model derivatives at t.

get_parameters(S0, E0, I0, R0, N[, beta, …])

Produce a set of parameters for the SIR model.

================================================ FILE: docs/_build/html/classes/gs_quant.models.epidemiology.SIR.html ================================================ gs_quant.models.epidemiology.SIR — gs_quant 0.1 documentation

gs_quant.models.epidemiology.SIR

class SIR[source]

SIR Model

__init__()

Initialize self. See help(type(self)) for accurate signature.

Methods

__init__()

Initialize self.

calibrate(xs, t, parameters)

SIR model derivatives at t.

get_parameters(S0, I0, R0, N[, beta, gamma, …])

Produce a set of parameters for the SIR model.

================================================ FILE: docs/_build/html/classes/gs_quant.timeseries.datetime.Window.html ================================================ gs_quant.timeseries.datetime.Window — gs_quant 0.1 documentation

gs_quant.timeseries.datetime.Window

class Window(w=None, r=None)[source]

Create a Window with size and ramp up to use.

Parameters
  • w (Union[int, str, None]) – window size

  • r (Union[int, str, None]) – ramp up value. Defaults to the window size.

Returns

new window object

Usage

The window size and ramp up value can either the number of observations or a string representation of the time period.

Examples

Window size is \(22\) obversations and the ramp up value is \(10\):

>>> Window(22, 10)

Window size is one month and the ramp up size is one week:

>>> Window('1m', '1w')

Methods

as_dict()

from_dict(obj)

================================================ FILE: docs/_build/html/classes/gs_quant.timeseries.statistics.LinearRegression.html ================================================ gs_quant.timeseries.statistics.LinearRegression — gs_quant 0.1 documentation

gs_quant.timeseries.statistics.LinearRegression

class LinearRegression(X, y, fit_intercept=True)[source]

Fit an Ordinary least squares (OLS) linear regression model.

Parameters
  • X (Union[Series, List[Series]]) – observations of the explanatory variable(s)

  • y (Series) – observations of the dependant variable

  • fit_intercept (bool) – whether to calculate intercept in the model

Usage

Fit OLS Model based on observations of the explanatory variables(s) X and the dependant variable y. If X and y are not aligned, only use the intersection of dates/times

Examples

R Squared of an OLS model:

>>> x = generate_series(100)
>>> y = generate_series(100)
>>> r = LinearRegression(x, y)
>>> r.r_squared()

Methods

coefficient(i)

Estimated coefficient

fitted_values()

Fitted values

predict(X_predict)

Use the model for prediction

r_squared()

Coefficient of determination (R Squared)

standard_deviation_of_errors()

Standard deviation of the error term

================================================ FILE: docs/_build/html/classes/gs_quant.timeseries.statistics.RollingLinearRegression.html ================================================ gs_quant.timeseries.statistics.RollingLinearRegression — gs_quant 0.1 documentation

gs_quant.timeseries.statistics.RollingLinearRegression

class RollingLinearRegression(X, y, w, fit_intercept=True)[source]

Fit a rolling ordinary least squares (OLS) linear regression model.

Parameters
  • X (Union[Series, List[Series]]) – observations of the explanatory variable(s)

  • y (Series) – observations of the dependant variable

  • w (int) – number of observations in each rolling window. Must be larger than the number of observations or explanatory variables

  • fit_intercept (bool) – whether to calculate intercept in the model

Usage

Fit OLS Model based on observations of the explanatory variables(s) X and the dependant variable y across a rolling window with fixed number of observations. The parameters of each rolling window are stored at the end of each window. If X and y are not aligned, only use the intersection of dates/times

Examples

R Squared of a rolling OLS model:

>>> x = generate_series(100)
>>> y = generate_series(100)
>>> r = RollingLinearRegression(x, y, 5)
>>> r.r_squared()

Methods

coefficient(i)

Estimated coefficients

fitted_values()

Fitted values at the end of each rolling window

r_squared()

Coefficients of determination (R Squared) of rolling regressions

standard_deviation_of_errors()

Standard deviations of the error terms

================================================ FILE: docs/_build/html/classes/gs_quant.timeseries.statistics.SEIRModel.html ================================================ gs_quant.timeseries.statistics.SEIRModel — gs_quant 0.1 documentation

gs_quant.timeseries.statistics.SEIRModel

class SEIRModel(beta=None, gamma=None, sigma=None, s=None, e=None, i=None, r=None, n=None, fit=True, fit_period=None)[source]

SEIR Compartmental model for transmission of infectious disease

Parameters
  • beta (Optional[float]) – transmission rate of the infection

  • gamma (Optional[float]) – recovery rate of the infection

  • sigma (Optional[float]) – immunity rate from exposed to infected

  • s (Union[Series, float, None]) – number of susceptible individuals in population

  • e (Union[Series, float, None]) – number of exposed individuals in population

  • i (Union[Series, float, None]) – number of infectious individuals in population

  • r (Union[Series, float, None]) – number of recovered individuals in population

  • n (Union[Series, float, None]) – total population size

  • end_date – end date for the evolution of the model

  • fit (bool) – whether to fit the model to the data

  • fit_period (Optional[int]) – on how many days back to fit the model

Usage

Fit SEIR Model based on the population in each compartment over a given time period.

The SEIR models the movement of individuals between four compartments: susceptible (S), exposed (E), infected (I), and resistant (R). The model calibrates parameters :

Parameter

Description

S0

initial susceptible individuals

E0

initial exposed individuals

I0

initial infected individuals

R0

initial recovered individuals

beta

Transmission rate from susceptible to exposed

gamma

Immunity rate from infected to resistant

sigma

Immunity rate from exposed to infected

The parameters beta, gamma, and sigma, model how fast people move from being susceptible to exposed (beta), from exposed to infected (sigma), and subsequently from infected to resistant (gamma). This model can be used to predict the populations of each compartment once calibrated.

Methods

beta()

Model calibration for transmission rate (susceptible to exposed)

e0()

Model calibration for initial exposed individuals

gamma()

Model calibration for immunity (infected to resistant)

i0()

Model calibration for initial infectious individuals

predict_e()

Model calibration for exposed individuals through time

predict_i()

Model calibration for infected individuals through time

predict_r()

Model calibration for recovered individuals through time

predict_s()

Model calibration for susceptible individuals through time

r0()

Model calibration for initial recovered individuals

s0()

Model calibration for initial susceptible individuals

sigma()

Model calibration for infection rate (exposed to infected)

================================================ FILE: docs/_build/html/classes/gs_quant.timeseries.statistics.SIRModel.html ================================================ gs_quant.timeseries.statistics.SIRModel — gs_quant 0.1 documentation

gs_quant.timeseries.statistics.SIRModel

class SIRModel(beta=None, gamma=None, s=None, i=None, r=None, n=None, fit=True, fit_period=None)[source]

SIR Compartmental model for transmission of infectious disease

Parameters
  • beta (Optional[float]) – transmission rate of the infection

  • gamma (Optional[float]) – recovery rate of the infection

  • s (Union[Series, float, None]) – number of susceptible individuals in population

  • i (Union[Series, float, None]) – number of infectious individuals in population

  • r (Union[Series, float, None]) – number of recovered individuals in population

  • n (Union[Series, float, None]) – total population size

  • end_date – end date for the evolution of the model

  • fit (bool) – whether to fit the model to the data

  • fit_period (Optional[int]) – on how many days back to fit the model

Usage

Fit SIR Model based on the population in each compartment over a given time period.

The SIR models the movement of individuals between three compartments: susceptible (S), infected (I), and resistant (R). The model calibrates parameters :

Parameter

Description

S0

initial susceptible individuals

I0

initial infected individuals

R0

initial recovered individuals

beta

Transmission rate from susceptible to infected

gamma

Immunity rate from infected to resistant

The parameters beta and gamma model how fast people move from being susceptible to infected (beta), and subsequently from infected to resistant (gamma). This model can be used to forecast the populations of each compartment once calibrated

Methods

beta()

Model calibration for transmission rate (susceptible to infected)

gamma()

Model calibration for immunity (infected to resistant)

i0()

Model calibration for initial infectious individuals

predict_i()

Model calibration for infected individuals through time

predict_r()

Model calibration for recovered individuals through time

predict_s()

Model calibration for susceptible individuals through time

r0()

Model calibration for initial recovered individuals

s0()

Model calibration for initial susceptible individuals

================================================ FILE: docs/_build/html/data.html ================================================ Data Package — gs_quant 0.1 documentation

Data Package

DataContext([start, end])

Dataset(dataset_id[, provider])

A collection of related data

Fields([value, names, module, type, start])

Data field enumeration

================================================ FILE: docs/_build/html/datetime.html ================================================ Datetime Package — gs_quant 0.1 documentation

Datetime Package

Date

business_day_count(begin_dates, end_dates[, …])

Determine the number of business days between begin_dates and end_dates

business_day_offset(dates, offsets[, roll, …])

Apply offsets to the dates and move to the nearest business date

date_range(begin, end[, calendars, week_mask])

Construct a range of dates

is_business_day(dates[, calendars, week_mask])

Determine whether each date in dates is a business day

prev_business_date([dates, calendars, week_mask])

Returns the previous business date for a given date or date series, defaulting to today.

Point

point_sort_order(point[, ref_date])

Calculates a number that can be used to sort Mkt Points by it.

================================================ FILE: docs/_build/html/functions/gs_quant.datetime.date.business_day_count.html ================================================ gs_quant.datetime.date.business_day_count — gs_quant 0.1 documentation

gs_quant.datetime.date.business_day_count

business_day_count(begin_dates, end_dates, calendars=(), week_mask=None)[source]

Determine the number of business days between begin_dates and end_dates

Parameters
  • begin_dates (Union[date, Iterable[date]]) – A date or collection of beginning dates

  • end_dates (Union[date, Iterable[date]]) – A date or collection of end dates

  • calendars (Union[str, Tuple[str, …]]) – Calendars to use for holidays

  • week_mask (Optional[str]) – Which days are considered weekends (defaults to Saturday and Sunday)

Return type

Union[int, Tuple[int]]

Returns

An int or tuple of ints, representing the number of business days between begin_dates and end_dates

Examples

>>> import datetime as dt
>>> today = dt.date.today()
>>> bus_days = business_day_count(today, today + dt.timedelta(days=7))
================================================ FILE: docs/_build/html/functions/gs_quant.datetime.date.business_day_offset.html ================================================ gs_quant.datetime.date.business_day_offset — gs_quant 0.1 documentation

gs_quant.datetime.date.business_day_offset

business_day_offset(dates, offsets, roll='raise', calendars=(), week_mask=None)[source]

Apply offsets to the dates and move to the nearest business date

Parameters
  • dates (Union[date, Iterable[date]]) – The input date or dates

  • offsets (Union[int, Iterable[int]]) – The number of days by which to adjust the dates

  • roll (str) – Which direction to roll, in order to get to the nearest business date

  • calendars (Union[str, Tuple[str, …]]) – Calendars to use for holidays

  • week_mask (Optional[str]) – Which days are considered weekends (defaults to Saturday and Sunday)

Return type

Union[date, Iterable[date]]

Returns

A date (if dates is a single date) or tuple of dates, adjusted by the offsets

Examples

>>> import datetime as dt
>>> prev_bus_date = business_day_offset(dt.date.today(), -1, roll='forward')
================================================ FILE: docs/_build/html/functions/gs_quant.datetime.date.date_range.html ================================================ gs_quant.datetime.date.date_range — gs_quant 0.1 documentation

gs_quant.datetime.date.date_range

date_range(begin, end, calendars=(), week_mask=None)[source]

Construct a range of dates

Parameters
  • begin (Union[int, date]) – Beginning date or int. An int will be interpreted as the number of business days before end (which must be a date)

  • end (Union[int, date]) – End date or int. An int will be interpreted as the number of business days after begin (which must be a date)

  • calendars (Union[str, Tuple[str, …]]) – Calendars to use for holidays

  • week_mask (Optional[str]) – Which days are considered weekends (defaults to Saturday and Sunday)

Return type

Iterable[date]

Returns

A generator of dates

>>> import datetime as dt
>>> today = dt.date.today()
>>> dates = tuple(date_range(5, today))
>>>
>>> for date in date_range(dt.date(2019, 1, 1), dt.date(2019, 2, 1)):
>>>     print(date)
================================================ FILE: docs/_build/html/functions/gs_quant.datetime.date.is_business_day.html ================================================ gs_quant.datetime.date.is_business_day — gs_quant 0.1 documentation

gs_quant.datetime.date.is_business_day

is_business_day(dates, calendars=(), week_mask=None)[source]

Determine whether each date in dates is a business day

Parameters
  • dates (Union[date, Iterable[date]]) – The input date or dates

  • calendars (Union[str, Tuple[str, …]]) – Calendars to use for holidays

  • week_mask (Optional[str]) – Which days are considered weekends (defaults to Saturday and Sunday)

Return type

Union[bool, Tuple[bool]]

Returns

True/False if dates is a single date. A tuple indicating True/False for each date if dates is an iterable

Examples

>>> import datetime as dt
>>> is_business_day(dt.date.today())
>>> is_business_day(dt.date(2019, 7, 4), calendars=('NYSE',))
================================================ FILE: docs/_build/html/functions/gs_quant.datetime.date.prev_business_date.html ================================================ gs_quant.datetime.date.prev_business_date — gs_quant 0.1 documentation

gs_quant.datetime.date.prev_business_date

prev_business_date(dates=datetime.date(2021, 1, 5), calendars=(), week_mask=None)[source]

Returns the previous business date for a given date or date series, defaulting to today.

Parameters
  • dates (Union[date, Iterable[date]]) – The input date or dates, defaults to today

  • calendars (Union[str, Tuple[str, …]]) – Calendars to use for holidays

  • week_mask (Optional[str]) – Which days are considered weekends (defaults to Saturday and Sunday)

Return type

Union[date, Iterable[date]]

Returns

A date (if dates is a single date) or tuple of dates, adjusted by the offset of one day.

Example

>>> import datetime as dt
>>> prev_bus_date = prev_business_date()
================================================ FILE: docs/_build/html/functions/gs_quant.datetime.point.point_sort_order.html ================================================ gs_quant.datetime.point.point_sort_order — gs_quant 0.1 documentation

gs_quant.datetime.point.point_sort_order

point_sort_order(point, ref_date=datetime.date(2021, 1, 5))[source]

Calculates a number that can be used to sort Mkt Points by it.

Parameters
  • point (str) – The point string from MarketDataCoordinate.

  • ref_date (date) – Reference date, normally the pricing date.

Return type

float

Returns

The number of days from the reference date to the date specified by the point string

Examples

>>> import datetime as dt
>>> days = point_sort_order(point = 'Dec20', ref_date=dt.date.today())
================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.algebra.abs_.html ================================================ gs_quant.timeseries.algebra.abs_ — gs_quant 0.1 documentation

gs_quant.timeseries.algebra.abs_

abs_(x)[source]

Absolute value of each element in series

Parameters

x (Series) – date-based time series of prices

Return type

Series

Returns

date-based time series of absolute value

Usage

Return the absolute value of \(X\). For each value in time series \(X_t\), return \(X_t\) if \(X_t\) is greater than or equal to 0; otherwise return \(-X_t\):

\(R_t = |X_t|\)

Equivalent to \(R_t = \sqrt{X_t^2}\)

Examples

Generate price series and take absolute value of \(X_t-100\)

>>> prices = generate_series(100) - 100
>>> abs_(prices)

See also

exp() sqrt()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.algebra.add.html ================================================ gs_quant.timeseries.algebra.add — gs_quant 0.1 documentation

gs_quant.timeseries.algebra.add

add(x, y, method=<Interpolate.STEP: 'step'>)[source]

Add two series or scalars

Parameters
  • x (Union[Series, Real]) – timeseries or scalar

  • y (Union[Series, Real]) – timeseries or scalar

  • method (Interpolate) – interpolation method (default: step). Only used when both x and y are timeseries

Return type

Union[Series, Real]

Returns

timeseries of x + y or sum of the given real numbers

Usage

Add two series or scalar variables with the given interpolation method

\(R_t = X_t + Y_t\)

Alignment operators:

Method

Behavior

intersect

Resultant series only has values on the intersection of dates. Values for dates present in only one series will be ignored

nan

Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as nan in the other series, and therefore in the resultant series

zero

Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as zero in the other series

step

Resultant series has values on the union of dates in both series. Values for dates only available in one series will be interpolated via step function in the other series

time

Resultant series have values on the union of dates / times. Missing values surrounded by valid values will be interpolated given length of interval. Input series must use DateTimeIndex.

Examples

Add two series:

>>> a = generate_series(100)
>>> b = generate_series(100)
>>> add(a, b, Interpolate.STEP)

See also

subtract()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.algebra.and_.html ================================================ gs_quant.timeseries.algebra.and_ — gs_quant 0.1 documentation
================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.algebra.ceil.html ================================================ gs_quant.timeseries.algebra.ceil — gs_quant 0.1 documentation

gs_quant.timeseries.algebra.ceil

ceil(x, value=0)[source]

Cap series at maximum value

Parameters
  • x (Series) – date-based time series of prices

  • value (float) – maximum value

Return type

Series

Returns

date-based time series of maximum value

Usage

Returns series where all values are less than or equal to the maximum value.

\(R_t = min(X_t, value)\)

See Floor and Ceil functions for more details

Examples

Generate price series and floor all values at 100

>>> prices = generate_series(100)
>>> floor(prices, 100)

See also

floor()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.algebra.divide.html ================================================ gs_quant.timeseries.algebra.divide — gs_quant 0.1 documentation

gs_quant.timeseries.algebra.divide

divide(x, y, method=<Interpolate.STEP: 'step'>)[source]

Divide two series or scalars

Parameters
  • x (Union[Series, Real]) – timeseries or scalar

  • y (Union[Series, Real]) – timeseries or scalar

  • method (Interpolate) – interpolation method (default: step). Only used when both x and y are timeseries

Return type

Union[Series, Real]

Returns

timeseries of x / y or quotient of the given real numbers

Usage

Divide two series or scalar variables applying the given interpolation method

\(R_t = X_t / Y_t\)

Alignment operators:

Method

Behavior

intersect

Resultant series only has values on the intersection of dates. Values for dates present in only one series will be ignored

nan

Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as nan in the other series, and therefore in the resultant series

zero

Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as zero in the other series

step

Resultant series has values on the union of dates in both series. Values for dates only available in one series will be interpolated via step function in the other series

time

Resultant series have values on the union of dates / times. Missing values surrounded by valid values will be interpolated given length of interval. Input series must use DateTimeIndex.

Examples

Divide two series:

>>> a = generate_series(100)
>>> b = generate_series(100)
>>> divide(a, b, Interpolate.STEP)

See also

multiply()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.algebra.exp.html ================================================ gs_quant.timeseries.algebra.exp — gs_quant 0.1 documentation

gs_quant.timeseries.algebra.exp

exp(x)[source]

Exponential of series

Parameters

x (Series) – timeseries

Return type

Series

Returns

exponential of each element

Usage

For each element in the series, \(X_t\), raise \(e\) (Euler’s number) to the power of \(X_t\). Euler’s number is the base of the natural logarithm, \(ln\).

\(R_t = e^{X_t}\)

Examples

Raise \(e\) to the power \(1\). Returns Euler’s number, approximately 2.71828

>>> exp(1)

See also

log()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.algebra.filter_.html ================================================ gs_quant.timeseries.algebra.filter_ — gs_quant 0.1 documentation

gs_quant.timeseries.algebra.filter_

filter_(x, operator=None, value=None)[source]

Removes values where comparison with the operator and value combination results in true, defaults to removing missing values from the series

Parameters
  • x (Series) – timeseries

  • operator (Optional[FilterOperator]) – FilterOperator describing logic for value removal, e.g ‘less_than’

  • value (Optional[Real]) – number indicating value(s) to remove from the series

Return type

Series

Returns

timeseries with specified values removed

Usage

Remove each value determined by operator and value from timeseries where that expression yields true

Examples

Remove 0 from time series

>>> prices = generate_series(100)
>>> filter_(prices, FilterOperator.EQUALS, 0)

Remove positive numbers from time series

>>> prices = generate_series(100)
>>> filter_(prices, FilterOperator.GREATER, 0)

Remove missing values from time series

>>> prices = generate_series(100)
>>> filter_(prices)
================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.algebra.floor.html ================================================ gs_quant.timeseries.algebra.floor — gs_quant 0.1 documentation

gs_quant.timeseries.algebra.floor

floor(x, value=0)[source]

Floor series at minimum value

Parameters
  • x (Series) – date-based time series of prices

  • value (float) – minimum value

Return type

Series

Returns

date-based time series of maximum value

Usage

Returns series where all values are greater than or equal to the minimum value.

\(R_t = max(X_t, value)\)

See Floor and Ceil functions for more details

Examples

Generate price series and floor all values at 100

>>> prices = generate_series(100)
>>> floor(prices, 100)

See also

ceil()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.algebra.floordiv.html ================================================ gs_quant.timeseries.algebra.floordiv — gs_quant 0.1 documentation

gs_quant.timeseries.algebra.floordiv

floordiv(x, y, method=<Interpolate.STEP: 'step'>)[source]

Floor divide two series or scalars

Parameters
  • x (Union[Series, Real]) – timeseries or scalar

  • y (Union[Series, Real]) – timeseries or scalar

  • method (Interpolate) – interpolation method (default: step). Only used for operating two series

Return type

Union[Series, Real]

Returns

timeseries of x // y or quotient of the floor division of the given real numbers

Usage

Divide two series or scalar variables applying the given interpolation method

\(R_t = X_t / Y_t\)

Alignment operators:

Method

Behavior

intersect

Resultant series only has values on the intersection of dates. Values for dates present in only one series will be ignored

nan

Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as nan in the other series, and therefore in the resultant series

zero

Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as zero in the other series

step

Resultant series has values on the union of dates in both series. Values for dates only available in one series will be interpolated via step function in the other series

Examples

Floor divide two series:

>>> a = generate_series(100)
>>> b = generate_series(100)
>>> floordiv(a, b, Interpolate.STEP)

See also

divide()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.algebra.if_.html ================================================ gs_quant.timeseries.algebra.if_ — gs_quant 0.1 documentation
================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.algebra.log.html ================================================ gs_quant.timeseries.algebra.log — gs_quant 0.1 documentation

gs_quant.timeseries.algebra.log

log(x)[source]

Natural logarithm of series

Parameters

x (Series) – timeseries

Return type

Series

Returns

series with exponential of each element

Usage

For each element in the series, \(X_t\), return the natural logarithm \(ln\) of \(X_t\) The natural logarithm is the logarithm in base \(e\).

\(R_t = log(X_t)\)

This function is the inverse of the exponential function.

More information on logarithms

Examples

Take natural logarithm of 3

>>> log(3)

See also

exp()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.algebra.multiply.html ================================================ gs_quant.timeseries.algebra.multiply — gs_quant 0.1 documentation

gs_quant.timeseries.algebra.multiply

multiply(x, y, method=<Interpolate.STEP: 'step'>)[source]

Multiply two series or scalars

Parameters
  • x (Union[Series, Real]) – timeseries or scalar

  • y (Union[Series, Real]) – timeseries or scalar

  • method (Interpolate) – interpolation method (default: step). Only used when both x and y are timeseries

Return type

Union[Series, Real]

Returns

timeseries of x * y or product of the given real numbers

Usage

Multiply two series or scalar variables applying the given interpolation method

\(R_t = X_t \times Y_t\)

Alignment operators:

Method

Behavior

intersect

Resultant series only has values on the intersection of dates. Values for dates present in only one series will be ignored

nan

Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as nan in the other series, and therefore in the resultant series

zero

Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as zero in the other series

step

Resultant series has values on the union of dates in both series. Values for dates only available in one series will be interpolated via step function in the other series

time

Resultant series have values on the union of dates / times. Missing values surrounded by valid values will be interpolated given length of interval. Input series must use DateTimeIndex.

Examples

Multiply two series:

>>> a = generate_series(100)
>>> b = generate_series(100)
>>> multiply(a, b, Interpolate.STEP)

See also

divide()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.algebra.not_.html ================================================ gs_quant.timeseries.algebra.not_ — gs_quant 0.1 documentation
================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.algebra.or_.html ================================================ gs_quant.timeseries.algebra.or_ — gs_quant 0.1 documentation
================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.algebra.power.html ================================================ gs_quant.timeseries.algebra.power — gs_quant 0.1 documentation

gs_quant.timeseries.algebra.power

power(x, y=1)[source]

Raise each element in series to power

Parameters
  • x (Series) – timeseries

  • y (float) – value

Return type

Series

Returns

date-based time series of square roots

Usage

Raise each value in time series \(X_t\) to the power \(y\):

\(R_t = X_t^{y}\)

Examples

Generate price series and raise each value to the power 2:

>>> prices = generate_series(100)
>>> power(prices, 2)

See also

sqrt()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.algebra.repeat.html ================================================ gs_quant.timeseries.algebra.repeat — gs_quant 0.1 documentation

gs_quant.timeseries.algebra.repeat

repeat(x, n=1)[source]

Repeats values for days where data is missing. For any date with missing data, the last recorded value is used. Optionally downsamples the result such that there are data points every n days.

Parameters
  • x (Series) – date-based timeseries

  • n (int) – desired frequency of output

Return type

Series

Returns

a timeseries that has been forward-filled, and optionally downsampled

Usage

Fill missing values with last seen value e.g. to combine daily with weekly or monthly data.

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.algebra.smooth_spikes.html ================================================ gs_quant.timeseries.algebra.smooth_spikes — gs_quant 0.1 documentation

gs_quant.timeseries.algebra.smooth_spikes

smooth_spikes(x, threshold)[source]

Smooth out the spikes of a series. If a point is larger/smaller than (1 +/- threshold) times both neighbors, replace it with the average of those neighbours. Note: the first and last points in the input series are dropped.

Parameters
  • x (Series) – timeseries

  • threshold (float) – minimum increment to trigger filter

Return type

Series

Returns

smoothed timeseries

Usage

Returns series where values that exceed the threshold relative to both neighbors are replaced.

Examples

Generate price series and smooth spikes over a threshold of 0.5.

>>> prices = generate_series(100)
>>> smooth_spikes(prices, 0.5)

See also

exponential_moving_average()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.algebra.sqrt.html ================================================ gs_quant.timeseries.algebra.sqrt — gs_quant 0.1 documentation

gs_quant.timeseries.algebra.sqrt

sqrt(x)[source]

Square root of (a) each element in a series or (b) a real number

Parameters

x (Union[Real, Series]) – date-based time series of prices or real number

Return type

Union[Real, Series]

Returns

date-based time series of square roots or square root of given number

Usage

Return the square root of each value in time series \(X_t\):

\(R_t = \sqrt{X_t}\)

Examples

Generate price series and take square root of each value:

>>> prices = generate_series(100)
>>> sqrt(prices)

See also

pow()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.algebra.subtract.html ================================================ gs_quant.timeseries.algebra.subtract — gs_quant 0.1 documentation

gs_quant.timeseries.algebra.subtract

subtract(x, y, method=<Interpolate.STEP: 'step'>)[source]

Add two series or scalars

Parameters
  • x (Union[Series, Real]) – timeseries or scalar

  • y (Union[Series, Real]) – timeseries or scalar

  • method (Interpolate) – index alignment operator (default: intersect). Only used when both x and y are timeseries

Return type

Union[Series, Real]

Returns

timeseries of x - y or difference between the given real numbers

Usage

Subtracts one series or scalar from another applying the given interpolation method

\(R_t = X_t - Y_t\)

Alignment operators:

Method

Behavior

intersect

Resultant series only has values on the intersection of dates. Values for dates present in only one series will be ignored

nan

Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as nan in the other series, and therefore in the resultant series

zero

Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as zero in the other series

step

Resultant series has values on the union of dates in both series. Values for dates only available in one series will be interpolated via step function in the other series

time

Resultant series have values on the union of dates / times. Missing values surrounded by valid values will be interpolated given length of interval. Input series must use DateTimeIndex.

Examples

Subtract one series from another:

>>> a = generate_series(100)
>>> b = generate_series(100)
>>> subtract(a, b, Interpolate.STEP)

See also

add()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.algebra.weighted_sum.html ================================================ gs_quant.timeseries.algebra.weighted_sum — gs_quant 0.1 documentation

gs_quant.timeseries.algebra.weighted_sum

weighted_sum(series, weights)[source]

Calculate a weighted sum.

Parameters
  • series (List[Series]) – list of time series

  • weights (list) – list of weights

Return type

Series

Returns

time series of weighted average

Usage

Calculate a weighted sum e.g. for a basket.

Examples

Generate price series and get a sum (weights 70%/30%).

>>> prices1 = generate_series(100)
>>> prices2 = generate_series(100)
>>> mybasket = weighted_sum([prices1, prices2], [0.7, 0.3])

See also

basket()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.analysis.count.html ================================================ gs_quant.timeseries.analysis.count — gs_quant 0.1 documentation

gs_quant.timeseries.analysis.count

count(x)[source]

Count observations in series

Parameters

x (Series) – time series

Return type

Series

Returns

number of observations

Usage

Count the number of valid observations in a series:

\(R_t = R_{t-1} + 1\)

if \(X_t\) is not NaN, and

\(R_t = R_{t-1} + 0\)

if \(X_t\) is NaN

Examples

Count observations in series:

>>> series = generate_series(100)
>>> count = count(series)

See also

sum()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.analysis.diff.html ================================================ gs_quant.timeseries.analysis.diff — gs_quant 0.1 documentation

gs_quant.timeseries.analysis.diff

diff(x, obs=1)[source]

Diff observations with given lag

Parameters
  • x (Series) – time series of prices

  • obs (int) – number of observations to lag

Return type

Series

Returns

date-based time series of return

Usage

Compute the difference in series values over a given lag:

\(R_t = X_t - X_{t-obs}\)

where \(obs\) is the number of observations to lag series in diff function

Examples

Diff prices levels:

>>> series = generate_series(100)
>>> returns = diff(series)

See also

lag()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.analysis.first.html ================================================ gs_quant.timeseries.analysis.first — gs_quant 0.1 documentation

gs_quant.timeseries.analysis.first

first(x)[source]

First value of series

Parameters

x (Series) – time series

Return type

Series

Returns

time series of first value

Usage

Return series with first value of X for all dates:

\(R_t = X_0\)

where \(X_0\) is the first value in the series

Examples

Last value of series:

>>> series = generate_series(100)
>>> returns = first(series)

See also

last()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.analysis.lag.html ================================================ gs_quant.timeseries.analysis.lag — gs_quant 0.1 documentation

gs_quant.timeseries.analysis.lag

lag(x, obs=1, mode=<LagMode.EXTEND: 'extend'>)[source]

Lag timeseries by a number of observations or a relative date.

Parameters
  • x (Series) – timeseries of prices

  • obs (Union[Window, int, str]) – non-zero integer (number of observations) or relative date e.g. “-90d”, “1d”, “1m”, “1y”

  • mode (LagMode) – whether to extend series index (into the future)

Return type

Series

Returns

date-based time series of return

Usage

Shift the series backwards by a specified number of observations:

\(R_t = X_{t-obs}\)

where \(obs\) is the number of observations to lag series

Examples

Lag series by 2 observations:

>>> prices = generate_series(100)
>>> lagged = lag(prices, 2)

Lag series by 1 year:

>>> prices = generate_series(100)
>>> lagged = lag(prices, '1y')

See also

diff()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.analysis.last.html ================================================ gs_quant.timeseries.analysis.last — gs_quant 0.1 documentation

gs_quant.timeseries.analysis.last

last(x)[source]

Last value of series (as a series)

Parameters

x (Series) – time series

Return type

Series

Returns

time series of last value

Usage

Return series with last value of X for all dates:

\(R_t = X_T\)

where \(X_T\) is the last value in the series

Examples

Last value of series:

>>> series = generate_series(100)
>>> returns = last(series)

See also

first()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.analysis.last_value.html ================================================ gs_quant.timeseries.analysis.last_value — gs_quant 0.1 documentation

gs_quant.timeseries.analysis.last_value

last_value(x)[source]

Last value of series (as a scalar)

Parameters

x (Series) – time series

Return type

Union[int, float]

Returns

last value

Usage

Return a scalar value \(X_T\) where T is the last index value in the series.

Examples

Last value of series:

>>> series = generate_series(100)
>>> lv = last_value(series)

See also

last()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.analysis.weighted_sum.html ================================================ weighted_sum — gs_quant 0.1 documentation
================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.backtesting.basket.html ================================================ gs_quant.timeseries.backtesting.basket — gs_quant 0.1 documentation

gs_quant.timeseries.backtesting.basket

basket(series, weights, costs=None, rebal_freq=<RebalFreq.DAILY: 'daily'>, return_type=<ReturnType.EXCESS_RETURN: 'excess_return'>)[source]

Calculates a basket return series.

Parameters
  • series (list) – list of time series of instrument prices

  • weights (list) – list of weights

  • costs (Optional[list]) – list of execution costs in decimal; defaults to costs of 0

  • rebal_freq (RebalFreq) – rebalancing frequency - Daily or Monthly

  • return_type (ReturnType) – return type of underlying instruments - only excess return is supported

Return type

Series

Returns

time series of the resulting basket

Usage

Calculates a basket return series.

Examples

Generate price series and combine them in a basket (weights 70%/30%) which rebalances monthly and assumes execution costs 5bps and 10bps each time the constituents are traded.

>>> prices1 = generate_series(100)
>>> prices2 = generate_series(100)
>>> mybasket = basket([prices1, prices2], [0.7, 0.3], [0.0005, 0.001], monthly)

See also

prices()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.datetime.align.html ================================================ gs_quant.timeseries.datetime.align — gs_quant 0.1 documentation

gs_quant.timeseries.datetime.align

align(x, y, method=<Interpolate.INTERSECT: 'intersect'>)[source]

Align dates of two series or scalars

Parameters
  • x (Union[Series, Real]) – first timeseries or scalar

  • y (Union[Series, Real]) – second timeseries or scalar

  • method (Interpolate) – interpolation method (default: intersect). Only used when both x and y are timeseries

Return type

Union[List[Series], List[Real]]

Returns

timeseries with specified dates or two scalars from the input

Usage

Align the dates of two series using the specified interpolation method. Returns two series with dates based on the method of interpolation, for example, can be used to intersect the dates of two series, union dates with a defined manner to compute missing values.

Interpolation methods:

Type

Behavior

intersect

Resultant series only have values on the intersection of dates /times.

nan

Resultant series have values on the union of dates /times. Values will be NaN for dates or times only present in the other series

zero

Resultant series have values on the union of dates / times. Values will be zero for dates or times only present in the other series

step

Resultant series have values on the union of dates / times. Each series will use the value of the previous valid point if requested date does not exist. Values prior to the first date will be equivalent to the first available value

time

Resultant series have values on the union of dates / times. Missing values surrounded by valid values will be interpolated given length of interval. Input series must use DateTimeIndex.

Examples

Stepwize interpolation of series based on dates in second series:

>>> a = generate_series(100)
>>> b = generate_series(100)
>>> align(a, b)

See also

sub()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.datetime.date_range.html ================================================ gs_quant.timeseries.datetime.date_range — gs_quant 0.1 documentation

gs_quant.timeseries.datetime.date_range

date_range(x, start_date, end_date, weekdays_only=False)[source]

Create a time series from a (sub-)range of dates in an existing time series.

Parameters
  • x (Series) – time series

  • start_date (Union[date, int]) – start date for the sliced time series. If integer, number of observations after the first

  • end_date (Union[date, int]) – end date for the sliced time series. If integer, number of observations before the last

  • weekdays_only (bool) – whether to include only weekdays in the sliced ranges

Return type

Series

Returns

sliced time series

Usage

Returns a restricted (“sliced”) time series based on start and end dates:

\(Y_t = R_t |_{start < t < end}\)

Examples

Slice the first and last week of a time series:

>>> series = generate_series(100)
>>> sliced_series = date_range(series,7,7)

See also

day() :func: lag

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.datetime.day.html ================================================ gs_quant.timeseries.datetime.day — gs_quant 0.1 documentation

gs_quant.timeseries.datetime.day

day(x)[source]

Day of each value in series

Parameters

x (Series) – time series

Return type

Series

Returns

day of observations

Usage

Returns the day as a numeric value for each observation in the series:

\(Y_t = day(t)\)

Day of the time or date is the integer day number within the month, e.g. 1-31

Examples

Day for observations in series:

>>> series = generate_series(100)
>>> days = day(series)

See also

month() year()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.datetime.interpolate.html ================================================ gs_quant.timeseries.datetime.interpolate — gs_quant 0.1 documentation

gs_quant.timeseries.datetime.interpolate

interpolate(x, dates=None, method=<Interpolate.INTERSECT: 'intersect'>)[source]

Interpolate over specified dates or times

Parameters
  • x (Series) – timeseries to interpolate

  • dates (Union[List[date], List[time], Series, None]) – array of dates/times or another series to interpolate

  • method (Interpolate) – interpolation method (default: intersect)

Return type

Series

Returns

timeseries with specified dates

Usage

Interpolate the series X over the dates specified by the dates parameter. This can be an array of dates or another series, in which case the index of the series will be used to specify dates

Interpolation methods:

Type

Behavior

intersect

Resultant series only has values on the intersection of dates /times. Will only contain intersection of valid dates / times in the series

nan

Resultant series only has values on the intersection of dates /times. Value will be NaN for dates not present in the series

zero

Resultant series has values on all requested dates / times. The series will have a value of zero where the requested date or time was not present in the series

step

Resultant series has values on all requested dates / times. The series will use the value of the previous valid point if requested date does not exist. Values prior to the first date will be equivalent to the first available value

Examples

Stepwize interpolation of series based on dates in second series:

>>> a = generate_series(100)
>>> b = generate_series(100)
>>> interpolate(a, b, Interpolate.INTERSECT)

See also

sub()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.datetime.month.html ================================================ gs_quant.timeseries.datetime.month — gs_quant 0.1 documentation

gs_quant.timeseries.datetime.month

month(x)[source]

Month of each value in series

Parameters

x (Series) – time series

Return type

Series

Returns

month of observations

Usage

Returns the month as a numeric value for each observation in the series:

\(Y_t = month(t)\)

Month of the time or date is the integer month number, e.g. 1-12

Examples

Day for observations in series:

>>> series = generate_series(100)
>>> days = month(series)

See also

day() year()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.datetime.prepend.html ================================================ gs_quant.timeseries.datetime.prepend — gs_quant 0.1 documentation

gs_quant.timeseries.datetime.prepend

prepend(x)[source]

Prepend data series

Parameters

x (List[Series]) – an array of timeseries

Return type

Series

Returns

concatenated timeseries

Usage

For input series [\(x_1\), \(x_2\), … , \(X_n\)], takes data from series \(X_i\) until the first date for which \(X_{i+1}\) has data, useful when a higher quality series has a shorter history than a lower quality series.

Examples

Prepend two series:

>>> x = generate_series(100)
>>> y = generate_series(100)
>>> prepend([x, y])

See also

union()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.datetime.quarter.html ================================================ gs_quant.timeseries.datetime.quarter — gs_quant 0.1 documentation

gs_quant.timeseries.datetime.quarter

quarter(x)[source]

Quarter of each value in series

Parameters

x (Series) – time series

Return type

Series

Returns

quarter of observations

Usage

Returns the quarter as a numeric value for each observation in the series:

\(Y_t = quarter(t)\)

Quarter of the time or date is the integer quarter number, e.g. 1, 2, 3, 4

Examples

Quarter for observations in series:

>>> series = generate_series(100)
>>> days = quarter(series)

See also

day() month()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.datetime.union.html ================================================ gs_quant.timeseries.datetime.union — gs_quant 0.1 documentation

gs_quant.timeseries.datetime.union

union(x)[source]

Fill in missing dates or times of one series with another

Parameters

x (List[Series]) – an array of timeseries

Return type

Series

Returns

combined series

Usage

Starting from \(i=1\), takes points from series \(x_i\). Where points are missing from \(x_i\), returns points from \(x_{i+1}\).

Examples

Union of two series:

>>> x = generate_series(100)
>>> y = generate_series(100)
>>> union([x, y])

See also

prepend()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.datetime.value.html ================================================ gs_quant.timeseries.datetime.value — gs_quant 0.1 documentation

gs_quant.timeseries.datetime.value

value(x, date, method=<Interpolate.STEP: 'step'>)[source]

Value at specified date or time

Parameters
  • x (Series) – timeseries

  • date (Union[date, time]) – requested date or time

  • method (Interpolate) – interpolation method (default: step)

Return type

Series

Returns

value at specified date or time

Usage

Returns the value of series X at the specified date:

\(Y_t = X_{date}\)

If the requested date or time is not present in the series, the value function will return the value from the previous available date or time by default. Caller can specify other interpolation styles via the method param:

Interpolation methods:

Type

Behavior

intersect

Only returns a value for valid dates

nan

Value will be NaN for dates not present in the series

zero

Value will be zero for dates not present in the series

step

Value of the previous valid point if requested date does not exist. Values prior to the first date will be equivalent to the first available value

Examples

Value of series on 5Mar18:

>>> a = generate_series(100)
>>> value(a, date(2019, 1, 3)

See also

interpolate()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.datetime.weekday.html ================================================ gs_quant.timeseries.datetime.weekday — gs_quant 0.1 documentation

gs_quant.timeseries.datetime.weekday

weekday(x)[source]

Weekday of each value in series

Parameters

x (Series) – time series

Return type

Series

Returns

weekday of observations

Usage

Returns the weekday as a numeric value for each observation in the series:

\(Y_t = weekday(t)\)

Weekday of the time or date is the integer day of the week, e.g. 0-6, where 0 represents Monday

Examples

Weekday for observations in series:

>>> series = generate_series(100)
>>> days = weekday(series)

See also

day() month()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.datetime.year.html ================================================ gs_quant.timeseries.datetime.year — gs_quant 0.1 documentation

gs_quant.timeseries.datetime.year

year(x)[source]

Year of each value in series

Parameters

x (Series) – time series

Return type

Series

Returns

year of observations

Usage

Returns the year as a numeric value for each observation in the series:

\(Y_t = year(t)\)

Year of the time or date is the integer year number, e.g. 2019, 2020

Examples

Year for observations in series:

>>> series = generate_series(100)
>>> days = year(series)

See also

day() month()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.econometrics.annualize.html ================================================ gs_quant.timeseries.econometrics.annualize — gs_quant 0.1 documentation

gs_quant.timeseries.econometrics.annualize

annualize(x)[source]

Annualize series based on sample observation frequency

Parameters

x (Series) – time series of prices

Return type

Series

Returns

date-based time series of annualized values

Usage

Based on number of days between observations, will determine an annualization factor and then adjust values accordingly. Useful for annualizing daily or monthly returns

\(Y_t = X_t * \sqrt{F}\)

Annualization factors as follows, based on period implied by observations:

Period

Annualization Factor (F)

Daily

\(252\)

Weekly

\(52\)

Bi-Weekly

\(26\)

Monthly

\(12\)

Quarterly

\(4\)

Annually

\(1\)

Examples

Annualize daily returns series:

>>> prices = generate_series(100)
>>> ann = annualize(returns(prices))

See also

returns()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.econometrics.beta.html ================================================ gs_quant.timeseries.econometrics.beta — gs_quant 0.1 documentation

gs_quant.timeseries.econometrics.beta

beta(x, b, w=<gs_quant.timeseries.helper.Window object>, prices=True)[source]

Rolling beta of price series and benchmark

Parameters
  • x (Series) – time series of prices

  • b (Series) – time series of benchmark prices

  • w (Union[Window, int, str]) – Window, int, or str: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like ‘1m’, ‘1d’, etc. Window size defaults to length of series.

  • prices (bool) – True if input series are prices, False if they are returns

Return type

Series

Returns

date-based time series of beta

Usage

Calculate rolling beta, \(\beta_t\) of a series to a benchmark over a given window:

\(R_t = \alpha_t + \beta S_t + \epsilon_t\)

Calculated as:

\(\beta_t = \frac{\sum_{i=t-w+1}^t Cov(R_t, S_t)}{Var(S_t)}\)

where N is the number of observations in each rolling window, \(w\), and \(R_t\) and \(S_t\) are the simple returns for each series on time \(t\):

\(R_t = \frac{X_t}{X_{t-1}} - 1\) and \(S_t = \frac{b_t}{b_{t-1}} - 1\)

If prices = False, assumes returns are provided:

\(R_t = X_t\) and \(S_t = b_t\)

\(Cov(R_t, S_t)\) and \(Var(S_t)\) are the mean and variance of series \(R_t\) and \(S_t\) over the same window

If window is not provided, computes beta over the full series

Examples

Compute rolling \(1\) month (\(22\) business day) beta of two price series

>>> series = generate_series(100)
>>> benchmark = generate_series(100)
>>> b = beta(series, benchmark, 22)

See also

var() cov() correlation() returns()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.econometrics.change.html ================================================ gs_quant.timeseries.econometrics.change — gs_quant 0.1 documentation
================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.econometrics.correlation.html ================================================ gs_quant.timeseries.econometrics.correlation — gs_quant 0.1 documentation

gs_quant.timeseries.econometrics.correlation

correlation(x, y, w=<gs_quant.timeseries.helper.Window object>, type_=<SeriesType.PRICES: 'prices'>)[source]

Rolling correlation of two price series

Parameters
  • x (Series) – price series

  • y (Series) – price series

  • w (Union[Window, int, str]) – Window, int, or str: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like ‘1m’, ‘1d’, etc. Window size defaults to length of series.

  • type – type of both input series: prices or returns

Return type

Series

Returns

date-based time series of correlation

Usage

Calculate rolling realized correlation, \(\rho_t\) of two price series over a given window:

\(\rho_t = \frac{\sum_{i=t-w+1}^t (R_t - \overline{R_t})(Y_t - \overline{S_t})}{(N-1)\sigma R_t\sigma S_t}\)

where N is the number of observations in each rolling window, \(w\), and \(R_t\) and \(S_t\) are the simple returns for each series on time \(t\)

If prices are provided:

\(R_t = \frac{X_t}{X_{t-1}} - 1\) and \(S_t = \frac{Y_t}{Y_{t-1}} - 1\)

If returns are provided:

\(R_t = X_t\) and \(S_t = Y_t\)

\(\overline{R_t}\), \(\overline{S_t}\) are the mean values, and \(\sigma R_{t}\) and \(\sigma S_{t}\) are the sample standard deviations, of series \(R_t\) and \(S_t\) over the same window

If window is not provided, computes realized correlation over the full series

Examples

Compute rolling \(1\) month (\(22\) business day) correlation of price series

>>> series1 = generate_series(100)
>>> series2 = generate_series(100)
>>> corr = correlation(series1, series2, 22)

See also

std() returns()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.econometrics.excess_returns_.html ================================================ gs_quant.timeseries.econometrics.excess_returns_ — gs_quant 0.1 documentation

gs_quant.timeseries.econometrics.excess_returns_

excess_returns_(price_series, currency=<RiskFreeRateCurrency.USD: 'USD'>)[source]

Calculate excess returns

Parameters
  • price_series (Series) – price series

  • currency (RiskFreeRateCurrency) – currency for risk-free rate, defaults to USD

Return type

Series

Returns

excess returns

Usage

Given a price series P and risk-free rate R, excess returns E are defined as:

\(E_t = E_{t-1} + P_t - P_{t-1} * (1 + R * (D_t - D_{t-1}) / 360)\)

The Actual/360 day count convention is used.

Examples

Get excess returns from a price series.

>>> er = excess_returns(generate_series(100), USD)
================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.econometrics.index.html ================================================ gs_quant.timeseries.econometrics.index — gs_quant 0.1 documentation
================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.econometrics.max_drawdown.html ================================================ gs_quant.timeseries.econometrics.max_drawdown — gs_quant 0.1 documentation

gs_quant.timeseries.econometrics.max_drawdown

max_drawdown(x, w=<gs_quant.timeseries.helper.Window object>)[source]

Compute the maximum peak to trough drawdown over a rolling window.

Parameters
  • x (Series) – time series

  • w (Union[Window, int, str]) – Window, int, or str: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like ‘1m’, ‘1d’, etc. Window size defaults to length of series.

Return type

Series

Returns

time series of rolling maximum drawdown

Examples

Compute the maximum peak to trough drawdown

>>> series = generate_series(100)
>>> max_drawdown(series)

See also

returns()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.econometrics.prices.html ================================================ gs_quant.timeseries.econometrics.prices — gs_quant 0.1 documentation

gs_quant.timeseries.econometrics.prices

prices(series, initial=1, type=<Returns.SIMPLE: 'simple'>)[source]

Calculate price levels from returns series

Parameters
  • series (Series) – time series of returns

  • initial (int) – initial price level

  • type (Returns) – returns type: simple, logarithmic or absolute

Return type

Series

Returns

date-based time series of return

Usage

Compute price levels from returns series, based on the value of type:

Type

Description

simple

Simple arithmetic returns

logarithmic

Logarithmic returns

absolute

Absolute returns

Simple

Compute asset price series from simple returns:

\(Y_t = (1 + X_{t-1}) Y_{t-1}\)

where \(X_t\) is the asset price at time \(t\) and \(Y_0 = initial\)

Logarithmic

Compute asset price series from logarithmic returns:

\(Y_t = e^{X_{t-1}} Y_{t-1}\)

where \(X_t\) is the asset price at time \(t\) and \(Y_0 = initial\)

Absolute

Compute asset price series from absolute returns:

\(Y_t = X_{t-1} + Y_{t-1}\)

where \(X_t\) is the asset price at time \(t\) and \(Y_0 = initial\)

Examples

Generate price series and take compute returns

>>> series = generate_series(100)
>>> returns = prices(returns(series))

See also

returns() product() exp()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.econometrics.returns.html ================================================ gs_quant.timeseries.econometrics.returns — gs_quant 0.1 documentation

gs_quant.timeseries.econometrics.returns

returns(series, obs=1, type=<Returns.SIMPLE: 'simple'>)[source]

Calculate returns from price series

Parameters
  • series (Series) – time series of prices

  • obs (int) – number of observations

  • type (Returns) – returns type: simple, logarithmic or absolute

Return type

Series

Returns

date-based time series of return

Usage

Compute returns series from price levels, based on the value of type:

Type

Description

simple

Simple arithmetic returns

logarithmic

Logarithmic returns

absolute

Absolute returns

Simple

Simple geometric change in asset prices, which can be aggregated across assets

\(Y_t = \frac{X_t}{X_{t-obs}} - 1\)

where \(X_t\) is the asset price at time \(t\)

Logarithmic

Natural logarithm of asset price changes, which can be aggregated through time

\(Y_t = log(X_t) - log(X_{t-obs})\)

where \(X_t\) is the asset price at time \(t\)

Absolute

Absolute change in asset prices

\(Y_t = X_t - X_{t-obs}\)

where \(X_t\) is the asset price at time \(t\)

Examples

Generate price series and take compute returns

>>> prices = generate_series(100)
>>> returns = returns(prices)

See also

prices()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.econometrics.sharpe_ratio.html ================================================ gs_quant.timeseries.econometrics.sharpe_ratio — gs_quant 0.1 documentation

gs_quant.timeseries.econometrics.sharpe_ratio

sharpe_ratio(series, currency=<RiskFreeRateCurrency.USD: 'USD'>, w=None, curve_type=<CurveType.PRICES: 'prices'>)[source]

Calculate Sharpe ratio

Parameters
  • series (Series) – series of prices or excess returns for an asset

  • currency (RiskFreeRateCurrency) – currency for risk-free rate, defaults to USD

  • curve_type (CurveType) – whether input series is of prices or excess returns, defaults to prices

  • w (Union[Window, int, None]) – Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value.

Return type

Series

Returns

Sharpe ratio

Usage

Given a price series P, risk-free rate R, and window of size w returns the rolling Sharpe ratio S:

\(S_t = \frac{(E_t / E_{t-w+1})^{365.25 / (D_t - D_{t-w} - 1)}}{volatility(E, w)_t}\)

Excess returns E are defined as:

\(E_t = E_{t-1} + P_t - P_{t-1} * (1 + R * (D_t - D_{t-1}) / 360)\)

where D is the date for a data point. The Actual/360 day count convention is used.

Examples

Get rolling sharpe ratio of a price series (with window of 252).

>>> sr = sharpe_ratio(generate_series(100), USD, 252, CurveType.PRICES)

See also

volatility()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.econometrics.volatility.html ================================================ gs_quant.timeseries.econometrics.volatility — gs_quant 0.1 documentation

gs_quant.timeseries.econometrics.volatility

volatility(x, w=<gs_quant.timeseries.helper.Window object>, returns_type=<Returns.SIMPLE: 'simple'>)[source]

Realized volatility of price series

Parameters
  • x (Series) – time series of prices

  • w (Union[Window, int, str]) – Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like ‘1m’, ‘1d’, etc. Window size defaults to length of series.

  • returns_type (Returns) – returns type: simple, logarithmic or absolute

Return type

Series

Returns

date-based time series of return

Usage

Calculate rolling annualized realized volatility of a price series over a given window. Annual volatility of 20% is returned as 20.0:

\(Y_t = \sqrt{\frac{1}{N-1} \sum_{i=t-w+1}^t (R_t - \overline{R_t})^2} * \sqrt{252} * 100\)

where N is the number of observations in each rolling window \(w\), \(R_t\) is the return on time \(t\) based on returns_type

Type

Description

simple

Simple geometric change in asset prices: \(R_t = \frac{X_t}{X_{t-1}} - 1\) where \(X_t\) is the asset price at time \(t\)

logarithmic

Natural logarithm of asset price changes: \(R_t = log(X_t) - log(X_{t-1})\) where \(X_t\) is the asset price at time \(t\)

absolute

Absolute change in asset prices: \(Y_t = X_t - X_{t-obs}\) where \(X_t\) is the asset price at time \(t\)

and \(\overline{R_t}\) is the mean value over the same window:

\(\overline{R_t} = \frac{\sum_{i=t-w+1}^{t} R_t}{N}\)

If window is not provided, computes realized volatility over the full series

Examples

Compute rolling \(1\) month (\(22\) business day) annualized volatility of price series

>>> series = generate_series(100)
>>> vol_series = volatility(series, 22)
>>> vol_series = volatility(series, Window(22, 30))

See also

std() annualize() returns()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.statistics.cov.html ================================================ gs_quant.timeseries.statistics.cov — gs_quant 0.1 documentation

gs_quant.timeseries.statistics.cov

cov(x, y, w=<gs_quant.timeseries.helper.Window object>)[source]

Rolling co-variance of series over given window

Parameters
  • x (Series) – series: timeseries

  • y (Series) – series: timeseries

  • w (Union[Window, int, str]) – Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like ‘1m’, ‘1d’, etc. Window size defaults to length of series.

Return type

Series

Returns

timeseries of covariance

Usage

Provides unbiased estimator of sample co-variance over a rolling window:

\(R_t = \frac{1}{N-1} \sum_{i=t-w+1}^t (X_i - \overline{X_t}) (Y_i - \overline{Y_t})\)

where \(N\) is the number of observations in each rolling window, \(w\), and \(\overline{X_t}\) and \(\overline{Y_t}\) represent the sample mean of series \(X_t\) and \(Y_t\) over the same window:

\(\overline{X_t} = \frac{\sum_{i=t-w+1}^{t} X_i}{N}\) and \(\overline{Y_t} = \frac{\sum_{i=t-w+1}^{t} Y_i}{N}\)

If window is not provided, computes variance over the full series

Examples

Generate price series and compute variance of returns over \(22\) observations

>>> prices_x = generate_series(100)
>>> prices_y = generate_series(100)
>>> cov(returns(prices_x) returns(prices_y), 22)

See also

sum() mean() var()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.statistics.exponential_std.html ================================================ gs_quant.timeseries.statistics.exponential_std — gs_quant 0.1 documentation

gs_quant.timeseries.statistics.exponential_std

exponential_std(x, beta=0.75)[source]

Exponentially weighted standard deviation

Parameters
  • x (Series) – time series

  • beta (float) – how much to weigh the previous price in the time series, thus controlling how much importance we place on the (more distant) past. Must be between 0 (inclusive) and 1 (exclusive)

Return type

Series

Returns

time series of standard deviation of the input series

Usage

Provides an unbiased estimator of exponentially weighted standard deviation of a series [\(X_0\), \(X_1\), \(X_2\), …]:

\(S_t = \sqrt{[EWMA(X_t^2) - EWMA(X_t)^2] * DF_t}\)

where \(EWMA(X_t)\) is the exponential moving average at \(t\) (see function exponential_moving_average()), \(DF_t\) is the debiasing factor (see Weighted sample variance for further details):

\(DF_t = \frac{(\sum_{i=0}^t w_i)^2} {(\sum_{i=0}^t w_i)^2 - \sum_{i=0}^t w_i^2}\)

where \(w_i\) is the weight assigned to \(i\) th observation:

\(w_i = (1-\beta)\beta^i\) for i<t; \(\beta^i\) for i=t

Examples

Generate price series and compute exponentially weighted standard deviation of returns

>>> prices = generate_series(100)
>>> exponential_std(returns(prices), 0.9)

See also

std() var() exponential_moving_average()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.statistics.generate_series.html ================================================ gs_quant.timeseries.statistics.generate_series — gs_quant 0.1 documentation

gs_quant.timeseries.statistics.generate_series

generate_series(length)[source]

Generate sample timeseries

Parameters

length (int) – number of observations

Return type

Series

Returns

date-based time series of randomly generated prices

Usage

Create timeseries from returns generated from a normally distributed random variables (IDD). Length determines the number of observations to be generated.

Assume random variables \(R\) which follow a normal distribution with mean \(0\) and standard deviation of \(1\)

\(R \sim N(0, 1)\)

The timeseries is generated from these random numbers through:

\(X_t = (1 + R)X_{t-1}\)

Examples

Generate price series with 100 observations starting from today’s date:

>>> prices = generate_series(100)

See also

numpy.random.normal()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.statistics.max_.html ================================================ gs_quant.timeseries.statistics.max_ — gs_quant 0.1 documentation

gs_quant.timeseries.statistics.max_

max_(x, w=<gs_quant.timeseries.helper.Window object>)[source]

Maximum value of series over given window

Parameters
  • x (Union[Series, List[Series]]) – series: a timeseries or an array of timeseries

  • w (Union[Window, int, str]) – Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like ‘1m’, ‘1d’, etc. Window size defaults to length of series.

Return type

Series

Returns

timeseries of maximum value

Usage

Returns the maximum value of the series over each window.

If \(x\) is a series:

\(R_t = max(X_{t-w+1}:X_t)\)

where \(w\) is the size of the rolling window.

If \(x\) is an array of series:

\(R_t = max(X_{1, t-w+1}:X_{n, t})\)

where \(w\) is the size of the rolling window, and \(n\) is the number of series.

If window is not provided, returns the maximum value over the full series. If the window size is greater than the available data, will return maximum of available values.

Examples

Maximum value of price series over the last \(22\) observations:

>>> prices = generate_series(100)
>>> max_(prices, 22)

See also

min_()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.statistics.mean.html ================================================ gs_quant.timeseries.statistics.mean — gs_quant 0.1 documentation

gs_quant.timeseries.statistics.mean

mean(x, w=<gs_quant.timeseries.helper.Window object>)[source]

Arithmetic mean of series over given window

Parameters
  • x (Union[Series, List[Series]]) – series: a timeseries or an array of timeseries

  • w (Union[Window, int, str]) – Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like ‘1m’, ‘1d’, etc. Window size defaults to length of series.

Return type

Series

Returns

timeseries of mean value

Usage

Calculates arithmetic mean of the series over a rolling window

If a timeseries is provided:

\(R_t = \frac{\sum_{i=t-w+1}^{t} X_i}{N}\)

where \(N\) is the number of observations in each rolling window, \(w\).

If an array of timeseries is provided:

\(R_t = \frac{\sum_{i=t-w+1}^{t} {\sum_{j=1}^{n}} X_{ij}}{N}\)

where \(n\) is the number of series, and \(N\) is the number of observations in each rolling window, \(w\).

If window is not provided, computes rolling mean over the full series. If the window size is greater than the available data, will return mean of available values.

Examples

Generate price series and compute mean over \(22\) observations

>>> prices = generate_series(100)
>>> mean(prices, 22)

See also

median() mode()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.statistics.median.html ================================================ gs_quant.timeseries.statistics.median — gs_quant 0.1 documentation

gs_quant.timeseries.statistics.median

median(x, w=<gs_quant.timeseries.helper.Window object>)[source]

Median value of series over given window

Parameters
  • x (Series) – series: timeseries

  • w (Union[Window, int, str]) – Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like ‘1m’, ‘1d’, etc. Window size defaults to length of series.

Return type

Series

Returns

timeseries of median value

Usage

Computes the median value over a given window. For each window, this function will return the middle value when all elements in the window are sorted. If the number of observations in the window is even, will return the average of the middle two values. If the window size is greater than the available data, will return median of available values:

\(d = \frac{w-1}{2}\)

\(R_t = \frac{X_{\lfloor t-d \rfloor} + X_{\lceil t-d \rceil}}{2}\)

where \(w\) is the size of the rolling window. If window is not provided, computes median over the full series

Examples

Generate price series and compute median over \(22\) observations

>>> prices = generate_series(100)
>>> median(prices, 22)

See also

mean() mode()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.statistics.min_.html ================================================ gs_quant.timeseries.statistics.min_ — gs_quant 0.1 documentation

gs_quant.timeseries.statistics.min_

min_(x, w=<gs_quant.timeseries.helper.Window object>)[source]

Minimum value of series over given window

Parameters
  • x (Union[Series, List[Series]]) – series: a timeseries or an array of timeseries

  • w (Union[Window, int, str]) – Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like ‘1m’, ‘1d’, etc. Window size defaults to length of series.

Return type

Series

Returns

timeseries of minimum value

Usage

Returns the minimum value of the series over each window.

If \(x\) is a series:

\(R_t = min(X_{t-w+1}:X_t)\)

where \(w\) is the size of the rolling window.

If \(x\) is an array of series:

\(R_t = min(X_{1, t-w+1}:X_{n, t})\)

where \(w\) is the size of the rolling window, and \(n\) is the number of series.

If window is not provided, returns the minimum value over the full series. If the window size is greater than the available data, will return minimum of available values.

Examples

Minimum value of price series over the last \(22\) observations:

>>> prices = generate_series(100)
>>> min_(prices, 22)

See also

max_()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.statistics.mode.html ================================================ gs_quant.timeseries.statistics.mode — gs_quant 0.1 documentation

gs_quant.timeseries.statistics.mode

mode(x, w=<gs_quant.timeseries.helper.Window object>)[source]

Most common value in series over given window

Parameters
  • x (Series) – series: timeseries

  • w (Union[Window, int, str]) – Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like ‘1m’, ‘1d’, etc. Window size defaults to length of series.

Return type

Series

Returns

timeseries of mode value

Usage

Computes the mode over a given window. For each window, this function will return the most common value of all elements in the window. If there are multiple values with the same frequency of occurrence, will return the smallest value.

If window is not provided, computes mode over the full series.

Examples

Generate price series and compute mode over \(22\) observations

>>> prices = generate_series(100)
>>> mode(prices, 22)

See also

mean() median()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.statistics.percentile.html ================================================ gs_quant.timeseries.statistics.percentile — gs_quant 0.1 documentation

gs_quant.timeseries.statistics.percentile

percentile(x, n, w=None)[source]

Returns the nth percentile of a series.

Parameters
  • x (Series) – series

  • n (float) – percentile

  • w (Union[Window, int, str, None]) – Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like ‘1m’, ‘1d’, etc.

Return type

Union[Series, float]

Returns

nth percentile

Usage

Calculates the nth percentile rank of \(x\). Rolling nth percentile is returned if a window is specified, else a scalar for nth percentile over the entire series.

Example

Compute the 90th percentile of a series.

>>> a = generate_series(100)
>>> percentile(a, 90)
================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.statistics.percentiles.html ================================================ gs_quant.timeseries.statistics.percentiles — gs_quant 0.1 documentation

gs_quant.timeseries.statistics.percentiles

percentiles(x, y=None, w=<gs_quant.timeseries.helper.Window object>)[source]

Rolling percentiles over given window

Parameters
  • x (Series) – value series

  • y (Optional[Series]) – distribution series

  • w (Union[Window, int, str]) – Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like ‘1m’, ‘1d’, etc. Window size defaults to length of series.

Return type

Series

Returns

timeseries of percentiles

Usage

Calculate percentile rank of \(y\) in the sample distribution of \(x\) over a rolling window of length \(w\):

\(R_t = \frac{\sum_{i=t-N+1}^{t}{[X_i<{Y_t}]}+0.5\sum_{i=t-N+1}^{t}{[X_i={Y_t}]}}{N}\times100\%\)

Where \(N\) is the number of observations in a rolling window. If \(y\) is not provided, calculates percentiles of \(x\) over its historical values. If window length \(w\) is not provided, uses an ever-growing history of values. If \(w\) is greater than the available data size, returns empty.

Examples

Compute percentile ranks of a series in the sample distribution of a second series over \(22\) observations

>>> a = generate_series(100)
>>> b = generate_series(100)
>>> percentiles(a, b, 22)

See also

zscores()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.statistics.product.html ================================================ gs_quant.timeseries.statistics.product — gs_quant 0.1 documentation

gs_quant.timeseries.statistics.product

product(x, w=<gs_quant.timeseries.helper.Window object>)[source]

Rolling product of series over given window

Parameters
  • x (Series) – series: timeseries

  • w (Union[Window, int, str]) – Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like ‘1m’, ‘1d’, etc. Window size defaults to length of series.

Return type

Series

Returns

timeseries of rolling product

Usage

Calculate the product of observations over a given rolling window. For each time, \(t\), returns the value of all observations from \(t-w+1\) to \(t\) multiplied together:

\(R_t = \prod_{i=t-w+1}^{t} X_i\)

where \(w\) is the size of the rolling window. If window is not provided, computes product over the full series

Examples

Generate price series and compute rolling sum over \(22\) observations

>>> prices = generate_series(100)
>>> product(1+returns(prices))

See also

sum_()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.statistics.range_.html ================================================ gs_quant.timeseries.statistics.range_ — gs_quant 0.1 documentation

gs_quant.timeseries.statistics.range_

range_(x, w=<gs_quant.timeseries.helper.Window object>)[source]

Range of series over given window

Parameters
  • x (Series) – series: timeseries

  • w (Union[Window, int, str]) – Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like ‘1m’, ‘1d’, etc. Window size defaults to length of series.

Return type

Series

Returns

timeseries of range

Usage

Returns the range of the series (max - min) over rolling window:

\(R_t = max(X_{t-w+1}:X_t) - min(X_{t-w+1}:X_t)\)

where \(w\) is the size of the rolling window. If window is not provided, returns the range over the full series. If the window size is greater than the available data, will return range of all available values.

Examples

Range of price series over the last \(22\) observations:

>>> prices = generate_series(100)
>>> range_(prices, 22)

See also

min_() max_()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.statistics.std.html ================================================ gs_quant.timeseries.statistics.std — gs_quant 0.1 documentation

gs_quant.timeseries.statistics.std

std(x, w=<gs_quant.timeseries.helper.Window object>)[source]

Rolling standard deviation of series over given window

Parameters
  • x (Series) – series: timeseries

  • w (Union[Window, int, str]) – Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like ‘1m’, ‘1d’, etc. Window size defaults to length of series.

Return type

Series

Returns

timeseries of standard deviation

Usage

Provides unbiased estimator of sample standard deviation over a rolling window:

\(R_t = \sqrt{\frac{1}{N-1} \sum_{i=t-w+1}^t (X_i - \overline{X_t})^2}\)

where \(N\) is the number of observations in each rolling window, \(w\), and \(\overline{X_t}\) is the mean value over the same window:

\(\overline{X_t} = \frac{\sum_{i=t-w+1}^{t} X_i}{N}\)

If window is not provided, computes standard deviation over the full series

Examples

Generate price series and compute standard deviation of returns over \(22\) observations

>>> prices = generate_series(100)
>>> std(returns(prices), 22)

See also

sum() mean() var()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.statistics.sum_.html ================================================ gs_quant.timeseries.statistics.sum_ — gs_quant 0.1 documentation

gs_quant.timeseries.statistics.sum_

sum_(x, w=<gs_quant.timeseries.helper.Window object>)[source]

Rolling sum of series over given window

Parameters
  • x (Union[Series, List[Series]]) – series: a timeseries or an array of timeseries

  • w (Union[Window, int, str]) – Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like ‘1m’, ‘1d’, etc. Window size defaults to length of series.

Return type

Series

Returns

timeseries of rolling sum

Usage

Calculate the sum of observations over a given rolling window.

If \(x\) is a series:

\(R_t = \sum_{i=t-w+1}^{t} X_i\)

where \(w\) is the size of the rolling window.

If \(x\) is an array of series:

\(R_t = \sum_{i=t-w+1}^{t} \sum_{j=1}^{n} X_{ij}\)

where \(w\) is the size of the rolling window and \(n\) is the number of series

If window is not provided, computes sum over the full series. If the window size is greater than the available data, will return sum of available values.

Examples

Generate price series and compute rolling sum over \(22\) observations

>>> prices = generate_series(100)
>>> sum_(prices, 22)

See also

product()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.statistics.var.html ================================================ gs_quant.timeseries.statistics.var — gs_quant 0.1 documentation

gs_quant.timeseries.statistics.var

var(x, w=<gs_quant.timeseries.helper.Window object>)[source]

Rolling variance of series over given window

Parameters
  • x (Series) – series: timeseries

  • w (Union[Window, int, str]) – Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like ‘1m’, ‘1d’, etc. Window size defaults to length of series.

Return type

Series

Returns

timeseries of variance

Usage

Provides unbiased estimator of sample variance over a rolling window:

\(R_t = \frac{1}{N-1} \sum_{i=t-w+1}^t (X_i - \overline{X_t})^2\)

where \(N\) is the number of observations in each rolling window, \(w\), and \(\overline{X_t}\) is the mean value over the same window:

\(\overline{X_t} = \frac{\sum_{i=t-w+1}^{t} X_i}{N}\)

If window is not provided, computes variance over the full series

Examples

Generate price series and compute variance of returns over \(22\) observations

>>> prices = generate_series(100)
>>> var(returns(prices), 22)

See also

var() mean() std()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.statistics.winsorize.html ================================================ gs_quant.timeseries.statistics.winsorize — gs_quant 0.1 documentation

gs_quant.timeseries.statistics.winsorize

winsorize(x, limit=2.5, w=<gs_quant.timeseries.helper.Window object>)[source]

Limit extreme values in series

Parameters
  • x (Series) – time series of prices

  • limit (float) – max z-score of values

  • w (Union[Window, int, str]) – Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like ‘1m’, ‘1d’, etc. Window size defaults to length of series.

Return type

Series

Returns

timeseries of winsorized values

Usage

Cap and floor values in the series which have a z-score greater or less than provided value. This function will restrict the distribution of values. Calculates the sample standard deviation and adjusts values which fall outside the specified range to be equal to the upper or lower limits

Lower and upper limits are defined as:

\(upper = \mu + \sigma \times limit\)

\(lower = \mu - \sigma \times limit\)

Where \(\mu\) and \(\sigma\) are sample mean and standard deviation. The series is restricted by:

\(R_t = max( min( X_t, upper), lower )\)

See winsorising for additional information

Examples

Generate price series and winsorize z-score of returns over \(22\) observations

>>> prices = generate_series(100)
>>> winsorize(zscore(returns(prices), 22))

See also

zscore() mean() std()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.statistics.zscores.html ================================================ gs_quant.timeseries.statistics.zscores — gs_quant 0.1 documentation

gs_quant.timeseries.statistics.zscores

zscores(x, w=<gs_quant.timeseries.helper.Window object>)[source]

Rolling z-scores over a given window

Parameters
  • x (Series) – time series of prices

  • w (Union[Window, int, str]) – Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like ‘1m’, ‘1d’, etc. Window size defaults to length of series.

Return type

Series

Returns

timeseries of z-scores

Usage

Calculate standard score of each value in series over given window. Standard deviation and sample mean are computed over the specified rolling window, then element is normalized to provide a rolling z-score:

\(R_t = \frac { X_t - \mu }{ \sigma }\)

Where \(\mu\) and \(\sigma\) are sample mean and standard deviation over the given window

If window is not provided, computes z-score relative to mean and standard deviation over the full series

Examples

Generate price series and compute z-score of returns over \(22\) observations

>>> prices = generate_series(100)
>>> zscores(returns(prices), 22)

See also

mean() std()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.technicals.bollinger_bands.html ================================================ gs_quant.timeseries.technicals.bollinger_bands — gs_quant 0.1 documentation

gs_quant.timeseries.technicals.bollinger_bands

bollinger_bands(x, w=<gs_quant.timeseries.helper.Window object>, k=2)[source]

Bollinger bands with given window and width

Parameters
  • x (Series) – time series of prices

  • w (Union[Window, int, str]) – Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like ‘1m’, ‘1d’, etc. Window size defaults to length of series.

  • k (float) – band width in standard deviations (default: 2)

Return type

DataFrame

Returns

date-based time series of return

Usage

Standard deviation bands around the moving average of asset price level. Bollinger bands can be used to determine a range around the price level which responds to local volatility changes. Returns two series, upper, \(u_t\) and lower, \(l_t\)

\(u_t = \bar{X_t} + k\sigma_t\)

\(l_t = \bar{X_t} - k\sigma_t\)

where \(\bar{X_t}\) is the moving average over specified window, and \(\sigma_t\) is the rolling standard deviation over the specified window

See Bollinger Bands for more information

Examples

Compute bollinger bands around \(20\) day moving average at \(2\) standard deviations:

>>> prices = generate_series(100)
>>> bollinger_bands(prices, 20, 2)

See also

moving_average() std()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.technicals.exponential_moving_average.html ================================================ gs_quant.timeseries.technicals.exponential_moving_average — gs_quant 0.1 documentation

gs_quant.timeseries.technicals.exponential_moving_average

exponential_moving_average(x, beta=0.75)[source]

Exponentially weighted moving average

Parameters
  • x (Series) – time series of prices

  • beta (float) – how much to weigh the previous observations in the time series, thus controlling how much importance we place on the (more distant) past. Must be between 0 (inclusive) and 1 (exclusive)

Return type

Series

Returns

date-based time series of return

Usage

The exponential(ly weighted) moving average (EMA) of a series [\(X_0\), \(X_1\), \(X_2\), …], is defined as:

\(Y_0 = X_0\)

\(Y_t = \beta \cdot Y_{t-1} + (1 - \beta) \cdot X_t\)

where \(\beta\) is the weight we place on the previous average.

See Exponential moving average for more information

Examples

Generate price series with 100 observations starting from today’s date:

>>> prices = generate_series(100)
>>> exponential_moving_average(prices, 0.9)

See also

mean() moving_average() smoothed_moving_average()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.technicals.exponential_spread_volatility.html ================================================ gs_quant.timeseries.technicals.exponential_spread_volatility — gs_quant 0.1 documentation

gs_quant.timeseries.technicals.exponential_spread_volatility

exponential_spread_volatility(x, beta=0.75)[source]

Exponentially weighted spread volatility

Parameters
  • x (Series) – time series of prices

  • beta (float) – how much to weigh the previous price in the time series, thus controlling how much importance we place on the (more distant) past. Must be between 0 (inclusive) and 1 (exclusive)

Return type

Series

Returns

date-based time series of exponential spread volatility of the input series

Usage

Exponentially weights the daily differences of the input series, calculates the annualized standard deviation

Examples

Generate price series and compute exponentially weighted standard deviation of returns

>>> prices = generate_series(100)
>>> exponential_volatility(prices, 0.9)

The above is equivalent to

>>> annualize(exponential_std(diff(prices, 1), 0.9))

See also

volatility() exponential_std() exponential_volatility()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.technicals.exponential_volatility.html ================================================ gs_quant.timeseries.technicals.exponential_volatility — gs_quant 0.1 documentation

gs_quant.timeseries.technicals.exponential_volatility

exponential_volatility(x, beta=0.75)[source]

Exponentially weighted volatility

Parameters
  • x (Series) – time series of prices

  • beta (float) – how much to weigh the previous price in the time series, thus controlling how much importance we place on the (more distant) past. Must be between 0 (inclusive) and 1 (exclusive)

Return type

Series

Returns

date-based time series of exponential volatility of the input series

Usage

Calculates the exponentially weighted standard deviation of the return of the input series, and annualizes the standard deviation

Examples

Generate price series and compute exponentially weighted standard deviation of returns

>>> prices = generate_series(100)
>>> exponential_volatility(prices, 0.9)

The above is equivalent to

>>> annualize(exponential_std(returns(prices), 0.9)) * 100

See also

volatility() exponential_std() exponential_spread_volatility()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.technicals.moving_average.html ================================================ gs_quant.timeseries.technicals.moving_average — gs_quant 0.1 documentation

gs_quant.timeseries.technicals.moving_average

moving_average(x, w=<gs_quant.timeseries.helper.Window object>)[source]

Moving average over specified window

Parameters
  • x (Series) – time series of prices

  • w (Union[Window, int, str]) – Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like ‘1m’, ‘1d’, etc. Window size defaults to length of series.

Return type

Series

Returns

date-based time series of return

Usage

Simple arithmetic moving average over the specified window (number of observations). Shorter windows will be more reactive to changes in the asset price, but more volatile. Larger windows will be smoother but less reactive to near term changes in asset prices.

\(R_t = \frac{\sum_{i=t-w+1}^{t} X_t}{N}\)

where N is the number of observations in each rolling window, \(w\). If window is not provided, computes rolling mean over the full series

Equivalent to mean

Examples

Generate price series with 100 observations starting from today’s date:

>>> prices = generate_series(100)
>>> moving_average(prices, 22)

See also

mean()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.technicals.relative_strength_index.html ================================================ gs_quant.timeseries.technicals.relative_strength_index — gs_quant 0.1 documentation

gs_quant.timeseries.technicals.relative_strength_index

relative_strength_index(x, w=14)[source]

Relative Strength Index

Parameters
  • x (Series) – time series of prices

  • w (Union[Window, int, str]) – Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like ‘1m’, ‘1d’, etc. Window size defaults to length of series.

Return type

DataFrame

Returns

date-based time series of RSI

Usage

The RSI computes momentum as the ratio of higher closes to lower closes: stocks which have had more or stronger positive changes have a higher RSI than stocks which have had more or stronger negative changes.

See RSI for more information

Examples

Compute relative strength index over a \(14\) day window:

>>> prices = generate_series(100)
>>> relative_strength_index(prices, 14)

See also

moving_average() std() smoothed_moving_average()

================================================ FILE: docs/_build/html/functions/gs_quant.timeseries.technicals.smoothed_moving_average.html ================================================ gs_quant.timeseries.technicals.smoothed_moving_average — gs_quant 0.1 documentation

gs_quant.timeseries.technicals.smoothed_moving_average

smoothed_moving_average(x, w=<gs_quant.timeseries.helper.Window object>)[source]

Smoothed moving average over specified window

Parameters
  • x (Series) – time series of prices

  • w (Union[Window, int, str]) – Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like ‘1m’, ‘1d’, etc. Window size defaults to length of series.

Return type

Series

Returns

date-based time series of return

Usage

A modified moving average (MMA), running moving average (RMA), or smoothed moving average (SMMA) is defined as:

\(P_{MM,today} = \frac{(N-1)P_{MM,yesterday} + P_today}{N}\)

where N is the number of observations in each rolling window, \(w\). If window is not provided, computes rolling mean over the full series

See Modified moving average for more information

Examples

Generate price series with 100 observations starting from today’s date:

>>> prices = generate_series(100)
>>> smoothed_moving_average(prices, 22)

See also

mean() :func:’moving_average’

================================================ FILE: docs/_build/html/genindex.html ================================================ Index — gs_quant 0.1 documentation

Index

_ | A | B | C | D | E | F | G | H | I | J | L | M | N | O | P | Q | R | S | T | U | V | W | Y | Z

_

A

B

C

D

E

F

G

H

I

J

L

M

N

O

P

Q

R

S

T

U

V

W

Y

Z


================================================ FILE: docs/_build/html/index.html ================================================ GS Quant API — gs_quant 0.1 documentation
================================================ FILE: docs/_build/html/instrument.html ================================================ Instrument Package — gs_quant 0.1 documentation

Instrument Package

Priceable(**kwargs)

Instruments

CommodOTCSwap([start, end, …])

Object representation of a commodities swap

EqCliquet([underlier, underlier_type, …])

Object representation of an Equity Cliquet

EqForward([underlier, underlier_type, …])

Object representation of an equity forward

EqOption([underlier, expiration_date, …])

Instrument definition for equity option

EqSynthetic(underlier, expiry[, currency, …])

Instrument definition for equity synthetics

EqVarianceSwap([underlier, underlier_type, …])

Instrument definition for equity variance swap

Forward([currency, expiration_date, …])

Forward cash payment

FXBinary([pair, buy_sell, option_type, …])

Object representation of a FX binary option

FXForward([pair, settlement_date, …])

Object representation of an FX forward

FXMultiCrossBinary([buy_sell, legs, …])

Object representation of an FX multi-cross binary

FXMultiCrossBinaryLeg([pair, option_type, …])

Object representation of a single leg of a multi-cross binary option

FXOption([pair, buy_sell, option_type, …])

Object representation of an FX option

FXVolatilitySwap([pair, buy_sell, …])

Object representation of an FX Vol Swap

InflationSwap([pay_or_receive, …])

A vanilla inflation swap of fixed vs floating cashflows adjusted to an inflation rate

IRBasisSwap([termination_date, …])

A single currency exchange of cashflows from different interest rate indices

IRCap([termination_date, notional_currency, …])

Object representation of an interest rate cap

IRCMSOption([cap_floor, termination_date, …])

Object representation of a constant maturity option (cap, floor, straddle)

IRCMSOptionStrip([cap_floor, …])

Object representation of a constant maturity option strip (cap, floor, straddle)

IRCMSSpreadOption([cap_floor, …])

Object representation of a constant maturity spread option (cap, floor, straddle)

IRCMSSpreadOptionStrip([cap_floor, …])

Object representation of a constant maturity spread option strip (cap, floor, straddle)

IRFloor([termination_date, …])

Object representation of an interest rate floor

IRSwap([pay_or_receive, termination_date, …])

A vanilla interest rate swap of fixed vs floating cashflows

IRSwaption([pay_or_receive, …])

Object representation of a swaption

IRXccySwap([termination_date, …])

An exchange of cashflows from different interest rate indices

IRXccySwapFixFix([termination_date, …])

An exchange of fixed cashflows in different currencies

IRXccySwapFixFlt([pay_or_receive, …])

An exchange of fixed vs floating cashflows in different currencies

Security([ticker, bbid, ric, isin, cusip, …])

A security, specified by a well-known identifier

================================================ FILE: docs/_build/html/market.html ================================================ Market Package — gs_quant 0.1 documentation

Market Package

PricingContext([pricing_date, …])

A context for controlling pricing and market data behaviour

HistoricalPricingContext([start, end, …])

A context for producing valuations over multiple dates

Securities

Asset(id_, asset_class, name[, exchange])

AssetClass

Asset classification of security.

AssetIdentifier

Asset type enumeration

AssetType

Asset type enumeration

Index(id_, asset_class, name[, exchange])

Index Asset

Stock(id_, name[, exchange])

Base Security Type

SecurityMaster

Security Master

================================================ FILE: docs/_build/html/markets.html ================================================ Markets Package — gs_quant 0.1 documentation

Markets Package

PricingContext([pricing_date, …])

A context for controlling pricing and market data behaviour

HistoricalPricingContext([start, end, …])

A context for producing valuations over multiple dates

Portfolio

Portfolio([priceables, name])

A collection of instruments

Securities

Asset(id_, asset_class, name[, exchange, …])

AssetClass(value)

Asset classification of security.

AssetIdentifier(value)

Asset type enumeration

AssetType(value)

Asset type enumeration

Index(id_, asset_class, name[, exchange, …])

Index Asset

Stock(id_, name[, exchange, currency, entity])

Base Security Type

SecurityMaster()

Security Master

================================================ FILE: docs/_build/html/models.html ================================================ Models Package — gs_quant 0.1 documentation
================================================ FILE: docs/_build/html/risk.html ================================================ Risk Package — gs_quant 0.1 documentation

Risk Package

Functions

aggregate_risk(results, threshold=None)[source]

Combine the results of multiple InstrumentBase.calc() calls, into a single result

Parameters
  • results (Iterable[Union[DataFrameWithInfo, Future]]) – An iterable of Dataframes and/or Futures (returned by InstrumentBase.calc())

  • threshold (Optional[float]) – exclude values whose absolute value falls below this threshold

Return type

DataFrame

Returns

A Dataframe with the aggregated results

Examples

>>> from gs_quant.instrument import IRCap, IRFloor
>>> from gs_quant.markets import PricingContext
>>> from gs_quant.risk import IRDelta, IRVega
>>>
>>> cap = IRCap('5y', 'GBP')
>>> floor = IRFloor('5y', 'GBP')
>>> instruments = (cap, floor)
>>>
>>> with PricingContext():
>>>     delta_f = [inst.calc(IRDelta) for inst in instruments]
>>>     vega_f = [inst.calc(IRVega) for inst in (cap, floor)]
>>>
>>> delta = aggregate_risk(delta_f, threshold=0.1)
>>> vega = aggregate_risk(vega_f)

delta_f and vega_f are lists of futures, where the result will be a Dataframe delta and vega are Dataframes, representing the merged risk of the individual instruments

subtract_risk(left, right)[source]

Subtract bucketed risk. Dimensions must be identical

Parameters
  • left (DataFrameWithInfo) – Results to substract from

  • right (DataFrameWithInfo) – Results to substract

Examples

>>> from gs_quant.datetime.date import business_day_offset
>>> from gs_quant.instrument IRSwap
>>> from gs_quant.markets import PricingContext
>>> from gs_quant.risk import IRDelta
>>> import datetime as dt
>>>
>>> ir_swap = IRSwap('Pay', '10y', 'USD')
>>> delta_today = ir_swap.calc(IRDelta)
>>>
>>> with PricingContext(pricing_date=business_day_offset(dt.date.today(), -1, roll='preceding')):
>>>     delta_yday_f = ir_swap.calc(IRDelta)
>>>
>>> delta_diff = subtract_risk(delta_today, delta_yday_f.result())
Return type

DataFrame

sort_risk(df, by=('date', 'time', 'mkt_type', 'mkt_asset', 'mkt_class', 'mkt_point'))[source]

Sort bucketed risk

Parameters
  • df (DataFrame) – Input Dataframe

  • by (Tuple[str, …]) – Columns to sort by

Return type

DataFrame

Returns

A sorted Dataframe

Measures

DollarPrice = DollarPrice

Present value in USD

Price = Price

Present value in local currency

ForwardPrice = ForwardPrice

Forward price

Theta = Theta

1 day Theta

BaseCPI = BaseCPI

Base CPI level

CommodDelta = CommodDelta

Commodity Delta

CommodTheta = CommodTheta

Commodity Theta

CommodVega = CommodVega

Commodity Vega

EqDelta = EqDelta

Equity Delta

EqGamma = EqGamma

Equity Gamma

EqVega = EqVega

Equity Vega

EqSpot = EqSpot

Equity Spot Level

EqAnnualImpliedVol = EqAnnualImpliedVol

Equity Annual Implied Volatility (%)

FairVarStrike = FairVarStrike

Fair Variance Strike Value of a Variance Swap

FairVolStrike = FairVolStrike

Fair Volatility Strike Value of a Variance Swap

FXDelta = FXDelta

FX Delta

FXGamma = FXGamma

FX Gamma

FXVega = FXVega

FX Vega

FXSpot = FXSpot

FX Spot Rate

InflationDelta = InflationDelta

Inflation Delta

InflationDeltaParallel = InflationDeltaParallel

Inflation Parallel Delta

InflationDeltaParallelLocalCcy = InflationDeltaParallelLocalCcy

Inflation Parallel Delta (Local Ccy)

IRBasis = IRBasis

Interest Rate Basis

IRDelta = IRDelta

Interest Rate Delta

IRDeltaParallel = IRDeltaParallel

Interest Rate Parallel Delta

IRDeltaLocalCcy = IRDeltaLocalCcy

Interest Rate Delta (Local Ccy)

IRDeltaParallelLocalCcy = IRDeltaParallelLocalCcy

Interest Rate Parallel Delta (Local Ccy)

IRXccyDelta = IRXccyDelta

Cross-ccy Delta

IRXccyDeltaParallel = IRXccyDeltaParallel

Cross-ccy Parallel Delta

IRXccyDeltaParallelLocalCurrency = IRXccyDeltaParallelLocalCurrency

Cross-ccy Parallel Delta (Local Ccy)

IRGammaParallel = IRGammaParallel

Interest Rate Parallel Gamma

IRGammaParallelLocalCcy = IRGammaParallelLocalCcy

Interest Rate Parallel Gamma (Local Ccy)

IRVega = IRVega

Interest Rate Vega

IRVegaParallel = IRVegaParallel

Interest Rate Parallel Vega

IRVegaLocalCcy = IRVegaLocalCcy

Interest Rate Vega (Local Ccy)

IRVegaParallelLocalCcy = IRVegaParallelLocalCcy

Interest Rate Parallel Vega (Local Ccy)

IRAnnualImpliedVol = IRAnnualImpliedVol

Interest Rate Annual Implied Volatility (%)

IRAnnualATMImpliedVol = IRAnnualATMImpliedVol

Interest Rate Annual Implied At-The-Money Volatility (%)

IRDailyImpliedVol = IRDailyImpliedVol

Interest Rate Daily Implied Volatility (bps)

IRSpotRate = IRSpotRate

At-The-Money Spot Rate (%)

IRFwdRate = IRFwdRate

Par Rate (%)

================================================ FILE: docs/_build/html/search.html ================================================ Search — gs_quant 0.1 documentation

Search

Please activate JavaScript to enable the search functionality.

Searching for multiple words only shows matches that contain all words.


================================================ FILE: docs/_build/html/searchindex.js ================================================ Search.setIndex({docnames:["classes/gs_quant.base.Priceable","classes/gs_quant.data.DataContext","classes/gs_quant.data.Dataset","classes/gs_quant.data.Fields","classes/gs_quant.instrument.CommodOTCSwap","classes/gs_quant.instrument.EqCliquet","classes/gs_quant.instrument.EqForward","classes/gs_quant.instrument.EqOption","classes/gs_quant.instrument.EqSynthetic","classes/gs_quant.instrument.EqVarianceSwap","classes/gs_quant.instrument.FXBinary","classes/gs_quant.instrument.FXForward","classes/gs_quant.instrument.FXMultiCrossBinary","classes/gs_quant.instrument.FXMultiCrossBinaryLeg","classes/gs_quant.instrument.FXOption","classes/gs_quant.instrument.FXVolatilitySwap","classes/gs_quant.instrument.Forward","classes/gs_quant.instrument.IRBasisSwap","classes/gs_quant.instrument.IRCMSOption","classes/gs_quant.instrument.IRCMSOptionStrip","classes/gs_quant.instrument.IRCMSSpreadOption","classes/gs_quant.instrument.IRCMSSpreadOptionStrip","classes/gs_quant.instrument.IRCap","classes/gs_quant.instrument.IRFloor","classes/gs_quant.instrument.IRSwap","classes/gs_quant.instrument.IRSwaption","classes/gs_quant.instrument.IRXccySwap","classes/gs_quant.instrument.IRXccySwapFixFix","classes/gs_quant.instrument.IRXccySwapFixFlt","classes/gs_quant.instrument.InflationSwap","classes/gs_quant.instrument.Security","classes/gs_quant.markets.HistoricalPricingContext","classes/gs_quant.markets.PricingContext","classes/gs_quant.markets.portfolio.Portfolio","classes/gs_quant.markets.securities.Asset","classes/gs_quant.markets.securities.AssetClass","classes/gs_quant.markets.securities.AssetIdentifier","classes/gs_quant.markets.securities.AssetType","classes/gs_quant.markets.securities.Index","classes/gs_quant.markets.securities.SecurityMaster","classes/gs_quant.markets.securities.Stock","classes/gs_quant.models.epidemiology.EpidemicModel","classes/gs_quant.models.epidemiology.SEIR","classes/gs_quant.models.epidemiology.SIR","classes/gs_quant.timeseries.datetime.Window","classes/gs_quant.timeseries.statistics.LinearRegression","classes/gs_quant.timeseries.statistics.RollingLinearRegression","classes/gs_quant.timeseries.statistics.SEIRModel","classes/gs_quant.timeseries.statistics.SIRModel","data","datetime","functions/gs_quant.datetime.date.business_day_count","functions/gs_quant.datetime.date.business_day_offset","functions/gs_quant.datetime.date.date_range","functions/gs_quant.datetime.date.is_business_day","functions/gs_quant.datetime.date.prev_business_date","functions/gs_quant.datetime.point.point_sort_order","functions/gs_quant.timeseries.algebra.abs_","functions/gs_quant.timeseries.algebra.add","functions/gs_quant.timeseries.algebra.and_","functions/gs_quant.timeseries.algebra.ceil","functions/gs_quant.timeseries.algebra.divide","functions/gs_quant.timeseries.algebra.exp","functions/gs_quant.timeseries.algebra.filter_","functions/gs_quant.timeseries.algebra.floor","functions/gs_quant.timeseries.algebra.floordiv","functions/gs_quant.timeseries.algebra.if_","functions/gs_quant.timeseries.algebra.log","functions/gs_quant.timeseries.algebra.multiply","functions/gs_quant.timeseries.algebra.not_","functions/gs_quant.timeseries.algebra.or_","functions/gs_quant.timeseries.algebra.power","functions/gs_quant.timeseries.algebra.repeat","functions/gs_quant.timeseries.algebra.smooth_spikes","functions/gs_quant.timeseries.algebra.sqrt","functions/gs_quant.timeseries.algebra.subtract","functions/gs_quant.timeseries.algebra.weighted_sum","functions/gs_quant.timeseries.analysis.count","functions/gs_quant.timeseries.analysis.diff","functions/gs_quant.timeseries.analysis.first","functions/gs_quant.timeseries.analysis.lag","functions/gs_quant.timeseries.analysis.last","functions/gs_quant.timeseries.analysis.last_value","functions/gs_quant.timeseries.analysis.weighted_sum","functions/gs_quant.timeseries.backtesting.basket","functions/gs_quant.timeseries.datetime.align","functions/gs_quant.timeseries.datetime.date_range","functions/gs_quant.timeseries.datetime.day","functions/gs_quant.timeseries.datetime.interpolate","functions/gs_quant.timeseries.datetime.month","functions/gs_quant.timeseries.datetime.prepend","functions/gs_quant.timeseries.datetime.quarter","functions/gs_quant.timeseries.datetime.union","functions/gs_quant.timeseries.datetime.value","functions/gs_quant.timeseries.datetime.weekday","functions/gs_quant.timeseries.datetime.year","functions/gs_quant.timeseries.econometrics.annualize","functions/gs_quant.timeseries.econometrics.beta","functions/gs_quant.timeseries.econometrics.change","functions/gs_quant.timeseries.econometrics.correlation","functions/gs_quant.timeseries.econometrics.excess_returns_","functions/gs_quant.timeseries.econometrics.index","functions/gs_quant.timeseries.econometrics.max_drawdown","functions/gs_quant.timeseries.econometrics.prices","functions/gs_quant.timeseries.econometrics.returns","functions/gs_quant.timeseries.econometrics.sharpe_ratio","functions/gs_quant.timeseries.econometrics.volatility","functions/gs_quant.timeseries.statistics.cov","functions/gs_quant.timeseries.statistics.exponential_std","functions/gs_quant.timeseries.statistics.generate_series","functions/gs_quant.timeseries.statistics.max_","functions/gs_quant.timeseries.statistics.mean","functions/gs_quant.timeseries.statistics.median","functions/gs_quant.timeseries.statistics.min_","functions/gs_quant.timeseries.statistics.mode","functions/gs_quant.timeseries.statistics.percentile","functions/gs_quant.timeseries.statistics.percentiles","functions/gs_quant.timeseries.statistics.product","functions/gs_quant.timeseries.statistics.range_","functions/gs_quant.timeseries.statistics.std","functions/gs_quant.timeseries.statistics.sum_","functions/gs_quant.timeseries.statistics.var","functions/gs_quant.timeseries.statistics.winsorize","functions/gs_quant.timeseries.statistics.zscores","functions/gs_quant.timeseries.technicals.bollinger_bands","functions/gs_quant.timeseries.technicals.exponential_moving_average","functions/gs_quant.timeseries.technicals.exponential_spread_volatility","functions/gs_quant.timeseries.technicals.exponential_volatility","functions/gs_quant.timeseries.technicals.moving_average","functions/gs_quant.timeseries.technicals.relative_strength_index","functions/gs_quant.timeseries.technicals.smoothed_moving_average","index","instrument","markets","models","risk","timeseries"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":3,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":2,"sphinx.domains.rst":2,"sphinx.domains.std":1,"sphinx.ext.viewcode":1,sphinx:56},filenames:["classes\\gs_quant.base.Priceable.rst","classes\\gs_quant.data.DataContext.rst","classes\\gs_quant.data.Dataset.rst","classes\\gs_quant.data.Fields.rst","classes\\gs_quant.instrument.CommodOTCSwap.rst","classes\\gs_quant.instrument.EqCliquet.rst","classes\\gs_quant.instrument.EqForward.rst","classes\\gs_quant.instrument.EqOption.rst","classes\\gs_quant.instrument.EqSynthetic.rst","classes\\gs_quant.instrument.EqVarianceSwap.rst","classes\\gs_quant.instrument.FXBinary.rst","classes\\gs_quant.instrument.FXForward.rst","classes\\gs_quant.instrument.FXMultiCrossBinary.rst","classes\\gs_quant.instrument.FXMultiCrossBinaryLeg.rst","classes\\gs_quant.instrument.FXOption.rst","classes\\gs_quant.instrument.FXVolatilitySwap.rst","classes\\gs_quant.instrument.Forward.rst","classes\\gs_quant.instrument.IRBasisSwap.rst","classes\\gs_quant.instrument.IRCMSOption.rst","classes\\gs_quant.instrument.IRCMSOptionStrip.rst","classes\\gs_quant.instrument.IRCMSSpreadOption.rst","classes\\gs_quant.instrument.IRCMSSpreadOptionStrip.rst","classes\\gs_quant.instrument.IRCap.rst","classes\\gs_quant.instrument.IRFloor.rst","classes\\gs_quant.instrument.IRSwap.rst","classes\\gs_quant.instrument.IRSwaption.rst","classes\\gs_quant.instrument.IRXccySwap.rst","classes\\gs_quant.instrument.IRXccySwapFixFix.rst","classes\\gs_quant.instrument.IRXccySwapFixFlt.rst","classes\\gs_quant.instrument.InflationSwap.rst","classes\\gs_quant.instrument.Security.rst","classes\\gs_quant.markets.HistoricalPricingContext.rst","classes\\gs_quant.markets.PricingContext.rst","classes\\gs_quant.markets.portfolio.Portfolio.rst","classes\\gs_quant.markets.securities.Asset.rst","classes\\gs_quant.markets.securities.AssetClass.rst","classes\\gs_quant.markets.securities.AssetIdentifier.rst","classes\\gs_quant.markets.securities.AssetType.rst","classes\\gs_quant.markets.securities.Index.rst","classes\\gs_quant.markets.securities.SecurityMaster.rst","classes\\gs_quant.markets.securities.Stock.rst","classes\\gs_quant.models.epidemiology.EpidemicModel.rst","classes\\gs_quant.models.epidemiology.SEIR.rst","classes\\gs_quant.models.epidemiology.SIR.rst","classes\\gs_quant.timeseries.datetime.Window.rst","classes\\gs_quant.timeseries.statistics.LinearRegression.rst","classes\\gs_quant.timeseries.statistics.RollingLinearRegression.rst","classes\\gs_quant.timeseries.statistics.SEIRModel.rst","classes\\gs_quant.timeseries.statistics.SIRModel.rst","data.rst","datetime.rst","functions\\gs_quant.datetime.date.business_day_count.rst","functions\\gs_quant.datetime.date.business_day_offset.rst","functions\\gs_quant.datetime.date.date_range.rst","functions\\gs_quant.datetime.date.is_business_day.rst","functions\\gs_quant.datetime.date.prev_business_date.rst","functions\\gs_quant.datetime.point.point_sort_order.rst","functions\\gs_quant.timeseries.algebra.abs_.rst","functions\\gs_quant.timeseries.algebra.add.rst","functions\\gs_quant.timeseries.algebra.and_.rst","functions\\gs_quant.timeseries.algebra.ceil.rst","functions\\gs_quant.timeseries.algebra.divide.rst","functions\\gs_quant.timeseries.algebra.exp.rst","functions\\gs_quant.timeseries.algebra.filter_.rst","functions\\gs_quant.timeseries.algebra.floor.rst","functions\\gs_quant.timeseries.algebra.floordiv.rst","functions\\gs_quant.timeseries.algebra.if_.rst","functions\\gs_quant.timeseries.algebra.log.rst","functions\\gs_quant.timeseries.algebra.multiply.rst","functions\\gs_quant.timeseries.algebra.not_.rst","functions\\gs_quant.timeseries.algebra.or_.rst","functions\\gs_quant.timeseries.algebra.power.rst","functions\\gs_quant.timeseries.algebra.repeat.rst","functions\\gs_quant.timeseries.algebra.smooth_spikes.rst","functions\\gs_quant.timeseries.algebra.sqrt.rst","functions\\gs_quant.timeseries.algebra.subtract.rst","functions\\gs_quant.timeseries.algebra.weighted_sum.rst","functions\\gs_quant.timeseries.analysis.count.rst","functions\\gs_quant.timeseries.analysis.diff.rst","functions\\gs_quant.timeseries.analysis.first.rst","functions\\gs_quant.timeseries.analysis.lag.rst","functions\\gs_quant.timeseries.analysis.last.rst","functions\\gs_quant.timeseries.analysis.last_value.rst","functions\\gs_quant.timeseries.analysis.weighted_sum.rst","functions\\gs_quant.timeseries.backtesting.basket.rst","functions\\gs_quant.timeseries.datetime.align.rst","functions\\gs_quant.timeseries.datetime.date_range.rst","functions\\gs_quant.timeseries.datetime.day.rst","functions\\gs_quant.timeseries.datetime.interpolate.rst","functions\\gs_quant.timeseries.datetime.month.rst","functions\\gs_quant.timeseries.datetime.prepend.rst","functions\\gs_quant.timeseries.datetime.quarter.rst","functions\\gs_quant.timeseries.datetime.union.rst","functions\\gs_quant.timeseries.datetime.value.rst","functions\\gs_quant.timeseries.datetime.weekday.rst","functions\\gs_quant.timeseries.datetime.year.rst","functions\\gs_quant.timeseries.econometrics.annualize.rst","functions\\gs_quant.timeseries.econometrics.beta.rst","functions\\gs_quant.timeseries.econometrics.change.rst","functions\\gs_quant.timeseries.econometrics.correlation.rst","functions\\gs_quant.timeseries.econometrics.excess_returns_.rst","functions\\gs_quant.timeseries.econometrics.index.rst","functions\\gs_quant.timeseries.econometrics.max_drawdown.rst","functions\\gs_quant.timeseries.econometrics.prices.rst","functions\\gs_quant.timeseries.econometrics.returns.rst","functions\\gs_quant.timeseries.econometrics.sharpe_ratio.rst","functions\\gs_quant.timeseries.econometrics.volatility.rst","functions\\gs_quant.timeseries.statistics.cov.rst","functions\\gs_quant.timeseries.statistics.exponential_std.rst","functions\\gs_quant.timeseries.statistics.generate_series.rst","functions\\gs_quant.timeseries.statistics.max_.rst","functions\\gs_quant.timeseries.statistics.mean.rst","functions\\gs_quant.timeseries.statistics.median.rst","functions\\gs_quant.timeseries.statistics.min_.rst","functions\\gs_quant.timeseries.statistics.mode.rst","functions\\gs_quant.timeseries.statistics.percentile.rst","functions\\gs_quant.timeseries.statistics.percentiles.rst","functions\\gs_quant.timeseries.statistics.product.rst","functions\\gs_quant.timeseries.statistics.range_.rst","functions\\gs_quant.timeseries.statistics.std.rst","functions\\gs_quant.timeseries.statistics.sum_.rst","functions\\gs_quant.timeseries.statistics.var.rst","functions\\gs_quant.timeseries.statistics.winsorize.rst","functions\\gs_quant.timeseries.statistics.zscores.rst","functions\\gs_quant.timeseries.technicals.bollinger_bands.rst","functions\\gs_quant.timeseries.technicals.exponential_moving_average.rst","functions\\gs_quant.timeseries.technicals.exponential_spread_volatility.rst","functions\\gs_quant.timeseries.technicals.exponential_volatility.rst","functions\\gs_quant.timeseries.technicals.moving_average.rst","functions\\gs_quant.timeseries.technicals.relative_strength_index.rst","functions\\gs_quant.timeseries.technicals.smoothed_moving_average.rst","index.rst","instrument.rst","markets.rst","models.rst","risk.rst","timeseries.rst"],objects:{"gs_quant.base":{Priceable:[0,0,1,""]},"gs_quant.base.Priceable":{__init__:[0,1,1,""]},"gs_quant.data":{DataContext:[1,0,1,""],Dataset:[2,0,1,""],Fields:[3,0,1,""]},"gs_quant.data.DataContext":{__init__:[1,1,1,""]},"gs_quant.data.Dataset":{__init__:[2,1,1,""]},"gs_quant.data.Fields":{__init__:[3,1,1,""]},"gs_quant.datetime.date":{business_day_count:[51,2,1,""],business_day_offset:[52,2,1,""],date_range:[53,2,1,""],is_business_day:[54,2,1,""],prev_business_date:[55,2,1,""]},"gs_quant.datetime.point":{point_sort_order:[56,2,1,""]},"gs_quant.instrument":{CommodOTCSwap:[4,0,1,""],EqCliquet:[5,0,1,""],EqForward:[6,0,1,""],EqOption:[7,0,1,""],EqSynthetic:[8,0,1,""],EqVarianceSwap:[9,0,1,""],FXBinary:[10,0,1,""],FXForward:[11,0,1,""],FXMultiCrossBinary:[12,0,1,""],FXMultiCrossBinaryLeg:[13,0,1,""],FXOption:[14,0,1,""],FXVolatilitySwap:[15,0,1,""],Forward:[16,0,1,""],IRBasisSwap:[17,0,1,""],IRCMSOption:[18,0,1,""],IRCMSOptionStrip:[19,0,1,""],IRCMSSpreadOption:[20,0,1,""],IRCMSSpreadOptionStrip:[21,0,1,""],IRCap:[22,0,1,""],IRFloor:[23,0,1,""],IRSwap:[24,0,1,""],IRSwaption:[25,0,1,""],IRXccySwap:[26,0,1,""],IRXccySwapFixFix:[27,0,1,""],IRXccySwapFixFlt:[28,0,1,""],InflationSwap:[29,0,1,""],Security:[30,0,1,""]},"gs_quant.instrument.CommodOTCSwap":{end:[4,3,1,""],instrument_quantity:[4,3,1,""],legs:[4,3,1,""],number_of_periods:[4,3,1,""],provider:[4,3,1,""],quantity:[4,3,1,""],quantity_period:[4,3,1,""],quantity_unit:[4,3,1,""],resolution_key:[4,3,1,""],settlement:[4,3,1,""],start:[4,3,1,""],strategy:[4,3,1,""],unresolved:[4,3,1,""]},"gs_quant.instrument.EqCliquet":{currency:[5,3,1,""],expiration_date:[5,3,1,""],first_valuation_date:[5,3,1,""],global_cap:[5,3,1,""],global_floor:[5,3,1,""],instrument_quantity:[5,3,1,""],last_valuation_date:[5,3,1,""],notional_amount:[5,3,1,""],payment_frequency:[5,3,1,""],provider:[5,3,1,""],resolution_key:[5,3,1,""],return_style:[5,3,1,""],return_type:[5,3,1,""],strike_price:[5,3,1,""],underlier:[5,3,1,""],underlier_type:[5,3,1,""],unresolved:[5,3,1,""],valuation_period:[5,3,1,""]},"gs_quant.instrument.EqForward":{expiration_date:[6,3,1,""],forward_price:[6,3,1,""],instrument_quantity:[6,3,1,""],number_of_shares:[6,3,1,""],provider:[6,3,1,""],resolution_key:[6,3,1,""],underlier:[6,3,1,""],underlier_type:[6,3,1,""],unresolved:[6,3,1,""]},"gs_quant.instrument.EqOption":{buy_sell:[7,3,1,""],exchange:[7,3,1,""],expiration_date:[7,3,1,""],instrument_quantity:[7,3,1,""],method_of_settlement:[7,3,1,""],multiplier:[7,3,1,""],number_of_options:[7,3,1,""],option_style:[7,3,1,""],option_type:[7,3,1,""],premium:[7,3,1,""],premium_currency:[7,3,1,""],premium_payment_date:[7,3,1,""],provider:[7,3,1,""],resolution_key:[7,3,1,""],settlement_currency:[7,3,1,""],settlement_date:[7,3,1,""],strike_price:[7,3,1,""],trade_as:[7,3,1,""],underlier:[7,3,1,""],underlier_type:[7,3,1,""],unresolved:[7,3,1,""],valuation_time:[7,3,1,""]},"gs_quant.instrument.EqSynthetic":{buy_sell:[8,3,1,""],currency:[8,3,1,""],effective_date:[8,3,1,""],expiry:[8,3,1,""],instrument_quantity:[8,3,1,""],num_of_underlyers:[8,3,1,""],provider:[8,3,1,""],resolution_key:[8,3,1,""],swap_type:[8,3,1,""],underlier:[8,3,1,""],underlier_type:[8,3,1,""],unresolved:[8,3,1,""]},"gs_quant.instrument.EqVarianceSwap":{expiration_date:[9,3,1,""],instrument_quantity:[9,3,1,""],premium:[9,3,1,""],provider:[9,3,1,""],resolution_key:[9,3,1,""],settlement_date:[9,3,1,""],strike_price:[9,3,1,""],underlier:[9,3,1,""],underlier_type:[9,3,1,""],unresolved:[9,3,1,""],variance_cap:[9,3,1,""]},"gs_quant.instrument.FXBinary":{buy_sell:[10,3,1,""],expiration_date:[10,3,1,""],expiration_time:[10,3,1,""],fixing_source:[10,3,1,""],instrument_quantity:[10,3,1,""],notional_amount:[10,3,1,""],notional_currency:[10,3,1,""],option_type:[10,3,1,""],pair:[10,3,1,""],premium:[10,3,1,""],premium_currency:[10,3,1,""],premium_payment_date:[10,3,1,""],provider:[10,3,1,""],resolution_key:[10,3,1,""],settlement_date:[10,3,1,""],strike_price:[10,3,1,""],unresolved:[10,3,1,""]},"gs_quant.instrument.FXForward":{forward_rate:[11,3,1,""],instrument_quantity:[11,3,1,""],notional_amount:[11,3,1,""],pair:[11,3,1,""],provider:[11,3,1,""],resolution_key:[11,3,1,""],settlement_date:[11,3,1,""],unresolved:[11,3,1,""]},"gs_quant.instrument.FXMultiCrossBinary":{buy_sell:[12,3,1,""],expiration_date:[12,3,1,""],expiration_time:[12,3,1,""],instrument_quantity:[12,3,1,""],legs:[12,3,1,""],notional_amount:[12,3,1,""],notional_currency:[12,3,1,""],premium:[12,3,1,""],premium_currency:[12,3,1,""],premium_payment_date:[12,3,1,""],provider:[12,3,1,""],resolution_key:[12,3,1,""],settlement_date:[12,3,1,""],unresolved:[12,3,1,""]},"gs_quant.instrument.FXMultiCrossBinaryLeg":{fixing_source:[13,3,1,""],instrument_quantity:[13,3,1,""],option_type:[13,3,1,""],pair:[13,3,1,""],provider:[13,3,1,""],resolution_key:[13,3,1,""],strike_price:[13,3,1,""],unresolved:[13,3,1,""]},"gs_quant.instrument.FXOption":{buy_sell:[14,3,1,""],expiration_date:[14,3,1,""],expiration_time:[14,3,1,""],instrument_quantity:[14,3,1,""],method_of_settlement:[14,3,1,""],notional_amount:[14,3,1,""],notional_amount_other_currency:[14,3,1,""],notional_currency:[14,3,1,""],option_type:[14,3,1,""],pair:[14,3,1,""],premium:[14,3,1,""],premium_currency:[14,3,1,""],premium_payment_date:[14,3,1,""],provider:[14,3,1,""],resolution_key:[14,3,1,""],settlement_currency:[14,3,1,""],settlement_date:[14,3,1,""],settlement_rate_option:[14,3,1,""],strike_price:[14,3,1,""],unresolved:[14,3,1,""]},"gs_quant.instrument.FXVolatilitySwap":{annualization_factor:[15,3,1,""],buy_sell:[15,3,1,""],calculate_mean_return:[15,3,1,""],first_fixing_date:[15,3,1,""],fixing_frequency:[15,3,1,""],fixing_source:[15,3,1,""],instrument_quantity:[15,3,1,""],last_fixing_date:[15,3,1,""],notional_amount:[15,3,1,""],notional_currency:[15,3,1,""],pair:[15,3,1,""],provider:[15,3,1,""],resolution_key:[15,3,1,""],settlement_date:[15,3,1,""],strike_vol:[15,3,1,""],unresolved:[15,3,1,""]},"gs_quant.instrument.Forward":{currency:[16,3,1,""],expiration_date:[16,3,1,""],instrument_quantity:[16,3,1,""],notional_amount:[16,3,1,""],provider:[16,3,1,""],resolution_key:[16,3,1,""],unresolved:[16,3,1,""]},"gs_quant.instrument.IRBasisSwap":{clearing_house:[17,3,1,""],effective_date:[17,3,1,""],fee:[17,3,1,""],fee_currency:[17,3,1,""],fee_payment_date:[17,3,1,""],instrument_quantity:[17,3,1,""],notional_amount:[17,3,1,""],notional_currency:[17,3,1,""],payer_business_day_convention:[17,3,1,""],payer_day_count_fraction:[17,3,1,""],payer_designated_maturity:[17,3,1,""],payer_frequency:[17,3,1,""],payer_rate_option:[17,3,1,""],payer_spread:[17,3,1,""],principal_exchange:[17,3,1,""],provider:[17,3,1,""],receiver_business_day_convention:[17,3,1,""],receiver_day_count_fraction:[17,3,1,""],receiver_designated_maturity:[17,3,1,""],receiver_frequency:[17,3,1,""],receiver_rate_option:[17,3,1,""],receiver_spread:[17,3,1,""],resolution_key:[17,3,1,""],termination_date:[17,3,1,""],unresolved:[17,3,1,""]},"gs_quant.instrument.IRCMSOption":{buy_sell:[18,3,1,""],cap_floor:[18,3,1,""],effective_date:[18,3,1,""],fee:[18,3,1,""],fee_currency:[18,3,1,""],fee_payment_date:[18,3,1,""],index:[18,3,1,""],instrument_quantity:[18,3,1,""],multiplier:[18,3,1,""],notional_amount:[18,3,1,""],notional_currency:[18,3,1,""],premium:[18,3,1,""],premium_payment_date:[18,3,1,""],provider:[18,3,1,""],resolution_key:[18,3,1,""],strike:[18,3,1,""],termination_date:[18,3,1,""],unresolved:[18,3,1,""]},"gs_quant.instrument.IRCMSOptionStrip":{buy_sell:[19,3,1,""],cap_floor:[19,3,1,""],effective_date:[19,3,1,""],fee:[19,3,1,""],fee_currency:[19,3,1,""],fee_payment_date:[19,3,1,""],floating_rate_business_day_convention:[19,3,1,""],floating_rate_day_count_fraction:[19,3,1,""],floating_rate_frequency:[19,3,1,""],index:[19,3,1,""],instrument_quantity:[19,3,1,""],multiplier:[19,3,1,""],notional_amount:[19,3,1,""],notional_currency:[19,3,1,""],premium:[19,3,1,""],premium_payment_date:[19,3,1,""],provider:[19,3,1,""],reset_delay:[19,3,1,""],resolution_key:[19,3,1,""],strike:[19,3,1,""],termination_date:[19,3,1,""],unresolved:[19,3,1,""]},"gs_quant.instrument.IRCMSSpreadOption":{buy_sell:[20,3,1,""],cap_floor:[20,3,1,""],effective_date:[20,3,1,""],fee:[20,3,1,""],fee_currency:[20,3,1,""],fee_payment_date:[20,3,1,""],index1_tenor:[20,3,1,""],index2_tenor:[20,3,1,""],instrument_quantity:[20,3,1,""],notional_amount:[20,3,1,""],notional_currency:[20,3,1,""],premium:[20,3,1,""],premium_payment_date:[20,3,1,""],provider:[20,3,1,""],resolution_key:[20,3,1,""],strike:[20,3,1,""],termination_date:[20,3,1,""],unresolved:[20,3,1,""]},"gs_quant.instrument.IRCMSSpreadOptionStrip":{buy_sell:[21,3,1,""],cap_floor:[21,3,1,""],effective_date:[21,3,1,""],fee:[21,3,1,""],fee_currency:[21,3,1,""],fee_payment_date:[21,3,1,""],floating_rate_business_day_convention:[21,3,1,""],floating_rate_day_count_fraction:[21,3,1,""],floating_rate_frequency:[21,3,1,""],index1:[21,3,1,""],index2:[21,3,1,""],instrument_quantity:[21,3,1,""],notional_amount:[21,3,1,""],notional_currency:[21,3,1,""],premium:[21,3,1,""],premium_payment_date:[21,3,1,""],provider:[21,3,1,""],reset_delay:[21,3,1,""],resolution_key:[21,3,1,""],strike:[21,3,1,""],termination_date:[21,3,1,""],unresolved:[21,3,1,""]},"gs_quant.instrument.IRCap":{cap_rate:[22,3,1,""],effective_date:[22,3,1,""],fee:[22,3,1,""],fee_currency:[22,3,1,""],fee_payment_date:[22,3,1,""],floating_rate_business_day_convention:[22,3,1,""],floating_rate_day_count_fraction:[22,3,1,""],floating_rate_designated_maturity:[22,3,1,""],floating_rate_frequency:[22,3,1,""],floating_rate_option:[22,3,1,""],instrument_quantity:[22,3,1,""],notional_amount:[22,3,1,""],notional_currency:[22,3,1,""],premium:[22,3,1,""],premium_payment_date:[22,3,1,""],provider:[22,3,1,""],resolution_key:[22,3,1,""],termination_date:[22,3,1,""],unresolved:[22,3,1,""]},"gs_quant.instrument.IRFloor":{effective_date:[23,3,1,""],fee:[23,3,1,""],fee_currency:[23,3,1,""],fee_payment_date:[23,3,1,""],floating_rate_business_day_convention:[23,3,1,""],floating_rate_day_count_fraction:[23,3,1,""],floating_rate_designated_maturity:[23,3,1,""],floating_rate_frequency:[23,3,1,""],floating_rate_option:[23,3,1,""],floor_rate:[23,3,1,""],instrument_quantity:[23,3,1,""],notional_amount:[23,3,1,""],notional_currency:[23,3,1,""],premium:[23,3,1,""],premium_payment_date:[23,3,1,""],provider:[23,3,1,""],resolution_key:[23,3,1,""],termination_date:[23,3,1,""],unresolved:[23,3,1,""]},"gs_quant.instrument.IRSwap":{clearing_house:[24,3,1,""],effective_date:[24,3,1,""],fee:[24,3,1,""],fee_currency:[24,3,1,""],fee_payment_date:[24,3,1,""],fixed_first_stub:[24,3,1,""],fixed_holidays:[24,3,1,""],fixed_last_stub:[24,3,1,""],fixed_rate:[24,3,1,""],fixed_rate_business_day_convention:[24,3,1,""],fixed_rate_day_count_fraction:[24,3,1,""],fixed_rate_frequency:[24,3,1,""],floating_first_stub:[24,3,1,""],floating_holidays:[24,3,1,""],floating_last_stub:[24,3,1,""],floating_rate_business_day_convention:[24,3,1,""],floating_rate_day_count_fraction:[24,3,1,""],floating_rate_designated_maturity:[24,3,1,""],floating_rate_for_the_initial_calculation_period:[24,3,1,""],floating_rate_frequency:[24,3,1,""],floating_rate_option:[24,3,1,""],floating_rate_spread:[24,3,1,""],instrument_quantity:[24,3,1,""],notional_amount:[24,3,1,""],notional_currency:[24,3,1,""],pay_or_receive:[24,3,1,""],principal_exchange:[24,3,1,""],provider:[24,3,1,""],resolution_key:[24,3,1,""],roll_convention:[24,3,1,""],termination_date:[24,3,1,""],unresolved:[24,3,1,""]},"gs_quant.instrument.IRSwaption":{buy_sell:[25,3,1,""],clearing_house:[25,3,1,""],effective_date:[25,3,1,""],expiration_date:[25,3,1,""],fee:[25,3,1,""],fee_currency:[25,3,1,""],fee_payment_date:[25,3,1,""],fixed_rate_business_day_convention:[25,3,1,""],fixed_rate_day_count_fraction:[25,3,1,""],fixed_rate_frequency:[25,3,1,""],floating_rate_business_day_convention:[25,3,1,""],floating_rate_day_count_fraction:[25,3,1,""],floating_rate_designated_maturity:[25,3,1,""],floating_rate_frequency:[25,3,1,""],floating_rate_option:[25,3,1,""],floating_rate_spread:[25,3,1,""],instrument_quantity:[25,3,1,""],notional_amount:[25,3,1,""],notional_currency:[25,3,1,""],pay_or_receive:[25,3,1,""],premium:[25,3,1,""],premium_payment_date:[25,3,1,""],provider:[25,3,1,""],resolution_key:[25,3,1,""],settlement:[25,3,1,""],strike:[25,3,1,""],termination_date:[25,3,1,""],unresolved:[25,3,1,""]},"gs_quant.instrument.IRXccySwap":{effective_date:[26,3,1,""],fee:[26,3,1,""],fee_currency:[26,3,1,""],fee_payment_date:[26,3,1,""],initial_fx_rate:[26,3,1,""],instrument_quantity:[26,3,1,""],notional_amount:[26,3,1,""],notional_reset_side:[26,3,1,""],payer_business_day_convention:[26,3,1,""],payer_currency:[26,3,1,""],payer_day_count_fraction:[26,3,1,""],payer_designated_maturity:[26,3,1,""],payer_first_stub:[26,3,1,""],payer_frequency:[26,3,1,""],payer_holidays:[26,3,1,""],payer_last_stub:[26,3,1,""],payer_rate_option:[26,3,1,""],payer_spread:[26,3,1,""],principal_exchange:[26,3,1,""],provider:[26,3,1,""],receiver_business_day_convention:[26,3,1,""],receiver_currency:[26,3,1,""],receiver_day_count_fraction:[26,3,1,""],receiver_designated_maturity:[26,3,1,""],receiver_first_stub:[26,3,1,""],receiver_frequency:[26,3,1,""],receiver_holidays:[26,3,1,""],receiver_last_stub:[26,3,1,""],receiver_rate_option:[26,3,1,""],receiver_spread:[26,3,1,""],resolution_key:[26,3,1,""],termination_date:[26,3,1,""],unresolved:[26,3,1,""]},"gs_quant.instrument.IRXccySwapFixFix":{effective_date:[27,3,1,""],fee:[27,3,1,""],fee_currency:[27,3,1,""],fee_payment_date:[27,3,1,""],instrument_quantity:[27,3,1,""],notional_amount:[27,3,1,""],payer_business_day_convention:[27,3,1,""],payer_currency:[27,3,1,""],payer_day_count_fraction:[27,3,1,""],payer_frequency:[27,3,1,""],payer_rate:[27,3,1,""],principal_exchange:[27,3,1,""],provider:[27,3,1,""],receiver_business_day_convention:[27,3,1,""],receiver_currency:[27,3,1,""],receiver_day_count_fraction:[27,3,1,""],receiver_frequency:[27,3,1,""],receiver_notional_amount:[27,3,1,""],receiver_rate:[27,3,1,""],resolution_key:[27,3,1,""],termination_date:[27,3,1,""],unresolved:[27,3,1,""]},"gs_quant.instrument.IRXccySwapFixFlt":{effective_date:[28,3,1,""],fee:[28,3,1,""],fee_currency:[28,3,1,""],fee_payment_date:[28,3,1,""],fixed_first_stub:[28,3,1,""],fixed_holidays:[28,3,1,""],fixed_last_stub:[28,3,1,""],fixed_rate:[28,3,1,""],fixed_rate_business_day_convention:[28,3,1,""],fixed_rate_currency:[28,3,1,""],fixed_rate_day_count_fraction:[28,3,1,""],fixed_rate_frequency:[28,3,1,""],floating_first_stub:[28,3,1,""],floating_holidays:[28,3,1,""],floating_last_stub:[28,3,1,""],floating_rate_business_day_convention:[28,3,1,""],floating_rate_currency:[28,3,1,""],floating_rate_day_count_fraction:[28,3,1,""],floating_rate_designated_maturity:[28,3,1,""],floating_rate_for_the_initial_calculation_period:[28,3,1,""],floating_rate_frequency:[28,3,1,""],floating_rate_option:[28,3,1,""],floating_rate_spread:[28,3,1,""],instrument_quantity:[28,3,1,""],notional_amount:[28,3,1,""],pay_or_receive:[28,3,1,""],principal_exchange:[28,3,1,""],provider:[28,3,1,""],resolution_key:[28,3,1,""],termination_date:[28,3,1,""],unresolved:[28,3,1,""]},"gs_quant.instrument.InflationSwap":{base_cpi:[29,3,1,""],clearing_house:[29,3,1,""],effective_date:[29,3,1,""],fee:[29,3,1,""],fixed_rate:[29,3,1,""],fixed_rate_business_day_convention:[29,3,1,""],floating_rate_business_day_convention:[29,3,1,""],index:[29,3,1,""],instrument_quantity:[29,3,1,""],notional_amount:[29,3,1,""],notional_currency:[29,3,1,""],pay_or_receive:[29,3,1,""],provider:[29,3,1,""],resolution_key:[29,3,1,""],termination_date:[29,3,1,""],unresolved:[29,3,1,""]},"gs_quant.instrument.Security":{bbid:[30,3,1,""],bbid_equivalent:[30,3,1,""],bcid:[30,3,1,""],cid:[30,3,1,""],cm_id:[30,3,1,""],cross:[30,3,1,""],cusip:[30,3,1,""],delisted:[30,3,1,""],dollar_cross:[30,3,1,""],eid:[30,3,1,""],em_id:[30,3,1,""],exchange_code:[30,3,1,""],gsid:[30,3,1,""],gsid_equivalent:[30,3,1,""],gsideid:[30,3,1,""],gsn:[30,3,1,""],gss:[30,3,1,""],instrument_quantity:[30,3,1,""],isin:[30,3,1,""],jsn:[30,3,1,""],lms_id:[30,3,1,""],mdapi:[30,3,1,""],mdapi_class:[30,3,1,""],mic:[30,3,1,""],mq_symbol:[30,3,1,""],pl_id:[30,3,1,""],plot_id:[30,3,1,""],pnode_id:[30,3,1,""],primary_country_ric:[30,3,1,""],prime_id:[30,3,1,""],provider:[30,3,1,""],ps_id:[30,3,1,""],rcic:[30,3,1,""],resolution_key:[30,3,1,""],ric:[30,3,1,""],sec_name:[30,3,1,""],sedol:[30,3,1,""],sf_id:[30,3,1,""],simon_id:[30,3,1,""],tdapi:[30,3,1,""],ticker:[30,3,1,""],unresolved:[30,3,1,""],valoren:[30,3,1,""],wi_id:[30,3,1,""],wpk:[30,3,1,""]},"gs_quant.markets":{HistoricalPricingContext:[31,0,1,""],PricingContext:[32,0,1,""]},"gs_quant.markets.HistoricalPricingContext":{__init__:[31,1,1,""]},"gs_quant.markets.PricingContext":{__init__:[32,1,1,""]},"gs_quant.markets.portfolio":{Portfolio:[33,0,1,""]},"gs_quant.markets.portfolio.Portfolio":{__init__:[33,1,1,""]},"gs_quant.markets.securities":{Asset:[34,0,1,""],AssetClass:[35,0,1,""],AssetIdentifier:[36,0,1,""],AssetType:[37,0,1,""],Index:[38,0,1,""],SecurityMaster:[39,0,1,""],Stock:[40,0,1,""]},"gs_quant.markets.securities.Asset":{__init__:[34,1,1,""]},"gs_quant.markets.securities.AssetClass":{__init__:[35,1,1,""]},"gs_quant.markets.securities.AssetIdentifier":{__init__:[36,1,1,""]},"gs_quant.markets.securities.AssetType":{__init__:[37,1,1,""]},"gs_quant.markets.securities.Index":{__init__:[38,1,1,""]},"gs_quant.markets.securities.SecurityMaster":{__init__:[39,1,1,""]},"gs_quant.markets.securities.Stock":{__init__:[40,1,1,""]},"gs_quant.models.epidemiology":{EpidemicModel:[41,0,1,""],SEIR:[42,0,1,""],SIR:[43,0,1,""]},"gs_quant.models.epidemiology.EpidemicModel":{__init__:[41,1,1,""]},"gs_quant.models.epidemiology.SEIR":{__init__:[42,1,1,""]},"gs_quant.models.epidemiology.SIR":{__init__:[43,1,1,""]},"gs_quant.risk":{BaseCPI:[135,4,1,""],CommodDelta:[135,4,1,""],CommodTheta:[135,4,1,""],CommodVega:[135,4,1,""],DollarPrice:[135,4,1,""],EqAnnualImpliedVol:[135,4,1,""],EqDelta:[135,4,1,""],EqGamma:[135,4,1,""],EqSpot:[135,4,1,""],EqVega:[135,4,1,""],FXDelta:[135,4,1,""],FXGamma:[135,4,1,""],FXSpot:[135,4,1,""],FXVega:[135,4,1,""],FairVarStrike:[135,4,1,""],FairVolStrike:[135,4,1,""],ForwardPrice:[135,4,1,""],IRAnnualATMImpliedVol:[135,4,1,""],IRAnnualImpliedVol:[135,4,1,""],IRBasis:[135,4,1,""],IRDailyImpliedVol:[135,4,1,""],IRDelta:[135,4,1,""],IRDeltaLocalCcy:[135,4,1,""],IRDeltaParallel:[135,4,1,""],IRDeltaParallelLocalCcy:[135,4,1,""],IRFwdRate:[135,4,1,""],IRGammaParallel:[135,4,1,""],IRGammaParallelLocalCcy:[135,4,1,""],IRSpotRate:[135,4,1,""],IRVega:[135,4,1,""],IRVegaLocalCcy:[135,4,1,""],IRVegaParallel:[135,4,1,""],IRVegaParallelLocalCcy:[135,4,1,""],IRXccyDelta:[135,4,1,""],IRXccyDeltaParallel:[135,4,1,""],IRXccyDeltaParallelLocalCurrency:[135,4,1,""],InflationDelta:[135,4,1,""],InflationDeltaParallel:[135,4,1,""],InflationDeltaParallelLocalCcy:[135,4,1,""],Price:[135,4,1,""],Theta:[135,4,1,""],aggregate_risk:[135,2,1,""],sort_risk:[135,2,1,""],subtract_risk:[135,2,1,""]},"gs_quant.timeseries.algebra":{abs_:[57,2,1,""],add:[58,2,1,""],and_:[59,2,1,""],ceil:[60,2,1,""],divide:[61,2,1,""],exp:[62,2,1,""],filter_:[63,2,1,""],floor:[64,2,1,""],floordiv:[65,2,1,""],if_:[66,2,1,""],log:[67,2,1,""],multiply:[68,2,1,""],not_:[69,2,1,""],or_:[70,2,1,""],power:[71,2,1,""],repeat:[72,2,1,""],smooth_spikes:[73,2,1,""],sqrt:[74,2,1,""],subtract:[75,2,1,""],weighted_sum:[76,2,1,""]},"gs_quant.timeseries.analysis":{count:[77,2,1,""],diff:[78,2,1,""],first:[79,2,1,""],lag:[80,2,1,""],last:[81,2,1,""],last_value:[82,2,1,""]},"gs_quant.timeseries.backtesting":{basket:[84,2,1,""]},"gs_quant.timeseries.datetime":{Window:[44,0,1,""],align:[85,2,1,""],date_range:[86,2,1,""],day:[87,2,1,""],interpolate:[88,2,1,""],month:[89,2,1,""],prepend:[90,2,1,""],quarter:[91,2,1,""],union:[92,2,1,""],value:[93,2,1,""],weekday:[94,2,1,""],year:[95,2,1,""]},"gs_quant.timeseries.econometrics":{annualize:[96,2,1,""],beta:[97,2,1,""],change:[98,2,1,""],correlation:[99,2,1,""],excess_returns_:[100,2,1,""],index:[101,2,1,""],max_drawdown:[102,2,1,""],prices:[103,2,1,""],returns:[104,2,1,""],sharpe_ratio:[105,2,1,""],volatility:[106,2,1,""]},"gs_quant.timeseries.statistics":{"var":[121,2,1,""],LinearRegression:[45,0,1,""],RollingLinearRegression:[46,0,1,""],SEIRModel:[47,0,1,""],SIRModel:[48,0,1,""],cov:[107,2,1,""],exponential_std:[108,2,1,""],generate_series:[109,2,1,""],max_:[110,2,1,""],mean:[111,2,1,""],median:[112,2,1,""],min_:[113,2,1,""],mode:[114,2,1,""],percentile:[115,2,1,""],percentiles:[116,2,1,""],product:[117,2,1,""],range_:[118,2,1,""],std:[119,2,1,""],sum_:[120,2,1,""],winsorize:[122,2,1,""],zscores:[123,2,1,""]},"gs_quant.timeseries.technicals":{bollinger_bands:[124,2,1,""],exponential_moving_average:[125,2,1,""],exponential_spread_volatility:[126,2,1,""],exponential_volatility:[127,2,1,""],moving_average:[128,2,1,""],relative_strength_index:[129,2,1,""],smoothed_moving_average:[130,2,1,""]}},objnames:{"0":["py","class","Python class"],"1":["py","method","Python method"],"2":["py","function","Python function"],"3":["py","attribute","Python attribute"],"4":["py","data","Python data"]},objtypes:{"0":"py:class","1":"py:method","2":"py:function","3":"py:attribute","4":"py:data"},terms:{"0005":84,"001":84,"100":[45,46,57,58,60,61,63,64,65,68,71,73,74,75,76,77,78,79,80,81,82,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130],"1000":4,"10000":[7,10,13,14,18,19,20,21,25],"1000000":5,"10000usd":[7,10,13,14,18,19,20,21,25],"100k":[7,10,13,14,18,19,20,21,25],"10bp":84,"10y":[17,18,19,20,21,24,25,26,27,28,29,31,135],"200k":[7,10,13,14,18,19,20,21,25],"2018":[5,6,7,9,10,11,12,14,15,16],"2019":[18,19,20,21,25,53,54,93,95],"2020":[25,95],"2021":[55,56],"2025":[22,23],"2030":[18,19,20,21,25],"2050":[17,24,26,27,28,29],"252":[96,105,106],"30y":20,"30yusd":[18,19,21],"360":[100,105],"365":105,"4217":[5,7,8,10,12,14,16],"5bp":84,"5mar18":93,"5yusd":21,"71828":62,"8601":5,"90d":80,"90th":115,"boolean":[59,69,70],"case":88,"class":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48],"default":[31,32,41,44,51,52,53,54,55,58,61,63,65,68,75,84,85,88,93,97,99,100,102,105,106,107,110,111,112,113,114,116,117,118,119,120,121,122,123,124,128,129,130],"float":[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,41,47,48,56,60,64,66,71,73,82,108,115,122,124,125,126,127,135],"function":[39,41,58,60,61,64,65,67,68,75,78,93,108,112,114,122,131],"import":[31,32,51,52,53,54,55,56,108,125,126,127,135],"int":[4,6,31,44,46,47,48,51,52,53,72,78,80,82,86,97,99,101,102,103,104,105,106,107,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,128,129,130],"new":44,"return":[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,39,44,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,135],"true":[15,31,32,45,46,47,48,54,63,97],"var":[97,107,108,119],"while":32,CMS:[18,19,20,21],For:[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,32,57,62,66,67,72,90,112,114,117],OLS:[45,46],The:[2,4,8,10,12,13,14,15,17,18,19,20,21,22,23,24,25,26,27,28,29,32,39,44,46,47,48,52,54,55,56,67,88,100,105,109,122,125,126,127,129,135],Useful:96,Uses:39,Will:[39,88],__init__:[0,1,2,3,31,32,33,34,35,36,37,38,39,40,41,42,43],abov:[41,126,127],absolut:[9,57,103,104,106,135],accordingli:96,accrual:[24,26,28],accur:[0,1,3,34,35,36,37,38,39,40,42,43],across:[46,104],actual:[100,105],add:75,addit:122,adjust:[29,52,55,96,122],after:[10,12,14,53,86],aggreg:[104,135],aggregate_risk:135,algebra:131,align:[45,46,58,61,65,68,75],all:[41,60,64,79,81,88,112,114,117,118],allow:39,alpha_t:97,also:[39,57,58,60,61,62,64,65,67,68,71,73,74,75,76,77,78,79,80,81,82,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,101,102,103,104,105,106,107,108,109,110,111,112,113,114,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130],amount:[4,10,11,12,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29],analysi:131,ani:72,ann:96,annual:[15,106,126,127,135],annualization_factor:15,anoth:[75,88,92],api:3,appli:[52,61,65,68,75],approxim:62,arg:3,argument:39,arithmet:[98,103,104,111,128],around:124,arrai:[41,88,90,92,110,111,113,120],asset:[4,30,35,36,37,38,39,40,103,104,105,106,124,128],asset_class:[34,38],assign:108,assum:[84,97,109],asynchron:32,atm:[7,10,13,14,18,19,20,21,22,23,25],atmf:[7,10,13,14,18,19,20,21,22,23,25],attribut:[1,2,3,31,32,33,34,35,36,37,38,40],avail:[3,41,58,61,65,68,75,85,88,93,110,111,112,113,116,118,120],averag:[15,73,76,108,112,124,125,128,130],avoid:[31,32],b_t:97,back:[41,47,48],backtest:131,backward:80,band:124,bar:124,base:[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,39,40,45,46,47,48,57,60,62,64,67,71,72,74,78,80,85,86,88,96,97,99,103,104,106,109,124,125,126,127,128,129,130,131,135],base_cpi:29,basecpi:135,basi:135,basket:76,bba:[17,22,23,24,25,26,28],bbid:30,bbid_equival:30,bcid:30,becom:[8,17,22,23,24,26,27,28,29],been:[30,72],befor:[53,86],begin:[51,53],begin_d:51,behav:35,behavior:[58,61,65,68,75,85,88,93],behaviour:32,being:[47,48],below:135,benchmark:[17,18,19,20,21,22,23,24,25,26,28,29,97],beta:[47,48,108,125,126,127],beta_t:97,between:[47,48,51,75,96,108,125,126,127],binari:[10,12,13,18,19,21],block:32,bloomberg:30,bolling:124,bool:[31,32,45,46,47,48,54,86,97],both:[58,61,65,68,73,75,85,99],bps:135,broad:35,bucket:135,bui:[7,8,10,12,14,15,18,19,20,21,25],bus_dai:51,busi:[15,17,19,21,22,23,24,25,26,27,28,29,51,52,53,54,55,97,99,106],business_day_offset:135,businessdayconvent:[17,19,21,22,23,24,25,26,27,28,29],buy_sel:[7,8,10,12,14,15,18,19,20,21,25],buysel:[7,8,10,12,14,15,18,19,20,21,25],cach:[31,32],calc:135,calcul:[5,15,31,32,45,46,56,76,84,97,99,100,103,104,105,106,111,115,116,117,120,122,123,126,127],calculate_mean_return:15,calendar:[24,26,28,31,51,52,53,54,55],calibr:[41,47,48],call:[32,135],callabl:41,caller:93,can:[31,32,33,38,40,41,44,47,48,56,85,88,93,104,124],cap:[5,9,18,19,20,21,22,32,60,122,135],cap_floor:[18,19,20,21],cap_rat:22,cash:[7,14,16,38],cashflow:[17,24,26,27,28,29],ccy:[31,32,135],cdot:125,ceil:64,chang:[32,104,106,124,128,129],characterist:35,cid:30,classif:35,classifi:35,clear:[17,24,25,29],clearing_hous:[17,24,25,29],client:30,cliquet:5,close:129,cm_id:30,code:[4,5,7,8,10,12,14,16,30],collect:[2,33,51],column:135,combin:[63,72,84,92,135],commod:[4,135],commoddelta:135,commodotcswapleg:4,commodtheta:135,commodvega:135,common:114,compani:30,comparison:63,compart:[47,48],compartment:[47,48],compartmentalmodel:41,composit:30,comput:[15,78,85,97,98,99,102,103,104,106,107,108,111,112,114,115,116,117,119,120,121,123,124,126,127,128,129,130],concaten:90,condit:[35,41],consid:[51,52,53,54,55],consist:35,constant:[18,19,20,21],constitu:84,construct:[33,53],contain:88,content:[31,32],context:[31,32],contract:[4,7,8,10,12,14,15,18,19,20,21,25],control:[32,41,108,125,126,127],convent:[17,19,21,22,23,24,25,26,27,28,29,100,105],corr:99,correl:97,correspond:4,cost:84,count:[17,19,21,22,23,24,25,26,27,28,100,105],countri:30,coupon:[24,28,29],cov:97,covari:107,cpi:[29,135],creat:[33,44,86,109],cross:[12,13,30,135],csa:[31,32],csa_term:[31,32],currenc:[5,7,8,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,34,38,40,100,105,135],current:[32,39,41],curve_typ:105,curvetyp:105,cusip:30,custom:31,d_t:[100,105],dai:[15,17,19,21,22,23,24,25,26,27,28,29,47,48,51,52,53,54,55,56,72,86,89,91,94,95,96,97,99,100,105,106,124,129,135],daili:[15,72,84,96,126,135],data:[10,13,15,31,32,41,47,48,72,90,105,110,111,112,113,116,118,120,131],dataapi:2,datacontext:131,datafram:[124,129,135],dataframewithinfo:135,dataset:131,dataset_id:2,date:[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,31,32,39,45,46,47,48,56,57,58,60,61,64,65,68,71,72,74,75,78,79,80,81,85,86,87,88,89,90,91,92,93,94,95,96,97,99,102,103,104,105,106,107,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,135],datetim:[32,131,135],datetimeindex:[58,61,68,75,85],daycountfract:[17,19,21,22,23,24,25,26,27,28],debias:108,dec20:56,dec21:[5,6,7,10,11,12,14,15,16],decim:84,defin:[4,85,100,105,122,125,130],definit:[7,8,9],delai:[19,21],delist:30,delta:135,delta_diff:135,delta_f:135,delta_todai:135,delta_yday_f:135,depend:[39,45,46],deriv:[7,38,40],describ:63,descript:[4,30,47,48,103,104,106],desir:72,detail:[60,64,108],determin:[51,54,63,96,109,124],deviat:[99,108,109,119,122,123,124,126,127],df_t:108,dict:33,dictionari:33,diff:[80,126],differ:[17,26,27,28,35,36,37,39,75,78,98,126],dimens:135,direct:52,directli:32,diseas:[47,48],distant:[108,125,126,127],distribut:[109,116,122],divid:[65,68,101],divis:65,dkk:31,doe:[85,88,93],dollar:15,dollar_cross:30,dollar_pric:32,dollarpric:135,done:32,downsampl:72,drawdown:102,drop:73,e_t:[100,105],each:[15,46,47,48,54,57,62,63,67,71,74,84,85,87,89,91,94,95,97,99,106,107,110,111,112,113,114,117,119,121,123,128,130],econometr:131,eex:30,effect:[8,17,18,19,20,21,22,23,24,25,26,27,28,29],effective_d:[8,17,18,19,20,21,22,23,24,25,26,27,28,29],eid:30,either:[33,44],element:[20,21,57,62,67,71,74,112,114,123],els:[66,115],em_id:30,ema:125,empti:116,end:[1,4,31,46,47,48,51,53,86],end_dat:[47,48,51,86],english:4,entir:115,entiti:[30,34,38,40],enumer:[3,36,37],epidem:41,epidemiolog:131,epsilon_t:97,eqannualimpliedvol:135,eqdelta:135,eqgamma:135,eqspot:135,equal:[57,60,63,64,122],equiti:[5,6,7,8,9,135],equival:[30,57,85,88,93,126,127,128],eqvega:135,error:41,estim:[107,108,119,121],etc:[7,97,99,102,106,107,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,128,129,130],euler:62,eur:[10,11,13,14,15,17,22,23,24,25,26,28],euribor:[17,22,23,24,25,26,28],eurusd:[10,11,13,14,15],even:112,ever:116,everi:[72,98,101],evolut:[47,48],evolv:38,ewma:108,exampl:[31,32,44,45,46,51,52,54,55,56,57,58,60,61,62,63,64,65,67,68,71,73,74,75,76,77,78,79,80,81,82,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,135],exce:73,excess:[84,100,105],excess_return:[84,100],exchang:[5,7,8,10,12,14,16,17,26,27,28,30,34,38,40],exchange_cod:30,exclud:135,exclus:[108,125,126,127],execut:84,exercis:7,exhibit:35,exist:[85,86,88,93],exp:[57,67,103],expect:[31,32],expir:[10,12,14,25],expiration_d:[5,6,7,9,10,12,14,16,25],expiration_tim:[10,12,14],expiri:8,explanatori:[45,46],exponenti:[62,67,108,125,126,127],exponential_moving_averag:[73,108],exponential_spread_volatil:127,exponential_std:[126,127],exponential_volatil:126,expos:47,express:63,extend:80,extrem:122,factor:[15,96,108],fair:135,fairvarstrik:135,fairvolstrik:135,fall:[122,135],fals:[15,31,32,54,86,97],far:41,fast:[47,48],fee:[17,18,19,20,21,22,23,24,25,26,27,28,29],fee_curr:[17,18,19,20,21,22,23,24,25,26,27,28],fee_payment_d:[17,18,19,20,21,22,23,24,25,26,27,28],field:131,fill:[72,92],filter:73,filteroper:63,financi:40,first:[15,20,21,24,26,28,73,81,85,86,88,90,93,98,101],first_fixing_d:15,first_valuation_d:5,fit:[41,45,46,47,48],fit_intercept:[45,46],fit_method:41,fit_period:[41,47,48],fix:[10,13,15,24,25,27,28,29,40,46],fixed_first_stub:[24,28],fixed_holidai:[24,28],fixed_last_stub:[24,28],fixed_r:[24,28,29],fixed_rate_business_day_convent:[24,25,28,29],fixed_rate_curr:28,fixed_rate_day_count_fract:[24,25,28],fixed_rate_frequ:[24,25,28],fixing_frequ:15,fixing_sourc:[10,13,15],flag:66,floating_first_stub:[24,28],floating_holidai:[24,28],floating_last_stub:[24,28],floating_rate_business_day_convent:[19,21,22,23,24,25,28,29],floating_rate_curr:28,floating_rate_day_count_fract:[19,21,22,23,24,25,28],floating_rate_designated_matur:[22,23,24,25,28],floating_rate_for_the_initial_calculation_period:[24,28],floating_rate_frequ:[19,21,22,23,24,25,28],floating_rate_opt:[22,23,24,25,28],floating_rate_spread:[24,25,28],floatingrateopt:[22,23,24,28],floor:[5,18,19,20,21,23,60,65,122,135],floor_rat:23,follow:[96,109],forecast:48,form:41,format:5,forward:[6,11,52,72,135],forward_pric:6,forward_r:11,forwardpric:135,four:47,frac:[97,99,104,105,106,107,108,111,112,116,119,121,123,128,130],fraction:[17,19,21,22,23,24,25,26,27,28],free:[100,105],frequenc:[15,17,22,23,24,25,26,27,28,72,84,96,114],from:[14,17,21,26,30,31,32,47,48,56,63,75,85,86,90,92,93,98,100,103,104,109,117,125,128,130,135],full:[97,99,106,107,110,111,112,113,114,117,118,119,120,121,123,128,130],func:[86,130],further:108,futur:[32,80,135],fxdelta:135,fxgamma:135,fxmulticrossbinaryleg:12,fxspot:135,fxvega:135,gamma:[47,48,135],gbp:[5,7,8,10,12,14,16,32,135],gener:[30,53,57,60,64,71,73,74,76,84,103,104,107,108,109,111,112,114,117,119,120,121,122,123,125,126,127,128,130],generate_seri:[45,46,57,58,60,61,63,64,65,68,71,73,74,75,76,77,78,79,80,81,82,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130],geometr:[101,104,106],get:[52,76,100,105],get_paramet:41,given:[41,47,48,55,58,61,65,68,74,75,78,85,97,99,100,105,106,107,110,111,112,113,114,116,117,118,119,120,121,123,124],global:5,global_cap:5,global_floor:5,goldman:30,greater:[57,63,64,110,111,112,113,116,118,120,122],group:35,grow:116,gs_quant:[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,131,135],gsid:30,gsid_eid:30,gsid_equival:30,gsideid:30,gsn:30,gss:30,had:129,has:[30,40,58,61,65,68,72,75,88,90],have:[58,61,68,75,85,88,122,129],held:40,help:[0,1,3,34,35,36,37,38,39,40,42,43],helper:[97,99,102,106,107,110,111,112,113,114,116,117,118,119,120,121,122,123,124,128,130],higher:[90,129],histor:116,histori:[90,116],historicalpricingcontext:131,hkg:[31,32],hold:33,holidai:[31,51,52,53,54,55],hous:[17,24,25,29],how:[7,14,41,47,48,108,125,126,127],id_:[34,38,40],idd:109,ident:135,identifi:[2,5,6,7,8,9,30,36,39],ignor:[58,61,65,68,75],immedi:[31,32],immun:[47,48],impli:[96,135],includ:86,inclus:[108,125,126,127],increment:73,index1:21,index1_tenor:20,index2:21,index2_tenor:20,index:[18,19,29,30,31,32,66,75,80,82,88,98,129,131],indic:[15,17,26,54,63],individu:[47,48,135],infect:[47,48],infecti:[47,48],inflat:[29,135],inflationdelta:135,inflationdeltaparallel:135,inflationdeltaparallellocalcci:135,inform:[67,122,124,125,129,130],initi:[0,1,3,34,35,36,37,38,39,40,41,42,43,47,48,98,101,103],initial_condit:41,initial_fx_r:26,input:[52,54,55,58,59,61,68,69,70,73,75,85,90,97,99,105,108,126,127,135],inst:135,instead:32,instrument:[7,8,9,30,31,32,33,84,131,135],instrument_quant:[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30],instrumentbas:135,integ:[80,86,87,89,91,94,95],intercept:[45,46],interest:[17,22,23,24,26,135],interfac:39,intern:30,interpol:[58,61,65,68,75,85,93],interpret:53,intersect:[45,46,58,61,65,68,75,85,88,93],interv:[58,61,68,75,85],invers:67,ir_swap:[31,135],irannualatmimpliedvol:135,irannualimpliedvol:135,irbasi:135,ircap:[32,135],irdailyimpliedvol:135,irdelta:135,irdeltalocalcci:135,irdeltaparallel:135,irdeltaparallellocalcci:135,irfloor:135,irfwdrat:135,irgammaparallel:135,irgammaparallellocalcci:135,irspotr:135,irswap:[31,135],irvega:135,irvegalocalcci:135,irvegaparallel:135,irvegaparallellocalcci:135,irxccydelta:135,irxccydeltaparallel:135,irxccydeltaparallellocalcurr:135,is_async:[31,32],is_batch:[31,32],isin:30,iso:[5,7,8,10,12,14,16],iter:[31,33,51,52,53,54,55,135],its:116,japan:30,jsn:30,kei:33,kenn:30,known:30,kwarg:0,kwd:3,l_t:124,lag:[78,86],lagmod:80,larger:[46,73,128],last:[15,24,26,28,72,73,79,82,86,110,113,118],last_fixing_d:15,last_valuation_d:5,lceil:112,ldn:[31,32],least:[41,45,46],leastsq:41,left:135,leg:[4,12,13,24,26,28,29],length:[58,61,68,75,85,97,99,102,106,107,109,110,111,112,113,114,116,117,118,119,120,121,122,123,124,128,129,130],less:[60,122,128],less_than:63,level:[7,29,78,98,103,104,124,135],levenberg:41,lfloor:112,libor:[17,22,23,24,25,26,28],like:[4,97,99,102,106,107,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,128,129,130],limit:122,linear:[45,46],list:[7,30,41,45,46,76,84,85,88,90,92,110,111,113,120,135],lmfit:41,lms_id:30,local:[31,32,124,135],locat:[10,12,14,31,32],log:[62,104,106],logarithm:[62,67,103,104,106],logic:[59,63,69,70],longer:[31,32],lookalik:7,lookup:39,lower:[90,122,124,129],made:[31,32],mani:[47,48],manner:85,market:[30,131,135],market_data_loc:[31,32],marketdatacoordin:56,marketplac:7,marquardt:41,marque:30,master:[30,39],matur:[5,18,19,20,21],max:[64,110,118,122],max_:[113,118],maximum:[60,64,102,110],mdapi:30,mdapi_class:30,mean:[15,97,99,106,107,109,112,114,119,121,122,123,125,128,130],measur:131,median:[111,114],merg:135,method:[0,1,2,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,38,39,40,41,42,43,44,45,46,47,48,58,61,65,68,75,85,88,93],method_of_settl:[7,14],mic:30,middl:112,min:[31,32,60,113,118,122],min_:[110,118],minim:41,minimum:[64,73,113],miss:[58,61,63,68,72,75,85,92],mkt:56,mkt_asset:135,mkt_class:135,mkt_point:135,mkt_type:135,mktclose:7,mktopen:7,mma:130,mode:[80,111,112],model:[45,46,47,48,131],modifi:[5,7,8,10,12,14,16,130],modul:3,momentum:129,mondai:94,monei:[7,10,13,14,18,19,20,21,22,23,25,135],month:[4,44,87,91,94,95,97,99,106],monthli:[72,84,96],more:[59,60,64,67,70,108,124,125,126,127,128,129,130],morningstar:30,most:114,move:[47,48,52,108,124,125,128,130],movement:[47,48],moving_averag:[124,125,129,130],mq_symbol:30,much:[108,125,126,127],multi:[12,13],multipl:[7,10,13,14,18,19,20,21,25,31,114,135],multipli:[7,18,19,61,117],must:[46,53,58,61,68,75,85,108,125,126,127,135],mybasket:[76,84],name:[3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,33,34,38,40],nan:[58,61,65,68,75,77,85,88,93],natur:[62,67,104,106],nearest:52,neg:129,negat:69,neighbor:73,neighbour:73,node:30,non:80,none:[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,38,40,41,44,47,48,51,52,53,54,55,63,84,88,105,115,116,135],normal:[56,98,101,109,123],note:73,notion:[4,5,10,11,12,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29],notional_amount:[5,10,11,12,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29],notional_amount_other_curr:14,notional_curr:[10,12,14,15,17,18,19,20,21,22,23,24,25,29],notional_reset_sid:26,notionalcurr:14,nth:115,num_of_underly:8,number:[4,6,7,8,15,30,44,46,47,48,51,52,53,56,58,61,62,63,65,68,74,75,77,78,80,86,87,89,91,95,96,97,99,104,106,107,109,110,111,112,113,116,119,120,121,128,130],number_of_opt:7,number_of_period:4,number_of_shar:6,numer:[59,69,70,87,89,91,94,95],nummer:30,numpi:109,nyc:[31,32],nyse:54,object:[3,4,5,6,10,11,12,13,14,15,18,19,20,21,22,23,25,33,44,97,99,102,106,107,110,111,112,113,114,116,117,118,119,120,121,122,123,124,128,130],obs:[78,80,104,106],observ:[10,13,15,40,44,45,46,77,78,80,86,87,89,91,94,95,96,97,99,104,106,107,108,109,110,111,112,113,114,116,117,118,119,120,121,122,123,125,128,130],obvers:44,occurr:114,offset:[52,55],ois:[31,32],onc:[47,48],one:[44,55,58,61,65,68,75,92],onli:[4,5,45,46,58,61,65,68,75,84,85,86,88,93],oper:[58,61,63,65,68,75],option:[2,7,10,12,13,14,18,19,20,21,31,32,39,41,47,48,51,52,53,54,55,63,72,84,116,135],option_styl:7,option_typ:[7,10,13,14],optionsettlementmethod:[7,14],optionstyl:7,optiontyp:[7,10,13,14],order:[33,52],ordinari:[45,46],otc:[4,7],other:[7,14,58,61,65,68,75,85,93],otherwis:57,out:73,output:[41,72],outsid:122,over:[17,24,25,26,28,31,47,48,73,78,88,97,99,102,106,107,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,128,129,130],overlin:[99,106,107,119,121],p_t:[100,105],p_todai:130,packag:131,pai:[5,24,25,26,28,29,31,135],pair:[10,11,13,14,15],par:135,parallel:135,param:93,paramet:[2,31,32,33,34,41,44,45,46,47,48,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,135],parti:30,particular:30,past:[108,125,126,127],pay_or_rec:[24,25,28,29],payer:[17,26,27],payer_business_day_convent:[17,26,27],payer_curr:[26,27],payer_day_count_fract:[17,26,27],payer_designated_matur:[17,26],payer_first_stub:26,payer_frequ:[17,26,27],payer_holidai:26,payer_last_stub:26,payer_r:27,payer_rate_opt:[17,26],payer_spread:[17,26],payerrateopt:[17,26],payment:[10,12,14,16,17,18,19,20,21,22,23,24,25,26,27,28],payment_frequ:5,payrec:[24,26,28,29],peak:102,peopl:[47,48],per:[7,15,32],percent:[7,10,13,14,18,19,20,21,22,23,25],percentag:9,perform:41,period:[4,5,19,21,44,47,48,96],physic:[7,14],pl_id:30,place:[108,125,126,127],plain:4,platt:30,plot:30,plot_id:30,pnode_id:30,point:[39,72,73,85,88,92,93,105,131],popul:[47,48],portfolio:[38,40,131],posit:[5,63,129],pow:74,power:62,preced:135,predict:47,premium:[7,9,10,12,14,18,19,20,21,22,23,25],premium_curr:[7,10,12,14],premium_payment_d:[7,10,12,14,18,19,20,21,22,23,25],prepend:92,present:[58,61,65,68,75,85,88,93,135],prev_bus_d:[52,55],previou:[55,85,88,93,108,125,126,127],price:[5,6,30,31,32,33,40,56,57,60,63,64,71,73,74,76,78,80,84,96,97,99,100,104,105,106,107,108,109,110,111,112,113,114,117,118,119,120,121,122,123,124,125,126,127,128,129,130,135],price_f:[31,32],price_seri:[31,100],priceabl:[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,33,131],priceableimpl:33,prices1:[76,84],prices2:[76,84],prices_i:107,prices_x:107,pricing_d:[32,135],pricingcontext:[39,131,135],pricingloc:32,primari:30,primary_country_r:30,prime_id:30,primeid:30,principal_exchang:[17,24,26,27,28],principalexchang:[17,24,26,27,28],print:53,prior:[85,88,93],prod_:117,produc:31,product:[5,30,68,103,120],properti:[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30],provid:[2,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,39,97,99,106,107,108,110,111,112,113,114,116,117,118,119,120,121,122,123,128,130],ps_id:30,qualiti:90,quantiti:[4,30],quantity_period:4,quantity_unit:4,quarterli:96,queri:39,quot:[5,7,8,10,12,14,16],quotient:[61,65],r_squar:[45,46],r_t:[57,58,60,61,62,64,65,67,68,71,74,75,77,78,79,80,81,86,97,99,106,107,110,111,112,113,116,117,118,119,120,121,122,123,128],rais:[52,62,71],ramp:[44,97,99,102,105,106,107,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,128,129,130],random:109,randomli:109,rang:[53,86,118,122,124],rank:[115,116],rate:[5,11,17,19,21,22,23,24,25,26,27,28,29,47,48,100,105,135],ratio:[105,129],rceil:112,rcic:30,reactiv:128,read:4,real:[58,61,63,65,68,74,75,85],realiz:[15,99,106],rebal:84,rebal_freq:84,rebalanc:84,rebalfreq:84,rec:26,receiv:[17,24,25,26,27,28,29],receiver_business_day_convent:[17,26,27],receiver_curr:[26,27],receiver_day_count_fract:[17,26,27],receiver_designated_matur:[17,26],receiver_first_stub:26,receiver_frequ:[17,26,27],receiver_holidai:26,receiver_last_stub:26,receiver_notional_amount:27,receiver_r:27,receiver_rate_opt:[17,26],receiver_spread:[17,26],receiverrateopt:[17,26],record:72,recov:[47,48],recoveri:[47,48],ref_dat:56,refer:56,referenc:[8,40],regress:[45,46],rel:[73,80,97,99,102,106,107,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,128,129,130],relat:2,relev:[5,39],remov:63,replac:73,repres:[40,51,59,69,70,94,107,135],represent:[4,5,6,10,11,12,13,14,15,18,19,20,21,22,23,25,44],request:[31,32,85,88,93],reset:[19,21,26],reset_delai:[19,21],residu:41,resist:[47,48],resolution_kei:[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30],respond:124,restrict:[86,122],result:[31,32,58,59,61,63,65,66,68,69,70,72,75,84,85,88,135],retriev:39,return_styl:5,return_typ:[5,84],returns_typ:106,returntyp:84,reuter:30,rfloor:112,rho_t:99,ric:30,right:135,risk:[31,32,33,100,105,131],riskfreeratecurr:[100,105],riskkei:[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30],rma:130,roll:[24,46,52,97,99,102,105,106,107,110,111,112,113,115,116,117,118,119,120,121,123,124,128,130,135],roll_convent:24,root:[71,74],rsi:129,run:[31,32,33,130],s_t:[97,99,105,108],sach:30,salesforc:30,same:[97,99,106,107,114,119,121],sampl:[96,99,107,108,109,116,119,121,122,123],saturdai:[51,52,53,54,55],scalar:[58,61,65,68,75,82,85,115],scenario:33,scheme:4,score:[122,123],sec_nam:30,second:[20,21,85,88,116],secur:[5,6,7,8,9,131],sedol:30,see:[0,1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,34,35,36,37,38,39,40,42,43,57,58,60,61,62,64,65,67,68,71,73,74,75,76,77,78,79,80,81,82,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,101,102,103,104,105,106,107,108,109,110,111,112,113,114,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130],seen:72,seir:[41,47],self:[0,1,3,34,35,36,37,38,39,40,42,43],sell:[7,8,10,12,14,15,18,19,20,21,25],seri:[45,46,47,48,55,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130],series1:99,series2:99,seriestyp:99,settl:[7,14],settlement:[4,9,10,12,14,25],settlement_curr:[7,14],settlement_d:[7,9,10,11,12,14,15],settlement_rate_opt:14,sf_id:30,share:6,sharp:105,shift:80,shorter:[90,128],should:[32,97,99,102,106,107,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,128,129,130],show_progress:[31,32],side:[7,8,10,12,14,15,18,19,20,21,25],sigma:[47,99,122,123],sigma_t:124,signatur:[0,1,3,34,35,36,37,38,39,40,42,43],sim:109,similar:35,simon:30,simon_id:30,simpl:[97,99,103,104,106,128],singl:[12,13,17,52,54,55,69,135],sir:[41,48],size:[4,44,47,48,97,99,102,105,106,107,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,128,129,130],slice:86,sliced_seri:86,smaller:73,smallest:114,smma:130,smooth:[73,130],smoothed_moving_averag:[125,129],smoother:128,solut:41,solv:41,some:4,sort:[56,112,135],sort_risk:135,sourc:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,135],specifi:[30,56,63,80,85,88,93,115,122,123,124,128,130],spike:73,spot:[10,12,13,14,15,135],spread:[4,17,20,21,24,25,26,28,126],sqrt:[57,71,96,106,108,119],squar:[41,45,46,71,74],standard:[41,99,108,109,119,122,123,124,126,127],start:[1,3,4,31,86,92,109,125,128,130],start_dat:86,statist:131,std:[99,106,108,121,122,123,124,129],step:[58,61,65,68,75,85,88,93],stepwiz:[85,88],stock:[7,129],store:[31,32,46],str:[2,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,41,44,51,52,53,54,55,56,80,97,99,102,106,107,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,128,129,130,135],straddl:[18,19,20,21],strategi:4,strength:129,strike:[5,7,9,10,13,14,15,18,19,20,21,25,135],strike_pric:[5,7,9,10,13,14],strike_vol:15,string:[9,44,56,97,99,102,106,107,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,128,129,130],strip:[4,19,21],stronger:129,structur:[18,19,20,21],stub:[24,26,28],style:[5,7,93],sub:[85,86,88],subclass:39,subsequ:[47,48],substract:135,subtract:[58,135],subtract_risk:135,sum:[5,58,76,77,107,117,119,120],sum_:[97,99,106,107,108,111,116,117,119,121,128],sundai:[51,52,53,54,55],support:84,surround:[58,61,68,75,85],suscept:[47,48],swap:[4,8,9,15,17,18,19,20,21,24,25,26,27,28,29,135],swap_typ:8,swapclearinghous:[17,24,25,29],swapsettl:25,swaption:25,symbol:30,synchron:32,synthet:8,take:[57,67,74,90,92,103,104],taken:15,tdapi:30,technic:131,teler:[17,22,23,24,25,26,28],tenor:[5,6,7,8,9,10,11,12,14,15,16,17,20,22,23,24,25,26,28],term:[4,15,128],termin:[17,18,19,20,21,22,23,24,25,26,27,28,29],termination_d:[17,18,19,20,21,22,23,24,25,26,27,28,29],than:[14,31,32,46,57,60,64,73,90,110,111,112,113,116,118,120,122,129],thei:97,them:84,therefor:[58,61,65,68,75],theta:135,thi:[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,32,39,47,48,67,88,112,114,122,135],those:[41,73],three:48,threshold:[73,135],through:[3,38,39,104,109],thu:[108,125,126,127],ticker:30,time:[7,10,12,14,39,44,45,46,47,48,57,58,60,61,63,64,68,71,73,74,75,76,77,78,79,80,81,82,84,85,86,87,88,89,91,92,93,94,95,96,97,98,99,101,102,103,104,106,108,109,117,122,123,124,125,126,127,128,129,130,131,135],timedelta:51,timeout:[31,32],times100:116,timeseri:131,todai:[31,32,51,52,53,54,55,56,109,125,128,130,135],togeth:117,total:[47,48],track:38,trade:[7,38,84],trade_a:7,tradea:7,transact:40,transmiss:[47,48],treat:[58,61,65,68,75],trigger:73,trough:102,tupl:[4,12,31,41,51,52,53,54,55,135],two:[58,59,61,65,68,70,75,85,90,92,97,99,112,124],type:[0,1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,34,35,36,37,38,39,40,41,42,43,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,135],type_:99,u_t:124,unbias:[107,108,119,121],under:[31,32,35],underli:[5,6,7,8,9,17,18,19,20,21,22,23,24,25,26,28,29,84],underlier_typ:[5,6,7,8,9],underliertyp:[5,6,7,8,9],underly:[5,6,7,8,9],union:[2,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,31,32,33,44,45,46,47,48,51,52,53,54,55,58,61,65,66,68,74,75,80,82,85,86,88,90,93,97,99,102,105,106,107,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,128,129,130,135],unit:7,unresolv:[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30],until:90,upper:[122,124],usag:[44,45,46,47,48,57,58,60,61,62,63,64,65,67,68,71,72,73,74,75,76,77,78,79,80,81,82,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130],usd:[10,11,13,14,15,17,22,23,24,25,26,28,30,100,105,135],use:[31,32,41,44,45,46,51,52,53,54,55,58,61,66,68,75,85,88,97,99,102,105,106,107,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,128,129,130],use_cach:[31,32],used:[10,13,15,31,32,33,41,47,48,56,58,61,65,68,72,75,85,88,100,105,124],useful:90,uses:116,using:85,valid:[58,61,68,75,77,85,88,93],valoren:30,valu:[3,4,5,7,9,10,13,14,18,19,20,21,22,23,25,33,35,36,37,44,57,58,60,61,63,64,65,66,68,71,72,73,74,75,78,79,81,82,85,87,88,89,91,94,95,96,97,98,99,101,102,103,104,105,106,107,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,128,129,130,135],valuat:[7,15,31],valuation_period:5,valuation_tim:7,valuationtim:7,vanilla:[24,29],variabl:[45,46,58,61,65,68,109],varianc:[9,97,107,108,121,135],variance_cap:9,varieti:39,varswap:9,vega:135,vega_f:135,vendor:2,via:[58,61,65,68,75,93],visibl:[31,32],visible_to_g:[31,32],vol:[7,10,13,14,15,18,19,20,21,25],vol_seri:106,volatil:[15,105,124,126,127,128,135],w_i:108,wai:35,wait:31,weather:30,week:[44,86,94],week_mask:[51,52,53,54,55],weekdai:86,weekdays_onli:86,weekend:[51,52,53,54,55],weekli:[72,96],weigh:[108,125,126,127],weight:[76,84,108,125,126,127],well:30,wertpapi:30,what:41,when:[58,61,66,68,75,85,90,112],where:[7,33,60,63,64,72,73,78,79,80,81,82,88,92,94,97,98,99,101,102,103,104,105,106,107,108,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,128,129,130,135],whether:[15,30,45,46,47,48,54,80,86,105],which:[8,17,22,23,24,26,27,28,29,31,32,33,35,38,40,41,51,52,53,54,55,84,88,90,104,109,122,124,129],whose:135,wi_id:30,width:124,window:[46,80,97,99,102,105,106,107,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,128,129,130],winsoris:122,within:87,wpk:30,x_0:[79,98,101,108,125],x_1:[90,108,125],x_2:[90,108,125],x_i:[90,92,107,111,116,117,119,120,121],x_n:90,x_t:[57,58,60,61,62,64,65,67,68,71,74,75,77,78,81,82,96,97,98,99,101,103,104,106,107,108,109,110,113,118,119,121,122,123,124,125,128],y_0:[103,125],y_i:107,y_t:[58,61,65,68,75,86,87,89,91,93,94,95,96,98,99,101,103,104,106,107,116,125],year:[15,80,87,89],yesterdai:130,yield:63,zero:[15,58,61,65,68,75,80,85,88,93],zscore:[116,122]},titles:["gs_quant.base.Priceable","gs_quant.data.DataContext","gs_quant.data.Dataset","gs_quant.data.Fields","CommodOTCSwap","EqCliquet","EqForward","EqOption","EqSynthetic","EqVarianceSwap","FXBinary","FXForward","FXMultiCrossBinary","FXMultiCrossBinaryLeg","FXOption","FXVolatilitySwap","Forward","IRBasisSwap","IRCMSOption","IRCMSOptionStrip","IRCMSSpreadOption","IRCMSSpreadOptionStrip","IRCap","IRFloor","IRSwap","IRSwaption","IRXccySwap","IRXccySwapFixFix","IRXccySwapFixFlt","InflationSwap","Security","gs_quant.markets.HistoricalPricingContext","gs_quant.markets.PricingContext","gs_quant.markets.portfolio.Portfolio","gs_quant.markets.securities.Asset","gs_quant.markets.securities.AssetClass","gs_quant.markets.securities.AssetIdentifier","gs_quant.markets.securities.AssetType","gs_quant.markets.securities.Index","gs_quant.markets.securities.SecurityMaster","gs_quant.markets.securities.Stock","gs_quant.models.epidemiology.EpidemicModel","gs_quant.models.epidemiology.SEIR","gs_quant.models.epidemiology.SIR","gs_quant.timeseries.datetime.Window","gs_quant.timeseries.statistics.LinearRegression","gs_quant.timeseries.statistics.RollingLinearRegression","gs_quant.timeseries.statistics.SEIRModel","gs_quant.timeseries.statistics.SIRModel","Data Package","Datetime Package","gs_quant.datetime.date.business_day_count","gs_quant.datetime.date.business_day_offset","gs_quant.datetime.date.date_range","gs_quant.datetime.date.is_business_day","gs_quant.datetime.date.prev_business_date","gs_quant.datetime.point.point_sort_order","gs_quant.timeseries.algebra.abs_","gs_quant.timeseries.algebra.add","gs_quant.timeseries.algebra.and_","gs_quant.timeseries.algebra.ceil","gs_quant.timeseries.algebra.divide","gs_quant.timeseries.algebra.exp","gs_quant.timeseries.algebra.filter_","gs_quant.timeseries.algebra.floor","gs_quant.timeseries.algebra.floordiv","gs_quant.timeseries.algebra.if_","gs_quant.timeseries.algebra.log","gs_quant.timeseries.algebra.multiply","gs_quant.timeseries.algebra.not_","gs_quant.timeseries.algebra.or_","gs_quant.timeseries.algebra.power","gs_quant.timeseries.algebra.repeat","gs_quant.timeseries.algebra.smooth_spikes","gs_quant.timeseries.algebra.sqrt","gs_quant.timeseries.algebra.subtract","gs_quant.timeseries.algebra.weighted_sum","gs_quant.timeseries.analysis.count","gs_quant.timeseries.analysis.diff","gs_quant.timeseries.analysis.first","gs_quant.timeseries.analysis.lag","gs_quant.timeseries.analysis.last","gs_quant.timeseries.analysis.last_value","weighted_sum","gs_quant.timeseries.backtesting.basket","gs_quant.timeseries.datetime.align","gs_quant.timeseries.datetime.date_range","gs_quant.timeseries.datetime.day","gs_quant.timeseries.datetime.interpolate","gs_quant.timeseries.datetime.month","gs_quant.timeseries.datetime.prepend","gs_quant.timeseries.datetime.quarter","gs_quant.timeseries.datetime.union","gs_quant.timeseries.datetime.value","gs_quant.timeseries.datetime.weekday","gs_quant.timeseries.datetime.year","gs_quant.timeseries.econometrics.annualize","gs_quant.timeseries.econometrics.beta","gs_quant.timeseries.econometrics.change","gs_quant.timeseries.econometrics.correlation","gs_quant.timeseries.econometrics.excess_returns_","gs_quant.timeseries.econometrics.index","gs_quant.timeseries.econometrics.max_drawdown","gs_quant.timeseries.econometrics.prices","gs_quant.timeseries.econometrics.returns","gs_quant.timeseries.econometrics.sharpe_ratio","gs_quant.timeseries.econometrics.volatility","gs_quant.timeseries.statistics.cov","gs_quant.timeseries.statistics.exponential_std","gs_quant.timeseries.statistics.generate_series","gs_quant.timeseries.statistics.max_","gs_quant.timeseries.statistics.mean","gs_quant.timeseries.statistics.median","gs_quant.timeseries.statistics.min_","gs_quant.timeseries.statistics.mode","gs_quant.timeseries.statistics.percentile","gs_quant.timeseries.statistics.percentiles","gs_quant.timeseries.statistics.product","gs_quant.timeseries.statistics.range_","gs_quant.timeseries.statistics.std","gs_quant.timeseries.statistics.sum_","gs_quant.timeseries.statistics.var","gs_quant.timeseries.statistics.winsorize","gs_quant.timeseries.statistics.zscores","gs_quant.timeseries.technicals.bollinger_bands","gs_quant.timeseries.technicals.exponential_moving_average","gs_quant.timeseries.technicals.exponential_spread_volatility","gs_quant.timeseries.technicals.exponential_volatility","gs_quant.timeseries.technicals.moving_average","gs_quant.timeseries.technicals.relative_strength_index","gs_quant.timeseries.technicals.smoothed_moving_average","GS Quant API","Instrument Package","Markets Package","Models Package","Risk Package","Timeseries Package"],titleterms:{"function":135,"return":104,"var":121,abs_:57,add:58,algebra:[57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,136],align:85,analysi:[77,78,79,80,81,82,136],and_:59,annual:96,api:131,asset:34,assetclass:35,assetidentifi:36,assettyp:37,backtest:[84,136],base:0,basket:84,beta:97,bollinger_band:124,business_day_count:51,business_day_offset:52,ceil:60,chang:98,commodotcswap:4,correl:99,count:77,cov:107,dai:87,data:[1,2,3,49],datacontext:1,dataset:2,date:[50,51,52,53,54,55,136],date_rang:[53,86],datetim:[44,50,51,52,53,54,55,56,85,86,87,88,89,90,91,92,93,94,95],diff:78,divid:61,econometr:[96,97,98,99,100,101,102,103,104,105,106,136],epidemicmodel:41,epidemiolog:[41,42,43,134],eqcliquet:5,eqforward:6,eqopt:7,eqsynthet:8,eqvarianceswap:9,excess_returns_:100,exp:62,exponential_moving_averag:125,exponential_spread_volatil:126,exponential_std:108,exponential_volatil:127,field:3,filter_:63,first:79,floor:64,floordiv:65,forward:16,fxbinari:10,fxforward:11,fxmulticrossbinari:12,fxmulticrossbinaryleg:13,fxoption:14,fxvolatilityswap:15,generate_seri:109,gs_quant:[0,1,2,3,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130],historicalpricingcontext:31,if_:66,index:[38,101],inflationswap:29,instrument:132,interpol:88,irbasisswap:17,ircap:22,ircmsopt:18,ircmsoptionstrip:19,ircmsspreadopt:20,ircmsspreadoptionstrip:21,irfloor:23,irswap:24,irswapt:25,irxccyswap:26,irxccyswapfixfix:27,irxccyswapfixflt:28,is_business_dai:54,lag:80,last:81,last_valu:82,linearregress:45,log:67,market:[31,32,33,34,35,36,37,38,39,40,133],max_:110,max_drawdown:102,mean:111,measur:135,median:112,min_:113,mode:114,model:[41,42,43,134],month:89,moving_averag:128,multipli:68,not_:69,or_:70,packag:[49,50,132,133,134,135,136],percentil:[115,116],point:[50,56],point_sort_ord:56,portfolio:[33,133],power:71,prepend:90,prev_business_d:55,price:103,priceabl:0,pricingcontext:32,product:117,quant:131,quarter:91,range_:118,relative_strength_index:129,repeat:72,risk:135,rollinglinearregress:46,secur:[30,34,35,36,37,38,39,40,133],securitymast:39,seir:42,seirmodel:47,sharpe_ratio:105,sir:43,sirmodel:48,smooth_spik:73,smoothed_moving_averag:130,sqrt:74,statist:[45,46,47,48,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,136],std:119,stock:40,subtract:75,sum_:120,technic:[124,125,126,127,128,129,130,136],time:136,timeseri:[44,45,46,47,48,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,136],union:92,valu:93,volatil:106,weekdai:94,weighted_sum:[76,83],window:44,winsor:122,year:95,zscore:123}}) ================================================ FILE: docs/_build/html/timeseries.html ================================================ Timeseries Package — gs_quant 0.1 documentation

Timeseries Package

Algebra

abs_(x)

Absolute value of each element in series

add(x, y[, method])

Add two series or scalars

and_(*series)

Logical “and” of two or more boolean series.

ceil(x[, value])

Cap series at maximum value

divide(x, y[, method])

Divide two series or scalars

exp(x)

Exponential of series

filter_(x[, operator, value])

Removes values where comparison with the operator and value combination results in true, defaults to removing missing values from the series

floor(x[, value])

Floor series at minimum value

floordiv(x, y[, method])

Floor divide two series or scalars

if_(flags, x, y)

Returns a series s.

log(x)

Natural logarithm of series

multiply(x, y[, method])

Multiply two series or scalars

not_(series)

Logical negation of a single boolean series.

or_(*series)

Logical “or” of two or more boolean series.

power(x[, y])

Raise each element in series to power

repeat(x[, n])

Repeats values for days where data is missing.

smooth_spikes(x, threshold)

Smooth out the spikes of a series.

sqrt(x)

Square root of (a) each element in a series or (b) a real number

subtract(x, y[, method])

Add two series or scalars

weighted_sum(series, weights)

Calculate a weighted sum.

Analysis

diff(x[, obs])

Diff observations with given lag

first(x)

First value of series

last(x)

Last value of series (as a series)

last_value(x)

Last value of series (as a scalar)

count(x)

Count observations in series

lag(x[, obs, mode])

Lag timeseries by a number of observations or a relative date.

Backtesting

basket(series, weights[, costs, rebal_freq, …])

Calculates a basket return series.

Date / Time

align(x, y[, method])

Align dates of two series or scalars

interpolate(x[, dates, method])

Interpolate over specified dates or times

value(x, date[, method])

Value at specified date or time

day(x)

Day of each value in series

weekday(x)

Weekday of each value in series

month(x)

Month of each value in series

year(x)

Year of each value in series

quarter(x)

Quarter of each value in series

date_range(x, start_date, end_date[, …])

Create a time series from a (sub-)range of dates in an existing time series.

prepend(x)

Prepend data series

union(x)

Fill in missing dates or times of one series with another

Window([w, r])

Create a Window with size and ramp up to use.

Econometrics

annualize(x)

Annualize series based on sample observation frequency

beta(x, b[, w, prices])

Rolling beta of price series and benchmark

change(x)

Arithmetic series normalization

correlation(x, y[, w, type_])

Rolling correlation of two price series

excess_returns_(price_series[, currency])

Calculate excess returns

index(x[, initial])

Geometric series normalization

max_drawdown(x[, w])

Compute the maximum peak to trough drawdown over a rolling window.

prices(series[, initial, type])

Calculate price levels from returns series

returns(series[, obs, type])

Calculate returns from price series

sharpe_ratio(series[, currency, w, curve_type])

Calculate Sharpe ratio

volatility(x[, w, returns_type])

Realized volatility of price series

Statistics

cov(x, y[, w])

Rolling co-variance of series over given window

exponential_std(x[, beta])

Exponentially weighted standard deviation

generate_series(length)

Generate sample timeseries

max_(x[, w])

Maximum value of series over given window

mean(x[, w])

Arithmetic mean of series over given window

median(x[, w])

Median value of series over given window

min_(x[, w])

Minimum value of series over given window

mode(x[, w])

Most common value in series over given window

percentile(x, n[, w])

Returns the nth percentile of a series.

percentiles(x[, y, w])

Rolling percentiles over given window

product(x[, w])

Rolling product of series over given window

range_(x[, w])

Range of series over given window

std(x[, w])

Rolling standard deviation of series over given window

sum_(x[, w])

Rolling sum of series over given window

var(x[, w])

Rolling variance of series over given window

winsorize(x[, limit, w])

Limit extreme values in series

zscores(x[, w])

Rolling z-scores over a given window

LinearRegression(X, y[, fit_intercept])

Fit an Ordinary least squares (OLS) linear regression model.

RollingLinearRegression(X, y, w[, fit_intercept])

Fit a rolling ordinary least squares (OLS) linear regression model.

SIRModel([beta, gamma, s, i, r, n, fit, …])

SIR Compartmental model for transmission of infectious disease

SEIRModel([beta, gamma, sigma, s, e, i, r, …])

SEIR Compartmental model for transmission of infectious disease

Technical Analysis

bollinger_bands(x[, w, k])

Bollinger bands with given window and width

moving_average(x[, w])

Moving average over specified window

exponential_moving_average(x[, beta])

Exponentially weighted moving average

exponential_volatility(x[, beta])

Exponentially weighted volatility

exponential_spread_volatility(x[, beta])

Exponentially weighted spread volatility

smoothed_moving_average(x[, w])

Smoothed moving average over specified window

relative_strength_index(x[, w])

Relative Strength Index

================================================ FILE: docs/_build/search/search-index.json ================================================ [ { "body": "gs_quant base Priceable class Priceable kwargs source __init__ kwargs Initialize self See help type self for accurate signature Methods", "key": "classes/gs_quant.base.Priceable", "title": "gs_quant base Priceable" }, { "body": "gs_quant data DataContext class DataContext start None end None source __init__ start None end None source Initialize self See help type self for accurate signature Methods Attributes", "key": "classes/gs_quant.data.DataContext", "title": "gs_quant data DataContext" }, { "body": "gs_quant data Dataset class Dataset dataset_id provider None source A collection of related data __init__ dataset_id provider None source Parameters dataset_id Union str Vendor The dataset s identifier provider Optional DataApi The data provider Methods Attributes", "key": "classes/gs_quant.data.Dataset", "title": "gs_quant data Dataset" }, { "body": "gs_quant data Fields class Fields value object object names None module None type None start 1 source Data field enumeration Enumeration of fields available through data APIs __init__ args kwds Initialize self See help type self for accurate signature Attributes", "key": "classes/gs_quant.data.Fields", "title": "gs_quant data Fields" }, { "body": "CommodOTCSwap For methods of this class see gs_quant base Priceable class CommodOTCSwap start None end None number_of_periods None strategy None quantity None quantity_unit None quantity_period None legs None settlement None name None source Object representation of a commodities swap Properties end Date or Contract Month Return type Union date str instrument_quantity Return type float legs Commodities OTC swap leg Return type Tuple CommodOTCSwapLeg number_of_periods The number of settlement periods Return type int provider quantity Size of some value i e notional like 1 3b 1 5 1000 Return type Union float str quantity_period A coding scheme to define a period corresponding to a quantity amount Return type Union Period str quantity_unit Commodity asset Return type str resolution_key Return type RiskKey settlement read only description in plain English of settlement terms Return type str start Date or Contract Month Return type Union date str strategy Strip and Commodity Spread Type Swap Strategy Return type str unresolved", "key": "classes/gs_quant.instrument.CommodOTCSwap", "title": "CommodOTCSwap" }, { "body": "EqCliquet For methods of this class see gs_quant base Priceable class EqCliquet underlier None underlier_type None expiration_date None strike_price None currency None first_valuation_date None global_floor 1000000 global_cap 1000000 last_valuation_date None notional_amount None payment_frequency Maturity return_style Rate of Return return_type Sum valuation_period None name None source Object representation of an Equity Cliquet Properties currency Currency ISO 4217 currency code or exchange quote modifier e g GBP vs GBp Return type Union Currency str expiration_date Date or tenor e g 2018 09 03 3m Dec21 Return type Union date str first_valuation_date ISO 8601 formatted date Return type date global_cap Global Cap of return relevant only if paying at maturity Return type float global_floor Global Floor of return relevant only if paying at maturity Return type float instrument_quantity Return type float last_valuation_date ISO 8601 formatted date Return type date notional_amount Notional of this position Return type Union float str payment_frequency Return type str provider resolution_key Return type RiskKey return_style Return calculation style Return type str return_type Sum or Product of periodic return relevant only if paying at maturity Return type str strike_price Strike price as value Return type float underlier Underlier security identifier Return type Union float str underlier_type Type of underlyer Return type Union UnderlierType str unresolved valuation_period Tenor Return type str", "key": "classes/gs_quant.instrument.EqCliquet", "title": "EqCliquet" }, { "body": "EqForward For methods of this class see gs_quant base Priceable class EqForward underlier None underlier_type None expiration_date None forward_price None number_of_shares 1 name None source Object representation of an equity forward Properties expiration_date Date or tenor e g 2018 09 03 3m Dec21 Return type Union date str forward_price Forward price Return type float instrument_quantity Return type float number_of_shares Number of shares Return type int provider resolution_key Return type RiskKey underlier Underlier security identifier Return type Union float str underlier_type Type of underlyer Return type Union UnderlierType str unresolved", "key": "classes/gs_quant.instrument.EqForward", "title": "EqForward" }, { "body": "EqOption For methods of this class see gs_quant base Priceable class EqOption underlier None expiration_date None strike_price None option_type None option_style None number_of_options None exchange None multiplier None settlement_date None settlement_currency None premium 0 premium_payment_date None valuation_time None method_of_settlement None underlier_type None buy_sell None premium_currency None trade_as None name None source Instrument definition for equity option Properties buy_sell Buy or Sell side of contract Return type Union BuySell str exchange Name of marketplace where security derivative or other instrument is traded Return type str expiration_date Date or tenor e g 2018 09 03 3m Dec21 Return type Union date str instrument_quantity Return type float method_of_settlement How the option is settled e g Cash Physical Return type Union OptionSettlementMethod str multiplier Number of stock units per option contract Return type float number_of_options Number of options Return type float option_style Option Exercise Style Return type Union OptionStyle str option_type Option Type Return type Union OptionType str premium Option premium Return type float premium_currency Currency of the option premium Return type Union Currency str premium_payment_date Option premium Return type Union date str provider resolution_key Return type RiskKey settlement_currency Currency ISO 4217 currency code or exchange quote modifier e g GBP vs GBp Return type Union Currency str settlement_date Date or tenor e g 2018 09 03 3m Dec21 Return type Union date str strike_price Strike as value percent or at the money e g 62 5 95 ATM 25 ATMF 10 vol 100k pv p 10000 p 10000USD 200K BP or multiple strikes 65 4 45 8 Return type Union float str trade_as Option trade as i e listed otc lookalike etc Return type Union TradeAs str underlier Underlier security identifier Return type Union float str underlier_type Type of underlyer Return type Union UnderlierType str unresolved valuation_time Valuation time e g MktClose MktOpen of the underlying level for exercise Return type Union ValuationTime str", "key": "classes/gs_quant.instrument.EqOption", "title": "EqOption" }, { "body": "EqSynthetic For methods of this class see gs_quant base Priceable class EqSynthetic underlier expiry currency None swap_type Eq Swap buy_sell None underlier_type None effective_date None num_of_underlyers None name None source Instrument definition for equity synthetics Properties buy_sell Buy or Sell side of contract Return type Union BuySell str currency Currency ISO 4217 currency code or exchange quote modifier e g GBP vs GBp Return type Union Currency str effective_date The date on which the synthetic becomes effective Return type date expiry Tenor Return type str instrument_quantity Return type float num_of_underlyers number of underlyers referenced in synthetic contract Return type float provider resolution_key Return type RiskKey swap_type Return type str underlier Underlier security identifier Return type Union float str underlier_type Type of underlyer Return type Union UnderlierType str unresolved", "key": "classes/gs_quant.instrument.EqSynthetic", "title": "EqSynthetic" }, { "body": "EqVarianceSwap For methods of this class see gs_quant base Priceable class EqVarianceSwap underlier None underlier_type None expiration_date None strike_price None variance_cap None settlement_date None premium None name None source Instrument definition for equity variance swap Properties expiration_date Date or tenor e g 2018 09 03 3m Return type Union date str instrument_quantity Return type float premium VarSwap premium Return type Union float str provider resolution_key Return type RiskKey settlement_date Settlement date Return type Union date str strike_price Variance strike as value or percentage string e g 62 5 95 Return type Union float str underlier Underlier security identifier Return type Union float str underlier_type Type of underlyer Return type Union UnderlierType str unresolved variance_cap Variance Cap as absolute value Return type float", "key": "classes/gs_quant.instrument.EqVarianceSwap", "title": "EqVarianceSwap" }, { "body": "FXBinary For methods of this class see gs_quant base Priceable class FXBinary pair None buy_sell None option_type None notional_amount None notional_currency None strike_price None settlement_date None expiration_date None expiration_time None premium None premium_currency None premium_payment_date None fixing_source None name None source Object representation of a FX binary option Properties buy_sell Buy or Sell side of contract Return type Union BuySell str expiration_date Date or tenor e g 2018 09 03 3m Dec21 Return type Union date str expiration_time The location and optionally time of spot for expiration Return type str fixing_source The data source to be used for observation of FX spot on the fixing date Return type str instrument_quantity Return type float notional_amount Notional amount Return type Union float str notional_currency Currency ISO 4217 currency code or exchange quote modifier e g GBP vs GBp Return type Union Currency str option_type Option Type Return type Union OptionType str pair EURUSD or EUR USD Type A currency pair e g Return type str premium Option premium Return type Union float str premium_currency Currency of the option premium Return type Union Currency str premium_payment_date Payment date of the option premium Return type str provider resolution_key Return type RiskKey settlement_date Settlement date of the option after expiration Return type Union date str strike_price Strike as value percent or at the money e g 62 5 95 ATM 25 ATMF 10 vol 100k pv p 10000 p 10000USD 200K BP or multiple strikes 65 4 45 8 Return type Union float str unresolved", "key": "classes/gs_quant.instrument.FXBinary", "title": "FXBinary" }, { "body": "FXForward For methods of this class see gs_quant base Priceable class FXForward pair None settlement_date None forward_rate None notional_amount None name None source Object representation of an FX forward Properties forward_rate Forward FX rate Return type Union float str instrument_quantity Return type float notional_amount Notional amount Return type Union float str pair EURUSD or EUR USD Type A currency pair e g Return type str provider resolution_key Return type RiskKey settlement_date Date or tenor e g 2018 09 03 3m Dec21 Return type Union date str unresolved", "key": "classes/gs_quant.instrument.FXForward", "title": "FXForward" }, { "body": "FXMultiCrossBinary For methods of this class see gs_quant base Priceable class FXMultiCrossBinary buy_sell None legs None notional_amount None notional_currency None settlement_date None expiration_date None expiration_time None premium None premium_currency None premium_payment_date None name None source Object representation of an FX multi cross binary Properties buy_sell Buy or Sell side of contract Return type Union BuySell str expiration_date Date or tenor e g 2018 09 03 3m Dec21 Return type Union date str expiration_time The location and optionally time of spot for expiration Return type str instrument_quantity Return type float legs Object representation of a single leg of a multi cross binary option Return type Tuple FXMultiCrossBinaryLeg notional_amount Notional amount Return type Union float str notional_currency Currency ISO 4217 currency code or exchange quote modifier e g GBP vs GBp Return type Union Currency str premium Option premium Return type Union float str premium_currency Currency of the option premium Return type Union Currency str premium_payment_date Payment date of the option premium Return type str provider resolution_key Return type RiskKey settlement_date Settlement date of the option after expiration Return type Union date str unresolved", "key": "classes/gs_quant.instrument.FXMultiCrossBinary", "title": "FXMultiCrossBinary" }, { "body": "FXMultiCrossBinaryLeg For methods of this class see gs_quant base Priceable class FXMultiCrossBinaryLeg pair None option_type None strike_price None fixing_source None name None source Object representation of a single leg of a multi cross binary option Properties fixing_source The data source to be used for observation of FX spot on the fixing date Return type str instrument_quantity Return type float option_type Option Type Return type Union OptionType str pair EURUSD or EUR USD Type A currency pair e g Return type str provider resolution_key Return type RiskKey strike_price Strike as value percent or at the money e g 62 5 95 ATM 25 ATMF 10 vol 100k pv p 10000 p 10000USD 200K BP or multiple strikes 65 4 45 8 Return type Union float str unresolved", "key": "classes/gs_quant.instrument.FXMultiCrossBinaryLeg", "title": "FXMultiCrossBinaryLeg" }, { "body": "FXOption For methods of this class see gs_quant base Priceable class FXOption pair None buy_sell None option_type None notional_amount None notional_currency None notional_amount_other_currency None strike_price None settlement_date None settlement_currency None settlement_rate_option None method_of_settlement None expiration_date None expiration_time None premium None premium_currency None premium_payment_date None name None source Object representation of an FX option Properties buy_sell Buy or Sell side of contract Return type Union BuySell str expiration_date Date or tenor e g 2018 09 03 3m Dec21 Return type Union date str expiration_time The location and optionally time of spot for expiration Return type str instrument_quantity Return type float method_of_settlement How the option is settled e g Cash Physical Return type Union OptionSettlementMethod str notional_amount Notional amount Return type Union float str notional_amount_other_currency Notional amount in currency other than NotionalCurrency from the pair Return type Union float str notional_currency Currency ISO 4217 currency code or exchange quote modifier e g GBP vs GBp Return type Union Currency str option_type Option Type Return type Union OptionType str pair EURUSD or EUR USD Type A currency pair e g Return type str premium Option premium Return type Union float str premium_currency Currency of the option premium Return type Union Currency str premium_payment_date Payment date of the option premium Return type str provider resolution_key Return type RiskKey settlement_currency Currency of settlement Return type Union Currency str settlement_date Settlement date of the option after expiration Return type Union date str settlement_rate_option The source of spot for settlement Return type str strike_price Strike as value percent or at the money e g 62 5 95 ATM 25 ATMF 10 vol 100k pv p 10000 p 10000USD 200K BP or multiple strikes 65 4 45 8 Return type Union float str unresolved", "key": "classes/gs_quant.instrument.FXOption", "title": "FXOption" }, { "body": "FXVolatilitySwap For methods of this class see gs_quant base Priceable class FXVolatilitySwap pair None buy_sell None strike_vol None notional_currency None notional_amount None first_fixing_date None last_fixing_date None settlement_date None fixing_source None fixing_frequency None annualization_factor None calculate_mean_return 0 name None source Object representation of an FX Vol Swap Properties annualization_factor Annualization factor is the number of days used per year to compute volatility Return type float buy_sell Buy or Sell side of contract Return type Union BuySell str calculate_mean_return Indicates whether the mean return is calculated true or taken as zero false in the realized volatility computation Return type float first_fixing_date First averaging date or observation date Return type Union date str fixing_frequency Fixing frequency ex Daily Business Days Return type str fixing_source The data source to be used for observations of FX spot on each fixing Return type str instrument_quantity Return type float last_fixing_date Last averaging date or valuation date Return type Union date str notional_amount Notional amount in dollar terms Return type Union float str notional_currency Notional currency Return type Union Currency str pair EURUSD or EUR USD Type A currency pair e g Return type str provider resolution_key Return type RiskKey settlement_date Date or tenor e g 2018 09 03 3m Dec21 Return type Union date str strike_vol Volatility strike Return type Union float str unresolved", "key": "classes/gs_quant.instrument.FXVolatilitySwap", "title": "FXVolatilitySwap" }, { "body": "Forward For methods of this class see gs_quant base Priceable class Forward currency None expiration_date None notional_amount None name None source Forward cash payment Properties currency Currency ISO 4217 currency code or exchange quote modifier e g GBP vs GBp Return type Union Currency str expiration_date Date or tenor e g 2018 09 03 3m Dec21 Return type Union date str instrument_quantity Return type float notional_amount Notional amount Return type Union float str provider resolution_key Return type RiskKey unresolved", "key": "classes/gs_quant.instrument.Forward", "title": "Forward" }, { "body": "IRBasisSwap For methods of this class see gs_quant base Priceable class IRBasisSwap termination_date None notional_currency None notional_amount None effective_date None principal_exchange None payer_spread None payer_rate_option None payer_designated_maturity None payer_frequency None payer_day_count_fraction None payer_business_day_convention None receiver_spread None receiver_rate_option None receiver_designated_maturity None receiver_frequency None receiver_day_count_fraction None receiver_business_day_convention None fee 0 fee_currency None fee_payment_date None clearing_house None name None source A single currency exchange of cashflows from different interest rate indices Properties clearing_house Swap Clearing House Return type Union SwapClearingHouse str effective_date The date on which the swap becomes effective Return type Union date str fee The fee Return type float fee_currency Currency of the fee Return type Union Currency str fee_payment_date Payment date of the fee Return type Union date str instrument_quantity Return type float notional_amount Notional amount Return type Union float str notional_currency Notional currency Return type Union Currency str payer_business_day_convention The business day convention for the payer Return type Union BusinessDayConvention str payer_day_count_fraction The day count fraction for the payer Return type Union DayCountFraction str payer_designated_maturity Tenor of the payerRateOption e g 3m 6m Return type str payer_frequency The frequency of payer payments e g 6m Return type str payer_rate_option The underlying benchmark for the payer e g USD LIBOR BBA EUR EURIBOR TELERATE Return type str payer_spread Spread over the payer rate Return type Union float str principal_exchange The date on which the swap becomes effective Return type Union PrincipalExchange str provider receiver_business_day_convention The business day convention for the receiver Return type Union BusinessDayConvention str receiver_day_count_fraction The day count fraction for the receiver Return type Union DayCountFraction str receiver_designated_maturity Tenor of the receiverRateOption e g 3m 6m Return type str receiver_frequency The frequency of receiver payments e g 6m Return type str receiver_rate_option The underlying benchmark for the receiver e g USD LIBOR BBA EUR EURIBOR TELERATE Return type str receiver_spread Spread over the receiver rate Return type Union float str resolution_key Return type RiskKey termination_date The termination of the swap e g 2050 04 01 10y Return type Union date str unresolved", "key": "classes/gs_quant.instrument.IRBasisSwap", "title": "IRBasisSwap" }, { "body": "IRCMSOption For methods of this class see gs_quant base Priceable class IRCMSOption cap_floor None termination_date None notional_currency None notional_amount None effective_date None strike None index None multiplier None premium None premium_payment_date None fee 0 fee_currency None fee_payment_date None buy_sell None name None source Object representation of a constant maturity option cap floor straddle Properties buy_sell Buy or Sell side of contract Return type Union BuySell str cap_floor Structure type e g Cap Floor Straddle Binary Cap Return type str effective_date CMS option effective date e g 2019 01 01 10y Return type Union date str fee The fee Return type float fee_currency Currency of the fee Return type Union Currency str fee_payment_date Payment date of the fee Return type Union date str index The underlying benchmark i e 30yUSD Return type str instrument_quantity Return type float multiplier Multiplier Return type float notional_amount Notional amount Return type Union float str notional_currency Notional currency Return type Union Currency str premium The premium Return type Union float str premium_payment_date Payment date of the premium Return type Union date str provider resolution_key Return type RiskKey strike Strike as value percent or at the money e g 62 5 95 ATM 25 ATMF 10 vol 100k pv p 10000 p 10000USD 200K BP or multiple strikes 65 4 45 8 Return type Union float str termination_date Swap termination date e g 2030 05 01 10y Return type Union date str unresolved", "key": "classes/gs_quant.instrument.IRCMSOption", "title": "IRCMSOption" }, { "body": "IRCMSOptionStrip For methods of this class see gs_quant base Priceable class IRCMSOptionStrip cap_floor None termination_date None notional_currency None notional_amount None effective_date None strike None index None floating_rate_frequency None floating_rate_day_count_fraction None floating_rate_business_day_convention None reset_delay None multiplier None premium None premium_payment_date None fee 0 fee_currency None fee_payment_date None buy_sell None name None source Object representation of a constant maturity option strip cap floor straddle Properties buy_sell Buy or Sell side of contract Return type Union BuySell str cap_floor Structure type e g Cap Floor Straddle Binary Cap Return type str effective_date CMS option effective date e g 2019 01 01 10y Return type Union date str fee The fee Return type float fee_currency Currency of the fee Return type Union Currency str fee_payment_date Payment date of the fee Return type Union date str floating_rate_business_day_convention The business day convention of the floating rate Return type Union BusinessDayConvention str floating_rate_day_count_fraction The day count fraction of the floating rate Return type Union DayCountFraction str floating_rate_frequency Period e g 3m 1y Return type str index The underlying benchmark i e 30yUSD Return type str instrument_quantity Return type float multiplier Multiplier Return type float notional_amount Notional amount Return type Union float str notional_currency Notional currency Return type Union Currency str premium The premium Return type Union float str premium_payment_date Payment date of the premium Return type Union date str provider reset_delay Delay of the reset e g 2d Return type str resolution_key Return type RiskKey strike Strike as value percent or at the money e g 62 5 95 ATM 25 ATMF 10 vol 100k pv p 10000 p 10000USD 200K BP or multiple strikes 65 4 45 8 Return type Union float str termination_date Swap termination date e g 2030 05 01 10y Return type Union date str unresolved", "key": "classes/gs_quant.instrument.IRCMSOptionStrip", "title": "IRCMSOptionStrip" }, { "body": "IRCMSSpreadOption For methods of this class see gs_quant base Priceable class IRCMSSpreadOption cap_floor None termination_date None notional_currency None notional_amount None effective_date None strike None index1_tenor None index2_tenor None premium None premium_payment_date None fee 0 fee_currency None fee_payment_date None buy_sell None name None source Object representation of a constant maturity spread option cap floor straddle Properties buy_sell Buy or Sell side of contract Return type Union BuySell str cap_floor Structure type e g Cap Floor Straddle Return type str effective_date CMS option effective date e g 2019 01 01 10y Return type Union date str fee The fee Return type float fee_currency Currency of the fee Return type Union Currency str fee_payment_date Payment date of the fee Return type Union date str index1_tenor The tenor of the underlying benchmark to be the first element i e 30y Return type str index2_tenor The tenor of the underlying benchmark to be the second element i e 5y Return type str instrument_quantity Return type float notional_amount Notional amount Return type Union float str notional_currency Notional currency Return type Union Currency str premium The premium Return type Union float str premium_payment_date Payment date of the premium Return type Union date str provider resolution_key Return type RiskKey strike Strike as value percent or at the money e g 62 5 95 ATM 25 ATMF 10 vol 100k pv p 10000 p 10000USD 200K BP or multiple strikes 65 4 45 8 Return type Union float str termination_date Swap termination date e g 2030 05 01 10y Return type Union date str unresolved", "key": "classes/gs_quant.instrument.IRCMSSpreadOption", "title": "IRCMSSpreadOption" }, { "body": "IRCMSSpreadOptionStrip For methods of this class see gs_quant base Priceable class IRCMSSpreadOptionStrip cap_floor None termination_date None notional_currency None notional_amount None effective_date None strike None index1 None index2 None floating_rate_frequency None floating_rate_day_count_fraction None floating_rate_business_day_convention None reset_delay None premium None premium_payment_date None fee 0 fee_currency None fee_payment_date None buy_sell None name None source Object representation of a constant maturity spread option strip cap floor straddle Properties buy_sell Buy or Sell side of contract Return type Union BuySell str cap_floor Structure type e g Cap Floor Straddle Binary Cap Return type str effective_date CMS option effective date e g 2019 01 01 10y Return type Union date str fee The fee Return type float fee_currency Currency of the fee Return type Union Currency str fee_payment_date Payment date of the fee Return type Union date str floating_rate_business_day_convention The business day convention of the floating rate Return type Union BusinessDayConvention str floating_rate_day_count_fraction The day count fraction of the floating rate Return type Union DayCountFraction str floating_rate_frequency Period e g 3m 1y Return type str index1 The underlying benchmark to be the first element from i e 30yUSD Return type str index2 The underlying benchmark to be the second element from i e 5yUSD Return type str instrument_quantity Return type float notional_amount Notional amount Return type Union float str notional_currency Notional currency Return type Union Currency str premium The premium Return type Union float str premium_payment_date Payment date of the premium Return type Union date str provider reset_delay Delay of the reset e g 2d Return type str resolution_key Return type RiskKey strike Strike as value percent or at the money e g 62 5 95 ATM 25 ATMF 10 vol 100k pv p 10000 p 10000USD 200K BP or multiple strikes 65 4 45 8 Return type Union float str termination_date Swap termination date e g 2030 05 01 10y Return type Union date str unresolved", "key": "classes/gs_quant.instrument.IRCMSSpreadOptionStrip", "title": "IRCMSSpreadOptionStrip" }, { "body": "IRCap For methods of this class see gs_quant base Priceable class IRCap termination_date None notional_currency None notional_amount None effective_date None floating_rate_option None floating_rate_designated_maturity None floating_rate_frequency None floating_rate_day_count_fraction None floating_rate_business_day_convention None cap_rate None premium None premium_payment_date None fee 0 fee_currency None fee_payment_date None name None source Object representation of an interest rate cap Properties cap_rate The rate of this cap as value percent or at the money e g 62 5 95 ATM 25 ATMF Return type Union float str effective_date The date on which the cap becomes effective Return type Union date str fee The fee Return type float fee_currency Currency of the fee Return type Union Currency str fee_payment_date Payment date of the fee Return type Union date str floating_rate_business_day_convention The business day convention of the floating rate Return type Union BusinessDayConvention str floating_rate_day_count_fraction The day count fraction of the floating rate Return type Union DayCountFraction str floating_rate_designated_maturity Tenor of the floatingRateOption e g 3m 6m Return type str floating_rate_frequency The frequency of floating payments e g 3m Return type str floating_rate_option The underlying benchmark for the floating rate e g USD LIBOR BBA EUR EURIBOR TELERATE Return type str instrument_quantity Return type float notional_amount Notional amount Return type Union float str notional_currency Notional currency Return type Union Currency str premium The premium Return type Union float str premium_payment_date Payment date of the premium Return type Union date str provider resolution_key Return type RiskKey termination_date The termination of the cap e g 2025 04 01 2y Return type Union date str unresolved", "key": "classes/gs_quant.instrument.IRCap", "title": "IRCap" }, { "body": "IRFloor For methods of this class see gs_quant base Priceable class IRFloor termination_date None notional_currency None notional_amount None effective_date None floating_rate_option None floating_rate_designated_maturity None floating_rate_frequency None floating_rate_day_count_fraction None floating_rate_business_day_convention None floor_rate None premium None premium_payment_date None fee 0 fee_currency None fee_payment_date None name None source Object representation of an interest rate floor Properties effective_date The date on which the floor becomes effective Return type Union date str fee The fee Return type float fee_currency Currency of the fee Return type Union Currency str fee_payment_date Payment date of the fee Return type Union date str floating_rate_business_day_convention The business day convention of the floating rate Return type Union BusinessDayConvention str floating_rate_day_count_fraction The day count fraction of the floating rate Return type Union DayCountFraction str floating_rate_designated_maturity Tenor of the floatingRateOption e g 3m 6m Return type str floating_rate_frequency The frequency of floating payments e g 3m Return type str floating_rate_option The underlying benchmark for the floating rate e g USD LIBOR BBA EUR EURIBOR TELERATE Return type str floor_rate The rate of this floor as value percent or at the money e g 62 5 95 ATM 25 ATMF Return type Union float str instrument_quantity Return type float notional_amount Notional amount Return type Union float str notional_currency Notional currency Return type Union Currency str premium The premium Return type Union float str premium_payment_date Payment date of the premium Return type Union date str provider resolution_key Return type RiskKey termination_date The termination of the floor e g 2025 04 01 2y Return type Union date str unresolved", "key": "classes/gs_quant.instrument.IRFloor", "title": "IRFloor" }, { "body": "IRSwap For methods of this class see gs_quant base Priceable class IRSwap pay_or_receive None termination_date None notional_currency None notional_amount None effective_date None principal_exchange None floating_rate_for_the_initial_calculation_period None floating_rate_option None floating_rate_designated_maturity None floating_rate_spread None floating_rate_frequency None floating_rate_day_count_fraction None floating_rate_business_day_convention None fixed_rate None fixed_rate_frequency None fixed_rate_day_count_fraction None fixed_rate_business_day_convention None fee 0 fee_currency None fee_payment_date None clearing_house None fixed_first_stub None floating_first_stub None fixed_last_stub None floating_last_stub None fixed_holidays None floating_holidays None roll_convention None name None source A vanilla interest rate swap of fixed vs floating cashflows Properties clearing_house Swap Clearing House Return type Union SwapClearingHouse str effective_date The date on which the swap becomes effective Return type Union date str fee The fee Return type float fee_currency Currency of the fee Return type Union Currency str fee_payment_date Payment date of the fee Return type Union date str fixed_first_stub The date of the first stub for fixed leg Return type Union date str fixed_holidays The accrual calendar for fixed leg Return type str fixed_last_stub The date of the last stub for fixed leg Return type Union date str fixed_rate The coupon of the fixed leg Return type Union float str fixed_rate_business_day_convention The business day convention for the fixed rate Return type Union BusinessDayConvention str fixed_rate_day_count_fraction The day count fraction for the fixed rate Return type Union DayCountFraction str fixed_rate_frequency The frequency of fixed payments e g 6m Return type str floating_first_stub The date of the first stub for floating leg Return type Union date str floating_holidays The accrual calendar for floating leg Return type str floating_last_stub The date of the last stub for floating leg Return type Union date str floating_rate_business_day_convention The business day convention of the floating rate Return type Union BusinessDayConvention str floating_rate_day_count_fraction The day count fraction of the floating rate Return type Union DayCountFraction str floating_rate_designated_maturity Tenor of the floatingRateOption e g 3m 6m Return type str floating_rate_for_the_initial_calculation_period First fixing Return type float floating_rate_frequency The frequency of floating payments e g 3m Return type str floating_rate_option The underlying benchmark for the floating rate e g USD LIBOR BBA EUR EURIBOR TELERATE Return type str floating_rate_spread The spread over the floating rate Return type Union float str instrument_quantity Return type float notional_amount Notional amount Return type Union float str notional_currency Notional currency Return type Union Currency str pay_or_receive Pay or receive fixed Return type Union PayReceive str principal_exchange The date on which the swap becomes effective Return type Union PrincipalExchange str provider resolution_key Return type RiskKey roll_convention The roll convention Return type str termination_date The termination of the swap e g 2050 04 01 10y Return type Union date str unresolved", "key": "classes/gs_quant.instrument.IRSwap", "title": "IRSwap" }, { "body": "IRSwaption For methods of this class see gs_quant base Priceable class IRSwaption pay_or_receive None termination_date None notional_currency None effective_date None notional_amount None expiration_date None floating_rate_option None floating_rate_designated_maturity None floating_rate_spread None floating_rate_frequency None floating_rate_day_count_fraction None floating_rate_business_day_convention None fixed_rate_frequency None fixed_rate_day_count_fraction None fixed_rate_business_day_convention None strike None premium None premium_payment_date None fee 0 fee_currency None fee_payment_date None clearing_house None settlement None buy_sell None name None source Object representation of a swaption Properties buy_sell Buy or Sell side of contract Return type Union BuySell str clearing_house Swap Clearing House Return type Union SwapClearingHouse str effective_date Swaption effective date e g 2019 01 01 10y Return type Union date str expiration_date Swaption expiration date 2020 05 01 3m Return type Union date str fee The fee Return type float fee_currency Currency of the fee Return type Union Currency str fee_payment_date Payment date of the fee Return type Union date str fixed_rate_business_day_convention The business day convention for the fixed rate Return type Union BusinessDayConvention str fixed_rate_day_count_fraction The day count fraction for the fixed rate Return type Union DayCountFraction str fixed_rate_frequency The frequency of fixed payments e g 6m Return type str floating_rate_business_day_convention The business day convention of the floating rate Return type Union BusinessDayConvention str floating_rate_day_count_fraction The day count fraction of the floating rate Return type Union DayCountFraction str floating_rate_designated_maturity Tenor Return type str floating_rate_frequency The frequency of floating payments e g 3m Return type str floating_rate_option The underlying benchmark for the floating rate e g USD LIBOR BBA EUR EURIBOR TELERATE Return type str floating_rate_spread The spread over the floating rate Return type float instrument_quantity Return type float notional_amount Notional amount Return type Union float str notional_currency Notional currency Return type Union Currency str pay_or_receive Pay or receive fixed Return type str premium The premium Return type Union float str premium_payment_date Payment date of the premium Return type Union date str provider resolution_key Return type RiskKey settlement Swap Settlement Type Return type Union SwapSettlement str strike Strike as value percent or at the money e g 62 5 95 ATM 25 ATMF 10 vol 100k pv p 10000 p 10000USD 200K BP or multiple strikes 65 4 45 8 Return type Union float str termination_date Swaption termination date e g 2030 05 01 10y Return type Union date str unresolved", "key": "classes/gs_quant.instrument.IRSwaption", "title": "IRSwaption" }, { "body": "IRXccySwap For methods of this class see gs_quant base Priceable class IRXccySwap termination_date None notional_amount None effective_date None principal_exchange None payer_currency None payer_spread None payer_rate_option None payer_designated_maturity None payer_frequency None payer_day_count_fraction None payer_business_day_convention None receiver_currency None receiver_spread None receiver_rate_option None receiver_designated_maturity None receiver_frequency None receiver_day_count_fraction None receiver_business_day_convention None fee 0 fee_currency None fee_payment_date None initial_fx_rate None payer_first_stub None receiver_first_stub None payer_last_stub None receiver_last_stub None payer_holidays None receiver_holidays None notional_reset_side None name None source An exchange of cashflows from different interest rate indices Properties effective_date The date on which the swap becomes effective Return type Union date str fee The fee Return type float fee_currency Currency of the fee Return type Union Currency str fee_payment_date Payment date of the fee Return type Union date str initial_fx_rate Payment date of the fee Return type float instrument_quantity Return type float notional_amount Notional amount Return type float notional_reset_side Pay or Rec leg resetting Return type Union PayReceive str payer_business_day_convention The business day convention for the payer Return type Union BusinessDayConvention str payer_currency Payer currency Return type Union Currency str payer_day_count_fraction The day count fraction for the payer Return type Union DayCountFraction str payer_designated_maturity Tenor of the payerRateOption e g 3m 6m Return type str payer_first_stub The date of the first stub for payer leg Return type Union date str payer_frequency The frequency of payer payments e g 6m Return type str payer_holidays The accrual calendar for payer leg Return type str payer_last_stub The date of the last stub for payer leg Return type Union date str payer_rate_option The underlying benchmark for the payer e g USD LIBOR BBA EUR EURIBOR TELERATE Return type str payer_spread Spread over the payer rate Return type Union float str principal_exchange The date on which the swap becomes effective Return type Union PrincipalExchange str provider receiver_business_day_convention The business day convention for the receiver Return type Union BusinessDayConvention str receiver_currency Receiver currency Return type Union Currency str receiver_day_count_fraction The day count fraction for the receiver Return type Union DayCountFraction str receiver_designated_maturity Tenor of the receiverRateOption e g 3m 6m Return type str receiver_first_stub The date of the first stub for receiver leg Return type Union date str receiver_frequency The frequency of receiver payments e g 6m Return type str receiver_holidays The accrual calendar for receiver leg Return type str receiver_last_stub The date of the last stub for receiver leg Return type Union date str receiver_rate_option The underlying benchmark for the receiver e g USD LIBOR BBA EUR EURIBOR TELERATE Return type str receiver_spread Spread over the receiver rate Return type Union float str resolution_key Return type RiskKey termination_date The termination of the swap e g 2050 04 01 10y Return type Union date str unresolved", "key": "classes/gs_quant.instrument.IRXccySwap", "title": "IRXccySwap" }, { "body": "IRXccySwapFixFix For methods of this class see gs_quant base Priceable class IRXccySwapFixFix termination_date None notional_amount None receiver_notional_amount None effective_date None principal_exchange None payer_currency None payer_rate None payer_frequency None payer_day_count_fraction None payer_business_day_convention None receiver_currency None receiver_rate None receiver_frequency None receiver_day_count_fraction None receiver_business_day_convention None fee 0 fee_currency None fee_payment_date None name None source An exchange of fixed cashflows in different currencies Properties effective_date The date on which the swap becomes effective Return type Union date str fee The fee Return type float fee_currency Currency of the fee Return type Union Currency str fee_payment_date Payment date of the fee Return type Union date str instrument_quantity Return type float notional_amount Notional amount Return type float payer_business_day_convention The business day convention for the payer Return type Union BusinessDayConvention str payer_currency Payer currency Return type Union Currency str payer_day_count_fraction The day count fraction for the payer Return type Union DayCountFraction str payer_frequency The frequency of payer payments e g 6m Return type str payer_rate Payer rate Return type Union float str principal_exchange The date on which the swap becomes effective Return type Union PrincipalExchange str provider receiver_business_day_convention The business day convention for the receiver Return type Union BusinessDayConvention str receiver_currency Receiver currency Return type Union Currency str receiver_day_count_fraction The day count fraction for the receiver Return type Union DayCountFraction str receiver_frequency The frequency of receiver payments e g 6m Return type str receiver_notional_amount Receiver notional amount Return type float receiver_rate Receiver rate Return type Union float str resolution_key Return type RiskKey termination_date The termination of the swap e g 2050 04 01 10y Return type Union date str unresolved", "key": "classes/gs_quant.instrument.IRXccySwapFixFix", "title": "IRXccySwapFixFix" }, { "body": "IRXccySwapFixFlt For methods of this class see gs_quant base Priceable class IRXccySwapFixFlt pay_or_receive None termination_date None notional_amount None effective_date None principal_exchange None floating_rate_currency None floating_rate_for_the_initial_calculation_period None floating_rate_option None floating_rate_designated_maturity None floating_rate_spread None floating_rate_frequency None floating_rate_day_count_fraction None floating_rate_business_day_convention None fixed_rate_currency None fixed_rate None fixed_rate_frequency None fixed_rate_day_count_fraction None fixed_rate_business_day_convention None fee 0 fee_currency None fee_payment_date None fixed_first_stub None floating_first_stub None fixed_last_stub None floating_last_stub None fixed_holidays None floating_holidays None name None source An exchange of fixed vs floating cashflows in different currencies Properties effective_date The date on which the swap becomes effective Return type Union date str fee The fee Return type float fee_currency Currency of the fee Return type Union Currency str fee_payment_date Payment date of the fee Return type Union date str fixed_first_stub The date of the first stub for fixed leg Return type Union date str fixed_holidays The accrual calendar for fixed leg Return type str fixed_last_stub The date of the last stub for fixed leg Return type Union date str fixed_rate The coupon of the fixed leg Return type Union float str fixed_rate_business_day_convention The business day convention for the fixed rate Return type Union BusinessDayConvention str fixed_rate_currency Fixed rate currency Return type Union Currency str fixed_rate_day_count_fraction The day count fraction for the fixed rate Return type Union DayCountFraction str fixed_rate_frequency The frequency of fixed payments e g 6m Return type str floating_first_stub The date of the first stub for floating leg Return type Union date str floating_holidays The accrual calendar for floating leg Return type str floating_last_stub The date of the last stub for floating leg Return type Union date str floating_rate_business_day_convention The business day convention of the floating rate Return type Union BusinessDayConvention str floating_rate_currency Floating rate currency Return type Union Currency str floating_rate_day_count_fraction The day count fraction of the floating rate Return type Union DayCountFraction str floating_rate_designated_maturity Tenor of the floatingRateOption e g 3m 6m Return type str floating_rate_for_the_initial_calculation_period First fixing Return type float floating_rate_frequency The frequency of floating payments e g 3m Return type str floating_rate_option The underlying benchmark for the floating rate e g USD LIBOR BBA EUR EURIBOR TELERATE Return type str floating_rate_spread The spread over the floating rate Return type Union float str instrument_quantity Return type float notional_amount Notional amount Return type Union float str pay_or_receive Pay or receive fixed Return type Union PayReceive str principal_exchange The date on which the swap becomes effective Return type Union PrincipalExchange str provider resolution_key Return type RiskKey termination_date The termination of the swap e g 2050 04 01 10y Return type Union date str unresolved", "key": "classes/gs_quant.instrument.IRXccySwapFixFlt", "title": "IRXccySwapFixFlt" }, { "body": "InflationSwap For methods of this class see gs_quant base Priceable class InflationSwap pay_or_receive None termination_date None notional_currency None effective_date None notional_amount None index None floating_rate_business_day_convention None fixed_rate None fixed_rate_business_day_convention None fee 0 base_cpi None clearing_house None name None source A vanilla inflation swap of fixed vs floating cashflows adjusted to an inflation rate Properties base_cpi Base CPI level Return type float clearing_house Swap Clearing House Return type Union SwapClearingHouse str effective_date The date on which the swap becomes effective Return type Union date str fee The fee Return type float fixed_rate The coupon of the fixed leg Return type Union float str fixed_rate_business_day_convention The business day convention for the fixed rate Return type Union BusinessDayConvention str floating_rate_business_day_convention The business day convention of the floating rate Return type Union BusinessDayConvention str index The underlying benchmark for the floating rate e g CPI U Return type str instrument_quantity Return type float notional_amount Notional amount Return type Union float str notional_currency Notional currency Return type Union Currency str pay_or_receive Pay or receive fixed Return type Union PayReceive str provider resolution_key Return type RiskKey termination_date The termination of the swap e g 2050 04 01 10y Return type Union date str unresolved", "key": "classes/gs_quant.instrument.InflationSwap", "title": "InflationSwap" }, { "body": "Security For methods of this class see gs_quant base Priceable class Security ticker None bbid None ric None isin None cusip None prime_id None quantity 1 source A security specified by a well known identifier Properties bbid Bloomberg Id Identifier Return type str bbid_equivalent Bloomberg Equivalent Identifier Return type str bcid Bloomberg Composite Identifier Return type str cid Company Id Identifier Return type str cm_id Client Master Party Id Return type str cross Cross identifier Return type str cusip Cusip Identifier Return type str delisted Whether an asset has been delisted Return type str dollar_cross USD cross identifier for a particular currency Return type str eid EID Identifier Return type str em_id Entity Master Identifier Return type str exchange_code EEX Exchange Code Return type str gsid GSID Identifier Return type str gsid_equivalent GSID Equivalent Identifier Return type str gsideid GSID_EID Identifier Return type str gsn Goldman Sachs internal product number Return type str gss GS Symbol identifier Return type str instrument_quantity Return type float isin International Security Number Return type str jsn Japan Security Number Return type str lms_id Listed Market Symbol Return type str mdapi MDAPI Asset Return type str mdapi_class MDAPI Asset Class Return type str mic Market Identifier Code Return type str mq_symbol Marquee Symbol for generic MQ entities Return type str pl_id Platts Symbol Name Return type str plot_id Plot Identifier Return type str pnode_id Pricing node identifier sourced from Morningstar Return type str primary_country_ric Reuters Primary Country Instrument Code Identifier Return type str prime_id PrimeID Identifier Return type str provider ps_id Platts Symbol Return type str rcic Reuters Composite Instrument Code Identifier Return type str resolution_key Return type RiskKey ric Reuters Instrument Code identifier Return type str sec_name Internal Goldman Sachs security name Return type str sedol Sedol Identifier Return type str sf_id SalesForce ID Return type str simon_id SIMON product identifier Return type str tdapi TDAPI Description Return type str ticker Ticker Identifier Return type str unresolved valoren Valoren Identifier Return type str wi_id Weather Index Identifier Return type str wpk Wertpapier Kenn Nummer Return type str", "key": "classes/gs_quant.instrument.Security", "title": "Security" }, { "body": "gs_quant markets HistoricalPricingContext class HistoricalPricingContext start None end None calendars dates None is_async False is_batch False use_cache False visible_to_gs False csa_term None market_data_location None timeout None show_progress False source A context for producing valuations over multiple dates __init__ start None end None calendars dates None is_async False is_batch False use_cache False visible_to_gs False csa_term None market_data_location None timeout None show_progress False source A context for producing valuations over multiple dates Parameters start Union int date None start date end Union int date None end date defaults to today calendars Union str Tuple holiday calendars dates Optional Iterable date a custom iterable of dates is_async bool return immediately True or wait for results False defaults to False is_batch bool use for calculations expected to run longer than 3 mins to avoid timeouts It can be used with is_async True False defaults to False use_cache bool store results in the pricing cache defaults to False visible_to_gs bool are the contents of risk requests visible to GS defaults to False csa_term Optional str the csa under which the calculations are made Default is local ccy ois index market_data_location Optional str the location for sourcing market data NYC LDN or HKG defaults to LDN Examples from gs_quant instrument import IRSwap ir_swap IRSwap Pay 10y DKK with HistoricalPricingContext 10 price_f ir_swap price price_series price_f result Methods Attributes", "key": "classes/gs_quant.markets.HistoricalPricingContext", "title": "gs_quant markets HistoricalPricingContext" }, { "body": "gs_quant markets PricingContext class PricingContext pricing_date None market_data_location None is_async False is_batch False use_cache False visible_to_gs None csa_term None timeout None market None show_progress False source A context for controlling pricing and market data behaviour __init__ pricing_date None market_data_location None is_async False is_batch False use_cache False visible_to_gs None csa_term None timeout None market None show_progress False source The methods on this class should not be called directly Instead use the methods on the instruments as per the examples Parameters pricing_date Optional date the date for pricing calculations Default is today market_data_location Union PricingLocation str None the location for sourcing market data NYC LDN or HKG defaults to LDN is_async bool if True return a future immediately If False block defaults to False is_batch bool use for calculations expected to run longer than 3 mins to avoid timeouts It can be used with is_async True False defaults to False use_cache bool store results in the pricing cache defaults to False visible_to_gs Optional bool are the contents of risk requests visible to GS defaults to False csa_term Optional str the csa under which the calculations are made Default is local ccy ois index Examples To change the market data location of the default context from gs_quant markets import PricingContext import datetime as dt PricingContext current PricingContext market_data_location LDN For a blocking synchronous request from gs_quant instrument import IRCap cap IRCap 5y GBP with PricingContext price_f cap dollar_price price price_f result For an asynchronous request with PricingContext is_async True price_f cap dollar_price while not price_f done Methods Attributes", "key": "classes/gs_quant.markets.PricingContext", "title": "gs_quant markets PricingContext" }, { "body": "gs_quant markets portfolio Portfolio class Portfolio priceables name None source A collection of instruments Portfolio holds a collection of instruments in order to run pricing and risk scenarios __init__ priceables name None source Creates a portfolio object which can be used to hold instruments Parameters priceables Union PriceableImpl Iterable PriceableImpl dict None constructed with an instrument portfolio iterable of either or a dictionary where key is name and value is a priceable Methods Attributes", "key": "classes/gs_quant.markets.portfolio.Portfolio", "title": "gs_quant markets portfolio Portfolio" }, { "body": "gs_quant markets securities Asset class Asset id_ asset_class name exchange None currency None parameters None entity None source __init__ id_ asset_class name exchange None currency None parameters None entity None source Initialize self See help type self for accurate signature Methods Attributes", "key": "classes/gs_quant.markets.securities.Asset", "title": "gs_quant markets securities Asset" }, { "body": "gs_quant markets securities AssetClass class AssetClass value source Asset classification of security Assets are classified into broad groups which exhibit similar characteristics and behave in a consistent way under different market conditions __init__ Initialize self See help type self for accurate signature Attributes", "key": "classes/gs_quant.markets.securities.AssetClass", "title": "gs_quant markets securities AssetClass" }, { "body": "gs_quant markets securities AssetIdentifier class AssetIdentifier value source Asset type enumeration Enumeration of different security identifiers __init__ Initialize self See help type self for accurate signature Attributes", "key": "classes/gs_quant.markets.securities.AssetIdentifier", "title": "gs_quant markets securities AssetIdentifier" }, { "body": "gs_quant markets securities AssetType class AssetType value source Asset type enumeration Enumeration of different types of asset or security __init__ Initialize self See help type self for accurate signature Attributes", "key": "classes/gs_quant.markets.securities.AssetType", "title": "gs_quant markets securities AssetType" }, { "body": "gs_quant markets securities Index class Index id_ asset_class name exchange None currency None entity None source Index Asset Index which tracks an evolving portfolio of securities and can be traded through cash or derivatives markets __init__ id_ asset_class name exchange None currency None entity None source Initialize self See help type self for accurate signature Methods Attributes", "key": "classes/gs_quant.markets.securities.Index", "title": "gs_quant markets securities Index" }, { "body": "gs_quant markets securities SecurityMaster class SecurityMaster source Security Master The SecurityMaster class provides an interface to security lookup functions This allows querying and retrieval of different security types assets based on a variety of different identifiers through point in time lookups Uses the current PricingContext to provide as of dates if optional arguments are not provided Will return the relevant asset subclass depending on the type of the security See also Asset __init__ Initialize self See help type self for accurate signature Methods", "key": "classes/gs_quant.markets.securities.SecurityMaster", "title": "gs_quant markets securities SecurityMaster" }, { "body": "gs_quant markets securities Stock class Stock id_ name exchange None currency None entity None source Base Security Type Represents a financial asset which can be held in a portfolio or has an observable price fixing which can be referenced in a derivative transaction __init__ id_ name exchange None currency None entity None source Initialize self See help type self for accurate signature Methods Attributes", "key": "classes/gs_quant.markets.securities.Stock", "title": "gs_quant markets securities Stock" }, { "body": "gs_quant models epidemiology EpidemicModel class EpidemicModel model parameters None data None initial_conditions None fit_method leastsq error None fit_period None source Class to perform solutions and parameter fitting of epidemic models __init__ model parameters None data None initial_conditions None fit_method leastsq error None fit_period None source A class to standardize fitting and solving epidemiological models Parameters model Type CompartmentalModel the model to use currently a class in the form of SIR SEIR above parameters Optional tuple tuple parameters to use for the model defaults to the output of model get_parameters data Optional array np array data that can be used to calibrate the model initial_conditions Optional list list initial conditions for the model fit_method str str the method to use to minimize the given error Available methods are those in the lmfit minimizer minimize function Default is Levenberg Marquardt least squares minimization error Optional callable callable control which residuals and in what form to minimize for fitting fit_period Optional float float how far back to fit the data defaults to fitting all data Methods", "key": "classes/gs_quant.models.epidemiology.EpidemicModel", "title": "gs_quant models epidemiology EpidemicModel" }, { "body": "gs_quant models epidemiology SEIR class SEIR source SEIR Model __init__ Initialize self See help type self for accurate signature Methods", "key": "classes/gs_quant.models.epidemiology.SEIR", "title": "gs_quant models epidemiology SEIR" }, { "body": "gs_quant models epidemiology SIR class SIR source SIR Model __init__ Initialize self See help type self for accurate signature Methods", "key": "classes/gs_quant.models.epidemiology.SIR", "title": "gs_quant models epidemiology SIR" }, { "body": "gs_quant timeseries datetime Window class Window w None r None source Create a Window with size and ramp up to use Parameters w Union int str None window size r Union int str None ramp up value Defaults to the window size Returns new window object Usage The window size and ramp up value can either the number of observations or a string representation of the time period Examples Window size is 22 obversations and the ramp up value is 10 Window 22 10 Window size is one month and the ramp up size is one week Window 1m 1w Methods", "key": "classes/gs_quant.timeseries.datetime.Window", "title": "gs_quant timeseries datetime Window" }, { "body": "gs_quant timeseries statistics LinearRegression class LinearRegression X y fit_intercept True source Fit an Ordinary least squares OLS linear regression model Parameters X Union Series List Series observations of the explanatory variable s y Series observations of the dependant variable fit_intercept bool whether to calculate intercept in the model Usage Fit OLS Model based on observations of the explanatory variables s X and the dependant variable y If X and y are not aligned only use the intersection of dates times Examples R Squared of an OLS model x generate_series 100 y generate_series 100 r LinearRegression x y r r_squared Methods", "key": "classes/gs_quant.timeseries.statistics.LinearRegression", "title": "gs_quant timeseries statistics LinearRegression" }, { "body": "gs_quant timeseries statistics RollingLinearRegression class RollingLinearRegression X y w fit_intercept True source Fit a rolling ordinary least squares OLS linear regression model Parameters X Union Series List Series observations of the explanatory variable s y Series observations of the dependant variable w int number of observations in each rolling window Must be larger than the number of observations or explanatory variables fit_intercept bool whether to calculate intercept in the model Usage Fit OLS Model based on observations of the explanatory variables s X and the dependant variable y across a rolling window with fixed number of observations The parameters of each rolling window are stored at the end of each window If X and y are not aligned only use the intersection of dates times Examples R Squared of a rolling OLS model x generate_series 100 y generate_series 100 r RollingLinearRegression x y 5 r r_squared Methods", "key": "classes/gs_quant.timeseries.statistics.RollingLinearRegression", "title": "gs_quant timeseries statistics RollingLinearRegression" }, { "body": "gs_quant timeseries statistics SEIRModel class SEIRModel beta None gamma None sigma None s None e None i None r None n None fit True fit_period None source SEIR Compartmental model for transmission of infectious disease Parameters beta Optional float transmission rate of the infection gamma Optional float recovery rate of the infection sigma Optional float immunity rate from exposed to infected s Union Series float None number of susceptible individuals in population e Union Series float None number of exposed individuals in population i Union Series float None number of infectious individuals in population r Union Series float None number of recovered individuals in population n Union Series float None total population size end_date end date for the evolution of the model fit bool whether to fit the model to the data fit_period Optional int on how many days back to fit the model Usage Fit SEIR Model based on the population in each compartment over a given time period The SEIR models the movement of individuals between four compartments susceptible S exposed E infected I and resistant R The model calibrates parameters Parameter Description S0 initial susceptible individuals E0 initial exposed individuals I0 initial infected individuals R0 initial recovered individuals beta Transmission rate from susceptible to exposed gamma Immunity rate from infected to resistant sigma Immunity rate from exposed to infected The parameters beta gamma and sigma model how fast people move from being susceptible to exposed beta from exposed to infected sigma and subsequently from infected to resistant gamma This model can be used to predict the populations of each compartment once calibrated Methods", "key": "classes/gs_quant.timeseries.statistics.SEIRModel", "title": "gs_quant timeseries statistics SEIRModel" }, { "body": "gs_quant timeseries statistics SIRModel class SIRModel beta None gamma None s None i None r None n None fit True fit_period None source SIR Compartmental model for transmission of infectious disease Parameters beta Optional float transmission rate of the infection gamma Optional float recovery rate of the infection s Union Series float None number of susceptible individuals in population i Union Series float None number of infectious individuals in population r Union Series float None number of recovered individuals in population n Union Series float None total population size end_date end date for the evolution of the model fit bool whether to fit the model to the data fit_period Optional int on how many days back to fit the model Usage Fit SIR Model based on the population in each compartment over a given time period The SIR models the movement of individuals between three compartments susceptible S infected I and resistant R The model calibrates parameters Parameter Description S0 initial susceptible individuals I0 initial infected individuals R0 initial recovered individuals beta Transmission rate from susceptible to infected gamma Immunity rate from infected to resistant The parameters beta and gamma model how fast people move from being susceptible to infected beta and subsequently from infected to resistant gamma This model can be used to forecast the populations of each compartment once calibrated Methods", "key": "classes/gs_quant.timeseries.statistics.SIRModel", "title": "gs_quant timeseries statistics SIRModel" }, { "body": "Data Package", "key": "data", "title": "Data Package" }, { "body": "Datetime Package Date Point", "key": "datetime", "title": "Datetime Package Date Point" }, { "body": "gs_quant datetime date business_day_count business_day_count begin_dates end_dates calendars week_mask None source Determine the number of business days between begin_dates and end_dates Parameters begin_dates Union date Iterable date A date or collection of beginning dates end_dates Union date Iterable date A date or collection of end dates calendars Union str Tuple str Calendars to use for holidays week_mask Optional str Which days are considered weekends defaults to Saturday and Sunday Return type Union int Tuple int Returns An int or tuple of ints representing the number of business days between begin_dates and end_dates Examples import datetime as dt today dt date today bus_days business_day_count today today dt timedelta days 7", "key": "functions/gs_quant.datetime.date.business_day_count", "title": "gs_quant datetime date business_day_count" }, { "body": "gs_quant datetime date business_day_offset business_day_offset dates offsets roll raise calendars week_mask None source Apply offsets to the dates and move to the nearest business date Parameters dates Union date Iterable date The input date or dates offsets Union int Iterable int The number of days by which to adjust the dates roll str Which direction to roll in order to get to the nearest business date calendars Union str Tuple str Calendars to use for holidays week_mask Optional str Which days are considered weekends defaults to Saturday and Sunday Return type Union date Iterable date Returns A date if dates is a single date or tuple of dates adjusted by the offsets Examples import datetime as dt prev_bus_date business_day_offset dt date today 1 roll forward", "key": "functions/gs_quant.datetime.date.business_day_offset", "title": "gs_quant datetime date business_day_offset" }, { "body": "gs_quant datetime date date_range date_range begin end calendars week_mask None source Construct a range of dates Parameters begin Union int date Beginning date or int An int will be interpreted as the number of business days before end which must be a date end Union int date End date or int An int will be interpreted as the number of business days after begin which must be a date calendars Union str Tuple str Calendars to use for holidays week_mask Optional str Which days are considered weekends defaults to Saturday and Sunday Return type Iterable date Returns A generator of dates import datetime as dt today dt date today dates tuple date_range 5 today for date in date_range dt date 2019 1 1 dt date 2019 2 1 print date", "key": "functions/gs_quant.datetime.date.date_range", "title": "gs_quant datetime date date_range" }, { "body": "gs_quant datetime date is_business_day is_business_day dates calendars week_mask None source Determine whether each date in dates is a business day Parameters dates Union date Iterable date The input date or dates calendars Union str Tuple str Calendars to use for holidays week_mask Optional str Which days are considered weekends defaults to Saturday and Sunday Return type Union bool Tuple bool Returns True False if dates is a single date A tuple indicating True False for each date if dates is an iterable Examples import datetime as dt is_business_day dt date today is_business_day dt date 2019 7 4 calendars NYSE", "key": "functions/gs_quant.datetime.date.is_business_day", "title": "gs_quant datetime date is_business_day" }, { "body": "gs_quant datetime date prev_business_date prev_business_date dates datetime date 2021 1 5 calendars week_mask None source Returns the previous business date for a given date or date series defaulting to today Parameters dates Union date Iterable date The input date or dates defaults to today calendars Union str Tuple str Calendars to use for holidays week_mask Optional str Which days are considered weekends defaults to Saturday and Sunday Return type Union date Iterable date Returns A date if dates is a single date or tuple of dates adjusted by the offset of one day Example import datetime as dt prev_bus_date prev_business_date", "key": "functions/gs_quant.datetime.date.prev_business_date", "title": "gs_quant datetime date prev_business_date" }, { "body": "gs_quant datetime point point_sort_order point_sort_order point ref_date datetime date 2021 1 5 source Calculates a number that can be used to sort Mkt Points by it Parameters point str The point string from MarketDataCoordinate ref_date date Reference date normally the pricing date Return type float Returns The number of days from the reference date to the date specified by the point string Examples import datetime as dt days point_sort_order point Dec20 ref_date dt date today", "key": "functions/gs_quant.datetime.point.point_sort_order", "title": "gs_quant datetime point point_sort_order" }, { "body": "gs_quant timeseries algebra abs_ abs_ x source Absolute value of each element in series Parameters x Series date based time series of prices Return type Series Returns date based time series of absolute value Usage Return the absolute value of X For each value in time series X_t return X_t if X_t is greater than or equal to 0 otherwise return X_t R_t X_t Equivalent to R_t sqrt X_t 2 Examples Generate price series and take absolute value of X_t 100 prices generate_series 100 100 abs_ prices See also exp sqrt", "key": "functions/gs_quant.timeseries.algebra.abs_", "title": "gs_quant timeseries algebra abs_" }, { "body": "gs_quant timeseries algebra add add x y method Interpolate STEP step source Add two series or scalars Parameters x Union Series Real timeseries or scalar y Union Series Real timeseries or scalar method Interpolate interpolation method default step Only used when both x and y are timeseries Return type Union Series Real Returns timeseries of x y or sum of the given real numbers Usage Add two series or scalar variables with the given interpolation method R_t X_t Y_t Alignment operators Method Behavior intersect Resultant series only has values on the intersection of dates Values for dates present in only one series will be ignored nan Resultant series has values on the union of dates in both series Values for dates only available in one series will be treated as nan in the other series and therefore in the resultant series zero Resultant series has values on the union of dates in both series Values for dates only available in one series will be treated as zero in the other series step Resultant series has values on the union of dates in both series Values for dates only available in one series will be interpolated via step function in the other series time Resultant series have values on the union of dates times Missing values surrounded by valid values will be interpolated given length of interval Input series must use DateTimeIndex Examples Add two series a generate_series 100 b generate_series 100 add a b Interpolate STEP See also subtract", "key": "functions/gs_quant.timeseries.algebra.add", "title": "gs_quant timeseries algebra add" }, { "body": "gs_quant timeseries algebra and_ and_ series source Logical and of two or more boolean series Parameters series Series input series Return type Series Returns result series of numeric type with booleans represented as 1s and 0s", "key": "functions/gs_quant.timeseries.algebra.and_", "title": "gs_quant timeseries algebra and_" }, { "body": "gs_quant timeseries algebra ceil ceil x value 0 source Cap series at maximum value Parameters x Series date based time series of prices value float maximum value Return type Series Returns date based time series of maximum value Usage Returns series where all values are less than or equal to the maximum value R_t min X_t value See Floor and Ceil functions for more details Examples Generate price series and floor all values at 100 prices generate_series 100 floor prices 100 See also floor", "key": "functions/gs_quant.timeseries.algebra.ceil", "title": "gs_quant timeseries algebra ceil" }, { "body": "gs_quant timeseries algebra divide divide x y method Interpolate STEP step source Divide two series or scalars Parameters x Union Series Real timeseries or scalar y Union Series Real timeseries or scalar method Interpolate interpolation method default step Only used when both x and y are timeseries Return type Union Series Real Returns timeseries of x y or quotient of the given real numbers Usage Divide two series or scalar variables applying the given interpolation method R_t X_t Y_t Alignment operators Method Behavior intersect Resultant series only has values on the intersection of dates Values for dates present in only one series will be ignored nan Resultant series has values on the union of dates in both series Values for dates only available in one series will be treated as nan in the other series and therefore in the resultant series zero Resultant series has values on the union of dates in both series Values for dates only available in one series will be treated as zero in the other series step Resultant series has values on the union of dates in both series Values for dates only available in one series will be interpolated via step function in the other series time Resultant series have values on the union of dates times Missing values surrounded by valid values will be interpolated given length of interval Input series must use DateTimeIndex Examples Divide two series a generate_series 100 b generate_series 100 divide a b Interpolate STEP See also multiply", "key": "functions/gs_quant.timeseries.algebra.divide", "title": "gs_quant timeseries algebra divide" }, { "body": "gs_quant timeseries algebra exp exp x source Exponential of series Parameters x Series timeseries Return type Series Returns exponential of each element Usage For each element in the series X_t raise e Euler s number to the power of X_t Euler s number is the base of the natural logarithm ln R_t e X_t Examples Raise e to the power 1 Returns Euler s number approximately 2 71828 exp 1 See also log", "key": "functions/gs_quant.timeseries.algebra.exp", "title": "gs_quant timeseries algebra exp" }, { "body": "gs_quant timeseries algebra filter_ filter_ x operator None value None source Removes values where comparison with the operator and value combination results in true defaults to removing missing values from the series Parameters x Series timeseries operator Optional FilterOperator FilterOperator describing logic for value removal e g less_than value Optional Real number indicating value s to remove from the series Return type Series Returns timeseries with specified values removed Usage Remove each value determined by operator and value from timeseries where that expression yields true Examples Remove 0 from time series prices generate_series 100 filter_ prices FilterOperator EQUALS 0 Remove positive numbers from time series prices generate_series 100 filter_ prices FilterOperator GREATER 0 Remove missing values from time series prices generate_series 100 filter_ prices", "key": "functions/gs_quant.timeseries.algebra.filter_", "title": "gs_quant timeseries algebra filter_" }, { "body": "gs_quant timeseries algebra floor floor x value 0 source Floor series at minimum value Parameters x Series date based time series of prices value float minimum value Return type Series Returns date based time series of maximum value Usage Returns series where all values are greater than or equal to the minimum value R_t max X_t value See Floor and Ceil functions for more details Examples Generate price series and floor all values at 100 prices generate_series 100 floor prices 100 See also ceil", "key": "functions/gs_quant.timeseries.algebra.floor", "title": "gs_quant timeseries algebra floor" }, { "body": "gs_quant timeseries algebra floordiv floordiv x y method Interpolate STEP step source Floor divide two series or scalars Parameters x Union Series Real timeseries or scalar y Union Series Real timeseries or scalar method Interpolate interpolation method default step Only used for operating two series Return type Union Series Real Returns timeseries of x y or quotient of the floor division of the given real numbers Usage Divide two series or scalar variables applying the given interpolation method R_t X_t Y_t Alignment operators Method Behavior intersect Resultant series only has values on the intersection of dates Values for dates present in only one series will be ignored nan Resultant series has values on the union of dates in both series Values for dates only available in one series will be treated as nan in the other series and therefore in the resultant series zero Resultant series has values on the union of dates in both series Values for dates only available in one series will be treated as zero in the other series step Resultant series has values on the union of dates in both series Values for dates only available in one series will be interpolated via step function in the other series Examples Floor divide two series a generate_series 100 b generate_series 100 floordiv a b Interpolate STEP See also divide", "key": "functions/gs_quant.timeseries.algebra.floordiv", "title": "gs_quant timeseries algebra floordiv" }, { "body": "gs_quant timeseries algebra if_ if_ flags x y source Returns a series s For i in the index of flags s i x i if flags i 1 else y i Parameters flags Series series of 1s and 0s x Union Series float values to use when flag is 1 y Union Series float values to use when flag is 0 Return type Series Returns result series", "key": "functions/gs_quant.timeseries.algebra.if_", "title": "gs_quant timeseries algebra if_" }, { "body": "gs_quant timeseries algebra log log x source Natural logarithm of series Parameters x Series timeseries Return type Series Returns series with exponential of each element Usage For each element in the series X_t return the natural logarithm ln of X_t The natural logarithm is the logarithm in base e R_t log X_t This function is the inverse of the exponential function More information on logarithms Examples Take natural logarithm of 3 log 3 See also exp", "key": "functions/gs_quant.timeseries.algebra.log", "title": "gs_quant timeseries algebra log" }, { "body": "gs_quant timeseries algebra multiply multiply x y method Interpolate STEP step source Multiply two series or scalars Parameters x Union Series Real timeseries or scalar y Union Series Real timeseries or scalar method Interpolate interpolation method default step Only used when both x and y are timeseries Return type Union Series Real Returns timeseries of x y or product of the given real numbers Usage Multiply two series or scalar variables applying the given interpolation method R_t X_t times Y_t Alignment operators Method Behavior intersect Resultant series only has values on the intersection of dates Values for dates present in only one series will be ignored nan Resultant series has values on the union of dates in both series Values for dates only available in one series will be treated as nan in the other series and therefore in the resultant series zero Resultant series has values on the union of dates in both series Values for dates only available in one series will be treated as zero in the other series step Resultant series has values on the union of dates in both series Values for dates only available in one series will be interpolated via step function in the other series time Resultant series have values on the union of dates times Missing values surrounded by valid values will be interpolated given length of interval Input series must use DateTimeIndex Examples Multiply two series a generate_series 100 b generate_series 100 multiply a b Interpolate STEP See also divide", "key": "functions/gs_quant.timeseries.algebra.multiply", "title": "gs_quant timeseries algebra multiply" }, { "body": "gs_quant timeseries algebra not_ not_ series source Logical negation of a single boolean series Parameters series Series single input series Return type Series Returns result series of numeric type with booleans represented as 1s and 0s", "key": "functions/gs_quant.timeseries.algebra.not_", "title": "gs_quant timeseries algebra not_" }, { "body": "gs_quant timeseries algebra or_ or_ series source Logical or of two or more boolean series Parameters series Series input series Return type Series Returns result series of numeric type with booleans represented as 1s and 0s", "key": "functions/gs_quant.timeseries.algebra.or_", "title": "gs_quant timeseries algebra or_" }, { "body": "gs_quant timeseries algebra power power x y 1 source Raise each element in series to power Parameters x Series timeseries y float value Return type Series Returns date based time series of square roots Usage Raise each value in time series X_t to the power y R_t X_t y Examples Generate price series and raise each value to the power 2 prices generate_series 100 power prices 2 See also sqrt", "key": "functions/gs_quant.timeseries.algebra.power", "title": "gs_quant timeseries algebra power" }, { "body": "gs_quant timeseries algebra repeat repeat x n 1 source Repeats values for days where data is missing For any date with missing data the last recorded value is used Optionally downsamples the result such that there are data points every n days Parameters x Series date based timeseries n int desired frequency of output Return type Series Returns a timeseries that has been forward filled and optionally downsampled Usage Fill missing values with last seen value e g to combine daily with weekly or monthly data", "key": "functions/gs_quant.timeseries.algebra.repeat", "title": "gs_quant timeseries algebra repeat" }, { "body": "gs_quant timeseries algebra smooth_spikes smooth_spikes x threshold source Smooth out the spikes of a series If a point is larger smaller than 1 threshold times both neighbors replace it with the average of those neighbours Note the first and last points in the input series are dropped Parameters x Series timeseries threshold float minimum increment to trigger filter Return type Series Returns smoothed timeseries Usage Returns series where values that exceed the threshold relative to both neighbors are replaced Examples Generate price series and smooth spikes over a threshold of 0 5 prices generate_series 100 smooth_spikes prices 0 5 See also exponential_moving_average", "key": "functions/gs_quant.timeseries.algebra.smooth_spikes", "title": "gs_quant timeseries algebra smooth_spikes" }, { "body": "gs_quant timeseries algebra sqrt sqrt x source Square root of a each element in a series or b a real number Parameters x Union Real Series date based time series of prices or real number Return type Union Real Series Returns date based time series of square roots or square root of given number Usage Return the square root of each value in time series X_t R_t sqrt X_t Examples Generate price series and take square root of each value prices generate_series 100 sqrt prices See also pow", "key": "functions/gs_quant.timeseries.algebra.sqrt", "title": "gs_quant timeseries algebra sqrt" }, { "body": "gs_quant timeseries algebra subtract subtract x y method Interpolate STEP step source Add two series or scalars Parameters x Union Series Real timeseries or scalar y Union Series Real timeseries or scalar method Interpolate index alignment operator default intersect Only used when both x and y are timeseries Return type Union Series Real Returns timeseries of x y or difference between the given real numbers Usage Subtracts one series or scalar from another applying the given interpolation method R_t X_t Y_t Alignment operators Method Behavior intersect Resultant series only has values on the intersection of dates Values for dates present in only one series will be ignored nan Resultant series has values on the union of dates in both series Values for dates only available in one series will be treated as nan in the other series and therefore in the resultant series zero Resultant series has values on the union of dates in both series Values for dates only available in one series will be treated as zero in the other series step Resultant series has values on the union of dates in both series Values for dates only available in one series will be interpolated via step function in the other series time Resultant series have values on the union of dates times Missing values surrounded by valid values will be interpolated given length of interval Input series must use DateTimeIndex Examples Subtract one series from another a generate_series 100 b generate_series 100 subtract a b Interpolate STEP See also add", "key": "functions/gs_quant.timeseries.algebra.subtract", "title": "gs_quant timeseries algebra subtract" }, { "body": "gs_quant timeseries algebra weighted_sum weighted_sum series weights source Calculate a weighted sum Parameters series List Series list of time series weights list list of weights Return type Series Returns time series of weighted average Usage Calculate a weighted sum e g for a basket Examples Generate price series and get a sum weights 70 30 prices1 generate_series 100 prices2 generate_series 100 mybasket weighted_sum prices1 prices2 0 7 0 3 See also basket", "key": "functions/gs_quant.timeseries.algebra.weighted_sum", "title": "gs_quant timeseries algebra weighted_sum" }, { "body": "gs_quant timeseries analysis count count x source Count observations in series Parameters x Series time series Return type Series Returns number of observations Usage Count the number of valid observations in a series R_t R_ t 1 1 if X_t is not NaN and R_t R_ t 1 0 if X_t is NaN Examples Count observations in series series generate_series 100 count count series See also sum", "key": "functions/gs_quant.timeseries.analysis.count", "title": "gs_quant timeseries analysis count" }, { "body": "gs_quant timeseries analysis diff diff x obs 1 source Diff observations with given lag Parameters x Series time series of prices obs int number of observations to lag Return type Series Returns date based time series of return Usage Compute the difference in series values over a given lag R_t X_t X_ t obs where obs is the number of observations to lag series in diff function Examples Diff prices levels series generate_series 100 returns diff series See also lag", "key": "functions/gs_quant.timeseries.analysis.diff", "title": "gs_quant timeseries analysis diff" }, { "body": "gs_quant timeseries analysis first first x source First value of series Parameters x Series time series Return type Series Returns time series of first value Usage Return series with first value of X for all dates R_t X_0 where X_0 is the first value in the series Examples Last value of series series generate_series 100 returns first series See also last", "key": "functions/gs_quant.timeseries.analysis.first", "title": "gs_quant timeseries analysis first" }, { "body": "gs_quant timeseries analysis lag lag x obs 1 mode LagMode EXTEND extend source Lag timeseries by a number of observations or a relative date Parameters x Series timeseries of prices obs Union Window int str non zero integer number of observations or relative date e g 90d 1d 1m 1y mode LagMode whether to extend series index into the future Return type Series Returns date based time series of return Usage Shift the series backwards by a specified number of observations R_t X_ t obs where obs is the number of observations to lag series Examples Lag series by 2 observations prices generate_series 100 lagged lag prices 2 Lag series by 1 year prices generate_series 100 lagged lag prices 1y See also diff", "key": "functions/gs_quant.timeseries.analysis.lag", "title": "gs_quant timeseries analysis lag" }, { "body": "gs_quant timeseries analysis last last x source Last value of series as a series Parameters x Series time series Return type Series Returns time series of last value Usage Return series with last value of X for all dates R_t X_T where X_T is the last value in the series Examples Last value of series series generate_series 100 returns last series See also first", "key": "functions/gs_quant.timeseries.analysis.last", "title": "gs_quant timeseries analysis last" }, { "body": "gs_quant timeseries analysis last_value last_value x source Last value of series as a scalar Parameters x Series time series Return type Union int float Returns last value Usage Return a scalar value X_T where T is the last index value in the series Examples Last value of series series generate_series 100 lv last_value series See also last", "key": "functions/gs_quant.timeseries.analysis.last_value", "title": "gs_quant timeseries analysis last_value" }, { "body": "weighted_sum", "key": "functions/gs_quant.timeseries.analysis.weighted_sum", "title": "weighted_sum" }, { "body": "gs_quant timeseries backtesting basket basket series weights costs None rebal_freq RebalFreq DAILY daily return_type ReturnType EXCESS_RETURN excess_return source Calculates a basket return series Parameters series list list of time series of instrument prices weights list list of weights costs Optional list list of execution costs in decimal defaults to costs of 0 rebal_freq RebalFreq rebalancing frequency Daily or Monthly return_type ReturnType return type of underlying instruments only excess return is supported Return type Series Returns time series of the resulting basket Usage Calculates a basket return series Examples Generate price series and combine them in a basket weights 70 30 which rebalances monthly and assumes execution costs 5bps and 10bps each time the constituents are traded prices1 generate_series 100 prices2 generate_series 100 mybasket basket prices1 prices2 0 7 0 3 0 0005 0 001 monthly See also prices", "key": "functions/gs_quant.timeseries.backtesting.basket", "title": "gs_quant timeseries backtesting basket" }, { "body": "gs_quant timeseries datetime align align x y method Interpolate INTERSECT intersect source Align dates of two series or scalars Parameters x Union Series Real first timeseries or scalar y Union Series Real second timeseries or scalar method Interpolate interpolation method default intersect Only used when both x and y are timeseries Return type Union List Series List Real Returns timeseries with specified dates or two scalars from the input Usage Align the dates of two series using the specified interpolation method Returns two series with dates based on the method of interpolation for example can be used to intersect the dates of two series union dates with a defined manner to compute missing values Interpolation methods Type Behavior intersect Resultant series only have values on the intersection of dates times nan Resultant series have values on the union of dates times Values will be NaN for dates or times only present in the other series zero Resultant series have values on the union of dates times Values will be zero for dates or times only present in the other series step Resultant series have values on the union of dates times Each series will use the value of the previous valid point if requested date does not exist Values prior to the first date will be equivalent to the first available value time Resultant series have values on the union of dates times Missing values surrounded by valid values will be interpolated given length of interval Input series must use DateTimeIndex Examples Stepwize interpolation of series based on dates in second series a generate_series 100 b generate_series 100 align a b See also sub", "key": "functions/gs_quant.timeseries.datetime.align", "title": "gs_quant timeseries datetime align" }, { "body": "gs_quant timeseries datetime date_range date_range x start_date end_date weekdays_only False source Create a time series from a sub range of dates in an existing time series Parameters x Series time series start_date Union date int start date for the sliced time series If integer number of observations after the first end_date Union date int end date for the sliced time series If integer number of observations before the last weekdays_only bool whether to include only weekdays in the sliced ranges Return type Series Returns sliced time series Usage Returns a restricted sliced time series based on start and end dates Y_t R_t _ start t end Examples Slice the first and last week of a time series series generate_series 100 sliced_series date_range series 7 7 See also day func lag", "key": "functions/gs_quant.timeseries.datetime.date_range", "title": "gs_quant timeseries datetime date_range" }, { "body": "gs_quant timeseries datetime day day x source Day of each value in series Parameters x Series time series Return type Series Returns day of observations Usage Returns the day as a numeric value for each observation in the series Y_t day t Day of the time or date is the integer day number within the month e g 1 31 Examples Day for observations in series series generate_series 100 days day series See also month year", "key": "functions/gs_quant.timeseries.datetime.day", "title": "gs_quant timeseries datetime day" }, { "body": "gs_quant timeseries datetime interpolate interpolate x dates None method Interpolate INTERSECT intersect source Interpolate over specified dates or times Parameters x Series timeseries to interpolate dates Union List date List time Series None array of dates times or another series to interpolate method Interpolate interpolation method default intersect Return type Series Returns timeseries with specified dates Usage Interpolate the series X over the dates specified by the dates parameter This can be an array of dates or another series in which case the index of the series will be used to specify dates Interpolation methods Type Behavior intersect Resultant series only has values on the intersection of dates times Will only contain intersection of valid dates times in the series nan Resultant series only has values on the intersection of dates times Value will be NaN for dates not present in the series zero Resultant series has values on all requested dates times The series will have a value of zero where the requested date or time was not present in the series step Resultant series has values on all requested dates times The series will use the value of the previous valid point if requested date does not exist Values prior to the first date will be equivalent to the first available value Examples Stepwize interpolation of series based on dates in second series a generate_series 100 b generate_series 100 interpolate a b Interpolate INTERSECT See also sub", "key": "functions/gs_quant.timeseries.datetime.interpolate", "title": "gs_quant timeseries datetime interpolate" }, { "body": "gs_quant timeseries datetime month month x source Month of each value in series Parameters x Series time series Return type Series Returns month of observations Usage Returns the month as a numeric value for each observation in the series Y_t month t Month of the time or date is the integer month number e g 1 12 Examples Day for observations in series series generate_series 100 days month series See also day year", "key": "functions/gs_quant.timeseries.datetime.month", "title": "gs_quant timeseries datetime month" }, { "body": "gs_quant timeseries datetime prepend prepend x source Prepend data series Parameters x List Series an array of timeseries Return type Series Returns concatenated timeseries Usage For input series x_1 x_2 X_n takes data from series X_i until the first date for which X_ i 1 has data useful when a higher quality series has a shorter history than a lower quality series Examples Prepend two series x generate_series 100 y generate_series 100 prepend x y See also union", "key": "functions/gs_quant.timeseries.datetime.prepend", "title": "gs_quant timeseries datetime prepend" }, { "body": "gs_quant timeseries datetime quarter quarter x source Quarter of each value in series Parameters x Series time series Return type Series Returns quarter of observations Usage Returns the quarter as a numeric value for each observation in the series Y_t quarter t Quarter of the time or date is the integer quarter number e g 1 2 3 4 Examples Quarter for observations in series series generate_series 100 days quarter series See also day month", "key": "functions/gs_quant.timeseries.datetime.quarter", "title": "gs_quant timeseries datetime quarter" }, { "body": "gs_quant timeseries datetime union union x source Fill in missing dates or times of one series with another Parameters x List Series an array of timeseries Return type Series Returns combined series Usage Starting from i 1 takes points from series x_i Where points are missing from x_i returns points from x_ i 1 Examples Union of two series x generate_series 100 y generate_series 100 union x y See also prepend", "key": "functions/gs_quant.timeseries.datetime.union", "title": "gs_quant timeseries datetime union" }, { "body": "gs_quant timeseries datetime value value x date method Interpolate STEP step source Value at specified date or time Parameters x Series timeseries date Union date time requested date or time method Interpolate interpolation method default step Return type Series Returns value at specified date or time Usage Returns the value of series X at the specified date Y_t X_ date If the requested date or time is not present in the series the value function will return the value from the previous available date or time by default Caller can specify other interpolation styles via the method param Interpolation methods Type Behavior intersect Only returns a value for valid dates nan Value will be NaN for dates not present in the series zero Value will be zero for dates not present in the series step Value of the previous valid point if requested date does not exist Values prior to the first date will be equivalent to the first available value Examples Value of series on 5Mar18 a generate_series 100 value a date 2019 1 3 See also interpolate", "key": "functions/gs_quant.timeseries.datetime.value", "title": "gs_quant timeseries datetime value" }, { "body": "gs_quant timeseries datetime weekday weekday x source Weekday of each value in series Parameters x Series time series Return type Series Returns weekday of observations Usage Returns the weekday as a numeric value for each observation in the series Y_t weekday t Weekday of the time or date is the integer day of the week e g 0 6 where 0 represents Monday Examples Weekday for observations in series series generate_series 100 days weekday series See also day month", "key": "functions/gs_quant.timeseries.datetime.weekday", "title": "gs_quant timeseries datetime weekday" }, { "body": "gs_quant timeseries datetime year year x source Year of each value in series Parameters x Series time series Return type Series Returns year of observations Usage Returns the year as a numeric value for each observation in the series Y_t year t Year of the time or date is the integer year number e g 2019 2020 Examples Year for observations in series series generate_series 100 days year series See also day month", "key": "functions/gs_quant.timeseries.datetime.year", "title": "gs_quant timeseries datetime year" }, { "body": "gs_quant timeseries econometrics annualize annualize x source Annualize series based on sample observation frequency Parameters x Series time series of prices Return type Series Returns date based time series of annualized values Usage Based on number of days between observations will determine an annualization factor and then adjust values accordingly Useful for annualizing daily or monthly returns Y_t X_t sqrt F Annualization factors as follows based on period implied by observations Period Annualization Factor F Daily 252 Weekly 52 Bi Weekly 26 Monthly 12 Quarterly 4 Annually 1 Examples Annualize daily returns series prices generate_series 100 ann annualize returns prices See also returns", "key": "functions/gs_quant.timeseries.econometrics.annualize", "title": "gs_quant timeseries econometrics annualize" }, { "body": "gs_quant timeseries econometrics beta beta x b w gs_quant timeseries helper Window object prices True source Rolling beta of price series and benchmark Parameters x Series time series of prices b Series time series of benchmark prices w Union Window int str Window int or str size of window and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value If w is a string it should be a relative date like 1m 1d etc Window size defaults to length of series prices bool True if input series are prices False if they are returns Return type Series Returns date based time series of beta Usage Calculate rolling beta beta_t of a series to a benchmark over a given window R_t alpha_t beta S_t epsilon_t Calculated as beta_t frac sum_ i t w 1 t Cov R_t S_t Var S_t where N is the number of observations in each rolling window w and R_t and S_t are the simple returns for each series on time t R_t frac X_t X_ t 1 1 and S_t frac b_t b_ t 1 1 If prices False assumes returns are provided R_t X_t and S_t b_t Cov R_t S_t and Var S_t are the mean and variance of series R_t and S_t over the same window If window is not provided computes beta over the full series Examples Compute rolling 1 month 22 business day beta of two price series series generate_series 100 benchmark generate_series 100 b beta series benchmark 22 See also var cov correlation returns", "key": "functions/gs_quant.timeseries.econometrics.beta", "title": "gs_quant timeseries econometrics beta" }, { "body": "gs_quant timeseries econometrics change change x source Arithmetic series normalization Parameters x Series time series Return type Series Returns normalized time series Usage Compute difference of every value from the initial value of x Y_t X_t X_0 where X_0 is the first value in the series Examples Change in level from initial value series generate_series 100 returns change series See also index", "key": "functions/gs_quant.timeseries.econometrics.change", "title": "gs_quant timeseries econometrics change" }, { "body": "gs_quant timeseries econometrics correlation correlation x y w gs_quant timeseries helper Window object type_ SeriesType PRICES prices source Rolling correlation of two price series Parameters x Series price series y Series price series w Union Window int str Window int or str size of window and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value If w is a string it should be a relative date like 1m 1d etc Window size defaults to length of series type type of both input series prices or returns Return type Series Returns date based time series of correlation Usage Calculate rolling realized correlation rho_t of two price series over a given window rho_t frac sum_ i t w 1 t R_t overline R_t Y_t overline S_t N 1 sigma R_t sigma S_t where N is the number of observations in each rolling window w and R_t and S_t are the simple returns for each series on time t If prices are provided R_t frac X_t X_ t 1 1 and S_t frac Y_t Y_ t 1 1 If returns are provided R_t X_t and S_t Y_t overline R_t overline S_t are the mean values and sigma R_ t and sigma S_ t are the sample standard deviations of series R_t and S_t over the same window If window is not provided computes realized correlation over the full series Examples Compute rolling 1 month 22 business day correlation of price series series1 generate_series 100 series2 generate_series 100 corr correlation series1 series2 22 See also std returns", "key": "functions/gs_quant.timeseries.econometrics.correlation", "title": "gs_quant timeseries econometrics correlation" }, { "body": "gs_quant timeseries econometrics excess_returns_ excess_returns_ price_series currency RiskFreeRateCurrency USD USD source Calculate excess returns Parameters price_series Series price series currency RiskFreeRateCurrency currency for risk free rate defaults to USD Return type Series Returns excess returns Usage Given a price series P and risk free rate R excess returns E are defined as E_t E_ t 1 P_t P_ t 1 1 R D_t D_ t 1 360 The Actual 360 day count convention is used Examples Get excess returns from a price series er excess_returns generate_series 100 USD", "key": "functions/gs_quant.timeseries.econometrics.excess_returns_", "title": "gs_quant timeseries econometrics excess_returns_" }, { "body": "gs_quant timeseries econometrics index index x initial 1 source Geometric series normalization Parameters x Series time series initial int initial value Return type Series Returns normalized time series Usage Divides every value in x by the initial value of x Y_t initial X_t X_0 where X_0 is the first value in the series Examples Normalize series to 1 series generate_series 100 returns index series See also returns", "key": "functions/gs_quant.timeseries.econometrics.index", "title": "gs_quant timeseries econometrics index" }, { "body": "gs_quant timeseries econometrics max_drawdown max_drawdown x w gs_quant timeseries helper Window object source Compute the maximum peak to trough drawdown over a rolling window Parameters x Series time series w Union Window int str Window int or str size of window and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value If w is a string it should be a relative date like 1m 1d etc Window size defaults to length of series Return type Series Returns time series of rolling maximum drawdown Examples Compute the maximum peak to trough drawdown series generate_series 100 max_drawdown series See also returns", "key": "functions/gs_quant.timeseries.econometrics.max_drawdown", "title": "gs_quant timeseries econometrics max_drawdown" }, { "body": "gs_quant timeseries econometrics prices prices series initial 1 type Returns SIMPLE simple source Calculate price levels from returns series Parameters series Series time series of returns initial int initial price level type Returns returns type simple logarithmic or absolute Return type Series Returns date based time series of return Usage Compute price levels from returns series based on the value of type Type Description simple Simple arithmetic returns logarithmic Logarithmic returns absolute Absolute returns Simple Compute asset price series from simple returns Y_t 1 X_ t 1 Y_ t 1 where X_t is the asset price at time t and Y_0 initial Logarithmic Compute asset price series from logarithmic returns Y_t e X_ t 1 Y_ t 1 where X_t is the asset price at time t and Y_0 initial Absolute Compute asset price series from absolute returns Y_t X_ t 1 Y_ t 1 where X_t is the asset price at time t and Y_0 initial Examples Generate price series and take compute returns series generate_series 100 returns prices returns series See also returns product exp", "key": "functions/gs_quant.timeseries.econometrics.prices", "title": "gs_quant timeseries econometrics prices" }, { "body": "gs_quant timeseries econometrics returns returns series obs 1 type Returns SIMPLE simple source Calculate returns from price series Parameters series Series time series of prices obs int number of observations type Returns returns type simple logarithmic or absolute Return type Series Returns date based time series of return Usage Compute returns series from price levels based on the value of type Type Description simple Simple arithmetic returns logarithmic Logarithmic returns absolute Absolute returns Simple Simple geometric change in asset prices which can be aggregated across assets Y_t frac X_t X_ t obs 1 where X_t is the asset price at time t Logarithmic Natural logarithm of asset price changes which can be aggregated through time Y_t log X_t log X_ t obs where X_t is the asset price at time t Absolute Absolute change in asset prices Y_t X_t X_ t obs where X_t is the asset price at time t Examples Generate price series and take compute returns prices generate_series 100 returns returns prices See also prices", "key": "functions/gs_quant.timeseries.econometrics.returns", "title": "gs_quant timeseries econometrics returns" }, { "body": "gs_quant timeseries econometrics sharpe_ratio sharpe_ratio series currency RiskFreeRateCurrency USD USD w None curve_type CurveType PRICES prices source Calculate Sharpe ratio Parameters series Series series of prices or excess returns for an asset currency RiskFreeRateCurrency currency for risk free rate defaults to USD curve_type CurveType whether input series is of prices or excess returns defaults to prices w Union Window int None Window or int size of window and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value Return type Series Returns Sharpe ratio Usage Given a price series P risk free rate R and window of size w returns the rolling Sharpe ratio S S_t frac E_t E_ t w 1 365 25 D_t D_ t w 1 volatility E w _t Excess returns E are defined as E_t E_ t 1 P_t P_ t 1 1 R D_t D_ t 1 360 where D is the date for a data point The Actual 360 day count convention is used Examples Get rolling sharpe ratio of a price series with window of 252 sr sharpe_ratio generate_series 100 USD 252 CurveType PRICES See also volatility", "key": "functions/gs_quant.timeseries.econometrics.sharpe_ratio", "title": "gs_quant timeseries econometrics sharpe_ratio" }, { "body": "gs_quant timeseries econometrics volatility volatility x w gs_quant timeseries helper Window object returns_type Returns SIMPLE simple source Realized volatility of price series Parameters x Series time series of prices w Union Window int str Window or int size of window and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value If w is a string it should be a relative date like 1m 1d etc Window size defaults to length of series returns_type Returns returns type simple logarithmic or absolute Return type Series Returns date based time series of return Usage Calculate rolling annualized realized volatility of a price series over a given window Annual volatility of 20 is returned as 20 0 Y_t sqrt frac 1 N 1 sum_ i t w 1 t R_t overline R_t 2 sqrt 252 100 where N is the number of observations in each rolling window w R_t is the return on time t based on returns_type Type Description simple Simple geometric change in asset prices R_t frac X_t X_ t 1 1 where X_t is the asset price at time t logarithmic Natural logarithm of asset price changes R_t log X_t log X_ t 1 where X_t is the asset price at time t absolute Absolute change in asset prices Y_t X_t X_ t obs where X_t is the asset price at time t and overline R_t is the mean value over the same window overline R_t frac sum_ i t w 1 t R_t N If window is not provided computes realized volatility over the full series Examples Compute rolling 1 month 22 business day annualized volatility of price series series generate_series 100 vol_series volatility series 22 vol_series volatility series Window 22 30 See also std annualize returns", "key": "functions/gs_quant.timeseries.econometrics.volatility", "title": "gs_quant timeseries econometrics volatility" }, { "body": "gs_quant timeseries statistics cov cov x y w gs_quant timeseries helper Window object source Rolling co variance of series over given window Parameters x Series series timeseries y Series series timeseries w Union Window int str Window or int size of window and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value If w is a string it should be a relative date like 1m 1d etc Window size defaults to length of series Return type Series Returns timeseries of covariance Usage Provides unbiased estimator of sample co variance over a rolling window R_t frac 1 N 1 sum_ i t w 1 t X_i overline X_t Y_i overline Y_t where N is the number of observations in each rolling window w and overline X_t and overline Y_t represent the sample mean of series X_t and Y_t over the same window overline X_t frac sum_ i t w 1 t X_i N and overline Y_t frac sum_ i t w 1 t Y_i N If window is not provided computes variance over the full series Examples Generate price series and compute variance of returns over 22 observations prices_x generate_series 100 prices_y generate_series 100 cov returns prices_x returns prices_y 22 See also sum mean var", "key": "functions/gs_quant.timeseries.statistics.cov", "title": "gs_quant timeseries statistics cov" }, { "body": "gs_quant timeseries statistics exponential_std exponential_std x beta 0 75 source Exponentially weighted standard deviation Parameters x Series time series beta float how much to weigh the previous price in the time series thus controlling how much importance we place on the more distant past Must be between 0 inclusive and 1 exclusive Return type Series Returns time series of standard deviation of the input series Usage Provides an unbiased estimator of exponentially weighted standard deviation of a series X_0 X_1 X_2 S_t sqrt EWMA X_t 2 EWMA X_t 2 DF_t where EWMA X_t is the exponential moving average at t see function exponential_moving_average DF_t is the debiasing factor see Weighted sample variance for further details DF_t frac sum_ i 0 t w_i 2 sum_ i 0 t w_i 2 sum_ i 0 t w_i 2 where w_i is the weight assigned to i th observation w_i 1 beta beta i for i t beta i for i t Examples Generate price series and compute exponentially weighted standard deviation of returns prices generate_series 100 exponential_std returns prices 0 9 See also std var exponential_moving_average", "key": "functions/gs_quant.timeseries.statistics.exponential_std", "title": "gs_quant timeseries statistics exponential_std" }, { "body": "gs_quant timeseries statistics generate_series generate_series length source Generate sample timeseries Parameters length int number of observations Return type Series Returns date based time series of randomly generated prices Usage Create timeseries from returns generated from a normally distributed random variables IDD Length determines the number of observations to be generated Assume random variables R which follow a normal distribution with mean 0 and standard deviation of 1 R sim N 0 1 The timeseries is generated from these random numbers through X_t 1 R X_ t 1 Examples Generate price series with 100 observations starting from today s date prices generate_series 100 See also numpy random normal", "key": "functions/gs_quant.timeseries.statistics.generate_series", "title": "gs_quant timeseries statistics generate_series" }, { "body": "gs_quant timeseries statistics max_ max_ x w gs_quant timeseries helper Window object source Maximum value of series over given window Parameters x Union Series List Series series a timeseries or an array of timeseries w Union Window int str Window or int size of window and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value If w is a string it should be a relative date like 1m 1d etc Window size defaults to length of series Return type Series Returns timeseries of maximum value Usage Returns the maximum value of the series over each window If x is a series R_t max X_ t w 1 X_t where w is the size of the rolling window If x is an array of series R_t max X_ 1 t w 1 X_ n t where w is the size of the rolling window and n is the number of series If window is not provided returns the maximum value over the full series If the window size is greater than the available data will return maximum of available values Examples Maximum value of price series over the last 22 observations prices generate_series 100 max_ prices 22 See also min_", "key": "functions/gs_quant.timeseries.statistics.max_", "title": "gs_quant timeseries statistics max_" }, { "body": "gs_quant timeseries statistics mean mean x w gs_quant timeseries helper Window object source Arithmetic mean of series over given window Parameters x Union Series List Series series a timeseries or an array of timeseries w Union Window int str Window or int size of window and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value If w is a string it should be a relative date like 1m 1d etc Window size defaults to length of series Return type Series Returns timeseries of mean value Usage Calculates arithmetic mean of the series over a rolling window If a timeseries is provided R_t frac sum_ i t w 1 t X_i N where N is the number of observations in each rolling window w If an array of timeseries is provided R_t frac sum_ i t w 1 t sum_ j 1 n X_ ij N where n is the number of series and N is the number of observations in each rolling window w If window is not provided computes rolling mean over the full series If the window size is greater than the available data will return mean of available values Examples Generate price series and compute mean over 22 observations prices generate_series 100 mean prices 22 See also median mode", "key": "functions/gs_quant.timeseries.statistics.mean", "title": "gs_quant timeseries statistics mean" }, { "body": "gs_quant timeseries statistics median median x w gs_quant timeseries helper Window object source Median value of series over given window Parameters x Series series timeseries w Union Window int str Window or int size of window and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value If w is a string it should be a relative date like 1m 1d etc Window size defaults to length of series Return type Series Returns timeseries of median value Usage Computes the median value over a given window For each window this function will return the middle value when all elements in the window are sorted If the number of observations in the window is even will return the average of the middle two values If the window size is greater than the available data will return median of available values d frac w 1 2 R_t frac X_ lfloor t d rfloor X_ lceil t d rceil 2 where w is the size of the rolling window If window is not provided computes median over the full series Examples Generate price series and compute median over 22 observations prices generate_series 100 median prices 22 See also mean mode", "key": "functions/gs_quant.timeseries.statistics.median", "title": "gs_quant timeseries statistics median" }, { "body": "gs_quant timeseries statistics min_ min_ x w gs_quant timeseries helper Window object source Minimum value of series over given window Parameters x Union Series List Series series a timeseries or an array of timeseries w Union Window int str Window or int size of window and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value If w is a string it should be a relative date like 1m 1d etc Window size defaults to length of series Return type Series Returns timeseries of minimum value Usage Returns the minimum value of the series over each window If x is a series R_t min X_ t w 1 X_t where w is the size of the rolling window If x is an array of series R_t min X_ 1 t w 1 X_ n t where w is the size of the rolling window and n is the number of series If window is not provided returns the minimum value over the full series If the window size is greater than the available data will return minimum of available values Examples Minimum value of price series over the last 22 observations prices generate_series 100 min_ prices 22 See also max_", "key": "functions/gs_quant.timeseries.statistics.min_", "title": "gs_quant timeseries statistics min_" }, { "body": "gs_quant timeseries statistics mode mode x w gs_quant timeseries helper Window object source Most common value in series over given window Parameters x Series series timeseries w Union Window int str Window or int size of window and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value If w is a string it should be a relative date like 1m 1d etc Window size defaults to length of series Return type Series Returns timeseries of mode value Usage Computes the mode over a given window For each window this function will return the most common value of all elements in the window If there are multiple values with the same frequency of occurrence will return the smallest value If window is not provided computes mode over the full series Examples Generate price series and compute mode over 22 observations prices generate_series 100 mode prices 22 See also mean median", "key": "functions/gs_quant.timeseries.statistics.mode", "title": "gs_quant timeseries statistics mode" }, { "body": "gs_quant timeseries statistics percentile percentile x n w None source Returns the nth percentile of a series Parameters x Series series n float percentile w Union Window int str None Window or int size of window and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value If w is a string it should be a relative date like 1m 1d etc Return type Union Series float Returns nth percentile Usage Calculates the nth percentile rank of x Rolling nth percentile is returned if a window is specified else a scalar for nth percentile over the entire series Example Compute the 90th percentile of a series a generate_series 100 percentile a 90", "key": "functions/gs_quant.timeseries.statistics.percentile", "title": "gs_quant timeseries statistics percentile" }, { "body": "gs_quant timeseries statistics percentiles percentiles x y None w gs_quant timeseries helper Window object source Rolling percentiles over given window Parameters x Series value series y Optional Series distribution series w Union Window int str Window or int size of window and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value If w is a string it should be a relative date like 1m 1d etc Window size defaults to length of series Return type Series Returns timeseries of percentiles Usage Calculate percentile rank of y in the sample distribution of x over a rolling window of length w R_t frac sum_ i t N 1 t X_i Y_t 0 5 sum_ i t N 1 t X_i Y_t N times100 Where N is the number of observations in a rolling window If y is not provided calculates percentiles of x over its historical values If window length w is not provided uses an ever growing history of values If w is greater than the available data size returns empty Examples Compute percentile ranks of a series in the sample distribution of a second series over 22 observations a generate_series 100 b generate_series 100 percentiles a b 22 See also zscores", "key": "functions/gs_quant.timeseries.statistics.percentiles", "title": "gs_quant timeseries statistics percentiles" }, { "body": "gs_quant timeseries statistics product product x w gs_quant timeseries helper Window object source Rolling product of series over given window Parameters x Series series timeseries w Union Window int str Window or int size of window and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value If w is a string it should be a relative date like 1m 1d etc Window size defaults to length of series Return type Series Returns timeseries of rolling product Usage Calculate the product of observations over a given rolling window For each time t returns the value of all observations from t w 1 to t multiplied together R_t prod_ i t w 1 t X_i where w is the size of the rolling window If window is not provided computes product over the full series Examples Generate price series and compute rolling sum over 22 observations prices generate_series 100 product 1 returns prices See also sum_", "key": "functions/gs_quant.timeseries.statistics.product", "title": "gs_quant timeseries statistics product" }, { "body": "gs_quant timeseries statistics range_ range_ x w gs_quant timeseries helper Window object source Range of series over given window Parameters x Series series timeseries w Union Window int str Window or int size of window and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value If w is a string it should be a relative date like 1m 1d etc Window size defaults to length of series Return type Series Returns timeseries of range Usage Returns the range of the series max min over rolling window R_t max X_ t w 1 X_t min X_ t w 1 X_t where w is the size of the rolling window If window is not provided returns the range over the full series If the window size is greater than the available data will return range of all available values Examples Range of price series over the last 22 observations prices generate_series 100 range_ prices 22 See also min_ max_", "key": "functions/gs_quant.timeseries.statistics.range_", "title": "gs_quant timeseries statistics range_" }, { "body": "gs_quant timeseries statistics std std x w gs_quant timeseries helper Window object source Rolling standard deviation of series over given window Parameters x Series series timeseries w Union Window int str Window or int size of window and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value If w is a string it should be a relative date like 1m 1d etc Window size defaults to length of series Return type Series Returns timeseries of standard deviation Usage Provides unbiased estimator of sample standard deviation over a rolling window R_t sqrt frac 1 N 1 sum_ i t w 1 t X_i overline X_t 2 where N is the number of observations in each rolling window w and overline X_t is the mean value over the same window overline X_t frac sum_ i t w 1 t X_i N If window is not provided computes standard deviation over the full series Examples Generate price series and compute standard deviation of returns over 22 observations prices generate_series 100 std returns prices 22 See also sum mean var", "key": "functions/gs_quant.timeseries.statistics.std", "title": "gs_quant timeseries statistics std" }, { "body": "gs_quant timeseries statistics sum_ sum_ x w gs_quant timeseries helper Window object source Rolling sum of series over given window Parameters x Union Series List Series series a timeseries or an array of timeseries w Union Window int str Window or int size of window and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value If w is a string it should be a relative date like 1m 1d etc Window size defaults to length of series Return type Series Returns timeseries of rolling sum Usage Calculate the sum of observations over a given rolling window If x is a series R_t sum_ i t w 1 t X_i where w is the size of the rolling window If x is an array of series R_t sum_ i t w 1 t sum_ j 1 n X_ ij where w is the size of the rolling window and n is the number of series If window is not provided computes sum over the full series If the window size is greater than the available data will return sum of available values Examples Generate price series and compute rolling sum over 22 observations prices generate_series 100 sum_ prices 22 See also product", "key": "functions/gs_quant.timeseries.statistics.sum_", "title": "gs_quant timeseries statistics sum_" }, { "body": "gs_quant timeseries statistics var var x w gs_quant timeseries helper Window object source Rolling variance of series over given window Parameters x Series series timeseries w Union Window int str Window or int size of window and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value If w is a string it should be a relative date like 1m 1d etc Window size defaults to length of series Return type Series Returns timeseries of variance Usage Provides unbiased estimator of sample variance over a rolling window R_t frac 1 N 1 sum_ i t w 1 t X_i overline X_t 2 where N is the number of observations in each rolling window w and overline X_t is the mean value over the same window overline X_t frac sum_ i t w 1 t X_i N If window is not provided computes variance over the full series Examples Generate price series and compute variance of returns over 22 observations prices generate_series 100 var returns prices 22 See also var mean std", "key": "functions/gs_quant.timeseries.statistics.var", "title": "gs_quant timeseries statistics var" }, { "body": "gs_quant timeseries statistics winsorize winsorize x limit 2 5 w gs_quant timeseries helper Window object source Limit extreme values in series Parameters x Series time series of prices limit float max z score of values w Union Window int str Window or int size of window and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value If w is a string it should be a relative date like 1m 1d etc Window size defaults to length of series Return type Series Returns timeseries of winsorized values Usage Cap and floor values in the series which have a z score greater or less than provided value This function will restrict the distribution of values Calculates the sample standard deviation and adjusts values which fall outside the specified range to be equal to the upper or lower limits Lower and upper limits are defined as upper mu sigma times limit lower mu sigma times limit Where mu and sigma are sample mean and standard deviation The series is restricted by R_t max min X_t upper lower See winsorising for additional information Examples Generate price series and winsorize z score of returns over 22 observations prices generate_series 100 winsorize zscore returns prices 22 See also zscore mean std", "key": "functions/gs_quant.timeseries.statistics.winsorize", "title": "gs_quant timeseries statistics winsorize" }, { "body": "gs_quant timeseries statistics zscores zscores x w gs_quant timeseries helper Window object source Rolling z scores over a given window Parameters x Series time series of prices w Union Window int str Window or int size of window and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value If w is a string it should be a relative date like 1m 1d etc Window size defaults to length of series Return type Series Returns timeseries of z scores Usage Calculate standard score of each value in series over given window Standard deviation and sample mean are computed over the specified rolling window then element is normalized to provide a rolling z score R_t frac X_t mu sigma Where mu and sigma are sample mean and standard deviation over the given window If window is not provided computes z score relative to mean and standard deviation over the full series Examples Generate price series and compute z score of returns over 22 observations prices generate_series 100 zscores returns prices 22 See also mean std", "key": "functions/gs_quant.timeseries.statistics.zscores", "title": "gs_quant timeseries statistics zscores" }, { "body": "gs_quant timeseries technicals bollinger_bands bollinger_bands x w gs_quant timeseries helper Window object k 2 source Bollinger bands with given window and width Parameters x Series time series of prices w Union Window int str Window or int size of window and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value If w is a string it should be a relative date like 1m 1d etc Window size defaults to length of series k float band width in standard deviations default 2 Return type DataFrame Returns date based time series of return Usage Standard deviation bands around the moving average of asset price level Bollinger bands can be used to determine a range around the price level which responds to local volatility changes Returns two series upper u_t and lower l_t u_t bar X_t k sigma_t l_t bar X_t k sigma_t where bar X_t is the moving average over specified window and sigma_t is the rolling standard deviation over the specified window See Bollinger Bands for more information Examples Compute bollinger bands around 20 day moving average at 2 standard deviations prices generate_series 100 bollinger_bands prices 20 2 See also moving_average std", "key": "functions/gs_quant.timeseries.technicals.bollinger_bands", "title": "gs_quant timeseries technicals bollinger_bands" }, { "body": "gs_quant timeseries technicals exponential_moving_average exponential_moving_average x beta 0 75 source Exponentially weighted moving average Parameters x Series time series of prices beta float how much to weigh the previous observations in the time series thus controlling how much importance we place on the more distant past Must be between 0 inclusive and 1 exclusive Return type Series Returns date based time series of return Usage The exponential ly weighted moving average EMA of a series X_0 X_1 X_2 is defined as Y_0 X_0 Y_t beta cdot Y_ t 1 1 beta cdot X_t where beta is the weight we place on the previous average See Exponential moving average for more information Examples Generate price series with 100 observations starting from today s date prices generate_series 100 exponential_moving_average prices 0 9 See also mean moving_average smoothed_moving_average", "key": "functions/gs_quant.timeseries.technicals.exponential_moving_average", "title": "gs_quant timeseries technicals exponential_moving_average" }, { "body": "gs_quant timeseries technicals exponential_spread_volatility exponential_spread_volatility x beta 0 75 source Exponentially weighted spread volatility Parameters x Series time series of prices beta float how much to weigh the previous price in the time series thus controlling how much importance we place on the more distant past Must be between 0 inclusive and 1 exclusive Return type Series Returns date based time series of exponential spread volatility of the input series Usage Exponentially weights the daily differences of the input series calculates the annualized standard deviation Examples Generate price series and compute exponentially weighted standard deviation of returns prices generate_series 100 exponential_volatility prices 0 9 The above is equivalent to annualize exponential_std diff prices 1 0 9 See also volatility exponential_std exponential_volatility", "key": "functions/gs_quant.timeseries.technicals.exponential_spread_volatility", "title": "gs_quant timeseries technicals exponential_spread_volatility" }, { "body": "gs_quant timeseries technicals exponential_volatility exponential_volatility x beta 0 75 source Exponentially weighted volatility Parameters x Series time series of prices beta float how much to weigh the previous price in the time series thus controlling how much importance we place on the more distant past Must be between 0 inclusive and 1 exclusive Return type Series Returns date based time series of exponential volatility of the input series Usage Calculates the exponentially weighted standard deviation of the return of the input series and annualizes the standard deviation Examples Generate price series and compute exponentially weighted standard deviation of returns prices generate_series 100 exponential_volatility prices 0 9 The above is equivalent to annualize exponential_std returns prices 0 9 100 See also volatility exponential_std exponential_spread_volatility", "key": "functions/gs_quant.timeseries.technicals.exponential_volatility", "title": "gs_quant timeseries technicals exponential_volatility" }, { "body": "gs_quant timeseries technicals moving_average moving_average x w gs_quant timeseries helper Window object source Moving average over specified window Parameters x Series time series of prices w Union Window int str Window or int size of window and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value If w is a string it should be a relative date like 1m 1d etc Window size defaults to length of series Return type Series Returns date based time series of return Usage Simple arithmetic moving average over the specified window number of observations Shorter windows will be more reactive to changes in the asset price but more volatile Larger windows will be smoother but less reactive to near term changes in asset prices R_t frac sum_ i t w 1 t X_t N where N is the number of observations in each rolling window w If window is not provided computes rolling mean over the full series Equivalent to mean Examples Generate price series with 100 observations starting from today s date prices generate_series 100 moving_average prices 22 See also mean", "key": "functions/gs_quant.timeseries.technicals.moving_average", "title": "gs_quant timeseries technicals moving_average" }, { "body": "gs_quant timeseries technicals relative_strength_index relative_strength_index x w 14 source Relative Strength Index Parameters x Series time series of prices w Union Window int str Window or int size of window and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value If w is a string it should be a relative date like 1m 1d etc Window size defaults to length of series Return type DataFrame Returns date based time series of RSI Usage The RSI computes momentum as the ratio of higher closes to lower closes stocks which have had more or stronger positive changes have a higher RSI than stocks which have had more or stronger negative changes See RSI for more information Examples Compute relative strength index over a 14 day window prices generate_series 100 relative_strength_index prices 14 See also moving_average std smoothed_moving_average", "key": "functions/gs_quant.timeseries.technicals.relative_strength_index", "title": "gs_quant timeseries technicals relative_strength_index" }, { "body": "gs_quant timeseries technicals smoothed_moving_average smoothed_moving_average x w gs_quant timeseries helper Window object source Smoothed moving average over specified window Parameters x Series time series of prices w Union Window int str Window or int size of window and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value If w is a string it should be a relative date like 1m 1d etc Window size defaults to length of series Return type Series Returns date based time series of return Usage A modified moving average MMA running moving average RMA or smoothed moving average SMMA is defined as P_ MM today frac N 1 P_ MM yesterday P_today N where N is the number of observations in each rolling window w If window is not provided computes rolling mean over the full series See Modified moving average for more information Examples Generate price series with 100 observations starting from today s date prices generate_series 100 smoothed_moving_average prices 22 See also mean func moving_average", "key": "functions/gs_quant.timeseries.technicals.smoothed_moving_average", "title": "gs_quant timeseries technicals smoothed_moving_average" }, { "body": "GS Quant API Packages Data Package gs_quant data DataContext gs_quant data Dataset gs_quant data Fields Datetime Package Date Point Instrument Package gs_quant base Priceable Instruments Markets Package gs_quant markets PricingContext gs_quant markets HistoricalPricingContext Portfolio Securities Models Package Epidemiology Risk Package Functions Measures Timeseries Package Algebra Analysis Backtesting Date Time Econometrics Statistics Technical Analysis Index", "key": "index", "title": "GS Quant API" }, { "body": "Instrument Package Instruments", "key": "instrument", "title": "Instrument Package Instruments" }, { "body": "Markets Package Portfolio Securities", "key": "markets", "title": "Markets Package Portfolio Securities" }, { "body": "Models Package Epidemiology", "key": "models", "title": "Models Package Epidemiology" }, { "body": "Risk Package Functions aggregate_risk results threshold None source Combine the results of multiple InstrumentBase calc calls into a single result Parameters results Iterable Union DataFrameWithInfo Future An iterable of Dataframes and or Futures returned by InstrumentBase calc threshold Optional float exclude values whose absolute value falls below this threshold Return type DataFrame Returns A Dataframe with the aggregated results Examples from gs_quant instrument import IRCap IRFloor from gs_quant markets import PricingContext from gs_quant risk import IRDelta IRVega cap IRCap 5y GBP floor IRFloor 5y GBP instruments cap floor with PricingContext delta_f inst calc IRDelta for inst in instruments vega_f inst calc IRVega for inst in cap floor delta aggregate_risk delta_f threshold 0 1 vega aggregate_risk vega_f delta_f and vega_f are lists of futures where the result will be a Dataframe delta and vega are Dataframes representing the merged risk of the individual instruments subtract_risk left right source Subtract bucketed risk Dimensions must be identical Parameters left DataFrameWithInfo Results to substract from right DataFrameWithInfo Results to substract Examples from gs_quant datetime date import business_day_offset from gs_quant instrument IRSwap from gs_quant markets import PricingContext from gs_quant risk import IRDelta import datetime as dt ir_swap IRSwap Pay 10y USD delta_today ir_swap calc IRDelta with PricingContext pricing_date business_day_offset dt date today 1 roll preceding delta_yday_f ir_swap calc IRDelta delta_diff subtract_risk delta_today delta_yday_f result Return type DataFrame sort_risk df by date time mkt_type mkt_asset mkt_class mkt_point source Sort bucketed risk Parameters df DataFrame Input Dataframe by Tuple str Columns to sort by Return type DataFrame Returns A sorted Dataframe Measures DollarPrice DollarPrice Present value in USD Price Price Present value in local currency ForwardPrice ForwardPrice Forward price Theta Theta 1 day Theta BaseCPI BaseCPI Base CPI level CommodDelta CommodDelta Commodity Delta CommodTheta CommodTheta Commodity Theta CommodVega CommodVega Commodity Vega EqDelta EqDelta Equity Delta EqGamma EqGamma Equity Gamma EqVega EqVega Equity Vega EqSpot EqSpot Equity Spot Level EqAnnualImpliedVol EqAnnualImpliedVol Equity Annual Implied Volatility FairVarStrike FairVarStrike Fair Variance Strike Value of a Variance Swap FairVolStrike FairVolStrike Fair Volatility Strike Value of a Variance Swap FXDelta FXDelta FX Delta FXGamma FXGamma FX Gamma FXVega FXVega FX Vega FXSpot FXSpot FX Spot Rate InflationDelta InflationDelta Inflation Delta InflationDeltaParallel InflationDeltaParallel Inflation Parallel Delta InflationDeltaParallelLocalCcy InflationDeltaParallelLocalCcy Inflation Parallel Delta Local Ccy IRBasis IRBasis Interest Rate Basis IRDelta IRDelta Interest Rate Delta IRDeltaParallel IRDeltaParallel Interest Rate Parallel Delta IRDeltaLocalCcy IRDeltaLocalCcy Interest Rate Delta Local Ccy IRDeltaParallelLocalCcy IRDeltaParallelLocalCcy Interest Rate Parallel Delta Local Ccy IRXccyDelta IRXccyDelta Cross ccy Delta IRXccyDeltaParallel IRXccyDeltaParallel Cross ccy Parallel Delta IRXccyDeltaParallelLocalCurrency IRXccyDeltaParallelLocalCurrency Cross ccy Parallel Delta Local Ccy IRGammaParallel IRGammaParallel Interest Rate Parallel Gamma IRGammaParallelLocalCcy IRGammaParallelLocalCcy Interest Rate Parallel Gamma Local Ccy IRVega IRVega Interest Rate Vega IRVegaParallel IRVegaParallel Interest Rate Parallel Vega IRVegaLocalCcy IRVegaLocalCcy Interest Rate Vega Local Ccy IRVegaParallelLocalCcy IRVegaParallelLocalCcy Interest Rate Parallel Vega Local Ccy IRAnnualImpliedVol IRAnnualImpliedVol Interest Rate Annual Implied Volatility IRAnnualATMImpliedVol IRAnnualATMImpliedVol Interest Rate Annual Implied At The Money Volatility IRDailyImpliedVol IRDailyImpliedVol Interest Rate Daily Implied Volatility bps IRSpotRate IRSpotRate At The Money Spot Rate IRFwdRate IRFwdRate Par Rate", "key": "risk", "title": "Risk Package Functions Measures" }, { "body": "Timeseries Package Algebra Analysis Backtesting Date Time Econometrics Statistics Technical Analysis", "key": "timeseries", "title": "Timeseries Package Algebra Analysis Backtesting Date Time Econometrics Statistics Technical Analysis" } ] ================================================ FILE: docs/_build/search/searchdata.json ================================================ [ { "body": "gs_quant base Priceable class Priceable source A priceable such as a derivative instrument __init__ source Initialize self See help type self for accurate signature Methods Attributes", "key": "classes/gs_quant.base.Priceable", "title": "gs_quant base Priceable" }, { "body": "gs_quant data Dataset class Dataset dataset_id provider None source A collection of related data __init__ dataset_id provider None source Parameters dataset_id Union str Vendor The dataset s identifier provider Optional DataApi The data provider Methods Attributes", "key": "classes/gs_quant.data.Dataset", "title": "gs_quant data Dataset" }, { "body": "CommodSwap For methods of this class see gs_quant base Priceable class CommodSwap commodity start commodity_reference_price None notional_amount 1000000 0 currency None calculation_periods None calculation_period_frequency None source Object representation of a commodities swap Properties calculation_period_frequency The frequency of the calculation periods Return type Union Frequency str calculation_periods The number of calculation periods Return type int commodity Commodity asset Return type Union CommodityAsset str commodity_reference_price Return type str currency Currency ISO 4217 currency code or exchange quote modifier e g GBP vs GBp Return type Union Currency str notional_amount Notional amount Return type float start Return type Union date str", "key": "classes/gs_quant.instrument.CommodSwap", "title": "CommodSwap" }, { "body": "EqOption For methods of this class see gs_quant base Priceable class EqOption asset expiration_date strike_price option_type option_style number_of_options None exchange None multiplier None settlement_date None currency None premium None source Instrument definition for equity option Properties asset Ticker of the underlying stock or index Return type str currency Currency ISO 4217 currency code or exchange quote modifier e g GBP vs GBp Return type Union Currency str exchange Name of marketplace where security derivative or other instrument is traded Return type str expiration_date Date or tenor e g 2018 09 03 3m Return type Union date str multiplier Number of stock units per option contract Return type float number_of_options Number of options Return type float option_style Option Exercise Style Return type Union OptionStyle str option_type Option Type Return type Union OptionType str premium Option premium Return type float settlement_date Date or tenor e g 2018 09 03 3m Return type Union date str strike_price Strike as value percent or string e g 62 5 95 ATM ATMF 25ATM 20CallDelta 10PutDelta 10NS Return type Union float str", "key": "classes/gs_quant.instrument.EqOption", "title": "EqOption" }, { "body": "FXForward For methods of this class see gs_quant base Priceable class FXForward pair None settlement_date None forward_rate None notional_amount None source Object representation of an FX forward Properties forward_rate Forward FX rate Return type float notional_amount Notional amount Return type float pair Currency pair Return type str settlement_date Date or tenor e g 2018 09 03 3m Return type Union date str", "key": "classes/gs_quant.instrument.FXForward", "title": "FXForward" }, { "body": "FXOption For methods of this class see gs_quant base Priceable class FXOption call_currency put_currency expiration_date option_type call_amount 1000000 0 put_amount 1000000 0 strike None premium 0 source Object representation of a FX option Properties call_amount Amount of the call currency Return type float call_currency Currency ISO 4217 currency code or exchange quote modifier e g GBP vs GBp Return type Union Currency str expiration_date Date or tenor e g 2018 09 03 3m Return type Union date str option_type Option Type Return type Union OptionType str premium Option premium Return type float put_amount Amount of the put currency Return type float put_currency Currency ISO 4217 currency code or exchange quote modifier e g GBP vs GBp Return type Union Currency str strike Strike as value percent or at the money e g 62 5 95 ATM 25 ATMF Return type Union float str", "key": "classes/gs_quant.instrument.FXOption", "title": "FXOption" }, { "body": "Forward For methods of this class see gs_quant base Priceable class Forward currency expiration_date notional_amount None source Object representation of a forward Properties currency Currency ISO 4217 currency code or exchange quote modifier e g GBP vs GBp Return type Union Currency str expiration_date Date or tenor e g 2018 09 03 3m Return type Union date str notional_amount Notional amount Return type float", "key": "classes/gs_quant.instrument.Forward", "title": "Forward" }, { "body": "IRBasisSwap For methods of this class see gs_quant base Priceable class IRBasisSwap termination_date notional_currency notional_amount 1000000 0 effective_date None payer_spread None payer_rate_option None payer_designated_maturity None payer_frequency None payer_day_count_fraction None payer_business_day_convention None receiver_spread None receiver_rate_option None receiver_designated_maturity None receiver_frequency None receiver_day_count_fraction None receiver_business_day_convention None fee 0 clearing_house None source An exchange of cashflows from different interest rate indices Properties clearing_house Swap Clearing House Return type Union SwapClearingHouse str effective_date The date on which the swap becomes effective Return type Union date str fee The fee Return type float notional_amount Notional amount Return type float notional_currency Notional currency Return type Union Currency str payer_business_day_convention The business day convention for the payer Return type Union BusinessDayConvention str payer_day_count_fraction The day count fraction for the payer Return type Union DayCountFraction str payer_designated_maturity Tenor of the payerRateOption e g 3m 6m Return type str payer_frequency The frequency of payer payments e g 6m Return type str payer_rate_option The underlying benchmark for the payer e g USD LIBOR BBA EUR EURIBOR TELERATE Return type str payer_spread Spread over the payer rate Return type float receiver_business_day_convention The business day convention for the receiver Return type Union BusinessDayConvention str receiver_day_count_fraction The day count fraction for the receiver Return type Union DayCountFraction str receiver_designated_maturity Tenor of the receiverRateOption e g 3m 6m Return type str receiver_frequency The frequency of receiver payments e g 6m Return type str receiver_rate_option The underlying benchmark for the receiver e g USD LIBOR BBA EUR EURIBOR TELERATE Return type str receiver_spread Spread over the receiver rate Return type float termination_date The termination of the swap e g 2050 04 01 10y Return type Union date str", "key": "classes/gs_quant.instrument.IRBasisSwap", "title": "IRBasisSwap" }, { "body": "IRCap For methods of this class see gs_quant base Priceable class IRCap termination_date notional_currency notional_amount 1000000 0 effective_date None floating_rate_option None floating_rate_designated_maturity None floating_rate_frequency None floating_rate_day_count_fraction None floating_rate_business_day_convention None cap_rate None premium 0 fee 0 premium_payment_date None source Object representation of an interest rate cap Properties cap_rate The rate of this cap as value percent or at the money e g 62 5 95 ATM 25 ATMF Return type Union float str effective_date The date on which the cap becomes effective Return type Union date str fee The fee Return type float floating_rate_business_day_convention The business day convention of the floating rate Return type Union BusinessDayConvention str floating_rate_day_count_fraction The day count fraction of the floating rate Return type Union DayCountFraction str floating_rate_designated_maturity Tenor of the floatingRateOption e g 3m 6m Return type str floating_rate_frequency The frequency of floating payments e g 3m Return type str floating_rate_option The underlying benchmark for the floating rate e g USD LIBOR BBA EUR EURIBOR TELERATE Return type str notional_amount Notional amount Return type float notional_currency Notional currency Return type Union Currency str premium The premium Return type float premium_payment_date Date or tenor e g 2018 09 03 3m Return type Union date str termination_date The termination of the cap e g 2025 04 01 2y Return type Union date str", "key": "classes/gs_quant.instrument.IRCap", "title": "IRCap" }, { "body": "IRFloor For methods of this class see gs_quant base Priceable class IRFloor termination_date notional_currency notional_amount 1000000 0 effective_date None floating_rate_option None floating_rate_designated_maturity None floating_rate_frequency None floating_rate_day_count_fraction None floating_rate_business_day_convention None floor_rate None fee 0 premium_payment_date None source Object representation of an interest rate floor Properties effective_date The date on which the floor becomes effective Return type Union date str fee The fee Return type float floating_rate_business_day_convention The business day convention of the floating rate Return type Union BusinessDayConvention str floating_rate_day_count_fraction The day count fraction of the floating rate Return type Union DayCountFraction str floating_rate_designated_maturity Tenor of the floatingRateOption e g 3m 6m Return type str floating_rate_frequency The frequency of floating payments e g 3m Return type str floating_rate_option The underlying benchmark for the floating rate e g USD LIBOR BBA EUR EURIBOR TELERATE Return type str floor_rate The rate of this floor as value percent or at the money e g 62 5 95 ATM 25 ATMF Return type Union float str notional_amount Notional amount Return type float notional_currency Notional currency Return type Union Currency str premium_payment_date Date or tenor e g 2018 09 03 3m Return type Union date str termination_date The termination of the floor e g 2025 04 01 2y Return type Union date str", "key": "classes/gs_quant.instrument.IRFloor", "title": "IRFloor" }, { "body": "IRSwap For methods of this class see gs_quant base Priceable class IRSwap pay_or_receive termination_date notional_currency notional_amount 1000000 0 effective_date None floating_rate_for_the_initial_calculation_period None floating_rate_option None floating_rate_designated_maturity None floating_rate_spread None floating_rate_frequency None floating_rate_day_count_fraction None floating_rate_business_day_convention None fixed_rate None fixed_rate_frequency None fixed_rate_day_count_fraction None fixed_rate_business_day_convention None fee 0 clearing_house None source A vanilla interest rate swap of fixed vs floating cashflows Properties clearing_house Swap Clearing House Return type Union SwapClearingHouse str effective_date The date on which the swap becomes effective Return type Union date str fee The fee Return type float fixed_rate The coupon of the fixed leg Return type Union float str fixed_rate_business_day_convention The business day convention for the fixed rate Return type Union BusinessDayConvention str fixed_rate_day_count_fraction The day count fraction for the fixed rate Return type Union DayCountFraction str fixed_rate_frequency The frequency of fixed payments e g 6m Return type str floating_rate_business_day_convention The business day convention of the floating rate Return type Union BusinessDayConvention str floating_rate_day_count_fraction The day count fraction of the floating rate Return type Union DayCountFraction str floating_rate_designated_maturity Tenor of the floatingRateOption e g 3m 6m Return type str floating_rate_for_the_initial_calculation_period First fixing Return type float floating_rate_frequency The frequency of floating payments e g 3m Return type str floating_rate_option The underlying benchmark for the floating rate e g USD LIBOR BBA EUR EURIBOR TELERATE Return type str floating_rate_spread The spread over the floating rate Return type float notional_amount Notional amount Return type float notional_currency Notional currency Return type Union Currency str pay_or_receive Pay or receive fixed Return type Union PayReceive str termination_date The termination of the swap e g 2050 04 01 10y Return type Union date str", "key": "classes/gs_quant.instrument.IRSwap", "title": "IRSwap" }, { "body": "IRSwaption For methods of this class see gs_quant base Priceable class IRSwaption pay_or_receive termination_date notional_currency effective_date None notional_amount 1000000 0 expiration_date None floating_rate_option None floating_rate_designated_maturity None floating_rate_spread None floating_rate_frequency None floating_rate_day_count_fraction None floating_rate_business_day_convention None fixed_rate_frequency None fixed_rate_day_count_fraction None fixed_rate_business_day_convention None strike None premium 0 fee 0 clearing_house None settlement None premium_payment_date None source Object representation of a swaption Properties clearing_house Swap Clearing House Return type Union SwapClearingHouse str effective_date Swaption effective date e g 2019 01 01 10y Return type Union date str expiration_date Swaption expiration date 2020 05 01 3m Return type Union date str fee The fee Return type float fixed_rate_business_day_convention The business day convention for the fixed rate Return type Union BusinessDayConvention str fixed_rate_day_count_fraction The day count fraction for the fixed rate Return type Union DayCountFraction str fixed_rate_frequency The frequency of fixed payments e g 6m Return type str floating_rate_business_day_convention The business day convention of the floating rate Return type Union BusinessDayConvention str floating_rate_day_count_fraction The day count fraction of the floating rate Return type Union DayCountFraction str floating_rate_designated_maturity Tenor Return type str floating_rate_frequency The frequency of floating payments e g 3m Return type str floating_rate_option The underlying benchmark for the floating rate e g USD LIBOR BBA EUR EURIBOR TELERATE Return type str floating_rate_spread The spread over the floating rate Return type float notional_amount Notional amount Return type float notional_currency Notional currency Return type Union Currency str pay_or_receive Pay or receive fixed Return type str premium The premium Return type float premium_payment_date Date or tenor e g 2018 09 03 3m Return type Union date str settlement Swap Settlement Type Return type Union SwapSettlement str strike Strike as value percent or at the money e g 62 5 95 ATM 25 ATMF Return type Union float str termination_date Swaption termination date e g 2030 05 01 10y Return type Union date str", "key": "classes/gs_quant.instrument.IRSwaption", "title": "IRSwaption" }, { "body": "InflationSwap For methods of this class see gs_quant base Priceable class InflationSwap termination_date pay_or_receive None notional_currency None notional_amount 1000000 0 effective_date None index None floating_rate_business_day_convention None fixed_rate None fixed_rate_business_day_convention None fee 0 source A vanilla inflation swap of fixed vs floating cashflows adjusted to an inflation rate Properties effective_date The date on which the swap becomes effective Return type Union date str fee The fee Return type float fixed_rate The coupon of the fixed leg Return type Union float str fixed_rate_business_day_convention The business day convention for the fixed rate Return type Union BusinessDayConvention str floating_rate_business_day_convention The business day convention of the floating rate Return type Union BusinessDayConvention str index The underlying benchmark for the floating rate e g CPI U Return type str notional_amount Notional amount Return type float notional_currency Notional currency Return type Union Currency str pay_or_receive Pay or receive fixed Return type Union PayReceive str termination_date The termination of the swap e g 2050 04 01 10y Return type Union date str", "key": "classes/gs_quant.instrument.InflationSwap", "title": "InflationSwap" }, { "body": "Security For methods of this class see gs_quant base Priceable class Security ticker None bbid None isin None cusip None prime_id None quantity 1 source A security specified by a well known identifier Properties bbid Bloomberg Id Identifier Return type str bbid_equivalent Bloomberg Equivalent Identifier Return type str bcid Bloomberg Composite Identifier Return type str cid Company Id Identifier Return type str cm_id Client Master Party Id Return type str cross Cross identifier Return type str cusip Cusip Identifier Return type str delisted Whether an asset has been delisted Return type str dollar_cross USD cross identifier for a particular currency Return type str eid EID Identifier Return type str em_id Entity Master Identifier Return type str gsid GSID Identifier Return type str gsideid GSID_EID Identifier Return type str gsn Goldman Sachs internal product number Return type str gss GS Symbol identifier Return type str isin International Security Number Return type str jsn Japan Security Number Return type str lms_id Listed Market Symbol Return type str mdapi MDAPI Asset Return type str mdapi_class MDAPI Asset Class Return type str mic Market Identifier Code Return type str mq_symbol Marquee Symbol for generic MQ entities Return type str prime_id PrimeID Identifier Return type str rcic Reuters Composite Instrument Code Identifier Return type str ric Reuters Instrument Code identifier Return type str sec_name Internal Goldman Sachs security name Return type str sedol Sedol Identifier Return type str sf_id SalesForce ID Return type str simon_id SIMON product identifier Return type str ticker Ticker Identifier Return type str valoren Valoren Identifier Return type str wpk Wertpapier Kenn Nummer Return type str", "key": "classes/gs_quant.instrument.Security", "title": "Security" }, { "body": "gs_quant markets HistoricalPricingContext class HistoricalPricingContext start None end None calendars dates None is_async False is_batch False source A context for producing valuations over multiple dates __init__ start None end None calendars dates None is_async False is_batch False source A context for producing valuations over multiple dates Parameters start Union int date None start date end Union int date None end date defaults to today calendars Union str Tuple holiday calendars dates Optional Iterable date a custom iterable of dates is_async bool return immediately True or wait for results False Defaults to False is_batch bool use for calculations expected to run longer than 3 mins to avoid timeouts It can be used with is_aync True False Examples from gs_quant instrument import IRSwap ir_swap IRSwap Pay 10y DKK with HistoricalPricingContext 10 price_f ir_swap price price_series price_f result Methods Attributes", "key": "classes/gs_quant.markets.HistoricalPricingContext", "title": "gs_quant markets HistoricalPricingContext" }, { "body": "gs_quant markets PricingContext class PricingContext pricing_date None market_data_as_of None market_data_location None is_async False is_batch False source A context for controlling pricing and market data behaviour __init__ pricing_date None market_data_as_of None market_data_location None is_async False is_batch False source The methods on this class should not be called directly Instead use the methods on the instruments as per the examples Parameters pricing_date Optional date the date for pricing calculations Default is today market_data_as_of Union date datetime None the date datetime for sourcing market data Default is 1 business day before pricing_date market_data_location Optional str the location for sourcing market data NYC LDN or HKG Default is NYC is_async bool if True return a future immediately If False block is_batch bool use for calculations expected to run longer than 3 mins to avoid timeouts It can be used with is_aync True False Examples To change the market data location of the default context from gs_quant risk import PricingContext import datetime as dt PricingContext current PricingContext market_data_location LDN For a blocking synchronous request from gs_quant instrument import IRCap cap IRCap 5y GBP with PricingContext price_f cap dollar_price price price_f result For an asynchronous request with PricingContext is_async True price_f inst dollar_price while not price_f done Methods Attributes", "key": "classes/gs_quant.markets.PricingContext", "title": "gs_quant markets PricingContext" }, { "body": "gs_quant markets securities Asset class Asset id_ asset_class name exchange None source __init__ id_ asset_class name exchange None source Initialize self See help type self for accurate signature Methods", "key": "classes/gs_quant.markets.securities.Asset", "title": "gs_quant markets securities Asset" }, { "body": "gs_quant markets securities AssetClass class AssetClass source Asset classification of security Assets are classified into broad groups which exhibit similar characteristics and behave in a consistent way under different market conditions __init__ Initialize self See help type self for accurate signature Attributes", "key": "classes/gs_quant.markets.securities.AssetClass", "title": "gs_quant markets securities AssetClass" }, { "body": "gs_quant markets securities AssetIdentifier class AssetIdentifier source Asset type enumeration Enumeration of different security identifiers __init__ Initialize self See help type self for accurate signature Attributes", "key": "classes/gs_quant.markets.securities.AssetIdentifier", "title": "gs_quant markets securities AssetIdentifier" }, { "body": "gs_quant markets securities AssetType class AssetType source Asset type enumeration Enumeration of different types of asset or security __init__ Initialize self See help type self for accurate signature Attributes", "key": "classes/gs_quant.markets.securities.AssetType", "title": "gs_quant markets securities AssetType" }, { "body": "gs_quant markets securities Index class Index id_ asset_class name exchange None source Index Asset Index which tracks an evolving portfolio of securities and can be traded through cash or derivatives markets __init__ id_ asset_class name exchange None source Initialize self See help type self for accurate signature Methods", "key": "classes/gs_quant.markets.securities.Index", "title": "gs_quant markets securities Index" }, { "body": "gs_quant markets securities SecurityMaster class SecurityMaster source Security Master The SecurityMaster class provides an interface to security lookup functions This allows querying and retrieval of different security types assets based on a variety of different identifiers through point in time lookups Uses the current PricingContext to provide as of dates if optional arguments are not provided Will return the relevant asset subclass depending on the type of the security See also Asset __init__ Initialize self See help type self for accurate signature Methods", "key": "classes/gs_quant.markets.securities.SecurityMaster", "title": "gs_quant markets securities SecurityMaster" }, { "body": "gs_quant markets securities Stock class Stock id_ name exchange None source Base Security Type Represents a financial asset which can be held in a portfolio or has an observable price fixing which can be referenced in a derivative transaction __init__ id_ name exchange None source Initialize self See help type self for accurate signature Methods", "key": "classes/gs_quant.markets.securities.Stock", "title": "gs_quant markets securities Stock" }, { "body": "Data Package", "key": "data", "title": "Data Package" }, { "body": "Datetime Package Date Point", "key": "datetime", "title": "Datetime Package Date Point" }, { "body": "business_day_count business_day_count begin_dates end_dates calendars week_mask None source Determine the number of business days between begin_dates and end_dates Parameters begin_dates Union date Iterable date A date or collection of beginning dates end_dates Union date Iterable date A date or collection of end dates calendars Union str Tuple str Calendars to use for holidays week_mask Optional str Which days are considered weekends defaults to Saturday and Sunday Return type Union int Tuple int Returns An int or tuple of ints representing the number of business days between begin_dates and end_dates Examples import datetime as dt today dt date today bus_days business_day_count today today dt timedelta days 7", "key": "functions/gs_quant.datetime.date.business_day_count", "title": "business_day_count" }, { "body": "business_day_offset business_day_offset dates offsets roll raise calendars week_mask None source Apply offsets to the dates and move to the nearest business date Parameters dates Union date Iterable date The input date or dates offsets Union int Iterable int The number of days by which to adjust the dates roll str Which direction to roll in order to get to the nearest business date calendars Union str Tuple str Calendars to use for holidays week_mask Optional str Which days are considered weekends defaults to Saturday and Sunday Return type Union date Iterable date Returns A date if dates is a single date or tuple of dates adjusted by the offsets Examples import datetime as dt prev_bus_date business_day_offset dt date today 1 roll forward", "key": "functions/gs_quant.datetime.date.business_day_offset", "title": "business_day_offset" }, { "body": "date_range date_range begin end calendars week_mask None source Construct a range of dates Parameters begin Union int date Beginning date or int An int will be interpreted as the number of business days before end which must be a date type end Union int date param end End date or int An int will be interpreted as the number of business days after begin which must be a date type calendars Union str Tuple str param calendars Calendars to use for holidays type week_mask Optional str param week_mask Which days are considered weekends defaults to Saturday and Sunday rtype Iterable date return A generator of dates import datetime as dt today dt date today dates tuple date_range 5 today for date in date_range dt date 2019 1 1 dt date 2019 2 1 print date", "key": "functions/gs_quant.datetime.date.date_range", "title": "date_range" }, { "body": "is_business_day is_business_day dates calendars week_mask None source Determine whether each date in dates is a business day Parameters dates Union date Iterable date The input date or dates calendars Union str Tuple str Calendars to use for holidays week_mask Optional str Which days are considered weekends defaults to Saturday and Sunday Return type Union bool Tuple bool Returns True False if dates is a single date A tuple indicating True False for each date if dates is an iterable Examples import datetime as dt is_bus_date is_business_day dt date today is_bus_date is_business_day dt date 2019 7 4 calendars NYSE", "key": "functions/gs_quant.datetime.date.is_business_day", "title": "is_business_day" }, { "body": "gs_quant datetime date prev_business_date prev_business_date dates datetime date 2019 11 18 calendars week_mask None source Returns the previous business date for a given date or date series defaulting to today Parameters dates Union date Iterable date The input date or dates defaults to today calendars Union str Tuple str Calendars to use for holidays week_mask Optional str Which days are considered weekends defaults to Saturday and Sunday Return type Union date Iterable date Returns A date if dates is a single date or tuple of dates adjusted by the offset of one day Example import datetime as dt prev_bus_date prev_business_date", "key": "functions/gs_quant.datetime.date.prev_business_date", "title": "gs_quant datetime date prev_business_date" }, { "body": "point_sort_order point_sort_order point ref_date datetime date 2019 11 18 source Calculates a number that can be used to sort Mkt Points by it Parameters point str The point string from MarketDataCoordinate ref_date date Reference date normally the pricing date Return type float Returns The number of days from the reference date to the date specified by the point string Examples import datetime as dt days point_sort_order point Dec20 ref_date dt date today", "key": "functions/gs_quant.datetime.point.point_sort_order", "title": "point_sort_order" }, { "body": "abs abs_ x source Absolute value of each element in series Parameters x Series date based time series of prices Return type Series Returns date based time series of absolute value Usage Return the absolute value of X For each value in time series X_t return X_t if X_t is greater than or equal to 0 otherwise return X_t R_t X_t Equivalent to R_t sqrt X_t 2 Examples Generate price series and take absolute value of X_t 100 prices generate_series 100 100 abs_ prices See also exp sqrt", "key": "functions/gs_quant.timeseries.algebra.abs_", "title": "abs" }, { "body": "add add x y method Interpolate STEP step source Add two series or scalars Parameters x Union Series Real timeseries or scalar y Union Series Real timeseries or scalar method Interpolate interpolation method default step Only used when both x and y are timeseries Return type Union Series Real Returns timeseries of x y or sum of the given real numbers Usage Add two series or scalar variables with the given interpolation method R_t X_t Y_t Alignment operators Method Behavior intersect Resultant series only has values on the intersection of dates Values for dates present in only one series will be ignored nan Resultant series has values on the union of dates in both series Values for dates only available in one series will be treated as nan in the other series and therefore in the resultant series zero Resultant series has values on the union of dates in both series Values for dates only available in one series will be treated as zero in the other series step Resultant series has values on the union of dates in both series Values for dates only available in one series will be interpolated via step function in the other series time Resultant series have values on the union of dates times Missing values surrounded by valid values will be interpolated given length of interval Input series must use DateTimeIndex Examples Add two series a generate_series 100 b generate_series 100 add a b Interpolate STEP See also subtract", "key": "functions/gs_quant.timeseries.algebra.add", "title": "add" }, { "body": "ceil ceil x value 0 source Cap series at maximum value Parameters x Series date based time series of prices value float maximum value Return type Series Returns date based time series of maximum value Usage Returns series where all values are less than or equal to the maximum value R_t min X_t value See Floor and Ceil functions for more details Examples Generate price series and floor all values at 100 prices generate_series 100 floor prices 100 See also floor", "key": "functions/gs_quant.timeseries.algebra.ceil", "title": "ceil" }, { "body": "divide divide x y method Interpolate STEP step source Divide two series or scalars Parameters x Union Series Real timeseries or scalar y Union Series Real timeseries or scalar method Interpolate interpolation method default step Only used when both x and y are timeseries Return type Union Series Real Returns timeseries of x y or quotient of the given real numbers Usage Divide two series or scalar variables applying the given interpolation method R_t X_t Y_t Alignment operators Method Behavior intersect Resultant series only has values on the intersection of dates Values for dates present in only one series will be ignored nan Resultant series has values on the union of dates in both series Values for dates only available in one series will be treated as nan in the other series and therefore in the resultant series zero Resultant series has values on the union of dates in both series Values for dates only available in one series will be treated as zero in the other series step Resultant series has values on the union of dates in both series Values for dates only available in one series will be interpolated via step function in the other series time Resultant series have values on the union of dates times Missing values surrounded by valid values will be interpolated given length of interval Input series must use DateTimeIndex Examples Divide two series a generate_series 100 b generate_series 100 divide a b Interpolate STEP See also multiply", "key": "functions/gs_quant.timeseries.algebra.divide", "title": "divide" }, { "body": "exp exp x source Exponential of series Parameters x Series timeseries Return type Series Returns exponential of each element Usage For each element in the series X_t raise e Euler s number to the power of X_t Euler s number is the base of the natural logarithm ln R_t e X_t Examples Raise e to the power 1 Returns Euler s number approximately 2 71828 exp 1 See also log", "key": "functions/gs_quant.timeseries.algebra.exp", "title": "exp" }, { "body": "floor floor x value 0 source Floor series at minimum value Parameters x Series date based time series of prices value float minimum value Return type Series Returns date based time series of maximum value Usage Returns series where all values are greater than or equal to the minimum value R_t max X_t value See Floor and Ceil functions for more details Examples Generate price series and floor all values at 100 prices generate_series 100 floor prices 100 See also ceil", "key": "functions/gs_quant.timeseries.algebra.floor", "title": "floor" }, { "body": "log log x source Natural logarithm of series Parameters x Series timeseries Return type Series Returns series with exponential of each element Usage For each element in the series X_t return the natural logarithm ln of X_t The natural logarithm is the logarithm in base e R_t log X_t This function is the inverse of the exponential function More information on logarithms Examples Take natural logarithm of 3 log 3 See also exp", "key": "functions/gs_quant.timeseries.algebra.log", "title": "log" }, { "body": "multiply multiply x y method Interpolate STEP step source Multiply two series or scalars Parameters x Union Series Real timeseries or scalar y Union Series Real timeseries or scalar method Interpolate interpolation method default step Only used when both x and y are timeseries Return type Union Series Real Returns timeseries of x y or product of the given real numbers Usage Multiply two series or scalar variables applying the given interpolation method R_t X_t times Y_t Alignment operators Method Behavior intersect Resultant series only has values on the intersection of dates Values for dates present in only one series will be ignored nan Resultant series has values on the union of dates in both series Values for dates only available in one series will be treated as nan in the other series and therefore in the resultant series zero Resultant series has values on the union of dates in both series Values for dates only available in one series will be treated as zero in the other series step Resultant series has values on the union of dates in both series Values for dates only available in one series will be interpolated via step function in the other series time Resultant series have values on the union of dates times Missing values surrounded by valid values will be interpolated given length of interval Input series must use DateTimeIndex Examples Multiply two series a generate_series 100 b generate_series 100 multiply a b Interpolate STEP See also divide", "key": "functions/gs_quant.timeseries.algebra.multiply", "title": "multiply" }, { "body": "power power x y 1 source Raise each element in series to power Parameters x Series timeseries y float value Return type Series Returns date based time series of square roots Usage Raise each value in time series X_t to the power y R_t X_t y Examples Generate price series and raise each value to the power 2 prices generate_series 100 power prices 2 See also sqrt", "key": "functions/gs_quant.timeseries.algebra.power", "title": "power" }, { "body": "sqrt sqrt x source Square root of a each element in a series or b a real number Parameters x Union Real Series date based time series of prices or real number Return type Union Real Series Returns date based time series of square roots or square root of given number Usage Return the square root of each value in time series X_t R_t sqrt X_t Examples Generate price series and take square root of each value prices generate_series 100 sqrt prices See also pow", "key": "functions/gs_quant.timeseries.algebra.sqrt", "title": "sqrt" }, { "body": "subtract subtract x y method Interpolate STEP step source Add two series or scalars Parameters x Union Series Real timeseries or scalar y Union Series Real timeseries or scalar method Interpolate index alignment operator default intersect Only used when both x and y are timeseries Return type Union Series Real Returns timeseries of x y or difference between the given real numbers Usage Subtracts one series or scalar from another applying the given interpolation method R_t X_t Y_t Alignment operators Method Behavior intersect Resultant series only has values on the intersection of dates Values for dates present in only one series will be ignored nan Resultant series has values on the union of dates in both series Values for dates only available in one series will be treated as nan in the other series and therefore in the resultant series zero Resultant series has values on the union of dates in both series Values for dates only available in one series will be treated as zero in the other series step Resultant series has values on the union of dates in both series Values for dates only available in one series will be interpolated via step function in the other series time Resultant series have values on the union of dates times Missing values surrounded by valid values will be interpolated given length of interval Input series must use DateTimeIndex Examples Subtract one series from another a generate_series 100 b generate_series 100 subtract a b Interpolate STEP See also add", "key": "functions/gs_quant.timeseries.algebra.subtract", "title": "subtract" }, { "body": "count count x source Count observations in series Parameters x Series time series Return type Series Returns number of observations Usage Count the number of valid observations in a series R_t R_ t 1 1 if X_t is not NaN and R_t R_ t 1 0 if X_t is NaN Examples Count observations in series series generate_series 100 count count series See also sum", "key": "functions/gs_quant.timeseries.analysis.count", "title": "count" }, { "body": "diff diff x obs 1 source Diff observations with given lag Parameters x Series time series of prices obs int number of observations to lag Return type Series Returns date based time series of return Usage Compute the difference in series values over a given lag R_t X_t X_ t obs where obs is the number of observations to lag series in diff function Examples Diff prices levels series generate_series 100 returns diff series See also lag", "key": "functions/gs_quant.timeseries.analysis.diff", "title": "diff" }, { "body": "first first x source First value of series Parameters x Series time series Return type Series Returns time series of first value Usage Return series with first value of X for all dates R_t X_0 where X_0 is the first value in the series Examples Last value of series series generate_series 100 returns first series See also last", "key": "functions/gs_quant.timeseries.analysis.first", "title": "first" }, { "body": "lag lag x obs 1 source Lag timeseries by a specified number of observations Parameters x Series timeseries of prices obs int number of observations to lag series Return type Series Returns date based time series of return Usage Shift the series backwards by a specified number of observations R_t X_ t obs where obs is the number of observations to lag series Examples Lag series by 2 observations prices generate_series 100 lagged lag prices 2 See also diff", "key": "functions/gs_quant.timeseries.analysis.lag", "title": "lag" }, { "body": "last last x source Last value of series Parameters x Series time series Return type Series Returns time series of last value Usage Return series with last value of X for all dates R_t X_T where X_T is the last value in the series Examples Last value of series series generate_series 100 returns last series See also first", "key": "functions/gs_quant.timeseries.analysis.last", "title": "last" }, { "body": "align align x y method Interpolate INTERSECT intersect source Align dates of two series or scalars Parameters x Union Series Real first timeseries or scalar y Union Series Real second timeseries or scalar method Interpolate interpolation method default intersect Only used when both x and y are timeseries Return type Union List Series List Real Returns timeseries with specified dates or two scalars from the input Usage Align the dates of two series using the specified interpolation method Returns two series with dates based on the method of interpolation for example can be used to intersect the dates of two series union dates with a defined manner to compute missing values Interpolation methods Type Behavior intersect Resultant series only have values on the intersection of dates times nan Resultant series have values on the union of dates times Values will be NaN for dates or times only present in the other series zero Resultant series have values on the union of dates times Values will be zero for dates or times only present in the other series step Resultant series have values on the union of dates times Each series will use the value of the previous valid point if requested date does not exist Values prior to the first date will be equivalent to the first available value time Resultant series have values on the union of dates times Missing values surrounded by valid values will be interpolated given length of interval Input series must use DateTimeIndex Examples Stepwize interpolation of series based on dates in second series a generate_series 100 b generate_series 100 align a See also sub", "key": "functions/gs_quant.timeseries.datetime.align", "title": "align" }, { "body": "day day x source Day of each value in series Parameters x Series time series Return type Series Returns day of observations Usage Returns the day as a numeric value for each observation in the series Y_t day t Day of the time or date is the integer day number within the month e g 1 31 Examples Day for observations in series series generate_series 100 days day series See also month year", "key": "functions/gs_quant.timeseries.datetime.day", "title": "day" }, { "body": "interpolate interpolate x dates None method Interpolate INTERSECT intersect source Interpolate over specified dates or times Parameters x Series timeseries to interpolate dates Union List date List time Series None array of dates times or another series to interpolate method Interpolate interpolation method default intersect Return type Series Returns timeseries with specified dates Usage Interpolate the series X over the dates specified by the dates parameter This can be an array of dates or another series in which case the index of the series will be used to specify dates Interpolation methods Type Behavior intersect Resultant series only has values on the intersection of dates times Will only contain intersection of valid dates times in the series nan Resultant series only has values on the intersection of dates times Value will be NaN for dates not present in the series zero Resultant series has values on all requested dates times The series will have a value of zero where the requested date or time was not present in the series step Resultant series has values on all requested dates times The series will use the value of the previous valid point if requested date does not exist Values prior to the first date will be equivalent to the first available value Examples Stepwize interpolation of series based on dates in second series a generate_series 100 b generate_series 100 interpolate a b Interpolate INTERSECT See also sub", "key": "functions/gs_quant.timeseries.datetime.interpolate", "title": "interpolate" }, { "body": "month month x source Month of each value in series Parameters x Series time series Return type Series Returns month of observations Usage Returns the month as a numeric value for each observation in the series Y_t month t Month of the time or date is the integer month number e g 1 12 Examples Day for observations in series series generate_series 100 days month series See also day year", "key": "functions/gs_quant.timeseries.datetime.month", "title": "month" }, { "body": "quarter quarter x source Quarter of each value in series Parameters x Series time series Return type Series Returns quarter of observations Usage Returns the quarter as a numeric value for each observation in the series Y_t quarter t Quarter of the time or date is the integer quarter number e g 1 2 3 4 Examples Quarter for observations in series series generate_series 100 days quarter series See also day month", "key": "functions/gs_quant.timeseries.datetime.quarter", "title": "quarter" }, { "body": "value value x date method Interpolate STEP step source Value at specified date or time Parameters x Series timeseries date Union date time requested date or time method Interpolate interpolation method default step Return type Series Returns value at specified date or time Usage Returns the value of series X at the specified date Y_t X_ date If the requested date or time is not present in the series the value function will return the value from the previous available date or time by default Caller can specify other interpolation styles via the method param Interpolation methods Type Behavior intersect Only returns a value for valid dates nan Value will be NaN for dates not present in the series zero Value will be zero for dates not present in the series step Value of the previous valid point if requested date does not exist Values prior to the first date will be equivalent to the first available value Examples Value of series on 5Mar18 a generate_series 100 value a date 2019 1 3 See also interpolate", "key": "functions/gs_quant.timeseries.datetime.value", "title": "value" }, { "body": "weekday weekday x source Weekday of each value in series Parameters x Series time series Return type Series Returns weekday of observations Usage Returns the weekday as a numeric value for each observation in the series Y_t weekday t Weekday of the time or date is the integer day of the week e g 0 6 where 0 represents Monday Examples Weekday for observations in series series generate_series 100 days weekday series See also day month", "key": "functions/gs_quant.timeseries.datetime.weekday", "title": "weekday" }, { "body": "year year x source Year of each value in series Parameters x Series time series Return type Series Returns year of observations Usage Returns the year as a numeric value for each observation in the series Y_t year t Year of the time or date is the integer year number e g 2019 2020 Examples Year for observations in series series generate_series 100 days year series See also day month", "key": "functions/gs_quant.timeseries.datetime.year", "title": "year" }, { "body": "annualize annualize x source Annualize series based on sample observation frequency Parameters x Series time series of prices Return type Series Returns date based time series of annualized values Usage Based on number of days between observations will determine an annualization factor and then adjust values accordingly Useful for annualizing daily or monthly returns Y_t X_t sqrt F Annualization factors as follows based on period implied by observations Period Annualization Factor F Daily 252 Weekly 52 Bi Weekly 26 Monthly 12 Quarterly 4 Annually 1 Examples Annualize daily returns series prices generate_series 100 ann annualize returns prices See also returns", "key": "functions/gs_quant.timeseries.econometrics.annualize", "title": "annualize" }, { "body": "beta beta x b w Window w None r 0 prices True source Rolling beta of price series and benchmark Parameters x Series time series of prices b Series time series of benchmark prices w Union Window int Window or int number of observations and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value Window size defaults to length of series type prices bool param prices True if input series are prices False if they are returns rtype Series return date based time series of beta Usage Calculate rolling beta beta_t of a series to a benchmark over a given window R_t alpha_t beta S_t epsilon_t Calculated as beta_t frac sum_ i t w 1 t Cov R_t S_t Var S_t where N is the number of observations in each rolling window w and R_t and S_t are the simple returns for each series on time t R_t frac X_t X_ t 1 1 and S_t frac b_t b_ t 1 1 If prices False assumes returns are provided R_t X_t and S_t b_t Cov R_t S_t and Var S_t are the mean and variance of series R_t and S_t over the same window If window is not provided computes beta over the full series Examples Compute rolling 1 month 22 business day beta of two price series series generate_series 100 benchmark generate_series 100 b beta series benchmark 22 See also var cov correlation returns", "key": "functions/gs_quant.timeseries.econometrics.beta", "title": "beta" }, { "body": "change change x source Arithmetic series normalization Parameters x Series time series Return type Series Returns normalized time series Usage Compute difference of every value from the initial value of x Y_t X_t X_0 where X_0 is the first value in the series Examples Change in level from initial value series generate_series 100 returns change series See also index", "key": "functions/gs_quant.timeseries.econometrics.change", "title": "change" }, { "body": "correlation correlation x y w Window w None r 0 type_ SeriesType PRICES prices source Rolling correlation of two price series Parameters x Series price series y Series price series w Union Window int Window or int number of observations and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value Window size defaults to length of series param type_ type of both input series rtype Series return date based time series of correlation Usage Calculate rolling realized correlation rho_t of two price series over a given window rho_t frac sum_ i t w 1 t R_t overline R_t Y_t overline S_t N 1 sigma R_t sigma S_t where N is the number of observations in each rolling window w and R_t and S_t are the simple returns for each series on time t R_t frac X_t X_ t 1 1 and S_t frac Y_t Y_ t 1 1 If prices False assumes returns are provided R_t X_t and S_t Y_t overline R_t overline S_t are the mean values and sigma R_ t and sigma S_ t are the sample standard deviations of series R_t and S_t over the same window If window is not provided computes realized correlation over the full series Examples Compute rolling 1 month 22 business day correlation of price series series1 generate_series 100 series2 generate_series 100 corr correlation series1 series2 22 See also std returns", "key": "functions/gs_quant.timeseries.econometrics.correlation", "title": "correlation" }, { "body": "index index x initial 1 source Geometric series normalization Parameters x Series time series initial int initial value Return type Series Returns normalized time series Usage Divides every value in x by the initial value of x Y_t initial X_t X_0 where X_0 is the first value in the series Examples Normalize series to 1 series generate_series 100 returns index series See also returns", "key": "functions/gs_quant.timeseries.econometrics.index", "title": "index" }, { "body": "max_drawdown max_drawdown x w Window w None r 0 source Compute the maximum peak to trough drawdown over a rolling window Parameters x Series time series w Union Window int Window or int number of observations and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value Window size defaults to length of series rtype Series return time series of rolling maximum drawdown Examples Compute the maximum peak to trough drawdown series generate_series 100 max_drawdown series See also returns", "key": "functions/gs_quant.timeseries.econometrics.max_drawdown", "title": "max_drawdown" }, { "body": "prices prices series initial 1 type Returns SIMPLE simple source Calculate price levels from returns series Parameters series Series time series of returns initial int initial price level type Returns returns type simple log Return type Series Returns date based time series of return Usage Compute price levels from returns series based on the value of type Type Description simple Simple arithmetic returns log Logarithmic returns Simple Compute asset price series from simple returns Y_t 1 X_ t 1 Y_ t 1 where X_t is the asset price at time t and Y_0 initial Logarithmic Compute asset price series from logarithmic returns Y_t e X_ t 1 Y_ t 1 where X_t is the asset price at time t and Y_0 initial Examples Generate price series and take compute returns series generate_series 100 returns prices returns series See also returns product exp", "key": "functions/gs_quant.timeseries.econometrics.prices", "title": "prices" }, { "body": "returns returns series obs 1 type Returns SIMPLE simple source Calculate returns from price series Parameters series Series time series of prices obs int number of observations type Returns returns type Return type Series Returns date based time series of return Usage Compute returns series from price levels based on the value of type Type Description simple Simple arithmetic returns log Logarithmic returns Simple Simple geometric change in asset prices which can be aggregated across assets Y_t frac X_t X_ t obs 1 where X_t is the asset price at time t Logarithmic Natural logarithm of asset price changes which can be aggregated through time Y_t log X_t log X_ t obs where X_t is the asset price at time t Examples Generate price series and take compute returns prices generate_series 100 returns returns prices See also prices", "key": "functions/gs_quant.timeseries.econometrics.returns", "title": "returns" }, { "body": "volatility volatility x w Window w None r 0 returns_type Returns SIMPLE simple source Realized volatility of price series Parameters x Series time series of prices w Union Window int Window or int number of observations and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value Window size defaults to length of series type returns_type Returns param returns_type returns type rtype Series return date based time series of return Usage Calculate rolling annualized realized volatility of a price series over a given window Annual volatility of 20 is returned as 20 0 Y_t sqrt frac 1 N 1 sum_ i t w 1 t R_t overline R_t 2 sqrt 252 100 where N is the number of observations in each rolling window w R_t is the simple return on time t R_t frac X_t X_ t 1 1 and overline R_t is the mean value over the same window overline R_t frac sum_ i t w 1 t R_t N If window is not provided computes realized volatility over the full series Examples Compute rolling 1 month 22 business day annualized volatility of price series series generate_series 100 vol_series volatility series 22 vol_series volatility series Window 22 30 See also std annualize returns", "key": "functions/gs_quant.timeseries.econometrics.volatility", "title": "volatility" }, { "body": "cov cov x y w Window w None r 0 source Rolling co variance of series over given window Parameters x Series series timeseries y Series series timeseries w Union Window int Window or int number of observations and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value Window size defaults to length of series rtype Series return timeseries of covariance Usage Provides unbiased estimator of sample co variance over a rolling window R_t frac 1 N 1 sum_ i t w 1 t X_i overline X_t Y_i overline Y_t where N is the number of observations in each rolling window w and overline X_t and overline Y_t represent the sample mean of series X_t and Y_t over the same window overline X_t frac sum_ i t w 1 t X_i N and overline Y_t frac sum_ i t w 1 t Y_i N If window is not provided computes variance over the full series Examples Generate price series and compute variance of returns over 22 observations prices_x generate_series 100 prices_y generate_series 100 cov returns prices_x returns prices_y 22 See also sum mean var", "key": "functions/gs_quant.timeseries.statistics.cov", "title": "cov" }, { "body": "generate_series generate_series length source Generate sample timeseries Parameters length int number of observations Return type Series Returns date based time series of randomly generated prices Usage Create timeseries from returns generated from a normally distributed random variables IDD Length determines the number of observations to be generated Assume random variables R which follow a normal distribution with mean 0 and standard deviation of 1 R sim N 0 1 The timeseries is generated from these random numbers through X_t 1 R X_ t 1 Examples Generate price series with 100 observations starting from today s date prices generate_series 100 See also numpy random normal", "key": "functions/gs_quant.timeseries.statistics.generate_series", "title": "generate_series" }, { "body": "max max_ x w Window w None r 0 source Maximum value of series over given window Parameters x Series series timeseries w Union Window int Window or int number of observations and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value Window size defaults to length of series rtype Series return timeseries of maximum value Usage Returns the maximum value of the series over each window R_t max X_ t w 1 X_t where w is the size of the rolling window If window is not provided returns the minimum value over the full series If the window size is greater than the available data will return minimum of available values Examples Maximum value of price series over the last 22 observations prices generate_series 100 max_ prices 22 See also min_", "key": "functions/gs_quant.timeseries.statistics.max_", "title": "max" }, { "body": "mean mean x w Window w None r 0 source Arithmetic mean of series over given window Parameters x Series series timeseries w Union Window int Window or int number of observations and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value Window size defaults to length of series rtype Series return timeseries of mean value Usage Calculates arithmetic mean of the series over a rolling window R_t frac sum_ i t w 1 t X_i N where N is the number of observations in each rolling window w If window is not provided computes rolling mean over the full series If the window size is greater than the available data will return mean of available values Examples Generate price series and compute mean over 22 observations prices generate_series 100 mean prices 22 See also median mode", "key": "functions/gs_quant.timeseries.statistics.mean", "title": "mean" }, { "body": "median median x w Window w None r 0 source Median value of series over given window Parameters x Series series timeseries w Union Window int Window or int number of observations and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value Window size defaults to length of series rtype Series return timeseries of median value Usage Computes the median value over a given window For each window this function will return the middle value when all elements in the window are sorted If the number of observations in the window is even will return the average of the middle two values If the window size is greater than the available data will return median of available values d frac w 1 2 R_t frac X_ lfloor t d rfloor X_ lceil t d rceil 2 where w is the size of the rolling window If window is not provided computes median over the full series Examples Generate price series and compute median over 22 observations prices generate_series 100 median prices 22 See also mean mode", "key": "functions/gs_quant.timeseries.statistics.median", "title": "median" }, { "body": "min min_ x w Window w None r 0 source Minimum value of series over given window Parameters x Series series timeseries w Union Window int Window or int number of observations and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value Window size defaults to length of series rtype Series return timeseries of minimum value Usage Returns the minimum value of the series over each window R_t min X_ t w 1 X_t where w is the size of the rolling window If window is not provided returns the minimum value over the full series If the window size is greater than the available data will return minimum of available values Examples Minimum value of price series over the last 22 observations prices generate_series 100 min_ prices 22 See also max_", "key": "functions/gs_quant.timeseries.statistics.min_", "title": "min" }, { "body": "mode mode x w Window w None r 0 source Most common value in series over given window Parameters x Series series timeseries w Union Window int Window or int number of observations and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value Window size defaults to length of series rtype Series return timeseries of mode value Usage Computes the mode over a given window For each window this function will return the most common value of all elements in the window If there are multiple values with the same frequency of occurrence will return the smallest value If window is not provided computes mode over the full series Examples Generate price series and compute mode over 22 observations prices generate_series 100 mode prices 22 See also mean median", "key": "functions/gs_quant.timeseries.statistics.mode", "title": "mode" }, { "body": "percentile percentiles x y None w Window w None r 0 source Rolling percentiles over given window Parameters x Series value series y Optional Series distribution series w Union Window int Window or int number of observations and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value Window size defaults to length of series rtype Series return timeseries of percentiles Usage Calculate percentile rank of y in the sample distribution of x over a rolling window of length w R_t frac sum_ i t N 1 t X_i Y_t 0 5 sum_ i t N 1 t X_i Y_t N times100 Where N is the number of observations in a rolling window If y is not provided calculates percentiles of x over its historical values If window length w is not provided uses an ever growing history of values If w is greater than the available data size returns empty Examples Compute percentile ranks of a series in the sample distribution of a second series over 22 observations a generate_series 100 b generate_series 100 percentiles a b 22 See also zscores", "key": "functions/gs_quant.timeseries.statistics.percentiles", "title": "percentile" }, { "body": "product product x w Window w None r 0 source Rolling product of series over given window Parameters x Series series timeseries w Union Window int Window or int number of observations and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value Window size defaults to length of series rtype Series return timeseries of rolling product Usage Calculate the product of observations over a given rolling window For each time t returns the value of all observations from t w 1 to t multiplied together R_t prod_ i t w 1 t X_i where w is the size of the rolling window If window is not provided computes product over the full series Examples Generate price series and compute rolling sum over 22 observations prices generate_series 100 product 1 returns prices See also sum_", "key": "functions/gs_quant.timeseries.statistics.product", "title": "product" }, { "body": "range range_ x w Window w None r 0 source Range of series over given window Parameters x Series series timeseries w Union Window int Window or int number of observations and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value Window size defaults to length of series rtype Series return timeseries of range Usage Returns the range of the series max min over rolling window R_t max X_ t w 1 X_t min X_ t w 1 X_t where w is the size of the rolling window If window is not provided returns the range over the full series If the window size is greater than the available data will return range of all available values Examples Range of price series over the last 22 observations prices generate_series 100 range_ prices 22 See also min_ max_", "key": "functions/gs_quant.timeseries.statistics.range_", "title": "range" }, { "body": "std std x w Window w None r 0 source Rolling standard deviation of series over given window Parameters x Series series timeseries w Union Window int Window or int number of observations and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value Window size defaults to length of series Return type Series Returns timeseries of standard deviation Usage Provides unbiased estimator of sample standard deviation over a rolling window R_t sqrt frac 1 N 1 sum_ i t w 1 t X_i overline X_t 2 where N is the number of observations in each rolling window w and overline X_t is the mean value over the same window overline X_t frac sum_ i t w 1 t X_i N If window is not provided computes standard deviation over the full series Examples Generate price series and compute standard deviation of returns over 22 observations prices generate_series 100 std returns prices 22 See also sum mean var", "key": "functions/gs_quant.timeseries.statistics.std", "title": "std" }, { "body": "sum sum_ x w Window w None r 0 source Rolling sum of series over given window Parameters x Series series timeseries w Union Window int Window or int number of observations and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value Window size defaults to length of series rtype Series return timeseries of rolling sum Usage Calculate the sum of observations over a given rolling window For each time t returns the value of all observations from t w 1 to t summed together R_t sum_ i t w 1 t X_i where w is the size of the rolling window If window is not provided computes sum over the full series Examples Generate price series and compute rolling sum over 22 observations prices generate_series 100 sum_ prices 22 See also product", "key": "functions/gs_quant.timeseries.statistics.sum_", "title": "sum" }, { "body": "var var x w Window w None r 0 source Rolling variance of series over given window Parameters x Series series timeseries w Union Window int Window or int number of observations and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value Window size defaults to length of series rtype Series return timeseries of variance Usage Provides unbiased estimator of sample variance over a rolling window R_t frac 1 N 1 sum_ i t w 1 t X_i overline X_t 2 where N is the number of observations in each rolling window w and overline X_t is the mean value over the same window overline X_t frac sum_ i t w 1 t X_i N If window is not provided computes variance over the full series Examples Generate price series and compute variance of returns over 22 observations prices generate_series 100 var returns prices 22 See also var mean std", "key": "functions/gs_quant.timeseries.statistics.var", "title": "var" }, { "body": "winsorize winsorize x limit 2 5 w Window w None r 0 source Limit extreme values in series Parameters x Series time series of prices limit float max z score of values w Union Window int Window or int number of observations and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value Window size defaults to length of series rtype Series return timeseries of winsorized values Usage Cap and floor values in the series which have a z score greater or less than provided value This function will restrict the distribution of values Calculates the sample standard deviation and adjusts values which fall outside the specified range to be equal to the upper or lower limits Lower and upper limits are defined as upper mu sigma times limit lower mu sigma times limit Where mu and sigma are sample mean and standard deviation The series is restricted by R_t max min X_t upper lower See winsorising for additional information Examples Generate price series and winsorize z score of returns over 22 observations prices generate_series 100 winsorize zscore returns prices 22 See also zscore mean std", "key": "functions/gs_quant.timeseries.statistics.winsorize", "title": "winsorize" }, { "body": "zscores zscores x w Window w None r 0 source Rolling z scores over a given window Parameters x Series time series of prices w Union Window int Window or int number of observations and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value Window size defaults to length of series rtype Series return timeseries of z scores Usage Calculate standard score of each value in series over given window Standard deviation and sample mean are computed over the specified rolling window then element is normalized to provide a rolling z score R_t frac X_t mu sigma Where mu and sigma are sample mean and standard deviation over the given window If window is not provided computes z score relative to mean and standard deviation over the full series Examples Generate price series and compute z score of returns over 22 observations prices generate_series 100 zscores returns prices 22 See also mean std", "key": "functions/gs_quant.timeseries.statistics.zscores", "title": "zscores" }, { "body": "bollinger_bands bollinger_bands x w Window w None r 0 k 2 source Bollinger bands with given window and width Parameters x Series time series of prices w Union Window int Window or int number of observations and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value Window size defaults to length of series type k float param k band width in standard deviations default 2 rtype DataFrame return date based time series of return Usage Standard deviation bands around the moving average of asset price level Bollinger bands can be used to determine a range around the price level which responds to local volatility changes Returns two series upper u_t and lower l_t u_t bar X_t k sigma_t l_t bar X_t k sigma_t where bar X_t is the moving average over specified window and sigma_t is the rolling standard deviation over the specified window See Bollinger Bands for more information Examples Compute bollinger bands around 20 day moving average at 2 standard deviations prices generate_series 100 bollinger_bands prices 20 2 See also moving_average std", "key": "functions/gs_quant.timeseries.technicals.bollinger_bands", "title": "bollinger_bands" }, { "body": "moving_average moving_average x w Window w None r 0 source Moving average over specified window Parameters x Series time series of prices w Union Window int Window or int number of observations and ramp up to use e g Window 22 10 where 22 is the window size and 10 the ramp up value Window size defaults to length of series Return type Series Returns date based time series of return Usage Simple arithmetic moving average over the specified window number of observations Shorter windows will be more reactive to changes in the asset price but more volatile Larger windows will be smoother but less reactive to near term changes in asset prices R_t frac sum_ i t w 1 t X_t N where N is the number of observations in each rolling window w If window is not provided computes rolling mean over the full series Equivalent to mean Examples Generate price series with 100 observations starting from today s date prices generate_series 100 moving_average prices 22 See also mean", "key": "functions/gs_quant.timeseries.technicals.moving_average", "title": "moving_average" }, { "body": "GS Quant API Packages Data Package gs_quant data Dataset Datetime Package Date Point Instrument Package gs_quant base Priceable Instruments Market Package gs_quant markets PricingContext gs_quant markets HistoricalPricingContext Securities Risk Package Functions Measures Timeseries Package Algebra Analysis Date Time Econometrics Statistics Technical Analysis Index", "key": "index", "title": "GS Quant API" }, { "body": "Instrument Package Instruments", "key": "instrument", "title": "Instrument Package Instruments" }, { "body": "Market Package Securities", "key": "market", "title": "Market Package Securities" }, { "body": "Risk Package Functions aggregate_risk results threshold None source Combine the results of multiple Instrument calc calls into a single result Parameters results Iterable Union DataFrame Future An iterable of Dataframes and or Futures returned by Instrument calc threshold Optional float exclude values whose absolute value falls below this threshold Return type DataFrame Returns A Dataframe with the aggregated results Examples with PricingContext delta_f inst calc risk IRDelta for inst in instruments vega_f inst calc risk IRVega for inst in instruments delta aggregate_risk delta_f threshold 0 1 vega aggregate_risk vega_f delta_f and vega_f are lists of futures where the result will be a Dataframe delta and vega are Dataframes representing the merged risk of the individual instruments subtract_risk left right source Subtract bucketed risk Dimensions must be identical Parameters left DataFrame Results to substract from right DataFrame Results to substract Examples ir_swap IRSwap Pay 10y USD delta_today ir_swap calc risk IRDelta with PricingContext pricing_date business_day_offset datetime date today 1 roll preceding delta_yday_f ir_swap calc risk IRDelta delta_diff subtract_risk delta_today delta_yday_f result Return type DataFrame sort_risk df by date time marketDataType assetId point source Sort bucketed risk Parameters df DataFrame Input Dataframe by Tuple str Columns to sort by Returns A sorted Dataframe Measures DollarPrice gs_quant target common RiskMeasure object Present value in USD Price gs_quant target common RiskMeasure object Present value in local currency ForwardPrice gs_quant target common RiskMeasure object Forward price Theta gs_quant target common RiskMeasure object 1 day Theta CommodDelta gs_quant target common RiskMeasure object Commodity Delta CommodTheta gs_quant target common RiskMeasure object Commodity Theta CommodVega gs_quant target common RiskMeasure object Commodity Vega EqDelta gs_quant target common RiskMeasure object Equity Delta EqGamma gs_quant target common RiskMeasure object Equity Gamma EqVega gs_quant target common RiskMeasure object Equity Vega EqSpot gs_quant target common RiskMeasure object Equity Spot Level EqAnnualImpliedVol gs_quant target common RiskMeasure object Equity Annual Implied Volatility FXDelta gs_quant target common RiskMeasure object FX Delta FXGamma gs_quant target common RiskMeasure object FX Gamma FXVega gs_quant target common RiskMeasure object FX Vega IRDelta gs_quant target common RiskMeasure object Interest Rate Delta IRDeltaParallel gs_quant target common RiskMeasure object Interest Rate Parallel Delta IRDeltaLocalCcy gs_quant target common RiskMeasure object Interest Rate Delta Local Ccy IRDeltaParallelLocalCcy gs_quant target common RiskMeasure object Interest Rate Parallel Delta Local Ccy IRGamma gs_quant target common RiskMeasure object Interest Rate Gamma IRVega gs_quant target common RiskMeasure object Interest Rate Vega IRVegaParallel gs_quant target common RiskMeasure object Interest Rate Parallel Vega IRVegaLocalCcy gs_quant target common RiskMeasure object Interest Rate Vega Local Ccy IRVegaParallelLocalCcy gs_quant target common RiskMeasure object Interest Rate Parallel Vega Local Ccy IRAnnualImpliedVol gs_quant target common RiskMeasure object Interest Rate Annual Implied Volatility IRAnnualATMImpliedVol gs_quant target common RiskMeasure object Interest Rate Annual Implied At The Money Volatility IRDailyImpliedVol gs_quant target common RiskMeasure object Interest Rate Daily Implied Volatility bps IRSpotRate gs_quant target common RiskMeasure object At The Money Spot Rate IRFwdRate gs_quant target common RiskMeasure object Par Rate", "key": "risk", "title": "Risk Package Functions Measures" }, { "body": "Timeseries Package Algebra Analysis Date Time Econometrics Statistics Technical Analysis", "key": "timeseries", "title": "Timeseries Package Algebra Analysis Date Time Econometrics Statistics Technical Analysis" } ] ================================================ FILE: docs/_templates/contexts.rst ================================================ {{ name | escape | underline}} .. currentmodule:: {{ module }} .. autoclass:: {{ objname }} {% block methods %} {% if methods %} .. rubric:: Methods {% for item in methods %} {% if item not in ('has_default',) %} .. automethod:: {{ item }} {% endif %} {%- endfor %} {% endif %} {% endblock %} {% block attributes %} .. rubric:: Properties {% if attributes %} {% for item in attributes %} {% if item not in () %} .. autoattribute:: {{ item }} {% endif %} {%- endfor %} {% endif %} {% endblock %} ================================================ FILE: docs/_templates/instruments.rst ================================================ {{ name | escape | underline}} For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: {{ module }} .. autoclass:: {{ objname }} {% block attributes %} .. rubric:: Properties {% if attributes %} {% for item in attributes %} {% if item not in ('asset_class', 'type', 'PROVIDER') %} .. autoattribute:: {{ item }} {% endif %} {%- endfor %} {% endif %} {% endblock %} ================================================ FILE: docs/_templates/models_class.rst ================================================ {{ name | escape | underline}} .. currentmodule:: {{ module }} .. autoclass:: {{ objname }} {% block attributes %} {% if attributes %} .. rubric:: Properties {% for item in attributes %} {% if item not in () %} .. autoattribute:: {{ item }} {% endif %} {%- endfor %} {% endif %} {% endblock %} {% block methods %} {% if methods %} .. rubric:: Methods {% for item in methods %} {% if item not in ('has_default',) %} .. automethod:: {{ item }} {% endif %} {%- endfor %} {% endif %} {% endblock %} ================================================ FILE: docs/_templates/portfolio_manager.rst ================================================ {{ name | escape | underline}} .. currentmodule:: {{ module }} .. autoclass:: {{ objname }} :inherited-members: :undoc-members: {% block methods %} {% if methods %} .. rubric:: Methods {% for item in methods %} {% if item not in ('has_default',) %} .. automethod:: {{ item }} {% endif %} {%- endfor %} {% endif %} {% endblock %} ================================================ FILE: docs/_templates/processors.rst ================================================ {{ name | escape | underline}} .. currentmodule:: {{ module }} .. autoclass:: {{ objname }} {% block methods %} {% if methods %} .. rubric:: Methods {% for item in methods %} {% if item not in ('has_default',) %} .. automethod:: {{ item }} {% endif %} {%- endfor %} {% endif %} {% endblock %} {% block attributes %} {% if attributes %} .. rubric:: Properties {% for item in attributes %} {% if item not in () %} .. autoattribute:: {{ item }} {% endif %} {%- endfor %} {% endif %} {% endblock %} ================================================ FILE: docs/_templates/report.rst ================================================ {{ name | escape | underline}} .. currentmodule:: {{ module }} .. autoclass:: {{ objname }} {% block methods %} {% if methods %} .. rubric:: Methods {% for item in methods %} {% if item not in ('has_default',) %} .. automethod:: {{ item }} {% endif %} {%- endfor %} {% endif %} {% endblock %} ================================================ FILE: docs/_templates/timeseries_class.rst ================================================ {{ fullname | escape | underline}} .. currentmodule:: {{ module }} .. autoclass:: {{ objname }} {% set l = methods | reject('equalto', '__init__') | list %} {% block methods -%} {% if l %} .. rubric:: {{ _('Methods') }} .. autosummary:: {% for item in l %} ~{{ name }}.{{ item }} {% endfor %} {% endif %} {%- endblock %} ================================================ FILE: docs/_themes/gs/LICENSE.txt ================================================ The MIT License (MIT) Copyright (c) 2013-2018 Dave Snider, Read the Docs, Inc. & contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: docs/_themes/gs/__init__.py ================================================ """Sphinx ReadTheDocs theme. From https://github.com/ryan-roemer/sphinx-bootstrap-theme. """ from os import path __version__ = '0.4.3.dev0' __version_full__ = __version__ def get_html_theme_path(): """Return list of HTML theme paths.""" cur_dir = path.abspath(path.dirname(path.dirname(__file__))) return cur_dir # See http://www.sphinx-doc.org/en/stable/theming.html#distribute-your-theme-as-a-python-package def setup(app): app.add_html_theme('sphinx_rtd_theme', path.abspath(path.dirname(__file__))) ================================================ FILE: docs/_themes/gs/breadcrumbs.html ================================================ {# Support for Sphinx 1.3+ page_source_suffix, but don't break old builds. #} {% if page_source_suffix %} {% set suffix = page_source_suffix %} {% else %} {% set suffix = source_suffix %} {% endif %} {% if meta is defined and meta is not none %} {% set check_meta = True %} {% else %} {% set check_meta = False %} {% endif %} {% if check_meta and 'github_url' in meta %} {% set display_github = True %} {% endif %} {% if check_meta and 'bitbucket_url' in meta %} {% set display_bitbucket = True %} {% endif %} {% if check_meta and 'gitlab_url' in meta %} {% set display_gitlab = True %} {% endif %}
{% if (theme_prev_next_buttons_location == 'top' or theme_prev_next_buttons_location == 'both') and (next or prev) %} {% endif %}
================================================ FILE: docs/_themes/gs/footer.html ================================================ ================================================ FILE: docs/_themes/gs/layout.html ================================================ {# TEMPLATE VAR SETTINGS #} {%- set url_root = pathto('', 1) %} {%- if url_root == '#' %}{% set url_root = '' %}{% endif %} {%- if not embedded and docstitle %} {%- set titlesuffix = " — "|safe + docstitle|e %} {%- else %} {%- set titlesuffix = "" %} {%- endif %} {%- set lang_attr = 'en' if language == None else (language | replace('_', '-')) %} {{ metatags }} {% block htmltitle %} {{ title|striptags|e }}{{ titlesuffix }} {% endblock %} {# GS Developer React #} {# pathto is traversing up from a known static point. #} {# auto-mount is housed outside of the Sphinx directories. #} {# MathJax CDN #} {# FAVICON #} {% if favicon %} {% endif %} {# CANONICAL URL #} {% if theme_canonical_url %} {% endif %} {# JAVASCRIPTS #} {%- block scripts %} {%- if not embedded %} {# XXX Sphinx 1.8.0 made this an external js-file, quick fix until we refactor the template to inherert more blocks directly from sphinx #} {% if sphinx_version >= "1.8.0" %} {% else %} {%- for scriptfile in script_files %} {%- endfor %} {% endif %} {%- endif %} {%- endblock %} {# CSS #} {%- for css in css_files %} {%- if css|attr("rel") %} {%- else %} {%- endif %} {%- endfor %} {%- for cssfile in extra_css_files %} {%- endfor %} {%- block linktags %} {%- if hasdoc('about') %} {%- endif %} {%- if hasdoc('genindex') %} {%- endif %} {%- if hasdoc('copyright') %} {%- endif %} {%- if next %} {%- endif %} {%- if prev %} {%- endif %} {%- endblock %} {%- block extrahead %} {% endblock %}
{% block extrabody %} {% endblock %}
{# SIDE NAV, TOGGLES ON MOBILE #}
{%- block content %} {% if theme_style_external_links|tobool %}
{% include "versions.html" %} {# Do not conflict with RTD insertion of analytics script #} {% if not READTHEDOCS %} {% if theme_analytics_id %} {% endif %} {% endif %} {%- block footer %} {% endblock %} ================================================ FILE: docs/_themes/gs/static/css/theme.css ================================================ /* sphinx_rtd_theme version 0.4.3 | MIT license */ /* Built 20190212 16:02 */ * { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } @font-face { font-family: "Roboto-Regular"; src: url("/resources/fonts/Roboto-Regular.ttf"), url("/resources/fonts/Roboto-Regular.woff"), url("/resources/fonts/Roboto-Regular.woff2"); } @font-face { font-family: "Roboto-Medium"; src: url("/resources/fonts/Roboto-Medium.ttf"), url("/resources/fonts/Roboto-Medium.woff"), url("/resources/fonts/Roboto-Medium.woff2"); } @font-face { font-family: "Roboto Mono"; src: url("/resources/fonts/Roboto-Mono-Regular.ttf"), url("/resources/fonts/Roboto-Mono-Regular.woff"), url("/resources/fonts/Roboto-Mono-Regular.woff2"); } @font-face { font-family: "Material-Icons"; src: url("/resources/fonts/MaterialIcons-Regular.ttf"), url("/resources/fonts/MaterialIcons-Regular.woff"), url("/resources/fonts/MaterialIcons-Regular.woff2"); } article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { display: block; } audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; } audio:not([controls]) { display: none; } [hidden] { display: none; } * { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } html { font-size: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; text-rendering: optimizeLegibility; } body { margin: 0; } a:hover, a:active { outline: 0; } abbr[title] { border-bottom: 1px dotted; } b, strong { font-weight: bold; } blockquote { margin: 0; } dfn { font-style: italic; } ins { background: #ff9; color: #000; text-decoration: none; } mark { background: #ff0; color: #000; font-style: italic; font-weight: bold; } pre, code, .rst-content tt, .rst-content code, kbd, samp { font-family: "Roboto Mono", serif; _font-family: "courier new", monospace; font-size: 1em; } pre { white-space: pre; } q { quotes: none; } q:before, q:after { content: ""; content: none; } small { font-size: 85%; } sub, sup { /* font-size: 75%; */ line-height: 0; position: relative; vertical-align: baseline; } sup { top: -0.5em; } sub { bottom: -0.25em; } ul, ol, dl { margin: 0; padding: 0; list-style: none; list-style-image: none; } li { list-style: none; } dd { margin: 0; } img { border: 0; -ms-interpolation-mode: bicubic; vertical-align: middle; max-width: 100%; } svg:not(:root) { overflow: hidden; } figure { margin: 0; } form { margin: 0; } fieldset { border: 0; margin: 0; padding: 0; } label { cursor: pointer; } legend { border: 0; *margin-left: -7px; padding: 0; white-space: normal; } button, input, select, textarea { font-size: 100%; margin: 0; vertical-align: baseline; *vertical-align: middle; } button, input { line-height: normal; } button, input[type="button"], input[type="reset"], input[type="submit"] { cursor: pointer; -webkit-appearance: button; *overflow: visible; } button[disabled], input[disabled] { cursor: default; } input[type="checkbox"], input[type="radio"] { box-sizing: border-box; padding: 0; *width: 13px; *height: 13px; } input[type="search"] { -webkit-appearance: textfield; -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box; } input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; } button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } textarea { overflow: auto; vertical-align: top; resize: vertical; } table { border-collapse: collapse; border-spacing: 0; } td { vertical-align: top; } .chromeframe { margin: 0.2em 0; background: #ccc; color: #000; padding: 0.2em 0; } .ir { display: block; border: 0; text-indent: -999em; overflow: hidden; background-color: transparent; background-repeat: no-repeat; text-align: left; direction: ltr; *line-height: 0; } .ir br { display: none; } .hidden { display: none !important; visibility: hidden; } .visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } .visuallyhidden.focusable:active, .visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; } .invisible { visibility: hidden; } .relative { position: relative; } big, small { font-size: 100%; } @media print { html, body, section { background: none !important; } * { box-shadow: none !important; text-shadow: none !important; filter: none !important; -ms-filter: none !important; } a, a:visited { text-decoration: underline; } .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } pre, blockquote { page-break-inside: avoid; } thead { display: table-header-group; } tr, img { page-break-inside: avoid; } img { max-width: 100% !important; } @page { margin: 0.5cm; } p, h2, .rst-content .toctree-wrapper p.caption, h3 { orphans: 3; widows: 3; } h2, .rst-content .toctree-wrapper p.caption, h3 { page-break-after: avoid; } } .fa:before, .wy-menu-vertical li span.toctree-expand:before, .wy-menu-vertical li.on a span.toctree-expand:before, .wy-menu-vertical li.current > a span.toctree-expand:before, .rst-content .admonition-title:before, .rst-content h1 .headerlink:before, .rst-content h2 .headerlink:before, .rst-content h3 .headerlink:before, .rst-content h4 .headerlink:before, .rst-content h5 .headerlink:before, .rst-content h6 .headerlink:before, .rst-content dl dt .headerlink:before, .rst-content p.caption .headerlink:before, .rst-content table > caption .headerlink:before, .rst-content .code-block-caption .headerlink:before, .rst-content tt.download span:first-child:before, .rst-content code.download span:first-child:before, .icon:before, .wy-dropdown .caret:before, .wy-inline-validate.wy-inline-validate-success .wy-input-context:before, .wy-inline-validate.wy-inline-validate-danger .wy-input-context:before, .wy-inline-validate.wy-inline-validate-warning .wy-input-context:before, .wy-inline-validate.wy-inline-validate-info .wy-input-context:before, .wy-alert, .rst-content .note, .rst-content .attention, .rst-content .caution, .rst-content .danger, .rst-content .error, .rst-content .hint, .rst-content .important, .rst-content .tip, .rst-content .warning, .rst-content .seealso, .rst-content .admonition-todo, .rst-content .admonition, .btn, input[type="text"], input[type="password"], input[type="email"], input[type="url"], input[type="date"], input[type="month"], input[type="time"], input[type="datetime"], input[type="datetime-local"], input[type="week"], input[type="number"], input[type="search"], input[type="tel"], input[type="color"], select, textarea, .wy-menu-vertical li.on a, .wy-menu-vertical li.current > a, .wy-side-nav-search > a, .wy-side-nav-search .wy-dropdown > a, .wy-nav-top a { -webkit-font-smoothing: antialiased; } .clearfix { *zoom: 1; } .clearfix:before, .clearfix:after { display: table; content: ""; } .clearfix:after { clear: both; } .fa-border { padding: 0.2em 0.25em 0.15em; border: solid 0.08em #eee; border-radius: 0.1em; } .fa-pull-left { float: left; } .fa-pull-right { float: right; } .fa.fa-pull-left, .wy-menu-vertical li span.fa-pull-left.toctree-expand, .wy-menu-vertical li.on a span.fa-pull-left.toctree-expand, .wy-menu-vertical li.current > a span.fa-pull-left.toctree-expand, .rst-content .fa-pull-left.admonition-title, .rst-content h1 .fa-pull-left.headerlink, .rst-content h2 .fa-pull-left.headerlink, .rst-content h3 .fa-pull-left.headerlink, .rst-content h4 .fa-pull-left.headerlink, .rst-content h5 .fa-pull-left.headerlink, .rst-content h6 .fa-pull-left.headerlink, .rst-content dl dt .fa-pull-left.headerlink, .rst-content p.caption .fa-pull-left.headerlink, .rst-content table > caption .fa-pull-left.headerlink, .rst-content .code-block-caption .fa-pull-left.headerlink, .rst-content tt.download span.fa-pull-left:first-child, .rst-content code.download span.fa-pull-left:first-child, .fa-pull-left.icon { margin-right: 0.3em; } .fa.fa-pull-right, .wy-menu-vertical li span.fa-pull-right.toctree-expand, .wy-menu-vertical li.on a span.fa-pull-right.toctree-expand, .wy-menu-vertical li.current > a span.fa-pull-right.toctree-expand, .rst-content .fa-pull-right.admonition-title, .rst-content h1 .fa-pull-right.headerlink, .rst-content h2 .fa-pull-right.headerlink, .rst-content h3 .fa-pull-right.headerlink, .rst-content h4 .fa-pull-right.headerlink, .rst-content h5 .fa-pull-right.headerlink, .rst-content h6 .fa-pull-right.headerlink, .rst-content dl dt .fa-pull-right.headerlink, .rst-content p.caption .fa-pull-right.headerlink, .rst-content table > caption .fa-pull-right.headerlink, .rst-content .code-block-caption .fa-pull-right.headerlink, .rst-content tt.download span.fa-pull-right:first-child, .rst-content code.download span.fa-pull-right:first-child, .fa-pull-right.icon { margin-left: 0.3em; } .pull-right { float: right; } .pull-left { float: left; } .fa.pull-left, .wy-menu-vertical li span.pull-left.toctree-expand, .wy-menu-vertical li.on a span.pull-left.toctree-expand, .wy-menu-vertical li.current > a span.pull-left.toctree-expand, .rst-content .pull-left.admonition-title, .rst-content h1 .pull-left.headerlink, .rst-content h2 .pull-left.headerlink, .rst-content h3 .pull-left.headerlink, .rst-content h4 .pull-left.headerlink, .rst-content h5 .pull-left.headerlink, .rst-content h6 .pull-left.headerlink, .rst-content dl dt .pull-left.headerlink, .rst-content p.caption .pull-left.headerlink, .rst-content table > caption .pull-left.headerlink, .rst-content .code-block-caption .pull-left.headerlink, .rst-content tt.download span.pull-left:first-child, .rst-content code.download span.pull-left:first-child, .pull-left.icon { margin-right: 0.3em; } .fa.pull-right, .wy-menu-vertical li span.pull-right.toctree-expand, .wy-menu-vertical li.on a span.pull-right.toctree-expand, .wy-menu-vertical li.current > a span.pull-right.toctree-expand, .rst-content .pull-right.admonition-title, .rst-content h1 .pull-right.headerlink, .rst-content h2 .pull-right.headerlink, .rst-content h3 .pull-right.headerlink, .rst-content h4 .pull-right.headerlink, .rst-content h5 .pull-right.headerlink, .rst-content h6 .pull-right.headerlink, .rst-content dl dt .pull-right.headerlink, .rst-content p.caption .pull-right.headerlink, .rst-content table > caption .pull-right.headerlink, .rst-content .code-block-caption .pull-right.headerlink, .rst-content tt.download span.pull-right:first-child, .rst-content code.download span.pull-right:first-child, .pull-right.icon { margin-left: 0.3em; } .fa-arrow-circle-left:before, .icon-circle-arrow-left:before { content: url(../images/gs-arrow-left.svg); position: relative; top: 5px; right: 4px; } .fa-arrow-circle-right:before, .icon-circle-arrow-right:before { content: url(../images/gs-arrow-right.svg); position: relative; top: 5px; left: 0px; } .fa-minus-square:before { content: url(../images/gs-menu-collapse.svg); } .fa-minus-square-o:before, .wy-menu-vertical li.on a span.toctree-expand:before, .wy-menu-vertical li.current > a span.toctree-expand:before { content: url(../images/gs-menu-collapse.svg); } .fa-plus-square-o:before, .wy-menu-vertical li span.toctree-expand:before { content: url(../images/gs-menu-expand.svg); } .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); border: 0; } .sr-only-focusable:active, .sr-only-focusable:focus { position: static; width: auto; height: auto; margin: 0; overflow: visible; clip: auto; } .fa, .wy-menu-vertical li span.toctree-expand, .wy-menu-vertical li.on a span.toctree-expand, .wy-menu-vertical li.current > a span.toctree-expand, .rst-content .admonition-title, .rst-content h1 .headerlink, .rst-content h2 .headerlink, .rst-content h3 .headerlink, .rst-content h4 .headerlink, .rst-content h5 .headerlink, .rst-content h6 .headerlink, .rst-content dl dt .headerlink, .rst-content p.caption .headerlink, .rst-content table > caption .headerlink, .rst-content .code-block-caption .headerlink, .rst-content tt.download span:first-child, .rst-content code.download span:first-child, .icon, .wy-dropdown .caret, .wy-inline-validate.wy-inline-validate-success .wy-input-context, .wy-inline-validate.wy-inline-validate-danger .wy-input-context, .wy-inline-validate.wy-inline-validate-warning .wy-input-context, .wy-inline-validate.wy-inline-validate-info .wy-input-context { font-family: inherit; } .fa:before, .wy-menu-vertical li span.toctree-expand:before, .wy-menu-vertical li.on a span.toctree-expand:before, .wy-menu-vertical li.current > a span.toctree-expand:before, .rst-content .admonition-title:before, .rst-content h1 .headerlink:before, .rst-content h2 .headerlink:before, .rst-content h3 .headerlink:before, .rst-content h4 .headerlink:before, .rst-content h5 .headerlink:before, .rst-content h6 .headerlink:before, .rst-content dl dt .headerlink:before, .rst-content p.caption .headerlink:before, .rst-content table > caption .headerlink:before, .rst-content .code-block-caption .headerlink:before, .rst-content tt.download span:first-child:before, .rst-content code.download span:first-child:before, .icon:before, .wy-dropdown .caret:before, .wy-inline-validate.wy-inline-validate-success .wy-input-context:before, .wy-inline-validate.wy-inline-validate-danger .wy-input-context:before, .wy-inline-validate.wy-inline-validate-warning .wy-input-context:before, .wy-inline-validate.wy-inline-validate-info .wy-input-context:before { display: inline-block; font-style: normal; font-weight: normal; line-height: 1; text-decoration: inherit; width: 12px; height: 16px; margin-top: -2px; margin-left: -8px; } a .fa, a .wy-menu-vertical li span.toctree-expand, .wy-menu-vertical li a span.toctree-expand, .wy-menu-vertical li.on a span.toctree-expand, .wy-menu-vertical li.current > a span.toctree-expand, a .rst-content .admonition-title, .rst-content a .admonition-title, a .rst-content h1 .headerlink, .rst-content h1 a .headerlink, a .rst-content h2 .headerlink, .rst-content h2 a .headerlink, a .rst-content h3 .headerlink, .rst-content h3 a .headerlink, a .rst-content h4 .headerlink, .rst-content h4 a .headerlink, a .rst-content h5 .headerlink, .rst-content h5 a .headerlink, a .rst-content h6 .headerlink, .rst-content h6 a .headerlink, a .rst-content dl dt .headerlink, .rst-content dl dt a .headerlink, a .rst-content p.caption .headerlink, .rst-content p.caption a .headerlink, a .rst-content table > caption .headerlink, .rst-content table > caption a .headerlink, a .rst-content .code-block-caption .headerlink, .rst-content .code-block-caption a .headerlink, a .rst-content tt.download span:first-child, .rst-content tt.download a span:first-child, a .rst-content code.download span:first-child, .rst-content code.download a span:first-child, a .icon { display: inline-block; text-decoration: inherit; } .btn .fa, .btn .wy-menu-vertical li span.toctree-expand, .wy-menu-vertical li .btn span.toctree-expand, .btn .wy-menu-vertical li.on a span.toctree-expand, .wy-menu-vertical li.on a .btn span.toctree-expand, .btn .wy-menu-vertical li.current > a span.toctree-expand, .wy-menu-vertical li.current > a .btn span.toctree-expand, .btn .rst-content .admonition-title, .rst-content .btn .admonition-title, .btn .rst-content h1 .headerlink, .rst-content h1 .btn .headerlink, .btn .rst-content h2 .headerlink, .rst-content h2 .btn .headerlink, .btn .rst-content h3 .headerlink, .rst-content h3 .btn .headerlink, .btn .rst-content h4 .headerlink, .rst-content h4 .btn .headerlink, .btn .rst-content h5 .headerlink, .rst-content h5 .btn .headerlink, .btn .rst-content h6 .headerlink, .rst-content h6 .btn .headerlink, .btn .rst-content dl dt .headerlink, .rst-content dl dt .btn .headerlink, .btn .rst-content p.caption .headerlink, .rst-content p.caption .btn .headerlink, .btn .rst-content table > caption .headerlink, .rst-content table > caption .btn .headerlink, .btn .rst-content .code-block-caption .headerlink, .rst-content .code-block-caption .btn .headerlink, .btn .rst-content tt.download span:first-child, .rst-content tt.download .btn span:first-child, .btn .rst-content code.download span:first-child, .rst-content code.download .btn span:first-child, .btn .icon, .nav .fa, .nav .wy-menu-vertical li span.toctree-expand, .wy-menu-vertical li .nav span.toctree-expand, .nav .wy-menu-vertical li.on a span.toctree-expand, .wy-menu-vertical li.on a .nav span.toctree-expand, .nav .wy-menu-vertical li.current > a span.toctree-expand, .wy-menu-vertical li.current > a .nav span.toctree-expand, .nav .rst-content .admonition-title, .rst-content .nav .admonition-title, .nav .rst-content h1 .headerlink, .rst-content h1 .nav .headerlink, .nav .rst-content h2 .headerlink, .rst-content h2 .nav .headerlink, .nav .rst-content h3 .headerlink, .rst-content h3 .nav .headerlink, .nav .rst-content h4 .headerlink, .rst-content h4 .nav .headerlink, .nav .rst-content h5 .headerlink, .rst-content h5 .nav .headerlink, .nav .rst-content h6 .headerlink, .rst-content h6 .nav .headerlink, .nav .rst-content dl dt .headerlink, .rst-content dl dt .nav .headerlink, .nav .rst-content p.caption .headerlink, .rst-content p.caption .nav .headerlink, .nav .rst-content table > caption .headerlink, .rst-content table > caption .nav .headerlink, .nav .rst-content .code-block-caption .headerlink, .rst-content .code-block-caption .nav .headerlink, .nav .rst-content tt.download span:first-child, .rst-content tt.download .nav span:first-child, .nav .rst-content code.download span:first-child, .rst-content code.download .nav span:first-child, .nav .icon { display: inline; } .btn .fa.fa-large, .btn .wy-menu-vertical li span.fa-large.toctree-expand, .wy-menu-vertical li .btn span.fa-large.toctree-expand, .btn .rst-content .fa-large.admonition-title, .rst-content .btn .fa-large.admonition-title, .btn .rst-content h1 .fa-large.headerlink, .rst-content h1 .btn .fa-large.headerlink, .btn .rst-content h2 .fa-large.headerlink, .rst-content h2 .btn .fa-large.headerlink, .btn .rst-content h3 .fa-large.headerlink, .rst-content h3 .btn .fa-large.headerlink, .btn .rst-content h4 .fa-large.headerlink, .rst-content h4 .btn .fa-large.headerlink, .btn .rst-content h5 .fa-large.headerlink, .rst-content h5 .btn .fa-large.headerlink, .btn .rst-content h6 .fa-large.headerlink, .rst-content h6 .btn .fa-large.headerlink, .btn .rst-content dl dt .fa-large.headerlink, .rst-content dl dt .btn .fa-large.headerlink, .btn .rst-content p.caption .fa-large.headerlink, .rst-content p.caption .btn .fa-large.headerlink, .btn .rst-content table > caption .fa-large.headerlink, .rst-content table > caption .btn .fa-large.headerlink, .btn .rst-content .code-block-caption .fa-large.headerlink, .rst-content .code-block-caption .btn .fa-large.headerlink, .btn .rst-content tt.download span.fa-large:first-child, .rst-content tt.download .btn span.fa-large:first-child, .btn .rst-content code.download span.fa-large:first-child, .rst-content code.download .btn span.fa-large:first-child, .btn .fa-large.icon, .nav .fa.fa-large, .nav .wy-menu-vertical li span.fa-large.toctree-expand, .wy-menu-vertical li .nav span.fa-large.toctree-expand, .nav .rst-content .fa-large.admonition-title, .rst-content .nav .fa-large.admonition-title, .nav .rst-content h1 .fa-large.headerlink, .rst-content h1 .nav .fa-large.headerlink, .nav .rst-content h2 .fa-large.headerlink, .rst-content h2 .nav .fa-large.headerlink, .nav .rst-content h3 .fa-large.headerlink, .rst-content h3 .nav .fa-large.headerlink, .nav .rst-content h4 .fa-large.headerlink, .rst-content h4 .nav .fa-large.headerlink, .nav .rst-content h5 .fa-large.headerlink, .rst-content h5 .nav .fa-large.headerlink, .nav .rst-content h6 .fa-large.headerlink, .rst-content h6 .nav .fa-large.headerlink, .nav .rst-content dl dt .fa-large.headerlink, .rst-content dl dt .nav .fa-large.headerlink, .nav .rst-content p.caption .fa-large.headerlink, .rst-content p.caption .nav .fa-large.headerlink, .nav .rst-content table > caption .fa-large.headerlink, .rst-content table > caption .nav .fa-large.headerlink, .nav .rst-content .code-block-caption .fa-large.headerlink, .rst-content .code-block-caption .nav .fa-large.headerlink, .nav .rst-content tt.download span.fa-large:first-child, .rst-content tt.download .nav span.fa-large:first-child, .nav .rst-content code.download span.fa-large:first-child, .rst-content code.download .nav span.fa-large:first-child, .nav .fa-large.icon { line-height: 0.9em; } .btn .fa.fa-spin, .btn .wy-menu-vertical li span.fa-spin.toctree-expand, .wy-menu-vertical li .btn span.fa-spin.toctree-expand, .btn .rst-content .fa-spin.admonition-title, .rst-content .btn .fa-spin.admonition-title, .btn .rst-content h1 .fa-spin.headerlink, .rst-content h1 .btn .fa-spin.headerlink, .btn .rst-content h2 .fa-spin.headerlink, .rst-content h2 .btn .fa-spin.headerlink, .btn .rst-content h3 .fa-spin.headerlink, .rst-content h3 .btn .fa-spin.headerlink, .btn .rst-content h4 .fa-spin.headerlink, .rst-content h4 .btn .fa-spin.headerlink, .btn .rst-content h5 .fa-spin.headerlink, .rst-content h5 .btn .fa-spin.headerlink, .btn .rst-content h6 .fa-spin.headerlink, .rst-content h6 .btn .fa-spin.headerlink, .btn .rst-content dl dt .fa-spin.headerlink, .rst-content dl dt .btn .fa-spin.headerlink, .btn .rst-content p.caption .fa-spin.headerlink, .rst-content p.caption .btn .fa-spin.headerlink, .btn .rst-content table > caption .fa-spin.headerlink, .rst-content table > caption .btn .fa-spin.headerlink, .btn .rst-content .code-block-caption .fa-spin.headerlink, .rst-content .code-block-caption .btn .fa-spin.headerlink, .btn .rst-content tt.download span.fa-spin:first-child, .rst-content tt.download .btn span.fa-spin:first-child, .btn .rst-content code.download span.fa-spin:first-child, .rst-content code.download .btn span.fa-spin:first-child, .btn .fa-spin.icon, .nav .fa.fa-spin, .nav .wy-menu-vertical li span.fa-spin.toctree-expand, .wy-menu-vertical li .nav span.fa-spin.toctree-expand, .nav .rst-content .fa-spin.admonition-title, .rst-content .nav .fa-spin.admonition-title, .nav .rst-content h1 .fa-spin.headerlink, .rst-content h1 .nav .fa-spin.headerlink, .nav .rst-content h2 .fa-spin.headerlink, .rst-content h2 .nav .fa-spin.headerlink, .nav .rst-content h3 .fa-spin.headerlink, .rst-content h3 .nav .fa-spin.headerlink, .nav .rst-content h4 .fa-spin.headerlink, .rst-content h4 .nav .fa-spin.headerlink, .nav .rst-content h5 .fa-spin.headerlink, .rst-content h5 .nav .fa-spin.headerlink, .nav .rst-content h6 .fa-spin.headerlink, .rst-content h6 .nav .fa-spin.headerlink, .nav .rst-content dl dt .fa-spin.headerlink, .rst-content dl dt .nav .fa-spin.headerlink, .nav .rst-content p.caption .fa-spin.headerlink, .rst-content p.caption .nav .fa-spin.headerlink, .nav .rst-content table > caption .fa-spin.headerlink, .rst-content table > caption .nav .fa-spin.headerlink, .nav .rst-content .code-block-caption .fa-spin.headerlink, .rst-content .code-block-caption .nav .fa-spin.headerlink, .nav .rst-content tt.download span.fa-spin:first-child, .rst-content tt.download .nav span.fa-spin:first-child, .nav .rst-content code.download span.fa-spin:first-child, .rst-content code.download .nav span.fa-spin:first-child, .nav .fa-spin.icon { display: inline-block; } .btn.fa:before, .wy-menu-vertical li span.btn.toctree-expand:before, .rst-content .btn.admonition-title:before, .rst-content h1 .btn.headerlink:before, .rst-content h2 .btn.headerlink:before, .rst-content h3 .btn.headerlink:before, .rst-content h4 .btn.headerlink:before, .rst-content h5 .btn.headerlink:before, .rst-content h6 .btn.headerlink:before, .rst-content dl dt .btn.headerlink:before, .rst-content p.caption .btn.headerlink:before, .rst-content table > caption .btn.headerlink:before, .rst-content .code-block-caption .btn.headerlink:before, .rst-content tt.download span.btn:first-child:before, .rst-content code.download span.btn:first-child:before, .btn.icon:before { opacity: 0.5; -webkit-transition: opacity 0.05s ease-in; -moz-transition: opacity 0.05s ease-in; transition: opacity 0.05s ease-in; } .btn.fa:hover:before, .wy-menu-vertical li span.btn.toctree-expand:hover:before, .rst-content .btn.admonition-title:hover:before, .rst-content h1 .btn.headerlink:hover:before, .rst-content h2 .btn.headerlink:hover:before, .rst-content h3 .btn.headerlink:hover:before, .rst-content h4 .btn.headerlink:hover:before, .rst-content h5 .btn.headerlink:hover:before, .rst-content h6 .btn.headerlink:hover:before, .rst-content dl dt .btn.headerlink:hover:before, .rst-content p.caption .btn.headerlink:hover:before, .rst-content table > caption .btn.headerlink:hover:before, .rst-content .code-block-caption .btn.headerlink:hover:before, .rst-content tt.download span.btn:first-child:hover:before, .rst-content code.download span.btn:first-child:hover:before, .btn.icon:hover:before { opacity: 1; } .btn-mini .fa:before, .btn-mini .wy-menu-vertical li span.toctree-expand:before, .wy-menu-vertical li .btn-mini span.toctree-expand:before, .btn-mini .rst-content .admonition-title:before, .rst-content .btn-mini .admonition-title:before, .btn-mini .rst-content h1 .headerlink:before, .rst-content h1 .btn-mini .headerlink:before, .btn-mini .rst-content h2 .headerlink:before, .rst-content h2 .btn-mini .headerlink:before, .btn-mini .rst-content h3 .headerlink:before, .rst-content h3 .btn-mini .headerlink:before, .btn-mini .rst-content h4 .headerlink:before, .rst-content h4 .btn-mini .headerlink:before, .btn-mini .rst-content h5 .headerlink:before, .rst-content h5 .btn-mini .headerlink:before, .btn-mini .rst-content h6 .headerlink:before, .rst-content h6 .btn-mini .headerlink:before, .btn-mini .rst-content dl dt .headerlink:before, .rst-content dl dt .btn-mini .headerlink:before, .btn-mini .rst-content p.caption .headerlink:before, .rst-content p.caption .btn-mini .headerlink:before, .btn-mini .rst-content table > caption .headerlink:before, .rst-content table > caption .btn-mini .headerlink:before, .btn-mini .rst-content .code-block-caption .headerlink:before, .rst-content .code-block-caption .btn-mini .headerlink:before, .btn-mini .rst-content tt.download span:first-child:before, .rst-content tt.download .btn-mini span:first-child:before, .btn-mini .rst-content code.download span:first-child:before, .rst-content code.download .btn-mini span:first-child:before, .btn-mini .icon:before { font-size: 14px; vertical-align: -15%; } .wy-alert, .rst-content .note, .rst-content .attention, .rst-content .caution, .rst-content .danger, .rst-content .error, .rst-content .hint, .rst-content .important, .rst-content .tip, .rst-content .warning, .rst-content .seealso, .rst-content .admonition-todo, .rst-content .admonition { padding: 12px; line-height: 24px; margin-bottom: 24px; background: #e7f2fa; } .wy-alert-title, .rst-content .admonition-title { color: #fff; font-weight: bold; display: block; color: #fff; background: #6ab0de; margin: -12px; padding: 6px 12px; margin-bottom: 12px; } .wy-alert.wy-alert-danger, .rst-content .wy-alert-danger.note, .rst-content .wy-alert-danger.attention, .rst-content .wy-alert-danger.caution, .rst-content .danger, .rst-content .error, .rst-content .wy-alert-danger.hint, .rst-content .wy-alert-danger.important, .rst-content .wy-alert-danger.tip, .rst-content .wy-alert-danger.warning, .rst-content .wy-alert-danger.seealso, .rst-content .wy-alert-danger.admonition-todo, .rst-content .wy-alert-danger.admonition { background: #fdf3f2; } .wy-alert.wy-alert-danger .wy-alert-title, .rst-content .wy-alert-danger.note .wy-alert-title, .rst-content .wy-alert-danger.attention .wy-alert-title, .rst-content .wy-alert-danger.caution .wy-alert-title, .rst-content .danger .wy-alert-title, .rst-content .error .wy-alert-title, .rst-content .wy-alert-danger.hint .wy-alert-title, .rst-content .wy-alert-danger.important .wy-alert-title, .rst-content .wy-alert-danger.tip .wy-alert-title, .rst-content .wy-alert-danger.warning .wy-alert-title, .rst-content .wy-alert-danger.seealso .wy-alert-title, .rst-content .wy-alert-danger.admonition-todo .wy-alert-title, .rst-content .wy-alert-danger.admonition .wy-alert-title, .wy-alert.wy-alert-danger .rst-content .admonition-title, .rst-content .wy-alert.wy-alert-danger .admonition-title, .rst-content .wy-alert-danger.note .admonition-title, .rst-content .wy-alert-danger.attention .admonition-title, .rst-content .wy-alert-danger.caution .admonition-title, .rst-content .danger .admonition-title, .rst-content .error .admonition-title, .rst-content .wy-alert-danger.hint .admonition-title, .rst-content .wy-alert-danger.important .admonition-title, .rst-content .wy-alert-danger.tip .admonition-title, .rst-content .wy-alert-danger.warning .admonition-title, .rst-content .wy-alert-danger.seealso .admonition-title, .rst-content .wy-alert-danger.admonition-todo .admonition-title, .rst-content .wy-alert-danger.admonition .admonition-title { background: #f29f97; } .wy-alert.wy-alert-warning, .rst-content .wy-alert-warning.note, .rst-content .attention, .rst-content .caution, .rst-content .wy-alert-warning.danger, .rst-content .wy-alert-warning.error, .rst-content .wy-alert-warning.hint, .rst-content .wy-alert-warning.important, .rst-content .wy-alert-warning.tip, .rst-content .warning, .rst-content .wy-alert-warning.seealso, .rst-content .admonition-todo, .rst-content .wy-alert-warning.admonition { background: #ffedcc; } .wy-alert.wy-alert-warning .wy-alert-title, .rst-content .wy-alert-warning.note .wy-alert-title, .rst-content .attention .wy-alert-title, .rst-content .caution .wy-alert-title, .rst-content .wy-alert-warning.danger .wy-alert-title, .rst-content .wy-alert-warning.error .wy-alert-title, .rst-content .wy-alert-warning.hint .wy-alert-title, .rst-content .wy-alert-warning.important .wy-alert-title, .rst-content .wy-alert-warning.tip .wy-alert-title, .rst-content .warning .wy-alert-title, .rst-content .wy-alert-warning.seealso .wy-alert-title, .rst-content .admonition-todo .wy-alert-title, .rst-content .wy-alert-warning.admonition .wy-alert-title, .wy-alert.wy-alert-warning .rst-content .admonition-title, .rst-content .wy-alert.wy-alert-warning .admonition-title, .rst-content .wy-alert-warning.note .admonition-title, .rst-content .attention .admonition-title, .rst-content .caution .admonition-title, .rst-content .wy-alert-warning.danger .admonition-title, .rst-content .wy-alert-warning.error .admonition-title, .rst-content .wy-alert-warning.hint .admonition-title, .rst-content .wy-alert-warning.important .admonition-title, .rst-content .wy-alert-warning.tip .admonition-title, .rst-content .warning .admonition-title, .rst-content .wy-alert-warning.seealso .admonition-title, .rst-content .admonition-todo .admonition-title, .rst-content .wy-alert-warning.admonition .admonition-title { background: #f0b37e; } .wy-alert.wy-alert-info, .rst-content .note, .rst-content .wy-alert-info.attention, .rst-content .wy-alert-info.caution, .rst-content .wy-alert-info.danger, .rst-content .wy-alert-info.error, .rst-content .wy-alert-info.hint, .rst-content .wy-alert-info.important, .rst-content .wy-alert-info.tip, .rst-content .wy-alert-info.warning, .rst-content .seealso, .rst-content .wy-alert-info.admonition-todo, .rst-content .wy-alert-info.admonition { background: #e7f2fa; } .wy-alert.wy-alert-info .wy-alert-title, .rst-content .note .wy-alert-title, .rst-content .wy-alert-info.attention .wy-alert-title, .rst-content .wy-alert-info.caution .wy-alert-title, .rst-content .wy-alert-info.danger .wy-alert-title, .rst-content .wy-alert-info.error .wy-alert-title, .rst-content .wy-alert-info.hint .wy-alert-title, .rst-content .wy-alert-info.important .wy-alert-title, .rst-content .wy-alert-info.tip .wy-alert-title, .rst-content .wy-alert-info.warning .wy-alert-title, .rst-content .seealso .wy-alert-title, .rst-content .wy-alert-info.admonition-todo .wy-alert-title, .rst-content .wy-alert-info.admonition .wy-alert-title, .wy-alert.wy-alert-info .rst-content .admonition-title, .rst-content .wy-alert.wy-alert-info .admonition-title, .rst-content .note .admonition-title, .rst-content .wy-alert-info.attention .admonition-title, .rst-content .wy-alert-info.caution .admonition-title, .rst-content .wy-alert-info.danger .admonition-title, .rst-content .wy-alert-info.error .admonition-title, .rst-content .wy-alert-info.hint .admonition-title, .rst-content .wy-alert-info.important .admonition-title, .rst-content .wy-alert-info.tip .admonition-title, .rst-content .wy-alert-info.warning .admonition-title, .rst-content .seealso .admonition-title, .rst-content .wy-alert-info.admonition-todo .admonition-title, .rst-content .wy-alert-info.admonition .admonition-title { background: #6ab0de; } .wy-alert.wy-alert-success, .rst-content .wy-alert-success.note, .rst-content .wy-alert-success.attention, .rst-content .wy-alert-success.caution, .rst-content .wy-alert-success.danger, .rst-content .wy-alert-success.error, .rst-content .hint, .rst-content .important, .rst-content .tip, .rst-content .wy-alert-success.warning, .rst-content .wy-alert-success.seealso, .rst-content .wy-alert-success.admonition-todo, .rst-content .wy-alert-success.admonition { background: #dbfaf4; } .wy-alert.wy-alert-success .wy-alert-title, .rst-content .wy-alert-success.note .wy-alert-title, .rst-content .wy-alert-success.attention .wy-alert-title, .rst-content .wy-alert-success.caution .wy-alert-title, .rst-content .wy-alert-success.danger .wy-alert-title, .rst-content .wy-alert-success.error .wy-alert-title, .rst-content .hint .wy-alert-title, .rst-content .important .wy-alert-title, .rst-content .tip .wy-alert-title, .rst-content .wy-alert-success.warning .wy-alert-title, .rst-content .wy-alert-success.seealso .wy-alert-title, .rst-content .wy-alert-success.admonition-todo .wy-alert-title, .rst-content .wy-alert-success.admonition .wy-alert-title, .wy-alert.wy-alert-success .rst-content .admonition-title, .rst-content .wy-alert.wy-alert-success .admonition-title, .rst-content .wy-alert-success.note .admonition-title, .rst-content .wy-alert-success.attention .admonition-title, .rst-content .wy-alert-success.caution .admonition-title, .rst-content .wy-alert-success.danger .admonition-title, .rst-content .wy-alert-success.error .admonition-title, .rst-content .hint .admonition-title, .rst-content .important .admonition-title, .rst-content .tip .admonition-title, .rst-content .wy-alert-success.warning .admonition-title, .rst-content .wy-alert-success.seealso .admonition-title, .rst-content .wy-alert-success.admonition-todo .admonition-title, .rst-content .wy-alert-success.admonition .admonition-title { background: #1abc9c; } .wy-alert.wy-alert-neutral, .rst-content .wy-alert-neutral.note, .rst-content .wy-alert-neutral.attention, .rst-content .wy-alert-neutral.caution, .rst-content .wy-alert-neutral.danger, .rst-content .wy-alert-neutral.error, .rst-content .wy-alert-neutral.hint, .rst-content .wy-alert-neutral.important, .rst-content .wy-alert-neutral.tip, .rst-content .wy-alert-neutral.warning, .rst-content .wy-alert-neutral.seealso, .rst-content .wy-alert-neutral.admonition-todo, .rst-content .wy-alert-neutral.admonition { background: #f3f6f6; } .wy-alert.wy-alert-neutral .wy-alert-title, .rst-content .wy-alert-neutral.note .wy-alert-title, .rst-content .wy-alert-neutral.attention .wy-alert-title, .rst-content .wy-alert-neutral.caution .wy-alert-title, .rst-content .wy-alert-neutral.danger .wy-alert-title, .rst-content .wy-alert-neutral.error .wy-alert-title, .rst-content .wy-alert-neutral.hint .wy-alert-title, .rst-content .wy-alert-neutral.important .wy-alert-title, .rst-content .wy-alert-neutral.tip .wy-alert-title, .rst-content .wy-alert-neutral.warning .wy-alert-title, .rst-content .wy-alert-neutral.seealso .wy-alert-title, .rst-content .wy-alert-neutral.admonition-todo .wy-alert-title, .rst-content .wy-alert-neutral.admonition .wy-alert-title, .wy-alert.wy-alert-neutral .rst-content .admonition-title, .rst-content .wy-alert.wy-alert-neutral .admonition-title, .rst-content .wy-alert-neutral.note .admonition-title, .rst-content .wy-alert-neutral.attention .admonition-title, .rst-content .wy-alert-neutral.caution .admonition-title, .rst-content .wy-alert-neutral.danger .admonition-title, .rst-content .wy-alert-neutral.error .admonition-title, .rst-content .wy-alert-neutral.hint .admonition-title, .rst-content .wy-alert-neutral.important .admonition-title, .rst-content .wy-alert-neutral.tip .admonition-title, .rst-content .wy-alert-neutral.warning .admonition-title, .rst-content .wy-alert-neutral.seealso .admonition-title, .rst-content .wy-alert-neutral.admonition-todo .admonition-title, .rst-content .wy-alert-neutral.admonition .admonition-title { color: #404040; background: #e1e4e5; } .wy-alert.wy-alert-neutral a, .rst-content .wy-alert-neutral.note a, .rst-content .wy-alert-neutral.attention a, .rst-content .wy-alert-neutral.caution a, .rst-content .wy-alert-neutral.danger a, .rst-content .wy-alert-neutral.error a, .rst-content .wy-alert-neutral.hint a, .rst-content .wy-alert-neutral.important a, .rst-content .wy-alert-neutral.tip a, .rst-content .wy-alert-neutral.warning a, .rst-content .wy-alert-neutral.seealso a, .rst-content .wy-alert-neutral.admonition-todo a, .rst-content .wy-alert-neutral.admonition a { color: #2980b9; } .wy-alert p:last-child, .rst-content .note p:last-child, .rst-content .attention p:last-child, .rst-content .caution p:last-child, .rst-content .danger p:last-child, .rst-content .error p:last-child, .rst-content .hint p:last-child, .rst-content .important p:last-child, .rst-content .tip p:last-child, .rst-content .warning p:last-child, .rst-content .seealso p:last-child, .rst-content .admonition-todo p:last-child, .rst-content .admonition p:last-child { margin-bottom: 0; } .wy-tray-container { position: fixed; bottom: 0px; left: 0; z-index: 600; } .wy-tray-container li { display: block; width: 300px; background: transparent; color: #fff; text-align: center; box-shadow: 0 5px 5px 0 rgba(0, 0, 0, 0.1); padding: 0 24px; min-width: 20%; opacity: 0; height: 0; line-height: 56px; overflow: hidden; -webkit-transition: all 0.3s ease-in; -moz-transition: all 0.3s ease-in; transition: all 0.3s ease-in; } .wy-tray-container li.wy-tray-item-success { background: #27ae60; } .wy-tray-container li.wy-tray-item-info { background: #2980b9; } .wy-tray-container li.wy-tray-item-warning { background: #e67e22; } .wy-tray-container li.wy-tray-item-danger { background: #e74c3c; } .wy-tray-container li.on { opacity: 1; height: 56px; } @media screen and (max-width: 768px) { .wy-tray-container { bottom: auto; top: 0; width: 100%; } .wy-tray-container li { width: 100%; } } button { font-size: 100%; margin: 0; vertical-align: baseline; *vertical-align: middle; cursor: pointer; line-height: normal; -webkit-appearance: button; *overflow: visible; } button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } button[disabled] { cursor: default; } .btn { display: inline-block; line-height: normal; white-space: nowrap; text-align: center; cursor: pointer; font-size: 16px; padding: 24px; color: #fff; background-color: #fafafa; text-decoration: none; font-weight: normal; font-family: "Roboto-Regular", sans-serif; outline-none: false; vertical-align: middle; *display: inline; zoom: 1; -webkit-user-drag: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; width: 50%; } .btn-hover { background: #2e8ece; color: #fff; } .btn:hover { background: #2cc36b; color: #fff; } .btn:focus { background: #2cc36b; outline: 0; } .btn:active { box-shadow: 0px -1px 0px 0px rgba(0, 0, 0, 0.05) inset, 0px 2px 0px 0px rgba(0, 0, 0, 0.1) inset; padding: 8px 12px 6px 12px; } .btn:visited { color: #fff; } .btn:disabled { background-image: none; filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); filter: alpha(opacity=40); opacity: 0.4; cursor: not-allowed; box-shadow: none; } .btn-disabled { background-image: none; filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); filter: alpha(opacity=40); opacity: 0.4; cursor: not-allowed; box-shadow: none; } .btn-disabled:hover, .btn-disabled:focus, .btn-disabled:active { background-image: none; filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); filter: alpha(opacity=40); opacity: 0.4; cursor: not-allowed; box-shadow: none; } .btn::-moz-focus-inner { padding: 0; border: 0; } .btn-small { font-size: 80%; } .btn-info { background-color: #2980b9 !important; } .btn-info:hover { background-color: #2e8ece !important; } .btn-neutral { background-color: #fafafa !important; color: #404040 !important; } .btn-neutral:hover { background-color: #fafafa !important; color: #404040; } .btn-neutral:visited { color: #404040 !important; } .btn-success { background-color: #27ae60 !important; } .btn-success:hover { background-color: #295 !important; } .btn-danger { background-color: #e74c3c !important; } .btn-danger:hover { background-color: #ea6153 !important; } .btn-warning { background-color: #e67e22 !important; } .btn-warning:hover { background-color: #e98b39 !important; } .btn-invert { background-color: #222; } .btn-invert:hover { background-color: #2f2f2f !important; } .btn-link { background-color: transparent !important; color: #2980b9; box-shadow: none; border-color: transparent !important; } .btn-link:hover { background-color: transparent !important; color: #409ad5 !important; box-shadow: none; } .btn-link:active { background-color: transparent !important; color: #409ad5 !important; box-shadow: none; } .btn-link:visited { color: #9b59b6; } .wy-btn-group .btn, .wy-control .btn { vertical-align: middle; } .wy-btn-group { margin-bottom: 24px; *zoom: 1; } .wy-btn-group:before, .wy-btn-group:after { display: table; content: ""; } .wy-btn-group:after { clear: both; } .wy-dropdown { position: relative; display: inline-block; } .wy-dropdown-active .wy-dropdown-menu { display: block; } .wy-dropdown-menu { position: absolute; left: 0; display: none; float: left; top: 100%; min-width: 100%; background: #fcfcfc; z-index: 100; border: solid 1px #cfd7dd; box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.1); padding: 12px; } .wy-dropdown-menu > dd > a { display: block; clear: both; color: #404040; white-space: nowrap; font-size: 90%; padding: 0 12px; cursor: pointer; } .wy-dropdown-menu > dd > a:hover { background: #2980b9; color: #fff; } .wy-dropdown-menu > dd.divider { border-top: solid 1px #cfd7dd; margin: 6px 0; } .wy-dropdown-menu > dd.search { padding-bottom: 12px; } .wy-dropdown-menu > dd.search input[type="search"] { width: 100%; } .wy-dropdown-menu > dd.call-to-action { background: #e3e3e3; text-transform: uppercase; font-weight: 500; font-size: 80%; } .wy-dropdown-menu > dd.call-to-action:hover { background: #e3e3e3; } .wy-dropdown-menu > dd.call-to-action .btn { color: #fff; } .wy-dropdown.wy-dropdown-up .wy-dropdown-menu { bottom: 100%; top: auto; left: auto; right: 0; } .wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu { background: #fcfcfc; margin-top: 2px; } .wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a { padding: 6px 12px; } .wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover { background: #2980b9; color: #fff; } .wy-dropdown.wy-dropdown-left .wy-dropdown-menu { right: 0; left: auto; text-align: right; } .wy-dropdown-arrow:before { content: " "; border-bottom: 5px solid #f5f5f5; border-left: 5px solid transparent; border-right: 5px solid transparent; position: absolute; display: block; top: -4px; left: 50%; margin-left: -3px; } .wy-dropdown-arrow.wy-dropdown-arrow-left:before { left: 11px; } .wy-form-stacked select { display: block; } .wy-form-aligned input, .wy-form-aligned textarea, .wy-form-aligned select, .wy-form-aligned .wy-help-inline, .wy-form-aligned label { display: inline-block; *display: inline; *zoom: 1; vertical-align: middle; } .wy-form-aligned .wy-control-group > label { display: inline-block; vertical-align: middle; width: 10em; margin: 6px 12px 0 0; float: left; } .wy-form-aligned .wy-control { float: left; } .wy-form-aligned .wy-control label { display: block; } .wy-form-aligned .wy-control select { margin-top: 6px; } fieldset { border: 0; margin: 0; padding: 0; } legend { display: block; width: 100%; border: 0; padding: 0; white-space: normal; margin-bottom: 24px; font-size: 150%; *margin-left: -7px; } label { display: block; margin: 0 0 0.3125em 0; color: #333; font-size: 90%; } input, select, textarea { font-size: 100%; margin: 0; vertical-align: baseline; *vertical-align: middle; } .wy-control-group { margin-bottom: 24px; *zoom: 1; max-width: 68em; margin-left: auto; margin-right: auto; *zoom: 1; } .wy-control-group:before, .wy-control-group:after { display: table; content: ""; } .wy-control-group:after { clear: both; } .wy-control-group:before, .wy-control-group:after { display: table; content: ""; } .wy-control-group:after { clear: both; } .wy-control-group.wy-control-group-required > label:after { content: " *"; color: #e74c3c; } .wy-control-group .wy-form-full, .wy-control-group .wy-form-halves, .wy-control-group .wy-form-thirds { padding-bottom: 12px; } .wy-control-group .wy-form-full select, .wy-control-group .wy-form-halves select, .wy-control-group .wy-form-thirds select { width: 100%; } .wy-control-group .wy-form-full input[type="text"], .wy-control-group .wy-form-full input[type="password"], .wy-control-group .wy-form-full input[type="email"], .wy-control-group .wy-form-full input[type="url"], .wy-control-group .wy-form-full input[type="date"], .wy-control-group .wy-form-full input[type="month"], .wy-control-group .wy-form-full input[type="time"], .wy-control-group .wy-form-full input[type="datetime"], .wy-control-group .wy-form-full input[type="datetime-local"], .wy-control-group .wy-form-full input[type="week"], .wy-control-group .wy-form-full input[type="number"], .wy-control-group .wy-form-full input[type="search"], .wy-control-group .wy-form-full input[type="tel"], .wy-control-group .wy-form-full input[type="color"], .wy-control-group .wy-form-halves input[type="text"], .wy-control-group .wy-form-halves input[type="password"], .wy-control-group .wy-form-halves input[type="email"], .wy-control-group .wy-form-halves input[type="url"], .wy-control-group .wy-form-halves input[type="date"], .wy-control-group .wy-form-halves input[type="month"], .wy-control-group .wy-form-halves input[type="time"], .wy-control-group .wy-form-halves input[type="datetime"], .wy-control-group .wy-form-halves input[type="datetime-local"], .wy-control-group .wy-form-halves input[type="week"], .wy-control-group .wy-form-halves input[type="number"], .wy-control-group .wy-form-halves input[type="search"], .wy-control-group .wy-form-halves input[type="tel"], .wy-control-group .wy-form-halves input[type="color"], .wy-control-group .wy-form-thirds input[type="text"], .wy-control-group .wy-form-thirds input[type="password"], .wy-control-group .wy-form-thirds input[type="email"], .wy-control-group .wy-form-thirds input[type="url"], .wy-control-group .wy-form-thirds input[type="date"], .wy-control-group .wy-form-thirds input[type="month"], .wy-control-group .wy-form-thirds input[type="time"], .wy-control-group .wy-form-thirds input[type="datetime"], .wy-control-group .wy-form-thirds input[type="datetime-local"], .wy-control-group .wy-form-thirds input[type="week"], .wy-control-group .wy-form-thirds input[type="number"], .wy-control-group .wy-form-thirds input[type="search"], .wy-control-group .wy-form-thirds input[type="tel"], .wy-control-group .wy-form-thirds input[type="color"] { width: 100%; } .wy-control-group .wy-form-full { float: left; display: block; margin-right: 2.3576515979%; width: 100%; margin-right: 0; } .wy-control-group .wy-form-full:last-child { margin-right: 0; } .wy-control-group .wy-form-halves { float: left; display: block; margin-right: 2.3576515979%; width: 48.821174201%; } .wy-control-group .wy-form-halves:last-child { margin-right: 0; } .wy-control-group .wy-form-halves:nth-of-type(2n) { margin-right: 0; } .wy-control-group .wy-form-halves:nth-of-type(2n + 1) { clear: left; } .wy-control-group .wy-form-thirds { float: left; display: block; margin-right: 2.3576515979%; width: 31.7615656014%; } .wy-control-group .wy-form-thirds:last-child { margin-right: 0; } .wy-control-group .wy-form-thirds:nth-of-type(3n) { margin-right: 0; } .wy-control-group .wy-form-thirds:nth-of-type(3n + 1) { clear: left; } .wy-control-group.wy-control-group-no-input .wy-control { margin: 6px 0 0 0; font-size: 90%; } .wy-control-no-input { display: inline-block; margin: 6px 0 0 0; font-size: 90%; } .wy-control-group.fluid-input input[type="text"], .wy-control-group.fluid-input input[type="password"], .wy-control-group.fluid-input input[type="email"], .wy-control-group.fluid-input input[type="url"], .wy-control-group.fluid-input input[type="date"], .wy-control-group.fluid-input input[type="month"], .wy-control-group.fluid-input input[type="time"], .wy-control-group.fluid-input input[type="datetime"], .wy-control-group.fluid-input input[type="datetime-local"], .wy-control-group.fluid-input input[type="week"], .wy-control-group.fluid-input input[type="number"], .wy-control-group.fluid-input input[type="search"], .wy-control-group.fluid-input input[type="tel"], .wy-control-group.fluid-input input[type="color"] { width: 100%; } .wy-form-message-inline { display: inline-block; padding-left: 0.3em; color: #666; vertical-align: middle; font-size: 90%; } .wy-form-message { display: block; color: #999; font-size: 70%; margin-top: 0.3125em; font-style: italic; } .wy-form-message p { font-size: inherit; font-style: italic; margin-bottom: 6px; } .wy-form-message p:last-child { margin-bottom: 0; } input { line-height: normal; } input[type="button"], input[type="reset"], input[type="submit"] { -webkit-appearance: button; cursor: pointer; font-family: "Roboto-Regular", sans-serif; *overflow: visible; } input[type="text"], input[type="password"], input[type="email"], input[type="url"], input[type="date"], input[type="month"], input[type="time"], input[type="datetime"], input[type="datetime-local"], input[type="week"], input[type="number"], input[type="search"], input[type="tel"], input[type="color"] { -webkit-appearance: none; padding: 6px; display: inline-block; border: 1px solid #ccc; font-size: 80%; font-family: "Roboto-Regular", sans-serif; box-shadow: inset 0 1px 3px #ddd; border-radius: 0; -webkit-transition: border 0.3s linear; -moz-transition: border 0.3s linear; transition: border 0.3s linear; } input[type="datetime-local"] { padding: 0.34375em 0.625em; } input[disabled] { cursor: default; } input[type="checkbox"], input[type="radio"] { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding: 0; margin-right: 0.3125em; *height: 13px; *width: 13px; } input[type="search"] { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } input[type="text"]:focus, input[type="password"]:focus, input[type="email"]:focus, input[type="url"]:focus, input[type="date"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="week"]:focus, input[type="number"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="color"]:focus { outline: 0; outline: thin dotted \9; border-color: #333; } input.no-focus:focus { border-color: #ccc !important; } input[type="file"]:focus, input[type="radio"]:focus, input[type="checkbox"]:focus { outline: thin dotted #333; outline: 1px auto #129fea; } input[type="text"][disabled], input[type="password"][disabled], input[type="email"][disabled], input[type="url"][disabled], input[type="date"][disabled], input[type="month"][disabled], input[type="time"][disabled], input[type="datetime"][disabled], input[type="datetime-local"][disabled], input[type="week"][disabled], input[type="number"][disabled], input[type="search"][disabled], input[type="tel"][disabled], input[type="color"][disabled] { cursor: not-allowed; background-color: #fafafa; } input:focus:invalid, textarea:focus:invalid, select:focus:invalid { color: #e74c3c; border: 1px solid #e74c3c; } input:focus:invalid:focus, textarea:focus:invalid:focus, select:focus:invalid:focus { border-color: #e74c3c; } input[type="file"]:focus:invalid:focus, input[type="radio"]:focus:invalid:focus, input[type="checkbox"]:focus:invalid:focus { outline-color: #e74c3c; } input.wy-input-large { padding: 12px; font-size: 100%; } textarea { overflow: auto; vertical-align: top; width: 100%; font-family: "Roboto-Regular", sans-serif; } select, textarea { padding: 0.5em 0.625em; display: inline-block; border: 1px solid #ccc; font-size: 80%; box-shadow: inset 0 1px 3px #ddd; -webkit-transition: border 0.3s linear; -moz-transition: border 0.3s linear; transition: border 0.3s linear; } select { border: 1px solid #ccc; background-color: #fff; } select[multiple] { height: auto; } select:focus, textarea:focus { outline: 0; } select[disabled], textarea[disabled], input[readonly], select[readonly], textarea[readonly] { cursor: not-allowed; background-color: #fafafa; } input[type="radio"][disabled], input[type="checkbox"][disabled] { cursor: not-allowed; } .wy-checkbox, .wy-radio { margin: 6px 0; color: #404040; display: block; } .wy-checkbox input, .wy-radio input { vertical-align: baseline; } .wy-form-message-inline { display: inline-block; *display: inline; *zoom: 1; vertical-align: middle; } .wy-input-prefix, .wy-input-suffix { white-space: nowrap; padding: 6px; } .wy-input-prefix .wy-input-context, .wy-input-suffix .wy-input-context { line-height: 27px; padding: 0 8px; display: inline-block; font-size: 80%; background: #fafafa; border: solid 1px #ccc; color: #999; } .wy-input-suffix .wy-input-context { border-left: 0; } .wy-input-prefix .wy-input-context { border-right: 0; } .wy-switch { position: relative; display: block; height: 24px; margin-top: 12px; cursor: pointer; } .wy-switch:before { position: absolute; content: ""; display: block; left: 0; top: 0; width: 36px; height: 12px; border-radius: 4px; background: #ccc; -webkit-transition: all 0.2s ease-in-out; -moz-transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out; } .wy-switch:after { position: absolute; content: ""; display: block; width: 18px; height: 18px; border-radius: 4px; background: #999; left: -3px; top: -3px; -webkit-transition: all 0.2s ease-in-out; -moz-transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out; } .wy-switch span { position: absolute; left: 48px; display: block; font-size: 12px; color: #ccc; line-height: 1; } .wy-switch.active:before { background: #1e8449; } .wy-switch.active:after { left: 24px; background: #27ae60; } .wy-switch.disabled { cursor: not-allowed; opacity: 0.8; } .wy-control-group.wy-control-group-error .wy-form-message, .wy-control-group.wy-control-group-error > label { color: #e74c3c; } .wy-control-group.wy-control-group-error input[type="text"], .wy-control-group.wy-control-group-error input[type="password"], .wy-control-group.wy-control-group-error input[type="email"], .wy-control-group.wy-control-group-error input[type="url"], .wy-control-group.wy-control-group-error input[type="date"], .wy-control-group.wy-control-group-error input[type="month"], .wy-control-group.wy-control-group-error input[type="time"], .wy-control-group.wy-control-group-error input[type="datetime"], .wy-control-group.wy-control-group-error input[type="datetime-local"], .wy-control-group.wy-control-group-error input[type="week"], .wy-control-group.wy-control-group-error input[type="number"], .wy-control-group.wy-control-group-error input[type="search"], .wy-control-group.wy-control-group-error input[type="tel"], .wy-control-group.wy-control-group-error input[type="color"] { border: solid 1px #e74c3c; } .wy-control-group.wy-control-group-error textarea { border: solid 1px #e74c3c; } .wy-inline-validate { white-space: nowrap; } .wy-inline-validate .wy-input-context { padding: 0.5em 0.625em; display: inline-block; font-size: 80%; } .wy-inline-validate.wy-inline-validate-success .wy-input-context { color: #27ae60; } .wy-inline-validate.wy-inline-validate-danger .wy-input-context { color: #e74c3c; } .wy-inline-validate.wy-inline-validate-warning .wy-input-context { color: #e67e22; } .wy-inline-validate.wy-inline-validate-info .wy-input-context { color: #2980b9; } .rotate-90 { -webkit-transform: rotate(90deg); -moz-transform: rotate(90deg); -ms-transform: rotate(90deg); -o-transform: rotate(90deg); transform: rotate(90deg); } .rotate-180 { -webkit-transform: rotate(180deg); -moz-transform: rotate(180deg); -ms-transform: rotate(180deg); -o-transform: rotate(180deg); transform: rotate(180deg); } .rotate-270 { -webkit-transform: rotate(270deg); -moz-transform: rotate(270deg); -ms-transform: rotate(270deg); -o-transform: rotate(270deg); transform: rotate(270deg); } .mirror { -webkit-transform: scaleX(-1); -moz-transform: scaleX(-1); -ms-transform: scaleX(-1); -o-transform: scaleX(-1); transform: scaleX(-1); } .mirror.rotate-90 { -webkit-transform: scaleX(-1) rotate(90deg); -moz-transform: scaleX(-1) rotate(90deg); -ms-transform: scaleX(-1) rotate(90deg); -o-transform: scaleX(-1) rotate(90deg); transform: scaleX(-1) rotate(90deg); } .mirror.rotate-180 { -webkit-transform: scaleX(-1) rotate(180deg); -moz-transform: scaleX(-1) rotate(180deg); -ms-transform: scaleX(-1) rotate(180deg); -o-transform: scaleX(-1) rotate(180deg); transform: scaleX(-1) rotate(180deg); } .mirror.rotate-270 { -webkit-transform: scaleX(-1) rotate(270deg); -moz-transform: scaleX(-1) rotate(270deg); -ms-transform: scaleX(-1) rotate(270deg); -o-transform: scaleX(-1) rotate(270deg); transform: scaleX(-1) rotate(270deg); } @media only screen and (max-width: 480px) { .wy-form button[type="submit"] { margin: 0.7em 0 0; } .wy-form input[type="text"], .wy-form input[type="password"], .wy-form input[type="email"], .wy-form input[type="url"], .wy-form input[type="date"], .wy-form input[type="month"], .wy-form input[type="time"], .wy-form input[type="datetime"], .wy-form input[type="datetime-local"], .wy-form input[type="week"], .wy-form input[type="number"], .wy-form input[type="search"], .wy-form input[type="tel"], .wy-form input[type="color"] { margin-bottom: 0.3em; display: block; } .wy-form label { margin-bottom: 0.3em; display: block; } .wy-form input[type="password"], .wy-form input[type="email"], .wy-form input[type="url"], .wy-form input[type="date"], .wy-form input[type="month"], .wy-form input[type="time"], .wy-form input[type="datetime"], .wy-form input[type="datetime-local"], .wy-form input[type="week"], .wy-form input[type="number"], .wy-form input[type="search"], .wy-form input[type="tel"], .wy-form input[type="color"] { margin-bottom: 0; } .wy-form-aligned .wy-control-group label { margin-bottom: 0.3em; text-align: left; display: block; width: 100%; } .wy-form-aligned .wy-control { margin: 1.5em 0 0 0; } .wy-form .wy-help-inline, .wy-form-message-inline, .wy-form-message { display: block; font-size: 80%; padding: 6px 0; } } @media screen and (max-width: 768px) { .tablet-hide { display: none; } } @media screen and (max-width: 480px) { .mobile-hide { display: none; } } .float-left { float: left; } .float-right { float: right; } .full-width { width: 100%; } .wy-table, .rst-content table.docutils, .rst-content table.field-list { border-collapse: collapse; border-spacing: 0; empty-cells: show; margin-bottom: 24px; } .wy-table caption, .rst-content table.docutils caption, .rst-content table.field-list caption { color: #000; font: italic 85%/1 arial, sans-serif; padding: 1em 0; text-align: center; } .wy-table td, .rst-content table.docutils td, .rst-content table.field-list td, .wy-table th, .rst-content table.docutils th, .rst-content table.field-list th { font-size: 14px; margin: 0; overflow: visible; /* padding: 8px 16px; */ } .wy-table-responsive .wy-table td:first-child, .rst-content table.docutils td:first-child, .rst-content table.field-list td:first-child, .wy-table th:first-child, .rst-content table.docutils th:first-child, .rst-content table.field-list th:first-child { border: none; } .wy-table thead, .rst-content table.docutils thead, .rst-content table.field-list thead { color: #000; text-align: left; vertical-align: bottom; white-space: nowrap; } .wy-table thead th, .rst-content table.docutils thead th, .rst-content table.field-list thead th { font-weight: bold; border-bottom: solid 2px #e1e4e5; } .wy-table td, .rst-content table.docutils td, .rst-content table.field-list td { background-color: transparent; vertical-align: middle; } .wy-table td p, .rst-content table.docutils td p, .rst-content table.field-list td p { line-height: 18px; } .wy-table td p:last-child, .rst-content table.docutils td p:last-child, .rst-content table.field-list td p:last-child { margin-bottom: 0; } .wy-table .wy-table-cell-min, .rst-content table.docutils .wy-table-cell-min, .rst-content table.field-list .wy-table-cell-min { width: 1%; padding-right: 0; } .wy-table .wy-table-cell-min input[type="checkbox"], .rst-content table.docutils .wy-table-cell-min input[type="checkbox"], .rst-content table.field-list .wy-table-cell-min input[type="checkbox"], .wy-table .wy-table-cell-min input[type="checkbox"], .rst-content table.docutils .wy-table-cell-min input[type="checkbox"], .rst-content table.field-list .wy-table-cell-min input[type="checkbox"] { margin: 0; } .wy-table-secondary { color: gray; font-size: 90%; } .wy-table-tertiary { color: gray; font-size: 80%; } .wy-table-odd td, .wy-table-striped tr:nth-child(2n-1) td, .rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td { border: none; } .wy-table-backed { background-color: #f3f6f6; } tr.row-odd { background: #fafafa; /* border: 1px solid #e6e6e6; */ border-radius: 8px; } .wy-table-bordered-all, .rst-content table.docutils { border: none; } .wy-table-bordered-all td, .rst-content table.docutils td { padding: 24px 16px; border: none; } .wy-table-bordered-all tbody > tr:last-child td, .rst-content table.docutils tbody > tr:last-child td { border-bottom-width: 0; } .wy-table-bordered { border: 1px solid #e1e4e5; } .wy-table-bordered-rows td { border-bottom: 1px solid #e1e4e5; } .wy-table-bordered-rows tbody > tr:last-child td { border-bottom-width: 0; } .wy-table-horizontal tbody > tr:last-child td { border-bottom-width: 0; } .wy-table-horizontal td, .wy-table-horizontal th { border-width: 0 0 1px 0; border-bottom: 1px solid #e1e4e5; } .wy-table-horizontal tbody > tr:last-child td { border-bottom-width: 0; } .wy-table-responsive { margin-bottom: 24px; max-width: 100%; overflow: auto; } .wy-table-responsive table { margin-bottom: 0 !important; } .wy-table-responsive table td, .wy-table-responsive table th { white-space: nowrap; } a { color: #0f6dee; text-decoration: none; cursor: pointer; } a:hover { color: #0f6dee; } html { height: 100%; overflow-x: hidden; } body { font-family: "Roboto-Regular", sans-serif; font-weight: normal; color: #404040; min-height: 100%; background: #edf0f2; } .wy-text-left { text-align: left; } .wy-text-center { text-align: center; } .wy-text-right { text-align: right; } .wy-text-large { font-size: 120%; } .wy-text-normal { font-size: 100%; } .wy-text-small, small { font-size: 80%; } .wy-text-strike { text-decoration: line-through; } .wy-text-warning { color: #e67e22 !important; } a.wy-text-warning:hover { color: #eb9950 !important; } .wy-text-info { color: #2980b9 !important; } a.wy-text-info:hover { color: #409ad5 !important; } .wy-text-success { color: #27ae60 !important; } a.wy-text-success:hover { color: #36d278 !important; } .wy-text-danger { color: #e74c3c !important; } a.wy-text-danger:hover { color: #ed7669 !important; } .wy-text-neutral { color: #404040 !important; } a.wy-text-neutral:hover { color: #595959 !important; } h1, h2, .rst-content .toctree-wrapper p.caption, h3, h4, h5, h6, legend { margin-top: 0; font-weight: normal; font-family: "Roboto-Regular", sans-serif; } p { line-height: 20px; margin: 0; font-size: 14px; margin-bottom: 24px; } h1 { font-family: "Roboto-Medium", sans-serif; font-size: 34px; line-height: 54px; margin-bottom: 16px; } h2 { font-family: "Roboto-Medium", sans-serif; font-size: 24px; line-height: 36px; } .rst-content .toctree-wrapper p.caption { font-family: Roboto-Medium; font-weight: 400; font-size: 20px; color: rgba(0, 0, 0, 0.87); line-height: 32px; margin-bottom: 8px; } h3 { font-size: 125%; } h4 { font-family: "Roboto Mono", monospace; font-size: 16px; line-height: 24px; color: rgba(0, 0, 0, 0.8); font-weight: 700; } h5 { font-size: 110%; } h6 { font-size: 100%; } hr { margin-left: 0; margin-right: 0; margin-top: 3rem; margin-bottom: 3rem; padding-bottom: 0; padding-left: 0; padding-right: 0; padding-top: 0; background: hsla(0, 0%, 0%, 0.2); border: none; height: 1px; } code, .rst-content tt, .rst-content code { white-space: nowrap; max-width: 100%; background: #fafafa; border: 1px solid #e6e6e6; border-radius: 8px; /* font-size: 75%; */ padding: 0 5px; font-family: "Roboto Mono", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; color: #e74c3c; overflow-x: auto; } code.code-large, .rst-content tt.code-large { font-size: 90%; } .wy-plain-list-disc, .rst-content .section ul, .rst-content .toctree-wrapper ul, article ul { list-style: disc; font-size: 14px; line-height: 24px; margin-bottom: 24px; } .wy-plain-list-disc li, .rst-content .section ul li, .rst-content .toctree-wrapper ul li, article ul li { /* list-style: disc; margin-left: 24px; */ } .wy-plain-list-disc li p:last-child, .rst-content .section ul li p:last-child, .rst-content .toctree-wrapper ul li p:last-child, article ul li p:last-child { margin-bottom: 0; } .wy-plain-list-disc li ul, .rst-content .section ul li ul, .rst-content .toctree-wrapper ul li ul, article ul li ul { margin-bottom: 0; margin-left: 24px; } .wy-plain-list-disc li li, .rst-content .section ul li li, .rst-content .toctree-wrapper ul li li, article ul li li { list-style: none; } .wy-plain-list-disc li li li, .rst-content .section ul li li li, .rst-content .toctree-wrapper ul li li li, article ul li li li { list-style: none; } .wy-plain-list-disc li ol li, .rst-content .section ul li ol li, .rst-content .toctree-wrapper ul li ol li, article ul li ol li { list-style: decimal; } .wy-plain-list-decimal, .rst-content .section ol, .rst-content ol.arabic, article ol { list-style: decimal; line-height: 24px; margin-bottom: 24px; } .wy-plain-list-decimal li, .rst-content .section ol li, .rst-content ol.arabic li, article ol li { list-style: decimal; margin-left: 24px; } .wy-plain-list-decimal li p:last-child, .rst-content .section ol li p:last-child, .rst-content ol.arabic li p:last-child, article ol li p:last-child { margin-bottom: 0; } .wy-plain-list-decimal li ul, .rst-content .section ol li ul, .rst-content ol.arabic li ul, article ol li ul { margin-bottom: 0; } .wy-plain-list-decimal li ul li, .rst-content .section ol li ul li, .rst-content ol.arabic li ul li, article ol li ul li { list-style: disc; } .wy-breadcrumbs { *zoom: 1; font-size: 12px; text-transform: uppercase; color: rgba(0, 0, 0, 0.54); line-height: 16px; margin-bottom: 4px; font-weight: 700; } .wy-breadcrumbs:before, .wy-breadcrumbs:after { display: table; content: ""; } .wy-breadcrumbs:after { clear: both; } .wy-breadcrumbs li { display: inline-block; } .wy-breadcrumbs li.wy-breadcrumbs-aside { float: right; } .wy-breadcrumbs li a { display: inline-block; color: rgba(0, 0, 0, 0.54); } .wy-breadcrumbs li a:hover { display: inline-block; color: rgba(15, 109, 238, 0.87); text-decoration: underline; } .wy-breadcrumbs li a:first-child { padding-left: 0; } .wy-breadcrumbs li code, .wy-breadcrumbs li .rst-content tt, .rst-content .wy-breadcrumbs li tt { /* padding: 5px; */ border: none; background: none; } .wy-breadcrumbs li code.literal, .wy-breadcrumbs li .rst-content tt.literal, .rst-content .wy-breadcrumbs li tt.literal { color: #404040; } .wy-breadcrumbs-extra { margin-bottom: 0; color: #b3b3b3; font-size: 80%; display: inline-block; } @media screen and (max-width: 480px) { .wy-breadcrumbs-extra { display: none; } .wy-breadcrumbs li.wy-breadcrumbs-aside { display: none; } } @media print { .wy-breadcrumbs li.wy-breadcrumbs-aside { display: none; } } .wy-affix { position: fixed; top: 1.618em; } .wy-menu a:hover { text-decoration: none; } .wy-menu-horiz { *zoom: 1; } .wy-menu-horiz:before, .wy-menu-horiz:after { display: table; content: ""; } .wy-menu-horiz:after { clear: both; } .wy-menu-horiz ul, .wy-menu-horiz li { display: inline-block; } .wy-menu-horiz li:hover { background: rgba(255, 255, 255, 0.1); } .wy-menu-horiz li.divide-left { border-left: solid 1px #404040; } .wy-menu-horiz li.divide-right { border-right: solid 1px #404040; } .wy-menu-horiz a { height: 32px; display: inline-block; line-height: 32px; padding: 0 16px; } .wy-menu-vertical { width: 100%; } .wy-menu-vertical header, .wy-menu-vertical p.caption { font-family: "Roboto-Regular", sans-serif; font-size: 12px; line-height: 16px; color: rgba(0, 0, 0, 0.54); text-transform: uppercase; margin-bottom: 4px; color: rgba(0, 0, 0, 0.54); height: 32px; display: inline-block; line-height: 32px; padding: 0 32px; margin: 48px 0 0 0; white-space: nowrap; font-weight: bold; } .wy-menu-vertical ul { margin-bottom: 48px; } .wy-menu-vertical li.divide-top { border-top: solid 1px #404040; } .wy-menu-vertical li.divide-bottom { border-bottom: solid 1px #404040; } .wy-menu-vertical li.current { /* background: #e3e3e3; */ } .wy-menu-vertical li.current a { /* color: gray; */ /* border-right: solid 1px #c9c9c9; */ padding: 0.4045em 2.427em; padding-left: 48px; } .wy-menu-vertical li.current a:hover { /* background: #d6d6d6; */ } .wy-menu-vertical li code, .wy-menu-vertical li .rst-content tt, .rst-content .wy-menu-vertical li tt { border: none; background: inherit; color: inherit; padding-left: 0; padding-right: 0; } .wy-menu-vertical li span.toctree-expand { display: block; float: left; margin-left: -1.2em; font-size: 0.8em; line-height: 1.6em; color: #4d4d4d; } .wy-menu-vertical li.on a, .wy-menu-vertical li.current > a { padding: 0.4045em 32px; font-weight: bold; position: relative; /* background: #fcfcfc; */ border: none; padding-left: 1.618em -4px; } .wy-menu-vertical li.on a:hover, .wy-menu-vertical li.current > a:hover { /* background: #fcfcfc; */ } .wy-menu-vertical li.on a:hover span.toctree-expand, .wy-menu-vertical li.current > a:hover span.toctree-expand { /* color: gray; */ } .wy-menu-vertical li.on a span.toctree-expand, .wy-menu-vertical li.current > a span.toctree-expand { display: block; font-size: 0.8em; line-height: 1.6em; /* color: #333; */ } .wy-menu-vertical li.toctree-l1.current > a { /* border-bottom: solid 1px #c9c9c9; */ /* border-top: solid 1px #c9c9c9; */ } .wy-menu-vertical li.toctree-l2 a, .wy-menu-vertical li.toctree-l3 a, .wy-menu-vertical li.toctree-l4 a { color: rgba(15, 109, 238, 0.87); } .wy-menu-vertical li.toctree-l1.current li.toctree-l2 > ul, .wy-menu-vertical li.toctree-l2.current li.toctree-l3 > ul { display: none; } .wy-menu-vertical li.toctree-l1.current li.toctree-l2.current > ul, .wy-menu-vertical li.toctree-l2.current li.toctree-l3.current > ul { display: block; } .wy-menu-vertical li.toctree-l2.current > a { /* background: #c9c9c9; */ padding: 0.4045em 2.427em; padding-left: 48px; } .wy-menu-vertical li.toctree-l2.current li.toctree-l3 > a { display: block; /* background: #c9c9c9; */ padding: 0.4045em 4.045em; } .wy-menu-vertical li.toctree-l2 a:hover span.toctree-expand { /* color: gray; */ } .wy-menu-vertical li.toctree-l2 span.toctree-expand { /* color: #a3a3a3; */ } .wy-menu-vertical li.toctree-l3 { font-size: 0.9em; } .wy-menu-vertical li.toctree-l3.current > a { /* background: #bdbdbd; */ padding: 0.4045em 32px; } .wy-menu-vertical li.toctree-l3.current li.toctree-l4 > a { display: block; background: #bdbdbd; padding: 0.4045em 5.663em; } .wy-menu-vertical li.toctree-l3 a:hover span.toctree-expand { color: gray; } .wy-menu-vertical li.toctree-l3 span.toctree-expand { color: #969696; } .wy-menu-vertical li.toctree-l4 { font-size: 0.9em; } .wy-menu-vertical li.current ul { display: block; } .wy-menu-vertical li ul { margin-bottom: 0; display: none; } .wy-menu-vertical li ul li a { margin-bottom: 0; /* color: #d9d9d9; */ font-weight: normal; } .wy-menu-vertical a { display: inline-block; line-height: 18px; padding: 0.4045em 32px; display: block; position: relative; font-size: 14px; color: rgba(15, 109, 238, 0.87); } .wy-menu-vertical a:hover { /* background-color: #4e4a4a; */ cursor: pointer; } .wy-menu-vertical a:hover span.toctree-expand { /* color: #d9d9d9; */ } .wy-menu-vertical a:active { /* background-color: #2980b9; */ cursor: pointer; /* color: #fff; */ } .wy-menu-vertical a:active span.toctree-expand { /* color: #fff; */ } .wy-side-nav-search { display: none; width: 100%; padding: 0.809em; margin-bottom: 0.809em; /* z-index: 200; */ background-color: #2980b9; text-align: center; padding: 0.809em; color: #fcfcfc; margin-bottom: 0.809em; } .wy-side-nav-search input[type="text"] { width: 100%; border-radius: 50px; padding: 6px 12px; border-color: #2472a4; } .wy-side-nav-search img { display: block; margin: auto auto 0.809em auto; height: 45px; width: 45px; background-color: #2980b9; padding: 5px; border-radius: 100%; } .wy-side-nav-search > a, .wy-side-nav-search .wy-dropdown > a { color: #fcfcfc; font-size: 100%; font-weight: bold; display: inline-block; padding: 4px 6px; margin-bottom: 0.809em; } .wy-side-nav-search > a:hover, .wy-side-nav-search .wy-dropdown > a:hover { background: rgba(255, 255, 255, 0.1); } .wy-side-nav-search > a img.logo, .wy-side-nav-search .wy-dropdown > a img.logo { display: block; margin: 0 auto; height: auto; width: auto; border-radius: 0; max-width: 100%; background: transparent; } .wy-side-nav-search > a.icon img.logo, .wy-side-nav-search .wy-dropdown > a.icon img.logo { margin-top: 0.85em; } .wy-side-nav-search > div.version { margin-top: -0.4045em; margin-bottom: 0.809em; font-weight: normal; color: rgba(255, 255, 255, 0.3); } .wy-nav .wy-menu-vertical header { color: #2980b9; } .wy-nav .wy-menu-vertical a { color: #b3b3b3; } .wy-nav .wy-menu-vertical a:hover { background-color: #2980b9; color: #fff; } [data-menu-wrap] { -webkit-transition: all 0.2s ease-in; -moz-transition: all 0.2s ease-in; transition: all 0.2s ease-in; position: absolute; opacity: 1; width: 100%; opacity: 0; } [data-menu-wrap].move-center { left: 0; right: auto; opacity: 1; } [data-menu-wrap].move-left { right: auto; left: -100%; opacity: 0; } [data-menu-wrap].move-right { right: -100%; left: auto; opacity: 0; } .wy-body-for-nav { background: #fff; } .wy-grid-for-nav { display: flex; width: 100%; height: 100%; min-height: 100vh; } .wy-nav-side { /* padding-bottom: 2em; */ width: 320px; /* overflow-x: hidden; overflow-y: hidden; */ min-height: 100%; height: 100vh; color: #9b9b9b; border-right: 0.5px solid lightgrey; background-color: #fafafa; z-index: 200; visibility: visible; /* margin-top: 111px; */ /* transform: translate3D(0, 0, 0); transition: ease all 0.5s; */ position: sticky; top: 0; } .wy-side-scroll { width: 320px; color: #9b9b9b; border-right: 0.5px solid lightgrey; background-color: #fafafa; position: relative; overflow-x: hidden; overflow-y: scroll; height: 100%; } .wy-nav-top { display: none; background: #2980b9; color: #fff; padding: 0.4045em 0.809em; position: relative; line-height: 50px; text-align: center; font-size: 100%; *zoom: 1; } .wy-nav-top:before, .wy-nav-top:after { display: table; content: ""; } .wy-nav-top:after { clear: both; } .wy-nav-top a { color: #fff; font-weight: bold; } .wy-nav-top img { margin-right: 12px; height: 45px; width: 45px; background-color: #2980b9; padding: 5px; border-radius: 100%; } .wy-nav-top i { font-size: 30px; float: left; cursor: pointer; padding-top: inherit; } .wy-nav-content-wrap { width: 100%; min-height: 100%; margin: 0px 32px; } .wy-nav-content { padding: 64px 0px; height: 100%; max-width: 1140px; margin: auto; } .wy-body-mask { position: fixed; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.2); display: none; z-index: 499; } .wy-body-mask.on { display: block; } footer { color: gray; } footer p { margin-bottom: 12px; } footer span.commit code, footer span.commit .rst-content tt, .rst-content footer span.commit tt { padding: 0px; font-family: "Roboto Mono", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; font-size: 1em; background: none; border: none; color: gray; } .rst-footer-buttons { display: flex; } .rst-footer-buttons:before, .rst-footer-buttons:after { width: 100%; } .rst-footer-buttons:before, .rst-footer-buttons:after { display: none; content: ""; } .rst-footer-buttons:after { clear: both; } .rst-breadcrumbs-buttons { margin-top: 12px; *zoom: 1; } .rst-breadcrumbs-buttons:before, .rst-breadcrumbs-buttons:after { display: none; content: ""; } .rst-breadcrumbs-buttons:after { clear: both; } #search-results .search li { margin-bottom: 24px; border-bottom: solid 1px #e1e4e5; padding-bottom: 24px; } #search-results .search li:first-child { border-top: solid 1px #e1e4e5; padding-top: 24px; } #search-results .search li a { font-size: 120%; margin-bottom: 12px; display: inline-block; } #search-results .context { color: gray; font-size: 90%; } .genindextable li > ul { margin-left: 24px; } /* GS Developer Action Button */ #gs-developer-action-button-container { -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -webkit-tap-highlight-color: transparent; user-select: none; /* transform: translate3D(0, 0, 0); transition: ease all 0.5s; */ z-index: 999; justify-content: center; align-items: center; position: fixed; display: flex; right: 32px; bottom: 32px; background: #0f6dee; color: white; width: 64px; height: 64px; border-radius: 50%; visibility: hidden; opacity: 0; box-shadow: 0 10px 20px 0 rgba(48, 48, 50, 0.19), 0 6px 6px 0 rgba(48, 48, 50, 0.23); } #gs-developer-action-button-container:active { transform: scale(0.95); background: #0a5cce; } #gs-nav-action { margin-top: 4px; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -webkit-tap-highlight-color: transparent; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } #gs-nav-action:before { -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -webkit-tap-highlight-color: transparent; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; content: url(../images/menu-open.svg); } #gs-nav-action.active:before { -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -webkit-tap-highlight-color: transparent; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; content: url(../images/menu-close.svg); } @media screen and (max-width: 80rem) { .wy-body-for-nav { background: #fff; } #gs-developer-action-button-container { /* transform: translate3D(0, 0, 0); transition: ease all 0.5s; */ opacity: 1; visibility: visible; } .wy-nav-top { display: block; } .wy-nav-side { /* transform: translate3D(0, 0, 0); transition: ease all 0.5s; */ visibility: hidden; max-width: 0px; height: 100vh; } .wy-nav-side.shift { /* transform: translate3D(0, 0, 0); transition: ease all 0.5s; */ visibility: visible; max-width: 320px; height: 100vh; } .wy-side-scroll { width: 320px; } .wy-side-nav-search { width: auto; } .wy-menu.wy-menu-vertical { width: auto; } .wy-nav-content-wrap { /* transform: translate3D(0, 0, 0); transition: ease all 0.5s; */ /* margin-left: 0; */ } .wy-nav-content-wrap .wy-nav-content { /* padding: 1.618em; */ } .wy-nav-content-wrap.shift { /* transform: translate3D(0, 0, 0); transition: ease all 0.5s; */ /* position: fixed; */ min-width: 100%; /* left: 85%; */ top: 0; height: 100%; overflow: hidden; } } @media screen and (min-width: 1100px) { .wy-nav-content-wrap { background: #fff; } .wy-nav-content { margin: 0 auto; /* background: #fcfcfc; */ } } @media print { .rst-versions, footer, .wy-nav-side { display: none; } .wy-nav-content-wrap { margin-left: 0; } } .rst-versions { position: fixed; bottom: 0; left: 0; width: 300px; color: #fcfcfc; background: #1f1d1d; font-family: "Roboto-Regular", sans-serif; z-index: 400; } .rst-versions a { color: #2980b9; text-decoration: none; } .rst-versions .rst-badge-small { display: none; } .rst-versions .rst-current-version { padding: 12px; background-color: #272525; display: block; text-align: right; font-size: 90%; cursor: pointer; color: #27ae60; *zoom: 1; } .rst-versions .rst-current-version:before, .rst-versions .rst-current-version:after { display: table; content: ""; } .rst-versions .rst-current-version:after { clear: both; } .rst-versions .rst-current-version .fa, .rst-versions .rst-current-version .wy-menu-vertical li span.toctree-expand, .wy-menu-vertical li .rst-versions .rst-current-version span.toctree-expand, .rst-versions .rst-current-version .rst-content .admonition-title, .rst-content .rst-versions .rst-current-version .admonition-title, .rst-versions .rst-current-version .rst-content h1 .headerlink, .rst-content h1 .rst-versions .rst-current-version .headerlink, .rst-versions .rst-current-version .rst-content h2 .headerlink, .rst-content h2 .rst-versions .rst-current-version .headerlink, .rst-versions .rst-current-version .rst-content h3 .headerlink, .rst-content h3 .rst-versions .rst-current-version .headerlink, .rst-versions .rst-current-version .rst-content h4 .headerlink, .rst-content h4 .rst-versions .rst-current-version .headerlink, .rst-versions .rst-current-version .rst-content h5 .headerlink, .rst-content h5 .rst-versions .rst-current-version .headerlink, .rst-versions .rst-current-version .rst-content h6 .headerlink, .rst-content h6 .rst-versions .rst-current-version .headerlink, .rst-versions .rst-current-version .rst-content dl dt .headerlink, .rst-content dl dt .rst-versions .rst-current-version .headerlink, .rst-versions .rst-current-version .rst-content p.caption .headerlink, .rst-content p.caption .rst-versions .rst-current-version .headerlink, .rst-versions .rst-current-version .rst-content table > caption .headerlink, .rst-content table > caption .rst-versions .rst-current-version .headerlink, .rst-versions .rst-current-version .rst-content .code-block-caption .headerlink, .rst-content .code-block-caption .rst-versions .rst-current-version .headerlink, .rst-versions .rst-current-version .rst-content tt.download span:first-child, .rst-content tt.download .rst-versions .rst-current-version span:first-child, .rst-versions .rst-current-version .rst-content code.download span:first-child, .rst-content code.download .rst-versions .rst-current-version span:first-child, .rst-versions .rst-current-version .icon { color: #fcfcfc; } .rst-versions .rst-current-version .fa-book, .rst-versions .rst-current-version .icon-book { float: left; } .rst-versions .rst-current-version .icon-book { float: left; } .rst-versions .rst-current-version.rst-out-of-date { background-color: #e74c3c; color: #fff; } .rst-versions .rst-current-version.rst-active-old-version { background-color: #f1c40f; color: #000; } .rst-versions.shift-up { height: auto; max-height: 100%; overflow-y: scroll; } .rst-versions.shift-up .rst-other-versions { display: block; } .rst-versions .rst-other-versions { font-size: 90%; padding: 12px; color: gray; display: none; } .rst-versions .rst-other-versions hr { display: block; height: 1px; border: 0; margin: 20px 0; padding: 0; border-top: solid 1px #413d3d; } .rst-versions .rst-other-versions dd { display: inline-block; margin: 0; } .rst-versions .rst-other-versions dd a { display: inline-block; padding: 6px; color: #fcfcfc; } .rst-versions.rst-badge { width: auto; bottom: 20px; right: 20px; left: auto; border: none; max-width: 300px; max-height: 90%; } .rst-versions.rst-badge .icon-book { float: none; } .rst-versions.rst-badge .fa-book, .rst-versions.rst-badge .icon-book { float: none; } .rst-versions.rst-badge.shift-up .rst-current-version { text-align: right; } .rst-versions.rst-badge.shift-up .rst-current-version .fa-book, .rst-versions.rst-badge.shift-up .rst-current-version .icon-book { float: left; } .rst-versions.rst-badge.shift-up .rst-current-version .icon-book { float: left; } .rst-versions.rst-badge .rst-current-version { width: auto; height: 30px; line-height: 30px; padding: 0 6px; display: block; text-align: center; } @media screen and (max-width: 768px) { .rst-versions { width: 85%; display: none; } .rst-versions.shift { display: block; } } .rst-content img { max-width: 100%; height: auto; } .rst-content div.figure { margin-bottom: 24px; } .rst-content div.figure p.caption { font-style: italic; } .rst-content div.figure p:last-child.caption { margin-bottom: 0px; } .rst-content div.figure.align-center { text-align: center; } .rst-content .section > img, .rst-content .section > a > img { margin-bottom: 24px; } .rst-content abbr[title] { text-decoration: none; } .rst-content.style-external-links a.reference.external:after { content: ""; color: #b3b3b3; vertical-align: super; font-size: 60%; margin: 0 0.2em; } .rst-content blockquote { margin-left: 24px; line-height: 24px; margin-bottom: 24px; } .rst-content pre.literal-block { white-space: pre; margin: 0; padding: 12px 12px; font-family: "Roboto Mono", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; display: block; overflow: auto; } .rst-content pre.literal-block, .rst-content div[class^="highlight"] { font-family: "Roboto Mono"; display: flex; flex-wrap: wrap; margin: 16px 0; font-size: 90%; line-height: normal; background: #e7f2fa; color: #2980b9; padding: 16px 12px; position: relative; background: #fafafa; border: 1px solid #e6e6e6; border-radius: 8px; } .rst-content pre.literal-block div[class^="highlight"], .rst-content div[class^="highlight"] div[class^="highlight"] { padding: 0px; border: none; margin: 0; } .rst-content div[class^="highlight"] td.code { width: 100%; } .rst-content .linenodiv pre { border-right: solid 1px #e6e9ea; margin: 0; padding: 12px 12px; font-family: "Roboto Mono", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; user-select: none; pointer-events: none; } .rst-content div[class^="highlight"] pre { white-space: pre; margin: 0; /* padding: 12px 12px; */ display: block; overflow: auto; } .rst-content div[class^="highlight"] pre .hll { display: block; margin: 0 -12px; padding: 0 12px; } .rst-content pre.literal-block, .rst-content div[class^="highlight"] pre, .rst-content .linenodiv pre { font-family: "Roboto Mono", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; font-size: 14px; line-height: 1.5; } .rst-content .code-block-caption { font-style: italic; line-height: 1; padding: 1em 0; text-align: center; } @media print { .rst-content .codeblock, .rst-content div[class^="highlight"], .rst-content div[class^="highlight"] pre { white-space: pre-wrap; } } .rst-content .note .last, .rst-content .attention .last, .rst-content .caution .last, .rst-content .danger .last, .rst-content .error .last, .rst-content .hint .last, .rst-content .important .last, .rst-content .tip .last, .rst-content .warning .last, .rst-content .seealso .last, .rst-content .admonition-todo .last, .rst-content .admonition .last { margin-bottom: 0; } .rst-content .admonition-title:before { margin-right: 4px; } .rst-content .admonition table { border-color: rgba(0, 0, 0, 0.1); } .rst-content .admonition table td, .rst-content .admonition table th { background: transparent !important; border-color: rgba(0, 0, 0, 0.1) !important; } .rst-content .section ol.loweralpha, .rst-content .section ol.loweralpha li { list-style: lower-alpha; } .rst-content .section ol.upperalpha, .rst-content .section ol.upperalpha li { list-style: upper-alpha; } .rst-content .section ol p, .rst-content .section ul p { margin-bottom: 12px; } .rst-content .section ol p:last-child, .rst-content .section ul p:last-child { margin-bottom: 24px; } .rst-content .line-block { margin-left: 0px; margin-bottom: 24px; line-height: 24px; } .rst-content .line-block .line-block { margin-left: 24px; margin-bottom: 0px; } .rst-content .topic-title { font-weight: bold; margin-bottom: 12px; } .rst-content .toc-backref { color: #404040; } .rst-content .align-right { float: right; margin: 0px 0px 24px 24px; } .rst-content .align-left { float: left; margin: 0px 24px 24px 0px; } .rst-content .align-center { margin: auto; } .rst-content .align-center:not(table) { display: block; } .rst-content h1 .headerlink, .rst-content h2 .headerlink, .rst-content .toctree-wrapper p.caption .headerlink, .rst-content h3 .headerlink, .rst-content h4 .headerlink, .rst-content h5 .headerlink, .rst-content h6 .headerlink, .rst-content dl dt .headerlink, .rst-content p.caption .headerlink, .rst-content table > caption .headerlink, .rst-content .code-block-caption .headerlink { visibility: hidden; font-size: 14px; } .rst-content h1 .headerlink:after, .rst-content h2 .headerlink:after, .rst-content .toctree-wrapper p.caption .headerlink:after, .rst-content h3 .headerlink:after, .rst-content h4 .headerlink:after, .rst-content h5 .headerlink:after, .rst-content h6 .headerlink:after, .rst-content dl dt .headerlink:after, .rst-content p.caption .headerlink:after, .rst-content table > caption .headerlink:after, .rst-content .code-block-caption .headerlink:after { content: url(../images/gs-link.svg); position: relative; top: 2px; } .rst-content h1:hover .headerlink:after, .rst-content h2:hover .headerlink:after, .rst-content .toctree-wrapper p.caption:hover .headerlink:after, .rst-content h3:hover .headerlink:after, .rst-content h4:hover .headerlink:after, .rst-content h5:hover .headerlink:after, .rst-content h6:hover .headerlink:after, .rst-content dl dt:hover .headerlink:after, .rst-content p.caption:hover .headerlink:after, .rst-content table > caption:hover .headerlink:after, .rst-content .code-block-caption:hover .headerlink:after { visibility: visible; } .rst-content table > caption .headerlink:after { font-size: 12px; } .rst-content .centered { text-align: center; } .rst-content .sidebar { float: right; width: 40%; display: block; margin: 0 0 24px 24px; padding: 24px; background: #f3f6f6; border: solid 1px #e1e4e5; } .rst-content .sidebar p, .rst-content .sidebar ul, .rst-content .sidebar dl { font-size: 90%; } .rst-content .sidebar .last { margin-bottom: 0; } .rst-content .sidebar .sidebar-title { display: block; font-family: "Roboto-Regular", sans-serif; font-weight: bold; background: #e1e4e5; padding: 6px 12px; margin: -24px; margin-bottom: 24px; font-size: 100%; } .rst-content .highlighted { background: #f1c40f; display: inline-block; font-weight: bold; padding: 0 6px; } .rst-content .footnote-reference, .rst-content .citation-reference { vertical-align: baseline; position: relative; top: -0.4em; line-height: 0; font-size: 90%; } .rst-content table.docutils.citation, .rst-content table.docutils.footnote { background: none; border: none; color: gray; } .rst-content table.docutils.citation td, .rst-content table.docutils.citation tr, .rst-content table.docutils.footnote td, .rst-content table.docutils.footnote tr { border: none; background-color: transparent !important; white-space: normal; } .rst-content table.docutils.citation td.label, .rst-content table.docutils.footnote td.label { padding-left: 0; padding-right: 0; vertical-align: top; } .rst-content table.docutils.citation tt, .rst-content table.docutils.citation code, .rst-content table.docutils.footnote tt, .rst-content table.docutils.footnote code { color: #555; } .rst-content .wy-table-responsive.citation, .rst-content .wy-table-responsive.footnote { margin-bottom: 0; } .rst-content .wy-table-responsive.citation + :not(.citation), .rst-content .wy-table-responsive.footnote + :not(.footnote) { margin-top: 24px; } .rst-content .wy-table-responsive.citation:last-child, .rst-content .wy-table-responsive.footnote:last-child { margin-bottom: 24px; } .rst-content table.docutils th { border-color: #e1e4e5; } .rst-content table.docutils td .last, .rst-content table.docutils td .last :last-child { margin-bottom: 0; } .rst-content table.field-list { border: none; } .rst-content table.field-list td { border: none; } .rst-content table.field-list td p { font-size: inherit; line-height: inherit; } .rst-content table.field-list td > strong { display: inline-block; } .rst-content table.field-list .field-name { padding-right: 10px; text-align: left; white-space: nowrap; } .rst-content table.field-list .field-body { text-align: left; } .rst-content tt, .rst-content tt, .rst-content code { color: #000; font-family: "Roboto Mono", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; padding: 2px 5px; } .rst-content tt big, .rst-content tt em, .rst-content tt big, .rst-content code big, .rst-content tt em, .rst-content code em { font-size: 100% !important; line-height: normal; } .rst-content tt.literal, .rst-content tt.literal, .rst-content code.literal { color: #e74c3c; } .rst-content tt.xref, a .rst-content tt, .rst-content tt.xref, .rst-content code.xref, a .rst-content tt, a .rst-content code { font-weight: bold; color: #404040; } .rst-content pre, .rst-content kbd, .rst-content samp { font-family: "Roboto Mono", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; } .rst-content a tt, .rst-content a tt, .rst-content a code { color: #2980b9; } .rst-content dl { margin-bottom: 24px; } .rst-content dl dt { font-weight: normal; margin-bottom: 12px; } .rst-content dl p, .rst-content dl table, .rst-content dl ul, .rst-content dl ol { margin-bottom: 12px !important; } .rst-content dl dd { margin: 0px; line-height: 24px; } .rst-content dl:not(.docutils) { margin-bottom: 56px; } .rst-content dl:not(.docutils) dt { font-family: "Roboto Mono"; display: flex; flex-wrap: wrap; margin: 16px 0; font-size: 14px; background: #e7f2fa; color: #0f6dee; padding: 16px 12px; position: relative; background: #fafafa; border: 1px solid #e6e6e6; border-radius: 8px; } .rst-content dl:not(.docutils) dt:before { color: #6ab0de; } .rst-content dl:not(.docutils) dt .headerlink { color: #404040; font-size: 100% !important; } .rst-content dl:not(.docutils) dl dt { font-family: "Roboto Mono"; display: flex; flex-wrap: wrap; align-items: center; margin: 16px 0; font-size: 14px; line-height: normal; background: #e7f2fa; color: #2980b9; padding: 16px 12px; position: relative; background: #fafafa; border: 1px solid #e6e6e6; border-radius: 8px; } .rst-content dl:not(.docutils) dl dt .headerlink { color: #404040; font-size: 100% !important; } .rst-content dl:not(.docutils) dt:first-child { margin-top: 0; } .rst-content dl:not(.docutils) tt, .rst-content dl:not(.docutils) tt, .rst-content dl:not(.docutils) code { font-weight: bold; } .rst-content dl:not(.docutils) tt.descname, .rst-content dl:not(.docutils) tt.descclassname, .rst-content dl:not(.docutils) tt.descname, .rst-content dl:not(.docutils) code.descname, .rst-content dl:not(.docutils) tt.descclassname, .rst-content dl:not(.docutils) code.descclassname { background-color: transparent; border: none; padding: 0; font-size: 100% !important; } .rst-content dl:not(.docutils) tt.descname, .rst-content dl:not(.docutils) tt.descname, .rst-content dl:not(.docutils) code.descname { font-weight: bold; } .rst-content dl:not(.docutils) .optional { display: inline-block; padding: 0 4px; color: #000; font-weight: bold; } .rst-content dl:not(.docutils) .property { display: inline-block; padding-right: 8px; } .rst-content .viewcode-link, .rst-content .viewcode-back { display: inline-block; color: #27ae60; padding-left: 24px; } .rst-content .viewcode-back { display: block; float: right; } .rst-content p.rubric { font-family: "Roboto Mono", monospace; font-size: 24px; line-height: 36px; margin-bottom: 12px; margin-top: 48px; font-weight: bold; } .rst-content tt.download, .rst-content code.download { background: inherit; padding: inherit; font-weight: normal; font-family: inherit; font-size: inherit; color: inherit; border: inherit; white-space: inherit; } .rst-content tt.download span:first-child, .rst-content code.download span:first-child { -webkit-font-smoothing: subpixel-antialiased; } .rst-content tt.download span:first-child:before, .rst-content code.download span:first-child:before { margin-right: 4px; } .rst-content .guilabel { border: 1px solid #7fbbe3; background: #e7f2fa; font-size: 80%; font-weight: 700; border-radius: 4px; padding: 2.4px 6px; margin: auto 2px; } .rst-content .versionmodified { font-style: italic; } @media screen and (max-width: 480px) { .rst-content .sidebar { width: 100%; } } span[id*="MathJax-Span"] { color: #404040; } .math { text-align: center; } em { font-style: normal; } /* GS Developer Overrides */ td.field-body { padding: 0 !important; } /* GS Developer Global */ #gs-developer-global { background: #0c1521; position: fixed; height: auto; width: 100%; z-index: 1000; } #gs-developer-global p { color: white; font-size: 24px; margin: 0; } /* GS Developer Components */ .container-block-module { display: flex; flex-direction: row; flex-wrap: wrap; margin: -16px; } .link-block-module { display: flex; flex-direction: row; background-color: #fafafa; flex-basis: 240px; cursor: pointer; flex-grow: 1; margin: 16px; } .link-block-module-container { display: flex; color: unset; } .link-block-module-content { display: flex; flex-direction: column; justify-content: center; margin: 24px; width: 100%; } .link-block-module-title-label { margin: 0; margin-bottom: 4px; } .link-block-module-body-label { margin: 0; margin-bottom: 0; color: rgba(0, 0, 0, 0.8); } i { font-family: "Material-Icons"; } ================================================ FILE: docs/_themes/gs/static/js/theme.js ================================================ document.addEventListener("DOMContentLoaded", () => { var trigger = document.getElementById("gs-nav-action"); var navigation = document.getElementById("wy-nav-shift"); trigger.addEventListener("click", () => { trigger.classList.toggle("active"); navigation.classList.toggle("shift"); }); }); ================================================ FILE: docs/_themes/gs/theme.conf ================================================ [theme] inherit = basic stylesheet = css/theme.css pygments_style = default [options] canonical_url = analytics_id = collapse_navigation = True sticky_navigation = True navigation_depth = 4 includehidden = True titles_only = logo_only = display_version = True prev_next_buttons_location = bottom style_external_links = False style_nav_header_background = ================================================ FILE: docs/_themes/gs/versions.html ================================================ {% if READTHEDOCS %} {# Add rst-badge after rst-versions for small badge style. #}
Read the Docs v: {{ current_version }}
{{ _("Versions") }}
{% for slug, url in versions %}
{{ slug }}
{% endfor %}
{{ _("Downloads") }}
{% for type, url in downloads %}
{{ type }}
{% endfor %}
{{ _("On Read the Docs") }}
{{ _("Project Home") }}
{{ _("Builds") }}

{% trans %}Free document hosting provided by Read the Docs.{% endtrans %}
{% endif %} ================================================ FILE: docs/analytics.rst ================================================ Analytics Package ============ DataGrid ----------- .. currentmodule:: gs_quant.analytics.datagrid.datagrid .. autosummary:: :toctree: classes :template: processors.rst DataGrid Processors ------- .. currentmodule:: gs_quant.analytics.processors .. autosummary:: :toctree: classes :template: processors.rst AppendProcessor AdditionProcessor SubtractionProcessor MultiplicationProcessor DivisionProcessor ChangeProcessor CoordinateProcessor CorrelationProcessor EntityProcessor LastProcessor PercentilesProcessor SharpeRatioProcessor VolatilityProcessor Workspaces ----------- .. currentmodule:: gs_quant.analytics.workspaces .. autosummary:: :toctree: classes :template: processors.rst Components Workspaces ================================================ FILE: docs/classes/gs_quant.analytics.datagrid.datagrid.DataGrid.rst ================================================ DataGrid ======== .. currentmodule:: gs_quant.analytics.datagrid.datagrid .. autoclass:: DataGrid .. rubric:: Methods .. automethod:: __init__ .. automethod:: add_filter .. automethod:: add_sort .. automethod:: aggregate_queries .. automethod:: as_dict .. automethod:: create .. automethod:: delete .. automethod:: from_dict .. automethod:: get_id .. automethod:: initialize .. automethod:: open .. automethod:: poll .. automethod:: save .. automethod:: set_filters .. automethod:: set_primary_column_index .. automethod:: set_sorts .. automethod:: to_frame .. rubric:: Properties .. autoattribute:: polling_time ================================================ FILE: docs/classes/gs_quant.analytics.processors.AdditionProcessor.rst ================================================ AdditionProcessor ================= .. currentmodule:: gs_quant.analytics.processors .. autoclass:: AdditionProcessor .. rubric:: Methods .. automethod:: __init__ .. automethod:: as_dict .. automethod:: build_graph .. automethod:: calculate .. automethod:: from_dict .. automethod:: get_default_params .. automethod:: get_plot_expression .. automethod:: post_process .. automethod:: process .. automethod:: update ================================================ FILE: docs/classes/gs_quant.analytics.processors.AppendProcessor.rst ================================================ AppendProcessor =============== .. currentmodule:: gs_quant.analytics.processors .. autoclass:: AppendProcessor .. rubric:: Methods .. automethod:: __init__ .. automethod:: as_dict .. automethod:: build_graph .. automethod:: calculate .. automethod:: from_dict .. automethod:: get_default_params .. automethod:: get_plot_expression .. automethod:: post_process .. automethod:: process .. automethod:: update ================================================ FILE: docs/classes/gs_quant.analytics.processors.ChangeProcessor.rst ================================================ ChangeProcessor =============== .. currentmodule:: gs_quant.analytics.processors .. autoclass:: ChangeProcessor .. rubric:: Methods .. automethod:: __init__ .. automethod:: as_dict .. automethod:: build_graph .. automethod:: calculate .. automethod:: from_dict .. automethod:: get_default_params .. automethod:: get_plot_expression .. automethod:: post_process .. automethod:: process .. automethod:: update ================================================ FILE: docs/classes/gs_quant.analytics.processors.CoordinateProcessor.rst ================================================ CoordinateProcessor =================== .. currentmodule:: gs_quant.analytics.processors .. autoclass:: CoordinateProcessor .. rubric:: Methods .. automethod:: __init__ .. automethod:: as_dict .. automethod:: build_graph .. automethod:: calculate .. automethod:: from_dict .. automethod:: get_default_params .. automethod:: get_plot_expression .. automethod:: post_process .. automethod:: process .. automethod:: update ================================================ FILE: docs/classes/gs_quant.analytics.processors.CorrelationProcessor.rst ================================================ CorrelationProcessor ==================== .. currentmodule:: gs_quant.analytics.processors .. autoclass:: CorrelationProcessor .. rubric:: Methods .. automethod:: __init__ .. automethod:: as_dict .. automethod:: build_graph .. automethod:: calculate .. automethod:: from_dict .. automethod:: get_benchmark_coordinate .. automethod:: get_default_params .. automethod:: get_plot_expression .. automethod:: post_process .. automethod:: process .. automethod:: update ================================================ FILE: docs/classes/gs_quant.analytics.processors.DivisionProcessor.rst ================================================ DivisionProcessor ================= .. currentmodule:: gs_quant.analytics.processors .. autoclass:: DivisionProcessor .. rubric:: Methods .. automethod:: __init__ .. automethod:: as_dict .. automethod:: build_graph .. automethod:: calculate .. automethod:: from_dict .. automethod:: get_default_params .. automethod:: get_plot_expression .. automethod:: post_process .. automethod:: process .. automethod:: update ================================================ FILE: docs/classes/gs_quant.analytics.processors.EntityProcessor.rst ================================================ EntityProcessor =============== .. currentmodule:: gs_quant.analytics.processors .. autoclass:: EntityProcessor .. rubric:: Methods .. automethod:: __init__ .. automethod:: as_dict .. automethod:: build_graph .. automethod:: calculate .. automethod:: from_dict .. automethod:: get_default_params .. automethod:: get_plot_expression .. automethod:: post_process .. automethod:: process .. automethod:: update ================================================ FILE: docs/classes/gs_quant.analytics.processors.LastProcessor.rst ================================================ LastProcessor ============= .. currentmodule:: gs_quant.analytics.processors .. autoclass:: LastProcessor .. rubric:: Methods .. automethod:: __init__ .. automethod:: as_dict .. automethod:: build_graph .. automethod:: calculate .. automethod:: from_dict .. automethod:: get_default_params .. automethod:: get_plot_expression .. automethod:: post_process .. automethod:: process .. automethod:: update ================================================ FILE: docs/classes/gs_quant.analytics.processors.MultiplicationProcessor.rst ================================================ MultiplicationProcessor ======================= .. currentmodule:: gs_quant.analytics.processors .. autoclass:: MultiplicationProcessor .. rubric:: Methods .. automethod:: __init__ .. automethod:: as_dict .. automethod:: build_graph .. automethod:: calculate .. automethod:: from_dict .. automethod:: get_default_params .. automethod:: get_plot_expression .. automethod:: post_process .. automethod:: process .. automethod:: update ================================================ FILE: docs/classes/gs_quant.analytics.processors.PercentilesProcessor.rst ================================================ PercentilesProcessor ==================== .. currentmodule:: gs_quant.analytics.processors .. autoclass:: PercentilesProcessor .. rubric:: Methods .. automethod:: __init__ .. automethod:: as_dict .. automethod:: build_graph .. automethod:: calculate .. automethod:: from_dict .. automethod:: get_default_params .. automethod:: get_plot_expression .. automethod:: post_process .. automethod:: process .. automethod:: update ================================================ FILE: docs/classes/gs_quant.analytics.processors.ReturnsProcessor.rst ================================================ ReturnsProcessor =============== .. currentmodule:: gs_quant.analytics.processors .. autoclass:: ReturnsProcessor .. rubric:: Methods .. automethod:: __init__ .. automethod:: as_dict .. automethod:: build_graph .. automethod:: calculate .. automethod:: from_dict .. automethod:: get_plot_expression .. automethod:: process .. automethod:: update ================================================ FILE: docs/classes/gs_quant.analytics.processors.SharpeRatioProcessor.rst ================================================ SharpeRatioProcessor ==================== .. currentmodule:: gs_quant.analytics.processors .. autoclass:: SharpeRatioProcessor .. rubric:: Methods .. automethod:: __init__ .. automethod:: as_dict .. automethod:: build_graph .. automethod:: calculate .. automethod:: from_dict .. automethod:: get_default_params .. automethod:: get_excess_returns_query .. automethod:: get_plot_expression .. automethod:: post_process .. automethod:: process .. automethod:: update ================================================ FILE: docs/classes/gs_quant.analytics.processors.SubtractionProcessor.rst ================================================ SubtractionProcessor ==================== .. currentmodule:: gs_quant.analytics.processors .. autoclass:: SubtractionProcessor .. rubric:: Methods .. automethod:: __init__ .. automethod:: as_dict .. automethod:: build_graph .. automethod:: calculate .. automethod:: from_dict .. automethod:: get_default_params .. automethod:: get_plot_expression .. automethod:: post_process .. automethod:: process .. automethod:: update ================================================ FILE: docs/classes/gs_quant.analytics.processors.VolatilityProcessor.rst ================================================ VolatilityProcessor =================== .. currentmodule:: gs_quant.analytics.processors .. autoclass:: VolatilityProcessor .. rubric:: Methods .. automethod:: __init__ .. automethod:: as_dict .. automethod:: build_graph .. automethod:: calculate .. automethod:: from_dict .. automethod:: get_default_params .. automethod:: get_plot_expression .. automethod:: post_process .. automethod:: process .. automethod:: update ================================================ FILE: docs/classes/gs_quant.analytics.workspaces.Component.rst ================================================ Component ========= .. currentmodule:: gs_quant.analytics.workspaces .. autoclass:: Component .. rubric:: Methods .. automethod:: __init__ .. automethod:: as_dict .. automethod:: from_dict .. rubric:: Properties .. autoattribute:: container_ids .. autoattribute:: height .. autoattribute:: id_ .. autoattribute:: selections .. autoattribute:: width ================================================ FILE: docs/classes/gs_quant.analytics.workspaces.Workspace.rst ================================================ Workspace ========= .. currentmodule:: gs_quant.analytics.workspaces .. autoclass:: Workspace .. rubric:: Methods .. automethod:: __init__ .. automethod:: as_dict .. automethod:: create .. automethod:: delete .. automethod:: delete_all .. automethod:: from_dict .. automethod:: get_by_alias .. automethod:: get_by_id .. automethod:: open .. automethod:: save .. rubric:: Properties .. autoattribute:: PERSISTED_COMPONENTS .. autoattribute:: alias .. autoattribute:: call_to_action .. autoattribute:: description .. autoattribute:: disclaimer .. autoattribute:: entitlements .. autoattribute:: id .. autoattribute:: maintainers .. autoattribute:: name .. autoattribute:: rows .. autoattribute:: selector_components .. autoattribute:: tabs .. autoattribute:: tags ================================================ FILE: docs/classes/gs_quant.base.Priceable.rst ================================================ gs\_quant.base.Priceable ======================== .. currentmodule:: gs_quant.base .. autoclass:: Priceable .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~Priceable.__init__ ~Priceable.as_dict ~Priceable.calc ~Priceable.clone ~Priceable.default_instance ~Priceable.dollar_price ~Priceable.from_dict ~Priceable.from_instance ~Priceable.from_json ~Priceable.price ~Priceable.properties ~Priceable.properties_init ~Priceable.resolve ~Priceable.schema ~Priceable.to_dict ~Priceable.to_json ================================================ FILE: docs/classes/gs_quant.data.DataContext.rst ================================================ DataContext ========================== .. currentmodule:: gs_quant.data .. autoclass:: DataContext .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~DataContext.__init__ ~DataContext.default_value .. rubric:: Attributes .. autosummary:: ~DataContext.end_date ~DataContext.end_time ~DataContext.is_entered ~DataContext.start_date ~DataContext.start_time ================================================ FILE: docs/classes/gs_quant.data.Dataset.rst ================================================ Dataset ====================== .. currentmodule:: gs_quant.data .. autoclass:: Dataset .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~Dataset.__init__ ~Dataset.get_coverage ~Dataset.get_data ~Dataset.get_data_last ~Dataset.get_data_series .. rubric:: Attributes .. autosummary:: ~Dataset.id ~Dataset.name ~Dataset.provider ================================================ FILE: docs/classes/gs_quant.data.Fields.rst ================================================ Fields ===================== .. currentmodule:: gs_quant.data .. autoclass:: Fields .. automethod:: __init__ .. rubric:: Attributes .. autosummary:: ~Fields.ADJUSTED_ASK_PRICE ~Fields.ADJUSTED_BID_PRICE ~Fields.ADJUSTED_HIGH_PRICE ~Fields.ADJUSTED_LOW_PRICE ~Fields.ADJUSTED_TRADE_PRICE ~Fields.ADJUSTED_VOLUME ~Fields.ASK_PRICE ~Fields.ASSET_ID ~Fields.BID_PRICE ~Fields.CLOSE_PRICE ~Fields.EXPIRATION_DATE ~Fields.HIGH_PRICE ~Fields.IMPLIED_VOLATILITY ~Fields.LOW_PRICE ~Fields.MID_PRICE ~Fields.NAME ~Fields.OPEN_PRICE ~Fields.RELATIVE_STRIKE ~Fields.RIC ~Fields.SPOT_PRICE ~Fields.STRIKE_REFERENCE ~Fields.TENOR ~Fields.TRADE_PRICE ~Fields.UPDATE_TIME ~Fields.VAR_SWAP ~Fields.VOLUME ================================================ FILE: docs/classes/gs_quant.data.core.DataContext.rst ================================================ gs\_quant.data.core.DataContext =============================== .. currentmodule:: gs_quant.data.core .. autoclass:: DataContext .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~DataContext.__init__ ~DataContext.default_value .. rubric:: Attributes .. autosummary:: ~DataContext.end_date ~DataContext.end_time ~DataContext.interval ~DataContext.is_entered ~DataContext.start_date ~DataContext.start_time ================================================ FILE: docs/classes/gs_quant.data.dataset.Dataset.rst ================================================ gs\_quant.data.dataset.Dataset ============================== .. currentmodule:: gs_quant.data.dataset .. autoclass:: Dataset .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~Dataset.__init__ ~Dataset.delete ~Dataset.delete_data ~Dataset.get_coverage ~Dataset.get_coverage_async ~Dataset.get_data ~Dataset.get_data_async ~Dataset.get_data_bulk ~Dataset.get_data_last ~Dataset.get_data_series ~Dataset.get_data_series_async ~Dataset.undelete ~Dataset.upload_data .. rubric:: Attributes .. autosummary:: ~Dataset.id ~Dataset.name ~Dataset.provider ================================================ FILE: docs/classes/gs_quant.data.fields.Fields.rst ================================================ gs\_quant.data.fields.Fields ============================ .. currentmodule:: gs_quant.data.fields .. autoclass:: Fields .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~Fields.__init__ .. rubric:: Attributes .. autosummary:: ~Fields.unit ~Fields.ASK_PRICE ~Fields.BID_PRICE ~Fields.HIGH_PRICE ~Fields.MID_PRICE ~Fields.LOW_PRICE ~Fields.OPEN_PRICE ~Fields.CLOSE_PRICE ~Fields.TRADE_PRICE ~Fields.SPOT_PRICE ~Fields.VOLUME ~Fields.ADJUSTED_ASK_PRICE ~Fields.ADJUSTED_BID_PRICE ~Fields.ADJUSTED_HIGH_PRICE ~Fields.ADJUSTED_LOW_PRICE ~Fields.ADJUSTED_OPEN_PRICE ~Fields.ADJUSTED_CLOSE_PRICE ~Fields.ADJUSTED_TRADE_PRICE ~Fields.ADJUSTED_VOLUME ~Fields.IMPLIED_VOLATILITY ~Fields.VAR_SWAP ~Fields.PRICE ~Fields.NAV_PRICE ~Fields.SPREAD ~Fields.NAV_SPREAD ~Fields.IMPLIED_VOLATILITY_BY_DELTA_STRIKE ~Fields.FORWARD_POINT ~Fields.DIVIDEND_YIELD ~Fields.EARNINGS_PER_SHARE ~Fields.EARNINGS_PER_SHARE_POSITIVE ~Fields.NET_DEBT_TO_EBITDA ~Fields.PRICE_TO_BOOK ~Fields.PRICE_TO_CASH ~Fields.PRICE_TO_EARNINGS ~Fields.PRICE_TO_EARNINGS_POSITIVE ~Fields.PRICE_TO_EARNINGS_POSITIVE_EXCLUSIVE ~Fields.PRICE_TO_SALES ~Fields.RETURN_ON_EQUITY ~Fields.SALES_PER_SHARE ~Fields.CURRENT_CONSTITUENTS_DIVIDEND_YIELD ~Fields.CURRENT_CONSTITUENTS_EARNINGS_PER_SHARE ~Fields.CURRENT_CONSTITUENTS_EARNINGS_PER_SHARE_POSITIVE ~Fields.CURRENT_CONSTITUENTS_NET_DEBT_TO_EBITDA ~Fields.CURRENT_CONSTITUENTS_PRICE_TO_BOOK ~Fields.CURRENT_CONSTITUENTS_PRICE_TO_CASH ~Fields.CURRENT_CONSTITUENTS_PRICE_TO_EARNINGS ~Fields.CURRENT_CONSTITUENTS_PRICE_TO_EARNINGS_POSITIVE ~Fields.CURRENT_CONSTITUENTS_PRICE_TO_SALES ~Fields.CURRENT_CONSTITUENTS_RETURN_ON_EQUITY ~Fields.CURRENT_CONSTITUENTS_SALES_PER_SHARE ~Fields.ONE_YEAR ~Fields.TWO_YEARS ~Fields.THREE_YEARS ~Fields.FORWARD ~Fields.TRAILING ~Fields.ASSET_ID ~Fields.NAME ~Fields.RIC ~Fields.TENOR ~Fields.STRIKE_REFERENCE ~Fields.RELATIVE_STRIKE ~Fields.EXPIRATION_DATE ~Fields.UPDATE_TIME ================================================ FILE: docs/classes/gs_quant.datetime.relative_date.RelativeDate.rst ================================================ gs\_quant.datetime.relative\_date.RelativeDate ============================================== .. currentmodule:: gs_quant.datetime.relative_date .. autoclass:: RelativeDate .. rubric:: Methods .. autosummary:: ~RelativeDate.apply_rule ~RelativeDate.as_dict ================================================ FILE: docs/classes/gs_quant.instrument.CommodOTCSwap.rst ================================================ CommodOTCSwap ============= For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: CommodOTCSwap .. rubric:: Properties .. autoattribute:: dataclass_json_config .. autoattribute:: end .. autoattribute:: instrument_quantity .. autoattribute:: legs .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: number_of_periods .. autoattribute:: provider .. autoattribute:: quantity .. autoattribute:: quantity_ .. autoattribute:: quantity_period .. autoattribute:: quantity_unit .. autoattribute:: resolution_key .. autoattribute:: settlement .. autoattribute:: start .. autoattribute:: strategy .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.EqCliquet.rst ================================================ EqCliquet ========= For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: EqCliquet .. rubric:: Properties .. autoattribute:: currency .. autoattribute:: dataclass_json_config .. autoattribute:: expiration_date .. autoattribute:: first_valuation_date .. autoattribute:: global_cap .. autoattribute:: global_floor .. autoattribute:: instrument_quantity .. autoattribute:: last_valuation_date .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: payment_frequency .. autoattribute:: premium .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: return_style .. autoattribute:: return_type .. autoattribute:: strike_price .. autoattribute:: type_ .. autoattribute:: underlier .. autoattribute:: underlier_type .. autoattribute:: unresolved .. autoattribute:: valuation_period ================================================ FILE: docs/classes/gs_quant.instrument.EqForward.rst ================================================ EqForward ========= For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: EqForward .. rubric:: Properties .. autoattribute:: dataclass_json_config .. autoattribute:: expiration_date .. autoattribute:: forward_price .. autoattribute:: instrument_quantity .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: number_of_shares .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: type_ .. autoattribute:: underlier .. autoattribute:: underlier_type .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.EqOption.rst ================================================ EqOption ======== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: EqOption .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: dataclass_json_config .. autoattribute:: exchange .. autoattribute:: expiration_date .. autoattribute:: future_contract .. autoattribute:: instrument_quantity .. autoattribute:: metadata .. autoattribute:: method_of_settlement .. autoattribute:: multiplier .. autoattribute:: name .. autoattribute:: number_of_options .. autoattribute:: option_style .. autoattribute:: option_type .. autoattribute:: premium .. autoattribute:: premium_currency .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: settlement_currency .. autoattribute:: settlement_date .. autoattribute:: strike_price .. autoattribute:: trade_as .. autoattribute:: type_ .. autoattribute:: underlier .. autoattribute:: underlier_type .. autoattribute:: unresolved .. autoattribute:: valuation_time ================================================ FILE: docs/classes/gs_quant.instrument.EqSynthetic.rst ================================================ EqSynthetic =========== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: EqSynthetic .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: commission .. autoattribute:: commission_type .. autoattribute:: commission_units .. autoattribute:: currency .. autoattribute:: dataclass_json_config .. autoattribute:: designated_maturity .. autoattribute:: dividend_pay_ratio .. autoattribute:: effective_date .. autoattribute:: eq_leg .. autoattribute:: expiry .. autoattribute:: funding_leg .. autoattribute:: fx_data_source .. autoattribute:: initial_valuation_date .. autoattribute:: instrument_quantity .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: num_of_underlyers .. autoattribute:: oet_terms .. autoattribute:: payment_bdc .. autoattribute:: payment_delay .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: rate_option .. autoattribute:: rate_tenor .. autoattribute:: reset_delay .. autoattribute:: resolution_key .. autoattribute:: schedule_type .. autoattribute:: settlement_currency .. autoattribute:: settlement_delay .. autoattribute:: strike .. autoattribute:: swap_type .. autoattribute:: trade_date .. autoattribute:: type_ .. autoattribute:: underlier .. autoattribute:: underlier_type .. autoattribute:: unresolved .. autoattribute:: valuation_bdc ================================================ FILE: docs/classes/gs_quant.instrument.EqVarianceSwap.rst ================================================ EqVarianceSwap ============== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: EqVarianceSwap .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: dataclass_json_config .. autoattribute:: days_in_contract .. autoattribute:: denominated .. autoattribute:: expiration_date .. autoattribute:: expiry_settlement_days .. autoattribute:: fixing_schedule_dates .. autoattribute:: force_forward_tradable .. autoattribute:: holiday_calendar .. autoattribute:: initial_date .. autoattribute:: initial_spot .. autoattribute:: initial_spot_source_flag .. autoattribute:: instrument_quantity .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: premium .. autoattribute:: provider .. autoattribute:: quantity .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: settlement_date .. autoattribute:: strike_price .. autoattribute:: type_ .. autoattribute:: underlier .. autoattribute:: underlier_type .. autoattribute:: unresolved .. autoattribute:: valuation_time .. autoattribute:: variance_cap ================================================ FILE: docs/classes/gs_quant.instrument.FXAccumulator.rst ================================================ FXAccumulator ============= For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXAccumulator .. rubric:: Properties .. autoattribute:: accum_or_decum .. autoattribute:: accumulator_type .. autoattribute:: coupon_frequency .. autoattribute:: dataclass_json_config .. autoattribute:: european_knock_in .. autoattribute:: expiration_date .. autoattribute:: first_fixing_date .. autoattribute:: fixing_rate_option .. autoattribute:: guaranteed_coupons .. autoattribute:: instrument_quantity .. autoattribute:: knock_out_level .. autoattribute:: leverage_ratio .. autoattribute:: metadata .. autoattribute:: method_of_settlement .. autoattribute:: name .. autoattribute:: new_or_unwind .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: number_of_expiry .. autoattribute:: pair .. autoattribute:: premium .. autoattribute:: premium_currency .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: schedules .. autoattribute:: settlement_currency .. autoattribute:: settlement_date .. autoattribute:: settlement_rate_option .. autoattribute:: strike .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.FXAccumulatorScheduleLeg.rst ================================================ FXAccumulatorScheduleLeg ======================== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXAccumulatorScheduleLeg .. rubric:: Properties .. autoattribute:: dataclass_json_config .. autoattribute:: european_knock_in .. autoattribute:: fixing_date .. autoattribute:: instrument_quantity .. autoattribute:: knock_out_level .. autoattribute:: leverage_ratio .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: strike .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.FXBinary.rst ================================================ FXBinary ======== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXBinary .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: dataclass_json_config .. autoattribute:: expiration_date .. autoattribute:: expiration_time .. autoattribute:: instrument_quantity .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: option_type .. autoattribute:: pair .. autoattribute:: premium .. autoattribute:: premium_currency .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: settlement_date .. autoattribute:: settlement_rate_option .. autoattribute:: strike_price .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.FXCorrelationSwap.rst ================================================ FXCorrelationSwap ================= For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXCorrelationSwap .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: calculate_mean_return .. autoattribute:: dataclass_json_config .. autoattribute:: first_fixing_date .. autoattribute:: fixing_frequency .. autoattribute:: fixing_source .. autoattribute:: instrument_quantity .. autoattribute:: last_fixing_date .. autoattribute:: legs .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: premium .. autoattribute:: premium_currency .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: settlement_date .. autoattribute:: strike_corr .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.FXCorrelationSwapLeg.rst ================================================ FXCorrelationSwapLeg ==================== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXCorrelationSwapLeg .. rubric:: Properties .. autoattribute:: dataclass_json_config .. autoattribute:: instrument_quantity .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: pair .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.FXDoubleKnockout.rst ================================================ FXDoubleKnockout ================ For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXDoubleKnockout .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: dataclass_json_config .. autoattribute:: expiration_date .. autoattribute:: expiration_time .. autoattribute:: instrument_quantity .. autoattribute:: knock_in_or_out .. autoattribute:: knockout_convention .. autoattribute:: lower_barrier_level .. autoattribute:: metadata .. autoattribute:: method_of_settlement .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: option_type .. autoattribute:: pair .. autoattribute:: premium .. autoattribute:: premium_currency .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: settlement_currency .. autoattribute:: settlement_date .. autoattribute:: settlement_rate_option .. autoattribute:: strike_price .. autoattribute:: type_ .. autoattribute:: unresolved .. autoattribute:: upper_barrier_level ================================================ FILE: docs/classes/gs_quant.instrument.FXDoubleOneTouch.rst ================================================ FXDoubleOneTouch ================ For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXDoubleOneTouch .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: dataclass_json_config .. autoattribute:: expiration_date .. autoattribute:: expiration_time .. autoattribute:: instrument_quantity .. autoattribute:: knockout_convention .. autoattribute:: lower_barrier_level .. autoattribute:: metadata .. autoattribute:: method_of_settlement .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: pair .. autoattribute:: payout_type .. autoattribute:: premium .. autoattribute:: premium_currency .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: settlement_currency .. autoattribute:: settlement_date .. autoattribute:: settlement_rate_option .. autoattribute:: touch_or_no_touch .. autoattribute:: type_ .. autoattribute:: unresolved .. autoattribute:: upper_barrier_level ================================================ FILE: docs/classes/gs_quant.instrument.FXDualDoubleKnockout.rst ================================================ FXDualDoubleKnockout ==================== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXDualDoubleKnockout .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: dataclass_json_config .. autoattribute:: expiration_date .. autoattribute:: expiration_time .. autoattribute:: instrument_quantity .. autoattribute:: legs .. autoattribute:: metadata .. autoattribute:: method_of_settlement .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: option_type .. autoattribute:: premium .. autoattribute:: premium_currency .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: settlement_currency .. autoattribute:: settlement_date .. autoattribute:: settlement_rate_option .. autoattribute:: strike_price .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.FXDualDoubleKnockoutLeg.rst ================================================ FXDualDoubleKnockoutLeg ======================= For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXDualDoubleKnockoutLeg .. rubric:: Properties .. autoattribute:: dataclass_json_config .. autoattribute:: instrument_quantity .. autoattribute:: knockout_convention .. autoattribute:: lower_barrier_level .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: pair .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: type_ .. autoattribute:: unresolved .. autoattribute:: upper_barrier_level ================================================ FILE: docs/classes/gs_quant.instrument.FXEuropeanKnockout.rst ================================================ FXEuropeanKnockout ================== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXEuropeanKnockout .. rubric:: Properties .. autoattribute:: barrier_level .. autoattribute:: buy_sell .. autoattribute:: dataclass_json_config .. autoattribute:: expiration_date .. autoattribute:: expiration_time .. autoattribute:: instrument_quantity .. autoattribute:: knock_in_or_out .. autoattribute:: knock_up_or_down .. autoattribute:: metadata .. autoattribute:: method_of_settlement .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: option_type .. autoattribute:: pair .. autoattribute:: premium .. autoattribute:: premium_currency .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: settlement_currency .. autoattribute:: settlement_date .. autoattribute:: settlement_rate_option .. autoattribute:: strike_price .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.FXForward.rst ================================================ FXForward ========= For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXForward .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: dataclass_json_config .. autoattribute:: forward_rate .. autoattribute:: instrument_quantity .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_amount_in_other_currency .. autoattribute:: notional_currency .. autoattribute:: pair .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: settlement_date .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.FXKnockout.rst ================================================ FXKnockout ========== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXKnockout .. rubric:: Properties .. autoattribute:: barrier_level .. autoattribute:: buy_sell .. autoattribute:: dataclass_json_config .. autoattribute:: expiration_date .. autoattribute:: expiration_time .. autoattribute:: instrument_quantity .. autoattribute:: knock_in_or_out .. autoattribute:: knock_up_or_down .. autoattribute:: knockout_convention .. autoattribute:: metadata .. autoattribute:: method_of_settlement .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: option_type .. autoattribute:: pair .. autoattribute:: premium .. autoattribute:: premium_currency .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: settlement_currency .. autoattribute:: settlement_date .. autoattribute:: settlement_rate_option .. autoattribute:: strike_price .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.FXMultiCrossBinary.rst ================================================ FXMultiCrossBinary ================== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXMultiCrossBinary .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: dataclass_json_config .. autoattribute:: expiration_date .. autoattribute:: expiration_time .. autoattribute:: instrument_quantity .. autoattribute:: legs .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: premium .. autoattribute:: premium_currency .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: settlement_date .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.FXMultiCrossBinaryLeg.rst ================================================ FXMultiCrossBinaryLeg ===================== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXMultiCrossBinaryLeg .. rubric:: Properties .. autoattribute:: dataclass_json_config .. autoattribute:: fixing_source .. autoattribute:: instrument_quantity .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: option_type .. autoattribute:: pair .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: strike_price .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.FXMultiCrossDoubleBinary.rst ================================================ FXMultiCrossDoubleBinary ======================== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXMultiCrossDoubleBinary .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: dataclass_json_config .. autoattribute:: expiration_date .. autoattribute:: expiration_time .. autoattribute:: instrument_quantity .. autoattribute:: legs .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: premium .. autoattribute:: premium_currency .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: settlement_date .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.FXMultiCrossDoubleBinaryLeg.rst ================================================ FXMultiCrossDoubleBinaryLeg =========================== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXMultiCrossDoubleBinaryLeg .. rubric:: Properties .. autoattribute:: dataclass_json_config .. autoattribute:: fixing_source .. autoattribute:: instrument_quantity .. autoattribute:: lower_barrier_level .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: pair .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: type_ .. autoattribute:: unresolved .. autoattribute:: upper_barrier_level ================================================ FILE: docs/classes/gs_quant.instrument.FXMultiCrossDoubleOneTouch.rst ================================================ FXMultiCrossDoubleOneTouch ========================== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXMultiCrossDoubleOneTouch .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: dataclass_json_config .. autoattribute:: expiration_date .. autoattribute:: expiration_time .. autoattribute:: instrument_quantity .. autoattribute:: legs .. autoattribute:: metadata .. autoattribute:: method_of_settlement .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: payout_type .. autoattribute:: premium .. autoattribute:: premium_currency .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: settlement_currency .. autoattribute:: settlement_date .. autoattribute:: touch_or_no_touch .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.FXMultiCrossDoubleOneTouchLeg.rst ================================================ FXMultiCrossDoubleOneTouchLeg ============================= For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXMultiCrossDoubleOneTouchLeg .. rubric:: Properties .. autoattribute:: dataclass_json_config .. autoattribute:: instrument_quantity .. autoattribute:: knockout_convention .. autoattribute:: lower_barrier_level .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: pair .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: type_ .. autoattribute:: unresolved .. autoattribute:: upper_barrier_level ================================================ FILE: docs/classes/gs_quant.instrument.FXOneTouch.rst ================================================ FXOneTouch ========== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXOneTouch .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: dataclass_json_config .. autoattribute:: expiration_date .. autoattribute:: expiration_time .. autoattribute:: instrument_quantity .. autoattribute:: knock_up_or_down .. autoattribute:: knockout_convention .. autoattribute:: metadata .. autoattribute:: method_of_settlement .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: pair .. autoattribute:: payout_type .. autoattribute:: premium .. autoattribute:: premium_currency .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: settlement_currency .. autoattribute:: settlement_date .. autoattribute:: settlement_rate_option .. autoattribute:: strike_price .. autoattribute:: touch_or_no_touch .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.FXOption.rst ================================================ FXOption ======== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXOption .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: dataclass_json_config .. autoattribute:: exercise_style .. autoattribute:: expiration_date .. autoattribute:: expiration_time .. autoattribute:: instrument_quantity .. autoattribute:: metadata .. autoattribute:: method_of_settlement .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_amount_in_other_currency .. autoattribute:: notional_currency .. autoattribute:: option_type .. autoattribute:: pair .. autoattribute:: premium .. autoattribute:: premium_currency .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: settlement_currency .. autoattribute:: settlement_date .. autoattribute:: settlement_rate_option .. autoattribute:: strike_price .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.FXOptionLeg.rst ================================================ FXOptionLeg =========== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXOptionLeg .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: dataclass_json_config .. autoattribute:: exercise_style .. autoattribute:: expiration_date .. autoattribute:: expiration_time .. autoattribute:: instrument_quantity .. autoattribute:: metadata .. autoattribute:: method_of_settlement .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_amount_in_other_currency .. autoattribute:: notional_currency .. autoattribute:: option_type .. autoattribute:: premium .. autoattribute:: premium_currency .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: settlement_currency .. autoattribute:: settlement_date .. autoattribute:: settlement_rate_option .. autoattribute:: strike_price .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.FXOptionStrategy.rst ================================================ FXOptionStrategy ================ For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXOptionStrategy .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: dataclass_json_config .. autoattribute:: exercise_style .. autoattribute:: expiration_date .. autoattribute:: expiration_time .. autoattribute:: instrument_quantity .. autoattribute:: legs .. autoattribute:: metadata .. autoattribute:: method_of_settlement .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_amount_in_other_currency .. autoattribute:: notional_currency .. autoattribute:: option_type .. autoattribute:: pair .. autoattribute:: premium .. autoattribute:: premium_currency .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: settlement_currency .. autoattribute:: settlement_date .. autoattribute:: settlement_rate_option .. autoattribute:: strategy_name .. autoattribute:: strike_price .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.FXPivot.rst ================================================ FXPivot ======= For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXPivot .. rubric:: Properties .. autoattribute:: coupon_frequency .. autoattribute:: dataclass_json_config .. autoattribute:: expiration_date .. autoattribute:: first_fixing_date .. autoattribute:: fixing_rate_option .. autoattribute:: instrument_quantity .. autoattribute:: lower_knock_in .. autoattribute:: lower_leverage_ratio .. autoattribute:: lower_strike .. autoattribute:: metadata .. autoattribute:: method_of_settlement .. autoattribute:: name .. autoattribute:: new_or_unwind .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: number_of_expiry .. autoattribute:: pair .. autoattribute:: payment_on_hitting_target .. autoattribute:: pivot .. autoattribute:: premium .. autoattribute:: premium_currency .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: schedules .. autoattribute:: settlement_currency .. autoattribute:: settlement_date .. autoattribute:: settlement_rate_option .. autoattribute:: target .. autoattribute:: target_adj_notional_or_strike .. autoattribute:: target_type .. autoattribute:: type_ .. autoattribute:: unresolved .. autoattribute:: upper_knock_in .. autoattribute:: upper_leverage_ratio .. autoattribute:: upper_strike ================================================ FILE: docs/classes/gs_quant.instrument.FXPivotScheduleLeg.rst ================================================ FXPivotScheduleLeg ================== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXPivotScheduleLeg .. rubric:: Properties .. autoattribute:: dataclass_json_config .. autoattribute:: fixing_date .. autoattribute:: instrument_quantity .. autoattribute:: lower_knock_in .. autoattribute:: lower_leverage_ratio .. autoattribute:: lower_strike .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: payment_date .. autoattribute:: pivot .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: type_ .. autoattribute:: unresolved .. autoattribute:: upper_knock_in .. autoattribute:: upper_leverage_ratio .. autoattribute:: upper_strike ================================================ FILE: docs/classes/gs_quant.instrument.FXShiftingBermForward.rst ================================================ FXShiftingBermForward ===================== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXShiftingBermForward .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: dataclass_json_config .. autoattribute:: exercise_decision_freq .. autoattribute:: expiration_date .. autoattribute:: instrument_quantity .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_amount_in_other_currency .. autoattribute:: notional_currency .. autoattribute:: pair .. autoattribute:: premium .. autoattribute:: premium_currency .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: settlement_currency .. autoattribute:: settlement_date .. autoattribute:: strike_price .. autoattribute:: type_ .. autoattribute:: unresolved .. autoattribute:: window_start_date ================================================ FILE: docs/classes/gs_quant.instrument.FXTarf.rst ================================================ FXTarf ====== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXTarf .. rubric:: Properties .. autoattribute:: coupon_frequency .. autoattribute:: dataclass_json_config .. autoattribute:: european_knock_in .. autoattribute:: expiration_date .. autoattribute:: first_fixing_date .. autoattribute:: fixing_rate_option .. autoattribute:: instrument_quantity .. autoattribute:: leverage_ratio .. autoattribute:: long_or_short .. autoattribute:: loss_strike .. autoattribute:: metadata .. autoattribute:: method_of_settlement .. autoattribute:: name .. autoattribute:: new_or_unwind .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: number_of_expiry .. autoattribute:: pair .. autoattribute:: payment_on_hitting_target .. autoattribute:: premium .. autoattribute:: premium_currency .. autoattribute:: premium_payment_date .. autoattribute:: profit_strike .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: schedules .. autoattribute:: settlement_currency .. autoattribute:: settlement_date .. autoattribute:: settlement_rate_option .. autoattribute:: target .. autoattribute:: target_adj_notional_or_strike .. autoattribute:: target_type .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.FXTarfScheduleLeg.rst ================================================ FXTarfScheduleLeg ================= For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXTarfScheduleLeg .. rubric:: Properties .. autoattribute:: dataclass_json_config .. autoattribute:: european_knock_in .. autoattribute:: fixing_date .. autoattribute:: instrument_quantity .. autoattribute:: leverage_ratio .. autoattribute:: loss_strike .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: payment_date .. autoattribute:: profit_strike .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.FXVarianceSwap.rst ================================================ FXVarianceSwap ============== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXVarianceSwap .. rubric:: Properties .. autoattribute:: annualization_factor .. autoattribute:: buy_sell .. autoattribute:: calculate_mean_return .. autoattribute:: dataclass_json_config .. autoattribute:: first_fixing_date .. autoattribute:: fixing_frequency .. autoattribute:: fixing_source .. autoattribute:: instrument_quantity .. autoattribute:: last_fixing_date .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: pair .. autoattribute:: premium .. autoattribute:: premium_currency .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: settlement_date .. autoattribute:: strike_vol .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.FXVolatilitySwap.rst ================================================ FXVolatilitySwap ================ For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXVolatilitySwap .. rubric:: Properties .. autoattribute:: annualization_factor .. autoattribute:: buy_sell .. autoattribute:: calculate_mean_return .. autoattribute:: dataclass_json_config .. autoattribute:: first_fixing_date .. autoattribute:: fixing_frequency .. autoattribute:: fixing_source .. autoattribute:: instrument_quantity .. autoattribute:: last_fixing_date .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: pair .. autoattribute:: premium .. autoattribute:: premium_currency .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: settlement_date .. autoattribute:: strike_vol .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.FXWorstOf.rst ================================================ FXWorstOf ========= For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXWorstOf .. rubric:: Properties .. autoattribute:: best_or_worst .. autoattribute:: buy_sell .. autoattribute:: dataclass_json_config .. autoattribute:: instrument_quantity .. autoattribute:: legs .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: premium .. autoattribute:: premium_currency .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: settlement_date .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.FXWorstOfKO.rst ================================================ FXWorstOfKO =========== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXWorstOfKO .. rubric:: Properties .. autoattribute:: best_or_worst .. autoattribute:: buy_sell .. autoattribute:: dataclass_json_config .. autoattribute:: instrument_quantity .. autoattribute:: legs .. autoattribute:: metadata .. autoattribute:: method_of_settlement .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: premium .. autoattribute:: premium_currency .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: settlement_currency .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.FXWorstOfKOLeg.rst ================================================ FXWorstOfKOLeg ============== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: FXWorstOfKOLeg .. rubric:: Properties .. autoattribute:: barrier_level .. autoattribute:: dataclass_json_config .. autoattribute:: expiration_date .. autoattribute:: expiration_time .. autoattribute:: instrument_quantity .. autoattribute:: knock_in_or_out .. autoattribute:: knock_up_or_down .. autoattribute:: knockout_convention .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: option_type .. autoattribute:: pair .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: settlement_date .. autoattribute:: settlement_rate_option .. autoattribute:: strike_price .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.Forward.rst ================================================ Forward ======= For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: Forward .. rubric:: Properties .. autoattribute:: currency .. autoattribute:: dataclass_json_config .. autoattribute:: expiration_date .. autoattribute:: instrument_quantity .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.IRBasisSwap.rst ================================================ IRBasisSwap =========== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: IRBasisSwap .. rubric:: Properties .. autoattribute:: clearing_house .. autoattribute:: dataclass_json_config .. autoattribute:: effective_date .. autoattribute:: fee .. autoattribute:: fee_currency .. autoattribute:: fee_payment_date .. autoattribute:: instrument_quantity .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: payer_business_day_convention .. autoattribute:: payer_day_count_fraction .. autoattribute:: payer_designated_maturity .. autoattribute:: payer_frequency .. autoattribute:: payer_rate_option .. autoattribute:: payer_spread .. autoattribute:: principal_exchange .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: receiver_business_day_convention .. autoattribute:: receiver_day_count_fraction .. autoattribute:: receiver_designated_maturity .. autoattribute:: receiver_frequency .. autoattribute:: receiver_rate_option .. autoattribute:: receiver_spread .. autoattribute:: resolution_key .. autoattribute:: termination_date .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.IRCMSOption.rst ================================================ IRCMSOption =========== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: IRCMSOption .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: cap_floor .. autoattribute:: dataclass_json_config .. autoattribute:: effective_date .. autoattribute:: fee .. autoattribute:: fee_currency .. autoattribute:: fee_payment_date .. autoattribute:: index .. autoattribute:: instrument_quantity .. autoattribute:: metadata .. autoattribute:: multiplier .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: premium .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: rate_option .. autoattribute:: resolution_key .. autoattribute:: start_date .. autoattribute:: strike .. autoattribute:: termination_date .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.IRCMSOptionStrip.rst ================================================ IRCMSOptionStrip ================ For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: IRCMSOptionStrip .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: cap_floor .. autoattribute:: dataclass_json_config .. autoattribute:: effective_date .. autoattribute:: fee .. autoattribute:: fee_currency .. autoattribute:: fee_payment_date .. autoattribute:: floating_rate_business_day_convention .. autoattribute:: floating_rate_day_count_fraction .. autoattribute:: floating_rate_frequency .. autoattribute:: index .. autoattribute:: instrument_quantity .. autoattribute:: metadata .. autoattribute:: multiplier .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: premium .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: reset_delay .. autoattribute:: resolution_key .. autoattribute:: strike .. autoattribute:: termination_date .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.IRCMSSpreadOption.rst ================================================ IRCMSSpreadOption ================= For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: IRCMSSpreadOption .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: cap_floor .. autoattribute:: dataclass_json_config .. autoattribute:: effective_date .. autoattribute:: fee .. autoattribute:: fee_currency .. autoattribute:: fee_payment_date .. autoattribute:: index1_tenor .. autoattribute:: index2_tenor .. autoattribute:: instrument_quantity .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: premium .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: strike .. autoattribute:: termination_date .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.IRCMSSpreadOptionStrip.rst ================================================ IRCMSSpreadOptionStrip ====================== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: IRCMSSpreadOptionStrip .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: cap_floor .. autoattribute:: dataclass_json_config .. autoattribute:: effective_date .. autoattribute:: fee .. autoattribute:: fee_currency .. autoattribute:: fee_payment_date .. autoattribute:: floating_rate_business_day_convention .. autoattribute:: floating_rate_day_count_fraction .. autoattribute:: floating_rate_frequency .. autoattribute:: index1 .. autoattribute:: index2 .. autoattribute:: instrument_quantity .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: premium .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: reset_delay .. autoattribute:: resolution_key .. autoattribute:: strike .. autoattribute:: termination_date .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.IRCap.rst ================================================ IRCap ===== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: IRCap .. rubric:: Properties .. autoattribute:: cap_rate .. autoattribute:: dataclass_json_config .. autoattribute:: effective_date .. autoattribute:: fee .. autoattribute:: fee_currency .. autoattribute:: fee_payment_date .. autoattribute:: floating_rate_business_day_convention .. autoattribute:: floating_rate_day_count_fraction .. autoattribute:: floating_rate_designated_maturity .. autoattribute:: floating_rate_frequency .. autoattribute:: floating_rate_option .. autoattribute:: instrument_quantity .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: premium .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: termination_date .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.IRFloor.rst ================================================ IRFloor ======= For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: IRFloor .. rubric:: Properties .. autoattribute:: dataclass_json_config .. autoattribute:: effective_date .. autoattribute:: fee .. autoattribute:: fee_currency .. autoattribute:: fee_payment_date .. autoattribute:: floating_rate_business_day_convention .. autoattribute:: floating_rate_day_count_fraction .. autoattribute:: floating_rate_designated_maturity .. autoattribute:: floating_rate_frequency .. autoattribute:: floating_rate_option .. autoattribute:: floor_rate .. autoattribute:: instrument_quantity .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: premium .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: termination_date .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.IRSwap.rst ================================================ IRSwap ====== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: IRSwap .. rubric:: Properties .. autoattribute:: clearing_house .. autoattribute:: dataclass_json_config .. autoattribute:: effective_date .. autoattribute:: fee .. autoattribute:: fee_currency .. autoattribute:: fee_payment_date .. autoattribute:: fixed_first_stub .. autoattribute:: fixed_holidays .. autoattribute:: fixed_last_stub .. autoattribute:: fixed_rate .. autoattribute:: fixed_rate_accrual_convention .. autoattribute:: fixed_rate_business_day_convention .. autoattribute:: fixed_rate_day_count_fraction .. autoattribute:: fixed_rate_frequency .. autoattribute:: floating_first_stub .. autoattribute:: floating_holidays .. autoattribute:: floating_last_stub .. autoattribute:: floating_rate_accrual_convention .. autoattribute:: floating_rate_business_day_convention .. autoattribute:: floating_rate_day_count_fraction .. autoattribute:: floating_rate_designated_maturity .. autoattribute:: floating_rate_for_the_initial_calculation_period .. autoattribute:: floating_rate_frequency .. autoattribute:: floating_rate_option .. autoattribute:: floating_rate_spread .. autoattribute:: instrument_quantity .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: pay_or_receive .. autoattribute:: principal_exchange .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: roll_convention .. autoattribute:: termination_date .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.IRSwaption.rst ================================================ IRSwaption ========== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: IRSwaption .. rubric:: Properties .. autoattribute:: buy_sell .. autoattribute:: clearing_house .. autoattribute:: dataclass_json_config .. autoattribute:: effective_date .. autoattribute:: expiration_date .. autoattribute:: fee .. autoattribute:: fee_currency .. autoattribute:: fee_payment_date .. autoattribute:: fixed_rate_business_day_convention .. autoattribute:: fixed_rate_day_count_fraction .. autoattribute:: fixed_rate_frequency .. autoattribute:: floating_rate_business_day_convention .. autoattribute:: floating_rate_day_count_fraction .. autoattribute:: floating_rate_designated_maturity .. autoattribute:: floating_rate_frequency .. autoattribute:: floating_rate_option .. autoattribute:: floating_rate_spread .. autoattribute:: instrument_quantity .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: pay_or_receive .. autoattribute:: premium .. autoattribute:: premium_payment_date .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: settlement .. autoattribute:: strike .. autoattribute:: termination_date .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.IRXccySwap.rst ================================================ IRXccySwap ========== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: IRXccySwap .. rubric:: Properties .. autoattribute:: dataclass_json_config .. autoattribute:: effective_date .. autoattribute:: fee .. autoattribute:: fee_currency .. autoattribute:: fee_payment_date .. autoattribute:: initial_fx_rate .. autoattribute:: instrument_quantity .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_reset_side .. autoattribute:: payer_business_day_convention .. autoattribute:: payer_currency .. autoattribute:: payer_day_count_fraction .. autoattribute:: payer_designated_maturity .. autoattribute:: payer_first_stub .. autoattribute:: payer_frequency .. autoattribute:: payer_holidays .. autoattribute:: payer_last_stub .. autoattribute:: payer_rate_option .. autoattribute:: payer_spread .. autoattribute:: principal_exchange .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: receiver_business_day_convention .. autoattribute:: receiver_currency .. autoattribute:: receiver_day_count_fraction .. autoattribute:: receiver_designated_maturity .. autoattribute:: receiver_first_stub .. autoattribute:: receiver_frequency .. autoattribute:: receiver_holidays .. autoattribute:: receiver_last_stub .. autoattribute:: receiver_rate_option .. autoattribute:: receiver_spread .. autoattribute:: resolution_key .. autoattribute:: termination_date .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.IRXccySwapFixFix.rst ================================================ IRXccySwapFixFix ================ For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: IRXccySwapFixFix .. rubric:: Properties .. autoattribute:: dataclass_json_config .. autoattribute:: effective_date .. autoattribute:: fee .. autoattribute:: fee_currency .. autoattribute:: fee_payment_date .. autoattribute:: instrument_quantity .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: payer_business_day_convention .. autoattribute:: payer_currency .. autoattribute:: payer_day_count_fraction .. autoattribute:: payer_frequency .. autoattribute:: payer_rate .. autoattribute:: principal_exchange .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: receiver_business_day_convention .. autoattribute:: receiver_currency .. autoattribute:: receiver_day_count_fraction .. autoattribute:: receiver_frequency .. autoattribute:: receiver_notional_amount .. autoattribute:: receiver_rate .. autoattribute:: resolution_key .. autoattribute:: termination_date .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.IRXccySwapFixFlt.rst ================================================ IRXccySwapFixFlt ================ For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: IRXccySwapFixFlt .. rubric:: Properties .. autoattribute:: dataclass_json_config .. autoattribute:: effective_date .. autoattribute:: fee .. autoattribute:: fee_currency .. autoattribute:: fee_payment_date .. autoattribute:: fixed_first_stub .. autoattribute:: fixed_holidays .. autoattribute:: fixed_last_stub .. autoattribute:: fixed_rate .. autoattribute:: fixed_rate_business_day_convention .. autoattribute:: fixed_rate_currency .. autoattribute:: fixed_rate_day_count_fraction .. autoattribute:: fixed_rate_frequency .. autoattribute:: floating_first_stub .. autoattribute:: floating_holidays .. autoattribute:: floating_last_stub .. autoattribute:: floating_rate_business_day_convention .. autoattribute:: floating_rate_currency .. autoattribute:: floating_rate_day_count_fraction .. autoattribute:: floating_rate_designated_maturity .. autoattribute:: floating_rate_for_the_initial_calculation_period .. autoattribute:: floating_rate_frequency .. autoattribute:: floating_rate_option .. autoattribute:: floating_rate_spread .. autoattribute:: instrument_quantity .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: pay_or_receive .. autoattribute:: principal_exchange .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: termination_date .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.InflationSwap.rst ================================================ InflationSwap ============= For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: InflationSwap .. rubric:: Properties .. autoattribute:: base_cpi .. autoattribute:: clearing_house .. autoattribute:: dataclass_json_config .. autoattribute:: effective_date .. autoattribute:: fee .. autoattribute:: fixed_rate .. autoattribute:: fixed_rate_business_day_convention .. autoattribute:: floating_rate_business_day_convention .. autoattribute:: index .. autoattribute:: instrument_quantity .. autoattribute:: metadata .. autoattribute:: name .. autoattribute:: notional_amount .. autoattribute:: notional_currency .. autoattribute:: pay_or_receive .. autoattribute:: provider .. autoattribute:: quantity_ .. autoattribute:: resolution_key .. autoattribute:: termination_date .. autoattribute:: type_ .. autoattribute:: unresolved ================================================ FILE: docs/classes/gs_quant.instrument.Security.rst ================================================ Security ======== For methods of this class, see :doc:`gs_quant.base.Priceable` .. currentmodule:: gs_quant.instrument .. autoclass:: Security .. rubric:: Properties .. autoattribute:: bbgid .. autoattribute:: bbid .. autoattribute:: bbid_equivalent .. autoattribute:: bcid .. autoattribute:: cid .. autoattribute:: cins .. autoattribute:: cm_id .. autoattribute:: coin_metrics_id .. autoattribute:: cross .. autoattribute:: cusip .. autoattribute:: dataclass_json_config .. autoattribute:: delisted .. autoattribute:: display_id .. autoattribute:: dollar_cross .. autoattribute:: eid .. autoattribute:: em_id .. autoattribute:: exchange_code .. autoattribute:: gsid .. autoattribute:: gsid_equivalent .. autoattribute:: gsideid .. autoattribute:: gsn .. autoattribute:: gss .. autoattribute:: instrument_quantity .. autoattribute:: isin .. autoattribute:: jsn .. autoattribute:: lms_id .. autoattribute:: mdapi .. autoattribute:: mdapi_class .. autoattribute:: metadata .. autoattribute:: mic .. autoattribute:: mq_symbol .. autoattribute:: name .. autoattribute:: pl_id .. autoattribute:: plot_id .. autoattribute:: pnode_id .. autoattribute:: primary_country_ric .. autoattribute:: prime_id .. autoattribute:: provider .. autoattribute:: ps_id .. autoattribute:: quantity_ .. autoattribute:: rcic .. autoattribute:: resolution_key .. autoattribute:: ric .. autoattribute:: sec_master_id .. autoattribute:: sec_name .. autoattribute:: sedol .. autoattribute:: sf_id .. autoattribute:: simon_id .. autoattribute:: tdapi .. autoattribute:: ticker .. autoattribute:: tsdb_shortname .. autoattribute:: unresolved .. autoattribute:: valoren .. autoattribute:: wi_id .. autoattribute:: wpk ================================================ FILE: docs/classes/gs_quant.markets.baskets.Basket.rst ================================================ gs\_quant.markets.baskets.Basket ================================ .. currentmodule:: gs_quant.markets.baskets .. autoclass:: Basket .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~Basket.__init__ ~Basket.add_factor_risk_report ~Basket.cancel_rebalance ~Basket.clone ~Basket.create ~Basket.delete_factor_risk_report ~Basket.entity_type ~Basket.get ~Basket.get_all_esg_data ~Basket.get_all_thematic_exposures ~Basket.get_asset_measures ~Basket.get_bottom_five_thematic_exposures ~Basket.get_carbon_analytics ~Basket.get_carbon_attribution_table ~Basket.get_carbon_coverage ~Basket.get_carbon_emissions ~Basket.get_carbon_emissions_allocation ~Basket.get_carbon_sbti_netzero_coverage ~Basket.get_close_price_for_date ~Basket.get_close_prices ~Basket.get_corporate_actions ~Basket.get_data_coordinate ~Basket.get_data_series ~Basket.get_details ~Basket.get_entitlements ~Basket.get_entity ~Basket.get_esg_bottom_ten ~Basket.get_esg_by_region ~Basket.get_esg_by_sector ~Basket.get_esg_quintiles ~Basket.get_esg_summary ~Basket.get_esg_top_ten ~Basket.get_factor_risk_report ~Basket.get_factor_risk_reports ~Basket.get_factor_scenario_analytics ~Basket.get_fundamentals ~Basket.get_hloc_prices ~Basket.get_identifier ~Basket.get_identifiers ~Basket.get_last_positions_data ~Basket.get_latest_close_price ~Basket.get_latest_position_set ~Basket.get_latest_rebalance_data ~Basket.get_latest_rebalance_date ~Basket.get_live_date ~Basket.get_marquee_id ~Basket.get_position_dates ~Basket.get_position_set_for_date ~Basket.get_position_sets ~Basket.get_positions_data ~Basket.get_rebalance_approval_status ~Basket.get_reports ~Basket.get_status_of_reports ~Basket.get_thematic_beta ~Basket.get_thematic_breakdown ~Basket.get_thematic_exposure ~Basket.get_thematic_report ~Basket.get_top_five_thematic_exposures ~Basket.get_type ~Basket.get_unique_entity_key ~Basket.get_url ~Basket.poll_report ~Basket.poll_status ~Basket.update ~Basket.update_positions ~Basket.upload_position_history .. rubric:: Attributes .. autosummary:: ~Basket.allow_ca_restricted_assets ~Basket.allow_limited_access_assets ~Basket.asset_class ~Basket.backtest_parameters ~Basket.benchmark ~Basket.bloomberg_publish_parameters ~Basket.cash_reinvestment_treatment ~Basket.clone_parent_id ~Basket.currency ~Basket.data_dimension ~Basket.default_backcast ~Basket.description ~Basket.divisor ~Basket.entitlements ~Basket.flagship ~Basket.hedge_id ~Basket.historical_methodology ~Basket.id ~Basket.include_price_history ~Basket.initial_price ~Basket.name ~Basket.parent_basket ~Basket.position_set ~Basket.positioned_entity_type ~Basket.preferred_risk_model ~Basket.publish_to_bloomberg ~Basket.publish_to_factset ~Basket.publish_to_reuters ~Basket.rebalance_calendar ~Basket.return_type ~Basket.reweight ~Basket.target_notional ~Basket.ticker ~Basket.weighting_strategy ================================================ FILE: docs/classes/gs_quant.markets.core.PricingContext.rst ================================================ gs\_quant.markets.core.PricingContext ===================================== .. currentmodule:: gs_quant.markets.core .. autoclass:: PricingContext .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~PricingContext.__init__ ~PricingContext.calc ~PricingContext.clone ~PricingContext.default_value .. rubric:: Attributes .. autosummary:: ~PricingContext.active_context ~PricingContext.csa_term ~PricingContext.is_async ~PricingContext.is_batch ~PricingContext.is_current ~PricingContext.is_entered ~PricingContext.market ~PricingContext.market_behaviour ~PricingContext.market_data_location ~PricingContext.pricing_date ~PricingContext.provider ~PricingContext.request_priority ~PricingContext.set_parameters_only ~PricingContext.show_progress ~PricingContext.timeout ~PricingContext.use_cache ~PricingContext.use_historical_diddles_only ~PricingContext.use_server_cache ~PricingContext.visible_to_gs ================================================ FILE: docs/classes/gs_quant.markets.historical.HistoricalPricingContext.rst ================================================ gs\_quant.markets.historical.HistoricalPricingContext ===================================================== .. currentmodule:: gs_quant.markets.historical .. autoclass:: HistoricalPricingContext .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~HistoricalPricingContext.__init__ ~HistoricalPricingContext.calc ~HistoricalPricingContext.clone ~HistoricalPricingContext.default_value .. rubric:: Attributes .. autosummary:: ~HistoricalPricingContext.active_context ~HistoricalPricingContext.csa_term ~HistoricalPricingContext.date_range ~HistoricalPricingContext.is_async ~HistoricalPricingContext.is_batch ~HistoricalPricingContext.is_current ~HistoricalPricingContext.is_entered ~HistoricalPricingContext.market ~HistoricalPricingContext.market_behaviour ~HistoricalPricingContext.market_data_location ~HistoricalPricingContext.pricing_date ~HistoricalPricingContext.provider ~HistoricalPricingContext.request_priority ~HistoricalPricingContext.set_parameters_only ~HistoricalPricingContext.show_progress ~HistoricalPricingContext.timeout ~HistoricalPricingContext.use_cache ~HistoricalPricingContext.use_historical_diddles_only ~HistoricalPricingContext.use_server_cache ~HistoricalPricingContext.visible_to_gs ================================================ FILE: docs/classes/gs_quant.markets.index.Index.rst ================================================ gs\_quant.markets.index.Index ============================= .. currentmodule:: gs_quant.markets.index .. autoclass:: Index .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~Index.__init__ ~Index.entity_type ~Index.get ~Index.get_all_esg_data ~Index.get_all_thematic_exposures ~Index.get_asset_measures ~Index.get_bottom_five_thematic_exposures ~Index.get_carbon_analytics ~Index.get_carbon_attribution_table ~Index.get_carbon_coverage ~Index.get_carbon_emissions ~Index.get_carbon_emissions_allocation ~Index.get_carbon_sbti_netzero_coverage ~Index.get_close_price_for_date ~Index.get_close_prices ~Index.get_constituent_instruments ~Index.get_constituent_instruments_for_date ~Index.get_constituents ~Index.get_constituents_for_date ~Index.get_currency ~Index.get_data_coordinate ~Index.get_data_series ~Index.get_entitlements ~Index.get_entity ~Index.get_esg_bottom_ten ~Index.get_esg_by_region ~Index.get_esg_by_sector ~Index.get_esg_quintiles ~Index.get_esg_summary ~Index.get_esg_top_ten ~Index.get_factor_risk_report ~Index.get_factor_risk_reports ~Index.get_factor_scenario_analytics ~Index.get_fundamentals ~Index.get_hloc_prices ~Index.get_identifier ~Index.get_identifiers ~Index.get_last_positions_data ~Index.get_latest_close_price ~Index.get_latest_constituent_instruments ~Index.get_latest_constituents ~Index.get_latest_position_set ~Index.get_marquee_id ~Index.get_position_dates ~Index.get_position_set_for_date ~Index.get_position_sets ~Index.get_positions_data ~Index.get_reports ~Index.get_return_type ~Index.get_status_of_reports ~Index.get_thematic_beta ~Index.get_thematic_breakdown ~Index.get_thematic_exposure ~Index.get_thematic_report ~Index.get_top_five_thematic_exposures ~Index.get_type ~Index.get_underlier_attribution ~Index.get_underlier_tree ~Index.get_underlier_weights ~Index.get_unique_entity_key ~Index.get_url ~Index.poll_report ~Index.update_positions ~Index.visualise_tree .. rubric:: Attributes .. autosummary:: ~Index.data_dimension ~Index.id ~Index.positioned_entity_type ================================================ FILE: docs/classes/gs_quant.markets.indices_utils.BasketType.rst ================================================ gs\_quant.markets.indices\_utils.BasketType =========================================== .. currentmodule:: gs_quant.markets.indices_utils .. autoclass:: BasketType .. automethod:: __init__ .. rubric:: Attributes .. autosummary:: ~BasketType.CUSTOM_BASKET ~BasketType.RESEARCH_BASKET ================================================ FILE: docs/classes/gs_quant.markets.indices_utils.CorporateActionType.rst ================================================ gs\_quant.markets.indices\_utils.CorporateActionType ==================================================== .. currentmodule:: gs_quant.markets.indices_utils .. autoclass:: CorporateActionType .. automethod:: __init__ .. rubric:: Attributes .. autosummary:: ~CorporateActionType.ACQUISITION ~CorporateActionType.CASH_DIVIDEND ~CorporateActionType.IDENTIFIER_CHANGE ~CorporateActionType.RIGHTS_ISSUE ~CorporateActionType.SHARE_CHANGE ~CorporateActionType.SPECIAL_DIVIDEND ~CorporateActionType.SPIN_OFF ~CorporateActionType.STOCK_DIVIDEND ~CorporateActionType.STOCK_SPLIT ================================================ FILE: docs/classes/gs_quant.markets.indices_utils.CustomBasketStyles.rst ================================================ gs\_quant.markets.indices\_utils.CustomBasketStyles =================================================== .. currentmodule:: gs_quant.markets.indices_utils .. autoclass:: CustomBasketStyles .. automethod:: __init__ .. rubric:: Attributes .. autosummary:: ~CustomBasketStyles.AD_HOC_DESK_WORK ~CustomBasketStyles.CLIENT_CONSTRUCTED_WRAPPER ~CustomBasketStyles.CONSUMER ~CustomBasketStyles.ENERGY ~CustomBasketStyles.ENHANCED_INDEX_SOLUTIONS ~CustomBasketStyles.ESG ~CustomBasketStyles.FACTORS ~CustomBasketStyles.FINANCIALS ~CustomBasketStyles.FLAGSHIP ~CustomBasketStyles.GEOGRAPHIC ~CustomBasketStyles.GROWTH ~CustomBasketStyles.HEALTHCARE ~CustomBasketStyles.HEDGING ~CustomBasketStyles.INDUSTRIALS ~CustomBasketStyles.MATERIALS ~CustomBasketStyles.MOMENTUM ~CustomBasketStyles.PIPG ~CustomBasketStyles.SECTORS_INDUSTRIES ~CustomBasketStyles.SIZE ~CustomBasketStyles.STRUCTURED_ONE_DELTA ~CustomBasketStyles.THEMATIC ~CustomBasketStyles.TMT ~CustomBasketStyles.UTILITIES ~CustomBasketStyles.VALUE ~CustomBasketStyles.VOLATILITY ================================================ FILE: docs/classes/gs_quant.markets.indices_utils.FundamentalMetricPeriod.rst ================================================ gs\_quant.markets.indices\_utils.FundamentalMetricPeriod ======================================================== .. currentmodule:: gs_quant.markets.indices_utils .. autoclass:: FundamentalMetricPeriod .. automethod:: __init__ .. rubric:: Attributes .. autosummary:: ~FundamentalMetricPeriod.ONE_YEAR ~FundamentalMetricPeriod.THREE_YEARS ~FundamentalMetricPeriod.TWO_YEARS ================================================ FILE: docs/classes/gs_quant.markets.indices_utils.FundamentalMetricPeriodDirection.rst ================================================ gs\_quant.markets.indices\_utils.FundamentalMetricPeriodDirection ================================================================= .. currentmodule:: gs_quant.markets.indices_utils .. autoclass:: FundamentalMetricPeriodDirection .. automethod:: __init__ .. rubric:: Attributes .. autosummary:: ~FundamentalMetricPeriodDirection.FORWARD ~FundamentalMetricPeriodDirection.TRAILING ================================================ FILE: docs/classes/gs_quant.markets.indices_utils.FundamentalsMetrics.rst ================================================ gs\_quant.markets.indices\_utils.FundamentalsMetrics ==================================================== .. currentmodule:: gs_quant.markets.indices_utils .. autoclass:: FundamentalsMetrics .. automethod:: __init__ .. rubric:: Attributes .. autosummary:: ~FundamentalsMetrics.DIVIDEND_YIELD ~FundamentalsMetrics.EARNINGS_PER_SHARE ~FundamentalsMetrics.EARNINGS_PER_SHARE_POSITIVE ~FundamentalsMetrics.NET_DEBT_TO_EBITDA ~FundamentalsMetrics.PRICE_TO_BOOK ~FundamentalsMetrics.PRICE_TO_CASH ~FundamentalsMetrics.PRICE_TO_EARNINGS ~FundamentalsMetrics.PRICE_TO_EARNINGS_POSITIVE ~FundamentalsMetrics.PRICE_TO_SALES ~FundamentalsMetrics.RETURN_ON_EQUITY ~FundamentalsMetrics.SALES_PER_SHARE ================================================ FILE: docs/classes/gs_quant.markets.indices_utils.IndicesDatasets.rst ================================================ gs\_quant.markets.indices\_utils.IndicesDatasets ================================================ .. currentmodule:: gs_quant.markets.indices_utils .. autoclass:: IndicesDatasets .. automethod:: __init__ .. rubric:: Attributes .. autosummary:: ~IndicesDatasets.BASKET_FUNDAMENTALS ~IndicesDatasets.CORPORATE_ACTIONS ~IndicesDatasets.GIRBASKETCONSTITUENTS ~IndicesDatasets.GSBASKETCONSTITUENTS ~IndicesDatasets.GSCB_FLAGSHIP ================================================ FILE: docs/classes/gs_quant.markets.indices_utils.Region.rst ================================================ gs\_quant.markets.indices\_utils.Region ======================================= .. currentmodule:: gs_quant.markets.indices_utils .. autoclass:: Region .. automethod:: __init__ .. rubric:: Attributes .. autosummary:: ~Region.AMERICAS ~Region.ASIA ~Region.EM ~Region.EUROPE ~Region.GLOBAL ================================================ FILE: docs/classes/gs_quant.markets.indices_utils.ResearchBasketStyles.rst ================================================ gs\_quant.markets.indices\_utils.ResearchBasketStyles ===================================================== .. currentmodule:: gs_quant.markets.indices_utils .. autoclass:: ResearchBasketStyles .. automethod:: __init__ .. rubric:: Attributes .. autosummary:: ~ResearchBasketStyles.ASIA_EX_JAPAN ~ResearchBasketStyles.EQUITY_THEMATIC ~ResearchBasketStyles.EUROPE ~ResearchBasketStyles.FUNDAMENTALS ~ResearchBasketStyles.FUND_OWNERSHIP ~ResearchBasketStyles.FX_OIL ~ResearchBasketStyles.GEOGRAPHICAL_EXPOSURE ~ResearchBasketStyles.HEDGE_FUND ~ResearchBasketStyles.IP_FACTORS ~ResearchBasketStyles.JAPAN ~ResearchBasketStyles.MACRO ~ResearchBasketStyles.MACRO_SLICE_STYLES ~ResearchBasketStyles.MUTUAL_FUND ~ResearchBasketStyles.PORTFOLIO_STRATEGY ~ResearchBasketStyles.POSITIONING ~ResearchBasketStyles.RISK_AND_LIQUIDITY ~ResearchBasketStyles.SECTOR ~ResearchBasketStyles.SHAREHOLDER_RETURN ~ResearchBasketStyles.STYLES_THEMES ~ResearchBasketStyles.STYLE_FACTOR_AND_FUNDAMENTAL ~ResearchBasketStyles.TACTICAL_RESEARCH ~ResearchBasketStyles.THEMATIC ~ResearchBasketStyles.US ~ResearchBasketStyles.WAVEFRONTS ~ResearchBasketStyles.WAVEFRONT_COMPONENTS ~ResearchBasketStyles.WAVEFRONT_PAIRS ================================================ FILE: docs/classes/gs_quant.markets.indices_utils.ReturnType.rst ================================================ gs\_quant.markets.indices\_utils.ReturnType =========================================== .. currentmodule:: gs_quant.markets.indices_utils .. autoclass:: ReturnType .. automethod:: __init__ .. rubric:: Attributes .. autosummary:: ~ReturnType.GROSS_RETURN ~ReturnType.PRICE_RETURN ~ReturnType.TOTAL_RETURN ================================================ FILE: docs/classes/gs_quant.markets.indices_utils.WeightingStrategy.rst ================================================ gs\_quant.markets.indices\_utils.WeightingStrategy ================================================== .. currentmodule:: gs_quant.markets.indices_utils .. autoclass:: WeightingStrategy .. automethod:: __init__ .. rubric:: Attributes .. autosummary:: ~WeightingStrategy.EQUAL ~WeightingStrategy.MARKET_CAPITALIZATION ~WeightingStrategy.QUANTITY ~WeightingStrategy.WEIGHT ================================================ FILE: docs/classes/gs_quant.markets.portfolio.Grid.rst ================================================ gs\_quant.markets.portfolio.Grid ================================ .. currentmodule:: gs_quant.markets.portfolio .. autoclass:: Grid .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~Grid.__init__ ~Grid.append ~Grid.as_dict ~Grid.calc ~Grid.clone ~Grid.default_instance ~Grid.dollar_price ~Grid.extend ~Grid.from_asset_id ~Grid.from_asset_name ~Grid.from_book ~Grid.from_csv ~Grid.from_dict ~Grid.from_eti ~Grid.from_frame ~Grid.from_instance ~Grid.from_json ~Grid.from_portfolio_id ~Grid.from_portfolio_name ~Grid.from_quote ~Grid.get ~Grid.market ~Grid.paths ~Grid.pop ~Grid.price ~Grid.properties ~Grid.properties_init ~Grid.resolve ~Grid.save ~Grid.save_as_quote ~Grid.save_to_shadowbook ~Grid.scale ~Grid.schema ~Grid.subset ~Grid.to_csv ~Grid.to_dict ~Grid.to_frame ~Grid.to_json .. rubric:: Attributes .. autosummary:: ~Grid.all_instruments ~Grid.all_paths ~Grid.all_portfolios ~Grid.id ~Grid.instruments ~Grid.portfolios ~Grid.priceables ~Grid.quote_id ================================================ FILE: docs/classes/gs_quant.markets.portfolio.Portfolio.rst ================================================ gs\_quant.markets.portfolio.Portfolio ===================================== .. currentmodule:: gs_quant.markets.portfolio .. autoclass:: Portfolio .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~Portfolio.__init__ ~Portfolio.append ~Portfolio.as_dict ~Portfolio.calc ~Portfolio.clone ~Portfolio.default_instance ~Portfolio.dollar_price ~Portfolio.extend ~Portfolio.from_asset_id ~Portfolio.from_asset_name ~Portfolio.from_book ~Portfolio.from_csv ~Portfolio.from_dict ~Portfolio.from_eti ~Portfolio.from_frame ~Portfolio.from_instance ~Portfolio.from_json ~Portfolio.from_portfolio_id ~Portfolio.from_portfolio_name ~Portfolio.from_quote ~Portfolio.get ~Portfolio.market ~Portfolio.paths ~Portfolio.pop ~Portfolio.price ~Portfolio.properties ~Portfolio.properties_init ~Portfolio.resolve ~Portfolio.save ~Portfolio.save_as_quote ~Portfolio.save_to_shadowbook ~Portfolio.scale ~Portfolio.schema ~Portfolio.subset ~Portfolio.to_csv ~Portfolio.to_dict ~Portfolio.to_frame ~Portfolio.to_json .. rubric:: Attributes .. autosummary:: ~Portfolio.all_instruments ~Portfolio.all_paths ~Portfolio.all_portfolios ~Portfolio.id ~Portfolio.instruments ~Portfolio.portfolios ~Portfolio.priceables ~Portfolio.quote_id ================================================ FILE: docs/classes/gs_quant.markets.portfolio_manager.PortfolioManager.rst ================================================ PortfolioManager ================ .. currentmodule:: gs_quant.markets.portfolio_manager .. autoclass:: PortfolioManager .. rubric:: Methods .. automethod:: __init__ .. automethod:: get_all_esg_data .. automethod:: get_all_fund_of_fund_tags .. automethod:: get_all_thematic_exposures .. automethod:: get_aum .. automethod:: get_aum_source .. automethod:: get_bottom_five_thematic_exposures .. automethod:: get_carbon_analytics .. automethod:: get_carbon_attribution_table .. automethod:: get_carbon_coverage .. automethod:: get_carbon_emissions .. automethod:: get_carbon_emissions_allocation .. automethod:: get_carbon_sbti_netzero_coverage .. automethod:: get_custom_aum .. automethod:: get_entitlements .. automethod:: get_esg_bottom_ten .. automethod:: get_esg_by_region .. automethod:: get_esg_by_sector .. automethod:: get_esg_quintiles .. automethod:: get_esg_summary .. automethod:: get_esg_top_ten .. automethod:: get_factor_risk_report .. automethod:: get_factor_risk_reports .. automethod:: get_factor_scenario_analytics .. automethod:: get_last_positions_data .. automethod:: get_latest_position_set .. automethod:: get_macro_exposure .. automethod:: get_performance_report .. automethod:: get_pnl_contribution .. automethod:: get_position_dates .. automethod:: get_position_set_for_date .. automethod:: get_position_sets .. automethod:: get_positions_data .. automethod:: get_reports .. automethod:: get_schedule_dates .. automethod:: get_status_of_reports .. automethod:: get_tag_name_hierarchy .. automethod:: get_thematic_beta .. automethod:: get_thematic_breakdown .. automethod:: get_thematic_exposure .. automethod:: get_thematic_report .. automethod:: get_top_five_thematic_exposures .. automethod:: poll_report .. automethod:: run_reports .. automethod:: schedule_reports .. automethod:: set_aum_source .. automethod:: set_currency .. automethod:: set_entitlements .. automethod:: set_tag_name_hierarchy .. automethod:: share .. automethod:: update_portfolio_tree .. automethod:: update_positions .. automethod:: upload_custom_aum ================================================ FILE: docs/classes/gs_quant.markets.position_set.Position.rst ================================================ gs\_quant.markets.position\_set.Position ======================================== .. currentmodule:: gs_quant.markets.position_set .. autoclass:: Position .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~Position.__init__ ~Position.as_dict ~Position.to_target .. rubric:: Attributes .. autosummary:: ~Position.asset_id ~Position.hard_to_borrow ~Position.identifier ~Position.name ~Position.quantity ~Position.restricted ~Position.tags ~Position.weight ================================================ FILE: docs/classes/gs_quant.markets.position_set.PositionSet.rst ================================================ gs\_quant.markets.position\_set.PositionSet =========================================== .. currentmodule:: gs_quant.markets.position_set .. autoclass:: PositionSet .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~PositionSet.__init__ ~PositionSet.equalize_position_weights ~PositionSet.from_dicts ~PositionSet.from_frame ~PositionSet.from_list ~PositionSet.from_target ~PositionSet.get_hard_to_borrow_positions ~PositionSet.get_positions ~PositionSet.get_restricted_positions ~PositionSet.get_unpriced_positions ~PositionSet.get_unresolved_positions ~PositionSet.price ~PositionSet.price_many ~PositionSet.redistribute_weights ~PositionSet.remove_hard_to_borrow_positions ~PositionSet.remove_restricted_positions ~PositionSet.remove_unpriced_positions ~PositionSet.remove_unresolved_positions ~PositionSet.resolve ~PositionSet.resolve_many ~PositionSet.to_frame ~PositionSet.to_frame_many ~PositionSet.to_target .. rubric:: Attributes .. autosummary:: ~PositionSet.date ~PositionSet.divisor ~PositionSet.positions ~PositionSet.reference_notional ~PositionSet.unpriced_positions ~PositionSet.unresolved_positions ================================================ FILE: docs/classes/gs_quant.markets.report.FactorRiskReport.rst ================================================ FactorRiskReport ================ .. currentmodule:: gs_quant.markets.report .. autoclass:: FactorRiskReport .. rubric:: Methods .. automethod:: __init__ .. automethod:: delete .. automethod:: from_target .. automethod:: get .. automethod:: get_annual_risk .. automethod:: get_benchmark_id .. automethod:: get_daily_risk .. automethod:: get_ex_ante_var .. automethod:: get_factor_exposure .. automethod:: get_factor_pnl .. automethod:: get_factor_proportion_of_risk .. automethod:: get_most_recent_job .. automethod:: get_results .. automethod:: get_risk_model_id .. automethod:: get_table .. automethod:: get_view .. automethod:: run .. automethod:: save .. automethod:: schedule .. automethod:: set_position_source ================================================ FILE: docs/classes/gs_quant.markets.report.PerformanceReport.rst ================================================ PerformanceReport ================= .. currentmodule:: gs_quant.markets.report .. autoclass:: PerformanceReport .. rubric:: Methods .. automethod:: __init__ .. automethod:: delete .. automethod:: from_target .. automethod:: get .. automethod:: get_asset_count .. automethod:: get_asset_count_long .. automethod:: get_asset_count_priced .. automethod:: get_asset_count_short .. automethod:: get_aum .. automethod:: get_aum_source .. automethod:: get_brinson_attribution .. automethod:: get_custom_aum .. automethod:: get_gross_exposure .. automethod:: get_long_exposure .. automethod:: get_many_measures .. automethod:: get_measure .. automethod:: get_most_recent_job .. automethod:: get_net_exposure .. automethod:: get_pnl .. automethod:: get_pnl_contribution .. automethod:: get_pnl_measure .. automethod:: get_portfolio_constituents .. automethod:: get_positions_data .. automethod:: get_servicing_cost_long_pnl .. automethod:: get_servicing_cost_short_pnl .. automethod:: get_short_exposure .. automethod:: get_trading_cost_pnl .. automethod:: get_trading_pnl .. automethod:: get_turnover .. automethod:: run .. automethod:: save .. automethod:: schedule .. automethod:: set_aum_source .. automethod:: set_position_source .. automethod:: upload_custom_aum ================================================ FILE: docs/classes/gs_quant.markets.report.Report.rst ================================================ Report ====== .. currentmodule:: gs_quant.markets.report .. autoclass:: Report .. rubric:: Methods .. automethod:: __init__ .. automethod:: delete .. automethod:: from_target .. automethod:: get .. automethod:: get_most_recent_job .. automethod:: run .. automethod:: save .. automethod:: schedule .. automethod:: set_position_source ================================================ FILE: docs/classes/gs_quant.markets.report.ReportJobFuture.rst ================================================ ReportJobFuture =============== .. currentmodule:: gs_quant.markets.report .. autoclass:: ReportJobFuture .. rubric:: Methods .. automethod:: __init__ .. automethod:: done .. automethod:: result .. automethod:: status ================================================ FILE: docs/classes/gs_quant.markets.report.ThematicReport.rst ================================================ ThematicReport ============== .. currentmodule:: gs_quant.markets.report .. autoclass:: ThematicReport .. rubric:: Methods .. automethod:: __init__ .. automethod:: delete .. automethod:: from_target .. automethod:: get .. automethod:: get_all_thematic_exposures .. automethod:: get_bottom_five_thematic_exposures .. automethod:: get_most_recent_job .. automethod:: get_thematic_betas .. automethod:: get_thematic_breakdown .. automethod:: get_thematic_data .. automethod:: get_thematic_exposure .. automethod:: get_top_five_thematic_exposures .. automethod:: run .. automethod:: save .. automethod:: schedule .. automethod:: set_position_source ================================================ FILE: docs/classes/gs_quant.markets.securities.Asset.rst ================================================ gs\_quant.markets.securities.Asset ================================== .. currentmodule:: gs_quant.markets.securities .. autoclass:: Asset .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~Asset.__init__ ~Asset.entity_type ~Asset.get ~Asset.get_asset_measures ~Asset.get_close_price_for_date ~Asset.get_close_prices ~Asset.get_data_coordinate ~Asset.get_data_series ~Asset.get_entitlements ~Asset.get_entity ~Asset.get_hloc_prices ~Asset.get_identifier ~Asset.get_identifiers ~Asset.get_latest_close_price ~Asset.get_marquee_id ~Asset.get_type ~Asset.get_unique_entity_key ~Asset.get_url .. rubric:: Attributes .. autosummary:: ~Asset.data_dimension ================================================ FILE: docs/classes/gs_quant.markets.securities.AssetClass.rst ================================================ gs\_quant.markets.securities.AssetClass ======================================= .. currentmodule:: gs_quant.markets.securities .. autoclass:: AssetClass .. automethod:: __init__ .. rubric:: Attributes .. autosummary:: ~AssetClass.Cash ~AssetClass.Commod ~AssetClass.Credit ~AssetClass.Cross_Asset ~AssetClass.Digital_Asset ~AssetClass.Econ ~AssetClass.Equity ~AssetClass.Fund ~AssetClass.FX ~AssetClass.Mortgage ~AssetClass.Rates ~AssetClass.Repo ~AssetClass.Loan ~AssetClass.Social ~AssetClass.Cryptocurrency ================================================ FILE: docs/classes/gs_quant.markets.securities.AssetIdentifier.rst ================================================ gs\_quant.markets.securities.AssetIdentifier ============================================ .. currentmodule:: gs_quant.markets.securities .. autoclass:: AssetIdentifier .. automethod:: __init__ .. rubric:: Attributes .. autosummary:: ~AssetIdentifier.MARQUEE_ID ~AssetIdentifier.REUTERS_ID ~AssetIdentifier.BLOOMBERG_ID ~AssetIdentifier.BLOOMBERG_COMPOSITE_ID ~AssetIdentifier.CUSIP ~AssetIdentifier.ISIN ~AssetIdentifier.SEDOL ~AssetIdentifier.TICKER ~AssetIdentifier.PLOT_ID ~AssetIdentifier.GSID ================================================ FILE: docs/classes/gs_quant.markets.securities.AssetType.rst ================================================ gs\_quant.markets.securities.AssetType ====================================== .. currentmodule:: gs_quant.markets.securities .. autoclass:: AssetType .. automethod:: __init__ .. rubric:: Attributes .. autosummary:: ~AssetType.INDEX ~AssetType.ETF ~AssetType.CUSTOM_BASKET ~AssetType.RESEARCH_BASKET ~AssetType.STOCK ~AssetType.FUTURE ~AssetType.CROSS ~AssetType.CURRENCY ~AssetType.RATE ~AssetType.CASH ~AssetType.WEATHER_INDEX ~AssetType.SWAP ~AssetType.SWAPTION ~AssetType.OPTION ~AssetType.BINARY ~AssetType.COMMODITY_REFERENCE_PRICE ~AssetType.COMMODITY_NATURAL_GAS_HUB ~AssetType.COMMODITY_EU_NATURAL_GAS_HUB ~AssetType.COMMODITY_POWER_NODE ~AssetType.COMMODITY_POWER_AGGREGATED_NODES ~AssetType.BOND ~AssetType.FUTURE_MARKET ~AssetType.FUTURE_CONTRACT ~AssetType.COMMODITY ~AssetType.CRYPTOCURRENCY ~AssetType.FORWARD ~AssetType.FUND ~AssetType.DEFAULT_SWAP ~AssetType.SYSTEMATIC_HEDGING ~AssetType.ACCESS ~AssetType.RISK_PREMIA ~AssetType.MULTI_ASSET_ALLOCATION ~AssetType.ADR ~AssetType.GDR ~AssetType.DUTCH_CERT ~AssetType.NYRS ~AssetType.RECEIPT ~AssetType.UNIT ~AssetType.MUTUAL_FUND ~AssetType.RIGHT ~AssetType.PREFERRED ~AssetType.MISC ~AssetType.REIT ~AssetType.PRIVATE_COMP ~AssetType.PREFERENCE ~AssetType.LIMITED_PARTNERSHIP ~AssetType.TRACKING_STOCK ~AssetType.ROYALTY_TRUST ~AssetType.CLOSED_END_FUND ~AssetType.OPEN_END_FUND ~AssetType.FUND_OF_FUNDS ~AssetType.MLP ~AssetType.STAPLED_SECURITY ~AssetType.SAVINGS_SHARE ~AssetType.EQUITY_WRT ~AssetType.SAVINGS_PLAN ~AssetType.EQUITY_INDEX ~AssetType.COMMON_STOCK ================================================ FILE: docs/classes/gs_quant.markets.securities.SecurityMaster.rst ================================================ gs\_quant.markets.securities.SecurityMaster =========================================== .. currentmodule:: gs_quant.markets.securities .. autoclass:: SecurityMaster .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~SecurityMaster.__init__ ~SecurityMaster.asset_type_to_str ~SecurityMaster.get_all_identifiers ~SecurityMaster.get_all_identifiers_gen ~SecurityMaster.get_asset ~SecurityMaster.get_asset_async ~SecurityMaster.get_identifiers ~SecurityMaster.get_many_assets ~SecurityMaster.get_many_assets_async ~SecurityMaster.map_identifiers ~SecurityMaster.set_source ================================================ FILE: docs/classes/gs_quant.markets.securities.Stock.rst ================================================ gs\_quant.markets.securities.Stock ================================== .. currentmodule:: gs_quant.markets.securities .. autoclass:: Stock .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~Stock.__init__ ~Stock.entity_type ~Stock.get ~Stock.get_asset_measures ~Stock.get_close_price_for_date ~Stock.get_close_prices ~Stock.get_currency ~Stock.get_data_coordinate ~Stock.get_data_series ~Stock.get_entitlements ~Stock.get_entity ~Stock.get_hloc_prices ~Stock.get_identifier ~Stock.get_identifiers ~Stock.get_latest_close_price ~Stock.get_marquee_id ~Stock.get_thematic_beta ~Stock.get_type ~Stock.get_unique_entity_key ~Stock.get_url .. rubric:: Attributes .. autosummary:: ~Stock.data_dimension ================================================ FILE: docs/classes/gs_quant.models.epidemiology.EpidemicModel.rst ================================================ gs\_quant.models.epidemiology.EpidemicModel =========================================== .. currentmodule:: gs_quant.models.epidemiology .. autoclass:: EpidemicModel .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~EpidemicModel.__init__ ~EpidemicModel.fit ~EpidemicModel.residual ~EpidemicModel.solve ================================================ FILE: docs/classes/gs_quant.models.epidemiology.SEIR.rst ================================================ gs\_quant.models.epidemiology.SEIR ================================== .. currentmodule:: gs_quant.models.epidemiology .. autoclass:: SEIR .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~SEIR.__init__ ~SEIR.calibrate ~SEIR.get_parameters ================================================ FILE: docs/classes/gs_quant.models.epidemiology.SIR.rst ================================================ gs\_quant.models.epidemiology.SIR ================================= .. currentmodule:: gs_quant.models.epidemiology .. autoclass:: SIR .. automethod:: __init__ .. rubric:: Methods .. autosummary:: ~SIR.__init__ ~SIR.calibrate ~SIR.get_parameters ================================================ FILE: docs/classes/gs_quant.models.risk_model.FactorRiskModel.rst ================================================ FactorRiskModel =============== .. currentmodule:: gs_quant.models.risk_model .. autoclass:: FactorRiskModel .. rubric:: Methods .. automethod:: __init__ .. automethod:: delete .. automethod:: delete_factor_metadata .. automethod:: from_many_targets .. automethod:: from_target .. automethod:: get .. automethod:: get_asset_capitalization .. automethod:: get_asset_contribution_to_risk .. automethod:: get_asset_factor_attribution .. automethod:: get_asset_price .. automethod:: get_asset_universe .. automethod:: get_bid_ask_spread .. automethod:: get_calendar .. automethod:: get_composite_value .. automethod:: get_composite_volume .. automethod:: get_covariance_matrix .. automethod:: get_currency .. automethod:: get_currency_exchange_rate .. automethod:: get_daily_return .. automethod:: get_data .. automethod:: get_dates .. automethod:: get_dividend_yield .. automethod:: get_estimation_universe_weights .. automethod:: get_factor .. automethod:: get_factor_data .. automethod:: get_factor_portfolios .. automethod:: get_factor_returns_by_id .. automethod:: get_factor_returns_by_name .. automethod:: get_factor_volatility .. automethod:: get_global_predicted_beta .. automethod:: get_historical_beta .. automethod:: get_issuer_market_cap .. automethod:: get_issuer_specific_covariance .. automethod:: get_many .. automethod:: get_many_factors .. automethod:: get_missing_dates .. automethod:: get_most_recent_date_from_calendar .. automethod:: get_pre_vra_covariance_matrix .. automethod:: get_predicted_beta .. automethod:: get_risk_free_rate .. automethod:: get_specific_return .. automethod:: get_specific_risk .. automethod:: get_total_risk .. automethod:: get_traded_value .. automethod:: get_trading_volume .. automethod:: get_unadjusted_covariance_matrix .. automethod:: get_unadjusted_specific_risk .. automethod:: get_universe_exposure .. automethod:: get_universe_factor_exposure .. automethod:: save .. automethod:: save_factor_metadata .. automethod:: upload_asset_coverage_data .. automethod:: upload_calendar .. automethod:: upload_data .. automethod:: upload_partial_data ================================================ FILE: docs/classes/gs_quant.models.risk_model.MacroRiskModel.rst ================================================ MacroRiskModel ============== .. currentmodule:: gs_quant.models.risk_model .. autoclass:: MacroRiskModel .. rubric:: Methods .. automethod:: __init__ .. automethod:: delete .. automethod:: delete_factor_metadata .. automethod:: from_many_targets .. automethod:: from_target .. automethod:: get .. automethod:: get_asset_universe .. automethod:: get_calendar .. automethod:: get_data .. automethod:: get_dates .. automethod:: get_factor .. automethod:: get_factor_data .. automethod:: get_factor_returns_by_id .. automethod:: get_factor_returns_by_name .. automethod:: get_factor_standard_deviation .. automethod:: get_factor_z_score .. automethod:: get_fair_value_gap .. automethod:: get_many .. automethod:: get_many_factors .. automethod:: get_missing_dates .. automethod:: get_model_price .. automethod:: get_most_recent_date_from_calendar .. automethod:: get_r_squared .. automethod:: get_specific_risk .. automethod:: get_universe_exposure .. automethod:: get_universe_sensitivity .. automethod:: save .. automethod:: save_factor_metadata .. automethod:: upload_asset_coverage_data .. automethod:: upload_calendar .. automethod:: upload_data .. automethod:: upload_partial_data ================================================ FILE: docs/classes/gs_quant.models.risk_model.MarqueeRiskModel.rst ================================================ MarqueeRiskModel ================ .. currentmodule:: gs_quant.models.risk_model .. autoclass:: MarqueeRiskModel .. rubric:: Methods .. automethod:: __init__ .. automethod:: delete .. automethod:: delete_factor_metadata .. automethod:: from_many_targets .. automethod:: from_target .. automethod:: get .. automethod:: get_asset_universe .. automethod:: get_calendar .. automethod:: get_data .. automethod:: get_dates .. automethod:: get_factor .. automethod:: get_factor_data .. automethod:: get_factor_returns_by_id .. automethod:: get_factor_returns_by_name .. automethod:: get_many_factors .. automethod:: get_missing_dates .. automethod:: get_most_recent_date_from_calendar .. automethod:: get_specific_risk .. automethod:: get_universe_exposure .. automethod:: save .. automethod:: save_factor_metadata .. automethod:: upload_asset_coverage_data .. automethod:: upload_calendar .. automethod:: upload_data .. automethod:: upload_partial_data ================================================ FILE: docs/classes/gs_quant.models.risk_model.RiskModel.rst ================================================ RiskModel ========= .. currentmodule:: gs_quant.models.risk_model .. autoclass:: RiskModel .. rubric:: Methods .. automethod:: __init__ ================================================ FILE: docs/classes/gs_quant.models.risk_model.ThematicRiskModel.rst ================================================ ThematicRiskModel ================= .. currentmodule:: gs_quant.models.risk_model .. autoclass:: ThematicRiskModel .. rubric:: Methods .. automethod:: __init__ .. automethod:: delete .. automethod:: delete_factor_metadata .. automethod:: from_many_targets .. automethod:: from_target .. automethod:: get .. automethod:: get_asset_universe .. automethod:: get_calendar .. automethod:: get_data .. automethod:: get_dates .. automethod:: get_factor .. automethod:: get_factor_data .. automethod:: get_factor_returns_by_id .. automethod:: get_factor_returns_by_name .. automethod:: get_many .. automethod:: get_many_factors .. automethod:: get_missing_dates .. automethod:: get_most_recent_date_from_calendar .. automethod:: get_specific_risk .. automethod:: get_universe_exposure .. automethod:: get_universe_sensitivity .. automethod:: save .. automethod:: save_factor_metadata .. automethod:: upload_asset_coverage_data .. automethod:: upload_calendar .. automethod:: upload_data .. automethod:: upload_partial_data ================================================ FILE: docs/classes/gs_quant.timeseries.backtesting.Basket.rst ================================================ gs\_quant.timeseries.backtesting.Basket ======================================= .. currentmodule:: gs_quant.timeseries.backtesting .. autoclass:: Basket .. rubric:: Methods .. autosummary:: ~Basket.average_forward_vol ~Basket.average_implied_volatility ~Basket.average_realized_correlation ~Basket.average_realized_volatility ~Basket.get_actual_weights ~Basket.get_marquee_ids ~Basket.get_returns ~Basket.get_spot_data ~Basket.price ================================================ FILE: docs/classes/gs_quant.timeseries.datetime.Window.rst ================================================ gs\_quant.timeseries.datetime.Window ==================================== .. currentmodule:: gs_quant.timeseries.datetime .. autoclass:: Window .. rubric:: Methods .. autosummary:: ~Window.as_dict ~Window.from_dict ================================================ FILE: docs/classes/gs_quant.timeseries.statistics.LinearRegression.rst ================================================ gs\_quant.timeseries.statistics.LinearRegression ================================================ .. currentmodule:: gs_quant.timeseries.statistics .. autoclass:: LinearRegression .. rubric:: Methods .. autosummary:: ~LinearRegression.coefficient ~LinearRegression.fitted_values ~LinearRegression.predict ~LinearRegression.r_squared ~LinearRegression.standard_deviation_of_errors ================================================ FILE: docs/classes/gs_quant.timeseries.statistics.RollingLinearRegression.rst ================================================ gs\_quant.timeseries.statistics.RollingLinearRegression ======================================================= .. currentmodule:: gs_quant.timeseries.statistics .. autoclass:: RollingLinearRegression .. rubric:: Methods .. autosummary:: ~RollingLinearRegression.coefficient ~RollingLinearRegression.fitted_values ~RollingLinearRegression.r_squared ~RollingLinearRegression.standard_deviation_of_errors ================================================ FILE: docs/classes/gs_quant.timeseries.statistics.SEIRModel.rst ================================================ gs\_quant.timeseries.statistics.SEIRModel ========================================= .. currentmodule:: gs_quant.timeseries.statistics .. autoclass:: SEIRModel .. rubric:: Methods .. autosummary:: ~SEIRModel.beta ~SEIRModel.e0 ~SEIRModel.gamma ~SEIRModel.i0 ~SEIRModel.predict_e ~SEIRModel.predict_i ~SEIRModel.predict_r ~SEIRModel.predict_s ~SEIRModel.r0 ~SEIRModel.s0 ~SEIRModel.sigma ================================================ FILE: docs/classes/gs_quant.timeseries.statistics.SIRModel.rst ================================================ gs\_quant.timeseries.statistics.SIRModel ======================================== .. currentmodule:: gs_quant.timeseries.statistics .. autoclass:: SIRModel .. rubric:: Methods .. autosummary:: ~SIRModel.beta ~SIRModel.gamma ~SIRModel.i0 ~SIRModel.predict_i ~SIRModel.predict_r ~SIRModel.predict_s ~SIRModel.r0 ~SIRModel.s0 ================================================ FILE: docs/conf.py ================================================ # -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. # # This file does only contain a selection of the most common options. For a # full list see the documentation: # http://www.sphinx-doc.org/en/master/config # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import os import sys sys.path.insert(0, os.path.abspath('.')) # -- Project information ----------------------------------------------------- project = 'gs_quant' copyright = '2019, Goldman Sachs' author = 'Andy Phillips' # The short X.Y version version = '' # The full version, including alpha/beta/rc tags release = '0.1' # -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'searchdataext', 'sphinx.ext.autodoc', 'sphinx.ext.napoleon', # 'sphinx.ext.intersphinx', 'sphinx.ext.mathjax', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode', 'sphinx.ext.autosummary', 'sphinx_autodoc_typehints', ] # Do not show packages in function names add_module_names = False # Generate function doc pages automatically autosummary_generate = True # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The master toctree document. master_doc = 'index' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = 'en' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = None # -- Options for HTML output ------------------------------------------------- # A string with the fully-qualified name of a HTML Translator class, that is, # a subclass of Sphinx’s HTMLTranslator, that is used to translate document # trees to HTML. Default is None (use the builtin translator). # html_translator_class = './_writers/html5' # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # # html_theme = 'sphinx_rtd_theme' html_theme = 'gs' html_theme_path = ['./_themes'] # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = { 'nosidebar': 'false', 'body_min_width': '0', 'body_max_width': '100%', } # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # The default sidebars (for documents that don't match any pattern) are # defined by theme itself. Builtin themes are using these templates by # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', # 'searchbox.html']``. # # html_sidebars = {} # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. htmlhelp_basename = 'gs_quantdoc' # -- Options for LaTeX output ------------------------------------------------ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'gs_quant.tex', 'gs\\_quant Documentation Formatting', 'Andy Phillips', 'manual'), ] # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [(master_doc, 'gs_quant', 'gs_quant Documentation Formatting', [author], 1)] # -- Options for Texinfo output ---------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ( master_doc, 'gs_quant', 'gs_quant Documentation Formatting', author, 'gs_quant', 'One line description of project.', 'Miscellaneous', ), ] # -- Options for Epub output ------------------------------------------------- # Bibliographic Dublin Core info. epub_title = project # The unique identifier of the text. This can be a ISBN number # or the project homepage. # # epub_identifier = '' # A unique identification for the text. # # epub_uid = '' # A list of files that should not be packed into the epub file. epub_exclude_files = ['search.html'] # -- Extension configuration ------------------------------------------------- mathjax_path = '/resources/mathjax/latest.js?config=TeX-AMS-MML_HTMLorMML' # -- Options for intersphinx extension --------------------------------------- # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'https://docs.python.org/': None} ================================================ FILE: docs/data.rst ================================================ Data Package ============ DataContext ----------- .. currentmodule:: gs_quant.data.core .. autosummary:: :toctree: classes DataContext Dataset ------- .. currentmodule:: gs_quant.data.dataset .. autosummary:: :toctree: classes Dataset Fields ------ .. currentmodule:: gs_quant.data.fields .. autosummary:: :toctree: classes Fields ================================================ FILE: docs/datetime.rst ================================================ Datetime Package ================ Date ---- .. currentmodule:: gs_quant.datetime.date .. autosummary:: :toctree: functions business_day_count business_day_offset date_range is_business_day prev_business_date Point ----- .. currentmodule:: gs_quant.datetime.point .. autosummary:: :toctree: functions point_sort_order RelativeDate ----- .. currentmodule:: gs_quant.datetime.relative_date .. autosummary:: :toctree: classes :template: timeseries_class.rst RelativeDate ================================================ FILE: docs/entities.rst ================================================ Entities Package =============== PositionedEntity ---------------- .. currentmodule:: gs_quant.entities.entity .. autosummary:: :toctree: classes :template: report.rst PositionedEntity ================================================ FILE: docs/functions/gs_quant.datetime.date.business_day_count.rst ================================================ gs\_quant.datetime.date.business\_day\_count ============================================ .. currentmodule:: gs_quant.datetime.date .. autofunction:: business_day_count ================================================ FILE: docs/functions/gs_quant.datetime.date.business_day_offset.rst ================================================ gs\_quant.datetime.date.business\_day\_offset ============================================= .. currentmodule:: gs_quant.datetime.date .. autofunction:: business_day_offset ================================================ FILE: docs/functions/gs_quant.datetime.date.date_range.rst ================================================ gs\_quant.datetime.date.date\_range =================================== .. currentmodule:: gs_quant.datetime.date .. autofunction:: date_range ================================================ FILE: docs/functions/gs_quant.datetime.date.is_business_day.rst ================================================ gs\_quant.datetime.date.is\_business\_day ========================================= .. currentmodule:: gs_quant.datetime.date .. autofunction:: is_business_day ================================================ FILE: docs/functions/gs_quant.datetime.date.prev_business_date.rst ================================================ gs\_quant.datetime.date.prev\_business\_date ============================================ .. currentmodule:: gs_quant.datetime.date .. autofunction:: prev_business_date ================================================ FILE: docs/functions/gs_quant.datetime.point.point_sort_order.rst ================================================ gs\_quant.datetime.point.point\_sort\_order =========================================== .. currentmodule:: gs_quant.datetime.point .. autofunction:: point_sort_order ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.add_factor_risk_report.rst ================================================ gs\_quant.markets.baskets.Basket.add\_factor\_risk\_report ============================================================= .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.add_factor_risk_report ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.cancel_rebalance.rst ================================================ gs\_quant.markets.baskets.Basket.cancel\_rebalance ================================================== .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.cancel_rebalance ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.clone.rst ================================================ gs\_quant.markets.baskets.Basket.clone ====================================== .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.clone ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.create.rst ================================================ gs\_quant.markets.baskets.Basket.create ======================================= .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.create ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.delete_factor_risk_report.rst ================================================ gs\_quant.markets.baskets.Basket.delete\_factor\_risk\_report ============================================================= .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.delete_factor_risk_report ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.entity_type.rst ================================================ gs\_quant.markets.baskets.Basket.entity\_type ============================================= .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.entity_type ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.get.rst ================================================ gs\_quant.markets.baskets.Basket.get ==================================== .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.get ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.get_close_price_for_date.rst ================================================ gs\_quant.markets.baskets.Basket.get\_close\_price\_for\_date ============================================================= .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.get_close_price_for_date ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.get_close_prices.rst ================================================ gs\_quant.markets.baskets.Basket.get\_close\_prices =================================================== .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.get_close_prices ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.get_corporate_actions.rst ================================================ gs\_quant.markets.baskets.Basket.get\_corporate\_actions ======================================================== .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.get_corporate_actions ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.get_data_coordinate.rst ================================================ gs\_quant.markets.baskets.Basket.get\_data\_coordinate ====================================================== .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.get_data_coordinate ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.get_data_series.rst ================================================ gs\_quant.markets.baskets.Basket.get\_data\_series ================================================== .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.get_data_series ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.get_details.rst ================================================ gs\_quant.markets.baskets.Basket.get\_details ============================================= .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.get_details ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.get_entity.rst ================================================ gs\_quant.markets.baskets.Basket.get\_entity ============================================ .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.get_entity ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.get_fundamentals.rst ================================================ gs\_quant.markets.baskets.Basket.get\_fundamentals ================================================== .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.get_fundamentals ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.get_identifier.rst ================================================ gs\_quant.markets.baskets.Basket.get\_identifier ================================================ .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.get_identifier ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.get_identifiers.rst ================================================ gs\_quant.markets.baskets.Basket.get\_identifiers ================================================= .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.get_identifiers ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.get_latest_close_price.rst ================================================ gs\_quant.markets.baskets.Basket.get\_latest\_close\_price ========================================================== .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.get_latest_close_price ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.get_latest_position_set.rst ================================================ gs\_quant.markets.baskets.Basket.get\_latest\_position\_set =========================================================== .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.get_latest_position_set ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.get_latest_rebalance_data.rst ================================================ gs\_quant.markets.baskets.Basket.get\_latest\_rebalance\_data ============================================================= .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.get_latest_rebalance_data ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.get_latest_rebalance_date.rst ================================================ gs\_quant.markets.baskets.Basket.get\_latest\_rebalance\_date ============================================================= .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.get_latest_rebalance_date ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.get_live_date.rst ================================================ gs\_quant.markets.baskets.Basket.get\_live\_date ================================================ .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.get_live_date ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.get_marquee_id.rst ================================================ gs\_quant.markets.baskets.Basket.get\_marquee\_id ================================================= .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.get_marquee_id ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.get_position_set_for_date.rst ================================================ gs\_quant.markets.baskets.Basket.get\_position\_set\_for\_date ============================================================== .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.get_position_set_for_date ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.get_position_sets.rst ================================================ gs\_quant.markets.baskets.Basket.get\_position\_sets ==================================================== .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.get_position_sets ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.get_positions_data.rst ================================================ gs\_quant.markets.baskets.Basket.get\_positions\_data ===================================================== .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.get_positions_data ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.get_rebalance_approval_status.rst ================================================ gs\_quant.markets.baskets.Basket.get\_rebalance\_approval\_status ================================================================= .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.get_rebalance_approval_status ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.get_type.rst ================================================ gs\_quant.markets.baskets.Basket.get\_type ========================================== .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.get_type ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.get_unique_entity_key.rst ================================================ gs\_quant.markets.baskets.Basket.get\_unique\_entity\_key ========================================================= .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.get_unique_entity_key ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.get_url.rst ================================================ gs\_quant.markets.baskets.Basket.get\_url ========================================= .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.get_url ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.poll_report.rst ================================================ gs\_quant.markets.baskets.Basket.poll\_report ============================================= .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.poll_report ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.poll_status.rst ================================================ gs\_quant.markets.baskets.Basket.poll\_status ============================================= .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.poll_status ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.update.rst ================================================ gs\_quant.markets.baskets.Basket.update ======================================= .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.update ================================================ FILE: docs/functions/gs_quant.markets.baskets.Basket.upload_position_history.rst ================================================ gs\_quant.markets.baskets.Basket.upload\_position\_history ========================================================== .. currentmodule:: gs_quant.markets.baskets .. automethod:: Basket.upload_position_history ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get.rst ================================================ gs\_quant.markets.index.Index.get ================================= .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_close_price_for_date.rst ================================================ gs\_quant.markets.index.Index.get_close_price_for_date ====================================================== .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_close_price_for_date ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_close_prices.rst ================================================ gs\_quant.markets.index.Index.get_close_prices ============================================== .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_close_prices ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_constituent_instruments.rst ================================================ gs\_quant.markets.index.Index.get_constituent_instruments ========================================================= .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_constituent_instruments ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_constituent_instruments_for_date.rst ================================================ gs\_quant.markets.index.Index.get_constituent_instruments_for_date ================================================================== .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_constituent_instruments_for_date ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_constituents.rst ================================================ gs\_quant.markets.index.Index.get_constituents ============================================== .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_constituents ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_constituents_for_date.rst ================================================ gs\_quant.markets.index.Index.get_constituents_for_date ======================================================= .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_constituents_for_date ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_currency.rst ================================================ gs\_quant.markets.index.Index.get_currency ========================================== .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_currency ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_data_coordinate.rst ================================================ gs\_quant.markets.index.Index.get_data_coordinate ================================================= .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_data_coordinate ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_data_series.rst ================================================ gs\_quant.markets.index.Index.get_data_series ============================================= .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_data_series ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_entitlements.rst ================================================ gs\_quant.markets.index.Index.get_entitlements ============================================== .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_entitlements ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_entity.rst ================================================ gs\_quant.markets.index.Index.get_entity ======================================== .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_entity ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_factor_risk_report.rst ================================================ gs\_quant.markets.index.Index.get_factor_risk_report ==================================================== .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_factor_risk_report ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_fundamentals.rst ================================================ gs\_quant.markets.index.Index.get_fundamentals ============================================== .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_fundamentals ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_identifier.rst ================================================ gs\_quant.markets.index.Index.get_identifier ============================================ .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_identifier ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_identifiers.rst ================================================ gs\_quant.markets.index.Index.get_identifiers ============================================= .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_identifiers ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_latest_close_price.rst ================================================ gs\_quant.markets.index.Index.get_latest_close_price ==================================================== .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_latest_close_price ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_latest_constituent_instruments.rst ================================================ gs\_quant.markets.index.Index.get_latest_constituent_instruments ================================================================ .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_latest_constituent_instruments ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_latest_constituents.rst ================================================ gs\_quant.markets.index.Index.get_latest_constituents ===================================================== .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_latest_constituents ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_latest_position_set.rst ================================================ gs\_quant.markets.index.Index.get_latest_position_set ===================================================== .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_latest_position_set ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_marquee_id.rst ================================================ gs\_quant.markets.index.Index.get_marquee_id ============================================ .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_marquee_id ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_position_dates.rst ================================================ gs\_quant.markets.index.Index.get_position_dates ================================================ .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_position_dates ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_position_set_for_date.rst ================================================ gs\_quant.markets.index.Index.get_position_set_for_date ======================================================= .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_position_set_for_date ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_position_sets.rst ================================================ gs\_quant.markets.index.Index.get_position_sets =============================================== .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_position_sets ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_positions_data.rst ================================================ gs\_quant.markets.index.Index.get_positions_data ================================================ .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_positions_data ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_return_type.rst ================================================ gs\_quant.markets.index.Index.get_return_type ============================================= .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_return_type ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_status_of_reports.rst ================================================ gs\_quant.markets.index.Index.get_status_of_reports =================================================== .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_status_of_reports ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_type.rst ================================================ gs\_quant.markets.index.Index.get_type ====================================== .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_type ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_underlier_attribution.rst ================================================ gs\_quant.markets.index.Index.get_underlier_attribution ======================================================= .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_underlier_attribution ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_underlier_tree.rst ================================================ gs\_quant.markets.index.Index.get_underlier_tree ================================================ .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_underlier_tree ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_underlier_weights.rst ================================================ gs\_quant.markets.index.Index.get_underlier_weights =================================================== .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_underlier_weights ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_unique_entity_key.rst ================================================ gs\_quant.markets.index.Index.get_unique_entity_key =================================================== .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_unique_entity_key ================================================ FILE: docs/functions/gs_quant.markets.index.Index.get_url.rst ================================================ gs\_quant.markets.index.Index.get_url ===================================== .. currentmodule:: gs_quant.markets.index .. automethod:: Index.get_url ================================================ FILE: docs/functions/gs_quant.markets.index.Index.update_positions.rst ================================================ gs\_quant.markets.index.Index.update_positions ============================================== .. currentmodule:: gs_quant.markets.index .. automethod:: Index.update_positions ================================================ FILE: docs/functions/gs_quant.markets.index.Index.visualise_tree.rst ================================================ gs\_quant.markets.index.Index.visualise_tree ============================================ .. currentmodule:: gs_quant.markets.index .. automethod:: Index.visualise_tree ================================================ FILE: docs/functions/gs_quant.markets.indices_utils.get_flagship_baskets.rst ================================================ gs\_quant.markets.indices\_utils.get\_flagship\_baskets ======================================================= .. currentmodule:: gs_quant.markets.indices_utils .. autofunction:: get_flagship_baskets ================================================ FILE: docs/functions/gs_quant.markets.indices_utils.get_flagships_constituents.rst ================================================ gs\_quant.markets.indices\_utils.get\_flagships\_constituents ============================================================= .. currentmodule:: gs_quant.markets.indices_utils .. autofunction:: get_flagships_constituents ================================================ FILE: docs/functions/gs_quant.markets.indices_utils.get_flagships_performance.rst ================================================ gs\_quant.markets.indices\_utils.get\_flagships\_performance ============================================================ .. currentmodule:: gs_quant.markets.indices_utils .. autofunction:: get_flagships_performance ================================================ FILE: docs/functions/gs_quant.markets.indices_utils.get_flagships_with_assets.rst ================================================ gs\_quant.markets.indices\_utils.get\_flagships\_with\_assets ============================================================= .. currentmodule:: gs_quant.markets.indices_utils .. autofunction:: get_flagships_with_assets ================================================ FILE: docs/functions/gs_quant.markets.indices_utils.get_my_baskets.rst ================================================ gs\_quant.markets.indices\_utils.get\_my\_baskets ================================================= .. currentmodule:: gs_quant.markets.indices_utils .. autofunction:: get_my_baskets ================================================ FILE: docs/functions/gs_quant.markets.position_set.PositionSet.equalize_position_weights.rst ================================================ gs\_quant.markets.position\_set.PositionSet.equalize\_position\_weights ========================================== .. currentmodule:: gs_quant.markets.position_set .. automethod:: PositionSet.equalize_position_weights ================================================ FILE: docs/functions/gs_quant.markets.position_set.PositionSet.from_dicts.rst ================================================ gs\_quant.markets.position\_set.PositionSet.from\_dicts ========================================== .. currentmodule:: gs_quant.markets.position_set .. automethod:: PositionSet.from_dicts ================================================ FILE: docs/functions/gs_quant.markets.position_set.PositionSet.from_frame.rst ================================================ gs\_quant.markets.position\_set.PositionSet.from\_frame ========================================== .. currentmodule:: gs_quant.markets.position_set .. automethod:: PositionSet.from_frame ================================================ FILE: docs/functions/gs_quant.markets.position_set.PositionSet.from_list.rst ================================================ gs\_quant.markets.position\_set.PositionSet.from\_list ========================================== .. currentmodule:: gs_quant.markets.position_set .. automethod:: PositionSet.from_list ================================================ FILE: docs/functions/gs_quant.markets.position_set.PositionSet.get_positions.rst ================================================ gs\_quant.markets.position\_set.PositionSet.get\_positions ========================================== .. currentmodule:: gs_quant.markets.position_set .. automethod:: PositionSet.get_positions ================================================ FILE: docs/functions/gs_quant.markets.position_set.PositionSet.get_unpriced_positions.rst ================================================ gs\_quant.markets.position\_set.PositionSet.get\_unpriced\_positions ========================================== .. currentmodule:: gs_quant.markets.position_set .. automethod:: PositionSet.get_unpriced_positions ================================================ FILE: docs/functions/gs_quant.markets.position_set.PositionSet.get_unresolved_positions.rst ================================================ gs\_quant.markets.position\_set.PositionSet.get\_unresolved\_positions ========================================== .. currentmodule:: gs_quant.markets.position_set .. automethod:: PositionSet.get_unresolved_positions ================================================ FILE: docs/functions/gs_quant.markets.position_set.PositionSet.price.rst ================================================ gs\_quant.markets.position\_set.PositionSet.price ========================================== .. currentmodule:: gs_quant.markets.position_set .. automethod:: PositionSet.price ================================================ FILE: docs/functions/gs_quant.markets.position_set.PositionSet.price_many.rst ================================================ gs\_quant.markets.position\_set.PositionSet.price\_many ====================================================== .. currentmodule:: gs_quant.markets.position_set .. automethod:: PositionSet.price_many ================================================ FILE: docs/functions/gs_quant.markets.position_set.PositionSet.remove_unpriced_positions.rst ================================================ gs\_quant.markets.position\_set.PositionSet.remove\_unpriced\_positions ========================================== .. currentmodule:: gs_quant.markets.position_set .. automethod:: PositionSet.remove_unpriced_positions ================================================ FILE: docs/functions/gs_quant.markets.position_set.PositionSet.remove_unresolved_positions.rst ================================================ gs\_quant.markets.position\_set.PositionSet.remove\_unresolved\_positions ========================================== .. currentmodule:: gs_quant.markets.position_set .. automethod:: PositionSet.remove_unresolved_positions ================================================ FILE: docs/functions/gs_quant.markets.position_set.PositionSet.resolve.rst ================================================ gs\_quant.markets.position\_set.PositionSet.resolve ========================================== .. currentmodule:: gs_quant.markets.position_set .. automethod:: PositionSet.resolve ================================================ FILE: docs/functions/gs_quant.markets.position_set.PositionSet.resolve_many.rst ================================================ gs\_quant.markets.position\_set.PositionSet.resolve\_many ====================================================== .. currentmodule:: gs_quant.markets.position_set .. automethod:: PositionSet.resolve_many ================================================ FILE: docs/functions/gs_quant.markets.position_set.PositionSet.to_frame.rst ================================================ gs\_quant.markets.position\_set.PositionSet.to\_frame ========================================== .. currentmodule:: gs_quant.markets.position_set .. automethod:: PositionSet.to_frame ================================================ FILE: docs/functions/gs_quant.timeseries.algebra.abs_.rst ================================================ gs\_quant.timeseries.algebra.abs\_ ================================== .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: abs_ ================================================ FILE: docs/functions/gs_quant.timeseries.algebra.add.rst ================================================ gs\_quant.timeseries.algebra.add ================================ .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: add ================================================ FILE: docs/functions/gs_quant.timeseries.algebra.and_.rst ================================================ gs\_quant.timeseries.algebra.and\_ ================================== .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: and_ ================================================ FILE: docs/functions/gs_quant.timeseries.algebra.ceil.rst ================================================ gs\_quant.timeseries.algebra.ceil ================================= .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: ceil ================================================ FILE: docs/functions/gs_quant.timeseries.algebra.divide.rst ================================================ gs\_quant.timeseries.algebra.divide =================================== .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: divide ================================================ FILE: docs/functions/gs_quant.timeseries.algebra.exp.rst ================================================ gs\_quant.timeseries.algebra.exp ================================ .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: exp ================================================ FILE: docs/functions/gs_quant.timeseries.algebra.filter_.rst ================================================ gs\_quant.timeseries.algebra.filter\_ ===================================== .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: filter_ ================================================ FILE: docs/functions/gs_quant.timeseries.algebra.filter_dates.rst ================================================ gs\_quant.timeseries.algebra.filter\_dates ========================================== .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: filter_dates ================================================ FILE: docs/functions/gs_quant.timeseries.algebra.floor.rst ================================================ gs\_quant.timeseries.algebra.floor ================================== .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: floor ================================================ FILE: docs/functions/gs_quant.timeseries.algebra.floordiv.rst ================================================ gs\_quant.timeseries.algebra.floordiv ===================================== .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: floordiv ================================================ FILE: docs/functions/gs_quant.timeseries.algebra.if_.rst ================================================ gs\_quant.timeseries.algebra.if\_ ================================= .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: if_ ================================================ FILE: docs/functions/gs_quant.timeseries.algebra.log.rst ================================================ gs\_quant.timeseries.algebra.log ================================ .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: log ================================================ FILE: docs/functions/gs_quant.timeseries.algebra.multiply.rst ================================================ gs\_quant.timeseries.algebra.multiply ===================================== .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: multiply ================================================ FILE: docs/functions/gs_quant.timeseries.algebra.not_.rst ================================================ gs\_quant.timeseries.algebra.not\_ ================================== .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: not_ ================================================ FILE: docs/functions/gs_quant.timeseries.algebra.or_.rst ================================================ gs\_quant.timeseries.algebra.or\_ ================================= .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: or_ ================================================ FILE: docs/functions/gs_quant.timeseries.algebra.power.rst ================================================ gs\_quant.timeseries.algebra.power ================================== .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: power ================================================ FILE: docs/functions/gs_quant.timeseries.algebra.sqrt.rst ================================================ gs\_quant.timeseries.algebra.sqrt ================================= .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: sqrt ================================================ FILE: docs/functions/gs_quant.timeseries.algebra.subtract.rst ================================================ gs\_quant.timeseries.algebra.subtract ===================================== .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: subtract ================================================ FILE: docs/functions/gs_quant.timeseries.algebra.weighted_sum.rst ================================================ gs\_quant.timeseries.algebra.weighted\_sum ========================================== .. currentmodule:: gs_quant.timeseries.algebra .. autofunction:: weighted_sum ================================================ FILE: docs/functions/gs_quant.timeseries.analysis.compare.rst ================================================ gs\_quant.timeseries.analysis.compare ================================== .. currentmodule:: gs_quant.timeseries.analysis .. autofunction:: compare ================================================ FILE: docs/functions/gs_quant.timeseries.analysis.count.rst ================================================ gs\_quant.timeseries.analysis.count =================================== .. currentmodule:: gs_quant.timeseries.analysis .. autofunction:: count ================================================ FILE: docs/functions/gs_quant.timeseries.analysis.diff.rst ================================================ gs\_quant.timeseries.analysis.diff ================================== .. currentmodule:: gs_quant.timeseries.analysis .. autofunction:: diff ================================================ FILE: docs/functions/gs_quant.timeseries.analysis.first.rst ================================================ gs\_quant.timeseries.analysis.first =================================== .. currentmodule:: gs_quant.timeseries.analysis .. autofunction:: first ================================================ FILE: docs/functions/gs_quant.timeseries.analysis.lag.rst ================================================ gs\_quant.timeseries.analysis.lag ================================= .. currentmodule:: gs_quant.timeseries.analysis .. autofunction:: lag ================================================ FILE: docs/functions/gs_quant.timeseries.analysis.last.rst ================================================ gs\_quant.timeseries.analysis.last ================================== .. currentmodule:: gs_quant.timeseries.analysis .. autofunction:: last ================================================ FILE: docs/functions/gs_quant.timeseries.analysis.last_value.rst ================================================ gs\_quant.timeseries.analysis.last\_value ========================================= .. currentmodule:: gs_quant.timeseries.analysis .. autofunction:: last_value ================================================ FILE: docs/functions/gs_quant.timeseries.backtesting.basket_series.rst ================================================ gs\_quant.timeseries.backtesting.basket\_series =============================================== .. currentmodule:: gs_quant.timeseries.backtesting .. autofunction:: basket_series ================================================ FILE: docs/functions/gs_quant.timeseries.datetime.align.rst ================================================ gs\_quant.timeseries.datetime.align =================================== .. currentmodule:: gs_quant.timeseries.datetime .. autofunction:: align ================================================ FILE: docs/functions/gs_quant.timeseries.datetime.bucketize.rst ================================================ gs\_quant.timeseries.datetime.bucketize ======================================= .. currentmodule:: gs_quant.timeseries.datetime .. autofunction:: bucketize ================================================ FILE: docs/functions/gs_quant.timeseries.datetime.date_range.rst ================================================ gs\_quant.timeseries.datetime.date\_range ========================================= .. currentmodule:: gs_quant.timeseries.datetime .. autofunction:: date_range ================================================ FILE: docs/functions/gs_quant.timeseries.datetime.day.rst ================================================ gs\_quant.timeseries.datetime.day ================================= .. currentmodule:: gs_quant.timeseries.datetime .. autofunction:: day ================================================ FILE: docs/functions/gs_quant.timeseries.datetime.interpolate.rst ================================================ gs\_quant.timeseries.datetime.interpolate ========================================= .. currentmodule:: gs_quant.timeseries.datetime .. autofunction:: interpolate ================================================ FILE: docs/functions/gs_quant.timeseries.datetime.month.rst ================================================ gs\_quant.timeseries.datetime.month =================================== .. currentmodule:: gs_quant.timeseries.datetime .. autofunction:: month ================================================ FILE: docs/functions/gs_quant.timeseries.datetime.prepend.rst ================================================ gs\_quant.timeseries.datetime.prepend ===================================== .. currentmodule:: gs_quant.timeseries.datetime .. autofunction:: prepend ================================================ FILE: docs/functions/gs_quant.timeseries.datetime.quarter.rst ================================================ gs\_quant.timeseries.datetime.quarter ===================================== .. currentmodule:: gs_quant.timeseries.datetime .. autofunction:: quarter ================================================ FILE: docs/functions/gs_quant.timeseries.datetime.union.rst ================================================ gs\_quant.timeseries.datetime.union =================================== .. currentmodule:: gs_quant.timeseries.datetime .. autofunction:: union ================================================ FILE: docs/functions/gs_quant.timeseries.datetime.value.rst ================================================ gs\_quant.timeseries.datetime.value =================================== .. currentmodule:: gs_quant.timeseries.datetime .. autofunction:: value ================================================ FILE: docs/functions/gs_quant.timeseries.datetime.weekday.rst ================================================ gs\_quant.timeseries.datetime.weekday ===================================== .. currentmodule:: gs_quant.timeseries.datetime .. autofunction:: weekday ================================================ FILE: docs/functions/gs_quant.timeseries.datetime.year.rst ================================================ gs\_quant.timeseries.datetime.year ================================== .. currentmodule:: gs_quant.timeseries.datetime .. autofunction:: year ================================================ FILE: docs/functions/gs_quant.timeseries.econometrics.annualize.rst ================================================ gs\_quant.timeseries.econometrics.annualize =========================================== .. currentmodule:: gs_quant.timeseries.econometrics .. autofunction:: annualize ================================================ FILE: docs/functions/gs_quant.timeseries.econometrics.beta.rst ================================================ gs\_quant.timeseries.econometrics.beta ====================================== .. currentmodule:: gs_quant.timeseries.econometrics .. autofunction:: beta ================================================ FILE: docs/functions/gs_quant.timeseries.econometrics.change.rst ================================================ gs\_quant.timeseries.econometrics.change ======================================== .. currentmodule:: gs_quant.timeseries.econometrics .. autofunction:: change ================================================ FILE: docs/functions/gs_quant.timeseries.econometrics.correlation.rst ================================================ gs\_quant.timeseries.econometrics.correlation ============================================= .. currentmodule:: gs_quant.timeseries.econometrics .. autofunction:: correlation ================================================ FILE: docs/functions/gs_quant.timeseries.econometrics.excess_returns_.rst ================================================ gs\_quant.timeseries.econometrics.excess\_returns\_ =================================================== .. currentmodule:: gs_quant.timeseries.econometrics .. autofunction:: excess_returns_ ================================================ FILE: docs/functions/gs_quant.timeseries.econometrics.index.rst ================================================ gs\_quant.timeseries.econometrics.index ======================================= .. currentmodule:: gs_quant.timeseries.econometrics .. autofunction:: index ================================================ FILE: docs/functions/gs_quant.timeseries.econometrics.max_drawdown.rst ================================================ gs\_quant.timeseries.econometrics.max\_drawdown =============================================== .. currentmodule:: gs_quant.timeseries.econometrics .. autofunction:: max_drawdown ================================================ FILE: docs/functions/gs_quant.timeseries.econometrics.prices.rst ================================================ gs\_quant.timeseries.econometrics.prices ======================================== .. currentmodule:: gs_quant.timeseries.econometrics .. autofunction:: prices ================================================ FILE: docs/functions/gs_quant.timeseries.econometrics.returns.rst ================================================ gs\_quant.timeseries.econometrics.returns ========================================= .. currentmodule:: gs_quant.timeseries.econometrics .. autofunction:: returns ================================================ FILE: docs/functions/gs_quant.timeseries.econometrics.sharpe_ratio.rst ================================================ gs\_quant.timeseries.econometrics.sharpe\_ratio =============================================== .. currentmodule:: gs_quant.timeseries.econometrics .. autofunction:: sharpe_ratio ================================================ FILE: docs/functions/gs_quant.timeseries.econometrics.volatility.rst ================================================ gs\_quant.timeseries.econometrics.volatility ============================================ .. currentmodule:: gs_quant.timeseries.econometrics .. autofunction:: volatility ================================================ FILE: docs/functions/gs_quant.timeseries.statistics.cov.rst ================================================ gs\_quant.timeseries.statistics.cov =================================== .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: cov ================================================ FILE: docs/functions/gs_quant.timeseries.statistics.exponential_std.rst ================================================ gs\_quant.timeseries.statistics.exponential\_std ================================================ .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: exponential_std ================================================ FILE: docs/functions/gs_quant.timeseries.statistics.generate_series.rst ================================================ gs\_quant.timeseries.statistics.generate\_series ================================================ .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: generate_series ================================================ FILE: docs/functions/gs_quant.timeseries.statistics.max_.rst ================================================ gs\_quant.timeseries.statistics.max\_ ===================================== .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: max_ ================================================ FILE: docs/functions/gs_quant.timeseries.statistics.mean.rst ================================================ gs\_quant.timeseries.statistics.mean ==================================== .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: mean ================================================ FILE: docs/functions/gs_quant.timeseries.statistics.median.rst ================================================ gs\_quant.timeseries.statistics.median ====================================== .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: median ================================================ FILE: docs/functions/gs_quant.timeseries.statistics.min_.rst ================================================ gs\_quant.timeseries.statistics.min\_ ===================================== .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: min_ ================================================ FILE: docs/functions/gs_quant.timeseries.statistics.mode.rst ================================================ gs\_quant.timeseries.statistics.mode ==================================== .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: mode ================================================ FILE: docs/functions/gs_quant.timeseries.statistics.percentile.rst ================================================ gs\_quant.timeseries.statistics.percentile ========================================== .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: percentile ================================================ FILE: docs/functions/gs_quant.timeseries.statistics.percentiles.rst ================================================ gs\_quant.timeseries.statistics.percentiles =========================================== .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: percentiles ================================================ FILE: docs/functions/gs_quant.timeseries.statistics.product.rst ================================================ gs\_quant.timeseries.statistics.product ======================================= .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: product ================================================ FILE: docs/functions/gs_quant.timeseries.statistics.range_.rst ================================================ gs\_quant.timeseries.statistics.range\_ ======================================= .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: range_ ================================================ FILE: docs/functions/gs_quant.timeseries.statistics.std.rst ================================================ gs\_quant.timeseries.statistics.std =================================== .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: std ================================================ FILE: docs/functions/gs_quant.timeseries.statistics.sum_.rst ================================================ gs\_quant.timeseries.statistics.sum\_ ===================================== .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: sum_ ================================================ FILE: docs/functions/gs_quant.timeseries.statistics.var.rst ================================================ gs\_quant.timeseries.statistics.var =================================== .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: var ================================================ FILE: docs/functions/gs_quant.timeseries.statistics.winsorize.rst ================================================ gs\_quant.timeseries.statistics.winsorize ========================================= .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: winsorize ================================================ FILE: docs/functions/gs_quant.timeseries.statistics.zscores.rst ================================================ gs\_quant.timeseries.statistics.zscores ======================================= .. currentmodule:: gs_quant.timeseries.statistics .. autofunction:: zscores ================================================ FILE: docs/functions/gs_quant.timeseries.technicals.bollinger_bands.rst ================================================ gs\_quant.timeseries.technicals.bollinger\_bands ================================================ .. currentmodule:: gs_quant.timeseries.technicals .. autofunction:: bollinger_bands ================================================ FILE: docs/functions/gs_quant.timeseries.technicals.exponential_moving_average.rst ================================================ gs\_quant.timeseries.technicals.exponential\_moving\_average ============================================================ .. currentmodule:: gs_quant.timeseries.technicals .. autofunction:: exponential_moving_average ================================================ FILE: docs/functions/gs_quant.timeseries.technicals.exponential_spread_volatility.rst ================================================ gs\_quant.timeseries.technicals.exponential\_spread\_volatility =============================================================== .. currentmodule:: gs_quant.timeseries.technicals .. autofunction:: exponential_spread_volatility ================================================ FILE: docs/functions/gs_quant.timeseries.technicals.exponential_volatility.rst ================================================ gs\_quant.timeseries.technicals.exponential\_volatility ======================================================= .. currentmodule:: gs_quant.timeseries.technicals .. autofunction:: exponential_volatility ================================================ FILE: docs/functions/gs_quant.timeseries.technicals.macd.rst ================================================ gs\_quant.timeseries.technicals.macd ==================================== .. currentmodule:: gs_quant.timeseries.technicals .. autofunction:: macd ================================================ FILE: docs/functions/gs_quant.timeseries.technicals.moving_average.rst ================================================ gs\_quant.timeseries.technicals.moving\_average =============================================== .. currentmodule:: gs_quant.timeseries.technicals .. autofunction:: moving_average ================================================ FILE: docs/functions/gs_quant.timeseries.technicals.relative_strength_index.rst ================================================ gs\_quant.timeseries.technicals.relative\_strength\_index ========================================================= .. currentmodule:: gs_quant.timeseries.technicals .. autofunction:: relative_strength_index ================================================ FILE: docs/functions/gs_quant.timeseries.technicals.seasonally_adjusted.rst ================================================ gs\_quant.timeseries.technicals.seasonally\_adjusted ========================================================= .. currentmodule:: gs_quant.timeseries.technicals .. autofunction:: seasonally_adjusted ================================================ FILE: docs/functions/gs_quant.timeseries.technicals.smoothed_moving_average.rst ================================================ gs\_quant.timeseries.technicals.smoothed\_moving\_average ========================================================= .. currentmodule:: gs_quant.timeseries.technicals .. autofunction:: smoothed_moving_average ================================================ FILE: docs/functions/gs_quant.timeseries.technicals.trend.rst ================================================ gs\_quant.timeseries.technicals.trend ========================================================= .. currentmodule:: gs_quant.timeseries.technicals .. autofunction:: trend ================================================ FILE: docs/html/.doctrees/environment.pickle ================================================ ================================================ FILE: docs/html/.doctrees/index.doctree ================================================ ================================================ FILE: docs/html/.doctrees/timeseries.doctree ================================================ ================================================ FILE: docs/html/_sources/timeseries.rst.txt ================================================ Timeseries Package ================== Statistics functions -------------------- .. currentmodule:: gs_quant.timeseries.statistics .. autosummary:: :toctree: functions generate_series Econometrics functions ---------------------- .. currentmodule:: gs_quant.timeseries.econometrics .. autosummary:: :toctree: functions annualize lag returns prices diff index volatility correlation beta ================================================ FILE: docs/html/objects.inv ================================================ ================================================ FILE: docs/html/searchindex.js ================================================ Search.setIndex({docnames:["index","timeseries"],envversion:{"sphinx.domains.c":1,"sphinx.domains.changeset":1,"sphinx.domains.cpp":1,"sphinx.domains.javascript":1,"sphinx.domains.math":2,"sphinx.domains.python":1,"sphinx.domains.rst":1,"sphinx.domains.std":1,"sphinx.ext.intersphinx":1,"sphinx.ext.viewcode":1,sphinx:55},filenames:["index.rst","timeseries.rst"],objects:{},objnames:{},objtypes:{},terms:{"function":0,content:0,econometr:0,index:0,modul:0,packag:0,page:0,search:0,statist:0,timeseri:0},titles:["Welcome to GS Quant","Timeseries Package"],titleterms:{"function":1,econometr:1,indic:0,packag:1,quant:0,statist:1,tabl:0,timeseri:1,welcom:0}}) ================================================ FILE: docs/index.rst ================================================ .. gs_quant documentation master file, created by sphinx-quickstart on Sat Jan 5 20:53:00 2019. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. GS Quant API ============ .. toctree:: :maxdepth: 2 :caption: Packages analytics data datetime instrument markets models risk timeseries :ref:`genindex` ================================================ FILE: docs/instrument.rst ================================================ Instrument Package ================== Priceable --------- .. currentmodule:: gs_quant.base .. autosummary:: :toctree: classes Priceable Instruments ----------- .. currentmodule:: gs_quant.instrument .. autosummary:: :toctree: classes :template: instruments.rst CommodOTCSwap EqCliquet EqForward EqOption EqSynthetic EqVarianceSwap Forward FXAccumulator FXAccumulatorScheduleLeg FXBinary FXCorrelationSwap FXCorrelationSwapLeg FXDoubleKnockout FXDoubleOneTouch FXDualDoubleKnockout FXDualDoubleKnockoutLeg FXEuropeanKnockout FXForward FXKnockout FXMultiCrossBinary FXMultiCrossBinaryLeg FXMultiCrossDoubleBinary FXMultiCrossDoubleBinaryLeg FXMultiCrossDoubleOneTouch FXMultiCrossDoubleOneTouchLeg FXOneTouch FXOption FXOptionLeg FXOptionStrategy FXPivot FXPivotScheduleLeg FXShiftingBermForward FXTarf FXTarfScheduleLeg FXVarianceSwap FXVolatilitySwap FXWorstOf FXWorstOfKO FXWorstOfKOLeg InflationSwap IRBasisSwap IRCap IRCMSOption IRCMSOptionStrip IRCMSSpreadOption IRCMSSpreadOptionStrip IRFloor IRSwap IRSwaption IRXccySwap IRXccySwapFixFix IRXccySwapFixFlt Security ================================================ FILE: docs/make.bat ================================================ @ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=. set BUILDDIR=_build if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% :end popd ================================================ FILE: docs/markets.rst ================================================ Markets Package =============== PricingContext -------------- .. currentmodule:: gs_quant.markets.core .. autosummary:: :toctree: classes PricingContext HistoricalPricingContext ------------------------ .. currentmodule:: gs_quant.markets.historical .. autosummary:: :toctree: classes HistoricalPricingContext Basket ---------- .. currentmodule:: gs_quant.markets.baskets .. autosummary:: :toctree: classes Basket .. currentmodule:: gs_quant.markets .. autosummary:: :toctree: modules indices_utils Index ---------- .. currentmodule:: gs_quant.markets.index .. autosummary:: :toctree: classes Index .. currentmodule:: gs_quant.markets .. autosummary:: :toctree: modules indices_utils Portfolio --------- .. currentmodule:: gs_quant.markets.portfolio .. autosummary:: :toctree: classes Portfolio Grid PortfolioManager ---------------- .. currentmodule:: gs_quant.markets.portfolio_manager .. autosummary:: :toctree: classes :template: portfolio_manager.rst PortfolioManager PositionSet ----------- .. currentmodule:: gs_quant.markets.position_set .. autosummary:: :toctree: classes Position PositionSet Securities ---------- .. currentmodule:: gs_quant.markets.securities .. autosummary:: :toctree: classes Asset AssetClass AssetIdentifier AssetType Stock SecurityMaster Report ------------ .. currentmodule:: gs_quant.markets.report .. autosummary:: :toctree: classes :template: report.rst ReportJobFuture Report PerformanceReport FactorRiskReport ThematicReport Optimizer ------------ .. currentmodule:: gs_quant.markets.optimizer .. autosummary:: :toctree: classes :template: report.rst AssetUniverse AssetConstraint FactorConstraint MaxProportionOfRiskByGroupConstraint CountryConstraint IndustryConstraint SectorConstraint TurnoverConstraint ConstraintPriorities OptimizerSettings OptimizerUniverse OptimizerStrategy ================================================ FILE: docs/models.rst ================================================ Models Package ============== Epidemiology ------------ .. currentmodule:: gs_quant.models.epidemiology .. autosummary:: :toctree: classes SIR SEIR EpidemicModel RiskModel ------------ .. currentmodule:: gs_quant.models.risk_model .. autosummary:: :toctree: classes :template: report.rst RiskModel MarqueeRiskModel FactorRiskModel MacroRiskModel ThematicRiskModel ================================================ FILE: docs/module/models.rst ================================================ Models Package ============== Epidemiology ------------ .. currentmodule:: gs_quant.models.epidemiology .. autosummary:: :toctree: classes SIR SEIR EpidemicModel RiskModel ------------ .. currentmodule:: gs_quant.models.risk_model .. autosummary:: :toctree: classes :template: processors.rst RiskModel FactorRiskModel MacroRiskModel ThematicRiskModel ================================================ FILE: docs/modules/gs_quant.markets.indices_utils.rst ================================================ gs\_quant.markets.indices\_utils ================================ .. automodule:: gs_quant.markets.indices_utils .. rubric:: Functions .. autosummary:: get_constituents_dataset_coverage get_flagship_baskets get_flagships_constituents get_flagships_performance get_flagships_with_assets get_my_baskets .. rubric:: Classes .. autosummary:: BasketType CorporateActionType CustomBasketStyles IndicesDatasets PriceType Region ResearchBasketStyles ReturnType STSIndexType WeightingStrategy ================================================ FILE: docs/risk.rst ================================================ Risk Package ============ Functions --------- .. currentmodule:: gs_quant.risk .. autofunction:: aggregate_risk .. autofunction:: subtract_risk .. autofunction:: sort_risk Measures -------- .. autodata:: DollarPrice .. autodata:: Price .. autodata:: ForwardPrice .. autodata:: Theta .. autodata:: BaseCPI .. autodata:: CommodDelta .. autodata:: CommodTheta .. autodata:: CommodVega .. autodata:: EqDelta .. autodata:: EqGamma .. autodata:: EqVega .. autodata:: EqSpot .. autodata:: EqAnnualImpliedVol .. autodata:: FairVarStrike .. autodata:: FairVolStrike .. autodata:: FXDelta .. autodata:: FXGamma .. autodata:: FXVega .. autodata:: FXSpot .. autodata:: FXAnnualImpliedVol .. autodata:: FXAnnualATMImpliedVol .. autodata:: InflationDelta .. autodata:: IRBasis .. autodata:: IRDelta .. autodata:: IRXccyDelta .. autodata:: IRGammaParallel .. autodata:: IRGammaParallelLocalCcy .. autodata:: IRVega .. autodata:: IRAnnualImpliedVol .. autodata:: IRAnnualATMImpliedVol .. autodata:: IRDailyImpliedVol .. autodata:: IRSpotRate .. autodata:: IRFwdRate ================================================ FILE: docs/searchdataext/__init__.py ================================================ import json import os import sphinx.search from pathlib import Path from docutils import nodes def setup(app): print("Adding searchdataext output") sphinx.search.IndexBuilder = IndexBuilder class IndexBuilder(sphinx.search.IndexBuilder): def __init__(self, env, lang, options, scoring): super(IndexBuilder, self).__init__(env, lang, options, scoring) self._doc_collector=[] self._outputfile = Path(os.path.dirname(__file__), '..', '_build', 'search', 'search-index.json').resolve().absolute() def feed(self, docname, filename, title, doctree): super(IndexBuilder, self).feed(docname, filename, title, doctree) visitor = sphinx.search.WordCollector(doctree, self.lang) doctree.walk(visitor) # index page should be marked as 'Home' for developer site search results if ' '.join(visitor.found_title_words) == '': visitor.found_title_words = ['Home'] newdoc = { 'title': ' '.join(visitor.found_title_words), 'body': ' '.join(visitor.found_words), 'key': docname } self._doc_collector.append(newdoc) def freeze(self): data = super(IndexBuilder, self).freeze() def ensure_dir(file_path): if not os.path.exists(file_path): os.makedirs(file_path) output_file_path = str(self._outputfile) print('Writing search data to ' + output_file_path) ensure_dir(os.path.dirname(output_file_path)) with open(str(self._outputfile), 'w+') as outfile: json.dump(self._doc_collector, outfile, sort_keys=True, indent=4) return data ================================================ FILE: docs/timeseries.rst ================================================ Timeseries Package ================== Algebra ------- .. currentmodule:: gs_quant.timeseries.algebra .. autosummary:: :toctree: functions abs_ add and_ ceil divide exp filter_ filter_dates floor floordiv if_ log multiply not_ or_ power sqrt subtract weighted_sum Analysis -------- .. currentmodule:: gs_quant.timeseries.analysis .. autosummary:: :toctree: functions diff first last last_value count lag Backtesting ----------- .. currentmodule:: gs_quant.timeseries.backtesting .. autosummary:: :toctree: functions basket_series .. autosummary:: :toctree: classes :template: timeseries_class.rst Basket Date / Time ----------- .. currentmodule:: gs_quant.timeseries.datetime .. autosummary:: :toctree: functions align interpolate value day weekday month year quarter date_range prepend union bucketize .. autosummary:: :toctree: classes :template: timeseries_class.rst Window Econometrics ------------ .. currentmodule:: gs_quant.timeseries.econometrics .. autosummary:: :toctree: functions annualize beta change correlation excess_returns_ index max_drawdown prices returns sharpe_ratio volatility Statistics ---------- .. currentmodule:: gs_quant.timeseries.statistics .. autosummary:: :toctree: functions cov exponential_std generate_series max_ mean median min_ mode percentile percentiles product range_ std sum_ var winsorize zscores .. autosummary:: :toctree: classes :template: timeseries_class.rst LinearRegression RollingLinearRegression SIRModel SEIRModel Technical Analysis ------------------ .. currentmodule:: gs_quant.timeseries.technicals .. autosummary:: :toctree: functions bollinger_bands moving_average exponential_moving_average exponential_volatility exponential_spread_volatility smoothed_moving_average relative_strength_index macd ================================================ FILE: gs_quant/__init__.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import sys from importlib.metadata import version as get_lib_version, PackageNotFoundError from ._version import get_versions name = "gs_quant" __version__ = get_versions()['version'] def get_environment_summary(): """ Returns a summary of the Python version and the versions of specific libraries. """ libraries = ['gs_quant', 'gs_quant_internal', 'numpy', 'pandas'] summary = { 'Python Version': sys.version, } for lib in libraries: try: lib_version = get_lib_version(lib) except PackageNotFoundError: lib_version = 'Not Installed' summary[lib] = lib_version return summary __summary__ = get_environment_summary() del get_versions version = __version__ summary = __summary__ # Set up PyXll, if available try: from .xl_interface.instrument_generation import install_hook install_hook() except ModuleNotFoundError: pass # Jupyter needs nest_asyncio to avoid event loop issues try: from IPython import get_ipython ipython = get_ipython() if ipython and 'IPKernelApp' in get_ipython().config: import nest_asyncio nest_asyncio.apply() except ImportError: pass # Setup tracing for Jupyter try: from gs_quant.tracing import tracing # pylint: disable=unused-import except ImportError: pass ================================================ FILE: gs_quant/_version.py ================================================ # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag # feature). Distribution tarballs (built by setup.py sdist) and build # directories (produced by setup.py build) will contain a much shorter file # that just contains the computed version number. # This file is released into the public domain. # Generated by versioneer-0.28 # https://github.com/python-versioneer/python-versioneer """Git implementation of _version.py.""" import errno import os import re import subprocess import sys from typing import Callable, Dict import functools def get_keywords(): """Get the keywords needed to look up the version information.""" # these strings will be replaced by git during git-archive. # setup.py/versioneer.py will grep for the variable names, so they must # each be defined on a line of their own. _version.py will just call # get_keywords(). git_refnames = "$Format:%d$" git_full = "$Format:%H$" git_date = "$Format:%ci$" keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} return keywords class VersioneerConfig: """Container for Versioneer configuration parameters.""" def get_config(): """Create, populate and return the VersioneerConfig() object.""" # these strings are filled in when 'setup.py versioneer' creates # _version.py cfg = VersioneerConfig() cfg.VCS = "git" cfg.style = "pep440" cfg.tag_prefix = "release-" cfg.parentdir_prefix = "gs_quant-" cfg.versionfile_source = "gs_quant/_version.py" cfg.verbose = False return cfg class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" LONG_VERSION_PY: Dict[str, str] = {} HANDLERS: Dict[str, Dict[str, Callable]] = {} def register_vcs_handler(vcs, method): # decorator """Create decorator to mark a method as the handler of a VCS.""" def decorate(f): """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f return f return decorate def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None): """Call the given command(s).""" assert isinstance(commands, list) process = None popen_kwargs = {} if sys.platform == "win32": # This hides the console window if pythonw.exe is used startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW popen_kwargs["startupinfo"] = startupinfo for command in commands: try: dispcmd = str([command] + args) # remember shell=False, so use git.cmd on windows, not just git process = subprocess.Popen([command] + args, cwd=cwd, env=env, stdout=subprocess.PIPE, stderr=(subprocess.PIPE if hide_stderr else None), **popen_kwargs) break except OSError: e = sys.exc_info()[1] if e.errno == errno.ENOENT: continue if verbose: print("unable to run %s" % dispcmd) print(e) return None, None else: if verbose: print("unable to find command, tried %s" % (commands,)) return None, None stdout = process.communicate()[0].strip().decode() if process.returncode != 0: if verbose: print("unable to run %s (error)" % dispcmd) print("stdout was %s" % stdout) return None, process.returncode return stdout, process.returncode def versions_from_parentdir(parentdir_prefix, root, verbose): """Try to determine the version from the parent directory name. Source tarballs conventionally unpack into a directory that includes both the project name and a version string. We will also support searching up two directory levels for an appropriately named parent directory """ rootdirs = [] for _ in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): return {"version": dirname[len(parentdir_prefix):], "full-revisionid": None, "dirty": False, "error": None, "date": None} rootdirs.append(root) root = os.path.dirname(root) # up a level if verbose: print("Tried directories %s but none started with prefix %s" % (str(rootdirs), parentdir_prefix)) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") @register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs): """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from # _version.py. keywords = {} try: with open(versionfile_abs, "r") as fobj: for line in fobj: if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["full"] = mo.group(1) if line.strip().startswith("git_date ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["date"] = mo.group(1) except OSError: pass return keywords @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): """Get version information from git keywords.""" if "refnames" not in keywords: raise NotThisMethod("Short version file found") date = keywords.get("date") if date is not None: # Use only the last line. Previous lines may contain GPG signature # information. date = date.splitlines()[-1] # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 # -like" string, which we must then edit to make compliant), because # it's been around since git-1.5.3, and it's too difficult to # discover which version we're using, or to work around using an # older one. date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") refs = {r.strip() for r in refnames.strip("()").split(",")} # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " tags = {r[len(TAG):] for r in refs if r.startswith(TAG)} if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %d # expansion behaves like git log --decorate=short and strips out the # refs/heads/ and refs/tags/ prefixes that would let us distinguish # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". tags = {r for r in refs if re.search(r'\d', r)} if verbose: print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: print("likely tags: %s" % ",".join(sorted(tags))) for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] # Filter out refs that exactly match prefix or that don't start # with a number once the prefix is stripped (mostly a concern # when prefix is '') if not re.match(r'\d', r): continue if verbose: print("picking %s" % r) return {"version": r, "full-revisionid": keywords["full"].strip(), "dirty": False, "error": None, "date": date} # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: print("no suitable tags, using unknown + full revision id") return {"version": "0+unknown", "full-revisionid": keywords["full"].strip(), "dirty": False, "error": "no suitable tags", "date": None} @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* expanded, and _version.py hasn't already been rewritten with a short version string, meaning we're inside a checked out source tree. """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] # GIT_DIR can interfere with correct operation of Versioneer. # It may be intended to be passed to the Versioneer-versioned project, # but that should not change where we get our version from. env = os.environ.copy() env.pop("GIT_DIR", None) runner = functools.partial(runner, env=env) _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=not verbose) if rc != 0: if verbose: print("Directory %s not under git control" % root) raise NotThisMethod("'git rev-parse --git-dir' returned error") # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) describe_out, rc = runner(GITS, [ "describe", "--tags", "--dirty", "--always", "--long", "--match", f"{tag_prefix}[[:digit:]]*" ], cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() pieces = {} pieces["long"] = full_out pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], cwd=root) # --abbrev-ref was added in git-1.6.3 if rc != 0 or branch_name is None: raise NotThisMethod("'git rev-parse --abbrev-ref' returned error") branch_name = branch_name.strip() if branch_name == "HEAD": # If we aren't exactly on a branch, pick a branch which represents # the current commit. If all else fails, we are on a branchless # commit. branches, rc = runner(GITS, ["branch", "--contains"], cwd=root) # --contains was added in git-1.5.4 if rc != 0 or branches is None: raise NotThisMethod("'git branch --contains' returned error") branches = branches.split("\n") # Remove the first line if we're running detached if "(" in branches[0]: branches.pop(0) # Strip off the leading "* " from the list of branches. branches = [branch[2:] for branch in branches] if "master" in branches: branch_name = "master" elif not branches: branch_name = None else: # Pick the first branch that is returned. Good or bad. branch_name = branches[0] pieces["branch"] = branch_name # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out # look for -dirty suffix dirty = git_describe.endswith("-dirty") pieces["dirty"] = dirty if dirty: git_describe = git_describe[:git_describe.rindex("-dirty")] # now we have TAG-NUM-gHEX or HEX if "-" in git_describe: # TAG-NUM-gHEX mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: # unparsable. Maybe git-describe is misbehaving? pieces["error"] = ("unable to parse git-describe output: '%s'" % describe_out) return pieces # tag full_tag = mo.group(1) if not full_tag.startswith(tag_prefix): if verbose: fmt = "tag '%s' doesn't start with prefix '%s'" print(fmt % (full_tag, tag_prefix)) pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" % (full_tag, tag_prefix)) return pieces pieces["closest-tag"] = full_tag[len(tag_prefix):] # distance: number of commits since tag pieces["distance"] = int(mo.group(2)) # commit: short hex revision ID pieces["short"] = mo.group(3) else: # HEX: no tags pieces["closest-tag"] = None out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root) pieces["distance"] = len(out.split()) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip() # Use only the last line. Previous lines may contain GPG signature # information. date = date.splitlines()[-1] pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces def plus_or_dot(pieces): """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" def render_pep440(pieces): """Build up version string, with post-release "local version identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty Exceptions: 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += plus_or_dot(pieces) rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_branch(pieces): """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . The ".dev0" means not master branch. Note that .dev0 sorts backwards (a feature branch will appear "older" than the master branch). Exceptions: 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: if pieces["branch"] != "master": rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0" if pieces["branch"] != "master": rendered += ".dev0" rendered += "+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def pep440_split_post(ver): """Split pep440 version string at the post-release segment. Returns the release segments before the post-release and the post-release version number (or -1 if no post-release segment is present). """ vc = str.split(ver, ".post") return vc[0], int(vc[1] or 0) if len(vc) == 2 else None def render_pep440_pre(pieces): """TAG[.postN.devDISTANCE] -- No -dirty. Exceptions: 1: no tags. 0.post0.devDISTANCE """ if pieces["closest-tag"]: if pieces["distance"]: # update the post release segment tag_version, post_version = pep440_split_post(pieces["closest-tag"]) rendered = tag_version if post_version is not None: rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"]) else: rendered += ".post0.dev%d" % (pieces["distance"]) else: # no commits, use the tag as the version rendered = pieces["closest-tag"] else: # exception #1 rendered = "0.post0.dev%d" % pieces["distance"] return rendered def render_pep440_post(pieces): """TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that .dev0 sorts backwards (a dirty tree will appear "older" than the corresponding clean one), but you shouldn't be releasing software with -dirty anyways. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%s" % pieces["short"] else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += "+g%s" % pieces["short"] return rendered def render_pep440_post_branch(pieces): """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . The ".dev0" means not master branch. Exceptions: 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["branch"] != "master": rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%s" % pieces["short"] if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["branch"] != "master": rendered += ".dev0" rendered += "+g%s" % pieces["short"] if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_old(pieces): """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" return rendered def render_git_describe(pieces): """TAG[-DISTANCE-gHEX][-dirty]. Like 'git describe --tags --dirty --always'. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render_git_describe_long(pieces): """TAG-DISTANCE-gHEX[-dirty]. Like 'git describe --tags --dirty --always -long'. The distance/hash is unconditional. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render(pieces, style): """Render the given version pieces into the requested style.""" if pieces["error"]: return {"version": "unknown", "full-revisionid": pieces.get("long"), "dirty": None, "error": pieces["error"], "date": None} if not style or style == "default": style = "pep440" # the default if style == "pep440": rendered = render_pep440(pieces) elif style == "pep440-branch": rendered = render_pep440_branch(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) elif style == "pep440-post-branch": rendered = render_pep440_post_branch(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) elif style == "git-describe": rendered = render_git_describe(pieces) elif style == "git-describe-long": rendered = render_git_describe_long(pieces) else: raise ValueError("unknown style '%s'" % style) return {"version": rendered, "full-revisionid": pieces["long"], "dirty": pieces["dirty"], "error": None, "date": pieces.get("date")} def get_versions(): """Get version information or return default if unable to do so.""" # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have # __file__, we can work backwards from there to the root. Some # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which # case we can only use expanded keywords. cfg = get_config() verbose = cfg.verbose try: return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose) except NotThisMethod: pass try: root = os.path.realpath(__file__) # versionfile_source is the relative path from the top of the source # tree (where the .git directory might live) to this file. Invert # this to find the root from __file__. for _ in cfg.versionfile_source.split('/'): root = os.path.dirname(root) except NameError: return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to find root of source tree", "date": None} try: pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) return render(pieces, cfg.style) except NotThisMethod: pass try: if cfg.parentdir_prefix: return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) except NotThisMethod: pass return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to compute version", "date": None} ================================================ FILE: gs_quant/analytics/__init__.py ================================================ ================================================ FILE: gs_quant/analytics/common/__init__.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from .constants import * from .enumerators import * ================================================ FILE: gs_quant/analytics/common/constants.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ DATAGRID_HELP_MSG: str = ( '\nDatagrid is currently experimental. The API is likely to change. For' ' questions contact gs-marquee-markets@gs.com\n' ) DATA_CELL_NOT_CALCULATED: str = 'Cell has not been calculated' CELL_GRAPH: str = 'cell_graph' QUERIES_TO_PROCESSORS: str = 'queries_to_processors' DATA_COORDINATE: str = 'dataCoordinate' PROCESSOR: str = 'processor' PROCESSOR_NAME: str = 'processorName' ENTITY: str = 'entity' ENTITY_ID: str = 'entityId' ENTITY_TYPE: str = 'entityType' DATE: str = 'date' DATETIME: str = 'datetime' TYPE: str = 'type' LIST: str = 'list' VALUE: str = 'value' PARAMETER: str = 'parameter' PARAMETERS: str = 'parameters' DATA_ROW: str = 'dataRow' REFERENCE: str = 'reference' RELATIVE_DATE: str = 'relativeDate' ================================================ FILE: gs_quant/analytics/common/enumerators.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from enum import Enum class ScaleShape(Enum): DIAMOND = 'diamond' PIPE = 'pipe' BAR = 'bar' ================================================ FILE: gs_quant/analytics/common/helpers.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import logging from typing import List, Dict from gs_quant.analytics.common import TYPE, DATA_ROW, PROCESSOR, REFERENCE, PARAMETER, ENTITY_ID, ENTITY_TYPE from gs_quant.datetime.relative_date import RelativeDate from gs_quant.entities.entity import Entity from gs_quant.errors import MqValueError, MqRequestError _logger = logging.getLogger(__name__) def is_of_builtin_type(obj): return type(obj).__module__ in ('builtins', '__builtin__') def resolve_entities(reference_list: List[Dict], entity_cache: Dict = None): """ Utility function to fetch entities (assets, countries, etc.). Allows us to split functionality that requires data fetching. :param reference_list: A list of entity references (entityId and entityType dictionaries) :param entity_cache: Map of entity id to the entity for external cache management :return: None """ entity_cache = entity_cache or {} for reference in reference_list: # Check if the entity is in the cache entity_id = reference.get(ENTITY_ID) if entity_id in entity_cache: entity = entity_cache[entity_id] else: try: entity = Entity.get(entity_id, 'MQID', reference.get(ENTITY_TYPE)) except MqRequestError as e: _logger.warning(e) entity = entity_id if reference[TYPE] == DATA_ROW: # If the reference is for a data row, simply set the entity of the row. reference[REFERENCE].entity = entity elif reference[TYPE] == PROCESSOR: # If the reference is for a processor, set the given parameter as the entity. setattr(reference[REFERENCE], reference[PARAMETER], entity) data_query_info = reference[REFERENCE].children.get(reference[PARAMETER]) if not data_query_info: raise MqValueError( f'{reference[PARAMETER]} does not exist in children of {reference[REFERENCE].__class__.__name__}' ) data_query_info.entity = entity def get_rdate_cache_key(rule: str, base_date: str, currencies: List[str], exchanges: List[str]) -> str: return f'{rule}::{base_date}::{currencies}::{exchanges}' def get_entity_rdate_key(entity_id: str, rule: str, base_date): return f'{entity_id}::{rule}::{base_date}' def get_entity_rdate_key_from_rdate(entity_id: str, rdate: RelativeDate): base_date = str(rdate.base_date) if rdate.base_date_passed_in else None return f'{entity_id}::{rdate.rule}::{base_date}' ================================================ FILE: gs_quant/analytics/core/__init__.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from .processor import BaseProcessor ================================================ FILE: gs_quant/analytics/core/processor.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import asyncio import datetime as dt import functools import logging import uuid from abc import ABCMeta, abstractmethod from concurrent.futures.process import ProcessPoolExecutor from dataclasses import dataclass from enum import Enum, EnumMeta from typing import List, Optional, Union, Dict, get_type_hints, Set, Tuple import numpy as np import pandas as pd from pydash import decapitalize, get from gs_quant.analytics.common import ( TYPE, PROCESSOR, PARAMETERS, DATA_COORDINATE, ENTITY, VALUE, DATE, DATETIME, PROCESSOR_NAME, ENTITY_ID, ENTITY_TYPE, PARAMETER, REFERENCE, RELATIVE_DATE, LIST, ) from gs_quant.analytics.common.enumerators import ScaleShape from gs_quant.analytics.common.helpers import is_of_builtin_type, get_entity_rdate_key_from_rdate from gs_quant.analytics.core.processor_result import ProcessorResult from gs_quant.common import Currency from gs_quant.data import DataCoordinate, DataFrequency from gs_quant.data.coordinate import DateOrDatetime from gs_quant.data.query import DataQuery, DataQueryType from gs_quant.entities.entity import Entity from gs_quant.timeseries import Window, Returns, RelativeDate PARSABLE_OBJECT_MAP = {'window': Window, 'returns': Returns, 'currency': Currency, 'scaleShape': ScaleShape} _logger = logging.getLogger(__name__) @dataclass class DataQueryInfo: attr: str processor: 'BaseProcessor' query: DataQuery entity: Entity data: pd.Series = None @dataclass class MeasureQueryInfo: attr: str processor: 'BaseProcessor' entity: Entity DateOrDatetimeOrRDate = Union[DateOrDatetime, RelativeDate] class BaseProcessor(metaclass=ABCMeta): def __init__(self, **kwargs): self.id = f'{self.__class__.__name__}-{str(uuid.uuid4())}' self.value: ProcessorResult = ProcessorResult(False, 'Value not set') self.parent: Optional[Union[BaseProcessor]] = None self.parent_attr: Optional[str] = None self.children: Dict[str, Union[DataCoordinateOrProcessor, DataQueryInfo]] = {} self.children_data: Dict[str, ProcessorResult] = {} self.data_cell = None self.last_value = kwargs.get('last_value', False) self.measure_processor = kwargs.get('measure_processor', False) @abstractmethod def process(self, *args): """Handle the calculation of the data with given coordinate data series""" pass def post_process(self): if self.last_value: if ( isinstance(self.value, ProcessorResult) and self.value.success and isinstance(self.value.data, pd.Series) and not self.value.data.empty ): self.value.data = self.value.data.iloc[-1:] def __handle_date_range(self, result, rdate_entity_map: Dict[str, dt.date]): """ Applies a date/datetime mask on the result using the start/end parameters on a processor :param result: :param rdate_entity_map: map of entity, rule, base_date to date value :return: None """ if not isinstance(result, ProcessorResult) or not result.success: return start, end = get(self, 'start'), get(self, 'end') if not (start or end): return entity = self.data_cell.entity # If the entity wasn't found default to base rule (no entity) entity_id = '' if isinstance(entity, str) else entity.get_marquee_id() if start and end: if isinstance(start, RelativeDate): key = get_entity_rdate_key_from_rdate(entity_id, start) start = rdate_entity_map[key] if isinstance(end, RelativeDate): key = get_entity_rdate_key_from_rdate(entity_id, end) end = rdate_entity_map[key] mask = (result.data.index >= np.datetime64(start)) & (result.data.index <= np.datetime64(end)) elif start: if isinstance(start, RelativeDate): key = get_entity_rdate_key_from_rdate(entity_id, start) start = rdate_entity_map[key] mask = result.data.index >= np.datetime64(start) else: if isinstance(end, RelativeDate): key = get_entity_rdate_key_from_rdate(entity_id, end) end = rdate_entity_map[key] mask = result.data.index >= np.datetime64(end) result.data = result.data[mask] async def update( self, attribute: str, result: ProcessorResult, rdate_entity_map: Dict[str, dt.date], pool: ProcessPoolExecutor = None, query_info: Union[DataQueryInfo, MeasureQueryInfo] = None, ): """Handle the update of a single coordinate and recalculate the value :param attribute: Attribute alinging to data coordinate in the processor :param result: Processor result including success and series from data query """ if not self.measure_processor: self.__handle_date_range(result, rdate_entity_map) self.children_data[attribute] = result if isinstance(result, ProcessorResult): if result.success: try: if pool: if self.measure_processor: value = await asyncio.get_running_loop().run_in_executor( pool, functools.partial(self.process, query_info.entity) ) else: value = await asyncio.get_running_loop().run_in_executor(pool, self.process) self.value = value else: if self.measure_processor: self.process(query_info.entity) else: self.process() self.post_process() except Exception as e: self.value = ProcessorResult( False, f'Error Calculating processor {self.__class__.__name__} due to {e}' ) else: self.value = result @abstractmethod def get_plot_expression(self): """Returns a plot expression used to go from grid to plottool""" pass def __add_required_rdates(self, entity: Entity, rdate_entity_map: Dict[str, Set[Tuple]]): start, end = get(self, 'start'), get(self, 'end') entity_id = entity.get_marquee_id() if isinstance(entity, Entity) else '' if isinstance(start, RelativeDate): base_date = str(start.base_date) if start.base_date_passed_in else None rdate_entity_map[entity_id].add((start.rule, base_date)) if isinstance(end, RelativeDate): base_date = str(end.base_date) if end.base_date_passed_in else None rdate_entity_map[entity_id].add((end.rule, base_date)) def build_graph( self, entity: Entity, cell, queries: List[Union[DataQueryInfo, MeasureQueryInfo]], rdate_entity_map: Dict[str, Set[Tuple]], overrides: Optional[List], ): """Generates the nested cell graph and keeps a map of leaf data queries to processors""" self.data_cell = cell self.__add_required_rdates(entity, rdate_entity_map) attributes = self.__dict__ if self.measure_processor: queries.append(MeasureQueryInfo(attr='a', processor=self, entity=entity)) for attr_name, child in self.children.items(): if isinstance(child, DataCoordinate): # Override coordinate dimensions if overrides: override_dimensions = list(filter(lambda x: x.coordinate == child, overrides)) if len(override_dimensions): use_default = True for override in overrides: if override.coordinate_id == child.id: child.set_dimensions(override.dimensions) use_default = False break if use_default and overrides[0].coordinate_id is None: child.set_dimensions(overrides[0].dimensions) if child.frequency == DataFrequency.DAILY: query = DataQuery(coordinate=child, start=attributes.get('start'), end=attributes.get('end')) else: query = DataQuery(coordinate=child, query_type=DataQueryType.LAST) # track the leaf data query queries.append(DataQueryInfo(attr=attr_name, processor=self, query=query, entity=entity)) elif isinstance(child, BaseProcessor): # Set the children's parent fields child.parent = self child.parent_attr = attr_name child.build_graph(entity, cell, queries, rdate_entity_map, overrides) elif isinstance(child, DataQueryInfo): child.parent = self child.parent_attr = attr_name child.processor = self queries.append(child) async def calculate( self, attribute: str, result: ProcessorResult, rdate_entity_map: Dict[str, dt.date], pool: ProcessPoolExecutor = None, query_info: Union[DataQueryInfo, MeasureQueryInfo] = None, ): """Sets the result on the processor and recursively calls parent to set and calculate value :param attribute: Attribute alinging to data coordinate in the processor :param result: Processor result including success and series from data query """ # update the result await self.update(attribute, result, rdate_entity_map, pool, query_info) # if there is a parent, traverse up and recompute if self.parent: value: ProcessorResult = self.value if isinstance(value, ProcessorResult): # only traverse if processor successful calculates if value.success: if isinstance(self.parent, BaseProcessor): await self.parent.calculate(self.parent_attr, value, rdate_entity_map, pool) else: # Must be the data cell self.parent.update(value) else: self.data_cell.value = value # Put the error on the data cell self.data_cell.updated_time = f'{dt.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3]}Z' def as_dict(self) -> Dict: """ Create a dictionary representation of the processor. Used for eventually turning the processor into json for API usage. Allows for nested processors. :return: Dictionary representation of the processor """ processor = {TYPE: PROCESSOR, PARAMETERS: self.get_default_params()} if isinstance(self, BaseProcessor): processor[PROCESSOR_NAME] = self.__class__.__name__ parameters = processor[PARAMETERS] for parameter, alias in get_type_hints(self.__init__).items(): if alias in ( DataCoordinateOrProcessor, DataCoordinate, Union[DataCoordinateOrProcessor, None], BaseProcessor, ): # If the parameter is a DataCoordinate or processor, recursively call as_dict() attribute = self.children[parameter] if attribute is None: continue parameters[parameter] = {} this_parameter = parameters[parameter] if isinstance(attribute, BaseProcessor): this_parameter[TYPE] = PROCESSOR this_parameter[PROCESSOR_NAME] = attribute.__class__.__name__ elif isinstance(attribute, DataCoordinate): this_parameter[TYPE] = DATA_COORDINATE else: # Continue if not an expected type or None continue this_parameter.update(**attribute.as_dict()) else: # Handle the less complex types such as python built in types, enums, entities, datetimes attribute = getattr(self, parameter) if attribute is not None: parameters[parameter] = {} if isinstance(attribute, list): value = [item.as_dict() for item in attribute] elif is_of_builtin_type(attribute): value = attribute elif isinstance(attribute, Enum): value = attribute.value elif isinstance(attribute, Entity): parameters[parameter].update( { TYPE: ENTITY, ENTITY_ID: attribute.get_marquee_id(), ENTITY_TYPE: attribute.entity_type().value, } ) continue elif isinstance(attribute, (dt.date, dt.datetime)): if isinstance(attribute, dt.date): value = str(attribute) else: value = f"{attribute.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3]}Z" else: value = attribute.as_dict() parameters[parameter].update({TYPE: decapitalize(type(attribute).__name__), VALUE: value}) return processor def get_default_params(self): # Handle all the kwarg based parameters that all processors take default_params = {} if self.last_value: default_params['last_value'] = dict(type='bool', value=True) return default_params @classmethod def from_dict(cls, obj: Dict, reference_list: List): processor_name: str = obj.get(PROCESSOR_NAME, 'None') # Dynamically import the processor to for instantiation. processor = getattr(__import__('gs_quant.analytics.processors', fromlist=['']), processor_name, None) parameters = obj.get(PARAMETERS, {}) local_reference_list = [] arguments = {} for parameter, parameters_dict in parameters.items(): # Loop through all the parameters and turned them into objects based off their dictionary values. # Will recursively handle the more complex objects such as DataCoordinate and Processors. parameter_type: str = parameters_dict.get(TYPE) if parameter_type == DATA_COORDINATE: # Handle the DataCoordinate parameters arguments[parameter] = DataCoordinate.from_dict(parameters_dict) elif parameter_type == PROCESSOR: # Handle the BaseProcessor parameters arguments[parameter] = BaseProcessor.from_dict(parameters_dict, reference_list) elif parameter_type == ENTITY: # Handle the entity parameter list and put into the reference mapped to be resolved later local_reference_list.append( { TYPE: PROCESSOR, ENTITY_ID: parameters_dict.get(ENTITY_ID), ENTITY_TYPE: parameters_dict.get(ENTITY_TYPE), PARAMETER: parameter, } ) arguments[parameter] = None elif parameter_type in (DATE, DATETIME, RELATIVE_DATE): # Handle date/datetime parameters if parameter_type == DATE: arguments[parameter] = dt.datetime.strptime(parameters_dict.get(VALUE), '%Y-%m-%d').date() elif parameter_type == RELATIVE_DATE: val = parameters_dict.get(VALUE) base_date = val.get('baseDate') base_date = dt.datetime.strptime(base_date, '%Y-%m-%d').date() if base_date else None arguments[parameter] = RelativeDate(rule=val['rule'], base_date=base_date) else: arguments[parameter] = dt.datetime.strptime( parameters_dict.get(VALUE)[0:-1], '%Y-%m-%dT%H:%M:%S.%f' ) else: # Handle all other object which should be mapped in the PARSABLE_OBJECT_MAP if parameter_type in PARSABLE_OBJECT_MAP: parameter_obj = PARSABLE_OBJECT_MAP[parameter_type] if isinstance(parameter_obj, (Enum, EnumMeta)): arguments[parameter] = parameter_obj(parameters_dict.get(VALUE, {})) else: arguments[parameter] = parameter_obj.from_dict(parameters_dict.get(VALUE, {})) elif parameter_type == LIST: arguments[parameter] = [ BaseProcessor.from_dict(item, reference_list) if isinstance(item, dict) else item for item in parameters_dict['value'] ] else: # Handles built in types that are stored natively arguments[parameter] = parameters_dict.get(VALUE) processor = processor(**arguments) if processor else None # Instantiate the processor with all arguments # Add all the references to entities to the list which will be resolved later for reference in local_reference_list: reference[REFERENCE] = processor reference_list.extend(local_reference_list) return processor DataCoordinateOrProcessor = Union[DataCoordinate, BaseProcessor] ================================================ FILE: gs_quant/analytics/core/processor_result.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from dataclasses import dataclass from typing import Union, Dict import pandas as pd @dataclass class ProcessorResult: success: bool data: Union[str, pd.Series, Dict] ================================================ FILE: gs_quant/analytics/core/query_helpers.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import asyncio import logging from collections import defaultdict from typing import Dict, Tuple, Union import pandas as pd from gs_quant.analytics.core.processor import MeasureQueryInfo from gs_quant.analytics.core.processor_result import ProcessorResult from gs_quant.data import DataFrequency from gs_quant.session import GsSession _logger = logging.getLogger(__name__) def aggregate_queries(query_infos): mappings = defaultdict(dict) # DataSet -> start/end for query_info in query_infos: if isinstance(query_info, MeasureQueryInfo): continue query = query_info.query coordinate = query.coordinate dataset_id = coordinate.dataset_id dataset_mappings = mappings[dataset_id] query_key = query.get_range_string() if dataset_id is None: series: ProcessorResult = ProcessorResult( False, f'No dataset resolved for measure={coordinate.measure} with dimensions={coordinate.dimensions}' ) asyncio.get_event_loop().run_until_complete(query_info.processor.calculate(query_info.attr, series, None)) continue dataset_mappings.setdefault( query_key, { 'datasetId': dataset_id, 'parameters': {}, 'queries': defaultdict(list), 'range': {}, 'realTime': True if coordinate.frequency == DataFrequency.REAL_TIME else False, 'measures': set(), }, ) query_map = dataset_mappings[query_key] if not query_map['range']: if isinstance(query.start, dt.date): query_map['range']['startDate'] = query.start elif isinstance(query.start, dt.datetime): query_map['range']['startTime'] = query.start if isinstance(query.end, dt.date): query_map['range']['endDate'] = query.end elif isinstance(query.end, dt.datetime): query_map['range']['endTime'] = query.end queries = query_map['queries'] queries[coordinate.get_dimensions()].append(query_info) parameters = query_map['parameters'] query_map['measures'].add(coordinate.measure) for dimension, value in coordinate.dimensions.items(): parameters.setdefault(dimension, set()) parameters[dimension].add(value) return mappings def fetch_query(query_info: Dict): where = {} for key, value in query_info['parameters'].items(): value_list = list(value) if isinstance(value_list[0], bool): if len(value_list) == 1: # If only 1 bool value is given (True/False) set the where to the value. Else skip. where[key] = value_list[0] continue # Skip adding as both True/False must be there where[key] = list(value) query = {'where': where, **query_info['range'], 'useFieldAlias': True, 'remapSchemaToAlias': True} try: if query_info['realTime'] and not query_info['range']: response = GsSession.current.sync.post(f'/data/{query_info["datasetId"]}/last/query', payload=query) else: response = GsSession.current.sync.post(f'/data/{query_info["datasetId"]}/query', payload=query) except Exception as e: _logger.error(f'Error fetching query due to {e}') return pd.DataFrame() df = pd.DataFrame(response.get('data', {})) if df.empty: return df df = df.set_index('date' if 'date' in df.columns else 'time') df.index = pd.to_datetime(df.index).tz_localize(None) return df def build_query_string(dimensions): output = '' for count, dimension in enumerate(dimensions): value = dimension[1] if isinstance(value, str): value = f'"{value}"' if count == 0: output += f'{dimension[0]} == {value}' else: output += f' & {dimension[0]} == {value}' return output def valid_dimensions(query_dimensions: Tuple[str, Union[str, float, bool]], df: pd.DataFrame) -> bool: columns = df.columns for query_dimension in query_dimensions: dimension = query_dimension[0] if dimension not in columns: return False return True ================================================ FILE: gs_quant/analytics/datagrid/__init__.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from .data_column import DataColumn, ColumnFormat from .data_row import DataRow, Override, DimensionsOverride, ProcessorOverride from .datagrid import DataGrid ================================================ FILE: gs_quant/analytics/datagrid/data_cell.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import copy import uuid from typing import List, Optional, Dict, Set, Tuple, Union import pandas as pd from gs_quant.analytics.common import DATA_CELL_NOT_CALCULATED from gs_quant.analytics.core import BaseProcessor from gs_quant.analytics.core.processor import DataQueryInfo, MeasureQueryInfo from gs_quant.analytics.core.processor_result import ProcessorResult from gs_quant.analytics.datagrid import Override from gs_quant.analytics.datagrid.utils import get_utc_now from gs_quant.entities.entity import Entity class DataCell: """Entity data cell Computes value and manages formatting of a data cell """ def __init__( self, name: str, processor: BaseProcessor, entity: Entity, dimension_overrides: List[Override], column_index: int, row_index: int, row_group: str = None, ): # Cell starts with root processor # Deep copies so the processor and children are unique objects self.cell_id = str(uuid.uuid4()) self.processor: BaseProcessor = copy.deepcopy(processor) self.entity: Entity = entity self.name: str = name self.dimension_overrides = dimension_overrides self.column_index = column_index self.row_index = row_index self.row_group = row_group self.updated_time: Optional[str] = None # Default the value for a cell processor self.value: ProcessorResult = ProcessorResult(False, DATA_CELL_NOT_CALCULATED) # Store the cell data queries self.data_queries: List[DataQueryInfo] = [] def build_cell_graph( self, all_queries: List[Union[DataQueryInfo, MeasureQueryInfo]], rdate_entity_map: Dict[str, Set[Tuple]] ) -> None: """Generate and store the cell graph and data queries This can be modified to return the data queries rather than store on the cell """ # Set the root processor node parent to data cell if self.processor: self.processor.parent = self cell_queries: List[DataQueryInfo] = [] self.processor.build_graph(self.entity, self, cell_queries, rdate_entity_map, self.dimension_overrides) self.data_queries = cell_queries all_queries.extend(cell_queries) # Add this cell's queries to the entire datagrid's list of queries def update(self, result: ProcessorResult) -> None: """Sets the value of the cell""" if isinstance(result.data, pd.Series): if result.data.empty: self.value = ProcessorResult(False, 'Empty series as a result of processing.') else: self.value = ProcessorResult(True, result.data.iloc[-1]) else: self.value = ProcessorResult(True, result.data) self.updated_time = get_utc_now() ================================================ FILE: gs_quant/analytics/datagrid/data_column.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from dataclasses import dataclass, asdict, fields, field from typing import Dict, List, Union from gs_quant.analytics.core.processor import BaseProcessor DEFAULT_WIDTH = 100 class RenderType: DEFAULT = 'default' HEATMAP = 'heatmap' BOXPLOT = 'boxplot' SCALE = 'scale' DATE_MMM_YY = 'dateMmmYy' TIME_HH_MM = 'timeHhMm' @dataclass class HeatMapColorRange: """ Dataclass for HeatMap color ranges. All values must be in hex format. Example: '#ffffff' for white. """ low: str mid: str high: str @classmethod def from_dict(cls, dict_: dict): class_fields = {f.name for f in fields(cls)} return HeatMapColorRange(**{k: v for k, v in dict_.items() if k in class_fields}) @dataclass class MultiColumnGroup: """ MultiColumnGroup allows you to group DataGrid columns by index. Useful when working with heatmap renderers. :param id (Union[int, str]): Integer or string to identify the group. Defaults to 0. :param columnIndices (List[int]): Optional List of integers specifying the column indices to group :param groupAll (List[int]): Optional flag that allows you to easily group all columns so you don't have to pass every column index to the columnIndices list. :param heatMapColorRange (HeatMapColorRange): Optional HeatMapColorRange which allows you to specify a color theme for your grouped heatmap columns """ id: Union[int, str] = 0 columnIndices: List[int] = field(default_factory=list) groupAll: bool = False heatMapColorRange: HeatMapColorRange = None def asdict(self): obj = {'id': self.id, 'columnIndices': self.columnIndices} if self.groupAll: obj['groupAll'] = True if self.heatMapColorRange: obj['heatMapColorRange'] = asdict(self.heatMapColorRange) return obj @classmethod def from_dict(cls, dict_: dict): heat_map_color_range = dict_.get('heatMapColorRange') return MultiColumnGroup( id=dict_.get('id'), groupAll=dict_.get('groupAll', False), columnIndices=dict_.get('columnIndices'), heatMapColorRange=HeatMapColorRange.from_dict(heat_map_color_range) if heat_map_color_range else None, ) class ColumnFormat: def __init__( self, *, renderType: RenderType = RenderType.DEFAULT, precision: int = 2, humanReadable: bool = True, tooltip: str = None, displayValues: bool = True, ): """ Use this class to specify the format options for your column. :param renderType: Type to use when rendering the column. :param precision: Number of precision points to use. :param humanReadable: Formats number to have commas. :param tooltip: Helper text to use as tooltip for the column. :param displayValues: For graphical render types only. True will show numerical values and title in the column. else only the graphics will be displayed. """ self.renderType = renderType self.precision = precision self.humanReadable = humanReadable self.tooltip = tooltip self.displayValues = displayValues def as_dict(self): format_ = { 'renderType': self.renderType, 'precision': self.precision, 'humanReadable': self.humanReadable, } if self.tooltip: format_['tooltip'] = self.tooltip if self.renderType != RenderType.DEFAULT: format_['displayValues'] = self.displayValues return format_ @classmethod def from_dict(cls, obj: dict): return ColumnFormat( renderType=obj.get('renderType'), precision=obj.get('precision'), humanReadable=obj.get('humanReadable'), tooltip=obj.get('tooltip'), displayValues=obj.get('displayValues'), ) class DataColumn: """Base class for grid column""" def __init__( self, name: str, processor: BaseProcessor = None, *, format_: ColumnFormat = ColumnFormat(), width: int = DEFAULT_WIDTH, ): """DataColumn :param name: Name of the column :param processor: Processor to apply to the column for calculation :param format_: Formatting information for the column result :param width: Size of the column in pixels when presented on the UI """ self.name = name self.processor = processor self.format_ = format_ self.width = width def as_dict(self): format_ = self.format_.as_dict() column = {'name': self.name, 'format': format_, 'width': self.width} processor = self.processor if processor: column['processorName'] = processor.__class__.__name__ column.update(**processor.as_dict()) return column @classmethod def from_dict(cls, obj: Dict, reference_list: List): processor = BaseProcessor.from_dict(obj, reference_list) return DataColumn( name=obj['name'], processor=processor, format_=ColumnFormat.from_dict(obj.get('format', {})), width=obj.get('width', DEFAULT_WIDTH), ) ================================================ FILE: gs_quant/analytics/datagrid/data_row.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from abc import ABC from enum import Enum from typing import Dict, List, Optional, Union from gs_quant.analytics.core import BaseProcessor from gs_quant.data import DataCoordinate from gs_quant.data.fields import DataDimension from gs_quant.entities.entity import Entity DataDimensions = Dict[Union[DataDimension, str], Union[str, float]] # Override Types DIMENSIONS_OVERRIDE = 'dimensionsOverride' PROCESSOR_OVERRIDE = 'processorOverride' VALUE_OVERRIDE = 'valueOverride' # Row Types DATA_ROW = 'dataRow' ROW_SEPARATOR = 'rowSeparator' class Override(ABC): """Base class for a DataGrid row override""" def __init__(self, column_names: List[str]): """Abstract Row Override :param column_names: column names to override with the specified dimensions """ self.column_names = column_names super().__init__() def as_dict(self) -> Dict: return {'columnNames': self.column_names} @classmethod def from_dict(cls, obj, reference_list): pass class ValueOverride(Override): def __init__(self, column_names: List[str], value: Union[float, str, bool]): """ Allows the ability to set a cell to a specific value. :param column_names: Name of columns to apply the value override. :param value: Value to set to the row and column intersections. """ super().__init__(column_names) self.value = value def as_dict(self): override = super().as_dict() override['type'] = VALUE_OVERRIDE override['value'] = self.value return override @classmethod def from_dict(cls, obj, ref): return ValueOverride(column_names=obj.get('columnNames', []), value=obj['value']) class DimensionsOverride(Override): def __init__( self, column_names: List[str], dimensions: DataDimensions, coordinate: DataCoordinate, coordinate_id: str = None ): """Override dimensions for the given coordinate :param column_names: column names to override with the specified dimensions :param dimensions: dict of dimensions to override columns when fetching data :param coordinate: coordinate to apply the override :param coordinate_id: id of the coordinate to apply override. Gives additional control vs just passing coordinate """ super().__init__(column_names) # Following coordinate model, convert override dimensions to match coordinate dimension self.dimensions = {k.value if isinstance(k, Enum) else k: v for k, v in dimensions.items()} self.coordinate = coordinate self.coordinate_id = coordinate_id def as_dict(self): override = super().as_dict() override['type'] = DIMENSIONS_OVERRIDE override['dimensions'] = self.dimensions override['coordinate'] = self.coordinate.as_dict() if self.coordinate_id: override['coordinateId'] = self.coordinate_id return override @classmethod def from_dict(cls, obj, reference_list): parsed_dimensions = {} data_dimension_map = DataDimension._value2member_map_ for key, value in obj.get('dimensions', {}).items(): if key in data_dimension_map: parsed_dimensions[DataDimension(key)] = value else: parsed_dimensions[key] = value return DimensionsOverride( column_names=obj.get('columnNames', []), dimensions=parsed_dimensions, coordinate=DataCoordinate.from_dict(obj.get('coordinate', {})), coordinate_id=obj.get('coordinateId'), ) class ProcessorOverride(Override): def __init__(self, column_names: List[str], processor: BaseProcessor): """Abstract Row Override :param column_names: column names to override with the specified dimensions :param processor: processor to override """ super().__init__(column_names=column_names) self.processor = processor def as_dict(self): override = super().as_dict() override['type'] = PROCESSOR_OVERRIDE if self.processor: override['processor'] = self.processor.as_dict() override['processor']['processorName'] = self.processor.__class__.__name__ else: override['processor'] = None override['processor']['processorName'] = None return override @classmethod def from_dict(cls, obj, reference_list): return ProcessorOverride( column_names=obj.get('columnNames', []), processor=BaseProcessor.from_dict(obj.get('processor', {}), reference_list), ) class RowSeparator: def __init__(self, name: str): """Row Separator :param name: name of the row separator """ self.name = name def as_dict(self): return {'type': ROW_SEPARATOR, 'name': self.name} @classmethod def from_dict(cls, obj): return RowSeparator(obj['name']) class DataRow: """Row object for DataGrid""" def __init__(self, entity: Entity, overrides: Optional[List[Override]] = None): """Data row :param entity: Specified entity for the DataRow :param overrides: Optional List of DataRowOverride's for retrieving data """ self.entity = entity self.overrides: List[Override] = overrides or [] def as_dict(self): data_row = { 'type': DATA_ROW, 'entityId': self.entity.get_marquee_id() if isinstance(self.entity, Entity) else self.entity, 'entityType': self.entity.entity_type().value if isinstance(self.entity, Entity) else '', } if len(self.overrides): data_row['overrides'] = [override.as_dict() for override in self.overrides] return data_row @classmethod def from_dict(cls, obj, reference_list): overrides = [] for override_dict in obj.get('overrides', []): override_type = override_dict.get('type') if override_type == PROCESSOR_OVERRIDE: override = ProcessorOverride.from_dict(override_dict, reference_list) elif override_type == DIMENSIONS_OVERRIDE: override = DimensionsOverride.from_dict(override_dict, reference_list) else: override = ValueOverride.from_dict(override_dict, reference_list) overrides.append(override) data_row = DataRow(entity=None, overrides=overrides) # Entity gets resolved later reference_list.append( { 'type': DATA_ROW, 'entityId': obj.get('entityId', ''), 'entityType': obj.get('entityType', ''), 'reference': data_row, } ) return data_row ================================================ FILE: gs_quant/analytics/datagrid/datagrid.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import asyncio import datetime as dt import json import logging import webbrowser from collections import defaultdict from dataclasses import asdict from numbers import Number from typing import List, Dict, Optional, Tuple, Union, Set import numpy as np import pandas as pd from gs_quant.analytics.common import DATAGRID_HELP_MSG from gs_quant.analytics.common.helpers import ( resolve_entities, get_entity_rdate_key, get_entity_rdate_key_from_rdate, get_rdate_cache_key, ) from gs_quant.analytics.core.processor import DataQueryInfo, MeasureQueryInfo from gs_quant.analytics.core.processor_result import ProcessorResult from gs_quant.analytics.core.query_helpers import aggregate_queries, fetch_query, build_query_string, valid_dimensions from gs_quant.analytics.datagrid.data_cell import DataCell from gs_quant.analytics.datagrid.data_column import DataColumn, ColumnFormat, MultiColumnGroup from gs_quant.analytics.datagrid.data_row import ( DataRow, DimensionsOverride, ProcessorOverride, Override, ValueOverride, RowSeparator, ) from gs_quant.analytics.datagrid.serializers import row_from_dict from gs_quant.analytics.datagrid.utils import ( DataGridSort, SortOrder, SortType, DataGridFilter, FilterOperation, FilterCondition, get_utc_now, ) from gs_quant.analytics.processors import CoordinateProcessor, EntityProcessor from gs_quant.common import Entitlements as Entitlements_ from gs_quant.datetime.relative_date import RelativeDate from gs_quant.entities.entitlements import Entitlements from gs_quant.entities.entity import Entity from gs_quant.errors import MqValueError from gs_quant.session import GsSession, OAuth2Session _logger = logging.getLogger(__name__) API = '/data/grids' DATAGRID_HEADERS: Dict[str, str] = {'Content-Type': 'application/json;charset=utf-8'} class DataGrid: """ DataGrid is an object for fetching Marquee data and applying processors (functions). DataGrids can be persisted via the DataGrid API and utilized on the Marquee Markets platform. :param name: Name of the DataGrid :param rows: List of DataGrid rows for the grid :param columns: List of DataGrid columns for the grid :param id_: Unique identifier of the grid :param entitlements: Marquee entitlements of the grid for the Marquee Market's platform :param sorts: Optional list of DataGridSort. Use this if you want to sort your columns. :param filters: Optional list of DataGridFilter. Use this to filter column's data. :param multiColumnGroups: Optional list of MultiColumnGroup. Useful to group columns for heatmaps. **Usage** To create a DataGrid, we define two components, rows and columns: >>> from gs_quant.markets.securities import Asset, AssetIdentifier >>> from gs_quant.data.coordinate import DataMeasure, DataFrequency >>> from gs_quant.analytics.processors import LastProcessor >>> >>> GS = Asset.get("GS UN", AssetIdentifier.BLOOMBERG_ID) >>> AAPL = Asset.get("AAPL UW", AssetIdentifier.BLOOMBERG_ID) >>> rows = [ >>> DataRow(GS), >>> DataRow(AAPL) >>> ] >>> trade_price = DataCoordinate( >>> measure=DataMeasure.TRADE_PRICE, >>> frequency=DataFrequency.REAL_TIME, >>> ) >>> >>> col_0 = DataColumn(name="Name", processor=EntityProcessor(field="short_name")) >>> col_1 = DataColumn(name="Last", processor=LastProcessor(trade_price)) >>> columns = [ col_0, col_1 ] >>> >>> datagrid = DataGrid(name="Example DataGrid", rows=rows, columns=columns) >>> datagrid.initialize() >>> datagrid.poll() >>> print(datagrid.to_frame()) **Documentation** Full Documentation and examples can be found here: https://developer.gs.com/docs/gsquant/tutorials/Data/DataGrid/ """ def __init__( self, name: str, rows: List[Union[DataRow, RowSeparator]], columns: List[DataColumn], *, id_: str = None, entitlements: Union[Entitlements, Entitlements_] = None, polling_time: int = None, sorts: Optional[List[DataGridSort]] = None, filters: Optional[List[DataGridFilter]] = None, multiColumnGroups: Optional[List[MultiColumnGroup]] = None, **kwargs, ): self.id_ = id_ self.entitlements = entitlements self.name = name self.rows = rows self.columns = columns self.sorts = sorts or [] self.filters = filters or [] self.multiColumnGroups = multiColumnGroups self.polling_time = polling_time or 0 # store the graph, data queries to leaf processors and results self._primary_column_index: int = kwargs.get('primary_column_index', 0) self._cells: List[DataCell] = [] self._data_queries: List[Union[DataQueryInfo, MeasureQueryInfo]] = [] self._entity_cells: List[DataCell] = [] self._coord_processor_cells: List[DataCell] = [] self._value_cells: List[DataCell] = [] self.entity_map: Dict[str, Entity] = {} # RDate Mappings self.rdate_entity_map: Dict[str, Set[Tuple]] = defaultdict(set) self.rule_cache: Dict[str, dt.date] = {} self.results: List[List[DataCell]] = [] self.is_initialized: bool = False print(DATAGRID_HELP_MSG) def get_id(self) -> Optional[str]: """Get the unique DataGrid identifier. Will only exists if the DataGrid has been persisted.""" return self.id_ def initialize(self) -> None: """ Initializes the DataGrid. Iterates over all rows and columns, preparing cell structures. Cells then contain a graph and data queries to leaf processors. Upon providing data to a leaf, the leaf processor is calculated and propagated up the graph to the cell level. """ all_queries: List[Union[DataQueryInfo, MeasureQueryInfo]] = [] entity_cells: List[DataCell] = [] current_row_group = None # Loop over rows, columns for row_index, row in enumerate(self.rows): if isinstance(row, RowSeparator): current_row_group = row.name continue entity: Entity = row.entity if isinstance(entity, Entity): self.entity_map[entity.get_marquee_id()] = entity else: self.entity_map[''] = entity cells: List[DataCell] = [] row_overrides = row.overrides for column_index, column in enumerate(self.columns): column_name = column.name column_processor = column.processor # Get all the data coordinate overrides and apply the processor override if it exists data_overrides, value_override, processor_override = _get_overrides(row_overrides, column_name) # Create the cell cell: DataCell = DataCell( column_name, column_processor, entity, data_overrides, column_index, row_index, current_row_group ) if processor_override: # Check if there is a processor override and apply if so cell.processor = processor_override if value_override: cell.value = ProcessorResult(True, value_override.value) cell.updated_time = get_utc_now() elif isinstance(column_processor, EntityProcessor): # store these cells to fetch entity data during poll entity_cells.append(cell) elif isinstance(column_processor, CoordinateProcessor): # store these cells to fetch entity data during poll if len(data_overrides): # Get the last in the list if more than 1 override is given cell.processor.children['a'].set_dimensions(data_overrides[-1].dimensions) self._coord_processor_cells.append(cell) elif column_processor.measure_processor: all_queries.append(MeasureQueryInfo(attr='', entity=entity, processor=column_processor)) else: # append the required queries to the map cell.build_cell_graph(all_queries, self.rdate_entity_map) cells.append(cell) self._cells.extend(cells) self.results.append(cells) self._data_queries = all_queries self._entity_cells = entity_cells self.is_initialized = True def poll(self) -> None: """Poll the data queries required to process this grid. Set the results at the leaf processors """ self._resolve_rdates() self._resolve_queries() self._process_special_cells() self._fetch_queries() def save(self) -> str: """ Saves the DataGrid. If the DataGrid has already been created, the DataGrid will be updated. If the DataGrid has not been created it will be added to the DataGrid service. :return: Unique identifier of the DataGrid """ datagrid_json = self.__as_json() if self.id_: response = GsSession.current.sync.put(f'{API}/{self.id_}', datagrid_json, request_headers=DATAGRID_HEADERS) else: response = GsSession.current.sync.post(f'{API}', datagrid_json, request_headers=DATAGRID_HEADERS) self.id_ = response['id'] return DataGrid.from_dict(response).id_ def create(self): """ Creates a new DataGrid even if the DataGrid already exists. If the DataGrid has already been persisted, the DataGrid id will be replaced with the newly persisted DataGrid. :return: New DataGrid unique identifier """ datagrid_json = self.__as_json() response = GsSession.current.sync.post(f'{API}', datagrid_json, request_headers=DATAGRID_HEADERS) self.id_ = response['id'] return response['id'] def delete(self): """ Deletes the DataGrid if it has been persisted. :return: None """ if self.id_: GsSession.current.sync.delete(f'{API}/{self.id_}', request_headers=DATAGRID_HEADERS) else: raise MqValueError('DataGrid has not been persisted.') def open(self): """ Opens the DataGrid in the default browser. :return: None """ if self.id_ is None: raise MqValueError('DataGrid must be created or saved before opening.') domain = GsSession.current.domain.replace(".web", "") if domain == 'https://api.gs.com': domain = 'https://marquee.gs.com' url = f'{domain}/s/markets/grids/{self.id_}' webbrowser.open(url) @property def polling_time(self): return self.__polling_time @polling_time.setter def polling_time(self, value): if value is None: self.__polling_time = 0 elif value != 0 and value < 5000: raise MqValueError('polling_time must be >= than 10000ms.') self.__polling_time = value def _process_special_cells(self) -> None: """ Processes Coordinate and Entity cells :return: None """ # fetch entity cells for cell in self._entity_cells: try: cell.value = cell.processor.process(cell.entity) except Exception as e: cell.value = ( f'Error Calculating processor {cell.processor.__class__.__name__} ' f'for entity: {cell.entity.get_marquee_id()} due to {e}' ) cell.updated_time = get_utc_now() for cell in self._coord_processor_cells: try: cell.value = cell.processor.process() except Exception as e: cell.value = ( f'Error Calculating processor {cell.processor.__class__.__name__} ' f'for entity: {cell.entity.get_marquee_id()} due to {e}' ) cell.updated_time = get_utc_now() def _resolve_rdates(self, rule_cache: Dict = None): # TODO: Thread this... rule_cache = rule_cache or {} # Default to no calendar for rdate for external and oauth calendar = [] if not GsSession.current.is_internal() and isinstance(GsSession.current, OAuth2Session) else None for entity_id, rules in self.rdate_entity_map.items(): entity = self.entity_map.get(entity_id) currencies = None exchanges = None if isinstance(entity, Entity): entity_dict = entity.get_entity() currency = entity_dict.get("currency") exchange = entity_dict.get("exchange") currencies = [currency] if currency else None exchanges = [exchange] if exchange else None for rule_base_date_tuple in rules: rule, base_date = rule_base_date_tuple[0], rule_base_date_tuple[1] cache_key = get_rdate_cache_key(rule_base_date_tuple[0], rule_base_date_tuple[1], currencies, exchanges) date_value = rule_cache.get(cache_key) if date_value is None: if base_date: base_date = dt.datetime.strptime(base_date, "%Y-%m-%d").date() date_value = RelativeDate(rule, base_date).apply_rule( currencies=currencies, exchanges=exchanges, holiday_calendar=calendar ) rule_cache[cache_key] = date_value self.rule_cache[get_entity_rdate_key(entity_id, rule, base_date)] = date_value def _resolve_queries(self, availability_cache: Dict = None) -> None: """Resolves the dataset_id for each data query This is used to query data thereafter """ availability_cache = availability_cache or {} for query in self._data_queries: entity = query.entity if isinstance(entity, str) or isinstance(query, MeasureQueryInfo): # If we were unable to fetch entity (404/403) or if we're processing a measure processor continue query = query.query coord = query.coordinate entity_dimension = entity.data_dimension entity_id = entity.get_marquee_id() query_start = query.start query_end = query.end if isinstance(query_start, RelativeDate): key = get_entity_rdate_key_from_rdate(entity_id, query_start) query.start = self.rule_cache[key] if isinstance(query_end, RelativeDate): key = get_entity_rdate_key_from_rdate(entity_id, query_end) query.end = self.rule_cache[key] if entity_dimension not in coord.dimensions: if coord.dataset_id: # don't need to fetch the data set if user supplied it coord.set_dimensions({entity_dimension: entity.get_marquee_id()}) query.coordinate = coord else: # Need to resolve the dataset from availability entity_id = entity.get_marquee_id() try: raw_availability = availability_cache.get(entity_id) if raw_availability is None: raw_availability: Dict = GsSession.current.sync.get( f'/data/measures/{entity_id}/availability' ) availability_cache[entity.get_marquee_id()] = raw_availability query.coordinate = entity.get_data_coordinate( measure=coord.measure, dimensions=coord.dimensions, frequency=coord.frequency, availability=raw_availability, ) except Exception as e: _logger.info(f'Could not get DataCoordinate with {coord} for entity {entity_id} due to {e}') def _fetch_queries(self): query_aggregations = aggregate_queries(self._data_queries) for dataset_id, query_map in query_aggregations.items(): for query in query_map.values(): df = fetch_query(query) for query_dimensions, query_infos in query['queries'].items(): if valid_dimensions(query_dimensions, df): queried_df = df.query(build_query_string(query_dimensions)) for query_info in query_infos: measure = query_info.query.coordinate.measure query_info.data = queried_df[measure if isinstance(measure, str) else measure.value] else: for query_info in query_infos: query_info.data = pd.Series(dtype=float) for query_info in self._data_queries: if isinstance(query_info, MeasureQueryInfo): asyncio.get_event_loop().run_until_complete( query_info.processor.calculate( query_info.attr, ProcessorResult(True, None), self.rule_cache, query_info=query_info ) ) elif query_info.data is None or len(query_info.data) == 0: asyncio.get_event_loop().run_until_complete( query_info.processor.calculate( query_info.attr, ProcessorResult(False, f'No data found for Coordinate {query_info.query.coordinate}'), self.rule_cache, ) ) else: asyncio.get_event_loop().run_until_complete( query_info.processor.calculate( query_info.attr, ProcessorResult(True, query_info.data), self.rule_cache ) ) @staticmethod def aggregate_queries(query_infos): mappings = defaultdict(dict) for query_info in query_infos: query = query_info.query coordinate = query.coordinate dataset_mappings = mappings[coordinate.dataset_id] query_key = query.get_range_string() dataset_mappings.setdefault(query_key, {'parameters': {}, 'queries': {}}) queries = dataset_mappings[query_key]['queries'] queries[coordinate.get_dimensions()] = query_info parameters = dataset_mappings[query_key]['parameters'] for dimension, value in coordinate.dimensions.items(): parameters.setdefault(dimension, set()) parameters[dimension].add(value) def _post_process(self) -> pd.DataFrame: columns = self.columns results = defaultdict(list) for row in self.results: if len(row): results['rowGroup'].append(row[0].row_group or '') for column in row: column_value = column.value if column_value.success is True: column_data = column_value.data if isinstance(column_data, Number): format_: ColumnFormat = columns[column.column_index].format_ results[column.name].append(round(column_data, format_.precision)) else: results[column.name].append(column_data) else: results[column.name].append(np.nan) df = pd.DataFrame.from_dict(results) row_groups = list(df['rowGroup'].unique()) sub_dfs = [] for row_group in row_groups: sub_df = self.__handle_filters(df[df['rowGroup'] == row_group]) sub_df = self.__handle_sorts(sub_df) sub_dfs.append(sub_df) df = pd.concat(sub_dfs) df = df.set_index(['rowGroup', df.index]).rename_axis(index=['', '']) return df def __handle_sorts(self, df): """ Handles sorting of the dataframe :param df: incoming dataframe to be sorted :return: dataframe with sorting applied if any """ for sort in self.sorts: ascending = True if sort.order == SortOrder.ASCENDING else False if sort.sortType == SortType.ABSOLUTE_VALUE: df = df.reindex(df[sort.columnName].abs().sort_values(ascending=ascending, na_position='last').index) else: df = df.sort_values(by=sort.columnName, ascending=ascending, na_position='last') return df def __handle_filters(self, df) -> pd.DataFrame: """ Handles filtering the dataframe :param df: incoming dataframe to be filtered :return: dataframe with filters applied if any """ if not len(df): return df starting_df = df.copy() running_df = df for filter_ in self.filters: filter_value = filter_.value if filter_value is None: continue filter_condition = filter_.condition if filter_condition == FilterCondition.OR: df = starting_df else: df = running_df column_name = filter_.columnName operation = filter_.operation if operation == FilterOperation.TOP: df = df.sort_values(by=column_name, ascending=False, na_position='last').head(filter_value) elif operation == FilterOperation.BOTTOM: df = df.sort_values(by=column_name, ascending=True, na_position='last').head(filter_value) elif operation == FilterOperation.ABSOLUTE_TOP: df = df.reindex(df[column_name].abs().sort_values(ascending=False, na_position='last').index).head( filter_value ) elif operation == FilterOperation.ABSOLUTE_BOTTOM: df = df.reindex(df[column_name].abs().sort_values(ascending=True, na_position='last').index).head( filter_value ) elif operation == FilterOperation.EQUALS: if not isinstance(filter_value, list): filter_value = [filter_value] # Special case to handle different types of floats if isinstance(filter_value[0], str): df = df.loc[df[column_name].isin(filter_value)] else: # Add a tolerance for the special case to handle different types of floats df = df[np.isclose(df[column_name].values[:, None], filter_value, atol=1e-10).any(axis=1)] elif operation == FilterOperation.NOT_EQUALS: if not isinstance(filter_value, list): filter_value = [filter_value] if isinstance(filter_value[0], str): df = df.loc[~df[column_name].isin(filter_value)] else: # Add a tolerance for the special case to handle different types of float df = df[~np.isclose(df[column_name].values[:, None], filter_value, atol=1e-10).any(axis=1)] elif operation == FilterOperation.GREATER_THAN: df = df[df[column_name] > filter_value] elif operation == FilterOperation.LESS_THAN: df = df[df[column_name] < filter_value] elif operation == FilterOperation.LESS_THAN_EQUALS: df = df[df[column_name] <= filter_value] elif operation == FilterOperation.GREATER_THAN_EQUALS: df = df[df[column_name] >= filter_value] else: raise MqValueError(f'Invalid Filter operation Type: {operation}') if filter_.condition == FilterCondition.OR: # Need to merge the results running_df = running_df.merge(df, how='outer') else: running_df = df return running_df def to_frame(self) -> pd.DataFrame: """ Returns the results of the DataGrid data fetching and applied processors. :return: pd.DataFrame of results """ if not self.is_initialized: _logger.info("Grid has not been initialized. Ensure to run DataGrid.initialize()") return pd.DataFrame() return self._post_process() @classmethod def from_dict(cls, obj, reference_list: Optional[List] = None): id_ = obj.get('id', None) name = obj.get('name', '') parameters = obj.get('parameters', {}) entitlements = Entitlements_.from_dict(obj.get('entitlements', {})) # If a reference list is given, then the entities will be resolved by the caller if reference_list is not None: should_resolve_entities = False else: should_resolve_entities = True reference_list = [] rows = [row_from_dict(row, reference_list) for row in parameters.get('rows', [])] columns = [DataColumn.from_dict(column, reference_list) for column in parameters.get('columns', [])] sorts = [DataGridSort.from_dict(sort) for sort in parameters.get('sorts', [])] filters = [DataGridFilter.from_dict(filter_) for filter_ in parameters.get('filters', [])] multi_column_groups = [MultiColumnGroup.from_dict(group) for group in parameters.get('multiColumnGroups', [])] if should_resolve_entities: resolve_entities(reference_list) return DataGrid( name=name, rows=rows, columns=columns, id_=id_, entitlements=entitlements, primary_column_index=parameters.get('primaryColumnIndex', 0), polling_time=parameters.get('pollingTime', 0), multiColumnGroups=multi_column_groups, sorts=sorts, filters=filters, ) def as_dict(self): datagrid = { 'name': self.name, 'parameters': { 'rows': [row.as_dict() for row in self.rows], 'columns': [column.as_dict() for column in self.columns], 'primaryColumnIndex': self._primary_column_index, 'pollingTime': self.polling_time or 0, }, } if self.entitlements: if isinstance(self.entitlements, Entitlements_): datagrid['entitlements'] = self.entitlements.as_dict() elif isinstance(self.entitlements, Entitlements): datagrid['entitlements'] = self.entitlements.to_dict() else: datagrid['entitlements'] = self.entitlements if len(self.sorts): datagrid['parameters']['sorts'] = [asdict(sort) for sort in self.sorts] if len(self.filters): datagrid['parameters']['filters'] = [asdict(filter_) for filter_ in self.filters] if self.multiColumnGroups: datagrid['parameters']['multiColumnGroups'] = [group.asdict() for group in self.multiColumnGroups] return datagrid def set_primary_column_index(self, index: int): """ Sets the primary column index which affects which row will expand to fill any additional horizontal space. :param index: index of the column to make primary :return: None """ self._primary_column_index = index def set_sorts(self, sorts: List[DataGridSort]): """ Set the sorts parameter of the grid response :param sorts: value of grid sorts :return: None """ self.sorts = sorts def add_sort(self, sort: DataGridSort, index: int = None): """ Add a sort to the grid response :param sort: DataGridSort :param index: index of the sort object to be added, defaults to end of sorts list :return: None """ if index: self.sorts.insert(index, sort) else: self.sorts.append(sort) def set_filters(self, filters: List[DataGridFilter]): """ Set the filters parameter of the grid response :param filters: value of grid sorts :return: None """ self.filters = filters def add_filter(self, filter_: DataGridFilter, index: int = None): """ Add a filter to the grid response :param filter_: DataGridFilter :param index: index of the sort object to be added, defaults to end of filters list :return: None """ if index: self.filters.insert(index, filter_) else: self.filters.append(filter_) def __as_json(self) -> str: return json.dumps(self.as_dict()) def _get_overrides( row_overrides: List[Override], column_name: str ) -> Tuple[List[DimensionsOverride], Optional[ValueOverride], Optional[ProcessorOverride]]: if not row_overrides: return [], None, None dimensions_overrides, value_override, processor_override = [], None, None for override in row_overrides: if column_name in override.column_names: if isinstance(override, DimensionsOverride): dimensions_overrides.append(override) elif isinstance(override, ValueOverride): value_override = override elif isinstance(override, ProcessorOverride): processor_override = override.processor return dimensions_overrides, value_override, processor_override ================================================ FILE: gs_quant/analytics/datagrid/serializers.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on ans "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from typing import List, Dict from gs_quant.analytics.datagrid.data_row import RowSeparator, DataRow, ROW_SEPARATOR def row_from_dict(row: Dict, reference_list: List): if row.get('type') == ROW_SEPARATOR: return RowSeparator.from_dict(row) else: return DataRow.from_dict(row, reference_list) ================================================ FILE: gs_quant/analytics/datagrid/utils.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from dataclasses import dataclass, fields import datetime as dt from enum import Enum from typing import Union, List def get_utc_now() -> str: return f'{dt.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3]}Z' class SortType(str, Enum): VALUE = 'value' # Sort the column based off the value ABSOLUTE_VALUE = 'absoluteValue' # Sort the column based off the absolute value class SortOrder(str, Enum): ASCENDING = 'ascending' # Sort the column in ascending order DESCENDING = 'descending' # Sort the column in descending order class FilterOperation(str, Enum): TOP = 'top' # Top n rows by value BOTTOM = 'bottom' # Bottom n rows by value ABSOLUTE_TOP = 'absoluteTop' # Top n rows by absolute value ABSOLUTE_BOTTOM = 'absoluteBottom' # Bottom n rows by absolute value EQUALS = 'equals' # Rows with column value equal to a value or list of values NOT_EQUALS = 'notEquals' # Rows with column value not equal to a value or list of values GREATER_THAN = 'greaterThan' # Rows with column value greater than a value LESS_THAN = 'lessThan' # Rows with column value less than a value LESS_THAN_EQUALS = 'lessThanEquals' # Rows with column value greater than or equal to a value GREATER_THAN_EQUALS = 'greaterThanEquals' # Rows with column value less than or equal to a value class FilterCondition(str, Enum): AND = 'and' # Intersect the rows that match the filter OR = 'or' # Union the rows that match the filter @dataclass class DataGridSort: columnName: str sortType: SortType = SortType.VALUE order: SortOrder = SortOrder.ASCENDING def __post_init__(self): self.sortType = SortType(self.sortType) self.order = SortOrder(self.order) @classmethod def from_dict(cls, dict_): class_fields = {f.name for f in fields(cls)} return DataGridSort(**{k: v for k, v in dict_.items() if k in class_fields}) @dataclass class DataGridFilter: columnName: str operation: FilterOperation value: Union[float, str, List[float], List[str]] condition: FilterCondition = FilterCondition.AND def __post_init__(self): self.operation = FilterOperation(self.operation) self.condition = FilterCondition(self.condition) @classmethod def from_dict(cls, dict_): class_fields = {f.name for f in fields(cls)} return DataGridFilter(**{k: v for k, v in dict_.items() if k in class_fields}) ================================================ FILE: gs_quant/analytics/processors/__init__.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from .analysis_processors import DiffProcessor from .econometrics_processors import SharpeRatioProcessor, VolatilityProcessor, CorrelationProcessor, ChangeProcessor, \ ReturnsProcessor, BetaProcessor, FXImpliedCorrProcessor from .special_processors import EntityProcessor, CoordinateProcessor from .statistics_processors import PercentilesProcessor, PercentileProcessor, StdMoveProcessor, \ CovarianceProcessor, ZscoresProcessor, MeanProcessor, VarianceProcessor, SumProcessor, StdDevProcessor, \ CompoundGrowthRate from .utility_processors import LastProcessor, AppendProcessor, AdditionProcessor, SubtractionProcessor, \ MultiplicationProcessor, DivisionProcessor, MinProcessor, MaxProcessor, NthLastProcessor, OneDayProcessor from .scale_processors import ScaleProcessor, BarMarkerProcessor, SpotMarkerProcessor, ScaleShape ================================================ FILE: gs_quant/analytics/processors/analysis_processors.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from typing import Optional from gs_quant.analytics.core.processor import BaseProcessor, DataCoordinateOrProcessor, DateOrDatetimeOrRDate from gs_quant.analytics.core.processor_result import ProcessorResult from gs_quant.timeseries import diff class DiffProcessor(BaseProcessor): def __init__( self, a: DataCoordinateOrProcessor, *, obs: int = 1, start: Optional[DateOrDatetimeOrRDate] = None, end: Optional[DateOrDatetimeOrRDate] = None, **kwargs, ): """DiffProcessor :param a: DataCoordinate or BaseProcessor for the series :param obs: number of observations to lag :param start: start date or time used in the underlying data query :param end: end date or time used in the underlying data query **Usage** Compute the difference in series values over a given lag: :math:`R_t = X_t - X_{t-obs}` where :math:`obs` is the number of observations to lag series in diff function """ super().__init__(**kwargs) self.children['a'] = a self.obs = obs self.start = start self.end = end def process(self): a_data = self.children_data.get('a') if isinstance(a_data, ProcessorResult): if a_data.success: result = diff(a_data.data, self.obs) self.value = ProcessorResult(True, result) else: self.value = ProcessorResult(False, "DiffProcessor does not have 'a' series values yet") else: self.value = ProcessorResult(False, "DiffProcessor does not have 'a' series yet") return self.value def get_plot_expression(self): pass ================================================ FILE: gs_quant/analytics/processors/econometrics_processors.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from typing import Optional, Union import pandas as pd from gs_quant.analytics.core.processor import ( BaseProcessor, DataCoordinateOrProcessor, DataQueryInfo, DateOrDatetimeOrRDate, ) from gs_quant.analytics.core.processor_result import ProcessorResult from gs_quant.analytics.processors.special_processors import MeasureProcessor from gs_quant.common import Currency from gs_quant.data import DataFrequency from gs_quant.data.coordinate import DataCoordinate from gs_quant.data.query import DataQuery from gs_quant.entities.entity import Entity from gs_quant.markets.securities import Stock, Cross from gs_quant.timeseries import correlation, Window, SeriesType, DataMeasure, DataContext, fx_implied_correlation from gs_quant.timeseries import excess_returns_pure from gs_quant.timeseries.econometrics import get_ratio_pure, SharpeAssets, change, returns from gs_quant.timeseries.econometrics import volatility, Returns, beta from gs_quant.timeseries.helper import CurveType class VolatilityProcessor(BaseProcessor): def __init__( self, a: DataCoordinateOrProcessor, *, start: Optional[DateOrDatetimeOrRDate] = None, end: Optional[DateOrDatetimeOrRDate] = None, w: Union[Window, int] = Window(None, 0), returns_type: Returns = Returns.SIMPLE, **kwargs, ): """VolatilityProcessor :param a: DataCoordinate or BaseProcessor for the series to apply the volatility timeseries function :param start: start date or time used in the underlying data query :param end: end date or time used in the underlying data query :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :param returns_type: returns type: simple, logarithmic or absolute """ super().__init__(**kwargs) # coordinate self.children['a'] = a self.start = start self.end = end self.w = w self.returns_type = returns_type def process(self): a_data = self.children_data.get('a') if isinstance(a_data, ProcessorResult): if a_data.success: result = volatility(a_data.data, self.w, self.returns_type) self.value = ProcessorResult(True, result) else: self.value = ProcessorResult(False, 'Could not compute volatility') else: self.value = ProcessorResult(False, 'Processor does not have data') return self.value def get_plot_expression(self): pass class SharpeRatioProcessor(BaseProcessor): def __init__( self, a: DataCoordinateOrProcessor, *, start: Optional[DateOrDatetimeOrRDate] = None, end: Optional[DateOrDatetimeOrRDate] = None, currency: Currency, w: Union[Window, int] = None, curve_type: CurveType = CurveType.PRICES, **kwargs, ): """SharpeRatioProcessor :param a: DataCoordinate or BaseProcessor for the series of prices or excess returns :param currency: currency for risk-free rate, defaults to USD :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. :param curve_type: whether input series is of prices or excess returns, defaults to prices :param start: start date or time used in the underlying data query :param end: end date or time used in the underlying data query """ super().__init__(**kwargs) # coordinates self.children['a'] = a # datetime self.start = start self.end = end # parameters self.currency = currency self.w = w self.curve_type = curve_type # additional queries self.children['excess_returns'] = self.get_excess_returns_query() def get_excess_returns_query(self) -> DataQueryInfo: marquee_id = SharpeAssets[self.currency.value].value entity = Stock(marquee_id, "", "") coordinate: DataCoordinate = DataCoordinate(measure=DataMeasure.CLOSE_PRICE, frequency=DataFrequency.DAILY) data_query: DataQuery = DataQuery(coordinate=coordinate, start=self.start, end=self.end) return DataQueryInfo('excess_returns', None, data_query, entity) def process(self): a_data = self.children_data.get('a') excess_returns_data = self.children_data.get('excess_returns') if isinstance(a_data, ProcessorResult) and isinstance(excess_returns_data, ProcessorResult): if a_data.success and excess_returns_data.success: if self.curve_type == CurveType.PRICES: excess_returns = excess_returns_pure(a_data.data, excess_returns_data.data) else: excess_returns = a_data.data ratio = get_ratio_pure(excess_returns, self.w) self.value = ProcessorResult(True, ratio) return self.value def get_plot_expression(self): pass class CorrelationProcessor(BaseProcessor): def __init__( self, a: DataCoordinateOrProcessor, *, benchmark: Entity, start: Optional[DateOrDatetimeOrRDate] = None, end: Optional[DateOrDatetimeOrRDate] = None, w: Union[Window, int] = Window(None, 0), type_: SeriesType = SeriesType.PRICES, **kwargs, ): """CorrelationProcessor :param a: DataCoordinate or BaseProcessor for the series :param benchmark: benchmark to compare price series :param start: start date or time used in the underlying data query :param end: end date or time used in the underlying data query :param w: Window, int, or str: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :param type_: type of both input series: prices or returns """ super().__init__(**kwargs) # coordinate self.children['a'] = a # Used for additional query self.benchmark: Entity = benchmark # datetime self.start = start self.end = end self.children['benchmark'] = self.get_benchmark_coordinate() # parameters self.w = w self.type_ = type_ def get_benchmark_coordinate(self) -> DataQueryInfo: coordinate = DataCoordinate(measure=DataMeasure.CLOSE_PRICE, frequency=DataFrequency.DAILY) data_query = DataQuery(coordinate=coordinate, start=self.start, end=self.end) return DataQueryInfo('benchmark', None, data_query, self.benchmark) def process(self): a_data = self.children_data.get('a') benchmark_data = self.children_data.get('benchmark') if isinstance(a_data, ProcessorResult) and isinstance(benchmark_data, ProcessorResult): if a_data.success and benchmark_data.success: result = correlation(a_data.data, benchmark_data.data, w=self.w, type_=self.type_) self.value = ProcessorResult(True, result) else: self.value = ProcessorResult(False, "Processor does not have A and Benchmark data yet") else: self.value = ProcessorResult(False, "Processor does not have A and Benchmark data yet") return self.value def get_plot_expression(self): pass class ChangeProcessor(BaseProcessor): def __init__( self, a: DataCoordinateOrProcessor, *, start: Optional[DateOrDatetimeOrRDate] = None, end: Optional[DateOrDatetimeOrRDate] = None, **kwargs, ): """ChangeProcessor computes the change of a series :param a: DataCoordinate or BaseProcessor for the series :param start: start date or time used in the underlying data query :param end: end date or time used in the underlying data query """ super().__init__(**kwargs) # coordinate self.children['a'] = a # datetime self.start = start self.end = end def process(self): a_data = self.children_data.get('a') if isinstance(a_data, ProcessorResult): if a_data.success: value = change(a_data.data) self.value = ProcessorResult(True, value) return self.value def get_plot_expression(self): pass class ReturnsProcessor(BaseProcessor): def __init__( self, a: DataCoordinateOrProcessor, *, start: Optional[DateOrDatetimeOrRDate] = None, end: Optional[DateOrDatetimeOrRDate] = None, observations: Optional[int] = None, type_: Returns = Returns.SIMPLE, **kwargs, ): """ReturnsProcessor computes the returns of a series :param a: DataCoordinate or BaseProcessor for the series :param start: start date or time used in the underlying data query :param end: end date or time used in the underlying data query :param observations: number of observations, defaults to the return of the entire series as a single value :param type_: simple, logarithmic or absolute """ super().__init__(**kwargs) # coordinate self.children['a'] = a # datetime self.start = start self.end = end self.observations = observations self.type_ = type_ def process(self): a_data = self.children_data.get('a') if isinstance(a_data, ProcessorResult): if a_data.success: data = a_data.data if self.observations is None: if len(data) > 1: self.value = ProcessorResult(True, pd.Series([(data.iloc[-1] - data.iloc[0]) / data.iloc[0]])) else: self.value = ProcessorResult(True, 'Series has is less than 2.') else: value = returns(a_data.data, self.observations, self.type_) self.value = ProcessorResult(True, value) return self.value def get_plot_expression(self): pass class BetaProcessor(BaseProcessor): def __init__( self, a: DataCoordinateOrProcessor, b: DataCoordinateOrProcessor, *, start: Optional[DateOrDatetimeOrRDate] = None, end: Optional[DateOrDatetimeOrRDate] = None, w: Union[Window, int] = Window(None, 0), **kwargs, ): """BetaProcessor :param a: DataCoordinate or BaseProcessor for the first series :param b: DataCoordinate or BaseProcessor for the second series :param start: start date or time used in the underlying data query :param end: end date or time used in the underlying data query :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. **Usage** Calculate rolling `beta `_ If window is not provided, computes beta over the full series """ super().__init__(**kwargs) self.children['a'] = a self.children['b'] = b self.start = start self.end = end self.w = w def process(self): a_data = self.children_data.get('a') if isinstance(a_data, ProcessorResult): if a_data.success: b_data = self.children_data.get('b') # Need to check if the child node b was set in the first place. if self.children.get('b') and isinstance(b_data, ProcessorResult): if b_data.success: result = beta(a_data.data, b_data.data, w=self.w) self.value = ProcessorResult(True, result) else: self.value = ProcessorResult(True, "BetaProcessor does not have 'b' series values yet.") else: self.value = ProcessorResult(True, 'BetaProcessor: b is not a valid series.') else: self.value = ProcessorResult(False, "BetaProcessor does not have 'a' series values yet") else: self.value = ProcessorResult(False, "BetaProcessor does not have 'a' series yet") return self.value def get_plot_expression(self): pass class FXImpliedCorrProcessor(MeasureProcessor): def __init__( self, *, cross2: Entity = None, tenor: str = '3m', start: Optional[DateOrDatetimeOrRDate] = None, end: Optional[DateOrDatetimeOrRDate] = None, **kwargs, ): """CorrelationProcessor :param a: DataCoordinate or BaseProcessor for the series :param benchmark: benchmark to compare price series :param start: start date or time used in the underlying data query :param end: end date or time used in the underlying data query :param w: Window, int, or str: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :param type_: type of both input series: prices or returns """ super().__init__( **kwargs, ) self.cross2: Entity = cross2 self.tenor: str = tenor # datetime self.start = start self.end = end def process(self, cross1: Entity) -> ProcessorResult: if isinstance(cross1, Cross) and isinstance(self.cross2, Cross): try: with DataContext(self.start, self.end): result = fx_implied_correlation(cross1, self.cross2, self.tenor) self.value = ProcessorResult(True, result) except Exception as e: self.value = ProcessorResult(False, str(e)) else: self.value = ProcessorResult(False, "Processor does not have valid crosses as inputs") return self.value def get_plot_expression(self): pass ================================================ FILE: gs_quant/analytics/processors/scale_processors.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import math from typing import Union, List, Tuple, Dict from gs_quant.analytics.common.enumerators import ScaleShape from gs_quant.analytics.core import BaseProcessor from gs_quant.analytics.core.processor_result import ProcessorResult class SpotMarkerProcessor(BaseProcessor): def __init__(self, a: BaseProcessor, *, name: str, shape: ScaleShape): """SpotMarkerProcessor Spot Marker Processors are used to accommodate a single value in your scale and this value can take either a PIPE shape or a DIAMOND shape. :param a: BaseProcessor that should resolve in a single value :param name: Name of the Scale Marker :param shape: ScaleShape.PIPE or ScaleShape.DIAMOND """ if shape not in [ScaleShape.PIPE, ScaleShape.DIAMOND]: raise TypeError("SpotMarkerProcessor only allows PIPE or DIAMOND ScaleShapes.") super().__init__() self.children['a'] = a self.name = name self.shape = shape def process(self): a_data = self.children_data.get('a') if isinstance(a_data, ProcessorResult): if a_data.success: self.value = ProcessorResult( True, {'name': self.name, 'value': a_data.data.get(-1), 'shape': self.shape.value} ) else: self.value = ProcessorResult(False, 'Could not compute pipe marker') else: self.value = ProcessorResult(False, 'Processor does not have data') return self.value def get_plot_expression(self): pass class BarMarkerProcessor(BaseProcessor): def __init__(self, start: BaseProcessor, end: BaseProcessor, *, name: str): """BarMarkerProcessor Bar Marker Processors require 2 values to render correctly in the scale. Both start and end processors should resolve in single values. :param start: BaseProcessor to obtain a starting value :param end: BaseProcessor to obtain an ending value :param name: Name of the Scale Marker """ super().__init__() self.children['start'] = start self.children['end'] = end self.name = name self.shape = ScaleShape.BAR def process(self): start = self.children_data.get('start') end = self.children_data.get('end') if isinstance(start, ProcessorResult) and isinstance(end, ProcessorResult): if start.success and end.success: self.value = ProcessorResult( True, {'name': self.name, 'start': start.data.get(0), 'end': end.data.get(0), 'shape': self.shape.value}, ) else: self.value = ProcessorResult(False, "Processor does not have start and end values yet") else: self.value = ProcessorResult(False, "Processor does not have start and end data yet") return self.value def get_plot_expression(self): pass SpotOrBarMarker = Union[SpotMarkerProcessor, BarMarkerProcessor] def validate_markers_data(result: Dict, marker_data: Dict) -> Tuple[bool, str]: min_val = result['min'] max_val = result['max'] if not min_val or math.isnan(min_val): result['min'] = None return False, 'Min Value needs to exist for Scale to render' if not max_val or math.isnan(max_val): result['max'] = None return False, 'Max Value needs to exist for Scale to render' marker_name = marker_data["name"] if ScaleShape(marker_data['shape']) == ScaleShape.BAR: starting_val = marker_data['start'] ending_val = marker_data['end'] if starting_val > ending_val or starting_val < min_val or starting_val > max_val: return ( False, f'Invalid marker={marker_name} with starting value={starting_val}.' f'Has to be within range (min={min_val}, max={max_val}) and less ' f'than or equal to ending value=({ending_val})', ) if ending_val < starting_val or ending_val < min_val or ending_val > max_val: return ( False, f'Invalid marker={marker_name} with ending value={ending_val}.' f'Has to be within range (min={min_val}, max={max_val}) and greater ' f'than or equal to starting value=({starting_val})', ) else: if marker_data['value'] < min_val or marker_data['value'] > max_val: return ( False, f'Invalid marker={marker_data["name"]} with value={marker_data["value"]}. Has to be ' f'within range (min={min_val}, max={max_val})', ) return True, '' class ScaleProcessor(BaseProcessor): def __init__(self, minimum: BaseProcessor, maximum: BaseProcessor, *, markers: List[SpotOrBarMarker]): """ScaleProcessor A Scale processor can be used to render a scale in a DataGrid Column. It takes in the min value and max value processors which should resolve to single values. Additionally it take a list of markers to render within the scale. At the moment you can use wither a Spot Marker or a Bar Marker Processor. :param minimum: BaseProcessor to obtain min value for the scale :param maximum: BaseProcessor to obtain max value for the scale :param markers: List of either Spot or Bar Markers to display in the scale """ super().__init__() self.children['minimum'] = minimum self.children['maximum'] = maximum self.markers = markers for marker in markers: self.children[marker.name] = marker def process(self): min_data = self.children_data.get('minimum') max_data = self.children_data.get('maximum') markers_data = [self.children_data.get(marker.name) for marker in self.markers] if isinstance(min_data, ProcessorResult) and isinstance(max_data, ProcessorResult): if min_data.success and max_data.success: result = {'min': min_data.data.get(0), 'max': max_data.data.get(0), 'markers': []} for marker_data in markers_data: if marker_data and marker_data.success and marker_data.data: valid, reason = validate_markers_data(result, marker_data.data) if valid: result['markers'].append(marker_data.data) else: result['markers'].append({**marker_data.data, **{'invalidReason': reason}}) self.value = ProcessorResult(True, result) else: self.value = ProcessorResult(False, "Processor does not have min, max values yet") else: self.value = ProcessorResult(False, "Processor does not have min, max data yet") return self.value def get_plot_expression(self): pass ================================================ FILE: gs_quant/analytics/processors/special_processors.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from enum import Enum from typing import Union from pydash import get from gs_quant.analytics.core.processor import BaseProcessor from gs_quant.analytics.core.processor_result import ProcessorResult from gs_quant.data import DataDimension, DataCoordinate from gs_quant.entities.entity import Entity class EntityProcessor(BaseProcessor): def __init__(self, field: str): """EntityProcessor gets a value off an entity object :param field: The entity property to be returned. If a nested field, separate each level with ".", i.e. 'xref.bbid' """ super().__init__() self.field = field def process(self, entity: Entity) -> ProcessorResult: """Fetch the entity and resolve the field""" if isinstance(entity, str): # If we were unable to fetch entity (404/403) return ProcessorResult(False, f"Unable to resolve Entity {entity}") try: # First try to get the value off the entity entity_dict = entity.get_entity() data = get(entity_dict, self.field) if data: return ProcessorResult(True, data) # If not found, try to get the value from the asset identifiers identifier = next(iter(filter(lambda x: x['type'] == self.field, entity_dict.get('identifiers', []))), None) if identifier: return ProcessorResult(True, identifier['value']) # Return a failed processor result if no field was found on the object or it's identifiers return ProcessorResult( False, f'Unable to find {self.field} in identifiers for entity {entity.get_marquee_id()}' ) except ValueError: return ProcessorResult(False, "Could not get field on entity") def update(self, attribute: str, result: ProcessorResult) -> None: """This method does not apply for entity processor""" pass def get_plot_expression(self): """This method does not apply for entity processor""" pass class CoordinateProcessor(BaseProcessor): def __init__(self, a: DataCoordinate, dimension: Union[DataDimension, str]): """Returns a field from a coordinate :param a: coordinate to get the field :param dimension: dimension to get from the coordinate """ super().__init__() # coordinate self.children['a'] = a # parameters self.dimension = dimension def process(self): key: str = self.dimension.value if isinstance(self.dimension, Enum) else self.dimension coordinate = self.children.get('a') dimension_value = coordinate.dimensions.get(key) if coordinate else None if dimension_value: return ProcessorResult(True, dimension_value) else: return ProcessorResult(False, f'Dimension {key} not in given coordinate') def update(self, attribute: str, result: ProcessorResult): pass def get_plot_expression(self): pass class MeasureProcessor(BaseProcessor): def __init__(self, **kwargs): super().__init__(**kwargs, measure_processor=True) def process(self, *args): pass def get_plot_expression(self): pass ================================================ FILE: gs_quant/analytics/processors/statistics_processors.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from typing import Optional, Union import pandas as pd from gs_quant.analytics.core.processor import BaseProcessor, DataCoordinateOrProcessor, DateOrDatetimeOrRDate from gs_quant.analytics.core.processor_result import ProcessorResult from gs_quant.timeseries import returns from gs_quant.timeseries.statistics import percentiles, percentile, Window, mean, sum_, std, var, cov, zscores class PercentilesProcessor(BaseProcessor): def __init__( self, a: DataCoordinateOrProcessor, *, b: Optional[DataCoordinateOrProcessor] = None, start: Optional[DateOrDatetimeOrRDate] = None, end: Optional[DateOrDatetimeOrRDate] = None, w: Union[Window, int] = Window(None, 0), **kwargs, ): """PercentilesProcessor :param a: DataCoordinate or BaseProcessor for the first series :param b: DataCoordinate or BaseProcessor for the second series :param start: start date or time used in the underlying data query :param end: end date or time used in the underlying data query :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. """ super().__init__(**kwargs) self.children['a'] = a self.children['b'] = b self.start = start self.end = end self.w = w def process(self): a_data = self.children_data.get('a') if isinstance(a_data, ProcessorResult): if a_data.success: b_data = self.children_data.get('b') # Need to check if the child node b was set in the first place. if self.children.get('b') and isinstance(b_data, ProcessorResult): if b_data.success: result = percentiles(a_data.data, b_data.data, w=self.w) self.value = ProcessorResult(True, result) else: self.value = ProcessorResult(True, 'PercentilesProcessor: b is not a valid series.') result = percentiles(a_data.data, w=self.w) self.value = ProcessorResult(True, result) else: self.value = ProcessorResult(False, "PercentilesProcessor does not have 'a' series values yet") else: self.value = ProcessorResult(False, "PercentilesProcessor does not have 'a' series yet") return self.value def get_plot_expression(self): pass class PercentileProcessor(BaseProcessor): def __init__( self, a: DataCoordinateOrProcessor, *, n: float, start: Optional[DateOrDatetimeOrRDate] = None, end: Optional[DateOrDatetimeOrRDate] = None, w: Union[Window, int] = None, **kwargs, ): """PercentileProcessor :param a: DataCoordinate or BaseProcessor for the series :param n: Percentile :param start: start date or time used in the underlying data query :param end: end date or time used in the underlying data query :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. """ super().__init__(**kwargs) self.children['a'] = a self.n = n self.start = start self.end = end self.w = w def process(self): a_data = self.children_data.get('a') if isinstance(a_data, ProcessorResult): if a_data.success: series_length = len(a_data.data) window = None if self.w: window = self.w if self.w <= series_length else series_length result = percentile(a_data.data, self.n, w=window) if not isinstance(result, pd.Series): result = pd.Series(result) self.value = ProcessorResult(True, result) else: self.value = ProcessorResult(False, "PercentileProcessor does not have 'a' series values yet") else: self.value = ProcessorResult(False, "PercentileProcessor does not have 'a' series yet") return self.value def get_plot_expression(self): pass class MeanProcessor(BaseProcessor): def __init__( self, a: DataCoordinateOrProcessor, *, start: Optional[DateOrDatetimeOrRDate] = None, end: Optional[DateOrDatetimeOrRDate] = None, w: Union[Window, int] = None, **kwargs, ): """MeanProcessor :param a: DataCoordinate or BaseProcessor for the series :param start: start date or time used in the underlying data query :param end: end date or time used in the underlying data query :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. **Usage** Calculates `arithmetic mean `_ of the series over a rolling window If window is not provided, computes rolling mean over the full series. If the window size is greater than the available data, will return mean of available values. """ super().__init__(**kwargs) self.children['a'] = a self.start = start self.end = end self.w = w def process(self): a_data = self.children_data.get('a') if isinstance(a_data, ProcessorResult): if a_data.success: series_length = len(a_data.data) window = None if self.w: window = self.w if self.w <= series_length else series_length result = mean(a_data.data, w=window) self.value = ProcessorResult(True, result) else: self.value = ProcessorResult(False, "MeanProcessor does not have 'a' series values yet") else: self.value = ProcessorResult(False, "MeanProcessor does not have 'a' series yet") return self.value def get_plot_expression(self): pass class SumProcessor(BaseProcessor): def __init__( self, a: DataCoordinateOrProcessor, *, start: Optional[DateOrDatetimeOrRDate] = None, end: Optional[DateOrDatetimeOrRDate] = None, w: Union[Window, int] = None, **kwargs, ): """SumProcessor :param a: DataCoordinate or BaseProcessor for the series :param start: start date or time used in the underlying data query :param end: end date or time used in the underlying data query :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. **Usage** Calculate the sum of observations over a given rolling window. If window is not provided, computes sum over the full series. If the window size is greater than the available data, will return sum of available values. """ super().__init__(**kwargs) self.children['a'] = a self.start = start self.end = end self.w = w def process(self): a_data = self.children_data.get('a') if isinstance(a_data, ProcessorResult): if a_data.success: series_length = len(a_data.data) window = None if self.w: window = self.w if self.w <= series_length else series_length result = sum_(a_data.data, w=window) self.value = ProcessorResult(True, result) else: self.value = ProcessorResult(False, "SumProcessor does not have 'a' series values yet") else: self.value = ProcessorResult(False, "SumProcessor does not have 'a' series yet") return self.value def get_plot_expression(self): pass class StdDevProcessor(BaseProcessor): def __init__( self, a: DataCoordinateOrProcessor, *, start: Optional[DateOrDatetimeOrRDate] = None, end: Optional[DateOrDatetimeOrRDate] = None, w: Union[Window, int] = Window(None, 0), **kwargs, ): """StdDevProcessor :param a: DataCoordinate or BaseProcessor for the first series :param start: start date or time used in the underlying data query :param end: end date or time used in the underlying data query :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. **Usage** Provides `unbiased estimator `_ of sample standard deviation `_ over a rolling window If window is not provided, computes standard deviation over the full series """ super().__init__(**kwargs) self.children['a'] = a self.start = start self.end = end self.w = w def process(self): a_data = self.children_data.get('a') if isinstance(a_data, ProcessorResult): if a_data.success: series_length = len(a_data.data) window = None if self.w: window = self.w if self.w <= series_length else series_length result = std(a_data.data, w=window) self.value = ProcessorResult(True, result) else: self.value = ProcessorResult(False, "StdDevProcessor does not have 'a' series values yet") else: self.value = ProcessorResult(False, "StdDevProcessor does not have 'a' series yet") return self.value def get_plot_expression(self): pass class VarianceProcessor(BaseProcessor): def __init__( self, a: DataCoordinateOrProcessor, *, start: Optional[DateOrDatetimeOrRDate] = None, end: Optional[DateOrDatetimeOrRDate] = None, w: Union[Window, int] = Window(None, 0), **kwargs, ): """VarianceProcessor :param a: DataCoordinate or BaseProcessor for the first series :param start: start date or time used in the underlying data query :param end: end date or time used in the underlying data query :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. **Usage** Provides `unbiased estimator `_ of sample variance `_ over a rolling window If window is not provided, computes variance over the full series """ super().__init__(**kwargs) self.children['a'] = a self.start = start self.end = end self.w = w def process(self): a_data = self.children_data.get('a') if isinstance(a_data, ProcessorResult): if a_data.success: series_length = len(a_data.data) window = None if self.w: window = self.w if self.w <= series_length else series_length result = var(a_data.data, w=window) self.value = ProcessorResult(True, result) else: self.value = ProcessorResult(False, "VarianceProcessor does not have 'a' series values yet") else: self.value = ProcessorResult(False, "VarianceProcessor does not have 'a' series yet") return self.value def get_plot_expression(self): pass class CovarianceProcessor(BaseProcessor): def __init__( self, a: DataCoordinateOrProcessor, b: DataCoordinateOrProcessor, *, start: Optional[DateOrDatetimeOrRDate] = None, end: Optional[DateOrDatetimeOrRDate] = None, w: Union[Window, int] = Window(None, 0), **kwargs, ): """CovarianceProcessor :param a: DataCoordinate or BaseProcessor for the first series :param b: DataCoordinate or BaseProcessor for the second series :param start: start date or time used in the underlying data query :param end: end date or time used in the underlying data query :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. **Usage** Provides `unbiased estimator `_ of sample co-variance `_ over a rolling window: If window is not provided, computes variance over the full series """ super().__init__(**kwargs) self.children['a'] = a self.children['b'] = b self.start = start self.end = end self.w = w def process(self): a_data = self.children_data.get('a') if isinstance(a_data, ProcessorResult): if a_data.success: b_data = self.children_data.get('b') # Need to check if the child node b was set in the first place. if self.children.get('b') and isinstance(b_data, ProcessorResult): if b_data.success: result = cov(a_data.data, b_data.data, w=self.w) self.value = ProcessorResult(True, result) else: self.value = ProcessorResult(True, "CovarianceProcessor does not 'b' series values yet.") else: self.value = ProcessorResult(True, 'CovarianceProcessor: b is not a valid series.') else: self.value = ProcessorResult(False, "CovarianceProcessor does not have 'a' series values yet") else: self.value = ProcessorResult(False, "CovarianceProcessor does not have 'a' series yet") return self.value def get_plot_expression(self): pass class ZscoresProcessor(BaseProcessor): def __init__( self, a: DataCoordinateOrProcessor, *, start: Optional[DateOrDatetimeOrRDate] = None, end: Optional[DateOrDatetimeOrRDate] = None, w: Union[Window, int] = None, **kwargs, ): """ZscoresProcessor :param a: DataCoordinate or BaseProcessor for the series :param start: start date or time used in the underlying data query :param end: end date or time used in the underlying data query :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. **Usage** Calculate `standard score `_ of each value in series over given window. Standard deviation and sample mean are computed over the specified rolling window, then element is normalized to provide a rolling z-score If window is not provided, computes z-score relative to mean and standard deviation over the full series """ super().__init__(**kwargs) self.children['a'] = a self.start = start self.end = end self.w = w def process(self): a_data = self.children_data.get('a') if isinstance(a_data, ProcessorResult): if a_data.success: result = zscores(a_data.data, w=Window(None, 0) if self.w is None else self.w) self.value = ProcessorResult(True, result) else: self.value = ProcessorResult(False, "ZscoresProcessor does not have 'a' series values yet") else: self.value = ProcessorResult(False, "ZscoresProcessor does not have 'a' series yet") return self.value def get_plot_expression(self): pass class StdMoveProcessor(BaseProcessor): def __init__( self, a: DataCoordinateOrProcessor, *, start: Optional[DateOrDatetimeOrRDate] = None, end: Optional[DateOrDatetimeOrRDate] = None, w: Union[Window, int] = None, **kwargs, ): """StdMoveProcessor: Returns normalized by std deviation of series a :param a: DataCoordinate or BaseProcessor for the first series :param start: start date or time used in the underlying data query :param end: end date or time used in the underlying data query :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. """ super().__init__(**kwargs) self.children['a'] = a self.start = start self.end = end self.w = w def process(self): a_data = self.children_data.get('a') if isinstance(a_data, ProcessorResult): if a_data.success: data_series = a_data.data change_pd = data_series.tail(2) change = returns(change_pd).iloc[-1] # Pass in all values except last value (which is last value) returns_series = returns(data_series.head(-1)) std_result = std(returns_series, w=Window(None, 0) if self.w is None else self.w).iloc[-1] if change is not None and std_result != 0: self.value = ProcessorResult(True, pd.Series([change / std_result])) else: self.value = ProcessorResult(False, "StdMoveProcessor returns a NaN") else: self.value = ProcessorResult(False, "StdMoveProcessor does not have 'a' series values yet") else: self.value = ProcessorResult(False, "StdMoveProcessor does not have 'a' series yet") return self.value def get_plot_expression(self): pass class CompoundGrowthRate(BaseProcessor): def __init__( self, a: DataCoordinateOrProcessor, *, start: Optional[DateOrDatetimeOrRDate] = None, end: Optional[DateOrDatetimeOrRDate] = None, n: Optional[float] = None, **kwargs, ): """CompoundGrowthRate: indicates the growth rate over given time period n :param a: DataCoordinate or BaseProcessor of series :param start: start date or time used in the underlying data query :param end: end date or time used in the underlying data query :param n: Number of time """ super().__init__(**kwargs) self.children['a'] = a self.start = start self.end = end self.n = n def process(self): a_data = self.children_data.get('a') if isinstance(a_data, ProcessorResult): if a_data.success: data_series = a_data.data self.value = ProcessorResult( True, pd.Series([(data_series.iloc[-1] / data_series.iloc[0]) ** (1 / self.n) - 1]) ) else: self.value = ProcessorResult(False, "CompoundGrowthRate does not have 'a' series values yet") else: self.value = ProcessorResult(False, "CompoundGrowthRate does not have 'a' series yet") return self.value def get_plot_expression(self): pass ================================================ FILE: gs_quant/analytics/processors/utility_processors.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from typing import Optional import pandas as pd from gs_quant.analytics.core.processor import BaseProcessor, DataCoordinateOrProcessor, DateOrDatetimeOrRDate from gs_quant.analytics.core.processor_result import ProcessorResult class LastProcessor(BaseProcessor): def __init__( self, a: DataCoordinateOrProcessor, *, start: Optional[DateOrDatetimeOrRDate] = None, end: Optional[DateOrDatetimeOrRDate] = None, **kwargs, ): """LastProcessor returns the last value of the series :param a: DataCoordinate or BaseProcessor for the first coordinate :param start: start date or time used in the underlying data query :param end: end date or time used in the underlying data query """ super().__init__(**kwargs) # coordinates self.children['a'] = a # datetime self.start = start self.end = end def process(self): """Calculate the result and store it as the processor value""" a_data = self.children_data.get('a') if isinstance(a_data, ProcessorResult): if a_data.success and isinstance(a_data.data, pd.Series): self.value = ProcessorResult(True, pd.Series(a_data.data[-1:])) return self.value def get_plot_expression(self): pass class MinProcessor(BaseProcessor): def __init__( self, a: DataCoordinateOrProcessor, *, start: Optional[DateOrDatetimeOrRDate] = None, end: Optional[DateOrDatetimeOrRDate] = None, **kwargs, ): """MinProcessor returns the minimum value of the series :param a: DataCoordinate or BaseProcessor for the coordinate :param start: start date or time used in the underlying data query :param end: end date or time used in the underlying data query """ super().__init__(**kwargs) # coordinates self.children['a'] = a # datetime self.start = start self.end = end def process(self): """Calculate the result and store it as the processor value""" a = self.children_data.get('a') if isinstance(a, ProcessorResult): if a.success and isinstance(a.data, pd.Series): self.value = ProcessorResult(True, pd.Series(min(a.data))) else: self.value = ProcessorResult(False, "Processor does not data series yet") else: self.value = ProcessorResult(False, "Processor does not have series yet") return self.value def get_plot_expression(self): pass class MaxProcessor(BaseProcessor): def __init__( self, a: DataCoordinateOrProcessor, *, start: Optional[DateOrDatetimeOrRDate] = None, end: Optional[DateOrDatetimeOrRDate] = None, **kwargs, ): """MaxProcessor returns the maximum value of the series :param a: DataCoordinate or BaseProcessor for the coordinate :param start: start date or time used in the underlying data query :param end: end date or time used in the underlying data query """ super().__init__(**kwargs) # coordinates self.children['a'] = a # datetime self.start = start self.end = end def process(self): """Calculate the result and store it as the processor value""" a = self.children_data.get('a') if isinstance(a, ProcessorResult): if a.success and isinstance(a.data, pd.Series): self.value = ProcessorResult(True, pd.Series(max(a.data))) else: self.value = ProcessorResult(False, "Processor does not have data series yet") else: self.value = ProcessorResult(False, "Processor does not have series yet") return self.value def get_plot_expression(self): pass class AppendProcessor(BaseProcessor): def __init__( self, a: DataCoordinateOrProcessor, b: DataCoordinateOrProcessor, *, start: Optional[DateOrDatetimeOrRDate] = None, end: Optional[DateOrDatetimeOrRDate] = None, **kwargs, ): """AppendProcessor appends both a and b data series into one series :param a: DataCoordinate or BaseProcessor for the first series :param b: DataCoordinate or BaseProcessor for the second series :param start: start date or time used in the underlying data query :param end: end date or time used in the underlying data query """ super().__init__(**kwargs) # coordinates self.children['a'] = a self.children['b'] = b # datetime self.start = start self.end = end def process(self): a_data = self.children_data.get('a') b_data = self.children_data.get('b') if isinstance(a_data, ProcessorResult) and isinstance(b_data, ProcessorResult): if a_data.success and b_data.success: result = a_data.data.append(b_data.data) self.value = ProcessorResult(True, result) else: self.value = ProcessorResult(False, "Processor does not have A and B data yet") else: self.value = ProcessorResult(False, "Processor does not have A and B data yet") return self.value def get_plot_expression(self): pass class AdditionProcessor(BaseProcessor): def __init__( self, a: DataCoordinateOrProcessor, b: Optional[DataCoordinateOrProcessor] = None, *, start: Optional[DateOrDatetimeOrRDate] = None, end: Optional[DateOrDatetimeOrRDate] = None, addend: Optional[float] = None, **kwargs, ): """AdditionProcessor adds two series or an addend to a series :param a: DataCoordinate or BaseProcessor for the first series :param b: DataCoordinate or BaseProcessor for the second series to add to the first :param start: start date or time used in the underlying data query :param end: end date or time used in the underlying data query :param addend: number to add to all values in the series """ super().__init__(**kwargs) # coordinates self.children['a'] = a self.children['b'] = b # datetime self.start = start self.end = end self.addend = addend def process(self): a_data = self.children_data.get('a') if isinstance(a_data, ProcessorResult): if not a_data.success: self.value = a_data return self.value if self.addend: value = a_data.data.add(self.addend) self.value = ProcessorResult(True, value) return self.value b_data = self.children_data.get('b') if isinstance(b_data, ProcessorResult): if b_data.success: value = a_data.data.add(b_data.data) self.value = ProcessorResult(True, value) else: self.value = ProcessorResult(True, b_data.data) return self.value def get_plot_expression(self): pass class SubtractionProcessor(BaseProcessor): def __init__( self, a: DataCoordinateOrProcessor, b: Optional[DataCoordinateOrProcessor] = None, *, start: Optional[DateOrDatetimeOrRDate] = None, end: Optional[DateOrDatetimeOrRDate] = None, subtrahend: Optional[float] = None, **kwargs, ): """SubtractionProcessor subtract two series or a subtrahend to a series :param a: DataCoordinate or BaseProcessor for the first series :param b: DataCoordinate or BaseProcessor for the second series to subtract to the first :param start: start date or time used in the underlying data query :param end: end date or time used in the underlying data query :param subtrahend: number to subtract from all values in the series """ super().__init__(**kwargs) # coordinates self.children['a'] = a self.children['b'] = b # datetime self.start = start self.end = end self.subtrahend = subtrahend def process(self): a_data = self.children_data.get('a') if isinstance(a_data, ProcessorResult): if not a_data.success: self.value = a_data return self.value if self.subtrahend: value = a_data.data.sub(self.subtrahend) self.value = ProcessorResult(True, value) return self.value b_data = self.children_data.get('b') if isinstance(b_data, ProcessorResult): if b_data.success: value = a_data.data.sub(b_data.data) self.value = ProcessorResult(True, value) else: self.value = b_data return self.value def get_plot_expression(self): pass class MultiplicationProcessor(BaseProcessor): """Multiply scalar or series together""" def __init__( self, a: DataCoordinateOrProcessor, b: Optional[DataCoordinateOrProcessor] = None, *, start: Optional[DateOrDatetimeOrRDate] = None, end: Optional[DateOrDatetimeOrRDate] = None, factor: Optional[float] = None, **kwargs, ): """MultiplicationProcessor multiply two series or a factor to a series :param a: DataCoordinate or BaseProcessor for the first series :param b: DataCoordinate or BaseProcessor for the second series to multiply to the first :param start: start date or time used in the underlying data query :param end: end date or time used in the underlying data query :param factor: number to multiply all values in the series """ super().__init__(**kwargs) # coordinates self.children['a'] = a self.children['b'] = b # datetime self.start = start self.end = end self.factor = factor def process(self): a_data = self.children_data.get('a') if isinstance(a_data, ProcessorResult): if not a_data.success: self.value = a_data return a_data if self.factor: value = a_data.data.mul(self.factor) self.value = ProcessorResult(True, value) return self.value b_data = self.children_data.get('b') if isinstance(b_data, ProcessorResult): if b_data.success: value = a_data.data.mul(b_data.data) self.value = ProcessorResult(True, value) else: self.value = b_data return self.value def get_plot_expression(self): pass class DivisionProcessor(BaseProcessor): def __init__( self, a: DataCoordinateOrProcessor, b: Optional[DataCoordinateOrProcessor] = None, *, start: Optional[DateOrDatetimeOrRDate] = None, end: Optional[DateOrDatetimeOrRDate] = None, dividend: Optional[float] = None, **kwargs, ): """DivisionProcessor divides two series or divides a dividend to a series :param a: DataCoordinate or BaseProcessor for the first series :param b: DataCoordinate or BaseProcessor for the second series to multiply to the first :param start: start date or time used in the underlying data query :param end: end date or time used in the underlying data query :param dividend: number to divide all values in the series """ super().__init__(**kwargs) # coordinates self.children['a'] = a self.children['b'] = b # datetime self.start = start self.end = end self.dividend = dividend def process(self): a_data = self.children_data.get('a') if isinstance(a_data, ProcessorResult): if not a_data.success: self.value = a_data return self.value if self.dividend: value = a_data.data.div(self.dividend) self.value = ProcessorResult(True, value) return self.value b_data = self.children_data.get('b') if isinstance(b_data, ProcessorResult): if b_data.success: value = a_data.data.div(b_data.data) self.value = ProcessorResult(True, value) else: self.value = b_data return self.value def get_plot_expression(self): pass class OneDayProcessor(BaseProcessor): def __init__(self, a: DataCoordinateOrProcessor, **kwargs): super().__init__(**kwargs) self.children['a'] = a def process(self): a_data = self.children_data.get('a') if isinstance(a_data, ProcessorResult): if not a_data.success: self.value = a_data return self.value data = a_data.data if len(data) >= 2: value = data.drop(data.index[-1].date(), errors='ignore') if len(value) >= 2: self.value = ProcessorResult(True, value[-2:]) return self.value self.value = ProcessorResult(False, 'Not enough values given to OneDayProcessor.') return self.value def get_plot_expression(self): pass class NthLastProcessor(BaseProcessor): def __init__( self, a: DataCoordinateOrProcessor, *, n: int = 1, start: Optional[DateOrDatetimeOrRDate] = None, end: Optional[DateOrDatetimeOrRDate] = None, **kwargs, ): """LastProcessor returns the last value of the series :param a: DataCoordinate or BaseProcessor for the first coordinate :param start: start date or time used in the underlying data query :param end: end date or time used in the underlying data query """ super().__init__(**kwargs) # coordinates self.children['a'] = a # datetime self.start = start self.end = end self.n = n def process(self): """Calculate the result and store it as the processor value""" a_data = self.children_data.get('a') if isinstance(a_data, ProcessorResult): if a_data.success and isinstance(a_data.data, pd.Series): index = -1 * self.n self.value = ProcessorResult(True, pd.Series(a_data.data[index])) else: self.value = ProcessorResult(False, "NthLastProcessor does not have 'a' series values yet") else: self.value = ProcessorResult(False, "NthLastProcessor does not have 'a' series values yet") return self.value def get_plot_expression(self): pass ================================================ FILE: gs_quant/analytics/workspaces/__init__.py ================================================ from .components import (ArticleComponent, CommentaryComponent, Component, ContainerComponent, DataGridComponent, LegendComponent, MonitorComponent, PlotComponent, PromoComponent, RelatedLinksComponent, SelectorComponent, SeparatorComponent, Selection, LegendItem, RelatedLink, RelatedLinkType) from .workspace import (Workspace, WorkspaceColumn, WorkspaceRow, WorkspaceCallToAction, WorkspaceTab) ================================================ FILE: gs_quant/analytics/workspaces/components.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import uuid from abc import ABC, abstractmethod from enum import Enum from typing import Dict, List, Optional from pydash import unset, snake_case class Selection: def __init__(self, selector_id: str, tag: str): """ Selection option. :param selector_id: identifier of the selector which applies to this selection :param tag: tag to match in the selector. Will show up as the option in the associated selector. """ self.__selector_id = selector_id self.__tag = tag @property def selector_id(self): return self.__selector_id @selector_id.setter def selector_id(self, value): self.__selector_id = value @property def tag(self): return self.__tag @tag.setter def tag(self, value): self.__tag = value def as_dict(self): return {'selectorId': self.__selector_id, 'tag': self.__tag} @classmethod def from_dict(cls, obj): return Selection(obj['selectorId'], obj['tag']) class LegendItem: def __init__(self, color: str, icon: str, name: str, tooltip: str = None): """ Item in the legend component :param color: color of the legend item :param icon: icon of the legend :param name: name of the legend item :param tooltip: tooltip to display on the the name """ self.color = color self.icon = icon self.name = name self.tooltip = tooltip def as_dict(self): dict_ = {'color': self.color, 'icon': self.icon, 'name': self.name} if self.tooltip: dict_['tooltip'] = self.tooltip return dict_ @classmethod def from_dict(cls, obj): return LegendItem(color=obj['color'], icon=obj['icon'], name=obj['name'], tooltip=obj.get('tooltip')) class RelatedLinkType(Enum): anchor = 'anchor' internal = 'internal' external = 'external' mail = 'mail' notification = 'notification' class RelatedLink: def __init__(self, type_: RelatedLinkType, name: str, link: str, description: str = None): """ Related Link Item :param type_: Type of the Related Link :param name: Name that appears on the related links :param link: link to navigate when the item is clicked :param description: description of the link """ self.type_ = type_ self.name = name self.link = link self.description = description # TODO: self.notification_properties def as_dict(self): dict_ = {'type': self.type_.value, 'name': self.name, 'link': self.link} if self.description: dict_['description'] = self.description return dict_ @classmethod def from_dict(cls, obj): return RelatedLink( type_=RelatedLinkType(obj['type']), name=obj['name'], link=obj['link'], description=obj.get('description') ) class PromoSize(Enum): DEFAULT = 'default' LARGE = 'large' class Component(ABC): def __init__( self, height: Optional[int] = None, id_: Optional[str] = None, *, width: int = None, selections: List[Selection] = None, container_ids: List[str] = None, ): self.__id = id_ or f'{self.__class__.__name__}-{str(uuid.uuid4())[0:5]}' self._height = height self.__width = width self.__selections = selections self.__container_ids = container_ids self._type = None @property def id_(self): return self.__id @id_.setter def id_(self, value): self.__id = value or f'{self.__class__.__name__}-{str(uuid.uuid4())[0:5]}' @property def width(self): return self.__width @width.setter def width(self, value): self.__width = value @property def height(self): return self._height @height.setter def height(self, value): self._height = value @property def selections(self): return self.__selections @selections.setter def selections(self, value): self.__selections = value @property def container_ids(self): return self.__container_ids @container_ids.setter def container_ids(self, value): self.__container_ids = value @abstractmethod def as_dict(self) -> Dict: dict_ = {'id': self.__id, 'type': self._type, 'parameters': {'height': self._height or 200}} if self.__selections: dict_['selections'] = [selection.as_dict() for selection in self.__selections] if self.__container_ids: dict_['containerIds'] = [containerId for containerId in self.__container_ids] return dict_ @classmethod def from_dict(cls, obj, scale: int = None): parameters = obj.get('parameters', {}) height = parameters.get('height', 200) unset(parameters, 'height') unset(parameters, 'width') component = TYPE_TO_COMPONENT[obj['type']]( id_=obj['id'], height=height, width=scale, **{snake_case(k): v for k, v in parameters.items()} ) selections, container_ids, tags = obj.get('selections'), obj.get('containerIds'), obj.get('tags') if selections: component.selections = [Selection.from_dict(selection) for selection in selections] if container_ids: component.__container_ids = [containerId for containerId in container_ids] if tags: component.tags = tags return component class PlotComponent(Component): def __init__( self, height: int, id_: str, *, width: int = None, selections: List[Selection] = None, tooltip: str = None, hide_legend: bool = False, ): """ Plot Component :param id_: identifier of the plot :param height: height of the component :param width: width of the component integers 1-12 :param selections: List of selections used for selectors :param tooltip: text to show in a tooltip on the chart name :param hide_legend: whether to hide the series legend under the plot """ super().__init__(id_=id_, height=height, width=width, selections=selections) self._type = 'plot' self.tooltip = tooltip self.hide_legend = hide_legend def as_dict(self) -> Dict: dict_ = super().as_dict() dict_['parameters']['hideLegend'] = self.hide_legend if self.tooltip: dict_['parameters']['tooltip'] = self.tooltip return dict_ class DataVizComponent(Component): def __init__(self, height: int, id_: str, *, width: int = None): """ Data Visualization Component :param id_: identifier of the Visualization :param height: height of the componentTYPE_TO_COMPONENT :param width: width of the component integers 1-12 """ super().__init__(id_=id_, height=height, width=width) self._type = 'dataviz' def as_dict(self) -> Dict: return super().as_dict() class DataGridComponent(Component): def __init__( self, height: int, id_: str, *, width: int = None, selections: List[Selection] = None, tooltip: str = None ): """ DataGrid Component :param height: height of the component :param id_: unique identifier of the DataGrid :param width: width of the component integers 1-12 :param selections: List of selections used for selectors :param tooltip: text to show in a tooltip on the DataGrid name """ super().__init__(id_=id_, height=height, width=width, selections=selections) self._type = 'datagrid' self.tooltip = tooltip def as_dict(self) -> Dict: dict_ = super().as_dict() if self.tooltip: dict_['parameters']['tooltip'] = self.tooltip return dict_ class DataScreenerComponent(Component): def __init__(self, height: int, id_: str, *, width: int = None, tooltip: str = None): """ Data Screener Component :param height: height of the component :param id_: unique identifier of the Data Screener :param width: width of the component integers 1-12 :param tooltip: text to show in a tooltip on the Data Screener name """ super().__init__(id_=id_, height=height, width=width) self._type = 'screener' self.tooltip = tooltip def as_dict(self) -> Dict: dict_ = super().as_dict() if self.tooltip: dict_['parameters']['tooltip'] = self.tooltip return dict_ class ArticleComponent(Component): def __init__( self, height: int, id_: Optional[str] = None, *, width: int = None, selections: List[Selection] = None, tooltip: str = None, commentary_channels: List[str] = None, commentary_to_desktop_link: bool = None, ): """ Article Component :param height: height of the component :param id_: unique identifier of the component :param width: width of the component integers 1-12 :param selections: List of selections used for selectors :param tooltip: text to show in a tooltip on the article component name :param commentary_channels: List of commentary channels that provides data :param commentary_to_desktop_link: Whether or not to display a link from commentary to desktop in the header """ super().__init__(id_=id_, height=height, width=width, selections=selections) self._type = 'article' self.tooltip = tooltip self.commentary_channels = commentary_channels self.commentary_to_desktop_link = commentary_to_desktop_link def as_dict(self) -> Dict: dict_ = super().as_dict() if self.tooltip: dict_['parameters']['tooltip'] = self.tooltip if self.commentary_channels: dict_['parameters']['commentaryChannels'] = self.commentary_channels if self.commentary_to_desktop_link: dict_['parameters']['commentaryToDesktopLink'] = self.commentary_to_desktop_link return dict_ class CommentaryComponent(Component): def __init__( self, height: int, id_: Optional[str] = None, *, width: int = None, selections: List[Selection] = None, tooltip: str = None, commentary_channels: List[str] = None, commentary_to_desktop_link: bool = None, ): """ Commentary Component :param height: height of the component :param id_: unique identifier of the component :param width: width of the component integers 1-12 :param selections: List of selections used for selectors :param tooltip: text to show in a tooltip on the article component name :param commentary_channels: List of commentary channels that provides data :param commentary_to_desktop_link: Whether or not to display a link from commentary to desktop in the header """ super().__init__(id_=id_, height=height, width=width, selections=selections) self._type = 'plot' self.tooltip = tooltip self.commentary_channels = commentary_channels self.commentary_to_desktop_link = commentary_to_desktop_link def as_dict(self) -> Dict: dict_ = super().as_dict() if self.tooltip: dict_['parameters']['tooltip'] = self.tooltip if self.commentary_channels: dict_['parameters']['commentaryChannels'] = self.commentary_channels if self.commentary_to_desktop_link: dict_['parameters']['commentaryToDesktopLink'] = self.commentary_to_desktop_link return dict_ class ContainerComponent(Component): def __init__(self, id_: Optional[str] = None, *, width: int = None, component_id: str = None): """ Container Component which acts as a placeholder for components used with selectors :param id_: unique identifier of the component :param width: width of the component integers 1-12 :param component_id: default component id to use in the container """ super().__init__(id_=id_, width=width, selections=None) self._type = 'container' self.component_id = component_id def as_dict(self) -> Dict: dict_ = super().as_dict() if self.component_id: dict_['parameters']['componentId'] = self.component_id del dict_['parameters']['height'] return dict_ class SelectorComponent(Component): def __init__( self, height: int, id_: Optional[str] = None, *, container_ids: List[str], width: int = None, title: str = None, default_option_index: int = None, tooltip: str = None, parent_selector_id: str = None, ): """ Selector Component to conditionally pick components based on their selection tags. :param height: height of the component :param id_: unique identifier of the component :param container_ids: Name of containers affected by the selector :param width: width of the component integers 1-12 :param title: Text to show next to selector dropdown :param default_option_index: default index of the dropdown :param tooltip: text to show in tooltip on the title :param parent_selector_id: unique identifier of the parent selector component for nested selections """ super().__init__(id_=id_, height=height, width=width, selections=None) self._type = 'selector' self.container_ids = container_ids self.title = title self.default_option_index = default_option_index self.tooltip = tooltip self.parent_selector_id = parent_selector_id def as_dict(self) -> Dict: dict_ = super().as_dict() dict_['parameters']['containerIds'] = self.container_ids if self.default_option_index: dict_['parameters']['defaultOptionIndex'] = self.default_option_index if self.title: dict_['parameters']['title'] = self.title if self.tooltip: dict_['parameters']['tooltip'] = self.tooltip if self.parent_selector_id: dict_['parameters']['parentSelectorId'] = self.parent_selector_id return dict_ @classmethod def from_dict(cls, obj, scale: int = None): parameters = obj.get('parameters', {}) return SelectorComponent( id_=obj['id'], height=parameters.get('height', 200), width=scale, title=parameters.get('title'), container_ids=parameters['containerIds'], tooltip=parameters.get('tooltip'), default_option_index=parameters.get('defaultOptionIndex'), parent_selector_id=parameters.get('parentSelectorId'), ) class PromoComponent(Component): def __init__( self, height: int, id_: Optional[str] = None, *, width: int = None, selections: List[Selection] = None, tooltip: str = None, transparent: bool = None, body: str = None, size: PromoSize = None, hide_border: bool = None, ): """ Promo Component for arbitrary text :param height: height of the component :param id_: unique identifier of the component :param width: width of the component integers 1-12 :param selections: List of selections used for selectors :param tooltip: text to show in tooltip on the title :param transparent: whether or not the background of the component is transparent :param body: text to show in the component that can have html tags :param size: size of the component text :param hide_border: whether to hide the border of the component """ super().__init__(id_=id_, height=height, width=width, selections=selections) self._type = 'promo' self.tooltip = tooltip self.transparent = transparent self.body = body self.size = size self.hide_border = hide_border def as_dict(self) -> Dict: dict_ = super().as_dict() if self.tooltip: dict_['parameters']['tooltip'] = self.tooltip if self.body: dict_['parameters']['body'] = self.body if self.size: dict_['parameters']['size'] = self.size.value if self.hide_border is not None: dict_['parameters']['hideBorder'] = self.size if self.transparent is not None: dict_['parameters']['transparent'] = self.transparent return dict_ @classmethod def from_dict(cls, obj: Dict, scale: int = None): parameters = obj.get('parameters', {}) size = parameters.get('size') size = PromoSize(size) if size else None return PromoComponent( id_=obj['id'], height=parameters.get('height', 200), width=scale, tooltip=parameters.get('tooltip'), body=parameters.get('body'), size=size, hide_border=parameters.get('hideBorder'), ) class SeparatorComponent(Component): def __init__( self, height: int, id_: Optional[str] = None, *, width: int = None, selections: List[Selection] = None, name: str = None, size: str = None, show_more_url: str = None, ): """ Separator Component :param height: height of the component :param id_: unique identifier of the component :param width: width of the component integers 1-12 :param selections: List of selections used for selectors :param name: Text of the separator :param size: size of the component :param show_more_url: Url link to redirect """ super().__init__(id_=id_, height=height, width=width, selections=selections) self._type = 'separator' self.name = name self.size = size self.show_more_url = show_more_url def as_dict(self) -> Dict: dict_ = super().as_dict() if self.name: dict_['parameters']['name'] = self.name if self.size: dict_['parameters']['size'] = self.size if self.show_more_url: dict_['parameters']['showMoreUrl'] = self.show_more_url return dict_ class LegendComponent(Component): def __init__( self, height: int, id_: Optional[str] = None, *, width: int = None, selections: List[Selection] = None, items: List[LegendItem] = None, position: str = None, transparent: bool = None, ): """ Legend Component :param height: height of the component :param id_: unique identifier of the component :param width: width of the component integers 1-12 :param selections: List of selections used for selectors :param items: Legend items to appear in the legend :param position: position of the legend :param transparent: Whether the background of the legend is transparent """ super().__init__(id_=id_, height=height, width=width, selections=selections) self._type = 'legend' self.items = items self.position = position self.transparent = transparent def as_dict(self) -> Dict: dict_ = super().as_dict() dict_['parameters']['items'] = [item.as_dict() for item in self.items] if self.position: dict_['parameters']['position'] = self.position if self.transparent: dict_['parameters']['transparent'] = self.transparent return dict_ @classmethod def from_dict(cls, obj: Dict, scale: int = None): parameters = obj.get('parameters', {}) items = [LegendItem.from_dict(item) for item in parameters.get('items', [])] return LegendComponent( id_=obj['id'], height=parameters.get('height', 200), width=scale, selections=obj.get('selections'), position=parameters.get('position'), transparent=parameters.get('transparent'), items=items, ) class MonitorComponent(Component): def __init__( self, height: int, id_: str, *, width: int = None, selections: List[Selection] = None, tooltip: str = None ): """ Monitor Component :param height: height of the component :param id_: unique identifier of the component :param width: width of the component integers 1-12 :param selections: List of selections used for selectors :param tooltip: text to show in a tooltip when hovering over monitor name """ super().__init__(id_=id_, height=height, width=width, selections=selections) self._type = 'monitor' self.tooltip = tooltip def as_dict(self) -> Dict: dict_ = super().as_dict() if self.tooltip: dict_['parameters']['tooltip'] = self.tooltip return dict_ class RelatedLinksComponent(Component): def __init__( self, height: int, id_: Optional[str] = None, *, width: int = None, selections: List[Selection] = None, links: List[RelatedLink], title: str, ): """ Related Links Component :param height: height of the component :param id_: unique identifier of the component :param width: width of the component integers 1-12 :param selections: List of selections used for selectors :param links: links to add to the component :param title: title of the component """ super().__init__(id_=id_, height=height, width=width, selections=selections) self._type = 'relatedLinks' self.links = links self.title = title def as_dict(self) -> Dict: dict_ = super().as_dict() dict_['parameters']['title'] = self.title dict_['parameters']['links'] = [link.as_dict() for link in self.links] return dict_ @classmethod def from_dict(cls, obj, scale: int = None): parameters = obj.get('parameters', {}) return RelatedLinksComponent( id_=obj['id'], height=parameters.get('height', 200), width=scale, selections=obj.get('selections'), title=parameters['title'], links=[RelatedLink.from_dict(link) for link in parameters['links']], ) TYPE_TO_COMPONENT = { 'article': ArticleComponent, 'container': ContainerComponent, 'datagrid': DataGridComponent, 'dataviz': DataVizComponent, 'legend': LegendComponent, 'monitor': MonitorComponent, 'plot': PlotComponent, 'promo': PromoComponent, 'relatedLinks': RelatedLinksComponent, 'selector': SelectorComponent, 'separator': SeparatorComponent, 'screener': DataScreenerComponent, } ================================================ FILE: gs_quant/analytics/workspaces/workspace.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import logging import webbrowser from collections import deque from typing import List, Tuple, Union, Dict from pydash import get from gs_quant.analytics.workspaces.components import ( Component, TYPE_TO_COMPONENT, RelatedLink, DataGridComponent, MonitorComponent, PlotComponent, DataScreenerComponent, ) from gs_quant.common import Entitlements as Entitlements_ from gs_quant.entities.entitlements import Entitlements from gs_quant.errors import MqValueError, MqRequestError from gs_quant.session import GsSession _logger = logging.getLogger(__name__) API = '/workspaces/markets' HEADERS = {'Content-Type': 'application/json;charset=utf-8'} class WorkspaceCallToAction: def __init__(self, actions: List[RelatedLink], text: str, name: str = None): """ Call to action displayed on the top right of the page. :param actions: link to external/internal pages, embed a mail to link, anchor links within page or notifications :param text: description below the link :param name: name of the link/button """ self.actions = actions self.text = text self.name = name def as_dict(self): actions = [] for action in self.actions: if isinstance(action, RelatedLink): actions.append(action.as_dict()) else: actions.append(action) cta_dict = {'actions': actions, 'text': self.text} if self.name: cta_dict['name'] = self.name return cta_dict @classmethod def from_dict(cls, obj): actions = [] for action in obj['actions']: if isinstance(action, Dict): actions.append(RelatedLink.from_dict(action)) else: actions.append(action) return WorkspaceCallToAction(actions=actions, text=obj['text'], name=obj['name']) class WorkspaceTab: def __init__(self, id_: str, name: str): """ Workspace Tab to connect other workspaces. :param id_: alias of the workspace to create a tab :param name: Name of the tab """ self.id_ = id_ self.name = name def as_dict(self): return {'id': self.id_, 'name': self.name} @classmethod def from_dict(cls, obj): return WorkspaceTab(id_=obj['id'], name=obj['name']) class WorkspaceColumn: def __init__(self, components: List[Union[Component, 'WorkspaceRow']], width: int = None): """ :param components: List of components in the same row """ self.__components = [] self.components = components self.__width = width @property def components(self): return self.__components @components.setter def components(self, value): if len(value) > 12: raise MqValueError(f'{value} exceeds the max number of columns of 12.') width_sum = 0 for component in self.__components: if not isinstance(component, WorkspaceRow): width_sum += component.width if width_sum > 12: raise MqValueError(f'{width_sum} exceeds the max sum of widths of 12.') without_width_count = 0 for component in value: if not isinstance(component, WorkspaceRow): without_width_count += component.width or 1 if width_sum + without_width_count > 12: raise MqValueError( f'Cannot fit all components in column due to given total width of {width_sum} ' f'and {without_width_count} components without a width.' ) self.__components = value @property def width(self): return self.__width @width.setter def width(self, value): self.__width = value def get_layout(self, count): layout = '' width_sum = 0 for component in self.__components: if not isinstance(component, WorkspaceRow): width_sum += component.width or 0 components_length = len(self.__components) if width_sum == 0: # Equally spread out size = int(12 / components_length) last_size = 12 % components_length for i, component in enumerate(self.__components): if isinstance(component, WorkspaceRow): sub_layout, count = component.get_layout(count) layout += sub_layout elif isinstance(component, WorkspaceColumn): sub_layout, count = component.get_layout(count) layout += ( f'c{size + last_size if i == components_length - 1 and last_size != 0 else size}({sub_layout})' ) else: # Case: Component layout += ( f'c{size + last_size if i == components_length - 1 and last_size != 0 else size}(${count})' ) count += 1 else: used_sum = 0 if width_sum == 12: default_width = 0 else: default_width = int(12 - width_sum / sum(1 for component in self.components if component.width is None)) for i, component in enumerate(self.components): if i == components_length - 1 and not component.width: layout += f'c{12 - used_sum}(${count})' elif component.width is None: layout += f'c{default_width}(${count})' used_sum += default_width else: width = component.width or 1 layout += f'c{width}(${count})' used_sum += width count += 1 return layout, count def _add_components(self, components): for component in self.__components: if isinstance(component, (WorkspaceRow, WorkspaceColumn)): component._add_components(components) else: components.append(component.as_dict()) class WorkspaceRow: """ Wrapper on a list of components in the same row. """ def __init__(self, components: List[Union[Component, WorkspaceColumn]]): """ :param components: List of components in the same row """ self.__components = [] self.components = components @property def components(self): return self.__components @components.setter def components(self, value): if len(value) > 12: raise MqValueError(f'{value} exceeds the max number of columns of 12.') width_sum = 0 for component in self.__components: if not isinstance(component, WorkspaceRow): width_sum += component.width if width_sum > 12: raise MqValueError(f'{width_sum} exceeds the max sum of widths of 12.') without_width_count = 0 for component in value: if not isinstance(component, WorkspaceRow): without_width_count += component.width or 1 if width_sum + without_width_count > 12: raise MqValueError( f'Cannot fit all components in row due to given total width of {width_sum} ' f'and {without_width_count} components without a width.' ) self.__components = value def get_layout(self, count: int) -> Tuple[str, int]: layout = 'r(' width_sum = 0 for component in self.__components: if not isinstance(component, WorkspaceRow): width_sum += component.width or 0 components_length = len(self.__components) if width_sum == 0: # Equally spread out size = int(12 / components_length) last_size = 12 % components_length for i, component in enumerate(self.__components): if isinstance(component, WorkspaceColumn): sub_layout, count = component.get_layout(count) layout += ( f'c{size + last_size if i == components_length - 1 and last_size != 0 else size}({sub_layout})' ) else: # Case: Component layout += ( f'c{size + last_size if i == components_length - 1 and last_size != 0 else size}(${count})' ) count += 1 else: used_sum = 0 if width_sum == 12: default_width = 0 else: default_width = ( self.components[0].width if len(self.components) == 1 else int(12 - width_sum / sum(1 for component in self.components if component.width is None)) ) for i, component in enumerate(self.components): if i == components_length - 1 and not component.width: if isinstance(component, WorkspaceColumn): sub_layout, count = component.get_layout(count) layout += f'c{12 - used_sum}({sub_layout})' else: layout += f'c{12 - used_sum}(${count})' count += 1 elif component.width is None: if isinstance(component, WorkspaceColumn): sub_layout, count = component.get_layout(count) layout += f'c{default_width}({sub_layout})' else: layout += f'c{default_width}(${count})' count += 1 used_sum += default_width else: width = component.width or 1 if isinstance(component, WorkspaceColumn): sub_layout, count = component.get_layout(count) layout += f'c{width}({sub_layout})' else: layout += f'c{width}(${count})' count += 1 used_sum += width layout += ')' return layout, count def _add_components(self, components): for component in self.__components: if isinstance(component, (WorkspaceRow, WorkspaceColumn)): component._add_components(components) else: components.append(component.as_dict()) class Workspace: PERSISTED_COMPONENTS = { DataGridComponent: '/data/grids', MonitorComponent: '/monitors', PlotComponent: '/charts', DataScreenerComponent: '/data/screens', } def __init__( self, name: str, rows: List[WorkspaceRow] = None, alias: str = None, description: str = None, entitlements: Union[Entitlements, Entitlements_] = None, tabs: List[WorkspaceTab] = None, selector_components: List[Component] = None, disclaimer: str = None, maintainers: List[str] = None, call_to_action: Union[WorkspaceCallToAction, Dict] = None, tags: List[str] = None, ): self.__id = None self.__name = name self.__rows = rows or [] self.__selector_components = selector_components or [] self.__alias = alias self.__entitlements = entitlements self.__description = description self.__disclaimer = disclaimer self.__maintainers = maintainers or [] self.__tabs = tabs or [] self.__call_to_action = call_to_action self.__tags = tags or [] @classmethod def get_by_id(cls, workspace_id: str) -> 'Workspace': resp = GsSession.current.sync.get(f'{API}/{workspace_id}') return Workspace.from_dict(resp) @classmethod def get_by_alias(cls, alias: str) -> 'Workspace': resp = get(GsSession.current.sync.get(f'{API}?alias={alias}'), 'results.0') if not resp: raise MqValueError(f'Workspace not found with alias {alias}') return Workspace.from_dict(resp) def save(self): if self.__id: GsSession.current.sync.put(f'{API}/{self.__id}', self.as_dict(), request_headers=HEADERS) elif self.__alias: id_ = get(GsSession.current.sync.get(f'{API}?alias={self.__alias}'), 'results.0.id') if id_: self.__id = GsSession.current.sync.put(f'{API}/{id_}', self.as_dict(), request_headers=HEADERS)['id'] else: self.__id = GsSession.current.sync.post(API, self.as_dict(), request_headers=HEADERS)['id'] def open(self): if self.__id is None: raise MqValueError('Workspace must be created or saved before opening.') domain = GsSession.current.domain.replace(".web", "") if domain == 'https://api.gs.com': domain = 'https://marquee.gs.com' url = f'{domain}/s/markets/{self.__alias or self.__id}' webbrowser.open(url) def create(self): resp = GsSession.current.sync.post(f'{API}', self.as_dict(), request_headers=HEADERS) self.__id = resp['id'] def delete(self): if self.__id is None: raise MqValueError('Workspace must have an id to be deleted.') resp = GsSession.current.sync.delete(f'{API}/{self.__id}') self.__id = resp['id'] def delete_all(self, include_tabs: bool = False): """ Deletes the workspace and all persisted components. :param include_tabs: whether to delete all tabs and their persisted components also :return: None """ for row in self.__rows: self.__delete_components(row.components) self.__delete_components(self.__selector_components) if include_tabs: for tab in self.__tabs: tab_workspace = self.get_by_alias(tab.id_) tab_workspace.delete_all() @property def name(self): return self.__name @name.setter def name(self, value): self.__name = value @property def alias(self): return self.__alias @alias.setter def alias(self, value): self.__alias = value @property def rows(self) -> List[WorkspaceRow]: return self.__rows @rows.setter def rows(self, value: List[WorkspaceRow]): self.__rows = value @property def entitlements(self): return self.__entitlements @entitlements.setter def entitlements(self, value): self.__entitlements = value @property def description(self): return self.__description @description.setter def description(self, value): self.__description = value @property def disclaimer(self): return self.__disclaimer @disclaimer.setter def disclaimer(self, value): self.__disclaimer = value @property def maintainers(self): return self.__maintainers @maintainers.setter def maintainers(self, value): self.__maintainers = value @property def tabs(self): return self.__tabs @tabs.setter def tabs(self, value): self.__tabs = value @property def selector_components(self): return self.__selector_components @selector_components.setter def selector_components(self, value): self.__selector_components = value @property def call_to_action(self): return self.__call_to_action @call_to_action.setter def call_to_action(self, value): self.__call_to_action = value @property def tags(self): return self.__tags @tags.setter def tags(self, value): self.__tags = value @property def id(self): return self.__id @classmethod def _parse(cls, layout: str, workspace_components: List[Dict]): current_str = '' outside_components = [] stack = deque() for c in layout: current_str += c if c == '(': stack.append('(') elif c == ')': stack.pop() if len(stack) == 0: if current_str.startswith('c'): is_component = current_str[current_str.index('(') + 1 :].startswith('$') if is_component: # Component Case scale, id_ = current_str.split('($') scale = int(scale[1:]) id_ = int(id_[0:-1]) component = workspace_components[id_] component_type = component['type'] component = TYPE_TO_COMPONENT[component_type].from_dict(component, scale) outside_components.append(component) else: # Column Case column_layout = current_str[current_str.index('(') + 1 : -1] components = Workspace._parse(column_layout, workspace_components) width = int(current_str[1 : current_str.index('(')]) outside_components.append(WorkspaceColumn(components, width)) elif current_str.startswith('r'): # Row Case row_layout = current_str[current_str.index('(') + 1 : -1] components = Workspace._parse(row_layout, workspace_components) outside_components.append(WorkspaceRow(components)) current_str = '' return outside_components @classmethod def from_dict(cls, obj): workspace_components = obj['parameters']['components'] layout = obj['parameters']['layout'] stack = deque() row_layout = '' row_layouts = [] for c in layout[1:]: if c == '(': stack.append('(') elif c == ')': stack.pop() if len(stack) == 0: row_layouts.append(row_layout[row_layout.index('c') :]) row_layout = '' row_layout += c workspace_rows = [ WorkspaceRow(components=Workspace._parse(row_layout, workspace_components)) for row_layout in row_layouts ] component_count = 0 # The rest of the components not in the layout should be selector components selector_components = [] if component_count < len(workspace_components): for i in range(component_count, len(workspace_components)): component = workspace_components[i] selector_components.append(TYPE_TO_COMPONENT[component['type']].from_dict(component)) params = obj['parameters'] tabs = [WorkspaceTab.from_dict(tab) for tab in params.get('tabs', [])] return Workspace( name=obj['name'], rows=workspace_rows, selector_components=selector_components, alias=obj.get('alias'), tabs=tabs, entitlements=Entitlements.from_dict(obj.get('entitlements', {})), description=obj.get('description'), disclaimer=params.get('disclaimer'), maintainers=params.get('maintainers'), ) def as_dict(self): components, count, layout = [], 0, '' for row in self.__rows: row_layout, count = row.get_layout(count) layout += row_layout for component in row.components: if isinstance(component, (WorkspaceRow, WorkspaceColumn)): component._add_components(components) else: components.append(component.as_dict()) # Add the hidden components at the end components.extend([component.as_dict() for component in self.__selector_components]) parameters = {'layout': layout, 'components': components} if len(self.__maintainers): parameters['maintainers'] = self.__maintainers if self.__call_to_action: if isinstance(self.__call_to_action, WorkspaceCallToAction): parameters['callToAction'] = self.__call_to_action.as_dict() else: parameters['callToAction'] = self.__call_to_action if len(self.__tabs): parameters['tabs'] = [tab.as_dict() for tab in self.__tabs] if self.__disclaimer: parameters['disclaimer'] = self.__disclaimer dict_ = {'name': self.__name, 'parameters': parameters} if self.__alias: dict_['alias'] = self.__alias if self.__entitlements: if isinstance(self.__entitlements, Entitlements_): dict_['entitlements'] = self.__entitlements.as_dict() elif isinstance(self.__entitlements, Entitlements): dict_['entitlements'] = self.__entitlements.to_dict() else: dict_['entitlements'] = self.__entitlements if len(self.__tags): dict_['tags'] = self.__tags if self.__description: dict_['description'] = self.__description return dict_ @classmethod def __delete_components(cls, components: List[Component]): for component in components: if isinstance(component, (WorkspaceRow, WorkspaceColumn)): cls.__delete_components(component.components) else: type_ = type(component) if type_ in cls.PERSISTED_COMPONENTS: try: GsSession.current.sync.delete(f'{cls.PERSISTED_COMPONENTS[type_]}/{component.id_}') except MqRequestError as ex: _logger.warning( f'Failed to delete {type_.__name__} with id {component.id_} due to {ex.message}' ) def __get_layout(components, count): layout = 'r(' width_sum = 0 for component in components: if not isinstance(component, WorkspaceRow): width_sum += component.width or 0 components_length = len(components) if width_sum == 0: # Equally spread out size = int(12 / components_length) last_size = 12 % components_length for i, component in enumerate(components): if isinstance(component, WorkspaceRow): sub_layout, count = __get_layout(component.components, count) layout += ( f'c{size + last_size if i == components_length - 1 and last_size != 0 else size}({sub_layout})' ) elif isinstance(component, WorkspaceColumn): sub_layout, count = __get_layout(component.components, count) layout += ( f'c{size + last_size if i == components_length - 1 and last_size != 0 else size}({sub_layout})' ) else: # Case: Component layout += f'c{size + last_size if i == components_length - 1 and last_size != 0 else size}(${count})' count += 1 else: used_sum = 0 if width_sum == 12: default_width = 0 else: default_width = int(12 - width_sum / sum(1 for component in components if component.width is None)) for i, component in enumerate(components): if i == components_length - 1 and not component.width: layout += f'c{12 - used_sum}(${count})' elif component.width is None: layout += f'c{default_width}(${count})' used_sum += default_width else: width = component.width or 1 layout += f'c{width}(${count})' used_sum += width count += 1 layout += ')' return layout, count ================================================ FILE: gs_quant/api/__init__.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ __name__ = 'api' ================================================ FILE: gs_quant/api/api_cache.py ================================================ """ Copyright 2023 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from abc import ABC, abstractmethod from enum import Enum from typing import Any, Tuple import cachetools import pandas as pd from gs_quant.base import Base from gs_quant.session import GsSession class CacheEvent(Enum): PUT = 'Put' GET = 'Get' class ApiRequestCache(ABC): def get(self, session: GsSession, key: Any, **kwargs): cache_lookup = self._get(session, key, **kwargs) if cache_lookup is not None: self.record(session, key, CacheEvent.GET, **kwargs) return cache_lookup @abstractmethod def _get(self, session: GsSession, key: Any, **kwargs): pass def record(self, session: GsSession, key: Any, method: CacheEvent, **kwargs): pass def put(self, session: GsSession, key: Any, value, **kwargs): self._put(session, key, value, **kwargs) self.record(session, key, CacheEvent.PUT, **kwargs) @abstractmethod def _put(self, session: GsSession, key: Any, value, **kwargs): pass class InMemoryApiRequestCache(ApiRequestCache): def __init__(self, max_size=1000, ttl_in_seconds=3600): self._cache = cachetools.TTLCache(max_size, ttl_in_seconds) self._records = [] def get_events(self) -> Tuple[Tuple[CacheEvent, Any], ...]: return tuple(self._records) def clear_events(self): self._records.clear() def _make_str_key(self, key: Any): if isinstance(key, (list, tuple)): return "_".join(self._make_str_key(k) for k in key) elif isinstance(key, (Base, pd.DataFrame)): return key.to_json() elif isinstance(key, dict): return self._make_str_key(list(key.items())) return str(key) def _get(self, session: GsSession, key: Any, **kwargs): return self._cache.get(self._make_str_key(key)) def record(self, session: GsSession, key: Any, method: CacheEvent, **kwargs): self._records.append((method, key)) def _put(self, session: GsSession, key, value, **kwargs): self._cache[self._make_str_key(key)] = value ================================================ FILE: gs_quant/api/api_session.py ================================================ """ Copyright 2023 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from typing import Callable, Optional from gs_quant.session import GsSession class ApiWithCustomSession: __SESSION_SUPPLIER: Optional[Callable[[], GsSession]] = None @classmethod def set_session_provider(cls, session_supplier: Callable[[], GsSession]): """ To allow session context override specific to this API, set a factory/supplier. Default is GsSession.current. :param session_supplier: callable which returns a GsSession """ cls.__SESSION_SUPPLIER = session_supplier @classmethod def set_session(cls, session: GsSession): """ To allow session context override specific to this API, set a session directly. Default is GsSession.current. :param session: a GsSession """ cls.__SESSION_SUPPLIER = None if session is None else lambda: session @classmethod def get_session(cls) -> GsSession: if cls.__SESSION_SUPPLIER: return cls.__SESSION_SUPPLIER() else: return GsSession.current ================================================ FILE: gs_quant/api/data.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import logging from abc import ABCMeta from typing import Optional, Union, List import inflection import pandas as pd from gs_quant.api.api_session import ApiWithCustomSession from gs_quant.api.fred.fred_query import FredQuery from gs_quant.base import Base from gs_quant.target.coordinates import MDAPIDataQuery from gs_quant.target.data import DataQuery _logger = logging.getLogger(__name__) class DataApi(ApiWithCustomSession, metaclass=ABCMeta): @classmethod def query_data(cls, query: Union[DataQuery, FredQuery], dataset_id: str = None) -> Union[list, tuple]: raise NotImplementedError('Must implement get_data') @classmethod def last_data(cls, query: DataQuery, dataset_id: str = None) -> Union[list, tuple]: raise NotImplementedError('Must implement last_data') @classmethod def symbol_dimensions(cls, dataset_id: str) -> tuple: raise NotImplementedError('Must implement symbol_dimensions') @classmethod def time_field(cls, dataset_id: str) -> str: raise NotImplementedError('Must implement time_field') @classmethod def construct_dataframe_with_types( cls, dataset_id: str, data: Union[Base, list, tuple, pd.Series], schema_varies=False, standard_fields=False ) -> pd.DataFrame: raise NotImplementedError('Must implement time_field') @staticmethod def build_query( start: Optional[Union[dt.date, dt.datetime]] = None, end: Optional[Union[dt.date, dt.datetime]] = None, as_of: Optional[dt.datetime] = None, since: Optional[dt.datetime] = None, restrict_fields: bool = False, format: str = 'MessagePack', dates: List[dt.date] = None, empty_intervals: Optional[bool] = None, **kwargs, ): end_is_time = isinstance(end, dt.datetime) start_is_time = isinstance(start, dt.datetime) if kwargs.get('market_data_coordinates'): real_time = (start is None or start_is_time) and (end is None or end_is_time) query = MDAPIDataQuery( start_time=start if real_time else None, end_time=end if real_time else None, start_date=start if not real_time else None, end_date=end if not real_time else None, format=format, real_time=real_time, **kwargs, ) else: if start_is_time and end is not None and not end_is_time: raise ValueError('If start is of type datetime, so must end be!') if isinstance(start, dt.date) and end is not None and not isinstance(end, dt.date): raise ValueError('If start is of type date, so must end be!') query = DataQuery( start_date=start if not start_is_time else None, start_time=start if start_is_time else None, end_date=end if not end_is_time else None, end_time=end if end_is_time else None, as_of_time=as_of, since=since, format=format, dates=dates, empty_intervals=empty_intervals, ) query_properties = query.properties() query.where = dict() for field, value in kwargs.items(): snake_case_field = inflection.underscore(field) if snake_case_field in query_properties and snake_case_field not in ('name',): setattr(query, snake_case_field, value) else: query.where[field] = value if getattr(query, 'fields', None) is not None: try: query.restrict_fields = restrict_fields except AttributeError as e: _logger.debug('unable to set restrict_fields', exc_info=e) return query ================================================ FILE: gs_quant/api/fred/__init__.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. This product uses the FRED® API but is not endorsed or certified by the Federal Reserve Bank of St. Louis. FRED terms of use available at https://research.stlouisfed.org/docs/api/terms_of_use.html Portions copyright Maverick Lin. Licensed under Apache 2.0 license """ __name__ = 'fred' ================================================ FILE: gs_quant/api/fred/data.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. This product uses the FRED® API but is not endorsed or certified by the Federal Reserve Bank of St. Louis. FRED terms of use available at https://research.stlouisfed.org/docs/api/terms_of_use.html """ from typing import Iterable, Optional, Union import pandas as pd import datetime as dt import textwrap from gs_quant.api.utils import handle_proxy from requests.exceptions import HTTPError from dataclasses import asdict, replace from gs_quant.api.data import DataApi from gs_quant.api.fred.fred_query import FredQuery """ Fred Data API that provides functions to query the Fred dataset. You need to specify a valid API key by passing in the string via api_key. You can sign up for a free API key on the Fred website at: http://research.stlouisfed.org/fred2/ """ class FredDataApi(DataApi): earliest_realtime_start = '1776-07-04' latest_realtime_end = '9999-12-31' root_url = 'https://api.stlouisfed.org/fred/series/observations' def __init__(self, api_key=None): if api_key is not None: self.api_key = api_key else: raise ValueError( textwrap.dedent(""" Please pass a string with your API key. You can sign up for a free api key on the Fred website at http://research.stlouisfed.org/fred2/""") ) def build_query( self, start: Optional[Union[dt.date, dt.datetime]] = None, end: Optional[Union[dt.date, dt.datetime]] = None, as_of: Optional[dt.datetime] = None, since: Optional[dt.datetime] = None, fields: Optional[Iterable[str]] = None, **kwargs, ) -> FredQuery: """ Builds a FRED URL to query. :param start: Requested start date/datetime for data :param end: Requested end date/datetime for data :param as_of: Request data as_of :param since: Request data since :param fields: DataSet fields to include :param kwargs: Extra query arguments :return: a url string of the requested data """ if start is not None and end is not None: if type(start) is not type(end): raise ValueError('Start and end types must match!') request = FredQuery(observation_start=start, observation_end=end, realtime_end=as_of, realtime_start=since) return request def query_data(self, query: FredQuery, dataset_id: str, asset_id_type: str = None) -> pd.Series: """ Query data given a valid FRED series id and url. Will raise an HTTPError if the response was an HTTP error. :param query: A url string of the requested data :param id: A FRED series id :return: with id as key and requested DataFrame as value. """ request = replace(query, api_key=self.api_key, series_id=dataset_id) response = handle_proxy(self.root_url, asdict(request)) handled = self.__handle_response(response) handled.name = dataset_id return handled def last_data(self, query: FredQuery, dataset_id: str) -> pd.Series: """ Get the last point for a data series, at or before as_of :param query: A url string of the requested data :param id: A FRED series id :param kwargs: Extra query arguments :return: with id as key and last point for this DataSet as value. """ data = self.query_data(query, dataset_id) return data.last('1D') @staticmethod def __handle_response(response: str) -> pd.Series: """ Helper function for handling the response given a request URL. Will raise an HTTPError if the response was an HTTP error. """ try: response.raise_for_status() json_data = response.json() except HTTPError: raise ValueError(response.json()['error_message']) if not len(json_data['observations']): raise ValueError('No data exists for {} for the provided parameters... '.format(id)) data = pd.DataFrame(json_data['observations'])[['date', 'value']] data = data[data.value != '.'] data['date'] = pd.to_datetime(data['date']) data['value'] = data['value'].astype(float) data = data.set_index('date')['value'] data = data.sort_index() return data def construct_dataframe_with_types( self, dataset_id: str, data: pd.Series, schema_varies=False, standard_fields=False ) -> pd.DataFrame: """ Constructs a dataframe with correct date types. :param data: Data to convert with correct types :return: Dataframe with correct types """ if len(data) and isinstance(data, pd.Series): return data.to_frame() else: return pd.DataFrame({}) def symbol_dimensions(self, dataset_id: str) -> tuple: query = FredQuery() data = self.query_data(query, dataset_id) return data.shape def time_field(self, dataset_id: str) -> str: pass ================================================ FILE: gs_quant/api/fred/fred_query.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. This product uses the FRED® API but is not endorsed or certified by the Federal Reserve Bank of St. Louis. FRED terms of use available at https://research.stlouisfed.org/docs/api/terms_of_use.html """ from typing import Union import datetime as dt from dataclasses import dataclass @dataclass class FredQuery: api_key: str = '' series_id: str = '' file_type: str = 'json' observation_start: Union[dt.date, dt.datetime] = None observation_end: Union[dt.date, dt.datetime] = None realtime_end: dt.datetime = None realtime_start: dt.datetime = None ================================================ FILE: gs_quant/api/gs/__init__.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ __name__ = 'gs' ================================================ FILE: gs_quant/api/gs/assets.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import logging import os import threading from enum import auto, Enum from functools import wraps from typing import Iterable, List, Optional, Tuple, Union, Callable import backoff import cachetools import cachetools.keys import pandas as pd from pydash import get, has from requests.exceptions import HTTPError from gs_quant.api.api_cache import ApiRequestCache, InMemoryApiRequestCache from gs_quant.common import Entitlements, PositionType from gs_quant.context_base import nullcontext from gs_quant.errors import MqValueError, MqRateLimitedError, MqTimeoutError, MqInternalServerError from gs_quant.instrument import Instrument, Security from gs_quant.session import GsSession from gs_quant.target.assets import ( Asset as __Asset, AssetToInstrumentResponse, TemporalXRef, Position, EntityQuery, PositionSet, ) from gs_quant.target.assets import FieldFilterMap from gs_quant.target.reports import Report from gs_quant.tracing import Tracer _logger = logging.getLogger(__name__) IdList = Union[Tuple[str, ...], List] ENABLE_ASSET_CACHING = 'GSQ_SEC_MASTER_CACHE' class AssetCache: def __init__(self, cache: ApiRequestCache, ttl: int, construct_key_fn: Callable): self.__cache = cache self.__ttl = ttl self.__construct_key_fn = construct_key_fn @property def ttl(self): return self.__ttl @property def cache(self): return self.__cache @property def construct_key_fn(self): return self.__construct_key_fn def construct_key(self, session: GsSession, *args, **kwargs): return self.construct_key_fn(session, *args, **kwargs) def get_default_cache() -> AssetCache: ttl = 30 # seconds def in_memory_key_fn(session, *args, **kwargs): args = [tuple(x) if isinstance(x, list) else x for x in args] # tuples are hashable for k, v in kwargs.items(): if isinstance(v, list): kwargs[k] = tuple(v) k = cachetools.keys.hashkey(session, *args, **kwargs) return k return AssetCache(cache=InMemoryApiRequestCache(1024, ttl), ttl=ttl, construct_key_fn=in_memory_key_fn) def _cached(fn): _fn_cache_lock = threading.Lock() # short-term cache to avoid retrieving the same data several times in succession fallback_cache: AssetCache = get_default_cache() @wraps(fn) def wrapper(cls, *args, **kwargs): if os.environ.get(ENABLE_ASSET_CACHING): _logger.debug("Asset caching is enabled") asset_cache = cls.get_cache() or fallback_cache k = asset_cache.construct_key(GsSession.current, fn.__name__, *args, **kwargs) with Tracer("acquiring cache lock"): _logger.debug('cache get: %s', k) with _fn_cache_lock: result = asset_cache.cache.get(GsSession.current, k) if result: _logger.debug('cache hit: %s', k) return result with Tracer("Executing function"): result = fn(cls, *args, **kwargs) with Tracer("acquiring cache lock"): _logger.debug('cache set: %s', k) with _fn_cache_lock: asset_cache.cache.put(GsSession.current, k, result, ttl=asset_cache.ttl) else: _logger.debug("Asset caching is disabled, calling function") result = fn(cls, *args, **kwargs) return result return wrapper def _cached_async(fn): _fn_cache_lock = threading.Lock() # short-term cache to avoid retrieving the same data several times in succession fallback_cache: AssetCache = get_default_cache() @wraps(fn) async def wrapper(cls, *args, **kwargs): if os.environ.get(ENABLE_ASSET_CACHING): _logger.debug("Asset caching is enabled") asset_cache = cls.get_cache() or fallback_cache k = asset_cache.construct_key(GsSession.current, fn.__name__, *args, **kwargs) with Tracer("acquiring cache lock"): _logger.debug('cache get: %s', k) with _fn_cache_lock: result = asset_cache.cache.get(GsSession.current, k) if result: _logger.debug('cache hit: %s', k) return result with Tracer("Executing function"): result = await fn(cls, *args, **kwargs) with Tracer("acquiring cache lock"): _logger.debug('cache set: %s', k) with _fn_cache_lock: asset_cache.cache.put(GsSession.current, k, result, ttl=asset_cache.ttl) else: _logger.debug("Asset caching is disabled, calling function") result = await fn(cls, *args, **kwargs) return result return wrapper class GsIdType(Enum): """GS Asset API identifier type enumeration""" ric = auto() bbid = auto() bcid = auto() cusip = auto() isin = auto() sedol = auto() mdapi = auto() primeId = auto() id = auto() gsid = auto() rcic = auto() ticker = auto() class GsAsset(__Asset): """GS Asset API object model for an asset object""" pass class GsTemporalXRef(TemporalXRef): pass class GsAssetApi: """GS Asset API client implementation""" _cache: Optional[AssetCache] = None @classmethod def set_cache(cls, cache: AssetCache): cls._cache = cache @classmethod def get_cache(cls) -> Optional[AssetCache]: return cls._cache @classmethod def __create_query( cls, fields: Union[List, Tuple] = None, as_of: dt.datetime = None, limit: int = None, scroll: str = None, scroll_id: str = None, order_by: List[str] = None, **kwargs, ) -> EntityQuery: keys = set(kwargs.keys()) valid = keys.intersection(FieldFilterMap.properties()) invalid = keys.difference(valid) if invalid: bad_args = ['{}={}'.format(k, kwargs[k]) for k in invalid] raise KeyError('Invalid asset query argument(s): {}'.format(', '.join(bad_args))) return EntityQuery( where=FieldFilterMap(**kwargs), fields=fields, asOfTime=as_of or dt.datetime.utcnow(), limit=limit, scroll=scroll, scroll_id=scroll_id, order_by=order_by, ) @staticmethod def _set_tags(scope, kwargs): if kwargs and scope and scope.span: for k, v in kwargs.items(): if isinstance(v, (list, tuple)): if len(v) > 5: scope.span.set_tag(f'request.payload.{k}', len(v)) else: scope.span.set_tag(f'request.payload.{k}', ", ".join((str(x) for x in v))) elif isinstance(v, (int, float, bool, str)): scope.span.set_tag(f'request.payload.{k}', v) @classmethod @_cached def get_many_assets( cls, fields: IdList = None, as_of: dt.datetime = None, limit: int = 100, return_type: Optional[type] = GsAsset, order_by: List[str] = None, **kwargs, ) -> Union[Tuple[GsAsset, ...], Tuple[dict, ...]]: span = Tracer.active_span() tracer = Tracer('GsAsset.get_many_assets') if span and span.is_recording() else nullcontext() with tracer as scope: cls._set_tags(scope, kwargs) query = cls.__create_query(fields, as_of, limit, order_by=order_by, **kwargs) response = GsSession.current.sync.post('/assets/query', payload=query, cls=return_type) return response['results'] @classmethod @_cached_async async def get_many_assets_async( cls, fields: IdList = None, as_of: dt.datetime = None, limit: int = 100, return_type: Optional[type] = GsAsset, order_by: List[str] = None, **kwargs, ) -> Union[Tuple[GsAsset, ...], Tuple[dict, ...]]: span = Tracer.active_span() tracer = Tracer('GsAsset.get_many_assets_async') if span and span.is_recording() else nullcontext() with tracer as scope: cls._set_tags(scope, kwargs) query = cls.__create_query(fields, as_of, limit, order_by=order_by, **kwargs) response = await GsSession.current.async_.post('/assets/query', payload=query, cls=return_type) return response['results'] @classmethod @_cached def get_many_assets_scroll( cls, scroll: str = '1m', fields: IdList = None, as_of: dt.datetime = None, limit: int = 1000, return_type: Optional[type] = GsAsset, order_by: List[str] = None, **kwargs, ) -> Union[Tuple[GsAsset, ...], Tuple[dict, ...]]: span = Tracer.active_span() tracer = Tracer('GsAsset.get_many_assets_scroll') if span and span.is_recording() else nullcontext() with tracer as scope: cls._set_tags(scope, kwargs) query = cls.__create_query(fields, as_of, limit, scroll, order_by=order_by, **kwargs) response = GsSession.current.sync.post('/assets/query', payload=query, cls=return_type) results = get(response, 'results') while has(response, 'scrollId') and len(get(response, 'results')): query = cls.__create_query(fields, as_of, limit, scroll, get(response, 'scrollId'), **kwargs) response = GsSession.current.sync.post('/assets/query', payload=query, cls=return_type) results += get(response, 'results') return results @classmethod @_cached def get_many_assets_data( cls, fields: IdList = None, as_of: dt.datetime = None, limit: int = None, source: Optional[str] = None, **kwargs ) -> dict: span = Tracer.active_span() tracer = Tracer('GsAsset.get_many_assets_data') if span and span.is_recording() else nullcontext() with tracer as scope: cls._set_tags(scope, kwargs) query = cls.__create_query(fields, as_of, limit, **kwargs) request_headers = {'X-Application': 'Studio'} if source == "Basket" else None response = GsSession.current.sync.post('/assets/data/query', payload=query, request_headers=request_headers) return response['results'] @classmethod @_cached async def get_many_assets_data_async( cls, fields: IdList = None, as_of: dt.datetime = None, limit: int = None, **kwargs ) -> dict: span = Tracer.active_span() tracer = Tracer('GsAsset.get_many_assets_data_async') if span and span.is_recording() else nullcontext() with tracer as scope: cls._set_tags(scope, kwargs) query = cls.__create_query(fields, as_of, limit, **kwargs) response = await GsSession.current.async_.post('/assets/data/query', payload=query) return response['results'] @classmethod @_cached def get_many_assets_data_scroll( cls, scroll: str = '1m', fields: IdList = None, as_of: dt.datetime = None, limit: int = None, source: Optional[str] = None, **kwargs, ) -> dict: span = Tracer.active_span() tracer = Tracer('GsAsset.get_many_assets_data_scroll') if span and span.is_recording() else nullcontext() with tracer as scope: cls._set_tags(scope, kwargs) query = cls.__create_query(fields, as_of, limit, scroll, **kwargs) request_headers = {'X-Application': 'Studio'} if source == "Basket" else None response = GsSession.current.sync.post('/assets/data/query', payload=query, request_headers=request_headers) results = get(response, 'results') while has(response, 'scrollId') and len(get(response, 'results')): query = cls.__create_query(fields, as_of, limit, scroll, get(response, 'scrollId'), **kwargs) response = GsSession.current.sync.post( '/assets/data/query', payload=query, request_headers=request_headers ) results += get(response, 'results') return results @classmethod @_cached @backoff.on_exception(lambda: backoff.expo(base=2, factor=2), (MqTimeoutError, MqInternalServerError), max_tries=5) @backoff.on_exception(lambda: backoff.constant(90), MqRateLimitedError, max_tries=5) def resolve_assets( cls, identifier: [str], fields: IdList = [], limit: int = 100, as_of: dt.datetime = dt.datetime.today(), **kwargs, ) -> Tuple[dict, ...]: where = dict(identifier=identifier, **kwargs) query = dict(where=where, limit=limit, fields=fields, asOfTime=as_of.strftime("%Y-%m-%dT%H:%M:%SZ")) return GsSession.current.sync.post('/positions/resolver', payload=query) @classmethod def get_many_asset_xrefs( cls, identifier: [str], fields: IdList = [], limit: int = 100, as_of: dt.datetime = dt.datetime.today(), **kwargs, ) -> Tuple[dict, ...]: where = dict(identifier=identifier, **kwargs) query = dict(where=where, limit=limit, fields=fields, asOfTime=as_of.strftime("%Y-%m-%dT%H:%M:%SZ")) return GsSession.current.sync.post('/assets/xrefs/query', payload=query).get('results') @classmethod @_cached def get_asset_xrefs(cls, asset_id: str) -> Tuple[GsTemporalXRef, ...]: response = GsSession.current.sync.get('/assets/{id}/xrefs'.format(id=asset_id)) return tuple(GsTemporalXRef.from_dict(x) for x in response.get('xrefs', ())) @classmethod def put_asset_xrefs(cls, asset_id: str, xrefs: List[TemporalXRef]): return GsSession.current.sync.put(f'/assets/{asset_id}/xrefs', payload=xrefs) @classmethod @_cached def get_asset( cls, asset_id: str, ) -> GsAsset: return GsSession.current.sync.get('/assets/{id}'.format(id=asset_id), cls=GsAsset) @classmethod @_cached_async async def get_asset_async( cls, asset_id: str, ) -> GsAsset: return await GsSession.current.async_.get('/assets/{id}'.format(id=asset_id), cls=GsAsset) @classmethod def get_asset_by_name(cls, name: str) -> GsAsset: ret = GsSession.current.sync.get('/assets?name={}'.format(name)) num_found = ret.get('totalResults', 0) if num_found == 0: raise ValueError('Asset {} not found'.format(name)) elif num_found > 1: raise ValueError('More than one asset named {} found'.format(name)) else: return GsAsset.from_dict(ret['results'][0]) @classmethod def create_asset(cls, asset: GsAsset) -> GsAsset: return GsSession.current.sync.post('/assets', payload=asset, cls=GsAsset) @classmethod def delete_asset(cls, asset_id: str): return GsSession.current.sync.delete(f'/assets/{asset_id}') @staticmethod def get_position_dates(asset_id: str) -> Tuple[dt.date, ...]: position_dates = GsSession.current.sync.get(f'/assets/{asset_id}/positions/dates')['results'] return tuple(dt.datetime.strptime(d, '%Y-%m-%d').date() for d in position_dates) @staticmethod def get_asset_positions_for_date( asset_id: str, position_date: dt.date, position_type: PositionType = None, ) -> Tuple[PositionSet, ...]: position_date_str = position_date.isoformat() url = f'/assets/{asset_id}/positions/{position_date_str}' if position_type is not None: url += f'?type={position_type}' if isinstance(position_type, str) else f'?type={position_type.value}' results = GsSession.current.sync.get(url)['results'] return tuple(PositionSet.from_dict(r) for r in results) @staticmethod def get_asset_positions_for_dates( asset_id: str, start_date: dt.date, end_date: dt.date, position_type: PositionType = PositionType.CLOSE, ) -> Tuple[PositionSet, ...]: position_type = position_type if isinstance(position_type, str) else position_type.value position_sets = [] periods = (end_date - start_date).days // 30 start_date_str = start_date.isoformat() if periods > 1: end_dates = pd.date_range(start=start_date, end=end_date, periods=periods, inclusive='right') for date in end_dates: end_date_str = date.date().isoformat() url = f'/assets/{asset_id}/positions?startDate={start_date_str}&endDate={end_date_str}&type={position_type}' try: position_sets += GsSession.current.sync.get(url)['positionSets'] start_date_str = (date.date() + dt.timedelta(days=1)).isoformat() except HTTPError as err: raise ValueError(f'Unable to fetch position data at {url} with {err}') else: end_date_str = end_date.isoformat() url = f'/assets/{asset_id}/positions?startDate={start_date_str}&endDate={end_date_str}&type={position_type}' try: position_sets += GsSession.current.sync.get(url)['positionSets'] except HTTPError as err: raise ValueError(f'Unable to fetch position data at {url} with {err}') return tuple(PositionSet.from_dict(r) for r in position_sets) @staticmethod def get_latest_positions(asset_id: str, position_type: PositionType = None) -> PositionSet: url = '/assets/{id}/positions/last'.format(id=asset_id) if position_type is not None and position_type is not PositionType.ANY: url += '?type={ptype}'.format( ptype=position_type if isinstance(position_type, str) else position_type.value ) results = GsSession.current.sync.get(url)['results'] return PositionSet.from_dict(results) @staticmethod def get_or_create_asset_from_instrument(instrument: Instrument) -> str: asset = GsAsset( asset_class=instrument.asset_class, type_=instrument.type, name=instrument.name or '', parameters=instrument.as_dict(as_camel_case=True), ) results = GsSession.current.sync.post('/assets', asset) return results['id'] @staticmethod def get_instruments_for_asset_ids(asset_ids: Tuple[str, ...]) -> Tuple[Optional[Union[Instrument, Security]]]: instrument_infos = GsSession.current.sync.post('/assets/instruments', asset_ids, cls=AssetToInstrumentResponse) instrument_lookup = {i.assetId: i.instrument for i in instrument_infos if i} ret: Tuple[Optional[Union[Instrument, Security]], ...] = tuple(instrument_lookup.get(a) for a in asset_ids) return ret @staticmethod def get_instruments_for_positions(positions: Iterable[Position]) -> Tuple[Optional[Union[Instrument, Security]]]: asset_ids = tuple(filter(None, (p.asset_id for p in positions))) instrument_infos = ( GsSession.current.sync.post('/assets/instruments', asset_ids, cls=AssetToInstrumentResponse) if asset_ids else {} ) instrument_lookup = {i.assetId: (i.instrument, i.sizeField) for i in instrument_infos if i} ret = () for position in positions: instrument = None if position.instrument: instrument = position.instrument else: instrument_info = instrument_lookup.get(position.assetId) if instrument_info: instrument, size_field = instrument_info if ( instrument is not None and size_field is not None and getattr(instrument, size_field, None) is None ): setattr(instrument, size_field, position.quantity) ret += (instrument,) return ret @staticmethod def get_asset_positions_data( asset_id: str, start_date: dt.date, end_date: dt.date, fields: IdList = None, position_type: PositionType = None, ) -> List[dict]: start_date_str = start_date.isoformat() end_date_str = end_date.isoformat() url = '/assets/{id}/positions/data?startDate={start_date}&endDate={end_date}'.format( id=asset_id, start_date=start_date_str, end_date=end_date_str ) if fields is not None: url += '&fields='.join([''] + fields) if position_type is not None: url += '&type=' + position_type.value results = GsSession.current.sync.get(url)['results'] return results @staticmethod def update_asset_entitlements(asset_id: str, entitlements: Entitlements) -> dict: url = f'/assets/{asset_id}/entitlements' try: results = GsSession.current.sync.put(url, payload=entitlements) except HTTPError as err: raise ValueError(f'Unable to update asset entitlements with {err}') return results @classmethod def get_reports(cls, asset_id: str) -> Tuple[Report, ...]: return GsSession.current.sync.get(f'/assets/{asset_id}/reports', cls=Report)['results'] @classmethod @_cached def map_identifiers( cls, input_type: Union[GsIdType, str], output_type: Union[GsIdType, str], ids: IdList, as_of: dt.datetime = None, multimap: bool = False, limit: int = None, **kwargs, ) -> dict: if isinstance(input_type, GsIdType): input_type = input_type.name elif not isinstance(input_type, str): raise ValueError('input_type must be of type str or IdType') if isinstance(output_type, GsIdType): output_type = output_type.name elif not isinstance(output_type, str): raise ValueError('output_type must be of type str or IdType') the_args = kwargs the_args[input_type] = ids limit = limit or 4 * len(ids) query = cls.__create_query((input_type, output_type), as_of, limit, **the_args) results = GsSession.current.sync.post('/assets/data/query', payload=query) if len(results) >= query.limit: raise MqValueError('number of results may have exceeded capacity') if 'results' in results: results = results['results'] out = {} for entry in results: key = entry.get(input_type) value = entry.get(output_type) if multimap: bunch = out.setdefault(key, []) bunch.append(value) else: if key in out: _logger.warning('%s: more than one mapping for %s', GsAssetApi.map_identifiers.__name__, key) out[key] = value return out ================================================ FILE: gs_quant/api/gs/backtests.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import logging from typing import Tuple, Optional from urllib.parse import urlencode from gs_quant.common import FieldValueMap from gs_quant.errors import MqValueError from gs_quant.session import GsSession, DEFAULT_TIMEOUT from gs_quant.target.backtests import ( Backtest, BacktestResult, BacktestRisk, ComparisonBacktestResult, BacktestRiskRequest, BacktestRefData, ) _logger = logging.getLogger(__name__) class GsBacktestApi: """GS Backtest API client implementation""" @classmethod def get_many_backtests( cls, limit: int = 100, backtest_id: str = None, owner_id: str = None, name: str = None, mq_symbol: str = None ) -> Tuple[Backtest, ...]: query_string = urlencode( dict( filter( lambda item: item[1] is not None, dict(id=backtest_id, ownerId=owner_id, name=name, mqSymbol=mq_symbol, limit=limit).items(), ) ) ) return GsSession.current.sync.get('/backtests?{query}'.format(query=query_string), cls=Backtest)['results'] @classmethod def get_backtest(cls, backtest_id: str) -> Backtest: return GsSession.current.sync.get('/backtests/{id}'.format(id=backtest_id), cls=Backtest) @classmethod def create_backtest(cls, backtest: Backtest) -> Backtest: request_headers = {'Content-Type': 'application/json;charset=utf-8', 'Accept': 'application/json;charset=utf-8'} return GsSession.current.sync.post('/backtests', backtest, request_headers=request_headers, cls=Backtest) @classmethod def update_backtest(cls, backtest: Backtest): request_headers = {'Content-Type': 'application/json;charset=utf-8', 'Accept': 'application/json;charset=utf-8'} return GsSession.current.sync.put( '/backtests/{id}'.format(id=backtest.id), backtest, request_headers=request_headers, cls=Backtest ) @classmethod def delete_backtest(cls, backtest_id: str) -> dict: return GsSession.current.sync.delete('/backtests/{id}'.format(id=backtest_id)) @classmethod def get_results(cls, backtest_id: str) -> Tuple[BacktestResult, ...]: return GsSession.current.sync.get('/backtests/results?id={id}'.format(id=backtest_id))['backtestResults'] @classmethod def get_comparison_results( cls, limit: int = 100, start_date: dt.date = None, end_date: dt.date = None, backtest_id: str = None, comparison_id: str = None, owner_id: str = None, name: str = None, mq_symbol: str = None, ) -> Tuple[Tuple[BacktestResult, ...], Tuple[ComparisonBacktestResult, ...]]: query_string = urlencode( dict( filter( lambda item: item[1] is not None, dict( id=backtest_id, comparisonIds=comparison_id, ownerId=owner_id, name=name, mqSymbol=mq_symbol, limit=limit, startDate=start_date.isoformat(), endDate=end_date.isoformat(), ).items(), ) ) ) result = GsSession.current.sync.get('/backtests/results?{query}'.format(query=query_string)) return result['backtestResults'], result['comparisonResults'] @classmethod def schedule_backtest(cls, backtest_id: str) -> dict: return GsSession.current.sync.post('/backtests/{id}/schedule'.format(id=backtest_id)) @classmethod def run_backtest( cls, backtest: Backtest, correlation_id: str = None, timeout: Optional[int] = DEFAULT_TIMEOUT ) -> BacktestResult: """ :param backtest: definition of a backtest which should be run on Marquee API :param correlation_id: used for logging purposes; helps in tracking all the requests which ultimately serve the same purpose (e.g. calculating a backtest) :return: result of running the backtest """ request_headers = {'Content-Type': 'application/json;charset=utf-8', 'Accept': 'application/json;charset=utf-8'} if correlation_id is not None: request_headers["X-CorrelationId"] = correlation_id response = GsSession.current.sync.post( '/backtests/calculate', backtest, request_headers=request_headers, timeout=timeout ) return cls.backtest_result_from_response(response) @classmethod def backtest_result_from_response(cls, response: dict) -> BacktestResult: if 'RiskData' not in response: raise MqValueError('No risk data received') portfolio = response['Portfolio'] if 'Portfolio' in response else None risks = tuple( BacktestRisk(name=k, timeseries=tuple(FieldValueMap(date=r['date'], value=r['value']) for r in v)) for k, v in response['RiskData'].items() ) return BacktestResult(portfolio=portfolio, risks=risks) @classmethod def calculate_position_risk( cls, backtestRiskRequest: BacktestRiskRequest, timeout: Optional[int] = DEFAULT_TIMEOUT ) -> dict: request_headers = {'Content-Type': 'application/json;charset=utf-8', 'Accept': 'application/json;charset=utf-8'} return GsSession.current.sync.post( '/backtests/calculate-position-risk', backtestRiskRequest, request_headers=request_headers, timeout=timeout ) @classmethod def get_ref_data(cls) -> BacktestRefData: return GsSession.current.sync.get('/backtests/refData', cls=BacktestRefData) @classmethod def update_ref_data(cls, backtest_ref_data: BacktestRefData): request_headers = {'Content-Type': 'application/json;charset=utf-8', 'Accept': 'application/json;charset=utf-8'} return GsSession.current.sync.put( '/backtests/refData', backtest_ref_data, request_headers=request_headers, cls=backtest_ref_data ) class GsBacktestApiAsync(GsBacktestApi): @classmethod async def calculate_position_risk( cls, backtestRiskRequest: BacktestRiskRequest, timeout: Optional[int] = DEFAULT_TIMEOUT ) -> dict: request_headers = {'Content-Type': 'application/json;charset=utf-8', 'Accept': 'application/json;charset=utf-8'} response = await GsSession.current.async_.post( '/backtests/calculate-position-risk', backtestRiskRequest, request_headers=request_headers, timeout=timeout ) return response @classmethod async def run_backtest( cls, backtest: Backtest, correlation_id: str = None, timeout: Optional[int] = DEFAULT_TIMEOUT ) -> BacktestResult: """ :param backtest: definition of a backtest which should be run on Marquee API :param correlation_id: used for logging purposes; helps in tracking all the requests which ultimately serve the same purpose (e.g. calculating a backtest) :return: result of running the backtest """ request_headers = {'Content-Type': 'application/json;charset=utf-8', 'Accept': 'application/json;charset=utf-8'} if correlation_id is not None: request_headers["X-CorrelationId"] = correlation_id response = await GsSession.current.async_.post( '/backtests/calculate', backtest, request_headers=request_headers, timeout=timeout ) return cls.backtest_result_from_response(response) ================================================ FILE: gs_quant/api/gs/backtests_xasset/__init__.py ================================================ ================================================ FILE: gs_quant/api/gs/backtests_xasset/apis.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.api.gs.backtests_xasset.request import RiskRequest, BasicBacktestRequest from gs_quant.api.gs.backtests_xasset.response import RiskResponse, BasicBacktestResponse from gs_quant.session import GsSession class GsBacktestXassetApi: HEADERS = {'Accept': 'application/json'} TIMEOUT = 90 @classmethod def calculate_risk(cls, risk_request: RiskRequest) -> RiskResponse: response = GsSession.current.sync.post( '/backtests/xasset/risk', risk_request.to_json(), request_headers=cls.HEADERS, timeout=cls.TIMEOUT ) result = RiskResponse.from_dict(response) return result @classmethod def calculate_basic_backtest( cls, backtest_request: BasicBacktestRequest, decode_instruments: bool = True ) -> BasicBacktestResponse: response = GsSession.current.sync.post( '/backtests/xasset/strategy/basic', backtest_request.to_json(), request_headers=cls.HEADERS, timeout=cls.TIMEOUT, ) result = BasicBacktestResponse.from_dict_custom(response, decode_instruments) return result class GsBacktestXassetApiAsync(GsBacktestXassetApi): @classmethod async def calculate_risk(cls, risk_request: RiskRequest) -> RiskResponse: response = await GsSession.current.async_.post( '/backtests/xasset/risk', risk_request.to_json(), request_headers=cls.HEADERS, timeout=cls.TIMEOUT ) result = RiskResponse.from_dict(response) return result @classmethod async def calculate_basic_backtest( cls, backtest_request: BasicBacktestRequest, decode_instruments: bool = True ) -> BasicBacktestResponse: response = await GsSession.current.async_.post( '/backtests/xasset/strategy/basic', backtest_request.to_json(), request_headers=cls.HEADERS, timeout=cls.TIMEOUT, ) result = BasicBacktestResponse.from_dict_custom(response, decode_instruments) return result ================================================ FILE: gs_quant/api/gs/backtests_xasset/json_encoders/__init__.py ================================================ ================================================ FILE: gs_quant/api/gs/backtests_xasset/json_encoders/request_encoders.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from typing import Any, Iterable from gs_quant.base import EnumBase from gs_quant.common import RiskMeasure from gs_quant.instrument import Instrument from gs_quant.json_convertors_common import encode_risk_measure def encode_request_object(data: Any): if isinstance(data, RiskMeasure): return encode_risk_measure(data) if isinstance(data, Instrument): return data.to_dict() if isinstance(data, tuple): return tuple(encode_request_object(d) for d in data) def legs_decoder(data: Any): if data is None: return None result = [Instrument.from_dict(d) for d in data] names = set(getattr(i, 'name', None) for i in result) name_idx = 0 for i in result: if i.name is not None: continue cur_name = 'leg_' + str(name_idx) while cur_name in names: name_idx += 1 cur_name = 'leg_' + str(name_idx) i.name = cur_name name_idx += 1 return result def legs_encoder(data: Iterable[Instrument]): return [i.to_dict() for i in data] def enum_decode(enum_class): def decode_value(value): if value is None or 'null' == value: return None if isinstance(value, EnumBase): return value if isinstance(value, str): try: return enum_class(value) except ValueError: pass raise ValueError(f'Unable to decode {value} into any enum class') return decode_value ================================================ FILE: gs_quant/api/gs/backtests_xasset/json_encoders/response_datatypes/__init__.py ================================================ ================================================ FILE: gs_quant/api/gs/backtests_xasset/json_encoders/response_datatypes/generic_datatype_encoders.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from typing import Dict, Tuple from gs_quant.instrument import Instrument def decode_inst(i: dict) -> Instrument: return Instrument.from_dict(i) def decode_inst_tuple(t: tuple) -> Tuple[Instrument, ...]: return tuple(decode_inst(i) for i in t) def decode_daily_portfolio(results: dict, decode_instruments: bool = True) -> Dict[dt.date, Tuple[Instrument, ...]]: return {dt.date.fromisoformat(k): decode_inst_tuple(v) if decode_instruments else v for k, v in results.items()} ================================================ FILE: gs_quant/api/gs/backtests_xasset/json_encoders/response_datatypes/risk_result_datatype_encoders.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import pandas as pd from typing import Dict def encode_series_result(s: pd.Series) -> Dict: return {'index': tuple(s.index), 'name': s.name, 'values': tuple(s.values)} def encode_dataframe_result(df: pd.DataFrame) -> Dict: return {'index': tuple(df.index), 'columns': tuple(df.columns), 'values': tuple(tuple(v) for v in df.values)} def _convert_list_to_dates(lst: list): if not (lst and isinstance(lst[0], str)): return lst try: lst = tuple(dt.date.fromisoformat(v) for v in lst) except ValueError: pass return lst def decode_series_result(s: dict) -> pd.Series: return pd.Series(s['values'], index=_convert_list_to_dates(s['index']), name=s['name']) def decode_dataframe_result(s: dict) -> pd.DataFrame: return pd.DataFrame(s['values'], index=_convert_list_to_dates(s['index']), columns=s['columns']) ================================================ FILE: gs_quant/api/gs/backtests_xasset/json_encoders/response_datatypes/risk_result_encoders.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from typing import Any, Type import pandas as pd from gs_quant.api.gs.backtests_xasset.response_datatypes.risk_result import ( RiskResultsByDate, RefType, RiskResultsError, RiskResults, ) from gs_quant.api.gs.backtests_xasset.response_datatypes.risk_result_datatypes import ( FloatWithData, StringWithData, VectorWithData, MatrixWithData, RiskResultWithData, DefnValuesWithData, DictsWithData, ) from gs_quant.priceable import PriceableImpl _type_to_datatype_map = { 'float': FloatWithData, 'string': StringWithData, 'vector': VectorWithData, 'matrix': MatrixWithData, 'defn': DefnValuesWithData, 'dict': DictsWithData, } def map_result_to_datatype(data: Any) -> Type[RiskResultWithData]: if isinstance(data, (float, int)): return FloatWithData if isinstance(data, str): return StringWithData if isinstance(data, pd.Series): return VectorWithData if isinstance(data, pd.DataFrame): return MatrixWithData if isinstance(data, PriceableImpl): return DefnValuesWithData if isinstance(data, dict): return DictsWithData raise ValueError('Cannot assign result type to data') def decode_risk_result_with_data(r: dict) -> RiskResultWithData: return _type_to_datatype_map[r['type']].from_dict(r) def decode_risk_result(d: dict) -> RiskResults: refs = {RefType(k): v for k, v in d['refs'].items()} if 'result' in d: result = {dt.date.fromisoformat(k): decode_risk_result_with_data(v) for k, v in d['result'].items()} return RiskResultsByDate(refs, result) else: return RiskResultsError(refs, d['error'], d['trace_id']) ================================================ FILE: gs_quant/api/gs/backtests_xasset/json_encoders/response_datatypes/test_backtest_datatypes_encoders.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import json from gs_quant.api.gs.backtests_xasset.response_datatypes.backtest_datatypes import ( TransactionCostConfig, TradingCosts, FixedCostModel, ScaledCostModel, TransactionCostScalingType, AggregateCostModel, CostAggregationType, ) def test_transaction_cost_config_encoding(): tc = TransactionCostConfig.from_dict( {"tradeCostModel": {"entry": {"cost": 5.0, "type": "fixed_cost_model"}}, "hedgeCostModel": None} ) assert tc == TransactionCostConfig( trade_cost_model=TradingCosts(entry=FixedCostModel(cost=5.0), exit=None), hedge_cost_model=None ) tc = TransactionCostConfig.from_dict( { "tradeCostModel": { "entry": {"scalingLevel": 5.0, "scalingQuantityType": "Vega", "type": "scaled_cost_model"}, "exit": { "models": [ {"scalingLevel": 7.0, "scalingQuantityType": "Notional", "type": "scaled_cost_model"}, {"cost": 9.0, "type": "fixed_cost_model"}, ], "aggregationType": "Sum", "type": "aggregate_cost_model", }, }, "hedgeCostModel": {"entry": {"cost": 10, "type": "fixed_cost_model"}}, } ) assert tc == TransactionCostConfig( TradingCosts( ScaledCostModel(5.0, TransactionCostScalingType.Vega), AggregateCostModel( models=(ScaledCostModel(7.0, TransactionCostScalingType.Notional), FixedCostModel(9.0)), aggregation_type=CostAggregationType.Sum, ), ), TradingCosts(FixedCostModel(10)), ) assert tc == TransactionCostConfig.from_dict(json.loads(tc.to_json())) tc = TransactionCostConfig.from_dict( { "tradeCostModel": { "entry": {"scalingLevel": 5.0, "scalingQuantityType": "Quantity", "type": "ScaledCostModel"}, "exit": { "models": [ {"scalingLevel": 7.0, "scalingQuantityType": "Notional", "type": "ScaledCostModel"}, {"cost": 9.0, "type": "FixedCostModel"}, ], "aggregationType": "Sum", "type": "AggregateCostModel", }, }, "hedgeCostModel": {"entry": {"cost": 10, "type": "FixedCostModel"}}, } ) assert tc == TransactionCostConfig( TradingCosts( ScaledCostModel(5.0, TransactionCostScalingType.Quantity), AggregateCostModel( models=(ScaledCostModel(7.0, TransactionCostScalingType.Notional), FixedCostModel(9.0)), aggregation_type=CostAggregationType.Sum, ), ), TradingCosts(FixedCostModel(10)), ) assert tc == TransactionCostConfig.from_dict(json.loads(tc.to_json())) ================================================ FILE: gs_quant/api/gs/backtests_xasset/json_encoders/response_encoders.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import pandas as pd from typing import Dict, Any, Tuple, Union from gs_quant.api.gs.backtests_xasset.json_encoders.response_datatypes.generic_datatype_encoders import ( decode_inst_tuple, decode_inst, ) from gs_quant.api.gs.backtests_xasset.json_encoders.response_datatypes.risk_result_datatype_encoders import ( encode_series_result, encode_dataframe_result, ) from gs_quant.api.gs.backtests_xasset.json_encoders.response_datatypes.risk_result_encoders import ( decode_risk_result, decode_risk_result_with_data, ) from gs_quant.api.gs.backtests_xasset.response_datatypes.backtest_datatypes import Transaction, TransactionDirection from gs_quant.api.gs.backtests_xasset.response_datatypes.risk_result_datatypes import RiskResultWithData from gs_quant.common import Currency, CurrencyName, RiskMeasure from gs_quant.json_convertors_common import encode_risk_measure, decode_risk_measure from gs_quant.priceable import PriceableImpl from gs_quant.target.backtests import FlowVolBacktestMeasure def encode_response_obj(data: Any) -> Dict: if isinstance(data, RiskMeasure): return encode_risk_measure(data) if isinstance(data, pd.Series): return encode_series_result(data) if isinstance(data, pd.DataFrame): return encode_dataframe_result(data) return data.to_dict() def decode_leg_refs(d: dict) -> Dict[str, PriceableImpl]: return {k: decode_inst(v) for k, v in d.items()} def decode_risk_measure_refs(d: dict) -> Dict[str, RiskMeasure]: return {k: decode_risk_measure(v) for k, v in d.items()} def decode_result_tuple(results: tuple): return tuple(decode_risk_result(r) for r in results) def decode_basic_bt_measure_dict(results: dict) -> Dict[FlowVolBacktestMeasure, Dict[dt.date, RiskResultWithData]]: return { FlowVolBacktestMeasure(k): {dt.date.fromisoformat(d): decode_risk_result_with_data(r) for d, r in v.items()} for k, v in results.items() } def decode_basic_bt_transactions( results: dict, decode_instruments: bool = True ) -> Dict[dt.date, Tuple[Transaction, ...]]: def to_ccy(s: str) -> Union[Currency, CurrencyName, str]: if s in [x.value for x in Currency]: return Currency(s) elif s in [x.value for x in CurrencyName]: return CurrencyName(s) else: return s return { dt.date.fromisoformat(k): tuple( Transaction( decode_inst_tuple(t['portfolio']) if decode_instruments else t['portfolio'], t.get('portfolio_price'), t.get('cost'), to_ccy(t['currency']) if t.get('currency') else None, TransactionDirection(t['direction']) if t.get('direction') else None, t.get('quantity'), ) for t in v ) for k, v in results.items() } ================================================ FILE: gs_quant/api/gs/backtests_xasset/request.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from dataclasses import dataclass, field from enum import Enum from typing import Optional, Union, Tuple from dataclasses_json import dataclass_json, LetterCase, config from gs_quant.api.gs.backtests_xasset.json_encoders.request_encoders import legs_encoder, legs_decoder, enum_decode from gs_quant.api.gs.backtests_xasset.response_datatypes.backtest_datatypes import ( DateConfig, Trade, Configuration, TransactionCostConfig, StrategyHedge, ) from gs_quant.api.gs.backtests_xasset.response_datatypes.generic_backtest_datatypes import Strategy from gs_quant.base import EnumBase from gs_quant.common import RiskMeasure from gs_quant.json_convertors import decode_optional_date, decode_date_tuple, encode_date_tuple from gs_quant.json_convertors_common import encode_risk_measure_tuple, decode_risk_measure_tuple from gs_quant.priceable import PriceableImpl from gs_quant.target.backtests import FlowVolBacktestMeasure class RiskProviderEnum(EnumBase, Enum): Default = "Default" DataSetProvider = "DataSetProvider" EqVolRiskProvider = "EqVolRiskProvider" @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class RiskRequest: start_date: Optional[dt.date] = field(default=None, metadata=config(decoder=decode_optional_date)) end_date: Optional[dt.date] = field(default=None, metadata=config(decoder=decode_optional_date)) additional_dates: Optional[Tuple[dt.date, ...]] = field( default=None, metadata=config(encoder=encode_date_tuple, decoder=decode_date_tuple) ) legs: Optional[Tuple[PriceableImpl, ...]] = field( default=None, metadata=config(encoder=legs_encoder, decoder=legs_decoder) ) measures: Optional[Tuple[RiskMeasure, ...]] = field( default=None, metadata=config(encoder=encode_risk_measure_tuple, decoder=decode_risk_measure_tuple) ) risk_provider: Optional[RiskProviderEnum] = field( default=None, metadata=config(decoder=enum_decode(RiskProviderEnum)) ) @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BasicBacktestRequest: dates: DateConfig trades: Tuple[Trade, ...] measures: Tuple[FlowVolBacktestMeasure, ...] delta_hedge_frequency: Optional[str] = None transaction_costs: Optional[TransactionCostConfig] = None configuration: Optional[Configuration] = None hedge: Optional[StrategyHedge] = None risk_provider: Optional[RiskProviderEnum] = field( default=None, metadata=config(decoder=enum_decode(RiskProviderEnum)) ) @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class GenericBacktestRequest: strategy: Strategy dates: Union[DateConfig, Tuple[dt.date, ...]] configuration: Optional[Configuration] = None ================================================ FILE: gs_quant/api/gs/backtests_xasset/response.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from dataclasses import dataclass, field from typing import Dict, Optional, Tuple, Any from dataclasses_json import dataclass_json, LetterCase, config from gs_quant.api.gs.backtests_xasset.json_encoders.response_datatypes.generic_datatype_encoders import ( decode_daily_portfolio, ) from gs_quant.api.gs.backtests_xasset.json_encoders.response_encoders import ( decode_leg_refs, decode_risk_measure_refs, decode_result_tuple, decode_basic_bt_measure_dict, decode_basic_bt_transactions, ) from gs_quant.api.gs.backtests_xasset.response_datatypes.backtest_datatypes import Transaction, AdditionalResults from gs_quant.api.gs.backtests_xasset.response_datatypes.risk_result import RiskResults from gs_quant.api.gs.backtests_xasset.response_datatypes.risk_result_datatypes import RiskResultWithData from gs_quant.instrument import Instrument from gs_quant.priceable import PriceableImpl from gs_quant.common import RiskMeasure from gs_quant.target.backtests import FlowVolBacktestMeasure @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass class RiskResponse: legRefs: Dict[str, PriceableImpl] = field(default=None, metadata=config(decoder=decode_leg_refs)) riskMeasureRefs: Dict[str, RiskMeasure] = field(default=None, metadata=config(decoder=decode_risk_measure_refs)) results: Tuple[RiskResults, ...] = field(default=None, metadata=config(decoder=decode_result_tuple)) @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass class BasicBacktestResponse: measures: Dict[FlowVolBacktestMeasure, Dict[dt.date, RiskResultWithData]] = field( default=None, metadata=config(decoder=decode_basic_bt_measure_dict) ) portfolio: Dict[dt.date, Tuple[Instrument, ...]] = field( default=None, metadata=config(decoder=decode_daily_portfolio) ) transactions: Dict[dt.date, Tuple[Transaction, ...]] = field( default=None, metadata=config(decoder=decode_basic_bt_transactions) ) additional_results: Optional[AdditionalResults] = field(default=None) @classmethod def from_dict_custom(cls, data: Any, decode_instruments: bool = True): if decode_instruments: return cls.from_dict(data) return BasicBacktestResponse( measures=decode_basic_bt_measure_dict(data['measures']), portfolio=decode_daily_portfolio(data['portfolio'], decode_instruments), transactions=decode_basic_bt_transactions(data['transactions'], decode_instruments), additional_results=AdditionalResults.from_dict_custom(data['additional_results'], decode_instruments) if data['additional_results'] is not None else None, ) @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass class GenericBacktestResponse: pass ================================================ FILE: gs_quant/api/gs/backtests_xasset/response_datatypes/__init__.py ================================================ ================================================ FILE: gs_quant/api/gs/backtests_xasset/response_datatypes/backtest_datatypes.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import dataclasses import datetime as dt from abc import abstractmethod from dataclasses import dataclass, field from enum import Enum from typing import Optional, Tuple, Dict, Union, Any from dataclasses_json import dataclass_json, LetterCase, config from gs_quant.api.gs.backtests_xasset.json_encoders.request_encoders import legs_decoder from gs_quant.api.gs.backtests_xasset.json_encoders.response_datatypes.generic_datatype_encoders import ( decode_daily_portfolio, ) from gs_quant.instrument import Instrument from gs_quant.interfaces.algebra import AlgebraicType from gs_quant.json_convertors import ( decode_optional_date, encode_date_tuple, decode_date_tuple, decode_dict_date_key_or_float, ) from gs_quant.target.backtests import BacktestTradingQuantityType, EquityMarketModel from gs_quant.common import Currency, CurrencyName, PricingLocation class TransactionCostModel(Enum): Fixed = 'Fixed' class TransactionDirection(Enum): Entry = 'Entry' Exit = 'Exit' class RollDateMode(Enum): OTC = 'OTC' Listed = 'Listed' @classmethod def _missing_(cls, value): if value is None: return None for member in cls: if member.value.lower() == value.lower(): return member return None class TransactionCostScalingType(Enum): Quantity = 'Quantity' Notional = 'Notional' Delta = 'Delta' Vega = 'Vega' class CostAggregationType(Enum): Sum = 'Sum' Max = 'Max' Min = 'Min' class HedgeRiskMeasure(Enum): Delta = 'Delta' @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass class Transaction: portfolio: Tuple[Instrument, ...] portfolio_price: Optional[float] = None cost: Optional[float] = None currency: Optional[Union[Currency, CurrencyName, str]] = None direction: Optional[TransactionDirection] = None quantity: Optional[float] = None @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass class TradeEvent: direction: TransactionDirection price: float trade_id: Optional[str] = None def decode_trade_event_tuple_dict(results: dict) -> Dict[dt.date, Tuple[TradeEvent, ...]]: return {dt.date.fromisoformat(k): tuple(TradeEvent.from_dict(e) for e in v) for k, v in results.items()} @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass class AdditionalResults: hedges: Optional[Dict[dt.date, Tuple[Instrument, ...]]] = field( default=None, metadata=config(decoder=decode_daily_portfolio) ) hedge_pnl: Optional[Dict[dt.date, float]] = None no_of_calculations: Optional[int] = None trade_events: Optional[Dict[dt.date, Tuple[TradeEvent, ...]]] = field( default=None, metadata=config(decoder=decode_trade_event_tuple_dict) ) hedge_events: Optional[Dict[dt.date, Tuple[TradeEvent, ...]]] = field( default=None, metadata=config(decoder=decode_trade_event_tuple_dict) ) @classmethod def from_dict_custom(cls, data: Any, decode_instruments: bool = True): if decode_instruments: return cls.from_dict(data) return AdditionalResults( hedges=decode_daily_portfolio(data['hedges'], decode_instruments), hedge_pnl=data['hedge_pnl'], no_of_calculations=data['no_of_calculations'], trade_events=decode_trade_event_tuple_dict(data['trade_events']), hedge_events=decode_trade_event_tuple_dict(data['hedge_events']), ) @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DateConfig: start_date: dt.date = field(default=None, metadata=config(decoder=decode_optional_date)) end_date: dt.date = field(default=None, metadata=config(decoder=decode_optional_date)) frequency: str = '1b' holiday_calendar: Optional[str] = None @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Trade: legs: Optional[Tuple[Instrument, ...]] = field(default=None, metadata=config(decoder=legs_decoder)) buy_frequency: str = None buy_dates: Optional[Tuple[dt.date, ...]] = field( default=None, metadata=config(encoder=encode_date_tuple, decoder=decode_date_tuple) ) holding_period: str = None exit_dates: Optional[Tuple[dt.date, ...]] = field( default=None, metadata=config(encoder=encode_date_tuple, decoder=decode_date_tuple) ) quantity: Optional[Union[float, dict[dt.date, float]]] = field( default=None, metadata=config(decoder=decode_dict_date_key_or_float) ) quantity_type: BacktestTradingQuantityType = BacktestTradingQuantityType.quantity @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Model(AlgebraicType): pass @property @abstractmethod def scaling_property(self): ... def set_scaling_property(self, value: float): setattr(self, self.scaling_property, value) def __add__(self, other): if not isinstance(other, Model): raise TypeError('Can only add to another cost model') if isinstance(other, AggregateCostModel): return other + self scaling_prop_name = self.scaling_property can_add_scaling = type(self) is type(other) and other == dataclasses.replace( self, **{scaling_prop_name: getattr(other, scaling_prop_name)} ) if can_add_scaling: result = dataclasses.replace(self) result.set_scaling_property(getattr(self, scaling_prop_name) + getattr(other, scaling_prop_name)) return result return AggregateCostModel((self, other), CostAggregationType.Sum) def __sub__(self, other): raise NotImplementedError('Multiplication not implemented for cost models') def __mul__(self, other): raise NotImplementedError('Multiplication not implemented for cost models') def __div__(self, other): raise NotImplementedError('Division not implemented for cost models') @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FixedCostModel(Model): cost: float = 0.0 type: str = 'FixedCostModel' @property def scaling_property(self): return 'cost' def __eq__(self, other): if not isinstance(other, FixedCostModel): return False return (self.cost,) == (other.cost,) @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ScaledCostModel(Model): scaling_level: float = 0.0 scaling_quantity_type: TransactionCostScalingType = TransactionCostScalingType.Quantity type: str = 'ScaledCostModel' @property def scaling_property(self): return 'scaling_level' def __eq__(self, other): if not isinstance(other, ScaledCostModel): return False return (self.scaling_level, self.scaling_quantity_type) == (other.scaling_level, other.scaling_quantity_type) _type_to_basic_model_map = { 'fixed_cost_model': FixedCostModel, 'scaled_cost_model': ScaledCostModel, 'FixedCostModel': FixedCostModel, 'ScaledCostModel': ScaledCostModel, } def basic_tc_tuple_decoder(data: Optional[Tuple[dict, ...]]) -> Optional[Union[FixedCostModel, ScaledCostModel]]: if data is None: return None return tuple(_type_to_basic_model_map[m['type']].from_dict(m) for m in data) @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AggregateCostModel(Model): models: Tuple[Union[FixedCostModel, ScaledCostModel], ...] = field(metadata=config(decoder=basic_tc_tuple_decoder)) aggregation_type: CostAggregationType type: str = 'AggregateCostModel' @property def scaling_property(self): return None def set_scaling_property(self, value: float): pass def __add__(self, other): if isinstance(other, AggregateCostModel) and self.aggregation_type is other.aggregation_type: return AggregateCostModel(self.models + other.models, self.aggregation_type) raise TypeError('Can only add with AggregateCostModels with the same aggregation type') def __eq__(self, other): if not isinstance(other, AggregateCostModel): return False return (self.models, self.aggregation_type) == (other.models, other.aggregation_type) def tcm_decoder(data: Optional[dict]) -> Optional[Union[FixedCostModel, ScaledCostModel, AggregateCostModel]]: full_type_map = { **_type_to_basic_model_map, **{'aggregate_cost_model': AggregateCostModel, 'AggregateCostModel': AggregateCostModel}, } return full_type_map[data['type']].from_dict(data) if data is not None else None @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class TradingCosts: entry: Union[FixedCostModel, ScaledCostModel, AggregateCostModel] = field( default=FixedCostModel(0), metadata=config(decoder=tcm_decoder) ) exit: Optional[Union[FixedCostModel, ScaledCostModel, AggregateCostModel]] = field( default=None, metadata=config(decoder=tcm_decoder) ) @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class TransactionCostConfig: trade_cost_model: TradingCosts hedge_cost_model: Optional[TradingCosts] = None @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Configuration: market_data_location: Optional[PricingLocation] = None market_model: Optional[EquityMarketModel] = None cash_accrual: bool = False roll_date_mode: Optional[RollDateMode] = None combine_roll_signal_entries: bool = False @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class StrategyHedge: risk: HedgeRiskMeasure = HedgeRiskMeasure.Delta frequency: str = '1b' risk_percentage: float = 100 ================================================ FILE: gs_quant/api/gs/backtests_xasset/response_datatypes/generic_backtest_datatypes.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from dataclasses import dataclass from dataclasses_json import dataclass_json @dataclass_json @dataclass class Strategy(object): """ A strategy object on which one may run a backtest """ pass ================================================ FILE: gs_quant/api/gs/backtests_xasset/response_datatypes/risk_result.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from dataclasses import dataclass from enum import Enum from typing import Dict, Optional from dataclasses_json import dataclass_json, LetterCase from gs_quant.api.gs.backtests_xasset.response_datatypes.risk_result_datatypes import RiskResultWithData class RefType(Enum): LEG_ID = 'legId' RISK_MEASURE = 'riskMeasure' @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass class RiskResults: refs: Dict[RefType, str] @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass class RiskResultsByDate(RiskResults): result: Dict[dt.date, RiskResultWithData] @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass class RiskResultsError(RiskResults): error: Optional[str] = None trace_id: Optional[str] = None ================================================ FILE: gs_quant/api/gs/backtests_xasset/response_datatypes/risk_result_datatypes.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from dataclasses import dataclass, field from typing import Optional import pandas as pd from dataclasses_json import dataclass_json, LetterCase, config from gs_quant.api.gs.backtests_xasset.json_encoders.response_datatypes.generic_datatype_encoders import decode_inst from gs_quant.api.gs.backtests_xasset.json_encoders.response_datatypes.risk_result_datatype_encoders import ( encode_series_result, decode_series_result, encode_dataframe_result, decode_dataframe_result, ) from gs_quant.priceable import PriceableImpl @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass class RiskResultWithData: unit: Optional[str] = None def check_can_aggregate(self, other): if isinstance(other, RiskResultWithData): if self.unit != other.unit: raise ValueError(f'Cannot aggregate risk results with different units: {self.unit}, {other.unit}') else: if not isinstance(other, type(self.result)): raise TypeError(f'Incorrect type for other operand {type(other)}') def __add__(self, other): self.check_can_aggregate(other) other_operand = other.result if isinstance(other, RiskResultWithData) else other return type(self)(unit=self.unit, result=self.result + other_operand) def __radd__(self, other): self.check_can_aggregate(other) other_operand = other.result if isinstance(other, RiskResultWithData) else other return type(self)(unit=self.unit, result=other_operand + self.result) def __sub__(self, other): self.check_can_aggregate(other) other_operand = other.result if isinstance(other, RiskResultWithData) else other return type(self)(unit=self.unit, result=self.result - other_operand) def __mul__(self, other): self.check_can_aggregate(other) other_operand = other.result if isinstance(other, RiskResultWithData) else other return type(self)(unit=self.unit, result=self.result * other_operand) def __rmul__(self, other): self.check_can_aggregate(other) other_operand = other.result if isinstance(other, RiskResultWithData) else other return type(self)(unit=self.unit, result=other_operand * self.result) def __truediv__(self, other): self.check_can_aggregate(other) other_operand = other.result if isinstance(other, RiskResultWithData) else other return type(self)(unit=self.unit, result=self.result / other_operand) @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass class FloatWithData(RiskResultWithData): result: Optional[float] = None type: str = 'float' @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass class StringWithData(RiskResultWithData): result: Optional[str] = None type: str = 'string' @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass class VectorWithData(RiskResultWithData): result: Optional[pd.Series] = field( default=None, metadata=config(encoder=encode_series_result, decoder=decode_series_result) ) type: str = 'vector' @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass class MatrixWithData(RiskResultWithData): result: pd.DataFrame = field( default=None, metadata=config(encoder=encode_dataframe_result, decoder=decode_dataframe_result) ) type: str = 'matrix' @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass class DefnValuesWithData(RiskResultWithData): result: Optional[PriceableImpl] = field(default=None, metadata=config(decoder=decode_inst)) type: str = 'defn' @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass class DictsWithData(RiskResultWithData): result: Optional[dict] = None type: str = 'dict' ================================================ FILE: gs_quant/api/gs/base_screener.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import logging from typing import Tuple, Dict, Any, List from gs_quant.session import GsSession from gs_quant.target.base_screener import Screener _logger = logging.getLogger(__name__) class GsBaseScreenerApi: @classmethod def get_screeners(cls) -> Tuple[Screener, ...]: """ Retrieves screener information about all screeners accessible to the current user. :return: Screener tuple, a tuple containing each screener available to the user. """ return GsSession.current.sync.get('/data/screeners', cls=Screener)['results'] @classmethod def get_screener(cls, screener_id: str) -> Screener: """ Retrieves information about the screener specified in parameter screener_id. :param screener_id: str, the id of the screener whose information is being retrieved. :return: Screener, an object containing information about the screener associated with screener_id. """ return GsSession.current.sync.get('/data/screeners/{id}'.format(id=screener_id), cls=Screener) @classmethod def create_screener(cls, screener: Screener) -> Screener: """ Creates a new screener with a new id from a Screener object. The new screener copies the specifications of the Screener object passed into this function. To make changes to the new screener's schema upon creation, change the attributes of the Screener object before passing it into this function. User must be a part of the PlotScreenerAdmins group to perform this action. :param screener: Screener, the screener object that is copied to make the new screener. :return: Screener, the new screener object containing a new id """ request_headers = {'Content-Type': 'application/json;charset=utf-8'} return GsSession.current.sync.post('/data/screeners', screener, request_headers=request_headers, cls=Screener) @classmethod def edit_screener(cls, screener_id: str, screener: Screener) -> Screener: """ Edits the existing screener with ID screener_id to follow the schema specified in the passed in Screener object. The existing screener's original schema will be entirely overwritten by this object's schema. The edited screener will retain its screener ID after being altered by this function. User must be a part of the PlotScreenerAdmins group to perform this action. Throws an error if the screener_id provided does not match the ID of the screener object provided. :param screener_id: str, the ID of the screener being edited. Must be the same ID as contained in the screener parameter. :param screener: Screener, the object representing the screener to be edited. This object should contain any desired changes to the screener. :return: Screener, an object containing information about the updated screener. """ assert screener_id == screener.id request_headers = {'Content-Type': 'application/json;charset=utf-8'} return GsSession.current.sync.put( '/data/screeners/{id}'.format(id=screener_id), screener, request_headers=request_headers, cls=Screener ) @classmethod def publish_to_screener(cls, screener_id: str, data: Dict[str, List[Dict[str, Any]]]) -> List[Dict[str, Any]]: """ Permanently publishes additional data specified in screener_rows to the existing screener with ID screener_id. Although this function returns a dictionary of all published data, only data formats consistent with the screener schema will be persisted. User must be a part of the PlotScreenerAdmins group to perform this action. :param screener_id: str, the ID of the screener to which data will be published. :param data:, dict list, a list of dictionaries where each dictionary represents a row of data to publish to the screener. :return: dict list, a list of dictionaries, where each dictionary represents a row of data to be published to the screener. """ request_headers = {'Content-Type': 'application/json;charset=utf-8'} return GsSession.current.sync.post( '/data/screeners/{id}/publish'.format(id=screener_id), data, request_headers=request_headers )['data'] @classmethod def clear_screener(cls, screener_id: str) -> Dict[str, Any]: """ Permanently clears all data from a screener with the corresponding screener_id, but does not delete the screener. This function leaves the schema of the screener unchanged. User must be a part of the PlotScreenerAdmins group to perform this action. :param screener_id: str, the ID of the screener whose data is being cleared. :return: dict, a dictionary with information about if the screener was successfully cleared. """ request_headers = {'Content-Type': 'application/json;charset=utf-8'} return GsSession.current.sync.post( '/data/screeners/{id}/clear'.format(id=screener_id), {}, request_headers=request_headers ) @classmethod def delete_screener(cls, screener_id: str) -> None: """ Permanently deletes the screener associated with ID screener_id, as well as all of its data. User must be a part of the PlotScreenerAdmins group to perform this action. :param screener_id: str, the ID of the screener being deleted. :return: None """ return GsSession.current.sync.delete('/data/screeners/{id}'.format(id=screener_id)) ================================================ FILE: gs_quant/api/gs/carbon.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import logging from enum import Enum from typing import Dict, List from urllib.parse import urlencode from gs_quant.common import Currency from gs_quant.session import GsSession _logger = logging.getLogger(__name__) class CarbonCard(Enum): """ Carbon Cards """ COVERAGE = 'coverage' SBTI_AND_NET_ZERO_TARGETS = 'sbtiAndNetZeroTargets' EMISSIONS = 'emissions' ALLOCATIONS = 'allocations' ATTRIBUTION = 'attribution' # highestEmitters should not be accessible through API def __str__(self): return self.value class CarbonCoverageCategory(Enum): """ Carbon Coverage Category """ WEIGHTS = 'weights' NUMBER_OF_COMPANIES = 'numberOfCompanies' def __str__(self): return self.value class CarbonTargetCoverageCategory(Enum): """ Carbon Targets Coverage Category """ CAPITAL_ALLOCATED = 'capitalAllocated' PORTFOLIO_EMISSIONS = 'portfolioEmissions' def __str__(self): return self.value class CarbonScope(Enum): """ Carbon Scopes """ TOTAL_GHG = 'totalGHG' SCOPE1 = 'scope1' SCOPE2 = 'scope2' def __str__(self): return self.value class CarbonEmissionsAllocationCategory(Enum): """ Carbon Emissions Allocation Category """ GICS_SECTOR = 'gicsSector' GICS_INDUSTRY = 'gicsIndustry' REGION = 'region' def __str__(self): return self.value class CarbonEmissionsIntensityType(Enum): """ Carbon Emissions Intensity Type """ EI_ENTERPRISE_VALUE = 'emissionsIntensityEnterpriseValue' EI_REVENUE = 'emissionsIntensityRevenue' EI_MARKETCAP = 'emissionsIntensityMarketCap' def __str__(self): return self.value class CarbonEntityType(Enum): """ Carbon analytics at portfolio or benchmark """ PORTFOLIO = 'portfolio' BENCHMARK = 'benchmark' def __str__(self): return self.value class CarbonAnalyticsView(Enum): """ Carbon analytics at Long or Short component of """ LONG = 'Long' SHORT = 'Short' def __str__(self): return self.value class GsCarbonApi: """GS Carbon API client implementation""" @classmethod def get_carbon_analytics( cls, entity_id: str, benchmark_id: str = None, reporting_year: str = 'Latest', currency: Currency = None, include_estimates: bool = False, use_historical_data: bool = False, normalize_emissions: bool = False, cards: List[CarbonCard] = [], analytics_view: CarbonAnalyticsView = CarbonAnalyticsView.LONG, ) -> Dict: url = f'/carbon/{entity_id}?' url += urlencode( dict( filter( lambda item: item[1] is not None, dict( benchmark=benchmark_id, reportingYear=reporting_year, currency=currency.value if currency is not None else None, includeEstimates=str(include_estimates).lower(), useHistoricalData=str(use_historical_data).lower(), normalizeEmissions=str(normalize_emissions).lower(), card=[c for c in CarbonCard] if len(cards) == 0 else cards, analyticsView=analytics_view.value, ).items(), ) ), True, ) # TODO: Add scope as API parameter return GsSession.current.sync.get(url) ================================================ FILE: gs_quant/api/gs/content.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from base64 import b64decode from collections import OrderedDict from enum import Enum from typing import List, Tuple from urllib.parse import quote from gs_quant.session import GsSession from gs_quant.target.content import ContentResponse, GetManyContentsResponse class OrderBy(Enum): """ Content ordering """ ASC = 'asc' DESC = 'desc' def __str__(self): return str(self.value) class GsContentApi: """GS Content API client implementation""" @classmethod def get_contents( cls, channels: set = None, asset_ids: set = None, author_ids: set = None, tags: set = None, offset: int = 0, limit: int = 10, order_by: dict = {'direction': OrderBy.DESC, 'field': 'createdTime'}, ) -> List[ContentResponse]: """ Get contents for given parameters :param channels: Channels :param asset_ids: Marquee Asset Ids :param author_ids: Marquee Author Guids :param tags: Tags :param limit: Limit (number of contents to return) :param offset: Offset (offset of contents) :param order_by: OrderBy (dict for specifying how to sort contents) :return: A list of ContentResponse objects **Examples** >>> from gs_quant.api.gs.content import GsContentApi >>> from gs_quant.session import GsSession >>> >>> GsSession.use() >>> contents = GsContentApi.get_contents(channels=['G10']) """ if limit and limit > 1000: raise ValueError('Limit is too large. Limit must be <= 1000.') if offset and (offset < 0 or offset >= limit): raise ValueError('Invalid offset. Offset must be >= 0 and < limit') parameters_dict = cls._build_parameters_dict( channel=channels, asset_id=asset_ids, author_id=author_ids, tag=tags, offset=[offset] if offset else None, limit=[limit] if limit else None, order_by=[order_by] if order_by else None, ) query_string = '' if not parameters_dict else cls._build_query_string(parameters_dict) contents = GsSession.current.sync.get(f'/content{query_string}', cls=GetManyContentsResponse) return contents.data @staticmethod def get_text(contents: List[ContentResponse]) -> List[Tuple[str, str]]: """ Get text for contents :param contents: List of ContentResponse objects :return: A list of tuples representing (, ) **Examples** >>> from gs_quant.api.gs.content import GsContentApi >>> from gs_quant.session import GsSession >>> >>> GsSession.use() >>> contents = GsContentApi.get_contents(channels=['G10']) >>> text = gs_content_api.get_text(contents) """ return [(content.id, b64decode(content.content.body)) for content in contents] @classmethod def _build_parameters_dict(cls, **kwargs) -> dict: """ Builds dict of valid parameters to their value, filtering out any parameters for which "None" is the value. """ parameters = {} for key, value in kwargs.items(): if value: parameters.setdefault(key, []).extend(sorted(value)) return OrderedDict(parameters) @classmethod def _build_query_string(cls, parameters: dict) -> str: """ Builds a query string accepted by the Content API Example: In: { 'channel': ['G10', 'EM'], 'limit': 10 } Out: ?channel=G10&channel=EM&limit=10 """ query_string = '?' # Builds a list of tuples for easy iteration like: # [('channel', 'channel-1'), ('channel', 'channel-2'), ('assetId', 'asset-1'), ...] parameter_tuples = [ (parameter_name, parameter_value) for parameter_name, parameter_values in parameters.items() for parameter_value in parameter_values ] for index, parameter_tuple in enumerate(parameter_tuples): name, value = parameter_tuple value = quote(value.encode()) if isinstance(value, str) else value if name == 'order_by': value = cls._convert_order_by(value) query_string += f'{name}={value}' if index == 0 else f'&{name}={value}' return query_string @classmethod def _convert_order_by(cls, order_by: dict) -> str: """ Converts an orderByDirection and orderByField to an acceptable query parameter format expected by the Content API """ direction = order_by['direction'] if direction == OrderBy.DESC: order_by_parameter = '>' else: order_by_parameter = '<' return order_by_parameter + order_by['field'] ================================================ FILE: gs_quant/api/gs/countries.py ================================================ """ Copyright 2020 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from typing import Tuple from gs_quant.session import GsSession from gs_quant.target.countries import Country, Subdivision class GsCountryApi: """GS Country API client implementation""" @classmethod def get_many_countries(cls, limit: int = 100) -> Tuple[Country, ...]: return GsSession.current.sync.get('/countries?limit={limit}'.format(limit=limit), cls=Country)['results'] @classmethod async def get_many_countries_async(cls, limit: int = 100) -> Tuple[Country, ...]: response = await GsSession.current.async_.get('/countries', payload={"limit": limit}, cls=Country) return response.get("results") @classmethod def get_country(cls, country_id: str) -> Country: return GsSession.current.sync.get('/countries/{id}'.format(id=country_id), cls=Country) @classmethod async def get_country_async(cls, country_id: str) -> Country: response = await GsSession.current.async_.get(f'/countries/{country_id}', cls=Country) return response @classmethod def create_country(cls, country: Country) -> Country: return GsSession.current.sync.post('/countries', country, cls=Country) @classmethod def update_country(cls, country: Country): return GsSession.current.sync.put('/countries/{id}'.format(id=country.id), country, cls=Country) @classmethod def delete_country(cls, country_id: str) -> dict: return GsSession.current.sync.delete('/countries/{id}'.format(id=country_id)) @classmethod def get_many_subdivisions(cls, limit: int = 100) -> Tuple[Subdivision, ...]: return GsSession.current.sync.get('/countries/subdivisions?limit={limit}'.format(limit=limit), cls=Subdivision)[ 'results' ] @classmethod def get_subdivision(cls, subdivision_id: str) -> Subdivision: return GsSession.current.sync.get('/countries/subdivisions/{id}'.format(id=subdivision_id), cls=Subdivision) @classmethod def create_subdivision(cls, subdivision: Subdivision) -> Subdivision: return GsSession.current.sync.post('/countries/subdivisions', subdivision, cls=Subdivision) @classmethod def update_subdivision(cls, subdivision: Subdivision): return GsSession.current.sync.put( '/countries/subdivisions/{id}'.format(id=subdivision.id), subdivision, cls=Subdivision ) @classmethod def delete_subdivision(cls, subdivision_id: str) -> dict: return GsSession.current.sync.delete('/countries/subdivisions/{id}'.format(id=subdivision_id)) ================================================ FILE: gs_quant/api/gs/data.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import asyncio import datetime as dt import json import logging import time from copy import copy, deepcopy from enum import Enum from itertools import chain from typing import Iterable, List, Optional, Tuple, Union, Dict import cachetools import pandas as pd from cachetools import TTLCache from dateutil import parser from pydash import get from gs_quant.api.data import DataApi from gs_quant.base import Base from gs_quant.common import MarketDataVendor, PricingLocation, Format 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.coordinates import MDAPIDataBatchResponse, MDAPIDataQuery, MDAPIDataQueryResponse, MDAPIQueryField from gs_quant.target.data import DataQuery, DataQueryResponse, DataSetCatalogEntry, DataSetEntity, DataSetFieldEntity from .assets import GsIdType from ..api_cache import ApiRequestCache from ...target.assets import EntityQuery, FieldFilterMap _logger = logging.getLogger(__name__) _REQUEST_HEADERS = "request_headers" class QueryType(Enum): IMPLIED_VOLATILITY = "Implied Volatility" IMPLIED_VOLATILITY_BY_EXPIRATION = "Implied Volatility By Expiration" IMPLIED_CORRELATION = "Implied Correlation" REALIZED_CORRELATION = "Realized Correlation" AVERAGE_IMPLIED_VOLATILITY = "Average Implied Volatility" AVERAGE_IMPLIED_VARIANCE = "Average Implied Variance" AVERAGE_REALIZED_VOLATILITY = "Average Realized Volatility" SWAP_RATE = "Swap Rate" SWAP_ANNUITY = "Swap Annuity" SWAPTION_PREMIUM = "Swaption Premium" SWAPTION_ANNUITY = "Swaption Annuity" BASIS_SWAP_RATE = "Basis Swap Rate" XCCY_SWAP_SPREAD = "Xccy Swap Spread" SWAPTION_VOL = "Swaption Vol" MIDCURVE_VOL = "Midcurve Vol" CAP_FLOOR_VOL = "Cap Floor Vol" SPREAD_OPTION_VOL = "Spread Option Vol" INFLATION_SWAP_RATE = "Inflation Swap Rate" FORWARD = "Forward" PRICE = "Price" ATM_FWD_RATE = "Atm Fwd Rate" BASIS = "Basis" VAR_SWAP = "Var Swap" MIDCURVE_PREMIUM = "Midcurve Premium" MIDCURVE_ANNUITY = "Midcurve Annuity" MIDCURVE_ATM_FWD_RATE = "Midcurve Atm Fwd Rate" CAP_FLOOR_ATM_FWD_RATE = "Cap Floor Atm Fwd Rate" SPREAD_OPTION_ATM_FWD_RATE = "Spread Option Atm Fwd Rate" FORECAST = "Forecast" IMPLIED_VOLATILITY_BY_DELTA_STRIKE = "Implied Volatility By Delta Strike" FUNDAMENTAL_METRIC = "Fundamental Metric" POLICY_RATE_EXPECTATION = "Policy Rate Expectation" CENTRAL_BANK_SWAP_RATE = "Central Bank Swap Rate" FORWARD_PRICE = "Forward Price" FAIR_PRICE = "Fair Price" PNL = "Pnl" SPOT = "Spot" AUM = "Aum" RATE = "Rate" ES_NUMERIC_SCORE = "Es Numeric Score" ES_NUMERIC_PERCENTILE = "Es Numeric Percentile" ES_POLICY_SCORE = "Es Policy Score" ES_POLICY_PERCENTILE = "Es Policy Percentile" ES_SCORE = "Es Score" ES_PERCENTILE = "Es Percentile" ES_PRODUCT_IMPACT_SCORE = "Es Product Impact Score" ES_PRODUCT_IMPACT_PERCENTILE = "Es Product Impact Percentile" G_SCORE = "G Score" G_PERCENTILE = "G Percentile" ES_MOMENTUM_SCORE = "Es Momentum Score" ES_MOMENTUM_PERCENTILE = "Es Momentum Percentile" G_REGIONAL_SCORE = "G Regional Score" G_REGIONAL_PERCENTILE = "G Regional Percentile" ES_DISCLOSURE_PERCENTAGE = "Es Disclosure Percentage" CONTROVERSY_SCORE = "Controversy Score" CONTROVERSY_PERCENTILE = "Controversy Percentile" RATING = "Rating" CONVICTION_LIST = "Conviction List" FAIR_VALUE = "Fair Value" FX_FORECAST = "Fx Forecast" GROWTH_SCORE = "Growth Score" FINANCIAL_RETURNS_SCORE = "Financial Returns Score" MULTIPLE_SCORE = "Multiple Score" INTEGRATED_SCORE = "Integrated Score" COMMODITY_FORECAST = "Commodity Forecast" FORECAST_VALUE = "Forecast Value" FORWARD_POINT = "Forward Point" FCI = "Fci" LONG_RATES_CONTRIBUTION = "Long Rates Contribution" SHORT_RATES_CONTRIBUTION = "Short Rates Contribution" CORPORATE_SPREAD_CONTRIBUTION = "Corporate Spread Contribution" SOVEREIGN_SPREAD_CONTRIBUTION = "Sovereign Spread Contribution" EQUITIES_CONTRIBUTION = "Equities Contribution" REAL_LONG_RATES_CONTRIBUTION = "Real Long Rates Contribution" REAL_SHORT_RATES_CONTRIBUTION = "Real Short Rates Contribution" REAL_FCI = "Real Fci" DWI_CONTRIBUTION = "Dwi Contribution" REAL_TWI_CONTRIBUTION = "Real Twi Contribution" TWI_CONTRIBUTION = "Twi Contribution" COVARIANCE = "Covariance" FACTOR_EXPOSURE = "Factor Exposure" FACTOR_RETURN = "Factor Return" HISTORICAL_BETA = "Historical Beta" FACTOR_PNL = "Factor Pnl" FACTOR_PROPORTION_OF_RISK = "Factor Proportion Of Risk" DAILY_RISK = "Daily Risk" ANNUAL_RISK = "Annual Risk" VOLATILITY = "Volatility" CORRELATION = "Correlation" OIS_XCCY = "Ois Xccy" OIS_XCCY_EX_SPIKE = "Ois Xccy Ex Spike" USD_OIS = "Usd Ois" NON_USD_OIS = "Non Usd Ois" SETTLEMENT_PRICE = "Settlement Price" THEMATIC_EXPOSURE = "Thematic Exposure" THEMATIC_BETA = "Thematic Beta" THEMATIC_MODEL_BETA = "Thematic Model Beta" CDS_SPREAD_100 = "Spread At100" CDS_SPREAD_250 = "Spread At250" CDS_SPREAD_500 = "Spread At500" STRIKE_VOL = "Strike Vol" OPTION_PREMIUM = "Option Premium" ABSOLUTE_STRIKE = "Absolute Strike" RETAIL_PCT_SHARES = 'impliedRetailPctShares' RETAIL_PCT_NOTIONAL = 'impliedRetailPctNotional' RETAIL_SHARES = 'impliedRetailShares' RETAIL_NOTIONAL = 'impliedRetailNotional' SHARES = 'shares' NOTIONAL = 'notional' RETAIL_BUY_NOTIONAL = 'impliedRetailBuyNotional' RETAIL_BUY_PCT_NOTIONAL = 'impliedRetailBuyPctNotional' RETAIL_BUY_PCT_SHARES = 'impliedRetailBuyPctShares' RETAIL_BUY_SHARES = 'impliedRetailBuyShares' RETAIL_SELL_NOTIONAL = 'impliedRetailSellNotional' RETAIL_SELL_PCT_NOTIONAL = 'impliedRetailSellPctNotional' RETAIL_SELL_PCT_SHARES = 'impliedRetailSellPctShares' RETAIL_SELL_SHARES = 'impliedRetailSellShares' FWD_POINTS = 'Fwd Points' S3_AGGREGATE_DATA = 'value' HIT_RATE = 'Hit Rate' MAX_DRAWDOWN = "Max Drawdown" STANDARD_DEVIATION = "Standard Deviation" DOWNSIDE_RISK = "Downside Risk" KURTOSIS = 'Kurtosis' SKEWNESS = 'Skewness' REALIZED_VAR = 'Realized VaR' TRACKING_ERROR = 'Tracking Error' TRACKING_ERROR_BEAR = 'Tracking Error Bear' TRACKING_ERROR_BULL = 'Tracking Error Bull' SHARPE_RATIO = 'Sharpe Ratio' CALMAR_RATIO = 'Calmar Ratio' SORTINO_RATIO = 'Sortino Ratio' INFORMATION_RATIO = 'Information Ratio' MODIGLIANI_RATIO = 'Modigliani Ratio' TREYNOR_RATIO = 'Treynor Ratio' ALPHA = 'Alpha' BETA = 'Beta' R_SQUARED = 'R Squared' CAPTURE_RATIO = 'Capture Ratio' class GsDataApi(DataApi): __definitions = {} __asset_coordinates_cache = TTLCache(10000, 86400) _api_request_cache: ApiRequestCache = None DEFAULT_SCROLL = '30s' # DataApi interface @classmethod def set_api_request_cache(cls, cache: ApiRequestCache): cls._api_request_cache = cache @classmethod def _construct_cache_key(cls, url, **kwargs) -> tuple: def fallback_encoder(v) -> str: if isinstance(v, dt.date): return v.isoformat() def serialize_value(v): if any(isinstance(v, class_) for class_ in {MDAPIDataQuery, DataQuery}): return v.to_json(sort_keys=True, default=fallback_encoder) encoded_v = fallback_encoder(v) return encoded_v or v json_kwargs = {k: serialize_value(v) for k, v in kwargs.items() if k not in {_REQUEST_HEADERS}} cache_key = (url, 'POST', json_kwargs) return cache_key @classmethod def _check_cache(cls, url, **kwargs): session = cls.get_session() cached_val, cache_key = None, None if cls._api_request_cache: cache_key = cls._construct_cache_key(url, **kwargs) cached_val = cls._api_request_cache.get(session, cache_key) return cached_val, cache_key, session @classmethod def _post_with_cache_check(cls, url, validator=lambda x: x, domain=None, **kwargs): result, cache_key, session = cls._check_cache(url=url, **kwargs) if result is None: result = validator(session.sync.post(url, domain=domain, **kwargs)) if cls._api_request_cache: cls._api_request_cache.put(session, cache_key, result) return result @classmethod def _get_with_cache_check(cls, url, validator=lambda x: x, domain=None, **kwargs): result, cache_key, session = cls._check_cache(url, **kwargs) if result is None: result = validator(session.sync.get(url, domain=domain, **kwargs)) if cls._api_request_cache: cls._api_request_cache.put(session, cache_key, result) return result @classmethod async def _get_with_cache_check_async(cls, url, validator=lambda x: x, domain=None, **kwargs): result, cache_key, session = cls._check_cache(url, **kwargs) if result is None: result = await session.async_.get(url, domain=domain, **kwargs) result = validator(result) if cls._api_request_cache: cls._api_request_cache.put(session, cache_key, result) return result @classmethod async def _post_with_cache_check_async(cls, url, validator=lambda x: x, domain=None, **kwargs): result, cache_key, session = cls._check_cache(url, **kwargs) if result is None: result = await session.async_.post(url, domain=domain, **kwargs) result = validator(result) if cls._api_request_cache: cls._api_request_cache.put(session, cache_key, result) return result @classmethod def query_data( cls, query: Union[DataQuery, MDAPIDataQuery], dataset_id: str = None, asset_id_type: Union[GsIdType, str] = None ) -> Union[MDAPIDataBatchResponse, DataQueryResponse, tuple, list]: if isinstance(query, MDAPIDataQuery) and query.market_data_coordinates: # Don't use MDAPIDataBatchResponse for now - it doesn't handle quoting style correctly results: Union[MDAPIDataBatchResponse, dict] = cls.execute_query('coordinates', query) if isinstance(results, dict): return results.get('responses', ()) else: return results.responses if results.responses is not None else () response: Union[DataQueryResponse, dict] = cls.execute_query(dataset_id, query) return cls.get_results(dataset_id, response, query) @classmethod async def query_data_async( cls, query: Union[DataQuery, MDAPIDataQuery], dataset_id: str = None ) -> Union[MDAPIDataBatchResponse, DataQueryResponse, tuple, list]: if isinstance(query, MDAPIDataQuery) and query.market_data_coordinates: # Don't use MDAPIDataBatchResponse for now - it doesn't handle quoting style correctly results: Union[MDAPIDataBatchResponse, dict] = await cls.execute_query_async('coordinates', query) if isinstance(results, dict): return results.get('responses', ()) else: return results.responses if results.responses is not None else () response: Union[DataQueryResponse, dict] = await cls.execute_query_async(dataset_id, query) results = await cls.get_results_async(dataset_id, response, query) return results @classmethod def execute_query(cls, dataset_id: str, query: Union[DataQuery, MDAPIDataQuery]): kwargs = {'payload': query} if getattr(query, 'format', None) in (Format.MessagePack, 'MessagePack'): kwargs[_REQUEST_HEADERS] = {'Accept': 'application/msgpack'} domain = cls._check_data_on_cloud(dataset_id) return cls._post_with_cache_check('/data/{}/query'.format(dataset_id), domain=domain, **kwargs) @classmethod async def execute_query_async(cls, dataset_id: str, query: Union[DataQuery, MDAPIDataQuery]): kwargs = {'payload': query} if getattr(query, 'format', None) in (Format.MessagePack, 'MessagePack'): kwargs[_REQUEST_HEADERS] = {'Accept': 'application/msgpack'} domain = await cls._check_data_on_cloud_async(dataset_id) result = await cls._post_with_cache_check_async('/data/{}/query'.format(dataset_id), domain=domain, **kwargs) return result @classmethod def _check_data_on_cloud(cls, dataset_id: str): session = cls.get_session() if session.redirect_to_mds and dataset_id != 'coordinates': dataset_data = cls._get_with_cache_check('/data/datasets/{}'.format(dataset_id)) database_id_exists = get(dataset_data, 'parameters.databaseId') if database_id_exists: return cls.get_session()._get_mds_domain() return None @classmethod async def _check_data_on_cloud_async(cls, dataset_id: str): session = cls.get_session() if session.redirect_to_mds and dataset_id != 'coordinates': dataset_data = await cls._get_with_cache_check(f'/data/datasets/{dataset_id}') database_id_exists = get(dataset_data, 'parameters.databaseId') if database_id_exists: return cls.get_session()._get_mds_domain() return None @staticmethod def _get_results(response: Union[DataQueryResponse, dict]): if isinstance(response, dict): total_pages = response.get('totalPages') results = response.get('data', []) if 'groups' in response: group_by = set() for group in response['groups']: group_by.update(group['context'].keys()) for row in group['data']: row.update(group['context']) results += group['data'] results = (results, list(group_by)) else: total_pages = response.total_pages if response.total_pages is not None else 0 results = response.data if response.data is not None else () return results, total_pages @staticmethod def get_results( dataset_id: str, response: Union[DataQueryResponse, dict], query: DataQuery ) -> Union[list, Tuple[list, list]]: results, total_pages = GsDataApi._get_results(response) if total_pages: if query.page is None: query.page = total_pages - 1 results = results + GsDataApi.get_results(dataset_id, GsDataApi.execute_query(dataset_id, query), query) elif query.page - 1 > 0: query.page -= 1 results = results + GsDataApi.get_results(dataset_id, GsDataApi.execute_query(dataset_id, query), query) else: return results return results @staticmethod async def get_results_async( dataset_id: str, response: Union[DataQueryResponse, dict], query: DataQuery ) -> Union[list, Tuple[list, list]]: results, total_pages = GsDataApi._get_results(response) if total_pages and total_pages > 1: futures = [] for page in range(1, total_pages): query = deepcopy(query) query.page = page futures.append(GsDataApi.execute_query_async(dataset_id, query)) all_responses = await asyncio.gather(*futures, return_exceptions=True) for response_crt in all_responses: results += GsDataApi._get_results(response_crt)[0] return results @classmethod def last_data( cls, query: Union[DataQuery, MDAPIDataQuery], dataset_id: str = None, timeout: int = None ) -> Union[list, tuple]: kwargs = {} if timeout is not None: kwargs['timeout'] = timeout if getattr(query, 'marketDataCoordinates', None): result = cls._post_with_cache_check('/data/coordinates/query/last', payload=query, **kwargs) return result.get('responses', ()) else: domain = cls._check_data_on_cloud(dataset_id) result = cls._post_with_cache_check( '/data/{}/last/query'.format(dataset_id), payload=query, domain=domain, **kwargs ) return result.get('data', ()) @classmethod def symbol_dimensions(cls, dataset_id: str) -> tuple: definition = cls.get_definition(dataset_id) return definition.dimensions.symbolDimensions @classmethod def time_field(cls, dataset_id: str) -> str: definition = cls.get_definition(dataset_id) return definition.dimensions.timeField # GS-specific functionality @classmethod def _build_params( cls, scroll: str, scroll_id: Optional[str], limit: int, offset: int, fields: List[str], include_history: bool, **kwargs, ) -> dict: params = {'limit': limit or 4000, 'scroll': scroll} if scroll_id: params['scrollId'] = scroll_id if offset: params['offset'] = offset if fields: params['fields'] = fields if include_history: params['includeHistory'] = 'true' params = {**params, **kwargs} return params @classmethod def get_coverage( cls, dataset_id: str, scroll: str = DEFAULT_SCROLL, scroll_id: Optional[str] = None, limit: int = None, offset: int = None, fields: List[str] = None, include_history: bool = False, **kwargs, ) -> List[dict]: session = cls.get_session() params = cls._build_params(scroll, scroll_id, limit, offset, fields, include_history, **kwargs) body = session.sync.get(f'/data/{dataset_id}/coverage', payload=params) results = scroll_results = body['results'] total_results = body['totalResults'] while len(scroll_results) and len(results) < total_results: scroll_id = body.get('scrollId') if scroll_id is None: break params['scrollId'] = scroll_id body = session.sync.get(f'/data/{dataset_id}/coverage', payload=params) scroll_results = body['results'] results += scroll_results return results @classmethod async def get_coverage_async( cls, dataset_id: str, scroll: str = DEFAULT_SCROLL, scroll_id: Optional[str] = None, limit: int = None, offset: int = None, fields: List[str] = None, include_history: bool = False, **kwargs, ) -> List[dict]: session = cls.get_session() params = cls._build_params(scroll, scroll_id, limit, offset, fields, include_history, **kwargs) body = await session.async_.get(f'/data/{dataset_id}/coverage', payload=params) results = scroll_results = body['results'] total_results = body['totalResults'] while len(scroll_results) and len(results) < total_results: params['scrollId'] = body['scrollId'] body = await session.async_.get(f'/data/{dataset_id}/coverage', payload=params) scroll_results = body['results'] if scroll_results: results += scroll_results return results @classmethod def create(cls, definition: Union[DataSetEntity, dict]) -> DataSetEntity: result = cls.get_session().sync.post('/data/datasets', payload=definition) return result @classmethod def get_catalog_url(cls, dataset_id: str) -> DataSetEntity: url = cls.get_session()._build_url( domain=cls.get_session()._get_web_domain(), path=f'/s/data-services/datasets/{dataset_id}', include_version=False, ) return url @classmethod def delete_dataset(cls, dataset_id: str) -> dict: result = cls.get_session().sync.delete(f'/data/datasets/{dataset_id}') return result @classmethod def undelete_dataset(cls, dataset_id: str) -> dict: result = cls.get_session().sync.put(f'/data/datasets/{dataset_id}/undelete') return result @classmethod def update_definition(cls, dataset_id: str, definition: Union[DataSetEntity, dict]) -> DataSetEntity: result = cls.get_session().sync.put( '/data/datasets/{}'.format(dataset_id), payload=definition, cls=DataSetEntity ) return result @classmethod def upload_data(cls, dataset_id: str, data: Union[pd.DataFrame, list, tuple]) -> dict: if isinstance(data, pd.DataFrame): # We require the Dataframe to return a list in the 'records' format: # https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_json.html data = data.to_json(orient='records') # Don't use msgpack for MDS session = cls.get_session() headers = None if 'us-east' in session.domain else {'Content-Type': 'application/x-msgpack'} result = session.sync.post('/data/{}'.format(dataset_id), payload=data, request_headers=headers) return result @classmethod def delete_data(cls, dataset_id: str, delete_query: Dict) -> Dict: """ Delete data from dataset. You must have admin access to the dataset to delete data. All data deleted is not recoverable. """ return cls.get_session().sync.delete(f'/data/{dataset_id}', payload=delete_query, use_body=True) @classmethod def get_definition(cls, dataset_id: str) -> DataSetEntity: definition = cls.__definitions.get(dataset_id) if not definition: definition = cls.get_session().sync.get('/data/datasets/{}'.format(dataset_id), cls=DataSetEntity) if not definition: raise MqValueError('Unknown dataset {}'.format(dataset_id)) cls.__definitions[dataset_id] = definition return definition @classmethod def get_many_definitions( cls, limit: int = 100, offset: int = None, scroll: str = DEFAULT_SCROLL, scroll_id: Optional[str] = None, ) -> Tuple[DataSetEntity, ...]: params = dict( filter( lambda item: item[1] is not None, dict(limit=limit, offset=offset, scroll=scroll, scrollId=scroll_id, enablePagination='true').items(), ) ) body = cls.get_session().sync.get('/data/datasets', payload=params, cls=DataSetEntity) results = scroll_results = body['results'] total_results = body['totalResults'] while len(scroll_results) and len(results) < total_results: params['scrollId'] = body['scrollId'] body = cls.get_session().sync.get('/data/datasets', payload=params, cls=DataSetEntity) scroll_results = body['results'] results = results + scroll_results return results @classmethod def get_catalog( cls, dataset_ids: List[str] = None, limit: int = 100, offset: int = None, scroll: str = DEFAULT_SCROLL, scroll_id: Optional[str] = None, ) -> Tuple[DataSetCatalogEntry, ...]: query = f'dataSetId={"&dataSetId=".join(dataset_ids)}' if dataset_ids else '' gs_session = cls.get_session() if len(query): return gs_session.sync.get(f'/data/catalog?{query}', cls=DataSetCatalogEntry)['results'] else: params = dict( filter( lambda item: item[1] is not None, dict( limit=limit, offset=offset, scroll=scroll, scrollId=scroll_id, enablePagination='true' ).items(), ) ) body = gs_session.sync.get('/data/catalog', payload=params, cls=DataSetEntity) results = scroll_results = body['results'] total_results = body['totalResults'] while len(scroll_results) and len(results) < total_results: params['scrollId'] = body['scrollId'] body = gs_session.sync.get('/data/catalog', payload=params, cls=DataSetEntity) scroll_results = body['results'] results = results + scroll_results return results @classmethod @cachetools.cached(__asset_coordinates_cache) def get_many_coordinates( cls, mkt_type: str = None, mkt_asset: str = None, mkt_class: str = None, mkt_point: Tuple[str, ...] = (), *, limit: int = 100, return_type: type = str, ) -> Union[Tuple[str, ...], Tuple[MarketDataCoordinate, ...]]: where = FieldFilterMap( mkt_type=mkt_type.upper() if mkt_type is not None else None, mkt_asset=mkt_asset.upper() if mkt_asset is not None else None, mkt_class=mkt_class.upper() if mkt_class is not None else None, ) for index, point in enumerate(mkt_point): setattr(where, 'mkt_point' + str(index + 1), point.upper()) query = EntityQuery(where=where, limit=limit) results = cls._post_with_cache_check('/data/mdapi/query', payload=query)['results'] if return_type is str: return tuple(coordinate['name'] for coordinate in results) elif return_type is MarketDataCoordinate: return tuple( MarketDataCoordinate( mkt_type=coordinate['dimensions']['mktType'], mkt_asset=coordinate['dimensions']['mktAsset'], mkt_class=coordinate['dimensions']['mktClass'], mkt_point=tuple(coordinate['dimensions']['mktPoint'].values()), mkt_quoting_style=coordinate['dimensions']['mktQuotingStyle'], ) for coordinate in results ) else: raise NotImplementedError('Unsupported return type') @classmethod def _to_zulu(cls, d): return d.strftime('%Y-%m-%dT%H:%M:%SZ') @classmethod def get_mxapi_curve_measure( cls, curve_type=None, curve_asset=None, curve_point=None, curve_tags=None, measure=None, start_time=None, end_time=None, request_id=None, close_location=None, real_time=None, ) -> pd.DataFrame: real_time = real_time or isinstance(start_time, dt.datetime) if not start_time: if real_time: start_time = DataContext.current.start_time else: start_time = DataContext.current.start_date if not end_time: if real_time: end_time = DataContext.current.end_time else: end_time = DataContext.current.end_date if not real_time and not close_location: close_location = 'NYC' if real_time and not isinstance(end_time, dt.date): raise ValueError("Start and end need to be either both date or both time") if real_time: request_dict = { 'type': 'MxAPI Measure Request', 'modelType': curve_type, 'modelAsset': curve_asset, 'point': curve_point, 'tags': curve_tags, 'startTime': cls._to_zulu(start_time), 'endTime': cls._to_zulu(end_time), 'measureName': measure, } else: request_dict = { 'type': 'MxAPI Measure Request EOD', 'modelType': curve_type, 'modelAsset': curve_asset, 'point': curve_point, 'tags': curve_tags, 'startDate': start_time.isoformat(), 'endDate': end_time.isoformat(), 'close': close_location, 'measureName': measure, } url = '/mxapi/mq/measure' if real_time else '/mxapi/mq/measure/eod' start = time.perf_counter() try: body = cls._post_with_cache_check(url, payload=request_dict) except Exception as e: log_warning(request_id, _logger, f'Mxapi measure query {request_dict} failed due to {e}') raise e log_debug( request_id, _logger, 'MxAPI measure query (%s) with payload (%s) ran in %.3f ms', body.get('requestId'), request_dict, (time.perf_counter() - start) * 1000, ) if real_time: values = body['measures'] valuation_times = body['measureTimes'] timestamps = [parser.parse(s) for s in valuation_times] column_name = body['measureName'] d = {column_name: values, 'timeStamp': timestamps} df = MarketDataResponseFrame(pd.DataFrame(data=d)) df = df.set_index('timeStamp') return df else: values = body['measures'] valuation_date_strings = body['measureDates'] valuation_dates = [dt.date.fromisoformat(s) for s in valuation_date_strings] column_name = body['measureName'] d = {column_name: values, 'date': valuation_dates} df = MarketDataResponseFrame(pd.DataFrame(data=d)) df = df.set_index('date') return df @classmethod def get_mxapi_vector_measure( cls, curve_type=None, curve_asset=None, curve_point=None, curve_tags=None, vector_measure=None, as_of_time=None, request_id=None, close_location=None, ) -> pd.DataFrame: if not vector_measure: raise ValueError("Vector measure must be specified.") if not as_of_time: raise ValueError("As-of date or time must be specified.") real_time = isinstance(as_of_time, dt.datetime) if not real_time and not isinstance(as_of_time, dt.date): raise ValueError("As-of date or time must be specified.") if not real_time and not close_location: close_location = 'NYC' if real_time: request_dict = { 'type': 'MxAPI Curve Request', 'modelType': curve_type, 'modelAsset': curve_asset, 'point': curve_point, 'tags': curve_tags, 'asOfTime': cls._to_zulu(as_of_time), 'curveName': vector_measure, } else: request_dict = { 'type': 'MxAPI Curve Request EOD', 'modelType': curve_type, 'modelAsset': curve_asset, 'point': curve_point, 'tags': curve_tags, 'asOfDate': as_of_time.isoformat(), 'close': close_location, 'curveName': vector_measure, } url = '/mxapi/mq/curve' if real_time else '/mxapi/mq/curve/eod' start = time.perf_counter() try: body = cls._post_with_cache_check(url, payload=request_dict) except Exception as e: log_warning(request_id, _logger, f'Mxapi curve query {request_dict} failed due to {e}') raise e log_debug( request_id, _logger, 'MxAPI curve query (%s) with payload (%s) ran in %.3f ms', body.get('requestId'), request_dict, (time.perf_counter() - start) * 1000, ) values = body['curve'] value_col_name = body['curveName'] knots = body['knots'] column_name = body['knotType'] if len(values) == 0 and len(body['errMsg']) > 0: raise RuntimeError(body['errMsg']) d = {value_col_name: values, column_name: knots} df = MarketDataResponseFrame(pd.DataFrame(data=d)) df = df.set_index(column_name) return df @classmethod def get_mxapi_backtest_data( cls, builder, start_time=None, end_time=None, num_samples=120, csa=None, request_id=None, close_location=None, real_time=None, ) -> pd.DataFrame: real_time = real_time or isinstance(start_time, dt.datetime) if not start_time: if real_time: start_time = DataContext.current.start_time else: start_time = DataContext.current.start_date if not end_time: if real_time: end_time = DataContext.current.end_time else: end_time = DataContext.current.end_date if not csa: csa = 'Default' if not real_time and not close_location: close_location = 'NYC' if real_time and not isinstance(end_time, dt.date): raise ValueError("Start and end need to be either both date or both time") leg = builder.resolve(in_place=False) leg_dict_string = json.dumps(leg, cls=JSONEncoder) leg_dict = json.loads(leg_dict_string) if real_time: 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, } else: request_dict = { 'type': 'MxAPI Backtest Request MQEOD', 'builder': leg_dict, 'startDate': start_time.isoformat(), 'endDate': end_time.isoformat(), 'sampleSize': num_samples, 'csa': csa, 'close': close_location, } url = '/mxapi/mq/backtest' if real_time else '/mxapi/mq/backtest/eod' start = time.perf_counter() try: body = cls._post_with_cache_check(url, 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, ) if real_time: 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 else: values = body['valuations'] valuation_date_strings = body['valuationDates'] valuation_dates = [dt.date.fromisoformat(s) for s in valuation_date_strings] column_name = body['valuationName'] d = {column_name: values, 'date': valuation_dates} df = MarketDataResponseFrame(pd.DataFrame(data=d)) df = df.set_index('date') return df @staticmethod def _get_market_data_filters( asset_ids: List[str], query_type: Union[QueryType, str], where: Union[FieldFilterMap, Dict] = None, source: Union[str] = None, real_time: bool = False, measure='Curve', vendor: str = '', ): inner = { 'entityIds': asset_ids, 'queryType': query_type.value if isinstance(query_type, QueryType) else query_type, 'where': where or {}, 'source': source or 'any', 'frequency': 'Real Time' if real_time else 'End Of Day', 'measures': [measure], } if vendor != '': inner['vendor'] = vendor return inner @staticmethod def build_interval_chunked_market_data_queries( asset_ids: List[str], query_type: Union[QueryType, str], where: Union[FieldFilterMap, Dict] = None, source: Union[str] = None, real_time: bool = False, measure='Curve', vendor: str = '', ) -> List[dict]: parallel_interval = 365 # chunk over a year def chunk_time(start, end) -> tuple: # chunk the time interval into 1 year chunks s = start while s < end: e = min(s + dt.timedelta(days=parallel_interval), end) yield s, e s = e queries = [] if real_time: start, end = DataContext.current.start_time, DataContext.current.end_time start_key, end_key = 'startTime', 'endTime' else: start, end = DataContext.current.start_date, DataContext.current.end_date start_key, end_key = 'startDate', 'endDate' for s, e in chunk_time(start, end): inner = copy( GsDataApi._get_market_data_filters(asset_ids, query_type, where, source, real_time, measure, vendor) ) inner[start_key], inner[end_key] = s, e queries.append({'queries': [inner]}) log_debug("", _logger, f"Created {len(queries)} market data queries") return queries @staticmethod def build_market_data_query( asset_ids: List[str], query_type: Union[QueryType, str], where: Union[FieldFilterMap, Dict] = None, source: Union[str] = None, real_time: bool = False, measure='Curve', parallelize_queries: bool = False, vendor: str = '', ) -> Union[dict, List[dict]]: if parallelize_queries: return GsDataApi.build_interval_chunked_market_data_queries( asset_ids, query_type, where, source, real_time, measure, vendor ) inner = GsDataApi._get_market_data_filters(asset_ids, query_type, where, source, real_time, measure, vendor) if DataContext.current.interval is not None: inner['interval'] = DataContext.current.interval if real_time: inner['startTime'] = DataContext.current.start_time inner['endTime'] = DataContext.current.end_time else: inner['startDate'] = DataContext.current.start_date inner['endDate'] = DataContext.current.end_date return {'queries': [inner]} @classmethod def get_data_providers(cls, entity_id: str, availability: Optional[Dict] = None) -> Dict: """Return daily and real-time data providers :param entity_id: identifier of entity i.e. asset, country, subdivision :param availability: Optional Measures Availability response for the entity :return: dictionary of available data providers ** Usage ** Return a dictionary containing a set of dataset providers for each available data field. For each field will return a dict of daily and real-time dataset providers where available. """ response = ( availability if availability else cls.get_session().sync.get(f'/data/measures/{entity_id}/availability') ) if 'errorMessages' in response: raise MqValueError( f"Data availability request {response['requestId']} failed: {response.get('errorMessages', '')}" ) if 'data' not in response: return {} providers = {} all_data_mappings = sorted(response['data'], key=lambda x: x['rank'], reverse=True) for source in all_data_mappings: freq = source.get('frequency', 'End Of Day') dataset_field = source.get('datasetField', '') rank = source.get('rank') providers.setdefault(dataset_field, {}) if rank: if freq == 'End Of Day': providers[dataset_field][DataFrequency.DAILY] = source['datasetId'] elif freq == 'Real Time': providers[dataset_field][DataFrequency.REAL_TIME] = source['datasetId'] return providers @classmethod def get_market_data(cls, query, request_id=None, ignore_errors: bool = False) -> pd.DataFrame: def validate(body): for e in body['responses']: container = e['queryResponse'][0] if 'errorMessages' in container: msg = f'measure service request {body["requestId"]} failed: {container["errorMessages"]}' raise MqValueError(msg) return body start = time.perf_counter() try: body = cls._post_with_cache_check(url='/data/measures', validator=validate, payload=query) except Exception as e: log_warning(request_id, _logger, f'Market data query {query} failed due to {e}') raise e log_debug( request_id, _logger, 'market data query (%s) with payload (%s) ran in %.3f ms', body.get('requestId'), query, (time.perf_counter() - start) * 1000, ) ids = [] parts = [] for e in body['responses']: container = e['queryResponse'][0] ids.extend(container.get('dataSetIds', ())) if 'errorMessages' in container: msg = f'measure service request {body["requestId"]} failed: {container["errorMessages"]}' if ignore_errors: log_warning(request_id, _logger, msg) else: raise MqValueError(msg) if 'response' in container: df = MarketDataResponseFrame(container['response']['data']) df = df.set_index('date' if 'date' in df.columns else 'time') df.index = pd.to_datetime(df.index) parts.append(df) log_debug(request_id, _logger, f'fetched data from {ids}') df = pd.concat(parts) if len(parts) > 0 else MarketDataResponseFrame() df.dataset_ids = tuple(ids) return df @classmethod def __normalise_coordinate_data( cls, data: Iterable[Union[MDAPIDataQueryResponse, Dict]], fields: Optional[Tuple[MDAPIQueryField, ...]] = None ) -> Iterable[Iterable[Dict]]: ret = [] for response in data: coord_data = [] rows = ( (r.as_dict() for r in response.data) if isinstance(response, MDAPIDataQueryResponse) else response.get('data', ()) ) for pt in rows: if not pt: continue if not fields and 'value' not in pt: value_field = pt['mktQuotingStyle'] if value_field not in pt: continue pt['value'] = pt.pop(value_field) coord_data.append(pt) ret.append(coord_data) return ret @classmethod def __df_from_coordinate_data( cls, data: Iterable[Dict], *, use_datetime_index: Optional[bool] = True ) -> pd.DataFrame: df = cls._sort_coordinate_data(pd.DataFrame.from_records(data)) index_field = next((f for f in ('time', 'date') if f in df.columns), None) if index_field and use_datetime_index: df = df.set_index(pd.DatetimeIndex(df.loc[:, index_field].values)) return df @classmethod def _sort_coordinate_data( cls, df: pd.DataFrame, by: Tuple[str, ...] = ( 'date', 'time', 'mktType', 'mktAsset', 'mktClass', 'mktPoint', 'mktQuotingStyle', 'value', ), ) -> pd.DataFrame: columns = df.columns field_order = [f for f in by if f in columns] field_order.extend(f for f in columns if f not in field_order) return df[field_order] @classmethod def _coordinate_from_str(cls, coordinate_str: str) -> MarketDataCoordinate: tmp = coordinate_str.rsplit(".", 1) dimensions = tmp[0].split("_") if len(dimensions) < 2: raise MqValueError('invalid coordinate ' + coordinate_str) kwargs = { 'mkt_type': dimensions[0], 'mkt_asset': dimensions[1] or None, 'mkt_quoting_style': tmp[-1] if len(tmp) > 1 else None, } if len(dimensions) > 2: kwargs['mkt_class'] = dimensions[2] or None if len(dimensions) > 3: kwargs['mkt_point'] = tuple(dimensions[3:]) or None return MarketDataCoordinate(**kwargs) @classmethod def coordinates_last( cls, coordinates: Union[Iterable[str], Iterable[MarketDataCoordinate]], as_of: Union[dt.datetime, dt.date] = None, vendor: MarketDataVendor = MarketDataVendor.Goldman_Sachs, as_dataframe: bool = False, pricing_location: Optional[PricingLocation] = None, timeout: int = None, ) -> Union[Dict, pd.DataFrame]: """ Get last value of coordinates data :param coordinates: market data coordinate(s) :param as_of: snapshot date or time :param vendor: data vendor :param as_dataframe: whether to return the result as Dataframe :param pricing_location: the location where close data has been recorded (not used for real-time query) :param timeout: data query timeout; if timeout is not set then the default timeout is used :return: Dataframe or dictionary of the returned data **Examples** >>> coordinate = ("FX Fwd_USD/EUR_Fwd Pt_2y",) >>> data = GsDataApi.coordinates_last(coordinate, dt.datetime(2019, 11, 19)) """ market_data_coordinates = tuple( cls._coordinate_from_str(coord) if isinstance(coord, str) else coord for coord in coordinates ) query = cls.build_query( end=as_of, market_data_coordinates=market_data_coordinates, vendor=vendor, pricing_location=pricing_location ) kwargs = {} if timeout is not None: kwargs['timeout'] = timeout data = cls.last_data(query, **kwargs) if not as_dataframe: ret = {coordinate: None for coordinate in market_data_coordinates} for idx, row in enumerate(cls.__normalise_coordinate_data(data)): try: ret[market_data_coordinates[idx]] = row[0]['value'] except IndexError: ret[market_data_coordinates[idx]] = None return ret ret = [] datetime_field = 'time' if isinstance(as_of, dt.datetime) else 'date' for idx, row in enumerate(cls.__normalise_coordinate_data(data)): coordinate_as_dict = market_data_coordinates[idx].as_dict(as_camel_case=True) try: ret.append( dict( chain( coordinate_as_dict.items(), (('value', row[0]['value']), (datetime_field, row[0][datetime_field])), ) ) ) except IndexError: ret.append(dict(chain(coordinate_as_dict.items(), (('value', None), (datetime_field, None))))) return cls.__df_from_coordinate_data(ret, use_datetime_index=False) @classmethod def coordinates_data( cls, coordinates: Union[str, MarketDataCoordinate, Iterable[str], Iterable[MarketDataCoordinate]], start: Union[dt.datetime, dt.date] = None, end: Union[dt.datetime, dt.date] = None, vendor: MarketDataVendor = MarketDataVendor.Goldman_Sachs, as_multiple_dataframes: bool = False, pricing_location: Optional[PricingLocation] = None, fields: Optional[Tuple[MDAPIQueryField, ...]] = None, **kwargs, ) -> Union[pd.DataFrame, Tuple[pd.DataFrame]]: """ Get coordinates data :param coordinates: market data coordinate(s) :param start: start date or time :param end: end date or time :param vendor: data vendor :param as_multiple_dataframes: whether to return the result as one or multiple Dataframe(s) :param pricing_location: the location where close data has been recorded (not used for real-time query) :param fields: value fields to return :param kwargs: Extra query arguments :return: Dataframe(s) of the returned data **Examples** >>> coordinate = ("FX Fwd_USD/EUR_Fwd Pt_2y",) >>> data = GsDataApi.coordinates_data(coordinate, dt.datetime(2019, 11, 18), dt.datetime(2019, 11, 19)) """ coordinates_iterable = (coordinates,) if isinstance(coordinates, (MarketDataCoordinate, str)) else coordinates query = cls.build_query( market_data_coordinates=tuple( cls._coordinate_from_str(coord) if isinstance(coord, str) else coord for coord in coordinates_iterable ), vendor=vendor, start=start, end=end, pricing_location=pricing_location, fields=fields, **kwargs, ) results = cls.__normalise_coordinate_data(cls.query_data(query), fields=fields) if as_multiple_dataframes: return tuple(GsDataApi.__df_from_coordinate_data(r) for r in results) else: return cls.__df_from_coordinate_data(chain.from_iterable(results)) @classmethod def coordinates_data_series( cls, coordinates: Union[str, MarketDataCoordinate, Iterable[str], Iterable[MarketDataCoordinate]], start: Union[dt.datetime, dt.date] = None, end: Union[dt.datetime, dt.date] = None, vendor: MarketDataVendor = MarketDataVendor.Goldman_Sachs, pricing_location: Optional[PricingLocation] = None, **kwargs, ) -> Union[pd.Series, Tuple[pd.Series]]: """ Get coordinates data series :param coordinates: market data coordinate(s) :param start: start date or time :param end: end date or time :param vendor: data vendor :param pricing_location: the location where close data has been recorded (not used for real-time query) :param kwargs: Extra query arguments :return: Series of the returned data **Examples** >>> coordinate = ("FX Fwd_USD/EUR_Fwd Pt_2y",) >>> data = GsDataApi.coordinates_data_series(coordinate, dt.datetime(2019, 11, 18), dt.datetime(2019, 11, 19)) """ dfs = cls.coordinates_data( coordinates, start=start, end=end, pricing_location=pricing_location, vendor=vendor, as_multiple_dataframes=True, **kwargs, ) ret = tuple( pd.Series(dtype=float) if df.empty else pd.Series(index=df.index, data=df.value.values) for df in dfs ) if isinstance(coordinates, (MarketDataCoordinate, str)): return ret[0] else: return ret @classmethod @cachetools.cached(TTLCache(ttl=3600, maxsize=128)) def get_types(cls, dataset_id: str): results = cls.get_session().sync.get(f'/data/catalog/{dataset_id}') fields = results.get("fields") if fields: field_types = {} for key, value in fields.items(): field_type = value.get('type') field_format = value.get('format') field_types[key] = field_format or field_type return field_types raise RuntimeError(f"Unable to get Dataset schema for {dataset_id}") @classmethod def get_field_types(cls, field_names: Union[str, List[str]]): try: fields = cls.get_dataset_fields(names=field_names, limit=len(field_names)) except Exception: return {} if fields: field_types = {} field: DataSetFieldEntity for field in fields: field_name = field.name field_type = field.type_ field_format = field.parameters.get('format') if field.parameters else None field_types[field_name] = field_format or field_type return field_types return {} @classmethod def construct_dataframe_with_types( cls, dataset_id: str, data: Union[Base, List, Tuple], schema_varies=False, standard_fields=False ) -> pd.DataFrame: """ Constructs a dataframe with correct date types. :param dataset_id: id of the dataset :param data: data to convert with correct types :param schema_varies: if set, method will not assume that all rows have the same columns :param standard_fields: if set, will use fields api instead of catalog api to get fieldTypes :return: dataframe with correct types """ if len(data): # Use first row to infer fields from data sample = data if schema_varies else [data[0]] incoming_data_data_types = pd.DataFrame(sample).dtypes.to_dict() dataset_types = ( cls.get_types(dataset_id) if not standard_fields else cls.get_field_types(field_names=list(incoming_data_data_types.keys())) ) # fallback approach in case fields api doesn't return results if dataset_types == {} and standard_fields: dataset_types = cls.get_types(dataset_id) df = pd.DataFrame(data, columns={**dataset_types, **incoming_data_data_types}) for field_name, type_name in dataset_types.items(): if ( df.get(field_name) is not None and type_name in ('date', 'date-time') and len(df.get(field_name).value_counts()) > 0 ): if int(pd.__version__.split('.')[0]) > 1: df[field_name] = pd.to_datetime(df[field_name], format='ISO8601') else: df[field_name] = pd.to_datetime(df[field_name]) field_names = dataset_types.keys() if 'date' in field_names: df = df.set_index('date') elif 'time' in field_names: df = df.set_index('time') return df else: return pd.DataFrame({}) @classmethod def get_dataset_fields( cls, ids: Union[str, List[str]] = None, names: Union[str, List[str]] = None, limit: int = 10, ) -> Union[Tuple[DataSetFieldEntity, ...], Tuple[dict, ...]]: """ Get many dataset fields :param ids: ID(s) of the field(s) :param names: Name(s) of the field(s) :param limit: Limit on the number of results returned. Default: 10 :return: Tuple of DataSetFieldEntity **Examples** >>> from gs_quant.api.gs.data import GsDataApi >>> fields = GsDataApi.get_dataset_fields(names = ['adjustedClosePrice', 'adjustedOpenPrice']) """ where = dict(filter(lambda item: item[1] is not None, dict(id=ids, name=names).items())) response = cls.get_session().sync.post( '/data/fields/query', payload={'where': where, 'limit': limit}, cls=DataSetFieldEntity ) return response['results'] @classmethod def create_dataset_fields( cls, fields: List[DataSetFieldEntity] ) -> Union[Tuple[DataSetFieldEntity, ...], Tuple[dict, ...]]: """ Create many dataset fields :param fields: Fields to be created :return: Tuple of DataSetFieldEntity **Examples** >>> from gs_quant.api.gs.data import GsDataApi >>> from gs_quant.target.data import DataSetFieldEntity >>> fields = [ >>> DataSetFieldEntity(name='price', type_='number', description='Price of the instrument.'), >>> DataSetFieldEntity(name='strikeReference', type_='string', description='Reference for strike level.', >>> parameters={'enum': ['delta', 'spot', 'forward', 'normalized']}) >>> ] >>> GsDataApi.create_dataset_fields(fields) """ params = {'fields': fields} response = cls.get_session().sync.post('/data/fields/bulk', payload=params, cls=DataSetFieldEntity) return response['results'] @classmethod def update_dataset_fields( cls, fields: List[DataSetFieldEntity] ) -> Union[Tuple[DataSetFieldEntity, ...], Tuple[dict, ...]]: """ Update many dataset fields :param fields: Fields to be created :return: Tuple of DataSetFieldEntity **Examples** >>> from gs_quant.api.gs.data import GsDataApi >>> from gs_quant.target.data import DataSetFieldEntity >>> fields = [ >>> DataSetFieldEntity(id='FIMFMQ0P19AZ2XK9', name='price', type_='number', >>> description='Price of the instrument.'), >>> DataSetFieldEntity(id='FI7EFDC3SQQBMDX8', name='strikeReference', type_='string', >>> description='Reference for strike level.', >>> parameters={'enum': ['delta', 'spot', 'forward', 'normalized']}) >>> ] >>> GsDataApi.update_dataset_fields(fields) """ params = {'fields': fields} response = cls.get_session().sync.put('/data/fields/bulk', payload=params, cls=DataSetFieldEntity) return response['results'] class MarketDataResponseFrame(pd.DataFrame): _internal_names = pd.DataFrame._internal_names + ['dataset_ids'] _internal_names_set = set(_internal_names) @property def _constructor(self): return MarketDataResponseFrame def __finalize__(self, other, method=None, **kwargs): # Call the parent class's __finalize__ method super().__finalize__(other, method, **kwargs) # Copy custom attributes from the other DataFrame if isinstance(other, MarketDataResponseFrame) and hasattr(other, 'dataset_ids'): self.dataset_ids = getattr(other, 'dataset_ids', None) return self ================================================ FILE: gs_quant/api/gs/data_screen.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import logging from typing import Tuple, Dict from gs_quant.session import GsSession from gs_quant.target.data_screen import AnalyticsScreen, FilterRequest, DataRow _logger = logging.getLogger(__name__) class GsDataScreenApi: @classmethod def get_screens(cls) -> Tuple[AnalyticsScreen, ...]: """ Retrieves screen information about all screens accessible to the current user. :return: AnalyticsScreen tuple, a tuple containing each screen available to the user. """ return GsSession.current.sync.get('/data/screens', cls=AnalyticsScreen)['results'] @classmethod def get_screen(cls, screen_id: str) -> AnalyticsScreen: """ Retrieves information about the screen specified in parameter screen_id. :param screen_id: str, the id of the screen whose information is being retrieved. :return: AnalyticsScreen, an object containing information about the screen associated with screen_id. """ return GsSession.current.sync.get('/data/screens/{id}'.format(id=screen_id), cls=AnalyticsScreen) @classmethod def get_column_info(cls, screen_id: str) -> Dict[str, Dict]: """ Retrieves information about each column in the screen with id corresponding to screen_id. This column information can be used to create filters on the screen. :param screen_id: str, the id of the screen whose column information is being retrieved. :return: dict, a dictionary where each key identifies a column and each value identifies properties of that column. """ return GsSession.current.sync.get('/data/screens/{id}/filters'.format(id=screen_id))['aggregations'] @classmethod def delete_screen(cls, screen_id: str) -> None: """ Deletes the screen identified by screen_id. A deleted screen's data and information cannot be retrieved again. :param screen_id: str, the id of the screen that is being deleted :return: None """ return GsSession.current.sync.delete('/data/screens/{id}'.format(id=screen_id)) @classmethod def create_screen(cls, screen: AnalyticsScreen) -> AnalyticsScreen: """ Creates a new screen with a new id from an existing screen object. The new screen is a copy of the screen object passed into this function. To make changes to the new screen upon creation, change the attributes of the AnalyticsScreen object before passing it into this function. :param screen: AnalyticsScreen, the screen object that is copied to make the new screen. :return: AnalyticsScreen, the new screen object containing a new id """ request_headers = {'Content-Type': 'application/json;charset=utf-8'} return GsSession.current.sync.post( '/data/screens', screen, request_headers=request_headers, cls=AnalyticsScreen ) @classmethod def filter_screen(cls, screen_id: str, filter_request: FilterRequest) -> Tuple[DataRow, ...]: """ Returns filtered data from the screen associated with this screen_id. The filters applied to the data are contained in the filters parameter. Any filters previously applied to the relevant screen will be temporarily ignored in favor of the filters passed into this function. To see the data from this screen with the screen's current filters, simply pass in the screen's id and filter_parameters from the screen's AnalyticsScreen object. :param screen_id: str, the id of the screen whose data is being retrieved. :param filter_request: FilterRequest, an object populated with information about how the retrieved data should be filtered. :return: Tuple of DataRow objects, each DataRow in the tuple is a row of filtered data from this screen. """ request_headers = {'Content-Type': 'application/json;charset=utf-8'} return GsSession.current.sync.post( '/data/screens/{id}/filter'.format(id=screen_id), filter_request, request_headers=request_headers, cls=DataRow, )['results'] @classmethod def update_screen(cls, screen_id: str, screen: AnalyticsScreen) -> AnalyticsScreen: """ Permanently overwrites the screen at screen_id with the screen object provided in the screen parameter. This includes overwriting any filters applied to the screen at screen_id. Throws an error if the screen_id provided does not match the ID of the screen object provided. :param screen_id: str, the id of the screen being updated. Must be the same id as is contained in the screen parameter. :param screen: AnalyticsScreen, the object representing the screen to be updated. This object should contain any desired changes to the screen :return: AnalyticsScreen, an object containing information about the updated screen. """ assert screen_id == screen.id_ request_headers = {'Content-Type': 'application/json;charset=utf-8'} return GsSession.current.sync.put( '/data/screens/{id}'.format(id=screen_id), screen, request_headers=request_headers, cls=AnalyticsScreen ) ================================================ FILE: gs_quant/api/gs/datagrid.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import json import urllib.parse from typing import List from pydash import get from gs_quant.analytics.datagrid import DataGrid from gs_quant.analytics.datagrid.datagrid import API, DATAGRID_HEADERS from gs_quant.session import GsSession class GsDataGridApi: """GS DataGrids API implementation""" @classmethod def get_datagrids(cls, limit: int = 10, **kwargs) -> List[DataGrid]: raw_datagrids = get( GsSession.current.sync.get( f'{API}?limit={limit}&orderBy=>lastUpdatedTime&{urllib.parse.urlencode(kwargs)}' ), 'results', [], ) return [DataGrid.from_dict(raw_datagrid) for raw_datagrid in raw_datagrids] @classmethod def get_my_datagrids(cls, limit: int = 10, **kwargs) -> List[DataGrid]: user_id = GsSession.current.sync.get('/users/self')['id'] raw_datagrids = get( GsSession.current.sync.get( f'{API}?limit={limit}&ownerId={user_id}&orderBy=>lastUpdatedTime&{urllib.parse.urlencode(kwargs)}' ), 'results', [], ) return [DataGrid.from_dict(raw_datagrid) for raw_datagrid in raw_datagrids] @classmethod def get_datagrid(cls, datagrid_id: str) -> DataGrid: raw_datagrid = GsSession.current.sync.get(f'{API}/{datagrid_id}') return DataGrid.from_dict(raw_datagrid) @classmethod def create_datagrid(cls, datagrid: DataGrid) -> DataGrid: datagrid_json = json.dumps(datagrid.as_dict()) response = GsSession.current.sync.post(f'{API}', datagrid_json, request_headers=DATAGRID_HEADERS) return DataGrid.from_dict(response) @classmethod def update_datagrid(cls, datagrid: DataGrid): datagrid_json = json.dumps(datagrid.as_dict()) datagrid = GsSession.current.sync.put(f'{API}/{datagrid.id_}', datagrid_json, request_headers=DATAGRID_HEADERS) return DataGrid.from_dict(datagrid) @classmethod def delete_datagrid(cls, datagrid: DataGrid): return GsSession.current.sync.delete(f'{API}/{datagrid.id_}') ================================================ FILE: gs_quant/api/gs/esg.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import logging from enum import Enum from typing import List, Dict from gs_quant.session import GsSession _logger = logging.getLogger(__name__) class ESGCard(Enum): """ ESG Cards """ SUMMARY = 'summary' QUINTILES = 'quintiles' WEIGHTS_BY_SECTOR = 'weightsBySector' MEASURES_BY_SECTOR = 'measuresBySector' WEIGHTS_BY_REGION = 'weightsByRegion' MEASURES_BY_REGION = 'measuresByRegion' TOP_TEN_RANKED = 'topTenRanked' BOTTOM_TEN_RANKED = 'bottomTenRanked' NO_ESG_DATA = 'noEsgData' NO_PRICING_DATA = 'noPricingData' def __str__(self): return self.value class ESGMeasure(Enum): """ ESG Measures """ G_PERCENTILE = 'gPercentile' G_REGIONAL_PERCENTILE = 'gRegionalPercentile' ES_PERCENTILE = 'esPercentile' ES_DISCLOSURE_PERCENTAGE = 'esDisclosurePercentage' ES_MOMENTUM_PERCENTILE = 'esMomentumPercentile' def __str__(self): return self.value class GsEsgApi: """GS ESG API client implementation""" @classmethod def get_esg( cls, entity_id: str, benchmark_id: str = None, pricing_date: dt.date = None, measures: List[ESGMeasure] = [], cards: List[ESGCard] = [], ) -> Dict: url = f'/esg/{entity_id}?' if pricing_date: url += f'&date={pricing_date.strftime("%Y-%m-%d")}' if benchmark_id: url += f'&benchmark={benchmark_id}' for measure in measures: url += f'&measure={measure}' for card in cards: url += f'&card={card}' return GsSession.current.sync.get(url) ================================================ FILE: gs_quant/api/gs/federated_secmaster.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from enum import Enum from gs_quant.target.secmaster import SecMasterAssetType from gs_quant.common import AssetType, AssetClass from typing import Union, Iterable, Dict, Optional from gs_quant.json_encoder import JSONEncoder from gs_quant.session import GsSession import json import datetime as dt SECURITIES_FEDERATED = '/markets/securities/federated' class FederatedIdentifiers(Enum): IDENTIFIER = 'identifier' ID = 'id' ASSET_ID = 'assetId' GSID = 'gsid' TICKER = 'ticker' BBID = 'bbid' BCID = 'bcid' RIC = 'ric' RCIC = 'rcic' CUSIP = 'cusip' CINS = 'cins' SEDOL = 'sedol' ISIN = 'isin' PRIMEID = 'primeId' class GsSecurityMasterFederatedApi: @classmethod def get_a_security(cls, id: str, effective_date: dt.date = None) -> Optional[dict]: """ Get security or asset for given security id or asset id. @param id: secmaster id(e.g. 'GSPD111E123') or asset id(e.g. 'MANYS1FCCWWV45P7') @param effective_date: As of date for query @return: dict """ if not id.startswith("GS") and not id.startswith("MA"): raise ValueError(f"Invalid id: {id}. Security id starts with 'GS' and Asset id starts with 'MA'") params = {} if effective_date is not None: params["effectiveDate"] = effective_date payload = json.loads(json.dumps(params, cls=JSONEncoder)) return GsSession.current.sync.get(f'{SECURITIES_FEDERATED}/{id}', payload=payload) @classmethod def get_security_identifiers(cls, id: str) -> Optional[dict]: """ Get identifiers history for given security id or asset id. @param id: secmaster id(e.g. 'GSPD111E123') or asset id(e.g. 'MANYS1FCCWWV45P7') @return: dict """ if not id.startswith("GS") and not id.startswith("MA"): raise ValueError(f"Invalid id: {id}. Security id starts with 'GS' and Asset id starts with 'MA'") return GsSession.current.sync.get(f'{SECURITIES_FEDERATED}/{id}/identifiers') @classmethod def get_many_securities( cls, type_: Union[SecMasterAssetType, AssetType] = None, effective_date: dt.date = None, limit: int = 50, is_primary=None, offset_key: str = None, **query_params: Dict[FederatedIdentifiers, Union[str, Iterable[str]]], ) -> Optional[dict]: """ Get reference data for a single page. Use returned offsetKey to fetch next page. @param is_primary: @param type_: security or asset type @param effective_date: As of date for query @param limit: integer of individual page @param offset_key: string, an offset indicating where the page ends @return: dict """ return cls.__query_securities( type_=type_, effective_date=effective_date, limit=limit, flatten=False, is_primary=is_primary, offset_key=offset_key, **query_params, ) @classmethod def get_securities_data( cls, type_: Union[SecMasterAssetType, AssetType] = None, effective_date: dt.date = None, limit: int = 50, is_primary=None, offset_key: str = None, **query_params: Dict[FederatedIdentifiers, Union[str, Iterable[str]]], ) -> Optional[dict]: """ Get flattened reference data for a single page. Use returned offsetKey to fetch next page. @param is_primary: @param type_: security or asset type @param effective_date: As of date for query @param limit: integer of individual page @param offset_key: string, an offset indicating where the page ends @return: dict """ return cls.__query_securities( type_=type_, effective_date=effective_date, limit=limit, flatten=True, is_primary=is_primary, offset_key=offset_key, **query_params, ) @classmethod def search_many_securities( cls, q: str = None, limit: int = 10, offset_key: str = None, asset_class: AssetClass = None, type_: Union[SecMasterAssetType, AssetType] = None, is_primary: bool = None, ) -> Optional[dict]: """ Search reference data by a query string. It does a full text search among names, identifiers, company. @param q: query string @param limit: number of returned matches @param offset_key: string, an offset indicating where the page ends @param asset_class: filter restricting the class of results @param type_: filter restricting the type of results @param is_primary: filter restricting the matches to primary listings @return: dict """ return cls.__search_securities( q=q, limit=limit, offset_key=offset_key, flatten=False, asset_class=asset_class, type_=type_, is_primary=is_primary, ) @classmethod def search_securities_data( cls, q: str = None, limit: int = 10, offset_key: str = None, asset_class: AssetClass = None, type_: Union[SecMasterAssetType, AssetType] = None, is_primary: bool = None, ) -> Optional[dict]: """ Search flattened reference data by a query string. It does a full text search among names, identifiers, company. @param q: query string @param limit: number of returned matches @param offset_key: string, an offset indicating where the page ends @param asset_class: filter restricting the class of results @param type_: filter restricting the type of results @param is_primary: filter restricting the matches to primary listings @return: dict """ return cls.__search_securities( q=q, limit=limit, offset_key=offset_key, flatten=True, asset_class=asset_class, type_=type_, is_primary=is_primary, ) @classmethod def __query_securities( cls, type_: Union[SecMasterAssetType, AssetType] = None, effective_date: dt.date = None, limit: int = 50, flatten=False, is_primary=None, offset_key: str = None, **query_params: Dict[FederatedIdentifiers, Union[str, Iterable[str]]], ) -> Optional[dict]: if (query_params is None or len(query_params) == 0) and type_ is None: raise ValueError("Neither '_type' nor 'query_params' are provided") params = {"limit": limit} cls.__prepare_params(params, effective_date, offset_key, is_primary, type_, None) params = {**params, **query_params} payload = json.loads(json.dumps(params, cls=JSONEncoder)) if flatten: return GsSession.current.sync.get(f'{SECURITIES_FEDERATED}/data', payload=payload) return GsSession.current.sync.get(f'{SECURITIES_FEDERATED}', payload=payload) @classmethod def __search_securities( cls, q: str = None, limit: int = 10, offset_key: str = None, flatten=False, asset_class: AssetClass = None, type_: Union[SecMasterAssetType, AssetType] = None, is_primary: bool = None, ) -> Optional[dict]: if q is None: raise ValueError("No search query provided") params = {"q": q, "limit": limit} cls.__prepare_params(params, None, offset_key, is_primary, type_, asset_class) payload = json.loads(json.dumps(params, cls=JSONEncoder)) if flatten: return GsSession.current.sync.get(f'{SECURITIES_FEDERATED}/search/data', payload=payload) return GsSession.current.sync.get(f'{SECURITIES_FEDERATED}/search', payload=payload) @classmethod def __prepare_params(cls, params, effective_date, offset_key, is_primary, type_, asset_class): if effective_date is not None: params["effectiveDate"] = effective_date if offset_key is not None: params["offsetKey"] = offset_key if is_primary is not None: params["isPrimary"] = is_primary if type_ is not None: params["type"] = type_.value if asset_class is not None: params["assetClass"] = asset_class.value ================================================ FILE: gs_quant/api/gs/groups.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from typing import List, Dict from gs_quant.session import GsSession from gs_quant.target.groups import Group class GsGroupsApi: @classmethod def get_groups( cls, ids: List[str] = None, names: List[str] = None, oe_ids: List[str] = None, owner_ids: List[str] = None, tags: List[str] = None, user_ids: List[str] = None, scroll_id: str = None, scroll_time: str = None, limit: int = 100, offset: int = 0, order_by: str = None, ) -> List: url = f'/groups?limit={limit}&offset={offset}' if ids: url += f'&id={"&id=".join(ids)}' if names: url += f'&name={"&name=".join(names)}' if oe_ids: url += f'&oeId={"&oeId=".join(oe_ids)}' if owner_ids: url += f'&ownerId={"&ownerId=".join(owner_ids)}' if tags: url += f'&tags={"&tags=".join(tags)}' if user_ids: url += f'&userIds={"&userIds=".join(user_ids)}' if scroll_id: url += f'&scrollId={scroll_id}' if scroll_time: url += f'&scrollTime={scroll_time}' if order_by: url += f'&orderBy={order_by}' return GsSession.current.sync.get(url, cls=Group)['results'] @classmethod def create_group(cls, group: Group) -> Dict: return GsSession.current.sync.post('/groups', group, cls=Group) @classmethod def get_group(cls, group_id: str) -> Group: return GsSession.current.sync.get(f'/groups/{group_id}', cls=Group) @classmethod def update_group(cls, group_id: str, group: Group) -> Group: # PUT request for updating a group can't have the group ID in it group_dict = group.to_json() if group_dict.get('entitlements'): group_dict['entitlements'] = group_dict['entitlements'].to_json() group_dict.pop('id') return GsSession.current.sync.put(f'/groups/{group_id}', group_dict, cls=Group) @classmethod def delete_group(cls, group_id: str): GsSession.current.sync.delete(f'/groups/{group_id}') @classmethod def get_users_in_group(cls, group_id: str) -> List: return GsSession.current.sync.get(f'/groups/{group_id}/users').get('users', []) @classmethod def add_users_to_group(cls, group_id: str, user_ids: List[str]): GsSession.current.sync.post(f'/groups/{group_id}/users', {'userIds': user_ids}) @classmethod def delete_users_from_group(cls, group_id: str, user_ids: List[str]): GsSession.current.sync.delete(f'/groups/{group_id}/users', {'userIds': user_ids}, use_body=True) ================================================ FILE: gs_quant/api/gs/hedges.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import logging from typing import Tuple, List, Dict from gs_quant.session import GsSession from gs_quant.target.hedge import Hedge from gs_quant.target.hedge import PerformanceHedgeParameters, ClassificationConstraint, AssetConstraint, Target _logger = logging.getLogger(__name__) CALCULATION_TIMEOUT = 180 class GsHedgeApi: """GS Hedge API client implementation""" @classmethod def get_many_hedges(cls, ids: List[str] = None, names: List[str] = None, limit: int = 100): url = f'/hedges?limit={limit}' if ids: url += f'&id={"&id=".join(ids)}' if names: url += f'&name={"&name=".join(names)}' return GsSession.current.sync.get(url, cls=Hedge) @classmethod def create_hedge(cls, hedge: Dict) -> Hedge: return GsSession.current.sync.post('/hedges', hedge, cls=Hedge) @classmethod def get_hedge(cls, hedge_id: str) -> Hedge: return GsSession.current.sync.get(f'/hedges/{hedge_id}', cls=Hedge) @classmethod def get_hedge_data(cls, ids: List[str] = None, names: List[str] = None, limit: int = 100) -> List[Dict]: url = f'/hedges/data?limit={limit}' if ids: url += f'&id={"&id=".join(ids)}' if names: url += f'&name={"&name=".join(names)}' return GsSession.current.sync.get(url, cls=Hedge)['results'] @classmethod def get_hedge_results(cls, hedge_id: str, start_date: dt.date = None, end_date: dt.date = None) -> Dict: url = f'/hedges/results?id={hedge_id}' if start_date is not None: url += f'&startDate={start_date.strftime("%Y-%m-%d")}' if end_date is not None: url += f'&endDate={end_date.strftime("%Y-%m-%d")}' return GsSession.current.sync.get(url)['results'][0] @classmethod def update_hedge(cls, hedge_id: str, hedge: Hedge) -> Hedge: return GsSession.current.sync.put(f'/hedges/{hedge_id}', hedge, cls=Hedge) @classmethod def delete_hedge(cls, hedge_id: str): return GsSession.current.sync.delete(f'/hedges/{hedge_id}', cls=Hedge) @classmethod def construct_performance_hedge_query( cls, hedge_target: str, universe: Tuple[str, ...], notional: float, observation_start_date: dt.date, observation_end_date: dt.date, backtest_start_date: dt.date, backtest_end_date: dt.date, use_machine_learning: bool = False, lasso_weight: float = None, ridge_weight: float = None, max_return_deviation: float = 5, max_adv_percentage: float = 15, max_leverage: float = 100, max_weight: float = 100, min_market_cap: float = None, max_market_cap: float = None, asset_constraints: Tuple[AssetConstraint, ...] = None, benchmarks: Tuple[str, ...] = None, classification_constraints: Tuple[ClassificationConstraint, ...] = None, exclude_corporate_actions: bool = False, exclude_corporate_actions_types: Tuple = None, exclude_hard_to_borrow_assets: bool = False, exclude_restricted_assets: bool = False, exclude_target_assets: bool = True, explode_universe: bool = True, market_participation_rate: float = 10, sampling_period: str = 'Daily', ) -> dict: """ Function to construct a performance hedge query (for both the New Performance Hedger and Standard Hedger) by passing in required/optional arguments similar to the performance hedger on the Marquee UI. :param hedge_target: str, the target asset we hedge - in Marquee ID (MQID) form :param universe: Tuple[str, ...], the universe(s) used to create the hedge - in Marquee ID (MQID) form :param notional: float, the total notional dollar amount of the single asset to hedge :param observation_start_date: datetime.date, the observation start date of the hedge specified in datetime form :param observation_end_date: datetime.date, the observation end date of the hedge specified in datetime form :param backtest_start_date: datetime.date, the backtest start date of the hedge specified in datetime form :param backtest_end_date: datetime.date, the backtest end date of the hedge specified in datetime form :param use_machine_learning: bool, whether to run the New Performance Hedger or Standard Hedger :param lasso_weight: float, the value of Concentration (Lasso) include in the hedge if using the New Performance Hedger :param ridge_weight: float, the value of Diversity (Ridge) include in the hedge if using the New Performance Hedger :param max_return_deviation: float, the maximum amount that a hedge portfolio's returns can deviate from the target asset returns, as a percentage :param max_adv_percentage: float, the maximum trading liquidity of each asset in the hedge portfolio, as a percentage :param max_leverage: float, the maximum amount of equity used to construct the hedge, as a percentage (i.e. if equal to 60, then 60% equity and 40% cash are used) :param max_weight: float, the maximum weight that any individual asset can hold in the hedge portfolio :param min_market_cap: float, the minimum market cap to filter assets chosen in the hedge portfolio by :param max_market_cap: float, the maximum market cap to filter assets chosen in the hedge portfolio by :param asset_constraints: Tuple[AssetConstraint, ...], constraints on individual assets to limit how much weight they can contribute in the hedge portfolio (i.e. [{assetId: "MA4B66MW5E27UAL9SUX", min: 0.01, max: 100}]) :param benchmarks: Tuple[str, ...], benchmarks to compare the hedge against :param classification_constraints: Tuple[ClassificationConstraint, ...], constraints on classifications such as Sector/Industry/etc. (i.e [{type: "Sector", min: 0, max: 38, name: "Energy"}, …]) :param exclude_corporate_actions: bool, whether to exclude assets in the hedge portfolio that have pending corporate actions (such as a merger) :param exclude_corporate_actions_types: Tuple[Union[CorporateActionsTypes, str], ...], if excluding assets with corporate actions, this includes all of the types of corporate actions (i.e. ["Mergers", "Spinoffs", "Reorganization"]) :param exclude_hard_to_borrow_assets: bool, whether to exclude assets in the hedge portfolio that are harder to borrow :param exclude_restricted_assets: bool, whether to exclude assets in the hedge portfolio that are considered restricted companies :param exclude_target_assets: bool, whether to exclude to the target asset in the hedge portfolio (important to leave this as true for a valid hedge) :param explode_universe: bool, whether to explode the underlying universe into its constituents in the hedge :param market_participation_rate: float, the percentage of market to use to incur transaction costs, used by Marquee :param sampling_period: str, the sampling period to use (i.e. 'Daily' or 'Weekly') :return: dict, the hedge query represented by a dictionary of inputs """ hedge_dict = { 'objective': 'Replicate Performance', 'parameters': PerformanceHedgeParameters( Target(id=hedge_target), universe, notional, observation_start_date, observation_end_date, max_leverage, backtest_start_date, backtest_end_date, sampling_period, exclude_target_assets, exclude_corporate_actions, exclude_corporate_actions_types, exclude_hard_to_borrow_assets, exclude_restricted_assets, max_adv_percentage, explode_universe, max_return_deviation, max_weight, min_market_cap, max_market_cap, market_participation_rate, asset_constraints, classification_constraints, benchmarks, use_machine_learning, lasso_weight, ridge_weight, ), } return hedge_dict @classmethod def calculate_hedge(cls, hedge_query: dict) -> dict: """ This function is designed to take in a performance hedge query and then return the hedge results (in the form of a dictionary) from calling the performance hedger API. :param hedge_query: dict, hedge data that is sent to the Marquee API as input to the performance hedger :return: dict, the results of calling the Marquee performance hedger """ return GsSession.current.sync.post('/hedges/calculations', payload=hedge_query, timeout=CALCULATION_TIMEOUT) @classmethod def share_hedge_group( cls, hedge_group_id: str, strategy_request: Dict, optimization_response: Dict, hedge_name: str = "Custom Hedge", group_name: str = "New Hedge Group", view_emails: List[str] = None, admin_emails: List[str] = None, ) -> Dict: """ Share a saved hedge group with other users by updating entitlements using email addresses. :param hedge_group_id: The ID of the hedge group to share (from save response) :param strategy_request: The strategy_as_dict from run() - the request payload sent to optimizer :param optimization_response: The optimization_results from run() - the response from optimizer :param hedge_name: Name for the individual hedge :param group_name: Name for the hedge group :param view_emails: List of user email addresses to grant view access :param admin_emails: List of user email addresses to grant admin access :return: API response with updated entitlements Example: >>> from gs_quant.api.gs.hedges import GsHedgeApi >>> response = GsHedgeApi.share_hedge_group( ... hedge_group_id="HEDGE123", ... strategy_request=strategy_request, ... optimization_response=optimization_response, ... hedge_name="Factor Hedge", ... group_name="My Hedge Group", ... view_emails=["user1@example.com", "user2@example.com"] ... ) """ from gs_quant.entities.entitlements import User url = f"/hedges/groups/{hedge_group_id}" try: # First, GET the current hedge group to retrieve existing metadata hedge_group_data = GsSession.current.sync.get(url) # Get current user's GUID from existing entitlements current_user_guid = hedge_group_data.get('ownerId', '') if current_user_guid: current_user_guid = f"guid:{current_user_guid}" # Build updated entitlements, preserving owner's access entitlements = hedge_group_data.get('entitlements', {}) # Ensure owner always has access if current_user_guid: # Admin access if 'admin' not in entitlements: entitlements['admin'] = [] if current_user_guid not in entitlements['admin']: entitlements['admin'].append(current_user_guid) # Edit access if 'edit' not in entitlements: entitlements['edit'] = [] if current_user_guid not in entitlements['edit']: entitlements['edit'].append(current_user_guid) # View access if 'view' not in entitlements: entitlements['view'] = [] if current_user_guid not in entitlements['view']: entitlements['view'].append(current_user_guid) # Convert emails to user GUIDs and add view access if view_emails: view_users = User.get_many(emails=view_emails) for user in view_users: user_guid = f"guid:{user.id}" if user_guid not in entitlements['view']: entitlements['view'].append(user_guid) # Convert emails to user GUIDs and add admin access (admin users also need edit and view access) if admin_emails: admin_users = User.get_many(emails=admin_emails) for user in admin_users: user_guid = f"guid:{user.id}" if user_guid not in entitlements['admin']: entitlements['admin'].append(user_guid) if user_guid not in entitlements['edit']: entitlements['edit'].append(user_guid) if user_guid not in entitlements['view']: entitlements['view'].append(user_guid) # Build the complete payload with updated entitlements payload = { "active": True, "entitlements": entitlements, "hedges": [ { "entitlements": entitlements, "id": hedge_group_data.get('hedgeIds', [])[0] if hedge_group_data.get('hedgeIds') else None, "name": hedge_name, "objective": strategy_request.get("objective", "Minimize Factor Risk"), "parameters": strategy_request.get("parameters", {}), "result": optimization_response.get("result", {}), } ], "id": hedge_group_id, "name": group_name, "objective": strategy_request.get("objective", "Minimize Factor Risk"), "ownerId": hedge_group_data.get('ownerId', ''), "createdById": hedge_group_data.get('createdById', ''), "createdTime": hedge_group_data.get('createdTime', ''), "lastUpdatedById": hedge_group_data.get('lastUpdatedById', ''), "lastUpdatedTime": hedge_group_data.get('lastUpdatedTime', ''), "hedgeIds": hedge_group_data.get('hedgeIds', []), } # PUT the updated hedge group back result = GsSession.current.sync.put(url, payload) print("Hedge group shared successfully!") print(f" Hedge Group ID: {hedge_group_id}") print("\n Updated Entitlements:") print(f" View Access: {len(result.get('entitlements', {}).get('view', []))} users/groups") print(f" Admin Access: {len(result.get('entitlements', {}).get('admin', []))} users/groups") return result except Exception as e: print(f"Failed to share hedge: {e}") raise print("Hedge group shared successfully!") print(f" Hedge Group ID: {hedge_group_id}") print("\n Updated Entitlements:") print(f" View Access: {len(result.get('entitlements', {}).get('view', []))} users/groups") print(f" Admin Access: {len(result.get('entitlements', {}).get('admin', []))} users/groups") return result except Exception as e: print(f"✗ Failed to share hedge: {e}") raise ================================================ FILE: gs_quant/api/gs/indices.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from typing import Dict, List, Union import backoff from gs_quant.api.gs.assets import IdList from gs_quant.common import PositionType from gs_quant.errors import MqTimeoutError, MqInternalServerError, MqRateLimitedError from gs_quant.session import GsSession from gs_quant.target.indices import ( CustomBasketsCreateInputs, CustomBasketsRebalanceInputs, CustomBasketsRebalanceAction, CustomBasketsResponse, CustomBasketsEditInputs, CustomBasketsBackcastInputs, CustomBasketsRiskScheduleInputs, CustomBasketRiskParams, ISelectResponse, ISelectRequest, ISelectRebalance, ISelectActionRequest, IndicesDynamicConstructInputs, IndicesRebalanceInputs, IndicesEditInputs, DynamicConstructionResponse, ApprovalCustomBasketResponse, IndicesBackcastInputs, ) # type aliases -- can add for edit/backcast/etc. if STS implements them in future CreateRequest = Union[CustomBasketsCreateInputs, IndicesDynamicConstructInputs] CreateRepsonse = Union[CustomBasketsResponse, DynamicConstructionResponse] RebalanceRequest = Union[CustomBasketsRebalanceInputs, ISelectRebalance, ISelectRequest] RebalanceResponse = Union[CustomBasketsResponse, ISelectResponse] RebalanceCancelRequest = Union[CustomBasketsRebalanceAction, ISelectActionRequest] RebalanceCancelResponse = Union[Dict, ISelectResponse] ValidatedRequest = Union[CreateRequest, RebalanceRequest] class GsIndexApi: """GS Index API client implementation""" _response_cls = { CustomBasketsCreateInputs: CustomBasketsResponse, IndicesDynamicConstructInputs: DynamicConstructionResponse, CustomBasketsRebalanceInputs: CustomBasketsResponse, ISelectRebalance: ISelectResponse, ISelectRequest: ISelectResponse, CustomBasketsRebalanceAction: Dict, ISelectActionRequest: ISelectResponse, } @classmethod def create(cls, inputs: CreateRequest) -> CreateRepsonse: """Create new basket or iselect strategy""" response_cls = cls._response_cls[type(inputs)] return GsSession.current.sync.post('/indices', payload=inputs, cls=response_cls) @classmethod def edit(cls, id_: str, inputs: CustomBasketsEditInputs) -> CustomBasketsResponse: """Update basket metadata""" url = f'/indices/{id_}/edit' inputs = IndicesEditInputs(parameters=inputs) return GsSession.current.sync.post(url, payload=inputs, cls=CustomBasketsResponse) @classmethod def rebalance(cls, id_: str, inputs: RebalanceRequest) -> RebalanceResponse: """Rebalance existing index with new composition""" url = f'/indices/{id_}/rebalance' response_cls = cls._response_cls[type(inputs)] inputs = IndicesRebalanceInputs(parameters=inputs) if not isinstance(inputs, ISelectRequest) else inputs return GsSession.current.sync.post(url, payload=inputs, cls=response_cls) @classmethod def cancel_rebalance(cls, id_: str, inputs: RebalanceCancelRequest) -> RebalanceCancelResponse: """Cancel most recent rebalance submission if not yet approved""" url = f'/indices/{id_}/rebalance/cancel' response_cls = cls._response_cls[type(inputs)] return GsSession.current.sync.post(url, payload=inputs, cls=response_cls) @classmethod def last_rebalance_data(cls, id_: str) -> Dict: """Get latest basket rebalance data""" url = f'/indices/{id_}/rebalance/data/last' return GsSession.current.sync.get(url) @classmethod def last_rebalance_approval(cls, id_: str) -> ApprovalCustomBasketResponse: """Get latest basket rebalance approval info""" url = f'/indices/{id_}/rebalance/approvals/last' return GsSession.current.sync.get(url, cls=ApprovalCustomBasketResponse) @classmethod def initial_price(cls, id_: str, date: dt.date) -> Dict: """Get initial basket price""" url = f'/indices/{id_}/rebalance/initialprice/{date.isoformat()}' return GsSession.current.sync.get(url) @classmethod def validate_ticker(cls, ticker: str): """Validate basket ticker""" url = '/indices/validate' GsSession.current.sync.post(url, payload={'ticker': ticker}) @classmethod def backcast(cls, _id: str, inputs: CustomBasketsBackcastInputs) -> CustomBasketsResponse: """Backcast basket composition history before live date""" url = f'/indices/{_id}/backcast' inputs = IndicesBackcastInputs(parameters=inputs) return GsSession.current.sync.post(url, payload=inputs, cls=CustomBasketsResponse, timeout=240) @classmethod def update_risk_reports(cls, _id: str, inputs: CustomBasketRiskParams): """Create, modify, or delete a custom basket factor risk report""" url = f'/indices/{_id}/risk/reports' inputs = CustomBasketsRiskScheduleInputs(risk_models=inputs) return GsSession.current.sync.post(url, payload=inputs) @staticmethod @backoff.on_exception(lambda: backoff.expo(base=2, factor=2), (MqTimeoutError, MqInternalServerError), max_tries=5) @backoff.on_exception(lambda: backoff.constant(90), MqRateLimitedError, max_tries=5) def get_positions_data( asset_id: str, start_date: dt.date, end_date: dt.date, fields: IdList = None, position_type: PositionType = None, ) -> List[dict]: start_date_str = start_date.isoformat() end_date_str = end_date.isoformat() url = '/indices/{id}/positions/data?startDate={start_date}&endDate={end_date}'.format( id=asset_id, start_date=start_date_str, end_date=end_date_str ) if fields is not None: url += '&fields='.join([''] + fields) if position_type is not None: url += '&type=' + position_type.value results = GsSession.current.sync.get(url)['results'] return results @staticmethod def get_last_positions_data( asset_id: str, fields: IdList = None, position_type: PositionType = None, ) -> List[dict]: url = f'/indices/{asset_id}/positions/last/data' params = '' if fields is not None: params += '&fields='.join([''] + fields) if position_type is not None: params += '&type=' + position_type.value if len(params): url = f'{url}?{params}' results = GsSession.current.sync.get(url)['results'] return results ================================================ FILE: gs_quant/api/gs/monitors.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import logging from typing import Tuple from urllib.parse import urlencode from gs_quant.session import GsSession from gs_quant.target.monitor import Monitor, MonitorResponseData _logger = logging.getLogger(__name__) class GsMonitorsApi: """GS Monitor API client implementation""" @classmethod def get_monitors( cls, limit: int = 100, monitor_id: str = None, owner_id: str = None, name: str = None, folder_name: str = None, monitor_type: str = None, tags: str = None, ) -> Tuple[Monitor, ...]: query_string = urlencode( dict( filter( lambda item: item[1] is not None, dict( id=monitor_id, ownerId=owner_id, name=name, folderName=folder_name, type=monitor_type, tags=tags, limit=limit, ).items(), ) ) ) return GsSession.current.sync.get('/monitors?{query}'.format(query=query_string), cls=Monitor)['results'] @classmethod def get_monitor(cls, monitor_id: str) -> Monitor: return GsSession.current.sync.get('/monitors/{id}'.format(id=monitor_id), cls=Monitor) @classmethod def create_monitor(cls, monitor: Monitor) -> Monitor: request_headers = {'Content-Type': 'application/json;charset=utf-8'} return GsSession.current.sync.post('/monitors', monitor, request_headers=request_headers, cls=Monitor) @classmethod def update_monitor(cls, monitor: Monitor): request_headers = {'Content-Type': 'application/json;charset=utf-8'} return GsSession.current.sync.put( '/monitors/{id}'.format(id=monitor.id), monitor, request_headers=request_headers, cls=Monitor ) @classmethod def delete_monitor(cls, monitor_id: str) -> dict: return GsSession.current.sync.delete('/monitors/{id}'.format(id=monitor_id)) @classmethod def calculate_monitor(cls, monitor_id) -> MonitorResponseData: return GsSession.current.sync.get('/monitors/{id}/data'.format(id=monitor_id), cls=MonitorResponseData) ================================================ FILE: gs_quant/api/gs/parser.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import logging from gs_quant.session import GsSession _logger = logging.getLogger(__name__) class GsParserApi: """GS instrument parser API client implementation""" @classmethod def get_instrument_from_text_asset_class(cls, text: str, asset_class: str) -> dict: res = GsSession.current.sync.post('/parser/quoteTicket', payload={'message': text, 'assetClass': asset_class}) return res['ticket']['quote']['instrument'] @classmethod def get_instrument_from_text(cls, text: str) -> dict: res = GsSession.current.sync.post('/parser/portfolio', payload={'message': text}) return res['instruments'] ================================================ FILE: gs_quant/api/gs/plots.py ================================================ """ Copyright 2020 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from collections.abc import Iterable from typing import Tuple, Union, Sequence from gs_quant.datetime.relative_date import RelativeDate from gs_quant.session import GsSession from gs_quant.target.charts import Chart, ChartShare import datetime as dt from gs_quant.common import TimeFilter class GsPlotApi: """GS Chart API client implementation""" # CRUD @classmethod def get_many_charts(cls, limit: int = 100) -> Tuple[Chart, ...]: return GsSession.current.sync.get(f'/charts?limit={limit}', cls=Chart)['results'] @classmethod async def get_many_charts_async(cls, limit: int = 100) -> Tuple[Chart, ...]: return (await GsSession.current.async_.get(f'/charts?limit={limit}', cls=Chart))['results'] @classmethod def get_chart(cls, chart_id: str) -> Chart: return GsSession.current.sync.get(f'/charts/{chart_id}', cls=Chart) @classmethod async def get_chart_async(cls, chart_id: str) -> Chart: return await GsSession.current.async_.get(f'/charts/{chart_id}', cls=Chart) @classmethod def create_chart(cls, chart: Chart) -> Chart: return GsSession.current.sync.post('/charts', chart, cls=Chart) @classmethod async def create_chart_async(cls, chart: Chart) -> Chart: return await GsSession.current.async_.post('/charts', chart, cls=Chart) @classmethod def update_chart(cls, chart: Chart): return GsSession.current.sync.put(f'/charts/{chart.id}', chart, cls=Chart) @classmethod async def update_chart_async(cls, chart: Chart): return await GsSession.current.async_.put(f'/charts/{chart.id}', chart, cls=Chart) @classmethod def delete_chart(cls, chart_id: str) -> dict: return GsSession.current.sync.delete(f'/charts/{chart_id}') @classmethod async def delete_chart_async(cls, chart_id: str) -> dict: return await GsSession.current.async_.delete(f'/charts/{chart_id}') # Additional methods @staticmethod def _build_plot_payload( expressions: Sequence[str], start: Union[dt.date, dt.datetime, RelativeDate, str] = RelativeDate("-1y"), end: Union[dt.date, dt.datetime, RelativeDate, str] = RelativeDate("-1b"), real_time: bool = False, *, statistics: bool = False, interval: str = None, time_filter: TimeFilter = None, ) -> dict: start = start.apply_rule() if isinstance(start, RelativeDate) else start end = end.apply_rule() if isinstance(end, RelativeDate) else end expressions = [expressions] if isinstance(expressions, str) else expressions if real_time and ( (isinstance(start, dt.date) and not isinstance(start, dt.datetime)) or (isinstance(end, dt.date) and not isinstance(end, dt.datetime)) ): raise ValueError("Real-time plots require start and end to be datetimes, not dates.") return { "expressions": expressions, "statistics": statistics, "realTime": real_time, "interval": interval or ("1m" if real_time else "1D"), **({"startTime": start, "endTime": end} if real_time else {"startDate": start, "endDate": end}), **({"timeFilter": time_filter} if time_filter else {}), } @classmethod async def plot_runner_async( cls, expressions: Sequence[str], start: Union[dt.date, dt.datetime, RelativeDate, str] = RelativeDate("-1y"), end: Union[dt.date, dt.datetime, RelativeDate, str] = RelativeDate("-1b"), real_time: bool = False, *, statistics: bool = False, interval: str = None, time_filter: TimeFilter = None, ) -> dict: payload = cls._build_plot_payload( expressions, start, end, real_time, statistics=statistics, interval=interval, time_filter=time_filter ) return await GsSession.current.async_.post('/plots/runner', payload) @classmethod def plot_runner( cls, expressions: Sequence[str], start: Union[dt.date, dt.datetime, RelativeDate, str] = RelativeDate("-1y"), end: Union[dt.date, dt.datetime, RelativeDate, str] = RelativeDate("-1b"), real_time: bool = False, *, statistics: bool = False, interval: str = None, time_filter: TimeFilter = None, ) -> dict: payload = cls._build_plot_payload( expressions, start, end, real_time, statistics=statistics, interval=interval, time_filter=time_filter ) return GsSession.current.sync.post('/plots/runner', payload) @classmethod def share_chart(cls, chart_id: str, users: Iterable): # endpoint silently discards tokens not prefixed with 'guid:' => can't be used for roles, groups, etc. if any(map(lambda x: not x.startswith('guid:'), users)): raise ValueError('Chart can only be shared with individual users via this method.') chart = cls.get_chart(chart_id) share = ChartShare(tuple(users), chart.version) return GsSession.current.sync.post(f'/charts/{chart_id}/share', share, cls=Chart) @classmethod async def share_chart_async(cls, chart_id: str, users: Iterable): # endpoint silently discards tokens not prefixed with 'guid:' => can't be used for roles, groups, etc. if any(map(lambda x: not x.startswith('guid:'), users)): raise ValueError('Chart can only be shared with individual users via this method.') chart = await cls.get_chart_async(chart_id) share = ChartShare(tuple(users), chart.version) return await GsSession.current.async_.post(f'/charts/{chart_id}/share', share, cls=Chart) ================================================ FILE: gs_quant/api/gs/portfolios.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import backoff import deprecation import logging from time import sleep from typing import Tuple, Union, List, Dict from gs_quant.api.api_session import ApiWithCustomSession from gs_quant.common import PositionType, PositionTag from gs_quant.common import RiskRequest, Currency from gs_quant.errors import MqInternalServerError, MqTimeoutError, MqRateLimitedError from gs_quant.instrument import Instrument from gs_quant.session import GsSession from gs_quant.target.portfolios import Portfolio, Position, PositionSet, PortfolioTree from gs_quant.target.reports import Report from gs_quant.target.risk_models import RiskModelTerm as Term from gs_quant.workflow import WorkflowPosition, WorkflowPositionsResponse, SaveQuoteRequest _logger = logging.getLogger(__name__) class GsPortfolioApi(ApiWithCustomSession): """GS Asset API client implementation. To pass additional query parameters, use kwargs.""" @classmethod def get_portfolios( cls, portfolio_ids: List[str] = None, portfolio_names: List[str] = None, limit: int = 100, **kwargs ) -> Tuple[Portfolio, ...]: url = '/portfolios?' if portfolio_ids: url += f'&id={"&id=".join(portfolio_ids)}' if portfolio_names: url += f'&name={"&name=".join(portfolio_names)}' for k, v in kwargs.items(): if isinstance(v, list): for i in v: # in case v is not a list of strings url += f'&{k}={i}' else: url += f'&{k}={v}' return GsSession.current.sync.get(f'{url}&limit={limit}', cls=Portfolio)['results'] @classmethod def get_portfolio(cls, portfolio_id: str) -> Portfolio: return GsSession.current.sync.get('/portfolios/{id}'.format(id=portfolio_id), cls=Portfolio) @classmethod def get_portfolio_by_name(cls, name: str) -> Portfolio: ret = cls.get_session().sync.get('/portfolios?name={}'.format(name)) num_found = ret.get('totalResults', 0) if num_found == 0: raise ValueError('Portfolio {} not found'.format(name)) elif num_found > 1: raise ValueError('More than one portfolio named {} found'.format(name)) else: return Portfolio.from_dict(ret['results'][0]) @classmethod def create_portfolio(cls, portfolio: Portfolio) -> Portfolio: return GsSession.current.sync.post('/portfolios', portfolio, cls=Portfolio) @classmethod def update_portfolio(cls, portfolio: Portfolio): return GsSession.current.sync.put('/portfolios/{id}'.format(id=portfolio.id), portfolio, cls=Portfolio) @classmethod def delete_portfolio(cls, portfolio_id: str) -> dict: return GsSession.current.sync.delete('/portfolios/{id}'.format(id=portfolio_id)) # manage portfolio positions @classmethod def get_portfolio_analyze(cls, portfolio_id: str) -> dict: url = '/portfolios/{id}/analyze'.format(id=portfolio_id) res = GsSession.current.sync.get(url) return res @classmethod @backoff.on_exception(lambda: backoff.expo(base=2, factor=2), (MqTimeoutError, MqInternalServerError), max_tries=5) @backoff.on_exception(lambda: backoff.constant(90), MqRateLimitedError, max_tries=5) def get_positions( cls, portfolio_id: str, start_date: dt.date = None, end_date: dt.date = None, position_type: str = 'close' ) -> Tuple[PositionSet, ...]: url = '/portfolios/{id}/positions?type={positionType}'.format(id=portfolio_id, positionType=position_type) if start_date is not None: url += '&startDate={sd}'.format(sd=start_date.isoformat()) if end_date is not None: url += '&endDate={sd}'.format(sd=end_date.isoformat()) res = GsSession.current.sync.get(url) return tuple(PositionSet.from_dict(v) for v in res.get('positionSets', ())) @classmethod def get_positions_for_date( cls, portfolio_id: str, position_date: dt.date, position_type: str = 'close' ) -> PositionSet: url = '/portfolios/{id}/positions/{date}?type={ptype}'.format( id=portfolio_id, date=position_date.isoformat(), ptype=position_type ) position_sets = GsSession.current.sync.get(url, cls=PositionSet)['results'] return position_sets[0] if len(position_sets) > 0 else None @classmethod def get_position_set_by_position_type( cls, positions_type: str, positions_id: str, activity_type: str = 'position' ) -> Tuple[PositionSet, ...]: root = 'deals' if positions_type == 'ETI' else 'books/' + positions_type if activity_type != 'position': url = '/risk-internal/{}/{}/positions?activityType={}'.format(root, positions_id, activity_type) else: url = '/risk-internal/{}/{}/positions'.format(root, positions_id) results = cls.get_session().sync.get(url, timeout=181) return tuple(cls._unpack_position_set(res) for res in results['positionSets']) @classmethod def _unpack_position_set(cls, kvs: dict) -> PositionSet: position_set = PositionSet.from_dict(kvs) # NY - this is rather unfortunate: we end up with instruments all with the name of the ETI for position in position_set.positions: position.instrument.name = None return position_set @classmethod def get_instruments_by_position_type( cls, positions_type: str, positions_id: str, activity_type: str ) -> Tuple[Instrument, ...]: position_sets = cls.get_position_set_by_position_type( positions_type=positions_type, positions_id=positions_id, activity_type=activity_type ) instruments = [] for position_set in position_sets: for positions in position_set.positions: instrument = positions.instrument instrument.metadata = { 'trade_date': position_set.position_date, 'tags': positions.tags, 'external_ids': {id_['idType']: id_['idValue'] for id_ in positions.external_ids}, 'party_from': positions.party_from, 'party_to': positions.party_to, } instruments.append(instrument) return tuple(instruments) @classmethod def get_latest_positions(cls, portfolio_id: str, position_type: str = 'close') -> Union[PositionSet, dict]: url = '/portfolios/{id}/positions/last?type={ptype}'.format(id=portfolio_id, ptype=position_type) results = GsSession.current.sync.get(url)['results'] # Annoyingly, different types are returned depending on position_type if isinstance(results, dict) and 'positions' in results: results['positions'] = tuple(Position.from_dict(p) for p in results['positions']) return PositionSet.from_dict(results) @classmethod def get_instruments_by_workflow_id( cls, workflow_id: str, prefer_instruments: bool = False ) -> Tuple[Instrument, ...]: root = 'quote' url = '/risk{}/{}/{}'.format('-internal' if not prefer_instruments else '', root, workflow_id) results = cls.get_session().sync.get(url, timeout=181) instruments = [] for position in results.get('workflowPositions').get(workflow_id): for insts in position['positions']: instrument_values = insts['instrument'] instrument = Instrument.from_dict(instrument_values) name = instrument_values.get('name') if name: instrument.name = name instruments.append(instrument) return tuple(instruments) @classmethod def get_position_dates(cls, portfolio_id: str) -> Tuple[dt.date, ...]: position_dates = cls.get_session().sync.get('/portfolios/{id}/positions/dates'.format(id=portfolio_id))[ 'results' ] return tuple(dt.datetime.strptime(d, '%Y-%m-%d').date() for d in position_dates) @classmethod @backoff.on_exception(lambda: backoff.expo(base=2, factor=2), (MqTimeoutError, MqInternalServerError), max_tries=5) @backoff.on_exception(lambda: backoff.constant(90), MqRateLimitedError, max_tries=5) def update_positions(cls, portfolio_id: str, position_sets: List[PositionSet], net_positions: bool = True) -> float: url = f'/portfolios/{portfolio_id}/positions?netPositions={str(net_positions).lower()}' return GsSession.current.sync.put(url, position_sets) @classmethod @backoff.on_exception(lambda: backoff.expo(base=2, factor=2), (MqTimeoutError, MqInternalServerError), max_tries=5) @backoff.on_exception(lambda: backoff.constant(90), MqRateLimitedError, max_tries=5) def get_positions_data( cls, portfolio_id: str, start_date: dt.date, end_date: dt.date, fields: List[str] = None, performance_report_id: str = None, position_type: PositionType = None, include_all_business_days: bool = False, ) -> List[dict]: start_date_str = start_date.isoformat() end_date_str = end_date.isoformat() url = f'/portfolios/{portfolio_id}/positions/data?startDate={start_date_str}&endDate={end_date_str}' if fields is not None: url += '&fields='.join([''] + fields) if performance_report_id is not None: url += f'&reportId={performance_report_id}' if position_type is not None: url += '&type=' + position_type.value if include_all_business_days: url += '&includeAllBusinessDays=true' return GsSession.current.sync.get(url)['results'] @classmethod def update_quote(cls, quote_id: str, request: RiskRequest): return cls.get_session().sync.put('/risk-internal/quote/save/{id}'.format(id=quote_id), request) @classmethod def save_quote(cls, request: RiskRequest) -> str: return cls.get_session().sync.post('/risk-internal/quote/save', request)['results'] @classmethod def update_workflow_quote(cls, quote_id: str, request: SaveQuoteRequest): headers = {'Content-Type': 'application/x-msgpack'} return cls.get_session().sync.put( '/risk-internal/quote/workflow/save/{id}'.format(id=quote_id), tuple([request]), request_headers=headers )['results'] @classmethod def save_workflow_quote(cls, request: SaveQuoteRequest) -> str: headers = {'Content-Type': 'application/x-msgpack'} return cls.get_session().sync.post( '/risk-internal/quote/workflow/save', tuple([request]), request_headers=headers )['results'] @classmethod def share_workflow_quote(cls, request: SaveQuoteRequest) -> str: headers = {'Content-Type': 'application/x-msgpack'} return cls.get_session().sync.post( '/risk-internal/quote/workflow/share', tuple([request]), request_headers=headers )['results'] @classmethod def get_workflow_quote(cls, workflow_id: str) -> Tuple[WorkflowPosition, ...]: url = f'/risk-internal/quote/workflow/{workflow_id}' results = cls.get_session().sync.get(url, timeout=181) wf_pos_res = WorkflowPositionsResponse.from_dict(results) if wf_pos_res: return wf_pos_res.results else: return () @classmethod def get_shared_workflow_quote(cls, workflow_id: str) -> Tuple[WorkflowPosition, ...]: url = f'/risk-internal/quote/workflow/shared/{workflow_id}' results = cls.get_session().sync.get(url, timeout=181) wf_pos_res = WorkflowPositionsResponse.from_dict(results) if wf_pos_res: return wf_pos_res.results else: return () @classmethod def save_to_shadowbook(cls, request: RiskRequest, name: str) -> str: return cls.get_session().sync.put(f'/risk-internal/shadowbook/save/{name}', request)['results'] @classmethod def get_risk_models_by_coverage(cls, portfolio_id: str, term: Term = Term.Medium): return cls.get_session().sync.get(f'/portfolios/{portfolio_id}/models?sortByTerm={term.value}')['results'] @classmethod @backoff.on_exception(lambda: backoff.expo(base=2, factor=2), (MqTimeoutError, MqInternalServerError), max_tries=5) @backoff.on_exception(lambda: backoff.constant(90), MqRateLimitedError, max_tries=5) def get_reports(cls, portfolio_id: str, tags: Dict) -> Tuple[Report, ...]: results = cls.get_session().sync.get('/portfolios/{id}/reports'.format(id=portfolio_id), cls=Report)['results'] if tags is not None: tags_as_list = tuple(PositionTag(name=key, value=tags[key]) for key in tags) results = [r for r in results if r.parameters.tags == tags_as_list] return results @classmethod @backoff.on_exception(lambda: backoff.expo(base=2, factor=2), (MqTimeoutError, MqInternalServerError), max_tries=5) @backoff.on_exception(lambda: backoff.constant(90), MqRateLimitedError, max_tries=5) def schedule_reports( cls, portfolio_id: str, start_date: dt.date = None, end_date: dt.date = None, backcast: bool = False ) -> dict: payload = {'parameters': {'backcast': backcast}} if start_date is not None: payload['startDate'] = start_date.isoformat() if end_date is not None: payload['endDate'] = end_date.isoformat() portfolio = cls.get_portfolio(portfolio_id) if portfolio.tag_name_hierarchy is None or len(portfolio.tag_name_hierarchy) == 0: cls.get_session().sync.post(f'/portfolios/{portfolio_id}/schedule', payload) else: count = 10 for report_id in portfolio.report_ids: if count == 0: sleep(2) count = 10 cls.get_session().sync.post(f'/reports/{report_id}/schedule', payload) else: cls.get_session().sync.post(f'/reports/{report_id}/schedule', payload) count -= 1 @classmethod @backoff.on_exception(lambda: backoff.expo(base=2, factor=2), (MqTimeoutError, MqInternalServerError), max_tries=5) @backoff.on_exception(lambda: backoff.constant(90), MqRateLimitedError, max_tries=5) def get_schedule_dates(cls, portfolio_id: str, backcast: bool = False) -> List[dt.date]: results = cls.get_session().sync.get( f'/portfolios/{portfolio_id}/schedule/dates?backcast={str(backcast).lower()}' ) return [ dt.datetime.strptime(results['startDate'], '%Y-%m-%d').date(), dt.datetime.strptime(results['endDate'], '%Y-%m-%d').date(), ] @classmethod @deprecation.deprecated( deprecated_in='1.0.10', details='GsPortfolioApi.get_custom_aum is now deprecated, please use GsReportApi.get_custom_aum instead.', ) def get_custom_aum(cls, portfolio_id: str, start_date: dt.date = None, end_date: dt.date = None) -> dict: url = f'/portfolios/{portfolio_id}/aum?' if start_date: url += f"&startDate={start_date.strftime('%Y-%m-%d')}" if end_date: url += f"&endDate={end_date.strftime('%Y-%m-%d')}" return GsSession.current.sync.get(url)['data'] @classmethod @deprecation.deprecated( deprecated_in='1.0.10', details='GsPortfolioApi.upload_custom_aum is now deprecated, please use GsReportApi.upload_custom_aum instead.', ) def upload_custom_aum(cls, portfolio_id: str, aum_data: List[Dict], clear_existing_data: bool = None) -> dict: url = f'/portfolios/{portfolio_id}/aum' payload = {'data': aum_data} if clear_existing_data: url += '?clearExistingData=true' return GsSession.current.sync.post(url, payload) @classmethod def update_portfolio_tree(cls, portfolio_id: str): return GsSession.current.sync.post(f'/portfolios/{portfolio_id}/tree', {}) @classmethod def get_portfolio_tree(cls, portfolio_id: str): return GsSession.current.sync.get(f'/portfolios/{portfolio_id}/tree', cls=PortfolioTree) @classmethod def get_attribution( cls, portfolio_id: str, start_date: dt.date = None, end_date: dt.date = None, currency: Currency = None, performance_report_id: str = None, ) -> Dict: url = f'/attribution/{portfolio_id}?' if start_date: url += f"&startDate={start_date.strftime('%Y-%m-%d')}" if end_date: url += f"&endDate={end_date.strftime('%Y-%m-%d')}" if currency: url += f"¤cy={currency.value}" if performance_report_id: url += f'&reportId={performance_report_id}' return GsSession.current.sync.get(url)['results'] ================================================ FILE: gs_quant/api/gs/price.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import backoff from gs_quant.errors import MqRateLimitedError, MqTimeoutError, MqInternalServerError from gs_quant.session import GsSession from gs_quant.target.positions_v2_pricing import PositionsPricingRequest from gs_quant.target.price import PositionSetPriceInput, PositionSetPriceResponse class GsPriceApi: """GS Price API client implementation""" @classmethod @backoff.on_exception(lambda: backoff.expo(base=2, factor=2), (MqTimeoutError, MqInternalServerError), max_tries=5) @backoff.on_exception(lambda: backoff.constant(60), MqRateLimitedError, max_tries=5) def price_positions(cls, inputs: PositionSetPriceInput) -> PositionSetPriceResponse: url = '/price/positions' return GsSession.current.sync.post(url, payload=inputs, cls=PositionSetPriceResponse) @classmethod def price_many_positions(cls, pricing_request: PositionsPricingRequest) -> dict: url = '/positions/price/bulk' GsSession.current.api_version = "v2" pricing_response = GsSession.current.sync.post(url, payload=pricing_request) GsSession.current.api_version = "v1" positions = pricing_response.get("positions") return positions ================================================ FILE: gs_quant/api/gs/reports.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import logging import urllib.parse from enum import Enum from typing import Tuple, List, Dict import backoff from gs_quant.base import EnumBase from gs_quant.common import Currency, PositionTag from gs_quant.errors import MqTimeoutError, MqInternalServerError, MqRateLimitedError from gs_quant.session import GsSession from gs_quant.target.reports import Report _logger = logging.getLogger(__name__) class OrderType(EnumBase, Enum): """Source object for position data""" Ascending = 'Ascending' Descending = 'Descending' class FactorRiskTableMode(EnumBase, Enum): """Source object for position data""" Pnl = 'Pnl' Exposure = 'Exposure' ZScore = 'ZScore' Mctr = 'Mctr' class GsReportApi: """GS Reports API client implementation""" @classmethod def create_report(cls, report: Report) -> Report: return GsSession.current.sync.post('/reports', report, cls=Report) @classmethod def get_report(cls, report_id: str) -> Report: return GsSession.current.sync.get('/reports/{id}'.format(id=report_id), cls=Report) @classmethod def get_reports( cls, limit: int = 100, offset: int = None, position_source_type: str = None, position_source_id: str = None, status: str = None, report_type: str = None, order_by: str = None, tags: Dict = None, scroll: str = None, ) -> Tuple[Report, ...]: def build_url(scroll_id=None): url = f'/reports?limit={limit}' if scroll: url += '&scroll={scroll}'.format(scroll=scroll) if scroll_id: url += f'&scrollId={scroll_id}' if offset: url += '&offset={offset}'.format(offset=offset) if position_source_type: url += f'&positionSourceType={position_source_type}' if position_source_id: url += f'&positionSourceId={position_source_id}' if status: url += f'&status={status}' if report_type: url += f'&reportType={urllib.parse.quote(report_type)}' if order_by: url += f'&orderBy={order_by}' return url response = GsSession.current.sync.get(build_url(), cls=Report) results = response.get('results', []) while response.get('scrollId') and response.get('results'): response = GsSession.current.sync.get(build_url(scroll_id=response.get('scrollId')), cls=Report) results += response.get('results', []) if tags is not None: tags_as_list = tuple(PositionTag(name=key, value=tags[key]) for key in tags) results = [r for r in results if r.parameters.tags == tags_as_list] else: results = [r for r in results if r.parameters.tags is None] return tuple(results) @classmethod def update_report(cls, report: Report) -> dict: return GsSession.current.sync.put('/reports/{id}'.format(id=report.id), report, cls=Report) @classmethod def delete_report(cls, report_id: str) -> dict: return GsSession.current.sync.delete('/reports/{id}'.format(id=report_id)) @classmethod @backoff.on_exception(lambda: backoff.expo(base=2, factor=2), (MqTimeoutError, MqInternalServerError), max_tries=5) @backoff.on_exception(lambda: backoff.constant(90), MqRateLimitedError, max_tries=5) def schedule_report(cls, report_id: str, start_date: dt.date, end_date: dt.date, backcast: bool = False) -> dict: report_schedule_request = { 'startDate': start_date.strftime('%Y-%m-%d'), 'endDate': end_date.strftime('%Y-%m-%d'), } if backcast: report_schedule_request['parameters'] = {'backcast': backcast} return GsSession.current.sync.post('/reports/{id}/schedule'.format(id=report_id), report_schedule_request) @classmethod def get_report_status(cls, report_id: str) -> Tuple[dict, ...]: return GsSession.current.sync.get('/reports/{id}/status'.format(id=report_id)) @classmethod def get_report_jobs(cls, report_id: str) -> Tuple[dict, ...]: return GsSession.current.sync.get('/reports/{id}/jobs'.format(id=report_id))['results'] @classmethod def get_report_job(cls, report_job_id: str) -> dict: return GsSession.current.sync.get('/reports/jobs/{report_job_id}'.format(report_job_id=report_job_id)) @classmethod def reschedule_report_job(cls, report_job_id: str): return GsSession.current.sync.post(f'/reports/jobs/{report_job_id}/reschedule', {}) @classmethod def cancel_report_job(cls, report_job_id: str) -> dict: return GsSession.current.sync.post('/reports/jobs/{report_job_id}/cancel'.format(report_job_id=report_job_id)) @classmethod def update_report_job(cls, report_job_id: str, status: str) -> dict: status_body = {"status": '{status}'.format(status=status)} return GsSession.current.sync.post( '/reports/jobs/{report_job_id}/update'.format(report_job_id=report_job_id), status_body ) @classmethod def get_custom_aum(cls, report_id: str, start_date: dt.date = None, end_date: dt.date = None) -> dict: url = f'/reports/{report_id}/aum?' if start_date: url += f"&startDate={start_date.strftime('%Y-%m-%d')}" if end_date: url += f"&endDate={end_date.strftime('%Y-%m-%d')}" return GsSession.current.sync.get(url)['data'] @classmethod def upload_custom_aum(cls, report_id: str, aum_data: List[dict], clear_existing_data: bool = None) -> dict: url = f'/reports/{report_id}/aum' payload = {'data': aum_data} if clear_existing_data: url += '?clearExistingData=true' return GsSession.current.sync.post(url, payload) @classmethod @backoff.on_exception(lambda: backoff.expo(base=2, factor=2), (MqTimeoutError, MqInternalServerError), max_tries=5) @backoff.on_exception(lambda: backoff.constant(90), MqRateLimitedError, max_tries=5) def get_factor_risk_report_results( cls, risk_report_id: str, view: str = None, factors: List[str] = None, factor_categories: List[str] = None, currency: Currency = None, start_date: dt.date = None, end_date: dt.date = None, unit: str = None, ) -> dict: url = f'/risk/factors/reports/{risk_report_id}/results?' if view is not None: url += f'&view={view}' if factors is not None: factors = map(urllib.parse.quote, factors) # to support factors like "Automobiles & Components" url += f'&factors={"&factors=".join(factors)}' if factor_categories is not None: url += f'&factorCategories={"&factorCategories=".join(factor_categories)}' if currency is not None: url += f'¤cy={currency.value}' if start_date is not None: url += f'&startDate={start_date.strftime("%Y-%m-%d")}' if end_date is not None: url += f'&endDate={end_date.strftime("%Y-%m-%d")}' if unit is not None: url += f'&unit={unit}' return GsSession.current.sync.get(url) @classmethod def get_factor_risk_report_view( cls, risk_report_id: str, factor: str = None, factor_category: str = None, currency: Currency = None, start_date: dt.date = None, end_date: dt.date = None, unit: str = None, ) -> dict: query_string = urllib.parse.urlencode( dict( filter( lambda item: item[1] is not None, dict( factor=factor, factorCategory=factor_category, currency=currency, startDate=start_date, endDate=end_date, unit=unit, ).items(), ) ) ) GsSession.current.api_version = "v2" url = f'/factor/risk/{risk_report_id}/views?{query_string}' response = GsSession.current.sync.get(url) GsSession.current.api_version = "v1" return response @classmethod def get_factor_risk_report_table( cls, risk_report_id: str, mode: FactorRiskTableMode = None, unit: str = None, currency: Currency = None, date: dt.date = None, start_date: dt.date = None, end_date: dt.date = None, ) -> dict: GsSession.current.api_version = "v2" url = f'/factor/risk/{risk_report_id}/tables?' if mode is not None: url += f'&mode={mode.value}' if unit is not None: url += f'&unit={unit}' if currency is not None: url += f'¤cy={currency.value}' if date is not None: url += f'&date={date.strftime("%Y-%m-%d")}' if start_date is not None: url += f'&startDate={start_date.strftime("%Y-%m-%d")}' if end_date is not None: url += f'&endDate={end_date.strftime("%Y-%m-%d")}' response = GsSession.current.sync.get(url) GsSession.current.api_version = "v1" return response @classmethod def get_brinson_attribution_results( cls, portfolio_id: str, benchmark: str = None, currency: Currency = None, include_interaction: bool = None, aggregation_type: str = None, aggregation_category: str = None, start_date: dt.date = None, end_date: dt.date = None, ): url = f'/attribution/{portfolio_id}/brinson?' if benchmark is not None: url += f'&benchmark={benchmark}' if currency is not None: url += f'¤cy={currency.value}' if include_interaction is not None: url += f'&includeInteraction={str(include_interaction).lower()}' if aggregation_type is not None: url += f'&aggregationType={aggregation_type}' if aggregation_category is not None: url += f'&aggregationCategory={aggregation_category}' if start_date is not None: url += f'&startDate={start_date.strftime("%Y-%m-%d")}' if end_date is not None: url += f'&endDate={end_date.strftime("%Y-%m-%d")}' return GsSession.current.sync.get(url) ================================================ FILE: gs_quant/api/gs/risk.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import asyncio import base64 import datetime as dt import json import logging import math import os import sys import time from socket import gaierror from typing import Iterable, Optional, Union import re import msgpack from websockets import ConnectionClosed from gs_quant.api.risk import RiskApi from gs_quant.errors import MqValueError from gs_quant.risk import RiskRequest from gs_quant.target.risk import OptimizationRequest from gs_quant.tracing import Tracer, TracingSpan _logger = logging.getLogger(__name__) class WebsocketUnavailable(Exception): pass class GsRiskApi(RiskApi): USE_MSGPACK = True POLL_FOR_BATCH_RESULTS = False WEBSOCKET_RETRY_ON_CLOSE_CODES = (1000, 1001, 1006) PRICING_API_VERSION = None @classmethod def calc_multi(cls, requests: Iterable[RiskRequest]) -> dict: requests = tuple(requests) results = cls._exec(requests) if len(results) < len(requests): results = [RuntimeError('Missing results')] * len(requests) return dict(zip(requests, results)) @classmethod def calc(cls, request: RiskRequest) -> Iterable: return cls._exec(request) @classmethod def _exec(cls, request: Union[RiskRequest, Iterable[RiskRequest]]) -> Union[Iterable, dict]: use_msgpack = cls.USE_MSGPACK and not isinstance(request, RiskRequest) headers = {'Content-Type': 'application/x-msgpack'} if use_msgpack else {} risk_session = cls.get_session() version = GsRiskApi.PRICING_API_VERSION or risk_session.api_version result, request_id = risk_session.sync.post( f'/{version}' + cls.__url(request), request, include_version=False, request_headers=headers, timeout=181, return_request_id=True, ) for sub_request in request: sub_request._id = request_id return result @classmethod def __url(cls, request: Union[RiskRequest, Iterable[RiskRequest]]): is_bulk = not isinstance(request, RiskRequest) return '/risk/calculate{}'.format('/bulk' if is_bulk else '') @classmethod async def get_results( cls, responses: asyncio.Queue, results: asyncio.Queue, timeout: Optional[int] = None, span: Optional[TracingSpan] = None, ) -> Optional[str]: if cls.POLL_FOR_BATCH_RESULTS: return await cls.__get_results_poll(responses, results, timeout=timeout) else: try: return await cls.__get_results_ws(responses, results, timeout=timeout) except WebsocketUnavailable: return await cls.__get_results_poll(responses, results, timeout=timeout) @classmethod async def __get_results_poll(cls, responses: asyncio.Queue, results: asyncio.Queue, timeout: Optional[int] = None): run = True pending_requests = {} end_time = dt.datetime.now() + dt.timedelta(seconds=timeout) if timeout else None while pending_requests or run: # Check for timeout if end_time is not None and dt.datetime.now() > end_time: _logger.error('Fatal error: timeout while waiting for results') cls.shutdown_queue_listener(results) return shutdown, items = await cls.drain_queue_async(responses, timeout=2) if shutdown: run = False if items: # ... update the pending requests ... pending_requests.update(((i[1]['reportId'], i[0]) for i in items)) if not pending_requests: continue # ... poll for completed requests ... try: risk_session = cls.get_session() version = GsRiskApi.PRICING_API_VERSION or risk_session.api_version calc_results = risk_session.sync.post( f'/{version}/risk/calculate/results/bulk', list(pending_requests.keys()), include_version=False ) # ... enqueue the request and result for the listener to handle ... for result in calc_results: if 'error' in result: results.put_nowait((pending_requests.pop(result['requestId']), RuntimeError(result['error']))) elif 'result' in result: results.put_nowait((pending_requests.pop(result['requestId']), result['result'])) except Exception as e: error_str = f'Fatal error polling for results: {e}' _logger.error(error_str) cls.shutdown_queue_listener(results) return error_str @classmethod async def __get_results_ws(cls, responses: asyncio.Queue, results: asyncio.Queue, timeout: Optional[int] = None): async def handle_websocket(): nonlocal all_requests_dispatched ret = '' try: # If we're re-connecting then re-send any in-flight request ids if pending_requests: _logger.info(f'Re-subscribing {len(pending_requests)} requests') await asyncio.wait_for(ws.send(json.dumps(list(pending_requests.keys()))), timeout=send_timeout) while pending_requests or not all_requests_dispatched: # Continue while we have pending or un-dispatched requests _logger.debug(f'waiting for {", ".join(pending_requests.keys())}') request_listener = ( asyncio.ensure_future(cls.drain_queue_async(responses)) if not all_requests_dispatched else None ) result_listener = asyncio.ensure_future(ws.recv()) listeners = tuple(filter(None, (request_listener, result_listener))) # Wait for either a request or result complete, pending = await asyncio.wait(listeners, return_when=asyncio.FIRST_COMPLETED) # Check results before sending more requests. Results can be lost otherwise if result_listener in complete: # New results have been received request_id = None try: raw_res = result_listener.result() # Message of the form ; # The status char tells us the format of the data: # 'E' - an error string - encoded as utf-8 bytes # 'R' - json data encoded as utf-8 string # 'M' - msgpack data encoded as base64 string # 'B' - msgpack data (raw binary) is_bytes_response = isinstance(raw_res, bytes) separator = b';' if is_bytes_response else ';' # Use partition to split the byte or char sequence at the first semicolon request_id_raw, _, result_data_raw = raw_res.partition(separator) if is_bytes_response: request_id = request_id_raw.decode() status, risk_data = chr(result_data_raw[0]), result_data_raw[1:] else: request_id = request_id_raw status, risk_data = result_data_raw[0], result_data_raw[1:] except ConnectionClosed as conn_closed: if conn_closed.rcvd and conn_closed.rcvd.code in cls.WEBSOCKET_RETRY_ON_CLOSE_CODES: # websocket closed, but we can retry if request_listener: if request_listener in complete: # WebSocket Closed on us, but had we had results just dispatched # They need subscribing so re-queue them to pick up later _, res = request_listener.result() cls.enqueue(responses, res, wait=False) else: # Conn Closed, cancelling request listener as no-one will hear the response # And we don't want to take anything from the queue request_listener.cancel() # Now re-raise connection closed to be handled and potentially we'll try again raise status = 'E' risk_data = str(conn_closed) except Exception as ee: status = 'E' risk_data = str(ee) if status == 'E': # An error result = RuntimeError(risk_data.decode() if isinstance(risk_data, bytes) else risk_data) else: # Unpack the result try: result = ( msgpack.unpackb(risk_data) if status == 'B' else msgpack.unpackb(base64.b64decode(risk_data), raw=False) if status == 'M' else json.loads(risk_data) ) except Exception as ee: result = ee if request_id is None: # Certain fatal websocket errors (e.g. ConnectionClosed) that are caught above will mean # we have no request_id - In this case we abort and set the error on all results result_listener.cancel() for req in pending_requests.values(): results.put_nowait((req, result)) # Give up pending_requests.clear() all_requests_dispatched = True else: # Enqueue the request and result for the listener to handle results.put_nowait((pending_requests.pop(request_id), result)) else: result_listener.cancel() if request_listener: if request_listener in complete: # New requests have been posted ... all_requests_dispatched, items = request_listener.result() if items: if not all([isinstance(i[1], dict) for i in items]): error_item = next(i[1] for i in items if not isinstance(i[1], dict)) raise RuntimeError(error_item[0][0][0]['errorString']) # ... extract the request IDs ... request_ids = [i[1]['reportId'] for i in items] # ... update the pending requests ... pending_requests.update(zip(request_ids, (i[0] for i in items))) # ... add to our result subscription ... await asyncio.wait_for(ws.send(json.dumps(request_ids)), timeout=send_timeout) # ... note dispatched dispatched.update(request_ids) else: request_listener.cancel() except ConnectionClosed: raise except Exception as ee: exc_type, exc_obj, exc_tb = sys.exc_info() fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] ret = f'{exc_type} {fname} ln:{exc_tb.tb_lineno}' + str(ee) return ret all_requests_dispatched = False pending_requests = {} dispatched = set() error = '' exc_info = None attempts = 0 max_attempts = 5 send_timeout = 30 while attempts < max_attempts: if attempts > 0: await asyncio.sleep(math.pow(2, attempts - 1)) _logger.error(f'{error} error, retrying (attempt {attempts + 1} of {max_attempts})') try: risk_session = cls.get_session() api_version = GsRiskApi.PRICING_API_VERSION or risk_session.api_version ws_url = f'/{api_version}/risk/calculate/results/subscribe' subprotocols = ["msgpack-binary"] if cls.USE_MSGPACK else None # we set a 50ms timeout for the websocket close to avoid manually waiting for marquee # to acknowledge the close and close the underlying TCP stream. We have seen delays of up to 1000ms here ws_close_timeout = 0.05 async with risk_session.async_.connect_websocket( ws_url, include_version=False, subprotocols=subprotocols, close_timeout=ws_close_timeout ) as ws: error = await handle_websocket() attempts = max_attempts except ConnectionClosed as cce: error = ( f'Unexpected Connection Closed ({len(pending_requests)}/{len(dispatched)} ' f'request(s) still pending): {cce}' ) attempts += 1 except asyncio.TimeoutError: error = 'Timed out' attempts = max_attempts except gaierror: raise WebsocketUnavailable() except Exception as e: error = str(e) exc_info = e attempts = max_attempts if error != '': _logger.error(f'Fatal error with websocket: {error}', exc_info=exc_info) span = Tracer.active_span() if span and span.is_recording(): span.set_tag('error', True) span.log_kv({'event': 'error', 'message': error}) cls.shutdown_queue_listener(results) return error @classmethod def create_pretrade_execution_optimization(cls, request: OptimizationRequest) -> str: try: response = cls.get_session().sync.post(r'/risk/execution/pretrade', request) _logger.info('New optimization is created with id: {}'.format(response.get("optimizationId"))) return response except Exception as e: error = str(e) _logger.error(error) return error @classmethod def get_pretrade_execution_optimization(cls, optimization_id: str, max_attempts: int = 15): url = '/risk/execution/pretrade/{}/results'.format(optimization_id) attempts = 0 start = time.perf_counter() results = {} while attempts < max_attempts: if attempts > 0: time.sleep(math.pow(2, attempts)) _logger.error('Retrying (attempt {} of {})'.format(attempts, max_attempts)) try: results = cls.get_session().sync.get(url) if results.get('status') == 'Running': attempts += 1 else: break except Exception as e: error = str(e) _logger.error(error) return error if results.get('status') == 'Running': _logger.info('Optimization is still running. Please retry fetching the results.') return results else: _logger.info('Optimization is fetched in {:.3f}s.'.format(time.perf_counter() - start)) return results @classmethod def get_liquidity_and_factor_analysis( cls, positions: list, risk_model: str, date: dt.date, currency: str = 'USD', participation_rate: float = 0.1, measures: Optional[list] = None, notional: Optional[float] = None, time_series_benchmark_ids: Optional[list] = None, ): """ Get liquidity and factor analysis for a portfolio using the /risk/liquidity endpoint. :param positions: List of positions with assetId and quantity/weight :param risk_model: Risk model identifier (e.g., 'BARRA_EFM_USALTL', 'AXIOMA_AXUS4S') :param date: Analysis date :param currency: Currency for analysis (default: USD) :param participation_rate: Market participation rate (default: 0.1 = 10%) :param measures: List of measures to include (default: all available measures) :param notional: Optional reference notional :param time_series_benchmark_ids: Optional benchmark IDs for time series comparison :return: Dictionary with liquidity and factor analysis results """ if measures is None: measures = [ "Time Series Data", "Risk Buckets", "Factor Risk Buckets", "Factor Exposure Buckets", "Exposure Buckets", ] payload = { "currency": currency, "date": date.isoformat() if isinstance(date, dt.date) else date, "positions": positions, "participationRate": participation_rate, "riskModel": risk_model, "timeSeriesBenchmarkIds": time_series_benchmark_ids or [], "measures": measures, } if notional is not None: payload["notional"] = notional try: response = cls.get_session().sync.post('/risk/liquidity', payload) if isinstance(response, dict) and 'errorMessage' in response: error_msg = response['errorMessage'] asset_ids_pattern = ( r'Assets with the following ids are missing in marquee:' r'\s*\[\s*([^\]]+)\s*\]' ) asset_ids_match = re.search(asset_ids_pattern, error_msg, re.IGNORECASE) if asset_ids_match: clean_error_pattern = ( r'(Assets with the following ids are missing in ' r'marquee:\s*\[[^\]]+\])' ) clean_error_line = re.search(clean_error_pattern, error_msg, re.IGNORECASE) if clean_error_line: clean_message = f"ERROR: liquidity analysis failed\n{clean_error_line.group(1)}" _logger.error(clean_message) raise MqValueError(clean_message) else: missing_assets = asset_ids_match.group(1).strip() clean_message = ( f"ERROR: liquidity analysis failed\n" f"Assets with the following ids are missing in marquee: " f"[ {missing_assets} ]" ) _logger.error(clean_message) raise MqValueError(clean_message) else: _logger.error(f'Liquidity analysis failed: {error_msg}') raise MqValueError("ERROR: liquidity analysis failed") _logger.info('Liquidity analysis completed successfully') return response except Exception: raise ================================================ FILE: gs_quant/api/gs/risk_models.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import logging from enum import Enum from typing import Tuple, Dict, List, Union import backoff from gs_quant.errors import MqRateLimitedError, MqTimeoutError, MqInternalServerError from gs_quant.session import GsSession from gs_quant.target.risk_models import ( RiskModel, RiskModelCalendar, Factor, RiskModelData, RiskModelDataAssetsRequest, RiskModelDataMeasure, RiskModelEventType, RiskModelTerm, ) _logger = logging.getLogger(__name__) class IntradayFactorDataSource(Enum): """Data source for intraday factor data""" GS_FMP = "GS_FMP" GS_REGRESSION = "GS_Regression" BARRA = "BARRA" AXIOMA = "AXIOMA" WOLFE = "WOLFE" QI = "QI" class GsRiskModelApi: """GS Risk Models API client implementation""" @classmethod def create_risk_model(cls, model: RiskModel) -> RiskModel: return GsSession.current.sync.post('/risk/models', model, cls=RiskModel) @classmethod @backoff.on_exception(lambda: backoff.expo(base=2, factor=2), (MqTimeoutError, MqInternalServerError), max_tries=5) @backoff.on_exception(lambda: backoff.constant(90), MqRateLimitedError, max_tries=5) def get_risk_model(cls, model_id: str) -> RiskModel: return GsSession.current.sync.get(f'/risk/models/{model_id}', cls=RiskModel) @classmethod def get_risk_models( cls, ids: List[str] = None, limit: int = None, offset: int = None, terms: List[str] = None, versions: List[str] = None, vendors: List[str] = None, names: List[str] = None, types: List[str] = None, coverages: List[str] = None, ) -> Tuple[RiskModel, ...]: url = '/risk/models?' if limit: url += f'&limit={limit}' if ids: url += '&id={ids}'.format(ids='&id='.join(ids)) if offset: url += f'&offset={offset}' if terms: url += f'&term={terms}' if versions: url += '&version={versions}'.format(versions='&version='.join(versions)) if vendors: url += '&vendor={vendors}'.format(vendors='&vendor='.join(vendors)) if names is not None: url += '&name={names}'.format(names='&name='.join(names)) if coverages is not None: url += '&coverage={cov}'.format(cov='&coverage='.join(coverages)) if types is not None: url += '&type={type}'.format(type='&type='.join(types)) return GsSession.current.sync.get(url, cls=RiskModel)['results'] @classmethod def update_risk_model(cls, model: RiskModel) -> RiskModel: return GsSession.current.sync.put(f'/risk/models/{model.id}', model, cls=RiskModel) @classmethod def delete_risk_model(cls, model_id: str) -> Dict: return GsSession.current.sync.delete(f'/risk/models/{model_id}') @classmethod @backoff.on_exception(lambda: backoff.expo(base=2, factor=2), (MqTimeoutError, MqInternalServerError), max_tries=5) @backoff.on_exception(lambda: backoff.constant(90), MqRateLimitedError, max_tries=5) def get_risk_model_calendar(cls, model_id: str) -> RiskModelCalendar: return GsSession.current.sync.get(f'/risk/models/{model_id}/calendar', cls=RiskModelCalendar) @classmethod def upload_risk_model_calendar(cls, model_id: str, model_calendar: RiskModelCalendar) -> RiskModelCalendar: return GsSession.current.sync.put(f'/risk/models/{model_id}/calendar', model_calendar, cls=RiskModelCalendar) @classmethod @backoff.on_exception(lambda: backoff.expo(base=2, factor=2), (MqTimeoutError, MqInternalServerError), max_tries=5) @backoff.on_exception(lambda: backoff.constant(90), MqRateLimitedError, max_tries=5) def get_risk_model_dates( cls, model_id: str, start_date: dt.date = None, end_date: dt.date = None, event_type: RiskModelEventType = None ) -> List: url = f'/risk/models/{model_id}/dates?' if start_date is not None: url += f'&startDate={start_date.strftime("%Y-%m-%d")}' if end_date is not None: url += f'&endDate={end_date.strftime("%Y-%m-%d")}' if event_type is not None: url += f'&eventType={event_type.value}' return GsSession.current.sync.get(url)['results'] class GsFactorRiskModelApi(GsRiskModelApi): def __init__(self): super().__init__() @classmethod def get_risk_model_factors(cls, model_id: str) -> Tuple[Factor, ...]: return GsSession.current.sync.get(f'/risk/models/{model_id}/factors', cls=Factor)['results'] @classmethod def create_risk_model_factor(cls, model_id: str, factor: Factor) -> Factor: return GsSession.current.sync.post(f'/risk/models/{model_id}/factors', factor, cls=Factor) @classmethod def get_risk_model_factor(cls, model_id: str, factor_id: str) -> Factor: return GsSession.current.sync.get(f'/risk/models/{model_id}/factors/{factor_id}') @classmethod def update_risk_model_factor(cls, model_id: str, factor: Factor) -> Factor: url = f'/risk/models/{model_id}/factors/{factor.identifier}' return GsSession.current.sync.put(url, factor, cls=Factor) @classmethod def delete_risk_model_factor(cls, model_id: str, factor_id: str) -> Dict: return GsSession.current.sync.delete(f'/risk/models/{model_id}/factors/{factor_id}') @classmethod @backoff.on_exception(lambda: backoff.expo(base=2, factor=2), (MqTimeoutError, MqInternalServerError), max_tries=5) @backoff.on_exception(lambda: backoff.constant(90), MqRateLimitedError, max_tries=5) def get_risk_model_factor_data( cls, model_id: str, start_date: dt.date = None, end_date: dt.date = None, identifiers: List[str] = None, include_performance_curve: bool = False, factor_categories: List[str] = None, names: List[str] = None, ) -> List[Dict]: url = f'/risk/models/{model_id}/factors/data?' if start_date is not None: url += f'&startDate={start_date.strftime("%Y-%m-%d")}' if end_date is not None: url += f'&endDate={end_date.strftime("%Y-%m-%d")}' if identifiers is not None: url += '&identifiers={ids}'.format(ids='&identifiers='.join(identifiers)) if include_performance_curve: url += '&includePerformanceCurve=true' if names: url += '&name={names}'.format(names='&name='.join(names)) if factor_categories: url += '&factorCategory={factor_categories}'.format( factor_categories='&factorCategory='.join(factor_categories) ) return GsSession.current.sync.get(url)['results'] @classmethod @backoff.on_exception(lambda: backoff.expo(base=2, factor=2), (MqTimeoutError, MqInternalServerError), max_tries=5) @backoff.on_exception(lambda: backoff.constant(90), MqRateLimitedError, max_tries=5) def get_risk_model_factor_data_intraday( cls, model_id: str, start_time: dt.datetime = None, end_time: dt.datetime = None, factor_ids: List[str] = None, factor_categories: List[str] = None, factors: List[str] = None, data_source: Union[IntradayFactorDataSource, str] = None, ) -> List[Dict]: url = f'/risk/models/{model_id}/factors/data/intraday?' if start_time is not None: url += f'&startTime={start_time.strftime("%Y-%m-%dT%H:%M:%SZ")}' if end_time is not None: url += f'&endTime={end_time.strftime("%Y-%m-%dT%H:%M:%SZ")}' if factor_ids is not None: url += '&factorId={ids}'.format(ids='&factorId='.join(factor_ids)) if data_source: url += f'&dataSource={data_source if isinstance(data_source, str) else data_source.value}' if factors: url += '&factor={names}'.format(names='&factor='.join(factors)) if factor_categories: url += '&factorCategory={factor_categories}'.format( factor_categories='&factorCategory='.join(factor_categories) ) return GsSession.current.sync.get(url)['results'] @classmethod def get_risk_model_coverage( cls, asset_ids: List[str] = None, as_of_date: dt.datetime = None, sort_by_term: RiskModelTerm = None ) -> List[Dict]: query = {} if asset_ids is not None: query['assetIds'] = asset_ids if as_of_date is not None: query['asOfDate'] = as_of_date.strftime('%Y-%m-%d') if sort_by_term is not None: query['sortByTerm'] = sort_by_term return GsSession.current.sync.post('/risk/models/coverage', query, timeout=200)['results'] @classmethod def upload_risk_model_data( cls, model_id: str, model_data: Union[Dict, RiskModelData], partial_upload: bool = False, target_universe_size: float = None, final_upload: bool = None, aws_upload: bool = False, ) -> str: url = f'/risk/models/data/{model_id}' if partial_upload: url += '?partialUpload=true' if target_universe_size: url += f'&targetUniverseSize={target_universe_size}' if final_upload is not None: final_upload_flag = 'true' if final_upload else 'false' url += f'&finalUpload={final_upload_flag}' if aws_upload: url += '&awsUpload=true' else: if aws_upload: url += '?awsUpload=true' return GsSession.current.sync.post(url, model_data, timeout=200) @classmethod @backoff.on_exception(lambda: backoff.expo(base=2, factor=2), (MqTimeoutError, MqInternalServerError), max_tries=5) @backoff.on_exception(lambda: backoff.constant(90), MqRateLimitedError, max_tries=5) def get_risk_model_data( cls, model_id: str, start_date: dt.date, end_date: dt.date = None, assets: RiskModelDataAssetsRequest = None, measures: List[RiskModelDataMeasure] = None, factors: list = None, limit_factors: bool = None, ) -> Dict: end_date = cls.get_risk_model_dates(model_id)[-1] if not end_date else end_date.strftime('%Y-%m-%d') query = {'startDate': start_date.strftime('%Y-%m-%d'), 'endDate': end_date} if assets is not None: query['assets'] = assets if measures is not None: query['measures'] = measures if factors is not None: query['factors'] = factors if limit_factors is not None: query['limitFactors'] = limit_factors return GsSession.current.sync.post(f'/risk/models/data/{model_id}/query', query, timeout=200) ================================================ FILE: gs_quant/api/gs/scenarios.py ================================================ """ Copyright 2024 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import logging from typing import Dict, List, Tuple from gs_quant.session import GsSession from gs_quant.target.risk import Scenario _logger = logging.getLogger(__name__) class GsScenarioApi: """GS Scenarios API client implementation""" @classmethod def create_scenario(cls, scenario: Scenario) -> Scenario: return GsSession.current.sync.post('/risk/scenarios', scenario, cls=Scenario) @classmethod def get_scenario(cls, scenario_id: str) -> Scenario: return GsSession.current.sync.get(f'/risk/scenarios/{scenario_id}', cls=Scenario) @classmethod def get_many_scenarios( cls, ids: List[str] = None, names: List[str] = None, limit: int = 100, **kwargs ) -> Tuple[Scenario, ...]: url = f'/risk/scenarios?limit={limit}' if ids: url += f'&id={"&id=".join(ids)}' if names: url += f'&name={"&name=".join(names)}' if kwargs: for k, v in kwargs.items(): url += f'&{k}={f"&{k}=".join(v)}' if isinstance(v, list) else f'&{k}={v}' return GsSession.current.sync.get(url, cls=Scenario).get('results', []) @classmethod def get_scenario_by_name(cls, name: str) -> Scenario: url = f"/risk/scenarios?name={name}" ret = GsSession.current.sync.get(url, cls=Scenario) num_found = ret.get('totalResults', 0) if num_found == 0: raise ValueError(f'Scenario {name}not found') elif num_found > 1: raise ValueError(f'More than one scemario named {name}') else: return ret['results'][0] @classmethod def update_scenario(cls, scenario: Scenario) -> Dict: return GsSession.current.sync.put(f'/risk/scenarios/{scenario.id_}', scenario, cls=Scenario) @classmethod def delete_scenario(cls, scenario_id: str) -> Dict: return GsSession.current.sync.delete(f'/risk/scenarios/{scenario_id}') @classmethod def calculate_scenario(cls, request: Dict) -> Dict: return GsSession.current.sync.post('/scenarios/calculate', request) class GsFactorScenarioApi(GsScenarioApi): def __init__(self): super().__init__() @classmethod def get_many_scenarios( cls, ids: List[str] = None, names: List[str] = None, limit: int = 100, type: str = None, risk_model: str = None, shocked_factors: List[str] = None, shocked_factor_categories: List[str] = None, start_date: dt.date = None, end_date: dt.date = None, tags: List[str] = None, ) -> Tuple[Scenario, ...]: factor_scenario_args = {} if risk_model: factor_scenario_args['riskModel'] = risk_model if type: factor_scenario_args['factorScenarioType'] = type if shocked_factors: factor_scenario_args['shockedFactor'] = shocked_factors if shocked_factor_categories: factor_scenario_args['shockedFactorCategory'] = shocked_factor_categories if start_date: factor_scenario_args['historicalSimulationStartDate'] = start_date if end_date: factor_scenario_args['historicalSimulationEndDate'] = end_date if tags: factor_scenario_args['tags'] = tags many_scenarios = super().get_many_scenarios(ids=ids, names=names, limit=limit, **factor_scenario_args) many_scenarios = tuple([scenario for scenario in many_scenarios if scenario.type_]) return many_scenarios @classmethod def calculate_scenario(cls, calculation_request: Dict) -> Dict: return super().calculate_scenario(request=calculation_request) ================================================ FILE: gs_quant/api/gs/screens.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import logging from typing import Tuple, List from gs_quant.session import GsSession from gs_quant.target.screens import Screen from gs_quant.target.assets_screener import AssetScreenerRequest _logger = logging.getLogger(__name__) class GsScreenApi: @classmethod def get_screens( cls, screen_ids: List[str] = None, screen_names: List[str] = None, limit: int = 100 ) -> Tuple[Screen, ...]: url = '/screens?' if screen_ids: url += f'&id={"&id=".join(screen_ids)}' if screen_names: url += f'&name={"&name=".join(screen_names)}' return GsSession.current.sync.get(f'{url}&limit={limit}', cls=Screen)['results'] @classmethod def get_screen(cls, screen_id: str) -> Screen: return GsSession.current.sync.get(f'/screens/{screen_id}', cls=Screen) @classmethod def create_screen(cls, screen: Screen) -> Screen: return GsSession.current.sync.post('/screens', screen, cls=Screen) @classmethod def update_screen(cls, screen: Screen) -> Screen: return GsSession.current.sync.put(f'/screens/{screen.id}', screen, cls=Screen) @classmethod def delete_screen(cls, screen_id: str) -> str: return GsSession.current.sync.delete(f'/screens/{screen_id}') @classmethod def get_filter_options(cls) -> dict: return GsSession.current.sync.get('/assets/screener/options') @classmethod def calculate(cls, payload: AssetScreenerRequest) -> dict: return GsSession.current.sync.post('/assets/screener', payload=payload) ================================================ FILE: gs_quant/api/gs/secmaster.py ================================================ """ Copyright 2023 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import json import math from enum import Enum from functools import partial from itertools import groupby from typing import Union, Iterable, Dict, Optional import tqdm from gs_quant.data.utilities import SecmasterXrefFormatter from gs_quant.json_encoder import JSONEncoder from gs_quant.session import GsSession from gs_quant.target.secmaster import SecMasterAssetType DEFAULT_SCROLL_PAGE_SIZE = 500 class SecMasterIdentifiers(Enum): CUSIP = 'cusip' TICKER = 'ticker' ISIN = 'isin' GSID = 'gsid' BBG = 'bbg' BBID = 'bbid' BCID = 'bcid' RIC = 'ric' RCIC = 'rcic' ID = 'id' ASSET_ID = 'assetId' CUSIP8 = 'cusip8' SEDOL = 'sedol' CINS = 'cins' PRIMEID = 'primeId' FACTSET_REGIONAL_ID = 'factSetRegionalId' TOKEN_ID = 'tokenId' COMPOSITE_FIGI = 'compositeFigi' BARRA_ID = 'barraId' AXIOMA_ID = 'axiomaId' FIGI = 'figi' def __extend_enum(base_enum, new_values): members = {item.name: item.value for item in base_enum} members.update(new_values) return Enum('CapitalStructureIdentifiers', members) # FIXME Create an enum in service def for this and reference once module_generator get fixed. CapitalStructureIdentifiers = __extend_enum(SecMasterIdentifiers, {"ISSUER_ID": "issuerId"}) class ExchangeId(Enum): RIC_SUFFIX_CODE = "ricSuffixCode" RIC_EXCHANGE_CODE = "ricExchangeCode" DATASCOPE_IPC_CODE = "datascopeIpcCode" BBG_EXCHANGE_CODE = "bbgExchangeCode" TAQ_EXCHANGE_CODE = "taqExchangeCode" IVERSON_EXCHANGE_CODE = "iversonExchangeCode" INDEX_CHANGE_EXCHANGE_CODE = "indexChangeExchangeCode" DOW_JONES_EXCHANGE_CODE = "dowJonesExchangeCode" STOXX_EXCHANGE_CODE = "stoxxExchangeCode" ML_ETF_EXCHANGE_CODE = "mlEtfExchangeCode" FTSE_EXCHANGE_CODE = "ftseExchangeCode" DJGI_EXCHANGE_CODE = "djgiExchangeCode" SECDB_EXCHANGE_CODE = "secdbExchangeCode" DADD_EXCHANGE_CODE = "daddExchangeCode" MIC = "mic" OPERATING_MIC = "operatingMic" GS_EXCHANGE_ID = "gsExchangeId" COUNTRY = "country" EXCHANGE_NAME = "name" class GsSecurityMasterApi: @classmethod def get_security(cls, id_value: str, id_type: SecMasterIdentifiers, effective_date: dt.date = None): """ Get flatten asset reference data @param id_value: identifier value @param id_type: identifier type @param effective_date: As of date for query @return: dict or None """ args = {id_type.value: id_value} results = cls.get_many_securities(effective_date=effective_date, flatten=False, **args) if results is not None: return results["results"][0] return results @classmethod def get_many_securities( cls, type_: SecMasterAssetType = None, effective_date: dt.date = None, limit: int = 10, flatten=False, is_primary=None, offset_key: str = None, **query_params: Dict[SecMasterIdentifiers, Union[str, Iterable[str]]], ) -> Optional[dict]: """ Get reference data for a single page of a given asset type. Use returned offsetKey to fetch next page. @param is_primary: @param flatten: flag if data should be flattened @param type_: asset type @param effective_date: As of date for query @param limit: integer of individual page @param offset_key: string, an offset indicating where the page ends. @return: list of dict """ if (query_params is None or len(query_params) == 0) and type_ is None: raise ValueError("Neither '_type' nor 'query_params' are provided") params = {"limit": limit} cls.prepare_params(params, is_primary, offset_key, type_, effective_date) params = {**params, **query_params} payload = json.loads(json.dumps(params, cls=JSONEncoder)) if flatten: r = GsSession.current.sync.get('/markets/securities/data', payload=payload) else: r = GsSession.current.sync.get('/markets/securities', payload=payload) if r['totalResults'] == 0: return None return r @classmethod def get_all_securities( cls, type_: SecMasterAssetType = None, effective_date: dt.date = None, is_primary=None, flatten=False, **query_params, ) -> Optional[dict]: """ Get all securities reference data matching the type, with respect of effective_date property. Function runs in batches fetching all securities. @param flatten: Flag, whether data should be flattened @param is_primary: Restrict to primary listings @param type_: asset type @param effective_date: As of date for query @return:" list of dict """ if 'limit' in query_params: limit = query_params['limit'] del query_params['limit'] else: limit = DEFAULT_SCROLL_PAGE_SIZE response = cls.get_many_securities( type_, effective_date, limit=limit, offset_key=None, flatten=flatten, is_primary=is_primary, **query_params ) if response is None or "offsetKey" not in response: return response if response['totalResults'] == 0: return None results = response["results"] offset_key = response["offsetKey"] fn = partial( cls.get_many_securities, type_=type_, effective_date=effective_date, limit=limit, flatten=flatten, **query_params, ) results.extend(cls.__fetch_all(fn, offset_key)) response["totalResults"] = len(results) response["results"] = results return response @classmethod def get_security_data( cls, id_value: str, id_type: SecMasterIdentifiers, effective_date: dt.date = None ) -> Optional[dict]: """ Get flatten asset reference data @param id_value: identifier value @param id_type: identifier type @param effective_date: As of date for query @return: dict or None """ args = {id_type.value: id_value} results = cls.get_many_securities(effective_date=effective_date, flatten=True, **args) if results is not None: return results["results"][0] return results @classmethod def get_identifiers(cls, secmaster_id: str) -> dict: """ Get identifiers history for given secmaster id. @param secmaster_id: secmaster id e.g. ['GSPD111E123'] @return: list of dict with date ranges of the identifiers. """ if not secmaster_id.startswith("GS"): raise ValueError(f"Invalid id_value {secmaster_id}. Secmaster id starts with 'GS'") r = GsSession.current.sync.get(f'/markets/securities/{secmaster_id}/identifiers') return r['results'] @classmethod def get_many_identifiers(cls, ids: Iterable[str], limit=100, xref_format=False) -> dict: """ Get identifiers for a list of secmaster ids. It runs in batches till all data is fetched. This method retrieves identifier information for multiple securities. The data can be returned in either standard format or transformed using the SecmasterXrefFormatter for time-based cross-reference analysis. Args: ids (Iterable[str]): An iterable collection of secmaster identifiers to query. Examples: ['GSPD111E123', 'GSPD222F456', 'GSPD333G789'] limit (int, optional): Maximum number of identifier records to return per secmaster id. Defaults to 100. Used to control response size and prevent memory issues with large datasets. xref_format (bool, optional): Flag to control output format. Defaults to False. - False: Returns raw identifier data in standard format - True: Returns data transformed by SecmasterXrefFormatter, where identifiers are grouped by overlapping date ranges Returns: dict: A dictionary containing identifier information for the requested secmaster ids. Standard format (xref_format=False): { 'secmaster_id_1': [ { 'type': 'ISIN', 'value': 'US1234567890', 'startDate': '2020-01-01', 'endDate': '2023-12-31' }, ... ], 'secmaster_id_2': [...], ... } Xref format (xref_format=True): { 'secmaster_id_1': { 'xrefs': [ { 'startDate': '2020-01-01', 'endDate': '2023-12-31', 'identifiers': [ {'type': 'ISIN', 'value': 'US1234567890'}, {'type': 'CUSIP', 'value': '123456789'}, ... ] }, ... ] }, 'secmaster_id_2': {...}, ... } Raises: ValueError: If ids parameter is empty or contains invalid secmaster identifiers ConnectionError: If unable to connect to secmaster database TimeoutError: If database query exceeds timeout limits Note: - The method processes requests in batches to handle large datasets efficiently - Infinity dates ('9999-99-99') are normalized to '9999-12-31' in xref format Example: >>> # Standard format >>> result = GsSecurityMasterApi.get_many_identifiers(['GSPD111E123'], limit=50, xref_format=False) >>> print(result['GSPD111E123'][0]['type']) # 'ISIN' >>> # Xref format for time-based analysis >>> xref_result = GsSecurityMasterApi.get_many_identifiers(['GSPD111E123'], xref_format=True) >>> print(xref_result['GSPD111E123']['xrefs'][0]['startDate']) # '2020-01-01' """ if not isinstance(ids, Iterable): raise ValueError(f"secmaster_id must be an iterable, got {type(ids)}") if len(ids) == 0: raise ValueError("secmaster_id cannot be an empty iterable") ids = list(ids) for id_value in ids: if not id_value.startswith("GS"): raise ValueError(f"Invalid id_value {id_value}. Secmaster id starts with 'GS'") consolidated_results = {} current_offset_key = None while True: payload = {'id': ids} if current_offset_key is not None: payload['offsetKey'] = current_offset_key if limit is not None: payload['limit'] = limit payload = json.loads(json.dumps(payload, cls=JSONEncoder)) response = GsSession.current.sync.get('/markets/securities/identifiers', payload=payload) if 'results' in response: for entity_id, data in response['results'].items(): if entity_id not in consolidated_results: consolidated_results[entity_id] = [] consolidated_results[entity_id].extend(data) current_offset_key = response.get('offsetKey') if current_offset_key is None: break if xref_format: return SecmasterXrefFormatter.convert(consolidated_results) return consolidated_results @classmethod def map( cls, input_type: SecMasterIdentifiers, ids: Iterable[str], output_types: Iterable[SecMasterIdentifiers] = frozenset([SecMasterIdentifiers.GSID]), start_date: dt.date = None, end_date: dt.date = None, effective_date: dt.date = None, ) -> Iterable[dict]: """ Map to other identifier types, from given IDs. :param input_type: type of input IDs :param ids: security IDs :param output_types: types of IDs to map to :param start_date: first as-of date (defaults to current date) :param end_date: last as-of date (defaults to current date) :param effective_date: an exact as-of date for mapping :return: dict containing mappings for as-of date(s) """ params = { input_type.value: list(ids), 'toIdentifiers': [identifier.value for identifier in output_types], 'compact': True, } if effective_date is not None: if (start_date or end_date) is not None: raise ValueError('provide (start date / end date) or effective_date, but not both') params['effectiveDate'] = effective_date if start_date is not None: params['startDate'] = start_date if end_date is not None: params['endDate'] = end_date payload = json.loads(json.dumps(params, cls=JSONEncoder)) r = GsSession.current.sync.get('/markets/securities/map', payload) results = r['results'] return results @classmethod def search( cls, q: str, limit: int = 10, type_: SecMasterAssetType = None, is_primary: bool = None, active_listing: bool = None, ) -> Union[Iterable[dict], None]: """ Search securities by a query string. It does a full text search among names, identifiers, company @param q: query string @param limit: number of returned matches @param type_: filter restricting the type of results @param is_primary: filter restricting the matches to primary listings @param active_listing: filter restricting the matches to active listings @return: """ params = {"q": q, "limit": limit} if type_ is not None: params["type"] = type_.value if is_primary is not None: params["isPrimary"] = is_primary if active_listing is not None: params["activeListing"] = active_listing payload = json.loads(json.dumps(params, cls=JSONEncoder)) r = GsSession.current.sync.get('/v2/markets/securities/search', payload=payload, include_version=False) if r['totalResults'] == 0: return None return r["results"] @classmethod def __stringify_boolean(cls, bool_value): return str(bool_value).lower() @classmethod def __fetch_all(cls, fetch_fn, offset_key, total_batches=None, extract_results=True): accumulator = [] offset = offset_key progress_info = ( tqdm.tqdm(desc="Processing", unit=" batch") if total_batches is None else tqdm.tqdm(range(total_batches), desc="Processing", unit=" batch", ascii=True) ) while True: progress_info.update(1) data = fetch_fn(offset_key=offset) if data is not None: if extract_results is True: accumulator.extend(data['results']) else: accumulator.append(data) if 'offsetKey' not in data: progress_info.close() break offset = data["offsetKey"] return accumulator @classmethod def _get_corporate_actions(cls, id_value: str, id_type: SecMasterIdentifiers, effective_date: dt.date, offset_key): params = { id_type.value: id_value, } if effective_date is not None: params['effectiveDate'] = effective_date if offset_key is not None: params["offsetKey"] = offset_key payload = json.loads(json.dumps(params, cls=JSONEncoder)) r = GsSession.current.sync.get("/markets/corpactions", payload=payload) return r @classmethod def get_corporate_actions( cls, id_value: str, id_type: SecMasterIdentifiers = SecMasterIdentifiers.GSID, effective_date: dt.date = None ) -> Iterable[dict]: """ Get corporate actions from a given security. @param effective_date: parameter to query securities at a given date @param id_value: identifier value @param id_type: identifier type @return: """ supported_identifiers = [SecMasterIdentifiers.GSID, SecMasterIdentifiers.ID] if id_type not in supported_identifiers: raise ValueError( f"Unsupported identifier {id_type} for this endpoint. Use one of this {supported_identifiers}" ) fn = partial(cls._get_corporate_actions, id_value, id_type, effective_date) results = cls.__fetch_all(fn, None) return results @classmethod def get_capital_structure( cls, id_value: Union[str, list], id_type: CapitalStructureIdentifiers, type_: SecMasterAssetType = None, is_primary: bool = None, effective_date: dt.date = None, ) -> dict: """ Get a capital structure of the given company by id_value of the security. It runs in batches till all data is fetched @param is_primary: filter to select primary listings only @param type_: filter to restrict data to a given type @param effective_date: parameter to query securities at a given date @param id_value: identifier value @param id_type: identifier type @return: dict """ response = cls._get_capital_structure( id_value=id_value, id_type=id_type, type_=type_, is_primary=is_primary, effective_date=effective_date, offset_key=None, ) if "offsetKey" not in response: return response asset_types_total = response["assetTypesTotal"] batch_count = math.floor(sum(asset_types_total.values()) / 100) results = response["results"] offset_key = response["offsetKey"] fn = partial(cls._get_capital_structure, id_value, id_type, type_, is_primary, effective_date) results.extend(cls.__fetch_all(fn, offset_key, total_batches=batch_count)) aggregated_results, total_results = cls.__capital_structure_aggregate(asset_types_total, results) response["results"] = aggregated_results response["totalResults"] = total_results del response["offsetKey"] return response @classmethod def __capital_structure_aggregate(cls, asset_types_total, results): group_by_issuer_id = {k: list(g) for k, g in groupby(results, lambda x: x["issuerId"])} aggregated_results = [] aggregated_total_results = 0 for issuer_id in group_by_issuer_id: issuer_id_data = group_by_issuer_id[issuer_id] issuer_id_data_instance = issuer_id_data[0] consolidated_types_obj = {key: [] for key in asset_types_total} for obj in group_by_issuer_id[issuer_id]: aggregated_total_results += sum(len(e) for e in obj["types"].values()) [ consolidated_types_obj.get(asset_type).extend(obj["types"].get(asset_type)) for asset_type in obj["types"] ] issuer_id_data_instance["types"] = consolidated_types_obj aggregated_results.append(issuer_id_data_instance) return aggregated_results, aggregated_total_results @classmethod def _get_capital_structure( cls, id_value: Union[str, list], id_type: Union[CapitalStructureIdentifiers, SecMasterIdentifiers], type_, is_primary, effective_date, offset_key: Union[str, None], ): params = {id_type.value: id_value} cls.prepare_params(params, is_primary, offset_key, type_, effective_date) payload = json.loads(json.dumps(params, cls=JSONEncoder)) r = GsSession.current.sync.get("/markets/capitalstructure", payload=payload) return r @classmethod def prepare_params(cls, params, is_primary, offset_key, type_, effective_date=None): if type_ is not None: params["type"] = type_.value if is_primary is not None: params["isPrimary"] = is_primary if offset_key is not None: params["offsetKey"] = offset_key if effective_date is not None: params["effectiveDate"] = effective_date @classmethod def _get_deltas( cls, start_time: dt.datetime = None, end_time: dt.datetime = None, raw: bool = None, scope: list = None, limit: int = None, offset_key: str = None, ) -> Iterable[dict]: params = {} if raw is not None: params["raw"] = GsSecurityMasterApi.__stringify_boolean(raw) if start_time is not None: params["startTime"] = start_time if end_time is not None: params["endTime"] = end_time if scope is not None: params["scope"] = scope if limit is not None: params["limit"] = limit if offset_key is not None: params["offsetKey"] = offset_key payload = json.loads(json.dumps(params, cls=JSONEncoder)) r = GsSession.current.sync.get("/markets/securities/identifiers/updates-feed", payload=payload) return r @classmethod def get_deltas( cls, start_time: dt.datetime = None, end_time: dt.datetime = None, raw: bool = None, scope: list = None, limit: int = None, offset_key: str = None, scroll_all_pages: bool = True, ) -> Union[dict, Iterable[dict]]: """ Get all identifier changes between two time stamps @param scroll_all_pages: @param start_time: start time @param end_time: end time @param limit: page size of returned matches @param scope: narrow down the search to a specific set of events @param offset_key: offset key to fetch next page @param raw: flag, if true (default) aggregates data to more readable form, if false shows unprocessed results. @return: list of dict """ if scroll_all_pages: fn = partial(cls._get_deltas, start_time, end_time, raw, scope, limit) results = cls.__fetch_all(fn, offset_key, extract_results=False) latest_update_time = max(result['lastUpdateTime'] for result in results) res = [item for result in results for item in result["results"]] request_id = results[0]["requestId"] if results else None return {"results": res, "lastUpdateTime": latest_update_time, "requestId": request_id} else: results = cls._get_deltas(start_time, end_time, raw, scope, limit, offset_key) return results @classmethod def get_exchanges(cls, effective_date: dt.date = None, **query_params: Dict[str, Union[str, Iterable[str]]]): """ Returns reference data for exchanges - e.g. MICs, exchange codes, name, listing country. @param effective_date: As of date for query @param query_params: one of allowed params: 'ricSuffixCode', 'ricExchangeCode', 'datascopeIpcCode', 'bbgExchangeCode', 'taqExchangeCode', 'iversonExchangeCode', 'indexChangeExchangeCode', 'dowJonesExchangeCode', 'stoxxExchangeCode', 'mlEtfExchangeCode', 'ftseExchangeCode', 'djgiExchangeCode', 'secdbExchangeCode', 'daddExchangeCode', 'mic', 'operatingMic', 'gsExchangeId', 'country', 'name' @return: """ results = [] fn = partial(cls._get_exchanges, effective_date, DEFAULT_SCROLL_PAGE_SIZE, query_params) results.extend(cls.__fetch_all(fn, offset_key=None)) response = dict() response["totalResults"] = len(results) response["results"] = results return response @classmethod def _get_exchanges( cls, effective_date: dt.date = None, limit: int = 10, query_params=None, offset_key: Union[str, None] = None ): if query_params is None: query_params = dict() allowed_keys = list(ExchangeId._value2member_map_.keys()) for qp in query_params.keys(): if qp not in allowed_keys: raise ValueError(f" Parameter '{qp}' is not supported. Allowed parameters: {allowed_keys}") params = {"limit": limit} if effective_date is not None: params['effectiveDate'] = effective_date params = {**params, **query_params} if offset_key is not None: params["offsetKey"] = offset_key payload = json.loads(json.dumps(params, cls=JSONEncoder)) r = GsSession.current.sync.get('/markets/exchanges', payload=payload) if r['totalResults'] == 0: return None return r @classmethod def get_exchange_identifiers_history(cls, gs_exchange_id: str) -> Iterable[dict]: """ Get identifiers history for given exchange id. @param gs_exchange_id: exchange_id id @return: list of dict with date ranges of the identifiers. """ r = GsSession.current.sync.get(f'/markets/exchanges/{gs_exchange_id}/identifiers') return r['results'] @classmethod def _prepare_string_or_list_param(cls, value: Union[str, list], param_name: str): if isinstance(value, str): return [value] # Wrap single string in a list elif isinstance(value, list) and all(isinstance(item, str) for item in value): return value # Use the list directly else: raise ValueError(f"{param_name} must be a string or a list of strings") @classmethod def _prepare_underlyers_params( cls, params, id_value, offset_key=None, type_=None, effective_date=None, country_code=None, currency=None, include_inactive=None, ): if id_value is not None: params["id"] = cls._prepare_string_or_list_param(id_value, "id_value") else: raise ValueError("id_value must be defined") if type_ is not None: if isinstance(type_, list): if all(isinstance(t, SecMasterAssetType) for t in type_): params["type"] = [t.value for t in type_] else: raise ValueError("All elements in the type_ list must be instances of SecMasterAssetType") elif isinstance(type_, SecMasterAssetType): params["type"] = type_.value else: raise ValueError("type_ must be either a SecMasterAssetType or a list of SecMasterAssetType") if country_code is not None: params["countryCode"] = cls._prepare_string_or_list_param(country_code, "country_code") if currency is not None: params["currency"] = cls._prepare_string_or_list_param(currency, "currency") if offset_key is not None: params["offsetKey"] = offset_key if effective_date is not None: params["effectiveDate"] = effective_date if include_inactive is not None: params["includeInactive"] = include_inactive @classmethod def _get_securities_by_underlyers( cls, id_value: Union[str, list], type_: Union[SecMasterAssetType, list] = None, effective_date: dt.date = None, limit: int = 100, country_code: Union[str, list] = None, currency: Union[str, list] = None, include_inactive: bool = False, offset_key: str = None, ) -> Optional[dict]: params = {"limit": limit} cls._prepare_underlyers_params( params=params, id_value=id_value, offset_key=offset_key, type_=type_, effective_date=effective_date, country_code=country_code, currency=currency, include_inactive=include_inactive, ) payload = json.loads(json.dumps(params, cls=JSONEncoder)) r = GsSession.current.sync.get('/markets/securities/underlyers', payload=payload) if r['totalResults'] == 0: return None return r @classmethod def get_securities_by_underlyers( cls, id_value: Union[str, list], type_: Union[SecMasterAssetType, list] = None, effective_date: dt.date = None, limit: int = None, offset_key: str = None, country_code: Union[str, list] = None, currency: Union[str, list] = None, include_inactive: bool = False, scroll_all_pages: bool = False, ) -> Optional[dict]: """ Retrieve reference data for listed derivatives based on their underlyers. This method retrieves securities linked to specific underlyers, with optional filters for asset type, effective date, country code and currency. The results can be paginated, and the method supports fetching either a single page or all pages of results. Args: id_value (Union[str, list]): Identifier(s) of the underlyers. Can be a single string or a list of strings. Example: 'GSPD100E0' or ['GSPD100E0']. type_ (Union[SecMasterAssetType, list], optional): Asset type(s) to filter the results. Can be a single `SecMasterAssetType` or a list of `SecMasterAssetType`. Defaults to None. effective_date (dt.date, optional): Effective date for the query. Defaults to None. limit (int, optional): Maximum number of results to return per page. Defaults to None. offset_key (str, optional): String indicating where the page ends, used for pagination. Defaults to None. country_code (Union[str, list], optional): Country code(s) to filter the results. Can be a single string or a list of strings. Defaults to None. currency (Union[str, list], optional): Currency code(s) to filter the results. Can be a single string or a list of strings. Defaults to None. include_inactive (bool, optional): Whether to include inactive listed derivatives in the results. Defaults to False. scroll_all_pages (bool, optional): Whether to fetch all pages of results. If True, retrieves all pages. Defaults to False. Returns: Optional[dict]: A dictionary containing the results for the requested page(s), or None if no results are found. Example: { "totalResults": 10, "offsetKey": "ABCD=", "results": [...], "asOfTime": "2025-01-11T11:22:33.740Z", "requestId": "abc123" } Raises: ValueError: If `id_value` is not provided or is invalid. ValueError: If `type_` is not a valid `SecMasterAssetType` or a list of valid `SecMasterAssetType`. ValueError: If `country_code` or `currency` is not a string or a list of strings. ValueError: If `effective_date` is not in the format 'YYYY-MM-DD' when provided as a string. TypeError: If `effective_date` is not of type `datetime.date` or `str`. Note: - This method fetches a single page of results by default. To fetch all pages, set `scroll_all_pages=True`. - The `effective_date` parameter is used to query securities as of a specific date. - The `offset_key` parameter is used for pagination to fetch subsequent pages of results. Example: >>> result = GsSecurityMasterApi.get_securities_by_underlyers( ... id_value="GSPD100E0", ... type_=SecMasterAssetType.Equity_Option, ... effective_date="2023-10-01", ... country_code="US", ... currency="USD", ... include_inactive=False, ... scroll_all_pages=True ... ) >>> print(result["results"]) """ supported_types = [ SecMasterAssetType.Equity_Option, SecMasterAssetType.Future, SecMasterAssetType.Future_Option, ] if type_ is not None: if isinstance(type_, SecMasterAssetType): if type_ not in supported_types: raise ValueError(f"Unsupported type {type_}. Supported types are {supported_types}") elif isinstance(type_, list): for t in type_: if t not in supported_types: raise ValueError(f"Unsupported type {t}. Supported types are {supported_types}") if scroll_all_pages: if limit is None: limit = 500 fn = partial( cls._get_securities_by_underlyers, id_value=id_value, type_=type_, effective_date=effective_date, limit=limit, country_code=country_code, currency=currency, include_inactive=include_inactive, ) results = cls.__fetch_all(fn, offset_key, extract_results=False) as_of_time = max(result['asOfTime'] for result in results) res = [item for result in results for item in result["results"]] request_id = results[0]["requestId"] if results else None total_results = len(res) return {"totalResults": total_results, "results": res, "asOfTime": as_of_time, "requestId": request_id} else: return cls._get_securities_by_underlyers( id_value=id_value, type_=type_, effective_date=effective_date, limit=limit, offset_key=offset_key, country_code=country_code, currency=currency, include_inactive=include_inactive, ) ================================================ FILE: gs_quant/api/gs/thematics.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import json from enum import Enum from typing import List from gs_quant.session import GsSession class ThematicMeasure(Enum): """ Thematic Measures """ ALL_THEMATIC_EXPOSURES = 'allThematicExposures' TOP_FIVE_THEMATIC_EXPOSURES = 'topFiveThematicExposures' BOTTOM_FIVE_THEMATIC_EXPOSURES = 'bottomFiveThematicExposures' THEMATIC_BREAKDOWN_BY_ASSET = 'thematicBreakdownByAsset' NO_THEMATIC_DATA = 'noThematicData' NO_PRICING_DATA = 'noPricingData' def __str__(self): return self.value class Region(Enum): """ Thematic Regions """ AMERICAS = 'Americas' ASIA = 'Asia' EUROPE = 'Europe' class GsThematicApi: """GS Thematic API client implementation""" @classmethod def get_thematics( cls, entity_id: str, basket_ids: List[str] = None, regions: List[Region] = None, start_date: dt.date = None, end_date: dt.date = None, measures: List[ThematicMeasure] = None, notional: float = None, ) -> List: payload = { 'id': entity_id, } if basket_ids: payload['basketId'] = basket_ids if regions: payload['region'] = [r.value for r in regions] if start_date: payload['startDate'] = start_date.strftime("%Y-%m-%d") if end_date: payload['endDate'] = end_date.strftime("%Y-%m-%d") if measures: payload['measures'] = [m.value for m in measures] if notional: payload['notional'] = notional return GsSession.current.sync.post('/thematics', payload=json.dumps(payload)).get('results', []) ================================================ FILE: gs_quant/api/gs/users.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from typing import List, Any, Dict, Optional from pydash import get from gs_quant.session import GsSession from gs_quant.target.reports import User DEFAULT_SEARCH_FIELDS = [ "id", "name", "firstName", "lastName", "kerberos", "company", "departmentName", "divisionName", "city", "country", "region", "title", "email", "internal", ] class GsUsersApi: @classmethod def get_users( cls, user_ids: List[str] = None, user_emails: List[str] = None, user_names: List[str] = None, user_companies: List[str] = None, limit: int = 100, offset: int = 0, ) -> List: url = '/users?' if user_ids: url += f'&id={"&id=".join(user_ids)}' if user_emails: url += f'&email={"&email=".join(user_emails)}' if user_names: url += f'&name={"&name=".join(user_names)}' if user_companies: url += f'&company={"&company=".join(user_companies)}' return GsSession.current.sync.get(f'{url}&limit={limit}&offset={offset}', cls=User)['results'] @classmethod def get_my_guid(cls) -> str: return f"guid:{GsSession.current.sync.get('/users/self')['id']}" @classmethod def get_current_user_info(cls) -> Dict[str, Any]: """ Gets user :return: user """ return GsSession.current.sync.get('/users/self') @classmethod def get_current_app_managers(cls) -> List[str]: return [f"guid:{manager}" for manager in get(GsSession.current.sync.get('/users/self'), 'appManagers', [])] @classmethod def get_many(cls, key_type: str, keys: List[str], fields: Optional[List[str]] = None) -> dict: users_by_key = {} chunk_size = 100 glue = "&" + key_type + "=" if fields is not None and key_type not in fields: fields = [*fields, key_type] for i in range(0, len(keys), chunk_size): chunk = keys[i : i + chunk_size] fields_str = f"fields={','.join(fields)}&" if fields else '' url = f'/users?{fields_str}{key_type}={glue.join(chunk)}&limit=200' response = GsSession.current.sync.get(url) for user in response.get('results', []): users_by_key[user[key_type]] = user return users_by_key @classmethod def search( cls, query: str, fields: Optional[List[str]] = None, where: Optional[Dict[str, any]] = None ) -> Dict[str, Any]: payload = { "q": query, "fields": fields or DEFAULT_SEARCH_FIELDS, **({"where": where} if where else {}), } return GsSession.current.sync.post("/search/users/query", payload=payload) ================================================ FILE: gs_quant/api/gs/workspaces.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import urllib.parse from typing import Tuple, Dict from pydash import get from gs_quant.session import GsSession from gs_quant.target.workspaces_markets import Workspace import webbrowser API = '/workspaces/markets' WORKSPACES_MARKETS_HEADERS: Dict[str, str] = {'Content-Type': 'application/json;charset=utf-8'} class GsWorkspacesMarketsApi: """GS Workspaces Markets API implementation""" @classmethod def get_workspaces(cls, limit: int = 10, **kwargs) -> Tuple[Workspace, ...]: return GsSession.current.sync.get(f'{API}?limit={limit}&{urllib.parse.urlencode(kwargs)}', cls=Workspace)[ 'results' ] @classmethod def get_workspace(cls, workspace_id: str): return GsSession.current.sync.get(f'{API}/{workspace_id}', cls=Workspace) @classmethod def get_workspace_by_alias(cls, alias: str) -> Workspace: workspace = get(GsSession.current.sync.get(f'{API}?alias={alias}', cls=Workspace), 'results.0') if not workspace: raise ValueError(f'Workspace with alias {alias} not found') return workspace @classmethod def create_workspace(cls, workspace: Workspace) -> Workspace: return GsSession.current.sync.post( f'{API}', workspace, cls=Workspace, request_headers=WORKSPACES_MARKETS_HEADERS ) @classmethod def update_workspace(cls, workspace: Workspace): return GsSession.current.sync.put( f'{API}/{workspace.id}', workspace, cls=Workspace, request_headers=WORKSPACES_MARKETS_HEADERS ) @classmethod def delete_workspace(cls, workspace_id: str) -> Dict: return GsSession.current.sync.delete(f'{API}/{workspace_id}') @classmethod def open_workspace(cls, workspace: Workspace): if workspace.alias: webbrowser.open(f'{GsSession.current.domain.replace(".web", "")}/s/markets/{workspace.alias}') else: webbrowser.open(f'{GsSession.current.domain.replace(".web", "")}/s/markets/{workspace.id}') ================================================ FILE: gs_quant/api/risk.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import asyncio import itertools import logging import queue import sys from abc import ABCMeta, abstractmethod from concurrent.futures import TimeoutError from threading import Thread from typing import Iterable, Optional, Union, Tuple, Dict, Any from tqdm import tqdm from gs_quant.api.api_session import ApiWithCustomSession from gs_quant.base import RiskKey, Sentinel, Priceable from gs_quant.risk import ErrorValue, RiskRequest from gs_quant.risk.result_handlers import result_handlers from gs_quant.risk.results import PricingFuture from gs_quant.session import GsSession from gs_quant.tracing import Tracer, TracingSpan _logger = logging.getLogger(__name__) class GenericRiskApi(ApiWithCustomSession, metaclass=ABCMeta): batch_dates = True @classmethod @abstractmethod def populate_pending_futures( cls, requests: list, session: GsSession, pending: Dict[Tuple[RiskKey, Priceable], PricingFuture], **kwargs ): ... @classmethod @abstractmethod def build_keyed_results( cls, request: RiskRequest, results: Union[Iterable, Exception] ) -> Dict[Tuple[RiskKey, Priceable], Any]: ... class RiskApi(GenericRiskApi, metaclass=ABCMeta): __SHUTDOWN_SENTINEL = Sentinel('QueueListenerShutdown') @classmethod def populate_pending_futures( cls, requests: list, session: GsSession, pending: Dict[Tuple[RiskKey, Priceable], PricingFuture], **kwargs ): results = queue.Queue() done = False max_concurrent, progress_bar, timeout, span, cache_impl, is_async = [ kwargs.get(arg) for arg in ['max_concurrent', 'progress_bar', 'timeout', 'span', 'cache_impl', 'is_async'] ] try: with session: cls.run(requests, results, max_concurrent, progress_bar, timeout=timeout, span=span) except Exception as e: cls.enqueue(results, ((k, e) for k in pending.keys())) while pending and not done: done, chunk_results = cls.drain_queue(results) for (risk_key_, priceable_), result in chunk_results: future = pending.pop((risk_key_, priceable_), None) if future is not None: future.set_result(result) if cache_impl is not None: cache_impl.put(risk_key_, priceable_, result) if not is_async: # In async mode we can't tell if we've completed, we could be re-used while pending: (risk_key_, _), future = pending.popitem() future.set_result(ErrorValue(risk_key_, 'No result returned')) @classmethod @abstractmethod async def get_results( cls, responses: asyncio.Queue, results: asyncio.Queue, timeout: Optional[int] = None, span: Optional[TracingSpan] = None, ) -> Optional[str]: ... @classmethod @abstractmethod def calc(cls, request: RiskRequest) -> Iterable: ... @classmethod def calc_multi(cls, requests: Iterable[RiskRequest]) -> dict: return {request: cls.calc(request) for request in requests} @classmethod def __handle_queue_update(cls, q: Union[queue.Queue, asyncio.Queue], first: object) -> Tuple[bool, list]: if first is cls.__SHUTDOWN_SENTINEL: return True, [] ret = [first] shutdown = False while True: try: elem = q.get_nowait() if elem is cls.__SHUTDOWN_SENTINEL: shutdown = True else: ret.append(elem) except (asyncio.QueueEmpty, queue.Empty): break return shutdown, ret @classmethod def drain_queue(cls, q: queue.Queue, timeout: Optional[int] = None) -> Tuple[bool, list]: try: return cls.__handle_queue_update(q, q.get(timeout=timeout)) except queue.Empty: return False, [] @classmethod async def drain_queue_async(cls, q: asyncio.Queue, timeout: Optional[int] = None) -> Tuple[bool, list]: try: elem = await asyncio.wait_for(q.get(), timeout=timeout) if timeout else await q.get() return cls.__handle_queue_update(q, elem) except (TimeoutError, asyncio.TimeoutError): return False, [] @classmethod def enqueue( cls, q: Union[queue.Queue, asyncio.Queue], items: Iterable, loop: Optional[asyncio.AbstractEventLoop] = None, wait: Optional[bool] = False, ): try: iter(items) except TypeError: items = (items,) put = q.put if wait else q.put_nowait for item in items: if loop: loop.call_soon_threadsafe(put, item) else: put(item) @classmethod def shutdown_queue_listener( cls, q: Union[queue.Queue, asyncio.Queue], loop: Optional[asyncio.AbstractEventLoop] = None ): if loop and not loop.is_closed(): loop.call_soon_threadsafe(q.put_nowait, cls.__SHUTDOWN_SENTINEL) else: q.put_nowait(cls.__SHUTDOWN_SENTINEL) @classmethod def run( cls, requests: list, results: queue.Queue, max_concurrent: int, progress_bar: Optional[tqdm] = None, timeout: Optional[int] = None, span: Optional[str] = None, ): def _process_results(completed: list): chunk_results = tuple( itertools.chain.from_iterable( cls.build_keyed_results(request, result).items() for request, result in completed ) ) cls.enqueue(results, chunk_results, wait=True) def process_results(unprocessed_results: queue.Queue): shutdown = False while not shutdown: shutdown, completed = cls.drain_queue(unprocessed_results) _process_results(completed) def execute_requests( outstanding_requests: queue.Queue, responses: asyncio.Queue, raw_results: asyncio.Queue, session: GsSession, loop: asyncio.AbstractEventLoop, active_span, ): with Tracer.activate_span(active_span), session: shutdown = False while not shutdown: shutdown, requests_chunk = cls.drain_queue(outstanding_requests) if requests_chunk: try: # Get the responses for our requests chunk responses_chunk = cls.calc_multi(requests_chunk) # Enqueue the replies for either the result subscriber (if async requests) or directly cls.enqueue(responses, responses_chunk.items(), loop=loop) except Exception as e: # Enqueue the error as a reply cls.enqueue(raw_results, ((r, e) for r in requests_chunk), loop=loop) if responses != raw_results: # If we are in async mode, indicate to the result subscriber that there are no more requests cls.shutdown_queue_listener(responses, loop=loop) async def run_async(current_span): def num_risk_jobs(request: RiskRequest): # size of calculation job return len(request.pricing_and_market_data_as_of) * len(request.positions) def num_risk_keys(request: RiskRequest): # total number of risk calculations return num_risk_jobs(request) * len(request.measures) is_async = not requests[0].wait_for_results loop = asyncio.get_event_loop() raw_results = asyncio.Queue() responses = asyncio.Queue() if is_async else raw_results outstanding_requests = queue.Queue() unprocessed_results = None results_handler = None # determine session to use session = cls.get_session() # The requests library (which we use for dispatching) is not async, so we need a thread for concurrency Thread( daemon=True, target=execute_requests, args=(outstanding_requests, responses, raw_results, session, loop, current_span), ).start() if is_async: # If async we need a task to handle result subscription results_handler = loop.create_task( cls.get_results(responses, raw_results, timeout=timeout, span=current_span) ) expected = sum(num_risk_jobs(r) for r in requests) received = 0 chunk_size = min(max_concurrent, expected) result_thread = None if expected > chunk_size: # Result handling can occur while we're blocked on I/O unprocessed_results = queue.Queue() result_thread = Thread(daemon=True, target=process_results, args=(unprocessed_results,)) result_thread.start() while received < expected: if requests: # Enqueue requests for dispatch dispatch_risk_keys = 0 dispatch_requests = [] while requests and dispatch_risk_keys < chunk_size: dispatch_request = requests.pop() dispatch_requests.append(dispatch_request) dispatch_risk_keys += num_risk_jobs(dispatch_request) cls.enqueue(outstanding_requests, dispatch_requests, loop=loop) # Wait for results shutdown, completed = await cls.drain_queue_async(raw_results) if shutdown: # Only happens on error break # Enable as many new requests as we've received results, to keep the outstanding number constant risk_jobs_received = sum(num_risk_jobs(request) for request, _ in completed) chunk_size = min(risk_jobs_received, expected - received) if progress_bar: risk_calcs_received = sum(num_risk_keys(request) for request, _ in completed) progress_bar.update(risk_calcs_received) progress_bar.refresh() received += risk_jobs_received # Handle the results if unprocessed_results is not None: cls.enqueue(unprocessed_results, completed) else: _process_results(completed) cls.shutdown_queue_listener(outstanding_requests) if results_handler: results_error = await results_handler if results_error: # Raise an exception so that pending results can be filled with an error raise RuntimeError(f'Fatal Error subscribing to results: {results_error}') if progress_bar: progress_bar.close() if result_thread is not None: cls.shutdown_queue_listener(unprocessed_results) result_thread.join() cls.shutdown_queue_listener(results) if sys.version_info >= (3, 7): asyncio.run(run_async(span)) else: try: existing_event_loop = asyncio.get_event_loop() except RuntimeError: existing_event_loop = None use_existing = existing_event_loop and existing_event_loop.is_running() main_loop = existing_event_loop if use_existing else asyncio.new_event_loop() if not use_existing: asyncio.set_event_loop(main_loop) try: main_loop.run_until_complete(run_async(span)) except Exception: if not use_existing: main_loop.stop() raise finally: if not use_existing: main_loop.close() asyncio.set_event_loop(None) @classmethod def build_keyed_results( cls, request: RiskRequest, results: Union[Iterable, Exception] ) -> Dict[Tuple[RiskKey, Priceable], Any]: formatted_results = {} if isinstance(results, Exception): date_results = [{'$type': 'Error', 'errorString': str(results)}] * len( request.pricing_and_market_data_as_of ) position_results = [date_results] * len(request.positions) results = [position_results] * len(request.measures) for risk_measure, position_results in zip(request.measures, results): for position, date_results in zip(request.positions, position_results): for as_of, date_result in zip(request.pricing_and_market_data_as_of, date_results): handler = result_handlers.get(date_result.get('$type')) risk_key = RiskKey( cls, as_of.pricing_date, as_of.market, request.parameters, request.scenario, risk_measure ) try: result = ( handler( date_result, risk_key, position.instrument, request_id=getattr(request, '_id', None) ) if handler else date_result ) except Exception as e: result = ErrorValue(risk_key, str(e)) _logger.error(result) formatted_results[(risk_key, position.instrument)] = result return formatted_results ================================================ FILE: gs_quant/api/utils.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import concurrent import socket from concurrent.futures.thread import ThreadPoolExecutor from typing import List, Callable, Optional import requests from gs_quant.data import DataContext from gs_quant.errors import MqUninitialisedError from gs_quant.session import GsSession from gs_quant.tracing import Tracer, TracingSpan def handle_proxy(url, params): try: internal = GsSession.current.is_internal() except MqUninitialisedError: internal = False if internal or socket.getfqdn().split('.')[-2:] == ['gs', 'com']: try: import gs_quant_auth proxies = gs_quant_auth.__proxies__ response = requests.get(url, params=params, proxies=proxies) except ModuleNotFoundError: raise RuntimeError('You must install gs_quant_auth to be able to use this endpoint') else: response = requests.get(url, params=params) return response class ThreadPoolManager: __executor: ThreadPoolExecutor = None @classmethod def initialize(cls, max_workers: int): cls.__executor = ThreadPoolExecutor(max_workers=max_workers) @classmethod def run_async(cls, tasks: List[Callable]) -> List: if not cls.__executor: cls.__executor = ThreadPoolExecutor() tasks_to_idx = {} for i, task in enumerate(tasks): tasks_to_idx[ cls.__executor.submit(cls.__run, GsSession.current, DataContext.current, Tracer.active_span(), task) ] = i results = [None] * len(tasks_to_idx) for task in concurrent.futures.as_completed(tasks_to_idx): idx = tasks_to_idx[task] results[idx] = task.result() return results @staticmethod def __run(session, data_context, span: Optional[TracingSpan], func): with Tracer.activate_span(span), session: with data_context: return func() ================================================ FILE: gs_quant/backtests/SKILL.md ================================================ yea--- name: gs-quant-backtesting description: Guide to the gs_quant backtesting framework — engines, triggers, actions, strategies, and result extraction. Covers GenericEngine (multi-asset OTC), EquityVolEngine, and PredefinedAssetEngine. --- # SKILL.md — gs_quant Backtesting Guide This document covers the backtesting framework in `gs_quant.backtests`. It explains how to construct strategies from triggers and actions, choose the right engine, run backtests, and extract results. --- ## 1. Architecture Overview A backtest in gs_quant is built from three core concepts: | Concept | Description | |---|---| | **Strategy** | Combines an optional initial portfolio with one or more `Trigger` objects. | | **Trigger** | Defines *when* to act — on a schedule, when a risk threshold is breached, when market data crosses a level, etc. Each trigger holds one or more `Action` objects. | | **Action** | Defines *what* to do when the trigger fires — add a trade, hedge a risk, exit a position, rebalance, etc. | A backtest **Engine** runs the strategy over a date range, resolving instruments, computing risks, and building the P&L time series. ### Import Map ```python # Strategy from gs_quant.backtests.strategy import Strategy # Triggers from gs_quant.backtests.triggers import ( PeriodicTrigger, PeriodicTriggerRequirements, StrategyRiskTrigger, RiskTriggerRequirements, MktTrigger, MktTriggerRequirements, AggregateTrigger, AggregateTriggerRequirements, DateTrigger, DateTriggerRequirements, MeanReversionTrigger, MeanReversionTriggerRequirements, PortfolioTrigger, PortfolioTriggerRequirements, NotTrigger, NotTriggerRequirements, TriggerDirection, AggType, ) # Actions from gs_quant.backtests.actions import ( AddTradeAction, AddScaledTradeAction, HedgeAction, ExitTradeAction, ExitAllPositionsAction, EnterPositionQuantityScaledAction, RebalanceAction, ) # Engines from gs_quant.backtests.generic_engine import GenericEngine from gs_quant.backtests.equity_vol_engine import EquityVolEngine # Data sources (for market triggers) from gs_quant.backtests.data_sources import GenericDataSource, GsDataSource, MissingDataStrategy ``` --- ## 2. Engines gs_quant ships three backtest engines. Choose the one that matches your instrument type and use case: | Engine | Best For | Instruments | Notes | |---|---|---|---| | **GenericEngine** | Multi-asset OTC strategies | IRSwap, IRSwaption, FXOption, FXForward, FXBinary, EqOption, etc. | Most flexible. Prices via the GS analytics API. Supports all trigger and action types. | | **EquityVolEngine** | Equity vol strategies | EqOption, EqVarianceSwap | Server-side execution — faster for simple equity vol roll strategies. Supports delta hedging and signals. | | **PredefinedAssetEngine** | Strategies on predefined assets with intraday logic | Custom order-based | For advanced users building execution-style backtests. | **Best practice:** Use `GenericEngine` unless you specifically need `EquityVolEngine` performance for equity options. --- ## 3. Strategy Construction A `Strategy` takes an optional initial portfolio and one or more triggers: ```python from gs_quant.backtests.strategy import Strategy # Empty starting portfolio, one trigger strategy = Strategy(None, trigger) # Start with an instrument already in the portfolio strategy = Strategy(initial_instrument, trigger) # Multiple triggers strategy = Strategy(None, [trigger_add_trade, trigger_hedge]) ``` --- ## 4. Triggers Triggers define *when* actions fire. Each trigger pairs a `TriggerRequirements` (the condition) with one or more `Action` objects (what to do). ### 4.1 PeriodicTrigger — Trade on a Schedule The most common trigger. Fires on a regular frequency (e.g. monthly, weekly). ```python from datetime import date from gs_quant.backtests.triggers import PeriodicTrigger, PeriodicTriggerRequirements from gs_quant.backtests.actions import AddTradeAction start_date = date(2023, 1, 3) end_date = date(2024, 12, 31) trig_req = PeriodicTriggerRequirements( start_date=start_date, end_date=end_date, frequency='1m', # '1b' (daily), '1w', '1m', '3m', '1y', etc. ) action = AddTradeAction(instrument, trade_duration='1m') trigger = PeriodicTrigger(trig_req, action) ``` **Key parameters for `PeriodicTriggerRequirements`:** - `start_date` / `end_date` — date range for the schedule - `frequency` — tenor string: `'1b'` (daily), `'1w'`, `'1m'`, `'3m'`, `'6m'`, `'1y'` - `calendar` — optional holiday calendar (iterable of dates) ### 4.2 StrategyRiskTrigger — Trigger on Risk Breach Fires when a portfolio risk measure breaches a threshold. ```python from gs_quant.backtests.triggers import StrategyRiskTrigger, RiskTriggerRequirements, TriggerDirection from gs_quant.backtests.actions import HedgeAction from gs_quant.risk import FXDelta from gs_quant.common import AggregationLevel hedge_risk = FXDelta(aggregation_level=AggregationLevel.Type, currency='USD') trig_req = RiskTriggerRequirements( risk=hedge_risk, trigger_level=50_000, direction=TriggerDirection.ABOVE, # ABOVE, BELOW, or EQUAL ) trigger = StrategyRiskTrigger(trig_req, hedge_action) ``` ### 4.3 MktTrigger — Trigger on Market Data Fires when an external data series crosses a level. ```python from gs_quant.backtests.triggers import MktTrigger, MktTriggerRequirements, TriggerDirection from gs_quant.backtests.data_sources import GenericDataSource, MissingDataStrategy # Build a data source from a pandas Series data_source = GenericDataSource(pandas_series, MissingDataStrategy.fill_forward) trig_req = MktTriggerRequirements( data_source=data_source, trigger_level=100.0, direction=TriggerDirection.BELOW, ) trigger = MktTrigger(trig_req, action) ``` You can also use `GsDataSource` to pull data directly from the GS Marquee Data Catalog. ### 4.4 DateTrigger — Trigger on Specific Dates ```python from gs_quant.backtests.triggers import DateTrigger, DateTriggerRequirements trig_req = DateTriggerRequirements( dates=[date(2024, 3, 15), date(2024, 6, 15), date(2024, 9, 15)], ) trigger = DateTrigger(trig_req, action) ``` ### 4.5 AggregateTrigger — Combine Triggers with AND/OR Logic ```python from gs_quant.backtests.triggers import AggregateTrigger, AggregateTriggerRequirements, AggType agg_req = AggregateTriggerRequirements( triggers=[periodic_trigger, risk_trigger], # can be Trigger or TriggerRequirements aggregate_type=AggType.ALL_OF, # ALL_OF (AND) or ANY_OF (OR) ) trigger = AggregateTrigger(agg_req, action) ``` ### 4.6 NotTrigger — Invert a Trigger ```python from gs_quant.backtests.triggers import NotTrigger, NotTriggerRequirements not_req = NotTriggerRequirements(trigger=some_trigger_requirements) trigger = NotTrigger(not_req, action) ``` --- ## 5. Actions Actions define *what* happens when a trigger fires. ### 5.1 AddTradeAction — Add a Trade The most common action. Resolves an instrument on the trigger date and adds it to the portfolio. ```python from gs_quant.backtests.actions import AddTradeAction action = AddTradeAction( priceables=instrument, # single instrument or list of instruments trade_duration='1m', # how long to hold: tenor, date, 'expiration_date', or None (forever) name='my_trade', # optional name prefix ) ``` **`trade_duration` options:** - `None` — hold forever (trade stays in portfolio until backtest ends) - Tenor string (`'1m'`, `'3m'`, `'1y'`) — hold for that period then unwind - `'expiration_date'` — hold until the instrument's expiration date (useful for options) - `'next schedule'` — hold until the next periodic trigger date (auto-rolling) - Explicit `datetime.date` — hold until that date - `datetime.timedelta` — hold for that time delta ### 5.2 HedgeAction — Delta Hedge Computes a risk measure on the portfolio and scales a hedge instrument to offset it. ```python from gs_quant.backtests.actions import HedgeAction from gs_quant.risk import FXDelta, IRDelta from gs_quant.instrument import FXForward, IRSwap # FX Delta hedge hedge_risk = FXDelta(aggregation_level='Type', currency='USD') hedge_instrument = FXForward(pair='EURUSD', settlement_date='1y', name='hedge_fwd') action = HedgeAction( risk=hedge_risk, priceables=hedge_instrument, trade_duration='1m', # optional — how long to hold the hedge ) ``` ### 5.3 AddScaledTradeAction — Scale a Trade Adds a trade scaled by a risk measure, size, or NAV. ```python from gs_quant.backtests.actions import AddScaledTradeAction, ScalingActionType action = AddScaledTradeAction( priceables=instrument, trade_duration='1m', scaling_type=ScalingActionType.size, scaling_level=1_000_000, # target notional ) ``` ### 5.4 EnterPositionQuantityScaledAction — Enter with Specific Quantity Used primarily with the EquityVolEngine for quantity-based trading. ```python from gs_quant.backtests.actions import EnterPositionQuantityScaledAction from gs_quant.target.backtests import BacktestTradingQuantityType action = EnterPositionQuantityScaledAction( priceables=eq_option, trade_duration='1m', trade_quantity=1000, trade_quantity_type=BacktestTradingQuantityType.quantity, ) ``` ### 5.5 ExitTradeAction / ExitAllPositionsAction — Close Positions ```python from gs_quant.backtests.actions import ExitTradeAction, ExitAllPositionsAction # Exit a specific named trade exit_named = ExitTradeAction(priceable_names='my_trade') # Exit everything exit_all = ExitAllPositionsAction() ``` ### 5.6 Combining Actions on a Single Trigger A trigger can have multiple actions. **Order matters** — they execute in sequence: ```python # First exit old position, then add new one trigger = StrategyRiskTrigger(trig_req, [exit_action, add_action]) ``` --- ## 6. Running a Backtest with GenericEngine ### Basic Example — Monthly FX Option Roll ```python from datetime import date, datetime from gs_quant.session import GsSession from gs_quant.instrument import FXOption from gs_quant.common import BuySell, OptionType from gs_quant.backtests.triggers import PeriodicTrigger, PeriodicTriggerRequirements from gs_quant.backtests.actions import AddTradeAction from gs_quant.backtests.generic_engine import GenericEngine from gs_quant.backtests.strategy import Strategy from gs_quant.risk import Price GsSession.use() start_date = date(2023, 1, 3) end_date = date(2024, 12, 31) # Define instrument — remember premium=0 for FX options! call = FXOption( buy_sell=BuySell.Buy, option_type=OptionType.Call, pair='USDJPY', strike_price='ATMF', expiration_date='2y', name='2y_call', premium=0, ) # Periodic trigger: roll monthly, hold for 1 month trig_req = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m') action = AddTradeAction(call, '1m') trigger = PeriodicTrigger(trig_req, action) strategy = Strategy(None, trigger) # Run GE = GenericEngine() backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True) ``` ### GenericEngine.run_backtest Parameters | Parameter | Description | Default | |---|---|---| | `strategy` | The Strategy object | *required* | | `start` | Backtest start date | None | | `end` | Backtest end date | None | | `frequency` | How often to evaluate: `'1b'` (daily), `'1w'`, `'1m'` | `'1m'` | | `states` | Explicit list of dates (overrides start/end/frequency) | None | | `risks` | Additional risk measures to compute | None | | `show_progress` | Show progress bar | True | | `csa_term` | CSA term for discounting | None | | `initial_value` | Starting cash value | 0 | | `result_ccy` | Currency for results | None | | `market_data_location` | `'LDN'`, `'NYC'`, `'HKG'` | None | | `is_batch` | Use websocket batching | True | --- ## 7. Extracting Backtest Results The `run_backtest` call returns a `BackTest` object with several useful views. ### 7.1 result_summary — Main P&L DataFrame ```python summary = backtest.result_summary ``` This is a `pandas.DataFrame` indexed by date with columns: - `Price` — mark-to-market of live instruments - `Cumulative Cash` — cumulative cash from unwound trades - `Transaction Costs` — cumulative transaction costs - `Total` — `Price + Cumulative Cash + Transaction Costs` (the total strategy P&L) - Additional risk columns if requested ### 7.2 Plotting Performance ```python import pandas as pd # Total performance (MTM + Cash) backtest.result_summary['Total'].plot(figsize=(10, 6), title='Strategy Performance') # Or build manually perf = backtest.result_summary[Price] + backtest.result_summary['Cumulative Cash'] perf.plot(figsize=(10, 6), title='Performance') ``` ### 7.3 trade_ledger — Trade History ```python ledger = backtest.trade_ledger() ``` Returns a DataFrame showing each trade: when it was entered, when it was closed, entry/exit values, and P&L. ### 7.4 risk_summary — Risk Time Series ```python risk_df = backtest.risk_summary ``` Like `result_summary` but fills zero for dates with no instruments held (useful for risk plots). ### 7.5 Accessing Additional Risks Pass extra risk measures via the `risks` parameter: ```python from gs_quant.risk import Price, FXDelta from gs_quant.common import AggregationLevel backtest = GE.run_backtest( strategy, start=start_date, end=end_date, frequency='1b', risks=[Price, FXDelta(aggregation_level=AggregationLevel.Type, currency='USD')], ) # Access the risk time series delta_series = backtest.result_summary[FXDelta(aggregation_level=AggregationLevel.Type, currency='USD')] ``` ### 7.6 summary_stats — Strategy Performance Statistics Call `summary_stats()` to get a pandas Series of key metrics for evaluating and comparing backtests: ```python stats = backtest.summary_stats() print(stats) ``` Output includes: | Metric | Description | |---|---| | Start Date / End Date | Backtest date range | | Duration (days) | Calendar days in backtest | | Total PnL | Final cumulative P&L | | Total Transaction Costs | Cumulative transaction costs | | Total Trades | Number of trades entered | | Peak PnL | Highest P&L reached | | Annualised Return | Average daily P&L × 252 | | Annualised Volatility | Std dev of daily P&L × √252 | | Sharpe Ratio | Annualised return / annualised volatility | | Sortino Ratio | Annualised return / annualised downside deviation | | Max Drawdown | Largest peak-to-trough decline | | Max Drawdown Duration (days) | Longest period spent in drawdown | | Calmar Ratio | Annualised return / |max drawdown| | | Current Drawdown | Drawdown at end of backtest | | Average Daily PnL | Mean of daily P&L changes | | Daily PnL Std Dev | Std dev of daily P&L changes | | Best Day / Worst Day | Largest gain and loss in a single day | | % Positive Days | Proportion of days with positive P&L | | Skewness | Skewness of daily P&L distribution | | Kurtosis | Excess kurtosis of daily P&L distribution | You can customise the annualisation factor (default 252 business days): ```python # Use 260 for a different convention stats = backtest.summary_stats(annualisation_factor=260) ``` To compare two backtests side by side: ```python comparison = pd.DataFrame({ 'Strategy A': backtest_a.summary_stats(), 'Strategy B': backtest_b.summary_stats(), }) comparison ``` --- ## 8. Common Patterns and Best Practices ### 8.1 FX Options — Always Set premium=0 When backtesting FX options, always set `premium=0`. Otherwise resolution sets a premium that makes `DollarPrice` zero, and your backtest P&L will be meaningless. ```python call = FXOption( buy_sell=BuySell.Buy, option_type=OptionType.Call, pair='EURUSD', strike_price='ATMF', expiration_date='1y', name='1y_call', premium=0, # <-- Essential for backtests! ) ``` ### 8.2 trade_duration — Controlling Position Lifetime The `trade_duration` on `AddTradeAction` is critical: - Use a **tenor matching the trigger frequency** for roll strategies (e.g. `'1m'` trade_duration with `'1m'` trigger frequency) - Use **`'expiration_date'`** to hold options until expiry - Use **`None`** if you want the trade to stay in the portfolio indefinitely - Use **`'next schedule'`** to auto-exit when the next periodic trigger fires ### 8.3 Multiple Triggers — Order Matters When passing multiple triggers to a Strategy, they are evaluated in order on each date. Put entry triggers before hedge triggers: ```python strategy = Strategy(None, [entry_trigger, hedge_trigger]) ``` When a single trigger has multiple actions, they execute in sequence: ```python # Exit old positions first, then add new ones trigger = PeriodicTrigger(trig_req, [exit_action, add_action]) ``` ### 8.4 Running Daily vs Monthly - `frequency='1b'` — evaluate every business day (most common for accurate P&L) - `frequency='1m'` — evaluate monthly (faster but misses intra-month dynamics) - The trigger frequency and the backtest evaluation frequency are independent — e.g. you can run daily (`'1b'`) evaluation with monthly (`'1m'`) trigger to see daily P&L of a monthly roll strategy. ### 8.5 Naming Instruments Always name your instruments. Names appear in the trade ledger and make debugging much easier: ```python call = FXOption(..., name='1y_call') hedge = FXForward(..., name='hedge_fwd') ``` ### 8.6 Starting with a Pre-Existing Portfolio Pass an instrument or list of instruments as the first argument to Strategy: ```python # Start with a swaption already in the portfolio strategy = Strategy(swaption, trigger) # Start with multiple instruments strategy = Strategy([swaption, swap], trigger) ``` ### 8.7 Transaction Costs Actions accept `transaction_cost` and `transaction_cost_exit` parameters. There are three transaction cost models, which can be used individually or combined. #### ConstantTransactionModel — Fixed Cost per Transaction A flat cash amount charged every time a trade is entered (or exited). ```python from gs_quant.backtests.backtest_objects import ConstantTransactionModel from gs_quant.backtests.actions import AddTradeAction # $500 flat cost on entry; same cost on exit (default) action = AddTradeAction( instrument, trade_duration='1m', transaction_cost=ConstantTransactionModel(500), ) # Different cost for entry vs exit action = AddTradeAction( instrument, trade_duration='1m', transaction_cost=ConstantTransactionModel(500), # entry cost transaction_cost_exit=ConstantTransactionModel(250), # exit cost ) ``` #### ScaledTransactionModel — Cost Proportional to an Instrument Attribute or Risk The cost is computed by reading an instrument attribute (e.g. `notional_amount`) or calculating a risk measure, then multiplying by a `scaling_level`. **Scaling by instrument attribute** (e.g. notional): ```python from gs_quant.backtests.backtest_objects import ScaledTransactionModel # Cost = notional_amount × 0.0001 (i.e. 1bp of notional) action = AddTradeAction( instrument, trade_duration='1m', transaction_cost=ScaledTransactionModel( scaling_type='notional_amount', # any instrument property name scaling_level=0.0001, # multiplier applied to the attribute value ), ) ``` **Scaling by a risk measure** (e.g. dollar price, vega): ```python from gs_quant.backtests.backtest_objects import ScaledTransactionModel from gs_quant.risk import Price, IRVega # Cost = |Price| × 0.01 (i.e. 1% of premium) action = AddTradeAction( instrument, trade_duration='1m', transaction_cost=ScaledTransactionModel( scaling_type=Price, # a RiskMeasure — will be calculated on the instrument scaling_level=0.01, ), ) # Cost = |IRVega| × 0.05 action = AddTradeAction( instrument, trade_duration='1m', transaction_cost=ScaledTransactionModel( scaling_type=IRVega, scaling_level=0.05, ), ) ``` The formula is: **cost = |scaling_type value| × scaling_level**. The absolute value is always taken for risk-based costs. #### AggregateTransactionModel — Combine Multiple Models Combines multiple transaction models using SUM, MAX, or MIN aggregation. ```python from gs_quant.backtests.backtest_objects import ( AggregateTransactionModel, ConstantTransactionModel, ScaledTransactionModel, ) # Total cost = fixed $100 + 0.5bp of notional combined = AggregateTransactionModel( transaction_models=( ConstantTransactionModel(100), ScaledTransactionModel('notional_amount', 0.00005), ), # aggregate_type defaults to TransactionAggType.SUM ) action = AddTradeAction( instrument, trade_duration='1m', transaction_cost=combined, ) ``` ```python from gs_quant.backtests.backtest_objects import AggregateTransactionModel, TransactionAggType # Pay the MAX of a fixed cost or a scaled cost (e.g. minimum fee with proportional cost) floor_model = AggregateTransactionModel( transaction_models=( ConstantTransactionModel(1000), # minimum $1000 ScaledTransactionModel('notional_amount', 0.0001), # or 1bp of notional ), aggregate_type=TransactionAggType.MAX, ) ``` #### Transaction Costs on HedgeAction Transaction costs work the same way on `HedgeAction`: ```python action = HedgeAction( risk=hedge_risk, priceables=hedge_instrument, trade_duration='1m', transaction_cost=ScaledTransactionModel('notional_amount', 0.00005), transaction_cost_exit=ConstantTransactionModel(0), # no cost on hedge unwind ) ``` #### Viewing Transaction Costs in Results Transaction costs appear in `backtest.result_summary` as the `'Transaction Costs'` column (cumulative). They are also included in the `'Total'` column: ```python # Total = Price + Cumulative Cash + Transaction Costs backtest.result_summary[['Total', 'Transaction Costs']].plot(title='Performance & Transaction Costs') ``` --- ## 9. EquityVolEngine — Equity Vol Strategies The `EquityVolEngine` runs equity option and variance swap backtests server-side for better performance. ```python from gs_quant.instrument import EqOption, OptionType, OptionStyle from gs_quant.backtests.strategy import Strategy from gs_quant.backtests.triggers import PeriodicTrigger, PeriodicTriggerRequirements from gs_quant.backtests.actions import EnterPositionQuantityScaledAction from gs_quant.backtests.equity_vol_engine import EquityVolEngine from gs_quant.target.backtests import BacktestTradingQuantityType option = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, ) action = EnterPositionQuantityScaledAction( priceables=option, trade_duration='1m', trade_quantity=1000, trade_quantity_type=BacktestTradingQuantityType.quantity, ) trig_req = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m') trigger = PeriodicTrigger(trig_req, action) strategy = Strategy(None, trigger) engine = EquityVolEngine() backtest = engine.run_backtest(strategy, start=start_date, end=end_date) ``` --- ## 10. Quick Reference — Minimal Backtest Template ```python from datetime import date from gs_quant.session import GsSession from gs_quant.instrument import IRSwaption from gs_quant.common import Currency from gs_quant.backtests.triggers import PeriodicTrigger, PeriodicTriggerRequirements from gs_quant.backtests.actions import AddTradeAction from gs_quant.backtests.generic_engine import GenericEngine from gs_quant.backtests.strategy import Strategy from gs_quant.risk import Price GsSession.use() start_date = date(2023, 1, 3) end_date = date(2024, 12, 31) # 1. Define the instrument instrument = IRSwaption('Pay', '10y', Currency.USD, expiration_date='6m', name='6m10y') # 2. Define trigger + action trig_req = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='6m') action = AddTradeAction(instrument, trade_duration='6m') trigger = PeriodicTrigger(trig_req, action) # 3. Build strategy strategy = Strategy(None, trigger) # 4. Run GE = GenericEngine() backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True) # 5. View results backtest.result_summary['Total'].plot(title='Performance') backtest.trade_ledger() ``` ================================================ FILE: gs_quant/backtests/__init__.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from .core import * from .strategy_systematic import StrategySystematic from gs_quant.target.backtests import DeltaHedgeParameters ================================================ FILE: gs_quant/backtests/action_handler.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from abc import abstractmethod from typing import Union, Iterable, Any, TypeVar from gs_quant.backtests.actions import TAction from gs_quant.backtests.backtest_objects import TBaseBacktest class ActionHandler: def __init__(self, action: TAction) -> None: self._action = action @property def action(self) -> TAction: return self._action @abstractmethod def apply_action( self, state: Union[dt.date, Iterable[dt.date]], backtest: TBaseBacktest, trigger_info: Any, ) -> Any: pass TActionHandler = TypeVar('TActionHandler', bound='ActionHandler') class ActionHandlerBaseFactory: @abstractmethod def get_action_handler(self, action: TAction) -> TActionHandler: pass ================================================ FILE: gs_quant/backtests/actions.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import warnings from collections import namedtuple from dataclasses import field, dataclass from enum import Enum from typing import List, Optional, Iterable, Union, Callable from typing import TypeVar, ClassVar from dataclasses_json import config, dataclass_json from gs_quant.backtests.backtest_objects import ConstantTransactionModel, TransactionModel from gs_quant.backtests.backtest_utils import make_list, CalcType, CustomDuration from gs_quant.base import Priceable, static_field from gs_quant.common import RiskMeasure from gs_quant.instrument import Instrument from gs_quant.json_convertors import ( decode_named_instrument, dc_decode, encode_named_instrument, decode_date_or_str, decode_dict_date_key_or_float, ) from gs_quant.json_convertors_common import decode_risk_measure, encode_risk_measure from gs_quant.markets.portfolio import Portfolio from gs_quant.risk.transform import Transformer from gs_quant.target.backtests import BacktestTradingQuantityType action_count = 1 Duration = Union[str, dt.date, dt.timedelta, CustomDuration] def default_transaction_cost(): return ConstantTransactionModel(0) class ScalingActionType(Enum): risk_measure = 'risk_measure' size = 'size' NAV = 'NAV' @dataclass_json @dataclass class Action(object): _needs_scaling = False _calc_type = CalcType.simple _risk = None _transaction_cost = ConstantTransactionModel(0) _transaction_cost_exit = None name = None __sub_classes: ClassVar[List[type]] = [] def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) Action.__sub_classes.append(cls) @staticmethod def sub_classes(): return tuple(Action.__sub_classes) def __post_init__(self): self.set_name(self.name) @property def calc_type(self): return self._calc_type @property def risk(self): return self._risk def set_name(self, name: str): global action_count if self.name is None: self.name = 'Action{}'.format(action_count) action_count += 1 @property def transaction_cost(self): return self._transaction_cost @transaction_cost.setter def transaction_cost(self, value): self._transaction_cost = value @property def transaction_cost_exit(self): return self._transaction_cost_exit @transaction_cost_exit.setter def transaction_cost_exit(self, value): self._transaction_cost_exit = value TAction = TypeVar('TAction', bound='Action') @dataclass_json @dataclass class AddTradeAction(Action): """ create an action which adds a trade when triggered. The trades are resolved on the trigger date (state) and last until the trade_duration if specified or for all future dates if not. :param priceables: a priceable or a list of pricables. :param trade_duration: an instrument attribute eg. 'expiration_date' or a date or a tenor or timedelta if left as None the trade will be added for all future dates can also specify 'next schedule' in order to exit at the next periodic trigger date :param name: optional additional name to the priceable name :param transaction_cost: optional a cash amount paid for each transaction :param transaction_cost_exit: optionally specify a different model for exits; defaults to entry cost if None """ priceables: Union[Instrument, Iterable[Instrument]] = field( default=None, metadata=config(decoder=decode_named_instrument, encoder=encode_named_instrument) ) trade_duration: Duration = field( default=None, # de/encoder doesn't handle timedelta metadata=config(decoder=decode_date_or_str), ) name: str = None transaction_cost: TransactionModel = field( default_factory=default_transaction_cost, metadata=config(decoder=dc_decode(ConstantTransactionModel)) ) transaction_cost_exit: Optional[TransactionModel] = field( default=None, metadata=config(decoder=dc_decode(ConstantTransactionModel)) ) holiday_calendar: Iterable[dt.date] = None class_type: str = static_field('add_trade_action') def __post_init__(self): super().__post_init__() self._dated_priceables = {} named_priceables = [] for i, p in enumerate(make_list(self.priceables)): if p.name is None: named_priceables.append(p.clone(name=f'{self.name}_Priceable{i}')) elif p.name.startswith(self.name): named_priceables.append(p) else: named_priceables.append(p.clone(name=f'{self.name}_{p.name}')) self.priceables = named_priceables if self.transaction_cost is None: self.transaction_cost = ConstantTransactionModel(0) if self.transaction_cost_exit is None: self.transaction_cost_exit = self.transaction_cost def set_dated_priceables(self, state, priceables): self._dated_priceables[state] = make_list(priceables) @property def dated_priceables(self): return self._dated_priceables AddTradeActionInfo = namedtuple('AddTradeActionInfo', ['scaling', 'next_schedule']) HedgeActionInfo = namedtuple('HedgeActionInfo', 'next_schedule') ExitTradeActionInfo = namedtuple('ExitTradeActionInfo', 'not_applicable') RebalanceActionInfo = namedtuple('RebalanceActionInfo', 'not_applicable') AddScaledTradeActionInfo = namedtuple('AddScaledActionInfo', 'next_schedule') AddWeightedTradeActionInfo = namedtuple('AddWeightedActionInfo', 'next_schedule') @dataclass_json @dataclass class AddScaledTradeAction(Action): """ create an action which adds a trade when triggered. The trade is scaled by a measure or trade property. The trades are resolved on the trigger date (state) and last until the trade_duration if specified or for all future dates if not. :param priceables: a priceable or a list of pricables. :param trade_duration: an instrument attribute eg. 'expiration_date' or a date or a tenor or timedelta if left as None the trade will be added for all future dates can also specify 'next schedule' in order to exit at the next periodic trigger date :param name: optional additional name to the priceable name :param scaling_type: the type of scaling we are doing :param scaling_risk: if the scaling type is a measure then this is the definition of the measure :param scaling_level: the level of scaling to be done :param transaction_cost: optional a cash amount paid for each transaction :param transaction_cost_exit: optionally specify a different model for exits; defaults to entry cost if None """ priceables: Union[Priceable, Iterable[Priceable]] = field( default=None, metadata=config(decoder=decode_named_instrument, encoder=encode_named_instrument) ) trade_duration: Duration = field( default=None, # de/encoder doesn't handle timedelta metadata=config(decoder=decode_date_or_str), ) name: str = None scaling_type: ScalingActionType = ScalingActionType.size scaling_risk: RiskMeasure = None scaling_level: Union[float, dict[dt.date, Union[float, int]]] = field( default=1, metadata=config(decoder=decode_dict_date_key_or_float) ) transaction_cost: TransactionModel = field( default_factory=default_transaction_cost, metadata=config(decoder=dc_decode(ConstantTransactionModel)) ) transaction_cost_exit: Optional[TransactionModel] = field( default=None, metadata=config(decoder=dc_decode(ConstantTransactionModel)) ) holiday_calendar: Iterable[dt.date] = None dated_priceables: dict[dt.date, Priceable] = None class_type: str = static_field('add_scaled_trade_action') def __post_init__(self): super().__post_init__() named_priceables = [] for i, p in enumerate(make_list(self.priceables)): if p.name is None: named_priceables.append(p.clone(name=f'{self.name}_Priceable{i}')) elif p.name.startswith(self.name): named_priceables.append(p) else: named_priceables.append(p.clone(name=f'{self.name}_{p.name}')) self.priceables = named_priceables if self.transaction_cost_exit is None: self.transaction_cost_exit = self.transaction_cost @dataclass_json @dataclass class AddWeightedTradeAction(Action): """ create an action which adds trades when triggered. The trades are weighted by a measure. The trades are resolved on the trigger date (state) and last until the trade_duration if specified or for all future dates if not. :param priceables: a portfolio. :param trade_duration: an instrument attribute eg. 'expiration_date' or a date or a tenor or timedelta if left as None the trades will be added for all future dates can also specify 'next schedule' in order to exit at the next periodic trigger date :param name: optional additional name to the priceable name :param scaling_risk: if the scaling type is a measure then this is the definition of the measure :param total_size: the total notional that we are scaling to :param transaction_cost: optional a cash amount paid for each transaction :param transaction_cost_exit: optionally specify a different model for exits; defaults to entry cost if None :param holiday_calendar: optional an iterable list of holiday dates """ priceables: Portfolio = field( default=None, metadata=config(decoder=decode_named_instrument, encoder=encode_named_instrument) ) trade_duration: Duration = field( default=None, # de/encoder doesn't handle timedelta metadata=config(decoder=decode_date_or_str), ) name: str = None scaling_risk: RiskMeasure = None total_size: float = 100000.0 transaction_cost: TransactionModel = field( default_factory=default_transaction_cost, metadata=config(decoder=dc_decode(ConstantTransactionModel)) ) transaction_cost_exit: Optional[TransactionModel] = field( default=None, metadata=config(decoder=dc_decode(ConstantTransactionModel)) ) holiday_calendar: Iterable[dt.date] = None class_type: str = static_field('add_weighted_trade_action') def __post_init__(self): super().__post_init__() self._calc_type = CalcType.semi_path_dependent named_priceables = [] for i, p in enumerate(make_list(self.priceables)): if p.name is None: named_priceables.append(p.clone(name=f'{self.name}_Priceable{i}')) elif p.name.startswith(self.name): named_priceables.append(p) else: named_priceables.append(p.clone(name=f'{self.name}_{p.name}')) self.priceables = named_priceables if self.transaction_cost_exit is None: self.transaction_cost_exit = self.transaction_cost @dataclass_json @dataclass class EnterPositionQuantityScaledAction(Action): """ create an action which enters trades when triggered. The trades are executed with specified quantity and last until the trade_duration if specified, or for all future dates if not. :param priceables: a priceable or a list of pricables. :param trade_duration: an instrument attribute eg. 'expiration_date' or a date or a tenor if left as None the trade will be added for all future dates :param name: optional additional name to the priceable name :param trade_quantity: the amount, in units of trade_quantity_type to be traded :param trade_quantity_type: the quantity type used to scale trade. eg. quantity for units, notional for underlier notional :param transaction_cost: optional a cash amount paid for each transaction :param transaction_cost_exit: optionally specify a different model for exits; defaults to entry cost if None """ priceables: Union[Priceable, Iterable[Priceable]] = field( default=None, metadata=config(decoder=decode_named_instrument, encoder=encode_named_instrument) ) trade_duration: Duration = field( default=None, # de/encoder doesn't handle timedelta metadata=config(decoder=decode_date_or_str), ) name: str = None trade_quantity: Union[float, dict[dt.date, Union[float, int]]] = field( default=1, metadata=config(decoder=decode_dict_date_key_or_float) ) trade_quantity_type: BacktestTradingQuantityType = BacktestTradingQuantityType.quantity transaction_cost: TransactionModel = field( default_factory=default_transaction_cost, metadata=config(decoder=dc_decode(ConstantTransactionModel)) ) transaction_cost_exit: Optional[TransactionModel] = field( default=None, metadata=config(decoder=dc_decode(ConstantTransactionModel)) ) class_type: str = static_field('enter_position_quantity_scaled_action') def __post_init__(self): super().__post_init__() named_priceables = [] for i, p in enumerate(make_list(self.priceables)): if p.name is None: named_priceables.append(p.clone(name=f'{self.name}_Priceable{i}')) elif p.name.startswith(self.name): named_priceables.append(p) else: named_priceables.append(p.clone(name=f'{self.name}_{p.name}')) self.priceables = named_priceables if self.transaction_cost_exit is None: self.transaction_cost_exit = self.transaction_cost @dataclass_json @dataclass class ExitPositionAction(Action): name: str = None class_type: str = 'exit_position_action' @dataclass_json @dataclass class ExitTradeAction(Action): priceable_names: Union[str, Iterable[str]] = None name: str = None transaction_cost: TransactionModel = field( default_factory=default_transaction_cost, metadata=config(decoder=dc_decode(ConstantTransactionModel)) ) class_type: str = static_field('exit_trade_action') def __post_init__(self): super().__post_init__() self.priceables_names = make_list(self.priceable_names) @dataclass_json @dataclass class ExitAllPositionsAction(ExitTradeAction): """ Fully exit all held positions """ class_type: str = static_field('exit_all_positions_action') def __post_init__(self): super().__post_init__() self._calc_type = CalcType.path_dependent @dataclass_json @dataclass class HedgeAction(Action): """ create an action which adds a hedge trade when triggered. This trade will be scaled to hedge the risk specified. The trades are resolved on the trigger date (state) and last until the trade_duration if specified or for all future dates if not. :param risk: a risk measure which should be hedged :param priceables: a priceable or a list of pricables these should have sensitivity to the risk. :param trade_duration: an instrument attribute eg. 'expiration_date' or a date or a tenor or timedelta if left as None the trade will be added for all future dates can also specify 'next schedule' in order to exit at the next periodic trigger date :param name: optional additional name to the priceable name :param transaction_cost: optional a cash amount paid for each transaction :param transaction_cost_exit: optionally specify a different model for exits; defaults to entry cost if None :param risk_transformation: optional a Transformer which will be applied to the raw risk numbers before hedging :param holiday_calendar: optional an iterable list of holiday dates :param risk_percentage: proportion of risk to hedge expressed as a percentage. Default is 100% """ risk: RiskMeasure = field(default=None, metadata=config(decoder=decode_risk_measure, encoder=encode_risk_measure)) priceables: Optional[Priceable] = field( default=None, metadata=config(decoder=decode_named_instrument, encoder=encode_named_instrument) ) trade_duration: Duration = field( default=None, # de/encoder doesn't handle timedelta metadata=config(decoder=decode_date_or_str), ) name: str = None csa_term: str = None scaling_parameter: str = 'notional_amount' transaction_cost: TransactionModel = field( default_factory=default_transaction_cost, metadata=config(decoder=dc_decode(ConstantTransactionModel)) ) transaction_cost_exit: Optional[TransactionModel] = field( default=None, metadata=config(decoder=dc_decode(ConstantTransactionModel)) ) risk_transformation: Transformer = None holiday_calendar: Iterable[dt.date] = None risk_percentage: float = 100 class_type: str = static_field('hedge_action') def __post_init__(self): super().__post_init__() self._calc_type = CalcType.semi_path_dependent portfolio = ( self.priceables if isinstance(self.priceables, Portfolio) else Portfolio(self.priceables.clone(name=None), name=self.priceables.name) if isinstance(self.priceables, Priceable) else None ) if not Portfolio: raise RuntimeError('hedge action only accepts one trade or one portfolio') named_priceables = [] for i, priceable in enumerate(portfolio): if priceable.name is None: named_priceables.append(priceable.clone(name=f'{self.name}_Priceable{i}')) elif priceable.name.startswith(self.name): named_priceables.append(priceable) else: named_priceables.append(priceable.clone(name=f'{self.name}_{priceable.name}')) named_priceable = Portfolio(named_priceables, name=portfolio.name) self.priceables = named_priceable if self.transaction_cost_exit is None: self.transaction_cost_exit = self.transaction_cost if self.scaling_parameter != 'notional_amount': warnings.warn( 'HedgeAction.scaling_parameter is deprecated. It is no longer used and will be removed ' 'in a future release', DeprecationWarning, stacklevel=2, ) @property def priceable(self): return self.priceables @dataclass_json @dataclass class RebalanceAction(Action): priceable: Priceable = field( default=None, metadata=config(decoder=decode_named_instrument, encoder=encode_named_instrument) ) size_parameter: Union[str, float] = None method: Callable = None transaction_cost: TransactionModel = field( default_factory=default_transaction_cost, metadata=config(decoder=dc_decode(ConstantTransactionModel)) ) transaction_cost_exit: Optional[TransactionModel] = field( default=None, metadata=config(decoder=dc_decode(ConstantTransactionModel)) ) name: str = None def __post_init__(self): super().__post_init__() self._calc_type = CalcType.path_dependent if self.priceable.unresolved is None: raise ValueError("Please specify a resolved priceable to rebalance.") if self.priceable is not None: if self.priceable.name is None: self.priceable = self.priceable.clone(name=f'{self.name}_Priceable0') else: self.priceable = self.priceable.clone(name=f'{self.name}_{self.priceable.name}') if self.transaction_cost_exit is None: self.transaction_cost_exit = self.transaction_cost ================================================ FILE: gs_quant/backtests/backtest_engine.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from abc import abstractmethod from gs_quant.backtests.action_handler import TActionHandler from gs_quant.backtests.actions import TAction class BacktestBaseEngine: @abstractmethod def get_action_handler(self, action: TAction) -> TActionHandler: pass ================================================ FILE: gs_quant/backtests/backtest_objects.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from abc import ABC from collections import defaultdict from copy import deepcopy from dataclasses import dataclass, field from dataclasses_json import dataclass_json, config from enum import Enum from queue import Queue as FifoQueue from typing import Iterable, TypeVar, Optional, Union, Callable, Tuple, ClassVar import datetime as dt import numpy as np import pandas as pd from gs_quant.backtests.backtest_utils import make_list from gs_quant.backtests.core import ValuationMethod from gs_quant.backtests.data_handler import DataHandler from gs_quant.backtests.data_sources import DataSource, GenericDataSource, MissingDataStrategy from gs_quant.backtests.event import FillEvent from gs_quant.backtests.order import OrderBase, OrderCost from gs_quant.base import field_metadata, static_field from gs_quant.common import RiskMeasure from gs_quant.datetime.relative_date import RelativeDate from gs_quant.instrument import Cash, IRSwap, Instrument from gs_quant.json_convertors import dc_decode from gs_quant.markets import PricingContext from gs_quant.markets.portfolio import Portfolio from gs_quant.risk import ErrorValue, Cashflows from gs_quant.risk.transform import Transformer from gs_quant.risk.results import PricingFuture, PortfolioRiskResult class BaseBacktest(ABC): pass TBaseBacktest = TypeVar('TBaseBacktest', bound='BaseBacktest') class TransactionAggType(Enum): SUM = 'sum' MAX = 'max' MIN = 'min' @dataclass_json() @dataclass class PnlAttribute: attribute_name: str attribute_metric: RiskMeasure market_data_metric: RiskMeasure scaling_factor: float second_order: bool = False def get_risks(self): return [self.attribute_metric, self.market_data_metric] @dataclass_json() @dataclass class PnlDefinition: attributes: Iterable[PnlAttribute] def get_risks(self): return [risk for attribute in self.attributes for risk in attribute.get_risks()] @dataclass_json @dataclass class BackTest(BaseBacktest): CUMULATIVE_CASH_COLUMN: ClassVar[str] = "Cumulative Cash" TRANSACTION_COSTS_COLUMN: ClassVar[str] = "Transaction Costs" TOTAL_COLUMN: ClassVar[str] = "Total" strategy: object states: Iterable risks: Iterable[RiskMeasure] price_measure: RiskMeasure holiday_calendar: Iterable[dt.date] = None pnl_explain_def: Optional[PnlDefinition] = None def __post_init__(self): self._portfolio_dict = defaultdict(Portfolio) # portfolio by state self._cash_dict = {} # cash by state self._hedges = defaultdict(list) # list of Hedge by date self._weighted_trades = defaultdict(list) # list of WeightedTrade by date self._cash_payments = defaultdict(list) # list of cash payments (entry, unwind) self._transaction_costs = defaultdict(int) # list of transaction costs by date self._transaction_cost_entries = defaultdict(list) # entries tracking transaction costs by state self.strategy = deepcopy(self.strategy) # the strategy definition self._results = defaultdict(list) self._trade_exit_risk_results = defaultdict(list) self.risks = make_list(self.risks) # list of risks to calculate self._risk_summary_dict = None # Summary dict shared between output views, only initialized once self._calc_calls = 0 self._calculations = 0 @property def cash_dict(self): return self._cash_dict @property def portfolio_dict(self): return self._portfolio_dict @portfolio_dict.setter def portfolio_dict(self, portfolio_dict): self._portfolio_dict = portfolio_dict @property def cash_payments(self): return self._cash_payments @cash_payments.setter def cash_payments(self, cash_payments): self._cash_payments = cash_payments @property def transaction_costs(self): return self._transaction_costs @transaction_costs.setter def transaction_costs(self, transaction_costs): self._transaction_costs = transaction_costs @property def transaction_cost_entries(self): return self._transaction_cost_entries @property def hedges(self): return self._hedges @hedges.setter def hedges(self, hedges): self._hedges = hedges @property def weighted_trades(self): return self._weighted_trades @weighted_trades.setter def weighted_trades(self, weighted_trades): self._weighted_trades = weighted_trades @property def results(self): return self._results def set_results(self, date, results): self._results[date] = results def add_results(self, date, results, replace=False): if date in self._results and len(self._results[date]) and not replace: self._results[date] += results else: self._results[date] = results @property def trade_exit_risk_results(self): return self._trade_exit_risk_results @property def calc_calls(self): return self._calc_calls @calc_calls.setter def calc_calls(self, calc_calls): self._calc_calls = calc_calls @property def calculations(self): return self._calculations @calculations.setter def calculations(self, calculations): self._calculations = calculations def get_risk_summary_df(self, zero_on_empty_dates=False): if self._risk_summary_dict is not None: summary_dict = self._risk_summary_dict else: if not self._results: return pd.DataFrame(columns=self.risks) dates_with_results = list(filter(lambda x: len(x[1]), self._results.items())) summary_dict = defaultdict(dict) for date, results in dates_with_results: for risk in results.risk_measures: try: value = results[risk].aggregate(True, True) except TypeError: value = ErrorValue(None, error='Could not aggregate risk results') summary_dict[date][risk] = value self._risk_summary_dict = summary_dict zero_risk_sd_copy = summary_dict.copy() if zero_on_empty_dates: for cash_only_date in set(self._cash_dict.keys()).difference(zero_risk_sd_copy.keys()): for risk in self.risks: zero_risk_sd_copy[cash_only_date][risk] = 0 result = pd.DataFrame(zero_risk_sd_copy).T.sort_index() return result @property def result_summary(self): """ Get a dataframe showing the PV and other risks and cash on each day in the backtest """ summary = self.get_risk_summary_df() cash_summary = defaultdict(dict) for date, results in self._cash_dict.items(): for ccy, value in results.items(): cash_summary[f'Cumulative Cash {ccy}'][date] = value if len(cash_summary) > 1: raise RuntimeError('Cannot aggregate cash in multiple currencies') elif len(cash_summary) == 1: cash = pd.concat( [pd.Series(cash_dict, name=self.CUMULATIVE_CASH_COLUMN) for name, cash_dict in cash_summary.items()], axis=1, sort=True, ) else: cash = pd.DataFrame(columns=[self.CUMULATIVE_CASH_COLUMN]) transaction_costs = pd.Series(self.transaction_costs, name=self.TRANSACTION_COSTS_COLUMN) transaction_costs = transaction_costs.sort_index().cumsum() df = pd.concat([summary, cash, transaction_costs], axis=1, sort=True).ffill().fillna(0) df[self.TOTAL_COLUMN] = ( df[self.price_measure] + df[self.CUMULATIVE_CASH_COLUMN] + df[self.TRANSACTION_COSTS_COLUMN] ) return df[: self.states[-1]] @property def risk_summary(self): """ Get a dataframe showing the risks in the backtest with zero values for days with no instruments held """ return self.get_risk_summary_df(zero_on_empty_dates=True) def trade_ledger(self): # this is a ledger of each instrument when it was entered and when it was closed out. The cash associated # with the entry and exit are used in the open value and close value and PnL calc. If the PnL is None it # means the instrument is still live and therefore will show up in the PV ledger = {} names = [] for date in sorted(self.cash_payments.keys()): cash_list = self.cash_payments[date] for cash in cash_list: if cash.direction == 0: ledger[cash.trade.name] = { 'Open': date, 'Close': date, 'Open Value': 0, 'Close Value': 0, 'Long Short': cash.direction, 'Status': 'closed', 'Trade PnL': 0, } elif cash.trade.name in names: if len(cash.cash_paid) > 0: ledger[cash.trade.name]['Close'] = date ledger[cash.trade.name]['Close Value'] += sum(cash.cash_paid.values()) open_value = ledger[cash.trade.name]['Open Value'] ledger[cash.trade.name]['Trade PnL'] = ledger[cash.trade.name]['Close Value'] + open_value ledger[cash.trade.name]['Status'] = 'closed' else: names.append(cash.trade.name) ledger[cash.trade.name] = { 'Open': date, 'Close': None, 'Open Value': sum(cash.cash_paid.values()), 'Close Value': 0, 'Long Short': cash.direction, 'Status': 'open', 'Trade PnL': None, } return pd.DataFrame(ledger).T.sort_index() def strategy_as_time_series(self): """ Get a dataframe indexed by strategy dates and instruments present on the respective dates For each tradable, displays calculated risk measures, cash payment amount and ccy (if any) and static data. """ # Construct a table of cash payments for each date and concat them in a single table of all cash payments cp_table = pd.concat( [pd.concat([cp.to_frame() for cp in date_payments]) for _, date_payments in self.cash_payments.items()] ) cp_table = cp_table.set_index(['Pricing Date', 'Instrument Name']).sort_index() cp_table.columns = pd.MultiIndex.from_product([['Cash Payments'], cp_table.columns]) risk_measure_dict = { date: risk_res.to_frame(values='value', index='instrument_name', columns='risk_measure').assign( pricing_date=[date] * len(risk_res) ) for date, risk_res in self.results.items() } risk_measure_table = pd.concat(risk_measure_dict.values()) risk_measure_table = risk_measure_table.reset_index() risk_measure_table = risk_measure_table.rename( columns={'pricing_date': 'Pricing Date', 'instrument_name': 'Instrument Name'} ) risk_measure_table = risk_measure_table.set_index(['Pricing Date', 'Instrument Name']) risk_measure_table.columns = pd.MultiIndex.from_product( [['Risk Measures'], [str(col) for col in risk_measure_table.columns]] ) risk_and_cp_joined = risk_measure_table.join(cp_table, how='outer') static_inst_info = pd.concat([info.portfolio.to_frame() for info in self.results.values()]) static_inst_info = static_inst_info.rename(columns={'name': 'Instrument Name'}) static_inst_info = static_inst_info.set_index(['Instrument Name']) static_inst_info = static_inst_info[~static_inst_info.index.duplicated(keep='first')] static_inst_info.columns = pd.MultiIndex.from_product([['Static Instrument Data'], static_inst_info.columns]) result = static_inst_info.join(risk_and_cp_joined, how='outer') return result.sort_index() def pnl_explain(self): """ Get a dictionary of risk attributions which explain the pnl """ if self.pnl_explain_def is None: return None risk_results = self.results exit_risk_results = self.trade_exit_risk_results dates = sorted(set(risk_results.keys()).union(exit_risk_results.keys())) pnl_explain_results = {} for attribute in self.pnl_explain_def.attributes: result = {} cum_total = 0.0 for idx in range(1, len(dates)): metric_pnl = 0.0 cur_date = dates[idx] prev_date = dates[idx - 1] if prev_date not in risk_results: result[cur_date] = cum_total continue for prev_date_inst in risk_results[prev_date].portfolio.all_instruments: prev_date_risk = risk_results[prev_date][prev_date_inst][attribute.attribute_metric] if prev_date_risk == 0: continue prev_date_mkt_data = risk_results[prev_date][prev_date_inst][attribute.market_data_metric] if cur_date in risk_results and prev_date_inst in risk_results[cur_date].portfolio: cur_date_mkt_data = risk_results[cur_date][prev_date_inst][attribute.market_data_metric] else: cur_date_mkt_data = exit_risk_results[cur_date][prev_date_inst][attribute.market_data_metric] if attribute.second_order: metric_pnl += ( 0.5 * attribute.scaling_factor * prev_date_risk * (cur_date_mkt_data - prev_date_mkt_data) * (cur_date_mkt_data - prev_date_mkt_data) ) else: metric_pnl += ( attribute.scaling_factor * prev_date_risk * (cur_date_mkt_data - prev_date_mkt_data) ) cum_total += metric_pnl result[cur_date] = cum_total pnl_explain_results[attribute.attribute_name] = result return pnl_explain_results def summary_stats(self, annualisation_factor: int = 252) -> pd.Series: """ Compute summary statistics for the backtest useful for evaluating and comparing strategies. Returns a pandas Series with the following metrics: - **Total PnL**: final value of the Total performance series - **Total Transaction Costs**: cumulative transaction costs (always negative or zero) - **Total Trades**: number of trades entered during the backtest - **Start Date / End Date**: backtest date range - **Duration (days)**: calendar days in the backtest - **Annualised Return**: total return annualised assuming the given annualisation_factor - **Annualised Volatility**: standard deviation of daily P&L changes, annualised - **Sharpe Ratio**: annualised return / annualised volatility (assumes zero risk-free rate) - **Sortino Ratio**: annualised return / annualised downside deviation - **Max Drawdown**: largest peak-to-trough decline in the Total series - **Max Drawdown Duration (days)**: longest period (calendar days) spent in drawdown - **Calmar Ratio**: annualised return / |max drawdown| - **Average Daily PnL**: mean of daily PnL changes - **Daily PnL Std Dev**: standard deviation of daily PnL changes - **Best Day**: largest single-day gain - **Worst Day**: largest single-day loss - **% Positive Days**: proportion of days with positive PnL change - **Skewness**: skewness of daily PnL changes - **Kurtosis**: excess kurtosis of daily PnL changes - **Peak PnL**: highest Total value reached - **Current Drawdown**: drawdown at the end of the backtest :param annualisation_factor: number of business days per year (default 252) :return: pandas Series of summary statistics """ summary = self.result_summary if summary.empty: return pd.Series(dtype=float) total = summary[self.TOTAL_COLUMN] daily_pnl = total.diff().dropna() # Basic info start_date = total.index[0] end_date = total.index[-1] duration_days = (end_date - start_date).days num_periods = len(daily_pnl) # Total PnL and transaction costs total_pnl = total.iloc[-1] total_tc = summary[self.TRANSACTION_COSTS_COLUMN].iloc[-1] if self.TRANSACTION_COSTS_COLUMN in summary else 0 # Trade count try: ledger = self.trade_ledger() num_trades = len(ledger) except Exception: num_trades = np.nan # Annualised return and volatility avg_daily = daily_pnl.mean() std_daily = daily_pnl.std() ann_return = avg_daily * annualisation_factor ann_vol = std_daily * np.sqrt(annualisation_factor) # Sharpe ratio (excess return over zero risk-free rate) sharpe = ann_return / ann_vol if ann_vol != 0 else np.nan # Sortino ratio (downside deviation) downside = daily_pnl[daily_pnl < 0] downside_std = np.sqrt((downside**2).mean()) if len(downside) > 0 else 0.0 ann_downside = downside_std * np.sqrt(annualisation_factor) sortino = ann_return / ann_downside if ann_downside != 0 else np.nan # Drawdown analysis running_max = total.cummax() drawdown = total - running_max max_drawdown = drawdown.min() # Calmar ratio calmar = ann_return / abs(max_drawdown) if max_drawdown != 0 else np.nan # Current drawdown current_drawdown = drawdown.iloc[-1] # Peak PnL peak_pnl = running_max.iloc[-1] # Max drawdown duration (longest streak below the running max) in_drawdown = drawdown < 0 if in_drawdown.any(): dd_groups = (~in_drawdown).cumsum() dd_durations = in_drawdown.groupby(dd_groups).apply( lambda g: (g.index[-1] - g.index[0]).days if g.any() else 0 ) max_dd_duration = dd_durations.max() else: max_dd_duration = 0 # Best / worst day best_day = daily_pnl.max() worst_day = daily_pnl.min() # Percentage of positive days pct_positive = (daily_pnl > 0).sum() / num_periods * 100 if num_periods > 0 else np.nan # Higher moments skewness = daily_pnl.skew() kurtosis = daily_pnl.kurtosis() # excess kurtosis stats = pd.Series( { 'Start Date': start_date, 'End Date': end_date, 'Duration (days)': duration_days, 'Total PnL': total_pnl, 'Total Transaction Costs': total_tc, 'Total Trades': num_trades, 'Peak PnL': peak_pnl, 'Annualised Return': ann_return, 'Annualised Volatility': ann_vol, 'Sharpe Ratio': sharpe, 'Sortino Ratio': sortino, 'Max Drawdown': max_drawdown, 'Max Drawdown Duration (days)': max_dd_duration, 'Calmar Ratio': calmar, 'Current Drawdown': current_drawdown, 'Average Daily PnL': avg_daily, 'Daily PnL Std Dev': std_daily, 'Best Day': best_day, 'Worst Day': worst_day, '% Positive Days': pct_positive, 'Skewness': skewness, 'Kurtosis': kurtosis, } ) return stats class ScalingPortfolio: def __init__( self, trade, dates, risk, csa_term=None, risk_transformation: Transformer = None, risk_percentage: float = 100 ): self.trade = trade self.dates = dates self.risk = risk self.csa_term = csa_term self.risk_transformation = risk_transformation self.risk_percentage = risk_percentage self.results = None @dataclass_json() @dataclass(frozen=True) class TransactionModel: def get_unit_cost(self, state, info, instrument) -> float: pass @dataclass_json() @dataclass(frozen=True) class ConstantTransactionModel(TransactionModel): cost: Union[float, int] = 0 class_type: str = static_field('constant_transaction_model') def get_unit_cost(self, state, info, instrument) -> float: return self.cost @dataclass_json @dataclass(frozen=True) class ScaledTransactionModel(TransactionModel): scaling_type: Union[str, RiskMeasure] = 'notional_amount' scaling_level: Union[float, int] = 0.0001 class_type: str = static_field('scaled_transaction_model') def get_unit_cost(self, state, info, instrument) -> Union[float, PricingFuture]: if isinstance(self.scaling_type, str): try: return getattr(instrument, self.scaling_type) except AttributeError: raise RuntimeError(f'{self.scaling_type} not recognised for instrument {instrument.type}') if state > dt.date.today(): return np.nan with PricingContext(state): risk = instrument.calc(self.scaling_type) return risk @dataclass_json() @dataclass(frozen=True) class AggregateTransactionModel(TransactionModel): transaction_models: tuple = tuple() aggregate_type: TransactionAggType = field(default=TransactionAggType.SUM, metadata=field_metadata) def get_unit_cost(self, state, info, instrument) -> float: if not self.transaction_models: return 0 if self.aggregate_type == TransactionAggType.SUM: return sum(model.get_unit_cost(state, info, instrument) for model in self.transaction_models) elif self.aggregate_type == TransactionAggType.MAX: return max(model.get_unit_cost(state, info, instrument) for model in self.transaction_models) elif self.aggregate_type == TransactionAggType.MIN: return min(model.get_unit_cost(state, info, instrument) for model in self.transaction_models) else: raise RuntimeError(f'unrecognised aggregation type:{str(self.aggregation_type)}') class TransactionCostEntry: """ Stateful wrapper around TransactionModel used in the Generic Engine. Used to link costs to CashPayments, which can be scaled throughout the backtest (e.g. hedges), and to resolve risk-based costs under the same PricingContext for efficiency. """ def __init__(self, date: dt.date, instrument: Instrument, transaction_model: TransactionModel): self._date = date self._instrument = instrument self._transaction_model = transaction_model self._unit_cost_by_model_by_inst = {} self._additional_scaling = 1 @property def all_instruments(self) -> Tuple[Instrument, ...]: return self._instrument.all_instruments if isinstance(self._instrument, Portfolio) else (self._instrument,) @property def all_transaction_models(self): return ( self._transaction_model.transaction_models if isinstance(self._transaction_model, AggregateTransactionModel) else (self._transaction_model,) ) @property def cost_aggregation_func(self) -> Callable: if isinstance(self._transaction_model, AggregateTransactionModel): if self._transaction_model.aggregate_type is TransactionAggType.SUM: return sum if self._transaction_model.aggregate_type is TransactionAggType.MAX: return max if self._transaction_model.aggregate_type is TransactionAggType.MIN: return min return sum @property def additional_scaling(self): return self._additional_scaling @additional_scaling.setter def additional_scaling(self, value: float): self._additional_scaling = value @property def date(self): return self._date @date.setter def date(self, value: dt.date): self._date = value @property def no_of_risk_calcs(self) -> int: return len( [ m for m in self.all_transaction_models if isinstance(m, ScaledTransactionModel) and isinstance(m.scaling_type, RiskMeasure) ] ) def calculate_unit_cost(self): for m in self.all_transaction_models: self._unit_cost_by_model_by_inst[m] = {} for i in self.all_instruments: self._unit_cost_by_model_by_inst[m][i] = m.get_unit_cost(self._date, None, i) @staticmethod def __resolved_cost(cost: Union[float, PricingFuture]) -> float: if isinstance(cost, PortfolioRiskResult): return cost.aggregate() elif isinstance(cost, PricingFuture): return cost.result() return cost def get_final_cost(self): final_costs = [] for m in self.all_transaction_models: # charges may net out for portfolios cost = sum(self.__resolved_cost(self._unit_cost_by_model_by_inst[m][i]) for i in self.all_instruments) if isinstance(m, ScaledTransactionModel): cost = m.scaling_level * abs(cost * self._additional_scaling) final_costs.append(cost) return self.cost_aggregation_func(final_costs) if final_costs else 0 def get_cost_by_component(self) -> Tuple[float, float]: fixed_costs = [] scaled_costs = [] for m in self.all_transaction_models: # charges may net out for portfolios cost = sum(self.__resolved_cost(self._unit_cost_by_model_by_inst[m][i]) for i in self.all_instruments) if isinstance(m, ScaledTransactionModel): cost = abs(cost * m.scaling_level * self._additional_scaling) scaled_costs.append(cost) else: fixed_costs.append(cost) fixed_cost = self.cost_aggregation_func(fixed_costs) if fixed_costs else None scaled_cost = self.cost_aggregation_func(scaled_costs) if scaled_costs else None if scaled_cost is None: return fixed_cost, 0 elif fixed_cost is None: return 0, scaled_cost if self.cost_aggregation_func is sum: return fixed_cost, scaled_cost else: # min or max if self.cost_aggregation_func([fixed_cost, scaled_cost]) == fixed_cost: return fixed_cost, 0 elif self.cost_aggregation_func([fixed_cost, scaled_cost]) == scaled_cost: return 0, scaled_cost else: raise ValueError(f"Unable to split cost for aggregation function {self.cost_aggregation_func}") class CashPayment: def __init__( self, trade, effective_date=None, direction=1, transaction_cost_entry: Optional[TransactionCostEntry] = None ): self.trade = trade self.effective_date = effective_date self.direction = direction self.cash_paid = defaultdict(float) self.transaction_cost_entry = transaction_cost_entry def to_frame(self): df = pd.DataFrame(self.cash_paid.items(), columns=['Cash Ccy', 'Cash Amount']) df['Instrument Name'] = self.trade.name df['Pricing Date'] = self.effective_date return df class Hedge: def __init__( self, scaling_portfolio: ScalingPortfolio, entry_payment: CashPayment, exit_payment: Optional[CashPayment] ): self.scaling_portfolio = scaling_portfolio self.entry_payment = entry_payment self.exit_payment = exit_payment class WeightedScalingPortfolio: """ Similar to ScalingPortfolio but for weighted trade actions where each instrument in the portfolio is scaled to have equal risk contribution. """ def __init__( self, trades: Portfolio, dates: list, risk: RiskMeasure, total_size: float, csa_term=None, ): self.trades = trades # Portfolio of instruments to be weighted self.dates = dates self.risk = risk # The risk measure used for weighting self.total_size = total_size # Total notional to distribute equally by risk self.csa_term = csa_term self.results = None # Will store risk calculation results class WeightedTrade: """ Represents a weighted trade entry containing the scaling portfolio and cash payments. Similar to Hedge but for weighted trade actions. """ def __init__( self, scaling_portfolio: WeightedScalingPortfolio, entry_payments: list, # List of CashPayment for each instrument exit_payments: list, # List of CashPayment for each instrument (or None) ): self.scaling_portfolio = scaling_portfolio self.entry_payments = entry_payments self.exit_payments = exit_payments @dataclass_json @dataclass class PredefinedAssetBacktest(BaseBacktest): data_handler: DataHandler initial_value: float def __post_init__(self): self.performance = pd.Series(dtype=float) self.cash_asset = Cash('USD') self.holdings = defaultdict(float) self.historical_holdings = pd.Series(dtype=float) self.historical_weights = pd.Series(dtype=float) self.orders = [] self.results = {} def set_start_date(self, start: dt.date): self.performance[start] = self.initial_value self.holdings[self.cash_asset] = self.initial_value def record_orders(self, orders: Iterable[OrderBase]): self.orders.extend(orders) def update_fill(self, fill: FillEvent): inst = fill.order.instrument self.holdings[self.cash_asset] -= fill.filled_price * fill.filled_units self.holdings[inst] += fill.filled_units def trade_ledger(self): instrument_queues = {} order_pairs = {} for o in self.orders: if o.instrument not in instrument_queues: instrument_queues[o.instrument] = (FifoQueue(), FifoQueue()) # (longs, shorts) longs, shorts = instrument_queues[o.instrument] if o.quantity < 0: shorts.put(o) else: longs.put(o) # match up the longs and shorts for inst in instrument_queues.keys(): longs, shorts = instrument_queues[inst] open_close_order_pairs = [] while not longs.empty() and not shorts.empty(): long, short = longs.get(), shorts.get() open_order, close_order = ( (long, short) if long.execution_end_time() < short.execution_end_time() else (short, long) ) open_close_order_pairs.append((open_order, close_order)) # handle unmatched longs or shorts i.e. positions currently open while not longs.empty() or not shorts.empty(): unclosed_open_order = longs.get() if not longs.empty() else shorts.get() open_close_order_pairs.append((unclosed_open_order, None)) order_pairs[inst] = open_close_order_pairs trade_df = [] for inst in order_pairs.keys(): for open_order, close_order in [(o, c) for o, c in order_pairs[inst]]: if close_order: end_dt = close_order.execution_end_time() end_value = close_order.executed_price status = 'closed' else: end_dt = None end_value = None status = 'open' start_dt, open_value = open_order.execution_end_time(), open_order.executed_price long_or_short = np.sign(open_order.quantity) trade_df.append( ( inst, start_dt, end_dt, open_value, end_value, status, (end_value - open_value) * long_or_short if status == 'closed' else None, ) ) return pd.DataFrame( trade_df, columns=['Instrument', 'Open', 'Close', 'Open Value', 'Close Value', 'Status', 'Trade PnL'] ) def mark_to_market(self, state: dt.datetime, valuation_method: ValuationMethod): epsilon = 1e-12 date = state.date() mtm = 0 self.historical_holdings[date] = {} self.historical_weights[date] = {} for instrument, units in self.holdings.items(): if abs(units) > epsilon: self.historical_holdings[date][instrument] = units if isinstance(instrument, Cash): fixing = 1 else: tag, window = valuation_method.data_tag, valuation_method.window if window: start = dt.datetime.combine(state.date(), window.start) end = dt.datetime.combine(state.date(), window.end) fixings = self.data_handler.get_data_range(start, end, instrument, tag) fixing = np.mean(fixings) if len(fixings) else np.nan else: # no time window specified, use daily fixing fixing = self.data_handler.get_data(state.date(), instrument, tag) notional = fixing * units self.historical_weights[date][instrument] = notional mtm += notional self.performance[date] = mtm for instrument, notional in self.historical_weights[date].items(): self.historical_weights[date][instrument] = notional / mtm def get_level(self, date: dt.date) -> float: return self.performance[date] def get_costs(self) -> pd.Series(dtype=float): costs = defaultdict(float) for order in self.orders: if isinstance(order, OrderCost): costs[order.execution_end_time().date()] += order.execution_quantity(self.data_handler) return pd.Series(costs) def get_orders_for_date(self, date: dt.date) -> pd.DataFrame(): return pd.DataFrame( [order.to_dict(self.data_handler) for order in self.orders if order.execution_end_time().date() == date] ) @dataclass_json @dataclass class CashAccrualModel: class_type: str = static_field('cash_accrual_model') def get_accrued_value(self, current_value, to_state) -> dict: pass @dataclass_json @dataclass class ConstantCashAccrualModel(CashAccrualModel): rate: float = 0 annual: bool = True class_type: str = static_field('cash_accrual_model') def get_accrued_value(self, current_value, to_state) -> dict: new_value = {} from_state = current_value[1] days = (to_state - from_state).days for currency, value in current_value[0].items(): new_value[currency] = value * (1 + (self.rate / (365 if self.annual else 1))) ** days return new_value @dataclass_json @dataclass class DataCashAccrualModel(CashAccrualModel): data_source: DataSource = field( default=None, metadata=config(decoder=dc_decode(*DataSource.sub_classes(), allow_missing=True)) ) annual: bool = True class_type: str = static_field('cash_accrual_model') def get_accrued_value(self, current_value, to_state) -> dict: new_value = {} from_state = current_value[1] days = (to_state - from_state).days rate = self.data_source.get_data(from_state) for currency, value in current_value[0].items(): new_value[currency] = value * (1 + (rate / (365 if self.annual else 1))) ** days return new_value ois_fixings = {} @dataclass_json @dataclass class OisFixingCashAccrualModel(CashAccrualModel): start_date: Union[dt.date, str] = '-1y' end_date: Union[dt.date, str] = dt.date.today() class_type: str = static_field('ois_fixing_cash_accrual_model') def get_accrued_value(self, current_value, to_state) -> dict: for currency in current_value[0].keys(): if currency not in ois_fixings: start_date = ( self.start_date if isinstance(self.start_date, dt.date) else RelativeDate(self.start_date).apply_rule() ) start_date = start_date - dt.timedelta(days=7) swap = IRSwap( notional_currency=currency, floating_rate_frequency='1b', effective_date=start_date, termination_date=self.end_date if isinstance(self.end_date, dt.date) else RelativeDate(self.end_date).apply_rule(), floating_rate_option='OIS', ) with PricingContext(): result = swap.calc(Cashflows) ois_fixings[currency] = GenericDataSource( result.result()[result.result()['payment_type'] == 'Flt'].set_index(['accrual_start_date'])['rate'], MissingDataStrategy.fill_forward, ) ds_accrual_model = DataCashAccrualModel(ois_fixings[currency], True) return ds_accrual_model.get_accrued_value(current_value, to_state) ================================================ FILE: gs_quant/backtests/backtest_utils.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import pandas as pd from enum import Enum from dataclasses import dataclass from dataclasses_json import dataclass_json from typing import Callable, Tuple, Union from gs_quant.common import CurrencyName from gs_quant.datetime.relative_date import RelativeDate from gs_quant.instrument import Instrument from gs_quant.timeseries import interpolate, Interpolate class CalcType(Enum): simple = 'simple' semi_path_dependent = 'semi_path_dependent' path_dependent = 'path_dependent' @dataclass_json @dataclass class CustomDuration: durations: Tuple[Union[str, dt.date, dt.timedelta], ...] function: Callable[[Tuple[Union[str, dt.date, dt.timedelta], ...]], Union[str, dt.date, dt.timedelta]] def __hash__(self): return hash((self.durations, self.function)) def make_list(thing): if thing is None: return [] if isinstance(thing, str): return [thing] else: try: iter(thing) except TypeError: return [thing] else: return list(thing) final_date_cache = {} def get_final_date(inst, create_date, duration, holiday_calendar=None, trigger_info=None): cache_key = (inst, create_date, duration, holiday_calendar) if cache_key in final_date_cache: return final_date_cache[cache_key] if duration is None: final_date_cache[cache_key] = dt.date.max return dt.date.max if isinstance(duration, (dt.datetime, dt.date)): final_date_cache[cache_key] = duration return duration if hasattr(inst, str(duration)): final_date_cache[cache_key] = getattr(inst, str(duration)) return getattr(inst, str(duration)) if str(duration).lower() == 'next schedule': if hasattr(trigger_info, 'next_schedule'): return trigger_info.next_schedule or dt.date.max raise RuntimeError('Next schedule not supported by action') if isinstance(duration, CustomDuration): return duration.function( *(get_final_date(inst, create_date, d, holiday_calendar, trigger_info) for d in duration.durations) ) final_date_cache[cache_key] = RelativeDate(duration, create_date).apply_rule(holiday_calendar=holiday_calendar) return final_date_cache[cache_key] def scale_trade(inst: Instrument, ratio: float): new_inst = inst.scale(ratio) return new_inst def map_ccy_name_to_ccy(currency_name: Union[str, CurrencyName]): map = { 'United States Dollar': 'USD', 'Australian Dollar': 'AUD', 'Canadian Dollar': 'CAD', 'Swiss Franc': 'CHF', 'Yuan Renminbi (Hong Kong)': 'CNH', 'Czech Republic Koruna': 'CZK', 'Euro': 'EUR', 'Pound Sterling': 'GBP', 'Japanese Yen': 'JPY', 'South Korean Won': 'KRW', 'Malasyan Ringgit': 'MYR', 'Norwegian Krone': 'NOK', 'New Zealand Dollar': 'NZD', 'Polish Zloty': 'PLN', 'Russian Rouble': 'RUB', 'Swedish Krona': 'SEK', 'South African Rand': 'ZAR', 'Yuan Renminbi (Onshore)': 'CHY', } return map.get(currency_name.value if isinstance(currency_name, CurrencyName) else currency_name) def interpolate_signal(signal: dict[dt.date, float], method=Interpolate.STEP) -> pd.Series: min_date = min(signal.keys()) max_date = max(signal.keys()) all_dates = [min_date + dt.timedelta(days=day) for day in range((max_date - min_date).days + 1)] signal_curve = interpolate(pd.Series(signal).sort_index(), all_dates, method=method) return signal_curve ================================================ FILE: gs_quant/backtests/core.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from enum import Enum from typing import Tuple, NamedTuple, Union import datetime as dt from gs_quant.base import EnumBase from gs_quant.target.backtests import Backtest as __Backtest, BacktestResult from typing import Optional # TODO add these in Studio as a standalone JSON, so they will be generated class TradeInMethod(EnumBase, Enum): FixedRoll = 'fixedRoll' class Backtest(__Backtest): def get_results(self) -> Tuple[BacktestResult, ...]: from gs_quant.api.gs.backtests import GsBacktestApi return GsBacktestApi.get_results(backtest_id=self.id) class MarketModel(EnumBase, Enum): STICKY_FIXED_STRIKE = "SFK" STICKY_DELTA = "SD" class TimeWindow(NamedTuple): start: Union[dt.time, dt.datetime] = None end: Union[dt.time, dt.datetime] = None class ValuationFixingType(EnumBase, Enum): PRICE = 'price' class ValuationMethod(NamedTuple): data_tag: ValuationFixingType = ValuationFixingType.PRICE window: Optional[TimeWindow] = None ================================================ FILE: gs_quant/backtests/data_handler.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from typing import Union from gs_quant.backtests.data_sources import DataManager class Clock(object): def __init__(self): self._time = None self.reset() def update(self, time: dt.datetime): compare_time = ( self._time.replace(tzinfo=None) if time.tzinfo is None or time.tzinfo.utcoffset(time) is None else self._time ) if time < compare_time: raise RuntimeError(f'current time is {compare_time}, cannot run backwards to {time}') self._time = time def reset(self): self._time = dt.datetime(1900, 1, 1).replace(tzinfo=dt.timezone.utc) def time_check(self, state: Union[dt.date, dt.datetime]): if isinstance(state, dt.datetime): if state.tzinfo is None or state.tzinfo.utcoffset(state) is None: # timezone naive lookahead = state > self._time.replace(tzinfo=None) else: lookahead = state > self._time else: lookahead = state > self._time.date() if lookahead: raise RuntimeError(f'accessing data at {state} not allowed, current time is {self._time}') class DataHandler(object): def __init__(self, data_mgr: DataManager, tz: dt.timezone): self._data_mgr = data_mgr self._clock = Clock() self._tz = tz def reset_clock(self): self._clock.reset() def update(self, state: dt.datetime): self._clock.update(state) def _utc_time(self, state: Union[dt.date, dt.datetime]): # only switch to utc time if the datetime you've been sent is timezone naive if isinstance(state, dt.datetime) and (state.tzinfo is None or state.tzinfo.utcoffset(state) is None): return state.replace(tzinfo=self._tz).astimezone(dt.timezone.utc).replace(tzinfo=None) else: return state def get_data(self, state: Union[dt.date, dt.datetime], *key): self._clock.time_check(state) return self._data_mgr.get_data(self._utc_time(state), *key) def get_data_range(self, start: Union[dt.date, dt.datetime], end: Union[dt.date, dt.datetime], *key): self._clock.time_check(end) if type(start) is not type(end): raise RuntimeError('expect same type for start and end when asking for data range') return self._data_mgr.get_data_range(self._utc_time(start), self._utc_time(end), *key) ================================================ FILE: gs_quant/backtests/data_sources.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from dataclasses import dataclass, field from dataclasses_json import dataclass_json, config import datetime as dt from enum import Enum import numpy as np import pandas as pd from typing import Union, Iterable, ClassVar, List from gs_quant.backtests.core import ValuationFixingType from gs_quant.base import field_metadata, static_field from gs_quant.data import DataFrequency, Dataset from gs_quant.instrument import Instrument from gs_quant.json_convertors import decode_pandas_series, encode_pandas_series class MissingDataStrategy(Enum): fill_forward = 'fill_forward' interpolate = 'interpolate' fail = 'fail' @dataclass_json @dataclass class DataSource: __sub_classes: ClassVar[List[type]] = [] def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) DataSource.__sub_classes.append(cls) @staticmethod def sub_classes(): return tuple(DataSource.__sub_classes) def get_data(self, state, **kwargs): raise RuntimeError("Implemented by subclass") def get_data_range(self, start: Union[dt.date, dt.datetime], end: Union[dt.date, dt.datetime, int], **kwargs): raise RuntimeError("Implemented by subclass") @dataclass_json @dataclass class GsDataSource(DataSource): data_set: str asset_id: str min_date: dt.date = None max_date: dt.date = None value_header: str = 'rate' class_type: str = static_field('gs_data_source') def __post_init__(self): self.loaded_data = None def get_data(self, state: Union[dt.date, dt.datetime] = None, **kwargs): if self.loaded_data is None: ds = Dataset(self.data_set) if self.min_date: self.loaded_data = ds.get_data(self.min_date, self.max_date, assetId=(self.asset_id,)) elif state is not None: return ds.get_data(state, state, assetId=(self.asset_id,))[self.value_header] else: return ds.get_data(dt.datetime(2000, 1, 1), **kwargs)[self.value_header] return self.loaded_data[self.value_header].at[pd.to_datetime(state)] def get_data_range(self, start: Union[dt.date, dt.datetime], end: Union[dt.date, dt.datetime, int], **kwargs): if self.loaded_data is None: ds = Dataset(self.data_set) if self.asset_id is not None: kwargs['assetId'] = (self.asset_id,) if self.min_date: self.loaded_data = ds.get_data(self.min_date, self.max_date, **kwargs) else: self.loaded_data = ds.get_data(start, self.max_date, **kwargs) if isinstance(end, int): return self.loaded_data.loc[self.loaded_data.index < start].tail(end) return self.loaded_data.loc[(start < self.loaded_data.index) & (self.loaded_data.index <= end)] @dataclass_json @dataclass class GenericDataSource(DataSource): """ A data source which holds a pandas series indexed by date or datetime :param data_set: a pandas dataframe indexed by date or datetime :param missing_data_strategy: MissingDataStrategy which defines behaviour if data is missing, will only take effect if using get_data, get_data_range has no expectations of the number of expected data points. """ data_set: pd.Series = field( default=None, metadata=config(decoder=decode_pandas_series, encoder=encode_pandas_series) ) missing_data_strategy: MissingDataStrategy = field(default=MissingDataStrategy.fail, metadata=field_metadata) class_type: str = static_field('generic_data_source') def __eq__(self, other): if not isinstance(other, GenericDataSource): return False return self.missing_data_strategy == other.missing_data_strategy and self.data_set.equals(other.data_set) def __post_init__(self): self._tz_aware = isinstance(self.data_set.index[0], dt.datetime) and self.data_set.index[0].tzinfo is not None def get_data(self, state: Union[dt.date, dt.datetime, Iterable]): """ Get the value of the dataset at a time or date. If a list of dates or times is provided return list of values :param state: a date, datetime or a list of dates or datetimes :return: float value """ if state is None: return self.data_set if isinstance(state, Iterable): return [self.get_data(i) for i in state] if self._tz_aware and (state.tzinfo is None or state.tzinfo.utcoffset(state) is None): state = state.replace(tzinfo=dt.timezone.utc) if pd.Timestamp(state) in self.data_set: return self.data_set[pd.Timestamp(state)] elif state in self.data_set or self.missing_data_strategy == MissingDataStrategy.fail: return self.data_set[state] else: if isinstance(self.data_set.index, pd.DatetimeIndex): self.data_set.at[pd.to_datetime(state)] = np.nan self.data_set = self.data_set.sort_index() else: self.data_set.at[state] = np.nan self.data_set.sort_index() if self.missing_data_strategy == MissingDataStrategy.interpolate: self.data_set = self.data_set.interpolate() elif self.missing_data_strategy == MissingDataStrategy.fill_forward: self.data_set = self.data_set.ffill() else: raise RuntimeError(f'unrecognised missing data strategy: {str(self.missing_data_strategy)}') return ( self.data_set[pd.to_datetime(state)] if isinstance(self.data_set.index, pd.DatetimeIndex) else self.data_set[state] ) def get_data_range(self, start: Union[dt.date, dt.datetime], end: Union[dt.date, dt.datetime, int]): """ get a range of values from the dataset. :param start: a date or datetime :param end: a date, datetime or an int. If an int is provided we return that many data points back from the start date :return: pd.Series """ if isinstance(end, int): return self.data_set.loc[self.data_set.index < start].tail(end) return self.data_set.loc[(start < self.data_set.index) & (self.data_set.index <= end)] @dataclass_json @dataclass class DataManager: def __post_init__(self): self._data_sources = {} def add_data_source( self, series: Union[pd.Series, DataSource], data_freq: DataFrequency, instrument: Instrument, valuation_type: ValuationFixingType, ): if not isinstance(series, DataSource) and not len(series): return if instrument.name is None: raise RuntimeError('Please add a name identify your instrument') key = (data_freq, instrument.name, valuation_type) if key in self._data_sources: raise RuntimeError( 'A dataset with this frequency instrument name and valuation type already added to Data Manager' ) self._data_sources[key] = GenericDataSource(series) if isinstance(series, pd.Series) else series def get_data(self, state: Union[dt.date, dt.datetime], instrument: Instrument, valuation_type: ValuationFixingType): key = ( DataFrequency.REAL_TIME if isinstance(state, dt.datetime) else DataFrequency.DAILY, instrument.name.split('_')[-1], valuation_type, ) return self._data_sources[key].get_data(state) def get_data_range( self, start: Union[dt.date, dt.datetime], end: Union[dt.date, dt.datetime], instrument: Instrument, valuation_type: ValuationFixingType, ): key = ( DataFrequency.REAL_TIME if isinstance(start, dt.datetime) else DataFrequency.DAILY, instrument.name.split('_')[-1], valuation_type, ) return self._data_sources[key].get_data_range(start, end) ================================================ FILE: gs_quant/backtests/decorator.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ def plot_backtest(): """ Indicates that class should be exported to plottool as a member function / pseudo-measure (of the backtest). """ def decorator(obj): obj._plot_backtest = True return obj return decorator ================================================ FILE: gs_quant/backtests/equity_vol_engine.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import copy import re import warnings from functools import reduce from typing import Union import datetime as dt import pandas as pd from gs_quant.api.gs.backtests_xasset.response_datatypes.backtest_datatypes import ( TransactionCostConfig, TradingCosts, FixedCostModel, ScaledCostModel, TransactionCostScalingType, AggregateCostModel, CostAggregationType, ) from gs_quant.backtests import actions as a from gs_quant.backtests import triggers as t from gs_quant.backtests.backtest_objects import ( TransactionModel, ConstantTransactionModel, ScaledTransactionModel, AggregateTransactionModel, ) from gs_quant.backtests.strategy_systematic import StrategySystematic, DeltaHedgeParameters, TradeInMethod from gs_quant.base import get_enum_value from gs_quant.common import OptionType, BuySell from gs_quant.instrument import EqOption, EqVarianceSwap from gs_quant.markets.portfolio import Portfolio from gs_quant.risk import EqDelta, EqSpot, EqGamma, EqVega from gs_quant.target.backtests import ( BacktestSignalSeriesItem, BacktestTradingQuantityType, EquityMarketModel, FlowVolBacktestMeasure, ) from gs_quant.common import TradeAs def get_backtest_trading_quantity_type(scaling_type, risk): if scaling_type == a.ScalingActionType.size: return a.BacktestTradingQuantityType.quantity if scaling_type == a.ScalingActionType.NAV: return a.BacktestTradingQuantityType.NAV if risk == EqSpot: return BacktestTradingQuantityType.notional if risk == EqGamma: return BacktestTradingQuantityType.gamma if risk == EqVega: return BacktestTradingQuantityType.vega raise ValueError(f"unable to translate {scaling_type} and {risk}") def is_synthetic_forward(priceable): is_portfolio = isinstance(priceable, Portfolio) is_syn_fwd = is_portfolio if is_portfolio: is_size_two = len(priceable) == 2 is_syn_fwd = is_size_two if is_size_two: has_two_eq_options = isinstance(priceable[0], EqOption) and isinstance(priceable[1], EqOption) is_syn_fwd &= has_two_eq_options if has_two_eq_options: is_syn_fwd &= ( (priceable[0].underlier == priceable[1].underlier) and (priceable[0].expiration_date == priceable[1].expiration_date) and (priceable[0].strike_price == priceable[1].strike_price) ) is_syn_fwd &= (OptionType.Call, BuySell.Buy) and (OptionType.Put, BuySell.Sell) in { (priceable[0].option_type, priceable[0].buy_sell), (priceable[1].option_type, priceable[1].buy_sell), } return is_syn_fwd class BacktestResult: def __init__(self, results): self._results = results def get_measure_series(self, measure: FlowVolBacktestMeasure): data = next(iter(r.timeseries for r in self._results.risks if r.name == measure.value), ()) df = pd.DataFrame.from_records(data) if len(df) == 0: return df df['date'] = pd.to_datetime(df['date']) return df.set_index('date').value def get_portfolio_history(self): data = [] for item in self._results.portfolio: positions = list( map( lambda x: dict({'date': item['date'], 'quantity': x['quantity']}, **x['instrument']), item['positions'], ) ) data = data + positions return pd.DataFrame(data) def get_trade_history(self): data = [] for item in self._results.portfolio: for transaction in item['transactions']: trades = list( map( lambda x: dict( { 'date': item['date'], 'quantity': x['quantity'], 'transactionType': transaction['type'], 'price': x['price'], 'cost': transaction.get('cost') if len(transaction['trades']) == 1 else None, }, **x['instrument'], ), transaction['trades'], ) ) data = data + trades return pd.DataFrame(data) class EquityVolEngine(object): @classmethod def check_strategy(cls, strategy): check_results = [] if len(strategy.initial_portfolio) > 0: check_results.append('Error: initial_portfolio must be empty or None') # Validate Triggers if len(strategy.triggers) > 3: check_results.append('Error: Maximum of 3 triggers') if not all(isinstance(x, (t.AggregateTrigger, t.PeriodicTrigger)) for x in strategy.triggers): check_results.append('Error: Only AggregateTrigger and PeriodTrigger supported') # aggregate triggers composed of a dated and portfolio trigger define a signal aggregate_triggers = [x for x in strategy.triggers if isinstance(x, t.AggregateTrigger)] for at in aggregate_triggers: if not len(at.trigger_requirements.triggers) == 2: check_results.append('Error: AggregateTrigger must be composed of 2 triggers') if not len([x for x in at.trigger_requirements.triggers if isinstance(x, t.DateTriggerRequirements)]) == 1: check_results.append('Error: AggregateTrigger must be contain 1 DateTrigger') portfolio_triggers = [ x for x in at.trigger_requirements.triggers if isinstance(x, t.PortfolioTriggerRequirements) ] if not len(portfolio_triggers) == 1: check_results.append('Error: AggregateTrigger must be contain 1 PortfolioTrigger') if not (portfolio_triggers[0].data_source == 'len' and portfolio_triggers[0].trigger_level == 0): check_results.append( 'Error: PortfolioTrigger.trigger_requirements must have data_source = \'len\' and trigger_level = 0' ) # Validate Actions all_actions = reduce(lambda acc, x: acc + x, (map(lambda x: x.actions, strategy.triggers)), []) if any(isinstance(x, a.ExitPositionAction) for x in all_actions): warnings.warn('ExitPositionAction will be deprecated soon, use ExitTradeAction.', DeprecationWarning, 2) if any(isinstance(x, a.EnterPositionQuantityScaledAction) for x in all_actions): warnings.warn( 'EnterPositionQuantityScaledAction will be deprecated soon, use AddScaledTradeAction.', DeprecationWarning, 2, ) if not all( isinstance( x, ( a.EnterPositionQuantityScaledAction, a.HedgeAction, a.ExitPositionAction, a.ExitTradeAction, a.AddTradeAction, a.AddScaledTradeAction, ), ) for x in all_actions ): check_results.append( 'Error: actions must be one of EnterPositionQuantityScaledAction, HedgeAction, ExitPositionAction, ' 'ExitTradeAction, AddTradeAction, AddScaledTradeAction' ) # no duplicate actions if not len(set(map(lambda x: type(x), all_actions))) == len(all_actions): check_results.append('Error: There are multiple actions of the same type') all_child_triggers = reduce( lambda acc, x: acc + x, map( lambda x: x.trigger_requirements.triggers if isinstance(x, t.AggregateTriggerRequirements) else [x], strategy.triggers, ), [], ) for trigger in all_child_triggers: if isinstance(trigger, t.PortfolioTrigger): continue # action one of enter position, exit position, hedge if len(trigger.actions) != 1: check_results.append('Error: All triggers must contain only 1 action') for action in trigger.actions: if isinstance(action, (a.EnterPositionQuantityScaledAction, a.AddTradeAction, a.AddScaledTradeAction)): if ( isinstance(trigger, t.PeriodicTrigger) and not trigger.trigger_requirements.frequency == action.trade_duration ): check_results.append( f'Error: {type(action).__name__}: PeriodicTrigger frequency must be the same ' 'as trade_duration' ) if not all((isinstance(p, (EqOption, EqVarianceSwap))) for p in action.priceables): check_results.append( f'Error: {type(action).__name__}: Only EqOption or EqVarianceSwap supported' ) if isinstance(action, a.EnterPositionQuantityScaledAction): if action.trade_quantity is None or action.trade_quantity_type is None: check_results.append( 'Error: EnterPositionQuantityScaledAction trade_quantity or trade_quantity_type is None' ) if isinstance(action, a.AddScaledTradeAction): if action.scaling_level is None or action.scaling_type is None: check_results.append('Error: AddScaledTradeAction scaling_level or scaling_type is None') expiry_date_modes = map(lambda x: TenorParser(x.expirationDate).get_mode(), action.priceables) expiry_date_modes = list(set(expiry_date_modes)) if len(expiry_date_modes) > 1: check_results.append( f'Error: {type(action).__name__} all priceable expiration_date modifiers must ' 'be the same. Found [' + ', '.join([str(edm) for edm in expiry_date_modes]) + ']' ) if expiry_date_modes[0] is not None and expiry_date_modes[0] not in ['otc', 'listed']: check_results.append( f'Error: {type(action).__name__} invalid expiration_date modifier ' + expiry_date_modes[0] ) size_fields = ('quantity', 'number_of_options', 'multiplier') priceable_size_values = [[getattr(p, sf, 1) or 1 for sf in size_fields] for p in action.priceables] priceable_sizes = [reduce(lambda x, y: x * y, size_vals, 1) for size_vals in priceable_size_values] if not all(priceable_size == 1 for priceable_size in priceable_sizes): check_results.append( f'Error: {type(action).__name__} every priceable should have a unit size of 1. ' 'Found [' + ', '.join([str(s) for s in priceable_sizes]) + ']' ) elif isinstance(action, a.HedgeAction): if not is_synthetic_forward(action.priceable): check_results.append( 'Error: HedgeAction: Hedge instrument must be a synthetic forward - a portfolio of two ' 'equity options (long call and short put) with the same underlier, strike price and ' 'expiration date' ) if not trigger.trigger_requirements.frequency == action.trade_duration: check_results.append( 'Error: HedgeAction: PeriodicTrigger frequency must be the same as trade_duration' ) if not action.risk == EqDelta: check_results.append('Error: HedgeAction: risk type must be EqDelta') elif isinstance(action, (a.ExitPositionAction, a.ExitTradeAction)): continue else: check_results.append('Error: Unsupported action type \'{}\''.format(type(action))) return check_results @classmethod def supports_strategy(cls, strategy): check_result = cls.check_strategy(strategy) if len(check_result): return False return True @classmethod def run_backtest(cls, strategy, start, end, market_model=EquityMarketModel.SFK, cash_accrual=True): check_result = cls.check_strategy(strategy) if len(check_result): raise RuntimeError(check_result) underlier_list = None roll_frequency = None trade_quantity = None trade_quantity_type = None trade_in_signals = None trade_out_signals = None hedge = None transaction_cost = TransactionCostConfig(None) for trigger in strategy.triggers: if isinstance(trigger, t.AggregateTrigger): child_triggers = trigger.trigger_requirements.triggers date_trigger = [x for x in child_triggers if isinstance(x, t.DateTriggerRequirements)][0] date_signal = list(map(lambda x: BacktestSignalSeriesItem(x, True), date_trigger.dates)) portfolio_trigger = [x for x in child_triggers if isinstance(x, t.PortfolioTriggerRequirements)][0] if portfolio_trigger.direction == t.TriggerDirection.EQUAL and portfolio_trigger.trigger_level == 0: is_trade_in = True else: is_trade_in = False if is_trade_in: trade_in_signals = date_signal else: trade_out_signals = date_signal action = trigger.actions[0] if isinstance(action, a.EnterPositionQuantityScaledAction): underlier_list = cls.__get_underlier_list(action.priceables) tp = TenorParser(action.trade_duration) roll_frequency = tp.get_date() roll_date_mode = tp.get_mode() trade_quantity = action.trade_quantity trade_quantity_type = action.trade_quantity_type expiry_date_mode = TenorParser(action.priceables[0].expiration_date).get_mode() transaction_cost.trade_cost_model = TradingCosts( cls.__map_tc_model(action.transaction_cost), cls.__map_tc_model(action.transaction_cost_exit) ) elif isinstance(action, a.AddTradeAction): underlier_list = cls.__get_underlier_list(action.priceables) tp = TenorParser(action.trade_duration) roll_frequency = tp.get_date() roll_date_mode = tp.get_mode() trade_quantity = 1 trade_quantity_type = BacktestTradingQuantityType.quantity expiry_date_mode = TenorParser(action.priceables[0].expiration_date).get_mode() transaction_cost.trade_cost_model = TradingCosts( cls.__map_tc_model(action.transaction_cost), cls.__map_tc_model(action.transaction_cost_exit) ) elif isinstance(action, a.AddScaledTradeAction): underlier_list = cls.__get_underlier_list(action.priceables) tp = TenorParser(action.trade_duration) roll_frequency = tp.get_date() roll_date_mode = tp.get_mode() trade_quantity = action.scaling_level trade_quantity_type = get_backtest_trading_quantity_type(action.scaling_type, action.scaling_risk) expiry_date_mode = TenorParser(action.priceables[0].expiration_date).get_mode() transaction_cost.trade_cost_model = TradingCosts( cls.__map_tc_model(action.transaction_cost), cls.__map_tc_model(action.transaction_cost_exit) ) elif isinstance(action, a.HedgeAction): hedge = DeltaHedgeParameters( frequency=trigger.trigger_requirements.frequency, notional=action.risk_percentage ) transaction_cost.hedge_cost_model = TradingCosts( cls.__map_tc_model(action.transaction_cost), cls.__map_tc_model(action.transaction_cost_exit) ) transaction_cost_config = ( transaction_cost if (transaction_cost.trade_cost_model or transaction_cost.hedge_cost_model) else None ) strategy = StrategySystematic( name="Flow Vol Backtest", underliers=underlier_list, index_initial_value=0, delta_hedge=hedge, quantity=trade_quantity, quantity_type=trade_quantity_type, trade_in_method=TradeInMethod.FixedRoll, roll_frequency=roll_frequency, trade_in_signals=trade_in_signals, trade_out_signals=trade_out_signals, market_model=market_model, expiry_date_mode=expiry_date_mode, roll_date_mode=roll_date_mode, cash_accrual=cash_accrual, transaction_cost_config=transaction_cost_config, use_xasset_backtesting_service=True, ) result = strategy.backtest(start, end) return BacktestResult(result) @classmethod def __get_underlier_list(cls, priceables): priceables_copy = copy.deepcopy(priceables) for priceable in priceables_copy: edp = TenorParser(priceable.expiration_date) priceable.expiration_date = edp.get_date() if hasattr(priceable, 'trade_as'): expiry_date_mode = get_enum_value(TradeAs, edp.get_mode()) priceable.trade_as = ( priceable.trade_as or expiry_date_mode if isinstance(expiry_date_mode, TradeAs) else None ) print(priceable.trade_as) return priceables_copy @classmethod def __map_tc_model(cls, model: TransactionModel): if isinstance(model, ConstantTransactionModel): return FixedCostModel(cost=model.cost) elif isinstance(model, ScaledTransactionModel): if model.scaling_type == EqVega: scaling_quantity_type = TransactionCostScalingType.Vega else: scaling_quantity_type = get_enum_value(TransactionCostScalingType, model.scaling_type) if not isinstance(scaling_quantity_type, TransactionCostScalingType): raise RuntimeError(f'unsupported scaled transaction quantity type "{model.scaling_type}"') return ScaledCostModel(scaling_quantity_type=scaling_quantity_type, scaling_level=model.scaling_level) elif isinstance(model, AggregateTransactionModel): return AggregateCostModel( models=[cls.__map_tc_model(m) for m in model.transaction_models], aggregation_type=CostAggregationType(model.aggregate_type.value), ) return None class TenorParser(object): # match expiration dates expressed as 3m@listed expiry_regex = '(.*)@(.*)' def __init__(self, expiry: Union[str, dt.date]): self.expiry = expiry def get_date(self): if isinstance(self.expiry, dt.date): return self.expiry parts = re.search(self.expiry_regex, self.expiry) if parts: return parts.group(1) else: return self.expiry def get_mode(self): if isinstance(self.expiry, dt.date): return None parts = re.search(self.expiry_regex, self.expiry) if parts: return parts.group(2) else: return None ================================================ FILE: gs_quant/backtests/event.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.backtests.order import OrderBase class Event(object): pass class MarketEvent(Event): def __init__(self): self.type = 'Market' class ValuationEvent(Event): def __init__(self): self.type = 'Valuation' class OrderEvent(Event): def __init__(self, order: OrderBase): self.type = 'Order' self.order = order class FillEvent(Event): def __init__(self, order: OrderBase, filled_units: float, filled_price: float): self.type = 'Fill' self.order = order self.filled_units = filled_units self.filled_price = filled_price ================================================ FILE: gs_quant/backtests/execution_engine.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from gs_quant.backtests.data_handler import DataHandler from gs_quant.backtests.event import OrderEvent, FillEvent from gs_quant.backtests.order import OrderBase class ExecutionEngine(object): pass class SimulatedExecutionEngine(ExecutionEngine): def __init__(self, data_handler: DataHandler): self.data_handler = data_handler self.orders = [] def submit_order(self, order: OrderEvent): self.orders.append(order) self.orders.sort(key=lambda e: e.order.execution_end_time()) def ping(self, state: dt.datetime): fill_events = [] while self.orders: order: OrderBase = self.orders[0].order end_time = order.execution_end_time() if end_time > state: break else: fill = FillEvent( order=order, filled_price=order.execution_price(self.data_handler), filled_units=order.execution_quantity(), ) fill_events.append(fill) self.orders.pop(0) return fill_events ================================================ FILE: gs_quant/backtests/generic_engine.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import copy import datetime as dt import logging from collections import defaultdict from functools import reduce from typing import Union, Iterable, Optional from gs_quant.backtests.action_handler import ActionHandlerBaseFactory, ActionHandler from gs_quant.backtests.actions import ( Action, AddTradeAction, HedgeAction, ExitTradeAction, RebalanceAction, ExitAllPositionsAction, AddScaledTradeAction, AddWeightedTradeAction, ) from gs_quant.backtests.backtest_engine import BacktestBaseEngine from gs_quant.backtests.backtest_objects import BackTest, CashPayment, PnlDefinition from gs_quant.backtests.backtest_utils import make_list, CalcType, get_final_date, map_ccy_name_to_ccy from gs_quant.backtests.generic_engine_action_impls import ( AddTradeActionImpl, AddScaledTradeActionImpl, HedgeActionImpl, ExitTradeActionImpl, RebalanceActionImpl, AddWeightedTradeActionImpl, ) from gs_quant.backtests.strategy import Strategy from gs_quant.common import Currency, ParameterisedRiskMeasure, RiskMeasure from gs_quant.context_base import nullcontext from gs_quant.datetime.relative_date import RelativeDateSchedule from gs_quant.markets import PricingContext, HistoricalPricingContext from gs_quant.markets.portfolio import Portfolio from gs_quant.risk import Price from gs_quant.risk.results import PortfolioRiskResult from gs_quant.tracing import Tracer # priority set to contexts making requests to the pricing API (min. 1 - max. 10) DEFAULT_REQUEST_PRIORITY = 5 def raiser(ex): raise RuntimeError(ex) logger = logging.getLogger(__name__) class GenericEngineActionFactory(ActionHandlerBaseFactory): def __init__(self, action_impl_map=None): self.action_impl_map = { AddTradeAction: AddTradeActionImpl, HedgeAction: HedgeActionImpl, ExitTradeAction: ExitTradeActionImpl, ExitAllPositionsAction: ExitTradeActionImpl, RebalanceAction: RebalanceActionImpl, AddScaledTradeAction: AddScaledTradeActionImpl, AddWeightedTradeAction: AddWeightedTradeActionImpl, } | (action_impl_map or {}) def get_action_handler(self, action: Action) -> ActionHandler: if type(action) in self.action_impl_map: return self.action_impl_map[type(action)](action) raise RuntimeError(f'Action {type(action)} not supported by engine') class GenericEngine(BacktestBaseEngine): def __init__(self, action_impl_map=None, price_measure=Price): self.action_impl_map = {} if action_impl_map is None else action_impl_map self.price_measure = price_measure self._pricing_context_params = None self._initial_pricing_context = None self._tracing_enabled = False def get_action_handler(self, action: Action) -> ActionHandler: handler_factory = GenericEngineActionFactory(self.action_impl_map) return handler_factory.get_action_handler(action) def supports_strategy(self, strategy): all_actions = reduce(lambda x, y: x + y, (map(lambda x: x.actions, strategy.triggers))) try: for x in all_actions: self.get_action_handler(x) except RuntimeError: return False return True def new_pricing_context(self): """ generate context with the same params to avoid duplication """ context_params = self._pricing_context_params show_progress = context_params.get('show_progress', False) csa_term = context_params.get('csa_term') market_data_location = context_params.get('market_data_location') request_priority = context_params.get('request_priority', DEFAULT_REQUEST_PRIORITY) is_batch = context_params.get('is_batch', True) context = PricingContext( set_parameters_only=True, show_progress=show_progress, csa_term=csa_term, market_data_location=market_data_location, request_priority=request_priority, is_batch=is_batch, use_historical_diddles_only=True, ) context._max_concurrent = 1500 context._dates_per_batch = 200 return context def run_backtest( self, strategy: Strategy, start: Optional[dt.date] = None, end: Optional[dt.date] = None, frequency: Optional[str] = '1m', states: Optional[Iterable[dt.date]] = None, risks: Optional[Iterable[RiskMeasure]] = None, show_progress: bool = True, csa_term: Optional[str] = None, visible_to_gs: bool = False, initial_value: float = 0, result_ccy: Optional[Union[str, Currency]] = None, holiday_calendar: Optional[str] = None, market_data_location: Optional[str] = None, is_batch: bool = True, calc_risk_at_trade_exits: bool = False, pnl_explain: Optional[PnlDefinition] = None, ): """ run the backtest following the triggers and actions defined in the strategy. If states are entered run on those dates otherwise build a schedule from the start, end, frequency using gs_quant.datetime.relative_date.RelativeDateSchedule :param strategy: the strategy object :param start: a datetime :param end: a datetime :param frequency: str, default '1m' :param states: a list of dates will override the start, end, freq if provided :param risks: risks to run :param show_progress: boolean default true :param csa_term: the csa term to use :param visible_to_gs: are the contents of risk requests visible to GS (defaults to False) :param initial_value: initial cash value of strategy defaults to 0 :param result_ccy: ccy of all risks, pvs and cash :param holiday_calendar for date maths - list of dates :param market_data_location: location for the market data :param is_batch: use websockets to reduce timeout issues :param calc_risk_at_trade_exits: separate results for requested risk measures on tradable exit dates; not to be included in main results but useful for PnL decomposition :param pnl_explain: a Pnl Definition object which defines the risk attribution and mkt data for a pnl explain :return: a backtest object containing the portfolios on each day and results which show all risks on all days """ logger.info(f'Starting Backtest: Building Date Schedule - {dt.datetime.now()}') self._tracing_enabled = Tracer.active_span() is not None and Tracer.active_span().is_recording() self._pricing_context_params = { 'show_progress': show_progress, 'csa_term': csa_term, 'visible_to_gs': visible_to_gs, 'market_data_location': market_data_location, 'is_batch': is_batch, } with self.new_pricing_context(): return self.__run( strategy, start, end, frequency, states, risks, initial_value, result_ccy, holiday_calendar, calc_risk_at_trade_exits, pnl_explain, ) def _trace(self, label: str): if self._tracing_enabled: return Tracer(label) else: return nullcontext() def __run( self, strategy, start, end, frequency, states, risks, initial_value, result_ccy, holiday_calendar, calc_risk_at_trade_exits, pnl_explain, ): """ Run the backtest strategy using the ambient pricing context """ with self._trace('Relative Schedule'): strategy_pricing_dates = ( RelativeDateSchedule(frequency, start, end).apply_rule(holiday_calendar=holiday_calendar) if states is None else states ) strategy_pricing_dates.sort() strategy_start_date = strategy_pricing_dates[0] strategy_end_date = strategy_pricing_dates[-1] for trigger in strategy.triggers: strategy_pricing_dates += [ t for t in trigger.get_trigger_times() if strategy_start_date <= t <= strategy_end_date ] strategy_pricing_dates = list(set(strategy_pricing_dates)) strategy_pricing_dates.sort() if pnl_explain is not None: calc_risk_at_trade_exits = True pnl_risks = pnl_explain.get_risks() else: pnl_risks = [] risks = list(set(make_list(risks) + strategy.risks + pnl_risks + [self.price_measure])) if result_ccy is not None: risks = [ ( r(currency=result_ccy) if isinstance(r, ParameterisedRiskMeasure) else raiser(f'Unparameterised risk: {r}') ) for r in risks ] if result_ccy is not None: if isinstance(self.price_measure, ParameterisedRiskMeasure): price_risk = self.price_measure(currency=result_ccy) else: raiser(f'Unparameterised price measure: {self.price_measure}') else: price_risk = self.price_measure backtest = BackTest(strategy, strategy_pricing_dates, risks, price_risk, holiday_calendar, pnl_explain) logger.info('Resolving initial portfolio') with self._trace('Resolve initial portfolio'): self._resolve_initial_portfolio( strategy.initial_portfolio, backtest, strategy_start_date, strategy_pricing_dates, holiday_calendar ) logger.info('Building simple and semi-deterministic triggers and actions') self._build_simple_and_semi_triggers_and_actions(strategy, backtest, strategy_pricing_dates) logger.info(f'Filtering strategy calculations to run from {strategy_start_date} to {strategy_end_date}') backtest.portfolio_dict = defaultdict( Portfolio, { k: backtest.portfolio_dict[k] for k in backtest.portfolio_dict if strategy_start_date <= k <= strategy_end_date }, ) backtest.hedges = defaultdict( list, {k: backtest.hedges[k] for k in backtest.hedges if strategy_start_date <= k <= strategy_end_date} ) backtest.weighted_trades = defaultdict( list, { k: backtest.weighted_trades[k] for k in backtest.weighted_trades if strategy_start_date <= k <= strategy_end_date }, ) logger.info('Pricing simple and semi-deterministic triggers and actions') with self._trace('Pricing semi-det Triggers'): self._price_semi_det_triggers(backtest, risks) logger.info('Scaling semi-determ triggers and actions and calculating path dependent triggers and actions') with self._trace('Process dates') as scope: if scope: scope.span.set_tag('dates.length', len(strategy_pricing_dates)) for d in strategy_pricing_dates: if scope: scope.span.log_kv({'date': str(d)}) self._process_triggers_and_actions_for_date(d, strategy, backtest, risks) with self._trace('Calc New Trades'): self._calc_new_trades(backtest, risks) with self._trace('Handle Cash'): self._handle_cash( backtest, risks, price_risk, strategy_pricing_dates, strategy_end_date, initial_value, calc_risk_at_trade_exits, strategy.cash_accrual, ) with self._trace('Populate Transaction Costs'): backtest.transaction_costs = { d: -sum(tce.get_final_cost() for tce in tce_list) for d, tce_list in backtest.transaction_cost_entries.items() } logger.info(f'Finished Backtest:- {dt.datetime.now()}') return backtest def _resolve_initial_portfolio( self, initial_portfolio, backtest, strategy_start_date, strategy_pricing_dates, holiday_calendar, duration=None ): if isinstance(initial_portfolio, dict): sorted_dates = sorted(list(initial_portfolio.keys())) for i, d in enumerate(sorted_dates): portfolio = make_list(initial_portfolio[d]) end_date = sorted_dates[i + 1] if i + 1 < len(sorted_dates) else strategy_pricing_dates[-1] self._resolve_initial_portfolio( portfolio, backtest, d, strategy_pricing_dates, holiday_calendar, end_date ) else: if len(initial_portfolio): renamed_port = [] for index in range(len(initial_portfolio)): old_name = initial_portfolio[index].name renamed_inst = initial_portfolio[index].clone( name=f'{old_name}_{strategy_start_date.strftime("%Y-%m-%d")}' ) renamed_port.append(renamed_inst) entry_payment = CashPayment(renamed_inst, effective_date=strategy_start_date, direction=-1) backtest.cash_payments[strategy_start_date].append(entry_payment) final_date = get_final_date(renamed_inst, strategy_start_date, duration, holiday_calendar) exit_payment = CashPayment(initial_portfolio[index], effective_date=final_date) backtest.cash_payments[final_date].append(exit_payment) init_port = Portfolio(renamed_port) with PricingContext(strategy_start_date): init_port.resolve() for d in strategy_pricing_dates: if duration is None or ( d >= strategy_start_date and (d < duration or duration == strategy_pricing_dates[-1]) ): backtest.portfolio_dict[d].append(init_port.instruments) def _build_simple_and_semi_triggers_and_actions(self, strategy, backtest, strategy_pricing_dates): for trigger in strategy.triggers: if trigger.calc_type != CalcType.path_dependent: triggered_dates = [] trigger_infos = defaultdict(list) with self._trace('Build semi-det trigger') as scope: for d in strategy_pricing_dates: t_info = trigger.has_triggered(d, backtest) if t_info: triggered_dates.append(d) if t_info.info_dict: for k, v in t_info.info_dict.items(): trigger_infos[k].append(v) if scope: scope.span.set_tag('trigger.type', type(trigger).__name__) scope.span.set_tag('dates.triggered', len(triggered_dates)) scope.span.set_tag('action.count', len(trigger.actions)) for action in trigger.actions: if action.calc_type != CalcType.path_dependent: with self._trace('Build semi-det action') as scope: if scope: scope.span.set_tag('action.type', type(action).__name__) trigger_info = None if type(action) in trigger_infos: trigger_info = trigger_infos[type(action)] else: for mapped_action_type, action_trigger_info in trigger_infos.items(): if isinstance(action, mapped_action_type): trigger_info = action_trigger_info break self.get_action_handler(action).apply_action(triggered_dates, backtest, trigger_info) @staticmethod def _price_semi_det_triggers(backtest, risks): with PricingContext(): backtest.calc_calls += 1 for day, portfolio in backtest.portfolio_dict.items(): if isinstance(day, dt.date): with PricingContext(day): backtest.calculations += len(portfolio) * len(risks) backtest.add_results(day, portfolio.calc(tuple(risks))) # semi path dependent initial calc for hedges for _, hedge_list in backtest.hedges.items(): scaling_list = [h.scaling_portfolio for h in hedge_list] for p in scaling_list: with HistoricalPricingContext(dates=p.dates): backtest.calculations += len(risks) * len(p.dates) port = p.trade if isinstance(p.trade, Portfolio) else Portfolio([p.trade]) p.results = port.calc(tuple(risks)) # semi path dependent initial calc for weighted trades for _, weighted_trade_list in backtest.weighted_trades.items(): for wt in weighted_trade_list: sp = wt.scaling_portfolio with HistoricalPricingContext(dates=sp.dates): backtest.calculations += len(risks) * len(sp.dates) * len(sp.trades) sp.results = sp.trades.calc(tuple(risks)) @staticmethod def __ensure_risk_results(dates, backtest: BackTest, risks): port_by_date = {} for d in dates: port = [] for t in backtest.portfolio_dict[d]: if not backtest.results[d] or t.name not in backtest.results[d].portfolio: port.append(t) if len(port): port_by_date[d] = port if len(port_by_date): results_by_date = {} with PricingContext(): for d, port in port_by_date.items(): with PricingContext(pricing_date=d): results_by_date[d] = Portfolio(port).calc(tuple(risks)) for d, results in results_by_date.items(): backtest.add_results(d, results) def _process_triggers_and_actions_for_date(self, d, strategy, backtest: BackTest, risks): logger.debug(f'{d}: Processing triggers and actions') # need to ensure risk results for the day are available prior to the path-dependent action/trigger being applied # note that __ensure_risk_results sends a risk calculation for the day, so it should only happen when required trigger_infos = defaultdict(list) for trigger in strategy.triggers: if trigger.calc_type == CalcType.path_dependent: t_info = trigger.has_triggered(d, backtest) if t_info: if t_info.info_dict: for k, v in t_info.info_dict.items(): trigger_infos[k].append(v) for action in trigger.actions: self.__ensure_risk_results([d], backtest, risks) trigger_info = trigger_infos.get(type(action), None) self.get_action_handler(action).apply_action(d, backtest, trigger_info) else: for action in trigger.actions: if action.calc_type == CalcType.path_dependent: t_info = trigger.has_triggered(d, backtest) if t_info: trigger_info = t_info.info_dict.get(type(action), None) if t_info.info_dict else None self.__ensure_risk_results([d], backtest, risks) self.get_action_handler(action).apply_action(d, backtest, trigger_info) # explicit check needed because backtest.hedges is a defaultdict that gets populated on access below if d not in backtest.hedges and d not in backtest.weighted_trades: return for hedge in backtest.hedges[d]: sp = hedge.scaling_portfolio if sp.results is None: with HistoricalPricingContext(dates=sp.dates): backtest.calculations += len(risks) * len(sp.dates) port_sp = sp.trade if isinstance(sp.trade, Portfolio) else Portfolio([sp.trade]) sp.results = port_sp.calc(tuple(risks)) # semi path dependent scaling for hedges; only apply if there are any hedges to scale if backtest.hedges[d]: # ensure all risk results are available (including the hedge risk required to scale below) # this is useful when there is overlap between hedges (e.g. an instrument hedging risk from another hedge) # note it does not get applied when there is no overlap between hedges, as the ensure fn will skip the calc self.__ensure_risk_results([d], backtest, risks) # this needs to be applied after the "ensure" line above, which may add results # if there is no risk to hedge, skip to the next day; hedges for this day can be ignored if d not in backtest.results: return for hedge in backtest.hedges[d]: p = hedge.scaling_portfolio current_risk = ( backtest.results[d][p.risk] .transform(risk_transformation=p.risk_transformation) .aggregate(allow_mismatch_risk_keys=True) ) hedge_risk = p.results[d][p.risk].transform(risk_transformation=p.risk_transformation).aggregate() if hedge_risk == 0: continue if current_risk.unit != hedge_risk.unit: raise RuntimeError('cannot hedge in a different currency') scaling_factor = current_risk / hedge_risk * hedge.scaling_portfolio.risk_percentage / 100 hedge.entry_payment.transaction_cost_entry.additional_scaling = scaling_factor if hedge.exit_payment is not None: hedge.exit_payment.transaction_cost_entry.additional_scaling = scaling_factor if isinstance(p.trade, Portfolio): # Scale the portfolio by risk target scaled_portfolio_position = copy.deepcopy(p.trade) scaled_portfolio_position.name = f'Scaled_{scaled_portfolio_position.name}' for instrument in scaled_portfolio_position.all_instruments: instrument.name = f'Scaled_{instrument.name}' # trade hedge in opposite direction scale_direction = -1 scaled_portfolio_position.scale(scaling_factor * scale_direction) for day in p.dates: # add scaled hedge position to portfolio for day. # NOTE this adds leaves, not the portfolio backtest.portfolio_dict[day] += copy.deepcopy(scaled_portfolio_position) # scale trade in hedge cash payments hedge.entry_payment.trade = copy.deepcopy(scaled_portfolio_position) if hedge.exit_payment is not None: hedge.exit_payment.trade = copy.deepcopy(scaled_portfolio_position) else: raise RuntimeError('Hedge trade instrument must be a Portfolio') # Add cash payments for hedge entry and exit backtest.cash_payments[hedge.entry_payment.effective_date].append(hedge.entry_payment) if hedge.exit_payment is not None: backtest.cash_payments[hedge.exit_payment.effective_date].append(hedge.exit_payment) # semi path dependent scaling for weighted trades if d in backtest.weighted_trades and backtest.weighted_trades[d]: for weighted_trade in backtest.weighted_trades[d]: sp = weighted_trade.scaling_portfolio if sp.results is None: with HistoricalPricingContext(dates=sp.dates): backtest.calculations += len(risks) * len(sp.dates) * len(sp.trades) sp.results = sp.trades.calc(tuple(risks)) # Get risk for each instrument on the entry date instrument_risks = {} for inst in sp.trades.all_instruments: try: inst_risk = sp.results[d][sp.risk][inst.name] if hasattr(inst_risk, 'aggregate'): inst_risk = inst_risk.aggregate() instrument_risks[inst.name] = abs(inst_risk) if inst_risk != 0 else 0 except (KeyError, ValueError): instrument_risks[inst.name] = 0 # Calculate scaling factors so each instrument is scaled by its proportion of total portfolio risk # and the sum of all notionals equals total_size num_instruments = len(sp.trades.all_instruments) if num_instruments == 0: continue total_portfolio_risk = sum(instrument_risks.values()) if total_portfolio_risk == 0: continue scaling_factors = {} for inst_name, inst_risk in instrument_risks.items(): if inst_risk != 0: # Weight is the proportion of this instrument's risk to total portfolio risk weight = inst_risk / total_portfolio_risk # Notional for this instrument = weight * total_size # Scaling factor = notional (since unit notional instruments) scaling_factors[inst_name] = weight * sp.total_size else: scaling_factors[inst_name] = 0 # Scale each instrument and add to portfolio for idx, inst in enumerate(sp.trades.all_instruments): scaling_factor = scaling_factors.get(inst.name, 0) if scaling_factor == 0: continue scaled_inst = copy.deepcopy(inst) scaled_inst.name = f'Weighted_{inst.name}' scaled_inst.scale(scaling_factor) # Update transaction cost entries with scaling entry_payment = weighted_trade.entry_payments[idx] entry_payment.transaction_cost_entry.additional_scaling = scaling_factor if weighted_trade.exit_payments[idx] is not None: weighted_trade.exit_payments[idx].transaction_cost_entry.additional_scaling = scaling_factor # Add scaled instrument to portfolio for each active date for day in sp.dates: backtest.portfolio_dict[day].append(copy.deepcopy(scaled_inst)) # Update cash payment trades with scaled version entry_payment.trade = copy.deepcopy(scaled_inst) if weighted_trade.exit_payments[idx] is not None: weighted_trade.exit_payments[idx].trade = copy.deepcopy(scaled_inst) # Add cash payments backtest.cash_payments[entry_payment.effective_date].append(entry_payment) if weighted_trade.exit_payments[idx] is not None: backtest.cash_payments[weighted_trade.exit_payments[idx].effective_date].append( weighted_trade.exit_payments[idx] ) def _calc_new_trades(self, backtest, risks): logger.info('Calculating and scaling newly added portfolio positions') # test to see if new trades have been added and calc with PricingContext(): backtest.calc_calls += 1 leaves_by_date = {} for day, portfolio in backtest.portfolio_dict.items(): # Nothing to schedule for calculation, continue if not portfolio: continue results_for_date = backtest.results[day] trades_for_date = ( results_for_date.portfolio if isinstance(results_for_date, PortfolioRiskResult) else [] ) leaves = [] for leaf in portfolio: if leaf.name not in trades_for_date: logger.debug(f'{day}: new portfolio position {leaf.name} scheduled for calculation') leaves.append(leaf) if len(leaves): with PricingContext(pricing_date=day): leaves_by_date[day] = Portfolio(leaves).calc(tuple(risks)) backtest.calculations += len(leaves) * len(risks) logger.info('Processing results for newly added portfolio positions') for day, leaves in leaves_by_date.items(): backtest.add_results(day, leaves) @staticmethod def _handle_cash( backtest, risks, price_risk, strategy_pricing_dates, strategy_end_date, initial_value, calc_risk_at_trade_exits, cash_accrual, ): logger.info('Calculating prices for cash payments') # run any additional calcs to handle cash scaling (e.g. unwinds) cash_results = {} cash_trades_by_date = defaultdict(list) exited_cash_trades_by_date = defaultdict(list) for _, cash_payments in backtest.cash_payments.items(): for cp in cash_payments: # only calc if additional point is required trades = cp.trade.all_instruments if isinstance(cp.trade, Portfolio) else [cp.trade] for trade in trades: if cp.effective_date and cp.effective_date <= strategy_end_date: if ( cp.effective_date not in backtest.results or trade not in backtest.results[cp.effective_date] ): cash_trades_by_date[cp.effective_date].append(trade) if calc_risk_at_trade_exits and cp.direction == 1: exited_cash_trades_by_date[cp.effective_date].append(trade) with PricingContext(): backtest.calc_calls += 1 for cash_date, trades in cash_trades_by_date.items(): with PricingContext(cash_date): backtest.calculations += len(risks) cash_results[cash_date] = Portfolio(trades).calc(price_risk) if calc_risk_at_trade_exits and cash_date in exited_cash_trades_by_date: expiring_trades = exited_cash_trades_by_date[cash_date] backtest.trade_exit_risk_results[cash_date] = Portfolio(expiring_trades).calc(risks) # handle cash current_value = None for d in sorted(set(strategy_pricing_dates + list(backtest.cash_payments.keys()))): if d <= strategy_end_date: if current_value is not None: backtest.cash_dict[d] = ( current_value[0] if cash_accrual is None else cash_accrual.get_accrued_value(current_value, d) ) if d in backtest.cash_payments: for cp in backtest.cash_payments[d]: trades = cp.trade.all_instruments if isinstance(cp.trade, Portfolio) else [cp.trade] for trade in trades: value = cash_results.get(cp.effective_date, {}).get(price_risk, {}).get(trade.name, {}) try: value = ( backtest.results[cp.effective_date][price_risk][trade.name] if value == {} else value ) except (KeyError, ValueError): raise RuntimeError( f'failed to get cash value for {trade.name} on ' f'{cp.effective_date} received value of {value}' ) if not isinstance(value, float): raise RuntimeError( f'failed to get cash value for {trade.name} on ' f'{cp.effective_date} received value of {value}' ) ccy = map_ccy_name_to_ccy(next(iter(value.unit))) if d not in backtest.cash_dict: backtest.cash_dict[d] = {ccy: initial_value} if ccy not in backtest.cash_dict[d]: backtest.cash_dict[d][ccy] = 0 cp.cash_paid[ccy] += value * cp.direction for ccy, cash_paid in cp.cash_paid.items(): backtest.cash_dict[d][ccy] += cash_paid current_value = backtest.cash_dict[d], d current_value = copy.deepcopy(current_value) ================================================ FILE: gs_quant/backtests/generic_engine_action_impls.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from abc import ABCMeta import datetime as dt from collections import defaultdict, namedtuple from itertools import zip_longest from typing import Union, Iterable, Optional, Dict, Collection from gs_quant.backtests.actions import ( Action, AddTradeAction, HedgeAction, AddTradeActionInfo, HedgeActionInfo, ExitTradeAction, ExitTradeActionInfo, RebalanceAction, RebalanceActionInfo, AddScaledTradeAction, ScalingActionType, AddScaledTradeActionInfo, AddWeightedTradeAction, AddWeightedTradeActionInfo, ) from gs_quant.backtests.action_handler import ActionHandler from gs_quant.backtests.backtest_objects import ( BackTest, ScalingPortfolio, CashPayment, Hedge, TransactionCostEntry, WeightedScalingPortfolio, WeightedTrade, ) from gs_quant.backtests.backtest_utils import make_list, get_final_date, interpolate_signal from gs_quant.common import RiskMeasure from gs_quant.instrument import Instrument from gs_quant.markets import PricingContext, HistoricalPricingContext from gs_quant.markets.portfolio import Portfolio from gs_quant.risk.results import PortfolioRiskResult from gs_quant.target.measures import ResolvedInstrumentValues # Action Implementations class OrderBasedActionImpl(ActionHandler, metaclass=ABCMeta): def __init__(self, action: Action): self._order_valuations = [ResolvedInstrumentValues] super().__init__(action) def get_base_orders_for_states(self, states: Collection[dt.date], **kwargs): orders = {} dated_priceables = getattr(self.action, 'dated_priceables', {}) or {} with PricingContext(): for s in states: active_portfolio = dated_priceables.get(s) or self.action.priceables with PricingContext(pricing_date=s): orders[s] = Portfolio(active_portfolio).calc(tuple(self._order_valuations)) return orders def get_instrument_final_date(self, inst: Instrument, order_date: dt.date, info: namedtuple): return get_final_date(inst, order_date, self.action.trade_duration, self.action.holiday_calendar, info) class AddTradeActionImpl(OrderBasedActionImpl): def __init__(self, action: AddTradeAction): super().__init__(action) def _raise_order( self, state: Union[dt.date, Iterable[dt.date]], trigger_info: Optional[Union[AddTradeActionInfo, Iterable[AddTradeActionInfo]]] = None, ): state_list = make_list(state) if trigger_info is None or isinstance(trigger_info, AddTradeActionInfo): trigger_info = [trigger_info for _ in range(len(state_list))] ti_by_state = {} for s, ti in zip_longest(state_list, trigger_info): ti_by_state[s] = ti orders = self.get_base_orders_for_states(state_list, trigger_infos=ti_by_state) final_orders = {} for d, p in orders.items(): new_port = Portfolio([t.clone(name=f'{t.name}_{d}') for t in p.result()]) ti = ti_by_state[d] final_orders[d] = (new_port.scale(None if ti is None else ti.scaling, in_place=False), ti) return final_orders def apply_action( self, state: Union[dt.date, Iterable[dt.date]], backtest: BackTest, trigger_info: Optional[Union[AddTradeActionInfo, Iterable[AddTradeActionInfo]]] = None, ): orders = self._raise_order(state, trigger_info) current_tc_entries = [] # record entry and unwind cashflows for create_date, (portfolio, info) in orders.items(): for inst in portfolio.all_instruments: tc_enter = TransactionCostEntry(create_date, inst, self.action.transaction_cost) current_tc_entries.append(tc_enter) backtest.cash_payments[create_date].append( CashPayment(inst, effective_date=create_date, direction=-1, transaction_cost_entry=tc_enter) ) backtest.transaction_cost_entries[create_date].append(tc_enter) final_date = self.get_instrument_final_date(inst, create_date, info) tc_exit = TransactionCostEntry(final_date, inst, self.action.transaction_cost_exit) current_tc_entries.append(tc_exit) backtest.cash_payments[final_date].append( CashPayment(inst, effective_date=final_date, transaction_cost_entry=tc_exit) ) backtest.transaction_cost_entries[final_date].append(tc_exit) backtest_states = (s for s in backtest.states if final_date > s >= create_date) for s in backtest_states: backtest.portfolio_dict[s].append(inst) with PricingContext(is_async=True): if any(tce.no_of_risk_calcs > 0 for tce in current_tc_entries): backtest.calc_calls += 1 for tce in current_tc_entries: backtest.calculations += tce.no_of_risk_calcs tce.calculate_unit_cost() return backtest class AddScaledTradeActionImpl(OrderBasedActionImpl): def __init__(self, action: AddScaledTradeAction): super().__init__(action) self._scaling_level_signal = ( interpolate_signal(self.action.scaling_level) if isinstance(self.action.scaling_level, dict) else None ) @staticmethod def __portfolio_scaling_for_available_cash( portfolio, available_cash, cur_day, unscaled_prices_by_day, unscaled_entry_tces_by_day ) -> float: fixed_tcs = 0 scaling_based_tcs = 0 for inst in portfolio: insed_fixed_tc, inst_scaling_tc = unscaled_entry_tces_by_day[cur_day][inst].get_cost_by_component() fixed_tcs += insed_fixed_tc scaling_based_tcs += inst_scaling_tc # solve such that fixed TCs, instrument prices and scaled transaction costs (under the same scaling factor) # add up to available_cash # do not floor to zero on the first iteration - first scale factor can be negative, # e.g. if the aggregation operator is "min" and the fixed cost is the minimum but it exceeds the available cash, # it would be too early to floor to zero, must solve again in case there still is an acceptable scaling level first_scale_factor = (available_cash - fixed_tcs) / ( unscaled_prices_by_day[cur_day].aggregate() + scaling_based_tcs ) if first_scale_factor == 0: return 0 # set additional scaling on TCE and solve again in case aggregation (min/max) has been affected by scaling fixed_tcs = 0 scaling_based_tcs = 0 for inst in portfolio: unscaled_entry_tces_by_day[cur_day][inst].additional_scaling = first_scale_factor insed_fixed_tc, inst_scaling_tc = unscaled_entry_tces_by_day[cur_day][inst].get_cost_by_component() fixed_tcs += insed_fixed_tc scaling_based_tcs += inst_scaling_tc # this is 1 if aggregation is unaffected (e.g. switch from Scaled to Fixed), otherwise additional scaling needed second_scale_factor = max(available_cash - fixed_tcs, 0) / ( unscaled_prices_by_day[cur_day].aggregate() * first_scale_factor + scaling_based_tcs ) return first_scale_factor * second_scale_factor def _nav_scale_orders(self, orders, price_measure, trigger_infos): sorted_order_days = sorted(make_list(orders.keys())) final_days_orders = {} unscaled_entry_tces_by_day = defaultdict(dict) unscaled_unwind_tces_by_day = defaultdict(dict) # Populate dict of dates and instruments sold on those dates for create_date, portfolio in orders.items(): info = trigger_infos[create_date] for inst in portfolio.all_instruments: tc_enter = TransactionCostEntry(create_date, inst, self.action.transaction_cost) unscaled_entry_tces_by_day[create_date][inst] = tc_enter d = self.get_instrument_final_date(inst, create_date, info) tc_exit = TransactionCostEntry(d, inst, self.action.transaction_cost_exit) unscaled_unwind_tces_by_day[d][inst] = tc_exit if d not in final_days_orders.keys(): final_days_orders[d] = [] final_days_orders[d].append(inst) unscaled_prices_by_day = {} unscaled_unwind_prices_by_day = {} # Send all unscaled prices and transaction costs to calculate together with PricingContext(is_async=True): for day, portfolio in orders.items(): with PricingContext(pricing_date=day): unscaled_prices_by_day[day] = portfolio.calc(price_measure) for unwind_day, unwind_instruments in final_days_orders.items(): if unwind_day <= dt.date.today(): with PricingContext(pricing_date=unwind_day): unscaled_unwind_prices_by_day[unwind_day] = Portfolio(unwind_instruments).calc(price_measure) for day, inst_tce_map in unscaled_entry_tces_by_day.items(): for inst, tce in inst_tce_map.items(): tce.calculate_unit_cost() for day, inst_tce_map in unscaled_unwind_tces_by_day.items(): for inst, tce in inst_tce_map.items(): tce.calculate_unit_cost() # Start with scaling_level, then only use proceeds from selling instruments available_cash = self.action.scaling_level scaling_factors_by_inst = {} scaling_factors_by_day = {} # Go through each order day of the strategy in sorted order for idx, cur_day in enumerate(sorted_order_days): portfolio = orders[cur_day] scale_factor = self.__portfolio_scaling_for_available_cash( portfolio, available_cash, cur_day, unscaled_prices_by_day, unscaled_entry_tces_by_day ) scaling_factors_by_day[cur_day] = scale_factor for inst in portfolio: scaling_factors_by_inst[inst] = scale_factor available_cash = 0 if idx + 1 < len(sorted_order_days): next_day = sorted_order_days[idx + 1] else: break # Cash received from unwinds is the cash available for the next order for d, p in final_days_orders.items(): # Only consider final days between current order date and the next in an iteration if cur_day < d <= next_day: for inst in p: available_cash += unscaled_unwind_prices_by_day[d][inst] * scaling_factors_by_inst[inst] tce = unscaled_unwind_tces_by_day[d][inst] # additional_scaling only scales the scaling part of the TC tce.additional_scaling = scaling_factors_by_inst[inst] available_cash -= unscaled_unwind_tces_by_day[d][inst].get_final_cost() available_cash = max(available_cash, 0) # portfolio.scale() applies a deepcopy so interferes with inst dict lookup; apply at the end for day in sorted_order_days: if scaling_factors_by_day[day] == 0: del orders[day] else: orders[day].scale(scaling_factors_by_day[day]) def _scaling_level_for_date(self, d: dt.date) -> float: if self._scaling_level_signal is not None: if d in self._scaling_level_signal: return self._scaling_level_signal[d] return 0 else: return self.action.scaling_level def _scale_order(self, orders, daily_risk, price_measure, trigger_infos): if self.action.scaling_type == ScalingActionType.size: for day, portfolio in orders.items(): portfolio.scale(self._scaling_level_for_date(day)) elif self.action.scaling_type == ScalingActionType.NAV: self._nav_scale_orders(orders, price_measure, trigger_infos) elif self.action.scaling_type == ScalingActionType.risk_measure: for day, portfolio in orders.items(): scaling_factor = self._scaling_level_for_date(day) / daily_risk[day] portfolio.scale(scaling_factor) else: raise RuntimeError(f'Scaling Type {self.action.scaling_type} not supported by engine') def _raise_order( self, state_list: Collection[dt.date], price_measure: RiskMeasure, trigger_infos: Dict[dt.date, Optional[Union[AddScaledTradeActionInfo, Iterable[AddScaledTradeActionInfo]]]], ): if self.action.scaling_type == ScalingActionType.risk_measure: self._order_valuations.append(self.action.scaling_risk) orders = self.get_base_orders_for_states(state_list, trigger_infos=trigger_infos) final_orders = {} for d, res in orders.items(): new_port = [] dated_priceables = getattr(self.action, 'dated_priceables', {}) or {} instruments = dated_priceables.get(d) or self.action.priceables for inst in instruments: new_inst = res[inst] if len(self._order_valuations) > 1: new_inst = new_inst[ResolvedInstrumentValues] new_inst.name = f'{new_inst.name}_{d}' new_port.append(new_inst) final_orders[d] = Portfolio(new_port) daily_risk = ( {d: res[self.action.scaling_risk].aggregate() for d, res in orders.items()} if self.action.scaling_type == ScalingActionType.risk_measure else None ) self._scale_order(final_orders, daily_risk, price_measure, trigger_infos) return final_orders def apply_action( self, state: Union[dt.date, Iterable[dt.date]], backtest: BackTest, trigger_info: Optional[Union[AddScaledTradeActionInfo, Iterable[AddScaledTradeActionInfo]]] = None, ): state_list = make_list(state) if trigger_info is None or isinstance(trigger_info, AddScaledTradeActionInfo): trigger_info = [trigger_info for _ in range(len(state_list))] trigger_infos = dict(zip_longest(state_list, trigger_info)) orders = self._raise_order(state_list, backtest.price_measure, trigger_infos) current_tc_entries = [] # record entry and unwind cashflows for create_date, portfolio in orders.items(): info = trigger_infos[create_date] for inst in portfolio.all_instruments: tc_enter = TransactionCostEntry(create_date, inst, self.action.transaction_cost) current_tc_entries.append(tc_enter) backtest.cash_payments[create_date].append( CashPayment(inst, effective_date=create_date, direction=-1, transaction_cost_entry=tc_enter) ) backtest.transaction_cost_entries[create_date].append(tc_enter) final_date = self.get_instrument_final_date(inst, create_date, info) tc_exit = TransactionCostEntry(final_date, inst, self.action.transaction_cost_exit) current_tc_entries.append(tc_exit) backtest.cash_payments[final_date].append( CashPayment(inst, effective_date=final_date, transaction_cost_entry=tc_exit) ) backtest.transaction_cost_entries[final_date].append(tc_exit) backtest_states = (s for s in backtest.states if final_date > s >= create_date) for s in backtest_states: backtest.portfolio_dict[s].append(inst) with PricingContext(is_async=True): if any(tce.no_of_risk_calcs > 0 for tce in current_tc_entries): backtest.calc_calls += 1 for tce in current_tc_entries: backtest.calculations += tce.no_of_risk_calcs tce.calculate_unit_cost() return backtest class HedgeActionImpl(OrderBasedActionImpl): def __init__(self, action: HedgeAction): super().__init__(action) def get_base_orders_for_states(self, states: Collection[dt.date], **kwargs): with HistoricalPricingContext(dates=states, csa_term=self.action.csa_term): f = Portfolio(self.action.priceable).resolve(in_place=False) return f.result() def apply_action( self, state: Union[dt.date, Iterable[dt.date]], backtest: BackTest, trigger_info: Optional[Union[HedgeActionInfo, Iterable[HedgeActionInfo]]] = None, ): state_list = make_list(state) if trigger_info is None or isinstance(trigger_info, HedgeActionInfo): trigger_info = [trigger_info for _ in range(len(state_list))] trigger_infos = dict(zip_longest(state_list, trigger_info)) backtest.calc_calls += 1 backtest.calculations += len(state_list) orders = self.get_base_orders_for_states(state_list, trigger_infos=trigger_infos) current_tc_entries = [] for create_date, portfolio in orders.items(): info = trigger_infos[create_date] hedge_trade = portfolio.priceables[0] hedge_trade.name = f'{hedge_trade.name}_{create_date.strftime("%Y-%m-%d")}' if isinstance(hedge_trade, Portfolio): for instrument in hedge_trade.all_instruments: instrument.name = f'{hedge_trade.name}_{instrument.name}' final_date = self.get_instrument_final_date(hedge_trade, create_date, info) active_dates = [s for s in backtest.states if create_date <= s < final_date] if len(active_dates): scaling_portfolio = ScalingPortfolio( trade=hedge_trade, dates=active_dates, risk=self.action.risk, csa_term=self.action.csa_term, risk_transformation=self.action.risk_transformation, risk_percentage=self.action.risk_percentage, ) tc_enter = TransactionCostEntry(create_date, hedge_trade, self.action.transaction_cost) current_tc_entries.append(tc_enter) entry_payment = CashPayment( trade=hedge_trade, effective_date=create_date, direction=-1, transaction_cost_entry=tc_enter ) backtest.transaction_cost_entries[create_date].append(tc_enter) tc_exit = TransactionCostEntry(final_date, hedge_trade, self.action.transaction_cost_exit) current_tc_entries.append(tc_exit) exit_payment = ( CashPayment(trade=hedge_trade, effective_date=final_date, transaction_cost_entry=tc_exit) if final_date <= dt.date.today() else None ) backtest.transaction_cost_entries[final_date].append(tc_exit) hedge = Hedge( scaling_portfolio=scaling_portfolio, entry_payment=entry_payment, exit_payment=exit_payment ) backtest.hedges[create_date].append(hedge) with PricingContext(is_async=True): if any(tce.no_of_risk_calcs > 0 for tce in current_tc_entries): backtest.calc_calls += 1 for tce in current_tc_entries: backtest.calculations += tce.no_of_risk_calcs tce.calculate_unit_cost() return backtest class ExitTradeActionImpl(ActionHandler): def __init__(self, action: ExitTradeAction): super().__init__(action) def apply_action( self, state: Union[dt.date, Iterable[dt.date]], backtest: BackTest, trigger_info: Optional[Union[ExitTradeActionInfo, Iterable[ExitTradeActionInfo]]] = None, ): for s in make_list(state): trades_to_remove = [] if self.action.priceable_names is None: current_trade_names = [i.name for i in list(backtest.portfolio_dict[s].all_instruments)] fut_dates = list(filter(lambda d: d >= s and type(d) is dt.date, backtest.states)) for port_date in fut_dates: res_fut = [] res_futures = [] pos_fut = list(backtest.portfolio_dict[port_date].all_instruments) if backtest.results[port_date]: # there are results in future dates which need removing res_fut = list(backtest.results[port_date].portfolio.all_instruments) res_futures = list(backtest.results[port_date].futures) # We expect tradable names to be defined as __ if self.action.priceable_names: # List of trade names provided -> TradeDate <= exit trigger date and TradeName is present in list port_indexes_to_remove = [ i for i, x in enumerate(pos_fut) if dt.datetime.strptime(x.name.split('_')[-1], '%Y-%m-%d').date() <= s and x.name.split('_')[-2] in self.action.priceable_names ] result_indexes_to_remove = [ i for i, x in enumerate(res_fut) if dt.datetime.strptime(x.name.split('_')[-1], '%Y-%m-%d').date() <= s and x.name.split('_')[-2] in self.action.priceable_names ] else: # List of trade names not provided -> TradeDate <= exit trigger date and trade present on trigger # date port_indexes_to_remove = [i for i, x in enumerate(pos_fut) if x.name in current_trade_names] result_indexes_to_remove = [i for i, x in enumerate(res_fut) if x.name in current_trade_names] for index in sorted(port_indexes_to_remove, reverse=True): # Get list of trades that have been removed to check for their future cash flow date if pos_fut[index].name not in trades_to_remove: trades_to_remove.append(pos_fut[index]) del pos_fut[index] for index in sorted(result_indexes_to_remove, reverse=True): del res_fut[index] del res_futures[index] backtest.portfolio_dict[port_date] = Portfolio(tuple(pos_fut)) if result_indexes_to_remove: backtest.results[port_date] = PortfolioRiskResult( Portfolio(res_fut), backtest.results[port_date].risk_measures, res_futures ) for cp_date, cp_list in list(backtest.cash_payments.items()): if cp_date > s: indexes_to_remove = [ i for i, cp in enumerate(cp_list) if cp.trade.name in [x.name for x in trades_to_remove] ] for index in sorted(indexes_to_remove, reverse=True): cp = cp_list[index] prev_pos = [i for i, x in enumerate(backtest.cash_payments[s]) if cp.trade.name == x.trade.name] # If trade already exists in exit trigger date cash payments, net out the position if prev_pos: backtest.cash_payments[s][prev_pos[0]].direction += cp.direction else: cp.effective_date = s backtest.cash_payments[s].append(cp) backtest.transaction_cost_entries[s].append(cp.transaction_cost_entry) backtest.transaction_cost_entries[cp_date].remove(cp.transaction_cost_entry) cp.transaction_cost_entry.date = s del backtest.cash_payments[cp_date][index] if not backtest.cash_payments[cp_date]: del backtest.cash_payments[cp_date] for trade in trades_to_remove: if trade.name not in [x.trade.name for x in backtest.cash_payments[s]]: # to_dict omits name trade_instruments = ( set(t.to_dict() for t in trade.all_instruments) if isinstance(trade, Portfolio) else {trade.to_dict()} ) # find TCE corresponding to trade trade_tce = [ tce for tce in backtest.transaction_cost_entries[s] if set(i.to_dict() for i in tce.all_instruments) == trade_instruments ] tce = trade_tce[0] if trade_tce else None backtest.cash_payments[s].append(CashPayment(trade, effective_date=s, transaction_cost_entry=tce)) return backtest class RebalanceActionImpl(ActionHandler): def __init__(self, action: RebalanceAction): super().__init__(action) def apply_action( self, state: Union[dt.date, Iterable[dt.date]], backtest: BackTest, trigger_info: Optional[Union[RebalanceActionInfo, Iterable[RebalanceActionInfo]]] = None, ): new_size = self.action.method(state, backtest, trigger_info) current_size = 0 for trade in backtest.portfolio_dict[state]: if self.action.priceable.name.split('_')[-1] in trade.name: current_size += getattr(trade, self.action.size_parameter) # if we are already at the required size then do nothing. if new_size - current_size == 0: return backtest pos = self.action.priceable.clone( **{self.action.size_parameter: new_size - current_size, 'name': f'{self.action.priceable.name}_{state}'} ) current_tc_entries = [] tc_enter = TransactionCostEntry(state, pos, self.action.transaction_cost) current_tc_entries.append(tc_enter) backtest.cash_payments[state].append( CashPayment(pos, effective_date=state, direction=-1, transaction_cost_entry=tc_enter) ) backtest.transaction_cost_entries[state].append(tc_enter) unwind_payment = None cash_payment_dates = backtest.cash_payments.keys() for d in reversed(sorted(cash_payment_dates)): for cp in backtest.cash_payments[d]: if self.action.priceable.name.split('_')[-1] in cp.trade.name and cp.direction == 1: tc_exit = TransactionCostEntry(d, pos, self.action.transaction_cost_exit) current_tc_entries.append(tc_exit) unwind_payment = CashPayment(pos, effective_date=d, transaction_cost_entry=tc_exit) backtest.cash_payments[d].append(unwind_payment) backtest.transaction_cost_entries[d].append(exit) break if unwind_payment: break if unwind_payment is None: raise ValueError("Found no final cash payment to rebalance for trade.") for s in backtest.states: if unwind_payment.effective_date > s >= state: backtest.portfolio_dict[s].append(pos) with PricingContext(is_async=True): if any(tce.no_of_risk_calcs > 0 for tce in current_tc_entries): backtest.calc_calls += 1 for tce in current_tc_entries: backtest.calculations += tce.no_of_risk_calcs tce.calculate_unit_cost() return backtest class AddWeightedTradeActionImpl(OrderBasedActionImpl): def __init__(self, action: AddWeightedTradeAction): super().__init__(action) def get_base_orders_for_states(self, states: Collection[dt.date], **kwargs): with HistoricalPricingContext(dates=states): f = Portfolio(self.action.priceables).resolve(in_place=False) return f.result() def apply_action( self, state: Union[dt.date, Iterable[dt.date]], backtest: BackTest, trigger_info: Optional[Union[AddWeightedTradeActionInfo, Iterable[AddWeightedTradeActionInfo]]] = None, ): state_list = make_list(state) if trigger_info is None or isinstance(trigger_info, AddWeightedTradeActionInfo): trigger_info = [trigger_info for _ in range(len(state_list))] trigger_infos = dict(zip_longest(state_list, trigger_info)) backtest.calc_calls += 1 backtest.calculations += len(state_list) orders = self.get_base_orders_for_states(state_list, trigger_infos=trigger_infos) current_tc_entries = [] for create_date, portfolio in orders.items(): info = trigger_infos[create_date] # Get all instruments from the resolved portfolio instruments = portfolio.priceables if not instruments: continue # Rename instruments with the create date renamed_instruments = [] for inst in instruments: renamed_inst = inst.clone(name=f'{inst.name}_{create_date.strftime("%Y-%m-%d")}') renamed_instruments.append(renamed_inst) weighted_portfolio = Portfolio(renamed_instruments) final_date = self.get_instrument_final_date(renamed_instruments[0], create_date, info) active_dates = [s for s in backtest.states if create_date <= s < final_date] if len(active_dates): scaling_portfolio = WeightedScalingPortfolio( trades=weighted_portfolio, dates=active_dates, risk=self.action.scaling_risk, total_size=self.action.total_size, ) # Create entry and exit payments for each instrument entry_payments = [] exit_payments = [] for inst in renamed_instruments: tc_enter = TransactionCostEntry(create_date, inst, self.action.transaction_cost) current_tc_entries.append(tc_enter) entry_payment = CashPayment( trade=inst, effective_date=create_date, direction=-1, transaction_cost_entry=tc_enter ) entry_payments.append(entry_payment) backtest.transaction_cost_entries[create_date].append(tc_enter) tc_exit = TransactionCostEntry(final_date, inst, self.action.transaction_cost_exit) current_tc_entries.append(tc_exit) exit_payment = ( CashPayment(trade=inst, effective_date=final_date, transaction_cost_entry=tc_exit) if final_date <= dt.date.today() else None ) exit_payments.append(exit_payment) backtest.transaction_cost_entries[final_date].append(tc_exit) weighted_trade = WeightedTrade( scaling_portfolio=scaling_portfolio, entry_payments=entry_payments, exit_payments=exit_payments, ) backtest.weighted_trades[create_date].append(weighted_trade) with PricingContext(is_async=True): if any(tce.no_of_risk_calcs > 0 for tce in current_tc_entries): backtest.calc_calls += 1 for tce in current_tc_entries: backtest.calculations += tce.no_of_risk_calcs tce.calculate_unit_cost() return backtest ================================================ FILE: gs_quant/backtests/order.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from abc import ABCMeta from gs_quant.instrument import Instrument, Cash from gs_quant.backtests.core import TimeWindow, ValuationFixingType from gs_quant.backtests.data_handler import DataHandler import numpy as np import datetime as dt class OrderBase(metaclass=ABCMeta): def __init__(self, instrument: Instrument, quantity: float, generation_time: dt.datetime, source: str): """ Create an order :param instrument: an Instrument or Security to be traded :param quantity: quantity to be traded in instrument :param generation_time: the time when this order was generated :param source: the name of the entity that generated this order """ self.instrument = instrument self.quantity = quantity self.generation_time = generation_time self.source = source self.executed_price = None def execution_end_time(self) -> dt.datetime: raise RuntimeError('The method execution_end_time is not implemented on OrderBase') def _execution_price(self, data_handler: DataHandler) -> float: raise RuntimeError('The method execution_price is not implemented on OrderBase') def execution_price(self, data_handler: DataHandler) -> float: price = self._execution_price(data_handler) if np.isnan(price): raise RuntimeError('can not compute the execution price') else: return price def execution_quantity(self) -> float: raise RuntimeError('The method execution_price is not implemented on OrderBase') def execution_notional(self, data_hander: DataHandler) -> float: return self.execution_price(data_hander) * self.execution_quantity() def _short_name(self) -> str: raise RuntimeError('The method _short_name is not implemented on OrderBase') def to_dict(self, data_hander: DataHandler) -> dict: return { 'Instrument': self.instrument.ric, 'Type': self._short_name(), 'Price': self.execution_price(data_hander), 'Quantity': self.execution_quantity(), } class OrderTWAP(OrderBase): def __init__( self, instrument: Instrument, quantity: float, generation_time: dt.datetime, source: str, window: TimeWindow ): super().__init__(instrument, quantity, generation_time, source) """ Create a TWAP order :param window: TWAP window """ self.window = window def execution_end_time(self) -> dt.datetime: return self.window.end def _execution_price(self, data_handler: DataHandler) -> float: if self.executed_price is None: fixings = data_handler.get_data_range( self.window.start, self.window.end, self.instrument, ValuationFixingType.PRICE ) self.executed_price = np.mean(fixings) return self.executed_price def execution_quantity(self) -> float: return self.quantity def _short_name(self) -> str: return 'TWAP {0}:{1}'.format(self.window.start, self.window.end) class OrderMarketOnClose(OrderBase): def __init__( self, instrument: Instrument, quantity: float, generation_time: dt.datetime, execution_date: dt.date, source: str, ): super().__init__(instrument, quantity, generation_time, source) self.execution_date = execution_date def execution_end_time(self) -> dt.datetime: return dt.datetime.combine(self.execution_date, dt.time(23, 0, 0)) def _execution_price(self, data_handler: DataHandler) -> float: if self.executed_price is None: self.executed_price = data_handler.get_data(self.execution_date, self.instrument, ValuationFixingType.PRICE) return self.executed_price def execution_quantity(self) -> float: return self.quantity def _short_name(self) -> str: return 'MOC' class OrderCost(OrderBase): def __init__(self, currency: str, quantity: float, source: str, execution_time: dt.datetime): super().__init__(Cash(currency), quantity, generation_time=execution_time, source=source) """ Create a cost order e.g. transaction or servicing cost :param execution_time: the time when the order is executed """ self.execution_time = execution_time def execution_end_time(self) -> dt.datetime: return self.execution_time def _execution_price(self, data_handler: DataHandler) -> float: if self.executed_price is None: self.executed_price = 0 return self.executed_price def execution_quantity(self) -> float: return self.quantity def _short_name(self) -> str: return 'Cost' def to_dict(self, data_hander: DataHandler) -> dict: return { 'Instrument': self.instrument.currency, 'Type': self._short_name(), 'Price': self.execution_price(data_hander), 'Quantity': self.execution_quantity(), } class OrderAtMarket(OrderBase): def __init__( self, instrument: Instrument, quantity: float, generation_time: dt.datetime, execution_datetime: dt.datetime, source: str, ): super().__init__(instrument, quantity, generation_time, source) self.execution_datetime = execution_datetime def execution_end_time(self) -> dt.datetime: return self.execution_datetime def _execution_price(self, data_handler: DataHandler) -> float: if self.executed_price is None: self.executed_price = data_handler.get_data( self.execution_datetime, self.instrument, ValuationFixingType.PRICE ) return self.executed_price def execution_quantity(self) -> float: return self.quantity def _short_name(self) -> str: return 'Market' class OrderTwapBTIC(OrderTWAP): def __init__( self, instrument: Instrument, quantity: float, generation_time: dt.datetime, source: str, window: TimeWindow, btic_instrument: Instrument, future_underlying, ): super().__init__(instrument, quantity, generation_time, source, window) """ Create a BTIC TWAP order: an order for a future executed at underlying spot + BTIC TWAP """ self.btic_instrument = btic_instrument self.future_underlying = future_underlying def _execution_price(self, data_handler: DataHandler) -> float: if self.executed_price is None: btic_fixings = data_handler.get_data_range( self.window.start, self.window.end, self.btic_instrument, ValuationFixingType.PRICE ) btic_twap = np.mean(btic_fixings) close = data_handler.get_data(self.window.end.date(), self.future_underlying) self.executed_price = close + btic_twap return self.executed_price def _short_name(self) -> str: return 'TwapBTIC' ================================================ FILE: gs_quant/backtests/predefined_asset_engine.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from collections import deque from functools import reduce from itertools import compress from typing import Union, Tuple import pandas as pd from pandas.tseries.offsets import BDay # noqa from tqdm import tqdm from gs_quant.backtests import ValuationFixingType from gs_quant.backtests.action_handler import ActionHandlerBaseFactory, ActionHandler from gs_quant.backtests.actions import Action, AddTradeAction, AddTradeActionInfo from gs_quant.backtests.backtest_engine import BacktestBaseEngine from gs_quant.backtests.backtest_objects import PredefinedAssetBacktest from gs_quant.backtests.core import ValuationMethod from gs_quant.backtests.data_handler import DataHandler from gs_quant.backtests.data_sources import DataManager from gs_quant.backtests.event import ValuationEvent, OrderEvent, MarketEvent from gs_quant.backtests.execution_engine import SimulatedExecutionEngine from gs_quant.backtests.order import OrderAtMarket from gs_quant.datetime import is_business_day, prev_business_date, business_day_offset # Action Implementations class AddTradeActionImpl(ActionHandler): def __init__(self, action: AddTradeAction): super().__init__(action) def generate_orders(self, state: dt.datetime, backtest: PredefinedAssetBacktest, info: AddTradeActionInfo): orders = [] for pricable in self.action.priceables: quantity = pricable.instrument_quantity * 1 if info is None or info.scaling is None else info.scaling orders.append( OrderAtMarket( instrument=pricable, quantity=quantity, generation_time=state, execution_datetime=state, source=self.action.name, ) ) if isinstance(self.action.trade_duration, dt.timedelta): # create close order orders.append( OrderAtMarket( instrument=pricable, quantity=quantity * -1, generation_time=state, execution_datetime=state + self.action.trade_duration, source=self.action.name, ) ) return orders def apply_action(self, state: dt.datetime, backtest: PredefinedAssetBacktest, info=None): orders = self.generate_orders(state, backtest, info) return orders class SubmitOrderActionImpl(ActionHandler): """ The apply_action method simply returns the orders generated by the trigger. """ def __init__(self, action: Action): super().__init__(action) def apply_action(self, state: dt.datetime, backtest: PredefinedAssetBacktest, info=None): return info class PredefinedAssetEngineActionFactory(ActionHandlerBaseFactory): def __init__(self, action_impl_map=None): action_impl_map = action_impl_map or {} self.action_impl_map = action_impl_map self.action_impl_map[AddTradeAction] = AddTradeActionImpl def get_action_handler(self, action: Action) -> Action: if type(action) in self.action_impl_map: return self.action_impl_map[type(action)](action) raise RuntimeError(f'Action {type(action)} not supported by engine') class PredefinedAssetEngine(BacktestBaseEngine): def get_action_handler(self, action: Action) -> Action: handler_factory = PredefinedAssetEngineActionFactory(self.action_impl_map) return handler_factory.get_action_handler(action) def supports_strategy(self, strategy): all_actions = reduce(lambda x, y: x + y, (map(lambda x: x.actions, strategy.triggers))) try: for x in all_actions: self.get_action_handler(x) except RuntimeError: return False return True def __init__( self, data_mgr: DataManager = DataManager(), calendars: Union[str, Tuple[str, ...]] = None, tz: dt.timezone = dt.timezone.utc, valuation_method: ValuationMethod = ValuationMethod(ValuationFixingType.PRICE), action_impl_map=None, ): if action_impl_map is None: action_impl_map = {Action: SubmitOrderActionImpl} self.action_impl_map = action_impl_map self.calendars = calendars self.tz = tz self.data_handler = DataHandler(data_mgr, tz=tz) self.valuation_method = valuation_method self.execution_engine = None def _eod_valuation_time(self): if self.valuation_method.window: return self.valuation_method.window.end else: return dt.time(23) def _timer(self, strategy, start, end, frequency, states=None): dates = ( list(map(lambda x: x.date(), pd.to_datetime(pd.bdate_range(start=start, end=end, freq=frequency)))) if states is None else states ) all_times = [] times = list() for trigger in strategy.triggers: if hasattr(trigger, 'get_trigger_times'): for t in trigger.get_trigger_times(): # allow user to define their trigger times as a time, in which case add that time to every date # or as a datetime itself in which case just add it to the timer if isinstance(t, dt.datetime): all_times.append(t) else: times.append(t) times.append(self._eod_valuation_time()) times = list(dict.fromkeys(times)) if self.calendars is not None: dates = list( compress( dates, is_business_day(dates, (None if self.calendars.lower() == 'weekend' else self.calendars)) ) ) for d in dates: if isinstance(d, dt.datetime): all_times.append(d) for t in times: if d.tzinfo is not None and d.tzinfo.utcoffset(d) is not None: all_times.append(dt.datetime.combine(d.date(), t, d.tzinfo)) else: for t in times: all_times.append(dt.datetime.combine(d, t)) all_times = list(set(all_times)) all_times.sort() return all_times def _adjust_date(self, date): date = (date + BDay(1) - BDay(1)).date() # 1st move to latest weekday. if self.calendars is None or self.calendars.lower() == 'weekend' or is_business_day(date, self.calendars): return date else: return prev_business_date(date, None if self.calendars.lower() == 'weekend' else self.calendars) def run_backtest(self, strategy, start, end, frequency="B", states=None, initial_value=100): # initialize backtest object self.data_handler.reset_clock() backtest = PredefinedAssetBacktest(self.data_handler, initial_value) # initialize execution engine self.execution_engine = SimulatedExecutionEngine(self.data_handler) if states is not None: timer = self._timer(strategy, start, end, frequency, states) else: # if start is a holiday, go back to the previous day on the backtest calendar adjusted_start = self._adjust_date(start) backtest.set_start_date(adjusted_start) # create timer timer_start = ( (adjusted_start + BDay(1)).date() if self.calendars is None else business_day_offset( adjusted_start, 1, roll='forward', calendars=None if self.calendars.lower() == 'weekend' else self.calendars, ) ) timer_end = self._adjust_date(end) timer = self._timer(strategy, timer_start, timer_end, frequency) self._run(strategy, timer, backtest) return backtest def _run(self, strategy, timer, backtest: PredefinedAssetBacktest): events = deque() for state in tqdm(timer): # update to latest data self.data_handler.update(state) # see if any submitted orders have been executed fills = self.execution_engine.ping(state) events.extend(fills) # generate a market event events.append(MarketEvent()) # create valuation event when it's due for daily valuation if state.time() == self._eod_valuation_time(): events.append(ValuationEvent()) while events: event = events.popleft() if event.type == 'Market': # market event (new mkt data coming in) for trigger in strategy.triggers: trigger_info = trigger.has_triggered(state, backtest) if trigger_info.triggered: for action in trigger.actions: info_dict = trigger_info.info_dict info = info_dict[type(action)] if info_dict and type(action) in info_dict else None orders = self.get_action_handler(action).apply_action(state, backtest, info) backtest.record_orders(orders) events.extend([OrderEvent(o) for o in orders]) elif event.type == 'Order': # order event (submit the order to execution engine) self.execution_engine.submit_order(event) elif event.type == 'Fill': # fill event (update backtest with the fill results) backtest.update_fill(event) elif event.type == 'Valuation': # valuation event (calculate daily level) backtest.mark_to_market(state, self.valuation_method) return backtest ================================================ FILE: gs_quant/backtests/strategy.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from dataclasses import dataclass, field from typing import Tuple, Optional, Union, Iterable from dataclasses_json import dataclass_json, config from gs_quant.backtests.backtest_objects import CashAccrualModel from gs_quant.backtests.backtest_utils import make_list from gs_quant.backtests.triggers import Trigger from gs_quant.base import Priceable from gs_quant.json_convertors import decode_named_instrument, encode_named_instrument, dc_decode def _backtest_engines(): from gs_quant.backtests.equity_vol_engine import EquityVolEngine from gs_quant.backtests.generic_engine import GenericEngine from gs_quant.backtests.predefined_asset_engine import PredefinedAssetEngine return [GenericEngine(), PredefinedAssetEngine(), EquityVolEngine()] @dataclass_json @dataclass class Strategy: """ A strategy object on which one may run a backtest """ initial_portfolio: Optional[Union[Tuple[Priceable, ...], dict]] = field( default=None, metadata=config(decoder=decode_named_instrument, encoder=encode_named_instrument) ) triggers: Union[Trigger, Iterable[Trigger]] = field( default=None, metadata=config(decoder=dc_decode(*Trigger.sub_classes(), allow_missing=True)) ) cash_accrual: CashAccrualModel = None risks = None def __post_init__(self): if not isinstance(self.initial_portfolio, dict): self.initial_portfolio = make_list(self.initial_portfolio) self.triggers = make_list(self.triggers) self.risks = self.get_risks() def get_risks(self): risk_list = [] for t in self.triggers: risk_list += t.risks if t.risks is not None else [] return risk_list def get_available_engines(self): return [engine for engine in _backtest_engines() if engine.supports_strategy(self)] ================================================ FILE: gs_quant/backtests/strategy_systematic.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import logging from typing import Iterable, Union, Tuple import gs_quant.target.backtests as backtests from gs_quant.api.gs.backtests import GsBacktestApi from gs_quant.api.gs.backtests_xasset.apis import GsBacktestXassetApi from gs_quant.api.gs.backtests_xasset.request import BasicBacktestRequest from gs_quant.api.gs.backtests_xasset.response_datatypes.backtest_datatypes import ( DateConfig, Trade, Configuration, RollDateMode, TransactionCostConfig, StrategyHedge, ) from gs_quant.backtests.core import Backtest, TradeInMethod from gs_quant.base import get_enum_value, Base from gs_quant.common import Currency from gs_quant.common import FieldValueMap, AssetClass from gs_quant.errors import MqValueError from gs_quant.instrument import ( EqOption, EqVarianceSwap, Instrument, ) from gs_quant.target.backtests import ( BacktestResult, BacktestRisk, BacktestTradingQuantityType, DeltaHedgeParameters, BacktestSignalSeriesItem, BacktestStrategyUnderlier, BacktestStrategyUnderlierHedge, EquityMarketModel, BacktestTradingParameters, FlowVolBacktestMeasure, ) _logger = logging.getLogger(__name__) BACKTEST_TYPE_NAME = 'VolatilityFlow' BACKTEST_TYPE_VALUE = 'Volatility Flow' ISO_FORMAT = r"^([0-9]{4})-([0-9]{2})-([0-9]{2})$" class StrategySystematic: """Equity back testing systematic strategy""" _supported_eq_instruments = (EqOption, EqVarianceSwap) def __init__( self, underliers: Union[Instrument, Iterable[Instrument]], quantity: float = 1, quantity_type: Union[BacktestTradingQuantityType, str] = BacktestTradingQuantityType.notional, trade_in_method: Union[TradeInMethod, str] = TradeInMethod.FixedRoll, roll_frequency: str = None, scaling_method: str = None, index_initial_value: float = 0.0, delta_hedge: DeltaHedgeParameters = None, name: str = None, cost_netting: bool = False, currency: Union[Currency, str] = Currency.USD, trade_in_signals: Tuple[BacktestSignalSeriesItem, ...] = None, trade_out_signals: Tuple[BacktestSignalSeriesItem, ...] = None, market_model: Union[EquityMarketModel, str] = EquityMarketModel.SFK, roll_date_mode: str = None, expiry_date_mode: str = None, cash_accrual: bool = True, combine_roll_signal_entries: bool = False, transaction_cost_config: TransactionCostConfig = None, use_xasset_backtesting_service: bool = True, ): self.__cost_netting = cost_netting self.__currency = get_enum_value(Currency, currency) self.__name = name self.__backtest_type = BACKTEST_TYPE_NAME self.__cash_accrual = cash_accrual trade_in_method = get_enum_value(TradeInMethod, trade_in_method).value market_model = get_enum_value(EquityMarketModel, market_model).value self.__trading_parameters = BacktestTradingParameters( quantity=quantity, quantity_type=get_enum_value(BacktestTradingQuantityType, quantity_type).value, trade_in_method=trade_in_method, roll_frequency=roll_frequency, trade_in_signals=trade_in_signals, trade_out_signals=trade_out_signals, roll_date_mode=roll_date_mode, ) self.__underliers = [] trade_instruments = [] def is_unsupported_eq_instrument(inst): return inst.__class__.__name__.startswith('Eq') and not isinstance(inst, self._supported_eq_instruments) if isinstance(underliers, Iterable): for underlier in underliers: if isinstance(underlier, tuple): instrument = underlier[0] notional_percentage = underlier[1] else: instrument = underlier notional_percentage = 100 if is_unsupported_eq_instrument(instrument): raise MqValueError('The format of the backtest asset is incorrect.') else: instrument = instrument.scale(notional_percentage / 100, in_place=False, check_resolved=False) trade_instruments.append(instrument) self.__underliers.append( BacktestStrategyUnderlier( instrument=instrument, notional_percentage=notional_percentage, hedge=BacktestStrategyUnderlierHedge(risk_details=delta_hedge), market_model=market_model, expiry_date_mode=expiry_date_mode, ) ) else: instrument = underliers if is_unsupported_eq_instrument(instrument): raise MqValueError('The format of the backtest asset is incorrect.') notional_percentage = 100 trade_instruments.append(instrument) self.__underliers.append( BacktestStrategyUnderlier( instrument=instrument, notional_percentage=notional_percentage, hedge=BacktestStrategyUnderlierHedge(risk_details=delta_hedge), market_model=market_model, expiry_date_mode=expiry_date_mode, ) ) # xasset backtesting service fields trade_buy_dates = tuple(s.date for s in trade_in_signals if s.value) if trade_in_signals is not None else None trade_exit_dates = ( tuple(s.date for s in trade_out_signals if s.value) if trade_out_signals is not None else None ) self.__trades = ( Trade( tuple(trade_instruments), roll_frequency, trade_buy_dates, roll_frequency, trade_exit_dates, quantity, quantity_type, ), ) if delta_hedge: self.__hedge_params = StrategyHedge() if delta_hedge.frequency: self.__hedge_params.frequency = '1b' if delta_hedge.frequency == 'Daily' else delta_hedge.frequency if delta_hedge.notional: self.__hedge_params.risk_percentage = delta_hedge.notional else: self.__hedge_params = None self.__transaction_cost_config = transaction_cost_config self.__xasset_bt_service_config = Configuration( roll_date_mode=RollDateMode(roll_date_mode) if roll_date_mode is not None else None, market_model=EquityMarketModel(market_model) if market_model else None, cash_accrual=cash_accrual, combine_roll_signal_entries=combine_roll_signal_entries, ) backtest_parameters_class: Base = getattr(backtests, self.__backtest_type + 'BacktestParameters') backtest_parameter_args = { 'trading_parameters': self.__trading_parameters, 'underliers': self.__underliers, 'trade_in_method': trade_in_method, 'scaling_method': scaling_method, 'index_initial_value': index_initial_value, } self.__backtest_parameters = backtest_parameters_class.from_dict(backtest_parameter_args) self.__use_xasset_backtesting_service = use_xasset_backtesting_service def __run_service_based_backtest( self, start: dt.date, end: dt.date, measures: Iterable[FlowVolBacktestMeasure] ) -> BacktestResult: date_cfg = DateConfig(start, end) if not measures: measures = (FlowVolBacktestMeasure.PNL,) basic_bt_request = BasicBacktestRequest( dates=date_cfg, trades=self.__trades, measures=measures, transaction_costs=self.__transaction_cost_config, configuration=self.__xasset_bt_service_config, hedge=self.__hedge_params, ) basic_bt_response = GsBacktestXassetApi.calculate_basic_backtest(basic_bt_request, decode_instruments=False) risks = tuple( BacktestRisk(name=k.value, timeseries=tuple(FieldValueMap(date=d, value=r.result) for d, r in v.items())) for k, v in basic_bt_response.measures.items() ) events = [] if basic_bt_response.additional_results is not None: if basic_bt_response.additional_results.trade_events is not None: events.append( BacktestRisk( name="trade_events", timeseries=tuple( FieldValueMap(date=d, value=e) for d, e in basic_bt_response.additional_results.trade_events.items() ), ) ) if basic_bt_response.additional_results.hedge_events is not None: events.append( BacktestRisk( name="hedge_events", timeseries=tuple( FieldValueMap(date=d, value=e) for d, e in basic_bt_response.additional_results.hedge_events.items() ), ) ) portfolio = [] for d in sorted(set().union(basic_bt_response.portfolio.keys(), basic_bt_response.transactions.keys())): if d in basic_bt_response.portfolio: positions = [ {'instrument': i if i is not None else {}, 'quantity': self.__position_quantity(i)} for i in basic_bt_response.portfolio[d] ] else: positions = [] transactions = [] if d in basic_bt_response.transactions: for t in basic_bt_response.transactions[d]: trades = ( [ { 'instrument': i if i is not None else {}, 'price': t.portfolio_price, 'quantity': t.quantity if t.quantity is not None else None, } for i in t.portfolio ] if t.portfolio is not None else [] ) transactions.append({'type': t.direction.value, 'trades': trades, 'cost': t.cost}) portfolio.append({'date': d, 'positions': positions, 'transactions': transactions}) return BacktestResult(risks=risks, events=tuple(events), portfolio=portfolio) def __position_quantity(self, instrument: dict) -> float: if instrument.get('assetClass') == AssetClass.Equity.value: direction = 1 if instrument['buySell'] == 'Buy' else -1 quantity = instrument['numberOfOptions'] if instrument['type'] == 'Option' else instrument['quantity'] return direction * quantity return None def backtest( self, start: dt.date = None, end: dt.date = dt.date.today() - dt.timedelta(days=1), is_async: bool = False, measures: Iterable[FlowVolBacktestMeasure] = (FlowVolBacktestMeasure.ALL_MEASURES,), correlation_id: str = None, ) -> Union[Backtest, BacktestResult]: if self.__use_xasset_backtesting_service: return self.__run_service_based_backtest(start, end, measures) params_dict = self.__backtest_parameters.as_dict() params_dict['measures'] = [m.value for m in measures] backtest_parameters_class: Base = getattr(backtests, self.__backtest_type + 'BacktestParameters') params = backtest_parameters_class.from_dict(params_dict) backtest = Backtest( name=self.__name, mq_symbol=self.__name, parameters=params, start_date=start, end_date=end, type=BACKTEST_TYPE_VALUE, asset_class=AssetClass.Equity, currency=self.__currency, cost_netting=self.__cost_netting, cash_accrual=self.__cash_accrual, ) if is_async: # Create back test ... response = GsBacktestApi.create_backtest(backtest) # ... and schedule it GsBacktestApi.schedule_backtest(backtest_id=response.id) else: # Run on-the-fly back test response = GsBacktestApi.run_backtest(backtest, correlation_id) return response ================================================ FILE: gs_quant/backtests/triggers.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from dataclasses import dataclass, field from enum import Enum from typing import ClassVar, List, Optional, Iterable, Union from dataclasses_json import dataclass_json, config from gs_quant.backtests.actions import ( AddTradeAction, AddTradeActionInfo, AddScaledTradeAction, AddScaledTradeActionInfo, HedgeAction, HedgeActionInfo, Action, ) from gs_quant.backtests.backtest_objects import BackTest, PredefinedAssetBacktest from gs_quant.backtests.backtest_utils import make_list, CalcType from gs_quant.backtests.data_sources import DataSource, GsDataSource from gs_quant.base import field_metadata, exclude_none, static_field from gs_quant.data import Dataset from gs_quant.datetime.relative_date import RelativeDateSchedule from gs_quant.json_convertors import decode_iso_date_or_datetime, decode_date_tuple, dc_decode from gs_quant.json_convertors_common import encode_risk_measure, decode_risk_measure from gs_quant.risk import RiskMeasure from gs_quant.risk.transform import Transformer class TriggerDirection(Enum): ABOVE = 1 BELOW = 2 EQUAL = 3 class AggType(Enum): ALL_OF = 1 ANY_OF = 2 @dataclass_json @dataclass class TriggerRequirements: __sub_classes: ClassVar[List[type]] = [] def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) TriggerRequirements.__sub_classes.append(cls) @staticmethod def sub_classes(): return tuple(TriggerRequirements.__sub_classes) def get_trigger_times(self): return [] @property def calc_type(self): return CalcType.simple @dataclass_json @dataclass class TriggerInfo(object): triggered: bool info_dict: Optional[dict] = None def __eq__(self, other): return self.triggered is other def __bool__(self): return self.triggered def check_barrier(direction, test_value, trigger_level) -> TriggerInfo: if direction == TriggerDirection.ABOVE: if test_value > trigger_level: return TriggerInfo(True) elif direction == TriggerDirection.BELOW: if test_value < trigger_level: return TriggerInfo(True) else: if test_value == trigger_level: return TriggerInfo(True) return TriggerInfo(False) @dataclass_json @dataclass class PeriodicTriggerRequirements(TriggerRequirements): start_date: Optional[dt.date] = field(default=None, metadata=field_metadata) end_date: Optional[dt.date] = field(default=None, metadata=field_metadata) frequency: Optional[str] = field(default=None, metadata=field_metadata) calendar: Optional[Iterable[dt.date]] = field( default=None, metadata=config(exclude=exclude_none, decoder=decode_date_tuple) ) trigger_dates = [] class_type: str = static_field('periodic_trigger_requirements') def get_trigger_times(self) -> [dt.date]: if not self.trigger_dates: self.trigger_dates = RelativeDateSchedule(self.frequency, self.start_date, self.end_date).apply_rule( holiday_calendar=self.calendar ) return self.trigger_dates def has_triggered(self, state: dt.date, backtest: BackTest = None) -> TriggerInfo: if not self.trigger_dates: self.get_trigger_times() if state in self.trigger_dates: next_state = None if self.trigger_dates.index(state) != len(self.trigger_dates) - 1: next_state = self.trigger_dates[self.trigger_dates.index(state) + 1] return TriggerInfo( True, { AddTradeAction: AddTradeActionInfo(scaling=None, next_schedule=next_state), AddScaledTradeAction: AddScaledTradeActionInfo(next_schedule=next_state), HedgeAction: HedgeActionInfo(next_schedule=next_state), }, ) return TriggerInfo(False) @dataclass_json @dataclass class IntradayTriggerRequirements(TriggerRequirements): start_time: Optional[dt.time] = field(default=None, metadata=field_metadata) end_time: Optional[dt.time] = field(default=None, metadata=field_metadata) frequency: Optional[float] = field(default=None, metadata=field_metadata) class_type: str = static_field('intraday_trigger_requirements') def __post_init__(self): # generate all the trigger times self._trigger_times = [] time = self.start_time while time <= self.end_time: self._trigger_times.append(time) time = (dt.datetime.combine(dt.date.today(), time) + dt.timedelta(minutes=self.frequency)).time() def get_trigger_times(self): return self._trigger_times def has_triggered(self, state: Union[dt.date, dt.datetime], backtest: BackTest = None) -> TriggerInfo: return TriggerInfo(state.time() in self._trigger_times) @dataclass_json @dataclass class MktTriggerRequirements(TriggerRequirements): data_source: DataSource = field( default=None, metadata=config(decoder=dc_decode(*DataSource.sub_classes(), allow_missing=True)) ) trigger_level: float = field(default=None, metadata=field_metadata) direction: TriggerDirection = field(default=None, metadata=field_metadata) class_type: str = static_field('mkt_trigger_requirements') def has_triggered(self, state: dt.date, backtest: BackTest = None) -> TriggerInfo: data_value = self.data_source.get_data(state) try: triggered = check_barrier(self.direction, data_value, self.trigger_level) except TypeError: raise RuntimeError(f'unable to determine trigger state on {str(state)}, data value was {data_value}') return triggered @dataclass_json @dataclass class RiskTriggerRequirements(TriggerRequirements): risk: RiskMeasure = field(default=None, metadata=config(decoder=decode_risk_measure, encoder=encode_risk_measure)) trigger_level: float = field(default=None, metadata=field_metadata) direction: TriggerDirection = field(default=None, metadata=field_metadata) risk_transformation: Optional[Transformer] = field(default=None, metadata=field_metadata) class_type: str = static_field('risk_trigger_requirements') def has_triggered(self, state: dt.date, backtest: BackTest = None) -> TriggerInfo: if state not in backtest.results: return TriggerInfo(False) if self.risk_transformation is None: risk_value = backtest.results[state][self.risk].aggregate() else: risk_value = ( backtest.results[state][self.risk] .transform(risk_transformation=self.risk_transformation) .aggregate(allow_mismatch_risk_keys=True) ) return check_barrier(self.direction, risk_value, self.trigger_level) @property def calc_type(self): return CalcType.path_dependent @dataclass_json @dataclass class AggregateTriggerRequirements(TriggerRequirements): triggers: Iterable[TriggerRequirements] = field(default=None, metadata=field_metadata) aggregate_type: AggType = field(default=AggType.ALL_OF, metadata=field_metadata) class_type: str = static_field('aggregate_trigger_requirements') def __setattr__(self, key, value): if key == 'triggers': if all([isinstance(v, Trigger) for v in value]): value = tuple(v.trigger_requirements for v in value) super().__setattr__(key, value) def has_triggered(self, state: dt.date, backtest: BackTest = None) -> TriggerInfo: info_dict = {} if self.aggregate_type == AggType.ALL_OF: for trigger in self.triggers: t_info = trigger.has_triggered(state, backtest) if not t_info: return TriggerInfo(False) else: if t_info.info_dict: info_dict.update(t_info.info_dict) return TriggerInfo(True, info_dict) elif self.aggregate_type == AggType.ANY_OF: triggered = False for trigger in self.triggers: t_info = trigger.has_triggered(state, backtest) if t_info: triggered = True if t_info.info_dict: info_dict.update(t_info.info_dict) return TriggerInfo(True, info_dict) if triggered else TriggerInfo(False) else: raise RuntimeError(f'Unrecognised aggregation type: {self.aggregate_type}') @property def calc_type(self): seen_types = set() for trigger in self.triggers: seen_types.add(trigger.calc_type) if CalcType.path_dependent in seen_types: return CalcType.path_dependent elif CalcType.semi_path_dependent in seen_types: return CalcType.semi_path_dependent else: return CalcType.simple @dataclass_json @dataclass class NotTriggerRequirements(TriggerRequirements): trigger: TriggerRequirements = field(default=None, metadata=field_metadata) class_type: str = static_field('not_trigger_requirements') def __setattr__(self, key, value): if key == 'trigger': if isinstance(value, Trigger): value = value.trigger_requirements super().__setattr__(key, value) def has_triggered(self, state: dt.date, backtest: BackTest = None) -> TriggerInfo: t_info = self.trigger.has_triggered(state, backtest) if t_info: return TriggerInfo(False) else: return TriggerInfo(True) @dataclass_json @dataclass class DateTriggerRequirements(TriggerRequirements): dates: Iterable[Union[dt.datetime, dt.date]] = field( default=None, metadata=config(exclude=exclude_none, decoder=decode_iso_date_or_datetime) ) entire_day: bool = field(default=False, metadata=field_metadata) class_type: str = static_field('date_trigger_requirements') dates_from_datetimes = [] def __post_init__(self): self.dates_from_datetimes = ( [d.date() if isinstance(d, dt.datetime) else d for d in self.dates] if self.entire_day else None ) def has_triggered(self, state: Union[dt.date, dt.datetime], backtest: BackTest = None) -> TriggerInfo: if self.entire_day: dates = sorted(self.dates_from_datetimes) if isinstance(state, dt.datetime): state = state.date() else: dates = sorted(self.dates) if state in dates: next_state = None if dates.index(state) < len(dates) - 1: next_state = dates[dates.index(state) + 1] return TriggerInfo( True, { AddTradeAction: AddTradeActionInfo(scaling=None, next_schedule=next_state), AddScaledTradeAction: AddScaledTradeActionInfo(next_schedule=next_state), HedgeAction: HedgeActionInfo(next_schedule=next_state), }, ) return TriggerInfo(False) def get_trigger_times(self): return self.dates_from_datetimes or self.dates @dataclass_json @dataclass class PortfolioTriggerRequirements(TriggerRequirements): data_source: str = field(default=None, metadata=field_metadata) trigger_level: float = field(default=None, metadata=field_metadata) direction: TriggerDirection = field(default=None, metadata=field_metadata) class_type: str = static_field('portfolio_trigger_requirements') def has_triggered(self, state: dt.date, backtest: BackTest = None) -> TriggerInfo: if self.data_source == 'len': value = len(backtest.portfolio_dict) if self.direction == TriggerDirection.ABOVE: if value > self.trigger_level: return TriggerInfo(True) elif self.direction == TriggerDirection.BELOW: if value < self.trigger_level: return TriggerInfo(True) else: if value == self.trigger_level: return TriggerInfo(True) return TriggerInfo(False) @dataclass_json @dataclass class MeanReversionTriggerRequirements(TriggerRequirements): data_source: DataSource = field( default=None, metadata=config(decoder=dc_decode(*DataSource.sub_classes(), allow_missing=True)) ) z_score_bound: float = field(default=None, metadata=field_metadata) rolling_mean_window: int = field(default=None, metadata=field_metadata) rolling_std_window: int = field(default=None, metadata=field_metadata) current_position = 0 class_type: str = static_field('mean_reversion_trigger_requirements') def has_triggered(self, state: dt.date, backtest: BackTest = None) -> TriggerInfo: rolling_mean = self.data_source.get_data_range(state, self.rolling_mean_window).mean() rolling_std = self.data_source.get_data_range(state, self.rolling_std_window).std() current_price = self.data_source.get_data(state) if self.current_position == 0: if abs((current_price - rolling_mean) / rolling_std) > self.z_score_bound: if current_price > rolling_mean: self.current_position = -1 return TriggerInfo(True, {AddTradeAction: AddTradeActionInfo(scaling=-1)}) else: self.current_position = 1 return TriggerInfo(True, {AddTradeAction: AddTradeActionInfo(scaling=1)}) elif self.current_position == 1: if current_price > rolling_mean: self._current_position = 0 return TriggerInfo(True, {AddTradeAction: AddTradeActionInfo(scaling=-1)}) elif self.current_position == -1: if current_price > rolling_mean: self.current_position = 0 return TriggerInfo(True, {AddTradeAction: AddTradeActionInfo(scaling=1)}) else: raise RuntimeWarning(f'unexpected current position: {self.current_position}') return TriggerInfo(False) @dataclass_json @dataclass class TradeCountTriggerRequirements(TriggerRequirements): trade_count: float = field(default=None, metadata=field_metadata) direction: TriggerDirection = field(default=None, metadata=field_metadata) class_type: str = static_field('trade_count_requirements') def has_triggered(self, state: dt.date, backtest: BackTest = None) -> TriggerInfo: value = len(backtest.portfolio_dict.get(state, [])) if self.direction == TriggerDirection.ABOVE: if value > self.trade_count: return TriggerInfo(True) elif self.direction == TriggerDirection.BELOW: if value < self.trade_count: return TriggerInfo(True) else: if value == self.trade_count: return TriggerInfo(True) return TriggerInfo(False) @property def calc_type(self): return CalcType.path_dependent @dataclass_json @dataclass class EventTriggerRequirements(TriggerRequirements): event_name: str = field(default=None, metadata=field_metadata) offset_days: int = 0 data_source: DataSource = field( default=None, metadata=config(decoder=dc_decode(*DataSource.sub_classes(), allow_missing=True)) ) class_type: str = static_field('event_requirements') trigger_dates = [] def __post_init__(self): if self.data_source is None: self.data_source = GsDataSource(data_set='MACRO_EVENTS_CALENDAR', asset_id=None, value_header='eventName') def get_trigger_times(self) -> [dt.date]: if not self.trigger_dates: kwargs = {'eventName': self.event_name} self.trigger_dates = [ d.date() + dt.timedelta(days=self.offset_days) for d in self.data_source.get_data(None, **kwargs).index ] return self.trigger_dates def has_triggered(self, state: dt.date, backtest: BackTest = None) -> TriggerInfo: dates = sorted(self.trigger_dates) if state in dates: next_state = None if dates.index(state) < len(dates) - 1: next_state = dates[dates.index(state) + 1] return TriggerInfo( True, { AddTradeAction: AddTradeActionInfo(scaling=None, next_schedule=next_state), AddScaledTradeAction: AddScaledTradeActionInfo(next_schedule=next_state), HedgeAction: HedgeActionInfo(next_schedule=next_state), }, ) return TriggerInfo(False) @staticmethod def list_events(currency: str, start=Optional[dt.datetime], end=Optional[dt.datetime], **kwargs): kwargs['currency'] = currency dataset = Dataset('MACRO_EVENTS_CALENDAR') return dataset.get_data(start, end, **kwargs)['eventName'].unique() @dataclass_json @dataclass class Trigger: trigger_requirements: Optional[TriggerRequirements] = field(default=None, metadata=field_metadata) actions: Union[Action, Iterable[Action]] = field( default=None, metadata=config(decoder=dc_decode(*Action.sub_classes(), allow_missing=True)) ) __sub_classes: ClassVar[List[type]] = [] def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) Trigger.__sub_classes.append(cls) @staticmethod def sub_classes(): return tuple(Trigger.__sub_classes) def __post_init__(self): self.actions = make_list(self.actions) def has_triggered(self, state: dt.date, backtest: BackTest = None) -> TriggerInfo: """ implemented by sub classes :param state: :param backtest: :return: TriggerInfo containing a bool indication of whether the trigger has triggered and optionally info in the form of a dictionary of action type to object info understood by that action. """ return self.trigger_requirements.has_triggered(state, backtest) def get_trigger_times(self): return self.trigger_requirements.get_trigger_times() @property def calc_type(self): return self.trigger_requirements.calc_type @property def risks(self): return [x.risk for x in make_list(self.actions) if x.risk is not None] @dataclass_json @dataclass class PeriodicTrigger(Trigger): trigger_requirements: PeriodicTriggerRequirements = field(default=None, metadata=field_metadata) _trigger_dates = None class_type: str = static_field('periodic_trigger') @dataclass_json @dataclass class IntradayPeriodicTrigger(Trigger): trigger_requirements: IntradayTriggerRequirements = field(default=None, metadata=field_metadata) class_type: str = static_field('intraday_periodic_trigger') @dataclass_json @dataclass class MktTrigger(Trigger): trigger_requirements: MktTriggerRequirements = field(default=None, metadata=field_metadata) class_type: str = static_field('mkt_trigger') @dataclass_json @dataclass class StrategyRiskTrigger(Trigger): trigger_requirements: RiskTriggerRequirements = field(default=None, metadata=field_metadata) class_type: str = static_field('strategy_risk_trigger') @property def risks(self): return [x.risk for x in make_list(self.actions) if x.risk is not None] + [self.trigger_requirements.risk] @dataclass_json @dataclass class AggregateTrigger(Trigger): trigger_requirements: AggregateTriggerRequirements = field(default=None, metadata=field_metadata) class_type: str = static_field('aggregate_trigger') @dataclass_json @dataclass class NotTrigger(Trigger): trigger_requirements: NotTriggerRequirements = field(default=None, metadata=field_metadata) class_type: str = static_field('not_trigger') @dataclass_json @dataclass class DateTrigger(Trigger): trigger_requirements: DateTriggerRequirements = field(default=None, metadata=field_metadata) class_type: str = static_field('date_trigger') @dataclass_json @dataclass class PortfolioTrigger(Trigger): trigger_requirements: PortfolioTriggerRequirements = field(default=None, metadata=field_metadata) class_type: str = static_field('portfolio_trigger') @dataclass_json @dataclass class MeanReversionTrigger(Trigger): trigger_requirements: MeanReversionTriggerRequirements = field(default=None, metadata=field_metadata) class_type: str = static_field('mean_reversion_trigger') @dataclass_json @dataclass class TradeCountTrigger(Trigger): trigger_requirements: TradeCountTriggerRequirements = field(default=None, metadata=field_metadata) class_type: str = static_field('trade_count_trigger') @dataclass_json @dataclass class EventTrigger(Trigger): trigger_requirements: EventTriggerRequirements = field(default=None, metadata=field_metadata) class_type: str = static_field('event_trigger') @dataclass_json @dataclass class OrdersGeneratorTrigger(Trigger): """Base class for triggers used with the PredefinedAssetEngine.""" def __post_init__(self): if not self.actions: self.actions = [Action()] super().__post_init__() def get_trigger_times(self) -> list: """ Returns the set of times when orders can be generated e.g. every 30 min :return: list """ raise RuntimeError('get_trigger_times must be implemented by subclass') def generate_orders(self, state: dt.datetime, backtest: PredefinedAssetBacktest = None) -> list: """ Returns the orders generated at state :param state: the time when orders are generated :param backtest: the backtest, used to access the holdings and orders generated so far :return: list """ raise RuntimeError('generate_orders must be implemented by subclass') def has_triggered(self, state: dt.datetime, backtest: PredefinedAssetBacktest = None) -> TriggerInfo: """ Calls generate_orders if state is among the trigger times :param state: the time of the trigger :param backtest: the backtest, used to access the holdings and orders generated so far :return: list """ if state.time() not in self.get_trigger_times(): return TriggerInfo(False) else: orders = self.generate_orders(state, backtest) return TriggerInfo(True, {type(a): orders for a in self.actions}) if len(orders) else TriggerInfo(False) # These special trigger requirements have a special dependency on other trigger types triggers_field = AggregateTriggerRequirements.__dataclass_fields__['triggers'] triggers_field.metadata = config(decoder=dc_decode(*TriggerRequirements.sub_classes(), allow_missing=True)) trigger_field = NotTriggerRequirements.__dataclass_fields__['trigger'] trigger_field.metadata = config(decoder=dc_decode(*TriggerRequirements.sub_classes(), allow_missing=True)) ================================================ FILE: gs_quant/base.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import builtins import copy import datetime as dt import logging import sys import typing from abc import ABC, ABCMeta, abstractmethod from collections import namedtuple from dataclasses import Field, InitVar, MISSING, dataclass, field, fields, replace from enum import EnumMeta, Enum from functools import update_wrapper from typing import Iterable, Mapping, Optional, Union, Tuple import numpy as np from dataclasses_json import config, global_config, LetterCase, dataclass_json from dataclasses_json.core import _decode_generic, _is_supported_generic from inflection import camelize, underscore from gs_quant.context_base import ContextBase, ContextMeta from gs_quant.json_convertors import ( encode_date_or_str, decode_date_or_str, decode_optional_date, encode_datetime, decode_datetime, decode_float_or_str, decode_instrument, encode_dictable, decode_quote_report, decode_quote_reports, decode_custom_comment, decode_custom_comments, decode_optional_time, encode_optional_time, ) _logger = logging.getLogger(__name__) __builtins = set(dir(builtins)) __getattribute__ = object.__getattribute__ __setattr__ = object.__setattr__ _rename_cache = {} _is_supported_generic_cache = {} def exclude_none(o): return o is None def exclude_always(_o): return True def is_iterable(o, t): return isinstance(o, Iterable) and all(isinstance(it, t) for it in o) def is_instance_or_iterable(o, t): return isinstance(o, t) or is_iterable(o, t) def _get_underscore(arg): if arg not in _rename_cache: _rename_cache[arg] = underscore(arg) return _rename_cache[arg] def _get_is_supported_generic(arg): if arg in _is_supported_generic_cache: is_supported_generic = _is_supported_generic_cache[arg] else: is_supported_generic = _is_supported_generic(arg) _is_supported_generic_cache[arg] = is_supported_generic return is_supported_generic def handle_camel_case_args(cls): init = cls.__init__ def wrapper(self, *args, **kwargs): normalised_kwargs = {} for arg, value in kwargs.items(): if not arg.isupper(): snake_case_arg = _get_underscore(arg) if snake_case_arg != arg and snake_case_arg in kwargs: raise ValueError('{} and {} both specified'.format(arg, snake_case_arg)) arg = snake_case_arg arg = cls._field_mappings().get(arg, arg) normalised_kwargs[arg] = value return init(self, *args, **normalised_kwargs) cls.__init__ = update_wrapper(wrapper=wrapper, wrapped=init) return cls def static_field(val): return field(init=False, default=val) field_metadata = config(exclude=exclude_none) name_metadata = config(exclude=exclude_always) class RiskKey(namedtuple('RiskKey', ('provider', 'date', 'market', 'params', 'scenario', 'risk_measure'))): @property def ex_measure(self): from gs_quant.target.common import RiskRequestParameters # noqa return RiskKey( self.provider, self.date, self.market, RiskRequestParameters(self.params.csa_term, self.params.raw_results, False, self.params.market_behaviour), self.scenario, None, ) @property def ex_historical_diddle(self): from gs_quant.target.common import RiskRequestParameters # noqa return RiskKey( self.provider, self.date, self.market, RiskRequestParameters(self.params.csa_term, self.params.raw_results, False, self.params.market_behaviour), self.scenario, self.risk_measure, ) @property def fields(self): return self._fields class EnumBase: @classmethod def _missing_(cls: EnumMeta, key): if not isinstance(key, str): key = str(key) return next((m for m in cls.__members__.values() if m.value.lower() == key.lower()), None) def __reduce_ex__(self, protocol): return self.__class__, (self.value,) def __lt__(self: EnumMeta, other): return self.value < other.value def __repr__(self): return str(self) def __str__(self): return self.value class HashableDict(dict): @staticmethod def hashables(in_dict) -> Tuple: hashables = [] for it in in_dict.items(): if isinstance(it[1], dict): hashables.append((it[0], HashableDict.hashables(it[1]))) else: hashables.append(it) return tuple(hashables) def __hash__(self): return hash(HashableDict.hashables(self)) class DictBase(HashableDict): _PROPERTIES = set() def __init__(self, *args, **kwargs): if self._PROPERTIES: invalid_arg = next((k for k in kwargs.keys() if k not in self._PROPERTIES), None) if invalid_arg is not None: raise AttributeError(f"'{self.__class__.__name__}' has no attribute '{invalid_arg}'") super().__init__( *args, **{camelize(k, uppercase_first_letter=False): v for k, v in kwargs.items() if v is not None} ) def __getitem__(self, item): return super().__getitem__(camelize(item, uppercase_first_letter=False)) def __setitem__(self, key, value): if value is not None: return super().__setitem__(camelize(key, uppercase_first_letter=False), value) def __getattr__(self, item): if self._PROPERTIES: if _get_underscore(item) in self._PROPERTIES: return self.get(item) elif item in self: return self[item] raise AttributeError(f"'{self.__class__.__name__}' has no attribute '{item}'") def __setattr__(self, key, value): if key in dir(self): return super().__setattr__(key, value) elif self._PROPERTIES and _get_underscore(key) not in self._PROPERTIES: raise AttributeError(f"'{self.__class__.__name__}' has no attribute '{key}'") self[key] = value @classmethod def properties(cls) -> set: return cls._PROPERTIES class Base(ABC): """The base class for all generated classes""" __fields_by_name = None __field_mappings = None def __getattr__(self, item): fields_by_name = __getattribute__(self, '_fields_by_name')() if item.startswith('_') or item in fields_by_name: return __getattribute__(self, item) # Handle setting via camelCase names (legacy behaviour) and field mappings from disallowed names snake_case_item = _get_underscore(item) field_mappings = __getattribute__(self, '_field_mappings')() snake_case_item = field_mappings.get(snake_case_item, snake_case_item) try: return __getattribute__(self, snake_case_item) except AttributeError: return __getattribute__(self, item) def __setattr__(self, key, value): # Handle setting via camelCase names (legacy behaviour) snake_case_key = _get_underscore(key) snake_case_key = self._field_mappings().get(snake_case_key, snake_case_key) fld = self._fields_by_name().get(snake_case_key) if fld: if not fld.init: raise ValueError(f'{key} cannot be set') key = snake_case_key value = self.__coerce_value(fld.type, value) __setattr__(self, key, value) def __repr__(self): if self.name is not None: return f'{self.name} ({self.__class__.__name__})' return super().__repr__() @classmethod def __is_type_match(cls, tp, val): if sys.version_info >= (3, 9): from types import GenericAlias is_generic_alias = isinstance(tp, (typing._GenericAlias, GenericAlias)) if sys.version_info >= (3, 10) and not is_generic_alias: from types import UnionType if isinstance(tp, UnionType): return any(cls.__is_type_match(arg, val) for arg in tp.__args__) else: is_generic_alias = isinstance(tp, typing._GenericAlias) if not is_generic_alias: # Do not convert Enums to strings is_enum_to_str = isinstance(val, Enum) and tp is str return isinstance(tp, type) and (isinstance(val, tp) or is_enum_to_str) if getattr(tp, '_special', False): return False origin = tp.__origin__ args = tp.__args__ if float in args: args += (int,) if origin == Union: return any(cls.__is_type_match(arg, val) for arg in args) if origin is tuple: if not isinstance(val, tuple) or not args: return False if len(args) == 1 or args[1] == Ellipsis: return all(cls.__is_type_match(args[0], x) for x in val) else: return len(args) == len(val) and all(cls.__is_type_match(arg, x) for arg, x in zip(args, val)) return False @classmethod def __coerce_value(cls, typ: type, value): if cls.__is_type_match(typ, value): return value if isinstance(value, np.generic): # Handle numpy types return value.item() elif hasattr(value, 'tolist'): # tolist converts scalar or array to native python type if not already native. return value.tolist() elif typ in (DictBase, Optional[DictBase]) and isinstance(value, Base): return value.to_dict() is_supported_generic = _get_is_supported_generic(typ) if is_supported_generic: return _decode_generic(typ, value, False) else: return value @classmethod def _fields_by_name(cls) -> Mapping[str, Field]: if cls is Base: return {} if cls.__fields_by_name is None: cls.__fields_by_name = {f.name: f for f in fields(cls)} return cls.__fields_by_name @classmethod def _field_mappings(cls) -> Mapping[str, str]: if cls is Base: return {} if cls.__field_mappings is None: field_mappings = {} for fld in fields(cls): config_fn = fld.metadata.get('dataclasses_json', {}).get('letter_case') if config_fn: mapped_name = config_fn('field_name') if mapped_name: field_mappings[mapped_name] = fld.name cls.__field_mappings = field_mappings return cls.__field_mappings def clone(self, **kwargs): """ Clone this object, overriding specified values :param kwargs: property names and values, e.g. swap.clone(fixed_rate=0.01) **Examples** To change the market data location of the default context: >>> from gs_quant.instrument import IRCap >>> cap = IRCap('5y', 'GBP') >>> >>> new_cap = cap.clone(cap_rate=0.01) """ return replace(self, **kwargs) @classmethod def properties(cls) -> set: """The public property names of this class""" return set(f[:-1] if f[-1] == '_' else f for f in cls._fields_by_name().keys()) @classmethod def properties_init(cls) -> set: """The public property names of this class""" return set(f[:-1] if f[-1] == '_' else f for f, v in cls._fields_by_name().items() if v.init) def as_dict(self, as_camel_case: bool = False) -> dict: """Dictionary of the public, non-null properties and values""" # to_dict() converts all the values to JSON type, does camel case and name mappings # asdict() does not convert values or case of the keys or do name mappings ret = {} field_mappings = {v: k for k, v in self._field_mappings().items()} for key in self.__fields_by_name.keys(): value = __getattribute__(self, key) key = field_mappings.get(key, key) if value is not None: if as_camel_case: key = camelize(key, uppercase_first_letter=False) ret[key] = value return ret @classmethod def default_instance(cls): """ Construct a default instance of this type """ required = {f.name: None if f.default == MISSING else f.default for f in fields(cls) if f.init} return cls(**required) def from_instance(self, instance): """ Copy the values from an existing instance of the same type to our self :param instance: from which to copy: :return: """ if not isinstance(instance, type(self)): raise ValueError('Can only use from_instance with an object of the same type') for fld in fields(self.__class__): if fld.init: __setattr__(self, fld.name, __getattribute__(instance, fld.name)) @dataclass_json @dataclass class Priceable(Base): def resolve(self, in_place: bool = True): """ Resolve non-supplied properties of an instrument **Examples** >>> from gs_quant.instrument import IRSwap >>> >>> swap = IRSwap('Pay', '10y', 'USD') >>> rate = swap.fixedRate rate is None >>> swap.resolve() >>> rate = swap.fixedRate rates is now the solved fixed rate """ raise NotImplementedError def dollar_price(self): """ Present value in USD :return: a float or a future, depending on whether the current PricingContext is async, or has been entered **Examples** >>> from gs_quant.instrument import IRCap >>> >>> cap = IRCap('1y', 'EUR') >>> price = cap.dollar_price() price is the present value in USD (a float) >>> cap_usd = IRCap('1y', 'USD') >>> cap_eur = IRCap('1y', 'EUR') >>> >>> from gs_quant.markets import PricingContext >>> >>> with PricingContext(): >>> price_usd_f = cap_usd.dollar_price() >>> price_eur_f = cap_eur.dollar_price() >>> >>> price_usd = price_usd_f.result() >>> price_eur = price_eur_f.result() price_usd_f and price_eur_f are futures, price_usd and price_eur are floats """ raise NotImplementedError def price(self): """ Present value in local currency. Note that this is not yet supported on all instruments ***Examples** >>> from gs_quant.instrument import IRSwap >>> >>> swap = IRSwap('Pay', '10y', 'EUR') >>> price = swap.price() price is the present value in EUR (a float) """ raise NotImplementedError def calc(self, risk_measure, fn=None): """ Calculate the value of the risk_measure :param risk_measure: the risk measure to compute, e.g. IRDelta (from gs_quant.risk) :param fn: a function for post-processing results :return: a float or dataframe, depending on whether the value is scalar or structured, or a future thereof (depending on how PricingContext is being used) **Examples** >>> from gs_quant.instrument import IRCap >>> from gs_quant.risk import IRDelta >>> >>> cap = IRCap('1y', 'USD') >>> delta = cap.calc(IRDelta) delta is a dataframe >>> from gs_quant.instrument import EqOption >>> from gs_quant.risk import EqDelta >>> >>> option = EqOption('.SPX', '3m', 'ATMF', 'Call', 'European') >>> delta = option.calc(EqDelta) delta is a float >>> from gs_quant.markets import PricingContext >>> >>> cap_usd = IRCap('1y', 'USD') >>> cap_eur = IRCap('1y', 'EUR') >>> with PricingContext(): >>> usd_delta_f = cap_usd.calc(IRDelta) >>> eur_delta_f = cap_eur.calc(IRDelta) >>> >>> usd_delta = usd_delta_f.result() >>> eur_delta = eur_delta_f.result() usd_delta_f and eur_delta_f are futures, usd_delta and eur_delta are dataframes """ raise NotImplementedError class __ScenarioMeta(ABCMeta, ContextMeta): pass @dataclass class Scenario(Base, ContextBase, ABC, metaclass=__ScenarioMeta): def __lt__(self, other): if self.__repr__ != other.__repr__: return self.name < other.name return False def __repr__(self): if self.name: return self.name else: params = self.as_dict() sorted_keys = sorted(params.keys(), key=lambda x: x.lower()) params = ', '.join( [f'{k}:{params[k].__repr__ if isinstance(params[k], Base) else params[k]}' for k in sorted_keys] ) return self.scenario_type + '(' + params + ')' @dataclass class RiskMeasureParameter(Base, ABC): def __repr__(self): params = self.as_dict() params.pop("parameter_type", None) sorted_keys = sorted(params.keys(), key=lambda x: x.lower()) params = ", ".join( [f"{k}:{params[k].value if isinstance(params[k], EnumBase) else params[k]}" for k in sorted_keys] ) return f"{self.parameter_type}({params})" @dataclass class InstrumentBase(Base, ABC): quantity_: InitVar[float] = field(default=1, init=False) @property @abstractmethod def provider(self): ... @property def instrument_quantity(self) -> float: return self.quantity_ @property def resolution_key(self) -> Optional[RiskKey]: try: return self.__resolution_key except AttributeError: return None @property def unresolved(self): try: return self.__unresolved except AttributeError: return None @property def metadata(self): try: return self.__metadata except AttributeError: return None @metadata.setter def metadata(self, value): self.__metadata = value def from_instance(self, instance): self.__resolution_key = None super().from_instance(instance) self.__unresolved = instance.__unresolved self.__resolution_key = instance.__resolution_key def resolved(self, values: dict, resolution_key: RiskKey): all_values = self.as_dict(True) all_values.update(values) new_instrument = self.from_dict(all_values) new_instrument.name = self.name new_instrument.__unresolved = copy.copy(self) new_instrument.__resolution_key = resolution_key return new_instrument def clone(self, **kwargs): new_instrument = super().clone(**kwargs) new_instrument.__unresolved = self.unresolved new_instrument.metadata = self.metadata new_instrument.__resolution_key = self.resolution_key return new_instrument @dataclass class Market(ABC): def __hash__(self): return hash(self.market or self.location) def __eq__(self, other): return (self.market or self.location) == (other.market or other.location) def __lt__(self, other): return repr(self) < repr(other) @property @abstractmethod def market(self): ... @property @abstractmethod def location(self): ... def to_dict(self): return self.market.to_dict() class Sentinel: def __init__(self, name: str): self.__name = name def __eq__(self, other): return self.__name == other.__name @dataclass class QuoteReport(Base, ABC): pass @dataclass class CustomComments(Base, ABC): pass def get_enum_value(enum_type: EnumMeta, value: Union[EnumBase, str]): if value in (None,): return None if isinstance(value, enum_type): return value try: enum_value = enum_type(value) except ValueError: _logger.warning('Setting value to {}, which is not a valid entry in {}'.format(value, enum_type)) enum_value = value return enum_value @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MarketDataScenario(Base): scenario: Scenario = field(default=None, metadata=field_metadata) subtract_base: Optional[bool] = field(default=False, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) # Yes, I know this is a little evil ... global_config.encoders[dt.date] = dt.date.isoformat global_config.encoders[Optional[dt.date]] = encode_date_or_str global_config.decoders[dt.date] = decode_optional_date global_config.decoders[Optional[dt.date]] = decode_optional_date global_config.encoders[Union[dt.date, str]] = encode_date_or_str global_config.encoders[Optional[Union[dt.date, str]]] = encode_date_or_str global_config.decoders[Union[dt.date, str]] = decode_date_or_str global_config.decoders[Optional[Union[dt.date, str]]] = decode_date_or_str global_config.encoders[dt.time] = dt.time.isoformat global_config.decoders[dt.time] = dt.time.fromisoformat global_config.encoders[Optional[dt.time]] = encode_optional_time global_config.decoders[Optional[dt.time]] = decode_optional_time global_config.encoders[dt.datetime] = encode_datetime global_config.encoders[Optional[dt.datetime]] = encode_datetime global_config.decoders[dt.datetime] = decode_datetime global_config.decoders[Optional[dt.datetime]] = decode_datetime global_config.decoders[Union[float, str]] = decode_float_or_str global_config.decoders[Optional[Union[float, str]]] = decode_float_or_str global_config.decoders[InstrumentBase] = decode_instrument global_config.decoders[Optional[InstrumentBase]] = decode_instrument global_config.decoders[QuoteReport] = decode_quote_report global_config.decoders[Optional[Tuple[QuoteReport, ...]]] = decode_quote_reports global_config.decoders[CustomComments] = decode_custom_comment global_config.decoders[Optional[Tuple[CustomComments, ...]]] = decode_custom_comments global_config.encoders[Market] = encode_dictable global_config.encoders[Optional[Market]] = encode_dictable ================================================ FILE: gs_quant/common.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.target.common import * # noqa from gs_quant.target.common import PayReceive as _PayReceive # noqa from gs_quant.target.common import RiskMeasure as __RiskMeasure # noqa from gs_quant.target.common import RiskMeasureType, AssetClass, RiskMeasureUnit # noqa from gs_quant.base import EnumBase, RiskMeasureParameter # noqa from enum import Enum from typing import Union import datetime as dt class PositionType(Enum): """Position type enumeration Enumeration of different position types for a portfolio or index """ OPEN = "open" #: Open positions (corporate action adjusted) CLOSE = "close" #: Close positions (reflect trading activity on the close) ANY = 'any' class DateLimit(Enum): """Datetime date constants""" LOW_LIMIT = dt.date(1952, 1, 1) class PayReceive(EnumBase, Enum): """Pay or receive fixed""" Pay = 'Pay' Receive = 'Rec' Straddle = 'Straddle' @classmethod def _missing_(cls, key): if isinstance(key, _PayReceive): key = key.value return cls.Receive if key.lower() in ('receive', 'receiver') else super()._missing_(key) class RiskMeasure(__RiskMeasure): def __lt__(self, other): if self.name != other.name: return self.name < other.name elif self.parameters is not None: if other.parameters is None: return False if not isinstance(other.parameters, type(self.parameters)): return self.parameters.parameter_type < other.parameters.parameter_type else: return repr(self.parameters) < repr(other.parameters) elif other.parameters is not None: return True return False def __repr__(self): return self.name or self.measure_type.name @property def pricing_context(self): from gs_quant.markets import PricingContext return PricingContext.current class ParameterisedRiskMeasure(RiskMeasure): def __init__( self, asset_class: Union[AssetClass, str] = None, measure_type: Union[RiskMeasureType, str] = None, unit: Union[RiskMeasureUnit, str] = None, value: Union[float, str] = None, parameters: RiskMeasureParameter = None, name: str = None, ): super().__init__(asset_class=asset_class, measure_type=measure_type, unit=unit, value=value, name=name) if parameters: if isinstance(parameters, RiskMeasureParameter): self.parameters = parameters else: raise TypeError(f"Unsupported parameter {parameters}") def __repr__(self): name = self.name or self.measure_type.name params = None if self.parameters: params = self.parameters.as_dict() params.pop('parameter_type', None) sorted_keys = sorted(params.keys(), key=lambda x: x.lower()) params = ', '.join( [f'{k}:{params[k].value if isinstance(params[k], EnumBase) else params[k]}' for k in sorted_keys] ) return name + '(' + params + ')' if params else name def parameter_is_empty(self): return self.parameters is None ================================================ FILE: gs_quant/config/__init__.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from .options import * ================================================ FILE: gs_quant/config/options.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ class DisplayOptions: """ config options """ def __init__(self, show_na: bool = False): self.__show_na = show_na @property def show_na(self): return self.__show_na @show_na.setter def show_na(self, show_na): self.__show_na = show_na display_options = DisplayOptions() ================================================ FILE: gs_quant/config.ini ================================================ [DEFAULT] AppDomain = https://api.gs.com MdsDomainEast = https://us-east.data.gsapis.com MdsWebDomain = https://data.gs.com AuthURL = https://idfs.gs.com/as/token.oauth2 MarqueeWebDomain = https://marquee.gs.com [QA] AppDomain = https://api.marquee-qa.gs.com MdsDomainEast = https://qa.us-east.data.gsapis.com MdsWebDomain = https://data-qa.gs.com AuthURL = https://idfs-qa.gs.com/as/token.oauth2 MarqueeWebDomain = https://marquee-qa.gs.com [DEV] AppDomain = https://api.marquee-dev-ext.web.gs.com MdsDomainEast = https://dev.us-east.data.gsapis.com MdsWebDomain = https://data-dev.gs.com AuthURL = https://idfs-dev.nimbus.gs.com:9031/as/token.oauth2 MarqueeWebDomain = https://marquee-dev.web.gs.com [PROD] AppDomain = https://api.gs.com MdsDomainEast = https://us-east.data.gsapis.com MdsWebDomain = https://data.gs.com AuthURL = https://idfs.gs.com/as/token.oauth2 MarqueeWebDomain = https://marquee.gs.com ================================================ FILE: gs_quant/content/Contents.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "d1e67001", "metadata": {}, "source": [ "# Contents" ] }, { "cell_type": "markdown", "id": "d1e67002", "metadata": {}, "source": [ "## 📂 events" ] }, { "cell_type": "markdown", "id": "d1e67003", "metadata": {}, "source": [ "- 📁 **00_gsquant_meets_markets**\n", " - 📁 00_us_election_analysis\n", " - 📄 [0000_macro_big_picture.ipynb](events/00_gsquant_meets_markets/00_us_election_analysis/0000_macro_big_picture.ipynb)\n", " - 📄 [0001_portfolios_and_var.ipynb](events/00_gsquant_meets_markets/00_us_election_analysis/0001_portfolios_and_var.ipynb)\n", " - 📄 [0002_past_elections.ipynb](events/00_gsquant_meets_markets/00_us_election_analysis/0002_past_elections.ipynb)\n", " - 📄 [0003_trades.ipynb](events/00_gsquant_meets_markets/00_us_election_analysis/0003_trades.ipynb)\n", " - 📁 01_ideas_for_risk_re_rating\n", " - 📄 [eq_sx5e_spx_vol_spread_trade.ipynb](events/00_gsquant_meets_markets/01_ideas_for_risk_re_rating/eq_sx5e_spx_vol_spread_trade.ipynb)\n", " - 📄 [fx_covid19_recovery_trade.ipynb](events/00_gsquant_meets_markets/01_ideas_for_risk_re_rating/fx_covid19_recovery_trade.ipynb)\n", " - 📄 [inflation_covid19_recovery_trade.ipynb](events/00_gsquant_meets_markets/01_ideas_for_risk_re_rating/inflation_covid19_recovery_trade.ipynb)\n", " - 📁 02_optimizing_equity_trading\n", " - 📄 [quants_meet_markets_qes.ipynb](events/00_gsquant_meets_markets/02_optimizing_equity_trading/quants_meet_markets_qes.ipynb)\n", " - 📄 [section_a_liquidity_and_clusters.ipynb](events/00_gsquant_meets_markets/02_optimizing_equity_trading/section_a_liquidity_and_clusters.ipynb)\n", " - 📄 [section_b_ownership.ipynb](events/00_gsquant_meets_markets/02_optimizing_equity_trading/section_b_ownership.ipynb)\n", " - 📄 [section_c_apex.ipynb](events/00_gsquant_meets_markets/02_optimizing_equity_trading/section_c_apex.ipynb)\n", " - 📁 03_esg_basket_portfolio_optimisation\n", " - 📄 [quants_meet_markets_msci.ipynb](events/00_gsquant_meets_markets/03_esg_basket_portfolio_optimisation/quants_meet_markets_msci.ipynb)\n" ] }, { "cell_type": "markdown", "id": "d1e67004", "metadata": {}, "source": [ "## 📂 made_with_gs_quant" ] }, { "cell_type": "markdown", "id": "d1e67005", "metadata": {}, "source": [ "- 📄 [1-Navigating Rates.ipynb](made_with_gs_quant/1-Navigating%20Rates.ipynb)\n", "- 📄 [10-Explaining Performance Drivers.ipynb](made_with_gs_quant/10-Explaining%20Performance%20Drivers.ipynb)\n", "- 📄 [11-FX Election Hedge.ipynb](made_with_gs_quant/11-FX%20Election%20Hedge.ipynb)\n", "- 📄 [12-Structuring for Uncertaintly.ipynb](made_with_gs_quant/12-Structuring%20for%20Uncertaintly.ipynb)\n", "- 📄 [13-Cyclicals.ipynb](made_with_gs_quant/13-Cyclicals.ipynb)\n", "- 📄 [14-Curve Inversions.ipynb](made_with_gs_quant/14-Curve%20Inversions.ipynb)\n", "- 📄 [15-Hypothetical Lifetime Risk Calculator.ipynb](made_with_gs_quant/15-Hypothetical%20Lifetime%20Risk%20Calculator.ipynb)\n", "- 📄 [2-Levels and Tails.ipynb](made_with_gs_quant/2-Levels%20and%20Tails.ipynb)\n", "- 📄 [3-Systematic Selling.ipynb](made_with_gs_quant/3-Systematic%20Selling.ipynb)\n", "- 📄 [4-Delta Hedging.ipynb](made_with_gs_quant/4-Delta%20Hedging.ipynb)\n", "- 📄 [5-What's New.ipynb](made_with_gs_quant/5-What's%20New.ipynb)\n", "- 📄 [6-Evolving Correlations.ipynb](made_with_gs_quant/6-Evolving%20Correlations.ipynb)\n", "- 📄 [7-Predicting Performance and Live Risk.ipynb](made_with_gs_quant/7-Predicting%20Performance%20and%20Live%20Risk.ipynb)\n", "- 📄 [8-What's New Internal.ipynb](made_with_gs_quant/8-What's%20New%20Internal.ipynb)\n", "- 📄 [9-Hedging using Machine Learning Techniques.ipynb](made_with_gs_quant/9-Hedging%20using%20Machine%20Learning%20Techniques.ipynb)\n" ] }, { "cell_type": "markdown", "id": "d1e67006", "metadata": {}, "source": [ "## 📂 reports_and_screens" ] }, { "cell_type": "markdown", "id": "d1e67007", "metadata": {}, "source": [ "- 📁 **00_fx**\n", " - 📄 [0000_vol_screen.ipynb](reports_and_screens/00_fx/0000_vol_screen.ipynb)\n", " - 📄 [0001_vol_calendar_screen.ipynb](reports_and_screens/00_fx/0001_vol_calendar_screen.ipynb)\n", " - 📄 [0002_forward_vol_screen.ipynb](reports_and_screens/00_fx/0002_forward_vol_screen.ipynb)\n", " - 📄 [0003_10x_binary_screen.ipynb](reports_and_screens/00_fx/0003_10x_binary_screen.ipynb)\n", " - 📄 [0004_bear_put_spread_screen.ipynb](reports_and_screens/00_fx/0004_bear_put_spread_screen.ipynb)\n", " - 📄 [0005_vol_skew.ipynb](reports_and_screens/00_fx/0005_vol_skew.ipynb)\n", " - 📄 [0006_vol_swap_spread.ipynb](reports_and_screens/00_fx/0006_vol_swap_spread.ipynb)\n", " - 📄 [0007_eq_fx_hedges.ipynb](reports_and_screens/00_fx/0007_eq_fx_hedges.ipynb)\n", "- 📁 **02_rates**\n", " - 📄 [0000_vol_fixed_strike_grid.ipynb](reports_and_screens/02_rates/0000_vol_fixed_strike_grid.ipynb)\n", " - 📄 [0001_vol_moneyness_screen.ipynb](reports_and_screens/02_rates/0001_vol_moneyness_screen.ipynb)\n", " - 📄 [0002_swaption_carry_grid.ipynb](reports_and_screens/02_rates/0002_swaption_carry_grid.ipynb)\n", " - 📄 [0003_spot_vol_shock.ipynb](reports_and_screens/02_rates/0003_spot_vol_shock.ipynb)\n", "- 📁 **03_prime**\n", " - 📄 [0001_gs_pb_data_case_study_sector_positioning.ipynb](reports_and_screens/03_prime/0001_gs_pb_data_case_study_sector_positioning.ipynb)\n", " - 📄 [0002_gs_pb_data_case_study_flows.ipynb](reports_and_screens/03_prime/0002_gs_pb_data_case_study_flows.ipynb)\n", " - 📄 [0003_gs_pb_data_case_study_factors.ipynb](reports_and_screens/03_prime/0003_gs_pb_data_case_study_factors.ipynb)\n", " - 📄 [0004_gs_pb_data_case_study_country_positioning.ipynb](reports_and_screens/03_prime/0004_gs_pb_data_case_study_country_positioning.ipynb)\n", " - 📄 [0005_gs_pb_data_case_study_leverage.ipynb](reports_and_screens/03_prime/0005_gs_pb_data_case_study_leverage.ipynb)\n", "- 📄 [README.md](reports_and_screens/README.md)\n" ] } ], "metadata": {}, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/content/Index.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "06d212c0", "metadata": {}, "source": [ "# Index" ] }, { "cell_type": "markdown", "id": "8bdc7267", "metadata": {}, "source": [] } ], "metadata": {}, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/content/README.md ================================================ # Content Overview Content and research produced using gs_quant - featuring tactical case studies looking at screens, backtesting strategies, attribution analytics and more. - 📄 [Contents.ipynb](Contents.ipynb) - 📄 [Index.ipynb](Index.ipynb) - 📄 [README.md](README.md) - 📁 **events** - 📁 00_gsquant_meets_markets - 📁 00_us_election_analysis - 📄 [0000_macro_big_picture.ipynb](events/00_gsquant_meets_markets/00_us_election_analysis/0000_macro_big_picture.ipynb) - 📄 [0001_portfolios_and_var.ipynb](events/00_gsquant_meets_markets/00_us_election_analysis/0001_portfolios_and_var.ipynb) - 📄 [0002_past_elections.ipynb](events/00_gsquant_meets_markets/00_us_election_analysis/0002_past_elections.ipynb) - 📄 [0003_trades.ipynb](events/00_gsquant_meets_markets/00_us_election_analysis/0003_trades.ipynb) - 📁 01_ideas_for_risk_re_rating - 📄 [eq_sx5e_spx_vol_spread_trade.ipynb](events/00_gsquant_meets_markets/01_ideas_for_risk_re_rating/eq_sx5e_spx_vol_spread_trade.ipynb) - 📄 [fx_covid19_recovery_trade.ipynb](events/00_gsquant_meets_markets/01_ideas_for_risk_re_rating/fx_covid19_recovery_trade.ipynb) - 📄 [inflation_covid19_recovery_trade.ipynb](events/00_gsquant_meets_markets/01_ideas_for_risk_re_rating/inflation_covid19_recovery_trade.ipynb) - 📁 02_optimizing_equity_trading - 📄 [quants_meet_markets_qes.ipynb](events/00_gsquant_meets_markets/02_optimizing_equity_trading/quants_meet_markets_qes.ipynb) - 📄 [section_a_liquidity_and_clusters.ipynb](events/00_gsquant_meets_markets/02_optimizing_equity_trading/section_a_liquidity_and_clusters.ipynb) - 📄 [section_b_ownership.ipynb](events/00_gsquant_meets_markets/02_optimizing_equity_trading/section_b_ownership.ipynb) - 📄 [section_c_apex.ipynb](events/00_gsquant_meets_markets/02_optimizing_equity_trading/section_c_apex.ipynb) - 📁 03_esg_basket_portfolio_optimisation - 📄 [quants_meet_markets_msci.ipynb](events/00_gsquant_meets_markets/03_esg_basket_portfolio_optimisation/quants_meet_markets_msci.ipynb) - 📁 **made_with_gs_quant** - 📄 [1-Navigating Rates.ipynb](made_with_gs_quant/1-Navigating%20Rates.ipynb) - 📄 [10-Explaining Performance Drivers.ipynb](made_with_gs_quant/10-Explaining%20Performance%20Drivers.ipynb) - 📄 [11-FX Election Hedge.ipynb](made_with_gs_quant/11-FX%20Election%20Hedge.ipynb) - 📄 [12-Structuring for Uncertaintly.ipynb](made_with_gs_quant/12-Structuring%20for%20Uncertaintly.ipynb) - 📄 [13-Cyclicals.ipynb](made_with_gs_quant/13-Cyclicals.ipynb) - 📄 [14-Curve Inversions.ipynb](made_with_gs_quant/14-Curve%20Inversions.ipynb) - 📄 [15-Hypothetical Lifetime Risk Calculator.ipynb](made_with_gs_quant/15-Hypothetical%20Lifetime%20Risk%20Calculator.ipynb) - 📄 [2-Levels and Tails.ipynb](made_with_gs_quant/2-Levels%20and%20Tails.ipynb) - 📄 [3-Systematic Selling.ipynb](made_with_gs_quant/3-Systematic%20Selling.ipynb) - 📄 [4-Delta Hedging.ipynb](made_with_gs_quant/4-Delta%20Hedging.ipynb) - 📄 [5-What's New.ipynb](made_with_gs_quant/5-What's%20New.ipynb) - 📄 [6-Evolving Correlations.ipynb](made_with_gs_quant/6-Evolving%20Correlations.ipynb) - 📄 [7-Predicting Performance and Live Risk.ipynb](made_with_gs_quant/7-Predicting%20Performance%20and%20Live%20Risk.ipynb) - 📄 [8-What's New Internal.ipynb](made_with_gs_quant/8-What's%20New%20Internal.ipynb) - 📄 [9-Hedging using Machine Learning Techniques.ipynb](made_with_gs_quant/9-Hedging%20using%20Machine%20Learning%20Techniques.ipynb) - 📁 **reports_and_screens** - 📁 00_fx - 📄 [0000_vol_screen.ipynb](reports_and_screens/00_fx/0000_vol_screen.ipynb) - 📄 [0001_vol_calendar_screen.ipynb](reports_and_screens/00_fx/0001_vol_calendar_screen.ipynb) - 📄 [0002_forward_vol_screen.ipynb](reports_and_screens/00_fx/0002_forward_vol_screen.ipynb) - 📄 [0003_10x_binary_screen.ipynb](reports_and_screens/00_fx/0003_10x_binary_screen.ipynb) - 📄 [0004_bear_put_spread_screen.ipynb](reports_and_screens/00_fx/0004_bear_put_spread_screen.ipynb) - 📄 [0005_vol_skew.ipynb](reports_and_screens/00_fx/0005_vol_skew.ipynb) - 📄 [0006_vol_swap_spread.ipynb](reports_and_screens/00_fx/0006_vol_swap_spread.ipynb) - 📄 [0007_eq_fx_hedges.ipynb](reports_and_screens/00_fx/0007_eq_fx_hedges.ipynb) - 📁 02_rates - 📄 [0000_vol_fixed_strike_grid.ipynb](reports_and_screens/02_rates/0000_vol_fixed_strike_grid.ipynb) - 📄 [0001_vol_moneyness_screen.ipynb](reports_and_screens/02_rates/0001_vol_moneyness_screen.ipynb) - 📄 [0002_swaption_carry_grid.ipynb](reports_and_screens/02_rates/0002_swaption_carry_grid.ipynb) - 📄 [0003_spot_vol_shock.ipynb](reports_and_screens/02_rates/0003_spot_vol_shock.ipynb) - 📁 03_prime - 📄 [0001_gs_pb_data_case_study_sector_positioning.ipynb](reports_and_screens/03_prime/0001_gs_pb_data_case_study_sector_positioning.ipynb) - 📄 [0002_gs_pb_data_case_study_flows.ipynb](reports_and_screens/03_prime/0002_gs_pb_data_case_study_flows.ipynb) - 📄 [0003_gs_pb_data_case_study_factors.ipynb](reports_and_screens/03_prime/0003_gs_pb_data_case_study_factors.ipynb) - 📄 [0004_gs_pb_data_case_study_country_positioning.ipynb](reports_and_screens/03_prime/0004_gs_pb_data_case_study_country_positioning.ipynb) - 📄 [0005_gs_pb_data_case_study_leverage.ipynb](reports_and_screens/03_prime/0005_gs_pb_data_case_study_leverage.ipynb) - 📄 [README.md](reports_and_screens/README.md) ================================================ FILE: gs_quant/content/__init__.py ================================================ ================================================ FILE: gs_quant/content/events/00_gsquant_meets_markets/00_us_election_analysis/0000_macro_big_picture.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [], "source": [ "from gs_quant.data import Dataset\n", "from gs_quant.markets.securities import Asset, AssetIdentifier, SecurityMaster\n", "from gs_quant.datetime import *\n", "from gs_quant.api.fred.data import FredDataApi\n", "from gs_quant.timeseries import returns, diff\n", "from sklearn.decomposition import PCA\n", "from collections import defaultdict\n", "import matplotlib.pyplot as plt\n", "import datetime as dt\n", "import pandas as pd" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics', 'read_product_data'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this notebook, we'll take a look at the current macro landscape and understand how risk has evolved in the last 6mo through the lens of principal component analysis (PCA).\n", "\n", "The content of this notebook is split into the following parts:\n", "* [1: First, the data](#1:-First,-the-data)\n", "* [2: PCA](#2:-PCA)\n", "* [3: Interpreting top 2 risk drivers](#3:-Interpreting-top-2-risk-drivers)\n", "* [4: Realized vs predicted](#4:-Realized-vs-predicted)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1: First, the data\n", "\n", "Let’s start by pulling volatility data from [gs data catalog](https://marquee.gs.com/s/discover/data-services/catalog) to get a sense for the macro landscape." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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nl27dlFYWMiePXsYMGAAZWVlFBcXs2nTJgB27drFmjVrGDt2bNQ5vF4vd911F3379mX06NEt/RKEEEKIlLE6eF5bXpTahrSgtMxkpcKcOXO48847w7ZdccUVzJ07l1deeYUbbriBuro63G43zz77LJ06dQrud80115CdnU19fT3nnXceb775JgA+n4/s7OwWfR1CCCFEKqgMLEcqQVaCPvnkk6htt912W/D2smXLEj7OsmHDBgYNGgRA//79Wb9+fdx9X3jhhYTaKYQQQrRKmRdjSXdhqsycOZMf/OAHPPTQQ6luihBCCCGSQDJZKXLjjTdK5XYhhBAZIwMTWZLJEkIIIUTyZc6cwhAJsoQQQgiRdB1z3KluQouTIEsIIYQQSRevEHdbJkGWEEIIIZIu80IsCbKa7PXXX0cpxebNmwGzRMMll1wSts+UKVOYP38+ABMnTmTo0KGMHj2aYcOGceutt1JeXg5AYWEhI0eODDt2xowZPPbYY4BZFuK0005j7NixDB8+nBkzZgBmOYeCggJOOukkBg8ezAUXXMBnn32WzJcthBBCHJMMTGRJkNVUc+bMYcKECcydOzfhY2bPns3atWtZu3Yt2dnZXHbZZQkdN3nyZGbNmsXq1atZv349V155ZfCxq666ilWrVrF161buvPNOLr/88mDVeSGEEKK1yaTldCwSZDVBVVUVS5cu5bnnnmtSkGXJysri0UcfZffu3axZs6bR/UtKSujZsycATqeTESNGxNzvnHPOYdq0acyaNavJbRJCCCFaQiZmstKzTta7d8L+dc17zh6j4MJHGtzljTfeYNKkSQwZMoSuXbuycuXKJj+N0+lkzJgxbN68mdNOO63BfadPn87QoUOZOHEikyZNYvLkyeTk5MTc9+STT+aZZ55pcnuEEEKIlpCBMZZksppizpw5XH311QBcffXVzJkzB6Vil1eLtx1CMywaO/a+++5j+fLlnH/++bz66qtMmjSp0XMKIYQQrVEm/k6lZyarkYxTMpSWlvLxxx+zfv16lFL4/X6UUlx//fUcPnw4bN+ysjK6desW8zx+v59169YxfPhw8vPzYx47YMCA4P1BgwZx0003MXXqVAoKCigtLY153lWrVjF8+PBjfJVCCCGEaC6SyUrQ/Pnzuf7669m1axeFhYXs2bOHAQMGUFZWRnFxcXDQ+a5du1izZg1j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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "start_date = dt.date(2012, 1, 3)\n", "end_date = business_day_offset(dt.date.today(), -1, roll='preceding')\n", "\n", "# gs data sets\n", "fxspot, irspot, fxvol, irvol = [Dataset(x) for x in ['FXSPOT_PREMIUM', 'IR_SWAP_RATES', 'FXIMPLIEDVOL_PREMIUM', 'IR_SWAPTION_VOLS']]\n", "\n", "fxv = fxvol.get_data(start_date, end_date, bbid=['EURUSD', 'USDJPY', 'AUDJPY', 'CADUSD', 'AUDUSD'], tenor='3m', deltaStrike='DN', location='NYC')\n", "pd.pivot_table(fxv, values='impliedVolatility', index=['date'], columns=['bbid'], aggfunc=np.sum).plot(figsize=(10, 6), title='3m implied fx vol')" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "irv = irvol.get_data(start_date, end_date, payOrReceive='Pay', currency=['USD', 'EUR', 'JPY', 'AUD'], terminationTenor='10y', expirationTenor='3m', strikeRelative='ATM')\n", "pd.pivot_table(irv, values='impliedNormalVolatility', index=['date'], columns=['currency'], aggfunc=np.sum).plot(figsize=(10, 6), title='3m10y implied rate vol')" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "FRED_API_KEY = 'insert key'\n", "fred_API = FredDataApi(api_key=FRED_API_KEY)\n", "fred_pull = fred_API.build_query(start=start_date, end=end_date)\n", "fred_API.query_data(fred_pull, dataset_id='VIXCLS').plot(figsize=(10, 6), title='VIX')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2: PCA" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's now look across a number of macro series and run PCA to understand what's driving risk - to start we'll grab more data from [gs data catalog](https://marquee.gs.com/s/discover/data-services/catalog) and FRED and normalize it.\n", "\n", "* GS FX spot [here](https://marquee.gs.com/s/developer/datasets/FXSPOT_PREMIUM) and vol [here](https://marquee.gs.com/s/developer/datasets/FXIMPLIEDVOL_PREMIUM)\n", "* GS Cash and Swap Rates [here](https://marquee.gs.com/s/developer/datasets/IR_SWAP_RATES) and vol [here]()\n", "* Fred API [here](https://research.stlouisfed.org/useraccount/login/secure/) " ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "instruments = {\n", " 'Equities' : ['SPX', 'NDX', 'N225'],\n", " 'Commodities': ['Gold', 'WTI'],\n", " 'Rates': ['5y', '30y'],\n", " 'FX': ['USDJPY', 'EURUSD', 'USDCAD', 'AUDJPY'],\n", " 'Credit': ['HY'],\n", " 'Fundamental': ['Breakevens', 'VIX']\n", "}" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
SPXNDXN225GoldWTI5y1y5y_curve5y_xmkt3m5y_impvol3m5y_impreal...EURUSD_3m_imprealUSDCADUSDCAD_3m_impvolUSDCAD_3m_imprealAUDJPYAUDJPY_3m_impvolAUDJPY_3m_imprealHYBreakevensVIX
date
2020-09-013526.6512292.86323138.071987.9542.6132.0547038.77615169.4507282.6415591.220437...1.1919311.307480.07040.98809178.0690.10870.9562714.971.7626.12
2020-09-023580.8412420.54323247.151969.0042.6132.5516078.56411673.3431462.6274511.234413...1.1617211.307050.07080.98792577.7760.11050.9706064.931.7326.57
2020-09-033455.0611771.36723465.531934.1042.6130.9082237.56001072.9130062.7010931.268510...1.1473921.313400.07341.11124477.1600.11631.0682325.041.6533.60
2020-09-043426.9611622.13323205.431937.6042.6136.66436312.72093177.8652802.7022461.266355...1.1262251.304480.07251.09161277.5070.11731.1690465.131.7030.75
2020-09-083331.8411068.25823274.131920.6042.6136.66436312.72093177.8652802.7184081.252479...1.1673261.322240.07511.11292676.5380.12241.2310855.251.6831.46
\n", "

5 rows × 34 columns

\n", "
" ], "text/plain": [ " SPX NDX N225 Gold WTI 5y \\\n", "date \n", "2020-09-01 3526.65 12292.863 23138.07 1987.95 42.61 32.054703 \n", "2020-09-02 3580.84 12420.543 23247.15 1969.00 42.61 32.551607 \n", "2020-09-03 3455.06 11771.367 23465.53 1934.10 42.61 30.908223 \n", "2020-09-04 3426.96 11622.133 23205.43 1937.60 42.61 36.664363 \n", "2020-09-08 3331.84 11068.258 23274.13 1920.60 42.61 36.664363 \n", "\n", " 1y5y_curve 5y_xmkt 3m5y_impvol 3m5y_impreal ... \\\n", "date ... \n", "2020-09-01 8.776151 69.450728 2.641559 1.220437 ... \n", "2020-09-02 8.564116 73.343146 2.627451 1.234413 ... \n", "2020-09-03 7.560010 72.913006 2.701093 1.268510 ... \n", "2020-09-04 12.720931 77.865280 2.702246 1.266355 ... \n", "2020-09-08 12.720931 77.865280 2.718408 1.252479 ... \n", "\n", " EURUSD_3m_impreal USDCAD USDCAD_3m_impvol USDCAD_3m_impreal \\\n", "date \n", "2020-09-01 1.191931 1.30748 0.0704 0.988091 \n", "2020-09-02 1.161721 1.30705 0.0708 0.987925 \n", "2020-09-03 1.147392 1.31340 0.0734 1.111244 \n", "2020-09-04 1.126225 1.30448 0.0725 1.091612 \n", "2020-09-08 1.167326 1.32224 0.0751 1.112926 \n", "\n", " AUDJPY AUDJPY_3m_impvol AUDJPY_3m_impreal HY Breakevens \\\n", "date \n", "2020-09-01 78.069 0.1087 0.956271 4.97 1.76 \n", "2020-09-02 77.776 0.1105 0.970606 4.93 1.73 \n", "2020-09-03 77.160 0.1163 1.068232 5.04 1.65 \n", "2020-09-04 77.507 0.1173 1.169046 5.13 1.70 \n", "2020-09-08 76.538 0.1224 1.231085 5.25 1.68 \n", "\n", " VIX \n", "date \n", "2020-09-01 26.12 \n", "2020-09-02 26.57 \n", "2020-09-03 33.60 \n", "2020-09-04 30.75 \n", "2020-09-08 31.46 \n", "\n", "[5 rows x 34 columns]" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fred_symbols = {\n", " 'SPX': 'SP500',\n", " 'NDX': 'NASDAQ100',\n", " 'N225': 'NIKKEI225',\n", " 'Gold': 'GOLDAMGBD228NLBM',\n", " 'WTI': 'DCOILWTICO', \n", " 'HY': 'BAMLH0A0HYM2', \n", " 'VIX': 'VIXCLS',\n", " 'Breakevens': 'T10YIE'\n", "}\n", "\n", "color_map = {\n", " 'Equities': '#20396D',\n", " 'FX': '#68A2BF',\n", " 'Rates': '#CD252B',\n", " 'Commodities': '#E3E311',\n", " 'Credit': '#E3E000',\n", " 'Vol': '#67E311',\n", " 'Fundamental': '#25cdae',\n", "}\n", "\n", "realVolWindow = 66 \n", "asset_map = {}\n", "df = pd.DataFrame()\n", "\n", "for asset_type, asset in instruments.items():\n", " for x in asset:\n", " asset_map[x] = asset_type \n", " if asset_type=='FX':\n", " asset_map['{}_3m_impvol'.format(x)]= asset_type\n", " asset_map['{}_3m_impreal'.format(x)]= asset_type\n", " \n", " df[x] = fxspot.get_data(start_date, end_date, bbid=x)[['spot']]\n", " realVol_f = df[x].copy()\n", " realVol_f = [returns(realVol_f)[r-realVolWindow:r].std()*15.875 for r in range(len(realVol_f))]\n", " \n", " df['{}_3m_impvol'.format(x)] = fxvol.get_data(start_date, end_date, bbid=x, tenor='3m', deltaStrike='DN', location='NYC')[['impliedVolatility']]\n", " df['{}_3m_impreal'.format(x)] = fxvol.get_data(start_date, end_date, bbid=x, tenor='3m', deltaStrike='DN', location='NYC')[['impliedVolatility']]\n", " df['{}_3m_impreal'.format(x)] /= realVol_f\n", " elif asset_type=='Rates':\n", " asset_map['1y{}_curve'.format(x)] = asset_type\n", " asset_map['{}_xmkt'.format(x)] = asset_type\n", " asset_map['3m{}_impvol'.format(x)]= asset_type\n", " asset_map['3m{}_impreal'.format(x)]= asset_type\n", " asset_map['3y_vs_3m_{}_calendar'.format(x)]= asset_type\n", " asset_map['3y{}_skew'.format(x)]= asset_type\n", " \n", " irspot_d = irspot.get_data(start_date, end_date, currency='USD',effectiveTenor = '0b', terminationTenor=x, floatingRateOption='USD-LIBOR-BBA', floatingRateDesignatedMaturity='3m', clearingHouse='LCH', pricingLocation='NYC')\n", " irfront_d = irspot.get_data(start_date, end_date, currency='USD',effectiveTenor = '0b', terminationTenor='1y', floatingRateOption='USD-LIBOR-BBA', floatingRateDesignatedMaturity='3m', clearingHouse='LCH', pricingLocation='NYC')\n", " irspot_eur_d = irspot.get_data(start_date, end_date, currency='EUR',effectiveTenor = '0b', terminationTenor=x, floatingRateOption='EUR-EURIBOR-TELERATE', floatingRateDesignatedMaturity='6m', clearingHouse='LCH', pricingLocation='LDN')\n", " gammaVolA = irvol.get_data(start_date, end_date, payOrReceive='Pay', currency='USD', terminationTenor=x, floatingRateOption='USD-LIBOR-BBA', expirationTenor='3m', clearingHouse='LCH', strikeRelative='ATM', pricingLocation='NYC')[['impliedNormalVolatility']]\n", " vegaVolA = irvol.get_data(start_date, end_date, payOrReceive='Pay', currency='USD', terminationTenor=x, floatingRateOption='USD-LIBOR-BBA', expirationTenor='3y', clearingHouse='LCH', strikeRelative='ATM', pricingLocation='NYC')[['impliedNormalVolatility']]\n", " vegaVolhigh = irvol.get_data(start_date, end_date, payOrReceive='Pay', currency='USD', terminationTenor=x, floatingRateOption='USD-LIBOR-BBA', expirationTenor='3y', clearingHouse='LCH', strikeRelative='ATM+50', pricingLocation='NYC')[['impliedNormalVolatility']] \n", " df[x] = irspot_d.rate.copy()*1e4\n", " realVol_r = [df[x].diff()[r-realVolWindow:r].std() for r in range(len(df[x]))]\n", " \n", " df['1y{}_curve'.format(x)] = (irspot_d.rate - irfront_d.rate) * 1e4\n", " df['{}_xmkt'.format(x)] = (irspot_d.rate - irspot_eur_d.rate)*1e4 \n", " df['3m{}_impvol'.format(x)] = gammaVolA\n", " df['3m{}_impreal'.format(x)] = gammaVolA\n", " df['3m{}_impreal'.format(x)] /= realVol_r\n", " df['3y_vs_3m_{}_calendar'.format(x)] = vegaVolA/gammaVolA\n", " df['3y{}_skew'.format(x)] = vegaVolhigh/vegaVolA\n", " else:\n", " df[x] = fred_API.query_data(fred_pull, dataset_id=fred_symbols[x])\n", " \n", "data = df\n", "data = data.fillna(method='ffill').dropna()\n", "data.tail().head()" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "frame_t = data.copy().apply(lambda x: x.diff() if x.name in instruments['Rates'] else returns(x))\n", "frame_t.dropna(inplace=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's run a 3-factor PCA for a 126 day (6mo) rolling period and record how much variance is explained by each component over our time frame." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "period = 126\n", "components = 3\n", "f_loadings = defaultdict(list)\n", "\n", "for start in range(len(frame_t) - period):\n", " d = frame_t.iloc[start:start + period]\n", " d = (d - d.mean()) / d.std()\n", " model = PCA(n_components=components)\n", " model.fit(d)\n", " for i, c in enumerate(model.explained_variance_ratio_):\n", " f_loadings[i].append(c)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Text(0.5, 1.0, 'Contribution to variance from each component')" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "factors = pd.DataFrame(f_loadings)\n", "factors.index = frame_t.index[period:]\n", "plt.figure(figsize=(12, 8))\n", "plt.stackplot(factors.index, factors.transpose())\n", "plt.title('Contribution to variance from each component')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3: Interpreting top 2 risk drivers\n", "Let’s now look at the top 2 components explaining risk in 2020 as well as over the entire period. Note, components can rotate over time, so we look at the absolute ratios of PC1 vs PC2 and vise versa to understand the primary drivers." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "# model 1 trained on full dataset\n", "full_model = PCA(n_components=components)\n", "full_data = (frame_t - frame_t.mean()) / frame_t.std()\n", "full_model.fit(full_data)\n", "components_full = pd.DataFrame(full_model.components_, columns=frame_t.columns)\n", "\n", "# model 2 trained on 2020\n", "model_2020 = PCA(n_components=components)\n", "data2 = frame_t[frame_t.index.year == 2020]\n", "model_2020.fit((data2 - data2.mean()) / data2.std())\n", "components_2020 = pd.DataFrame(model_2020.components_, columns=data.columns, index=factors.columns)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [ { "data": { "text/plain": [ "Text(0, 0.5, 'PC2')" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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IKwK9XtfYPCVJEmSdMhL52ZZkiRJkiQdcAbhg0xJSQmdO3cmlUrRsWNHZs+efUDu26NHD1KpFO3bt2fq1Kl7NdYJJ5xAKpUilUqRm5u7jyrcWfPmzfd6jB49elBUVLT3xUiSJEmqMlwafZCpU6cOhYWFVK9enSVLltC3b1/mz5+/3+/7/PPPk5mZSUlJCa1bt6Z///7fe6yMjAzy8/P3XXEHiS1btpCRkZHuMiRJkiTtJWeEDzLVqlWjevWy3ydKSkpo1aoVXbt2ZfXq1QAUFhYyePDgna7bvHkzrVq1YvPmzQDk5eUxevRoFi9ezNlnn0337t358Y9/vMv7ZmZmArBu3TpatmwJQH5+Pueffz79+vXj1FNPZcaMGVx66aWcccYZ/PGPf9zlWDFGunfvzvnnn8/ChQsB6Nu3L3//+98B+OijjzjvvPMqvfaLL77gJz/5Cd26daN79+58+umnzJkzh+7du/PDH/6QPn36sGG7na0BSktLueaaa+jevTtdu3blzTffBGDgwIHceOON9OrVi06dOlU8wwcffJD27dszYMAAvvzySwA+//xzzj33XFKpFF26dOGf//xnxRhDhw7lggsu4NVXX93ld5YkSZJUdRiED0IrVqyga9eu9OzZk4suuohBgwYxefJkACZMmMC111670zXVq1fn3HPP5YUXXgDgiSee4Kc//SkvvfQSgwYNYs6cOcyaNWuX99yyZQvdunXjjDPOoE+fPhXtxcXFTJkyhccff5yhQ4cyadIkCgoKGDdu3C7HeuONN5gzZw733XcfAwYMAGDIkCE89thjADz++OOVhnmAsWPH8qMf/YiCggLmzJnDUUcdRceOHZkzZw6vvvoqp5xyCk899dQO1zz22GM0b96cOXPmMH36dG677baKc82bN2fWrFn07t2bp556itWrVzNx4kRef/11fve737F06VIADj/8cF544QXy8/MZOXIkv/rVryrGOO6443juuedIpVK7/M6SJEmSqg6XRh+EmjRpwty5cykqKiKVSrF48WJycnIYMmQI7733Hp06dar0uquuuorc3Fw6dOjA+vXrOfHEExk0aBC5ubkMGDCAVq1aceedd1Z6bUZGBgUFBRQXF9OhQwcuu+wyAFq1akW1atVo2rQpJ598MrVr16Z27dqsX79+l/U3aNAAgNatW5Odnc0XX3xBTk4OI0aM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r74C4Oqrr+aKK67g3HPPZfTo0XTp0oVmzZoxZMgQunfvvr8ei6SECjHGdNewz7Vv3z4uWLAg3WVIkhJu4sSJAAwcODCtdUjfZuHChdx///3k5eV9Z99Vq1bRv39/XnnllQNQmSRVLoTwVoxxr96ZcEZYkiQpofLy8hg/fjyTJk36zr4vv/wyI0eOZOzYsRVtV155JcuWLas4PvbYY5k8efJ+qVWS9iVnhCVJ2k+cEZYkad/bFzPC/h1hSZIkSVKiGIQlSZIkSYliEJYkSZIkJYpBWJIkSZKUKAZhSZIkSVKiGIQlSZIkSYliEK7Cimc+x6LUebzd4gwWpc6jeOZz6S5JkiRJkg561dNdgL6f4pnPsWzkaOKGDQCUrvyEZSNHA1C/9wVprEySJEmSDm7OCFdRK8c9WBGCt4kbNrBy3INpqkiSJEmSqgaDcBVV+smne9QuSZIkSSpjEK6iahzTaI/aJUmSJEllDMJVVONhtxCysnZoC1lZNB52S5oqkiRJkqSqwc2yqqhtG2KtHPcgpZ98So1jGtF42C1ulCVJkiRJ38EgXIXV732BwVeSJEmS9pBLoyVJkiRJiWIQ3kslJSV07tyZVCpFx44dmT179gG5b48ePUilUrRv356pU6ful7HGjBlDly5dyMnJoaioaC8rrlxRURE9evTY63GaN2++D6qRJEmSlAQujd5LderUobCwkOrVq7NkyRL69u3L/Pnz9/t9n3/+eTIzMykpKaF169b0799/n471/vvv88orr/Daa69RWFjI8OHDmTZt2j78BumxdetWqlXz9x9JkiQpyUwEe6latWpUr172e0JJSQmtWrWia9eurF69GoDCwkIGDx6803WbN2+mVatWbN68GYC8vDxGjx7N4sWLOfvss+nevTs//vGPd3nfzMxMANatW0fLli0ByM/P5/zzz6dfv36ceuqpzJgxg0svvZQzzjiDP/7xj3s8Vq9evQA455xzeOedd9i8eTNt2rRh06ZNAEyePJl777230jHfeecdUqkUqVSqIqSPGzeOnJwcOnTowC9+8Yudrvn444/p1asXOTk59OrVi88++wwom+39xS9+Qbdu3ejbty9QFmivuOIKunXrxvDhwyvGmDNnDt27d+eHP/whffr0YcOGDRVj/PznP+fcc8/l66+/3uWzkCRJknToMwjvAytWrKBr16707NmT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b8m8/namd2+pJFhuW2RkJIMHD7beJyYmYrfbiYqKolWrVtjtdlq0aEFkZCQANWvWJCAgwDq2Zs0a69yEhAS6d++O3W7H19eXcePGWceio6NxcHAgPj7eagsKCsLLy4tHH32U5s2bM2vWLEzTzDXOn376CV9fX+x2O61ateLbb7+95XuOi4tj1qxZt3z+7bryMy8prrdrtK2wAhEREREREcnN2LFjWbFiBdWrVycjI4Pz588DYLPZ2Lp1KwDHjh2jc+fOPPzww3h6etKzZ0/mzZuHt7c3ABs2bLDGCw8P56WXXmLp0qVMnjzZap87dy5+fn6kpqYSFBTEokWLCAoKuioeDw8PduzYgWEYbNmyhWnTprFixYpburfGjRvTuHHjWzo3NxkZGdhsSuOu53oVYREREREpxu6UCprI7Shbtizr16/n999/x2azce+9917V54EHHuCFF15g5cqV7N69m1q1allJMEDHjh0BSE9P55tvvmHmzJmsW7cu1+s5OzszdepUwsPDcz1eqlQpspeNJicn07BhQwCCg4MJCgoiMDCQpk2bsnbtWjp16kSDBg3Yt29frmNd/t9wUFAQI0aMoFOnTgQEBPDpp5/SoUMHmjZtyrFjxwDw9PRkwoQJ+Pv7069fPzIzM0lISKBZs2b079+fIUOG8Ntvv9GrVy/atm1LmzZtOHToEAB///vfCQgIoEmTJoSGhl73c7+bKREWERERuQuNHTuWZcuWERkZyY4dO/Dy8gL+qqDt2LHDqoYdPHiQjIwMevbsycSJE4mMjCQ6Opp27dpZ411eQbvc3Llz+frrr9m2bRuxsbEsWrSoUO9TSoaPPvqImJgYvLy8aN++PYcPH861X7Vq1Th69ChHjhyhWrVqufbZsGEDnTt3xsnJicaNGxMbG3vNsfISGxtLy5YteeGFF+jQoYPV7urqSkREBD179iQ0NJR169YxdepUFixYcEP3WqdOHdatW0ft2rXZsWMHGzdupH///nzyyScAXLp0icDAQLZt24aLiwsRERFA1hdZ7733Hh9++CEzZsyge/fubN68mdmzZzN+/HgAXnvtNbZu3crOnTt56623SE9PzzOOu50SYREREZG7UHGroIncCBcXF1JTU633qampuLi4UKNGDUJDQzl06BD9+vXjtddey/X8I0eO4ObmRrVq1fjll19y7TM39ENCl31G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wObNm/Hz87ul8W5mqm1esr/lz+bq6prn1GwRESmeLp8aDVm79L/00kvY7XbS09Pp1atXrpsSDh8+HH9/f4YOHcqMGTMYNmwY06ZNIz09nffeew/Iel557969cXR05OLFi4wfP96q/E6fPp358+dz4cIF/P398/xidv/+/bz88svYbDbS09OZM2dO/n8I17Bz505CQ0NxdHTMdTf8IUOGMHLkSAICAgDw9vZm1qxZdO/enVatWlGnTh3KlStXqDHL7THuxm8tvL29zexvdURE7iTvvfceH374IUOHDuX5558nODiYL774gtdff53AwEAg6/EVl08tvvK9FB/ZuzBfa3dWEREpWna7nSVLluT5VAQpfgzDiDVN0/t2xlBFWESkGHn00Ud58cUX8fX1BaBVq1a88cYbtGrVqogjExERyR8hISFXPYbt888/z3W36cIcS0oWVYRFREQKiCrCIiIi+S8/KsLaLEtERERERERKFCXCIiIiIiIiUqIoERYREREREZESRYmwiIiIiIiIlChKhEVERERERKRE0eOTROSO9Z8Ne3nrgzUcO3WWB+6vyCvDuvJEx2ZFHZaIiIiIFHNKhEXkjvSfDXuZOHM5F9LSATh68iwTZy4HUDIsIiIiItekqdEickd664M1VhKc7UJaOm99sKaIIhIRERGRO4USYRG5Ix07dfam2kVEREREsikRFpE70gP3V7ypdhERERGRbEqEReSO9Mqwrrg4lc7R5uJUmleGdS2iiERERETkTqHNskTkjpS9IZZ2jRYRERGRm6VEWETuWE90bKbEV0RERERumqZGi4iIiIiISImiRFhEbltCQgKGYbB69WqrzdPTk8WLF9O8eXNat25N7969SUtLA2D06NG0aNGCFi1aMHPmTOscu91Oy5YtsdvtjBw5stDvQ0RERERKBiXCIpIv6tSpw4wZMzBN02rz8/MjOjqa7du3U716dZYsWQLACy+8wK5du4iOjmb16tX83//9n3XOihUriIyMZO7cuYV+DyIiIiJSMigRFpF84ebmRpMmTXJUhT08PLDZbAA4OjpSqlTWtgQPP/wwAA4ODthsNquPYRj07t2bNm3asGXLlkK+AxEREREpKbRZlojkm4kTJ/LUU0/RrVu3HO0//PADX375JdHR0TnaP/74Yx566CEefPBBIKsaXKlSJY4cOUK7du2IiYnhnnvuKazwRURERKSEUEVYRPKNu7s7TZs25T//+Y/VlpiYSFBQECtWrMDZ2dlq37RpE4sWLeKDDz6w2ipVqgRAtWrVaNSoEYcOHSq84EVERESkxFBFWETy1YQJE3jqqacAOH36ND169GDevHk89NBDVp/du3czZcoU1q1bh4uLCwCmafL7779Tvnx5fv/9d/bt20eNGjWK5B5ERERE5O6mirCI5Ct3d3e8vb0BCA4O5ujRo4wZMwa73c7ChQsBGDRoEL///jtPPPEEdrud2NhYLl26REBAAH5+frRr147g4GBcXV2L8lZERERE5C5lXL7D693C29vbjImJKeowRESkhAsLCwMgKCioSOMQERG5mxiGEWuapvftjKGKsIiIiIiIiJQoSoRFRERERESkRFEiLCIiIiIiIiWKEmEREREREREpUZQIi4iIiIiISImiRFhERERERERKFCXCIiIiIiIiUqIoERYREREREZESRYmwiIiIiIiIlChKhEVERERERKREUSIsIiIiIiIiJYoSYRERERERESlRlAiLiIiIiIhIiaJEWEREREREREoUJcIiIiIiIiJSoigRFhERERERkRJFibCIiIiIiIiUKEqERUREREREpERRIiwiIiIiIiIlihJhERERERERKVGUCIuIiIiIiEiJokRYREREREREShQlwiIiIiIiIlKiKBEWERERERGREkWJsIiIiIiIiJQoRZIIG4bhahjGV4Zh/O/PPyvm0e9DwzBOGYaxv7BjFBERERERkbtTUVWExwObTdN8GNj85/vchAGPFVZQIsVVxJkj2Petp/Z/V2Hft56IM0eKOiQRERERkTtWUSXC3YBFf75eBDyRWyfTNLcDSYUVlEhxFHHmCJN/+YZj6RcwgWPpF5j8yzdKhkVEREREblFRJcKVTdM8DvDnn/cXURwixV7Ise9JNTNytKWaGYQc+76IIhIRERERubOVKqiBDcPYBFTJ5dCkArreUGAoQPXq1QviEiJF4nj6hZtqFxERERGRayuwRNg0zXZ5HTMM46RhGFVN0zxuGEZV4FQ+XC8UCAXw9vY2b3c8keKiamkXjuWS9FYt7VIE0YiIiIiI3PmKamp0BPDsn6+fBVYXURwixd6YB+rhbNhytDkbNsY8UK+IIhIRERERubMVVSI8E2hvGMb/gPZ/vscwjAcMw/gyu5NhGMuAnUBt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at26tTwejxo2bKisrCz/dhMnTtS2bdvUtGlTJScnq06dOjp8+LB//eHDh1WnTh1FRETo6NGj2rt3r+bMmaP4+Phi1XVyDW3btvUvn6jBGKOoqChJUseOHfXVV19Jkq688krVqlVLkrR582ZNnz5dXq9XiYmJOnTokCTpH//4h6KionTRRRdp9+7d8nq9kqT8/PxKeQHgtttu0+zZsyVJCxYsUL9+/WSt1dixY7V06VKtWbNGK1eu1KZNm4r12eMXBGEAAACgisnNzfUv16pVSyEhIZJUJLCdvHxiRDMnJ8e/7oILLtB5552nmjVr6rzzztOuXbuUl5entLQ0f1C9/fbbNXHiRIWGhqp+/frFqu1sNVhrlZ6eLklav369mjVrJknyeDz+vq1atdJ9990nn88nn8+nxYsX6+DBg5o1a5bWrl2rffv2qUGDBpo4caIkqW7dupIq3wWANm3aaM+ePcrKytJrr72moUOH6rvvvtPFF1+s0NBQGWPUqVMn/8UCFB9BGAAAAKhitmzZom7duik2NlZxcXGaNm1asba799575fV61a1bN3377bcaPHiwJGn69On6wx/+oJiYGI0cOVK1a9eWJA0YMECLFy9WQkJCqdVerVo1vf3224qJidEPP/yguLi4U/pMnDhRb7zxhq699lrFxsbqmWeeUWhoqK644gpFR0dr5MiRqlevnoKDgyVV7gsAt9xyi5555hn9+OOPioiI0IUXXqh9+/bp0KFDstbqk08+UfPmzYtVA37B9wgDAAAAVUyHDh2Umpp6xvXR0dGKjo6WJNWrV09paWmSpOeee+60/SMjI/0PkjqZtVaNGzfW9ddff8Zjeb1e/xTlk5+8PGnSJP/yTTfdpJtuusn/+sknnyyyjyZNmmjZsmX+1xdccIFef/31U441YcIEjRkzRt98841CQkI0depU7d692x+Iz+Tee+/V559/7r8/+K9//aukXy4AWGtPuQAwevRovfLKK7+535I4cQHgvvvuU3h4uP8CwODBg9W4cWNNnz5dUsGD0J566in17NlTAQEB6t27t6666ir5fL5Sq8UNzOlu7K7sIiMj7YmrKQAAOOXEP/iKO20OACqTjRs3atSoURoxYoSGDBkiSbrvvvu0bt06f5/q1av7n8BcXBEREf6nQFdUubm5io6O1ieffFJkyva5qAzvu6Iwxmyw1kae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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "fig = plt.figure(figsize=(16, 16))\n", "comp1, comp2 = 0, 1\n", "for i, row in components_full.iteritems():\n", " plt.scatter(row[comp1], row[comp2], color=color_map[asset_map[row.name]], label=row.name)\n", " plt.text(row[comp1]-0.02, row[comp2]-.01, row.name, fontsize=9)\n", "\n", "plt.axhline(0, color='grey')\n", "plt.axvline(0, color='grey')\n", "plt.title('PC1 vs PC2 - Full History')\n", "plt.xlabel('PC'+str(comp1+1))\n", "plt.ylabel('PC'+str(comp2+1))" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [ { "data": { "text/plain": [ "Text(0, 0.5, 'PC2')" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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7u7mLW95C5dddhm33HILH/vYx1i+fDl/8id/Mua5yWSSTZs2kU6nOfnkk7ngggsAWL58OYlEgqVLl3LsscdSWlpKaWkp3d3d477WscYaft2jjwG44ooruOaaazjppJMoLS1l6dKlY47Z1NTE29/+9ted++CDD/Ltb3+bEAJbt27llVdeYcmSJSPnPPfccyNdSAMDAyPrKBUUFHDiiScCcPjhh5NOp3nxxRc55ZRTADj11FMJIQCwZcsWbrjhBnp7e9m9ezeVlZUjNYz3OUiSJEmSDo6dS5pSiUSCGOOMDJggW38qleKoo45iwYIFDA0NsXPLC2z7t3+ntbWVnt5e6O2DF7dT2TNARUUFZWVlvPOd7+STn/wkF1988bhjn3jiiWzfvp2vf/3rfOxjHwOy0/O+/OUvc8899/Doo4/y3HPPveG8/v7+kfezrKyMkpISSkpKAEZClr1vj2e8sVavXj3SVfXkk09ywgknAHDEEUcQQuDmm28ec7rfsPr6el544QWAkfFvvPFGfvrTn/LEE0+wbNkyYoyvO+f444/nq1/9Khs3buRnP/sZ3/rWt8YcO8bIMcccw9NPPw3AU089NTLWLbfcws0338ymTZs499xzRx4PIezX+yFJkiRJOnB2LmlKDXetDA4OzuiFlEMIVFdXU1VVRcf2PaRj5LWONl7ZsYP/tf6bJBMJ+ocGueOOO4DsFLDTTjttn9PRANauXctNN93Ejh07ALjvvvu46667CCGwcOFCjjvuuDecs2fPHi666CKSySS9vb3ceOONFBcXT+h1jTfW2972NlatWsXKlSspKirizjvvHDnn8ssv55prrmH9+vXjjvvXf/3XXHXVVYQQWLRoEffddx/nn38+K1eu5K1vfSvl5eVvOOcrX/kKf/iHf0hHRwcAH//4x7nkkkvGHP+8887j+9//PqtXr+bUU08dWffqwgsv5PLLL+e4446jqqpqpHNJkiRJkjR1wt7dA7PBihUr4nBXg/Kro6ODnTt3csQRR4x018x4m7Lfrc6+XoqSBRTmAjQAVq8A4JlnnuH222/nnnvuyUeFU+qHP/whTz31FLfccku+S5GkCbnrrrsAuPTSS/NahyRJ0kwTQvhljHHF3o/buaQpNdytNFOnxY2puAh6+ygrKn7j42R3frvjjju4++67R55673vfO7LLGmQXnv6Lv/iLN73U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HLdLAQBgxAWDQUWjUcbhAAA5iXApvxEuIWskT0J0LwEAcs3W43ClpaVulwMAwIizbVuGYRAu5SnCJWSN5EmIdZcAALmEcTgAQD6Ix+MES3mMcAlZw+MZ/HGkcwkAkEsCgQDjcACAnGfbNr/n8hjhErIGnUsAgFwzMDCgYDDIOBwAIOcRLuU3wiVkja3DpZ6eHh1//PFqbm7WjBkz9OKLL2akhpNPPlnNzc2aPn26HnnkkX061pQpU9Tc3Kzm5mYtWrRohCocburUqft8jJNPPlnr1q3b92IAACmO46i1tZVxOABAXiBcym9etwsAkrYeiysvL9fLL78sr9erNWvW6Mtf/rLefPPNtNfw7LPPyufzqaenR0cccYTOOeecvT6WaZpqaWkZueKyBL80AGD3JMfhxo8fz3kTAJDzuE7Ib3QuIWts3bnk8Xjk9Q5mnz09PTr88MPV1NSk9vZ2SdLLL7+siy++eNgx4vG4Dj/8cMXjcUnSkiVLtHDhQq1evVqzZs3SnDlzdNppp+2wBp/PJ0kKh8M67LDDJEktLS363Oc+p3nz5umQQw7Rk08+qbPPPlvTpk3Tb37zmx0ey3EczZkzR5/73Of01ltvSZK+/OUv669//askaf369TrllFO2+9iuri6dddZZmj17tubMmaPW1lYtXbpUc+bM0ac//WmdccYZGhgYGPIYy7J0ySWXaM6cOWpqatIbb7whSZo/f76uvvpq/dM//ZNmzpyZeg1//OMfa/r06TrvvPMUCoUkSZ2dnTrppJPU3NysE044QR988EHqGFdccYU+//nP65VXXtnhcwYADEqOw1VUVMjv97tdDgAAaeU4jmzbTl3DIf8QLiFrGO1BeT5YL/vPf5Ne/5s2vfWumpqadOqpp+rMM8/URRddpAcffFCS9Otf/1qXXnrpsGN4vV6ddNJJeu655yRJDz30kM4//3z993//ty666CItXbpUzzzzzA5rsG1bs2fP1rRp03TGGWekbg8EAnr44Yd1//3364orrtADDzygZcuW6Y477tjhsf785z9r6dKluu2223TeeedJki677DLdd999kqT7779/uwGZJN1666367Gc/q2XLlmnp0qWqr6/XjBkztHTpUr3yyis6+OCD9dhjjw15zH333aepU6dq6dKleuKJJ/SNb3wj9bWpU6fqmWee0dy5c/XYY4+pvb1dixcv1muvvaaf//znWrt2rSSpoqJCzz33nFpaWnTzzTfrBz/4QeoYkyZN0tNPP63m5uYdPmcAwNBxuLq6OrfLAQAg7ZLr5tK5lL+IFZEd2gLSB+vlidtKGKYUjanR8mj5E09qXaRXzc3NWr16tU488URddtlleu+99zRz5sztHurCCy/UokWLdOyxxyoSiWj//ffXRRddpEWLFum8887T4Ycfrm9961vbfaxpmlq2bJkCgYCOPfZYfelLX5IkHX744fJ4PBo/frwOPPBAlZSUqKSkRJFIZIdPqba2VpJ0xBFHyO/3q6urSyeeeKJuuOEG9ff3649//KNuuOGG7T723XffHRKeeTwerV69WjfffLOi0aja2tpUXl4+5DHvvPOOVqxYoeeff16SUt1IknTMMcdIkiZOnKiPPvpIa9eu1ac+9SkVFBSooKBABx98sCSpu7tbV111lVpbWxWLxVRWVpY6xvHHH7/D5woA+IfOzk7G4QAAeYVwCXQuITus3SQlEjI9HtmOo2gsJiUS0tpNKi8vV1lZmfx+v44++mhde+21Ovfcc3d4qCOPPFLr16/Xz372s1THUGFhoW6//XYtWbJEf/rTn/TOO+8Me5xlWUokEpIkv9+voqIiFRUVSZIMw0jdb+v/35FoNJoaW9u0aZO6u7tVWVkpwzB01lln6Wtf+5o+85nPqLCwcLuP/9SnPjVkvaZEIqFFixbpO9/5jpYtW6a5c+fKcZwhjznssMN0wQUXqKWlRS0tLfrLX/6y3Zodx9GUKVO0evVqxeNx9fb26v3335c02Ol11FFH6eWXX9a3v/3tId+DXxQAsGuRSERdXV2MwwEA8grhEuhcQnaIxiRJxd4CeQxD7679SN/42X/K9Hhk+Yt05513SpIuv/xyzZw5c6fjaNLg2kYLFy7Uxo0bJUmPPPKIFi9eLMMwNGbMGB100EHDHtPe3q5zzjlHpmkqGo3qlltu2WH4syvt7e0644wz5Pf7Zdu2fvnLX6YCnosuukjjx49Prb20PTfccIO++tWv6qGHHpJpmnr44Yc1b948XXzxxTrooINUUVExrHPp0ksv1TXXXKM5c+ZIkqZPn64f/vCH2z1+fX29vvKVr+i4447TgQceqClTpkiSTj31VJ177rl65ZVXdOihh+7VcweAfJVIJNTa2iqv18vucACAvEK4BGPb7odcMH36dGflypVul4E98frfUgHTEIU+aebhqT++9dZb+uEPf6glS5ZksLiR1dbWpnPOOUcvvfSS26UAQF5avHixpMHNCkZSR0eHgsGgxo8fT9cSACCvdHd3q62tTfvvvz+Leuc4wzBWOY4zfdvb+VtHdpjSKH2wfnAULsnjGbx9iyVLlujOO+/UAw88kLrt1FNPVSz2j1BqxowZ+o//+I9dfruHH35Y99xzz5Db7r777r3q1tmTY/3pT3/SzTffrFtvvTV12wUXXKANGzak/jxx4sTUwuUAgNEhEokoGAyqsrKSYAkAkHeSu3XTuZS/6FxC9mgLDK69FI0NdixNaZQaatyuCgCQY0a6cymRSGj9+vVyHEeTJ0+Wx8OSlgCA/NLW1qbe3l5NnTrV7VKQZnQuIfs11BAmAQBGnc7OTsViMU2YMIFgCQCQl2zbpmspz/EOCAAAYC8ld4errKxUSUmJ2+UAAOAKwiUQLgEAAOyF5O5wBQUFqqurc7scAABcQ7gEwiUAAIC9kByHGzNmDONwAIC8RrgE3gkBAADsof7+fnV1damqqopxOABAXnMcR7Zty+tlSed8RrgEAACwB5LjcD6fT7W1tW6XAwCAqxKJhBzHoXMpzxEuAQAA7IGOjg5ZlsU4HAAAGhyJk0S4lOd4RwQAALCb+vv71d3draqqKhUXF7tdDgAAriNcgkS4BGSdyy67TM3NzZKkdevW6eSTTx7y9alTp0qSFi5cqEMOOUSzZ8/WjBkzdP311ysWi0mS5s+fr+XLl0uSiouL1dzcrBkzZuj222/XCy+8oLlz56aO9+ijj+qyyy7LwDMDgNGNcTgAAIYjXIJEuARklVgsprfffltlZWXasGHDLu9/0003admyZXrttdcUi8X0/e9/f9h9Ghsb1dLSoldffVUPPPCADj30UFVVVemJJ55QKBTSbbfdpttuuy0dTyfrrVu3TlVVVWpubtasWbN0zTXX7PPxtg0DAeQOxuEAABiOcAkS4RKQVZ555hnNnTtXF154oR5++OHdfpxpmlq0aJF++9vf7vA+BQUFOuyww/Txxx/rjjvu0Pe+9z1dd911+ta3vqWqqqqRKH9UOuaYY9TS0qLXXntN//u//6vVq1envpb8RQkA4XBY3d3dqq6uZhwOAICtxONxSYRL+Y5wCcgijzzyiM4//3z98z//s5577rk9emxxcbEGBgZ2+PWenh699dZb2n///VVTU6Orr75a69at07x58/a17JwQj8cViURUVlamSZMm6Wtf+5rOOOMMWZalSy65RHPmzFFTU5PeeOMNSdIdd9yhE088Uccee6z+3//7f8OO99Of/lRXXnmlbNvW448/rk9/+tNqamrSd7/7XUnSWWedpbfffluS9PHHH+ukk06SpO3et6WlRaeddprOOeccTZs2TY8//rgk6T//8z913HHHac6cOfrxj3+c9tcIyFeJREJtbW3y+XyqqalxuxwAALKKbdvyeDx09eY5r9sFAHmvLSCt3aRQIKhXl7bosvkXSb4CrVu3TuvXrx8SGFmWJZ/Pt93DDAwMqKioaNjtmzZtSq3htHDhwtQ6IVOnTk2t35TPVq1apebmZn3yySc68sgjNXHiRG3evFnXX3+9Jk6cqF/84heaOnWq7r33XrW1tekLX/iCXn31VV1++eVasGCBEomEmpqadPHFF6eOeeONN8rn8+nnP/+5urq69KMf/UivvPKKCgoKdOaZZ+qdd97RBRdcoAcffFA/+tGPtGTJEp133nk7vK8ktbe36+mnn1ZbW5vmzp2rs88+W0uWLNHSpUtVVlamRCLh1ksI5Lz29nbF43FNmDCBN84AAGzDtm26lkC4BLiqLSDn7+uUsG3917IXdcO583X1F+dJB07Si+++pWeeeUaBQEDBYFDV1dVqaWnRUUcdNewwiURCt9xyi84+++xhX0uuuYQttoR5isakYIeOmXa4Xtjy+lx33XV69NFH1djYqIkTJ0qS3nnnHa1YsULPP/+8JCkUCkmSnnjiCd17770yDENr1qzRxx9/rMbGRq1evVrBYFCvv/66JOnDDz/U+vXrdcopp0iSuru7tX79ep1++um65ZZbZNu2nnjiCb300kt6//33t3vf0tJSHXnkkTJNU+PGjVN3d7ck6c4779S1116reDyuyy+/XE1NTRl7GYF8EQ6HFQqFGIcDAGAHCJcgES4B7lq7SQOxmDaEgnrwf57Vff92s5RISGs3qampSVdddZXuuusuzZ07VwUFBSoqKtIvf/nL1MMXLVqkX/3qV4pEIpo9e7ZuuukmSZLjOJzgt6ctIH2wfvA1lqSoJfUPDN7eUKOqqip1dHQMee0OO+wwTZ06Vd/4xjckKbUj3y233KL3339fhYWFOuGEE+Q4Tur+V155pb70pS/p0Ucf1X777aepU6fqhRdekNfrVSKRSP39NDc369Zbb9WBBx6osrKyHd73lVdekWEYw57O0UcfraamJm3cuFFnnHGGVq1aleYXEMgvtm2zOxwAALtAuASJcAlwVzQmOzG4aPR//+inKvIWpG4vLCzU+++/L0k69dRThz104cKFWrhw4XYPu27dOjU2Nkoa7JzZnubm5tS4XN5Yu0lKJNQ90K/uSES2k9Cqv7+n5tM+J6esRGVlZXr44YeHrF906aWX6pprrtGcOXMkSdOnT9cPf/hDfeELX9AJJ5yggw8+WKWlpUO+zVlnnaWCggKdffbZeuyxx/T1r39dJ554okzTVEFBgR588EGNGTNGF154oY477jg988wzkqSamprt3ndHzj//fHV2dmpgYEBXXXVVGl4wIL91dHTItm01NjZuN+AFAACD4VJhYaHbZcBlRvLT9lwyffp0Z+XKlW6XAeza639TKBRSa1+P9quqVUEy8S/0STMP36tDzpkzR4cccojuvvvuESw0RywbPC+0h3sVGojogJr6f3xt9nSXigKQaYsXL5YkzZ8/f4f3CYfD2rhxo2pqauhaAgBgJz744ANVVVWprq7O7VKQAYZhrHIcZ9jFE51LgJumNCr+1uD6OWZykViPR5rSuNeHXLp06UhUlpsKfVI0Jsu2VeAxh94OAFskx+EKCwvZHQ4AgJ3YeskH5De2PAHc1FAje+IYeXwF8hjGYMhx4CSpgYuZtJjSKHk8g+FS8hfgPoZ5AHJPchxuzJgxjMMBALATtj24xAfhEuhcAlxmV5bJPGyqtN9+bpeS+7aEdtYbnSoxzcEwb0ojYR6AlL6+PoVCIdXU1KioqMjtcgAAyGqES0giXAJcZtu2vF7+KWZKvKZCiQMnqaC+XqqqcrscAFnEtm21tbUxDgcAwG4iXEISY3GAy+LxOCfjDLIsS5Lk87HOEoCh2tvbGYcDAGAPxONxSYRLIFwCXGfbNifjDEqGSwUFBS5XAiCb9PX1qaenR9XV1YzDAQCwm+hcQhLhEuAyxuIyi3AJwLYYhwMAYO/Yti3DMAiXQLgEuMm2bbbuzLBYLCav18vIC4CU5Djc2LFjOTcAALAHmMJAEuES4CLaSDPPsizWWwKQ0tvbq56eHtXU1KiwsNDtcgAAGFUIl5BEuAS4iHAp8yzLYiQOgKR/jMMVFRWpurra7XIAABh1WOIDSYRLgIuSuytwQs6MRCKheDxOuARAktTW1qZEIsHucAAA7CU6l5BEuAS4iM6lzGIxbwBJtm2rt7eXcTgAAPYB4RKSCJcAFxEuZRbhEgBJchxHsViMcTgAAPaB4ziES0hxNVwyDONzhmH83TCMDw3DuH47Xz/YMIzXDMOIGobxr27UCKRT8mTMOEZmEC4BkP5xLmAcDgCAvccH5diaawu9GIZhSvqZpFMkbZT0pmEYTzmO879b3S0o6VpJ/+JCiUDaxeNxTsYZZFmWPB4Pa1wBeaynp0fxeFw+n49xOAAA9gHhErbmZufSDEkfOo6zxnGcmKRHJZ2x9R0cx2l3HOdNSZYbBQLpRhtpZrFTHJDf4vG42tvbZZomITMAAPsouTkR1zOQ3A2XGiV9vNWfN265ba8YhnGZYRgrDcNY2dHRsc/FAZlAuJRZhEtAfkvuDufz+dwuBQCAUY/OJWzNzXBpe4scOHt7MMdx7nEcZ7rjONPr6ur2oSwgc+LxOJ+eZ1AsFiNcAvJUT0+P+vr6VFtbyzpLAACMgGS4xPUMJHfDpY2SJmz15/GSPnGpFiDjHMdRIpEg6c+QeDwux3HoWADyUHIcrri4WFVVVW6XAwBATkiGSx4Pm9DD3XDpTUkHGIYxxTAMn6R5kp5ysR4goxKJhBzHIVzKEHaKA/JXchyO3eEAABg57HyNrbnWv+Y4TtwwjKsl/bckU9KvHcdZbRjGFVu+/gvDMMZIWimpXFLCMIyvSzrUcZwet+oGRgoL4GUW4RKQn5LjcPX19XQuAgAwglg/FltzdTjScZxnJT27zW2/2Or/WzU4LgfkHGaUMysWi0kiXALyydbjcJWVlW6XAwBATiFcwtYYjgRcwu4KmZXcKY62XSB/tLa2ynEcxuEAAEgD27b5oBwphEuASxiLy6xkuAQgP4RCIYXDYdXW1jIOBwBAGsTjca5lkEK4BLiEzqXMIlwC8kdyHK6kpIRxOAAA0oCdr7EtwiXAJeyukDmJRELxeJxwCcgTra2tksQ4HAAAacLO19gW4RLgEhbAyxx2igPyR3Icrq6ujn/zAACkCVMY2BarbwEuicfjLICXIYRLQH6wLCs1DldRUeF2OQAAZNRTgY91xyertdmKaGxBsRaMO0xzayak5XsRLmFbXNkCLrFtW4WFhW6XkReS4RKL+gK5ra2tTRLjcACA/PNU4GPdvOGvGnAGQ59PrIhu3vBXSUpLwJTcnIgPy5HEWBzgEsbiMseyLHk8Hl5vIId1d3czDgcAyFt3fLJaA46t4khMtcGwPImEBhxbd3yyOi3fj84lbItwCXCB4ziybZukP0PYKQ7IbZZlqaOjQ36/n93hAAB5abMVkRxHFX1ReRxHiS0dvJutSFq+H+EStkW4BLiAk3FmxWIxwiUgRzmOk9odrqGhweVqAABwx9iCYpX2x2TajkKlRdKWcGlsQXFavp9t2/J4PIyhI4VwCXAB4VLmOI4jy7JYbwnIUaFQSP39/aqvrydEBgDkra83HKya/rgGCr2KFg5ORxQZphaMOywt348lPrAtZnIAFyQXwOOEnH62bctxHC46gRy09Tgcu8MBAPLZ8SpRb80ULfZ0K6hYRnaLY4kPbI2fBsAFyc4lTsjpl9wpjnAJyC2MwwEAMCgWi6m7u1unjN9f548Zk5HvGY/HeX+NIRiLA1zAWFzmxGIxSYRLQK7p7u5mHA4AAEmdnZ0yDEO1tbUZ+56MxWFbhEuAC2zblmEY8nj4J5hudC4BuScWi6mzs5NxOABA3otEIurt7VUpRxwTAAAgAElEQVRVVVVGpyIIl7AtrmwBF8TjcZmmye4KGWBZlgoKCnitgRyRHIczDENjMtT6DwBAturo6JDX61V1dXXGvmcikZDjOIRLGIJwCXABSX/mJMMlALmhu7tbkUhEdXV1rFsHAMhrfX19ikQiqqmpyehEBEt8YHsIlwAXEC5lDuESkDuS43ClpaWMwwEA8prjOOro6JDP58v470R2vsb28JEf4ALbtlVUVOR2GTkvkUiwkwUwyr2xpk1PvrVGwb4B1RgRzZpcp8/OmuJ2WQAAuCoUCikWi6mxsTHjyz+w8zW2h84lwAXJNZeQXizmDYxub6xp05LX/65gOCozPqC+cL/+tKZLf9kQcLs0AABck0gk1NnZqZKSEpWWlmb8+zMWh+0hXAIyzHEcJRIJTsYZkAyXfD6fy5UA2BtPvrVGMTshI2HLa/XLNn2Kenx68q01bpcGAIBrgsGgbNtWXV2dK9+fcAnbQ7gEZBgn48yhcwkY3YLh6Jb/c5TweBUvLN3mdgAA8otlWQoGgyovL3dtmQ3btmUYRkYXEUf246cByLDkAnjMKKefZVnyeDwEecAoVe0vlCQ5Hq+sogrJ8Ay5HQCAfBMIDI6G19bWulYDmxNhewiXgAyjcylzYrEYXUvAKHbGkfvJZw59q+IzPTrjyP1cqggAAPdEo1GFQiFVVVW5+h7Xtm0+KMcw/EQAGUa4lDmWZamwkA4HYLSasV+DpMG1l4LhqKr9hTrjyP1StwMAkE86Ojpkmqaqq6tdrYPNibA9hEtAhiXH4jghp5fjOLIsy5UdNACMnBn7NRAmAQDyXjgcVjgcVn19vevXEbZts2EOhmEsDsiw5AJ4bv9SyHW2bctxHMbiAAAAMKo5jqOOjg4VFBSosrLS7XJYcwnbRbgEZBgn48xgpzgAAADkgp6eHkWjUdXV1ckwDFdrcRxHiUSC6xkMQ7gEZBjhUmbEYjFJhEsAAAAYvRKJhDo7O1VUVKSysjK3y2H9WOwQ4RKQYfF4nN0VMsCyLBmGQbgEAACAUaurq0vxeFz19fVulyLpH+vHcj2DbREuARlG51JmWJYlr9freuswAAAAsDfi8biCwaBKS0tVXFzsdjmS6FzCjhEuARlGuJQZlmXRtQQAAIBRKxAIyHEc1dXVuV1KCuESdoRwCcigRCKhRCJBG2kGEC4BAABgtIrFYgqFQqqoqJDP53O7nBTCJewI4RKQQZyMMyORSCgejxMuAQAAYFTq6OiQYRiqqalxu5QhbNuWYRjyeIgSMBQ/EUAGES5lhmVZkpRVn/IAAAAAuyMSiaivr0/V1dVZN/GQXOKDdU2xLcIlIIOSuysQLqVXMlyicwkAAACjTUdHh7xer6qqqtwuZZh4PM61DLaLcAnIoGTnUrZ9ApFrCJcAAAAwGvX29ioSiai2tjYrR8/YnAg7kn0/rUAOYywuM2KxmEzT5HUGAADAqOE4jjo6OlRYWKjy8nK3y9kuwiXsCOESkEG2bcvj8WTlpxC5hJ3iAAAAMNp0d3fLsizV1dVl7ZpGhEvYEa5wgQxiRjkzCJcAAAAwmti2rUAgIL/fL7/f73Y52+U4DuESdohwCcggTsbp5zgO4RIAAABGlWAwKNu2VVdX53YpO8QSH9gZwiUggwiX0s+2bTmOQ7gEAACAUcGyLHV1damiokKFhYVul7NDbE6EnSFcAjLItm1OxmkWi8UksVMcAAAARofOzk5JUm1trcuV7BydS9gZwiUgg1hzKf0sy5JEuAQAAIDsNzAwoJ6eHlVXV2f9h9CES9gZwiUgQxKJhBzH4WScZpZladOmTSosLNSTTz6Zun3q1Kl68MEHddxxx+kzn/mM5s2bp2g0Kkn6+te/rpkzZ2rmzJn6wQ9+kHpMc3OzZs2apebmZl1zzTUZfy4AAADIbR0dHTJNU1VVVW6XskuES9gZwiUgQ+LxuCROxulmWZa8Xq8OPvhg3XrrrXIcJ/W1pqYmrVixQi+//LImTpyohx56SJJ01VVX6fXXX9eKFSv05JNP6qOPPko95vHHH1dLS4t+8pOfZPy5AAAAIHf19fWpv79fNTU1o+IagXAJO0O4BGQIC+BlRjJcamxs1NFHHz2ke2m//fZL/TL0+Xypv4sDDjhAkuTxeGSaZuo+hmFo3rx5OvHEE/XSSy9l+JkAAAAgVzmOo46ODvl8PlVWVrpdzm5Jbk5kGIbbpSALcZULZAhJf2bEYrHUeks33nijvvjFL+qMM84Ycp/33ntPzz77rFasWDHk9t/85jfaf//9NXnyZEmDXUu1tbX6+OOPdfLJJ2vlypUqKyvLyPMAAABA7gqFQorFYho3btyoCWvY+Ro7Q7gEZAjhUpq1BZT46GPZmzepYGBAilkaP368jjnmGP3hD39I3W3jxo2aP3++Hn/8cRUVFaVuf+GFF/TAAw/oj3/8Y+q25I4dEyZM0BFHHKEPP/xQRx11VOaeEwAAAHJOIpFQIBBQcXHxqPrgks2JsDOES0CGsOZSGrUFpA/Wqz8SUdy25bUTUv+A1BbQDTfcoC9+8YuSBrd5Peuss/Tzn/9c+++/f+rhf/7zn3XLLbfoueeeU3FxsaTBVuXe3l6Vl5ert7dX77zzjiZNmuTK0wMAAEDuCAaDisfjGjdunNul7BHbttmRGTvEmktAhti2LY/HI4+Hf3Yjbu0mObatdd2d6o0NqMBjSo4jrd2k8ePHa/r06ZKkhQsXatOmTVqwYIGam5t13333SZIuvvhi9fb26l/+5V/U3NysVatWKR6Pa86cOWpqatLJJ5+shQsXqrq62s1nCQAAgFEuHo+rq6tLZWVlqQ81RwvG4rAzdC4BGcLJOI2iMRmGoQLTqwE7rP0nTNILd9wtRWOSpJ/+9Kepu279/0nvvvvudg+7atWq9NQLAACAvNTZ2SnHcVLLL4wmXM9gZ2ihADLEtm12ikuXQp8kbelYkqxEYsjtAAAAgNui0ah6enpUWVkpn290vU+1bVuO43A9gx0iXAIyhAXw0mhKo+TxyLvlv7AVlTyewdsBAACALNDR0SGPx6Oamhq3S9ljbE6EXSFcAjKENtI0aqiRDpwk22uqpMCnsBLSgZMGbwcAAABc1t/fr3A4rOrq6lF5TUC4hF2hpw3IEMbi0supr5Z1wASVGRM1kEjIrq0Uv/oAAADgNsdx1NHRoYKCAlVVVbldzl4hXMKu0LkEZEByRpmTcfrE43E5jqPy8nJJUjgcdrkiAAAAQOrt7dXAwIBqa2tlGIbb5ewVwiXsCuESkAGcjNPPsixJkt/vl2mahEsAAABwneM46uzsVFFRkcrKytwuZ69xPYNdIVwCMoCTcfolw6XCwkL5/X6Fw2E5juNyVQAAAMhnXV1dsixLdXV1o7ZrSRq8nvF4PPJ4iBCwffxkABkQj8cliTWX0siyLBmGIa/XK7/fL9u2FY1G3S4LAAAAecq2bQWDQfn9fpWUlLhdzj5h52vsCuESkAF0LqWfZVnyer0yDEN+v18S6y4BAADAPYFAQIlEQnV1dW6Xss/Y+Rq7QrgEZADhUvrFYjH5fD5Jg69zcXEx4RIAAABcEYvF1N3drfLychUWFrpdzj4jXMKuEC4BGZA8GY/mOetsZ1mWCgoKUn/2+/2KRCKpYA8AAADIlM7OThmGodraWrdLGRGES9gVwiUgA5hRTq9EIiHbtoeFSxKjcQAAAMisSCSi3t5eVVVV5cyaq4RL2BXCJSADOBmnV3KnuK3DpcLCQpmmSbgEAACAjOro6JDX61V1dbXbpYyIRCKhRCLB9Qx2inAJyADCpfTaXriUXNg7HA7LcRy3SgMAAEAe6evrUyQSUU1NjTye3LjcTi4zkStdWEiP3PhpB7JcPB7nZJxGsVhM0tBwSRocjbNtW9Fo1I2yAAAAkEccx1FHR4d8Pp8qKircLmfEsDkRdgfhEpBmjuPQRppmlmXJNM1hrzHrLgEAACBTQqGQYrGY6urqcmojH8Il7A7CJSDNEomEHMfhZJxG2+4Ul2SapoqKigiXgN309ttv67TTTtPs2bPV1NSkSy+9NDV2uq358+dr+fLlw26fOnVqussEACDrJBIJdXZ2qqSkRKWlpW6XM6IIl7A7mNMB0iwej0viZJxOlmWpsLBwu1/z+/0KBoOsewXsQigU0vnnn6/f//732n///SVJy5cvH7YTIwAAGC75frOurs7tUkYc4RJ2B51LQJqxAF56OY6zw84laTBcchyH7iVgF5555hnNnTs3FSxJUlNTkwoLC3X55ZerqalJxx9/vN54441hj/23f/s3zZo1S1dcccUOO50AAMhVlmUpGAyqvLxcRUVFbpcz4mzblmEYhEvYKa52gTQj6U+veDwux3Hk8/m2+/WioiKZpqlwOKzy8vIMVwdkubaAtHaTFI3p41ff0ITJEyQNbqF89tlnq7OzU/Pnz5dlWVq+fLnWrFmjefPmDQmY/vrXv+qdd97Ra6+9pnXr1um+++5z69kAAOCKQCAgSaqtrXW5kvSIx+Ncy2CX6FwC0oxwKb2SXRI76lwyDEN+v1/9/f1yHCeTpQHZrS0gfbBeisbkOI4mVNfq43fek9oCqqurU0tLi6ZPn65wOKzjjz9ekrTffvupq6tryGE++OADHXvssZKkyZMnq6GhIeNPBQAAt0SjUYVCIVVVVeXsGDnLS2B3EC4BacaaS+m1q3BJGhyNi8fjikajmSoLyH5rN0mJhHqjA1rbFdBnZ8zSU68u05rlf07dJflJ5YoVKyRJa9asUWVl5ZDDHHDAAVq1apUkacOGDWpra8vccwAAwGUdHR0yTVPV1dVul5I2mQqXWlpadMkll6T+vHHjRjU3N2v58uU64YQT1NzcrJkzZ6qlpUWSNGXKFM2ZMyf1taeffjr12OQGI4sXL9aUKVPU3Nys448/Xm+99ZbOP/98PfHEE6n7nn766dvdpKSnp0fHH3+8mpubNWPGDL344ot7/dxaW1v1zW9+c68fv6fWrVunk08+OWPfT2IsDki75Mk4l7YjzSaWZckwjJ2uaVVSUiJJCofDOTkHD+yVaEyS5DO9iju2okaBHrxhob522/cUues2FRcXa+LEibryyit1/fXXq6mpSbZt6yc/+cmQwxx99NE65JBDNGvWLH3qU5/SuHHj3Hg2AABkXDgcVjgcVn19fU5/kGzb9g43z8mEb37zm3r88cc1ceJE2batvr4+SYMf3i9dulSS9Mknn+j000/XAQccoIMOOmjI4y+++GLdfPPNWrFiha6//no99NBDOumkk3TKKafo+eef14QJE9TU1DTs+5aWlurll1+W1+vVmjVr9OUvf1lvvvnmXj2HMWPG6Ec/+tFePXa0IFwC0ow20vRKLua9s/DO6/WqqKhI4XBYNTU1GawOyGKFPikaU6HXq5pivzr7w5o6cZKev+uX0szDh9z1V7/61bCHL168OPX/uf5mCQCAbTmOo46ODhUUFAzr6s01bl/P+P1+Pf/88zrnnHNUVlamioqKYfcZN26crrrqKv3Xf/2Xbrrppu0e5+ijj9b69etVW1urBQsW6LrrrtPbb7+tl156abv393g88ngGh716enp0+OGD748WL16sZ599VvF4XO+//77uuOMO3X333froo49011136aSTThp2rHXr1umSSy7RCy+8oIULF2rdunUKBoPatGmTvvvd7+qnP/2pNm7cqIcffljTpk1Tc3Ozjj76aP3tb39TUVGRHn30Ud1///1yHEfXXnutJOmoo47SK6+8onfffVcLFiyQx+PRtGnTdPfdd+/V67yvGIsD0iwej7NTXBrFYrHdmm/3+/0aGBhIrYEF5L0pjdKWN0zVxX4Ver1qi4RlTxzjcmEAAGS/np4eRaNR1dXV5fSEguM4sm3b1euZ+++/XytXrtRRRx2lU045RevXr9/u/SZMmKBNmzbt8Dj/8z//o8MOO0ySdOGFF+r//u//dO211+40HNy0aZOampp06qmn6swzz0zdHo/H9bvf/U7f/va3deONN+r3v/+9lixZorvuumu3nlN1dbWeeuopnX322brnnnv03HPP6Xvf+57uvffe1H2OPfZYvfDCC5o1a5buvfdenXPOOXrkkUckSa+//rqmTZum0tJSXX311XrooYe0fPlyRaNR/fGPf9ytGkYa4RKQZm4n/bku2bm0K36/X47jqL+/PwNVAaNAQ4104CSp0CfDMDSmulb22Bp1eBJuVwYAQFZLJBLq7OxUUVGRysrK3C4nrdK+OVFbQHr9b9KylSr+aKMGukOpLw0MDKi4uFiTJk3SPffcow8//FBf+cpX9O1vf3u7h/r444/V2Ng47Pb77rtPzc3N+vWvf63bb789dfvUqVNTazPtSGNjo5YvX6433nhDV199der2o446SpI0fvx4TZs2TaZpavz48QoGg7v1tLd+/JFHHpn6/60fP2PGDEnScccdp7///e+qra1VQ0ODVq9erd/85je68MILJUmhUEj77befJOn444/X+++/v1s1jDTCJSDNCJfSx7Zt2ba9W+FSUVGRTNNUOBzOQGXAKNFQMzgCN3u6ij59jKqmTFIoFOLfCQAAO9HV1aV4PK76+nq3S0m7tIZLW3au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2d/f1NQU7rrrrrI//lSy2SyklIjFYms+9+DBg7jqqqs8/fp+4q6XqIrcmUvkPcMwEAwGy75boWla4YKB/42I1tbR0YGlpSXMzs6ira2t5GH6REREjcQ0TczPz6Ozs7Pk04mbhXvy9amut++66y488cQT2LBhA2zbRiqVApC/IfX8888DAI4ePYprrrkGZ511Fs4555wTPv7WW2/FJz7xCbz00ku455578PWvfx3vete7cPXVV+PZZ5/F+vXrcfnll6/4uh0dHXj55ZeRTqexd+9e3HHHHfjpT3+KaDRa8vc3ODiIBx98sOSPOx3btpHJZCCEKGs9jY6VS0RVIqWE4zgMLqrENM2yWuJcmqZBSolMJuPhqoiaWzweh6IoSCQSHIhPRERNbXZ2FgDQ19fn80r844ZLpzohT9M0PPvss1haWoKqqujs7FzxnKGhIfzpn/4pnnzyydN+ja1bt+LQoUPo6+vDnXfeidtvvx2f/exn8bnPfe6Uz1cUBYFAAJ2dnWhvb8e5556LI0eO4G//9m9x44034oYbbsBb3vIWPPvss7juuutw3nnn4bnnnjvl51peOXT//ffj5ptvxnXXXYeLL74Y3/nOd/Dud78b559/Pl599VUAwNjYGO68805cddVVuPbaawuB2saNG/GhD30I119/PRYXF/GpT30K73rXu3D55Zdjz549AICHHnoIV155JS699FJ8+tOfPu3Po5ExXCKqEvfFmOFSdVQaLkUiEaiqytY4ohIEAgH09/cjk8lgYWHB7+UQERFVRTabxeLiInp6elpzxEViDtj9S9g/+TlwZAoia6x4yte+9jXs3bsXF110Ea6++mocOnTolJ9q/fr1mJiYOO2X+v73v4/zzjsPAPAnf/In+I//+A/82Z/9Gbq6uk77MRMTE7j88stx7bXX4qabbkJbWxuWlpawtLSEJ554Ap/61Kdw33334dvf/jYef/xxPPzww0V92z09PXj66afx3ve+F4888gi++93v4oEHHsCjjz5aeM6ll16KH/7wh3jb295WeHxychL33HMP/vVf/xX/8A//gM2bN+P555/HU089VWgp/OAHP4gf/ehH+OlPf4of/OAHOHz4cFFraiQt+C+FqDYsywKA1nxDqjLbtmHbdkXhkhACsViM4RJRiTo7O7G0tISZmRlomlbRv0MiIqJ6NDMzA1VV0dPT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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "fig = plt.figure(figsize=(20, 20))\n", "\n", "for (_, col1), (_, col2) in zip(components_full.iteritems(), components_2020.iteritems()):\n", " plt.scatter(col2[comp1], col2[comp2], color='pink')\n", " plt.scatter(col1[comp1], col1[comp2], color=color_map[asset_map[col2.name]])\n", " plt.plot((col2[comp1], col1[comp1]), (col2[comp2], col1[comp2]), color='grey', alpha=0.3)\n", " plt.text(col2[comp1], col2[comp2], col1.name, fontsize=9)\n", "\n", "plt.axhline(0, color='grey')\n", "plt.axvline(0, color='grey')\n", "\n", "plt.title('PC1 vs PC2 - Full History vs 2020 Data only')\n", "plt.xlabel('PC'+str(comp1+1))\n", "plt.ylabel('PC'+str(comp2+1))" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "PC1_f, PC2_f = components_full.iloc[0].abs(), components_full.iloc[1].abs()\n", "PC1_2020, PC2_2020 = components_2020.iloc[0].abs(), components_2020.iloc[1].abs()\n", "\n", "plt.subplot(2, 2, 1)\n", "(PC1_f/PC2_f).sort_values(ascending=False).head(15).plot(kind='barh', title='PC1/PC2 - full', figsize=(16, 8))\n", "plt.subplot(2, 2, 2)\n", "(PC2_f/PC1_f).sort_values(ascending=False).head(15).plot(kind='barh', title='PC2/PC1 - full', figsize=(16, 8))\n", "plt.subplot(2, 2, 3)\n", "(PC1_2020/PC2_2020).sort_values(ascending=False).head(15).plot(kind='barh', title='PC1/PC2 - 2020', figsize=(16, 8))\n", "plt.subplot(2, 2, 4)\n", "(PC2_2020/PC1_2020).sort_values(ascending=False).head(15).plot(kind='barh', title='PC2/PC1 - 2020', figsize=(16, 8))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4: Realized vs modeled\n", "\n", "Let’s now look at where the 3 component PCA model would tell us the returns for several series of interest should be vs where they have realized. The series with the largest model deviations present potential dislocations." ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "pca_returns = pd.DataFrame(full_model.transform(full_data), index=full_data.index)\n", "res = pca_returns.dot(components_full) * frame_t.std() + frame_t.mean()" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [], "source": [ "def transform(tr_df, since):\n", " t_d = tr_df.copy()[since:]\n", " for key, v in t_d.items():\n", " t_d[key] = (1 + t_d[key] / 100).cumprod() if key in instruments['Rates'] else t_d[key].cumsum()\n", " return t_d\n", "\n", "def compare_plot(asset, actual, predicted):\n", " plt.figure(figsize=(10, 4))\n", " plt.plot(actual[asset], label='actual')\n", " plt.plot(predicted[asset], label='predicted')\n", " plt.title(asset)\n", " plt.legend()\n", " plt.show()" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAmIAAAEICAYAAAD80ZhHAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8li6FKAAAgAElEQVR4nOzdd3iUVfbA8e+d9N4LJCSBFDqhdwQFLAgW7GDX1cWuu79dXV3XtffeXSuKih0VUUB674SWRhJSSO+9zPv74yYQID0TUjif55lnwsz7vnMHEubk3nPPUYZhIIQQQgghTj9TZw9ACCGEEOJMJYGYEEIIIUQnkUBMCCGEEKKTSCAmhBBCCNFJJBATQgghhOgkEogJIYQQQnQSCcSEEEIIITqJBGJCiG5NKbVaKZWnlLI76bFbTzpumlIqpd6fDaVUiVKqWCmVo5RaqZS66qRzPlFKPVn7dUjtOcW1t0Sl1INKW6uUevSkc29QSsUrpRw75p0LIXoCCcSEEN2WUioEmAIYwEVtuESkYRjOQH/gE+BNpdR/mjnHvfaca4BHgfOAW4AHlFKDa8flA7wI3GoYRmkbxiWEOENIICaE6M6uBzajg6gb2noRwzCyDcNYCCwAHlJKebXgnE3AfmCIYRixwFPAh0opE/A68J1hGKvaOiYhxJlBAjEhRHd2PfBF7e08pZRfO6/3E2ANjG3qoNrlyEnAYGBX7cMvAwr4FpgE/F87xyKEOANYd/YAhBCiLZRSk4FgYLFhGNlKqXhgHvBKW69pGEaVUiob8GzisGz0Umg68KBhGCtrz61RSt0M7AMuMQyjqK3jEEKcOSQQE0J0VzcAfxiGkV3750W1j70CVAM2Jx1vA1Q1dUGllA3gA+Q2cZi3YRjVDT1hGMZ+pRToJUshhGiWBGJCiG5HKeUAXAlYKaXSax+2A9yVUpHAESDkpNP6AknNXPpidBC31XKjFUKIxkkgJoToji4BaoChQGW9xxej88a+BhYqpb4DtgHhwP3Aaw1dTCnlCVyAzvN6zjCMnI4buhBCHCeBmBCiO7oB+NgwjCP1H1RKvYnesfhP4EHgY6APkAn8D3j/pOvsUUoZ6GBuD3C/YRiLTjrGsPzwhRBCU4Yh/8cIIURDlFLfA2sNw3i1s8cihOiZpHyFEEI0QCkVAEwGtnf2WIQQPZcEYkIIcRKl1B3o+mAfG4axvrPHI4TouWRpUgghhBCik8iMmBBCCCFEJ+mWuya9vb2NkJCQzh6GEEIIIUSzduzYkW0Yhk9Dz3XLQCwkJITt2yV/VgghhBBdn1Kq0WLSsjQphBBCCNFJJBATQgghhOgkEogJIYQQQnQSCcSEEEIIITqJBGJCCCGEEJ1EAjEhhBBCiE4igZgQQgghRCeRQEwIIYQQ3U5WUQWLthyhorqms4fSLhKICSGEEKJbKaus4eZPtvGvH6KY98EWsooqOntIbSaBmBBCCCG6DcMw+Pu3e9iXVsDtZ/Vjf1oBF725vtsGYxKICSGEEKLbeOPPOH7de5QHzx/AQ7MGsvj2CeQUV/LELwc6e2htIoGYEEIIIbqF36KO8vLyGOaODOC2s/oBMCzQnQXTQlmyJ43V0ZmdPMLWk0BMCCGEEF3evtQCHli8h5FB7jx96VCUUseeu+PsUPr5OPHvn/ZRXtW9kvclEBNCCCFEl5ZZVM5fPtuOh6MN7103GnsbqxOet7O24pELB5KcW8b62OxOGmXbSCAmhBBCiC6rvKqG2xfuIL+0ig9uGI2Pi12Dx00O88HFzpqVhzJO8wjbRwIxIYQQQnRJJRXV3L5wB7uO5PPKVZEM7u3W6LG21ibOivBh5cFMDMMgu7iCp5ceJKe4a++mlEBMCCGEEF1OTnEFV7+/mfVx2Tx32VDOH9Kr2XPOGeBLZlEF+9MKeemPaN5fe5j7vt5Njdk4DSNuGwnEhBBCCNHlvLsmnkPphXxw/SiuGhPUonOm9fdBKfjfusMs3p5Cfz8X1sVm89aquA4ebdtJICaEEEKILmdrQi4jgzw4Z4Bfi8/xcrZjRB93ftydhq2ViYW3juXi4b15dUUMG+O7ZhK/BGJCCCGE6FJKK6vZl1bI6BCPVp87faAO3G6eHIKviz1PXzqUEG8n7vlyN5lF5ZYeartJICaEEEKILmV3cj41ZoPRwZ6tPveKUYHMHxfE7VNDAXCys+bt+SMprqji3i+7Xr6YBGJCCCGE6FJ2JOahFIwMav2MmK+rPU9dOhRXe5tjjw3wd+Xxi4aw6XAOr62MteRQ200CMSGEEEJ0KduS8ojwdcHN0ab5g1voitGBzB0ZwBt/xrIuNsti120vCcSEEEII0WXUmA12JeUxqg35YU1RSvHkJUMI83Hmvq92k1HYNfLFJBDrasw1YHSt9WshhBDidInJKKKoopoxFg7EABxtdb5YaWUNd3+5i+oas8Vfo7UkEOsqijLg94fh6QB4ewJs/xgqSzt7VEIIIcRpYxgGS6OOArQpUb8lwv1cePKSIWxNyOWVFTEd8hqtIYFYV5C0Ed4cDZvfhv4XgJU1/HIfvDwQlj8K+Uc6e4RCCCEsaOGmRG79dFtnD6NZZrPBC78fYsWBju/fGJ1exJw31/PGn3GMCfEg0MOhw17rslGBXDk6kLdWxbM6OrPDXqclrDv11c90ZjPELYfFN4B7H7h6EXiH66XJI5thyzuw8U3Y+AYMuBDGLYDgiaBUZ49cCCFEGyVml/DkrwepqDaTnFtKH0/Hzh5So/63/jBvrYrHpOClKyOZ0M+br7clk1FUjpVSmBSYTAorpbAyKQb2cmXW0F7YWrd+nuelP6JJzi3jpSsiuXh4b1QHf9b996IhVFSbCfZy6tDXaY4EYqdLYRrELIP4P6E4E8ryIC8JairAfyhc+wM4++hjlYLgCfqWnwzbP4Qdn8DBn8FvKLgHQV4CDL4Upv6jU9+WEEKIljMMg3//tO9YKvCm+JzTGogVlFVx5xc7efCCAQwJaLyBNsDelHyeXxbNzEF+FJdX88DiPZiUwmwYeDnZYTYMaswGZrNBjWFQXWNQWWPmqaUHuWp0H2ZH9qK/n0uLAqqqGjMb43O4aHhvLhsVaKm32yQHWyteu3rEaXmtpkgg1lEMA9KjIPo3iF4KR3frx92DwKMv+AyA8HPBO0IHVPauDV/HvQ/MeAym/hP2LobtH0HuYTBZw6qnoM9Y6Dft9LwnIUSPYRgG13+0FSdbax68YAAh3p07K3Cm+DXqKOtis3lsziDeXBXHpsM5XDmmz2l7/a+2HmF9XDbf7khpMhCrMRvc99VufF3seOHyYdjbWPHYkv242Ftz3fgQgrxODR7NZoO1sVl8vCGRt1fH8eaqOGYM9OXda0dhbdX0DNnOpDyKK6qZGuHT7vfY3UggZknlhbDhVR2Ape+DojRAQeAYmP4f6D8LfPq3bWnRxgFG3aBvoBP53zsLflgACzaAY8ckNQoheqbD2SWsi9W991YeyuCmSX2565ywE4pgCstbGnWUAHcHrpsQwrakPDbF52AYRocvwwFU15j5bFMSAGtimq6jtS0xl8PZJbx29XDcHW0BePayYU2eYzIppvX3ZVp/X7KKKvhiSxKvrojlyV8P8thFg5s8d01MFtYmxcRQr1a8o55BkvUtpaoMvrwa1r8ChUf1suJFb8LfY+DW5TDlAfAdYLn8LltHuOwDKMnUuy2FEKIV1tZ+EH+3YAKXjgjgg3WHOfuF1SzacqTLtYDpSaLTixjc2xUrk2JCPy/SC8tJzDk9O+RXHMwgNb+MiaFeJGSXkJRT0uixP+9Jw8HGipmDWt5wuz4fFzvumxHBzZP68snGRL7e1vSmszUxWYwM9sDlDPxFQAKxhuz/ET48D2qqW3Z8TZVOuE/aCHM/gAXr4fKPYOR14OzbcePsPQLGL4A9X0JWdMe9Tntk7If3z9b3QoguY11sNiFejowK9uT5yyNZcudk+vk48a8forjw9XUcPFrY2UPscSqqa0jMKSXCzwWACbWzP5vic07L63+0IZEAdwcev3gIcDwYP1l1jZnf9qUzfaAvjrbtWzj716wBTArz4r8/H2i0gGpWUQX70wrPyGVJsFAgppQ6XykVrZSKU0o92MDzSin1eu3ze5VSI1t6bqcwWUPyZkhY07Lj//g3xP4OF74EQy/v2LGdbNJ9YOMIq585va/bEoYByx6CtJ2w4r+dPRohRK2K6ho2xedwVr0PvqGBbiy+fQJvzRtJRmE5zy871Ikj7JkSskuoMRuE+zkD0M/bCV8XOzYd7vhAbPPhHLYm5HLDxGBCfZwI8nRsdHlyY3wOuSWVzIns3e7XtbYy8dQlQ6muMXh+2akTBjVmg1/3pgGcsYFYu3PElFJWwFvATCAF2KaUWmIYxoF6h10AhNfexgHvAONaeO7pFz4T7Nwg6lsIm970sVHf6jIT4/4KY245PeOrz8kbxv8V1r0EZ/0f+DW9Dt9mabthy7swcI7eZGDVgunjuBU6mPUbqgPV5K16c4EQolPtTMqnrKqGKeEnfvAppbhwWC+WH0hnS0JuJ42u54pOLwKgv7+eEVNKMSHUiw1xHZsnVlFdw8M/RBHo4cC144NRSjE1wofvdqZQUV2DnbXVCcf/vCcNFztriwVGId5O3Dy5L++uiefCYf5UVpvZlZzPnuR8olIKKKmswc/VjkG9Gtm01sNZIll/LBBnGMZhAKXUV8DFQP1g6mLgM8MwDGCzUspdKdULCGnBuaeftZ0OOA4ugapXwMb+xOcriiH2D0jeAjsXQp9xMPOJzhkrwIS7YOsHsPJxmPd1x7zG8kd1ULXnS73r87ZV4NBE+4maaj1T6NkPbvwZ3hwDKx6DiPP1TlInb71z1Ke/vnmF6Q0JddJ2wZ9PwYQ7IfTsjnlPQpyh1sbqxOgJjSRG9/d35cfdaRSUVlm06fKZLjajGCuTom+9HaqTw7z5aXcaB48WMah3xwQi764+THxWCZ/cNObYUuNZET4s3JzELZ9sB6CwvIqi8mqKyqvIKank0hEB2NtYNXXZVrnrnDC+25nCzbWvZ2OlGNTLlctHBRLZx52Jod6YTGdmjUxLBGIBQHK9P6egZ72aOyaghecCoJS6DbgNICgoqH0jbomhl8Huz3XANegivcyWuhN2fgL7vofKYr0kGDQBLn4TrG07fkyNcfSEKX+DFf+BmD8g4lzLXv/oXh2EnfOIDsK+uwU2vaX/3Jgt70LWQbjyMx2wTfkbLHsQkjaA/zC9yeDQL2DU9flS4BGigzJ7d4j6BowayI2HO7e2bAZOCNEswzBYHa0To53tGv4IGFA7YxOdUcTYvrIj21JiMooI8XI8YQaqbnl4bWxWhwRih9ILeWtVHHMiezOt//Gc5UlhXkT2cSe9sBxXe2s8HG0J8nTExd4GNwcb5o+z7Oess5017183ir0pBQwLdGNQb9dTZuLOVJYIxBoKYU/ectPYMS05Vz9oGO8D7wOMHj2647f0hJwFTj6w63MoOgo7PoXM/Tr4GjwXRszXZSm6SoAw/g7YtRCW/RP6TdWzepay6U2wdYYxfwEHd11YdvM7+jUbKpuReUjPzvWfBQMv0o+NvkWPKWgC+A7Uj1VXQE48ZB2C7Bh9nxUN+eth+DX63+CH22DnZ52z7CtED/Tmn3EcPFrIE5cMafSYuqWz6PRCCcQsKCbj1FkvP1d7Bvi7sCY6i79ODW31NY/klPL4LweI8HNmTF9PRgV7HCtBUlhexYLPd+LmaMOjswedcJ6jrTU/3Tmp7W+mDUYEeTAiyPKNvLs7SwRiKUD9anSBQFoLj7Ftwbmdw8paF1rd+r7Ob+o1HGa/AkMub7z4ameytoULnofP5+rAacrfGj4u97BeNvSJaNl1C1Jg33cw9nYdhAFMexAO/AQbX4dzHoXKIigvgLJ8ff/Hw2DnDHNeO16uw9oWRt980pjtwG+QvjXEMGDHx7DmOYi8Gmyl4KQQ7fHjrlReWh7D3BEBXNvEjEcvN3tc7K05VJvTJNqvvKqGpNxSLh4ecMpzUyN8+GhDAiUV1Tg1MkvZmCd+PcCamExWR2fy9mrdimhgL1fGhHiSkF3CkdxSvvzLeHxcLPjLubAoSwRi24BwpVRfIBW4Gph30jFLgLtqc8DGAQWGYRxVSmW14NzOM/l+vaw24ELoFdnuy+1Jzufr7cnEZRSTVlDG4N6unDPAl8ScUnYk5XHV6D7ta+0QNh0GzIa1L8Kwq8Ct3rWKs2DVkzqnzd4N7t+va5E1Z/eXYK7RGwLq+A6EIZfB+lf1raFJzCsXtr90h1Iw47/w0bk6X+z8p9t3PSHOYKWV1Tz0fRRj+3ryzGVDm0wMV0oxwN/lWHK5aL+4zGIM4/hsY31TI3x4b+1hNsXnMKMVdbu2JuSy/EAGfz83gpsn92X3kXy2JuayNSGXr7YdobzKzMOzBsqsZhfX7kDMMIxqpdRdwO+AFfCRYRj7lVJ/rX3+XWApMAuIA0qBm5o6t71jsoSsogp8XHvD2f9q13VqzAbLD6Tz4foEtiXm4WRrxeAANyL7uLM9MZff92dgbVL4udrzt2/2UFlj5pqx7VibP+9peGss/PEIXPHJ8cd/uA0S1umg8uASiFoMo25s/nrJW3Tg5X7SmGY+Di7+eqnW3u3Em1sgeLV+ir1BQeP0kujmt3QwHHmVZa4rxBlm8+EcyqpquOec8Bbl5vT3d+Gn3Wmnrep7TxeToYPaiNrSFfWNCvHA0daKNTFZpwRiZrNBSWU1BWVVpOSVsTYmi7jMYiaHe/PdjhT8Xe25ZXI/HGytmBjmzcQwb0D3bkzJKyOkgVZEomuxSIsjwzCWooOt+o+9W+9rA7izped2tvfWxPPC79HsenRmm6v8FldUs3hbMh9vTCA5t4xADwf+PXsQV44OPHZNs9kgJrOI3u4O2FqZWPD5Dh76Poqyyhpunty3bYP3CIbJD8Dqp2HUTTpfrCAF4lfpJcWp/9StkTa/CyNvaLrSv2FAyja9WeFkbgFw3lNtG2Nrnf8MZB6EJXfrJdXend+kVYjuZm1MNvY2JkaHtCxHp7+/K0XlR0grKCfA3aH5E0SjDMNgy+FcbKwUwV6npljYWVsxoZ8XS/akEZ9VTGF5FQVlVRSW6V2M9RsdWJkU/q72/HEgA4DnLx+Gg+2pgbWNlemE3Zmi65Jekw0Y3sedarPBhrhszh/Sq9XnHy0o48LX15NbUsnoYA/+dcFAzh3sj9VJW3NNJsUA/+P5Zu9eN4p7vtzF478cIDGnhEdnD2q2UWqDJt0Du7+A3/4Bf10Pe74CDL1cqZSuxv/jAji8uunSEDnxUJ6vNyV0JisbvfvyrbF62fXqLzp3PEJ0Q2tispjQz6vFJQkG1EvY35ucj6+rPaOCJdG6tTKLynnouyhWHsrkosje2DTyf/q8cUGk5pdRWW3G18WeMB9n3BxscHWwwbV2J6O3iy2jQzxxtbchJqOI6PQiLhza+s8o0bVIINaAkcEeuNhZszo6q02B2PIDGeSWVPLFreOYVDtN3BJ21la8M38Uzy07xHtrD5OUU8ob80a0vgmvjQOc/yx8dQ1seU/X/gqaCJ61s2xDLtN1wba823QglrJN3weMbt3rdwQnL72TcvM7Ot/N+cyswCxEWyTllJCQXcINE4JbfE5dG55Hf9pPSl4Z4/t58tVtEzpqiD3Sb1FH+dcPUZRW1vDv2YO4aWJIo8dOH+jH9IEtzw+L8HM59m8kujfpNdkAGysTk8O9WR2dhV5VbZ21MdkEeTq2KgirYzIpHpo1kGfnDmVDXDaXv7OR5Nw2NITtfwGEzYSV/4WcOB3E1LG2g+HzIXY5lOU1fo2UbWDromt7dQXD54O5WtcYE0K0WF1PwbNaUSndzcGGAHcHUvLK8HC0Iae4sqOG1+MUlFVx/9e7WfDFTvp4OvLrPZO5ZXLfM7ZgqWiaBGKNOLu/L+mF5a3evl1VY2bz4Rwmh7c+CKvv6rFBfHrzWNILyrn07Q3sPNJEwNQQpeCC5/TX1vYw6OITnx84RxdMjfmj8WukboeAkWDqIkX3fAdC75F62bUNAbIQZ6o1Mdn08XRodc7Qs5cN5bObx3LhsF7klPSsQCyvpJLSymoAKqvNxGQUUVltbuasU8VlFrMuNovyqhqKK6r5cVcq57+6liV70rhvRjjfLZhImK/MXInGydJkI6b21785ro7OYmAr+l/tTs6nuKKas9oZiAFMCvPm+zsmccun27j6/c28eEUkF7WmCatXqG5EXlWmdzPW13skOPtD9K8N70SsLIX0fbqER1cyfB4s/Tuk77VISREheroas8Gm+GwuGRHQ6t2Pdb0odyTlkVdaSXWNuW15q11IQWkVr6yIYeHmJMyGQYC7A1lFFVRUmxkZ5M7CW8a1uJbX8gMZ3LloJ5XVZuysTRgGVNaYCfN15ofrRjEs0L2D343oCSQQa4Sfqz0De7my6lAmV44OJKOwgsyicjJr72vMcMfZoackXq6LzcakYEJo+wMxQP9A3zGJvy7cwT1f7iIhq4Q5kb3YdDiHyEB3hgS4NX2Bkdc3/LjJpJcvo76BqvJT+2ke3a1nzDo7Uf9kQy+H3/8Fu76QQEyIFsgpqaCksqbB+lUt5e1ih2FAbmklvi72zZ/QBdWYDb7ceoSX/oimoKyKq8YE4e9qT2xmEX6u9ng62fLy8hhu/XQ7H980ptFNDetjs9mTkk9eSSUfb0xkSIAbd58dxubDOSgFMwf5MyrY45TNWUI0RgKxJpzd34e3V8cz6skVDT4/LNCNswecWLR0XWwWkX3ccXOwXOsjTydbFt46loe+i+KVFTG8siIGQG9hfuCs1ifz1xlwoa5cn7BW96c0DN1qKOZ3XWcMILALJOrXV1dgN2oxnPuEZVs5CdEDZRZWALQrgPJ20r10c4q7ZyC2+XAOjy3Zz6H0Isb19eQ/cwY32NcxwN2B+xfv5tnfDvHYRYNPeK66xszzv0fz/trDxx6b1t+HN+eNxNnOulWFWIWoTwKxJtw4MQRbaxPuDjb4utrj52qHr4s9rg42jH1qBetiszl7gC+Hs4q5beEO+ng4sCc5n7vODrP4WOysrXjpykimRHhTXF6Nr6s9Cz7fwTNLD/LM3GFtu2jfs3QPya3vQ9wK3copL1E/5zsYZr0ITpaZ2bOo4dfC/h8gZtmpuW9CiBNkFpUD4Ova9l9avJz1ud0tYT8lr5Rnlh7i16ijBLg78Pb8kVwwxL/RJdpLRgSwMT6br7Yd4Z7p4XjWBqDZxRXcvWgXmw7ncO34IB66YCD2NlYy6yUsQgKxJvi62nPfjIZ7Mo4J8WRDXDYAi7enkJBdgkLvuDxviH+HjEcpxaUjjrct+suUfry39jBzhvU+Vk25VaztIHymDmqs7aHvVJh4N4SfB+59mj+/s4SeDS69YPciCcSEaMbxGbH2BGK1M2IlFRYZk6UdLSjjlk+2c+uUvswdGUh5VQ1vr47nvTXxKAUPzIzgtrP6taiG2l+m9GPx9hQWbkri3hnh7E7OZ8HnO8gtqeTFKyK5vD1t6IRogARibTQpzJvnlh0is7CcX/amMSnMm89uHnta24HcPzOCZfvTefLXg/x6z+S2ve4Fz+s8sj7jW9Z7siswWekm4Bteh6IMcJElASEak1EbiLWn6bO3kz43u4vOiC2NSufA0UIeWLyHHUl5rI/LJimnlDmRvXnoggH0bkVngHA/F84Z4MtnmxLxcLLhyV8O4utqx3cLJjafkytEG3Tv7S+daErtrsi3V8eTklfGnGG68Ovp7Mlmb2PFHdNCOXC0kE3xOW27iLMvhJ7TfYKwOsPn680Ee7/u7JEI0aVlFpXj4WjTov6SjXF1sMbapMgp7pozYn8eyiDUx4nLRwXyxZYjWCnFolvH8cY1I1oVhNX5y5R+5JRU8uhP+xnXz5Of75osQZjoMBKItdGgXq64O9rw2aZEbK1MnDu4Y5Yjm3Px8AC8nW15f93h5g/uSbzDIXCs1BQTohmZRRXtTrBXSuHlbNtsjthvUUd5a1Vcu16rtYrKq9hyOJeZg/x54fJhfLdgAkvvndK2dI1a4/t5cs3YPtw/I4JPbhqLR22umBAdQQKxNjKZFJNCvTEbulq1JXdJtoa9jRU3TAhhdXQWMRmtKz7b7Q2fB1mHIG1nZ49EiC4rs7C8XYn6dbyd7chuYkZsX2oB9361mxd+j2bZvqPtfr2WWh+bTbXZ4JwBviilGBXs2eJ+mo1RSvHM3GHcOyNcEvJFh5NArB3qWhjNiezcpqvXjg/G3sbEu2viO3UclhadXkRFdU3jBwyZqzcZ7JIm4EI0xhIzYqB3TmY3Ul2/uKKau7/chaeTLQN7ufLIj/vJO02V+FceysTNwYaRQVI8VXRPEoi1wyUjevPv2YO4oA2NwS3Jw8mW6yeE8MOuVPalFnTqWCyloKyK2W+s4+lfDzZ+kL2bbtW071tdlFYIcQKz2SCrqMIyM2JOto3miD360z6Sckp49erhvHRFJPmllfz7p32YzS1LG/gt6igXvr7uWE/MljKbDVZHZzKtv0+3r/gvzlzyndsOjrbW3DK5L7bWnf/XeOfZYXg42vLELwfa1Ki8q4lOL6KqxmDR1iNNNz0fPh/KC3SrJiHECfJKK6k2G+0qXVGnsRyx73em8P3OVO4+J5zx/bwY1NuV+2dG8Mveo/zft3uprmm6f2N6QTn//G4vB48Wcv1HW3m0hQFcbkklD30fRXZxJeecVFhbiO6k8yMIYRFuDjbcPyOcLQm5/L4/o7OH027RtfluZgNeXxnb+IF9zwL3YFj7EtRUn6bRCdE91JWu8HO1zNJkWVXNsUbZAAnZJTzy4z7G9vXk7nOOF7K+Y1ooD8yM4LudKfzj272NXtMwDB76fi+VNWaW3juF6ycE89mmJNbGNj4zVl1j5tONiUx7YRXf7Uzh1sl9uXBo565KCNEeEoj1INeMDSLc15lnfjvYdG7VaVRdY2bJnjQqq5v+rfhk0emFuNhbc+PEEL7bmUJ0eiMbEUxWcN5TkLkftrxrgREL0XMcq6pviRmxem2OACqqa7j7y53YWpt47erhJywNKqW4Z3o4N0/qy/e7Uhtd0vxmRwqrorP45/kDGODvyiMXDqRKb18AACAASURBVMLb2Y6Fm5IASMop4ZEfo479/G85nMPsN9bznyX7GRroxm/3TuGR2YNkWVJ0a/Ld24NYW5l4+MKBJOWU8tnGJItee/mBDGa9to6Xl8c0vVR4ku93pnLPl7t4e3XrtrRHpxcxwN+FBdNCcbaz5tK3N/DB2sNUNbTMMWC27gaw+hkoSG3V6wjRk2UWtb/PZB1v57qirvqaz/0Wzb7UQl64PJJebg3X6rp4eG8A1td2IakvLb+MJ34+wNi+ntwwIQQAW2sT14ztw5/RmSRkl3DPV7v5fPMRZr2+jivf3cRV72+mqLyad+aP5PNbxhHu1/ZG5kJ0FRKI9TDT+vsyNcKH1/+MtVjxxfKqGh5bsp/kvFLe+DOWWa+ta/GOqC+2HgF04duknJIWnWMYBofSi+jv74K3sx2/3D2F8f28eGrpQZ5e2kDyvlJwwXNgroa3xsIXV0Di+ha/PyF6qszC9veZrFPX5ii7uJKVBzP4aEMCN04MYWYTza6HBLjh5mDDutgTAzHDMPjnd3upMQxevDwSU70SEfPGBWFSimv/t4U9yfk8eckQ5o0NIiGnhHumh7PigalcMLTXaS2eLURHkkCsB3rkwoGUVtbw6oomcqta4eMNiaTml/HedaP47OaxFFVUsy0xt9nz9qcVsCc5n79ODcXGpHhsyf4WbSQ4WlBOUXk1/Wt/2w3ycuTDG0ZzzdggFm5K4nBW8aknefaF65fAsKsgeQusfbHV71OIniazqAJXe+t219WC4zNiGYXl/GfJfgb4u/DQrAFNnmNlUkwO82ZdbNYJP/tfbUtmXWw2D80aSJDXiV09erk5cO4gP1Lzy5g9rBfXjg/miUuGsO3hGTwwMwIH2/a/FyG6EgnEeqBwPxfmjQ1i0dYjxLazyGtOcQVvr4pjxkBfJoZ6MybEE1srE9uT8po9d9GWI9hZm1gwNZT7Z0awKjqLjzckNnteXaJ+f3/XY48ppfjbuRHY21jx7G+HGj4xaBzMfhkizofsmBa9PyF6sszCCnwtkKgP4FmbI7ZwUxIpeWX87dz+LWqbNCXcm4zCCmIz9S9QKXmlPPnLASaFeTF/bFCD59w3I4I5kb154uIhFhm7EF2ZBGI91P0zI3C0teLJ2jpcldVmdiTl8uH6BJYfaPmuyq+3J1NUUc2DF+jffO1trBga6Mb2ZmbEiiuq+XFXKrOH9cbN0YYbJ4Zw7iA/Hv/lAJ9vbjp/rS4xt/9J+R/eznYsmBbKHwcy2HK4id6a3hFQmArlhS14h0L0XBlF5fhZYFkS9M++i5010RlFRPg5M72FJSMm1/blXRuThdlsHNtF+dxlw05Ykqyvv78Lb1wzQloLiTOCBGI9lKeTLfdOD2dNTBYP/xDFpOf+5LJ3NvHELwf42+LdLS60GJ1eRIC7A2G+x4Oi0cEeRKUWUF7V+M7MJbvTKKmsYd44/RuvtZWJN+eN5JwBvjzy4z4ufXsDr62IZU9y/iljiU4vopebPW6Op7aNumVyX3q52fPU0oONvwef2uWSbMsszQrRXWUWWqaqfp26PLEF00IbDaJOFujhSD8fJ77ZnsLNn25jY3wOj8weRKCHY/MnC3EGkECsB7t+QgghXo58seUIEX7OvDN/JA/PGkhheTVxDeVZNSA+q5hQX+cTHhsd4klVjcHelMar+C/amsQAf5cT2o7YWpt4e/5I/jYzArPZ4NWVMVz81gbGPLWC+7/ezU+7U8krqTyWqN8Qexsr/u+8/uxNKeDnvWkNv7hPf32fHd2i9yhET2QYtVX1LVC6oo6/mz0B7g7MHta7VefNGOhHdEYRMelF3Hl2KFeP6WOxMQnR3Vl39gBEx7G1NvHFX8ZTUFrFoN463yoxu4Snlh5kW2IuEc1s/TabDeIzSxgz1vOEx0cFewCwPSmXsX09Tzlvb0o++1ILefziwafsbLK3seLu6eHcPT2c7OIK1sVmsTo6i9XRmfywK5W6w88K79fouC4ZHsBHGxJ4flk05w32PzUR2aMvmGwgSwIxceY6lF5EZY2ZYC8ni13zmbnDALBpZd2uB2ZGcO24YPp4OshuRyFOIoFYDxfg7kCA+/EaP8Fejng727EjMY/544KbPDe9sJyyqhpCfU6cEfN0siXUx4ntiQ0n7C/acgQHGysuGRHQ5PW9ne24dEQgl44IpMZssDcln9XRWew8ksesJiplm0yKf80ayLwPtvDxhkQWTAs98QAra/AKk0BMnNG+3ZGCjZXi/CH+FrtmX++2BXX2Nlan7I4UQmgSiJ1hlFKMDvZgW1Lz5Sfia5cvw05amgQYHezJb/uO8sWWJExKYVIc+013yZ405kT2wtX+1ByvxliZFCOCPBgR5NGi4yeGejNjoC9vr4rjytGBeDmftPzi0x/SG2+tIkRPVlVj5sddqcwY6Hdst6MQomuSQOwMNDrEg2X708koLG+yB11c7Xbzk2fEAKb19+Hr7ck8/MO+U55TCq4bH2Kx8TbmwQsGct6ra3ltZSyPn7zN3ac/HFwCVeVgY7lkZSG6g1WHMskpqeSK0YGdPRQhRDMkEDsDjQnReV3bE/O4cFjjS4DxWcW42lvj7Xzqb9QXDO3Frn/PpKrGjNkAs2FgNgwMA+xsTBbdqdWYMF9n5o0N4ostR7hhYsiJAaN3BBhmyIkDf6lFJM4s3+xIwcfFjrPCfTp7KEKIZsiuyTPQoN6uONhYNVsdPz6zhFBf50aTaz2cbPF1tcffzZ7e7g4EejjSx9PxtARhde6dEY6DjRWvndxFoK6ERVYjxV+F6KGyiytYdSiTuSMCpBm2EN2A/JSegWysTAzv487WhGYCsaxiwhpYluxKvJ3tuGJ0IL/tO0pWUb3eml5hoExSYV+ccX7clUq12eDyUbIsKUR3IIHYGWr6QF8OHC1kX2rDtcAKy6vILKo4pYZYVzR/XDBVNQaLtycff9DGHjxCZOekOKMYhsG3O1KI7ONOeDPlaYQQXYMEYmeoK0b3wcHGik83Jjb4fHwTifpdTZivMxP6ebFoyxFq6lfb9wyFvITOG5gQp9m+1EIOpRdxhcyGCdFttCsQU0p5KqWWK6Via+8brD2glDpfKRWtlIpTSj1Y7/HHlFKpSqndtbdZ7RmPaDk3BxsuGxXAT3vSyCmuOOG5ymrzsfyxUB/LFYPsSNeODyY1v4w1MZnHH/QIgdxEMFrWzkmI7u6bHcnYWpuYE9m6yvdCiM7T3l2TDwIrDcN4tjbAehD4Z/0DlFJWwFvATCAF2KaUWmIYxoHaQ14xDOPFdo5DtMENE0L4fPMR3vgzjgH+LkSlFhCVWsCho7oit4udNUGe3aMI47mD/fBxseOTjUmcM8BPP+gRAhUFUJYHjqd2ABCiJ6moruGn3WmcN9gfN4eW1/ATQnSu9gZiFwPTar/+FFjNSYEYMBaIMwzjMIBS6qva8w4gOlW4nwtTwr35pHZ50sXemqEBbtw0KYQhAW6M7evZbXZd2ViZuGFCMC/+EUN0Xa9KjxD9ZF5CywKxxPWQlwQj5nfoWIXoCCsOZFJQViXLkkJ0M+0NxPwMwzgKYBjGUaWUbwPHBAD1sqhJAcbV+/NdSqnrge3A3wzDaLBvjlLqNuA2gKCgoHYOW9R5/vJh7DqSz6BergR5OmIydd8+cPPHBfPWqnj+t+4wL1wRCZ599RN5iRAwqvkLrHkeMvZJICa6pW92JNPLzZ5JYd6dPRQhRCs0O92hlFqhlNrXwO3iFr5GQ5/sdUk77wChwHDgKPBSYxcxDON9wzBGG4Yx2sdHihRaSi83B2YN7UWIt1O3DsJA1zW7YnQgP+5OZfH2ZGZ8lKifyEts/mTDgLTdUJqjq/EL0Y1kFJazNiaLuSMDsOrmP8dCnGmanREzDGNGY88ppTKUUr1qZ8N6AZkNHJYC9Kn350AgrfbaGfWu9QHwS0sHLkRDbpncl4Wbk/jHt7rPZL6jO+65Ldg5mXtY55MBFKWBZ78OHKUQlvX9zlTMBlw+qk/zBwshupT2JgAtAW6o/foG4KcGjtkGhCul+iqlbIGra8+jNnircylwauNCIVoh2MuJf10wkAcvGMC5g/xIxa9lM2Jpu45/XZDaYeMTwtJ07bBkxoR40Ne7e+xyFkIc195A7FlgplIqFr0r8lkApVRvpdRSAMMwqoG7gN+Bg8BiwzD2157/vFIqSim1FzgbuL+d4xGCv5zVj79ODcXP1Z5Es69OwG9O6s7jXxemddzghGij1Pwy5r69gcTskmOP5ZVU8pfPthOfVcLVYyR3VojuqF3J+oZh5ADTG3g8DZhV789LgaUNHHdde15fiKZ4OdsSV+2NUbgBVV0J1qc2Lz8mbSf4DdHJ+oUyIya6no/WJ7DzSD6/Rh3lzrPDyC+tZPYb68kqquA/cwYxd2RAZw9RCNEG3aM2gRBt4OVkS7LhizLMUJB86gEFKXrZ0lwDR/dAyGSwd5dATHQ5JRXVLN6mv4fXxmQB8Pv+dFLzy/jkpjHcNKkvSkmSvhDdkQRiosfydLIjyVxb3LV+q6OyfPj9YXhtOLx3FsQsg6pS6D0CXANkaVJ0Od/vSqWooprx/TzZeSSP4opqlu1LJ9DDgQmhXp09PCFEO0ggJnosTydbjhi1pe1yE6CmCra8B6+PgE1vwZDLQJngm5v0Mb1HgmtvmRETXYphGHyyIYFhgW7cfU44VTUGKw9msCEuh/MG+7dvJuzAEoj5Hcxmyw1YCEupqYKi9M4eRYeTQEz0WF7OtmTiTo3JFg78BG+Ph9/+Af5D4Pa1MPc9uPwjMFeBrQt4hYFbgOyaFF3K+rhs4rNKuHFiCKNDPLC3MfH8smgqa8ycN9i/7ReuKIZvb4JFV8I7E2HPV/qDT4iu4veH4c0xUF7Q2SPpUBKIiR7Ly8kWAxNFDoGQuA5QcM3XcP0S6DVMHxR6Dlz8Fkz9B5hMemmyNFuKuoou45MNiXg723LhsF7YWVsxvp8XqflleDvbMirYo+0XTtoI5mqYcBcoBT/crmeLN78LlSXNny9ERypMgx0fQ0UhHPy5s0fToSQQEz2Wu6MtSsGqPnfBnNfhjk3Q/3z9oVPf8Hkw6R79tWvtzrOio6d3sEI0ICmnhD+jM5k3Lhg7aysApoTrziIzB/m1r4p+whqwsoNzHoEFG2HeYnALhGX/hFeGwEGpry060YbX9UYql16w9+vOHk2HkkBM9FhWJoWHoy3b7cbCqBvAyqb5k1x763vJExNdwGebkrBSivnjjtcImznQDxd7a+aObGdz78NrIGgc2DjoX04izoObl8HNf4CDB6x9oZ2jF6KVDEPPhGVF69mwyGtg1E2QsK5Hp4xIICZ6NE8nW3KKK1t+Qt2MmOycFJ2srmTFrKG98HO1P/Z4kJcjUY+dx5gQz3ZcPBsyoqDv1FOfCxoHQ6+A9L1Qltf21xCitVY9BS8PhLfGQk0lTHkAhl0BGBD1TWePrsO0q6CrEF2dp5MtuSWtCcRqZ8QKUjpmQELUisss4uc9ujirrfWpvxN/vzOFoopqbpwUYvkXT1ir7/tNa/j5vlNgzbM6j2zAhZZ/fSEaEr8KfAbA2NvAIwS8QvXjgWNh+4eAAc7+4FLvZu9+arpJNyOBmOjRvJxsickoavkJds5g7yYzYqJDHS0o49r/bSW9sBxPJ1tumBhywvNms8EnGxOJDHRjRB93yw8gYQ3YuUKv4Q0/HzgGrO31kpAEYuJ0qCrXhbXHL4Axt5z43PgFsOQeWPHYqefZu8Gwq2DQJVCao2fShlzWrYIzCcREj+blbEvu4VbMiAG4BkogJjpMYXkVN360jeKKagb1cuX1lbHMHRmAi/3xHMa6khUvXxlp+Yr55YUQu0J3krBq5CPA2g76jDs+cyZER0vfq0sJ9Rl76nND5upbRREUZUBxuq4vVpQOabtgxyew9f3jx3uEQODo0zXydpNATPRonk525JdVUV1jxtqq6ZTIZfuOkpJXxq2uvaFQliZFx/hq6xGiM4pYeMtYXO1tuPitDXyw9jAPnNv/2DGfbjxessKiirPg87n6g2zMa00f2/cs+PMJnU/m5G3ZcQhxsuSt+j5wTOPH2Lnom3fYiY+X5EDyFrCxh4WX6mt1o0BMkvVFj+blZIthQF5p04Uqk3JKuP/rPbz4RzRml14yIyY6zK4j+QR5OjIl3IfIPu5cOLQXH6xLIKe4AoDE7FNLVliE2QyfzoHsWF1PL2xG08f3PUvfJ6633BiEaEzKNnAL0nlfreXkBQNm6bqQbn30tboRCcREj+bpZAvQZMK+2Wzwf9/upayqhvIqM7kmD51rYK45XcMUZ5C9KQVE1sv7undGOGVVNXyzQ8/C1pWsuLZeyQqLSNsFWQfhwhchvJkgDHTvVVtnnU8mREdL2WaZWazA0ZCyvf3XOY0kEBM9mldtIJZTUtHoMQs3J7E1IZfbp/YD4EiFCxhmvSQjhAVlFpWTml9GZKDbscci/FwY29eTRVuOUFhexTfbdckK33olKywi9nfdW7X/rJYdb2Wjy1tE/ya9KEXHKkjVtRsbyg9rrcAxUHCkW/WolEBM9GheznZA4zNi2cUVvPh7NFPCvfnHeQNwsrUipsRBP1mccbqGKc4Qe5N1z7zIk3ZCXjs+mCO5pTzw9Z6OK1kR87v+kHJsRf2xwZfoLhMpWy0/HiHq1C0lNpUf1lJ116ibFTOM9l+zg0kgJnq05pYmX/ojmrKqGv4zZzBWJsXg3m7sydfBG8WZp2uY4gyxNyW/9vvM9YTHzx/sj7ezLSsOZnRMyYqiDDi6G8LPbd15EefrNkj7f7TseIQwDMg4AOtfgdXP6O8z/2Htv67/MDDZ6OAuehk8F6Ib2ndhEoiJHs3DUZcEaKi6flRKAV9tS+bGiSGE+ToDMDTQjW3ZtWUEZEZMWNjulAIi/FxwtD1xw7qttYkrR/cB4MZJIZYvWRG3XN+3NhCzd9VJ/Qd+lOVJoQtd5ye3fZapqhwOLYWf79P9TN+ZoGuDWdnAnFfB2rb9Y7Sxh17DIHY5/HQHVBbrhvZb3mv/tTuIlK8QPZq1lQl3RxtySioor6phR1IeG+Ky2RCfQ1RKPp6Ottw9PfzY8UMD3FhU5QJWSCAmLMowDPam5HPBkIZ3hd1+ViieTrbMHtbb8i8e+we49Ab/oa0/d/ClEP2rXp4MGm/5sZ1pzDW66KiNQ2ePpOVSd8C6l+HQr4ABtrUlJLzq30LBM1QH7w0x1+jSKUkb9CaQftNg6j8gfObxjiaWEjgGtryrixL/ZRWseQ5++weUF8BZ/9flir1KICZ6PE8nW37ancbi7SlUVpuxNiki+7hz19lhXDwiADeH44U0hwS4UYY9VdZO2MjSpLCgI7ml5JdWMSyw4WVHN0cbbp3Sz/IvXFmiW8cMvrRtH0D96y1PSiDWNmYz7PkS9n4FKTugulwHxQMuhMkPNF5YtyvY/hH8+nddv+usv+ugKfMQ5MTq2l1R3wL1ZsgCRsG0hyBoAuQeBmdfXZJi4xs6CDv/ORh9ky4a3FGCxutA7Nwn9ezYFZ/Ckrt0L8uyfP24qessCHbhf30hLOOc/r5sjM9hYqgXk8K8GdPXE2e7hr/1+3k74WRrRYGVJ94yIyYsaHdyPgCRjQRiHWb7x1BRCMPnt+18OxddEuDobsuO60xxdA/8+jeds+QzAIbP07NGRzbrwCB5K1zxsf577mpWPg7rXtJL2pd92PBsV1U55CVAThxkHYIdn8EXlx9/3soWIq/WeVoD58C42zt+RmrgRXDzH8d3YVpZw8Vv63ZIm9+C8nyY83qXCYC7xiiE6ECPzB7U4mNNJsXgADcysl3xlhmxbqvGbPDO6jjOHuDL4N5uzZ/QCoXlVVTXGMc2grTUD7tS8Xa2I8LP2aLjaVJVGWx4TZehCBrX9uu4+Os6ZKLlyvLgz6d0s2pHL7jkXR2Q1A9CdnwCvzwAn10Mt67sWktmsSt0EDbiOpj9auNBi409+A7Ut4FzYOK9ELVYl4/w7AeHV8Guz/XfwexXT897NFmd+v1uMsH5z4KDh94cUFGog8uOnJlrIQnEhDjJ5DBvElKcCcs/Suf/iIrWMgyD//68n882JZFZVMHjF1smEMsvreTD9Ql8vCERB1srfrt3Ct7OLfsOicssYnV0Fg/MjGi21ZZF7fgUSjJh6iftu46zX8/aRXxki/4wHvdXvfRqSXXLkMsfhbJcGHMrnP0wODQwEzrqRjBXH58xs0QdLUuoKoOlfwOvcLjwpdbNHFnbwohrj/95yFy9/KpU57fKUgqmPahnxpY9CIuuhKu+ALvT+MtRA7rOIqkQXcSNk0IosvKkujAdoxvUoBEnemdNPJ9tSkIpSMsvb/f18koqefH3aCY/t4o3/oxjQqgXBWVVPPjd3hZ/f3y0IRFbaxPzLVUtv7wQcuL1rTGpO2H9yxA8CUImte/1nH317rOK4vZdp7OV5sLP98JH5+qZmp/u1I9ZSlE6LLxY79bz7Ae3rYZZLzQchNUZeqXOwYv61nLjaK91L0Neog7CLDFj5NlXN+LuKsYvgEvegYR1sPASy34PtIHMiAlxEld7GyLCQnGK+411B1OYMqhPZw9JtNDelHxe+D2aOZG9KS6vIi2/rM3XKiit4v118XyyIZHSqhpmDenF3dPDGODvyofrE3jilwMs2nqE+eOCm7xOXkkl3+9MYe6IgGMFhtsldjl8NR9qartFnP8cjP/r8ecrimHV07DlHXDyhfOebv9rOvvp+5LMTp89aBPDgL2L4fd/6SXDCXfpPKKPL4Dl/9ZLVgd/0QGreyuD5fxk3Y+zJAs2vq43R8x+FUbe0LKEcHtXiDgX9v8A5z+jl9U6g9msuy9segsS18HQK6Df1M4Zy+kwfB7YucK3N0P8nzD08ubP6SASiAnRgMgB/SEOPv5jiwRi3YTZbPDYkv14Odnx9KVDeH5ZNLtqE+Tb4r6vd7E6JotZQ3txzznh9Pc/nkx908QQVhzI4JXlscwbG9Rk3a9FW49QXmXm5sl92zyWY7Ji9AeHdwRMvAt2L9IJ1QMuBPc+Oq/nl/t1i5fRN8OMx/QyTHs5++r74kw909OdZMfBr/dDwlq9o++6H/ROOtB/hxte0ztCK4uhz3i4eVnL85jK8uB/04+XuvEdBDf8Ar4DWjfGIZfBwZ91QGfJ4KeiqPlNAJUl+vto8zuQGw+uATDjvzD2NsuNo6saOBvu3t764NvCJBATogHWrrrWU15GCvmllbg7WqDQoOhQP+5OZeeRfF64fBgu9jb0dncgv7SKkopqnBrZJduY8qoaNsTncNPEvjw659TNHiaTYnZkLx7+YR9HcksJ9nJq8DqV1WY+3ZjIlHBvIvzauSsufR8svl7vQrtmkf7wCJoAb43TwZeDO0R9o4O0m5ZB8IT2vV59zrW1z7rTTuKqcl21ff3LYO2gl9lG3XTijNPUB/WuRmd/vSFhw6tw4CcIm66T6AvTdIDr1qfefRC4B+u8qeWP6p601/+kK7rbu7etLEL4ebq21r7vLBOIJW+FtS/o2dOrF8GABvqLVhTrZPztH+r6WgGjdPL6oIt1gdUzRScHYSCBmBANq50B8FH5RKUWMCXcp5MHJJqyJzmfp5ceJLKPO5eNDASgt7tump2WX0Z4K4OgPcn5VFabmRDq1egxo4I9ANiRlNdoIPZrVBqZRRU8d3kTrVsqivSHuWcjM2aZh3Ri+YEfwc4N5n19/MPDIxjOfkgHBCYbHVhMecDyO8HqliZbmrCfuB6O7oUJd1h2HC1VeBQ+naNrXQ29As59Clz8Tj3O1lEHUaALjsYuhxX/0ctzqTt02Y6EdVCUBka9zgKuAXqH4M7PYNK9ujhpe9g66mbsUd/of9PRtzSdV9YQw9BLimtf0LN/jl7g0ku/n/BzT0y4P7xG19XKT4ZBF8H4O/VGga60a/MMIoGYEA2p/eDxUQUSiHVxn2xI4KmlB/F1seeFy4dhMukPkwB3Xbk8tQ2B2JaEXJSCsSGNN8gO93XB2c6anUfymFsb/NVnGAYfrk8g1MeJqQ19/6TsgCV3Q+YBwIB5iyHivOPP58TD6mf1h7OtE0z5u15Kc/A48Trj7wCTNYROb/2SWEs5eoKy0snoLbH+Vd1WycVf75o7naorYPF1ejbr2u/17FZLmKzg3Cd09feCVLjyUx1sAdRU6esVJOsk9r1f64Khnv108VJLOOcRKM2prd31Coy5Wf/bujTcieEYw4C4lToAS96s/+869yldNDVupf672PuVriN3eLWeJUxYo6vg3/SbZWdORZtIICZEQ5y8AUWYYwlbUwo6ezSiEauiM3ns5wPMGOjLi1dEnrCE3Ls2EGvLzsktCTkM8HfFzbHxJRork2JEkDs7khrOQ9uakMu+1EKeunTIseDwmMpS+O4WHTRMe1DnB/3wV1iwQbe/WfOCLoFgZQuT7tG1mZwamZ2zsoEJd7b6PbaKyQqcfFq2NGkYejYJ9JJp0PimW9gYhp6Bilqsg82Bc9o3M/PbP3QpiCs/a3kQVidsOpz3DPgNPnGJ0MpGz1R5BEPIZF2eIfOQzr+zVKsij2C47nu9VLr+VV2JfvM7Oql84j26hRDoHX5xK3SnBCsbHVit/C+4BsKsF3XdLxs9G8zAOdB7pK5ntvUDXZTX2R9mPg5j/qJn4kSnk0BMiIZY2YCjF2E2pfxPArEuqayyhkd/2keojxNvzR+JnfWJu818XeywMqlW75ysrDazIymPq8c0nzsyMsiDN/6Mpbii+pRuDR+uT8Dd0Ya5I06dLWPl47oa+Q2/QN8pOln7vanw0Xl65kVZ6Qrkk+8/nijf2Zx9W7Y0mZeg62dNvBu2faRn/a79ruFjK0v18/u+BQdPPXsTfh5c/UXb8pS2f6yLpE5+QOc6tUVLl1M7avaxV6SutJ/7bx2M7fpCF0Q9+2H9nhZdqavY7/xM7/Rb+V/9/XPJu6c2zVYKJ7NHXQAAIABJREFUZvxHF4y1cdDV5COv7hJFTMVxUkdMiMY4+xFoU0Rqfhm5JZWdPRpxkjdXxZKcW8aTlww9JQgD3fDd39W+1YFYVGo+5VVmxvdrfFmyzshgD8yGzimrLymnhOUHM5g/LggH23pjK86EtS/qZa2xt+kgDMA7HOa8qnPFRt0I9+7WpQy6ShAGtUVdWzAjlrpT3w+9Eqb+n569yYo59bi8RPjwXJ2gPv1R+HuMXuaL/V3nOLVW8lZY+n8QNkMv83V3nv1g9itwX5Se2Vr5X3hzjF6+nPqgfr8/3wuBY3X7npODsDr9psG9e+GubTDqBgnCuiAJxIRojLMv3ugP2KhUmRXrSmIzinh/7WEuGxnYZEJ9gLsDqa0MxDYf1sUdxzSRH1ZneB93lIKdSXnEZxWzPjYbgI83JGJtUlw/IeT4wQd+gpcHwp9P6A/H6f858WLDroR/pendfU0t5XWWllbXT9mudyn6DoLIeXp2b8+iE4+J/xPen6bLbMz/Bqb8Tc+ATboXbJzg0C+tG1tpLnx9HbgFwGX/67xaXB3BxQ8u/1gHW/2mwi3L9QaNm5bqJdKrFx1fimyMR3DP+jvpYWRpUojGOPvhlB0HQFRKPlMjJGG/pVIObiVtzYf8EXA3fm6O3Dqlb5O1tlrDMAwe/mEfTnbW/GtW08tDvd3t2XEkr1XX35qQS7ivc4uKr7o52BDu68zHGxN5ZUUMZgOuGBXI0qijzB7WGz/X2g/Imir449+66fMVn+gZsIZ05V1rLn66oKvZ3HSJhtQd0Hu43qXn4qfzrvYuhnP+Dcqki56ueAy8++slyLrcJ9DLZ2HT4dBSmPVSy0tB7PwUitN1JfuTNzP0BErBiPn6VidwtL6Jbq9dM2JKKU+l1HKlVGztfYM/AUqpj5RSmUqpfW05X4hO4eKHqSSDvl6OMiPWCjuP5JH49T8Y+//t3Xd4XMW5+PHvqK7KqnfLtuQiyx03bKoxuFMMCYTQAgnBgUBCComBFJIb+IXkcgmQGyAG0w1cmikJNraJjTu4yb1LLqpWL1bXzu+POatiq6y6vHo/z6Nnz56dc86M16DXU97Jfpe1X2/lic8PcDy/vMvu/8H2dL45XsAj85LbDJbiQvzIKqqkzuH6VlX7MksYP9D11AGXDY+krLKWOy9O4EfTh/D+9nTOVNdxd+MErrvehaITZgiupSCsrwuMNvsiVrSyHUxdjZlsPmBSw7nxt0BJhlnBt+xek2pj5LXww9VNgzCnkdeaoCpjm2v1ctTBtlcg4TKIm9C+NgnRB3R2aPJh4Eut9XDgS+t9c14DmttZ1dXrheh5gTFQV83UWA/2yIR9l/x7dxa/eelDLmUnAC/MNVndj53umj0KC85U8/8+P8DkwaHcNKntHQ/iQvyodWhyS6taLPPmlhOsOWSG3PLKqsgrqyI5xvV0F4vmJrP1tzN57NrRPDJvJG/dPZU/XjeaMQOsjPZ1NSa1QNwEk8/pfFWfXb+VeWI5e83WSwMmNpwbMd/kP/u/200ahSsehZteb3mrpOGzTTqOA5+ZVaU5+1qv19HVUHQSptzdvvYI0Ud0NhBbALxuHb8OXN9cIa31OqC5f0a5dL0QvcLK3zMlvIrM4spWf5n3d2VVtfzq/V3c//YOfhLwH7SHmfUQX3cKgGO5HQvEistruGPJ12w/Yf738eTyA5RW1vLEDWPPTQnRjMa5xJrz4fZ0fvfxXp778ggAh7JLARgZG+RyHX28PAj2a1jhd+nwCO68OKGhgLM37IpH+vbQY1vqk7q2EoilW71YAxoNmXnbzEo9NHzrJbhiUet/Dn4hkHi5Sd/x7AXwwsWmx6slW182dUu+xuWmCNGXdHaOWLTWOgtAa52llGrvEh+Xr1dKLQQWAgwa1PtbEoh+wB4LwCh7OeDN3oxiZiT3oVVsfUTKqSIefHcnpwrKeejyKObtXIMadzMc+hy/klQiAkd1OBBbe/g064/ksTu9mEVzk3lvWzr3Th/aZN/H1jTkEquoz4QPZp7ZjpOFPLJsD14ein0ZJVTV1nEgqwSgXT1irarvDZt4fveGQdPs+uUFJjloYRoUpJkVkAVpZggyIOrcbWNmP24m5DeX3b45Y75tJvQPvsQMX/77IQhNNEOeeUcg7xDkHTarMY+sgum/7l/b8gi30mYgppRaDTSX2vc3XV+dlmmtFwOLASZPnuz6hA8hOsrqEUvwKUGpcPZIINZEnUPzwtqjPLv6EDMDj/PuxOPEHlkNNeUw9V6T6yjvKEMiA0nNPdOhZ2xJLSDQ1wsfLw8eXbaH+FA/HrzK9TlWzm2O3tt2ivVHcsksqiSzqIKMogqqah0MCPHjgSuH8chHe9ibUcLB7FIiAn1dmqjvkl3vmN6w+U+d371h0DA0WXjCpJ3IN72IBESZ7ZkSLjWvQ2ac21YvH9eDMDBZ4BMuM6v9KktMfrW3vg26rqGMh7cJ0sbdbP6+CXGeajMQ01rPbOkzpVSOUirW6s2KBVzciKxeZ68XovtYgZhf5WmGRAxit8wTq5dxOo+33n6DxLy17PDbhb26CA54m7xYV/4WYsdB+HA4uoqhQwJZsTerQ8/5OjWfqYlh/HxWEg+9v4vfXj2qaV6uNtht3iSE+7P+SB5Rdl/iQvwYGRfEzFHRxAbbmDM6Bi9PEzTsPFnIwewSRsa20BvmcJhte7a8YN7f+IrZ+qclTXrDZrlc5z7LJxC8/U2S0epSM89r2MyW53p1hlImCAOwBZkUF1teMPsnRo4wG5uHJkgvmHALnR2a/BS4E3jSev2kh68Xovt4+4EtBEqzGTsgmM2p+b1do96Xe5isjx4mLGs9i6imxhaIV/IcSL7a/FK2BTeUjRgGKW+RHKp5p7yGgjPVhAW0kHSyGadLKknNO8MtFw5izIBgVvzs8g5VeeXPp6PRzSZ9dYoP9eObtAKO5JTxvYsGN/2wqgxS3jZJWAuOmSHr8nx47Wq4Y1nLewHuesdMIp//P+d/bxiYNgRGm+HIiXfC6B6c0hscD3Oe6LnnCdGDOhuIPQm8p5S6GzgJ3ASglIoDXtZaz7fevwNcAUQopdKBx7TWS1q6Xog+wx4DZdmMjQ/h45RMTpdUEhXURvJEN3b8vUVEnN7EGv/ZTJ5zO1Fjrmo5o3e4GUIc7Ws6uo/llpGaCw+8vRO7zYuoIF8iA32JtPsSZbcRH+rHnNEx9ZPwt6SZCfrThrScsNUVPl5tr0maOCiUFXuzqa5zkBxjTdQvzTE5r3a8CVXFZgL6t5eYbWZObIR3b4OlN8GP1p0baLlbb5hTyECzknH2n3q7JkK4jU4FYlrrfOCcXVW11pnA/Ebvb2nP9UL0GfYYKM1mXLzp6dmTUcxV/TQQ0w4Hoblb2ep/GbMfehMvzzYCHCtfViKZQDjHTpex/kgeZ6pruWBgCKdLK9l+spDTJVVU1ToAePX7U5gxwsxF2pKaj93Xi1Fxrq9g7KhJg0P5dFcmQMNCgGU/MlvtjL4ept4HA6c0XDDkCpj5B/j8IZNeIWZM0xumvO1evWFOC/5hXhv3fAohOkUy6wvRGnssHN/AqNggPBTsTi/mqpHtmHTsRk4e2slgSvEdemnbQRiYVW7Kk9DyE/h6RbLjZCGr9udw69RB/OG60fXFtNYUltdw8ZNf8tWh3CaB2IWJYXi6kKaisyYOMisqPT0Uw6ICTZLQU9+Y3FTz/7v5i0ZdD8t/Dfs/bhqI1VbD+qfcrzcMzl0NKYToNNlrUojWWD1iAd4eDI0MZG8/zrB/bPsqAEZMmePaBV4+EDoYj4IjJEYE8NGODKrrHNw4Kb5JMaUUYQE+TE0MZ93hXABySipJzT3T6WFJVyXH2rF5ezAkIgCbt6dZ8VlzpvVM7YGRJr3Cvo9BN1rInbLU9Iad73nDhBA9QgIxIVpjjwVHDVQUMDY+mN0ZxWjrl+7aQ6e56cVNlFTW9HIle4Y6uYlCj1DCB7W+v2MT4cMh7yhDowKpdWjGRNsYXbWzaeBiuTwpktS8M5wqKOeD7ekAzEjumf09vT09+PbEeK4eZ3LHkZliXmMvaP3C0TeYNA7O7O+VxbDmCRg41f16w4QQ3UICMSFa41wRV5rFuAHB5JZWkVNiMuwv35PN1uOF/G3V4V6sYM84lX+GEVV7KIiY0r5enojhUHCMoRH+APwuaiPqjQVwaPk5RacnRQDwn4OneXPzCS4bHsGwqC5KrOqCJ24Yy89mJpk3WSng5WfSJLRm5HVmI+v9H5v3X/0VzuTBvL9Ib5gQwiUSiAnRGiu7PqU5jLUm7O9OLwKo3wj89U3H2Z9Z0ivV6ykbtu0kThUQmjy9fRfGjIXaSubZ0xgaGcCkIisAW/OEycvVyNDIQOKCbTyz+jDZJZV8/5KEjlU2YzvseAM2PGOSj3ZEZoqpu2cb02idw5PfLIZ//dykuJj4Pdl8WgjhMgnEhGiNc1uX0ixGxQbjoWBvRjGVNXUczinllgsHEeznze8/2Vs/ZOmOMnZ/CUDYqCvad+HI68AvlJEn3uLLW8Pwyt1v9hHM2Qv7PmpSVCnF5UmRFJbXkBgRwBVJHdjFoKoUlsyBT38Cqx8z6Sfay+GA7N0Q18awpNO8v8Cgi2HnUvC1w1W/b/8zhRD9lgRiQrSmfmgyGz8fT5Ki7ezOKOZQdim1Ds1lwyO4d/pQtp0oJLuksnfr2k32Z5YwuHgbVV52iBrVvot9/GHy3XDw37D2z+DpAze+Zu6z5v9BXW2T4tOTzJywOy8a7NKm3ufI2GHm9H17CQycBlm723+P/KNQXdb2/DCn6NFw67vw61R4YDsERLT/mUKIfksCMSFa4+ULfmFQarboGTsgmD3pxfXDkmMHBDM82mzxklnknoHYh9vSmOm5A4bPAY8O/C/jwoVmK5pDn0PSXAgIN9sgFRyDXW83KTprVDR/u3k8t04d3MLN2pCxzbwOvdL0aOXsNako2iPLmqjvao+Yk2+gaZsQQrSDBGJCtMUeC6XZAIyNDyb/TDUr9+cQ7OdNfKgfcSF+AGQVV/RmLbtFda2DzJRVhKoyfMfd0LGb2KNhrLVpxgW3mtcR82HAJDO5vbaqvqiXpwc3TIh3KRt+s9K3QfgwswdkzDizAXlBavvukemcqD+iY3UQQoh2kEBMiLbYY5r0iAGsP5LL2AHBKKWIDbYCMTfsEfvPwRwuq95IrZc/DOvEJhgzHoXpD8MwK6WDUqZXrPgUbH+tS+qK1pC+FeKtDPgxY81r1i7Xrk/9Ct67E7a/CrHj256oL4QQXUACMSHa0qhHbGRsEF4eCq1hjBWUBdm8CPDxJNMNe8S+2JPOXK9teCTNMZugd1RwPMx4pGlwM2QGJFwG656C6jOdr2zRSTiTC/GTzfvIZPDwNhPv27LjTXjzejixCcbd3LCVjxBCdDMJxIRoiz0aynLA4cDm7cnwaJPbytk7ppQiNsTPLXvEfDK+IYwSPMZ0cFiyNUrBlb+DM6dN+oeOyNkH/7wc9n5oesPAbM4NJrN/VDJk72n9Ht+8BJ8+YALDB1Pg2mcgYljH6iOEEO0kgZgQbbHHgq6D8jwAxlkBmDMQA4gNtrldj5jDoRlb8hXVHraGIcWuNmgqDJ9tcn5VtnP7qBOb4dV5Zujx0wdh3zIztyu6YR9LYsablZMtpRapKoNVj8HQq+CWd8EnoONtEUKIDpBATIi2NMquD/CdKQP53kWDGRjWMFQXF+zndqsms0oqmcQB8sImmjQU3eXK30JlEWz6X9evObTcDCUGRMFdn5tzB/9lEql6ejeUix1nAmjruzvHwX+ZPSUv/5XpQRNCiB4mgZgQbanPrm/miU0aHMp/LRiDarSFTVyIH3llVVTVtjNVQh92PCObJHWKurjJ3fug2PEw6nrY8jyUF7RdfudSePc2k4vsB19AwiUw70nzWfykpmWdE/ZbGp5MeRtCE2DQtA5XXwghOkMCMSHaclaPWHNiQ2wA5BRXtVjmfFOa+g2eSmMfdlH3P+zin5okqsf+03q5jc/CJz822fnv/Kwhb9cFt5kJ9lPva1o+eox53fAM7H6/aaBXnA5p62D8LbIvpBCi18j6bCHaUr/NUXaLReKsFBaZxRUMCu/GYbwe5GUlRw3uiUAsdhx42cw+kWNvhMLj8PIsCIoz+caC4825Ha/DmG/D9S82HUpUCibcfu59bUEmyEtZCh/9EFBmVeWwWWaRANqskhRCiF4igZgQbfH0Bv+IVgMxZ4+YOyV1DSvaxSnPeAb6h3b/wzy9zZZC6VZm/EMrTKAUMRz2fABV1kT+C38Ec59sX4b/2X+CmX+AzJ1wZBUcXWW2W0KbPSLDEru4MUII4ToJxIRwRaNcYs2p7xFzlwn7WpNYeYDDwZcwsKeeGT/ZpJKorYa0r8zcre9bE/Gry02W/I7u4+jhae4fP9nkMzuTZ57h6n6SQgjRTWSOmBCuaJRdvzl+Pp6E+nuTWdQ7PWJvbjnB2kOnu+x+5dlHCKWE8qgJXXbPNg2YBHVVJgHr8Q2QOL3hMx//rt1MOyDCDHGGD+26ewohRAdIj5gQrrDHtJkYNDbYj6zinu8R23a8gN99vBeAuaNj+P21o+r3v+yogkMb8Ae8Bk/tghq6yJkRf+vLUFUCQ6a3Xl4IIdyA9IgJ4Qp7rJmzVFfbYpG4EFuv9Ig9++URwgN8+OWsJNYePs3Mp7/ipXWp1NQ5OnzPmhNfU6r9iBrSg0N3wQNNXrDd75n3iRKICSHcnwRiQrjCHgPaYfYydKqphLyj9W97o0ds+4kC1h/JY+HlQ/jJVcNZ9fPpXDQknCc+P8C1f9/AtuMu5OU6W10tkemr2eIYxeBIe9dXuiXKWtGo60zaia4cihRCiD5KAjEhXOHMJVbWaML+jjfg+WlQmgOYlZPFFTW8sPYYh3NKu7U6q/bn8MLaY/zh0/2EBfhwx0WDARgY5s/Ld07mn3dMoqSihhtf3MzL61Pbd/PUtQTW5LHO7yps3p7dUPtWDLASskpvmBCin5BATAhX1Cd1bRSIFZ8CRw0c+QKAK5KiGB4VyF9WHOTq59aTlnemW6qSUVTBPW9s4y8rDnIwu4SH5ybj79Mw3VMpxZzRMaz6xXQuT4rk2dVHKK6ocfn+dSlvU6wDcAyf0x3Vb13CpeZ1+Myef7YQQvQCCcSEcEX9NkeNVk5WWMN+h5YDMCouiFW/mM7qX0ynpk7z+Z6WV1l2xoq9Jhhc+fPLOfz4PL4zpfkEEwG+XiyaO4LSqlre2HTctZtXFsPBf/Fp3UXMHDuoi2rcDoOmwf3fwNAre/7ZQgjRCyQQE8IVAVGAatojVlFkXo+tMXmuLMOiAhk/MISV+1rOO9YZX+zNJjnGTlK0vcl+l80ZHRfMlclRvLIxjTNVLS80qLfvYzzrqvi3xxVcNDS8ayrcXpEjeue5QgjRCyQQE8IVnl4QGNW0R6y8ALz8oLbCJAdtZO7oGHalF5PRxasoc0ur2HqigDmjY1y+5v4Zwygsr+Gdb062WVbveofjxBE+4uKenx8mhBD9kARiQrjKHnNWj1iByXXlY68fnnSaM9rsT9nVvWIr92ejNcwd43ogNmlwKCNjg/jqcG7rBQvSUCc3817NZcxuR6AnhBCi4yQQE8JVgWdl168oNBuCD7sKDq8AR0PeriGRgYyIttfP5+oqK/ZmkxDuT3JM+9JKjIgOJDW3jcUDu95Fo/iMy5iRHNWJWgohhHCVBGJCuMoeU5+qAq3N0KRfqFnpV5ZjfhqZMzqarccLyC+r6pLHV1TXsflYPrNHx7Q5N+xsQyMDySiqoLy6hXliWqN3vcN2j7EkDh1BkM27C2oshBCiLRKICeEqe6xJ6FpXA9VnTOoK/zDwtya1VzRNnjpnTAwODasP5DRzs/bLKKqg1qEZGdv+JKsX12xmgjrScq/Yyc2oohMsrbykflhVCCFE95NATAhX2WMADWWnG4Iuv1ATjIHpIQMzj+zLPzEq2p+BYX5dNjyZVWwm/scGt3MfSYeDC3b8ll96vUdqS7nNUt6m2sOPlXoKs0ZKICaEED1FAjEhXFWfSyy7IejyCzM/0BCcHfwXrH8Klb2HOaNi2Hg0n9JK1xOqtiSryGyfFBdkgw3PwOmDrl2YdxjP6mLGeaRxrLmM/9XlsO9j1nlfzIiBMUQF2TpdVyGEEK7pVCCmlApTSq1SSh2xXkNbKPeKUuq0UmrvWef/oJTKUEqlWD/zO1MfIbpVfXb9LDNRH5oOTTqDszP55jX3EHPHxFBd52DNoTZWLLog0+oRi/YuhdWPwSc/NnPV2pL+DQBBqpzizMPnfn7oc6guZUnpNFktKYQQPayzPWIPA19qrYcDX1rvm/MaMLeFz/6mtb7A+vm8k/URovs0zq7f3NCk85xzY/Dcg0wcFEqk3ZcvumB4MquokohAX3xLrHxgGdth37K2Lzz1DWAm99tyd5lza/4Ma580xylvU2aLZYtjZLvykwkhhOg8r7aLtGoBcIV1/DqwFlh0diGt9TqlVEInnyVE7wqIAOXRdHWkXxh4+5nErs4esfI885p7EA8PxaxR0Xy8M4PKmrpOJUnNLK4gLsQGBWlWfaLgyz9C8jXg5dPyhelbYch0atM2EVW6H0dFMR4bnoa6avN56hpWBnyHYVFBJEYEdLh+Qggh2q+zPWLRWussAOu1I8mHHlBK7baGL5sd2gRQSi1USm1TSm3Lze38MI8Q7ebhafKGlWY1bG/kZ/2V9Q9rGK480xCIgcmyX15dx/ojeZ16fFZxJbHBNihIBRRc+wwUHoeUt1q+qKLI1GPwpRQFJzOKVApTPjNBWEQSrP0zaAf/KJgivWFCCNEL2gzElFKrlVJ7m/lZ0AXPfwEYClwAZAH/01JBrfVirfVkrfXkyMjILni0EB3gzK5fUQA+gQ09UX5hUG7NDXMGYoUnoLqcaUPCCbJ58UUnsuxrrckqqjArJgvTIDgeRsyHsKFweGXLF2ZsM68Dp1AbPZ4xKg2972N0YAyOu5ZDaCKnwy/kmCOW2ZK2QgghelybgZjWeqbWekwzP58AOUqpWADr9XR7Hq61ztFa12mtHcBLwIUdaYQQPcYeawVihQ2rJQH8Q5sOTfqHAxryj+Dj5cFVI6NZfSCHmjpHs7dtS0llLWeq6xqGJkMTQClIvAxObAJHXfMXntpqhlMHTCIgcQqBqpKw9NV8XDmRhR+moX+8mT/af09ssI2xA4I7VDchhBAd19mhyU+BO63jO4FP2nOxM4iz3ADsbamsEH2C3drmqLwA/EIazvuFmV4yh8P0jA2+2JzPPQTAnNExFJXX8E1aQTM3bVuTHGIFqRCWaD5IuAyqiiF7T/MXntoCUaPA105g4mQAPNB8WjOZ1QdO88GuPFYfLWP2qOh2Z+sXQgjReZ0NxJ4EZimljgCzrPcopeKUUvUrIJVS7wCbgRFKqXSl1N3WR39VSu1RSu0GZgA/72R9hOhe9lgTaJVlN6yWBHNcXmB6yrQDBk4FDy84fQCA6UmR2Lw9Opzc1ZlDLD6g1vS4hQ0xHwy+xLwe39D0gpoK+PQnkLoWhs0EQEWMoNrDRplnME8/dB+j44J45KM9VNU6ZH6YEEL0kk6tmtRa5wNXNXM+E5jf6P0tLVx/R2eeL0SPC7TmUeUehhGNMrL4hUFlEZyxRuftsRA+rL5HzM/HkyuSoli5P5s/XjcaD4/29T45c4jFa2vFZqjVIxYUa+aJHd8AFz9gzhWkwnvfM71kl/0SrnjUnPf0wmfKXfjYY8Duz5+uH8O3nt9EsJ83UxLDEEII0fM6m75CiP7FmUustuKsOWJhpics/5h5HxAJkSMgu2G0fe6YGFbsy+brtAIuGhrersdmFVXi6aEIq84wJ5xDk2A2Hd/3sZknduhz+PjHZl7Yre9B0pymN5r3l/rDiYNC+dWcEfj7eOLtKZtsCCFEb5D/+wrRHvZGQ3iNhyadQVme6QEjIAIik80KxxozrDhndAyh/t68sjGt3Y/NLK4g2u6LZ6F1bWjjQMyaJ/b+XfB/t5ueuHvXnxuENeP+GcP4/iWJbZYTQgjRPSQQE6I97I3Wl/g1Snvn3OYo74j1PsL0iGkH5Jtzfj6e3D5tMKsP5JDW0ubbLcgqqiQ2xM+smPSPAFtQw4cJ1jyxA5/ClHvgBysgZFB7WyaEEKIXSCAmRHv4h5tJ+HDu0CRA3uGGcpEjzbE1TwzgjosG4+3hwavt7BXLKq4wyVwL05oOSwIExcGVv4ObXoernwIv33bdWwghRO+RQEyI9vDwgEBreLJxj5jzOPewOfb0gvChoDzrM+wDRNltLLggjve3pVNUXt3qo7TWlFbWkF5YTlZxJXHOHrHQZoYSL38IRl/f2dYJIYToYTJZX4j2skdDSfq56SsAqkshfLg59vI1aSYaBWIAN0wcwPvb09mTUcxlwxt2iSiprOG+t7aTUVhBcUUNJZW11Dl0/ecJId5QnH5uj5gQQojzlgRiQrSXc55Y46FJ32CzUlE7zIpJp8gRcLppIBYZaIYOiytqmpzfcCSPjUfzmTkyiphgG8F+3vU/of4+XB5TDSt103lqQgghzmtuE4jV1NSQnp5OZWVlb1flvGez2YiPj8fb27u3q9I32ZsZmvTwMO/L8yGgUWqKqJFwaDnUVtXP3Qr2M3+uReVNA7EtqfkE+Hjywu2Tmk8nkbXLvAZEdFlThBBC9C63CcTS09Ox2+0kJCTIVi2doLUmPz+f9PR0EhNlCKxZMePMPLHGWxxBw8bfTXrEkkHXmfxi0aMACLICsbN7xDYfy2dyQljLOb2cm4r7ty8HmRBCiL7LbSbrV1ZWEh4eLkFYJymlCA8Pl57F1kz8Hvx8H3h4Nj3vnCfm36jHKnKEeW00T8zm7YnN26NJIJZXVsWR02VMG9JKkHXGGYhJj5gQQrgLtwnEAAnCuoj8ObZBKbMq8mzOOWONhw7Dh5m5Y41SWOCo43qcbcZdAAARFUlEQVTfbZScaQh2v041m4FPG9LKVkPSIyaEEG7HrQIxIXqVfzOBmLcfhCZA7oGGc6lreLL2KYblrqw/5ZwfNmZAcMv3L88zQd3ZQ6JCCCHOWxKI9ZK1a9eyadOmTt0jMDCwi2ojuoRz8v7ZQ4eRI5v2iBWYZK4XFH9Zf2pzaj5TEluZHwamR8wv9NwhUSGEEOctCcR6SVcEYqKPcQ4Znr2qMXIE5B+FOmtOWOFxAMZXbYeKQnJLqzja1vwwgDN5Mj9MCCHcjNusmmzsj5/tY39mSZfec1RcEI9dO7rNctdffz2nTp2isrKSBx98kIULF7JixQoeffRR6urqiIiIYMmSJbz44ot4enry1ltv8fe//50lS5ZwzTXXcOONNwKmt6usrIyysjIWLFhAYWEhNTU1PP744yxYsKBL2ya6SNRI8A2C4Pim5yOTwVELBakmKCs8TrXyxUdXwYF/cSxkPgBj4loZlgQoL5D5YUII4WbcMhDrTa+88gphYWFUVFQwZcoUFixYwD333MO6detITEykoKCAsLAw7r33XgIDA3nooYcAWLJkSbP3s9lsLFu2jKCgIPLy8pg2bRrXXXedTKjvi5Lmwq/Tzp3I71w5efqAOS46wUn7RHyKUxm090NOj78KgOigNvaILM+DiOHdUHEhhBC9xS0DMVd6rrrLc889x7JlywA4deoUixcv5vLLL6/PyRUW1sqquGZorXn00UdZt24dHh4eZGRkkJOTQ0xMTJfXXXRSS6spI5IA1TBPrPAk5WGzWVEQxf1pn1EalwGYfShbVZ4P/tO6ts5CCCF6lcwR60Jr165l9erVbN68mV27djFhwgTGjx/vUu+Vl5cXDocDMMFXdbXZEHrp0qXk5uayfft2UlJSiI6Olhxf5xsffwgdbHKJVRRCVTHV9oF8UTcFpR0EZG3Gx8uDIL9W/l3kcFhDkzJHTAgh3IkEYl2ouLiY0NBQ/P39OXjwIFu2bKGqqoqvvvqKtDSzUq6gwOSLstvtlJaW1l+bkJDA9u3bAfjkk0+oqampv2dUVBTe3t6sWbOGEydO9HCrRJeITDY9YoXm+3MED+KgHoT28MZetJ8ou2/rAXtlkcnQL3PEhBDCrUgg1oXmzp1LbW0t48aN43e/+x3Tpk0jMjKSxYsX861vfYvx48dz8803A3DttdeybNkyLrjgAtavX88999zDV199xYUXXsjXX39NQEAAALfddhvbtm1j8uTJLF26lOTk5N5souioyBGQf8RM2Ac8whKowYuKkCSiyg4RaW9rfpiVzFX2mRRCCLfilnPEeouvry/Lly9v9rN58+Y1eZ+UlMTu3bubnNuyZUv98Z///GcAIiIi2Lx5c7P3LCsr60x1RU+KHAl11ZC6FgDfyCHAXoqCRzKwcCVRgT6tX1+fVb99cwyFEEL0bdIjJkRPcK6cPLISbMEEhpierdzAJEJ0CcNsbaRbOZNnXmWOmBBCuBUJxIToCRFJ5rU0C0IGE+LnDUCat0lHMZK01q+XfSaFEMItSSAmRE/wDYTgQeY4dDBBViC2tTwWh1Yk1Bxr/fpyZ4+YBGJCCOFOJBAToqdEWQstQgbj6aGw27zYl+8gTccQXX649WvLC8Db36TCEEII4TYkEBOipzjniYUmABDs583R02Xs0wkEFx+A7D3w2jVwqJkFH7LPpBBCuCUJxIToKZENPWJgArGyqlr2OhLwKcuAV+fD8Q3w7q3w9eKm15bny4pJIYRwQxKI9WGBgYEAZGZm1m8G3pJnnnmG8vLydt1/7dq1XHPNNR2un2in4XNg3M0wyGxTFOJv5ont12b7K+wxcP83kDQPlv8KVjwCjjrzWXme5BATQgg3JIFYD6urq2v3NXFxcXzwwQetlulIICZ6WGAkfGsx2IIA0yMGcMR/Aix4Hr6/AiKT4OY3Yep9sOV5eO97UF1u9YjJRH0hhHA37pnQdfnDZr5NV4oZC/OebLXI8ePHmTt3LlOnTmXnzp0kJSXxxhtvMGrUKH7wgx+wcuVKHnjgAaZMmcL9999Pbm4u/v7+vPTSSyQnJ5OWlsatt95KbW0tc+fObXLfa665hr1791JXV8eiRYv44osvUEpxzz33oLUmMzOTGTNmEBERwZo1a1i5ciWPPfYYVVVVDB06lFdffZXAwEBWrFjBz372MyIiIpg4cWLX/hmJdgn2M0lcI4L8YMJtDR94eJq/a6EJsOJheO1qKMuVOWJCCOGGpEesix06dIiFCxeye/dugoKCeP755wGw2Wxs2LCB7373uyxcuJC///3vbN++naeeeoof//jHADz44IPcd999bN26lZiYmGbvv3jxYtLS0ti5cye7d+/mtttu46c//SlxcXGsWbOGNWvWkJeXx+OPP87q1avZsWMHkydP5umnn6ayspJ77rmHzz77jPXr15Odnd1jfy7iXM4esaiWtjeadi98922zWXhthcwRE0IIN+SePWJt9Fx1p4EDB3LJJZcAcPvtt/Pcc88B1O8xWVZWxqZNm7jpppvqr6mqqgJg48aNfPjhhwDccccdLFq06Jz7r169mnvvvRcvL/PVhYWd+8t5y5Yt7N+/v74e1dXVXHTRRRw8eJDExESGDx9eX7/Fixefc73oGc45YlF2W8uFkufDXf+GT38C8VN6qGZCCCF6insGYr1IKdXse+cm3g6Hg5CQEFJSUly6/mxaa5fKzJo1i3feeafJ+ZSUlDavFT2nvkcsqI0NvwdMhPs29kCNhBBC9DQZmuxiJ0+erN+k+5133uHSSy9t8nlQUBCJiYm8//77gAmadu3aBcAll1zCu+++C8DSpUubvf/s2bN58cUXqa2tBaCgoAAAu91OaWkpANOmTWPjxo0cPXoUgPLycg4fPlw/D+3YsWP19RO9J6StoUkhhBBur1OBmFIqTCm1Sil1xHoNbabMQKXUGqXUAaXUPqXUg+25/nwzcuRIXn/9dcaNG0dBQQH33XffOWWWLl3KkiVLGD9+PKNHj+aTTz4B4Nlnn+Uf//gHU6ZMobi4uNn7//CHP2TQoEGMGzeO8ePH8/bbbwOwcOFC5s2bx4wZM4iMjOS1117jlltuYdy4cUybNo2DBw9is9lYvHgxV199NZdeeimDBw/uvj8I0SZnj1hka0OTQggh3JrSWnf8YqX+ChRorZ9USj0MhGqtF51VJhaI1VrvUErZge3A9Vrr/a5c35zJkyfrbdu2NTl34MABRo4c2eG2dIXGqxvPd33hz9PdVdbU8T8rD/HgzCQCfWWWgBBCuCul1Hat9eTmPuvs0OQC4HXr+HXg+rMLaK2ztNY7rONS4AAwwNXrhXBXNm9PfnP1KAnChBCiH+tsIBattc4CE3ABUa0VVkolABOAr9t7vVJqoVJqm1JqW25ubier3T0SEhLcojdMCCGEED2jzX+KK6VWA80ltfpNex6klAoEPgR+prUuac+1AFrrxcBiMEOTLZSRVYFdoDPD1UIIIYRwXZuBmNZ6ZkufKaVylFKxWussay7Y6RbKeWOCsKVa648afeTS9a6w2Wzk5+cTHh4uwVgnaK3Jz8/HZpMJ5EIIIUR36+zklE+BO4EnrddPzi6gTFS0BDigtX66vde7Kj4+nvT0dPrqsOX5xGazER8f39vVEEIIIdxeZ1dNhgPvAYOAk8BNWusCpVQc8LLWer5S6lJgPbAHcFiXPqq1/ryl69t6bnOrJoUQQggh+qLWVk12qkdMa50PXNXM+UxgvnW8AWh2rLCl64UQQggh+gPJrC+EEEII0UskEBNCCCGE6CWdmiPWW5RSucCJbrp9BJDXTffu6/pT2/tTW8/WH9veH9vs1B/b3h/bDP233dD32z5Yax3Z3AfnZSDWnZRS21qaUOfu+lPb+1Nbz9Yf294f2+zUH9veH9sM/bfdcH63XYYmhRBCCCF6iQRiQgghhBC9RAKxcy3u7Qr0ov7U9v7U1rP1x7b3xzY79ce298c2Q/9tN5zHbZc5YkIIIYQQvUR6xIQQQggheokEYkIIIYQQveS8D8SUUgOVUmuUUgeUUvuUUg9a58OUUquUUkes11Dr/Cyl1Hal1B7r9cpG95pknT+qlHrO2rC8uWc2W04pdblSaodSqlYpdWM/a/svlFL7lVK7lVJfKqUGu3Fb77XOpyilNiilRnVlW/ty2xt9fqNSSiulumW5eF9qs1LqLqVUrvV9pyilftgdbe6Lbbc++4713/Y+pdTb7txepdTfGn3Ph5VSRV3d3j7c9kFWXXYq8//x+f2o7YOV+b21Wym1VikV351tP4fW+rz+AWKBidaxHTgMjAL+CjxsnX8Y+It1PAGIs47HABmN7vUNcBFmb8zlwLwWntlsOSABGAe8AdzYz9o+A/C3ju8D/s+N2xrUqMx1wIr+8j03qsM6YAsw2d3bDNwF/G93fsd9uO3DgZ1AqPU+yp3be1aZnwCv9KPvejFwn3U8Cjjej9r+PnCndXwl8GZ3tv2cevXkw3qkQfAJMAs4BMQ2+sIPNVNWAfmAr1XmYKPPbgH+2cJfnlbLAa/RA4FYX2y7dX4CsLGftPUWYHl/+p6BZ4BrgLV0UyDWl9pMDwdifaztfwV+2F/ae1a5TcCs/tJ24J/AIuv4ImBTP2r7PiC+0b1LerLt5/3QZGNKqQRMEPA1EK21zgKwXqOaueTbwE6tdRUwAEhv9Fm6de5srpbrUX2s7Xdj/rXRLfpCW5VS9yuljmF+Uf20o21pr95uu1JqAjBQa/2vTjWkHXq7zc57WsMWHyilBnawKe3WB9qeBCQppTYqpbYopeZ2vDVt6wPtddZjMJAI/Kcj7eiIPtD2PwC3K6XSgc8xPYI9og+0fZd1T4AbALtSKrwjbekItwnElFKBwIfAz7TWJS6UHw38BfiR81QzxXRzl7pYrsf0pbYrpW4HJgP/3VY9OqKvtFVr/Q+t9VBgEfDbturRFXq77UopD+BvwC9dq3Hn9XabrdfPgASt9ThgNfB6W/XoCn2k7V6Y4ckrMD0ILyulQtqqS0f0kfY6fRf4QGtd11Y9ukIfafstwGta63hgPvCm9d98t+ojbX8ImK6U2glMBzKA2rbq0lXcIhBTSnljvsilWuuPrNM5SqlY6/NY4HSj8vHAMuB7Wutj1ul0oPEEvXggUynl2Wjy5n+1VK472uWKvtR2pdRM4DfAdda/VLpUX2prI+8C13e+da3rI223Y+ZmrFVKHQemAZ+q7puw3xfajNY6v9Hf55eASV3Zzub0lbZbn32ita7RWqdhho2Gd2Vbrfr3lfY6fRd4p2ta17o+1Pa7gfcAtNabARtmI+1u01farrXO1Fp/S2s9AfM7DK11cRc3t2U9OQ7aHT+YKPcN4Jmzzv83TSf8/dU6DsHqhmzmXlsxv1ycE/nmt/DMVsvRQ3PE+lLbMd3Kx4Dh/aCtwxuVuRbY1l++57PKrKX7Juv3mTZjzVexjm8AtvSX7xuYC7xuHUcAp4Bwd22v9dkI4DiYhOf96LteDtxlHY/EBCnd9mfQx9oeAXhYx08A/9Xd332TevXkw7rpy7wU0724G0ixfuYD4cCXwBHrNcwq/1vgTKOyKVgrgTBDansxAcX/tvSXsKVywBRM1H0GM5FwXz9q+2ogp9F9P3Xjtj6LmdyZAqwBRveX7/msMmvpvkCsz7QZ+LP1fe+yvu/k/vJ9Y35hPQ3sB/YA33Xn9lqf/QF4sju/477YdsyKxY3W3/MUYHY/avuN1vMOAy8Dvj3x/Tt/ZIsjIYQQQohe4hZzxIQQQgghzkcSiAkhhBBC9BIJxIQQQggheokEYkIIIYQQvUQCMSGEEEKIXiKBmBBCCCFEL5FATAghhBCil/x/3DhOJakNmOYAAAAASUVORK5CYII=\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "# compare each of the variables since beginning of the year\n", "since = '2020-01-01'\n", "predicted = transform(res, since)\n", "actual = transform(frame_t, since)\n", "\n", "fx_graphs = ['USDCAD', 'AUDJPY'] + [f+'_3m_impvol' for f in ['USDJPY', 'AUDJPY']]\n", "for asset in fx_graphs:\n", " compare_plot(asset, actual, predicted)" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "rates_graphs = ['5y', '30y']\n", "rates_vols = ['3m'+r+'_impvol' for r in rates_graphs] + ['3y_vs_3m_'+r+'_calendar' for r in rates_graphs] + ['3y5y_skew']\n", "\n", "for asset in rates_vols:\n", " compare_plot(asset, actual, predicted)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Disclaimers\n", "\n", "Scenarios/predictions: Simulated results are for illustrative purposes only. GS provides no assurance or guarantee that the strategy will operate or would have operated in the past in a manner consistent with the above analysis. Past performance figures are not a reliable indicator of future results.\n", "\n", "Indicative Terms/Pricing Levels: This material may contain indicative terms only, including but not limited to pricing levels. There is no representation that any transaction can or could have been effected at such terms or prices. Proposed terms and conditions are for discussion purposes only. Finalized terms and conditions are subject to further discussion and negotiation.\n", "www.goldmansachs.com/disclaimer/sales-and-trading-invest-rec-disclosures.html If you are not accessing this material via Marquee ContentStream, a list of the author's investment recommendations disseminated during the preceding 12 months and the proportion of the author's recommendations that are 'buy', 'hold', 'sell' or other over the previous 12 months is available by logging into Marquee ContentStream using the link below. Alternatively, if you do not have access to Marquee ContentStream, please contact your usual GS representative who will be able to provide this information to you.\n", "\n", "Backtesting, Simulated Results, Sensitivity/Scenario Analysis or Spreadsheet Calculator or Model: There may be data presented herein that is solely for illustrative purposes and which may include among other things back testing, simulated results and scenario analyses. The information is based upon certain factors, assumptions and historical information that Goldman Sachs may in its discretion have considered appropriate, however, Goldman Sachs provides no assurance or guarantee that this product will operate or would have operated in the past in a manner consistent with these assumptions. In the event any of the assumptions used do not prove to be true, results are likely to vary materially from the examples shown herein. Additionally, the results may not reflect material economic and market factors, such as liquidity, transaction costs and other expenses which could reduce potential return.\n", "\n", "OTC Derivatives Risk Disclosures: \n", "Terms of the Transaction: To understand clearly the terms and conditions of any OTC derivative transaction you may enter into, you should carefully review the Master Agreement, including any related schedules, credit support documents, addenda and exhibits. You should not enter into OTC derivative transactions unless you understand the terms of the transaction you are entering into as well as the nature and extent of your risk exposure. You should also be satisfied that the OTC derivative transaction is appropriate for you in light of your circumstances and financial condition. You may be requested to post margin or collateral to support written OTC derivatives at levels consistent with the internal policies of Goldman Sachs. \n", " \n", "Liquidity Risk: There is no public market for OTC derivative transactions and, therefore, it may be difficult or impossible to liquidate an existing position on favorable terms. Transfer Restrictions: OTC derivative transactions entered into with one or more affiliates of The Goldman Sachs Group, Inc. (Goldman Sachs) cannot be assigned or otherwise transferred without its prior written consent and, therefore, it may be impossible for you to transfer any OTC derivative transaction to a third party. \n", " \n", "Conflict of Interests: Goldman Sachs may from time to time be an active participant on both sides of the market for the underlying securities, commodities, futures, options or any other derivative or instrument identical or related to those mentioned herein (together, \"the Product\"). Goldman Sachs at any time may have long or short positions in, or buy and sell Products (on a principal basis or otherwise) identical or related to those mentioned herein. Goldman Sachs hedging and trading activities may affect the value of the Products. \n", " \n", "Counterparty Credit Risk: Because Goldman Sachs, may be obligated to make substantial payments to you as a condition of an OTC derivative transaction, you must evaluate the credit risk of doing business with Goldman Sachs or its affiliates. \n", " \n", "Pricing and Valuation: The price of each OTC derivative transaction is individually negotiated between Goldman Sachs and each counterparty and Goldman Sachs does not represent or warrant that the prices for which it offers OTC derivative transactions are the best prices available, possibly making it difficult for you to establish what is a fair price for a particular OTC derivative transaction; The value or quoted price of the Product at any time, however, will reflect many factors and cannot be predicted. If Goldman Sachs makes a market in the offered Product, the price quoted by Goldman Sachs would reflect any changes in market conditions and other relevant factors, and the quoted price (and the value of the Product that Goldman Sachs will use for account statements or otherwise) could be higher or lower than the original price, and may be higher or lower than the value of the Product as determined by reference to pricing models used by Goldman Sachs. If at any time a third party dealer quotes a price to purchase the Product or otherwise values the Product, that price may be significantly different (higher or lower) than any price quoted by Goldman Sachs. Furthermore, if you sell the Product, you will likely be charged a commission for secondary market transactions, or the price will likely reflect a dealer discount. Goldman Sachs may conduct market making activities in the Product. To the extent Goldman Sachs makes a market, any price quoted for the OTC derivative transactions, Goldman Sachs may differ significantly from (i) their value determined by reference to Goldman Sachs pricing models and (ii) any price quoted by a third party. The market price of the OTC derivative transaction may be influenced by many unpredictable factors, including economic conditions, the creditworthiness of Goldman Sachs, the value of any underlyers, and certain actions taken by Goldman Sachs. \n", " \n", "Market Making, Investing and Lending: Goldman Sachs engages in market making, investing and lending businesses for its own account and the accounts of its affiliates in the same or similar instruments underlying OTC derivative transactions (including such trading as Goldman Sachs deems appropriate in its sole discretion to hedge its market risk in any OTC derivative transaction whether between Goldman Sachs and you or with third parties) and such trading may affect the value of an OTC derivative transaction. \n", " \n", "Early Termination Payments: The provisions of an OTC Derivative Transaction may allow for early termination and, in such cases, either you or Goldman Sachs may be required to make a potentially significant termination payment depending upon whether the OTC Derivative Transaction is in-the-money to Goldman Sachs or you at the time of termination. Indexes: Goldman Sachs does not warrant, and takes no responsibility for, the structure, method of computation or publication of any currency exchange rates, interest rates, indexes of such rates, or credit, equity or other indexes, unless Goldman Sachs specifically advises you otherwise.\n", "Risk Disclosure Regarding futures, options, equity swaps, and other derivatives as well as non-investment-grade securities and ADRs: Please ensure that you have read and understood the current options, futures and security futures disclosure document before entering into any such transactions. Current United States listed options, futures and security futures disclosure documents are available from our sales representatives or at http://www.theocc.com/components/docs/riskstoc.pdf, http://www.goldmansachs.com/disclosures/risk-disclosure-for-futures.pdf and https://www.nfa.futures.org/investors/investor-resources/files/security-futures-disclosure.pdf, respectively. Certain transactions - including those involving futures, options, equity swaps, and other derivatives as well as non-investment-grade securities - give rise to substantial risk and are not available to nor suitable for all investors. If you have any questions about whether you are eligible to enter into these transactions with Goldman Sachs, please contact your sales representative. Foreign-currency-denominated securities are subject to fluctuations in exchange rates that could have an adverse effect on the value or price of, or income derived from, the investment. In addition, investors in securities such as ADRs, the values of which are influenced by foreign currencies, effectively assume currency risk.\n", "Options Risk Disclosures: Options may trade at a value other than that which may be inferred from the current levels of interest rates, dividends (if applicable) and the underlier due to other factors including, but not limited to, expectations of future levels of interest rates, future levels of dividends and the volatility of the underlier at any time prior to maturity. Note: Options involve risk and are not suitable for all investors. Please ensure that you have read and understood the current options disclosure document before entering into any standardized options transactions. United States listed options disclosure documents are available from our sales representatives or at http://theocc.com/publications/risks/riskstoc.pdf. A secondary market may not be available for all options. Transaction costs may be a significant factor in option strategies calling for multiple purchases and sales of options, such as spreads. When purchasing long options an investor may lose their entire investment and when selling uncovered options the risk is potentially unlimited. Supporting documentation for any comparisons, recommendations, statistics, technical data, or other similar information will be supplied upon request.\n", "This material is for the private information of the recipient only. This material is not sponsored, endorsed, sold or promoted by any sponsor or provider of an index referred herein (each, an \"Index Provider\"). GS does not have any affiliation with or control over the Index Providers or any control over the computation, composition or dissemination of the indices. While GS will obtain information from publicly available sources it believes reliable, it will not independently verify this information. Accordingly, GS shall have no liability, contingent or otherwise, to the user or to third parties, for the quality, accuracy, timeliness, continued availability or completeness of the data nor for any special, indirect, incidental or consequential damages which may be incurred or experienced because of the use of the data made available herein, even if GS has been advised of the possibility of such damages.\n", "Standard & Poor's ® and S&P ® are registered trademarks of The McGraw-Hill Companies, Inc. and S&P GSCI™ is a trademark of The McGraw-Hill Companies, Inc. and have been licensed for use by the Issuer. This Product (the \"Product\") is not sponsored, endorsed, sold or promoted by S&P and S&P makes no representation, warranty or condition regarding the advisability of investing in the Product.\n", "Notice to Brazilian Investors\n", "Marquee is not meant for the general public in Brazil. The services or products provided by or through Marquee, at any time, may not be offered or sold to the general public in Brazil. You have received a password granting access to Marquee exclusively due to your existing relationship with a GS business located in Brazil. The selection and engagement with any of the offered services or products through Marquee, at any time, will be carried out directly by you. Before acting to implement any chosen service or products, provided by or through Marquee you should consider, at your sole discretion, whether it is suitable for your particular circumstances and, if necessary, seek professional advice. Any steps necessary in order to implement the chosen service or product, including but not limited to remittance of funds, shall be carried out at your discretion. Accordingly, such services and products have not been and will not be publicly issued, placed, distributed, offered or negotiated in the Brazilian capital markets and, as a result, they have not been and will not be registered with the Brazilian Securities and Exchange Commission (Comissão de Valores Mobiliários), nor have they been submitted to the foregoing agency for approval. Documents relating to such services or products, as well as the information contained therein, may not be supplied to the general public in Brazil, as the offering of such services or products is not a public offering in Brazil, nor used in connection with any offer for subscription or sale of securities to the general public in Brazil.\n", "The offer of any securities mentioned in this message may not be made to the general public in Brazil. Accordingly, any such securities have not been nor will they be registered with the Brazilian Securities and Exchange Commission (Comissão de Valores Mobiliários) nor has any offer been submitted to the foregoing agency for approval. Documents relating to the offer, as well as the information contained therein, may not be supplied to the public in Brazil, as the offer is not a public offering of securities in Brazil. These terms will apply on every access to Marquee.\n", "Ouvidoria Goldman Sachs Brasil: 0800 727 5764 e/ou ouvidoriagoldmansachs@gs.com\n", "Horário de funcionamento: segunda-feira à sexta-feira (exceto feriados), das 9hs às 18hs.\n", "Ombudsman Goldman Sachs Brazil: 0800 727 5764 and / or ouvidoriagoldmansachs@gs.com\n", "Available Weekdays (except holidays), from 9 am to 6 pm.\n", "\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/events/00_gsquant_meets_markets/00_us_election_analysis/0001_portfolios_and_var.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 7, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [], "source": [ "import pandas as pd\n", "import numpy as np\n", "import datetime\n", "import seaborn as sns\n", "import matplotlib.pyplot as plt\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.risk import MarketDataShockBasedScenario, MarketDataPattern, MarketDataShock, MarketDataShockType, PnlExplain\n", "from gs_quant.markets import PricingContext\n", "plt.rcParams['figure.figsize'] = (16, 8)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics', 'read_product_data'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this notebook, we'll load a portfolio and run analytics to understand it's risk and pnl. We'll also run a number of vol and spot scenarios which we'll use to compute VaR.\n", "\n", "The content of this notebook is split into the following parts:\n", "* [1: First, the portfolio](#1:-First,-the-portfolio)\n", "* [2: Run risk and PnL explain](#2:-Run-risk-and-PnL-explain)\n", "* [3: Scenarios grid and VaR](#3:-Scenarios-grid-and-VaR)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1: First, the portfolio\n", "\n", "Let’s first load a portfolio from csv, mapping each column and look at it as a dataframe. Internal users can load this books directly using `from book`." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
instrument<gs_quant.target.instrument.FXOption object at 0x0000000019252518>
asset_classAssetClass.FX
typeAssetType.Option
buy_sellBuySell.Buy
call_amount25000000
call_currencyCurrency.AUD
expiration_date2020-10-21
expiration_timeNYC
method_of_settlementOptionSettlementMethod.Physical
notional_amount2.5e+07
option_typeOptionType.Call
pairAUD USD
premium0
premium_currencyCurrency.USD
premium_payment_date2020-10-23
put_amount18750000
put_currencyCurrency.USD
settlement_date2020-10-23
strike_price0.75
\n", "
" ], "text/plain": [ "instrument \n", "asset_class AssetClass.FX \n", "type AssetType.Option \n", "buy_sell BuySell.Buy \n", "call_amount 25000000 \n", "call_currency Currency.AUD \n", "expiration_date 2020-10-21 \n", "expiration_time NYC \n", "method_of_settlement OptionSettlementMethod.Physical \n", "notional_amount 2.5e+07 \n", "option_type OptionType.Call \n", "pair AUD USD \n", "premium 0 \n", "premium_currency Currency.USD \n", "premium_payment_date 2020-10-23 \n", "put_amount 18750000 \n", "put_currency Currency.USD \n", "settlement_date 2020-10-23 \n", "strike_price 0.75 " ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mappers = {\n", " 'pair': lambda row: row['foreign ccy'] + 'USD',\n", " 'notional_amount': 'notional',\n", " 'expiration_date': 'expiry',\n", " 'option_type': lambda row: 'Call' if row['C/P'] == 'C' else 'Put',\n", " 'strike_price': 'strike',\n", " 'premium': lambda row: 0\n", "}\n", "portfolio = Portfolio.from_csv(r'FXBook.csv', mappings=mappers)\n", "portfolio.resolve()\n", "frame = portfolio.to_frame()\n", "frame.index = frame.index.droplevel(0)\n", "frame.head(1).transpose()\n", "\n", "# for internal users:\n", "# portfolio = Portfolio.from_book('my_book_id')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2: Run risk and PnL explain\n", "\n", "With the portfolio in hand, let’s use gs-quant to understand risk and pnl over the last business day." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "from gs_quant.datetime.date import business_day_offset\n", "from gs_quant.markets import CloseMarket, PricingContext, close_market_date\n", "from gs_quant.risk import Price, DollarPrice, PnlExplain, Theta, FXDelta, FXGamma, FXVega\n", "\n", "to_date = business_day_offset(close_market_date(), -1)\n", "\n", "# Previous business day\n", "from_date = business_day_offset(to_date, -1)\n", "\n", "# A risk measure for calculating PnlExplain from that date\n", "explain = PnlExplain(CloseMarket(date=to_date))\n", "\n", "# Calculate PnlExplain and dollar price from 1 day ago\n", "with PricingContext(pricing_date=from_date):\n", " result = portfolio.calc((FXDelta, FXGamma, FXVega, DollarPrice, Theta, explain))\n", " \n", "# Calculate dollar price with today's market\n", "with PricingContext(pricing_date=to_date):\n", " target_price = portfolio.calc((DollarPrice))\n", "\n", "with PricingContext(pricing_date=from_date, market=CloseMarket(date=to_date)):\n", " target_price_ex_theta = portfolio.calc((DollarPrice))" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Risk and 1day Pnl as of 2020-09-04\n", "Book PV (in mUSD): 15.8\n", "Book Delta (in mUSD): 186\n", "Book Vega (in kUSD): 1390\n", "Dollar price day on day change (in kUSD): 770\n", "Dollar price day on day change(ex theta) (in kUSD): 799\n", "Pnl explain total (in kUSD): 799\n", "Theta total (in kUSD): -61\n", "Theta + Pnl explain total (in kUSD): 738\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
mkt_typemkt_assetvalue
0FXUSD/AUD775947.0
3FX EVENTSUSD-12.0
4FX FWDUSD/AUD1419.0
5FX VOLUSD/AUD58135.0
6IRAUD-50239.0
7IR BASISAUD OIS/AUD-3M35.0
8IR BASISAUD-3M/AUD-6M2843.0
11IR XCAUD11020.0
\n", "
" ], "text/plain": [ " mkt_type mkt_asset value\n", "0 FX USD/AUD 775947.0\n", "3 FX EVENTS USD -12.0\n", "4 FX FWD USD/AUD 1419.0\n", "5 FX VOL USD/AUD 58135.0\n", "6 IR AUD -50239.0\n", "7 IR BASIS AUD OIS/AUD-3M 35.0\n", "8 IR BASIS AUD-3M/AUD-6M 2843.0\n", "11 IR XC AUD 11020.0" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "print('Risk and 1day Pnl as of '+str(from_date))\n", "print(f'Book PV (in mUSD): {target_price[DollarPrice].aggregate()/1e6:.1f}')\n", "print(f'Book Delta (in mUSD): {result[FXDelta].aggregate().value.sum()/1e6:.0f}')\n", "print(f'Book Vega (in kUSD): {result[FXVega].aggregate().value.sum():.0f}')\n", "print(f'Dollar price day on day change (in kUSD): {target_price[DollarPrice].aggregate()/1e3 - result[DollarPrice].aggregate()/1e3:.0f}')\n", "print(f'Dollar price day on day change(ex theta) (in kUSD): {target_price_ex_theta[DollarPrice].aggregate()/1e3 - result[DollarPrice].aggregate()/1e3:.0f}')\n", "print(f'Pnl explain total (in kUSD): {result[explain].aggregate().value.sum()/1e3:.0f}')\n", "print(f'Theta total (in kUSD): {result[Theta].aggregate().value.sum()/1e3:.0f}')\n", "print(f'Theta + Pnl explain total (in kUSD): {result[Theta].aggregate().value.sum()/1e3 + result[explain].aggregate().value.sum()/1e3:.0f}')\n", "\n", "# Show the PnlExplain breakdown\n", "explain_all = result[explain].aggregate()\n", "explain_all[explain_all.value.abs() > 1.0].round(0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3: Scenarios grid and VaR\n", "\n", "We can also better understand risk by applying a number of market shocks – in this case we’ll look at a grid of vol and spot shocks, we are can also use to calculate VaR by looking at the 95% percentile price change." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "shocks_fx = [-10, -7.5, -5, -2,-1, 0, 1, 2, 5, 7.5, 10]\n", "shocks_vols = [-5, -2, -1, -0.5, 0, 0.5, 1, 2, 5, 7.5, 10]\n", "shocked_prices = pd.DataFrame(index=shocks_vols, columns=shocks_fx)\n", "\n", "cross = explain_all[explain_all['mkt_type'] == 'FX']['mkt_asset'][0]\n", "\n", "with PricingContext(is_async=True):\n", " for fx in shocks_fx:\n", " for vol in shocks_vols:\n", " with MarketDataShockBasedScenario({\n", " MarketDataPattern('FX', cross): MarketDataShock(MarketDataShockType.Proportional, fx / 1e2),\n", " MarketDataPattern('FX Vol', cross, 'ATM Vol'): MarketDataShock(MarketDataShockType.Absolute, vol / 1e2),\n", " }):\n", " shocked_prices[fx][vol] = portfolio.calc((DollarPrice))" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "# Aggregate and compute pnl by substracting 0 shock PV\n", "shocked_prices_res = pd.DataFrame(index=shocks_vols, columns=shocks_fx, dtype='float')\n", "\n", "for fx in shocks_fx:\n", " for vol in shocks_vols:\n", " shocked_prices_res[fx][vol] = shocked_prices[fx][vol].aggregate()\n", " \n", "shocked_prices_res -= shocked_prices[0][0].aggregate()\n", "shocked_prices_res /= 1e3\n", "\n", "ax = sns.heatmap(shocked_prices_res, cmap='coolwarm', annot=True, fmt='.1f')\n", "ax.set(ylabel='absolute vol point moves', xlabel='% spot change', title='PV changes ($k)')\n", "ax.xaxis.tick_top()\n", "ax.xaxis.set_label_position('top')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Compute VaR" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Portfolio base price ($m): 15.2\n", "Scenario Based VaR with spot/vol grid ($m): -10.7\n" ] } ], "source": [ "p = np.percentile(shocked_prices_res, 5) # return 95th percentile\n", "print('Portfolio base price ($m): {:,.1f}'.format(portfolio.price().aggregate()/1e6))\n", "print('Scenario Based VaR with spot/vol grid ($m): {:,.1f}'.format(p/1e3))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Disclaimers\n", "\n", "Scenarios/predictions: Simulated results are for illustrative purposes only. GS provides no assurance or guarantee that the strategy will operate or would have operated in the past in a manner consistent with the above analysis. Past performance figures are not a reliable indicator of future results.\n", "\n", "Indicative Terms/Pricing Levels: This material may contain indicative terms only, including but not limited to pricing levels. There is no representation that any transaction can or could have been effected at such terms or prices. Proposed terms and conditions are for discussion purposes only. Finalized terms and conditions are subject to further discussion and negotiation.\n", "www.goldmansachs.com/disclaimer/sales-and-trading-invest-rec-disclosures.html If you are not accessing this material via Marquee ContentStream, a list of the author's investment recommendations disseminated during the preceding 12 months and the proportion of the author's recommendations that are 'buy', 'hold', 'sell' or other over the previous 12 months is available by logging into Marquee ContentStream using the link below. Alternatively, if you do not have access to Marquee ContentStream, please contact your usual GS representative who will be able to provide this information to you.\n", "\n", "Backtesting, Simulated Results, Sensitivity/Scenario Analysis or Spreadsheet Calculator or Model: There may be data presented herein that is solely for illustrative purposes and which may include among other things back testing, simulated results and scenario analyses. The information is based upon certain factors, assumptions and historical information that Goldman Sachs may in its discretion have considered appropriate, however, Goldman Sachs provides no assurance or guarantee that this product will operate or would have operated in the past in a manner consistent with these assumptions. In the event any of the assumptions used do not prove to be true, results are likely to vary materially from the examples shown herein. Additionally, the results may not reflect material economic and market factors, such as liquidity, transaction costs and other expenses which could reduce potential return.\n", "\n", "OTC Derivatives Risk Disclosures: \n", "Terms of the Transaction: To understand clearly the terms and conditions of any OTC derivative transaction you may enter into, you should carefully review the Master Agreement, including any related schedules, credit support documents, addenda and exhibits. You should not enter into OTC derivative transactions unless you understand the terms of the transaction you are entering into as well as the nature and extent of your risk exposure. You should also be satisfied that the OTC derivative transaction is appropriate for you in light of your circumstances and financial condition. You may be requested to post margin or collateral to support written OTC derivatives at levels consistent with the internal policies of Goldman Sachs. \n", " \n", "Liquidity Risk: There is no public market for OTC derivative transactions and, therefore, it may be difficult or impossible to liquidate an existing position on favorable terms. Transfer Restrictions: OTC derivative transactions entered into with one or more affiliates of The Goldman Sachs Group, Inc. (Goldman Sachs) cannot be assigned or otherwise transferred without its prior written consent and, therefore, it may be impossible for you to transfer any OTC derivative transaction to a third party. \n", " \n", "Conflict of Interests: Goldman Sachs may from time to time be an active participant on both sides of the market for the underlying securities, commodities, futures, options or any other derivative or instrument identical or related to those mentioned herein (together, \"the Product\"). Goldman Sachs at any time may have long or short positions in, or buy and sell Products (on a principal basis or otherwise) identical or related to those mentioned herein. Goldman Sachs hedging and trading activities may affect the value of the Products. \n", " \n", "Counterparty Credit Risk: Because Goldman Sachs, may be obligated to make substantial payments to you as a condition of an OTC derivative transaction, you must evaluate the credit risk of doing business with Goldman Sachs or its affiliates. \n", " \n", "Pricing and Valuation: The price of each OTC derivative transaction is individually negotiated between Goldman Sachs and each counterparty and Goldman Sachs does not represent or warrant that the prices for which it offers OTC derivative transactions are the best prices available, possibly making it difficult for you to establish what is a fair price for a particular OTC derivative transaction; The value or quoted price of the Product at any time, however, will reflect many factors and cannot be predicted. If Goldman Sachs makes a market in the offered Product, the price quoted by Goldman Sachs would reflect any changes in market conditions and other relevant factors, and the quoted price (and the value of the Product that Goldman Sachs will use for account statements or otherwise) could be higher or lower than the original price, and may be higher or lower than the value of the Product as determined by reference to pricing models used by Goldman Sachs. If at any time a third party dealer quotes a price to purchase the Product or otherwise values the Product, that price may be significantly different (higher or lower) than any price quoted by Goldman Sachs. Furthermore, if you sell the Product, you will likely be charged a commission for secondary market transactions, or the price will likely reflect a dealer discount. Goldman Sachs may conduct market making activities in the Product. To the extent Goldman Sachs makes a market, any price quoted for the OTC derivative transactions, Goldman Sachs may differ significantly from (i) their value determined by reference to Goldman Sachs pricing models and (ii) any price quoted by a third party. The market price of the OTC derivative transaction may be influenced by many unpredictable factors, including economic conditions, the creditworthiness of Goldman Sachs, the value of any underlyers, and certain actions taken by Goldman Sachs. \n", " \n", "Market Making, Investing and Lending: Goldman Sachs engages in market making, investing and lending businesses for its own account and the accounts of its affiliates in the same or similar instruments underlying OTC derivative transactions (including such trading as Goldman Sachs deems appropriate in its sole discretion to hedge its market risk in any OTC derivative transaction whether between Goldman Sachs and you or with third parties) and such trading may affect the value of an OTC derivative transaction. \n", " \n", "Early Termination Payments: The provisions of an OTC Derivative Transaction may allow for early termination and, in such cases, either you or Goldman Sachs may be required to make a potentially significant termination payment depending upon whether the OTC Derivative Transaction is in-the-money to Goldman Sachs or you at the time of termination. Indexes: Goldman Sachs does not warrant, and takes no responsibility for, the structure, method of computation or publication of any currency exchange rates, interest rates, indexes of such rates, or credit, equity or other indexes, unless Goldman Sachs specifically advises you otherwise.\n", "Risk Disclosure Regarding futures, options, equity swaps, and other derivatives as well as non-investment-grade securities and ADRs: Please ensure that you have read and understood the current options, futures and security futures disclosure document before entering into any such transactions. Current United States listed options, futures and security futures disclosure documents are available from our sales representatives or at http://www.theocc.com/components/docs/riskstoc.pdf, http://www.goldmansachs.com/disclosures/risk-disclosure-for-futures.pdf and https://www.nfa.futures.org/investors/investor-resources/files/security-futures-disclosure.pdf, respectively. Certain transactions - including those involving futures, options, equity swaps, and other derivatives as well as non-investment-grade securities - give rise to substantial risk and are not available to nor suitable for all investors. If you have any questions about whether you are eligible to enter into these transactions with Goldman Sachs, please contact your sales representative. Foreign-currency-denominated securities are subject to fluctuations in exchange rates that could have an adverse effect on the value or price of, or income derived from, the investment. In addition, investors in securities such as ADRs, the values of which are influenced by foreign currencies, effectively assume currency risk.\n", "Options Risk Disclosures: Options may trade at a value other than that which may be inferred from the current levels of interest rates, dividends (if applicable) and the underlier due to other factors including, but not limited to, expectations of future levels of interest rates, future levels of dividends and the volatility of the underlier at any time prior to maturity. Note: Options involve risk and are not suitable for all investors. Please ensure that you have read and understood the current options disclosure document before entering into any standardized options transactions. United States listed options disclosure documents are available from our sales representatives or at http://theocc.com/publications/risks/riskstoc.pdf. A secondary market may not be available for all options. Transaction costs may be a significant factor in option strategies calling for multiple purchases and sales of options, such as spreads. When purchasing long options an investor may lose their entire investment and when selling uncovered options the risk is potentially unlimited. Supporting documentation for any comparisons, recommendations, statistics, technical data, or other similar information will be supplied upon request.\n", "This material is for the private information of the recipient only. This material is not sponsored, endorsed, sold or promoted by any sponsor or provider of an index referred herein (each, an \"Index Provider\"). GS does not have any affiliation with or control over the Index Providers or any control over the computation, composition or dissemination of the indices. While GS will obtain information from publicly available sources it believes reliable, it will not independently verify this information. Accordingly, GS shall have no liability, contingent or otherwise, to the user or to third parties, for the quality, accuracy, timeliness, continued availability or completeness of the data nor for any special, indirect, incidental or consequential damages which may be incurred or experienced because of the use of the data made available herein, even if GS has been advised of the possibility of such damages.\n", "Standard & Poor's ® and S&P ® are registered trademarks of The McGraw-Hill Companies, Inc. and S&P GSCI™ is a trademark of The McGraw-Hill Companies, Inc. and have been licensed for use by the Issuer. This Product (the \"Product\") is not sponsored, endorsed, sold or promoted by S&P and S&P makes no representation, warranty or condition regarding the advisability of investing in the Product.\n", "Notice to Brazilian Investors\n", "Marquee is not meant for the general public in Brazil. The services or products provided by or through Marquee, at any time, may not be offered or sold to the general public in Brazil. You have received a password granting access to Marquee exclusively due to your existing relationship with a GS business located in Brazil. The selection and engagement with any of the offered services or products through Marquee, at any time, will be carried out directly by you. Before acting to implement any chosen service or products, provided by or through Marquee you should consider, at your sole discretion, whether it is suitable for your particular circumstances and, if necessary, seek professional advice. Any steps necessary in order to implement the chosen service or product, including but not limited to remittance of funds, shall be carried out at your discretion. Accordingly, such services and products have not been and will not be publicly issued, placed, distributed, offered or negotiated in the Brazilian capital markets and, as a result, they have not been and will not be registered with the Brazilian Securities and Exchange Commission (Comissão de Valores Mobiliários), nor have they been submitted to the foregoing agency for approval. Documents relating to such services or products, as well as the information contained therein, may not be supplied to the general public in Brazil, as the offering of such services or products is not a public offering in Brazil, nor used in connection with any offer for subscription or sale of securities to the general public in Brazil.\n", "The offer of any securities mentioned in this message may not be made to the general public in Brazil. Accordingly, any such securities have not been nor will they be registered with the Brazilian Securities and Exchange Commission (Comissão de Valores Mobiliários) nor has any offer been submitted to the foregoing agency for approval. Documents relating to the offer, as well as the information contained therein, may not be supplied to the public in Brazil, as the offer is not a public offering of securities in Brazil. These terms will apply on every access to Marquee.\n", "Ouvidoria Goldman Sachs Brasil: 0800 727 5764 e/ou ouvidoriagoldmansachs@gs.com\n", "Horário de funcionamento: segunda-feira à sexta-feira (exceto feriados), das 9hs às 18hs.\n", "Ombudsman Goldman Sachs Brazil: 0800 727 5764 and / or ouvidoriagoldmansachs@gs.com\n", "Available Weekdays (except holidays), from 9 am to 6 pm.\n", "\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/events/00_gsquant_meets_markets/00_us_election_analysis/0002_past_elections.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 8, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [], "source": [ "from gs_quant.data import Dataset\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.timeseries import returns\n", "from gs_quant.risk import MarketDataPattern, MarketDataShock, MarketDataShockBasedScenario, MarketDataShockType\n", "from gs_quant.instrument import IRSwap\n", "from gs_quant.common import PayReceive\n", "from gs_quant import risk\n", "import datetime\n", "import pandas as pd\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from gs_quant.datetime.date import business_day_offset" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics', 'read_product_data'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this notebook, we'll look at how several rate and fx instruments moved in the 2012 and 2016 elections and use these moves as shocks for our portfolio.\n", "\n", "The content of this notebook is split into the following parts:\n", "* [1: Visualize last 2 elections](#1:-Visualize-last-2-elections)\n", "* [2: Grab portfolio](#2:-Grab-portfolio)\n", "* [3: Use prior elections as shocks](#3:-Use-prior-elections-as-shocks)\n", "* [4: Custom shocks](#4:-Custom-shocks)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1: Visualize last 2 elections\n", "\n", "Let's start by retrieving the data from the [Marquee data catalog](https://marquee.gs.com/s/discover/data-services/catalog) and visualizing it." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "elec_12 = datetime.date(2012, 11, 6)\n", "elec_16 = datetime.date(2016, 11, 8)\n", "elec_20 = datetime.date(2020, 11, 3)\n", "\n", "t0 = datetime.date(2012, 1, 1)\n", "tn = datetime.date.today()\n", "\n", "fx = Dataset('FXSPOT_PREMIUM').get_data(t0, tn, bbid='AUDJPY', tenor='3m', fields=('spot',))[['spot']].spot\n", "fx_vol = Dataset('FXIMPLIEDVOL_PREMIUM').get_data(t0, tn, bbid='AUDJPY', deltaStrike='25DC', tenor='3m')[['impliedVolatility']].impliedVolatility\n", "rates = Dataset('IR_SWAP2').get_data(t0, assetId=['MAAXGV0GZTW4GFNC'], tenor='30y', fields=('rate',))[['rate']].rate" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [], "source": [ "def slice_data(df, y_0):\n", " df_period = pd.concat((df.loc[x:y].reset_index() for x, y in\n", " [(datetime.date(t, 8, 1), datetime.date(t + 1, 1, 31)) for t in [2012, 2016]]), axis=1)\n", " df_period['month'] = pd.DatetimeIndex(df_period.date.iloc[:, 0]).month_name()\n", " df_period.set_index('month', drop=True, inplace=True)\n", " df_period.drop('date', axis=1, inplace=True)\n", " df_period.columns = ['Democrat win 2012', 'Republican win 2016']\n", " return df_period\n", "\n", "def plot_data(title, data):\n", " ax = data.plot(title=title, figsize=(10, 4))\n", " plt.axvline(x=len(fx[datetime.date(2012, 8, 1):datetime.date(2012, 11, 5)]), linestyle='--', color='r')\n", " ax.set_xticks([21*k for k in range(6)])\n", " _ = ax.set_xticklabels(['August', 'September', 'October', 'November', 'December', 'January'], horizontalalignment='left')" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "fx_sliced, fx_vol_sliced, rates_sliced = [slice_data(x, 2012) for x in [fx, fx_vol, rates]]" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plot_data('AUDJPY', fx_sliced)\n", "plot_data('AUDJPY 3m vol', fx_vol_sliced)\n", "plot_data('30y rates', rates_sliced)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2: Grab portfolio" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's use our FX book from the previous notebook and add a few rates trades. We'll shock this book in the next step." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
asset_classtypebuy_sellcall_amountcall_currencyclearing_houseeffective_dateexpiration_dateexpiration_timefee...pay_or_receivepremiumpremium_currencypremium_payment_dateprincipal_exchangeput_amountput_currencysettlement_datestrike_pricetermination_date
instrument
<gs_quant.target.instrument.FXOption object at 0x000000001CE38860>AssetClass.FXAssetType.OptionBuySell.Sell-27300000Currency.USD2027-03-24NYC...0Currency.USD2027-03-30-42000000Currency.AUD2027-03-300.65
<gs_quant.target.instrument.IRSwap object at 0x00000000050652E8>AssetClass.RatesAssetType.SwapSwapClearingHouse.NONE2020-09-110...PayReceive.ReceivePrincipalExchange._None2030-09-11
<gs_quant.target.instrument.IRSwap object at 0x000000001CE32D68>AssetClass.RatesAssetType.SwapSwapClearingHouse.NONE2020-09-110...PayReceive.PayPrincipalExchange._None2025-09-11
\n", "

3 rows × 37 columns

\n", "
" ], "text/plain": [ " asset_class \\\n", "instrument \n", "\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
spotimpliedVolatilityrate
2012 Election0.1-1.1-6.2
2016 Election2.36.025.9
\n", "" ], "text/plain": [ " spot impliedVolatility rate\n", "2012 Election 0.1 -1.1 -6.2\n", "2016 Election 2.3 6.0 25.9" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "time_window = 2 \n", "effective12 = business_day_offset(elec_12, time_window) \n", "effective16 = business_day_offset(elec_16, time_window) \n", "\n", "fx_shocks = fx.pct_change(time_window+5).loc[[effective12, effective16]] \n", "fx_vol_shocks = fx_vol.pct_change(time_window+5).loc[[effective12, effective16]] \n", "ir_shocks = rates.diff(time_window+5).loc[[effective12, effective16]] \n", "\n", "shock_df = pd.concat([fx_shocks*1e2, fx_vol_shocks*1e2, ir_shocks*1e4], axis=1).round(1) \n", "shock_df.index = ['2012 Election', '2016 Election'] \n", "\n", "def get_market_scenario(s_date): \n", " return MarketDataShockBasedScenario(shocks={ \n", " MarketDataPattern('IR', 'USD'): MarketDataShock(MarketDataShockType.Absolute, ir_shocks.loc[s_date]), \n", " MarketDataPattern('FX', 'USD/AUD'): MarketDataShock(MarketDataShockType.Proportional, fx_shocks.loc[s_date]), \n", " MarketDataPattern('FX', 'JPY/USD'): MarketDataShock(MarketDataShockType.Proportional, 0), \n", " MarketDataPattern('FX Vol', 'JPY/AUD', 'ATM Vol'): MarketDataShock(MarketDataShockType.Absolute, fx_vol_shocks.loc[s_date]),\n", " MarketDataPattern('FX Vol', 'USD/AUD', 'ATM Vol'): MarketDataShock(MarketDataShockType.Absolute, fx_vol_shocks.loc[s_date]),\n", " MarketDataPattern('FX Vol', 'JPY/USD', 'ATM Vol'): MarketDataShock(MarketDataShockType.Absolute, fx_vol_shocks.loc[s_date]),\n", "\n", " }) \n", "\n", "win_12 = get_market_scenario(effective12) \n", "win_16 = get_market_scenario(effective16) \n", "shock_df" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "# scenario carrying swap to election day and then applying spot shock\n", "results, results_agg = {}, {}\n", "\n", "with win_12:\n", " results['2012'] = portfolio.dollar_price()\n", "\n", "with win_16:\n", " results['2016'] = portfolio.dollar_price()\n", "\n", "results['eod'] = portfolio.dollar_price()\n", "\n", "for scenario in ['2012', '2016']:\n", " results_agg[scenario] = results[scenario].aggregate() - results['eod'].aggregate()\n", "\n", "plt.bar(results_agg.keys(), results_agg.values())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4: Custom shocks\n", "\n", "Finally let's look at how to run a completely custom shock." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
mkt_typemkt_assetmkt_classmkt_pointvalue
25IRAUDSWAP5Y-31481.0
54IRUSDSWAP5Y275314.0
59IRUSDSWAP10Y-249668.0
\n", "
" ], "text/plain": [ " mkt_type mkt_asset mkt_class mkt_point value\n", "25 IR AUD SWAP 5Y -31481.0\n", "54 IR USD SWAP 5Y 275314.0\n", "59 IR USD SWAP 10Y -249668.0" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# get points available to shock by looking at irdelta \n", "threshold = 25000\n", "delta = portfolio.calc(risk.IRDelta)\n", "delta.aggregate()[(delta.aggregate().value>threshold) | (delta.aggregate().value<-threshold)].round()" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
mkt_typemkt_assetmkt_classmkt_pointshock
0IRAUDSWAP5YNaN
1IRUSDSWAP10YNaN
2IRUSDSWAP5YNaN
\n", "
" ], "text/plain": [ " mkt_type mkt_asset mkt_class mkt_point shock\n", "0 IR AUD SWAP 5Y NaN\n", "1 IR USD SWAP 10Y NaN\n", "2 IR USD SWAP 5Y NaN" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "custom_shocks = pd.read_csv(r'curve_shocks.csv', na_values='')\n", "custom_shocks" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
mkt_typemkt_assetmkt_classmkt_pointshock
0IRAUDSWAP5Y50
1IRUSDSWAP10Y40
2IRUSDSWAP5Y30
\n", "
" ], "text/plain": [ " mkt_type mkt_asset mkt_class mkt_point shock\n", "0 IR AUD SWAP 5Y 50\n", "1 IR USD SWAP 10Y 40\n", "2 IR USD SWAP 5Y 30" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Fill in your shocks programatically or in excel\n", "custom_shocks['shock'] = [50, 40, 30]\n", "custom_shocks" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "shocks = {\n", " MarketDataPattern(custom_shocks.at[i, 'mkt_type'], custom_shocks.at[i, 'mkt_asset'], custom_shocks.at[i, 'mkt_class']):\n", " MarketDataShock(MarketDataShockType.Absolute, custom_shocks.at[i, 'shock'] / 1e4) for i in range(len(custom_shocks)-2)\n", "}\n", "\n", "shocks.update({\n", " MarketDataPattern(custom_shocks.at[i, 'mkt_type'], custom_shocks.at[i, 'mkt_asset'], custom_shocks.at[i, 'mkt_class'],\n", " (custom_shocks.at[i, 'mkt_point'],)): \n", " MarketDataShock(MarketDataShockType.Absolute, custom_shocks.at[i, 'shock'] / 1e4) for i in [len(custom_shocks)-2, len(custom_shocks)-1]\n", "})\n", "\n", "with MarketDataShockBasedScenario(shocks):\n", " results_agg['custom shocks'] = portfolio.dollar_price().aggregate() - results['eod'].aggregate()\n", "\n", "plt.bar(results_agg.keys(), results_agg.values())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Disclaimers\n", "\n", "Scenarios/predictions: Simulated results are for illustrative purposes only. GS provides no assurance or guarantee that the strategy will operate or would have operated in the past in a manner consistent with the above analysis. Past performance figures are not a reliable indicator of future results.\n", "\n", "Indicative Terms/Pricing Levels: This material may contain indicative terms only, including but not limited to pricing levels. There is no representation that any transaction can or could have been effected at such terms or prices. Proposed terms and conditions are for discussion purposes only. Finalized terms and conditions are subject to further discussion and negotiation.\n", "www.goldmansachs.com/disclaimer/sales-and-trading-invest-rec-disclosures.html If you are not accessing this material via Marquee ContentStream, a list of the author's investment recommendations disseminated during the preceding 12 months and the proportion of the author's recommendations that are 'buy', 'hold', 'sell' or other over the previous 12 months is available by logging into Marquee ContentStream using the link below. Alternatively, if you do not have access to Marquee ContentStream, please contact your usual GS representative who will be able to provide this information to you.\n", "\n", "Backtesting, Simulated Results, Sensitivity/Scenario Analysis or Spreadsheet Calculator or Model: There may be data presented herein that is solely for illustrative purposes and which may include among other things back testing, simulated results and scenario analyses. The information is based upon certain factors, assumptions and historical information that Goldman Sachs may in its discretion have considered appropriate, however, Goldman Sachs provides no assurance or guarantee that this product will operate or would have operated in the past in a manner consistent with these assumptions. In the event any of the assumptions used do not prove to be true, results are likely to vary materially from the examples shown herein. Additionally, the results may not reflect material economic and market factors, such as liquidity, transaction costs and other expenses which could reduce potential return.\n", "\n", "OTC Derivatives Risk Disclosures: \n", "Terms of the Transaction: To understand clearly the terms and conditions of any OTC derivative transaction you may enter into, you should carefully review the Master Agreement, including any related schedules, credit support documents, addenda and exhibits. You should not enter into OTC derivative transactions unless you understand the terms of the transaction you are entering into as well as the nature and extent of your risk exposure. You should also be satisfied that the OTC derivative transaction is appropriate for you in light of your circumstances and financial condition. You may be requested to post margin or collateral to support written OTC derivatives at levels consistent with the internal policies of Goldman Sachs. \n", " \n", "Liquidity Risk: There is no public market for OTC derivative transactions and, therefore, it may be difficult or impossible to liquidate an existing position on favorable terms. Transfer Restrictions: OTC derivative transactions entered into with one or more affiliates of The Goldman Sachs Group, Inc. (Goldman Sachs) cannot be assigned or otherwise transferred without its prior written consent and, therefore, it may be impossible for you to transfer any OTC derivative transaction to a third party. \n", " \n", "Conflict of Interests: Goldman Sachs may from time to time be an active participant on both sides of the market for the underlying securities, commodities, futures, options or any other derivative or instrument identical or related to those mentioned herein (together, \"the Product\"). Goldman Sachs at any time may have long or short positions in, or buy and sell Products (on a principal basis or otherwise) identical or related to those mentioned herein. Goldman Sachs hedging and trading activities may affect the value of the Products. \n", " \n", "Counterparty Credit Risk: Because Goldman Sachs, may be obligated to make substantial payments to you as a condition of an OTC derivative transaction, you must evaluate the credit risk of doing business with Goldman Sachs or its affiliates. \n", " \n", "Pricing and Valuation: The price of each OTC derivative transaction is individually negotiated between Goldman Sachs and each counterparty and Goldman Sachs does not represent or warrant that the prices for which it offers OTC derivative transactions are the best prices available, possibly making it difficult for you to establish what is a fair price for a particular OTC derivative transaction; The value or quoted price of the Product at any time, however, will reflect many factors and cannot be predicted. If Goldman Sachs makes a market in the offered Product, the price quoted by Goldman Sachs would reflect any changes in market conditions and other relevant factors, and the quoted price (and the value of the Product that Goldman Sachs will use for account statements or otherwise) could be higher or lower than the original price, and may be higher or lower than the value of the Product as determined by reference to pricing models used by Goldman Sachs. If at any time a third party dealer quotes a price to purchase the Product or otherwise values the Product, that price may be significantly different (higher or lower) than any price quoted by Goldman Sachs. Furthermore, if you sell the Product, you will likely be charged a commission for secondary market transactions, or the price will likely reflect a dealer discount. Goldman Sachs may conduct market making activities in the Product. To the extent Goldman Sachs makes a market, any price quoted for the OTC derivative transactions, Goldman Sachs may differ significantly from (i) their value determined by reference to Goldman Sachs pricing models and (ii) any price quoted by a third party. The market price of the OTC derivative transaction may be influenced by many unpredictable factors, including economic conditions, the creditworthiness of Goldman Sachs, the value of any underlyers, and certain actions taken by Goldman Sachs. \n", " \n", "Market Making, Investing and Lending: Goldman Sachs engages in market making, investing and lending businesses for its own account and the accounts of its affiliates in the same or similar instruments underlying OTC derivative transactions (including such trading as Goldman Sachs deems appropriate in its sole discretion to hedge its market risk in any OTC derivative transaction whether between Goldman Sachs and you or with third parties) and such trading may affect the value of an OTC derivative transaction. \n", " \n", "Early Termination Payments: The provisions of an OTC Derivative Transaction may allow for early termination and, in such cases, either you or Goldman Sachs may be required to make a potentially significant termination payment depending upon whether the OTC Derivative Transaction is in-the-money to Goldman Sachs or you at the time of termination. Indexes: Goldman Sachs does not warrant, and takes no responsibility for, the structure, method of computation or publication of any currency exchange rates, interest rates, indexes of such rates, or credit, equity or other indexes, unless Goldman Sachs specifically advises you otherwise.\n", "Risk Disclosure Regarding futures, options, equity swaps, and other derivatives as well as non-investment-grade securities and ADRs: Please ensure that you have read and understood the current options, futures and security futures disclosure document before entering into any such transactions. Current United States listed options, futures and security futures disclosure documents are available from our sales representatives or at http://www.theocc.com/components/docs/riskstoc.pdf, http://www.goldmansachs.com/disclosures/risk-disclosure-for-futures.pdf and https://www.nfa.futures.org/investors/investor-resources/files/security-futures-disclosure.pdf, respectively. Certain transactions - including those involving futures, options, equity swaps, and other derivatives as well as non-investment-grade securities - give rise to substantial risk and are not available to nor suitable for all investors. If you have any questions about whether you are eligible to enter into these transactions with Goldman Sachs, please contact your sales representative. Foreign-currency-denominated securities are subject to fluctuations in exchange rates that could have an adverse effect on the value or price of, or income derived from, the investment. In addition, investors in securities such as ADRs, the values of which are influenced by foreign currencies, effectively assume currency risk.\n", "Options Risk Disclosures: Options may trade at a value other than that which may be inferred from the current levels of interest rates, dividends (if applicable) and the underlier due to other factors including, but not limited to, expectations of future levels of interest rates, future levels of dividends and the volatility of the underlier at any time prior to maturity. Note: Options involve risk and are not suitable for all investors. Please ensure that you have read and understood the current options disclosure document before entering into any standardized options transactions. United States listed options disclosure documents are available from our sales representatives or at http://theocc.com/publications/risks/riskstoc.pdf. A secondary market may not be available for all options. Transaction costs may be a significant factor in option strategies calling for multiple purchases and sales of options, such as spreads. When purchasing long options an investor may lose their entire investment and when selling uncovered options the risk is potentially unlimited. Supporting documentation for any comparisons, recommendations, statistics, technical data, or other similar information will be supplied upon request.\n", "This material is for the private information of the recipient only. This material is not sponsored, endorsed, sold or promoted by any sponsor or provider of an index referred herein (each, an \"Index Provider\"). GS does not have any affiliation with or control over the Index Providers or any control over the computation, composition or dissemination of the indices. While GS will obtain information from publicly available sources it believes reliable, it will not independently verify this information. Accordingly, GS shall have no liability, contingent or otherwise, to the user or to third parties, for the quality, accuracy, timeliness, continued availability or completeness of the data nor for any special, indirect, incidental or consequential damages which may be incurred or experienced because of the use of the data made available herein, even if GS has been advised of the possibility of such damages.\n", "Standard & Poor's ® and S&P ® are registered trademarks of The McGraw-Hill Companies, Inc. and S&P GSCI™ is a trademark of The McGraw-Hill Companies, Inc. and have been licensed for use by the Issuer. This Product (the \"Product\") is not sponsored, endorsed, sold or promoted by S&P and S&P makes no representation, warranty or condition regarding the advisability of investing in the Product.\n", "Notice to Brazilian Investors\n", "Marquee is not meant for the general public in Brazil. The services or products provided by or through Marquee, at any time, may not be offered or sold to the general public in Brazil. You have received a password granting access to Marquee exclusively due to your existing relationship with a GS business located in Brazil. The selection and engagement with any of the offered services or products through Marquee, at any time, will be carried out directly by you. Before acting to implement any chosen service or products, provided by or through Marquee you should consider, at your sole discretion, whether it is suitable for your particular circumstances and, if necessary, seek professional advice. Any steps necessary in order to implement the chosen service or product, including but not limited to remittance of funds, shall be carried out at your discretion. Accordingly, such services and products have not been and will not be publicly issued, placed, distributed, offered or negotiated in the Brazilian capital markets and, as a result, they have not been and will not be registered with the Brazilian Securities and Exchange Commission (Comissão de Valores Mobiliários), nor have they been submitted to the foregoing agency for approval. Documents relating to such services or products, as well as the information contained therein, may not be supplied to the general public in Brazil, as the offering of such services or products is not a public offering in Brazil, nor used in connection with any offer for subscription or sale of securities to the general public in Brazil.\n", "The offer of any securities mentioned in this message may not be made to the general public in Brazil. Accordingly, any such securities have not been nor will they be registered with the Brazilian Securities and Exchange Commission (Comissão de Valores Mobiliários) nor has any offer been submitted to the foregoing agency for approval. Documents relating to the offer, as well as the information contained therein, may not be supplied to the public in Brazil, as the offer is not a public offering of securities in Brazil. These terms will apply on every access to Marquee.\n", "Ouvidoria Goldman Sachs Brasil: 0800 727 5764 e/ou ouvidoriagoldmansachs@gs.com\n", "Horário de funcionamento: segunda-feira à sexta-feira (exceto feriados), das 9hs às 18hs.\n", "Ombudsman Goldman Sachs Brazil: 0800 727 5764 and / or ouvidoriagoldmansachs@gs.com\n", "Available Weekdays (except holidays), from 9 am to 6 pm.\n", "\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/events/00_gsquant_meets_markets/00_us_election_analysis/0003_trades.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [], "source": [ "import warnings\n", "from datetime import date\n", "\n", "import matplotlib.pyplot as plt\n", "import pandas as pd\n", "import seaborn as sns\n", "from gs_quant.datetime import business_day_offset\n", "from gs_quant.markets import PricingContext, BackToTheFuturePricingContext\n", "from gs_quant.risk import RollFwd, MarketDataPattern, MarketDataShock, MarketDataShockBasedScenario, MarketDataShockType\n", "from gs_quant.instrument import FXOption, IRSwaption\n", "from gs_quant.timeseries import *\n", "from gs_quant.timeseries import percentiles\n", "warnings.filterwarnings('ignore')\n", "sns.set(style=\"darkgrid\", color_codes=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',)) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this notebook, we'll look at entry points for G10 vol, look for crosses with the largest downside sensivity to SPX, indicatively price several structures and analyze their carry profile.\n", "\n", "* [1: FX entry point vs richness](#1:-FX-entry-point-vs-richness)\n", "* [2: Downside sensitivity to SPX](#2:-Downside-sensitivity-to-SPX)\n", "* [3: AUDJPY conditional relationship with SPX](#3:-AUDJPY-conditional-relationship-with-SPX)\n", "* [4: Price structures](#4:-Price-structures)\n", "* [5: Analyse rates package](#5:-Analyse-rates-package)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1: FX entry point vs richness\n", "Let's pull [GS FX Spot](https://marquee.gs.com/s/developer/datasets/FXSPOT_PREMIUM) and [GS FX Implied Volatility](https://marquee.gs.com/s/developer/datasets/FXIMPLIEDVOL_PREMIUM) and look at implied vs realized vol as well as current implied level as percentile relative to the last 2 years." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "hide_input": true }, "outputs": [], "source": [ "def format_df(data_dict):\n", " df = pd.concat(data_dict, axis=1)\n", " df.columns = data_dict.keys()\n", " return df.fillna(method='ffill').dropna()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "g10 = ['USDJPY', 'EURUSD', 'AUDUSD', 'GBPUSD', 'USDCAD', 'USDNOK', 'NZDUSD', 'USDSEK', 'USDCHF', 'AUDJPY']\n", "start_date = date(2005, 8, 26)\n", "end_date = business_day_offset(date.today(), -1, roll='preceding')\n", "fxspot_dataset, fxvol_dataset = Dataset('FXSPOT_PREMIUM'), Dataset('FXIMPLIEDVOL_PREMIUM')\n", "\n", "spot_data, impvol_data, spot_fx = {}, {}, {}\n", "for cross in g10:\n", " spot = fxspot_dataset.get_data(start_date, end_date, bbid=cross)[['spot']].drop_duplicates(keep='last')\n", " spot_fx[cross] = spot['spot']\n", " spot_data[cross] = volatility(spot['spot'], 63) # realized vol \n", " vol = fxvol_dataset.get_data(start_date, end_date, bbid=cross, tenor='3m', deltaStrike='DN', location='NYC')[['impliedVolatility']]\n", " impvol_data[cross] = vol.drop_duplicates(keep='last') * 100\n", "\n", "spdata, ivdata = format_df(spot_data), format_df(impvol_data)\n", "diff = ivdata.subtract(spdata).dropna()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [], "source": [ "_slice = ivdata['2018-09-01': '2020-09-08']\n", "pct_rank = {}\n", "for x in _slice.columns:\n", " pct = percentiles(_slice[x])\n", " pct_rank[x] = pct.iloc[-1]\n", "\n", "for fx in pct_rank:\n", " plt.scatter(pct_rank[fx], diff[fx]['2020-09-08'])\n", " plt.legend(pct_rank.keys(),loc='best', bbox_to_anchor=(0.9, -0.13), ncol=3)\n", " \n", "plt.xlabel('Percentile of Current Implied Vol')\n", "plt.ylabel('Implied vs Realized Vol')\n", "plt.title('Entry Point vs Richness')\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2: Downside sensitivity to SPX\n", "\n", "Let's now look at beta and correlation with SPX across G10." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "spx_spot = Dataset('TREOD').get_data(start_date, end_date, bbid='SPX')[['closePrice']]\n", "spx_spot = spx_spot.fillna(method='ffill').dropna()\n", "df = pd.DataFrame(spx_spot)\n", "\n", "#FX Spot data\n", "fx_spots = format_df(spot_fx)\n", "data = pd.concat([spx_spot, fx_spots], axis=1).dropna()\n", "data.columns = ['SPX'] + g10" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [], "source": [ "beta_spx, corr_spx = {}, {}\n", "\n", "#calculate rolling 84d or 4m beta to S&P\n", "for cross in g10:\n", " beta_spx[cross] = beta(data[cross],data['SPX'], 84)\n", " corr_spx[cross] = correlation(data['SPX'], data[cross], 84)\n", "\n", "fig, axs = plt.subplots(5, 2, figsize=(18, 20))\n", "for j in range(2):\n", " for i in range(5):\n", " color='tab:blue'\n", " axs[i,j].plot(beta_spx[g10[i + j*5]], color=color)\n", " axs[i,j].set_title(g10[i + j*5])\n", " color='tab:blue'\n", " axs[i,j].set_ylabel('Beta', color=color)\n", " axs[i,j].plot(beta_spx[g10[i + j*5]], color=color)\n", " ax2 = axs[i,j].twinx()\n", " color = 'tab:orange' \n", " ax2.plot(corr_spx[g10[i + j*5]], color=color)\n", " ax2.set_ylabel('Correlation', color=color)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Part 3: AUDJPY conditional relationship with SPX\n", "\n", "Let's focus on AUDJPY and look at its relationship with SPX when SPX is significantly up and down." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# resample data to weekly from daily & get weekly returns\n", "wk_data = data.resample('W-FRI').last()\n", "rets = returns(wk_data, 1)\n", "sns.set(style='white', color_codes=True)\n", "spx_returns = [-.1, -.05, .05, .1]\n", "\n", "betas = pd.DataFrame(index=spx_returns, columns=g10)\n", "for ret in spx_returns:\n", " dns = rets[rets.SPX <= ret].dropna() if ret < 0 else rets[rets.SPX >= ret].dropna() \n", " j = sns.jointplot(x='SPX', y='AUDJPY', data=dns, kind='reg')\n", " j.set_axis_labels('SPX with {}% Returns'.format(ret*100), 'AUDJPY')\n", " j.fig.subplots_adjust(wspace=.02)\n", " plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's use the beta for all S&P returns to price a structure" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sns.jointplot(x='SPX', y='AUDJPY', data=rets, kind='reg')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4: Price structures \n", "\n", "##### Let's now look at a few AUDJPY structures as potential hedges\n", "\n", "* Buy 4m AUDJPY put using spx beta to size. Max loss limited to premium paid.\n", "* Buy 4m AUDJPY put spread (4.2%/10.6% OTMS). Max loss limited to premium paid.\n", "\n", "For more info on this trade, check out our market strats piece [here](https://marquee.gs.com/content/#/article/2020/08/28/gs-marketstrats-audjpy-as-us-election-hedge)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#buy 4m AUDJPY put\n", "audjpy_put = FXOption(option_type='Put', pair='AUDJPY', strike_price= 's-4.2%', expiration_date='4m', buy_sell='Buy') \n", "print('cost in bps: {:,.2f}'.format(audjpy_put.premium / audjpy_put.notional_amount * 1e4))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#buy 4m AUDJPY put spread (5.3%/10.6% OTMS)\n", "from gs_quant.markets.portfolio import Portfolio\n", "put1 = FXOption(option_type='Put', pair='AUDJPY', strike_price= 's-4.2%', expiration_date='4m', buy_sell='Buy')\n", "put2 = FXOption(option_type='Put', pair='AUDJPY', strike_price= 's-10.6%', expiration_date='4m', buy_sell='Sell')\n", "\n", "fx_package = Portfolio((put1, put2))\n", "cost = put2.premium/put2.notional_amount - put1.premium/put1.notional_amount \n", "print('cost in bps: {:,.2f}'.format(cost * 1e4))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### ...And some rates ideas\n", "\n", "* Sell straddle. Max loss unlimited.\n", "* Sell 3m30y straddle, buy 2y30y straddle in a 0 pv package. Max loss unlimited." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "leg = IRSwaption('Straddle', '30y', notional_currency='USD', expiration_date='3m', buy_sell='Sell')\n", "print('PV in USD: {:,.2f}'.format(leg.dollar_price()))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "rates_package = Portfolio((IRSwaption('Straddle', '30y', notional_currency='USD', expiration_date='3m', buy_sell='Sell',\n", " name='3m30y ATM Straddle'),\n", " IRSwaption('Straddle', '30y', notional_currency='USD', expiration_date='2y', buy_sell='Buy', \n", " notional_amount='=solvefor([3m30y ATM Straddle].risk.Price,pv)', \n", " name = '2y30y ATM Straddle')))\n", "rates_package.resolve()\n", "\n", "print('Package cost in USD: {:,.2f}'.format(rates_package.price().aggregate()))\n", "print('PV Flat notionals ($$m):', round(leg1.notional_amount/1e6, 1),' by ',round(leg2.notional_amount/1e6, 1))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 5: Analyse rates package" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dates = pd.bdate_range(date(2020, 6, 8), leg1.expiration_date, freq='5B').date.tolist()\n", "\n", "with BackToTheFuturePricingContext(dates=dates, roll_to_fwds=True):\n", " future = rates_package.price()\n", "rates_future = future.result().aggregate()\n", "\n", "rates_future.plot(figsize=(10, 6), title='Historical PV and carry for rates package')\n", "\n", "print('PV breakdown between legs:')\n", "results = future.result().to_frame()\n", "results /= 1e6\n", "results.loc['Total'] = results.sum()\n", "results.round(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's focus on the next 3m and how the calendar carries in different rates shocks." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dates = pd.bdate_range(dt.date.today(), leg1.expiration_date, freq='5B').date.tolist()\n", "shocked_pv = pd.DataFrame(columns=['Base', '5bp per week', '50bp instantaneous'], index=dates)\n", "\n", "p1, p2, p3 = [], [], []\n", "with PricingContext(is_batch=True):\n", " for t, d in enumerate(dates):\n", " with RollFwd(date=d, realise_fwd=True):\n", " p1.append(rates_package.price())\n", " with MarketDataShockBasedScenario({MarketDataPattern('IR', 'USD'): MarketDataShock(MarketDataShockType.Absolute, t*0.0005)}):\n", " p2.append(rates_package.price())\n", " with MarketDataShockBasedScenario({MarketDataPattern('IR', 'USD'): MarketDataShock(MarketDataShockType.Absolute, 0.005)}):\n", " p3.append(rates_package.price())\n", "\n", "shocked_pv.Base = [p.result().aggregate() for p in p1]\n", "shocked_pv['5bp per week'] = [p.result().aggregate() for p in p2]\n", "shocked_pv['50bp instantaneous'] = [p.result().aggregate() for p in p3]\n", "\n", "shocked_pv/=1e6\n", "shocked_pv.round(1)\n", "shocked_pv.plot(figsize=(10, 6), title='Carry + scenario analysis')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Disclaimers\n", "\n", "Scenarios/predictions: Simulated results are for illustrative purposes only. GS provides no assurance or guarantee that the strategy will operate or would have operated in the past in a manner consistent with the above analysis. Past performance figures are not a reliable indicator of future results.\n", "\n", "Indicative Terms/Pricing Levels: This material may contain indicative terms only, including but not limited to pricing levels. There is no representation that any transaction can or could have been effected at such terms or prices. Proposed terms and conditions are for discussion purposes only. Finalized terms and conditions are subject to further discussion and negotiation.\n", "www.goldmansachs.com/disclaimer/sales-and-trading-invest-rec-disclosures.html If you are not accessing this material via Marquee ContentStream, a list of the author's investment recommendations disseminated during the preceding 12 months and the proportion of the author's recommendations that are 'buy', 'hold', 'sell' or other over the previous 12 months is available by logging into Marquee ContentStream using the link below. Alternatively, if you do not have access to Marquee ContentStream, please contact your usual GS representative who will be able to provide this information to you.\n", "\n", "Backtesting, Simulated Results, Sensitivity/Scenario Analysis or Spreadsheet Calculator or Model: There may be data presented herein that is solely for illustrative purposes and which may include among other things back testing, simulated results and scenario analyses. The information is based upon certain factors, assumptions and historical information that Goldman Sachs may in its discretion have considered appropriate, however, Goldman Sachs provides no assurance or guarantee that this product will operate or would have operated in the past in a manner consistent with these assumptions. In the event any of the assumptions used do not prove to be true, results are likely to vary materially from the examples shown herein. Additionally, the results may not reflect material economic and market factors, such as liquidity, transaction costs and other expenses which could reduce potential return.\n", "\n", "OTC Derivatives Risk Disclosures: \n", "Terms of the Transaction: To understand clearly the terms and conditions of any OTC derivative transaction you may enter into, you should carefully review the Master Agreement, including any related schedules, credit support documents, addenda and exhibits. You should not enter into OTC derivative transactions unless you understand the terms of the transaction you are entering into as well as the nature and extent of your risk exposure. You should also be satisfied that the OTC derivative transaction is appropriate for you in light of your circumstances and financial condition. You may be requested to post margin or collateral to support written OTC derivatives at levels consistent with the internal policies of Goldman Sachs. \n", " \n", "Liquidity Risk: There is no public market for OTC derivative transactions and, therefore, it may be difficult or impossible to liquidate an existing position on favorable terms. Transfer Restrictions: OTC derivative transactions entered into with one or more affiliates of The Goldman Sachs Group, Inc. (Goldman Sachs) cannot be assigned or otherwise transferred without its prior written consent and, therefore, it may be impossible for you to transfer any OTC derivative transaction to a third party. \n", " \n", "Conflict of Interests: Goldman Sachs may from time to time be an active participant on both sides of the market for the underlying securities, commodities, futures, options or any other derivative or instrument identical or related to those mentioned herein (together, \"the Product\"). Goldman Sachs at any time may have long or short positions in, or buy and sell Products (on a principal basis or otherwise) identical or related to those mentioned herein. Goldman Sachs hedging and trading activities may affect the value of the Products. \n", " \n", "Counterparty Credit Risk: Because Goldman Sachs, may be obligated to make substantial payments to you as a condition of an OTC derivative transaction, you must evaluate the credit risk of doing business with Goldman Sachs or its affiliates. \n", " \n", "Pricing and Valuation: The price of each OTC derivative transaction is individually negotiated between Goldman Sachs and each counterparty and Goldman Sachs does not represent or warrant that the prices for which it offers OTC derivative transactions are the best prices available, possibly making it difficult for you to establish what is a fair price for a particular OTC derivative transaction; The value or quoted price of the Product at any time, however, will reflect many factors and cannot be predicted. If Goldman Sachs makes a market in the offered Product, the price quoted by Goldman Sachs would reflect any changes in market conditions and other relevant factors, and the quoted price (and the value of the Product that Goldman Sachs will use for account statements or otherwise) could be higher or lower than the original price, and may be higher or lower than the value of the Product as determined by reference to pricing models used by Goldman Sachs. If at any time a third party dealer quotes a price to purchase the Product or otherwise values the Product, that price may be significantly different (higher or lower) than any price quoted by Goldman Sachs. Furthermore, if you sell the Product, you will likely be charged a commission for secondary market transactions, or the price will likely reflect a dealer discount. Goldman Sachs may conduct market making activities in the Product. To the extent Goldman Sachs makes a market, any price quoted for the OTC derivative transactions, Goldman Sachs may differ significantly from (i) their value determined by reference to Goldman Sachs pricing models and (ii) any price quoted by a third party. The market price of the OTC derivative transaction may be influenced by many unpredictable factors, including economic conditions, the creditworthiness of Goldman Sachs, the value of any underlyers, and certain actions taken by Goldman Sachs. \n", " \n", "Market Making, Investing and Lending: Goldman Sachs engages in market making, investing and lending businesses for its own account and the accounts of its affiliates in the same or similar instruments underlying OTC derivative transactions (including such trading as Goldman Sachs deems appropriate in its sole discretion to hedge its market risk in any OTC derivative transaction whether between Goldman Sachs and you or with third parties) and such trading may affect the value of an OTC derivative transaction. \n", " \n", "Early Termination Payments: The provisions of an OTC Derivative Transaction may allow for early termination and, in such cases, either you or Goldman Sachs may be required to make a potentially significant termination payment depending upon whether the OTC Derivative Transaction is in-the-money to Goldman Sachs or you at the time of termination. Indexes: Goldman Sachs does not warrant, and takes no responsibility for, the structure, method of computation or publication of any currency exchange rates, interest rates, indexes of such rates, or credit, equity or other indexes, unless Goldman Sachs specifically advises you otherwise.\n", "Risk Disclosure Regarding futures, options, equity swaps, and other derivatives as well as non-investment-grade securities and ADRs: Please ensure that you have read and understood the current options, futures and security futures disclosure document before entering into any such transactions. Current United States listed options, futures and security futures disclosure documents are available from our sales representatives or at http://www.theocc.com/components/docs/riskstoc.pdf, http://www.goldmansachs.com/disclosures/risk-disclosure-for-futures.pdf and https://www.nfa.futures.org/investors/investor-resources/files/security-futures-disclosure.pdf, respectively. Certain transactions - including those involving futures, options, equity swaps, and other derivatives as well as non-investment-grade securities - give rise to substantial risk and are not available to nor suitable for all investors. If you have any questions about whether you are eligible to enter into these transactions with Goldman Sachs, please contact your sales representative. Foreign-currency-denominated securities are subject to fluctuations in exchange rates that could have an adverse effect on the value or price of, or income derived from, the investment. In addition, investors in securities such as ADRs, the values of which are influenced by foreign currencies, effectively assume currency risk.\n", "Options Risk Disclosures: Options may trade at a value other than that which may be inferred from the current levels of interest rates, dividends (if applicable) and the underlier due to other factors including, but not limited to, expectations of future levels of interest rates, future levels of dividends and the volatility of the underlier at any time prior to maturity. Note: Options involve risk and are not suitable for all investors. Please ensure that you have read and understood the current options disclosure document before entering into any standardized options transactions. United States listed options disclosure documents are available from our sales representatives or at http://theocc.com/publications/risks/riskstoc.pdf. A secondary market may not be available for all options. Transaction costs may be a significant factor in option strategies calling for multiple purchases and sales of options, such as spreads. When purchasing long options an investor may lose their entire investment and when selling uncovered options the risk is potentially unlimited. Supporting documentation for any comparisons, recommendations, statistics, technical data, or other similar information will be supplied upon request.\n", "This material is for the private information of the recipient only. This material is not sponsored, endorsed, sold or promoted by any sponsor or provider of an index referred herein (each, an \"Index Provider\"). GS does not have any affiliation with or control over the Index Providers or any control over the computation, composition or dissemination of the indices. While GS will obtain information from publicly available sources it believes reliable, it will not independently verify this information. Accordingly, GS shall have no liability, contingent or otherwise, to the user or to third parties, for the quality, accuracy, timeliness, continued availability or completeness of the data nor for any special, indirect, incidental or consequential damages which may be incurred or experienced because of the use of the data made available herein, even if GS has been advised of the possibility of such damages.\n", "Standard & Poor's ® and S&P ® are registered trademarks of The McGraw-Hill Companies, Inc. and S&P GSCI™ is a trademark of The McGraw-Hill Companies, Inc. and have been licensed for use by the Issuer. This Product (the \"Product\") is not sponsored, endorsed, sold or promoted by S&P and S&P makes no representation, warranty or condition regarding the advisability of investing in the Product.\n", "Notice to Brazilian Investors\n", "Marquee is not meant for the general public in Brazil. The services or products provided by or through Marquee, at any time, may not be offered or sold to the general public in Brazil. You have received a password granting access to Marquee exclusively due to your existing relationship with a GS business located in Brazil. The selection and engagement with any of the offered services or products through Marquee, at any time, will be carried out directly by you. Before acting to implement any chosen service or products, provided by or through Marquee you should consider, at your sole discretion, whether it is suitable for your particular circumstances and, if necessary, seek professional advice. Any steps necessary in order to implement the chosen service or product, including but not limited to remittance of funds, shall be carried out at your discretion. Accordingly, such services and products have not been and will not be publicly issued, placed, distributed, offered or negotiated in the Brazilian capital markets and, as a result, they have not been and will not be registered with the Brazilian Securities and Exchange Commission (Comissão de Valores Mobiliários), nor have they been submitted to the foregoing agency for approval. Documents relating to such services or products, as well as the information contained therein, may not be supplied to the general public in Brazil, as the offering of such services or products is not a public offering in Brazil, nor used in connection with any offer for subscription or sale of securities to the general public in Brazil.\n", "The offer of any securities mentioned in this message may not be made to the general public in Brazil. Accordingly, any such securities have not been nor will they be registered with the Brazilian Securities and Exchange Commission (Comissão de Valores Mobiliários) nor has any offer been submitted to the foregoing agency for approval. Documents relating to the offer, as well as the information contained therein, may not be supplied to the public in Brazil, as the offer is not a public offering of securities in Brazil. These terms will apply on every access to Marquee.\n", "Ouvidoria Goldman Sachs Brasil: 0800 727 5764 e/ou ouvidoriagoldmansachs@gs.com\n", "Horário de funcionamento: segunda-feira à sexta-feira (exceto feriados), das 9hs às 18hs.\n", "Ombudsman Goldman Sachs Brazil: 0800 727 5764 and / or ouvidoriagoldmansachs@gs.com\n", "Available Weekdays (except holidays), from 9 am to 6 pm.\n", "\n" ] } ], "metadata": { "celltoolbar": "Edit Metadata", "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/events/00_gsquant_meets_markets/01_ideas_for_risk_re_rating/eq_sx5e_spx_vol_spread_trade.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [], "source": [ "from gs_quant.markets.securities import AssetIdentifier, SecurityMaster\n", "from gs_quant.timeseries.measures import forward_vol, VolReference, implied_volatility\n", "from gs_quant.timeseries.algebra import *\n", "from gs_quant.timeseries.analysis import *\n", "from gs_quant.data import DataContext\n", "from gs_quant.instrument import EqOption\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.risk import EqDelta, EqGamma, EqVega, DollarPrice, Price\n", "from gs_quant.markets import PricingContext\n", "from gs_quant.backtests.triggers import *\n", "from gs_quant.backtests.actions import *\n", "from gs_quant.backtests import FlowVolBacktestMeasure\n", "from gs_quant.backtests.strategy import Strategy\n", "from gs_quant.backtests.equity_vol_engine import EquityVolEngine\n", "from gs_quant.session import GsSession, Environment\n", "\n", "from dateutil.relativedelta import relativedelta\n", "from datetime import date, datetime\n", "import matplotlib.pyplot as plt\n", "import pandas as pd\n", "import warnings\n", "warnings.filterwarnings('ignore')\n", "pd.options.display.float_format = '{:,.2f}'.format" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this notebook, we will take a look at historical and future implied trends in equity implied volatility, formulate a trade idea, analyze a trading strategy backtest and calculate spot price and greeks. This is an example of the GS Quant functionalities and it is not a recommendation of a trade in the instruments discussed, please follow up with your salesperson to discuss specific implementations.\n", "\n", "* [1 - Analyze Vol Spread](#2---Analyze-Vol-Spread)\n", "* [2 - Trade Idea & Backtest](#3---Trade-Idea-&-Backtest)\n", "* [3 - Compare Performance](#4---Compare-Performance)\n", "* [4 - Price & Greeks](#5---Price-&-Greeks)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1 - Analyze Vol Spread\n", "\n", "Let's start by looking at historical forward vol for SPX and SX5E." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "def forward_vol_spread(asset1, asset2, tenor, forward_date):\n", " '''\n", " Calculate the forward starting vol spread between two assets\n", " '''\n", " asset1_sec = SecurityMaster.get_asset(asset1, AssetIdentifier.BLOOMBERG_ID)\n", " asset2_sec = SecurityMaster.get_asset(asset2, AssetIdentifier.BLOOMBERG_ID)\n", " \n", " implied = lambda asset: implied_volatility(asset, tenor, VolReference.FORWARD, 100)\n", " forward = lambda asset: forward_vol(asset, tenor, forward_date, VolReference.FORWARD, 100)\n", " \n", " asset1_fvol = implied(asset1_sec) if forward_date=='0m' else forward(asset1_sec)\n", " asset2_fvol = implied(asset2_sec) if forward_date=='0m' else forward(asset2_sec)\n", " \n", " return subtract(asset1_fvol, asset2_fvol)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "start_date, end_date = date(2014, 1, 1), date(2020, 11, 2)\n", "DataContext.current = DataContext(start=start_date, end=end_date)\n", "\n", "sx5e_spx_fvs = forward_vol_spread('SX5E', 'SPX', '2y', '1y')\n", "\n", "current_roll_data = {}\n", "for rMonth in [0, 6, 9, 12]:\n", " d = end_date + relativedelta(months=rMonth)\n", " months_to_start = 12 - rMonth\n", " curve = forward_vol_spread('SX5E', 'SPX', '2y', f'{months_to_start}m')\n", " if curve.size:\n", " current_roll_data[d] = last_value(curve)\n", "current_roll = pd.Series(current_roll_data)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "sx5e_spx_fvs.plot(figsize=(12, 6), legend=True, label='SX5E vs SPX', title='Vol Spread')\n", "current_roll.plot(figsize=(12, 6), legend=True, label='Current Spread Roll-Up')\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2 - Trade Idea & Backtest\n", "\n", "The analysis shows a projected narrowing of vol spread between SX5E and SPX. Let's create an option trading strategy that takes advantage of this and backtest it to see how it could have performed." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "# Create instruments\n", "sx5e_straddle = (EqOption('.STOXX50E', '3y', 'ATMF', 'Call'), EqOption('.STOXX50E', '3y', 'ATMF','Put'))\n", "spx_straddle = (EqOption('.SPX', '3y', 'ATMF', 'Call', multiplier=1), EqOption('.SPX', '3y', 'ATMF', 'Put', multiplier=1))\n", "\n", "# Define triggers for trade and hedge actions.\n", "\n", "# Trade and roll 1 sx5e_straddle every 1m\n", "trade_sx5e_action = EnterPositionQuantityScaledAction(priceables=Portfolio(sx5e_straddle), trade_duration='1m', trade_quantity=1, trade_quantity_type=BacktestTradingQuantityType.quantity)\n", "trade_sx5e_trigger = PeriodicTrigger(trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'),\n", " actions=trade_sx5e_action)\n", "\n", "# Hedge sx5e_straddle delta every business day\n", "hedge_sx5e_action = HedgeAction(EqDelta, priceables=Portfolio(sx5e_straddle), trade_duration='B')\n", "hedge_sx5e_trigger = PeriodicTrigger(trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='B'),\n", " actions=hedge_sx5e_action)\n", "\n", "# Trade and roll 1 spx_straddle every 1m\n", "trade_spx_action = EnterPositionQuantityScaledAction(priceables=Portfolio(spx_straddle), trade_duration='1m', trade_quantity=1, trade_quantity_type=BacktestTradingQuantityType.quantity)\n", "trade_spx_trigger = PeriodicTrigger(trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'),\n", " actions=trade_spx_action)\n", "\n", "# Hedge spx_straddle delta every business day\n", "hedge_spx_action = HedgeAction(EqDelta, priceables=Portfolio(spx_straddle), trade_duration='B')\n", "hedge_spx_trigger = PeriodicTrigger(trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='B'),\n", " actions=hedge_spx_action)\n", "\n", "EqVolE = EquityVolEngine()\n", "\n", "# Define SX5E backtest\n", "triggers_sx5e = [trade_sx5e_trigger, hedge_sx5e_trigger]\n", "\n", "strategy_sx5e_result = EqVolE.run_backtest(Strategy(None, triggers_sx5e), start=start_date, end=end_date)\n", "perf_sx5e = strategy_sx5e_result.get_measure_series(FlowVolBacktestMeasure.PNL)\n", "\n", "# Define SPX backtest\n", "triggers_spx = [trade_spx_trigger, hedge_spx_trigger]\n", "\n", "strategy_spx_result = EqVolE.run_backtest(Strategy(None, triggers_spx), start=start_date, end=end_date)\n", "perf_spx = strategy_spx_result.get_measure_series(FlowVolBacktestMeasure.PNL)\n" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "perf_sx5e_spx_spread = subtract(perf_sx5e, perf_spx)\n", "perf_sx5e_spx_spread.index = pd.DatetimeIndex(perf_sx5e_spx_spread.index)\n", "perf_sx5e_spx_spread.plot(figsize=(12, 6), title='Strategy SX5E vs SPX 3y Vol Spread')\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3 - Compare Performance\n", "\n", "Now let's plot the trading strategy with the actual SX5E vs SPX vol spread to see how the two compare." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAA1gAAAGoCAYAAABbkkSYAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjEsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+j8jraAAAgAElEQVR4nOydeXhU5fXHv3f2yUz2hKxAwr4JAVllEVwRsQjqz4W2LlWLS6tttWhrRa1WqbZqtbhbtFjFBRdcEEVBVmXfwhqyELJvk2T2uff9/XGX2SeTMCEJOZ/n4WHm3vfe+04ymXm/95zzPRxjDARBEARBEARBEMTpo+rqCRAEQRAEQRAEQZwtkMAiCIIgCIIgCIKIESSwCIIgCIIgCIIgYgQJLIIgCIIgCIIgiBhBAosgCIIgCIIgCCJGaLp6Au1BpVIxo9HY1dMgCIIgCIIgCOI0sNlsjDF2VgZ7epTAMhqNsFqtXT0NgiAIgiAIgiBOA47j7F09h87irFSNBEEQBEEQBEEQXQEJLIIgCIIgCIIgiBhBAosgCIIgCIIgCCJG9KgaLIIgCIIgCKLzcLvdKC8vh8Ph6OqpEGcJBoMBubm50Gq1XT2VMwYJLIIgCIIgCAIAUF5ejvj4eOTl5YHjuK6eDtHDYYyhvr4e5eXlyM/P7+rpnDEoRZAgCIIgCIIAADgcDqSmppK4ImICx3FITU3tdRFRElgEQRAEQRCEAokrIpb0xvcTCSyCIAiCIAiCIIgYQQKLIAiCIAiC6DZUVVXhuuuuw8CBAzFixAjMmTMHR48ePaNzWL9+PbZs2RJyX3V1NebOnYsxY8Yo8zvTc5s7d27I7YmJiRg7diyGDRuG++67r81z5eXloa6uDgBgNptDjrnpppvw4Ycf+m0LN5YQIYFFEARBEARBdAsYY5g/fz5mzpyJoqIiFBYW4m9/+xuqq6ujPgfP8xGfR0MkgfXwww/j4osvxt69e1FYWIinnnqqXefuyHyiZfr06di9ezd2796Nzz//HJs3b+60axHhIYFFEARBEARBdAu+//57aLVaLFq0SNlWUFCA6dOnB0Vu7r77bixfvhyAGIl57LHHMG3aNHzwwQdBz9euXYspU6Zg3LhxuOaaa9Da2qoct2TJEowbNw7nnHMODh8+jJKSErz88st49tlnUVBQgI0bN/rNsbKyErm5ucrz0aNHAxBF2YwZMzB//nyMGDECixYtgiAIAMSIz8MPP4xJkyZh69atWLFiBSZOnIiCggL8+te/VkTXHXfcgfHjx2PkyJFYsmSJco01a9Zg2LBhmDZtGlatWtXmz9FoNKKgoACnTp0CALz77rs455xzMGrUKCxevDjq30dbRHrNvRmyaScIgiAIgiCCWPrTUhxuOBzTcw5LGYbFE8Mv8A8cOIBzzz23Q+c2GAzYtGkTAOCBBx5QntfV1WHBggX49ttvYTKZsHTpUvzzn//Eww8/DABIS0vDrl27sGzZMjzzzDN4/fXXsWjRIpjN5pBpdnfddReuvfZavPjii7joootw8803Izs7GwDw008/obCwEP3798fs2bOxatUqXH311bBarRg1ahQee+wxHDp0CEuXLsXmzZuh1Wpx55134p133sEvf/lLPPHEE0hJSQHP87jwwguxb98+DBkyBLfddhu+++47DBo0CNdee22bP4vGxkYcO3YMM2bMQEVFBRYvXoydO3ciOTkZl1xyCT755BNceeWVHfo5BxLuNfdmKIJFEARBEARB9HgChYf8fNu2bSgsLMTUqVNRUFCAt956C6Wlpcq4BQsWAADOPfdclJSUtHmdSy+9FCdOnMBtt92Gw4cPY+zYsaitrQUATJw4EQMGDIBarcb111+vCD61Wo2rrroKALBu3Trs3LkTEyZMQEFBAdatW4cTJ04AAN5//32MGzcOY8eOxcGDB1FYWIjDhw8jPz8fgwcPBsdx+PnPfx52bhs3bsTo0aORmZmJuXPnIjMzE9u3b8fMmTORnp4OjUaDhQsX4ocffojypxraBdB3W7jX3JuhCBZBEARBEAQRRKRIU2cxcuTIIEMFGY1G45d+FthbyWQyhXzOGMPFF1+Md999N+R59Xo9AFEEeTyeqOaZkpKCG264ATfccAPmzp2LH374IWT/MPm5wWCAWq1W5nPjjTfiySef9BtbXFyMZ555Btu3b0dycjJuuukm5TVGa3U+ffp0fP755zh69CimTZuG+fPngzEW1bEyf/7zn/HFF18AAPbs2YPU1FQ0NjYq+xsaGpCWlhb0GsM9741QBIsgCKIdeDx8u7+sCIIgiOi44IIL4HQ68dprrynbtm/fjg0bNqB///4oLCyE0+mExWLBunXrojrn5MmTsXnzZhw/fhwAYLPZ2nQl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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "fig, ax1 = plt.subplots()\n", "\n", "# left axis\n", "ax1.set_xlabel('date')\n", "ax1.set_ylabel('Vol Spread',color='tab:red')\n", "\n", "#right axis\n", "ax2 = ax1.twinx() \n", "ax2.set_ylabel('Strategy PNL', color='tab:blue') \n", "\n", "sx5e_spx_fvs.plot(figsize=(12, 6), ax=ax1, color='tab:red')\n", "current_roll.plot(figsize=(12, 6), ax=ax1, color='tab:green', legend=True, label='Current Spread Roll-Up')\n", "perf_sx5e_spx_spread.plot(figsize=(12, 6), ax=ax2, color='tab:blue')\n", "\n", "fig.tight_layout() \n", "plt.xlim(start_date, end_date + relativedelta(years=1))\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4 - Price & Greeks\n", "\n", "Finallly, let's calculate current strategy price and greeks." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Strategy Price$: -4.5888085670912915\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
SPXSX5E
valuevalue
risk_measure
EqDelta613.43619.86
EqGamma86.84129.77
EqVega50.9057.17
\n", "
" ], "text/plain": [ " SPX SX5E\n", " value value\n", "risk_measure \n", "EqDelta 613.43 619.86\n", "EqGamma 86.84 129.77\n", "EqVega 50.90 57.17" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sx5e_straddle_port, spx_straddle_port = Portfolio(sx5e_straddle), Portfolio(spx_straddle)\n", "\n", "# Price and greeks in USD\n", "sx5e_straddle_price = sx5e_straddle_port.calc(DollarPrice).aggregate()\n", "spx_straddle_price = spx_straddle_port.calc(DollarPrice).aggregate()\n", "print(f'Strategy Price$: {sx5e_straddle_price - spx_straddle_price}')\n", "\n", "greeks = (EqDelta, EqGamma, EqVega)\n", "sx5e_straddle_greeks = sx5e_straddle_port.calc(greeks).aggregate().to_frame()\n", "spx_straddle_greeks = spx_straddle_port.calc(greeks).aggregate().to_frame()\n", "pd.concat([spx_straddle_greeks,sx5e_straddle_greeks],keys=['SPX', 'SX5E']).transpose()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Disclaimers\n", "\n", "Indicative Terms/Pricing Levels: This material may contain indicative terms only, including but not limited to pricing levels. There is no representation that any transaction can or could have been effected at such terms or prices. Proposed terms and conditions are for discussion purposes only. Finalized terms and conditions are subject to further discussion and negotiation.\n", "www.goldmansachs.com/disclaimer/sales-and-trading-invest-rec-disclosures.html If you are not accessing this material via Marquee ContentStream, a list of the author's investment recommendations disseminated during the preceding 12 months and the proportion of the author's recommendations that are 'buy', 'hold', 'sell' or other over the previous 12 months is available by logging into Marquee ContentStream using the link below. Alternatively, if you do not have access to Marquee ContentStream, please contact your usual GS representative who will be able to provide this information to you.\n", "Backtesting, Simulated Results, Sensitivity/Scenario Analysis or Spreadsheet Calculator or Model: There may be data presented herein that is solely for illustrative purposes and which may include among other things back testing, simulated results and scenario analyses. The information is based upon certain factors, assumptions and historical information that Goldman Sachs may in its discretion have considered appropriate, however, Goldman Sachs provides no assurance or guarantee that this product will operate or would have operated in the past in a manner consistent with these assumptions. In the event any of the assumptions used do not prove to be true, results are likely to vary materially from the examples shown herein. Additionally, the results may not reflect material economic and market factors, such as liquidity, transaction costs and other expenses which could reduce potential return.\n", "OTC Derivatives Risk Disclosures: \n", "Terms of the Transaction: To understand clearly the terms and conditions of any OTC derivative transaction you may enter into, you should carefully review the Master Agreement, including any related schedules, credit support documents, addenda and exhibits. You should not enter into OTC derivative transactions unless you understand the terms of the transaction you are entering into as well as the nature and extent of your risk exposure. You should also be satisfied that the OTC derivative transaction is appropriate for you in light of your circumstances and financial condition. You may be requested to post margin or collateral to support written OTC derivatives at levels consistent with the internal policies of Goldman Sachs. \n", "\n", "Liquidity Risk: There is no public market for OTC derivative transactions and, therefore, it may be difficult or impossible to liquidate an existing position on favorable terms. Transfer Restrictions: OTC derivative transactions entered into with one or more affiliates of The Goldman Sachs Group, Inc. (Goldman Sachs) cannot be assigned or otherwise transferred without its prior written consent and, therefore, it may be impossible for you to transfer any OTC derivative transaction to a third party. \n", "\n", "Conflict of Interests: Goldman Sachs may from time to time be an active participant on both sides of the market for the underlying securities, commodities, futures, options or any other derivative or instrument identical or related to those mentioned herein (together, \"the Product\"). Goldman Sachs at any time may have long or short positions in, or buy and sell Products (on a principal basis or otherwise) identical or related to those mentioned herein. Goldman Sachs hedging and trading activities may affect the value of the Products. \n", "\n", "Counterparty Credit Risk: Because Goldman Sachs, may be obligated to make substantial payments to you as a condition of an OTC derivative transaction, you must evaluate the credit risk of doing business with Goldman Sachs or its affiliates. \n", "\n", "Pricing and Valuation: The price of each OTC derivative transaction is individually negotiated between Goldman Sachs and each counterparty and Goldman Sachs does not represent or warrant that the prices for which it offers OTC derivative transactions are the best prices available, possibly making it difficult for you to establish what is a fair price for a particular OTC derivative transaction; The value or quoted price of the Product at any time, however, will reflect many factors and cannot be predicted. If Goldman Sachs makes a market in the offered Product, the price quoted by Goldman Sachs would reflect any changes in market conditions and other relevant factors, and the quoted price (and the value of the Product that Goldman Sachs will use for account statements or otherwise) could be higher or lower than the original price, and may be higher or lower than the value of the Product as determined by reference to pricing models used by Goldman Sachs. If at any time a third party dealer quotes a price to purchase the Product or otherwise values the Product, that price may be significantly different (higher or lower) than any price quoted by Goldman Sachs. Furthermore, if you sell the Product, you will likely be charged a commission for secondary market transactions, or the price will likely reflect a dealer discount. Goldman Sachs may conduct market making activities in the Product. To the extent Goldman Sachs makes a market, any price quoted for the OTC derivative transactions, Goldman Sachs may differ significantly from (i) their value determined by reference to Goldman Sachs pricing models and (ii) any price quoted by a third party. The market price of the OTC derivative transaction may be influenced by many unpredictable factors, including economic conditions, the creditworthiness of Goldman Sachs, the value of any underlyers, and certain actions taken by Goldman Sachs. \n", "\n", "Market Making, Investing and Lending: Goldman Sachs engages in market making, investing and lending businesses for its own account and the accounts of its affiliates in the same or similar instruments underlying OTC derivative transactions (including such trading as Goldman Sachs deems appropriate in its sole discretion to hedge its market risk in any OTC derivative transaction whether between Goldman Sachs and you or with third parties) and such trading may affect the value of an OTC derivative transaction. \n", "\n", "Early Termination Payments: The provisions of an OTC Derivative Transaction may allow for early termination and, in such cases, either you or Goldman Sachs may be required to make a potentially significant termination payment depending upon whether the OTC Derivative Transaction is in-the-money to Goldman Sachs or you at the time of termination. Indexes: Goldman Sachs does not warrant, and takes no responsibility for, the structure, method of computation or publication of any currency exchange rates, interest rates, indexes of such rates, or credit, equity or other indexes, unless Goldman Sachs specifically advises you otherwise.\n", "Risk Disclosure Regarding futures, options, equity swaps, and other derivatives as well as non-investment-grade securities and ADRs: Please ensure that you have read and understood the current options, futures and security futures disclosure document before entering into any such transactions. Current United States listed options, futures and security futures disclosure documents are available from our sales representatives or at http://www.theocc.com/components/docs/riskstoc.pdf, http://www.goldmansachs.com/disclosures/risk-disclosure-for-futures.pdf and https://www.nfa.futures.org/investors/investor-resources/files/security-futures-disclosure.pdf, respectively. Certain transactions - including those involving futures, options, equity swaps, and other derivatives as well as non-investment-grade securities - give rise to substantial risk and are not available to nor suitable for all investors. If you have any questions about whether you are eligible to enter into these transactions with Goldman Sachs, please contact your sales representative. Foreign-currency-denominated securities are subject to fluctuations in exchange rates that could have an adverse effect on the value or price of, or income derived from, the investment. In addition, investors in securities such as ADRs, the values of which are influenced by foreign currencies, effectively assume currency risk.\n", "Options Risk Disclosures: Options may trade at a value other than that which may be inferred from the current levels of interest rates, dividends (if applicable) and the underlier due to other factors including, but not limited to, expectations of future levels of interest rates, future levels of dividends and the volatility of the underlier at any time prior to maturity. Note: Options involve risk and are not suitable for all investors. Please ensure that you have read and understood the current options disclosure document before entering into any standardized options transactions. United States listed options disclosure documents are available from our sales representatives or at http://theocc.com/publications/risks/riskstoc.pdf. A secondary market may not be available for all options. Transaction costs may be a significant factor in option strategies calling for multiple purchases and sales of options, such as spreads. When purchasing long options an investor may lose their entire investment and when selling uncovered options the risk is potentially unlimited. Supporting documentation for any comparisons, recommendations, statistics, technical data, or other similar information will be supplied upon request.\n", "This material is for the private information of the recipient only. This material is not sponsored, endorsed, sold or promoted by any sponsor or provider of an index referred herein (each, an \"Index Provider\"). GS does not have any affiliation with or control over the Index Providers or any control over the computation, composition or dissemination of the indices. While GS will obtain information from publicly available sources it believes reliable, it will not independently verify this information. Accordingly, GS shall have no liability, contingent or otherwise, to the user or to third parties, for the quality, accuracy, timeliness, continued availability or completeness of the data nor for any special, indirect, incidental or consequential damages which may be incurred or experienced because of the use of the data made available herein, even if GS has been advised of the possibility of such damages.\n", "iTraxx® is a registered trade mark of International Index Company Limited.\n", "iTraxx® is a trade mark of International Index Company Limited and has been licensed for the use by Goldman Sachs Japan Co., Ltd. International Index Company Limited does not approve, endorse or recommend Goldman Sachs Japan Co., Ltd. or iTraxx® derivatives products.\n", "iTraxx® derivatives products are derived from a source considered reliable, but neither International Index Company Limited nor any of its employees, suppliers, subcontractors and agents (together iTraxx Associates) guarantees the veracity, completeness or accuracy of iTraxx® derivatives products or other information furnished in connection with iTraxx® derivatives products. No representation, warranty or condition, express or implied, statutory or otherwise, as to condition, satisfactory quality, performance, or fitness for purpose are given or assumed by International Index Company Limited or any of the iTraxx Associates in respect of iTraxx® derivatives products or any data included in such iTraxx® derivatives products or the use by any person or entity of iTraxx® derivatives products or that data and all those representations, warranties and conditions are excluded save to the extent that such exclusion is prohibited by law.\n", "None of International Index Company Limited nor any of the iTraxx Associates shall have any liability or responsibility to any person or entity for any loss, damages, costs, charges, expenses or other liabilities whether caused by the negligence of International Index Company Limited or any of the iTraxx Associates or otherwise, arising in connection with the use of iTraxx® derivatives products or the iTraxx® indices.\n", "Standard & Poor's ® and S&P ® are registered trademarks of The McGraw-Hill Companies, Inc. and S&P GSCI™ is a trademark of The McGraw-Hill Companies, Inc. and have been licensed for use by the Issuer. This Product (the \"Product\") is not sponsored, endorsed, sold or promoted by S&P and S&P makes no representation, warranty or condition regarding the advisability of investing in the Product.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/events/00_gsquant_meets_markets/01_ideas_for_risk_re_rating/fx_covid19_recovery_trade.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [], "source": [ "import itertools\n", "\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "import pandas as pd\n", "import seaborn as sns\n", "\n", "from gs_quant.instrument import FXBinary, FXMultiCrossBinary, FXMultiCrossBinaryLeg\n", "from gs_quant.markets import PricingContext\n", "from gs_quant.risk import FXSpot\n", "from gs_quant.session import GsSession" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "jupyter": { "source_hidden": true }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this notebook, we will screen for attractive dual binaries in FX to express a leveraged view on a potential vaccine led COVID recovery. This is an example of the GS Quant functionalities and it is not a recommendation of a trade in the instruments discussed, please follow up with your salesperson to discuss specific implementations" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1 - Define a few functions and inputs\n", "\n", "Let's start by defining a few functions and inputs to improve the readability of the notebook.\n", "\n", "First, let's define a tenor and our targets for various FX crosses. In this example, we are leveraging GS GIR's work for analyzing FX price targets in the event of an approved EUA of a COVID vaccine by the end of the year ([source](https://marquee.gs.com/content/research/en/reports/2020/11/08/e8589bbb-cc23-4480-859a-000a8014bd1d.html)). However, the framework we build in this notebook could be deployed to your own targets on vaccine developments (or any other relevant themes)." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# note that we are representing FX crosses as strings in mathematical notation (e.g. USD per GBP)\n", "targets = {\n", " 'USD/GBP': (1.34, 'call'),\n", " 'USD/AUD': (0.75, 'call'),\n", " 'USD/EUR': (1.20, 'call'),\n", " 'CAD/USD': (1.28, 'put'),\n", " 'JPY/USD': (104.1, 'call'),\n", " 'KRW/USD': (1099, 'put'),\n", " 'BRL/USD': (5.26, 'put'),\n", " 'MXN/USD': (19.87, 'put')\n", "}\n", "\n", "tenor = '2m'\n", "crosses = targets.keys()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we'll define a few utility functions we'll use throughout the notebook." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "def build_binary(tenor, cross, strike, opt_type, size=10e6, denominated='USD', buy_sell='Buy'):\n", " '''\n", " utility method to construct a FXBinaryBuilder\n", " '''\n", " return FXBinary(\n", " buy_sell=buy_sell,\n", " expiration_date=tenor,\n", " option_type=opt_type,\n", " notional_currency=denominated,\n", " pair=cross,\n", " strike_price=strike,\n", " premium=0, \n", " notional_amount=size,\n", " payment_date=tenor\n", " )\n", "\n", "\n", "def build_dual_binary(tenor, legs, size=10e6, denominated='USD', buy_sell='Buy'):\n", " '''\n", " utility method to construct an MCB builder\n", " '''\n", " built_legs = []\n", " for leg in legs:\n", " built_legs.append(FXMultiCrossBinaryLeg(\n", " strike_price=leg['strike'], pair=leg['cross'], option_type='Binary %s' % leg['opt_type']\n", " ))\n", " return FXMultiCrossBinary(buy_sell, tenor, legs=tuple(built_legs), premium=0, notional_amount=size,\n", " notional_currency=denominated)\n", "\n", "\n", "\n", "def plot_heatmap(data, cmap, ylabel, xlabel, title):\n", " '''\n", " Utility function to build a heatmap\n", " '''\n", " plt.subplots(figsize=(16, 10))\n", "\n", " vmax = data.max().max()\n", " vmin = data.min().min()\n", "\n", " ax = sns.heatmap(data, annot=True, vmin=vmin, vmax=vmax, fmt='.2f', cmap=cmap)\n", " ax.set(ylabel=ylabel, xlabel=xlabel, title=title)\n", " ax.xaxis.tick_top()\n", " ax.xaxis.set_label_position('top')\n", " \n", " \n", "def corr(mcb_price, p1, p2):\n", " '''\n", " compute the correlation implied by the dual binary price and individuals\n", " the sign is just the correlation between the payouts (so negative always indicates higher savings regardless of call/put combo)\n", " '''\n", " return (mcb_price - p1 * p2) / np.sqrt(p1 * (1 - p1) * p2 * (1 - p2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2 - Vanilla Binaries\n", "\n", "First, a quick example that shows how to compute the percent price of a binary:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0.5046423444297603" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "binary = build_binary('2m', 'JPY/USD', 104, 'Put')\n", "binary.price() / 10e6" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's calculate the vanilla binary prices for each of our individual targets to get a sense of the probability the market is assigning to these targets." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "vanilla_binaries = pd.DataFrame(index=crosses, columns=['spot_ref', 'target', 'price', 'price_future', 'risk_future'])\n", "\n", "with PricingContext(is_batch=True):\n", " for cross, (strike, opt_type) in targets.items():\n", " binary = build_binary(tenor, cross, strike, opt_type)\n", " \n", " vanilla_binaries.loc[cross, 'price_future'] = binary.price()\n", " vanilla_binaries.loc[cross, 'risk_future'] = binary.calc(FXSpot) \n", " vanilla_binaries.loc[cross, 'target'] = strike" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
spot_reftargetprice
CAD/USD1.306651.280.215593
USD/AUD0.732080.750.255526
USD/EUR1.18671.20.318962
MXN/USD20.244619.870.36556
KRW/USD1105.2510990.426555
USD/GBP1.33011.340.456705
BRL/USD5.30625.260.466285
JPY/USD103.745104.10.478362
\n", "
" ], "text/plain": [ " spot_ref target price\n", "CAD/USD 1.30665 1.28 0.215593\n", "USD/AUD 0.73208 0.75 0.255526\n", "USD/EUR 1.1867 1.2 0.318962\n", "MXN/USD 20.2446 19.87 0.36556\n", "KRW/USD 1105.25 1099 0.426555\n", "USD/GBP 1.3301 1.34 0.456705\n", "BRL/USD 5.3062 5.26 0.466285\n", "JPY/USD 103.745 104.1 0.478362" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# once all results are computed summarize relevant details\n", "for cross in crosses:\n", " vanilla_binaries.loc[cross, 'price'] = vanilla_binaries.loc[cross, 'price_future'].result() / 10e6\n", " vanilla_binaries.loc[cross, 'spot_ref'] = vanilla_binaries.loc[cross, 'risk_future'].result()\n", " \n", "vanilla_binaries[['spot_ref', 'target', 'price']].sort_values('price')" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "## 3 - Dual Binaries\n", "\n", "Now calculate the price for ever combination of dual binaries struck at the targets defined above. The top row displays the cheapest dual binary by price, with the right column indicating the savings to the cheapest digi. Darker colors indicate potentially higher savings and therefore potentially more attractive dual digis.\n", "\n", "Please note that the payout of dual binaries is not guaranteed and is dependent on the level of FX spot in both crosses at the time of expiry." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "dual_binary_dict = []\n", "with PricingContext(is_batch=True):\n", " # iterate over all 2-cross combinations\n", " for idx, (cross1, cross2) in enumerate(itertools.combinations(crosses, 2)):\n", " # build the dual binary\n", " legs = [\n", " {'cross': cross1, 'strike': targets[cross1][0], 'opt_type': targets[cross1][1]},\n", " {'cross': cross2, 'strike': targets[cross2][0], 'opt_type': targets[cross2][1]}\n", " ]\n", " mcb = build_dual_binary(tenor, legs)\n", "\n", " dual_binary_dict.append({\n", " 'cross1': cross1,\n", " 'cross2': cross2, \n", " 'price_future': mcb.price(),\n", " }) " ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "for row in dual_binary_dict:\n", " row['price'] = row['price_future'].result() / 10e6\n", " \n", " # individual prices\n", " row['individual_1'] = vanilla_binaries.loc[row['cross1'], 'price']\n", " row['individual_2'] = vanilla_binaries.loc[row['cross2'], 'price']\n", " \n", " row['corr'] = corr(row['price'], row['individual_1'], row['individual_2'])\n", " # define savings as the % savings from the cheapest individual binary\n", " row['savings'] = 1 - row['price'] / min(row['individual_1'], row['individual_2'])\n", " \n", " \n", "dual_binaries = pd.DataFrame(dual_binary_dict)[['cross1', 'cross2', 'price', 'savings', 'corr', 'individual_1', 'individual_2']]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Format the results, showing only the 10 dual binaries with the absolute cheapest mid prices" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
cross1 cross2 price savings corr individual_1 individual_2
18CAD/USDJPY/USD7.3%65-14.5%21.6%47.8%
9USD/AUDJPY/USD7.4%70-21.9%25.6%47.8%
14USD/EURJPY/USD8.7%72-28.1%31.9%47.8%
21CAD/USDMXN/USD12.4%4222.9%21.6%36.6%
13USD/EURCAD/USD14.1%3437.7%31.9%21.6%
8USD/AUDCAD/USD14.5%3250.4%25.6%21.6%
24JPY/USDMXN/USD15.1%58-10.1%47.8%36.6%
2USD/GBPCAD/USD15.8%2629.2%45.7%21.6%
3USD/GBPJPY/USD15.9%65-23.8%45.7%47.8%
19CAD/USDKRW/USD16.4%2335.5%21.6%42.7%
" ], "text/plain": [ "" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "display_table = dual_binaries.sort_values('price').head(10)\n", "\n", "display_table['price'] = pd.Series(['{0:.1f}%'.format(val * 100) for val in display_table['price']], index=display_table.index)\n", "display_table['savings'] = pd.Series([int(val * 100) for val in display_table['savings']], index=display_table.index)\n", "\n", "display_table['corr'] = pd.Series(['{0:.1f}%'.format(val * 100) for val in display_table['corr']], index=display_table.index)\n", "\n", "display_table['individual_1'] = pd.Series(['{0:.1f}%'.format(val * 100) for val in display_table['individual_1']], index=display_table.index)\n", "display_table['individual_2'] = pd.Series(['{0:.1f}%'.format(val * 100) for val in display_table['individual_2']], index=display_table.index)\n", "\n", "display_table.style.background_gradient(subset=['savings'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4 - Dual Binary Grids" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "We will now consider a wider range of strikes for the cheapest dual binary in\n", "the table above. Let us consider strikes within a 3% range from the targets\n", "defined above. The tables below show both the mid price and the implied correlation. Optimizing both of these outputs allows us to determine\n", "our preferable strikes for the trade." ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "min_price_index = dual_binaries['price'].idxmin()\n", "cross1 = dual_binaries.iloc[min_price_index]['cross1']\n", "cross2 = dual_binaries.iloc[min_price_index]['cross2']\n", "\n", "# % from target strikes to compute for the grid\n", "strikes = [-3, -2.5, -2, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, 2, 2.5, 3]\n", "\n", "mcb_ppct_future = pd.DataFrame(index=strikes, columns=strikes)\n", "indiv_ppct_future = pd.DataFrame(index=strikes, columns=[cross1, cross2])\n", "\n", "with PricingContext(is_batch=True):\n", " # loop over all dual binary combinations\n", " for (i, j) in itertools.product(strikes, strikes):\n", " strike1 = targets[cross1][0] * (1 + i / 100)\n", " strike2 = targets[cross2][0] * (1 + j / 100)\n", "\n", " # build the dual binary\n", " legs = [\n", " {'cross': cross1, 'strike': strike1, 'opt_type': targets[cross1][1]},\n", " {'cross': cross2, 'strike': strike2, 'opt_type': targets[cross2][1]}\n", " ]\n", " mcb = build_dual_binary(tenor, legs)\n", " mcb_ppct_future.loc[i, j] = mcb.price()\n", "\n", " # loop over all indvidual binary combinations for both crosses\n", " # we'll use these to compute savings later as well\n", " for cross in [cross1, cross2]:\n", " for i in strikes:\n", " strike = targets[cross][0] * (1 + i / 100)\n", " binary = build_binary(tenor, cross, strike, targets[cross][1])\n", " indiv_ppct_future.loc[i, cross] = binary.price()" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "price_matrix = pd.DataFrame(index=strikes, columns=strikes, dtype='float64')\n", "savings_matrix = pd.DataFrame(index=strikes, columns=strikes, dtype='float64')\n", "corr_matrix = pd.DataFrame(index=strikes, columns=strikes, dtype='float64')\n", "\n", "for (i, j) in itertools.product(strikes, strikes):\n", " # price pct of the mcb\n", " price_pct = mcb_ppct_future.loc[i, j].result() / 10e6\n", " price_matrix.loc[i, j] = price_pct * 100\n", "\n", " # calc savings \n", " p_1 = indiv_ppct_future.loc[i, cross1].result() / 10e6\n", " p_2 = indiv_ppct_future.loc[j, cross2].result() / 10e6\n", " savings_matrix.loc[i, j] = 1 - (price_pct / min(p_1, p_2))\n", " \n", " # calc correlation\n", " corr_matrix.loc[i, j] = 100 * corr(price_pct, p_1, p_2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Quickly sanity check the percent prices of the individual binaries at the different strike combinations:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
CAD/USDJPY/USD
-3.03.485.0
-2.54.881.4
-2.06.677.0
-1.59.171.3
-1.012.364.5
-0.516.456.5
0.021.647.8
0.527.739.1
1.034.830.9
1.542.623.6
2.050.817.6
2.558.712.8
3.065.99.2
\n", "
" ], "text/plain": [ " CAD/USD JPY/USD\n", "-3.0 3.4 85.0\n", "-2.5 4.8 81.4\n", "-2.0 6.6 77.0\n", "-1.5 9.1 71.3\n", "-1.0 12.3 64.5\n", "-0.5 16.4 56.5\n", " 0.0 21.6 47.8\n", " 0.5 27.7 39.1\n", " 1.0 34.8 30.9\n", " 1.5 42.6 23.6\n", " 2.0 50.8 17.6\n", " 2.5 58.7 12.8\n", " 3.0 65.9 9.2" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "round(indiv_ppct_future.applymap(lambda x: x.result() / 10e6) * 100, 1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First, let's plot the mid price of the dual binary for all of the strike combinations:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "jupyter": { "source_hidden": true }, "pycharm": { "name": "#%%\n" } }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plot_heatmap(price_matrix, \"coolwarm\", cross2 + \" \" + targets[cross2][1],\n", " cross1 + \" \" + targets[cross1][1], \"MCB Price (%) By Strikes\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then, let's plot the savings vs. the cheapest inividual binary for all of the combinations on the grid. Negative numbers indicate correlations that provide more savings." ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plot_heatmap(savings_matrix, \"Blues\", cross2 + \" \" + targets[cross2][1],\n", " cross1 + \" \" + targets[cross1][1], \"Savings (%) By Strikes\" )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Appendix - Dual Binary Carry" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "with PricingContext(is_batch=True):\n", " for row in dual_binary_dict:\n", " # build the dual binary\n", " legs = [\n", " {'cross': row['cross1'], 'strike': targets[row['cross1']][0], 'opt_type': targets[row['cross1']][1]},\n", " {'cross': row['cross2'], 'strike': targets[row['cross2']][0], 'opt_type': targets[row['cross2']][1]}\n", " ]\n", " mcb = build_dual_binary('1m', legs) # simple static carry, just reduce tenor to 1m\n", "\n", " row['price_fwd_future'] = mcb.price()" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
cross1cross2pricesavingsstatic_carry_1m
23JPY/USDBRL/USD0.2017740.567274-0.015183
25KRW/USDBRL/USD0.2298530.461141-0.017895
24JPY/USDMXN/USD0.1506490.587894-0.021028
5USD/GBPBRL/USD0.2605220.429560-0.022684
4USD/GBPKRW/USD0.2502000.413440-0.025069
27BRL/USDMXN/USD0.2520880.310407-0.025489
14USD/EURJPY/USD0.0872490.726459-0.027515
3USD/GBPJPY/USD0.1591570.651511-0.027681
22JPY/USDKRW/USD0.1727470.595019-0.028986
9USD/AUDJPY/USD0.0744240.708740-0.030358
\n", "
" ], "text/plain": [ " cross1 cross2 price savings static_carry_1m\n", "23 JPY/USD BRL/USD 0.201774 0.567274 -0.015183\n", "25 KRW/USD BRL/USD 0.229853 0.461141 -0.017895\n", "24 JPY/USD MXN/USD 0.150649 0.587894 -0.021028\n", "5 USD/GBP BRL/USD 0.260522 0.429560 -0.022684\n", "4 USD/GBP KRW/USD 0.250200 0.413440 -0.025069\n", "27 BRL/USD MXN/USD 0.252088 0.310407 -0.025489\n", "14 USD/EUR JPY/USD 0.087249 0.726459 -0.027515\n", "3 USD/GBP JPY/USD 0.159157 0.651511 -0.027681\n", "22 JPY/USD KRW/USD 0.172747 0.595019 -0.028986\n", "9 USD/AUD JPY/USD 0.074424 0.708740 -0.030358" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "for row in dual_binary_dict:\n", " row['price_fwd'] = row['price_fwd_future'].result() / 10e6\n", " row['static_carry_1m'] = row['price_fwd'] - row['price']\n", "\n", "carry_table = pd.DataFrame(dual_binary_dict).sort_values('static_carry_1m', ascending=False)\n", "carry_table.head(10)[['cross1', 'cross2', 'price', 'savings', 'static_carry_1m']]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Disclaimers\n", "\n", "Indicative Terms/Pricing Levels: This material may contain indicative terms only, including but not limited to pricing levels. There is no representation that any transaction can or could have been effected at such terms or prices. Proposed terms and conditions are for discussion purposes only. Finalized terms and conditions are subject to further discussion and negotiation.\n", "www.goldmansachs.com/disclaimer/sales-and-trading-invest-rec-disclosures.html If you are not accessing this material via Marquee ContentStream, a list of the author's investment recommendations disseminated during the preceding 12 months and the proportion of the author's recommendations that are 'buy', 'hold', 'sell' or other over the previous 12 months is available by logging into Marquee ContentStream using the link below. Alternatively, if you do not have access to Marquee ContentStream, please contact your usual GS representative who will be able to provide this information to you.\n", "Backtesting, Simulated Results, Sensitivity/Scenario Analysis or Spreadsheet Calculator or Model: There may be data presented herein that is solely for illustrative purposes and which may include among other things back testing, simulated results and scenario analyses. The information is based upon certain factors, assumptions and historical information that Goldman Sachs may in its discretion have considered appropriate, however, Goldman Sachs provides no assurance or guarantee that this product will operate or would have operated in the past in a manner consistent with these assumptions. In the event any of the assumptions used do not prove to be true, results are likely to vary materially from the examples shown herein. Additionally, the results may not reflect material economic and market factors, such as liquidity, transaction costs and other expenses which could reduce potential return.\n", "OTC Derivatives Risk Disclosures: \n", "Terms of the Transaction: To understand clearly the terms and conditions of any OTC derivative transaction you may enter into, you should carefully review the Master Agreement, including any related schedules, credit support documents, addenda and exhibits. You should not enter into OTC derivative transactions unless you understand the terms of the transaction you are entering into as well as the nature and extent of your risk exposure. You should also be satisfied that the OTC derivative transaction is appropriate for you in light of your circumstances and financial condition. You may be requested to post margin or collateral to support written OTC derivatives at levels consistent with the internal policies of Goldman Sachs. \n", "\n", "Liquidity Risk: There is no public market for OTC derivative transactions and, therefore, it may be difficult or impossible to liquidate an existing position on favorable terms. Transfer Restrictions: OTC derivative transactions entered into with one or more affiliates of The Goldman Sachs Group, Inc. (Goldman Sachs) cannot be assigned or otherwise transferred without its prior written consent and, therefore, it may be impossible for you to transfer any OTC derivative transaction to a third party. \n", "\n", "Conflict of Interests: Goldman Sachs may from time to time be an active participant on both sides of the market for the underlying securities, commodities, futures, options or any other derivative or instrument identical or related to those mentioned herein (together, \"the Product\"). Goldman Sachs at any time may have long or short positions in, or buy and sell Products (on a principal basis or otherwise) identical or related to those mentioned herein. Goldman Sachs hedging and trading activities may affect the value of the Products. \n", "\n", "Counterparty Credit Risk: Because Goldman Sachs, may be obligated to make substantial payments to you as a condition of an OTC derivative transaction, you must evaluate the credit risk of doing business with Goldman Sachs or its affiliates. \n", "\n", "Pricing and Valuation: The price of each OTC derivative transaction is individually negotiated between Goldman Sachs and each counterparty and Goldman Sachs does not represent or warrant that the prices for which it offers OTC derivative transactions are the best prices available, possibly making it difficult for you to establish what is a fair price for a particular OTC derivative transaction; The value or quoted price of the Product at any time, however, will reflect many factors and cannot be predicted. If Goldman Sachs makes a market in the offered Product, the price quoted by Goldman Sachs would reflect any changes in market conditions and other relevant factors, and the quoted price (and the value of the Product that Goldman Sachs will use for account statements or otherwise) could be higher or lower than the original price, and may be higher or lower than the value of the Product as determined by reference to pricing models used by Goldman Sachs. If at any time a third party dealer quotes a price to purchase the Product or otherwise values the Product, that price may be significantly different (higher or lower) than any price quoted by Goldman Sachs. Furthermore, if you sell the Product, you will likely be charged a commission for secondary market transactions, or the price will likely reflect a dealer discount. Goldman Sachs may conduct market making activities in the Product. To the extent Goldman Sachs makes a market, any price quoted for the OTC derivative transactions, Goldman Sachs may differ significantly from (i) their value determined by reference to Goldman Sachs pricing models and (ii) any price quoted by a third party. The market price of the OTC derivative transaction may be influenced by many unpredictable factors, including economic conditions, the creditworthiness of Goldman Sachs, the value of any underlyers, and certain actions taken by Goldman Sachs. \n", "\n", "Market Making, Investing and Lending: Goldman Sachs engages in market making, investing and lending businesses for its own account and the accounts of its affiliates in the same or similar instruments underlying OTC derivative transactions (including such trading as Goldman Sachs deems appropriate in its sole discretion to hedge its market risk in any OTC derivative transaction whether between Goldman Sachs and you or with third parties) and such trading may affect the value of an OTC derivative transaction. \n", "\n", "Early Termination Payments: The provisions of an OTC Derivative Transaction may allow for early termination and, in such cases, either you or Goldman Sachs may be required to make a potentially significant termination payment depending upon whether the OTC Derivative Transaction is in-the-money to Goldman Sachs or you at the time of termination. Indexes: Goldman Sachs does not warrant, and takes no responsibility for, the structure, method of computation or publication of any currency exchange rates, interest rates, indexes of such rates, or credit, equity or other indexes, unless Goldman Sachs specifically advises you otherwise.\n", "Risk Disclosure Regarding futures, options, equity swaps, and other derivatives as well as non-investment-grade securities and ADRs: Please ensure that you have read and understood the current options, futures and security futures disclosure document before entering into any such transactions. Current United States listed options, futures and security futures disclosure documents are available from our sales representatives or at http://www.theocc.com/components/docs/riskstoc.pdf, http://www.goldmansachs.com/disclosures/risk-disclosure-for-futures.pdf and https://www.nfa.futures.org/investors/investor-resources/files/security-futures-disclosure.pdf, respectively. Certain transactions - including those involving futures, options, equity swaps, and other derivatives as well as non-investment-grade securities - give rise to substantial risk and are not available to nor suitable for all investors. If you have any questions about whether you are eligible to enter into these transactions with Goldman Sachs, please contact your sales representative. Foreign-currency-denominated securities are subject to fluctuations in exchange rates that could have an adverse effect on the value or price of, or income derived from, the investment. In addition, investors in securities such as ADRs, the values of which are influenced by foreign currencies, effectively assume currency risk.\n", "Options Risk Disclosures: Options may trade at a value other than that which may be inferred from the current levels of interest rates, dividends (if applicable) and the underlier due to other factors including, but not limited to, expectations of future levels of interest rates, future levels of dividends and the volatility of the underlier at any time prior to maturity. Note: Options involve risk and are not suitable for all investors. Please ensure that you have read and understood the current options disclosure document before entering into any standardized options transactions. United States listed options disclosure documents are available from our sales representatives or at http://theocc.com/publications/risks/riskstoc.pdf. A secondary market may not be available for all options. Transaction costs may be a significant factor in option strategies calling for multiple purchases and sales of options, such as spreads. When purchasing long options an investor may lose their entire investment and when selling uncovered options the risk is potentially unlimited. Supporting documentation for any comparisons, recommendations, statistics, technical data, or other similar information will be supplied upon request.\n", "This material is for the private information of the recipient only. This material is not sponsored, endorsed, sold or promoted by any sponsor or provider of an index referred herein (each, an \"Index Provider\"). GS does not have any affiliation with or control over the Index Providers or any control over the computation, composition or dissemination of the indices. While GS will obtain information from publicly available sources it believes reliable, it will not independently verify this information. Accordingly, GS shall have no liability, contingent or otherwise, to the user or to third parties, for the quality, accuracy, timeliness, continued availability or completeness of the data nor for any special, indirect, incidental or consequential damages which may be incurred or experienced because of the use of the data made available herein, even if GS has been advised of the possibility of such damages.\n", "iTraxx® is a registered trade mark of International Index Company Limited.\n", "iTraxx® is a trade mark of International Index Company Limited and has been licensed for the use by Goldman Sachs Japan Co., Ltd. International Index Company Limited does not approve, endorse or recommend Goldman Sachs Japan Co., Ltd. or iTraxx® derivatives products.\n", "iTraxx® derivatives products are derived from a source considered reliable, but neither International Index Company Limited nor any of its employees, suppliers, subcontractors and agents (together iTraxx Associates) guarantees the veracity, completeness or accuracy of iTraxx® derivatives products or other information furnished in connection with iTraxx® derivatives products. No representation, warranty or condition, express or implied, statutory or otherwise, as to condition, satisfactory quality, performance, or fitness for purpose are given or assumed by International Index Company Limited or any of the iTraxx Associates in respect of iTraxx® derivatives products or any data included in such iTraxx® derivatives products or the use by any person or entity of iTraxx® derivatives products or that data and all those representations, warranties and conditions are excluded save to the extent that such exclusion is prohibited by law.\n", "None of International Index Company Limited nor any of the iTraxx Associates shall have any liability or responsibility to any person or entity for any loss, damages, costs, charges, expenses or other liabilities whether caused by the negligence of International Index Company Limited or any of the iTraxx Associates or otherwise, arising in connection with the use of iTraxx® derivatives products or the iTraxx® indices.\n", "Standard & Poor's ® and S&P ® are registered trademarks of The McGraw-Hill Companies, Inc. and S&P GSCI™ is a trademark of The McGraw-Hill Companies, Inc. and have been licensed for use by the Issuer. This Product (the \"Product\") is not sponsored, endorsed, sold or promoted by S&P and S&P makes no representation, warranty or condition regarding the advisability of investing in the Product.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.0" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/events/00_gsquant_meets_markets/01_ideas_for_risk_re_rating/inflation_covid19_recovery_trade.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [], "source": [ "import datetime as dt\n", "import warnings\n", "from datetime import date\n", "\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "import pandas as pd\n", "import seaborn as sns\n", "import statsmodels.api as sm\n", "\n", "warnings.filterwarnings('ignore')\n", "\n", "from gs_quant.instrument import InflationSwap\n", "from gs_quant.markets import HistoricalPricingContext\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.risk import IRFwdRate\n", "from gs_quant.data import Dataset\n", "from gs_quant.datetime import business_day_offset, business_day_count\n", "from gs_quant.api.fred.data import FredDataApi\n", "from gs_quant.timeseries import correlation, std, diff\n", "from gs_quant.common import PayReceive, Currency\n", "\n", "pd.options.display.float_format = '{:,.2f}'.format" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "\n", "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics', 'read_product_data'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this notebook, we will focus on inflation curves. Using macro economic drivers such as rates, equities, credit and oil, we will analyse trends and compare pre and post Covid-19 pandemic relationships. This is an example of the GS Quant functionalities and it is not a recommendation of a trade in the instruments discussed, please follow up with your salesperson to discuss specific implementations.\n", "\n", "* [1 - Define a few function and inputs](#1---Define-a-few-functions-and-inputs)\n", "* [2 - Build the inflation portfolio](#2---Build-the-inflation-portfolio)\n", "* [3 - Retrieve macro drivers data](#3---Retrieve-macro-drivers-data)\n", "* [4 - Carry](#4---Carry)\n", "* [5 - Correlation to macro drivers](#5---Correlation-to-macro-drivers)\n", "* [6 - Actual vs Predicted](#6---Actual-vs-Predicted)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1 - Define a few functions and inputs\n", "\n", "Let's start by defining a few handy functions and inputs to improve readability of the notebook. The functions created here help with handling date offsets and rolling of trades, aggregating the portfolio at the instrument level rather than leg level (which proves useful for the curves), and finally computing correlations and multi-factor linear regressions." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [], "source": [ "def roll_forward_1y(trade):\n", " '''\n", " Roll the trade 1y\n", " For forward starting trade decrease the fwd start\n", " For spot starting roll the tenor\n", " '''\n", " fwd, tenor = trade[-2:]\n", " tenor = int(tenor[:-1])\n", " if fwd is None:\n", " trade[-1] = '{}y'.format(tenor - 1)\n", " else:\n", " fwd = int(fwd[:-1])\n", " trade[-2] = '{}y'.format(fwd - 1)\n", " return trade\n", "\n", "\n", "def date_subtract_period_str(ref_date, period): \n", " '''\n", " Substract 1y/m/d to a fixed date\n", " '''\n", " time_letter = period[-1]\n", " num = int(period[:-1])\n", "\n", " if time_letter == 'd':\n", " past_date = ref_date - dt.timedelta(days=num)\n", " elif time_letter == 'm':\n", " past_date = date(ref_date.year, ref_date.month - num, ref_date.day)\n", " else: \n", " past_date = date(ref_date.year - num, ref_date.month, ref_date.day)\n", "\n", " return past_date\n", "\n", "\n", "def create_inflation_swap(trade):\n", " '''\n", " Create an inflation swap from\n", " name, CCY, pay/receive, fwd start\n", " and tenor input\n", " '''\n", " trade_name, ccy, payrec, fwd, tenor = trade\n", " return InflationSwap(pay_or_receive=payrec, termination_date=tenor, notional_currency=ccy, effective_date=fwd,\n", " name=trade_name)\n", "\n", "\n", "def negate_leg(col):\n", " '''\n", " In order to aggregate price for the flatteners we need to negate\n", " the receive leg\n", " '''\n", " instr = col.name\n", " if 'flattener' in instr.name and instr.pay_or_receive == PayReceive.Receive:\n", " return -col\n", " return col\n", " \n", "\n", "def negate_carry(port, series):\n", " '''\n", " In order to aggregate carry for the flatteners we need to negate\n", " carry numbers for the receive leg\n", " '''\n", " mask = port.to_frame()['pay_or_receive'] == PayReceive.Receive \n", " return series.where(mask.array, -series)\n", "\n", "\n", "def flip_flatteners(prices):\n", " '''\n", " When computing correlation we flip prices for the flatteners to visualise \n", " numbers as if we were putting on the trade\n", " '''\n", " flipped_p = prices\n", " if 'flattener' in prices.name:\n", " flipped_p = prices * (-1)\n", " return flipped_p\n", "\n", "\n", "def correl_map(drivers_df, prices_df, start_date):\n", " '''\n", " Compute correlation of returns between two timeseries from a specific date \n", " '''\n", " start_date = business_day_offset(start_date, 0, roll='preceding')\n", " drivers_diff = diff(drivers_df[drivers_df.index > start_date])\n", " prices_diff = diff(prices_df[prices_df.index > start_date])\n", "\n", " correl_df = pd.DataFrame(index=prices_df.columns, columns=drivers_df.columns)\n", " for x in range(len(drivers_df.columns)):\n", " for y in range(len(prices_df.columns)):\n", " correl = correlation(drivers_diff.iloc[:, x], prices_diff.iloc[:, y], type_='returns')\n", " correl_df.iloc[y, x] = correl.iloc[-1]\n", "\n", " return correl_df\n", "\n", "\n", "def regression(y, x, regr_start, regr_end, select_x=False):\n", " '''\n", " Compute a multi-factors linear regression of y on a subset of x\n", " '''\n", " y = y.loc[regr_start:regr_end]\n", " x = x.loc[regr_start:regr_end]\n", " if select_x:\n", " x = x[select_x]\n", "\n", " x = sm.add_constant(x) # adding a constant\n", " model = sm.OLS(y, x).fit()\n", "\n", " return model\n", "\n", "\n", "def regr_prediction(y, x, regr_start, regr_end, predict_start, predict_end=None, select_x=False):\n", " '''\n", " Compute a multi-factors linear regression of y on a subset of x between regr_start and regr_end\n", " and return predictions vs actuals for y from predict_start to predict_end\n", " '''\n", " model = regression(y, x, regr_start, regr_end, select_x=select_x)\n", " y_actual = y.loc[slice(predict_start, predict_end)]\n", " x_actual = x.loc[slice(predict_start, predict_end)]\n", " \n", " prediction = model.predict(sm.add_constant(x_actual))\n", " prediction.name = 'Predicted'\n", "\n", " return pd.DataFrame([y_actual, prediction]).T\n", "\n", "\n", "today = date.today()\n", "\n", "start_date = business_day_offset(date(2015, 11, 1), 0, roll='following')\n", "end_date = business_day_offset(today, -1, roll='preceding')\n", "\n", "regr_start = date(2018, 10, 1)\n", "regr_end = date(2019, 10, 1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2 - Build the inflation portfolio\n", "\n", "let's build a portfolio of inflation swaps and curves, then retrieve historical rates." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
asset_classtypeeffective_datefeenotional_currencypay_or_receivetermination_date
instrument
2y2y HICP (InflationSwap)AssetClass.RatesAssetType.InflationSwap2y0Currency.EURPayReceive.Receive2y
5y5y HICP (InflationSwap)AssetClass.RatesAssetType.InflationSwap5y0Currency.EURPayReceive.Receive5y
5s20s HICP flattener (InflationSwap)AssetClass.RatesAssetType.InflationSwapdefault0Currency.EURPayReceive.Receive5y
5s20s HICP flattener (InflationSwap)AssetClass.RatesAssetType.InflationSwapdefault0Currency.EURPayReceive.Pay20y
10s30s HICP flattener (InflationSwap)AssetClass.RatesAssetType.InflationSwapdefault0Currency.EURPayReceive.Receive10y
10s30s HICP flattener (InflationSwap)AssetClass.RatesAssetType.InflationSwapdefault0Currency.EURPayReceive.Pay30y
5y5y/10y10y HICP flattener (InflationSwap)AssetClass.RatesAssetType.InflationSwap5y0Currency.EURPayReceive.Receive5y
5y5y/10y10y HICP flattener (InflationSwap)AssetClass.RatesAssetType.InflationSwap10y0Currency.EURPayReceive.Pay10y
\n", "
" ], "text/plain": [ " asset_class \\\n", "instrument \n", "2y2y HICP (InflationSwap) AssetClass.Rates \n", "5y5y HICP (InflationSwap) AssetClass.Rates \n", "5s20s HICP flattener (InflationSwap) AssetClass.Rates \n", "5s20s HICP flattener (InflationSwap) AssetClass.Rates \n", "10s30s HICP flattener (InflationSwap) AssetClass.Rates \n", "10s30s HICP flattener (InflationSwap) AssetClass.Rates \n", "5y5y/10y10y HICP flattener (InflationSwap) AssetClass.Rates \n", "5y5y/10y10y HICP flattener (InflationSwap) AssetClass.Rates \n", "\n", " type \\\n", "instrument \n", "2y2y HICP (InflationSwap) AssetType.InflationSwap \n", "5y5y HICP (InflationSwap) AssetType.InflationSwap \n", "5s20s HICP flattener (InflationSwap) AssetType.InflationSwap \n", "5s20s HICP flattener (InflationSwap) AssetType.InflationSwap \n", "10s30s HICP flattener (InflationSwap) AssetType.InflationSwap \n", "10s30s HICP flattener (InflationSwap) AssetType.InflationSwap \n", "5y5y/10y10y HICP flattener (InflationSwap) AssetType.InflationSwap \n", "5y5y/10y10y HICP flattener (InflationSwap) AssetType.InflationSwap \n", "\n", " effective_date fee \\\n", "instrument \n", "2y2y HICP (InflationSwap) 2y 0 \n", "5y5y HICP (InflationSwap) 5y 0 \n", "5s20s HICP flattener (InflationSwap) default 0 \n", "5s20s HICP flattener (InflationSwap) default 0 \n", "10s30s HICP flattener (InflationSwap) default 0 \n", "10s30s HICP flattener (InflationSwap) default 0 \n", "5y5y/10y10y HICP flattener (InflationSwap) 5y 0 \n", "5y5y/10y10y HICP flattener (InflationSwap) 10y 0 \n", "\n", " notional_currency \\\n", "instrument \n", "2y2y HICP (InflationSwap) Currency.EUR \n", "5y5y HICP (InflationSwap) Currency.EUR \n", "5s20s HICP flattener (InflationSwap) Currency.EUR \n", "5s20s HICP flattener (InflationSwap) Currency.EUR \n", "10s30s HICP flattener (InflationSwap) Currency.EUR \n", "10s30s HICP flattener (InflationSwap) Currency.EUR \n", "5y5y/10y10y HICP flattener (InflationSwap) Currency.EUR \n", "5y5y/10y10y HICP flattener (InflationSwap) Currency.EUR \n", "\n", " pay_or_receive \\\n", "instrument \n", "2y2y HICP (InflationSwap) PayReceive.Receive \n", "5y5y HICP (InflationSwap) PayReceive.Receive \n", "5s20s HICP flattener (InflationSwap) PayReceive.Receive \n", "5s20s HICP flattener (InflationSwap) PayReceive.Pay \n", "10s30s HICP flattener (InflationSwap) PayReceive.Receive \n", "10s30s HICP flattener (InflationSwap) PayReceive.Pay \n", "5y5y/10y10y HICP flattener (InflationSwap) PayReceive.Receive \n", "5y5y/10y10y HICP flattener (InflationSwap) PayReceive.Pay \n", "\n", " termination_date \n", "instrument \n", "2y2y HICP (InflationSwap) 2y \n", "5y5y HICP (InflationSwap) 5y \n", "5s20s HICP flattener (InflationSwap) 5y \n", "5s20s HICP flattener (InflationSwap) 20y \n", "10s30s HICP flattener (InflationSwap) 10y \n", "10s30s HICP flattener (InflationSwap) 30y \n", "5y5y/10y10y HICP flattener (InflationSwap) 5y \n", "5y5y/10y10y HICP flattener (InflationSwap) 10y " ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "trades_info = (\n", " ['2y2y HICP', Currency.EUR, PayReceive.Receive, '2y', '2y'],\n", " ['5y5y HICP', Currency.EUR, PayReceive.Receive, '5y', '5y'],\n", " ['5s20s HICP flattener', Currency.EUR, PayReceive.Receive, None, '5y'],\n", " ['5s20s HICP flattener', Currency.EUR, PayReceive.Pay, None, '20y'],\n", " ['10s30s HICP flattener', Currency.EUR, PayReceive.Receive, None, '10y'],\n", " ['10s30s HICP flattener', Currency.EUR, PayReceive.Pay, None, '30y'],\n", " ['5y5y/10y10y HICP flattener', Currency.EUR, PayReceive.Receive, '5y', '5y'],\n", " ['5y5y/10y10y HICP flattener', Currency.EUR, PayReceive.Pay, '10y', '10y'],\n", ")\n", "\n", "infla_port = Portfolio(map(create_inflation_swap, trades_info), name = 'inflation swaps')\n", "infla_df = infla_port.to_frame()\n", "infla_df.index = infla_df.index.droplevel(0)\n", "infla_df.fillna('default')" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
2y2y HICP5y5y HICP5s20s HICP flattener10s30s HICP flattener5y5y/10y10y HICP flattener
2020-11-1185.58118.1243.5041.7539.26
2020-11-1283.83116.2942.8241.1338.61
2020-11-1381.97114.6142.1441.0638.91
2020-11-1684.71116.0339.7539.3237.92
2020-11-1781.40114.1640.5039.6338.10
\n", "
" ], "text/plain": [ " 2y2y HICP 5y5y HICP 5s20s HICP flattener 10s30s HICP flattener \\\n", "2020-11-11 85.58 118.12 43.50 41.75 \n", "2020-11-12 83.83 116.29 42.82 41.13 \n", "2020-11-13 81.97 114.61 42.14 41.06 \n", "2020-11-16 84.71 116.03 39.75 39.32 \n", "2020-11-17 81.40 114.16 40.50 39.63 \n", "\n", " 5y5y/10y10y HICP flattener \n", "2020-11-11 39.26 \n", "2020-11-12 38.61 \n", "2020-11-13 38.91 \n", "2020-11-16 37.92 \n", "2020-11-17 38.10 " ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "with HistoricalPricingContext(start_date, end_date, is_batch=True, csa_term='EUR-OIS'):\n", " port_hist_rate = infla_port.calc(IRFwdRate)\n", "\n", "prices = pd.DataFrame(port_hist_rate)\n", "prices.index = infla_port.instruments\n", "prices_T = prices.T.apply(negate_leg)\n", "prices_T.columns = [instr.name for instr in infla_port.instruments]\n", "prices_T = prices_T.groupby(level=0, axis=1, sort=False).sum()\n", "prices_T *= 1e4\n", "prices_T.tail()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3 - Retrieve macro drivers data\n", "\n", "\n", "For this analysis we use rates, credit and equities data from [Marquee data catalog](https://marquee.gs.com/s/discover/data-services/catalog) and oil data from [FRED](https://fred.stlouisfed.org/). You will need to use your own FRED API key. See [here](https://research.stlouisfed.org/useraccount/login/secure/) to create one (takes under a minute). " ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "FRED_API_KEY = 'YOUR_KEY_HERE'\n", "fred_API = FredDataApi(api_key=FRED_API_KEY)\n", "fred_pull = fred_API.build_query(start=start_date, end=end_date)\n", "\n", "drivers = {\n", " 'Rates': {'instr': ['EUR', 'GBP', 'USD'], 'tenor': ['10y', '30y']},\n", " 'Credit': {'instr': ['Xover', 'CDX_IG'], 'tenor': ['5y']},\n", " 'Equities': ['SPX', 'SX5E'],\n", " 'Commodities': ['WTI']\n", "}\n", "\n", "dataset_map = {\n", " 'Rates': {'id': 'IR_SWAP2', 'value': 'rate'},\n", " 'Credit': {'id': 'CDSLEVELS', 'value': 'spread'},\n", " 'Equities': {'id': 'TREOD', 'value': 'closePrice'}, \n", " 'Commodities': 'fred'\n", "}\n", "\n", "id_map = {\n", " 'EUR': 'MA5WM2QWRVMYKDK0',\n", " 'GBP': 'MABXTJXXN8WJR7R8',\n", " 'USD': 'MAAXGV0GZTW4GFNC',\n", " 'Xover': 'MA67A5J6SPG8F7R6',\n", " 'CDX_IG': 'MANJG63BKCNEHYW3',\n", " 'SX5E': 'MA4B66MW5E27U8P32SY',\n", " 'SPX': 'MA4B66MW5E27U8P32SB',\n", " 'WTI': 'DCOILWTICO'\n", "}\n", "id_map_inv = {v: k for k, v in id_map.items()}\n", "\n", "df_list = []\n", "\n", "for asset_type, assets in drivers.items():\n", " ds_info = dataset_map[asset_type]\n", " \n", " if ds_info == 'fred': \n", " sub_df = pd.DataFrame(fred_API.query_data(fred_pull, dataset_id=id_map['WTI']))\n", " sub_df.columns = ['WTI']\n", "\n", " else: \n", " col = ['instr name']\n", "\n", " if asset_type in ['Rates', 'Credit']:\n", " id_keys = assets['instr']\n", " kwargs = {'tenor': assets['tenor']}\n", " col.append('tenor')\n", " separator = '_'\n", " else:\n", " id_keys = assets\n", " kwargs = {}\n", " separator = ''\n", "\n", " asset_ids = [id_map[x] for x in id_keys]\n", " raw_data = Dataset(ds_info['id']).get_data(start_date, end_date, assetId=asset_ids, **kwargs)\n", " raw_data['instr name'] = raw_data.apply(lambda x: id_map_inv[x['assetId']], axis=1)\n", " sub_df = pd.pivot_table(raw_data, values=ds_info['value'], index=['date'], columns=col)\n", " sub_df.columns = sub_df.columns.map(separator.join)\n", "\n", " df_list.append(sub_df)\n", "\n", "drivers_df = pd.concat(df_list, axis=1)\n", "drivers_df = drivers_df.fillna(method='ffill').dropna()" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [ { "data": { "image/png": "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LrbuLvH7OduIVHUXXbIhP2NMNC67vhSoZ609AO7DAXdxVNK24Z6nfzXGrDLGfGqMqVeO4YpUqPM5P85wKzDFXmzdqOSCdT7nhrU2A/gNsA5PDwfgrXIMV6TCnOffjW1AO2NMtKcH0HVAk/KLVqTilOHcuBFYba1NwyUo9uZbttfTJlLlXbTJBmNMKDAVeNhae6IMu/ADGgM/Wmu7A4uB570Yokil8cL5kd9o4KPzj0qk8p3vuWGM8cclG7oBDYG1wBNeDVKkEpzvuWGtPYo7N6bgesLtBDK9GaNIZTjXc8MY0xF4Frgnp6mI1XQDR6qFizLZ4PmyNxWYbK393NN80NNNKWeM1aGz7CYJOAVM87z+FOheDuGKVCgvnR85++oK+FlrV5ZLsCIVyEvnRgyAtXa7p7fPJ0DfcgpZpEJ46++GtfYra+0l1to+wBYgvrxiFqkI53puGGMa464t7rDWbvc078Xd4MzRGA2/k2rioks2eMbIvgVsstb+O9+iL4Gxnudjgekl7cfzJfErYLCnaSiw0avBilQwb50f+dyKejXIBcCL58Y+oIMxpo7n9eW4cbwi1ZI3/24YY+p6HmsD9wFvejdakYpzrueGZ4j218AT1tofc1b2DLVINsb09uzzDkr/PUykUl2Ms1H0x3XPWwdke5r/iBtD9QnQFNgN3GytPeLZZieuiFcAcAwYZq3daIxpBvwPCAcOA7+01u6uuKMR8S5vnh+eZTuAkdbazRV4GCJe5+W/HffiqopnALuAcdbapIo7GhHv8fK58RHQ1bOPp621H1fUcYh427meG8aYP+GG1eXv0TPMWnvIGBMLvAsEA98Av1UtLKkOLrpkg4iIiIiIiIiUr4tuGIWIiIiIiIiIlC8lG0RERERERETEq5RsEBERERERERGvUrJBRERERERERLxKyQYRERERERER8SolG0RERERERETEq5RsEBERERERERGvUrJBRERERERERLxKyQYRERERERER8SolG0RERERERETEq5RsEBERERERERGvUrJBRERERERERLxKyQYRERERERER8SolG0RERERERETEq5RsEBERERERERGvUrJBRERERERERLxKyQYRERERERER8SolG0RERERERETEq5RsEBERERERERGvUrJBRERERERERLxKyQYRERERERER8SolG0RERERERETEq5Rs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+YslvDRlFRGhQfRp7s2EUSE8mPj/68fOg8lUjQglMMBrsEPzMc2lP0o2iIiIiIhIkalXyZv+skn1iByJBoCrO9VjxupEBrauSY2oMNb+bWBJhCh5aFm1HNe1rsr8bYepExlS0uHIaXB1p2j6x9TkUHIaUeWCiQoLzlpnZicUqywoJRtERERERKTIXNs1mi37jnJ+yxonrLuoTW0GxtYiIEAFIUuzp/rURYNbzm4Vw0OoGH56k0lKNoiIiIiISJEJDwniicExua5XoqH0MzP0rySFlXvlCBERERERERGRAlCyQURERERERESKlJINIiIiIiIiIlKklGwQERERERERkSKlZIOIiIiIiIiIFCklG0RERERERESkSCnZICIiIiIiIiJFqsiTDWb2hJltMbN438/AbOseMbM1ZrbSzPoX9blFREREREREpOQFnabjvuScez77AjNrBQwFYoDawBQza+acSz9NMYiIiIiIiIhICSjOYRSXAB8755Kdc+uBNUDnYjy/iIiIiIiIiBSD09Wz4S4zux6YDzzgnNsL1AHmZNtms2/ZCcxsBDDC9/KQma08TXHmR1UgsQTPL1KaqX2I+Ke2IeKf2oaIf2obcqaqn9uKAiUbzGwKUNPPqkeB14GnAed7fAG4CTA/2zt/x3fOvQW8VZDYipqZzXfOdSzpOERKI7UPEf/UNkT8U9sQ8U9tQ85GBUo2OOfOz892ZvY28I3v5WagXrbVdYGtBTm/iIiIiIiIiJRep2M2ilrZXl4GLPE9/xoYamahZtYQaArMLerzi4iIiIiIiEjJOh01G54zszi8IRIbgNsAnHNLzewTYBmQBtx5hsxEUSqGc4iUUmofIv6pbYj4p7Yh4p/ahpx1zDm/ZRNERERERERERAqkOKe+FBEREREREZEyQMkGERERERERESlSSjaIiIiISJExM3/TnYuUeWobUtYo2QCYWUczq17ScYiURmZ2vpl1KOk4REobM6uQ7bn+gBQ55nQUIBc5GwSXdAAixalMJxvMLMbMZgGPAxVLOh6R0sTM2pnZROALoElJxyNSWphZFzP7CviXmd1kZqFO1ZZFMLOuZjYOeMrMmppZYEnHJFIamFk3M/sUeN7MWqltSFlRppMNwL3AF865i51zq0DfTomYWaCZvQW8DbwJfAS09K0r6/9nSBlnZm2AV4EJwKfAeSgZJ4KZ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Hx/P/Pnzi/zcIiIiIiIiwHHDKNSzQU7uzJ/6cuIo2P570R6zZmu4cHSem9x333106NCBsWPHMnPmTF555RWcczz00ENMnDgRM+Oxxx7j6quv5uqrr+aGG25g4MCBANx4441cfPHFXHrppYwaNYpp06aRnJzMnXfeyW233ca0adN48sknqVWrFvHx8Sxbtizfoa9du5Yrr7yS3377DYDVq1czdOhQFixYUPD3Q0REREREyi6XATglG+SUnPnJhhISHBzMmDFjGDBgAJMmTSIkJITPPvuM+Ph4Fi1aRGJiIp06daJ3794MHTqU8ePHM3DgQFJSUpg6dSqvv/4677zzDhUqVGDevHkkJyfTo0cPLrjgAgDmzp3LkiVLaNiwYa4xmBkXXHABZsZtt93GiBEjaNy4MRUqVCA+Pp64uDjee+89brzxxmJ6V0RERERE5KyTmVjInmxIO1py8cgZ4cxPNpykB8LpNHHiRGrVqsWSJUvo168fM2fOZNiwYQQGBlKjRg3OOecc5s2bx4UXXsg999xDcnIy33//Pb1796ZcuXJMmjSJxYsXM2HCBAD279/P6tWrCQkJoXPnznkmGgB++eUXateuzc6dO+nXrx8tWrSgd+/e3HLLLbz33nu8+OKLjB8/nrlz5xbH2yEiIiIiImcjf8kG9WyQk1DNhgKKj49n8uTJzJkzh5deeolt27bhnPO7bVhYGH369OGHH35g/PjxDB06FADnHK+88grx8fHEx8ezfv36rJ4N5cuXP2kMtWvXBqB69epcdtllWUmFIUOGMHHiRL755hs6dOhAlSpViuKSRURERESkLMpMLGQViAzyZqcQyYOSDQXgnGPkyJGMHTuW6OhoHnzwQf70pz/Ru3dvxo8fT3p6Ort27WL69Ol07twZgKFDh/Lee+8xY8YM+vfvD0D//v15/fXXSU1NBWDVqlUcPnw4XzEcPnyYgwcPZj2fNGkSsbGxgJfc6N+/PyNHjmT48OFFffkiIiIiIlKWOF9iISDY96ieDXJySjYUwNtvv010dDT9+vUD4I477mDFihVUqVKFNm3a0LZtW8477zyee+45atasCcAFF1zA9OnTOf/88wkJCQHglltuoVWrVrRv357Y2Fhuu+020tLy12h37NhBz549adu2LZ07d2bQoEEMGDAga/21116bVdNBRERERESkwDK8L0cJ8PVs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0rlbtmzJww8/TL9+/RgwYABt27YlKOjEkTK9evVi+vTpTJ8+nUceeYSZM2cyb948OnXypq2ZNGkSo0ePJi4ujj59+pCUlERCQgLdunXjb3/7G88++ywbN248If7MfSdNmkS7du1o3749K1asYPXq1QDUr1+frl27Zm3bsGFD4uLiAOjQoQMbNmw44XizZ8/OGhLxhz/8gZkzZ57SewLQr18/qlSpQrly5bj88suzjpE9nrzifvnll2nbti1du3Zl06ZNWctTU1Pp27cvzz33HP369TvluERERERESq2M9GPPjx9GEZBLgUg4lmzIbTaK3Wvhr9Vh8SeFj1FKNSUbCiAmJoYFCxb4XZfb0IewsDD69OnDDz/8wPjx4xk6dKjf7czshGWvvvoqcXFxxMXFsXXr1jxju/nmm/ntt9+YPn06lStX9luvoVevXsyYMYO5c+cycOBA9u3bx7Rp0+jdu3fWNXz22WfEx8cTHx9PQkICLVu25JprruHrr7+mXLly9O/fnx9//NHv9T/yyCNZ+65Zs4abb74ZgPLly+fYNjT0WJerwMBA0tLSOJnM9ycoKIiMjIysc6akpJx0n+NfZ48nt7inTZvGlClTmD17NosWLaJdu3YkJSVlxdChQwd++OGHk8YtIiIiInJGyUg99jwk59/xBGSv2XD8MIpQwHLvvbBzmfe47KtChyilm5INBXDeeeeRnJzM22+/nbVs3rx5/Pzzz/Tu3Zvx48eTnp7Orl27mD59Op07dwZg6NChvPfee8yYMYP+/fufcNzevXszbtw4ACZOnMjevXsBuPPOO7NugmvXrp1nbDt37gQgISGBzz//nGHDhp2wTZcuXZg1axYBAQGEhYURFxfHm2++Sa9evQDo378/r7zySlbiZOHChQCsW7eORo0acc899zB48GAWL15MZGQkBw8ezDp2//79effddznkK7K5ZcuWrJgKonv37llFOMeNG0fPnj0Bb6aOzITPV199RWqq95/h8fEATJ48mT179nD06FG+/PJLevToccJ5cot7//79VKpUifDwcFasWMGcOXOy9jEz3n33XVasWMHo0aMLfI0iIiIiIqVOfodRBOasI5c1/aVmoyjzzo7ZKMLCTjqDxCkfLw9mxhdffMEf//hHRo8eTVhYGA0aNGDs2LH07t2b2bNn07ZtW8yM5557jpo1awJwwQUXcP311zN48GBCQkJOOO7jjz/OsGHDaN++Peeccw7R0dF+z799+3Y6duzIgQMHCAgIYOzYsSxbtoyoqCiGDBnC/7N33uFVlOkbvieFhCR0Qu8dBERBLBQRFEER7NgL9lXX7qprWfena1vL2ntFRVRUREEUGyKKdKT33ksIBFLn98dzxjknvZxU3vu6cn1zpp0vJycz8z3f+z7vzp07iY6O5oUXXsg1FSMmJobmzZv/lULQr18/PvzwQ7p16wbAfffdx80330z37t1xXZdWrVoxYcIEPvroI0aPHk10dDSNGjXi/vvvp27duvTp04euXbsydOhQnnjiCRYvXsyxxx4LyLhx9OjRREZGFv7zD+LZZ59l1KhRPPHEEyQmJvLWW28BcNVVVzFixAh69+7NoEGD/opS6N69O1FRURx++OFcdtll1KlTh759+3LxxRezYsUKLrjgAnr16pUjZWPw4MG59nvIkCG8/PLLdO/enY4dO4akgYAiMsaMGcNpp51GzZo1i1SVwzAMwzAMwzAqLMFiQ440iqBhZFS2yAZvnVWjOORx8gr7ryj06tXL9Qz9PBYvXkznzp3LqUeGUXjsu2oYhmEYhmFUaJYtg4SEnOv3rocxg7Xc70HofG7o9lcDz7inPglHXRm67clO0O5EGPF8zvMu/hI+ugg6DYPz3i9cH9MPwu8vQbdzoFazwh1jlAmO48xyXbdXbtssjcIwDMMwDMMwDMMIJSSyIT7v/bJ7NoAiG/KqRuFmBdoiTHp//3/w3b9gTiHFCaNCYGKDYRiGYRiGYRiGEUp+ng3BZK9GARBVPW+xISNvY/c82b1G7b6tRT/WKDcqrdhQ0dM/DMO+o4ZhGIZhGEalpbBiQ1QuYkNMAqTszn3/v4wji/CsnLJLbdKGwh9jlDuVUmyIjY1l586dNpgzKiyu67Jz505iCzAbNQzDMAzDMIwKSaHTKHIRGxp1gy3zIVCqPoS8SmLmxwETGyojlbIaRbNmzdiwYQPbt28v764YRp7ExsbSrJkZ2BiGYRiGYRiVkGCxISI67/1y82xo2hNmvgk//gcG3hu6Lb0YJTFTdqo1saFSUSnFhujoaFq3bl3e3TAMwzAMwzAMw6iaZKX7y3GJObdHx0P6/tzTKNqdpPbPcTnFBi+yIa8o9VU/QcOuUC0OoqtrvwO7JXikJsHBJIitVfTfxyhzKmUahWEYhmEYhmEYhlGKeJENp74FMTVzbo+trTa3NIoaDeHYGyB5c05RwfNsCBYzPLb8Ce8OhyfawGOtJCyk7lVfGnfXPkkbi/XrGGWPiQ2GYRiGYRiGYRhGKJ7YEJVLmgRATEBscCJz316zCaSnwME9oeu9yIb0XKpVzP0gaL+DsO433xyykSc2WCpFZcHEBsMwDMMwDMMwDCMUN1NtXn4NXmRDdjHBo2YTtXs3ha73SmJmL42ZmQ4LxoauWzHFL3fZ9Ei1Sevz77dRYTCxwTAMwzAMwzAMwwjFS3OIyMPm75g7oV5XaHFM7ttrNlW7d3Po+vQ8xIYV38H+oAIAjXvA7Hdhw0y9bnKkhA+LbKg0mNhgGIZhGIZhGIZhhOKlUeQlNtTtACe/BTE1ct9eo7Havdk8FjyRIXtVij8/hbj6UL2OXp/5mqIrfnxEr2s3V7SEiQ2VhhKJDY7jrHEcZ4HjOHMdx5kZWFfXcZxvHcdZHmjrBO1/t+M4KxzHWeo4zskl7bxhGIZhGIZhGIZRChQkNhREjUaAk3caRXpK6Prda6BRN7hyioSGxA7Q+2pI2wfVEmRSWau5iQ2ViHBENpzgum4P13V7BV7fBUxxXbc9MCXwGsdxugDnAYcBQ4AXHScvNxHDMAzDMAzDMAyj3PhLbMjDs6EgIqMhoWHOyIY969Qmb/aXQSkU8YlQry10P1fr+t2mMpc1m4LjQK1mJjZUIkojjWIE8E5g+R3g9KD1Y1zXTXVddzWwAuhdCu9vGIZhGIZhGIZhlISSRjYA1GwsUcEjZRdsWQAxtfT6mW6wbbGW9++A+Pqhx8fVhbPfgpMe1OtazSReZGUWv09GmVFSscEFJjuOM8txnKsD6xq6rrsZINA2CKxvCgRbh24IrMuB4zhXO44z03Gcmdu3b89tF8MwDMMwDMMwDKO08MSGkgSj12wamkaxbjrgQtcz/XWfXwepyUqXyC42ALQbBB2HarlOK/k47F5T/D4ZZUZJxYY+ruseCQwFrnccp38++zq5rHNz29F13Vdd1+3lum6vxMTEEnbRMAzDMAzDMAzDKBJhiWxo4qdRzHwLfnwUoqpDhyH+PpvmwCPNtBxfwNivUVe1WxYUv09GmVEiscF13U2BdhvwGUqL2Oo4TmOAQLstsPsGoHnQ4c2AbG4hhmEYhmEYhmEYRrlTUs8GkNhwMAkO7oUJN8OW+dC8N9QKBLjH1YPWQfPVBYkNiZ0VabFlfvH7ZJQZxRYbHMeJdxynhrcMDAb+BMYDlwZ2uxT4IrA8HjjPcZwYx3FaA+2BGcV9/0rPpjlwYHd598IwDMMwDMMwDCMn4YhsqNFE7Zqp/rpW/VTiEqDJETD08aD9G+V/vuhYCRVmElkpKME3h4bAZ47jeOf5wHXdSY7j/AGMdRznCmAdcA6A67oLHccZCywCMoDrXdc9NJ09XBdeHQCNusO1Uwvc3TAMwzAMwzAMo0zJSldb0jQKgGWT/HVNj5Rx5HkfQMs+ofvXbVPwOWNrKVICYM962LcNmvUsfh+NUqPY3xzXdVcBh+eyficwKI9jHgYeLu57Vhm8mrIW/mMYhmEYhmEYRkUkKwNwIKIkBpEBsWHDLLWnvwRtB2q506lq3SAbv9haBZ8zppZSM1KT4Z3TwM2Cm21cVREpjdKXRkF4SpxhGIZhGIZhGEZFJOOAzBxLgic27FqptvtIcLLVDcj+uiBia8L+bapisXs1HNxTsj4apUZJ0iiM4pKaXN49MAzDMAzDMAzDyJvUvRBTs2TniK4O1evIqy4yJu8oieHPFf69YmvBzhX6iaunKAejQmKRDWVNZjq8d0Z598IwDMMwDMMwDCN3MtNg3yalLJSUmoHKE9H5REkceQkcdnrhzueJEvXaw9HXKt0jI61kfTRKBRMbypody2CvuacahmEYhmEYhlFBmXgNbPq95JEN4KdSRMeV/FwAkYFSnN1H+v1L2xeecxthxcSGsmbP+tDXmenl0w/DMAzDMAzDMIzc2PSb2nAIBDUaq60WJrHBS0mPqwPV4rWctj885zbCiokNZc3u1aGv104rn34YhmEYhmEYhmHkR/qBkp+jRiO1kTElPxfISwKU4uEJGCY2VEhMbChrdq3yl50I+P0VWPgZZGWWX58MwzAMwzAMwzCykx6G9ITqddVmppb8XAB9boKazaDdIKiWoHUmNlRITGwoa3YFRTYc8zdY+jV8fJlawzAMwzAMwzCMikJ6GAbxcQGxISNMYkOTI+DWhTqvl0YRjn4aYcfEhrJm1ypFNHQaBr2v9tebd4NhGIZhGIZhGBWB2Dpq+z9U8nN5kQ0ZB0t+ruyYZ0OFxsSGsiQrE/asU+jPee9DnZZyUQXf6MQwDMMwDMMwDKM8yUiFbpdC414lP1f1gHCRXhpig6VRVGRMbChLkjZAVjrUbeOvO/UptduXwOZ55dMvwzAMwzAMwzAMgKwMyEiBajXCc764gNhQGpENXrUMK31ZITGxoSzxzCHrtPbXVYuHiCj47UV4pT9kZZVP3wzDMAzDMAzDMLyBuxc1UFK8yIasUkgbj62l9sCe8J/bKDEmNpQlXtnL4MgGx5F66LFjGSz7Bj67rnRCjQzDMAzDMAzDMPLiL7EhTJENMQFBoGG38Jwv5NwJEB0P+7aF/9xGiYkq7w4cUuxapfqyNRrnvc/63+DLm7R89NVyWzUMwzAMwzAMwygL0gJecuGKbIiIgCu+hbptw3O+7CQ0gH1bSufcRokwsaEs2bUa6rTSP1xuVKsBSyf5r/fvLJNuGYZhGIZhGIZhAEFiQ5giGwCa9w7fubJTo5FFNlRQLI2iLNm1OjSFIjut+8Gyif7rFBMbDMMwDMMwDMMoQ9IDlR2iwxTZUNokNIBki2yoiJjYUFa4rjwb6rbOue3G2XDd9JyKX8qOsumbYRiGYRiGYRgGhD+NorRJaAQ7l8P756hkp1FhsDSKsmLfVkhPyT2yoV4gf+lgUuh6i2wwDMMwDMMwDKMsKY00itKkZhO1yyfDqp+gw+Dy7Y/xFxbZUFbsXqO2Ti6RDR6eGWS7kyA+0cQGgLT9MPUpyEgr754YhmEYhmEYRtUn3NUoSpvaLfzlJV+WXz+MHJjYUFbs3ai2VtO894mOhZvmw8jREFcf9m4um75VZBaNhykPwoYZ5d0TwzAMwzAMw6j6pCVDRDRExZR3TwpH7Zb+8tKJkJVZfn0xQjCxoazwTEtqNMp/vzotJTq06gOrfoSUXaXetQrNhj/UHtxbvv0wDMMwDMMwjEOBtH2VJ6oBQiMb9m+H9TZJWVEwsaGs2LsJoqpDbO3C7d/zcshMhXkflm6/Kjqe2JBqYoNhGIZhGIZhlDpp+yqPOSRAfH1wIqDfbRBZDZZMKO8eGQFMbCgrkrcoqsFxCrd/o67Q7CiY+ZYqWRyKpO2HrQu1bJENhmEYhmEYhlH6pCVXLrHBceCB3TDofmjUzR8/FIU1v0BmRvj7dohTYrHBcZxIx3HmOI4zIfC6ruM43zqOszzQ1gna927HcVY4jrPUcZyTS/relYrkzVCjcdGO6Xm5yris/710+lTR2TQH3EDOVWpS/vsahmEYhmEYhlFyKlsaRTDV68LBPUU7ZutCePtU+OO10unTIUw4IhtuAhYHvb4LmOK6bntgSuA1juN0Ac4DDgOGAC86jhMZhvev+LgubF+ae9nL/Gg7UG1x1LmqgJdC4URAanL59sUwDMMwDMMwDgUqW2RDMNVrw4FsYkNWFuxaDclbYdw1sGdd6PZtgaHsoRxRXkqUSGxwHKcZcCrwetDqEcA7geV3gNOD1o9xXTfVdd3VwAqgd0nev9Kwbyuk7FBYT1FIaABOpPweDkU2zJRAE1fP0igMwzAMwzAMoyyozJENsbVzRjbMHwPP9oCX+2p57fTQ7btWqd2xFNZl22aUiJJGNjwD3AlkBa1r6LruZoBA2yCwvimwPmi/DYF1OXAc52rHcWY6jjNz+/btJexiBWDLn2obdS3acRGR8nk4VMWGTXOhyZEQU8MMIg3DMAzDMAyjLEivxGKDF9mQFTQ83btR7f5tatOyRUzvWA7xDSCmlqIbjLBRbLHBcZxhwDbXdWcV9pBc1uUap+K67quu6/ZyXbdXYmJicbtYtiybnLcosHO52sRORT9vQkOY9wHsWV/wvlWNtGRFNcTUtMgGwzAMwzAMwyht3CxFNkTHl3dPikdsbcDNfaJyyKNqU/epPZgEPz4Gi76AtidA1zNh8ZehQoVRIkoS2dAHGO44zhpgDDDQcZzRwFbHcRoDBNqAhMQGoHnQ8c2AqjFln5UJH5wDbwzOffuuVVIH4+oV/dxpgX+Gnx4tfv8qK5kZEBkNsbV0MTAMwzAMwzAMo/RITwHcyh3ZAKGpFAeTIKo6HH0t4Pjjq4n/gB//A5mp2taoK2QcgH1byrzbVZViiw2u697tum4z13VbIePH713XvQgYD1wa2O1S4IvA8njgPMdxYhzHaQ20B2YUu+cVhQN79EUFSMoj+mDXaqjbuvBlL4M59Sm1634rXv8qM5lpEhvi68vzwjAMwzAMwzCM0sNLMai0BpGBQogHsokN1WtrLFYtQZENa36BeR/6+9RsCrVbaTm7gaRRbMJRjSI7jwInOY6zHDgp8BrXdRcCY4FFwCTgetf16hpWUrb8Ca/0L7hMyq5VEhuKQ+t+cMI/YecKSD9QvHNURlwXstIhIlo5VPuqgHeHYRiGYRiGYVRk/hIbKmlkQ3wgBX/fVn/dwSRFSgPEJMjDYcItgZQL77j6ULuFlnevLZu+HgKERWxwXfdH13WHBZZ3uq47yHXd9oF2V9B+D7uu29Z13Y6u604Mx3uXK9//X8ElGTMzpI4VtexlMLUC2SeHklFkVobayGqqypGWDGkp5dsnwzAMwzAMw6jKpO1XW1kjG+oEJnh3rfbXBYsN1RJg8XjYuRLOfNXfJyISagfGXBbZEDZKI7Lh0CF5MzTrFboue23WvRs0Q1+nmJENALWaqU1aDyumwNpfi3+uisqOFfB8b7/0TGa62shoiQ3gO8gahmEYhmEYhhF+MgKR1FHVy7cfxSW+vqIydgeJDQf2hEY2ACR2hA4nhx4bXV3m/HtKENlwYLdFRgRhYkNJ2L8T4uqHrtu3VYaRHp6qVqLIhkCF0KQNMPpMeGto1UorSN4Cz/dUbdtfntG6zDS1kYE0Cqhav7NhGIZhGIZhVDSyvGfwauXbj+LiOEpf37nSX3cwyU+Z8CI26rXL/fjaLXKKDat+gqlPwfZlMOZCP/ojNz67Dv7XXeM2w8SGYuO6Mi2Mrw8D7/PXT/k/eLy1vBweaQEb/tD6kogNNZuCEwG/veSv2zSn+OeraMx621/eMl/tX5EN1aBGQy0nWUiTYRiGYVQqdq6E7x/2S8kt+AQm35f/MYZhlB8ZntgQU779KAkND4NNs/3rTsou3zgyIkpt/fZqb5wNtyz0j63dElb/DE+0g33bdOy7w2HKg/DCUbBkAqz8Pu/33hY418y3wvs7lSVLJ8H4v4flVCY2FJe0fZBxUGJD/9vhzkAEw9zRUs82z4PUJPj5CWh/MtRsUvz3ioqBYc8obcMj2PSkshMdFKa1fZlvDgmKbGjQRWrk8u/Kp3+GYRiGYRSPsZfAz4/7aZKfXgG/Plu+fTIMI28qe2QDQNtBkLITNs+VwX5qkj956fnCtR2ktl5bP2UdfJPI/dth2SSN6UCm9R7bFuf93l76ybJvSstnHyoAACAASURBVP57lBcfjoTZ78DP/81pEVBETGwoLvsDpRi9NIq4uopeaHw49LjI3y+2Nox4oXhlL4PpeSncNB8uD/hqViX/goNJitw45b+Qvl9GmF4aRUS0BIc2x8O6gFdFViZsnA0H95Zfnw3DMAzDKJg9gbLgO5eHrs8vDNkwjPIjswqIDU16qN2xXOnaAAmN1J76FFz4CbTqk/ux7Qf7y2um+WLDtb9AjcDk8Q8Pw8eX5zx22xKlhQNsXVB5UylqBQSX7//PjzovJiY2FJeUnWrjgzwbRk2GUd9An5sUohNXH855CxISw/OeMQnQ8jiIqamwnqrCgT0KbUrsqNc7lqmKB/gXuhpN5JEBsPAzeO0EGH9j2ffVMAzDMIyCSd0Hn12rGUWA7UtDPa28AYBhGBWLzFS1lVlsiKmpNi3Zjwb3IhsSO0D7k/I+tuWxmuBtP1hjjqlPyui/QSe4cRZcM1X7LRznj1c8XjxabfeRapdNCs/vU9bUDSpsMPfDEp3q0BUb1k73fQGKg1eGskYjf11ColICEjvAPZvgzpXQqm/J+pkbCQ2qVhrFwYBDbP0Oer1jeZCqGsiriqurC0ZmOmxZoHVV6TMwDMMwjKqC68L4G2D+R9Ay8By0copq23vYPdwwKiZVIbLBqziRui9nZENhqNMSOg2T8JK6F078l9ZXi4PG3eGsN/R62yL/GC/qHZSWUad12adSTH8Bnuzse1UUl8w0aH40dB4OC8b6Ph7F4NAUGzbOhreGyOCjuHhhMbWa5749qhRNVeIbVK3KDJ5DbEJDiKml8KOsIINI8E1dDuyGnSu0nLqv7PtqGIZhGEb+zHhVM4KDHoDLv4Ihj8pw7cfH/H0sssEwKiZVQWyIjlOKdmpwZEMRxAaANgP85XaDQrc166XWKwQAsPhLtTWbwpGXQMehqmJRlilj39wDyZtg48ySnSc1WRH6PS5UNP+Kb4t9qkNTbFge+MCKOmDfNAcm3SNX0mWTIDreHwSXJdVra4BeVfBq3zqOnGF3LAutRgGhYsOO5f6yYRiGYRjlw6/PwU+Ph67LyoJpz0KrfkorBTjqSkUvzh3t7+flQRuGUbH4yzetEosNjgPVasjQP3mL0tur1y3aOeq09JdjaoRuq90S4hNhQ9Cgfs57kNhZlS3qtIIOJysyYtVPxf41ioxXQWTJVyU7T+pe/c7tBun3XPBJsU91aIoNK6eoLeqAffZ78NsLKm25+ieZGZbU+LE4RMdBekrZv29pcTBJAgroYWTHcl9siAhKowDYs843mTq4p2z7WRlI2w+z3w2tLWwYhmEY4eTAHnkwfPcg/Pp8aMjulvmwdwP0uMB/RoqMhqGPgxMpB/hOw3SvSj9QPv03DCNvMtPAiYKIyPLuScmISfAjG+IbQEQxhr3nvANnvJpzveNAs6P8yIati2DjLEU0eNe9FsdBtQR/3Fna7N/h+20s/bpk5zq4F2JrBkz6T4A1vxS7KsWhJzYc2O1/MVKLKDbsXg01m/k5P3Va579/aRFdvercoLMydRHw1Mb67VXi0zPg/CuyIbB97gfgZmnGJG1fyXw3qhprfoFnuss4c+qT5d0bwzAMo6ryWEt4obdSHlOTFJHo4UUfNjky9Ji2J8A/1sAFY+GY6+DALnk6GIZRschMhahKHNXgEVNDYkPyFt8csqgcdjocPjL3bc16aQI0ZZeiGiKifWNI0GfYqBv88Tqsn6EU/PxKZpaU5M1qW/XTNfmJ9upbUXFdfW5eNEfL41QF0StfXEQOPbFh1Y8arELRIxt2rYbmveHWxQqRuWJy2LtXKKpSZMP6GQrV8Yw0PZNIz3AlMlDT1otsWDhOgo9XluaARTf8xay3wc2EBofp8/vmn/BkJ3i6GyydVOI6uYZhGMYhysG9vut6cBRDuxPVbv3TX7fX87RqmvM8sTVl/Nyyjx7Cv7wJ5ozOuZ9hGOVHZlrlTqHwqBYU2VAUc8jC0uwotet/V5pBx6EQXy90n7pt1b5xErzUB94aqgnjPevC3x+vUmHPy9Tu3waz3ir6edIPaDzhiQ1NjlBbzBKYh57YsGKKTAjrtg0VG5ILcEXOzICk9SoFEhEhl9GEBqXb17yIrl51xIalX0sJ9IxXPLHBy4HyxIZazWU0dcarcP1vUDNQ57Yi+zZkpJZtOsPuNdCwK7Q5Xv4i05+XSU3SOvhwJHx4Hky+F148TsKZRYUYhmEYBbF+BjzaHMZcoNfBKYyn/Fftvm0StH97Gb77lzytsuc4B+M4cMK9Wp4/Vu28j+D7h8PefcMwikhmWuU2h/Tw0iiSNxc/siE/mvZUatiH52lgn91EEiC+fujrA7vho4vgmW4w7mpd/0paOcJjf8CLsMkRcNyNWv7hP7D216KdZ8dStbFBKe44sG1Jsbp16IkNm+cpOiGurpR60M3xqU7Kt8mLTbMhKwMadCmbfuZHdJwuBNlru1ZGlk5UVENsLb2u104z88sDpWK8i53jQL9bFcoUU8N3lF0ztez7XFhe6A3PHQl7N+u79sIxcuPOi/SDhTeRWTsd3jgZvrxZogZIbKjbWjNGHiNeAAK5Y8smqSTOjqXwbA94/cTQmueGYRiGkZ3fXlLrpaB6zu5nvykTtIgoPeT+/gpM+oe2pRfCfb3jEJVV27tJz2ZfXA+/PK17oWEY5Udmmm80WJmJqaGqDCk7FUkVbqrFw/Bn/dfNj8m5T//bJcqe+CD8bbo+1xXfQVSs0sjGXQWLPgtPf7zIhvhEGPwQ3LUOareAz64pWvr9lH9DXD3ofJpeV4vTtX578VJADj2xIWWX/gixtRTZsGs1THlQqRWLPs/9mIxUWPSFSqi0HVi2/c2NanFqM4rh2+C6MO1/eigId+nIWe/AawMLL4LsWK5cp46n+OsiIuCs14JeR+d+bIvjlJP07QOwZ33x+1wabF2of+rda/R610pY95v+SccHlMZdq2H/Tn0fXVemLv9tD+8Ol7v3pLvzz7MadzWs/03hUX9+qr/l/u26GHQ6Fe7ZDPfvhgad4JIv4Ph/wMWfwfV/wNBA6bHNc3WsYRiGYWQnbb/uT2un6fXBJN3fvQfahIaaCIhP1DPSN/dAx1O1zQsvLoi6rfUc8Ep/PYdlpRc7VNcwjDBRVSIbareEqOpw6pPQ64rSeY8jLoIbZsKAeyCxY87tMTWg91XQ92ZFZXspCZ2GwaUTtPzJKFj2Tcn7sn+bRAwvqiy2Fgx7Rikb057Nuf+Sr2H6i6Hrti+Dld/LVyc4KqNeW39cU0SiinVUZebALkU1ZKZKWRp9pkJg6rXXrPMJ9+Q85s2TFZbeuIfvHVCeRFdXm34g/zDF3Fj0OXx7v5Z/fAQun6QBaWH59gH5Xpz7bmhJGIAv/672xaPVt+um5V8adNkktR2HhK5veJi/HJmH2BARASOeV0rA13fABWMK1/+ti2DfltIRjRZ/CYvGw4Kx0CHod9qzXqYwoH/UL29WjmpWhh7U2g7UdzA1EGkzORBaWqORXzYsmD3r/JklkHBUr72W63cIlPuJ87e3OV4/HvXbwZGXwRsnyteh/UnlU8LVMAzDqJhkpMKzR0jA3rdVUYc7V+hhNlhsAD2QblmgkNszX5FoEFHIx8v4RH/5jFdg3JVKo2zeW+sO7Na5ivqsYxhG8clMrRpiw8D74Pg7/ejp0qJ+exjwj8Lt23GIPB4OPx9a99Pz94Hd8McbKpVZErYtVop/cKXENsdDl9Phl6egx/mKdPAYc77aY65T+9FFsGSCoi96Xh567vgGlkZRKNIPyuugeh3l2YCcNfverFD+7UtymuhlZUlogJyD6/IiOjCQTE/R4LYoM/ub5+vGPWqyHgh+eKjwxy6dBNOe0Yz4G4OlfnmkH9CAGRQBsnej70idF1v+lKdA8Bc/O/ld7Oq0kqK4+ic/32n/Thkl5hVdMeXfMOYizdiEm48uktAAvpAC+l7N+0DLCQ0VjRBbE3D1N1g9VWkjcfXh9Jfh3PegUXdYPCH0/FlZ8PMT8MLR+lyu+E6hWZvnwu+BMNfs7t95ERkltTNlh/JrDcMwqhJp+81AuLhsmiMhet9WPRSDbwS5dxOs+VnPETUaa50nGDTuLlEgtpbCiwvDkZfC8Ofgvp3Q/RwZQM/9QH+/rYvg8bZ63ghXTrNhGAVTVSIbomNLX2goKn1uhvt2QPvANfWaqYpyWD5ZE5Gz383b0H3Hcn/yMjtbF2oS/YiLc2476d+QcRAWBkXwB6dVbF8qD4klgXFHp1Nyek3E11cEdTHM5g8tseFAICw9ri4ce72/vuNQhb4c2K0/ZMouPw9+5wp/v/IqdZkdL7LhwB4YezE807Xwx+7bqgFvi6Oh3UmajSgsP/5HxpqjJutB4OvbYOI/YP0f8HI/OZde+qXyOEEPJcHsWA4fXy5BAGDPWgkG+ZFXZINHg04SXXatgp+egCfayN06r5q22xYpl3TpxAJ/3SIR/Lue/J/QXLdpzyht4rKv4arvZXJ57TQ9ZN04G25eAH1vhcu+kurYZbhMZjbNCc1dXTgOvn9IkRDXTYPmR6mOeUwtpUPUaOwbZxaGJj2g1yhd2EpDfDEMwygvXj1B5RlXT1VamlXjKRzJW+HVAfDHa5pAGPk+dD0LDjtT29f9JjHgyEtlvga+2OAZPBeF2JqqSx8ZiISo1wa2LoCfHpOQ7mbqvr1wHLx3pkwkDcMoXbKqiNhQEXEc/3oHULs5nPkqNOgs35rxN+acrM3M0D3s+V7w4rGwbLI/TvXwPOF6XJDzPeu01HXaM34EiRMer52gIggefW/NeY74REW8pBU9Bf/QSqPwcuCrB1Ihzh+jm2aDLjIPAXghkGdYozHcNE958QAn/NN39ixvvMiG4C/jvu2QkJhz36wspRx4JG/2zRVrNobFm/UFDg65yY20/YpE6HerhIrW/VRJYvXP8PvL2qdZb/koeJ+zV+/VY94YPTCk7oULPlZKQUHpDAWJDfUD+VHPByJVWhwL66ZroJ49HCltvwQOUImabmfnf+6iMOsdtaMm6/PZvwNmvCblcuFn0O82aBUwbvTq9Qabypz4QOj5mvbyc1eb99bf6NdnlS5x7nv+37RavMJWF3yskKeC/o7ZaXGs6v/uXhOavmIYhlGZ8R6qPr5U9/ejr6040YkVGS9KDhSl0HmYfrKyoPnR8N0DSgEMvncf93eF73Y5veTvf/xdeq5Y95tyrUHRkp9dq3viyinQYbCl/hlGaZKRZqlLZUm1eBg5WqbyoAiF6OoykOwyQuUyvb9H6l744Bw4+joY+qh/jrW/Qq0WqpaYG/U7hkak71rlL7c8Do66SpOxh52Z+1jCE5X3by/yd+PQEhuCIxtAEQ0dh2q5ZV/9oZO3aOA1/XkZZKz7XTe1freHDtrLk7/EhiCFatcqiQ2ZGfDpFRroJ29RbuVN8/ySL8lboG4bLddsKpXqwO6CvSg2BWYYPNOnLiOUKlCrORx2usKCvHPE1ZUiml1sWDNV5bBWfKcoieTNMm/JjdbHKz0iL4NIj+ABcrUaMGqSqj54qS/bFssQc+X3vvhQv4MeWDLTCxYzCsOu1XLQ7nqWhAZQntix1+sC0u1caD+4aOdsHjjPGydJ6Gp+tNy6T/tfzu9h8Pe4qNRt7f8OJjYYhlEVCE6f8Mojr59hYkNBpCbDH29qsiV5c+jMZkQEjHgRXu4jsaHx4f62Rl3h2jBVhmrVR2HAS77Sw23NpsqHXvUj9L4GZrwiM0qvjrxhGOEnYz8kNCrvXhxa1Gur9IrXBsI3dyvtO+MAzHhV97RazUP3//0lCcGt+ur15rnQrGfe50/sCDPfgFeOh1HfBMQGB/65RekmBfGX2LDDH0cWkgoyei4jkjaqjauXc1tEhEp89L4KBj0go6OFnytfsfnRFUdoAF9sCC7V6SlUO5fLBDItRUJDxgHf2XnlDwpHTGig116+5aY5BYeYbgu8V8NAykb3kcqxvHm+coGCxQrHUfTE3iCx4cBumT4dcy10GCrvAYDW/XN/v/Peh8sn+mGaeVG9tqIkQDlmIKfXZZPgvTPgxWNg3odKH5n9rpS7frdr3x3L8j4v6PNL3pr/PiBDx4golZnxiIhQflN0deU+RRZR10tI1PcQ4IeHVaUivgF0P69o5ykILzVo2cT8q18YhmGUFVlZ8MUNumcUh6Vf+8vDnoaYmrrGGfkz+11ITYIzAtGKA/8Zur1+O7m6tx+c9+xZOGg/WJNDSyZoQmLQ/fIzGvqYxIfVQcLGnvXwUh/4+k7NxhqGUXLS9mty0ChbIqPhlCdkyNvhZEV17dsqb7urf1CUNCjFrU5rGP93jd8O7JZ5fKPueZ/bi0bbPBfW/KJxY82mhRMaQNHwEJp+UUgq0Ai6iGyao/DvouRhLhwHNZpAYgHVF6KqSS2aP0aDd2+WuaLgmZ0s/cpft2ulWi+1YvizcMufgXWBQfWCT9R6A3wvv3/0mTDh5vzfc8cyRQ54xzhO/gJMrRb64nusmKLIiA5D4eirte7w86HlsbkfH1NDYT2Fwaud64VVNumhduX3MPBehWV2GKoat6c84c/IeA+yrgtZmdl+3xUSKt4dHvody8pUlEfw62XfKOe0KH4JhaHfrfDAHuh/h3wZhjxS+ItCYYmrK9+MOaNhwi3hPbdhGEZxSNkBc95Tjn7I+l0Fu2Gn7tOMUJMj4f5dmgHveZnS2fIy1ioJqcnFeviqcCRtgJ8eh5Z9oM0A+FdS7tEDR1wEF35c9JS9otBluEzTjvmbXNKb9pSfkeMoymH3an/f316ErX8q4uHdETnzmA3DKDrp+6FaAZN9RunQ4hi4cRac+44fzeCNL2o2VRtbU8LDrpW6/3j+e43zERvanSgjeoC572u8WK8IEQoNuuhnzuii/T5URrHBdWWS99pA+Oq2UEOLgo5bMw06nQoRkQXvf9SV/nJFExvqtYXzP9JM+llvSMmaN0bRDDsDYkO9dorgiK3tCxB71spX4bAz9LpRN/9iMuttDc6zs3URjL1EVS/qty/8A0bdVqH5QMsm6Uve9Ehoc4KMJIc/V5zfPic1GinV4KKAmOLVsO1/pwbqJ9yt0ph9b1b/67XTbMlXt6ls5vO9YEzAUGXLn/DZdXpoObBHlSTWTfff6+s74NXj/Xq4yZuVR5pYDGOswuA4EkzuWhtej4lgLvlC7YopZqJmGEb543koZWYbOL47QqWVDyblfewvT+u6PPQx/17fa5Qq/8wrZInkojD2UnjpON1/KzMrpsDBPTD08fLuiWjcXQJ7l+Gh6+u0Vtrf0knwzmmKfmjcQxEX6371TdKqOtuWKC3WMMKN60psiI4reF+jdPHSFbxIdC8y/7Az/ApBq370xYb8IhuiY+HOlRoXLRyn1OwGRUifdhxVzdg0W6J+Eah8YsOWBQrB9z6gvZvz398jaYP+eRp2Kdz+TY6Aiz7VjbdFHrPv5YXjqE7rcTdqAHriAxJdVk5R+ZIajaV6OY7SHjbN1nF71oaWmYyurkoI102Xj8Hn1/uzAhmpeqBbMBYWf6mUgNwcTvOiTmvV407dp/Iqy79VaGREpPrVun94/BK8z+P4O/0Ih+a9VRZywN257x8ZBVf9IOFpxquqOLJsEmycLQFi3gewdwNcNkHht59cAcu/gz/HKd8J4NOr4Lme8MN/9Dov74lwUZqzSHVa6UEtLVkXH8MwjPJk/3a1wbPUB5P8lMD5Y3Mek7JLYsKvzynNr3lvf1vd1rqn5yaol4QNM/3KR1v/DO+5y5qkDQrZTexY3j3Jn7qtlWLx4UgJC3vW6fmlx0UQFQvvny3BqSqTtAFeHwTPH2XVOYzwsncDzH9T4my0RTaUO14kdlSgwl3HoTD8eRjymNIaqtXQ9WDzfHlseGny+dH3VkVMuJlF92prdpS+G54vXiEpttjgOE6s4zgzHMeZ5zjOQsdxHgysr+s4zreO4ywPtHWCjrnbcZwVjuMsdRzn5LzPng+LxytXZcgjer13Y+GO2x4wUywohSKYdifC0ddULL+G3Gh9vCIYlk6EDX8o5NCj7QANIL++U54V2Q2y4upKgBn0ACRvkgM0qPTK/3ro4axee5Va7H1V4fvkGQ/+73CVaTm4B464sES/ZpFoflT+f7f4esrl/et1Irw5xK8+cuK/lMZx9LWwbwu8fxZ8crlqgJ/6ZMCHIUahSFBwCc+KTufhKok69hKZvxiGYZQXf12DXPjxMS1unO1v//p2eOPk0NmVyffCZ9coGuLEf+U8Z/NjYOMs5fVvnu+nFRaXP8dpwOfxyRUa5G5bXLLzhhPXlTFmZnrB+yZt0MNquCYBSovW/TWZMeAev8R0/Q6atTvyEr3+7l/l1r1SJy0Fnj5M5eeiq8NnV1eNNB6j/Nm7DsZfCL//V6+rmWdDueOZv3tR6FExcOTFSvcHjWVSdmgiPr8UimCqxcGQR+U1FyzKF4ZmvdRu+KNIh5VkFJ0KDHRd93CgBzDEcZxjgLuAKa7rtgemBF7jOE4X4DzgMGAI8KLjOIXIZwiQlamIhp+f0ODYU2OyVzzIzu41KgeyPZDnWRSxobIQGa2ogcVfKnXBqxgB0OUM+VTMG6Noh5Z9cj9HmwGq/LBwnIxGFn6m2YPN84pnBNV6gAawTXrob9X31rzfu7yIq6vZkIH3Kbqjw2ClelwyHvoG/AsG/hPuXAUXjdPPddOUYnPGy3DNz9D1bPkpZHeJrWwkNIDzAkaaX9xQdu+7Z50fEv3r8/Dfjipxtntt2fXBMIyKRbDgOTXw4Ls54JUz6H61639T+CgoAmLeh1oe9nTu/jltjoeMg4pIe6WfqjalHyxe/7YtgU9G6V573I0SoVN2aJD79R3FO2e42btZpb3fOEmi/0t9VV0rz/03lK7pY7hocgTcNBcG/MOPVO0yQu2Qx3RPBxk8V0WChQUv5aUgs2vDKIjMdPj+ekgJ+r+xyIbyp8UxSps/4Z7ct8fVh72bNMb1orsLQ+dhcPeGokeyxdWFum2LbN5cbLHBFd60QnTgxwVGAO8E1r8DeIWXRwBjXNdNdV13NbACKFhS8RT5af+TVwMo5756HYXM7d2kdan74OW+GnB7HNyrm+xbQzV4jk8suMRjZaXjUNVehVClqn47uG0x3L0O/rEG2g3K9XBiEqDnpfJu+OUZVWvod7u25VUxIj/i68HI95SKct77SvUozVSA4nL6C9D/dlV/GDla+Uxtjg/dp3odfW7tBqn6hUdkFJz9BtyxwlcZKzPNekpIWTml9E22XFczkf87XG66+3coJWXfFg0a3h6mfFTXVZjo2uk5TTyLQmqycqu3F+OhbM00eGd45c/JNoyKjuvKJXvDDH9dvXZqN85WGmDvq/1tq37QMWMvUWjniQ/KnyE3OgyBc9/1HbUhNG0sNbnw/dw4C3BVIWHwQ3DrQvjnZuhxoe+RFMzHl8FTh+maF86qP64bGt2xfgbMekfrX+4DX/xNVYyyMmHrAvj+//I+1551UKtp+PpWFpzzjlImPc+kiAgZSQLMfqdqehB5oluHof6zSmkYnxqHFik7IWULtBzor7NqFBWDjkM0WZwb8fVh7TSlROTn15Ab0dWL159mRymyYe2v8Ew3idoFUKL8AMdxIh3HmQtsA751Xfd3oKHrupsBAq2XQNIUCHZz3BBYl9t5r3YcZ6bjODP379igBwIvNx4kQHhGf7Pe0YV2wccKI/noIv8GE6z2bpxZNaMaPNoNUmRCRJRvkFhUvBDEac/osxp4L9yxEo69Pnz9rIpUBaHBo8UxEpqm/a90B9fzP1JudXwDWD4ZfnxUZVqv+xXOfF03vtFny/zrs6vhrSHwZEfVV1/+nR6q0/YX7r0yUmXgtuhzlREtDEkbYPoL8NMT8PYpsPqn0HJ6hmGEn42z4O1TdT/3hIWkDRosr5kKLfuqUtFtS6FpL1jxHYy5UJ47R12V/73KcTQDfvkkOONVrVv3q9rN8+GRZjD1qbyPTz+gSYtfnoHtiyGyWs70uXptJZgGCwBpKbDkK73/9Bdh9FnhGwTPfBMeaar+r/lFEytf/l3v55lsnvO2nM0PP18Ph7kZe21fpijQxj3C06+yok5LpUwG0/wY6HiKJqcKqrJVWdi+DJ7srGfcNVOV7nL+h0qf9XK2DaMkeGJrjaDoJkujqPjEBk2ABqfQlybNesmT75enJVJ/9y+Vq86HEokNrutmuq7bA2gG9HYcp2s+u+c2rZ3rHdd13Vdd1+3lum6veOegBIS4ehqIgO8dcMTFMrV7+rDQm8qqH9R6Tr0DAuEnFd34qCTE1oL2J8nMsrhqVWJnf/nw8/RwFF+/4udwGuGjxXEyxfzhYQ3yx1yoaJdgXBeSt+Z+/IHdmsXzQomDH6rX/a7Zyb2bYeKdeigc/hykp8Afr0H385Ry0/0cGPmuHug/ukhpKiNe1LnHXiL/jDdOUkWawtRVX/q1X4Z10xyZgD57hITK9TMkKHwwUp4moLKnz/WEb+6BHx7yz5P9czCMqkJGGiyeAF/driii8sKLNLjwE7hyinLzU/fKG+fAbmh7grbXaKTKRrvXSKwc/JBCygtzr4qIgMNHQsNu8jlyXd/kccqD8PHliooMZv8OePZIXdu+e0BCab12im4Lpm5btcsm+Q9f66ZLwD3tGRj8fzJszi36oTjMD5gDvtJPIk1Whl5/FPBHunE2tOqjyMXDztTsV3Zjr+1L4YXAgL04UYwVjcgoGPm+/JZmvQ2/vSSBujJHOaz4Tp5aL/eVkNR5mJ7PHEepLxtn+75kFYkFnxTexN0oX/4SG4JS0CyNouLjlQE+6f+gdhmlc3up+ssnq923VRP6+RAW50PXdfcAPyIvhq2O4zQGCLReAtAGIPiTaAZsKvDkGQc1g3ne+xqI/CtJ/gKgkO/+d2iG4air4PYVqsTwwXkKmV4yQfv1vFTq/nE3lvh3rdCc/SZcUAJn4qhqetioDxyKSAAAIABJREFUVsPPezQOLeLryYsirp5SkpZM0Exe8IPaL0/Dkx00yxI82F/xHTzWSrN/M15VNNLjreWzsm0xvDkYXjsB3j9H0QYjXlAYaLUEReQcf6d/rnYn+pE27QbKXPTKKXDas3qQHPq4ctQWj8/991j5g7wnvLBsHBj8sCqy/PG6vE2+/LtEix8e0uBg8r2BY7/Xdeean+G+nfo5+T+aUQqOsDKMqsLn12mA+sdriiKaeJfWpx/U8qa5ZdOP7Ut0/2l3onxkup6pWdzxgXt3mwH+vgkN1fa7Vff2oho5dz0D1v8O752uuuF128i/Z9EX8OMjoftOe0YPVOd96EczdD835zmbBCIDPr1C10CQr0RkNU0EdBkBOPDN3SUb/KbuU4TE+oAHQ/87/TJ17U6SW/nI0Yq08PCMvX54GJZNVrRI6j746GKtP+rKyhfZkBcREapGFVMLJt0lgXr5t7nvu2d9aJnu8ubTq+RbFExKwMOk0zCVvDvmb/62Bp2VdvRCEY3eShPXlSjy6RXwQgUrHW/kjic2JASJDdXrlU9fjMLTYYjaI8pwzBZcwWLIYzLqXfhZvodE5bs1HxzHSQTSXdfd4zhOdeBE4DFgPHAp8Gig/SJwyHjgA8dxngKaAO2BGTlOnBvnvuvfKIOJjFKo/4C7/XraV3yrB4OFn/lhhPGJukBXdYob0RDMBR9rdiii8N6dRhWjbmtFEa38QSah39yjNII2AzT48FIRXu6r1J0zXtaAfvY7eqjODAgQPwVc5L9/yPdbAeUOn/2m/ERAgqHj+BVMPI69QX3wvEOa9PAf5jMzYPJ9mgntdrbWHdgt0SIiWgMIgJ6XqW/tBsEx18nBu2UfWPGtTN5WfCfPjvQUpU3sfViDg4SGyn/zfEZ6X6NZ0D9eh+PvqvgVagyjsOxYDn9+Asf9XbPwG/6A31+CoY/qf/j3lxQZNHJ06X/vN85SBKL3f1cjEC7+1ikaOAeX9eo1Sv/PfYoZKn/cTRI2vn9I0RMjntcD27ZFEh/636EH8J+fgDnvyfC40ymKFEjbn7sJZXBaxex3VUpy9jvQrLdCkqvFw0n/hm/v0zNK1zOL1/dp/9O1C2DoE3D01XrOWfAxnPxw7lGccXUlmk59Cj44B7qeJSFn53KZImf3KqrsVK8NV/+g6LXJ/5RfRfuTQr2jXBc+PE+GxFd+q4F7ebJjucqNV6+rvnl93bVaUT7nvZ/zmH63ytgbJCCV17NbVhYkrdPkwaLPFUEIkJqk/xcLya/YpAXSq+Ib+eviG5ZPX4zC0+dmpRvGlGEUSmQ0XPKF/NR6jVIJ4oWf53uI4xZTXXccpzsygIxEERJjXdf9t+M49YCxQAtgHXCO67q7Asf8ExgFZAA3u647saD36dWyhjtz1e6c4YoFsXczPBXwaPhXUtGONQxDHExSykHKTkU7ZGWqjGm/2yRuBYsInYfD6S/pQjT9eVVIqd0Cnu+tPOYLPtbM6cG9MGpSyQ1DX+4rz4eLx+nB7NUBqk5To7FvonXERRo4DHpAD2XZST+g32PrInjpWH99r1GhpVEB5o+FcVfBld/LTNMwKjv7tsN/A6Lf3+dKaBgXSFPseblm+Q8EDA0TGkns27kCzn3PrwQQLtb9ruinkx+BY/8Wum3DTAmZhS3tVRRS9ynSKj4wi7d1oa4tva5QlNOulVp//kcy6iqIbYthwi0SbjxGvq/Qd5BQ+toJGkBe9qUGup2GFf4ZJ2mj0rwyDuj1bUslyhSWjDSlgvz2ol53Gpb7ILYqMfcDRe+c+x50Ge6v3zxfKShOhNIRrvrR/x4Es22xyqy3O7F0+/nVbRK0QfeZRt2UCjLpLkWdXpLHA/0fb8BXt8Iti8rH5DMrE57qrOgfkOCfFVRuddRkaGERDhWaeR8pdXbkRPgoUG7x6iKU8d23Dzp0KJ2+GRWb+R/DuCtxHtw7y3XdXCIDShDZ4LrufCCHE6HrujuBXEseuK77MFBIh7YAiR2LLjSAHKfrtCpcfWnDMHInthZcPlEpFXsDnga1W0KfmyQWeGLDCffC8UEl3/rd5i/fMENVYxp01sxSVmZ4KpM07Aorpkho2DRHAkNkNc2ijByt9I85o7Vvq365n8OLBmrQWWHEezcpfaP9yTn3bTsIcBQVYWKDUV5kpuv/scvpJY808EpCgyKL6raWOfCYC/QeUTEq7/vnJ7oXL5ukfX9+As55q2TvHcwPj8BPj2q5xwU5t+cW2RguYhJCZ4UaHqZB3h+vQVRQtGBelZyy06AzXPa1Bl5Z6boeBc+YR0Yp3fH1EyWQ/tWPWqqK1O+23D8DkNnkj/9R5Y2rvpdQUhShAZQu2e/2ILHh1KIdXxnpPlIRHb88rb/jnnX6myz9GnAkJH04Ema8ElpiLjNdvkXLv9Hrs97Qd6M0/L/mfiihocFhsG0hvD5Q0XVb5mt70yPzPrZ2C7VJG8pHbFgz1RcaQJGBe4NMKzfPM7GhouNVs4uO10/T48q3P0blofNpGhOQd7WjYosNlYLrZ+imbBhG8UnsmPfD1dHXasDf5+95Hx9bSz8gkaE44mFuND9aZTI3zlYOdGxtuHaq8sdqNFT6xMaZ4ET66Rd54Thw6pP57xNfTw98y7+FAXeF53cwjMKydroGwI26a2Z6xAF5mZQE7wHzwk/8dY27wy1/hu53xiv6v510twapK6copSo6NnS/X55WNMAl4wsvKC74xBcaILS8cHnhiQwD/6mZ/7T9RTNKjogILbGZnZpN9JmPucA3+Dp8pHwePr8O2pyQ8/gFnygHHhQ2WxLn8fh6cPgFSqHodk7xz1NZiIjU/8p3/5Jn0NppihRY84uErA6DdT9ZOjFUbNixTEJDlxGK8vn0CqjZTKVOw4nrwucBn4YzX5FR6/rfJDTUbimvoTYn5H18rYAd2o6l5TOoX/BJ6Osuw30xq1oNpff0vqpilj83hJdGUS0BLp9ZuQ1VjbIlOlbpgYes2BAVU949MIyqzdDHlKtZHh4GnnP66wPl1XDGK/4MD2jG7veXZfwWrooq7U5SHvv+nbmH2xpGabFkAvz5qW90t3FWqNiw8gfl5Tc+XK8P7NGDY37i3sFAimG9dvm/t3eOIY8oOum9MzQL3OcmhX6v/lnvtz/gB71zhf7vFn2h8oyef1K9tnDma4ooSkuBsRfLe6BlH6VdVZSKUX1ugk/mqkJOQmLpvEfDLvD3OfIUOJikAW+jbjLD3LPWFxvmfqgIsuCZ4p6Xlfz9z3gp1BegqtPldIkNa6ephOrqqYCrqmYAHYfCt/cHogMC5f8848g+NyuVYscypQSGm32B/5s+N+k7MOAf+h+LbwBX/6j/+5Z98j6+XjtFJH33ILQdqH7OeF2Tbe0G+RXcSoOMVBk1dz9PURVTnwz1Lhn8b6UVjbtK9+S4eqruUqsZNK9AppaHOqnJSieKDAjIh8p1wSgTqrbYYBhG6VNeZon12sqVe/EEzQa1zBb217q/cqXrtAzfe7Y/SbOwK79XiU7DKCuS1qv1ohHW/eZvS92nMrHx9eGGmaqm8kx3Pfyf8l+ZGuaGJzZ4kUeFofUAzfZ/e7/C0f94XYOdjkMVdj7vA1V5Sezk+xZ0Pk3pU4u/hGnPajC1cJxvcjjsGUisQPm+nU6Be/Mo7xtOHCd0Jrp5YHnPOg0Ud63SZ5ayQwMBL1KzQZj8Mg6lAUXd1vpfWP+7/HhSdkkMO/x8be94ir7TX98B57yjdJOdAb+Oem1lmjz1SVUqCbfhoRfd4qX7Ne2p/8lhT0lALEgsiIySH8VrJ6gUPMg3oXptiZKlKTasmKLrSLez1e/0A/JKanKkIguPuBh+fEzRDQs+9o+LiII7VxXt2mOUHqn7ICr+0LomGGWGiQ2GYVRehjyin7zwTNnCRZMjlKYx7kpVv7DQUKOs2LNepQn3rJNp47aFmhFN2gBvD4P0/QqFnf8RVK8jF/jkCHh3BNy8IPew/oMB4SKmZuH7ERGhajIfni+hIaERXDMVqgVKL7bur5nYLfMhKlamsV7VhXdHqLpF7RbyfQAY/FDFEhrKEy8cfvF4WP6dbwLZ/mQ46zV5W8TVs2tOcel9lT/wjqkRmv5Xv70qIE1/Ht4+VWl1O5br8/YGxIkB0/Gti6D5UeHr184Var2IgNhacNe6op0jsQOc+pRM/gBuWQhz3lVUjGeEXBqs/F4RVG0GKILQux83P8r/jIb8Bz4Z5R/T9xalXG2eD63z8FMyypYDu5TyYhilgNVvMwzDKCwRkX6pzYl3qLb5m0M1S2YYpcmedUqRuOYn32Nh9c/w5U0SGkBi2E+Pw6x39OA4crRMCtf/nvs5DyYVnGqRG1ExOvcRF2uW2BMaAHqcDxd9ArcvU3RAcHnHIy+RSPf5tZCyGy6fBMfdWLT3rspUi1PZw8VfKkrF85Gp01ID0KGPhhrxGuHl5IfhzNcVUfLq8TB3dGjEXOv+8vOY9Xb43tN1ZWZcvW5oGmBx6H6uqqic+558i2oHovqSglJw9u8M7/0qebNEsvxSFbueBcOf81/3vkatVzXKKH/2rA8te2kYYcQiGwzDMIrC0Mfg+DtV6WLzPFg+WYODnpeWd8+MqkpaikLpazfXgKRmUw0+v3/IT684f4zMUD84Rzn/A+5WJATAx5dC/V9VaSGYg0lFi2oIplocjHi+aMd0PSswO+xA/Q7hM4utSgx+CKa/AGe+Co26ypDQnPzLju7naEb+fwHvky6n+9vi6qpSyJz3YND9GtCD0oecCInRRWX/Dhl1nvTvkvuMOY5SLzy8SJk9axW5sW0JvHi0PCsu/6pk7+WRvMX/HPKjw1BVkGrUXVFWiZ1h9rtKhSzO52aEl6T1UK8AI23DKCYW2WAYhlEUYmoo3HXgvXDBWC3PeFUzRsGkBWabM9Ph06tgw6yy6V/6wcpR8nfZZBh9NjzZCZZOKu/eVGy8mclagZnPiEgZKu5ZJ2O2O1fLM6HDYEUcnPasyhvGJCh3G2DO+znPm5pU9jnTDQ+TOaIJDblzxIXwt18lNAB0HKK0GKPsCDY47JCtDPKx10NmGjzZQaasB3bDS31UIrM4Dv6eWFiQSWtxqNta7ZZAdZmF49Su/cW/P5WU5C1KpSqIhES4bpqMSUGf445l+vH6mJlR8v54kSI7VpT8XIcKmemKUIkrhGhkGMXAxAbDMIzi4jgw5DHYvgReOg5+fFSlOP94HR5pDr+9rAfSBWPlMl8WvDtCs3KT7oElYZq9Cjdf3a4Z+G2L9bD+4UiYN6a8e1VxWfSF2uAw62HPwG1L4PQXNOPq0fk0Rdl4g/nblir8e+E4mTRuXybX+uQteiA3gzbDyMnA++QtEJMtj71eW/2PgapbvHOaSk4umwgLP8v7fAeTdL3zmP6CDCf/EhKbhbX7ANRopNSq7x6QsDv9BX/bml9Kfv4dy1Wdo0Yxwu89k9NdqyU0vNwHfn48DH1apt93zAUlP9ehgmdIG59PuV7DKAEmNhiGYZSEjkPgyikQnyix4bUTVA7QzYRJ/9CgGlS6bvoLMPMtCRKg3Nm1v4avpvWWP1WfPW0//PaCHrhKMluUkSpzsXDzx2tqz34DLp2g5c+ugXkfhf+9KjsbZ8EPD2m5dnN/fUwCJDQo+Pj4eiqVmLxZxo2/vwy/PAVPdoTti6HXqAJPYRiHHP1vhxP/lfu2s96E6Dh5DmxZIGPGRt1UqSKva/n758KLx0h0yMqEb+6BKf/2/VRqloLYAPJwAFjxLXQZAZd9BTWawJT/86vR5McXN8DT3WQEGcymufB8L8jKKJ7Y4EVd/PKULzJkf4+isOAT/Xjn2LWy+Oc61Fj0udqGvcq3H0aVxeIYDcMwSkqTHnDdL7B3M7zcF1ocIxf+r29XdYD4RIWzf3OPf0ztlrBvq8oUNj4czvtQM19uZs7Q3cIy+x1Vy7h+Bkz+p0qNbVsEjbsX/Vyuq1m73WvgpnlyM//0SpkSui70uxWOuqr44fBtB+pzAhkFTrhFTuqrfpAnRt02Ejoy0yG2mL4C2VkxRbOJ548J3zlLk42zYPxN/usaxZx56nQaNOsN4//uVzgA6HMTHD6yZH00jEONqGoqg7zoC6Us9bwUcCUyb1usqjALP5OvQ3R1lRVcHyhV+92D0OZ4/1zTn4eYWqHRSeGkx4WKYupwsn8fOO0ZCdHvnQkXj/Ojm1J2KWXHq3aSlgJzP9A96f1z4PSX/ZLP62eoTewMHYYUvV/e77vhD3/djuWQlaWKNweTAEfX6d1rNSDucZHE09z4NCCqRFZTm5Wh0qX12ha9b4cayyarVGmNUhK8jEMeExsMwzDCRc3GcPN8zXo5jkLd67XTzHJCAz00fXwZbJqth9DDzpCp37wP4eku/nmu+Baa9y7ce+5cqZ9mvZSKcNjpMuwaeK/EhrXTiic2bJrjz7r9/F/VTl/wsULyV/8Mk+7SA91xN+r94+oWLrfc85Nocay/ruWxcO1UVVL4+XF9HgA4Ou95H8rgLK6uhI4VU2Salz3EOT8yUmF0oDLCyu/1OVVkNs6G1wbpe9P/DkhoWHwjtahqcN778NpA5YgfdZVc9rtU8M/AMCoqw5+HE+71y7Z2Hg5f3yGBee2vgCsT4ZgasHej9qnbBma+oZ+azVRtJCtDs/ylVc40IiJnBZMOJ8O578LYS2Qye/J/YMqD8Otz0Kqfqsa0O1HXWTcTznkbfn8Fxt8AnU6VOeyGGRDfAP42vfh973c7VK+t++PSiTDuKti6QOL7oy2gTmu4aa7K5M55D9ZMgwvH5jxPcPReq74y23y5rwSKPjcDjj4HIyeZGSpTfOQl5d0TowpjYoNhGEY4qRYftBynWXqPOi3h8okw6y3d3L19e1wgASIrA6a/CJ9fBzfMLPghbu8mmZMFz1Z7YfG1W8r5e9Jdygse/FDRHgrXBWbi2g6Eqf/VD2gmb+cKeP9seVLU7wAfnKtSi7cvDf39c8MzJsu+X2Q0DPynBJsJt2jdcTfoAfjNwRpsn/KE3Nu/uhW6j5Rjf0HsXgNjL1UdcY+PL4VZA5ST3WZAwecoD1Z8p/baaTJXKykJDeDyr2HJ1ypHWZgUDMMwcie2Zmh0VEIDDXRX/6xrZsdTlGLhZioKIDoOrv5R65Z9A12GQ2LH8uq9RIPuIyWI7F4Ly7+RF8WaaRr0R8Uq6q5hVwkpsbXgvTP0+21ZIOH5iItLJpIMus9fbtVP7eqffVPb3at1v1gWMBBe/g3s257zerhnrdrjboQTH5Qo2+AwXesWfgb1Oyplz8jJjmWQnqLIBsMoJUxsMAzDKEuiY+GY60LXteqjH9Bs0Rd/g48u0gD8jFcgIir3We21v0poGHA37N+usoLNA2XyHEcCw+fXKVQ3Iko5yIV9ONw0R7m9Z76u/P6sQERC3db6GfUNvHWKhAaAtGRYOx3an5j/edNTAp9DXO7be41StEdqssSGzfNh9U9KORkbNPsy/yM94O/dpBnDgffmfr7vH1bkRdsToGUfqJagXOsdKxQafM1UaNCpcJ9JWeG6iipJ7BQeocGjdgs45trwnc8wDJ/hz2vg26pf6HXWdeXT4DiKMitOpFlpcNgZMPd9DeKPulKRFvu2q1rF3A9lIjnied17WvaB6HgJwcmbJFSc+lTB71FYajaWcL3iO9i1yl//yRWQshN6Xi6RPmVnzmuiV9Gi02n+fbL5UTDrbS3v25b/e6cf1N8tL/Fn3W9KhayKKRmb5qhtcgTsLt+uGFUXExsMwzAqEp1OhW/rSUg4sEszMxFRcMFHCm31SE1WnmpEFPS7TcJEdtocD7cs1APitGeUB3vSg5qVStkNx/4t70iEDTOg6ZHKkb1rHTzTVWGvHi2Ogb/Plk9FdKxC9DfMkJiwaTb0vkYPkNn5K7IhIe/PoOel/vLI95RS0Ki7HghXToEuZ8gA89fn/P363a5+ZGfjTGg7QOcJZt921Zx/dYA+v/j60LRn6EDAdUMHDSm7YMLNGkwcdWXphD5nZcL/ekDSOs3UGYZROajTUj/ZcZyKWeo1OJWtdX+1CYkSIQ47QyH2Xr+jYhQFtvQrGU2e8Ur4r3+t+6uS06of/XXLJirNo34HiQ2pyTmP2zBTArVXLhYUpbHkK92nFn+Ze0SExxsnKZXgrvU5vXzS9sObJ0NMTbh7ve4JblbxU9oqGpvm6F5crx3stnKhRulQAa9+hmEYhzDVa8PtgZv+c0coDSArAz6/XnXKF32u2f7MNO3TdmDuQoOH42gGys3Uw9qst/xtyyZC17M1079/Oxx9HXQYrHJku9fAMX/TftXi4M5VOc9dp5Vfk77x4fDTY/62X56WN8DJD+tB1eMvsSGPyIbsxNZSVAJI+GgaCPcc9rSEma9uV7jt8m/0EBzMwSTNkvXIpQxaQqLylj+71q/2UKe10le8B+xXj4eo6nDJ53qYHXsJrJkqY7ilE9Wvo64KFTky0mDnckVctB2Y90Pp0kmaVTzj5VDBZ/m3EhoA+txSuM/IMAyjqMQkwLE3wOz3QoUHj+wCSa9RiqQb/lzpCK19boKaTWQ6GRGpqLnGPXQf8vyDUveqzUyH7UshZYfuiY26hl5H250Ity9XWsbiL+G/7WRE3DLb77l1kYQGULpL9si8pRP9983KUqTgsonQ/0449vrS89ooKzbN0WdsnhZGKWJig2EYRkXDu/Ff8a3yexseBm8MhicCYZxePm3TXnDhx4U738D7VVrSzVIKRNI6DdS/uVshopHVVKaz3UlwcA/ghEZSFET/O+Rw3vsa6DxMlSz+eE0zUWe8HORwnodnQ3FodyLcOAue6qyyom1OCJ2ZWjNNbdM8Snq16qvKHet/U63xL2+Cd4bpvN3Ogc3ztN/bw9Tv7YvlyJ6yA355RlEWa39ViDHAuumwfLIvBP3lVA9sW6IH1oSGsHUhjDlf63ev0UPr5vmw9GtFb9RsKtGjsIKMYRhGcTj5YfkcFCbyov2JBafJlYTaLRRlBhITTv6PjIkdR5EFoGtoRhq8NUTVegBw4NT/5jyf4+ja3OJYXZs/uTxgaBmpKhu9Lg8yIwbmj4F2gySab1kgA92F4/ztnvif2EnVnkCpfh6ZGYpGLC8/nIw0iSut+uYe5ZedzHT9nr2vKv2+GYc0jhuu+u6lRK9evdyZM2eWdzcMwzDKlznvK8S016iAoeRczQLllqqQF8lbNRCu3VyvszJVFq1mE63/9n5VtIivL1+Jo64sWh8PJvll1NZO14PajFdVAs57iFz+rcwlr/hOebXhYO4Hqgdfry1c9KkeWgE+/xssngB3rFBFhvxwXfjiekUbBNN2oKI+Iqtphq3b2f627x9WKU0301/XuIfSH358VJ/jqEl6qHuqC+wPyh2Oq68H1dnvBdWEd/T3HXhv6ZXCMwzDqGzsWa9UvuHP6fr+7ghdZ9ufLA+hWvmUbcxIU4nNd4crvSKhIfz+MnQYqvXNe0ODzrqW50anYTLK3DhLkXxDH5NwsWi8KnV4lY0+uw7mfQD3bC5bofjPcbpv12sLv72oEsmnPqnIv/zYPA9e6Q9nvaH72rJlkJBPemN+7NsHHToU71ijSuA4zizXdXOd2TGxwTAMwygdXBc+vRL+/ATi/p+9+w6Po7waPvyb7V3SrqRVt7psyZYr7oABg+mYGiCQBEIIgZACbyC9EBJI+UhCCAHelwBJCA4tgOnYxr3i3mTJlmRZvW+vM/P9MbJs4YIrNuG5r8uXtTM7M8+u16uZM+ecxwPT79GCIy/fCt9YAd7KT97HkWpYAi9cD0hQdQWcfb92IlU2C6568uj2teopeOc+7ST2zlWHD1QkovsyGfoatFlArKmw8Dew8CG4c6V2Ytq5TZuGzV2k1cdmjdICM4qiTcVpMEPRmcf88gVBEP5rRfrhN8O0bIe+3dr0ofc3Ht30x0v+H8x/YN9jow2QtH4+xefAS1/SSi7G3gwXPqyV4LVt0NbtDdDvFQ9r2Xsta7VZRlLy4XfF2rqLfgeTbj++13uk4iF4dKzWQBm0cRgs2u+k72w6/LZrn9Wy+e5epwUqRLBBOA4i2CAIgiCcGomoNkf6jne0soO9vr1xX7+HE2XVU/DOx+aU/8LzWlnH0eqq1TI+zMd48tW+WZvrHbSsiDNug/N/eXo2ihMEQTidKTI84Ib8yVrZ24jL4Av/PPp9vHWP9vesXx/YDFJOaD0M8s44sl4M4V74w0itX1GoU5sSeq/bFwISdGzRMgysaUc31iP18QDKjS9qr2Hhw/CjNjBaD73t3G9rDajv3629XhFsEI7D4YIN4qxHEARBOHmMFq0m9IzbtOkq//N1bfnhZqM4VpNu12pufXu0Ez+TAyouOrZ9ZRzniZN3pJZ+W/uudgK4t8mlIAiCcHR0eu37fM9K8JTBVf97bPu47E+HXq83aiUVR8rmhlkPwns/1ra95V2tNOHd+7VZjvZa83/wtQ9PfDPJ/iZY9ietz1LLR1qwpPDMgb5IKnTXHX6a1Za12pSXn/Uml8JpTwQbBEEQhJNPkmD09Vrq6kd/A0vqyTmOp0T7Uzzj5Oz/SEmSVs8b6Tt1DcMEQRD+W5idEA/CpY8c/o79p2nCrfumCXVkaLNdOLOgu1Zr9Nu/W2s42bZBu7A/kd78LqhoWRrWNC0Lz2jVGlgCrHtOezzhq1r53v5aN2jZdxc8eGLHJAgHIYINgiAIwqen8nLtz+eB3igCDYIgCCdC7njtQr7orFM9kqE+XiKxt2EkaKUWyx6Fd38AX3rjkxsVg5aZsOpJrY/P5DsPnnnQ/BHsnA9n/c+BWXiZI8BdomVUAKx4HM5/QAtC+Fsg3APzf6GtG/elI3+dgnCMRLBBEARBEARBEITT1/XPf/JzTjc2N1zxGLzyVXgwA/ImaqUcaYVa02DQSu72TlXZugFe/JKWEQGQPwnyJmhTLO9ZDZPuAFTtOTb3wYMFkqQFF97+Hkz7NtRlJSVqAAAgAElEQVQv3DdV5/7OvHff7FGCcBIdc4NISZLygb8DWYACPKWq6p8kSXID/wYKgUbgOlVV+wa2+QHwVUAGvqWq6nufdBzRIFIQBEEQBEEQhM+kBQ/C4t9pP6cMTM3sa9L+nni71jx47bNavwd7Jlz+qBZQyB2vTaf80d9AjmlTJiejWjnJzf/Rpmb+JFE/vPdDbSrmytlao+bhl0F62dCsCdEgUjgOJ2U2CkmSsoFsVVXXSZLkBNYCs4GvAL2qqj4sSdL3gTRVVe+XJKkSeAGYCOQA84ByVd1/gvIDiWCDIAiCIAiCIAifWYkobHtNa5KcVgTn/VRrmlz3vjZNNAPXY5PvhAsfgpduga2vgs4IhdO03hC7PtTKNqpmn/i+RCLYIByHkzIbhaqqbUDbwM8BSZK2A7nAFcCMgac9BywE7h9YPkdV1RjQIEnSTrTAw4pjHYMgCIIgCIIgCMJpzWiBUddpjS6LztYaOmaN0rIVUvLAUwp9DTD+Fu35M38GJhuc+xOtVwXA+K+csuELwrE6IT0bJEkqBMYCqwDvQCACVVXbJEna2x0rF1i532bNA8sOtr/bgdsBCgoKTsQQBUEQBEEQBEEQTg2dDoZfsu9xehnM/svBn5tWCFccYp0gfIbojncHkiQ5gFeA76iq6j/cUw+y7KA1HKqqPqWq6gRVVSdkZGQc7xAFQRAEQRAEQRAEQfgUHVewQZIkI1qg4XlVVV8dWNwx0M9hb1+HzoHlzUD+fpvnAa3Hc3xBEARBEARBEARBEE4/xxxskCRJAp4Gtquq+sh+q94Avjzw85eB1/dbfr0kSWZJkoqAMmD1sR5fEARBEARBEARBEITT0/H0bJgG3AxsliRpw8CyHwIPAy9KkvRVoAm4FkBV1a2SJL0IbAOSwF2fNBOFIAiCIAiCIAiCIAifPcczG8VSDt6HAeC8Q2zzK+BXx3pMQRAEQRAEQRAEQRBOf8fdIFIQBEEQBEEQBEEQBGF/ItggCIIgCIIgCIIgCMIJJYINgiAIgiAIgiAIgiCcUCLYIAiCIAiCIAiCIAjCCSWCDYIgCIIgCIIgCIIgnFAi2CAIgiAIgiAIgiAIwgklgg2CIAiCIAiCIAiCIJxQItggCIIgCIIgCIIgCMIJJYINgiAIgiAIgiAIgiCcUCLYIAiCIAiCIAiCIHyi/13Xwc2v7TzVwxA+IwynegCCIAiCIAiCIAjC6e9XS1sBiCQUrEZx31o4PPEJEQRBEARBEARBEI5YXW/0VA9B+AwQwQZBEARBEARBEAThsPoiycGfa3oip3AkwmeFCDYIgiAIgiAIgiAIB5j89Bae3dgFwB9XtQ0ur+kWwQbhk4lggyAIgiAIgiAIgjBELKnQHkrw80XNAGzoCDMt30F1po0dIrNBOAIi2CAIgiAIgiAIgiAM0R+VhzxuDybIcZqoSLewo1v0bBA+mQg2CIIgCIIgCIIgCEP0x/b1aEjIKp2hBNkOExUeK92RJF3hxAHbdPijxJPKpzlM4TQmgg2CIAiCIAiCIAjCEHsbQpp0Eq2BOCqQ4zAy3GMBYMfH+jYsqOlg0q/nc+fzaz/toQqnKRFsOAUicZmr/7qc/1tST5svQlIW0T9BEARBEARBEE4fe8sojHqJr79dD0Cuy8TwdCsANT37Sim6AjFuffYjAOZt78QfPTDrQfj8EcGGT9lbm9qY9psFrN3dx4NvbWfKQwu46E9L6AnGTvXQBEEQBEEQBEEQAOiPapkNMVmhrifKaK+NKXlO0m1GXGY9Tb591y+7e0JDtn35o+ZPdazC6UkEGz5FC3d0cte/1tEbinPt+DweuW409184nPruEBN+NY8rHltKZ0A0WxEEQRAEQRAE4dTqG8hsSCogq/D9aTkYdBIAWXYjbcF92QtNvWEAvnlOKWMLUvn7ikYURf3UxyycXgynegCfF819Ye56fh2V2S6evfUMMp2WwXUJWeEP82rZ2Oxj4q/mU+ix8fWzS7hhYsEpHLEgCIIgCIIgCJ9XHaF9wQSLQWJcln3wcZbDSMfHgg2SBHefV0qZ18G352xgUV0X51RkfqpjFk4vIrPhOKiqSjCWpMMf5emlDWxr9R/yuW9taiMUl/nrTeOGBBoAvnVeGQ0PXcK/b5+MzaSnsSfMT1/fwtZW38l+CYIgCIIgCIIgCEMsbfLz7MauwccTcxyYDfsuHbMcRtpDcVa1R1i4o5Md7QFyU62YDXouGplNhtPMv1fvORVDF04jIrPhONz/yiZe3K8eyWk2sPT+c1FUlTS7aXD531c08uj8OoZnORnmsR9kT5pJxR5W/fA84kmFWX9cwrVPrOBLUwq55/xyTAYRFxIEQRAEQRAE4eS76bVdQx5PL3AOeZzlMNEVSvKDZR3U+5rRSXDt+HwATAYdZ5als7i2C1VVkSTpUxu3cHo5ritYSZL+JklSpyRJW/Zb5pYk6QNJkuoG/k7bb90PJEnaKUnSDkmSZh3PsU+1XV1BXvyomcnFbn58yQgevmoUgViSyx5bythffsBVjy+jzRehtT/CT1/fSiguc/9Fwz9xv06LEY/DzEt3TOG8EV6eWLSLl9aKqKAgCIIgCIIgCKfG1LyhwYZytwUVqPclKPTY0OskLqnOHlw/tiCN7mCcZ5c3froDFU4rx3u7/Fngwo8t+z4wX1XVMmD+wGMkSaoErgeqBrZ5XJIk/XEe/5RQVZUbnloJwK3TirjtzGKum5BPcbqdcFxrpLKuqZ8bnlrJ1X9djkEnseS+c46qZqko3c6j148hL83Ke1s7TsrrEARBEARBEARB2J8/pl3PVHgsvHVDBT87K5eqDOuQ55xfnMJAr0humjyM7Q9cyFnlGYPrZwz8/Kf5dSRl5dMZuHDaOa5gg6qqi4Hejy2+Anhu4OfngNn7LZ+jqmpMVdUGYCcw8XiO/2lbu7uXu19Yz/de3kRnIMb5lV7Or/QCoNNJvPmt6Sz//rnU/PJC/vaVCbT5omQ6zbxw+2Ty3bajPp4kSVwyKpvFtV38Y+VuANY09rKpuf+Evi5BEARBEARBEASA2p4IAPdNzaEqw8YtYzIPKIUwG3QUuMwA5KRaMeiHXlbmu208cdM4+sMJ1jT2DVmnKCqvrmsmFEse8ZhUVcxs8Vl0Mno2eFVVbQNQVbVNkqS9t/NzgZX7Pa95YNkBJEm6HbgdoKDgxM7I8PTSBjr9Ub51Xhk2k563N7ezpy9MhsPM7LG56HUHrylSFJUf/WcLTb1hLEY96Q4zD84eOeQ/ns207+08d7iXtT85H7tJf1x1St+bVcHmFh+/e7eGs8rSufaJFQAs+/655KZaP2FrQRAEQRCET5+sqCzb2U2K1cjo/NRTPRxBEI5CTU8UgArP4a81RnmtNPpipFqNB11/VnkGZoOO97e1M6XEM7h8YW0n97y4kesm9PCbq6s/8VppT2+Y655cQUWWk59cWklJhuMoX5FwqnyaDSIP9ik6aIhKVdWngKcAJkyYcMLCWKqq8teFO+kOxnl7SxvnVGTy9xW7B9cHogm+Mq3ooNu+ubmNmvYAf7p+DFeMOWiM5AAO8/G/vQa9jl9cXsVFf1rC2b9bOLj8rU2t3H5WyXHv/7NANJYRBEEQhM+OJXVd/GLuNnZ2BjEZdGz86QVYTZ/JyllB+Fza0R3BadKR6zx4EGGvB2fkM8KpZ1Kx56DrbSYDZ5al8/7WDn56aeXg+fwr61oAePGjZl5b38owj42rx+fx9bOKkSSJhKzw/Vc2E44nmT02lxdWN9Hmi9Lmi7KtdSUvfn0KhemHbrovnD5ORrChQ5Kk7IGshmygc2B5M5C/3/PygNaTcPxDavdH6Q7GuWZ8HvO3d/D3FbsZnZ/KC1+bxNf/sZbfv1/Lmt19NHaHcFmMfGdmGd3BOC+t3cPCHV1UZru4tDrn0xwyAGVeJ6/dNY1VDb1E4kne2NjK/O2d3H5WCWsaeynJcODeb/aL/xaqqvL4wl38bWkDL94xRUQxBUEQBOE019Ad4rbnPiI31crlo3N4Y2MrTy7exS3TikixGnl/azu7ukJ8Y8bn44aJIHwW1fREqPBYP/FmX4rFwJ2j3XCIzHCACyqzmLe9k21tflKsRhbVdvHWpjZumFhAlstCMKaVWTz8Tg3rdvexurGX/nBicPt3trRjMui4+9xSZlVlcfPTq7j+qZU8efN4kTX1GXAygg1vAF8GHh74+/X9lv9LkqRHgBygDFh9LAdo6A7hdZmxmQwkZQUVSMgKXYEYuQepGQKIxGUe/1CbwuXGSQVcMSaHn7+xlbvPKcVmMvDg7JHc++JGtrX6yUuzsrK+hy8MNIHMclm4dVoRd5xdfMgyi5NtZG4KI3NTAOgJxZmzeg9NPWGufWIFw7OczL17OsaDvO4TLZqQCcaSpDvMB6xTVZW4rGA2HP3di4SsoJOkIe/v91/ZzL8/0mbieGdzG988t+zYBy4IgiAIwkk3Z3UTsqLywu2TMRt0vLGxlT/Oq+OP8+pId5joDsYB+NKUYdhPQAaoIAgnlqqq1HRHubwi7ZOffATOG5GJToLvzNlAXWcQgCnFHn55RdXgNZsvkmD0L97n/W0dTBiWRmWOi3hSYdbILJKyyrRSz2C5+j9vm8QNT63klmfXcPtZxazd3YfXZebB2aNOyHiFE+u4vuUlSXoBmAGkS5LUDPwMLcjwoiRJXwWagGsBVFXdKknSi8A2IAncpaqqfLTHXFzbxVeeWY3TYuT8Si8vr21Gr5OQFa3awmrUYzdrPRWuGZ+HP5qkJMPOa+tb+HBHF+dXehmTl4pOJzH/3hmD+x3msfPyN6YOPm7sDtHUG8Zq0jM2P/WgAYxTZUx+Ks8sa+SaJ5YDUNMeoOxH7/CfO6cyKjeFJxbtoisQw2LSM7HQzXkjvEO239bqx2bSM8xjO6ryhLc2tfGzN7bSHYxRlG7ngSuqkJDoj8Sp8Dr52Rtb2binn8nFHqpyU7j73NIjCoCsaezltuc+ojjDzi8ur6I6L5WErPDKumauHZ9HTXuA97d1iGCDIAiCIJwG4kmFRbVdFKXbKEp3DN4oiMRlXlrbzIyKTLwuCwBP3DSe3lCcQDRBQ3eIl9c2k1RU3tjYyvVn5IsySUE4zbQFEwTiMsM9lhOyP4/DzIRhblY3anMKXD46h59cWjnk2irFauT6M/JZXNvFw1dXU5p56GzmqpwUrh6fxzPLGnn4nRrSbEb6wglumFhAVU7KIbcTZdmnhnS6d/acMGGC+vybH/LulnaW7epmdUMv5V4HxekO3t3aDsDIXBcXj8rGYTZQ3xUilpRZ39RPTXtgyL6+dV4Z351Z9pn/oLX5Isz8f4uoyk3hlqmF/HXRLjY1+8hNtVKcYWdJXTcuiwF/VOvw+tIdUzij0I0vkuAXc7fy6kCdVG6qlR9dMoKWvgiTit1U5x06FWl7m5/L/ryUqhwXF47M5rnljbT7o0OeYzHqKPc6iSZkajuCn9jfIikrzFmzh0c+qKU3FEeSQFXh7PIMrhqXy7fnbOCR60bjjyT4+dxtvPHNaVTnpRJNyJj0OnSnKMvkSKiqys1Pr2Z6WTp3nF1CLCmTlNUTchdHVVXWNfUxd2MbHf4oP7hoBAWeo5/tRBAEQRA+Lp5UiMRl3trchkEnMaXEM2RGLV84wZOLd/H4Qi1btCrHxVNfmsBr61v43Xs7AC3AcOHIrIPuP5qQueTRJezqCjG9NJ2HrhpFvttGMJZEllVSbIevERcE4SSorQWHdoG/oMHHrXPreemaMs7IOYIS5mAQyssP+5RX1zVzz4sb+cKEfH5zTfVxD7e+K8hv393B9y6sIN1uZtJD85g9JpeHr65GVlR0EkOu91bs6uHWZ9fw2l3TqMhyHrC/Dn+UO59fx4TCNG6ZWkRWyokJtHxeSJK0VlXVCQddd7oHGzKKKlXXDb9HUVWqclyMyk3lzhkl5LttROIy65r6mFriOSCA0NwX5kf/2cKVY3NxWgyMLUj7r+pr8PHo3JK6Lm5+WqtKuff8cu4+rwx/NMHYBz5AVlT+ddsk/rJwJ8t29pDpNPOt88r427IG6rtCg/u4cmwuD101CotxXxnEwh2d/HvNHjY1+/BHEyy57xxSbSYW13bxq7e2U+p1MHtMLjs7g1xanU2+24aiqMx8ZBH9kQT3nF+O1ajHH02wtdWPx2HiS1MKyU218ss3t/H00gYmFrr51ZUjyXRZ+NeqJh5fuJPAQKDk9bumUZRhZ/Kv5zM8y8mY/DReXruHAo+NR68fS/FR9nFo7A7RH0kw5gTWeAWiCcwGPVtbfby5qY0bJxWgKCrn/2ExABdWZbGyoYekrGIx6njgipFcPCqbpKwMieqGYkl6gnHMRh3dwRgjslxDAirNfWF++eY2VjVotWxmgw6jXofZoOOpL41nXEEaW1v9vLGxlfMrvaTZTDT1hphY5DkhzUoFQRA+C3wxH3Nq5nDDiBtwmVynejgnTGuwFZPeRLo1/aQdoz8c545/rmVl/dBZzW+bXsSPL60kHE8y9eEF9IcTTCpyc2l1Nr96ezvRhALAjIoMJhd7uG160WEzQmNJmb8v382v3t4OQKHHRkt/hBSriTm3Tz7sXU1BEE6C/YINj61p5/cr2tj89Wqc5iMojT6CYAPA2t19lHkduCwnPqD4g1c3MWfNHsYVpLG9zc+ZZek8efO+a9/vv6Ktn1ri4fnbJiErKnf8cy1mo568VCtPLq4HtNYTKVYj8++d8V913XiyfaaDDebsMvXuR1/m+xcNP2ifAGGfv3y4k2Asyf0XDh9ctjeSuL+535zOqLwUwvEkb21qIzvFyor6bh5fuIs0m4nvziyjpj3Awh1dtPRHyHSaKfM6uHly4SHvVHzc8p3d3P6PtQT3mz833WGiP5xAJ0lcODKLd7e2c1l1Dr+/duiUNzXtfi784xIANv/8ApwWI79/bwdPLdG+COJJ7aRmeJaTl78x9ZAX0qqq8sG2DlbW95JmM2I16XnwLe3E5pVvTGH8MPcRvZb9+cIJFtZ2cuHILMwGPbGkzNm/XYhOgnBCHtLQBrQvLUWFaaUelu3sGVye4TTTE4wxoyKTFKuRpt4wa3cPnYP4zLJ0ppako5OgsSfMK+ua0UsSl43OZkqJhwsqs2j3R7nlmTU09YYPOWaTXsdV43Ip9zrJTbPSFYiR7jCRlWJlVG7KKetDIgiCcDJ898PvMq9pHiadiS9XfVnrbK4kSDWnEk1GSSgJbhpxEx7rwbunnw46w53U9dVR5anirYa3+PeOf9PgayDFnMLMgpmEk2FiyRgPnfkQNuPxZ7bFkwpPL20YDPZnuSxcOS6XK8bk8Pv3alm6s4sKr5NYUqGmPcCPLh7BleNySXeYWVnfw53Pr+Ouc0r56vSDz+h1KP9YuZtH3t+Bx2FmaomHOav3oNdJvPj1KSQUhe/M2cDFo7K54+xifJEEGU7zkGnGBUE4QfYLNtz5dgNbOsMs/krVkW17hMGGk8kfTfDwOzVsbfHhshpZUtc9JMPq2ieWs6ZRO89+4qbxxJIy356zAafFMHiD8+eXVTKh0M0Vf1lGUbqdK8fmMntsLs8sbeDDHZ2MzE2htT/CMI+dH18yglSbCEbs9ZkONpRWjlbrtm74zJc+nEqLa7tYu7uP0fkpzCjPPGT5wSPv7+DRBTsBrfdFcYYdWVF58Y4pxxSFlBWV5r7w4P4ynGZafVH+PL+Ol9Y2U+C2Mef2yYN1nftLygotA/+hP05VVR6dv5M/zKsFoDTTQXVuCm2+KHlpVpKKSjCWpMMfZVOzb8i2VTku6jqDpFiNXD0ujx3tfjr8MXJSrQzPcmIy6JAAk0HHyvoeOgMxLEY9oViSkkwHqxt66QrE0EngtBgx6CR6QnGK0u009oS4eFQ2iqJSmG5HL0lcOjobo15HSYaDba1+XljdRHcwhtNiIJZU2NLiIyGr6HUSk4s9FKXbsBj1hOMyf5xXO3i3yKiXuHpcHl87q/iAWTn80QT/770dtPminF2Rgd1k4Lfv1jC9LJ0zyzJ4bX0L82s6ORiLUceUYg9/+eK4/+oTOEVRkVX1U2miKgjCPgk5wdaerTT4GmgONtMb7SXXkUs4ESaSjBCIB9jZv5OWYAt5jjzumXAPZ2SdcczH88V8nPviucQVrQmhXtKjqArqx2baHukZyaUll3JFyRU4TA6aA83E5TgOk4Pntj5Hf6yf6yquY0HTAq4pv4Z8Z/7BDndQSSVJa7CVSDKC1+alrr+ONHMakiSRbk0nxazVFCeUBBs6NzAucxx6nZ6a3hreaXiHnkgPbzW8RVLZF6yvzqjGaXJS319PQkkQSUYIJUJ4LB7K08rpj/Uz3jue+864D0mSUFWVVl+U7a1+Wn0RclOtnDs886DnUoqi8vV/ruWDbR2cOzyT+y6sYHjWvoyQDXv6uerxZVTnpWIy6MhwmPnzDWOHnEsoinpCSht3tAe4+elVdAZiB12f6TTz3nfOIu1zfsdxa6uPDn+ULJeVypz/nuwd4RQaCDZ8UO/ja2/Wc2FJCk9cUnxk254GwYb9xZMKF/5xMQ09IV79xlT6IwlueWYNd51TwvztnbT7oyiKSk6qlbl3T+f5lbvJS7Mxs1Lrcffhjk4emLuNhu7QYIn3XsOznINl+l+YkM/MSi/DPDZ6Q3H8kQSrG3rR6yTuOrf0pGRwgHbj02kxoNNJbG318fqGVu69oPyYGvSfKJ/pYMOECRPUjz766FQP43NBUVS2tvqJJWVGZLtOapfoSFzGqJeOufGmqqosqu1iw55+XljdRCypkOWy0BOK0zVwkuK2a1kal4/J5cOaTixGPbOqvNR2BLnuyRWE40lKMhxIkkRvKEZ3MD7YaBQgJ8VCdyhOWaY2tWhDd2iwMWkolqQzEGNXZ5CKLCe/v3Y0PaEYmc4TV+MVS8ooCqio6CRpSHnL0Yom5MF+JhlOM1tafPSHE2xv8/P3lbsx6nRcMyGPYW4bC3d0UdsRICvFwuj8VC4ZlU2510lnIIrZoGNBTScN3WGyUyw4zAaun5h/yEBFQlYw6CQkSSKW1HptHC5w6AsncFkNJzS42BWIcfPTq+gPJ7huQh45qVYC0SQWk54Lq7KQJDDopCER6lX1PSzb1UMsKdMfSrC8vpvppencfW4ZOanWEzY2QTgd/HPlbl5b38IvZ49kW6ufiUVunBYDS+q6mVTsHvK9Vt9fT4OvgTxnHnE5znPbnsOsN5NmTsNhctAV7iKSjNAb6yXdks7Gro00+hsPOKZO0mE32LEarRS5ish35bO8ZTnhZJjZpbNZ3bSbgpQs7p9yF+m2gZmYgjFSbSYkSaU32ktSSZJl1+5aJZUk35z/TZa3LkdFZZbnp6Qby9CrFswGieLsJGMLUsmyZ/HbNb/lXzX/AsBtcVORVsHKtpWoqKSYUwglQkMu9PWSntLUUm0M0R5m5M+gylOFWW8mw5bBeO94jDoj4USYN3a9wTNbnqE1dPCZvS16C9eUX0O6NZ13G9+lprcGg86A1+alI9SBgoJRZ2R26WyqM6qp6a1hZsFMxnnHHbCvF3e8yF82/IU8Zx56Sc/6zvUYJBMgYcZNV+Ml6Ey9qLINOZrNlSOr+cUVVTj3OwHuC8V5ZV0zD761nR9fMoLbzjz4xUU0IR/X76Cj0dofYe7GVpKKygWVXjr8MRbUdOKwGHh0fh03TCzg3gvKSXeYWbijk9fWtzB+WBpWk4GrxuaesH5OsqLyp3m1LN/VQzguM6XEw3fPLz+lZYmKovLe1na+8fy6wWVzbp/M5OLTN0tH+IyorSVqsTH8cS0b+p7J2Xxr4pFlM59uwQbQyqZn/H4hd51TQmNPmNUNvSy7/1w2t/Rz9V9XAPDyHVOYUHjoLOddXUEeW7CThKzw9bNKqMxxoddJPLagjlfWtdDaHyE2kG19MBML3Vw8KoubpxQOZhHXdwXpC8dZUtfNmPxUjHod9V1BMl0WppWm44skWFrXxYhs12Avve5gjL5QnN+8u4N52zsAcJoNjBuWxvqmPvzRJM/dOpGzyzNO1Nt31ESwQfhc6fBHkRWV7BTLIS9aowkZnSRhMuwLdoTjSSJxmZr2wEDpiPOE3a05nT2zrIFfzN02+Nhq1DMy14XFqGdDUz+B/Uph9rIYdYNZFxePymJGeSZbWn3saA9Q2xHAZNDhMBvY0xvBZtZj0El0B7VZS/5nVgUN3UHe29pBTZtfC2hUZ1PfFeK55Y2cXZ7BFycXMDY/DaNBR7svgl6nI81mPGzK2t4Sl4buEM19EXpDcZ68eTzf+Oc65m3vIN1hpjt48Ltlep1EWaaDrBQL8aTC8l1ayYvJoPXEyE6xUNsRxGTQcdv0Iu6YUUJDVwi7Wc8wj/2wGRPvbmlnUW0nd51TSl6aaOT5eRWIB9jWs42+WB86dKRb07EYLHgsHnb07eDpzU+zo28HJp2JktQSTHoTcTnO9NzpnJ13NqVppYfdfzQZpT3UTkJJUJJagk46skDu27ve5/vzHidJCFW2oSZSQXEgJT1E+6vIdJqZXpKDL6wjaJvL1vCrQ7bXSTq8Ni/9sX4iyQgp5hTsBjs6SUdzsJlCVyG3V9+OzWCjpq+Gr436GioqJp3pgO/n2r5a7pr/TbrD3SRVrSRNp6RQkTKankg/7eFmdDoFyRBARZvMqjytnLgcZ09gD7IqY4+dTVdHMXKoHJAGS9kAbphYwC+vqEInQV1fPRE5yOMbHqcp0MTFRRdjNVh5pe4V7hpzF22hNmRV5tz8c5mzYw4tgRZMehPRZJRV7auGjNtqsGI1WOmNan0ORmeM5srSKwknw2zs2siswlnIioyKyvuN7zO/af7gezA6czRuixujzojH4uFr1V/DaXJ+4r+fqqrc/cJ6Ftd2MbUknd29QXbG3kZvbkOV7RjTViDp9p/wSyLRPw56rqQ6N5NLq7Op6wjyj5W7Aa3U759fnXTaZ5He+fxa3tCYeDoAACAASURBVN7cjl4nMbXEw6r6XuLyvpP9WVVeLqjM4urxeZ+4r703WArctgOaUkbiMjMfWURLf4TR+anEkwrb2/xcPjqHR28YO/g8f9zP+o719EZ7cZlc5DhycBgd9ER7SCgJVrev5oPGD+iKdHF5yeXcOeZOfDEfBp1hMFC2/3j+OK+WUFzmfy6ooDsYY2dXEK/TMpgV2dAdoqY9gEEn8cRN4/nuvzcwOj+Vx24c+7lO6f54D6zPOlmR0es+vbvUTT1hvv3sCqKqxPbuCAB/vrCQy8qPcOrL0zDYAHDJo0to7osQiiW5dkIeD12lNaZcXNtFuz/KdROOPGPtYKIJmU3NPra1+shKsQye4zX2hNje5mdBTRfb2/xMLnYzzG1nY/OBkxfsT6+TUFR1MJNiRkUGM8ozePCt7ST3uxl6VnkGeWlW3t3STprNyJ7eCF+ZVsgPLx5xXK9nf/3hOP+3pIGGbi1r+/xK75Brpo8TwQZBEA5LVlS2tPjITbPise+7CIgmZN7e3EZvKE5OqpV4UiHdYWZaqQdZUXlycf1g93Gn2UB5lpOyTAfRhExCVgfTvFRVJd1h5vlVTfSGtPTmymwXo/NTeXNTK4FocsgUtnvtv8ygkwZP+ixGHW67iW+dV0ZltosdHQG+8rc1B8yQstfeO3ZNPWEkCSQJuoNxFu3owmU10BmIsaXFhz+iXeCcUejm3gsqsJr2/bJv6A7x6Pw6/rO+Zci+DToJl9VIms1IuddJKC5jM+pp7AmRlWJhc7OPnoHXPDzLSXVeCglZJRDVjpVqM3HLtEIqvE46AzHqu0KoqFTlpJBmMxKIJXl/awfDs5y8tbmNpXXd/PaaaoZ5bLy8thl/JMGMikzSHWZq2v28tamNFKuRpKIyMjeFa47gpPtg+kJxHl+4kzH5aZw3IpNVDb387r0a0h1m0mwmzhuRyXnDvUPeo887VVXpinShqir1vnoWNy8m1ZxKZ7iTN3a9QVQ++OcTIMOawbTcaeglPUualxCRI6Rb02nwNQBwUeFl3FX9HYalZeKL+Xin4R0afA3odXqSSpK36t/CH/cDMDZzLNeWX8umrk3U9NZQnVGNhITVaKUktYRYMsbCPQuJJBIsa1sEgNdShD+sI65rRlbjIO37v6iqEnolDUXfS6J/HJcWXsPYkgQRtZeLCi+mpdvCqvoeNjb3ML3UyxfOKKC5L8Ta5lYWbQ+SatP69ViNer53YQXZKVa2tfqpafezoyOAP5LEbNARiCaZu7GVuKygszQh6aNYPatQTU1Iujg62UOmuYj2XjOxmI1M7x5cjgCJuI0WXwAlkk+B9AXG5buZVOzmkupsjDqt4e7jC3fx7PJG0h1mXBYDgViS311TjdtuwqjX8f7WDsKJJHpJ4vIxOUPKCPanqApL96xi/tYAbX0yNYGVhE2rqPSMxKHP5Jxhk7isYhoW47673wlZwajXEYolCUSTZDiNxOQYkiRhNQzNlNrdE2LuxlaW7+ohNNCDaWrpvoaQa3f3sbK+hzZfhH+ubAK0oOjeXkbfm1XB1ePyaAhspzPaxOiM0USSEd5peIdntj4DSKBKyJE84n1TOL/gAqaWerl0VPZnojTBF07w0to9dAZivLquBVVVeezGcRRn2Pney5tYWd+DrKhU56WQm2qlKicFg06iKN1OSaaDwoEpv0OxJPe8uIH3tnbgtpt45LrRzKjIHDzO/O0dfPW5NZgNerY9cCF6ncQf59Xyxw83UFrQimpfC7oI3ck6FPXQdzYlJMZ7x5NmSeOD3R8MWT41dyo2g40Of4TaVvAFbChJF3pzJ3rHdiRdHKQkKGbibTdS6BiOUa/jugl5TC/LoDTTwdNLG/j129txWQzcOq2ImZVeVuzqQa+TKPDYGFeQRor1yFO5k0oSg+7oMjdUVeW1DS2MzEmhzOuk0x/ltQ0tLKrtYnReKt+bVXHMQaz+aD9ffvfL3DbqNi4ruQyAYCxJVyDG0p3deJ1m4rLCt+ds4Poz8rmkOpsfv7aFi0dmc+8F5adF8ExVVZJqkuZAMzv6duC1ecmwZlDv07LEeqO96CU9WfYsNnVt4r3G94jKUUZ6RvK/F/wvDtPJb5b67TnreX1DK1UZVrZ2RShNs/DSNWWkWY/ws3CaBhv+8uFO/rWqiVlVWdx9bukp+Y7716omfv32dvQ6ieq8FCJxmUyXmZ9fVkVNe4DdvWGmlnjoCsRYUteFxaDnvBFeFtV28YcPaonLChVeJ3edW0qK1Uh3IMYl1dlYjPrByQKuf2oF/kiSt7995pBjh2LJY85S/9YL63lzUytuu4nuYJxLqrP5y40HZtjtJYINgiCcFIqi8ubmNkoy7FRmuz7xF3tvKE5dR4A8t43cgXKEDn+U5r4II7Kd2EwGQrEkm1t8rN3dRyCapNzrQFW1GVda+iNYjHrkgTtSvkgCo14iIat47CYeu3EcEwrT0EkSX3lmNct2djOh0M0LX5t8whphvr6hhRW7eji7PINoUqauI8iuriA7O4PoJAm9TqLNF2V0firdgRjt/ig/vHgEbf0RVjf2sr0tgNmgGzwB3NMXJhBNHlAXCFojUWCwNOhI2Ux6JCAUl7lmfB7nV3rJTbWSn2YbUqYSjCVp6YuQZjeSbjfjjybY0R6gMxDjT/Pr2NkZBMBpMYAKgViSUbkpbGn1oapax+Ysl4XecJxyr4MfX1JJTqqV//f+Duo6gtR2BPjaWcXcOq1oSET8tfUtPPbhzsFmpV+eWsgXJw074c1KfTEfNqMNo057ryNxGUliMB3cH/ezpHkJm7o2kePIodBViFFvpDvSTZGriCx7Fm6Lm7ZQG92Rboa7h6OoCgklQVJJ0uhvpMnfREyOsaBpAbV9tfRE9zWC1Uk6FFVBJ+mYWTCTq8uuJtOWSVJN0hPpoS/WRyQZwW12Mz1vOma99u+99/eyJEm0h9p5etOzzKl9HiXuJsuWg19tJCoHcRqdKCjIiswE7xlcXHwRfdE+Hl3/Z2JyFKvBSrY9m3pf/QHvjdviBsVCR2c2WcpsFnz7cnQ6nXYRjESDr4FFzYto71f4qGMlOakWUqnm/ZXF9IZk9DqJ8QVpdASi7B4I4mU6zXT4D/ysptqMeOwm9vRFsBh0DPPYqe0IEEsq6CSt5E37WeKcigzKvE4qvE5mVnpRVZXmvgh94TgZTjPZKVaCsSRPLNzFYx/uHDzGjIoMfnF5FQVu20G/h1RVZd72Tl5Z20xtZ2DITEx7mQ06kgNTpn1x0jBcViMtfRFSbUb8kQSKCm67kea+CO9saSc31UpLf+SA/UgSFHnsVOWmIAFvb25Dp5NIygqKCnaTngmFbmaPzWFMfhpF6VpvoqSsMPXhBXQGYozIduGPJGj1RZhY6KYqJ4Wadv9g1pUkQXmmk398dSJWk55YUqE7GDtkkARgTfsaVratJJqM8nrt+/iS2vThTpOT+864j9mls4FP/67qsUrK2mdm/+zD/nCcB+ZuoyMQZU1j32AQZi+jXqUkw0WHP4ovkuCWaUUs29lNTbufEcV7GJbTj8Mqs6l9N62x9ZS48zkjazzbe7cTTkTY1b8LFQWSTuSECyVSjBIcQTJpYVwx3DQlnyRBJNmBQbLg1OcRidhJKgrb/UtxOALE40beq19Er1yLTe+kJ9aBpNvXXFovGbAqheQ5C0ix2NjYu5io4ud/JvwPMTlGZ7iTG4ffSHGqVvKyvc3PQ+/UsLi264D3KDfVyjvfmU5Y7sUXDfK39XOJSLsZ4amgP9bPVWVXUeAqIC7Hqe2r5Zvzv4miKnisHio9lfxk8k9Isxx4d1tWZOY1zSPFlMLCjak8sUibBjXLZaEvHB9ILVcBaTC9OykrPLm4nvHD0g5Z9qGoCnV9dThMDnIduTyw4gFeqn0JgHGWu6nv6aE/1kewezyqfPiL8G+dW8ot04pwWY3H9btFVVXaQ+2kWdIw683Mb5o/mH3UHGhGkiQKXYWMzhjNkpYlLNyzkL5oH3ajHYPOwPym+fTH+g+5f4POgKIqg0Grs/POJt+Zzz+3/xO3xc2swlkUugox6Az0RftQVAWDzsBZeWdR4a445te1/+ub9Ov5TEo38ufLyo5tJ6dpsOF0kZQVpIHzw6Px7LIG/raskYeuGsW00kPPQvTYgjp+/34tK39wHh/u6GRDUz8uq4GnlzbgdVmoyknh62cXI8EBZSOqqrKuqY+dnUHOKs/AbTfx7pZ27n1xIzdNHsZPLq3kt+/W8OTiet68ezojc1MOOgYRbBAE4YTZ/0Lo0+aP+9Ghw26009Ad4u3NbfijSbJTLJxf6T2gTKE7GMNuMpzWd9/7QnFeWNNENC6TlWJlmMdGLCnT0B3mjQ0t9ITi/PDiEYRiSWwmAxOL3Ly7tZ3a9gB6nURxhn0wAGM3G7ig0ovFqCcpKzz8Tg3/WLl7SE2hToJ0h5msFAs7O4OE41qqdbpDCzbsPTn3usx8b9ZwvC7zYHbLV6cXM7HITU27nxW7eqhpC+CLJIglZdY19eOL7DthdttNeOwm6jqD5KVZsZn0FHrs+CIJVjX0UpntYvwwbYqqj3b3MSLbxdXjcinOsFOW6cRlNRKMJYkltH17HCaGZznJcJiHpMvGkwqRhIzNpH0+9gYQXt/1OrV9WhNZAzZUVUcybodwFflZ/ZhNCp3RegIJ/2H/fQw6w2D9vkVvOWR2gtPo5JyCcxjuHo7FYMEgGbig8AL0kh6z3nzM/186/FFeXLOHPy5/A0fWIhJJBVW2cl72F3ns6tnIisofPqjl/5bW881zSjm7PJP7/rOChv5Wrqgci4SeBU2LmDgsm4cvuZSle9azqzPC5SPO4JH3d/H+1nY2/XzWEZ8EqarKrq4QL69tZnVDD3azgWvG5zGjPBOX1cC87Z1sbfUxzGOjKN3B8CznYHBna6uPP8/fSSwpk2Y3ce7wTCYWuY+5182WFh97esPkpFqpzks5qve4PxxnZb3WyKsvHOeMQjdF6Xb6QnHufWkjC2o6B+dpV1WVTKcFvU6iMxAlIavcdU4J35s1nEA0gcmgo75LayT27zV7UBSVlv4o29v8g2n4k4vd6CUJm0lPZyDGB9s6aPNpn6XzK70Eo0nafBEae8L85cZxXFKdjS+S4Dfv1rC+qZ/tbX48dhNXjcvl7vPKcJqPr7eNoiosaV7C1p6tvNPwDr6YD6/dS1e4i75YH7+Y+gvGZo4l15F71He6TyZZkdnUvYk9gT0UugoZmT5ySMlJS7CFubvmEklG2O1r5aOO1cTlCFXuifSGwjSE12JVijEbjNgtMZwWM26Lh00ddQRkrRZaVYyoSQd2KZ/cjDBdkS7MejMFzgKGu4dzUdFFVHmq2N4W5O3NbciqSocvytxNbXgHAsStvkNnMX1cRZaDOV+bTG1nHxlpYdIsaUMu8N/Y9QY/WvqjIdsUOAt49YpXBwOUoNWXL9rRRZrdSGmGkz19Ye56YQUZFU8SYb8eIqoeJJlDuab8GkLx0MBFdSqXl1xCTI5R01tDijmFuBynrq+OzojWeDrWPYN8+3CSgRJ290aZMSrJis730Fsb0PVfRNKwB5O9mXjMQcI/CjlUQba3ldSchYzNKWKCdyKLG2roTm6lOdhET7QHnaTjCxVfYE7NHMrSymgONBNO7pt1yyQ5qXSPptQ5BntiPNeNrWRFfTfpDjNjC9L46esbeLt2A0osg9yUFKaXpnPeiEzG5Kfy3Na/81HXEuwmI8PdI7BRwI7+rcRopznYTE+kh6SSRFZlTHoTiqoMBmE/3mh2f1n2LNpDWgCvOKWYcDJMNBmlOqOa6vRqUs2pjEwfSYO/gYScwGvzUpVeNTg9b3OgGZ1OR64jF9B6srzT8A7berYNee2D74HOxM+n/nww4+NYLa7t4kt/W82DUzO4acKxZUKKYMOptb6pjysfX47dpCcU3/d/2+sy0xWIsX/S8GM3juXS6hzqOgI88kEtG/f0H/T7aniWk+dunYjXZcEfTTD94QVMLPLwf1/W4gnRhMxHjX2cUZSG2aAXwQbh9CMr8uAXuXBwTf4mchw5J+VET1ZkNndv1rqZW7Vu5hs6N7ChawN1fXVcXnI5vdFeQgnt7p/daEdWZQLxAM9ufZbeSC9mgxm9pMdpctId6WaEewSXl1xOS7CFcDKMy+RCQmK4ezi1fbV0R7ox6Aw0+htJKAlcJhe7+nfRHenGZrRRklJCSWoJC5oWoJN0ZDuykRXtmLIq0xHqIJDYV+tm1BlxmVxk2bMw6oyM8IzgnvH3YDEc/YVLQk6wvXc7y1uXs7N/J82BZkx6E1aDFa/NS4W7gusrrj8hd/s+7buGgWiCxu4wLf1h9vRG6I9oTVTb/TGyXGamlabTF9IuvFKsRi6pzsZm0lOZ4zqqGUp6Q3H+s76FWFJmVG4KZ5ZpjYo+rOnkycW7CMaShOMyvaE48aTC/HvPxmmV2di1kYV1jczd4KOjx4maSAP2v4iSkfRRJIMPyRBCpw+TZnEwMtdNOOmnpquduHkDelvDkPFY5CLigRFEEgns1ih2s0TStIuQ2oKSdKLE3ejkVM7Nu4QsUxVrmzqQDR2kp8TIdeQhGbtZ0zcXtyWF2eXnk2pJZXX7atwWNw6j1lh2mGsYw1zDMOvNuEyuY/rsHc62Vj9feGoFgWiSFKuRj348k5q2AL96extrGvt45LrRrGnsHUyn38ugk7Aa9UgSRJMKxel2agaCU6qqDjnx+KTUyM8jRVFp6AkNZl/FEspgTb8vksAfSZDvPrL+K4eqJZcVlW2tfp5Z3sC8bR0UpttJd5hJKipP3Tz+gGaMPcEYTovxsDWzx2pB0wJ+verXlKaV4rV5+bDpQ/pi2hRxpamlfGfcd5iQNQF/zM+8pnksa1mGy+Qiw5bBGVlnkGpOZVnrMmYWzDzqO62RZIRX614l35nPlJwpbO7azLymeSxoWkBSSTLCMwJFVQjGg0zMnshLO14akj1k0BmYljONPGcevZFe3t/9PoqqNdl0mpxMzJ6I3Wjnw6YP8cV8VGdU0xnuJNOWic1oozfaSzQZpSS1hJGeMZzpvZw9Pdr346Ri9wGzPx3O3I2tvLS2mVSrkTH5qbisRlKtRiIJmXA8OVhKk2ozMaEwjXZfFI/dTL7betjAkaqqbOneQoGrAKPOyMaujdz+we14bV7MejNROUpcjqOikmHNoDilmOLUYiQkntz4vygkiXXNhEQG+SmZdHbmEFMiVBb2k3Qswpwso7Y9RjSuJ90wgkxLHrGkQmu0BtnzL3SmHiTVSorRjdOqR6dakJIZ7OqIoE9ZN2SsZr2F2AEBWQk5moXO4EMyhNFhQOHAXlBS0sOErLGUOkezrmcpO/yryLRm8o8LX2ZxbRc/nLuYX142jjFFZp7e/DS1fbXs9u9GL+mZmDWRDFsGEhImvYnV7R+x29+ADgMmNYuo0oOi6kE1oDP2I0ezMelMyMZWkLQgudtQhNfuRifpKU4tRocJXzRImz9AqScfkzHGf+r/Rbo0gS8Mv5ox+R6ybFnodXpWta1izvZXSTVl8KeZv8ZuPHAmtWOlqirdkW78cT/Z9mwsBgu90V7uW3wfa9rX4LV5yXPmYTPY+OKILzIxeyJGnXHIuUZcjrOhcwMtwRYSSoJFzYuo6a1htGciC1YXk2Eq57VZXuxpxziziQg2nFJJWaHqZ+8RSyrMHOHlsRvHsrK+hxHZrsGMw798uJOX1u4hmlAYke1iZ2cAu9nA1BIPZ5VlUJnjYvmuHpp6w5w3PJMZFZlDbkI8Or+ORz6oZVRuCuOHpbGotouG7hBuu4krx+by08uqRLBBOLnCiTC+mG/wy74v1seS5iV0hjsHu2sbdUbsRjvberaxuHkx/rifm0bcRFV6Fdn2bMrSytjSvYV1Hesw681YDVauKrsKX8zH4xsfx2Px4La6yXXkMjVn6kl7Lf64n85Qp/bL5gibrB2NYDzItp5tbO/dzge7PyDbnk0wEaS+vx6T3oTD6MCgM7ChawNmvRmvzUuOI4fytHIKnAVcVnLZUc2rrqgKEtLgyUxMjvGNed9gTfuawedkWDPoihyYgnkwpamlTM2ZiqzKbO7aTG+0lyk5U1jRuoLmYPMht3OZXISTYZxGJ/mufILxIHqdnkp3JbIqs6lrE02BJiZlTcJldtEW/P/t3Xl03NV5//H3nRlpRhqN9tVabMuyvBtvWAaMbUJIICyBQErpj5BmIy0haZomIaQlzSH9NSshJE1Pwy9Lm0LIwpaQGEIISzB4wRt2bFmWLS/arNEujaTZ7++PkScyls0mS7b0eZ3jc6yZ70h3bD/+zn3uc5/bSqozlczUTFwOF+kp6cmO99F4lGA0yCttr+BN8RKNR9nh38H1s6/n9qW3k+ZKS3TED3bhS/GRn56fLKUHaA208u1t32Z3x25SHCkcGziWXLEuzShleuZ0YvEYgUiAtsE2OoY6qMyqZG35WsoyyghGg4kPtM4UyjLKuLjs4hP+rVhrCUQC7OvaR3ewm13tu/hT85/oCnbRF+rD7XRzzaxrsFgK0wu5qvIqynxlROIRsJDiPDPHJY0UiUV4rvE5nm98Hv+gn0x3JmW+MqLxaHKlJcudRXVONQvzF+J2uvEP+gnHwpRmlNI+1E5+Wv6oMbLDv4N9Xfu4tOJSstxZbGrZxFOHn2IoOoS10BPqpWWgiY6hjhO6/gN4XT6MTSeFDKqyFrC3908MxE5dggrgwI178EKi4VwiYTezcitIj88ibi23XzKbi6rykv/+u4b6GAg68PeFuefp/Wxq6MRhEnu6m7qHGIqcvOLnSXEwLSuNvmCUYCTG0opsSrPTKM7y4Bz+vmmpTsKxOGkpTtJSEo1DV1XmnrKkvy+Y6FNwqg7/dcf6ef9/vUwkZllUmsUX3jOXZRWJ1U5/f5BrvvdSskfJDcvL+OYNi9nZ2MOvd7bw4YtmUpGX+D8iPpxZ+P2eY+xt7cNhDEsqstl2uJulFdlcVJU/bqcMyLnh5eaX2dS6iTJfGT/+849pDpzYp2ZG5gyO9h8dtVfBwryFfHr5p1lZvPJ1J9BdwS6+/srXefLQkyc9X5Vdxeyc2bzc8jIZKRlkpGRQ111HZVYl18y6hotKL6Kuq47nG59nV/suhqKJ7SzXzb6OD8z/wMnNF22caDw6aRY4Hqx9kFfbX6U/3M+G5g0sLljMvNx57PTvpK67LnldVmoWK3Nu4iOLb2ZecSYOh+Fge4CHNh/lj/sSDZUBrl0yjRn5Xh7f0Uyhz0NmWgr5Gal0BoaIEaZv0MnWI93J7+t0GAp8KVy1+iA3LbqM5kAzn/jjJwD4+sVfpzqnmmxPNvXd9czPm48vJZMth/3s6n2GnmgzHmcahbH3cLCrnZgdonFoF7vqZtDW85cEd0luiLSUFBraEveYFKdhwx3vOOG49APdB1h/aD1PHnqSwehgsrFujjuHm+ffzNH+o2xq2URVdjU9AzGa+9uZnbmUGSnvot4foD8UYuGMPl5o2M+u/TOw9nWqhZwDOK2XWByqizKomZnHQDjK5oau5JaqBdMyWTAtk9beIPNKMvm7tbPIPQO9AiLxCA/XPcwfGjbROtBGT7SRQKQPh00nKzWX7kgT1TnVzMyaya72XbQOtCZfm+5Kp6bkAp4/ugFrQtww6xbuKrgah0/JhnPVXY//mcd2NPO/H1nJ0orRG3uGojG+/mQd9f5+fB4Xd14x7w0nzgOhKP++vpbdTb3sbu6lNDuNC2bl0R+M8Eytn4avXnnuJhsyZ2Xa515+juVFyyd6KFNOJBbhmaPPEI6FWVK4hApfRfLDg7WWJw89yXb/dloHWtnUsolwPJzco3w6GSkZLC1cykBkgO3+7ae99vrZ13Oo99BJ15V4S/iXVf9CVXYVD9Q+QIWvAm+Kl2kZ06jKrnpD3bxHOr5P8HeHfsev6n5FIBJInIeemijNPX42/IXTLuTWxbeS6kzFWsvP637Or/b/istnXI7b6eb62dcnm/nEbZznGp+jLKMMl8PFc43P8cDeB05YlSlKLyLVmYrDOChMLyTbnc1gdJDByCBO42RaxjQisQh7u/ZypC/RNTzNlcbq0tXcNPcmzi8+H2stncFOUhwpZLmzGIwMsqF5A94UL5taN7H+0HrCsTA1JTWU+8oJRoM8UPsAn13xWc4rOI+tbVvZ0rqFq2ddzerS1QQiAZ458gxLC5dS4i2hN9xLJBYh052ZbGJ0/M925JaKuI3z5KEnmZYxjfMKzsNgaB9qZ3PrZlaVrKIgvYBIPIIDx6gr+9ZaQrHQW14d/tqWr/Fg7YOjPudyuJiROQOncSb3WcZsjLXla4nFY2S5s1hRtILVZauTE+2R4/rZvp+x/tB69nbuPWlyDIlyxjVlayj3ldM60MqWY1uSnelhuBnYtAsp95UzGE0cj5fuSifVmZrcy+lL9RGNRynNKOVDCz9Eb6iX7mA3a8rWsKRwyajvK27jbGvbRn13PQAZqRnMyZlDdU41cRtnZ/tONjRvSI7hzx1/Zn7efLLd2TxQ+wBtg23kenIp8ZZwoOcAoViIVEcq4Xj4pD+/4vRi2gbbiMQjeFO8DEQGcBkXC/IXMC93Ho2BRloDrYRioZMmJ5DoEZDrySVu48RsjAV5CyhKL6KmpIaM1AyC0SBH+o6wt3Mvezv30tDbgMM4qMyq5OKyi5nmnUZFZgVZqVm0DLSQ7konLy2PHHcOvlQfTsdfmiW9GfG4JRq3yRXj42Xxbb0h+kMRDrYPsKuxJ9kvBKDe309T9xA9g5HTfWves6iYv1pRjsMYXqxvp6l7iGjc0tg1mOwoX5qTljz9pmZmLl0DYZ6ra2f7kW7SUp08dtuFo55i0jsUYU9LLylOB8sqcsa834UIJJLTfzjyB35/6PdMz5ye2EKQv4DDvYfxpnip7aqlfbCdVGcqLYEWfln3S/xDfmpKarh9ye0YY2jub6YqpwqPQ2Iv2AAAG9BJREFU00MoFuI3B3/DI/WP0B9OVKp9aOGHWJi3kB3+HWS5s7hi5hWU+8pPuof7B/3keHJOSBxPddZa6nvqqcyqxOVwMRAZYFPrJtaUrqE33Isv1XfCVovXOtI5gLUwI//0K/HWWr71dB0bDnRyy6rpXDqv8KTTL/Z07MGX6qMis+ItvZeugTCbGjopzvLQ0R/i/j814PO4Ek0u01NYMC2xonqmdA2E2dPSS9wmtlmlp7pIS3Eyp9jHtiPdDEWilOekM7ckk0e2NfG73a3sONpNntfN4rIs1lQXcMAf4A972+gPRqjIS2dfaz/RuKW6KINL5xVRXZTBzPwMCnxuUpyGggx3cqtWe3+IvmCUXU09RGOW3qEIN9VUJI9aHXl/a+8PsfVwFz/deISNDcOfKU2EzJwG0rL30s8hFuWdR9B20h9rpdhbyIcX/S3z8+bjNE7SXF7ueqyeX+86xGWrN7OpfT01mYu4ovRSnva/iNeZzu2zbqEy4w3+XSrZMKX0ByN4U13JfjnBSIy0VNe5m2xIm5lmq75cRVV2Fd4UL6UZpXzu/M+Rn3bqRhnyxkTjUQLhANmexDmuoViIJw4+gcVyXsF5/Gj3j1h/aH3y+llZswAYiA5QnF7MzvadyfPBLym/hMrsStoG2hLnp6d4WZS/iHl580h1pBK1UUKxEB2DHVRkVuByuLDW4h/00xnsZEvrFn745x/yqaWf4p3T30ncxvnq5q/yXONzxGyM25fczoriFZR4S/jalq/xx6N/PO17K/GWcFXlVSwrWsbigsUnTCCPr6jUddVxqO8QnUOdPFL/SLIj8IriFVxWcRkvt7xMbVctbqeb8wrOoynQxLa2bQAUphUSjAWT3d+Py0/LZ23ZWvLS8tjp38mWY1tOeL46p5p3z3g3C/IWUOwtpjKr8g1NjuI2TudQJ02BJh4/8DjPHn2WnlAPpRmldAW7GIoO4TIuLptxGe2D7WxtS1QDuYyLlSUr6Q310h/upyXQQtRGqcqu4tFrHj0rOjWPpT93/JlX21+lL9zHQHiAhQULCYQD1HXV0RhoxGBwGAcep4dPL/s05Zlv7tijaDxKx1AHHmciIWKxbGjewLe2fouuYFdyor60cClry9YyJ3cOuZ5cyn3l+FJ9ye/T0NvAjMwZOIyDXe272OHfQUughdquWnb4d5z0c13GRaY7k7m5c7l61tUEwgH8g36eaHgiuUd0pDxPHuF4mP5wP06TmCDHbZzpmdM53HcY+EvC7qJpFyUn6sFYEI/TQzSeiNcj/UfwD/h5tf1V9nTuId2VzpLCJTT2NyYbJR7pO8L+7v1E4hEunJborj4rexYrilbwx6N/JMeTQ7mvnEsrLp00q4rHNXUP4nI4yMtIJRCMEozGcBpDJG75xSuN3P+ng8kjYlOchhl5XlxOB6kuB2tn5xOJW5q7h6ht7aOhYyB58kpZThpLyrP5xCVVzCt5iytNIhNgQ/MG/v6Zvz/tNQ7j4F3T38WSwiVUZlWyqmTVpLsXydQwWoJ75CLM7qZent57jK2Hu9lyuOukE7eKMz3kelM50jlwwl774zLcLgp9bgbDMToCicaxqS4Hu5p6iMQs+Rmp3LauipcOdJDhcfGxiyupKszgPfe9SEPHX5rgOgwsKs0iHLMEQhG6ByIEQlE+9+453LZuFj/d+1O+tfVbAJR6iugId1PpreCXq77/xv4gpniyYSAyQLpr9ObE0XjiJJJSX2ly/nGk7wjZ7uxk5awv1UeuJzdZ+exL9eFyuHA73XhcHtaUrRnTrTlnwjnds2HZ8mX21h/fyt7OvXQOdbK3cy8ZqRl4U7z0h/tZUbSCZUXLmJk1k8cPPE5DTwOlvlLSXenEbIzOoU6urLyS66quG5fS5Inw1KFEeXJfuI/HDzzOXavuYlnRqffg7mrfxcstL/PrA7+mKdDE3Ny5XFJ+CVuObUlOpo+7ed7N3FB9AxtbNvJEwxPkenLxpfio667jsumXcduS28Zsq8Fo/2lH4hGGokMnrTY39Texr2sf/kE/bqeb9JR05uTMoSnQxO6O3Wxp3cLO9p3JKoscdw55aXmsK1/HTv/O5GT8uGWFy7i++npqimso8hadcoyP1j/Kfdvvo6akBm+Kl6WFS1lXvg5rLXVdddy77d7kxKsgrYArZ12JAwelvlIGI4NcWXnlmCTKBiIDPLL/EXa270zu16vtrOXZxmeJxWPcsuAWzi86nzm5c8hy/6VzbDAaZF/XPoq9xSeVmcpb1xfuIxgNUpheSDAafFt797uCXQTCAbLcWVhreajuIYLRRGLr+cbn6RjqSF67tHBpssLFYRw09DTwUstLtA204XF5OL/4fNaUrSHNlUYoFiLNlUb7YDsuh4ssd9aYxm7URrXi+Bq9QxH2tfZhgTlFvtMeuxWNxXm1qZeSLA/TstNOeZ3I2c5ay/7u/fgHEw0Ee0I9NPY3Uu5LJHfn5Caqr0Smkv5ghLa+ELWtfQyGEz2Mth7uZjAcZXqel7KcNJq6h7hsfhHFWR5ae4I8uPkITofB5TBkp6dywB/AYvGmunjnvCKuOq9k1N5KhzoG2Hq4i4WlWRxsD/DSgQ4a2gfweVLIcDvJ8CRef/y4V2stT238CdOyp7M4ay4PHH2Mb+z/AY9e8ANmZ8x4/Td3liYbuoJdNPY3Mj93Pvt79lPqLU0usB5vkvtq+6u0DrRydeXVLCtaxr6ufRwbOMaujl20BFoIxUKEYiEcOCj2FpOXlsfBnoP0hfvI8+TRFEjMR/I8eckq39KMUhYXLKY/3M8rx16hdaA1+Tns9aq/RzM3dy43z7uZIm8RNcU1yblSY38j92y9h46hDtaVr0ssKPUeTs4JHcbB1ZVXs6hgERuaN/Dw/ocJx8IsK1pGQVoBTx95mnAsjMvhom2wjRx3DpeUX8La8rVEYhGmZ05PViLH4jFCsRAuhyu5GD2yYerv3ve7czfZ8NqeDfXd9dy3/b7kvqwDPQeSJbsuh4tVJatoCbQQt3H6wn3JUubC9ELWla1jft58lhYuZW/XXgLhADfOuRFjDB1DHTy07yEOdB9gVvYsGnob8KX6KEovSk5kL5x24YRl39sG2njswGNsa9uGxbK/az8VmRXEbZzdHbtPuDbNlUZ+Wj5up5ulhUupyq7i2qpr8bg83PXSXfzm4G8AKMsoY03ZGja2buRw72FcDhdfWPkFakpq2OlPVC1cPuPyc+IIrNEMRgbZ3bGbnf6dHO0/yrGBY2xr24bb6ebm+TezvHA55b5ytvm3ccXMK05bavhmRONRHMZxRvo9yNQ2FB3iSN8R8jx5ZHuyNbkXERGRt2//fshIbAPuDvfy7hc/wKVFq/nqws+//mvPomRDJB4hGA3yi7pf8LPan9E+1J48VWp2zmw+svAjbGjewIbmDfSEenAaJ8aYk7bIpjpSqciswO104zAO9nTuwevyMhQbIis1i9KM0mQz9AX5C2gbaMOX6iMcC7Pdv53mQDNF6UVU51Rz4bQLOdBzgPy0fDwuDxW+CiyWYm8x+Wn59IX66A5143a6mZs7l3AsTNzGCcfC7PDv4Msbv5zsS5PqSOXaqmsxxvBc43MMRgaZnjmdPZ17kmM/Pv84nthYWrg0WTV7vKcZwDTvNArSC4jGo7QPtidPmDnuqsqruG3Jbezv3s93tn0nWRkLcEHJBcnEDcA3135z8iQbXstaS21XLYFwgGkZ0yjzlZ3wHMDGlo38tPan7PLvOqGbPcD0zOkUe4vZ3b6boegQFZkVHO07mszWjDzq5qa5N/G+2e9jTs6c10069AR72Ni6kfl585meOf0tvXdrLc82PssTB59gQ/MGQrHE+eV5njyWFi7FP+RnV/suakpq+Kfl/0SOJwf/oJ9H6x9NNsfb3LoZiyXXk0tRehG1XbVkubN4/L2Pn7DCHo6Ficajb6rx4LmoL9yHwZxQ0i4iIiIiMmWNSDYAfHv/D/nJkV9xWeHFfGD6dSzNXnDq105AsqEv3Mdj9Y8BJE/E6RzqZGvb1uQke3rmdN5R8Y7ktvHHDiSuT3GksLxoOVfPuprLZ1xOd7Cb7+74LqUZpVTnVJOfls/MrJknVAb3hhI9UEY2XD8Vay3heHjMFjH7w/30BHu4c8OdiaRHihdrLSXeEu6+6G7m582nqb+J3nAvMzNnkuZKIxgL8mLTi/zPnv/BP+Tnb+b+DTfOuZH0lHS6g90c6TvCwvyFJ5x4Z63lcN9hNrZs5OH6h5P9wCAx9yxML2QwOsidK+/kotKLThjjOb2NYixPo7DW8sqxV2gONFPmK+NAzwFebnmZ7mA3HpeHz5//eapzqukL9xGJRfC4PKS70hmMDvLNV77JI/WPAInzja+qvIqh2BA3zrmR/LR8XMbFZ1/4LKFYiHXl6/jO9u8kmx9BIpN0bdW1rC5dzbLCZckmggD7uvax9dhWCtILeOXYK9R21pLiTOFQ7yG6gl1ku7NZXrScf1z+j5R4SzDGJFc0mwPNFKYVnnKLSCQWYWPrRn578Lfs697HvNx5/Pvqfz9nqxVERERERGQMvSbZ0BnuYd0LNwLDTa7zlvHRmTexImfRya8dx2RDV7CLB/Y+wEP7HiIQCZz0fE1JDSuKVrCyeOVJW8rruuowxjAjc8Y52UPqrTTBHtlD5M3a3LqZI31HmJ0zm/l583E73accg5INY6Sht4Gd/p38+sCvT3mKQrY7m55QDxkpGdyz9h52d+zmP3b+B0CyjCfPk8ei/EVsaNlAiiMlWc4CJBvMFaYXckHJBSzKX8Q1VdeQ5tIeXhERERERGWOvSTYA/OjQL9jS/Sp5qdm81LGVrkgvf1V2Jf8y95MnTjhHJBustXxvx/fIcmdx3ezrTuq59nbUd9fzwac+SCAc4LLpl/GxxR8jKzWLUCyE0+GkOdB8Qk8DGT9KNowxay2tA634B/1s92/HP+jnwdoHWVa4jB+/+8dsPraZzNRMFuYvBOBw7+HkcU07/Du4e+PdtAy0UOGrYF35OpwOJxeXXsxAZICVxSuJ2igep+eczLqJiIiIiMg5ZJRkw0hDsSD31f+EBxsfZ0nWfO4970vkpg43mh6RbHi+8Xk++ewnk6+7svJK7lx55wlbEt6ItoE2trVtIxgLMhQdor67nkfqH6EgrYD7L7ufqpyqt/Y+5YxQsmEc7OnYQ0VmxRvqBRCLx3ii4QlWFK04oceEiIiIiIjIuHqdZAPAYCxIzbPvBWBORiXt4S7eUXAhd5bdQurcBcTiMT741Ac5NnCMe9fdyy1P3kLURslx51DsLWZR/iK+UPOF121uba3lM89/hmeOPnPC4xW+Cr6x5hssyD9N/wiZEKdLNpx8Xoq8JW/mH77T4eTaqmvP4GhERERERETGRrrTQ4pJIWIj1AUaAHi4eT3BUICvzv0+39z6TV5tf5W/XfC3LCpYxCPXPMLG1o3s7dxL+2A7v9z/S55oeIL3zX4fd5x/x0nbHbqD3dzxpzvY2b6ToegQNSU1fHzxx5mZNZOByAAVvgptkTgHKdkgIiIiIiIip/Xbi37ECx2baQ918dGZf82X9tzD+rYNLK37Jc8efRa3081HF30UgMrsSiqzK5OvferQUzy8/2EerH2Q91e/n8qsSn7b8Fvqu+v5+Hkf5+f7fs7G1o28v/r9zMubx3tnvTe5pXzkCXpyblGyQURERERERE5rWloRN5Vfk/z689V/x6H+I3xl01cA+GLNF0/Zn+HymZdTkF7A5qc20zbYxgtNL3DvtnsBeOrwU3QMdbC6dDVfuuBLZ/6NyLhRskFERERERETelEJPHg8vvof6QsOejj1cPvPy016f58kDoGOog5/V/oya4hpuXXwrd754J94UL59d8dnxGLaMo3FPNhhjLgfuA5zAD621XxvvMYiIiIiIiMjbY4yhOqea6pzq1732+HaI7W3baRts4xNLPsHKkpU8fcPTWCwuh9bBJxvHeP4wY4wT+D5wBTAfuMkYM388xyAiIiIiIiLjy5vixe1009jfCECOJwdINM9XomFyGtdkA7ASOGCtbbDWhoGfA+8d5zGIiIiIiIjIODLGkOfJoznQDIDH5ZngEcmZNt7JhlKgccTXTcOPncAYc6sxZqsxZmt7e/u4DU5ERERERETOjLy0PI4NHAPA41SyYbIb72TDaIej2pMesPZ+a+0Ka+2KgoKCcRiWiIiIiIiInEl5aXnEbAwAt9M9waORM228kw1NQPmIr8uAlnEeg4iIiIiIiIyz4ydSgLZRTAXjnWx4BZhtjJlpjEkF/hr4zTiPQURERERERMZZXtqIZIO2UUx649r201obNcbcDvyexNGXP7bW7hnPMYiIiIiIiMj4O378JYDbpW0Uk924nzFirV0PrB/vnysiIiIiIiIT54RtFKpsmPTGexuFiIiIiIiITEEnbKNQz4ZJT8kGEREREREROeOOb6NIcaTgMJqKTnb6GxYREREREZEz7vg2Cm2hmBqUbBAREREREZEzzpvixe10awvFFDHuDSJFRERERERk6jHGkJ+Wj8FM9FBkHKiyQURERERERMZFnidPlQ1ThCobREREREREZFwsLlhMd6h7ooch40DJBhERERERERkXd6y8Y6KHIONE2yhEREREREREZEwp2SAiIiIiIiIiY0rJBhEREREREREZU0o2iIiIiIiIiMiYUrJBRERERERERMaUkg0iIiIiIiIiMqaUbBARERERERGRMaVkg4iIiIiIiIiMKSUbRERERERERGRMKdkgIiIiIiIiImPKWGsnegynZYxpB45M4BDygY4J/PkiZzPFh8joFBsio1NsiIxOsSHnqunW2oLRnjjrkw0TzRiz1Vq7YqLHIXI2UnyIjE6xITI6xYbI6BQbMhlpG4WIiIiIiIiIjCklG0RERERERERkTCnZ8Prun+gBiJzF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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "fig, (ax1, ax2, ax3, ax4) = plt.subplots(4, 1, figsize=(18, 25))\n", "label = 'Covid-19 western outbreak'\n", "prices_T.plot(title='Inflation rates in bps', ax=ax1) \n", "ax1.axvspan(date(2020, 2, 27), date(2020, 3, 26), color='red', alpha=0.1, label=label)\n", "ax1.legend(loc='upper left')\n", "\n", "drivers_df[['SPX', 'SX5E']].plot(title='Equity prices in bps', ax=ax2)\n", "ax2.axvspan(date(2020, 2, 27), date(2020, 3, 26), color='red', alpha=0.1, label=label)\n", "ax2.xaxis.label.set_visible(False)\n", "ax2.legend(loc='upper left')\n", "\n", "rates = drivers_df[['EUR_10y', 'EUR_30y', 'GBP_10y', 'GBP_30y', 'USD_10y', 'USD_30y']] * 1e4\n", "rates.plot(title='Rates prices in bps', ax=ax3)\n", "ax3.axvspan(date(2020, 2, 27), date(2020, 3, 26), color='red', alpha=0.1, label=label)\n", "ax3.xaxis.label.set_visible(False)\n", "ax3.legend()\n", "\n", "drivers_df[['CDX_IG_5y', 'Xover_5y', 'WTI']].plot(title='Credit and Oil prices in bps', ax=ax4)\n", "ax4.axvspan(date(2020, 2, 27), date(2020, 3, 26), color='red', alpha=0.1, label=label)\n", "ax4.xaxis.label.set_visible(False)\n", "ax4.legend()\n", "\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4 - Carry\n", "\n", "Let's compute 3 months carry and vol adjusted carry using 3m realised volatility. Here we assume linearity of the roll over the year thus compute 3m roll based on 1y roll." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "infla_port.resolve()\n", "base_rate = infla_port.to_frame()['fixed_rate']\n", "\n", "roll_port = Portfolio(map(lambda x: create_inflation_swap(roll_forward_1y(x)), trades_info), name='inflation swaps roll fwd')\n", "roll_port.resolve()\n", "fwd_rate = roll_port.to_frame()['fixed_rate']" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "carry_df = pd.concat([pd.DataFrame(base_rate.values,columns=['Base']), pd.DataFrame(fwd_rate.values,columns=['Roll Fwd'])], axis=1)\n", "carry_df['1y Carry'] = negate_carry(infla_port, carry_df['Roll Fwd'] - carry_df['Base'])\n", "carry_df['3m Carry (bps)'] = carry_df['1y Carry'] * 1e4 / 4\n", "carry_df.index = [instr.name for instr in infla_port.instruments]\n", "carry_df = carry_df.groupby(level=0, sort=False).sum()\n", "\n", "back_3m = date(today.year, today.month - 3, today.day)\n", "infla_diff = diff(prices_T[prices_T.index > back_3m])\n", "infla_vol = infla_diff.apply(lambda x: std(x))\n", "vol_3m = infla_vol.iloc[-1] * np.sqrt(business_day_count(back_3m, today))\n", "\n", "carry_df['3m Vol-adjusted Carry ratio in %'] = carry_df['3m Carry (bps)'] / vol_3m * 100\n", "carry_df.reset_index(inplace=True)" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "clr_palette = sns.color_palette()\n", "\n", "f,(ax_b1, ax_b2) = plt.subplots(1, 2, figsize=(19, 6))\n", "\n", "ax_b1.tick_params('x', labelrotation=45)\n", "ax_b2.tick_params('x', labelrotation=45)\n", "ax_b1.set_title('Carry')\n", "ax_b2.set_title('Vol-adjusted carry')\n", "\n", "b1 = sns.barplot(x='index', y='3m Carry (bps)', data=carry_df, ax=ax_b1, palette=[clr_palette[0]])\n", "for index, row in carry_df.iterrows():\n", " b1.text(index, row['3m Carry (bps)'], round(row['3m Carry (bps)'],2), color='black', ha=\"center\")\n", " \n", "b1.xaxis.set_label_text('')\n", "b2 = sns.barplot(x='index', y='3m Vol-adjusted Carry ratio in %', data=carry_df, ax=ax_b2, palette=[clr_palette[1]])\n", "for index, row in carry_df.iterrows():\n", " b2.text(index, row['3m Vol-adjusted Carry ratio in %'], round(row['3m Vol-adjusted Carry ratio in %'], 2), color='black', ha=\"center\")\n", "b2.xaxis.set_label_text('')\n", "\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 5 - Correlation to macro drivers\n", "\n", "Now we look at the correlations between inflation rates and our macro series over various timeframes. From a trade's perspective, a flattening move should be positively correlated to an equity rally and bond sell-off. To correct for the directional bias of flattening, we to flip the curves' direction in the correlation calculations in order to keep the correlation sign accurante." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "correl_str = ['3m','1y','3y','5y']\n", "correl_start_dates = [date_subtract_period_str(today, t) for t in correl_str]\n", "\n", "flipped_prices = prices_T.apply(flip_flatteners)\n", "drivers_df.index = drivers_df.index.date\n", "correl_matrices = [correl_map(drivers_df, flipped_prices, x) for x in correl_start_dates] " ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [ { "data": { "image/png": "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EdzYV0yZQK0zLw5a+Wu2xAatbIAtv31BuTbnBaXst5a+HJRy0328bC2961QJXvljFgWtbIA5KbG0yFM4KGV/fHNw3/vdC7+leJDOFwDs3rAhaGVDcHPnRSOCl2cBvDd6FFcODM7xMkDxQjEnNG+WU5MateSUEOxGrWAKdqNWMIVCo1YwBbtRK5hCoVFLgifYjVrBFOxGrWALdqNWMAW7USvYgt2oFWz/pkatYAt2o1YwBbtRK9iC3agVTMFu1Aq2YDdqBVOwG7WCLdiNWsEU7EatYAt2o1YwBbtRK9jUqCWg4QdFRERERERERERERETkFKBGLREREREREREREREREQl5atQSERERERERERERERGRkKdGLREREREREREREREREQl5atQSERERERERERERERGRkKdGLREREREREREREREREQl5atQSERERERERERERERGRkKdGLREREREREREREREREQl5atQSERERERERERERERGRkKdGLREREREREREREREREQl5atQSERERERERERERERGRkKdGLREREREREREREREREQl5atQSERERERERERERERGRkKdGLREREREREREREREREQl5atQSERERERERERERERGRkKdGLREREREREREREREREQl5atQSERERERERERERERGRkKdGLREREREREREREREREQl5atQSERERERERERERERGRkKdGLREREREREREREREREQl5atQSERERERERERERERGRkKdGLREREREREREREREREQl5atQSERERERERERERERGRkKdGLREREREREREREREREQl5atQSERERERERERERERGRkKdGLREREREREREREREREQl5atQSERERERERERERERGRkKdGLREREREREREREREREQl5atQSERERERERERERERGRkKdGLREREREREREREREREQl5eYJdAZHcVKRgfob/dwD1q5fHOcegJ96nQuli/Pf6ntSpUpZ2Vw1h7tL1mdarWbkMHz597d/Pq5YvyeNvf8Gwj6bw5C0X0bVtfRau2Mg1j74LQN9urSlWpACvj5uSW6EdUZGC+XnrkYHUr14B5xzXPTaaC89sRs8OTTiYnMLqjTu49pGR7ItPOGy9WpXLMubZG/5+XrVCKR4b/hmvjf2ep/9zKV3PaMiCFRu4+qERAPTrcTrF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ERERERGXp6SWiIiIiIiIiIiIiIiIuDwltURERERERERERERERMTlKaklIiIiIiIiIiIiIiIiLk9JLREREREREREREREREXF5SmqJiIiIiIiIiIiIiIiIy1NSS0RERERERERERERERFyekloiIiIiIiIiIiIiIiLi8pTUEhEREREREREREREREZenpJaIiIiIiIiIiIiIiIi4PCW1RERERERERERERERExOUpqSUiIiIiIiIiIiIiIiIuT0ktERERERERERERERERcXlKaomIiIiIiIiIiIiIiIjLU1JLREREREREREREREREXJ6SWiIiIiIiIiIiIiIiIuLylNQSERERERERERERERERl6ekloiIiIiIiIiIiIiIiLg8JbVERERERERERERERETE5SmpJSIiIiIiIiIiIiIiIi5PSS0RERERERERERERERFxeUpqiYiIiIiIiIiIiIiIiMtTUktERERERERERERERERcnpJaIiIiIiIiIiIiIiIi4vKU1BIRERERERERERERERGXp6SWiIiIiIiIiIiIiIiIuDwltURERERERERERERERMTl2ep6B0ROh19E07rehTpjtbnX9S7UGU8ft7reBakjcQMHk7Qzvq53o864+wXX9S7UqQBPa13vQp2pF+pT17tQp8rsjrrehTpjWP69xz1A/06Rdb0L/yjlFWZd70KdSckpqetdqDOBXv/uZ2dfT1Vv/FsZFqOud6HO1GsdU9e7UKe6RRys612oMyujA+t6F+qUX7BXXe9CnUkusNf1LtSpYL+63gNxBeqpJSIiLuffnNASERERERERERGR2impJSIiIiIiIiIiIiIiIi5PSS0RERERERERERERERFxeUpqiYiIiIiIiIiIiIiIiMtTUktERERERERERERERERcnpJaIiIiIiIiIiIiIiIi4vKU1BIRERERERERERERERGXp6SWiIiIiIiIiIiIiIiIuDwltURERERERERERERERMTlKaklIiIiIiIiIiIiIiIiLk9JLREREREREREREREREXF5SmqJiIiIiIiIiIiIiIiIy1NSS0RERERERERERERERFyekloiIiIiIiIiIiIiIiLi8pTUEhEREREREREREREREZenpJaIiIiIiIiIiIiIiIi4PCW1RERERERERERERERExOUpqSUiIiIiIiIiIiIiIiIuT0kt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wIkf59P4o7eJvO1mDrzz4VWtz/n8PV14umt00d/9sgOJrCsMSHesEcSSfSWfZwV4ujCiUxTP/bQLPw9nXuxlH9lkNWDxvgQ2xJ67F2xT1Z+9xb/3J1P55KbGHErM4FCSY3zvy9IhOogR3aLx9XBh4o2N2Hcqjce/3kqglwvP9qrNyG/tU3K/tWAvL15XF2erQVxyNq8Wm4a/fXQgu06kkphur/uOuDPMvKslB+LTORDvGM9KAN56+Tl2bN1E2pkU7hrUmyHD78GrQgWmTppA6plkXn32capGRTNm/HucTkxg8vhXGP3GJABysrPZtmk9940cVeq461YsJapWHfwD7W29mnXq89jwIURWj6JqVPRVraP8exnmZS6GJ47FMIxq2EdngT1IOcs0zVeL7W+FfcRW5bNBKsMwFgNB2DsbbQXuM00zvXCfM5AEtDBNc08Z73c70Mw0zYeKbVsKPGGa5kbDMGIK9ycahhGGfX2upkA2EAM8BuQBP5mmWXIs+kUEXTf+P/tFtTqVf6/38hLRoHZ5F0HKSdyug3+e6F/Mxdu/vItQrv54t195F6HcrGve/s8T/Yv9/uYX5V2EcjN95sryLkK5Gnpz2/IuQrl6s2/dv3WIzrKDif/ZtvPPu079eaJ/qVqh3n+e6F/sbMDkv2j70bJHBf5XxJy6tHVj/41mM6e8i1CuNr2/tLyLUG4mPO5YU6FfbRkO0BGkvEwd3vzPE/2L1Q2tUP5D26XcaaTWP5xpmoeAi61S3w74rPioK9M0O18kfUNgW1kBrcK8nwOfn7etY7HfqxT7/ThwwwXe55IDWiIiIiIiIiIiIiIiIgpq/YsZhvEDUB24WBCrePpngPuBoVeyXCIiIiIiIiIiIiIiIpdLQa1/MdM0B1xm+teB169QcURERERERERERERERP4yS3kXQEREREREREREREREROTPKKglIiIiIiIiIiIiIiIiDk9BLREREREREREREREREXF4CmqJiIiIiIiIiIiIiIiIw1NQS0RERERERERERERERByegloiIiIiIiIiIiIiIiLi8BTUEhEREREREREREREREYenoJaIiIiIiIiIiIiIiIg4PAW1RERERERERERERERExOEpqCUiIiIiIiIiIiIiIiIOT0EtERERERERERERERERcXgKaomIiIiIiIiIiIiIiIjDU1BLREREREREREREREREHJ6CWiIiIiIiIiIiIiIiIuLwFNQSERERERERERERERERh6egloiIiIiIiIiIiIiIiDg8BbVERERERERERERERETE4SmoJSIiIiIiIiIiIiIiIg5PQS0RERERERERERERERFxeApqiYiIiIiIiIiIiIiIiMNTUEtEREREREREREREREQcnoJaIiIiIiIiIiIiIiIi4vAU1BIRERERERERERERERGHp6CWiIiIiIiIiIiIiIiIODwFtURERERERERERERERMThKaglIiIiIiIiIiIiIiIiDk9BLREREREREREREREREXF4CmqJiIiIiIiIiIiIiIiIw1NQS0RERERERERERERERByeU3kXQORSjHysf3kXodycycwr7yKUmxaRfuVdhHKVnmsr7yKUn751+Pi3/eVdinLj5PLf7nNyOuu/+92vfUOT8i5C+YoOKu8SlBv/+zqXdxHKVQU35/Iuwr9KzUD38i5CuYloXbm8i1BuCszyLkH5ikvNKe8ilBtPl//2o53IAI/yLkK58cqLKu8ilKvq3Y6WdxHKTYMI3/IuQrnaGXemvItQbipVULtZ5L/91ExERBzSfzmgJSIiIiIiIiIiImVTUEtEREREREREREREREQcnoJaIiIiIiIiIiIiIiIi4vAU1BIRERERERERERERERGHp6CWiIiIiIiIiIiIiIiIODwFtURERERERERERERERMThKaglIiIiIiIiIiIiIiIiDk9BLREREREREREREREREXF4CmqJiIiIiIiIiIiIiIiIw1NQS0RERERERERERERERByegloiIiIiIiIiIiIiIiLi8JwuN4NhGB5AIyCY84Jipmn+728ql4iIiIiIiIiIiIiIiEiRywpqGYbRFfgKCChjtwlY/45CiYiIiIiIiIiIiIiIiBR3udMPTgJ+BiqZpmk570cBLREREREREREREREREbkiLnf6wSpAP9M0j1+BsoiIiIiIiIiIiIiIiIiU6XJHaq0Cal6JgoiIiIiIiIiIiIiIiIhcyOWO1PoImGAYRhjwB5BXfKdpmpv/roKJiIiIiIiIiIiIiIiInHW5Qa3vCv+dUsY+E9C6WiIiIiIiIiIiIiIiIvK3u9ygVtUrUgoRERERERERERERERGRi7isoJZpmkeuVEFERERERERERERERERELsRyuRkMw2hgGMZ0wzA2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hZjbtu18McGZ3UXINs7b9qi7HI/M+T3/bpZdOfSmoqxy4nzmhtP+N+iT8Ed2FyFbHft1YXYXIdt82/KD7C5Cttp+/PZtO0/tUjO7i5Ct/Hy8/tF2s9yabnRIvDuBRtbaGGOME3C31m4wxrwBfA3cEjO9WmsPAOlmqLPWJgDpBo621nYHul9jX78Bv13Pfq5aZxGw6KplT6V6fE+qx5HAM9fYz18mhK7aV8r7WmtXAmVTrfpu8vKzuI51alcf2w4ZvM8HVz2vnOrxFGBKZsovIiIiIiIiIiIiIiJyo7eCG1w9hcDVu+fyxDrHgJwx1paIiIiIiIiIiIiIiIj869xoT61tuHo4HQDWAG8aY5Jw9SLa9w+XTURERERERERERERERAS48aTWx4BP8uN3gFm45o6KAB79B8slIiIiIiIiIiIiIiIikuKGklrW2jmpHh8AKhpjAoFz1trbfFpaERERERERERERERERuVlutKdWOtbas/9EQURERERERERERERERESu5S+TWsaYn693Z9baB/5ecURERERERERERERERETSu56eWmdueilERERERERERERERERE/sT1JLW+B1ZaaxNudmFEREREREREREREREREMuK4jnUWAv4AxpgDxpigm1skERERERERERERERERkbSuJ6l1DiiZ/DjkOrcRERERERERERERERER+cdcz/CDPwKLjTEnAQusM8YkZbSitbZkRstFRERERERERERERERE/o7rSWo9C/wMlAE+xzXHVtTNLJSIiIiIiIiIiIiIiIhIan+Z1LLWWuBXAGNMNWCQtVZJLREREREREREREREREcky19NTK4W1tuvNKoiIiIiIiIiIiIiIiIjItdxQUssY4wm8BDQF8gOO1K9ba6v+c0UTERERERERERERERERcbmhpBbwDfAgMA1YAdh/vEQiIiIiIiIiIiIiIiK3AI8a3f6VeZL4jaNNdpchIzea1GoHPGKtnX8zCiMiIiIiIiIiIiIiIiKSkRtNal0Ejt6MgoiIiIiIiIiIiIiIiNxKjMMtu4twW7nRpNYA4FVjTC9rrfNmFEhERERERERERERERORWoKRW1rrRpFYzoAHQ0hizA0hI/aK19oF/qmAiIiIiIiIiIiIiIiKSMxljWgJfAm7ASGvtJxmscw/wBZALiLDWNvo773mjSa0I4H9/5w1FRERERERERERERET+DW7XnlrGGDdgKK7OUMeAtcaYn621O1Kt4w98A7S01h4xxuT/u+97Q0kta23Xv/uGIiIiIiIiIiIiIiIi/wa3a1ILqA3ss9YeADDGTAbaAjtSrfMY8JO19giAtTbs777pdSW1jDE/X8dq1lrb9m+WR0RERERERERERERE5JZg3P6dSS1jTA+gR6pFw621w1M9LwIcTfX8GFDnqt2UBXIZYxYBeYAvrbXj/k65rren1pm/8yYiIiIiIiIiIiIiIiJya0hOYA3/k1VMRptd9dwduANoCngBK40xq6y1ezJbrutKamnYQRERERERERERERERkbQct+/wg8eAYqmeFwVOZLBOhLU2BogxxiwBqgE3N6klIiIiIiIiIiIiIiIiad3Gc2qtBcoYY0KB40BHXHNopTYTGGKMcQc8cA1POPjvvKmSWiIiIiIiIiIiIiIiIplwuya1rLWJxpgXgDmAGzDaWrvdGPNs8uvDrLU7jTGzgS2AExhprd32d95XSS0RERERERERERERERG5Idba34Dfrlo27KrnA4GB/9R7KqklIiIiIiIiIiIiIiKSCcbhyO4i3FaU1BIREREREREREREREcmE23X4weyipJbcEvy8cmV3EbKNh/vtm+k/eO4ipYN8srsY2SaXm8nuImSbF1qW5evfd2d3MSSbRMYnZXcRsk2hAN/sLkK2KuaXO7uLkG2OR96+sQNUzp8nu4vwr+LhuH3bEB65b98LCuGXErO7CNkqn7dHdhch25QKtNldhGyV3+f2PfZuBdtmdxGyVQm/oOwuQrZ5PKRodhchW52rWCC7i5Btkpy3d52fUymplbVu36vlIpLj3c4JrdudEloiIiIiIiIiIiJyNfXUEhERERERERERERERyQT11MpaSmqJiIiIiIiIiIiIiIhkgnFTUisrKaklIiIiIiIiIiIiIiKSCeqplbU0p5aIiIiIiIiIiIiIiIjkeOqpJSIiIiIiIiIiIiIikgnqqZW1lNQSERERERERERERERHJBIeSWllKSS0REREREREREREREZFMUE+trKU5tURERERERERERERERCTHU08tERERERERERERERGRTFBPraylpJaIiIiIiIiIiIiIiEgmKKmVtZTUEhERERERERERERERyQQltbKW5tQSERERERERERERERGRHE89tURERERERERERERERDJBPbWylpJaIiIiIiIiIiIiIiIimWDclNTKShp+UERERERERERERERERHI89dQSERERERERERERERHJBA0/mLWU1BIREREREREREREREckEJbWylpJaIiIiIiIiIiIiIiIimaCkVtbSnFoiIiIiIiIiIiIiIiKS46mnloiIiIiIiIiIiIiISCY4HCa7i3BbUVJLREREREREREREREQkE4ySWllKSS0REREREREREREREZFMMEZJraykObVEREREREREREREREQkx1NPLRERERERERERERERkUzQnFpZS0ktERERERERERERERGRTNCcWllLSS0REREREREREREREZFMUFIra2lOLREREREREREREREREcnx1FNLREREREREREREREQkExxGPbWykpJaIiIiIiIiIiIiIiIimaDhB7OWkloiIiIiIiIiIiIiIiKZoKRW1tKcWiIiIiIiIiIiIiIiIpLjqaeW3DaizoQxZ/hAYi6cwxhDlcb3UaP5gymvr/9tGkunjKTnkKl45fHLcB9OZxKT3n8R34Ag2r76IQBLp4zk8JZ1BBcvSYuebwCwc/l8YmOi0uw/O0WfDWfhqEFcvHAO4zBUaNiSKve2Y93M8excOicl3toPdqF41TvTbHv+1DHmf/dJyvPI8JPUatuZqs3asWr6aI5uXUdQ8ZI0efp1APas/IO4mCiq3Nsu6wL8C1O//oQd61bi6xfA61+NAeD4wb38NOxzEuLjcXNz48Eer1C8bIU024UdP8L4z/qmPD97+gQtOnWjwf2P8Ou4YezasJrCoaXp9NJ/AVi/aA4Xo6JocH/7LIvtr1yICOOnoZ8Qff4sxhjuuLcNde97mIvRkUwb/CHnw0/hH1yQR195Dy/fPGm2jThxhGmDP0x5fi7sJI0ffYq6rdszd/xw9m1aQ8GQUjz0wlsAbF4yl4vRUdS97+EsjfGvOAyMeupOwqPieGP6FvJ4uvNh28oU9PPk1IVY3p2xjai4xHTbPVKrKA9UK4wBft58gqnrjgHQ655S3FUyiL1hUXw0aycALSoVJK+XO9OS18kJPNwMXz5anVxuDtwchsV7wxmz8jClg314tWlZPNwcJFnL4D/2sut0VLrt29coQusqhcDCgYgYPp27i/gkS4/6odQJCWRfeDT95+wGoFmF/OT1zMWPG49ndZjXNPTTD1m/chl+/gEMHjMZgBWL5jN1zAiOHz5E/2+/p3T5ihlu++v0ycyfNQOL5d7W7WjzSCcAfvjuazauXklI6TL8521X3bB47m9ER0bSun3HrAnsOuUuVxOvOxoDYBPiiV44naSIkwB4Vm+IZ6U6gCUp4hRR8ydDUtrvgMnthe+9HXDzC8ImJhI9fwpJZ09hvHzI27orJrcnF1fOJv7ANgDytOlKzMIfccZEZmmcGfmyfz/WrViGX0AAQ8ZNASAq8gID3n+bsFMnyV+wEG/2649vnrzptl2/egUjvxxEktNJ8zZtaf/EUwCM+fZr1q9aQckyZXnlHdexXzj7N6KiLvBA8ucjJ4g8E8bP335KzHlXW6d6k9bUbvUQf0z4jr0bVuHm7k5AgcK06dkbTx/fDPfhdCbx/X+fI09gPh7t/TEACyaNYP+mNRQoUYoHnusDwNal87gUHUXtVg9lWXx/5buBH7Jx9XLy+gcwYOQkAKZ+P4z1K5bicBjy+gfwbO/3CMgXnGa7E0cP8/VH/015HnbyOO279KDVw52YNGIIm9aspESpMjzX5wMAls77jeioSFo9lLO+9zdLXFwcvZ55moSEeJKSkmjc9F6e6dkrzTrWWgZ/NoAVy5fj6enJux/0pVz5Cpw7d5Y+r79GdFQUPZ57nkb3uOqlN159md5vvU1wcP7sCOmGXE/8c37/jR/GjgHAy9uLN/q8TZmy5W7Z+G/nenTIp/1Yl9x++HKMK/YVi+YzZcxwjh0+xKffjrlm+6Fnhwfw8vbG4XDg5ubOwOHjABj33ddsXL2CkNJleSm5/bBo7m9ER16gTfucEzvAD1/8H9vWriCPXwDvfPNDyvJFv0xn8awfcbi5UblWPR7s9ly6bRfMmMLyub9gMBQOKUnnl98ml0duZnz/DdvXr6ZoaGm6vPYuAKsXzOZiVCSN2z6aZbH9lWlDP2XXupX4+vnzyhdjADhxaB8zvvucuNhLBAQXpOPL7+Dp7ZNu20sxUfz4zUBOHzkIxtD++TcpUa4Sv//wHbs3rKZQaGk6/OdtADYsmsvF6Ejqt8k554xXW7thE//p8x5FChUEoGmj+vTq9mS69SZOn8H4qT9y9PgJlvz6EwH+rusK8xYuYejIMfjlzcOXn/TD38+Po8dO8NXwUQzs926WxnK9DpwM593vZ7DzyEn+82BTnmpxd8pr4+au4KdlGzAYyhTNz4dd25E7V650+1i76yCfTplNYlIS/r7ejHmjG2ejYnh56GSiLsbywoNNaFrDdc3hxSETefeJNuT3T1+PZrXMtp8AYqKjGDHoY44eOoAxhh6vv0PZilVumfbT36nzFs6cyvI5v2Cx3N3iAZok12e3Sp2X2u3Y1rkZHOqplaX+1T21jDGjjTFhxphtVy0PNMbMM8bsTf4/4E/24WmMWWOM2WyM2W6M6ZuZ/WSw30PGmHypnt9jjJmV/PgpY8yQVK89aYzZlvz+O4wxrycvH2OMOWiM2WSM2WCMqZvB+wQbY1YbYzYaYxpc/b7XKNvbqR77G2PS1963IIebGw079aDLJyPp+N6XbJ7/C2eOHwZcCa/D2zeSJ+jPK9tNc2cQWLhYyvO4izGc3LeDJz4ehtM6iTh6kMT4OHYsm0fVJvff1HhuhHG4cdej3enw0Xe0e/tzti+cxbkTRwCo2qwd7d8fQvv3h6RLaAH4Fyya8vpD736Ju4cnoTXrEncxhtP7d/JI32+wTidnjrli3718PhXvaZPVIf6pWk1a0f29gWmW/Tp2GM0e7cKrg0fRvFM3fh03LN12+YsU59XBo3h18Che/mw4uXJ7UrlOAy7FRHN41zZe++J7rNPJycP7SYiLY92C2dRrlXOSeeD63Lfo/CwvDh7DMx8PZe2cmYQdO8SyGZMoWaUGL331AyWr1GDpjEnpts1XuDi9Bo6g18AR9Px0GLk8clOhdn1iL0ZzdM92nvtsJE6nk9NHDpAQH8fGRXOo3bxtNkT55x6pVYxDETEpzzvfVYJ1h8/Rcfgq1h0+xxN1S6TbJjSfDw9UK0z3sevoMnot9Urno2iAFz653ahSxI8uo9fgZgwlg33wcHdwX5WC/LQh5yR0AOKTLK9O30z38evpPn49tUsEUrFgHno2KMmYVYfpPmE9o1cc4tkGJdNtm8/Hg4drFKHnhA10/WEdDgc0KZcfHw83Khf24+nx63EYQ2iQDx5uDlpWLMiMzSeyIcpra9yyNe8M+DLNsuKhpejdbwAVqta45nZHDuxn/qwZfDJsDINGTmD9ymWcPHaEmOhodm/bwuejJ+J0Ojl8YB9xcbEsnD2LFu1y3kWJpAtnufDjN5yfOIiLa+bh2+QRABw+efGqVp/zkwdzfsJn4DDkLpv+7+FVqymJ4Sc4P3EQ0fMm4dPIVbflLluD2J1ruTD1a7xq3gOAR2hFEsOO54iEFkDTVm344LOv0iybPn4s1e64k+8m/US1O+5k+vix6bZLSkriu88H8P5nXzL0h6ksmT+XIwcPEBMdza5tW/h67CScziQO7Xcd+z9+/4X7Hnwkq8K6Lg6HG/c+/iw9PxtNl35fs2HeTMKPHSa0yh30GDCSZz4dQWChoqz4OX2df9na3/9HUJHiKc9jL0ZzbM92nvl0BNbpJCy5zt+yZC53NHsgK8K6bg1btOHN/l+kWdbm0Sf4dMQE+n83nhp31een8aPSbVe4WAn6fzee/t+N5+NvxuKR25Na9e/hYnQ0e7Zv4dMRE3A6nRw5sI/4uFiWzP2VZg/kvO/9zeLh4cGQYcP5YdJUxk2czKoVK9i2dUuadVYuX8bRo0eY9r+Z9PnvOwzo/38AzJszm/va3M/w78cyYZzre7d0yWLKla9wy1zkuJ74CxUuzDfDRzJ+8lS6Pf0Mn3z8EXDrxn8716ONW7bh3QFpYy8eWoo3+g2g4p+0Hy7rN3gYn4+amJLQutx+GDzaFfuV9sMvtGyXs2IHuOve+3i+76A0y/Zs2cCWVUt5e8hY3v1mPPc+lD4Rdz4inEW/TOfNwaN455sfcDqdrFvyB5diojmwcxv/HTIWp9PJ8UP7iY+LY9X832nYOufcFAFwxz0t6fbugDTLfvpmIC2f6MErg7+nUp0GLJk5OcNtfxk9hLI1avPa1z/w0qBR5C9anNiYaA7v3sbLg0djnU5OHT5AQlwc6xfNpm7LnHXOmJGa1Sozfexwpo8dnmFCC6BG1UqM+HIghQsWSLN87ORpTBg+hPtbNefXuQsA+HrEaF54putNL3dm+fl48Van+3iqeb00y0+fi2TigtVMfqcn/+v3PElOy+9rtqXbPvLiJT6a8Ctfv9CJGf1eYNCzruTF76u38kC9aox/uztjZi8HYNGm3VQsXihHJLQg8+0ngHFDP6fanXUZ9P1UPvluPEWKh9xS7afM1nknDh1g+ZxfeOPzEbz99Ri2rVlO2PGjt1Sdl9rt2Na5GYzj3/kvp8rBRftHjAFaZrC8D/CHtbYM8Efy82uJA5pYa6sB1YGWxpi7MrGfTDHGtAJeBppbaysBNYELqVbpba2tnvze32Wwi6bALmttDWvt0ut827dTPfYHbnpSyxhz03sN+vgHkT+kDAAeXt4EFi5G9LkIABZP/I4GHZ4Gc+2setTZcA5uXkPlRq1SlhljSEpMxFpLYnw8Djc31v02jerN2uLmnnM6Qvr4BxJcojQAHp7e+BcqTkxy7Dfi+M7N5A0uSJ6gAhiHwZmYkCp2dzbP+ZEqTR/IUbEDlKxUDe88aXshGWOIvXQRcF2wyxsY9Kf72Lt1A0EFCxOQvyDG4SAx+bgnxMXh5ubOohmTuLv1wzku9jwBQRQuWRaA3F7e5CtSnKizEexau5zqjVoAUL1RC3atXfan+zmwdQMBBQvjH1wQYxwkpRz7OBxu7iz/eQp3tXoox8UfnCc39UoF8cuWkynLGpTJx+9bXc9/33qShmXS5/lDgrzZfiKSuEQnSday6ch5GpYNxlpwd3PVE7ndHSQmWR6vU5xp64+R5LRZE9QNuJTgBMDdYXB3GCxgLfh4uAHgk9uNiJi4DLd1cxhyuztwM+Dp7kZEdDxO69oXuOJPcjrpWKsYP208nuPir1itZro7yIuWCKVI8fRJzNSOHTlI2YqVye3piZu7OxWr12T10kU4HCblex8fF4e7mzs/Tx7PfQ91wD2Hfe4BEk8dwsZdSn58GIev/5UXHW4Y91xgHBh3D5wxF9Jt7x5YgISjewFIOheGW94AjJcvOJNc27q5uz5MxoFn9YZc2rAwS+K6HpWr18Q3b9pjv2bZYpq0dN1w0aRlG1YvXZRuu707t1OoSDEKFi5Krly5aNC0GauXLcY4DIkJrjovLi4Od3d3/jfxB9q075jjjr1vQBAFQ11tndxe3gQVKU70uQhKVq2Fw831vS9SugJRZ8Iz3D7yTDj7Nq2meuP7UpYZ48B5+TcvwfV7v2rWVO5s0S7H1fkVqtZI9733TtUjLe7SJeDP76DctnEtBQoXJbhAIdexvxx7fBxu7u7MmjqeFu0ezXHH/mYyxuDt7Q1AYmIiiYmJmKvazEsWL6bVfW0wxlC5SlWio6KIiAjH3d2duLhY12cnuf00ZdJEHn8y4wukOdH1xF+1WnXyJtc7lapUJSzsNMAtG//tXI9WqlaTPBm2H0IytT9X+yEhpf3g5ubOzMk/0PqhnBc7QJnK1fG5Kv4lv/2P5o88Qa5cHgDk8c/4Xt6kpCQS4uNISkokIS4O/8B8GONIiT8h3hX//J8mcs8D7XPcb0jJSmQgtJEAACAASURBVNXSjVwRfuIooRWrAVCmWi22rVqSbrvYizEc3LGZO5u2BsA9Vy68fPJgHI6UawUJ8XE43NxYPHMy9e7LeedMmVWhbJmU3lypORwO4hMSiI2NJZe7O+s3bSFfUCAlihXNhlJen6C8vlQOLYJ7cnsptcQkJ3HxCSQmJREbn0B+/zzp1vlt9Vaa1qxAoSD/lP0BuLs5iItPJD4h0VUfJCUxfv7KND3Bsltm208XY6LZtXUj97Ry3eTknisXPr55bqn2U2brvFPHDhFavhIenp64ublTpnINNq9cckvVeandjm2dm8EY86/8l1P9q5Na1tolwNkMXmoLXL61bCzQDsAYUym5V9YmY8wWY0wZ6xKdvG6u5H/2RvfzN8J4C3jdWnsiOaZYa+2IDNZbApROvcAYUx0YANyXXBavq16fYYxZn9wDrEfysk8Ar+T1JwCfAKWSnw9MXqe3MWZtcmx9k5eFGGN2GmNGJO9v7uX3M8aUMsbMTn6vpcaY8snLxxhjPjfGLAQ+/Rt/oxt2IfwU4Yf3U7BUefZvWIlvQD6Ci5f6020WTxhG/Ue7p0l8eXh5U6ZWfSa89xx+wQXI7e3D6YN7KFWz3p/sKXtFRZzmzJH95C9ZHoBtC35h2vvPsej7wcTFpB+CLLX9axZTus49gCs5Flrzbn7s9yJ58xXAw8uHsIN7CKmRrsNgjvRAtxf4dey3fNS9PbPGfMt9T/T40/U3L/2DGg2aAuDp5U2Vug0Z/Gp3AgsUwtPbl6P7dlG5Tv2sKHqmnQs7xamD+yhSugIxF86RJ8CVyMsTEERM5Pk/3Xbb8oVUubsJ4LpQWrFOQ4a90QP//IXw9Pbh+L5dlL8z5zTML3upaRm+Wbgfa68kXAJ8PDgTEw/AmZh4/H080m13ICKGasX8yevpTm53B3VLBVEgb24uxiexaHc4Y7reyYkLscTEJVK+UF6W7b3xJHFWcBgY+fgdzOhZj3VHzrHzVBRDFu/n2QYlmdq9Dr0almLEsoPptouIiWfK+mNM7X4XP/aoS3RcIuuOnONSQhJL9kUw8vE7OBkZS3R8EuUL5mH5gTPZEN3NUTy0FDu2bCTqwnniYmPZuGo5Z8JO4+Xtw10NG9O7+xMUKFQYb19f9u3aQe36jbK7yH/Js2IdEg7vAsAZE8mlDYsI7Pougd3fxxkXS8KRPem2SYw4Qe7SVQBwL1AMR54AHL7+xO3eiEfxcuRt+wwXV8/Fs2o94naug8SELI3pRp0/d5bAfK4EdmC+fJw/dy7dOmfCw8mX/8qdxvmCC3AmIhxvbx/qNmrCy90edx17H1/27trBXQ1y9rE/H36K04f2UbhU+TTLNy+aTanqtTPcZt4P39Ck0zNpTl5ye3lTrnYDRr39LP7BBfH09uHk/t2UrZXz6vxrmTL6W17odD/LF8zhkaf+/Pd+5cJ51G3cHAAvbx9qN2jM2892Jrig69jv372TWnfn7GN/MyQlJfHkYx24r1lTate5i0qVq6R5PTw8jAIFr1zYDC5QgPCwMJq3bMXqlSt55cXnebpHT36aPpVWrVvj6el19VvkaH8Vf2q/zJxB3Xqu78e/JX64PevRG2WMoW/vF3i9R2fm/vITQHL7oQmvdX+c/LdY++GysONH2bd9CwNefYbBfV7g8J6d6dbxzxfMvQ925J2uD/N253Z4evtQoWZtPL29qVHvHvr/pytBBQrh5ePD4T07qXZXg2yI5MYVKB7KjrWu3jVbVyzifERYunXOnj6BT15/pg35hC9f7870bwYQH3uJ3F7eVL6rIV+93p2A/IXw9PHl2L5dVKqds88ZL9u8bQcPd3mGZ1/rw74Dh25o22e7dqbnq2+yat0GWjVrzPCxE+j5VOebU9CbrEBAXp5qUY9mbw6myWuf4euVm3qVSqdb7/DpM0RevETXAd/zaL9h/LxiEwD31anK8u37ePaL8fR6oDFTFq7l/rrV8cqd/hw0p/mr9lPYyRPk8Qvgu4Ef8lbPzgwf9DGxly7d8u2n66nzCpcoyb5tm4iOvEB8bCzb163kXETYLV3nqa0jt5qcmya+uQpYa08CWGtPGmMu94d8FvjSWjvBGOMBuAEYY9yA9biSRkOttaszs58MLDTGJCU/9gV2ZbBO5eT3/iv3A1tTL7DWbjLGvAfUsta+kBxL6lW6WWvPJief1hpjfrTW9jHGvJDc+wtjTAhQOdXz5kAZoDauWzV+NsY0BI4kL+9krX3GGDMVeBgYDwwHnrXW7jXG1AG+AZokl6EscK+19vLf4aaLj73Er19/SKPHn8XhcGPNL5N4qHf/P93mwKZVeOf1p0BoGY7u3JzmtVqtH6VWa1fX8nmjBlP3oSfZtuh3Dm9bT75iJanT9rGbFsuNSoi9xNxvPqZuhx54eHlT8Z7W1Ly/EwbD2hk/sHLqSO7p+kqG2yYlJnB482pqP/RUyrLqrR6heivXsBmLx3zBne06s3PJbI7t2EBQ0VBqtslZY8SntnLOTO7v9gJV6zZi8/IFTB06gJ59P89w3cSEBLavXUGrzlcaco0ffIzGD7qO7bShA2jRqRur581iz6a1FAopxb2P5Ky7UuJiLzFl0Pu0fOq5DMeB/zOJiQnsXr+Cex/rnrKsftuO1G/rGgt75rDPaNKhK+v/+JX9m9dRoERJGj2c/Sct9UoFce5iPLtPR1GjuP9fb5DK4TMXmbDqMF90rMGlhCT2hUWn9ESauPoIE1e7hu/s06o8I5ce4P6qhbgzNJD94TGMXXHonw4l05wWuk9Yj29uNz68vzKhQd60qVKIoYv3s2RfBPeUDeaN5uV47ce0Qwv45nbn7pJBdBy9mui4RPq2rkiz8vmZtyuMyeuOMnndUQB631uW0SsO0bpyQWqVCOBAeAw/rDmSHaH+Y4qWCKVdpyfp9/qLeHp5UaJUmZQeLu06PUm7Tq7v9rcDPqJDt57MnzWDzetWU6Jkado/+XR2Fj1DuYqWInel2lyY7hrV2OT2wqNkJc6O/Rgbd4k8rbqQu1xN4nZvSLPdpfUL8GnYDv9Or5J45iSJ4cfBJmHjY4n8ZVTKvrxqNSby1zH4NnkE4+nFpQ2LSTx1OMvj/CdY0vc2NMl3pT78+JM8/Ljr2H/9yUc89vSzzP1lBhvXriakVGk6dMlZxz4+9hI/De7LvZ2fI3eqOn/5jAk43NyodHfTdNvs3bAKn7z+FCpZlsM7NqV5re79Hah7fwcAfh0+iIaPdGHTwt84sGUd+YuXpP6DT9zcgP6mDt160aFbL2ZOHMPcmdNo3yXjxFZiQgLrVy6lY/crgxTc36Ez93dw/aYNH/Qxj3TpwcLfZrJl3WqKlyzNg090y5IYspubmxvjJk4hKiqKPq+/yv59+yhVOtVFPZvB98cYfH3zMOjLrwGIjIxk/Ngx9B84iP4f9SMqMpJOT3SmStVqWRVGpv1l/MnWr1vLLzNn8N3I0QD/mviv17+pHs2M/xsyksB8wZw/d5a+r79AkeIhVKpWkwc7PcmDye2HoQM+omO3Z5mXqv3wSA5sP6TmTEriYnQUvQcN5/CenYz69D36jpya5trCxehItqxeRr9RU/H2ycPIT95lzcI51G7cgmbtH6dZ+8cBmPDVJ7R5ojvL5/zCzo1rKBJSilYdn8qmyP5a++fe4JfRX7Ng2jgq3FkPd/f08yg5k5I4cWAPDzz9H4qXrcjPo75m0f8m0rzT0zRq14lG7VznxNO/GUCzjt1YM38Wezeto2BISZq2z1nnjJdVKFeGuT9OwtvbiyUrVvPSW+/x65Rx1719vdq1qFe7FgAzf5tDg7q1OXTkKGMnTSVvnjy8+fLzeHl63qzi/6MuxFxi4abdzP7kZfJ4efLasKn8snIz99dNW3cnJjnZefgkI17rQlx8Ak/0H0nVkkUJKZiPb156ImVfo39fxhfPdeCDsTOJvBjLk83rUb1UsYzeOtv9VfvJmZTEob27eeqF1yhdoTJjhw7i58ljebTrs7d0++l66ryCxUJo1v4Jhrz7Ch6eXhQJLZ1yznir1nlq6/x9mlMra/2re2plwkrgbWPMm0AJa+0lAGttUnJSpyhQ2xhTOTP7yUBja2315H13v8Y6f2WgMWYT0AO40dbwf4wxm4FVQDFcSam/0jz530ZgA1A+1XYHrbWXr4KsB0KMMb5APWBacjm/Awql2t+0ayW0jDE9jDHrjDHrls2YeIOhZSwpMZFZX39I+XpNKF2rPhfCThIZforx7/Zi1GtPEn02nInvPU/M+bQd/E7s2cGBjasY9dqT/P5tf47u3MzsYWk7l4Ud3gdAQMGi7Fw+n9YvvMOZ44c4dypnzLOTlJjI3G8/psxd91DyDtcdFd5+ATgcbhiHgwoNWxJ2MP3d+pcd3bqOfMVL4e2Xvut1xJH9APgVKMKelX/Q7Nm3OXv8MBdO54zYM7J+4Ryq3NUQgKr1GnN0b/q7by7btWE1RUqWIY9/YLrXjh9w/c2CCxdj/aI5dO7dl1NHDhJ+4tjNKXgmJCUmMmXQ+1RtcC8V67hi9vELIOqcq3dN1Lkz+OS9dtJn38Y1FAotg28G8Z886BqeLKhQUTYvmcujr75P2NFDnDmZ/fFXLepH/dL5mN6rLn0fqMQdJQJ4r01FzsXEE5TcOyvIx4Pzyb22rjZry0m6jVnL8xM2EHkpgaNn01blZQq4hmQ4evYiLasU4r2Z2ymZz4eiATnvjqTouCQ2HTtP7ZBAWlQsyJJ9rp5li/aEU75A+uEz7ijuz8nIWC5cSiDJaVmyL4JKhdMOy1A62BX/sXMXaV6hAH1/3UloPh+K+Oe8+G9U09ZtGTjiBz78aji+ef0oVLR4mtcP7N0NQOGixVk89zde+6A/Rw4e4OSx7E3oeVa9G/9Or+Lf6VUcPnlxCyqEb9NHiZw1GhvrGm41V7EyOCPPYi/FgNNJ/P4tuBcKSbcvGx9H9PwpnJ/0OdFzJ+Hw8sUZmfa30bt2My6unU/usjVIDDtG9PwpeNe7L92+cgL/gEDORrg+92cjIvAPSP9bli84PxHJw2gARISfTumVcNn+Pa5jX6RYcRbM+Y03+/XnyIH9nDiac5K5SYmJ/Dj4Ayrd3ZTyta/cFbplyVz2bVhF2+ffynAYiWN7trF3w0qG/udxZnz9MYe2b2Lm0LQ3/Zw65KrzAwsWZevSeTz00nuEHz3E2RxQ51+Pek1bsGbptYfK3LRmBaFlyuEXkH5I4kPJ3/uCRYuzdN5vvPTe/3H00P5s/95ntTx58lDzjlqsWrkizfLg/AU4fepUyvPw06fJF5x2QvnRI4bTpdvTzJszm3LlK/Df9z5g2NAh3EquFT/Avr176P9hPwYMGoyff/p21a0e/+1Uj2ZWYD7XZ94/IJA69e9h787taV6/uv3w+gf9OXJwPydyeD3iny+Y6nUbYowhpFxFjDFEXzXKw65N6wgqUIg8fgG4ubtTvW5DDuxMc88tR/e7zpvyFynG6gWz6d7nQ04ePkjY8aNZFsuNyl+0BE+/9xkvDhxOtfpNCSxYON06fkHB5A0KpnjZigBUqduI4wf2plnn8vPgwkXZsGguj7/+AaePHCQiB50zTvpxBu279KB9lx5cvHgJb29Xm75hvTokJiZy7nz64ar/yqXYWH7+fS4dHmrLl8NG0u/t3lQsV5Zf5/7xTxc/UyYtWE37vt/Svu+3hJ3PeG7YVTsPUCSfP4F5fMjl7sa9NSuweX/6z2yBgLzcXak03rk9CMjjwx1lS7D72Ok06wz7ZRHPtG7Ib2u2UrFEYfo91Zavfpp/U2L7J12r/RQYnJ/A4PyUruC6RFqnYZOU9tJlt2L76XrqPIB6zdvQ58vRvPrpULzz5CV/4bTDa96KdR7c3m2dv8s4zL/yX051uya1ThtjCgEk/x8GYK2dCDwAXALmGGOapN7IWnseWMSVeboytZ8btB24409e752cGGtmrU0/W+U1GGPuAe4F6ibPF7YRuJ5bZQzQ/3Iyzlpb2lp7ecbI1BOzJOHqCegAzqdav7q1tkKq9WKu9UbW2uHW2lrW2lr12/393k7WWuaP+pzAwsWo2fJhAPIVC6XnkKk8PWgcTw8ah29gMI/1G4rPVRfv6z/aje5fTODpQeNo1estilWoRstn30yzzsofx1L3oSdJSkzE6XTNY2OMITE+9m+X/e+y1rJ47Bf4FypG1eZXJqdMnbw7uGEFgUWuPdfMvjWLKVU74+7ia2eMo1a7zjiTErFpYs94rp6cIG9AEAe2u3Kw+7ZuIF+ha4/vvWnZlaEHrzZn4miad+pGUlLa454Ql/3HHVzHfuawgQQXKU69Nlcmoy5Xqx6bFs8BYNPiOX86dODW5QtShh682oIp39P40adISkq6Kv7sP/bDFh/gwW9W0P7blbz/83bWHz5Hv1k7WLYvglZVXLn1VlUKsfQaQwf6e7vuxCyQNzeNygUzf0fak5JnGpRk5NIDuDscXP6dd1qLZ65rdc7NWn5eufDN7SqLh5uDO4oHcOTsRc5Ex1G9qB8ANYv5c+x8+vsuwqLiqFgoL7ndXc2EmsX9OXz2Ypp1nq4XwuiVh3B3Myl3JDmtxdP91m9aXDjnqhvDT59i9ZKF1G/aPM3rk0cNo0O3nmnqe4fDEBebvd/72C3LOT/pc85P+hyMg7ytnyJqziSc5698xp1R53EvWAKS7zTOVawMSWfTD6VjPDzB4fr85K5Uh4TjB7Cp6nSHXz4cPn4kHj+AcfdwDfFpwbjlzEEAat/dkAWzZwGwYPasDId9KlO+IieOHeHUieMkJCSw9I951KnfMM06E0YO47HuPUlMTMSZ5LonxzgcxOWgOv/X4Z+Rr0gJ6rS+MhH3/s1rWPnLZNq//iG5cmfc3GvcsTsvDpnM819NoN2L/yWkUnXaPv9WmnWWTBtDw0e64ExKuvJ77zAk5ODf+9QXTTasWErhYtdu66xYODdl6MGrTRvzHY906ZHm995hHMTnkGN/M507d5aoKNcQ1bGxsaxds5oSISFp1mnQqBG//zYLay3btm7Bx9eXfPmuJLWOHjlMREQ4Ne+oRWxsLA6HA4whPgd/di67nvhPnTpJn96v816/DyleIv1n7FaO/7LbpR7NrNhLl7h0MSbl8eZ1qygemnZ4+0mjhtEpuf2Q5HTF7nA4sr398Feq3dWQPVtcPbpPHz9CYmIivlfdEBcQXICDu7cTHxuLtZbdm9dTsFhImnVmjR9J68e7u+aZSvUbkpPr0egLrmE2nU4nC6b/QJ3mD6RbJ09AEP758hN+3PV7s2/regoUTVsPzJs8iuYdXeeMV86XHcTngGsFl3V6uB3Txw5n+tjhOByOlOHbt+7YhdNa/P3y/sUe0vt+whQef/Qhcrm7ExcXj8F1gTQ2h3zmOzWpw/T3ezH9/V7k9884vkKBfmw5cIxLcfFYa1m98wChhdLPydykenk27D1MYlISl+Li2XrgOCVTrXf49BnCz0dxZ7kQYuMTUuaqiUtIvGnx/R3X037yDwwiKDg/J466RmnYtmEdRUqEplnnVmw/XU+dBxB13lU/nA07xeaVi6nV6N40r99KdZ7aOv+M7E4+3W5JrZx55eHm+xnogmu+qC7ATABjTEnggLX2q+THVY0xW4EEa+355GH67uXK/E/XvR9gQSbL2h8YYIxpY609ZYzJDfS01n6Vyf1d5gecs9ZeTJ7j6q5UryUYY3JZaxOAKCD1bfxzgA+NMROstdHGmCLANSfSsNZGGmMOGmMesdZOM65bg6taazdfa5ub5cTe7exc8Qf5ioYy/t1eANzdviuh1TKeVyL63Bnmjx5Mu9c++st971u/ggKh5fBNvrO3UOkK/PDfnuQrFvqXc3VlhVP7drB35QICi4Qwve8LANR+sAv71izizNEDgCFPvgI06PwiADHnz7B4zJfc93I/ABLiYjm2Y2PK66kd3LiC4JCy+Pi7Yi9QqgLT3u9FYNFQgoqVzJoA/8KEQX3Zv30TMZEX+Kh7e5p37Er753ozc9TXOJ1JuOfyoP1zrwNw4WwE04cO4Ol3BwAQHxfL3k3rePjZ19Ltd9vqpRQtUx6/QFdjtUS5Sgx66SkKhZSicGj6btrZ4cjubWxeMo8CxUvybe9nAGja6WkatOvE1MH92LDgd/zy5efRV98HIPJsBD9/9xlPvPUJ4Ip//5b13N8j/bCUO9cso3CpcuRNjr9YmYoMfe1pCpQoScGQ7P/cX8sPKw/zYbvKtKlaiNORsbwzw3U/QD5fD/q0Ks/r01xD8f3fg1XI65WLRKeTQXP3EBV35YSjQZl87DwZSUS0q5fXtuORjOtWm/3h0ewLi07/ptkgyMeDt1qUw2EMDmNYuCeclQfPEh2XyAv3lMbNYYhPdDJo/p6U9Xs3K0ufGdvYeSqKxXvDGfH4HSQ5LXvDo5m19WTKvuuXCmLX6aiUucl2nIhkdOc72B8ew/6Ia96rkKUG93uH7ZvWE3XhPD3at6FD12fwzZuXUV8OIvLCOfq/9Sohpcvw7sCvORsRzrcDP+a/n34BwMD33iQ6MhI3dze6v9w7zcTJa5YuonT5iil3Y5etWIVXu3aieKnShJQumy2xZsS7TnOMpze+jV03MlinkwtTviDx9BHi923Bv+OrYJNIDD9O7PaVAHhWds2JGLttJW6BBcjTvBPWaUk6e4roP6am2b9PvVbErPgdgLg9G8nbpite1RtwcdXsLIwyYwM/+C/bNq4n8sJ5uj7Umk7devDwE10Y8N5bzPv1Z4LzF+DND1113JmIcIZ8+hHvD/wSN3d3er7yBh+89h+cziTubf1AmguSq5YsokyFigQlH/vylavwYpeOhJQqTWgOOfbHdm9j27L5BBcLZeRbPQG459FuzBs3lMSEBCb1d92QU6R0BVr9P3v3HR5F8cdx/D1JkJbQu4hUqYrSi6iAShfpJYHQu0gVFJCOgAXpVXoTBAFpShcVkGJHUH+KiIokdJD0+f1xR0hIAuHAy5F8Xs9zD7nd2b2ZY3duZ787Mx37cPl8MJvnvEOLQeNuu+/jBz8nd8Gi+GV21PkPFinB3EGdyPFQQXI+7Bl1/tSxQ/nxmyNcvniBXi3r0ySwC18f+Jy/T53EGC+y5cxFxz6O7+B8cBBz3hnLoHGO8z40JITvD39Jpz6vxtnvwc/3ULBoCTI7/++LlCjFoE6teahgYR4u5Bn/9/+ls8HBjBr+OlFRUdioKGo89xxPVnuKtR+sBqBx02ZUqfokX3z+Gc1efIHUadIwdPiIWPuYNWM63Xr0BOC5WrUZNKAvq1Yup3PX7u4uzh1LTPnnz53DpYsXeGuCo3ejt7c3C5bcGGnifit/Sq5H3xk1hO+d1w+dmtajZfsu+GbIwLzJb3Hp4nnGvtqXAoUf4XXn9cOMN8cwdMJkLpw/y4RhrwAQFRlBtZq1KVPxxjzLB266fiha4lH6tG/Jwx5UdoD5E4fz83dfc+XSBYYENqKef0cqP1ePpZPfYEyPNvikSkXbvkMwxnDhbDDLpoyn58i3KFC0JE9Urc74Ph3w8vImb6FHqFr7RgDom32fkq9IMTJldfyGFChWkrE925InfyHyFrybKcjvnRXvjOLXH77m6uWLjOvclOdatCc05Br7t64DoGTFapSrUQdwtJnWzHiT9kMdt4Ze6NiblZPHEBkeQZacuWnaa3D0fn84sJe8hYtFt5nyFS3BpL7tyf1wIfLk94w2480+2fUpqz7cgLePN2keSM2bI4dG9/Lu3v9VRg7uT47s2Vi2ei3zl73P2XPnaNK2M9UqV2Dkq4529ZmgYH44dpweHQMBaNuqGf5deuHn58vkN0YlWdkSEnzxMi3GzOHqtVC8jGHJ9v2sH9WTxwrm5bmyJWg+ejY+Xl4Uy5eLZk85hlZctfsgAM2fKU/BPNmpWqowTUbMxMsYGlcrQ5EHb8wxOOXDHfRu5HhQtk6FR3l5+kqW7dhPz4Z38wz8vXE310+BvQYw/Y3XiQiPIEfuPHQdOCx6v/fD9ZOrdR7A3HFDuHr5Et7e3jTv1o90vjfajPdDnRdTSrzWkfufsfGMf55cGGNWAM8A2YB/gOHW2veMMVmBVUA+HHNBNXPOLfUqEIAjSHMaaI1jyMFFOObF8gJWWWtHOfef6P1Ya2ON2WOMOYFjrqtg5/tngAHW2vrGmHbEngerPdAfRy8pC8y31r5jjFkIbLTWfnCL7+DmfZ0AyuEIVq0DHgSOA9mBEdba3caYCTh6mh2x1vobY5bjCMxtsdYONMa8zI3hEq84yxrpzEsp5+cMAHyttSOMMQWAmTiGHUwFrLTWjkpM/q+buf9E8j1Qb+NauNumG/M4hbPe2dxPyc2/Kfj/fuqW47dPlIylSu0Zvb2SytSWjyd1FpJM7tWe18h3p7Mthyd1FpLM/j/ufFif5KRUjrhDoaYkZR/KdE8fgzx3+d8Ue+2ckgVd88wn/t0lIuVeOvP3lZTzJHx8Yj58ltLUy5WCD3yAHz9L6hwkme/y1759omTsfEiCz9cne2Vypex7ZVn80nlk96GKo7Yly+vvA68/55Hfd7IOaknyoaBWyqSgVsr9v1dQS0GtlEpBLQW1UioFtRTUkrunoFZS5yDpKKiVco99BbUU1EqpFNRKuTw1qFVpzPZkef29f+izHvl93/8TX4iIiIiIiIiIiIiIiEiyp6CWiIiIiIiIiIiIiIiIC4yXSZavRJXdmNrGmOPGmF+MMYNvka68MSbSGNP0br9vn7vdgYiIiIiIiIiIiIiISErklcgAUHJjjPEGpgPPAaeAg8aYDdbao/GkmwB8fC8+V0EtERERERERERERERERFxiTMoNaQAXgF2vtrwDGmJVAQ+DoTeleAtYA5e/Fh2r4QREREREREREREREREbkTDwJ/xHh/yrksmjHmQaARMOtefah6aomIiIiIiIiIiIiIiLjAJNOuQ8aYLkCXGIvmWGvnxEwSz2b2pvfvAoOsQvcjYAAAIABJREFUtZH3qkebgloiIiIiIiIiIiIiIiIuSK5zajkDWHNukeQU8FCM93mBv25KUw5Y6QxoZQPqGmMirLXrXM2XgloiIiIiIiIiIiIiIiIuMMk0qJUIB4EixpgCwJ9AS6B1zATW2gLX/zbGLAQ23k1ACxTUEhERERERERERERERkTtgrY0wxvQCPga8gfnW2h+MMd2c6+/ZPFoxKaglIiIiIiIiIiIiIiLigns1V9T9yFq7Gdh807J4g1nW2nb34jMV1BIREREREREREREREXFBcp1Ty1MpqCUiIiIiIiIiIiIiIuKCFDynVpLwSuoMiIiIiIiIiIiIiIiIiNyOemqJiIiIiIiIiIiIiIi4wFs9tdxKQS0REREREREREREREREXKKjlXgpqiYiIiIiIiIiIiIiIuEBBLffSnFoiIiIiIiIiIiIiIiLi8dRTS0RERERERERERERExAXqqeVeCmqJiIiIiIiIiIiIiIi4QEEt91JQS0RERERERERERERExAU+Cmq5lebUEhEREREREREREREREY+nnloiIiIiIiIiIiIiIiIu0PCD7qWgloiIiIiIiIiIiIiIiAsU1HIvBbVERERERERERERERERc4O2lWZ7cSd+2iIiIiIiIiIiIiIiIeDz11BIREREREREREREREXGBhh90LwW1REREREREREREREREXKCglntp+EERERERERERERERERHxeOqpJSIiIiIiIiIiIiIi4gL11HIvBbVERERERERERERERERc4G0U1HInBbVERERERERERERERERcoJ5a7mWstUmdB5Hbunj1Woo9UNMQkdRZSDo2KqlzkKRMZHhSZyFJBUWlTeosJJmMqVP2lJcnL6XcY79w2rCkzkKSukDKPe+vhKfs37y8XpeTOgtJ6oEsee5pKzjswpkUe+1sQq8mdRaSzL++uZI6C0kqTVRoUmchyXhdu5DUWZAkMu/XpM5B0iqXJ2NSZyHJlLnyTVJnIUkZn1RJnYUkE5zz8aTOQpLKkTG9R0aPXv7wu2R5/T250aMe+X2rp5aIiHiclBzQEhERERERERGR+4d6armXgloiIiIiIiIiIiIiIiIu8FFQy60U1BIREREREREREREREXGBemq5V8qetENERERERERERERERETuC+qpJSIiIiIiIiIiIiIi4gL11HIvBbVERERERERERERERERcoKCWeymoJSIiIiIiIiIiIiIi4gIFtdxLc2qJiIiIiIiIiIiIiIiIx1NPLREREREREREREREREReop5Z7KaglIiIiIiIiIiIiIiLiAgW13EtBLRERERERERERERERERcoqOVemlNLREREREREREREREREPJ56aomIiIiIiIiIiIiIiLhAPbXcS0EtERERERERERERERERFyio5V4KaomIiIiIiIiIiIiIiLhAQS330pxaIiIiIiIiIiIiIiIi4vHUU0tERERERERERERERMQF3kY9tdxJQS0REREREREREREREREXeCmo5VYKaomIiIiIiIiIiIiIiLjAWzEtt9KcWiIiIiIiIiIiIiIiIuLx1FNLRERERERERERERETEBV5e6qrlTgpqiYiIiIiIiIiIiIiIuMBbc2q5lYYfFBEREREREREREREREY+nnloiIiIiIiIiIiIiIiIu8FJPLbdSUEtERERERERERERERMQF3ik4pmWMqQ1MBryBedba8Tet9wcGOd9eAbpba7+5m89UUEtERERERERERERERMQFXl4pM6pljPEGpgPPAaeAg8aYDdbaozGS/QY8ba09b4ypA8wBKt7N52pOLREREREREREREREREbkTFYBfrLW/WmvDgJVAw5gJrLVfWGvPO9/uB/Le7Yeqp5aIiIiIiIiIiIiIiIgLUvCcWg8Cf8R4f4pb98LqCGy52w9VUEtERERERERERERERMQFyXVOLWNMF6BLjEVzrLVzYiaJZzObwL6q4whqPXm3+VJQS0RERERERERERERExAXJtaeWM4A15xZJTgEPxXifF/jr5kTGmMeAeUAda+3Zu82X5tQSERERERERERERERGRO3EQKGKMKWCMeQBoCWyImcAYkw9YC7Sx1v50Lz5UQS1Jkf45fZruXTrRvHEjWjRtzMrly+KkOfHbb3QIbEvViuVZunhR9PLz58/RuUM7WjZrwu5dO6OXD+jbh6CgM27J/9367bcTBAS2p2zFyixcvCTBdIOHDKVBo8Y0atac10eMJDw8AoBtO3bQqGlzAjt04sKFCwD88ccpXhn8qlvyf7d+++0EAe06ULZSVRYuXppg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Y8aY7MD71toaMfa5F8fwhb7OtB2ttR8787ISx5CGXwEB1trQ+PbrXFbemffMOHpenbbWlnSu6wBcn219rLV2QYzPb4kj+BR7Ru4b63fjmOvqkPN9fmCjtbaUMeYZYs+DVR8YCaTDMVziJmvtQGPMCODKrXpqxbOv6M919s6qCPwKhOIYCnChMeYloCfwt7W2ujFmAvACcMRa62+MaYEjgOcFhAM9rbX7jTFXrLW+zs9pCtS31rYzxmQDpuOYR8sH+NRa2y0x+b/uXvfUup+k4W46+t3nbHzTzKUcJtJzntxxt6ColD2paMbU7hwd2POcvJRyj/3CaT33yXV3uEDKPfevhKfs37y8XpeTOgtJ6oEsee7ps51hF86k2GtnE5rgQBDJ3r++uZI6C0kqTVTKeRr8Zl7X4h8+TJK/eXFHQk1RyuVJeEjQ5K7MlYQGdEoZjE+qpM5CkgnO+XhSZyFJ5ciY3iP7RO35X3CyvP5+ulA2j/y+3RLUum0mjBkAZLTWDktk+nI4ht2r5nwfAOS11o6/y3zE2u9d7msa8JW19t5PBpUCKaiVQimoldRZSDIKaimolVIpqJVyz30FtRTUupf7U1ArZVJQS0EtSXkU1FJQK6VSUCvl8tSg1t5fzybL6+9qBbN65Pfttjm1EuKcX6sQUON2aZ3pBwPdiTHnlbV26T3IR5z93sW+DuMY2q//3e5LREREREREREREREQ8k3fKfjbZ7ZI8qGWtjTuT863TjwfuqkfWf71fa23Z26cSERERERERERERERGRxEryoJaIiIiIiIiIiIiIiMj9yMt45Ch9yZaCWiIiIiIiIiIiIiIiIi7wVlDLrRTUEhERERERERERERERcYF6armXpjATERERERERERERERERj6eeWiIiIiIiIiIiIiIiIi7wVtcht1JQS0RERERERERERERExAUaftC9FEMUERERERERERERERERj6eeWiIiIiIiIiIiIiIiIi5QRy33UlBLRERERERERERERETEBV4oquVOCmqJiIiIiIiIiIiIiIi4QD213EtzaomIiIiIiIiIiIiIiIjHU08tERERERERERERERERF3ipp5ZbKaglIiIiIiIiIiIiIiLiAg0/6F4KaomIiIiIiIiIiIiIiLjAC0W13ElzaomIiIiIiIiIiIiIiIjHU08tERERERERERERERERF2j4QfdSUEtERERERERERERERMQFXgpquZWCWiIiIiIiIiIiIiIiIi5QTMu9NKeWiIiIiIiIiIiIiIiIeDz11BIREREREREREREREXGBlybVcisFtURERERERERERERERFygmJZ7KaglIiIiIiIiIiIiIiLiAs3x5F76vkVERERERERERERERMTjqaeWiIiIiIiIiIiIiIiIC4zGH3QrBbVERERERERERERERERc4KWYllspqCUiIiIiIiIiIiIiIuICddRyL82pJSIiIiIiIiIiIiIiIh5PPbVERERERERERERERERcoJ5D7qWgloiIiIiIiIiIiIiIiAuMxh90KwW1RETE42T3ukZQVNqkzoaIiIiIiIiIiMgteSmm5VbGWpvUeRAREREREREREREREbnvnL38b7IMsmT1S+eR4Tr11BIREREREREREREREXGBR0Z+kjEFtURERERERERERERERFyg4QfdyyupMyAiIiIiIiIiIiIiIiJyO+qpJSIiIiIiIiIiIiIi4gJj1FXLnRTUEhERERERERERERERcYGGH3QvDT8oIiIiIiIiIiIiIiLiApNMX4kquzG1jTHHjTG/GGMGx7PeGGOmONd/a4wpk8hdJ0hBLREREREREREREREREUk0Y4w3MB2oA5QAWhljStyUrA5QxPnqAsy828/V8IMiIiIiIiIiIiIiIiIu8Eq5c2pVAH6x1v4KYIxZCTQEjsZI0xBYbK21wH5jTCZjTG5r7d+ufqiCWiIiIiIiIiIiIiIiIi5IuTEtHgT+iPH+FFAxEWkeBBTUEhERERERERERERERcSdjbVJn4T9hjOmCY8jA6+ZYa+fETBLPZjd/GYlJc0cU1BIREREREREREREREZFozgDWnFskOQU8FON9XuAvF9LcEQW1REREREREREREREREXGGjkjoHSeUgUMQYUwD4E2gJtL4pzQagl3O+rYrAxbuZTwsU1BIREREREREREREREXGJSaFBLWtthDGmF/Ax4A3Mt9b+YIzp5lw/C9gM1AV+Af4F2t/t5xqbTMd7FBERERERERERERER+S+FXrmYLIMsqX0zxjcfVpLzSuoMiIiIiIiIiIiIiIiIiNyOhh8UERERERERERERERFxhUbDcysFtURERERERERERERERFyRQufUSioKaomIiIiIiIiIiIiIiLjAKKjlVppTS0RE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cSQn1kgtERERERERERERERER+f/SyhiTBeD9P/Ygx70I/A044jkcNVJLRERERERERERERESkEcxRuqaWZVnXAdftt2miMWbifvt/BFo3kPS+Izz/GCDbGLPUsqxhR5ovBbVEREREREREREREREQawRyl0w96A1gTD7F/5MH2WZa1x7KsOGNMlmVZcUB2A4cNBs60LOt0IBAItyzrA2PMpYfKl4JaIiIiIiIiIiIiIiIijaA1tRr0NXAF8LT3/ykHHmCMuRe4F8A7UuuuwwW0QGtqiYiIiIiIiIiIiIiIyP+fp4GTLcvaBJzs/R7LsuIty5r6e06skVoiIiIiIiIiIiIiIiKNcLSuqfV7GGPygJMa2L4LOL2B7T8BPx3JuRXUEhERERERERERERERaQS3W9MPNiUFtURERERERERERERERBrBaE2tJqU1tURERERERERERERERKTZ00gtERERERERERERERGRRnBrTa0mpaCWiIiIiIiIiIiIiIhII2j6waaloJaIiIiIiIiIiIiIiEgjKKjVtLSmloiIiIiIiIiIiIiIiDR7GqklIiIiIiIiIiIiIiLSCFpTq2kpqCUiIiIiIiIiIiIiItIIxq3pB5uSgloiIiIiIiIiIiIiIiKN4NaaWk1Ka2qJiIiIiIiIiIiIiIhIs6eRWiIiIiIiIiIiIiIiIo1gtKZWk1JQS0REREREREREREREpBGMph9sUpp+UERERERERERERERERJo9jdQSERERERERERERERFpBLdGajUpBbVEREREREREREREREQawbi1plZTUlBLRERERERERERERESkETRSq2kpqCXHjLKCHBa+/yKVJYVgWbQbdCodh51Bwc6tLJn0Gi5nDZbNRr/z/0qLlA510rpqqpnx0t9xO2twu10k9RpE99MvBmDFlHfJWreUqIQ2DLzsdgC2LZpFdXkpHYed0eTlbIif3eKJ0V3xs1vYbRbztuXzybKdXNgnkZM7xlJcWQPAB4t3sHRnYb30Ey/oTUWNC7cxuNyGu6asBuDy/sn0SYpgW145L83eAsCw9jGEBjj4ds3upivgYZTm5zD7recpLy7Asmx0GjKKbiedBcCamV+zdta3WDY7Sd37c9x5VzV4DrfbxZQnxhMc2YJTb3kYgEWfv8WO1UtpkdSWYVfdCcCm+TOpKi+pPb+vlRXkMP+9F6ko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+vZYUtHAdJDHiomhv/g6Cz6184e5vs6Cz0y99NnDH3QUW7gl7/AHHaX+dlKar7PgU70SIpvH8O4DfNm661HZEDl795pm+fvWSK0/OWPMVuDgY8DhBOCt/UddGWNGHOL4nsDKhgJa3rTvAO8csG3Yfl+n7vf1LuBg8wYecUBLRERERERERERERKQ5asbLTx2VFNQ6ilmW9SXQDjhUEGv/4+8BbgAaXkxARERERERERERERERqafrBpqWg1lHMGHP2bzz+aeDpPyg7IiIiIiIiIiIiIiJHFY3Ualo2X2dARERERERERERERERE5HA0UktERERERERERERERKQRNP1g01JQS0REREREREREREREpBE0/WDTUlBLRERERERERERERESkETRSq2lpTS0RERERERERERERERFp9jRSS0REREREREREREREpBE0/WDTUlBLRERERERERERERESkERTUaloKaomIiIiIiIiIiIiIiDSC1tRqWlpTS0RERERERERERERERJo9jdQSERERERERERERERFpBE0/2LQU1BIREREREREREREREWkETT/YtDT9oIiIiIiIiIiIiIiIiDR7GqklIiIiIiIiIiIiIiLSCJp+sGkpqCUiIiIiIiIiIiIiItIImn6waSmoJSIiIiIiIiIiIiIi0ggaqdW0tKaWiIiIiIiIiIiIiIiINHsaqSUiIiIiIiIiIiIiItIImn6waSmoJSIiIiIiIiIiIiIi0ghuX2fgGKOgloiIiIiIiIiIiIiISCNopFZ9lmVFA5OAVCAdON8YU9DAcZHAG0A3wABXGWPmH+rcWlNLRERERERERERERERE/r/cA8wwxqQBM7zfN+QlYJoxphPQE1h3uBNrpJaIiIiIiIiIiIiIiEgjuDRQqyFnAcO8X78L/ARM2P8Ay7LCgSHAXwCMMdVA9eFOrKCWiIiIiIiIiIiIiIhIIxyt0w9alnUdcN1+myYaYyYeYfJWxpgsAGNMlmVZsQ0c0xbIAd62LKsnsBS4zRhTdqgTK6glIiIiIiIiIiIiIiLSCEfrSC1vAOugQSzLsn4EWjew674j/BEOoA9wizFmoWVZL+GZpvCBwyUSEREREREREREREREROSLGmJEH22dZ1h7LsuK8o7TigOwGDtsJ7DTGLPR+/xkHX3urloJaIiIiIiIiIiIiIiIijXC0Tj/4O30NXAE87f1/yoEHGGN2W5a1w7KsjsaYDcBJwNrDnVhBLRERERERERERERERkUY4Wqcf/J2eBj61LOtqIAMYB2BZVjzwhjHmdO9xtwAfWpblD2wFrjzciRXUEhERERERERERERERaQSN1KrPGJOHZ+TVgdt3Aafv9/0KoN9vObftd+dORERERERERERERERE5A+mkVoiIiIiIiIiIiIiIiKNoOkHm5aCWvKncPPxyb7Ogs+YgUm+zoLPuH2dAR8rrT6GfwO94imocPo6Fz5TVHXslh3Akbvb11nwmdh+ub7Ogk91Swj3dRZ8Ji4y0NdZ8KmEUD9fZ+Goct2AY7f96HQn+joLPlPjPrbfpjiP4fIf63/7/PIaX2fBZxy5O32dBZ9qfXyRr7PgM4nhx3bbMaRTrK+z4DOtQtRubo5eN+mWr/NwLNH0gyIi0uwcywEtEZH/Y+++w6OoujiOf+8mQBq9996lSBEFpCNVpIrSOyqC0qUjSBMEsYCAIL1IUVCqSIfQkS6K9E4gkATSM+8fu4SEhBIFsnn5fZ4nD7szd4Z7Ntmds3Pm3hERERERERGR2KmoJSIiIiIiIiIiIiIiIk5PRS0RERERERERERERERFxeipqiYiIiIiIiIiIiIiIiNNTUUtEREREREREREREREScnopaIiIiIiIiIiIiIiIi4vRU1BIRERERERERERERERGnp6KWiIiIiIiIiIiIiIiIOD0VtURERERERERERERERMTpqaglIiIiIiIiIiIiIiIiTk9FLREREREREREREREREXF6rnHdwBjjARQH0vFAUcyyrGVPqV8iIiIiIiIiIiIiIiIikeJU1DLGVAMWAKljWW0BLk+jUyIiIiIiIiIiIiIiIiJRxXX6wYnASiCLZVm2B35U0BIREREREREREREREZFnIq7TD+YA6lmWdekZ9EVEREREREREREREREQkVnEdqbUdyP8sOiIiIiIiIiIiIiIiIiLyMHEdqfUdMM4Ykwk4DIRGXWlZ1v6n1TERERERERERERERERGRe+Ja1Fri+HdqLOssQPfVEhERERERERERERERkacurkWtnM+kFyIiIiIiIiIiIiIiIiKPEKeilmVZZ59VR0REREREREREREREREQexhbXDYwxRY0xs40xe40xe4wxs4wxRZ5F50REREREREREREREREQgjkUtY0w9YD+Q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1ftv3RERERERERERERERERGJ6UlGat145r0QEREREREREREREREReYQnKWr9AHhblhX6rDsjIiIiIiIiIiIiIiIiEhvbE7TZCKQAMMacMsakfrZdEhEREREREREREREREYnuSYpavkAux+McT7iNiIiIiIiIiIiIiIiIyFPzJNMPLgU2G2MuAxaw1xgTHltDy7JyxbZcRERERERERERERERE5L94kqLWe8AKIC8wHvs9tvyfZadEREREREREREREREREonpsUcuyLAtYCWCMKQZ8YVmWiloiIiIiIiIiIiIiIiLy3DzJSK1IlmW1fVYdEREREREREREREREREXmYOBW1jDFuwEdAVSAdYIu63rKsok+vayIiIiIiIiIiIiIiIiJ2cSpqAZOABsBiYAdgPfUeiYiIiIiIiIiIiIiIiDwgrkWt+kATy7LWP4vOiIiIiIiIiIiIiIiIiMTG9vgm0dwFzj+LjoiIiIiIiIiIiIiIiIg8TFyLWp8DPYwxcd1ORERERERERERERERE5F+L6/SD1YHXgZrGmGNAaNSVlmXVe1odExEREREREREREREREbknrkUtH+CnZ9ERERERERERERERERERkYeJU1HLsqy2z6ojIiIiIiIiIiIiIiIiIg/zREUtY8yKJ2hmWZb11n/sj4iIiIiIiIiIiIiIiEgMTzpS68Yz7YWIiIiIiIiIiIiIiIjIIzxRUUvTDoqIiIiIiIiIiIiIiEh8ssV3B0REREREREREREREREQeR0UtERERERERERERERERcXoqaomIiIiIiIiIiIiIiIjTU1FLREREREREREREREREnJ6KWiIiIiIiIiIiIiIiIuL0VNQSERERERERERERERERp+ca3x0QeRKeiV7c+qsx8d2D+BMQEhHfXYhXXolf3L97r8SJuR0cHt/diDceiVziuwvxK2WZ+O5BvEmZNEV8dyFevZrmxY5f5Glxc31xE8jQFzh9jAi14rsL8crF5cX9u3e1vbixA6T1TBzfXYg/nkXiuwfxyj2xW3x3Id68lN4rvrsQrwJf4AN+4hf4eCdyz4t7xlRERJzWi1zQEhERERERERERkdipqCUiIiIiIiIiIiIiIiJOT0UtERERERERERERERERcXoqaomIiIiIiIiIiIiIiIjTU1FLREREREREREREREREnJ6KWiIiIiIiIiIiIiIiIuL0VNQSERERERERERERERERp6eiloiIiIiIiIiIiIiIiDg9FbVERERERERERERERETE6amoJSIiIiIiIiIiIiIiIk5PRS0RERERERERERERERFxeipqiYiIiIiIiIiIiIiIiNNTUUtEREREREREREREREScnopaIiIiIiIiIiIiIiIi4vRU1BIRERERERERERERERGnp6KWiIiIiIiIiIiIiIiIOD0VtURERERERERERERERMTpqaglIiIiIiIiIiIiIiIiTk9FLREREREREREREREREXF6KmqJiIiIiIiIiIiIiIiI01NRS0RERERERERERERERJyeiloiIiIiIiIiIiIiIiLi9FTUEhEREREREREREREREaenopaIiIiIiIiIiIiIiIg4PRW1RERERERERERERERExOmpqCUiIiIiIiIiIiIiIiJOT0UtERERERERERERERERcXoqaomIiIiIiIiIiIiIiIjTU1FLREREREREREREREREnJ6KWiIiIiIiIiIiIiIiIuL0VNQSERERERERERERERERp6eilrywtm/fTv236lHvzbrMmDE9xno/Pz96dP+Yt5s0pkXzZpw8+TcAN2/epG2b1jRu1JCNGzZEtv/444+4du3ac+v/f7F9+3beqlePN+vWZcb0mLHfc+TIEUq8/DK//fYbYI+9TevWNGrYkA1RY/8o4cQOEB4eTtvm79Cne7cY6yzL4stxY2jaoB6t332bE38eB8DX9ybvd2hLy6aN2bJpY2T7T3p+jM/1hBN707fq0Obdt2nf/B06tWoeY/22zZto2+z++kN/HADglq8vH3ZsR5t3mrA1Svz9e3XH5/r159b/uJo4ahgt33yDD1s1jVzm73ebQd270Pndhgzq3oUAf79Yt923awfvN2tEp3casGTuzMjlMyd/TdfW7zLhsyGRyzauWcWKxQueWRz/1jdjhtGm/ht81OZ+/Ds2reejNm/TqPIrnPzzWKzbXTx3hh7tm0X+NK9diV8Wzwfgf+zdd3QUVRvH8e9NglISUKqVIii9CAIioNKE0GvoCT00kV6lF2mKSJFmIEBClyK9BgTpiIqKr4oCNkKoAUmf94/dhIQEDS3ZNb/PORyyM3dm7zM7M/fOPFMWz51Bnw4tmT7hdvxB2zezcbXjxR/f0RMnqfBWfZr6dKGpTxc+9lucZLnA1euo7dWW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7xixx5fW+3rinEzSz0H+bzDGLAPeBLIDF4CRlmV9YozJBqwEcmN7F1Qz+7ulhgBtsCVp/gJaYXvkoD+292K5ACstyxpjn3+y52NZ1uU76vYrtnddhdg/vwn0tyyrrjGmHQnfg9Ue6IftLikL8LMs6wNjzCJgo2VZq/9hGdw5r1+BV7Alq9YBzwI/ADmAUZZlBRljJmG70+yEZVmtjTGB2BJzWyzLGmCMeYfbj0u8YY812l6XYvbv6Q+4W5Y1yhiTD/gY22MH0wHLLcsak5z6x/r7Vth/d0X9F3e87ipNuRERk9pVSFXR/+H987+5Fp6yr9lzNFFpO3xeTHc9tauQalz/+iG1q5CqfsheNrWrIKnkqUxp9Vo7m6weGR9qj+9S6N9pthMRGZNmQ+dWZNruO6dl0Wl3tQcgIg0vgBe5mNpVSFXmzx9Tuwqp5udnX0vtKqSqtNzmPZ/5sdSuQqrK9pD7zeKc/tNJLfnvUFIrbVJSK82u9kpqpe3wldRKw5TUSruU1FJS62FRUkvSojSc0wGU1ErLlNRKu9Jym6eklpJaknbfqSUiIiIiIiIiIiIiIiJOREktERERERERERERERERcXhKaomIiIiIiIiIiIiIiIjDU1JLREREREREREREREREHJ6SWiIiIiIiIiIiIiIiIuLwlNQSERERERERERERERERh6ekloiIiIiIiIiIiIiIiDg8JbVERERERERERERERETE4SmpJSIiIiIiIiIiIiIiIg5PSS0RERERERERERERERFxeEpqiYiIiIiIiIiIiIiIiMNTUktEREREREREREREREQcnpJaIiIiIiIiIiIiIiIi4vCU1BIRERERERERERERERGHp6SWiIiIiIiIiIiIiIiIODwltURERERERERERERERMThKaklIiIiIiIiIiIiIiIiDk9JLREREREREREREREREXF4SmqJiIiIiIiIiIiIiIiIw1NSS0RERERERERERERERByekloiIiIiIiIiIiIiIiLi8JTUEhEREREREREREREREYenpJaIiIiIiIiIiIiIiIg4PCW1RERERERERERERERExOEpqSUiIiIiIiIiIiIiIiIOT0ktERERERERERERERERcXhKaomIiIiIiIiIiIiIiIjDU1JLREREREREREREREREHJ6SWiIiIiIiIiIiIiIiIuLwlNQSERERERERERERERERh6ekloiIiIiIiIiIiIiIiDg8Y1lWatdB5F9FXA1OuytqTFRq1yDVRGfMmtpVSFUuUeGpXYVUZaIjU7sKqceKSe0apKqtf6Td+PM9mSG1q5Cqit78PrWrkHpM2r7WLOyZEqldhVSVMUN68zDnF3EtJO32ndOwiHSZUrsKqcrtoW5FzsVE3krtKqQqk4aPmX+49XhqVyFVZU3vltpVSDW5/j6X2lVIVdZjabfNi8mQJbWrkKoed8+Shlt8iZW2j55FRMQhpemEloiIiIiIiIiIiCRJSS0RERERERERERERERFxeEpqiYiIiIiIiIiIiIiIiMNTUktEREREREREREREREQcnpJaIiIiIiIiIiIiIiIi4vCU1BIRERERERERERERERGHp6SWiIiIiIiIiIiIiIiIODwltURERERERERERERERMThKaklIiIiIiIiIiIiIiIiDk9JLREREREREREREREREXF4SmqJiIiIiIiIiIiIiIiIw1NSS0RERERERERERERERByekloiIiIiIiIiIiIiIiLi8JTUEhEREREREREREREREYenpJaIiIiIiIiIiIiIiIg4PCW1RERERERERERERERExOEpqSUiIiIiIiIiIiIiIiIOT0ktERERERERERERERERcXhKaomIiIiIiIiIiIiIiIjDU1JLREREREREREREREREHJ6SWiIiIiIiIiIiIiIiIuLwlNQSERERERERERERERERh6ekloiIiIiIiIiIiIiIiDg8JbVERERERERERERERETE4SmpJSIiIiIiIiIiIiIiIg5PSS0RERERERERERERERFxeEpqiYiIiIiIiIiIiIiIiMNTUktEREREREREREREREQcnpJaIiIiIiIiIiIiIiIi4vCU1BIRERERERERERERERGHp6SWiIiIiIiIiIiIiIiIODwltURERERERERERERERMThKakladbuvZ/TuLUPTdu0p7lPJ06c/DrJcoePHcfLuwONWnozbPR4oqKiANixO4iGLdri06UHV69dA+D8b78zYNjIFIvhQZ367jQlK1Zj++69SY63LIuP5iygrldb6rfwIWDlGgB27NlLw1bt8OnaK2Hsw8ekWN0fxIEDB2hQvz716tbF75NPEo3ftGkTzZo2pVnTpnh7e/PDDz8AcPnyZdr5+NCkcWN2794dV773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lC1TmtFpQ/cd9wP/Q9rgW6hetSq9uqZy24hRPP/SK5Q75BDSbrtl7zrWrV/P4mXL6dfL+z64OrUdna+7kZSU8jxyT1rY7caDoN8rkpLHCtdbXRE3YnYM8Jr/NAmY6pwbGTL/dLxfbB2dVZlkZnOBani/DFoA3OCc2+zPKwX8CZzqnFtqZtWAF51z54es8yO87guT/WV7Oedm+2VJx+vS8Fugq3Nue7j1+tNO8cteGe+XV2udc038edcAWaMEj3TOTQrZfipe5dPpEd6TeXhjXX3tP68FvOWcO9bMziX7OFitgDSgHF53iTOcc4PMbDiwOa9faoVZ197t+r/OOg34GdiO1xXgs2b2L6Af8Ltz7jwzuw+4AvjGOdfFzDriVeAlADuBfs65z81ss3Mu2d9OO6CVc66HmVUFxuONo5UEfOicu6Eg5c+yI3Pdgd9R49We/fmhX8m2u1yVWBchphJ2xV8rpmix3fHV4j/qXLghFoNj1m/Bjb925fgeV+NAa7Llh/wXOlhZNHsFjz/bjjg+1kWIqXKHlI08SE8R7Ni0Pri5c4DtKFU+1kWIqaRiPYpKFtsZtkf/wLAAXzMv2xq/4xNFQ5WyUeuAKu7U+L/47L4xWlzp4H7n7TkkcnewQVAmuWKAv/ElS1QqtfIthNlAoKJzbli+C3vLN8Xrdu9s/3lX4Ejn3L37WY5s693PdY0DvnXOPZ3vwpIvVWoFkyq1VKkVWKrUinURYkaVWqrUCipVaqlSS/afKrViXYLYUaVWcK+ZVamlSq2gUqVWcKlSSyCKY2pF4o+vVQc4P79l/eUHA30IGfPKOfdCMZQj13r3Y13z8br2u3V/1yUiIiIiIiIiIiIiIiJxUKnlnGuT/1LZlr8X2K9fZB3o9TrnTs5/KRERERERERERERERESmoYPdzIiIiIiIiIiIiIiIiIiWCKrVEREREREREREREREQk7qlSS0REREREREREREREROKeKrVEREREREREREREREQk7qlSS0REREREREREREREROKeKrVEREREREREREREREQk7qlSS0REREREREREREREROKeKrVEREREREREREREREQk7qlSS0REREREREREREREROKeKrVEREREREREREREREQk7qlSS0REREREREREREREROKeKrVEREREREREREREREQk7qlSS0REREREREREREREROKeKrVEREREREREREREREQk7qlSS0REREREREREREREROKeKrVEREREREREREREREQk7qlSS0REREREREREREREROKeKrVEREREREREREREREQk7qlSS0REREREREREREREROKeKrVEREREREREREREREQk7qlSS0REREREREREREREROKeKrVEREREREREREREREQk7qlSS0REREREREREREREROKeKrVEREREREREREREREQk7qlSS0REREREREREREREROKeKrVEREREREREREREREQk7qlSS0REREREREREREREROKeKrVEREREREREREREREQk7qlSS0RE4o5LLBXrIoiIiIiIiIiIiEicMedcrMsgEvfM7Drn3MRYlyMWghw7BDv+IMcOwY4/yLFDsOMPcuwQ7PiDHDso/uIU9PcyyPEHOXYIdvxBjh2CHX+QY4dgxx/k2CHY8Qc5dokf+qWWSMFcF+sCxFCQY4dgxx/k2CHY8Qc5dgh2/EGOHYIdf5BjB8VfnIL+XgY5/iDHDsGOP8ixQ7DjD3LsEOz4gxw7BDv+IMcucUKVWiIiIiIiIiIiIiIiIhL3VKklIiIiIiIiIiIiIiIicU+VWiIFE+S+YoMcOwQ7/iDHDsGOP8ixQ7DjD3LsEOz4gxw7KP7iFPT3MsjxBzl2CHb8QY4dgh1/kGOHYMcf5Ngh2PEHOXaJE+aci3UZRERERERERERERERERPKkX2qJiIiIiIiIiIiIiIhI3FOlloiIiIgEgpkNNbPFZrbQzBaY2WlmNs/MlpnZd2b2iZk1MLNEM5tvZs1DXjvHzNrHsvyFFSHeiHGFvBcL/Ed1f/pwM1sTMn2BmVWKVVyFZWZjzGxAyPPZZvZUyPNXzOwvP64NZvaL//+7ZlbLzBbFpuQiIiIisRG0vBmUO4PyZik5VKklBx0z253ji2OwP32FmVUNWe5cM3vL/7+HmWX4yy81s5vz2UZzM/vGzHaZWbsc8642s//5j6sPRIx5lCsasd9gZt/7y39sZo1D5sUsdn/7Ncxsqpn97Ccen5lZGz/eTX6ZF/pftlnJRmj8S8ysdx7rb+ivc7uZDcwxr6WfzPyY9b5HWxTivzIkufvazJqFzItZ/OESJz+JHGhmp5vZF36ZfzCz4f78rLi/9ffX2WZ2Zj7baW9egrvHzJrmmDfEj32ZmV1c7EHm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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "fig, axn = plt.subplots(2, 2, figsize=(25, 12))\n", "cbar_ax = fig.add_axes([0.91, 0.3, 0.03, 0.4])\n", "lim = max([np.nanmax(abs(mat)) for mat in correl_matrices])\n", "\n", "for i, ax in enumerate(axn.flat):\n", " g = sns.heatmap(correl_matrices[i].apply(pd.to_numeric), ax=ax,\n", " cbar=i == 0,\n", " vmin=-lim, vmax=lim, center=0,\n", " annot=True, cmap='RdBu', fmt='.1%',\n", " cbar_ax=None if i else cbar_ax)\n", " ax.set_title('{} Correlation'.format(correl_str[i]), fontsize=14)\n", " if i % 2 == 0:\n", " ax.set_ylabel('Inflation', fontsize=14)\n", " else:\n", " ax.axes.get_yaxis().set_visible(False)\n", " if i // 2 == 0:\n", " ax.xaxis.tick_top()\n", "\n", "fig.tight_layout(rect=[0, 0, 0.9, 1], pad=2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 6 - Actual vs Predicted\n", "\n", "Finally, we run a multifactors linear regression on our macro drivers using pre-covid data. \n", "We then visualise where we would have expected inflation rates to be, given previously determined pre-covid relationship, versus where they are now." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "regr_data = pd.concat([drivers_df, prices_T],axis=1).fillna(method='ffill').dropna() # to ensure same data for both drivers and inflation\n", "regr_drivers = regr_data[['EUR_10y', 'EUR_30y', 'Xover_5y', 'SPX', 'SX5E', 'WTI']]\n", "regr_infla = regr_data[list(prices_T.columns)]" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "pred = {}\n", "for x in regr_infla.columns:\n", " pred[x] = regr_prediction(regr_infla[x], regr_drivers, regr_start=regr_start, regr_end=regr_end, predict_start=regr_start)['Predicted']\n", "pred_df = pd.DataFrame.from_dict(pred, orient='columns')\n", "pred_df.columns = pred_df.columns + ' pred'\n", "\n", "df = regr_infla[regr_infla.index > dt.date(2019, 1, 1)].join(pred_df)" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "jupyter": { "source_hidden": true } }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAzUAAAEICAYAAABml9q2AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+WH4yJAAAgAElEQVR4nOzdd3hVRfrA8e+kk04aAQKE3lIoofeigtJFxba6+gMbtl1R1LU3FlwUVFxZKVZQaYKiIh2kJvQWIPSaRiAhPXd+f0wqJCGN3ATez/Oc59x7ypw5lyTc98zMO0prjRBCCCGEEEJUVzbWroAQQgghhBBClIcENUIIIYQQQohqTYIaIYQQQgghRLUmQY0QQgghhBCiWpOgRgghhBBCCFGtSVAjhBBCCCGEqNYkqBFCCCGEEEJUaxLUCCGEKJZSarVSKlUplZS9RJbiXK2Uupzv3C/LWIc3lVLfFlF+k8KOUcYzSqk92XU4pZT6SSkVnL1/tlIqPbte8UqpP5VSLcpSPyGEENYlQY0QQoiSGKu1ds1empfy3NB85/7fdald4aYAzwLPAF5AM2ARcEe+YyZqrV2BACAamF2J9RNCCFFBJKgRQghRZtmtIIPzvbdXSsUqpdpc4zx/pVSyUso737b2SqkYpZR9BdSrKfAUcK/WeqXWOk1rnay1/k5rPeHK47XWycD3QFB5ry2EEKLySVAjhBCiJD7IDlb+Ukr1zrf9a+CBfO9vB85qrXfk27ZWKXVOKbVAKRUIoLU+B6wG7s533APAXK11RgXUtx9wSmu9pSQHK6VcgfuB7RVwbSGEEJVMghohhBDX8hLQCKgLTAeWKKUaZ+/7FrhdKeWe/f5B4Jt85/YCAoEWwBngF6WUXfa+r8gOiJRStsC9V5x7pbuVUgn5l2KO9QbOluDeXsgu5zDgCjxcgnOEEEJUMRLUCCGEKJbWerPWOjG7C9dXwF+YFhm01mey39+plPIEBgLf5Tt3rdY6XWudgBnf0hBomb37Z6CVUqoRcAtw8RotKz9qrT3zL8UcGwfULsHtfZhdlr/WeojWOqoE5wghhKhiJKgRQghRWhpQ+d7ntLjcBWzUWp8uybla61TgR0y3rytbeMprBRCglAqrwDKFEEJUURLUCCGEKJJSylMpdZtSykkpZaeUuh/oCfyR77BFQDtMS8zX+c5trZRqo5SyzR6z8h/gNLA/37lfY7p8DcF0ZasQWutDwDRgjlKqt1LKIfseRimlxlfUdYQQQlQNEtQIIYQojj3wLhADxAJPA8O01rlz1WitU4D5mK5lC/KdWwv4AbgEHMGMrRmUPxGA1vovwAJs01ofq+C6PwN8CnwGJABRwHBgSQVfRwghhJUprbW16yCEEKKaU0q9DjTTWj9wzYOvPncl8L3WukwTcwohhBB21z5ECCGEKJpSygt4FDMuprTndsB0XRta0fUSQghx85DuZ0IIIcpMKTUaOAn8prVeW8pzvwKWA89prROvR/2EEELcHK7Z/UwpNRMYBERrrYOyt7UB/gs4AZnAkzlpOJVSL2Oe2GUBz2it/yi0YCGEEEIIIYSoACVpqZkNDLhi20TgLa11G+D17PcopVoBo4DW2edMy55QTQghhBBCCCGui2uOqdFar1VKBV65GciZPdoDM0s0mD7Rc7XWacBRpdRhoCOwsbhr+Pj46MDAKy8hhBBCCCGEEHkiIiJitda+V24va6KA54A/lFIfYlp7umZvrwtsynfcqextxQoMDCQ8PLyMVRFCiGrm5EmzrlfPuvUQQgghqhml1PHCtpc1UcATwPNa63rA88CMnOsUcmyhg3aUUmOUUuFKqfCYmJgyVkMIIaqhBx80ixBCCCEqRFlbah7CzBwN8BOQM7fAKSD/o8cA8rqmFaC1ng5MBwgLC5PJcoQQN49//cvaNRBCCCFuKGVtqTkD9Mp+3Rc4lP16MTBKKeWolGoINAW2lK+KQghxg+nf3yxCCCGEqBDXbKlRSs0BegM+SqlTwBvAaGCKUsoOSAXGAGit9yqlfgT2YVI9P6W1zrpOdRdCiOrpyBGzbtTIuvUQQtyQMjIyOHXqFKmpqdauihBl5uTkREBAAPb29iU6/prz1FSGsLAwLYkChBA3jd69zXr1amvWQghxgzp69Chubm54e3ujVGHDnYWo2rTWxMXFkZiYSMOGDQvsU0pFaK3DrjynrGNqhBBClNVbb1m7BkKIG1hqaiqBgYES0IhqSymFt7c3pUkmJkGNEEJUtl69rn2MEEKUgwQ0oror7c9wWRMFCCGEKKvISLMIUd1c2AHnllu7FkIIcRVpqRFCiMr22GNmLWNqRHWzZgjoLBh+2to1ETeQ1atX4+DgQNeuXa99cBFcXV1JSkqqwFqJ6kaCGiGEqGzvv2/tGghRehlJkHwSWr1k7ZqIG8zq1atxdXUtV1AjhHQ/E0KIyta1q1mEqE7iNpl1jTpwfo116yKqhWHDhtG+fXtat27N9OnTAfj9999p164doaGh9OvXj2PHjvHf//6Xjz76iDZt2rBu3Toefvhh5s2bl1uOq6srAElJSfTr14927doRHBzMzz//bJX7ElWTtNQIIURl27PHrIOCrFsPIUojeh0oG4gLh91vwcg4a9dIlMBbS/ay78ylCi2zVR133hjc+prHzZw5Ey8vL1JSUujQoQNDhw5l9OjRrF27loYNGxIfH4+XlxePP/44rq6uvPDCCwDMmDGj0PKcnJxYuHAh7u7uxMbG0rlzZ4YMGSJJEQQgQY0QQlS+sWPNWsbUiOokdiN4hkINf8hKtnZtRDUwdepUFi5cCMDJkyeZPn06PXv2zJ13xMvLq1Tlaa155ZVXWLt2LTY2Npw+fZrz58/j7+9f4XUX1Y8ENUIIUdkmTbJ2DYQovZ4/Q3ocHP4SslJBW0zLjajSStKicj2sXr2a5cuXs3HjRpydnenduzehoaFEliDzo52dHRaLBTCBTHp6OgDfffcdMTExREREYG9vT2BgIKmpqdf1PkT1IX+NhBCisnXoYBYhqhO7GuAcAHbO5n1WinXrI6q0ixcvUrNmTZydnTlw4ACbNm0iLS2NNWvWcPToUQDi4+MBcHNzIzExMffcwMBAIiIiAPj555/JyMjILdPPzw97e3tWrVrF8ePHK/muRFUmQY0QQlS2HTvMIkR1cSkStr0Al4+DbXZQkyld0ETRBgwYQGZmJiEhIbz22mt07twZX19fpk+fzogRIwgNDeWee+4BYPDgwSxcuDA3UcDo0aNZs2YNHTt2ZPPmzbi4uABw//33Ex4eTlhYGN999x0tWrSw5i2KKkZpra1dB8LCwnR4eLi1qyGEEJWjd2+zljE1oro4+g1s/BvcsR9sneDSAajV27wWVc7+/ftp2bKltashRLkV9rOslIrQWoddeayMqRFCiMr28cfWroEQpXPpACg7cGsMNvbgGmjtGgkhRAES1AghRGVr08baNRCi5BJ2w/nVeQFNajTE/AV+PcHR29q1E0IIQMbUCCFE5du61SxCVAdbn4LYDeB/i3mfsAvWjYCL+6xbLyGEyEdaaoQQorKNG2fWMqZGVGWpMWDvAWGfgENNcKlvtkuiACFEFSRBjRBCVLZPP7V2DYQontawsDa0eglC3yu4z85kopIJOIUQVYkENUIIUdmCgqxdAyGKl3kZdBbYe169T1pqhBBVkIypEUKIyrZhg1mEqKoyEszawePqfbmTb0pQIyrH6tWrGTRoEACLFy9mwoQJRR6bkJDAtGnTSn2NN998kw8//LDMdRTWJ0GNEEJUtldeMYsQVVX6RbMurKXG0Rf6r4W6Qyq3TuKGk5WVVepzhgwZwvjx44vcX9agRlR/EtQIIURl++ILswhRVeW21BTW/cwB/HpAjVqVWydRrRw7dowWLVrw0EMPERISwsiRI0lOTiYwMJC3336b7t2789NPP7Fs2TK6dOlCu3btuOuuu0hKSgLg999/p0WLFnTv3p0FCxbkljt79mzGjh0LwPnz5xk+fDihoaGEhoayYcMGxo8fT1RUFG3atGFcdlKWSZMm0aFDB0JCQnjjjTdyy3rvvfdo3rw5/fv3JzIyshI/HXE9yJgaIYSobM2bW7sGQhTPOQBC3wf3FoXvP/Y9uDUF7w6VWy9RNst7X72t/t3Q7EkzNmr17Vfvb/SwWVJjYf3Igvv6ry7RZSMjI5kxYwbdunXjkUceyW1BcXJyYv369cTGxjJixAiWL1+Oi4sL//73v5k8eTIvvvgio0ePZuXKlTRp0oR77rmn0PKfeeYZevXqxcKFC8nKyiIpKYkJEyawZ88eduzYAcCyZcs4dOgQW7ZsQWvNkCFDWLt2LS4uLsydO5ft27eTmZlJu3btaN++fYnuS1RNEtQIIURlW7PGrHv1sm49hCiKSwNo/XLR+7c8Bk3GSFAjilWvXj26desGwAMPPMDUqVMBcoOUTZs2sW/fvtxj0tPT6dKlCwcOHKBhw4Y0bdo099zp06dfVf7KlSv5+uuvAbC1tcXDw4MLFy4UOGbZsmUsW7aMtm3bApCUlMShQ4dITExk+PDhODubMWJDhkh3yupOghohhKhsOd0fZJ4aUVWlxUFmEjjXA1VIT3U7Z8l+Vp0U17Ji51z8fiefErfMXEkpVeh7FxeTFlxrzS233MKcOXMKHLdjx46rzi0rrTUvv/wyjz32WIHtH3/8cYVdQ1QNMqZGCCEq28yZZhGiqjr8BfwcCJaMwvfbOkv2M3FNJ06cYOPGjQDMmTOH7t27F9jfuXNn/vrrLw4fPgxAcnIyBw8epEWLFhw9epSoqKjccwvTr18/Pv/8c8AkHbh06RJubm4kJibmHnPbbbcxc+bM3LE6p0+fJjo6mp49e7Jw4UJSUlJITExkyZIlFXvzotJJUCOEEJWtUSOzCFFVpSeArRPYOha+X1pqRAm0bNmSr776ipCQEOLj43niiScK7Pf19WX27Nnce++9hISE0LlzZw4cOICTkxPTp0/njjvuoHv37jRo0KDQ8qdMmcKqVasIDg6mffv27N27F29vb7p160ZQUBDjxo3j1ltv5b777qNLly4EBwczcuRIEhMTadeuHffccw9t2rThzjvvpEePHpXxkYjrSGmtrV0HwsLCdHh4uLWrIYQQlWP5crPu39+69RCiKJvHwOklMOJs4ft/DwOnWtD718qtlyiR/fv307JlS6vW4dixYwwaNIg9e/ZYtR6ieivsZ1kpFaG1DrvyWBlTI4QQle3dd81aghpRVWUkFJ7OOUfX701LjhBCVBES1AghRGX75htr10CI4qUngL1H0fvdm1VeXYqjNcRthSMz4MQ88OkKAUOh7mCZR8fKAgMDpZVGVKprjqlRSs1USkUrpfZcsf1ppVSkUmqvUmpivu0vK6UOZ++77XpUWgghqrV69cwiRFXV/Blo9WLR+8/8AUe+qrz6FOXCNtj5CkTNgFq94eJu2DIaDn5i7ZoJISpZSVpqZgOfAl/nbFBK9QGGAiFa6zSllF/29lbAKKA1UAdYrpRqprXOquiKCyFEtfX772Y9YIB16yFEUeoOKn7/iR/hxE/g1wtcAyulSoXyag/dvjevnfxMy03CLnDwsl6dhBBWcc2WGq31WiD+is1PABO01mnZx0Rnbx8KzNVap2mtjwKHgY4VWF8hhKj+JkwwixBVVdxWSDlf9P6W/zTz16y+HdIvFH1cZXDyMwuAUlAzFFzqFZ2OWghxQyprSudmQA+l1Gal1BqlVM6UwnWBk/mOO5W97SpKqTFKqXClVHhMTEwZqyGEENXQ3LlmEaIq0hZY1qX4LlweraDnIkiKgrXDISst+9xKzqi6ehCsHXb19t/aw8a/VW5dhBBWVdagxg6oCXQGxgE/KjMta2FTsxb6F05rPV1rHaa1DvP19S1jNYQQohry9zeLEFVR+gXQWXmtH0Wp1Rs6z4LoNXBkJlzYAX92A0sl9ji/fBSU7dXbHb0hMary6iEqnKura6mOnzp1Ki1btuT++++/TjWqOIsXL2ZCBbTWJyQkMG3atNz3Z86cYeTIkeUuFyAmJoZOnTrRtm1b1q1bVyFlXm9lDWpOAQu0sQWwAD7Z2/OPfg0AzpSvikIIcYNZssQsQlRFqdk9yh1L8MAx8D7otxqaPAZnfoPYjZBaxNw2xVk9GA7/r/TnJZ+GGoV0CHFtBJePlL48UW1NmzaNpUuX8t133xV5TGZmZrmvk5VV/qB9yJAhjB8/vtzlXBnU1KlTh3nz5pW7XIAVK1bQokULtm/fftXEpBXxGVwPZQ1qFgF9AZRSzQAHIBZYDIxSSjkqpRoCTYEtFVFRIYS4YfznP2YRoipKy+4Sfq2Wmhy1epnxNR6tzfuUc6W7ntZw5hcIf7rgdktG9pIFyaeuPi/zMmRcBOfCgprGkBYH6RdLVxdRYb799ls6duxImzZteOyxx3K/CLu6uvLqq68SGhpK586dOX/ejN06evQoXbp0oUOHDrz22mtFljt58mSCgoIICgri448/BuDxxx/nyJEjDBkyhI8++qjA8bNnz+auu+5i8ODB3HrrrVy+fJlHHnmEDh060LZtW37++WcAkpOTufvuuwkJCeGee+6hU6dO5EwM7+rqyuuvv06nTp3YuHFjofeWlZXFww8/TFBQEMHBwbn1mDp1Kq1atSIkJIRRo0bl1mns2LEAHD9+nH79+hESEkK/fv04ceIEAA8//DDPPPMMXbt2pVGjRoUGK+PHjycqKoo2bdowbtw4jh07RlBQUO41hg0bxuDBg2nYsCGffvopkydPpm3btnTu3Jn4eDNcPioqigEDBtC+fXt69OjBgQMH2LFjBy+++CJLly6lTZs2pKSkXPUZvP3223To0IGgoCDGjBmDzu562rt3b55//nl69uxJy5Yt2bp1KyNGjKBp06b861//uubPR7lorYtdgDnAWSAD0xLzKCaI+RbYA2wD+uY7/lUgCogEBl6rfK017du310IIcdOIiTGLEFXR8Z+0/g6tL+wq3Xkxm815p5aU/prhz2k910nrzDTzPmG/1oubav1zE62XddN6SXOtM1MLnnMx0lzvyDeF3MM8rb9TWsdFlL4u15J+Seu4bRVfbgXat29fwQ29emk9a5Z5nZ5u3n+T/bldvmzez51r3ickmPfz55v3MTHm/eLF5v3ZsyW6/qBBg3R6errWWusnnnhCf/XVV1prrQG9OLuscePG6XfeeUdrrfXgwYNzj/n000+1i4vLVeWGh4froKAgnZSUpBMTE3WrVq30tm3m36JBgwY6ppC/q7NmzdJ169bVcXFxWmutX375Zf1N9r1fuHBBN23aVCclJelJkybpMWPGaK213r17t7a1tdVbt27NrfMPP/xQ7L2Fh4fr/v375173woULWmuta9eurVNTUwtsmzVrln7qqae01loPGjRIz549W2ut9YwZM/TQoUO11lo/9NBDeuTIkTorK0vv3btXN27c+Kp7O3r0qG7dunWh72fNmqUbN26sL126pKOjo7W7u7v+/PPPtdZaP/fcc/qjjz7SWmvdt29fffDgQa211ps2bdJ9+vS5qo5XfgZa69zPU2utH3jggdx/0169eukXX3xRa631xx9/rGvXrq3PnDmjU1NTdd26dXVsbGyxPx9Xuupn2dQlXBcST1wzpbPW+t4idj1QxPHvAe+VNKgSQoibjo+PtWsgRNF8OkPXOeDSsHTn1cgeJ1balprYTaAzISsV4iPAOwxWD4SsZEDBhfPQYZoZ6xO9Fhrcbc6zdYQmY8Az+OoyvdqBdwdwb166uuSXGmsSIXh3NFnVMhLh+BzY8pjZP+gguDcte/k3sBUrVhAREUGHDiaPVEpKCn5+puXPwcGBQYNMyvD27dvz559/AvDXX38xf/58AB588EFeeumlq8pdv349w4cPx8XFBYARI0awbt062rZtW2x9brnlFry8TJrvZcuWsXjxYj788EMAUlNTOXHiBOvXr+fZZ58FICgoiJCQkNzzbW1tufPOO4u9t8GDB3PkyBGefvpp7rjjDm699VYAQkJCuP/++xk2bBjDhl2d1GLjxo0sWLAg975ffDFvfqhhw4ZhY2NDq1atclu0SqNPnz64ubnh5uaGh4cHgwcPBiA4OJhdu3aRlJTEhg0buOuuu3LPSUtLK7Ss/J8BwKpVq5g4cSLJycnEx8fTunXr3PKHDBmSe53WrVtTu3ZtABo1asTJkydZv359kT8f5VGSeWqEEEJUpOz/wBgxwrr1EKIwzgEQOKr05znVAtcmYFPKrxb7JsL5leb1yfng2wW6fAMu9cHezXRBc/KDiOch8mM4twzaTwWXBtDxi8LLdG0It6yHQ/8FGwdo+ljp72fzo3B6MYxMAAcP+GsUnFkKzvUg+SSc+7P6BDWrV+e9trcv+N7ZueB7D4+C7318Cr4vQZITrTUPPfQQH3zwwVX77O3tMbmlzBfl/ONccrYXV25Z5ARBOWXMnz+f5s0LBrzFle3k5IStrW3ucUXd286dO/njjz/47LPP+PHHH5k5cya//vora9euZfHixbzzzjvs3bu32Lrm/wwcHR1LVL+i5D/fxsYm972NjQ2ZmZlYLBY8PT3ZsWPHNcvK/xmkpqby5JNPEh4eTr169XjzzTdJTU296rr5r5n/usV9huVR1jE1N6VF208zZ8sJa1dDCFHdTZ1qFiGqovjtELOx9OfZOsKQQ9Do4VJeLwLqDITat5k5ZgD8upugxqFm3tietpOg9asQNRP+6ASHPjfpp4tiYw+nFkHUjNLfS/JpOL3ETC7q4GG2NX0KeiyEoceh2TMmrXV+Wemw643i5/e5SfTr14958+YRHW2STsTHx3P8+PFiz+nWrRtzs1PdFzXYv2fPnixatIjk5GQuX77MwoULrxrEfi233XYbn3zySW6QsH37dgC6d+/Ojz/+CMC+ffvYvXt3qe4tNjYWi8XCnXfeyTvvvMO2bduwWCycPHmSPn36MHHiRBISEkhKSipQXteuXQvcd/fu3Ut8L25ubiQmJpbq/vNzd3enYcOG/PTTT4AJnHbu3HnN83ICGB8fH5KSkkqdnKAsPx8lIS01JfTDuq289Gs0DnY2DA6tg6ujfHRCiDLKHpgqRJW09324uBcG7bv+10qNheQTUHMstBpX/LE2dhD6Lvj2gI0PwNYnwa0p+Pcv+hyPIIj60gQ/qoTPcS/sgL0fABo6fZm3ve7tea/DpsDZP2FZN5PW2r2ZSW29522TaKHDtKuKvZm0atWKd999l1tvvRWLxYK9vT2fffYZDRo0KPKcKVOmcN999zFlypQC3Zzya9euHQ8//DAdO5p53f/v//7vml3PrvTaa6/x3HPPERISgtaawMBAfvnlF5588kkeeughQkJCaNu2LSEhIXh4eJT43mrUqMHf//53LBYTaH/wwQdkZWXxwAMPcPHiRbTWPP/883h6ehYob+rUqTzyyCNMmjQJX19fZs2aVeJ78fb2plu3bgQFBTFw4ECeeuqpUn0WYAKpJ554gnfffZeMjAxGjRpFaGhosed4enoyevRogoODCQwMzO1GVlJl+fkoCVXWpryKFBYWpnMyTFRJmZdJnuvH1wkjmXDqbqbe25YhoXWsXSshhBCi4i3vZdb915T+3G0vmJTQXb8u2fFnl8Gq26DvCvDvW/LrpJyFi/vAv1/xxx3+EraMhsGHwa1xycpeNQDO/gFNnyg+OEk+BYvqgXsLuG0zHPgIdr8JNo4w9CjUqF3i26lo+/fvp2XLlla7fnWUlZVFRkYGTk5OREVF0a9fPw4ePIiDg4O1q3ZTK+xnWSkVobUOu/JYaW4ogYyjP+Bsk4xH/d6MtfzK/m1R9Gz6OJ7ORfygH/4SUs5gsXUhw64mjk0eNM3gQggB8MMPZn3PPdathxCFSY02LRxlkXIG4koxk0N8hFl7tSvddWrULlnQkJNEIGG3CWpOLTYtKqHvm+5yOS4fN93Zgt+GzrPBkmbG7BTHOQD6rYKV/WHDA9DkcWg8GqL+Z1pxGv2tdPckrCo5OZk+ffqQkZGB1prPP/9cAppqRoKafFLSs3j7l710CPRiRLsAMzjx0Oeona8TlRqAV3A/Bia/TlzKYkZ/bMPQvoO5r2N9bGzyBnUdjb3MwXW/cpvjImwAR+Bo/EUadn7eavclhKhiPv/crCWoEVVRWgw4lWDizcI4+UNqKbKftfgH1LkdHDyvfWxZeIaAvQeknDZZ2TY/aoIRbcnrkpaVCuvuhMTDZhJR11JkfavVG9p9BBHPQM22JnFB20l543BEteHm5kaV7jUkrkkSBeTjZG/DvrOJTPw9kpT0LIh4FiKeJcY+mNHH/0XrAB88es2ivmsGcxo8x9H17zDqfxs5EpM96OvsMhZuiGDs4dGMsw1nSs0tHEwLxP5IGWZJFkLcuJYuNYsQVU36RTNppXP9sp1fwz97UswSDF62ZJnAombx/ffLxa4GjIg2Xck2PQyZSSYN9I/O8EMN2PEKLO9tWoy6fFO6gCZHs7HQ6O9mbBBUmYCmKgwvEKI8SvszLEFNPkopXr29JecupfLhskjSGj/NwRbf80LcRC7YNqSuZw1U7f7YDz2Abb3BvFZnBqMsb9F/8hru+3wlmasGU/v0FHo392PSPe15dmAHwp1H80tsG1LTUq9dASHEzcHZ2SxCVDW2NUwq5MCipqi7hpwuW0lHij8u5TysHgBbxsD1/vJt6wAHPzXjZNpNzpt/x5IO+z6AuM0Q+gEEDC5b+UpBx+nQaaZ5Hb0eVt0OafEVdw+l5OTkRFxcnAQ2otrSWhMXF4eTk1OJz5HuZ1foWNvCG2EnmbLxErM3eJBlcQfiubdjvbzc4Q41UT0WQOQU+tg15KkjTUg/tgg70vk1ri33dM1LIhDYfgz3fbmZk0sP8fbQIGxtis/BLoS4CXz7rVk/UOgcxkJYj60D+HYr+/meoeDX0wQMRTm/Gv66FzISoP0nZb9WSaUnmJ4XHkFm3EvGxbx93X4w3dPq3Fa+a+Sfm8eSDmd/M2OL6gwoX7llFBAQwKlTp4iJibHK9YWoCE5OTgQEBJT4eMl+dqXVg+HMLwBMc19O00ZBtK3viY+rY9HnaA1Lg0hPucC/7X9l3O0hONnnTdI04fVrHYsAACAASURBVLcDZOz7iPp+tbhn1JvUcLA1Myg71SpbU7cQonrr3dus809oJ0RVcOYPyEqGesOvT/kXdsLvYeDaCLr/BDVDrn1ORTj9i0kFndM1bP9/wKermeizomUkwcLaUHcIdCt8vpVCJR423fHcm1V8nYS4gRSV/UyCmiudmAfr74IadWDYKdOUfC3rR8GJH6DD59D08UIPObegC/6pm/jD4QVuG/4e/NEREnaaWZGbP331CUdmm3VpJzETQlR9GRlmbS9ZEUUVs2ogpJ6HgdvKV47Whf//ufIWuLAdBh0ER6/yXaMq2/4iHPgPtP0QWpQgUdC+SbDjRTPR6PBzJfvuIcRNqqigRsbUXKnuENOC4n9Lyf+otHwBGj8KjR4p8hD/FsMA6JH6CWsOJ0CPeeYp0f6JZrBkDq1h1+uw6e+mHkKIG4+9vQQ0omq6uBc8WpWvjO3jYGlw4fvCPjUD8m/kgAZMIGPrAjvGm/fpF+HUkqKPP7/SrFOj4VLk9a+fEDcgCWquZOsAA8Kh/ZSSn+MdZmYdti0mn3ntgQCsSenNQ7O28tSSS8TVG2sm71ra2gQz2gLbnoc975gAyf9Ws00IcWOZPdssQlQllixIPgmuJZyksih2rmZizIykq/e5N4c6A8tXfnVQozaMOGcWgP0fwrphsPstk0jgSn1+g4E7zeuYdZVXTyFuIBLUFMY5oOJTMtYMgb5/0ufB+TzfvxnL952n1/euXHYIBCd/5q5ayb45PSFyCutsHmD8yaeZ91cE+o8ucG55xdalGJP/PMiDMzZzNPZypV1TiJuOBDWiKspKMWt7t/KV498f0LDtHwW3n/kDjn5bvrKrEztncKhpXjf+u3lIuftNCH+y8OM9g6HBvab7u6hY+XvEiBuWBDWVyb8/Tk4uPNu/KSv+2Ysm/l502/0JP/t8x7vL4/HKjOTT+NGMO/IQyyNjefO345yIjiFt/UPXtcVGa03kuUQm/HaAqSsOsSEqjodmbpFUkEJcL6tXS5IAUfVkZj/MsnMpXzm+3aDVyxD1P4ialbf98H9h73vlK7u6cm0EYZ+ZlqqkY2bCzxzH5sK6kebz7/Y91L3DatW84Vw6CL93gF9b3Rw9X7SGuHCIXnf9U6VXQZLS2UoCajrz6X1tGfjxOp6duwMf15q43n2GsU72jMUEGot2nOardffwut0ELLHh2Ph2zCsgNQZi1pc7Q838iFN8tPwgpy6YJ3QDg/xp36Am7/66n/OX0vD3KHl+cCGEENWYow8MPW5SHJdXyDsmpfHBqdDwb2Bja8breFZStrOqqNmTZv4fGyewZEDMBogPh2PfQvLpvGAy5ZxJO+3e3Lr1vV60xaS9PrPUJGdqO9H0kCkvS4ZpbbR3N2m897wDBz8x24NeN9dVN/iz/IjnzO8cgF8vk4yqsjIMVgES1FhRQE1nfnmmO99vOUG3xj64OuUNHFZKMbxtACrtPjKjJnLp0E945Q9q9k+CUz9DwDCT0MCSZf7TKAWLRfPe0v34ujrywYhg+rbwo5a7ExHHLwCw+/RFCWqEuB7+9z+zHj3auvUQIj8bW3CpX3FldZtrxpra2JpWiKQoaDCqYsqvrnK6o/3ZHWL+Mq9dGkKrF83/5doCy7qAc13o9iM4V/OuaJGfwOVjEPJ2XtB2fiVseMAkRUDDuWUmeUT+sVZJRyDieWj6ZMnmEMpMNt0dzy6DHvNh1a2QFgeNH4GQd6GG//W4u6oh/YIZx6azIPYvaPx/ULMt7H4dfm8PQ6Iq7ve6irvBQ9aqr4G3Cy8PbEnPZr6F7g9qFMjWy62xO70ALJlmo7bA8Tng1sz8Edz+Iqwfmbe/JLRm39lLpKZc5H/NpnJvWy9quZsAplVtd2xtFLtPJVx1msWi2XbignRNE6I8fvjBLEJUJZdPwp73zBfKiuDkY56aawss62bWnkVkRbvZBL8FvX+HO2Nh6BFo9ZLZrmyg5TgT8PxcH9YOq9RxtRXO3g0OTDZfruO3m221+kHwm9DiHzBwO9QIgMipBbtL1ahrEiasHgCbHjUZ4XK67F06CGnxBa+zdigc/sI86K3ZBgIfNEmfOn1pApqEvQW7QlZ3h6dDxD/g8JewpBkc/AxsneCWDRD2iWkVHHQQOs/KC2j2vg8Zl6xb7+tMgpoqrpGPK99dHMlv+hFQtnByAWz7p8maFni/OcipFpxaBIvqmQDn4v5rF7x2KOd3zaCF0zHqXVwIK/pkPzWBGg62NPVzZdfpi1edNmfrCUZM28CE3w8UWmz0pVQJeIS4luXLzSJEVZIUBbv+BZePV2y5mcmm21Xn2VB3aMWWXV359zMtEI7eV+9r9iQMPgQt/mm6qK0aAJcOVX4dK0Kjh6HvCshIhGWdzHcUnWXm9Gv3oQlAbt0IXb/Lm0ZDa7B1hOFnoNV4ODobfmkB6+82Ad6vrWBBLdj1hjk2/QKcW2HGcbWfbMppPxm82uXV4/gc2DLadN2/nrLSSz6W5crA7EqRn0Dc1qu3X9gJW5+CyI/MPbk1g1q9zT5bBxPcgEmb3vAB8zojEXa+Cvv+XbK6VVMS1FRxNjaKJJ8BzDjd3fyiRk6ByI/BvSUEDDEHtfgH9FwE3h3NE5FfW5noPSPJTHR2flXBQlPOwuklXIiOJMmtA6rnItPXeVnX3Pz4wXU92FNIULPqgPmD8MWaI2w/cSF3u8Wi+WDpfjq+v4JJf0iOfSGEqHYyk83a1rliy7V3NS0RjR4qfuoDkcetCbT9txnj1G8luDcte1nnV5uWiutBW2DdnbB5jBkXlCMrFfZNhKSj4N/XpKuuOxQOfQ5br5ik3K6G+QJ+bjmc/hWWNIUDU8yX8zYfwK2bzRyCLf4JF3ZkZ4m7B/a8DVufgHMrAQ11bi+6nvXvMsHUwU8rfgC9JQPCnzZ12zEeVvaDhD2QlZa335JhrntsjnnwfG4FzPcxwYkl4+oyMxIh4hkzUfuVXBtBy39Cr1+h+49wyzoTHBbH3s10/YycUvDf6QYjY2qqgZC6Hqw9GENqRhZO3eeZH07bfGNdlIKAoWZJOQ+HPjNNsEdmmj8S6Rfgtq15T0HO/AbA/PNtaduoJgT0gn6rYM0d8EtLaPwoTWuN56eIU1xMycCjhhnrk55pYWNULMPa1OH3veeYF3GKtvVN/+C5W0/yxdojNPB25r9rohgYVJvggApOiy3EjWLaNLN+sojUrkJYQ1YFZT8TFceuBvj1NK8zU8z70lrRx6zvuw69KBIPmx4kNg6mS1mObf8wAYxHK3BtaLoi9vip+LJ2vQGxG8xrR5+87d5h0Otn8zo9DnovBSd/cK4PR78y5bs1NQ92i+IZAu4tTCCUedm0EpXH4f+BR2vw7WoyjR381HSr82gBx76GZZ3zPpODn5nvZxe2m+9kTZ+CxEOmNerQNJNQ47Yt5jvalsfBu5NJAR74IBz7xtQ3/++kvRu0mVD6Oge/CaeXmDFbfr0h6LXyBctVkLTUVAPN/N2waIiKSQIn34IBzZVq1DID8hy94dRisK0B8RFm8BzAiZ9g86NYHGuxMT6ARr7Zvyg+nWDANtP/tM2/aeBtth+Py5uvJvxYPJfTs7g9uDYDWvuzZOcZ3l+6n9Ffh/Pur/vo0sibxWO7Y9Gw8kD09fo4hKj+liwxixBVSU5LjV0Ft9SI8ot4DpYGm+5DhT3ZL0r6hWsfUx453aP6rzFJDdITTGvFof9C82eh7qCSl1V7gFm7NoKAwYUfU2+EmdhUKWjzPty+y7QCDj5YfCugUtB4tMns1/gR8wD42Ny8/Rf3wa9BJevmF/kpbBlj0pPvfsd02bRxhNq3QJMxplXKzsV89rX6msxu+yeZ4CXsU5PtzZJugoou30Diwdzu/2SlmBaay8dNyyYUHFMV/szVvW9Kyr25eYDt5A8ZCdU/CUUhpKWmGmhey0yEFnkukdZ1Str6ocHBE3osgKgZ5jWAU22wsSfW5y5A0cjXNe8Ul/rmlx0I9E4E4FhcMiEB5twv1h7B09mebk18cK9hz6IdZ5i94RgNvJzp09yPl29vgUcNezxq2BN3Oa0ibl2IG9Nvv1m7BkJcLes6dT8T5ecZbLoO7RgPbs2h3rCrjzm50KTxdfTK23ZxX8nKP/2LeYrf5LGCY1G0zuvlUZi4LebnxSvMvI/ZYAIvOzfzpb00mj0JWKDZ0ybBREkUNiapKC2eh6ZPmNau8KdNK4mDJ9QZADtfMd3wz/5WfOvF/g9h+zjT8tLla1hU36Tfbjw6rzXFua5JAhEfDp5B0HG66TnT7Om8LGz9V+WlmA68P+8zDnnbtHz9HAgdpplsefsnme53WSlmric7Z6jVp+T3nZ93BxiwJe99RiIou7K1AFZBEtRUA4E+LjjY2hB5PrHkJykb6DHPvK4zIG+7X3e4K4m/dp4F9tDQ54puBukJcOBjGviZFIrHY01LzaoD0aw5GMPLA1vg4mhH50be7Hz9Vlyd7LC1KfgHz8fVgbik9NLephBCCGtq/H9Q/+68tMOi6mj0d9O9ankvOLXw6qAmPgLWjYA2E6HVuLztXmEwIMKM0SmKtsDeD0zXr8PTTeuCa0MT0GRczPsuUZjMS6anh03218m6t0PrV82439IEHGCOD36jdOeUhlJ5X97bTDDdxv4aBd1+MFNkAMRuhpzpgVLOmxYNOzfTOrTnHdj9hvkd6fot2NibwCMtDpo9VfBaXm3NAuDWGEILmXQ2Z86c/EGjSwNoO8mMFTr+owmcbBzMMbEbTSudX6+K+Tyy0s3Pk1sTk379BpjDR4KaasDe1oZGvi5EnkvkRFwy205c4HJ6JiPbB+BoV3BumpT0LJbsOsOgkNo4OxT8583MsvDS/N0E13Un7nI6tjaK+l5XPJFTdrDnLZxC7Kjt0YWjcZdZeeA8T3y7jWa1XPlbl8DcQz2c7SmMt6sjMUnSUiNEkaZMMetnn7VuPYTIz8a+9F9EReVQNqa1ps4g0x3pyokko2aZLlBN/q/gebaOBVte8tPafKlv+Dfo+TPoTDMR6IGPIXqNGVx/cr7pkpXTepF82ly/3nDTmtJ5lqlLfqHvVtx9Xy92LiYwWRoMZ341CZfcm4N7KzN2addrJruYtkCzZ6D9x3Bpv/msOs3IC+IC76v4ujUZYyZgDRgMXu3ztu8Yb9a+3SrmOrYOppVo+wvm37LD5+ZvQDUmQU010cLfjUU7ztBzUl5fym83nWDu6M65wcWZhBTGfBPOntOXOHQ+kVfvaFWgjBnrjzJ/2ynmbwNPZ3vq1ayBg90Vkbm9q3lScHEvDbz7sfZgDIt3nKFlbXe+eqQjNRyuPcGnr6sjB87d2LnQhSiXFSvMWoIaUZWcWgLxW00XGFE1tfnAjAtRNpAYBc71AA3HvzfjTWycTGYtZWeyfZ2cB65NzID6dpNNKukcCbvgxI9m0kun7IH5LV8wX+JTz2WXOxdO/wzuL8CWJ8xcMGjTRar5syazWXV9wu8ZBF4dIHot3L7DbIteD7+FmoH8jUeDTxfwzc4+23l2XqvJ9aRsIOTNgtsSo0x3Nie/knfNK4kW/zCtcXveAUc/M04pR2YKHPvOdKfLPzFqFSZBTTXxYJcG1HCwpXUdD9rVr8nJC8mM/X4b//xpB9MfDGP7yQQe+yaC1IwsOgZ6MXvDMZbuPsfrg1txW2t/Dkcn8Z8/D3Jrq1p4uzowZ8tJ2tbzLPxiHkFwcS+B3i5sOhJPh8CazHi4A+5OJYvgvV0diJXuZ0IUbfFia9dAiKud+xOOfiNBTVXmXNestQXWDDbjoELeNYPS6w4xLQ9JUQXPqXOHCWASDxUMas7+Ydb+txY83tYhb8JGz1DTNavlCyYLmZOfGeS+b4IZfzIgonpPqNpsrAnks9JMq1bcJtPFq+/ygp8VmP3W4tbY1KmiW1KVMr/viYfg4Cfm31nZmDlyDn4CaTFmjsQ+v4N//4q99nUgQU010b6BF+0b5A3+a1XHnVdub8lbS/bx5HfbWHkgmtqeTswZ3Qk3J3se/Wor8ZfTGT9/F23qefLivJ04O9jy7vAgfF0daeLnRkt/t8Iv5t4czq9kRIe62Nva8PLtLa7qylYcH1dHLqZkkJ5pyWsJSjoCdu55T4OEEEJULVnJkvms2lBm7MXaISZbFphWE8+FZi46ZWe+jCpbqBkC87zh8om807WGo1+b7k3FZcGqM8AM/E+Lg8aPZp9rMemRfbsVP1anOmj0N7PkaP48NHnc9Fqpaq4MsipS61dMIoLMRLBkwp63TDa65s+YaUC8O12/a1cgCWqqsYe7BhJ+7AK/7j5L18befHZfO2q6mJSGvz7Tg8PRidw+dT2DPllPTGIaH90Tip+bSQf9aPeGRRfsEghoOtaFjo2CSl0vb1dTh/jL6fh7OJk/gIsbmz+Cg/aXujwhbjgfZs+R8MIL1q2HEPllJkvms+pCKah7h/myqbPMYHcnP7MU1nLiXBf2fQCXj0K3OWZczKX90Glm8ddp8piZVDIzOa+VQNnkpRu+0djYgk0VDGiuN89gM4l7jqHHTCpqgNq3FnpKVXTNjpBKqZlKqWil1J5C9r2glNJKKZ98215WSh1WSkUqpW6r6AqLPEopPrwrlP8+0I6vHumYG9DkaOLnxgfDg4lJTKNfCz+GtalbsoKbPg53J5e5mdPH1TTRxuYkC9BZZp1YgvzvleRicgaTl0Uy4bcD1q6KuBlt3GgWIaqSrGSZeLO6qT3AzBVjX0TPixytX4HAB8yki2C6krk0MrPMF8e1oel65FKvQqorqomcgKaaKUlLzWzgU+Dr/BuVUvWAW4AT+ba1AkYBrYE6wHKlVDOtc77ViopWw8GWAUG1i9x/Z/sA6nk506qOO6qkg9vKmf3CJ7ulJjeosbE3KRAv7ChXuRXpvaX7+DH8FAB3hQXQ2PcmfDIjrGf+fGvXQIirZaZI97Pqxr+/STMct7Xg9A1XajLGLDlsnKDvn9YdJyJEBbtmS43Wei0QX8iuj4AXAZ1v21BgrtY6TWt9FDgMdKyIioqy69jQC1fHUvQ0tGTCpkfg+A+mL+XOf5mMIJdPwpk/zIC6YuS11KRn99v91vTVTDlbntsotSMxSZy/lHrVdq01qyJj6BBo5mL4fc85MrIspGeaJcuirzpHCCFueH1+NzPDi+rDt6uZ6LG0kzHa1QDXwOtSJSGspUxjapRSQ4DTWuudVzz9rwtsyvf+VPa2wsoYA4wBqF+/flmqIa4XGzs4tQhsncCplslysjffxFF1B5vc9Ee/NjPk2hT8McoJamavi6TlsbG0TvsZ7eCDUramX255ngReObuxtsCe9yDw3gIDFpPTMxn53420ruPON48WHOC2/2wiMYlpjLutOZkWzdQVh5j0R2TufjcnO1b+sze+bvIES1wnEyaY9fjx1q2HEPkpBap6z1NxU6ojPf2FgDIENUopZ+BVoLCRQ4X1byr0sbfWejowHSAsLEwejVc1jr4Qsx5u32Vy0Z9bbtJEujUz3chO/ATb/gEHJptc7n69zOBBvx64ONoxvq8fnc48Seu0cCafu5/4wHG8OyK0ZNfOSjOZW2yumBPn4j74vb3pytb2PyaT2tk/YPfrZMRuJbbdD7mHLtp+hvjL6WyIiuPC5fQC443WHooBoGdTX+xtFZ+sOMyAIH9cHO2ISUxj9oZjRBy/wIAg/3J/jEIUakfV6YopRK6dr4JbU2j0sLVrIoQQpVaWlprGQEMgp5UmANimlOqIaZnJP5osADhT3koKK0g8aNbx28xsxPVH5u2rN9y0mNSoA4emmf68AB6tTRCUGs3jcV3BEdLaf8mO9cEcPRxXsuvum2hmze3+o7lm0jHTIhT0Gpz9E7JS4fzK3HE/WbvfwxZYG3mOR/9YWaCo2h5OnL2Yytg52xgaWpe7O9QjI8vC95tPEBrggb+HE8PbBjC8bd6AuNSMLL7ddJzdpxMkqBHXz9y51q6BEFc7+rWZs0SCGiFENVTqoEZrvRvwy3mvlDoGhGmtY5VSi4HvlVKTMYkCmgJbKqiuojI1GwsHPzUTbxVGKQgYYpbEKDNpW90hprUmKwWaPgn1huFYqw99Y47ySdQhkleOxLnVEwVzrV88AJZUqNkGLuwyTwr9+5sJQMHMZrv7DfC/BeK2mIwcgw+ZrnFaQ9xm9qU05HTAP5nQrmAay34e4byyNJo/D8O+M5e4u0M9fgo/xYn4ZN4cEnb1PcVswGnX69wTMJBdpyp4gishhKjqyts9WAghrOiaQY1Sag7QG/BRSp0C3tBazyjsWK31XqXUj8A+IBN4SjKfVVPtp5iJva7sAlYYt8ZmyeHaEMKm5L7t1sSHydoO53PzoVZ7k1JS2ZhxOnveBntPGHEO9r5v0lJ2m5OXTrrJaNj1L9MVLm4zeHcEWye01ny28iCHTv4D14ZDeW9Q14J1ykqFH+7kf7XghYAdzIs4RWxSGp+sPETb+p70ae6Xd+ylQxDxtOnK5uRHK98B/LwngfOXUqnl7lT2z1CIorzzjlm/9pp163EzyUgyD0NsZHq2ImVelnlqhBDVVkmyn92rta6ttbbXWgdcGdBorQO11rH53r+ntW6stW6utf7telRaVAJlY74AVIAmfq44OJtMY+x8BTY9ZFp6kqLIcGkGaTEQvcYkJwi8v+D8OE5+ZhxPzDpo/hw0+jsA09ce4cM/D2OpP4rxA5vByUWQcr7ghe1MmuZBwSaAeWXBbs5eTOXF/g1Q60ZA7BaIXgfLu5tWoDYTYcgRVL2ROGTG0en9FWw/cQEsWSYhgRAVJTLSLKLybHoYjn0LSUdg52uQGnvNU24qliywpMk8NUKIakseWYnrTilFPa98/1E2Hg1ASpvPuX/aUhb438mxrZ8RWDMUGv7t6gJ8u5uAp+ciUDZsjIrj378f4PZgf6aOaoNK2AnrhkOrl6FmKGQmmW5zYZ/Bpodo425SSW+KjOKlZnvocmIiRK+FZk+ZNNO2ztD/d3BvDsDdiUPo3SaBmSc7ErH5DG3PRZhECY0fgYYPg3Od6/2RiRvdt99auwY3l/gIODnfdGt1awZ73wWvtlBvhLVrVnWkXwBHH3DytXZNhBCiTCSoEZXCz82RF+M/ZOKoHuBjpi6a9OcRtkU78lfNXizdV4vAzhMZ7d0o95zUjCxOJ6TQ2K8HnPkFMhI5l+LI03O20dDHhYkjQ82Eom7NzdPFfR/kXbD1q9DwQQA8U7bzfuAfjHRdiINNJiTVh3aTzdidrHRocG+BLil2XiHUiZvGS/57mR99ih9VDwa51MV556uw63Xo9KUMpBWiOtn1Ojh4Qct/mEkHbWvA+TUS1OTn5AMjzoMlw9o1EUKIMpGgRlQKPzcnFh8JZmJ2QLP5SByzNhzloS4N6HDHSr6Zs505v+2nTwtfnOxt+XbTCX7YeoKElAwWPD6Itp39yVA1eOr7cJLTs5g7pnPehKJ2NWBQJKSeN19W7FzMFxg7Zwh5F7w74lLnMltSHOje7xnw7pA3142tQyGV7QGRH2OrnPg65lYOnGrEBzXa8O09k2l96kXY8hh4hpiscEKUxeuvm/Xbb1u3HtWZ1ialfJ2B4NGq6ONiNsKZpRD6Adi7m20+XSBmbeXUszpRNjLDvBCi2pKgRlQKPzdHLqZkkJqRRZZFM27eLup7OfPSwBY42Nnw/ohg1h2K4YEvtxCdmArAra382XIsnsnLo/jm0QGs2HOWiOMX+OieUJr4uRW8gHNds1wp6FUAhg4JvnpfUQKGwW1bsHGuz9dZniSlZvK3mVsY+W0M00dNo4fDq7ld1YQok5MnrV0D68pKM+M3coKMsji5ALa/AEe/gQERoLPg8jFIPgUZCXDoC5PAJDHKzLvVbGzeuX69YPebpsuVQ81rX2vHy5B8Gtp8UPjfmerOkgXLOpvPqNFD1q6NEEKUyTUTBQhREfzczdO/mMQ0Jv95kJMXkpk0MhRnBxNXe7k48Gz/pli05vFejVn3Ul/++2B7nuzdmHWHYll1IJpVB2Jwc7JjUMh1HtOibExrTo1a+Lg6EujjwvwnutLYz4WHv4tiY50vTGuQJdN8OcuRchZW9IU9713f+lUjKelZrD0Yg9Yyv24Bs2aZ5Wa1ZggsqF328y1ZJumIvSe4NYHMREjYCb80h5X9YN2dZj6rM7+ZBxudZoC9a975fj1NIpFLhSRryEqHQ5+bgCf9otkW+IAZk7O8N1y+AQPSC9shPjx3/i8hhKiOJKgRlcLPzWRSi05M4/c957itlT8dG3oVOGZMz8ZsebU/Lw5oQV3PGgD8rUsgTf1ceXXhbn6MOEnPpr7Y21b+j62vmyNzx3TBy8WBGeuPmi87S4Pg4Gd5Bzn6mae5e966Mb/4lMHinaf528wtfLXhmLWrIqqSWr0gK9lkIDu1BNbfXbqxHGd/MxMEd5oOPeaBg6dpPe3yNfRbBQPCYfhpGLTfBDABgwue79sDRsaDT+eryz65ALY+CfO8YHlPM3eLZ2vo+yeknjO/9yd+Kt/9VzXnlpt1rX7FHyeEEFWYBDWiUvi6mZaa/WcvcTohhbDAEnT5ABzsbPj3yBDOXExFa+jd3HqZeVwd7bizXQCrIqOJTnM0XVoOfQ7HvjdfzmxszRcfgH0TrFbPquREfDIA7y3dT8TxC1auTRXy8stmuRlpbca0gGkdOPyFCRIOTC55GQ41of7dpqtoDnt3kxykVm/wam/SwReVlt7G1iQHSYuHlHNmndPqGr3GrO1coN6duWVony4wcDt4tDZBWFq8Oe7UzxDzV8nrXhWdW27GCdaoZe2aCCFEmUlQIypFTvez5fvNXDIhAZ4lPrdd/Zr8+XxPxvRsxO3B5eiyUgHuCgsgy6L5fvMJaPIYJB2GDffnBTEu9c1cOlFfmr79N7kzCan4uDri7+HE2O+3EZeUdu2TbgZxcWa52WSlwm9tzDgXFMRuMEGEsjGT72alm+MyL0PCbtPN7OQCwqtopAAAIABJREFU8z4/327Q/YfydZc6vRSWNIWFtWG+N8z3gbPLTHe25s/CXZcg+HVQNkRfSqXPh6uZudMW+q6AZk+b1iGAkwth1UDY/H9wYErx17Q2S6aZmyt/d9DMFDO5sX9/69VLCCEqgAQ1olJ4uzhio2B1ZAw2CoLqlm6AcNNabrxye0tcHK2b26Kxryu3tKrFjPVHuegzFFwaQMBwCH0fAK01cy/fj8WiTcalm9zphBQa+bjw+f3tibuczrNzd5BlkfE1TJ9ulpvNiZ8gYRe4BoJHSzj9q5lbquOX0PV7k41Qa1gaCktD4Nh3ZnzMovrw170Q8RxseqRiJs707Qrt/gMdpkH7KeDaCNYOhcD7oP3HJtDKNuG3AxyLS+bdX/ex8XgyhE3N2x/ytql31AzYOR7SqnCweux706UubkvetoxL5p7rDrFevYQQogJIUCMqha2NoqazSZ/c1M8tN0FAdfSPW5qRmJrJq0sOYbk9EnouAFsHLBbNvxbtYfwfiQw9+Q06e5JRwp+BAx+ZmcxvMmcvplDb04mguh68M7Q16w/HMvnPSKIvpUrygJtR5Cfg3sKM3WjxT2j5T7hlPTT+O9S9wxyTHg91B4OyhcRI6L8GarY1E2hGzYQjs8yYmjLKsmjz0MHB08w31fQJaP4M9F0OIe9AjYKtwV+siWLB9tM88v/s3Xd4VGX68PHvmZnMpPfeSCUVCCT03ouooCgqWLAv9sKqa1n33Z+6Kro2XAXFuhZcC6gIUqV3CBACpPfeeyYz5/3jhBTSQ0jB53NdcyWcM3PmmZDMnPs893Pf431xtjLlw10JVOsNxGSWsOl0FmuO1nF++A6Y8puyLuhC+lp/5DxR+Vp8snGbmQuMWauscxIEQRjABu6ZpTDgXBPhzoHEQpaN8+nroVySEDdrnpoTzKubzmJnruXeSX7sjc/n11NZ7I7LZ6inDSfTYW98AZVVpUxIXo95bSqGUy+hvi697Tz/K4zBKJNdUo17fdGHxSO9OZJcxKodCazakcC3945htJ9DH4+yjzz5pPJ15cq+HcfFZKNSvS9xLRhrQa5TgpBp25V1KJci/xAUHoao95Q+Uf53tn4/nQNE/lupyJX8FQx7CaZvbdxv1Hcr7ey1TWf5cFciBqOMm40p79w8nJE+TYqVmDpByJMN/5RlmTe3nOfd7fHMH+rGM/OCMdeqeX9nPBNe3U5+eW3DfXf4O/DVPXNgQUb/XJeStx/0JeA2S+nlVXKmcV9ZPFj6N/buEgRBGKBEUCP0mr9fHdbXQ+gx90/2o6iyltW7EvniQAqgFEP427xgrh7mzthXtrP044P1936fGdYH+cjnn+Se+wHn0Fsu7cnrqkClvfSTzMssv7wGvUFuCGoA/rkgnEEO5qz8/Txnskr/vEFNVVVfj6B1sSvh1AvgOktZH4akLL7vzO9a8SmwDFCa4bbm3FugsQLf2zo3FqcJyqxH7i6lgtkF3Qhotp/N4f2dCcwIcSHcw5r1JzJZ+tFBNjw4gSBXK6pqDVTrDdhZKLPJsizzj5/P8Om+ZG4a6cVLC4egVkksivTkvR3xSJLEOzcPx8/Rgi/2p/DLyUzqDEY0/TGgqcqC3QuVmal5p8A6BEpjlX3V+cq6ouGvNwvoBEEQBiIR1AhCN0iSxDNzg/FxsKC2zsD4AEcCnC2R6q92BrtakVxQwX+WRuJqbUpF9Ugq9q4k7ehq8LyOs7G7mBTsBTpHJc2mrgKqMpQSs6XnwcSm7Su+Mf8HGb9A+HPgtajPr7CWVOkprdLjZW/ebHtmsXLi7m7TODNlaqLmgakBrNqRQFphPz2x74TaOiMlVfqGqn5dtmpVx/fpC/YjYMiLEP5C898rWYbyBGURfUWqUjHMdogSwJSeV353t01T0sYmrW/9dzLoUSU4MbFqua814c8ps0ROE9q8y564fELcrHCwbPv/Ib+8hr/+7yTBrlasWjIcnUbNktGDmPv2bhav3o9akiioqEWjkvj9sUn4OVny0e4kPt2XzF0TfHnuqpCGv2sfRws+vj2KQGcrvB2U3/dxAQ58eySNczllhNlVKoVDgh4Bz2s79zovt6QvoTpHSa9TmShrmXJ3KftKzypfbcL7bnyCIAg9RAQ1gtBNkiRxy2jvVvetvGEYtQYjI7wvlK625tipW0lKTeDVr4/ztOphSLio8Z/DKGWdwd7Fyr9tQmHc12A3tPn9/JZB+galrKz9SBjzsXKCWc9olFGpei/Q+cfPMfxwLIM5Ya48PD2QUHelCERmcTVAs5kaUH5u3vbmDeWeB4IN0ZnsPJfL/ZP9WX8ig28Pp1FRY+CPv05p6MHUr8kypHytpHR5LwaHqNbv5zqj9SpYJ/4K8avB/+7G0stzo5XfzazNcPQRkDSQsxPqypXAJeZlcBynrIfR2oDjKOXWWWpT8F3a5u7YrFKWfnyQqEF2vLRwCNvO5rDjbG6ztLBZoS4k5JVTWl3Hl3ePRqdRZpycrHR8sHQEq3cl4mCpxcXalHe2xfH9sXTmD3Xn1U1nmR3m0iyguWB6SPOLDRf+xo+lFBHm6qEsws/a0hjUnH8fzr+rFBRI+AQmfqeUi75cKlKgPLlxjUzxSTDzANv6wCVkBQQ+oHx/YZ2fpf/lG48gCEIvEUGNIFwG4R42Lba5T3qT217fSLmhkOUmz/BiZApzQuxBNig9M9znwfEVypqC0Gcga1N9ChDKSakkgb5cOQGZewKSv4Bjj8Pxp2DqRqpqDez7djHDpN3Yj1iBKrR30kmOphThaWfG3vh8NsVksyjSk9cXDeVEWhEalYSnXct0JC97c9IGUFDz7eFU9sYX8MOxDFQSTBrsxM5zeXxzKI2Hpwd2/YCPPqp8feutnh1oa0rPwen/g+QvlYpdSZ/D/LNKr5emqrKUK/o2Q1qmm/ktg6QvlIDG+wbwvV2p/AfKbIrWVklhQtU4E5O+HuLX1Pd7WQRDX+zRl/XxniRUEhxJKWL2W8rMQ7iHNUPq//YKK2r5cJdy0v78/FCCXZtXXIzysSeqyZqaE2nF/HgsgxNpxViaanj1+qEtAprWeNqZ4WSl4+1tcdQZZZY5T4WE1SDrIexvYBehzIjsubH+EZISeFRlKSWWLbwh81flAkVbwWZH0jeAxlwJSCszYNsUJZ1s+OtQcroxoAGl2hwo7yXlicp4LvxfCoIgDGAiqBGEXuJqa8GQQV7sTyxAbeHFCzEeqAcPYWKgI6YmatCXQVE0+NymVIUKeUJ5YNbvcOIZmLETdi1U1tNM3ahUbsrcCIXHAXjrl108Ia2n0GBNZsoRPEMv/2sqqdSTUlDJitlBLB0ziJWbz/HFgRSuG+HBTycymRLkjJVpyzUQ3vbm7EvIR5blTp049jUTtVIo8rEZg7k+0gNPO3Nu/fggXx1M5S9T/Bv297nkr+DMqzDk70qFMa2NkiqWuk6ZBfS5GXbOVxpHau2UHjBaB+V+sW8ogc/CLDBzbX5cm1AlEMreqjS8VDX56HCbCdfnNyuBDEDo07D7OuX7iFd79GXmllWz4UQmS0YPYpCDOWZaNdODXXBtkupoNMo888MpqvSGThUnuXmUN/d9cZTMkmpemB+KbX21xo5IksRzV4Xw9rY4Vu1I4I7H1yDF/FPpVZX4KVybovxf5GyD6TuV4GP3/cpFi6a8roOxXyj7u0KW4dhj4DxZCWrsR4DPrcr6qEE3KYUB7Ec2f0xVFmweA2odmHspJakFQRAGOBHUCEIvemhaAH5OFlwzzJ37vjzKPZ8fwVKnYVaoC09Oc8XdOhiCH2v+IK09FB1T+ksURUPoXxv3DXtZOZk01DK06G20ZnU8mfc2RcUe/DzLgCp/tzKzo7Xt/FqGi6X9oDQYHPkf5WRWpWtYM3E6swSAoZ422JiZ8Ld5IWyIzuSv/ztJXlkN14/waPWQXvZmVNYaKKioxbGd9RD9RWmVngkBjjwyo3FWZtl4H+789AjrT2SyKNKzawe8XDM0ad9DSQzsXqTM+F0drzSqvLG8cYF9xCtgXj/e6OcaF42Dki52cUBzgdYWvBe1vu/igAaUvicOo8AmrLFccw/5cn8KeqOROyf44uvYeiqXSiXx6qKhre5rzewwV356YDzxueUsiHDv0niujfCgts7Iiv+d5GyJOSEjVylBXdZm5ec5eg3k7W1MCQt/TmngaaxV0sM8rlYCR7UO0n5S1tpZ+jT+P7Wn4LAy4xL2nPJvtSlEvQvpPyrNQGfta/kYtamyDsphVGMqmiAIwgAnghpB6EXjAhwZF+AIwOFnZ7AvoYCNJ7P4+WQmv53OZpz/C7wxygXbpg9yiILRHyvrFjwXQPjzjfusAgCQk78mXH2YY5qFLJw0hSe+i2bXqZNMOTMNzD2gMl25WjxlY9euylakwaH7wNxbKWiwa4FyojXyP6Ay4VSGEtSEuyspP2ZaNbeM9uY/OxMY4W3LtBDnVg/rXV9UILWwckAENWXVdbhYN187MzXImRA3a97fGc/C4R6oe3EdUwN9KRy6X5nVs4+EqFXgs1SZIXGdpczAXMzvjsbv5xxWUpfqKpT0x4t6tFwSlRpm7uvxKn1FFbV8eTCV6cEubQY03RXhZUuEl23Hd2zFxEAnAB5fF81D0wKYN8QLAu5Wdlr6KrcLnMY3fFvnfg0/ncjE1qyYqYPtUe9ZpKSkAox4C4Ifaf+JU75RZm+9FjZu09qA/11w7h3luQLva/4YrZ0S0AD4LunOyxUEQeh3RFAjCH3ERK1i8mAnJg924sFpAbzx+zl+OpHJkeQiZoReVPnM/07wWaKcvDRN16rOh+QvKLGdwTVxb/DwnFHcHuHOu9vjeH1XOZPDpiHlbFMqSPndAXSx4eW+W8BQw49mr5G9O4PbrYZjfv5fyknXmE+IySzFw9asoRQuKM1Jbx7pjZe9WZupZRcqpaUVVjYpptB/lVbrsTJt/nYpSRL3TPTl8XXRnEgrJnJQF17HA/VXxy+lClplJmyfpvQZcZ2hBDVmrkrgO/F7cJna8TE0FkpK2uXSAwGNLMscTSkiuaCS7JIqNsVkU15TxyPdWct0GbnamOJha0ZsVimPfnOCIR42LSoCtmZTTDZPfhcNwIdLhzN7bjRUpsHZN+DYo2AVCB7zlDQzua5lSevMX5T/f+1FwdiwlwCp9cIPoAQ7sSuVCxcWXt14xYIgCP1LP0kEF4Q/Ny97c569SlkEk1nSRqljta5lqdy6cjj2OGWp2ygxWOHtYIFGreKBqQHEZJZy0OavStGB6TuUSlLqLsyKVKRB3h6MYc/x1NZaXt18jogfJ7FDWoqc+DmUxZNSUIGfU/Or5SZqFd4O5u2ulXGprxiWV1bT+fH0obLqOqxbWRs0LdgZlQR/nM9r+8F1rRREMDNTbpfi+BPKgvPp25s3spQkZX3GxYUABqid5/NY9MF+nvwumpW/nye7pIZ3bopgiGcrs1B97P0lI1h5wzBUKnht87mOHwBsj83F2lSDTqPiQFIR2IaB+xwYtUbp65OxXrlj0hfwvZOSWhj/kfL3WZ2rBECus1oeWGOhNDG1aqOy2YWZmtRvu/FKBUEQ+h8xUyMI/YSDhRatRkVGURf6t9Tn3HvFP4aN+uuGtK6Fwz14d3s8rxwwYf2DLyv3LU+GnB3gv6xzx87bC0Cm6Vhq60p5cGoAZdV6Xjwxly0B69DGriSt8DrCh3Q9ZcnaTINWoxoQQY3eYKSy1tBqwQNbcy0RXrb8cT6Px2cObr5TNsKRByHuP0ppbp+bGvetXNnJJy8HE8uW2+PXKGlH4X9v3pjyMkgpqOCrQ6msmBWEpg8KInx9MBVHSy3r7huLu62ZUlSjnxrmZcswL1tSCip4d3s8d03wbTedzWCU2Xk+j2nBzmSXVnM4ubBxp6UPLMxsLBxg7qmsacrarKydAvBcCAuzldTQrvJaBBN/APeeXe8kCILQV8RMjSD0EyqVhIetGRnFXQhqmlShKjFY4WWvXP3XqFVcM8ydUxkl1BmMyh3Sf4SDd0L6z507tvMEGLWamEplLcDMUBf+cW04t0wZzfqiSVRn76eoshYvuy5Wa0JJ3XKy1HUqqJFlmcPJhcTnlnfq2DV1hi6Ppz3l1XWAEoi1ZkqQMyfTiymsqG2+w1jb2DgydV3Xn7jgMKz3VgJRqE8/qk8fLD6lpBU1LRrRA6pqDWQ1mSmsMxiZ/PpOPvwjkZP166d6U25ZNdvO5nL9CE/8nCz7dUDT1H2T/XG01PLyr7HU1BkaGtHKskxuaTVFFbVU6w1siM6gsKKWaSEujPKx50xmKWXV+sYDmVg2FmFwnQajP4JrU2HeaaUwgKRSgp7WAt+OSJKyDkdUPhME4QohZmoEoR9xtzXtWlADEPk235/Ix8lKh7m28U/axcYUowwFFbXKIveA+yD5v8o6mVkHlDSX9ph7QsA9nN8WB0CAs3LiNG+IG7M338d+R38gsyGQ6ionKx155W0HNXUGI7+eymLN7kROZ5Ti72TB1scnt5vWtul0Nvd/eZRVt4xgcpCycFujki7pZLisPqhpbaYGYHyAA29ugUNJhcwJb1I5TG0KPrcoDSlTvwWjvnE9xL33Kl9Xr277iW2HgMZSSTcysYKqTKX5ZeS7MHylcnW+hxfhv/JbLN8dSefre8dwMr2YL/anNOw7k1na6+uffjiWgcEoc+PIgbXmw1Kn4dEZg3nup9PMfHMXOaXVrL1jJB/8kcDuuPxm9x3ubcvMEBfszE0wboftZ3O5NqL1qoGAEozYhoHtPxv7VwnCFeB4ahFPfBfNmtui8HfqRqAu/OmJoEYQ+hF3GzN2xbWzPqM1QQ/z7Y79eNs3LwLgYqWsn8kprVaCGo05TPoJNkXBrmtg9iGl7G9ragqUUs7eN3AupwxPOzMsdMrbhZe9Of5urmw4mYWlqrJbMzUAzlY6UgouWm9iNDScqN+29hD7Egrwc7Jg/lA3fjmZxdnsMkLcrFs5muLn6EwAHvjqWMM2tUriu/vHdnhCXlhRi5mJGjNt80ChtP7K+cWFAi4Y4mGLTqPicPJFQU3aT0pjSve5kLAGcncrV9sBLIyQs11pjJm3VymlbBPc/MB7b1IW+9cUKGW9TZ2Usr+SqseDGVBSoTaeyqJKb2DBqr31r82GtxZH8PcNMcRklvb4c7ZHlmXWHU5jpI/dgDzBuWmkF5/sTSIhrwJTExVLPjqImYmax2YMxsZMQ6XegFatYsnoQZhp1Yz2dSDM3ZoX1scQOcgOz878XYmARriCfHs4jcS8Cm5fewgTtYo1t0U1XEzrDL3ByEe7kziXXYqVqUnDn8dQT9uul90XBiQR1AhCP+JhZ0ZuWQ21dUa0ms5lhxqMMjGZJVx/0Zv2hRLEuaVNZkPMPWHij0rH8aOPwrgvWj9o+k9w6F6wjyIup5zBLs173Mwd4gqFR/jS9zmkuu+AuZ19iQ2crHQcSSlSrjYba5Wywr8NB/e5JPuuZF9CAQ9M9eeJmUEUVtay8VQWv57MahHUxGSWkFJQydxwVw4mFTIt2JkpQU7U6JW0u/d2xPPx7iRGLGk/qBnxzy34O1mw7YkpzbZfCGpaKxQAoNWoiPCybb4ewlgH+5eC7x0Q8TLYhCtVrC643RliU0HSwJGHwFAJLtMgcDl4XqM0Yk1fD8PfgJDHO/cDvQSxWaWsP5FJfnkty8b7YDDKXD/Ck2H160G+PZzGmczeTT87nFxEYn4Fy6cG9Orz9hSNWsUHSyM5k1WKpU7D98fS+evsYHzaKEOt1ahYdcsI5r+7hwe/Os66+8Z2+j1AEAa6OoORzTHZeNubN2QrrN2bxMsLh3T6GA9+dYzNMTm42ZhSrVfSkKv1Rr45lMZVQ9xaXLASrjwiqBGEfsTd1gxZhuySarwdOjcDkpRfTkWtgaGezRckO1vXz9SUVTd/gNNYZYGwfVTLgxkNoC9RFqFb+lGiCyM+byuzwpqXmJ4b7sZbm32okC1wi38ZfLse1DhbmVJYUYs+/VdMjtwLVoOhMhUKj7ClMh2Am0Z6o1JJOFrqGO3rwLazuTw5O0h5XaXVvL75HN8fS0eW4fVFQ8kvr2FOmGuzdKW88ho+3pNEVkkVbjatp8pd+ABMyKugsKIW+yYlqkurLqSfXfR2KcugLwatHaN87Hj/j0QqqvVY1KVD3j4lSHOZCibWMPeEMrtiNEDeLkj6XOkbZOUP1yZBwlqI/wD2LAK32TDk/ynP0Vblqh729w0xHEoqRKdR8eSsoIZZuQvC3K354kAKdQZjrxUL+OZwKlY6DfOGtNEMdAAIdLEisP6CwPQQlw7uDT6OFry2aCjL/3uMf/12lheuDr3cQxSEfmFXXB5FlXpeXjiEaSHOPPfjaX46nsFTc4KxMWv9glJTp9JL2ByTwyPTA3msSdGWnedyueOTwxxJKWzoJdVVZdV6LLQaVH3Ri0zoEnEZSBD6EU9b5aQ7tVBJy6qqNfDOtriGhcatiU5TrqAPvajEraOlDkm6aKbmAo+rwKyVk6xtU+B7B8jeCt438Ed8PgajzJSg5h8Gvo4WBLg6sLX2aqS8PVCV3YVXqRjJRr7z/yulNbLSdT73Dwi4H2YfZlNsIWHu1s36fMx1T8Wm/AC1dUY2nc5i6sqdbDiRyV3jfbE21fDcT6eRJBgf6NjseRZEeGAwyhxKKrx4CA2SCyoavn9xQwxfH0rl95hs8strGhZuO1UeVMozy0Y4sAy2z4DvneGPa7nT8DCSUc/+wxthg59SkMFhtNIzBhrTxc6+AdumwdtZsKY+cDB1hrCn4eoEpfx2SQwUHlH2WV7+WYqC8hqOJBeyIMKdz+8c1SKgARjiaUNNnZHo9N6ZrSmt1rPxVBZXR7g3Wyf2ZzBviBt3jPNh7d4kdnc1FVUQBqD0okpWfHcSHwdzpgY7o9OouWO8D9V6A8//dBpZbr+/mizLvLM9Diudhrsm+jbbN8rXHo1K4unvT/HgV8corqxt4yitS8wrZ+wr21m7N6nLr0vofSKoEYR+JNzTBhO1xO64PMqq9dy+9hBvbjnP2j1tv6GeTC/GXKtuse7ARK3CwUJH7sUzNRdkb4OdV4GhyZt84F+ULuZjP4ewv7HjbC525iZEeLVM3Xrn5uFMmFLfHyXz186/yPJEADyNpwgxTSJdOx6mbYFrEiDqHWRDNfqCE4z2bbLeRzZyW+livvH9K3G5Zby1NQ4PWzO2PD6J5+aHcu8kPwxGmZcXDsHDtvlszIWu8+ntlMpOylOCGkdLHRuiM3nmh1Pc+8VR5r29m8T8CuzUJTgfmQ+n/gGV6ZC1BfL3KzMxWb9h6xRAmKsZa47JyMNeVsosj/mk5dqXgPvAbxkMuw4CI5vvU6lhyAtwTTLU1J/MWvp1/ufaTdvO5mKU4e6Jfoz2a32N1bRgZ8y1atYdTgOUpqkvbogh6v+2ciCx4JLHsPFUFjPe/IMX1p9m57lcXtt0lmq9kZsGWIGAnvLMvGDszE348XgGoFzc2HQ6i/0JBY3VDAXhClBRU8fdnx2h1mDko9tHNhR1CXO34YlZQWyIzuTTfcntHuPz/SlsOZPD/VP8W6QJm2s1uNdXFf3lZBY3rzmIwdi5JtRGo8zj66Ipr6lja2xOt16f0Ls6vAQmSdJaYD6QK8tyeP2214GrgVogAVgmy3Jx/b5ngLsAA/CwLMubL9PYBeGKY21qwlh/RzaezuJAYgExmaW42ZiyL6HtE8fYrDJC3axRtzI17mylI6d+pkaWZfLKanCuX2uDbIDMjbB1stKN3mWyUq2rnizL7Dqfx5Qg51aPHeBsqaSyxXhD4XHoTKZU0hew/zaYtAF7/Tniqr0pKK8vYVt/Al998lU2+D3PZ9YnlO36MqW8MbAq9wbKTmRyNruMv18dyiAHJWB5YGoAt471wUbOhZMvQvjzDQGFmVaNo6WO1IuLEjSRmK8ENX+smIJaJVFQUUtcThn3f3mU/+xM4FaH3UiyXvn5WHjDglTl56cyAUMNklrHbap0nvguml0mdzN52jOtP5HWBsashTFtDERd/3/jNBEsfEHTucpy8bllfLYvhQenBTSspeoMWZb55lAqHrZmhLm3XYDBytSEa4a5s/5EJiVVerbE5iChFGH478FUxrQRDHXWZ/uSySmp5rsj6XxeX3Htnom+LVIq/yx0GjUTA53YdT6PzOIqrv/PPrJKlIsTNmYmTA1y4r7J/u0WzRCE/s5olHn02xOczynj02WjWhQF+Mtkf46nFvPSr7EM8bAhyse+xTGyS6p55bdYpgU785fJrX8IPTw9kPUnMpga5Mz/++UMJ9KKiBzU8lgXi04v5kRaMR62ZhxLKaZabxgwZeX/rDozU/MpMOeibVuAcFmWhwLngWcAJEkKBW4Cwuof874kdacrmCD8ec0KdSGtsIrYrDL+szSSJaO9OZNV2rIPSr3CytqG9TMXc7FunKn54I9ERr+yjR3ncpWdbrOUGZnSMxC3qiFwaDhuRS0FFbUt0tqakSQIfryxo33Kt7B9lrJQvqnsrZDwCUT/Tfm3sRbzqljian34aE8itXVGZFnmpV/PsK9AKWc7ylDfYPD0P2HHTGQTaz4qvIUPdiUiScq6nsZhSEre9ZnX4PQ/oK55pS4vezNMSw5AXeuzNYl5Fbham2Kh02BqosbD1owpQc48NE1Z3D/L+gBYh4DdsPonVDWWZ1YrP/urh7njbKXjo92Jbf+8Ost+OExt/XqQLMtkFFdhMMoYjDJrdiUy7509fHEgpc0ZvU2nsziZXozRKDdL5dgQncmx1GIenh7QbqlsgGXjfXGx1nEstYg7x/uw+6mp3BDlyZYz2ZTX1LX72PbkldVwKLmQZRN8Of7CTD65YyT/uCaMp+YEd/zgK9iUICfyy2sZ96/tlFTp+WTZSD5YGsnMUBe2nc3l4a+Pd5iWIwj92bojaWw5k8Pz80OZNLjleheVSuKNG4fhaWfG8v8eI6e0ZdZJXVAsAAAgAElEQVTB29vOYzDK/OOasDbXvCyK9OSLu0ZzfaQnGpXE1tjcTo1vc0wOGpXEitlB1BqMHEku6toLFHpdh0GNLMu7gMKLtv0uy/KFT7EDwIWyS9cC38iyXCPLchIQD4zqwfEKwhXvqiFuzAhxZu0dI5kZ6sJYf2WNyP42ZmvKqvVY6VpfSOlibcrpjFJe3BDDR7sTkWV47NsTpBfVz1r43grTtytpVP53NXvshXUmPg6tV2tqEPwI2I9QvteXQPYWpdBA08Am9g1lnUl1LkzbBuZeqGoLCAoex4HEQp754RSnM0pZszuJNw8rsxMh2f+EqiylyaTv7UgRrzHb4SSL7Tczf6g7rjatzEjU5IKFD2ibp8tNtI3nH2b3wa9hUNvygykpv7whTa2pJaO90Uk1jLKIURbwt0OrUXH7OB92x+UTnVbMW1vPU9RGIMrSpcqtzYPZgXVgq7s2nspm/L+2E/GP35n39m5e2hjLpEAnxgc48P2xDPQXpScpvXuOsWDVXkL/vom//XgKUNI+Xt4Yy1BPG26I7DjNK8jVip0rpnLo2Rk8e1UobjZmLIjwoFpv5K0t54nLKet0apTeYMRYnwLyc3Qmsgxzw10xNVEzNdiZ28f59FpBgv6q6aLm924ZztQgZ+aEu7LyhmH8bV4IcbnlvbbGSRAuhxNpxThYaLljnE+b97ExM+GDWyMpr6njhg/2sycuvyGYT8grZ92RdJaMHtRs/WV7xxrtZ8/vMdkdvlfJsszvMdmM8XNgZqgLGpUk1rgNAD3xqXEn8Fv99x5AWpN96fXbBEHoJDsLLR/dPpIJ9Qveh3naYGaibl4yuImy6rq2e6jUz7L892AKBRW1vHr9EAwGmQe+Ok5NnVLxC/tIJbCxDmr22OR8JfAZ1MkqbIDSHNJ2CJx8AX4fB/FrlO1jPoFrkmBhptKrxSYEnCYybOydPDojkO+PpTf0lokpVlIQ6iwGg5mbMqM0Zi0E3scjQTH80+sT/r1gUOvPX3xSmUHJP9Bs82iz+lmoiiTObVrebJ/BKBOXW46fU8ugxtZcy7OjitCp9OA6s8OXv2S0N2Ymau789DBvbY3j+2Pprd8xKEi5dcPuuDysdBpmh7siSfDGDcNYc1sky8b5kl9ewx/nGj94UwsqWfG/aIZ62nDfZH/C3G349nAaSfkVrNoRT05pDS+2c4WzI5GD7LhltDcf7Uli5r93sbqdWapXNsYy4p9bGPzcbwQ++xsTX9vB6l0JvLb5LKN87Ql2tWrzsX9GTlY6Nj06kRMvzGRacPOiHvOHumFqouLz/cl9MjZB6AlJ+RX4Olp0OEsc7GrNV/eMobLWwNKPD/Jx/Yz0ys3nMDNR89C0zhdUuTbCg4S8Cha+v4+YJmXqa+uM7Dqf13CxZd2RNBLzK5g7xBULnYYxfg78fiZHzI72c5cU1EiS9CxQB/z3wqZW7tbqb4AkSfdKknREkqQjeXki+hWEtmjUKoZ52XAsteUMQ53BSGWtoc1u90tGDyLx5XlsfnQS79w8nBujvHj9hqFEpyl5yk0dSCxolkaUUlCBSqJzTQAvkFQQ+TYYqqA4GkzrT8bMXMHSp7HZp4kVzNwF5p48Mj2QhcM9Giq+gcS42E9h9uEWh3cb/QxauRxNwn9aPrehWmlmWRYHRx9ptmuwfIATlYE8m76cx4+P4e7PjnA6Q/lAO5tdSll1HVE+rfexuW3BnTDvlDKb1QFbcy03RnlSUD9D09bsGs8/r9y64XByISN97Vl5wzA2PTqJ6yM9kSSJyUFO2JiZsPFUFgA1dQYe+OoYErDqlhE8NSeYD5ZGotWoeOp/J/lodxLXj/DssClpeyRJ4qUF4Xx4ayShbtb8cCyj1Q/9yto6PtufjJe9OcvG+fDYjMHoTFS8vPEszlamvHfL8A5PbP6Mgl2tsTXXtthuZWrCbWN9+OFYButPZPTByATh0iUXVDSsi+xIhJcte56ayvgABz74I5EDiQX8djqbeyb64WDZevp1a26I9GTVLSPIKqnimvf2snLzOUqr9SxevZ/b1h5iQ3QmJ9KKef6nGCYEOLI4SpnFnh3mQlJ+BfG55d16rf1dcn5FQ2uDgazbtTIlSbodpYDAdLnxUywdaJrH4AlktvZ4WZZXA6sBoqKiROgrCO2IHGTHB38kUllb16zEbVm1EoRYm7X9p6xSSfg5WeJXXx1tTrgbd03w5eM9SSwZPYggVysS8sq5afUBApwt+fDWSPydLEkuqMTDzqzrDQBdpsKCNCXNy7TjvgCSJPGv64fgbK2jpFLPN4fTMJp5ojFtZRG0XQS4zVXWAIU9owRRF5TEKov3zb2UGZszr0F5Egx5geSA93j5p72cqwtj+RR/1uyK56EPjzE4YBh/1T2Og3q5Um0t/wAUnwZJrRyr7BxEvAa24Z1++XdP9ONoahHmWg0Hkwp7tLdLQXkNCXkVLRqtglLtblaoC5tOZ3MoqZBvDqVyKqOE1bdGNqRmOFnpePaqUJ7/6TSWOg1PzenebFFTkiQxO8yV3LIanv/ptFK44qKiAzvO5lGtN/L0nGDG+iuB7fKp/uSW1eBspcPkT55q1h0rZgdxLKWIZ388TeQgu65dfBCEPlZZW0dOaQ2+jp3/vTU1UfPA1ABuWXOQuz49jKOllrsvKuHcEUmSuGqoG+MDHHhxQwzv7Ygno7iK46nFWOk0fHEghYyiKpytdbx78/CG9+6Zoa48vz6Gn6MzeXzWpb9v9icVNXXMfXs3M0JdePfm4X09nEvSrU8SSZLmAE8B18iy3LSk0AbgJkmSdJIk+QKBwKFLH6Yg/LmN8LbDYJQ5eVEO/YWgpq2ZmrYsqj8pPp9TBjSWO04tqGTBe3vZciaHlIKKjtfTtEWl6VRAc4FOo+aZuSHMH+oO0Pp6mQt8lyprbfIPKut2Ej6B6OeURpWTf4Hw55RZmxNPQeo6qMxgcGAkOI7h63vG8OC0QA4uOMLmkMeYUPov/A37iLLPxl1KVoocHLpHWf9z6B5IWaesA+oCL3tzfnloIreP9aG8po6fT2a2nL246Sbl1kVHU5TZulGtVAECpcdJWU0dN364nx+OZ3DfZD9mhTVvXnnrmEG8v2QEq5aMaKyE1wPmhbtiopZ4d3tci9f7y8lMHC21jPJtHLeJWoWHrZkIaLrJRK3i34sjkGWZh74+Tml9PyVBGAgupDf7tLKWsT1j/RxYMTuIQBcrnp8f2mpfrc6wNdfyfwuHYKKW+PF4Bh62ZiwZM4ijKUUUV9Xy4a2R2DVpwuxqY8rMUBc+3pPUasGCgexIShFVegM/R2cO+HVDHX6aSJL0NbAfCJIkKV2SpLuA9wArYIskSSckSfoAQJblGGAdcAbYBDwgy/LAn88ShD52IUXo4tLOF05k2lpT05YL62SS60sZ59SXi/38rlH4OFpwz+dHiE4v6X5Q000XSnq6tRfUuM9TZmz0pbBtqhKAxLykzLJ4XAX+98DV8XBDCSwqAIcobMxM+HH5eIZ5KSWCTYPuQWvtx632P5FQ44HefhzkbFdKNl+TpNyujoerz7XepLQTJgQ64m1vzmPfRrPy93PNd0ZEKLcO1NQZmvUZ2pdQgKmJqmGtVGvPedcEX15fNJRdK6byzNyQVu83b4gbk1upNnQpHCx1PDkriN9OZzeUZQYlcN4ck831IzxbLQ0udJ+XvTlv3DiMU+klLFi1l53nuhaAC0Jf6XQhmotIksQDUwP46YHxXBtxaUu2LXWahgstkwY7cUOUJ85WOl69fihh7i3fY5+7KgS9UeZfv529pOftb/YnFKBRSThb6Zq9dw9EHZ4JybJ8cyubP27n/i8BL13KoARBaM7OQsvEQEe+OpjK8in+DbXyG2dquhbUmGs1uFjrSK7v3XLhylOEly3f3T+W93fEczCpkDnhru0dpse5WOtwszFlsEs7i8a1tjD3uPJ9TS4Mfgj2LoYds+H6fGXdjlUHTXPMXGHGTjj8F6pNr+PlwOFgM7bHXgcolXa2PD6JF36KYdWOBPwcLRvTxp5+ulPHeHtrHKt3JfL03GDumuDL3vh8Rvk6oNO0XinfRK3i+fmhPfUSuuyeiX4cSirk/349Q4SXLcO8bHlt0zkstBrub6OHhHBp5oS78dmdJjz74ynu+OQwkwY78fxVIQS29zckCH0soX5tSldnanra1CBn9sYXMHmwE/5Olhz82/Q21/cNcrDg3ol+vLcjniWjvVvtmzMQHUgsIMLLlnAPG745nEpVrQEz7cDsxiLm/QVhgPjLZH/yy2uaLQy+MFNzcRflzhjkYEFK/dWy7NJq7MxNMDVRY2qi5vFZQXx731jGBzj2zOA7SZIkfntkIg9M7WQ1G99bYdCNMPwNUJvReq2SNugcYMI6wqJuaj/d7RLoNGr+b2E44/wdeOaHU21WsGvLwSTl/v/3ayx3fHKYuNxyJgRcWqPLy+lCXwlnK1Me+OoYW8/ksDVW6fTdNJVD6FnjAxz5/bHJPD8/lBOpRSz6YD9VtSJJQuif9AYj3xxOY7i3LZbdTB/rKTeO9GLF7CCmBTsDdFiwZPlUf9xsTHlhfQwG48BfDl5eU8epjBLG+DkwK9SFar2RXQM4BU0ENYIwQIz1d8DRUsvx1OKGbQ2FAroR1Pg6WDSZqanpUif6y8nWXNv14gTBj8GiYtD1vytnJmoV7y8ZgYedGfd9cZTUgkq4/nrl1o46g5GYzBJuHTuI564KYW98PkCvB5pdZWuu5b1bhpNTWs3dnx/B2UrHneO7tphX6DqtRsVdE3z54NZISqr0rPhfNLd+fLDTvYMEoScl51e0uvZk0+lsFn+4n4ziKh6e1novrt5kbWrCA1MDOv2ZY67V8Ld5IZzJKuXljbFt9yIbIA4nFWIwyoz1d2Ckrz02ZiZsjskGlF49X+xPZtPp7L4dZBf0bYgsCEKnSZKEh60ZGcVVDdvKurmmBmCQozn55TWUVevJKa3uN0FNt0gSqPvvTICtuZaPb49i4fv7uOuzw/wcNaohhbAt8XnlVOuNDPO0ZcFwD4Z723EkuZAQ11aqwvUzw73t+ObeMWw5k8ukQMcBm8owEI3xdcDD1oxfTiqlvU+kFV8xaTJC/5dVUoW1qQlLPz6Ih60Z396npPXKsszW2Fwe+voYzlam3BjlyZSgnl3X11vmD3Vj0+lsPt6TxN74fH5+aEKvFjxJyCtHbzCyL76AHedyGeVjz1h/B4Z62nb5guD+xAK0ahWRg+wwUauYHuzMtthcqmoNPPvjKX44noG7jSmzw1wGRNl9EdQIwgDiYWfG2eyyhn93d00NNC7QTKq/ohbq1v9PlgcyPydL/rNkBLetPcST4TN575YR7d7/ZJpS6W5ofVGAyEF2RA7qfk+Z3hY5yJ7IQeJkurepVBI3jfTioz1JlNfUsfNcnghqhMuuzmDkPzsTeHtbHP5OlqQXVZFRXEV6USXHU4v5cFcCpzNK8Xey4Ie/jMfGvOvZBf2FJEmsWjKCq05lsfy/x3hvezyPzRzcK89dVWtgyZqDFFXWojcYsbfQsjsuH7YoBYA2PDChSz/bA4kFRHjbNlxkmxXmyg/HM5j3zm6S8isY5WvPoaRCzuWUETwALqiJ9DNBGEDcbczILK5qKJlbWqXHXKvuVh+UCxXVtsXmkl9eg4t15xuYCd0zLsCRR6YH8svJLDadzmr3viczlL4JvV2BThj4HpgawIFnpjPC25Y/zg/c/Hih/yup1HMirZjrP9jPG1vO42FnxrmcMiQJZBnmvrWbh74+TmWNgVeuG8KvD08c0AFNU/OGuLEgwp23t8Xx1tbzXXpsncHIW1vPM/vfu/g9pvPpXWv3JpFdWo2jpQ5PO3N2PDmF48/P5N+Lh5FeVMU/fz3T6WOVVus5nVHCWL/GdZqTBjui06jIKKri7ZsiGvrWbD87MCoripkaQRhAPOzMqNYbKayoxcFSR1l1XbdmaUCpuz/Sx44vD6RglMHlMi2WF5q7f4o/ox5dRuWPdSTu2drQFPVip9JLCPewQSXKIAtdpFJJmGnVTAp04o0t5ymp1DecSGaXVPPgV8f49+KIhqasgtAVlbV1/PV/JxnsYsX7O+Op1huxNTfh3ZuHM87fgfGvbmeIhw11Rpm0wkpeuX4488Ldrsj3spU3DMNEreKtrXGUVtWxdIw3fk6WfPBHAt8eTuOWUd7cPdG3RerW+hOZvLU1DmcrHfd/eZQIL1uifOyJGmTH9BCXNsvff3s4jYmBjnx0exR6g9xQaGHhcE9iMkr5aE8ST88NxtGy44uUhxILMcowpklQY67VsOa2KOzMtQ2tA8LcrdlxNpflUzpZwKcPiaBGEAYQd1szADKLq5Wgpkbf5cabTV01xI0Xf1au7ISI9LNeYaJWEXTLtXzwRyI/74jnzRtb9quprTMSm1XGsvE+vT9A4YoR6q78TcfnlTWkAm6JzeFIShGvbT434LuHC50Tk1mCjZkJnnY9E8RujsmuX7OVha+jBQ9NC2BCoCPOVsqFsY9uG4mTlQ53W1PUKglz7ZV7qqlRq/jX9UMBZRZl7d4kApwtScgrx8XKlJc2xmJjbsKNUV6Akj6WU1rNuiNp+DiY8+vDE3l/ZzwHEgv5dG8yq3cl8uDUAJ6cHdTiudKLKkktrOTO8T7oNGouLhw3f5g7H+1JYl9CAdcMc+9w7PsTC9BqVAz3tm22fdJFPcxWzA5qs41Af3Pl/qYJwhXIoz6oySiuZIinDWXVdVh3c6YGYOEIT87llDF/qHtDOppw+dk+s4JYl0PkZZW1uv9cdhm1BmObTTYFoTMuNLONzy1vCGqM9WVoT6YXt/k44cphNMrcvvYwfk4WrLuvZ3px/Xg8Ew9bM+6Z6MvMMNeGz6ULJgT27wqNPU2tknj9hmE8NnMwm2Oy+e10Nvbm9qxdNpJ7PjvCcz+eZm98Pkn5FZzJLKWu/m9wxewgLHQaVswOBpRmy498fYJP9yVz90RfbM2bF7/ZX998e6x/6z/fIR42WJtq2BOX17mgJqGASG+7DovWTAly7vBY/YUIagRhALnw4bE3voC0wipis8oIc+/+DIuNmQmvXDe0p4YndMFgF0sOJBZgMMotUg1OZignnMM8bVt7qCB0iqedOVqNivj6RocAuWVKmd2UgkryympwshJr6a5kJzNKyC+vIb+8hrTCyoaUw+LKWvQGucv//ykFFeyJy2P5lADuEKXam3G3NWPZeF+WNfm5vHfLcP75yxl2nMsjxM2Keyf5YWeu5WBSIYtHejV7vE6j5tGZgWyKyea/B1Nb9GvbHZePg4WWwS6tpyyrVRLj/B3ZdT6fhLxyUgsrmTLYqdWqZcWVtcRml/LYjN4pcNBbRFAjCAOIrbkJljoNXxxIAcDb3rxTV2SEfmbuXO4sqWbNpCdJK6xs1lVblmU2x+RgZ26Cp51ZOwcRhPapVRJ+jhbNgpqc0pqG74+nFjErzLUvhib0kh1ncxsW7X+8J4mJgY7872g6W2NzMMpw53gfnr0qtFPHMhplVnx3EguthiVjvC/zyK8MDpY63rqpZZrnPZP8Wr1/sKs1Y/zsWXckjeVT/JEkCaNR5o0t59gQncmS0d7tlla+boQHv5/JZvobfwDw2Z2jmDy4Zensg0mFyLLS/+5KIoIaQRhAJEli9a2RlFbXMWKQbUMOszDAXH01dYWVUA5xueX4OFpQU2cgrbCSLw+ksut8Hs9dFTIg+gII/VuAsyXRTVLNcstqcLcxJbOkmuxWmiMKV5ad53IZ4W2Hi7WOT/cl8+m+ZBwstNw21oeC8hrW7E5iWrBLmye3RqPcsMB/7d4kDiUX8vqiobjZiAsul8uNUV48vi6aQ0mFhHvY8Pi6E2yOyeHmUV78/eqwdh87K8yV7+4fy564AtYdSePN388xKdCxxWfJ/oQCTE1UV1w2gAhqBGGAGdfPO8oLnbB8ObbVenjxd+75/AgetmZkllQhy0of0ZtGenHXBJHaIVy6AGdLfj2V1VABLbe0mhA3a3LLasguEUHNlay8po5TGSU8ODWAR2cM5o+oPIxGmUmDnTBRq6jWGziUVMjrm8/yw/LxLR6/81wuf/nyGL8/NomaOgOvbT7HjBAXFkV69sGr+fOYG+7GixtieG9HPKXVdZxKL+aF+aEsG+/TqQtdF3qEudroeOr7U2yLzWVGqEuz+xxILCBqkH2Xm3X2d1fWqxEEQRggrExNMKtfoDnc25aHpwXy78XD2PHEFP51/VAxSyP0iBkhLqgkiRd/jgGUmRpXG1NcrE3FTM0VLjqtGKMMkT72qFQSU4OcmR7igkl9XzNTEzWLorw4nlZMtd7Q4vGrdyVSpTew/WwuT6yLxkKr5uXrwsV702VmplWzfGoAu+PyiU4rZtUtI7hzQsuy0B25boQn3vbmvLnlPLIsU2cwAlBYUcvZ7LIrLvUMxEyNIAhC75sxA4D/ff49JmoVg12s+nhAwpUq3MNGCZi3nmd8gCOFFbU4W5niYq0TMzVXuCPJRUgSLUr2NhXkYoUsQ0JeOWHujdUW43LK2FdfbevNLecpqdLz7s3DRcpzL7ljnA+/ncpiarAzc4e4desYJmoVj0wP5InvonlxQwxfH07jp+XjScxX1tiJoEYQBEG4dIsXAzQ7iRCEy+WBqf7sjsvjbz+eAsDFWoerjSlns1svKS4MLEajzEd7ElkQ4YGzdWPQcTS1iCAXK6zb6WUW6NJY9rvp+9HvZ3IAmBjoyO64fIJdrZg/tHsn10LXmZqoWf/ghEs+zrUR7qzaGc9n+5XiQjvP55JaUImVqYahHlfe549IPxMEQeht99yj3AShF2jUKv69OAJdfdqRs7UOV2szskuqkWW5j0cnXKojKUW8vPEsn+1Pbth2LruMA4kFjPK1b/exPg4WaFQS53OaB7hHU4rwc7Lg2ggPAO6e6CfSzgYgjVrFillBmKglLHUaDicVsjsun7F+DmjUV14IcOW9IkEQBEEQmvGyN+el64ag1agIcLLC1UZHZa2Bspq6vh7an54syw1NUbtja6wyq7LrfD4AFTV1LP/vUWzMTHhoWmC7j9VqVPg4WhCX01j222iUOZpSxMhB9lwzzJ23b4pg4XCPbo9P6Ftzh7hx7PmZXD3MjR3n8sgorrpiG6SK9DNBEITeNmWK8nXnzr4chfAnc80wd+aEuaLVqHCtL8mbU1LdbnqScHmsO5LGz9GZZBRVkVFchVGWmRPuxrs3t+xp0pGt9alipzNLKCiv4f9+jSUpv4Iv7x7dqeaagc6WzVIR4/PKKanSE+ljh1ajapitEQYuK1MTRvrY8/WhNACuHXZl/p+KmRpBEITedscdyk0QetmFEq6u9WsvskSxgD7x1pbznM8pI8TNmtvGDmLyYGd+js4kOb+iS8dJL6okMb+C64Z7IMvw8DfH+fF4Bo/OGMw4/85djQ91sya5oILiyloAvqk/8R3p037qmjCwTA1yZmaoC5sfnYSN+ZV5IUPM1AiCIPQ2EdAIfczNRglqRFnn3ldWrSezpJoVs4N4YGoAAFklVWw7m8MPxzN4fObgTh8rMU8Jgm4c6UWNwcivJ7OYGOjYcNzOGO3ngCzDoaRCiiprWbs3iSWjvfF1tOjaCxP6NTsLLWtui+rrYVxWIqgRBEHobXq98tXkyrxaJvR/ztZKWpIo69z7ztevX2layt3Nxoxx/g78cjKzS0FNSoES1Pg5WvDezcO5Y5wPIW7WqFWdX9Q/zMsGUxMVq3clciKtmImBjrx4Tfud6wWhPxLpZ4IgCL1t5kzlJgh9RKdRY2+hFTM1fSCuvtJY0EX9qSK97UjOr6C2ztjpYyUXVGJmosbJSockSYz0scdS17Xr1TqNmshBdhypr3i2asmIhgadgjCQiJkaQRCE3nb33X09AkHA1dpUzNT0gfM55ZiZqPG0M2u23cfRAqMMqYWVBDhbdupYKQUVDHIwv+Ryy3PD3UjOr+Tj20eKwhHCgCWCGkEQhN62dGlfj0AQcLURQU1fOJ9TRoCzJaqLUsR86tewJOdXdDqoSS6oJMCpc/dtz9Ixg1gy2lv0ohEGNDG/KAiC0NsqK5WbIPQhVxtTckT6Wa9LzCtvNWjxdagPago6VwHNYJRJLahkkIN5j4xLBDTCQCdmagRBEHrbvHnKV9GnRuhDrtamFFTUUlNnQKdR9/Vw/hRq6gxklVa3GojYWWixMTMhqZWyzrV1RjKKq3jp1zO42phSrTeSX15DrcHIIAdRpUwQQAQ1giAIve8vf+nrEQhCQ6+a3NIavOx75mq/0L6MoipkGbzb+Hn7OFq0mKn5ZG8SKzefw9fJgvPZ5ZiaqLDQabA11zJ5sBOTBl+Z3eEFoatEUCMIgtDbFi/u6xEIAq42jQ04RVDTO1ILlbTTtoIaP0cLfjudxf1fHMVcq0ZnouL7YxnU1hk5nVHarLeNIAjNiaBGEASht5WUKF9tbPp2HMKf2oUGnJnFVX08kj+PtA6CmmXjfaisrSM+r5xqvYFqvZEAJ0uenx/Kttgclo336cXRCsLA0mFQI0nSWmA+kCvLcnj9NnvgW8AHSAZulGW5qH7fM8BdgAF4WJblzZdl5IIgCAPVtdcqX8WaGqEPDXKwQKtWEZtdygI8+no4fwqphZXoNCqcrHSt7h/qacuHt7be9X2sv8PlHJogDHidqX72KTDnom1PA9tkWQ4EttX/G0mSQoGbgLD6x7wvSZJYfSgIgtDUww8rN0HoQ1qNiiBXK2IySvt6KH8aqYWVeNtfel8ZQRBa6jCokWV5F1B40eZrgc/qv/8MWNBk+zeyLNfIspwExAOjemisgiAIV4brrlNugtDHwj2sOZ1ZgizLfT2UVm2LzeG3U1l9PYweYTDKxOWWt5l6JgjCpelunxoXWZazAOq/Otdv9wDSmtwvvX5bC5Ik3StJ0hFJko7k5eV1cxiCIAgDUH6+chOEPhbmbkNxpTkmMVgAABZsSURBVJ6MfriuJqO4ige/Os7z62P6bdDVWZW1dTz30ykS8yqYHeba18MRhCtSTxcKaG0+tdV3IlmWVwOrAaKiogb2u5UgCEJXLFqkfBVraoQ+Fu6hFKs4nVGCp13fzCAUVdRiZarhjk8OE51e3LC9ts5ITZ2RKr2BczllBLta98n4LlV5TR3XvLuHxPwK7pvsx40jvfp6SIJwRepuUJMjSZKbLMtZkiS5Abn129OBpn+tnkDmpQxQEAThivPEE309AkEAINjVCrVK4nRGKXPC3Xr9+atqDUx9YycuVqacyynjqqFuODdZRB/mbsOT30WzJy5/wAY1r2yMJamggk+WjWRqkHPHDxAEoVu6G9RsAG4H/lX/dX2T7V9JkvQm4A4EAocudZCCIAhXlKuv7usRCAIApiZqAp0tOZ1ZctmeI7ukuqEnzsW2xuZQXKmnuFKPl70Zby+OQKNunhn//s549sTnc/dEv8s2xstlX3w+/z2Yyt0TfEVAIwiXWYdraiRJ+hrYDwRJkpQuSdJdKMHMTEmS4oCZ9f9GluUYYB1wBtgEPCDLsuFyDV4QBGFAys5WboLQD4R72HA64/IUC9h+Nocxr2zjcPLF9YYUG6IzcbHWcd9kP15ZOLRFQAMwxs+Bo8lFGI0DK1O9oqaOp344iY+DOU/MCurr4QjCFa/DmRpZlm9uY9f0Nu7/EvDSpQxKEAThinbTTcpXsaZG6AfC3a3539F0cstqcLFufUaluz7anQTAryezGOlj37C9Wm/g31vOszU2h3sm+vHM3JA2jxHhZctXB1NJzC8nwNmqR8d3Ob226SzpRVV8e+9YzLSiu4UgXG49XShAEARB6MjTT/f1CAShQdNiAT0Z1JzPKWNfQgFatYrfY7L5+9WhHEstpqSqlpd+jSUhr4KbR3nxyPTAdo8z3MsWgOOpxQMmqCmurOXzAyncMsqbUb72HT9AEIRLJoIaQRCE3jbn4n7GgtB3QtyskSQ4nVHK9BCXHjvup/uS0WpUPDlrMC9vPMu6I2k89f0pANxtTPn8zlFMGuzU4XH8nSyx0mk4kVbMDVEDo3JYQl4FsgzTgsU6GkHoLd3tUyMIgiB0V1qachOEfsBCp8HP0aJHiwWUVOr58VgGCyLcWRTphUqCf/12FoB3bh7O5scmdSqgAVCpJIZ62XA8tbjjO/cTyfkVAPg4WvTxSAThz0MENYIgCL3t1luVmyD0E+EeNsRk9FxQ893RNKr0Bm4f54O9hZaRPvYUVeoJc7fmmmHuWJmadOl4UYPsOZtdSkmVvsfGeDkl5VegVkl49VHvH0H4MxJBjSAIQm977jnlJgj9RLi7DZkl1RSU11zysQxGmc/2JzPSx44wd2W9zqwwVwAmd3J25mJj/R0wynCkjSpq/U1SQQWedmZoNeI0SxB6i/hrEwRB6G0zZig3QegnGooFZJZe8rH2xOeTVljFHeN8G7bNH+pGuIc110Z4dOuYEV62aDUq9icUXPL4ekNSXgW+IvVMEHqVCGoEQRB6W2KichOEfiLU3RqVBHvi8i75WEl55QCM9mus+uVibcovD00kyLV71ctMTdSM8LZld1x+v+9XI8syyQUV+DiIoEYQepMIagRBEHrbnXcqN0HoJ2zMTJg3xI3/396dR1dZ33kcf3+Tmx3IxhYgMcEFVKQgkaLtuNaxjrU6apV6tLSnM3ZqO63Omc6xnZluYzsu05bxMKN4XMc6btQO2lpE7aBTqaAoWFZFBIMsQiCBLJCE/OaP50GvIWS7ybPc+3md8zv33uc+y/f5kvMj3/ye3/M8sryOxpbU5q00+PNeSgr6N2+mN5efOoENO/dzzx+i/QeB9/a00NJ2iGNHqagRCZKKGhGRoP3oR14TiZBvnHMcTQc7eGJFanfma2hpZ3hegkT24P6KccWMCVw4ZSy3LdrAqrro3gntubU7ATjrBN3OWSRIKmpERIJ21lleE4mQEytGMGZEHuu2709pP42t7RQXDu4oDYCZcctlUxk9PI9vPfoGTQc7Bv0Yg+F3q3dwUsUIqsp15zORIKmoEREJ2oYNXhOJmOryIrbUN6e0j4aWNkqGoKgBKC7MYe7s6dTtaeH7/7P6w+M9vGxLt3NtDrQf4u+fWMXiNTuGJJ6ulm7czYote7lwythAjiciH1FRIyIStK99zWsiEVNdXsTmVIua1nZKCnIHKaIjzawp4/qzj+PJN95nzbZG/nv5e/zjr1ezeO2RhcsfN9WzYMVWrntoBQtWbB2ymAB2NB7gKw+8yqQxw7lm1jFDeiwROZKKGhGRoP30p14TiZjqkUXsbmpj/4GB3yygsWVoLj9L9tdnTqQoN5t7/+9dXtu8F4A7X9xE+6HOj623dONuAD4xoZgfP72GXftTfw7P0bz01i4OdnTy71+cRmnR0BV1ItI9FTUiIkE74wyviURMtT8PZEt9y4D34Y3UDG1RU1yQw5WnVfLUqm0s21TPyGF5rKproPbm5/nOE6tYsuEDOjsdL2+sZ9bEMn5+1TQOtHfyL79ZO2QxvbKpnvKiXCaNGdhtq0UkNSpqRESCtnq110Qiptp/YOTm+mZ2Nx2kpa1/k/E7O92QzqlJdt2ZE8nKMprbDvHdCydz75xazp08mkWrd/Dl+1/l3J8tYe32fXzq2JEcO2oY159zLE+t2saSDR8MeizOOV7ZVM+sieWY2aDvX0R6lwg7ABGRjPPNb3qvS5aEGoZIV8f4IzUbP2jiJ79dx9mTRvOvl53S5+2b2jrodAzpnJrDKooLuHpmFQ8s3czMmjIqywo578QxHGg/xLNrdrBgxVaOGz2cS6ePB+DrZx/L06u28c8LV7P4hrMoyM0e8LGbD3awcOU27vnDJto6OnEOtjUe4OtJDxwVkWCpqBERCdrtt4cdgUi3CnMTnFQxgvtf3kxjazt/2LirX9sffnDnUM+pOeymCyfzuakVVJZ9dPvk/JxsLpk2nkumjf/YunmJbH76l6dw1d2vcOui9UweO5wp44uZMr64X8e868V3uG3RejodTK8qocYf3crPyeaiqeNSPykRGRAVNSIiQTvttLAjEDmqy2dM+HDuSd2eVrY1tDKupKBP2zb4Rc1Qz6k5LD8nm9rqvo+OfHJiOV8+o5oHlm4G4BOVJSz8xqf6vP3iNTu45XfrueDkMVxZW8k5k0aTlaXLzUSiQHNqRESCtnKl10Qi6JJp40hk2Yc3DXh1854+b9vQ2gZASWF07/5104WT+ezJY/lkTRmr6hpYs62xz9sufaeeotxs/uPqUznvxDEqaEQiREWNiEjQbrjBayIRNHJYHvfMqeX+r8xkeF6CZe/2o6g5PFIT0OVnA5Gfk81d185g/rUzyE1k8fl5L3PtvcvYvLv35/Os276PSWOHk8jWr08iUaPLz0REgjZ3btgRiPTo7EmjAaitLmXZpvo+b9fQGuzlZ6koKczljtnTWP7uXp54rY4L5r7EjeefwF99uqbbosU5x/od+7loakUI0YpIb/SnBhGRoE2b5jWRiJtZU847u7zbO/fFnibv8rO4PHzys1Mq+P7FJ/Hc353FWSeM8ubLzH2J36/fecS6O/YdoLG1nRPH6jk0IlGkokZEJGivvuo1kYibWeNNwn+1j5eg7Wk+yIj8BDkxuzxrbHE+d3+plvnXzsA5+PajKznQfuhj66zfvh+AyRUjwghRRHoRr15HRCQdfOc7XhOJuFPGF5Ofk9XneTW7m9soH5Y3xFENnQtOHsvNl05h/4EOnl2z48Pl67bv49ZF68nJNiZppEYkkjSnRkQkaPPmhR2BSJ/kJrI4taqU5X0dqWlqozwml54dzayJ5VSWFfD4a3VcdEoF81/axNzn36K4IJf5185gRH705wuJZCKN1IiIBG3KFK+JxMDMmjLW7dhHo38TgJ7saW6jLOZFTVaW8YUZlby8sZ7rH36d25/dwAUnj2XxjWdy7uQxYYcnIkehokZEJGhLl3pNJAZm1pThHKzY0vtoTX1zG+XD4l3UgPcAUjNYvHYnX5xZxbyrT419sSaS7lTUiIgE7Xvf85pIDEyvLCUn23qdV9PZ6djbEv+RGoDxJQV8+riR5CWyuOEzx4cdjoj0gebUiIgEbf78sCMQ6bOC3GymTijpdV5NY2s7hzod5UXxvVFAslsvn8rOfQcYMyI/7FBEpA9SGqkxsxvNbI2ZrTazR8ws38zKzOw5M3vbfy0drGBFRNLCpEleE4mJmTVl/GlrIy1tHUddp77Ze0ZNOlx+BjCupIDpVfoVRiQuBlzUmNl44FtArXNuCpANzAZuAl5wzh0PvOB/FhGRw1580WsiMTGzpoyOTscb7zUcdZ09flGTDpefiUj8pDqnJgEUmFkCKAS2AZcAD/rfPwhcmuIxRETSyw9+4DWRmJhxTClZRo/zavY0HwRU1IhIOAY8p8Y5976Z/RvwHtAKLHbOLTazMc657f46281sdHfbm9l1wHUAVVVVAw1DRCR+7rsv7AhE+mVEfg4nVozo8Q5ou5v8y8/SZE6NiMRLKpefleKNytQA44AiM7umr9s75+52ztU652pHjRo10DBEROJn4kSvicTICWOGs3l3y1G/3+tfflZapIdTikjwUrn87DPAu865Xc65duBJ4Axgp5lVAPivH6QepohIGnn+ea+JxEhlWSHbGltp6+js9vuG1nYKc7PJS2QHHJmISGpFzXvALDMrNDMDzgPWAU8Bc/x15gALUwtRRCTN3Hyz10RipKqsEOfg/YbWbr/f29JGaaHm04hIOFKZU7PMzBYArwMdwBvA3cAw4HEz+ype4fOFwQhURCRtPPRQ2BGI9FtVWSEAW+qbqRlZdMT3DS3tlBTq0jMRCUdKD990zv0A6HoLn4N4ozYiItKdysqwIxDpt2PKvaKmbk/382oaNFIjIiFK9ZbOIiLSX4sWeU0kRkYNyyMvkcUjy+t45k/bj/i+oaWdYo3UiEhIVNSIiATtllu8JhIjWVnGwY5O1m7fx/UPv45z7mPfe3NqVNSISDhU1IiIBO3RR70mEjMXTa348P3WvR/dMKCz09HY2q7Lz0QkNCpqRESCNnas10Ri5hdXTuPxr50OwKqtDR8u33+gg04HxQUaqRGRcKioEREJ2tNPe00kZnITWUyrLCE3kcWquo+Kmr0t/oM3NVIjIiFJ6e5nIiIyAD/7mfd68cXhxiEyALmJLE4eN4KV3RU1RRqpEZFwaKRGRCRoCxZ4TSSm/uz4Uby2ZS9rt+0DoKG1HYDiAo3UiEg4VNSIiARt5EivicTUVz9Vw/C8BLc9ux7wnlED6O5nIhIaFTUiIkF78kmvicRUcWEO3zjnOJZs2MUf36lnb7M3UqM5NSISFhU1IiJBu+MOr4nE2JwzqqkozueWRet5c2sDw/MSjNDdz0QkJLpRgIhI0BYuDDsCkZTl52Rz42dO4B9+9Sar6hq4ZlYV2VkWdlgikqE0UiMiErTiYq+JxNxlp47nuNHDAJh9WlXI0YhIJtNIjYhI0B57zHu96qpw4xBJUSI7i9uvmMpLb+1myngV6iISHhU1IiJBu/NO71VFjaSB6VWlTK8qDTsMEclwKmpERIL2zDNhRyAiIpJWVNSIiAStsDDsCERERNKKbhQgIhK0X/7SayIiIjIoNFIjIhK0e+7xXq+5Jtw4RERE0oSKGhGRoD33XNgRiIiIpBUVNSIiQcvRU9dFREQGk+bUiIgE7YEHvCYiIiKDQkWNiEjQVNSIiIgMKnPOhR0DZrYL2BJ2HL6RwO6wg8gwynk4lPdwKf/hUe7Do9yHQ3kPj3I/+I5xzo3qujASRU2UmNlrzrnasOPIJMp5OJT3cCn/4VHuw6Pch0N5D49yHxxdfiYiIiIiIrGmokZERERERGJNRc2R7g47gAyknIdDeQ+X8h8e5T48yn04lPfwKPcB0ZwaERERERGJNY3UiIiIiIhIrKmoERERERGRWIt9UWNmlWb2v2a2zszWmNm3/eVlZvacmb3tv5b6y8v99ZvMbF6XfV1lZm/6+7mth2POMLM/mdlGM7vDzMxffqaZvW5mHWZ2xVCed5iilPOk768wM2dmaXvbxCjl3cx+YWYr/faWmTUM5blHQUj5/4mZ1ZlZU5fleWb2mP/vsszMqgf/jKNjALk/38xW+D+7K8zs3KR99diX9LZepvTzEK28J32f9n09RCv3mdbfh5R79fWDwTkX6wZUAKf674cDbwEnAbcBN/nLbwJu9d8XAZ8G/gaYl7SfcuA9YJT/+UHgvKMcczlwOmDA74AL/eXVwFTgv4Arws5NJuQ8KYaXgFeA2rDzkyl5T1rnb4H7ws5PmuZ/ln/cpi7Lrwfu8t/PBh4LOz8Ry/10YJz/fgrwftK+ev2Z7mk9MqSfj1rek2JI+74+irlPWift+/uQcq++fjD+7cIOYNBPCBYC5wMbgAp/WQWwoct6X+bjv2icBjyf9Pla4D+72X8FsD7p8xeB+V3WeYA0/88uSjkH5gKfA5ak+390Ucp70vKlwPlh5yPd8t9lH13/o3sWON1/n8B7WrWFnZOo5d5fbkA9kNePn2n18xHMe6b29VHIfdLyjOvvhzr3XbZXX59Ci/3lZ8n8YbnpwDJgjHNuO4D/OrqXzTcCk82s2swSwKVAZTfrjQe2Jn3e6i/LSGHn3MymA5XOud+kcBqxE3bek+I4BqgBft//s4ivgPLfk/FAnX/MDqARbwQo7Q0g95cDbzjnDtL3/lv9fBdh5z1T+3oIP/dJcWRcfx9Q7nuSsX39QCTCDmCwmNkw4FfADc65fUe5bPGonHN7zezrwGNAJ95fIyZ2d6juNu9nuGkh7JybWRbwC7y/hGeMsPPe5fNsYIFz7lC/goixAPPfYxjd7bqf+4id/ubezE4GbgX+/PCiblbrLm8Zmd+jCTvvmdrXQ/i57/I5o/r7AHPf424HYR8ZIy1GaswsB+8H72Hn3JP+4p1mVuF/XwF80Nt+nHNPO+c+6Zw7HW+Y8W0zy06aIPdjvEp7QtJmE4Btg3k+cRCRnA/Hu351iZltxrsm9al0nkAakbwnmw08ktpZxUfA+e/JVvzRHX+0pxjYM7Cziof+5t7MJgC/Br7knHvHX9ztz7T6+aOLSN4zrq+HyOQ+Wcb09wHnvicZ19enIvZFjX8niXuBdc65nyd99RQwx38/B++ayN72Ndp/LcWbnHWPc+6Qc26a377vDznuN7NZ/rG/1Jd9p5Oo5Nw51+icG+mcq3bOVeNNHv28c+61wTrXKIlK3pP2MQkoBf44CKcXeUHnv5ddJB/zCuD3zrm0/etdf3NvZiXAb4HvOudePrxyD32J+vluRCXvmdbXQ3RynxRPxvT3Qee+l3Ayqq9PmYvAxJ5UGt7dhRzwJrDSb3+Bd83hC8Db/mtZ0jab8SrdJrwq+CR/+SPAWr/N7uGYtcBq4B1gHv6kLbwJwFuBZryJYmvCzk+657zLOktI48mjUcs78EPglrDzkub5v83frtN//aG/PB94Am9+znJgYtj5iVLugX/y++GVSW10bz/TXY6Z0f181PLeZZ0lpHFfH8Xck0H9fUi5V18/CO1wJy0iIiIiIhJLsb/8TEREREREMpuKGhERERERiTUVNSIiIiIiEmsqakREREREJNZU1IiIiIiISKypqBERERERkVhTUSMiIiIiIrH2/64POiPIA7bSAAAAAElFTkSuQmCC\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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/i3h0hS4PMvTkcgpnH/++RQWFhIfH88DDzzAiBEjaNOmDbNnz2b69On079+fK664AoDJkyczf/780g77N954I9999x3Dhg3jhx9+ICQkBICrr76ahIQEhgwZwltvvUWvXr08+YjSyBhrbfUnGfNr4DYgE9gE5ADXW2sjypyTaq1tdapyhgwZYhMSEuoXsYiINxg3zlmrz4uIuMnmzZvp3bu3p8MQqZfKPsfGmDXW2iGVnV+joZKtta9YawdZa8cAKcB2INEYE+O6QQxwtF6Ri4g0Jc884ywiUjc5R+BtA4snezoSEWlEajraWLS19qgx5gxgOjAS6AJcBzzuWmsQbhGREgMGeDoCEe+Wc8hZH/rUs3GISKNS03lePjTGtAYKgNustanGmMeB91xNyvYBl7krSBERr7N6tbNWp32RuilSh30RqahGyYu19uxK9h0DJjR4RCIiTcG99zpr9XkRqRslLyJSiZrWvIiISG08/7ynIxDxbkpeRKQSSl5ERNwhLs7TEYh4t5J5XoLP8GwcItKo1Gi0MRERqaXly51FROqm48VwZT5M3e3pSOQ0Wbx4MRdddBEACxYs4PHHH6/y3LS0NF588cVa3+Ohhx7i73//e51jFM9T8iIi4g5/+IOziEjdGAM+/mD0VcXbFRUV1fqaKVOmMGvWrCqP1zV5Ee+nfxFERNzh3/92FhGpm8RvnXlelkzzdCRyCnv27KFXr15cd911xMfHc+mll5KdnU1sbCwPP/wwo0eP5v3332fRokWMHDmSQYMGcdlll5GZmQnAwoUL6dWrF6NHj+ajjz4qLXfOnDncfvvtACQmJnLxxRfTv39/+vfvz/Lly5k1axY7d+5kwIAB3OsaIOXJJ59k6NChxMfH86c//am0rL/85S/07NmTiRMnsnXr1tP47og7qM+LiIg79Ozp6QhEvFvKj8766PeejcObfDWu4r4zLoczb4XCbFh8YcXjXWc4S24yLL20/LGJi2t0261bt/LKK68watQobrjhhtIakaCgIJYuXUpycjLTp0/nq6++IiQkhL/97W889dRT3Hfffdx444188803dO/enSuuuKLS8u+44w7Gjh3L/PnzKSoqIjMzk8cff5wNGzawbt06ABYtWsT27dtZtWoV1lqmTJnCkiVLCAkJ4d1332Xt2rUUFhYyaNAgBg8eXKPnksZJyYuIiDt8952zHjvWs3GIeKuS0cZ8/D0bh1SrU6dOjBo1CoBrrrmG5557DqA0GVm5ciWbNm0qPSc/P5+RI0eyZcsWunTpQo8ePUqvnT17doXyv/nmG15//XUAfH19admyJampqeXOWbRoEYsWLWLgwIEAZGZmsn37djIyMrj44osJDg4GnOZo4t2UvIiIuENJkwXN8yJSNyXJi/q81Nypakr8gk99PCiqxjUtJzPGVLodEhICgLWWc889l3feeafceevWratwbV1Za7n//vu56aabyu1/5plnGuwe0jjoXwQREXd49VVnEZG6KRkq2da+s7ecXvv27WPFihUAvPPOO4wePbrc8REjRrBs2TJ27NgBQHZ2Ntu2baNXr17s3r2bnTt3ll5bmQkTJvDSSy8BTuf/9PR0wsLCyMjIKD3nvPPO49VXXy3tS3Pw4EGOHj3KmDFjmD9/Pjk5OWRkZPDJJ5807MPLaafkRUTEHbp2dRYRqRtrnXXLvp6NQ6rVu3dv5s6dS3x8PCkpKdxyyy3ljrdp04Y5c+bwy1/+kvj4eEaMGMGWLVsICgpi9uzZ/OIXv2D06NF07ty50vKfffZZvv32W/r168fgwYPZuHEjrVu3ZtSoUcTFxXHvvfcyadIkrrrqKkaOHEm/fv249NJLycjIYNCgQVxxxRUMGDCASy65hLPPPvt0vCXiRsaW/ONwGgwZMsQmJCSctvuJiHjMV18564kTPRuHiDRZmzdvpnfv3h6NYc+ePVx00UVs2LDBo3GI96rsc2yMWWOtHVLZ+erzIiLiDo8+6qyVvIiIiDQYJS8iIu7wxhuejkDEu215GtY/BBH94Nylno5GqhAbG6taFzmtlLyIiLhDp06ejkDEux35GgrS4dhqT0ciIo2IOuyLiLjDwoXOIiJ1U6TRxkSkItW8iIi4w+OPO+vzz/dsHCLequxQydaC5uoQEZS8iIi4x7vvejoCEe9WUvMCYIvB+HouFhFpNNRsTETEHdq1cxYRqRu/MMBA9Big2NPRiJuEhobW6vznnnuO3r17c/XVV7spooazYMECHi+pha+HtLQ0XnzxxdLtQ4cOcemll9a7XICkpCSGDx/OwIED+f777xukTHfTPC8iIu5QMovz5MmejUNEmqzGMM9LfYWGhpKZmVnj83v16sUXX3xBly5dqjynsLAQP7/6NS4qKirC17dx1Pa5cy6dd999ly+++IK5c+dWOHa63oPazvOimhcREXf4xz+cRUSkCXvzzTcZNmwYAwYM4KabbqKoyBlgITQ0lP/3//4f/fv3Z8SIESQmJgKwe/duRo4cydChQ3nggQeqLPepp54iLi6OuLg4nnnmGQBuvvlmdu3axZQpU3j66afLnT9nzhwuu+wyJk+ezKRJk8jKyuKGG25g6NChDBw4kI8//hiA7OxsLr/8cuLj47niiisYPnw4JT+sh4aG8uCDDzJ8+HBWrFhR6bMVFRUxY8YM4uLi6NevX2kczz33HH369CE+Pp4rr7yyNKbbb78dgL179zJhwgTi4+OZMGEC+/btA2DGjBnccccdnHXWWXTt2pUPPvigwnsxa9Ysdu7cyYABA7j33nvZs2cPcXFxpfeYNm0akydPpkuXLjz//PM89dRTDBw4kBEjRpCSkgLAzp07Of/88xk8eDBnn302W7ZsYd26ddx33318/vnnDBgwgJycnArvwcMPP8zQoUOJi4tj5syZlFR6jBs3jrvvvpsxY8bQu3dvVq9ezfTp0+nRowd//OMfq/181Iu19rQtgwcPtiIizUJSkrOISN0s/aW1X46xdsGZ1uYf93Q0jdKmTZvK7xg71trXXnNe5+c722+84WxnZTnb777rbKelOdsffuhsJyU52wsWONuHD9fo/hdddJHNz8+31lp7yy232Llz51prrQXsAldZ9957r33kkUestdZOnjy59Jznn3/ehoSEVCg3ISHBxsXF2czMTJuRkWH79Oljf/zxR2uttZ07d7ZJlfzb+tprr9kOHTrYY8eOWWutvf/+++0brmdPTU21PXr0sJmZmfbJJ5+0M2fOtNZau379euvr62tXr15dGvO8efNO+WwJCQl24sSJpfdNTU211lobExNjc3Nzy+177bXX7G233Wattfaiiy6yc+bMsdZa+8orr9ipU6daa6297rrr7KWXXmqLiorsxo0bbbdu3So82+7du23fvn0r3X7ttddst27dbHp6uj169KgNDw+3L730krXW2rvuuss+/fTT1lprzznnHLtt2zZrrbUrV66048ePrxDjye+Btbb0/bTW2muuuab0z3Ts2LH2vvvus9Za+8wzz9iYmBh76NAhm5ubazt06GCTk5NP+fkoq8Ln2IkjwVaRT6jDvoiIO0RFeToCEe92cAEU50NxgbNIo/P111+zZs0ahg4dCkBOTg7R0dEABAQEcNFFFwEwePBgvvzySwCWLVvGhx9+CMC1117L73//+wrlLl26lIsvvpiQkBAApk+fzvfff8/AgQNPGc+5555LZGQkAIsWLWLBggX8/e9/ByA3N5d9+/axdOlS7rzzTgDi4uKIj48vvd7X15dLLrnklM82efJkdu3axW9/+1t+8YtfMGnSJADi4+O5+uqrmTZtGtOmTasQ24oVK/joo49Kn/u+++4rPTZt2jR8fHzo06dPaQ1VbYwfP56wsDDCwsJo2bIlk13Nlfv168fPP/9MZmYmy5cv57LLLiu9Ji8vr9Kyyr4HAN9++y1PPPEE2dnZpKSk0Ldv39Lyp0yZUnqfvn37EhMTA0DXrl3Zv38/S5curfLzUR81Sl6MMXcDvwEssB64HggG5gGxwB7gcmttar0jEhFpClz/STF9umfjEPFWRbngHw75qVBc6OlovMPixSde+/uX3w4OLr/dsmX57aio8ts1GHDEWst1113HY489VuGYv78/xjW8ta+vL4WFJ/4MTTXDXts69scuSXZKyvjwww/p2bNnjcsOCgoq7eNxqmf76aef+N///scLL7zAe++9x6uvvspnn33GkiVLWLBgAY888ggbN248Zaxl34PAwMAaxVeVstf7+PiUbvv4+FBYWEhxcTERERGsW7eu2rLKvge5ubnceuutJCQk0KlTJx566CFyc0+MAlj2PifHUFhYeMr3sD6q7fNijOkA3AEMsdbGAb7AlcAs4GtrbQ/ga9e2iIgAPPecs4hI7RUXOPO7+Lm+jGqiykZpwoQJfPDBBxw9ehSAlJQU9u7de8prRo0axbuuoeTfeuutSs8ZM2YM//3vf8nOziYrK4v58+dz9tln1yq28847j3/+85+lycDatWsBGD16NO+99x4AmzZtYv369bV6tuTkZIqLi7nkkkt45JFH+PHHHykuLmb//v2MHz+eJ554grS0tAqDEJx11lnlnnv06NE1fpawsDAyMjJq9fxlhYeH06VLF95//33ASZB++umnaq8rSVSioqLIzMystD/OqdTl81ETNW025ge0MMYU4NS4HALuB8a5js8FFgMV6/5ERJojV+dQEamDkjlelLw0an369OHRRx9l0qRJFBcX4+/vzwsvvEDnzp2rvObZZ5/lqquu4tlnny3XPKmsQYMGMWPGDIYNGwbA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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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HWRlZXHXXXexe/duMjIyKp9j6tSpTJo0iS5duvDcc8/x5JNP0rdvX4YMGUJOTg4AO3bsYMKECfTv358RI0awefNm1q5dy913383HH39MVlYWhYWFNa7Bn//8ZwYOHEhGRgbXX3899oB99OjR3HnnnYwcOZKePXvyww8/MG3aNLp378599zmy49T2/lDKrXfekUWppmjQPyF1OES1gJhWgW6NUupMYoxptKV///6muo0bN1bdMGqUMXPnyu2SErn/73/L/dOn5f6bb8r9kyfl/rvvyv3sbLm/YIHcP3SoxvO5ev6JEyeakpISY4wxN910k3nllVeMkW+4ZoHtXHfddZd56KGHjDHGTJo0qXKf5557zsTHx9c478qVK01GRobJz883eXl5Jj093axevdoYY0znzp1NdnZ2jWPmzp1r2rdvb44fP26MMeb3v/+9+bfttZ84ccJ0797d5Ofnm8cff9xcf/31xhhj1q1bZ8LDw80PP/xQ2ea33nrL7WtbuXKlOf/88yuf98SJE8YYY9q2bWuKioqqbJs7d6655ZZbjDHGTJw40cybN88YY8ycOXPMlClTjDHGXHvttWb69OmmvLzcbNiwwXTr1q3Ga9u1a5fp1auXy/tz58413bp1M6dOnTJHjx41zZo1My+88IIxxpg77rjDPPXUU8YYY8477zyzdetWY4wxK1asMGPGjKnRxurXwBhTeT2NMebqq6+u/JuOGjXK3H333cYYY55++mnTtm1bc/DgQVNUVGTat29vjh075vb94azG+1gppZq6kxuM2f9hoFuhlApBwEpTSzwRfBP2nYdYREZWvR8XV/V+UlLV+ykpVe97MWH2iy++YNWqVQwcOBCAwsJCWrWSX4mioqKYOHEiAP379+fzzz8H4JtvvuHdd98F4JprruF3v/tdjfMuW7aMSy65hPj4eACmTZvG0qVL6du3r9v2jBs3jhYtWgCwcOFCFixYwBNPPAFAUVERe/fuZdmyZdx+++0AZGRkkJmZWXl8eHg4l156qdvXNmnSJHbu3Mmvf/1rLr74YsaPHw9AZmYmV111FVOnTmXq1Kk12rZ8+XLes/VyXXPNNdx9992Vj02dOpWwsDDS09Mre6h8MWbMGBITE0lMTCQpKYlJkyYB0Lt3b3766Sfy8/P59ttvueyyyyqPKS4udnku52sA8NVXX/HYY49RUFBATk4OvXr1qjz/5MmTK5+nV69etG0rBdi6du3Kvn37WLZsWa3vD6XcsvcIT5sW2HYoFQjHvoPCg45ilUop5SfBF7w0MmMM1157LY888kiNxyIjI7EsC5AvxGVlZZWP2be7O29d2IMd+zneffddzjnnHK/PHRMTUznHw91r+/HHH/nss8/4+9//zttvv83LL7/MRx99xJIlS1iwYAEPPfQQGzZscNtW52sQHe0oXFaX1+58fFhYWOX9sLAwysrKqKiooHnz5qxdu9bjuZyvQVFRETfffDMrV66kY8eOPPDAAxQVFdV4XufndH5ed9dQKbdmz5a1Bi+qKdr4KOz/n853UUr5XfDNeWlkY8eO5Z133uHo0aMA5OTksGfPHrfHDB8+nDdtNRxef/11l/uMHDmS//3vfxQUFHD69Gnmz5/PiBEjfGrbBRdcwLPPPlsZDKxZswaAc889l7fffhuAjRs3sm7dOp9e27Fjx6ioqODSSy/loYceYvXq1VRUVLBv3z7GjBnDY489xsmTJ2skIRg2bFiV133uued6/VoSExPJy8vz6fU7a9asGV26dOG///0vIAHSjz/+6PE4e6CSkpJCfn6+y/k47tTl/aEUAO+/L4tSTVHeVlnnuv8RTCmlfNXke17S09N5+OGHGT9+PBUVFURGRvL3v/+dzp0713rMM888w5VXXskzzzxTZXiSs379+nHdddcxaNAgAH71q195HDJW3f33388dd9xBZmYmxhjS0tL48MMPufnmm7n22mvJzMykb9++ZGZmkpSU5PVri42N5Re/+AUVFRUAPPLII5SXl3P11VeTm5uLMYY777yT5s2bVznf7NmzmTlzJo8//jipqanMnTvX69fSsmVLhg8fTkZGBhdeeCG33HKLT9cCJGC66aabePjhhyktLeWKK66gT58+bo9p3rw5s2bNonfv3qSlpVUO//JWXd4fSgEyrFWppqqiVNaaJlkp5WeaKjkElZeXU1paSkxMDDt27GDs2LFs3brh+9C4AAAHH0lEQVSVqKioQDetydL3sarBnhVxxozAtkOpQDi5AQ58AOm/kxoxSinlA3epkpt8z0soKigoYMyYMZSWlmKM4YUXXtDARalg88ILstbgRTVFzXvJopRSfqbBSwhKTEykeg+WUirIfPxxoFuglFJKnXGCIngxxnjM3qVUsGrMoZcqhMTFBboF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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "for x in regr_infla.columns:\n", " fig,ax = plt.subplots(figsize=(14, 4))\n", " ax.plot(df[[x]], label='actual')\n", " ax.plot(df[[x + ' pred']], color=\"orange\", linestyle='--', label='predicted')\n", " plt.axvline(x=regr_end, linestyle=':', color='red', label='end of regression timeframe')\n", " ax.set_title(x)\n", " ax.legend()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Disclaimers\n", "\n", "Indicative Terms/Pricing Levels: This material may contain indicative terms only, including but not limited to pricing levels. There is no representation that any transaction can or could have been effected at such terms or prices. Proposed terms and conditions are for discussion purposes only. Finalized terms and conditions are subject to further discussion and negotiation.\n", "www.goldmansachs.com/disclaimer/sales-and-trading-invest-rec-disclosures.html If you are not accessing this material via Marquee ContentStream, a list of the author's investment recommendations disseminated during the preceding 12 months and the proportion of the author's recommendations that are 'buy', 'hold', 'sell' or other over the previous 12 months is available by logging into Marquee ContentStream using the link below. Alternatively, if you do not have access to Marquee ContentStream, please contact your usual GS representative who will be able to provide this information to you.\n", "Backtesting, Simulated Results, Sensitivity/Scenario Analysis or Spreadsheet Calculator or Model: There may be data presented herein that is solely for illustrative purposes and which may include among other things back testing, simulated results and scenario analyses. The information is based upon certain factors, assumptions and historical information that Goldman Sachs may in its discretion have considered appropriate, however, Goldman Sachs provides no assurance or guarantee that this product will operate or would have operated in the past in a manner consistent with these assumptions. In the event any of the assumptions used do not prove to be true, results are likely to vary materially from the examples shown herein. Additionally, the results may not reflect material economic and market factors, such as liquidity, transaction costs and other expenses which could reduce potential return.\n", "OTC Derivatives Risk Disclosures: \n", "Terms of the Transaction: To understand clearly the terms and conditions of any OTC derivative transaction you may enter into, you should carefully review the Master Agreement, including any related schedules, credit support documents, addenda and exhibits. You should not enter into OTC derivative transactions unless you understand the terms of the transaction you are entering into as well as the nature and extent of your risk exposure. You should also be satisfied that the OTC derivative transaction is appropriate for you in light of your circumstances and financial condition. You may be requested to post margin or collateral to support written OTC derivatives at levels consistent with the internal policies of Goldman Sachs. \n", "\n", "Liquidity Risk: There is no public market for OTC derivative transactions and, therefore, it may be difficult or impossible to liquidate an existing position on favorable terms. Transfer Restrictions: OTC derivative transactions entered into with one or more affiliates of The Goldman Sachs Group, Inc. (Goldman Sachs) cannot be assigned or otherwise transferred without its prior written consent and, therefore, it may be impossible for you to transfer any OTC derivative transaction to a third party. \n", "\n", "Conflict of Interests: Goldman Sachs may from time to time be an active participant on both sides of the market for the underlying securities, commodities, futures, options or any other derivative or instrument identical or related to those mentioned herein (together, \"the Product\"). Goldman Sachs at any time may have long or short positions in, or buy and sell Products (on a principal basis or otherwise) identical or related to those mentioned herein. Goldman Sachs hedging and trading activities may affect the value of the Products. \n", "\n", "Counterparty Credit Risk: Because Goldman Sachs, may be obligated to make substantial payments to you as a condition of an OTC derivative transaction, you must evaluate the credit risk of doing business with Goldman Sachs or its affiliates. \n", "\n", "Pricing and Valuation: The price of each OTC derivative transaction is individually negotiated between Goldman Sachs and each counterparty and Goldman Sachs does not represent or warrant that the prices for which it offers OTC derivative transactions are the best prices available, possibly making it difficult for you to establish what is a fair price for a particular OTC derivative transaction; The value or quoted price of the Product at any time, however, will reflect many factors and cannot be predicted. If Goldman Sachs makes a market in the offered Product, the price quoted by Goldman Sachs would reflect any changes in market conditions and other relevant factors, and the quoted price (and the value of the Product that Goldman Sachs will use for account statements or otherwise) could be higher or lower than the original price, and may be higher or lower than the value of the Product as determined by reference to pricing models used by Goldman Sachs. If at any time a third party dealer quotes a price to purchase the Product or otherwise values the Product, that price may be significantly different (higher or lower) than any price quoted by Goldman Sachs. Furthermore, if you sell the Product, you will likely be charged a commission for secondary market transactions, or the price will likely reflect a dealer discount. Goldman Sachs may conduct market making activities in the Product. To the extent Goldman Sachs makes a market, any price quoted for the OTC derivative transactions, Goldman Sachs may differ significantly from (i) their value determined by reference to Goldman Sachs pricing models and (ii) any price quoted by a third party. The market price of the OTC derivative transaction may be influenced by many unpredictable factors, including economic conditions, the creditworthiness of Goldman Sachs, the value of any underlyers, and certain actions taken by Goldman Sachs. \n", "\n", "Market Making, Investing and Lending: Goldman Sachs engages in market making, investing and lending businesses for its own account and the accounts of its affiliates in the same or similar instruments underlying OTC derivative transactions (including such trading as Goldman Sachs deems appropriate in its sole discretion to hedge its market risk in any OTC derivative transaction whether between Goldman Sachs and you or with third parties) and such trading may affect the value of an OTC derivative transaction. \n", "\n", "Early Termination Payments: The provisions of an OTC Derivative Transaction may allow for early termination and, in such cases, either you or Goldman Sachs may be required to make a potentially significant termination payment depending upon whether the OTC Derivative Transaction is in-the-money to Goldman Sachs or you at the time of termination. Indexes: Goldman Sachs does not warrant, and takes no responsibility for, the structure, method of computation or publication of any currency exchange rates, interest rates, indexes of such rates, or credit, equity or other indexes, unless Goldman Sachs specifically advises you otherwise.\n", "Risk Disclosure Regarding futures, options, equity swaps, and other derivatives as well as non-investment-grade securities and ADRs: Please ensure that you have read and understood the current options, futures and security futures disclosure document before entering into any such transactions. Current United States listed options, futures and security futures disclosure documents are available from our sales representatives or at http://www.theocc.com/components/docs/riskstoc.pdf, http://www.goldmansachs.com/disclosures/risk-disclosure-for-futures.pdf and https://www.nfa.futures.org/investors/investor-resources/files/security-futures-disclosure.pdf, respectively. Certain transactions - including those involving futures, options, equity swaps, and other derivatives as well as non-investment-grade securities - give rise to substantial risk and are not available to nor suitable for all investors. If you have any questions about whether you are eligible to enter into these transactions with Goldman Sachs, please contact your sales representative. Foreign-currency-denominated securities are subject to fluctuations in exchange rates that could have an adverse effect on the value or price of, or income derived from, the investment. In addition, investors in securities such as ADRs, the values of which are influenced by foreign currencies, effectively assume currency risk.\n", "Options Risk Disclosures: Options may trade at a value other than that which may be inferred from the current levels of interest rates, dividends (if applicable) and the underlier due to other factors including, but not limited to, expectations of future levels of interest rates, future levels of dividends and the volatility of the underlier at any time prior to maturity. Note: Options involve risk and are not suitable for all investors. Please ensure that you have read and understood the current options disclosure document before entering into any standardized options transactions. United States listed options disclosure documents are available from our sales representatives or at http://theocc.com/publications/risks/riskstoc.pdf. A secondary market may not be available for all options. Transaction costs may be a significant factor in option strategies calling for multiple purchases and sales of options, such as spreads. When purchasing long options an investor may lose their entire investment and when selling uncovered options the risk is potentially unlimited. Supporting documentation for any comparisons, recommendations, statistics, technical data, or other similar information will be supplied upon request.\n", "This material is for the private information of the recipient only. This material is not sponsored, endorsed, sold or promoted by any sponsor or provider of an index referred herein (each, an \"Index Provider\"). GS does not have any affiliation with or control over the Index Providers or any control over the computation, composition or dissemination of the indices. While GS will obtain information from publicly available sources it believes reliable, it will not independently verify this information. Accordingly, GS shall have no liability, contingent or otherwise, to the user or to third parties, for the quality, accuracy, timeliness, continued availability or completeness of the data nor for any special, indirect, incidental or consequential damages which may be incurred or experienced because of the use of the data made available herein, even if GS has been advised of the possibility of such damages.\n", "iTraxx® is a registered trade mark of International Index Company Limited.\n", "iTraxx® is a trade mark of International Index Company Limited and has been licensed for the use by Goldman Sachs Japan Co., Ltd. International Index Company Limited does not approve, endorse or recommend Goldman Sachs Japan Co., Ltd. or iTraxx® derivatives products.\n", "iTraxx® derivatives products are derived from a source considered reliable, but neither International Index Company Limited nor any of its employees, suppliers, subcontractors and agents (together iTraxx Associates) guarantees the veracity, completeness or accuracy of iTraxx® derivatives products or other information furnished in connection with iTraxx® derivatives products. No representation, warranty or condition, express or implied, statutory or otherwise, as to condition, satisfactory quality, performance, or fitness for purpose are given or assumed by International Index Company Limited or any of the iTraxx Associates in respect of iTraxx® derivatives products or any data included in such iTraxx® derivatives products or the use by any person or entity of iTraxx® derivatives products or that data and all those representations, warranties and conditions are excluded save to the extent that such exclusion is prohibited by law.\n", "None of International Index Company Limited nor any of the iTraxx Associates shall have any liability or responsibility to any person or entity for any loss, damages, costs, charges, expenses or other liabilities whether caused by the negligence of International Index Company Limited or any of the iTraxx Associates or otherwise, arising in connection with the use of iTraxx® derivatives products or the iTraxx® indices.\n", "Standard & Poor's ® and S&P ® are registered trademarks of The McGraw-Hill Companies, Inc. and S&P GSCI™ is a trademark of The McGraw-Hill Companies, Inc. and have been licensed for use by the Issuer. This Product (the \"Product\") is not sponsored, endorsed, sold or promoted by S&P and S&P makes no representation, warranty or condition regarding the advisability of investing in the Product.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/events/00_gsquant_meets_markets/02_optimizing_equity_trading/qes_utils.py ================================================ import os import matplotlib.pyplot as plt import seaborn as sns import warnings import pandas as pd warnings.filterwarnings('ignore') def persistXls(xls_report, path, filename, merge_cells=False, indentifier_marqueeid_map=pd.DataFrame()): xls_path = os.path.join(path, filename + '.xlsx') writer = pd.ExcelWriter(xls_path, options={'remove_timezone': True}) for sheet, data in xls_report.items(): data = pd.DataFrame(data) if 'assetId' in data.columns and len(indentifier_marqueeid_map) > 0: data = data.rename(columns={'assetId': 'marqueeid'}).merge(indentifier_marqueeid_map, how='left', on='marqueeid') pd.DataFrame(data).to_excel(writer, sheet, merge_cells=merge_cells) writer.save() writer.close() return xls_path def plotGross(intraday, color_pal='Blues_r'): fig, ax = plt.subplots(figsize=(10, 4)) pal = sns.color_palette(color_pal) intraday['gross'].plot.area(ax=ax, color=pal, alpha=0.4) ax.set_xlabel('Time/GMT') ax.set_title('Gross Remaining') ax.set_ylabel('Dollar') fig.tight_layout() # Plot cost breakdown def plotCost(intraday): intraday_cost = intraday.copy() initial_gross = intraday_cost.iloc[0].at['gross'] intraday_cost = intraday_cost[1:] # convert to bps intraday_cost[['totalCostPermanent', 'totalCostSpread', 'totalCostVolatility']] /= initial_gross / 10000 intraday_cost[['totalCostPermanent', 'totalCostSpread', 'totalCostVolatility']] = intraday_cost[['totalCostPermanent', 'totalCostSpread', 'totalCostVolatility']].cumsum() fig, ax = plt.subplots(figsize=(10, 4)) pal = sns.color_palette('Blues_r') intraday_cost[['totalCostPermanent', 'totalCostSpread', 'totalCostVolatility']].plot.area(ax=ax, color=pal, alpha=0.4) ax.set_xlabel('Time/GMT') ax.set_title('Cost Contribution') ax.set_ylabel('Cost (bps)') fig.tight_layout() # Plot Variance breakdown def plotVar(intraday): initial_gross = intraday.iloc[0]['gross'] risk_list = ['Factor', 'Specific', 'Diagonal'] intraday = intraday.iloc[1:] for r in risk_list: intraday[r + 'Risk_bps^2'] = pow(10000 * intraday[r.lower() + 'Risk'] / initial_gross, 1) fig, ax = plt.subplots(figsize=(10, 4)) pal = sns.color_palette('cubehelix', 8) intraday[['FactorRisk_bps^2', 'SpecificRisk_bps^2', 'DiagonalRisk_bps^2']].plot.area(ax=ax, color=pal, alpha=0.4) ax.legend(['Factor', 'Specific', 'Diagonal'], loc='upper right') ax.set_xlabel('Time/GMT') ax.set_title('Variance Contribution') ax.set_ylabel('Variance (bps)') fig.tight_layout() # Plot Buy/Sell/Net def plotBuySellNet(intraday): fig, ax = plt.subplots(figsize=(10, 4)) (100 * intraday['buy'] / intraday['gross'].iloc[0]).plot(ax=ax, color=(0 / 235, 121 / 235, 114 / 235)) (100 * intraday['sell'] / intraday['gross'].iloc[0]).plot(ax=ax, color=(142 / 235, 215 / 235, 205 / 235)) (100 * intraday['net'] / intraday['gross'].iloc[0]).plot(ax=ax, color=(32 / 235, 189 / 235, 168 / 235)) ax.axhline(linestyle='--') ax.set_xlabel('Time/GMT') ax.set_title('Buy/Sell/Net(%)') ax.set_ylabel('Dollar') fig.tight_layout() def plotGrossRemaining(intraday): fig, ax = plt.subplots(figsize=(10, 4)) (100 * intraday['gross'] / intraday['gross'].iloc[0]).plot(ax=ax, color="DarkBlue") ax.set_xlabel('Time/GMT') ax.set_title('Gross(%,LHS) and Participation Rate (%,RHS)') ax.set_ylabel('%') ax2 = ax.twinx() intraday['advAveragePercentage'].rolling('220min').mean().plot(ax=ax2, color='grey', style='--') fig.tight_layout() # function defined to plot three metrics every run def plotMultiStrategyPortfolioLevelAnalytics(results_dict_multi, metrics_list, title, ylabel, color_pal='Blues_r', urgency_list=['VERY_LOW', 'LOW', 'MEDIUM', 'HIGH', 'VERY_HIGH']): agg_result = [pd.DataFrame(results_dict_multi[u] ['analytics']['portfolioAnalyticsIntraday'])[metrics_list + ['time', 'gross', 'tradeDayNumber']] .assign(urgency=u).assign(time=lambda x: pd.to_datetime(x['time'])) .set_index('time').pipe(lambda df: df[df['tradeDayNumber'] == 1]) for u in urgency_list] initial_gross = agg_result[0].iloc[0]['gross'] fig, [ax1, ax2, ax3, ax4] = plt.subplots(4, 1, sharex=True, figsize=(10, 12)) for m, ax, t, y in zip(metrics_list, [ax1, ax2, ax3, ax4], title, ylabel): df_plot = pd.concat(agg_result).reset_index().set_index(['time', 'urgency'])[m].unstack().reindex(columns=urgency_list) df_plot = df_plot[1:] if m == 'totalCost': df_plot /= initial_gross / 10000 df_plot = df_plot.cumsum() elif m == 'advAveragePercentage': df_plot = df_plot.rolling('120min').mean() df_plot.plot(ax=ax, alpha=0.5) ax.legend(urgency_list, loc='right') ax.set_xlabel('Time/GMT') ax.set_title(t) ax.set_ylabel(y) fig.tight_layout() ================================================ FILE: gs_quant/content/events/00_gsquant_meets_markets/02_optimizing_equity_trading/quants_meet_markets_qes.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# GS Quant Meets Markets: Behind the Scenes of Goldman Sachs' Equity Trading Algos, By QES\n", "- For any questions, feedback and assistance, please do not hesitate to email us at [gs-marquee-qes](mailto:gs-marquee-qes)\n", "- If you want to explore further, please visit our Quantitative Execution Services hub of research, analytics, datasets and more at the [quantitative-execution-services](https://marquee.gs.com/content/markets/products/quantitative-execution-services.html) site\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# The Quantitative Execution Services group....Who Are We ?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Structure of the guide:\n", "- The guide aims to showcase a suite of datasets, computations and optimizations that GS quants typically harness when analyzing equities baskets.\n", "- The range of suite of analysis shown in this notebook are being used by GS internal quants, GS trading algos and range of clients globally.\n", "\n", "In an attempt to provide a realistic set of use-cases which utilize the offerings, we consider a Long\\Short Momentum basket rebalance, which a client intends to trade in the market.\n", "\n", "Throughout the guide, we'll attempt to analyze the basket and the market, via the following prisms:\n", "\n", "**[Section A](#Section_A): Liquidity through the QES-Marquee prism** \n", "
We'll utilize the GS Quant API to explore the liquidity landscape across indices, and provide further analysis across expected impact, through the lens of trade difficulty clusters. Find below API docs, corresponding to the section:\n", "- [API Docs: QES Single Stock Volume Forecast](https://marquee.web.gs.com/s/developer/datasets/SINGLE_STOCK_VOLUME_FORECAST_BASE)\n", "- [API Docs: QES Single Stock Volume Profiles](https://marquee.web.gs.com/s/developer/datasets/SINGLE_STOCK_VOLUME_PROFILE_BASE)\n", "- [API Docs: GS Single Stock Market Impact Estimates](https://marquee.web.gs.com/s/developer/datasets/SINGLE_STOCK_MARKET_IMPACT) and [Global Trading Cost Analysis - March 2021 example](https://marquee.gs.com/content/markets/en/2021/03/18/e750df5a-14bf-4c8a-8d57-298757450512.html)\n", "- [GS Equity Trade Clusters](https://marquee.web.gs.com/s/developer/datasets/EQTRADECLUSTERS) and [QES EDGE Vol. 1 | Microstructure Clustering & Trading Implications Paper](https://marquee.gs.com/content/markets/en/2020/10/28/dc0f4cf7-e6b7-4de0-97c6-76d19f7b8a25.html)\n", "\n", "More about clustering in section A. Here's a high level visual that demonstrates the concept:\n", "\n", "\n", "\n", "**[Section B](#Section_B): ETF Passive Ownership Analysis** \n", "
Evaluation of the Passive ownership by ETF holders for our example basket\n", "- [API Docs: Ownership Scores (Weekly Flow)](https://marquee.gs.com/s/developer/datasets/OWNERSHIP_SCORES_WEEKLY_FLOW)\n", "- [API Docs: Ownership Scores (Monthly)](https://marquee.gs.com/s/developer/datasets/OWNERSHIP_SCORES_MONTHLY)\n", "- [QES EDGE Vol. 2 | Effect of Shift from Active to Passive](https://marquee.gs.com/content/markets/en/2020/10/28/734dc59c-b03b-4793-9f56-98ec5ebca4d8.html)\n", "\n", "**[Section C](#Section_C): Algorithmic Portfolio EXecution - APEX**\n", "
We'll attempt to explore the basket unwind cost, using our APEX muti-period optimizer across a range of urgency\\risk aversion parameters\n", "- [APEX - The New Frontier in Portfolio Execution - Overview](https://marquee.gs.com/content/markets/en/2021/02/16/41e547e5-24ea-4ecb-9536-6de19884a8c4.html)\n", "- [APEX-in-Marquee UI](https://marquee.web.gs.com/s/portfolios/create/MPGRZ9CYS372EZD1)\n", "- [API Docs: APEX-in-Marquee API Docs]()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### Disclaimers:\n", "###### Indicative Terms/Pricing Levels: This material may contain indicative terms only, including but not limited to pricing levels. There is no representation that any transaction can or could have been effected at such terms or prices. Proposed terms and conditions are for discussion purposes only. Finalized terms and conditions are subject to further discussion and negotiation.\n", "###### www.goldmansachs.com/disclaimer/sales-and-trading-invest-rec-disclosures.html If you are not accessing this material via Marquee ContentStream, a list of the author's investment recommendations disseminated during the preceding 12 months and the proportion of the author's recommendations that are 'buy', 'hold', 'sell' or other over the previous 12 months is available by logging into Marquee ContentStream using the link below. Alternatively, if you do not have access to Marquee ContentStream, please contact your usual GS representative who will be able to provide this information to you.\n", "###### Please refer to https://marquee.gs.com/studio/ for price information of corporate equity securities.\n", "###### Notice to Australian Investors: When this document is disseminated in Australia by Goldman Sachs & Co. LLC (\"GSCO\"), Goldman Sachs International (\"GSI\"), Goldman Sachs Bank Europe SE (\"GSBE\"), Goldman Sachs (Asia) L.L.C. (\"GSALLC\"), or Goldman Sachs (Singapore) Pte (\"GSSP\") (collectively the \"GS entities\"), this document, and any access to it, is intended only for a person that has first satisfied the GS entities that: \n", "###### • the person is a Sophisticated or Professional Investor for the purposes of section 708 of the Corporations Act of Australia; and \n", "###### • the person is a wholesale client for the purpose of section 761G of the Corporations Act of Australia. \n", "###### To the extent that the GS entities are providing a financial service in Australia, the GS entities are each exempt from the requirement to hold an Australian financial services licence for the financial services they provide in Australia. Each of the GS entities are regulated by a foreign regulator under foreign laws which differ from Australian laws, specifically: \n", "###### • GSCO is regulated by the US Securities and Exchange Commission under US laws;\n", "###### • GSI is authorised by the Prudential Regulation Authority and regulated by the Financial Conduct Authority and the Prudential Regulation Authority, under UK laws;\n", "###### • GSBE is subject to direct prudential supervision by the European Central Bank and in other respects is supervised by the German Federal Financial Supervisory Authority (Bundesanstalt für Finanzdienstleistungsaufischt, BaFin) and Deutsche Bundesbank;\n", "###### • GSALLC is regulated by the Hong Kong Securities and Futures Commission under Hong Kong laws; and\n", "###### • GSSP is regulated by the Monetary Authority of Singapore under Singapore laws.\n", "###### Notice to Brazilian Investors\n", "###### Marquee is not meant for the general public in Brazil. The services or products provided by or through Marquee, at any time, may not be offered or sold to the general public in Brazil. You have received a password granting access to Marquee exclusively due to your existing relationship with a GS business located in Brazil. The selection and engagement with any of the offered services or products through Marquee, at any time, will be carried out directly by you. Before acting to implement any chosen service or products, provided by or through Marquee you should consider, at your sole discretion, whether it is suitable for your particular circumstances and, if necessary, seek professional advice. Any steps necessary in order to implement the chosen service or product, including but not limited to remittance of funds, shall be carried out at your discretion. Accordingly, such services and products have not been and will not be publicly issued, placed, distributed, offered or negotiated in the Brazilian capital markets and, as a result, they have not been and will not be registered with the Brazilian Securities and Exchange Commission (Comissão de Valores Mobiliários), nor have they been submitted to the foregoing agency for approval. Documents relating to such services or products, as well as the information contained therein, may not be supplied to the general public in Brazil, as the offering of such services or products is not a public offering in Brazil, nor used in connection with any offer for subscription or sale of securities to the general public in Brazil.\n", "###### The offer of any securities mentioned in this message may not be made to the general public in Brazil. Accordingly, any such securities have not been nor will they be registered with the Brazilian Securities and Exchange Commission (Comissão de Valores Mobiliários) nor has any offer been submitted to the foregoing agency for approval. Documents relating to the offer, as well as the information contained therein, may not be supplied to the public in Brazil, as the offer is not a public offering of securities in Brazil. These terms will apply on every access to Marquee.\n", "###### Ouvidoria Goldman Sachs Brasil: 0800 727 5764 e/ou ouvidoriagoldmansachs@gs.com\n", "###### Horário de funcionamento: segunda-feira à sexta-feira (exceto feriados), das 9hs às 18hs.\n", "###### Ombudsman Goldman Sachs Brazil: 0800 727 5764 and / or ouvidoriagoldmansachs@gs.com\n", "###### Available Weekdays (except holidays), from 9 am to 6 pm.\n", " \n", "###### Note to Investors in Israel: GS is not licensed to provide investment advice or investment management services under Israeli law.\n", "###### Notice to Investors in Japan\n", "###### Marquee is made available in Japan by Goldman Sachs Japan Co., Ltd.\n", "\n", "###### 本書は情報の提供を目的としております。また、売却・購入が違法となるような法域での有価証券その他の売却若しくは購入を勧めるものでもありません。ゴールドマン・サックスは本書内の取引又はストラクチャーの勧誘を行うものではございません。これらの取引又はストラクチャーは、社内及び法規制等の承認等次第で実際にはご提供できない場合がございます。\n", "\n", "###### <適格機関投資家限定 転売制限>\n", "###### ゴールドマン・サックス証券株式会社が適格機関投資家のみを相手方として取得申込みの勧誘(取得勧誘)又は売付けの申込み若しくは買付けの申込みの勧誘(売付け勧誘等)を行う本有価証券には、適格機関投資家に譲渡する場合以外の譲渡が禁止される旨の制限が付されています。本有価証券は金融商品取引法第4条に基づく財務局に対する届出が行われておりません。なお、本告知はお客様によるご同意のもとに、電磁的に交付させていただいております。\n", "###### <適格機関投資家用資料> \n", "###### 本資料は、適格機関投資家のお客さまのみを対象に作成されたものです。本資料における金融商品は適格機関投資家のお客さまのみがお取引可能であり、適格機関投資家以外のお客さまからのご注文等はお受けできませんので、ご注意ください。 商号等/ゴールドマン・サックス証券株式会社 金融商品取引業者 関東財務局長(金商)第69号 \n", "###### 加入協会/ 日本証券業協会、一般社団法人金融先物取引業協会、一般社団法人第二種金融商品取引業協会 \n", "###### 本書又はその添付資料に信用格付が記載されている場合、日本格付研究所(JCR)及び格付投資情報センター(R&I)による格付は、登録信用格付業者による格付(登録格付)です。その他の格付は登録格付である旨の記載がない場合は、無登録格付です。無登録格付を投資判断に利用する前に、「無登録格付に関する説明書」(http://www.goldmansachs.com/disclaimer/ratings.html)を十分にお読みください。 \n", "###### If any credit ratings are contained in this material or any attachments, those that have been issued by Japan Credit Rating Agency, Ltd. (JCR) or Rating and Investment Information, Inc. (R&I) are credit ratings that have been issued by a credit rating agency registered in Japan (registered credit ratings). Other credit ratings are unregistered unless denoted as being registered. Before using unregistered credit ratings to make investment decisions, please carefully read \"Explanation Regarding Unregistered Credit Ratings\" (http://www.goldmansachs.com/disclaimer/ratings.html).\n", "###### Notice to Mexican Investors: Information contained herein is not meant for the general public in Mexico. The services or products provided by or through Goldman Sachs Mexico, Casa de Bolsa, S.A. de C.V. (GS Mexico) may not be offered or sold to the general public in Mexico. You have received information herein exclusively due to your existing relationship with a GS Mexico or any other Goldman Sachs business. The selection and engagement with any of the offered services or products through GS Mexico will be carried out directly by you at your own risk. Before acting to implement any chosen service or product provided by or through GS Mexico you should consider, at your sole discretion, whether it is suitable for your particular circumstances and, if necessary, seek professional advice. Information contained herein related to GS Mexico services or products, as well as any other information, shall not be considered as a product coming from research, nor it contains any recommendation to invest, not to invest, hold or sell any security and may not be supplied to the general public in Mexico.\n", "###### Notice to New Zealand Investors: When this document is disseminated in New Zealand by Goldman Sachs & Co. LLC (\"GSCO\") , Goldman Sachs International (\"GSI\"), Goldman Sachs Bank Europe SE (\"GSBE\"), Goldman Sachs (Asia) L.L.C. (\"GSALLC\") or Goldman Sachs (Singapore) Pte (\"GSSP\") (collectively the \"GS entities\"), this document, and any access to it, is intended only for a person that has first satisfied; the GS entities that the person is someone: \n", "###### (i) who is an investment business within the meaning of clause 37 of Schedule 1 of the Financial Markets Conduct Act 2013 (New Zealand) (the \"FMC Act\");\n", "###### (ii) who meets the investment activity criteria specified in clause 38 of Schedule 1 of the FMC Act;\n", "###### (iii) who is large within the meaning of clause 39 of Schedule 1 of the FMC Act; or\n", "###### (iv) is a government agency within the meaning of clause 40 of Schedule 1 of the FMC Act. \n", "###### No offer to acquire the interests is being made to you in this document. Any offer will only be made in circumstances where disclosure is not required under the Financial Markets Conducts Act 2013 or the Financial Markets Conduct Regulations 2014.\n", "###### Notice to Swiss Investors: This is marketing material for financial instruments or services. The information contained in this material is for general informational purposes only and does not constitute an offer, solicitation, invitation or recommendation to buy or sell any financial instruments or to provide any investment advice or service of any kind.\n", "###### THE INFORMATION CONTAINED IN THIS DOCUMENT DOES NOT CONSITUTE, AND IS NOT INTENDED TO CONSTITUTE, A PUBLIC OFFER OF SECURITIES IN THE UNITED ARAB EMIRATES IN ACCORDANCE WITH THE COMMERCIAL COMPANIES LAW (FEDERAL LAW NO. 2 OF 2015), ESCA BOARD OF DIRECTORS' DECISION NO. (9/R.M.) OF 2016, ESCA CHAIRMAN DECISION NO 3/R.M. OF 2017 CONCERNING PROMOTING AND INTRODUCING REGULATIONS OR OTHERWISE UNDER THE LAWS OF THE UNITED ARAB EMIRATES. ACCORDINGLY, THE INTERESTS IN THE SECURITIES MAY NOT BE OFFERED TO THE PUBLIC IN THE UAE (INCLUDING THE DUBAI INTERNATIONAL FINANCIAL CENTRE AND THE ABU DHABI GLOBAL MARKET). THIS DOCUMENT HAS NOT BEEN APPROVED BY, OR FILED WITH THE CENTRAL BANK OF THE UNITED ARAB EMIRATES, THE SECURITIES AND COMMODITIES AUTHORITY, THE DUBAI FINANCIAL SERVICES AUTHORITY, THE FINANCIAL SERVICES REGULATORY AUTHORITY OR ANY OTHER RELEVANT LICENSING AUTHORITIES IN THE UNITED ARAB EMIRATES. IF YOU DO NOT UNDERSTAND THE CONTENTS OF THIS DOCUMENT, YOU SHOULD CONSULT WITH A FINANCIAL ADVISOR. THIS DOCUMENT IS PROVIDED TO THE RECIPIENT ONLY AND SHOULD NOT BE PROVIDED TO OR RELIED ON BY ANY OTHER PERSON." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/events/00_gsquant_meets_markets/02_optimizing_equity_trading/section_a_liquidity_and_clusters.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Liquidity through the QES-Marquee prism\n", "- Liquidity refers to the ease of trading a particular asset, usually defined by the avoidance of market impact while trading\n", "- There are several ways of understanding liquidity, the most natural looking at the amount of volume that can be accessed while trading\n", "- Better prediction of the amount of volume available for trading, helps avoid opportunity cost as well as excessive market impact\n", "- One can analyse stocks by also exploring our market impact estimates as well as our trade clusters, that try to characterise stocks with similar microstructure dynamics\n", "\n", "\n", "Source: GS Global Markets Division 2021 " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Import Libraries and Utilities" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.securities import SecurityMaster, AssetIdentifier, AssetType\n", "from gs_quant.session import GsSession, Environment\n", "from gs_quant.data import Dataset\n", "\n", "import datetime\n", "import matplotlib.pyplot as plt\n", "import pandas as pd\n", "import numpy as np\n", "import copy\n", "import seaborn as sns\n", "plt.rcParams['figure.figsize'] = (16,16 / 1.62)\n", "plt.rcParams.update({'font.size': 20})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Supply Credentials" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=client_id, client_secret=client_secret, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "date = datetime.date(2021, 6, 9)\n", "palette = ['#939393', '#7399c6', '#314c74','#434343']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Understanding the liquidity of the market" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### How can I get the market-wide Volume Profiles for an index (e.g. FTSE 100, CAC 40, DAX 30, ATX 20) built from its constituents?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def grabIndexData(index, dateDT, datasetId):\n", " ind = SecurityMaster.get_asset(index, AssetIdentifier.BLOOMBERG_ID, asset_type=AssetType.INDEX)\n", " tradedStocks = list(filter(None, ind.get_entity()['underlying_asset_ids'])) \n", " dt = Dataset(datasetId).get_data(dateDT, dateDT, assetId=tradedStocks, limit=50000)\n", " dt['index'] = index\n", " return dt" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "indicesEuro = ['UKX', 'DAX', 'CAC', 'ATX']\n", "dataId = 'SINGLE_STOCK_VOLUME_PROFILE_BASE'\n", "fields = ['cumulativeVolumeInPercentage', 'bucketStartTime', 'assetId']\n", "volumeProfileIndexEuro = pd.concat([grabIndexData(index, date, dataId)[fields] for index in indicesEuro], axis=0)\n", "volumeProfileIndexEuro.head()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "volumeProfileIndexEuroMean = volumeProfileIndexEuro.loc[str(date)].groupby(by=['index', 'bucketStartTime']).mean()\n", "volumeProfileIndexEuroMean['index'] = volumeProfileIndexEuroMean.index.get_level_values(0)\n", "volumeProfileIndexEuroMean['bucketStartTime'] = volumeProfileIndexEuroMean.index.get_level_values(1).time.astype('str')\n", "\n", "plt.figure(figsize=(15, 10))\n", "figs = sns.lineplot(x='bucketStartTime', y='cumulativeVolumeInPercentage', hue='index', palette=palette,\n", " data=volumeProfileIndexEuroMean)\n", "for ind, label in enumerate(figs.get_xticklabels()):\n", " label.set_visible(True if ind % 10 == 0 else False)\n", "\n", "xlabel = plt.xlabel('bucketStartTime (GMT Time)', fontsize = 18)\n", "x = plt.xticks(size=18, rotation=90)\n", "y = plt.yticks(size=18)\n", "figs.xaxis.set_label_coords(0.5, -0.2)\n", "title = plt.title('Average Start of Day Volume Profiles for Select Indices (09/06/2021)', fontsize=20)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Intraday, do I get a real-time updated forecast? Does it make any difference?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "Source: GS Global Markets Division, Reuters, 01Jan19-31Dec19\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### How is it constructed?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "Source: GS Global Markets Division 2021" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "indicesEuro = ['UKX', 'DAX', 'CAC', 'ATX']\n", "dataId = 'SINGLE_STOCK_VOLUME_FORECAST_BASE'\n", "dateTime=datetime.datetime(2021, 6, 9, 11, 0, 0)\n", "volumeUpdateIndexEuro = pd.concat([grabIndexData(index, dateTime, dataId) for index in indicesEuro], axis=0)\n", "volumeUpdateIndexEuro.head()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "volumeUpdateIndexEuro.groupby('index')['volumeForecastAdjustment'].describe()[['mean', 'std', 'min', '25%', '50%', '75%']]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## What if I am interested in a single stock?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ds = Dataset('SINGLE_STOCK_VOLUME_FORECAST_BASE')\n", "volumeUpdateAsset = ds.get_data(dateTime, dateTime, bbid='VOD LN', limit=5000)\n", "volumeUpdateAsset" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dsp = Dataset('SINGLE_STOCK_VOLUME_PROFILE_BASE')\n", "volumeProfileAsset= dsp.get_data(date, date, bbid='VOD LN', limit=5000)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "volumeProfileStatic = volumeProfileAsset[['cumulativeVolumeInShares', 'bucketVolumeInShares', 'bucketStartTime', 'bbid']].loc[str(date_].groupby(by=['bbid', 'bucketStartTime']).mean()\n", "volumeProfileStatic['VolumeInSharesUpdated'] = volumeProfileStatic['bucketVolumeInShares']*volumeUpdateAsset['volumeForecastAdjustment'][0]\n", "volumeProfileForecast = volumeProfileStatic[ volumeProfileStatic.index.get_level_values(1).time>=datetime.time(11, 0, 0)]\n", "volumeProfileForecast = volumeProfileForecast[['bucketVolumeInShares', 'VolumeInSharesUpdated']].cumsum().reset_index()\n", "volumeProfileForecast['bucketStartTime'] = volumeProfileForecast['bucketStartTime'].dt.time.astype('str')\n", "plt.figure(figsize=(15, 10))\n", "\n", "figs = sns.lineplot(x='bucketStartTime', y='bucketVolumeInShares', data=volumeProfileForecast,label='Static Prediction', color=palette[1])\n", "figs = sns.lineplot(x='bucketStartTime', y='VolumeInSharesUpdated', data=volumeProfileForecast,label='Dynamic Prediction', color=palette[2])\n", "figs.lines[1].set_linestyle('--')\n", "\n", "for ind, label in enumerate(figs.get_xticklabels()):\n", " label.set_visible(True if ind % 10 == 0 else False)\n", "xlabel = plt.xlabel('bucketStartTime (GMT Time)')\n", "plt.legend()\n", "\n", "x = plt.xticks(size=18, rotation=90)\n", "y = plt.yticks(size=18)\n", "coords = figs.xaxis.set_label_coords(0.5, -0.2)\n", "title = plt.title('Static and Dynamic Volume Profile Prediction (VOD LN 09/06/2021)', fontsize = 20)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Market Impact and Microstructure Stock clusters" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "Source: GS Global Markets Division 2021" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Query Market Impact Estimates for STOXX Total Market Index" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def fn_marketImpactEstimates(index, date):\n", " #List of stocks traded in a particular index\n", " ind = SecurityMaster.get_asset(index, AssetIdentifier.BLOOMBERG_ID, asset_type=AssetType.INDEX)\n", " tradedStocks = list(filter(None, ind.get_entity()['underlying_asset_ids']))\n", " ds = Dataset('SINGLE_STOCK_MARKET_IMPACT')\n", " return ds.get_data(date, date, assetId=tradedStocks, percentADV=[1,5,10], limit=50000)\n", "\n", "marketImpactEstimates = fn_marketImpactEstimates('BKXP', date)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Query Trade Clusters for STOXX Total Market Index" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df_clusters = grabIndexData('BKXP', date, 'EQTRADECLUSTERS')\n", "df_clusters" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "data_01pov = marketImpactEstimates[(marketImpactEstimates['percentADV']==1) & (marketImpactEstimates['participationRate']==1)]\n", "data_05pov = marketImpactEstimates[(marketImpactEstimates['percentADV']==5) & (marketImpactEstimates['participationRate']==5)]\n", "data_10pov = marketImpactEstimates[(marketImpactEstimates['percentADV']==10) & (marketImpactEstimates['participationRate']==10)]\n", "data_marketImpact = pd.concat([data_01pov, data_05pov, data_10pov])\n", "data_marketImpact_clusters = pd.merge(data_marketImpact, df_clusters[['assetId', 'clusterClass']], how='left', on=['assetId'])\n", "data_marketImpact_clusters['clusterClass'] = data_marketImpact_clusters['clusterClass'].astype('int')\n", "data_marketImpact_clusters.sort_values('clusterClass', inplace=True)\n", "data_marketImpact_clusters" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "bp = sns.boxplot(y='estimatedImpact', x='clusterClass', data=data_marketImpact_clusters,\n", " palette=palette, hue='percentADV')\n", "\n", "handles, labels = bp.get_legend_handles_labels()\n", "plt.xticks(fontsize=15)\n", "plt.yticks(fontsize=15)\n", "figs = plt.legend(handles[0:3], ['1%', '5%' , '10%'], title='POV', fontsize=15, title_fontsize=18)\n", "plt.title('Estimated Impact by Cluster for different POV rates', fontsize=20)\n", "l = plt.ylim([0, 40])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### Disclaimers:\n", "###### Indicative Terms/Pricing Levels: This material may contain indicative terms only, including but not limited to pricing levels. There is no representation that any transaction can or could have been effected at such terms or prices. Proposed terms and conditions are for discussion purposes only. Finalized terms and conditions are subject to further discussion and negotiation.\n", "###### www.goldmansachs.com/disclaimer/sales-and-trading-invest-rec-disclosures.html If you are not accessing this material via Marquee ContentStream, a list of the author's investment recommendations disseminated during the preceding 12 months and the proportion of the author's recommendations that are 'buy', 'hold', 'sell' or other over the previous 12 months is available by logging into Marquee ContentStream using the link below. Alternatively, if you do not have access to Marquee ContentStream, please contact your usual GS representative who will be able to provide this information to you.\n", "###### Please refer to https://marquee.gs.com/studio/ for price information of corporate equity securities.\n", "###### Notice to Australian Investors: When this document is disseminated in Australia by Goldman Sachs & Co. LLC (\"GSCO\"), Goldman Sachs International (\"GSI\"), Goldman Sachs Bank Europe SE (\"GSBE\"), Goldman Sachs (Asia) L.L.C. (\"GSALLC\"), or Goldman Sachs (Singapore) Pte (\"GSSP\") (collectively the \"GS entities\"), this document, and any access to it, is intended only for a person that has first satisfied the GS entities that: \n", "###### • the person is a Sophisticated or Professional Investor for the purposes of section 708 of the Corporations Act of Australia; and \n", "###### • the person is a wholesale client for the purpose of section 761G of the Corporations Act of Australia. \n", "###### To the extent that the GS entities are providing a financial service in Australia, the GS entities are each exempt from the requirement to hold an Australian financial services licence for the financial services they provide in Australia. Each of the GS entities are regulated by a foreign regulator under foreign laws which differ from Australian laws, specifically: \n", "###### • GSCO is regulated by the US Securities and Exchange Commission under US laws;\n", "###### • GSI is authorised by the Prudential Regulation Authority and regulated by the Financial Conduct Authority and the Prudential Regulation Authority, under UK laws;\n", "###### • GSBE is subject to direct prudential supervision by the European Central Bank and in other respects is supervised by the German Federal Financial Supervisory Authority (Bundesanstalt für Finanzdienstleistungsaufischt, BaFin) and Deutsche Bundesbank;\n", "###### • GSALLC is regulated by the Hong Kong Securities and Futures Commission under Hong Kong laws; and\n", "###### • GSSP is regulated by the Monetary Authority of Singapore under Singapore laws.\n", "###### Notice to Brazilian Investors\n", "###### Marquee is not meant for the general public in Brazil. The services or products provided by or through Marquee, at any time, may not be offered or sold to the general public in Brazil. You have received a password granting access to Marquee exclusively due to your existing relationship with a GS business located in Brazil. The selection and engagement with any of the offered services or products through Marquee, at any time, will be carried out directly by you. Before acting to implement any chosen service or products, provided by or through Marquee you should consider, at your sole discretion, whether it is suitable for your particular circumstances and, if necessary, seek professional advice. Any steps necessary in order to implement the chosen service or product, including but not limited to remittance of funds, shall be carried out at your discretion. Accordingly, such services and products have not been and will not be publicly issued, placed, distributed, offered or negotiated in the Brazilian capital markets and, as a result, they have not been and will not be registered with the Brazilian Securities and Exchange Commission (Comissão de Valores Mobiliários), nor have they been submitted to the foregoing agency for approval. Documents relating to such services or products, as well as the information contained therein, may not be supplied to the general public in Brazil, as the offering of such services or products is not a public offering in Brazil, nor used in connection with any offer for subscription or sale of securities to the general public in Brazil.\n", "###### The offer of any securities mentioned in this message may not be made to the general public in Brazil. Accordingly, any such securities have not been nor will they be registered with the Brazilian Securities and Exchange Commission (Comissão de Valores Mobiliários) nor has any offer been submitted to the foregoing agency for approval. Documents relating to the offer, as well as the information contained therein, may not be supplied to the public in Brazil, as the offer is not a public offering of securities in Brazil. These terms will apply on every access to Marquee.\n", "###### Ouvidoria Goldman Sachs Brasil: 0800 727 5764 e/ou ouvidoriagoldmansachs@gs.com\n", "###### Horário de funcionamento: segunda-feira à sexta-feira (exceto feriados), das 9hs às 18hs.\n", "###### Ombudsman Goldman Sachs Brazil: 0800 727 5764 and / or ouvidoriagoldmansachs@gs.com\n", "###### Available Weekdays (except holidays), from 9 am to 6 pm.\n", " \n", "###### Note to Investors in Israel: GS is not licensed to provide investment advice or investment management services under Israeli law.\n", "###### Notice to Investors in Japan\n", "###### Marquee is made available in Japan by Goldman Sachs Japan Co., Ltd.\n", "\n", "###### 本書は情報の提供を目的としております。また、売却・購入が違法となるような法域での有価証券その他の売却若しくは購入を勧めるものでもありません。ゴールドマン・サックスは本書内の取引又はストラクチャーの勧誘を行うものではございません。これらの取引又はストラクチャーは、社内及び法規制等の承認等次第で実際にはご提供できない場合がございます。\n", "\n", "###### <適格機関投資家限定 転売制限>\n", "###### ゴールドマン・サックス証券株式会社が適格機関投資家のみを相手方として取得申込みの勧誘(取得勧誘)又は売付けの申込み若しくは買付けの申込みの勧誘(売付け勧誘等)を行う本有価証券には、適格機関投資家に譲渡する場合以外の譲渡が禁止される旨の制限が付されています。本有価証券は金融商品取引法第4条に基づく財務局に対する届出が行われておりません。なお、本告知はお客様によるご同意のもとに、電磁的に交付させていただいております。\n", "###### <適格機関投資家用資料> \n", "###### 本資料は、適格機関投資家のお客さまのみを対象に作成されたものです。本資料における金融商品は適格機関投資家のお客さまのみがお取引可能であり、適格機関投資家以外のお客さまからのご注文等はお受けできませんので、ご注意ください。 商号等/ゴールドマン・サックス証券株式会社 金融商品取引業者 関東財務局長(金商)第69号 \n", "###### 加入協会/ 日本証券業協会、一般社団法人金融先物取引業協会、一般社団法人第二種金融商品取引業協会 \n", "###### 本書又はその添付資料に信用格付が記載されている場合、日本格付研究所(JCR)及び格付投資情報センター(R&I)による格付は、登録信用格付業者による格付(登録格付)です。その他の格付は登録格付である旨の記載がない場合は、無登録格付です。無登録格付を投資判断に利用する前に、「無登録格付に関する説明書」(http://www.goldmansachs.com/disclaimer/ratings.html)を十分にお読みください。 \n", "###### If any credit ratings are contained in this material or any attachments, those that have been issued by Japan Credit Rating Agency, Ltd. (JCR) or Rating and Investment Information, Inc. (R&I) are credit ratings that have been issued by a credit rating agency registered in Japan (registered credit ratings). Other credit ratings are unregistered unless denoted as being registered. Before using unregistered credit ratings to make investment decisions, please carefully read \"Explanation Regarding Unregistered Credit Ratings\" (http://www.goldmansachs.com/disclaimer/ratings.html).\n", "###### Notice to Mexican Investors: Information contained herein is not meant for the general public in Mexico. The services or products provided by or through Goldman Sachs Mexico, Casa de Bolsa, S.A. de C.V. (GS Mexico) may not be offered or sold to the general public in Mexico. You have received information herein exclusively due to your existing relationship with a GS Mexico or any other Goldman Sachs business. The selection and engagement with any of the offered services or products through GS Mexico will be carried out directly by you at your own risk. Before acting to implement any chosen service or product provided by or through GS Mexico you should consider, at your sole discretion, whether it is suitable for your particular circumstances and, if necessary, seek professional advice. Information contained herein related to GS Mexico services or products, as well as any other information, shall not be considered as a product coming from research, nor it contains any recommendation to invest, not to invest, hold or sell any security and may not be supplied to the general public in Mexico.\n", "###### Notice to New Zealand Investors: When this document is disseminated in New Zealand by Goldman Sachs & Co. LLC (\"GSCO\") , Goldman Sachs International (\"GSI\"), Goldman Sachs Bank Europe SE (\"GSBE\"), Goldman Sachs (Asia) L.L.C. (\"GSALLC\") or Goldman Sachs (Singapore) Pte (\"GSSP\") (collectively the \"GS entities\"), this document, and any access to it, is intended only for a person that has first satisfied; the GS entities that the person is someone: \n", "###### (i) who is an investment business within the meaning of clause 37 of Schedule 1 of the Financial Markets Conduct Act 2013 (New Zealand) (the \"FMC Act\");\n", "###### (ii) who meets the investment activity criteria specified in clause 38 of Schedule 1 of the FMC Act;\n", "###### (iii) who is large within the meaning of clause 39 of Schedule 1 of the FMC Act; or\n", "###### (iv) is a government agency within the meaning of clause 40 of Schedule 1 of the FMC Act. \n", "###### No offer to acquire the interests is being made to you in this document. Any offer will only be made in circumstances where disclosure is not required under the Financial Markets Conducts Act 2013 or the Financial Markets Conduct Regulations 2014.\n", "###### Notice to Swiss Investors: This is marketing material for financial instruments or services. The information contained in this material is for general informational purposes only and does not constitute an offer, solicitation, invitation or recommendation to buy or sell any financial instruments or to provide any investment advice or service of any kind.\n", "###### THE INFORMATION CONTAINED IN THIS DOCUMENT DOES NOT CONSITUTE, AND IS NOT INTENDED TO CONSTITUTE, A PUBLIC OFFER OF SECURITIES IN THE UNITED ARAB EMIRATES IN ACCORDANCE WITH THE COMMERCIAL COMPANIES LAW (FEDERAL LAW NO. 2 OF 2015), ESCA BOARD OF DIRECTORS' DECISION NO. (9/R.M.) OF 2016, ESCA CHAIRMAN DECISION NO 3/R.M. OF 2017 CONCERNING PROMOTING AND INTRODUCING REGULATIONS OR OTHERWISE UNDER THE LAWS OF THE UNITED ARAB EMIRATES. ACCORDINGLY, THE INTERESTS IN THE SECURITIES MAY NOT BE OFFERED TO THE PUBLIC IN THE UAE (INCLUDING THE DUBAI INTERNATIONAL FINANCIAL CENTRE AND THE ABU DHABI GLOBAL MARKET). THIS DOCUMENT HAS NOT BEEN APPROVED BY, OR FILED WITH THE CENTRAL BANK OF THE UNITED ARAB EMIRATES, THE SECURITIES AND COMMODITIES AUTHORITY, THE DUBAI FINANCIAL SERVICES AUTHORITY, THE FINANCIAL SERVICES REGULATORY AUTHORITY OR ANY OTHER RELEVANT LICENSING AUTHORITIES IN THE UNITED ARAB EMIRATES. IF YOU DO NOT UNDERSTAND THE CONTENTS OF THIS DOCUMENT, YOU SHOULD CONSULT WITH A FINANCIAL ADVISOR. THIS DOCUMENT IS PROVIDED TO THE RECIPIENT ONLY AND SHOULD NOT BE PROVIDED TO OR RELIED ON BY ANY OTHER PERSON." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/events/00_gsquant_meets_markets/02_optimizing_equity_trading/section_b_ownership.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Passive ownership and flows\n", "## Why it matters\n", "\n", "- Passive management has seen unprecedented growth in the past 10-15 years

Source: Sushko, V., and G. Turner (2018) The implications of passive investing for securities markets, BIS Quarterly Review, March 2018, 113–131.\n", "- Academics have found that institutional ownership in general tends to be positively associated with security return volatility (Sias, 1996)\n", "- Ben-David et al. (2018) find that this is particularly true for ETFs\n", "\n", "**The main mechanism:**\n", "- Liquidity shocks (i.e. inflows and outflows) that affect the fund propagate to the underlying securities, thereby increasing volatility\n", "- This mechanism is stronger in the case of passive funds because of the limited discretionality in the timing and direction of trades\n", "\n", "**Key results of our work:**\n", "- In our paper, \"The Shift from Active to Passive\" (July 2019), we find that passively held stocks tend to be more volatile after correcting for other stock characteristics\n", "- The effect of passive ownership on volatility is concentrated at the end of the continuous trading session and at the Close

Source: GS Global Markets Division.\n", "- The effect of passive inflows and outflows is similarly concentrated around the Close

Source: GS Global Markets Division.\n", "- This is arguably due to the concentration of portfolio trades in the order flow during that phase\n", "- We also detect differences in the impact of passive investing between US and European markets due to the different design of the closing auction\n", "\n", "**Passive ownership and the March 2020 selloff**\n", "- In our paper, \"Dissecting the Viral Selloff\" (April 2020), we group stocks by their level of passive ETF ownership and compare the percentage increase in volatility between January and March 2020

Source: GS Global Markets Division.\n", "\n", "- Stocks widely held by ETFs have experienced larger volatility increases, both in the US and in Europe\n", "- The result holds true after correcting for other effects, including sector, country, size and liquidity\n", "\n", "## Measuring passive ownership and flows\n", "- Starting from fund-level data, we produce stock-level indicators by using the latest information on holdings and flows\n", "- Raw ownership ratios:\n", "\\begin{equation}\n", "PassETFRatio_{i}=\\frac{\\sum_{j\\in F_{PassETF}}w_{ij}AuM_{j}}{MktCap_{i}}\n", "\\label{passETFratio}\n", "\\end{equation}\n", "where $w_{ij}$ is the weight of stock $i$ in fund $j$, $AuM_{j}$ is the total value of fund $j$'s holdings, $F_{PassETF}$ the set of passive ETFs and $MktCap_{i}$ is \n", "the market capitalisation in US dollars of stock $i$\n", "- Building on our research, we carry out a series of cross-sectional normalisations and adjustments to make the scores comparable across regions and over time\n", "- Scores are normalised between -3 and 3. Higher scores indicate a higher level of passive ownership\n", "- Similarly, for flow scores a higher score indicates stronger inflows to passive funds holding the stock\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Import libraries" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "from gs_quant.markets.securities import SecurityMaster, AssetIdentifier\n", "from gs_quant.session import GsSession\n", "from gs_quant.data import Dataset\n", "import datetime as dt\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Supply credentials" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=client_id, client_secret=client_secret, scopes=('read_product_data',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Load basket of stocks from text file" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# load data from csv\n", "ptf_df = pd.read_csv('trade_list_world.csv')\n", "ptf_df = ptf_df.rename(columns={'Symbol': 'SEDOL', 'Trade Value ($)': 'Value'})\n", "ptf_df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Map identifiers" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# load Marquee identifiers and Bloomberg Ids\n", "identifiers_df = {}\n", "for sedol_id in ptf_df.SEDOL:\n", " identifiers_df[sedol_id] = {}\n", " asset = SecurityMaster.get_asset(sedol_id, AssetIdentifier.SEDOL, sort_by_rank=True)\n", " identifiers_df[sedol_id]['assetId'] = asset.get_marquee_id()\n", " identifiers_df[sedol_id]['BCID'] = asset.get_identifiers(as_of=dt.date(2021, 6, 10))['BCID']\n", "\n", "identifiers_df = pd.DataFrame.from_dict(identifiers_df, orient='index')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Query Marquee datasets" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "## Get monthly ownership data \n", "ds = Dataset('OWNERSHIP_SCORES_MONTHLY')\n", "data_df = ds.get_data(start=dt.date(2021, 5, 9), end=dt.date(2021, 6, 10),\n", " assetId=identifiers_df.assetId.to_list())\n", "\n", "## Get weekly flow data \n", "ds2 = Dataset('OWNERSHIP_SCORES_WEEKLY_FLOW')\n", "data2_df = ds2.get_data(start=dt.date(2021, 6, 3), end=dt.date(2021, 6, 10),\n", " assetId=identifiers_df.assetId.to_list())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "## merge results in a summary table\n", "ptf_df = ptf_df.merge(identifiers_df, right_index=True, left_on='SEDOL').merge(data_df, on='assetId')\n", "ptf_df = ptf_df.merge(data2_df, on='assetId')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Explore the data" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "## display most passively held names\n", "ratios_names = [x for x in ptf_df.columns if x.endswith('Ratio')]\n", "ptf_df[['BCID']+ratios_names].sort_values(by='passiveRatio', ascending=False).head()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "## display least passively held names\n", "ptf_df[['BCID']+ratios_names].sort_values(by='passiveRatio', ascending=True).head()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "## display most affected by passive inflows\n", "ptf_df[['BCID']+ratios_names].sort_values(by='passiveFlowRatio', ascending=False).head()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "## display most affected by passive outflows\n", "ptf_df[['BCID']+ratios_names].sort_values(by='passiveFlowRatio', ascending=True).head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Compute impact of trade on portfolio-level ownership " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# plot breakdown by passive ETF ratio\n", "own_slices = [(-3, -2), (-2, -1), (-1, 0), (0, 1), (1, 2), (2, 3.1)]\n", "slice_labels = ['[-3,-2)', '[-2,-1)', '[-1,0)', '[0,1)', '[1,2)', '[2,3]']\n", "# scores are bounded between -3 and 3\n", "\n", "# compute total value sold and bought\n", "tot_traded_long = ptf_df.loc[ptf_df['Value']>0].sum()['Value']\n", "tot_traded_short = ptf_df.loc[ptf_df['Value']<0].sum()['Value']\n", "\n", "# compute values traded and plot bars\n", "text_kwargs = dict(ha='center', va='center', fontsize=12, color='black')\n", "fig, ax = plt.subplots(2, 1, figsize=(8, 12))\n", "cnc = 0\n", "for etf_var, var_label, chart_title in [('passiveEtfRatio', 'Passive ETF ratio', 'Breakdown by ETF ownership'),\n", " ('etfFlowRatio', 'Passive ETF flow ratio', 'Breakdown by ETF flows')]:\n", " slc = 0\n", " for lb, ub in own_slices:\n", " ax[cnc].barh(slc, ptf_df.loc[(ptf_df[etf_var]>=lb) & (ptf_df[etf_var]0)].sum()['Value']/tot_traded_long, color='#939393')\n", " ax[cnc].barh(slc, -ptf_df.loc[(ptf_df[etf_var]>=lb) & (ptf_df[etf_var]\n", "###### ゴールドマン・サックス証券株式会社が適格機関投資家のみを相手方として取得申込みの勧誘(取得勧誘)又は売付けの申込み若しくは買付けの申込みの勧誘(売付け勧誘等)を行う本有価証券には、適格機関投資家に譲渡する場合以外の譲渡が禁止される旨の制限が付されています。本有価証券は金融商品取引法第4条に基づく財務局に対する届出が行われておりません。なお、本告知はお客様によるご同意のもとに、電磁的に交付させていただいております。\n", "###### <適格機関投資家用資料> \n", "###### 本資料は、適格機関投資家のお客さまのみを対象に作成されたものです。本資料における金融商品は適格機関投資家のお客さまのみがお取引可能であり、適格機関投資家以外のお客さまからのご注文等はお受けできませんので、ご注意ください。 商号等/ゴールドマン・サックス証券株式会社 金融商品取引業者 関東財務局長(金商)第69号 \n", "###### 加入協会/ 日本証券業協会、一般社団法人金融先物取引業協会、一般社団法人第二種金融商品取引業協会 \n", "###### 本書又はその添付資料に信用格付が記載されている場合、日本格付研究所(JCR)及び格付投資情報センター(R&I)による格付は、登録信用格付業者による格付(登録格付)です。その他の格付は登録格付である旨の記載がない場合は、無登録格付です。無登録格付を投資判断に利用する前に、「無登録格付に関する説明書」(http://www.goldmansachs.com/disclaimer/ratings.html)を十分にお読みください。 \n", "###### If any credit ratings are contained in this material or any attachments, those that have been issued by Japan Credit Rating Agency, Ltd. (JCR) or Rating and Investment Information, Inc. (R&I) are credit ratings that have been issued by a credit rating agency registered in Japan (registered credit ratings). Other credit ratings are unregistered unless denoted as being registered. Before using unregistered credit ratings to make investment decisions, please carefully read \"Explanation Regarding Unregistered Credit Ratings\" (http://www.goldmansachs.com/disclaimer/ratings.html).\n", "###### Notice to Mexican Investors: Information contained herein is not meant for the general public in Mexico. The services or products provided by or through Goldman Sachs Mexico, Casa de Bolsa, S.A. de C.V. (GS Mexico) may not be offered or sold to the general public in Mexico. You have received information herein exclusively due to your existing relationship with a GS Mexico or any other Goldman Sachs business. The selection and engagement with any of the offered services or products through GS Mexico will be carried out directly by you at your own risk. Before acting to implement any chosen service or product provided by or through GS Mexico you should consider, at your sole discretion, whether it is suitable for your particular circumstances and, if necessary, seek professional advice. Information contained herein related to GS Mexico services or products, as well as any other information, shall not be considered as a product coming from research, nor it contains any recommendation to invest, not to invest, hold or sell any security and may not be supplied to the general public in Mexico.\n", "###### Notice to New Zealand Investors: When this document is disseminated in New Zealand by Goldman Sachs & Co. LLC (\"GSCO\") , Goldman Sachs International (\"GSI\"), Goldman Sachs Bank Europe SE (\"GSBE\"), Goldman Sachs (Asia) L.L.C. (\"GSALLC\") or Goldman Sachs (Singapore) Pte (\"GSSP\") (collectively the \"GS entities\"), this document, and any access to it, is intended only for a person that has first satisfied; the GS entities that the person is someone: \n", "###### (i) who is an investment business within the meaning of clause 37 of Schedule 1 of the Financial Markets Conduct Act 2013 (New Zealand) (the \"FMC Act\");\n", "###### (ii) who meets the investment activity criteria specified in clause 38 of Schedule 1 of the FMC Act;\n", "###### (iii) who is large within the meaning of clause 39 of Schedule 1 of the FMC Act; or\n", "###### (iv) is a government agency within the meaning of clause 40 of Schedule 1 of the FMC Act. \n", "###### No offer to acquire the interests is being made to you in this document. Any offer will only be made in circumstances where disclosure is not required under the Financial Markets Conducts Act 2013 or the Financial Markets Conduct Regulations 2014.\n", "###### Notice to Swiss Investors: This is marketing material for financial instruments or services. The information contained in this material is for general informational purposes only and does not constitute an offer, solicitation, invitation or recommendation to buy or sell any financial instruments or to provide any investment advice or service of any kind.\n", "###### THE INFORMATION CONTAINED IN THIS DOCUMENT DOES NOT CONSITUTE, AND IS NOT INTENDED TO CONSTITUTE, A PUBLIC OFFER OF SECURITIES IN THE UNITED ARAB EMIRATES IN ACCORDANCE WITH THE COMMERCIAL COMPANIES LAW (FEDERAL LAW NO. 2 OF 2015), ESCA BOARD OF DIRECTORS' DECISION NO. (9/R.M.) OF 2016, ESCA CHAIRMAN DECISION NO 3/R.M. OF 2017 CONCERNING PROMOTING AND INTRODUCING REGULATIONS OR OTHERWISE UNDER THE LAWS OF THE UNITED ARAB EMIRATES. ACCORDINGLY, THE INTERESTS IN THE SECURITIES MAY NOT BE OFFERED TO THE PUBLIC IN THE UAE (INCLUDING THE DUBAI INTERNATIONAL FINANCIAL CENTRE AND THE ABU DHABI GLOBAL MARKET). THIS DOCUMENT HAS NOT BEEN APPROVED BY, OR FILED WITH THE CENTRAL BANK OF THE UNITED ARAB EMIRATES, THE SECURITIES AND COMMODITIES AUTHORITY, THE DUBAI FINANCIAL SERVICES AUTHORITY, THE FINANCIAL SERVICES REGULATORY AUTHORITY OR ANY OTHER RELEVANT LICENSING AUTHORITIES IN THE UNITED ARAB EMIRATES. IF YOU DO NOT UNDERSTAND THE CONTENTS OF THIS DOCUMENT, YOU SHOULD CONSULT WITH A FINANCIAL ADVISOR. THIS DOCUMENT IS PROVIDED TO THE RECIPIENT ONLY AND SHOULD NOT BE PROVIDED TO OR RELIED ON BY ANY OTHER PERSON." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/events/00_gsquant_meets_markets/02_optimizing_equity_trading/section_c_apex.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ " \n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Introduction: What's APEX?\n", "APEX is a portfolio trade scheduler that optimizes execution with the latest intraday risk and market impact models from Goldman Sachs’ Quantitative Execution Services (QES) team.\n", "\n", "## Modeling Pillars\n", "\n", "\n", "## Constraints and Features\n", "\n", "\n", "## The APEX Trade Lifecycle\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### First, let's load a sample portfolio:\n", "#### Import Libs and Utils:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from qes_utils import persistXls, plotCost, plotVar, plotBuySellNet, plotGrossRemaining, plotMultiStrategyPortfolioLevelAnalytics\n", "\n", "from gs_quant.api.gs.assets import GsAssetApi\n", "from gs_quant.session import GsSession, Environment\n", "from gs_quant.common import Position\n", "from gs_quant.target.risk import OptimizationRequest, OptimizationType\n", "from gs_quant.api.gs.risk import GsRiskApi\n", "\n", "import matplotlib.pyplot as plt\n", "import pandas as pd\n", "import datetime\n", "import numpy as np\n", "import copy\n", "import datetime\n", "from matplotlib import cm" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Establish GS_Quant Connection: " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Fill in client_id and client_secret\\\n", "- Set up Marquee API: https://marquee.gs.com/s/developer/docs/getting-started\n", "- Once you create the application, click on the Application page and scroll down to the ‘Scope’ section. Request the “read_product_date”& “run_analytics” scope for your application. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print('INFO: Setting up Marquee Connection')\n", "client_id =\n", "client_secret =\n", "GsSession.use(Environment.PROD, client_id=client_id, client_secret=client_secret, scopes=['read_product_data', 'run_analytics'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Set up the portfolio: " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print('INFO: Setting up portfolio to schedule using APEX...')\n", "portfolio_input = pd.read_csv('trade_list_world.csv').rename(columns={'Symbol': 'sedol', 'Shares': 'qty'})\n", "portfolio_input.dtypes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Convert Identifier (SEDOL) to marqueeids:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "SEDOL access needs to be requested on Marquee with the following steps:\n", "- Go to https://marquee.gs.com/s/developer/datasets/SEDOL \n", "- Select an application to request access for \n", "- Request will be auto approved" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "assets = GsAssetApi.get_many_assets(sedol=list(portfolio_input['sedol']), fields=['sedol', 'rank'], listed=[True], type='Single Stock')\n", "identifier_to_marqueeid_map = pd.DataFrame([{'sedol': list(filter(lambda x: x.type=='SED', i.identifiers))[0].value, 'ID': i.id, 'rank': i.rank} for i in assets])\\\n", " .sort_values(['sedol', 'rank'], ascending=False).groupby('sedol').head(1)[['sedol','ID']].rename(columns={'ID': 'marqueeid'})\n", "print(f'found {len(identifier_to_marqueeid_map)} sedols to mrquee ids map...')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Identify assets with missing marquee ids and drop them from the portfolio" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "portfolio_input = portfolio_input.merge(identifier_to_marqueeid_map, how='left', on='sedol')\n", "\n", "missing_marqueeids = portfolio_input[portfolio_input['marqueeid'].isnull()]\n", "if len(missing_marqueeids):\n", " print(f'WARNING: the following bbids are missing marqueeids:\\n{missing_marqueeids}\\ndropping from the optimization...')\n", "else: \n", " print('INFO: all the assets has been succesfuly converted to marquee id')\n", "portfolio_input = portfolio_input.dropna()\n", "portfolio_input.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### At this point, we have a portfolio we can optimize using APEX.\n", "Our portfolio is now ready for optimization with APEX.\n", "### We'll run two variations:\n", "##### 1. single optimization analysis - optimize the basket using defined parameters and investigate the cost-risk trade-off.\n", "##### 2. trade scenario analysis - run multiple optimizations upon different risk aversion (urgency) parameters and compare the cost-risk trade-off among optimized execution strategies" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1. APEX Optimization: run my trade list in the APEX optimizer and explore the various analytics:\n", "#### in this section, we'll explore how to set optimization parameters and how to display multiple optimal trajectory analytics to develop further intuition for the decisions made by APEX\n", "\n", "we'll run an APEX-IS (Arrival) risk-cost minimization optimal trade allocation, in the following form:\n", "\\begin{equation*}\n", "Min \\displaystyle \\Bigg( \\lambda \\sum_{t=1}^T (\\mbox{Risk of Residual Holdings}) + (1-\\lambda) \\sum_{t=1}^T (\\mbox{Market Impact of Trades}) \\Bigg) \n", "\\end{equation*}\n", "\\begin{equation*}s.t.\\end{equation*}\n", "\\begin{equation*}Ax <= b\\end{equation*}\n", "\n", "where:\n", "\\begin{equation*}(\\mbox{Risk of Residual Holdings})\\end{equation*} \n", "- Incorporates the intraday and overnight expected risk, utilizing our high frequency intraday QES covariances. in other words, \"every $ I decided to trade later, is running at the Risk of missing the arrival price\"\n", "\n", "\\begin{equation*}(\\mbox{Market Impact of Trades})\\end{equation*}\n", "- Denote the expected market impact per asset, as a function of the physical interaction with the order book. in other words, \"every $ that I will trade now, will incur some expected market impact, based on the intraday predicted evolution of spread\\volume\\volatility\\participation rate, and other intraday calibrated parameters\"\n", "\n", "\\begin{equation*}\\lambda\\end{equation*}\n", "- Risk Aversion parameter\n", "\n", "\\begin{equation*}Ax <= b\\end{equation*}\n", "- set of linear constraints (see features available at the top of the notebook)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Set up the optimization constraints\n", "\n", "| Optimisation Parameters | Description | Value Chosen |\n", "| :- | :- | -: |\n", "| Start Time \\ End Time | APEX allowed \"Day1\" trade horizon, in GMT* | 11pm previous day to 11pm |\n", "| Urgency | APEX Urgency, from VERY_LOW to VERY_HIGH | Medium |\n", "| Target Benchmark | Currently supports 'IS', 'CLOSE' | IS |\n", "| Imbalance | (Optional) setting dollar imbalance for the trade duration; \"the net residual must be within +-5% of the residual gross to trade, throughout the entire trade duration\" | 0.05 (5%) |\n", "| Participation rate | Setting volume cap for trading | 0.075 (7.5%) |\n", "\n", "- Note that APEX allowed start end times range from 23:00 previous day to 23:00 of the query day.\n", " For example, if today is the 9th of October, APEX global optimization can run from start time of 23:00 on T-1 to 23:00 on T.\n", "- Please also note that APEX will automatically optimize up to 5 business days, providing an optimized intraday solution with granularity of 30\\60 minutes.\n", "- For a full set of parameters, please refer to the constraints & features image at the top, review the APEX api guide or contact [gs-qes-quant@gs.com](mailto:gs-qes-quant@gs.com)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "## set optimization configuration\n", "print('INFO: Constructing Optimization Request...')\n", "date_today = datetime.datetime.now().strftime('%Y-%m-%d')\n", "date_yesterday = (datetime.datetime.now() - datetime.timedelta(days=1)).strftime('%Y-%m-%d')\n", "\n", "apex_optimization_config = {\n", " 'executionStartTime': date_yesterday + 'T23:00:00.000Z', #execution start time\n", " 'executionEndTime': date_today +'T21:15:00.000Z', # execution end time (for day 1, can run multiday if not complete on day 1)\n", " 'waitForResults': False,\n", " 'parameters': {'urgency': 'MEDIUM', #VERY_LOW, LOW, HIGH, VERY_HIGH...\n", " 'targetBenchmark': 'IS', #CLOSE\n", " 'imbalance': 0.05, #Optional --> setting $ imbalance for the trade duration to never exceed +-20% of residual gross to trade\n", " 'participationRate': 0.075 #setting volume cap of 10%\n", " },\n", " }" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Send Optimization + Analytics request to Marquee" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def sendApexRequestAndGetAnalytics(portfolio_input, apex_optimization_config):\n", " positions = [Position(asset_id=row.marqueeid, quantity=row.qty) for _, row in portfolio_input.iterrows()]\n", " print('setting up the optimization request....')\n", " request = OptimizationRequest(positions=positions,\n", " execution_start_time=apex_optimization_config['executionStartTime'],\n", " execution_end_time=apex_optimization_config['executionEndTime'],\n", " parameters=apex_optimization_config['parameters'],\n", " **{'type': OptimizationType.APEX})\n", " print('Sending the request to the marquee service...')\n", " opt = GsRiskApi.create_pretrade_execution_optimization(request)\n", " analytics_results = GsRiskApi.get_pretrade_execution_optimization(opt.get('optimizationId'))\n", " print ('COMPLETE!')\n", " return analytics_results\n", "\n", "results_dict = sendApexRequestAndGetAnalytics(portfolio_input, apex_optimization_config)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print('INFO: High Level Cost estimation and % expected Completion:')\n", "pd.DataFrame(results_dict['analytics']['portfolioAnalyticsDaily']).set_index('tradeDayNumber')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print('missing assets:')\n", "pd.DataFrame(results_dict['analytics']['assetsExcluded'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Actual Optimization Parameters Used in APEX\n", "- Although a set of optimization parameters was specified above, APEX might conclude that the parameters joined feasible space does not exist (infeasible set).\n", "- APEX can then choose to soften/drop/relax the constraints in a hierarchical fashion. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "constraints_hierarchy = pd.DataFrame(results_dict['analytics']['constraintsConsultations'])['constraints']\n", "pd.concat([pd.DataFrame(constraints_hierarchy.values[i]).assign(iteration=i) for i in constraints_hierarchy.index]).set_index(['iteration', 'name'])['status'].unstack().T" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### What kind of Analytics provided by APEX ?\n", "##### APEX provide a vast set of numbers that helps understanding unravel the decision made by the optimizer:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "results_dict['analytics'].keys()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Visualise Your Optimisation Results" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "analytics_result_analytics = results_dict['analytics'] \n", "intraday = pd.DataFrame(analytics_result_analytics['portfolioAnalyticsIntraday'])\n", "intraday_to_plot = intraday.assign(time = lambda x: pd.to_datetime(x['time'])).set_index('time')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Four examples of visualizing your intraday analysis throughout trade date\n", "- Gross Remaining\n", "- Buy/Sell/Net\n", "- Cost Contribution\n", "- Risk Contribution " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "intraday_to_plot.head(5).append(intraday_to_plot.tail(5))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plotGrossRemaining(intraday_to_plot)\n", "plotBuySellNet(intraday_to_plot)\n", "plotCost(intraday_to_plot)\n", "plotVar(intraday_to_plot)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "###### Sources: Goldman Sachs, Bloomberg, Reuters, Axioma" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### The creativity around various analytics are endless, here are couple of examples, derived from the various analytics dataframes we use for our APEX clients:\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "###### Sources: Goldman Sachs, Bloomberg, Reuters, Axioma" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### save all results to excel for further exploration:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "xls_path = persistXls(xls_report=results_dict['analytics'],\n", " path='',\n", " filename='apex_optimization_detailed_analytics',\n", " indentifier_marqueeid_map=portfolio_input[\n", " [identifier_type, 'marqueeid']])\n", "print('saving all analytics frames to {0}...'.format(xls_path))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2. APEX Optimization - Trade Scenario Analysis: run my trade list in the APEX optimizer across multiple risk aversions\\urgency parameters to assess ideal parameters set." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Define a function for running multiple optimizations, keeping all constrains intact and change urgency only:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def optimisationMulti(portfolio_input, apex_optimization_config, urgency_list = ['VERY_LOW', 'LOW', 'MEDIUM', 'HIGH', 'VERY_HIGH']):\n", " results_dict_multi = {}\n", " apex_optimization_config_temp = copy.deepcopy(apex_optimization_config)\n", " \n", " for u in urgency_list:\n", " apex_optimization_config_temp['parameters']['urgency'] = u\n", " apex_optimization_config_temp['parameters']['imbalance'] = .3\n", " apex_optimization_config_temp['parameters']['participationRate'] = .5\n", " \n", " print('INFO Running urgency={0} optimization....'.format(u))\n", " results_dict_multi[u] = sendApexRequestAndGetAnalytics(portfolio_input, apex_optimization_config_temp)\n", " \n", " print('INFO: High Level Cost estimation and % expected Completion:\\n{0}'\\\n", " .format(pd.DataFrame(results_dict_multi[u]['analytics']['portfolioAnalyticsDaily'])))\n", " \n", " return results_dict_multi" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### Run Optimization Across Urgencies" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "urgency_list = ['VERY_LOW', 'LOW', 'MEDIUM', 'HIGH', 'VERY_HIGH']\n", "results_dict_multi = optimisationMulti(portfolio_input = portfolio_input,\\\n", " apex_optimization_config = apex_optimization_config,\\\n", " urgency_list=urgency_list)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Compare Results from Different Urgencies on Day 1:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ordering = ['grey', 'sky_blue', 'black', 'cyan', 'light_blue', 'dark_green']\n", "urgency_list = ['VERY_LOW', 'LOW', 'MEDIUM', 'HIGH', 'VERY_HIGH']\n", "ptAnalyticsDaily_list = []\n", "for u in urgency_list:\n", " ptAnalyticsDaily_list.append(pd.DataFrame(results_dict_multi[u]['analytics']['portfolioAnalyticsDaily']).iloc[[0]].assign(urgency=u) )\n", "pd.concat(ptAnalyticsDaily_list).set_index('urgency')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Visualise Optimization Results\n", "- Plotting 'Trade_cum_sum, Total Cost, Total Risk' against time for the chosen urgencies\n", "- Trade_cum_sum: Cumulative sum of the intraday trades" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "metrics_list = ['tradePercentageCumulativeSum', 'totalRiskBps', 'totalCost', 'advAveragePercentage']\n", "title = ['Intraday Trade', 'Risk', 'Cost', 'Participation Rate']\n", "ylabel = ['Trade Cum Sum %', 'Risk(bps) ', 'Cost(bps)', 'Prate(%)']\n", "\n", "plotMultiStrategyPortfolioLevelAnalytics(results_dict_multi, metrics_list, title, ylabel)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "###### Sources: Goldman Sachs, Bloomberg, Reuters, Axioma" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Plot the optimal Efficient Frontier - the expected Market Impact vs. Residual Risk Trade-off:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "initial_gross = pd.DataFrame(results_dict_multi['VERY_LOW']['analytics']['portfolioAnalyticsIntraday'])['gross'].iloc[0]\n", "risk_cost_tradeoff = pd.concat( [\\\n", " pd.DataFrame(results_dict_multi[urgency]['analytics']['portfolioAnalyticsDaily'])\\\n", " [['estimatedCostBps', 'meanExpectedCostVersusBenchmark']]\\\n", ".assign(totalRiskBps = lambda x: x['estimatedCostBps'] - x['meanExpectedCostVersusBenchmark'])\\\n", ".iloc[0].rename(urgency).to_frame()\n", "for urgency in ['VERY_LOW', 'LOW', 'MEDIUM']], axis=1).T\n", "\n", "cmap = cm.get_cmap('Set1')\n", "ax = risk_cost_tradeoff.plot.scatter(x='totalRiskBps', y='meanExpectedCostVersusBenchmark',\\\n", " title='The Example Basket Efficient Frontier',\\\n", " colormap=cmap, c=range(len(risk_cost_tradeoff)), s=100)\n", "\n", "for k, v in risk_cost_tradeoff[['totalRiskBps', 'meanExpectedCostVersusBenchmark']].iterrows():\n", " ax.annotate(k, v,\n", " xytext=(10,-5), textcoords='offset points',\n", " family='sans-serif', fontsize=10, color='darkslategrey')\n", "\n", "ax.plot(risk_cost_tradeoff['totalRiskBps'].values, risk_cost_tradeoff['meanExpectedCostVersusBenchmark'].values,\n", " color='grey', alpha=.5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "###### Sources: Goldman Sachs, Bloomberg, Reuters, Axioma" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# And That's IT! Find below an holistic view of our APEX platform in visual from:\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### Disclaimers:\n", "###### Indicative Terms/Pricing Levels: This material may contain indicative terms only, including but not limited to pricing levels. There is no representation that any transaction can or could have been effected at such terms or prices. Proposed terms and conditions are for discussion purposes only. Finalized terms and conditions are subject to further discussion and negotiation.\n", "###### www.goldmansachs.com/disclaimer/sales-and-trading-invest-rec-disclosures.html If you are not accessing this material via Marquee ContentStream, a list of the author's investment recommendations disseminated during the preceding 12 months and the proportion of the author's recommendations that are 'buy', 'hold', 'sell' or other over the previous 12 months is available by logging into Marquee ContentStream using the link below. Alternatively, if you do not have access to Marquee ContentStream, please contact your usual GS representative who will be able to provide this information to you.\n", "###### Please refer to https://marquee.gs.com/studio/ for price information of corporate equity securities.\n", "###### Notice to Australian Investors: When this document is disseminated in Australia by Goldman Sachs & Co. LLC (\"GSCO\"), Goldman Sachs International (\"GSI\"), Goldman Sachs Bank Europe SE (\"GSBE\"), Goldman Sachs (Asia) L.L.C. (\"GSALLC\"), or Goldman Sachs (Singapore) Pte (\"GSSP\") (collectively the \"GS entities\"), this document, and any access to it, is intended only for a person that has first satisfied the GS entities that: \n", "###### • the person is a Sophisticated or Professional Investor for the purposes of section 708 of the Corporations Act of Australia; and \n", "###### • the person is a wholesale client for the purpose of section 761G of the Corporations Act of Australia. \n", "###### To the extent that the GS entities are providing a financial service in Australia, the GS entities are each exempt from the requirement to hold an Australian financial services licence for the financial services they provide in Australia. Each of the GS entities are regulated by a foreign regulator under foreign laws which differ from Australian laws, specifically: \n", "###### • GSCO is regulated by the US Securities and Exchange Commission under US laws;\n", "###### • GSI is authorised by the Prudential Regulation Authority and regulated by the Financial Conduct Authority and the Prudential Regulation Authority, under UK laws;\n", "###### • GSBE is subject to direct prudential supervision by the European Central Bank and in other respects is supervised by the German Federal Financial Supervisory Authority (Bundesanstalt für Finanzdienstleistungsaufischt, BaFin) and Deutsche Bundesbank;\n", "###### • GSALLC is regulated by the Hong Kong Securities and Futures Commission under Hong Kong laws; and\n", "###### • GSSP is regulated by the Monetary Authority of Singapore under Singapore laws.\n", "###### Notice to Brazilian Investors\n", "###### Marquee is not meant for the general public in Brazil. The services or products provided by or through Marquee, at any time, may not be offered or sold to the general public in Brazil. You have received a password granting access to Marquee exclusively due to your existing relationship with a GS business located in Brazil. The selection and engagement with any of the offered services or products through Marquee, at any time, will be carried out directly by you. Before acting to implement any chosen service or products, provided by or through Marquee you should consider, at your sole discretion, whether it is suitable for your particular circumstances and, if necessary, seek professional advice. Any steps necessary in order to implement the chosen service or product, including but not limited to remittance of funds, shall be carried out at your discretion. Accordingly, such services and products have not been and will not be publicly issued, placed, distributed, offered or negotiated in the Brazilian capital markets and, as a result, they have not been and will not be registered with the Brazilian Securities and Exchange Commission (Comissão de Valores Mobiliários), nor have they been submitted to the foregoing agency for approval. Documents relating to such services or products, as well as the information contained therein, may not be supplied to the general public in Brazil, as the offering of such services or products is not a public offering in Brazil, nor used in connection with any offer for subscription or sale of securities to the general public in Brazil.\n", "###### The offer of any securities mentioned in this message may not be made to the general public in Brazil. Accordingly, any such securities have not been nor will they be registered with the Brazilian Securities and Exchange Commission (Comissão de Valores Mobiliários) nor has any offer been submitted to the foregoing agency for approval. Documents relating to the offer, as well as the information contained therein, may not be supplied to the public in Brazil, as the offer is not a public offering of securities in Brazil. These terms will apply on every access to Marquee.\n", "###### Ouvidoria Goldman Sachs Brasil: 0800 727 5764 e/ou ouvidoriagoldmansachs@gs.com\n", "###### Horário de funcionamento: segunda-feira à sexta-feira (exceto feriados), das 9hs às 18hs.\n", "###### Ombudsman Goldman Sachs Brazil: 0800 727 5764 and / or ouvidoriagoldmansachs@gs.com\n", "###### Available Weekdays (except holidays), from 9 am to 6 pm.\n", " \n", "###### Note to Investors in Israel: GS is not licensed to provide investment advice or investment management services under Israeli law.\n", "###### Notice to Investors in Japan\n", "###### Marquee is made available in Japan by Goldman Sachs Japan Co., Ltd.\n", "\n", "###### 本書は情報の提供を目的としております。また、売却・購入が違法となるような法域での有価証券その他の売却若しくは購入を勧めるものでもありません。ゴールドマン・サックスは本書内の取引又はストラクチャーの勧誘を行うものではございません。これらの取引又はストラクチャーは、社内及び法規制等の承認等次第で実際にはご提供できない場合がございます。\n", "\n", "###### <適格機関投資家限定 転売制限>\n", "###### ゴールドマン・サックス証券株式会社が適格機関投資家のみを相手方として取得申込みの勧誘(取得勧誘)又は売付けの申込み若しくは買付けの申込みの勧誘(売付け勧誘等)を行う本有価証券には、適格機関投資家に譲渡する場合以外の譲渡が禁止される旨の制限が付されています。本有価証券は金融商品取引法第4条に基づく財務局に対する届出が行われておりません。なお、本告知はお客様によるご同意のもとに、電磁的に交付させていただいております。\n", "###### <適格機関投資家用資料> \n", "###### 本資料は、適格機関投資家のお客さまのみを対象に作成されたものです。本資料における金融商品は適格機関投資家のお客さまのみがお取引可能であり、適格機関投資家以外のお客さまからのご注文等はお受けできませんので、ご注意ください。 商号等/ゴールドマン・サックス証券株式会社 金融商品取引業者 関東財務局長(金商)第69号 \n", "###### 加入協会/ 日本証券業協会、一般社団法人金融先物取引業協会、一般社団法人第二種金融商品取引業協会 \n", "###### 本書又はその添付資料に信用格付が記載されている場合、日本格付研究所(JCR)及び格付投資情報センター(R&I)による格付は、登録信用格付業者による格付(登録格付)です。その他の格付は登録格付である旨の記載がない場合は、無登録格付です。無登録格付を投資判断に利用する前に、「無登録格付に関する説明書」(http://www.goldmansachs.com/disclaimer/ratings.html)を十分にお読みください。 \n", "###### If any credit ratings are contained in this material or any attachments, those that have been issued by Japan Credit Rating Agency, Ltd. (JCR) or Rating and Investment Information, Inc. (R&I) are credit ratings that have been issued by a credit rating agency registered in Japan (registered credit ratings). Other credit ratings are unregistered unless denoted as being registered. Before using unregistered credit ratings to make investment decisions, please carefully read \"Explanation Regarding Unregistered Credit Ratings\" (http://www.goldmansachs.com/disclaimer/ratings.html).\n", "###### Notice to Mexican Investors: Information contained herein is not meant for the general public in Mexico. The services or products provided by or through Goldman Sachs Mexico, Casa de Bolsa, S.A. de C.V. (GS Mexico) may not be offered or sold to the general public in Mexico. You have received information herein exclusively due to your existing relationship with a GS Mexico or any other Goldman Sachs business. The selection and engagement with any of the offered services or products through GS Mexico will be carried out directly by you at your own risk. Before acting to implement any chosen service or product provided by or through GS Mexico you should consider, at your sole discretion, whether it is suitable for your particular circumstances and, if necessary, seek professional advice. Information contained herein related to GS Mexico services or products, as well as any other information, shall not be considered as a product coming from research, nor it contains any recommendation to invest, not to invest, hold or sell any security and may not be supplied to the general public in Mexico.\n", "###### Notice to New Zealand Investors: When this document is disseminated in New Zealand by Goldman Sachs & Co. LLC (\"GSCO\") , Goldman Sachs International (\"GSI\"), Goldman Sachs Bank Europe SE (\"GSBE\"), Goldman Sachs (Asia) L.L.C. (\"GSALLC\") or Goldman Sachs (Singapore) Pte (\"GSSP\") (collectively the \"GS entities\"), this document, and any access to it, is intended only for a person that has first satisfied; the GS entities that the person is someone: \n", "###### (i) who is an investment business within the meaning of clause 37 of Schedule 1 of the Financial Markets Conduct Act 2013 (New Zealand) (the \"FMC Act\");\n", "###### (ii) who meets the investment activity criteria specified in clause 38 of Schedule 1 of the FMC Act;\n", "###### (iii) who is large within the meaning of clause 39 of Schedule 1 of the FMC Act; or\n", "###### (iv) is a government agency within the meaning of clause 40 of Schedule 1 of the FMC Act. \n", "###### No offer to acquire the interests is being made to you in this document. Any offer will only be made in circumstances where disclosure is not required under the Financial Markets Conducts Act 2013 or the Financial Markets Conduct Regulations 2014.\n", "###### Notice to Swiss Investors: This is marketing material for financial instruments or services. The information contained in this material is for general informational purposes only and does not constitute an offer, solicitation, invitation or recommendation to buy or sell any financial instruments or to provide any investment advice or service of any kind.\n", "###### THE INFORMATION CONTAINED IN THIS DOCUMENT DOES NOT CONSITUTE, AND IS NOT INTENDED TO CONSTITUTE, A PUBLIC OFFER OF SECURITIES IN THE UNITED ARAB EMIRATES IN ACCORDANCE WITH THE COMMERCIAL COMPANIES LAW (FEDERAL LAW NO. 2 OF 2015), ESCA BOARD OF DIRECTORS' DECISION NO. (9/R.M.) OF 2016, ESCA CHAIRMAN DECISION NO 3/R.M. OF 2017 CONCERNING PROMOTING AND INTRODUCING REGULATIONS OR OTHERWISE UNDER THE LAWS OF THE UNITED ARAB EMIRATES. ACCORDINGLY, THE INTERESTS IN THE SECURITIES MAY NOT BE OFFERED TO THE PUBLIC IN THE UAE (INCLUDING THE DUBAI INTERNATIONAL FINANCIAL CENTRE AND THE ABU DHABI GLOBAL MARKET). THIS DOCUMENT HAS NOT BEEN APPROVED BY, OR FILED WITH THE CENTRAL BANK OF THE UNITED ARAB EMIRATES, THE SECURITIES AND COMMODITIES AUTHORITY, THE DUBAI FINANCIAL SERVICES AUTHORITY, THE FINANCIAL SERVICES REGULATORY AUTHORITY OR ANY OTHER RELEVANT LICENSING AUTHORITIES IN THE UNITED ARAB EMIRATES. IF YOU DO NOT UNDERSTAND THE CONTENTS OF THIS DOCUMENT, YOU SHOULD CONSULT WITH A FINANCIAL ADVISOR. THIS DOCUMENT IS PROVIDED TO THE RECIPIENT ONLY AND SHOULD NOT BE PROVIDED TO OR RELIED ON BY ANY OTHER PERSON." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/events/00_gsquant_meets_markets/02_optimizing_equity_trading/trade_list_world.csv ================================================ Symbol,Description,Shares,Price ($),Trade Value ($),Trade Value (%),Trade Type,Medium-Term Momentum,Country,Industries,20-Day ADV,Trade_Value_to_ADV B9M2WX3,L BRANDS INC,283808.6547,65.46,1.86E+07,0.018946035,Buy New,3.4948962,United States,Specialty Retail,3.36E+08,5.5% BNDYF48,IAC INTERACTIVECORP NEW,79702.53471,153.83,1.23E+07,0.01250345,Buy New,1.6383283,United States,Internet Software and Services,1.75E+08,7.0% 6356525,FUJIFILM HOLDINGS,100350.024,69.86313869,7010767.643,0.007149609,Buy New,0.14659888,Japan,Technology Hardware Storage and Peripherals,1.29E+08,5.4% BKVD2N4,SEAGATE TECHNOLOGY HLDGS,69534.16827,99.33,6906828.935,0.007043612,Buy New,0.8976723,United States,Technology Hardware Storage and Peripherals,3.46E+08,2.0% 2630643,NETAPP INC,70741.24269,82.01,5801489.313,0.005916382,Buy New,0.63259625,United States,Technology Hardware Storage and Peripherals,1.40E+08,4.1% B44Z3T8,EPAM SYS INC,9377.052449,492.98001,4622699.41,0.004714247,Buy New,0.64648473,United States,IT Services,1.68E+08,2.7% BGK4T39,FUTU HLDGS LTD ADR,30263.89615,145.69,4409147.03,0.004496466,Buy New,3.4948962,Hong Kong,Capital Markets,8.12E+08,0.5% 2352118,FREEPORT-MCMORAN INC,94987.48132,40.7,3865990.49,0.003942552,Buy More,3.4948962,United States,Metals and Mining,9.38E+08,0.4% 2372763,GARTNER INC,15665.67024,230.28999,3607647.042,0.003679093,Buy New,0.70093536,United States,IT Services,1.45E+08,2.5% 6643960,NIPPON YUSEN KK,63544.46152,44.98175182,2858341.198,0.002914948,Buy More,2.3644235,Japan,Marine,1.37E+08,2.1% 6455530,ICL GROUP LTD,133365.5454,7.352578337,980580.62,0.001,Buy New,0.94320285,Israel,Chemicals,1.56E+07,6.3% BYSS4X4,NOVOCURE LTD,4181.89719,211.89,886102.1955,9.04E-04,Buy More,2.2161908,United States,Health Care Equipment and Supplies,1.16E+08,0.8% 2989356,ZEBRA TECHNOLOGIES CORPORATI,1613.966263,505.35001,815617.8671,8.32E-04,Buy More,0.67949533,United States,Electronic Equipment Instruments and Components,1.31E+08,0.6% BYVYWS0,HEWLETT PACKARD ENTERPRISE C,49982.04051,15.65,782218.934,7.98E-04,Buy More,0.42147762,United States,Technology Hardware Storage and Peripherals,1.52E+08,0.5% 6821807,WASHINGTON H SOUL PATTINSON AND COMPANY LTD,6611.168676,23.80220177,157360.3708,1.60E-04,Buy More,0.17212428,Australia,Oil Gas and Consumable Fuels,5291398.151,3.0% 6883807,SYSMEX CORP,-98.25190551,101.7791971,-10000.00005,-1.02E-05,Sell Some,-0.15122022,Japan,Health Care Equipment and Supplies,4.55E+07,0.0% 0216238,AVIVA,-1709.998947,5.847957,-10000.00031,-1.02E-05,Sell Some,0.20779254,United Kingdom,Insurance,4.25E+07,0.0% 0677608,PEARSON,-58305.73693,12.0377511,-701869.9488,-7.16E-04,Sell Some,0.14770593,United Kingdom,Media,1.58E+07,4.4% 4741714,REMY COINTREAU,-4694.121841,203.132285,-953527.6957,-9.72E-04,Sell Some,-0.067527473,France,Beverages,1.27E+07,7.5% BZ1LFG7,ROKU INC,-3596.976023,339.64001,-1221676.973,-0.001245871,Sell Some,1.8364291,United States,Media,1.11E+09,0.1% 6948836,WESFARMERS LTD,-34812.63048,41.9998065,-1462123.744,-0.00149108,Sell Some,-0.20156763,Australia,Multiline Retail,5.65E+07,2.6% 6474535,JAPAN TOBACCO INC,-77943.17531,20.09580292,-1566330.69,-0.00159735,Sell Some,-0.49438974,Japan,Tobacco,8.63E+07,1.8% 2692632,ALTRIA GROUP INC,-36135.80062,49.9,-1803176.451,-0.001838886,Sell Some,-0.16012596,United States,Tobacco,3.14E+08,0.6% 2380498,GENERAL ELECTRIC CO,-140341.439,13.73,-1926887.958,-0.001965048,Sell Some,0.67412817,United States,Industrial Conglomerates,8.29E+08,0.2% 2951098,WEST FRASER TIMBER CO LTD,-28270.87841,73.70249369,-2083634.238,-0.002124898,Sell All,0.95223433,Canada,Paper and Forest Products,6.02E+07,3.5% BJ2Z0H2,PINTEREST INC,-36048.92935,66.12,-2383555.209,-0.002430759,Sell Some,1.711701,United States,Internet Software and Services,5.57E+08,0.4% 2002479,AES CORP,-106718.92,25.2,-2689316.783,-0.002742576,Sell Some,0.66341442,United States,Independent Power and Renewable Electricity Producers,1.34E+08,2.0% BK73B42,CORTEVA INC,-60406.73097,45.49,-2747902.192,-0.002802322,Sell Some,0.38967612,United States,Chemicals,1.56E+08,1.8% 6420538,HENDERSON LAND DEV,-753582.2952,4.948484204,-3729090.084,-0.003802941,Sell Some,-0.36262581,Hong Kong,Real Estate Management and Development,1.86E+07,20.0% B02K2M3,M3 INC,-61921.90398,64.91788321,-4019838.931,-0.004099448,Sell All,0.19367497,Japan,Health Care Technology,1.55E+08,2.6% B616C79,TESLA INC,-7472.422855,598.78003,-4474337.581,-0.004562947,Sell Some,1.9619533,United States,Automobiles,1.71E+10,0.0% 6770620,SOFTBANK GROUP CO,-61434.82368,73.65875912,-4525212.879,-0.00461483,Sell All,0.20267113,Japan,Wireless Telecommunication Services,1.16E+09,0.4% BYNZGK1,SQUARE INC,-21978.55419,210.21001,-4620112.096,-0.004711609,Sell Some,0.81130087,United States,IT Services,2.15E+09,0.2% 6859927,SUN HUNG KAI PROP,-395711.7491,15.74752415,-6231480.324,-0.006354888,Sell All,-0.32389265,Hong Kong,Real Estate Management and Development,5.44E+07,11.5% B28XP76,INVESCO LTD,-245304.3244,28.7,-7040234.111,-0.007179659,Sell Some,1.7908559,United States,Capital Markets,1.34E+08,5.3% 2831543,SOUTHWEST AIRLS CO,-125580.163,58.42,-7336393.121,-0.007481683,Sell All,0.48997331,United States,Airlines,3.47E+08,2.1% BYY88Y7,ALPHABET INC,-4819.316838,2491.3999,-1.20E+07,-0.012244629,Sell All,0.24704795,United States,Internet Software and Services,2.77E+09,0.4% ================================================ FILE: gs_quant/content/events/00_gsquant_meets_markets/03_esg_basket_portfolio_optimisation/quants_meet_markets_msci.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "source": [ "# GS Quant Meets Markets x MSCI" ], "metadata": { "collapsed": false } }, { "cell_type": "markdown", "source": [ "#### Step 1: Import Modules" ], "metadata": { "collapsed": false } }, { "cell_type": "code", "source": [ "# Import modules\n", "from typing import List\n", "\n", "from gs_quant.api.utils import ThreadPoolManager\n", "from gs_quant.data import Dataset\n", "from gs_quant.api.gs.assets import GsAssetApi\n", "from gs_quant.models.risk_model import FactorRiskModel, DataAssetsRequest\n", "from functools import partial\n", "\n", "from gs_quant.markets.baskets import Basket\n", "from gs_quant.markets.indices_utils import ReturnType\n", "from gs_quant.markets.position_set import PositionSet\n", "from gs_quant.session import Environment, GsSession\n", "import matplotlib.pyplot as plt\n", "import datetime as dt\n", "from dateutil.relativedelta import relativedelta\n", "import pandas as pd" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "#### Step 2: Authenticate" ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "# Initialize session -- for external users, input client id and secret below\n", "client = None\n", "secret = None\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes='')" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "#### Step 3: Implement basic functions to fetch coverage universe, ratings, factor & liquidity data" ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "# Initialize functions\n", "\n", "def batch_liquidity(dataset_id: str, asset_ids: list, day: dt.date, size: int=200) -> pd.DataFrame:\n", " data = Dataset(dataset_id)\n", " tasks = [partial(data.get_data, day, day, assetId=asset_ids[i:i+size]) for i in range(0, len(asset_ids), size)]\n", " results = ThreadPoolManager.run_async(tasks)\n", " return pd.concat(results)\n", "\n", "\n", "def batch_ratings(dataset_id: str, gsid_ids: list, day: dt.date, fallback_month, filter_value: str= None, size: int=5000) -> pd.DataFrame:\n", " data = Dataset(dataset_id)\n", " start_date = day - relativedelta(month=fallback_month)\n", " tasks = [partial(data.get_data, start_date=start_date, gsid=gsid_ids[i:i+size], rating=filter_value) for i in range(0, len(gsid_ids), size)] if filter_value else \\\n", " [partial(data.get_data, start_date=start_date, gsid=gsid_ids[i:i + size]) for i in range(0, len(gsid_ids), size)]\n", " results = ThreadPoolManager.run_async(tasks)\n", " return pd.concat(results)\n", "\n", "\n", "def batch_asset_request(day: dt.date, gsids_list: list, limit: int=1000) -> list:\n", " date_time = dt.datetime.combine(day, dt.datetime.min.time())\n", " fields = ['gsid', 'bbid', 'id', 'delisted', 'assetClassificationsIsPrimary']\n", " tasks = [partial(GsAssetApi.get_many_assets_data, gsid=gsids_list[i:i + limit], as_of=date_time, limit=limit*10, fields=fields) for i in range(0, len(gsids_list), limit)]\n", " results = ThreadPoolManager.run_async(tasks)\n", " return [item for sublist in results for item in sublist]\n", "\n", "\n", "def get_universe_with_xrefs(day: dt.date, model: FactorRiskModel) -> pd.DataFrame:\n", " print(f'---------Getting risk {model.id} coverage universe on {day}------------')\n", " # get coverage universe on date\n", " universe = model.get_asset_universe(day, day).iloc[:, 0].tolist()\n", " print(f'{len(universe)} assets in {model.id} on {day} that map to gsids')\n", " # need to map from id -> asset_identifier on date\n", " asset_identifiers = pd.DataFrame(batch_asset_request(day, universe))\n", " print(f'{len(asset_identifiers)} assets found')\n", "\n", " asset_identifiers = asset_identifiers[asset_identifiers['assetClassificationsIsPrimary'] != 'false']\n", " print(f'{len(asset_identifiers)} asset xrefs after is not primary dropped')\n", " asset_identifiers = asset_identifiers[asset_identifiers['delisted'] != 'yes']\n", " print(f'{len(asset_identifiers)} asset xrefs after delisted assets are dropped')\n", "\n", " asset_identifiers = asset_identifiers[['gsid', 'bbid', 'id']].set_index('gsid')\n", " asset_identifiers = asset_identifiers[~asset_identifiers.index.duplicated(keep='first')] # remove duplicate gsids\n", " asset_identifiers.reset_index(inplace=True)\n", " print(f'{len(asset_identifiers)} positions after duplicate gsids removed')\n", "\n", " return pd.DataFrame(asset_identifiers).set_index('id')\n", "\n", "\n", "def get_and_filter_ratings(day: dt.date, gsid_list: List[str], filter_value: str = None) -> list:\n", " # get ratings of assets from the ratings dataset and only keep 'Buy' ratings\n", " print(f'---------Filtering coverage universe by rating: {filter_value}------------')\n", " fallback_month = 3\n", " ratings_df = batch_ratings('RATINGS_CL', gsid_list, day, fallback_month, filter_value)\n", " df_by_asset = [ratings_df[ratings_df['gsid'] == asset] for asset in set(ratings_df['gsid'].tolist())]\n", " most_recent_rating = pd.concat([df.iloc[-1:] for df in df_by_asset])\n", " print(f'{len(most_recent_rating)} unique assets with ratings after filtering applied')\n", " return list(most_recent_rating['gsid'].unique())\n", "\n", "\n", "def get_and_filter_factor_exposures(day: dt.date, identifier_list: List[str], factor_model: FactorRiskModel, factors: List[str]= [] , filter_floor: int = 0.5) -> pd.DataFrame:\n", " # get factor info and filter by factors\n", " print(f'---------Filtering coverage universe by factors: {factors}------------')\n", " available_factors = factor_model.get_factor_data(day).set_index('identifier')\n", " req = DataAssetsRequest('gsid', identifier_list)\n", " factor_exposures = factor_model.get_universe_factor_exposure(day, day, assets=req).fillna(0)\n", "\n", " factor_exposures.columns = [available_factors.loc[x]['name'] for x in factor_exposures.columns]\n", " factor_exposures = factor_exposures.droplevel(1)\n", " print(f'{len(factor_exposures)} factor exposures available')\n", " for factor in factors:\n", " factor_exposures = factor_exposures[factor_exposures[factor] >= filter_floor]\n", " print(f'{len(factor_exposures)} factor exposures returned after filtering by {factor} with floor exposure {filter_floor}')\n", " return factor_exposures\n", "\n", "\n", "def get_and_filter_liquidity(day: dt.date, asset_ids: List[str], filter_floor: int = 0) -> pd.DataFrame:\n", " # get mdv22Day liquidity info and take assets above average adv\n", " print(f'---------Filtering coverage universe by liquidity value: {filter_floor}------------')\n", " liquidity = batch_liquidity('GSEOD', asset_ids, day).set_index(\"assetId\")\n", " print(f'{len(liquidity)} liquidity data available for requested universe')\n", " if filter_floor:\n", " liquidity = liquidity[liquidity['mdv22Day'] >= filter_floor]\n", " print(f'{len(liquidity)} unique assets with liquidity data returned after filtering')\n", " return liquidity\n", "\n", "\n", "def backtest_strategy(day: dt.date, position_set: List[dict], risk_model_id: str):\n", " # make a request to pretrade liquidity to get backtest timeseries\n", " print(f'---------Backtesting strategy------------')\n", " query = {\"currency\":\"USD\",\n", " \"notional\": 1000000,\n", " \"date\": day.strftime(\"%Y-%m-%d\"),\n", " \"positions\":position_set,\n", " \"participationRate\":0.1,\n", " \"riskModel\":risk_model_id,\n", " \"timeSeriesBenchmarkIds\":[],\n", " \"measures\":[\"Time Series Data\"]}\n", " result = GsSession.current._post('/risk/liquidity', query)\n", " result = result.get(\"timeseriesData\")\n", " return result\n", "\n", "\n", "def graph_df_list(df_list, title):\n", " for df in df_list:\n", " plt.plot(df[0], label=df[1])\n", " plt.legend(title='Measures')\n", " plt.xlabel('Date')\n", " plt.title(title)\n", " plt.show()" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "#### Step 4: Strategy Implementation\n", "\n", "Proposed Methodology\n", "- Starting universe: Chosen risk model coverage universe\n", "- High Conviction names: Retain GS \"Buy\" ratings only\n", "- High ESG names: Retain high ESG scores only, using BARRA GEMLTL ESG model\n", "- High Profitability names: Retain high Profitability scores only, using BARRA GEMLTL ESG model\n", "- Liquidity adjustment: Removing the tail of illiquid names\n", "- Weighting: MDV-based weighting" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "# Get risk model and available style factors\n", "start = dt.datetime.now()\n", "\n", "# Get risk model\n", "model_id = \"BARRA_GEMLTL_ESG\"\n", "factor_model = FactorRiskModel.get(model_id)\n", "\n", "# Get last date of risk model data\n", "date = factor_model.get_most_recent_date_from_calendar() - dt.timedelta(1)\n", "print(f\"-----Available style factors for model {model_id}-----\")\n", "factor_data = factor_model.get_factor_data(date, date)\n", "factor_data = factor_data[factor_data['factorCategoryId'] == 'RI']\n", "print(factor_data['name'])" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "# Get universe\n", "mqid_to_id = get_universe_with_xrefs(date, factor_model)" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "# Get available ratings for past 3 months and return most recent ratings data per asset\n", "ratings_filter = 'Buy'\n", "ratings_universe = get_and_filter_ratings(date, mqid_to_id['gsid'].tolist(), filter_value=ratings_filter)" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "# Pass in factors to filter by\n", "factors = ['ESG', 'Profitability']\n", "filter_floor = 0.5\n", "\n", "exposures = get_and_filter_factor_exposures(date, ratings_universe, factor_model, factors=factors, filter_floor=filter_floor)\n", "ids = mqid_to_id.reset_index().set_index(\"gsid\")\n", "exposures = exposures.join(ids, how='inner')" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "# Filter by liquidity, which takes in the MQ Id\n", "asset_ids = exposures['id'].tolist()\n", "liquidity_floor = 1000000\n", "liquidity = get_and_filter_liquidity(date, asset_ids, filter_floor=liquidity_floor)\n", "liquidity = liquidity.join(mqid_to_id, how='inner')" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "# Get weights as ADV / total ADV\n", "total_adv = sum(list(liquidity['mdv22Day']))\n", "liquidity['weights'] = liquidity['mdv22Day'] / total_adv" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "#### Step 5: Backtest Strategy" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "# Backtest composition\n", "backtest_set = [{'assetId': index, \"weight\": row['weights']} for index, row in liquidity.iterrows()]\n", "position_set = [{'bbid': row['bbid'], \"weight\": row['weights']} for index, row in liquidity.iterrows()]\n", "print(\"Position set for basket create: \")\n", "print(pd.DataFrame(position_set))\n", "print(f'Total time to build position set with requested parameters {dt.datetime.now() - start}')\n", "\n", "\n", "backtest = backtest_strategy(date, backtest_set, model_id)\n", "print(\"Available measures to plot for backtested strategy: \")\n", "measures = list(backtest[0].keys())\n", "measures.remove(\"name\")\n", "print(measures)" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "# Graph Normalized Performance\n", "np = ['normalizedPerformance']\n", "series_to_plot = []\n", "for measure in np:\n", " timeseries = backtest[0].get(measure)\n", " timeseries = {dt.datetime.strptime(data[0], \"%Y-%m-%d\"): data[1] for data in timeseries}\n", " timeseries = (pd.Series(timeseries), measure)\n", " series_to_plot.append(timeseries)\n", "\n", "graph_df_list(series_to_plot, \"Normalized Performance\")" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "# Plot many measures\n", "measures.remove(\"netExposure\")\n", "measures.remove(\"cumulativePnl\")\n", "measures.remove(\"maxDrawdown\")\n", "\n", "series_to_plot = []\n", "for measure in measures:\n", " timeseries = backtest[0].get(measure)\n", " timeseries = {dt.datetime.strptime(data[0], \"%Y-%m-%d\"): data[1] for data in timeseries}\n", " timeseries = (pd.Series(timeseries), measure)\n", " series_to_plot.append(timeseries)\n", "\n", "graph_df_list(series_to_plot, \"Backtested Strategy Measures\")" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "#### Step 6: Basket Creation" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "# Create basket with positions\n", "my_basket = Basket()\n", "my_basket.name = 'Basket Name'\n", "my_basket.ticker = 'Basket Ticker'\n", "my_basket.currency = 'USD'\n", "my_basket.return_type = ReturnType.PRICE_RETURN\n", "my_basket.publish_to_bloomberg = True\n", "my_basket.publish_to_reuters = True\n", "my_basket.publish_to_factset = False\n", "data=[]\n", "for row in position_set:\n", " data.append([row['bbid'], row['weight']])\n", "\n", "positions_df = pd.DataFrame(data, columns=['identifier', 'weight'])\n", "position_set = PositionSet.from_frame(positions_df)\n", "my_basket.position_set = position_set\n", "\n", "my_basket.get_details() # we highly recommend verifying the basket state looks correct before calling create!" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "# Publish basket\n", "my_basket.create()\n", "my_basket.poll_status(timeout=10000, step=20) # optional: constantly checks create status until report succeeds, fails, or the poll times out (this example checks every 20 seconds for 2 minutes)\n", "my_basket.get_url() # will return a url to your Marquee basket page ex. https://marquee.gs.com/s/products/MA9B9TEMQ2RW16K9/summary\n", "\n", "\n", "\n", "\n", "\n" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/content/made_with_gs_quant/1-Navigating Rates.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Navigating Rates" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Summary\n", "\n", "Following the spike in USD swaption implied vol last summer, vols have come down materially. When we dissect the moves of the vol surface over the past 2 years, we find that:\n", "\n", "* Vols on 1y tails are back at lows while vols on 10y tails haven’t fully retraced from last summer's spike. \n", "* Skew on 1y tails remains bid for receivers (and payers trade close to flat vs. ATM) while payer skew on 10y tails has richened translating into a flat skew but very high smile (both receiver and payer skews are high). \n", "\n", "Given these findings, we look at a hypothetical scenario analysis of a short 3m10y strangle over time. \n", "\n", "\n", "The content of this notebook is split into:\n", "* [1 - Let's get started with gs quant](#1---Let's-get-started-with-gs_quant)\n", "* [2 - Implied volatility in historical context](#2---Implied-volatility-in-historical-context)\n", "* [3 - Skew](#3---Skew)\n", "* [4 - Potential structures](#4---Potential-structures)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1 - Let's get started with gs_quant" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Start every session with authenticating with your unique client id and secret. If you don't have a registered app, create one [here](https://marquee.gs.com/s/developer/myapps/register). `run_analytics` scope is required for the functionality covered in this example. Below produced using gs-quant version 0.8.95." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',)) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2 - Implied volatility in historical context" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's start by looking at the implied volatility of 1y and 10y payer and receiver swaptions expiring at 3m.\n", "\n", "To do that, let's first create the 8 swaptions (2 types x 2 expiries x 2 strikes) and put them in a portfolio. " ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.common import Currency, PayReceive\n", "from gs_quant.instrument import IRSwaption\n", "\n", "currency = Currency.USD\n", "expiry = '3m'\n", "notional = 1e8\n", "\n", "tenors = ('1y', '10y')\n", "r_strikes = ('ATMF', 'ATMF-50')\n", "p_strikes = ('ATMF', 'ATMF+50')\n", "\n", "receivers = [IRSwaption(PayReceive.Receive, tenor, currency, expiration_date=expiry, strike=strike, notional_amount=notional, name='{},{},{}'.format('R',tenor,strike)) for strike in r_strikes for tenor in tenors]\n", "payers = [IRSwaption(PayReceive.Pay, tenor, currency, expiration_date=expiry, strike=strike, notional_amount=notional, name='{},{},{}'.format('P',tenor,strike)) for strike in p_strikes for tenor in tenors]\n", "\n", "swaptions = payers + receivers\n", "port = Portfolio(swaptions)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's now calculate annualized implied volatility for the last two years looking at month ends as well as the most recent date." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets import HistoricalPricingContext\n", "from gs_quant.risk import IRAnnualImpliedVol\n", "import pandas as pd\n", "import datetime as dt\n", "\n", "dates = pd.date_range('2018-01-01', '2020-01-01', freq='BM').date.tolist()\n", "dates.append(dt.date(2020,1,24))\n", "\n", "with HistoricalPricingContext(dates=dates): \n", " vols = port.calc(IRAnnualImpliedVol)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now construct a dataframe with the results and plot it." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import matplotlib.pyplot as plt\n", "\n", "d = pd.DataFrame({s.name: vols[s.name] for s in swaptions})\n", "d.plot(figsize=(10,6))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we can see in the charts above, implied vol for both 1y and 10y tenors has come in since 2019 highs but significantly more so for 1y tenor than 10y." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3 - Skew \n", "\n", "Let's now use the same data to take a look at skew." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "receiverSkew3m10y = d['R,10y,ATMF-50'] - d['R,10y,ATMF']\n", "payerSkew3m10y = d['P,10y,ATMF+50'] - d['P,10y,ATMF']\n", "\n", "receiverSkew3m1y = d['R,1y,ATMF-50'] - d['R,1y,ATMF']\n", "payerSkew3m1y = d['P,1y,ATMF+50'] - d['P,1y,ATMF']" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAlIAAAFlCAYAAAAgSAb7AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8li6FKAAAgAElEQVR4nOzdd1yX5f7H8dfNBkEQBBwowy2uXDgSV0rTHGmamWZlWadO1umchud0+jVPe52yOqanccqGaaXhyNyCSrkHCu6BCIoo+8v9++Mm0nIicH+B9/Px8MF33vcHRXhzXZ/7ugzTNBERERGRS+didwEiIiIiVZWClIiIiEgZKUiJiIiIlJGClIiIiEgZKUiJiIiIlJGClIiIiEgZudlx0rp165oRERF2nFpERETkkiQlJR01TTP4bM/ZEqQiIiJYu3atHacWERERuSSGYew513Oa2hMREREpIwUpERERkTJSkBIREREpI1t6pM6msLCQ/fv3k5eXZ3cp4uS8vLwICwvD3d3d7lJERKSGc5ogtX//fvz8/IiIiMAwDLvLESdlmiYZGRns37+fyMhIu8sREZEazmmm9vLy8ggKClKIkvMyDIOgoCCNXIqIiFNwmiAFKETJRdHXiYiIOAunClJ2ysvLo2vXrrRv357o6GiefPLJSz7G+PHjCQkJoU2bNpf83nHjxhEZGUmHDh1o3749P/744yUf40J69OhR7scE+PDDD2nbti3t2rWjTZs2zJ49G4A+ffpovTAREanWFKRKeHp6smjRItavX8+6deuIj48nISHhko4xbtw44uPjy1zDSy+9xLp163j99de55557ynycc1m5cuVlH8PhcJxxf//+/Tz77LMsX76cDRs2kJCQQLt27S77PCIiIlWBglQJwzDw9fUFrCsICwsLS6eQ+vTpw6RJk4iNjaVVq1asWbOGoUOH0qxZMyZPnlx6jNjYWAIDA884bkpKCh07diy9v2PHDjp16nTeWrp3786BAwdK7yclJdG7d286depEXFwchw4dAmDnzp1cddVVtG/fno4dO5KSkgJYgaxLly60a9fujJG1Xz+/m2++mblz55Y+Pm7cOL7++mscDgePPPJI6Xvfe+89ABYvXkzfvn255ZZbaNu27Rm1HjlyBD8/v9Jj+/r6/qEJvLi4mLFjx5b+Xc2fP5/u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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plt.figure(figsize=(10,6))\n", "plt.plot(receiverSkew3m10y*10000,label='3m10y Receiver Skew')\n", "plt.plot(payerSkew3m10y*10000,label='3m10y Payer Skew')\n", "plt.legend(loc=\"upper left\")\n", "plt.show()\n", "\n", "plt.figure(figsize=(10,6))\n", "plt.plot(receiverSkew3m1y*10000, label='3m1y Receiver Skew')\n", "plt.plot(payerSkew3m1y*10000, label='3m1y Payer Skew')\n", "plt.legend(loc=\"upper left\")\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the charts above we can see that 3m10y payer and receiver skew remains high but the spread between them has tightened. For the 1y tenor, however, the payer skew has flattened while receiver skew remains rich in the context of the last 2 years. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4 - Potential structures\n", "\n", "Let's now look at a 3m10y F+/-25 strangle to fade the steep vol smile. \n", "\n", "For this exercise, we will age it in a roll to spot scenario. " ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "strangles = {}\n", "labels = ('Inception', 'After 1m', 'After 2m', 'After 3m')\n", "expiries = ('3m', '2m', '1m', '1d')\n", "\n", "for label, expiry in zip(labels, expiries):\n", " R = IRSwaption('Receive', '10y', 'USD', expiration_date=expiry, strike='ATMF-25', notional_amount=1e8)\n", " P = IRSwaption('Pay', '10y', 'USD', expiration_date=expiry, strike='ATMF+25', notional_amount=1e8)\n", " strangle = Portfolio((R, P))\n", " strangle.resolve()\n", " strangles[label] = strangle" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's now construct a set of spot shocks and examine the impact on price accross the different expiries." ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [], "source": [ "from gs_quant.risk import MarketDataShockBasedScenario, MarketDataPattern, MarketDataShock, MarketDataShockType\n", "from gs_quant.markets import PricingContext\n", "from gs_quant.risk import IRVegaParallel, Price\n", "from collections import defaultdict\n", "\n", "shocks = [x for x in range(-50, 55, 5)]\n", "results = defaultdict(dict)\n", "\n", "with PricingContext():\n", " for expiry, strangle in strangles.items():\n", " for shock in shocks:\n", " ir_spot_scenario = MarketDataShockBasedScenario(shocks={\n", " MarketDataPattern('IR', 'USD'): MarketDataShock(MarketDataShockType.Absolute, -shock / 10000),\n", " MarketDataPattern('IR Reset', 'USD'): MarketDataShock(MarketDataShockType.Absolute, shock / 10000)})\n", "\n", " with ir_spot_scenario:\n", " results[expiry].update({shock:strangle.calc((Price, IRVegaParallel))})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now roll up the visualize the results:" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "p_res = {k:{shock:-x[Price].aggregate() for shock, x in v.items()} for k, v in results.items()}\n", "pd.DataFrame(p_res).plot(figsize=(10,6), title='Price by Shock')" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "v_res = {k:{shock:-x[IRVegaParallel].aggregate() for shock, x in v.items()} for k, v in results.items()}\n", "pd.DataFrame(v_res).plot(figsize=(10,6), title='Vega by Shock')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally let's look at our breakevens as we move from inception to expiry." ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [], "source": [ "df = pd.DataFrame(p_res).reindex(range(-50,50)).interpolate()\n", "breakeven = df.loc[0]['Inception']" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [], "source": [ "from gs_quant.risk import IRSpotRate\n", "spot = strangles['Inception'][0].calc(IRSpotRate) * 100" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [], "source": [ "top = pd.Series(dtype=float)\n", "bottom = pd.Series(dtype=float)\n", "\n", "for column in df.columns:\n", " sl = df.iloc[(df[column]-breakeven).abs().argsort()[:2]]\n", " a, b = sl[column].index\n", " top.at[column] = a if a >= 0 else b\n", " bottom.at[column] = a if a < 0 else b" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "(top/100 + spot).reindex(labels).plot(figsize=(10,6))\n", "(bottom/100 + spot).reindex(labels).plot(figsize=(10,6))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Please reach out to `gs-quant@gs.com` with any questions." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Disclaimer\n", "This website may contain links to websites and the content of third parties (\"Third Party Content\"). We do not monitor, review or update, and do not have any control over, any Third Party Content or third party websites. We make no representation, warranty or guarantee as to the accuracy, completeness, timeliness or reliability of any Third Party Content and are not responsible for any loss or damage of any sort resulting from the use of, or for any failure of, products or services provided at or from a third party resource. If you use these links and the Third Party Content, you acknowledge that you are doing so entirely at your own risk." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/made_with_gs_quant/10-Explaining Performance Drivers.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Explaining Performance Drivers\n", "### Summary \n", "\n", "In this note I team up Maxim Fedotov from Rates Structuring Strats to take a closer look at hedging a popular bond trade and present a new framework for explaining pnl that can be applied across any trade or portfolio in `gs_quant`. Traders, PMs, risk managers or operationally oriented users can deploy this framework to improve their understanding of historical performance drivers and in turn drive better hedging, risk management and cash management decisions.\n", "\n", "While foreign fixed income assets can present attractive investment opportunities, domestic investors may need to use overlays if they want to receive domestic currency. One popular example of this has been buying JGBs and using a fixfix swap to receive in local. In this bond + swap package, FX of the swap will fully match the bond FX component but clients can still face pnl volatility due to basis and IR differences not matched by the bond accounted at cost.\n", "\n", "In this notebook I will take a closer look at the fixfix swap tailored for a specific JGB bond and decompose its historical pnl drivers into rates, cross currency, fx and cashflow components to better understand the drivers of this volatility.\n", "\n", "\n", "The content of this notebook is split into:\n", "* [1 - Let's get started with gs quant](#1---Let's-get-started-with-gs-quant)\n", "* [2 - Model bond as FixFix swap](#2---Model-bond-as-fixfix-swap)\n", "* [3 - Attribute pnl and calculate cashflows](#3---Attribute-pnl-and-calculate-cashflows)\n", "* [4 - Putting it all together](#4---Putting-it-all-together)\n", "* [What's New](#What's-New)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1 - Let's get started with gs quant\n", "Start every session with authenticating with your unique client id and secret. If you don't have a registered app, create one [here](https://marquee.gs.com/s/developer/myapps/register). `run_analytics` scope is required for the risk functionality and `read_product_data` is required for pulling data covered in this example. Below produced using gs-quant version 0.8.155." ] }, { "cell_type": "code", "execution_count": 118, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics', 'read_product_data')) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2 - Model bond as fixfix swap\n", "\n", "Let's pick a JGB bond to analyze - in this example we will look at `JGB #53 JP1300531GC0` (ISIN) and fill in the relative details. Note here we are manually inputting the details but we'll remove this step once you're able to model bonds in gs quant directly." ] }, { "cell_type": "code", "execution_count": 119, "metadata": {}, "outputs": [], "source": [ "from datetime import date\n", "\n", "bond_notional = 1e8\n", "bond_coupon = 0.006\n", "coupon_freq = '6m'\n", "bond_maturity = date(2046, 12, 20)\n", "last_cpn_date = date(2018, 12, 20) # Last paid coupon date as seen from backtest start date\n", "maturity = bond_maturity\n", "bond_dirty_price = 97.66\n", "\n", "# historical window we'll examine\n", "start_date = date(2019, 1, 2)\n", "end_date = date(2019, 11, 1)\n", "CSA = 'EUR-OIS'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, in order to receive payments in local - let's say that is EUR rather than JPY - we can structure a `IRXccySwapFixFix` swap that matches our bond's characteristics outlined above.\n", "\n", "To do this, we need to size it to the bond notional using the EUR/JPY FX rate at the start of our window. Let's pull it from [Marquee data catalogue](https://marquee.gs.com/s/discover/data-services/catalog) as a first step." ] }, { "cell_type": "code", "execution_count": 120, "metadata": {}, "outputs": [], "source": [ "from gs_quant.data import Dataset\n", "\n", "ds = Dataset('FXSPOT_STANDARD')\n", "eurjpy_data = ds.get_data(start_date, end_date, bbid='JPYEUR')\n", "fx_rate = eurjpy_data.loc[start_date].spot" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With the FX spot as of `start_date` in hand, let's define our swap and `resolve()` to fix any relative parameters as of the same `start date`. We can use `as_dict()` to view what these are. I'll also calculate swap cashflows here that we will use later to add to the total pv we're attributing. Note below we can choose between par or proceeds asset swap format that impact the fixed notional and fee used." ] }, { "cell_type": "code", "execution_count": 121, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'termination_date': datetime.date(2046, 12, 20),\n", " 'payer_day_count_fraction': ACT/ACT ISDA,\n", " 'receiver_rate': 0.01795441569651901,\n", " 'fee': 0.0,\n", " 'fee_currency': JPY,\n", " 'payer_rate': 0.006,\n", " 'type': XccySwapFixFix,\n", " 'receiver_business_day_convention': Modified Following,\n", " 'payer_frequency': '6m',\n", " 'notional_amount': 100000000.0,\n", " 'receiver_frequency': '1y',\n", " 'principal_exchange': Last,\n", " 'asset_class': Rates,\n", " 'receiver_day_count_fraction': 30/360,\n", " 'receiver_currency': EUR,\n", " 'effective_date': datetime.date(2018, 12, 20),\n", " 'fee_payment_date': datetime.date(2019, 1, 7),\n", " 'receiver_notional_amount': 788340.8179999999,\n", " 'payer_currency': JPY,\n", " 'payer_business_day_convention': Modified Following}" ] }, "execution_count": 121, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from gs_quant.markets import PricingContext\n", "from gs_quant.instrument import IRXccySwapFixFix\n", "from gs_quant import risk\n", "\n", "ASWType = 'Proceeds' # Can either be par or proceeds\n", "fixed_notional = bond_notional * fx_rate * bond_dirty_price / 100 if ASWType=='Proceeds' else bond_notional * fx_rate\n", "fee = 0 if ASWType=='Proceeds' else -(bond_dirty_price - 100) / 100 * bond_notional\n", "\n", "swap = IRXccySwapFixFix(effective_date=last_cpn_date, termination_date=bond_maturity, notional_amount=bond_notional, \n", " payer_currency='JPY', receiver_notional_amount=fixed_notional, payer_rate=bond_coupon,\n", " receiver_currency='EUR', payer_frequency=coupon_freq, receiver_frequency='1y',\n", " payer_day_count_fraction='act/act ISDA', fee=fee, receiver_rate='ATM')\n", " \n", "with PricingContext(pricing_date=start_date, market_data_location='LDN', csa_term=CSA):\n", " swap.resolve()\n", " cf = swap.calc(risk.Cashflows)\n", "swap.as_dict()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3 - Attribute pnl and calculate cashflows\n", "\n", "Now, let's break out our swap pv into contributions from various drivers - to do this, we'll use a newly minted gs-quant analytic called `PnlExplain` which attributes the change in value of an individual trade (or portfolio) to market moves. Note the values returned are in USD. In the below, I look at `PnLExplain` every day to get a daily attribution but you can use this for any time period. I also group by`mkt_type` for clarity but you can use the measure to get a more granular view by removing the grouping. You'll notice a `CROSSES` PnL, which represents the cross-effects among the other types." ] }, { "cell_type": "code", "execution_count": 122, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets import CloseMarket\n", "\n", "result_dict = {}\n", "for d in eurjpy_data.index:\n", " exp_measure = risk.PnlExplain(CloseMarket(date=d.date()))\n", " with PricingContext(pricing_date=start_date, market_data_location='LDN', is_async=True, csa_term=CSA):\n", " exp_res = swap.calc(exp_measure)\n", " result_dict[d.date()] = exp_res" ] }, { "cell_type": "code", "execution_count": 144, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
mkt_typeCROSSESFXFX FWDIRIR BASISIR CCIR XC
date
2019-01-0336.621530-11980.63919125.769065-1120.076874-20.614371-0.1034772672.806140
2019-01-04-108.035684-5073.44524810.048568-30625.813539350.301729-0.0984693844.754112
2019-01-07-115.6540681844.80506812.789388-20658.964027951.194210-0.0053723189.912983
2019-01-08-89.085150953.0320558.299623-19361.553933-592.2762100.0027934873.192840
2019-01-09-230.0154294820.73888317.457522-16422.007642-607.153929-0.0204741183.107531
\n", "
" ], "text/plain": [ "mkt_type CROSSES FX FX FWD IR IR BASIS \\\n", "date \n", "2019-01-03 36.621530 -11980.639191 25.769065 -1120.076874 -20.614371 \n", "2019-01-04 -108.035684 -5073.445248 10.048568 -30625.813539 350.301729 \n", "2019-01-07 -115.654068 1844.805068 12.789388 -20658.964027 951.194210 \n", "2019-01-08 -89.085150 953.032055 8.299623 -19361.553933 -592.276210 \n", "2019-01-09 -230.015429 4820.738883 17.457522 -16422.007642 -607.153929 \n", "\n", "mkt_type IR CC IR XC \n", "date \n", "2019-01-03 -0.103477 2672.806140 \n", "2019-01-04 -0.098469 3844.754112 \n", "2019-01-07 -0.005372 3189.912983 \n", "2019-01-08 0.002793 4873.192840 \n", "2019-01-09 -0.020474 1183.107531 " ] }, "execution_count": 144, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def format_res(r, d):\n", " # here we group and sum by market type - you can get a more granular view when skipping this step\n", " df = r.groupby('mkt_type').sum().reset_index()\n", " df['date'] = d\n", " return df.set_index('date')\n", "\n", "result_clean = pd.concat([format_res(r.result(), d) for d, r in result_dict.items() if len(r.result())])\n", "result = result_clean.groupby(['date', 'mkt_type']).sum().unstack()\n", "result.columns = result.columns.droplevel(0)\n", "result.loc[start_date] = 0 # first day is 0 pnl\n", "result.head() # let's take a peak!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now for the final bit - cashflows. We have already calculated our cashflows in [step 2](#2---Model-bond-as-fixfix-swap). Let's remove anything that's later than our `end_date` and convert the cashflow amount into USD." ] }, { "cell_type": "code", "execution_count": 145, "metadata": {}, "outputs": [], "source": [ "import pandas as pd \n", "\n", "cfs = cf.result()\n", "cfs = cfs[cfs['payment_date'] < end_date]\n", "cash = pd.Series(cfs.payment_amount.values, index=cfs.payment_date)\n", "cash = (cash * eurjpy_data.spot).fillna(0)\n", "result['CASH'] = cash" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4 - Putting it all together" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, with all the results in hand, let's take a look!" ] }, { "cell_type": "code", "execution_count": 146, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 146, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAtsAAAHwCAYAAAB386PAAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8li6FKAAAgAElEQVR4nOzdeXzU1dX48c+dLXvIAgRIIAHEsCQQJKAoVhQFFwREUdEWkFqrtbVqRVv7vKpPrYjWnxW3Pm5PtVqglIpFK+6goBYkj2ACATWQSCAJkD2ZJLPd3x+zkJCFLJPMJDnv1ysvM9/1fmOAM2fOPVdprRFCCCGEEEL4nyHQAxBCCCGEEKKvkmBbCCGEEEKIbiLBthBCCCGEEN1Egm0hhBBCCCG6iQTbQgghhBBCdBMJtoUQQgghhOgmEmwLIYQ4LaXUjUqp9wM9DiGE6G0k2BZCiCChlMpXStUppWqUUiVKqb8opSLbcV6KUkorpUztOPYVpZRNKVXt+cpRSj2ilBrQ1nla679prWd35HmEEEJIsC2EEMHmSq11JHAWMBX4r264x2Na6yhgEHATcA7wmVIqoqWD2xPEd1Z3XlsIIYKBBNtCCBGEtNZHgM1AGoBSaqtS6iGl1GeejPT7SqmBXbxHvdb6S2AeEI878EYptcxznz8ppcqABz3btnv2/49S6vHG11JK/Uspdbfn+2FKqX8qpY4rpQ4ppe5odNyDSqkNSqnXlVJVwDKl1DSl1C6lVJUno/9EV55LCCGCiQTbQggRhJRSw4HLga8abb4Bd0A8GLAA9/jjXlrrauAD4PxGm88GDnru9fApp6wBrlNKKc9YY4HZwDqllAF4C9gDJAKzgDuVUnManT8f2ADEAH8DVgOrtdbRwGhgvT+eSwghgoEE20IIEVzeVEpVANuBT4CVjfb9RWv9jda6DndAmuHH+x4F4hq/1lo/rbV2eO7X2DZAczI4vwb4Qmt9FHfpyyCt9e+11jat9UHgReD6Rud/obV+U2vt8lzbDpyhlBqota7RWv/Hj88lhBABJcG2EEIElwVa6xitdbLW+menBLrFjb63AqedPNkBiUBZo9eHWztQa62BdcBiz6YbcGeoAZKBYUqpCu8XcD+Q0Ma1fwycCexXSn2plJrb+ccQQojgIhNThBCin/N0PLmYpuUi+jSnrQXeV0qtwl1ycpVn+2HgkNZ6TBvnNrm21vpbYLGnBGUhsEEpFa+1ru3AYwghRFCSzLYQQvQdIUqp0EZfbf4dr5QKUUpNAd4EyoG/tPdGWuuvgOPAS8B7WusKz66dQJVS6j6lVJhSyqiUSlNKTW1jHD9USg3SWrsA73Wc7R2LEEIEMwm2hRCi76gB6hp9XdTKcfcqpapxl438FcgCzu1EJnkt7oz4Gu8GrbUTuBJ3Pfkh4ATugLytPt6XAnuVUjW4J0ter7Wu7+BYhBAiKCl36Z0QQgghhBDC3ySzLYQQQgghRDeRYFsIIYQQQohuIsG2EEIIIYQQ3USCbSGEEEIIIbqJBNtCCCGEEEJ0kz67qM3AgQN1SkpKoIchhBBCCCH6uKysrBNa60Et7euzwXZKSgq7du0K9DCEEEIIIUQfp5QqaG2flJEIIYQQQgjRTSTYFkIIIYQQoptIsC2EEEIIIUQ36bM12y2x2+0UFhZSX18f6KH0aqGhoSQlJWE2mwM9FCGEEEKIoNavgu3CwkKioqJISUlBKRXo4fRKWmtKS0spLCxk5MiRgR6OEEIIIURQ61dlJPX19cTHx0ug3QVKKeLj4+XTASGEEEKIduhXwTYggbYfyM9QCCGEEKJ9+l2wLYQQQgghRE+RYLsbPfjggzz++OPNtr/yyiscPXq0zXOffPJJrFZrdw1NCCGEEEL0AAm2A0CCbSGEEEKI/kGC7U7Kz89n7Nix3HzzzaSlpXHjjTfy4Ycfct555zFmzBh27tzZ5PgXX3yRyy67jNdee41du3Zx4403kpGRQV1dXbNrP/XUUxw9epQLL7yQCy+8kJdffpm77rqrybXuvvtu3xiWLl3KxIkTueaaa3wBelZWFhdccAFTpkxhzpw5FBUVde8PRAghhBBCNNPuYFspNVwptUUplauU2quU+qVne5xS6gOl1Lee/8Y2Ouc3SqnvlFIHlFJzGm2fopTK9ux7Snlm3CmlQpRSf/ds36GUSml0zlLPPb5VSi31x8N31Xfffccvf/lLvv76a/bv38+aNWvYvn07jz/+OCtXrvQd98wzz/DWW2/x5ptv8qMf/YjMzEz+9re/sXv3bsLCwppd94477mDYsGFs2bKFLVu2cP3117Np0ybsdjsAf/nLX7jpppsAOHDgALfccgtff/010dHRPPfcc9jtdn7xi1+wYcMGsrKyWL58Ob/97W975ocihBBCCCF8OpLZdgC/0lqPA84BbldKjQd+DXyktR4DfOR5jWff9cAE4FLgOaWU0XOtPwO3AGM8X5d6tv8YKNdanwH8CXjUc6044AHgbGAa8EDjoD5QRo4cSXp6OgaDgQkTJjBr1iyUUqSnp5Ofnw/Aa6+9xubNm/nnP/9JSEhIp+4TERHBRRddxNtvv83+/fux2+2kp6cDMHz4cM477zwAfvjDH7J9+3YOHDhATk4Ol1xyCRkZGfzhD3+gsLDQL88shBBCCCHar92L2miti4Aiz/fVSqlcIBGYD8z0HPYqsBW4z7N9nda6ATiklPoOmKaUygeitdZfACil/gosADZ7znnQc60NwDOerPcc4AOtdZnnnA9wB+hrO/PQ/tI4eDYYDL7XBoMBh8MBQFpaGrt37+7yIjA333wzK1euZOzYsb6sNjRvw6eUQmvNhAkT+OKLLzp9PyGEEEII0XWdqtn2lHdMBnYACZ5A3BuQD/YclggcbnRaoWdbouf7U7c3OUdr7QAqgfg2rnXquG5RSu1SSu06fvx4Zx7N7yZPnszzzz/PvHnzfJMio6KiqK6ubvO8U485++yzOXz4MGvWrGHx4sW+7d9//70vqF67di0zZswgNTWV48eP+7bb7Xb27t3r70cTQgghhBCn0eFgWykVCfwTuFNrXdXWoS1s021s7+w5Jzdo/YLWOlNrnTlo0KA2htazZsyYweOPP84VV1zBiRMnWLZsGbfeemurEyQBbrnlFi677DIuvPBC37Zrr72W8847j9jYkxU048aN49VXX2XixImUlZVx2223YbFY2LBhA/fddx+TJk0iIyODzz//vNufUwghhBBCNKW0bhaztn6wUmbgbeA9rfUTnm0HgJla6yKl1FBgq9Y6VSn1GwCt9SOe497DXSKSD2zRWo/1bF/sOf+n3mO01l8opUxAMTAId+33TK31Tz3nPO+5T6tlJJmZmXrXrl1NtuXm5jJu3Lh2P2+wmTt3LnfddRezZs0C3B1R5s6dS05OTo+Ppbf/LIUQQggh/EUplaW1zmxpX0e6kSjgZSDXG2h7bAK83UGWAv9qtP16T4eRkbgnQu70lJpUK6XO8VxzySnneK91DfCxdr8beA+YrZSK9UyMnO3Z1i9UVFRw5plnEhYW5gu0hRBCiN7g+5/+lJJHVgV6GEIETLsnSALnAT8CspVSuz3b7gdWAeuVUj8GvgcWAWit9yql1gP7cHcyuV1r7fScdxvwChCGe2LkZs/2l4HXPJMpy3BntNFalymlHgK+9Bz3e+9kyd7uqquu4tChQ022Pfroo8yZ4+uUSExMDN98802zc1NSUgKS1RZCCCHaQ2uNdeeXOEv7xD/ZQnRKR7qRbKfl2mmAFtOtWuuHgYdb2L4LSGthez2eYL2Fff8L/G97x9tbbNy4MdBDEEIIIbqF49gxdF0dtoICtNbNOmgJ0R/ICpJCCCGE6Ba2/AIAXNXVOCsqAjwaIQJDgm0hhBBCdAtbQb7ve3tBQeAGIkQASbAthBBCiG7hzWwD2CTYFv2UBNsBUFxczPXXX8/o0aMZP348l19+Od988w1hYWFkZGQwfvx4lixZgt1u952zfft2pk2bxtixYxk7diwvvPCCb9+BAweYOXMmGRkZjBs3jltuuQUAq9XKjTfeSHp6OmlpacyYMYOamhoAjEYjGRkZvq9Vq9wzxd9++20mT57MpEmTGD9+PM8//3wP/mSEEEL0JbaCAszJI8BgwFbwfaCHI0RAdKQbifADrTVXXXUVS5cuZd26dQDs3r2bkpISRo8eze7du3E6nVxyySWsX7+eG2+8keLiYm644QbefPNNzjrrLE6cOMGcOXNITEzkiiuu4I477uCuu+5i/vz5AGRnZwOwevVqEhISfK8PHDiA2WwGICwsjN27dzcZm91u55ZbbmHnzp0kJSXR0NBAfn5+D/1khBBC9DW2gnxCxowBhxPb9xJsi/5JMts9bMuWLZjNZm699VbftoyMDIYPH+57bTQamTZtGkeOHAHg2WefZdmyZZx11lkADBw4kMcee8yXjS4qKiIpKcl3fnp6um97YuLJVe1TU1MJCQlpdWzV1dU4HA7i4+MBCAkJITU1tauPLIQQoh/STif2gu+xJCdjSR4hZSSi3+q3me3/fmsv+462tdp8x40fFs0DV05o85icnBymTJnS5jH19fXs2LGD1atXA7B3716WLl3a5JjMzEz27t0LwF133cVFF13Eueeey+zZs7npppuIiYlh+fLlzJ49mw0bNjBr1iyWLl3KmDFjAKirqyMjI8N3vd/85jdcd911zJs3j+TkZGbNmsXcuXNZvHgxBoO8JxNCCNEx9qJitN2OJTkZV20tdZvfDfSQhAgIiaKCSF5eHhkZGcTHxzNixAgmTpwI0GpvUu+2m266idzcXBYtWsTWrVs555xzaGhoICMjg4MHD7JixQrKysqYOnUqubm5wMkyEu/XddddB8BLL73ERx99xLRp03j88cdZvnx5Dz29EEKIvsTmKUO0pKRgSU7BVVmJo7w8sIMSIgD6bWb7dBno7jJhwgQ2bNjQ4j5vzXZRUREzZ85k06ZNzJs3jwkTJrBr1y7mzZvnOzYrK4vx48f7Xg8bNozly5ezfPly0tLSfBn0yMhIFi5cyMKFCzEYDLzzzjuMGzeuzTGmp6eTnp7Oj370I0aOHMkrr7zil2cXQgjRf3jb/lmSU3B5Jufbv/8eU2xsAEclRM+TzHYPu+iii2hoaODFF1/0bfvyyy8paFTLNnToUFatWsUjjzwCwO23384rr7zim9BYWlrKfffdx7333gvAu+++6+tcUlxcTGlpKYmJiXz22WeUe7IINpuNffv2kZyc3OrYampq2Lp1q+/17t272zxeCCGEaI2toAAVHo5p8CAsI0a4t8kkSdEPSbDdw5RSbNy4kQ8++IDRo0czYcIEHnzwQYYNG9bkuAULFmC1Wtm2bRtDhw7l9ddf5yc/+Qljx47l3HPPZfny5Vx55ZUAvP/++6SlpTFp0iTmzJnDH//4R4YMGUJeXh4XXHAB6enpTJ48mczMTK6++mrgZM229+vXv/41Wmsee+wxUlNTycjI4IEHHpCsthBCiE6x5edjGTECpRTm4cNBqSZ9t0X71Ofmcuzxx9FaB3ooopNUX/2fl5mZqXft2tVkW25u7mlLKET7yM9SCCFEW/KumEvIqJEkPf00AN9edBHhUzJJ/ONjAR5Z73LknhVUvf02Y774XEpwgphSKktrndnSPslsCyGEEMLvXLW1GKKifa8tycnS/q+DtN1OzaefAuAsLQ3waERnSbAthBBCCL9z1dVhCA/3vbaMSMYuwXaHWP/vK1xV7jbFjhMSbPdWEmwLIYQQwu9cViuGsDDfa8uIETgrK3FWVARwVL1LzZYtvu8dpScCOBLRFRJsCyGEEMKvtM0GdjuGiEaZ7RR3dyvpSNI+Wmuqt3xMqGfNDSkj6b0k2BZCCCGEX7nq6gCaZbYBbAUSbLeH7dAh7AXfM2DBfDCZpIykF5NgWwghhBB+5Q22VaOabfPw4QDYvpe67fao3bYNgKgLL8QUFydlJL2YBNs9zGg0NulvnZ+fzxtvvMGsWbN8x2zfvp2MjAwcDkcARyqEEEJ0jstqBcAQdjLYNoSGYho6VDqStJO9uAQVFoZ56FCMA+NxSma71+q3y7UHSlhYmG8lSK+UlBRefvll1qxZw7XXXsvPfvYz/ud//geTSf73CCGE6H1cVk8ZSaPMNrhLSexSRtIuzooKjDExAJjiB+KQmu1eS6K5IPH0009z8cUXs3fvXqZOncq5554b6CEJIYQQneKy1gJgCA9rst2SnEz1++/79V4NeXloh5PQ1DP9et1Ac5aXY4z1BtvxNHz3XYBHJDqr/wbbm38Nxdn+veaQdLhsVZuHeJdJBxg5ciQbN24EYNSoUVx33XU888wz5OXl+XdcQgghRA/SLUyQBLAkj8BZUYGzshLjgAF+uVfJqkdxVlYycv3f/XK9YOEsL8fkzWwPjMd54gRaa5RSAR6Z6Kj+G2wHSEtlJAAul4sPP/yQyMhICgoKGDhwYABGJ4QQQnSdr2b7lDISs7cjyfeHCUv3T7DtrKjAceyYX64VTJwVFZgTEwEwxg9E2+24qqsxRkef5kwRbPpvsH2aDHRPe/bZZ0lLS+Ohhx7i9ttv54svvpB3r0IIIXolb822CjulZjvZ02u7oICw9DT/3Ku6GkdpKdrlQhn6Tt8HR+Oa7YHx7m0nSiXY7oX6zm9lL1ZcXMwTTzzBY489xqWXXkpiYiIvvfRSoIclhBBCdIovsx1xSrDdDe3/nLU14HDgrKz02zUDTTscuKqqMMbGAu6abQCntP/rlSTYDgJ333039957L4MGDQLgySef5OGHH6asrCzAIxNCCCE6rqVFbbyvTQkJ2P3Y/s9V456M6TzRdwJRZ1UVaO3LbBvj3aWlbXUkcdXWUrP9sx4Zn+iY/ltGEiA1NTXNtq1Zs6bJ6+HDh5Ofn99DIxJCCCH8y2WtBYMBFRLSbJ8lOdlvq0hqh8M3GdNRWkrImDF+uW6gOSsqAE5mthuVkbTmxJ//TOlLL3PGlo8xDx3a/YMU7SaZbSGEEEL4la6rwxAW1uLcI0vyCGzf+yfYdjVKYDmO96HMdnk5wMnMdkwMGAytriKpnU4qN70FQH3u/p4ZpGg3CbaFEEII4Vcuq7VZJxIvS3IyzrIynNXVXb6P01NCAvSp5cx9wbanz7YyGjHGxbW6imTtF//xdWRpOCDBdrCRYFsIIYQQfuWy1qFOWdDGy9f+zw+lJK7ak5ntPlWz7SkjMXnKSMA9SbK1mu3Kf/0LQ3Q05mHDJLMdhCTYFkIIIYRfuTPbES3u87b/s/uhI4mrUXa8rXrm3sZxShkJeILtFt5QOGtqqf7gA6Ivu4zQtDTqJbMddCTYFkIIIYRfuTw12y3xtf/zQ0cSp7dm22RqMRDtrZwVFaiQEFSjn6HRs4rkqarfew9dX8+ABfMJHTcW+/eHm5TXiMCTYFsIIYQQftVWzbYhPBzT4MH+KSPxBJWWxMQ22+L1Ns5y94I2jSeYmuIHuhfv0brJsZX/+hfm5BGEZWQQkjoWtKbhm296esiiDRJs9zCj0UhGRobvKz8/nzfeeINZs2b5jtm+fTsZGRk4HI4m527dupUBAwb4zr344oupqKggPj7e94fPu/JkYWEhAJWVlcTFxeFyuVi2bBkjR45k0qRJnHnmmSxZsoQjR4703MMLIYToF3SdtdXMNoBlxAi/ZLZdNe4yEktKCo4Tx7t8vWDhrKjwtf3zMiUMRjc04Gz0psJ+5AjWnTsZMH8+SilCx6YCMkky2Eiw3cPCwsLYvXu37yslJYWFCxcSGhrKmjVrcDgc/OxnP+O5557DZGreBv3888/3nfvhhx8SExPDkCFDyM3NBeDzzz9n8uTJfP755wD85z//4eyzz8bgWcL2j3/8I3v27OHAgQNMnjyZCy+8EJvN1nM/ACGEEH2eq7b1zDaAOSXZL+3/vK3/LCkpOMvK0U5nl68ZDJzl5U3qtQHC0tzL29dlZ/u2VW7aBMCAefMBMA0diiE6mvr9B3popKI9JNgOEk8//TT/9V//xQMPPMDUqVM599xz233ueeed5wuuP//8c+66664mr1u6llKKu+66iyFDhrB582b/PIQQQgiBu2a7tW4kAJYRyThLS3HW1NCQl4fN82lsRzlrasBoxJyUBE6nr4tHb+csL/e1/fMKnTABjEbq9uwBQGtN5Zv/InzqVCxJiQCe7PZYGvZLZjuY9NsVJB/d+Sj7y/z7yzg2biz3TbuvzWPq6urIyMgAYOTIkWzcuBGAUaNGcd111/HMM8+Ql5fX6vnbtm3znb9o0SJ++9vfcu655/Lpp59y8803c/DgQRYtWsTzzz8PuIPt3/zmN61e76yzzmL//v3Mnz+/Q88qhBBCtKatmm042ZGkft8+jtx5F6FpExjxwgsdv09NLYbISEyDPMuZnyjFFB/fuUEHEWdFRbPMtiEsjJAzz6T+668BqN+zB1tBAfG3/KTJcSFjU6n4xwa004kyGntszKJ1/TbYDhRvGcmpXC4XH374IZGRkRQUFDBw4MAWzz///PN5++23m2w777zzWLVqFYcOHSIlJYXQ0FC01tTU1JCVlcW0adNaHc+pEy2EEEKIrtBOJ7qhAUNYW8G2u9d2ycMrcZaVYcvvXP22q7oaY0QEpoHeYPs4pJ7ZqWsFC+104qysbNJj2yts4kSq/v1vtMtFxb/+hQoNJWrOnKbHpKdT/tfXqN+3j7D09J4atmhDvw22T5eB7mnPPvssaWlpPPTQQ9x+++2+iY7tMWbMGMrLy3nrrbeYPn06AFOmTOEvf/kLI0eOJDIystVzv/rqqyaTM4UQQoiucNXVAbSd2fa0/2s4cACMRuxHj3YqE+usrcEQFYXRk8129oGOJM6qKtAaY0wLwfakSVT8/e80HDhA1Tubibr4Yoyn/BsfMWMGGI1Uf/QRYenp1Hz2GcdWPUrya39tli0XPUNqtoNAcXExTzzxBI899hiXXnopiYmJvPTSSx26xvTp01m9erUv2J4+fTpPPvlkq7XfWmueeuopioqKuPTSS7v8DEIIIQS4S0gADG3UbBsiIjANGgQmE3HLloLDgaO4uOP38pWRDALAcbz399p2lrvrzk+t2QYImzQRgOOrn8JVWcmAFkpATbGxhGdmUvPRRwCUPv8CDd9+S+U773TjqEVbJNgOAnfffTf33nsvgzx/WTz55JM8/PDDlJWVtfsa5513HocPHyYzMxNwB9sHDx5sFmyvWLHC1/rvyy+/ZMuWLVgsFv89jBBCiH5Ne4PtNlr/AQy4eiGD7riDyPPOA8BW2PFWtK7qagyRERgiIlAhIX2i17Z3kmdLmW3LyJEYoqKo2boV06BBRJw7vcVrRM26iIZvv6P644+x7twJSlG58c1uHbdoXb8tIwmUGu9qV42sWbOmyevhw4eTn5/f7LiZM2cyc+bMFq+7YsUKVqxY4XudkpLSrB77lVde6fB4hRBCiI7wlpGoNspIAAbfeSeArwWgvbAQzm59jlGL96qpwZKSglIK08CBfaLXtrOi+VLtXspgICw9jdrPvyB63pWtlt1EXjSLkpWPUHT/b1FmM3HLl1P6/PM0fPstIWPGdOv4RXOS2RZCCCGE3/jKSNqYINmYeehQMBiwFR7u8L2cte4yEmh9OfPexlnuCbZbmCAJEObpSObtrd0SS1IiIePG4ayoIOqyS4lb8iMwmah4U7LbgSDBthBCCCH8xmU9/QTJxpTZjHnIEOxdKCMBCBk1mrqcvbgaGjp8nWDiC7ZbmcwYt2QJSX9+jtDTdF2J8jQ/iL1+Mab4eCIvuIDKTZtw1db6d8DitCTYFkIIIYTftGeC5KnMSUnuMpIO0DYbuqEBY1QUANFzr8BVVUXNlq0duk6wqT/wDcZBAzFEtPxmxRgTQ9SFF572OnHLlpH05+cIP2uy+/WPfoiztIyCpcv6RG17byLBthBCCCH8xlXnDbbbl9kGMA9P6nAZidOToTVEuMtIIs45B1NCApW9vFTCmrWL8CmZ7W7/2xpjZESToDzinHNIeuYZGr77jvwbbvDVynvV7d5NwQ9/hLO6ukv3Fc1JsC2EEEIIv3G1sxtJY5akJJzHT/gmV7brPt5g21OzrYxGBsy7kppt23pt5tZ+5AiOo0WET5nSLdePuuhCkl/5C67KKvIX30Bddg4A2uWi+KE/YN21C+uuXd1y7/5Mgm0hhBBC+I1ux6I2pzInuRe5sR9pf922y5OBNUadXNRlwPz54HRSdcpKy72FNSsLgPDM7gm2wT3BMnnNGgyhoRQsXUrNtu1U/fsd6vfuBaDOsxy88B8JtnuYdzXH/Px8wsLCyMjIYPz48SxZsgS73R7g0QkhhBBd450gqTqQ2TYnJQJg60DdtsvTStfQaAXFkDPOIDQtjfJ//APtcrX7WsHCuisLQ2QkIWd275LzIaNGkrx2DZbkZA7fdhslK1cSMm4cIWeeSf0eCbb9TYLtABo9ejS7d+8mOzubwsJC1q9fH+ghCSGEEF3islpRYWEoQ/tDDEtSEkCHOpI4vcF2RNPlyuOWLcP2XR7V73/Q7msFC2tWFmFnTe7wsvWdYR48mOTX/kr41Eyc5eUkrLiHsIwM6rKze+UblWAmwXYQMBqNTJs2jSMd+PhMCCGECEauOmuH6rUBjAMHokJDsR9u/yRJb2a7cRkJQPRll2IZNYoTzz3Xq4JGR3k5trw8wqdk9tg9jZGRjHjhBUa9/RYR555L2KSJuKqrsbWwsJ7ovH67gmTxypU05O736zVDxo1lyP33d/i8+vp6duzYwerVq/06HiGEEKKnuazWDtVrAyilMCclYjvStTIScE+UHHjbrRxdcS/VH35I9OzZHRpLoNT1QL12S5TZTMgZZwAQNnGieyxff03IqFE9Oo6+TDLbAZSXl0dGRgbx8fGMGDGCiZ5fciGEEKK30nV1Hc5sA5gGDcJ5ov1dRJytBNsA0ZdfjiUlhRPP/bnXZLetu7JQFguh6ekBG4Nl1CgMERHUyyRJv+q3me3OZKD9zVuzXVRUxMyZM9m0aRPz5s0L9LCEEEKITnPVdjyzDWCKjaP+6N7236e6BkwmVEhIs32+7PZ9v6bm44+JuvjiDo+np1mzsgidmI7BYgnYGJCNw9sAACAASURBVJTRSGhaGnUySdKvJLMdBIYOHcqqVat45JFHAj0UIYQQoktcdXWoDqwe6WWMjcXhWaq8XfepqcEYGdnq4i/RV1yBOXkEx597Dq11h8fTk1y1tdTv29ej9dqtCZs4kfoDB3DV1wd6KH2GBNtBYsGCBVitVrZt2xbooQghhBCd5q7Zjujweca4WFxVVeh2tsF11da0WELipUwmBt56Gw37coN+Cfe6PXvA6ezxeu2WhI4fBw4HtoKCQA+lz5Bgu4fVeGrMUlJSyMnJ8W1XSrFnzx7OP//8QA1NCCGE6DKXtePdSMCd2QZwVlS063hnTW2bwTbAgCvnYh4+nBPPPhvU2W3rriwwGAibPDnQQ8E0aBAAzl66CmcwkmBbCCGEEH7jqqnBENV2ENwSkyfYbm8pibOiAmN0dJvHuLPbP6V+715qPvmkw2PqKdasLELHjsV4mjcPPcEYFw+Ao7QswCPpOyTYFkIIIYTfeGupO8oYGweAs7x9mW3HsWOYBg8+7XED5s3DnJTEiWeDs3Zb22zU7dlDWBCUkACY4t3/HxylJwI8kr5Dgm0hhBBC+IXLZkPb7c1WdWwPXxlJ+ekzqlprHCUlmBJOH2wrs5n4n95CfXY2tUE4L6p+3z50fX1QTI4EMERHg9mMUzLbfiPBthBCCCH8orWFZtrDFOcNtk9fRuKsqEDbbJgTEtp17Zj58zEPG8bxIKzdtu7aBUD4lLMCPBI3pRSmuDgcUrPtNxJsCyGEEMIvTgbbnehGEhMDgKPs9BlVx7HjAO0qIwFQFgvxP/0p9Xu+pnb7Zx0eW3ey7srCkpKCaeDAQA/FxxQfLxMk/UiCbSGEEEL4hTfY7kzNtjKbMURHt6tm23GsBADT4PZltgFirlqAaejQoOpMol0urF99FTT12l7G+HjJbPuRBNs9LNLzF1B+fj5hYWFkZGQwfvx4lixZgr2F3qKNj5s0aRLnnnsuBw4caHLML3/5SxITE3E1WpK2pKSEuXPnMmnSJMaPH8/ll1/uu15aWhoAVquVG2+8kfT0dNLS0pgxY4avNaEQQgjRUW0tod4extgYnO3JbJe4g21zO2q2vZTFwsBbfkLd7t3Ufv55p8bnbw3ffoersjJo6rW9TPHxOMok2PYXCbYDyLtce3Z2NoWFhaxfv77N4/bs2cPSpUtZuXKlb5/L5WLjxo0MHz6cTz/91Lf9d7/7HZdccgl79uxh3759rFq1qtl1V69eTUJCAtnZ2eTk5PDyyy9jNpv9/6BCCCH6BVdNLUCnJkgCmGJicVacvmbb7gm2vT2h22vA1VdjGjKEspdf7tT4/M2a5anXDrrMdhzOE6VB8wlAb9fuYFsp9b9KqWNKqZxG2x5USh1RSu32fF3eaN9vlFLfKaUOKKXmNNo+RSmV7dn3lPKss6qUClFK/d2zfYdSKqXROUuVUt96vpZ29aGDjdFoZNq0aRw5cuS0x1ZVVRHrmbENsGXLFtLS0rjttttYu3atb3tRURFJSUm+1xMnTmx2raKiIhITE32vU1NTCQkJ6exjCCGE6OdctZ2v2QYwxsXhaFcZyXGMcXEoi6VD1zdYLEScfTYN+fmdGp+/1e3KwjR4MOZG/14HA1NcPNpmw1VbG+ih9AmmDhz7CvAM8NdTtv9Ja/144w1KqfHA9cAEYBjwoVLqTK21E/gzcAvwH+Ad4FJgM/BjoFxrfYZS6nrgUeA6pVQc8ACQCWggSym1SWvdvq73rdi2/htOHPZvycTA4ZGcf+2ZHT6vvr6eHTt2sHr16hb35+XlkZGRQXV1NVarlR07dvj2rV27lsWLFzN//nzuv/9+7HY7ZrOZ22+/neuuu45nnnmGiy++mJtuuolhw4Y1ue7y5cuZPXs2GzZsYNasWSxdupQxY8Z0ePxCCCH6nop//pOQM88kLD293ed0pWYb3O3/6vftO+1x7rZ/7a/XbswwIBpXZVWnzvUnrTXWrCzCM6fgyTsGDdNA98I2zhMngmKhnd6u3ZltrfWnQHubLs4H1mmtG7TWh4DvgGlKqaFAtNb6C+3+bOKvwIJG57zq+X4DMMuT9Z4DfKC1LvME2B/gDtB7PW8QHR8fz4gRI1rMPsPJMpK8vDyefPJJbrnlFgBsNhvvvPMOCxYsIDo6mrPPPpv3338fgDlz5nDw4EF+8pOfsH//fiZPnszx48ebXDcjI4ODBw+yYsUKysrKmDp1Krm5ud370EIIIYKe1priPzxM+euvd+i8rtZsm+JicZaXn7Z8wX6sBHM7O5Gcyhg9AFdtLdrh6NT5/mI/cgRHSQlhU4KrhAQarSLZjvp5cXodyWy35udKqSXALuBXnoA4EXfm2qvQs83u+f7U7Xj+exhAa+1QSlUC8Y23t3BOE0qpW3BnzRkxYkSbg+5MBtrfvEF0UVERM2fOZNOmTcybN6/Nc+bNm8dNN90EwLvvvktlZSXpnqyD1WolPDycK664AoC4uDhuuOEGbrjhBubOncunn37KlFP+UEdGRrJw4UIWLlyIwWDgnXfeYdy4cd3wtEIIIXoLV3U1uq4OxylJmtOeV1MLRiMqNLRT9zXGxnrKF6wY2yhFcZQcI2xCWufuER0FgLO62rdEfCD4+mtnBtfkSDiZ2ZaOJP7R1QmSfwZGAxlAEfD/PNtb+jxEt7G9s+c03aj1C1rrTK115qAOTpoIpKFDh7Jq1SoeeeSR0x67fft2Ro8eDbhLSF566SXy8/PJz8/n0KFDvP/++1itVj7++GOsVisA1dXV5OXlNXsD8tlnn1HuWTzAZrOxb98+kpOT/fx0Qgghehtvt4+OB9s1GCIjO10WYYzxLGzTxiRJbbfjLC3tfBlJdDQArqrAlpLU5+zFEB5OSBCWb3oz29Jr2z+6FGxrrUu01k6ttQt4EZjm2VUIDG90aBJw1LM9qYXtTc5RSpmAAbjLVlq7Vp+yYMECrFYr21pYStZbbjJp0iTuv/9+XnrpJaxWK++9954viw0QERHBjBkzeOutt8jKyiIzM5OJEycyffp0br75ZqZOndrsuhdccAHp6elMnjyZzMxMrr766m5/ViGEEMHNXnIMOLl4THu5amowRnRuciSAsR2rSHrfALRnqfYW7xE9wH2Pquom2ys2bKD4Dw936pqdYTt4EMuoUShD8DWG867mKZlt/+hSGYlSaqjWusjz8irA26lkE7BGKfUE7gmSY4CdWmunUqpaKXUOsANYAjzd6JylwBfANcDHWmutlHoPWKmU8n7WMxv4TVfGHUjePtYpKSnk5Pgau6CUYs+ePc2OT0lJoa6ursVrlbVQS/XGG2/4vl+xYkWL1/Ped8mSJSxZsqRjDyCEEKLP82a2nZWVuGw2DO3s+uGsrel0vTbgK+toq9e2t+1f52u2PWUkVZVNttds/4zqjz5i8L0r2v28XdFw6BDhU4OvhATcCwwZBwyQzLaftDvYVkqtBWYCA5VShbg7hMxUSmXgLuvIB34KoLXeq5RaD+wDHMDtnk4kALfh7mwShrsLyWbP9peB15RS3+HOaF/vuVaZUuoh4EvPcb/XWkvFvhBCCNFNvCs0ur8/jiWpxalSzbhqarsUbBvj4tz3bCuz7cm6d7mMpLppZlvbbGC307B/P2GtNCzwF1dtLY6iIkJGjerW+3SFexVJCbf8od3BttZ6cQubW+0Kr7V+GGj2eYzWehfQbFaD1roeWNTKtf4X+N/2jlUIIYQQnefNHgM4jh/rQLBd4ysF6QyjL7PdRrB9rGvBttETbDtPaf+nGxoAqPs6u9uDbW+fb8vI4A22TfHxOEpPBHoYfULwFQoJIYQQIqAcJcfAs6JwRyZJumpqutSX2RAZCWZz2zXbx0rcZQ4xMZ26hy/YPqWMxBts12dnd+q6HWE7eAiAkFEju/1enWWMj8cpmW2/8EfrPyGEEEL0IY6SEkLPPJP6vXs7FGw7a2s6vVQ7uOcvmWJicJSX0XDwEM4WMqv1+w9gGjy40x1PVFgYmM24Tpkg6bLbAKhrNJ+qu9gOHQSDAXMQdwAzxcVRKzXbfiHBthBCCCGasB87RuQFP6B+//4OdSTpas02uEtJKjf8k8oN/2z1mPDp53T6+kopjFFROKtOLSNxB9u2gwdx1tS22ee7qxoOHsIyfHiPTMTsLOPAeFxVVWibDRXE4+wNJNgWQgghhI+3j7V5yFB33W47M9va4UDX1WHoYpAa+8Mbqfu/rwibchaWpCRoIYPd1d7UxujoFstIDFFRuKqrqd+7l4izp7Vydtd52/4FM1OjVSTNQ4YEeDS9mwTbPSwyMpKamhry8/MZN24cqamp2Gw2MjMzefnllzF7auQa++abb7jzzjv55ptvMJvNpKen8/TTT5OQkMDOnTu55557KCkpQSnFjBkzeOqppwgPDw/A0wkhhOjtHMePg9aYEgZjGjy43cG2q7YWoEs12wCx115L7LXXdukap2MYEN2sjETbbISdNZnaTz6lPie724Jt7XRiy88n4vwZ3XJ9fzHGezrDlJZKsN1FMkEygLzLtWdnZ1NYWMj69eubHVNfX88VV1zBbbfdxnfffUdubi633XYbx48fp6SkhEWLFvHoo49y4MABcnNzufTSS6k+pZ2REEII0V6+PtYJCZgGDWp/sO1ZR6KrZSQ9wRgV3ayMxGVrwJwwBHNSEnVfd98kSfuRI2ibLajb/kGjnucVFQEeSe8nme0gYDQamTZtGkeOHGm2b82aNUyfPp0rr7zSt+3CCy8E4He/+x1Lly5l+vTpgLsO7ZprrumZQQshhOiTGvexNg0aRF0LC661xFnjzmx3ZYJkTzFGR2M/fLjJNt3grk0OTU+jfs/X3XbvhoMHgeBu+weN2jCWS7DdVf022N7yygscKzjo12sOTh7Fhctu6fB59fX17Nixg9WrVzfbl5OTw5QpU1o8Lycnh6VLl3b4fkIIIURrvAvaeINtZ1kZ2m5HtVDm2Jirtvdktg0Dmme2tc2GCrEQlpZO9eZ3cZSVYfIssuNP3rZ/lpEpfr+2P3kXGGqrDaNoHykjCaC8vDwyMjKIj49nxIgRTOzmJvpCCCHE6dhLSlAWC8aYGEyDBgHuut3T8ZaRdGcXD3/xlpForQHQWrsnSIaEEJruXnevu/pt12V/jWnoUF+ZRrAyRkeDUhJs+0G/zWx3JgPtb96a7aKiImbOnMmmTZuYN29ek2MmTJjAJ5980uL5EyZMICsri/nz5/fEcIUQQvQDjpJjvj7WpsGD3duOHz/tJLleVbM9IBqcTrTVioqIALsdtHaXkYyfAEpRl51D5AUX+PW+WmusO78kcsZ5fr1ud1BGI8YBA3BWSLDdVZLZDgJDhw5l1apVPPLII8323XDDDXz++ef8+9//9m179913yc7O5uc//zmvvvoqO3bs8O17/fXXKS4u7pFxCyGE6HscJSW+pdB9mW3PEultcfaiYNvgW0XSXUristkBUJYQjJERWEaP6pbMti0vD2dpKeHTuq+toD8ZY2NxSGa7yyTYDhILFizAarWybdu2JtvDwsJ4++23efrppxkzZgzjx4/nlVdeYfDgwSQkJLBu3TruueceUlNTGTduHNu2bSPa85eIEEII0VH2YyWYE9wZbdNgT7Ddjo4kLu8EyV4QbBujmgbb2uZeql2FuBdvCUufSF1Ojq/MpLET//M/HLpmUafuW7tzJ0CvCrZlgmTX9dsykkCp8bzzT0lJIafRkrBKKfa0MuN77NixvPvuuy3umz59erMAXQghhOgMbbNhP1pE9OzZAJji40Gpdq0i6Ssj6QXrPBgHuINtlzfYbvAE256VEkPT06jcuBHH0aOYExObnGv9v/+jPicHx/Hjvsx/e1l3folp6FDMSUldfYQeYYyNxV5YGOhh9HqS2RZCCCEEAA3ffQd2O6HjxwOgTCaM8fHYPR1K2uKqrcEQEYEyBH9ocWoZiba5l2o3hIQAEJaeDkBddk6zc+2H3cFn3dcdaw/ortfeScS0qagWVsUMRsbYGJxlZYEeRq8X/H8ihBBCCOE3NZ98gq2VbGX9vn0AhI4b59tmThyGvYV1IE7lrKnpFSUk4Om0ATg9q0i6fJltd7AdkpoKZjP1OU3rtrXL5ftZ1O1uX/9xL1teHs6ysl5TQgLuhW0cFRUtltNoranL2RuAUfU+EmwLIYQQ/YR2uSj8xR18v3QZjhYylvX7cjFERGAeMcK3zZKYiP3I0dNe21VT2+uCbVdVJeBe0AZOlpEYLBZCU1ObZbYdx4/7suAdzWz3tnptAGNMLNjtuGprm+2rfOMN8q+5hvoDBwIwst5Fgm0hhBCin3CWlrrrso8cofCOO3yBo1f9vn2EjhvXpBTEnJiEvagI7XS2eW1XTQ2GXtBjG05O4nRWespI7J5g2zNBEtx12/V796JdLt82b/2yecQI6rOzT/szaax+716McXG9pl4bGq8i2bQjidaastf/BoDt++97fFy9jQTbQgghRD9hL3bXXkfPnUvdriyK/vu/Ty7s4nRSf+AAIePHNTnHnJQEdjuOkrbrtl01NRh7wVLt4O4hbYiKwlntLiPxTpA0WE4G22HpE3HV1GDLz/dts3mWeI++/DJcVisNeXntvqftuzxCRo/uNfXa4K7ZhubBdt1XX9GQmwuAo/j09fz9nQTbQgghRD/hKHGvwxB30zLib7uVyn++QdmrrwJgy89H19X5Jkd6ebtxnK5uuzfVbIO7lMRbRuKr2fZMkAQI86wk2bhcxF54BJQi+rLLAKhvZymJ1pqGgwexnDHaL2PvKaZWMtvlf1uDISoKZTb7fqdE6yTY7mGRnr+I8vPzCQsLIyMjg/Hjx7NkyRLsdnuz43ft2kVaWho2z0d9eXl5jBo1iirPDOqdO3fygx/8gNTUVMaOHcvNN9+M1WrtuQcSQgjRa9g92WnzkCEM+sUviJo9m2OP/ZGaTz89OTnylGDbkuQOtm2FrQfbzooKbAUFWJKTu2nk/meIjj5ZRmLzlpGcDLYto0ahwsOpb1S3bT98GNPgwYSMGYMhOpq6Pe0Lth3Hj+OqriZkVO8Ktr1lJI0XtnEcP07V++8Ts/AqTEOG+D4tEa2TYDuAvMu1Z2dnU1hYyPr165sdk5mZyQ9+8AMef/xxAG6//XYefvhhoqOjKSkpYdGiRTz66KMcOHCA3NxcLr30Uqo9H4sJIYQQjTmKS1BmM8bYWJTBwLBVjxCSmsqRu39F1b/fQYWEEDJqVJNzTMOGgVJt9luu3roVHA6iLrm4m5/Af4zR0Y3KSDzBtvlkGYkyGgkbP566Rh1JbEcKMQ9PQhkMhKWnt3uSpO3gQQBCRo86zZHBxRgXB9BkYZvyf/wD7HZiFy/GnJCAQ1atPi0JtoOA0Whk2rRpHGnlI7qVK1fy0ksv8dhjj2G321m8eDEAzz77LEuXLmX69OmAe2Gca665hgTPMrtCCCFEY/aSYkyDB/smQBrCwxn+3LOo0FBqtm4lJDUVZWq63p3BYsGUkNBmGUn1Bx9iGjKE0LS0bh2/PzUuI/GuIGloNEESIDQ9nYbc/b7Mt/1wIZZE9wRHy+hRbb4B0U4nLs8nzd7absvo3pXZNkRGgsnkKyPRdjsVf19PxIwZWFJS3Jnt09Tyn46roYHjTz2N07MoUl/Ub1eQrHgrD9vR5q1susIyLIKYKzv+B6m+vp4dO3awevXqFvfHxMRw33338bOf/Yx9no/5AHJycli6dGmnxyuEEKJ/cRSXYBoypMk289ChDH/maQqWLCVs4sQWzzMnJrYaWLqsVmq3bydm0aJesaCNlyEiAqenpV1LZSTgrtsus9mo//ZbQs44A8exY5iHDwfcS767amvRTifKaGx2/RPPP0/F39dzxgfvY8s7iCEiAtPgwd38VP6llHIvbOMJtqs/+hhHSQlDHngAAPOQBKrfK0Zr3emJn9YdOzjx3HOYhg4hdtEiv409mPSePxV9UF5eHhkZGcTHxzNixAgmtvKXHMDmzZtJSEhoEmwLIYQQHWEvKcbcwqefYRkZjHprE4Pu/GWL51mSErG1ktmu2bYd3dBA1CWX+HWs3c0QHo6udWeeXacs1+4V6vl3uT47x91rXGvMnhp2Q5R7DparlYysdeeXOEpKqP3iCxry8rD0sk4kXqaYWJwV7mC7fM0azMOGEXnBD9z7Eoag7fZmEyg7wpZfAEDdrqyuDzZI9dvMdmcy0P7mrdkuKipi5syZbNq0iXnz5jU77u2336ayspL33nuPq666ijlz5hAeHs6ECRPIyspi/vz5ARi9EEKI3kRr7c5sz2q5rrqtyY3mxCQcxW+hbbZmAWnVu5sxxsQQPuUsv463uxkiInxlHr6a7VMy2+bERIwxMdRlf405cRgAlkaZbQBndQ3GAQOanKe1pt7TGq9q87s0HMwjcsb53fcw3cgYG4ujvJz6b77BunMng+/5lS+TbxrifuPmKC7G5Knv7ihva0VrVt8NtiWzHQSGDh3KqlWreOSRR5rtq6ur41e/+hXPPvss6enpzJ8/n4cffhiAn//857z66qvs2LHDd/zrr79OsUxWEEIIcQpnRQW6oQHzkI7P6zEnJYHW2Bv9+2IvOUbhL35B9eZ3iZ47t1mtd7AzRISj7Xa0zXayjOSUNxJKKULT06nd/hnVH38M4FuUxpfZrq5qdm3H0aO4KisxhIdT9f77OI+f6HWTI72MsbE4yysoX7sWZbEw4Oqrffu8n5J0pSOJrcCd2bYXFna5/jtYSbAdJBYsWIDVamXbtm1Ntj/00EMsWLCA8Z5WTA8++CDr1q3j22+/JSEhgXXr1nHPPfeQmprKuHHj2LZtG9GeZWiFEEIIL++iNKaEIac5sjlfr+3CQrTWlP/jHxycO5eaT7cx6Fd3k/Dr+/w61p5gCA8HwFVXh7Y1oMzmFss84n/8Y/fEwLXrUBYLpkGDgJNLvjtb6ADmzWrHLV+O9mTPLb2s7Z+XMTYGR3Exlf/aRPQVV/h6b8PJ36Wu9Nq2FRRgSUkBwLprV5fGGqx619vQPqDGU9uVkpJCTs7J3p1KKfbs2dPs+JUrVzZ5HRUVRV6jFaumT5/eLEAXQgghTuXNSncms+3ttV37xX848fwLWHfsIHzqVIY+9HtfoNTb+ILt2lpcDQ3NSki8Is45m9HvvUvpiy+BQZ3s5BIV5T6/pWB7Xy4YDMQtXULZa6/hqqzs1Zltl2ciaewNNzTZZxoYD0ZjpzPbLpsN+9GjxP/kJ5S/9hp1WVkMuOKKLo852EiwLYQQQvQD3mW1T+1G0h6mhAQwmSh98UUMkZEM+e//JmbRNb2q+8ipfMG21equRW8l2AYwRkUx+O67mm0DcFa1nNm2jBqJMSqK6DlzqPr3v33lJ72NN5MdOnGib1VNL2U0Yho8uNO9tu2HD4PLRcjoUYRNnoy1j06S7L1/SoQQQgjRbo5jJWA0Yho4sMPnKpOJqFmziJo9m1H/fpvY667t1YE2uCdIgifYbmg+8fO057eV2c7NJXScu/xz8L0rSFn/9xbbA/YGxlj3xMe4G29ocb85IaHTtdbeyZGWlBTCM6fQ8O23OCsrO3WtYCaZbSGEEKIfsBeXYBo4sNNBX9LqJ/08osBqktluaMDQwWDbGOmeIOk8ZYKko7wcR3ExoePG+Y7zHtsbRV44k4T77yf68stb3G8aMoSG/fs7dW1v2z9LcjKuunrQmrqvs4k8f0anxxuMevfbUiGEEEK0i6O42NeqTYBqHGzb2y4jafF8sxkVHo6rummf7XrPehih48f5Z6ABZoyMJG7Jj1Bmc4v7vZltrXWHr23Lz8cYG4txwABfa0XHsWNdGm8wkmBbCCGE6AfsJSWYO9GJpK86OUHS6p4g2cHMNrgD0VMz2w2eTiShY8d2fZC9gGnIEHRdHa6q5i0QT8dWUODr7+7t0+0oLfXr+IKBBNtCCCFEP+AoLnZPdBQAGMI9Ndu1te6a7Q5mtgEM0VEtZrbNw4ZhjInxyziDnbe7jb0TkyRt+fm+bjaG8HBUeDhOCbaFPxQXF3P99dczevRoxo8fz+WXX84333wDwJ/+9CdCQ0OpbDRBwGq1cuONN5Kenk5aWhozZszwtRCMPKUO7JVXXuHnP/95zz2MEEKIoKdtNly1tZjiO7fKX1/UZIKkzYYhpBOZ7ajoZpnt+n25hPSREpL28Ha3sRcVdeg8V20tjmPHsKScXLnUFB8vmW3RdVprrrrqKmbOnEleXh779u1j5cqVlHhm8q5du5apU6eyceNG3zmrV68mISGB7OxscnJyePnllzG3UjslhBBCnMrp+YjfIIue+RjCQoGTEySVuePBtiEqsklm21Vbi62gwDc5sj8wD/MseHTkSIfOsx0+DOArIwFvsH3Cf4MLEhJs97AtW7ZgNpu59dZbfdsyMjI4//zzycvLo6amhj/84Q+sXbvWt7+oqIhEz+pdAKmpqYR04uMuIYQQ/ZO3nZpxwIAAjyR4KKMRFRaGy2rFZWt9UZu2nJrZrj9wALT2tf3rD0yDBqLMZuxHj3boPMdxd1DdeEVT48B4nCf6Xma737b+27x5M8WdbMLemiFDhnDZZZe1eUxOTg5Tpkxpcd/atWtZvHgx559/PgcOHODYsWMMHjyY5cuXM3v2bDZs2MCsWbNYunQpY8aMAaCuro6MjAzfNcrKypg3b57/HkoIIUSvdzLY7h91xO1lCA/HZa1F2+ydq9mOisTVaFGb+n2eyZF+KiPR2smhQ89QVf01kya+gFLB16tbGQyYhg3tcLDt+52MOfkG0BQXT91Xu/06vmAgme0gsm7dOq6//noMBgMLFy7kH//4B+DOfB88eJAVK1ZQVlbG1KlTyfXMdg4LC2P37t2+r9///veBfAQhhBBBSDLbLTOEh+Oq9ZSRWDpenmmMisZZU+Nre1efuw9jbKxfJqLa7ZXs3n0Th/KforR0Kw22412+ZncxDxvmC7Yd5eUU/e4BT8pt7QAAIABJREFUnC0s9tOYs7ICaPo7aRoYj7O8HO10dt9gA6DfZrZPl4HuLhMmTGDDhg3Ntn/99dd8++23XHLJJQDYbDZGjRrF7bffDrgnQi5cuJCFCxdiMBh45513GNePasKEEEJ03slgW2q2G3Nntj2L2nSyGwl2O7q+HhUW5lk5chxKqS6P7cjRdZSVf0ZCwjxKSjbR0FBCaEhwtm40DxtGzaefAlCz9RMq1q8nfMpZDJg/v9VzfL+TjeYRGOPjweXCWV7eqZVOg5VktnvYRRddRENDAy+++KJv25dffskvf/lLHnzwQfLz88nPz+fo0aMcOXKEgoICPvvsM8rLywF3EL5v3z6SG00oEEIIIdriksx2iwwREZ4yEhvK0pmabfeS7c7qarTNRsO33/mthMRmO47RGMmIET8GoKHBv6Wv/mQeNgzn8RO4Ghpo8HRXq925s81zXJWVGCIimiyWY4p3B9iO0rLuG2wA9NvMdqAopdi4cSN33nknq1atIjQ0lJSUFLZu3cqf//znJsdeddVVrFu3jqFDh3LbbbehtcblcnHFFVdw9dVXB+gJhBBC9DbOyipQCoMnOBRuhvBwnNVVuGy2Ti1q4/15uqqraSgtBbudED996uywV2I2RRPiyWY3NJT45brdwexp4mA/etQXbFt3ftnmOc6KymZv/rytKZ2lJ4Az/T/QAJFgOwCGDRvG+vXrT3vcE0884ft+yZIlLR7j7bfttWzZMpYtW9al8QkhhOhbnJWVGKKjUQb5QLsxQ3i4u9bY4UB1qs92o2A77yCA3zqR2B1VmMwDsJjjUMoU3MH2MPdS695gW5nN2A8fxn70qG/fqZxVVRhimgbbRl9mu291JJE/dUIIIUQf56xsnkUUnsy2p0yzUzXbjcpI6nNzUeHhTRZp6QqHvRKTKRqlDFgsg4K8jMSd2a7fuw/H8eNEX345ANYvW89ut/Q7aRoYD4Cjj7X/k2BbCCGE6OOcVRJst8QQHu6bqNeZMhLv5D6XJ9gOTU3126cHdkclZrP7/1lIyJDgzmwnDAaDgZpPPgEgeu5cDAMGtFm37Q62m7aiNERFocxmnGUSbAshhBCiF3FWVjbp+iDcDBER4HIBdGqCpCHSk9muqqLB04nEX9yZbW+wnRDUwbYymzENSaDuq68ACB2bSvj/Z+/O4ySrq4P/f27VvbV2V/Uy08vs+zAwDMMwDMMiBtFBHFCMomiCxLjEJT5Ro4iPPj95Eo1Gk2iiSYyPGDCIglsIOAiKuLDDwAADszD70uv0UlXdtd3l+/vjVlVv1dXV1fvMeb9evKb71l2+PcuLU6fO95wLN5es2y6W2dY0DW99vWS2hRBCCDG3OEU2ownwhEOFryuaIBlxg+30yy/jJJOT1okEcplt3X2DVE6w3d3zBK+8cjOOY03aGsbDWLAAHAdvXR3eefMIb9lSqNseTik1amnT6TiyXYJtIYQQ4jRnx2JDJvUJlyc0KNiuYKiNFgiArtP/5FMAk9aJxHEyOE4aPVdGEvA3Ytv9WFbxQTE9PU/xwgvvp7XtpySTBydlDeOV3wjpX7MGTdMIX3opAImHfzPiXJVMgmkWDbZPx5HtEmwLIYQQpzHlOG7nBykjGWFwsF3JBklN0/BWV2MePw6GgX/16klZl2nGATB0t6a5VPu/WOw5Xnjx/ei6m2Xv69s3KWsYr3ywHVjrtuzzr1yJf80a4r/4xYhzi41qz9Pr52F1n159tiXYnkZdXV1s3LiRjRs30tTUxMKFCwvfZ7PZEed3d3fz7W9/e8z7WpZFTU3NmOcJIYQ48zj9/e7H+1H5/8RwQzLbFQTbMNCRxL9qFZ4KNlkWY1luMKobA2UkMDLYjsdf5Pld78Xnm8/mC36Kpun09++flDWM1+DMdl5k+3ZSu3aRPXFyyLl2iSFLen0ddlcXSqkpXO30kmB7GtXX17Nr1y527drFhz70IT7xiU8UvvcV+QdabrAthBBCjKZUYHOm0wYH20ZlgXK+1/Zkbo40c8G2MWiDJAwNthN9e3l+159hGDVsOv9OgsGFhELL6et/ddLWMR6Bc85BMwyC528qHItsd1sAxnfsGHJuqb+T3vp6lGnixONTuNrpJcH2LPHVr36V9evXs379er75zW8CcMstt7Bv3z42btzILbfcQjwe53Wvex2bNm1iw4YN3H///TO8aiGEELPdQGAjZSTDecPhwteVDLUB8EQmP9i2zHxme6D1HwwE2/39B3j++RvxeoNsOv9OAgE3qxwOr6a/b2Yy28FzzmHtzmfxr1heOOZbtIjgeeeNKCWxe92fz1M0s336DbY5YydI7t//tyT69kzqPaur1rFmzf8Z93VPP/00P/jBD3j66aexbZstW7bw2te+lq985SscOHCAXbt2AWCaJvfeey/V1dV0dHRw6aWXcs0110zqzyCEEOL04sxwZvvEiTvx+xuYP3/bjDy/FG2CNdsA3lz7v0ntRGLmM9vuGySvN4iuR8hk2kmljvPc8zeiaZ5cRntx4bqq8Fo6OnZgWf3oerjovadSsV7lke3baf+7vyNz4AD+VauAwW8AR5Y25Qfb2F1dsGLFFK52+khmexb4wx/+wNve9jZCoRDV1dVcd911PProoyPOU0rxmc98hg0bNrBt2zaOHz/OqVOnV3scIYQQk2umy0iOHP13jh3/zxl59liGdiOZQGZb0/CvPWuyljVQs60P/Jm57f/a2L//b7DtJOdv/D6h0PIh11VVufXS/ckDk7aWiYpc/UbweIaUkpT6tMW3ZAkAqZd2F72f3dfPsfd/gMzhw1Ow2qlxxma2K8lAT5VyNwF8//vfJxaL8dxzz6HrOosWLSKdTk/x6oQQQsxldsytffVEpj/YdhyLbLYTxzGn/dnl8IQGl5FUltmuvvL1eKuq8FZNXibZtNw/M10fCEb9/iZ6ep/EshKsXHkzVVVrR1wXDueC7b79RCPnTdp6JkKfP5/Qli3EfvEL5n3sY2iahh3rRQsE8AQCI843Fi7Ef/Y6Eg89RP2fv3fE65k9r9D/6KMkHvoV/r/44HT8CBMmme1Z4PLLL+fnP/85qVSKvr4+7r33Xl7zmtdQXV1NIjHQUzMWi9HQ0ICu6/zqV7/i5MmTJe4qhBBCzGzNdtY8hVI2ptmFafZOyTNsO4Ntpyq6dshQmwomSAJUv+4KGj/72YquHY1lxvB6q/B4BnKifn8jlpXA729i8aKbil4XDC7G4wnQN0MdSUYT2f4mzKPHSO9+GSg+PXLI+duuIrVrF2Zb24jX8sfSu4tnvmcjCbZngS1btvCud72LCy+8kK1bt/LhD3+Yc889l8bGRjZv3sy5557LLbfcwo033sjjjz/O5s2b+fGPf8zqSernKYQQ4vRlx2KjZhGnWiY9ECz1909NacPevZ/lpd0fq+haTzBY+LqSoTZTxbRiGMbQYNTvawBgxYpP4PUW/7PUNG9uk+TM9NoeTWTbNjCMwkZJZ4xgu3qbW9+feOhXI14zW92/U6k5FGyfsWUkM+3WW28d8v3NN9/MzTffPOK8u+++e8j3Tz31VNH79fZOTcZACCHE3GbHZ25U++BWdcnkIWpqNk/6M1Lp42QyHRVdq3m9aMEgKpWqeIPkVLCs+JB6bYDGpjejUDQ3vbXktVXhNXR1/34qlzdu3miUqssuI/7AAzTc/Gns3tJ/J/0rluNfvZrEQw9R954bh7xmtbW6v7a2Yp06hT5v3pSufTJIZlsIIYQ4jTmxGN4Zmh6ZybTmvvJMWWbbtvrJZDoqHoKS3yRZ6QbJqWCavYVOJHlV4dWsWvkpNM1b8tpw1Wqy2c5CR5PZIrJ9O1Z7O8lnn3XLSIpMjxys+qqrSO7cidXZOeS42doGXvf3YK5ktyXYFkIIIU5jY2URp1I604bH46MqvJr+5KEpeYZl96NUFsuq7BNeTygEXi+aPns+7LeseKHH9njly01Mc3aNPK9+3RVowSDxX+zAjsWK9tgecv62N4BSJH796yHHzbY2QuefDx5PoQZ8tpNgWwghhDiN2bEYnjGyiFMlk2nD728iFF5Fsv/glDzDtvtzz6qslMQTClXciWSqmGasMD1yvPJB+lRtSK2UJxSi+oorSDz4IHZv75hvAP2rV+Nbvpz4gw8NOW61tuJbuRL/yhVzZpPkGRdsV/oxkxggv4dCCDF32PE43hlo+wfuBkm/v5lwaCWp9HFse/Lb1VrWBIPtcBjPLCohAbfPtm5UVvpj6O6gmNkWbANErtmO3duLymSKDrQZTNM0qq/aRvKZZ7C63Sy9k0ph9/ZiNDcROGc9qd2750RMckYF24FAgK6urjnxBzNbKaXo6uoiMAO72oUQQoyPUsrNIs5QzXY600bA30Q4vBJQJFNHJvX+jpNFqSwAmWz7GGcX5wmFZlW9tm1ncJxMxZntfBcT05pdNdsA4csuw5P7u1hOaVNk2zawbRIPPwwMtP3Tm5oIrF+PfeoUPXfdRdsXvzSitns2mT0FStNg0aJFnDhxgs5Z/AcyFwQCARYtWjTTyxBCCDEGq7UVlU7jW7pk2p+tlEMm014oIwFI9h+gumryJi3mS0gAsqdJGUmx6ZHjYRhuxtiahZltj89H9bY3EPvJT8sKtv3r1mEsXkziwYeovf56rFywbTQ14wm5bRvb//aL7rmrVlJ7ww1Tt/gJKDvY1jTte8A1QIdSan3uWB1wN7AMOAK8QynVk3vts8D7ABv4X0qpB3PHLwBuB4LADuCvlFJK0zQ/8H3gAqALeKdS6kjumpuAz+eW8kWl1B2V/LCGYbB8+fKxTxRCCCFOA+m9ewHwnzV5AW65TLMbpbL4/Y2EgssAbdI3SeZLSKDyMpLad78bs6117BOnSb78Y3if7XLlp07Otm4keTVvfSuxn/4MY/HYSTtN04hctY2u2+/AjsUKPbaN5iaMJUtY8LWvYSxcyNEbbyy8NhuNp4zkduCNw47dAjyslFoNPJz7Hk3TzgZuAM7JXfNv2kCvmn8HPgiszv2Xv+f7gB6l1Crg68Df5+5VB3wBuAjYAnxB07TacaxbCCGEOCOl9+wBTSOwZs30PzvjBj/+QBNeb4BgYPGkt/8bnNnOZCsLtsNbL6Lmuusma0kTZhVGtVcWbGuaF12PYFbYnWWqhS64gNWPPUrwnHPKOr962zawLBK/eaTwpkhvakLTNKLXXkNo0/nojQ2YrS1TuewJKTvYVkr9HhjeR+YtQD7LfAdw3aDjP1JKZZRSh4EDwBZN05qBiFLqCeUWTn9/2DX5e/0EuFLTNA24CviVUqo7lzX/FSODfiGEEEIMk9m7F9/SpXjC4el/dm6gTcDfDEAovJJkcnI7klh2X+4rT8WZ7dkmX2tdaWYb3E2S1izNbAPodXVlnxs491z05mYSDz6I1dqGt65uxAAio6kZ6zTJbBfTqJRqBcj92pA7vhA4Pui8E7ljC3NfDz8+5BqllAXEgPoS9xJCCCFECem9+2akhAQGRrX7/U0AhEMrSCYPo5Q9ac+wc2UkweAispnKNkjONvkgWdcr39SqG9FZm9keL03TiGzbRv9jj5E5cACjqWnEOUZTU2Hz5Gw0Vd1ItCLHVInjlV4z9KGa9kFN057VNO1Z2QQphBDiTGYnEpjHjxOYqWA704qm6fh89QCEw6twnAzp9MlJe4aVKyMJhVaQyXaeFt3GJiWzbdTM2prtSlRftQ1lmqSefx69uXnE63pzE1ZbG8pxZmB1Y5tosN2eKw0h92v+M5wTwOJB5y0CWnLHFxU5PuQaTdN0IIpbtjLavUZQSn1HKbVZKbV5/vz5E/ixhBBCiLkts28fAIF1MxNspzNt+H0NhfHiodAKAPoncbiNXQi2l6OUiWn2TNq9Z0o8/iIeT3BCmW032J77vxd5wY0b0XNxnVEk2DaaF6BME7t7dk3NzJtosP0/wE25r28C7h10/AZN0/yapi3H3Qj5dK7UJKFp2tZcPfZ7hl2Tv9fbgd/k6rofBLZpmlab2xi5LXdMCCGEEKNI78l3Ilk3I8/PZNrwBwY+8g/n2v/1T2Lddr6MJB/IV7pJcrbo7z9Ee/v9LFr0Jwz0lRg/Qz+9Mtuax+NulMTtRDJc/pjZOnu6ygxWdrCtadoPgSeAtZqmndA07X3AV4A3aJr2KvCG3PcopV4G7gFeAX4JfFQNFGl9GPgu7qbJg8ADueO3AfWaph0APkmus4lSqhv4W+CZ3H9/kzsmhBBCiFGk9+3FW1eH3jAzn/RmMp34fA2F7w2jBsOon9Sx7YMz28Ccr9s+fOSbeDx+li75wITuoxtRLCuGUrOzrKISkavd3hjG4sUjXtObZnewXXafbaXUu0Z56cpRzv8S8KUix58F1hc5ngauH+Ve3wO+V+5ahRBCiDOZchzSr7xC4KyzcD9Inn623YeuVw85Fg6vnNTMtmX3o2kGwYBboTqXO5L09x+gvf0+li75AD7fvAndyx1so7CsxIRqv2eT0ObNLL3rLoLnbRjxWr60xJqlmyTPqHHtQgghxOnMSaU48VcfZ/+FW8i8sodAmb2Mp4JtJ/F6Q0OOhUMr6e8/OGkbGW2rH683XMigz+UykkOH/wWvN8iSCWa1gcKod3MWTpGciNCm89G8I8trvLW1aH7/rB1sc0aNaxdCCCFOV0opWj/3eRIPPUTN9dcTPG9Doc51JtZi20n0YcF2KLwSy+rFNLsmnL0Ft8+27g3h9frR9Zo5m9nu69tPR8cOli79C3y+8ntQj6Ywst06feq2S9E0zW3/N9fLSIQQQggxe3X9x3eI79jB/E9+knkfnHh2dCKUyqKUhdc7dJhOOLQScDcCTkawbdv9eHX3GX5/w5yt2T585Jt4vSGWLnn/pNxPN07PzHYpenMz1iwNtqWMRAghhJjjEg8/TOc3vkHkmmuo/8DkBGwTYdtJALze4JDjAx1JJmdsu1tGUgWA39dQmFo5l/T17aOjYweLF70Hw6idlHsaupvZPpOC7dk82EaCbSGEEGIOS+/fT8unbyawfj3NX/zbGdsQOZhl5YPtoZltv78JjydIMnlocp5j96PnnuEPNJPOzM5gqxS3VruKJZOU1YaBgTjmGVJGAmAsaMbq7ESZ5kwvZQQJtoUQQog5yurp4cRHPooWDrHoX7+FJxCY6SUBAy35hm+Q1DQP4fAK+vsnKbM9qIwk4G8mm+3EcbKTcu/pkEjsobPzlyxefFOhznoy6KfpBslS9KYmcBysjtlXty/BthBCCDEHKdPk5Mc/gdXRweJvfQujsXGml1QwUEYSGvFaOLRq0nptW1ZfIbMdCDQDak5tkjx8JJfVXvy+Sb2vx2Pg9VZhnUHBdr7932wsJZFgWwghhJiD2r/8FZJPPUXz3/4NwfPOm+nlDDEQbIdHvBYKrSCdaSmcM7HnDN4g6QZb6XTLhO87HRKJV+jsfIgli987Jb2wDaMG0zqDgu3CYBsJtoUQQggxQWZLCz133UXtn/4p0be8ZaaXM0Ih2NaLZLYLmyQnVredby+Y3yDpZrYhk5mdHSmGO3rsO3i9VSxe/OdTcn/DiJ5WI9vHYixcSMOnP01g3VkzvZQRJNgWQggh5pjEI48AUPvu0YY7z6x8sK0Xy2yH3fZ/Ey0lcRy3vWBhg2Qhsz37g+1Mpp2OjgdYsOB6DCMyJc8w9JozqozEEwpR/74/x79y5UwvZQQJtoUQQog5pu+R3+JbuhTf8uUzvZSiRtsgCRAKLkXTvBMe227bfUOeoethdD1KusLM9qlTj3Ds2G0TWlO5Tp78IUrZLFr4p1P2DN2InlHdSGYzCbaFEEKIOcTu6yf51FNUXXHFrGjzV0ypDZIej49gcAn9ucx2T8/TxOMvVfCMXECvD2TPA4HmistIWtt+xoGD/4Btpyq6vlyOk+Fkyw+pr/8jQqFlU/Ycw6g5o7qRzGYSbAshhBBzSP/jj6FMk6orrpjppYzKygXbHk+w6Ouh0EqSyYM4jsVLuz/Kqwf+bvzPsNxgW8/VbINbSlJpGYlblpKlt/fZiq4vV0fHL8lmT7F40Xum9DmGHsWyYiilpvQ5YmwSbAshhBCznHIcWj73OU79+78Tf+ABPJEIoU3nz/SyRmXb/Xg8fjwevejr4dBKkskjdHf/AdPsrqjv9kCpytDMdqXdSBw7DUBPz+MVXV+uU12P4Pc1Uld32ZQ+RzdqUMrm+Inb5+Swn9NJ8X8FQgghhJg1rNZWYj/9WeH7yPbtaIYxgysqze0SMnJzZF44vBKlTI4c+VcATLObbLYbn6+u7GdYuZptfXAZib8Zy+rFtlMjRsWPxXEyAHRPcbAd691JtOYCNG1q8511tZcQDC7j1Ve/yKFD3+Diix/G75s3pc8UxUlmWwghhJjlsseOAdBwy2eIvu2PqXvve2d4RaW5wfbIeu28UMjtGBGLP0/AvwBg3CPcbWtkZtsfcO9VSSmJ7biZ7UTi5SmrdU6nW0lnWohGN03J/Qerrj6bSy5+mI3n/Se23UdP99S+iRCjk2BbCCGEmOWyR48CEHnjG1nwpS8RXH/ODK+otLGC7XB4oD3bsmUfARh3KclAGclAzXbAX3mvbcfJ4PPNAxQ9PU+N+/pyxGI7AaiJXjAl9y+mru5SdL2anp4npu2ZYigJtoUQQohZLnv0GJrfj97QMNNLKctYZSS6Xo3P14Bh1NPc/DY8Hv+4M9tWLtjWh3Ujgcoy246ToSZ6IV5veMpKSXpjO/F4glRVrZuS+xejaV5qai6ip/fJaXumGEpqtoUQQohZLnvsGL4lS9A8cyNHZtv96CUy2wBLl3wAXY/g8fgIhVbQnxxnZtsa2mcbwO9vBKio17bjZPDqVdTUXDhlmyRjsZ1EI+fh8UxvvX1t7VZOnfo16XQLgVypjZg+c+NfrRBCCHEGyx49grF0yUwvo2y2ncQzxgbFJUv+nAUL3g7gBtv9489sa5oPj8dXOObx+PH55pGpoCOJ42TwePzU1V5CMnlo0idRWlY/fX17idZMXwlJXl3tJQBSSjJDJNgWQgghZjHlOJjHjuNbsnSml1I220oWHdU+mnB4Fen0Cexc+72ynmEnh5SQ5AX8CyrKbNt2Gq/HT20hMJ3c7HY8vgul7Gmt184Lh1djGHV0S7A9IyTYFkIIIWYxq70dlc3iWzJ3MtuW3V9yg+Rw4dBKQI2rbtu2+ovWhYfCK4nHX8JxzLLvpZQqZLarqtbmAtPJDbZjsecBjUhk+vuja5qH2tqt9PQ8KUNuZoAE20IIIcQslj3qtv3zLZvezPYrez5Da9t/V3TtWN1IhgvlupP0Jw+WfY1l9xXNnjfMfyOW1Ut3z2Nl30spE3DwePwDgWn3E5MamPYnDxAILMQwIpN2z/Goq72ETKaV3tjUTsgUI0mwLYQQQsxCyraBgbZ/05nZdhyT1tafcerUw+O+VikHx0mV7EYyXCi4HPCQ7B9HsG0l8OpVI47X178GXa+mo/0XZd8rP9DG4w0AucA02z7uDimlJJNHCIWWT9r9xqup6Tr8/iYOvPp3KOXM2DrORBJsCyGEEGMw29s59Oa3kN6/f3qe19HBgT+6gu47f0D22FE0nw+9qWlang2QybQBDplM+7ivte0UAF69/My21+snGFw0rsy2aXbjM0ZOnPR4/Myft42Ozoew7UxZ9yoE2x4/QKFue7JKSZRSJJOHCQWXTcr9KuH1Blm54lPEEy/S3n7fjK3jTCTBthBCCDGG2H/fS2b/flLPPT8tz+v67nexOjvp/Kd/IvXc8xiLF09r279U6jhAhcF2EmBcmW1wNzaO53nZbFduCM1IjY3XYNt9dHf/vqx75YNyr8fNbAeDSwgEFk7aJknT7MK2+wiFlk3K/SrV1PQWqqvXc+DgVwtvisTUk2BbCCGEKEEpRfx+NxNotk1uO7hizI4Oeu++h/All+CYJqnnn8e3dHrrtdPpkwBkMh3jrlsemOxYfmYbwPDVYZo9ZZ2rlI1p9mD46ou+Xlt7MYZRS3tHeaUkwzPbmqZRW3tJbkOhXdY9SkkmjwAQnOFgW9M8rF71OTKZNo4d/96MruVMIsG2EEIIUUJm3z4yr7oDV6zWtil/Xvdtt6Esi6Zbv0DdjTcC01uvDZBKu5ltpbKYZve4rh3IbJfusz2cYdSWHWy75yl8owTbHo/B/PlXcerUw2VlcB0nnbvOXzhWV3sJlhUnkXi56DVKOWUH4vlg261Nn1m1tVuYP/8qjh79NplM50wv54wgwbYQQghRQuy++0DX8S1fjtk6tZltq7OTnh/dTfTNb8a3ZAnzPvwhgpsvIHzZZVP63OHSqROFr8dbSlJpGYnPqMM0e8sKYLPZrtw1xYNtyJeSJDnV9ciY9xvYIDkQbNfW5eq2u4uXkhw6/A2efuYtY94bIJk6jKbpBAILyzp/qq1aeTOOY3Lo0D/N9FLOCBJsCyGEEKNQjkP8FzuouvRSAuvOwmyb2sx213fdrPa8D/0FAN7qapbdeSdVl106pc8dLpU+USgDGX+w7ZaRjDWufTjDqAUUptk75rmFYHuUzDZAbc0WfL75tLffP+b97CKZbb9vHuHwmlHrtjs6HqS/f39ZnT2SySMEg0vwePQxz50OodAyFi96Dy2tPyaR2DPTyzntSbAthBBCjCL57LNYbW1Err0WvakZq61tyoaCuFntHxG99tppr9EeLp06QSSyERh/sG1VmNl2g23KKiXJmm6wPVrNNoCmeWlouJqurt9iWX0l75fPbOc3SObV1V5Cb+zZEV1NMpl2kskDKGVjWbEx15ua4U4kxSxb9lF0PcqrB/6u6N9p287Q1f3oDKzs9CPBthBCCDGK+H33o4VCVL/uCoymJlQ2i909vhrmcnXd9j03q/3hD03J/cvlOBky2Xai0fMBbdzBtlMItseX2c5nqbNlBNtmGWUkAI0N23GczJj9wodvkMyrrbsEx8kQiz835HhPz5OFr/NZ9tEo5ZCrmnnsAAAgAElEQVRMHZ3RHtvFGEaUFcv/Fz09j9PV9dshrynl8MqeT7Fr10309x+YmQWeRiTYFkIIIYpwslniDz5I9euvxBMKoTe7fa7NKdgkaZ065Wa1r7lm5rPa6RbA3czn89Xnem6Xz6ow2C5ktrNjv5lxA1wPhlFT8rxodBN+f9OYpSSOnQ+2h2a2a2u2oGleeobVbQ/uvz1WsJ3JtOE46RnvRFLMwoXvJhRazqsHvkQyebRw/PDhb9LRsQOorP2jGEqCbSGEEKKI/t//HiceJ3rNNQAYTc0AWBNs/2f39tJ1220ceusfk3jE3bzXddv3UNnsjGe1YaDHdiC4CL+/kUx2ejZIDpSRlBFsm134fHVoWukwRtM8NDZsp6v7D5jm6OUeo2W2db2a6uoNdPc8UTimlKKn+3GCubKQfEnLcEopHMca1Ilk2Rg/1fTzeAzWrLmVVOokTzz5ep577k948qmrOXzkX6iJXghAdpzdaMRIEmwLIYQ4YzmZDKe+/W3seHzEa7H77sdbV0f4ErcrhTEJmW07Hufg9mvo+No/YJ44Qetn/zfpvXvp+eEPiV57Db5lyyq+92RJpd1OJMHAIvy+xgo3SHpGBK5jMXLTIMuq2c52jVlCktfYeA1KmXR2PjTqOYUNkt6Ra66t2UIisbtQt51KHSOdaaGp8drCWoo5cuRb/O7353Hg4FcAZnygzWjq6y7j0kt+x9KlH8Q0ewgGF7N8+cc55xy3U0k5nzSI0iTYFkIIccbq+93v6PzGP9PxtX8Yctzu66PvkUeIXH01mu52kPDW1aEZxoQG2ySffRa7q4tF3/omy+7+EU4qxZF3/wkqm6X+QzOf1QZ3c6SmGfj9DW5mu4LWf15vCE3TxnWd1+vH6w2XlUk1S0yPHK66+lyCgSUlB9wMbJAcGWxHIuehlElfn9u1I9+dpKHhTYBWNBhVyuFky90YRg39/QfR9Rr8/qay1jsT/P4GVq38NBddtIPzNnyHFcs/ht/fCGhl9z4Xo5NgWwghxBkrtesFAHp/8hNSL7xQOJ546FeobJbotdcUjmkeD3pT04QG2ySf3YlmGIRf8xr8K1bQ8KlPoZJJotdeg3/57NhAl0qfIBBYgKZ58fsbMc2eEd04SrGt/nHXa+cZRi1mtrxuJKU6kQymaRoNjdvp6Xl81Cy0Y49s/ZcXiWwAIB53/3509zyO39dIOLwaw6gtWkYSiz1HJtPKqpU3c9mlj3PRlvvGLHmZbTTNi2HUSBnJJJhbf/JCCCHEBFhdXbR89n9j5TqKpF54Af/atejz59P2f/8GZbsDVeL334exeDGB884bcr3R1DShXtvJnc8S2LABj98N6mr/5N0s+NpXabjllorvOdnS6RMEA4sBCtnY7DjqtvOZ7Uq4UyTL2SDZXbLH9nCNDdtRyqaj88Gir7uZbQ+aZox4ze9vwudrIB5/Ecex6O5+jLr616BpGj5ffdEAvr39fjyeAPPmvR7DiBIILCh7rbOJYdRJGckkkGBbCCHEGaP79juI/fznxO79H1Q2S3r3bsJbt9J4y2dIv/IKPXffjdnRQf+TTxG5ZvuIUgi9uaniMhInmST98iuENm0qHNM8HqLXXoteWzuhn6scqdTxwubF0uedIBB0Jx26pQSQHkcpiW0n0ce5OTLP56sbs/Wfbaex7b6ya7YBqqrOIhRaOWpXEsfJ4PH4i5a+aJpGJLKBeOIFEokXsawY9XWXA/lgtGvYvSzaO3Ywb97r0PXKfh9mC59RN+WZbaUUHR2/xHGsKX3OTJJgWwghxBnBSSbpueceABIPPUR63z5UJkPw/I1UX3014UsupvMb/0zPnT8AxyF67bUj7mE0NWO1dxQy4OORevFFsCxCmy+Y8M8yXkopnnn2rRw58m8lz7PtNKbZVRgrng+2s+MMtqcys51/fTyZbU3TaGzYTm/v00Vr0G0ng9cbKHKlKxo5j2TyMG3t9wMe6uouLaxheBlJT88TmGYXjY3XFLnT3GL46sr6pGEienuf4qXdHx3R6/t0IsG2EEKIM0Lvf/83TixG+LWXk3r+eRIPud0pghs3usHY5/8PTipF13e+Q+Dss/GvWDHiHkZzE9g21qlT435+cudO0DSC558/4Z9lvBwnjWn2EE/sLnleJuNm7QN+t81hvoxkPJskbTuJV6802K4bc0NeNuv+3pdbs53X2LgdUHR0PDDitXxmezSRiFtO1NLyIyKR8wr9vYuVkbS334fXW0V93R+Na32zkWHUjtlHfKL6+vYBkEodHePMuUuCbSGEEKc95Tj03PF9AueeS+OnPw1A93/did7YiNHkBpT+Fcupf+97AYhcUzwrqefOtVrHX0qS2rkT/9q1eCORSn6ECTFzI8X7+18teV46nQu2czXGuh7B4wmMK9i27H68nsqCbZ9Ri20nsXMbFovJljk9crhweBVVVWcV7UriOGk8Ht+o11ZXn5s7L0N9/eWD1luPZcVwnCzgjjjv6HyQhvlX4S3SRnCu8Rl1mGYvSjlT9oz838l8f/fTkQTbQgghTnt9v/0d2aNHqfuzm/CvWoVvxQpUOk1w48Yh5837yIdp+NRfU/OO64vex1jolldkj44vC6csi+SuFwhdMP0lJABWbqBLJtOGaY7sKZ6XzrjTI/25zHZhE+A4Sgmy2c6y2/INN9Bre/Tn5cs2xlNGktfYsJ1Y7LnClMw8N7M9ehmJYUQIhdxPOvL12jCQXc9n47u7f4dt950WJSTglpGAg2n2Ttkz8uPgU+ljU/aMmSbBthBCiNNe9x13oDc1Edm2DYDqbW8AGBFsewIB6t//frxVVUXv41+5Ek8kQv/TT4/r+dmjR1HJJMEN51aw+okzrYEAuz85enZ7ILM90BNa1yNYVqKs59h2EsuKV9xT2vDlp0iOXkpSyGxXEGw3NGwHGJHdHquMBKAmuhnDqCcSGfgzzK8hv6a29vswjDpqay8Z99pmo/ynB1NVt62Uok8y20IIIcTclt6zh+RTT1F345+iGW5rt+ib34K+oJmq114+xtVDaV4v4Yu2kHziSZRSZV+XPeZm7XxLl47reZPFGjSqvL9v/6jnZdIt+HzzhwSebrA9ejZ8yPW5cpP8xsrxyme2S3UkMbNdeDzBijZhhkJLqa4+d0Tdtm2n8ZbIbAOsWvVZLtz8MzTNWziWD0az2S4sq59Tp35DQ8Ob8Hj0ca9tNnIz226rxamQNbuwrF683ipSqRNTWq4ykyTYFkIIcdpQShUC27zu2+9AC4WouX6gNMS/Yjmrf/ObopsgxxLauhWzpQXzePmZODO3JmOmgm1rINjuK1G3nc60FjZH5ul6ddnBdj4z7g9Ultn2GbnMdrYbpVTR/7LZroqy2nn1dZflxq+nCsccJztmZtswIgSDi4auN5/ZNrs4derXOE76tCkhAbdmG4pnthOJPezceQOZ7Pg3C+fl67Xr6i5Dqey4p5XOFafHWy8hhBAC6L3nx7R94Qssuf0/CW/ditnRQWzHDmrf+c5J25gYvvhiAPqfeBLfkiVlXZM9dhxPVRXemppJWcN45ctIgsFl9PePntlOp1sIh1cPOWboEfpK1HkPlg+WApWWkQwK7na//L/o6NhR9LxIpPKOLtHoJpSyiSd2U1tzIZDbIJnL4o5HvjbdzHbR0/Mkfn8TNdGZqcufCoXM9rBgWymHvfs+Rzz+Aqc6f8XChe+q6P75YHv+vNfR2flLUqnjBALNY1w190iwLYQQ4rRgdnTQ8Q//AEDP3XcT3rqVnrvuAsui7j03TtpzfMuXozc00P/kE9S+8x1lXZM9fgxjyeKiQ1Omg1tGolET3URX9++LnqOUIpNpHdJtA8CrV2PZ5dVsZzLudM3Ky0iigEZr689I9L1MU+N1BEMjPw2om0BNdL6NXzz23KBge+ya7WK83io0zUd/8iBd3X9g8eKb5txY9lIGf9IwWEvrj4nHX0DTDLq6fz+BYPsAuh4hGnUHPaXSx6hly8QWPQtJsC2EEOK00P7lL6MyGaquvJLErx/GbGmh94c/ourK15WdgS6HpmmEL95K3+//gHIcNM/YwZV59Bj+s86atDWMl2nF0PVqqqrOorXtZ7lx50MzuZYVw7aTBPxDR4sbegTL6kMpZ8xAMpNpR9ejFQ+10TQvhlFDou9lwuHVrFv395Ne/+zz1RMMLiEW31U45tjpioJtt1tLHe3t96GUSWPD6VNCAuDx+PF6q4Zktk2zl4MHv0ZN9EJC4RW0t/8CxzHxeEaOuh9Lf/+rhMOrckOUPKftJsnT5+2XEEKIM1bqxRdJPPBL6j/0FzR8/K/ANDn+0b/EjsWov+mmSX9eaOvF2D09ZPaPXpKRp2ybbEvLpAb842WZcXQ9WigRKdZve3iP7TxdjwAKy+ob8znpTGvFWe28fCnJ6lWfm7KNhtHIJmKx5wubXG0nM+YGydH4fPXYdpJgcBnV1esnc5mzgm/YFMmDh/4Ry4qzdu3/pb7+tdh2H7HY8xXdu7//AOHwajweH4FAM2kJtoUQQojZKf3KHgBq3vpW/KtXE9y4kcyePQTOOYfg5s2T/rzgeRsAygq2zdY2ME2MJYsnfR3lMq1eDCNCuGoNAH39+0acU+ixHRi+QdKtdS9nk2Qm015xvXZeNLKRxoZrqK9/zYTuU0okupFstrPQb7vSMhIY6EjS1HjtjJUJTSXDqC+UkcTjL3Ly5A9ZtOg9VFWtpa72EjRNH1KaZJpx9u27dcw3Z9lsF6bZXXgDGAwsJpU6PXttS7AthBBizsseP4bm86E3ulnVmne4tdR1f3bTlARA+Y2OdmzsANQ85g7A8S2eycx2DF2P4vc1Egws4eDBf6Sj48Eh5+QDz+FlJLpR7d6jjF7bmUxbxT22884++6ucc843JnSPsUSj7gbLWPw5IBdseyvLbOcH25xOXUgG8/nqyJrdKOWwb/+t+Hz1rFj+V4DbqSYa3UR310Cw3dPzOCdO/hfd3Y+WvG8yeRiAUGg5AMHgElJpyWwLIYQQs5J57DjGokWF+unodW9h8Xe/O+rY9YnKdzax47ExznQ7kQD4ls5csG1acQyjBk3T2LTpB4TDK3lp90d49cBXcBwLgEy6FU3zjWirp3vzwXbpNxaOY5LNnppwsA1MeYa4KrwWjydAPLYLpWyUMivObDc0XM2iRe8hHF41yaucHQyjDjPbXdgUuWrVZ9H16sLr9XWXk+h7mUymE3BLiQASiZdL3jc/uCjf0SUYXEw2ewrbTk7FjzGjJNgWQggx52WPH8dYPNADWfN4qLrs0ikL2jRdxxMO48THzmwPz7rPBMuKFcpBAoEFXLDphyxc+KccO/b/eH7Xe8hkOklnWgj4m0ZsgtSN8spIstlOQE24Zns6eDwGkcgGYvFdOE4md6yyYHv+vCtZu+YLk7m8WcVnuJnt/KbIpsa3DHk930mkr28vMNCRJtG3p+R988G2obsdT4JB983o6bhJUoJtIYQQc5pSCvP48Wkv0/BEI9i9Y2e2zWPHhmTdp5tSCtOMY+jRwjGPx89Za/8vZ5/9j8TjL/D0M28mFnt+RL02uN1IYOxgO5/RrHSgzXQLh1aSSh0rBNveCoPt053hq0MpE8uKs2btrSPewAYC7pvcdPokMNBrvS/xSsn7FoJto2bIfVLpE5O3+FlCgm0hhBBzmt3Tg9Pfj2+aNyB6I1HscjLbx47jWzxzmyMdJ41SWXQjOuK15qbruHDzz/B6g6TTJ4oOFMlnxM0xgu2BgTZzYyiJP9CMaXZj5gb2VJrZPt3le20vWngj1VUj21e6n2R4ChtsM2k3s53JtpMtMV3SNHvRNF+hTaTPN989nu2azOXPChJsCyGEmNMKo9CnOaD1RiJjBttKKbfEZUbrtd3sez5DPVxV1Vq2XHgvixf/OQuaRw7p8XqrAMbsLpEPsuZCGQkMTLlMpdwNrJ4KW/+d7urqXsOiRTeyYsXHi77u8ej4/Y2FzHY604bP1wC4I91HY5q9hX0EQGGvQFaCbSGEEGJ2yR53P3ae7uyxNxrBGWODpNXejkomZ7wTCVA0s52n69WsWf05amsvGvGax6Pj9YbHLCPJZNrweALo+ujPmU3yGzmT+WDbK5ntYvz+BtauuXXIpsjhAoGFpNMtuSmk7cyr/yMA+vpGLyUxzZ5C1hzA6w24A3RKZMPnKgm2hRBCzGnZ47nM9qJFY5w5uTyRyJit/xK/+Q0AoS0XTseSisqXfxgTCIJ1vbqMmu02/P7GOdNr2p8rd0kljwBUPNRGQDCwkHT6JKbZjVJZqqrWEvAvIFGibts0e9Fz9dp5bptByWwLIYQQs4p57Dh6YyOewPQGS+XUbMd37MC3aiX+NWumaVUjWWYvMFB7XQldj5SV2Z6Mtn/TJV/uUshsS812xQKBBWQybYVSEr+/iarqs0t2JMmaPRiDMtvgtgGUzLYQQggxy2SPz8wGRG80gkqncbLZoq+bbW2kdj5H5OqrZzTbW6jZLlFGMhY32C491CadbpkzmyMBdD2MrkdIpY4AEmxPRCCwEKVs4vGXADfYrq46m2Ty0Kh9s02zp9CJJM9n1JdVs93V9Xscx5z4wqeJBNtCCCHmNPP4cYwl018T7ckNtnFixeu247/8JShF5Oo3jfvejpPllT23EE/sntAaAaxct42J1FIbY2S2bTtJJtNamAY4V/j9TaRSbs2/BNuVCwQWAhCLuRM5/YEmqqvPBlQhAB9MKYVlxUZktg3f2MF2X/+r7HrhvXSe+tXkLH4aSLAthBBiznLSaayODnyLp7deG8AbdYPX0UpJ4g88gH/dOvwrxh+AtrT+hNbWH9PR8cshx5VSnDh5VyFAHE4pm5MtdxdGr0M+s62V3OA2Fl2vxjJHz2wXRm+HV1b8jJkQCDSjlJshlWC7cvlguze2E/DgM+ZRW7sVj8dPe8cvRpxv230oZY3MbPvmYZo9KGWP+qxCP+9c95u5QIJtIYQQc5Z53J02Z8xAtw9vJBdsF9kkmT1xgvQLLxJ509Xjvq/jZDhy5F+BgbZ0ebHYTvbt+z8cOvyNotft3v1X7N37vzly9D8Kx/PTI4dPhhwPXY+U7LPd338QcAfFzCWDa8y9XtkgWalAYAEA6fRx/P4GPB4dXa9m/rw30N7+CxxnaKnV8IE2eW77P6fwejHZTIf76xyq7ZZgWwghxJyVOXIEAN8M9LH2Rt0yErtI+7/4Aw8AELl6/MH2yZZ7yGTaMIz6EcH28RN3ANDZ+SCW1V84blkJdu36czo6H0DXa0gMKj+xhk2PrISuV2PbCZRSRV9PJg8BHkKhpRN6znTzD6oxl8x25bzeIIZRBwx9A9PUdB2W1UtX12+HnG/mNu36ctfk+XzzgNK9tjOFYLtzwuueLpMSbGuadkTTtJc0TduladqzuWN1mqb9StO0V3O/1g46/7Oaph3QNG2fpmlXDTp+Qe4+BzRN+xctt6NE0zS/pml3544/pWnasslYtxBCiLktvftl0HX8q1dP+7O9+ZrtImUk8R0PEDhvA75xtiO07QxHj/w7NdELaWi4mlTqWCHATadb6Ox8kJqaLdh2ks7OhwDIZDrZ+dy76Y09y9ln/yMLmt9GX98eHMcC3DIS3ai8EwmAbkRQyh51s1t/8iDB4OI5F7AGBgWGc23ts02+lGTwUKO6utdgGHW0tt075Nys2Q0UyWwb+cE2o2etM9kzO7N9hVJqo1Jqc+77W4CHlVKrgYdz36Np2tnADcA5wBuBf9M0zZu75t+BDwKrc/+9MXf8fUCPUmoV8HXg7ydx3UIIIeao9EsvElizZtrb/gF48jXbvUMz25lDh8ns2VNRVrul5Ydksu2sWPFxQsGlWFYCy3KzgCdO3IlSirPXfY1AYDFtbT8nmTzCzp3vIJk8zHkbvkNz03VUV6/HcTL0Jw8A7lCbiWe23WB9tE2Syf6Dc66EBIZmYWWC5MTkS0mG/p7qNDZey6lTv8E0B/6d5DPbI1v/jT1FcqCMZO70457KMpK3AHfkvr4DuG7Q8R8ppTJKqcPAAWCLpmnNQEQp9YRy38Z/f9g1+Xv9BLhSmytd84UQQkwJ5TikXtpNYMO5U/4s206yd+/nSadbC8e81e6Gw+EbJOMP7ABNG3ewbdspjhz9NjU1F1Fbu5VgriQjmTyK42Q52XI38+dvIxhcRHPTW+nueZxnd16PZSfYdP6d1Ne/FoDq6vUAJOJuKYlpxUtOjyxHfnNlsWBbKZtk6jCh8IoJPWMm+AODA0PfDK5k7stntgODMtsAzU3XoVSWjo4dhWOj12znykhKDLY5kzPbCnhI07SdmqZ9MHesUSnVCpD7tSF3fCFwfNC1J3LHFua+Hn58yDVKKQuIAfWTtHYhhBBzUPbIUZxEguC5G6b8Wae6fsvJlh9y7Nh3C8c0XccTDg+p2VZKEd/xAMELNmE0Nha7VeG81tafDdkIdvLkXWSznaxY/nEAgkG3Dj2VOkYi8QqW1Utj4zWAWwsLCq8nwAWb7iEa3Vi4Tyi0DK83XKjbNs3eCQ20gcGZ7ZEdSdLpkzhOdk5mtvN9wT0e34Q2kIrBme2hvdarq88lFFpJ26BSEjezrY34e+lu5NXLq9k2u1DKmaTVT63J+pt1qVJqE3A18FFN0y4vcW6xjLQqcbzUNUNvrGkf1DTtWU3Tnu3snDuF80IIIcYv/dKLAASnIbPd3fUHAFrbfoZtpwvHPdEIzqBuJJn9r5I9eJDIm0r31m5r+xmv7Pk0hw79M+Bmzo8c/TZ1tZdSW7sFgGBgCaCRTB2lN/YsADXRCwAIhZay6fwfsvnCnxMellHWNA/VVWcTT+wmk+nEsnpHbEQbL6NEsJ3vRBIKzb3Mtq5X4fVWSb32JAgF3U9i8kF3nqZpNDddR2/smULLSvcNYJSBKuL8uR58Rj3mKMG2Ug7Z7Cm83hBKWVhW8R73s82kBNtKqZbcrx3Az4EtQHuuNITcrx25008Ag0d9LQJacscXFTk+5BpN03QgCnQXWcd3lFKblVKb58+fPxk/mhBCiFkq9eJLeEIhfCvKD/LS6VZe2v2xcdV7KqXo7nkMv78Zy4oP+Th8+Mj2+I4d4PEQueqqYrcC3EDj1QNfAaC17aeYZpwTJ+7ENLtZvuKvBu7t9eP3N5JKHSUW20kwsAS/v6Hwem3tFvy5j92Hq46sp6/vFQ4d/jqg0dz8x2X/vMWUKiNxO5FAeI712M4LBJqlXnsS1NVdzvr13yKae0M4WGPjmwFoa/tvwC0j8flqR5wHucE2o5SRuD24TaqqzgIgM0dKSSYcbGuaFtY0rTr/NbAN2A38D3BT7rSbgPznB/8D3JDrMLIcdyPk07lSk4SmaVtz9djvGXZN/l5vB36jRus/JIQQ4oyQeuklAuvXo3m9Y5+cc+jQP9HRsYOe3qfLf07qCOn0SZYt/TDB4DJOtvyw8Jo3EikE20op4g88QHjrRej1o1c6Hjj4NSwrxrp1f49tJzl+4g6OHvsOdXWvKWSu84LBpaRSR+nt3Um0ZmQQM5r8JsmWlrtZsOCGCU92zH/cX6zXdn/yIIZRN2Kz21zh9zdJZnsSeDw6jQ1XU2xLXTC4iJqaLbS13+tOjzR7MfSaIndxN0mOVo+dybX7q646B5g7dduTkdluBB7VNO0F4GngF0qpXwJfAd6gadqrwBty36OUehm4B3gF+CXwUTUwKujDwHdxN00eBB7IHb8NqNc07QDwSXKdTYQQQpyZnGyWzJ494yoh6evbR2vbzwFIp46VfV1X96MA1NVdxsKF7yIWe45Tpx4B3F7bTq5mO737Zcxjx0qWkGQy7bS03M2ihTeyoPnt1EQv5PDhb2CaPYVa7cFCwaXE4y9hml0jAvFSIrlNkl5viOXL/rLs60ZTLLPtOBni8ReJxZ6fkyUkeY0N2wu18GLqNDVdRzJ5iETiJbJmz6hvznwlRrZnM+0AuVHwcyfY1id6A6XUIeC8Ise7gCtHueZLwJeKHH8WWF/keBq4fqJrFUIIcXrI7NmDMk0C49gcefDQP+H1hgFIpYuPOy+mu/tRgoElhEJLWdD8dlpa7uaFF9/PggU3EKmpKkyQjD/wAOg61a9//aj3isdfABSNjdsBWLz4z+iNPUN9/RVDNjnmBYNLC+PEi308P5pQaDnB4FIWLngnfv/Eyyo9Hh8eT4DenqfZlz1FPP4CicQelHInAy5b+pEJP2OmLFgg4cV0aJh/Nfv330pr288xzR6qc6Ugw/kMN7OtlBqRJc9k3Mx2VdU64AwKtoUQQojp1nPPPWh+P6ELN499MtAb28mpU79mxYpP0tn5IKnU8bEvAhzHpKfnSZoarwXcVmVbLryPgwe/xvETt+Nd8gaceBzlOMQfeICqSy/FW1P843GAeGI3muYtBAvz5r2eZUs/THPz24ueHwy5HUl0PUo4vKqsNQNompeLtz5c9CP9Svn9DXT3PEos/hzV1eeyePFNRCMbiUQ2jOhAIcRwhhFhXv2VtLffj20nS2S25+E4aWw7ia6Hh7yWybqZ7XB4NZpmSLAthBBi7ord/wt6776bJXfcjuaZ+pZoTjJJ4pFHiLzpTWMGiNkTJ4nd+z/U3nADet3YXTaUUhw88DV8vnksWfxe+hJ76OvfW9a64vEXsO0+6upeUzjm9QZYtuwjHD9xO061QqXT9D/2OFZrK5FPjCwFGSwRf4lwaBVebxBw61xXrvzUqOfn2/9Fo5vG3ZpussdRnL/xDiw7SVV49YguEkKUo6npOjo63Qrh4T228/KDbUyza0Swnc10outRvN5Aydru2UaaSgohhBjC6e+n/ctfJvnMM1id0/M/s9j999Py158is3fsILjru/8PTdOof//7yrp3V/fv6I09w/JlH8PrDREILiKVOllWj97u7kcBD7W1Fw85rucmMjoh9x6n/uPbeKJRqrdtG/VeSiniid1UR8qvMw8Fl+HxBKiru7Tsa+LvCWsAACAASURBVKZKMLiE6qqzJNAWFauvv7yQ0dZHCbYN3+gj2zPZ9kJHHgm2hRBCzFldt9+O3eVuUDJPnpyWZ2YPHQYgvW9fyfPMtjZiP/0Z0bf9MUZTU8lzwe3Le/DgPxAMLGHBgncAEAwsRqlsYRJdKV3djxKJnIdhDB2+4fHoeL1V2AF3f3/q2Z3U/PEflxwbn8m0YprdhQmP5dD1Ki7e+isWLbyx7GuEmK08Hh+NDe5m1NF6v4eCywCNvXs/TyLxypDXMplO/L58sD1Pgm0hhBBzj9XVRfdt38N/lrt5adqC7SNHAHcoTCld370NpRTzPvCBsu7b3n4/fX17WLHiE4Vx3MGgO9IhnSq9SdI048TjL4yaVTaMWmzDLHxfe8M7S94vnngJGOgUUq5AYAEej1R9itPDggXvxOutGnUPQii0jPM2/D+yZjfPPPtWDh/+Fo5jAW43El9uw6/PN1+CbSGEEHNP5z//C04mw4Iv/x0wE8H2/lHPsTo76f3xj4m+5c0YCxeOeU/HyXLo0Nepqlo3pLVbIODOVUuN0f6vp+cJwKF+UL32YIYRxdbdaZLhSy/Ft3Rpyfsl4kM3RwpxJqquXsdrL99VcsPvvHlXsPWiB2houJpDh7/Ozp3X09f/KplsJ35fI5DPbHczF8auSLAthBACgOTzz9N7zz3U3XgjgXXr8M6bNyTYVpZFeu9eUi/tntTnKssie8LNMpcKtru+958o02TeBz84+r2Uor//AErZtLTcQyp9jJUrPzVkc2EgsBDQxmz/1939B7zeKiKREd1tgXxmO4ve0FBW/XgisZtweDVer0wrFGe2cjbvGkYt68/5BuvXf5NU+jhPP/1mlDIHZbbnoVS26FTT2UaCbSGEECjTpO0Lt6I3NTH/Y+4QFGPhgkKwnfjNI+y7cAuHr3srR264Abu3d1z3Tz73HIff8U7MtrYRr5knT4Jl4V+9Cqujo+i9re5uen70IyLXbC+ZQe7ueYwnn7qKxx6/nIOHvk5NzRbq61475Jz8GPT0GO3/ursfo7Z2Kx6PUfR1Q49iqT5W//53hC++uOg5eYXNkdXlb44UQkBjw5u46KJfUl9/OUBhGqrPyG+k7JyxtZVLgm0hhBB0/9edZPbvp+nzn8MTdttt+RYuHAi2f/1rNJ+P+g98AGyb9J4947p/3yO/Jf3ii5z85F+jTHPIa/kSkuo3uJ080kWy292334FKp5n3oQ+VfE5vz5Nomk44vBrHSbFq5c1Fs2iBwKKSme1k8iip9LGSXUAMoxbTLO9NRybbntsceU5Z5wshBvh989hw7re5eOuvC2+efb55wNwYbCPBthBCnOHMlhY6v/lNqq64gqorBwb/GgsXYra0oByH9O7dBM/bQN17/wyA9CvjC7bT+/ehhUKknnuOzn/+5yGvFYLtbW8ARm6StHt76fnBD6h+41X4V5QeCx6L76Kq6izO33g7f/Tal4lGzy96XjC4qORgm+6exwBGrdcG0I0olhUrq4Vg/llupwUhxHhpmkYotLzw5jlfTpKRzLYQQojZru1L7mbIps9/bkgW2Fi4EGWamMePkzl4kMA556DX1aE3No47s53Zu4/IG15PzTvfSdd3byPx298OvHbkCJ5oFP/atXiiUTLD2v91/9edOP39zPvQh0s+QymbePxFohE3wC5VFxoMLCaTacNxskVf7+7+A4HAQoIlgmN3KIcqq2Y03/kk3wlFCDExgdzU0ky6dYZXMjYJtoUQ4gyWePhh+h5+mPl/+dERHT7y3yce/g3YNsFz3BKIwLp1pPeWH2xbPT1Y7e3416yl8bO34D/rLFo/cwtmSwvgZrZ9y5aiaRqB1auHbJK0Ewm6v/99ql5/JYG1a0o+p6//VWy7f9Rs9mDB4GJAkU6P7LbiOBY9PU9QV3tpyYDd0N2hHOWUkuRLVvz+BWOeK4QYm65XoetR0umWmV7KmCTYFkKIM5TT30/bF7+Ef80a6t7znhGvF4Lthx4CIJAPts9eR/bQYZxUqqznZPa5wbN/7Vo8gQCLvvF1lGkW6rezR47iX7ascE7m1VdRjlua0fODH+AkEsz7cOmsNkA89jwAkcjGMc8Nh1cD8Mqem+nrG5pJTyRexLIS1NWPXkICA+Omywm20+kT+H2NeL3+Mc8VQpQnEFhQ9A3zbCPBthBCnKE6//XfsFpbabr1VjRjZMcNY4GbhU3t2oW3vh49N7HRv24dOE7JNn2DZfa7wWzgrLUA+JYto/mLf0tq1y7av/wVrNZWfIVgew1OMklq1y6c/n66b7+Dqte+tpBVLyUW34Vh1BEMLhnz3EhkA+vW/T3J5GGefubNHDjwVWzbffPQ1f0YoFFXW7rDSCHYtsoItlMnCATH7g0uhChfILCQdEYy20IIIWah9L59dN9xBzXXX09oU/GyC08ggHeeu+M/cM7ZhZKKwLqz3XuUWbed3rfPDdZz9wKIvOlN1LzrBnruugugEGxH3vhG9OZmWj77Wbpu+x52by/zPjJ2VhsgFttFNLKxrB6+AAua387Wix6iqfEtHD32Hzz51Bs5deoRurv/QKT6XAyjtuT1+WDbMmNjPiuVPkEwN0xHCDE5JLMthBBiVlKOQ9v/9wW80SgNf/3JkucaC93sdmBQZtlYuABPNFp2R5LM3n1F660bb7kF/9nuNMV872xvJMLCr/495rHjnPq3fyN8ySUEzys+VGYw04yTTB4gEh27hGQwn6+Os8/+KpvOvwuPJ8ALL76fWGxnyZZ/eQNlJD0lz3Mci0ymNTdMRwgxWQKBBVhWAstKzPRSSpJgWwghzjC9P/4JqRdeoPEzN+OtqSl5ri9Xtz24jEPTNAJnnVVWZltZFpkDB/CvWTviNY/fz+JvfpN5f/mX+NcOvB668ELqP/QXoGnM++hHyvqZEomXAEad9jiW2tqLuGjLfaxc8df4/U00NGwf8xpdjwAa5hiZ7UymDaXs3KZMIcRkyb+BTc3y7LYE20IIcQaxurro+Md/JPT/s3fe4XGU19u+Z7b31arLsi3J3XKvgDGmF9MSWgihhxQINY0ACUm+9EAIEEhIgNB/EEpCsYMBh2KKG+6Wm2z1XlbaXW2f8v0xkmxZ1baKwXNflyxr95153xmtdp8585xzFizAfcEF/Y7vSJK0HuSZtk6ZQnzPHlRJ6nP7REUFajyOZXJ3sd2x//Sbv4dgMHR5PP3WWxn/4QfY587td42wv9au7Qiix6JoJi/vJk5c9Cku15R+xwuCAaPRTVLqO7Ida69Eoke2dXQGl46/qfhRXpHEONIL0NHRGV5UVR2wp1Xny4f/6adRIhGyfvHzAb0OvBdfjDEtrTM5sgPrlMmo8TjxkhKsE3svyddRM9s6qWex3RuCIGDKzBzw+A4rR38+68HGZPL0G9mOdoptvca2js5gYm0vpalHtnV0dI4apJYW9hx3PG0ffzzSS9EZIcKr12CfNavfTowdmPPy8F1zTTdhbpmiRX7j/VhJOmppm0YPrYUimfB3RpqHE5PR269nW2toI2K1Zg/PonR0jhHM5lRE0XzUJ0nqYltH5xgiumkzSiAw4JJtOl8u5LY2Yjt2YF8w/4j3ZSkoQLBYiO3c1ec4qdmPYLEgOhxHPGdfJJMtGI1eBGF4P9ZMJm+/1UiisSoslkxE0TxMq9LROTYQBBGLJeeob2yji20dnWOI2HYtiUxu7b8usM7QEnjzTZqffnpY54xu2ACKgn3BgiPel2A0Ypk4sd8kSbm5GUOqb8itS4lkC2azb0jn6AmjydtvU5tYtEpPjtTRGSK08n+62NbR0TlKiG7bDuhie6SJ791L7T0/penhv/SbYDiYRNavB5NpQKX0BoJ1yhRiO3eiqmqvY6QWP0Zf6qDM1xfJZMuw+7VBi2z319QmGqvSkyN1dIYIq3WUbiPR0dE5OlBVldg2PbI90qiyTM0996AmkyiRCLHdu3sdK7eFCa5YQdunnw7K3OF167FNn45osw3K/qxTp6AEgySre48qyc1+DL6hF8HJpH9kxLbRiyQFUVW5x+cVJUE8Xo9NT47U0RkSrNZRJBINKEp8pJfSK7rY1tE5RkhWV3eKbLlFF9sjhf+ZZ4lt2UrGD38AQHTjpi7PS42NtPzrZSq+/W2Kjz+e6tvvoPr2O1AV5bDmi6xfT+PDf0Hy+4kVFQ2KX7sDa3uSZGznjl7HSP4vf2Rbm79n33YwtA1QsNvzh3FVOjrHDjarVpEkFqsb4ZX0ji62dXSOETqi2sacbOSALrZHAqmxkaZHHsG5ZAm+b34TY042kY0bAAivWUvZ5V+n+KQl1P385yRKy0j5xjfwXXstSihEorz8kOeLl5RQeeNNNP31r5RceCHIMvb5gye2LRMngij2WpFEVdVOz/ZQoqoqyWQL5hEU25LUs9iuqnoeg8FJWtrpw7ksHZ1jBkun2D56rSS62NbROUaIbtuOYDLhmL8ASbeRjAiNDz+MkkySeddPEAQB+5y5RDdsRJUkau+5h2R9PWm33Ez+G28w7t13yPzJnXi++hUAYtu3H9JccjBI1U3fQzCbyf71r1DjCQSTCfvs2YN2PKLNhrkgv9e27Uo4jJpIDHlkW5JCqKqMyTQSCZIeoOeW7fF4Iw0Nb5OdfTFG49BWY9HROVbpsGhFY5UjvJLe0Zva6OgcI8S2bcMyZQrG9DTk1oDe3GaYie3cSeurr+G7+mrMeXkA2ObMJrhsGf5nniFZXU3uI3/BdXrXCKhl3DgEq5Xotm14zj9/QHOpskz1j35EoqqKsU/9E/v8+TgWLUKqr0e02wf1uKxTphJZt67H52S/H2DII9vJpDbPyNhIUtrX0D2yXV3zEqqaZHTuVcO9LB2dYwarNQdRNBOJlI70UnpFj2zr6BwDqLJMrKgI27RpGLxeSCZRwpGRXtYxRcN992HweEi76cbOxzpakTc++BCmMWNwnnJKt+0EoxHr1KnEthcNeK7GBx8i/NEqsn56T6dtxJSdjW3WrCM8iu5Yp0xBqq9HahfWByI1NwNg9A212G7vHmkefrFtMacD3W9hK0qS6ur/w+dbrPu1dXSGEEEwYLON1cW2jo7OyJKsrkaJRLBOnaKJbfSKJMNJeM0awp+tJvW738Hg8XQ+bhk/HtHlQk0m8V19NYLB0OP21mmFxHbsGFCZwMCy5TQ//jjer32NlMsvH7Rj6A3rlMkAnVaS0Icfsu+ss1Gi0f2R7SG2kXSIbfMI2EgslmyMRi+hUNeLocbGd0gkGhide82wr0lH51jDbs/XxbaOzhcWKQ573oE+6gh/EUjW1AJgys3VxfYwo6oqDX/+M8asLFK+/vUuzwkGA/Y5cxDdbrzt3uyesE2fjhqLEd+3b/9+ZRkl0vXuRGzHDmp/+lNsc+eSdc/dg3sgvdBRkSS+q11sr1xJoryc+N59+yPbQ2wjSSRGzkYiCAIu11RCbV0rslRWPYfNOobU1JOGfU06OscadnsB0WgFijJ8fQsOBV1s6+j0xedPwf9dBhueGumVHBHJ2naxnZWli+1hpu3994lt2Ura925CtFi6PZ91788Y++wzfbYztxZOA7omSTY88AD7lp6LHAoBmmWj8uabMaSkkPvQgwjm4WkNbvB6MeZkd0a2Y1u2ApAoLTkgsv3l9WwDuFyFtLXtQVGSAIRCOwgEPic390oEoee7FTo6OoOH3Z6PqiaJxapGeik9oottHZ2+2PGG9v3de6H16M107g+pThPbRl1sDyuqLNP44IOY8/LwfvWrPY4xjRqFdfLkPvdjzhuL6HQSbS/fCBBZsxapro6mRx5FTSSouu025GY/uY/8BWNa2qAeR39Yp0wltnMnclsb8b17Aa3soNTsR3Q6e7zIGEySyRYEwYzBMDIVP1zOqahqgnBYO/aqqucQRRvZ2ZeMyHp0dI41OvIijlYriS62dXR6I1QHFath1pWgKvDWrV9YO0myphaDz4dotepiexgJLl9OvHgv6bfdimA8/OJPgihiLSwktk2LbCuJBLE9exBsNvzPP0/VHd8n+vkGsn/zG2yFhYO1/AFjnTKFRFmZVpWk/W8kUVKK7PcPeVQbINFeY3ukquu4XNo5D7UVkUy2UFf/BllZF2IyefrZUkdHZzBw2AsAiERKRnglPaOLbR2d3tj5FqDCCbfAGb+Efe/D5hdGelWHRbKuDlNWFkBngp4utocWNZGg8eG/YJkyBddZZx3x/myzZxHbtQs5GCS+ezckk2TeeSei00nb//5H6g3fxHPeuYOw8kPHOnUKqCqtL78CgH3ePBKlJUj+5iGvRALt3SNHoBJJB3Z7HgaDnVCoiJqaV1CUOKNzrx6x9ejoHGuYTCkYjV49sq2j84Vj55uQNgkyJsO8b8LYRbDibgjWjPTKuqGqKsEV73RLmOtAqqvFmJMNgGAyITqdutgeYlpefZVkVRUZd9yOIB75W61z0SKQZcJr13baSZyLTyTnd7/Dd911pN9xxxHPcbh0JEm2ffQR5oICbLNnkSgrR2poxJA6HK3a/SPm1wat9JjTOZlQcBtV1S/g9S7E6Zw0YuvR0TkWcdjzCeuRbR2dLxDhJij7BKZeoP0sinDBX0BOwLI7jjo7SXzXLqpvv52GB/7c4/PJmlpMWdmdPxu8XuRAz+2lvyzES0pJNjQM+TyRjZsIr1nb5TElGqXpb3/DNncujsWLB2Ue28yZiHY74U8/JbZtOwafD2NODq5TTyHzzh/3WjZwOOjMBVBVbDNmYM4vQE0mSZSUDF9kewTFNoDLWUgguIlYrIpcvYmNjs6wY7cX6JFtHZ0vFLuWaT7tqRfufyx1HJz2M9izAra9MnJr64HY7t0AtLz4YmeCWgdyKIQSDmPK1sS2LMc0sT1Ske01f4NPH4bGPUN20aJKEuVXX03tz342JPvvnEdVqfnxj6m88UYSlfsTaP3PP4/c2ETG9+8YNB+xYDZjX7iQ8KefEdu+Heu0whHxKDc0rGDN2rNRlPj+tQmCZiUBbLNmYilob+KiqkPePRIgkWgZkVbtB9Lh27ZYskhPO2NE16Kjcyxit+eTSDQgSW0jvZRu6GJbR6cndrwBvgLInNb18YXfhdwF8PaPIVQ/MmvrgcTevZo9xG6n/g9/7PJcsrYWVVCpG72G1WvO5MOPCokWJkdGbCfCsOIn8N7P4NH58Jc5sOIuKPkIpMSgTRNevQa5qYnIZ6tRwuFB2+/BxLZsIVlVhRqNUnvvvaiqihwM0vzEkziWnNTZIXKwcCxaRLKyknhxMbZp0wd13wOloeFtwuFiQqGdXR63tFtJbDNnYs7f3zHROMQNbRRFQpICmEc6su3Wfh+jRl2BKB5+MqyOjs7hYe9Mkjz6otu62NbROZiIH0pXaVHtgyOHogEufBQSEVj+fZCTI7PGg4gVF2MeN460m24i/PHHtK1a1fmcVFuLlAl1xv9hNLq0xzKVkRHbTXu07+f8Ec79E/jGwfon4dkL4L5xWsRbVbXz+tlfoGbTYU0TXL4cBAE1mSS8evUgHkBXAsuWI5jNZPzwB0RWr6H2rrupuuVWlECAjNtuG/T5HItO6Py/ddq0PkYOHa2BDQAEg5u7PO658EJSrrgCy8SJGDweDO3lB4e6GokkBQAVk3mEI9vOycya+TRjx3xrRNeho3OscjSX/9PFto7Owex+GxSpq4XkQNInwil3a1aT+8bDy9fAy1fDK9dBPDQ8a6xYC69eD60VACSK92IZPx7fN67ANHYM9b//A2pSuxBI1taheDS7xvhxP0YUrSguYWTEdmO72C44GebfAFe+CneWwuUvwpjjtYj3irvgua/Cuz+FVfcf8hRKPE5o5Urc55+H6HIR+uCDQT2EDlRJIrhiBc5TTsF3/fU4TjiewBtvkCgrI+2mm7BOnTroc5rz8jDl5ABgmz78YjsWqyEe12q2B4NbuzxnnTiRrHt/1ukdt7RHt4e6e2RHq/aR9mwDpKYuRhSHp5mQjo5OV+z2sUyc+HPc7hkjvZRu6Pe6dHQOZscb4B0D2bN6H7PoNkifDDteh/LPtGhsoALmXAXjTh26tUlxeOduLRqMCqnjkeffRrKmBu9llyGYzWTeeSdVN32Plpf+he+qK0nW1iKnaBF6iyUDs8mH4lBQQiFUSTqi+s+HTOMuEI2aRacDswMmL4WJZ8OKO2Ht38Bg1irBVLbXbT4Eb3LbRx+htLXhufBCkGTaPlqFqiiDUhHkQMJr1yI3NeE+dymCKDL68cdRZRlxCDs3CoKA68wzCX/6Ccb09CGbpzcCgY0AWK25BA6KbB+MuaCAyPr1GIbYRpJoF9vmEfZs6+jojCyiaDlqS27qYltH50BiAa2e9sLv9C3wBAEmna19AQSq4M+F0FJ2ePPGQ/DOPVC/XSstmD1Ti/4WnKyJekEARYbXbtBKEi68UWu4U/wuCZ9WW9kycQIAzlNOwX78cTQ+8gie889DqqtFzXUArVgsmZjMPmSrltwmBwIYh6E0WydNezTriMHU/TlR1OwlOXO0covVG2D5D6C1HFLyBjxFcPl/MaSm4li4ENnvJ/jf/xLbtg3bzJmDdxzt84hOJ84lSwAQDIZhqQiS8cMfoN5x+5DP0xOtgY2Ioo2c7EspKf0zyWQrJpO3x7HWKVPAaMSYMbQXBbFYNQBm8/BffOjo6OgMBN1GoqNzIEX/ASXZu4WkN1zZWjS2pfzw5t3+Gmx8Bow2yFsMTcVaIuFfj4M/TYJ/fxteuUYT2mf9Fs75PUw+D2o2Ed+mRRst48cDWvQz8yd3oYRCND76V5K1dZBpxWh0YTDYMZt8SKZ2sT3cVpLGXZoNpzcEAWZ9HXJma4moAJXrD2mKyMYNOBcvRjAacS5eDAYDzU89TaKq+ggW3sM869bhWLRoyFuRH4xgNA77nB0EAhtwu2fg8WqJn8Hgll7Hei++iPx/v4YxZWjtHcHgZgwGOw7HuCGdR0dHR+dw0cW2jk4HchI+fqBd6M0/tG1Fg2Y9OdzIdtHrkJIP1y6Dix+HWzfC7dvggkc08b33f1pHy8U/hOO/p20z4XQA4ptWIVitmHJzO3dnnTQR72WX0vJ//0ds1y4Un4jZnAmAyexDMmjNb4ZVbEsJ8JdqkfqBkDEVzE6oXNv/2HbURAK5qbnzXBi8XlK+dhmhFSvYd/rplF15JS0vv3zENcYlv59kVRW2GSNTEWQkkOUIbW078Xrm4nZNBwQCB/m2D0QwmbBO7OPCapAIBDbjds1AEEauzriOjo5OX+hiW0engy0vaZaFJT85JI9wJyl5hye2w81a9ZPCr3Sd1ztG84Bf8iT8sBjuKNLqfHeQNRMcGcR37cQyblw3T3L6rbci2u0owSCSS8Zq0cS22ZSKhJbIOaxi278PVFnzYg8EgxFGzYGqdQOeItnQAKqKKTur87Gse+9l3MqVpN9+O7K/hbp7f07xiYupuuVWYjt2HOpRABDbvh0A6wiV3xsJAsEtqKqMxzMHo9GJwzG+W0WS4UaWY7S17cTtmT2i69DR0dHpC11s6+hAe1T7fi0pcuJZh7cP79jDE9u73tJEaOFXex8jiuDJ7f7Y+NOJ17ZgGd/9FrrR5yPtppsAkKwxzJYMQKvaoBBHMalIzc2Hvt7DpXEXAKVspq7uzYFtk7sA6rZr9bkHgFSrVcowZmV1edycO4q0736HguXLyHv1Vbxfv5zI+vVU3XwLSuLQ63tHt20DQcBaWHjI235RCbVHsT3twtbtnkUwuAV1BLuphkLbUVUJj7uPZGYdHR2dEUYX2zo6jXvgxcs1oXzyYUa1QYtsx1oheojR4g4LSdahlyuSs09EiohY0q09Pu/7xhWk3XYLkimMxbLfRgJAjovo1t5tAINO4x4kg0BJ00sU7biDnbvuQZbjfW8zeoF2ITLAetvJujqAzm6ZByMIArZphWTdfTc5991HsqaG1lcOvRtobNt2zAUFGJyOQ972i0osVovR6O5MiHS7Z5BMthCLVfaz5dARCGqvC7dHF9s6OjpHL3o1Ep0vL3Ky56oXHUT88OHvtDJ6Zgec9Tut/Nzh0lExo7UcbD1XaOhGW4NmIVl022GJ/ISkNQ4xe3qOLgpmM55vXob6yZ86xXZHiTTzvMlE1h1a8uER0biLSMYoIIbXu5CampcIhbYxfdqj2Gyjuw1vaV2PLX00VoCVv4S0Cb3ve+blkH+SlgwKmA6KbPeE48RF2ObNpemxx/BedBGizTagw1BVlei2bThPPHFA478sxOK1WCz7z2tHNDkQ2IzNNmZE1hQIbMZqHY3FnDYi8+vo6OgMBD2yrTO4xEOw5V/wwqXw4te1GskjwabntYYz9T14cqUErH4UHp4F65+AudfCrZvg+JsOP6oN+8V2SxlyMEj9735P26pVqLLc83gpDq9cqyVXzrz8sKZM+qMAmFxKr2Pi8QYALOaukW3TjHySFRUk260XQ07THiJpmlibNOmXzJj+d6LRCtatv5Cmpq6NZxRFYsuW6ykq/TUUXgShWu2ipKevotdh+Q9BVUnW1mDweBDt9n6XIwgCGbffjtzYRNX3bqb2Zz8j9P77/W4n1dYiNzdjnf7l9mvHE01dfi/xeB3WA8S2wzERUbQSDA3j3ZGDCAY3d9padHR0dI5W9Mi2zpGTjEHxu1r5uj0rQIqB1aPVrC7/FPKGOQIoS/DRHzVLx2s3wLfeB5NVE/6739Y6E/r3ac1nzvotZEwZnHlTxmrfW8oIbF6G/5ln8D/zDKZRo/Beeineiy/a34hEVeHNW7Tzc9ETkD7ApMGDSNQ1AmC29W7HiMfrAa2hDeyPbIsTNOEUWbdOawAzlMhJaComPOE4QMRuG4PTMYH5jjfYtv1mtmy9gby871GQfxuCYCAS2YcsR2htXUvLGS+R4u2jOsymF+CNm6DsE6TaOoy9WEh6wj5vHp6LLiL8ySdEi4poffU1cv7wezwXXNDrNtFtWnLkSHRwHE6qKp+hzxX0YgAAIABJREFUrPxvLDlpE0aji3i8Hpdzf1dMUTTick0jGBiZJMlYrJZ4vE73a+vo6Bz16JFtnSNj5zK4fwK8fBWUfQKzr4Lr34Hv7wSLBzY+O/xr2vWWZuWYdz00FMFbt8Gax+DZC+Clr2uR5CtegSv/PXhCG7QLDFsKtJQRWb0aU04Oox78M6bRo2l88EGKTzmVqtvvILx2nRaR3fovOOUemHHpYU+ZrK7GYAVRaul1zH6x3R7ZNmlNbNRUMwaPh/C63qt9qKpK81NPk6weYI1qKaEdW+yg0nqNu0COE7GK2GyjEUWtTrTdPpZ5c18hJ/syysoepbTsrwCEQkWA1hGsrOzRvuecdhFYvfD5kyTr6mi4pIa9+wbe5j3nt79hwqqPmPDRh9gXLqTmJ3fR+MijRDZsIFlTQ7K2tstXZN06MJmwTB5gCcMvKNFYJaASjVaiKEkSiaYuNhIAj3smobYiFCU57OvT/do6OjpfFPTIts6R8fmTmsi89GnIX6KVa+tgxqWaneOcP2gidDhQVfjsL1rC4dL7tdbg6/4BW18CRzqccx/Mu65vL/eRkJKH2lxGeE017rPPwn322bjPPpt4aSmtL79C4N//JrRiBbk3noxLEOG4G49oumRVFSaPCcKNvY6JJzQbSUeHPaPRhSCYSEot2BfMJ7K2d7Ed27GDhj/8ATUeI+273+15kKJA5RrY+rLWvj7aAhPOhCte3m/LqdGinxExhN1e0GVzg8HKlCm/IxIppalpJQX5txAMbUcUbeTn3cS+kj9RU/NyZzWVnvDMugTTuqdJNo4jnO0nUfMSBfm3I4oDf4sTrVZG//VRqm69jaZHHqHpkUd6HWudPn3EGssMF9FoVfv3yvakSLXzgq0Dt3smSuWTtLXtwu0eXltNa+vniKKtS7RdR0cHoqEE9WVBciZ4MVt1mXc0oP8WdA4fVdVE1OSlMP607s/PuVrzRG99BRZ+e3jWVP6p1uZ76f1aBPus38Gsb4B7FDjSjsyTPRBS8oht3owSiuM4/vjOhy35+WTe+WPSb7uVkgsvpPHfq3FePhXB4jqi6RLVVSQWGpAjjfTW0iMer8dkSkUUtQsMQRAwmVJIJvz45i8g9N5KktXVmEaN6rZt2/uaZ1dq6qFEYFMxbH4Btr0KgUow2WHSUrD7tAucLS9p3SABajejml1EErX40nt4rQApvkWUlj5EMhkgFCrC5ZpCbu7VVFT+k5277urzPGRlnMaUhEzMHgBBJZlsobV1HT7fCX1udzCi3c6YJx5HamoiunUbsr/n0oi22V9+n3As1iG2yzFbtAREi7VrZNvtnglonSSHW2wHWj/H45nV+brW0TlWSCZkqne1oCgqoiiAAKIoIIgCDeVBNqwoJxmTMZpFxs/JYM7ZY0nJOnYqJx2N6GJb5/AJVELUr9Wm7onsmdpzG56GBd86IqEbXrOWln+9xKg//alb85ZOoi3w+o3gzoVZV2iPGYyQM4y3mb1jCRe/DziwHyC2OxCtVtK+9S1qf/oz2kLTORKprSoKYVMlTWfHcFWG6F7PQyMer++S2AZgNqWQSPqxL9Raojc89BBZ9/68Wym70AdawqDU3ARtjdodCoMRKtbC0+eCqmgXWqfdqwlti1OLdNduhRV3wrhTwJUFNZuJjZ6MopR2i2x34Es5ntLSB2lpWU1b2w6ysy/BaHSyYMEy4rG6Xs9DSemDtEZ2k8w6DSl3W/ujIg2N7xyS2JblOIIgIIpmjGlpuE49ZcDbftmQ5RiJhHa3JBqrxBrXarxbLV398FbrKEym1Pa27VcO2/okKUSobSf5ed8btjl1dAYTVVUJNsUIB+I4PGYcXgtGU/9dUKWEzFsPb6Z2b+9dcPNmpDF1UTZl25vZs66e3WvrmHxCNkuumITBoLuHRwJdbOscGmWfQtMezYrRbg0gp48o3/wb4M2boeRDTXgdJi0vvkjonXdI3HILloIexJqiwH++C8FauH6FVspvJEjJI1xrxDJxPMaUnq0znoV5NDklmj6owfkjFeEwL0Kkxibi+ZpXtsmRYLSiaI1uDiIRb+hmwTCZfSQTzVimTyT1W9+i+YkniH6+gezf/gbHcccBWs3q+I6dAMj1dVr1lowpcPbvNY++J1c7166DyuyJIlz4KPzteFh1vza+fjuR+UuBUuyO7g14QKvbLIo2qqtfRJYjuFxawxirJavbxcKBpPoW4/d/TGTSt0jWb0WUBVIzT6ex8V0mTfw5gtD9nKiqSlPz+1gt2bhcU1FVmQ0bL8VszmDWzCd6netYIRbb79GPRiqw2/IButlIBEHA455JILhlWNcXCGwGFDzeecM6r47OkaLICuv/W8a2D6qIR6Quz1kcRpxeCw6vhbRcF/OW5mGy7Bfgsqyw4vHt1O4LcPI3JpEx1o2qqiiKCiooiorFZiR1lBOA/JnpLDy/gA0rytj6fhXuNBvzzskbzsPVaUcX2zoDR1HgrVvBXwpTLoDazZonOrOPLnozLoP3fwWfPXx4Yrt0FWrNNsKrVwNam+wexfanf9YqoZxzH+SO3Aew4plIpNlM6jx3r2OE6s9Jmxqidl0N0Y0bsc+de1hzJaurSIzTSv61eo3IbTUY3LndxsUT9bjcXStnmEw+QrHtWvm7H3wf5yknU3vX3VRcex3er19O5g9/SNsHH6Cikrg0DXFbBSTaoHojPHEamBxw1evdhXYHaeNh6ldg68vEU5dgisUJe90QBoc9v8dNRNGM1zsPv/9jANyugdkSOhLkAtESkrkqjmCSDJeLxkQDgcBGvAcJspaWNezd+weCoa2YzWkct/Bdmpo/aE/KLCIQ3IKn3R5xrNJhIbFYsojGKojH6xBFK0ajp9tYt3smTc3vI0khjMYjs0UNlNbAekDUK5HofKFoa4nz7pPbqd0boGB2OmOm+nD6rEQCCcKtMdpaE4Rb47S1xNj4bjnl25s4+zvT8WbYSUQl3nmiiIqiZpZcMYnCxd1tfz1hd5tZfNlEwq1xPl9exrjZ6YNmKZElhbaWGIIgYDQbsLvNg7LfLyO62NbR2PcB7FoG3jFa2/GUsdp3W8p++8fe96B5LwDN991DeN3njDl/Cpj6aAZitGhJgCt/oVkLsgfYJTHaotVO3v4qsSYTSlBL7otu3969LFvpKnj/1zDtYs2uMoJEGgBFwC6v0SpyWLuLEyrX4JycCusUopu3HLbYTlRXkhinYlVTiIkt+Bv+R7r7mi5jtCoSzd2qSJjNPpJJf+fP9jlzyH/9PzQ++BD+Z58lvOpjRKcT5cR0mk+pwZ0wgSDCtz+AD36r1SbP7Ccxbc7VKBtfofSGO0kpcBG5RMUY93RWQ+mJlJTj8fs/RhQt2O1aBFxqaUFqaOh1G5NqQhBMBGM7SI5SSYs6SNu7CzHXTGXl07jdsxBFI21tu9m77480N3+IxZJFQcH3KS19iOLiX9PSuh6nfTyxRANlZX9l5oy/931sX3I6kiN9KSdQV/8m0Vg1Fktmj3dh9vu2t+LzLRqW9bW2fo7LNRWj0Tks8+l8eZESMuveKqWhPEhqrhNnihU5qSAlZaSkgpzQ/p8x1s30k7sHMwZKa32ENx7aRCwscfp1U5m0sO/GWxVFzbz7zyJe/H9rGTUxhXBrnJa6CCd/Y+BC+0AWf20iVbvW8t4/d5A3PRVV1e7wqSqgqhiMIrlTfGQVeDQfONBc3Ub59mY86TbcaTYCjVH8NW34a8P4a8IEGqJaVL0dd5qVrHEeUEFKKphtRiw2TWYqiooqqyiq9l0FrHYjFocJRVZRFZWsAg+jJnoxmvu303zR0MW2jsaq+6D8M+CgJjQWN+SfpNkCVj8Crhww2Qi9voZ4QwxyFva/77nXwao/wacPwSVPAtDy0ku0rfqYUX9+oOeqDqvuh6L/wEk/pu3J/wMUzOPGEWuvcdxJsAZevR5SJ8D5Dw99AmQ/xHftBsDqaNaO97R7uw5QVahYg3HiIow5pcSKig57rlDDFtR8GOs5j73Nz9Dcsop0rmmfRqWl5TMqq54FVKzWnC7bmkypSFIIRUkgilo0QrTZyLzrJ7jOPIOau+8mvns30au07ZIpMoqzEDF7Jlzxr4EtMO9EwtExqFKSYJWdsNyE3V7Qp23Gl3I8+wCncwqiaERVVUovuhipn8Y75l+YaXXsQLWDS8jHWLGHsYtu0hIutwSwWnOorX0No9HJ+HE/Jjf3GgwGK7LURnnFPwCYWWoh6LZQKq2krW03Tufh1T7/MhCLVSEIZjyeOdTW/ZtgYBM2e16PY91u7QI6GNwyLGJbURIEg1sYlXN4jaB0vpwoikpjeQir04Qnve9usIqs0NoQpbmqjfXLS2mpi5A22smOj2uQku0NwgQwmkSMZgOCALtW12G2Gph0XNe8BVVVCbfGCQcSmCwGzFYDZqsRk8WAIAokohIVO/ys+tceVEXloh/MIX1M/3eAxhSm8rV7FrD1/UrKtjUTj0qcf8tMRk/xHdb5cXgsnPT1ibz/zC4aK0IgaDYwof27IiusX16GxW7El+NANIhU7+6hpKwAnjQbvhwHBbPS8WTYEQSIhZPUFLdSvbsVg1HAYDKQiErEoxLCAQmcgih0ivl4ONnlfKOCwSQyamIKo6ekIAgCiZi2j0T7lyypzDw1l9zJh3ceRgpdbOtoTWBqNsHC78Apd0NLuVanuqVci2Rveh7+cTK0lMLpv0CNthH7+wuosoCaPp1+5a3Nq3m8Vz8CJ/2IwPp91P3ilwA0/e1vZNx+e/dt9ryj2U5OvYfwnz/A6ivFXphLy7trUSUJwWjUGqW8ch0kInDtci05b4SJFxdjTE/HOG8RfPYIVK7TovsGs/YlCFo3xDHHYSt0HJHYDia0bVMzl+Df9wRN5k1IUoja2n9TVf08kUgJJpOPsWNvJCuz692AjsY2yWRLNx+ufe5cCl5/ncY3n6HG9wAAUrqKbJlxaIX5BYFIYhKwnWREIBzaQ1rGqX1u4nIVYjan4fVo0f5EWRlSbS0pV12FfX7P9qBkTQ2Bot8SPkXr1Olyz4DQJxRkXonVmsOuXT8DYMzo68nLu6m9jB1Qu4X8lAtpbFqJJRontXILHqNAxaIcysofY1rhnw/laL9URGNVWK05nQI7nqgnJaV7wi+AyeTBbs8fNt92KLQDRYnpfm0dPn+7jD1r6zCaDQSbosQjEgajyClXTWbSwixURSXkj+GvCdNc00ZztRaRbakPo0haYMnhtXDBrbMYPdWHIisk4zJGkwHRKHQGBhRZ4Y0HN/Ph/+0mfawbX7YDKSGzfnkpO1fXEQ0melyf0WJASSooiorTZ+H8W2bjyx64hcPls7LokgksumQCqnr4+T0dTJyfxYS5mZ1C+0A6Lgqqdvnx14Rpa4mx8IJ8Jh+fQzgQJ9Qcw5NuIyXL3mvkedbpYw55TVJSRjSIKLJCzZ5Wyrc3U17UTEXR/kpQRrPYGSWPRyXeeHAzM07N5YSLxmMwfjESPnWxfTShyNBSptWDtvbu+R0UPvsLtFbC0j9C025IRiBnjmZ7yJ7R1e5R+BX411Vaabc51xDfuJpI4fNIoxRk14SBvYgW3Q4bnib69I+ofa4c27y5mDKzaH7iSdznnIN10gFRRH8pNBfD/BuQg0GiJXWkzrRiSWxEjcWI7yvBOmmiZk2pXAMXP3nYHRgHm/jevVgmTIDTf6m1Yw83aZYYKQFyAuS4FoWfcCbWQoXQe+8hh0IYXP1HOuS2NgzO/RcUbeZyDGETVu800vwJGtMCfPzJcShKDLd7FlOn3E9GxlIMBovmty9fDbnzwWDsbNmeSPi7iW0A0aDQlvYuoOBJjiOYvg9JGk9HkTVFSVJT/hIYBay2UVis2VgtOe01vPe/iYdLw1h9SUIOI0mlpdNy0BuCYGDB/Lc6vb/RjRsBSLn8a1jG9ZxYCdDy2k7CvAoqODNOBP4KDTvJybsEr2cuomjZH92v2axZYYrfwWB2seDsXyN8cA/CxHMw+feR3RyhhhUkJ/4Ck6kHG9AxQCxahc02Gpt1/4dnT6+TDtzumfj9nwyKIOgPza8NXo8uto9l6kuDrH2jhIyxLmwuM6m5TnInpbDz0xpWPrWDTe9VEGyMkozLnds4fRZSc5yMKfSRmuPAN8qJL8uBwaQJNtEgYrF3F2+iQeSM6wv512/W8Z/7NzJqUgrN1W201kcYNzudnIkpuFKtSHGZREwiGZe1SGxcxmgSGTPVR2aB54gqgQzW35Ug9rwfs83I+LkZjJ/bvZ+BM8VCZt7QaJKOCiyiaGBMYSpjClNZDIQDcQwGEZPN0OW8JRMyq/+zj63vV2Gxm1hwXs85QEcbutgeQVRZwf+v3ZgyHbjVp2DtY5rozVsM1y4buokVGT59GCLNWiS7WhM0jJrT8/iCk+E7H2mi0e4jUh0k8DUJ1QyyJWdgLyJHKpx4B61/eBDBlMroRx7Rbr+tXk3tPT8l76UXtWg1wN6V2vcJZxBevQZkGeeF12FY+3sgk9j27VjlnVqkfMG3YfolR3Q6BgtVUYjv3UvK1y4Dzyj42nN9jrcWaomlsaIdOI7r244TXruOiuuvZ8xT/8SxQCvXF/W1YA9kIth9pPklHHhxZ5xGbu6Vnbf2AajbBsu+D1XrtMogx914QGTb332yaAvJFy+mJqeU1KQdl+Ij4NpHrM2LDQgHi9n68XVEbN2tHQaDA6s1hwkTfopbmkh8XykZ374av/E9kGtITz+7z+OE/W3lASIbNmLwejH3lBR7AKOW3kz56lexGrIxZLUnzTXsgLxF2DsSMuuLNJG9a5nWcfLku2HXMgxv3qYl+p75a6jbSs7bN1DlS6G+/i1yc/svZ7en+DcoSoLJk37Z79gvCtFYFemuqVitWQiCCVVNdquxfSBu90zq6l4nHq/tZlkabFpbP8dmG4vFkj6k8+gcvaiKyscv78HmNnPh7bMx2/Z/Co2fl8HaN0poKA+RM8GrieocJ74cR6d/+HBwplg47+aZbPlfJbX7WjEYxc6IuM7g4/D03DTMZDZw0tcmEg0m2PRuOVMX5eBMOfobjOlie4iJVwSRAwlMGTaMqTaEA255tC4rIbq1iZjZj1N8DLFgAbiyYcuLWvRtqOpDV62HcHvCWckHWhMYiwd8vUcO8e0XOw2Ny1GmAgrI4cjA5z3uRqTE45gcEgaPBwSBzHvupuYHP8T/7HOkXn+dNq74PUjJA18Bra/9AYPPh+0rt0HgbcT3aomu/QDv7v/AqHlw5m8O+fCHimRVFWospkW2B4B1WofYLupTbKvJJHW/+n8gy8S2bsWxYAHRSDWyR8IZKgDRgNnk47jwiTD1j103rlwPT52j3bFwZcPOZXDcjfsj2weL7VA90gsXsjm7DslkIr+ojqgpBtMgLLXiSrSx7pPzUBWJ7M+mI3zWSEJpwnLmXCxnLSBpClFb9zrV1S9iLD0LANuZXyFc+28sRQLk+WFC7wmSBxPdsAHb7Nn9RnWs1lws5kw8KfPAnaO9nhu0soU079MSaIv+AxYXLPkJHH+Tdk5OuBne/SmkjtcqqPgKcH3yAM5IDTWVz/crthUlSU3NywiC2GuZwQ6SyVZkOYrVmt3rmKMBSQqTTPqxWXMRBANW6yii0bI+I9sdVUECwc0kkwFq615jwvi7+zwfh4OqqgQCG0hL7duONNzIbQlEh2nIo/rHGs3VbdTu615PurUhQn1pkNOumdJFaAMYDCInXDR+SNaTmefmzG/2UX1LZ9g4/qvjKNnSyNo393HaNUd/F1ldbA8RqqrS9kkNgf+W7M85FMGYasOYbke0GYlsqMcy3kt8bysx0wLsZ/4GvKNh51talPurjx36xO/+VKuFPe96LeLbU6WQXctANIHZDnvehYYiTdj31izmoONqTNvUeTyJQB32ga7NZCMpZGAyVWgVRAqW4F66lOCy5TQ+/DCu00/DnJ2hPTf7SmK7dhFe9THpt9+OYLHA0vuxPn8psU/fhgtMWot449FTaiheXAwwYLFtTEnBlJNDrGh7n+P8z79AYu8+MBqJl5QCEKxaA7C/VbUjQ7OsdFlQCP79LU1kf/tDWPNX+OQBiPixWrIRBDOBwCayMs/XxreUIT93IZvHBAi5zEyf/lc8FQ8g1q0BUohGymje+x6KVSKv+iLG/fQ+lHCYpr//A/9TTxF/fjdp3/0uyRNOocn/PqmfOjF4vUSzgyQbQqSsNxKyrBzw+ZGam0mUl+O9tP87F4IgMHv2cxiNbs0XnzlVi2wrMjz7Fe0uzol3wAm3aB0uOzA74LwDvNmiCJc+Q86bp7PHXkyobhWulB7u+JgdIAgEgpuR5TZA67Ro76WsYTi8l02bryGRaCIn53Ly82/BYk4b0HkYbjpqbFttWuUFu20M0WhZt4Y2B+J0TkIQzLS2rKOk5AEikVLGjL5+0KPckUgJyWQLXu/8Qd3vkRDZ0oD/pd2kXjkFW+Gh/U5D/hgWu/GQWmpLSZldq7UmT94MGw6vBZvLjMVu/MKKfVlSuiTOAdTuC/Dmg5v2J9AdxKhJ3n4reuh8eXGn2Zh5ymg2rawgLddF4eKco7qKiS62h4jgijJCH1Vhm5aKa8lopKYoyYYIyYYIUkMEqTmGdWoqqd+YTN0vlhEWL8CeWYgKyJNuIL5pF4prF6IvBdFhwuAwIdpNiE4Tgkns+U01FoR1j4Ng0BrJvHev1jJ9/g2aiAetGsbOZVCwRLuVXvyOVqLuhFsGdFz++o9IZMSwNaUSTWsm3lZ/SOdFCiawZZo0z3jBEgRBIOvn91Jy7nnU3vtzxtx5MYIUhQln0PzY44gOBylXtLf8zp2LdfIE/J+UE51xNzZvbz0TR4Z4sVYW0Txu4FEVa2Eh0T6SJJMNDTQ98giOJSehRmMk9u0DIFjzOQDuUe2+VUcahBu7brziJ1qi67XLNRvP5KXw8f1Q/C7GmZeTnn4GdXWvM37cnRiaS1Ce/wpb8xME3EamFT5AevrpsDQb+z+WgAJRqRq56iMQIWOWJoBFh4OM79+B95KLqf/jH2l84AGkIi/ShQH8O1eQcvyp1Ne/icHgxMtkgu+9R9qNNw7o3ETa/dq2OQMrjeg4sFlOxhTY/hrsex8CFXDpM1ruwUBIHUfmyf+geN932LHuKmxRuduQNPtMcs5fjr95VedjgcDmHsV2ILCZzVu+iSgayc66iJqal/D7V7FwwdsYDNaBrWkY6RDbNqsmtm22sUDfnm1RtOByTaWq+nk6ogvJZOugi+3W1na/9lGSHBkvacX/8h5QIVbcOmCxLUsKa17fx+aVlQiiQPpoJ1aHlhFxYD0oAfCNcjJqoheLzUg4kGDNG/sINES77VM0CNhcZmwuE3a3mawCD+NmZ+DLObrbdEeCCV5/YCOyrHLqlZPJmeilvjTI8ke34PBaOPd7M7pFrwFsLnOv/mOdY4O5S/OoLwvyySvFrP9vKXaXGSmpcOIlEyiYfXTZzL5QYlsQhLOBhwAD8ISqqr8f4SX1SLIuTGhVFfZ5maRcNAFBFDCP7poAp5atQTBFIVSFXXmbUPLrxMuDBP5bSqLidOB0WNkINHbbv31OBr7LekgI3LUMpBh88z0tOW/d37VmMp89DJPPhQXf0aJ6LaWw6DYtOrf9VW3bUQMTNFXFzyC2QZblbEp5gcTB0dQ+UGIx5JZWTCfNh73LoH4HZE7FZImTcdEc6p77mMDv3sZb6CMh5hFc8Q6p37weg3t/YkbKPY8RuuZayu95lFznBJyLFw94/qEmXlyMKSenW8vzvrAWFmpJksFgl+PsoOG++1ETCbLuvpvmp58muPy/2l2Tlp2IgLPdv40jHWo27t9wx5taFZnFP4Cx7S3Ls2eDMwt2LYeZl2MXL6BBWk7p54+S9+FfKJog4veITJn8ezIzz9O2ySzEcNs2DP87mbjQRDTagjEk4Fjc1eJkHjOG0Y88Qvizz6h9/AGaaEBakoZryVfY23gLGRnn4Dl1Eg333Ueiqgpzbv/1aqMbNiKYzZ12m0MiYyrE/qmVtLSnaq3kDwFz/hmMDVxMY8tHRA7KXU0mW2kWd5Eab6DZ/zFu92zC4d0EQ1vIzv5ql7HNzavYuu0mLJZ0Zs96BpttDBmZ57J58zVUVDxBfv7Nh35sQ0w0Vglo1hyA1NQlhCP7MPcTiXe7ZxIMbsblmkYotJ1ksoeyYUdIa+BzTKZUbLa8Qd/3oSK1xml6difGVCuixUiiPDig7Zqr23j/2Z00lIeYujgHu8tM7d4W4lGp21hFVtn6QSWb36vofMyTbuP8W2eSkuUg0BAhHEgQDSWIhpJEQtr/w61x1i0rZd1bpaRk2cmfmUY4kKByp59ERAJRwOow4vRacaZY2r+sOLwWzFYDokEgbbSr8wJgqIiFk7z50GZC/hh2t5nX/7wJi91IPCLh8Ji54LZZuNP6LuOnc+xisRn56g/mUFPcyo5PapAlBYNJxOYa2tft4fCFEduCIBiAR4EzgCpgvSAIb6qqumNkV9ad4MpyBLMB79L8Hq+81dYq4s9eiUVuRsguxGFoIiRfQeNjWxGsBjzn5mOteQzDjn8iC6koY85CGbsUxTWZWEmQyMYGHMdlYxlzkDjb+i/N65w7X7uVnr8YWitg/ZOw8RnNnmLxAIImPgwmOotb5vSSHHkQoch2zHsF3BcsgooXSEa7Xwz0hlSvRcGNc5dC8ftaN0pFhpqNeFUIjhpL/fZMLN+5n9o7fohgNuO7+uou+zCPzWfsy69Q+Z3vUnnjTWT/6ld4vzrAiOUQEy8uHrBFogPrdK2zY2T9elynndblucj69QTfeovUG7+LeexYLAXjUIJB5KYmInIlppBtf3USRzq0tf8ugrXauc2epfmTOxBFmHQObH0Zf6WfFQ9DwTnp7Gx8hx2Zc3F4N5Hu+hGZmRd1XaQ7G3PYQdzaQtIaw7HP03NtdMBxwgmMP+EEmtedi3pqCo3udcjBCKPOJLU+AAAgAElEQVRHX4flDDsN991HaOVKUq+9tt9zE9m4Eev06Yjmw7AKZUzRvleuheNuOiy70bhZ99FTFkPkk//HmvjTFO/6BaHQdgryb0MUzQSDW7uMq6t7kx07f4TDMZFZM//ZmdCX6juR9PSzKSt/jOzsiwYc/fX7PyUcKWF07lWHfCyHQjCwBZMppVNcp6WdQlpa1+6vbS1xFFmho+6nIAi47eeQltpIXv4NfP75Rd3zAQaBQOsGvN55R4VdIvB2KaqkkHZNIeGNDYTer0CJSYi9WEI66hhvXFGO2WbkrBsKyUrIRLY1Mqo5ijHFiqXAgyXfg6XAg6E9QSyZkGkoCyInFYxmkYw8d2cFB5ev9zsj4UCckk2N7NvUyKZ3KzDbjYyZ4sORYkVVVGJtSdpaYzRWhijb2tTNrmEwioybk07h4lFkj/cM6JyrqkqoOUZdaYD6kiD1ZUEMRpGsAjcmq5FIMEE0mCDS/tXWqr2OzvveTLLGedj0bgXh1jiZ+W7GTkvtNUlOR+dAciZ4yZngHell9MkXRmwDC4C9qqqWAAiC8BJwIXBUie14dYjo9mZcp41BsBlpqQvTVKWVCGptiBBoiNJa2Uhc+gdZ3mYuqPkepjEzsZpTUSJJfJdNwuizAr+Ghm8gbnxOS5iseBzcudimX03ctpjgygrSrz+gBXeoTvM6L/5h18Yu3jFwxi/h5J/Atldh/eNasqOr/Zbw6AVaPW13/x/4yWSQuMmPp9mNLXs8VEAyMfDoVbJW8xmaxowD93Ww5lHImQ2n/xJh6oVkXQulF36Fsm/eiuh0MvqxxzCmd78VZMrIYOxzz1J9663U3nUXUn09qd/59qB/AEuyQmlTmNKmsNaOVhQwGgQMooBRFBEFiCRkEpKC2yTgKinBcdJJhzSHY/58jNnZ+J96uovYViWJul/9GlNODmnf/jYA5gLNolC/fRdxexCHfzRxScZiNGg2kkRIqzn++o2QjMHFT3QXmZOWwoanKP7vByCkkNnqpGV0e1fQokvYVTSRdS99Qv6MNMZOS6OxIkTZtiY82ROxZK8HARxyz5VBZEUlKSvIikpKyglUVT1HILCZzMzzcTkn02aSiI0tYNML/+GfyclYTQYsRrHbd4vJgA2Zk4p2ED7/EuyhGOnOvj90u/3uMw5ImJk9uOLUnnk82ev+To3wDgC+1JOQ5QgVlU+jKHFE0UJl5TPsKf5/eL0LmTnj793amE8YfxfNzR+wb9/9FBY+0O+ckhRie9EdKEqc3FFXDpnYVBSJpuYPSEs7tdc5PnttL5sOiLQeiNl6CZl3aB96yWTroK4tHq8nGqsg9xAuNuJRCUXSLgoE7R/t7bG9mYdoEDqF6yGtpTRAdEsjzlNHU1kdpq64hdEqNG1swDsnQ2tocsD5i0cl3n18OxU7/ExamMWiS8cjNkRp/MdWTFl2nAuzkVpiRLY2El6nvU8aU61YCry4ThvDqIkph7xGh8fC9JNzmX5yLomYhNFs6OKJPhBVVYlHJNpa4iTjMlJCpnRzI7vX1rFnXT0pWXayxnkw24xdy9d17E7VOiTWlQSItNecNppFMsa6kZIKm1dWosgqZpsRu9uM3W0mLdfJ6Kk+xs1O7zy+L0oZNx2dQ0VQVbX/UUcBgiBcApytquoN7T9fBSxUVbXH+7Dz5s1TP//88+FcIqqq8sYL5xKPpLFn3yhQQVD3v7mpgoqKAoKCCoiKEVVUkAxJbcDB74Nq1x8EVUFAxSmm4BFTqVdqiatxAERVQVBBHcD1k4CAoIqAiixKgIra34e3KpDia2Dm3M/YuW4h/piXRSe9Q8n26VTWF3RrPNnxY+deBQGjpGBJyEStRjjoTb9jvDGpYJQU4hYR+aA1Ce376fxZBXNCxigrJA0iCbO4f0btc7VrQ0m1x/8efJpR0VrY9vWXIaBiklREFQRVO39mWSFqMiC3N0PoKd1UPeB7x9+eSVKxJGXCZgOyKCAAJknBKmn7kwxC+/GqOOMyqiPB/FPfY1/RNMprxiEABkFFUGWtpbqqgGho/393DyiKhEG2ASqirZXZCz+hrmYs5aVTEBQRQRERVRFBFbRzIaiMGruHvCla05INH5xLLN53WmxKWh1T532Mqgh8/ukZhGM2FFQskopZUohYDSgc/Pttnw8VgwL2hEzMJJI09C8sO0YIgtD5excUSXtGNB5w7g/8t+svReXA12vX/R6M1Rxk3qIPkWUja1adQ2p6LVNnrGfT+pNI8TWQN24XTQ1Z7Cyah6r0LObGT9pCZnYln320FFXtOzk5f3wRo8dqF0WffXQOkjQ0icEebxMz537Kjq3zaWrsfgEuKCJGyYwiyqhi98Q1UTYiCDLHn/UaZSWTqCidPOC51QN/K0oSJVoJKCCaEa25pGfWMnX6BjauW0wo1FV8CqqAKuzfXkBAlIwYlP7fD1VBQREVVEHRViGoXfbVFQGDYiDdkImIgaZEY+cLJ9OSSVgK0ya1aUciHPjXLoAKsjGJImo5AD4hDZvgoEap1D4X2jFhxirYsGDFKthIqHEa1L47qA4ZKoiqAVExtH9m0Psbo6CiCNp51L72n8eOz8Hez+v++b7Q9PCGIfTf9u3/s3fe8XFU5/r/zszO9q5V77JsucsVY5qNDaGaGkoghIRQk5CQQpKb3PRwf+lwEyAkJEBCCx0CNhiDjQkYF9xt2ZZlS1YvK2l7n5nfHyPLFpIsWdhAbvz4s59d787MOTOaOec573nO857AMcb4meO57tNf+sjLFQRho6ZpQy4o+XeKbA91xw54NAVBuBm4GaCk5OgzGX1YqIkkst2PKkeBAlQRNEFB1Vk3AGJfo6oiogkKkmbAoIh9bbHWT240VLQPnLEmSAhoRLUIZhw4RS9RNURKjSOopqN6qA92bLIqoQojMEtA1ETcDl2TGAj7UDVdE2UwZuCwRlhARNR0Mi+Afuz+0jQyBkm/AsOUlzZIpA06ORnybD5AmJNGA0pGw6BqyBlBJ2YCoPYR2jG0cwJCP0kXhqmIpGgY+siyJoKogSKJaJJ+3mgw9Br6geUAZCSQVAFZ08NuAmDQIC1LqAadtAuCSl7BAQKNRdjtuvtFLO7GIIgcvMIHI2maIKFpwpDXWO/2jSCIqGKaTNrO2nfO7T9JTdBAUlBQ+sk2AsTj+nR1MmEmkTYzAjckEMwikzHgbyshHfMgo+mERoK0KCCr/fR42GOkjSAKKgZBYTS9cD+10fSXIBj7fzgUUzjyDaF94MNwpSaTTur3TkFDRNMMhEO6nWHF+J243N10tJVQu2sW9D0HQyHQk0tBUQNOZ5BQcHg9tMUaprB4H4mEBbM5jtmcJBoZWcc6li4+y9eJqooEe/IQ+eAgQUBUJVRJQ5MGDE36oQoqomIgk5YxyhnEw7oYt7eD4rLd7N01m0R8mGyvmoCggWpyYUyKGEPdJLLzUGUbbvculIxELOpDOsxSUFAFBEXqI3KHhvkCAqpBPYz0Dl3eQUKJ9sHucKj9BAyCAUSRYCaEKh0ilTESYBBRBXWI0byGJmoIooiEiICALJqJalFEQYLDrrWKRqzvn1mz4BBd2FQXCY7CZvVYoe9PrEkaGoMXCg+9i8igUMNob0ZhbPft2DBSSUfH/P/dxwn/lyBzQrP9YdAMHG4/UQS0Hr6Bpml/Bv4MemT7o6uaDslipljOEFJbmfTSS3i/eAO5d955aIP1D8Kybw1wRtjxdgste3pJphWCXXHCHXE0VcNgFPEU2JDNeorSgy+jlMK27qe4S8eT6ZqGFFHxZ1R2yylmXlCJI9eNbDYcFtHti4oe1q6Y7TI2l4lYKMXWN5vo7FvYo2kaakZDyagoioaaUVEyKmabTOnULIKJb5EK5+Dp1uuupF/Fp7opnPxFuvYFCbTpnYHFIVM8yYvNbaJ9f5DejhhKRiUd1yONklHEONVNpNSC1SpjNxmwmQzYTBI2owG31UiJ14rlAzY+aUWlO5IilEj3STkEDJKIQRRIPf0kkd/+GmN1Nc47vkn3/7sLtb6edb/8G00ZGZdFxm2V9XKMUn95VqMBsyyhahqiIJDrNGE1jvxYNFz7WZRggIqXX0YQBLRMBjUeR3I4UFWNfV0RdrWH0TStv66ioEtQjAaRqlwHOc5DesuuP9yL/777yLrlFlL19YRXraLiny9hKtenVdvaXqBm10t4gp0oDZ2EToKbPv/f2HL0NL6vPLkcYd02FAS2O2cguhw4PGa8WWZy8+x48q1oGvxrr5+Wt9so7VLYOzWGvaCEqjwnRR4LOQ4zuU4TXpsRw2FTxZqm0f7C/dSwDteOFN/9ytcwFhWOeI0SiW9iMHjZuzNE7aoWmnf3IgiQZejF1FSDlIohKQmMLjuWkgKsFSVIEyYTj6tEVq0msb+e1rLFKIpGbpmTI02+lE33Ub24GEEQaO6NsaKmg1d3tKOoGlV5DgpcZuwmA3azjN0kYTfJ2M2GAZ+tskRG1eiNpeiJ6q9EejDBUDWYuPQy2jo0rkx8X/8yplGVWIPb3c2Wrinct+NGVG3g/SuJAl6bfm9XZtvJL5UgfDFzz5qEwfVFVE0jk+5FSe1GS+1BTe1ByNQiKM0omoXH9n2BG6fcT3fJNKaOu5iLZhTgNB+7TkXTNN5buxir5TT++6f30NUUpqmmB0EU8DeFqd3QgdkqM+emSdSEYqQyKmlFJaNqZBSVtKJhkkWyaiKoiZWUF5Vx3fW673sq1c3adeeRTndz2qIdzJ711AC/8WgwyasPPY+p4BGa37mdgHqApEPEos5HNe1hXPYMSks2IBtn842v/xyb24QoCjRs97Ps/m0UTfRgdZroaAyRUDXCmspGm8K7gTCyKOIwyzjNBuxmAw6zAY/VyMUzClk8MQdRFFAVlUhvkpA/Tqg7QaQngaoOHnEJkkAxGuq7raw5p4Dfrz9Ac6/uDPJD2c5ZGYn8H5+MPEIbEt3YQe8ztWTfOh1T2fBZSjVNw/+X7aSaI+R9Y3a/jvsETuAE/r3w7yQjMQC1wGKgBdgAXKNp2pC+aR+HjARg7/KzaRb3UfL7KRhLSym+9179h2g3/GGWngb9c/9kOOaQTim07OmlYXs3wc4YqYRCOpEhFc+QTChkkgM7/ypzhIlmD9a5uXgvn4DW10EcD0uk1cumItQayZr1T8wOIw0tnyLSVkXb1tvIr3RRPMlL8WQvvkL7kOXvv/V2unpEus/8IvVb/X36SeFQ9LhvF7NVpmxaFsWTs7C5TbhzLVjsI0+bh157jdY7v42WToPBAJkMpU8+gXXmzGNy/pqqEQunCOxpou4bP8B87kUUXHYO3gIbNveH6wSVUIjW73yXyOrVoKpk3XwzOd/4ev/v72+8gmBwE3LMirwxQXwOzJ2/jZbaAPs2ddGwzY/NEMLkchCNyyRjA50NiiZ6WHhtFYIg8NI9m3FlW7joa6O/LsGVr7O54zZcr9uY8dDWMWmGe9ujmKy6LZmWSpGoqSH2/vvENrxPbNMm1HAY99VXkf/jH1N/+acRHQ589zzA2pf2EfInhj1uKpHB3xShbLqPKacXEO5OEPLHCfck8OTZmLawCKtz6PtHSasEu+K6E4SmHQxC6hIf7TC5T1/Q9KDEiPf+iNa8gfhVf8Wbb6OzK0rD9h8jSo2I4l3Ys73E0hliSYVoMkMklSGazNCdzLA/lmBXZ4RgPM2PTv4l0bSN32z8CpdVvswFFSv669YV99IYKuZAqIjt/unMrhjPhbk38EbL1Ty58xTMssjJFVk4zfpgwWWRcfcNKh0GA6a4ihTNoIbSSBkNm9M4IIOedlgYX9Mgox0gIFyPTbuDwL6F7Nt0aPGzJIuolXZey8TY1hU+4t9Z0uCXc+/GmLKQbbmHk5aUsXPXV/H73yTP9X3aA79CErJxab8nk7STTmSo29iJY8K9OIrWMXnifcjyXP74wP0oioJJsuLumsz4i+6ge9eF+HdehCgK2L16wMCda+WSb8ziL2sbuOeNWtKKhlESmVHiZlaJB1XTCCcyhBNpIskM4USGxp4YXeEk5T4bd106lVPGjd4fe/dft6HUBbhACzO3zMN5U/PJshtZ88pebo9I/JAYa2UNh8WA3WTAYZZxmA04+94dZgPnbQ9hjCv8NF/AbDTgtcqYZal/YC6JIgZJwGQQ+XSFD+mR3ZirPPiu++Qn7ziBE/hPxZFkJP82ZBtAEITzgXvQ59we0jRt2PSBHxfZbnrremrVdyj9ezVG0UPJQ3/Vf3j5a7DpUbjt3UNOCWOAqqhENi6n54VfYRai5J55PqHM9YRXN2OqcJFqi6KlFAw+C3KOFUOO9dC7z4Igjy2jWzLZwTvvnkL25iqmf3MZAO+8NBuhJcHJt2wfuGhmGOy/9DLknByK//QAbXUBGmt60NRDBOdgCCnUneDA9m7SfQMLg1Hksm/NJrvEMdyh+xHbuJHQ0qU4zjmXxuuvp+CXv8B18cVHfb6aqtGyN0Dzrh7a64OEuxP6yvnM0M+L02emaJKX6kXFePPH7mubbm8ntm4djnPP7Xf8CEd2s379BbhccwgG30dIgBIppHbVj0EDs02m+qxiZpxV3L/YK5NSiAaTRAMpOhpCbFhaTyap9BOsc26aSuXsnGFqMRjx7dtpuOJKLLNmUfbE42M+v+GgKQqtd36byL/+ReWqVeydPx/P564bODM03L6axraVzax5rq4/GinJIna3iaA/jmQQySqwDZitURV9BicRSfNhm0CT1UC679qarAYSkfSRdxDAm2/DO9GN5noQg7SU7rrfklX5ddKxmajpSzDbJ2Lx5GDzmjDbZbLtJgxJlfe3zcWYOY+22mtpbgiRSGXoMEEvKqakilsR8Coijg/op9KCpsuUjgBv1WvkVD9H3cu/JJ3OYqdN4y0tjiLokqiMADOK3Vw8o4DFE3OxmSQMkogs6QuGDaJAOJHh/QM9HNh7G45MD52v/xBX1W7yq39LqO4qWjedhcVXS/EZ95AK5dO4+pug2nBkqxQsuB1NS1JSciPjK/+L5cuX895773HppZdSkB9g244bKPTcixKZTk9njAONIXojKZJTnbzfFWLt/h7On5bHNSeVMrvUM2hm7HBkFJXXdrbzu9draeiO8pUzK1lQlYPbKuOxGrGbDIQSaXqiKbojB2c6kmxuCnDRpgBRWcR67UQWVmX3DzxjgQTtd2/EmFQJmUVqvDIbHQLNqkqoj+wn4xmujAlcrhp5zJhhY4GJVEalJ5YildEXF2dUDUXR35MZBbfVyJPTy7C9164nzpn6yUyGdAIn8J+O/yuabTRNWwYs+7jrcSRYrBUQeQfFm0Jt7dPYtW6BjX+Debd+KKINIEoiztln4Vx9u+44cepXcZo9pP1x0i0RLJOzEG0ymc4YqdYI8R3+AasVDV4z1rl5OBceXUKY3uZ3AHDlnNT/nUGzkjaGR0W0ATJtbViqpwOQX+kmv3J4q55MWqGnNUoslGL1E3t49YHtXPG9OSNGuK2zZ2OdPRstlQJRJHVgaNeEg9BUjWQ8g9EiobS1IRcW0tseZdVju2mrC/YnnMgtd1Hp1b1oI/f/FpshRfm9vyXUFcffHKGltpfa9R3seqeVqpPzmHtB+Zj8YeW8vEGDg5aWxxFFE9Om3suat89GNYcJtZYzbWERk08tIKvANmgmwWCUcGVbcWVbKRjvZvycHLatbMbmMVE80XvUiS4MPr2DN004OmvD0UKQJFyXXExo2TJ6Hn4YLZ3GMnXqyDuiz45ULy6mdGoW8XAKZ7YFa1/Ci972KFtXNhPuTiAZBERJ1N8NIlJfEhB3rhWzXdYXVSKAeGgxrnDQvaLfyaJPz9+yEV79NsH5v6YlkI3RZGD6oiIcXjM9bVHCPR+IxPc9g4lYmlBXnOY9vdStbMFeUEDRaSmcuXcDGh3rryLqdwE9fS8wmCREAVIJhfJzPIQi+4k2RhhX7NSTntQFyKTAZDXizLdg9poxuGQ0u4G0VSJqFHhtdwfv1foxaSAbROwmA4F4mkUTcwjFMzQHYlxZsplEsIjfyRYUU4z5433cWVVOIqMgCQLnTMmjzHfk+8ZllVk8KZcdagVN7a383ZHmSkMtmiZwoG4RCz5XhSdnJoFEId2Gb1J11UOkXL9DTq1ACyaRZS+BwEYAFi1axLhx46ioqKC+Xo+xvBMqYXVdM+vqe0hl9FUR0oZebEaJX1w2javmFo9q1sUgiVw4vYAzq3L47xd38PuVdfx+Zd2I+1kFgTtwYDuliKyJAwerVreZsu+fTHyHH9PGDk7eH+TkVjCWu7DO8pHpSRDf7kdRk1jn5fOdC8sRRnBCqfdHuf6h9Vy+YR9PO90IL9Wheo2sbuxl/rgsxmUPo30/gaNCdGMH6dYIzrNLh7VuPIET+DA4cVcdY1hcEyECGXcSuU7S52pf/baeWGPhd0c+wGggyXoqd1UFqxcBhp1e1NIK6a44ma4Y6c44yb29hJY3YJnqQz4KMtizfyUA3inn9n9nEO0kzW2oiQSi+ciZ8NREAiUQQM4bXXpdgyyRU6r7iFtuMfLCbzbx4u90+YOqahjNBkzWPh27Rf988N3qNJFVYEMuKCB14ED/MdNJhe6WCP7mCN3NEf1zS4R0QsEgKJginYiFxUTDCrJJYuG1VYyfkzsge1mitpb6ba+R+/3v4/CacXjNFFZ5qF5cTDycYtPyA2xf3ULt+g6qTs7D7hl4XQ7nAYIo4PCacedacedYMFkH6m9VVSPc00tb64tYDIt592k//tCpZE16jcrSMqrPnjCqawlg95g55fLRZ7b8IAzZ2VjnzMGx+KwxH2Mk2ObPR3K76X74YUBP+HM0cOdacecOdEnx5NlYeM0QCaA+LLKnwJt7ycuppWrJQA/qrEI7WYVHJkEnLdFTdUcCVdQ0/BGjaz/5eZdz1s8vJZXI9EthQv4E4e4EqqLitYeIWj1o+Ukuue60/mMpGZV0QsFkGz5d9zWnlPLWni5qO8JcPrsIWRL5n6W7WLWnk3y3hepykQp3A3HTZ3n4lnlU5tjJcYw9u6XJ6MUsRXjhF2ez9v1XCQc93CeluOefW/q2EJmTex23TP8bu3d9Bashjs9WwISC8+lofYT369uYXZZHZWUla+r87NyxAkEr5OfrGhifY+e6k0s5Y0I288q9mMdg3XcQNpOB311ZzU2nV9ARThCMpQnEUoQTGZwWGa/NSJbNiKfv3dadpPdP27AOM8smGiVss3KxzcrVrfw2dRLb1EHgpX0gCZjKXXiumIB53Og8gct9Np677RTueGozX6/r5UFsPPL79TxEEoss8ZOLp/DpWUXDWvqdwMhINYfpfW4vqBrxXT14Lh+PqWJ0vuIncAKjxQmyfYxh9k6HFsg446gx9EQzTevgonvBcgxN1ytHR3oEWcJYYMdYoHf+ysn5tP1yPZG3m/FcNvooZSi0Ezllxb7gUPIbg+REtWoowdCIZDvT3ucdO0qyfThyy5ws/vwkNixtINSdQBCgNxEjFcuQjGf6deqHw+o0klN+GRMa9Yj8ljcaWfN8HVqfRYjRLJFVZGfivDwMHfV0vbGGpMmFzehm3KIqZpxVPGRChdDSZSCKOM87d9BvFoeRUz89nurFJbz/agO71rQOKzsZCma7jMVhxGiWiIVSRHuTOMtXkTc7xs5Xq0n0tlF99mcQzbuomHbVqI97LCAYDJQ+9ujxLUOWcZx7DoF/PIXodCIXH93sy0cKew5YPNA5dpt/fbBWSFP3ZMLhnZSW3gqA0WwYmrA/eQ27Q5vpKMoa8LVkEJHsR55dEgSBMyfmcObEHGKxBkKh7fzy00v6f29re46aXSoLpl+G0/nhZQqy7EZV4wiksMpdmLPKWHbH6by6ox2LLOGyyDgtszBlCpkm6GrAF+qW8NhOA1+ekeH7Tz/FxNIFXD23hFseXcuvT68nbbqYNd9dRIH72GYUFASByQVOJjM4g+sHEdmpzzbIhSNL2gweM87FJTgWFZPpiiO5TYhHkLYMh2yHicdvPJlNjb20PLaLKzMGLv78bH7x2h6+/ew2vv3sNgQBZFHUNd+SgCyJnFrp45YzKphaOPwCzP90qEmFnid3Izlk3JeOJ/BSHf4HtyPn2bCdnId1Rs6JSPcJHBOcuIuOMSR3OaakQsaeQI1p8PZv9Ex+M679uKsGgOQwYpudS/T9DpxnlRB4+lEiq98m9/vfwzxxaE9cNa0Qt3XjiM4hsrYTxxlFaJqmd6gGUIIB5Nwj63/T7Xr2yNFGtj+I8XNyGT8nd9D3mqaRSakkYxmS8TSpWIZgV5yG7d3s3zQBv9VJ78v72bC0AZ9/G/nt72FPdGAzKxj2upBcLhI7d1I4bRqp1hYsvQcouvyCIeugaRqhpUuxzZ/fL6sYCnaPiYXXVLHgMx+IPH+AdysZlZA/QaCzL+FRV5xEJE0qniF/nAub10Ta+R6ioYqLv3QVzixLX5R95dFevk8Uwv9qQVNUTOUujIV2BMMhoui64AIC/3gK8+TJn+zIkiDoz/X2ZyG/GmZ9Xs/QOQaUl32FeLwJm23oZEH9aNuCxZkmo4TJZMKDEuWMFk3Nf6O5+VF8WQv7j9HlfxOTMReHY3TSnZEgy7oPdjrdSyLRitNVzfhcB+NzP1jnG2hsEmhqeoQbzv4yL27xAw9w3ewgP3izlZe2tHJKSTuymGZW1SJyjjHRPlqkmiOINhnJNXqfc0EQkHOO7Es/Gswq8RA9u4Le5/ZSIRp47MZ5PLepmbZAgoyqu8IcTCwVSWR4dUc7L29t5VOTc/nvCyZTkvXh6/B/BZqqkdjVQ+itJjI9CbJvmt6XvXM2sS2dRNe2EXhxH8Fl9Virc7DNy8NYNLbn7QROAE6Q7WMP2Yw5JZGyJ1BjCnTvh0U/GHNHfDzgOKOI6Pp2Qm82Enr9DRLbtlL/6Svw3XYrvltvRZAGRl+6lq9EtcawxIoJLqsnUdtLujOGWuiGYkgHuzBzZDqcCqQAACAASURBVElDul1PyjBWsj0cBEFANknIJgm7R49E51e6mTg/nx137+CdnW42LG0gX2pncutzZN94A0ogoL+CQZRAANspp5D/s5/Sec89hF9fgaYog64BQGLrVtLNzfi+/OVR123gFwP/azBKeAtsw+qnA8GNbNy4n4njfo5vFJG0fweku2IEl+7v/78gixhLnXqK6nIXctlkzLNOxb7w9I+xlqPERb+Hl74Cr3wdmtbr0q4xIDv77JE3inRBqAVzX+r6eKIFh330CWMORzLRBmiEwzvxeE5GUZL09PyLvLxLRhzgqMPMJB2EaJQQZLGfbKfS3SSSbeSYzx92n5LiL1Bc9HkEQWBW+XjWrpvAFFM9D153K0+sb+QbpwboaAKH4+N34ki3hJEL7R/bQNAyJYveF+uIbenCXeTgyjnDz/78YMlk/vZuA39cvY+z7l7NzadXcNvCcdhM/9ndfqolQu/ze0m3RJDcJjxXVmGq0KP/oknCPi8f20l5pJsjRNa16eR7QzuWaT6yrv1wa65O4D8X/9lP3XGCRbMQs6XQEgqaCkL+jI+7SgNgyLJgm5NHdF07Uu4VOJachEA7/j/cS3zzFgp/82skty55USIpuvdtgmmQu+A0pNw8EnsDSDYZIaRPc6fC7SOWmemLbBtyB0enjxdKq3NJPPoTlK/+AvdDD2KfP4+sG74w7Pa2+fMJPvsciZpdWKYNjvAFX1mKYDTiOPvY6JZjsXqs1uHTE7c0P4Ek2cnNveiYlDdWKNE0otmAMIpMjiMhtrkTBMi5fSaZ7gSp+iDJ/UFCbxzoj/zLJdeTidjJ+OMYxrDI9MMi3RFFkMSRy3aXwOdeQl32Q1j/Z8RTbofco9OZjxptutbZInkAhUS8acxkO5HUB76h8HY8npMJBNaiKDF8WYuG3Se5P0Dw9QOkGkJHPLZok8n58ox+sh2N7EHT0pjNR/ZlP5y8ut1zaW9/icVn+Dhrci71DesBMJlG755zPKAmMqQ7YzgmZY288XGCaJUxV3mJbe3CdX75ES1enWaZ2xeP54o5xfzytd3cu6qOZzc2c+81M5lT5v0Ia/3JgJpSCL3RSOSdZkSbjOeqKqzTs4ds1wRBwFjswFvsQL2wguDyBqLvtZFujyLnDQ6O/Gn1Pv65tZXPn1LGJTMLkUdpGvCfDFXVxrTWIKOobG0OsqMlyFmTcyn8mGe7RosTZPs4wCJ6SVtb0CQRVRGQ8qs/7ioNgvvSSgx5Ij2PNYE0l8yprXBqnPhv9nLg5p/gXHINWtpCujVCvKABISPgmbAQaZIeWUs1h+l5Rm90UuHOI5alaRqRVauQS0oQLR/dg2EsLcEW68AXfh9/ZzvWuXOPuL3t5JMBiL733iCyrWUyhF59FfvChUj2D+8A0NX1Btu238KsWf/A4x5cr1Sqh47OZRQWXIXBMHYrwQ+DtD9O6PUG4tv8SF4zjgVFWKuzx6xh1DSN2JYuTJXu/nUE1mm6HEeNpUkeCKHGM6ixDKE3G+n4/Wa8V074SK3OErW9+P9eg2gUyfnSjBEJt6ZqdNVehJqaTu7qPyBe+QBsewbSUZj9+WNXsVadbJurb4bEH0l0boDRRMSHQDKpD45DoW2ALiERRQsezymDttU0jeDL+4msaUV0GvvcGobWHWsahJY3EFy6H9OlOtkOhXcAYBmBbB8Ol3MmLS2PE4vVY7NVkkx2IsseRPGjT+iiqRrJ+iCxTZ3Et/tBBdMoFzceL1hnZJOo6Sa5P4j5CI5OB5HnMnP3VTP47MklfO0fW/j2c9t4/Y4zBiSu+r+OxN5eel+oQ+lJYDspD9e5ZYjW0SWEEs0GnItLiK5rJ7qpE/f5AwMk25uD/Gr5HqxGiTuf3cY9b+zl1gUVXDGneMDiXU3TPtnSuGMMfyRJY0+M1kCctkCClkCc1kCc1mCc1kCCQCxFdbGbM6tyuKi64IhOR5qmsakxwPObmlm6vY1ATLdX/X+v7uJLCyu5beG4T/wA5wTZPg6wGPNAaEHxgmrJR7Jnf9xVGgRBFBDFTmJv3YXvm39ij/JHks4mLHdUUbBtEfHtYSR7BNP4XOKp9zDHfEjSIZ2inG9H0voi27Ejk+3omjXEt24l78c/HlNdE8l2OjpeJpOJUFF+x6gbLLmoCASBwAvPA2A96aQjbm/IysJUVUX0vTX4br5p4DmsXYfS3Y3zwsF6blVN0tW1gkBwE+Mqvo7B4NAHGJEabLYJiOLgRv1A458ACIW2DEm229qeRdNSFBZ+ZlTneqyR2NOD/9EaBFHAfmoBycYwgRfqCLxYh1xgx31BOaaKoyMdqQMhlJ4EzsUlg34TrTKWwyKGlqk+eh7fRfcTu8i65vh6C6daI6SbI6iJDMHXG5B9FpRQCv8jO8m+rXrowYWoR78i77aS7kgAeQS25uB1fR/e60tkFetBO+3rqLEMajiFEk5h8JjHFq1v2wLeccjTrkNce79OtscQRFfVJKmUH4BwaIeeodD/JllZpyNJA8mspmoEXqojuq4d+ykFuM4rG9GqTksrhJYfQJ6jy8XCYT3n2EiR7cNhsejSiESiFZutklSyA5Nx9FFtJZQi8Mo+lFAKLaP2Wzj2eTvqbZ/FgGvJOAzDJKPKdMeJbuwgtrkTpTeJYJKwTPdhm5N7xIyPHwUsk7wIRpH4Dv+oyPZBzC718sMLJ3Pzoxt56v0mrp1XOuR2PdEU97xRiyyJXDA9nyKPhXhKId9lwWj4ZBOaD0KJpgku3U9sUycGn4Xsm6cddbsFINmNmKs8xDZ34jq3rH9GIZVR+dYzW/HZjSy/4ww2NfZy78o6fvDSTv73zTpuOr2cimw7f1i5l9qOMIsn5bJ4Yg65TjPjsu3kucbm+KNEUiTrQ4gWCdFmRLLJiDYDwieAcKqqxj1v1A6y0rSbDBS6LRS4zVQXubGbDazb38Pdb9TyuxW1zCn18LWzxnP6+EN8qaknxgubW3h+UzMN3THMssg5U/I4a1IulTl27l1Zx+9W1BKIpfnhko9fZnYknCDbxwFmaylkNpLxaWieT+4NkNy/HzQV87kukrVNONW5RLzbaFz0M3KWFqM9UYNl3lxSV4fJMc8bsK8gCZh9uiQknegetgxN0/Dfdz+GvDxcl1161HVsan6U2tqfcFBjUFx0HUbj6IiXaDJhyM8j09qG5PNhLC8bcR/b/Pn0PvHEIDvD0NKliHY79gULAFDVDC0tj9MbWE9v71oymQAABsnKuHHfoqXlcfbU/giTMZeCgispKLiqPz11ILiRYHATAJHw7kF10DSVltYncbnmYLcfG9s6La0SWduKZXo2hhFSPidqe/E/WoOcbcV3w1QkhxFN00gdCJHYGyC2uRP/32vI+dKMo1r4FdvciSCLoyLOBrcJ341T8f91B91P7MYyzXfEKXPBJCE5jVhn5GDwjr4Di+/uofvRGlD0+0sutOO7YSqZzhhdf9lO28/WDl2/XCuO04sIvXEA80QvslcjvOZsxLf/ieq6h0zSibJUQ3n1bVAPdYCS10zenXOOPsLVugVK5iE4cjErMvH4yJ7QBxGJ7CGZ6iLLexrJpC7nsloriMX209u7hmSyHV/W1wftF13bRnRdO46FRTjPKRtVnR2nFxF9v4Posm6YCeGw7tZyNGTbbC4AdLINkEx1YjwKCUnw9QbiO7sxljgPRS8PZgFVNTRNI1Hbi/LUHrJvmjbovoq810rgn/sAMFW6cX2qDPOUrDE5iRwPCLKEebyHRE032sXjjupeOntyLieVebl7xV7On5qPommE4um+DJsZWgIxfr28lmA8hYDAX9+p7993SXUBf/jMscnGe6yhqfrf96AkRNM04lu7CLy8HzWewXFmMc5FJUed1K0rnGTl7g5W1HQyIZDmmnCGZF0A8wR95ubelXvZ0xHmoc/PwW01smhiLmdW5bB2fw/3v1XH/3tVb9+LPBaWTC/gzd2dLN2my7iMksjdV83ggun5o66PmswQeqOR6No2tLQ66HfBbEDOseC9euJRtYPHAoFYip2tIf7+XgPLd3Zw2cxCLqzOp8BtocBtwWkeeiahPZjgxS0tPLb2ANf9dT2nVfrIshtp7ImxuVHvV+dXZPHlMys5b1o+9sPWHNx37Sx8L+3goXfrWViVzRkTdKL+SZxFOEG2jwMsziroASVLQ3WO3dv4eCO1vx7RZiOIPp08ef7PUbUM27d/idbzd5A3dR6hv69DM4F3CKtBS6HeSKRSw+s4Y+vWEd+0idwf/gDROPoV/KCThL17/wev9zR8WQup3fsz4vHGUZNtAGNJKZnWNmwnzR3Vw2c77TR6HnmE6Jr3cCzS/ZPVRILw66/jOOec/qyOLS2PUbv3Z5jNxfiyFpKXdzFt7c/T2PQwObkXsm//b3E6q5FlN/UN91LfcB8+3yKKCq+hpeVJDAYXdvtEIpFdg+rQ0/Mu8XgjFeWDCdBYoGVUuh+rIbGnl9imTj1aOwxxSNTpMgrZZ8V34zQkm95ACoKAqcyFqcyFbW4unfduwf/IThynFaJE06jRNGokpX+OZXCdXTqAVKc7osS2dOmkxTQ60iKaDPhumErP07Wkmo6cIlyNZ9DiGWKbO8m9Y9aIER4toxLf7qfn2VrkPBvez0xEkEWkvmQ4UrmL7JumkdwXHGJnXQ7T+2wtGETcSyqQ3CYSNSuIBC5CUowYcs2YAhsRg/9CmnYa0rSFpDtihN9sJN0cwVh8FAteo34INUOBbg1oMeYRUg+wb8//YHVNJj/vkqGviZqivuE+Dhx4ABBYuGAbiYQuIcnO/hQHDjzA/vp7AAGfb+Gg/WPb/ch5tlETbQDBIOI8s4TeZ2uRRAeKGkaWvUjS6KP5RmMOIPZry5PJDmy20dmUprtixDZ1YJ9fgHvJuGG3i77fQe+ztUTeacFxRhHQ5za04gDhlU2YJ3lxX1I54sD044J5chbxnd2kW6MYR/B0PxyCIPBf50/k0vvXMPNnK4bcZmKeg0e/eBKFHgsrd3USTqTZ1hzkmY3NfHZeCfMqPj7N+nDoeXI3ajSN76ZpCILQL32Six1kXz5+SJ314XhqQyNv7urk07OLKPfZeGNXJytq2tncFEDToNBtoSajciFGtj5bw8JvzqPeH+W+t/Zx2axCFk08tBZJEATmj8ti/rgstjYFaAnEOWtSLkaDSFpRafBH8UdS/Pb1PXzlyU009U7kxtPKRyXrCS6tJ7qhHevMHGzz8kFRUSJ97W80jRJNE9/aRdcDW/HdNA05+9g40GiaRksgTq7TPKRc4+n3m/iv57ejqBqSKPDfF0zii6eVj6rdyHOZuXXBOL5wahmPvNvA39Y00NQr4rObuPOcKi6eUUCRZ/jz+K/zJ7FmXzffeHors0rcbG0O8PNLpnH25I9ufdhocIJsHweY3JMQujQUn4ZqL/u4qzMskvv3YRw3Dn/3KizmEqxWPUoyd86L1Oy6kzbtDUzfzwE6cXlnD9rfUlIATZBRhk5PraZSdNx1F4a8PNyXX35UdVPVJDtrvoEsO5ky+bek070AxONNuFyzRtj7EIylpcTWrh1Rr30QtpPmIjqdOrnuI9uR1W+jRqO4+iQkmqbR2vYMDsc0Tpr7Yv++VmsZnZ2vsnHjVahqgsmTfoXNVkk83kRL6z9obX0av/8NAMpKb0MDGhsfRFVTiOKhgUhLy+PIspecnHNGfZ7D4aCPbGJPL7Z5eUTXtxN4fi+eq6oGNYSJfQG6/1aDIcuM78ap/UT7gzC4zfiun0LXg9v0CKAAotWAaJMRbUbUuC7HME/OQhAFMv44XX/ZgWAUcZ099NT1cBDNBnyfG93sULymm+6/1xBZ24bj1MGRVC2tkNjTS3xnN/Fd3WgJBbnQTvYXpw6p3zw4uBgKjjOLiW3qRLTKGLJ0IpnzzbPR0sqhYymT4Zk3YfeNUHU36qnXEX6ridh7OzGmk1CxYHQXoU+vTd9Ca6d7Nt2BZhpa/gotIm7X7H7pxUGEQtvZtes7RKJ7+gZ1u4nFGkj2Edhs31kcOPAAweAmXK5ZgwawaiJD6kAIxxmFRx0hMpXpftUGzYFC+Kii2gCiaMBkyiGZaEPTVFKprlHLSEJvNiJIIo4RMuRaZ+cQ39VNcHkDktuEZYqPwIt1RDe0Y5ubh/uSymOyIPh4wTzRC4J+zx8N2QaYWeLh7quqaQ0kcJgNOM0yDrMBh1nGaTEwLtveT6guman/7eIphTX7uvnxyzW8cvtpSJ+gJDpKOEV8p66nT+4PYnCZiKxtxTonF89l4484IwagqBq/fb2WrkiS12s6+r+fWujkjsUTOHtyLpPyHSQzKhsf3MLExii3PLgOf0bhc7KFm/36YF+0DKZT1cVuqosPyVZkSeyzwITHbpzH15/awi9e3c1TG5q446zxXDi9YMC11TIqme44cq6NTE+C6Psd2E7Ox3Px8EE8+7x8uv6yna4/byf/23OPOpr/QSTSCt96ZiuvbGvDZBCZWuhiRrGbmSVuZhS72dYc5LvPbeOUcT5uWVDB1AIXHtvRBdYATAaJWxaM45YFww+Sh4JZlrjn6hlc/ee11HVGmF+RhXcM5R9vnCDbxwGCuxhjXEXxSKjW0XU0qppGEEQE4aObqkztr8dyyhx6e5dRUHB1f6cqy06mT/sjBw78mX37f4vB4MBqLRu0v7U0Dw6IKOLQVmD+++4nubeO4j890B8Rbmt7gba2Z5k581EEYfhGoLnlCSKR3VRP/wtGYxaSpHcosfiR069/EMYyvd4j6bUPQjAacZx5JuFVq9DSaQRZJvTKK0g+H9Z5upQmHNlJJLKbqgk/HbCvxVJCYcFnaG55lJKSG7HZKvu+L6Zy3J1UlH+Vzq7X6e1ZQ3HxDfT0voumZYhG9+FwTELTVDo6XsHfvZKS4i9+6AVhqdYIPU/uJuOP4760Evu8fCSnidCKA0hZlgHEN7k/SPcjO5E8ZrJvmoZkP3JjZSx2kP+9eWgZFdEqD+jQops76X1qD4m9vRiLHHT9dTsoKtm3TO8npscD5kleTJVuQisa9WQUFgMZf5xUU5jE7h4Su3vQ0iqi1YBlig/L1CzM4z0DfL5HC0ESsc0daGMpyOLAjk2S4dMPw1OfhVe+jijKmAtKiW9uxlVzM8K1T0PFQlj5MwgcgE/dBa7D2otkGPx7Ycdz+v/zpwNQPvUuSn/zOOlZV7PGtJzGpoeomvAjQB+k1tf/gQONf8Yo+6ie/iAmUx7rNywhGt1Lom9xpM02Aau1nFisHl/W4kHnl6wLgKphnnCUrhWhVqSWdxDkbKSkGYxHJyE5CLMpn0SylVS6B01TMJlGjlLFa7qJb+3CsaAIyXHk+1cQBDyXjcf/yE56ntiNIctMpjuBY1ExzrNLP3FT0B+EZJMxljpJ1HQf9QAW4NKZRUe1vcUo8b3zJ/HlJzZx/UPryXeZsZkM2E0G/d1sYEKOnVmlno98oVpsaxeoupQsvKoJyWUCUcR1TtmIRBvg3To/neEkv//MTMwGEX8kxcKq7EHJk8yyxNxLquj8/WYKW2Js1FL8yehGaQzT/eRufNdPQZAEfaDaEiHdHNZ92S0G3JdWDrqnzLLE/dfOYkVNB79bUcvX/rGFe1fW8Y2zJ3DOlDxEUSD4WgORd1pwX1ZJujkCAjgWFlPvj/JGTQezSt3MKPYMIOhyng33hRX0/GOPTtRHiOofCb3RFNc/vJ7tLUFuOaMCRdXY0hTgsbUHBkiM5pR6+PPnZmM1fjyUckqBi20/+tQn+rk9QbaPBxz5yGmNtFVD1UYmTJqmsXHTZ7BYipg65Z6PoIKgRCJkOjpITRFR1SQ+30DbL0EQKSu7FY/nJDJKbEhiLBoNSGkLilFAU1QESURTFFIHDhDfvJnuv/wF16WX9uucAfzdq+gNrCUQ3DjkwsCD6O1di9Vagc+nR5clyYTJlEc8fmDYfYaC+/LLkIsKMY0b/WjZcc6nCL70EtF167FUTyeyejXuq67q995ua30WUTSRm7tk0L4VFV/HbCmiqPCaQb+Joom83CXk9e3nsOuerZHILkymXLZs/QLh8A7s9okUFw9vUTgSNE0juq6dwCv7EC0yvhun9aeHdpxZTKYnQfhNfdDiXFhEqimC/5EdSG7TqIh2//kM40pineYj+Go9kbebQRJRQilybq1Gzj2+riqCIOC+sIKO/91E213r4DA/aNEuY52Vg2WqT0/F/FERAoMRrvw7/OMa+OftWNRzSWhfJu0+C+Mz10PBTGj4F0hGqHsTplyqE2//Xgi1HDpO4Rww61F2wWhBKjwJqX4jeQsuorX1GSrKv0o83kTNrm8Tje4lP+9yxo//PrLsQlGSgEg0upd0OoDB4MRgsOFwTNPJdvZgsp2o7UUwSRhLRyl3ad8Ob/4M9i5HAIzKTxBDIvjAIh+9ZZ/JnE84XEOqT2N+JNs/TdUILm8gsroZOd/WLwsBINQGjjx9ceQHINlkcm6dTnhVE+G3m3EvqcA+xIzIJxWWyVkEl9WTCSQwuI+/Pvf8aXlcP7+Ud/d1s68rQiSZIZrMHP6Y4TAb+O55E4ddfHk8ENvSiVxoxzrdR/DVBhDAfkrBiAOug3h+UzNOs4FzpuRiMhw52GUssGMscXBr0Mh5JVak7QFsJ+cTXdtG15+3ocbTZLri/TamotWAGstgOzm/P4vz4RAEgU/1LfhbtqONu1fUctvjm5ic7+Ty6QUseK8VgwCB5+tABNtJ+ewIxfnCw+vp7XPk8FhlFkzI5syJOSyYkI3baiRp19vmTG/iQ5Htu9+opaY1xJ8+O5tPTTkUXEgrKrvbwmxp6sUfSXHDaeUfG9E+iE8y0YYTZPv4QJIxaDJJK6jxxIib9/S8Qyi0mXB4J5mqsWeGOxqk6vVRaTi/HUmyDUt8R5JsiGkjqlml5ftLSTe8SHLXRrRkEgC5uJjc735nwPbxWAMAHR2vDFumpmmEQlvxek8d8L3FUkI83jTiuR0OyenEefbRWaTZTj0V0Wol9MorBJ59Fi2VwrXkQgAUJUl7x0tkZ38KWR6c3lmWXZSW3DiqciyWMkTRpE/xxxsIh3cyedKv+5KLjI0MqokMvc/tJb7dj2mCB++VEwaQZ0EU8Fyu61/Dbzb2k26Dz0L2TdNH3UEdCYJBxH5KAaHXGgDdZvKoNMofAnKeDe9VVaRaIwgGEYPHjLHEgSHbOqoo1/GplBmufhye/hyWYIDeZoiX/wBj3ZXQ+B4s+T2Unw4vfw12PA++Sig7HbIngG8C+KrA+wE/9vIzYOXPKcn+HW3tz7Fl6xcJhbZjMuVQXf1XfFkL+zeVJBMWSwmR6F40LYPJpHeahQVXYzRmYbMOnJLWNI3Enh7Mle6RByU99bDqLj2TptkJC78HVedi3GRE7N0I1GEOBY76kpnNBfj9b/Yv6DQeIbIdXddGZHUztnl5uC8cd2h2Ye0D8Np3wFEAVefBxPP162o4FAARJBHnWaU4FpV8fPfHGGGe5CW4rJ7Iv1pwXVgxKrLRcd8WbHNysc8b/aK8gxAEgZ9c/AFLVE0jkVYJJdJsbgzw13f289OXa1hYlfOh/Y91u7deGvyxIX+vzLEz2WQk3RzBdUEFtrm5hFY1o2UUHAtGF7mPJDO8trOdy2cVjUi0D8I2L5/UM7WM36MglzrxXFKJaJKIbu7U7UyrczAW2ZH7Mk623bWO2NauIcn2QYiiwIXTCzhvaj4vbWnhf9/cy4bl+zgHCz8ypTgnKTBHk7mvN8A/H6zDYzPy9xvm0dAdZdXuTt6q7eLFLa2IAhR6LER7EvwTB6s3tHDORO+YiGhzb4wn1zdyxZziAUQbdDnMtCIX04o+XmeefyecINvHCXLeTLTUJtRYdMRtGxsfRBTNqGqCLv+bwy54OpZI7ttHxqvRLayjIPeKMUsWDIKJdGwPWtqEoeQazJNPwTItC3PVBIyVlQMWRWqaRiyuk/zOzmVMGP8DRHHwLZhMtpNKdeF0DvQnt5iL6e7515jqeTQQTSbsCxcQfFHXY+fceSeW6foUvt+/gkwmREH+FR++HNGAzTaBQHATsVg92dlnk59/2ZiPl2rSpzOVQALXeWXYTy8akkAcJNymChdKOIUgClhn5hwTon0Q9pPyiPyrGfPELGwnHdusoSPBOiMH64yPNwHKIMgWuPYZRE3D9PBOotsjOG56HVGIQE5fVrrrXx798crOAMDe1UZW1kK6u9+iIP9Kxo//3pCDdbttPNHoXiTJ3O+K4/HMw+OZN2jbTEcMJZjCvPgIEpJwB7z9a9j4MIgynHYHnPo1sOguDcZxPUhr9HqYm3fD6FRc/TCb8lHVJJGI7uZgMg5tn6qlFUIrmzCWO3Wd9UFSsfcNWP5f+qDE5IStT8L7fwWjA8afBZMvgckX90e8/92INoCcbcV2Uh6Rd1tBEHBdcOQFaWo8Q7opTNxiGBPZHgqCIGAxSliMEudOzWNqoZPFv13Nr17bzf9ePbxziaZpNPfG2dYcRBRgcoGTeFphS2OA1kCccDLD27Vd7Os6cv/5I6eLxcAdWxto2biPhW6ZuQVeipyj68+WbW8jkVa5bNboZTXW6T4CL+9HS2RwnK7PhLjOK8d13tAJyswTPMS3dI1K1iKJApfNKuKi6gLa79+CIaVy95fmce/KOv62o4NET4S55V5+efl0cp1mphW5WFJdgKJqbG0OsGp3J3WdESbNKiL9Zgfbajp57akt/HDJlKPWMf/hzToEBG5f9Mk1efh3wgmyfZwge8ahdm1CHWZUfhDh8E56et9lXMWdNLc8Rmfnso+EbKf21xO+UEUQJMrKRpd+fChYi6tI+jrI//QZ9PxjD4kmBU0RyXi7MaVEHMZDWe6SqQ4UJYbXcyo9ve/SG1hLlve0QccMhbYCDCbblhJSqU4UJX5U7gZjgevyy4m8/S/yfvSj/qg2QGvbs5hNBXg8849JOQ77JFrbngagZJQR8aGQ9sfp/NNWMx5E4AAAIABJREFUJJuR7FuqMZUOjrofDkEUsM0+fqu1RatM3ndO0nXMn/DpvY8UgoDr3DI6791C76ooWVePMf1z4SyQbbD5caaoEeLdSZy734KtjXoU3FsBnr53VxE223j83SuRJDsOx+DsqAehKSqht/TZI1OVZ+iN9q3SZTFKCmZdDwu+rUs1DoOxyI6U1qevzQ1bIBkB0+gX8h20/wv2tQXGYch2ZF07ajiF6zOHLfitfxue/QLkTIGrn9TLTcdh/2rYswz2vAo7X4CrHoNJg6Vg/05wX1KJYBCJvNOCllR0bfAwhC7Tq8+yppvDx80archj5abTK7h3VR3Xn1LGrJK+5EaJNBvqe9jaHGRrU4DtLUF6oqlhj2MzSlTlOfjVp6czr9yLwMC6qprGljVNVK/pZI2gEJRMFDksrPBH+dPmep6ck8vJo3BNeX5TM+U+G7NKRu+9LcgSjtMKiO/uwTx55DKsM7Lp2d1DqiHUnxZeSyvEtvlJ1PaS6Y4jWuU+r2wZ0d63BqYliu2CchwWI9+7YDJcMPxCcUkUmFXi6b/eAO1bQywQTHxmaytv7OrkhtPKuWJ2EcXekR1K6jojPLupmc/NLx2kXT+BseEE2T5OMJg8qFZQjhDZDkd2U7v3LiTJRmHhNaTTPTQ1P0o6HRpSonAskOntpeehh+lc8Xfi31IoKfp8f6RrLJBlN72BtWzYfTHJSe0oEyL6DzUgiTbOOGNDf9Q8FtOj2sXFXyAY2kpHxyvDkm1BkAelo7ZY9GQo8XgTdvuEMdd5NLCfeioT1q9DEA9NoycSrfT0vEN52e1jlnkMKqfPR9vlnInbNdjxZbSIvKPre3O+XI00yqjO8cYnxZf4kwZjgR3nomJCbzQSm+Lrz6J5JGiaRnBZPemWCN6rJyI5jVA6H/YuR7ZmIU++EsJt0F0He1eAkjy0syhjm1KN5lHIZIKYTUM/72osTffju0juC+JYXDK87d3qX4HNB9e9CFlDr4WQ7EasahWmVB7WyA7Yuxymjt6RyNRXx1BoC7KcNWRiKDWlEH6rCdM4l56oRFVgxQ/1pELecfCZJw4RfNkCVefqLyUD90yFLU/825NtQRRwLalAMEuEVzahJjN4r6wactGv0qvfE2osQ6Y7gTyW5EqjwG0Lx/H0+03c/sRmHrtxHtFkhs8/vAF/JIkowIRcB2dNymF6kZvqIjcaGjWtIYwGkZklHkq91v403ko0TcYfH1SGmsgwd1MvUs7/Z+++w+MqroePf2d7V5csW5Ll3gu4YhtssOnV4NBCJ5QUEkjCS0jIL6QnJIEQQgoBAgRCABN6MN1ggwvGNu5dsmWrd2lVVrt73z9mZcu2urSSLJ3P8+hZ6e7duzOSbJ2dPXOOiyW3TeHKSOUkf32Q8/60gruXfsnb3zntqJrMxzpYVsPqfaV878zRHX7h4Vs0FN+i9uWlO8YloKwmar4sxD48BiNsUPzMNup3l2PyWLGmugnXBgkW1RCubjhcP1vZTLhO7vyCiDnOQWZNA+/edRq/e2cnf/pgN3/6YDdjUrzYLCZCYYOwYRAKG4QMgxinlV9cMpHMBDfffG49XoeFbyyQVe3uIsF2lFgtMWCFcG11s/fv3Hk/Bw/9C5PJzsiR92K1+khOPo8DOU9QXPx+l9IJWhL2+zlw3XXU79mL/754zOYgQ4fe1qVrJiadqWtf25OJj5+Dwz4IU6WP8vVbKUx/nrLidSQk69zrxmDb4xlDUtKZFBS8jss1jIz0G45KY6mo/BKvZ9xxqS1Hgu0DUQ+2gaMCbYC8vJcBo1t/No058V35OYT8DdR8UYBranKfCbRF67ynp1O7vZTSF3YQqhyGY2Qsle8foKGwBmuyC2uKC+sgN5YUF5Z4JxVv6brBmBSFf9lI4k0TsS74IWTOg+k3Hd48CUA4rAPv0n36Y9c7uPctg2l61cvezIvrhqIaSp7eRrCsjrjLR+Nu6Y984Q448Bks+mmLgXaj+Ji5uL+YSFX4fzS8Vo8tNxvH6Lh2dWBsXAAIBIrxeI5f0TMMg/LX9xKubsB3TSTo2f6GDrSn3QBn/wpsLWwMM1tg0hJY/Vfwl4C779WN7gilFDFnZWKyW6h4O4uS+hAJ14w7rttnsPzI/qGGnKqoBdtuu4V/3jiDa59Yy1f+toraQJBYl41nb57FSRmxuJsJgCenNb+yXPLMNgL7m+/jYPJYSbxhIpYmJUrddgu//8oULv/7Kr7x3HpuP204s4YnNFuq8NUNeoGisbxhtJjsZhwTEqhZX4g9M4aGwhrqd5cTe/EI3LNSj3snIhwIEa5uQFlMLZZfbQ9LnJ3aQ1WMTPby92unc6Ckhjc35/J5VimgV8NNSulbk2JddimX/20VE4fEsLuwiqdvmkmSV/6edBcJtqPEElmZbmg4fnNQOFxPbt4LJCWdw7ixv8Rq1f/R+HxTcNgHU1j0TrcH24ZhkPd/P6F+7z58f7+L3OBvGJ55FzZb1/7QpCSfS0ryuccdT/DmUbjnBQq3v3dUsG0yObDbBzFyxP8jGKxi794HyM19gVGjfkRiwhlAmKqqLc3O/3CwXdexTZLdwTDC5OX9l7i4U46radwVPt9k5s5Z2e53F4xgmOpVuXrj37AYzG4r/rX5GA1hvPNOnEoKA50ym0i8cQJlS3dT8cY+KtCly+yZPgK51dRuKT5c0QCzgpCBZ94QXFOTKH5qK4WPbiT2ohG45t55/KqcyaRLCMYM0RsvB0/F/dhbKEwYhHFUVcO+h3WONVC3p5yS57ajTJB0y6TWg+EvntI52lO/2uYc7UN91G4qplqdgSVwSFf9+DAHx9h4Yi8a0WqHO72abSMcDjRbicS/Jo+adQV4M3ZjHxzZxJ29UqfWnPcHHVC3ZvKV8NkjuqzirFvbnMuJwDs/DeUwU/7qHoqe3EriDeMxNQlsQ2X1evOo0vs7XCdFb1/DhMExvHDrbK55Yg1DE9z888YZpPg6VjElVB0gcKAS98xBzXadtaa6m91nMiMznh+dN46H3tvF1buKSPHZuXDyYC45aQgTBvtQSmEYBv9df4hZw+LblVbRVTHnDiNUVk/pCzsBcM8YhOeUwc2ea7KZMcV3/V1Bc5yDsD9IOBDCZDOTkeDSK9ULmj8/v6KOG5/6nDVZpfy/c8Yc1TZddJ0E21FitehgOxg8/lV5ZeVmwuEAqYMuORxo127egsnlJD5+HoVFb2MYoW6tuV327HNUvvUWiXd+m332V3BY0rqUI9wWV2Yqri2jKa39FCNsoEyKmposXK5hKGXSVRMm/52SkhXs2v0LNm26lfj4Uxk8+ApCIT8+75Tjrmm1xmE2ezpc/q87lJevpbbuAMOH39nt1+5IGo//83wq3jpS39SS4iJcFcA+KrZLJZ5EzzN7bCRcPx7/5/mEyurxzBtyeCUrHAgRLKyhIb+GhgI/5hg7nrmDUUqR/M2plL64k7KXdlG3rYS4y0Y125TnsOQJmKxenGEXNaZq7Btege3LYcYtVG+ooPy1vViSnCReP6H1Fs8NtfBlJPXC0/YfYvesVKxDPFjr1mH6zzcIf+Vl/CVjqXz/APkPfoHv9HQdIDaT8qCUwm5PpbZ2/3ENbeqzKih/bQ8O83p8BT+FHR6YHKnskj6j7UAbYNBESJkIm/7Tb4Jt0A1NTHYzpS/spOqjg8Sck3n4vlB5HeZYOyaPlfo2OrLCkZKK1gQnrhkpHU61GJXi5eO7T8diUu3qjnis+t3lYIB75iBsaR2rZvS1U4fz1VlD+WBHAa9uyOXpVdk8vjKL4UluLpk6hGGJbvYV+7m9gw1UOssSYyfptslUfXKQhnw/sRdF/3ktsXpVOlRWh6kdZVcHxTh46fZTWJddyvzREmh3Nwm2o8Ri0atDwdDx/6mVl68FIDZ2OgDhmhpyvvY1bJmZxD58Jbl5L1JdvROvt32d89pSs34DBb/9LZ7TT8d/jqJ6304mTXwUszm6tVnjE+ZxsOYJqnZk4Rs/nJqarOPmlJBwKrPi3uTgoWfJynqY0ki1EVdojC4/NuZIRQSlVKfK/3WH3LyXsFi8JCV1vatjZxkNISo/ysGW6SPm3GHU76ugfl85YX8DvtO7b7Vd9BylFJ6Zx7/YMtnM2NK8zQYZljgHSbdMpnrlISqWZRN4ZAMJV49rubyi2QJp03H7d1HjBceeVQBUvbudipU1OMbEEX/V2Bbrph+27TWoq4Dp7asBrywmvUoeOAXMNkyHluM9axHOKUlUvLWPyvf2U7O+gNiLR+IYrVNcGl+Yg65IUlu7/6iGNsESPyVPfo6FIuKHfYwqS4Bdy2DUWVCwFRbc266xATDlSnj3Pijeo8st9hOuqclUr8mnbnfZUcF2sKwec5wD6yA31Z8ewgiGW23oVPnBAao/PgjoFzixi0d2eB+Gw9r5BaO6XWWY3BasrZTMa43TZuaCyYO5YPJgymsCvL0ln1c3HOLB93ZFxmbi3Ek9VylJmRS+NjqbdidznP77Hiyrb3ePA4/dwoIxfaySUz8hwXaUNKaRBMPHb5AsK1+L2z0aq1X/gSlf+jKhigpqN28myfzTyDlruiXYDpaUcOjOOzENS6b8NhOF+35HQsLpPRI0pow5i4MbH6dg/Ts406+jri6HlOTzjjvPZLKSkX4jg1IuYs/231Odt5fKR4tQRgnxV445qoyb05mO37876mNvKhisorBwGampl0b9BUprqtfkE64M4LtiDPahPl1xRILsAUmZFN7T0rBl+ij99w4K/7IR5+QkfAszsCY387Z4xikkbV9FcPwkzA2fUxeaQsWnfpyTk4i/Ymz7WpNvehFiM3St6o6wuSB9FuxbDuhVvoSrx1E3o4zy1/ZS/OQWbO58QpYMQtUNuE9OwTs/7XBFElskjcQoL6TkkeUYDTEkTN+DafGL8OadOld7/2eAoTeNtteExTrY3vkWJH6nY3MC2PUu/O97OiXltLt1A6M+Qu8B2E/I33D43ZJQeR22NI9+URYyaMjzt/gCrXZrMVUfHMA1LQVLvIPK9/eDWRG/JPp7ZUC/6KrbVaY7vHZDWcZYl42rZmZw1cwMcstreXNTLsleB15H53Oi+zpLJNgOlbXd60NEX8/2VR1ADqeRqKNL/4XDQSoq1hMbqwvPGg0NlDz1T8yJiRAOE9qwH4cj/fDqd0saCgpoKCho9RwjGOTQd79HTVIJhd+rpqjsXYYPu5PJk/7WI+XYfLFTMOOhIryWnEd0aoylNIlwbfD4sTaEqPukGt9LFzDk0+/gnZuOLdNH2cu7CeQe2WTauLKdlfUIlZWbMIxw1OdRUPAW4XAdqalLov5cLQnXNOjKC8NjDneDFMKe4SPlOyfjXZBO3fYSCh76gtIXdh5fwSFjNqkFdZy87hAhSwalDd/H4q4n7rLR7Qu0/cU6WJ64pNlujG0aPl93mfQXHz7kyHSSMvp5fJZnMGpqscXX4pqajH9DAQUPr8dq6Ley7fYUjEMbKPvjUzTUpRJ/ag3WJT8GsxVGn6NX21c+pHPJh0xv/5hi0nQqya53OjaXQA189md4/goI1sMnD8Bj86HiUNuP7SH2UbFgQP1evWcoHAgR9gcxx+omTwCVHx6gek0edbt1+TkjFMYIG1R9nEPJv3dgTfcSd8lIfAszcM9OpWZDIaGqlsv1daeG3GrC/gbsY1qp9d5Jg2Od3HraiKhvjOxtJo8VzIpgWX3bJ4uok5XtKLFEgu3QMcF2dfU2QiH/4e6JlcuWEczNY8ifHibvhz/C/+lnxF0xk+KSDzGMcLMl5oxgkP3XXUe4poYRb76JOab5DU0Ff3qQvCGf4l8UxuVIZcr4f+DzTe7mmbbMZLIQnzSHcvPnuOL05sbA+2Hy3/6cpNunYE126Q5k20oof3MfobJ6nFOSiDlvGJYYO6GqAIWPbKDwLxsBhcllIe7CUynzrGZf1sPsy/ojVms88fFzSYg/jfj4U7Hbuz/XLDdvKW73KHzenvveNRWqDFD85BbCtUF8Td4WFgLA5LQQc3YmnrmDqfrkEP5VudR8WYjr5BR8Z2ToPOy06aDMGJUFlNgex8BJwuiVmOzt7K669RUwQrqKR2cMPx0+/AVkfaxLAJbshZduQOVvwjf3dnxrvg2j74HT78U7P42CB7/AVKCDQntlBdVPPUtN4CZ8s2w4z1/c5LoLdKv7g2shbYZeRe+I0WfDyj9CbdnhhjzNaqjV34ON/4acNbrG+NgLYPHf9cbMF6+FTx+G8x7o8LcmGmxDvCi7mfo95bgmJxEq1wGXJc6OJcaOY0wcdbvKqNteeuRBKtJe3B/EOSGB2EtHHe7G6ZkzGP+qPPxr8tpd8q4r6naWgQLHKFlY6CxlUljiHLKy3UdIsB0lh4Nt09G/6OXlnwMQGzuDwMFDFD30R2wjRuBdtIiK11/Hv3IlMbfdRl7+y/j9uw/XYW6q6t13adiv22wXPPAAg3/5S+r37gWTCfsw3cWq4IOn2Zn0GMGpBkOGXMOokT+IeiOY5mQO/TpfVmwkz/0sAKmXnUHli7kUP7mF+MtHU7n8IPW7yrCkuEi8ZdJRq7Zmr43EmyZSvSYPZTVRt7OMuucCjD7tIZwLPJRVrqKk9GNKSlZQUKC773k840kdtJj09Bu7ZfXe799DZeUGRo38Ya80ZwmW1FL0xBbC1QESb5iAPSM69dfFic/ssRF73jC8pw6hankO1WvyqFlfiHt6Ct4F6VhSp1CxfwqB+iTik1/CWt+BvQ9b/gtJYyG5k6ltqVPBHqMby5gs8Nq3QJngqhd07evslTpgBqzJLmwZXtS2sQw993ZsX+yhOHA9jtEevBdPPfq6dq8uf7j3Q8joRKOpUWfDij/oxzdXB7xkL6x7EjY+pwPyhFEw63a9Uj/8DF35Zcw5uiPll8/Dwh/rMfUyZVbYR8RSt0evbDcGXObIprnEGydihA1ClQFCpbUES+sIltYRKqvHPjoO19Sko/6/sya5cIyJo3pNHt4F6cflejfNte8sIxim/PW91G4rIVzdgDXNg9nTd1JzTkTmODvB8s6vbAdL66h8N5vYS0dJ34QukmA7SkwmK6aQhbDp6LfdysrX4nQOhdwa9t94E+GaGjIe/iPKZMIzdy7V739AUk1ak3MzaGgoJxispKGhgoaGcspefBRbZiaehWdQ+sSTBIuL8X+yApPXS+aLz3Oo/CWyQo9hjrUwefyfSRrUztWrKPD5JjN71tvs2vUzXTN3dAa2G+IoemwTRY9tRtnNxFw4HM/swc2+nW0d5CbuYr15yTgzRPkb+6j+5CB1253EXTafQeMvwjDCVFdvp6TkEwqLlrF7zy9JSJiP2931Hd+lpZ8CkNxMecNoC+T5KX5yM4QMEr82SQJt0S5mr43YC0fgnZ9G5Uc5+Nfm41+bj9lxL6GQE/fMJFzhaji0r/ULHVwH65+BIdN0be3T7+tcCgnoTZqZ8+DL/8D6p3W6x1f+qXPAQa9Kb3lZ1wg3mXBOSiLwVhVDfd+kbMtSTJZa4q+e13xAN/ocHSwPndPxcaVNB2e8TiVpDLYb6mD3u7q9+77l+sXB2Atgxs06X72578HMW2Hzi7DpBZgRvSpPHeEYFUvdthKCJbWHUwka83ghsvIZa8cSa8c+vO3reeYOofjJLbpMpMVEqDpA2N9AuLqBcG0Q7/w0Ys5pvmV5W8L1IUqe1Y1enFOTsA5y4xzX/SkkA40lzkHttpJOP77my0JqNhbhmjFI0he7SILtKDKF7IRsR15VGkaY8vLPSUo6k4PfugMjEGDoM0/jGKs7Jbrn6nrUwbX7sA8dxK5d97Nr1/3HXdeySDHZ8WtiLriQ6g8/omb1GuKvu5aKV19j4+tLqJ5YgXOHjakXL8U1aEKPzLU1VmssEyY8ePhrW5qXhOsnULezFO+pac3WSm2OspqJu3QUzgkJlL2yh6K/b8I9O5WYczLxeifg9U4gKeksVq85i4qKL7ol2K7278RiiT3c0a6n1GdXUPzUVkw2M4m3TWr3bnIhGpl9duIuHol3fjq1m4sJZJWB0UDsRaPh42Gw9VUINejc52M11MF/b9FNcdY/rY9N7GLt/zHn6M2Is7+hm+I03VCYPgu++CcU7YCU8ThH26l4C8pe3Eqgfhixk/a3XC1l6tU6zWPEwo6PyWSGUWfqrpvbXoNtr+vAO1AFvjQ44z446TrwttHJL226Xr1f+zhMv7nzL0q6kX2kDo7qdpURqqgHs8LUzv9rW7qeLdNHYH/l4bbi1hQ3puFW6rMqqNlc3Klg2wgblDyzlfqsCuKWjMY9vfNdE8XRzLF2wtUN1G4pxprmoerDHOqzK0i8ceJRL7xaEsjWpYuDxbUgwXaXSLAdRRbDQdheg9HQgLJa8ft3EwxW4CqOp3bvXlJ/8+vDgTaALSMDa3o61cveZcwffkZFxTos1lisFh8WayzBbdnkffQ4FfPLUVOGYXI4yHz+3xjhMJb4eNRpw8mp/yHuD8xMOOcfuIb2fqDdEseI2E6/UnaMiSflrmlUvpNN9apc6raVErt4JM6x8bhcw7Fa4yiv+ILBgy/v8jirq3fh8Yzp0RSS2h2llD63HXOMncSb2/efohAtscTa8Z46BE5tsiEsfrjOwS4/0HwnyJUP6UD7mpfB4oC6yjY7RrbppGt1jnXjanZT6XrDODlrYP+nWJbdi23QqwRy6rCog7hPb2W/hN0L87pQ/37UWXpF+sXrwJWgX1SMv0jnmZva+da5UjDzFnjtm/D308CdpHPAGz9cCTqo7+r3sAMsiU6sg926XOhgD+YYe5dSPZRJkXz78f0PAKpWHqLizX0Ey+o6/P+Vf1Uu9XsriLt0lATa3cw5KRH/2nxKnt2uD5gVyqQoW7qLxJsntfr7YIQN6vfr0sXBotoWzxPtI8F2FJlx0eAqJVxbi9lqpSxSYST8v72YY2LwnXt8akL8tddQ8Ktfk7D9ZpJOuweAwP79FPzs11R//DExo9Opml9DftGrxCZMxxx7JGDN932MudDBqFPux3vqvJ6ZZC8x2c3EXjQC55Qkyl7eTclTW3FNTSLmwhHExJxMRcUXXX4Owwjj9+/sdBWSYEU9BJuplmJW+g9fMwG8//N8yl7ZgzXVTeKNEyRnUURHfGQFsizr+ACwaJcOtideBiMXdd9zKtV8oA06+Hcl6DJ+OWsh3ICr4U0CnI/P8zoq9cruG8exxl0EZ/0CUqdAxpz2NcVpzsQlcGi9fgFTW6a/t7VlUFsOGLDsHhg6D+bdBSMXRn31WylF3OJRFP5lI3U7SrEPb6UzaBc5RsZSAdTvrcAyvf3BdrCklopl2TjGxOGaIYF2d7MmuRh09wzqtpcQOFSNe8Yg6vaUUf7fPfjX5LXYxRIgWFiDUacrhwWLalo8T7SPBNtRZFZuAk7dtMbs81FevhabJZm6Nz4j4drrMNntxz0m7sorKXvu3xT89gGcU6dS8o/HKX3qKZTVSvLddxN/7TUE9vyQ/II3GDXqR4c3PVZVbaOo6B2GDbuDhOFf6emp9hr7UB8p3z6Jyo9yqFqeQ93uMlxnjqG45gMCgZIutaOvqztIKFSDx338JtXWBA5WUfFOtu6A1gKTx4pjVBwxFwzH7LZiGAaV7+6n6qMc7KNiSfjquLabjAjRWfGRJN3SrKOPb14Kb30XrA44+1c9Nx6lIG0m7HobzHaYfw/u5X/AYl2NfdzY6AamFhvMuaPr17E64IIHjz8eDkPlIZ3Tve6f8NxlMHQuXPoPiIlu+Tlbuhf37FT8q/IONzmJBkuKC5PbSv3ectzTU2go8KMsJiwJToyGEBXvHaDhYBVGMIwRCOvbhpAuA2tSxC4e1Ssb0AcCZVY4JyYebnnvnjGI2i0lVPwvC/uoOKyJzRdOqN+vU0isQzw0HFtKVHSY/DWPIqvJi99lEK6pwTAMyss/x1WSgAqWE3dF8ykOymYj+e7vc/Bbd7B7wekYNTXEXHIJSd+9C2uybu4wOPUr5Oe/Qvb+v6IwU1m5kcqqzVgsXtLTb+rJKfYJymIi5syhuCYlUrp0F8bHsTADKirWk5TU+c2h1dU7AfB4xrZxphauD1Hxdhb+1XmYXBZ8Zw/FHHP8CyojECKQXUnNpiLC9SESrh1H1ccHqfooB/fMQcRePALVifbGQrSbJwWsriPBdl0FvPV9HRCmzYBLHwNvz3XXA3Qqya639crv/HtQu9/DkbseRn2rZ8fR3UwmiE2HU78Hp9yhc+DfvQ8+/i1c9KeoP33M2ZkE9ldGdWVbKYV9RAz1e8sJltdR+OiXGMEQ7hmDqM+uJFhQoyvM2M2Y3FaU1YSymlFWE67JSYdbi4voU0oRf9ko8h9aT9lLu0i6bXKz6SSBbJ2b7xgTR9VHOW12HBWtk2A7iiwWL2EXhGtqqa3NJhAowr0iiHvOHGyZmS0+zrNwIZ6FCwmVlJDyg3twTj263FVs7Eyczgyysx8FTHg8o0lIWMDg1CVYrQO3YoV1kBvfmUOpf7oUhZXyii+6Jdh2u0e1eW4gp4qS53cQKqvDM28IvkUZra9Mzx6MdfBBKv6XRfkre/B/no9zShKxi0fKCo+IPqUgbpjOy87+FF65DSpzYcEPdVDY2VSKrphyFdRX6fxrk0mvEr9/f/emsvQ2i03ndudv0h05F90Prm6uulFbBst/AxmzYcJiTA4LKd8+uXufoxn2EbHUbiqm5JltYBi4Tk7B/3k+JpeVxJsm4hjdSh1z0aPMMXbiLh5B6Qs7qV5xCO/8tOPOqd9fiS3ThzXJBYZO+ZGN+p0nwXYUWSwxGA4I+auoLN+qj62vJvaHV7T6OKUU6Y/+udX7J0/6G7W1OcTGzhzQAfaxrAlOTGEbbvOYLudtV/t34nRkYLG0/h+M/4sCyl7ZjdljI+m2ydgz27eC5Jk3hLodpfjX5mNJcRF3mbyVKnpQ/DBdMm/XMojLhJvegfQZvTceXyos+smRrwefBNc6Ms3bAAAgAElEQVS91nvjiaaZt+qyihuf63gKS7AeHp0Jc76tyxE2lbMWlt4EFTmw5m+6usr5f+j+gL4ZjRveG3L9xFwwHO+8IfgWZmCymzG5+m9b9BOVc2oSzi3FVLybjWNs3FGBdKgqQKi0Ds/sVCxJOs0kWNQHg+2GOtjxpi7Nae3bhQTkPYEostrjwAQNNaWUla/FXGfFEU7Ge8YZXb62xzOGpKRFEmgfwxznAJPCHRhHZeUWwuHOF/Svrt7ZbFOhRoFD1RT/cwtlL+3CnuEj+Y6T2h1og97dH3f5GFwnJ5N47XhpGiB6VtJYaKiBk6+F21f2bqA90AyapDdjrv0HhEMde2z+FijLhg9/rtN/QOeFr3gQnjxHNwu66V1dtnD7G/DUBeDvfK3l9jInODAn6Hbwnjl6450lziGBdh+llCJ28UhMDgulL+7CCIWp2VRE3gOfk/cbXczBlunDEsnp7ra8bcPQ76jt/VCXHu2sop3w+CJ4+WZdNrSPk5XtKLI64sAPwboSyopXYd0eIu4rl6Ms8m2PFmVWWOId2CoHY8QFqK8vxOlM7/B1QqF6amuzW2xm41+XT9nS3SinhZhzM/HMG9KpPGtLrJ34yzu2AVOIbjHvTphwiQ78RM+bdSu8dIMOuGfffuS4YUDJHjiwCvI26VXwpNFH7j+0Tt/WlsFnf9ZpKa/cpoOX8ZfoPHBHDGTM0g2Jnr8Knr4Qrn8D3J3fMN4WpRTJt01G2cxd7iYpeobZYyNu8UhKnt1O8RNbqM+qwDrYg2tyIpZEJ7Z0L0rp+uydLv/X+PucvRL2f6rT1qpy9X3jLoIlTzZf678lWSv0vodtr4Pdo/ef7H4PZn+9c+PrIRL1RZHVod+68weyqLfn49trJfYnA6dSSG+xJDoxlbsgDgKB4g4H26FQHaVln2IYoWZXtuv3lVP23z1SNUSc2OxeCbR709gLdC3vZffoZj7xw+HAashZDTVNVqLrK/WG1UaHvgDPIBh6Cqx6VAcedRVwwUMw7cajK7eMOAOu+g88exmsfhQW/l9Up2T2yUbHE41zYiKuk5Kp2VCIY0ICCVeOQVmPfpfVmuTUjW06avNSvRm4Kk9/7U6GzLm6Gk9tOXz0C1h6I1z8F3C08S69YcDHD8DyX2HY4/Gn3ofrkiWY1j0Cnz8BgRqwuTo+xh4iUUIUWV261E4ZGwGIjZuJNUVqiUabJcGB2uaAYTrYbq/a2oPsy/oj+fmvAbo+ts878ahzgsW1lDy7HUuCg4SrJdAWQnSS2QpfXQof/BQ+i1QliR+h289nzIb02bDqz7D5Jaiv1qt4AAfX6Y6VZ/wYtr+pV7GvfQVSWmhiNuJ0XUu9eFfPzEuccGIXj8Q5IQHH+IRm35WwJDqp3dLy31LDMDDqQ0f+HgYD8Pq3dLOoIdNh/j2QOQ8SRh79YtDmhnfuhb3LYdr1+ne6udxrw9BpUyv+AFOupkLdSfXqQtgDnpGLYPVf9Mr56LO6+J2IHokUosjmTgKg3LQFVQODzrmll0c0MFiSnJhq9B+mQKD1XMXy8nXkHHyampos/P7dKGUmLe1aYnxT8XjG4nQeacIRrmmg+Gm90TXxhgmYnPLPRwjRBWYLnPVzOPk6HTR7ko++f8pVeuV6x5sw5UqoKYXSvXDSV3UAfce6SBnH5mslHxaXqfO8hWiGyWY+XIe7OZYkJ+GaIGWv7dEHDCBsYIR1kF2/v5JwZQBLohPH6DjcceuxbnoBTvt/OtBuqbrRKd/QLyxX/Vl/uOJ1NaSmDAPe+ZF+Z2baDfgH30v1S7sBCByogmlzdRnTPe/pVfPX74DTf9Sj3VrbQ6KFKLI69S9v2B3CuceF57y5vTyigcGS4MQc0G9JtbSy3dBQwZ69vyU39wWs1gR8vskkJMwnLe1aHPbj6wsboTAl/95BsLSOpK9NwpLQxh83IYRor8QWyotmzIbYofDlf3SwnbteHx8yXd/GZbbv+nHDYP8qHbhIxSPRQfZhMZjcFmrWF4JJoUzo3yOTQllN2IfFYE12EThQSfXafKqDybgcPyB27t2Y2iojOuRknbddXw2fPaL3KNi9+r5wGP73fVj3BMy6nbrRP6Dsn9uwD49B2cw62LY6IPNU2P2u7huw9wOYcKkE2wNJ00ohsfGzUCYp/tITLIlOTIYVMx4CDUevbBuGQUHhm+ze/QsaGsrISL+Z4cPvxGxuOdfLMAzKX99L/Z5y4paMxj4ses0hhBDiMKVg8hWw4vdQmafbwaN0WcSOiMuEQJXOBXe3vIIpRHNsaV4G//iUdp0bKq2i6qFfUl13PtbVeXjnt3PP1IJ74B9nwNrH9Op2OARvfBs2PAtzv0Nd5l2UPLUNS7yD+KvH4l9XQN2OUkL+BsyjzoTd7+h3by58GMZd0PnJRokE21FkNrt16q8JBs2+rreHM2CYY+xgUVjCsUetbNfW5rBz5/9RUvoJXu8kpk55Eq+3hTzHJqo/y8W/Jh/vgjTc0yXnXgjRg6ZcCZ88oHNgG2ohaUzbm8mOFT9M35ZlS7AtospcsppY819pSD6Xqk8O4T5lcPvK2g6ZBqPO1hV2bF7IXgHbX4f591A3+HaKn96GJcFB0i2TMHts2DP06ncgpwrnmHNh+a91zfppN0R3gp0kS61RpJQJU4MFFbIQO3h2bw9nwFAmhSXBiSXgO5yzXVm5idVrzqG84gtGj/oxM6a/3K5Au25nKRVv7sMxIQHfWZlRHrkQQhwjYQSc/yDs+1iXTmtMIemIuEiwXZrVvWMT4lg73gKbB995Ewj7G/CvyW//Y0+/V1ffeftuXSN+4f9RO/h2iv+1DWuSk6RbJ2P22ACwpnlBQeBAJcSkwfd3w7y7ojSprpOV7Sizxw7BYR+MyWTr7aEMKJYEJ+Y67+Fgu7hkOeFwPXNO+Rinc0iLjwuW1FL5UQ7eU4eASVHy/A6sg9zEXzFGascKIXrHjJt16siye2FyJ8rHxg3Vt7JJUkRTOKw70o44A/uIROwjYqj6JAdbhhdbmqftXhSDT4IfHNCdIU1marNDlDyzDWuKi8SbJ2F2H6nHbbKZsQ5yE8ipihzo203hJNiOsrFjf4nNGv1WueJoliQnpiIPgcBOAKoO7cRSF0fpb/Zj9uRi8tgweayYPVasKW7cs1NRJkXFu/up/bKImg2FmNxWlFmRcJ10dxRC9LIhJ8PN73TusVYneFOhTFa2RRSEgrrM365luqb22PMB8J2VSdE/NlH01y9RNjP2YT7sI2Oxj4jFEuegauUh/GvzIRRG2cy4TkrGM3cwJreLum0llPx7B9ZUN0k3TWy2E6ltqI+aDYUYYaPPL4ZJsB1l8XHt21QgupctzYM5x0swWE6oIYC/dB82YxDuaSmE/A2EqwIEi2sJZFXgX5OPYRg4x8ZTu6kI98xBGA1harcUk3jjRCxxzdT9FEKIE0lcpqSRiOjY8Sa89g3dbGnajTD+YgDsQ32k3juL+n3l1O+toH5POXU7I7+DCjDAMS4ec6ydUHk9VctzqPr4oL4/bGBN9+pAu4Uyu7Z0L/7VeQSLarCmuHtgop0nwbbol5wTErFv0BuBKjfuImAtICHmNGLnHF0OyDAMSp7aSuU72dTvrQCTwrcoA7PPjrFkNMrct18tCyFEu8QNg33Le3sUoj86sAosTrhry3Gt181uK65JSbgm6b4jwYp66veU01BQg2tKErYhnsPnNhTWULO+AFCY3FbcM1JabRxnH6o3Ctfvq+jzwbZskBT9kjIpvJN07dqi5Z8TslfgST2+lq1SithLRgJQt60E10nJh1sOS6AthOg34odBVa6uaCJEdzqwWnc1NR+f6nEsS4wd97QUYs8bdlSgDWBNdhFzzjBizsnEe+qQNjs0WxKdurvlttab1/UFEmyLfsudmQlAnUd3m3K6Mpo9zxLnIOb84SinBe/8tJ4anhBC9JzGBjjlB3p1GKKfqa+G/M26AVMvcIxPoH5fBeG6YK88f3tJsC36Lbtdp5E0jDwEgMs5tMVzPbNSGXzfLKxJLTe3EUKIE5aU/4sew4BQQ2+PInoqc+HlWyB3w/H3HVoHRgjSeyfYdo6Ph5BB3a6yXnn+9pJgW/RbNlsCADXOXQA4nc2vbDdqsyyREEKcqBpXtqX8X/f76FfwyLT+m6Kz4y3Y/CL8Y6GeazBw5L4DawAF6TN6ZWi2DB8mt4XabSUYoTCV7+8nXNP3XvhIdCH6LbPZg8lkJxAoxGLxYbXG9vaQhBCid7gTweqWYDsadi2D8v3wxdOtn7fzbVjxB70SfiIp3g02D0y+HD7+LTy+EAq26vtyVkPyeHDE9MrQlEnhGJtA3Y4yip/YQuX7B6jd2vdyuCXYFv2WUgqbVa9ut7WqLYQQ/ZpSEJsOlQd7eyT9S10lFGwBFKx8SDdkOVZDLbx5Fzx/JXzwM9j8Uo8Ps0tKdkPiKFj8N7jiOV1L++/z9QuHnM8hY1avDs85Lh6jLkj9gUrirhiDe8agXh1PcyTYFv2azabztiXYFkIMeL4hUCHBdrfKWQtGGE79LlTnw/pmVrc/+T2sexLm3AFpM2DZD6CmtOfH2lnFuyEhUs1r3AXwjdUw9jz9wiFQBRm920/EMTYez2lpJN8+BfdJyb06lpZIsC36NQm2hRAiIiZNgu3udmAVKDPM+y5kzGl+dTt7pd5AeNYv4MKHoa4C3r2vd8bbUQE/VORA4ugjx9yJ8JWn4bInYOSZMHJR740PUBaTLiWY5u3VcbRGgm3Rr1ltkkYihBAAxKSDv6j/buTrDQdWQeoUsHtgwT06xWLDv47cHwxA3kZdhxogZQLMvA02/vvEWN0u2atvE4/pU6EUTFoC1ywFV3zPj+sEI8G26NdkZVsIISJiIn0EKnN7dxz9RbAeDq6DoXP018Pm6xXsFQ/q+0DncwfrjgTbABMWAwbs+6jHh9xhxbqa13HBtugQCbZFv2a36/wtlzOzdwcihBC9rTHYjlYqSVW+TpEYKHI3QKj+SEMXpWDBD3SnzvXP6GMH1+nbtCal8QafpKt37P2wZ8fbGcW7AQXxI3p7JCc0CbZFv5Y6aDFTJj+Ow5Ha20MRQojeFTNE30Yr2H7mEnjnR9G5dl+0/zN923SD4PAFkD5L524H63XTF88gvTm1kdmiz9vzYd8vA1i8C+KGgtXR2yM5oUmwLfo1i8VLYuLpvT0MIYTofb52BtuG0fEgMBiA4p1QsqdzYzsRHVilNw66E48cUwrm3wOVh2DDs3Dwc51CotTRjx1xhl4BL9rZs2PuqJImlUhEp0mwLYQQQgwEFjt4UnR1iZYEA/Dn6fDBT48cq8rXVSlaU5GjS+BVHOqesfZ14ZDunthc2bsRZ0DaTN0ApnTf0SkkTc+Bvp1KEg5D8Z6jK5GITpFgWwghhBgo2ir/t+k/enV69d/AXwL+Ynh0Frz749avW7pP31bl6iCtvyvcDvUVRzZHNqWUrkxSXaC/bro5slFshl4x3vtBdMfZFZUHIVgrmyO7gQTbQgghxEDRWrAdDsHKP+pAMFgLax+Dj34JdeWQ9XHr120MtsNBXV6wvzuwSt+21NBlxEIYMl3X4E6d2vw5IxdC1gooy47KELuseLe+lWC7yyTYFkIIIQYKX5rOJ24uJ3vba1C6F878OYw5D1b/Bb54ClyJerXbX9zydUuzjnw+EFrC7/8MvIP1C5PmKKXbmy95Qtfgbs4p3wKzDV77Vt98N6AxnzxxTO+Oox+QYFsIIYQYKGLSoKEGasuOPl6wDT78hU5tGHchzL0T6it1ibqLH9XnHFjd8nVL9+nAEfp/HW/D0CvbQ085fuNjU4mjIjW1WxCbDmf9HLJXwBf/7P5xdlXhNv1Cy5PU2yM54UmwLYQQQgwUh2ttRzZJ1lfpcn1/mwe1pXDe78BkhoxZMOfbcOGfYMTpYLYfSZ1oTlnWkY2A/X2TZFm27hTZUgpJR0y7QZcBfO//oPxA16/XnYp2QPK43h5Fv2Dp7QEIIYQQooc0BtvlOboV9zs/1IHjydfDovuPbr191s+PfD5kWssr2+GQDkBHn6ObuFT2s2C7rkK3V9/4nG6xnjxeH29uc2RHKaVf0Px1Drz+bbj2ldZXyxsZRvvO6yzDgMIdMOXK6D3HANKllW2l1P1KqUNKqY2Rj/Oa3HevUmqPUmqnUursJsenKaU2R+77k1L6t0UpZVdKvRA5vkYpldnkMdcrpXZHPq7vypiFEEKIASsmXd++eRcsvRHcSXDz+3DRn44OtI+VMQvyNkKg5vj7Kg9BKADxw8E3uH8F27Vl8M/zYNkP9GbH5PGw532dXpHUTau+cUPhzJ/q9u2NnSdbs3kpPDjuyKbUaKg4CIEqWdnuJt2RRvKQYRhTIx//A1BKjQeuBCYA5wB/UUqZI+f/FbgVGBX5OCdy/GagzDCMkcBDwG8j14oHfgLMAmYCP1FKxXXDuIUQQoiBxZ2o87CD9XDu7+DW5ZDeTB3oY2WcoiuN5K4//r7GzZHxw3XjnP6Ss11fDc99RXdRvPoluO1juGYp3LkZbn4XTN2YiTvtJsg8Vaf0tNV0aNtr+t2IF66DhtruG0NTRTv0rQTb3SJaOdsXA/8xDKPeMIwsYA8wUymVCvgMw1hlGIYBPANc0uQxT0c+XwosjKx6nw28ZxhGqWEYZcB7HAnQhRBCCNFeSumV7Du+gFm36vzs9kifqW+//M/xAV7jCmv8MN0S/kRb2Q4GdGDdVEMd/OdqOLQeljwJo886cl9sOiSM6N4xmExw0SNghOCN77TcwTMchv2f6hX2gi3wv+937zgaFW7Tt0ljo3P9AaY7gu1vKaU2KaWebLLiPARo2qLqYOTYkMjnxx4/6jGGYQSBCiChlWsJIYQQoqOSRne8woQzDiZcChv+BX+cBJ/8HmrL9X2NlUh8QyJpJHltl7IL1sPWVyB3Q+fm0J2W3aNzphu7ZIaCsPQmXVv84kd1dZaeED8MFv1Up6ls+FfzAXfRdqgpgTl3wGl365bw7Uk96ajCHeAZ1HpqkWi3NoNtpdT7SqktzXxcjE4JGQFMBfKAPzQ+rJlLGa0c7+xjjh3rrUqpdUqpdUVFA6CovhBCCNFTljwJ178JqVPgw5/DQxPg3ft0wByXqVfJfUMg3NByY5vi3TpV4g9j4aUb4F+Lj1Qv2fMB7Phfz9ecztsE5fv1C4hwGF77Jux8S6fZTL2qZ8cy42swdC68fgf8PAkenQ37lh+5P3ulvh06Fxb8AIafDm99H3I3du84irZDsqxqd5c2q5EYhrGoPRdSSv0DeDPy5UEgvcndaUBu5HhaM8ebPuagUsoCxAClkeMLjnnM8hbG+hjwGMD06dNbeA9GCCGEEB2mFAw7VX/kbYJPH4ZVj4IRhlGROgi+yBvPlQfBm6I/D9bD9jd0g5zsFWCywNjzYcz5eqPmf2+FMefowB106sL5f4DMeT0zr7IsUCb47BFdVWXrf+GM+3SaTU8zmeDyf8Gm/+gXLNvfgGcuhuk367KM2St0I524ofr8y56Av58KL16nc8qd3bClLRzWDW2m3dD1awmg69VIUpt8uRjYEvn8deDKSIWRYeiNkGsNw8gDqpRSsyP52NcBrzV5TGOlkSXAh5G87neAs5RScZE0lbMix4QQQgjRG1In6+6Id6yHud+B2bfr4zGNwXZkHa0sG/5yCrx8s67tvfAn8N3tcPkzMOUKOP/3sH+lDrTHXwyXPq7TOd76Xs/Mo65Sp2XM/gZYnTrQnnMHnBqlXOj2cCfAKd/UpRhvX6nHtu4JWP4byP4UMk87+tyvPK2/36/c3j3vCpTv142PJF+723S1zvYDSqmp6LSObOA2AMMwtiqlXgS2AUHgm4ZhhCKP+TrwFOAE3o58ADwB/EsptQe9on1l5FqlSqmfA59HzvuZYRilXRy3EEIIIboqfhic+bMjXzeubFccgvzN8OxlemX7qhdg1FnHV/CYcpVuBa/MsOBefX91vg6+y3P0ZsRoKotUUkmfCcNO0xsD594Z3RrWHWF1wtm/0rW+P3lAHzt2xT99hj7n7bvh49/AvLv04zqrcXOkVCLpNspoacfrCW769OnGunXrensYQgghxMBhGPCLFPAO0lVJ3Mlw7X87FrgV7oC/zIILHoLpN0VvrABbX4WXrofbVujV+r6qoRaeOAvyN8GdW45/EWIY8PLXYMtSnRKTNhOufgGcsR1/rmX3wron4Z7srgXtA4xS6gvDMKY3d5+0axdCCCFE91AKEkbqOtDTb4JbP+r4CmnSGN18Z88HHXtc8W4o2NqxxzSubMcP69jjeprVCV99SaeMNLfarxQs/htc8axemc9ZA8t/3bnn2vuh7o4pgXa3kXbtQgghhOg+1yzVaSGNGyQ7SikYuQg2v6RrYFts7XvcSzfoFd5vfNb+5yrN0t0g7d5ODbVHeQfBhEtavt9s1WUKx12o007WPgYnXQuDJrZ+3awVunvl/HvAX6wb2px0TfeOfYCTlW0hhBBCdB/f4M4H2o1GnQmBashZ3b7zC7bpJi/FO3WA3l5lWX1/VbszzrgPHLG66U1LmyZL98EL18DTF8CKP+h63fs+0veNOKPnxjoAyMq2EEIIIfqWYaeByarLCzbX+CZ+uC432LjqvWWpvg0H9YbLlPHte57SbMiY3S1D7lNc8XDWz3XN8E9+BwvuOXJfXSWs+D2s/qv+Hp9xH+x+D1Y8CENO1s1sktv5/RPtIsG2EEIIIfoWu1fX3t7+hu6o2BxnvC47OOfbOuUkNgPKD+hqGu0JtoMBXQ+8P65sA0z9qk4RWf5rvfkzdSrsWgYf/VLX8J76VTjjx+BLhbQZup73jlyYcnXfqcbST0iwLYQQQoi+5/J/6XrPxzIMOLAa1v4d3v+J3gxYfgAuekQ3ySnc3r7rlx/QDXni+mmwrRRc+Eco3ArPX3nkeMYperPl4JOOHBs2H9Jn67QdSSHpdhJsCyGEEKLvUQps7ubvG7VIB4X/+75u+GJxwITFuqNlY53otpwolUi6wuqEq1+CDc+CKw4Sx+g63ceuXCul006W3QsjF/bOWPsxCbaFEEIIceIxmXRbd28qWB069SR5XPM53s0pjQTb/XVlu5EvFebf3fZ56TPhlg6WWxTtIsG2EEIIIU5MSh0dSCaPh62v6JbvLa2KNyrLAqsbPMnRHaMY8KT0nxBCCCH6h8YGOkU72j63LBviMmUzoIg6CbaFEEII0T80lqxrzybJ0n5aY1v0ORJsCyGEEKJ/iMsEi1M3uWmNYRxZ2RYiyiTYFkIIIUT/YDJD0pi2N0lW5UOwVla2RY+QYFsIIYQQ/cf4i+DAZ/DlCy2fUzZAKpGIPkGCbSGEEEL0H3O+oxu3vPVdKN3X/DmHy/5l9tiwxMAlwbYQQggh+g+zBS79B5gssPRm3Zb9WGVZoMy6xbsQUSbBthBCCCH6l9h03b49dz189Ivj7y/Ngpg0MFt7fmxiwJFgWwghhBD9z/iLYPpN8OnDsOeYzohlUvZP9BwJtoUQQgjRP539K0gaB6/cDtVFR46XZsnmSNFjJNgWQgghRP9kdcKSJ6G+El69HcJhqKuA2lJZ2RY9RoJtIYQQQvRfKePh7F/CnvdhzV+bVCKRYFv0DAm2hRBCCNG/Tb8Zxl4A7/0Etr2qj8nKtughEmwLIYQQon9TSlcn8STDyof0MamxLXqIBNtCCCGE6P9c8XDpY6BM4E4Cu7e3RyQGCEtvD0AIIYQQokdkzoNzH4Dast4eiRhAJNgWQgghxMAx85beHoEYYCSNRAghhBBCiCiRYFsIIYQQQogokWBbCCGEEEKIKJFgWwghhBBCiCiRYFsIIYQQQogokWBbCCGEEEKIKJFgWwghhBBCiCiRYFsIIYQQQogokWBbCCGEEEKIKJFgWwghhBBCiCiRYFsIIYQQQogokWBbCCGEEEKIKJFgWwghhBBCiCiRYFsIIYQQQogokWBbCCGEEEKIKJFgWwghhBBCiCiRYFsIIYQQQogokWBbCCGEEEKIKFGGYfT2GKJCKVUE7O/hp00Einv4OfuC/j7v/j6/1gy0uQ+0+TYaiPMeiHMGmfdAMdDm21RvzX2oYRhJzd3Rb4Pt3qCUWmcYxvTeHkdP6+/z7u/za81Am/tAm2+jgTjvgThnkHn39jh6ykCbb1N9ce6SRiKEEEIIIUSUSLAthBBCCCFElEiw3b0e6+0B9JL+Pu/+Pr/WDLS5D7T5NhqI8x6IcwaZ90Ax0ObbVJ+bu+RsCyGEEEIIESWysi2EEEIIIUSUDOhgWymVrpT6SCm1XSm1VSn1ncjxeKXUe0qp3ZHbuMjxhMj51UqpPx9zrSuUUpsi13mgleecppTarJTao5T6k1JKRY6fppRar5QKKqWWDKB5f1cptS1yjQ+UUkP72fxujxzfqJRaqZQa39X5nQjzbnL/EqWUoZSKys7wvjRnpdQNSqmiyM96o1Lqa9GYc1+bd+S+yyP/jrcqpf7dn+erlHqoyc94l1KqvLvn24fnnhG59obIdc4bIPMeqvTfp01KqeVKqbR+Mt9fKqVylFLVxxy3K6VeiHwf1iilMrt7vk2eqy/NO3pxmGEYA/YDSAVOjnzuBXYB44EHgB9Ejv8A+G3kczcwD7gd+HOT6yQAB4CkyNdPAwtbeM61wCmAAt4Gzo0czwQmA88ASwbQvE8HXJHPvw680M/m52tyzkXAsoHwc20yhk+A1cD0/j5n4Iam14zmRx+b9yhgAxAX+Tq5P8/3mHPuAJ4cQD/rx4CvRz4fD2QPkHm/BFwf+fwM4F/9ZL6zI89bfczxbwB/i3x+Jd3wd/kEmXcmUYrDBvTKtmEYeYZhrI98XgVsB4YAF6N/UERuL4mc4zcMYyVQd8ylhgO7DMMoinz9PnDZsc+nlEpFB1+rDP2TfabJtbMNw9gEhLtxis3qY/P+yDCMmsipq4Eurxj0sflVNjnVDURtk0RfmjZ2PWcAAAZXSURBVHfEz9H/YR57/W7TB+fcI/rYvG8BHjUMoyzyXIXdM8sj+th8m7oKeL4rc2tLH5u7Afgin8cAuV2fYfP62LzHAx9EPv8oMoZu1dPzjVxjtWEYec3c1fQ5lwILG1f5u1tfmnc047ABHWw3FXmb5CRgDZDS+IOI3Ca38fA9wFilVKZSyoL+pUhv5rwhwMEmXx+MHOs1fWzeN6NXE7pNX5ifUuqbSqm96MDz252bScf09ryVUicB6YZhvNmFaXRIb8854rLI25hLlVLNPb7b9YF5jwZGK6U+VUqtVkqd09m5tEcfmG/jOIYCw4APOz6LzukDc78fuEYpdRD4H3plP+r6wLy/5EjgthjwKqUSOj6T9umh+bZmCJATec4gUIFeOY6qPjDvqJFgG1BKeYCXgTuPWYlsl8iKzteBF4AVQDYQbO6pmnt4R5+vu/SleSulrgGmA7/r6Dha0lfmZxjGo4ZhjADuAe7r6Dg6qrfnrZQyAQ8B3+voc3dWb885cvsGkGkYxmT0qsrTzZzbrfrIvC3oVJIF6JXex5VSsR0dS3v0kfk2uhJYahhGqKPj6Iw+MvergKcMw0gDzgP+Ffn3HjV9ZN7fB+YrpTYA84FDLVyjy3pwvq0Oo7lLd3QsHXrCvjHvqBnwwbZSyor+AT9nGMZ/I4cLIm8pNb611ObbooZhvGEYxizDME4BdgK7lVJmdWQjzc/Qr5SbpkmkEcW34VrTl+atlFoE/Ai4yDCM+v42vyb+Q5RTDvrIvL3ARGC5UiobnR/3uoreJsm+MGcMwyhp8vv7D2Bad8yvJX1l3pH7XjMMo8EwjKzINUZ1xxyb6kPzbXQlUU4hadSH5n4z8GLkWqsAB5DY9Rk2r6/M2zCMXMMwLjUM4yT03yoMw6jopmke1sPzbc1BIqvCkVXiGKC0c7NqWx+ad9QM6GBbKaWAJ4DthmE82OSu14HrI59fD7zWjmslR27j0JsLHjcMI2QYxtTIx/9F3gqpUkrNjjz3de25dnfrS/NWOt3g7+hAu1tyPfvY/JoGHecDu7s4vdbG2ifmbRhGhWEYiYZhZBqGkYnOxb/IMIx13TXXJuPsE3OOPC61yeUuQuceRkVfmjfwKnqjM0qpRHRayb4uT/LoMfal+aKUGgPEAau6YXptjbcvzf0AsDByjXHoYLvouCfqBn1p3kqpRHVkBf9e4MlumOKxY+zR+bZxiabPuQT40DCMqKxs97F5R48RpR2mJ8IHekerAWwCNkY+zkPnJn2ADow+AOKbPCYb/QqvGv3qb3zk+PPAtsjHla0853RgC7AX+DMcbiw0I3I9P1ACbB0g834fKGgyjtf72fweBrZGxvARMGEg/FyPOWc50atG0mfmDPw68rP+MvKzHjsQftbot5wfjDx+c2vX6A/zjdx3P/CbaP18++rc0RsFP438jm8Ezhog814Seb5dwOOAvZ/M94HI48KR2/sjxx3oCix70BVahvezn3NL845aHCYdJIUQQgghhIiSAZ1GIoQQQgghRDRJsC2EEEIIIUSUSLAthBBCCCFElEiwLYQQQgghRJRIsC2EEEIIIUSUSLAthBADjFLqfqXU91u5/xKl1PieHJMQQvRXEmwLIYQ41iXomspCCCG6SOpsCyHEAKCU+hG6K14OuuvfF0AFcCtgQzewuBaYCrwZua8CuCxyiUeBJKAGuMUwjB09OX4hhDhRSbAthBD9nFJqGvAUMAuwAOuBvwH/NAyjJHLOL4ACwzAeUUo9BbxpGMbSyH0fALcbhrFbKTUL+LVhGGf0/EyEEOLEY+ntAQghhIi6U4FXDMOoAVBKvR45PjESZMcCHuCdYx+olPIAc4CXlFKNh+1RH7EQQvQTEmwLIcTA0NzbmE8BlxiG8aVS6gZgQTPnmIBywzCmRm9oQgjRf8kGSSGE6P8+ARYrpZxKKS9wYeS4F8hTSlmBrzY5vypyH4ZhVAJZSqmvAChtSs8NXQghTmySsy2EEANAkw2S+4GDwDbAD/+/XTskQjAMoih6f4EkAFmoQQBSEIASCArQhzQYPgwVlhlmzpHPrbtiu3y3Z7Vfa523bTtW9+pVnap3dasO1a56rLWuPz8C4A+JbQAAGOKNBAAAhohtAAAYIrYBAGCI2AYAgCFiGwAAhohtAAAYIrYBAGCI2AYAgCEfmRSShkFVA6EAAAAASUVORK5CYII=\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "result['Total'] = result.sum(axis=1)\n", "result.plot(figsize=(12, 8), title='PnL Drivers')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we can see in the chart above, over the backtest period rates drive most of the positive swap performance while cross currency effects are largely a negative drag over the period. FX, although a small positive contributor through mid 2019, ultimately drives much of the negative contribution through the remainder of the year. Remember we are looking at the swap hedge only here so FX will be entirely offset by the bond in the bond+swap package.\n", "\n", "In this note we decomposed the fixfix swap but you can use this framework to analyse any trade or portfolio - looking forward to hearing your feedback!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### What's New\n", "* `PnlExplain` which we covered in this note!\n", "* `Portfolio.from_frame` and `Portfolio.from_csv` to help map and represent your portfolio object from a dataframe or csv file.\n", "* `to_frame` to view complex results - see example [here](https://nbviewer.jupyter.org/github/goldmansachs/gs-quant/blob/master/gs_quant/examples/01_pricing_and_risk/00_rates/010014_spread_option_grid_pricing.ipynb)\n", "* Solvers for different fields to allow to solve for a strike or fixed rate such that PV=x. Examples to come - please reach out in the meantime." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/made_with_gs_quant/11-FX Election Hedge.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## FX Election Hedge\n", "### Summary \n", "\n", "With the US election less than 40 days away, election market dynamics are a major risk driver across asset classes. In this note, we look for interesting FX proxies for different election outcomes and find that AUDJPY has a strong relationship to and a higher beta to SPX in downside scenarios than in upside ones, making it a potentially attractive downside hedge. We use `gs-quant` to examine the relationship, price several structures and analyze impact of different election outcomes on these trades.\n", "\n", "The content of this notebook is split into:\n", "* [1 - Let's get started with gs quant](#1---Let's-get-started-with-gs-quant)\n", "* [2 - State of FX market](#2---State-of-FX-market)\n", "* [3 - Downside sensitivity to SPX](#3---Downside-sensitivity-to-SPX)\n", "* [4 - Structures](#4---Structures)\n", "* [5 - Pricing election outcomes](#5---Pricing-election-outcomes)\n", "* [What's New](#What's-New)\n", "\n", "P.S. In case you missed it, [check out the replay of our last webinar](https://developer.gs.com/docs/gsquant/videos/gs-quant-meets-markets/) where our traders and strats use code as content to comment on macro trends, in particular around US elections." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1 - Let's get started with gs quant\n", "Start every session with authenticating with your unique client id and secret. If you don't have a registered app, create one [here](https://marquee.gs.com/s/developer/myapps/register). `run_analytics` scope is required for the functionality covered in this example. Below produced using gs-quant version 0.8.199." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics', 'read_product_data')) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2 - State of FX market\n", "\n", "Let's focus on G10 and grab spot and vol data from [gs data catalog](https://marquee.gs.com/s/discover/data-services/catalog). To start, let's see how things have evolved over the last few months - we'll use the longer history in latter parts of the analysis." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.data import Dataset\n", "import pandas as pd\n", "import datetime\n", "\n", "start_date = datetime.date(2005, 8, 26)\n", "end_date = datetime.date.today()\n", "\n", "fx_pairs = ['USDJPY', 'EURUSD', 'AUDUSD', 'AUDJPY', 'USDNOK', 'GBPUSD','USDCAD', 'NZDUSD', 'USDSEK', 'USDCHF']\n", "\n", "fx_spot = Dataset('FXSPOT_PREMIUM').get_data(start_date, end_date, bbid=fx_pairs, location='NYC')\n", "fx_spot = pd.pivot_table(fx_spot, values='spot', index=['date'], columns=['bbid'])\n", "\n", "fx_vol = Dataset('FXIMPLIEDVOL_PREMIUM').get_data(start_date, end_date, bbid=fx_pairs, tenor='3m', deltaStrike='DN', location='NYC')\n", "fx_vol = pd.pivot_table(fx_vol, values='impliedVolatility', index=['date'], columns=['bbid'])\n", "\n", "spx_spot = Dataset('TREOD').get_data(start_date, end_date, bbid='SPX')[['closePrice']].rename(columns = {'closePrice':'SPX'})" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "last3m = fx_spot.tail(90).apply(lambda x: 1/x if x.name[:3]=='USD' else x)\n", "ax = (last3m / last3m.iloc[0]).plot(figsize=(12, 8), title='FX Moves last 3m (SPX on RHS, all crosses vs USD)')\n", "spx_spot.tail(90).plot(secondary_y=True, ax=ax)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fx_vol.tail(90).plot(figsize=(12, 8), title='FX Vol last 3m')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "USD has weakened relative to the major currencies over the first 2 months of the period but strengthened most recently. Looking to the vol space, 3m vols peaked first half of June, normalizing slightly over next few months but have started to climb again most recently. Let's see their z scores to see how many standard deviations they are away from the mean on a 1, 3, 6, 9 month basis." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.timeseries import beta, returns, diff, zscores\n", "import matplotlib.pyplot as plt\n", "import seaborn as sns\n", "import warnings\n", "warnings.filterwarnings('ignore')\n", "\n", "zs = lambda p, d: zscores(d.tail(p)).tail(1).values[0]\n", "fx_vol_zs = {cross: {'1m': zs(21, data), '3m': zs(63, data), '6m': zs(126, data), \n", " '9m': zs(189, data)} for cross, data in fx_vol.iteritems()}\n", "\n", "def plot_crosses(data, xlabel, title):\n", " plt.subplots(figsize=(10, 6))\n", " center = max(data.max().max(), abs(data.min().min())) \n", " ax = sns.heatmap(data, annot=True, vmin=-center, vmax=center, fmt='.2f', cmap='coolwarm_r', )\n", " ax.set(ylabel='Crosses', xlabel=xlabel, title=title)\n", " ax.xaxis.tick_top()\n", " ax.xaxis.set_label_position('top')\n", "\n", "plot_crosses(pd.DataFrame(fx_vol_zs).T, xlabel='Lookback', title='FX Vol Z Scores',)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3 - Downside sensitivity to SPX\n", "\n", "Although many parts of the market recovered much of the lost ground since the start of the pandemic, as we can see above, a skittish environment remains. Given an abundance of potential downside catalysts and elections on the horizon, it's no surprise implied volatility in FX and especially in equity markets where investors are looking to downside protection, remains high. \n", "\n", "Let's look at the relationship of the crosses we examined so far to SPX returns especially in drawdown scenarios to see if we can find FX alternatives with a high SPX beta but potentially lower cost." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from collections import defaultdict\n", "\n", "def package_data(data, freq='W-FRI', abs_c=False, w=1):\n", " spt_data = pd.concat([data, spx_spot], axis=1)\n", " sampled = spt_data.resample(freq).last()\n", " return diff(sampled, w).dropna() if abs_c else returns(sampled, w).dropna()\n", " \n", "def perf_vs_spx(spx_returns, data, show_ret=False):\n", " avg_p = defaultdict(dict)\n", " for ret in spx_returns:\n", " dns = data[data.SPX <= ret].dropna() if ret < 0 else data[data.SPX >= ret].dropna() \n", " for cross in fx_pairs:\n", " avg_p[ret][cross] = dns[cross].mean() * 100\n", " if show_ret:\n", " avg_p[ret]['SPX'] = dns.SPX.mean() * 100\n", " print(f'# of observations for SPX {ret}: {len(dns)}')\n", " return pd.DataFrame(avg_p)\n", "\n", "spx_returns = [-.1, -.05, .05, .1] \n", "spot_perf = perf_vs_spx(spx_returns, package_data(fx_spot), show_ret=True)\n", "vol_perf = perf_vs_spx(spx_returns, package_data(fx_vol, abs_c=True)*100)\n", "\n", "plot_crosses(spot_perf, 'SPX Weekly Move', 'Avg weekly % return during large SPX moves')\n", "plot_crosses(vol_perf, 'SPX Weekly Move', 'Avg weekly vol change during large SPX moves')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we can see above AUDJPY and AUDUSD moved with the largest magnitude in the same direction with as SPX when SPX experienced large moves since the start date (2005 in this case). Instead of looking at returns, let's focus on one of the crosses - AUDJPY - and look at the conditional relationship through a different lens: the R-squared number on each chart will tell us the strength of the relationship with numbers closer to 0 indicating a lack of one." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from scipy import stats\n", "\n", "r2 = lambda x,y: stats.pearsonr(x,y)[0]**2 \n", "\n", "def SPX_conditional_downside(thresholds, data, asset):\n", " for ret in thresholds:\n", " dns = data[data.SPX <= ret].dropna() if ret < 0 else data[data.SPX >= ret].dropna() \n", " j = sns.jointplot(x='SPX', y=asset, data=dns, kind='reg')\n", " j.set_axis_labels('SPX with {}% Returns'.format(ret*100), asset)\n", " j.fig.subplots_adjust(wspace=.02)\n", " plt.show()\n", "\n", "spot_package = package_data(fx_spot)\n", "SPX_conditional_downside(spx_returns, spot_package, 'AUDJPY')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we can see from the charts above, there's a strong positive relationship between AUDJPY and SPX in large downside scenarios (SPX -5% and -10% on the week) but not on the upside (SPX +5%, +10%). This makes this cross an attractive candidate for an alternative to hedging with SPX directly." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4 - Structures\n", "\n", "Let's now look at a few AUDJPY structures as potential hedges:\n", "\n", "* Buy 2m AUDJPY put using spx beta to size. Max loss limited to premium paid.\n", "* Buy 2m AUDJPY put spread (5.4%/10% OTMS). Max loss limited to premium paid.\n", "\n", "For more info on these trades, reach out to receive the piece from our marketstrats team. Note all pricing is based on GS T-1 mid marks and does not include transaction costs.\n", "\n", "We'll start by looking at the cost of an 10% OTMS SPX put (max loss limited to premium paid)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import EqOption, FXOption\n", "\n", "spx = EqOption(option_type='Put', underlier='.SPX', exchange='NYSE', strike_price='90%', expiration_date='2m', buy_sell='Buy')\n", "spx.resolve()\n", "\n", "notional = spx.strike_price * spx.multiplier\n", "print('cost in bps: {:,.2f}'.format(-spx.price()/notional * 1e4))\n", "print('cost in USD: {:,.2f}'.format(-spx.price()))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, I will calculate the beta over the last 6m and use that to size the FX hedge." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#buy 4m AUDJPY put\n", "b = beta(fx_spot.AUDJPY.resample('W-FRI').last(), spx_spot.SPX.resample('W-FRI').last(), 4 * 6).tail(1).values[0]\n", "beta_notional = notional * (1 / b)\n", "audjpy_put = FXOption(option_type='Put', pair='AUDJPY', strike_price= f's-{b * 10}%', notional_amount=beta_notional, expiration_date='2m', buy_sell='Buy')\n", "audjpy_put.resolve()\n", "print('cost in bps: {:,.2f}'.format(audjpy_put.premium / audjpy_put.notional_amount * 1e4))\n", "print('cost in USD: {:,.2f}'.format(audjpy_put.premium))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print('Discount to SPX: {:,.2f}%'.format((1 - audjpy_put.premium/-spx.price()) * 100))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can cheapen the structure further by structuring a put spread." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.portfolio import Portfolio\n", "\n", "put1 = FXOption(option_type='Put', pair='AUDJPY', strike_price= f's-{b * 10}%', expiration_date='2m', notional_amount=beta_notional, buy_sell='Buy')\n", "put2 = FXOption(option_type='Put', pair='AUDJPY', strike_price='s-10%', expiration_date='2m', notional_amount=beta_notional, buy_sell='Sell')\n", "print('cost in USD: {:,.2f}'.format(put1.premium + put2.premium))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print('Discount to put: {:,.2f}%'.format(100 - (put1.premium + put2.premium)/audjpy_put.premium * 100))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 5 - Pricing election outcomes\n", "\n", "Now, let's take this a step further and examine how different potential election outcomes might impact our put spread structure. First, we'll look at FXDelta to see which market assets are driving risk. Then, we'll roll our structure up to the election date and leverage work done by spot trading desk to shock the market assets we have risk to, examining PV impact of these rolling forward + shock scenarios for various election outcomes. Note, here I'm using spot shocks only but you can additionally add vol shocks (example in comment below) for a more representative scenario. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.risk import FXDelta\n", "\n", "fx_package = Portfolio((put1, put2))\n", "fx_package.resolve()\n", "fx_package.calc(FXDelta).aggregate()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.risk import RollFwd, MarketDataPattern, MarketDataShock, MarketDataShockBasedScenario, MarketDataShockType\n", "from gs_quant.markets import PricingContext\n", "\n", "scenarios = pd.DataFrame({\n", " 'AUDUSD': {'Rep sweep': 2.5, 'Trump + split congress': 2.0, 'Biden + split congress': -2.5, 'Dem sweep': -3.5, 'Still contested': -1.5},\n", " 'USDJPY': {'Rep sweep': 2.0, 'Trump + split congress': 1.0, 'Biden + split congress': -2.5, 'Dem sweep': -3.5, 'Still contested': -2.5}\n", "})\n", "\n", "rollfwd_scenario = RollFwd(date = datetime.date(2020, 11, 3))\n", "base_price = fx_package.price().aggregate()\n", "\n", "outcomes = {}\n", "with PricingContext(is_batch=True):\n", " for scenario, data in scenarios.iterrows():\n", " market_shock = MarketDataShockBasedScenario(shocks={ \n", " MarketDataPattern('FX', 'USD/AUD'): MarketDataShock(MarketDataShockType.Proportional, data.AUDUSD/100),\n", " MarketDataPattern('FX', 'JPY/USD'): MarketDataShock(MarketDataShockType.Proportional, data.USDJPY/100), \n", " # note - you can easily add additional shocks here - like vol \n", " # MarketDataPattern('FX Vol', 'JPY/AUD', 'ATM Vol'): MarketDataShock(MarketDataShockType.Absolute, x/100)\n", " })\n", " with rollfwd_scenario, market_shock:\n", " s_p = fx_package.price()\n", " outcomes[scenario] = s_p" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "final_scen = {s: s_p.aggregate() - base_price for s, s_p in outcomes.items()}\n", "pd.Series(final_scen).plot(kind='barh', figsize=(10, 6), title='PV impact of Rolling Forward and Election Market Data Scenarios on Structure')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### What's New\n", "* `FXOption` used in this notebook\n", "* Simplified backtesting - examples to come\n", "* `BacktoTheFuturePricingContext` pricing context - combining `HistoricalPricingContext` and `RollFwd`" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/made_with_gs_quant/12-Structuring for Uncertaintly.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Structuring for Uncertainty\n", "### Summary \n", "\n", "In this notebook, I team up with FX Structurer Lennart Scholte to analyze a strategy for hedging against FX moves when planning for uncertain investments denominated in a foreign currency. As an example, we consider a US company looking to invest into a European subsidiary: the deal may not be certain - as it is pending various approvals - but the company may be looking to lock-in the current market levels especially given the trend of USD depreciating against the EUR. We consider a net zero premium strategy of entering into an option that is restructured into a forward once the deal gets approved and explore the economics under various potential future spot and vol scenarios.\n", "\n", "The content of this notebook is split into:\n", "* [1 - Let's get started with gs quant](#1---Let's-get-started-with-gs-quant)\n", "* [2 - Spot and vol trends](#2---Spot-and-vol-trends)\n", "* [3 - Options](#3---Options)\n", "* [4 - Potential future scenarios](#4---Potential-future-scenarios)\n", "* [5 - Restructuring into a Forward](#5---Restructuring-into-a-Forward)\n", "* [What's New](#What's-New)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1 - Let's get started with gs quant\n", "Start every session with authenticating with your unique client id and secret. If you don't have a registered app, create one [here](https://marquee.gs.com/s/developer/myapps/register). `run_analytics` scope is required for the functionality covered in this example. Below produced using gs-quant version 0.8.207." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics', 'read_product_data')) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2 - Spot and vol trends\n", "\n", "Let's start by looking at spot and vol levels in the context of recent history. " ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "from dateutil.relativedelta import relativedelta\n", "from gs_quant.datetime import business_day_offset\n", "from gs_quant.data import Dataset\n", "from datetime import date\n", "\n", "date_end = business_day_offset(date.today(), -1, roll='forward')\n", "date_start = business_day_offset(date_end - relativedelta(years=2), -1, roll='forward')\n", "\n", "pair, term = 'EURUSD', '1y'\n", "fx_eurusd = Dataset('FXSPOT_STANDARD').get_data(date_start, date_end, name=pair, fields=('spot',))[['spot']].spot\n", "vol_eurusd = Dataset('FXIMPLIEDVOL_STANDARD').get_data(date_start, date_end, name=pair, tenor=term, deltaStrike='ATMS')[['impliedVolatility']].impliedVolatility * 100" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "import matplotlib.pyplot as plt\n", "fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(15, 5))\n", "\n", "fx_eurusd.plot(title='EURUSD Spot', ax=axes[0])\n", "vol_eurusd.plot(title='EURUSD 1y Implied Volatility', ax=axes[1])\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we can see in the left hand chart, USD has depreciated significantly against EUR year-to-date. Implied 1y volatility, on the other hand, has recently come down (although it's still rich compared to the beginning of the year). This means looking at options may be attractive to hedge against the dollar weakening further." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3 - Options\n", "\n", "Assuming the company has a budget of 1.2 USD/EUR, let's look at how much a 1.2 call costs and how it compares to the equivalent historically." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "spot vs budget: -1.49%\n" ] } ], "source": [ "from gs_quant.instrument import FXOption\n", "\n", "budget = 1.2\n", "call_option = FXOption(pair='EURUSD', expiration_date=term, option_type='Call', strike_price='s', \n", " premium_payment_date=term, premium_currency='USD').resolve(in_place=False)\n", "print('spot vs budget: {:,.2%}'.format((call_option.strike_price / budget) - 1))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we can see above, at current market levels, the 1.20 strike for a 1Y EURUSD Call option is ~1.5% out-of-the-money spot.\n", "\n", "Let's look at how the cost of trading a 1.5% OTMS spot call option compares to history. Note we are not calling `resolve()` on our `call_option` here which means it will re-strike to a new 1.5% OTMS option every day of the `HistoricalPricingContext`." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets import HistoricalPricingContext, PricingContext\n", "from gs_quant.risk import DollarPrice, FXSpot\n", "import pandas as pd\n", "\n", "call_option = FXOption(pair='EURUSD', expiration_date=term, option_type='Call', strike_price='1.015*S', \n", " notional_amount=100e6, premium_payment_date=term, premium_currency='USD')\n", "\n", "with HistoricalPricingContext(start=date_start, end=date_end, market_data_location='NYC'):\n", " inst = call_option.resolve(in_place=False)\n", " risks = call_option.calc((DollarPrice, FXSpot))\n", "\n", "call_info = pd.DataFrame({d: i.as_dict() for d, i in inst.result().items()}).T\n", "call_info['spot'] = risks[FXSpot]\n", "call_info['premium usd %'] = -call_info.premium/call_info.notional_amount_other_currency * 100" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(15, 5))\n", "call_info['premium usd %'].plot(title='Option Premium of 1.5% OTMS EURUSD 1Y Call (% of USD Notional)', ax=axes[0])\n", "call_info[['strike_price', 'spot']].plot(title='EURUSD Spot and Strike Price', ax=axes[1])\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we can see in the left chart, premium as a percentage of notional for a 1.5% OTMS call is relative lows compared to the last 2 years. \n", "\n", "Let's take this a step further and look at a quick grid across different strikes and tenors to better understand the sensitivity of the premium to different pricing parameters." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.portfolio import Portfolio\n", "import numpy as np\n", "\n", "tenors = ['6m', '9m', '12m', '15m']\n", "strikes = np.arange(1.17, 1.25, 0.01)\n", "\n", "fxos = Portfolio([Portfolio([FXOption(pair='EURUSD', expiration_date=t, option_type='Call', strike_price=k, \n", " notional_amount=100e6, premium_payment_date=t, premium_currency='USD') for t in tenors]) for k in strikes])\n", "fxos.resolve()\n", "call_grid = pd.DataFrame([[-i.premium / i.notional_amount_other_currency for i in p.priceables] for p in fxos.priceables], index=strikes, columns=tenors)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "import seaborn as sns\n", "fig, ax = plt.subplots(figsize=(15, 4))\n", "ax = sns.heatmap(call_grid.apply(pd.to_numeric).T, annot=True, fmt=',.2%', cmap='Blues')\n", "ax.set(ylabel='Tenor', xlabel='EURUSD Strike Prices', title='Premium of EURUSD Call Options (% of USD Notional)')\n", "ax.xaxis.tick_top()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4 - Potential future scenarios\n", "\n", "As the company gets certainty about moving forward with the project, it might look to restructure the call option above into a forward to secure a rate.\n", "\n", "To get a better sense of the economics of a possible restructuring, let's look at how the option carries and how the mark-to-market may vary with various possible levels of FX spot and implied vol.\n", "\n", "Let's start with the static decay of the option over time: we can use `CarryScenario` to roll forward in time assuming that all market parameters, including the FX spot, remain constant (`roll_to_fwds=False` means roll to spot)." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "from gs_quant.risk import CarryScenario\n", "\n", "# we set premium to 0 s.t. day-1 MtM is equal to the premium (as opposed to zero)\n", "call_option = FXOption(pair=pair, expiration_date=term, option_type='Call', strike_price=budget, \n", " notional_amount=100e6, premium_payment_date=term, premium_currency='USD', premium=0).resolve(in_place=False)\n", "\n", "future_dates = pd.bdate_range(date_end, call_option.expiration_date, freq='W').date.tolist()\n", "roll_to_spot = {}\n", "with PricingContext(is_batch=True, market_data_location='NYC'):\n", " for d in future_dates:\n", " with CarryScenario(date=d, roll_to_fwds=False):\n", " roll_to_spot[d] = call_option.dollar_price()\n", " \n", "static_roll = pd.Series({d: p.result() for d, p in roll_to_spot.items()})" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(15, 5))\n", "static_roll.plot(title='Call Option Price (in USDm) Forward in Time', ax=axes[0])\n", "axes[0].axhline(y=0, linestyle='-', lw=0.75, color='black')\n", "(static_roll/static_roll.iloc[0]).plot(title='As % of Initial Premium Paid', ax=axes[1])\n", "axes[1].axhline(y=0, linestyle='-', lw=0.75, color='black')\n", "axes[1].axhline(y=.5, linestyle='--', lw=0.75, color='red')\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we can see above, in about 7m from now, the time value of the option is still about 50% of the deferred premium payable, which can therefore be recouped when restructuring it into a forward.\n", "\n", "Let's now see how the option may behave with different levels of spot and implied volatility over time by adding spot and vol shocks on top of the above `CarryScenario`." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "tenors = ['1b', '3m', '6m', '9m', '1y']\n", "vol_bumps = np.arange(-5, 7.5, 2.5) / 100\n", "spot_range = np.arange(1.10, 1.27, 0.01)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "from gs_quant.risk import MarketDataPattern, MarketDataShock, MarketDataShockBasedScenario,MarketDataShockType\n", "from collections import defaultdict\n", "call_res = defaultdict(lambda: defaultdict(dict))\n", "\n", "with PricingContext(market_data_location='NYC', is_batch=True):\n", " for t in tenors:\n", " for s in spot_range:\n", " for v in vol_bumps:\n", " scenario = MarketDataShockBasedScenario(\n", " shocks = {MarketDataPattern('FX', 'USD/EUR'): MarketDataShock(MarketDataShockType.Override, s),\n", " MarketDataPattern('FX Vol', mkt_asset='USD/EUR', mkt_class='ATM Vol'): MarketDataShock(MarketDataShockType.Absolute, v),\n", " })\n", " with CarryScenario(date=t, roll_to_fwds=False), scenario:\n", " price = call_option.dollar_price()\n", " call_res[t][s][v] = price" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkkAAAE/CAYAAABSE1d1AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8vihELAAAACXBIWXMAAAsTAAALEwEAmpwYAACKQ0lEQVR4nOzddXSUx9fA8e8Td1fiwQkSIFhxK0UKpe5eSp229K1Rt18dKDXaQl2pQIHSIqW4uwUJgYQkxN1W5v3jWWig0Aaym43czzl7SFZm7m422cvMnRlNKYUQQgghhDiVg70DEEIIIYRoiCRJEkIIIYQ4A0mShBBCCCHOQJIkIYQQQogzkCRJCCGEEOIMJEkSQgghhDgDSZKEqCNN03ZrmjboX27/TdO0m2rRTn9N05KtGZs4u5o/N003W9O0Ak3TNliuu0vTtOOappVqmhZoz1itpb7eY7bqR9O06zRN+8Pa7QpxNpIkCZvRNC1V07RqTdOCTrt+q6ZpStO0WMv3n2qa9uL5PPYMfSZomvaHpmn5mqYVapq2WdO0UdZ9ZqdSSiUopZZb+n9W07QvT7t9pFLqs1q0s1Ip1fbE95bXYNj5xKRpWqzldXI6n8db2nhC07TDliQhXdO07863rdPaPe/nZXn8ck3Tbv+X208891LL5bimafM1TRte8341f25AP2A4EKmU6qlpmjPwFnChUspLKZV3vvGej/96jTRNG6Rpmtny/Eo0TUvWNO2W/2r39PfYf8QwSNO09HOJ+3z6Oa3PJ2r83Co1TTPV+H63UuorpdSF5xOTEOdDkiRha4eBa058o2laJ8DDho/9FVgMhAEhwP1A8TnEKwDLyNcNwDCllBeQBCy1b1TnzM8Sexf098TPmqbdfJb7xgCpSqkyy/ehgBuw+3w61jTN8Xwed44yLM/PB3gU+EjTtA710K/NKKVetiSlXsBEYO2J75VSCfaOTzRDSim5yMUmFyAVmAJsrHHdG8CTgAJigQmAAagGSoFfa/vYM/QXZLnN719iGgNsAwqBNUDn0+KdDOwAioDvALcabc+3PC4fWAk41HjcMOAiy/MwWJ7Ldsvty4HbAVfL4zvW6DMYqEBP6AYB6ZbrvwDMlttKgf8DFgD3nfZ8dgDjz/A8j1pei1LLpQ/6f4qmAEeAbOBzwPcsr9MMYOq/vI7LgVeADehJ6FwgoMbtY9ETjELLfduf7XmdoW1/y2udAxRYvo603PYSYAIqLY+fcYbHx1qeu9Np108Gjp/h53abpT2Tpc1vgLIar98yy/3boSdb+UAycGWNtj8F3gcWWh47DGgB/Gh5HoeB+2vc/1nge8vPoMTyWiWdw2t08r1S47oc4HL099lUIMNymQq4nulxnOU9D3ha+jfz93uoBdAT2GT5mR8H3jrL+6NW/fzH34+bgVX/dp3lZ3Q3cMDyOr4AtET/3S62vMYutfn9l4tcznSxewByabqXGh9CyUB7wBFIR/9f+8lEx/IB8+L5PPa0x2iWP5bzgUuA0NNu74qeHPSytHeTpR/XGn1usHwYBAB7gYmW214BPgCcLZf+gFYzVsvXzwJfntbvcuB2y9ezgJdq3HYPsMjy9Zk+WIbV+P5KYH2N77sAeTU/BGrcFstpiQJwK3AQiAe8gJ+AL87ys7sePRl4BH0UyfEMz+kY0BH9A/XHE88baIOeKAy3vFb/Z+nX5UzP6wx9BwKXoY8aegM/AL+c6fU8y+P/8dwt18dbrm9/ehz888P3lDYszzENuAVwQn8v5QIdaryHi4C+6MmoB7AZeBpwsfSdAoyo8T6pBEahvxdfAdad7Wd/hud48r1i6W88enLeFngeWIeeeAejJwMv/Mt77Gzv+VPua7luLXCD5WsvoPd/xfdf/fzLczzlZ3KWn5NCT9B9gASgCn3EMx7wBfYAN9Xm918ucjnTxWbTbZqmzdI0LVvTtF21vP+VmqbtsRRTfm2ruIRdfAHciP6huRf9w9Xqj1VKKWAw+h++N4FMTdNWaJrW2nKXCcCHSqn1SimT0uuEqoDeNZqZrpTKUErlo0/dJVquNwDhQIxSyqD0movzOfjwa+DqGt9fa7muNuYBbWo8nxuA75RS1bV8/HXo//NPUUqVAo8DV5+pbkkp9SVwHzAC+AvI1jTt0dPu9oVSapfSp6ieAq60TDNdBSxQSi1WShnQRwDdgQtqE6RSKk8p9aNSqlwpVYI+ejSwls/x32RY/g04j8eOQZ+Om62UMiqltqInhlfUuM9cpdRqpZQZ6AQEK6WeV0pVK6VSgI849We/Sim1UCllQn+fdznHmFpomlaInqw9g568JKP/nJ9XSmUrpXKA59DfK2dztvf8mRiAVpqmBSmlSpVS684h3nPp51y8ppQqVkrtBnYBf1je40XAb+jJEdTu91+IU9iyJulT9OmH/2T5o/840Ffp886TbBeWsIMv0JOBm9GnF2z2WKVUulLqXqVUS/RRp7Iaj4sBHrYUdBdaPmCi0P93e0JWja/L0f+3DPA6+mjIH5qmpWia9tg5Po8T/gQ8NE3rZSk+TwR+rs0DlVKV6NMU12ua5oBer/XFOfTdAn2q7YQj6KMioWfp7yul1DDAD70+5AVN00bUuEvaaW05o09LntKPJWlIAyJqE6SmaR6apn2oadoRTdOKgRWAnxXqfE70n38ej40Bep323rkOvfbthLTT7t/itPs/wamv9envNbdzLLTPUEr5KaUClFKJSqlvLdef6efc4p8PP2scXme7I/rUZBtgn6ZpGzVNG3MO8Z5LP+fieI2vK87w/Yl+avP7L8QpbJYkKaVWcNofI03TWmqatsiy4milpmntLDfdAbyrlCqwPDbbVnGJ+qeUOoJekzEKfYrnH3epw2P/rd804F30KSHQP8ResnywnLh4KKW+qUVbJUqph5VS8ej1Ng9pmjb0XJ6LpR0Tep3ENZbLfMtoyRnvfobrPkP/cB4KlCul1p7DYzPQPyhOiAaMnPqhcqaYDUqpH9DrSTrWuCnqtLYM6KMap/SjaZpmue+JUcD/GoF7GH3aqJdSygcYcKKpWj7+bMajT7ecz9L0NOCv0947Xkqpu2rcR512/8On3d9bKVXblZbn+xzhzD/njLPc95xiUEodUEpdgz6V9yowR9M0z/OKsv6d9++/aL7qe3XbTPTC0+7oRXzvWa5vgz6NsFrTtHWaptVqBEo0KrcBQ9Tfq4dqOo5eQ3A+jz1J0zR/TdOe0zStlaZpDpbtA25Fr88AfbpjomUUR9M0zVPTtNGapnn/V/Capo2xtKuh156Y0Itaz/RcYi0jPWfzNfqU1HX8+1TbP14XS1JkRp9O/LdRpBzL/Wo+/hvgQU3T4jRN8wJeRp+uM57+YE3Tbj7x2lhey5HoNR/ra9ztek3TOmia5oFeBzOnRhI4WtO0oZal9A+jT2usOdvzOo03+ghAoaZpAehTSTX91+NPfy6hmqbda2nnccvI1rmaj/436gZN05wtlx6aprU/y/03ACWapj2qaZq7pmmOmqZ11DStRy37O6fneJpvgCmapgVbfgeeBr78j8ecLYZATdN8T1yhadr1mqYFW17DQsvV5/N62sN5//6L5qvekiTLH+ULgB80TdsGfIhe4wH6kH9r9GK/a9CXsvrVV2zC9pRSh5RSm85y8ydAB8sQ+C/n+NiaqtELbpegr2zZhf7hfLOlnU3oo5Yz0FdNHTxxWy20trRbil68+p5S6s8z3O8Hy795mqZtOVNDSqn16NOALdBrJs7mFfQPu0JN0ybXuP5z9JqXs37wKaXK0Wt5Vlse3xu9aPwL9Omrw+iFw/edpYli9Omho+gfhq8BdymlVtW4zxfo0+pZ6Cui7rf0nYxe+P0O+sjSxcDFNWqnzva8TpiKXsOUi57gLjrt9mnA5Zq+8eP0s70G6ElWGbATfSTyCqXUrH+5/1lZRvsuRK8pykB/zq+iryQ70/1N6HVMieivdS7wMXoxcW3812v0b15EX4G2A/25b7Fcd06UUvvQE64USxwt0EsodmuaVor+c7haKVVxrm3bQx1//0UzdWJ1jm0a12su5iulOmqa5gMkK6XCz3C/D9BX7cy2fL8UeEwptdFmwQnRSGmadiMwQSnVz44xLEdfzfaxvWIQQghbq7eRJKVUMXBY07Qr4OQxACdWc/yCPoqEZXi4DfpyWSFEDZaprbvRp66FEELYkC23APgGfVqiraYfaXAbeg3GbZqmbUffPG2c5e6/o09P7EFf/fOIqudjAIRo6DR9ZVkOeq2IbJMhhBA2ZtPpNiGEEEKIxkrObhNCCCGEOANJkoQQQgghzuBcdnettaCgIBUbG2uLpoUQQgghrGrz5s25Sqng06+3SZIUGxvLpk212dZGCCGEEMK+NE07cqbrZbpNCCGEEOIMJEkSQgghhDgDSZKEEEIIIc7AJjVJZ2IwGEhPT6eysrK+urQZNzc3IiMjcXZ2tncoQgghhLCRekuS0tPT8fb2JjY2Fv0g9cZJKUVeXh7p6enExcXZOxwhhBBC2Ei9TbdVVlYSGBjYqBMkAE3TCAwMbBIjYkIIIYQ4u3qtSWrsCdIJTeV5CCGEEOLsmlXh9q233kpISAgdO3Y8ed2gQYNkTychhBBC/EOzSpJuvvlmFi1aZO8whBBCCNEINKskacCAAQQEBPzj+i+++ILExEQ6duzIhg0b7BCZEEIIIWpKLyjn+41pdo2hVqvbNE17ELgdUMBO4BalVJOpXC4vL2fbtm2sWLGCW2+9lV27dtk7JCGEEKLZWnUgl/u+2YJZwfAOofh7utgljv9MkjRNiwDuBzoopSo0TfseuBr49Hw7fe7X3ezJKD7fh59RhxY+PHNxwnk99pprrgH0kabi4mIKCwvx8/OzYnRCCCGE+C9KKWauSOHVRftoFeLFhzck2S1Bgtrvk+QEuGuaZgA8gAzbhVT/Tl+tJqvXhBBCiPpVVmXk/+bsYMHOTEZ3Cue1yzvj6Vpv2zme0X/2rpQ6pmnaG8BRoAL4Qyn1R106Pd8RH1v57rvvGDx4MKtWrcLX1xdfX197hySEEEI0G4dzy7jzi00czC7l8ZHtmDAgvkEMWNRmus0fGAfEAYXAD5qmXa+U+vK0+00AJgBER0dbP1IruOaaa1i+fDm5ublERkby3HPPAfoxI127dsVgMDBr1iw7RymEEEI0H0v3HmfSd9twctD4/NZe9GsdZO+QTqrNONYw4LBSKgdA07SfgAuAU5IkpdRMYCZAUlKSsnKcVvHNN9/847rbbrvNDpEIIYQQzZvZrJi+7ABTlxwgoYUPH1zfnagAD3uHdYraJElHgd6apnmgT7cNBWT3RSGEEEKcl+JKAw99t40le7O5tFsEL4/vhJuzo73D+ofa1CSt1zRtDrAFMAJbsYwYCSGEEEKci/3HS7jzi82k5Zfz3NgEbuwT0yDqj86kVmXjSqlngGdsHIsQQgghmrCFOzOZ/MN2PFyc+PqO3vSM++cGzw2JfdfWCSGEEKLJM5kVr/+ezAd/HaJrtB/vX9edMF83e4f1nyRJEkIIIYTNFJRVc983W1l1MJfrekXz9MUdcHVqePVHZyJJkhBCCCFsYtexIu78YjM5JVW8dllnruwRZe+QzkmzSZIqKysZMGAAVVVVGI1GLr/88pP7JAkhhBDCun7aks7jP+0kwNOFHyb2oUuUn71DOmfNJklydXVl2bJleHl5YTAY6NevHyNHjqR37972Dk0IIYRoMgwmMy8t2Muna1LpHR/AjGu7EeTlau+wzouDvQOoL5qm4eXlBYDBYMBgMKBpGrGxsTz++OMkJiaSlJTEli1bGDFiBC1btuSDDz6wc9RCCCFE45FdUsl1H63n0zWp3NYvji9v69VoEyRoRkkSgMlkIjExkZCQEIYPH06vXr0A/RiVbdu20b9/f26++WbmzJnDunXreOYZ2fVACCGEqI0tRwu4+J1V7DhWyLSrE3lqTAecHOuQZphNkHvQegGeB/tMt/32GGTttG6bYZ1g5P/+9S6Ojo5s27aNwsJCxo8fz65duwAYO3YsAJ06daK0tBRvb2+8vb1xdXWlsLAQPz8/68YqhBBCNCFfrz/KM/N2Eebrxk939aVDC5+6NVhRAHNug4ytcN9m8LDPfkrNpiapJj8/PwYPHsyiRYsAvV4JwMHB4eTXJ743Go12iVEIIYRo6CoNJp6dt5tvN6YxoE0w069OxM/DpW6NZu+Db6+BwjQY/YbdEiSwV5L0HyM+tpCTk4OzszN+fn5UVFSwePFiHn300XqPQwghhGgKMgoruOurLWxPK+Tewa14cHgbHB3qeLzIvgXw0wRw9oCb50O0fRdXNZuapMzMTAYPHkznzp3p0aMHw4cPZ8yYMfYOSwghhGh01qXkcfE7qziUXcoH13dn8oi2dUuQzGZY/ip8ey0EtYYJy+2eIAFoSimrN5qUlKQ2bdp0ynV79+6lffv2Vu/LXpra8xFCCCH+i1KKWatTeXnhXmICPZh5QxKtQrzq1mhVKfwyEfb+Cp2vhoungrO7VeKtLU3TNiulkk6/vlnWJAkhhBDi3FRUm3jspx3M3ZbBhR1CefPKLni7Odet0fwU+PY6yNkHI16B3neBVscpOyuSJEkIIYQQ/+poXjl3frmZfVnFTL6wDXcPaoVDXeuPDv0JP9ysf339T9BycJ3jtDZJkoQQQghxVn/tz+H+b7bqU20392Bw25C6NagUrHsP/pgCwe3g6q8gIN46wVqZJElCCCGE+AelFO8tP8QbfyTTNtSbD2/oTkygZ90aNVTAr5Ngx7fQ/mK45ANwrWNNkw1JkiSEEEKIUxSWVzP5h+0s2ZvN2C4t+N9lnfBwqWPKUHQMvrtO3yBy8JPQfzI4NOxF9pIkCSGEEOKkbWmF3PPVFrJLKnnm4g7cfEEsWl2LqY+uh++uB0M5XP01tBttnWBtrGGncFZWWFjI5ZdfTrt27Wjfvj1r1661d0hCCCFEg6CU4tPVh7nigzUA/DDxAm7pG1f3BGnzp/DpaH1a7fYljSZBgmY2kvTAAw9w0UUXMWfOHKqrqykvL7d3SEIIIYTdlVQaeOzHnSzYmcnQdiG8eWWXuh8vYjLAosdg48fQcghcPgvc/a0TcD1pNiNJRUVFrFixgttuuw0AFxcX/Pz8GDRoEA8++CBJSUm0b9+ejRs3cumll9K6dWumTJli56iFEEII29qTUczF76xi0e4sHh/Zjo9uTKp7glSaA5+P0xOkC+6H6+Y0ugQJmtFI0uHDhwkODuaWW25h+/btdO/enWnTpgF6wrRp0yamTZvGuHHj2Lx5MwEBAbRs2ZIHH3yQwMBAO0cvhBBCWJdSiu82pvHMvN34eTjz7YTe9Ii1wmGymdv1DSLLcuDSj6HzFXVv007+M0nSNK0t8F2Nq+KBp5VSU8+301c3vMq+/H3n+/AzahfQjkd7nv3AWqPRyJYtW3jnnXfo1asXDzzwAP/7n37Q7tixYwHo1KkTCQkJhIeHAxAfH09aWpokSUIIIZqU8mojU37exU9bj9G/dRBvX5VIkJdr3RveOQfm3gsegXDrImjRte5t2tF/TrcppZKVUolKqUSgO1AO/GzrwKwtMjKSyMhIevXqBcDll1/Oli1bAHB11d8YDg4OJ78+8b3RaKz/YIUQQggbOZhdwrgZq/l52zEeHNaGT2/pWfcEyWyCxU/Dj7fpidGE5XVOkPIq8lh8ZHHd4qqjc51uGwocUkodqUun/zbiYythYWFERUWRnJxM27ZtWbp0KR06dGDjxo31HosQQghhD79sPcbjP+3E09WRL2/rRd9WQXVvtKIAfrwdDi6BpNvgov+BU91qmpLzk7lv2X0UVxfTI7QHfm5+dY/zPJxrknQ18I0tAqkP77zzDtdddx3V1dXEx8cze/Zsxo8fb++whBBCCJuqNJh47tc9fLPhKD3jAnjnmq6E+rjVveGcZPjmGig8CmOmQtItdW5yedpyHl3xKF7OXnxy4Sd2S5AANKVU7e6oaS5ABpCglDp+htsnABMAoqOjux85cupg0969e2nfvn2dA24omtrzEUII0TSl5pZx91db2JNZzN2DWvLQ8DY4OVphcXvyb/DjHeDsBld+ATF96tScUorPdn/GW5vfon1ge6YPnk6oZ2jd46wFTdM2K6WSTr/+XEaSRgJbzpQgASilZgIzAZKSkmqXeQkhhBDCZn7bmcn/zdmBg4PGrJuTGNLOCkmHUrDiDfjzJQjvoh9Q6xtZpyarTdW8sO4Ffjn4C8NjhvNSv5dwd3Kve6x1dC5J0jU04qk2IYQQormoNpp55be9zF6dSmKUHzOu7Uqkv0fdG64qhV/ugr3zoPNVcPE0cK5bMpNfmc+Dfz7IluwtTOwykbu63IWD1jC2caxVkqRpmicwHLjTtuEIIYQQoi7SC8q55+utbE8r5Na+cTw2sh0uTlZIOgpS4ZtrIWcvXPgi9LkX6nhkycGCg9y77F5yynN4tf+rjIofVfc4rahWSZJSqgyQzYKEEEKIBmzp3uM89P12zGbF+9d1Y2SncOs0nLIcfrhZn2q7/kf9mJE6Wpm+kkdWPIKboxuzL5pN5+DOdW7T2prNjttCCCFEU2U0mXnjj/188NchElr48N513YgJ9Kx7w0rB+g/g9ychqA1c8zUExNexScWXe7/kjU1v0NqvNTOGziDMM6zusdqAJElCCCFEI3a8uJL7vt7KhtR8ru0VzdNjOuDm7Fj3hg2VsOAh2PYVtBsD4z8AV++6NWky8NL6l/jxwI8MiRrCK/1fwcPZCrVSNtIwKqPqybRp0+jYsSMJCQlMnTrV3uEIIYQQdbLqQC6jpq1kV0YRU69K5OXxnayTIBWkwqwL9QRp0OP6Ev86JkiFlYXcueROfjzwI7d3up23B7/doBMkaEYjSbt27eKjjz5iw4YNuLi4cNFFFzFmzBhatWpl79CEEEKIc2IyK95ZdoBpSw/QOsSL967rRquQuiUxJ+3/HX66Q//6mu+g7UV1bjKlKIX7lt5HZlkmL/d7mYtbXlznNutDsxlJ2rt3L7169cLDwwMnJycGDhzITz/9xKBBg3jwwQdJSkqiffv2bNy4kUsvvZTWrVszZcoUe4cthBBCnCK3tIqbZm1g6pIDjO8awS/39LVOgmQ2wbIX4esrwS8GJvxllQRpTcYarl9wPaWGUmaNmNVoEiRoRiNJHTt25MknnyQvLw93d3cWLlxIUpK+uaaLiwubNm1i2rRpjBs3js2bNxMQEEDLli158MEHCQyUhX1CCCHsb8PhfO77ZguF5QZevawTVyZFodVxGT4AZbn64bQpy6HrDTDq9TrvfwTwzb5veHXDq8T7xTNjyAxaeLWoe6z1yC5JUtbLL1O1d59V23Rt346wJ5446+3t27fn0Ucf5cILL8TT05PExEQcHfV527FjxwLQqVMnEhISCA/Xl0zGx8eTlpYmSZIQQgi7MpsVM1em8PrvyUQHeDD75p50aOFjncbTNsIPN+mJ0th3oNuNdW7SaDbyvw3/47vk7xgYOZBXB7yKp7MVVtvVs2Yz3QZw2223sXnzZlasWIG/vz9t2rQBwNXVFQAHB4eTX5/43mg02iVWIYQQAqCwvJo7Pt/E/37bx0UJYcy7t691EiSlYMNHMHskODjBbX9YJUEqqiririV38V3yd9yScAvTBk9rlAkS2Gkk6d9GfGwpOzubkJAQjh49yk8//cS6dev4/vvv7RKLEEII8V+2pRVyz1dbyC6p5LmxCdzYJ8Y602vVZfDrJNj5PbQeAZd+CO7+dW72SPER7l16L+ml6Tx/wfOMbz2+7rHaUbOpSQK47LLLyMvLw9nZmXfffRc/Pz97hySEEEL8w4nptTd+TybUx405Ey+gS5SfdRrPPQDf3QC5yTDkKej3EDjUfWJpfeZ6Hlr+EA6aAx8N/4iksKQ6tWcuL6cyORmPrl3rHNv50pRSVm80KSlJbdq06ZTr9u7dS/v27a3el700tecjhBCiYcgqquSh77ex5lAeozqF8cr4zvh6OFun8T1z4Zd7wMkFLvsEWg62SrPfJ3/PK+tfIcYnhneGvkOUd1Sd2jPm5ZF2191UHzpEyyWLcfKv+yjXv9E0bbNS6h9ZXbMaSRJCCCEasj92Z/HojzuoNJitu3rNZIAlz8LaGRDZA674FHwj69ys0WzkzU1v8uXeL+kb0ZfXB7yOt0vdtiOoTk3l6IQ7MWZnE/HmGzZPkP6NJElCCCGEnVVUm3hxwR6+Wn+UjhE+TLu6Ky2DvazTeEkW/HALHF0DPe+EC1/UR5Lq2mx1CY+seITVx1ZzffvreTjpYZwc6pZWVGzbRtpddwMQ8+ls3BMT6xxnXUiSJIQQQtjRnoxi7v92KwezS7lzQDwPX9gWFycrLT5PXaUnSNWlcOnH0PkKqzSbVpzGvcvu5WjxUZ7u8zRXtKl7uyVLl3Ls4ck4hYQQPfNDXGJj6x5oHUmSJIQQQtiBUorZq1P532/78PVw5ovbetK/dbC1Goc102HJcxAQBzfNgxDr1NFuytrEg8sfxKzMfDj8Q3qG96xzm/lffcXxl17GrWNHot5/D6cGsj+hJElCCCFEPcstrWLyD9tZnpzD0HYhvHZ5ZwK9XP/7gbVRWQS/3A375kOHcTB2BrhZZ+PJnw/8zPPrnifSK5IZQ2cQ4xNTp/aU2UzO22+T99HHeA0eTMSbb+Dg0XAOvZUkSQghhKhHy5OzmfzDdkoqjTw/LoEbeltp7yOA47v15f0FqTDiZeh9N1ihbZPZxNQtU/l096f0Ce/DG4PewMelbomXubqazCeepHj+fPyuuZqwJ59Ec2pYaUnDisbGbr31VubPn09ISAi7du2ydzhCCCGakSqjiVd/S2bW6sO0DfXmq9t70zbMCgfTnrD9O/j1AXDzhZvnQ8wFVmm2zFDGoyse5a/0v7i67dU82vPROhdom4qLSb/vfsrXryf4oYcIvON26yWKVtSskqSbb76Ze++9lxtvrPu260IIIURtHcwu4b5vtrE3s5ibL4jlsZHtcHN2tE7jxipY9Dhs+gRi+sHls8A71CpNHys9xr1L7+Vw0WGe6PUE17S7ps5tGjIzSZtwJ1WpqbR47VV8LeenNkTN6uy2AQMGEBAQcPL7Q4cO0a1bt5PfHzhw4JTvhRBCiLpQSvHV+iOMeWcVx4sr+eSmJJ4dm2C9BKkwDWZdpCdIF9wPN861WoK0KWsT1y64luNlx3lv2HtWSZAqk5NJvfoaDJmZRM/8sEEnSNDMRpJO17JlS3x9fdm2bRuJiYnMnj2bW265xd5hCSGEaAIKyqp57Kcd/L77OP1bB/HmFV0I8XGzXgcHl8CPd+gbRV75BXSwTsKhlOKbfd/w+sbXifCOYPqQ6cT7xte53bK1a0m/734cPD2J+epL3Nq2tUK0tlWrJEnTND/gY6AjoIBblVJrz7fTld/vJzet9HwffkZBUV70v7LNOT/u9ttvZ/bs2bz11lt89913bNiwwapxCSGEaH7WHMzloe+3k1dWxZOj2nNbvzgcHKxUc2M2w4rXYfkr+rL+K7+AoFZWabrKVMULa19g7qG5DIwcyCv9X6nzDtoARXPnkjHlKVxjY4n6aCbOYWFWiNb2ajuSNA1YpJS6XNM0F6DhrM+ro8suu4znnnuOIUOG0L17dwIbyN4MQgghGh+Dycxbi/fzwV+HiAvy5OOb+tIxwtd6HZTnw08T4OBi6HwVjHkbXDyt0nRWWRaT/pzE7rzdTOwykbu63IWDVreqHKUUeTM/Iuftt/Ho1YvId6bj6GOd7Qjqw38mSZqm+QIDgJsBlFLVQHVdOj2fER9bcXNzY8SIEdx111188skn9g5HCCFEI5WaW8YD325le3oR1/SM4qkxHfBwsWJVy7Et8P1NUJoFo9+CpFutsrwfYGPWRib/NZkqUxVTB09laPTQOrepjEayXnyRwm+/w+fii2nx0otoLud2HIpSyq6r3mqTIsYBOcBsTdO2apr2saZp1klb69k111xDnz59SE5OJjIy8mRSdN111+Hg4MCFF15o5wiFEEI0Nkop5mxOZ/T0laTmlfP+dd145dLO1kuQlIJNs2HWCEDBLYugx21WSZCUUny19ysm/DEBHxcfvh71tVUSJHN5Oen33U/ht98ReMcdtHj1f+ecIB3aks2imbswGc11jud81eYn6AR0A+5TSq3XNG0a8BjwVM07aZo2AZgAEB0dbe04reKbb7454/WrVq3illtuwdHRSqsNhBBCNAtFFQam/LKLX7dn0CsugLevSqSFn7v1Oqguh4WTYdtX0HKIfv6ap3XKQqpMVTy/9nnmHZrHoMhBvNz/ZavUHxnz8ki7624qd+0i9OmnCLj22nNuI3l9Fks/3UNonA8mgxlHa51ld45qkySlA+lKqfWW7+egJ0mnUErNBGYCJCUlKatFaGPjx4/n0KFDLFu2zN6hCCGEaEQ2pebzwLfbyCquZPKFbbhrUCscrVWcDZCzH+bcAsd3wcBH9YuDdf4zX7P+6K4udzGxy8Q61x8BVKemcnTCnRizs4l8ZzreQ899VGr3ymMs/zqZiDZ+jLqrMy5u9luI/589K6WyNE1L0zStrVIqGRgK7LF9aPXj559/tncIQgghGhGjycyMPw8yfekBIv09+GFiH7pF+1uvA6Vg65fw2/+BsztcNwdaD7da8zXrj6YPns7g6MFWabdi2zbS7robgJjPPsW9S5dzbmP70jRW/XCA6IRARt7ZEScX+87w1DY9uw/4yrKyLQWQzYSEEEI0O+kF5Uz6dhubjhRwadcInhuXgLebs/U6qCyC+Q/Crh8htj9c+hH4hFulaaUUX+/7mtc3vk6UdxTThkyzyv5HACVLl3Ls4ck4hYQQ/dFMXGLO/eDbTb+lsn5uCvGJwVx4WwKOzvbf77pWSZJSahuQVNfO7F2lbi1KNZrZRCGEEFby6/YMnvh5J0rB1KsSuaRrhHU7SN+sT68VpcOQKdDvIatNr1UaK3lh3Qt6/VHUIF7uZ536I4D8r77i+Esv49axI1EfvI9TjZMtakMpxfp5KWz+7Qite4Qy7Ob2ODjaP0GCetxx283Njby8PAIDAxt1oqSUIi8vDzc3K+6aKoQQosEqrTLy7LzdzNmcTtdoP6Zd1ZXoQCtuF2g2w5rpsOwF8A6HW36D6F5Waz6zNJNJyyexJ28PdyfezZ2d77RK/ZEym8l5+23yPvoYr8GDiXjrTRzcz61oXSnF6h8Osn1ZGh36hjPwunbW23TTCuotSYqMjCQ9PZ2cnJz66tJm3NzciIyMtHcYQgghbGxbWiGTvt3K0fxy7h/SivuGtsbZmqMcJcfhl4lwaBm0Hwtjp4O79eqbTtQfVZuqeWfIOwyKGmSVds3V1WQ+8STF8+fjd83VhD35JJrTuaUUyqz465tkdq/MoNPgSPpf0RqtASVIUI9JkrOzM3FxcfXVnRBCCHHeqowmpi89wAd/pRDq7co3d/SmV7yVT2Q4uBR+vhOqSvSds7vfYrXNIWvWH0X7RDNt8DTifK3zGWwqLib9vvspX7+e4IceIvCO2895hshsMrPs830kr8+i24gYel8S3yBnmZr1AbdCCCHE6XYdK2LyD9vZl1XC5d0jeWpMB3zdrVicbazWp9bWTIfg9nDjPAjtYLXmK42VPL/2eX5N+ZXBUYN5ud/LeLl4WaVtQ2YmaRPupCo1lRavv4bvxRefcxsmo5nFs3ZzaEsOvcbG0X1kbINMkECSJCGEEALQz11798+DzFh2kABPFz65KYmh7UOt20n+YZhzK2Rs0UeORrwMLtarb8oszeSBPx9gb/5eq9YfAVQmJ5M24U7MZWVEfzQTz969z7kNo8HEopm7OLIzj76XtyJxWMPcfPoESZKEEEI0e/uyinn4++3szihmfNcInrm4A34e53aMxn/aOQd+nQQODnDl59BhnFWb35i1kYeXP4zBbGDGkBkMjBpotbbL1q4l/d77cPDyIuarr3Bre+5nsBqqTCx8fwfp+woYeE0bOg5s+LW9kiQJIYRotowmMx+uSGHqkv34ujvzwfXduahjmHU7qS6Dhf8H276EqF5w2cfgZ70RlBPnr72x6Q2r1x8BFM2dS8aTU3CNiyPqo5k4h53761NdYWT+u9vJOlTE0Jva066PdfZ+sjVJkoQQQjRLB46X8PAP29mRXsTozuG8MK4jAZ5WHj3K3KFPr+UdhP6TYdDj4Gi9j96a9UdDoobwUr+XrFZ/pJQib+ZH5Lz9Nh69ehE54x0cvc99b6XKMgO/Tt9Gblopw29LoHWSlacwbUiSJCGEEM2Kyaz4eGUKby7ej6eLIzOu7cqYzi2s24lSsGEm/DEF3APgxrkQb73pL4CM0gwm/TmJffn7uCfxHiZ0nmC1+iNVXU3m889TNOdHfC6+mBYvvYjmcu4JZHlxNfOmbaPgeBkXTexEXOcgq8RXXyRJEkII0Wyk5JQy+YftbDlayIiEUF68pBPB3q7W7aQ8H+beA8kLofUIuOQ98LRucrAhcwOT/5qMwWzgnSHvWLX+yFRURPr9D1C+fj2Bd00k+L770BzOPfkqLahi7tStlOZXMubuLkR1OLeduBsCSZKEEEI0eWazYvaaVF5btA83Z0emXZ3I2C4trL/0PHUV/HgHlOXAiFeg911W2/sI9CmwL/d+yZub3iTGJ4Zpg6cR6xtrtfarjxwh7c6JGI4do8Wr/8N33PkVlxfnVjB36lYqSgxcfH8XWrS24gHA9UiSJCGEEE3akbwyHvlhBxtS8xnaLoRXLu1EiI+Vj5YyGWHFa7DidfCPg9uXQItEq3ZRYazg+bXPMz9lPkOjh/JSv5fwdPa0WvvlGzeSfu99oGlEfzobj+7dz6udwuPlzJ26FUOViXGTuhIa52O1GOubJElCCCGaJLNZ8eX6I7yycB9OjhpvXNGFy7pFWH/0qChdHz06uga6XAOjXgdX6xwee0LN+qP7ut7H7Z1ut1r9EUDhz7+Q+fTTuERFEfXB+7hEn9/qu7xjpcydtg1lVox7sCvBUdZ9HeqbJElCCCGanLT8ch79cQdrDuUxoE0wr17WiXDfczt8tVb2ztfrj8xGGD8Tulxl9S7WZ65n8l+TMZlNzBg6gwGRA6zWtjKbyZk6jbyZM/Ho05vIadNw9Dm/kZ+coyXMm7YNByeNSx7uRkC49Ua57EWSJCGEEE2GUopvNqTx0oI9ALxyaSeu7hFl/dEjQyX88SRs/BjCE+HyWRDY0qpdKKX4fM/nvL35bWJ9Ypk2ZBoxPjFWa99cUUHGY49T8vvv+F15JWFPTUFzPr/jV7JSivj1ne24uDsyblJX/EKst4u4PUmSJIQQoknIKKzg0R93sPJALhe0DOS1yzsT6W+DD+ucZPjhFsjeDX3uhaHPgJN191cqri7m6dVPs/ToUoZFD+PFfi9atf7IkJ1N+j33UrlrFyGPPkrAzTeddyKZnlzAgvd24OnjwrgHu+IdYOV6LzuSJEkIIUSjppRizuZ0nv91D0az4oVxCVzXKwYHByuPHikFWz6H3x7Vz1u79gdoc6F1+wB25e5i8l+TOV52nEeSHuGGDjdYdSSsct8+0u66G1NREZHvzsB7yJDzbuvI7jx++2AnPkHujJuUiKevlbdTsDNJkoQQQjRax4sreeKnnSzdl03PuADeuLwL0YE2GD2qLNLPXdv9E8QN0OuPfKx7tIZSiq/3fc0bm94g2D2YT0d+SpfgLlbto+TPP8l4eDIO3t7EfvUlbu3bn3dbKVtz+P3jXQS08GTs/Ym4e1t5t/IGQJIkIYQQjY5SirnbMnhm3m6qjCaeHtOBmy+Itf7oEUDaRvjxVig6BkOfhr6TwMHRql2UVJfwzJpnWHxkMYMiB/FivxfxdfW1WvtKKQo+/5zjr76GW/v2RL73Hs6hIefd3v4NWSz5dC8hMd6MubcLbp7nV8vU0EmSJIQQolHJKaniyZ938see43SL9uONK7oQH2yd88pOYTLC6qmw/BXwbgG3LoKonlbvZnfebiYvn0xWWRaTkyZzY4cbrTq9pgwGsl56icJvv8N7+DBavPoqDh7nP9q2Z3UGf365jxat/Bh9T2dc3JpuKtF0n5kQQogmZ/6ODJ76ZRdl1SaeGNWO2/rF42iL0aPcA/DzRDi2CRLGw5ip4O5n1S6UUnyb/C2vb3ydQPdAZl80m8SQRKv2YSop4dikBylbvZrAO24n+MEHz+uIkRN2/JnGyu8OENUhgJETO+HsYt0RtYZGkiQhhBANXn5ZNU/9sosFOzPpEunLm1d2oVWIDTYqNJthw4ew5FlwcoPLPoGOl1n1aBHQp9eeXfMsfxz5g/4R/Xm538v4uflZtY/q9HTSJk6kOvUI4S+9iN9ll9WpvS2/H2Htz4eI6xLEiNs74uhsvc0sG6paJUmapqUCJYAJMCqlkmwZlBBCCHHCol1ZTPllJ0UVBh4Z0ZY7B8Tj5GiDD+iCVJh7L6Su1A+mHTsdvMOs3s2evD1M/msyGaUZPNT9IW5KuMmqu2cDlG/ZSvq996JMJqI/+QTPXuc/TaiUYuP8w2xckErrpBCG3tIBR1u8/g3QuYwkDVZK5dosEiGEEKKG48WVPPfrbhbuzCKhhQ9f3t6LdmE2OAdMKdjyGfz+JKDB2BnQ9Xqrjx4ppfg++Xte3fgq/m7+zL5oNl1Dulq1D4CiX+eT+eSTOIWHEfXBB7jGxZ13W0op1vx0iG2Lj9LugnAGX9/ONsXxDZRMtwkhhGhQTGbFl+uO8PrvyRhMZiZf2IY7B7bE2RajF8UZMO9+OLgYYvvDJe+B3/mdW/ZvSqtLeW7tcyxKXUTfiL680u8V/N38rdqHUorcGe+S++67ePToQcT0aTj5n38fyqxY8d1+dv11jE6DIul/ZWu0ZpQgQe2TJAX8oWmaAj5USs08/Q6apk0AJgBEn+fBeEIIIZq3XceKePLnnWxPL6J/6yBevKQjMYE2OANMKdj5AyycDMZqGPk69Lgd6lDUfDb78vcx+a/JpJek80C3B7i1461Wn14zV1WR+cSTFC9YgO/48YQ/9yyay/nvW2Q2K/78Yi/71mbRdXg0fS5taf2jXRqB2iZJ/ZRSxzRNCwEWa5q2Tym1ouYdLInTTICkpCRl5TiFEEI0YWVVRt5avJ/Zqw8T4OnKtKsTGdulhW0+mMtyYf6DsHceRPaE8R9Y/dw10Ed2ftj/A69ueBU/Vz8+GfEJ3UO7W70fY14e6ffcS8W2bQQ/9BCBd9xep9fNZDKzZPYeDm7KpufFcSSNim2WCRLUMklSSh2z/JutadrPQE9gxb8/SgghhPhvf+zO4pl5u8ksquTaXtE8OqIdvh422pxw73z49QGoKoZhz8IF91t9Y0iAMkMZz619jt8O/0bfFn15uf/LBLgFWL2fqgMHSJt4F8a8PCKmTcNnRN2OSTEZzPz+8S4Ob8/lgktb0fXC5j0z9J9JkqZpnoCDUqrE8vWFwPM2j0wIIUSTllFYwTPzdrN4z3HahXkz49pudI+xbp3OSRWF+plrO76FsM4wfh6EJtikq+T8ZCb/NZmjJUe5r+t93N7pdqtPrwGUrlzFsQcfRHN3I+aLL3Dv1LFO7RmqTfz2wU7S9uQz4Oo2dBoUaaVIG6/ajCSFAj9bhtqcgK+VUotsGpUQQogmy2gy8+maVN5avB+zUjw+sh239ouzTWE2wMGl+tL+0uMw8FHoPxmcrH/OmFKKnw78xCsbXsHHxYePL/yYHmE9rN4PQP7XX3P8pZdxbd2aqPffwzm8bufIVVcaWfDuDjIPFjLkxna0v6CFlSJt3P4zSVJKpQDWPWFPCCFEs7Q9rZAnft7J7oxiBrcN5vlxHYkKsMGBtABVpbD4Kdg0C4LawtVfQUQ3m3RVbijn+XXPsyBlAX3C+/BK/1cIdA+0ej/KZOL4/16l4Isv8Bo0iIg338DBs26F7ZVlBubP2E72kRKG35pA6x6hVoq28ZMtAIQQQthcSaWBN35P5vN1Rwj2cuW967oxsmOY7QqCU1fDL3dB4VG44D4YPAWc3WzS1f6C/Ty8/GGOlhzlnsR7uKPTHTjaoM7JVFrGsYcfouyvFQTcdBMh//cImmPd+qkoqWbe9G3kZ5Zx0YSOxCcGWynapkGSJCGEEDajlOK3XVk89+tuskuquLF3DA+PaIuPm40Ksw0VsOxFWPsu+MfALb9BTB+bdKWU4ueDP/Py+pfxdvHmo+Ef0TPc+gfgAhgyMkibeBdVhw4R9uyz+F99VZ3bLCuqYu7UbRTnVjD6rs5EJ1h/5KuxkyRJCCGETaTll/PMvN0s25dNh3AfPrwhicQoP9t1eGyzfiht7n59z6Nhz4Grl026KjeU8+K6F/k15Vd6hffif/3/R5B7kE36qtixg7S770FVVRE180O8+vatc5sl+ZXMfXsr5cXVXHxfFyLa2KhgvpGTJEkIIYRVGUxmZq06zNQlB9A0mDK6PTdfEGub89ZA3wxyxWuw8i39rLUbfoaWQ2zTF3Cg4ACT/5rM4aLD3N3lbiZ0nmCT6TWA4kWLyHj0MZyCg4n6dDaurVrVuc3C7HLmTt2KodLE2AcSCYv3tUKkTZMkSUIIIaxm85ECnvx5J/uyShjeIZRnxyYQ4eduuw6P74af74SsndDlWrjoFXD3s1l3vxz8hZfWvYSnsyczL5xJ7/DeNulHmUzkTH+HvA8/xL1rVyLfnYFTQN33WcrPKGPutK2YTYpxk7oSHO1thWibLkmShBBC1FlRuYFXf9/HNxuOEubjxswbunNhQpjtOjQZYc10+PNlPSm6+mtoN9pm3ZUbynlp/UvMOzSPnmE9eXXAqzabXjMVF3Ns8mTKVqzE74rLCX3qKRzqcMTICTlpJcybtg0HB43xD3UjoIUNjntpYiRJEkIIcd6UUszbnsEL8/eSX1bFrX3jeHB4G7xcbfjxknsQfpkI6RuhwzgY/TZ42q7o+FDhIR5e/jApRSlM7DKRiZ0n2mx6rXL/ftLvuw9DRqbVCrQBsg4XMf+d7Ti7OjJuUlf8Qm207UITI0mSEEKI83Ikr4wpv+xi5YFcOkf68uktPegYYcP6FrMZNsyEJc+Ckytc9gl0vAxstI2AUopfDv7CKxtewd3JnQ+Gf8AFLS6wSV8Axb//Qcbjj+Pg6UHMZ5/h0a2rVdrNOFDA/Bk7cPdxYdykRHwCbTj92cRIkiSEEOKcVBvNfLQyhelLD+Ds6MBzYxO4vncMjg42PAS14AjMvQdSV0LrEXDxNPCp2y7T/ya/Mp/n1jzHsrRl9Ajrwf/6/48QjxCb9KVMJnKmTSdv5kzcu3QhYvp0nEOt09fRPXn89v5OvAPdGDepK55+rlZpt7mQJEkIIUStbTicz5M/7+RAdimjOoXx9JgEwnxts0kjAErB1i9g0RP692NnQNfrbTZ6BPBX2l88veZpSqpLeLj7w9zQ4QabTa+Zioo4NvkRylauxO+KKwh9aopV6o8ADm/PYdFHu/AP82TcA4m4e1v/KJamTpIkIYQQ/6mwvJpXFu7ju01pRPi5M+vmJIa0s/HxFbkHYcFDcPgviO0Pl7wHfrY7lb7cUM7rm15nzv45tPZvzczhM2kb0NZm/VXu30/6vfdhyMwk7Lnn8L/qSqu1fWDTcZbM2kNQtDcX39cFN08bbd7ZxEmSJIQQ4qyUUvy89RgvLdhLYYWBOwfG88DQ1ni42PDjw1gFq6bCyjfByQ1GvwndbwUHG+2zBGzL3sYTq54gvSSdWxJu4d6u9+LiaLuRl+JFi8h44kkcPT2J+fwzPLpap/4IYN/aTJZ9vpewlr6MuacLLu7yUX++5JUTQghxRpuPFPDC/D1sSyuka7QfX47vRPtwH9t2englzH8Q8g7oRdkjXgFv241YGUwG3t/+Pp/s+oQwjzBmjZhFUliSzfpTJhM5U6eR99FHuCcmEjFtmtXqjwB2/ZXOX9/sJ6q9PyPv6oyzi22mCZsLSZKEEEKcIr2gnFcXJfPr9gxCvF15/fLOXNYtEgdbFmaX5cEfU2D71+AXA9f/CK2G2a4/IKUwhcdWPsbe/L2MazmOx3o+hpeLbY4xATAVFur1R6tW4XfVVYQ++YTV6o8Ati4+ypofDxLbOYgRdyTg5CwJUl1JkiSEEAKAsioj7y8/xEcrUwC4f0gr7hzYEk9b7nmkFGz7Cv54CqqKod9DMOARcLHdPj5mZeabfd/w9ua3cXdy5+1BbzMsxrYJWWXyftLvvRdDVhZhzz+H/5XWqz9SSrFpYSobfj1Mq+4hDLu1A462OgKmmZEkSQghmjmzWTFnSzqv/55MTkkV4xJb8H8XtbPtcSIAOfv1qbUjqyCqN1w8FULa27TLrLIsnlr9FOsy19E/oj/P933eZjtnn1D82296/ZGXl9Xrj5RSrPvlEFt+P0q73mEMvrG9bUf8mhlJkoQQohlbl5LHC/P3sDujmK7Rfnx4Q3e6Rdv4RHhDpV6UveptcPGEi6dD1xtsWpgNsDBlIS+ufxGj2chTvZ/iijZXoNlwKwG9/mgqeR99jHvXrkRMm4pziPXqj5RZsfKHA+z8M52OAyIYcHUbNEmQrEqSJCGEaIaO5JXxysJ9LNqdRQtfN6ZdncjYLi1smjQAcOhPfVl/fgp0vgoufAm8gm3aZVFVES+te4nfUn+jc3BnXu73MjE+MTbt01RYyLGHJ1O2ejV+V19F2BNPoFmx/shsViz/ah97V2eSOCyKCy5rZfufXTMkSZIQQjQjxZUGZiw7yKerU3Fy1Hh4eBvuGBCPm62LfEtz4PcnYOf3EBAPN/wCLQfbtk9gTcYanlr9FPkV+dybeC+3dboNJwfbfvRVJieTfu99GLOyCHvhefyvuMKq7ZtMZpZ+upcDG4+TNDqWnmPiJEGyEUmShBCiGTCazHy7MY23F+8nv7yay7tFMnlEW0J9bLhbNujnrW39HBY/DdXlMPBRvTjb2bb9VhormbplKl/t/Yo43zimD55OQlCCTfsEKF64kIwnp+Do7U3MF5/jnpho1fZNBjN/fLKblG059Bnfkm4jbDsi1txJkiSEEE3civ05vLhgD/uPl9IzLoDPxnSw7UG0Jxzfoxdmp62DmH4w5m0IbmPzbnfn7ebxlY9zuOgw17a7lkndJ+HuZNsidGUykf3WW+R/Mgv3bt2InDYVp2DrTiMaq0389uFOju7Op/9Vrek8OMqq7Yt/qnWSpGmaI7AJOKaUGmO7kIQQQljDwexSXl64l2X7sokO8OCD67sxIiHM9lMz1eWw4jVY8w64+sC49yDxWpuetwZgNBv5ZOcnfLD9AwLcAvhw+Idc0OICm/YJlvqjhx6mbM0a/K65mrDHH7dq/RFAdaWRhe/v4Nj+Qgbf0I4OfVtYtX1xZucykvQAsBew8XarQggh6qKgrJppSw/w5bojuDk78vjIdtzcNxZXp3rYXPDAEr0wu/AIJF4Hw18Az0Cbd3u0+CiPr3qcHTk7uCj2Iqb0noKvq+1Hyyr37dPrj44fJ/zFF/C7/HKr91FVbmD+jO0cTy1h+C0daNMzzOp9iDOrVZKkaVokMBp4CXjIphEJIYQ4LwaTmS/WHmHa0gOUVBq4umc0Dw1vQ5CXq+07L8mCRY/D7p8gsDXcNB/i+tu8W6UUcw7M4fWNr+Pk4MSr/V9lVPwom/cLULRgAZlPTsHRx4eYL7/AvUsXq/dRUVrNr9O3k3eslIvu6Eh8V9uuBBSnqu1I0lTg/wBv24UihBDifCilWLYvm5cW7CUlt4x+rYKYMqY97cLqYeDfbIbNs2DJc/rBtIOfhL4PgJPtE7PcilyeWfMMK9JX0Cu8Fy/2fZEwT9uPsiijkey33iZ/lu3qjwDKiqqYN20bRTkVjLqrMzEdbT8iJ071n0mSpmljgGyl1GZN0wb9y/0mABMAoqOjrRWfEEKIf7Evq5gX5+9l1cFc4oM8+eSmJIa0C6mfJeFZO+HXSXBsE8QN1AuzA1vavl9g6ZGlPLv2WSqMFTzW8zGuaXcNDprtj+IwFhSQ8fDDlK1Zi/+11xD62GNWrz8CKMmvZO7UrZQVVTPm3i5EtrXxBp/ijGozktQXGKtp2ijADfDRNO1LpdT1Ne+klJoJzARISkpSVo9UCCHESbmlVby1eD/fbjiKt5szT4/pwA19YnCujzO7qstg+Suw9j1w94fxM6HzlTYvzAYorS7lfxv+x9xDc2kf0J5X+r9CS7/6Scwq9+0j/Z57MWZnE/7Si/hddplN+inKqWDu21upKjcw9v5EwlvWw0pEcUb/mSQppR4HHgewjCRNPj1BEkIIUT+qjCZmr07l3WUHqTCYuLFPLJOGtcbPw/qjGWeUvAgWToaiNOh2Iwx7DjwC6qXrTVmbeHLVk2SVZ3FHpzu4q8tdODs610vfRfMXkDllCo6+vsR89SXunTvbpJ+CrDLmvr0Vo9HMuAe7EhIja6XsSfZJEkKIRkApxaJdWbzy2z6O5pczpF0IT4xqT6sQr/oJoDgDfvs/2PsrBLeDWxZBTJ966braVM2MbTP4dNenRHpH8tlFn5EYklgvfSujkew33yJ/9mzcu3cncurbNqk/AshNL2XetK2gaYx/qBuBEfX0sxVndU5JklJqObDcJpEIIYQ4ox3phby4YC8bDufTNtSbL27rSf/W9bTKyWSAjZ/AshfBbIChT0Of+8Cpfkau9ubtZcrqKewv2M/lbS7nkaRH8HD2qJe+DcePk/HI/1G+YQP+115L6GOP2qT+COB4ajG/Tt+Gs6sj4yZ1xS+0fp6j+HcykiSEEA3Unoxi3l6yn8V7jhPg6cKLl3Tk6h5RONVH3ZFSsP93WPwU5O6HlkNh9Bv6uWv1oNxQzrvb3uXLvV/i7+rPjCEzGBg1sF76Bij5808yH38Cc1UV4a+8gt/4S2zWV8bBQubP2I67lzPjJnXFJ8i2u4OL2pMkSQghGpj9x0uYumQ/C3dm4e3mxIPD2nBLv1h83Oqn/oasnfD7k3D4LwhsBVd/A21H1kthNsBfaX/x0vqXyCzL5PI2lzOp26R62RgSwFxdTc6bb5L/2ee4tmtHxFtv4RofZ7P+0vbms/D9HXj5uzFuUle8/OthTytRa5IkCSFEA3Eop5SpSw4wf0cGni5O3D+kFbf1i8fXo56So+JMfVpt21fg7gcjX4OkW6GeiqOzy7P534b/sfjIYlr6tuSziz6jW2i3eukboDo1lWMPPUzlnj34X389IY9MxsHVdklL6s5cFn24C79Qd8Y+0BUPn3oqvhe1JkmSEELYWWpuGdOXHeCXrcdwdXJk4sCWTOgfj79nPX1oVpfp56ytnqbXIPW5BwZM1pf31wOzMvN98vdM2zKNalM193W9j1sSbqm3lWsARfPmkfXsc2jOzkS+OwPvoUNt2t/Bzdks/mQ3QVFeXHx/Im6e9fdcRe1JkiSEEHaSll/OO8sO8OOWYzg5aNzWL447B7asn2NEQN8te/s3sOwFKMmEDuNg2LP1VncEsL9gP8+tfY4dOTvoFdaLp/o8RYxPTL31by4rI+v5FyiaOxf3pO5EvP46zuHhNu0zeV0mSz/bS1i8L6Pv7YKru3wUN1TykxFCiHqWUVjBjD8P8v3GNBwcNG7oHcPdg1oS4uNWf0Gk/AV/PKnXH0UkwRWfQnTveuu+wljBh9s/5LPdn+Ht4s3L/V5mTPyY+tkp3KJyzx6OPfgQ1WlpBN1zD0F3TURzsu3H4u6Vx1j+dTKRbf0ZdVdnnF3r4dBhcd4kSRJCiHpyvLiS9/48yDcb0lAoru4ZxT2DWxHuW4+rmXIPwB9Pwf7fwDcaLvsEOl5Wb0XZAGuOreGFdS+QXprOuJbjeDjpYfzd6u/YDaUUBV98Qfbrb+AYEED0p7Px7NnT5v1uX5rGqh8OENMpkIsmdMTJWRKkhk6SJCGEsLGckireX36Ir9YfwWRWXJEUyT2DWxHpX4974ZTlwV//g02zwMldn1brdRc419/oVV5FHq9tfI2FhxcS6xPLrBGz6BHWo976B/3stcwnnqT0zz/xGjSI8Fdexsnf9gnapoWprJ+XQstuwQy/NQFHp3rYxkHUmSRJQghhI/ll1Xz41yE+W5tKtdHMpd0iuX9Ia6ID6zE5MlbB+g9hxRtQXQrdb4ZBj4NXPW1GiV6Y/cvBX3hz05uUG8uZ2GUit3e6HVfH+l3uXrZhAxmP/B+m/HxCn3gC/xuut/n0nlKK9XNT2LzoCG17hTHkxnY41Mc+V8IqJEkSQggrKyyv5qOVKXy6OpVyg4lxXVpw/9DWxAfX4zETSsGeX2DxM1B4BFpfCMNfgJB29RcDkFKYwnNrn2NL9ha6hXTjmT7PEO9Xf4XhoB8tkvv+B+S+/z4uUVFEffctbh062L5fpVj1wwF2LEunQ/8WDLqmLZpD/U1rirqTJEkIIaykqMLArFWHmbXqMCVVRkZ3DufBYa1pFeJdv4GkbdSLstPWQ0gC3PAztBxSryFUmar4eOfHfLzzYzycPHjugue4pNUlOGj1O4piyMzk2COPULFpM77jxhH61FM4ennavF9lViz/Jpk9KzPoPCSSfle0rteidGEdkiQJIUQdlVQa+HR1Kh+tTKG40shFCWFMGt6admH1fIJ7wRFY+hzs+hG8QmHsO5B4HTjUb4HwhswNvLDuBVKLUxkdP5pHkh4h0D2wXmMAKFm2jMzHn0AZDLR47VV8x46tl37NJjNLP9/L/vXH6X5RDL3GxUuC1EhJkiSEEOepvNrIZ2uO8OGKQxSWGxjWPoRJw9rQMaJ+jtA4qbIIVr4F694HzQEG/B/0fQBc6/cU+cLKQt7Y9AZzD80l0iuSD4d9yAURF9RrDADmqiqyX3+Dgi+/xK1DByLeehOX2Nh66dtkNLP4k90c2ppDr7HxJI2qn36FbUiSJIQQ56ii2sRX64/w/vJD5JVVM7BNMA8Nb0OXKL/6DcRkhM2zYfkrUJ4PXa6BIVPAN6Jew1BK8WvKr7yx8Q1Kqku4reNt3NnlTtyd6v+g1qqUwxx76CGq9u0j4KYbCX74YRxc6mfncmO1iUUzd3FkVx79rmhNl6FR9dKvsB1JkoQQopYqDSa+2XCU95YfIqekin6tgnhweGu6xwTUbyBKwYE/9P2OcpMhtj9c+CK0SKzfOIAjxUd4Yd0LrM9cT5fgLjzd52na+Lep9ziUUhT9/AtZL76Ig4sLkR+8j/egQfXWf3WlkYXv7+TY/gIGXtuWjgPqN1EVtiFJkhBC/Icqo4nvN6Xz7rKDZBVX0jMugHeu6Urv+PqvsyFrJ/wxBVKWQ2AruPobaDuyXjeDBDCYDMzePZsPt3+Ii6MLU3pN4Yq2V9R7YTaAqbSMrOeeo/jXX/Ho2ZMWr7+Gc2hovfVfVWFk/jvbOX64iGE3tadtb9seayLqjyRJQghxFgVl1Xy1/gifrT1CTkkV3WP8eevKLvRpGVj/hbjFmfDni7D1K3D3g5GvQdKtUI+HwJ6w5fgWnl/7PIeKDnFhzIU81vMxgj3qb9+lmip27uLYww9jSE8n+IH7CZwwAc2x/grVK0sNzJu+jbz0Ui68vSOtuofUW9/C9iRJEkKI06TmljFr9WF+2JROhcFE/9ZBvHVlF/q1Cqr/5KiiQC/IXjMDTNXQ5x4YMBnc6+8YjxOKqoqYumUqc/bPIdwznHeHvsuAyAH1HgeAMpvJ/+xzst96C6egIGK++ByP7t3rNYby4mrmTt1KUXYFIyd2IrZzUL32L2xPkiQhhECvadl8pICPVqbwx57jODlojEuM4Pb+cfW/lB/0Qux17+m7ZVcVQ4dLYNgzEFC/GzGC/tosSl3EqxtepaCqgJs63MTdiXfj4VyPO4fXYMzPJ+Pxxyn7awVew4bS4sUXcfTzq9cYSgsqmTt1G6UFlYy+pzNR7eu5Lk3UC0mShBDNmtFk5vfdx/loZQrb0grxdXfm7kEtualPLCE+9Xeu2UllubB2Bmz4CKrLoMM4GPAIhHWs/1jQd8x+bdNrrD62moTABN4f9j7tA9vbJRaAsnXr9KNFiooIfWoK/tdeW++je8W5FcydupWKUgMX359Ii1Z+9dq/qD+SJAkhmqXSKiPfb0xj1urDpBdUEBPowfPjEri8eyQeLnb401iaA2umw8ZPwFAOHS/Vk6MQ+yQkuRW5vLftPX468BPuTu482uNRrml3DY71vDHlCcpoJGfGDPI+nIlLbCxRH83ErV39HrECUJBVxtyp2zBWmxg3qSuhsXYYZRT15j//Emia5gasAFwt95+jlHrG1oEJIYQtZBZV8OnqVL7ecJSSSiNJMf5MGd2B4R1CcbTHuVolx/9OjkxV0OkK6D8Zgut/GT1AhbGCz3d/zqxds6g2VXNV26uY2GUi/m71XwN1giEjg2OTH6FiyxZ8L7uUsCefxMGj/qf68o6VMnfqVgAueagbQZH1u1mnqH+1+e9SFTBEKVWqaZozsErTtN+UUutsHJsQQljN7owiPl55mF+3Z2BWipEdw7m9fxxdo+304V+cCaunwuZPwWSAzldB/4chqJVdwjGZTcw7NI8ZW2eQXZHNsOhhPNDtAWJ9Y+0SD+i1UMXz55P1wotgMtHijTfwHTPaLrFkHylm3vRtODk5MO7BrviH2f78N2F//5kkKaUUUGr51tlyUbYMSgghrMFsVvy1P4ePVqaw5lAeHi6O3NAnhlv7xhEVYJ+iY4rSYdVU2PI5KBN0uRr6PQSBLe0TD7AmYw1vbnqT/QX76RTUidcHvk630G52iwf0g2kzn32Wsr9W4N6lCy1efw2X6Gi7xJJ5sJD5M7bj6uHMuAcT8Q2203tH1LtaTbxrmuYIbAZaAe8qpdbbNCohhKiDSoOJX7Ye4+NVhzmYXUqYjxuPjWzHNT2j8XWv/32FACg8Cqvehi1f6N8nXgv9HwL/WPvEA+wv2M9bm99i9bHVRHhF8PqA1xkRO8Kuh7Eqs5nC774j+403UWYzoU88gf9119br3kc1pe/LZ8H7O/H0dWHcpK54B9ihmF/YTa2SJKWUCUjUNM0P+FnTtI5KqV0176Np2gRgAkC0nbJ9IUTzlldaxZfrjvLFulRyS6vpEO7D21d1YXSnFrg41f9O0AAUpMLKN2Hb1/rhs91uhH6TwM9+fyezy7N5d9u7/HLwFzydPZmcNJlr2l2Di2P9nHF2NlWHD5P51FNUbNqM5wUXEPb8c7hERtotntSduSz6cBe+Ie6MfSART19Xu8Ui7OOclnAopQo1TfsTuAjYddptM4GZAElJSTIdJ4SoN4dySvlk1WF+3JxOldHM4LbB3NE/3j47Y5+QdwhWvgXbvwEHJ3137L4PgK/9PvTLDeXM3j2bz3Z/hsFs4Pr21zOh8wR8XX3tFhPoK9fyZs8m950ZaG5uhL/8Mr7jL7HriNahLdn88cluAiO8uPj+Lrh72TeBFPZRm9VtwYDBkiC5A8OBV20emRBC/AulFOsP5/PxyhSW7M3GxcmBS7tGcFu/OFqHetsvsNyDsPIN2PG9fmRIzwl6cuRjv/O8jGYjvxz8hXe3vUtuRS4jYkfwQNcHiPKx/yn1lXv3kvnkFCr37MF7+HBCn5qCc4h9j/ZIXp/F0s/2EhrrzZh7u+DqYacpWmF3tRlJCgc+s9QlOQDfK6Xm2zYsIYQ4M4PJzMKdmXy88jA7jxUR4OnC/UNbc0PvGIK97TgdkpMMK96AXXPA0RV63wUX3A/e9XfQ6umUUqw8tpK3Nr3FoaJDJAYn8vagt0kMSbRbTCeYq6rIfe998j7+GEd/fyKmTcNnxIX2DovdK4+x/OtkIlr7Meruzri4yXaCzVltVrftALrWQyxCCHFWxZUGvtuQxuzVh8koqiQ+yJOXxnfksm6RuDnbp6gXgON7YMXrsPtncHaHPvfqyZGXfQ58PWFv3l7e3Pwm6zPXE+0dzduD3mZo9FC7TmGdUL5lC5lPTqH68GF8L72U0P97pN6PFTmT7UvTWPXDAaITAhh5ZyecXOz4vhINgqTIQogGSynFzmNFfLsxjXnbMiitMtIrLoDnx3VkSLsQHOyx+eMJWbtgxWuwZy64eEG/B/XDZz3te8hpVlkW72x9h18P/Yqvqy+P9XyMK9tcibOj/aeMTKVl5Lz9NgVff41zixZEffwxXv362jssADYvSmXdLynEJwZz4W0JODrbqdBfNCiSJAkhGpyicgO/bDvGtxvT2JtZjJuzA6M6hXPzBbF0jvSzb3CZ2+Gv12DffHD10Y8O6X03eNj3gNPS6lJm7ZrF53s+RynFzR1v5vZOt+Pj0jCOzShduYrMZ57GmJmF//XXEzLpARw87b8ho1KKDb8eZtPCVFr3CGXoze1xdJQESegkSRJCNAhKKdal5PPdxqMs3JVFtdFMpwhfXrykI2MTW+DjZueRkGNb9ORo/2/g6gsDH4PeE8Hdfsd1ABjMBn7c/yPvb3+f/Mp8RsWN4v5u9xPhFWHXuE4wFhSQ/b9XKZo7F5f4eGK++gqPbg2jgkMpxeo5B9m+NI32fcMZdF07+45OigZHkiQhhF1ll1Ty4+ZjfLfxKKl55Xi7OXF1jyiuTIqiY4R9l6ZjNkHyb7DhQzi8Atz8YPAU6DUB3Oy8bF4plqct563Nb5FanEpSaBLvDX2PhKAEu8Z1glKKkt9/J+uFFzEVFRF0910ETpyIg0vDWEqvzIq/vt3P7hXH6DQ4kv5XtEaTBEmcRpIkIUS9M5rMrDiQw7cb0li6LxuTWdEzLoD7h7ZmZMdw3O1dMFuerx8bsvETKDoKPpEw7FlIug3c7D99tSt3F29seoPNxzcT6xPL9MHTGRQ1qEEUZQMYsrPJev55SpcsxS0hgehZn+DWtq29wzrJbDKz7It9JK/LotuIaHpf0rLBvHaiYZEkSQhRb9Lyy/l+Uxo/bEonq7iSIC8Xbu8fx5VJUbQMbgAnqmfthPUfws4fwFgJsf1hxEvQdhQ42v/P5bHSY0zfMp2FhxcS4BbAlF5TuLTNpTg72L8oG/TRo6KffuL4/15FVVcT8shkAm66Cc3J/q/dCSajmcWz9nBoSzY9L44jaVSsJEjirBrOO1cI0SRVGU38sfs4321MY9XBXBw0GNgmmGfHJjC0fQjO9i6SNRn1IuwNM+HIanByh85X6ZtAhnW0b2wWxdXFfLzzY77a8xWapnFHpzu4teOteLk0gMTSojotjcynn6Z87To8evQg/IXncYmNtXdYpzAaTPw+cxepO/O44LJWdB0uR2iJfydJkhDCJvYfL+G7jWn8tCWdgnIDEX7uPDS8DZd3j6SFn7u9w4OyXNj8KWyaBcXH9LPUhr8AXa+3+0q1E/Iq8vhq71d8s+8bygxlXNzyYu7reh9hnmH2Du0kZTKR/8UX5EybjubgQNizz+J35RVoDg1rhZihysTC93eQvq+AAVe3odMg+x0PIxoPSZKEEFZTVmVkwY5Mvt14lC1HC3F21LiwQxhX94yib8ughrFyKGMrrJ8Ju34EUxXEDYRRb0CbEeDQMDYPzCzN5NPdn/LTgZ+oMlUxLGYYEzpPoF1AO3uHdoqqAwfImDKFyu078Bo0iLBnn8E5rOEkcCdUVxiZ/+52sg4VMeTG9rS/wH5HxIjGRZIkIUSdKKXYnl7EdxuPMm9bBmXVJlqFeDFldHvGd40g0KsBnJxuMuibPm6YCWnrwdlTHzHqOQFCGk7ikVKUwqyds1iQsgCAMS3HcEvHW4j3jbdzZKdS1dXkfvQRuR98iKOXFy3efAOfUaMaZG1PZZmBX6dvIzetlOG3JdA6yX7HxIjGR5IkIcR5KSyv5uetx/huYxr7skpwd3ZkTOdwru4ZRbdo/4bxgVmaDZtm61NqpVngHwcjXoHEa8Hdz97RnbQ7bzef7PyEJUeW4OroypVtr+TmhJsJ92p4Ix4VO3aQ+eQUqg4cwGfMGEKffAInf/vuFXU25cXVzJu2jYLjZVx0Z0fiutj3qBjR+EiSJISoNbNZsS4lj283prFot77hY5dIX14e34mLu4Tjbe8NH09I36SvUtv9M5gN0HIojJ0OrYZDA6mVUUqx6fgmPt75MWsy1uDt7M3tnW7nuvbXEegeaO/w/sFcUUHOtOnkf/45TsHBRL7/Ht6DB9s7rLMqLahi3rStlORVMvruzkR3aHivqWj4JEkSQvynw7llzN+ewZwt6RzJK8fHzYlre0ZzZVIUHVrYf98gAIxVsPsXfePHY5vBxRuSboWed0BQa3tHd5JSihXpK/h458dsy9lGgFsAD3R7gKvaXoW3i7e9wzujsnXryHzqaQxpafhdfRUhkyfj6NVwVtadrjivgrlvb6WixMDF93ehReuGOdIlGj5JkoQQZ3Qkr4z5OzJZsCOTPZnFAPSKC+DBYW24qGMYbs4No8iZ4kx9Om3zbCjLgcDWMPJ16HJ1g9j48QSj2cgfqX/wya5P2F+wn3DPcJ7o9QTjW43HzcnN3uGdUdXhw2S/+SalS5biEhND9Oef4dmzp73D+leFx8uZO3UrhioTYyclEhZn513bRaMmSZIQ4qS0/HIW7NQTo53HigDoGu3HU2M6MKpTGOG+DWDpPoBSkLYB1n8Ae+fpx4e0vlA/LiR+SIOZUgOoNlUz79A8Zu2aRVpJGnG+cbzU7yVGxo1sMJtAns6Yn0/ujHcp+P57HFxcCJ70AAE334yDW8NM5k7Iyyhl3tRtmM2KcQ92JTiqYY7MicZDkiQhmrljhRUs3JHJ/J2ZbE8rBKBLlB9PjmrPyE5hRPp72DfAmgyV+tL9DR9C5nb9oNmed0LP2yGgYa0AKzeU88P+H/h89+dkV2STEJjA1EFTGRw9GAet4SRxNZkrKsj/7HPyPvoIc2Ul/lddSdA99+AU2PDreXKOljBv2jYcnDTGP9SNgBae9g5JNAGSJAnRDGUWVbBgRyYLdmay9WghAJ0ifHlsZDtGdwonKqABJUYAuQdh21ew5TMoz4PgdjD6LX1nbNeGVRtTVFXE13u/5qt9X1FUVUTPsJ680O8F+oT3aRgr/s5AmUwUzfuVnGnTMGZl4TV0KCEPP4RrfMNKPM8mK6WIX9/Zjou7I+MmdcUvpIG9f0WjJUmSEM3E8eJKFlqm0jYdKQAgoYUP/3dRW0Z3CicmsIH9z7ssF3b9BDu+1QuxNQdoM1KfUosbCA0s4cgpz+HzPZ/zffL3lBvLGRQ1iNs73U6X4C72Du1fla5eTfbrb1C1bx9unToR8fprePToYe+wau3Y/gIWvLsDdx8Xxk1KxCewgUwJiyZBkiQhmrDskkoW7cpi/vZMNh7JRyloF+bN5AvbMKpTOPEN4VDZmgwVkPwb7PgODi4BsxFCO+rHhXS6HHxa2DvCf0grSWP2rtn8cvAXTMrEyLiR3NbxNlr7N5wVdWdSmbyf7DfeoGzlSpwjIvQNIUeObHDHifybo7vzWPjBTnwC3Rg3qSuefg1g41LRpEiSJEQTk1NSxaLdWSzYkcH6w3pi1DbUmweH6YlRq5AGlhiZzfrBsju+03fFrioG73Dofbc+ndZADpk93f6C/Xyy8xMWpS7CUXNkfKvx3NzxZqK8o+wd2r8yHD9OzvTpFP38Cw7e3oQ8+ij+112Lg4uLvUM7Jynbcvj94134h3ky7oFE3L0bV/yicZAkSYgmIK+0it93H2f+jgzWpeRhVtAy2JP7h7RmdOdw2oQ2wFU+Ocmw/VvY+QMUpelHhXQYqydGcQMazDlqp9ues52Pd3zM8vTleDh5cFOHm7ihww0EezTs3ZxNpWXkffIx+bM/BZOJgBtvJGjinTj6+dk7tHN2YONxFs/eQ3C0Nxff1wU3z4a5SlA0fpIkCdFIFZRV8/vuLBbszGTNoTxMZkV8kCf3Dm7F6M4taBPq1fAKhUuzYeccfdQoc5teZ9RyCAx9BtqNApcGVhdloZRibeZaPt75MRuzNuLr6svdiXdzbbtr8XVt2PvwKKORwh9+IGfGu5jy8vAZNYrghx7EJTLS3qGdl71rMlj2xT7CW/oy5p4uuLjLx5iwnf98d2maFgV8DoQCCpiplJpm68CEEP9UVG7g9z1ZLNiRyeqDuRjNiphADyYOjGd0pxa0D/dueIlRdTkkL9RHjQ4tA2WC8C76GWodLwPvhnvgaFFVEb8e+pU5++dwqOgQIR4hPJL0CJe3uRwP54a9gkopRemff5L9xptUp6TgntSd0Pffw71zZ3uHdt52Lk9nxbf7iWrvz8i7OuPs0jBHG0XTUZsU3Ag8rJTaommaN7BZ07TFSqk9No5NiGZPKUVKbhl/7svmr/05rEvJw2BSRAW4c8eAeEZ3CiehhU/DS4zMJkhdCdu/0zd7rC4Fn0joez90vhpC2tk7wrM6cabajwd+ZHHqYqrN1XQO6szzFzzP6PjRuDg2/NqXip07yX71Nco3bcIlLo7Id2fgNWRIw3ufnIMtfxxh7U+HiO0cxIg7EnBqKDu+iybtP5MkpVQmkGn5ukTTtL1ABCBJkhA2UFFtYl1KHn8mZ7M8OYej+eUAtA7x4ta+cYzuHE6nCN+G+YF3fLelzmgOlGSAqw8kXKInRjF9G9RO2KcrqCxg3qF5zNk/h9TiVLydvbmszWVc1voy2ga0tXd4tVKdfoyct9+meMECHAMCCHvmafwuvxzNufHW7Cil2LgglY3zD9OqewjDbu2Ao2PDfR+JpuWcJnM1TYsFugLrbRKNEM1Uam4Zy5Oz+TNZHy2qMppxd3akb6tAJgyIZ1Db4Ia183VNJVl68fX27+D4TnBwglbDYMRL0HYkODfcfWvMyszGrI3M2T+HpUeXYjAb6BrSlds73c6FsRfi7tRwY6/JVFRE7gcfUvDll+DoSODEOwm8/fYGfQhtbSilWPvTIbYuPkq73mEMvrE9Dg4N8D8HosmqdZKkaZoX8CMwSSlVfIbbJwATAKKjo60WoBBNUaXBxPrD+Sen0Q7nlgEQH+TJdb1iGNwumB6xAQ3nENnTVZXCvvn6qNHhv0CZIaK7frBsx0vBM8jeEf6r3Ipc5h6cy48HfiStJA0fFx+uansVl7W+jFb+rewdXq2Zq6sp+Pprct//AHNxMb7jxxN8/304h4XZO7Q6U2bFyu/2s/OvY3QcEMGAq9ugSYIk6pmmlPrvO2maMzAf+F0p9dZ/3T8pKUlt2rTJCuEJ0XSk5ZefHC1acyiXSoMZVycHLmgZyKC2IQxqG9zwdr2uyWyClD9hx/ew91cwlINftL5kv/NVENSwN080KzPrMtYx58Ac/jz6J0ZlpHtody5vcznDY4bj6th4NiJUSlHy229kv/U2hvR0PPv2JeSRybi1a7i1XufCbFb8+eU+9q3JJHFYFBdc1qphTi+LJkPTtM1KqaTTr6/N6jYN+ATYW5sESQihqzKa2JRawJ/7svkzOZtDOfpoUXSAB1f3iGZg22D6xAc23NEigOoyOPQn7P8N9v8OZTng5gudr9TrjKJ6Neg6I4Ds8mx+OfgLPx34iWOlx/B39ee69tdxaZtLifdtHGeT1VS+aRPHX3udyh07cG3blqiPP8arX197h2U1JpOZpbP3cGBTNkmjY+k5Jk4SJGE3tZlu6wvcAOzUNG2b5bonlFILbRaVEI3UscIKllsKrlcfzKW82oSLowO94gO4rlcMg9oGExfk2bD/6Bdnwv5F+vEgKcvBVAWuvtB6uL7ZY+sR4Oxm7yj/lclsYnXGaubsn8OK9BWYlIleYb2Y1G0SQ6KHNIoVaqerSjlM9ptvUrp0KU4hIYS//DK+48aiOTbgJPscmQxmfv94F4e359JnfEu6jYixd0iimavN6rZVQAP+iy6E/RhMZjalFpxMjJKPlwAQ4efOpd0iGNw2hD4tA/FwacAb3ikFx3dB8iJ9P6OMLfr1fjGQdKtefB1zATg2/BVSWWVZ/HzgZ346+BNZZVkEuAVwU8JNXNb6MqJ9GmetZFVKCvmzP6Xwp59wcHUleNIDBNx0Ew7ujaOovLYM1SYWfbCTo3vy6X9VazoPbtjHu4jmoQH/5RaiYTpeXKnXFu3TR4tKqow4O2r0jAvg8u7tGdwumJbBDXC365qM1XBklT5alPybfiwIGkQmwdCnoe0oCG4HDfk5WBjNRlamr+THAz+y8thKzMrMBS0u4P96/B+DIgfh3AiSu9MppSjfsJH82bMpXb4czcUF/6uuIuieu3EKDLR3eFZXXWlkwbs7yDhYyOAb2tGhb8M7yFg0T5IkCfEfCsqq2ZCaz/qUfNam5LE3U1/cGe7rxpguLRjUNpi+rYLwcm3gv07l+XBwiT5adGAJVJeAkzu0HAwD/0+fRmvAu1+fLqM0g58O/MTPB34muyKbYPdgbut4G5e2vpRI78Z55IYyGChe9Dv5s2dTuWcPjv7+BN1zD/7XXtMkkyOAyjID82dsJ/tICcNv7UCbHo1/ZZ5oOhr4X3Uh6l92SSUbDutJ0YbD+Sen0FydHOgW7c9jI9sxqG0wbUMb4BEgp8s79Pdo0dG1+pEgXqH6Mv22IyFuILg00P2XzsBgNrAibQU/HPiBNcfWANAvoh9PtnmSAZEDcHJonH/STCUlFH7/A/lffokxMxOXuDjCnnsO33FjcXBr2PVfdVFRUs286dvIzyjjojs6Et+1YR8SLJqfxvkXRQgryiisYP3hvJOJUYplzyJPF0e6xwYwNrEFveIC6BTpi6tTAy+SNZsgfZM+WpT8G+Qm69eHJEC/B/VptBZdG/yKtJqUUiQXJLPo8CLmHppLbkUuoR6hTOwykfGtxhPuFW7vEM+b4dgx8j//gsI5czCXleHRsydhTz+F18CBaI3oZ3Q+yoqqmDt1G8W5FYy6uzMxCU1zpEw0bpIkiWZFKcXR/HLWp+Sz/nA+6w/nkV5QAYC3mxM9YwO4umcUPeMC6djCB6fGcPxBVam+f1GyZZl+ea6+63VsP0vh9UXgH2vvKM+JUopdubtYfHQxS44sIa0kDQfNgQGRA7i89eX0jejbaEeNQD9bLX/2bIp//wMAn5EjCbj5Ztw7Jtg5svpRkl/J3Le3UlZczcX3diGirb+9QxLijBrvXxkhakEpxaGcUtZZps42HM4nq7gSgABPF3rGBnBbvzh6xgXQLswHx8ayo29xRo1l+n/py/TdfKH1hfo0Wqth+veNiFmZ2Za9jcVHFrPk6BKyyrJw0pzoFd6L2zrexuDowQS4Bdg7zPOmzGZKly8nf9ZsyjdtwsHLi4AbbyTghutxbtF8CpULs8uZO3Ur1RUmxj2QSFh843qfiuZFkiTRpJjNin1ZJSenzzYczievrBqAEG9XesUH0jMugN5xAbQKaeAr0GoyGSFzOxxaalmmv1W/3j8WetymJ0bRfRrFMv2ajGYjm45vYsmRJSw9upTcilxcHFy4IOIC7ut6HwMjB+Lr2rg/RM0VFRTNnUv+p59RnZqKU4twQh59FL8rLm/0Z6udq/yMMuZO24rZqLjkwa4ER3vbOyQh/pUkSaJRM5rM7M4oPiUpKq40AvpeRQPbBtMrLoBecYHEBHo0nqTIbIKsHZC6Cg6v1Iuuq4rRl+n3gKHPWJbpt20Uy/RrMpgMrMtcx5KjS1h2dBmFVYW4O7nTL6Ifw2OGMyByAJ7ODfh4lloy5uZS8PXXFHz9DabCQtw6dqTFm2/gM2IEmlPz+9Obk1bCvGnb0Bw0LnmoK4ERzStBFI1T8/tNFY1atdHMjvRCSz1RPptT8ymrNgEQF+TJyI7h9IoPoGdcAJH+jWfVFmazvqFj6ipIXQlHVkNlkX5bYGvoeBnE9YfYAeDV+FYAVRorWZOxhsVHFvNX2l+UGErwdPZkYORAhscMp29EX9ydmsbmiFUHD5L36acUz/sVZTDgNXgwgbfcjHtSUuNJ0q0s63AR89/ZjrOrI+MmdcUvtBH9bopmTZIk0WCZzYqU3FK2pxWxI72Q7elF7MksptpoBqBNqBfju0XQK06fQgv1aURLpc1myNmnJ0SHV+hJUUWBfpt/HHQYpydEsf3Ap3Gu3io3lLPi2AqWHFnCivQVVBgr8HHxYUj0EC6MvZDe4b0b5fEgZ6KUonzdOvJmz6ZsxUo0V1d8Lx1PwE034RoXZ+/w7CrjQAHzZ+zA3duZcZO64hPUNJJh0TxIkiQaBKUU6QUV7Eg/kRAVsutYMaVV+tSZh4sjHSN8ualPDN1j/OkRG0CgV+M5tR2lIHe/nhClrtRHjMrz9Nv8oqHtaMtIUT/wbZwbIQKUVJewPG05S44sYXXGaqpMVQS4BTAmfgzDYobRI6wHzg6Nq27q36jqaop/+428Tz+jau9eHAMDCbr/PvyvuQYnf1mxdXRPHr+9vxPvQDfGPtAVL/9G9DsrBJIkCTvJKak6OTq0I72QHelF5FsKrF0cHWgf7s34rhF0jvSlS5QfLYO9Gs/KM9CTorxDkLpCrylKXQVl2fptPpH6KrRYS1Lk37gP8SysLOTPtD/548gfrMtch9FsJMQ9hMtaX8awmGF0C+mGo0MD31/qHJmKiyn47jsKvvgSY3Y2Lq1aEv7iC/hcfDEOrpIIABzensOij3bhH+rJ2AcS8fBpGqOGonmRJEnYXFGFgV3HitieXsgOy9RZRpG+DN9Bg9Yh3gxtF0LnKD+6RPrSNsy74W/aeDqloOCwJSGyJEUlmfpt3uEQP+jvkSL/uEZXbH263Ipclh5ZyuKji9mUtQmTMhHhFcF17a5jWMwwOgd3xkFrBHtMnaPq9HTyP/ucwh9/RJWX49GnN+EvvoBnv35NfvPHc3Fg03GWzNpDUJQXF9+fiJtn0xk9FM2LJEnCqiqqTezJLDpZR7QjvejkDtYAMYEedI8N4NZIXzpH+pHQwgfPhn7m2dkUHPk7ITq8EorT9es9Q/5OiGIHQGDLRp8UgX5W2rKjy1h8ZDFbs7eiUMT6xHJLx1sYHjOc9gHtm2RhsjKZKF+/noLvf6Dkjz/AwQHf0aMIuPlm3Nq3t3d4Dc6+tZks+3wvYS19GXNPF1zcG+nvtxBIkiTqwGAyk5xVUqOOqIj9x0swmRUAoT6udI7049JuEXSO9KNzpC9+Ho10yF0pyE+BtA2WFWgroPCofptHkCUhmgRxAyCoTZNIigoqC9iQtYH1metZn7meoyX6823l14q7utzFsJhhtPJr1TQTI6Wo3LmTovnzKf7tN0w5uTh4exN46y34X389zmFyCOuZ7Pornb++2U9kO39G3dUZZ9dGNiIsxGkkSRK1UlFt4kB2CfuyStiTUcz29EL2ZBRTZVlp5ufhTKcIX4a2a3myjqhRrTY7XUkWHNsMx7bo/2Zs+XtJvrs/xPSFPvfqdUUh7ZtEUlRuKGfz8c16UpS1nn35+wDwdPYkKTSJq9tdTb+IfsT5Nt3VWlWHDlG8YAFF8xdgOHoUzdkZr0GD8BkzBq+BA5r0YbN1tXXxUdb8eJDYToGMmNARJ2dJkETjJ0mSOIXJrDiSV0Zylp4QJWeVkHy8hNS8MpQ+QHRypdkNvWNO1hFFBzSijRpPV1mk72B9IiE6tgVKMvTbNEcITYCE8RDRXb8Et29UB8SejcFkYGfuTtZlrmN95np25O7AaDbi7OBMYkgi9ybeS6/wXiQEJTSpFWmnM2RmUrxwIUXzF1C1dy84OODZuxdBd96J9/BhOPr42DvEBk0pxaaFqWz49TAtu4Uw/NYOODo1/t8PIUCSpGZLKUVOSdUpiVByVgkHskuoNOijQ5oGsYGetA31ZmyXFrQL86ZtmDcxgZ6Na6VZTYZKfdPGkwnRZsg78PftAfEQ21dPhlp0g7BO4NI0Nr4zKzP7C/azPnM96zLXsfn4ZiqMFWhodAjswI0dbqRXeC+6hnRtMhs7no2xoICS3/+geP58yjdtAsCtS2dCn3gc74suwjkkxM4RNg5KKdb9cogtvx+lba8whtzYDofGcCi0ELUkSVIzUFplZL8lCdJHiIpJziqhoNxw8j7B3q60C/Pm+l4xtA3zpl2YD61CvHB3acRD5maTvjdRzYTo+G4wW563V6ieDHW5Sk+IWnQFj8Z7gOrplFKklaSdHCnamLWRgip9w8pYn1jGthxLn/A+JIUlNfrz0WrDXFZGybI/KV6wgNJVq8BoxCU+nuAH7sdn1ChcYhr3Vgz1TZkVK384wM4/00no34KB17RFa6z/eRLiLCRJakIMJjOHc8sso0PFJGeVkny8mLT8ipP38XBxpE2oNyMSwmhrGRlqF+ZDgGcjLag+QSkoSjt1yixzG1SX6re7eENEV+hzz9/TZj4tmkQtUU25Fbknk6L1mevJLNO3IQjxCKF/ZH96hfeiV1gvQj1D7Rxp/VDV1ZSuXk3x/AWULFuGqqjAKSyMgJtuxHfMGFzbtWu808R2ZDYrln+1j72rM+kyNIq+lzfNAn4hJElqhJRSZBZV1qgbKmZfVgkpOWVUm/SpMkcHjfggT7pE+nFVUhRtQvVkKNLfHYem8L+98vxTR4gytkBZjn6bowuEdoQu11gSom76+WdNoI7odCXVJWzK2sT6LD0pOlh4EAAfFx96hvXk1o630iu8F7E+sc3mQ0yZzVRs3kzR/AWULFqEqagIR19ffMeNxXfMGNy7dZM9jerAZDKz9NO9HNh4nKRRsfS8OK7ZvLdE8/OfSZKmabOAMUC2Uqqj7UMSJ5RXGzmcW6ZfcvR/U3LLOJRTSonlpHuAcF832oZ5M7BtsF43FOpDyxDPxrch45mYzfomjdl7LZfdepF1QarlDpq+5L7VcD0ZiuimJ0hOTXPX4ypTFduyt50cKdqVtwuzMuPm6Ea30G5c3PJieoX3op1/uya3y/W/UUpRtXcvRfMXULxwIcasLDR3d7yHDsVnzGi8LrgAzaWRj5Y2ACaDmT8+2U3Kthx6XxJP94ti7R2SEDalqRNLls52B00bAJQCn9c2SUpKSlKbLMWQ4t8ZTGbS8stPJkMpNRKirOLKU+7bwteNuGBP4oL0Yuq2YT60DfXG16MJrDxSSl92n73Hctlr+XcfGP+eLsQvGsITLQlRd/1rt6a5+kgpRXppOrtyd5287M7bTZWpCkfNkY5BHekV3ove4b3pEtylyRwWey6qjxyhaMECiucvoDolBZyc8OrfH5/Ro/EeMhgHj6ZRdN8QGKtN/PbhTo7uzqffFa3pMjTK3iEJYTWapm1WSiWdfv1/jiQppVZomhZrk6iaCbNZcbykksM5liSoxuVofvnJzRcB/D2ciQvypG+rIOItCVFckCexgZ6Nu4i6poqCGknQXjhuSYwqC/++j2eIvv9Q0i36vyEdILgtuHrbLWxby63IZXfubnbm7mRX3i525+6msKoQABcHF9oHtufKtlfSK6wX3UO74+XiZd+A7cSQnU3JokUUzV9A5Y4dAHj06EHATTfhfeFwOVjWBqorjSx8fwfH9hcy6Lq2JPSPsHdIQtQLqUmyosLy6lNGgk6MDKXmllFhMJ28n5uzA3FBXnQI92F0p3A9EQr2JC7QE//GXkBdU3U55CZbEqHdf0+ZndiDCMDVR0+CEi7RE6ETCZFnkN3Crg+l1aXsydvDztyd7M7bza7cXSeLrB00B1r5tWJI9BASAhPoFNSJVv6tmvReRf/FcPw4ZStXUrRgAeXrN4DZjGuH9oQ88gg+o0biHB5u7xCbrKpyA/NnbOd4agnDbu5A216y27hoPqyWJGmaNgGYABAdHW2tZhuckkoDafkVpOZZkqCcMg7nlnI4t+yUJfWODhrRAR7EBXlyQctA4oI8ibckQ6Hebk2jePoEkxHyD9VIhCwjRPkpgGWUzNEVgtvoZ5qFdPg7IfKNbHIrzE5XbaomOT/5lITocNFhlOW1ifKOIjE4kevaX0enoE60C2iHh3PzniYyFRdTvmEDZWvXUbZ2rT6VBjjHRBM0cSI+Y0bjGh9v5yibvorSan6dvp28Y6WMuD2Blt1k/yjRvPxnTRKAZbptfnOoSSqpNJBeUGG5lJ/2bwVFFYZT7h/m43ZyJCg+6O/psagAD5yb2qZqJiMUHYWc/afWDeXuB1O1fh/NQd+QsWYiFJoA/nHg2PQHLk1mE4eLDp+SECUXJGM064X2gW6BdArqREKQPkKUEJiAn5uffYNuAMzV1VRs2UrZ2rWUrVtL5c5dYDajubvj0SMJzz4X4NmnN65t28pKqnpSVlTFvGnbKMqu4KI7OxLbqWmP7orm7bxrkpqa0iqjnvTkn5r8pBfqXxeWn5oEuTs7EunvTqS/O92i/S1fexAb5EFsoGfjPcH+bExGKDwC+Yf10aH8FMiz/Ft4BMx/r6rDJ1JPgloO0ROhkPb6SjPnpr1b8wlKKTLKMk4prN6Tt4dyYzkAXs5eJAQmcGOHG+kU1ImOQR0J9QiVD3n0ZfqVe/dSvnYtZWvWUr5lC6qyEhwdce/cmaCJE/Hs0xv3Ll1kVZodlORXMnfqVsoKqxh9b2ei2jWdTVaFOBe12QLgG2AQEKRpWjrwjFLqE1sHdr7KqoxnHAVKKzhzEuTm7ECkvweR/u50jfo7CTqRGAV4ujS9DzWTQT/BPj/l1CQo/5B+fc1EyNkTAuMhrCN0GAsBLSGwlZ4QufvZ7SnUN6UUWWVZHCg8cMpKs/zKfEAvrG4X0I5xrcadHCmK9YnFQWtio4nnSSmFIS2NsjVrKVu7lvL16zEVFgLg2roVfldegWefPnj06IGjV/MsSG8oinIqmPv2VqrKDYy9P5HwVn72DkkIu6nN6rZr6iOQc3Ewu5Sj+WWknT4aVFB+Sl0QnJoEJUb51UiA9H8Dm2ISBKcmQjWToPyUfyZCLl4QEAdhnaHDJRDYUp8yC2gJXiFNvmaoJrMyk1mWyaHCQycvKUUpHCo8dHKEyEFzIN43ngGRA04mRG382uDs2HwLq8/EmJdH2Tq9pqh8zVoMGXrBvlNYGF6DB+N5QR88evWSc9IakIKsMua+vRWj0cy4B7sSEtM0t9cQorYa5VzRPV9tIfl4CQCuTg4nk57Okb6njAJF+nsQ5NVEkyD4OxE6PQnKs4wIqb9X1OmJULyeCCWM/zsJCohvdokQ6MnQsZJjHCr6OxE6WHiQw0WHqaixL1OwezDxfvGMbz2eeN94Wvq1pH1A+2ZfWH0m5rIyyjdvPjlaVJWcDICDjw+evXoRcPttePbpg0ts89n9uzHJTS9l3rStAIx/qBuBETKiJ0StCrfPla0Lt9en5OFsSY6CvVyb7h9ckwGKM6Ao3XJJ0/89MUL0j0TIW58aq5kAnRgV8gxudokQ6IXU6aXpf48MFR0ipTCFw0WHqTT9vVlniEcIrfxanUyEWvm1Is43rlkc/Hq+lMFAxc6derH12rVUbNsORiOaiwvu3bvh2bsPnhf0wa1DBzTHJrLHVxN1PLWYX6dvw8nFkXGTEvEP87R3SELUqyZVuN0rPtDeIdSdUvqmiicToBpJ0IlLSSYnl9Cf4BGoL5tv0RU6Xnbq1JhnULNMhACMZiNpJWlnTIaqzdUn7xfuGU68Xzw9wnroSZFfPPG+8Xi7NN1NKq1FKUXVgQOUr1unF1tv2IC5vBw0DbeEBAJvuUUvtu7WDQc3N3uHK2op42Ah82dsx83TmUse7IpPUPNYeCFEbTTKJKlRMFadNgp0hiTIUHbqYxxdwTdCT4JaDtb/PXmJAp8IcGne0zwGs4GjxUdPSYQOFh7kSPERDOa/69EivCKI942nT4s+tPRrSUvflsT7xePpLP9Drg1lNmM4epTKPXssl71U7tlzstjaJSYGn3Fj8ezTB8+ePXH087NrvOL8pO3NZ+H7O/Dyd2PcpES8/CW5FaImSZLOh1JQnvfPpKfm96XZ/GMUyDNET3iC20Krof9MgjyCmuRJ9edCKUVeZR7pJemkl6ZzrOQYx0r1S3pJOlnlWZiVGQANjQivCFr6tWRA5ICTyVCcb5zUDJ0DZTBQlZJyMhGq3LuHqr37MJdZknhnZ9xat8Z7+DDcExPx7NMH5xYt7Bu0qLPUnbks+nAXviHujJvUFQ8f2WpBiNNJklSTyQjlufpBq6XH9UvJcSjNOu3rbDCeevgsTu5/JzytL9STnppJkE8EOMv/0kA/kuNE0pNemn5KEpRRmnFKrRBAkHsQEV4RdA3tSoRXBLE+sbT005MhdyeZGjgX5spKqvbvP2V0qGr/flS1PiWpeXjg1rYtvpdcgluH9rh16IBry5ayV1ETc3BzNos/2U1gpBdj70/EzUtWZgpxJs0jSaou/zu5+bcEqDwXLKMUp3DzA+8wfRVYVG/wDtU3UjxlFCig2dYDnc5gMpBRlsGxkmOkl546IpRemk5RVdEp9/dy9jqZ/PSL6EeEVwSR3pFEekXSwqsFbk6SXJ4PU2kpVXv3npoQpaSASS/2d/D1xa19e/yvvx639u1xS+iAS0yMFFk3ccnrs1j66R5C43wZc18XXN2bx8eAEOej8f52KAXl+ZaEJ8uS8JxIfiwJ0Ynrq0v++XjNUU96vEL1UZ4WXcErTE+AvEL//tozREaATmNWZnLKc04mPSeSoROjQdnl2SfPJQNwcnAiwiuCCK8IOgR2INI7Uk+EvCKJ9I7Ex8Wn6a5QrCfG/PxTpssq9+zBcOToydudgoNx7dAer2FDcevQAbf2HXCOaCGvezOze+Uxln+dTEQbf0bd1QkXt8b7ESBEfWicvyGzR0PaejAb/nmbs4ee5HiHQWhHaDnUkviclgB5BDb7+p8zqTBWkFOeQ3Z5Ntnl2eRU6F/nlOeQXaH/m1WWdcqKMdCX0Ed6RdIzrCcR3noCdGJEKNg9GEcHGZ2wBqUUxqysU0aHKvfuxZiVdfI+zpGRuHXogN/48ZaEqD1OwcF2jFo0BNuXprHqhwPEdAzkogkdcXKR30kh/kvjTJJaDobIJEsyZEl6TnztKku5z8RgMvyd8NRMfMqzTyY/OeU5lBj+Oerm5uhGsEcwIR4hJAQmMCR6yMkEKMIrghZeLXB1dLXDs2q6TMXFVB9Nw3D0CNVH06g+ehTD0aNUHTqEqaBAv5ODAy5xcXj06KFPl3XogFv7djj6yt5O4lSbfktl/dwU4rsGc+FtCTg6yX8QhaiNxpkkDZhs7wgaDJPZRH5l/slE50wjQDkVOSfPGKvJycGJYPdggj2CifeNp3d475PJULC75V+PYLydvWVaxsqUUpjy86k+cpTqo0cwWBKh6rSjGI4cPbnU/gSn4GCco6PxGjrk74SobVscPGQVnzg7pRTr56Ww+bcjtOkZytCb2uPgKAmSELXVOJOkJq7cUE5hVSEFlQUUVBVQUFlAfmX+yevyKvPILc8luzyb3Mrck0viT3DQHAh0CyTYI5hwz3A6B3cm2COYUI/QU5IfP1c/OYDVhpTZjPH4cT0RStNHgvSv0zAcOaJvxHiCgwPOYWE4x0TjPWIELtFROEdH4xIdjUtkJA6esr+TODdKKVb/cJDty9Lo0Decgde1w8FB/rMjxLmQJMnGzMpMUVXRyWSnZuJz4uvCykLyK/NPfn36EvgTnDQn/Nz8CHALINgjmNb+rfWRH/eQkyNAIR4hBLgF4OQgP9r6oAwGDBkZ+ijQ0dMSobS0k0vrAXB2xiUiAueYaDy6d9cToJhonKOicY6MwEGW2QsrUWbFX98ks3tlBp0HR9LvytYyGizEeZBP0nOglKLcWE5xVfHfyU1V/skk58RIT82vi6qL/jHSc4Knsyd+rqcmPQFuASev83fzP/m1n5ufTHvZgbmsDGNODsacHAzZ2RiPZ1tGhSx1QhkZJ5fUA2hubrhER+MaH4fXwIF6IhQdhXN0DM7hYbK8Xtic2WRm2ef7SF6fRbeLYug9Ll7+bghxnppdklRprKSkuoSS6hKKq4spri4++f2J6852W0l1CaaaB8rW4KA54Ofqh7+rP/5u/rT0a3nya383f/xd/U+OAp34Woqd7UMppSc/2dkYs/UEyJidfTIZqvn1yV2na3Dw8cElOhr3Th3xGT0Kl+iYk9NjTsHB8oEk7MZkNLN41m4Obcmh19h4kkbF2jskIRq1RpkkFVUVUVhVePZEp8qS6BiKT/m+pLrkH0vXT+fm6Ia3izfeLt74uPgQ6BZIrE/sye99XHzwdvHGz+3vhCjALQBvF2+p77EzpRTm4uIzJjuGE19bkiJVUfGPx2tubjgFB+MUEoJru/9v715j5KrLOI5/n7nszl67QHe19AJNaKi1wBYbRJGLcklVpCaaiLcEAyEGDYgSb2+MISa+MKY1IZCmYhM1EAMkraSiLzTxgpqiRa5VK1jaCi0t3V22u3PZnccX5+wy285sl92Zc3bm/D7JyZzbDs/Dmf3vr+fMnFlL15VXkhnoD9b195MdGCAzMKBPj8miNFGa5Iltz3Hg2eNc8ckLGLxuVdwliTS9pgxJtzxxC/uH9lfdlrEMve1BkOnJ9tDb3suyrmUzgs9U0JlarpxvS+t9IYuJl8uU33yTyeHhYBoaYuLY8ZpnfrxQOO05rLOTbBh0OtavD4NQEIamAlBmYIBUd7fOAklTKhUm2X3/Mxzad4KrP3Mh669aHndJIi2hKUPSHYN3kJ/ITwecyqDTkenQH7pFyCcmmBwZYXJomMnhISaHhylPB5/ht0LQ9DREeWiYyZGR4O7qVaS6u6cDTsfg4PT8dAjq7yfTP0C6W58Mk9ZVHJ/g8fv+wWv/GebaW97F2suXxV2SSMtoypB0/XnXx11CIrk7XijMPLMzFXKGhqbDzXQAqgg/5dHR2k9sRqq3l/SSJdNT28qVwXxfsJyq2JY55xwy/f26R5AkXv5kiV/+6GmOHRzlhtvWc8F7BuIuSaSlNGVIkrkrF4uUT56cfRobm7E8OT0/dtp+lZ/kOk06PSPoZPr7aV9zQUXA6ZsRfKamVE+PPvUl8jaNjRTZtfVpThw5yaYvXsTqi5fGXZJIy1FIitnU2RnP5ykXCvj4ePCYz1Mez+OFPOV8Ac+PB4+FYH3twBOEHD85xuTYGJSqfL9dFZbNkurqmjGle3vJLlv21rrOzmB9Tzfpvr6KMzx9pPuWkOrq0qVOkQiMniiwc8teRt/Ic+Mdl7By3dlxlyTSkhSSQj4xgReLeLFIuViEUolysYgXS8H6UjhfCvfJ5/F8gXJ+PHgs5PHxfPBYGWYKM/fzfD782fx0GJqXTCYML52ku7pIdQZBJjMwMDPshMFm5hSGnYp9TDcyFGkKI8fG2bllL+OjJT525yDnrumLuySRljWnkGRmm4CtQBrY7u7fb2hVZzD06GNMHD0ShpgiXgqDzFSgmbEuDDWlyvUzt3mpBOXqN3x8W7JZUu3tWC5HKpfDcu2k2nNYR450dw+2tP+09an23Mz9cxXL7TlSHeFjrh3LdQSPHR26O7NIAg0dGWPnlr2UCpNsvmsD71jdG3dJIi3tjCHJzNLAfcD1wCFgj5ntcvcXGl1cLW/89KcU9u0DM6ytDctmg8fpKYtlg8dUti04a9J2VpX9pn42WJ+asW5qvsa+1UJMRifmRKQxjh8eZefWp8Gdj391A0tX9MRdkkjLm8tf9cuA/e7+EoCZPQxsBmILSdl7H4CSQyrNmd4CU4fzQ6ebAEbDiTIwFk4iIvVXzE/yx1/8m3TGuOnuSzl7mW5rIRKFuYSk5cDBiuVDwHtP3cnMbgduB1i1qrF3ev3TzgMcPzTLR8pFRFpMz9k5Nt89yJJ+3fpCJCp1uz7k7tuAbQAbN26sfve/Ornh1nczWWrIOSIRkUVpyUAHbTld0heJ0lx+4w4DKyuWV4TrYqNTzSIiItJoc/lG1j3AGjNbbWZtwM3ArsaWJSIiIhKvM55JcvcJM/sy8GuCWwA86O7PN7wyERERkRjN6QK3u+8Gdje4FhEREZFFYy6X20REREQSRyFJREREpAqFJBEREZEqFJJEREREqlBIEhEREalCIUlERESkCoUkERERkSrMvf5fs2ZmrwMH6v7EMy0FjjX4v7FYJbl3SHb/Se4dkt2/ek+uJPcfVe/nuXv/qSsbEpKiYGZPufvGuOuIQ5J7h2T3n+TeIdn9q/dk9g7J7j/u3nW5TURERKQKhSQRERGRKpo5JG2Lu4AYJbl3SHb/Se4dkt2/ek+uJPcfa+9N+54kERERkUZq5jNJIiIiIg2z6EKSmT1oZkfN7Lka29ea2Z/NrGBm95yybZOZ/dPM9pvZN6OpuH7m27uZrTSz35nZC2b2vJndFV3V9bOQYx9uT5vZXjN7vPHV1tcCX/d9ZvaIme0zsxfN7H3RVF0/C+z/7vB1/5yZPWRmuWiqro859P5ZM3vGzJ41syfN7JKKba0+5lXtPUFjXs1jH25v5TFvttd9ZGPeogtJwA5g0yzb3wDuBH5QudLM0sB9wIeBdcCnzWxdg2pslB3Mo3dgAviau68DLge+1IS9w/z7n3IX8GKda4rKDubf+1bgCXdfC1xCc/4/2MH8fu+Xh+s3uvt6IA3c3KAaG2UHs/f+MnC1u18E3Ev4Ho2EjHlVeyc5Y16t/qe08pg3W++RjXmLLiS5++8JBsRa24+6+x6gdMqmy4D97v6SuxeBh4HNjau0/ubbu7u/6u5/D+ffJHjBLG9krY2wgGOPma0APgpsb1yFjTPf3s1sCXAV8ONwv6K7DzWw1IZYyLEHMkCHmWWATuB/jamyMebQ+5PufiJc/AuwIpxPwphXtfcEjXm1jn0SxryqvUc95i26kLQAy4GDFcuHaMJfmoUys/OBDcBfYy4laluArwPlmOuI2mrgdeAn4Wn37WbWFXdRUXH3wwRnl14BXgWG3f038VbVULcCvwrnkzbmVfY+LUFj3qn9byE5Y15l75GOea0UkhLPzLqBR4GvuPtI3PVExcxuBI66+9/iriUGGeBS4H533wCcBJruvSnzZWZnEZw9WQ2cC3SZ2efiraoxzOyDBH8svhF3LVGr1XtSxrxT+0/SmFfl2Ec65rVSSDoMrKxYXhGuSwQzyxIMFj9398firidiVwA3mdl/CS45fMjMfhZvSZE5BBxy96l/RT9CMIAkxXXAy+7+uruXgMeA98dcU92Z2cUEl1U2u/vxcHUixrwavSdmzKvRfyLGvBq9RzrmtVJI2gOsMbPVZtZG8ObNXTHXFAkzM4Lrsy+6+w/jridq7v4td1/h7ucTHPffuntLnk04lbu/Bhw0swvDVdcCL8RYUtReAS43s87w9+BamveNrFWZ2SqC8Pd5d/9XxaaWH/Nq9Z6UMa9W/0kY82bpPdIxb9HdTNLMHgKuIfjm3yPAd4AsgLs/YGbvBJ4CegmuxY4C69x9xMw+QnCdNg086O7fi7yBBZhv78DFwB+AZ3nr+vS33X13lPUv1EKOfcVzXAPc4+43Rln7Qi3wdT9I8K+tNuAl4AsVb3hsCgvs/7vApwg+8bQXuM3dC5E3MU9z6H078AngQPgjE1Nf+JmAMa9q72b2AZIx5tU89hXPcQ2tOebN9rofJKIxb9GFJBEREZHFoJUut4mIiIjUjUKSiIiISBUKSSIiIiJVKCSJiIiIVKGQJCIiIlKFQpKIiIhIFQpJIiIiIlUoJImIiIhU8X/a5BOFNzjD6gAAAABJRU5ErkJggg==\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "unbumped_vols = pd.DataFrame({t: {s: v[0].result() for s, v in i.items()} for t, i in call_res.items()})\n", "unbumped_vols.plot(title='MtM Sensitivity to Spot at Different Points in Time', figsize=(10, 5)) " ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "tenor_3m = pd.DataFrame({s: {b: r.result() for b, r in v.items()} for s, v in call_res['3m'].items()}).T\n", "tenor_3m.plot(title='MtM Sensitivity to Volatility Changes in 3 Months', figsize=(10, 5))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we can see above, with vol and/or spot higher, our call option's mark-to-market increases. We can use the prevaling option mtm to secure a rate on the forward - let's look at this next." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 5 - Restructuring into a Forward\n", "\n", "Now that we understand how the mark-to-market of the option may evolve into the future, let's analyze how this option (including the premium payable) can be restructured into a forward once the company get clarity on moving forward with the deal.\n", "\n", "Specifically, let's assume there is clarity in 3 months and look at possible economics of unwinding the option after 3 months and using the mtm to finance a forward for the remaining 9 months.\n", "\n", "As a first step, let's define our `FXForward` and calculate the implied forward rate for the 9-month remaining tenor at different spot levels using the `MarketDataShockBasedScenario`." ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import FXForward\n", "\n", "passed_time, remaining_time = '3m', '9m'\n", "fxforward = FXForward(pair='EURUSD', settlement_date=remaining_time, notional_amount=100e6)\n", "forward_res = {}\n", "with PricingContext(market_data_location='NYC', is_batch=True):\n", " for s in spot_range:\n", " spot_scen = MarketDataShockBasedScenario(shocks={MarketDataPattern('FX', 'USD/EUR'): MarketDataShock(MarketDataShockType.Override, s),})\n", " with spot_scen:\n", " inst = fxforward.resolve(in_place=False)\n", " forward_res[s] = inst\n", "forward_rates = pd.DataFrame({s: {'Forward Rate': forward_res[s].result().forward_rate,\n", " 'Option MTM ($)': call_res[passed_time][s][0].result()} for s in spot_range}).T" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now calculate the adjustment to the forward rate based on the 3m in option mtm we calculated in the previous section (adjusting for the premium) and the above forward rate and examine how the achieved all-in rate on the forward may look like after the restructuring. \n", "\n", "Note two things here: (1) the premium payment for the option has been embedded into the new forward so the full structure is net zero premium (2) the below is an approximation that would work less well for currencies that have high carry, we are working on adding a forward rate solver to give a more precise answer." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "forward_rates['Forward Rate Adjustment'] = -1 * (forward_rates['Option MTM ($)'] - call_option.dollar_price()) / 100e6\n", "forward_rates['All-In Rate'] = forward_rates['Forward Rate'] + forward_rates['Forward Rate Adjustment']\n", "forward_rates[['Forward Rate', 'All-In Rate']].plot(title='Forward Rate Post Option Restructuring', figsize=(10, 6)) \n", "plt.xlabel('Prevailing EURUSD Spot in 3 Months')\n", "plt.ylabel('Estimated Forward Rate')\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we can see above, if the USD weakens vs EUR in 3 months time, the all-in rate will be lower (better) than the equivalent on-market forward - this is due to the EURUSD call option protection. However, if the dollar strenthens, the all-in rate will be higher (worse) than the on-market forward due to the premium payable for the option." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### What's New\n", "* Ability to overwrite market data used in a risk request - example [here](https://nbviewer.jupyter.org/github/goldmansachs/gs-quant/blob/master/gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/0005_market-data.ipynb)\n", "* Restructuring of all notebooks into [documentation](https://nbviewer.jupyter.org/github/goldmansachs/gs-quant/tree/master/gs_quant/documentation/) and [content](https://nbviewer.jupyter.org/github/goldmansachs/gs-quant/tree/master/gs_quant/content/)\n", "* Ability to use polling instead of websockets for batch mode (allowing python 3.6.0 support and cases where websockets may have firewall issues)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/made_with_gs_quant/13-Cyclicals.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Cyclicals\n", "### Summary \n", "\n", "With growing focus on cyclical asset recovery and a still very easy stance from the Fed, the short USD theme has gotten renewed attention from the investor community.\n", "\n", "In the analysis below, we look at G10 currencies' correlation to risk (proxied by SPX) and volatility and find that NOK offers both high exposure to a continued risk-on rally with cheap tails compared to peers.\n", "\n", "P.S. Missed our webcast? [Click here](https://developer.gs.com/docs/gsquant/videos/gs-quant-meets-markets-nov/) for a replay that touches on this theme. Also checkout [this note](https://marquee.gs.com/content/markets/en/2020/11/25/bbe0fca7-c70b-4c92-a0f3-22880574d957.html) from Brian Friedman for specific implementations of this theme.\n", "\n", "The content of this notebook is split into:\n", "* [1 - Let's get started with gs quant](#1---Let's-get-started-with-gs-quant)\n", "* [2 - Find cyclical crosses](#2---Find-cyclical-crosses)\n", "* [3 - Solve for best expression](#3---Solve-for-best-expresion)\n", "* [What's New](#What's-New)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1 - Let's get started with gs quant\n", "Start every session with authenticating with your unique client id and secret. If you don't have a registered app, create one [here](https://marquee.gs.com/s/developer/myapps/register). `run_analytics` scope is required for the functionality covered in this example. Below produced using gs-quant version 0.8.227." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',)) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2 - Find cyclical crosses\n", "\n", "Let's start by looking for crosses that are most sensitive to risk. To do that, we use SPX returns as the risk proxy and look at the YTD relationship of each cross' weekly returns to SPX's. We pull the FX spot data from FRED ([create token here](https://research.stlouisfed.org/docs/api/api_key.html) if you don't have one), downsample data from daily to weekly and calculate returns at a first step. FX spot also available via [gs data catalog](https://marquee.web.gs.com/s/developer/datasets/FXSPOT_PREMIUM/connect)." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
SPXEURJPYAUDGBPCADNOKCHFSEKNZD
date
2020-01-100.94-0.48-1.42-0.86-0.24-0.51-0.76-0.27-1.26-0.51
2020-01-171.97-0.23-0.60-0.38-0.24-0.18-0.310.48-0.11-0.44
2020-01-24-1.03-0.600.78-0.770.32-0.55-1.39-0.30-0.53-0.12
\n", "
" ], "text/plain": [ " SPX EUR JPY AUD GBP CAD NOK CHF SEK NZD\n", "date \n", "2020-01-10 0.94 -0.48 -1.42 -0.86 -0.24 -0.51 -0.76 -0.27 -1.26 -0.51\n", "2020-01-17 1.97 -0.23 -0.60 -0.38 -0.24 -0.18 -0.31 0.48 -0.11 -0.44\n", "2020-01-24 -1.03 -0.60 0.78 -0.77 0.32 -0.55 -1.39 -0.30 -0.53 -0.12" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from gs_quant.data import Dataset\n", "from gs_quant.api.fred.data import FredDataApi\n", "from gs_quant.timeseries import returns\n", "from datetime import date\n", "import pandas as pd\n", "pd.set_option('display.precision', 2)\n", "\n", "data = {'SPX': 'SP500', 'EUR': 'DEXUSEU', 'JPY': 'DEXJPUS', 'AUD': 'DEXUSAL', 'GBP': 'DEXUSUK', 'CAD': 'DEXCAUS',\n", " 'NOK': 'DEXNOUS', 'CHF': 'DEXSZUS', 'SEK': 'DEXSDUS', 'NZD': 'DEXUSNZ'}\n", "pairs = {'EUR': 'EURUSD', 'JPY': 'USDJPY', 'AUD': 'AUDUSD', 'GBP': 'GBPUSD', 'CAD': 'USDCAD', 'NOK': 'USDNOK',\n", " 'CHF': 'USDCHF', 'SEK': 'USDSEK', 'NZD': 'NZDUSD'}\n", "\n", "FRED_API_KEY = 'YOUR_KEY_HERE'\n", "fred_API = FredDataApi(api_key=FRED_API_KEY)\n", "df = pd.concat([Dataset(symbol, fred_API).get_data(date(2020, 1, 1)) for n, symbol in data.items()], axis=1)\n", "df.columns = data.keys()\n", "# let's flip these to look at all relative to USD\n", "df = df.apply(lambda x: 1/x if x.name in ['JPY', 'CAD', 'SEK', 'NOK', 'CHF'] else x)\n", "rets = returns(df.resample('W-FRI').last(), 1).dropna()\n", "rets.head(3) * 100" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With the data in hand, let's look at the relationship of SPX to the other pairs in order of the highest r-squared which represent the strongest relationship to SPX. " ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "from scipy.stats import pearsonr\n", "\n", "crosses = list(pairs.keys())\n", "r2 = pd.Series({i: pearsonr(rets.SPX, rets[i])[0] for i in crosses}, name='R2').sort_values(ascending=False)\n", "r2.plot(kind='bar', title='YTD R^2 with SPX')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we can see above, AUD, NOK and CAD have the top 3 highest correlations to SPX YTD.\n", "\n", "To visually confirm that the relationship is not driven by tail events let's look at a scatter; returns on weeks where SPX has realized greater or less than 10% are denoted in green and red, respectively. The below confirms the relationship still holds especially for the top 3 crosses." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "import seaborn as sns\n", "\n", "rets['spx_thres'] = rets.SPX.apply(lambda x: '>10%' if x >=.1 else '<10%' if x <=-.1 else '')\n", "sns.pairplot(rets, y_vars=['SPX'], x_vars=r2.keys().insert(0, 'SPX'), hue='spx_thres', aspect=.75)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3 - Solve for Best Expression\n", "\n", "Now let's see how far out-of-the-money we need to strike binaries to express a short dollar view and get a 10x payout (note - you can use any ratio below). P.S. You can find more screens like this in our brand new [reports and screens folder](https://nbviewer.jupyter.org/github/goldmansachs/gs-quant/tree/master/gs_quant/content/reports_and_screens)." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets import PricingContext\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.instrument import FXBinary\n", "from gs_quant.risk import FXSpot\n", "\n", "payout_ratio = '10%'\n", "\n", "binaries = Portfolio([FXBinary(pair=cross, expiration_date='3m', option_type='Put' if cross[:3]=='USD' else 'Call',\n", " notional_currency='USD', strike_price=f'p={payout_ratio}', premium=0, name=ccy)\n", " for ccy, cross in pairs.items()])\n", "with PricingContext(): \n", " binaries.resolve()\n", " spot = binaries.calc(FXSpot)" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
option_type strike_price spot R2 realized_vol % otms % otms (vol norm)
pair
USD NOKOptionType.Put8.288.860.8218.186.590.36
AUD USDOptionType.Call0.780.740.8213.025.830.45
NZD USDOptionType.Call0.740.700.7713.035.840.45
USD CADOptionType.Put1.251.300.778.483.930.46
USD SEKOptionType.Put8.118.550.6711.015.190.47
GBP USDOptionType.Call1.411.330.7211.195.370.48
USD JPYOptionType.Put100.04104.360.188.494.130.49
USD CHFOptionType.Put0.870.910.467.434.130.56
EUR USDOptionType.Call1.251.200.537.564.350.58
" ], "text/plain": [ "" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from gs_quant.timeseries import volatility\n", "\n", "bf = binaries.to_frame()\n", "bf.index = bf.index.droplevel(0)\n", "bf = bf[['pair', 'option_type', 'strike_price']]\n", "bf['ccy'] = [x.name for x in bf.index]\n", "bf['spot'] = list(spot)\n", "\n", "vols = pd.Series({ccy: volatility(row, len(row) - 1).iloc[0] for ccy, row in df.iteritems()}, name='realized_vol')\n", "bf = pd.concat([bf.set_index('ccy'), r2, vols], axis=1).dropna()\n", "bf['% otms'] = abs(bf.strike_price / bf.spot - 1) * 100 \n", "bf['% otms (vol norm)'] = bf['% otms'] / bf['realized_vol']\n", "\n", "bf.set_index(['pair']).sort_values(by=['% otms (vol norm)']).style.background_gradient(subset=['% otms (vol norm)'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, let's plot R-sq vs how far out of the money we need to strike to get a 10x payout. The most attractive trades will be in the top left: crosses that have the highest R^2 and are not as far otm (in green). As we can see below, USDNOK stands out as the best 10x binary implementation of this cyclical theme." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "import matplotlib.pyplot as plt\n", "\n", "fig, ax = plt.subplots(figsize=(5, 10))\n", "ax.imshow([[.5, 0], [0, 0]], cmap=plt.cm.YlGn, interpolation='bicubic', extent=[.2, .8, .1, .9])\n", "ax.scatter(bf['% otms (vol norm)'], bf['R2'])\n", "plt.title('% OTMS vs R^2')\n", "plt.xlabel('% OTMS for 10x binary (vol normalized)')\n", "plt.ylabel('YTD R-sq vs SPX')\n", "for i, row in bf.iterrows():\n", " ax.annotate(row.pair, (row['% otms (vol norm)'], row['R2']))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### What's New\n", " * Check out [the content from our recent event](https://nbviewer.jupyter.org/github/goldmansachs/gs-quant/tree/master/gs_quant/content/events/00_gsquant_meets_markets/01_ideas_for_risk_re_rating/) focused on ideas for vaccine risk re-rating\n", " * *New* [Reports and Screens](https://nbviewer.jupyter.org/github/goldmansachs/gs-quant/tree/master/gs_quant/content/reports_and_screens) content - please share your feedback on screens you'd like to see\n", " * *New* index curve shift scenario, [example here](https://nbviewer.jupyter.org/github/goldmansachs/gs-quant/blob/master/gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/tutorials/Scenarios.ipynb)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/made_with_gs_quant/14-Curve Inversions.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Curve Inversions\n", "\n", "Volatility levels have repriced lower on the back of positive vaccine efficacy news and a known US election outcome. Although tail risks are not negligible heading into 2021, we see flat or inverted term structures in several G10 crosses.\n", "\n", "In this note, we look at G10 calendar spread ideas to take advantage of term structure dislocations and find that EURUSD stands out as one of the most attractive curve inversions to fade using volatility swaps.\n", "\n", "The content of the notebook is split into:\n", "* [1 - Let's get started with gs quant](#1---Let's-get-started-with-gs-quant)\n", "* [2 - Flattest calendar spreads](#2---Flattest-calendar-spreads)\n", "* [3 - EURUSD](#3---EURUSD)\n", "* [4 - Structure a Vol Steepener](#4---Structure-a-Vol-Steepener)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1 - Let's get started with gs_quant\n", "\n", "Start every session with authenticating with your unique client id and secret. If you don't have a registered app, create one here. run_analytics scope is required for the functionality covered in this example. Below produced using gs-quant version 0.8.229." ] }, { "cell_type": "code", "execution_count": 56, "metadata": { "pycharm": { "is_executing": true } }, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics', 'read_financial_data')) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2 - Flattest calendar spreads\n", "\n", "Let's start by looking across G10 to compare how steep or flat the volatility spreads of various tenors screen. First, let's grab the data from the [marquee data catalog](). Note, there's also a [premium version available]() with additional currencies and tenors." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "from gs_quant.datetime import business_day_offset\n", "from gs_quant.data import Dataset\n", "from dateutil.relativedelta import relativedelta\n", "from datetime import date\n", "import numpy as np\n", "import pandas as pd\n", "\n", "g10 = ['USDJPY', 'EURUSD', 'AUDUSD', 'GBPUSD', 'USDCAD', 'USDNOK', 'NZDUSD', 'USDSEK', 'USDCHF']\n", "tenors = ['3m', '6m', '1y']\n", "calendars = ['6m-3m', '1y-6m'] \n", "end = business_day_offset(date.today(), -1, roll='forward')\n", "start = business_day_offset(end - relativedelta(years=2), -1, roll='forward')\n", "\n", "vol_data = Dataset('FXIVOL_STANDARD').get_data(start, end, bbid=g10, tenor=tenors, strikeReference='delta', location='NYC')\n", "vols = pd.pivot_table(vol_data, values='impliedVolatility', index=['date'], columns=['bbid', 'tenor'], aggfunc=np.sum) * 100" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With the data in hand, let's look at the spread and the spread ratio. A ratio of less than 1 means the vol curve is inverted because the implied vol for the shorter tenor is higher than the longer tenor. A ratio that's close to 1 is considered flat. We also look at how the spread compares relative to a 2 year history to see how significant the dislocation is in the context of recent history." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/html": [ "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
15 Flattest Calendars
Cross Tenors Vol Far Vol Near Spread Ratio %-ile
6GBPUSD6m-3m8.959.68-0.730.9220.02
7GBPUSD1y-6m8.408.95-0.550.9419.22
11USDNOK1y-6m11.3811.80-0.420.9618.41
10USDNOK6m-3m11.8012.10-0.300.9819.22
15USDSEK1y-6m9.269.37-0.120.9910.16
14USDSEK6m-3m9.379.48-0.110.9916.00
3EURUSD1y-6m6.616.68-0.070.9920.42
2EURUSD6m-3m6.686.75-0.070.9921.03
17USDCHF1y-6m6.586.64-0.060.9912.98
16USDCHF6m-3m6.646.69-0.050.9918.81
13NZDUSD1y-6m9.599.64-0.050.9925.65
5AUDUSD1y-6m9.489.51-0.021.0030.28
9USDCAD1y-6m6.866.840.021.0033.50
4AUDUSD6m-3m9.519.480.031.0033.50
8USDCAD6m-3m6.846.800.041.0132.49
" ], "text/plain": [ "" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from gs_quant.timeseries import last_value, percentiles\n", "pd.set_option('display.precision', 2)\n", "\n", "spreads = pd.DataFrame(columns=['Cross', 'Tenors', 'Vol Far', 'Vol Near', 'Spread', 'Ratio', '%-ile'])\n", "for cross in g10:\n", " for x in calendars:\n", " far, near = vols[cross, x[:2]], vols[cross, x[3:]]\n", " vols[cross, x] = far - near\n", " vols[cross, f'{x}_ratio'] = far / near\n", " spreads = spreads.append({'Cross': cross, 'Tenors': x, 'Vol Far': last_value(far), 'Vol Near': last_value(near), \n", " 'Spread': last_value(vols[cross, x]), 'Ratio': last_value(vols[cross, f'{x}_ratio']),\n", " '%-ile': last_value(percentiles(vols[cross, f'{x}_ratio']))}, ignore_index=True)\n", "vols = vols.sort_index(level=0, axis=1)\n", "spreads.sort_values(by=['Ratio'], ascending=True).head(15).style.set_caption('15 Flattest Calendars').background_gradient(subset=['Ratio', '%-ile'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "EURUSD calendar looks relatively attractive as the market is pricing elevated uncertainty in the short term as Covid cases remains elevated in the US / EU. On the other hand, longer maturity vol is seeing large supply by European corporate players which keeps it suppressed. Let's take a closer look. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3 - EURUSD\n", "\n", "To start, let's visualize 1y-6m EURUSD vol spread vs 6m implied vol. Any value below the green line indicates an inverted curve - including the current point shown in red. Note you can use the function below to examine any cross or spread from the table above." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "import seaborn as sns\n", "import matplotlib.pyplot as plt\n", "def plot_ratio(cross='EURUSD', vol='6m', spread='1y-6m'):\n", " k = sns.scatterplot(data=vols, x=vols[cross, vol], y=vols[cross, f'{spread}'])\n", " k.scatter(last_value(vols[cross, vol]), last_value(vols[cross, f'{spread}']), color='red')\n", " k.axhline(y=0, color='green')\n", " plt.title(f'{cross} {spread}')\n", " plt.xlabel(f'{cross} {vol} Implied Volatility')\n", " plt.ylabel(f'{spread} Implied Vol Spread')\n", " plt.legend(['Flat', cross, 'Current'])\n", " plt.show()" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plot_ratio('EURUSD', '6m', '1y-6m')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we can see, 6m vol is relatively low which offers attractive opportunity to position for vega neutral vol steepeners. Let's put this together next." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4 - Structure a Vol Steepener\n", "\n", "Let's put together a structure to take advantage of the 1y-6m inverted spread as well as the relatively low 6m vol. To do that, we'll look at a long a 6m6m EURUSD forward starting vol swap and short a 6m EURUSD spot starting vol swap with same vega notional." ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "pycharm": { "is_executing": true } }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
asset_classtypeannualization_factorbuy_sellcalculate_mean_returnfirst_fixing_datefixing_frequencyfixing_sourcelast_fixing_datenotional_amountnotional_currencypairsettlement_datestrike_vol
instrument
sell6m (FXVolatilitySwap)AssetClass.FXAssetType.VolatilitySwap252.0BuySell.Sell0.02020-12-17Daily/Business DaysWM Company LDN 4pm Mid2021-06-17-250000.0Currency.EUREUR USD2021-01-210.07
buy6m6m (FXVolatilitySwap)AssetClass.FXAssetType.VolatilitySwap252.0BuySell.Buy0.02021-06-17Daily/Business DaysWM Company LDN 4pm Mid2021-12-17250000.0Currency.EUREUR USD2021-01-210.07
\n", "
" ], "text/plain": [ " asset_class type \\\n", "instrument \n", "sell6m (FXVolatilitySwap) AssetClass.FX AssetType.VolatilitySwap \n", "buy6m6m (FXVolatilitySwap) AssetClass.FX AssetType.VolatilitySwap \n", "\n", " annualization_factor buy_sell \\\n", "instrument \n", "sell6m (FXVolatilitySwap) 252.0 BuySell.Sell \n", "buy6m6m (FXVolatilitySwap) 252.0 BuySell.Buy \n", "\n", " calculate_mean_return first_fixing_date \\\n", "instrument \n", "sell6m (FXVolatilitySwap) 0.0 2020-12-17 \n", "buy6m6m (FXVolatilitySwap) 0.0 2021-06-17 \n", "\n", " fixing_frequency fixing_source \\\n", "instrument \n", "sell6m (FXVolatilitySwap) Daily/Business Days WM Company LDN 4pm Mid \n", "buy6m6m (FXVolatilitySwap) Daily/Business Days WM Company LDN 4pm Mid \n", "\n", " last_fixing_date notional_amount \\\n", "instrument \n", "sell6m (FXVolatilitySwap) 2021-06-17 -250000.0 \n", "buy6m6m (FXVolatilitySwap) 2021-12-17 250000.0 \n", "\n", " notional_currency pair settlement_date \\\n", "instrument \n", "sell6m (FXVolatilitySwap) Currency.EUR EUR USD 2021-01-21 \n", "buy6m6m (FXVolatilitySwap) Currency.EUR EUR USD 2021-01-21 \n", "\n", " strike_vol \n", "instrument \n", "sell6m (FXVolatilitySwap) 0.07 \n", "buy6m6m (FXVolatilitySwap) 0.07 " ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from gs_quant.instrument import FXVolatilitySwap\n", "from gs_quant.markets.portfolio import Portfolio\n", "\n", "vol_swap = FXVolatilitySwap(pair='EURUSD', buy_sell='Sell', last_fixing_date='6m', strike_vol='atm', notional_amount=250000, name='sell6m')\n", "fwd_vol_swap = FXVolatilitySwap(pair='EURUSD', buy_sell='Buy', first_fixing_date='6m', last_fixing_date='1y', strike_vol='atm', notional_amount=250000, name='buy6m6m')\n", "vol_steepener = Portfolio((vol_swap, fwd_vol_swap)).resolve(in_place=False)\n", "\n", "vol_trade = vol_steepener.to_frame()\n", "vol_trade.index = vol_trade.index.droplevel(0)\n", "vol_trade" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sell vol strike: 6.96\n", "buy vol strike: 6.57\n" ] } ], "source": [ "print(f'sell vol strike: {vol_trade.strike_vol.iloc[0] * 100:.2f}')\n", "print(f'buy vol strike: {vol_trade.strike_vol.iloc[1] * 100:.2f}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note you can use gs-quant to analyze the risks of this trade using the risk package (`from gs_quant.risk import FXVega, FXGamma, FXDelta`), analyze how it would have performed historically using the `HistoricalPricingContext` and run scenario analysis (check out [the scenario tutorial](https://developer.gs.com/docs/gsquant/tutorials/Pricing-and-Risk/scenarios/) for details).\n", "\n", "Please reach out with any feedback and thoughts!" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/made_with_gs_quant/15-Hypothetical Lifetime Risk Calculator.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 1, "id": "f2178096", "metadata": {}, "outputs": [], "source": [ "import math\n", "import gs_quant\n", "from gs_quant.session import GsSession, Environment\n", "from gs_quant.data import DataMeasure, DataFrequency\n", "from gs_quant.markets.securities import SecurityMaster, AssetIdentifier\n", "import datetime as dt\n", "import pandas as pd\n" ] }, { "cell_type": "code", "execution_count": 5, "id": "7b210150-1de2-4faa-9fa4-918104cca874", "metadata": {}, "outputs": [], "source": [ "# setup client_id and client_secret for connection\n", "GsSession.use(Environment.PROD, client_id, client_secret, 'read_product_data')" ] }, { "cell_type": "code", "execution_count": 6, "id": "abe77632-5046-418a-9151-797558b7cf12", "metadata": { "scrolled": true }, "outputs": [], "source": [ "def get_vol_and_adv(ticker: str, start_date: dt.date, end_date: dt.date):\n", " \"\"\"\n", " Fetch volatility and average daily trading volume for a stock.\n", "\n", " :param ticker: Stock ticker (e.g., 'AAPL')\n", " :param start_date: Start date for the data (e.g., dt.date(2023, 1, 1))\n", " :param end_date: End date for the data (e.g., dt.date(2023, 10, 1))\n", " :return: Dictionary with volatility and ADV/ADTV\n", " \"\"\"\n", " # Get the asset using the ticker\n", " asset = SecurityMaster.get_asset(ticker, AssetIdentifier.TICKER)\n", "\n", " # Fetch historical close prices\n", " close_prices = asset.get_data_series(\n", " measure=DataMeasure.CLOSE_PRICE,\n", " frequency=DataFrequency.DAILY,\n", " start=start_date,\n", " end=end_date\n", " )\n", "\n", " # Calculate returns and volatility\n", " returns = close_prices.pct_change().dropna()\n", " volatility = returns.std()\n", "\n", " # Fetch historical trading volume\n", " trading_volume = asset.get_data_series(\n", " measure=DataMeasure.VOLUME,\n", " frequency=DataFrequency.DAILY,\n", " start=start_date,\n", " end=end_date\n", " )\n", "\n", " # Calculate average daily trading volume\n", " adv = trading_volume.mean()\n", "\n", " return volatility, adv" ] }, { "cell_type": "code", "execution_count": 7, "id": "5e5497e7", "metadata": {}, "outputs": [], "source": [ "def lifetime_risk_tcm_bps( notional,\n", " ticker,\n", " date,\n", " daily_vol = \"Optional\",\n", " unwind_speed_as_pct_adv = 0.03):\n", " \n", " start_date = date.replace(year=date.year - 1)\n", " \n", " if daily_vol == \"Optional\":\n", " daily_vol, dollar_adv = get_vol_and_adv(ticker, start_date, date)\n", " else:\n", " dollar_adv = get_vol_and_adv(ticker, start_date, date)[1]\n", " days_to_unwind = abs( notional ) / ( unwind_speed_as_pct_adv * dollar_adv )\n", " lifetime_risk = math.sqrt( 1 / 3 ) * notional * daily_vol * math.sqrt( days_to_unwind )\n", " out = ( lifetime_risk / abs( notional ) ) * 10e3\n", " return( out, daily_vol )" ] }, { "cell_type": "code", "execution_count": 8, "id": "aa9d762d", "metadata": {}, "outputs": [], "source": [ "notional = 100e6\n", "# daily_vol = .05\n", "# dollar_adv = 100e6\n", "ticker = \"AAPL\"\n", "date = dt.date(2025, 6, 9)\n" ] }, { "cell_type": "code", "execution_count": 9, "id": "abf6f445", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(1594.1136536329607, 0.020907195709856222)" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "lifetime_risk_tcm_bps( notional, ticker, date )" ] }, { "cell_type": "code", "execution_count": 10, "id": "f25fe349", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "0eff250fd2664b018372f26bb55d482d", "version_major": 2, "version_minor": 0 }, "text/plain": [ "VBox(children=(HTML(value='

Lifetime Risk Calculator

'), Text(value='1,000,000', description='Notional:…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import ipywidgets as widgets\n", "from IPython.display import display, clear_output\n", "import pandas as pd\n", "import datetime as dt\n", "\n", "# Custom formatted text input widget\n", "class FormattedTextInput:\n", " def __init__(self, description, initial_value=0):\n", " self.text_widget = widgets.Text(\n", " value=f\"{initial_value:,}\",\n", " description=description,\n", " style={'description_width': 'initial'},\n", " layout=widgets.Layout(width='300px')\n", " )\n", " self.text_widget.observe(self.on_text_change, names='value')\n", " self._updating = False\n", "\n", " def on_text_change(self, change):\n", " if self._updating:\n", " return\n", "\n", " self._updating = True\n", " current = change['new']\n", "\n", " # Remove existing commas for processing\n", " clean_value = current.replace(',', '')\n", "\n", " try:\n", " # Only format if it's a valid number\n", " num_value = float(clean_value)\n", " formatted = f\"{num_value:,.2f}\"\n", " if formatted != current:\n", " self.text_widget.value = formatted\n", " except ValueError:\n", " pass # Keep the current value if invalid\n", "\n", " self._updating = False\n", "\n", " def get_value(self):\n", " \"\"\"Return the numeric value without commas\"\"\"\n", " try:\n", " return float(self.text_widget.value.replace(',', ''))\n", " except ValueError:\n", " return 0.0\n", "\n", "# Create a class to handle the GUI\n", "class LifetimeRiskCalculator:\n", " def __init__(self):\n", " self.output = widgets.Output()\n", "\n", " # Create input widgets\n", " self.notional_input = FormattedTextInput('Notional:', 1000000)\n", " self.ticker = widgets.Text(\n", " value=\"AAPL\",\n", " description='Ticker:',\n", " style={'description_width': 'initial'},\n", " layout=widgets.Layout(width='300px')\n", " )\n", " self.date = widgets.DatePicker(\n", " value=dt.date(2025, 6, 10),\n", " description='Date:',\n", " style={'description_width': 'initial'},\n", " layout=widgets.Layout(width='300px')\n", " )\n", " self.daily_volatility = FormattedTextInput('Daily Volatility (Optional):', 0.0)\n", " self.unwind_speed_as_pct_adv = FormattedTextInput('Unwind Speed as Pct Adv:', 0.03)\n", "\n", " # Create calculate button\n", " self.calculate_button = widgets.Button(\n", " description='Calculate Risk',\n", " button_style='primary',\n", " layout=widgets.Layout(width='300px')\n", " )\n", " self.calculate_button.on_click(self.on_calculate_clicked)\n", "\n", " # Create reset button\n", " self.reset_button = widgets.Button(\n", " description='Reset',\n", " button_style='warning',\n", " layout=widgets.Layout(width='300px')\n", " )\n", " self.reset_button.on_click(self.on_reset_clicked)\n", "\n", " # Display everything\n", " self.container = widgets.VBox([\n", " widgets.HTML(value=\"

Lifetime Risk Calculator

\"),\n", " self.notional_input.text_widget,\n", " self.ticker,\n", " self.date,\n", " self.daily_volatility.text_widget,\n", " self.unwind_speed_as_pct_adv.text_widget,\n", " widgets.HBox([self.calculate_button, self.reset_button]),\n", " self.output\n", " ])\n", "\n", " def on_calculate_clicked(self, button):\n", " with self.output:\n", " clear_output()\n", "\n", " # Get values from inputs\n", " notional = self.notional_input.get_value()\n", " ticker = self.ticker.value\n", " date = self.date.value\n", " daily_volatility = self.daily_volatility.get_value()\n", " unwind_speed_as_pct_adv = self.unwind_speed_as_pct_adv.get_value()\n", "\n", " # Handle optional daily volatility\n", " daily_volatility = \"Optional\" if daily_volatility == 0.0 else daily_volatility\n", "\n", " # Calculate the result\n", " result, daily_vol = lifetime_risk_tcm_bps(notional, ticker, date, daily_volatility, unwind_speed_as_pct_adv)\n", "\n", " # Display the inputs and result\n", " data = {\n", " 'Parameter': ['Notional', 'Ticker', 'Date', 'Daily Volatility', 'Unwind Speed as Pct Adv', 'Result (TCM BPS)'],\n", " 'Value': [f\"${notional:,.2f}\", ticker, date, f\"{daily_vol:.4f}\", f\"{unwind_speed_as_pct_adv:.2%}\", f\"{result:.2f}\"]\n", " }\n", "\n", " result_df = pd.DataFrame(data)\n", " display(result_df)\n", "\n", " def on_reset_clicked(self, b):\n", " self.notional_input.text_widget.value = \"1,000,000\"\n", " self.ticker.value = \"AAPL\"\n", " self.date.value = dt.date(2025, 6, 10)\n", " self.daily_volatility.text_widget.value = \"0.0\"\n", " self.unwind_speed_as_pct_adv.text_widget.value = \"0.03\"\n", "\n", " with self.output:\n", " clear_output()\n", " print(\"Values reset to defaults.\")\n", "\n", " def display(self):\n", " display(self.container)\n", "\n", "# Create and display the calculator\n", "calculator = LifetimeRiskCalculator()\n", "calculator.display()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.1" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/content/made_with_gs_quant/2-Levels and Tails.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Levels and Tails\n", "\n", "### Summary \n", "\n", "In this note, I lay down a framework for analyzing swap rates levels both in spot and forward space, and rolldown for G3 currencies. I find that:\n", "* In USD swaps, the move over the last 3 months has been an almost parallel rally across the curve in spot space. But when we look at it in forward space, we find some nuances: \n", " * 1y2y and 1y3y rallied the most as the market started pricing a deeper cutting cycle in the next 2 years\n", " * 9y1y and 30y1y rallied more than points around them as investors use those liquid points for long duration hedges \n", "*\tThe curve is now overall very flat, though the belly of the curve has slowly steepened over the last 6 months\n", "*\tFor investors looking to go long rates, the current shape of the curve would favor going long the backend in forward space (e.g. 5y5y) as that’s where there is a slightly favorable rolldown\n", "*\tOn a comparative basis:\n", "\t* the EUR curve is lower and steeper (as front end rates are at the lower bound) making it slightly more attractive from a roll-down perspective (but with less room to rally); and\n", " * the GBP curve lower but flatter making it a slightly more attractive hedge for a broad duration sell-off\n", "\n", "\n", "\n", "The content of this notebook is split into:\n", "* [1 - Let's get started with gs quant](#1---Let's-get-started-with-gs-quant)\n", "* [2 - Spot Space](#2---Spot-Space)\n", "* [3 - Forward Space](#3---Forward-Space)\n", "* [4 - G3 Rolldown](#4---G3-Rolldown)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1 - Let's get started with gs quant\n", "Start every session with authenticating with your unique client id and secret. If you don't have a registered app, create one [here](https://marquee.gs.com/s/developer/myapps/register). `run_analytics` scope is required for the functionality covered in this example. Below produced using gs-quant version 0.8.99." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',)) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2 - Spot Space\n", "\n", "Let's start by looking at USD spot swap rates accross a range of tails and how current levels compare to 3 and 6 months ago." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets import PricingContext\n", "from gs_quant.instrument import IRSwap\n", "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.risk import IRFwdRate\n", "import matplotlib.pyplot as plt\n", "from datetime import timedelta\n", "import datetime as dt\n", "import pandas as pd\n", "\n", "def evaluate_with_context(instruments, measure=IRFwdRate, location='NYC', pricing_date=dt.date.today()):\n", " \"\"\"evaluates a measure on an iterable of instruments in a pricing context (specific data location and date)\"\"\"\n", " with PricingContext(market_data_location=location, pricing_date=pricing_date):\n", " calc = [s.calc(measure) for s in instruments]\n", " return [s.result() for s in calc]\n", "\n", "def levels_and_tails(look_back_mo, fwd_points, fixed_on='Tenors', fix_yrs=1, ccy=Currency.USD, p_r=PayReceive.Receive):\n", " \"\"\"constructs a swap array w/ varying tenors/effective dates, returns frame evaluated for given lookback periods\"\"\"\n", " swaps = [IRSwap(p_r, '{}y'.format(fix_yrs), ccy, effective_date='{}y'.format(f)) for f in fwd_points] \\\n", " if fixed_on=='Tenors' else [IRSwap(p_r, '{}y'.format(f), ccy, effective_date='{}y'.format(fix_yrs)) \n", " for f in fwd_points]\n", " return pd.DataFrame({'{}m ago'.format(m) : \n", " evaluate_with_context(swaps, pricing_date=dt.date.today() + timedelta(weeks=-4*m)) \n", " for m in look_back_mo})*100" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "spot = levels_and_tails((0, 3, 6), range(1, 31), fixed_on='Forwards', fix_yrs=0)\n", "spot.set_index([['{}y'.format(t) for t in spot.index]], inplace=True)\n", "ax = spot.plot(figsize=(10, 6), title='Spot Rates Across USD Tails')\n", "ax.set(ylabel='Rate (%)', xlabel='Underlying Swap')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we can see in the graph, swap rates have come in vs 3m ago but are higher than 6mo for tails longer than 2y. Let's visualize the difference for each tenor using a bar chart." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ax = (spot['0m ago']-spot['3m ago']).plot(figsize=(10, 6), kind='bar', title='USD Spot Rates Now vs 3m Ago')\n", "ax.set(ylabel='Rate (%)', xlabel='Underlying Swap')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we can see, compared to 3 months ago, swap rates have come in by ~18bps for all tails > 2y. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ax = (spot['0m ago']-spot['6m ago']).plot(figsize=(10, 6), kind='bar', title='USD Spot Rates Now vs 6m Ago')\n", "ax.set(ylabel='Rate (%)', xlabel='Underlying Swap')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "But compared to 6 months ago, swap rates are higher by ~5-10bps except at tails <3y where they are lower." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3 - Forward Space\n", "\n", "Let's look at how spot space compared to forward 1y space (we can use the same function to look at any \n", "point)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "forward = levels_and_tails((0, 3, 6), range(0, 31), fixed_on='Tenors', fix_yrs=1)\n", "forward.set_index([['{}y1y'.format(t) for t in forward.index]], inplace=True)\n", "ax = forward.plot(figsize=(10, 6), title='Forward 1y USD Rates')\n", "ax.set(ylabel='Rate (%)', xlabel='Underlying Swap')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We see a similiar comparative shift in the forward space - rates are higher vs 6m but lower vs 3m. We can also visualize where the best rolldown might be - more on this later. Let's first look if the changes are consistent across different tails." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ax = (forward['0m ago']-forward['3m ago']).plot(figsize=(10, 6), kind='bar', title='Forward 1y USD Rates Now vs 3m Ago')\n", "ax.set(ylabel='Rate (%)', xlabel='Underlying Swap')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Unlike in the spot space, 1y forward changes are less consistent across tenors - biggest changes vs 3 months ago are 2y, 9y and 30y tails." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ax = (forward['0m ago']-forward['6m ago']).plot(figsize=(10, 6), kind='bar', title='Forward 1y USD Rates Now vs 6m Ago')\n", "ax.set(ylabel='Rate (%)', xlabel='Underlying Swap')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Compared to 6 months ago, the biggest change is for 4y tails - for longer tails the change falls off almost monotonically.\n", "\n", "Let's now take a look at where rates are for a number of forward points and tails." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from copy import deepcopy\n", "import seaborn as sns\n", "\n", "# note, if using pandas version>1.0.0, please delete result_type='broadcast' and replace last line with\n", "# return df.apply(lambda x: evaluate_with_context(x, IRFwdRate, location), axis=1)\n", "def construct_swap_frame(frame, ccy=Currency.USD, with_rolldown=False, carry_months=3, location='NYC', p_r=PayReceive.Receive):\n", " \"\"\"constructs swap frame with option to subtract carry months from effective date and evaluates with pricing context\"\"\"\n", " df = frame.apply(lambda x: [ IRSwap(p_r, termination_date='{}m'.format(tenor), notional_currency=ccy, \n", " effective_date='{}m'.format(x.name-carry_months if with_rolldown else x.name)) \n", " for tenor in x.index], axis=1, result_type='broadcast')\n", " return df.apply(lambda x: evaluate_with_context(x, IRFwdRate, location))\n", "\n", "def styled_heatmap(frame, title=''):\n", " \"\"\"styles heatmap by adding labels and centering colors around 0\"\"\"\n", " df = deepcopy(frame)*100\n", " df.columns = ['{}m'.format(t) if t < 12 else '{}y'.format(int(t/12)) for t in df.columns]\n", " df.set_index([['{}m'.format(t) if t < 12 else '{}y'.format(int(t/12)) for t in df.index]], inplace=True)\n", " limit = max(abs(min(df.min())), abs(max(df.max())))\n", " plt.subplots(figsize=(10, 6))\n", " ax = sns.heatmap(df, annot=True, fmt='.2f', cmap='coolwarm_r', vmin=-limit, vmax=limit)\n", " ax.set(ylabel='Forward Start', xlabel='Tenor', title=title)\n", " ax.xaxis.tick_top()\n", " ax.xaxis.set_label_position('top')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "swap_df = pd.DataFrame(index=(3, 6, 9, 12, 24, 36, 60, 120), columns=(6, 12, 24, 36, 48, 60, 72, 84, 120, 180, 240)) \n", "usd_spot = construct_swap_frame(swap_df, Currency.USD)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "styled_heatmap(usd_spot, title='USD Rates Across Tenors and Forwards (%)')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Unsurprsingly, rates are lowest at the front of the curve for shorter forward starts and higher at the longer tails. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4 - G3 Rolldown\n", "\n", "Let's now look at where the best 3m rolldown opportunities might be by looking a simple roll to spot scenario - more on advanced carry scenarios in later publications." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "usd_rd = construct_swap_frame(swap_df, Currency.USD, with_rolldown=True, carry_months=3)\n", "styled_heatmap((usd_spot-usd_rd)*100, title='USD 3m Rolldown (bps)')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we can see, in line with the curves above, there is negative at the front of the USD curve especially for tails and forward points <2y. The best positive rolldown here is at 5y starting point across shorter tenors (<2y). Let's now compare this to EUR and GBP." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "eur_spot = construct_swap_frame(swap_df, Currency.EUR)\n", "eur_rd = construct_swap_frame(swap_df, Currency.EUR, with_rolldown=True, carry_months=3)\n", "styled_heatmap((eur_spot-eur_rd)*100, title='EUR 3m Rolldown (bps)')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The big picture for EUR is largely similar but the negative rolldown is smaller at the front of the curve and the best positive rolldown is still 5y starting but for 3y, 4y and 5y tails." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "gbp_spot = construct_swap_frame(swap_df, Currency.GBP)\n", "gbp_rd = construct_swap_frame(swap_df, Currency.GBP, with_rolldown=True, carry_months=3)\n", "styled_heatmap((gbp_spot-gbp_rd)*100, title='GBP 3m Rolldown (bps)')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The negative rolldown for GBP is lower than for EUR but higher than for USD. The best positive rolldown is at 2y forward start for 6m and 1y tenors." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Disclaimer\n", "This website may contain links to websites and the content of third parties (\"Third Party Content\"). We do not monitor, review or update, and do not have any control over, any Third Party Content or third party websites. We make no representation, warranty or guarantee as to the accuracy, completeness, timeliness or reliability of any Third Party Content and are not responsible for any loss or damage of any sort resulting from the use of, or for any failure of, products or services provided at or from a third party resource. If you use these links and the Third Party Content, you acknowledge that you are doing so entirely at your own risk." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/made_with_gs_quant/3-Systematic Selling.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Systematic Selling\n", "\n", "### Summary \n", "\n", "In this note I look at running a simple backtest where I sell a 1m10y straddle each day. I examine premium collected at inception, payout on option expiry and mark-to-market over the life of the trade. \n", "\n", "Look out for future publications where I will build on this strategy with added delta hedging and add analytics for understanding strategy performance!\n", "\n", "The content of this notebook is split into:\n", "* [1 - Let's get started with gs quant](#1---Let's-get-started-with-gs-quant)\n", "* [2 - Create portfolio](#2---Create-portfolio)\n", "* [3 - Evaluate portfolio historically](#3---Evaluate-portfolio-historically)\n", "* [4 - Putting it all together](#4---Putting-it-all-together)\n", "\n", "Note, since this notebook was released, we have built a generic backtesting framework to support the same flexibility with significantly less code. See [how you can achieve the same results using this framework at the end of the notebook](#Generic-backtesting-framework)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1 - Let's get started with gs quant\n", "Start every session with authenticating with your unique client id and secret. If you don't have a registered app, create one [here](https://marquee.gs.com/s/developer/myapps/register). `run_analytics` scope is required for the functionality covered in this example. Below produced using gs-quant version 0.8.102." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',)) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2 - Create portfolio\n", "Let's create a portfolio with a rolling strip of straddles. For each date in our date range (start of 2019 through today), we will construct a 1m10y straddle and include it in our portfolio." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.common import Currency, PayReceive\n", "from gs_quant.instrument import IRSwaption\n", "from gs_quant.markets import HistoricalPricingContext, PricingContext\n", "from datetime import datetime, date\n", "\n", "start_date = date(2020, 12, 1)\n", "end_date = datetime.today().date()\n", "with HistoricalPricingContext(start=start_date, end=end_date, show_progress=True): \n", " f = IRSwaption(PayReceive.Straddle, '10y', Currency.USD, expiration_date='1m', notional_amount=1e8,\n", " buy_sell='Sell').resolve(in_place=False)\n", "\n", "# put resulting swaptions in a portfolio\n", "result = f.result().items()\n", "portfolio = Portfolio([v[1] for v in sorted(result)])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can use `to_frame` to take a look at our portfolio and resolved instrument details as a dataframe. Let's remove any instruments with premium_payment_date larger than today." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "frame = portfolio.to_frame()\n", "frame.index = frame.index.droplevel(0)\n", "\n", "# extend dataframe with trade dates\n", "trade_dates = {value:key for key, value in result}\n", "frame['trade_date'] = frame.apply(lambda x: trade_dates[x.name], axis=1)\n", "\n", "frame = frame[frame.premium_payment_date < datetime.today().date()]\n", "frame.head(3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3 - Evaluate portfolio historically\n", "Let's now evaluate each instrument for the relevant date range (inception to option expiry).\n", "\n", "Note that I use the async flag in pricing context - this is what makes computing 9000 points so fast (~300 instruments * ~30 days)! It sends off a request for 1 month of data for each instrument to be computed in parallel. I keep track of each future in our portfolio frame.\n", "\n", "To learn more about async and other compute controls and how to use them, please see our [pricing context guide](https://developer.gs.com/docs/gsquant/guides/Pricing-and-Risk/pricing-context/). " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "frame['future'] = len(frame) * [None]\n", "\n", "with PricingContext(is_batch=True, show_progress=True):\n", " for inst, row in frame.iterrows():\n", " with HistoricalPricingContext(start=row.trade_date, \n", " end=min(row.expiration_date, datetime.today().date()),\n", " is_async=True):\n", " pv = inst.price()\n", " frame.at[inst, 'future'] = pv" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now grab all the results and organize them into a dataframe. This call will wait for all the results to come back from the pool so it's as fast as the slowest single request out of the ~300 we sent in the previous step." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "\n", "timeseries = pd.concat([pd.Series(row.future.result(), name=row.name) for _, row in frame.iterrows()], axis=1, sort=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4 - Putting it all together\n", "With the portfolio and historical PV's in hand, let's comb through the data to tease out components we want to track: premium collected, payout on expiry and mark-to-mark of the swaption." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "from collections import defaultdict\n", "from gs_quant.datetime import business_day_offset\n", "\n", "def get_p(df, first=True):\n", " p_p = df.apply(lambda series: series.first_valid_index() if first else series.last_valid_index())\n", " g = defaultdict(float)\n", " for i, r in p_p.items():\n", " g[r]+=df[i][r]\n", " return pd.Series(g)\n", "\n", "premia = get_p(timeseries)\n", "payoffs = get_p(timeseries, first=False).reindex(timeseries.index).fillna(0)\n", "mtm = timeseries.fillna(0).sum(axis=1)-payoffs\n", "\n", "overview = pd.concat([premia.cumsum(), payoffs.cumsum(), mtm], axis=1, sort=False)\n", "overview.columns = ['Premium Received at Inception', 'Paid at Expiry', 'Mark to Market']\n", "overview = overview.sort_index()\n", "overview = overview.fillna(method='ffill')[:business_day_offset(datetime.today().date(), -2)]\n", "\n", "overview.plot(figsize=(12, 8), title='Cumulative Payoff, Premium and Mark-to-Market')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "From the above, we can see that premium received at inception varies relative to payout at expiry. We can see when this is the case more clearly by looking at the difference between the two." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "(overview['Paid at Expiry'] - overview['Premium Received at Inception']).plot(figsize=(12, 8), title='Realized Performance')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "But looking at only premium collected and amount paid out doesn't speak to the volatility of this strategy - let's add mark-to-market in to see that. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "p = (overview['Paid at Expiry'] - overview['Premium Received at Inception'] + overview['Mark to Market'])\n", "p.plot(figsize=(12, 8), title='Performance Including Mark to Market')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we can see above, since beginning of 2019 this has mostly been a losing strategy although it has worked well 4Q19.\n", "\n", "Stay tuned to futher editions of gs quant for ways to modify this strategy (delta hedging, for example) and to analyze performance of strategies like this one." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Generic Backtesting Framework" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.backtests.triggers import PeriodicTrigger, PeriodicTriggerRequirements\n", "from gs_quant.backtests.actions import AddTradeAction, HedgeAction\n", "from gs_quant.backtests.generic_engine import GenericEngine\n", "from gs_quant.backtests.strategy import Strategy\n", "\n", "# dates on which actions will be triggered\n", "trig_req = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b')\n", "\n", "# instrument that will be added on AddTradeAction\n", "irswaption = IRSwaption(PayReceive.Straddle, '10y', Currency.USD, expiration_date='1m', notional_amount=1e8,\n", " buy_sell='Sell', name='1m10y')\n", "actions = AddTradeAction(irswaption, 'expiration_date')\n", "\n", "# starting with empty portfolio (first arg to Strategy), apply actions on trig_req\n", "triggers = PeriodicTrigger(trig_req, actions)\n", "strategy = Strategy(None, triggers)\n", "\n", "# run backtest\n", "GE = GenericEngine()\n", "backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# summarized results\n", "backtest.result_summary" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pd.DataFrame({'Original series': overview['Paid at Expiry'] - overview['Premium Received at Inception'], \n", " 'Generic backtester': backtest.result_summary['Cash'].cumsum()}).plot(figsize=(10, 6), \n", " title='Realized performance comparison')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.risk import DollarPrice\n", "\n", "pd.DataFrame({'Original series': overview['Mark to Market'], \n", " 'Generic backtester': backtest.result_summary[DollarPrice]}).plot(figsize=(10, 6),\n", " title='Mark to market comparison')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pd.DataFrame({'Generic backtester': backtest.result_summary['Cash'].cumsum() + backtest.result_summary[DollarPrice],\n", " 'Original series': p}).plot(figsize=(10, 6), title='Backtest comparison')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Disclaimer\n", "This website may contain links to websites and the content of third parties (\"Third Party Content\"). We do not monitor, review or update, and do not have any control over, any Third Party Content or third party websites. We make no representation, warranty or guarantee as to the accuracy, completeness, timeliness or reliability of any Third Party Content and are not responsible for any loss or damage of any sort resulting from the use of, or for any failure of, products or services provided at or from a third party resource. If you use these links and the Third Party Content, you acknowledge that you are doing so entirely at your own risk." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/made_with_gs_quant/4-Delta Hedging.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Delta Hedging\n", "\n", "### Summary \n", "\n", "Being short volatility hasn't been profitable in this period of extreme implied and realized volatility movements but may be an interesting entry point for some. \n", "\n", "In this note I take a further look at this strategy and extend it with delta hedging to understand how it can impact performance.\n", "\n", "Each day I sell a 1m10y straddle (like last time) - but this time I also trade a swap with a matched effective date and termination date to hedge my delta. Each day I unwind the previous day's swap and trade into a new one.\n", "\n", "I examine premium collected at inception, payout on option expiry and mark-to-market over the life of the trade to compare the two strategies.\n", "\n", "Look out for future publications where I will build on this analysis further by adding transaction costs and analyzing performance accross strategies.\n", "\n", "The content of this notebook is split into:\n", "* [1 - Let's get started with gs quant](#1---Let's-get-started-with-gs-quant)\n", "* [2 - Create portfolio](#2---Create-portfolio)\n", "* [3 - Grab the data](#3---Grab-the-data)\n", "* [4 - Putting it all together](#4---Putting-it-all-together)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1 - Let's get started with gs quant\n", "Start every session with authenticating with your unique client id and secret. If you don't have a registered app, create one [here](https://marquee.gs.com/s/developer/myapps/register). `run_analytics` scope is required for the functionality covered in this example. Below produced using gs-quant version 0.8.108." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))\n", "\n", "use_batch = True" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2 - Create portfolio\n", "Just like in our last analysis, let's start by creating a portfolio with a rolling strip of straddles. For each date in our date range (start of 2019 through today), we will construct a 1m10y straddle and include it in our portfolio." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets import HistoricalPricingContext, PricingContext\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.common import Currency, PayReceive\n", "from gs_quant.instrument import IRSwaption\n", "import datetime as dt\n", "\n", "start_date = dt.datetime(2020, 12, 1).date()\n", "end_date = dt.datetime.today().date()\n", "\n", "# create and resolve a new straddle on every day of the pricing context\n", "with HistoricalPricingContext(start=start_date, end=end_date, show_progress=True): \n", " f = IRSwaption(PayReceive.Straddle, '10y', Currency.USD, expiration_date='1m', \n", " notional_amount=1e8, buy_sell='Sell').resolve(in_place=False)\n", "\n", "# put resulting swaptions in a portfolio\n", "result = f.result().items()\n", "portfolio = Portfolio([v[1] for v in sorted(result)])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "I will now convert the portfolio to a dataframe, extend it with trade dates and remove any instruments with a premium payment date after today." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "frame = portfolio.to_frame()\n", "frame.index = frame.index.droplevel(0)\n", "\n", "# extend dataframe with trade dates\n", "trade_dates = {value:key for key, value in result}\n", "frame['trade_date'] = frame.apply(lambda x: trade_dates[x.name], axis=1)\n", "\n", "# filter any swaptions with premium date larger than today\n", "frame = frame[frame.premium_payment_date < dt.datetime.today().date()]\n", "frame.head(2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3 - Grab the Data\n", "\n", "Now the fun part - we need to calculate a lot of datapoints for this backtest. \n", "\n", "For each straddle, we need to define a new swap every day and price it the following day when we unwind it. This means about 36,000 points (~300 instruments * 30 days * 4 measures (swaption price, swaption delta, swap price, swap delta)).\n", "\n", "Like last time I will compute as much as I can asyncrously and keep track of the futures for each measure. \n", "\n", "Introducing a high-level `PricingContext` to batch requests can improve speed as well. Note just using `PricingContext` will improve speed but `batch=True` can add efficiency.\n", "\n", "To learn more about async and other compute controls and how to use them, please see our [pricing context guide](https://developer.gs.com/docs/gsquant/guides/Pricing-and-Risk/pricing-context/). " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "I'll start by getting the prices and delta for the swaptions first." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.risk import IRDeltaParallel\n", "\n", "# insert columns in our frame to track the futures\n", "frame['so_price_f'] = len(frame) * [None]\n", "frame['so_delta_f'] = len(frame) * [None]\n", "\n", "with PricingContext(is_batch=use_batch, show_progress=True):\n", " for inst, row in frame.iterrows():\n", " with HistoricalPricingContext(start=row.trade_date, \n", " end=min(row.expiration_date, dt.datetime.today().date()), \n", " is_async=True):\n", " so_price = inst.price()\n", " so_delta = inst.calc(IRDeltaParallel) \n", "\n", " frame.at[inst, 'so_price_f'] = so_price\n", " frame.at[inst, 'so_delta_f'] = so_delta" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Easy enough. I will now do the same for the swaps which I will use to delta hedge. Note instead of pricing the same already resolved swaption each day, here I create and price a new swap each day which will reflect that's day's ATM rate and matches the effective date and termination date of the corresponding swaption." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import IRSwap\n", "\n", "# insert columns in our frame to track the futures\n", "frame['s_f'] = len(frame) * [None]\n", "frame['s_delta_f'] = len(frame) * [None]\n", "\n", "with PricingContext(is_batch=use_batch, show_progress=True):\n", " for inst, row in frame.iterrows():\n", " swap = IRSwap(PayReceive.Pay, row.termination_date, Currency.USD, \n", " effective_date=row.effective_date, fixed_rate='ATMF', notional_amount=1e8)\n", "\n", " with HistoricalPricingContext(start=row.trade_date, \n", " end=min(row.expiration_date, dt.datetime.today().date()), \n", " is_async=True):\n", " # track the resolved swap - we will need to price it when we unwind following day\n", " s = swap.resolve(in_place=False)\n", " s_delta = swap.calc(IRDeltaParallel)\n", "\n", " frame.at[inst, 's_f'] = s\n", " frame.at[inst, 's_delta_f'] = s_delta" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the above request, we created a new resolved swaption for each day but we still need to price it the following day when we unwind it. In the below, I collect the resolved swaps from the previous request and price lagged 1 day - that is, the following day." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets import PricingContext\n", "import pandas as pd\n", "\n", "swaps = pd.concat([pd.Series(row.s_f.result(), name=row.name) for _, row in frame.iterrows()], \n", " axis=1, sort=True).shift(periods=1)\n", "g = {}\n", "\n", "with PricingContext(is_batch=use_batch, show_progress=True):\n", " for date, row in swaps.iterrows():\n", " with PricingContext(date, is_async=True):\n", " prices = {k: p if isinstance(p, float) else p.price() for k, p in row.iteritems()}\n", " g[date] = prices\n", " \n", "swap_prices = pd.DataFrame(g).T" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, let's collect all the points and do some arithmetic to create a timeseries for each swaption. I will create two frames - one for the simple vol selling strategy and one taking into account the changing delta hedge." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.timeseries import *\n", "\n", "not_delta_hedged = []\n", "delta_hedged = []\n", "\n", "for inst, row in frame.iterrows():\n", " # collect all the results\n", " total_result = pd.concat([row.so_price_f.result(), row.so_delta_f.result(), \n", " pd.Series({k: v.result() for k, v in swap_prices[inst].iteritems() \n", " if not isinstance(v, float)}), \n", " row.s_delta_f.result()], axis=1, sort=True)\n", " total_result.columns = ['swaption_prices', 'swaption_delta', 'swap_bought_prices', 'swap_sold_delta']\n", " \n", " # today's hedge notional will be the ratio of prior day's swaption/swap delta ratio - that's\n", " # how much of the swap we bought to hedge so will use it to scale unwind PV of the swap today\n", " total_result['hedge_notional'] = -(total_result.swaption_delta/total_result.swap_sold_delta).shift(periods=1)\n", " total_result = total_result.fillna(0)\n", " \n", " # scale the umwind PV of prior day's swap hedge\n", " total_result['swap_pos'] = total_result['hedge_notional'] * total_result['swap_bought_prices']\n", " \n", " # add to swaption price to get total performance cutting off last time due to the lag\n", " swaption_pl = diff(total_result['swaption_prices'], 1).fillna(0)\n", " total_result['total_pv'] = swaption_pl + total_result['swap_pos']\n", " \n", " not_delta_hedged.append(pd.Series(swaption_pl[:-1], name=inst))\n", " delta_hedged.append(pd.Series(total_result['total_pv'][:-1], name=inst))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4 - Putting it all together\n", "Now, let's combine all the results to look at the impact delta hedging makes on the strategy. Unsurprisingly, the delta hedged version provides protection to tail events like March 2020." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "not_dh = pd.concat(not_delta_hedged, axis=1, sort=True).fillna(0).sum(axis=1).cumsum()\n", "dh = pd.concat(delta_hedged, axis=1, sort=True).fillna(0).sum(axis=1).cumsum()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "comp = pd.DataFrame([dh, not_dh]).T\n", "comp.columns = ['Delta Hedged', 'Not Delta Hedged']\n", "comp.plot(figsize=(10, 6), title='Hedged vs Not Hedged')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that this backtesting doesn't include transaction costs and the implementation is different from how one might hedge in practice (unwinding and trading a new swap every day) but is economically equivalent to layering the hedges (and is cleaner from a calculation perspective).\n", "\n", "Look out for future publications for added transaction costs and ways to quantitatively compare these strategies." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Generic Backtesting Framework" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.backtests.triggers import PeriodicTrigger, PeriodicTriggerRequirements\n", "from gs_quant.backtests.actions import AddTradeAction, HedgeAction\n", "from gs_quant.backtests.generic_engine import GenericEngine\n", "from gs_quant.backtests.strategy import Strategy\n", "from gs_quant.common import AggregationLevel\n", "from gs_quant.risk import IRDelta, Price\n", "\n", "# dates on which actions will be triggered\n", "trig_req = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b')\n", "\n", "# instrument that will be added on AddTradeAction\n", "irswaption = IRSwaption('Straddle', '10y', 'USD', expiration_date='1m', notional_amount=1e8,\n", " buy_sell='Sell', name='1m10y')\n", "swap_hedge = IRSwap(PayReceive.Pay, '10y', 'USD', fixed_rate='ATMF', notional_amount=1e8, name='10y_swap')\n", "action_trade = AddTradeAction(irswaption, 'expiration_date')\n", "action_hedge = HedgeAction(IRDelta(aggregation_level=AggregationLevel.Type, currency='local'), swap_hedge, '1b')\n", "\n", "# starting with empty portfolio (first arg to Strategy), apply actions on trig_req\n", "triggers = PeriodicTrigger(trig_req, [action_trade, action_hedge])\n", "strategy = Strategy(None, triggers)\n", "\n", "# run backtest\n", "GE = GenericEngine()\n", "backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note, these strategies won't be exactly the same since the original series. This is because the original series is matching each swaption with a swap and holding both positions while resizing the swap each day. In the generic backtester snippet above, we passed '1b' for hedge holding period. This means that the delta from ALL swaption positions on a given date will be hedged with a single swap which will be replaced each day. Note if instead of '1b', we put '10y' for example (to match swaption tail), the swap hedge will be 'layered' - that is, we will add positive or negative notional each day but not unwind the existing swap hedges." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pd.DataFrame({'Generic backtester': backtest.result_summary['Cash'].cumsum() + backtest.result_summary[Price],\n", " 'Original series': dh}).plot(figsize=(10, 6), title='Delta hedged backtest comparison')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/made_with_gs_quant/5-What's New.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## What's New\n", "\n", "### Summary \n", "\n", "Brought to you by popular demand, this edition of `made-with-gs-quant` will showcase some of our newest features and capabilities. Going forward, look out for a `What's New` section in `made_with_gs_quant` that will highlight new additions.\n", "\n", "The content of this notebook is split into:\n", "* [1 - Let's get started with gs quant](#1---Let's-get-started-with-gs-quant)\n", "* [2 - Cash flows](#2---Cash-flows)\n", "* [3 - Portfolios](#3---Portfolios)\n", "* [4 - Scenarios](#4---Scenarios)\n", "* [5 - Compute](#4---Compute)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1 - Let's get started with gs quant\n", "Start every session with authenticating with your unique client id and secret. If you don't have a registered app, create one [here](https://marquee.gs.com/s/developer/myapps/register). `run_analytics` scope is required for the functionality covered in this example. Below produced using gs-quant version 0.8.115." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2 - Cash flows\n", "\n", "Let's start with something small but impactful - ability to model future cashflows. You can simply use `CashFlows` as any of the other risk measures to get a dataframe of future cashflows." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.risk import Cashflows\n", "from gs_quant.common import Currency, PayReceive\n", "from gs_quant.instrument import IRSwaption\n", "\n", "swaption = IRSwaption(PayReceive.Receive, '10y', Currency.USD, expiration_date='6m')\n", "cf = swaption.calc(Cashflows)\n", "cf.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3 - Portfolios\n", "I have used `Portfolio` wrappers in my previous analyses, so this one should be somewhat familiar. `Portfolio` makes working with many positions easier and allows risk to be viewed both at position level and aggregate level (more portfolio features like saving in the pipeline..look for it in `What's New` section in the new editions!)\n", "\n", "Let's first define a portfolio - I will define a book of swaptions with varying strikes." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.common import Currency, PayReceive\n", "from gs_quant.instrument import IRSwaption\n", "\n", "swaptions = [IRSwaption(PayReceive.Receive, '10y', Currency.USD, expiration_date='6m', strike=strike, \n", " name=strike) for strike in ('atm', 'atm-10', 'atm-25', 'atm-50')]\n", "\n", "# create portfolio from list of instruments\n", "portfolio = Portfolio(swaptions, name='swaption_port')\n", "\n", "# resolve all the instruments in place\n", "portfolio.resolve()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we can work with this portfolio the same way we do with an instrument - that is, we can price it or calculate any of the available risk measures on it. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.risk import DollarPrice, IRVegaParallel\n", "risks = portfolio.calc((DollarPrice, IRVegaParallel))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With `risks` in hand, we can examine these risks on an instrument level or aggregate level:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# all risks for a specific (in this case, first) instrument in the portfolio\n", "print(risks[portfolio[0]])\n", "\n", "# single risk for a specific (in this case, first) instrument in the portfolio\n", "print(risks[portfolio[0]][DollarPrice])\n", "# or...\n", "print(risks[DollarPrice][portfolio[0]])\n", "\n", "# aggregate risk for the portfolio\n", "print(risks[IRVegaParallel].aggregate())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, we can convert our portfolio to a dataframe where each row will represent an instrument. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "frame = portfolio.to_frame()\n", "frame.head(2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can append `RiskResults` as columns to this dataframe as we compute additional measures." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "\n", "rks = pd.DataFrame(risks) \n", "rks.index = frame.index\n", "\n", "pd.concat([frame, rks], axis=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note the last two columns in the frame are the risks we appended!\n", "\n", "Want to see more portfolio examples? Refer to portfolio [examples here](https://nbviewer.jupyter.org/github/goldmansachs/gs-quant/tree/master/gs_quant/examples/02_portfolio/) and [previous editions](https://nbviewer.jupyter.org/github/goldmansachs/gs-quant/tree/master/gs_quant/made_with_gs_quant/) of `made_with_gs_quant`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4 - Scenarios\n", "\n", "The team has also invested to build our scenario capabilities. In particular we: (1) simplified `MarketDataShock` scenario (2) added aging or `rollfwd` scenario (3) introduced composite scenarios. Let's look at an example of each." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### Simple Market Shock\n", "\n", "`MarketDataShockBasedScenario` is designed to allow users to shock any kind of market data. In this example we will shock the spot by 1bp. Note while before you had to specify each of the underlying curves used to build spot curve, you can now simply indicate `('IR', 'USD')`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.risk import MarketDataShockBasedScenario, MarketDataPattern, MarketDataShock, MarketDataShockType\n", "\n", "ir_spot_scenario = MarketDataShockBasedScenario(\n", " shocks={\n", " MarketDataPattern('IR', 'USD'): MarketDataShock(MarketDataShockType.Absolute, 1e-4), # 1 bp shock\n", " MarketDataPattern('IR Reset', 'USD-3m'): MarketDataShock(shock_type=MarketDataShockType.Absolute, value=1e-4),\n", " })\n", "\n", "with ir_spot_scenario:\n", " spot_shock = portfolio.price()\n", " \n", "print('Base price: {:,.2f}'.format(risks[DollarPrice].aggregate())) # or portfolio.price().aggregate()\n", "print('Spot shock price: {:,.2f}'.format(spot_shock.aggregate()))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### RollFwd\n", "\n", "The `RollFwd` Scenario can be used to move the pricing date into the future. In order to do this we do two things: (1) project the market data forward in time (either keeping spot rates constant or fwd rates constant - more on this in a bit) to the new pricing date and (2) lifecycle the trades so that any fixings or expiries that happen between today’s date and the target future date have been handled." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets import PricingContext\n", "from gs_quant.risk import RollFwd\n", "\n", "rollfwd_scenario = RollFwd(date='1m', holiday_calendar='NYC')\n", "with rollfwd_scenario:\n", " rollfwd_shock = portfolio.price()\n", " \n", "print('Base price: {:,.2f}'.format(risks[DollarPrice].aggregate())) # portfolio.price().aggregate()\n", "print('Rolled Forward price: {:,.2f}'.format(rollfwd_shock.aggregate()))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### Combined Scenarios\n", "\n", "We can now combine these two scenarios to produce a 'rollfwd then bump' scenario (note all permutations aren't supported yet but are underway):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "with rollfwd_scenario, ir_spot_scenario:\n", " rollfwd_then_bump_shock = portfolio.price()\n", " \n", "print('Base price: {:,.2f}'.format(risks[DollarPrice].aggregate())) # portfolio.price().aggregate()\n", "print('Rollfwd then bump price: {:,.2f}'.format(rollfwd_then_bump_shock.aggregate()))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "More on scenario examples in the [examples folder](https://nbviewer.jupyter.org/github/goldmansachs/gs-quant/tree/master/gs_quant/examples/04_scenario/) in the gs-quant repo." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 5 - Compute\n", "\n", "Finally to optimize both speed and performance we have improved our async and batch framework.\n", "\n", "In a nutshell, `async` allows the requests to be processed in parallel and `batch` allows them to be grouped together and avoid timeouts for particularly large requests.\n", "\n", "Additionally, multiple contexts can be controlled by a 'master' `PricingContext` to batch requests together in order to also avoid timeouts and sending too many requests. For example of this please see [`4-Delta Hedging`](https://nbviewer.jupyter.org/github/goldmansachs/gs-quant/blob/master/gs_quant/made_with_gs_quant/4-Delta%20Hedging.ipynb) notebook. \n", "\n", "For now, let's look at a simpler example using the scenarios we discussed in the previous section. Here we will look at how the price of our portfolio evolved as we move it 21 days forward in time both keeping spot rates constant (`roll_to_fwds=False`) and fwd rates constant (`roll_to_fwds=True`). Let's use a `PricingContext` with batch mode to organize these requests." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets import PricingContext\n", "from gs_quant.datetime import business_day_offset\n", "import datetime as dt\n", "\n", "roll_to_spot = []\n", "roll_to_fwd = []\n", "\n", "\n", "date = business_day_offset(dt.date.today(), -1, roll='preceding')\n", "\n", "\n", "with PricingContext(pricing_date=date, market_data_location='NYC', is_batch=True, visible_to_gs=True):\n", " for bus_days in range(21):\n", " with PricingContext(is_async=True), RollFwd(date=f'{bus_days}b', holiday_calendar='NYC', realise_fwd=True):\n", " roll_to_spot.append(portfolio.price())\n", " with PricingContext(is_async=True), RollFwd(date=f'{bus_days}b', holiday_calendar='NYC', realise_fwd=False):\n", " roll_to_fwd.append(portfolio.price())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's now visualize the result:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import numpy as np\n", "\n", "pd.Series([r.aggregate() for r in roll_to_spot], name='Roll to Spot', dtype=np.dtype(float)).plot(figsize=(10, 6))\n", "pd.Series([r.aggregate() for r in roll_to_fwd], name='Roll to Fwd', dtype=np.dtype(float)).plot(figsize=(10, 6))\n", "plt.xlabel('Business Days from Pricing Date')\n", "plt.ylabel('PV')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "That's all for this edition - please keep sharing your feedback on what would make `gs-quant` even more useful for you as well as suggestions for `made_with_gs_quant` you would like to see in the future.\n", "\n", "Happy coding!" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/made_with_gs_quant/6-Evolving Correlations.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Evolving Correlations\n", "\n", "### Summary \n", "In this note, I team up with Jack DiMassimo from MarketStrats and use `gs_quant` to analyze changes to the correlation structure among assets. We explore where there may be opportunities to trade assets that have strayed from their traditional relationships because of temporary factors. \n", "\n", "To do this, we perform a PCA and rolling PCA analysis on the correlation matrix of 19 assets sourced from FRED covering global Equities, Rates, FX, Commodities, and Vol. \n", "\n", "We find that:\n", "* Most of the explanatory power is dominated by the first two components where the first component aligns with risk / growth and the second with real rates. Deeper dive in [3 - Interpreting the top 2](#3---Interpreting-the-top-2)\n", "* US equities, unsurprisingly, have realized much higher than the model predicts\n", "* JPY, WTI and US 2y have realized much lower than predicted suggesting other factors at play and potential opportunity for entry\n", "\n", "In this analysis we leverage gs-quant FRED connector to pull data as well as a number of [timeseries functions](https://developer.gs.com/docs/gsquant/api/timeseries.html) to transform it.\n", "\n", "Outside of this analysis, we hope you can keep using this notebook as a way to monitor changing drivers of risk and screen to mispriced trading opportunities. Please reach out with your feedback and suggestions!\n", "\n", "Finally, check out the [What's New](#What's-New) section for the latest gs-quant additions.\n", "\n", "The content of this notebook is split into:\n", "* [1 - Grab the data](#1---Grab-the-data)\n", "* [2 - Model explanatory factors through time](#2---Model-explanatory-factors-through-time)\n", "* [3 - Interpreting the top 2](#3---Interpreting-the-top-2)\n", "* [4 - Actual vs Predicted](#4---Actual-vs-Predicted)\n", "* [What's New](#What's-New)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1 - Grab the data\n", "\n", "First - the data. For this analysis I will use public data from [FRED](https://fred.stlouisfed.org/) but of course you can substitute or enhance this piece with your own data. The goal is to get a deep history accross multiple categories or sources we're interested in investigating.\n", "\n", "I will use gs_quant FRED connector to pull in the data. For this, you will need to use your own FRED API key. Takes under a minute to create one [here](https://research.stlouisfed.org/useraccount/login/secure/). \n", "\n", "Note in the dictionary, I include a type to indicate whether to difference (b-a) or calculate returns (b/a-1) for each series and an asset class label that I will use later in the analysis." ] }, { "cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
WILSNDXN225EURJPYAUDGBPCADUSD 2yUSD 10y2Y10Y10Y3MUSD 5y RYUSD 10y RYBreakevensGoldWTIHYVIX
date
2003-01-0629.491061.488713.331.04690.0083990.57621.60800.6411081.844.092.252.881.792.461.63356.1132.298.4124.91
2003-01-0729.291071.858656.501.04190.0083210.57531.60550.6414371.774.042.272.851.762.421.62348.7131.208.2625.13
\n", "
" ], "text/plain": [ " WILS NDX N225 EUR JPY AUD GBP \\\n", "date \n", "2003-01-06 29.49 1061.48 8713.33 1.0469 0.008399 0.5762 1.6080 \n", "2003-01-07 29.29 1071.85 8656.50 1.0419 0.008321 0.5753 1.6055 \n", "\n", " CAD USD 2y USD 10y 2Y10Y 10Y3M USD 5y RY USD 10y RY \\\n", "date \n", "2003-01-06 0.641108 1.84 4.09 2.25 2.88 1.79 2.46 \n", "2003-01-07 0.641437 1.77 4.04 2.27 2.85 1.76 2.42 \n", "\n", " Breakevens Gold WTI HY VIX \n", "date \n", "2003-01-06 1.63 356.1 132.29 8.41 24.91 \n", "2003-01-07 1.62 348.7 131.20 8.26 25.13 " ] }, "execution_count": 51, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import pandas as pd\n", "from gs_quant.data import Dataset\n", "from gs_quant.api.fred.data import FredDataApi\n", "\n", "FRED_API_KEY = ''\n", "fred_API = FredDataApi(api_key=FRED_API_KEY)\n", "\n", "data = {\n", " 'WILS': {'symbol': 'WILL5000INDFC', 'type': 'r', 'asset class': 'Equities'},\n", " 'NDX': {'symbol': 'NASDAQ100', 'type': 'r', 'asset class': 'Equities'},\n", " 'N225': {'symbol': 'NIKKEI225', 'type': 'r', 'asset class': 'Equities'},\n", "\n", " 'EUR': {'symbol': 'DEXUSEU', 'type': 'r', 'asset class': 'FX'},\n", " 'JPY': {'symbol': 'DEXJPUS', 'type': 'r', 'asset class': 'FX'},\n", " 'AUD': {'symbol': 'DEXUSAL', 'type': 'r', 'asset class': 'FX'},\n", " 'GBP': {'symbol': 'DEXUSUK', 'type': 'r', 'asset class': 'FX'},\n", " 'CAD': {'symbol': 'DEXCAUS', 'type': 'r', 'asset class': 'FX'},\n", "\n", " 'USD 2y': {'symbol': 'DGS2', 'type': 'd', 'asset class': 'Rates'},\n", " 'USD 10y': {'symbol': 'DGS10', 'type': 'd', 'asset class': 'Rates'},\n", " '2Y10Y': {'symbol': 'T10Y2Y', 'type': 'd', 'asset class': 'Rates'},\n", " '10Y3M': {'symbol': 'T10Y3M', 'type': 'd', 'asset class': 'Rates'},\n", " \n", " 'USD 5y RY': {'symbol': 'DFII5', 'type': 'd', 'asset class': 'Rates'},\n", " 'USD 10y RY': {'symbol': 'DFII10', 'type': 'd', 'asset class': 'Rates'},\n", " 'Breakevens': {'symbol': 'T10YIE', 'type': 'd', 'asset class': 'Rates'},\n", " \n", " 'Gold': {'symbol': 'GOLDAMGBD228NLBM', 'type': 'r', 'asset class': 'Commodities'},\n", " 'WTI': {'symbol': 'DCOILWTICO', 'type': 'r', 'asset class': 'Commodities'}, \n", " \n", " 'HY': {'symbol': 'BAMLH0A0HYM2', 'type': 'd', 'asset class': 'Credit'},\n", " 'VIX': {'symbol': 'VIXCLS', 'type': 'd', 'asset class': 'Vol'}\n", "}\n", "\n", "# build dataframe with all series \n", "df = pd.concat([Dataset(v['symbol'], fred_API).get_data() for _, v in data.items()], axis=1)\n", "df.columns = data.keys()\n", "\n", "# filter for when data is available accross all and clean\n", "frame = df[df.index > '2003-01-01']\n", "frame = frame.fillna(method='ffill').dropna()\n", "\n", "# flip JPY and CAD for more intuitive direction in later analysis\n", "frame['JPY'] = 1/frame['JPY']\n", "frame['CAD'] = 1/frame['CAD']\n", "frame['WTI'] = 100 + frame['WTI'] # negative prices!\n", "frame.head(2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With all of our data in hand, let's standardize it by differencing levels and calculating returns. Note, I use a 2 day window to smooth the effect of comparing accross different markets. " ] }, { "cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [], "source": [ "from gs_quant.timeseries import returns, diff\n", "\n", "frame_t = frame.copy()\n", "for key, v in data.items():\n", " frame_t[key] = returns(frame_t[key], 2) if v['type'] == 'r' else diff(frame_t[key], 2)\n", "frame_t.dropna(inplace=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2 - Model explanatory factors through time\n", "\n", "Now the fun part. Let's run a 3-factor PCA for a 42 day rolling period and record how much variance is explained by each component over our time frame. I standardize the values (0 mean, unit variance) to bring them to the same scale. Note, you can play around with number of components and time frame without having to change any code!" ] }, { "cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [], "source": [ "from sklearn.decomposition import PCA\n", "from collections import defaultdict\n", "\n", "period = 42\n", "components = 3\n", "f_loadings = defaultdict(list)\n", "\n", "for start in range(len(frame_t) - period):\n", " d = frame_t.iloc[start:start + period]\n", " d = (d - d.mean()) / d.std()\n", " model = PCA(n_components=components)\n", " model.fit(d)\n", " for i, c in enumerate(model.explained_variance_ratio_):\n", " f_loadings[i].append(c)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's now look at variance explained by the top 3 components over time. I smooth the chart by looking at a 21d rolling average. \n", "\n", "Looking at the graph below, we can see the top 3 components explain ~65-85% of the variance over time. Additionally, variance explained most recently is among the highest over the period. This is not too surprising as with the high volatility we've seen correlations among many assets in question have increased significantly. \n", "\n", "As Jack pointed our in [his note](https://marquee.gs.com/s/content/articles/9ea25a7d-2560-4658-8122-2a5b1eff7d28), this is on par with the spikes observed during the GFC, Brexit, the August 2015 CNH devaluation, and the Eurozone Debt crisis. Notably, more equity specific crisis like the December 2018 SPX crash and the February 2018 'Volmageddon' lead to correlation spikes, but not to the extent observed during the more global events. " ] }, { "cell_type": "code", "execution_count": 54, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 54, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "import matplotlib.pyplot as plt\n", "\n", "factors = pd.DataFrame(f_loadings)\n", "factors.index = frame_t.index[period:]\n", "factors.sum(axis=1).rolling(21).mean().plot(title='Variance Explained by Top 3 Components')" ] }, { "cell_type": "code", "execution_count": 55, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Text(0.5, 1.0, 'Contribution to variance from each component')" ] }, "execution_count": 55, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plt.stackplot(factors.index, factors.to_numpy().T)\n", "plt.title('Contribution to variance from each component')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "From the chart above, we can see the top 2 components dominate the varience explained so let's dig into them in a bit more detail to understand what they intuitively represent." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3 - Interpreting the top 2\n", "\n", "In order to understand the most important factors that drive the structure of the correlation matrix we plot the loadings for each asset class against the first two PCs in the PCA over the full sample. This can allow us to visually identify how each asset class loads on the 2 principal components. If an asset or asset class has a high absolute value on one and not the other, we can say this is primary driver of this component." ] }, { "cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Text(0, 0.5, 'PC2')" ] }, "execution_count": 56, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "full_model = PCA(n_components=components)\n", "full_data = (frame_t - frame_t.mean()) / frame_t.std()\n", "full_model.fit(full_data)\n", "\n", "components_full = pd.DataFrame(full_model.components_, columns=frame_t.columns)\n", "\n", "fig = plt.figure(figsize=(8, 8))\n", "color_map = {\n", " 'Equities': '#20396D',\n", " 'FX': '#68A2BF',\n", " 'Rates': '#CD252B',\n", " 'Commodities': '#E3E311',\n", " 'Credit': '#E3E000',\n", " 'Vol': '#67E311',\n", "}\n", "\n", "for i, row in components_full.iteritems():\n", " plt.scatter(row[0], row[1], color=color_map[data[row.name]['asset class']], label=row.name)\n", " plt.text(row[0], row[1], row.name, fontsize=9)\n", "\n", "plt.axhline(0, color='grey')\n", "plt.axvline(0, color='grey')\n", "\n", "plt.xlabel('PC1')\n", "plt.ylabel('PC2')" ] }, { "cell_type": "code", "execution_count": 57, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 57, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "PC1, PC2 = components_full.iloc[0].abs(), components_full.iloc[1].abs()\n", "(PC1/PC2).sort_values().plot(kind='barh', title='PC1/PC2')" ] }, { "cell_type": "code", "execution_count": 58, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 58, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "(PC2/PC1).sort_values().plot(kind='barh', title='PC2/PC1')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Looking at the above, some themes pop out for each of the components:\n", " \n", "* PC1 = Growth/Risk (PC1): The primary driver of Equities, Credit, Vol\n", "* PC2 = Real Rates (PC2): The primary driver of Gold, Real Rates, and FX (though FX is more mixed)\n", "\n", "Let's now see how the loadings over the whole period compare to the most recent 42 day window to tell us how risk might have evolved." ] }, { "cell_type": "code", "execution_count": 59, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Text(0, 0.5, 'PC2')" ] }, "execution_count": 59, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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8+fPp6amxix929XVxYkTJzh8+DAnTpygs7OTvr4+tE79pOSePXu4/fbbzc9PnjzJ0qVLAXj77bepq6tj6dKlLF68mD179gBQW1vLlVdeaT63c+fOUV97xYoVlJWV8Z3vfMc8prXma1/7GpdddhmrVq2iq6sr5X0SqZOue/obgJ8rpW4DjgP/AaCUqgbqtdYr09QuIYQ4ZxaLZUgJ4eHpgYklhIdXD5zImYD77ruPF198kVmzZhGNRs0MhZycHN58800AmpqaWLlyJQsWLOC8884b8vWbN2/mjTfe4OTJk+ax3/zmNwSDQd566y22bdvGY489xoYNGyasD+LcpOVKX2vdqbVerrVeEP+3K368abSAr7XeIzn6QoipykgPLC8vZ86cOcyfP5/q6mpcLhehUIi2tjYaGxtpaGigqakJn89HKBRKeTsKCwvZtWsXPT095OTkjLqZVXV1NXfddRcvvfTSiOdmzpw54tiePXtYtWrwz/NnPvMZ9u7di9frZfHixeY53/72t3nmmWdS2BMxXrLCRAghJplRQriiooLa2lrmzp1LZWUlhYWF9PX10draytGjR2loaKC5uRm/3z+i1sR4bNmyhQMHDnDxxRdz9dVXc+zYsVHPq6mp4dSpU0m9ZldXF0VFRQB4PB7z8wULFnDgwAG01uzYsYPrrrvunNsvzp0kmAohRJqdKT0wEAiYewScKT2w89WdND3+BMeOHKFlIEjnqzspuWYV/f395lbEs2fP5umnnwZg69atPPLII2zdunVEe06cOMGMGTOSantxcbGZueD3+80BwJo1a6ivr6e7u5tPfOITZhtEeknQF0KIDDM8PTBxEJCYHmhUDxx463e0fft7qL4+5tjs/KP1FP/61iMAvNnazKJFiwA4dOgQ559/PgBVVVXEYrER37ulpYVNmzbx/PPPJ9XWK664gldeeYXVq1fz+uuvc8UVVwBw2WWX8c1vfpPW1lYeffTRc35PRGpI0BdCiAxmlAZOLCGcmB7o8/lofO4FosUerKEC7AMhvhwsY03DIXJvuoma5VeyefNmADZu3MiBAwcoKCjAYrHw1FNPAYMLDZcuXUooFMJms7F+/XpzcJDoK1/5Cvv27WNgYIADBw7wy1/+khUrVrBz504uu+wyXC4X27ZtM8///Oc/z3PPPcdFF100OW+WGJMEfSGEmEISqweWlJSg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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "fig = plt.figure(figsize=(8, 8))\n", "\n", "components_recent = pd.DataFrame(model.components_, columns=df.columns, index=factors.columns)\n", "\n", "for (_, col1), (_, col2) in zip(components_full.iteritems(), components_recent.iteritems()):\n", " plt.scatter(col2[0], col2[1], color=color_map[data[col2.name]['asset class']], label=col2.name)\n", " plt.scatter(col1[0], col1[1], color='grey')\n", " plt.plot((col2[0], col1[0]), (col2[1], col1[1]), color='grey', alpha=0.3)\n", " plt.text(col2[0], col2[1], col1.name, fontsize=9)\n", "\n", "plt.axhline(0, color='grey')\n", "plt.axvline(0, color='grey')\n", "\n", "plt.xlabel('PC1')\n", "plt.ylabel('PC2')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we can see in the above, this has been much rotation though equities, vol and credit are still primary drivers of PC1 and rates of PC2. One of the main changes is FX shifting towards PC1, reflect more about growth in the recent past." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4 - Actual vs Predicted\n", "\n", "Now let's leverage this PCA model to predict where levels should be as explained by the top three factors (PC1 and PC2 discussed above as the primary two). Let's transform back the levels and plot actual vs predicted to see where there may be interesting disclocations." ] }, { "cell_type": "code", "execution_count": 60, "metadata": {}, "outputs": [], "source": [ "pca_returns = pd.DataFrame(full_model.transform(full_data), index=full_data.index)\n", "res = pca_returns.dot(components_full) * frame_t.std() + frame_t.mean()" ] }, { "cell_type": "code", "execution_count": 61, "metadata": {}, "outputs": [], "source": [ "def transform(tr_df, since):\n", " t_d = tr_df.copy()[since:]\n", " for key, v in t_d.items():\n", " t_d[key] = (1 + t_d[key] / 100).cumprod() if data[key]['type'] == 'r' else t_d[key].cumsum()\n", " return t_d" ] }, { "cell_type": "code", "execution_count": 62, "metadata": {}, "outputs": [], "source": [ "since = '2020-01-01'\n", "predicted = transform(res, since)\n", "actual = transform(frame_t, since)" ] }, { "cell_type": "code", "execution_count": 63, "metadata": {}, "outputs": [], "source": [ "def compare_plot(asset, actual, predicted):\n", " plt.figure(figsize=(6, 4))\n", " actual[asset].plot(label='actual')\n", " predicted[asset].plot(label='predicted')\n", " plt.title(asset)\n", " plt.legend()\n", " plt.show()" ] }, { "cell_type": "code", "execution_count": 64, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYcAAAEdCAYAAADn46tbAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8li6FKAAAgAElEQVR4nOydd3hVRd6A37mppPeQkEoJSYCEEiD0IiIooiLYQLHBqljXuu76rbvrqru6rmJjUVFs2BVRRAVp0nsoCZBGCAnpPaTP98fchARSbnph3ue5z713zsycOZB7fmd+VUgp0Wg0Go2mNobOXoBGo9Fouh5aOGg0Go3mIrRw0Gg0Gs1FaOGg0Wg0movQwkGj0Wg0F6GFg0aj0WguQgsHjUaj0VyEFg4aTQsQQiQKIc4JIQprvd4QQjwrhPi4nv5SCNHf+HmTEKLEOCZTCPGNEMKr469Co2kYLRw0mpZztZTSrtbr/maMvV9KaQf0B+yAl9tniRpNy9DCQaPpRKSUucB3wNDOXotGUxstHDSaTkQI4QrMAWI7ey0aTW20cNBoWs53QojcWq9FzRi7VAiRB2QCbsAD7bNEjaZlaOGg0bSca6WUTrVe7wAVgEXtTkKI6u/ltZoflFI6AmGAM+DTISvWaExECweNpm1JAgIuaAsEKoEzF3aWUh4GngPeFEKIdl+dRmMiWjhoNG3LOmCgEOJWIYSFEMIFeB74SkpZ0cCYlYAHMLujFqnRNIUWDhpNy1lzQZzDt1LKdOBK4A9AOnAEyAPubWgSKWUZsBR4piMWrdGYgtDFfjQajUZzIXrnoNFoNJqL0MJBo9FoNBehhYNGo9FoLkILB41Go9FchBYOGo1Go7kI885eQFvg5uYmAwICOnsZGo1G063Yt29fppTSvb5jPUI4BAQEsHfv3s5ehkaj0XQrhBCnGjqm1UoajUajuQgtHDQajUZzEVo4aDQajeYieoTNQaPR9EzKy8tJTk6mpKSks5fSrbG2tsbHxwcLC4umOxvRwkGj0XRZkpOTsbe3JyAgAJ3RvGVIKcnKyiI5OZnAwECTx2m1kkaj6bKUlJTg6uqqBUMrEELg6ura7N2XFg4aTQ+gvLKKtzfFseVERmcvpc3RgqH1tOTfUKuVNJpuTmZhKUs+2c+uhGwi/J2ZGFRvTJOmndm0aROWlpaMHTu2xXPY2dlRWFjYhqtqOVo4aDTdmKMpeSxauZesojLCfBw5kpJHRWUV5mZaKdDRbNq0CTs7u1YJh66E/gvSaLopUkoe/eIQVRK+vncsd44LpKS8ipPpXePJs6dw7bXXMmLECAYNGsTy5csBWLduHcOHDyc8PJzLLruMxMREli1bxn//+1+GDh3K1q1buf322/nqq69q5rGzswOgsLCQyy67jOHDhzNkyBBWr17dKdfVFHrnoNF0Uw4lZXFb5isM7+dFcJ/LsLE0AyAqOZcQL4dOXl3PYcWKFbi4uHDu3DlGjhzJNddcw6JFi9iyZQuBgYFkZ2fj4uLCPffcg52dHY899hgA7733Xr3zWVtb8+233+Lg4EBmZiaRkZHMnj27y9lWtHDQaLojVZXIb+/lFvONVKW7gJQEuNpib23OwdN53DiysxfY9vxtzVGOpeS36Zyh3g789epBjfZZunQp3377LQCnT59m+fLlTJw4scYt1MXFpVnnlFLy9NNPs2XLFgwGA2fOnCEtLY3evXu37CLaCa1W0mi6G1VVlH93P8NyfyHZegCGc9lQmIbBIAj3cSIqObezV9hj2LRpE+vXr2fHjh0cOnSIYcOGER4ebtJTvrm5OVVVVYASCGVlZQB88sknZGRksG/fPg4ePIinp2eXDPJrcucghFgBzALSpZSD6zkugNeAK4Fi4HYp5X7jsRnGY2bAu1LKF43tLsDnQACQCNwgpcwRQlwOvAhYAmXA41LK31p5jRpNz0FK+PERLKI+5dWKOcycPA/W3QxpR8G+N2E+jizfEk9JeSXWFmadvdo2pakn/PYgLy8PZ2dnbGxsiImJYefOnZSWlrJ582YSEhLqqJXs7e3Jzz+/swkICGDfvn3ccMMNrF69mvLy8po5PTw8sLCwYOPGjZw61WBi1E7FlJ3DB8CMRo7PBAYYX4uBtwGEEGbAm8bjocDNQohQ45ingA1SygHABuN3gEzgainlEGAh8FFzLkaj6dFICT89Afs+4MteN7DWZSFBQ0arY+nHAAjzcaKiSnIstW3VL5cqM2bMoKKigrCwMJ555hkiIyNxd3dn+fLlzJkzh/DwcG688UYArr76ar799tsag/SiRYvYvHkzo0aNYteuXdja2gIwf/589u7dS0REBJ988gnBwcGdeYkN0uTOQUq5RQgR0EiXa4APpZQS2CmEcBJCeKF2BbFSyngAIcRnxr7HjO+TjeNXApuAJ6WUB2rNexSwFkJYSSlLm3FNGk3PQ0r4+c+wezlZYYt5fPck/m+WP8LWFex6Q5oSDkN9nQA4dDqX4X7OnbniHoGVlRU//fRTvcdmzpxZ53tQUBBRUVF12nbu3Fnz+YUXXgDAzc2NHTt21DtnV4lxgLaxOfQBTtf6nmxsa6gdwFNKmQpgfPeoZ97rgQNaMGgueaSE9X+FnW/CqD/wutlCLM3MuG6Y8efkGQrpRwHo7WiNh70VUcl5nbhgTU+gLYRDfZYZ2Uh70xMKMQj4F/CHRvosFkLsFULszcjoeSkDNJoaNj4P216DiDspmfY83xw4w4zBvXG2tVTHPUIh4zhUVQIQ7uvEIW2U1rSSthAOyYBvre8+QEoj7QBpRtUTxvf06k5CCB/gW+A2KWVcQyeVUi6XUkZIKSPc3XW6AE0PZfO/Ycu/YdgCuPI//HT0LPklFdw0qtZPy3MQVJRAdjwA4T6OxGcUkV9S3kmL1vQE2kI4fA/cJhSRQJ5RVbQHGCCECBRCWAI3GftWj1lo/LwQWA0ghHACfgT+JKXc1gZr02i6L6e2w8Z/QthNcPVSMBj4bPdpAlxtGNPX9Xw/D6OfR9oRQBmlAY5o1ZKmFTQpHIQQq4AdwEAhRLIQ4i4hxD1CiHuMXdYC8UAs8A5wH4CUsgK4H/gZiAa+kFIeNY55EbhcCHESqHZfxdi/P/CMEOKg8VWfPUKj6fmc2q7eZ/4LDGbEZxSyKyGbG0b61vWzdx8IwlBjlA7zcQTgkBYOmlZgirfSzU0cl8CSBo6tRQmPC9uzgMvqaX8OeK6pNWk0lwSph8A5EHqpncDne05jbhDMHeFTt59FL3DpV+PO6mRjiZudJUnZxR29Yk0PQkdIazRdldSD4D0UgLKKKr7al8xlIR542Ftf3NczVAXCGfFy7EVq3rmOWqnGRKqT76WkpDB37txG+7766qsUFzdPwG/atIlZs2a1eH210cJBo+mKFGdDbhJ4hQOwITqNrKIybhrlV39/j0GQkwhlRYByaT2b1/VSMvREKisrmz3G29u7TsbW+miJcGhLtHDQaLoiqYfUu5faOazacxpvR2smDmjAM88zFJCQHqOGOVqTkqt3Dq0lMTGR4OBgFi5cSFhYGHPnzqW4uJiAgAD+/ve/M378eL788kvi4uKYMWMGI0aMYMKECcTEqP+HhIQExowZw8iRI3nmmWfqzDt4sMpGVFlZyWOPPcaQIUMICwvj9ddfZ+nSpaSkpDBlyhSmTJkCwC+//MKYMWMYPnw48+bNqwmYW7duHcHBwYwfP55vvvmmza5dCweNpitSIxzCOZ1dzNaTGcyL8MXM0EDCt2qPJWMwnJdjL/JLKigqreiAxfZsjh8/zuLFi4mKisLBwYG33noLUKm3f//9d2666SYWL17M66+/zr59+3j55Ze57777AHjooYe499572bNnT4NZV5cvX05CQgIHDhwgKiqK+fPn8+CDD+Lt7c3GjRvZuHEjmZmZPPfcc6xfv579+/cTERHBK6+8QklJCYsWLWLNmjVs3bqVs2fPttl165TdGk1XJPUQOPmBjQtf/n4cgBtG+jbc3zkQLGxqPJa8HJVd4mx+Cf3c7dp9uR3CT0/B2cNtO2fvITDzxUa7+Pr6Mm7cOAAWLFjA0qVLAWpyKhUWFrJ9+3bmzZtXM6a0VCV22LZtG19//TUAt956K08++eRF869fv5577rkHc3N1O64vBfjOnTs5duxYzTrKysoYM2YMMTExBAYGMmDAgJr1VRckai1aOGg0XZHUg+AVTmWV5Iu9yUwKcqePU6+G+xsM4B5c47HU2ygcUnN7kHDoJC5Mz139vTqRXlVVFU5OThw8eNCk8RcipTSpz+WXX86qVavqtB88eLDdigRp4aDRdDVK8lS089D5bD6Rztn8Ep6dHdr0OI9QOPkLAN6OSpD0KI+lJp7w24ukpCR27NjBmDFjWLVqFePHj+fAgfM5Qh0cHAgMDOTLL79k3rx5SCmJiooiPDyccePG8dlnn7FgwQI++eSTeuefPn06y5YtY/LkyZibm9dJAV5QUICbmxuRkZEsWbKE2NhY+vfvT3FxMcnJyQQHB5OQkEBcXBz9+vW7SHi0Bm1z0Gi6GqnGzJ5eQ1m1+zRudlZcFuLZ9DiPEChKh6JMPBys1FTaY6nVhISEsHLlSsLCwsjOzubee++9qM8nn3zCe++9R3h4OIMGDaqpC/3aa6/x5ptvMnLkSPLy6g9KvPvuu/Hz8yMsLIzw8HA+/fRTABYvXszMmTOZMmUK7u7ufPDBB9x8882EhYURGRlJTEwM1tbWLF++nKuuuorx48fj7+/fZtctVAxb9yYiIkLu3bu3s5eh0bQN29+AX/5Mxj1HiFx6mEUT+vLUTBNy/sdugI/nwMIfIHACI/7xK9MH9eaFOUPaf83tRHR0NCEhIZ12/sTERGbNmsWRI0c6bQ1tRX3/lkKIfVLKiPr6652DRtPVSD0IDn3YftZAZZXk6nAv08Z5GiulpUcD4OVkzdmepFbSdChaOGg0XY3UQ+A1lITMIoTAdIOynSf0cj5vlHbopdVKrSQgIKBH7BpaghYOGk1XorQQMk+CVzjxGUX0ceplei1oIZRR2rhz8Hay1sJB02K0cNBouhKxvwIS/EaTkFlE3+a6oXqEKOEgJb0drck7V05xWfcOhOsJdtHOpiX/hlo4aDRdiYOfgkMfpP944jMK6etm27zxHiFQmgf5Z2oC4brz7sHa2pqsrCwtIFqBlJKsrCysretJ2NgITcY5CCFWALOAdCnl4HqOC+A14EqgGLhdSrnfeGyG8ZgZ8K6U8kVjuwvwORAAJAI3SClzjMf+BNwFVAIPSil/btYVaTTdlYKzELsexj9CRlEFRWWV9HVvrnCoTqMRjZfjcKB7B8L5+PiQnJyMLgXcOqytrfHx8Wm6Yy1MCYL7AHgD+LCB4zOBAcbXaOBtYLQQwgx4E1XMJxnYI4T4Xkp5DHgK2CClfFEI8ZTx+5NCiFBUxbhBgDewXggRJKVsftpDjaa7EfU5yCoIv4W4DJVdNbC5Owd3o8tr+jG8gscC3TsQzsLCgsDAwM5exiVJk2olKeUWILuRLtcAH0rFTsDJWBd6FBArpYyXUpYBnxn7Vo9Zafy8Eri2VvtnUspSKWUCqrrcqCavorKsyS5ICXEbYdUtcLjxVLkaTYcjJRz4BHwjwa0/CZlKODTb5mDjAvZekB6Np4Mxv1I3VitpOo+2SJ/RBzhd63uysa2+9tHGz57GOtNIKVNrlQLtA+ysZ67GSY9RCcc860kxICUc/wm2vgxn9oHBHE6sU9Wzgq8y6QI1mnbnzH7IPK5qRQPxGYVYWxjwcmienhgwGqWPYW1hhqutJSlaOGhaQFsYpOvL+iQbaW/JXBd3FGKxEGKvEGJvFcCnN0BB2vkOVZVqh/D2OPjsZijKgKtegUdPqOpaX94Bib83sRyNpoM4+AmY94JBahOdkFlEgKsthoZSdDeGRyhkHIeqSmPRn+6rVtJ0Hm0hHJKB2rmEfYCURtoB0oyqJ4zv6U3MdRFSyuVSyggpZYTBrT8UZykhUJIH+z+CN0bC13dBVTlcuwwe2A8j7wJbV7jlS3AOgE9vUj8ijaYzqSyHI19ByNVg7QhAfGZR843R1XiEQEUJ5CTi5ahjHTQtoy2Ew/fAbUIRCeQZVUZ7gAFCiEAhhCXK0Px9rTELjZ8XAqtrtd8khLASQgSijNy7m1yBhQ1c/67amr8cBN/fD5a2MG8l3LcLht4MZhbn+9u6wq3fqs9bXmrVxWs0reb0LvVQEzobgPLKKpKyi+nr1kIPIw9j/pz0Y8Za0lo4aJqPKa6sq4DJgJsQIhn4K2ABIKVcBqxFubHGolxZ7zAeqxBC3A/8jHJlXSGlrK6A/iLwhRDiLiAJmGccc1QI8QVwDKgAlpjiqVRZJZX9YNYrEL0GIu+D/tNUxGhDOPaBEQth1zKY9iw4Ns/NS6NpM2I3KFtY4CQAkrKLqaySzfdUqsY9GBCQdpTejiE1gXA2ljpDv8Z0mvxrkVLe3MRxCSxp4NhalPC4sD0LuKyBMf8E/tnUumpz/GwB2UVluETcCRF3mj5w9B9g51uwezlc/vfmnFKjaTti1ysvJWsHABKq3VhbqlaytFW7h9O78R68AFCBcN011kHTOfSICOlKKXl7U2zzBzr5Qeg1sPcDldNGo+loCtLgbBT0n1rTFJ+p/habHR1dG79IOL2b3naWgHZn1TSfHiEcnG0sWbnjFCm5LfDKGHO/SjdwsP4qTRpNuxL3m3rvP62mKSGzCBdbS5xsLFs+r98YKCvAvyIBgDMt+W1oLml6hHDwcLACCa+tP1nvcSkliZlFfL4niSe/imJHXNb5gz4R4DNKqZeqdCC2poOJ2wC2HuB5viBPXEZR63YNoHYOgEfuAQwCkrOLWzef5pKjR1ioLM0MzI3054PtCSya2Jd+7rbEZRSyMz6bXQnZ7E7IIi2/FAAzg2DtkVRWLxl3Pvp0zBL4ciGc/BUGzujEK9FcSpzNKcIzdgMi6AownH9OS8gsYnKQe+smd/QFhz6YJ+/CyzGIJC0cNM2kRwgHgCVT+vH5niTu/GAPxWUVZBaqlBoe9laM7uvK6EAXIvu6YGVuxjVvbuPuD/fy3ZJxOFhbKE8nW3c4+LEWDpoO49nln7LsXDYl/pOpjoPOKy4no6C0+WkzLkQItXs4tR0/58Wc0sJB00x6hFoJwNXOikenD0QImDDAnRfnDGHjY5PZ9fRlvH7zMBZE+tPfwx5fFxvemj+cpKxiHlp1QLnBmllA2I1wfB0UZTV9Mo2mlRSWVjAgfydVUvCnQ25UVUkKSspZ9NFehIBRgS6tP4nfGChIZahDPqe1cNA0kx6zcwC4c3wgd45vOoNjZF9X/jp7EM98d4TP95zmltF+MHQ+7HgDDn8Bkfd2wGo1lzJx6YVMMosi0SqIb4+X4rDmKAdP53I0JZ/XbhrGCH/n1p/EaHcYLo7zdmE/CksrsLPqUT95TTvSY3YOzWXBaD9CvRz4cEeiKiTiGQrew1RmTI2mnYlLzSRMxOE8aBrzR/uxcscpos8WsGzBCGaHe7fNSTxCwcqBoFJVA1nvHjTN4ZIVDkIIFkT6E3O2gP1Juapx2AJIO6wKvGs07Uhx4l4sRSX2A8by7OxBLJnSj4/uHMW0UM+2O4nBDHxH4Zl7AEAbpTXN4pIVDgDXDPXGzsqcT3adUg2DrwczKzjwcavmjc8o5KHPDnDre7u44X87uOP93RSUlLfBijU9BevUvQCY+0diYWbg8SuCGd3Xte1P5BeJdc4JHCkkKUsLB43pXNLCwdbKnOuG9eGHqFRyisqglzOEzILDX0JpQYvm/OlwKrPf2MZvMenkl1QgpWTj8Qy+2pfcxqvXdGc886NIs/ABW7f2PZHfGAAmWMfpnYOmWVzSwgFgfqQfZRVVfL3fePMefa/KkPndfapQkImkF5TwtzVHufeT/fTzsGPdwxNZvWQcX94zlqG+Tny44xRVVbpIugZKyioIqYgm0yms/U9mrCkdbpOl3Vk1zeKSFw7BvR2I8Hfmk11J6ubtO1Il4Yv+Hra91ujY8soq1h9LY9GHexnzwm+8vy2RWyP9+eIPkfRx6lXT7/axASRkFrHlpC6SroEzidG4iXzKvUe2/8l6OYOZFQFWBdogrWkWJvm1CSFmAK+hUm+/K6V88YLjzsAKoB9QAtwppTxiPPYQsAhV5e0dKeWrxvZwYBlgByQC86WU+UIIC+BdYLhxfR9KKV9o5XU2yoJIfx7+/CAf7zrFbWMCVL6lM/tgw9/AtR+49le7CeMrLT2N6ITTJKWkYlZehMFyAndPmMENEb71Zr68cogXz/0YzYc7TjF5oMfFC9BcUuSf2AaAXf+x7X8yIcDeEy9DHsk5KhW4WUuqy2kuOUyp52AGvAlcjqrUtkcI8b2U8litbk8DB6WU1wkhgo39LxNCDEYJhlFAGbBOCPGjlPIkSgA8JqXcLIS4E3gceAZV28FKSjlECGEDHBNCrJJSJrbVRV/IrDAv1hxK4dnvj9LbwZrpg3rD7DdUberPF1zU39P4KhXWmFmbcUvlJoSnG7iH1O1YWQFZsViWF3PLKF9e3xjLqawi/F1bmTdH060xJO+mQPbCJ2h4x5zQrjdu53Ior5Sk5p3Dx9mmY86r6daYsnMYBcRKKeMBhBCfAdegCvJUEwq8ACCljBFCBAghPIEQYKeUstg4djNwHfBvYCCwxTj+V1RRoGdQNaNthRDmQC+UUMlvzUU2hbmZgddvGcbN7+zigVUH+HRRJOE+juTN/ZL0vavZfrqE35MryK7shYeHJ1OHDmD6sAG4OjlAST58cZuqPpd7CjwHwantkLRTlSCtVDmdFo/+I2+JCD7ccYpnZoW25+VoujguOYeIMQtipFUrsq42B3tPHAqiAeXOqoWDxhRMEQ59gNO1vicDoy/ocwiYA/wuhBgF+KPqPx8B/imEcAXOoSrG7TWOOQLMRpUIncf52tFfoYRPKmADPCKlzG7eZTUfG0tzViyM4Pq3tzNv2XbO244DcbG15NrRfXh8hA+h3g51B1o7wPwvYc1D50uOWtiqbK+j/6CERcJW7Ha9whu9F/DgzquorJLcOS4QP1f9I73kKC3AuzSe/Y7zO+6c9l5Yl6jnsKSsYsb2q79beWUVK35PwNLcwB3jms40oOnZmCIc6lNQXuh28yLwmhDiIHAYOABUSCmjhRD/Qu0MClFCpMI45k5gqRDi/1C1o8uM7aOASsAbcAa2CiHWV+9cahYlxGJgMYCfn58Jl9E0rnZWfHz3aD7dlYSVuRlONhb4udgwrr8bluaN2O7NLOCaN1WchLUTeIXVrVk95AYwmDFj/0pWuBeydPcwrtjhx6RQf+6eEMgIf2dEYyVNNT2GytN7MaOKc54jOu6kdp4YSvOwNZQ36M4am17AH784RFRyHlbmBm4a6UcvS7OOW6Omy2GKcEjm/FM9qB1BSu0OUsp8jLWjhbrLJRhfSCnfA94zHnveOB9SyhhgurE9CLjKON0twDopZTmQLoTYBkQAdYSDlHI5sBwgIiKizXxEfZxteGJGcPMHCgH96618qtIxz3oVzCwYv+ddxpt/R5W5GSfjfDh4PJA9ToMYFDGZsWMmYG7VS7nQ5iQqo7i1E7gNUCmYDZe8c1m3J//kdpwBq8BRHXdS+94AhDmeu8idtapKsmJbAv/++Ti2lmbcPT6Qd39PYHtcJpeFtGG09qVIRSkgwLyD1IdtjCnCYQ8wQAgRCJwBbkLdwGsQQjgBxVLKMuBuYItRYCCE8JBSpgsh/FCqpzEXtBuAv6A8lwCSgKlCiI9RaqVI4NVWXmfnYzDAVf+BiY9DygEMZ/bTP3kf/qf3Y124CTa9SfkmczLsB2BfnoV1SXrd8VYOMOu/MGRupyxf00qkhONr6RX1ITFVvvj3aaP8SaZgFA4h9ufYW0s4nM4u5rEvD7ErIZtpIZ68MGcIjr0sWLU7ifXR6Vo4tJbv7oOkHXDnOlWSuJvRpHCQUlYIIe5HGYzNgBVSyqNCiHuMx5ehDM8fCiEqUYbqu2pN8bXR5lAOLJFS5hjbbxZCLDF+/gZ43/j5TePnIyiV1vtSyqjWXGSXwr43DJwJA2diBphJSVXOKY7s2Uzi4a0450aTQz92V13F/qoB2FJCf0MKCy22M/DruxEVJSoHlKb7kHYM1j0FCZsptunLn8oXstKjlfUamoOdEg4DbAr4JqkYKSWf7TnNcz8cwyAEL80NY+4InxrV5sQgd36LSUPKwVrd2VJKCyF6jXJI+fBaJSDsupcbu0lxDlLKtcDaC9qW1fq8AxjQwNgJDbS/hoqduLC9EGWgvjQQAoNLAGFXBBB2xUJOphXgUFbJaEdrnG0sOXwml83HM7hp6wQ+c3iD4NVLoPwcjFrU2SvXNEVRFmz8J+x7X+38Zr7EC4kjSKnIUUWmOgrjzsHPooDc4nJuW7GbrSczGdvPlZfmhdcJ2ASYGuzBT0fOcjQln8F9HDtunT2JuN+UYJj6DGx5GT6eA7f/CNbd599TJ3fvYgzwtK/zfYS/CyP8XSirlFyz5X72DfwUu7WPQf4ZmPp/bWuHOLVD3czcBkDvMGVY9xgEFtZNj9WcJ+8M7PsAdv9PPUGOvBsm/wnZy5kdmzYyyLuDbxC9XMBggZdBZR/ek5jNs1eHctuYAAz1BMRNHuiBEPBbTLoWDi3l+FplLxz3MHgPhU9vgl/+ArNf7+yVmYwWDt2EP0zsy8c7T/Gk2aO8OcIbfv8vZMXCdf8DyzYKqtv8LzizH1KjYO8K1SbMwH1gjbCQvYeAbySimxrZ2pXibFjzIMSsBVkFQTNg2l/BQwVHHkzKITnnHA9PC+rYdRkMYOeJn2UBS6b04/rhPo2WIXW3tyLcx4kNMek8eFm9CgFNY1RWwIl16v/fzBz6T4O+kyHlQGevrFlo4dBNcLa15K7xgby24ST33v8sg92C4OenIW08+I4Gl37gEqjSfbj0U/EXzSE7AeI3wuSnYdITylvqbBSkRiFToyg/+RuWUZ8hgO2OVzHm4U+0PvpCdr4F0T/AuAch4k5wDqhzeM2hVCzNDUwf1AmGXntPzIvSePx60zzxLgv24D+/niCjoBR3e6t2XlwP4/ROOJcDwVeeb3MbAKe2QVVVt/E61POeokkAACAASURBVMKhG3HXhEA+2J7IK+tPsuL2+1TOp+1LIWELHFpVt7ONm1FQ9FX1sftNaXzy/SvVLmH4rSAE0jmA6BJXVicG8f3psaTmleBrWcALNp8yPHc9X+w4wY1jB7bfxXY3KitUHZD+01TixgsPV0l+iEphykD3jrU3VGPvBVlxJnefGqKEw8bj6dwQ4dv0AM15Yn5UdWH61XJtd+0H5cVQkAqOfTpvbc1AC4duhIO1BYsn9uWln49z8HQuQ4OmQ9B0dbCsGHISIDte3QSy49Xr5K9w6DOY/BRMfKL+p5aKMnVjC5pBcqUTqzfGsvrgGU6kFWJuEEwMcuepmcFcHuqJ9WkvDB/NZvtPnzKs32MEXWAjuWSJ/VX98K98qd7DuxOySS8o5eq2KgHaXOw81ZOriYR6OdDbwZotJzK0cGgOUirh0HcSWNVS3bka1XNZJ7Vw0LQPC8cGsHxLPG9ujOWd2yLOH7C0Uak6PAfVHVBWDD88AptegOS9cP07Ko1zbY7/CEUZ7HWdzdx/bQQgwt+Zf1w7mKuGeOFiW8u+EDieSht3ri7eyYOrDvDdknFYW+hIWvatBFsPpWeuhzVRKdhYmjE1uJPcGe17K1VHRSmYN60mEkLQ192WlNxzHbA4xdm8Eq54VaX5cLW1xNXOEhdbS1xsrXCr+WzJcD9nfF26aOqZ9GMqx9qEP9Ztd+2v3rNilf2hG6CFQzfDzsqc28cG8NqGk8SczSe4dxO2BUsbuG6ZqlPx01Pw3hWw4GtwqvU0uPd9cPTlpTgf+rpVsPLOUQ3/+AxmmA26lin7P+LBsxm88Vssj11xiauX8lPg5M8w7qG6aVOMlFdW8dPhVKaFeGJj2Uk/OTujnaPgLDj7mzTE3d6KA9X11TuAY6l55J0r56ohXiAgq7CUhMwi9p3KIbuorCbfmZ2VOT8/MvEiF9xORUpI/F09hCEgaGbd4w7eYGEDmbGdsryWoIVDN+SOcQG8uzWetzbGsfTmYU0PEEK5U7oNhM/mw3uXw/yvoJcTHPseEjaTF/kEuzbl8fgVA5t+Khs8B7M97/B4QAIvb7PlzvGBdXcXlxoHPlHeScNvq/fw77GZ5BSXM7uzVEqgbA4AhWmmCwc7KzIKSpFSdojzQXKO2qX8dXYoHvZ13acrqyR558pJyCzi1vd28advDrPyjpGd7xRRUQpHvlbOCGcPg40rzHgR7C9wOhBC2R2yuo9w6B5mc00dnGwsWRDpzw9RKSRmFpk+MHAC3PkTIOCdKfDfQfDzn6D3EL5iGgDXDDXhBuYbCfZezOu1m+LySpZviW96TE+lqgoOfAiBE5Xxvx7WHErBwdqcCUHtXC+6Mexr7RxMxN3einPllRSVVbbTouqSnHMOK3MD7nYXq73MDAIXW0tG+Dvz5Ixgtpzo5LrshRmw6V/w38Hw3b3KIeHqpfDIUYi8p/4xrgOUzaGboIVDN+WuCYGYmxl4Y2Msshm1rvEcBHf/CuE3wbRn4f69yD9s5dOj5xgV6GJarn+DAUKvxS5pE/MGOfLhjkSyCktbeCXdnP0fQG6Scl2th5LySn45msaMwb2xMu9E24wxhUZzhQNARkHH/N+eyTlHH6deTe4Gbo30Z2SAM//44Rhp+SUdsrYa0qNh9RL1YLXpeRXgduu3cN8OGLEQLBpRdbkNUH8rFd3jt6KFQzfFw96am0f68tW+ZEb+cz0PrDrAZ7uTSMoyoU6wo4+K1Bz/CLgN4PCZPOIyipgzrBleFIPnQGUpj/odp+RS3T1kxcHPf4a+UyD02nq7bDqeQWFpRed5KVVj66ZclQtNFw5uxif4zA4S/Mk5xfRxbtqOYDAI/j03nNKKKv75Y3QHrMxIdgL8byIc+UblN7t/r6rl0m+qUhs1hWt/pX7MTmj/tbYB2ubQjXn6qhAG9XFke2wm2+KyWHNIZVL3ce7FuH5ujO3vyph+rhfpby/k2wNnsDQzMHOIl+kn7xMBnoPx3P1vbhq8nA93nOLuCX0vnYCpqkqlTqiu5dHAzWHNoRTc7CwZ09e1gxd4AQYzlfitIM3kIR29c0jOOcd0E1OLBLrZcvMoPz7dnURBSTn2HRE7cmw1VJbBfTuV/aC51HgsnQSPFpQF6GC0cOjGWJmbcUOELzdE+CKlJC6jkG2xWWyPy+SnI6l8vlcV8JsW4sHfrhlcr3dHRWUVaw6lcFmIB469mvEDMxjg2rfgnan8ifdZVTGPldsTe7bn0uaXVE4rz0Hq6e/0LpjzboN+64WlFWyISeOGCF/MzbrAJt3OU8VimEhHCodzZZVkFZXhY8LOoZpZYV58sD2RDdHpXNucXW9LifkBvMJbJhigrjtrN0ALhx6CEIL+Hvb097Bn4dgAKqskx1LyWR+dxvIt8Ux/ZTOPXTGQ28YEYGZMtial5LUNJ8ksLGvZj8srHCY+gf2m53nCN4zluyy4f2r/nhn3kHYMNj6nIl+NdcEJvbbR+hobotMoKa/qfJVSNfZekHe66X5GnG0sMTOIDhEOZ3KVOrQ5wmG4nzO9Haz5ISq1/YVDfgok74Gpf2n5HNYOSkB3E3dWkx5nhBAzhBDHhRCxQoin6jnuLIT4VggRJYTYLYQYXOvYQ0KII0KIo0KIh2u1hwshdgghDgsh1gghHGodCzMeO2o8rtOCNhMzg2CIjyOPXB7EL49MZESAC39bc4yrlm5lQ3QapRWVPPrFIV7/LZY5w/swraWFXSb8EbyGcnfe6xiKM/lm/5m2vZCuwq63wbwXPBoDjxyD21arnVMjuuY1h1LwcrRmhJ9zg306FHvPZhmkzQwCV1vLDhEO1W6szREOBoPgyiFebDmRQX5JeXstTRHzo3oPvrp187j27zY7hyaFgxDCDFWAZyYQiirSE3pBt6eBg1LKMOA2jHUajEJiEaoudDgwSwhRnebxXeApKeUQ4FvgceMYc+Bj4B4p5SBgMqpQkKaF+LrYsPKOkbxxyzBKyiu5a+VexrzwG98cOMOjlwfxn3nhNbuJZmNmAdctw7yikFccPmXFtoTmeU91B4qyIOoL5eFl46LUSH0nN5oNN7e4jM0nMpgV5lVvWuxOwa43FGeqdCkm4mZn1SEG6Wrh0MepeZHPV4V5UVZZxYZo020pLSLmB3Vjd2+l2tS1f7dxZzVl5zAKiJVSxhvLgH4GXHNBn1BgA9TUhg4QQniiKsTtlFIWSykrgM3AdcYxA4Etxs+/AtcbP08HoqSUh4zzZUkpO8bRugcjhGBWmDe//nESz107GC9Ha167aSgPXDag9YFEHiGIiU8wqWwr/pmb2XIys20W3VXY9z5UlMDoBvzXL0BKydub4iivlMwO70J5dIxFfyhKb7xfLdztrcjoIOFgYSbwaKZDwzBfJ7wdrfkxynRbSrMpzoaErRBytWleSY3h2h+Ks9ScXRxThEMfoLaiMtnYVptDqPrQCCFGAf6AD6rU50QhhKsQwga4EqjO23AEmG38PK9WexAghRA/CyH2CyGeaN4laRrDwszAgkh/fnxwAtcMbcMb17iHqHIP4XnL9/l0y5G2m7ezqSiDPe+qDJsmeJiUVVTx2JdR/G9LPNcP92Fwn2amTm9PqoVDzimTh7jbW3WQzeEc3k69mr3LMhgEM4d4seVEZvuplk78DLKy9SolULEO0KwMuZ2FKcKhvv+tC/UGLwLOQoiDwAPAAaBCShkN/Au1M1iHEiIVxjF3AkuEEPsAe6B6r2sOjAfmG9+vE0LUyn1rXJQQi4UQe4UQezMyMky4DE27Ym6J4Zo3cSeXCYmvc/xsQWevqG04tlp5+ETe12TXotIKbluxi6/3J/PwtAG8PC+s89M71MZ3tCpXun2pyUPc7ZVaqb1Vhck5xc2yN9SmWrW0/lg7qZai14C9N3ibkKqmKbqRx5IpwiGZ80/1oHYEKbU7SCnzpZR3SCmHomwO7kCC8dh7UsrhUsqJQDZw0tgeI6WcLqUcAawCqkVpMrBZSpkppSxG1a4efuGipJTLpZQRUsoId3f3Zlyypt3wGUFZxGIWmG/gpx++7OzVtJ6yItjyb5X2oN/UJrt/fyiFnfHZvDQ3jIenBXUtwQDKXjL+EVWlLGGrSUPc7awor1R5jdqT5Jxz+DTT3lDNMF8nPB2s2Hi8HR4SS/IgbgMEX9U2RXqcA8BgAWf2tn6udsaUq90DDBBCBAohLIGbgO9rdxBCOBmPAdwNbJFS5huPeRjf/VCqp1UXtBuAvwDLjON/BsKEEDZG4/Qk4FjLL1HTkVhP/z9yrHyYc/p5Tpw23TOmS/LTE5B5UtVoMOHGsCs+Czc7K+aO8OmAxbWQyHvBwUfVM66qarK7WwfEOpSUV5JRUGpSdHR9CCEY0seRmNT8Nl4ZcPgrZW8Kv7lt5jOzgLAbYP9Hyj22LYnfBB9fD9/dB1FfQqHptqX6aPIv3mhIvh91044GvpBSHhVC3COEqLbQhQBHhRAxKK+mh2pN8bUQ4hiwBlgipcwxtt8shDgBxKB2Iu8bz5cDvIISSgeB/VLKH1t1lZqOw9IW8zlv4UMmqV892dmraTlRX6gCSBMebbqKHsoIvSshm9GBLl1vx1Abi17KVz/1IBz9psnu1Unw2lM4VNeMaKlaCSDEy4H4zCJKytvYd2X/SvAcDH0uUl60nElPqjQaW+ovDNVsMk/CpzfCh9eoeJzja+Gbu+HlAfDWWJXi5eSvaifcDEwKgpNSrkWpd2q3Lav1eQdQbyVyKeWEBtpfw+jyWs+xj1HurJpuiP3ASez1volJqatI2vcTfiNmNj2oK5EVpwok+Y2ByX8yaUhyzjlS80oY3delnRfXBoTdCDvfhA1/Ux44jRT/qYmSbkePpfMxDi0v4BPi5UBlleRkWiFDfExLwdEkKQch9RDM/HfrvZRq4+yvkvTt+wDGPqhqv7eUilJYMUOl9Zj2N+VRZ2ah6r/Hb4K4jbD7HdjxhlJn+Y6CgPFK4F1YGOwCdIS0pl3of9OLJLyyCee1S6D0jxAyS+lbm0veGeVjnp8CRZlQkmv8oQoQhos/C4PyCAm7qW5BI1OpKIUvb1c/sOvfAzPTfiK7EpRr4qjAbiAcDAa4/B/w0bWwezmMfaDBrh2RQuOMcefQUrUSKOEAEJ2a33bC4cBHKiI+7Ia2ma82Ex9XdUA2vQhz/tfyeeI2qtiVW76AoCvOt3sPU6/xj0D5OUjaqYRF/EbY/G8u9im6GC0cNO2Ck6MTa4a+xPADf8bplz/DL3+G3mEQMls9rboPbPhprKpS/dHvex+O/6TcCA0WKrOotZMaJ6tU9S1ZBcjzn2UlRH0Ov/1T1a+4/O/N8zL55Rn11HXzZ82q9bsrPgsnGwuCPLpJTe1+U6D/NKXaGDpfGavrwcHaHEtzQzvvHIoxNwg8W5G00c/Fhl4WZkSfbSO7Q1mx0tuHXnNxWd22wL43jFoE21+H8Q+DR0jL5jn2HVg7qszADWHRS/1/95sC/E2plzJilArqb/UXqAItHDTtyI1XX8XzZn25Z8cu7nA5ynwOYrXxOZWjyHWA2k2EXA1OASowqDBNCYMjX6vU0jau6ql2+G2qkI6pW/ucRDj0OexdAatuhnu3N3jzq0P0Gtj9P4hcAgObpwrbnZjNyACXrhMNbQqX/x2WjYet/4Er/llvFyFETUW49iI55xy9Ha1blZzQzCAY2Nue6LYySh9bDaV5Sv1j5FxZJU98HUUfp15MC/FgmJ9zo5kFcorK2J+UQ2peCen5JUwa6MEI/1qCZvwjyqbx46Ow8Ifme0NVlELMWvU7Mm9GJUZLW+gzQr3QwkHTCViaG3h29iAi+7rw+Fd9eLVoOktn9WZy1W51I962FH7/b91BBgu1PR4yT92gG9GHN4hzAEx+EgbOgHcug+8fgBs/bly45CapIi7ew1QRpGZwNq+EU1nF3BppWvnNLoPnIBh6i1ItjVrUoNrPrZ0D4c7knGuVMbqaEC8H1h5ObZuypgc+Apd+4D+upmlDTBprDqUgBCzbHIezjQVTBnowNcSDiUHuONRKG77+WBpPfB1FdtH5VCU747P54p4x589h4wLTn1N/n/tXQsQdzVtj3EYlwAZd13TfFqCFg6bdmTHYi1AvR+5ftZ/bv0rmznHjeGr+XViW5aro05I8tUuwcQbv4aY95ZuCV7i60f/yZ6WiaqBaG5Xl8NVdyrVz7ormPYUBuxKyABgd2Mk1G1rClD/D4a9hwz9g7nv1dnG3syI5x4QiUi0kOecc4we0voRqqJc9q3YnkZpXgnc96elNpigTTm1XXkW1hMzPR9NwtbVkw6OT+D02kw3R6fx2PJ1vDpzB3CAYFejC1GAPErOK+HhnEqFeDrxxyzAC3Wx5a2McX+1LpqKyqu4OadityjPu17+qh6HqKHZTqFYpBU5q+bU2ghYOmg7Bz9WGL+8ZwwtrY1ixLYF9p7J545bh+A5tI//xhoi8TwUxrXsa/MeDe9DFfX57DpJ3w9z3G6wD3Ri7E7KxszInxKub2Btq4+ANY+9XtocxS+p12XS3t+Lg6dx2OX1ZRRVpBSX11hppLsFGo3TM2fzWCYeTvwJS7TyNlFZUsjEmnVlhXjjZWDIrzJtZYd5UVkkOJOWwPjqd32LSeM5YmW7RhEAeu2JgTWnYEf7OfLTzFCfSCgn1rpVSRQi4+jV4awysfRxu/Mi0NVaUqkyxIbOb/TBjKl2gAonmUsHK3IxnZw9i2YLhxGcWceXSraw70s6BcgYDXPs2mFnC+r9efDx2PWx7FUbcrkqftoBdCdmM8HfuGgV9WsK4h8DWXRnj60mT4W5vRXZRKZVVbZ9C42xeCVK2zlOpmuDeSjhHp7YydcuJdSqDbe/wmqbtsVkUllZwxeC6T/ZmBkFEgAtPzQzml0cmsfWJKaz/40T+fFVonZrhQ32dAOoXsq79YNITEP39+dTgTRG3EUrz202lBFo4aDqBGYO9WPvgBALdbLnn4328uv5E+57QvjeMe0AFByXXSluQnwrf/AE8QmHGiy2aOquwlNj0wu4R39AQVvYw+Sk49bu6MV6Au50lVRKyitre7pBcXeSnDXYO9tYW+Lr04lhrjNIVZRD3GwRNr2MgXnfkLPZW5ozt17jq0NfFhv71eKz5u9rgbGPBwdM59YxCCWiPQfDjY1BiwvqPfqs89/q2j0oJtHDQdBK+LkrNdP1wH15df5Iv9ppeoaxFjL4XbNxgw9/V9/IS+Hy+8gGf+75y92sB1fEN3dLeUJvhC1VSuF//Dyor6hxqz1iHM8YAuCbVQFJCygHY+AJsfaXBmhQhvR1a57GUtEM9kQedVylVVFbxa3QaU0M86uwGmoMQgnBfJw6dzqu/g5kFzF6qkjxu+FvTE57epWqKmLVf7WwtHDSdhpW5GS9eP4QJA9x4+pvD7IjLaseT2amqdQmbIX4zrHkQzuxTAUitKPa+PS4TG0szwtoq8KqzMLNQEbaZJ+DAh3UOtadwSMktAcDLqZ5ij+XnlMPCmofglRBYPhk2/0vdPD+cDQUXZ2EN9nIgMbOIc2UtTKNx4melgqxl5N17KofsojKuGNQMY3E9DPV14kR6AYWlFfV38ImA0X+APe9B0q6GJ6ooU951bvUmpWgztHDQdCoWZgbeuGU4AUYVU2x6YfudLOIulXr5i9tUoNyUv6g4i1awIy6LkQEuWHRXe0Ntgq8Cn5Gwa3mdZnc7dePOLDS9gpypnMktxsPe6vwTeUEa7Fup4lP+FQif3qCS3/lEKNvR47Eqcj3lICyfVFdNiPJYqpJwIq2FdocT6yBggnqYMLLuyFmszA1MCmpd9udwXyekhKjkRoz7U/8CDn3Uw0tFA8I495QK9nTp16r1NEUP+IvWdHcce1mwYuFIzA2C69/ezvbYdqokZ2ENkx5XKTgGzYGJj7VquvT8EuIyiprUQ3cbhAC/SMiOr5Ox1c1eecO0186hRqWUkwj/HaRujGcPw7AFsOBreCJexakMvUVFyQ+ZC3evVzEw78+E/ed3OrXTaDSbzFjIjqujUiqvrOLno2eZGOSOrVXrnDuH+iijdIOqJVD2n1mvqAjm31+tv091oSBXLRw0lwB+rjZ8c99YPOytuHXFbj7akdg+Jxq+UOWhufbtVidT2xGv1GBjeopwABUIV1mqotWN2FiaY2dlTnpBSZuf7kzuufOeSukxUFUON3wEDx+Gq15WKT7qC4TsPRgWbVRBat8/oKKMK8rwdbbB3sqcIymN3IAb4uTP6j1oek3Tit8TSM0r4eZRLcjTdQHOtpYEuNo0bJSuJugKGHw9bH0ZMo5ffDy7Wjj0b/WaGsMk4SCEmCGEOC6EiBVCPFXPcWchxLdCiCghxG4hxOBaxx4SQhwRQhwVQjxcqz1cCLFDCHFYCLFGCOFwwZx+QohCIUTrHu803QZ/V1u+uW8sk4PceWb1Ub47cKbtT2IwUz8+i3p03M1kZ3wW9tbmDPLu5vaG2jgFqPecxDrNHg5WpOe37c5BSqmEQ/XOIc/olOA72jTBbeMC879SKVb2vAvf34/BIBjcx5HDyS0RDr+Ae3BNpHhyTjGvrj/J5aGeTA32bP589TDU18m0mJEZL4KFjbK3XFh3IytOeSq1VbBoAzQpHIQQZsCbqDoNoag6DKEXdHsaOCilDEMl63jNOHYwsAgYBYQDs4QQ1VaUd4GnpJRDgG+Bxy+Y87/ATy25KE33xd7aguW3RTDAw473tyd29nIaZXtcFqMDXRrNr9PtqE6hkVu3zrSnvTVp+W27c8gsLKOsoqqWcEhWxmDbZuj2zcxVCopJTyo7UsxawnwciU4toKyi6WJGNVSUqsylfSfXND37vaox9uzsxlNbN4ehvk6k5ZdyNq+Jf0s7D5XvKmkH7P+g7rHsuHZXKYFpO4dRQKyUMl5KWQZ8BlxzQZ9QYAOo8p9AgBDCE1UEaKeUsthYNGgzUB21MRDYYvz8K3B99WRCiGuBeOBoi65K060xMwhuGe3HodO5HDnTgifADiAl9xynsooZ06/1aR+6FE6+gLho5+DpYMXZNhYO1UV+vGvvHBz6tKwc54THVI2CHx5huAeUVVY1zyidvFdVfAucCMAvR8+yPjqNRy4f0CbR29WE1wTDNa5aKimv5OvKSURZhJP/w19ISKu128iKb3djNJgmHPoAtZ3Qk41ttTmEKgGKEGIU4I+qNX0EmCiEcBVC2ABXcr4e9RFgtvHzvOp2IYQt8CRggrOvpqcyZ5gPVuYGPt2d1NlLqZdqt9sxfXuQvQGUft/BG3Iu2Dk4WpOeX4qsJ4K6pdTUcai9c3BsYYlVc0u45k0oymBcnErmGNUc1VLiVkCA/1jKKqr4+w/HCO5tzx3jWlGIpx5CvR2wtjDw7tYECkoursudkFnEcz8cI/KFDTz6VRTfmU3HgSKee/czld+qvEQJ0S6yc6hvz3zhX8iLgLMQ4iDwAHAAqJBSRgP/Qu0M1qGESLWT753AEiHEPsAeqPaT+xvwXylloz6NQojFQoi9Qoi9GRntUFhc06k42lhwdbg3qw+cadgvvBPZEZ+Fs41FTcqGHoWT/8U7B3tryiqryC2++IbWUqoD4OoIBye/lk/oPRTGP4xd9Odc2etI4y6jF5KwFbzCoJcz3+xPJjnnHE/ODG5zF2UrczP+dX0YB0/nMv/dXWQXlVFeWcW6I6kseHcXU17exAfbExnXz41PF43mmXtVptaB5Ue55Z1dZCbFALJDdg6m+GYlc/5pH9SOoE5lbCllPnAHgFC5chOML6SU7wHvGY89b5yvWv003dgeBFxlnG40MFcI8W/ACagSQpRIKd+44JzLgeUAERERbZ/0RdPp3DLaj6/2JbP64Bnmj+466bCllOyIy2J0oGv3qt9gKs4BKliwFr0dlQH/bH4JzrZtk+jtTO457KzMcehlrjLjFqS2fOdQzcQn4Nj3/DX3ff5weoRpY8rPqcSLoxZTXlnFm5tiCfdxZHIr4xoa4pqhfbC3Nufej/dz7ZvbKK2oJC2/FG9Hax6bHsQNI33xsK/lMOHkz2LHdFYmlPLWN7/wfwCuzU8Q2VxMEYt7gAFCiEAhhCVwE/B97Q5CCCfjMYC7gS1GgYEQwsP47odSPa26oN0A/AVYBqrmtJQyQEoZALwKPH+hYNBcGgzzdSLEy4FPdia1qTqjNUgpeX5tNGdyzzF9UNt4sHQ5nP1VWdZaQVieDsqdtC2N0mdyz+HtZK1qLxSkqkp+rRUOFtZw1X/wrEzlsqxPKCk3IVL69C5VgzlwIt8dOMPp7HM8eNmA1teEaISpwZ58eOcoissqCe7twDu3RbD1yancP3VAXcEA4DcGp8x9/GdeOOa58aqtK9gcjIbk+4GfgWjgCynlUSHEPUKIe4zdQoCjQogYlFfTQ7Wm+FoIcQxYAyyRUlZbYm4WQpwAYlA7kffb5Io0PQYhlGH6WGo++0414RveQby1KY53tiawcIw/1w0zvYxot8I5AJCQe97UWH3Dakt31pTabqzV52qtcADoO4kU31ksNnxPfMzBpvsnbAVhRoXPaN7cGMsgbwemBnu0fh1NMLqvK3v/Mo2Vd47i8lDPhr3e/CKhKIMZ3sWMc8knSzqQXNI+abprY5JCTUq5VkoZJKXsJ6X8p7FtmZSy+ml/h5RygJQyWEo5p5YAqN4JhEopw6WUG2q1v2acM0hK+ZSs59FQSvmslPLl1l+mprsyZ1gfXG0teXX9yU5dR2WV5L3fE3jp5+NcO9Sbv149qF2fLDsVJ6MKr5bdwcO4c2hLjyW1c6hlbwBwbIXNoRaGGc9TiiXOG/9UbxryOiRuBe9hfB9TSGJWcbvvGpqNn7F6XNIORjvmcIreNW627YmOkNZ0aWytzLl3cj9+j81s38R8DVBaUcnne5K4/JXN/OOHY1wW7MFL88J7pq2hmupYh5yEmiYrczNcbC3bTK1UVFpBbnH5+ejo6gA4x7bZjXl6+/G22S14Ze+Cdoa3xAAAIABJREFU14fD6vvh4CqVsK42pYUqAWPgBFbuOMVAT3umh3YxdaFbEPRyhqQdWOclYus1kPXRafx8tH1roehKcJouz4JIf97ZGs8rvx7ni75jOuyp7siZPB5cdYD4zCIGeauSjzMHe/WsoLf6sPMEM6uLA+Ec2i4QLqU+N1YbtxanTr8QIQQnfOexNM2cB92TVM3yA8Yqa46+Ku2G/1hAQlUFOR6RHFqfy+NXDOxauwZQcR++kRC3CQpS6D/iDoJL7PnLd0cYGeCCSxs5CFx02naZVaNpQ6wtzLh/6gD2JOaw5WQ7JeWrRVWV5N2t8Vz31jaKyipYcXsEPzwwnllh3j1fMIC6GTnX487qYEVaG9kcktsyxqEBBvu48GreRIrmfARPJMA922DmS6oUatwGleBvzUNgMGddvlKldbldQzV+kZCvVG9mbv155Yah5BWX8+TXUe3mrKF3DppuwY0RvizbFMeLP8VwrqyC3o698HK0xs3Oqk1v2JmFpTz25SE2Hc9gWogn/54b1m5PZl0a54CLA+HsrTmW0opCOrWo2TnUViu1cSK5MB9HqiT89v/tnXl8VfWZ/99PEkJISEgIO8imKEQEVH5oXUfccEVHndFWqVq1OorYzlhxaWdc+qu2dlrbscPParWOI2jFrUpdftoRtSqC7IuKoJKwGAUSthCSPPPH93uTm5vt5t6b3HPC8369zuue+/2ec/I5D4f7nO/yfJ81X3HO+EFusb4BY+Goa9w4xNefuux3OYW8smAHw4tzOahfz7YvnA4i4w4AxQdSMrCAH005hHteXs3sBRv49lGpGauJxpyDEQqyszKYecZoZsxZzLVPfFRfnpkh9MvvzoBeOQzslcOAgh6cdmh/jk4gcnn+J+X88OmlVFbt4+6ph3Lp0cOC18XQWRQOa5Jwpn+vHL7euZea2rqk82WXbdtDVoa4WVCqruVw4OSkrhnLpBG9GdEnj+mzF/P+um+YecZo8nN85jQR6Hsw9D2YHVX7eG/2/+e7xwT433vQBNfVV7u3fhrrlceO4K1PyrnrpZVMGtE75Y7NnIMRGs4ZP4jjDupD2fY9bK6oYnNlFZsrqthUUcWWyio+3ryDv64p57G/refOqWO57Oj4Aueqa+r45Wsf8//mr2NUv548cdUkRg8oaPvErkzRcNhbAXu2ucFQXLdSnboF8yJBcYmycfseBvTKca2+PdugemfKu5Xyc7rx8o3H8e+vfcIf3l3Pm2u+4qfnj22ywupbn5RTXVvHaUlmeutQsrrD4CNdrg2fiCgjQ7j/ovFM+fV8rn58IXOvOyalrVxzDkaoKMrLpigvm7GDm18me9feGqbPXsyPn1/Bhq27mTlldKszi9Z/vYsZcxazrLSC7xw1lDvOKqFHdmJ5grsURVHTWSPOIb8hSjpZ59D8NNbUOgdwuSjuOLuEs8YN5Ja5y7jysYVMnTCIn5xdQnFPNz339VVbKM7L5oihRSn/+ynl1Dth51eNivoX5PD7aRP5zsMfcOVjH/Lk1UeRm52an3UbkDa6FHnds3josiO57OhhPDR/Hfe+sqbFY5eXVnD2b97mi292M+vSI/jp+YeZY4hQP521Ydwh4hCSnbGkqmzY2kEBcC1w+NAiXpp+PDedMop5yzdx6q/m88KSMqpr6nhzzVdMHt0v+JMNDpgEY85uUjxxeG9+c8nhLCvdzvQnF8cXFR4H1nIwuhxZmRncNfVQaurqePjtdZw7flCzLY0H3viU7KwMXr7x+Ia3WMPRSiDcV0k6h5UbK9lcWcWRw/yben3LIflsa62RnZXBTacczBljB/KjucuYMWcJvx+8jh1VNcHuUoqD0w8dwJ1Tx/Lj51cw9l9f5ZAB+YwbUsi4Ib0YN6QXB/fPb/ciguYcjC6JiDBzyhheX7WF259fwbPXHdPozfCz8p28sWYL0yePMsfQHDkF0KN3o1iHPnluZliyUdLPflRGdmYG54wb5AoqNrjB1vYk+UmCQwbk8+x1x/Dou+u5/7WPyc3O5LiDwp+X47KjhzGiOI+/ffY1y8sqmLd8E7P9kvfdszIoGVTAuMG9OKhfTzLjyJlhzsHosvTK7cbtZ43hB08tZfaCL7k0aoD6kXfW0y0zg2nfCs5qr4Gj8ICGt3rcAGi//ORiHfbV1vHi0jJOHtOPXrl+5lAkxqETZwplZghXHT+SKWMHsH33vi7TnXjcqD4cN8o5OlXly627WVpawbIN21lWVsGfFpWyuzq+bidzDkaX5rwJg3n6w1J+/soaTi3pT/+CHL7ZuZe5i0q54IjB9OnZTPJ6w5HbB3Y1Djrsl2SU9NuflvP1zmr+/oio8YUOCICLlyFFuQwJ+Dh0oogIw4rzGFacx7njXSuttk75Zufe+oQ8A+5r+XwbkDa6NCLC3eeNZW9NHWc+8DYvLdvI4+99wd6aOr53XMeviR9q8vrA7sbOYUBB96Scw9yPyijK7caJ0bkSKjZ0+HiD4cjMEPoV5NDfb60Rl3MQkSki8rGIrBWRmc3UF4nIcyKyTEQWiMjYqLoZIrJCRFaKyE1R5eNF5D0RWS4ifxaRAl9+qogs8uWLRCS1kTHGfsdB/Xrywg3HMrioBzc8uZj/+OtaThnTL7jRsEEhtxh2b21U5NZXSqxbqWLPPl5ftYVzxw8iO8v/9NRUw47NPne1ESTadA4ikgk8iMvTUILLw1ASc9htwBJVHQdMAx7w544FrgYmAeOBs0VklD/nYWCmqh4GPAfc7Mu/Bs7x5d8F/ivx2zMMx+gBBTx73THcesZo+vbszvUnpXaphi5JbrELTtvX0FLoX5BDxZ59CU2XnLd8E9U1dY27lHZsAtTlrTYCRTwth0nAWlVdp6rVwBxgaswxJcAbUJ/+c7iI9MclAXpfVXf7pEFvAef7cw4B5vv914EL/PmLVTWShnQlkCMi1jFsJE1WZgbfP/FA3r/tZA4PesBTEMjzM3h2NyyVHumKSKRr6cUlGxnZN49xQ6KmFVf6/+oFXTRxUoiJxzkMBjZEfS/1ZdEsxaUARUQmAcNwuaZXACeISLGI5AJn0pCPegVwrt+/iMZ5qiNcACxW1dSlnzIMIz5y/fpUUeMODelC2/dfsmpfLYu+2MbJo/s1Xr+ossx9mnMIHPE4h+bml8WuEXsvUCQiS4DpwGKgRlVXA/fhWgav4JxIjT/nSuB6EVkE5APVjf6oyKH+3O83K0rkGhFZKCILy8vL47gNwzDaRa5vOUTNWBpQ0LCERntYumE71bV1TBoRsyBifcvBupWCRjxTWUtp/FY/BJfzuR5VrQSuABD3WrDeb6jqI8Ajvu7/+utFup9O8+UHA2dFriciQ3DjENNU9bPmRKnqQ8BDABMnTgxG9nnD6ErUtxwaBqUH+oDBL7/Z1a5LfbB+KyIwaXjvxhWVGyE73wXdGYEinpbDh8AoERkhItnAxcCL0QeISKGvA7gKmO8dBiLSz38OxXU9zY4pzwDuAGZFrgW8DNyqqu8md3uGYSRM/ZhDQ8uhZ/cshvbOZfWmHe261IL1Wzmkf35D4FuEyjJrNQSUNp2DH0i+AXgVWA08raorReRaEbnWHzYGWCkia3CzmmZEXWKuiKwC/gxcr6rbfPklIvIJsAbXEnnUl98AHAT8WESW+K1fcrdpGEa7ySkEyWg0IA0wZmA+qzfFn/RnX20di77YxlEjejetrNxoziGgxBUhrarzgHkxZbOi9t8DRsWe5+uOb6H8AfyU15jye4B74tFlGEYHkpHh1leKiZIePaCA11ZtYXd1TVzLQy8vq2DPvlqOai4BU+XGlCf5MVKDRUgbhtEyeX2aaTkUoAqfbNkZ1yUWrHdjFv8ndryhtgZ2braWQ0Ax52AYRsvkFjdxDiUD3eBxvF1LH6z7hgP75tE3PyZcaecW0DpzDgHFnINhGC2TW9ykW2lIUQ96ds+KyznU1ikLP9/WdAorWABcwDHnYBhGyzTTcsjIEEYPiG9QevWmSnbsrWlhMDoSAGcthyBizsEwjJbJ6wN7tkJdXaPiMQMLWLNpB6qthxh94McbJrU0UwnMOQQUcw6GYbRMbh83LlC1vVHxmIEF7NhbQ+m2PS2e+trKzfzmjU85qF/P5rPtVZZBVg/oYetcBRFzDoZhtEwkSjpm3GHMwHwAVjXTtVRdU8edf17JNf+1iAN69+DhaRObv3YkAK4TM8AZ8WPOwTCMlslruvgeuDzMIk1nLH35zW4unPU3Hn33cy4/ZjhzrzuG4X3ymr+2BcAFGksTahhGy9Svr9R4UDo3O4sRxXmNnMO85Zu45ZlliMCsS49kytgBrV+7ciMMOzbVio0UYc7BMIyWaWZl1ghjBhawvKyCqn213PPyKp54/0smHFDIby85nAN657Z+3bpal+jHWg6BxZyDYRgt00LLAdy4w8vLN3Heg++yZvMOrjlhJDeffgjdMuPord5VDnU15hwCjDkHwzBaplsOZPdswTm4SOktlVX84fKJTB7dP/7rWpKfwGPOwTCM1mkmEA7ghIP7csdZYzhr3EAG9mpmqmprWIxD4DHnYBhG6zSzhAZAt8wMrjp+ZGLXjDiHXkOSEGZ0JHFNZRWRKSLysYisFZGZzdQXichzIrJMRBaIyNiouhkiskJEVorITVHl40XkPRFZLiJ/FpGCqLpb/d/6WEROT/YmDcNIgrw+TaayJk1lGWRmN4xpGIGjTecgIpnAg7gkPiW4JD0lMYfdBixR1XHANHyeBu8krgYmAeOBs0UkkvfhYWCmqh6GSwl6sz+nBJdt7lBgCvA7r8EwjHSQW9woVWhKiMQ4WABcYImn5TAJWKuq61S1GpgDTI05pgR4A+pzQw8Xkf64DHHvq+pun1HuLeB8f84hwHy//zpwgd+fCsxR1b2quh5Y6zUYhpEOWuhWSorKjTYYHXDicQ6DgQ1R30t9WTRLcfmhEZFJwDBgCLACOEFEikUkFzgTOMCfswI41+9fFFUez98zDKOzyOsDNXugenfqrmm5owNPPM6huXZf7FKM9wJFIrIEmA4sBmpUdTVwH65l8ArOidT4c64ErheRRUA+UN2Ov4eIXCMiC0VkYXl5eRy3YRhGQuQ2v4RGwtTV2dIZISAe51BKw1s9uBbBxugDVLVSVa9Q1Qm4MYe+wHpf94iqHqGqJwBbgU99+RpVPU1VjwRmA5/F+/f8+Q+p6kRVndi3b984bsMwjIRoJUo6IXaVQ201FNhMpSATj3P4EBglIiNEJBs3WPxi9AEiUujrAK4C5qtqpa/r5z+H4rqeZseUZwB3ALP8+S8CF4tIdxEZAYwCFiR+i4ZhJEV9yyFFg9KVpe6zl/UWB5k24xxUtUZEbgBeBTKBP6jqShG51tfPwg08Py4itcAq4HtRl5grIsXAPuB6Vd3myy8Rkev9/rPAo/56K0XkaX+dGn9ObbI3ahhGguT5lkOqupUqLDo6DMQVBKeq84B5MWWzovbfw73hN3fu8S2UP4Cf8tpM3U+Bn8ajzTCMDibXZ3FrJko6ISJLZ1gAXKCxfA6GYbROTiFkZKVuzKGiFLJyLAAu4JhzMAyjdUQgf2DDG3+yVJa5LiULgAs05hwMw2ibwmGw7YvUXKuizAajQ4A5B8Mw2qZoGGxPlXMotWmsIcCcg2EYbVM4zGVu21eV3HVqa2DnZms5hABzDoZhtE3RMPdZsaH149pixybQOpvGGgLMORiG0TaF3jkkO+5g01hDgzkHwzDaJtJy2P55ctepiERHm3MIOuYcDMNom54DILN76loO1q0UeMw5GIbRNhkZUHhA8jOWKsqgewHkFLR9rJFWzDkYhhEfqYh1qCi1VkNIMOdgGEZ8pCLWobLUprGGBHMOhmHER+Ew2LMNqioTv0ZFmbUcQkJczkFEpojIxyKyVkRmNlNfJCLPicgyEVkgImOj6maIyAoRWSkiN0WVTxCR90Vkic/oNsmXdxORP4rIchFZLSK3puJGDcNIkvoZSwm2HvZVuWW/ex3Q9rFG2mnTOYhIJvAgcAZQgsvDUBJz2G3AElUdh8sE94A/dyxwNTAJGA+cLSKRpb1/Dtzps8f9xH8Hl0+6u6oeBhwJfF9Ehid6g4ZhpIhkYx3qYxys5RAG4mk5TALWquo6Va0G5gBTY44pAd4Al/4TGC4i/XFJgN5X1d2qWgO8BZzvz1EgMmWhFw2pQBXIE5EsoAcut3QS7VjDMFJC0XD3mWjLwaaxhop4nMNgIDpmvtSXRbMUlwIU3z00DJf7eQVwgogUi0gucCYN+aFvAn4hIhuA+4FI99EzwC5gE/AlcL+qpig/oWEYCdOjCLLzE285VFh0dJiIxzk0t+i6xny/FygSkSXAdGAxUKOqq4H7gNeBV3BOpMafcx3wA1U9APgB8IgvnwTUAoOAEcA/i8jIJqJErvFjFQvLy8vjuA3DMJJCJLkZS5Ho6IJBqdNkdBjxOIdSGt72wbUINkYfoKqVqnqFHz+YBvQF1vu6R1T1CFU9AdgKfOpP+y4udzTAn3BOAeDbwCuquk9VvwLeBSbGilLVh1R1oqpO7Nu3bxy3YRhG0iQT61BZ6rK/deuRWk1GhxCPc/gQGCUiI0QkG7gYeDH6ABEp9HUAVwHzVbXS1/Xzn0NxXU+z/XEbgRP9/mQanMaXwGRx5AFHA2sSuTnDMFJM4VDXctDYzoM4qCizLqUQkdXWAapaIyI3AK8CmcAfVHWliFzr62fhBp4fF5FaYBXwvahLzBWRYmAfcL2qbvPlVwMP+IHnKuAaX/4g8ChuvEKAR1V1WZL3aRhGKigaBvt2u3zSPdvRYq+phrJFcNApHafNSCltOgcAVZ0HzIspmxW1/x4wKvY8X3d8C+Xv4KaqxpbvxE1nNQwjaBRGxTq0xzl89gbs2QqHXdgxuoyUYxHShmHETyQQrrydPb1L50BuHzhwcuo1GR2COQfDMOKn+CDoPRL+cgusnx/fOVUV8PFfYOwFkNmtY/UZKcOcg2EY8ZPVHS6f5waWn7gQ1rzc9jmrXoDavTDuHzten5EyzDkYhtE+CgbCFX+BAWPhqUvh0TNh/v1Q9hHU1TU9ftnT0PtAGHxE52s1EiauAWnDMIxG5PaGaS/AO7+CT1+HN+92W4/eMPLv3NjCgZNB6+Dzt+Gk210QnREazDkYhpEY3fPh5J+4bWc5rPsf+OxNt6308a25xe7zMJuAGDbMORiGkTw9+8K4i9ymCl+tanAUhUOh94h0KzTaiTkHwzBSiwj0P9Rtx0xPtxojQWxA2jAMw2iCOQfDMAyjCeYcDMMwjCaYczAMwzCaYM7BMAzDaII5B8MwDKMJ5hwMwzCMJogmktEpYIhIOZBg7sIOpQ/wdbpFJEHQ9QddXzRB1xp0ffEQ9HsIor5hqtpsYo4u4RyCiogsVNUm+a/DQtD1B11fNEHXGnR98RD0ewi6vlisW8kwDMNogjkHwzAMownmHDqWh9ItIEmCrj/o+qIJutag64uHoN9D0PU1wsYcDMMwjCZYy8EwDMNogjmHFCBiKa46GrNx6jBbdixdxb7mHBJEHD8QkSEa0r45Ecn0n4F8mMNkY7NlxxNkG3cF+8ZiziEBRGQa8FfgcKAyiA9ra4jI5SKyGJiRbi0tERYbmy07nqDbOOz2bQkbkG4nInIs8DYwSVUXxtRJ0N8aRGQ08DjwKnAY8ENVXSciGapal151jrDY2GzZ8QTdxmG3b2tYyyEOIs1ZAFV9F/gAGOPrZorIOSLSM6gPgojkR/ZVdQ0wDfgVsAq4wZen9T9aWGxstux4gm7jsNs3Xsw5tIGI3AX8RESi1x+5FvijiCwBCoHpwC/8W06gEJGZwGIRuU9ELvfFH6vqVuA54EAROcEfm5bnISw2Nlt2PEG3cdjt2y5U1bZmNqA7cCtuQb/ngNNi6v8JONLv9wWeB05Pt+4YjZOB+cAI4CRgEzAuqr4ncBPw31FlmWZjs6XZuOvZt72btRxaZh/wElACvA+cJCIjIpWq+jtVXeT3y4GtQO90CG2FbsBiVV2vqn8FHgB+FlW/C3gG2Ckid4vIz4HhnagvTDY2W3Y8QbZxV7BvuzDn0ALq+jQ/UdVdwFPAEGCSiHSHhul0ItJbRH4JjAM+TJfeFsgFikUkB0BV7wUGishF/rsCVbiBvuuAclX9rLPEhczGZsuOJ7A27iL2bRfmHGh53rSq7vWfnwPvACcCo32Z+jeHp3BvPCeq6tpOERxD9ABedD+sqj4HHAicHXX4z4EfRn3/GbASGKqqv0iDxkDZWER6R+0H1ZYtaQyULVtCRMY0Vx4UG7eiLxT2TRnp7tdK5wZMBf4ITIgpFyDD72f6zwLgt8C3gcuAc3x5cRr1n4GbX/04cHtUeSbQ3e9fjOvHHe6/DwUeBPL995w0agyMjYEp3k6PA7+MKs8IkC1b0xgYW7ZxD78F1kdsGEAbt6QvFPZNqS3SLaDTb7ghtuMkYBmwCNdELYqu9/sjgcKo7zcC24C1wJnp0u9/WK/FNVvPBI7C9YdeGXPsSH/8XcDDwPXAK8CsgGlMi42jdF6D60ee6n+M/gc4I2C2jFdj4J7XmO//DXwEXBVxCOm0cQL6AmXfDv23S7eATr3Zxj/8w4GBuBkSj+GagZG6DGAmbrbEGf6BHQ2sA24LiP4zgVFR32/CBQjhf0xmAuXA8UAv4FhcK+nmgGlMi41jdJYAWX6/H/C0/wGOvCXeHgBbxqMxyM9rROd04Gpca3JsVP0tuBSanWbjBPRtDpJ9O3rLYj9BRG4AThaR+cBsdf2GAJtE5HTgRBFZq6plwACgAihR1W3+/M+Bw9QNSHU6UfrfBh5X1XkikikiWapagwvCWeMP74fTf3BEP/CuiLyvqrUB09jpNo55Fuao6ipffjiuCyML92OwB/gX0mvL9mgM4vP6FvC0qm4UkWxc19h3cS9mF4vIAtz00Erci0Sn2DhBfWOCYt9OId3eqTM24Hxc98ZJwKPAfwDjo+rHA08A5zdzblZA9U+I1odr/RzTzLmZxDSdA6ix02zc2rOA6zYY6vd74n5wDw+ILePVGNTn9Qhfd6f/vAT3g7uaqH76zrBxkvrSbt/O2vaX2UpHAf+pbu70v+EGnOoX8VLVpbiH5TARmeyjNCNro9SkQW8szem/EUBVa/x0ugOARSIyRESuhnr9teqf6gBr7EwbN6fzJq9znap+6fd34mae9I7SmU5bxqsxqM/rP/m6s3zL8hbgBdw4yi7oVBsnoy8I9u0UupRziJ2SGvV9HW5WAar6BfAykCci50YdPhs3CPUUUNzxapuSgP6pvn40TvMM4EU6MPgmDBoT0Jkb8ywgIncAh+KmTdIRP1hh0Nga7dRfKCLfwgW2/U1VJ6jqZbgu3DH+2JTqD7q+oNOlnANufnE9Uf+YzwC7o36oNuFme5SIoyfuoViOC9e/Oeb8zqK9+sf4B34k7gEeAZylqvfFnL+/aUxEZwmAiJwhIu8ABwMXqurmDtIXFo2t0R79bwIn4Ja+uCXqtPNVdfF+qi/QdAnnICLfEpE/4Ra7KpGGpCCRAfdtuPVQrvNNwwpcf22Of2CqgBmqepaqbgqR/h5e/1rgOFW9rqP0h0Fjsjp9/WrgWlWdFkRbdpbGDtCfh/v/VidukkIGgKpW7W/6wkLonYOI9MMNKM0DvsF1W1wJrq/bH9YDtx78JuAhERmES8yxL3Kcqn7VydKBlOlfrqodFqofBo0p0Fntj/tcVVfszxpbI0n9Nf64Wu2gJbeDri9UaDtHsIO2AafipqaC8/6n44KtRvuye3APwuG4fu57cE3039GJq2aGWX8YNIZFZxg0hll/0PWFaUu7gAT+8c8DbsP1W4NbHvdT4ED/vTfwr8B9uIW8nozURV0j1/SHW2NYdIZBY5j1B11fmLfQdCuJSF8ReR63CNdW4FERuVDd8rhzcZGNANuBN3APRY6qfltVP5PGC5Tt7mT5odAfBo1h0RkGja0RdP1B19cVCI1zwK3W+K6qnqCqs4B/pmG1xtnAaBE5RV1f4TdAf2AvuJUrNf19iGHQHwaNYdEZBo2tEXT9QdcXegK9fIaITAO+BBbgFshb78szcflkV/pDlwNzgF+LyHnAybj1T7pB+vLNhkF/GDSGRWcYNLZG0PUHXV9XI3DOQUQEF3jyJFAHfIZbCGuGqm4RkUxVrRW35novqP/HfszPVJiJC7i6WlW3m/5wagyLzjBoDLP+oOvr0qR70CN6o2FlxIOBJ/x+Fm7d9Gdjjnkc+Ae/PyDqGtmmP9waw6IzDBrDrD/o+rr6FoiWgw9OuQvIFJF5uEQatVC/Ls+NwEYROVFV3/Kn7QTWi8hdwN+LyBRVLVXVatMfTo1h0RkGjWHWH3R9+wtpH5AWkRNx/YdFuCjau3GBUyeJyCSoD3u/C7dIVqSP8UpcGHwBcJKqlna6eMKhPwwaw6IzDBpbI+j6g65vvyLdTRdcco/Lor7/DpeZ7XJgkS/LwPU7Pg0Mw81U+DV+mV3TH36NYdEZBo1h1h90ffvTln4BLjClOw19h98Bfub3lwDT/f5EXNKTtGsOm/4waAyLzjBoDLP+oOvbn7a0dyup6m5V3asNGZ9OxaVkBLgCt6rnS7i5y4ug6VK86SQM+sOgMSw6w6CxNYKuP+j69icCMSAN9f2GigtWedEX78CFxo8F1qtL4Yn6V4cgEQb9YdAI4dAZBo2tEXT9Qde3P5D2lkMUdbggla+Bcf7t4MdAnaq+E3kQAkwY9IdBI4RDZxg0tkbQ9QddX9cn3f1a0RtwNO6heAf4Xrr1dEX9YdAYFp1h0Bhm/UHX19U38f8IgUBEhgCXAf+uqnvTrae9hEF/GDRCOHSGQWNrBF1/0PV1dQLlHAzDMIxgEKQxB8MwDCMgmHMwDMMwmmDOwTAMw2iCOQfDMAyjCeYcDMMwjCaYczCMFCAi/yYi/9JK/XkiUtKZmgwjGcw5GEbncB5gzsEIDRZD6EceAAABaElEQVTnYBgJIiK3A9OADbjF4RYBFcA1QDYuH8FlwATgJV9XAVzgL/Eg0BfYjUtjuaYz9RtGa5hzMIwEEJEjgceAo3ALWH4EzAIeVdVv/DH3AFtU9bci8hjwkqo+4+veAK5V1U9F5CjcstSTO/9ODKN5ArMqq2GEjOOB51R1N4CIRFYOHeudQiHQE3g19kQR6QkcA/wparXp7h2u2DDagTkHw0ic5prdjwHnqepSEbkc+LtmjskAtqvqhI6TZhjJYQPShpEY84HzRaSHiOQD5/jyfGCTiHTDZTGLsMPXoaqVwHoRuQhcshoRGd950g2jbWzMwTASJGpA+gugFFgF7AJ+5MuWA/mqermIHAv8HtgLXIhbivo/gYG4vAVzVPWuTr8Jw2gBcw6GYRhGE6xbyTAMw2iCOQfDMAyjCeYcDMMwjCaYczAMwzCaYM7BMAzDaII5B8MwDKMJ5hwMwzCMJphzMAzDMJrwvxtfInVJo+/YAAAAAElFTkSuQmCC\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXwAAAEdCAYAAAAPT9w1AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8li6FKAAAgAElEQVR4nOzdd3hUVfrA8e+Z9N57p0NIAiFA6AIioCKKoCKgrgprL7uu+nMta1vZXXtlUddKsYugAqL03ksgQCAhDUJ6rzPn98edQBJSyaTO+TwPTzJ37txzEuCdM+895z1CSomiKIrS/ek6ugOKoihK+1ABX1EUxUyogK8oimImVMBXFEUxEyrgK4qimAkV8BVFUcyECviKoihmQgV8pcsSQkghRK86x/4hhPiyxuOnhBCJQogiIUSqEOKrGs9tEEKUCSEKhRAFQoi9QognhRA2jbT5DyFEpfF61X96tKL/xcZrpAkhXhdCWBife1kI8Xud8/sY+xlxOe0pigr4SrclhLgdmAdcKaV0BGKA3+uc9oCU0gnwA/4K3AL8IoQQjVz6KymlY40/p1vRzShj38YBNwN3Go+/APgKIeYbfxYBfAi8LqU83Ir2FDOmAr7SnQ0F1kgpTwFIKc9JKRfXd6KUslhKuQG4DhgBXNPSxoQQ7wkhXqtzbKUQ4pGmXiulTAC2AoOMj8vRgv9CIUQAsABwA15uab8UpZoK+Ep3tgO4TQjxNyFETHW6pDFSymRgDzCmkdOmCSFyhBBxQoh7axz/DJgthNABCCE8gYnAsqbaFUL0M7aZUKMvO4FPgc/RAv2dUsrKpq6lKA1RAV/ptqSUXwIPApOBjcB5IcSTzXhpOuDewHNfA/0BL2A+8KwQYraxvV1APlqQBy09tEFKmdFIW/uEEMXAMWAD8H6d558GegFfSCn3NKPvitIgFfCVrkwPWNU5ZgVcGAVLKZdIKa8EXIF7gBeEEJObuG4AkFPfE1LKo1LKdCmlXkq5DXgLmFnjlM+Aucbv5wJfNNFWNOCIlr8fDjjUaa8USATimriOojRJBXylK0sGQuscCwPO1D1RSlkppfwGOAQMbOiCQoggYAiwuZl9kEDNG7xfAtOFEFFonwR+bPICmq+B7cCzzWxXUVpMBXylK/sKeFoIESiE0AkhrgSmAd8CCCHuEEJcI4RwMj4/FQgHdta9kBDCXggxDlgB7AJ+qa9BIcR0IYSb0AwDHjK+BgApZSqwG21k/51xhN5cC4EFQgjfFrxGUZpNBXylK3sB2AZsAXKBfwNzpJRHjM8XAE+hfRLIMz5/r5RyS41rvCuEKAQygDeB74ApUkpDA23egnZjtRDtZuq/pJSf1TnnMyCCptM5tRinW24E/taS1ylKcwm1AYqimJYQYixaaie0kTcORWl3aoSvKCYkhLACHgY+UsFe6WxUwFcUExFC9EdLHfmhpYcUpVNRKR1FURQzoUb4iqIoZkIFfEVRFDNh2dEdaIynp6cMDQ3t6G4oiqJ0GXv37s2SUnrV91ynDvihoaHs2aPKhyiKojSXEOKSlebVVEpHURTFTKiAryiKYiZUwFcURTETnTqHryhK91NZWUlqaiplZWUd3ZUuzdbWlsDAQKys6lYIb5gK+IqitKvU1FScnJwIDQ2l8a2DlYZIKcnOziY1NZWwsLBmv06ldBRFaVdlZWV4eHioYN8KQgg8PDxa/ClJBXxFMXPZReW8/tsJNp/MbLc2VbBvvcv5HaqUjqKYqaLyKj7cdJqPNp+muELPhH7ejOld73ods7Vhwwasra0ZOXLkZV/D0dGRoqIiE/bq8qmAryhm6vmf4vhmbypTB/qSkltCVlF5R3ep09mwYQOOjo6tCvidiUrpKIqZOnm+iFG9PPhg7hD6+DiRVWg+Af/6669nyJAhhIeHs3jxYgBWr15NdHQ0UVFRTJw4kaSkJBYtWsQbb7zBoEGD2Lx5M3fccQfffvvthes4OjoCUFRUxMSJE4mOjiYiIoIVK1bU225HUyN8RTFT2cXlhHq4AeDlZENWUQVSSrPIr//vf//D3d2d0tJShg4dyvTp05k/fz6bNm0iLCyMnJwc3N3dueeee3B0dOSxxx4D4OOPP673era2tvzwww84OzuTlZVFbGws1113Xaf7XaqAryhmKruoAg9HGwC8HG2o0BsoKK3Cxb7587pb6/mVcRxNLzDpNQf4O/PctPBGz3n77bf54YcfAEhJSWHx4sWMHTv2whRHd3f3FrUppeSpp55i06ZN6HQ60tLSyMjIwNe3c+1Hb5KUjhDif0KI80KIIw08L4QQbwshEoQQh4QQ0aZoV1GUy1NSUUVJhR4PR2sAAiwLcKaYTDPI42/YsIF169axfft2Dh48yODBg4mKimrWaNzS0hKDQdu5UkpJRUUFAEuWLCEzM5O9e/dy4MABfHx8OuXCMlON8D8F3gU+b+D5qUBv45/hwAfGr4qidIDsIi1QeTpoI/yxOxfwnJUXWUUT6eXt2G79aGok3hby8/Nxc3PD3t6e+Ph4duzYQXl5ORs3biQxMbFWSsfJyYmCgoufQEJDQ9m7dy833XQTK1asoLKy8sI1vb29sbKyYv369Zw502DByg5lkhG+lHITkNPIKdOBz6VmB+AqhPAzRduKorRcdrEW8D0craGiGPu8E/QS6WYxU2fKlClUVVURGRnJM888Q2xsLF5eXixevJgZM2YQFRXFzTffDMC0adP44YcfLty0nT9/Phs3bmTYsGHs3LkTBwcHAObMmcOePXuIiYlhyZIl9OvXryN/xAa1Vw4/AEip8TjVeOxsO7WvKEoN2cbA7uFoA+fjEUgCRSb7zWCmjo2NDb/++mu9z02dOrXW4z59+nDo0KFax3bs2HHh+1deeQUAT09Ptm/fXu81O8scfGi/aZn1Jcfq3T1dCLFACLFHCLEnM7P9Vv4pijmpTul4OFjD+Tjte1FIfn5+R3ZLaWPtFfBTgaAajwOB9PpOlFIullLGSCljvLzUqj9FaQtZxdUjfGvIOHrhuD63c+aeFdNor4D/E3CbcbZOLJAvpVTpHEXpINlFFdhbW2BvbQkZR8BCu3lrUZDSxCuVrswkOXwhxDLgCsBTCJEKPAdYAUgpFwG/AFcDCUAJ8CdTtKsoyuXJLirXRvdSwvmj0OMKOLkGm+K0ju6a0oZMEvCllLObeF4C95uiLUVRWi+7uAIPBxsoOg8l2dBzPFUJf+BYqj54d2eqlo6imKGsogo8Ha21dA6ATzj51r64V55FG58p3ZEK+IpihrKLyrUR/nnjDVvvcErsA/Ank6Lyqo7tXBdUXUQtPT2dmTNnNnrum2++SUlJSYuuv2HDBq699trL7l81FfAVxcwYDJKc4oqLM3QcfcHBgyqnAAJEFlnGKZvmTq/Xt/g1/v7+tapp1udyAr6pqICvKGamoKySKoPUFl1lHAGfAdoTriF4iXyy87r/XPykpCT69evH7bffTmRkJDNnzqSkpITQ0FBeeOEFRo8ezTfffMOpU6eYMmUKQ4YMYcyYMcTHxwOQmJjIiBEjGDp0KM8880yt6w4cOBDQ3jAee+wxIiIiiIyM5J133uHtt98mPT2d8ePHM378eADWrl3LiBEjiI6OZtasWRcWaq1evZp+/foxevRovv/+e5P83CrgK4qZqR7Be9nrIPM4+Gj1bKw8QgAozkjssL61p+PHj7NgwQIOHTqEs7Mz77//PqCVOt6yZQu33HILCxYs4J133mHv3r28+uqr3HfffQA8/PDD3HvvvezevbvBipiLFy8mMTGR/fv3c+jQIebMmcNDDz2Ev78/69evZ/369WRlZfHSSy+xbt069u3bR0xMDK+//jplZWXMnz+flStXsnnzZs6dO2eSn1mVR1YUM1NdViHAcBb05eCtBXwHnx4AVOYkAaPapzO/PgnnDpv2mr4RMHVhk6cFBQUxapT2c86dO5e3334b4EIdnaKiIrZt28asWbMuvKa8XPvdbd26le+++w6AefPm8cQTT1xy/XXr1nHPPfdgaamF2fpKLu/YsYOjR49e6EdFRQUjRowgPj6esLAwevfufaF/1Ru1tIYK+IpiZqoLp/mUndIOGFM6Tj5aLXiZm9wh/WpvdcshVz+uLohmMBhwdXXlwIEDzXp9Xc3ZTEZKyaRJk1i2bFmt4wcOHGiTzVNUwFcUM1M9wncrOgnCAjz7AmDp4k8lllgVprZfZ5oxEm8rycnJbN++nREjRrBs2TJGjx7N/v37Lzzv7OxMWFgY33zzDbNmzUJKyaFDh4iKimLUqFEsX76cuXPnsmTJknqvf9VVV7Fo0SKuuOIKLC0ta5VcLiwsxNPTk9jYWO6//34SEhLo1asXJSUlpKam0q9fPxITEzl16hQ9e/a85A3hcqkcvqKYmeocvl1eAnj0BCtb7QmdBZk6L+xLzGO1bf/+/fnss8+IjIwkJyeHe++995JzlixZwscff0xUVBTh4eEX9qp96623eO+99xg6dGiDBefuvvtugoODiYyMJCoqiqVLlwKwYMECpk6dyvjx4/Hy8uLTTz9l9uzZREZGEhsbS3x8PLa2tixevJhrrrmG0aNHExISYpKfWXTmRRYxMTFyz549Hd0NRelWnv7xMD8fOsv+wDdAZwF3rLrw3JFXxmGpL6ff0zsauULrHDt2jP79+7fZ9ZsjKSmJa6+9liNH6t2kr8uo73cphNgrpYyp73w1wlcUM3NhL9uyPLB1qfVcka0/nnrTzAhROh8V8BXFzGQXVWh18Mvywda11nNlDgF4ylyo7Hz7sZpSaGholx/dXw4V8BXFzGQVl+PpaAOleWBXO+DrnbVtK0qyVF387kgFfEUxM9lFFXg56KCy+JKUjs4tGIDCc6fatA+d+d5hV3E5v0MV8BXFjFRUGcgvrcTfxlgvp05Kx8ZLm4tfltl2q21tbW3Jzs5WQb8VpJRkZ2dja2vbotepefiKYkZyS4yLrqyNOfo6I3xnryAqpAX6rLYb4QcGBpKamoras7p1bG1tCQwMbNFrVMBXFDOSZVx05W1Vqh2ok8P3dHbgoOxJaMauNuuDlZUVYWFhbXZ9pWEqpaMoZiTbuOjK3cJYnrfOCN/D0ZpthnA88uO0WTxKt6ICvqKYkexibYTvKqoDfu0RvpWFjkTHaHQY4Mz29u6e0sZUwFcUM1I9wnemWDtQJ6UDYB0ynHKskIkb27NrSjtQAV9RzEhWUQVWFgJbvbbJRt2UDkBEmC979H2oTFABv7tRAV9RzEhGQRneTraIsjywsAEru0vOiQ52ZZshHOusOCjO7oBeKm1FBXxFMSPpeaX4u9oayypcOroH6OvjxD6LCO1B0uZ27J3S1lTAVxQzkp5fir+rXb1lFapZWuiwCIimBDtI3NTOPVTakgr4imImDAbJufwy/Fzs6q2UWVNUqCc7DX0xnFZ5/O5EBXxFMRNZReVU6mWNlE79I3yA6GA3turD0eUkQEF6O/ZSaUsq4CuKmUjP18op+LsYUzqNjPAHB7ux3aBtbk6iyuN3FyrgK4qZOJunlVPwqx7hN5DDB3B3sKbUvT/FOieVx+9GVMBXFDORZgz4AS5Np3QABoW4s8MwQC3A6kZMEvCFEFOEEMeFEAlCiCfref4KIUS+EOKA8c+zpmhXUZTmO5tfhp2VBS4W5SD1jaZ0QMvjb6jsj8hPgdyk9umk0qZaXS1TCGEBvAdMAlKB3UKIn6SUR+ucullKeW1r21MU5fKczS/Fz9UWUV0UrZGUDmgB/5MLefxN4Bbath1U2pwpRvjDgAQp5WkpZQWwHJhugusqimJCaXllBLgap2RCkyP8vr5OnLMKotDSQ+XxuwlTBPwAIKXG41TjsbpGCCEOCiF+FUKEm6BdRVFa4GxeKX7V+XtoModvoRNEBbmxVzdQC/hqh6ouzxQBX9RzrO6/jH1AiJQyCngH+LHBiwmxQAixRwixR+2IoyimUVFlILOoXFt0Vdq8ET7A4GBXVpf0haIMyDrRxr1U2popAn4qEFTjcSBQa6WGlLJASllk/P4XwEoI4VnfxaSUi6WUMVLKGC8vLxN0T1GUjIIypMSY0mleDh+qF2D11x6otE6XZ4qAvxvoLYQIE0JYA7cAP9U8QQjhK4QQxu+HGdtVZfgUpZ2k15qD35IRvhsp0psCGz9Q0zO7vFbP0pFSVgkhHgDWABbA/6SUcUKIe4zPLwJmAvcKIaqAUuAWqbasV5R2c9a4ytbPxQ5S8wEBNk0HfHcHa8I8HTlEFKMTN4PBADq1fKerMskm5sY0zS91ji2q8f27wLumaEtRlJarXnTl72qr5fBtnJsduAcHu7L6WB9Gy9WQcRj8otqyq0obUm/VimIGzuaX4mpvhb21pZbSsWt6dF8tOtiN30r7aA9UHr9LUwFfUczA2TxjWWRoVlmFmqKD3cjAnULHMBXwuzgV8BXFDKTllRLgaqs9aKJSZl19fZ1wsLbgqM0gOLMN9JVt1EulramAryhm4Gx+nRF+M6ZkVtMWYLnye3lfqCiC9P1t1EulramAryjdXHF5FfmlldqUTGhyt6v6RAe78X1OmPZATc/sslTAV5Ru7my+sSyy6+Xl8AGiQ1zJMjhR7NZf5fG7MBXwFaWbS8+rMQe/qgIqS1oc8AcHuQFw0j4akndCZZnJ+6m0PRXwFaWbO5FRCGAsnGZcZduCHD6Am4M1PTwd2FTVH/TlkLrb1N1U2oEK+IrSjaXllfLW7yeJDnYl0K1GHZ0W5vBBK7PwbWYwUliotE4XpQK+onRTeoPk0a8OYDBI3rx5MEKIGpUyWzbCBy2Pn1xiSYV3lAr4XZQK+IrSTf130yl2Jebw/PSBBHvYawdbUCmzruhgLY+f5DwE0vZAeZGpuqq0ExXwFaUbOpSax+trT3BNhB83RtfYj6gFlTLr6uOjLcDaU9UTDFVw/piJequ0FxXwFaWbKamo4pHlB/BysuHlGwZirEyuKbv8lE71Aqw/cjy0A5kq4Hc1KuArSjfz4qpjJGYX89pNUbjaW9d+sgW7XdUnOtiNjZn2SEs7OB/fyp4q7U0FfEXpRtbGnWPZrmQWjO3ByJ71bCpXlgcWNmBle1nXjw5xpcogKHbuqUb4XZAK+IrSTZwvKOOJ7w4xMMCZv07qW/9JhefAyeey26hegJVqFaJG+F2QCviK0g0YDJK/fnOQ0ko9b948GGvLBv5r56WAS/Blt1O9AOtIhR8Upl9MESldggr4itINfLotic0ns3j6mgH08nZs+MS8ZHC9/IAP2gKszfnGdFGmGuV3JSrgK0oXF3+ugIWr47myvzdzhjcSzKsqoPAsuAa1qr3Bwa7sLfXVHqipmV2KCviK0oVJKXn0q4M421qx8MbI2lMw6ypIAyS4tC7g9/FxIk16orewUyP8LkYFfEXpwlJzSzl2toAHJ/TC09Gm8ZPzU7SvrRzh9/J2RKIjxz5MjfC7GBXwFaULO3q2AIDIwGbMq89L1r62coTv7mCNm70VZyxD1Qi/i1EBX1G6sKPpBegE9PN1bvrkvBRAgEtgq9vt6eXIsSp/KMqAkpxWX09pHyrgK0oXFpdeQA8vR+ysLZo+OT8FnHzBsonUTzP08nZkV7G39kCldboMFfAVpQs7draAAX7NGN2DltJpZTqnWk8vR/aWGGfqqBW3XYYK+IrSReUWV5CWV0q4fzMDfn5Kq2/YVuvp7UA6HuitHNWK2y5EBXxF6aKOGW/YDmhOwDfoIT/VZCP8Xl5OgCDPoYe6cduFqICvKF1U9QydZqV0Cs9pNexbucq2WoCbHdaWOlIsgyEjDgwGk1xXaVsq4CtKFxWXXoCvsy0eTc2/hxpz8E0T8C10gh6eDuwkAkpzIGmzSa6rtC2TBHwhxBQhxHEhRIIQ4sl6nhdCiLeNzx8SQkSbol1FMWdH0wual84B45RMTJbSAejp7ci3JYO02vr7PjfZdZW20+qAL4SwAN4DpgIDgNlCiAF1TpsK9Db+WQB80Np2FcWclVXqScgsasENW+OiKxPdtAVtps6pXD1VA2+CYyuhNNdk11bahilG+MOABCnlaSllBbAcmF7nnOnA51KzA3AVQviZoG1FMUsnMgrRG2TLpmTauYO1g8n60MvbEYOElNAbQV8Oh74x2bWVtmGKgB8ApNR4nGo81tJzFEVpprj0FszQAS2lY6L8fbWeXtqbx1FDKPgN0tI6Upq0DcW0TBHw6yvPV/dvvTnnaCcKsUAIsUcIsSczM7PVnVOU7uhoegFONpYEudk37wUmnINfrYenI0JAwvkiiJ4HGYfh7AGTtqGYlikCfipQ819SIJB+GecAIKVcLKWMkVLGeHl5maB7aKOOlF1QVW6a6ylKBzt6toD+fs7odI2UQ64mZat3uqqPnbUFAa52nMosgoEzwdJW3bzt5EwR8HcDvYUQYUIIa+AW4Kc65/wE3GacrRML5EspzzZ5ZVMEaH0VrHoUPp4EX94IZQWtv6aidKBKvYG49HzCA5qZzinJhqpSk4/wwXjjNrMI7Fyh37UQ96O2yEvplFod8KWUVcADwBrgGPC1lDJOCHGPEOIe42m/AKeBBOBD4L5mXbwku3WdqyiBr+fB3k84HzgZmbwdPr0Gis637rqK0oGOnS2grNJAdLBb816Qd0b7asIpmdV6eWsBv6LKAH2maHPyVVqn07I0xUWklL+gBfWaxxbV+F4C97f4uiU52mhB14xKgHUZ9LBkJvLMNr50u59nEkYx2z2Wl7P+je7DiRA+HXwiwHcgePYBC6uWt9Fcxdmg04FdM/+DKkoj9p3Rpj9GhzTz31PmCe2riW/aAgwNdePjLYnsT85leM/xgICEPyBgiMnbUlrPJAG/rQhDJZzeAL0mtvzFx1bCma28an0fi8+P5t4revDVbmvOVD3NBxZf4bxzMUJvTBlZWINX34tvAD4DwX8w2DbzI3NNBoNWWyRlp3bfIGUn5JwCJz9YsBGcfFp+TUWpYV9yHj7ONvi72DZ8UlU5xK+CvZ9B4kZtSqZ7mMn7MqKnJzoBWxKyGN6jL/gPgoR1MO5vJm9Lab1OHfD1WCAPLEW0NOBLSeWm10nDj+8MV7B8wTCGhLgxNzaEe76wIyotjBBXa6b3KOEKlwz6izPY5RyDU7/DwaXaNawdIfp2iL238dynlHBmKyRt1YJ76h4oz9ees/eEoOEQeRNsfQu+uR1uX9m2nyaUbm9fci7RwW7171+beQL2fQYHl2kpUZdgGP93GDwXbJxM3hcXOysiA13ZfDKLv17VF3pOhC1vQGmeltdXOpVOHfDzpAPy2EpES//xnN6AVcZB/ls1ny/vG0Uvb0cAAlzt+OaeEXyzN5XNJzL55KSBt8sCgAD6+01hdH8PxgUKYmxSsT36DexcBLv+C+OegHGP19/WjvdhzVOAAJ9wGDhDC/JBw8C9B1T/p/ToBd/dBWv+Dlf/u1W/F8V8nS8sIzW3lNtHhF48WFkKR1fA3k8heTvoLKHv1TDkdugx/vJSoi0wtrcn765PIL+kEpdeV8LmV7VPFQPqrr9UOlqnDvi5OKHT50PcDxDzp2a/rnT9qxRIV6wGz74Q7KvZWlkwLzaEebEhVOkNHEkvYGtCFltOZvHZtjN8qDdgZSGYHH4Pr9zzfzhteh7WvwwePWHgjbUbStkNvz1LZe+p5E9+B2lTJwVUVHHhW9cBM7BK2wc73tPSRYNmt/j3oSj7zuQBEB1iHAAZ9PDfcZB1XBtgXPk8DLoVHL3brU+je3vx9h8JbD+dxZT+MWDjDAm/q4DfCXXqgG9p48Bp4UzYgaWI5gb8tL3YpW7hHTmXB64Kb/z6FjoGBbkyKMiV+8f3orRCz54zOayPz+Tz7UkcTS/gv7e+Ru+CdFjxAHj21XL8ACU5VH51G/k6LyYcnkHB4V2NthXm6cDyu/4Pn3OHYOVDWj41OLZ5P5OiGO1PzsXKQhDub9y0vCBdC/ZXPKV9Cq0vzdPGBge74mBtweaTWUwZ6Ac9xmkBX8oO6Y/SsE4d8F3srVhWMYq/py6FlY/AqIebvPGUv2YhSHvsY+/E26mRm1r1sLO2YExvL8b09mJyuA/3L93H9Yt288pVC5mWdyti+a0w+Z/k5WaStfVzgorOc698gdnjIghsZMVjeaWeN347wdxP9vH1bR/htvRqWH4r3L1OG5UpSjPtT84j3N8FWytjmiY3UfsaNKzDgquVhY7YHh5sScjSDvScqE2ayDqhTYZQOo1OHfCd7az4islM9iwh5sASbRVfxEwY/Sh497/kfMPez3FJXsu7utncPiGyVW0P7+HBqgfH8MDSfTy06izrPB7ljeK/Y/HVHFwBJyn4NeRvfHDT3Xg2ox55uL8Ld3yyi9uWJbBs5lIcv5gCS26CO9eAg0er+qqYh0q9gUNpedw6LOTiwdwk7WsbzMBpidG9Pfk9/jwpOSUEVU+ySFinAn4n06k3QLEQgti+gdyXP4+CBXu1GTPHVsH7sbB8DqTuvXjyucMYfn6MLfpwfK5+Eifb1s+E8XWx5es/j+C9W6M5IvoypuQ1ple8xD97L+fcfSe49s6/NyvYA4zo6cEHc6M5draAa5ecZUvMW8jcJHhzIPxwDyRtUYWnlEZdWHAVUmMCQ06idpPWObDjOgaM6e0JwOaTWdp8f88+cGp9h/ZJuVSnHuEDzIoJYu3RDAa9eZiIgKlMjLyO6ytWEXTyc0T8KggbB7H3UbrqcfL19qzp9zIvxIQ0feFm0ukE10T6MTnch/XHMwn1sKe3z+VNb5vQz4fP7hzGCyuPMvc3S671eo07bX4n/PAKbA4uozDiDpxmvKnynkq9Liy4qrnCNjdJW0Fr0bH/lXt6OeLnYsvmk5ncOjwYQkbCkR+0dSm6Tj2uNCud/m9i0gAfvr9vJA9O6I21pY63t2czdncsQ4reYKnLfIrTjsCym7EqTGGhwxM8OXNM/fOTW8nSQsekAT6XHeyrjerlyS8Pj+GNm6M4Rgjzzt/KyKpFfKS/GqfDn1K06v/USF+p177kPHydbfF3tbt4MDexw9M5AEIIRvT0YFdiDlJKCIrV1qNkHuvorik1dPoRPmgjmuhgNx6d1Iei8ip2JWazNSGbzxM8eb5gFNdbbKVE58yDt8/Fwabz/2xfJmMAACAASURBVEgWOsENgwO5YfDFj+GHU8ax7KMFzN77AcU2Tjhc9fcO7KHSGSWcL6KfX50BR04iDBzcMR2qY3CQK9/vSyMtr5TA4OHaweQd2voUpVPo/NGxDkcbSyb082FCP61EwfnCMrafGkaAqx19Wjn67kgRQa6U3fE+P3zyJ27Y9m8yUg7jeP2rOHh0bG5W6RyklKTklBATWiOdU5oLZXng1vEjfICoIO3ewsGUfAIjwsDBW1t9PvSuDu6ZUq3Tp3Sa4u1ky/RBAcSEund0V1ptaJgnHrcu5h3DTbgmr8Pwdgwfvv4Ub649xu6kHCr1ho7uotJB8ksrKSyvIti9xvTfTjJDp1o/X2esLXQcSs3T7kMFD9dG+Eqn0eVG+N3d2H5+DHtmEUcOL8Bj41PMz3+PA1tW89T6uzhj1ZNhYe6M6uXJqF6e9PN1apP7FUrnk5JTClB7vUd1wHcLbff+1MfaUkd/f2cOpGirgQmK1ebjF54DJ9+O7ZwCqIDfKdlaWTAkeigMXguHvyVy9f+xqvQZtnvO4uWs63npuLb1o6ejNSN6ejIvNoRhYV3/E47SsOScEoDaI/wc46KrThLwAQYFuvDN3lT0BolF9Ury5B0Qfn3HdkwBukFKp1sTAiJnoXtwN7ro2xiVuZxfLB5j36wK/jMzktG9PNmWkMWfPtml7TrUVpJ3wAejtOJcSodIydUCfpB7zRk6SVpF1jaognm5ooJcKanQa/vc+kZq2x6m7OzobilGKuB3BXZuMO1NuHMt2DrjvvIOZp18gjeneLDqodFYW+q4f8k+SitMvLWcwQCbX4dProbzx+DnxyBtn2nbUJolJacEV3ur2gsKO8mUzJou3rjNA0trbSMUlcfvNFTA70qCh8OfN2kVEU+vh3eH4Xfofd6cOYDjGYU899MR07b3/d3w+/Mw4Dp4aB84+sC3f4KyfNO2ozQpOaekdjoHtBF+J5mhUy3MwwEnW0sOpFbn8YfBuUPadqNKh1MBv6uxsILRj8D9u6D3lfD7C4z7ZQJfh/7Cgb3bWXEgzTTtnD8GR77TCtbN/ETLE8/8GPJSYOXDanFYO0vNLSWo5g3bqgrIT+1U+XvQVqZHBrpoM3VAu3FrqIK0vY2/UGkX6qZtV+UaBDd/Caf+gF0fEXNyOWttvuTDVQeoingfS4tWvpfv+xyps+JY6B3o0wqw0An6BQ5HN+FpbdSPgCkL1ZaN7UBvkKTmlnBVeI3fdX4KSEOnS+kARAW6snjTacoq9dgGDdMOHv0RQkfXWzakrFLPvjO5bEnIYuupbDLyy4gJdWN0L08m9PducdVbpWEq4Hd1PSdAzwmIokwyly5gTtr3rN19P1fHRlz+NSvLMBxYxu8yhvkfX1waPyM6gNdmPozQV2q7Gp36XUsvRd+u6qW0oYyCMir1ss4c/M43Q6daVJArVQZJXHoBQ0Lcte0Vd38EOiuY/E/0CI6mF2gBPiGL3Uk5lFcZsNAJBge5MjTMnZ2ns1l16Cy9vB1Z95dxHf0jdRsq4HcXjl54XL8Q3h9O4fo3MAz7GJ3uMufox69CV5bLl5VX8NqsKFzsrNiSkMWn25IYHubOzVc8AeE3wKpHYdUj2v6p174JPgMavqa+CtY+rdVWcQ4El0CImAWevS6vj2YkxTgls1ZK58KUzM43wh9U48btkBA35LS3KdTb4LzzA3bHHeeeorvJLtPO7ePjyK3DgxnV05PhPdwv3JSWUvLvNcf5YMMpSiqqsLdWocoU1G+xG9F59yUlYCrXpq5i88F4xg2+dM+A5sjf8iH5Bi/CR1/HjUO00g7j+3mTcL6IZ1fEERnoSn+/PnDHKjiwFNb+Hf47Rsv3j/0bWNnVvqCU8MtjsPcTbape5nFtMc7Wt2DySxBzl6oQ2oh65+DnJmlTHjvhgiYfZ1t8nW1ZuDqet34/icEgKSyfwL0WxTxRtJxlDgWcuPoDhvULxtu5/nSNEOLCG8fJjKILs3+U1lGfw7sZv2nPYCcqyPztDa1qYQuVZ5zEJWMHa2yu4qErL25eYaETvHnLIFzsrLhvyT7ySyq1ID14DjywByJugs2vaXsVnPqj9kU3v6oF+9GPwj2b4a/x2p+QkfDzX2HpTdrmNvG/QMouyD4FpXnqxrBRSm4pQlCnSmaSls7ppG+Uz08P59ZhwdwwOIAbhwTy4vURzHrkNeR179KnZD/X7p+Pt66w0Wv0NdbGOp7R+HlK86kRfjdj6TuAJN9JTD77E3vin2Bo/54tev3Bn94hWuqIuvb+i9voGXk62vDO7MHM+WgnU9/axOs3DyK2hwc4eMINH2gbs698BL64AULHaBtpSwlx30PkLTDxuYsXc/KFud/Brg/ht2fh5NpLO6OzAnvj9X3CYdKLZnmTODWnBD9nW6wta4zPOuGUzJomh/syObyeTx9e88DBC765A/53Fcz9vsEbz0Hu9tha6ThxTgV8U1Ej/G7Ib9qzOIlSMv94v0Wv01dVEZq2kjj7YQyLqr+k7fAeHnx770isLXXM/nAHC3+Np6LKWNQtbCzcuw2u+D9trn76ATizDcJnwHXvUFKpv3guaKPT4QvgiSR45DDMXw9zvoXrF8FVL8GI+7Wppy5BcHSF9unh2MrL/K10Xck5JQTVTOdICblnwM10G/20q75T4PafoCQHPr4Kzh6q9zQLnaC3t5Ma4ZuQGuF3QzYBEZx0GELU+RWUlb+CrY11s16XsHs1fcnhzMBZjZ43KMiVnx8aw0s/H2XRxlNsOpHJ27MH0cvbCaxs4YontT815BRXMO2dTVQZDDw8sQ+zYgKxqp46amWrbYvnGtxwo5nH4fv58NVc6DMV+l0DvSaCkx/oK6CyBGxdO22KozVScksY09vr4oHSXKgoBNcuGvBBW5B15xr4coa2knv2Um3AUEcfHye2JGR2QAe7JzXC76aqBt9GgMjkyKYfmv2a0r3LKZJ29B17U5PnOthY8sqMSBbPG8K5gjKueXsLn29Pqve+gcEgeeSrA2QWlePrYsdTPxxm0usb2ZqQ1fwfyKsv3LUOxj0BZw/ATw/A6/3hRU94yRv+FQq/PdP863URZZV6MgrKa8/QyTujfW3sDbIr8O4Hd/0GLgHwxQzY8gYYapcH6evrSEZBOXklFR3Uye5FBfxuqvfYW8jGBcsDnzfrfFlZSq+s39nvOBpnJ+dmt3NVuC+rHxlDbA8Pnl0Rx58+3c35wrJa53xg/BTw3LQB/HjfSD66LQadTjDv450s2niq+TeXLa1h/FPwl2Na6mjSizDyIZjwDPSaBDs+gKyEZve9K0jN1coiB3vUvGFrDPhdNaVTk0sA3LlaS/Os+wd8MlW7aW9UvanRiYw2LA5oRlqV0hFCuANfAaFAEnCTlDK3nvOSgEJAD1RJKWNa067SNEtrW+K8pzEyYyklWSnYewY1en767hUEUELFgJktbsvbyZZP/zSUz7ef4Z+/HGPKm5t5aEIvnO2syCmu4LW1x5k+yJ9bhwUjhODKAT6M6OnB498eYuGv8RxMyWPSgMZvxga52zO0epMbIbSbuDW3zivMgHeiYd1zcMuSFv8MndWFKpm1RvjJ2teuPsKvZucGN30Bh7/Rpu8uGgPXvwfhN9DX9+JMHVUCvPVam8N/EvhdSrlQCPGk8fETDZw7XkrZgs/wSmu5jLoLyx++5OTvi+l/84uNnlu6dznnpSsRo6ddVltCCG4fGcrInh48vPwA/1h59MJzfXwc+ecNEbU2a3GwseTdWwcTucmFf62O59cj55psY/6YMJ6Y0q/+shFOPtq0zz9ehMTNEDbmsn6Ozialvjn4ecna/Qpblw7qVRsQAiJvgpBR2gyeb+6A1D34XvkPnGwtu+1MndTcEn7cn8a9V/TC4nIXSrZAawP+dOAK4/efARtoOOAr7SwiYjC7foyk54nloH8OLBr46y7NJSR7M6vtr2Wai0Or2uzt48TKB0eTlluKREvV+LrYYmNpccm5Qgj+PK4nM4cEUlRe1eA1pYT/bU3kw82JxKUX8M7swXg42lx64oj7Yc8nsOYpWLCxW5R7SMkpwcZSh5dTjZ8370z3Gd3X5RIAd/wMa/4Ptr+LyEumr8+93XamzrMr4vgj/jzRwW6M7OXZ5u21NuD7SCnPAkgpzwohvBs4TwJrhRAS+K+UcnEr21WaQacTpITdxLDTTyNf9MRg7Qy2LujsXRDVI0RbV0rzM7Gj6rLSOfWx0AmCPeybPtHIw9Gm/gBewwvTBxIR4MLffzzCpDc28cD4XsyJDa79RmJlB1f+QyvrvHGhNj20i8/aOVdQjq+Lbe2tLPOSwaMbl6SwtIZrXtM2dtnyBsP638bS4xIpZbfa0nPH6Wz+iD8PwMpD6Z0j4Ash1gH1rd/+ewvaGSWlTDe+IfwmhIiXUm5qoL0FwAKA4OBuOoppR33Hz+H/jifiK3JwrirBubQYl9wS3C2ycNMl4yyKsTcUs9vQh+jY8R3d3UbNigki3N+Fl34+ygurjvLxlkTmjQhhdC9PBvg5a7WDImZqK303/gsqirX5/F04SGQVluNZ881QSi3g97qy4zrVXobcAVveYGLVZt4vGU5mYXmDpRi6GiklC3+Nx9fZlkFBrvx65BzPXzew9uK6NtBkwJdSNvgvSwiRIYTwM47u/YDzDVwj3fj1vBDiB2AYUG/AN47+FwPExMSotfWtNDDInfv++hKpuaVkF5eTVVhOUnEFWUXlZBZWaMeKyunr48xHXo4d3d0mDfB3Zun8WLaczOK1346z8Nd4ANwdrHn+unCmRfnD9Pe00eH2d6EsD6a9DbpLU0pdQVZROT28aqTZirO0NQfdNaVTk1soBA6lX9YaYDjHMwq7TcBffeQcB1Ly+PeNkXg4WrM67hxbE7IY36+hJIlptDal8xNwO7DQ+HVF3ROEEA6ATkpZaPz+KuCFVrartECQu33tlZrdwOjenozu7UlGQRnbTmXx8ZZEnvr+MMPC3PFxtoWp/wI7V22kX1YAN34Elo2njTqjrKLy2rNTLszB7wZTMptj4EwcVj9BL5HK8XP9ay9A66Iq9Qb+s+Y4fXwcuXFIIHqDxNnWkpUH09s84Lf288NCYJIQ4iQwyfgYIYS/EOIX4zk+wBYhxEFgF/CzlHJ1K9tVFECrzHjD4EDemR1Nud7A8yvjtCeE0ObsT34Fjv0Ey27RUjxdSKXeQG5JZe2UTndZdNVc4TeA0HGL7S6Od5OZOq+uPc7prGIen9wPC53A2lLH1IF+rD2aQVmlifelrqNVAV9KmS2lnCil7G38mmM8ni6lvNr4/WkpZZTxT7iU8mVTdFxRagrzdOChCb345fA51h3NuPjEiPu0FM/pDVpRt9JLlol0WjnF2upSz1ozdLrZHPymOPlA2Fim6bYRf7ago3vTaisOpPHfjaeZMzyYK2usPZkW5U9ReRUbjtebFTeZrj9vTVGMFoztSR8fR55dcaT2NM/Bc2HWp5C2Dz69Fora9j+VqWQWlgPg5VijFlLuGa2CqE3nv99iMgNn4qNPx+b8ASr1hqbP76SOpOXzxHeHGBrqxnPTahcnjO3hjqejNSsPnm3TPqiAr3Qb1pY6XpkRwdmCMu78ZHft+isDpsOtX0HOafjf5Isj5U4sq0gL+LVTOsnmM7qv1n8aep0VV7OF05ldKy1XLauonAWf78Hd3pr35wy5ZDaOpYWOayP9+e1oBqcz266MhAr4SrcyJMSdt24ZzIGUPG78YNuFlaqAVl1z3o9Qkq1VaCzL77iONkNWkTGlUzeHby43bKvZuVLcYyozLTZyIqnzv1HXVVFl4L4v95FdXMF/58XUXkRXw31X9MTGSsfffzhyWZsXNYcK+Eq3c12UP5/fNYzMwnJmfLCNI2k1AnvwcK3mfn4q/N54uYmOdmGEXx0gDAbISzG/ET5gP+FxnEUpLgc/6uiutNgLq+LYlZTDv2dGEhHYcDkMb2dbnpzaj+2ns/l2b2qb9EUFfKVbijVu1GKlE9z03+21b4YFDYNhC2D3R5C6p/4L6Cs7fIvFrMJybK10OFgb1xAUZYC+3CwDvqV/BNusRzLk3Ffa9pddxNKdyXy5I5k/j+3B9EEBTZ4/e2gwMSFuvPzLMbKNb/impAK+0m318XHih/tHEeLhwF2f7eHr3SkXn5zwtLZ5ysqHteBerbwINv4b/hWm7bfbgbKKtFW2F8oJVN93cAvtsD51pD0h83GQxcgdH3R0V5plT1IOz/10hLF9vHh8Sr9mvUanE/xzRgTF5VU8uyIOg8G0gw4V8JVuzcfZlq//HMvInh48/t0h3vjthJYftXWGq/8NGUfgpwdhw7/g1yfhrShY/7I2HXDPx9p0zg6SVVRx6Q1bMMsRPoBbzyGs1Q/RAn4nv/9yNr+Ue77cR4CrHe/cMrhFlTD7+Djxl0l9+fnwWV5YddSk+XwV8JVuz8nWiv/dMZSZQwJ56/eTPP7tIW16X/9pEHETHFwGG/4J+z7TauzftQ7u2QLuPeGnhzpswVb1CP+CvCTtq5kG/AF+zrxVNQNdeT58fRucXHfJDlkNKi+Edc/DmpaUALs8ZZV6/vzFXkorqvjwthhc7K3qP1FKOPQ1/HgfVNVO39wzrgd3jQ7j021JvPHbCZP1Te1pq5gFKwsd/5kZib+rHW//fpLs4go+vC0GixmL4er/gLXjpeWjp7+r7cD0x0sw5ZV273NWUTmDg90uHshLBgdvrSqoGerv58RRwtgU+hBjzy2BJTdqG9wPnguD5oBrPZv8VAfV356FIuOeC4Pngnf/Nuvn8yvjOJSaz4e3xdDbuGPXJYrOw6pHIX6V9rjv1dD/2gtPCyF4+pr+FJVV8fYfCVToJY9O6l1vmfGWUCN8xWwIIfjLpD48N20Af8SfZ8nOM1oJBjvX+vcKCBkJQ+/Wtk5M2d2ufdUbJDnFFbUXXZnjHPwa7K0tCfN04EvddPhLPMz6DDz7wIaF8GYEfHkjHP0JqozrL5J3wEcT4YcF4OwPt34NOivY90Wb9VFKycqDZ5k5JLDhXdzObIP3hsPJ32DSC2DnDnHfX3KaEFo+/5ahQSzaeIqpb25u2T7Q9VABXzE7d4wMZUxvT/69+jjn8ssaP/nKf4CTr7YhRzvO2skprsAg65ZVSKl/FGtGBvg5E5deoNXMD78e5n0PDx+EcY/D+WPw9Tx4YwB8dp22wK4gHaa/D3f/Dn0mQ7+rtRRelelnwACk55dRVF7FoCDX+k849Ye2Ybu9h5Y2HPWwtijw+GqoKLnkdAudYOGNkXxx1zAMUjLno508vHz/JftGN5cK+IrZEULw0vUDqdQb+MdPcY2fbOOkFWFL3Q1HLykG22YuWWUrpbZ2wCWw3frQGYX7u5CWV0p+SY2ZVW4h2t/RI4e1NRZBwyHrBFzxFDy4FwbPubj7WfRtUJpzMZViYtVbMVbvxVtL/C+w9Gbw6Al/+hW8+mjHB86AymI4uabB647p7cXqR8by8MTe/Hr4HBNf28gXO86gb+EsHhXwFbMU4uHAw1f2ZnXcOb7anUz8uQLizxXUDiTVBs0B7wGw7h8X0wVt7JKAX5ypzcF3Md+UDmj7IQDEna1nlo7OAnpP0jax/2s8XPEEWNfZsrPHBO13uO/zNulf9VaMfbzrBPwj38FXc8E3Am5fCY41yjyHjAJHHzhyaVqnJlsrCx6d1IfVj4whMtCFZ348cunCwiaogK+YrfljetDP14knvjvMlDc3M+XNzQx6cS3T3tnCwl/jySgwfmzWWWi51txE2PO/dunbxYBvzOHnG9cQmPkIf4CfFvAPp17mtEydTrtpe3oD5CaZrF/VTpwrxNfZtvbMnP1fwnd3a5885v0I9u61X6Sz0NI6J9dqs4ma0MPLkS/vGs5btwwiLbeE697dwj9+iiMuPb/Jeftqlo5itqwsdCxfEMv2U9mAtvHyyYwitiZk8dHm0xxJy+fLu4drJ/e6EsLGaRuq9JkM7mFt2reswjqlkfOMAd/Mc/heTjb08XHkP2uOk19ayYMTemNn3cKZK4Pnanse7/sCJj5j0v4dzyikT810zq4P4ZfHoOcEuHkJWDewEVH4DNi1WMvlR85qsh0hBNMHBXBFX29eXXOcz7Yn8em2JDwcrBt9nRrhK2bN1d6aqRF+TI3w4+oIPx6+sjdf3zOCxyb3ZUtCFodSjcv4hdD2x60shXeGwPcL4Hx8m/Urq6gca0sdTjbGMVm+sbaKmY/wAZbNj+X6wQG8v+EUk97YyPr4Fpa7dgmA3lfB3k+1v08T0RskJ88X0dfHWLp6y5tasO97Dcxe3nCwB2307+Rf72ydxrjYWfHi9QPZ/uREXpsVxbg+je8IpgK+otRjzvBgnG0teX/9qYsH/SLhoX0Qey8cWwnvD4flc7Q6+yaWWVSOV82yCvmp2loB2wZmf5gRD0cbXp0VxfIFsdhaWfCnT3dz75d7OZvfguA98kEoydLSLSZyJruYiioDfbwd4Y+XYd1zMPBGuOmzprfX1OkgYiacWA2Jm1vctq+LLTcOCeT1mwc13kyLr6woZsDJ1orbRoSy5ug5Es7XqE/u7A+TX4ZH42DcE5C0GT4cD1/N06pZmkhmYfnF/D1oOXyXIO2ThgJoBfJ+eWgMf5vclz/iz3Plaxv5eEsiVXU2SanUG9h7Joe31p3kix3GLSJDRkFADGx7B/RV9Vy95U4Yb9iOTVsMm/6tpY5mfAgWDay0rWvc49rq7m/vhMJzJulTXSrgK0oD/jQqFBtLHYs2nrr0SXt341TAI9po8dhPkLTJZG1fUkcnP0Wlc+phbanj/vG9+O3RcQwNc+fFVUe57t2trDuawcdbErnr090MfuE3bvxgO2+sO8E/forTbogLAaMf0fYXOPqjSfpy/FwR7qIA70OLIGIWTHtHuyHbXDZOcPMXUFGkBX0TvRHVpAK+ojTAw9GGW4YG8+P+NNLyGkgX2DrD+Ke1VIsJV3BeWkdHLbpqTLCHPZ/cMZQP5kSTXVzO3Z/v4cVVRzmVWcR1g/z5YE40yxfEojdIfj1iHD33vQY8esPWN02yqO5ERiF3OO1BGCph1CMX5/63hHd/mPYWnNkK619qdZ/qUrN0FKUR88f2YMnOM7z7RwKvzIio/yQrW4i8CfZ+pm2SbudW/3nNZDCWVfB0MqZ0Koq1xUJqhN8oIQRTI/wY08eL7aeyGeDvTIDrxbpDUkp6ezuy8mA682JDtIA86iGtWuqm/4CDF1SVaTdy6361sNbObaQ09fGMQh5nA/hFge/Ay/9BIm+CxE2w9W1tDYhn78u/Vh0q4CtKIwJc7ZgzPIQvdpzh7jFh9PRqYPPwwXO1aXWHv4Vh81vVZm5JBXqDvDjCz0/TvrqoEX5zONpY1lvHRgjBtCh/3lh3grP5pfi52EHkzdr+B+tfvvRCFjbam7mlnVaO+dBXMPmf2mrdOvdSyqv02GQfJcQqAQb9p/U/xMTnIO5H+O05mL209dczUikdRWnCAxN6YWup4z+rjzd8kl+UtorSBLM+LtnLNt9YB18F/Fa7NtIPKeHnQ2e1A5Y2cM9meHAfPHoUHk+Ep87Cs7nwzHl4MhkeOw4P7IaAaFj5kDYzq05+/XRmMTPERvQ6K222TWs5emn3GI7/rBVbMxEV8BWlCZ6ONiwY25PVcefYeya34RMHz4OzB+Dc4Va1d0lZBTUH32R6eDkyMMCZldUBH7QUnEdPbX6+vbs2X75u/t01COatgInPakH44LJaT588m831FlsoCb3q0pW0lyv2Pm1u/tqnm3+PofrTYANUwFeUZrh7TBiejjYs/PUYeSUVl/wpr9JrMzMsrGH/kla1VR3wvapz+PmpICy0LRmVVpsW6c/BlDySsy+tTtkonQ5G/0WbzrnhFai8WLGyKn4tHqIQ26HzTNdRa3ttJXDaXvjjRUhYB5nHtW046xP3A3wwstFLqhy+ojSDg40lD1/Zm2d+PMKgF3675HlPR2u2PDEB2/7XaTtnDZ6jpXguQ2quNiPIy9FWO5CXos3/r69mv9Ji10T68cqv8aw8lM7943u17MVCwJXPwWfTtC0wR9wPZflEJ31EtnDDo88k03Y28mZt9tfm17Q/1WxdtBSfc4D2yaQkR5teGjAESG7wcupfkKI0063DgnGxsyK7qHYt9dTcUj7eksj209mMn/yylnNdNhvmr69dFbEZpJSsOJDGoCDXiwW4VFlkkwp0sycmxI3v9qVy3xU9L65mbq6wsdBjPGx6FfpPQ359GwEVp/ky8Hn+ZOo3ZZ0F3LEKCtK0dE1+KhSkal/z07TvU3dpM7nGPq4t3lrQcD0dFfAVpZksdILrovwvOV5WqWfpzmTWx59nfN+BWnneT6Zq+67etkLbrKOZ9qfkcSKjiIU1p4DmJ0NQrCl+BMXo1uHB/OXrg2xNyGZ0b8+WX2Dis9oK6/diQV/JnyseYWLENNN3FLSg7xrc+G5nBkOz5v2rHL6itJKtlQWjennwR/x5pJTabI7p70HyNvjx3hbV0P9qVwoO1hZMq35jMei1XZvUCN+kro7ww93Bms+3J13eBQKitTo50sDWYe/yhyGa6ODWrb9olWYu8mpVwBdCzBJCxAkhDEKImEbOmyKEOC6ESBBCPNmaNhWlM7qirzepuaWcyizWDkTM1OZSH/kWvrhey7E2oai8ipWH0pkW5Y9DdZXMogwwVKmAb2K2VhbcMjSIdccyGl5F3ZTrF8Ejh1hdNgBHG0v6NLRheSfS2hH+EWAG0GARESGEBfAeMBUYAMwWQgxoZbuK0qmM7+cNULtU75i/wIyPIHUPfDgBsuupyVPDyoPplFTouXlojfn2F+rgm/dOV21hTmwIAEuqC6q1lKU1OHqz70weUUEuWOg6f2G7VgV8KeUxKWUjq1EAGAYkSClPSykrgOXA9Na0qyidTYCrHX19nFh/vE5t9shZ2k23khz4o/HaKMt3p9DXx6n2DXCIlwAAEhpJREFUBthqp6s2E+Bqx5X9fVi+O4WySv1lXaO4vIr4cwUdm85pgfbI4QcAKTUepxqP1UsIsUAIsUcIsSczM7PNO6copjK+nze7EnMoLKuzL27QMOg7RSul3MACmmNnCziYksfNQ4NqzxpRAb9N3T4ylJziCl5YdZTv9qay8UQmcen5ZBSUUalvutz1wdQ8DJIuE/CbnKUjhFgH+Nbz1N+llCua0UZ9n3MaXDYmpVwMLAaIiYlpfQk7RWkn4/t6sWjjKbYmZDFlYJ1FUqGjtVosWSfAq+8lr/1qdwrWFjpuGFxnLJSXrFXitOn8+eGuaGRPDwYFubJ0ZzJLd146f93N3goPRxuC3Ox4cmp/+vrW/nvYn6ztiDY4uGtsTNNkwJdSXtnKNlKBmkVAAoH0Vl5TUTqdISFuONla8kf8+foDPkDSlksCflmlnh/2pzF5oC9udfckzTxe7xuEYhpCCH64byRF5VVkFVWQXVROVlE5WUUVZBWVk238uisxhxve38p/ZkZxTeTFv9v9ybn08HLA1b75U287UnvMw98N9BZChAFpwC3Are3QrqK0K0sLHWP7eLHxRCZSytqpGbcwbVVk0hYYelet162JO0d+aSWzh9YpjiYlZMRB+PXt0HvzJYTAydYKJ1srwjwd6j0no6CMe7/cy/1L93EorQd/u6ovFjrBvuQ8Jhhv2HcFrZ2WeYMQIhUYAfwshFhjPO4vhPgFQEpZBTwArAGOAV9LKeNa121F6ZxG9fQko6CcxKzi2k8IoW2r9//tnX+YFdV5gN9vd2EXFhB3A8TKL5dKgIiCoohGKJqIolaoGhNTKKHVYhpF0yRSTZuU2BrNY5o8NsbYpBJiIiopaR605Q+1KkYKbDChCimwoFCRn6KwKMju1z/OubvD5S67K3funNn53ueZZ+fOzL37nm/mfnfmzDlntiw/ph5/0cqtDK7pyfl1tUe/Z/9b8P4+6P/xmK2N9hjQp4pFN03gc+MH88PnG5j1yCpe2bqPvY2HU1N/DyfeSmeJqg5U1UpVHaCqU/zyN1V1amS7p1V1uKoOU9UCA08bRtfg/Do3UuKKhgLt7od+Ahp3wu4NLYu27G7k5YY9XH/uIMrym/XtfM397T8yLl2jE3SvKOMfpo/mvmvOZOXmvVz/8AoAzh6Sjvp7sJ62hlFUTvtINQP6VPJyw55jV7bU47/YsuiJ1VspE7j2nAKtcFoSvnVbCYlPnzuIJ+dMoLa6OzXV3Tm9f3puqNtYOoZRRESE8+tq+fWmPcfW49fUufHNfT3+kaZmnqzfxsUj+jOgT9WxH7ZzHfQaANW1x64zEuWsQX1ZdvtE3n3vg1R0uMphZ/iGUWQm1NWya/+h1mEWcojA0NZ6/Od+v4td+w9x/blt9KLd8aqd3QdMn6puDDy5Z9IancISvmEUmdzN1xVtVes07oQ9G3l81Rv0713J5I8VGEK5uck1ybSEbxQRS/iGUWSG1PbklJOq2qjHvwiAxuUP8ez6HVw3biAV5QW+hm9vgSPvwQBL+EbxsDp8wygyuXr8FzcUaI9fUwdj/5TqNT/i/op1nD3mp4U/xFroGDFgZ/iGEQMT6mrZfeAwG3fmPX9UhOYrH+Dhbjcwvfwlhiy9ARp3H/sBO9cBAv1GlMTXyAaW8A0jBo5Xj//y5r384/4rWXXu/bD9FTd08s71R2+041U4eSh0L9zz0zA+DJbwDSMGBtX04NS+PXhq7XaamvN61q7aSt+e3Rh96edh1lPwwUH48aWw6bnWjXaugwHWw9YoLpbwDSMGRIS/nFTHioa9zPvF72j2SX9x/Tb+Y+12po89lapu5TBwHNz4LJx0Kjx6Daz+VzhyCPZstPp7o+jYTVvDiImZE4ay58BhvvfMBqorK2hqVn664nUuGFbL3EtOb92w72CYvQwWz4alt8PGZ0CbrEmmUXQs4RtGjNz2ydM5cOgIP16+GYCbJtbx1SkfO7YpZlUf+OwiWHYnrPyhW2YJ3ygylvANI0ZEhK9dMZJ+vSsZWlvNZWcUepaQp7wCpt4H/YZDw/NQ+4elEzUygWgbj1wLgXHjxunq1auT1jAMw0gNIlKvquMKrbObtoZhGBnBEr5hGEZGsIRvGIaRESzhG4ZhZARL+IZhGBnBEr5hGEZGsIRvGIaREYJuhy8iu4DXk/bI4yNAgfFsU0Po/qH7RQndNXS/jhB6GUL0G6KqBR6jFnjCDxERWd1Wp4Y0ELp/6H5RQncN3a8jhF6G0P3ysSodwzCMjGAJ3zAMIyNYwu88DyctcIKE7h+6X5TQXUP36wihlyF0v6OwOnzDMIyMYGf4hmEYGcESfhuIiCTt0NWxGBcPi2W8dJX4WsKPII7bRWSgpriuS0TK/d/gDtK0xdhiGS8W39JiCd8jIjOB54CxwLshHoDtISKzRGQNMDdpl0KkKcYWy3ix+CaD3bQFRORC4EXgPFVdnbdO0vDrLiIjgIXAMmA08CVVbRCRMlVtTtYuXTG2WMaLxTc5MnuGn7uUBFDVl4D/Bkb6dfNE5CoR6RXyzhWR3rl5VV0PzAT+CXgN+KJfntgXKE0xtljGi8U3DDKZ8EVkPvB3IhIdb2IO8BMReQXoC9wCfNufjQSHiMwD1ojIvSIyyy/+varuBZYAw0Rkot+25Ps5TTG2WMaLxTcgVDUzE1AJ/A1uQLYlwKV5678AnOPn+wG/BKYk7V2gHBcDLwCnAZOB7cCZkfW9gNuAn0WWlVuMLZYW364V385OWTvD/wBYCowCVgCTReS03EpVfVBV6/38LmAvUJOEaDt0A9ao6mZVfQ74HnBPZH0jsBg4ICLfFJH7gKElcktbjC2W8WLxDYhMJXx1dYT/q6qNwOPAQOA8EamE1qZhIlIjIvcDZwKrkvI9Dj2BWhGpAlDVbwGniMh1/rUC7+NuiN0M7FLVTaUQS2GMLZbxYvENiC6b8NtqRqWqh/zfLcByYBIwwi9T/wv/OO7MZJKqbiyJcAGiN7qidZuqugQYBlwZ2fw+4EuR1/cArwKDVfXbJfYLLsYiUhOZDzGWbfkFF8tCiMjIQssDim9bfqmIb9FIuk6p2BNwNfATYEzecgHK/Hy5/9sHeAC4AZgBXOWX1yZchstxbYAXAndFlpcDlX7+M7i60aH+9WDg+0Bv/7oqIb+gYgxc5uO0ELg/srwskFgezy+oWB6nDA8Am3PxCym+7filIr5FjUXSAkXaobn+BJOB3wH1uMvDk6Pr/Xwd0Dfy+lbgbWAjMDXJMviEOQd32TgVGI+rY5ydt22d334+8CPgr4D/BB4KyC+xGEdcb8LVzV7tk8x/AZcHFMuO+gV1vEa/T/71z4DfAH+RS/JJx7eTfkHFN9Z9l7RAMXcu7mbPKbiWAQtwl2C5dWXAPFwrgcv9QTgCaADuDKgMU4HTI69vw3VMwSeJecAu4CLgJOBC3BXNVwLySyzGea6jgAo/3x94wifW3BndXQnHsiN+QR2vef45z1uAG3FXfWdE1t+Be/xfUvHtiN9bIcU37qmCFCMiXwQuEZEXgMfU1cMBbBeRKcAkEdmoqv8HfBR4Bxilqm/7928BRqu7aZMIkTK8CCxU1adFpFxEKlT1CK7zx3q/eX9cGYbnygC8JCIrVLUpIL9EYpx3PCxS1df88rG4KoQK3Jf8PeDLJBfLzvgFc7xG/J8HnlDVN0WkO65a6s9wJ1ufEZGVuKaO7+JODkod3874jQwlviUh6V+cDzsB03FVC5OBR4B/Bs6KrD8LeBSYXuC9FUn7H6cMY6KOuCuVCwq8t5y8S9fA/Eoa4+MdD7jL9sF+vhcukY4NIJYd9Uv8eG3D/2y/7u/938/ikug6IvXeCca3o36Jx7dUU5pb6YwHfqCube83cDdlWgZiUtXf4g6A0SJyse/tlxsL40gCvoUoVIZbAVT1iG8eNgioF5GBInIjtJShSf3RGqhfqWNcyPU279qgqm/4+QO4Vhc1EdekYtlRvxCO10L+X/DrrvBXgHcA/467L9EIice3o34hxLckBJ/w85tXRl434O6mo6qvA08B1SLyx5HNH8PdqHkcqI3ftjAfogxX+/UjcN5zgV8RU6eP0P1OwLVn3vGAiHwN+DiuGSDFTkSh+7VHJ/37isgEXGeqX6vqGFWdgas+Hem3TTK+JfcLneATPq79awuRHbQYOBhJPttxrRxGiaMXbkevxXXl/kre+0tJZ8sw0h/IdbgD8zTgClW9N+/9WfE7EddRACJyuYgsB4YD16rqWxn1a4/O+D8LTMQNi3BH5G3TVXVNRv2CJtiELyITRORJ3IBFo6T1QQm5G81v48a/uNlflr2Dq/+s8gfB+8BcVb1CVbenrAw9fBk2Ap9Q1ZvjKEPofsVy9evXAXNUdWZosSyFX0z+1bjvW7O4G/llAKr6ftb80kKQCV9E+uNuujwN7MFVGcwGV3fsN+uBG097O/CwiPwB7mEFH+S2U9WdJVZvoUhlWKuqsXTlDt2viK6H/XZbVPV/sujXHifof8Rv16QxDW8cul+q0E7e5S3FBHwK18wS3K/0FFwHnxF+2d24nTsWV298N+7y+EFKNNJe2ssQul+aXEP3S7t/6H5pmhIX8DtsGnAnrh4Y3FCkG4Bh/nUN8HXgXtxgTD/PrYt8Rk8rQ3r90uQaul/a/UP3S/OUaJWOiPQTkV/iBlLaCzwiIteqG4r0F7gecgD7gGdwO7pKVW9Q1U1y9CBTB0usD4RfhtD90uQaul97hO4ful9XIOk6/GHAS6o6UVUfAv6a1lH0HgNGiMgn1dW97QEGAIfAjSioYdTJhV6G0P3S5Bq6X3uE7h+6X+op+dAK4p4G/wawEjfI2Wa/vBz3fMtX/aZrgUXAd0VkGnAJbryLbpD48y+DLkPofmlyDd2vPUL3D92vq1GShC8iguvs8HOgGdiEG8xorqruEJFyVW0SN2b1SdCyAxf4O/TzcJ18blTVfaVwTlsZQvdLk2vofmn3D92vSxP3TQJaR6wbDjzq5ytw407/W942C4FP+/mPRj6je9yeaS5D6H5pcg3dL+3+oft19Sm2M3zfIWI+UC4iT+MeLtAELeOw3Aq8KSKTVPV5/7YDwGZxT5H/ExG5TFW3qerhuDzTXIbQ/dLkGrpf2v1D98sKsdy0FZFJuPq4k3G9Mb+J66wzWUTOg5Yu0fNxAx3l6uxm47pI9wEmq+q2OPw6QuhlCN0vTa6h+7VH6P6h+2WKOC4bcA88mBF5/SDuCVSzgHq/rAxXj/cEMAR3h/67+CFNk55CL0PofmlyDd0v7f6h+2VpimsH9wQqaa2L+xxwj59/BbjFz4/DPQgi8UCkrQyh+6XJNXS/tPuH7pelKZYqHVU9qKqHtPXJNp/CPUoO4PO40RaX4trW1sOxw54mTehlCN0vSuiuofu1R+j+oftliVibZfp6OMV1kPiVX7wf1236DGCzuscPov4nPjRCL0PoflFCdw3drz1C9w/dLwvE3dO2GdcxYjdwpv8V/1ugWVWX53Zu4IRehtD9ooTuGrpfe4TuH7pf1yfuOiPgfNyOXg78edJ1WF2xDKH7pck1dL+0+4fu19Un8TshNkRkIDAD+I6qHor1n8VE6GUI3S9K6K6h+7VH6P6h+3V1Yk/4hmEYRhgkPVqmYRiGUSIs4RuGYWQES/iGYRgZwRK+YRhGRrCEbxiGkREs4RtGG4jIN0Tky8dZP01ERpXSyTBOBEv4hvHhmQZYwjdSg7XDN4wIInIXMBPYihvgqx54B7gJ6I4bz30GMAZY6te9A1zjP+L7QD/gIO4RfOtL6W8Yx8MSvmF4ROQcYAEwHjew4G+Ah4BHVHWP3+ZuYIeqPiAiC4ClqrrYr3sGmKOqG0RkPG4I4ItLXxLDKExJHmJuGCnhImCJqh4EEJHciI5n+ETfF+gFLMt/o4j0Ai4AnoyM7FsZu7FhdAJL+IZxNIUueRcA01T1tyIyC/ijAtuUAftUdUx8aoZxYthNW8No5QVguoj0EJHewFV+eW9gu4h0wz2tKcd+vw5VfRf3wO3rwD3AQ0TOKp26YbSP1eEbRoTITdvXgW3Aa0Aj8FW/bC3QW1VniciFwL8Ah4BrccP+/gA4BTfu+yJVnV/yQhhGG1jCNwzDyAhWpWMYhpERLOEbhmFkBEv4hmEYGcESvmEYRkawhG8YhpERLOEbhmFkBEv4hmEYGcESvmEYRkb4f50eP1Y3ILwYAAAAAElFTkSuQmCC\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYAAAAEdCAYAAAAFP7AiAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8li6FKAAAgAElEQVR4nOzdd3xV9f348dc7myyygRBCAgSZASHsIThxjzrrrkLd2NZWa79d1lY7XdVSftVWW8RN1bpBpoCyEdmbMBMSAiHjJvd+fn98TiDEQG5Ickfyfj4e95Gbs+77HMJ53/OZYoxBKaVU2xPi7wCUUkr5hyYApZRqozQBKKVUG6UJQCml2ihNAEop1UZpAlBKqTZKE4BSSrVRmgCUOgkR+amIfFhn2aZ6lm0TkWoRKXVeRkSO1vp9jIj8S0Qe9+0ZKHVqmgCUOrl5wCgRCQUQkY5AODCozrIsINMYE2uMiXX2HVDzuzFmvh9iV6pBmgCUOrkl2Bv+QOf3scBsYEOdZVuMMXt8H55STaMJQKmTMMa4gC+xN3mcn/OBBXWWzfN9dEo1nSYApU5tLsdv9mOwCWB+nWVz/RCXUk2mCUCpU5sHjBaRRCDVGLMJWAiMdJb1Q58AVJDSBKDUqS0C2gOTgC8AjDGHgT3Osj3GmG3+C0+p06cJQKlTMMaUA0uBH2KLfmoscJbpt38VtDQBKNWwuUAa9qZfY76zTBOAClqiE8IopVTbpE8ASinVRmkCUEqpNkoTgFJKtVGaAJRSqo3SBKCUUm1UmL8DaIyUlBSTlZXl7zCUUiqoLFu2rNAYk1p3eVAlgKysLJYuXervMJRSKqiIyI76lmsRkFJKtVGaAJRSqo3SBKCUUm2UJgCllGqjNAEopVQbpQlAKaXaqAabgYrIS8AlwAFjTL961gvwDHARUAbcZoxZ7qyb4KwLBf5hjHnSWZ4EvA5kAduBa40xxU09mXKXm5W7DrF0exGbC0pJjI4gNS6S3Iz2jMn5VhNYpZRq07zpB/Av4K/AKydZfyGQ47yGAX8DholIKPA8cB6QDywRkfeMMWuBR4BZxpgnReQR5/eHGxv8wdJKlu4oZun2IpZsL2bN7hKqPQYRSG/fjsPlVRyprAbg6esGcsWZnRv7EUop1Wo1mACMMfNEJOsUm1wOvGLsxAKLRSRBRDphv91vNsZsBRCR15xt1zo/xzn7vwzMwcsEcKjMxXOfb2b2hgNsLTgKQERYCAMzEpg0thtDspIYlJlI++hwAEorq7njX0v4ydur6ZIUzeCuid58jFJKtXrN0RO4M7Cr1u/5zrL6lg9z3ncwxuwFMMbsFZE0bz6o+sBGZv7xRqSqAzd16EDWwDiy0+LISIolPKwMZB94QmFHCIRGQEwKsbFpTLmhP1f8fQnf//dS/nvvKDISo5t+1nUZA+4qcLvsyxgQsetEAAEJOf4+JAzCo5o/DqWU8lJzJACpZ5k5xfLGHVxkEnbybXp3iuYCFnJ1WCkcxL7WN3yMRAlhZlwXvqxKYNXfOpJ85hm0i0uGsCjwVINxQ7ULqsqgugKqyu2rurzW+wrn5l5t96k5lepKqDwMlUec5Y0QHg3RKRCbBolZkNQNkntAWm9I6akJQinVopojAeQDXWr9ngHsASJOshxgv4h0cr79dwIOnOzgxpipwFSA7N65Jubnq6CiGFyl4HGD8Rx/HfvduaEfLYCjB6BkN+FFWxi4ZwOVBxcR/uVMwP3tD5NQe1MOb2dvvmHtnPftICIGQpMgNBxCQrHf6AVCwiEqHiLjISIaQiPtNhJinwIwJ/40HvveXQXlxXC0EI7shfwl8M07znrs/h36Qo/zIOd8yMizx1VKqWbSHAngPeA+p4x/GFDi3NgLgBwRyQZ2A9cD3621z63Ak87Pd735oOSYCEJCBKKT7KuRYoEv1+3n+/9eyvDOkUy9MZfoqEh74w+L9P8NttoFRVuhYB3sXws7FsIXz8CCv9jEktYbOuVCx1zoNMAmiIgY/8aslApaDU4KLyLTsRW2KcB+4JdAOIAxZorTDPSvwARsM9DbjTFLnX0vAp7GNgN9yRjzW2d5MvAGkAnsBK4xxhQ1FGxeXp5pjtFAP/x6L/e9upykmEgGZSYwoEsCfTrF0yMtlvSEdoSG1Fd65ScVJbB1DuQvhX2rYe9qKK+5VAIpOccTQvezbVKQFozfGPvkcni3LfJK6ArtEhv/mdUu2LPcPvnsXwsHvoHSAzYZh4TYnxJyvN7k2PsQbB1KKMR3hsSukJAJMakQkwKxHWxMES1Qz6NUkBKRZcaYvG8tbygBBJLmSgAAczYc4L8rdrMqv4RthUePLY8MCyE7JYbuabF0T42le2qM8zOWdhGhzfLZTWKMvfnuXX08IexbDSVOfXv7LpA5wtZnlB20RWVhUfYVEWOLqqLaQ7sEe+Ou7xWVAGERtr6j8jAc2gFbPofNs2D3cls3UltEnK3HiIx1isJi7ftjP+NsMVp5sS2SK95hE1rNcWI72MQV39men6fKKcozJxbx1S5Gc7vg8B4o3m7Pta7YjpDc3dalpPaClB62jqV9JoQG1SjoSjWZJoBTOFTmYvOBUrYUlDo/j7KloJRdRWV4nMsTItCrYzx5WYkMyEigU0IUHeOjiIkM40hFFYcrqtm0/whLthezbEcxxWUue68yxlYXO8cx2GUhIUJKbCRpcZF0T4vl3vE96JzQ7vRP4vBe2PQpbPwE9q60N/HoJHsTrq6wldWuUntDryixr5r6hvqERdn9auuYC1mj7Tfu+HT7bfzQTnsTPlroHL/UVoi7jtj3rtLjx5FQ+009vhN0GQZdR9lkFduETnrGQFmRre8pK4Qj+6BoGxRvg4OboWADVBw6vn1ImI0/qRskZtufSd3sk1RSt5Z9elLKTzQBnIaKKjc7DpaxpaCU9XsPs2xnMSt2HqLMVU8FsiMxOpy8rCTS29sWPOLcUERAOP7e7TEUlFZy4HAFX+8uQRAmn5vD90ZlExHmgxE6PB6bDMqL63kdgsoS+809qr29QXcdDXEdTu+z3FX2W3pEnC3e8SVjbNHSwc02KRRtrfXaZq9Bjdzr4dKn7dOKUq2IJoBmUu32sKOojP0lFew7XEGZy018u3Dio8LISGxH99TYYzd9b+0qKuOx/63ls7X76d0pnn/dPoQO8doEtMXVPD0UbYWNH8H8v9hK9uv+Y58SlGolNAEEgU+/2ccPXl9Jcmwk/7ljGJnJWpHpUxs+hncm2mKisx6Gwbfq04BqFU6WAHQ00AByft+OTJs4nMMVVVw9ZSEb9h3xd0htyxkTYOJs29z244fhmQGw+G+2CEupVkgTQIAZ2CWBN74/AoBr/76IZTuaPEiqaoyUHnD7h3DbB7YF0cePwNRxttWSUq2MFgEFqF1FZdz04pccOFzJ324axLgzvBouSTW39R/ABw/Z3trD74bzH3d6gjdStQu2zoYdXzhDi1TafgvD74WY5OaPW6latA4gCBUcqeTWl75i4/4j3DOuO+f26UC/9Pa2N7TyncojMPNXsOQf0P8auGKK930JCjbAgqdtIqkssUOHRMTYnudHC20z3bE/gmF32WVKtQBNAEHqcEUVD762ks/X2+GSkmIiGJOTwlk9UxmTk0pqnN40fGb+X2DWr6HPFfCdf5x66JAj+2D272DFv+34Ur0vg75XQrdxtpMdwIH18NnPbf+NhEw499d2G+2LoJqZJoAgV1hayYJNhczdWMD8TQUUlroA6Jsez9ieqZzVM5VBmYm+6UMQBMpc1WzYd4Qqt6Ha4yEtLoruqTGNbqL7LQv/Cp/+DLLGwDm/gC5D7XKPB/avgS2zbK/pnYttR7shd8LYH9vinpPZ8jl8+nO7f8ZQuOC3x4+rVDPQBNCKeDyGtXsPM3djAXM3FrB8RzHVHkO78FCGdUsiNyOBiio3h8pcREeEcdWgzuRmJPg7bJ8wxvDeqj389oN1HDhSecK6zgntjiXLUT2SiYs6zcH/lr1si4TKiyB7rB12Yuts2xsZoEM/6D4eBt9uh6PwhscNK6fB549D6X7oexWc+0s7TLhSTaQJoBU7UlHFoi0H+WJzIQs2F7Kl4ChR4SEktIvgULmLiioP/TrHc/vIbK48s3OrrEMwxrBsRzF/+HgDX20vIjejPXef1Z24qHBCQ4RthUeZu/EAX2w+SGllNWEhwqCuiZzVM5VxZ6TSp1N8454OKkth2T/tE4Gn2g7E1/1se+OP63j6J1JZCgufhS+etcOaD78bxvzI9shW6jRpAmhDqtwewkNtUdDhiireXbGbaV/uZP2+I5yZmcBvLu9Hv87BeUNxewyHy6s4VF5FZbUbV7WHHQfLeOmLbazYeYikmAh+csEZXJvXpd5EV+X2sHxHMXM3FjBnQwFr99qhINLiInn6+oGM7H6Kopr61Pz/ae5y+5Ld9mlg1XQ7ptO4n9onCh3ITp0GTQBtnDGGd5bv5omP1lF01MU5vTswqnsyI7qnkJMWGzBPBQeOVDBz7QE2HyjlUJmL4jIXxWVVzvsqDldUUd+fbGZSNHeOyebqwRlER3h/kzxwuIJ5mwp5Yc5mio+6eO++0XRJCqAe2HtWwqf/B9vn25ZCF/7e3xGpIKQJQAFQUl7Fc7M28fE3+8gvtsMxJ8VEMCw7iRHdkxneLZmctMaPZ9QU1W4P/125h9eX7GTpjmKMgZiIUBJjIkiMjiAhOpyE6AgST/gZTlRYKOGhIcRFhZGXldSkeRy2FR7lsr8uIDMpmrfvHklUeAAM/V3DGPjoYfjq73DjW5Bznr8jUkFGE4D6ll1FZSzeepDFW4tYvPUguw/ZhJAcE8GI7sn84LyedE+N/dZ+WwpKmTJnC9sKj9KrUxx909tzRsc4eqTFEt+IilW3x/Duyt08O2sT2w+WkZMWy8W5nZjQryNndIjzaRIC+Hz9fu54eSlXDuzMn68d4PPPP6WqCvh/Z9v5FO5e1LQhtFWbowlAnZIxhvzichZtPcjiLQeZuW4/LreH/7u4DzcOy6TKbViyvYjpX+3kg6/3EhkWQt/09mzcd4QjldXHjpMWF0mPtNjjr9RYenSIJTU28oQb6pwNB3jiw/Vs2H+E3p3i+cG5OZzXp4Pfb7rPztrEXz7byB2js/m/i3v7PZ4T7F9rh6Xodhbc8Lrvh9ZWQUsTgGqU/YcreOjNVczfVEjvTvHsPHiUoy43MRGh3DIyiztGZ5MSG4kxhl1F5Wzcf4TNBaVs2l/K5oJSthwopbRWYrDDZUfTObEdRyqqWLy1iMykaH4y4Qwu6tcpYOogPB7DY/9by78WbufGYZn85vJ+ARMbAF/+HT76CaScASPugdzrdMRS1SBNAKrRPB7DvxZu5+3l+QzoksD4M9IY1SPZq0pWYwz7D1ey+UApmw8cYUvBUfKLy9h9qJwyl5vbR2Vz8/CuAdlxzRjDHz7ZwN/mbOGqMzvzh6tzCQsNkDiNgTVvwxfP2KlA49Jh0uymNT1VTVO42c58FxlnW2y1S/R3RN+iCUCpRjDG8NfPN/PnzzZyQd8OPHvDmUSGBVjF8JbPYdrVMGoynPsrf0fU9lSV2w6BX06ptVDsv8XoB/0TU308biQ0rN4EoI2KlaqHiHD/OTnERoXx6/fXcufLS/n7zYMb1cS0RYlAj3Og96Ww9CXbWSwyzt9RtV7G2LmtKw7bwQFLdtmhwgvW2+a52WPt8vX/g5m/tMlh3CO+Gdep8ojtN3I4Hw7vcd7vtvN1H9oBJfkn3TVA/pqVCky3j8omNjKMh99eza0vfcW0O4cHVrHVyAdg7buw/N+2TkA1jbva9vBe+y5UHLI315qbvqfOxECxHeGmd2wirtH/GnjvAZj7pG2xldrbDhkCkDkcugw7/Tobd5Wdl2LrbNi97PiNvva81gAIxKZB+y7QebAdYJBf13tIrxKAiEwAngFCgX8YY56ssz4ReAnoDlQA3zPGrHHWTQYm2qj4f8aYp53lA4EpQBRQDdxjjPnKm3iU8qVr8roQHhrCg6+v5KmZG3l4Qq9G7V/ucuNyewCIDAtp3j4GGXmQORIWvwBDJ2lP4abY8jl8/CgUrIMO/aF9Z4iMh6h4+3R17L3zyhz27fL+kFC47DmIiIavptZaIYCB0Ag74F/2GPvU0Dnv+OiwJ1PtgkXP2WHFKw+DhEBaXzvOVPZYG2d8zSsd4jrVc8z6E0CDdQAiEgpsBM4D8oElwA3GmLW1tvkjUGqM+bWI9AKeN8acIyL9gNeAoYAL+Bi42xizSUQ+BZ4yxnwkIhcBPzHGjDtVLFoHoPzpp++s5rUlu5h2xzBG9vBuyIhXFm3nsffXUu2x/88iQkO4bkgX7h7XnfSEZmq9s+EjmH49fOdF6H918xyzLal22eKcpS/awfcu+B2ccVHTi29K8iEsCqISoLrcjhC7bS5smwd7VwMGwtrZRJI9FgZ8F+I7nXiMnYvh/QdtUjrjYhhwvU0ejaxoPlklsDdfF4YCm40xW50DvQZcDqyttU0f4AkAY8x6EckSkQ5Ab2CxMabM2XcucCXwB3v2xDv7twf2NOqMlPKxn1/Shy+3FfGDN1by8eSxJMac/JubMYa/fLaR5z7fzLgz7NwNAJv2H+G1JTt5fckurhrUmcsGpDM0O6lprYxyLrDTV37xtB1FVPsHeO9oIbxxi52pbcR9dojv5pqYp33G8fehcbYHd00v7vJi2P6FHeJj23yY9ZgdZfaOzyCug91m5XT47932ODe8buesbmbePAFcDUwwxtzp/H4zMMwYc1+tbX4HRBljfigiQ4GFwDCgDHgXGAGUA7OApcaY+0WkN/AJ9tkoBBhpjNlxqlj0CUD525rdJVz5whcM75bMlJsGExP57e9Qew6V85fPNvLWsnyuy+vCb6/sd8INPr+4jOdnb2HGinwqqjwkRoczcWw37hnX4/QDW/0GvDMRzv8tjLyv4e0V7FoCb33PltVf9hzkXuu/WPKXwcuXQnI3uO1D2PwZvH2nfTK4bhpEfrtHfmOcdjNQEbkGuKBOAhhqjLm/1jbx2DqCM4GvgV7AncaYVSJyB3AvUIp9aig3xvxARJ4F5hpj3haRa4FJxphz6/n8ScAkgMzMzME7dpwyRyjV4t5YsotH3llN707xvHTbEDrER3GkooqP1uxjxvLdLN52EGPgvvE9+NH5PU/am7jc5WbuxgNM+3In8zcV8r/7R5/+KK3GwOs32dnFJs2FDn2acIatnLsaFvwF5jxpy82vfRk6D/J3VLBpJky/zlYcH1hrK4xvestOIdpETUkAI4BfGWMucH7/KYAx5omTbC/ANiDXGHO4zrrfAfnGmBdEpARIMMYYZ58SY0x8PYc8Rp8AVKD4fP1+7n91BXFR4eRlJfLZ2v1UVnvISo7myjMzuOLMdLome/cf93BFFeP+OIdeHeOYduew0x9+4mghvDAcYjvAxM91juH6VJTAq9fDzoXQ72q4+M/QLoAmS1r1Gsz4vq0cvuW/zda092QJwJvCwiVAjohki0gEcD3wXp2DJzjrAO4E5tXc/EUkzfmZCVwFTHe22wOc5bw/G9jUuFNSyn/O7tWBN+8aSWiIsGBzIdfmdeGde0Yy+6FxTD43x+ubP0B8VDgPnN2DhVsOMmdjwekHFZMClz9vp5b88MfgOnr6x2qNqirgtRsh/yu48u9w9YuBdfMHW8n7/Xlw63s+6dfhVU9gp5XO09hmoC8ZY34rIncBGGOmOE8JrwBubDHPHcaYYmff+UAyUAX80Bgzy1k+GltsFIZtOnqPMWbZqeLQJwAVaIwxuD2myUNFuKo9nPfUXKLCQvlw8pgmDW3Nx4/C4uchOtnOKDZ0ks4o5nHDW7fb9v1X/T//lvf7gQ4FoVSA+2D1Xu59dTl/uDqXa/O6NO1gO7+E+X+ydQKR7WHoRBh+D8QkN0+wLa3aBbu+hC2zbKenXpdC3vca18/BGNtRau9qWPOWHUPp/Mdh5P0N79vKaAJQKsAZY7jyhYXsK6lg9kPjaBfRDB3G9qyE+X+Gde/bHqh537PNHeu2N/elTZ/ByldtEVVVWa2fZVB11P6stnNTEBIGCZlQtBXS+tg2+t3GfbuNvsdjt9m70g6St3c17F11vBcuAmN+aJt5tkGaAJQKAl9tK+Lavy/ixxecwb3jm9AstK4D623Ll6/ftDfVM2+Gc37u+5ErF0+xna5i02xldUQMhEfbn8feR0N4DHTsb5tBRsbZMXY+edSObxOXbodf6DQACjfam/3+NXZEToCQcNsKqmOu3aZjLnTo2+SmlMFME4BSQWLiK0tZtOUgc388juTYZm7JU7TVDimwcpqdS+CKF5r3+CfjccMnP4Mv/wa9LrHl8BGNnHu5qsImsE2fwtY5dliEiFjo0A861brZp/ZqeHiFNkYTgFJBYvOBUi54eh43D+/Kry7r2zIf8vFP7eQy9y2xY8q0tFmP2aKo4ffYcviQJhZvuavgyD7bjl97PjeoKc1AlVI+1CMtluuGdOE/i3ewvbCFmnKOetAOTDb3Dy1z/Nryl8GCp2DgTTDhiabf/AFCwyGhi978m0ivnlIB6MFzc4gIC+GPn2xomQ+I6wBD74Sv34DCFuyCU1Vhx7OJ6wQTftdyn6NOiyYApQJQWlwUE8d044Ov97JiZ3HLfMjIyXa0yrm/b5njA8x5Ago3wGXPal+EAKQJQKkANWlsN1JiI/ndh+tokbq62FTbSezrt2DPiuY//p6VsPBZGHQL9PjWMF8qAGgCUCpAxUSG8YPzcliyvZjP1u5vmQ8ZNdkWz0y/4ZRTB56Wz39jx8I///HmPa5qNpoAlApg1+V1oVtqDE9+vJ5qZ1axZhWdZEecdB2FadfYwdKaw45FsHmmnRxdi34CliYApQJYWGgIj0zoxdaCo7y/uoXmTOrQF677t+1U9fpNtollUxgDnz8OMWkwZGLzxKhahCYApQLceX06kBwTwfxNhS33Id3GwWV/tdMVftbE4RK2zoEdC2DsQ43v7KV8SmeQVirAiQh5WYks3d5CrYFqDLzBjqWz+AXIGAL9rmr8MWq+/cdnwODbmj1E1bz0CUCpIDAkK4mdRWXsP1zRsh903m/sTFTv3gcFjeyD4HHD/34Au5fCuId1QpogoE8ASgWBIVlJgB0s7tIB6S33QWERcM2/4O9j4ZXLbdFQcndIyLITzsSkQEyqnWsgNPz4fu4q2+Hr6zdh9A/sYHMq4GkCUCoI9E2PJzoilCXbWzgBAMSnw/XT4fPHYOtcWDW9/u2iEmwyiEmxrYj2rYZzfmmHXVZBQROAUkEgLDSEQZmJLGnpeoAaXYbAre/b966jto/A0UIoK4SjBXD0oP1ZVmiXY+DSZ7TcP8hoAlAqSORlJfLMrE2UlFfRvl14wzs0l4gYSD3DvlSropXASgWJoVlJGAPLd/joKUC1epoAlAoSZ2YmEhYifLXdTnN4pKKKmWv34/EEz5weKrBoAlAqSLSLCKVf5/Ys3V7ErqIyvvO3hdz5ylL+/FkLDRmtWj1NAEoFkaHZSazaVcKVL3zB3pIKzumVxvOzt/DGkl3+Dk0FIa8SgIhMEJENIrJZRB6pZ32iiMwQkdUi8pWI9Ku1brKIrBGRb0TkwTr73e8c9xsR8cHUREoFt7yuibjcHqIjwphxzyim3DyYMTkpPDrja77Y3IJDRahWqcEEICKhwPPAhUAf4AYR6VNns0eBlcaYXOAW4Bln337ARGAoMAC4RERynHXjgcuBXGNMX+BPzXJGSrViZ/dK43dX9mfGPSPpkRZLeGgIz984iG6pMdzx8hKenbWJcpfb32GqIOHNE8BQYLMxZqsxxgW8hr1x19YHmAVgjFkPZIlIB6A3sNgYU2aMqQbmAlc6+9wNPGmMqXT2O9Dks1GqlQsLDeG7wzJJjj0+zEJ8VDj/vmMYZ/dK4y+fbeTsP8/h9SU7Ka2s9mOkKhh4kwA6A7ULGPOdZbWtAq4CEJGhQFcgA1gDjBWRZBGJBi4Cujj79ATGiMiXIjJXRIac/mko1bZ1iI/ihRsH8/qk4STHRvDw21+T9/hn3Pfqcj5bux9XdQvMJaCCnjcdwaSeZXXbnT0JPCMiK4GvgRVAtTFmnYj8HvgMKMUmipqvJWFAIjAcGAK8ISLdTJ2570RkEjAJIDMz06uTUqqtGtYtmffvG83yncX8d8Ue/rd6D/9bvZeE6HAu6t+JywekMyQriZCQ+v5bq7bGmwSQz/Fv7WC/2Z8wM4Ux5jBwO4CICLDNeWGMeRF40Vn3O+d4Ncd9x7nhfyUiHiAFKKhz7KnAVIC8vDxt8KxUA0SEwV2TGNw1iV9c2of5mwp4d+UeZizfzatf7iS9fRSXDkzn+2O7kxQT4e9w1UmUu9zcP305h8qaOEHPKXiTAJYAOSKSDewGrge+W3sDEUkAypw6gjuBeU5SQETSjDEHRCQTW0w0wtntv8DZwBwR6QlEANqMQalmFB4awtm9OnB2rw4craxm5rr9/HfFbv4xfxsLNhXy+vdHEBupI8IEom2FR5m57gC9OsaRHNsyibrBf3ljTLWI3Ad8AoQCLxljvhGRu5z1U7CVva+IiBtYC9xR6xBvi0gyUAXca4yp6cf+EvCSiKwBXMCtdYt/lFLNJyYyjMsHdubygZ2Zvf4Ad76ylLv/s4yXbhtCeKh2CQo0LmcO6J9MOIOze3Vo0rFePcnMnF6lfmPMh8CHdZZNqfV+EZBzkn3HnGS5C7jJm89XSjWv8b3SeOLK/vzk7dU8/PZq/nzNAGzprQoUNRX3EaGhLfYZ+uynVBt17ZAu7C2p4KmZG/F4DE9+J5eo8Ja72ajGqXKeAMJDWy4xawJQqg174JwehIbAnz7dyI6iMv5+82DS4qL8HZai1hNAWMsVz2kCUKoNExHuOzuHHmmx/OD1VVzx1y/4aPJY2kf7cL6BZlZR5WbHwTK2FR51XqXsLCrjtpHZTOjX0d/hea2mDkATgFKqRU3o14n4qHC++48vmbV+P1cNyvB3SF7bfKCUVxZtZ1vhUbYWHGVPSTm1m5OkxEbiMYbf/G8tZ/dKa9EbanM6XgegCUAp1cKGd0smJTaCORsKgioBPDtrEx+t2UufTvHkZSWSnZJBdkoM2SkxZKXEEB8VzlxYa0kAACAASURBVJwNB7jtn0t4a1k+3x0WHB1KtQhIKeUzISHC2J6pzF5/ALfHEBoEvYU9HsOCzYVckpvOU9cNPOl2Z/VMZUCXBJ6fvZmrB2cExVPA8Urglos18K+CUspnzuqZSnFZFavzD/k7FK+s2VNC0VEXY3umnHI7EeHBc3LYfaicd5bnn3LbQOGLOgBNAEqpY8bmpCICczcWNLxxAJjnxDkmJ7XBbcedkUpuRnv+OnvzsW/XgcwXRUCaAJRSxyTGRDAgI4E5G4IlARTSNz2elFrDY5+MiDD5nBzyi8u5+z/L2HzgiA8iPH3HngC0CEgp5SvjzkhlVf4hio+6/B3KKR2pqGL5zmLG9mz423+Ns3ul8ZMJZ7B4axHnPzWPH76xknkbC6ioCrxJdGqeALQOQCnlM2f1TMUYmLcpsJ8CFm45SLXHMNaL4p8aIsI943ow7yfjuWN0Nh+s3sstL33FgF9/yp0vL2X/4YoWjLhxqtweQkOkRSvjNQEopU6Qm5FAYnR4wNcDzNtYQExEKIO7JjZ636SYCH52cR9W/OI8/nnbEG4YmsnCLYVc9cLCgCkaclV7WrT4BzQBKKXqCA0RxuSkMndDAUUBWgxkjGHepgJGdE9uUiVpdEQY43ul8avL+vL6pBFUVnv4zt8WMWfDAYqOunB7/DdAcZXbtHhzVe0HoJT6lhuGZvLxN/u49LkFTLlpMP0z2vs7pBNsP1jGrqJyJo7p1mzH7J/Rnhn3jOTWl77itn8uAUAEEtqFkxgTQVJ0xLGfSbERfGdQBj3SYpvt8+uqrPa0+DDdmgCUUt8yonsyb901grv+vYzvTFnIg+fmMLBLAt1SYukQH+n3oaM//HovQKPK/73RJSmaGfeOYu7GAopKKykqq6L4qIuiMhfFR13sKipjdf4hDpa6eGtZPu/dN4pO7ds1aww1qtweIvUJQCnlD7kZCbx//2gmv7aSP3y84djy6IhQspJjyE6NoZsz5EK31Fj6d27vk97D5S43Ly3YxtieqWSlxDT78du3C+eyAemn3Gbj/iNc9cJCJr6ylDe+P4LoiOa/lbqqPS06FDRoAlBKnUJybCT/vmMouw+Vs72wjG2FpWx1Rtlcs7uEj77eS00xeV7XRJ777pnHvhEv31nMp9/s54FzejTrDfK1JTs5eNTFfeN7NNsxG6tnhzievWEgd7y8lB+9sYrnvzuIkGZOfq5qj9YBKKX8S0TISIwmIzGa0TknDrngqvaws6iMxVsP8rsP13Hxswv45aV9mL3+AP9duQeAbikxXDukS7PE4qr2MHXeVoZmJTE0O6lZjnm6zu7VgZ9d1JvHP1jH7z5cx88u7t2sRWNV7pZPANoKSCl12iLCQuiRFstNw7vy/v2jSYuLZPJrK/lwzT7uHd+dzKRo3l+9p9k+753l+ewtqeDes/337b+2O0Znc+uIrvxjwTaembWpWY/tcmslsFIqSHRPjWXGPaN4a3k+43qm0iUpGoApc7dysLSSZC+GaziVareHF+ZsITejPWNzTj34m6+ICL+8tC9HXW6enrmJ2Mgw7mymlknaD0ApFVTaRYRy8/Cux27+l+Sm4/YYPlqzr8nH/nz9AXYWlXHPuB5+b4VUW0iI8Pvv5HJx/048/sE6znzsU6564QsenfE1B5rQs9ilRUBKqWDWq2Mc3VNjeH9V04uB3lu1h6SYCM7tndYMkTWv0BDhqesG8pvL+zKhXyciw0J5e1k+Fz4z/7R7VAfME4CITBCRDSKyWUQeqWd9oojMEJHVIvKViPSrtW6yiKwRkW9E5MF69n1IRIyIBMYznVKq2YgIlw5I56vtRU0aZ6fMVc2sdQe4qH9Hwlr4pni6IsJCuHlEFk9c1Z/pk4bzwQOjSYmN5NaXvuKpzzY2+ngBUQksIqHA88CFQB/gBhHpU2ezR4GVxphc4BbgGWfffsBEYCgwALhERHJqHbsLcB6ws+mnopQKRJfkpmMMfLB672kfY+a6A5RXubk099Tt8wNJj7Q43r1vFBf268gzszZRUl7VqP1dPugJ7M3RhwKbjTFbjTEu4DXg8jrb9AFmARhj1gNZItIB6A0sNsaUGWOqgbnAlbX2ewr4CeC/ATeUUi2qR1osvTrG8b8mtAZ6f9UeOsRHMiTLv00/GysqPJSLczsBsOdQeaP29cVYQN4cvTOwq9bv+c6y2lYBVwGIyFCgK5ABrAHGikiyiEQDFwFdnO0uA3YbY1Y16QyUUgHv0gHpLN95iK0FpY3et6S8irkbCrgkN73ZO1v5QucE2zGusQnAF2MBeXP0+q543W/sTwKJIrISuB9YAVQbY9YBvwc+Az7GJopqJxn8DPhFgx8uMklElorI0oKCwB6eVilVv2vyMggPFV5ZtKPR+37yzT5cbk+DwzMEqtNNAK5qd4uPBeTN0fNxvrU7MoATnuWMMYeNMbcbYwZi6wBSgW3OuheNMYOMMWOBImAT0B3IBlaJyHbnmMtFpGPdDzfGTDXG5Blj8lJTm3fgJ6WUb6TFRXFx/068tSyf0srqRu37/qo9ZCZFkxtgI5J6KyU2kvBQYfehxlWCV7lNi48F5E0CWALkiEi2iEQA1wPv1d5ARBKcdQB3AvOMMYeddWnOz0xsMdF0Y8zXxpg0Y0yWMSYLm2QGGWOa3lhYKRWQbh2ZRWllNW8vy/d6nwNHKli45SCXDugUUG3/GyMkROjUvl3jnwACoRWQU3l7H/AJsA54wxjzjYjcJSJ3OZv1Br4RkfXY1kKTax3ibRFZC7wP3GuMKW7WM1BKBYUzMxMZ0CWBlxdtx+PlRCuvfrkTt8fwnUEZLRtcC0tPiGJ3IxKA22NwewwRoaEtGJWXQ0EYYz4EPqyzbEqt94uAnLr7OevGeHH8LG/iUEoFt9tGduUHr69i/uZCzmpgMndXtYdpX+5k3BmpdEttuYlXfKFzQjQLtxR6vX2V25kQPsz/RUBKKdUsLurfiZTYSP76+SZ2FZWdctuP1uyl4Eglt43M8k1wLahzQhT7D1ccu7E3pLLabhcQPYGVUqo5RIaF8sA5PVi6o5ixf5zNTf/4kndX7qaiyv2tbf/5xXa6pcQ0+6xf/pCe0A6Pweve0DWJQucDUEq1KreMyOLsXmm8vWw3by7bxeTXVhIfFcblAztzbV4X+nWOZ1V+CSt3HeJXl/YJyrb/daUfawpaQUZidIPbu3z0BKAJQCnlcxmJ0Uw+N4f7z+7B4q0HeWPpLt5Yuot/L95Br45xRIaHEhsZxncGB3flb42aBLD7UBnQcG9mfQJQSrV6ISHCyB4pjOyRwq/Lq3h/1R7eXLqLVbsOccfobOKiwv0dYrPoXOsJwBs1TwA6IYxSqk1o3y6cm4Z35abhXdlVVEaH+Ch/h9Rs2kWEkhQT4XVT0GOVwPoEoJRqa2omlGlN0hOivO4MdqwISFsBKaVU8EtvRG9gl4+eADQBKKWUD3RObMfu4nKMabgXdJXbbqMJQCmlWoHOCe046nJzuLzhwfBcbtsvIhCGg1ZKKdVEx5uCNlwM5Kt+AJoAlFLKB9IbMS+A61gRkI4FpJRSQS89wTZr3VPSmCeAlh0NVBOAUkr5QEpMJBFhIewubjgB+KonsCYApZTygZAQIb29d/MCHO8JrEVASinVKqQneNcXQPsBKKVUK2MTQMPjAbncvhkLSBOAUkr5SHpCO/YfaXhiGG0GqpRSrUxGQjuMgX0lp34KqHJ7CA+VFp8LQROAUkr5iLedwVzVnhYv/gFNAEop5TPH+gI0lADcnhavAAZNAEop5TPe9ga2RUABkgBEZIKIbBCRzSLySD3rE0VkhoisFpGvRKRfrXWTRWSNiHwjIg/WWv5HEVnv7DNDRBKa55SUUiowRYWHkhwTwe4GWgJVVntavAIYvEgAIhIKPA9cCPQBbhCRPnU2exRYaYzJBW4BnnH27QdMBIYCA4BLRCTH2eczoJ+zz0bgp00/HaWUCmydE9s1WAdQ5TZEBkgR0FBgszFmqzHGBbwGXF5nmz7ALABjzHogS0Q6AL2BxcaYMmNMNTAXuNLZ7lNnGcBioHXM/qyUUqfgzcQwrmp3wBQBdQZ21fo931lW2yrgKgARGQp0xd7Q1wBjRSRZRKKBi4Au9XzG94CPGhe6UkoFn5rewKeaGMZVHTiVwPU1RK0b+ZNAooisBO4HVgDVxph1wO+xxT0fYxPFCbMhiMjPnGXT6v1wkUkislRElhYUFHgRrlJKBa70hCjKXG5KyqtOuk2V27T4OEDgXQLI58Rv7RnAntobGGMOG2NuN8YMxNYBpALbnHUvGmMGGWPGAkXAppr9RORW4BLgRnOSdGiMmWqMyTPG5KWmpjbi1JRSKvBkJNqWQPmnGBU0kJ4AlgA5IpItIhHA9cB7tTcQkQRnHcCdwDxjzGFnXZrzMxNbTDTd+X0C8DBwmTGmrDlORimlAp03TUFtP4CWnQsAIKyhDYwx1SJyH/AJEAq8ZIz5RkTuctZPwVb2viIibmAtcEetQ7wtIslAFXCvMabYWf5XIBL4TETAVhbf1UznpZRSAcmrBFDtIcIHRUANJgAAY8yHwId1lk2p9X4RkFN3P2fdmJMs7+F9mEop1Tokx0QQERbCnlOMB6Q9gZVSqhUSETonnLovQED1BFZKKdV8Oie0O+XUkK5A6QmslFKqeaUnRJ2yDqDK7SFci4CUUqr1SU9ox4EjlVRWu+tdHzBjASmllGpeNS2B9pdU1rveVe0JmLGAlFJKNaPODUwMo5XASinVSp0qAVS7PXgM2gxUKaVao47tTz4zWJXbjoqjTwBKKdUKRYWHkhIbWW8CcFV7AH0CUEqpVqtzQlS9RUCVbtsySBOAUkq1UjXzAtRVUwTki7GANAEopZQf1AwHUXckfC0CUkqpVi49oR0VVR6Ky06cGKbKbROAVgIrpVQrdbJhoY89AWgCUEqp1ulkfQEqtQhIKaVat/SE+vsC1BQB6ROAUkq1UkkxEUSFh3xrWGhfVgJ7NSNYIKuqqiI/P5+KipPPrqO8ExUVRUZGBuHh4f4ORalWT0RsU9CS+p8AfFEJHPQJID8/n7i4OLKysnDmFlanwRjDwYMHyc/PJzs729/hKNUm2KagJ3551WagjVBRUUFycrLe/JtIREhOTtYnKaV8KL39tzuDudyaABpFb/7NQ6+jUr7VObEdBXUmhtFmoK3YnDlzWLhwYZOOERsb20zRKKX8qaYvwN5axUAB9wQgIhNEZIOIbBaRR+pZnygiM0RktYh8JSL9aq2bLCJrROQbEXmw1vIkEflMRDY5PxOb55QCW3MkAKVU61BfU9Cq6gDqCSwiocDzwIVAH+AGEelTZ7NHgZXGmFzgFuAZZ99+wERgKDAAuEREcpx9HgFmGWNygFnO70HriiuuYPDgwfTt25epU6cC8PHHHzNo0CAGDBjAOeecw/bt25kyZQpPPfUUAwcOZP78+dx222289dZbx45T8+2+tLSUc845h0GDBtG/f3/effddv5yXUqrl1NcZzJdPAN60AhoKbDbGbAUQkdeAy4G1tbbpAzwBYIxZLyJZItIB6A0sNsaUOfvOBa4E/uAcY5yz/8vAHODhJp6P37z00kskJSVRXl7OkCFDuPzyy5k4cSLz5s0jOzuboqIikpKSuOuuu4iNjeWhhx4C4MUXX6z3eFFRUcyYMYP4+HgKCwsZPnw4l112mZbTK9WKHJ8YplYRkA/rALxJAJ2BXbV+zweG1dlmFXAVsEBEhgJdgQxgDfBbEUkGyoGLgKXOPh2MMXsBjDF7RSTttM/C8ev3v2HtnsNNPcwJ+qTH88tL+za43bPPPsuMGTMA2LVrF1OnTmXs2LHHmlQmJSU16nONMTz66KPMmzePkJAQdu/ezf79++nYsWPjT0IpFZAiw0JJiztxYhjXsRnBWv7LnjcJoL4oTJ3fnwSeEZGVwNfACqDaGLNORH4PfAaUYhNFdWMCFJFJwCSAzMzMxuzqM3PmzGHmzJksWrSI6Ohoxo0bx4ABA9iwYUOD+4aFheHx2IxvjMHlcgEwbdo0CgoKWLZsGeHh4WRlZWkTTaVaoXRnWOgarmoPEaEhPnna9yYB5ANdav2eAeypvYEx5jBwO4DYqLc5L4wxLwIvOut+5xwPYL+IdHK+/XcCDtT34caYqcBUgLy8vLqJ5wTefFNvCSUlJSQmJhIdHc369etZvHgxlZWVzJ07l23btp1QBBQXF8fhw8efUrKysli2bBnXXnst7777LlVVVceOmZaWRnh4OLNnz2bHjh1+OTelVMvqnNCOdXuP3xOq3B6ffPsH71oBLQFyRCRbRCKA64H3am8gIgnOOoA7gXlOUqCmaEdEMrHFRNOd7d4DbnXe3woEbS3nhAkTqK6uJjc3l5///OcMHz6c1NRUpk6dylVXXcWAAQO47rrrALj00kuZMWPGsUrgiRMnMnfuXIYOHcqXX35JTEwMADfeeCNLly4lLy+PadOm0atXL3+eolKqhaQ7U0PWTAzjqvb4pAIYvHgCMMZUi8h9wCdAKPCSMeYbEbnLWT8FW9n7ioi4sZXDd9Q6xNtOHUAVcK8xpthZ/iTwhojcAewErmmuk/K1yMhIPvroo3rXXXjhhSf83rNnT1avXn3CssWLFx97/8QTTwCQkpLCokWL6j1maWlpU8JVSgWQ9IR2VFZ7KDrqIjk2MrASAIAx5kPgwzrLptR6vwjIqbufs27MSZYfBM7xOlKllGqFOh+bGKaC5NhIpwjINwlAewIrpZQfpR/rC1AGQKXbd08AmgCUUsqPjncGs638qpxWQL6gCUAppfwoITqcduGhxyaGcekTgFJKtQ0iQs8OsXyzpwSwrYC0DkAppdqIvKwkVu46hKvaQ5Vbi4DatJoB4fbs2cPVV199ym2ffvppysrKGnX8OXPmcMkll5x2fEqp5jUkK5HKag9f7y7xaTNQTQA+4na7G96ojvT09BNGCq3P6SQApVRgGdzVjhW2dHsRLrfRIqBgsn37dnr16sWtt95Kbm4uV199NWVlZWRlZfHYY48xevRo3nzzTbZs2cKECRMYPHgwY8aMYf369QBs27aNESNGMGTIEH7+85+fcNx+/ezUCm63m4ceeoj+/fuTm5vLc889x7PPPsuePXsYP34848ePB+DTTz9lxIgRDBo0iGuuueZYp7GPP/6YXr16MXr0aN555x0fXyGl1KmkxkXSLSWGJduLcVW7idQngOCyYcMGJk2axOrVq4mPj+eFF14A7LDOCxYs4Prrr2fSpEk899xzLFu2jD/96U/cc889AEyePJm7776bJUuWnHS0z6lTp7Jt2zZWrFjB6tWrufHGG3nggQdIT09n9uzZzJ49m8LCQh5//HFmzpzJ8uXLycvL4y9/+QsVFRVMnDiR999/n/nz57Nv3z6fXRellHfyshJZuqOIymrfjQXkVU/goPHRI7Dv6+Y9Zsf+cOGTDW7WpUsXRo0aBcBNN93Es88+C3BsDKDS0lIWLlzINdccH/GisrISgC+++IK3334bgJtvvpmHH/72tAgzZ87krrvuIizM/pPVN7z04sWLWbt27bE4XC4XI0aMYP369WRnZ5OTk3MsvppJa5RSgSEvK4k3luZTUl7FyO7JPvnM1pUA/Kju0K01v9cM7ubxeEhISGDlypVe7V+XMcarbc477zymT59+wvKVK1fqRDJKBbihWfZLnTG+mQ0MWlsC8OKbekvZuXMnixYtYsSIEUyfPp3Ro0ezYsWKY+vj4+PJzs7mzTff5JprrsEYw+rVqxkwYACjRo3itdde46abbmLatGn1Hv/8889nypQpjBs3jrCwsBOGlz5y5AgpKSkMHz6ce++9l82bN9OjRw/KysrIz8+nV69ebNu2jS1bttC9e/dvJQillP91TY4mJTaSwtJKrQQONr179+bll18mNzeXoqIi7r777m9tM23aNF588UUGDBhA3759j83z+8wzz/D8888zZMgQSkpK6j3+nXfeSWZmJrm5uQwYMIBXX30VgEmTJnHhhRcyfvx4UlNT+de//sUNN9xAbm4uw4cPZ/369URFRTF16lQuvvhiRo8eTdeuXVvuQiilTouIMCQrEfDd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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXcAAAEdCAYAAAD3ryfCAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8li6FKAAAgAElEQVR4nOzdd3hUVfrA8e9J7w1CIAUSek2ABATBgoiCgKgLiAKiq2Bfy7q76lp2LT/dXdcVXRVZQVERlSJgQ4ogHQw99JJAGkkgIb3P+f1xJxBgEtJnMnk/z5NnZm6bdy7hnZNzz32P0lojhBDCvjhYOwAhhBANT5K7EELYIUnuQghhhyS5CyGEHZLkLoQQdkiSuxBC2CFJ7kIIYYckuYsWSyn1s1LqFQvLxymlTiulvlBKvWZe1k8pla2U6lxpu2il1DmlVHjTRS1EzUhyFy3Zp8BUpZS6ZPlUYD5QVrFAa70LeB/4nzI4A3OBl7TWCU0TrhA1J8ldtGRLgQDgmooFSil/YAzwmYXt/w60A2YAzwN5wH8bP0whas/J2gEIYS1a60Kl1DfAPcB68+KJwCGt9Z5LG/Ra62Kl1P3ADxgNo4Faa1NTxixETUnLXbR084AJSil38+t7zMuqEofRXbNPa32osYMToq4kuYsWTWu9EcgAximlOgIDgC+r2eXfwK9AqFJqUhOEKESdSLeMEEb/+j1AN2Cl1jrN0kZKqeHAOKAnEAN8qpRaqbXObLJIhaghabkLYST3G4HpVNElo5TyBP4HPKm1ztBa/wSsAv7TZFEKUQuS3EWLZx7KuBnwBJZXsdn/YVxonV9p2ZPAKKXUTY0boRC1p2SyDiGEsD/SchdCCDskyV0IIeyQJHchhLBDktyFEMIOSXIXQgg7ZBM3MbVu3VqHh4dbOwwhhGhWduzYcUZrHWhpnU0k9/DwcGJjY60dhhBCNCtKqZNVrZNuGSGEsEOS3IUQwg5JchdCCDtkE33uQgj7VFpaSlJSEkVFRdYOpVlzc3MjNDQUZ2fnGu8jyV0I0WiSkpLw9vYmPDycy6eqFTWhtebs2bMkJSURERFR4/2kW0YI0WiKiopo1aqVJPZ6UErRqlWrWv/1I8ldCDu1aEcSK/eftnYYktgbQF3OoXTLCGGn3ll9BK3hxh5BODhIgr2SdevW4eLiwtVXX13nY3h5eZGXl9eAUdWdtNyFsEPlJs3p7CKSzxWy41SWtcNpFtatW8fmzZutHUaDkeQuhB1KyymizGRMxLNsd7KVo7Gu2267jejoaHr16sXs2bMBWLFiBf379ycqKorhw4eTkJDArFmz+M9//kPfvn3ZsGED9957L4sWLTp/HC8vLwDy8vIYPnw4/fv3p0+fPixbtswqn+tKpFtGCDuUfK4QgNZervywN5WXx/bC2bFltuXmzp1LQEAAhYWFDBgwgHHjxjF9+nTWr19PREQEmZmZBAQE8NBDD+Hl5cUzzzwDwJw5cywez83NjW+//RYfHx/OnDnDoEGDuPXWW23u2oIkdyHsUHKWkdzvHxrBP1YcYuPRMwzr3saqMf39u/0cSMlp0GP2DPbh5bG9qt3m3Xff5dtvvwUgMTGR2bNnc+21154fVhgQEFCr99Ra8/zzz7N+/XocHBxITk4mLS2Ntm3b1u1DNJKW+VUuhJ2raLnfPbA9vu7OLN+TYuWIrGPdunWsXr2aLVu2sGfPHvr160dUVFSNWtlOTk6YTCbASOglJSUAzJ8/n4yMDHbs2MHu3bsJCgqyyZu0pOUuhB1KyiokwNMFXw9nbunTlmW7UygsKcfdxdFqMV2phd0YsrOz8ff3x8PDg0OHDrF161aKi4v59ddfiY+Pv6hbxtvbm5ycC39ZhIeHs2PHDiZOnMiyZcsoLS09f8w2bdrg7OzM2rVrOXmyysKMViUtdyHsUPK5QkL83AG4NSqEgpJyVh1Ms3JUTW/kyJGUlZURGRnJiy++yKBBgwgMDGT27NnccccdREVFceeddwIwduxYvv322/MXVKdPn86vv/7KwIED2bZtG56engBMnjyZ2NhYYmJimD9/Pt27d7fmR6yS0lpbOwZiYmK01HMXouEM//c6urTxZtbUaEwmzXVvrcXb1ZnvHh+KYxOOeT948CA9evRosvezZ5bOpVJqh9Y6xtL2V2y5K6XmKqXSlVJxlZb9Syl1SCm1Vyn1rVLKr9K655RSx5RSh5VSN9fjswgh6kBrbbTc/Y2Wu4OD4pmbunEgNYclO5OsHJ1oKjXplvkUGHnJslVAb611JHAEeA5AKdUTmAT0Mu/zgVLKep18QrRAmfklFJWaznfLANwaFUzfMD/+9fNhCkrKrBidaCpXTO5a6/VA5iXLVmqtK35DtgKh5ufjgK+01sVa63jgGDCwAeMVQlxBxUiZipY7GLVJXhzTg/TcYmavP2Gt0EQTaogLqr8HfjI/DwESK61LMi8TQjSRijHulVvuANEdAhjdpx0f/XqCtBzbG7onGla9krtS6q9AGTC/YpGFzSxesVVKzVBKxSqlYjMyMuoThhCikoqWe6i/+2Xr/jyyG4Wl5SzZ2bJLErQEdU7uSqlpwBhgsr4w5CYJCKu0WShg8e4JrfVsrXWM1jomMDCwrmEIIS6RlFWIp4sjvu6Xz9rToZUnHVp5sCfxnBUiE02pTsldKTUS+Atwq9a6oNKq5cAkpZSrUioC6AJsr3+YQoiaqhgpU9VdmFGhfuxJkuReFxXFw1JSUhg/fny1277zzjsUFBRUu82l1q1bx5gxY+ocX2U1GQq5ANgCdFNKJSml7gf+C3gDq5RSu5VSswC01vuBb4ADwArgUa11eYNEKoSokeSswsv62yuLCvMjNbuIdOl3B6C8vPYpKjg4+KKKkZbUJbk3pJqMlrlLa91Oa+2stQ7VWs/RWnfWWodprfuafx6qtP3rWutOWutuWuufqju2EKLhVR7jbknfMF8A9iRlN1VIVpOQkED37t2ZNm0akZGRjB8/noKCAsLDw3nllVcYOnQoCxcu5Pjx44wcOZLo6GiuueYaDh06BEB8fDyDBw9mwIABvPjiixcdt3fv3oDx5fDMM8/Qp08fIiMjee+993j33XdJSUlh2LBhDBs2DICVK1cyePBg+vfvz4QJE85P6rFixQq6d+/O0KFDWbJkSYN9dik/IIQdySsuI7uwlBA/jyq36RXsi6ODajH97ocPH2bGjBns3bsXHx8fPvjgA8Ao3btx40YmTZrEjBkzeO+999ixYwdvvfUWjzzyCABPPPEEDz/8ML/99luVVR9nz55NfHw8u3btYu/evUyePJk//OEPBAcHs3btWtauXcuZM2d47bXXWL16NTt37iQmJoa3336boqIipk+fznfffceGDRs4fbrhpkWUwmFC2JHzwyCrabm7OTvSva130/e7//QsnN7XsMds2wdGvVntJmFhYQwZMgSAKVOm8O677wKcrymTl5fH5s2bmTBhwvl9iouLAdi0aROLFy8GYOrUqfzlL3+57PirV6/moYcewsnJSKeWSghv3bqVAwcOnI+jpKSEwYMHc+jQISIiIujSpcv5+ComFKkvSe5C2JHkc0Yfb3V97mD0u3+/JwWTSdv9/KqXXliueF1RCMxkMuHn58fu3btrtP+ltNY12mbEiBEsWLDgouW7d+9utEk+JLkLYUcqWu6WxrhX1jfUjy+3nSLhbD4dA72aIrQrtrAby6lTp9iyZQuDBw9mwYIFDB06lF27dp1f7+PjQ0REBAsXLmTChAlordm7dy9RUVEMGTKEr776iilTpjB//nyLx7/pppuYNWsW119/PU5OTheVEM7NzaV169YMGjSIRx99lGPHjtG5c2cKCgpISkqie/fuxMfHc/z4cTp16nRZ8q8P6XMXwo4knSvExdGBQC/XareLPH9R1f773Xv06MG8efOIjIwkMzOThx9++LJt5s+fz5w5c4iKiqJXr17n50WdOXMm77//PgMGDCA72/IF6AceeID27dsTGRlJVFQUX375JQAzZsxg1KhRDBs2jMDAQD799FPuuusuIiMjGTRoEIcOHcLNzY3Zs2czevRohg4dSocOHRrsc0vJXyHsRFFpORM/2kJuURlrn7m+2m3LTZo+f/uZiTFh/O3WxptEw9olfxMSEhgzZgxxcXFX3tjGNXjJXyGE7Ss3aZ76ejd7k7J58sYuV9ze0UHRO8S3RbTcWypJ7kI0c1prXloWx09xp3lhdA/G9a2mVl9xLswdBStfYFA7xf6UHErKTE0XbBMLDw+3i1Z7XcgFVSGauXdWH2X+tlM8dF0nHrimY/Ub7/0GTm2GU5t53HkeZXoUx3a70bN3f3DzbZqARZOQ5C5EM/b51pPMXHOUCdGh/GVkt+o31hpiP4G2kXDbh7DyZf584mv4/mv4HvBsA606Q6tO0LqL+XlnCOgIjpcXIaupmgwVFNWry7VRSe5CNFM/7E3lpWVx3NijDW/c0efKCTQpFtL2wZh3oG1vnO9ZzIpfN7Lk59U83EfTz+MsnD0OR1bArs8v7BfcD2asq1OMbm5unD17llatWkmCryOtNWfPnsXNza1W+0lyF6IZ2nzsDE99vZvo9v68d1d/nBxrcPksdi64eEOfC9UMb752CJ8dceKeQ9ms/uN1BPmYE0jhOcg8bnTjbJsF6QehTe1HvYSGhpKUlITM2VA/bm5uhIaGXnnDSiS5C9HMxCVnM/2zWCJaezJn2gDcXWowTXFBJuxfAn0ng6v3+cVKKV6/vQ83v7Oev3+3nw8mRxsr3P0gJBp8QmHbR7B/aZ2Su7OzMxEREbXeT9SfjJYRohlJOJPPvZ9sx8/DhXm/H4ivRw37wvd8BWVFEHPfZasiWnvyxPAu/LjvNKsPpF280jsIwofC/m8bIHrRlCS5C9FMZOaXMHXuNkwaPrt/IG19a9gHW5wL22dD6ECj0JYF06/pSLcgb15aFkd+cdnFK3uOgzOHja4Z0WxIcheimfjPqiOknCti7r0D6FTTejDlpfDNPXDuFAx7vsrNXJwc+L87+pCaU8S/Vx65eGWPWwFldM2IZkOSuxDNwLH0XL7cfoq7B7anb5hfzXbSGpY/Dsd/gbEzodOwajeP7uDPlKs68Onm+ItrvUvXTLNUk2n25iql0pVScZWWBSilVimljpof/Sute04pdUwpdVgpdXNjBS5ES/LGj4fwcHasUWmB8379J+xZAMP+Cv2n1miXP43sRqC3K88t2UdZeaU7V6VrptmpScv9U2DkJcueBdZorbsAa8yvUUr1BCYBvcz7fKCUqsGlfCFEVTYfO8OaQ+k8Mqwzra5Q7fEisXOh60i49k813sXHzZm/39qLA6k5zN0Uf2GFdM00OzWZQ3U9kHnJ4nHAPPPzecBtlZZ/pbUu1lrHA8eAgQ0UqxAtjsmkef3Hg4T4uXPfkPCa75iXDnmnIeI6qOXNQzf3asuInkG8veoIiZnmCZ69g6D9IOMGJ9Es1LXPPUhrnQpgfmxjXh4CJFbaLsm8TAhRB4lZBexPyeHB6zri5lyLP4JT9xqP7SJr/Z5KKV4Z1wtHpfjr0rgLt76HXwOn9xqjb4TNa+gLqpaaCBaLIiilZiilYpVSsXL3mhCWnc0vASAsoOoJry06bU7uQb3r9L7tfN35083dWH8kg+V7UoyFHQaDNkHi9jodUzStuib3NKVUOwDzY7p5eRIQVmm7UCDF0gG01rO11jFa65jAwMA6hiGEfcsyJ/cAD5fa7Xh6L/h1MO40raOpg8OJCvPjtR8OUlRaboyTV45wcnOdjymaTl2T+3Jgmvn5NGBZpeWTlFKuSqkIoAsgX/NC1FFWQSkAAZ61Te77qrxhqaYcHRTPj+pORm4x87edAlcvaBcFp7ZY3D42IZOpc7axeEdSnaoYioZVk6GQC4AtQDelVJJS6n7gTWCEUuooMML8Gq31fuAb4ACwAnhUa13eWMELYe8qWu7+tUnuxXlGdcd2UfV+/6s6tuLqTq2Y9etxo/Xe4Wp0Uiyf/nqIuRvjScwsoNykmbn6KBM/2sL2+Ez+uHAPE2ZtYX+K5TlHRdO4YuEwrfVdVawaXsX2rwOv1ycoIYQhs6AEF0cHPGtSHKxC2n5A17vlXuGJ4V24c/ZWvth6kgc6XI3a8l9+WPEDv+nuvPL9AVp5unA2v4Tb+4Xw93G9WLHvNG+uOMTY9zYyZVAH/jiiW81r4IgGI1UhhbBhWfkl+Hk4164WesXF1La1HyljyYXW+wl0TBumAw90OM1bEx5i9cF0tsefZWTvttzezyhJO3FAGDf3asvbqw7z+daT/LA3lb+M7M746FAcHKSme1OR8gNC2LDM/JLa97en7gH3APAJbrA4nryxK2fyinl9XTopLuHc5HmcDq08uX9oBB9NjTmf2Cv4ejjz93G9+e7xoUS09uTPi/dyx4eb2SsTcjcZSe5C2LCsghL8az1SZp8xvr0BZz4aGBHAuL7BjO7TjqDew1CJ26G87Ir79Qr2ZeFDg3l7YhRJWYWMe38Tb686csX9RP1JchfChtW65V5eCukHGqy/vbKZk/rx/uT+OEYMhZJcY8q+GlBKcUf/UH555jqGdWvDrF+PX1y3RjQKSe5C2LCsglL8PWtxMfLMESgvgbb1HylTpfaDjcdajnf3cXNmTGQ7SspMxJ/Jb4TARGWS3IWwUeUmzbmCktrdwFRRdqARWu7n+YZA665wYHmtd+3RzgeAA6k5DR2VuIQkdyFsVE5hKSZdyzHuqXvAyR1a16I0cF30mwqJW2tdArhToBfOjoqDqVKfprFJchfCRmUWmEsP1DS5J/4GOz6BjteBQyNX2u47GRxdYMentdrNxcmBToFeHDotLffGJsldCBt1/u7UmnTLZJ6ABZPAux2M+6CRIwM8W0GPscZkIKWFtdq1ZzsfDkq3TKOT5C6EjapxXZmCTJg/AXQ5TF5kJN6mEH0vFGXXegKPHu18SMspJtP85SUahyR3IWxURcvdr7pb98uK4avJxgTYkxZA685NFB1GffeATrXumqm4qCqt98YlyV0IG3XFPneTCZY+Aqc2w20fGvXWm5JSRus9cSsk76zxbj3aeQOS3BubJHchbFRWfgmuTg64VzUD09rXIW4RDH8Z+oxv2uAq9L0b3Hxhzk3w3RPGXxBX0MrLlUBv14tHzJQVw6ltsOldWHS/8VzUixQOE8JGVdydarFo2M7PYMNb0P8eGPpU0wdXwbM1PLwFNv4Hds4z4vINBf9wY7IQ//ALP34djO2Vokfli6plxfDRdZBhHlbp6AqntsIjm40vDlEnktyFsFFV1pU5/gt89yR0ugFGv92gNWTqxDcERr9lfMns+hzOHIVzJ+HIz5CffvG2zp7QZQQ92z7D3ONnKS034bztIyOx3/IW9BxntP7njICfn4dx71vnM9kBSe5C2CiLdWXS9sM30yCwO0yYB442VCfdNwSuf/biZSX5RrLOSoCsk5AWB7s+5/auQcwqv46TpxLpvP4t6DwCBk439vFqA0OehI1vQ49boevNTf5R7IEkdyFsVFZBKSH+lSbGzj8L8yeCiydM/gbcfKwXXE25eEKbHsZPBa3puns2gx1ao9b/YBQhu+nVi/e7/lk4sgKW/wEe3mR054haqdcFVaXUU0qp/UqpOKXUAqWUm1IqQCm1Sil11Pzo31DBCtGSZOaXEFB5GOT22ZCTBJO+NPq1m6tb/gmtOvOe838JT/gK3X/axckfwMnVGAFUmAUfD4f0Q9aJtRmrc3JXSoUAfwBitNa9AUdgEvAssEZr3QVYY34thKiFsnIT2YWlF+rKlBVD7BzocjOE9LducPXl4okaPxdflU+hyZkBmwbS8bkfeHFp3MUTawf3hXt/gJICow/+6CrrxdwM1XcopBPgrpRyAjyAFGAcMM+8fh5wWz3fQ4gW51zhJXenxi2G/AwY9LAVo2pA7SJJuPlTVvZ5myk3xjAmMpjPt55k9voTF28XNgCm/wL+HeDLibDlfaj8BSCqVOc+d611slLqLeAUUAis1FqvVEoFaa1TzdukKqXaNFCsQrQYF9WV0Rq2fgCBPaDj9VaNqyF1GTyWitqVJpOm3KR5c8UhOgV6cWPPoAsb+oXB73+GJTOMETTpB41RQk61nKGqhalPt4w/Ris9AggGPJVSU2qx/wylVKxSKjYjI6OuYQhhlyrqyvh7uMDJTcbUeYMesv6wx0bi4KB4a0IUvYN9eeKrXRxLv6QksIsnTPwcrv2TMdzys3GQf8Y6wTYT9emWuRGI11pnaK1LgSXA1UCaUqodgPkx3dLOWuvZWusYrXVMYGBgPcIQwv5UFNXy93SGrR8aE15H3mnlqBqXu4sj/7snhjKTZsH2xMs3cHCAG16A382B5B3wv2GQdqDpA20m6pPcTwGDlFIeyriFbjhwEFgOTDNvMw1YVr8QhWh5ssx1ZVqrHDj8I0RPA2d3K0fV+Nr6utEnxJfdieeq3qjPeLjvJ+Mi85wRcGRl0wXYjNQ5uWuttwGLgJ3APvOxZgNvAiOUUkeBEebXQohaqGi5+xUkgDZB+FDrBtSE+ob5EZecTWl1k2iHRsP0tRDQERbeC8Uys9Ol6jVaRmv9sta6u9a6t9Z6qta6WGt9Vms9XGvdxfyY2VDBCtFSZOWX4OHiiGvOSWNBQEfrBtSEosL8KC4zcfj0FRK2b4hxYbU0H+KWNE1wzYhUhRTCBmVW1JXJjAflCL5h1g6pyfQN8wNgV3VdMxVCY4xSDLs+b+Somh9J7kLYoKyKujJZ8cZQQFuqIdPIQv3daeXpwp6aJHeljMm6k36Tu1gvIcldCBuUWWC+OzXzBPhHWDucJqWUom+YX/UXVSuLvBMcnKT1fglJ7kLYiNiETFbEpZKVX2K03D2cjW6ZgJaV3MHomjmekUdOUemVN/YKhG6jYM9XUCbzslaQqpBC2ICCkjLu++Q3covLUMq4KXV0FzcoOtfiWu5gXFTVGvYlZTOkcw0qQvabCge/MypJ9ry18QNsBqTlLoQNWL47hdziMl6/vTdP3diVYd0CGRlcaKxsQSNlKkSFGhdVa9w102k4eLeDbbOMuWWFtNyFsDatNV9sO0n3tt7cPbD9hWn14hYbjy2wW8bXw5mOrT3ZdaqGyd3RCa77M3z/FGx6B655unEDbAak5S6Ele1NyiYuOYfJV7W/eL7UTHOFRP9wq8RlbRUXVXVNq0BG3we97oBfXoOTmxs3uGZAkrsQVvbF1pN4uDhyW7+Qi1dkJoBXkFE0qwXq296PM3nFpGQX1WwHpWDsTOPLcNHvW3xhMUnuQlhRdkEp3+1NYVzfELzdLhnLnhXfIi+mVujf3pjE7e2VR6ovRVCZmw9M+BQKMmHFc40XXDMgyV0IK1q8M4miUhNTBrW/fGVmfIu8mFqhV7APf7ihM4t3JvH7T3+r2bBIgHaRxmTb+5dATkrjBmnDJLkLYSUmk+azLQn0a+9Hr2Dfi1eWFkJuSou8mFpBKcXTN3Xjn+Mj2XL8LBM+3ELyucKa7TxwulFw7bePGzfI2jh7HBK3N9nbSXIXwkrWHEon4WwBvx9iIYFnmQuGteBumQoTY8KY9/uBpGQXctv7m9iXlH3lnfzDodstEPuJ8UVpC5bMgHljjSTfBCS5C2ElczaeINjXjVG9216+smKkTAtuuVc2pHNrljx8NS6ODkz8aAsr95++8k5XPQSFmbD3m8YP8ErSD0JyLJQVwXdPNMk8sJLchbCCuORstp7I5N4h4Tg5WvhvmBVvPErL/bwuQd4sfXQIXYO8ePCLHczdGF/9DuFDIai3cWOTtSfV3vWFUf9m2AuQsMF43cgkuQthBXM3xuPh4sidAyxcSAXjYqqrL3gENG1gNi7Q25WvZgzmpp5BvPL9Af62fD/lpioSt1Iw6GFIPwAn1jZtoJWVlxp1b7qNgmv+CB2GwMq/Qm5ao76tJHchmlhaThHf7U1hYkwYvu5VlPLNioeAcLudELs+3F0c+WByNA8MjeDTzQmMfncDD8yL5YmvdrFg+6mLb3rqPR58QmHZY5CTap2Aj/wMBWeM+jcODsZY/NIi+P7JRv2LQpK7EE3ssy0JlJk09w0Jt7yByWRcdJMumSo5OiheGNOTN+/og7ebEynnColNyOK5Jfv469I4yirGxTu7wd1fQVE2LLgTSvKbPthdn4NXW6P+DUDrLnDjy8bcuFs/aLS3rVdtGaWUH/Ax0BvQwO+Bw8DXQDiQAEzUWmfVK0oh7ERhSTnzt53ipp5BdGh1yZ2nGYeNoXsHvzeGQUbeaZ0gm5FJA9szaaDRtWUyad5aeZgP1h0nKauQ9+/uZ9wY1rYPjP/ESO6LH4DhL4OLBzh7GpOOO7s33l9IOalwdCUMecKof1Nh0CNGiYRVL0FIDLS/qsHfWtW4boOlnZWaB2zQWn+slHIBPIDngUyt9ZtKqWcBf631X6o7TkxMjI6Nja1zHEI0F19sPckLS+P45sHBDIyo1J+uNbzTx7hlvvNw6DkOetxqtDxFrXy1/RR/XRrHqN5t+e/d/S+s2P4/+PEZC3socPYwJ3wPCImGO2bXb/YrrSFhI6x7E05uhMd2UB7QCQfFhfpBhedg9nVGn/yDG8CzVa3fRim1Q2sdY2ldnVvuSikf4FrgXuOz6BKgRCk1DrjevNk8YB1QbXIXoiUwmTRzN8XTJ8SXAeH+F69MPwjZiXDrf6H/VOsEaCcmDWxP/Nl8/rf+BElZBYT6exgrBk6HdlHGeS4pgNICo5umtMD8Ot8oW7B/idF1Mux5y29gMhl95xbXlRt15TfNhJSd4NEaRv0TWnfm78viWHMwndn3RBs3rbn7wYR5MOcmmP87mLKkQS+g16dbpiOQAXyilIoCdgBPAEFa61QArXWqUqqNpZ2VUjOAGQDt21cxYkAIO/LrkQxOZOQzc1Lfi6s/Ahz/xXjsdEPTB2aH7hkczscb4vl8y0meu6XHhRVhA42f6ix5ENa/BV1HQkj/i9edOwVzR0JQL+OL2DvIWF5aCLu/hM3vmS+Gd4TRb0Pfu8HZndJyE0t3JZNTVMaEWVuYOakfI3oGQXBfmPgZfHMPfHIL3LMUvC3c91AH9bmg6gT0Bz7UWvcD8oFna7qz1nq21jpGa32JIfUAACAASURBVB0TGBhYjzCEaB4+3niCtj5u3NKn3eUrj6+BwO7gG3L5OlFrIX7ujOzVlgXbT1FQUla7nUe9aVTj/PYhY1RLheI8WHA3FOVA/Hr48GqIWwK//gv+0xt+eBrc/Y1k/VgsDLjf6M8HYhOyyCkq45VxvejSxosZn8eyYPsp47jdRsKURcZfFHNvhtS9DXIO6pPck4AkrfU28+tFGMk+TSnVDsD8mF6/EIVo/g6m5rDp2FmmXR2O86U3LZUWGhfXpNXeoO4bEk5OURmLdybXbkd3fxj3Hpw5DMseMS50m0zw7YOQvh8mfgoz1hkt7EX3wdrXjBb+vT/A9F+M6yUOjhcdcs3BNFwcHfhd/1C+mjGYIZ1a8+r3B8jILTY2iLgW7llmfHF8dA18M81433qoc3LXWp8GEpVS3cyLhgMHgOXANPOyacCyekUohB2YszEed2dH7h5ooQvy5GbjtvSKoXKiQUR38Ccy1JdPNsVjqupGp6p0vhGGPg37v4X3B8LMKDj0Pdz0urGuTQ8jkd/+ETy8GSYvNO6ItTDqRmvN6oNpDOrUCk9XJ9xdHHn1tt6UlJmYuebIhQ1DY+APu+DaP8Ox1fDBIKOLqKIURS3Vd5z748B8pdReoC/wf8CbwAil1FFghPm1EC1Wem4Ry3enMD46FF8PCyMwjv8Cjq7Q4eqmD86OKaW4b0g4JzLy+bkmtWgudePL8PQhuPkNo2990CPGHa8VnFwhapLR/16N4xn5JJwtYESPC5cfI1p7ctfA9izYnsiJjLwLG7v7wQ1/hSf2wuDH4MAyeC8Glv8BziXWKvx6JXet9W5zv3mk1vo2rXWW1vqs1nq41rqL+TGzPu8hRHP3xdZTlJpMVd+0dPwX6DDYGIonGtToPsF0buPF4wt28fGGEzWfsq+CdxAMfgQeWA0j36jTePg1B40yAzf0CLpo+R+Gd8HNyYF//Wyh+8WzFdz0KjyxGwY8AHsWwHv94cc/Q27NvqjkDlUhGlFRaTlfbD3J8O5t6BjodfkGOalG7RPpb28ULk4OLHnkam7o3obXfjjIw1/s5Kd9qWw5fpbDp3NJzymipKyGszzV0ZqD6fRo50OIn/tFywO9XZlxbSd+ijvNjpNV3Ofp3RZu+Sc8vhOi7jJucpsZBd89CWeOVfu+9bpDVQhRvaW7ksnML+H+oVXMqFRR0Er62xuNj5szH02N5uMN8by54hArLHTReLk64efhTICnCwPDA/jr6B6XD1etg6z8EmJPZvLYsM4W1z9wTQTztiTwv/UniJ4aXfWB/MLg1neNO103zTSGXe74tNr3luQuRCPRWjNnYzw92/kwqGMVN6cc+Rk821yx31bUj1KK6dd2ZHx0KKdzisgqKCErv9T8WEJWgfE8OauQjzfG06+9P6MjLQxZrQWtNV/HJmLSMPySLpkKnq5OjOsbzPytp8guKLV8TaayVp2MJH/DC7B9NvBilZtKcheikWyPz+Roeh7/nhBluRWYlw6HfjDGQ0v1xybh7+mCv6dLlevLTZrR727gzRUHubFnG1ydHC/bJjGzgEBvV9ycL19XIS45m9d+OMDWE5n0a+9HnxDfKre9o18on2xK4Id9qdx9VQ1v6PRqYyT4apK79LkLYUFWfglxyTWYzq0a2+IzUQpG9LLcamPHp2AqhQHT6/U+ouE4OiheGN2TxMxC5m1OuGz9usPp3PDvdUybu53S8sv76tNzivjTwj2M/e9GjqTl8eptvVn44GAcHKr+8u4d4kPnNl58uyupIT+KJHchKjt0OodnF+9l0BtrGPPeRradOFvnY+06lUXnQC983Cz8qV1eCrFzjb721pb7Y4V1DO3SmmHdAnnvl2Nk5pecX/5bQiYPfbGDNt5ubIvP5JXvDpxfV1RazntrjnL9W+tYujuZ6dd0ZO0z1zN1UAfLM21VopTi9n4h/JaQRWJmQYN9DknuQpjtPJXFqJkbWLo7mTv6hxIW4M6zS/ZRVFpe62NprdmVeI7+7f0tb3DwO8hNhaserGfUojE8f0sPCkrKeXzBTj7dFM+y3cn8/tPfCPZ1Z9ljQ3jw2o58vvUk87edZNnuZG54ax3/XnWEa7sEsvrp63j+lh5VT8Riwbi+wYBxAb6hSJ+7EGZbjp9Fa1j/p2G08XFj07EzTP54G/9ZdeTi4lM1EH8mn3MFpfRr72d5g+2zwT8cOo+of+CiwXUJ8ubpEV35cN1xNh0z/noL9nXj8weuorWXK38e2Z1Dp3P567dxgNG18vadfRnUsfZlewFC/T24KiKAb3cl89gNnRtkpI4kdyHMDp/OJcTPnTY+Rg31IZ1bc9fA9vxvwwlG9WlH37AqErUFu06dA6CfpZZ76l44tcW4lb2q0rHC6h4d1plHru9ERl4xCWcK6BrkhZ+HcTHW0UHx7l39+Nvy/VzdqRW/6x9abb96TdzeL4Rnl+xjd+I5y783tSS/WUKYHT6dS/e23hcte+6W7rTxduPPi/ZQXFbz7pmdp7LwdnWiSxsLNy5tn21MCtFvcn1DFo1MKUUbbzcGRgScT+wVfN2d+c+dfZkQE1bvxA5wS2Q7fN2deW7JvtpXsrRAkrsQQEmZieMZeXS9JLn7uDnzf3f05khaHu//Uv0dgZXtOnWOqDC/y//TF2TCvoUQOdGoPiiEmY+bM+/e1Y8jabk8s3BP7UslXEKSuxAYfeRlJn1Zyx3ghu5B3NEvhA/WHedASs4Vj1VQUsah0zmW+9t3fW5UgBw4oyHCFnbmuq6BPDeqBz/uO837a2vemLBEkrsQGEMgAbpZSO4AL43tiZ+HC39atMfi+ObK9iRmY9JcPlLGVG7UBukwVO5IFVV64JoIbu8Xwlsrj7DqQFqdjyPJXQiM/nYnB0XH1hb6yAE/DxdeHdeL/Sk5zF5ffX3tXYlGEajLLsAe+dmYpu0qabWLqimleOOOPkSG+vLU17s5mpZbp+NIchcCI7l3DPTExanq/xKj+rTjlj5tmbn6KEeq+Q+369Q5Ilp7Xn6b+/aPwCcEuo1uqLCFnXJzduSjqdG4OTsy/bNYsgtKa30MSe5CAIfTcunW1ueK270yrjdebk78aeEeyix0z2it2XUq6/L+9ozDcGIdxPweHGUEsriydr7ufDS1P8nnCnlswU6Lv2/VkeQuWry84jKSsgotXky9VGsvV14Z14s9SdnM3nB598y2+EzO5JVcPk55+//A0QWi722gqEVLEN0hgNdu682Go2f4p6VJPapR7+SulHJUSu1SSn1vfh2glFqllDpqfpTxXsKmHT5tdLF0DbpycgcY3acdo3q35Z1VF3fPpOcU8fiCXYS38jh/OzlgTHq8ZwH0/h14tm7Q2IX9u3NAe6YN7sDs9SdqVVysIVruTwAHK71+Flijte4CrDG/FsJmVST3mrTcwbjg9eptRvfMhFlbWLQjiZIyE49+uZO8ojJmTY2+uFjYngVQkifDH0WdvTCmJ4M6BvCXxfvYm3SuRvvUK7krpUKB0cDHlRaPA+aZn88DbqvPewjR2I6k5eLp4njZNGjVae3lyqKHBtOljRfPLNzDsLfW8VtCFm/+rg/dK/fdm0zGHakhMRDSvxGiFy2Bs6MDH0yOJtDLlemfxZKUdeXqkfVtub8D/Bmo3NMfpLVOBTA/trG0oxC24tDpHLoEedf6FvKOgV588+Bg/n5rL84VlPDA0AjG9Q25eKMTa+HsMan+KOotwNOFOffGUFhSztQ52zmTV1zt9nVO7kqpMUC61npHHfefoZSKVUrFZmRk1DUMIepFa22xpkxNOTgopl0dzu6Xb+KFMT0v32D7bGMavZ7yB6yov+5tfZh77wBSswuZNnd7tdvWp+U+BLhVKZUAfAXcoJT6AkhTSrUDMD+mW9pZaz1bax2jtY4JDAysRxhC1F1GbjFZBaVV3plaU86WJmTISjBuXIq+F5yqntpNiNqICQ9g1pToau+1gHokd631c1rrUK11ODAJ+EVrPQVYDkwzbzYNWFbX9xCisR1INcoOdK/BGPdaO/g9oKH/1IY/tmjRru/Whrcn9q12m8YY5/4mMEIpdRQYYX4thE3aby4E1jO4EZL7sdUQ2B38ajjpsRC1MDYquNr1DXKrnNZ6HbDO/PwsMLwhjitEY9ufkk37AI9aTYlWIyX5cHKTDH8UViN3qIoWLS45h94hjdBqT9gE5SXQWdo5wjokuYsWK7uwlFOZBfQK9m34gx9bDU7u0P7qhj+2EDUgyV20WBUTb/Sy1N9+YDnMvh5O76vbwY+thohrwNmt7gEKUQ+S3EWLtT8lG+DylntxHvz4DKTsgjk3w+GfanfgzBOQeRw639hAkQpRe5LcRYu1PyWHIB9XAr1dL16xaSbkpcGdX0BgV1hwF2x+D2o6p+WxNcajJHdhRZLcRYsVl5xN70tb7dlJRiLvPR56jIV7f4Set8LKF2D541BWcuUDH1sD/uEQ0LFR4haiJiS5ixapsKSc4xl5l/e3r3kF0HDjy8ZrFw8Y/ylc+ydjcusv7oCCzKoPXJwL8euNVruqXa0aIRqSJHfRIh08nYNJQ6+QSi330/tg79cw+NGLbzxycIAbXoA7/geJ22DWUPPdp5fIPAFzboKyQuh1R+N/CCGqIfN9iRap4s7U3pWT+9GVxuOgRy3vFDkRWnWC5X+ArydDt1ug3xRwdIX8DFjxF1AOMGUxhA9p5E8gRPUkuYsWaX9yNn4ezgT7VhqqmLgdWncDz1ZV7xgSDTPWwdYPYO0bcPjHC+uC+sCkL4z+diGsTJK7aJH2p+TQO9gXVdEvbjIZXS7dx1x5Z0dnGPIE9J0C2YlQXgq6HNr1lXHtwmZIchctzunsIg6fzuW+IeEXFp49BoVZEHZVzQ/k2ar6Vr4QViQXVEWLkldcxn2f/oaLkwPjo0MvrEjcZjy2H2SdwIRoYNJyFy1GWbmJR+fv5EhaLnPvHUCXoEoTdCRuBXd/aNXZegEK0YCk5S5ajNd+OMivRzJ4dVxvrut6yexfiduNLhkZmy7shCR30SKUlJn4ctspJkSHcvdVl0yeUZAJZ47Urr9dCBsnyV20CEfScikpN3FdNwvz9SaaJxqW5C7sSJ2Tu1IqTCm1Vil1UCm1Xyn1hHl5gFJqlVLqqPnRv+HCFaJu9iYZFSAjQ/wuX5m4DRycILhfE0clROOpT8u9DPij1roHMAh4VCnVE3gWWKO17gKsMb8Wwqr2JZ/D192ZsAD3y1cmboe2kUYdGSHsRJ2Tu9Y6VWu90/w8FzgIhADjgHnmzeYBt9U3SCHqa29SNpGhlW5aqlBeCsk7ZAiksDsN0ueulAoH+gHbgCCtdSoYXwBAm4Z4DyHqqqi0nMOnc4kMtTCdXtJvRqEv6W8XdqbeyV0p5QUsBp7UWufUYr8ZSqlYpVRsRkZGfcMQokoHU3MoM2n6WOpvj1sCTm4ykbWwO/VK7kopZ4zEPl9rvcS8OE0p1c68vh2QbmlfrfVsrXWM1jomMNDCCAYhGsi+ZPPF1Etb7uVlcGApdL0ZXL0t7ClE81Wf0TIKmAMc1Fq/XWnVcmCa+fk0YFndwxOi/vYmZdPay4V2vpcU9UrYYJTq7f076wQmRCOqT/mBIcBUYJ9Sard52fPAm8A3Sqn7gVPAhPqFKET97EvKpk+IhYupcYvBxRu63GSdwIRoRHVO7lrrjUBV92pLB6awCQUlZRxNz+Xm3m0vXlFWAgeXQ/fR4GxheKQQzZzcoSrs2oEUYzq9yJBL+tuP/wJF2dIlI+yWJHdh1yruTO1z6cXUuMVGFciO1zd5TEI0BUnuwq7tTTpHkI8rQT6VLqYWZhnT4/W4FZxcrBecEI1IkruwW0fScvkx7jRDOre+eMX6t6AkHwZOt05gQjQBSe7CLpWUmXjyq914uzrx/C09Lqw4exy2fQT9pkDbPtYLUIhGJjMxCbv0zuojHEjNYfbUaFp7uV5YseolcHKFG160XnBCNAFpuQu7E5uQyaxfj3NnTBg39ao0BDJ+Axz6HoY+Bd5B1gtQiCYgyV3YlbziMp7+Zg8h/u68OLbnxStXvwy+YTD4UesEJ0QTkuQu7Mpr3x8gMauAtyf2xcu1Uq9jdpJR2nfgdLlpSbQIktxbkOMZeby96ghrD1us5dbsrTqQxle/JfLQdZ0YEB5w8crja43Hzjc2fWBCWIFcULVzOUWlfL8nlUU7Etl56hwAbs4OLHl4CD2DfawcXcM5k1fMc0v20qOdD0/d2PXyDY6vAa+20Kbn5euEsEOS3O2QyaTZfPwsC3cksiLuNMVlJrq08eL5W7oztHMg9326nYe+2MF3jw3F18PZ2uHWm9aa55bsI6ewjPkP9MXF6ZI/SE3lcGIddB0FlxYPE8JOSXJvJgpKyvjo1xNc1y2Q/u0tzzmeXVDKxxtPsHhHEinZRfi4OTEhJpQJ0WEXTTH3weRoJs3ewpNf72LOtAE4ODTvhLcwNolVB9L46y096NbWQl321N3GXamdbmj64ISwEknuzUD8mXwe+nwHh9Ny+XDdcf4xvg+39wu9aJuCkjKmfbKdPUnnuKZLIM/d0oMRPYNwc3a87HjRHfx5aUxPXly2n3d/OcqTlroxmolTZwv4+3f7GdQxgPuHRlje6NgvxmOnYU0XmBBWJsndxq0+kMZT3+zG0UHx/t39+WLrSZ76eg/H0vN48sauODs6UFZu4vEvd7E36RyzpkRzc8XYblM5ZByG1L1wei+c3gfpB6HLCKaMeYddp84xc81R+ob5cX235jfVbblJ88eFu3FQircmRFX9F8jxX6BdFHi2trxeCDskyd1GmUyad385yjurj9I7xIcPJ0cTFuDBiJ5BvLQsjvfXHmfB9kTGRrbjXGEpaw6l84/RHbi58Ef4bo+RyNMOGJM/Azi6QJseENwXdn2O0iZev20mB1JzeOKr3Xz/+FDCAjys+6Fr6Z3VR/gtIYt/T4gi1L+K2ItyIGk7XP140wYnhJVJcrdBOUWlPP31HlYfTON3/UN5/fbeuDkq2L8Ul+B+vHFHH27u3ZZFO5JY8FsiJWUmnr3ahzv3TYf0A+DmC20jIeb3Rv2Utn2gddcLFRDXvgG/vom7swezJv+Nse9v4pH5O/n6wUF4uDSPX4mf9qXy3i/HmBAdyh39Q6reMGEDmMqgk8wfI1qWRvufrJQaCcwEHIGPtdZvNtZ72ZNj6XnM+DzW6Eu+tRf3DO6AKjoHCx+GIz+BswfqhhcYdtVDDOvWhpyiUhIO7abP2nuhMBumLDYSWXWjQq5/FkrzYfN7hHu14e2J05j+WSzX/nMtD17bicmD2qNQJJzN53R2EaXlJkxaU26Ccq0xmTTlJk25Nh793J0JC/AgLMADX/fGH31zMDWHPy7cQ7/2frx2e+/Lp8+r7NhqcPaEsKsaPS4hbInSWjf8QZVyBI4AI4Ak4DfgLq31AUvbx8TE6NjY2AaPo7lZuf80T3+zB1cnBz6Y3J+rOraClN3wzT2QkwLDnodTW+Hoz0YfckAnKM6FpN/A0RkmLzK6XWpCa1gyA/YthHuWssMxkv+sOsrGY2dwc3agqNRUp89wfbdAXhrTk46BXnXavzp5xWWsOZjGv34+TGm5ie8eG0obH7eqd9j5GXz3BPS6A8bPafB4hLA2pdQOrXWMxXWNlNwHA3/TWt9sfv0cgNb6DUvbt/TkbjJp3llzlHfXHCUy1JdZU6IJdiuFX/8B22aBVxBMmAdhA4ykHLfYWGcqBzcf8A6Gm16FVp1q98bFefC/G6AwEx7aCN5tiU3IZPmeFFp7uRLR2pNgP3dcnRxwUApHB4WjAzgohZODAw7m51kFJSRmFnIgNYdPNsZTVFbOA9d05OHrO+HjVr+WfHZhKb8cSuOnfaf59UgGxWUm2vq48dHUaKLC/KreceM7Ri2ZzjfCxM/AxbNecQhhi6yR3McDI7XWD5hfTwWu0lo/Zmn7WiX38jIjGRWchcJz4OIB7gHGSIhmWDMkp6iUp77azZpD6YyPDuW1cb1wO7QEVr4AeenQ/x648W/gEXClQ9VN+kEjwQf3h3uWgWP9euoycov5x4pDLNqRhLerE1MGd+C+IeG08a6mhW1BXHI276w+yrrD6ZSZNEE+rozq3Y7Rke2Ibu9f9cgYrY2yvpvfNeZHvW2WzLYk7JY1kvsE4OZLkvtArfXjlbaZAcwAaN++ffTJkycvPoipHLISyE/cS/qJPZSmxOGZfZQ2pUk4U3bZe2oHJ9TAB2HYc+Bq4UYWG3Q0LZcHP9/BqcwCXhrbk6kReaif/gwnN0FwP7jl3xAa3fiB7P4Slj4MvW43kqFz7RKxJXHJ2Xz463F+3JeKAtydHXFxcsDN2RFfd2f8PJzxc3cxHj2MR38PZ7zdnPl+bwo/7juNn4czd8aEMbJ3W6JC/a58s1V5GXz/JOz6HGLuh1v+BQ6Xj/MXwl7YfrdMmLv+7eWhlHgFk1cCOuMQPnkncNEl57c5pQNJco6g0LcLWU6BpJS4c6rAhby8HHzJo786ygSnXzF5tsXpljeh5zibvtV80Y4kXlwah6erI7PGdyYm/iPYPtsY6XLjy9BvatMmpk3vwqoXocNQmDQf3Kvp8qiF+DP5LN2VTF5xGSVlJgpLy8kuLCW7oJSsghLOFZZyrqCE0vILv4eeLo7cf01HHrgmoubdOqVFsPh+o177dX+B65+z6X9/IRqCNZK7E8YF1eFAMsYF1bu11vstbd8lxF9/dn8P2nIGF1XGMVMIyS7hlAZ0wzMsktCuUfQKD8bbwn/0wpJyjqTlsuZQOtvXr+Alhzn0VAkUh12D69h/GWO7rUxrzdH0PM7kFZNbVMbK/Wks3pnEoAh/Zkcdw2fDq5CfATH3GTMENVYXzJXsXWi04AMiIHIiBPWGVp2NdeWlxpDCyj8Vy/zDa9/fX4nWmoKScs4VlpKVX0Kovzt+HrXoSinKga/uNoY9jvwHDHqozrEI0Zw0eXI3v+ktwDsYQyHnaq1fr2rbNh176if/u5i2Pm60D/AgMtS3+lEQVUjNLuStn/bjse9z/ui0EG9VSErYGIoDulPuE4bJtz34d8DZqzWuzo64OTvi6uyAl4tTo9RXScoq4NudySzZlUz8mfzzy5WC5we7c3/m2zgkbICQGBj9ltEVY20n1sEPf4Szx2q3X6vO0HWkMba+Hom+1vLPwBe/M27auu1DiLqz6d5bCCuzSnKvjYYeLXPybD5LN+2j3a63ucm0ET+Vf9H6fO1Kkg4kSQeSqAPZqCPZ7ToQH09Xyk2a/OJyikrLcXJUuDk54uHqSPsADyJaexLi545bxReDuQ/ZzdkBVyfj0dFBsT0+k+/2pJwvsTuoYwDj+obQoZUHvk7ltE9YhPfG10E5GKNc+k8DBxsrrV+ca9zhmpVgdA85OF34cax47mx8htP74MgKo+WsHOCGF2DQI43frXQuET6/HbITjdFE3UY27vsJYWNaXHKvUFxWzomMfErys1DZiahzp3DKTcQlNwm3vCTcC5LxKkjEpbyAsy4hrPO5lQy3CBxc3FAuHmQ6BnIWP3KKyjmZWUDCmXwKS8tr9N7d23ozNiqYW6OCCTOlwIGlRqs4cTuUFxs3Go2dCX5hDf65rSb3NHz/FBz+EUIHwKh/QEg9LgjnngZnD2O456VS98L8CVBaCHd/BR2urvv7CNFMtdjkXiPlpXBwOWybDYlbL1/v5G50OXQZge4+hhz/PhSVmyguNVFUVn7xY2k5xWUmugV50tkxzWjNxi2GlF3Gsdr2gYjrjOqEV7qLtLnSGvYtgp/+bAxZ7XITDH3amJC6vBTKS8w/ZWAqvdBvX1564XXqHji6CjIOAgoCuxldV8F9jesARdmw+AEj6U9eBEEyAYdomSS519TZ40YfbnkxlBQYf+5nJRgVFRM2gS4HVx9w8TKGCzqZf5zdLzx3dDbqh2clGMds1xf6TDDGXPu0s+ana1rFucbon83vGbXUa8PB2WiJdx5ujIJJjoWkWOPLokKbXjB5IfhWU1dGCDsnyb0hFGQaLfHknUalxdIiKDP/lBYZy8qKjW6C1l2gywjoPAL8O1g7cusqzoXDK4wvRkdnI3E7upj77Z0rLav02if48nsVtDZKMKTFGY+97zCGjQrRgklyF0IIO1RdcrexIRpCCCEagiR3IYSwQ5LchRDCDklyF0IIOyTJXQgh7JAkdyGEsEOS3IUQwg7ZxDh3pVQGcPKKGza91sAZawdRD7Yev63HV5mtx2rr8dWErX8GW4yvg9Y60NIKm0jutkopFVvVDQLNga3Hb+vxVWbrsdp6fDVh65/B1uO7lHTLCCGEHZLkLoQQdkiSe/VmWzuAerL1+G09vspsPVZbj68mbP0z2Hp8F5E+dyGEsEPSchdCCDskyR1Qyh6nRLItco4bjpzLxmUv57fFJndleEopFaqbad+UUsrR/GiTv4zN6RzLuWx8tnyO7eH8XqpFJnel1D3AWqAfkGOLv2zVUUrdq5TaBTxh7Viq0lzOsZzLxmfr57i5n9+qtLgLqkqpIcAGYKDWOvaSdcrWv7WVUt2Bz4CfgT7A01rrE0opB621ybrRGZrLOZZz2fhs/Rw39/NbnRbRcq/4cxBAa70J2Ab0MK97Vik1VinlZav/kEqp8xOKaq0PAfcA/wEOAI+Zl1v1P0pzOcdyLhufrZ/j5n5+a8ruk7tS6hXgJaVU5foLDwHzlFK7AT/gceBf5laGTVFKPQvsUkr9Qyl1r3nxYa11JvAt0Ekpda15W6v8ezaXcyznsvHZ+jlu7ue3VrTWdvkDuALPYRQk+xa46ZL1jwDR5ueBwFLgZmvHfUmMNwDrgQhgGJAKRFZa7wU8CcyvtMxRzrGcSznH9nd+a/tjzy33UuB7oCewFRimlIqoWKm1/kBrvcP8PAPIBAKsEWg1nIFdWut4rfVaYCbwRqX1+cAiIE8p9apS6p9AeBPG15zOsZzLxmfL59geOqlYiQAACiBJREFUzm+t2G1y10af3hGtdT7wNRAKDFRKucKF4VhKqQCl1L+BSOA3a8VbBQ+glVLKDUBr/SbQTik1wfxaA0UYF6oeBjK01sebKrhmdo7lXDY+mz3HdnJ+a8UukntVQ5e01sXmxwRgI3Ad0N28TJu/ub/GaHFcp7U+1iQBX6LyBajK/ZBa62+BTsCYSpv/E3i60us3gP1Ae631v6wQo02dY6VUQKXntnouq4rRps5lVZRSPSwtt5VzXE18zeL8Nhhr9wvV5wcYB8wD+l6yXAEO5ueO5kcf4D3gbmAqMNa8vJUV4x+FMb72M+CvlZY7/n97Zx9zZVkG8N/F+8qXkGiTtJkVLgZvGFAtUlPCjymwEoqa1TCicFgJbmkwrWXE1vyj5mYqqzYYMyG0L0es/rClYWtuWEapmwhqJFoR4AeJAVd/XPd539Px9fh+nPM89/Wc67c94/k8+73383Cd89wf1w2MSutXYPWY70jbZwK3AePT9ugSHbMpY+CyVE4bge/U7R+RUVk2c8ymLN/gb7gV2FMrwwzL+PX8XJRvS8uibIEh3Lxa3/w5wJ+BHdgr3sn1x9P6JGBC3fYK4ACwC5hXln8KjMux1755wCysPnBpw7mT0vlrgB8CXwJ+BazLzLGUMq7zvAqrR708BZPfAnMzK8uBOmb3vDZs/wh4GPhCLaCXWcZD8MuqfNt678oWGOqNxBpiTsda6Ddgr1G1YyOA1Vhr/dz0wE0BdgM3ZOI/D3hX3fa12AAPUjBYDfwTOB84CTgPe0u5PjPHUsq4wbMH6E7rE4EtKYDWfqXdmEFZDsQx5+e15nkNsAx7m5tWd3wVNgVdYWU8BL/ncirfdi/dOEFEvgxcJCIPAJvU6s0A9onIpcBsEdmlqn8HTgMOAT2qeiBd/xRwtlqDSuHU+f8O2Kiq20SkS0S6VfUoNoji8XT6RMx/cs0feFBE/qCqxzJzLLyMG56Fzar6aNo/E6sC6Mb+M/8HuI5yy3Iwjjk+r/cDW1T1WREZiVUtfRb7YXWFiDyEdS98AfshUEgZD9Fvai7lWwhlf7sM8Bt6IVY9MAdYD3wPmF53fDpwJ7Cwn2u7M/WfUe+HvX2c28+1XTS8emboWFgZN3sWsNfuM9P6OCxgzsykLAfqmOvz+t507Jvp309hAfMx6uqpiyjjYfqVXr5FLV56y8wC7lDrO3sT1mDSm4RIVR/BbvbZInJhGiVXyw1xtATfRvrzXwGgqkdTd6y3ATtE5AwRWQa9/sc0PZUZOxZZxv15Xps8d6vqM2n9Jaznwyl1nmWW5UAdc31ev5iOzU9vdquAX2DtCC9DoWU8HL8cyrcQsgrujV0a67Z3Y63aqOrTwC+BE0Xko3Wnb8IaUX4MvLn9tq9lCP6Xp+NTMOeVwL20cfCEB8cheI5teBYQka8B78a63dGOgOPBsRmD9J8gIudgA5N+r6ozVHUxVgU6NZ3bUv/c/XInq+CO9S/tpe5m3AMcrgs0+7DeBj1ijMNu6k5suPP1DdcXxWD9p6YHdhL2AL4TmK+qNzdc32mOQ/HsARCRuSKyHZgMLFLV59rk58WxGYPx/w1wAZY6YFXdZQtV9Y8d6pc1WQR3ETlHRO7GkvX0SF9S/1qD7wEsH8TV6dXqEFZfOTrd8FeAlao6X1X3OfIfk/x3AR9S1avb5e/Bcbie6fhjwHJVvTLHsizKsQ3+J2L/346LNbKPAFDVVzrNzwulB3cRmYg1iGwD9mOv/UvB6nrTaWOwfND7gO+LyFuxxPr/rZ2nqv8oWB1omf9OVW3bUGcPji3wfDWd95Sq/qWTHZsxTP+j6bxj2qaUvbn7uUIH2QLb6gW4BOvaCPbteyk2WGZK2rcWu5EzsXretdgr7u0UmLXPs78HRy+eHhw9++fu52kp4+FaANyA1duCpdd8AjgrbZ8CfAO4GUtEdFftWN1njC2twBz4e3D04unB0bN/7n6el8KqZUTkVBH5OZZE6N/AehFZpJZe8yfYyDKAg8B96aaOVtVPq+qT8v8Jlg4X5V3Dg78HRy+eHhybkbt/7n5VoMg697OAB1X1AlVdB3yFvmxxm4ApInKxWl3ZfuAtwBGwzHlafh2aB38Pjl48PTg2I3f/3P3c09b0A2Kzij8DPIQl+NqT9ndh8yn+NZ26E9gM3CIiC4CLsPwPJ0B58y168Pfg6MXTg2MzcvfP3a9qtDy4i4hgAwfuAo4DT2KJfFaq6vMi0qWqx8RyLp8EvTdrQ2opX40NmFmmqgdb7VcFfw+OXjw9OHr2z92v0rSyAp++zGyTgTvTejeWN/mnDedsBD6Z1k+r+4yRrXSqmr8HRy+eHhw9++fuV/WlJb/c0+CCNUCXiGzDEuEfg968JCuAZ0Vktqreny57CdgjNhv5x0TkMlXdq6qvtsKpav4eHL14enD07J+7X6cw7AZVEZmN1Z+djI1i/BY28GWOiHwAeocNr8GS/NTq2JZiw4jfBMxR1b3DdRkKHvw9OHrx9ODYjNz9c/frKIb70x9Lzr+4bvt2bGakJcCOtG8EVu+2BXg71lJ+CylNZ5mLB38Pjl48PTh69s/dr5OWVtzMscAo+urOPgN8O63/Cbgmrb8fm7Sg9D/am78HRy+eHhw9++fu10nLsKtlVPWwqh7RvhlXLsGmNAP4HJZVcCvWd3UHvDaVZ5l48Pfg6MXTg2MzcvfP3a+TaFlXyFRvpthgg3vT7hexocXTgD1qU+Ch6as7Jzz4e3AEH54eHJuRu3/ufp1AK0eoHscGGfwLeE/6dv46cFxVt9duZMZ48PfgCD48PTg2I3f/3P2qTyvreIAPYjd1O/D5suucqujvwdGLpwdHz/65+1V9kXQTWoKInAEsBr6rqkda9sEF4cHfgyP48PTg2Izc/XP3qzotDe5BEARBHpQ+E1MQBEHQeiK4B0EQVJAI7kEQBBUkgnsQBEEFieAeBEFQQSK4BwEgIjeJyHVNji8QkZ4inYJgOERwD4KBsQCI4B64Ifq5Bx2LiNwIXAn8DUtutQM4BFwFjMTykS8GZgBb07FDwMfTR9wGnAocxqaBe7xI/yBoRgT3oCMRkfcBG4BZWAK9h4F1wHpV3Z/OWQs8r6q3isgGYKuq3pOO3QcsV9UnRGQWltb2wuL/kiDon5ZPkB0ETjgf+JmqHgYQkVrmwmkpqE8AxgG/brxQRMYB5wJ312WrHdV24yAYBBHcg06mv9fWDcACVX1ERJYAH+7nnBHAQVWd0T61IBge0aAadCoPAAtFZIyIjAc+kvaPB/aJyAnYLEI1XkzHUNUXsMmcPwE22YSITC9OPQjemKhzDzqWugbVp4G9wKPAy8BX076dwHhVXSIi5wE/AI4Ai7BUtncAp2N5yzer6prC/4ggeB0iuAdBEFSQqJYJgiCoIBHcgyAIKkgE9yAIggoSwT0IgqCCRHAPgiCoIBHcgyAIKkgE9yAIggoSwT0IgqCC/A8qJo43zLEDAgAAAABJRU5ErkJggg==\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "# compare each of the variables since beginning of the year\n", "since = '2020-01-01'\n", "predicted = transform(res, since)\n", "actual = transform(frame_t, since)\n", "\n", "for asset in frame_t.columns:\n", " compare_plot(asset, actual, predicted)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### What's New\n", "\n", "* New `IRCMSSpreadOption` instrument (`from gs_quant.instruments import IRCMSSpreadOption`)\n", "* New tunable epidemiological models (SIR, SEIR, age-stratified SEIR) (`from gs_quant.models.epidemiology import SIR, SEIR, SEIRCMAgeStratified`) \n", "* New timeseries functions (percentile, conditional funcs, exponential_moving_average) (`from gs_quant.timeseries import *`)\n", "* Added discount factor information to the `Cashflows` measure\n", "* `to_frame()` function for portfolios now supports nested portfolios\n", "* `gs-quant` now compatible with `pandas` versions > 1.0.0" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/made_with_gs_quant/7-Predicting Performance and Live Risk.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Live Risk and Performance Predict\n", "\n", "### Summary \n", "\n", "While we are working on delivering live risk in gs-quant, computing risk real-time for large portfolios can be computationaly expensive. In this notebook, I team up with FICC Derivatives' David Vanden Bon to introduce a simple and lightweight framework for approximating it. This means you can compute your end of day risk and connect a live market feed to this code to estimate how performance, delta and gamma risk are changing for your book in real time. \n", "\n", "You can also use this framework to predict performance - that is, estimate impact of a hypothetical market move, which can be a useful risk management tool.\n", "\n", "More specifically, we do this by calculating a delta ladder for our portfolio via applying a number of spot shocks. We use the delta values to fit a polynomial for each instrument in the curve and then use this polynomial to appriximate delta, gamma and pnl for any given market move in these instruments.\n", "\n", "Let's get started!\n", "\n", "Don't forget to check out the [What's New](#What's-New) section for the latest gs-quant additions.\n", "\n", "The content of this notebook is split into:\n", "* [1 - Let's get started with gs quant](#1---Let's-get-started-with-gs-quant)\n", "* [2 - Create or load portfolio](#2---Create-or-load-portfolio)\n", "* [3 - Calculate delta ladder](#3---Calculate-delta-ladder)\n", "* [4 - Fit polynomial](#4---Fit-polynomial)\n", "* [5 - Estimate performance and risk for any market move](#5---Estimate-performance-and-risk-for-any-market-move)\n", "* [What's New](#What's-New)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1 - Let's get started with gs quant\n", "Start every session with authenticating with your unique client id and secret. If you don't have a registered app, create one [here](https://marquee.gs.com/s/developer/myapps/register). `run_analytics` scope is required for the functionality covered in this example. Below produced using gs-quant version 0.8.130." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2 - Create or load portfolio\n", "\n", "The goal for this section is to load your own book. If you are an internal user please reach out for ways to connect your book.\n", "\n", "If you are an external user, we have additional natural language processing tools to help map your book coming soon! \n", "\n", "For this note, I'll create a sample portfolio of a combination of 3m, 1y, 2y and 3y expiry payer and receiver swaptions on 5y, 10y, 15y and 20y tails struck 10, 20 and 30bps OTMF and resolve it to fix all the relative parameters." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import IRSwaption\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.common import PayReceive, Currency\n", "\n", "ccy = Currency.USD\n", "payers = [IRSwaption(PayReceive.Pay, termination_date='{}y'.format(t), notional_currency=ccy, \n", " expiration_date='{}m'.format(e), strike='a+{}'.format(s)) \n", " for t in [5, 10, 15, 20] for e in [3, 12, 24, 36] for s in [10, 20, 30]]\n", "\n", "receivers = [IRSwaption(PayReceive.Receive, termination_date='{}y'.format(t), notional_currency=ccy, \n", " expiration_date='{}m'.format(e), strike='a-{}'.format(s)) \n", " for t in [5, 10, 15, 20] for e in [3, 12, 24, 36] for s in [10, 20, 30]]\n", "\n", "portfolio = Portfolio(payers + receivers)\n", "portfolio.resolve()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3 - Calculate Delta Ladder\n", "\n", "Now let's take our portfolio and calculate a delta ladder by shocking spot with varying shifts (where 0 our portfolio delta without any shocks applied). Each row in the resulting dataframe corresponds to a different instrument. We will use this delta information to fit a polynomial for each of these instruments in the next step." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
mkt_typemkt_assetmkt_classmkt_point-50-40-30-20-10-5-20251020304050
0IRUSDCASHO/N-96.652300-87.508851-80.140622-74.702595-71.280237-70.330161-70.001395-69.881709-69.840876-69.925542-70.448960-72.866759-76.972091-82.564403-89.423074
1IRUSDCASHCASH STUB19236.46265815938.26419812100.0098237851.2264803375.4121261107.540696-251.889772-1155.410051-2055.450143-3397.138702-5601.994693-9838.668869-13744.821189-17227.777252-20248.264302
2IRUSDFRAJUN2058295.74476248164.75397436542.37939623781.78665510376.9656343584.071217-489.994901-3198.958126-5898.759200-9925.239905-16549.585996-29314.223366-41151.872378-51799.726695-61135.816203
\n", "
" ], "text/plain": [ " mkt_type mkt_asset mkt_class mkt_point -50 -40 \\\n", "0 IR USD CASH O/N -96.652300 -87.508851 \n", "1 IR USD CASH CASH STUB 19236.462658 15938.264198 \n", "2 IR USD FRA JUN20 58295.744762 48164.753974 \n", "\n", " -30 -20 -10 -5 -2 \\\n", "0 -80.140622 -74.702595 -71.280237 -70.330161 -70.001395 \n", "1 12100.009823 7851.226480 3375.412126 1107.540696 -251.889772 \n", "2 36542.379396 23781.786655 10376.965634 3584.071217 -489.994901 \n", "\n", " 0 2 5 10 20 \\\n", "0 -69.881709 -69.840876 -69.925542 -70.448960 -72.866759 \n", "1 -1155.410051 -2055.450143 -3397.138702 -5601.994693 -9838.668869 \n", "2 -3198.958126 -5898.759200 -9925.239905 -16549.585996 -29314.223366 \n", "\n", " 30 40 50 \n", "0 -76.972091 -82.564403 -89.423074 \n", "1 -13744.821189 -17227.777252 -20248.264302 \n", "2 -41151.872378 -51799.726695 -61135.816203 " ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from gs_quant.markets import PricingContext\n", "from gs_quant.risk import MarketDataShockBasedScenario, MarketDataPattern, MarketDataShock, MarketDataShockType, Price, IRDeltaLocalCcy\n", "\n", "# calculate the base delta case (current delta of portfolio - 0bp shift in the rates)\n", "delta = portfolio.calc(IRDeltaLocalCcy).aggregate().rename(columns = {'value': 0})\n", "\n", "# calculate delta ladder (local delta for a number of shifts)\n", "shifts = [-50, -40, -30, -20, -10, -5, -2, 0, 2, 5, 10, 20, 30, 40, 50]\n", "results = {}\n", "with PricingContext(is_async=True):\n", " for shift in shifts:\n", " with MarketDataShockBasedScenario({\n", " MarketDataPattern('IR', ccy): MarketDataShock(MarketDataShockType.Absolute, shift / 10000),\n", " MarketDataPattern('IR Reset', '{}-3m'.format(ccy)): MarketDataShock(MarketDataShockType.Absolute, shift / 10000),\n", " MarketDataPattern('IR Reset', '{}-6m'.format(ccy)): MarketDataShock(MarketDataShockType.Absolute, shift / 10000)\n", " }):\n", " results[shift] = portfolio.calc(IRDeltaLocalCcy)\n", "\n", "# aggregate delta and insert into dataframe\n", "for shift in shifts:\n", " delta[shift] = results[shift].aggregate().value\n", "\n", "# insert base delta into sorted numerical column order \n", "cols = list(delta.columns)\n", "column_list = cols[:4] + sorted(cols[4:])\n", "delta = delta[column_list]\n", "delta.head(3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4 - Fit Polynomial\n", "\n", "With delta accross various shocks for each instrument in hand (the frame above), we can calculate a polynomial which fits the evolution of delta across the shifts for each instrument.\n", "\n", "`coeffs` will give us the polynomial (order number of shifts-1) coefficients for each instrument, thus `coeffs` is a matrix of size `len(delta)` x `len(shifts)` where each matrix row is a polynomial corresponding to the coefficients for each instrument. In this example it will be 30x15.\n", "\n", "We'll use this polynomial in the next step to extrapolate delta, gamma and performance to any market move.\n", "\n", "Note polynomial fitting is as much as art it is a science and additional techniques can be used here to avoid overfitting which can produce kinks and other undesirable effects." ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "scrolled": true }, "outputs": [], "source": [ "import numpy as np\n", "\n", "coeffs = [np.polynomial.polynomial.polyfit(shifts, row.values, len(shifts) - 1) for _, row in delta[shifts].iterrows()]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now visualize what the polynomial looks for a particular point in the curve - '5Y' in the example below. The points in the graph are the shocks we fit the polynomial on for the '5Y' instrument.\n", "\n", "This means that for '5Y' instrument (and the rest of instruments in the table above) we can plug in any market move and get the expected delta (plug in x=spot and get y=delta, which we'll do in the next step)." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Text(0, 0.5, 'Delta')" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "import matplotlib.pyplot as plt\n", "\n", "point = '5Y'\n", "\n", "x = shifts\n", "y = delta[delta['mkt_point'] == point][shifts].values[0]\n", "coefficients = np.polyfit(x, y, len(x)-1)\n", "poly = np.poly1d(coefficients)\n", "\n", "new_x = np.linspace(x[0], x[-1], 100)\n", "new_y = poly(new_x)\n", "\n", "plt.plot(x, y, \"o\", new_x, new_y)\n", "plt.xlim([x[0]-1, x[-1] + 1])\n", "plt.title('Delta curve for {}'.format(point))\n", "plt.xlabel('Spot Move (bps)')\n", "plt.ylabel('Delta')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 5 - Predicting Performance and Risk\n", "\n", "We can already get expected delta for any market move using our polynomial above - with a big of simple math, we can also get gamma (first derivative) and pnl approximation. Let's define a few functions to help us to that." ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "def delta_f(inst_coeff, market_move):\n", " return sum([c*market_move**(i) for i, c in enumerate(inst_coeff)])\n", "\n", "def gamma_f(inst_coeff, market_move):\n", " return sum([(i + 1)*c*market_move**(i) for i, c in enumerate(inst_coeff[1:])])\n", " \n", "def pnl_f(inst_coeff, market_move):\n", " return sum([(c*market_move**(i + 1))/(i + 1) for i, c in enumerate(inst_coeff)])\n", " \n", "def get_derived_values(inst_coeff, market_move):\n", " b_delta = inst_coeff[0]\n", " b_gamma = gamma_f(inst_coeff, 0)\n", " \n", " new_delta = delta_f(inst_coeff, market_move)\n", " new_gamma = gamma_f(inst_coeff, market_move)\n", "\n", " delta_pnl = b_delta * market_move\n", " gamma_pnl = pnl_f(inst_coeff, market_move) - delta_pnl\n", " \n", " return {'current_delta': b_delta, 'current_gamma': b_gamma, 'new_delta': new_delta, 'new_gamma': new_gamma,\n", " 'delta_pnl': delta_pnl, 'gamma_pnl': gamma_pnl}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now imagine you have a live market data feed - you can plug that in for `market_moves` and get live risk on your book. You can also plug in any market move to predict pnl and risk for your book. \n", "\n", "Here I apply a flat 22bp move to all instruments but you can specify different moves for each instrument using the `market_moves` dictionary. As an example, I'll update O/N to 9bps and 10Y swap to 25." ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "# plug in move (bps) - flat accross all instruments or construct custom dictionary\n", "move = 22\n", "market_moves = dict(zip(delta.mkt_point.values, len(delta.mkt_point)*[move]))\n", "market_moves['O/N'] = 9\n", "market_moves['10Y'] = 25" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's now calculate estimated delta, gamma and pnl given these market moves." ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
current_deltacurrent_gammanew_deltanew_gammadelta_pnlgamma_pnl
mkt_point
O/N-69.8817090.040065-70.306373-0.133143-628.935377-0.742775
CASH STUB-1155.410051-450.968775-10675.092828-418.613276-25419.021126-106376.459182
JUN20-3198.958126-1352.429189-31787.348638-1230.087633-70377.078769-319716.680302
\n", "
" ], "text/plain": [ " current_delta current_gamma new_delta new_gamma \\\n", "mkt_point \n", "O/N -69.881709 0.040065 -70.306373 -0.133143 \n", "CASH STUB -1155.410051 -450.968775 -10675.092828 -418.613276 \n", "JUN20 -3198.958126 -1352.429189 -31787.348638 -1230.087633 \n", "\n", " delta_pnl gamma_pnl \n", "mkt_point \n", "O/N -628.935377 -0.742775 \n", "CASH STUB -25419.021126 -106376.459182 \n", "JUN20 -70377.078769 -319716.680302 " ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import pandas as pd \n", "\n", "# calculate values for all the instruments\n", "instruments = delta['mkt_point']\n", "results = pd.DataFrame([get_derived_values(coeffs[delta[delta['mkt_point'] == instrument].index[0]], \n", " market_moves[instrument]) for instrument in list(instruments)])\n", "results.index = instruments\n", "results.head(3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We now have delta, gamma and delta given a specified move in each instrument. Current delta and gamma are values for the current portfolio and new delta and gamma are values after the market move is applied.\n", "\n", "Now to better understand the results, let's examine them visually! Note all of these values are approximations and may differ from actual performance and risk for the book." ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAngAAAF9CAYAAACXshUUAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8li6FKAAAgAElEQVR4nOzde7xVdZ3/8dcbUERBbqKhoOBopUaYHJEsCS8BjZlaOmEX8ZKMl9KaaVKrSfIyqdmPrlo6Ol7SEFETbYxIxck0uQiKiA6oqIykKIigYqCf3x/re2id7T6Hvc/ZnMvi/Xw81uOs/V3f72d999r7nP0537W+aysiMDMzM7Pi6NTWHTAzMzOz2nKCZ2ZmZlYwTvDMzMzMCsYJnpmZmVnBOMEzMzMzKxgneGZmZmYF4wTPzNqcpKWSDtuM8UPSHpsrfmuT9EtJ/97W/dhcJF0r6cK27odZR+YEz6wVSRon6WFJb0h6Oa2fLklt3bf2Kn3Y/03SmrQ8LukHknq2IF6zkwdJEyWtl7RW0muSHpT00QrbzpT0lebuu15EnBoRF7Q0zuYiqaukqyU9l16zeZI+lds+QtIMSSslrZB0i6T+bdlns6JxgmfWSiT9K/AT4IfA+4CdgFOBjwFbt2HXOoJLI6IH0A84ERgB/FnSdm3Un5sjonvqzwPAbU7SG+gCvAB8AugJ/DswRdKgtL03cCUwCNgNWAP8V2t30qzInOCZtYI02nQ+cHpETI2INZGZFxFfjIi3U73D02jH65JekDQxF2NQOtV4Ytq2StKpkvaX9FgaTfp5rv4Jkv4saVLa9oykA1P5C2kEcXyufqP7LvN8eku6K42+rErrA3LbZ0q6IO1/jaQ/SNoht/3LaXTnVUnfqfQ4RsS6iJgNfAboS5bs1cc8SdKi1J/pknYr0+8JwBeBb6URuDtT+TmSnk59fULS0RX2Zz1wHVnC3jcd2wckXZb68Wx+5Koakr4labmkFyV9JX+aOT8KmZ7zp3Ptukh6RdJ+6fGINMr4mqRHJY3K1W3ydSrpT5OveclxeSMiJkbE0oh4NyLuAp4FhqXtd0fELRHxekS8Cfyc7B+dvB3SKN8aSffnX890LM5M7+lXJP1QUtnPM2UjrrdI+nWKtUDS+yWdm34HXpA0Old/Z0nTlI0uLpF0Sq78LUl9cnU/kva/VXq8yfegWWtxgmfWOj4KdAXu2ES9N4DjgV7A4cBpko4qqXMAsCfweeDHwHeAw4B9gH+S9ImSuo+RJUM3AZOB/YE9gC8BP5fUvYp91+tENuKyG7Ar8BbZh3TeF8gSsB3JRii/CSBpb+AK4MvAzqlvZROFxkTEGmAGcFCKeRTwbeCzZKNqfwJ+U6bdlcCNZCOC3SPiiLTp6RSrJ/B94Neq4JShpK7ACcCyiHglFR8APAXsAFwKXC1VN7onaSzwL2Sv6x5kI2GN+Q1wXO7xGOCViHhE0i7A74ALgT5kr8Gtkvrl6pd9ncqo5DVv7PnsBLwfWNhIlZFltn0RuIDsOM4ne93yjgbqgP2AI4GTmujCEcANZCOH84Dp6fnsQvaP169ydX8DLCN7bx4D/IekQyPiReAh4HO5ul8ApkbE+krfg2atJiK8ePGymReyZOqvJWUPAq+RfVCObKTdj4FJaX0QEMAuue2vAp/PPb4V+HpaPwFYnNs2JLXfqaT9vpvadwXPb19gVe7xTOC7ucenA79P698DJue2bQf8DTiskdjXAheWKb8YmJHW7wZOzm3rBLwJ7JYeB7BHU/FKYs8Hjmxk28TU39eAl4F7gWG5Y74kV3fbtO/35Y7LVyo4ntcAP8g93qOx55C2rQG2TY9vBL6X1s8GbiiJPR0Yv6nXqdrXvIl6WwF/BH7VyPYPAyuBg0pe8/x7pDvwDjAw93qOLen3PU28XjNyj48A1gKd0+MeKV4vYGDaT49c/R8A16b1rwD3pnWRnYYeWcl70IuX1l48gmfWOl4lO+XUpb4gIg6MiF5pWycASQdIui+dBltNdo1e6Smzl3Lrb5V53L2JukRE2foV7ptUd1tJv0qnWV8H/gfoJalzrtpfc+tv5vq1M9kHY/1xeCMdg2rtQpYYQDaq9JN0GvK1VK5UZ5MkHS9pfq79h2jkuSdTIqJXROwYEYdExNzcto3PO7LTj9DwNalEg2NUst5ARCwBFgFHSNqW7PT1TWnzbsCx9c8rPbePA/nRycZepwYqfM1L23QiGzn7G/DVMtv3IEuMzoqIP5Vszr9H1pK9pjuX2w48V7KtVOl7/pWIeCf3GLLnvTOwMrIR4nzs+vfRVOCjknYmG3UMspE6aOF70KzWnOCZtY6HgLfJTiU15SZgGtlIRU/gl2QfEq2hmn3/K/AB4ICI2J7sw44m6uctJxspyRpkSUnfajqaTisfxt8/XF8A/jklXfVLt4h4sEzzKIm1G3AVWQLSNyXdj1f4XDaX5TQ8bT2wsYpJ/WnaI4EnUtIH2XG5oeS4bBcRFzejT1W95um09NVkk4k+F9n1ivntu5GN7F0QETeUCZF/j3QnO8X8YrntZKeM89ua60Wgj6QeJbH/DyAiXgP+APwT2enZ30RE/fupmveg2WbnBM+sFaQPhu8Dl0s6RlJ3SZ0k7Ut2irJeD7IRhHWShpN9iLSWavbdg2zk47V00fl5VexnKvBpSR+XtDXZNVAV/S1SdvuNYcBvgVX8feblL4FzJe2T6vWUdGwjYV4Cds893o4s6VuR2p5INoK3uXSRtE1u2apMnSnAiZL2Sgnw9zYRczIwGjiNv4/eAfyabGRvjKTOaX+jGpscsQnVvuZXAHsBR0TEW/kN6drAe4FfRMQvG2n/j7n3yAXAwxGRH7X7N2UTPwYCZwE3V/+UGkrxHwR+kI7Vh4GTaXj9301k16p+jobHupr3oNlm5wTPrJVExKVkF85/i+zarZfILu4+m+xDBbJric6XtIbsQ31KK3axmn3/GOgGvAL8Bfh9pTuJiIXAGWQfjsvJErVlm2j2rdSvlcD1wFzgwHR6l4i4HbgEmJxOHz4ONDZ79Wpg73Qq7bcR8QTwI7JR1pfIrlX8c6XPpxmuIEuU6pf33B4kIu4GfgrcByxJfYNsFPg9ImJ5qnMguUQnJSxHkl38v4JslOnfaN7f/opf8zQ6989k1+n9VdmM5bWSvpiqfIUsyT4vt21tSZibyJLIlWSzb79Ysv0OsvfBfLKJJFc34zmVcxzZ9a4vArcD50XEjNz2aWSTnF6KiEfrC6t8D5ptdvr76LKZmbVHkvYiSxi6RsSGtu5PW5MUwJ65U9FmVsIjeGZm7ZCkoyVtLak32cjQnU7uzKxSTvDMzNqnfyY7rfo02a07Tmvb7phZR+JTtGZmZmYF4xE8MzMzs4JxgmdmZmZWMF02XWXLscMOO8SgQYPauhtmZmZmmzR37txXIqJfuW1O8HIGDRrEnDlz2robZmZmZpsk6bnGtvkUrZmZmVnBOMEzMzMzKxgneGZmZmYF42vwzMzMrGLr169n2bJlrFu3rq27ssXYZpttGDBgAFtttVXFbZzgmZmZWcWWLVtGjx49GDRoEJLaujuFFxG8+uqrLFu2jMGDB1fcrsWnaCV9QNL83PK6pK9L6iNphqTF6WfvXJtzJS2R9JSkMbnyYZIWpG0/VXrnSOoq6eZU/rCkQbk249M+FksanysfnOouTm23bulzNTMz29KtW7eOvn37OrlrJZLo27dv1SOmLU7wIuKpiNg3IvYFhgFvArcD5wD3RMSewD3pMZL2BsYB+wBjgcsldU7hrgAmAHumZWwqPxlYFRF7AJPIvngbSX2A84ADgOHAeblE8hJgUtr/qhTDzMzMWsjJXetqzvGu9SSLQ4GnI+I54EjgulR+HXBUWj8SmBwRb0fEs8ASYLik/sD2EfFQZF+Qe31Jm/pYU4FD0+jeGGBGRKyMiFXADGBs2nZIqlu6fzMzM7NCq/U1eOOA36T1nSJiOUBELJe0YyrfBfhLrs2yVLY+rZeW17d5IcXaIGk10DdfXtKmL/BaRGwoE8vMzMxqZNA5v6tpvKUXH15V/YkTJ9K9e3e++c1vbnL7tddey+jRo9l5551r0dVN6t69O2vXrm2VfZWq2QheusbtM8Atm6papiyaKG9Om6ZiNeyMNEHSHElzVqxYUa6KmZmZFcC1117Liy++2NbdaBW1HMH7FPBIRLyUHr8kqX8avesPvJzKlwEDc+0GAC+m8gFlyvNtlknqAvQEVqbyUSVtZgKvAL0kdUmjePlYDUTElcCVAHV1dWWTQDMzs3ZvYs8K6qze/P1oBRdddBHXX389AwcOpF+/fgwbNoynn36aM844gxUrVrDtttty1VVX8cEPfnBjm6lTpzJnzhy++MUv0q1bNx566CF++MMfcuedd/LWW29x4IEH8qtf/arR691GjRrFvvvuy6xZs3j99de55pprGD58OBMnTuT555/nmWee4fnnn+frX/86Z555ZmsdikbV8hq84/j76VmAaUD9rNbxwB258nFpZuxgsskUs9Lp3DWSRqRr6I4vaVMf6xjg3nSd3nRgtKTeaXLFaGB62nZfqlu6fzMzM+ug5s6dy+TJk5k3bx633XYbs2fPBmDChAn87Gc/Y+7cuVx22WWcfvrpDdodc8wx1NXVceONNzJ//ny6devGV7/6VWbPns3jjz/OW2+9xV133dXkvt944w0efPBBLr/8ck466aSN5U8++STTp09n1qxZfP/732f9+vW1f+JVqskInqRtgU8C/5wrvhiYIulk4HngWICIWChpCvAEsAE4IyLeSW1OA64FugF3pwXgauAGSUvIRu7GpVgrJV0AzE71zo+IlWn9bGCypAuBeSmGmZmZdWB/+tOfOProo9l2220B+MxnPsO6det48MEHOfbYYzfWe/vttzcZ67777uPSSy/lzTffZOXKleyzzz4cccQRjdY/7rjjABg5ciSvv/46r732GgCHH344Xbt2pWvXruy444689NJLDBgwoNE4raEmCV5EvEk2sSFf9irZrNpy9S8CLipTPgf4UJnydaQEscy2a4BrypQ/Q3brFDMzMyuQ0tOo7777Lr169WL+/PkVx1i3bh2nn346c+bMYeDAgUycOHGT95or3W/9465du24s69y5Mxs2bKCt+btozczMrMMYOXIkt99+O2+99RZr1qzhzjvvZNttt2Xw4MHccks2zzMiePTRR9/TtkePHqxZswZgYzK3ww47sHbtWqZOnfqe+qVuvvlmAB544AF69uxJz54VXPfYRvxVZWZmZtZs1d7WpKX2228/Pv/5z7Pvvvuy2267cdBBBwFw4403ctppp3HhhReyfv16xo0bx9ChQxu0PeGEEzj11FM3TrI45ZRTGDJkCIMGDWL//fff5L579+7NgQceuHGSRXumbD6CQTaLds6cOW3dDTMzs+q10izaRYsWsddee7U4TkczatQoLrvsMurq6tpk/+WOu6S5EVG2Qz5Fa2ZmZlYwPkVrZmZmlpxxxhn8+c9/blB21llnMXPmzLbpUDM5wTMzMzNLfvGLX7R1F2rCp2jNzMzMCsYJnpmZmVnBOMEzMzMzKxgneGZmZmYF40kWZmZm1nyV3H+vqngtv1dfRzVx4kS6d+/ON7/5zRbH8giemZmZWcE4wTMzM7MO5YILLuCDH/wgn/zkJznuuOO47LLLuOqqq9h///0ZOnQon/vc53jzzTeB7OvJTjvtNA4++GB233137r//fk466ST22msvTjjhhI0xu3fvztlnn82wYcM47LDDmDVrFqNGjWL33Xdn2rRpACxdupSDDjqI/fbbj/32248HH3yw0T7OnDmTkSNHcvTRR7P33ntz6qmn8u67727c13e+8x2GDh3KiBEjeOmll2p+jJzgmZmZWYcxZ84cbr31VubNm8dtt91G/VeMfvazn2X27Nk8+uij7LXXXlx99dUb26xatYp7772XSZMmccQRR/CNb3yDhQsXsmDBAubPnw/A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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "results[['delta_pnl', 'gamma_pnl']].plot(kind='bar', stacked=True, title='Gamma and Delta PnL given a {}bp move'.format(move), figsize=(10, 5))" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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9tlNb3PhhRfy2GOPrfLrzpkzhz333HOV40iSpNVvjRiT1tnQyTe0Nd6cs97c1nidXXjhhey555684hWvWK2vI0mS6sOetB5y5plnsuuuu/LGN76Rhx56aGn5b3/7Ww477DDGjBnDAQccwIMPPrjMfldeeSXTp0/nXe96F6NGjeKZZ57h9NNPZ5999mHPPfdk0qRJZObLXu/xxx/nbW97GyNHjmTkyJH84he/AOCFF17gxBNPZPjw4Rx66KE888wzAJx//vnss88+jBw5kqOOOoqnn34agOOPP56PfOQj7L///uy0005ceeWVALz44oucdNJJDB8+nLe85S38wz/8w9J1M2bM4MADD2TMmDGMHz+eefPmtb9BJUnq50zSesCMGTO47LLLuOeee7j66quZNm3a0nWTJk3iq1/9KjNmzODss8/mpJNOWmbfo48+mrFjx3LppZdy7733st566/GhD32IadOmcf/99/PMM89w/fXXv+w1P/KRj3DggQcyc+ZM7r77boYPHw7A7NmzOfnkk5k1axabbropV111FQBvf/vbmTZtGjNnzmT33XdnypQpS2PNmzeP22+/neuvv57JkycDcPXVVzNnzhzuu+8+vvnNb3LHHXcA1eVOPvzhD3PllVcyY8YM3ve+93HKKae0t0ElSVoDrJGnO3vaz372M972trex/vrrA/DWt74VgMWLF/OLX/yCY445Zum2zz333Arj3XrrrXzpS1/i6aefZsGCBQwfPpzDDz98mW1uueUWLr74YgAGDhzIJptswsKFCxk2bBijRo0CYMyYMcyZMweA+++/n89+9rM8+eSTLF68mPHjxy+NdeSRRzJgwAD22GMPHn/8cQBuv/12jjnmGAYMGMA222zDwQcfDMBDDz3E/fffzyGHHAJUPXfbbrtty20mSdKaziSth3Q1O/HFF19k00035d577206zrPPPstJJ53E9OnT2X777TnttNNaujTFOuuss3R54MCBS093Hn/88Vx77bWMHDmSCy+8kNtuu63LfTpOrXZ1irWjfPjw4Ut71iRJzWtmzPTqHget+vB0Zw94/etfzzXXXMMzzzzDokWL+MEPfgDAxhtvzLBhw/je974HVAnOzJkzX7b/RhttxKJFiwCWJmRbbrklixcvXjoOrLNx48Zx7rnnAlVv1l//+tfl1nHRokVsu+22PP/881x66aUrPKbXve51XHXVVbz44os8/vjjS5O6XXfdlfnz5y9z+nPWrFkrjCdJkpZlktYDRo8ezbHHHsuoUaM46qijOOCAA5auu/TSS5kyZQojR45k+PDhfP/733/Z/scffzwf+MAHGDVqFOussw4nnngiI0aM4Mgjj2Sfffbp8jW//OUvc+uttzJixAjGjBmzwkTpjDPOYL/99uOQQw5ht912W+ExHXXUUQwZMoQ999yTf/qnf2K//fZjk002Ye211+bKK6/kU5/6FCNHjmTUqFFLJy1IkqTmRXenrfqqsWPH5vTp05cpe+CBB9h99917qUb91+LFi9lwww154okn2Hffffn5z3/ONttss9pez/dRUn/n6c41T0TMyMyxXa1rqictIjaNiCsj4sGIeCAiXhMRm0fE1IiYXX5u1rD9pyPi4Yh4KCLGN5SPiYj7yrqvRBmoFRHrRMTlpfzOiBjasM/E8hqzI2LiyjaC2u8tb3kLo0aN4oADDuBzn/vcak3QJEla0zQ7ceDLwA8z8+iIWBtYH/gMcHNmnhURk4HJwKciYg9gAjAceAXw44h4VWa+AJwLTAJ+CfwfcBhwI3ACsDAzd46ICcAXgWMjYnPgVGAskMCMiLguMxe25ei1ShonF0iSpPZaYU9aRGwMvB6YApCZf8/MJ4EjgIvKZhcBR5blI4DLMvO5zHwEeBjYNyK2BTbOzDuyOsd6cad9OmJdCYwrvWzjgamZuaAkZlOpEjtJkqR+rZnTnTsB84FvRcQ9EfHNiNgA2Doz5wGUn1uV7bcDHm3Yf24p264sdy5fZp/MXAI8BWyxnFiSJEn9WjNJ2lrAaODczNwb+BvVqc3uvPyCYNWpyu7KV3afl14wYlJETI+I6fPnz19O1SRJkvqGZpK0ucDczLyzPL+SKml7vJzCpPz8c8P22zfsPwR4rJQP6aJ8mX0iYi1gE2DBcmItIzPPy8yxmTl28ODBTRySJElSva0wScvMPwGPRsSupWgc8GvgOqBjtuVEoOMCX9cBE8qMzWHALsBd5ZToooh4dRlvdlynfTpiHQ3cUsat3QQcGhGbldmjh5Yy9ZDjjz++2wvmSpKk1afZ2Z0fBi4tMzt/B7yXKsG7IiJOAP4AHAOQmbMi4gqqRG4JcHKZ2QnwQeBCYD2qWZ03lvIpwCUR8TBVD9qEEmtBRJwBdNyR/PTMXLCSx/qS0zZZ5RDLxnuqvfEkSdIar6nrpGXmveV04l6ZeWRmLszMJzJzXGbuUn4uaNj+zMx8ZWbumpk3NpRPz8w9y7oPld4yMvPZzDwmM3fOzH0z83cN+1xQynfOzG+18+B70hlnnMFuu+3GIYccwjve8Q7OPvtsAM4//3z22WcfRo4cyVFHHcXTTz8NVD1YH/zgBzn44IPZaaed+MlPfsL73vc+dt99d44//vilcTfccEM+9alPMWbMGN74xjdy1113cdBBB7HTTjtx3XXXATBnzhwOOOAARo8ezejRo7u9A8DFF1/MXnvtxciRI3nPe96ztPynP/0p+++/PzvttNPSXrXFixczbtw4Ro8ezYgRI5beKWHOnDnsvvvunHjiiQwfPpxDDz106f1Bp02bxl577cVrXvMaPvnJT7LnnnsC1W2rPvnJT7LPPvuw11578b//+79tbHlJkvombwvVA6ZPn85VV13FPffcw9VXX03jHRHe/va3M23aNGbOnMnuu+/OlClTlq5buHAht9xyC+eccw6HH344H//4x5k1axb33Xff0puy/+1vf+Oggw5ixowZbLTRRnz2s59l6tSpXHPNNXz+858HYKuttmLq1KncfffdXH755XzkIx95WR1nzZrFmWeeyS233MLMmTP58pe/vHTdvHnzuP3227n++uuZPLmaM7LuuutyzTXXcPfdd3PrrbfyiU98YulN12fPns3JJ5/MrFmz2HTTTbnqqqsAeO9738s3vvEN7rjjDgYOHLg0/pQpU9hkk02YNm0a06ZN4/zzz+eRRx5pV/NLktQnNXu6U6vg9ttv54gjjmC99dYD4PDDD1+67v777+ezn/0sTz75JIsXL2b8+KU3aODwww8nIhgxYgRbb701I0aMAGD48OHMmTOHUaNGsfbaa3PYYdWl40aMGME666zDoEGDGDFiBHPmzAGqm5x/6EMf4t5772XgwIH85je/eVkdb7nlFo4++mi23HJLADbffPOl64488kgGDBjAHnvsweOPPw5UN4P/zGc+w09/+lMGDBjAH//4x6Xrhg0bxqhRowAYM2YMc+bM4cknn2TRokXsv//+ALzzne/k+uuvB+BHP/oRv/rVr5b20j311FPMnj2bYcOGrUqzS5LUp5mk9YDl3R/1+OOP59prr2XkyJFceOGFy1zFf5111gFgwIABS5c7ni9ZsgSAQYMG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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "risk_changes = pd.DataFrame([results.new_delta - results.current_delta, results.new_gamma - results.current_gamma]).T\n", "risk_changes.columns = ['delta change', 'gamma change']\n", "risk_changes.plot(kind='bar', stacked=True, figsize=(10, 6), title='Risk changes given a {}bp move'.format(move))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Please reach out with your thoughts and feedback!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### What's New\n", "\n", "* New `IRXccySwapFixFlt` and `IRXccySwapFixFix` instruments (`from gs_quant.instruments import IRXccySwapFixFlt, IRXccySwapFixFix`)\n", "* Improved error handling\n", "* Improved computational efficiency - see `4-Delta Hedging` for examples" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/made_with_gs_quant/8-What's New Internal.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# What's New - Internal " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Summary" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This new edition of `made-with-gs-quant` is tailored exclusively for our internal users and showcases some of the latest features of the internal gs-quant toolkit. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this notebook we find solutions for some the most popular questions we get such as:\n", "+ How do I generate an FX dual binary pricing grid in gs_quant? \n", "+ How do I load the trade that my client did with us? \n", "+ How do I price this rates structure live?\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The content of this notebook is split into:\n", "1. [Let's get started with gs-quant](#1---Let's-get-started-with-gs-quant)\n", "2. [TDAPI package](#2---TDAPI-package)\n", "3. [Load trade from SecDb](#3---Load-trade-from-SecDb)\n", "4. [Live Pricing](#4---Live-Pricing)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1 - Let's get started with gs-quant\n", "Upon installing the `gs-quant` and `gs-quant[internal]` packages ([see here](https://gitlab.gs.com/marquee/analytics/gs_quant_internal#internal-users) for instructions), start your session using your kerberos entitlements directly (no need to register an app). \n", " " ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "GsSession.use() " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2 - TDAPI package" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "On top of the [externalised instruments](https://developer.gs.com/docs/gsquant/guides/Pricing-and-Risk/instruments/) supported in gs-quant, TDAPI enables you to directly leverage a wider range of instruments from SecDb, considerably widening the available universe for pricing and risk analytics." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To see which instruments are supported, simply import the package and type `tdapi.[tab]`. " ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "import gs_quant_internal.tdapi as tdapi" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from IPython.display import Image\n", "Image(filename='images/tdapi_package.png')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this notebook we showcase the use of the TDAPI package using the example of Dual FX Binary Options. \n", "Let's consider 2 months options with payoff of $10M if strike 1 < EURUSD and GBPUSD < strike 2. These options are composed of 2 nested legs built as below." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.portfolio import Portfolio\n", "\n", "def FXDualBinaryOption(pair_1, pair_2, strikes_1, strikes_2, expiry, size):\n", " portfolio = Portfolio()\n", " for strike_1 in strikes_1:\n", " for strike_2 in strikes_2:\n", " over, under = pair_1.split('/')\n", " leg_1 = tdapi.FXMultiCrossBinaryChildBuilder(over=over, under=under, strike=strike_1)\n", " over, under = pair_2.split('/')\n", " leg_2 = tdapi.FXMultiCrossBinaryChildBuilder(over=over, under=under, strike=strike_2, striketype='Binary Put')\n", " dual = tdapi.FXMultiCrossBinaryBuilder(expiry=expiry, size=size, legs=(leg_1, leg_2), premium=0)\n", " portfolio.append(dual)\n", " return portfolio\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For assistance while building the instrument, `shif+tab` on the instrument name displays the list of parameters." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from IPython.display import Image\n", "Image(filename='images/instrument_help.png')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For each leg we consider a range of 9 possible strikes varying around the current spot rate of the associated currency pair. We build a portfolio of 81 options (9 USD/EUR strikes x 9 USD/GBP strikes) and compute the price for each option as a percentage of the payoff. Please note that the code uses internal SecDB notation for FX crosses (e.g. USD/EUR)." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "eur_strikes = [\"s{:+}%\".format(x) for x in range(-4, 5)]\n", "gbp_strikes = [\"s{:+}%\".format(x) for x in range(-4, 5)]\n", "\n", "dual_options = FXDualBinaryOption('USD/EUR', 'USD/GBP', eur_strikes, gbp_strikes, '2m', 10e6)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets import PricingContext\n", "import pandas as pd\n", "\n", "with PricingContext():\n", " dual_prices = dual_options.dollar_price()\n", "\n", "prices = pd.DataFrame(index=eur_strikes, columns=gbp_strikes)\n", "for dual in dual_options:\n", " prices.loc[dual.legs[0].strike][dual.legs[1].strike] = dual_prices[dual] / 10e6" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now visualise the price repartition of the options on the strike grid." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "import seaborn as sns\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "plt.subplots(figsize=(14, 8))\n", "ax = sns.heatmap(prices.apply(pd.to_numeric), annot=True, fmt='.2%', cmap='YlGn')\n", "ax.set(ylabel='USD/EUR Strikes', xlabel='USD/GBP Strikes', title='Option prices depending on Strike')\n", "ax.xaxis.tick_top()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By looking at prices as a % of premium, we can assimilate this grid of binary prices to the discounted value of the risk neutral probability of both crosses being in the money in two months time. This begs the question of how correlated they are and what premium we are saving from the correlation effect of USD/EUR and USD/GBP for the various different events." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's investigate by building a similar price grid for the individual binary options represented by the two legs. " ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "def FXBinaryOptionPrice(pair, strike, expiry, size, optiontype='Call'):\n", " over, under = pair.split('/')\n", " with PricingContext():\n", " option = tdapi.FXBinaryBuilder(over=over, under=under, strike=strike, expiry=expiry, size=size,\n", " premium=0, optiontype=optiontype)\n", " price = option.dollar_price()\n", " return price.result()" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "indep_prices = pd.DataFrame(index=eur_strikes, columns=gbp_strikes)\n", "for dual in dual_options:\n", " eurusd_price = FXBinaryOptionPrice('USD/EUR', dual.legs[0].strike, expiry='2m', size=10e6)\n", " gbpusd_price = FXBinaryOptionPrice('USD/GBP', dual.legs[1].strike, expiry='2m', size=10e6,\n", " optiontype='Put')\n", " indep_prices.loc[dual.legs[0].strike][dual.legs[1].strike] = eurusd_price/10e6 * gbpusd_price/10e6" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plt.subplots(figsize=(14, 8))\n", "ax = sns.heatmap(indep_prices.apply(pd.to_numeric)-prices.apply(pd.to_numeric), annot=True, fmt='.2%', cmap='YlGn')\n", "ax.set(ylabel='USD/EUR Strikes', xlabel='USD/GBP Strikes', title='Option price probabilities assuming independance of xccy pairs')\n", "ax.xaxis.tick_top()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By comparing the prices of the individual binary options in the above table to that of the dual binary option, you can see an approximation for the premium saved (or cost) from the correlation factor of the events." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3 - Load trade from SecDb" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Going one step further into leveraging the resources of SecDb, as an internal users you are now able to load real SecDb trades or portfolio of trades referenced by their SecDb ETI or Book ID.\n", "With this feature you can now run concrete risk analysis your own book. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With `from_book()` you can easily create a portfolio from your existing SecDb book:" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [], "source": [ "portfolio = Portfolio.from_book('my Book ID') # replace with your secDb book ID e.g. '76037320'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Your gs-quant portfolio now contains instruments based on your SecDb book leaves:" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(SDBBZCJ333333WQR3D (CashPaymentBuilder),\n", " SDBBZCJ333333WQR3D (CashPaymentBuilder),\n", " LTAABBCS3333PGQGCZ7A (IRSwaptionBuilder),\n", " LTAABBCS3333PGQGCZ7A (CashPaymentBuilder),\n", " LTAABBCS3333NRX9CCJA (CashPaymentBuilder),\n", " LTAABBCS3333NRX9CCJA (IRSwaptionBuilder))" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "portfolio.as_dict()['instruments'][:5]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can use all the gs-quant functionalities to price and calculate any of the available risk measures on your own portfolio:" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [], "source": [ "from gs_quant.risk import DollarPrice, IRDeltaParallel\n", "results = portfolio.calc((DollarPrice, IRDeltaParallel))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To view risk results, you may directly reference the SecDb leaf:" ] }, { "cell_type": "code", "execution_count": 167, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "({DollarPrice (RiskMeasure): -191826.0084344892,\n", " IRDeltaParallel (RiskMeasure): -13401.772371713341},\n", " {DollarPrice (RiskMeasure): 701555.9420614609,\n", " IRDeltaParallel (RiskMeasure): -14.823504145097733})" ] }, "execution_count": 167, "metadata": {}, "output_type": "execute_result" } ], "source": [ "results[portfolio['my leaf name']] # replace with your SecDb leaf e.g. 'LTAABBCS3333PGQGCZ7A'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Or view book level results:" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "122075528.76810677" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "results[DollarPrice].aggregate()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For trades involving supported instruments, `from.eti()` creates a gs-quant portfolio directly from the existing SecDb trade referenced by the ETI number:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "portfolio = Portfolio.from_eti('my ETI number') # Replace with your ETI trade number" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With `from_book` and `from_eti`, you can now fully leverage the gs-quant risk package on your own SecDb books! **Please note that all portfolio types are not yet handled.** Should you experience any issue, reach-out to the `gs-quant` distro.\n", "For more examples of what you can do with Portfolios, refer to the [examples folder](https://nbviewer.jupyter.org/github/goldmansachs/gs-quant/tree/master/gs_quant/examples/) and [previous made_with_gs_quant notebooks](https://nbviewer.jupyter.org/github/goldmansachs/gs-quant/tree/master/gs_quant/made_with_gs_quant/) of `made_with_gs_quant`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4 - Live Pricing" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, another powerful new feature brought to our internal users is the ability to get live prices." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Re-using the dual option portfolio built in the first section, let's compare our portfolio price under yesterday's market data at close and live pricing environments.\n", "Here we use a PricingContext with batch to group the requests together and avoid timeouts." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "160,206,436\n", "158,675,150\n" ] } ], "source": [ "from gs_quant.markets import LiveMarket\n", "\n", "live_pricing = PricingContext(market=LiveMarket('LDN'), market_data_location='LDN')\n", "close_pricing = PricingContext(is_batch=True, is_async=True)\n", "\n", "with live_pricing:\n", " prices_live = dual_options.dollar_price()\n", "with close_pricing:\n", " prices_close = dual_options.dollar_price()\n", " \n", "print('{:,.0f}'.format(prices_live.aggregate()))\n", "print('{:,.0f}'.format(prices_close.aggregate()))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`LiveMarket` accesses the latest market data available for the specified market location. This means you can now use gs-quant to compute real-time prices and intra-day risk on your portfolio." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This closes this first internal edition of `made_with_gs_quant` please continue sharing your feedback on to make `gs-quant` even more useful to you, and look out for the next editions of `What's New` in `made_with_gs_quant`. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Happy hacking!" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.0" }, "pycharm": { "stem_cell": { "cell_type": "raw", "source": [], "metadata": { "collapsed": false } } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/made_with_gs_quant/9-Hedging using Machine Learning Techniques.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Hedging using Machine Learning Techniques" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Summary" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As systematic and macro factors dominate the investment landscape, we see equity investors move away from one-size-fits all hedging strategies to more precise ways to separate intended and unintended risks and isolate alpha. \n", "\n", "If you are a fundamental equity investor, take concentrated positions in single names, or have significant idiosyncratic risk that is otherwise difficult to hedge, this highly customizable correlation-based approach is for you. Traditional factor based hedges can fare poorly when most of the risk cannot be explained by factors – in those instances this approach can allow for much better correlation to offset risk as well as a number of ways to guide which names get included while ensuring a high level of tradability by controlling liquidity and borrow costs.\n", "\n", "In this notebook, we will showcase how to leverage this approach through one of our most popular tools, the [marquee performance hedger](https://marquee.gs.com/s/hedging/performance).\n", "\n", "Additionally, based on feedback we have received from top users, we are adding the ability to easily run and compare hedges in python through [gs quant](https://developer.gs.com/docs/gsquant/) as well as new modeling techniques, two of whose key advantages are:\n", "\n", "* **Increased accuracy** through reduced overfitting\n", "* **More control** by allowing the user to specify how concentrated or diversified the hedge portfolio is\n", "\n", "\n", "The contents of this notebook are as follows:\n", "* [1 - Let's get started with gs quant](#1---Let's-get-started-with-gs-quant)\n", "* [2 - Calculate a hedge](#2---Calculate-a-hedge)\n", "* [3 - Increased accuracy](#3---Increased-accuracy)\n", "* [4 - More control](#4---More-control)\n", "* [5 - Customizing your optimization](#5---Customizing-your-optimization)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1 - Let's get started with gs quant\n", "Start every session with authenticating with your unique client id and secret. If you don't have a registered app, create one [here](https://marquee.gs.com/s/developer/myapps/register). `run_analytics` scope is required for the functionality covered in this example." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession, Environment\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Below, you can set the logging level of the notebook. By default, we set the level to INFO to show informative statements about the hedging functions." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "import logging\n", "\n", "logging.basicConfig(level=logging.INFO)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2 - Calculate a Hedge" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's start by calculating a hedge for Amazon with some starting parameters.\n", "\n", "To leverage the (machine learning-based) techniques in the enhanced performance hedger, please ensure:\n", "* `use_machine_learning` parameter is true\n", "\n", "Note: the hedge result utilizes optimal values found (using grid search) for the ML parameters, which are known as Concentration (`lasso_weight`) and Diversity (`ridge_weight`)" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
countrynametransactionCostsectorsharesassetIdbbidcurrencyindustrymarginalCostadvPercentagepriceweightborrowCost
0United StatesAdobe Inc20.263329Information Technology-832.032329MA4B66MW5E27U9XPV7XADBE UWUSDSoftware0.2613260.000149310.00-0.01289740
1United StatesAutodesk Inc20.628566Information Technology-1387.449548MA4B66MW5E27U9XPVBTADSK UWUSDSoftware0.2146010.000393149.96-0.01040340
2United StatesAkamai Technologies Inc26.810420Information Technology-4678.311848MA4B66MW5E27U9XPVVMAKAM UWUSDIT Services0.5642980.00205289.98-0.02104840
\n", "
" ], "text/plain": [ " country name transactionCost \\\n", "0 United States Adobe Inc 20.263329 \n", "1 United States Autodesk Inc 20.628566 \n", "2 United States Akamai Technologies Inc 26.810420 \n", "\n", " sector shares assetId bbid currency \\\n", "0 Information Technology -832.032329 MA4B66MW5E27U9XPV7X ADBE UW USD \n", "1 Information Technology -1387.449548 MA4B66MW5E27U9XPVBT ADSK UW USD \n", "2 Information Technology -4678.311848 MA4B66MW5E27U9XPVVM AKAM UW USD \n", "\n", " industry marginalCost advPercentage price weight borrowCost \n", "0 Software 0.261326 0.000149 310.00 -0.012897 40 \n", "1 Software 0.214601 0.000393 149.96 -0.010403 40 \n", "2 IT Services 0.564298 0.002052 89.98 -0.021048 40 " ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from gs_quant.api.gs.hedges import GsHedgeApi\n", "from gs_quant.markets.securities import SecurityMaster, AssetIdentifier\n", "from datetime import date\n", "import pandas as pd\n", "\n", "target_asset = SecurityMaster.get_asset('AMZN UW', AssetIdentifier.BLOOMBERG_ID).get_marquee_id()\n", "universe = SecurityMaster.get_asset('SPX', AssetIdentifier.BLOOMBERG_ID).get_marquee_id()\n", "hedge_query_example = GsHedgeApi.construct_performance_hedge_query(hedge_target=target_asset, \n", " universe=(universe, ),\n", " notional=10e6,\n", " observation_start_date=date(2019, 3, 25), \n", " observation_end_date=date(2020, 3, 24), \n", " backtest_start_date=date(2020, 3, 25), \n", " backtest_end_date=date(2020, 4, 24),\n", " use_machine_learning=True)\n", "\n", "# Calculate the hedge using the hedge_query_example as input\n", "results = GsHedgeApi.calculate_hedge(hedge_query_example)\n", "pd.DataFrame(results['result']['hedgedTarget']['constituents']).head(3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3 - Increased Accuracy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now let's compare results with and without using the new machine learning parameters and examine the first advantage - improved accuracy.\n", "\n", "We can do this by looking at the out-of-sample differences in cumulative returns of the two hedges over a hedge backtest period. Note here that we fit the hedge to the observation window and use the backtest window to see how both hedges perform. \n", "\n", "Note for this example: the ML parameters, Concentration (lasso_weight) and Diversity (ridge_weight), are being manually passed in, which is an alternative way to run the new performance hedger (compared to running grid search to find the optimal pair)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "observation_start = date(2019, 3, 25)\n", "observation_end = date(2020, 3, 24)\n", "backtest_start = observation_end\n", "backtest_end = date(2020, 4, 24)\n", "\n", "standard_hedger_query = GsHedgeApi.construct_performance_hedge_query(hedge_target=target_asset, \n", " universe=(universe, ), \n", " notional=10e6,\n", " observation_start_date=observation_start, \n", " observation_end_date=observation_end, \n", " backtest_start_date=backtest_start, \n", " backtest_end_date=backtest_end,\n", " max_return_deviation=20)\n", "\n", "new_hedger_query = GsHedgeApi.construct_performance_hedge_query(hedge_target=target_asset, \n", " universe=(universe, ), \n", " notional=10e6,\n", " observation_start_date=observation_start,\n", " observation_end_date=observation_end,\n", " backtest_start_date=backtest_start,\n", " backtest_end_date=backtest_end,\n", " use_machine_learning=True,\n", " lasso_weight=5.0,\n", " ridge_weight=5.0,\n", " max_return_deviation=20)\n", "\n", "standard_results = GsHedgeApi.calculate_hedge(standard_hedger_query)\n", "new_hedger_results = GsHedgeApi.calculate_hedge(new_hedger_query)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "def compare_backtests_against_target_asset(new_results, standard_results, figsize=(10, 6)):\n", " dates, target_returns = zip(*new_results['result']['target']['backtestPerformance'])\n", " _, new_hedge_returns = zip(*new_results['result']['hedge']['backtestPerformance'])\n", " _, standard_hedge_returns = zip(*standard_results['result']['hedge']['backtestPerformance'])\n", " \n", " results = pd.DataFrame([pd.Series(target_returns, dates, name='Target'), \n", " pd.Series(new_hedge_returns, dates, name='New Hedger'),\n", " pd.Series(standard_hedge_returns, dates, name='Standard Hedger')]).T\n", " \n", " results.plot(figsize=(10, 6))\n", " plt.legend(fontsize=18)\n", " plt.xlabel('Backtest Period', size=13)\n", " plt.ylabel('Cumulative Returns (% change)', size=13)\n", " plt.title('Hedge Performance Against Asset', size=22)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, we plot the cumulative returns of the target asset against the cumulative returns of each hedge." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "image/png": "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cuQIL774IqdOnUo3b8CAATz44IMsWbKEHj16sGHDBmJiYnjooYcICwtLq1e2bFnuvPNOHn/88XTLL1y4kMqVKzN8ePpb7D700EOZWse++uorEhMTue222zK1Bg4ePJi33nqLZcuWUadOnbTykJAQxowZk83eUCpvTtgXC3y1YT/VQwKZNaYdPRqEezospTwmp4maN1AJ2OdUVslp+bOAbz7GpfJZs2bN0sba2rdvH7/++isffvghq1at4pprrmHDhg34+fllu56NGzfy1FNPsWLFikyn0WrVqpWpvqsE0HFa8vjx4wDExsYSHx9Pw4YNM9Vt3Nj1jZZ/+eUXnnvuOdatW8f58+fTzTt58mSm+vXr189UlpftZqVevXqZkkF3oqKiABg7dqzbOkeOHAGsoVWAHMcZHR3NFVdcgZdX+i6o4eHhlCtXzmUcWcXtiMOhTp06eHtr3yCVv4wxfP3nAaZ8v40z55O5q0cd7u1VT/uhqRIvp4naXGCxiEwF/gVqYp32nGvPvxLrllLFQl5bri4XNWvWZOTIkWl90dasWcMff/xBly5dslzu33//pVu3bpQpU4annnqKBg0aEBwcjIhw//33u+yrlNU/dKul9+Kj80ULWVm/fj19+/albt26vPTSS9SqVYvAwEBEhJtuuonU1NRMywQFBbndfk63m58c2542bZrLizyAtIs8sorTMe9S45g9ezaVK7sejypjsu1qXyp1KfYcjeeJ+VtZu+c4bWqW5z9Dm9Ggkl5JrBTkPFGbBJwEngSqAgeAOcCL9vx1WHcqUJcREaF9+/asWbOGAwcOZFt//vz5xMfH891336U7VQhW65i/v3+e4ggPD6dUqVJprTvOtm3blqls7ty5pKSksHjx4nSteGfPnnXZmpZf281PjosugoODs22Fc5x2dBWnq7KIiAh27dpFampqula12NjYTKdZHXGEhYXluDVQqfySmJzCuyt2M335bgJ8vfjP0Gbc1K66XiyglJMcDc9hjEk2xjxnjKlnjAmyH58zxiTZ808ZY44XbKgqr5YuXepyuIeEhIS0Ky+dT6GVKlWKEydOZKrvaB3L2IrzwQcfXNJYbN7e3gwaNIjIyMh0d0kwxvDyyy/nOI7//Oc/LlvT8mu7+emqq64iPDycl156yeW+TkhI4MyZMwC0adOGatWqMXPmzHT9yOLi4lwOoTF4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\n", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "compare_backtests_against_target_asset(new_hedger_results, standard_results)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As you can see above, the New Performance Hedger more closely tracks the target asset in the backtest, or \"simulated future\", period (and thus is more accurate)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4 - More Control" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, let's demonstrate the second advantage, namely the enhanced control we have over the performance hedger results.\n", "\n", "Below, you can plot the effects of varying Concentration and Diversity on your hedge query from section 2 above. \n", "\n", "In this example, if the values for Concentration are 'None' and the values for Diversity are [10, 20], then the plotting function would run the hedge query passed in for Diversity values of 10% and 20% and then plot the weight/number of asset distributions on the y-axis and x-axis, respectively. \n" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "# For now, use ONLY Concentration or Diversity when plotting to view the effects of this hyperparameter value changing on a hedge (set the hyperparam you aren't \n", "# using to None)\n", "hyperparams = {'Concentration': None, 'Diversity': [10, 20]}" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "image/png": "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HAowTUfgB6317/lSQiGEKCRKlSoFwKlTp3ymswXdtiAcTKA4d+5c6taty3333QfATz/9xNatW+nVqxfZ2dkcOXLE/tO8eXMiIyNZuXKlW/7PP/+827DSpUtz7tw5li5ditZ5V7cycOBADh8+7NSOfu3atezevTugWvLZs2cTHR1NdHQ0tWvXZsyYMbRo0YKVK1faA7e5c+dy991306hRI6d1cfHiRRISEli3bp3bS6p9+vRxCsjBvGS5bds2+vXr5xSQ24SEmNDiiy++4Pfff6dfv36cOHHCaZ6dOnUCcFv/ISEhbk9AbM1wPDXz8SQiIsIekF++fJnjx49z5MgR2rVrB8D3339vT/vZZ58RGhrKs88+65THgAEDKF26tNOwQJenVatWaK3zvJYcTNMYTwE5mNpyWxpxcynsNeUbgZpKqWqYFzUfBVzfjFgCPKOU+gDTtOWk1tqx6UpPfDRdEUII4R9bMG4Lzr2pW7cuDRs2ZN68eYwdO5aQkBDWrl1LZmYmb731lj3djh07AHj11Vd59dVXPeb1++/u9Sl33XWX27AXX3yRtWvX0q1bN8qXL0/Lli3p2LEjPXr0cKqBD1SPHj147rnnmD17Nl26dAFMgB0REUGfPn38zqdr164888wzKKUoWrQoNWrUoEKFCk5pduzYwfnz5702tQFTU21rogKe14UtOG7YsKHPMtnWf3Jystc0rus/JibGHlTalC9fHjBtv/317rvvMm3aNLZt2+bWE8nx48ftf+/du5eYmBi3l1fDw8OpVq2aU9rcLE9+KV68OH/88YfHcRcuXLCnETeXQh2Ua60vK6WeAT4HQoE5WuttSqm/WeOnAcuATsAe4BzQzza9Uqo4pueWJwu67EIIcavZunUrALVq1coxbd++fXnuuedYvXo17dq1IzU11d60xcZWoz1s2DA6dOjgMZ+yZcu6DfMUzNSsWZPt27ezatUqVq1axZo1axgwYACvvvoqa9eutbdnDlSxYsV4/PHHmT59Or/99hvFixfno48+4qGHHvIZPLuqUqWKvSbYG6019erVY/z48V7TuM7T07qwrdec+pS3pXvrrbdo0KCBxzQxMTFO//vqacffJxTjx49n2LBhJCYmMmTIEGJiYoiIiODgwYMkJSU5BemBPPXIzfLkl5iYGLZv3052drZbjfnBgweJioqSpis3oUIdlANo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FACnTp3ymc4WdNuCcDCB4ty5c6lbty733XcfAD/99BNbt26lV69eZGdnc+TIEftP8+bNiYyMZOXKlW75P//8827DSpcuzblz51i6dCla61wvo6uBAwdy+PBhp3b0a9euZffu3QHVks+ePZvo6Giio6OpXbs2Y8aMoUWLFqxcudIeuM2dO5e7776bRo0aOa2LixcvkpCQwLp169xeUu3Tp49TQA7mJctt27bRr18/p4Dcxtbu+YsvvuD333+nX79+nDhxwmmenTp1AnBb/yEhIW5PQGzNcDw18/EkIiLCHpBfvnyZ48ePc+TIEdq1awfA999/b0/72WefERoayrPPOvd0NGDAAEqXLu00LNDladWqFVrrXNeS25pdVa5cmXfeecdp3Llz5zwG5IA9ePfUVEfc2KSmXAghxA3BFozbgnNv6tatS8OGDZk3bx5jx44lJCSEtWvXkpmZyVtvvWVPt2PHDgBeffVVXn31VY95/f77727D7rrrLrdhL774ImvXrqVbt26UL1+eli1b0rFjR3r06OFUAx+oHj168NxzzzF79my6dOkCmAA7IiKCPn36+J1P165deeaZZ1BKUbRoUWrUqEGFChWc0uzYsYPz5897bWoDpqba1kQFPK8LW3DcsGFDn2Wyrf/k5GSvaVzXf0xMjFONMED58uUB0/bbX++++y7Tpk1j27Ztbj2RHD9+3P733r17iYmJcXt5NTw8nGrVqjmlzc3y5NbevXtp27YtSimWL1/uts2KFy/u1Mbc0YULF+xpxM1FgnIhhBA3hK1btwJQq1atHNP27duX5557jtWrV9OuXTtSU1PtTVtsbDXaw4YNo0OHDh7zKVu2rNswT8FMzZo12b59O6tWrWLVqlWsWbOGAQMG8Oqrr7J27Vp7e+ZAFStWjMcff5zp06fz22+/Ubx4cT766CMeeughn8GzqypVqthrgr3RWlOvXj3Gjx/vNY2n4M9TPkCOfcrb0r311ls0aNDAY5qYmBin/331tOPvE4rx48czbNgwEhMTGTJkCDExMURERHDw4EGSkpKcgvRAnnrkZnlyIzMzk9atW3PmzBlWrVpFvXr1PM5n+/btZGdnu9WYHzx4kKioKGm6chOSoFwIIcQNYfbs2QB07tw5x7S9evVi+PDhpKam0qxZMz766CMSEhKoVKmSPU3NmjUBE+jlFLD6o0iRInTq1MneVGHZsmV07tyZ8ePHM3XqVK/T5RS8Dhw4kKlTp5KammpvJnM9L3h6U7NmTbKysmjTpo29iUlu2G6aXHts8TQ/gMjIyDxZ/458rdO0tDRiY2NZvny503KuWLHCLW21atX48ssvOXPmjFNt+aVLl9i7d6/Ty5X5uTw2+/bto3Xr1pw8eZIvv/zS69OIJk2asHLlSjZs2MCDDz5oH37hwgUyMjJumn79hTNpUy6EECKorly5wvPPP8+6devo1KkTzZo1y3Ga6OhoOnbsyKJFi5g3bx6nTp1yesETTPOKunXrMm3aNH755Re3PC5fvsyxY8f8KqPtJUpHtrbrOeVhC/a8patfvz5xcXHMmTOH2bNnU7VqVRITE/0qVyD69OnDb7/95rWm3N+mF/feey916tRhzpw5bNu2zW28rUa5ffv23Hbbbbz55psel/38+fOcPn06gCW4xtc6DQ0NRSnlVAt++fJl3nzzTbe0Xbp04cqVK0yc6Py12pkzZ3Ly5EmnYYEuTyBdIoIJyFu1asXx48dZuXKlvQcbT3r06IFSigkTJriV+9y5c05PjMTNQ2rKhRBCFJgffvjB3rPJ6dOn2bVrF4sXL2bfvn0kJiYyf/58v/Pq27cvS5YsYdiwYZQuXZquXbs6jVdKkZaWRps2bahfvz7JycnUqVOHc+fOsWfPHhYtWsS4cePcel/xJDExkdKlS9OiRQtuv/12Tpw4Ye9DvXfv3j6nvf/++5kyZQpPP/00nTt3Jjw8nPj4eKpVq2ZPM3DgQPr37w+YNvDXU5PtzbPPPssXX3zB8OHDWb16NW3atKFUqVLs37+fVatWUbRoUb766qsc81FK8d5779G2bVvi4uLsXSKeOHGCNWvW0KFDBwYPHkxkZCSpqal069aNWrVqkZycTI0aNThx4gQ7d+5k0aJFfPLJJ7nqraRJkyaEhIQwZswYjh8/TmRkJNWqVSM+Pp6//OUvjBw5ko4dO9K9e3dOnTrF/PnzPX7UqX///kyfPp2XX36ZPXv22LtEXLhwITVq1HDqezzQ5QmkS8TTp0/TunVrMjMzGTx4MLt27WLXrl1OaRISEuzvCdh6h5kyZQrdu3enU6dO7Nixg0mTJtGyZUv5cNBNSoJyIYQQBWbBggUsWLCAkJAQSpQoQZUqVWjZsiU9e/b02u7bmz/96U+UK1eOY8eO0b9/f4oVK+aWpkGDBmzZsoVx48axZMkSpk2bRsmSJYmNjSUpKYm2bdv6Na+nnnqKhQsXMn36dI4dO0b58uVp2LAhkydPpnXr1j6n7dmzJ1u2bOGDDz7gww8/5OrVq7z33ntOQfmjjz7K0KFDOXPmjP3DOHktPDycpUuX8u6775KWlmZ/+TUmJoa4uDi3Jw2+NGnShI0bNzJ69GgWLlzItGnTiIqKIi4uzulJR/v27dm4cSNvvvkmc+fOJSsri7Jly3LnnXcydOhQj723+KNq1arMmTOHf/7znzz11FNcunSJvn37Eh8fz/Dhw9FaM3v2bJ599lkqVqxIjx496Nevn1MXmmCaJK1atYrhw4fz6aefsnDhQuLj41m1ahX9+/d368Ekv5bn6NGj7N27F4DJkyd7TPPVV185vbw7YcIEYmNjmTFjBkuXLiUqKorBgwfz+uuv58tNnch/Ki+7/VBlqQAAIABJREFUdhI5a9y4sd60aVO+zmPkop88Dh/X3f1lESFE8OzYscP+4RNRuGVnZ1OpUiWaNGni9HEbERxXrlwhKiqK+Ph4j23RbxX+nIOUUpu11o19JhJ5Qm6lhBBCiCCbN28ex48f58knnwx2UQod177ZAaZNm8aJEydISEgIQolEYSXNV4QQQogg+eyzz9i3bx+jRo2idu3abu3iRf4bMGAAFy5coGnTphQpUoRvv/2W+fPnU6NGDQYOHBjs4olCRIJyIYQQIkgGDx7MoUOHaNSoEbNmzfLZT7fIH4mJiUydOpXRo0dz5swZKlSoQP/+/Rk9evR1fRhKiEAV+qBcKdUBmAiEArO01m+6jFfW+E7AOSBJa/2DNa4MMAuoC2ggWWv9bQEWXwghxE0sMzMz2EUo9Pr06RPQ11OFyC+Fuk25UioUmAp0BGoDPZVStV2SdQRqWj8DgX87jJsIrNBa3w3cC+zI90ILIYQQQohbTqEOyoE4YI/W+het9UXgA8C1QV9XIFUb3wFllFKVlFKlgBbAbACt9UWt9YmCLLwQQgghhLg1FPagvDLwq8P/B6xh/qSpDmQB7ymltiilZimlIvOzsEKIW490SyuECAY599x4CntQrjwMc91LvaUJA+4D/q21bgicBV7wOBOlBiqlNimlNmVlZV1PeYUQt5DQ0FAuXboU7GIIIQqhS5cuyYvFN5jCHpQfAG53+L8KcMjPNAeAA1rr763hH2GCdDda6xla68Za68bR0dF5UnAhxM2vZMmSnDp1KtjFEEIUQqdOnZLeZW4whT0o3wjUVEpVU0pFAI8CS1zSLAH6KON+4KTW+rDW+jfgV6VULStdW2B7gZVcCHHTK1euHMePH+fIkSNcvHhRHicLIfKV1pqLFy9y5MgRjh8/Trly5YJdJOGgUHeJqLW+rJR6Bvgc0yXiHK31NqXU36zx04BlmO4Q92C6ROznkMVgYJ4V0P/iMk4IIXwqUqQIVatW5dixY2RmZnLlypVgF0kIcYsLDQ2lZMmSVK1alSJFigS7OMJBoQ7KAbTWyzCBt+OwaQ5/a2CQl2kzgMb5WkAhxC2tSJEiVKpUiUqVKgW7KEIIIYKosDdfEUIIIYQQIugkKBdCCCGEECLIJCgXQgghhBAiyCQoF0IIIYQQIsgkKBdCCCGEECLICn3vK4XNyEU/eRw+rnu9Ai6JEEIIIYSwkZpyIYQQQgghgkyCciGEEEIIIYJMgnIhhBBCCCGCTIJyIYQQQgghgkxe9BRO5EVQIYQQQoiCJzXlQgghhBBCBJkE5UIIIYQQQgSZBOVCCCGEEEIEmQTlQgghhBBCBJkE5UIIIYQQQgSZBOVCCCGEEEIEmQTlQgghhBBCBJkE5UIIIYQQQgSZBOVCCCGEEEIEmQTlQgghhBBCBJkE5UIIIYQQQgSZBOVCCCGEEEIEmQTlQgghhBBCBJkE5UIIIYQQQgSZBOVCCCGEEEIEmQTlQgghhBBCBJkE5UIIIYQQQgSZBOVCCCGEEEIEmQTlQgghhBBCBJkE5UIIIYQQQgSZBOVCCCGEEEIEmQTlQgghhBBCBJkE5UIIIYQQQgRZWLALIG4eIxf95HH4uO71CrgkQgghhBC3lkJfU66U6qCU2qWU2qOUesHDeKWUmmSN36qUus9hXKZS6ielVIZSalPBllwIIYQQQtwqCnVNuVIqFJgKJAAHgI1KqSVa6+0OyToCNa2feODf1m+b1lrrIwVUZCGEEEIIcQsq7DXlccAerfUvWuuLwAdAV5c0XYFUbXwHlFFKVSroggohhBBCiFtXYQ/KKwO/Ovx/wBrmbxoNrFRKbVZKDfQ2E6XUQKXUJqXUpqysrDwothBCCCGEuJUU9qBceRimA0jTTGt9H6aJyyClVAtPM9Faz9BaN9ZaN46Ojs59aYUQQgghxC2psAflB4DbHf6vAhzyN43W2vb7D+ATTHMYIYQQQgghAlLYg/KNQE2lVDWlVATwKLDEJc0SoI/VC8v9wEmt9WGlVKRSqiSAUioSSAR+LsjCCyGEEEKIW0Oh7n1Fa31ZKfUM8DkQCszRWm9TSv3NGj8NWAZ0AvYA54B+1uQVgE+UUmDW43yt9YoCXgSPuh34l5cxaQVaDiGEEEII4Z9CHZQDaK2XYQJvx2HTHP7WwCAP0/0C3JvvBRRCCCGEELe8wt58RQghhBBCiKCToFwIIYQQQoggk6BcCCGEEEKIIJOgXAghhBBCiCCToFwIIYQQQoggk6BcCCGEEEKIIJOgXAghhBBCiCCToFwIIYQQQoggk6BcCCGEEEKIIJOgXAghhBBCiCCToFwIIYQQQoggCwt2AcStY+SinzwOH9e9XgGXRAghhBDi5iI15UIIIYQQQgSZBOVCCCGEEEIEmQTlQgghhBBCBJm0KS9kuh34l5cxaQVaDiGEEEIIcY3UlAshhBBCCBFkEpQLIYQQQggRZBKUCyGEEEIIEWQSlAshhBBCCBFkEpQLIYQQQggRZBKUCyGEEEIIEWQSlAshhBBCCBFkEpQLIYQQQggRZBKUCyGEEEIIEWQ3/Rc9lVI1gRNa6yylVCQwHLgMvK21vhDc0gkhhBBCCJGzW6GmfD5Qwfp7LNAd6AZMCFqJhBBCCCGECMBNX1MO3Alss/7+C9AMOA38BPwtWIUSQgghhBDCX7dCUK6AUKVUDeCc1joTQClVMqilEkIIIYQQwk+3QlD+PTAVqAgsA1BKxQLHglckIYQQQggh/HcrtCl/EiiBCcJft4bFYdqaCyGEEEIIccO7FWrKldb6MccBWuuFSqnvg1Wg/7+9+w+2q6zvPf7+GEG0qIhGRQISnJSKUFFTwKv1IgoNiAaxtmBVpGNT7wTEW60V7p1qnas4XnUKDiWlkCr4g6EKNSoWRaVqvSABQQSkRn7IgQBRBBEqCn7vH2ul3ezsk7MTkrPO3vv9mtlz9nqetfb+7mcifs5znvXsUXb41Aen6Tl7VuuQJEmaJOMQyr8HPGFA+3eBHWe5Fk3jhPOuHth+0hF7z3IlkiRJc884LF/JBg3JNkB1UIskSZK0yUZ2pjzJV2iC92OSfLmve1fgiiFfZwlwMjAPOKOqPtDXn7b/UOB+4E1VdUVP/zxgNXBrVR22mR9HkiRJE2xkQznwrfbnfwf+raf9N8DtwD/N9AJtoD4VOAiYAi5Lsqqqru057RBgUfvYDzit/bne8cB1DF5CI0mSJM1oZEN5Vf0NQJLrqurczXyZfYE1VXVD+1rnAEuB3lC+FDirqgq4JMkOSXaqqrVJFgCvAN4H/MXmfhZJkiRNtpEN5eutD+TtlwU9vq/vthku3xm4ped4iofPgk93zs7AWuBvgXf2v68kSZK0KUb+Rs8k+yf5d+BumvB8C01wvmWjF7aXD2jrv0F04DlJDgPurKrLh6hxWZLVSVavW7duiLIkSZI0SUY+lAOnA18A9gZ2bx8L258zmQJ26TleAPTPrk93zouAVyW5CTgHODDJJwa9SVWdXlWLq2rx/PnzhyhLkiRJk2QcQvlC4O1VdW1V3dz7GOLay4BFSRYm2RY4EljVd84q4I1p7A/cU1Vrq+qEqlpQVbu1132tql6/BT+XJEmSJsTIrykHLgX2AH6wqRdW1YNJjgUupNkScWVVXZPkLW3/CuACmu0Q19BsiXjMlip81Phtn5IkSVvHSIbyJK/rOfwqsCrJCpqtEP9TVX1qpteqqgtogndv24qe5wUsn+E1LgYunum9JEmSpEFGMpTTbEHY77i+4wJmDOWSJElS10YylFfVwq5rkCRJkraUcbjRU5IkSRppIzlT3ivJjWy4tzjAA8DNwKeq6qzZrUqb6oTzrh7YftIRe89yJZIkSbNvHGbKVwLbAp+gWWv+CZpfNs4FrgA+kuQvuytPkiRJ2riRnykHDgIOq6or1zck+Szw0ap6SZIvAWcC/7erAiVJkqSNGYeZ8ucC/WsfrgH2aZ9/C3jGrFYkSZIkbYJxCOU/BI7vazuubQd4GnDvrFYkSZIkbYJxWL6yHPhikuOAHwO7Ao8HXtH27wVM91WUkiRJUudGPpRX1aVJdgdeRbNM5VbgC1V1T9t/EXBRhyVKkiRJGzXyoRygqn5Os+uKJEmSNHJGMpQneUdVfah9fuJ051XV+2evKkmSJGnzjGQoBw4EPtQ+P2iacwowlI8Bv1hIkiSNu5EM5VV1aM/zl3ZZiyRJkvRIjWQoHyTJM4Bdq+qSrmvR7HImXZIkjbqR36c8yVOTXARM0e6ykuSPk/xdt5VJkiRJwxn5UA6cAtwIzAd+3bZ9DTi4s4okSZKkTTAOy1deCjyzqn6ZpACqal2S+R3XJUmSJA1lHGbKH6Dvl4skOwJ3dVOOJEmStGnGYab8y8CHkxzb0/Ye4IvdlDO5Dp/64DQ9Z89qHZIkSaNmHEL5O4HPAT8DtktyN3AlcHinVWlOcGcWSZI0CkY2lCd5BXBxVd0F/H6SFwALgZuB1VVVnRYoSZIkDWlkQznwGeBRSb5DsxXiV4Dzq+qhbsuSJEmSNs0o3+j5JOAVwDeBQ4F/Be5K8vkkxyd5TqfVSZIkSUMa2ZnyqvolzQz5+i8MegJwAPAy4K+ADzPCn0+zwzXnkiRpLhiL0JrkSTT7lR8IvBzYDvhCp0VJkiRJQxrZUJ7kYJpZ8ZcDzwK+DXwd+BPgCm/0lCRJ0qgY2VAO/AvwfZo9yVdV1YPdliNJkiRtnlG+0fNtwI+AM4Crknw0yavbpSySJEnSyBjZUF5Vp1TVq4GnAMcAtwL/A7g5yeVJpvt6SUmSJGlOGdlQvl5V/aaqvlNVHwCOB94L7AS8vdvKJEmSpOGM8ppykuxCc7Pny2h2Xnk68O/A+bRbJUqSJElz3ciG8iQ/BHYHbge+BpwIfLWqpjotTJIkSdpEIxvKgY8CF1XVtV0XopkdPjXdEv+zZ7UOSZKkuWhkQ3lVndJ1DZIkSdKWMLKhfEtJsgQ4GZgHnNHeMNrbn7b/UOB+4E1VdUWS7YBvAI+hGcfPVNW7Z7V4bVUnnHf1wPaTjth7liuRJEnjbuR3X3kkkswDTgUOAfYEjkqyZ99phwCL2scy4LS2/QHgwKp6LrAPsCTJ/rNSuCRJksbKpM+U7wusqaobAJKcAywFetepLwXOqqoCLkmyQ5Kdqmot8Iv2nG3aR81e6eqaM+mSJGlLmeiZcmBn4Jae46m2bahzksxLciVwJ/CVqrp00JskWZZkdZLV69at22LFS5IkaTxM+kx5BrT1z3ZPe05VPQTsk2QH4Pwke1XV9zc4uep04HSAxYsXO5s+gLuzSJKkSTbpM+VTwC49xwuA2zb1nKq6G7gYWLLlS5QkSdK4m/RQfhmwKMnCJNsCRwKr+s5ZBbwxjf2Be6pqbZL57Qw5SR4LvBz4wWwWL0mSpPEw0ctXqurBJMcCF9Jsibiyqq5J8pa2fwVwAc12iGtotkQ8pr18J+Dj7Q4ujwLOraovzPZnkCRJ0uib6FAOUFUX0ATv3rYVPc8LWD7guu8Bz9vqBUqSJGnsTXwo19znTaCSJGncTfqackmSJKlzhnJJkiSpYy5f0chzeYskSRp1hnKNNQO7JEkaBYZyTTRDuyRJmgsM5dI0DOySJGm2GMqlzWRolyRJW4q7r0iSJEkdc6Zc2gpOOO/qge0nHbH3LFciSZJGgTPlkiRJUscM5ZIkSVLHDOWSJElSxwzlkiRJUscM5ZIkSVLHDOWSJElSx9wSUeqAWyZKkqRezpRLkiRJHTOUS5IkSR1z+Yo0x7i0RZKkyWMol7aCw6c+OE3P2bNahyRJGg0uX5EkSZI65ky51AFn0iVJUi9nyiVJkqSOGcolSZKkjrl8RZpjXNoiSdLkcaZckiRJ6pihXJIkSeqYy1ekUfP54we3v/Lk2a1DkiRtMYZyaZwY2CVJGkkuX5EkSZI65ky5NGIuvfGuge37zdAnSZLmLmfKJUmSpI4ZyiVJkqSOTXwoT7IkyfVJ1iR514D+JDml7f9ekue37bsk+XqS65Jck2SaO+wkSZKkjZvoUJ5kHnAqcAiwJ3BUkj37TjsEWNQ+lgGnte0PAm+vqmcD+wPLB1wrSZIkzWjSb/TcF1hTVTcAJDkHWApc23POUuCsqirgkiQ7JNmpqtYCawGq6t4k1wE7910rzS1umShJ0pw06aF8Z+CWnuMpNtyoYtA5O9MGcoAkuwHPAy7dGkVKW4q7s0iSNDdN9PIVIAPaalPOSbI98FngbVX184FvkixLsjrJ6nXr1m12sZIkSRpPkx7Kp4Bdeo4XALcNe06SbWgC+Ser6rzp3qSqTq+qxVW1eP78+VukcEmSJI2PSV++chmwKMlC4FbgSOB1feesAo5t15vvB9xTVWuTBDgTuK6qPjKbRUtbhevNJUnqzESH8qp6MMmxwIXAPGBlVV2T5C1t/wrgAuBQYA1wP3BMe/mLgDcAVye5sm07saoumM3PIG0pM603v/SUNwzuf+vZW6kiSZImx0SHcoA2RF/Q17ai53kBywdc9y0GrzeXJs4J5109sP2kI/ae5UokSRpNEx/KJT1yh099cJoeZ9ElSRrGpN/oKUmSJHXOUC5JkiR1zFAuSZIkdcxQLkmSJHXMGz0lbXXuziJJ0sYZyiV1aqbAbqCXJE0CQ7mkrW5jWya6naIkSYZySXPcRkP7548f3PXKk7daPZIkbQ2Gcknjy9AuSRoR7r4iSZIkdcxQLkmSJHXM5SuSRtalN941sH2/Wa5DkqRHyplySZIkqWPOlEuaTN4EKkmaQ5wplyRJkjrmTLmkseWac0nSqHCmXJIkSeqYoVySJEnqmKFckiRJ6pihXJIkSeqYoVySJEnqmKFckiRJ6pihXJIkSeqYoVySJEnqmKFckiRJ6pihXJIkSeqYoVySJEnqmKFckiRJ6tijuy5Akuaczx8/uP2VJ89uHZKkieFMuSRJktQxZ8olaVM5ky5J2sKcKZckSZI65ky5JG1JM82iO8suSRrAUC5pIl16410D2/eb5TokSQJDOUmWACcD84AzquoDff1p+w8F7gfeVFVXtH0rgcOAO6tqr1ktXNJW01lgdxZdkibWRK8pTzIPOBU4BNgTOCrJnn2nHQIsah/LgNN6+j4GLNn6lUqSJGmcTfpM+b7Amqq6ASDJOcBS4Nqec5YCZ1VVAZck2SHJTlW1tqq+kWS32S5a0oTa2Ey6s+ySNNImPZTvDNzSczzFhn+hHnTOzsDaYd8kyTKaWXZ23XXXzSpUkh4RA70kzWkTvXwFyIC22oxzNqqqTq+qxVW1eP78+ZtyqSRJkibApM+UTwG79BwvAG7bjHMkTRB3bpEkbWmTPlN+GbAoycIk2wJHAqv6zlkFvDGN/YF7qmropSuSJEnSTCZ6pryqHkxyLHAhzZaIK6vqmiRvaftXABfQbIe4hmZLxGPWX5/k08ABwFOSTAHvrqozZ/dTSJpLnEWXJG2OiQ7lAFV1AU3w7m1b0fO8gOXTXHvU1q1OkuYAbwSVpK1u0pevSJIkSZ0zlEuSJEkdm/jlK5I0m1xzLkkaxFAuSdp8rjeXpC3C5SuSJElSxwzlkiRJUscM5ZIkSVLHDOWSJElSxwzlkiRJUsfcfUWStPVsbHcWd26RpP9kKJckzU2GdkkTxFAuSXOEXyy0CQzsksaMoVySNH4M7ZJGjKFckjRZDOyS5iBDuSRJvQztkjpgKJekEeGac0kaX4ZySZKGNdMsurPskjaTXx4kSZIkdcyZckkaAy5tGQHOokvaCEO5JE0AQ/sI8NtPpYlmKJckadQZ6KWR55pySZIkqWPOlEuSNMmcZZfmBEO5JE24mdabb6zfteoTblBod3tIabO4fEWSJEnqmDPlkiRpdvklTNIGnCmXJEmSOuZMuSRJGh3OsmtMGcolSVuNN4JqTjGwaw5z+YokSZLUMWfKJUmdeCRbMUpbhTPp6pChXJI0cgzsksaNy1ckSZKkjjlTLkmSNBOXtmgrm/hQnmQJcDIwDzijqj7Q15+2/1DgfuBNVXXFMNdKkqQJYWjXIzTRoTzJPOBU4CBgCrgsyaqqurbntEOARe1jP+A0YL8hr5UkSZPOwK4hTHQoB/YF1lTVDQBJzgGWAr3BeilwVlUVcEmSHZLsBOw2xLWSpA54I6hGysZCu4F+Ykz6jZ47A7f0HE+1bcOcM8y1kiRJ0ozSTABPpiSvBf6gqt7cHr8B2Leqjus554vASVX1rfb4q8A7gd1nurbnNZYBy9rDPYDrt96n2sBTgJ/M4vuNMsdqeI7VcByn4TlWw3OshudYDW+6sXpmVc2f7WIm0aQvX5kCduk5XgDcNuQ52w5xLQBVdTpw+iMtdnMkWV1Vi7t471HjWA3PsRqO4zQ8x2p4jtXwHKvhOVbdm/TlK5cBi5IsTLItcCSwqu+cVcAb09gfuKeq1g55rSRJkjSjiZ4pr6oHkxwLXEizreHKqromyVva/hXABTTbIa6h2RLxmI1d28HHkCRJ0oib6FAOUFUX0ATv3rYVPc8LWD7stXNQJ8tmRpRjNTzHajiO0/Acq+E5VsNzrIbnWHVsom/0lCRJkuaCSV9TLkmSJHXOUD6mkixJcn2SNUne1XU9c02SlUnuTPL9nrYdk3wlyQ/bn0/qssa5IMkuSb6e5Lok1yQ5vm13rPok2S7Jd5Jc1Y7V37TtjtUASeYl+W6SL7THjtM0ktyU5OokVyZZ3bY5Xn3aL/f7TJIftP/NeqHjtKEke7T/ltY/fp7kbY5V9wzlYyjJPOBU4BBgT+CoJHt2W9Wc8zFgSV/bu4CvVtUi4Kvt8aR7EHh7VT0b2B9Y3v5bcqw29ABwYFU9F9gHWNLu2ORYDXY8cF3PseO0cS+tqn16tqxzvDZ0MvAvVfU7wHNp/n05Tn2q6vr239I+wAtoNrE4H8eqc4by8bQvsKaqbqiqXwHnAEs7rmlOqapvAP3fw70U+Hj7/OPA4bNa1BxUVWur6or2+b00/ye3M47VBqrxi/Zwm/ZROFYbSLIAeAVwRk+z47RpHK8eSZ4AvAQ4E6CqflVVd+M4zeRlwI+q6mYcq84ZysfTzsAtPcdTbZs27mntHvS0P5/acT1zSpLdgOcBl+JYDdQuybgSuBP4SlU5VoP9Lc03I/+mp81xml4BX05yefsN0eB49dsdWAf8Y7ss6owkv4XjNJMjgU+3zx2rjhnKx1MGtLnNjjZbku2BzwJvq6qfd13PXFVVD7V/El4A7Jtkr65rmmuSHAbcWVWXd13LCHlRVT2fZkni8iQv6bqgOejRwPOB06rqecB9uPxio9ovPnwV8E9d16KGoXw8TQG79BwvAG7rqJZRckeSnQDan3d2XM+ckGQbmkD+yao6r212rDai/bP5xTT3LThWD/ci4FVJbqJZWndgkk/gOE2rqm5rf95Js/Z3XxyvflPAVPvXKYDP0IR0x2l6hwBXVNUd7bFj1TFD+Xi6DFiUZGH7m/CRwKqOaxoFq4Cj2+dHA5/rsJY5IUlo1mheV1Uf6elyrPokmZ9kh/b5Y4GXAz/AsXqYqjqhqhZU1W40/236WlW9HsdpoCS/leTx658DBwPfx/F6mKq6HbglyR5t08uAa3GcNuYo/mvpCjhWnfPLg8ZUkkNp1m3OA1ZW1fs6LmlOSfJp4ADgKcAdwLuBfwbOBXYFfgy8tqr6bwadKEleDHwTuJr/Wv97Is26cseqR5Lfpbk5ah7NhMe5VfXeJE/GsRooyQHAO6rqMMdpsCS708yOQ7NE41NV9T7Ha0NJ9qG5eXhb4AbgGNr/LeI4PUySx9Hce7Z7Vd3TtvlvqmOGckmSJKljLl+RJEmSOmYolyRJkjpmKJckSZI6ZiiXJEmSOmYolyRJkjpmKJekrSTJAUke7LoOgCRHJ5lK8oskr+m6HknSwxnKJY29JBcnqf6vJ0+yJsmbOipr1iR5NPB3wLKq2r6qPruRc1/cjtXKWayv2j3xJWliGcolTYqfAh9qv6V0ZCXZZjMuezrwOOB7Q5y7DLgL+OMkT9yM95IkbQZDuaRJ8Q/AApqvlt7AoKUmSd6T5KKe40pybJLVSe5L8u0kC5L8zyS3JPlpkg2+PbddOnJzkruSfCzJ9j19T05yZnv9uiTnJnlaT/9NSf46ydeT3AcMXHqS5DVJrkpyT/vz1W37C4Hr29Oub5evPGaa13gS8FrgOOA/gDf09R+Z5Lok9ya5I8nH2vYkeV+S29q+m5Ic13PdXkkuTPKTJD9OctL6Xy6SXNWe9uW2tjPa9rcmubF9vVuTvH9QzZI0LgzlkibFfcBfA++fLpQO6fXA4cB84JfA14AnAc8CDgTekeS/9Zw/D3gl8LvAs4HfBj4MTZgF/hkoYC/gmcC9wKf63vPPgL8Atgc+119QG7w/CbwLeDJwIvDpJPtV1f8DntOeuke7fOWBaT7b0cAvgM+0r7es5z0eB5wNLK+qxwO7A2e23Qe11+7X9u0H/Ft73VOBfwXOA54BvLA9/wSAqnpu+xoHt7W9OclvAx8ADmtf7znAqmlqlqSxYCiXNEn+kSb0Hv8IXuPDVTVVVffThNenA++pql9V1VXAVcDv9V3zV1V1T1XdQfOLwdFJHgW8oH0sb/vvB94JHJhkQc/1/1BV363Gfwyo6Rjgs1X1pap6sKq+CJwP/OkmfrY/Az5ZVb+iCdx7t4F/vV8Dv5Nkx6q6r6q+2bb/CtgOeE6S7arqjqq6ou17I3BVVf19O0a3Aie17dN5EEj7ettX1d1VdckmfhZJGimGckkTo6oeogm9JyZ58ma+zNqe5/cDd1bVb/raHt93zc09z28CHgM8BVjYPr8jyd1J7gZ+RDMDv2vfNRuzC3BDX9uP2vahJPl9YE9gJUBVfQ9YDfx5e3w/cCiwBPhRksuTvK7tu5hmdv5/A3e2S1UWty+9EHjR+s/XfsaVNL/MDFRVNwB/QvNLwm1JvpXk4GE/iySNIkO5pIlSVV/QLnG/AAACGUlEQVQCvkMzY93rF8C8vqUtz9hCb/vMnue7AQ8AP6EJ6/cBO1bVDj2Px1bVt3uu6Q39g9xCE3577d62D+vP259fTnJ7kttpQvofJdkBmvBdVa+i+YXi/wCfSPKstu/0qnoxTdi+ima5Cu1nvKjv8z2xqrbvee/qL6aqzquqg9r3Ohf4XLuERpLGkqFc0iT6S5r10vN72q6nCeZvTvKodou+P9xC73dSkie066vfA5zdzq6vBq4ETl4/c59kfpIjN/H1Pwa8JskfJJmX5BDgCJrlOjNKsiPNDaTLgX16Hs+mmbV/Q5KntTeTPrH9i8Pd7eUPJfm9divFx9D8wnEvzRIUgLOAxUn+NMl27djunmRJTwm3A4t66tkjyZI2hP8auIcmuM/0y4kkjSxDuaSJ0679Pgd4Qk/bvTRrs99OEwKPBz6+Bd7uIeCLwNU0wf8Gmps2aYP54TT/Lb48yb3ApcABm/IG7az60cCHgJ8BHwRevwnrsI+mCdlnVNXtPY8fAytoZtEfRRPab2rrPBU4uqpuolmucwrN7P9PgYOBI9vabgde2n7Om9r6zqeZyV/vfwHvTfKzJH8PbAu8m2ap0N3AW4HXVNUvN2VcJGmUpGqDvxpKkiRJmkXOlEuSJEkdM5RLkiRJHTOUS5IkSR0zlEuSJEkdM5RLkiRJHTOUS5IkSR0zlEuSJEkdM5RLkiRJHTOUS5IkSR37/xI+p6dVCjgLAAAAAElFTkSuQmCC\n", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from gs_quant.markets.hedge import Hedge\n", "\n", "# Plot results of the hedge - with emphasis on the effects that either the Concentration or Diversity hyperparameter has on the hedge\n", "hedge_plot = Hedge.plot_weights_against_number_of_assets(hedge_query_example, hyperparams, figsize=(10, 6))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As you can see above, varying the value of a single hyperparameter has significant effects on the weights & total number of assets of the hedge portfolio that is constructed to hedge the single target asset.\n", "\n", "For this particular example, the effect is that the weights become more balanced the more the \"Diversity\" hyperparameter increases." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 5 - Customizing your Optimization" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Since we know how to run a basic hedge, let's experiment with the concentration and diversity metrics to find an optimal hedge.\n", "\n", "Note, the hedger is flexible and allows you to choose which metric to optimize - for example, you may want to run to optimize correlation (in which case you would maximize r-squared) or to optimize transaction costs (in which case you would minimize total transaction costs)." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "# Modify hyperparameter grid to the values you want to use to find the optimal hedge . As mentioned before, the terminology used for Concentration and Diversity\n", "# seen below relates to the terminology used for each term in the machine learning literature.\n", "# Concentration = Lasso (as a percentage)\n", "# Diversity = Ridge (as a percentage)\n", "hyperparams = {'Concentration': [0, 20, 40], 'Diversity': [10, 20]}\n", "\n", "# Modify this to optimize a metric (maximize or minimize depending on the metric. \n", "# See the create_optimization_mappings function for how metrics are optimized\n", "metric_to_optimize = 'rSquared'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's now run the optimization through the custom grid of concentration/diversity values we specified, and optimize for the specified metric. In this case, we will look for the combination of concentration and diversity values that maximizes correlation, or r-squared." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "INFO:gs_quant.markets.hedge:We are trying to maximize rSquared and will return the optimized hedge & metric value...\n", "INFO:gs_quant.markets.hedge:Current Hedge is using the following values for Concentration/Diversity: (0, 10)\n", "INFO:gs_quant.markets.hedge:Current Hedge value for rSquared: 67.6%\n", "INFO:gs_quant.markets.hedge:Current Hedge is using the following values for Concentration/Diversity: (0, 20)\n", "INFO:gs_quant.markets.hedge:Current Hedge value for rSquared: 65.3%\n", "INFO:gs_quant.markets.hedge:Current Hedge is using the following values for Concentration/Diversity: (20, 10)\n", "INFO:gs_quant.markets.hedge:Current Hedge value for rSquared: 65.6%\n", "INFO:gs_quant.markets.hedge:Current Hedge is using the following values for Concentration/Diversity: (20, 20)\n", "INFO:gs_quant.markets.hedge:Current Hedge value for rSquared: 63.4%\n", "INFO:gs_quant.markets.hedge:Current Hedge is using the following values for Concentration/Diversity: (40, 10)\n", "INFO:gs_quant.markets.hedge:Current Hedge value for rSquared: 65.0%\n", "INFO:gs_quant.markets.hedge:Current Hedge is using the following values for Concentration/Diversity: (40, 20)\n", "INFO:gs_quant.markets.hedge:Current Hedge value for rSquared: 63.1%\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "The optimal pair of hyperparameters was (0, 10), achieving a value for rSquared of 67.6% during the out of sample period.\n" ] } ], "source": [ "from gs_quant.markets.hedge import Hedge\n", "\n", "opt_hedge, opt_metric_val, opt_hyperparams = Hedge.find_optimal_hedge(hedge_query_example, hyperparams, metric_to_optimize)\n", "print(f'The optimal pair of hyperparameters was {opt_hyperparams}, achieving a value for {metric_to_optimize} '\n", " f'of {opt_metric_val*100:.3}% during the out of sample period.')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Stay tuned for additional ways to use the New Performance Hedger using `gs-quant`. For any questions/comments, feel free to reach out to the email distro **gs-data-ml**!" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.0" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/reports_and_screens/00_fx/0000_vol_screen.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": true } }, "outputs": [], "source": [ "from gs_quant.data import Dataset\n", "from gs_quant.timeseries import percentiles, volatility, last_value, Returns\n", "from gs_quant.datetime import business_day_offset\n", "import seaborn as sns\n", "import pandas as pd\n", "pd.options.display.float_format = '{:,.2f}'.format \n", "import matplotlib.pyplot as plt\n", "from scipy import stats\n", "import warnings\n", "from datetime import date\n", "warnings.filterwarnings('ignore')\n", "sns.set(style=\"darkgrid\", color_codes=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',)) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Screen Functions" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def format_df(data_dict):\n", " df = pd.concat(data_dict, axis=1)\n", " df.columns = data_dict.keys()\n", " return df.fillna(method='ffill').dropna()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": true } }, "outputs": [], "source": [ "def volatility_screen(crosses, start_date, end_date, tenor='3m', history='2y', plot=True):\n", " #replace with premium dataset for more history\n", " fxspot_dataset, fxvol_dataset = Dataset('FXSPOT'), Dataset('FXIMPLIEDVOL')\n", " spot_data, impvol_data, spot_fx, data = {}, {}, {}, {}\n", " for cross in crosses:\n", " spot_fx[cross] = fxspot_dataset.get_data(start_date, end_date, bbid=cross)[['spot']].drop_duplicates(keep='last')['spot']\n", " spot_data[cross] = volatility(spot_fx[cross], tenor) # realized vol \n", " impvol_data[cross] = fxvol_dataset.get_data(start_date, end_date, bbid=cross, tenor=tenor, deltaStrike='DN', location='NYC')[['impliedVolatility']]* 100\n", "\n", " spdata, ivdata = format_df(spot_data), format_df(impvol_data)\n", " diff = ivdata.subtract(spdata).dropna()\n", " for cross in crosses:\n", " data[cross] = {'Spot': last_value(spot_fx[cross]),\n", " f'{tenor} Implied': last_value(ivdata[cross]),\n", " f'{tenor} Realized': last_value(spdata[cross]),\n", " 'Diff': last_value(diff[cross]),\n", " f'{history} Implied Low': min(ivdata[cross]),\n", " f'{history} Implied High': max(ivdata[cross]),\n", " '%-ile': last_value(percentiles(ivdata[cross]))\n", " }\n", " df = pd.DataFrame(data)\n", " vol_screen = df.transpose()\n", " \n", " if plot:\n", " for fx in vol_screen.index:\n", " plt.scatter(vol_screen.loc[fx]['%-ile'], vol_screen.loc[fx]['Diff'])\n", " plt.legend(vol_screen.index,loc='best', bbox_to_anchor=(0.9, -0.13), ncol=3)\n", " \n", " plt.xlabel('Percentile of Current Implied Vol')\n", " plt.ylabel('Implied vs Realized Vol')\n", " plt.title('Entry Point vs Richness')\n", " plt.show()\n", " return vol_screen.sort_values(by=['Diff']).style.background_gradient(subset=['Diff']).format(\"{:.1f}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### FX Implied Volatility Screen\n", "Let's pull [GS FX Spot](https://marquee.gs.com/s/developer/datasets/FXSPOT) and [GS FX Implied Volatility](https://marquee.gs.com/s/developer/datasets/FXIMPLIEDVOL) and look at implied vs realized vol as well as current implied level as percentile relative to the last 2 years. Note, FX Spot uses GS NYC closes.\n", "\n", "\n", "If you are looking for additional history or coverage, please see our premium version [link](https://marquee.gs.com/s/developer/datasets/FXIMPLIEDVOL_PREMIUM).\n", "\n", "The FX Structuring team uses this analysis to screen for the most attractive vols to buy in the 3m tenor by looking at where implied trades in its own history and where realized trades in relationship with implieds.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Entry Point vs Richness\n", "\n", "Note: Lower left corner shows currencies with low and cheap vol. Upper right corner\n", "shows currencies with high and rich vol." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.datetime import business_day_offset\n", "from dateutil.relativedelta import relativedelta\n", "g10 = ['USDJPY', 'EURUSD', 'AUDUSD', 'GBPUSD', 'USDCAD', 'USDNOK', 'NZDUSD', 'USDSEK', 'USDCHF']\n", "\n", "end = business_day_offset(date.today(), -1, roll='forward')\n", "start = business_day_offset(end - relativedelta(years=2), -1, roll='forward')\n", "\n", "tenor = '3m'\n", "history = '2y'\n", "\n", "screen = volatility_screen(g10, start, end, tenor, history, plot=True)\n", "screen" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/reports_and_screens/00_fx/0001_vol_calendar_screen.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Vol Calendar Functions" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.data import Dataset\n", "from gs_quant.timeseries import last_value, forward_vol, percentiles\n", "from gs_quant.markets.securities import AssetIdentifier, SecurityMaster\n", "import pandas as pd\n", "pd.options.display.float_format = '{:,.2f}'.format \n", "import warnings\n", "from datetime import date\n", "warnings.filterwarnings('ignore')\n", "import seaborn as sns\n", "import matplotlib.pyplot as plt\n", "\n", "\n", "def vol_calendar_screen(crosses, long_tenor, short_tenor, start_date, end_date, history='2y',boxplot=True, deltaStrike='DN', location='NYC'):\n", " impvol = Dataset('FXIMPLIEDVOL')\n", " data = {}\n", " boxplt = pd.DataFrame()\n", " for bbid in crosses:\n", " asset = SecurityMaster.get_asset(bbid, AssetIdentifier.BLOOMBERG_ID)\n", " long_implied = impvol.get_data(start_date , end_date, bbid=bbid, tenor=long_tenor, deltaStrike=deltaStrike, location=location)[['impliedVolatility']]\n", " short_implied = impvol.get_data(start_date , end_date, bbid=bbid, tenor=short_tenor, deltaStrike=deltaStrike, location=location)[['impliedVolatility']]\n", " spread = (long_implied - short_implied)*100\n", " spread = spread.dropna()\n", " \n", " boxplt[bbid] = spread['impliedVolatility']\n", " data[bbid] = {'Current': last_value(spread['impliedVolatility']),\n", " f'{history} Min': min(spread['impliedVolatility']),\n", " f'{history} Max': max(spread['impliedVolatility']),\n", " '%-ile': last_value(percentiles(spread['impliedVolatility'])),\n", " }\n", " df = pd.DataFrame(data)\n", " vol_cal_screen = df.transpose()\n", " vol_cal_screen = vol_cal_screen.sort_values(by=['Current'], ascending=False).style.background_gradient(subset=['Current']).format(\"{:.2f}\")\n", " if boxplot:\n", " f, ax = plt.subplots(figsize=(7, 6))\n", " ax = sns.boxplot(data=boxplt, orient=\"h\", palette=\"mako\").set_title('Spread Ranges')\n", " return vol_cal_screen" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#assumes tenors are ordered from shortest to longest\n", "def steepest_flattest(crosses, tenors, top=10):\n", " impvol = Dataset('FXIMPLIEDVOL')\n", " vols = pd.DataFrame()\n", " screen = pd.DataFrame(columns=['Cross', 'Tenors', 'Vol Far', 'Vol Near', 'Abs Spread', '%-ile'])\n", " for bbid in crosses:\n", " asset = SecurityMaster.get_asset(bbid, AssetIdentifier.BLOOMBERG_ID)\n", " for tenor in tenors:\n", " imp_vol = impvol.get_data(start , end, bbid=bbid, tenor=tenor, deltaStrike='DN', location='NYC')['impliedVolatility']\n", " vols[bbid + f'_{tenor}'] = imp_vol\n", " for x in tenors[:-1]:\n", " for y in tenors[1:]:\n", " if(x != y):\n", " vol_far = vols[bbid + f'_{y}']\n", " vol_near = vols[bbid + f'_{x}']\n", " spread = vol_far - vol_near\n", " row = {'Cross' : bbid, 'Tenors': f'{y}' +f'- {x}', 'Vol Far': last_value(vol_far)*100, 'Vol Near': last_value(vol_near)*100, 'Abs Spread' : last_value(spread), \n", " '%-ile': last_value(percentiles(spread))}\n", " screen = screen.append(row, ignore_index=True) \n", " display(screen.sort_values(by=['%-ile'], ascending=False).head(top).style.set_caption(f'{top} Steepest Calendars').background_gradient(subset=['%-ile']))\n", " display(screen.sort_values(by=['%-ile'], ascending=True).head(top).style.set_caption(f'{top} Flattest Calendars').background_gradient(subset=['%-ile']))\n", " return screen" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## FX Volatility Calendar Spread\n", "\n", "The FX Structuring team uses this analysis to screen for the steepest and flattest spreads between 1y implied volatility - 3m implied volatility.\n", "\n", "We are pulling [GS FX Implied Volatility](https://marquee.gs.com/s/developer/datasets/FXIMPLIEDVOL) by default. If you are looking for additional history or coverage, please see our premium version [link](https://marquee.gs.com/s/developer/datasets/FXIMPLIEDVOL_PREMIUM)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.datetime import business_day_offset\n", "from dateutil.relativedelta import relativedelta\n", "# Inputs to modify\n", "\n", "g10 = ['USDJPY', 'EURUSD', 'AUDUSD', 'GBPUSD', 'USDCAD', 'USDNOK', 'NZDUSD', 'USDSEK', 'USDCHF']\n", "\n", "end = business_day_offset(date.today(), -1, roll='forward')\n", "start = business_day_offset(end - relativedelta(years=2), -1, roll='forward')\n", "\n", "\n", "long_tenor = '1y'\n", "short_tenor = '3m'\n", "history = '2y'\n", "\n", "\n", "\n", "screen = vol_calendar_screen(crosses=g10, long_tenor='1y', short_tenor='3m', start_date=start, end_date=end, boxplot=True)\n", "screen" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Steepest & Flattest Calendars\n", "\n", "The FX Structuring team uses this analysis to screen for the steepest calendars, both in absolute terms and in percentile terms across multiple tenors. The steepest and flattest calendars are sorted by the percentile of calendar vs. its own history." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tenors = ['3m', '6m', '1y', '2y']\n", "result = steepest_flattest(crosses=g10, tenors=tenors, top=10)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/reports_and_screens/00_fx/0002_forward_vol_screen.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',)) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Forward Volatility Screen Functions" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.data import Dataset\n", "from gs_quant.timeseries import last_value, forward_vol, VolReference\n", "from gs_quant.markets.securities import AssetIdentifier, SecurityMaster\n", "import pandas as pd\n", "pd.options.display.float_format = '{:,.2f}'.format \n", "import warnings\n", "from datetime import date\n", "import matplotlib.pyplot as plt\n", "warnings.filterwarnings('ignore')\n", "\n", "def forward_volatility_screen(crosses, tenor, forward_tenor, start_date, end_date, plot=True):\n", " data = {}\n", " #replace with premium dataset for more history\n", " impvol = Dataset('FXIMPLIEDVOL')\n", " for bbid in crosses:\n", " asset = SecurityMaster.get_asset(bbid, AssetIdentifier.BLOOMBERG_ID)\n", " fwd_vol = forward_vol(asset, tenor=tenor, forward_start_date=forward_tenor, strike_reference=VolReference.DELTA_NEUTRAL, relative_strike=0)\n", " change = fwd_vol.diff(5)\n", " implied = impvol.get_data(start_date , end_date, bbid=bbid, tenor=tenor, deltaStrike='DN', location='NYC')[['impliedVolatility']]\n", " data[bbid] = {f'{tenor}{forward_tenor} Fwd Vol': last_value(fwd_vol),\n", " '1w Change': last_value(change),\n", " f'{tenor} Implied Vol': last_value(implied['impliedVolatility'])*100,\n", " 'Roll-down': last_value(fwd_vol) - last_value(implied['impliedVolatility'])*100,\n", " 'Strike to Lows': min(implied['impliedVolatility'])*100 - last_value(fwd_vol),\n", " 'Strike to Highs': max(implied['impliedVolatility'])*100 - last_value(fwd_vol),\n", " 'Diff' : (max(implied['impliedVolatility'])*100 - last_value(fwd_vol)) + (min(implied['impliedVolatility'])*100 - last_value(fwd_vol))\n", " }\n", " df = pd.DataFrame(data)\n", " fva_screen = df.transpose()\n", " fva_screen = fva_screen.sort_values(by=['Roll-down'])\n", " \n", " if plot:\n", " for fx in fva_screen.index:\n", " plt.scatter(fva_screen.loc[fx]['Roll-down'], fva_screen.loc[fx]['Diff'])\n", " plt.legend(fva_screen.index,loc='best', bbox_to_anchor=(0.9, -0.13), ncol=3)\n", " plt.xlabel('Carry (vols)')\n", " plt.ylabel('Upside vs Downside')\n", " plt.title('Carry vs Upside/Downside')\n", " plt.show()\n", " return fva_screen.style.background_gradient(subset=['Roll-down']).format(\"{:.2f}\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Forward Volatility\n", "The FX Structuring desk uses this analysis to screen for the most attractive 6m forward 6m vols in FVA format\n", "\n", "We are pulling [GS FX Implied Volatility](https://marquee.gs.com/s/developer/datasets/FXIMPLIEDVOL) by default. If you are looking for additional history or coverage, please see our premium version [link](https://marquee.gs.com/s/developer/datasets/FXIMPLIEDVOL_PREMIUM)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Carry vs Upside/Downside\n", "\n", "Note: Upper left corner shows attractive long vol crosses with low rolldown and high\n", "upside vs. downside difference. Lower right corner shows attractive short vol crosses\n", "with high carry and low upside vs. downside difference.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.datetime import business_day_offset\n", "from dateutil.relativedelta import relativedelta\n", "\n", "g10 = ['USDJPY', 'EURUSD', 'AUDUSD', 'GBPUSD', 'USDCAD', 'USDNOK', 'NZDUSD', 'USDSEK', 'USDCHF']\n", "EM = ['USDBRL', 'USDZAR', 'USDMXN', 'USDCLP','USDPLN', 'USDCZK', 'USDHUF', 'USDSGD', 'USDINR','USDKRW','USDSGD', 'USDINR',\n", " 'USDTWD', 'USDTRY', 'USDPHD']\n", "\n", "\n", "forward_tenor = '6m'\n", "tenor = '6m'\n", "\n", "end = business_day_offset(date.today(), -1, roll='forward')\n", "start = business_day_offset(end - relativedelta(years=2), -1, roll='forward')\n", "\n", "screen = forward_volatility_screen(g10, tenor, forward_tenor, start, end, plot=True)\n", "screen" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/reports_and_screens/00_fx/0003_10x_binary_screen.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',)) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 10x Binaries\n", "\n", "This screen shows how far a OTMS a 3m binary option must be struck to have a payout ratio of approximately 10:1. \n", "Normalized = % OTMS / 3m realized vol" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.timeseries import last_value, realized_volatility\n", "from gs_quant.markets.securities import AssetIdentifier, SecurityMaster\n", "from gs_quant.markets import PricingContext\n", "from gs_quant.instrument import FXBinary\n", "from gs_quant.target.common import OptionType\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.risk import FXSpot, Price\n", "from gs_quant.data import DataContext\n", "from gs_quant.api.gs.data import * \n", "import pandas as pd\n", "import warnings\n", "from datetime import date\n", "import matplotlib.pyplot as plt\n", "from gs_quant.datetime import business_day_offset\n", "from dateutil.relativedelta import relativedelta\n", "warnings.filterwarnings('ignore')\n", "pd.set_option('display.precision', 3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Functions\n", "\n", "1) Calculating payout for binaries using defaults of 3m and 10:1\n", "\n", "\n", "2) Plot historical spot with call and put strikes" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def payout_struck_binaries(crosses, payout_ratio='10%', tenor='3m', normalize=True, start_date=business_day_offset(business_day_offset(date.today(), -1, roll='forward') - relativedelta(years=1), -1, roll='forward')):\n", " \"\"\"Prices FX Binaries for inputted crosses strike price using inputted payout ratio\n", " \n", " Screens for % OTMS for a given payout ratio\n", " \n", " : param crosses: array of string FX pairs e.g. ['USDJPY', 'USDNOK']\n", " : param payout_ratio: string with % used to solve for strike price e.g 10% solves for a 10:1 payout ratio\n", " : param tenor: FX Binary expiration date\n", " : param normalize: divides % OTMS by realized volatility \n", " : param start_date: start date for volatility history, if normalizing % OTMS, defaults to 1yr\n", " \n", " \"\"\"\n", " binaries = Portfolio([FXBinary(pair=cross, expiration_date=tenor, option_type=direction, \n", " strike_price=f'p={payout_ratio}', premium=0) \n", " for direction in ('Call', 'Put') for cross in crosses])\n", " with PricingContext(): \n", " binaries.resolve()\n", " spot = binaries.calc(FXSpot) \n", " bf = binaries.to_frame()\n", " bf.index = bf.index.droplevel(0)\n", " bf = bf[['pair', 'option_type', 'strike_price']]\n", " bf['spot'] = list(spot)\n", " bf['% otms'] = abs(bf.strike_price/bf.spot-1)*100\n", " \n", " #normalizing otms\n", " if normalize:\n", " df_vol = pd.DataFrame(columns=['pair','implied_vol'])\n", " for cross in crosses:\n", " asset = SecurityMaster.get_asset(cross, AssetIdentifier.BLOOMBERG_ID)\n", " with DataContext(start=start_date, end=business_day_offset(date.today(), -1, roll='forward')): \n", " vol = last_value(realized_volatility(asset, w=tenor))\n", " df_vol = df_vol.append({'pair': f'{cross[:3]} {cross[3:]}', 'implied_vol': vol}, ignore_index=True)\n", " bf = bf.merge(df_vol, left_on='pair', right_on='pair')\n", " bf['normalized'] = bf['% otms'] / bf['implied_vol']\n", " return bf.set_index(['option_type', 'pair']).sort_values(by=['option_type', '% otms'])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def plot_strikes(crosses, binaries, start_date=business_day_offset(business_day_offset(date.today(), -1, roll='forward') - relativedelta(years=2), -1, roll='forward')):\n", " \"\"\"Plots historical spot for each FX pair and strike prices from FX Binary \n", " \n", " : param crosses: array of string FX pairs e.g. ['USDJPY', 'USDNOK']\n", " : param binaries: Dataframe with crosses as index with strikes for both call and put FX Binary\n", " : param start: start date for spot history, defaults to 2y\n", " \n", " \"\"\"\n", " for cross in binaries.index.get_level_values('pair'):\n", " with DataContext(start=start_date, end=business_day_offset(date.today(), -1, roll='forward')): \n", " asset = SecurityMaster.get_asset(cross.replace(\" \", \"\"), AssetIdentifier.BLOOMBERG_ID)\n", " q = GsDataApi.build_market_data_query(\n", " [asset.get_marquee_id()],\n", " QueryType.SPOT,\n", " source=None,\n", " real_time=False\n", " )\n", " spot = GsDataApi.get_market_data(q)\n", " spot.plot()\n", " plt.axhline(y=binaries.loc[(OptionType.Call, f'{cross}')]['strike_price'], color='g', label='Call Strike')\n", " plt.axhline(y=binaries.loc[(OptionType.Put, f'{cross}')]['strike_price'], color='r', label='Put Strike')\n", " plt.title(f'{cross} Binary')\n", " plt.legend()\n", " plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 10X Binary Screen" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": true } }, "outputs": [], "source": [ "g10 = ['USDJPY', 'EURUSD', 'AUDUSD', 'GBPUSD', 'USDCAD', 'USDNOK', 'NZDUSD', 'USDSEK', 'USDCHF']\n", "\n", "results = payout_struck_binaries(crosses=g10)\n", "results.style.background_gradient(subset=['% otms'])" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": true } }, "outputs": [], "source": [ "plot_strikes(g10, results, start_date=start_date)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/reports_and_screens/00_fx/0004_bear_put_spread_screen.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": { "pycharm": { "is_executing": true } }, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',)) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Costless 1x2 Put Spreads \n", "\n", "This screen looks for max payouts and breakeven levels for 1x2 zero cost put spreads for a given tenor. Top strike is fixed at the specified delta and the bottom strike is solved to make the structure zero-cost." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.markets import PricingContext\n", "from gs_quant.instrument import FXOption\n", "from gs_quant.risk import FXSpot\n", "from gs_quant.common import BuySell\n", "import pandas as pd; pd.set_option('display.precision', 2)\n", "\n", "def get_screen(df_portfolio, strike_label='40d'):\n", " screen = pd.pivot_table(df_portfolio, values='strike_price', index=['pair', 'Spot'], columns=['buy_sell'])\n", " screen = screen.reset_index('Spot')\n", " upper_label, lower_label = f'Upper Strike ({strike_label})', 'Lower Strike (Mid)*'\n", " screen = screen.rename(columns={BuySell.Buy: upper_label, BuySell.Sell: lower_label})\n", " screen['Max Payout'] = screen[upper_label] / screen[lower_label] - 1\n", " screen['Lower Breakeven (Mid)'] = 2 * screen[lower_label] - screen[upper_label]\n", " screen['Lower Breakeven OTMS'] = abs(screen['Lower Breakeven (Mid)'] / screen['Spot'] - 1)\n", " return screen\n", "\n", "def calculate_bear_put_spreads(upper_strike='40d', crosses=['USDNOK'], tenor='3m'):\n", " portfolio = Portfolio()\n", " for cross in crosses:\n", " option_type = 'Put' if cross[:3] == 'USD' else 'Call'\n", " upper_leg = FXOption(pair=f'{cross}', strike_price=upper_strike, notional_amount=10e6, \n", " option_type=option_type, buy_sell=BuySell.Buy, expiration_date=tenor, premium_currency=cross[:3])\n", " lower_leg = FXOption(pair=f'{cross}', strike_price=f'P={abs(upper_leg.premium)}', notional_amount=20e6,\n", " option_type=option_type, buy_sell=BuySell.Sell, expiration_date=tenor)\n", " portfolio.append((upper_leg, lower_leg))\n", " with PricingContext():\n", " portfolio.resolve()\n", " spot = portfolio.calc(FXSpot)\n", " summary = portfolio.to_frame()\n", " summary['Spot'] = list(spot.result())\n", " return get_screen(summary, upper_strike)" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
buy_sell Spot Upper Strike (40d) Lower Strike (Mid)* Max Payout Lower Breakeven (Mid) Lower Breakeven OTMS
pair
USD NOK8.498.378.182.29%7.995.81%
USD SEK8.338.248.091.82%7.944.70%
USD JPY105.79104.84103.031.75%101.234.31%
USD CHF0.900.890.871.55%0.864.05%
USD CAD1.271.261.241.45%1.223.66%
NZD USD0.720.730.74-1.10%0.743.34%
AUD USD0.770.780.79-1.28%0.803.77%
EUR USD1.211.221.24-1.85%1.264.73%
GBP USD1.391.411.44-2.33%1.475.74%
" ], "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "g10 = ['USDJPY', 'EURUSD', 'AUDUSD', 'GBPUSD', 'USDCAD', 'USDNOK', 'NZDUSD', 'USDSEK', 'USDCHF']\n", "\n", "result = calculate_bear_put_spreads(upper_strike='40d', crosses=g10, tenor='3m')\n", "result.sort_values(by='Max Payout', ascending=False).style.format({\n", " 'Max Payout': '{:,.2%}'.format, 'Lower Breakeven OTMS': '{:,.2%}'.format}).background_gradient(\n", " subset=['Max Payout', 'Lower Breakeven OTMS'])" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/reports_and_screens/00_fx/0005_vol_skew.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',)) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Volatility Skew & Smile Screen\n", "\n", "This screen looks at historical implied volatility for 3m 25 delta options. The screen shows either the volatility smile or skew. The delta strike, option tenor and history can be adjusted. Relative refers to the vol adjusted results.\n", "\n", "Volatility skew or risk reversal refers to 25d call vol - 25d put vol. Volatility smile or butterflys refer to (25d call vol + 25d put vol) / 2 - ATM vol." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "from gs_quant.data import Dataset\n", "import pandas as pd\n", "pd.set_option('display.precision', 2)\n", "\n", "def volatility_skew(bbids, start_date, end_date, tenor='3m', history='2y', strike_level='25D', smile=False, relative=False, plot=True):\n", " \"\"\"\n", " Screens for Volatility Skew \n", " defaults to risk reversal (25d call vol - 25d put vol)\n", " \n", " : param crosses: array of string FX pairs e.g. ['USDJPY', 'USDNOK']\n", " : param start_date: start date for volatility history\n", " : param end_date: end date for volatility history\n", " : param tenor: volatility tenor\n", " : param history: label for historical calculations \n", " : param strike_level: defaults to 25D\n", " : param smile: if true, calculates butterfly else displays risk reversal\n", " : param relative: if true, skew is divided by ATM Vol\n", " \"\"\"\n", " \n", " impvol_data = {}\n", " data = {}\n", " strikes = [f'{strike_level}C', f'{strike_level}P', 'DN']\n", " for cross in bbids:\n", " for strike in strikes:\n", " #replace with premium dataset for more history\n", " impvol_data[f'{cross}_{strike}'] = Dataset('FXIMPLIEDVOL').get_data(start_date, end_date, bbid=cross, tenor=tenor, deltaStrike=strike, location='NYC')['impliedVolatility']*100\n", " if smile:\n", " data[cross] = (impvol_data[f'{cross}_{strike_level}C'] + impvol_data[f'{cross}_{strike_level}P'])/2 - impvol_data[f'{cross}_DN']\n", " #risk reversal\n", " else:\n", " data[cross] = (impvol_data[f'{cross}_{strike_level}C'] - impvol_data[f'{cross}_{strike_level}P'])\n", " if relative:\n", " data[cross] = data[cross] / impvol_data[f'{cross}_DN']\n", " df = pd.DataFrame(data)\n", " return get_screen(df, plot=plot, smile=smile, history=history)" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "from gs_quant.timeseries import last_value, change, percentiles\n", "import matplotlib.pyplot as plt\n", "\n", "def get_screen(data, strike_label='25D', history='2y', plot=True, smile=False):\n", " screen = {}\n", " for cross in data.columns:\n", " screen[cross] = {'Current': last_value(data[cross]),\n", " '1w Change': last_value(change(data[cross].tail(5))),\n", " f'{history} Min': min(data[cross]),\n", " f'{history} Max': max(data[cross]),\n", " '%-ile': last_value(percentiles(data[cross]))\n", " }\n", " vol_screen = pd.DataFrame(screen).T\n", " \n", " if plot:\n", " fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(15, 5))\n", " vol_screen = vol_screen.sort_values('%-ile')\n", " label = 'Smile' if smile else 'Skew'\n", " top = vol_screen.iloc[-1]\n", " data[top.name].plot(ax=axes[0], title=f'Highest %-ile {label} ({top.name})')\n", " bottom = vol_screen.iloc[0]\n", " data[bottom.name].plot(ax=axes[1], title=f'Lowest %-ile {label} ({bottom.name})')\n", " return vol_screen" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Risk Reversal\n", "\n", "Risk reversals refer to 25d call vol - 25d put vol. Relative risk reversals are computed as risk reversals divided by ATM vol." ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3m 25D Risk Reversal\n" ] }, { "data": { "text/html": [ "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Current 1w Change 2y Min 2y Max %-ile
USDJPY-0.990.06-9.03-0.7899.21
GBPUSD-0.640.29-4.210.2187.72
USDSEK1.13-0.05-0.733.8379.21
EURUSD0.350.03-3.372.4978.42
USDCHF-0.60-0.09-3.26-0.2376.53
USDNOK1.520.13-0.296.1672.18
AUDUSD-1.11-0.03-8.08-0.4440.20
NZDUSD-1.00-0.07-8.12-0.3337.52
USDCAD0.250.040.144.7012.97
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "3m 25D Risk Reversal (Relative)\n" ] }, { "data": { "text/html": [ "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Current 1w Change 2y Min 2y Max %-ile
USDJPY-0.160.01-0.65-0.1499.31
GBPUSD-0.080.03-0.270.0287.62
USDCHF-0.09-0.02-0.32-0.0580.50
USDSEK0.120.00-0.060.2477.92
USDNOK0.120.01-0.030.2971.39
EURUSD0.050.01-0.270.2670.79
AUDUSD-0.11-0.01-0.47-0.0751.58
NZDUSD-0.10-0.01-0.48-0.0444.26
USDCAD0.040.010.020.393.66
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "from datetime import date\n", "from gs_quant.datetime import business_day_offset\n", "from dateutil.relativedelta import relativedelta\n", "end = business_day_offset(date.today(), -1, roll='forward')\n", "start = business_day_offset(end - relativedelta(years=2), -1, roll='forward')\n", "\n", "\n", "g10 = ['USDJPY', 'EURUSD', 'AUDUSD', 'GBPUSD', 'USDCAD', 'USDNOK', 'NZDUSD', 'USDSEK', 'USDCHF']\n", "tenor='3m'\n", "strike_level='25D'\n", "\n", "risk_reversal = volatility_skew(g10, start_date=start, end_date=end, tenor=tenor, strike_level=strike_level)\n", "relative_rr = volatility_skew(g10, start_date=start, end_date=end, tenor=tenor, strike_level=strike_level, relative=True, plot=False)\n", "print(f'{tenor} {strike_level} Risk Reversal')\n", "display(risk_reversal.sort_values('%-ile', ascending=False).style.background_gradient(subset=['%-ile']))\n", "\n", "print(f'{tenor} {strike_level} Risk Reversal (Relative)')\n", "display(relative_rr.sort_values('%-ile', ascending=False).style.background_gradient(subset=['%-ile']))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Smile \n", "\n", "Butterflys refer to (25d call vol + 25d put vol) / 2 - ATM vol. Relative BF are computed as BF divided by ATM vol." ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3m 25D Smile\n" ] }, { "data": { "text/html": [ "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Current 1w Change 2y Min 2y Max %-ile
USDNOK0.44-0.030.141.5063.27
NZDUSD0.350.010.191.5765.94
AUDUSD0.340.000.191.5765.15
USDSEK0.34-0.010.131.1180.69
GBPUSD0.28-0.010.161.3623.17
USDJPY0.270.000.211.3338.61
USDCHF0.240.000.151.2568.22
USDCAD0.23-0.010.111.1458.91
EURUSD0.22-0.030.121.4262.08
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "3m 25D Smile (Relative)\n" ] }, { "data": { "text/html": [ "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Current 1w Change 2y Min 2y Max %-ile
USDJPY0.040.000.030.1141.88
USDCHF0.040.000.020.1164.85
NZDUSD0.040.000.020.0869.01
AUDUSD0.040.000.020.0866.83
USDNOK0.04-0.000.020.0559.50
USDSEK0.040.000.020.0780.30
USDCAD0.03-0.000.020.1058.32
EURUSD0.03-0.000.020.1250.79
GBPUSD0.030.000.020.0839.90
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "vol_smile = volatility_skew(g10, start_date=start, end_date=end, tenor=tenor,smile=True)\n", "relative_smile = volatility_skew(g10, start_date=start, end_date=end, tenor=tenor, strike_level=strike_level, smile=True, relative=True, plot=False)\n", "print(f'{tenor} {strike_level} Smile')\n", "display(vol_smile.sort_values('Current', ascending=False).style.background_gradient(subset=['Current']))\n", "\n", "print(f'{tenor} {strike_level} Smile (Relative)')\n", "display(relative_smile.sort_values('Current', ascending=False).style.background_gradient(subset=['Current']))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/reports_and_screens/00_fx/0006_vol_swap_spread.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": { "pycharm": { "is_executing": true } }, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics')) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Volatility Swap Spread Screen" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Functions" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "pycharm": { "is_executing": true } }, "outputs": [], "source": [ "from gs_quant.datetime import business_day_offset\n", "from gs_quant.datetime.relative_date import RelativeDate\n", "from gs_quant.timeseries import implied_volatility, VolReference, last_value\n", "from datetime import date\n", "from gs_quant.data import Dataset, DataContext \n", "def get_data(bbids=['USDJPY', 'AUDUSD'], long_tenor='6m', end=business_day_offset(date.today(), -1, roll='forward'), start=RelativeDate('-5y').apply_rule()):\n", " \"\"\"Pulls historical implied volatility and realized volatility data \n", " \n", " : param bbids: array of string FX pairs e.g. ['USDJPY', 'USDNOK']\n", " : param long_tenor: implied vol tenor\n", " : param end: end date for historical data\n", " : param start: start date for historical data\n", " \n", " \"\"\"\n", " #implied vol\n", " vol_data = Dataset('FXIMPLIEDVOL').get_data(start, end, bbid=bbids, tenor=long_tenor, deltaStrike='DN', location='NYC')\n", " vol_df = pd.pivot_table(vol_data, values='impliedVolatility', index=['date'], columns=['bbid'])\n", " vol_df = vol_df*100\n", " \n", " shift_start = RelativeDate(f'-{long_tenor[0]}v', base_date=start).apply_rule()\n", " #internal users replace with 'WMFXSPOT'\n", " spot_data = Dataset('FXSPOT').get_data(shift_start, end, bbid=bbids)\n", " #replace with 'midPrice' if using WMFXSPOT\n", " spot_df = pd.pivot_table(spot_data, values='spot', index=['date'], columns=['bbid'])\n", " return vol_df, spot_df" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "pycharm": { "is_executing": true } }, "outputs": [], "source": [ "from gs_quant.timeseries import returns, percentile, filter_, FilterOperator, sum_, sqrt, union\n", "def robust_volatility(series, tenor='6m', cutoff=5, pct=[90, 10]):\n", " ret = returns(1/series, 1)\n", " std_rob = last_value((percentile(ret, pct[0], tenor) - percentile(ret, pct[1], tenor))/2)\n", " zap = filter_(abs(ret), FilterOperator.GREATER, 5*std_rob)\n", " combined = union([zap, (0*ret)/ret])\n", " zap_count = sum_(zap, tenor)\n", " days = int(tenor[0])*21\n", " var = sqrt((252/(days - zap_count))*sum_(combined**2, tenor))*100\n", " return var" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "from gs_quant.timeseries import volatility, returns, Returns, exponential_std, annualize, std\n", "def calculate_realized_vol(spot_df, tenor='6m'):\n", " \"\"\"Calculates realized vol using historical spot data\n", " \n", " : param tenor: realized vol tenor\n", " \"\"\"\n", " weighting = (22-1)/(22+1)\n", " short_vol, long_vol, robust_vol = {}, {}, {}\n", " \n", " for ccy, row in spot_df.iteritems():\n", " long_vol[ccy] = volatility(row, tenor, returns_type=Returns.LOGARITHMIC)\n", " short_vol[ccy] = annualize(returns(row,1, Returns.LOGARITHMIC).ewm(alpha=1-weighting, adjust=True).std())*100\n", " robust_vol[ccy] = robust_volatility(row, tenor)\n", " \n", " return pd.DataFrame.from_dict(long_vol), pd.DataFrame.from_dict(short_vol), pd.DataFrame.from_dict(robust_vol)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "pycharm": { "is_executing": true } }, "outputs": [], "source": [ "from gs_quant.timeseries import LinearRegression\n", "import pandas as pd\n", "pd.set_option('display.precision', 2)\n", "import warnings\n", "import itertools\n", "def calculate_vol_swap_screen(bbids=['USDJPY', 'AUDUSD'], long_tenor='6m', \n", " end=business_day_offset(date.today(), -1, roll='forward'), \n", " start=RelativeDate('-5y').apply_rule()):\n", " \"\"\"Calculates volatility swap spread screen\n", " \n", " : param bbids: array of string FX pairs e.g. ['USDJPY', 'USDNOK']\n", " : param long_tenor: implied vol and realized vol tenor\n", " : param short_tenor: shorter realized vol tenor\n", " : param end: end date for historical data\n", " : param start: start date for historical data\n", " \"\"\"\n", " \n", " vol_df, spot_df = get_data(bbids, long_tenor, end, start)\n", " long_rvol, short_rvol, robust_vol = calculate_realized_vol(spot_df, long_tenor)\n", " results = pd.DataFrame(columns=['crosses', '6m Implied spread', 'Beta', f'Entry vs {long_tenor} Z-score', \n", " 'Avg Carry Z-score', 'Score', f'{long_tenor} Realized Vol', f'{long_tenor} Carry', f'{long_tenor} 5y Avg', f'{long_tenor} 10th', \n", " f'{long_tenor} 90th', '1m Realized Vol', '1m Carry'])\n", " pairs = itertools.combinations(crosses, 2)\n", " for pair in pairs:\n", " short, long = pair[0], pair[1]\n", " beta = LinearRegression(vol_df[short], vol_df[long], fit_intercept=False).coefficient(1)\n", " iv_spread = vol_df.iloc[-1][long] - beta*vol_df.iloc[-1][short]\n", " rv_long_spread = long_rvol[long] - beta*long_rvol[short] \n", " rv_short_spread = short_rvol[long] - beta*short_rvol[short]\n", " robust_spread = robust_vol[long] - beta*robust_vol[short]\n", " z_score = (robust_spread.mean() - iv_spread)/robust_spread.std()\n", " carry_long = rv_long_spread[-1] - iv_spread\n", " carry_short = rv_short_spread[-1] - iv_spread\n", " carry_avg = (carry_long + carry_short)/2\n", " carry_zscore = carry_avg / robust_spread.std()\n", " results = results.append({'crosses': f'{long} vs. {short}', '6m Implied spread': iv_spread, 'Beta': beta, f'Entry vs {long_tenor} Z-score': z_score,\n", " 'Avg Carry Z-score': carry_zscore, 'Score': z_score + carry_zscore, f'{long_tenor} Realized Vol': rv_long_spread[-1], \n", " f'{long_tenor} Carry': carry_long,f'{long_tenor} 5y Avg': robust_spread.mean(), \n", " f'{long_tenor} 10th': rv_long_spread.quantile(0.1), f'{long_tenor} 90th': rv_long_spread.quantile(0.9), \n", " '1m Realized Vol': rv_short_spread[-1], '1m Carry': carry_short}, ignore_index=True)\n", " return results.set_index('crosses').sort_values('6m Implied spread')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Vol Swap Spread Screen\n", "\n", "This screen compares the historical realized volatility spread between two crosses with the implied volatility spread. The spread is beta adjusted on the short leg. The score is the combination of the entry vs 6m z-score and the average carry z-score (long carry + 1m carry). The volatility spreads with highest score are more attractive to buy." ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "pycharm": { "is_executing": true } }, "outputs": [ { "data": { "text/html": [ "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
6m Implied spread Beta Entry vs 6m Z-score Avg Carry Z-score Score 6m Realized Vol 6m Carry 6m 5y Avg 6m 10th 6m 90th 1m Realized Vol 1m Carry
crosses
GBPUSD vs. AUDUSD-1.961.001.571.022.60-0.041.920.52-2.263.87-0.651.30
GBPUSD vs. NZDUSD-1.550.951.020.982.000.281.840.07-1.623.63-0.251.31
USDNOK vs. AUDUSD0.951.07-0.421.961.543.922.970.41-0.924.073.112.15
USDJPY vs. AUDUSD-2.750.901.29-0.061.22-2.400.35-0.30-3.143.56-3.34-0.59
USDSEK vs. USDMXN-0.670.680.640.391.030.331.000.21-3.091.49-0.580.09
GBPUSD vs. USDCHF0.281.290.290.690.991.291.010.79-1.484.771.641.35
USDJPY vs. NZDUSD-2.430.850.98-0.090.89-2.150.28-0.73-2.742.81-3.01-0.58
USDSEK vs. AUDUSD-0.610.960.610.260.870.020.630.29-1.862.16-0.460.15
GBPUSD vs. USDCAD-0.791.290.520.250.770.931.720.15-1.863.90-1.60-0.81
USDNOK vs. NZDUSD1.401.01-0.901.560.664.292.89-0.05-1.314.463.552.15
GBPUSD vs. EURUSD0.391.270.150.390.551.531.130.69-1.524.540.760.37
USDSEK vs. NZDUSD-0.220.910.060.220.280.330.55-0.13-2.271.90-0.070.15
USDJPY vs. USDCHF-0.841.170.73-0.530.19-1.30-0.47-0.16-1.272.49-1.37-0.53
USDJPY vs. USDCAD-1.801.170.85-0.91-0.06-1.630.17-0.72-1.931.92-4.28-2.48
USDMXN vs. AUDUSD0.241.380.01-0.13-0.12-0.31-0.560.26-2.804.080.310.07
USDNOK vs. USDCHF3.411.36-1.020.81-0.215.422.010.78-2.007.055.602.19
USDCHF vs. USDCAD-0.821.000.32-0.54-0.21-0.270.55-0.48-1.821.08-2.49-1.67
USDNOK vs. USDJPY4.471.15-1.140.90-0.257.022.551.07-3.327.717.252.78
USDSEK vs. USDNOK-1.400.890.76-1.05-0.28-3.42-2.02-0.02-4.301.88-3.17-1.77
USDCAD vs. EURUSD0.930.98-0.620.29-0.330.48-0.450.44-0.371.711.840.91
USDNOK vs. USDCAD2.271.36-0.910.56-0.365.022.750.09-2.045.932.18-0.09
USDJPY vs. EURUSD-0.741.140.48-0.86-0.38-1.10-0.35-0.25-1.612.51-2.16-1.42
USDNOK vs. EURUSD3.521.33-1.120.64-0.475.662.140.66-2.196.544.671.15
USDMXN vs. NZDUSD0.821.31-0.52-0.13-0.650.16-0.66-0.33-3.424.570.890.07
USDSEK vs. USDJPY2.471.04-0.940.28-0.662.720.250.81-3.103.053.210.74
USDSEK vs. USDCHF1.511.23-0.65-0.01-0.661.28-0.240.54-1.362.771.710.20
USDCHF vs. EURUSD0.090.98-0.25-0.54-0.790.180.10-0.07-0.790.91-0.67-0.76
USDJPY vs. GBPUSD-0.880.880.17-0.99-0.82-2.22-1.34-0.62-2.502.57-2.64-1.76
USDSEK vs. USDCAD0.501.23-0.37-0.47-0.830.940.44-0.06-3.021.72-1.36-1.86
NZDUSD vs. USDCHF1.951.35-0.73-0.25-0.981.08-0.870.77-1.072.881.990.04
USDNOK vs. GBPUSD3.221.04-1.540.42-1.124.190.960.08-4.915.053.990.76
USDMXN vs. USDJPY4.731.50-1.10-0.03-1.123.64-1.101.05-4.257.455.630.90
USDMXN vs. USDCHF3.381.77-0.86-0.27-1.131.58-1.800.68-2.966.793.500.12
USDSEK vs. EURUSD1.611.21-0.87-0.32-1.191.49-0.120.44-1.412.210.88-0.73
AUDUSD vs. USDCHF2.271.28-1.12-0.24-1.361.37-0.900.31-1.713.222.310.04
NZDUSD vs. USDCAD0.811.36-0.53-0.86-1.390.68-0.130.08-1.692.11-1.41-2.22
USDMXN vs. USDCAD1.881.77-0.74-0.65-1.391.05-0.83-0.22-4.865.62-0.96-2.84
NZDUSD vs. EURUSD2.061.32-0.89-0.55-1.451.32-0.740.67-1.212.671.07-0.98
USDMXN vs. EURUSD3.511.73-1.03-0.49-1.521.88-1.630.53-2.786.602.28-1.22
USDMXN vs. USDNOK-0.941.290.40-1.97-1.57-5.33-4.39-0.22-2.633.07-3.66-2.72
USDSEK vs. GBPUSD1.360.94-1.00-0.79-1.790.19-1.18-0.06-5.312.030.27-1.09
AUDUSD vs. EURUSD2.371.25-1.31-0.52-1.831.59-0.780.20-1.822.891.44-0.93
AUDUSD vs. NZDUSD0.400.95-1.82-0.08-1.900.33-0.08-0.44-1.040.560.410.00
AUDUSD vs. USDCAD1.181.28-1.11-0.84-1.950.99-0.20-0.35-2.022.59-0.92-2.10
USDMXN vs. GBPUSD3.141.35-1.35-0.99-2.34-0.01-3.16-0.20-5.664.471.41-1.73
" ], "text/plain": [ "" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "crosses = ['EURUSD', 'USDCAD', 'USDCHF', 'NZDUSD', 'AUDUSD', 'GBPUSD', 'USDJPY' , 'USDNOK', 'USDMXN', 'USDSEK']\n", "screen = calculate_vol_swap_screen(crosses)\n", "screen.sort_values('Score', ascending=False).style.background_gradient(subset=['Score'])" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/content/reports_and_screens/00_fx/0007_eq_fx_hedges.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 1, "id": "changing-buyer", "metadata": { "pycharm": { "is_executing": true } }, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(client_id=None, client_secret=None,scopes=('read_product_data', 'run_analytics')) " ] }, { "cell_type": "code", "execution_count": 36, "id": "prostate-identifier", "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import FXOption, EqOption\n", "from gs_quant.data import Dataset\n", "from gs_quant.timeseries import last_value, correlation, percentiles, volatility, Returns\n", "from gs_quant.risk import FXSpot\n", "from gs_quant.datetime.relative_date import RelativeDate\n", "from gs_quant.markets.portfolio import Portfolio\n", "import pandas as pd\n", "import seaborn as sns\n", "import matplotlib.pyplot as plt\n", "import warnings\n", "warnings.filterwarnings('ignore')\n", "pd.set_option('display.precision', 2)" ] }, { "cell_type": "markdown", "id": "mounted-atlas", "metadata": {}, "source": [ "## EQ FX Hedging Screen\n", "\n", "We screen for the most attractive FX options to hedge an equity index such as S&P. For each 3m FX option, we the direction is chosen based on historical 1y correlations using weekly returns. The strikes and notionals of the FX options are adjusted by the 6m realized vol ratio to an equity index. \n", "\n", "Realized Correlation uses weekly returns with the currency value in USD and percentile uses a 5y history. 6m realized volatility ratio uses weekly returns. Strike price is spot + FX / SPX vol ratio. Strike and Spot are in normal spot convention. Discount to SPX is FX Option premium / SPX price. Past performance is not indicative of future results. " ] }, { "cell_type": "code", "execution_count": 35, "id": "signal-ground", "metadata": {}, "outputs": [], "source": [ "def calculate_screen(eq_ric, fx_crosses, start=RelativeDate('-5y').apply_rule(), end=RelativeDate('-1b').apply_rule()):\n", " fxspot_data = Dataset('WMFXSPOT').get_data(start, end, bbid=fx_crosses)\n", " fxspot_df = pd.pivot_table(fxspot_data, values='midPrice', index=['date'], columns=['bbid']).resample('W-FRI').last()\n", " eq = Dataset('TREOD').get_data(start, end, ric=eq_ric).closePrice.resample('W-FRI').last()\n", " eq_vol = last_value(volatility(eq, 24))\n", " cors = pd.DataFrame({bbid: correlation(eq, 1/fxspot_df[bbid] if bbid[0] == 'U' else fxspot_df[bbid] , 52) for bbid in fx_crosses})\n", " cur_cor = pd.Series(cors.tail(1).squeeze()*100, name='1y Realized Correlation (%)')\n", " pct_cor = pd.Series({bbid: last_value(percentiles(cors[bbid])) for bbid in fx_crosses}, name='Corr %-ile')\n", " vol_ = pd.DataFrame({bbid: volatility(fxspot_df[bbid], 24, Returns.LOGARITHMIC) for bbid in fx_crosses})\n", " vol_cur = pd.Series(vol_.tail(1).squeeze() / eq_vol , name=f'6m Realized Vol Ratio (FX / {eq_ric})')\n", " table = pd.concat([cur_cor, pct_cor, vol_cur ], axis=1)\n", " \n", " #price options\n", " eqo = EqOption(option_type='Put', underlier=eq_ric, exchange='NYSE', strike_price='90%', expiration_date='3m', buy_sell='Buy', premium=0)\n", " eqo.resolve()\n", " notional = eqo.strike_price * eqo.multiplier\n", " portfolio = Portfolio()\n", " for cross in fx_crosses:\n", " ratio = table.loc[cross][f'6m Realized Vol Ratio (FX / {eq_ric})']\n", " if cross[0] != 'U':\n", " cross = f'USD{cross[:3]}'\n", " portfolio.append(FXOption(pair=cross, option_type='Call', expiration_date='3m', strike_price=f's+{ratio*10}%', buy_sell='Buy', \n", " notional_amount=notional/ratio))\n", " portfolio.resolve()\n", " port_df = portfolio.to_frame()\n", " port_df['Cost in bps'] = (port_df['premium'] / port_df['notional_amount'])*1e4\n", " port_df[f'Discount to {eq_ric} (%)'] =(abs(port_df['premium'])/eqo.price() - 1)*100\n", " port_df['Spot'] = list(portfolio.calc(FXSpot).result())\n", " port_df['pair'] = port_df['pair'].str.replace(' ', '')\n", " port_df['pair_'] = g10\n", " port_df = port_df.set_index('pair_')\n", " port_df['Strike'] = [1 / port_df.loc[x]['strike_price'] if x != port_df.loc[x]['pair'] else port_df.loc[x]['strike_price'] for x in port_df.index]\n", " result = table.join([port_df['Strike'], port_df['Spot'],port_df['Cost in bps'],port_df[f'Discount to {eq_ric} (%)']])\n", " return result.sort_values(by=['1y Realized Correlation (%)', f'Discount to {eq_ric} (%)'], ascending=(False, True))" ] }, { "cell_type": "code", "execution_count": 29, "id": "effective-validity", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
1y Realized Correlation (%) Corr %-ile 6m Realized Vol Ratio (FX / .N225) Strike Spot Cost in bps Discount to .N225 (%)
AUDUSD73.2280.710.670.720.77-34.22-67.38
USDNOK69.6582.620.949.318.51-36.53-75.05
USDCAD69.5083.570.411.331.28-24.64-60.91
NZDUSD67.3890.710.570.680.72-44.89-49.14
GBPUSD58.7587.860.581.301.37-18.03-79.88
USDSEK58.5378.810.698.978.39-22.40-79.09
EURUSD50.3086.430.451.151.20-10.95-84.34
USDCHF41.0787.380.460.940.90-9.08-87.23
USDJPY15.1199.760.33108.65105.18-17.39-66.10
" ], "text/plain": [ "" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "start = RelativeDate('-5y').apply_rule()\n", "end = RelativeDate('-1b').apply_rule()\n", "g10 = ['USDJPY', 'EURUSD', 'AUDUSD', 'GBPUSD', 'USDCAD', 'USDNOK', 'NZDUSD', 'USDSEK', 'USDCHF']\n", "\n", "#example equity index rics '.FTSE','.N225','.SPX' or '.STOXX50E'\n", "eq_ric = '.N225'\n", "table = calculate_screen(eq_ric='.N225', fx_crosses=g10)\n", "table.style.background_gradient(\n", " subset=['1y Realized Correlation (%)', f'Discount to {eq_ric} (%)'])" ] }, { "cell_type": "code", "execution_count": 34, "id": "labeled-language", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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7bfffvLxxx8/XnHf3//+94V5eXlHf/vb33b+9ttvs9q0aePS3B0eKRlZ11gykojIfZ4oGYlNb1iwblokio77IbhVKfpPzkav+5pklaqGVDKSiIgas1735TfVZOwO3nMmIiLyMkzOREREXobJmYiIyMswORMREXkZJmciIiIvw+RMREQNWn2UjASAKVOmtI6Li0tOSEhI6tq1a+KsWbMuzrGdk5NjNpvNPadPn35JJarIyMhu8fHxSfHx8UkdO3ZMfuihh9pVNZ93ZZiciYio0appycjp06dHfP755803b968Kysra+dXX32123FekPnz57fs3r372ffff/+yoh3r1q3bs2fPnp1btmzZdeDAAf+RI0fGuBu3R5KziDwlItki8qPxGWy0x4rIOYf2OZ6Ij4iI6sd7u9+zDFg8oFtKekrqgMUDur23+z2vLBn54osvtnnttdcO2efKtlgs1gkTJpy0b//+++9bZsyYcfjYsWO++/fvd1p2KjQ01Jqenn5o9erVLY4dO+ZWzUhP9pxfVNUexmelQ/t+h/YHPBYdERHVqfd2v2eZvml6zIlzJ/wUihPnTvhN3zQ9pj4TdE1KRubn55vOnj3rk5SU5HTO73379vnm5eX5DhgwoPiWW24pmD9/fqXxWywWa2RkZOmOHTvcKn7BYW0iIroi5mydE1l6ofSSvFN6odQ0Z+scry0Z6cz8+fMtt9xySwEA3HPPPflLliyp8sdFTabJ9uT0neNFZBSADAD/q6oFRnuciPwA4DSAv6nq1x6LkIiI6szJcyedVmaqrN1VlZWMjIuLKwFsJSOTk5PzJk6cmBcWFtajupKRFovFGhQUZN25c6efs97zkiVLLHl5eb5Lly61AMDx48d9MzMz/bt161ZScduCggLT0aNH/bp16+Z0OL0y9dZzFpE1IrLdyWcogNkAOgLoASAHwAvGbjkAolX1KgATASwUkeaVHH+MiGSISEZeXl59XQYREdWRsMAwp8PElbW7qj5KRj7yyCM5DzzwQEx+fr4JAAoLC02zZs0K27Ztm//Zs2d9jh8/vi07OzszOzs7c/z48bnp6emX9Z4LCwtNo0ePjrnxxhtPRUREuFSNyq7ees6qOsiV7UTkdQArjH1KAJQY3zeLyH4A8bD1rise/zUArwG2qlR1FDYREdWTB7o/kD190/QYx6FtPx8/6wPdH/C6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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "sns.scatterplot(x=table['1y Realized Correlation (%)'], y=table[f'Discount to {eq_ric} (%)'], hue=table.index, s=60)\n", "plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/content/reports_and_screens/00_fx/__init__.py ================================================ ================================================ FILE: gs_quant/content/reports_and_screens/00_fx/vol_screen_app.py ================================================ from gs_quant.data import Dataset from gs_quant.timeseries import percentiles, volatility, last_value from gs_quant.datetime import business_day_offset import seaborn as sns import pandas as pd import matplotlib.pyplot as plt import warnings from datetime import date import streamlit as st from gs_quant.session import GsSession warnings.filterwarnings('ignore') sns.set(style="darkgrid", color_codes=True) # external users should substitute their client id and secret; please skip this step if using internal jupyterhub GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',)) def format_df(data_dict): df = pd.concat(data_dict, axis=1) df.columns = data_dict.keys() return df.ffill().dropna() def volatility_screen(crosses, start_date, end_date, tenor='3m', plot=True): fxspot_dataset, fxvol_dataset = Dataset('FXSPOT'), Dataset('FXIMPLIEDVOL') spot_data, impvol_data, spot_fx, data = {}, {}, {}, {} for cross in crosses: spot = fxspot_dataset.get_data(start_date, end_date, bbid=cross)[['spot']].drop_duplicates(keep='last') spot_fx[cross] = spot['spot'] spot_data[cross] = volatility(spot['spot'], tenor) # realized vol vol = fxvol_dataset.get_data(start_date, end_date, bbid=cross, tenor=tenor, deltaStrike='DN', location='NYC')[ ['impliedVolatility']] impvol_data[cross] = vol.drop_duplicates(keep='last') * 100 spdata, ivdata = format_df(spot_data), format_df(impvol_data) diff = ivdata.subtract(spdata).dropna() for cross in crosses: data[cross] = {'Spot': last_value(spot_fx[cross]), '{} Implied'.format(tenor): last_value(ivdata[cross]), '{} Realized'.format(tenor): last_value(spdata[cross]), 'Diff': last_value(diff[cross]), 'Historical Implied Low': min(ivdata[cross]), 'Historical Implied High': max(ivdata[cross]), '%-ile': last_value(percentiles(ivdata[cross])) } df = pd.DataFrame(data) vol_screen = df.transpose() st.write(st.dataframe(vol_screen.style.highlight_max(axis=0))) if plot: for fx in vol_screen.index: plt.scatter(vol_screen.loc[fx]['%-ile'], vol_screen.loc[fx]['Diff']) plt.legend(vol_screen.index, loc='best', bbox_to_anchor=(0.9, -0.13), ncol=3) plt.xlabel('Percentile of Current Implied Vol') plt.ylabel('Implied vs Realized Vol') plt.title('Entry Point vs Richness') st.pyplot(plt) return g10 = ['USDJPY', 'EURUSD', 'AUDUSD', 'GBPUSD', 'USDCAD', 'USDNOK', 'NZDUSD', 'USDSEK', 'USDCHF'] end = business_day_offset(date.today(), -1, roll='preceding') st.title('Vol Monitor') crosses = st.multiselect('FX Crosses', g10, default='EURUSD') tenors = st.selectbox('Tenor', ('1m', '3m', '6m', '1y')) start = st.slider("Start Date", value=(date(2018, 1, 1), end), format="MM/DD/YY") volatility_screen(crosses, start[0], start[1], tenors) ================================================ FILE: gs_quant/content/reports_and_screens/02_rates/0000_vol_fixed_strike_grid.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 3, "id": "potential-davis", "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',)) " ] }, { "cell_type": "markdown", "id": "developmental-private", "metadata": {}, "source": [ "### IR Implied Volatility Screen (Fixed Strike)\n", "\n", "In this screen, we compare the implied volatility of swaptions at a fixed strike across a range of expiries and tenors in a grid." ] }, { "cell_type": "code", "execution_count": 4, "id": "tight-resistance", "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import IRSwaption\n", "from gs_quant.common import PayReceive\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.risk import IRAnnualImpliedVol\n", "import matplotlib.pyplot as plt\n", "import pandas as pd\n", "import seaborn as sns\n", "pd.options.display.float_format = '{:,.0f}'.format\n", "\n", "def implied_vol_grid(expiries, termination, pay_rec, ccy, moneyness):\n", " portfolios = Portfolio([Portfolio([IRSwaption(pay_or_receive=pay_rec, notional_currency=ccy, termination_date=t, \n", " expiration_date=e, strike=f'ATMF+{moneyness}') for e in expiries]) for t in termination])\n", " portfolios.resolve()\n", " results = portfolios.calc(IRAnnualImpliedVol)\n", "\n", " frame = results.to_frame('value','portfolio_name_0','instrument_name') * 10000\n", " frame.columns=expiries\n", " frame.index=termination\n", " plt.subplots(figsize=(16, 10))\n", " ax = sns.heatmap(frame, annot=True, fmt='.2f', cmap='coolwarm')\n", " ax.set(ylabel='Expiries', xlabel='Tenor', title='Implied Vol Grid (bps) w. '+ str(moneyness)+'bps moneyness')\n", " ax.xaxis.tick_top()\n", " ax.xaxis.set_label_position('top') " ] }, { "cell_type": "code", "execution_count": 5, "id": "ceramic-marketing", "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "expiries = ['1m', '3m' ,'6m', '9m', '1y', '2y', '3y', '4y', '5y', '6y', '7y', '8y', '9y', '10y', '15y', '20y', '25y', '30y']\n", "termination = ['1y', '2y', '3y', '4y', '5y', '6y', '7y', '8y', '9y', '10y', '15y', '20y', '25y', '30y']\n", "pay_rec = PayReceive.Pay\n", "ccy = 'EUR'\n", "moneyness = 0\n", "\n", "implied_vol_grid(expiries, termination, pay_rec, ccy, moneyness)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/content/reports_and_screens/02_rates/0001_vol_moneyness_screen.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 3, "id": "biblical-positive", "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',)) " ] }, { "cell_type": "markdown", "id": "identified-radical", "metadata": {}, "source": [ "### IR Implied Volatility Across Strikes\n", "\n", "We look at how the implied vol changes for swaptions with varying strikes but fixed expiries." ] }, { "cell_type": "code", "execution_count": 2, "id": "expired-continuity", "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import IRSwaption\n", "from gs_quant.common import PayReceive\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.risk import IRAnnualImpliedVol\n", "import matplotlib.pyplot as plt\n", "import pandas as pd\n", "import numpy as np\n", "pd.options.display.float_format = '{:,.0f}'.format\n", "\n", "def moneyness_curve(expiration, termination, pay_rec, ccy, min_bps, max_bps, step_size):\n", " eval_range =np.arange(min_bps, max_bps + step_size, step_size).tolist()\n", " num_instr = len(termination)\n", " results = pd.DataFrame(index = eval_range)\n", " for i in range(num_instr):\n", " portfolios = Portfolio([IRSwaption(pay_or_receive=pay_rec, notional_currency=ccy, termination_date=termination[i], \n", " expiration_date=expiration[i], strike=f'ATMF+{eval_K}') for eval_K in eval_range])\n", " portfolios.resolve()\n", " name_i = expiration[i] + termination[i] +' '+ ccy +' '+ pay_rec\n", " results[name_i] = portfolios.calc(IRAnnualImpliedVol).to_frame().values * 10000 \n", "\n", " plt.figure(figsize=(8, 5))\n", " plt.plot(results)\n", " plt.xlabel('Moneyness (bps)')\n", " plt.ylabel('Implied Ann. Volatility (bps)')\n", " plt.legend(results.columns)" ] }, { "cell_type": "code", "execution_count": 4, "id": "confused-medication", "metadata": {}, "outputs": [ { "data": { "image/png": "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a9aMUaNGXRtr/IknnmDmzJkAHD58mIyMDCIiIgo9j1KKBx98kKioKKZMmQIYpdzy5ctf+xIwY8aM255pbNSoUbz77ru0bt2aV155hSNHjhR4bK1atcjNzeXixYsFHpOWlsaaNWto2rQpffr0YevWrezevZuGDRsybdo0ypYtS4cOHfjxxx8BmDdvHn369LH5y4oQQthTamYOI2Zs5UR8KlMej6ZpWHmzQyqQa1SVLx8P5/cW7zkrN4Vu79zRW5955hleffVVlFK8+uqr/N///R/Tp0+nf//+vPXWW7z//vtMnz6dESNGFHmu33//napVq3Lx4kW6dOlCgwYNaN++PaNHj2bGjBl89NFHfPPNN2zZsuWW916dwjO/bZGRkRw/fpyVK1eyevVqWrZsycaNG2978JNjx44RGRmJUoqePXvSrVs3fv31V1555RUuX75MSkoKXbt2BYwJVt577z169erFjBkzmDp16m1dSwgh7CEjO5cxX25j79krfDa4BW3rFO+jyeLmGom7hAkJCbm2/uSTT/LII48AUKpUKbp06cKSJUuYP38+27dvL/JcV6uzg4OD6d27N1u2bKF9+/b07duXCRMm0KlTJ6KioqhUqdIt761UqdK10j7cOq1nmTJl6NOnD3369MHDw4Nly5blm7iPHz+Op6fntelJ87r6jDuvESNGsHjxYiIiIpg5c+a1qUnbtm3LyZMnWbt2Lbm5ubc06hNCCEfLybXw/NydrD+awIf9I3ioSWWzQyqSayTuOywZ28vVubgBvvvuuxsS1OjRo3n00Ue57777CpzV66rU1FQsFgtly5YlNTWVlStX8tprrwHg5+dH165deeaZZ67NSnazDh06MGfOHB544AGUUsyaNYuOHTsCsH79eho1akTFihXJyspi//7916bfzCsuLo6nn36asWPH5luCz09ycjKhoaFkZ2fz1Vdf3fAs/fHHH2fw4MG8+uqrNp1LCCHsxWLRvPjtHlbuv8Abjzaib1SY2SHZxDUSt0kGDRrE2rVriY+PJywsjAkTJvDEE0/w4osvsmvXLpRS1KhRg8mTJ197T1RUFOXKlbvhmfT58+eJjo4mKSkJDw8PPvnkE/bv3098fDy9e/cGICcnh8GDB/PQQw9de9+QIUP47rvvePDBB/ONb8yYMRw8eJCIiAiUUkRHR1+b4OTYsWM888wzaK2x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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "expiration = ['10y','15y']\n", "termination = ['10y', '15y']\n", "pay_rec = 'Pay'\n", "ccy = 'USD'\n", "min_bps = -300\n", "max_bps = 300\n", "step_size = 20\n", "\n", "moneyness_curve(expiration, termination, pay_rec, ccy, min_bps, max_bps, step_size)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/content/reports_and_screens/02_rates/0002_swaption_carry_grid.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 9, "id": "separated-loading", "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',)) " ] }, { "cell_type": "markdown", "id": "bacterial-tutorial", "metadata": {}, "source": [ "### Swaption Carry Grid\n", "\n", "In this screen, we compare carry of swaptions for a fixed strike and for a fixed horizon across a range of expiries and tenors." ] }, { "cell_type": "code", "execution_count": 21, "id": "objective-superintendent", "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import IRSwaption\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.risk import RollFwd\n", "import pandas as pd\n", "import seaborn as sns\n", "pd.options.display.float_format = '{:,.0f}'.format\n", "\n", "def carry_grid(horizon, expiries, termination, pay_rec, ccy, moneyness):\n", " portfolios = Portfolio([Portfolio([IRSwaption(pay_or_receive=pay_rec, notional_currency=ccy, termination_date=t, \n", " expiration_date=e, strike=f'ATMF+{moneyness}') for e in expiries]) for t in termination])\n", " portfolios.resolve()\n", "\n", " price = portfolios.price()\n", " price_df = price.to_frame()\n", "\n", " with RollFwd(horizon):\n", " price_fwd = portfolios.price()\n", "\n", " price_fwd_df = price_fwd.to_frame()\n", " carry = 100*(price_fwd_df - price_df)/price_df\n", " carry.index = termination #Keep ordering\n", " carry.columns= expiries\n", " \n", " ##plot\n", " carry = carry.T\n", " plt.subplots(figsize=(12, 6))\n", " ax = sns.heatmap(carry, annot=True, fmt='.2f', cmap='coolwarm')\n", " ax.set(ylabel='Expiries', xlabel='Tenor', title= horizon + ' carry w. '+ str(moneyness)+'bps moneyness, %')\n", " ax.xaxis.tick_top()\n", " return carry" ] }, { "cell_type": "code", "execution_count": 22, "id": "better-saint", "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "horizon = '1y'\n", "expiries = ['4y', '5y', '6y', '7y', '8y', '9y', '10y', '15y', '20y', '25y', '30y']\n", "termination = ['1y', '2y', '3y', '4y', '5y', '6y', '7y', '8y', '9y', '10y', '15y', '20y', '25y', '30y']\n", "pay_rec = 'Pay'\n", "ccy = 'USD'\n", "moneyness = 100\n", "\n", "carry = carry_grid(horizon, expiries, termination, pay_rec, ccy, moneyness)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/content/reports_and_screens/02_rates/0003_spot_vol_shock.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 3, "id": "funny-creature", "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',)) " ] }, { "cell_type": "markdown", "id": "placed-uzbekistan", "metadata": {}, "source": [ "#### Combined IR and Vol Shock\n", "\n", "In this screen, we look at how the price fo a swaption changes under a range of spot and vol rate shocks." ] }, { "cell_type": "code", "execution_count": 32, "id": "cardiac-knitting", "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import IRSwaption\n", "from gs_quant.common import PayReceive\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.markets import PricingContext\n", "from gs_quant.risk import MarketDataShockBasedScenario, MarketDataPattern, MarketDataShock, MarketDataShockType, Price\n", "import pandas as pd\n", "import seaborn as sns\n", "import matplotlib.pyplot as plt\n", "\n", "def IR_scenario_grid(ccy, IR_min=-200, IR_max=200, IR_iter=50, vol_min=-10, vol_max=10, vol_iter=2):\n", " portfolio = Portfolio(IRSwaption(pay_or_receive=pay_rec, notional_currency=ccy, termination_date=termination, expiration_date=expiry, strike=f'ATMF+{moneyness}'))\n", " portfolio.resolve()\n", "\n", " IR_shocks = list(range(IR_min, IR_max + IR_iter, IR_iter))\n", " IR_vol_shocks = list(range(vol_min, vol_max + vol_iter, vol_iter))\n", " ir_vol_grid = pd.DataFrame(index = IR_vol_shocks, columns = IR_shocks)\n", " grid = {}\n", "\n", " base_price = portfolio.price().to_frame()\n", "\n", " with PricingContext(is_batch=True, is_async=True):\n", " for IR_shock in IR_shocks:\n", " vals = []\n", " for IR_vol_shock in IR_vol_shocks:\n", " shocks = MarketDataShockBasedScenario(shocks={\n", " MarketDataPattern('IR', ccy) : MarketDataShock(shock_type=MarketDataShockType.Absolute, value= IR_shock/10000),\n", " MarketDataPattern('IR Vol'): MarketDataShock(MarketDataShockType.Absolute, IR_vol_shock/10000)\n", " })\n", "\n", " with shocks:\n", " shock_price = portfolio.price()\n", " vals.append(shock_price)\n", " grid[IR_shock] = vals\n", "\n", " for k,v in grid.items():\n", " ir_vol_grid[k] = pd.DataFrame(v, index=IR_vol_shocks)\n", " result = 100*(ir_vol_grid/base_price[Price][0] -1)\n", " plt.subplots(figsize=(12, 6))\n", " ax = sns.heatmap(result.astype(float), annot=True, fmt='.2f', cmap='coolwarm')\n", " ax.set(ylabel='IR shocks (bps)', xlabel='IR Vol shocks (bps)', title=' Scenario Analysis: PV effect of IR and IR Vol shocks, %')\n", " ax.xaxis.tick_top()\n", " return result" ] }, { "cell_type": "code", "execution_count": 33, "id": "classified-rates", "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "expiry = '10y'\n", "termination = '10y'\n", "pay_rec = 'Pay'\n", "ccy = 'USD'\n", "horizon = '0b'\n", "moneyness = 50\n", "\n", "#Specify shocks\n", "IR_min = -200\n", "IR_max = 200\n", "IR_iter = 50\n", "vol_min = -10\n", "vol_max = 10\n", "vol_iter = 2\n", "\n", "result = IR_scenario_grid(ccy, IR_min, IR_max, IR_iter, vol_min, vol_max, vol_iter)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/content/reports_and_screens/03_prime/0001_gs_pb_data_case_study_sector_positioning.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "91187d9d", "metadata": {}, "source": [ "# Hedge Fund Positioning" ] }, { "cell_type": "markdown", "id": "18550fb1", "metadata": {}, "source": [ "The Federal Reserve signalled a major shift in monetary policy stance at the end of 2021. How has hedge fund positioning changed in a rising rates environment?\n", "We start by fetching global positioning data from January 2020 from our Prime Book Flows and Exposures (PBFA) dataset." ] }, { "cell_type": "code", "execution_count": null, "id": "649e79c0", "metadata": { "ExecuteTime": { "end_time": "2023-06-28T10:58:01.243652Z", "start_time": "2023-06-28T10:57:55.650450Z" } }, "outputs": [], "source": [ "# Import libraries\n", "import datetime\n", "from gs_quant.data import Dataset\n", "from gs_quant.session import GsSession, Environment\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "code", "execution_count": null, "id": "c593d046", "metadata": { "ExecuteTime": { "end_time": "2023-06-28T10:58:01.258652Z", "start_time": "2023-06-28T10:58:01.247656Z" } }, "outputs": [], "source": [ "# Graphical formatting\n", "import matplotlib.dates as mdates\n", "import matplotlib.ticker as mtick\n", "half_year_locator = mdates.MonthLocator(interval=6)\n", "year_month_formatter = mdates.DateFormatter(\"%Y-%m\") # four digits for year, two for month\n", "pct_formatter = mtick.PercentFormatter(xmax=1,decimals=0)" ] }, { "cell_type": "code", "execution_count": null, "id": "587c8116", "metadata": { "ExecuteTime": { "end_time": "2023-06-28T11:34:58.144247Z", "start_time": "2023-06-28T11:34:57.852239Z" } }, "outputs": [], "source": [ "# Authentication\n", "client_id = # \n", "client_secret = # \n", "GsSession.use(Environment.PROD, client_id, client_secret, ('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "id": "ba1401e0", "metadata": { "ExecuteTime": { "end_time": "2023-06-28T11:35:05.797524Z", "start_time": "2023-06-28T11:34:59.373295Z" } }, "outputs": [], "source": [ "# Fetch positioning data from January 2020\n", "ds = Dataset('PBFA')\n", "data = ds.get_data(datetime.date(2020, 1, 1))\n", "data = data.reset_index()\n", "data.date = data.date.apply(lambda x: x.date())" ] }, { "cell_type": "markdown", "id": "f563a2c6", "metadata": {}, "source": [ "We can then focus on sector rotation. We expect rising rates to have a significant impact on growth companies because their valuation is sensitive to the discount rate applied to earnings expected in the distant future.\n", "Sector positioning is available by selecting the appropriate GICS level 1 sectors and displaying the values in the “clientNetWeight” column." ] }, { "cell_type": "code", "execution_count": null, "id": "e11201f1", "metadata": { "ExecuteTime": { "end_time": "2023-06-28T11:35:20.353939Z", "start_time": "2023-06-28T11:35:19.948926Z" }, "scrolled": false }, "outputs": [], "source": [ "# Plot weight of Technology, Energy and Financials sectors\n", "glob_pos = data.loc[(data.clientStrategy==\"All\") & (data.region==\"Global\") & (data.sectorGICS2==\"All\") & \\\n", " (data.sectorGICS1.isin(['Information Technology','Financials','Energy'])) ]\n", "glob_pos = glob_pos.pivot(index=\"date\", columns=\"sectorGICS1\", values=\"clientNetWeight\")\n", "\n", "fig, ax = plt.subplots(figsize=(10, 6))\n", "ax.plot(glob_pos.index, glob_pos['Financials'], 'g-',label='Financials')\n", "ax.plot(glob_pos.index, glob_pos['Information Technology'], 'r-',label='Technology')\n", "ax.plot(glob_pos.index, glob_pos['Energy'], 'b-',label='Energy')\n", "ax.set_title('Global Hedge Fund Positioning')\n", "ax.set_xlabel('Time')\n", "ax.set_ylabel('Prime book weight', color='black')\n", "ax.xaxis.set_major_locator(half_year_locator)\n", "ax.xaxis.set_major_formatter(year_month_formatter)\n", "ax.yaxis.set_major_formatter(pct_formatter)\n", "fig.autofmt_xdate()\n", "ax.legend(loc='upper right')" ] }, { "cell_type": "markdown", "id": "15676525", "metadata": {}, "source": [ "The results suggest that global hedge funds rotated out of Technology stocks from the end of 2021, bringing the sector weight down from 22% to 15%.\n", "The rotation benefitted Financials, which briefly overtook Technology in terms of net weight at the end of 2022, and Energy. Thelatter was driven by soaring energy prices, particularly as the conflict in Ukraine started in February 2022.\n", "The beginning of 2023 saw a partial reversal of this trend, arguably due to cooling inflation expectations and a general improvement in the global macroeconomic outlook." ] }, { "cell_type": "markdown", "id": "fdddb30d", "metadata": {}, "source": [ "Is the pattern driven by any region in particular?\n", "The previous chart considered positions in global equities but we can restrict the analysis to a specific region, e.g. Europe, i.e. focus on equities issued by European firms.\n", "Here we rescale sector weights by the total net weight allocated to Europe in the global PB book so that sector weights sum up to one and are comparable to those displayed in the previous graph." ] }, { "cell_type": "code", "execution_count": null, "id": "7e22eee5", "metadata": { "ExecuteTime": { "end_time": "2023-06-28T11:38:30.931869Z", "start_time": "2023-06-28T11:38:30.657856Z" } }, "outputs": [], "source": [ "# Plot weight of Technology, Energy and Financials sectors in Europe\n", "eur_pos = data.loc[(data.clientStrategy==\"All\") & (data.region==\"Europe\") & (data.sectorGICS2==\"All\") & (data.country==\"All\") &\\\n", " (data.sectorGICS1.isin(['Information Technology','Financials','Energy','All'])) ]\n", "eur_pos = eur_pos.pivot(index=\"date\", columns=\"sectorGICS1\", values=\"clientNetWeight\")\n", "\n", "fig, ax = plt.subplots(figsize=(10, 6))\n", "ax.plot(eur_pos.index, eur_pos['Financials']/eur_pos['All'], 'g-',label='Financials')\n", "ax.plot(eur_pos.index, eur_pos['Information Technology']/eur_pos['All'], 'r-',label='Technology')\n", "ax.plot(eur_pos.index, eur_pos['Energy']/eur_pos['All'], 'b-',label='Energy')\n", "ax.set_title('Hedge Fund Positioning - European Stocks')\n", "ax.set_xlabel('Time')\n", "ax.set_ylabel('Prime book weight', color='black')\n", "ax.xaxis.set_major_locator(half_year_locator)\n", "ax.xaxis.set_major_formatter(year_month_formatter)\n", "ax.yaxis.set_major_formatter(pct_formatter)\n", "fig.autofmt_xdate()\n", "ax.legend(loc='lower right')" ] }, { "cell_type": "markdown", "id": "8fdaa067", "metadata": {}, "source": [ "The chart shows that the weight of Technology declined in Europe between the end of 2021 and the end of 2022, in line with our global results.\n", "Exposure to Energy also displays a marked increase, particularly from February 2022.\n", "Sector allocation is not as concentrated as in the global chart, where Technology exceeds 20% of the total net weight." ] }, { "cell_type": "markdown", "id": "c5b99e40", "metadata": {}, "source": [ "So far we have looked at portfolio weights, which can be affected by differences in return across sectors even if managers keep their holdings unchanged. For example, sustained outperformance of Energy stocks tends to boost the weight of the Energy sector in the portfolio.\n", "Our datasets contain information on trading flows, split into long flows (long buying and long selling) and short flows (short covering and short selling).\n", "Flows are converted into a z-score, which gives an indication of the intensity compared to history.\n", "We extract flows for the Technology sector from January 2020 and display the data using a daily bar plot." ] }, { "cell_type": "code", "execution_count": null, "id": "aaca6f02", "metadata": { "ExecuteTime": { "end_time": "2023-06-28T11:38:56.904856Z", "start_time": "2023-06-28T11:38:53.825749Z" } }, "outputs": [], "source": [ "# Plot long and short flow indicators for global Technology stocks\n", "glob_long_flow = data.loc[(data.clientStrategy==\"All\") & (data.region==\"Global\") & (data.sectorGICS2==\"All\") & \\\n", " (data.sectorGICS1.isin(['Information Technology','Financials','Energy'])) ]\n", "glob_long_flow = glob_long_flow.pivot(index=\"date\", columns=\"sectorGICS1\", values=\"longLevel\")\n", "glob_short_flow = data.loc[(data.clientStrategy==\"All\") & (data.region==\"Global\") & (data.sectorGICS2==\"All\") & \\\n", " (data.sectorGICS1.isin(['Information Technology','Financials','Energy'])) ]\n", "glob_short_flow = glob_short_flow.pivot(index=\"date\", columns=\"sectorGICS1\", values=\"shortLevel\")\n", "\n", "fig, (ax1,ax2) = plt.subplots(2,1,figsize=(10, 6),sharey=True)\n", "ax1.bar(glob_long_flow.index,glob_long_flow['Information Technology'],color=\"blue\",width=4)\n", "ax1.set_title('Long Flows - Technology')\n", "ax1.set_ylabel('Z-score', color='black')\n", "ax2.bar(glob_short_flow.index,glob_short_flow['Information Technology'],color=\"red\",width=4)\n", "ax2.set_ylabel('Z-score', color='black')\n", "ax2.set_title('Short flows - Technology')\n", "fig.autofmt_xdate()" ] }, { "cell_type": "markdown", "id": "e5da9ff7", "metadata": {}, "source": [ "Short flows throughout 2021 and 2022 appear strongly tilted towards short selling, while long flows are more balanced.\n", "The short squeeze of January 2021 is evident in the chart, with risk-off flows (both long selling and short covering) that exceed 5 standard deviations compared to history.\n", "In early 2023 we detect a sharp reversal in the directionality of the flows, with strong short covering in Technology stocks andmuted long flows." ] }, { "cell_type": "markdown", "id": "1a085043", "metadata": {}, "source": [ "## Disclaimers:\n", "Indicative Terms/Pricing Levels: This material may contain indicative terms only, including but not limited to pricing levels. There is no representation that any transaction can or could have been effected at such terms or prices. Proposed terms and conditions are for discussion purposes only. Finalized terms and conditions are subject to further discussion and negotiation.\n", "www.goldmansachs.com/disclaimer/sales-and-trading-invest-rec-disclosures.html If you are not accessing this material via Marquee ContentStream, a list of the author's investment recommendations disseminated during the preceding 12 months and the proportion of the author's recommendations that are 'buy', 'hold', 'sell' or other over the previous 12 months is available by logging into Marquee ContentStream using the link below. Alternatively, if you do not have access to Marquee ContentStream, please contact your usual GS representative who will be able to provide this information to you.\n", "Please refer to https://marquee.gs.com/studio/ for price information of corporate equity securities.\n", "Notice to Australian Investors: When this document is disseminated in Australia by Goldman Sachs & Co. LLC (\"GSCO\"), Goldman Sachs International (\"GSI\"), Goldman Sachs Bank Europe SE (\"GSBE\"), Goldman Sachs (Asia) L.L.C. (\"GSALLC\"), or Goldman Sachs (Singapore) Pte (\"GSSP\") (collectively the \"GS entities\"), this document, and any access to it, is intended only for a person that has first satisfied the GS entities that:\n", "• the person is a Sophisticated or Professional Investor for the purposes of section 708 of the Corporations Act of Australia; and\n", "• the person is a wholesale client for the purpose of section 761G of the Corporations Act of Australia.\n", "To the extent that the GS entities are providing a financial service in Australia, the GS entities are each exempt from the requirement to hold an Australian financial services licence for the financial services they provide in Australia. Each of the GS entities are regulated by a foreign regulator under foreign laws which differ from Australian laws, specifically:\n", "• GSCO is regulated by the US Securities and Exchange Commission under US laws;\n", "• GSI is authorised by the Prudential Regulation Authority and regulated by the Financial Conduct Authority and the Prudential Regulation Authority, under UK laws;\n", "• GSBE is subject to direct prudential supervision by the European Central Bank and in other respects is supervised by the German Federal Financial Supervisory Authority (Bundesanstalt für Finanzdienstleistungsaufischt, BaFin) and Deutsche Bundesbank;\n", "• GSALLC is regulated by the Hong Kong Securities and Futures Commission under Hong Kong laws; and\n", "• GSSP is regulated by the Monetary Authority of Singapore under Singapore laws.\n", "Notice to Brazilian Investors\n", "Marquee is not meant for the general public in Brazil. The services or products provided by or through Marquee, at any time, may not be offered or sold to the general public in Brazil. You have received a password granting access to Marquee exclusively due to your existing relationship with a GS business located in Brazil. The selection and engagement with any of the offered services or products through Marquee, at any time, will be carried out directly by you. Before acting to implement any chosen service or products, provided by or through Marquee you should consider, at your sole discretion, whether it is suitable for your particular circumstances and, if necessary, seek professional advice. Any steps necessary in order to implement the chosen service or product, including but not limited to remittance of funds, shall be carried out at your discretion. Accordingly, such services and products have not been and will not be publicly issued, placed, distributed, offered or negotiated in the Brazilian capital markets and, as a result, they have not been and will not be registered with the Brazilian Securities and Exchange Commission (Comissão de Valores Mobiliários), nor have they been submitted to the foregoing agency for approval. Documents relating to such services or products, as well as the information contained therein, may not be supplied to the general public in Brazil, as the offering of such services or products is not a public offering in Brazil, nor used in connection with any offer for subscription or sale of securities to the general public in Brazil.\n", "The offer of any securities mentioned in this message may not be made to the general public in Brazil. Accordingly, any such securities have not been nor will they be registered with the Brazilian Securities and Exchange Commission (Comissão de Valores Mobiliários) nor has any offer been submitted to the foregoing agency for approval. Documents relating to the offer, as well as the information contained therein, may not be supplied to the public in Brazil, as the offer is not a public offering of securities in Brazil. These terms will apply on every access to Marquee.\n", "Ouvidoria Goldman Sachs Brasil: 0800 727 5764 e/ou ouvidoriagoldmansachs@gs.com\n", "Horário de funcionamento: segunda-feira à sexta-feira (exceto feriados), das 9hs às 18hs.\n", "Ombudsman Goldman Sachs Brazil: 0800 727 5764 and / or ouvidoriagoldmansachs@gs.com\n", "Available Weekdays (except holidays), from 9 am to 6 pm.\n", "Note to Investors in Israel: GS is not licensed to provide investment advice or investment management services under Israeli law.\n", "Notice to Investors in Japan\n", "Marquee is made available in Japan by Goldman Sachs Japan Co., Ltd.\n", "本書は情報の提供を目的としております。また、売却・購入が違法となるような法域での有価証券その他の売却若しくは購入を勧めるものでもありません。ゴールドマン・サックスは本書内の取引又はストラクチャーの勧誘を行うものではございません。これらの取引又はストラクチャーは、社内及び法規制等の承認等次第で実際にはご提供できない場合がございます。\n", "<適格機関投資家限定 転売制限>\n", "ゴールドマン・サックス証券株式会社が適格機関投資家のみを相手方として取得申込みの勧誘(取得勧誘)又は売付けの申込み若しくは買付けの申込みの勧誘(売付け勧誘等)を行う本有価証券には、適格機関投資家に譲渡する場合以外の譲渡が禁止される旨の制限が付されています。本有価証券は金融商品取引法第4条に基づく財務局に対する届出が行われておりません。なお、本告知はお客様によるご同意のもとに、電磁的に交付させていただいております。\n", "<適格機関投資家用資料>\n", "本資料は、適格機関投資家のお客さまのみを対象に作成されたものです。本資料における金融商品は適格機関投資家のお客さまのみがお取引可能であり、適格機関投資家以外のお客さまからのご注文等はお受けできませんので、ご注意ください。 商号等/ゴールドマン・サックス証券株式会社 金融商品取引業者 関東財務局長(金商)第69号\n", "加入協会/ 日本証券業協会、一般社団法人金融先物取引業協会、一般社団法人第二種金融商品取引業協会\n", "本書又はその添付資料に信用格付が記載されている場合、日本格付研究所(JCR)及び格付投資情報センター(R&I)による格付は、登録信用格付業者による格付(登録格付)です。その他の格付は登録格付である旨の記載がない場合は、無登録格付です。無登録格付を投資判断に利用する前に、「無登録格付に関する説明書」(http://www.goldmansachs.com/disclaimer/ratings.html)を十分にお読みください。\n", "If any credit ratings are contained in this material or any attachments, those that have been issued by Japan Credit Rating Agency, Ltd. (JCR) or Rating and Investment Information, Inc. (R&I) are credit ratings that have been issued by a credit rating agency registered in Japan (registered credit ratings). Other credit ratings are unregistered unless denoted as being registered. Before using unregistered credit ratings to make investment decisions, please carefully read \"Explanation Regarding Unregistered Credit Ratings\" (http://www.goldmansachs.com/disclaimer/ratings.html).\n", "Notice to Mexican Investors: Information contained herein is not meant for the general public in Mexico. The services or products provided by or through Goldman Sachs Mexico, Casa de Bolsa, S.A. de C.V. (GS Mexico) may not be offered or sold to the general public in Mexico. You have received information herein exclusively due to your existing relationship with a GS Mexico or any other Goldman Sachs business. The selection and engagement with any of the offered services or products through GS Mexico will be carried out directly by you at your own risk. Before acting to implement any chosen service or product provided by or through GS Mexico you should consider, at your sole discretion, whether it is suitable for your particular circumstances and, if necessary, seek professional advice. Information contained herein related to GS Mexico services or products, as well as any other information, shall not be considered as a product coming from research, nor it contains any recommendation to invest, not to invest, hold or sell any security and may not be supplied to the general public in Mexico.\n", "Notice to New Zealand Investors: When this document is disseminated in New Zealand by Goldman Sachs & Co. LLC (\"GSCO\") , Goldman Sachs International (\"GSI\"), Goldman Sachs Bank Europe SE (\"GSBE\"), Goldman Sachs (Asia) L.L.C. (\"GSALLC\") or Goldman Sachs (Singapore) Pte (\"GSSP\") (collectively the \"GS entities\"), this document, and any access to it, is intended only for a person that has first satisfied; the GS entities that the person is someone:\n", "(i) who is an investment business within the meaning of clause 37 of Schedule 1 of the Financial Markets Conduct Act 2013 (New Zealand) (the \"FMC Act\");\n", "(ii) who meets the investment activity criteria specified in clause 38 of Schedule 1 of the FMC Act;\n", "(iii) who is large within the meaning of clause 39 of Schedule 1 of the FMC Act; or\n", "(iv) is a government agency within the meaning of clause 40 of Schedule 1 of the FMC Act.\n", "No offer to acquire the interests is being made to you in this document. Any offer will only be made in circumstances where disclosure is not required under the Financial Markets Conducts Act 2013 or the Financial Markets Conduct Regulations 2014.\n", "Notice to Swiss Investors: This is marketing material for financial instruments or services. The information contained in this material is for general informational purposes only and does not constitute an offer, solicitation, invitation or recommendation to buy or sell any financial instruments or to provide any investment advice or service of any kind.\n", "THE INFORMATION CONTAINED IN THIS DOCUMENT DOES NOT CONSITUTE, AND IS NOT INTENDED TO CONSTITUTE, A PUBLIC OFFER OF SECURITIES IN THE UNITED ARAB EMIRATES IN ACCORDANCE WITH THE COMMERCIAL COMPANIES LAW (FEDERAL LAW NO. 2 OF 2015), ESCA BOARD OF DIRECTORS' DECISION NO. (9/R.M.) OF 2016, ESCA CHAIRMAN DECISION NO 3/R.M. OF 2017 CONCERNING PROMOTING AND INTRODUCING REGULATIONS OR OTHERWISE UNDER THE LAWS OF THE UNITED ARAB EMIRATES. ACCORDINGLY, THE INTERESTS IN THE SECURITIES MAY NOT BE OFFERED TO THE PUBLIC IN THE UAE (INCLUDING THE DUBAI INTERNATIONAL FINANCIAL CENTRE AND THE ABU DHABI GLOBAL MARKET). THIS DOCUMENT HAS NOT BEEN APPROVED BY, OR FILED WITH THE CENTRAL BANK OF THE UNITED ARAB EMIRATES, THE SECURITIES AND COMMODITIES AUTHORITY, THE DUBAI FINANCIAL SERVICES AUTHORITY, THE FINANCIAL SERVICES REGULATORY AUTHORITY OR ANY OTHER RELEVANT LICENSING AUTHORITIES IN THE UNITED ARAB EMIRATES. IF YOU DO NOT UNDERSTAND THE CONTENTS OF THIS DOCUMENT, YOU SHOULD CONSULT WITH A FINANCIAL ADVISOR. THIS DOCUMENT IS PROVIDED TO THE RECIPIENT ONLY AND SHOULD NOT BE PROVIDED TO OR RELIED ON BY ANY OTHER PERSON." ] } ], "metadata": { "kernelspec": { "display_name": "venv311", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": true, "sideBar": true, "skip_h1_title": false, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": false, "toc_position": {}, "toc_section_display": true, "toc_window_display": true } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/content/reports_and_screens/03_prime/0002_gs_pb_data_case_study_flows.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "e5c22c5b", "metadata": {}, "source": [ "# Short Covering Episodes" ] }, { "cell_type": "markdown", "id": "76a78ef7", "metadata": {}, "source": [ "What was the strongest episode of short covering in our data?\n", "To answer this question we can load the full dataset on hedge fund positioning (PBFA)." ] }, { "cell_type": "code", "execution_count": null, "id": "085ef504", "metadata": { "ExecuteTime": { "end_time": "2023-06-28T11:40:55.678837Z", "start_time": "2023-06-28T11:40:51.175685Z" } }, "outputs": [], "source": [ "# Import libraries\n", "import datetime\n", "from gs_quant.data import Dataset\n", "from gs_quant.session import GsSession, Environment\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "code", "execution_count": null, "id": "d5ecb4de", "metadata": { "ExecuteTime": { "end_time": "2023-06-28T11:40:55.694836Z", "start_time": "2023-06-28T11:40:55.681841Z" } }, "outputs": [], "source": [ "# Graphical formatting\n", "import matplotlib.dates as mdates\n", "import matplotlib.ticker as mtick\n", "from matplotlib.patches import Ellipse" ] }, { "cell_type": "code", "execution_count": null, "id": "c0df50d2", "metadata": { "ExecuteTime": { "end_time": "2023-06-28T11:40:56.066851Z", "start_time": "2023-06-28T11:40:55.697839Z" } }, "outputs": [], "source": [ "# Authentication\n", "client_id = # \n", "client_secret = # \n", "GsSession.use(Environment.PROD, client_id, client_secret, ('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "id": "8147ac4a", "metadata": { "ExecuteTime": { "end_time": "2023-06-28T11:41:04.973171Z", "start_time": "2023-06-28T11:40:56.069853Z" } }, "outputs": [], "source": [ "# Fetch positioning data from 2016\n", "ds = Dataset('PBFA')\n", "data = ds.get_data(datetime.date(2016, 9, 16))\n", "data = data.reset_index()\n", "data.date = data.date.apply(lambda x: x.date())" ] }, { "cell_type": "markdown", "id": "0466390b", "metadata": {}, "source": [ "We then create a rolling one-month average of the short flow indicator (column ‘shortLevel’) and plot its evolution over time, highlighting the all-time high." ] }, { "cell_type": "code", "execution_count": null, "id": "f940ed09", "metadata": { "ExecuteTime": { "end_time": "2023-06-28T11:41:07.969281Z", "start_time": "2023-06-28T11:41:04.975173Z" } }, "outputs": [], "source": [ "# Plot moving average of short flows\n", "long_short_flows = data.loc[(data.clientStrategy==\"All\") & (data.region==\"Global\") & (data.sectorGICS2==\"All\") & \\\n", " (data.sectorGICS1==\"All\"),['date','longLevel','shortLevel']]\n", "long_short_flows = long_short_flows.set_index('date')\n", "long_short_flows_roll = long_short_flows.rolling(21).mean()\n", "\n", "fig, ax = plt.subplots(figsize=(10, 6))\n", "ax.bar(long_short_flows_roll.index, long_short_flows_roll.shortLevel,color='black',width=4)\n", "ax.axhline(y=0,linestyle='-', color='black')\n", "ax.axhline(y=long_short_flows_roll.shortLevel.max(),linestyle='--',color='blue')\n", "ax.set_ylabel('Z-score')\n", "ax.set_title('Average 21-day Short Flows')\n", "ax.add_patch(Ellipse((long_short_flows_roll.shortLevel.idxmax(),long_short_flows_roll.shortLevel.max()),\\\n", " height=0.5,width=100,color='aquamarine',zorder=0))" ] }, { "cell_type": "markdown", "id": "cbe80988", "metadata": {}, "source": [ "Peaks in the series represent periods of intense short covering, including the sharp portfolio rotation of November 2018, the short squeeze of January 2021 and a further peak in November 2022." ] }, { "cell_type": "markdown", "id": "74eb5036", "metadata": {}, "source": [ "By calling long_short_flows_roll.shortLevel.idxmax() we can find out when the highest peak occurred." ] }, { "cell_type": "code", "execution_count": null, "id": "0d1fcae5", "metadata": { "ExecuteTime": { "end_time": "2023-06-28T11:41:07.984281Z", "start_time": "2023-06-28T11:41:07.972284Z" } }, "outputs": [], "source": [ "# When did the peak occur?\n", "long_short_flows_roll.shortLevel.idxmax()" ] }, { "cell_type": "markdown", "id": "4836c17d", "metadata": {}, "source": [ "Disclaimers:\n", "Indicative Terms/Pricing Levels: This material may contain indicative terms only, including but not limited to pricing levels. There is no representation that any transaction can or could have been effected at such terms or prices. Proposed terms and conditions are for discussion purposes only. Finalized terms and conditions are subject to further discussion and negotiation.\n", "www.goldmansachs.com/disclaimer/sales-and-trading-invest-rec-disclosures.html If you are not accessing this material via Marquee ContentStream, a list of the author's investment recommendations disseminated during the preceding 12 months and the proportion of the author's recommendations that are 'buy', 'hold', 'sell' or other over the previous 12 months is available by logging into Marquee ContentStream using the link below. Alternatively, if you do not have access to Marquee ContentStream, please contact your usual GS representative who will be able to provide this information to you.\n", "Please refer to https://marquee.gs.com/studio/ for price information of corporate equity securities.\n", "Notice to Australian Investors: When this document is disseminated in Australia by Goldman Sachs & Co. LLC (\"GSCO\"), Goldman Sachs International (\"GSI\"), Goldman Sachs Bank Europe SE (\"GSBE\"), Goldman Sachs (Asia) L.L.C. (\"GSALLC\"), or Goldman Sachs (Singapore) Pte (\"GSSP\") (collectively the \"GS entities\"), this document, and any access to it, is intended only for a person that has first satisfied the GS entities that:\n", "• the person is a Sophisticated or Professional Investor for the purposes of section 708 of the Corporations Act of Australia; and\n", "• the person is a wholesale client for the purpose of section 761G of the Corporations Act of Australia.\n", "To the extent that the GS entities are providing a financial service in Australia, the GS entities are each exempt from the requirement to hold an Australian financial services licence for the financial services they provide in Australia. Each of the GS entities are regulated by a foreign regulator under foreign laws which differ from Australian laws, specifically:\n", "• GSCO is regulated by the US Securities and Exchange Commission under US laws;\n", "• GSI is authorised by the Prudential Regulation Authority and regulated by the Financial Conduct Authority and the Prudential Regulation Authority, under UK laws;\n", "• GSBE is subject to direct prudential supervision by the European Central Bank and in other respects is supervised by the German Federal Financial Supervisory Authority (Bundesanstalt für Finanzdienstleistungsaufischt, BaFin) and Deutsche Bundesbank;\n", "• GSALLC is regulated by the Hong Kong Securities and Futures Commission under Hong Kong laws; and\n", "• GSSP is regulated by the Monetary Authority of Singapore under Singapore laws.\n", "Notice to Brazilian Investors\n", "Marquee is not meant for the general public in Brazil. The services or products provided by or through Marquee, at any time, may not be offered or sold to the general public in Brazil. You have received a password granting access to Marquee exclusively due to your existing relationship with a GS business located in Brazil. The selection and engagement with any of the offered services or products through Marquee, at any time, will be carried out directly by you. Before acting to implement any chosen service or products, provided by or through Marquee you should consider, at your sole discretion, whether it is suitable for your particular circumstances and, if necessary, seek professional advice. Any steps necessary in order to implement the chosen service or product, including but not limited to remittance of funds, shall be carried out at your discretion. Accordingly, such services and products have not been and will not be publicly issued, placed, distributed, offered or negotiated in the Brazilian capital markets and, as a result, they have not been and will not be registered with the Brazilian Securities and Exchange Commission (Comissão de Valores Mobiliários), nor have they been submitted to the foregoing agency for approval. Documents relating to such services or products, as well as the information contained therein, may not be supplied to the general public in Brazil, as the offering of such services or products is not a public offering in Brazil, nor used in connection with any offer for subscription or sale of securities to the general public in Brazil.\n", "The offer of any securities mentioned in this message may not be made to the general public in Brazil. Accordingly, any such securities have not been nor will they be registered with the Brazilian Securities and Exchange Commission (Comissão de Valores Mobiliários) nor has any offer been submitted to the foregoing agency for approval. Documents relating to the offer, as well as the information contained therein, may not be supplied to the public in Brazil, as the offer is not a public offering of securities in Brazil. These terms will apply on every access to Marquee.\n", "Ouvidoria Goldman Sachs Brasil: 0800 727 5764 e/ou ouvidoriagoldmansachs@gs.com\n", "Horário de funcionamento: segunda-feira à sexta-feira (exceto feriados), das 9hs às 18hs.\n", "Ombudsman Goldman Sachs Brazil: 0800 727 5764 and / or ouvidoriagoldmansachs@gs.com\n", "Available Weekdays (except holidays), from 9 am to 6 pm.\n", "Note to Investors in Israel: GS is not licensed to provide investment advice or investment management services under Israeli law.\n", "Notice to Investors in Japan\n", "Marquee is made available in Japan by Goldman Sachs Japan Co., Ltd.\n", "本書は情報の提供を目的としております。また、売却・購入が違法となるような法域での有価証券その他の売却若しくは購入を勧めるものでもありません。ゴールドマン・サックスは本書内の取引又はストラクチャーの勧誘を行うものではございません。これらの取引又はストラクチャーは、社内及び法規制等の承認等次第で実際にはご提供できない場合がございます。\n", "<適格機関投資家限定 転売制限>\n", "ゴールドマン・サックス証券株式会社が適格機関投資家のみを相手方として取得申込みの勧誘(取得勧誘)又は売付けの申込み若しくは買付けの申込みの勧誘(売付け勧誘等)を行う本有価証券には、適格機関投資家に譲渡する場合以外の譲渡が禁止される旨の制限が付されています。本有価証券は金融商品取引法第4条に基づく財務局に対する届出が行われておりません。なお、本告知はお客様によるご同意のもとに、電磁的に交付させていただいております。\n", "<適格機関投資家用資料>\n", "本資料は、適格機関投資家のお客さまのみを対象に作成されたものです。本資料における金融商品は適格機関投資家のお客さまのみがお取引可能であり、適格機関投資家以外のお客さまからのご注文等はお受けできませんので、ご注意ください。 商号等/ゴールドマン・サックス証券株式会社 金融商品取引業者 関東財務局長(金商)第69号\n", "加入協会/ 日本証券業協会、一般社団法人金融先物取引業協会、一般社団法人第二種金融商品取引業協会\n", "本書又はその添付資料に信用格付が記載されている場合、日本格付研究所(JCR)及び格付投資情報センター(R&I)による格付は、登録信用格付業者による格付(登録格付)です。その他の格付は登録格付である旨の記載がない場合は、無登録格付です。無登録格付を投資判断に利用する前に、「無登録格付に関する説明書」(http://www.goldmansachs.com/disclaimer/ratings.html)を十分にお読みください。\n", "If any credit ratings are contained in this material or any attachments, those that have been issued by Japan Credit Rating Agency, Ltd. (JCR) or Rating and Investment Information, Inc. (R&I) are credit ratings that have been issued by a credit rating agency registered in Japan (registered credit ratings). Other credit ratings are unregistered unless denoted as being registered. Before using unregistered credit ratings to make investment decisions, please carefully read \"Explanation Regarding Unregistered Credit Ratings\" (http://www.goldmansachs.com/disclaimer/ratings.html).\n", "Notice to Mexican Investors: Information contained herein is not meant for the general public in Mexico. The services or products provided by or through Goldman Sachs Mexico, Casa de Bolsa, S.A. de C.V. (GS Mexico) may not be offered or sold to the general public in Mexico. You have received information herein exclusively due to your existing relationship with a GS Mexico or any other Goldman Sachs business. The selection and engagement with any of the offered services or products through GS Mexico will be carried out directly by you at your own risk. Before acting to implement any chosen service or product provided by or through GS Mexico you should consider, at your sole discretion, whether it is suitable for your particular circumstances and, if necessary, seek professional advice. Information contained herein related to GS Mexico services or products, as well as any other information, shall not be considered as a product coming from research, nor it contains any recommendation to invest, not to invest, hold or sell any security and may not be supplied to the general public in Mexico.\n", "Notice to New Zealand Investors: When this document is disseminated in New Zealand by Goldman Sachs & Co. LLC (\"GSCO\") , Goldman Sachs International (\"GSI\"), Goldman Sachs Bank Europe SE (\"GSBE\"), Goldman Sachs (Asia) L.L.C. (\"GSALLC\") or Goldman Sachs (Singapore) Pte (\"GSSP\") (collectively the \"GS entities\"), this document, and any access to it, is intended only for a person that has first satisfied; the GS entities that the person is someone:\n", "(i) who is an investment business within the meaning of clause 37 of Schedule 1 of the Financial Markets Conduct Act 2013 (New Zealand) (the \"FMC Act\");\n", "(ii) who meets the investment activity criteria specified in clause 38 of Schedule 1 of the FMC Act;\n", "(iii) who is large within the meaning of clause 39 of Schedule 1 of the FMC Act; or\n", "(iv) is a government agency within the meaning of clause 40 of Schedule 1 of the FMC Act.\n", "No offer to acquire the interests is being made to you in this document. Any offer will only be made in circumstances where disclosure is not required under the Financial Markets Conducts Act 2013 or the Financial Markets Conduct Regulations 2014.\n", "Notice to Swiss Investors: This is marketing material for financial instruments or services. The information contained in this material is for general informational purposes only and does not constitute an offer, solicitation, invitation or recommendation to buy or sell any financial instruments or to provide any investment advice or service of any kind.\n", "THE INFORMATION CONTAINED IN THIS DOCUMENT DOES NOT CONSITUTE, AND IS NOT INTENDED TO CONSTITUTE, A PUBLIC OFFER OF SECURITIES IN THE UNITED ARAB EMIRATES IN ACCORDANCE WITH THE COMMERCIAL COMPANIES LAW (FEDERAL LAW NO. 2 OF 2015), ESCA BOARD OF DIRECTORS' DECISION NO. (9/R.M.) OF 2016, ESCA CHAIRMAN DECISION NO 3/R.M. OF 2017 CONCERNING PROMOTING AND INTRODUCING REGULATIONS OR OTHERWISE UNDER THE LAWS OF THE UNITED ARAB EMIRATES. ACCORDINGLY, THE INTERESTS IN THE SECURITIES MAY NOT BE OFFERED TO THE PUBLIC IN THE UAE (INCLUDING THE DUBAI INTERNATIONAL FINANCIAL CENTRE AND THE ABU DHABI GLOBAL MARKET). THIS DOCUMENT HAS NOT BEEN APPROVED BY, OR FILED WITH THE CENTRAL BANK OF THE UNITED ARAB EMIRATES, THE SECURITIES AND COMMODITIES AUTHORITY, THE DUBAI FINANCIAL SERVICES AUTHORITY, THE FINANCIAL SERVICES REGULATORY AUTHORITY OR ANY OTHER RELEVANT LICENSING AUTHORITIES IN THE UNITED ARAB EMIRATES. IF YOU DO NOT UNDERSTAND THE CONTENTS OF THIS DOCUMENT, YOU SHOULD CONSULT WITH A FINANCIAL ADVISOR. THIS DOCUMENT IS PROVIDED TO THE RECIPIENT ONLY AND SHOULD NOT BE PROVIDED TO OR RELIED ON BY ANY OTHER PERSON." ] }, { "cell_type": "code", "execution_count": null, "id": "64d217eb", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": true, "sideBar": true, "skip_h1_title": false, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": false, "toc_position": {}, "toc_section_display": true, "toc_window_display": true } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/content/reports_and_screens/03_prime/0003_gs_pb_data_case_study_factors.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Hedge Fund Style Rotation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "How can we use our data to track hedge fund style exposures over time?\n", "To answer this question we load the dataset on hedge fund flows and exposures by factor (PBFF)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Import libraries\n", "import datetime\n", "from gs_quant.data import Dataset\n", "from gs_quant.session import GsSession, Environment\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Graphical formatting\n", "import matplotlib.dates as mdates\n", "import matplotlib.ticker as mtick\n", "from matplotlib.patches import Ellipse\n", "half_year_locator = mdates.MonthLocator(interval=6)\n", "year_month_formatter = mdates.DateFormatter(\"%Y-%m\") # four digits for year, two for month\n", "pct_formatter = mtick.PercentFormatter(xmax=1,decimals=0)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Authentication\n", "client_id = # \n", "client_secret = # \n", "GsSession.use(Environment.PROD, client_id, client_secret, ('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Fetch flow and exposure data by factor \n", "ds = Dataset('PBFF')\n", "data = ds.get_data(datetime.date(2021, 1, 1)).reset_index()\n", "data['date'] = data['date'].apply(lambda x: x.date())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We then focus on the rotation from growth to value stocks that has characterised the evolution of style exposures between 2021 and 2022.\n", "A few lines of code generate a time series plot of the aggregate beta to each of the two factors." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Plot hedge funds' exposure to Growth and Value over time\n", "style_pos_flows = data.loc[(data.riskModel==\"Prime v45\") & ((data.factor==\"Growth\") | (data.factor==\"Value\")), \\\n", " ['date','clientExposure','level','factor']]\n", "style_pos = style_pos_flows.pivot(index='date',columns='factor',values='clientExposure')\n", "\n", "fig, ax1 = plt.subplots(figsize=(10, 6))\n", "ax2 = ax1.twinx()\n", "ax1.plot(style_pos.index, style_pos.Growth, 'g-')\n", "ax2.plot(style_pos.index, style_pos.Value, 'b-')\n", "ax1.set_title('Growth vs. Value, Factor Exposure')\n", "ax1.set_xlabel('Time')\n", "ax1.set_ylabel('Growth', color='g')\n", "ax2.set_ylabel('Value', color='b')\n", "ax1.xaxis.set_major_locator(half_year_locator)\n", "ax1.xaxis.set_major_formatter(year_month_formatter)\n", "ax1.yaxis.set_major_formatter(pct_formatter)\n", "ax2.yaxis.set_major_formatter(pct_formatter)\n", "fig.autofmt_xdate()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The chart illustrates how long-short managers rotated from Growth to Value starting in 2021 as rates increased.\n", "Towards the end of 2022, as inflation concerns abated and a less hawkish monetary policy outlook gained traction, we saw a reversal in this trend. Hedge funds started to increase their exposure to the Growth factor and reduce their Value tilt." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, we use a bar plot to display the flow indicator for the two styles over the same period.\n", "As in the previous example, we create 21-day rolling averages of the t-stat that represents the intensity of signed flows in the Growth and Value factors.\n", "Most of the Growth flows are negative during this period, as expected, which indicates that managers actively decided to reduce their Growth exposure by trading out of the factor.\n", "The first two months of 2023 have seen the first period of rotation from Value into Growth in more than a year. This can be seen from the fact that the two indicators have opposite signs: positive for Growth (green area) and negative for Value (blue area).\n", "Rising geopolitical tensions in February 2022 led to negative flows in both Growth and Value." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Plot moving average of style flows\n", "style_flow = style_pos_flows.pivot(index='date',columns='factor',values='level')\n", "style_flow_roll = style_flow.rolling(21).mean()\n", "\n", "fig, ax = plt.subplots(figsize=(10, 6))\n", "ax.bar(style_flow_roll.index, style_flow_roll.Growth,color='g',width=4,label='Growth')\n", "ax.bar(style_flow_roll.index, style_flow_roll.Value,color='b',width=4,label='Value')\n", "ax.axhline(y=0,linestyle='-', color='black')\n", "ax.set_ylabel('Z-score')\n", "ax.set_title('Average 21-day Flows, Growth vs. Value')\n", "ax.legend(loc='upper right')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Disclaimers:\n", "Indicative Terms/Pricing Levels: This material may contain indicative terms only, including but not limited to pricing levels. There is no representation that any transaction can or could have been effected at such terms or prices. Proposed terms and conditions are for discussion purposes only. Finalized terms and conditions are subject to further discussion and negotiation.\n", "www.goldmansachs.com/disclaimer/sales-and-trading-invest-rec-disclosures.html If you are not accessing this material via Marquee ContentStream, a list of the author's investment recommendations disseminated during the preceding 12 months and the proportion of the author's recommendations that are 'buy', 'hold', 'sell' or other over the previous 12 months is available by logging into Marquee ContentStream using the link below. Alternatively, if you do not have access to Marquee ContentStream, please contact your usual GS representative who will be able to provide this information to you.\n", "Please refer to https://marquee.gs.com/studio/ for price information of corporate equity securities.\n", "Notice to Australian Investors: When this document is disseminated in Australia by Goldman Sachs & Co. LLC (\"GSCO\"), Goldman Sachs International (\"GSI\"), Goldman Sachs Bank Europe SE (\"GSBE\"), Goldman Sachs (Asia) L.L.C. (\"GSALLC\"), or Goldman Sachs (Singapore) Pte (\"GSSP\") (collectively the \"GS entities\"), this document, and any access to it, is intended only for a person that has first satisfied the GS entities that:\n", "• the person is a Sophisticated or Professional Investor for the purposes of section 708 of the Corporations Act of Australia; and\n", "• the person is a wholesale client for the purpose of section 761G of the Corporations Act of Australia.\n", "To the extent that the GS entities are providing a financial service in Australia, the GS entities are each exempt from the requirement to hold an Australian financial services licence for the financial services they provide in Australia. Each of the GS entities are regulated by a foreign regulator under foreign laws which differ from Australian laws, specifically:\n", "• GSCO is regulated by the US Securities and Exchange Commission under US laws;\n", "• GSI is authorised by the Prudential Regulation Authority and regulated by the Financial Conduct Authority and the Prudential Regulation Authority, under UK laws;\n", "• GSBE is subject to direct prudential supervision by the European Central Bank and in other respects is supervised by the German Federal Financial Supervisory Authority (Bundesanstalt für Finanzdienstleistungsaufischt, BaFin) and Deutsche Bundesbank;\n", "• GSALLC is regulated by the Hong Kong Securities and Futures Commission under Hong Kong laws; and\n", "• GSSP is regulated by the Monetary Authority of Singapore under Singapore laws.\n", "Notice to Brazilian Investors\n", "Marquee is not meant for the general public in Brazil. The services or products provided by or through Marquee, at any time, may not be offered or sold to the general public in Brazil. You have received a password granting access to Marquee exclusively due to your existing relationship with a GS business located in Brazil. The selection and engagement with any of the offered services or products through Marquee, at any time, will be carried out directly by you. Before acting to implement any chosen service or products, provided by or through Marquee you should consider, at your sole discretion, whether it is suitable for your particular circumstances and, if necessary, seek professional advice. Any steps necessary in order to implement the chosen service or product, including but not limited to remittance of funds, shall be carried out at your discretion. Accordingly, such services and products have not been and will not be publicly issued, placed, distributed, offered or negotiated in the Brazilian capital markets and, as a result, they have not been and will not be registered with the Brazilian Securities and Exchange Commission (Comissão de Valores Mobiliários), nor have they been submitted to the foregoing agency for approval. Documents relating to such services or products, as well as the information contained therein, may not be supplied to the general public in Brazil, as the offering of such services or products is not a public offering in Brazil, nor used in connection with any offer for subscription or sale of securities to the general public in Brazil.\n", "The offer of any securities mentioned in this message may not be made to the general public in Brazil. Accordingly, any such securities have not been nor will they be registered with the Brazilian Securities and Exchange Commission (Comissão de Valores Mobiliários) nor has any offer been submitted to the foregoing agency for approval. Documents relating to the offer, as well as the information contained therein, may not be supplied to the public in Brazil, as the offer is not a public offering of securities in Brazil. These terms will apply on every access to Marquee.\n", "Ouvidoria Goldman Sachs Brasil: 0800 727 5764 e/ou ouvidoriagoldmansachs@gs.com\n", "Horário de funcionamento: segunda-feira à sexta-feira (exceto feriados), das 9hs às 18hs.\n", "Ombudsman Goldman Sachs Brazil: 0800 727 5764 and / or ouvidoriagoldmansachs@gs.com\n", "Available Weekdays (except holidays), from 9 am to 6 pm.\n", "Note to Investors in Israel: GS is not licensed to provide investment advice or investment management services under Israeli law.\n", "Notice to Investors in Japan\n", "Marquee is made available in Japan by Goldman Sachs Japan Co., Ltd.\n", "本書は情報の提供を目的としております。また、売却・購入が違法となるような法域での有価証券その他の売却若しくは購入を勧めるものでもありません。ゴールドマン・サックスは本書内の取引又はストラクチャーの勧誘を行うものではございません。これらの取引又はストラクチャーは、社内及び法規制等の承認等次第で実際にはご提供できない場合がございます。\n", "<適格機関投資家限定 転売制限>\n", "ゴールドマン・サックス証券株式会社が適格機関投資家のみを相手方として取得申込みの勧誘(取得勧誘)又は売付けの申込み若しくは買付けの申込みの勧誘(売付け勧誘等)を行う本有価証券には、適格機関投資家に譲渡する場合以外の譲渡が禁止される旨の制限が付されています。本有価証券は金融商品取引法第4条に基づく財務局に対する届出が行われておりません。なお、本告知はお客様によるご同意のもとに、電磁的に交付させていただいております。\n", "<適格機関投資家用資料>\n", "本資料は、適格機関投資家のお客さまのみを対象に作成されたものです。本資料における金融商品は適格機関投資家のお客さまのみがお取引可能であり、適格機関投資家以外のお客さまからのご注文等はお受けできませんので、ご注意ください。 商号等/ゴールドマン・サックス証券株式会社 金融商品取引業者 関東財務局長(金商)第69号\n", "加入協会/ 日本証券業協会、一般社団法人金融先物取引業協会、一般社団法人第二種金融商品取引業協会\n", "本書又はその添付資料に信用格付が記載されている場合、日本格付研究所(JCR)及び格付投資情報センター(R&I)による格付は、登録信用格付業者による格付(登録格付)です。その他の格付は登録格付である旨の記載がない場合は、無登録格付です。無登録格付を投資判断に利用する前に、「無登録格付に関する説明書」(http://www.goldmansachs.com/disclaimer/ratings.html)を十分にお読みください。\n", "If any credit ratings are contained in this material or any attachments, those that have been issued by Japan Credit Rating Agency, Ltd. (JCR) or Rating and Investment Information, Inc. (R&I) are credit ratings that have been issued by a credit rating agency registered in Japan (registered credit ratings). Other credit ratings are unregistered unless denoted as being registered. Before using unregistered credit ratings to make investment decisions, please carefully read \"Explanation Regarding Unregistered Credit Ratings\" (http://www.goldmansachs.com/disclaimer/ratings.html).\n", "Notice to Mexican Investors: Information contained herein is not meant for the general public in Mexico. The services or products provided by or through Goldman Sachs Mexico, Casa de Bolsa, S.A. de C.V. (GS Mexico) may not be offered or sold to the general public in Mexico. You have received information herein exclusively due to your existing relationship with a GS Mexico or any other Goldman Sachs business. The selection and engagement with any of the offered services or products through GS Mexico will be carried out directly by you at your own risk. Before acting to implement any chosen service or product provided by or through GS Mexico you should consider, at your sole discretion, whether it is suitable for your particular circumstances and, if necessary, seek professional advice. Information contained herein related to GS Mexico services or products, as well as any other information, shall not be considered as a product coming from research, nor it contains any recommendation to invest, not to invest, hold or sell any security and may not be supplied to the general public in Mexico.\n", "Notice to New Zealand Investors: When this document is disseminated in New Zealand by Goldman Sachs & Co. LLC (\"GSCO\") , Goldman Sachs International (\"GSI\"), Goldman Sachs Bank Europe SE (\"GSBE\"), Goldman Sachs (Asia) L.L.C. (\"GSALLC\") or Goldman Sachs (Singapore) Pte (\"GSSP\") (collectively the \"GS entities\"), this document, and any access to it, is intended only for a person that has first satisfied; the GS entities that the person is someone:\n", "(i) who is an investment business within the meaning of clause 37 of Schedule 1 of the Financial Markets Conduct Act 2013 (New Zealand) (the \"FMC Act\");\n", "(ii) who meets the investment activity criteria specified in clause 38 of Schedule 1 of the FMC Act;\n", "(iii) who is large within the meaning of clause 39 of Schedule 1 of the FMC Act; or\n", "(iv) is a government agency within the meaning of clause 40 of Schedule 1 of the FMC Act.\n", "No offer to acquire the interests is being made to you in this document. Any offer will only be made in circumstances where disclosure is not required under the Financial Markets Conducts Act 2013 or the Financial Markets Conduct Regulations 2014.\n", "Notice to Swiss Investors: This is marketing material for financial instruments or services. The information contained in this material is for general informational purposes only and does not constitute an offer, solicitation, invitation or recommendation to buy or sell any financial instruments or to provide any investment advice or service of any kind.\n", "THE INFORMATION CONTAINED IN THIS DOCUMENT DOES NOT CONSITUTE, AND IS NOT INTENDED TO CONSTITUTE, A PUBLIC OFFER OF SECURITIES IN THE UNITED ARAB EMIRATES IN ACCORDANCE WITH THE COMMERCIAL COMPANIES LAW (FEDERAL LAW NO. 2 OF 2015), ESCA BOARD OF DIRECTORS' DECISION NO. (9/R.M.) OF 2016, ESCA CHAIRMAN DECISION NO 3/R.M. OF 2017 CONCERNING PROMOTING AND INTRODUCING REGULATIONS OR OTHERWISE UNDER THE LAWS OF THE UNITED ARAB EMIRATES. ACCORDINGLY, THE INTERESTS IN THE SECURITIES MAY NOT BE OFFERED TO THE PUBLIC IN THE UAE (INCLUDING THE DUBAI INTERNATIONAL FINANCIAL CENTRE AND THE ABU DHABI GLOBAL MARKET). THIS DOCUMENT HAS NOT BEEN APPROVED BY, OR FILED WITH THE CENTRAL BANK OF THE UNITED ARAB EMIRATES, THE SECURITIES AND COMMODITIES AUTHORITY, THE DUBAI FINANCIAL SERVICES AUTHORITY, THE FINANCIAL SERVICES REGULATORY AUTHORITY OR ANY OTHER RELEVANT LICENSING AUTHORITIES IN THE UNITED ARAB EMIRATES. IF YOU DO NOT UNDERSTAND THE CONTENTS OF THIS DOCUMENT, YOU SHOULD CONSULT WITH A FINANCIAL ADVISOR. THIS DOCUMENT IS PROVIDED TO THE RECIPIENT ONLY AND SHOULD NOT BE PROVIDED TO OR RELIED ON BY ANY OTHER PERSON." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.3" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/content/reports_and_screens/03_prime/0004_gs_pb_data_case_study_country_positioning.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# China Reopening and Hedge Fund Positioning" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We recently added country-level positioning and flow metrics to our data offering. In this notebook we use the dataset PBFA to investigate how the hedge fund community reacted to news that China was removing key Covid-era restrictions starting in late 2022.
\n", "In particular, our researchers speak of an 'abrupt reopening' in December 2022, after a proprietary GS indicator of the severity of covid-related restrictions peaked in November." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Import libraries\n", "import datetime\n", "from gs_quant.data import Dataset\n", "from gs_quant.session import GsSession, Environment\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Graphical formatting\n", "import matplotlib.dates as mdates\n", "import matplotlib.ticker as mtick\n", "from matplotlib.patches import Ellipse\n", "quarter_locator = mdates.MonthLocator(interval=3)\n", "year_month_formatter = mdates.DateFormatter(\"%Y-%m\") # four digits for year, two for month\n", "pct_formatter = mtick.PercentFormatter(xmax=1,decimals=0)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Authentication\n", "client_id = # \n", "client_secret = # \n", "GsSession.use(Environment.PROD, client_id, client_secret, ('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Fetch positioning data from 2022 Q4 \n", "ds = Dataset('PBFA')\n", "data = ds.get_data(datetime.date(2022, 10, 1)).reset_index()\n", "data['date'] = data['date'].apply(lambda x: x.date())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First, we gather data on the aggregate net weight of positions in Chinese equities present in our overall Prime book.\n", "A few lines of code generate a time series plot of the aggregate net exposure." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "china_weight = data.loc[(data.country=='CHINA'),:].pivot(columns='country',index='date',values='clientNetWeight')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Plot hedge funds' exposure to Chinese stocks over time\n", "fig, ax1 = plt.subplots(figsize=(10, 6))\n", "ax1.plot(china_weight.index, china_weight['CHINA'], color='black', linestyle='solid')\n", "ax1.set_title('Net weight of Chinese equities in the Book')\n", "ax1.set_xlabel('Time')\n", "ax1.xaxis.set_major_locator(quarter_locator)\n", "ax1.xaxis.set_major_formatter(year_month_formatter)\n", "ax1.yaxis.set_major_formatter(pct_formatter)\n", "fig.autofmt_xdate()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The chart illustrates how hedge funds ramped up their exposure to China in December 2022 as restrictions were dropped. The weight in our book almost doubled from 7% to over 13% between October 2022 and January 2023." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, portfolio weights are driven both by trading activity and by market prices. If Chinese equities outperform, then even in the absence of any active decision to buy more China, the country weight is bound to increase. Our data can be used to disentangle market dynamics from active rotation into Chinese names. In particular, we focus on our indicator of long flows." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Plot moving average of country-level long flows\n", "china_flow = data.loc[(data.country=='CHINA'),:].pivot(columns='country',index='date',values='longLevel')\n", "china_flow_roll = china_flow.rolling(21).mean()\n", "\n", "fig, ax = plt.subplots(figsize=(10, 6))\n", "ax.bar(china_flow_roll.index, china_flow_roll['CHINA'],color='black',width=4,label='China')\n", "ax.axhline(y=0,linestyle='-', color='black')\n", "ax.set_ylabel('Z-score')\n", "ax.set_title('Average 21-day Flows, Chinese equities')\n", "#ax.legend(loc='upper right')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The chart suggests that hedge funds started adding Chinese equities (positive flows) to their longs in November-December 2022 and the buying trend accelerated in January 2023. At the end of February a quick reversal appears in the data and flows remain negative for most of 2023." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Disclaimers:\n", "Indicative Terms/Pricing Levels: This material may contain indicative terms only, including but not limited to pricing levels. There is no representation that any transaction can or could have been effected at such terms or prices. Proposed terms and conditions are for discussion purposes only. Finalized terms and conditions are subject to further discussion and negotiation.\n", "www.goldmansachs.com/disclaimer/sales-and-trading-invest-rec-disclosures.html If you are not accessing this material via Marquee ContentStream, a list of the author's investment recommendations disseminated during the preceding 12 months and the proportion of the author's recommendations that are 'buy', 'hold', 'sell' or other over the previous 12 months is available by logging into Marquee ContentStream using the link below. Alternatively, if you do not have access to Marquee ContentStream, please contact your usual GS representative who will be able to provide this information to you.\n", "Please refer to https://marquee.gs.com/studio/ for price information of corporate equity securities.\n", "Notice to Australian Investors: When this document is disseminated in Australia by Goldman Sachs & Co. LLC (\"GSCO\"), Goldman Sachs International (\"GSI\"), Goldman Sachs Bank Europe SE (\"GSBE\"), Goldman Sachs (Asia) L.L.C. (\"GSALLC\"), or Goldman Sachs (Singapore) Pte (\"GSSP\") (collectively the \"GS entities\"), this document, and any access to it, is intended only for a person that has first satisfied the GS entities that:\n", "• the person is a Sophisticated or Professional Investor for the purposes of section 708 of the Corporations Act of Australia; and\n", "• the person is a wholesale client for the purpose of section 761G of the Corporations Act of Australia.\n", "To the extent that the GS entities are providing a financial service in Australia, the GS entities are each exempt from the requirement to hold an Australian financial services licence for the financial services they provide in Australia. Each of the GS entities are regulated by a foreign regulator under foreign laws which differ from Australian laws, specifically:\n", "• GSCO is regulated by the US Securities and Exchange Commission under US laws;\n", "• GSI is authorised by the Prudential Regulation Authority and regulated by the Financial Conduct Authority and the Prudential Regulation Authority, under UK laws;\n", "• GSBE is subject to direct prudential supervision by the European Central Bank and in other respects is supervised by the German Federal Financial Supervisory Authority (Bundesanstalt für Finanzdienstleistungsaufischt, BaFin) and Deutsche Bundesbank;\n", "• GSALLC is regulated by the Hong Kong Securities and Futures Commission under Hong Kong laws; and\n", "• GSSP is regulated by the Monetary Authority of Singapore under Singapore laws.\n", "Notice to Brazilian Investors\n", "Marquee is not meant for the general public in Brazil. The services or products provided by or through Marquee, at any time, may not be offered or sold to the general public in Brazil. You have received a password granting access to Marquee exclusively due to your existing relationship with a GS business located in Brazil. The selection and engagement with any of the offered services or products through Marquee, at any time, will be carried out directly by you. Before acting to implement any chosen service or products, provided by or through Marquee you should consider, at your sole discretion, whether it is suitable for your particular circumstances and, if necessary, seek professional advice. Any steps necessary in order to implement the chosen service or product, including but not limited to remittance of funds, shall be carried out at your discretion. Accordingly, such services and products have not been and will not be publicly issued, placed, distributed, offered or negotiated in the Brazilian capital markets and, as a result, they have not been and will not be registered with the Brazilian Securities and Exchange Commission (Comissão de Valores Mobiliários), nor have they been submitted to the foregoing agency for approval. Documents relating to such services or products, as well as the information contained therein, may not be supplied to the general public in Brazil, as the offering of such services or products is not a public offering in Brazil, nor used in connection with any offer for subscription or sale of securities to the general public in Brazil.\n", "The offer of any securities mentioned in this message may not be made to the general public in Brazil. Accordingly, any such securities have not been nor will they be registered with the Brazilian Securities and Exchange Commission (Comissão de Valores Mobiliários) nor has any offer been submitted to the foregoing agency for approval. Documents relating to the offer, as well as the information contained therein, may not be supplied to the public in Brazil, as the offer is not a public offering of securities in Brazil. These terms will apply on every access to Marquee.\n", "Ouvidoria Goldman Sachs Brasil: 0800 727 5764 e/ou ouvidoriagoldmansachs@gs.com\n", "Horário de funcionamento: segunda-feira à sexta-feira (exceto feriados), das 9hs às 18hs.\n", "Ombudsman Goldman Sachs Brazil: 0800 727 5764 and / or ouvidoriagoldmansachs@gs.com\n", "Available Weekdays (except holidays), from 9 am to 6 pm.\n", "Note to Investors in Israel: GS is not licensed to provide investment advice or investment management services under Israeli law.\n", "Notice to Investors in Japan\n", "Marquee is made available in Japan by Goldman Sachs Japan Co., Ltd.\n", "本書は情報の提供を目的としております。また、売却・購入が違法となるような法域での有価証券その他の売却若しくは購入を勧めるものでもありません。ゴールドマン・サックスは本書内の取引又はストラクチャーの勧誘を行うものではございません。これらの取引又はストラクチャーは、社内及び法規制等の承認等次第で実際にはご提供できない場合がございます。\n", "<適格機関投資家限定 転売制限>\n", "ゴールドマン・サックス証券株式会社が適格機関投資家のみを相手方として取得申込みの勧誘(取得勧誘)又は売付けの申込み若しくは買付けの申込みの勧誘(売付け勧誘等)を行う本有価証券には、適格機関投資家に譲渡する場合以外の譲渡が禁止される旨の制限が付されています。本有価証券は金融商品取引法第4条に基づく財務局に対する届出が行われておりません。なお、本告知はお客様によるご同意のもとに、電磁的に交付させていただいております。\n", "<適格機関投資家用資料>\n", "本資料は、適格機関投資家のお客さまのみを対象に作成されたものです。本資料における金融商品は適格機関投資家のお客さまのみがお取引可能であり、適格機関投資家以外のお客さまからのご注文等はお受けできませんので、ご注意ください。 商号等/ゴールドマン・サックス証券株式会社 金融商品取引業者 関東財務局長(金商)第69号\n", "加入協会/ 日本証券業協会、一般社団法人金融先物取引業協会、一般社団法人第二種金融商品取引業協会\n", "本書又はその添付資料に信用格付が記載されている場合、日本格付研究所(JCR)及び格付投資情報センター(R&I)による格付は、登録信用格付業者による格付(登録格付)です。その他の格付は登録格付である旨の記載がない場合は、無登録格付です。無登録格付を投資判断に利用する前に、「無登録格付に関する説明書」(http://www.goldmansachs.com/disclaimer/ratings.html)を十分にお読みください。\n", "If any credit ratings are contained in this material or any attachments, those that have been issued by Japan Credit Rating Agency, Ltd. (JCR) or Rating and Investment Information, Inc. (R&I) are credit ratings that have been issued by a credit rating agency registered in Japan (registered credit ratings). Other credit ratings are unregistered unless denoted as being registered. Before using unregistered credit ratings to make investment decisions, please carefully read \"Explanation Regarding Unregistered Credit Ratings\" (http://www.goldmansachs.com/disclaimer/ratings.html).\n", "Notice to Mexican Investors: Information contained herein is not meant for the general public in Mexico. The services or products provided by or through Goldman Sachs Mexico, Casa de Bolsa, S.A. de C.V. (GS Mexico) may not be offered or sold to the general public in Mexico. You have received information herein exclusively due to your existing relationship with a GS Mexico or any other Goldman Sachs business. The selection and engagement with any of the offered services or products through GS Mexico will be carried out directly by you at your own risk. Before acting to implement any chosen service or product provided by or through GS Mexico you should consider, at your sole discretion, whether it is suitable for your particular circumstances and, if necessary, seek professional advice. Information contained herein related to GS Mexico services or products, as well as any other information, shall not be considered as a product coming from research, nor it contains any recommendation to invest, not to invest, hold or sell any security and may not be supplied to the general public in Mexico.\n", "Notice to New Zealand Investors: When this document is disseminated in New Zealand by Goldman Sachs & Co. LLC (\"GSCO\") , Goldman Sachs International (\"GSI\"), Goldman Sachs Bank Europe SE (\"GSBE\"), Goldman Sachs (Asia) L.L.C. (\"GSALLC\") or Goldman Sachs (Singapore) Pte (\"GSSP\") (collectively the \"GS entities\"), this document, and any access to it, is intended only for a person that has first satisfied; the GS entities that the person is someone:\n", "(i) who is an investment business within the meaning of clause 37 of Schedule 1 of the Financial Markets Conduct Act 2013 (New Zealand) (the \"FMC Act\");\n", "(ii) who meets the investment activity criteria specified in clause 38 of Schedule 1 of the FMC Act;\n", "(iii) who is large within the meaning of clause 39 of Schedule 1 of the FMC Act; or\n", "(iv) is a government agency within the meaning of clause 40 of Schedule 1 of the FMC Act.\n", "No offer to acquire the interests is being made to you in this document. Any offer will only be made in circumstances where disclosure is not required under the Financial Markets Conducts Act 2013 or the Financial Markets Conduct Regulations 2014.\n", "Notice to Swiss Investors: This is marketing material for financial instruments or services. The information contained in this material is for general informational purposes only and does not constitute an offer, solicitation, invitation or recommendation to buy or sell any financial instruments or to provide any investment advice or service of any kind.\n", "THE INFORMATION CONTAINED IN THIS DOCUMENT DOES NOT CONSITUTE, AND IS NOT INTENDED TO CONSTITUTE, A PUBLIC OFFER OF SECURITIES IN THE UNITED ARAB EMIRATES IN ACCORDANCE WITH THE COMMERCIAL COMPANIES LAW (FEDERAL LAW NO. 2 OF 2015), ESCA BOARD OF DIRECTORS' DECISION NO. (9/R.M.) OF 2016, ESCA CHAIRMAN DECISION NO 3/R.M. OF 2017 CONCERNING PROMOTING AND INTRODUCING REGULATIONS OR OTHERWISE UNDER THE LAWS OF THE UNITED ARAB EMIRATES. ACCORDINGLY, THE INTERESTS IN THE SECURITIES MAY NOT BE OFFERED TO THE PUBLIC IN THE UAE (INCLUDING THE DUBAI INTERNATIONAL FINANCIAL CENTRE AND THE ABU DHABI GLOBAL MARKET). THIS DOCUMENT HAS NOT BEEN APPROVED BY, OR FILED WITH THE CENTRAL BANK OF THE UNITED ARAB EMIRATES, THE SECURITIES AND COMMODITIES AUTHORITY, THE DUBAI FINANCIAL SERVICES AUTHORITY, THE FINANCIAL SERVICES REGULATORY AUTHORITY OR ANY OTHER RELEVANT LICENSING AUTHORITIES IN THE UNITED ARAB EMIRATES. IF YOU DO NOT UNDERSTAND THE CONTENTS OF THIS DOCUMENT, YOU SHOULD CONSULT WITH A FINANCIAL ADVISOR. THIS DOCUMENT IS PROVIDED TO THE RECIPIENT ONLY AND SHOULD NOT BE PROVIDED TO OR RELIED ON BY ANY OTHER PERSON." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "venv311", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/content/reports_and_screens/03_prime/0005_gs_pb_data_case_study_leverage.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# The 2020 COVID Selloff and Hedge Fund Leverage" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "From mid February 2020, news of the spread of COVID-19, coupled with a surge in the volatility of commodity prices, triggered a deep selloff in global equities.
\n", "The most acute phase started on February 24, as Italian authorities adopted restrictive measures to try and curb the diffusion of the infection. After plunging for the best part of three weeks, stock prices entered a more stable phase, recovering a small fraction of the previous losses.
\n", "In this case study we build on the PBFA dataset to analyse how hedge fund leverage changed as a result of the sudden shock inflicted on the financial system by the spread of COVID-19." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Import libraries\n", "import datetime\n", "from gs_quant.data import Dataset\n", "from gs_quant.session import GsSession, Environment\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Graphical formatting\n", "import matplotlib.dates as mdates\n", "import matplotlib.ticker as mtick\n", "year_month_formatter = mdates.DateFormatter(\"%Y-%m\") # four digits for year, two for month\n", "pct_formatter = mtick.PercentFormatter(xmax=1,decimals=0)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Authentication\n", "client_id = # \n", "client_secret = # \n", "GsSession.use(Environment.PROD, client_id, client_secret, ('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Fetch positioning data for 2020\n", "ds = Dataset('PBFA')\n", "data = ds.get_data(datetime.date(2020, 1, 1),datetime.date(2021, 1, 1))\n", "data = data.reset_index()\n", "data.date = data.date.apply(lambda x: x.date())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "After fetching the data from Marquee, we use some simple filters to focus on the broadest aggregates (region=global and sectors=all) and create dataframe which contain gross and net leverage figures by date and strategy." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": false }, "outputs": [], "source": [ "# Gather gross and net leverage data\n", "glob_pos = data.loc[(data.region==\"Global\") & \\\n", " (data.sectorGICS1==\"All\"), ['date','clientStrategy','grossLeverage','netLeverage'] ]\n", "\n", "gross_lev= glob_pos.pivot(index=\"date\", columns=\"clientStrategy\", values=\"grossLeverage\").fillna(method='ffill')\n", "net_lev= glob_pos.pivot(index=\"date\", columns=\"clientStrategy\", values=\"netLeverage\").fillna(method='ffill')\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First, we plot gross and net leverage for the overall book.
\n", "As expected, from the end February both measures of leverage declined sharply. However, net leverage (black line) subsequently recovered and ended 2020 at around 85%, higher than the 70-75% level seen before the selloff.
\n", "In contrast, gross leverage (green) reached a trough at around 215% and remained below pre-COVID levels for most of 2020." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Plot book-level gross and net leverage\n", "fig, ax = plt.subplots(figsize=(10, 6))\n", "ax2 = ax.twinx()\n", "ax.plot(gross_lev.index, gross_lev['All'], 'g-',label='Gross')\n", "ax2.plot(net_lev.index, net_lev['All'], color='black',linestyle='solid',label='Net')\n", "ax.set_title('Gross vs. Net Leverage, Prime Book')\n", "ax.set_xlabel('Time')\n", "ax.set_ylabel('Gross Leverage', color='green')\n", "ax2.set_ylabel('Net Leverage', color='black')\n", "ax.xaxis.set_major_locator(mdates.MonthLocator(interval=3))\n", "ax.xaxis.set_major_formatter(year_month_formatter)\n", "ax.yaxis.set_major_formatter(pct_formatter)\n", "ax2.yaxis.set_major_formatter(pct_formatter)\n", "fig.autofmt_xdate()\n", "ax.legend(loc='center left')\n", "ax2.legend(loc='center right')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also use the data to track leverage for a specific category of hedge fund strategies, i.e. fundamental long-short\n", "The chart suggests that the evolution of gross leverage for fundamental long-short strategies (black line) was similar to that of the overall book (green line). If anything, fundamental managers seem to have returned to the pre-COVID level of risk exposure even faster than other types of investor.
\n", "Leverage is typically lower for fundamental strategies in our book, compared to other hedge fund categories, due to the concentrated nature of their typical portfolios." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Plot gross leverage of fundamental L/S managers\n", "fig, ax = plt.subplots(figsize=(10, 6))\n", "ax2 = ax.twinx()\n", "ax.plot(gross_lev.index, gross_lev['All'], 'g-',label='Prime Book')\n", "ax2.plot(gross_lev.index, gross_lev['Fundamental L/S v2'], color='black',label='Fundamental L/S')\n", "ax.set_title('Gross Leverage: Prime Book vs. Fundamental L/S')\n", "ax.set_xlabel('Time')\n", "ax.set_ylabel('Gross Leverage', color='green')\n", "ax2.set_ylabel('Gross Leverage', color='black')\n", "ax.xaxis.set_major_locator(mdates.MonthLocator(interval=3))\n", "ax.xaxis.set_major_formatter(year_month_formatter)\n", "ax.yaxis.set_major_formatter(pct_formatter)\n", "ax2.yaxis.set_major_formatter(pct_formatter)\n", "fig.autofmt_xdate()\n", "ax.legend(loc='center right')\n", "ax2.legend(loc='lower right')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Within the fundamental category, we can drill down further into the numbers by considering strategies with a regional focus\n", "Managers within a European and an Asia-focused mandate saw their leverage decline earlier than US-focused manager – a result that is intuitive given the staggered geographic spread of COVID.
\n", "After reaching very similar lows in gross leverage (slightly above 150%), US-focused managers (blue line) re-grossed more cautiously than their Asia and Europe-focused counterparts (green and red line, respectively)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Plot gross leverage of regionally focused fundamental L/S managers\n", "fig, ax = plt.subplots(figsize=(10, 6))\n", "ax.plot(gross_lev.index, gross_lev['Asia-Focused Fundamental L/S v2'], color='green',label='Asia-Focused')\n", "ax.plot(gross_lev.index, gross_lev['Europe-Focused Fundamental L/S v2'], color='red',label='Europe-Focused')\n", "ax.plot(gross_lev.index, gross_lev['US-Focused Fundamental L/S v2'], 'blue',label='US-Focused')\n", "ax.set_title('Gross Leverage: Regionally Focused Fundamental L/S Strategies')\n", "ax.set_xlabel('Time')\n", "ax.set_ylabel('Gross Leverage', color='black')\n", "ax.xaxis.set_major_locator(mdates.MonthLocator(interval=3))\n", "ax.xaxis.set_major_formatter(year_month_formatter)\n", "ax.yaxis.set_major_formatter(pct_formatter)\n", "fig.autofmt_xdate()\n", "ax.legend(loc='lower right')\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Disclaimers:\n", "Indicative Terms/Pricing Levels: This material may contain indicative terms only, including but not limited to pricing levels. There is no representation that any transaction can or could have been effected at such terms or prices. Proposed terms and conditions are for discussion purposes only. Finalized terms and conditions are subject to further discussion and negotiation.\n", "www.goldmansachs.com/disclaimer/sales-and-trading-invest-rec-disclosures.html If you are not accessing this material via Marquee ContentStream, a list of the author's investment recommendations disseminated during the preceding 12 months and the proportion of the author's recommendations that are 'buy', 'hold', 'sell' or other over the previous 12 months is available by logging into Marquee ContentStream using the link below. Alternatively, if you do not have access to Marquee ContentStream, please contact your usual GS representative who will be able to provide this information to you.\n", "Please refer to https://marquee.gs.com/studio/ for price information of corporate equity securities.\n", "Notice to Australian Investors: When this document is disseminated in Australia by Goldman Sachs & Co. LLC (\"GSCO\"), Goldman Sachs International (\"GSI\"), Goldman Sachs Bank Europe SE (\"GSBE\"), Goldman Sachs (Asia) L.L.C. (\"GSALLC\"), or Goldman Sachs (Singapore) Pte (\"GSSP\") (collectively the \"GS entities\"), this document, and any access to it, is intended only for a person that has first satisfied the GS entities that:\n", "• the person is a Sophisticated or Professional Investor for the purposes of section 708 of the Corporations Act of Australia; and\n", "• the person is a wholesale client for the purpose of section 761G of the Corporations Act of Australia.\n", "To the extent that the GS entities are providing a financial service in Australia, the GS entities are each exempt from the requirement to hold an Australian financial services licence for the financial services they provide in Australia. Each of the GS entities are regulated by a foreign regulator under foreign laws which differ from Australian laws, specifically:\n", "• GSCO is regulated by the US Securities and Exchange Commission under US laws;\n", "• GSI is authorised by the Prudential Regulation Authority and regulated by the Financial Conduct Authority and the Prudential Regulation Authority, under UK laws;\n", "• GSBE is subject to direct prudential supervision by the European Central Bank and in other respects is supervised by the German Federal Financial Supervisory Authority (Bundesanstalt für Finanzdienstleistungsaufischt, BaFin) and Deutsche Bundesbank;\n", "• GSALLC is regulated by the Hong Kong Securities and Futures Commission under Hong Kong laws; and\n", "• GSSP is regulated by the Monetary Authority of Singapore under Singapore laws.\n", "Notice to Brazilian Investors\n", "Marquee is not meant for the general public in Brazil. The services or products provided by or through Marquee, at any time, may not be offered or sold to the general public in Brazil. You have received a password granting access to Marquee exclusively due to your existing relationship with a GS business located in Brazil. The selection and engagement with any of the offered services or products through Marquee, at any time, will be carried out directly by you. Before acting to implement any chosen service or products, provided by or through Marquee you should consider, at your sole discretion, whether it is suitable for your particular circumstances and, if necessary, seek professional advice. Any steps necessary in order to implement the chosen service or product, including but not limited to remittance of funds, shall be carried out at your discretion. Accordingly, such services and products have not been and will not be publicly issued, placed, distributed, offered or negotiated in the Brazilian capital markets and, as a result, they have not been and will not be registered with the Brazilian Securities and Exchange Commission (Comissão de Valores Mobiliários), nor have they been submitted to the foregoing agency for approval. Documents relating to such services or products, as well as the information contained therein, may not be supplied to the general public in Brazil, as the offering of such services or products is not a public offering in Brazil, nor used in connection with any offer for subscription or sale of securities to the general public in Brazil.\n", "The offer of any securities mentioned in this message may not be made to the general public in Brazil. Accordingly, any such securities have not been nor will they be registered with the Brazilian Securities and Exchange Commission (Comissão de Valores Mobiliários) nor has any offer been submitted to the foregoing agency for approval. Documents relating to the offer, as well as the information contained therein, may not be supplied to the public in Brazil, as the offer is not a public offering of securities in Brazil. These terms will apply on every access to Marquee.\n", "Ouvidoria Goldman Sachs Brasil: 0800 727 5764 e/ou ouvidoriagoldmansachs@gs.com\n", "Horário de funcionamento: segunda-feira à sexta-feira (exceto feriados), das 9hs às 18hs.\n", "Ombudsman Goldman Sachs Brazil: 0800 727 5764 and / or ouvidoriagoldmansachs@gs.com\n", "Available Weekdays (except holidays), from 9 am to 6 pm.\n", "Note to Investors in Israel: GS is not licensed to provide investment advice or investment management services under Israeli law.\n", "Notice to Investors in Japan\n", "Marquee is made available in Japan by Goldman Sachs Japan Co., Ltd.\n", "本書は情報の提供を目的としております。また、売却・購入が違法となるような法域での有価証券その他の売却若しくは購入を勧めるものでもありません。ゴールドマン・サックスは本書内の取引又はストラクチャーの勧誘を行うものではございません。これらの取引又はストラクチャーは、社内及び法規制等の承認等次第で実際にはご提供できない場合がございます。\n", "<適格機関投資家限定 転売制限>\n", "ゴールドマン・サックス証券株式会社が適格機関投資家のみを相手方として取得申込みの勧誘(取得勧誘)又は売付けの申込み若しくは買付けの申込みの勧誘(売付け勧誘等)を行う本有価証券には、適格機関投資家に譲渡する場合以外の譲渡が禁止される旨の制限が付されています。本有価証券は金融商品取引法第4条に基づく財務局に対する届出が行われておりません。なお、本告知はお客様によるご同意のもとに、電磁的に交付させていただいております。\n", "<適格機関投資家用資料>\n", "本資料は、適格機関投資家のお客さまのみを対象に作成されたものです。本資料における金融商品は適格機関投資家のお客さまのみがお取引可能であり、適格機関投資家以外のお客さまからのご注文等はお受けできませんので、ご注意ください。 商号等/ゴールドマン・サックス証券株式会社 金融商品取引業者 関東財務局長(金商)第69号\n", "加入協会/ 日本証券業協会、一般社団法人金融先物取引業協会、一般社団法人第二種金融商品取引業協会\n", "本書又はその添付資料に信用格付が記載されている場合、日本格付研究所(JCR)及び格付投資情報センター(R&I)による格付は、登録信用格付業者による格付(登録格付)です。その他の格付は登録格付である旨の記載がない場合は、無登録格付です。無登録格付を投資判断に利用する前に、「無登録格付に関する説明書」(http://www.goldmansachs.com/disclaimer/ratings.html)を十分にお読みください。\n", "If any credit ratings are contained in this material or any attachments, those that have been issued by Japan Credit Rating Agency, Ltd. (JCR) or Rating and Investment Information, Inc. (R&I) are credit ratings that have been issued by a credit rating agency registered in Japan (registered credit ratings). Other credit ratings are unregistered unless denoted as being registered. Before using unregistered credit ratings to make investment decisions, please carefully read \"Explanation Regarding Unregistered Credit Ratings\" (http://www.goldmansachs.com/disclaimer/ratings.html).\n", "Notice to Mexican Investors: Information contained herein is not meant for the general public in Mexico. The services or products provided by or through Goldman Sachs Mexico, Casa de Bolsa, S.A. de C.V. (GS Mexico) may not be offered or sold to the general public in Mexico. You have received information herein exclusively due to your existing relationship with a GS Mexico or any other Goldman Sachs business. The selection and engagement with any of the offered services or products through GS Mexico will be carried out directly by you at your own risk. Before acting to implement any chosen service or product provided by or through GS Mexico you should consider, at your sole discretion, whether it is suitable for your particular circumstances and, if necessary, seek professional advice. Information contained herein related to GS Mexico services or products, as well as any other information, shall not be considered as a product coming from research, nor it contains any recommendation to invest, not to invest, hold or sell any security and may not be supplied to the general public in Mexico.\n", "Notice to New Zealand Investors: When this document is disseminated in New Zealand by Goldman Sachs & Co. LLC (\"GSCO\") , Goldman Sachs International (\"GSI\"), Goldman Sachs Bank Europe SE (\"GSBE\"), Goldman Sachs (Asia) L.L.C. (\"GSALLC\") or Goldman Sachs (Singapore) Pte (\"GSSP\") (collectively the \"GS entities\"), this document, and any access to it, is intended only for a person that has first satisfied; the GS entities that the person is someone:\n", "(i) who is an investment business within the meaning of clause 37 of Schedule 1 of the Financial Markets Conduct Act 2013 (New Zealand) (the \"FMC Act\");\n", "(ii) who meets the investment activity criteria specified in clause 38 of Schedule 1 of the FMC Act;\n", "(iii) who is large within the meaning of clause 39 of Schedule 1 of the FMC Act; or\n", "(iv) is a government agency within the meaning of clause 40 of Schedule 1 of the FMC Act.\n", "No offer to acquire the interests is being made to you in this document. Any offer will only be made in circumstances where disclosure is not required under the Financial Markets Conducts Act 2013 or the Financial Markets Conduct Regulations 2014.\n", "Notice to Swiss Investors: This is marketing material for financial instruments or services. The information contained in this material is for general informational purposes only and does not constitute an offer, solicitation, invitation or recommendation to buy or sell any financial instruments or to provide any investment advice or service of any kind.\n", "THE INFORMATION CONTAINED IN THIS DOCUMENT DOES NOT CONSITUTE, AND IS NOT INTENDED TO CONSTITUTE, A PUBLIC OFFER OF SECURITIES IN THE UNITED ARAB EMIRATES IN ACCORDANCE WITH THE COMMERCIAL COMPANIES LAW (FEDERAL LAW NO. 2 OF 2015), ESCA BOARD OF DIRECTORS' DECISION NO. (9/R.M.) OF 2016, ESCA CHAIRMAN DECISION NO 3/R.M. OF 2017 CONCERNING PROMOTING AND INTRODUCING REGULATIONS OR OTHERWISE UNDER THE LAWS OF THE UNITED ARAB EMIRATES. ACCORDINGLY, THE INTERESTS IN THE SECURITIES MAY NOT BE OFFERED TO THE PUBLIC IN THE UAE (INCLUDING THE DUBAI INTERNATIONAL FINANCIAL CENTRE AND THE ABU DHABI GLOBAL MARKET). THIS DOCUMENT HAS NOT BEEN APPROVED BY, OR FILED WITH THE CENTRAL BANK OF THE UNITED ARAB EMIRATES, THE SECURITIES AND COMMODITIES AUTHORITY, THE DUBAI FINANCIAL SERVICES AUTHORITY, THE FINANCIAL SERVICES REGULATORY AUTHORITY OR ANY OTHER RELEVANT LICENSING AUTHORITIES IN THE UNITED ARAB EMIRATES. IF YOU DO NOT UNDERSTAND THE CONTENTS OF THIS DOCUMENT, YOU SHOULD CONSULT WITH A FINANCIAL ADVISOR. THIS DOCUMENT IS PROVIDED TO THE RECIPIENT ONLY AND SHOULD NOT BE PROVIDED TO OR RELIED ON BY ANY OTHER PERSON." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.3" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/content/reports_and_screens/README.md ================================================ ### How to use Screen Apps To use the screeners in a browser format, 1) Install the open source streamlit package
"pip install streamlit" 2) Navigate to the folder with the python app file and type "streamlit run python_file_name"
Ex. \gs_quant\gs_quant\content\reports_and_screens\00_fx>streamlit run vol_screen_app.py 3) Launch the Streamlit app in your browser by clicking the local or Network URL ================================================ FILE: gs_quant/content/reports_and_screens/__init__.py ================================================ ================================================ FILE: gs_quant/context_base.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import threading from gs_quant.errors import MqUninitialisedError, MqValueError thread_local = threading.local() class ContextMeta(type): @property def __path_key(cls) -> str: return '{}_path'.format(cls.__name__) @property def __default_key(cls) -> str: return '{}_default'.format(cls.__name__) @classmethod def default_value(mcs) -> object: return None @property def path(cls) -> tuple: return getattr(thread_local, cls.__path_key, ()) @property def current(cls): """ The current instance of this context """ path = cls.path current = cls.__default if not path else next(iter(path)) if current is None: raise MqUninitialisedError('{} is not initialised'.format(cls.__name__)) return current @current.setter def current(cls, current): path = cls.path if cls.has_prior: raise MqValueError('Cannot set current while in a nested context {}'.format(cls.__name__)) if len(path) == 1: cur = cls.current try: if cur.is_entered: raise MqValueError('Cannot set current while in a nested context {}'.format(cls.__name__)) except AttributeError: pass setattr(thread_local, cls.__path_key, (current,)) @property def current_is_set(cls) -> bool: return bool(cls.path) or cls.__default is not None @property def __default(cls): default = getattr(thread_local, cls.__default_key, None) if default is None: default = cls.default_value() if default is not None: setattr(thread_local, cls.__default_key, default) return default @property def prior(cls): path = cls.path if len(path) < 2: raise MqValueError('Current {} has no prior'.format(cls.__name__)) return path[1] @property def has_prior(cls): path = cls.path return len(path) >= 2 def push(cls, context): setattr(thread_local, cls.__path_key, (context,) + cls.path) def pop(cls): path = cls.path setattr(thread_local, cls.__path_key, path[1:]) return path[0] class ContextBase(metaclass=ContextMeta): def __enter__(self): self._cls.push(self) setattr(thread_local, self.__entered_key, True) self._on_enter() return self def __exit__(self, exc_type, exc_val, exc_tb): try: self._on_exit(exc_type, exc_val, exc_tb) finally: self._cls.pop() setattr(thread_local, self.__entered_key, False) async def __aenter__(self): self._cls.push(self) setattr(thread_local, self.__entered_key, True) await self._on_aenter() return self async def __aexit__(self, exc_type, exc_val, exc_tb): try: await self._on_aexit(exc_type, exc_val, exc_tb) finally: self._cls.pop() setattr(thread_local, self.__entered_key, False) @property def __entered_key(self) -> str: return '{}_entered'.format(id(self)) @property def _cls(self) -> ContextMeta: seen = set() stack = [self.__class__] cls = None while stack: base = stack.pop() if ContextBase in base.__bases__ or ContextBaseWithDefault in base.__bases__: cls = base break if base not in seen: seen.add(base) stack.extend(b for b in base.__bases__ if issubclass(b, ContextBase)) return cls or self.__class__ @property def is_entered(self) -> bool: return getattr(thread_local, self.__entered_key, False) def _on_enter(self): pass def _on_exit(self, exc_type, exc_val, exc_tb): pass async def _on_aenter(self): pass async def _on_aexit(self, exc_type, exc_val, exc_tb): pass class ContextBaseWithDefault(ContextBase): @classmethod def default_value(cls) -> object: return cls() try: from contextlib import nullcontext except ImportError: from contextlib import AbstractContextManager class nullcontext(AbstractContextManager): def __init__(self, enter_result=None): self.enter_result = enter_result def __enter__(self): return self.enter_result def __exit__(self, exc_type, exc_val, exc_tb): pass ================================================ FILE: gs_quant/data/__init__.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from .coordinate import DataCoordinate from .core import DataContext, DataFrequency from .dataset import Dataset, PTPDataset from .fields import DataMeasure, DataDimension, Fields, AssetMeasure __name__ = 'data' ================================================ FILE: gs_quant/data/coordinate.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import json import uuid from abc import ABCMeta from enum import Enum from typing import Union, Dict, Tuple, Optional, List import pandas as pd from .core import DataContext, DataFrequency, DataAggregationOperator from .dataset import Dataset from .fields import DataMeasure, DataDimension DataDimensions = Dict[Union[DataDimension, str], Union[str, float]] DateOrDatetime = Union[dt.date, dt.datetime] class BaseDataCoordinate(metaclass=ABCMeta): """Base class for data coordinates""" __slots__ = ['__measure', '__dimensions'] def __init__(self, measure: Union[DataMeasure, str], dimensions: Optional[DataDimensions] = None): self.__measure = measure # Sorted so different dimension orders doesn't matter if dimensions is None: self.__dimensions = tuple() else: self.__dimensions = tuple( sorted({k.value if isinstance(k, Enum) else k: v for k, v in dimensions.items()}.items()) ) @property def measure(self) -> DataMeasure: return self.__measure @property def dimensions(self) -> Dict: return dict(self.__dimensions) def get_series(self, start: Optional[DateOrDatetime] = None, end: Optional[DateOrDatetime] = None): pass def set_dimensions(self, dimensions: DataDimensions): """Set further dimensions that weren't defined at initialization :param dimensions: Additional dimensions to add ** Usage ** For datagrids, we may not know dimension asset_id at point of initialization """ _dimensions = self.dimensions for key, v in dimensions.items(): _dimensions[key.value if isinstance(key, Enum) else key] = v self.__dimensions = tuple(sorted(_dimensions.items())) class DataCoordinate(BaseDataCoordinate): """A coordinate which locates a given datapoint through time""" __slots__ = ['__dataset_id', '__frequency', '__id'] def __init__( self, measure: Union[DataMeasure, str], dataset_id: Optional[str] = None, dimensions: Optional[DataDimensions] = None, frequency: Optional[DataFrequency] = None, ): """Initialize data coordinate :param dataset_id: Unique identifier for dataset :param measure: Unique identifier for data measure (dataset numerical field to query) :param dimensions: dict of dimensions to uniquely query dataset (e.g. keys) ** Usage ** A DataCoordinate uniquely identifies a single timeseries on the GS Data Platform. Any dataset has a given set of dimensions (keys or identifiers over which to query) and measures (values which can be aggregated). A single coordinate specifies the identifier of the dataset, values of each required dimension through a dict, and a specific data measure (field). This can be used as a unique reference to query a timeseries on the platform over a given time range or sample point-in-time. DataCoordinates are immutable and can therefore be compared for equality """ super().__init__(measure, dimensions) self.__dataset_id = dataset_id self.__frequency = frequency self.__id = str(uuid.uuid4()) @property def dataset_id(self) -> str: return self.__dataset_id @property def frequency(self) -> DataFrequency: return self.__frequency @property def id(self) -> str: return self.__id @id.setter def id(self, value): self.__id = value def __eq__(self, other): """Determine if coordinates are equal :param other: other object ** Usage ** Equality check for two coordinates. Validates if the dataset id, data measure and dimensions are equivalent. """ measure = self.measure if isinstance(self.measure, str) else self.measure.value other_measure = other.measure if isinstance(other.measure, str) else other.measure.value return (self.dataset_id, measure, self.dimensions) == (other.dataset_id, other_measure, other.dimensions) def get_dimensions(self) -> Tuple: return tuple(self.dimensions.items()) def __hash__(self): return hash((self.dataset_id, self.measure, tuple(self.dimensions))) def __str__(self): return ( f'Dataset Id: ({self.dataset_id}) ' f'Measure: ({self.measure if isinstance(self.measure, str) else self.measure.value}) ' f'Dimensions: ({json.dumps(self.dimensions)})' ) def get_range( self, start: Optional[DateOrDatetime] = None, end: Optional[DateOrDatetime] = None ) -> Tuple[Optional[DateOrDatetime], Optional[DateOrDatetime]]: if start is None: start = ( DataContext.current.start_time if self.frequency is DataFrequency.REAL_TIME else DataContext.current.start_date ) if end is None: end = ( DataContext.current.end_time if self.frequency is DataFrequency.REAL_TIME else DataContext.current.end_date ) return start, end def get_series( self, start: Optional[DateOrDatetime] = None, end: Optional[DateOrDatetime] = None, dates: List[dt.date] = None, operator: DataAggregationOperator = None, ) -> Union[pd.Series, None]: """Get timeseries of coordinate""" if not self.dataset_id: return None dataset = Dataset(self.dataset_id) start, end = self.get_range(start, end) measure = self.measure if operator: measure = f'{operator}({measure})' return dataset.get_data_series(measure, start=start, end=end, dates=dates, **self.dimensions) def last_value(self, before: Optional[DateOrDatetime] = None) -> Union[float, None]: """Return the last available value Returns the last value prior to to the specified date / time. If `before` argument is not provided, will return the last value prior to the end date / time of the current data context """ if not self.dataset_id: return None start, end = self.get_range(None, before) dataset = Dataset(self.dataset_id) measure = self.measure.value if isinstance(self.measure, Enum) else self.measure return dataset.get_data_last(end, fields=[measure], **self.dimensions).get(measure, default=None) def as_dict(self): dimensions = {} for key, value in self.dimensions.items(): if isinstance(key, Enum): dimensions[key.value] = value else: dimensions[key] = value coordinate = { 'measure': self.measure.value if isinstance(self.measure, Enum) else self.measure, 'frequency': self.frequency.value, 'id': self.id, } if self.dataset_id: coordinate['datasetId'] = self.dataset_id if dimensions: coordinate['dimensions'] = dimensions return coordinate @classmethod def from_dict(cls, obj): measure = obj.get('measure') dimensions = obj.get('dimensions', {}) frequency = obj.get('frequency') dataset_id = obj.get('datasetId') id_ = obj.get('id') measure = DataMeasure(measure) if measure in DataMeasure._value2member_map_ else measure parsed_dimensions = {} data_dimension_map = DataDimension._value2member_map_ for key, value in dimensions.items(): if key in data_dimension_map: parsed_dimensions[DataDimension(key)] = value else: parsed_dimensions[key] = value coordinate = ( DataCoordinate( dataset_id=dataset_id, measure=measure, dimensions=parsed_dimensions, frequency=DataFrequency(frequency) ) if dataset_id else DataCoordinate(measure=measure, dimensions=parsed_dimensions, frequency=DataFrequency(frequency)) ) if id_: coordinate.id = id_ return coordinate ================================================ FILE: gs_quant/data/core.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import re from enum import Enum from gs_quant.context_base import ContextBaseWithDefault from gs_quant.errors import MqTypeError, MqValueError def _now(): return dt.datetime.now(dt.timezone.utc) class DataFrequency(Enum): """Data Frequency enumeration Enumeration of different data frequencies for field subscription """ #: Data subscription for series updating daily DAILY = 'daily' #: Data subscription for real-time or intraday series REAL_TIME = 'realTime' #: Data subscription for real-time or daily series ANY = 'any' class DataAggregationOperator: MIN = 'min' MAX = 'max' FIRST = 'first' LAST = 'last' class IntervalFrequency(Enum): DAILY = 'daily' WEEKLY = 'weekly' MONTHLY = 'monthly' YEARLY = 'yearly' class DataContext(ContextBaseWithDefault): def __init__(self, start=None, end=None, interval=None): super().__init__() self.__start = start self.__end = end if interval is None: self.__interval = None return if not isinstance(interval, str): raise MqTypeError('interval must be a str') if not re.fullmatch('[1-9]\\d{0,2}[a-z]', interval): raise MqValueError('interval must be a valid str e.g. 1m, 2h, 3d') self.__interval = interval @staticmethod def _get_date(o, default): if o is None: return default elif isinstance(o, dt.datetime): # note that datetime objects are also instances of date return o.date() elif isinstance(o, dt.date): return o elif isinstance(o, str): loc = o.find('T') ds = o[:loc] if loc != -1 else o return dt.datetime.strptime(ds, '%Y-%m-%d').date() else: raise ValueError(f'{o} is not a valid date') @staticmethod def _get_datetime(o, default): if o is None: return default elif isinstance(o, dt.datetime): return o elif isinstance(o, dt.date): return dt.datetime.combine(o, dt.time(tzinfo=dt.timezone.utc)) elif isinstance(o, str): tmp = dt.datetime.strptime(o, '%Y-%m-%dT%H:%M:%SZ') return tmp.replace(tzinfo=dt.timezone.utc) else: raise ValueError(f'{o} is not a valid date') @property def start_date(self): return self._get_date(self.__start, dt.date.today() - dt.timedelta(days=30)) @property def end_date(self): return self._get_date(self.__end, dt.date.today()) @property def start_time(self): return self._get_datetime(self.__start, _now() - dt.timedelta(days=1)) @property def end_time(self): return self._get_datetime(self.__end, _now()) @property def interval(self): return self.__interval if __name__ == '__main__': with DataContext(dt.date(2019, 1, 1), dt.datetime(2019, 2, 1, tzinfo=dt.timezone.utc)) as dc: print(f'{dc.start_date}, {dc.end_date}') print(f'{dc.start_time}, {dc.end_time}') with DataContext(None, '2019-01-01T00:00:00Z') as dc2: print(f'{dc2.start_date}, {dc2.end_date}') print(f'{dc2.start_time}, {dc2.end_time}') ================================================ FILE: gs_quant/data/dataset.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import re import webbrowser from enum import Enum from typing import Iterable, Optional, Union, List, Dict, Callable from urllib.parse import quote import inflection import numpy as np import pandas as pd from pydash import camel_case, snake_case from gs_quant.api.data import DataApi from gs_quant.api.gs.users import GsUsersApi from gs_quant.data.fields import Fields from gs_quant.errors import MqValueError from gs_quant.session import GsSession from functools import partial from gs_quant.data.utilities import Utilities from gs_quant.target.data import ( DataSetEntity, DataSetParameters, DataSetDimensions, FieldColumnPair, DataSetFieldEntity, DBConfig, DataSetType, ) class InvalidInputException(Exception): pass class Dataset: """A collection of related data""" class Vendor(Enum): pass class GS(Vendor): HOLIDAY = 'HOLIDAY' HOLIDAY_CURRENCY = 'HOLIDAY_CURRENCY' EDRVOL_PERCENT_INTRADAY = 'EDRVOL_PERCENT_INTRADAY' EDRVOL_PERCENT_STANDARD = 'EDRVOL_PERCENT_STANDARD' MA_RANK = 'MA_RANK' EDRVS_INDEX_SHORT = 'EDRVS_INDEX_SHORT' EDRVS_INDEX_LONG = 'EDRVS_INDEX_LONG' # Baskets CBGSSI = 'CBGSSI' CB = 'CB' # STS STSLEVELS = 'STSLEVELS' # Central Bank Watch CENTRAL_BANK_WATCH = 'CENTRAL_BANK_WATCH_PREMIUM' IR_SWAP_RATES_INTRADAY_CALC_BANK = 'IR_SWAP_RATES_INTRADAY_CALC_BANK' RETAIL_FLOW_DAILY_V2_PREMIUM = 'RETAIL_FLOW_DAILY_V2_PREMIUM' FX_EVENTS_JUMPS = 'FX_EVENTS_JUMPS' FXSPOT_INTRADAY2 = 'FXSPOT_INTRADAY2' FXFORWARDPOINTS_PREMIUM = 'FXFORWARDPOINTS_PREMIUM' FXFORWARDPOINTS_INTRADAY = 'FXFORWARDPOINTS_INTRADAY' # Test Datasets WEATHER = 'WEATHER' # TCA QES_INTRADAY_COVARIANCE = 'QES_INTRADAY_COVARIANCE_PREMIUM' class TR(Vendor): TREOD = 'TREOD' TR = 'TR' TR_FXSPOT = 'TR_FXSPOT' class FRED(Vendor): GDP = 'GDP' class TradingEconomics(Vendor): MACRO_EVENTS_CALENDAR = 'MACRO_EVENTS_CALENDAR' def __init__(self, dataset_id: Union[str, Vendor], provider: Optional[DataApi] = None): """ :param dataset_id: The dataset's identifier :param provider: The data provider """ self.__id = self._get_dataset_id_str(dataset_id) self.__provider = provider def _get_dataset_id_str(self, dataset_id): return dataset_id.value if isinstance(dataset_id, Dataset.Vendor) else dataset_id @property def id(self) -> str: """ The dataset's identifier """ return self.__id @property def name(self): pass @property def provider(self): from gs_quant.api.gs.data import GsDataApi return self.__provider or GsDataApi def _build_data_query( self, start: Union[dt.date, dt.datetime], end: Union[dt.date, dt.datetime], as_of: dt.datetime, since: dt.datetime, fields: Iterable[Union[str, Fields]], empty_intervals: bool, **kwargs, ): field_names = None if fields is None else list(map(lambda f: f if isinstance(f, str) else f.value, fields)) # check whether a function is called e.g. difference(tradePrice) schema_varies = field_names is not None and any(map(lambda s: re.match("\\w+\\(", s), field_names)) if kwargs and "date" in kwargs: d = kwargs["date"] if type(d) is str: try: kwargs["date"] = dt.datetime.strptime(d, "%Y-%m-%d").date() except ValueError: pass # Ignore error if date parameter is in some other format if "dates" not in kwargs and start is None and end is None: kwargs["dates"] = (kwargs["date"],) return self.provider.build_query( start=start, end=end, as_of=as_of, since=since, fields=field_names, empty_intervals=empty_intervals, **kwargs, ), schema_varies def _build_data_frame(self, data, schema_varies, standard_fields) -> pd.DataFrame: if type(data) is tuple: df = self.provider.construct_dataframe_with_types( self.id, data[0], schema_varies, standard_fields=standard_fields ) return df.groupby(data[1], group_keys=True).apply(lambda x: x) else: return self.provider.construct_dataframe_with_types( self.id, data, schema_varies, standard_fields=standard_fields ) def get_data( self, start: Optional[Union[dt.date, dt.datetime]] = None, end: Optional[Union[dt.date, dt.datetime]] = None, as_of: Optional[dt.datetime] = None, since: Optional[dt.datetime] = None, fields: Optional[Iterable[Union[str, Fields]]] = None, asset_id_type: Optional[str] = None, empty_intervals: Optional[bool] = None, standard_fields: Optional[bool] = False, **kwargs, ) -> pd.DataFrame: """ Get data for the given range and parameters :param start: Requested start date/datetime for data :param end: Requested end date/datetime for data :param as_of: Request data as_of :param since: Request data since :param fields: DataSet fields to include :param empty_intervals: whether to request empty intervals :param standard_fields: If set, will use fields api instead of catalog api to get fieldTypes :param kwargs: Extra query arguments, e.g. ticker='EDZ19' :return: A Dataframe of the requested data **Examples** >>> from gs_quant.data import Dataset >>> import datetime as dt >>> >>> weather = Dataset('WEATHER') >>> weather_data = weather.get_data(dt.date(2016, 1, 15), dt.date(2016, 1, 16), city=('Boston', 'Austin')) """ query, schema_varies = self._build_data_query(start, end, as_of, since, fields, empty_intervals, **kwargs) data = self.provider.query_data(query, self.id, asset_id_type=asset_id_type) return self._build_data_frame(data, schema_varies, standard_fields) async def get_data_async( self, start: Optional[Union[dt.date, dt.datetime]] = None, end: Optional[Union[dt.date, dt.datetime]] = None, as_of: Optional[dt.datetime] = None, since: Optional[dt.datetime] = None, fields: Optional[Iterable[Union[str, Fields]]] = None, empty_intervals: Optional[bool] = None, standard_fields: Optional[bool] = False, **kwargs, ) -> pd.DataFrame: """ Get data for the given range and parameters :param start: Requested start date/datetime for data :param end: Requested end date/datetime for data :param as_of: Request data as_of :param since: Request data since :param fields: DataSet fields to include :param empty_intervals: whether to request empty intervals :param standard_fields: If set, will use fields api instead of catalog api to get fieldTypes :param kwargs: Extra query arguments, e.g. ticker='EDZ19' :return: A Dataframe of the requested data **Examples** >>> from gs_quant.data import Dataset >>> import datetime as dt >>> >>> weather = Dataset('WEATHER') >>> weather_data = await weather.get_data_async(dt.date(2016, 1, 15), dt.date(2016, 1, 16), >>> city=('Boston', 'Austin')) """ query, schema_varies = self._build_data_query(start, end, as_of, since, fields, empty_intervals, **kwargs) data = await self.provider.query_data_async(query, self.id) return self._build_data_frame(data, schema_varies, standard_fields) def _build_data_series_query( self, field: Union[str, Fields], start: Union[dt.date, dt.datetime], end: Union[dt.date, dt.datetime], as_of: dt.datetime, since: dt.datetime, dates: List[dt.date], **kwargs, ): field_value = field if isinstance(field, str) else field.value query = self.provider.build_query( start=start, end=end, as_of=as_of, since=since, fields=(field_value,), dates=dates, **kwargs ) symbol_dimensions = self.provider.symbol_dimensions(self.id) if len(symbol_dimensions) != 1: raise MqValueError('get_data_series only valid for symbol_dimensions of length 1') symbol_dimension = symbol_dimensions[0] return field_value, query, symbol_dimension def _build_data_series(self, data, field_value, symbol_dimension, standard_fields: bool) -> pd.Series: df = self.provider.construct_dataframe_with_types(self.id, data, standard_fields=standard_fields) from gs_quant.api.gs.data import GsDataApi if isinstance(self.provider, GsDataApi): gb = df.groupby(symbol_dimension) if len(gb.groups) > 1: raise MqValueError('Not a series for a single {}'.format(symbol_dimension)) if df.empty: return pd.Series(dtype=float) if '(' in field_value: field_value = field_value.replace('(', '_') field_value = field_value.replace(')', '') return pd.Series(index=df.index, data=df.loc[:, field_value].values) def get_data_series( self, field: Union[str, Fields], start: Optional[Union[dt.date, dt.datetime]] = None, end: Optional[Union[dt.date, dt.datetime]] = None, as_of: Optional[dt.datetime] = None, since: Optional[dt.datetime] = None, dates: Optional[List[dt.date]] = None, standard_fields: Optional[bool] = False, **kwargs, ) -> pd.Series: """ Get a time series of data for a field of a dataset :param field: The DataSet field to use :param start: Requested start date/datetime for data :param end: Requested end date/datetime for data :param as_of: Request data as_of :param since: Request data since :param dates: Requested dates for data :param standard_fields: If set, will use fields api instead of catalog api to get fieldTypes :param kwargs: Extra query arguments, e.g. ticker='EDZ19' :return: A Series of the requested data, indexed by date or time, depending on the DataSet **Examples** >>> from gs_quant.data import Dataset >>> import datetime as dt >>> >>> weather = Dataset('WEATHER') >>> dew_point = weather >>>> .get_data_series('dewPoint', dt.date(2016, 1, 15), dt.date(2016, 1, 16), city=('Boston', 'Austin')) """ field_value, query, symbol_dimension = self._build_data_series_query( field, start, end, as_of, since, dates, **kwargs ) data = self.provider.query_data(query, self.id) return self._build_data_series(data, field_value, symbol_dimension, standard_fields) async def get_data_series_async( self, field: Union[str, Fields], start: Optional[Union[dt.date, dt.datetime]] = None, end: Optional[Union[dt.date, dt.datetime]] = None, as_of: Optional[dt.datetime] = None, since: Optional[dt.datetime] = None, dates: Optional[List[dt.date]] = None, standard_fields: Optional[bool] = False, **kwargs, ) -> pd.Series: """ Get a time series of data for a field of a dataset :param field: The DataSet field to use :param start: Requested start date/datetime for data :param end: Requested end date/datetime for data :param as_of: Request data as_of :param since: Request data since :param dates: Requested dates for data :param standard_fields: If set, will use fields api instead of catalog api to get fieldTypes :param kwargs: Extra query arguments, e.g. ticker='EDZ19' :return: A Series of the requested data, indexed by date or time, depending on the DataSet **Examples** >>> from gs_quant.data import Dataset >>> import datetime as dt >>> >>> weather = Dataset('WEATHER') >>> dew_point = await weather.get_data_series_async('dewPoint', dt.date(2016, 1, 15), dt.date(2016, 1, 16), >>> city=('Boston', 'Austin')) """ field_value, query, symbol_dimension = self._build_data_series_query( field, start, end, as_of, since, dates, **kwargs ) data = await self.provider.query_data_async(query, self.id) return self._build_data_series(data, field_value, symbol_dimension, standard_fields) def get_data_last( self, as_of: Optional[Union[dt.date, dt.datetime]], start: Optional[Union[dt.date, dt.datetime]] = None, fields: Optional[Iterable[str]] = None, standard_fields: Optional[bool] = False, **kwargs, ) -> pd.DataFrame: """ Get the last point for this DataSet, at or before as_of :param as_of: The date or time as of which to query :param start: The start of the range to query :param fields: The fields for which to query :param standard_fields: If set, will use fields api instead of catalog api to get fieldTypes :param kwargs: Additional query parameters, e.g., city='Boston' :return: A Dataframe of values **Examples** >>> from gs_quant.data import Dataset >>> import datetime as dt >>> >>> weather = Dataset('WEATHER') >>> last = weather.get_data_last(dt.datetime.now()) """ query = self.provider.build_query(start=start, end=as_of, fields=fields, format='JSON', **kwargs) query.format = None # "last" endpoint does not support MessagePack data = self.provider.last_data(query, self.id) return self.provider.construct_dataframe_with_types(self.id, data, standard_fields=standard_fields) def get_coverage( self, limit: Optional[int] = None, offset: Optional[int] = None, fields: Optional[List[str]] = None, include_history: bool = False, **kwargs, ) -> pd.DataFrame: """ Get the assets covered by this DataSet :param limit: The maximum number of assets to return :param offset: The offset :param fields: The fields to return, e.g. assetId :param include_history: Return column for historyStartDate :return: A Dataframe of the assets covered **Examples** >>> from gs_quant.data import Dataset >>> >>> weather = Dataset('WEATHER') >>> cities = weather.get_coverage() """ coverage = self.provider.get_coverage( self.id, limit=limit, offset=offset, fields=fields, include_history=include_history, **kwargs ) return pd.DataFrame(coverage) async def get_coverage_async( self, limit: Optional[int] = None, offset: Optional[int] = None, fields: Optional[List[str]] = None, include_history: bool = False, **kwargs, ) -> pd.DataFrame: """ Get the assets covered by this DataSet :param limit: The maximum number of assets to return :param offset: The offset :param fields: The fields to return, e.g. assetId :param include_history: Return column for historyStartDate :return: A Dataframe of the assets covered **Examples** >>> from gs_quant.data import Dataset >>> >>> weather = Dataset('WEATHER') >>> cities = await weather.get_coverage_async() """ coverage = await self.provider.get_coverage_async( self.id, limit=limit, offset=offset, fields=fields, include_history=include_history, **kwargs ) return pd.DataFrame(coverage) def delete(self) -> Dict: """ Delete dataset definition. Needs 'modify_profuct_data' to execute operation. >>> from gs_quant.data import Dataset >>> >>> test_dataset = Dataset('TEST') >>> test_dataset.delete() """ return self.provider.delete_dataset(self.id) def undelete(self) -> Dict: """ Un-delete dataset definition. Needs 'modify_profuct_data' to execute operation. >>> from gs_quant.data import Dataset >>> >>> test_dataset = Dataset('TEST') >>> test_dataset.undelete() """ return self.provider.undelete_dataset(self.id) def delete_data(self, delete_query: Dict): """ Delete data from dataset. You must have admin access to the dataset to delete data. All data deleted is not recoverable. :param delete_query: Query to specify data to be deleted. >>> from gs_quant.data import Dataset >>> >>> test_dataset = Dataset('TEST') >>> delete_query = {'startDate': dt.date.today(), 'endDate': dt.date.today(), 'deleteAll': True} >>> test_dataset.delete_data(delete_query) """ return self.provider.delete_data(self.id, delete_query) def upload_data(self, data: Union[pd.DataFrame, list, tuple]) -> Dict: """ Upload data to this DataSet :param data: data to be uploaded to the dataset **Examples** >>> from gs_quant.data import Dataset >>> >>> weather = Dataset('WEATHER') >>> data = [{ >>> "date": "2016-12-31", >>> "city": "Chicago", >>> "maxTemperature": 40.0, >>> "minTemperature": 23.0, >>> "dewPoint": 21.0, >>> "windSpeed": 11.4, >>> "precipitation": 0.0, >>> "snowfall": 0.0, >>> "pressure": 29.03, >>> "updateTime": "2017-03-06T16:49:39.493Z" >>> }] >>> upload_response = weather.upload_data(data) """ return self.provider.upload_data(self.id, data) def get_data_bulk( self, request_batch_size, original_start: dt.datetime, final_end: Optional[dt.datetime] = None, identifier="bbid", symbols_per_csv: int = 1000, datetime_delta_override: Optional[int] = None, handler: Optional[Callable[[pd.DataFrame], None]] = None, ): """ Extracts data from dataset by running parallel queries in the background :param request_batch_size: Used to group number of symbols per batch run (> 0 and <5) :param original_start: Start date to fetch the data (mandatory) :param final_end: End date to fetch the data. If not entered defaults to current datetime :param identifier: Use specific identifier as per dataset configuration. ex. cusip, bbid, clusterRegion :param symbols_per_csv: Number of symbols per CSV (recommended value = 1000) :param datetime_delta_override: A numeric parameter to increment start date and fetch data in batches units are days for daily datasets and hours for intraday :param handler: A callable function, if provided, to handle dataframe instead of writing to CSV **Examples** >>> import datetime as dt >>> from gs_quant.data import Dataset >>> dataset_id = "EQTRADECLUSTERS" >>> original_start = dt.datetime(2023, 3, 1,0,0,0) >>> final_end = dt.datetime(2023, 3, 1,0,0,0) >>> c = Dataset(dataset_id) >>> c.get_data_bulk(original_start=original_start, >>> final_end=final_end, >>> datetime_delta_override=1, >>> request_batch_size=4, >>> identifier="clusterRegion") """ try: authenticate = partial( GsSession.use, client_id=GsSession.current.client_id, client_secret=GsSession.current.client_secret ) except AttributeError: authenticate = partial(GsSession.use) time_field, history_time, symbol_dimension, timedelta = Utilities.get_dataset_parameter(self) final_end = final_end or dt.datetime.now() write_to_csv = handler is None final_end, target_dir_result = Utilities.pre_checks( final_end, original_start, time_field, datetime_delta_override, request_batch_size, write_to_csv ) if write_to_csv: print("Target Destination Folder: ", target_dir_result) if time_field == 'date': original_start = max(original_start.date(), history_time.date()) final_end = max(final_end.date(), history_time.date()) datetime_delta_override = ( timedelta if datetime_delta_override is None else dt.timedelta(days=datetime_delta_override) ) elif time_field == 'time': original_start = max(original_start.astimezone(dt.timezone.utc), history_time.astimezone(dt.timezone.utc)) final_end = max(final_end.astimezone(dt.timezone.utc), history_time.astimezone(dt.timezone.utc)) datetime_delta_override = ( timedelta if datetime_delta_override is None else dt.timedelta(hours=datetime_delta_override) ) original_end = min(original_start + datetime_delta_override, final_end) coverage = Utilities.get_dataset_coverage(identifier, symbol_dimension, self) coverage_batches = Utilities.batch(coverage, n=symbols_per_csv) batch_number = 1 coverage_length = len(coverage) for coverage_batch in coverage_batches: Utilities.iterate_over_series( self, coverage_batch, original_start, original_end, datetime_delta_override, identifier, request_batch_size, authenticate, final_end, write_to_csv, target_dir_result, batch_number, coverage_length, symbols_per_csv, handler, ) batch_number += 1 class PTPDataset(Dataset): """ Special class for and viewing PTP-style datasets. PTP provides a wrapper around the data service that makes it easy to define datasets, upload data, and plot them in PlotTool Pro. PTP datasets can only contain numeric data with a pd.DatetimeIndex (will throw an error otherwise). Currently, we only support EOD data; realtime data is not allowed. (Technically, PTP datasets are datasets with one symbol dimension, which is the dataset ID. Tags indicate that they are PTP-style datasets and can be viewed in the 'Datasets' tab in PTP). :param series: pd.DataFrame or pd.Series with a pd.DatetimeIndex containing EOD numeric data. :param name: Name of dataset (default "GSQ Default") **Example** >>> from gs_quant.data import PTPDataset >>> import datetime >>> a = pd.Series(range(50), index=pd.date_range(start=datetime.date(2021, 1, 1), periods=50, freq='D')) >>> dataset = PTPDataset(a, 'My Dataset') >>> dataset.sync() >>> print(dataset.plot()) >>> dataset.delete() """ def __init__(self, series: Union[pd.Series, pd.DataFrame], name: Optional[str] = None): if isinstance(series, pd.Series): series = pd.DataFrame({series.attrs.get('name', 'values'): series}) if not isinstance(series.index, pd.DatetimeIndex): raise MqValueError('PTP datasets require a Pandas object with a DatetimeIndex.') if isinstance(series, pd.DataFrame) and len(series.select_dtypes(include=np.number).columns) != len( series.columns ): raise MqValueError('PTP datasets must contain only numbers.') self._series = series self._name = name super(PTPDataset, self).__init__('', None) def sync(self): """ Upload data and save dataset. """ temp_ser = self._series.assign(date=self._series.index.to_series().apply(dt.date.isoformat)) data = temp_ser.to_dict('records') kwargs = dict(data=data, name=self._name if self._name else 'GSQ Default', fields=list(self._series.columns)) res = GsSession.current.sync.post('/plots/datasets', payload=kwargs) self._fields = { key: inflection.underscore(field) for key, field in res['fieldMap'].items() if field not in ['updateTime', 'date', 'datasetId'] } self._id = res['dataset']['id'] super(PTPDataset, self).__init__(self._id, None) def plot(self, open_in_browser: bool = True, field: Optional[str] = None) -> str: """ Generate transient plot expression to view dataset in PTP. Copying and pasting the transient expression will show your data in PTP. :param open_in_browser: whether to use webbrowser to open the generated plot expression in your default browser (default True) :param field: if passed, only this field will be included in the transient plot (default None) :return: transient plot expression. """ if not field: fields = self._fields.values() else: fields = [field] fields = [inflection.underscore(re.sub(r'([a-zA-Z])(\d)', r'\1_\2', f)) for f in fields] domain = GsSession.current.domain.replace('marquee.web', 'marquee') # remove .web from prod domain expression = f'{domain}/s/plottool/transient?expr=Dataset("{self._id}").{fields[0]}()' for f in fields[1:]: expression += quote("\n") + f'Dataset("{self._id}").{f}()' if open_in_browser: webbrowser.open(expression) return expression class MarqueeDataIngestionLibrary: """ A class to create a native snowflake datasets and upload data from dataframe. **Example Usage:** >>> from gs_quant.data import MarqueeDataIngestionLibrary >>> import pandas as pd >>> data = pd.DataFrame({ >>> "date": ["2023-01-01", "2023-01-02", "2023-01-03"], >>> "symbol": ["AAPL", "GOOG", "MSFT"], >>> "price": [150.0, 2800.0, 310.0], >>> "volume": [1000, 2000, 1500] >>> }) >>> ingestion_lib = MarqueeDataIngestionLibrary() >>> dataset = ingestion_lib.create_dataset(data, "DATASET_NAME", "SYMBOL_DIMENSION", "date", >>> dimensions=["volume", "measure2", "measure3"]) """ def __init__(self, provider: Optional[DataApi] = None): """ :param provider: The data provider """ self.__provider = provider self.user = GsUsersApi.get_current_user_info() self.managers = GsUsersApi.get_current_app_managers() @property def provider(self): from gs_quant.api.gs.data import GsDataApi return self.__provider or GsDataApi def _create_parameters( self, time_dimension: str, symbol_dimension: str, is_internal_user: bool ) -> DataSetParameters: """ Create the parameters for the dataset. :return: A DataSetParameters object. """ parameters = DataSetParameters() parameters.frequency = 'Daily' parameters.snowflake_config = DBConfig() parameters.snowflake_config.db = "TIMESERIES" if is_internal_user else "EXTERNAL" parameters.snowflake_config.date_time_column = self.to_upper_underscore(time_dimension) parameters.snowflake_config.id_column = self.to_upper_underscore(symbol_dimension) return parameters def _check_and_create_field(self, fieldMap: Dict[str, str], dataframe: pd.DataFrame) -> None: fields_to_create = [] for column, field_name in fieldMap.items(): if not (self.provider.get_dataset_fields(names=field_name)): data_type = pd.api.types.infer_dtype(dataframe[column]) api_data_type = 'number' if data_type in ['floating', 'integer'] else data_type fields_to_create.append( DataSetFieldEntity( name=field_name, type_=api_data_type, description=f'field {field_name} created from GSQuant' ) ) if fields_to_create: return self.provider.create_dataset_fields(fields_to_create) def _create_dimensions( self, data: pd.DataFrame, symbol_dimension: str, time_dimension: str, dimensions: Optional[List[str]], measures: List[str], internal_user: bool, ) -> DataSetDimensions: """ Create the dimensions for the dataset. :param data: DataFrame containing the data to be ingested. :param symbol_dimension: List of columns that represent the symbol dimension. :param time_dimension: Column that represents the time dimension. :param dimensions: Optional List of non-symbol dimensions. :return: A DataSetDimensions object. """ if len(measures) > 25: raise ValueError("The number of measures exceeds the allowed limit of 25.") dataset_dimensions = DataSetDimensions() dataset_dimensions.time_field = time_dimension dataset_dimensions.symbol_dimensions = [symbol_dimension] if internal_user: fieldMap = {field: self.to_camel_case(field) for field in (dimensions + measures)} else: drgName = self.user.get("drgName") if drgName is None: raise InvalidInputException("drgName is required but was not found.") INVALID_DRG_NAME_CHARS = r"Pvt Ltd.*|Private Ltd.*|Limited.*|Ltd.*|Inc.*|LP$|LLP$|[^a-zA-Z0-9]" drgName = re.sub(INVALID_DRG_NAME_CHARS, "", drgName) fieldMap = { field: self.to_camel_case((field if field == 'updateTime' else f"{field}Org{drgName}")[:64]) for field in (dimensions + measures) } self._check_and_create_field(fieldMap, data) dataset_dimensions.non_symbol_dimensions = tuple( FieldColumnPair(field_=fieldMap.get(dim), column=self.to_upper_underscore(dim)) for dim in (dimensions or []) ) dataset_dimensions.measures = tuple( FieldColumnPair( field_=fieldMap.get(mea), column=self.to_upper_underscore(mea), resolvable=True if mea != 'updateTime' else None, ) for mea in measures ) return dataset_dimensions def to_upper_underscore(self, name: str) -> str: return snake_case(name).upper() def to_camel_case(self, name: str) -> str: return camel_case(name) def create_dataset( self, data: pd.DataFrame, dataset_id: str, symbol_dimension: str, time_dimension: str, dimensions: Optional[List[str]] = [], ) -> DataSetEntity: """ Create a dataset using the provided data and metadata. This method creates a dataset in the Marquee platform using the provided DataFrame and metadata. The dataset is defined by its unique identifier, symbol dimension, time dimension, and optional non-symbol dimensions. The data is validated to ensure compatibility with the platform's requirements. :param data: DataFrame containing the data to be ingested. The DataFrame must include the symbol and time dimensions, along with any additional dimensions or measures. :param dataset_id: The unique identifier for the dataset. This identifier is used to reference the dataset in the platform. :param symbol_dimension: Column name in the DataFrame that represents the symbol dimension. Valid symbol dimensions are: {"isin", "bbid", "ric", "sedol", "cusip", "ticker", "countryId", "currency"}. :param time_dimension: Column name in the DataFrame that represents the time dimension. This must be a date column, as intraday timestamps are not supported. :param dimensions: Optional list of column names in the DataFrame that represent non-symbol dimensions. :return: A DataSetEntity object representing the dataset specification. """ if data.empty or not dataset_id or not symbol_dimension or not time_dimension: print("Error: 'data', 'dataset_id', 'symbol_dimension', and 'time_dimension' are all required.") raise InvalidInputException("One or more required parameters are empty or null.") if not pd.api.types.is_datetime64_any_dtype(pd.to_datetime(data[time_dimension])) or not all( pd.to_datetime(data[time_dimension]).dt.time == dt.time(0, 0) ): raise InvalidInputException( f"Snowflake doesn't support intraday data. The time_dimension " f"'{time_dimension}' must be a date, not a timestamp." ) dataset_definition = DataSetEntity() dataset_definition.id_ = dataset_id dataset_definition.name = dataset_id.replace('_', ' ').title() dataset_definition.description = "Dataset created from GSQuant" all_columns = set(data.columns) specified_columns = set([symbol_dimension] + [time_dimension] + (dimensions or [])) measures = list(all_columns - specified_columns) VALID_TIME_DIMENSION = { # we do not support intraday data for snowflake datasets "date" } VALID_SYMBOL_DIMENSION = {"isin", "bbid", "ric", "sedol", "cusip", "ticker", "countryId", "currency"} internal_user = self.user.get("internal") custom_symbol_dimension = ( "customId" if (not internal_user and self.to_camel_case(symbol_dimension) not in VALID_SYMBOL_DIMENSION) else self.to_camel_case(symbol_dimension) ) custom_time_dimensions = "date" if time_dimension not in VALID_TIME_DIMENSION else time_dimension dataset_definition.parameters = self._create_parameters(time_dimension, symbol_dimension, internal_user) dataset_definition.dimensions = self._create_dimensions( data, custom_symbol_dimension, custom_time_dimensions, dimensions, measures, internal_user ) dataset_definition.type_ = DataSetType.NativeSnowflake result = self.provider.create(dataset_definition) url = self.provider.get_catalog_url(dataset_id) print(f"Dataset created successfully. Access it on Catalog: {url}") return result def write_data(self, df: pd.DataFrame, dataset_id: str) -> None: """ Write data from a DataFrame to the specified dataset. :param df: DataFrame containing the data to be written. :param dataset_id: The identifier of the dataset to write data to. """ if df.empty: raise ValueError("The DataFrame is empty. No data to write.") if not dataset_id: raise ValueError("Dataset ID is required.") # Upload data to the dataset dataset = self.provider.get_definition(dataset_id) allFields = ( dataset.dimensions.non_symbol_dimensions + dataset.dimensions.measures + ( FieldColumnPair( field_=dataset.dimensions.time_field, column=dataset.parameters.snowflake_config.date_time_column ), FieldColumnPair( field_=dataset.dimensions.symbol_dimensions[0], column=dataset.parameters.snowflake_config.id_column ), ) ) renamed_df = df.copy().rename( columns={ column: field.field_ for column in df.columns for field in allFields if self.to_upper_underscore(column) == field.column } ) data = renamed_df.to_dict('records') return self.provider.upload_data(dataset_id, data) ================================================ FILE: gs_quant/data/fields.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from aenum import Enum, extend_enum from typing import Optional from dataclasses_json import LetterCase, dataclass_json from dataclasses import dataclass, field class DataMeasure(Enum): """Data measure definitions Enumeration of measures available through data APIs. Measures are facts that are usually quantities and that can be aggregated over a defined period of time. For example: tradePrice and volume are measures """ ASK_PRICE = 'askPrice' BID_PRICE = 'bidPrice' HIGH_PRICE = 'highPrice' MID_PRICE = 'midPrice' LOW_PRICE = 'lowPrice' OPEN_PRICE = 'openPrice' CLOSE_PRICE = 'closePrice' TRADE_PRICE = 'tradePrice' SPOT_PRICE = 'spot' VOLUME = 'volume' ADJUSTED_ASK_PRICE = 'adjustedAskPrice' ADJUSTED_BID_PRICE = 'adjustedBidPrice' ADJUSTED_HIGH_PRICE = 'adjustedHighPrice' ADJUSTED_LOW_PRICE = 'adjustedLowPrice' ADJUSTED_OPEN_PRICE = 'adjustedOpenPrice' ADJUSTED_CLOSE_PRICE = 'adjustedClosePrice' ADJUSTED_TRADE_PRICE = 'adjustedTradePrice' ADJUSTED_VOLUME = 'adjustedVolume' IMPLIED_VOLATILITY = 'impliedVolatility' VAR_SWAP = 'varSwap' PRICE = 'price' NAV_PRICE = 'navPrice' SPREAD = 'spread' NAV_SPREAD = 'navSpread' IMPLIED_VOLATILITY_BY_DELTA_STRIKE = 'impliedVolatilityByDeltaStrike' FORWARD_POINT = 'forwardPoint' DIVIDEND_YIELD = 'Dividend Yield' EARNINGS_PER_SHARE = 'Earnings per Share' EARNINGS_PER_SHARE_POSITIVE = 'Earnings per Share Positive' NET_DEBT_TO_EBITDA = 'Net Debt to EBITDA' PRICE_TO_BOOK = 'Price to Book' PRICE_TO_CASH = 'Price to Cash' PRICE_TO_EARNINGS = 'Price to Earnings' PRICE_TO_EARNINGS_POSITIVE = 'Price to Earnings Positive' PRICE_TO_EARNINGS_POSITIVE_EXCLUSIVE = 'Price to Earnings Positive Exclusive' PRICE_TO_SALES = 'Price to Sales' RETURN_ON_EQUITY = 'Return on Equity' SALES_PER_SHARE = 'Sales per Share' CURRENT_CONSTITUENTS_DIVIDEND_YIELD = 'Current Constituents Dividend Yield' CURRENT_CONSTITUENTS_EARNINGS_PER_SHARE = 'Current Constituents Earnings per Share' CURRENT_CONSTITUENTS_EARNINGS_PER_SHARE_POSITIVE = 'Current Constituents Earnings per Share Positive' CURRENT_CONSTITUENTS_NET_DEBT_TO_EBITDA = 'Current Constituents Net Debt to EBITDA' CURRENT_CONSTITUENTS_PRICE_TO_BOOK = 'Current Constituents Price to Book' CURRENT_CONSTITUENTS_PRICE_TO_CASH = 'Current Constituents Price to Cash' CURRENT_CONSTITUENTS_PRICE_TO_EARNINGS = 'Current Constituents Price to Earnings' CURRENT_CONSTITUENTS_PRICE_TO_EARNINGS_POSITIVE = 'Current Constituents Price to Earnings Positive' CURRENT_CONSTITUENTS_PRICE_TO_SALES = 'Current Constituents Price to Sales' CURRENT_CONSTITUENTS_RETURN_ON_EQUITY = 'Current Constituents Return on Equity' CURRENT_CONSTITUENTS_SALES_PER_SHARE = 'Current Constituents Sales per Share' ONE_YEAR = '1y' TWO_YEARS = '2y' THREE_YEARS = '3y' FORWARD = 'forward' TRAILING = 'trailing' def __repr__(self): return self.value @classmethod def list_fundamentals(cls): return [ metric.value for metric in [ cls.DIVIDEND_YIELD, cls.EARNINGS_PER_SHARE, cls.EARNINGS_PER_SHARE_POSITIVE, cls.NET_DEBT_TO_EBITDA, cls.PRICE_TO_BOOK, cls.PRICE_TO_CASH, cls.PRICE_TO_EARNINGS, cls.PRICE_TO_EARNINGS_POSITIVE, cls.PRICE_TO_EARNINGS_POSITIVE_EXCLUSIVE, cls.PRICE_TO_SALES, cls.RETURN_ON_EQUITY, cls.SALES_PER_SHARE, cls.CURRENT_CONSTITUENTS_DIVIDEND_YIELD, cls.CURRENT_CONSTITUENTS_EARNINGS_PER_SHARE, cls.CURRENT_CONSTITUENTS_EARNINGS_PER_SHARE_POSITIVE, cls.CURRENT_CONSTITUENTS_NET_DEBT_TO_EBITDA, cls.CURRENT_CONSTITUENTS_PRICE_TO_BOOK, cls.CURRENT_CONSTITUENTS_PRICE_TO_CASH, cls.CURRENT_CONSTITUENTS_PRICE_TO_EARNINGS, cls.CURRENT_CONSTITUENTS_PRICE_TO_EARNINGS_POSITIVE, cls.CURRENT_CONSTITUENTS_PRICE_TO_SALES, cls.CURRENT_CONSTITUENTS_RETURN_ON_EQUITY, cls.CURRENT_CONSTITUENTS_SALES_PER_SHARE, ] ] class DataDimension(Enum): """Data dimension definitions Enumeration of dimensions available through data APIs. Dimensions describe or provide context to measures, and can be used to select or group data. For example: ticker and exchange are dimensions """ ASSET_ID = 'assetId' NAME = 'name' RIC = 'ric' TENOR = 'tenor' STRIKE_REFERENCE = 'strikeReference' RELATIVE_STRIKE = 'relativeStrike' EXPIRATION_DATE = 'expirationDate' UPDATE_TIME = 'updateTime' class Fields(Enum): """Data field enumeration Enumeration of fields available through data APIs """ @property def unit(self) -> Optional[str]: # TODO: Define units and look up appropriate unit for self return None for enum in DataMeasure: extend_enum(Fields, enum.name, enum.value) for enum in DataDimension: extend_enum(Fields, enum.name, enum.value) @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(frozen=True) class AssetMeasure: dataset_field: str = field(default=None) frequency: str = field(default=None) type: str = field(default=None) ================================================ FILE: gs_quant/data/log.py ================================================ """ Copyright 2020 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ NO_REQUEST_ID = 'no-request-id' def log_debug(request_id, logger, fmt_str, *args, **kwargs): logger.debug(f'{request_id or NO_REQUEST_ID}: {fmt_str}', *args, **kwargs) def log_warning(request_id, logger, fmt_str, *args, **kwargs): logger.warning(f'{request_id or NO_REQUEST_ID}: {fmt_str}', *args, **kwargs) def log_info(request_id, logger, fmt_str, *args, **kwargs): logger.info(f'{request_id or NO_REQUEST_ID}: {fmt_str}', *args, **kwargs) ================================================ FILE: gs_quant/data/query.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from enum import Enum from typing import Union import pandas as pd from gs_quant.data import DataCoordinate from gs_quant.data.coordinate import DateOrDatetime from gs_quant.datetime.relative_date import RelativeDate from .stream import DataSeries class DataQueryType(Enum): LAST = 'LAST' RANGE = 'RANGE' class DataQuery: """Defines a query on a coordinate""" def __init__( self, coordinate: DataCoordinate, start: Union[DateOrDatetime, RelativeDate] = None, end: Union[DateOrDatetime, RelativeDate] = None, query_type: DataQueryType = DataQueryType.RANGE, ): """Initialize data query""" self.coordinate = coordinate self.start = start self.end = end self.query_type = query_type def get_series(self) -> Union[pd.Series, None]: """Execute query and return series""" if self.query_type is DataQueryType.RANGE: return self.coordinate.get_series(self.start, self.end) if self.query_type is DataQueryType.LAST: return self.coordinate.last_value(self.end) def get_data_series(self) -> DataSeries: return DataSeries(self.get_series(), self.coordinate) def get_range_string(self) -> str: return f'start={self.start}|end={self.end}' ================================================ FILE: gs_quant/data/stream.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from typing import Union import pandas as pd from gs_quant.data import DataCoordinate class DataSeries: """Represents a data series update""" def __init__(self, series: pd.Series, coordinate: DataCoordinate): self.series = series self.coordinate = coordinate class DataEvent: """Represents a data update event""" def __init__(self, time: dt.datetime, value: Union[None, str, float], coordinate: DataCoordinate = None): self.time = time self.value = value self.coordinate = coordinate ================================================ FILE: gs_quant/data/utilities.py ================================================ """ Copyright 2023 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import os from dataclasses import dataclass from enum import Enum from itertools import groupby from functools import partial from concurrent.futures import ThreadPoolExecutor from gs_quant.target.assets import FieldFilterMap, EntityQuery from gs_quant.session import GsSession import math import pandas as pd import datetime as dt from typing import Dict, List, Any, Union, Tuple class Utilities: class AssetApi: IdList = Union[Tuple[str, ...], List] @classmethod def __create_query( cls, fields: Union[List, Tuple] = None, as_of: dt.datetime = None, limit: int = None, scroll: str = None, scroll_id: str = None, order_by: List[str] = None, **kwargs, ) -> EntityQuery: keys = set(kwargs.keys()) valid = keys.intersection(FieldFilterMap.properties()) invalid = keys.difference(valid) if invalid: bad_args = ['{}={}'.format(k, kwargs[k]) for k in invalid] raise KeyError('Invalid asset query argument(s): {}'.format(', '.join(bad_args))) return EntityQuery( where=FieldFilterMap(**kwargs), fields=fields, asOfTime=as_of or dt.datetime.utcnow(), limit=limit, scroll=scroll, scroll_id=scroll_id, order_by=order_by, ) @classmethod def get_many_assets_data( cls, fields: IdList = None, as_of: dt.datetime = None, limit: int = None, **kwargs ) -> dict: query = cls.__create_query(fields, as_of, limit, **kwargs) response = GsSession.current.sync.post('/assets/data/query', payload=query) return response['results'] @staticmethod def target_folder(): get_cwd = os.getcwd() target_dir = 'data_extract_' + dt.datetime.now().strftime("%d_%m_%Y_%H_%M_%S") if os.path.exists(get_cwd + "\\" + target_dir): final_dir = get_cwd + "\\" + target_dir return final_dir elif os.access(get_cwd, os.W_OK): try: os.makedirs(target_dir) final_dir = get_cwd + "\\" + target_dir return final_dir except Exception as ex: print(ex) return 1 @staticmethod def pre_checks(final_end, original_start, time_field, datetime_delta_override, request_batch_size, write_to_csv): if write_to_csv: target_dir_result = Utilities.target_folder() if target_dir_result == 1: raise ValueError("Current working directory doesn't have write permissions to save data. Exiting .") else: target_dir_result = None if request_batch_size is None or not (0 < request_batch_size < 5): raise ValueError("Enter request batch size beteen 1-5") if datetime_delta_override is not None: if not isinstance(datetime_delta_override, int): raise ValueError("Time delta override must be greater than 0 and 1 - 5 for intraday dataset") elif isinstance(datetime_delta_override, int) and (datetime_delta_override < 0): raise ValueError("Time delta override must be greater than 0 and 1 - 5 for intraday dataset") elif (time_field == "time") and (datetime_delta_override > 5): raise ValueError("Time delta override must be greater than 0 and 1 - 5 for intraday dataset") if final_end is not None: if not isinstance(final_end, dt.datetime): raise ValueError("End date must of datetime.datetime format. Existing program....") elif not isinstance(original_start, dt.datetime): raise ValueError("Start date must be of datetime.datetime format. Exiting program......") elif original_start > final_end: raise ValueError("Start date cannot be greater than end date. Exiting program......") elif (time_field == "time") and ((final_end - original_start).total_seconds() / 3600 > 5): raise ValueError("For intraday datasets diff between start & end date should be <= 5 hrs") return final_end, target_dir_result @staticmethod def batch(iterable, n=1): iter_len = len(iterable) for ndx in range(0, iter_len, n): yield iterable[ndx : min(ndx + n, iter_len)] @staticmethod def fetch_data(dataset, symbols, start=dt.datetime.now(), end=dt.datetime.now(), dimension="assetId", auth=None): if auth is not None: auth() try: return dataset.get_data(start, end, **{dimension: symbols}) except Exception as ex: print(ex) return pd.DataFrame() @staticmethod def execute_parallel_query( dataset, coverage, start, end, symbol_dimension, parallel_factor, batch_size, authenticate, retry=0 ): bound_get_data = partial( Utilities.fetch_data, dataset, start=start, end=end, dimension=symbol_dimension, auth=authenticate ) print(f"{len(coverage)} symbols, will run in {math.ceil(len(coverage) / batch_size)} batches") with ThreadPoolExecutor(max_workers=parallel_factor) as e: try: df = pd.concat(e.map(bound_get_data, Utilities.batch(coverage, n=batch_size))) print(f"Fetched {len(df.index)} rows in {parallel_factor} concurrent process from {start} - {end}") except Exception as e: print(f"Failure getting data with error {e}, retrying...") if retry > 3: raise Exception("retry failure") retry += 1 df = Utilities.execute_parallel_query( dataset, coverage, start, end, symbol_dimension, parallel_factor, batch_size, authenticate, retry ) return df @staticmethod def get_dataset_parameter(dataset): dimensions = dataset.provider.symbol_dimensions(dataset.id) symbol_dimension = dimensions[0] dataset_definition = dataset.provider.get_definition(dataset.id) history_time = dataset_definition.parameters.history_date time_field = dataset_definition.dimensions.time_field timedelta = dt.timedelta(days=180) if time_field == "date" else dt.timedelta(hours=1) return [time_field, history_time, symbol_dimension, timedelta] @staticmethod def write_consolidated_results( data_frame, target_dir_result, dataset, batch_number, handler, write_to_csv, coverage_length, symbols_per_csv ): if data_frame.shape[0] > 0: if write_to_csv: data_frame.to_csv(f"{target_dir_result}\\{dataset.id}-batch {batch_number}.csv", ",") else: handler(data_frame) print(f"Wrote batch {batch_number} file out of {math.ceil(coverage_length / symbols_per_csv)}") @staticmethod def iterate_over_series( dataset, coverage_batch, original_start, original_end, datetime_delta_override, symbol_dimension, request_batch_size, authenticate, final_end, write_to_csv, target_dir_result, batch_number, coverage_length, symbols_per_csv, handler, parallel_factor=5, ): start = original_start end = original_end data_frame = pd.DataFrame() while True: batch_frame = Utilities.execute_parallel_query( dataset, coverage_batch, start, end, symbol_dimension, parallel_factor, request_batch_size, authenticate ) data_frame = pd.concat([data_frame, batch_frame], axis=0) start += datetime_delta_override end += datetime_delta_override if end > final_end: Utilities.write_consolidated_results( data_frame, target_dir_result, dataset, batch_number, handler, write_to_csv, coverage_length, symbols_per_csv, ) break del data_frame return None @staticmethod def extract_xref(assets, out_type): # if multiple assets match, return highest ranking return sorted(assets, key=lambda x: x.get("rank", 0), reverse=True)[0].get(out_type, "") @staticmethod def map_identifiers(input_type: str, output_type: str, ids, as_of=dt.datetime.now()): asset_batches = Utilities.batch(ids, n=1000) all_assets = [] for asset_batch in asset_batches: assets = Utilities.AssetApi.get_many_assets_data( **{input_type: asset_batch}, limit=min(5000, 5 * len(asset_batch)), as_of=as_of, fields=[input_type, output_type, "listed", "assetClassificationsIsPrimary", "rank"], asset_classifications_is_primary=[True], ) assets = sorted(assets, key=lambda x: x[input_type]) all_assets = all_assets + assets return { inp_id: Utilities.extract_xref(grouped_assets, output_type) for inp_id, grouped_assets in groupby(all_assets, key=lambda x: x[input_type]) } @staticmethod def get_dataset_coverage(identifier, symbol_dimension, dataset): if symbol_dimension == "assetId": cov = dataset.get_coverage(fields=[identifier]) coverage = cov[cov[identifier].notna()][identifier].tolist() elif symbol_dimension == "gsid": coverage = dataset.get_coverage()[symbol_dimension].tolist() coverage = list(Utilities.map_identifiers(symbol_dimension, identifier, coverage).values()) else: coverage = dataset.get_coverage()[symbol_dimension].tolist() return coverage class SecmasterXrefFormatter: class EventType(Enum): START = "start" END = "end" @dataclass class Event: date: str event_type: 'SecmasterXrefFormatter.EventType' record: Dict[str, Any] def __post_init__(self): # For end events, we want them to be processed after start events on the same date # This ensures proper handling of adjacent periods self.priority = 1 if self.event_type == SecmasterXrefFormatter.EventType.END else 0 INFINITY_DATE = "9999-12-31" INFINITY_MARKER = "9999-99-99" @staticmethod def convert(data: Dict[str, Any]) -> Dict[str, Dict[str, List[Dict[str, Any]]]]: results = {} for entity_key, records in data.items(): xrefs = SecmasterXrefFormatter._convert_entity_records(records) results[entity_key] = {"xrefs": xrefs} return results @staticmethod def _convert_entity_records(records: List[Dict[str, Any]]) -> List[Dict[str, Any]]: """ Converts records using sweep-line algorithm for optimal time complexity. """ if not records: return [] # Filter and normalize records normalized_records = [] for record in records: normalized_record = record.copy() if normalized_record['endDate'] == SecmasterXrefFormatter.INFINITY_MARKER: normalized_record['endDate'] = SecmasterXrefFormatter.INFINITY_DATE normalized_records.append(normalized_record) if not normalized_records: return [] # Create events for sweep-line algorithm events = SecmasterXrefFormatter._create_events(normalized_records) # Sort events by date, then by priority (end events after start events on same date) events.sort(key=lambda e: (SecmasterXrefFormatter._date_sort_key(e.date), e.priority)) # Process events with sweep-line return SecmasterXrefFormatter._process_events(events) @staticmethod def _create_events(records: List[Dict[str, Any]]) -> List[Event]: """ Creates start and end events for each record. """ events = [] for record in records: # Create start event events.append( SecmasterXrefFormatter.Event( date=record['startDate'], event_type=SecmasterXrefFormatter.EventType.START, record=record ) ) # Create end event (day after the actual end date) if record['endDate'] != SecmasterXrefFormatter.INFINITY_DATE: next_day = SecmasterXrefFormatter._add_one_day(record['endDate']) if next_day: events.append( SecmasterXrefFormatter.Event( date=next_day, event_type=SecmasterXrefFormatter.EventType.END, record=record ) ) return events @staticmethod def _process_events(events: List[Event]) -> List[Dict[str, Any]]: """ Processes events using sweep-line algorithm to generate time periods. """ periods = [] active_identifiers = {} # type -> record mapping current_period_start = None i = 0 while i < len(events): current_date = events[i].date current_date_events = [] # Collect all events for the current date while i < len(events) and events[i].date == current_date: current_date_events.append(events[i]) i += 1 # Close current period if we have active identifiers if active_identifiers and current_period_start is not None: period_end = SecmasterXrefFormatter._subtract_one_day(current_date) periods.append( { "startDate": current_period_start, "endDate": period_end, "identifiers": {record['type']: record['value'] for record in active_identifiers.values()}, } ) # Process all events for this date # First process END events, then START events end_events = [e for e in current_date_events if e.event_type == SecmasterXrefFormatter.EventType.END] start_events = [e for e in current_date_events if e.event_type == SecmasterXrefFormatter.EventType.START] # Remove ending identifiers for event in end_events: identifier_type = event.record['type'] if identifier_type in active_identifiers: del active_identifiers[identifier_type] # Add starting identifiers for event in start_events: identifier_type = event.record['type'] active_identifiers[identifier_type] = event.record # Start new period if we have active identifiers if active_identifiers: current_period_start = current_date # Handle final period extending to infinity or latest end date if active_identifiers and current_period_start is not None: # Check if any active identifier has infinity end date has_infinity = any( record['endDate'] == SecmasterXrefFormatter.INFINITY_DATE for record in active_identifiers.values() ) if has_infinity: period_end = SecmasterXrefFormatter.INFINITY_DATE else: # Find latest end date among active identifiers latest_end = max(record['endDate'] for record in active_identifiers.values()) period_end = latest_end periods.append( { "startDate": current_period_start, "endDate": period_end, "identifiers": {record['type']: record['value'] for record in active_identifiers.values()}, } ) return periods @staticmethod def _date_sort_key(date_str: str) -> dt.datetime: if date_str == SecmasterXrefFormatter.INFINITY_DATE: return dt.datetime(9999, 12, 31) return dt.datetime.strptime(date_str, '%Y-%m-%d') @staticmethod def _add_one_day(date_str: str) -> str: try: if date_str == SecmasterXrefFormatter.INFINITY_DATE: return None date_obj = dt.datetime.strptime(date_str, '%Y-%m-%d') if date_obj.year == 9999 and date_obj.month == 12 and date_obj.day == 31: return None next_day = date_obj + dt.timedelta(days=1) return next_day.strftime('%Y-%m-%d') except (ValueError, OverflowError): return None @staticmethod def _subtract_one_day(date_str: str) -> str: try: date_obj = dt.datetime.strptime(date_str, '%Y-%m-%d') prev_day = date_obj - dt.timedelta(days=1) return prev_day.strftime('%Y-%m-%d') except (ValueError, OverflowError): return date_str ================================================ FILE: gs_quant/datetime/__init__.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from .date import * from .time import * from .gscalendar import * from .point import * __name__ = 'datetime' ================================================ FILE: gs_quant/datetime/date.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import calendar as cal import datetime as dt import zoneinfo from enum import Enum, IntEnum from typing import Iterable, Optional, Tuple, Union import numpy as np from gs_quant.common import PricingLocation from gs_quant.datetime.gscalendar import GsCalendar DateOrDates = Union[dt.date, Iterable[dt.date]] location_to_tz_mapping = { PricingLocation.NYC: zoneinfo.ZoneInfo("America/New_York"), PricingLocation.LDN: zoneinfo.ZoneInfo("Europe/London"), PricingLocation.HKG: zoneinfo.ZoneInfo("Asia/Hong_Kong"), PricingLocation.TKO: zoneinfo.ZoneInfo("Asia/Tokyo"), } class PaymentFrequency(IntEnum): """Payment frequency enumeration Provides an enumeration of different payment frequencies used to to discount cashflows and accrue interest """ DAILY = 252 WEEKLY = 52 SEMI_MONTHLY = 26 MONTHLY = 12 SEMI_QUARTERLY = 6 QUARTERLY = 4 TRI_ANNUALLY = 3 SEMI_ANNUALLY = 2 ANNUALLY = 1 class DayCountConvention(Enum): """Day Count Convention enumeration Provides an enumeration of different day count conventions for determining how interest accrues over payment periods for financial securities """ # Actual/360: Number of days between dates divided by 360 ACTUAL_360 = "ACTUAL_360" # Actual/364: Number of days between dates divided by 364 ACTUAL_364 = "ACTUAL_364" # Actual/365_25: Number of days between dates divided by 365.25 ACTUAL_365_25 = "ACTUAL_365_25" # Actual/365 FIXED: Number of days between dates divided by 365 ACTUAL_365F = "ACTUAL_365F" # Actual/365 LEAP: Number of days between dates divided by 365 or 366 in leap years ACTUAL_365L = "ACTUAL_365L" # ONE_ONE: Always returns a day count fraction of 1 ONE_ONE = "ONE_ONE" def is_business_day( dates: DateOrDates, calendars: Union[str, Tuple[str, ...]] = (), week_mask: Optional[str] = None ) -> Union[bool, Tuple[bool, ...]]: """ Determine whether each date in dates is a business day :param dates: The input date or dates :param calendars: Calendars to use for holidays :param week_mask: Which days are considered weekends (defaults to Saturday and Sunday) :return: True/False if dates is a single date. A tuple indicating True/False for each date if dates is an iterable **Examples** >>> import datetime as dt >>> is_business_day(dt.date.today()) >>> is_business_day(dt.date(2019, 7, 4), calendars=('NYSE',)) """ calendar = GsCalendar.get(calendars) res = np.is_busday(dates, busdaycal=calendar.business_day_calendar(week_mask)) return tuple(res) if isinstance(res, np.ndarray) else res def business_day_offset( dates: DateOrDates, offsets: Union[int, Iterable[int]], roll: str = 'raise', calendars: Union[str, Tuple[str, ...]] = (), week_mask: Optional[str] = None, ) -> DateOrDates: """ Apply offsets to the dates and move to the nearest business date :param dates: The input date or dates :param offsets: The number of days by which to adjust the dates :param roll: Which direction to roll, in order to get to the nearest business date :param calendars: Calendars to use for holidays :param week_mask: Which days are considered weekends (defaults to Saturday and Sunday) :return: A date (if dates is a single date) or tuple of dates, adjusted by the offsets **Examples** >>> import datetime as dt >>> prev_bus_date = business_day_offset(dt.date.today(), -1, roll='forward') """ calendar = GsCalendar.get(calendars) res = np.busday_offset(dates, offsets, roll, busdaycal=calendar.business_day_calendar(week_mask)).astype(dt.date) return tuple(res) if isinstance(res, np.ndarray) else res def prev_business_date( dates: DateOrDates = dt.date.today(), calendars: Union[str, Tuple[str, ...]] = (), week_mask: Optional[str] = None ) -> DateOrDates: """ Returns the previous business date for a given date or date series, defaulting to today. :param dates: The input date or dates, defaults to today :param calendars: Calendars to use for holidays :param week_mask: Which days are considered weekends (defaults to Saturday and Sunday) :return: A date (if dates is a single date) or tuple of dates, adjusted by the offset of one day. **Example** >>> import datetime as dt >>> prev_bus_date = prev_business_date() """ return business_day_offset(dates, -1, roll='forward', calendars=calendars, week_mask=week_mask) def business_day_count( begin_dates: DateOrDates, end_dates: DateOrDates, calendars: Union[str, Tuple[str, ...]] = (), week_mask: Optional[str] = None, ) -> Union[int, Tuple[int, ...]]: """ Determine the number of business days between begin_dates and end_dates :param begin_dates: A date or collection of beginning dates :param end_dates: A date or collection of end dates :param calendars: Calendars to use for holidays :param week_mask: Which days are considered weekends (defaults to Saturday and Sunday) :return: An int or tuple of ints, representing the number of business days between begin_dates and end_dates **Examples** >>> import datetime as dt >>> today = dt.date.today() >>> bus_days = business_day_count(today, today + dt.timedelta(days=7)) """ calendar = GsCalendar.get(calendars) res = np.busday_count(begin_dates, end_dates, busdaycal=calendar.business_day_calendar(week_mask)) return tuple(res) if isinstance(res, np.ndarray) else res def date_range( begin: Union[int, dt.date], end: Union[int, dt.date], calendars: Union[str, Tuple[str, ...]] = (), week_mask: Optional[str] = None, ) -> Iterable[dt.date]: """ Construct a range of dates :param begin: Beginning date or int. An int will be interpreted as the number of business days before end (which must be a date) :param end: End date or int. An int will be interpreted as the number of business days after begin (which must be a date) :param calendars: Calendars to use for holidays :param week_mask: Which days are considered weekends (defaults to Saturday and Sunday) :return: A generator of dates >>> import datetime as dt >>> today = dt.date.today() >>> dates = tuple(date_range(5, today)) >>> >>> for date in date_range(dt.date(2019, 1, 1), dt.date(2019, 2, 1)): >>> print(date) """ if isinstance(begin, dt.date): if isinstance(end, dt.date): def f(): prev = begin if prev > end: raise ValueError('begin must be <= end') while prev <= end: yield prev prev = business_day_offset(prev, 1, calendars=calendars, week_mask=week_mask) return (d for d in f()) elif isinstance(end, int): return (business_day_offset(begin, i, calendars=calendars, week_mask=week_mask) for i in range(end)) else: raise ValueError('end must be a date or int') elif isinstance(begin, int): if isinstance(end, dt.date): return ( business_day_offset(end, -i, roll='preceding', calendars=calendars, week_mask=week_mask) for i in range(begin) ) else: raise ValueError('end must be a date if begin is an int') else: raise ValueError('begin must be a date or int') def today(location: Optional[PricingLocation] = None) -> dt.date: if not location: return dt.date.today() tz = location_to_tz_mapping.get(location, None) if tz is None: raise ValueError(f'Unrecognized timezone {location}') return dt.datetime.now(tz).date() def has_feb_29(start: dt.date, end: dt.date): """ Determine if date range has a leap day (29Feb) :param start: first date :param end: second date **Usage** Determine if a given date range contains a leap day (Feb 29). Used for various day count convention calculations which alter behaviour for leap years. Start date is exclusive and end date is inclusive **Examples** Determine if a given date range contains 29Feb >>> start = date(2020, 1, 1) >>> end = date(2020, 3, 15) >>> has_feb_29(start, end) """ feb_29 = False for x in range(1, (end - start).days + 1): date = start + dt.timedelta(days=x) feb_29 = feb_29 | (date.month == 2 and date.day == 29) return feb_29 def day_count_fraction( start: dt.date, # First payment date end: dt.date, # Second payment date convention: DayCountConvention = DayCountConvention.ACTUAL_360, frequency: PaymentFrequency = PaymentFrequency.MONTHLY, ): """ Compute day count fraction between dates :param start: first date :param end: second date :param convention: day count convention :param frequency: payment frequency of instrument :return: day count fraction between dates per convention **Usage** Compute day count fraction between dates, based on the value of *convention*. For more information on the available day count conventions, see the `Day Count Conventions `_ guide. **Examples** Compute day count fraction between two dates using Actual/360 convention: >>> start = date(2015, 11, 12) >>> end = date(2017, 12, 15) >>> day_count_fraction(start, end, DayCountConvention.ACTUAL_360) """ if convention == DayCountConvention.ACTUAL_360: return (end - start).days / 360 elif convention == DayCountConvention.ACTUAL_364: return (end - start).days / 364 elif convention == DayCountConvention.ACTUAL_365F: return (end - start).days / 365 elif convention == DayCountConvention.ACTUAL_365L: if frequency == PaymentFrequency.ANNUALLY: days_in_year = 366 if has_feb_29(start, end) else 365 else: days_in_year = 366 if cal.isleap(end.year) else 365 return (end - start).days / days_in_year elif convention == DayCountConvention.ACTUAL_365_25: return (end - start).days / 365.25 elif convention == DayCountConvention.ONE_ONE: return 1 else: raise ValueError('Unknown day count convention: ' + convention.value) ================================================ FILE: gs_quant/datetime/gscalendar.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import logging from enum import Enum, EnumMeta from threading import Lock from typing import Tuple, Union, List import numpy as np from cachetools import TTLCache, cached from cachetools.keys import hashkey from gs_quant.common import PricingLocation, Currency from gs_quant.data import Dataset from gs_quant.errors import MqRequestError _logger = logging.getLogger(__name__) _calendar_cache = TTLCache(maxsize=128, ttl=600) _coverage_cache = TTLCache(maxsize=128, ttl=3600) def _split_list(items, predicate) -> Tuple[Tuple[str, ...], Tuple[str, ...]]: true_res = [] false_res = [] for item in items: item_str = item.value if isinstance(item, (Enum, EnumMeta)) else item.upper() if predicate(item): true_res.append(item_str) else: false_res.append(item_str) return tuple(true_res), tuple(false_res) class GsCalendar: DATE_LOW_LIMIT = dt.date(1952, 1, 1) DATE_HIGH_LIMIT = dt.date(2052, 12, 31) DEFAULT_WEEK_MASK = '1111100' # Default to Sat, Sun weekend days def __init__(self, calendars: Union[str, PricingLocation, Currency, Tuple[str, ...]] = (), skip_valid_check=True): if isinstance(calendars, (str, PricingLocation, Currency)): calendars = (calendars,) if calendars is None: calendars = () self.__calendars = calendars self.__business_day_calendars = {} self._skip_valid_check = skip_valid_check @staticmethod def get(calendars: Union[str, Tuple], skip_valid_check=True): return GsCalendar(calendars, skip_valid_check) @staticmethod def reset(): _calendar_cache.clear() def calendars(self) -> Tuple: return self.__calendars @staticmethod def is_currency(currency: Union[str, PricingLocation, Currency]) -> bool: if isinstance(currency, Currency): return True if isinstance(currency, PricingLocation): return False try: _ = Currency(currency.upper()) return True except (ValueError, AttributeError): return False @cached(_coverage_cache, key=lambda s, d, q: d.id, lock=Lock()) def _get_dataset_coverage(self, dataset: Dataset, query_key: str): coverage_df = dataset.get_coverage() coverage = set() if coverage_df.empty else set(coverage_df[query_key]) return coverage def holidays_from_dataset(self, dataset: Dataset, query_key: str, query_values: Tuple[str, ...]) -> List[dt.date]: if not len(query_values): return [] coverage = self._get_dataset_coverage(dataset, query_key) for item in query_values: if item not in coverage: if self._skip_valid_check: _logger.warning( f'Ignoring invalid calendar {item}. This will throw in future versions of gs-quant.' ) else: raise ValueError(f'Invalid calendar {item}') try: data = dataset.get_data(**{query_key: query_values}, start=self.DATE_LOW_LIMIT, end=self.DATE_HIGH_LIMIT) if not data.empty: return [d.date() for d in data.index.to_pydatetime()] except MqRequestError: pass return [] @property @cached(_calendar_cache, key=lambda s: hashkey(str(s.__calendars)), lock=Lock()) def holidays(self) -> Tuple[dt.date, ...]: currencies, exchanges = _split_list(self.__calendars, GsCalendar.is_currency) holidays = self.holidays_from_dataset(Dataset(Dataset.GS.HOLIDAY), 'exchange', exchanges) holidays = holidays + self.holidays_from_dataset(Dataset(Dataset.GS.HOLIDAY_CURRENCY), 'currency', currencies) holidays = tuple(set(holidays)) return holidays def business_day_calendar(self, week_mask: str = None) -> np.busdaycalendar: return self.__business_day_calendars.setdefault( week_mask, np.busdaycalendar( weekmask=week_mask or self.DEFAULT_WEEK_MASK, holidays=tuple([np.datetime64(d.isoformat()) for d in self.holidays]), ), ) ================================================ FILE: gs_quant/datetime/point.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import functools import re import string from typing import Optional from gs_quant.errors import MqValueError ConstPoints = { "O/N": 0, "T/N": 0.1, "OIS FIX": 1, "CASH STUB": 1.1, "CASHSTUB": 1.1, "DEFAULT": 0, "IN": 0.1, "OUT": 0.2, } # Regular expression for different types of market data coordinate points EuroOrFraReg = ( r"^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)+([0-9][0-9])$" ) NumberReg = r"^([0-9]*)$" MMMYYYYReg = r"^([a-zA-Z]{3}[0-9]{4})$" DDMMMYYYYReg = r"^([1-3]*[0-9]{1}[a-zA-Z]{3}[0-9]{4})$" SpikeQEReg = r"^(QE[0-9])-([0-9]{4})$" FRAxReg = r"^([0-9]+)x([0-9]+)$" RDatePartReg = r"^([-]*[0-9]+[mydwbfMYDWBF])([-]*[0-9]+[mydwbfMYDWBF])?$" CashFXReg = r"^([-]*[0-9]+[mydwbfMYDWBF])([-]*[0-9]+[mydwbfMYDWBF])? XC$" PricerCoordRegI = r"^(No )([0-9]*)$" PricerCoordRegII = r"^(Pricer )([0-9]*)$" PricerBFReg = r"^([-]*[0-9]+[mydwbfMYDWBFM])([-]*[0-9]+[mydwbfMYDWBF])([-]*[0-9]+[mydwbfMYDWBF])?$" PricerBondSpreadReg = r"^[0-9][SQHT]([0-9]{2})[/][0-9][SQHT]([0-9]{2})" SeasonalFrontReg = r"(Front|Back)" infl_volReg = r"(Caplet|ZCCap|Swaption|ZCSwo)" MMMReg = r"^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)$" MMMYYReg = r"^(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC) ([0-9]{2})" DatePairReg = r"^([0-9]{8})/([0-9]{8})$" DatePairReg2 = r"^([0-9]{8}) ([0-9]{8})$" FXVolAddonParmsReg = ( r"(Spread Addon|Spread Init|Spread Final|Rho Addon|Rho Init|Rho Final|Vol Addon|Vol Init|Vol Final|HL|Addon HL)" ) CopulaReg = r"(Rho$|Rho Rate|Rho Vol|RR|BF|Alpha|Beta|KO$|K0=L|K0=S)" BondCoordReg = r"^[0-9]* ([0-9.]*) ([0-9]{2}/[0-9]{2}/[0-9]{4})$" BondFutReg = r"^[A-Z]{3}([FGHJKMNQUVXZ])([0-9])$" FFFutReg = r"^FF([FGHJKMNQUVXZ])([0-9])$" RepoGCReg = r"^(ON|SN|TN|[0-9]+) (|Month |Week |Year |Day )GC$" FloatingYear = r"^([0-9]*\.[0-9])[yY]$" RelativeReg = r"^([0-9]+) (day|week|month|year|DAY|WEEK|MONTH|YEAR)$" LYYReg = r"([FGHJKMNQUVXZ])([0-9]{2})" DateRuleReg = r"^([-]*[0-9]+[mydwbfMYDWBFM])+$" DDMMMYYReg = r"^([0-3]*[0-9]{1}[a-zA-Z]{3}[0-9]{2})$" FutMonth = r"FGHJKMNQUVXZ" DictDayRule = { 'Month': 30, 'MONTH': 30, 'Week': 7, 'WEEK': 7, 'Year': 365, 'YEAR': 365, 'Day': 1, 'DAY': 1, 'd': 1, 'D': 1, 'w': 7, 'W': 7, 'b': 1, 'B': 1, 'f': 30, 'F': 30, 'm': 30, 'M': 30, 'y': 365, 'Y': 365, } def relative_date_add(date_rule: str, strict: bool = False) -> float: """Change the string in date rule format to the number of days. E.g 1d to 1, 1y to 365, 1m to 30, -1w to -7""" days = '' if re.search(DateRuleReg, date_rule) is not None: res = re.search(DateRuleReg, date_rule) date_str = res.group(1) if date_str[0] == '-': num = float(date_str[1:-1]) days = '-' else: num = float(date_str[:-1]) rule = date_str[-1:] if rule in DictDayRule: scale = DictDayRule[rule] days = days + str(num * scale) d = float(days) return d else: raise MqValueError('There are no valid day rule for the point provided.') if strict: raise MqValueError(f'invalid date rule {date_rule}') return 0 @functools.lru_cache(maxsize=None) def point_sort_order(point: str, ref_date: Optional[dt.date] = None) -> Optional[float]: """ Calculates a number that can be used to sort Mkt Points by it. :param point: The point string from MarketDataCoordinate. :param ref_date: Reference date, normally the pricing date. :return: The number of days from the reference date to the date specified by the point string **Examples** >>> import datetime as dt >>> days = point_sort_order(point = 'Dec20', ref_date=dt.date.today()) """ ref_date = dt.date.today() if ref_date is None else ref_date if not point or not isinstance(point, str): return 0 const_value = ConstPoints.get(point.upper()) if const_value is not None: return const_value parts = [p.strip() for p in point.split(';')] if len(parts) > 1: first = point_sort_order(parts[0]) if not first: return 0 return first + (0.1 * sum(point_sort_order(p, ref_date) for p in parts[1:]) / first) days = None if point == 'o/n': days = 0 elif point == 't/n': days = 0.1 elif point == 'Cash Stub': days = 1.1 elif point == 'CashStub': days = 1.1 elif point == 'Default': days = 0 elif point == 'In': days = 0.1 elif point == 'Out': days = 0.2 elif re.search(infl_volReg, point) is not None: res = re.search(infl_volReg, point) infl_vol = res.group(1) if infl_vol == 'Caplet': days = 0 elif infl_vol == 'ZCCap': days = 1 elif infl_vol == 'Swaption': days = 2 elif infl_vol == 'ZCSwo': days = 3 elif re.search(CopulaReg, point) is not None: pass elif re.search(SeasonalFrontReg, point) is not None: res = re.search(SeasonalFrontReg, point) if res.group(1) == 'Front': days = 0 else: days = 1 elif re.search(MMMReg, point) is not None: res = re.search(MMMReg, point) date_str = '1' + res.group(1) + '2000' format_str = '%d%b%Y' days = (dt.datetime.strptime(date_str, format_str).date() - ref_date).days elif re.search(EuroOrFraReg, point) is not None: res = re.search(EuroOrFraReg, point) date_str = '15' + res.group(1) + res.group(2) format_str = '%d%b%y' days = (dt.datetime.strptime(date_str, format_str).date() - ref_date).days elif re.search(RDatePartReg, point) is not None: res = re.search(RDatePartReg, point) date_str = res.group(1) days = relative_date_add(date_str) elif re.search(CashFXReg, point) is not None: res = re.search(CashFXReg, point) date_str = res.group(1) days = relative_date_add(date_str) elif re.search(PricerBFReg, point) is not None: res = re.search(PricerBFReg, point) date_str = res.group(1) days = relative_date_add(date_str) elif re.search(FRAxReg, point) is not None: res = re.search(FRAxReg, point) date_str = res.group(1) + 'm' days = relative_date_add(date_str) elif re.search(SpikeQEReg, point) is not None: res = re.search(SpikeQEReg, point) qe = res.group(1) if qe == 'QE1': month = "Mar" elif qe == 'QE2': month = "Jun" elif qe == 'QE3': month = "Sep" elif qe == 'QE4': month = 'Dec' else: month = 'Dec' date_str = "1" + month + res.group(2) format_str = '%d%b%Y' days = (dt.datetime.strptime(date_str, format_str).date() - ref_date).days elif re.search(MMMYYYYReg, point) is not None: res = re.search(MMMYYYYReg, point) date_str = '1' + res.group(1) format_str = '%d%b%Y' days = (dt.datetime.strptime(date_str, format_str).date() - ref_date).days elif re.search(DDMMMYYYYReg, point) is not None: res = re.search(DDMMMYYYYReg, point) date_str = res.group(1) format_str = '%d%b%Y' days = (dt.datetime.strptime(date_str, format_str).date() - ref_date).days elif re.search(NumberReg, point) is not None: res = re.search(NumberReg, point) days = float(res.group(1)) elif re.search(FloatingYear, point) is not None: res = re.search(FloatingYear, point) year = float(res.group(1)) days = 365 * year elif re.search(PricerCoordRegI, point) is not None: res = re.search(PricerCoordRegI, point) days = float(res.group(2)) elif re.search(PricerCoordRegII, point) is not None: res = re.search(PricerCoordRegII, point) days = float(res.group(2)) elif re.search(PricerBondSpreadReg, point) is not None: pass elif re.search(LYYReg, point) is not None: res = re.search(LYYReg, point) month = FutMonth.find(res.group(1)) + 1 year = res.group(2) date_str = year + '-' + str(month) + '-1' format_str = '%y-%m-%d' days = (dt.datetime.strptime(date_str, format_str).date() - ref_date).days elif re.search(DatePairReg, point) is not None: res = re.search(DatePairReg, point) date_str = res.group(2) format_str = '%Y%m%d' days = (dt.datetime.strptime(date_str, format_str).date() - ref_date).days elif re.search(DatePairReg2, point) is not None: res = re.search(DatePairReg2, point) date_str = res.group(2) format_str = '%Y%m%d' days = (dt.datetime.strptime(date_str, format_str).date() - ref_date).days elif re.search(MMMYYReg, point) is not None: res = re.search(MMMYYReg, point) date_str = '1' + res.group(1) + res.group(2) format_str = '%d%b%y' days = (dt.datetime.strptime(date_str, format_str).date() - ref_date).days elif re.search(FXVolAddonParmsReg, point) is not None: pass elif re.search(BondCoordReg, point) is not None: res = re.search(BondCoordReg, point) date_str = res.group(2) format_str = '%d/%m/%Y' days = (dt.datetime.strptime(date_str, format_str).date() - ref_date).days elif re.search(BondFutReg, point) is not None: res = re.search(BondFutReg, point) month = FutMonth.find(res.group(1)) + 1 date_str = str(ref_date.year) + '-' + str(month) + '-1' format_str = '%Y-%m-%d' days = (dt.datetime.strptime(date_str, format_str).date() - ref_date).days elif re.search(FFFutReg, point) is not None: res = re.search(FFFutReg, point) month = FutMonth.find(res.group(1)) + 1 date_str = str(ref_date.year) + '-' + str(month) + '-1' format_str = '%Y-%m-%d' days = (dt.datetime.strptime(date_str, format_str).date() - ref_date).days elif re.search(RepoGCReg, point) is not None: res = re.search(RepoGCReg, point) if point == 'ON GC': days = 0 elif point == 'TN GC': days = 1 elif point == 'SN GC': days = 2 elif res.group(2).strip() in DictDayRule: scale = DictDayRule[res.group(2).strip()] num = float(res.group(1)) days = num * scale elif re.search(RelativeReg, point) is not None: res = re.search(RelativeReg, point) rule = string.capwords(res.group(2)) if rule in DictDayRule: scale = DictDayRule[rule] num = float(res.group(1)) days = num * scale elif re.search(DDMMMYYReg, point) is not None: res = re.search(DDMMMYYReg, point) date_str = res.group(1) format_str = '%d%b%y' days = (dt.datetime.strptime(date_str, format_str).date() - ref_date).days else: days = 0 return days ================================================ FILE: gs_quant/datetime/relative_date.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import logging from copy import copy from typing import Union, Optional, List import pandas as pd import gs_quant.datetime.rules as rules from gs_quant.common import Currency from gs_quant.errors import MqValueError from gs_quant.markets import PricingContext from gs_quant.markets.securities import ExchangeCode _logger = logging.getLogger(__name__) class RelativeDate: """ RelativeDates are objects which provide utilities for getting dates given a relative date rule. Some rules require a business day calendar. :param rule: Rule to use :param base_date: Base date to use (Optional). :return: new RelativeDate object **Usage** Create a RelativeDate object and then call `apply_rule` to get a date back. **Examples** RelativeDate to return relative previous day: >>> my_date: date = RelativeDate('-1d').apply_rule() **Documentation** Full Documentation and examples can be found here: https://developer.gs.com/docs/gsquant/api/datetime.html """ def __init__(self, rule: str, base_date: Optional[dt.date] = None): self.rule = rule self.base_date_passed_in = False if base_date: self.base_date = base_date self.base_date_passed_in = True elif PricingContext.current.is_entered: pricing_date = PricingContext.current.pricing_date self.base_date = pricing_date else: self.base_date = dt.date.today() self.base_date = ( self.base_date.date() if isinstance(self.base_date, (dt.datetime, pd.Timestamp)) else self.base_date ) def apply_rule( self, currencies: List[Union[Currency, str]] = None, exchanges: List[Union[ExchangeCode, str]] = None, holiday_calendar: List[dt.date] = None, week_mask: str = '1111100', **kwargs, ) -> dt.date: """ Applies business date logic on the rule using the given holiday calendars for rules that use business day logic. week_mask is based off https://numpy.org/doc/stable/reference/generated/numpy.busdaycalendar.weekmask.html. :param holiday_calendar: Optional list of date to use for holiday calendar. This parameter takes precedence over currencies/exchanges. :param currencies: List of currency holiday calendars to use. (GS Internal only) :param exchanges: List of exchange holiday calendars to use. :param week_mask: String of seven-element boolean mask indicating valid days. Default weekend is Sat and Sun. :return: dt.date """ result = copy(self.base_date) for rule in self._get_rules(): result = self.__handle_rule( rule, result, week_mask, currencies=currencies, exchanges=exchanges, holiday_calendar=holiday_calendar, **kwargs, ) return result def _get_rules(self) -> List[str]: rule_list = [] current_rule = '' if not len(self.rule): raise MqValueError('Invalid Rule ""') current_alpha = self.rule[0].isalpha() for c in self.rule: is_alpha = c.isalpha() if current_alpha and not is_alpha: if current_rule.startswith('+'): rule_list.append(current_rule[1:]) else: rule_list.append(current_rule) current_rule = '' current_alpha = False if is_alpha: current_alpha = True current_rule += c if current_rule.startswith('+'): rule_list.append(current_rule[1:]) else: rule_list.append(current_rule) return rule_list def __handle_rule( self, rule: str, result: dt.date, week_mask: str, currencies: List[Union[Currency, str]] = None, exchanges: List[Union[ExchangeCode, str]] = None, holiday_calendar: List[dt.date] = None, **kwargs, ) -> dt.date: sign = "+" if rule.startswith('-'): index = 1 while index != len(rule) and rule[index].isdigit(): index += 1 number = int(rule[1:index]) * -1 if index < len(rule) else 0 rule_str = rule[index] sign = "-" else: index = 0 if not rule[0].isdigit(): rule_str = rule number = 0 else: while index != len(rule) and rule[index].isdigit(): index += 1 if index < len(rule): number = int(rule[0:index]) rule_str = rule[index] else: rule_str = rule number = 0 if not rule_str: raise MqValueError(f'Invalid rule "{rule}"') roll = kwargs.get('roll_convention') try: rule_class = getattr(rules, f'{rule_str}Rule') return rule_class( result, results=result, number=number, week_mask=week_mask, currencies=currencies, exchanges=exchanges, holiday_calendar=holiday_calendar, usd_calendar=kwargs.get('usd_calendar'), roll=roll, sign=sign, ).handle() except AttributeError: raise NotImplementedError(f'Rule {rule} not implemented') def as_dict(self): rdate_dict = {'rule': self.rule} if self.base_date_passed_in: rdate_dict['baseDate'] = str(self.base_date) return rdate_dict class RelativeDateSchedule: """ RelativeDatesSchedules are objects which wrap a RelativeDate to provide a schedule between two dates Some rules require a business day calendar. :param rule: Rule to use :param base_date: Base date to use (Optional). :param end_date: No dates past this date will be returned (Optional). :return: new RelativeDateSchedule object **Usage** Create a RelativeDateSchedule object and then call `apply_rule` to get a date schedule back. **Examples** RelativeDateSchedule to return a schedule from today to 1w in the future >>> my_date: date = RelativeDateSchedule('1w', datetime.date.today(), ).apply_rule() """ def __init__(self, rule: str, base_date: Optional[dt.date] = None, end_date: Optional[dt.date] = None): self.rule = rule self.base_date_passed_in = False if base_date: self.base_date = base_date self.base_date_passed_in = True elif PricingContext.current.is_entered: pricing_date = PricingContext.current.pricing_date self.base_date = ( pricing_date.date() if isinstance(pricing_date, (dt.datetime, pd.Timestamp)) else pricing_date ) else: self.base_date = dt.date.today() self.end_date = end_date def apply_rule( self, currencies: List[Union[Currency, str]] = None, exchanges: List[Union[ExchangeCode, str]] = None, holiday_calendar: List[dt.date] = None, week_mask: str = '1111100', **kwargs, ) -> List[dt.date]: """ Applies business date logic on the rule using the given holiday calendars for rules that use business day logic. week_mask is based off https://numpy.org/doc/stable/reference/generated/numpy.busdaycalendar.weekmask.html. :param holiday_calendar: Optional list of date to use for holiday calendar. This parameter takes precedence over currencies/exchanges. :param currencies: List of currency holiday calendars to use. (GS Internal only) :param exchanges: List of exchange holiday calendars to use. :param week_mask: String of seven-element boolean mask indicating valid days. Default weekend is Sat and Sun. :return: dt.date """ i = 1 schedule = [self.base_date] while True: rule = f'{int(self.rule[:-1]) * i}{self.rule[-1]}' result = RelativeDate(rule, self.base_date).apply_rule( currencies, exchanges, holiday_calendar, week_mask, **kwargs ) if self.end_date is None or result > self.end_date: break i += 1 schedule.append(result) return schedule def as_dict(self): rdate_dict = {'rule': self.rule} if self.base_date_passed_in: rdate_dict['baseDate'] = str(self.base_date) rdate_dict['endDate'] = str(self.end_date) return rdate_dict ================================================ FILE: gs_quant/datetime/rules.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import calendar import logging from abc import ABC, abstractmethod from typing import List, Union, Optional from dateutil.relativedelta import relativedelta, FR, SA, SU, TH, TU, WE, MO import numpy as np import pandas as pd from gs_quant.datetime.gscalendar import GsCalendar from gs_quant.markets.securities import ExchangeCode from gs_quant.common import Currency _logger = logging.getLogger(__name__) class RDateRule(ABC): result: dt.date number: int week_mask: str currencies: List[Union[Currency, str]] = None exchanges: List[Union[ExchangeCode, str]] = None holiday_calendar: List[dt.date] = None sign: Optional[str] = None def __init__(self, result: dt.date, **params): self.result = result self.number = params.get('number') self.week_mask = params.get('week_mask') self.currencies = params.get('currencies') self.exchanges = params.get('exchanges') self.holiday_calendar = params.get('holiday_calendar') self.usd_calendar = params.get('usd_calendar') self.roll = params.get('roll') self.sign = params.get('sign') super().__init__() @abstractmethod def handle(self) -> dt.date: """ Handle RDate Rule. Use any available class field to compute. :return: date """ pass def _get_holidays(self) -> List[dt.date]: if self.holiday_calendar is not None: if self.usd_calendar is None: return self.holiday_calendar return list(set().union(self.holiday_calendar, self.usd_calendar)) try: currencies = ( [] if self.currencies is None else [self.currencies] if isinstance(self.currencies, str) else self.currencies ) exchanges = ( [] if self.exchanges is None else [self.exchanges] if isinstance(self.exchanges, str) else self.exchanges ) cal = GsCalendar(exchanges + currencies) return cal.holidays except Exception as e: _logger.warning('Unable to fetch holiday calendar. Try passing your own when applying a rule. {}'.format(e)) return [] def _apply_business_days_logic(self, holidays: List[dt.date], offset: int = None, roll: str = 'preceding'): if offset is not None: offset_to_use = offset else: offset_to_use = self.number if self.number else 0 return pd.to_datetime( np.busday_offset(self.result, offset_to_use, roll, holidays=holidays, weekmask=self.week_mask) ).date() def _get_nth_day_of_month(self, calendar_day): temp = self.result.replace(day=1) adj = (calendar_day - temp.weekday()) % 7 temp += relativedelta(days=adj) temp += relativedelta(weeks=self.number - 1) return temp def add_years(self, holidays: List[dt.date]): self.result = self.result + relativedelta(years=self.number) if self.result.isoweekday() in {6, 7}: self.result += dt.timedelta(days=self.result.isoweekday() % 5) return self._apply_business_days_logic(holidays, offset=0) @staticmethod def is_weekend(d: dt.date): return False if d.weekday() < 5 else True # 5 Sat, 6 Sun def roll_convention(self, default=None): return self.roll or default class ARule(RDateRule): def handle(self) -> dt.date: result = self.result.replace(month=1, day=1) return result + relativedelta(year=self.number) class bRule(RDateRule): def handle(self) -> dt.date: holidays = self._get_holidays() roll = self.roll_convention('forward' if self.number <= 0 else 'preceding') return self._apply_business_days_logic(holidays, offset=self.number, roll=roll) class dRule(RDateRule): def handle(self) -> dt.date: return self.result + relativedelta(days=self.number) class eRule(RDateRule): def handle(self) -> dt.date: month_range = calendar.monthrange(self.result.year, self.result.month) return self.result.replace(day=month_range[1]) class FRule(RDateRule): def handle(self) -> dt.date: return self._get_nth_day_of_month(calendar.FRIDAY) class gRule(RDateRule): def handle(self) -> dt.date: self.result = self.result + relativedelta(weeks=self.number) holidays = self._get_holidays() return self._apply_business_days_logic(holidays, offset=0, roll=self.roll_convention('backward')) class NRule(RDateRule): def handle(self) -> dt.date: return self.result + relativedelta(weekday=MO(self.number)) class GRule(RDateRule): def handle(self) -> dt.date: return self.result + relativedelta(weekday=FR(self.number)) class IRule(RDateRule): def handle(self) -> dt.date: return self.result + relativedelta(weekday=SA(self.number)) class JRule(RDateRule): def handle(self) -> dt.date: return self.result.replace(day=1) class kRule(RDateRule): def handle(self) -> dt.date: self.result = self.result + relativedelta(years=self.number) while self.week_mask[self.result.isoweekday() - 1] == '0': self.result += relativedelta(days=1) holidays = self._get_holidays() return self._apply_business_days_logic(holidays, offset=0, roll=self.roll_convention('backward')) class mRule(RDateRule): def handle(self) -> dt.date: self.result = self.result + relativedelta(months=self.number) return self._apply_business_days_logic(self._get_holidays(), offset=0, roll=self.roll_convention('forward')) class MRule(RDateRule): def handle(self) -> dt.date: return self._get_nth_day_of_month(calendar.MONDAY) class PRule(RDateRule): def handle(self) -> dt.date: return self.result + relativedelta(weekday=SU(self.number)) class rRule(RDateRule): def handle(self) -> dt.date: return self.result.replace(month=12, day=31) + relativedelta(years=self.number) class RRule(RDateRule): def handle(self) -> dt.date: return self._get_nth_day_of_month(calendar.THURSDAY) class SRule(RDateRule): def handle(self) -> dt.date: return self.result + relativedelta(weekday=TH(self.number)) class TRule(RDateRule): def handle(self) -> dt.date: return self._get_nth_day_of_month(calendar.TUESDAY) class uRule(RDateRule): def handle(self) -> dt.date: holidays = self._get_holidays() roll = 'preceding' if self.sign == "-" and self.number == 0 else 'forward' if self.number <= 0 else 'preceding' return self._apply_business_days_logic(holidays, offset=self.number, roll=roll) class URule(RDateRule): def handle(self) -> dt.date: return self.result + relativedelta(weekday=TU(self.number)) class vRule(RDateRule): def handle(self) -> dt.date: self.result = self.result + relativedelta(months=self.number) if self.number else self.result month_range = calendar.monthrange(self.result.year, self.result.month) self.result = self.result.replace(day=month_range[1]) holidays = self._get_holidays() return self._apply_business_days_logic(holidays, offset=0, roll=self.roll_convention('backward')) class VRule(RDateRule): def handle(self) -> dt.date: return self._get_nth_day_of_month(calendar.SATURDAY) class WRule(RDateRule): def handle(self) -> dt.date: return self._get_nth_day_of_month(calendar.WEDNESDAY) class wRule(RDateRule): def handle(self) -> dt.date: self.result = self.result + relativedelta(weeks=self.number) holidays = self._get_holidays() roll = 'forward' if self.number >= 0 else 'backward' return self._apply_business_days_logic(holidays, offset=0, roll=self.roll_convention(roll)) class xRule(RDateRule): def handle(self) -> dt.date: month_range = calendar.monthrange(self.result.year, self.result.month) self.result = self.result.replace(day=month_range[1]) holidays = self._get_holidays() return self._apply_business_days_logic(holidays, offset=0, roll=self.roll_convention('backward')) class XRule(RDateRule): def handle(self) -> dt.date: return self.result + relativedelta(weekday=WE(self.number)) class yRule(RDateRule): def handle(self) -> dt.date: self.result = self.result + relativedelta(years=self.number) while self.week_mask[self.result.isoweekday() - 1] == '0': self.result += relativedelta(days=1) holidays = self._get_holidays() return self._apply_business_days_logic(holidays, offset=0, roll=self.roll_convention('backward')) class ZRule(RDateRule): def handle(self) -> dt.date: return self._get_nth_day_of_month(calendar.SUNDAY) ================================================ FILE: gs_quant/datetime/time.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import logging import numpy as np from gs_quant.errors import MqValueError _logger = logging.getLogger(__name__) DAYS_IN_YEAR = 365.25 DAYS_IN_WEEK = 7 HOURS_IN_DAY = 24 MINS_IN_HOUR = 60 SECS_IN_MIN = 60 SECS_IN_HOUR = SECS_IN_MIN * MINS_IN_HOUR MINS_IN_DAY = MINS_IN_HOUR * HOURS_IN_DAY SECS_IN_DAY = SECS_IN_MIN * MINS_IN_DAY HOURS_IN_WEEK = HOURS_IN_DAY * DAYS_IN_WEEK MINS_IN_WEEK = HOURS_IN_WEEK * MINS_IN_HOUR SECS_IN_WEEK = MINS_IN_WEEK * SECS_IN_HOUR SECS_IN_YEAR = SECS_IN_MIN * MINS_IN_HOUR * HOURS_IN_DAY * DAYS_IN_YEAR class Timer: def __init__(self, print_on_exit: bool = True, label: str = 'Execution', threshold: int = None): self.__print_on_exit = print_on_exit self.__label = label self.__threshold = threshold def __enter__(self): self.__start = dt.datetime.now() def __exit__(self, *args): self.__elapsed = dt.datetime.now() - self.__start if self.__print_on_exit: if self.__threshold is None or self.__elapsed.seconds > self.__threshold: _logger.warning( f'{self.__label} took {self.__elapsed.seconds + self.__elapsed.microseconds / 1000000} seconds' ) def to_zulu_string(time: dt.datetime): return time.isoformat()[:-3] + 'Z' def time_difference_as_string(time_delta: np.timedelta64, resolution: str = 'Second') -> str: times = [SECS_IN_YEAR, SECS_IN_WEEK, SECS_IN_DAY, SECS_IN_HOUR, SECS_IN_MIN, 1] time_strings = ['Year', 'Week', 'Day', 'Hour', 'Minute', 'Second'] if resolution not in time_strings: raise MqValueError('incorrect resolution passed in "s"' % resolution) times_mapped = zip(times, time_strings) diff: int = abs(time_delta / np.timedelta64(1, 's')) result = '' for time, time_string in times_mapped: m = diff // time if m > 0: added = time_string if m != 1: added += 's' result += '%d %s ' % (m, added) diff -= m * time if time_string == resolution: break return result.strip() ================================================ FILE: gs_quant/documentation/00_data/00_datasets/examples/0000_query_dataset.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "Examples require an initialized GsSession and relevant entitlements. External clients need to substitute thier own client id and client secret below. Please refer to [Authentication](https://developer.gs.com/p/docs/institutional/platform/authentication/) for details." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('read_product_data',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## How to query data \n", "The Data APIs support many ways to query datasets to intuitively fetch only the data users need.\n", "More details on [Querying Data](https://developer.gs.com/p/docs/services/data/data-access/query-data/) can be found in the documentation" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from datetime import date, timedelta, datetime\n", "from gs_quant.data import Dataset\n", "import pydash" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Data in Marquee is available in the form of Datasets (collections of homogenous data). Each Dataset has a set of entitlements, a fixed schema, and assets in coverage." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dataset_id = 'FXIVOL_STANDARD' # https://marquee.gs.com/s/developer/datasets/FXIVOL_STANDARD\n", "ds = Dataset(dataset_id)" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "Data for limited number of assets or spanning a small time frame can be queried in one go by specifying the assets to query and date/time range." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_date = date(2024, 1, 15)\n", "end_date = date(2024, 1, 18)\n", "\n", "data = ds.get_data(start_date, end_date, bbid=[\"EURUSD\"])\n", "data.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Instead of a range, one can also specify a set of date/times to get data for just those specific date/times" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "data = ds.get_data(dates=[date(2025, 1, 15), date(2025, 1, 18)], bbid=['EURCAD'])\n", "data.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For larger number of assets or for longer time ranges, \n", "we recommend iterating over assets and time to avoid hitting API query limits. " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# loop over assets\n", "def iterate_over_assets(\n", " dataset, coverage, start, end, batch_size=5, query_dimension='assetId', delta=timedelta(days=6)\n", "):\n", " for ids in pydash.chunk(coverage[query_dimension].tolist(), size=batch_size):\n", " print('iterate over assets', ids)\n", " iterate_over_time(start, end, ids, dataset, delta=delta, query_dimension=query_dimension)\n", "\n", "\n", "# loop over time\n", "def iterate_over_time(start, end, ids, dataset, delta=timedelta(days=6), query_dimension='assetId'):\n", " iter_start = start\n", " while iter_start < end:\n", " iter_end = min(iter_start + delta, end)\n", " print('time iteration since', iter_start, 'until', iter_end)\n", " data = dataset.get_data(iter_start, iter_end, **{query_dimension: ids})\n", " # Add your code here to make use of fetched data\n", "\n", " iter_start = iter_end" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "dataset_id = 'EDRVOL_PERCENT_V1_STANDARD' # https://marquee.gs.com/s/developer/datasets/EDRVOL_PERCENT_V1_STANDARD\n", "ds = Dataset(dataset_id)\n", "\n", "coverage = ds.get_coverage()\n", "\n", "iterate_over_assets(ds, coverage, date(2025, 3, 1), date(2021, 3, 31), batch_size=5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Similar approach can be used to download all data of a dataset" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "coverage = ds.get_coverage(include_history=True)\n", "coverage = coverage.sort_values(by='historyStartDate', axis=0)\n", "start_date = datetime.strptime(coverage['historyStartDate'].values[0], '%Y-%m-%d').date()\n", "\n", "# warning: long running operation if ran on whole of coverage for all time\n", "iterate_over_assets(ds, coverage.head(), date.today() - timedelta(days=90), date.today())" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/00_data/00_datasets/examples/0001_get_dataset_coverage.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "Examples require an initialized GsSession and relevant entitlements. External clients need to substitute thier own client id and client secret below. Please refer to [Authentication](https://developer.gs.com/p/docs/institutional/platform/authentication/) for details." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "from gs_quant.data import Dataset" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# get the dataset\n", "dataset = Dataset('EDRVOL_PERCENT_STANDARD')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Dataset Coverage\n", "Dataset coverage is a list of the symbol dimensions in a dataset. This allows users to quickly see what is available in a dataset.\n", "Coverage can by fetched via API as below or in the [dataset product page](https://marquee.gs.com/s/developer/datasets/EDRVOL_PERCENT_STANDARD/coverage)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [ { "data": { "text/plain": [ "(25, 4)" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# get the coverage\n", "coverage = dataset.get_coverage(include_history=True)\n", "\n", "# show the number of records and columns\n", "coverage.shape" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [ { "data": { "text/plain": [ "Index(['bbid', 'assetId', 'name', 'historyStartDate'], dtype='object')" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# show the columns names\n", "coverage.keys()" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
bbidassetIdnamehistoryStartDate
0AEXMA4B66MW5E27U8NN8LSAEX Index2018-06-03
1IBOVMA4B66MW5E27U8NN8N6Brazil Ibovespa Index2018-06-03
2INDUMA4B66MW5E27U8NN8SPDow Jones Industrial Average2018-06-03
3CACMA4B66MW5E27U8NN922CAC 40 Index2018-06-03
4UKXMA4B66MW5E27U8NN95PFTSE 100 Index2018-06-03
5DAXMA4B66MW5E27U8NPZQYDAX Index2018-06-03
6HSCEIMA4B66MW5E27U8P3294Hang Seng China Ent Index2018-06-03
7HSIMA4B66MW5E27U8P3295Hang Seng Index2018-06-03
8KOSPI2MA4B66MW5E27U8P32DMKOSPI 200 Index2018-06-03
9MXEAMA4B66MW5E27U8P32GBMSCI EAFE2018-06-03
10MXEFMA4B66MW5E27U8P32KKMSCI Emerging Markets Index2018-06-03
11MEXBOLMA4B66MW5E27U8P32LES&P/bmv Ipc2018-06-03
12NKYMA4B66MW5E27U8P32LHNikkei 2252018-06-03
13NDXMA4B66MW5E27U8P32LQNasdaq 100 Stock Index2018-06-03
14RTYMA4B66MW5E27U8P32PCRussell 2000 Index2018-06-03
15SPTSX60MA4B66MW5E27U8P32S9S&P/TSX 60 Index2018-06-03
16SPXMA4B66MW5E27U8P32SBS&P 500 Index2018-06-03
17SMIMA4B66MW5E27U8P32SRSwiss Market Index2018-06-03
18SX5EMA4B66MW5E27U8P32SYEuro Stoxx 50 Price Return2018-06-03
19TPXMA4B66MW5E27U8P32XGTOPIX Index (Tokyo)2018-06-03
20EEM UPMA4B66MW5E27UAEP4R5iShares MSCI Emerging Markets2018-06-03
21EFA UPMA4B66MW5E27UAEP4RTiShares MSCI EAFE ETF2018-06-03
22GLD UPMA4B66MW5E27UAGYYRRSPDR Gold Shares2018-06-03
23XLF UPMA4B66MW5E27UANZH2RFinancial Select Sector SPDR2018-06-03
24XLK UPMA4B66MW5E27UANZH2TTechnology Select Sect SPDR2018-06-03
\n", "
" ], "text/plain": [ " bbid assetId name \\\n", "0 AEX MA4B66MW5E27U8NN8LS AEX Index \n", "1 IBOV MA4B66MW5E27U8NN8N6 Brazil Ibovespa Index \n", "2 INDU MA4B66MW5E27U8NN8SP Dow Jones Industrial Average \n", "3 CAC MA4B66MW5E27U8NN922 CAC 40 Index \n", "4 UKX MA4B66MW5E27U8NN95P FTSE 100 Index \n", "5 DAX MA4B66MW5E27U8NPZQY DAX Index \n", "6 HSCEI MA4B66MW5E27U8P3294 Hang Seng China Ent Index \n", "7 HSI MA4B66MW5E27U8P3295 Hang Seng Index \n", "8 KOSPI2 MA4B66MW5E27U8P32DM KOSPI 200 Index \n", "9 MXEA MA4B66MW5E27U8P32GB MSCI EAFE \n", "10 MXEF MA4B66MW5E27U8P32KK MSCI Emerging Markets Index \n", "11 MEXBOL MA4B66MW5E27U8P32LE S&P/bmv Ipc \n", "12 NKY MA4B66MW5E27U8P32LH Nikkei 225 \n", "13 NDX MA4B66MW5E27U8P32LQ Nasdaq 100 Stock Index \n", "14 RTY MA4B66MW5E27U8P32PC Russell 2000 Index \n", "15 SPTSX60 MA4B66MW5E27U8P32S9 S&P/TSX 60 Index \n", "16 SPX MA4B66MW5E27U8P32SB S&P 500 Index \n", "17 SMI MA4B66MW5E27U8P32SR Swiss Market Index \n", "18 SX5E MA4B66MW5E27U8P32SY Euro Stoxx 50 Price Return \n", "19 TPX MA4B66MW5E27U8P32XG TOPIX Index (Tokyo) \n", "20 EEM UP MA4B66MW5E27UAEP4R5 iShares MSCI Emerging Markets \n", "21 EFA UP MA4B66MW5E27UAEP4RT iShares MSCI EAFE ETF \n", "22 GLD UP MA4B66MW5E27UAGYYRR SPDR Gold Shares \n", "23 XLF UP MA4B66MW5E27UANZH2R Financial Select Sector SPDR \n", "24 XLK UP MA4B66MW5E27UANZH2T Technology Select Sect SPDR \n", "\n", " historyStartDate \n", "0 2018-06-03 \n", "1 2018-06-03 \n", "2 2018-06-03 \n", "3 2018-06-03 \n", "4 2018-06-03 \n", "5 2018-06-03 \n", "6 2018-06-03 \n", "7 2018-06-03 \n", "8 2018-06-03 \n", "9 2018-06-03 \n", "10 2018-06-03 \n", "11 2018-06-03 \n", "12 2018-06-03 \n", "13 2018-06-03 \n", "14 2018-06-03 \n", "15 2018-06-03 \n", "16 2018-06-03 \n", "17 2018-06-03 \n", "18 2018-06-03 \n", "19 2018-06-03 \n", "20 2018-06-03 \n", "21 2018-06-03 \n", "22 2018-06-03 \n", "23 2018-06-03 \n", "24 2018-06-03 " ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# show the coverage\n", "coverage" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
bbidassetIdnamehistoryStartDate
16SPXMA4B66MW5E27U8P32SBS&P 500 Index2018-06-03
\n", "
" ], "text/plain": [ " bbid assetId name historyStartDate\n", "16 SPX MA4B66MW5E27U8P32SB S&P 500 Index 2018-06-03" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# find specific asset\n", "coverage[coverage['bbid'] == 'SPX']" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: gs_quant/documentation/00_data/00_datasets/examples/0002_using_data_contexts.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from datetime import date\n", "\n", "import gs_quant.timeseries as ts\n", "from gs_quant.data import DataContext\n", "from gs_quant.markets.securities import SecurityMaster, AssetIdentifier, ExchangeCode\n", "from gs_quant.session import GsSession, Environment" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Create a data context covering 2018\n", "data_ctx = DataContext(start=date(2018, 1, 1), end=date(2018, 12, 31))\n", "\n", "# Lookup S&P 500 Index via Security Master\n", "spx = SecurityMaster.get_asset('SPX', AssetIdentifier.TICKER, exchange_code=ExchangeCode.NYSE)\n", "\n", "# Use the data context\n", "with data_ctx:\n", " # Get 25 delta call implied volatility\n", " vol = ts.implied_volatility(spx, '1m', ts.VolReference.DELTA_CALL, 25)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "vol.tail()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: gs_quant/documentation/00_data/00_datasets/examples/0003_get_data_using_coordinate.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "from gs_quant.data import DataMeasure\n", "from gs_quant.markets.securities import SecurityMaster, AssetIdentifier, ExchangeCode\n", "from gs_quant.session import Environment, GsSession" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterh\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('read_product_data',))" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "# Lookup S&P 500 Index via Security Master\n", "spx = SecurityMaster.get_asset('SPX', AssetIdentifier.TICKER, exchange_code=ExchangeCode.NYSE)\n", "\n", "# Resolve the data coordinate for SPX without explicitly specifying the dataset\n", "spot_data_coordinate = spx.get_data_coordinate(DataMeasure.SPOT_PRICE)\n", "\n", "# Fetch the series\n", "series = spot_data_coordinate.get_series()" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "series.tail()" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } } ], "metadata": { "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" }, "kernelspec": { "name": "python3", "language": "python", "display_name": "Python 3" }, "pycharm": { "stem_cell": { "cell_type": "raw", "source": [], "metadata": { "collapsed": false } } } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/00_data/00_datasets/examples/0004_get_all_assets_from_region.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "Examples require an initialized GsSession and relevant entitlements. External clients need to substitute thier own client id and client secret below. Please refer to [Authentication](https://developer.gs.com/p/docs/institutional/platform/authentication/) for details." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "from gs_quant.api.gs.assets import GsAssetApi\n", "from gs_quant.data import Dataset\n", "import pandas as pd" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "ds = Dataset('EDRVOL_PERCENT_STANDARD') # https://marquee.gs.com/s/developer/datasets/EDRVOL_PERCENT_STANDARD\n", "ids = ds.get_coverage().get('assetId')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Filtering coverage by Asset properties\n", "A lot of datasets use Marquee Asset Id as symbol dimension. Marquee Asset Service to get more properties for these assets and/or filter by them.\n", "Below example showcases how to filter assets by Region" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# find all assets from given region (One of \"Asia\", \"Europe\", \"Americas\")\n", "def get_assets_in_region(region, asset_ids):\n", " step = 500\n", " size = asset_ids.shape[0]\n", " assets = []\n", " # go over all assets in the dataset in batches of 500 and query for region\n", " for i in range(0, size, step):\n", " end = min(i + step, size)\n", " # prepare the query\n", " query = {'id': asset_ids.values[i:end].tolist(), 'region': region}\n", " # run the query\n", " results = GsAssetApi.get_many_assets_data(limit=step, **query)\n", " assets.extend(results)\n", " return assets" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "filtered_assets = get_assets_in_region('Europe', ids)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
idbbidregioncurrencyexchange
0MA4B66MW5E27U8P32SRSMIEuropeCHFEBS
1MA4B66MW5E27U8P32SYSX5EEuropeEURDTBX
2MA4B66MW5E27U8NPZQYDAXEuropeEURXETR
3MA4B66MW5E27U8P32GBMXEAEuropeUSDDTBX
4MA4B66MW5E27U8NN95PUKXEuropeGBPLNSE
5MA4B66MW5E27U8NN922CACEuropeEURPARE
6MA4B66MW5E27U8NN8LSAEXEuropeEURAMSE
\n", "
" ], "text/plain": [ " id bbid region currency exchange\n", "0 MA4B66MW5E27U8P32SR SMI Europe CHF EBS\n", "1 MA4B66MW5E27U8P32SY SX5E Europe EUR DTBX\n", "2 MA4B66MW5E27U8NPZQY DAX Europe EUR XETR\n", "3 MA4B66MW5E27U8P32GB MXEA Europe USD DTBX\n", "4 MA4B66MW5E27U8NN95P UKX Europe GBP LNSE\n", "5 MA4B66MW5E27U8NN922 CAC Europe EUR PARE\n", "6 MA4B66MW5E27U8NN8LS AEX Europe EUR AMSE" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pd.DataFrame(filtered_assets, columns=['id', 'bbid', 'region', 'currency', 'exchange'])" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: gs_quant/documentation/00_data/00_datasets/examples/0005_get_vol_surface.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "increasing-model", "metadata": {}, "source": [ "Examples require an initialized GsSession and relevant entitlements. External clients need to substitute thier own client id and client secret below. Please refer to [Authentication](https://developer.gs.com/p/docs/institutional/platform/authentication/) for details." ] }, { "cell_type": "code", "execution_count": 1, "id": "color-legislation", "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": 2, "id": "destroyed-saying", "metadata": {}, "outputs": [], "source": [ "from gs_quant.data import Dataset\n", "import datetime as dt" ] }, { "cell_type": "markdown", "id": "labeled-tobacco", "metadata": {}, "source": [ "The entire vol surface is published for each snap of the data, so we don’t have to pull diffs and reconstruct the surface" ] }, { "cell_type": "code", "execution_count": 3, "id": "accepted-jungle", "metadata": {}, "outputs": [], "source": [ "dataset_id = 'EDRVOL_PERCENT_V1_STANDARD' # https://marquee.gs.com/s/developer/datasets/EDRVOL_PERCENT_STANDARD\n", "ds = Dataset(dataset_id)" ] }, { "cell_type": "markdown", "id": "smoking-musical", "metadata": {}, "source": [ "Get the latest available datapoint." ] }, { "cell_type": "code", "execution_count": 4, "id": "digital-korean", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
assetIdstrikeReferencetenorrelativeStrikeabsoluteStrikeimpliedVolatilityupdateTimebbid
date
2021-06-07MA4B66MW5E27U8P32SBforward9m1.56298.5827570.1547952021-06-07 23:12:59SPX
\n", "
" ], "text/plain": [ " assetId strikeReference tenor relativeStrike \\\n", "date \n", "2021-06-07 MA4B66MW5E27U8P32SB forward 9m 1.5 \n", "\n", " absoluteStrike impliedVolatility updateTime bbid \n", "date \n", "2021-06-07 6298.582757 0.154795 2021-06-07 23:12:59 SPX " ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "last = ds.get_data_last(as_of=dt.date.today(), bbid=['SPX'])\n", "last" ] }, { "cell_type": "markdown", "id": "twenty-relief", "metadata": {}, "source": [ "We can use this to identify the timestamp of the latest surface and then query for the full surface matching this timestamp" ] }, { "cell_type": "code", "execution_count": 5, "id": "finnish-consultancy", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
assetIdstrikeReferencetenorrelativeStrikeabsoluteStrikeimpliedVolatilityupdateTimebbid
date
2021-06-07MA4B66MW5E27U8P32SBforward18m0.401668.1519250.3936842021-06-07 23:12:59SPX
2021-06-07MA4B66MW5E27U8P32SBforward18m0.502085.1899070.3517152021-06-07 23:12:59SPX
2021-06-07MA4B66MW5E27U8P32SBforward18m0.552293.7088970.3332462021-06-07 23:12:59SPX
2021-06-07MA4B66MW5E27U8P32SBforward18m0.602502.2278880.3158522021-06-07 23:12:59SPX
2021-06-07MA4B66MW5E27U8P32SBforward18m0.652710.7468790.2992882021-06-07 23:12:59SPX
\n", "
" ], "text/plain": [ " assetId strikeReference tenor relativeStrike \\\n", "date \n", "2021-06-07 MA4B66MW5E27U8P32SB forward 18m 0.40 \n", "2021-06-07 MA4B66MW5E27U8P32SB forward 18m 0.50 \n", "2021-06-07 MA4B66MW5E27U8P32SB forward 18m 0.55 \n", "2021-06-07 MA4B66MW5E27U8P32SB forward 18m 0.60 \n", "2021-06-07 MA4B66MW5E27U8P32SB forward 18m 0.65 \n", "\n", " absoluteStrike impliedVolatility updateTime bbid \n", "date \n", "2021-06-07 1668.151925 0.393684 2021-06-07 23:12:59 SPX \n", "2021-06-07 2085.189907 0.351715 2021-06-07 23:12:59 SPX \n", "2021-06-07 2293.708897 0.333246 2021-06-07 23:12:59 SPX \n", "2021-06-07 2502.227888 0.315852 2021-06-07 23:12:59 SPX \n", "2021-06-07 2710.746879 0.299288 2021-06-07 23:12:59 SPX " ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "snap_time = last.index[0].date() # the dataframe is indexed on date\n", "df = ds.get_data(bbid=['SPX'], start=snap_time, end=snap_time, strikeReference='forward')\n", "\n", "df.head()" ] }, { "cell_type": "markdown", "id": "vulnerable-pipeline", "metadata": {}, "source": [ "The dataframe can be consumed directly or can be serialized for consumption by a different process" ] }, { "cell_type": "code", "execution_count": null, "id": "growing-gibson", "metadata": {}, "outputs": [], "source": [ "with open(f'SPX_{snap_time}_curve.csv', 'w') as f:\n", " df.to_csv(f)" ] }, { "cell_type": "markdown", "id": "frozen-twenty", "metadata": {}, "source": [ "Above process can be abstracted to a function" ] }, { "cell_type": "code", "execution_count": 6, "id": "negative-hampton", "metadata": {}, "outputs": [], "source": [ "def get_latest_vol_surface(dataset, bbid, strike_reference='delta', intraday=False):\n", " # Get the date/time of the most recent snap\n", " as_of = dt.datetime.now() if intraday else dt.date.today()\n", " last_data = dataset.get_data_last(as_of=as_of, bbid=bbid)\n", "\n", " # Pull the surface with the date/time of the most recent snap\n", " last_time = last_data.index[0] if intraday else last_data.index[0].date()\n", " df = ds.get_data(bbid=bbid, start=last_time, end=last_time, strikeReference=strike_reference)\n", "\n", " # Write latest vol surface to CSV\n", " with open(f'{bbid}_{last_time}.csv', 'w') as f:\n", " print(f'Writing latest vol surface for {bbid} to {bbid}_{last_time}.csv')\n", " df.to_csv(f)\n", "\n", " return df" ] }, { "cell_type": "code", "execution_count": 7, "id": "english-western", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Writing latest vol surface for SPX to SPX_2021-06-07.csv\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
assetIdstrikeReferencetenorrelativeStrikeabsoluteStrikeimpliedVolatilityupdateTimebbid
date
2021-06-07MA4B66MW5E27U8P32SBforward18m0.401668.1519250.3936842021-06-07 23:12:59SPX
2021-06-07MA4B66MW5E27U8P32SBforward18m0.502085.1899070.3517152021-06-07 23:12:59SPX
2021-06-07MA4B66MW5E27U8P32SBforward18m0.552293.7088970.3332462021-06-07 23:12:59SPX
2021-06-07MA4B66MW5E27U8P32SBforward18m0.602502.2278880.3158522021-06-07 23:12:59SPX
2021-06-07MA4B66MW5E27U8P32SBforward18m0.652710.7468790.2992882021-06-07 23:12:59SPX
\n", "
" ], "text/plain": [ " assetId strikeReference tenor relativeStrike \\\n", "date \n", "2021-06-07 MA4B66MW5E27U8P32SB forward 18m 0.40 \n", "2021-06-07 MA4B66MW5E27U8P32SB forward 18m 0.50 \n", "2021-06-07 MA4B66MW5E27U8P32SB forward 18m 0.55 \n", "2021-06-07 MA4B66MW5E27U8P32SB forward 18m 0.60 \n", "2021-06-07 MA4B66MW5E27U8P32SB forward 18m 0.65 \n", "\n", " absoluteStrike impliedVolatility updateTime bbid \n", "date \n", "2021-06-07 1668.151925 0.393684 2021-06-07 23:12:59 SPX \n", "2021-06-07 2085.189907 0.351715 2021-06-07 23:12:59 SPX \n", "2021-06-07 2293.708897 0.333246 2021-06-07 23:12:59 SPX \n", "2021-06-07 2502.227888 0.315852 2021-06-07 23:12:59 SPX \n", "2021-06-07 2710.746879 0.299288 2021-06-07 23:12:59 SPX " ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "get_latest_vol_surface(ds, 'SPX', strike_reference='forward').head()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/00_data/00_datasets/examples/0006_ptp_dataset.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "76da2538", "metadata": {}, "source": [ "Examples require an initialized GsSession and relevant entitlements. External clients need to substitute thier own client id and client secret below. Please refer to [Authentication](https://developer.gs.com/p/docs/institutional/platform/authentication/) for details." ] }, { "cell_type": "code", "execution_count": 1, "id": "a2dd9568", "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession, Environment\n", "from datetime import date\n", "from gs_quant.data import PTPDataset\n", "import pandas as pd\n", "\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('read_product_data',))" ] }, { "cell_type": "markdown", "id": "402e5482", "metadata": {}, "source": [ "# PTP Datasets\n", "\n", "PlotTool Pro provides a way to easily upload and visualize your data stored with Marquee Data Services, and you can use `gs_quant`'s `PTPDataset` class to upload, save, and view your `pandas` data structures. \n", "\n", "For now, PTP datasets are only available for internal Marquee users.\n", "\n", "Data uploaded to a PTP dataset must be\n", "1. Numeric\n", "1. Indexed by a `pd.DatetimeIndex`\n", "1. EOD (as opposed to real-time)\n", "\n", "For further information, see the `PTPDataset` class. " ] }, { "cell_type": "markdown", "id": "e325a576", "metadata": {}, "source": [ "## Workflow" ] }, { "cell_type": "markdown", "id": "41a9f73e", "metadata": {}, "source": [ "#### Create a PTP Dataset\n", "\n", "When creating a new PTP dataset, you first initialize an object, passing in a `pandas` Series or DataFrame, and an optional name. If you don't provide a name, the dataset's name will default to \"GSQ Default.\" \n", "\n", "If you pass a DataFrame, the field names in your dataset will be derived from the DataFrame's column names; if you provide a Series, the field name will be taken from the series name (if it has one), or otherwise \"values\" by de\n", "\n", "**Note**: your data is not *saved* until you call `.sync()` on your PTP dataset object. " ] }, { "cell_type": "code", "execution_count": 7, "id": "b52dae72", "metadata": {}, "outputs": [], "source": [ "df = pd.DataFrame(\n", " {'col': range(50), 'fieldb': range(100, 150)}, index=pd.date_range(start=date(2021, 1, 1), periods=50, freq='D')\n", ")\n", "dataset = PTPDataset(df, 'Test Dataset')" ] }, { "cell_type": "markdown", "id": "fb2aad49", "metadata": {}, "source": [ "#### Sync\n", "\n", "Sync your dataset to save your data. " ] }, { "cell_type": "code", "execution_count": 8, "id": "4e0cf7f6", "metadata": {}, "outputs": [], "source": [ "dataset.sync()" ] }, { "cell_type": "markdown", "id": "b25a2be3", "metadata": {}, "source": [ "#### Plot\n", "\n", "Generate a transient plot expression, which will bring you to a plot displaying your data. \n", "\n", "Make sure to hit \"Copy to My Plots\" to save the plot (although you can always re-generate another transient plot expression). \n", "\n", "If you're running `gs_quant` on a device with a default browser, `.plot()` will automatically open the plot for you. " ] }, { "cell_type": "code", "execution_count": 9, "id": "afd058d6", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'https://marquee.web.gs.com/s/plottool/transient?expr=Dataset(\"PTP_TEST_DATASET_LOAK1\").col()%0ADataset(\"PTP_TEST_DATASET_LOAK1\").fieldb()'" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dataset.plot(open_in_browser=False)" ] }, { "cell_type": "markdown", "id": "fb870105", "metadata": {}, "source": [ "#### Other dataset functions\n", "\n", "Since `PTPDataset` inherits from the `Dataset` class, it has all the functionality of a normal `Dataset` object in addition to the methods above. \n", "\n", "For example, to delete a dataset: " ] }, { "cell_type": "code", "execution_count": 10, "id": "853c639d", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Successfully deleted dataset.'" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dataset.delete()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/00_data/00_datasets/examples/0007_query_fxvol.script.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "dc1b5843-eca4-47f5-8e3c-4e4dc83775f9", "metadata": {}, "outputs": [], "source": [ "import datetime\n", "from functools import lru_cache\n", "import pandas as pd\n", "from gs_quant.api.gs.assets import GsAssetApi\n", "from gs_quant.data import Dataset\n", "from gs_quant.session import GsSession, Environment" ] }, { "cell_type": "code", "execution_count": null, "id": "fde1622d-5e57-4f4e-9602-7ac6aa75f23a", "metadata": {}, "outputs": [], "source": [ "# Internals\n", "@lru_cache(maxsize=128)\n", "def _get_coverage(ds):\n", " print(\"Fetching coverage\", end=\" \")\n", " cov = ds.get_coverage()[\"assetId\"].to_list()\n", " print(\"[DONE]\")\n", " return cov" ] }, { "cell_type": "code", "execution_count": null, "id": "34341f3d-cf64-4519-b808-335d874b3cd0", "metadata": {}, "outputs": [], "source": [ "def _nest_list(flat_list, nested_size):\n", " return [flat_list[i : i + nested_size] for i in range(0, len(flat_list), nested_size)]" ] }, { "cell_type": "code", "execution_count": null, "id": "f73538c6-a774-4ae4-bacd-fa584428cd9f", "metadata": {}, "outputs": [], "source": [ "def _get_data_by_asset_parameters(ds, start_date, end_date, pricing_location=\"NYC\", chunk_size=100, **kwargs):\n", " coverage = _get_coverage(ds)\n", " assets = []\n", " asset_chunks = _nest_list(coverage, 5000)\n", " print(\"Reading Assets:\", end=\" \")\n", " for ch in asset_chunks:\n", " print(\"#\", end=\"\")\n", " kwargs[\"id\"] = ch\n", " assets += GsAssetApi.get_many_assets([\"id\"], limit=10000, **kwargs)\n", " print(\" [DONE]\")\n", "\n", " ids = [a.id for a in assets]\n", " print(\"Assets to fetch \" + str(len(ids)))\n", " chunks = _nest_list(ids, chunk_size)\n", " frames = [pd.DataFrame()]\n", " print(\"Reading Data:\", end=\" \")\n", " for ch in chunks:\n", " print(\"#\", end=\"\")\n", " frames.append(ds.get_data(start_date, end_date, assetId=ch, pricingLocation=pricing_location))\n", " print(\" [DONE]\")\n", " return pd.concat(frames)" ] }, { "cell_type": "code", "execution_count": null, "id": "93546060-b2a4-42c1-94f9-87939b07e1e1", "metadata": {}, "outputs": [], "source": [ "def get_fxivol(\n", " ds,\n", " bbid: str = None,\n", " tenor=None,\n", " strike_reference=None,\n", " relative_strike=None,\n", " pricing_location=\"NYC\",\n", " start_date=datetime.date.today(),\n", " end_date=datetime.date.today(),\n", "):\n", " \"\"\"\n", " :param ds: Dataset object\n", " :param bbid: selected identifier i.e. EURUSD\n", " :param tenor: tenor of the option i.e. 1y, 5y\n", " :param strike_reference: one of \"delta\", \"forward\", \"spot\", one supported at at time\n", " :param relative_strike: moneyness of the option i.e. -5, 0, 5 ... etc Mixing negative and positive are not supported\n", " :param pricing_location:\n", " :param start_date: start date of the query\n", " :param end_date: end date of the query\n", " :return: DataFrame with data\n", " \"\"\"\n", " query = dict()\n", " if bbid is not None:\n", " query[\"asset_parameters_call_currency\"] = [bbid[0:3], bbid[3:]]\n", " query[\"asset_parameters_put_currency\"] = [bbid[0:3], bbid[3:]]\n", " if strike_reference is not None:\n", " relative_strike_l = relative_strike\n", " if strike_reference == \"delta\" and relative_strike is not None:\n", " if not isinstance(relative_strike, list):\n", " relative_strike_l = [relative_strike]\n", " if min(relative_strike_l) < 0 < max(relative_strike_l):\n", " print(\"ERROR: Relative strikes should be same sign in the query to work properly\")\n", " return None\n", " query[\"asset_parameters_option_type\"] = \"Put\" if relative_strike_l[0] < 0 else \"Call\"\n", " query[\"asset_parameters_strike_price_relative\"] = [\n", " str(abs(s)) + \"D\" if s != 0 else \"DN\" for s in relative_strike_l\n", " ]\n", "\n", " if strike_reference == \"forward\":\n", " query[\"asset_parameters_strike_price_relative\"] = \"ATMF\"\n", " if strike_reference == \"spot\":\n", " query[\"asset_parameters_strike_price_relative\"] = [\"ATMS\", \"Spot\", \"ATM\"]\n", "\n", " if tenor is not None:\n", " query[\"asset_parameters_expiration_date\"] = tenor\n", "\n", " print(query)\n", " return _get_data_by_asset_parameters(ds, start_date, end_date, pricing_location=pricing_location, **query)" ] }, { "cell_type": "code", "execution_count": null, "id": "516cd795-dcf1-40ee-bf59-cb7189d3a204", "metadata": {}, "outputs": [], "source": [ "def get_fx_fwd(\n", " ds,\n", " bbid: str = None,\n", " tenor=None,\n", " pricing_location=\"NYC\",\n", " start_date=datetime.date.today(),\n", " end_date=datetime.date.today(),\n", "):\n", " \"\"\"\n", " :param ds: Dataset object\n", " :param bbid: selected identifier i.e. EURUSD\n", " :param tenor: tenor of the option i.e. 1y, 5y\n", " :param pricing_location:\n", " :param start_date: start date of the query\n", " :param end_date: end date of the query\n", " :return: DataFrame with data\n", " \"\"\"\n", " args = dict()\n", " if bbid is not None:\n", " args[\"asset_parameters_pair\"] = bbid\n", " if tenor is not None:\n", " args[\"asset_parameters_expiration_date\"] = tenor\n", " return _get_data_by_asset_parameters(ds, start_date, end_date, pricing_location=pricing_location, **args)" ] }, { "cell_type": "code", "execution_count": null, "id": "28f5b009-3302-4829-b2a3-d6f56c61577e", "metadata": {}, "outputs": [], "source": [ "# USER DETAILS\n", "CLIENT_ID = None\n", "SECRET = None\n", "GsSession.use(Environment.PROD, client_id=CLIENT_ID, client_secret=SECRET)" ] }, { "cell_type": "code", "execution_count": null, "id": "5739dad6-aee6-4dde-b504-a355233fc4cd", "metadata": {}, "outputs": [], "source": [ "# SELECT DATASET\n", "FX_VOL_DS = Dataset(\"FXIVOL_V2_PREMIUM\")\n", "start_date = datetime.date(2021, 12, 17)\n", "end_date = datetime.date(2022, 12, 17)" ] }, { "cell_type": "code", "execution_count": null, "id": "3528c97b-9f4a-4381-a92f-5bf71a3d304a", "metadata": {}, "outputs": [], "source": [ "vol = get_fxivol(\n", " FX_VOL_DS,\n", " start_date=start_date,\n", " end_date=end_date,\n", " bbid=\"EURUSD\",\n", " relative_strike=[-10],\n", " tenor=\"5y\",\n", " strike_reference=\"delta\",\n", ")\n", "print(vol)" ] }, { "cell_type": "code", "execution_count": null, "id": "992630f6-7614-4165-94f8-18ebca34e25e", "metadata": {}, "outputs": [], "source": [ "vol = get_fxivol(\n", " FX_VOL_DS,\n", " start_date=start_date,\n", " end_date=end_date,\n", " bbid=\"EURUSD\",\n", " relative_strike=[10],\n", " tenor=\"5y\",\n", " strike_reference=\"delta\",\n", ")\n", "print(vol)" ] }, { "cell_type": "code", "execution_count": null, "id": "a5730f24-8624-44c8-93a5-5cf4683b2423", "metadata": {}, "outputs": [], "source": [ "vol = get_fxivol(\n", " FX_VOL_DS, start_date=start_date, end_date=end_date, bbid=\"USDPLN\", strike_reference=\"delta\", tenor=\"3m\"\n", ")\n", "print(vol)" ] }, { "cell_type": "code", "execution_count": null, "id": "3d62c755-55c9-42e7-9407-9f78ab1f10b2", "metadata": {}, "outputs": [], "source": [ "vol = get_fxivol(\n", " FX_VOL_DS,\n", " start_date=start_date,\n", " end_date=end_date,\n", " bbid=\"EURUSD\",\n", " relative_strike=[0, 10, 15],\n", " strike_reference=\"delta\",\n", " tenor=\"1y\",\n", ")\n", "print(vol)" ] }, { "cell_type": "code", "execution_count": null, "id": "ac617f52-0004-487a-b6a2-e67757846a51", "metadata": {}, "outputs": [], "source": [ "vol = get_fxivol(\n", " FX_VOL_DS, start_date=start_date, end_date=end_date, bbid=\"USDPLN\", strike_reference=\"forward\", tenor=\"5y\"\n", ")\n", "print(vol)" ] }, { "cell_type": "code", "execution_count": null, "id": "d4637647-6cd0-4695-b081-8a22b75081bb", "metadata": {}, "outputs": [], "source": [ "vol = get_fxivol(\n", " FX_VOL_DS, start_date=start_date, end_date=end_date, bbid=\"USDPLN\", strike_reference=\"spot\", tenor=\"1y\"\n", ")\n", "print(vol)" ] }, { "cell_type": "code", "execution_count": null, "id": "6ba12394-7b8d-4136-974e-5d100ad4c091", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.13" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/00_data/00_datasets/examples/0008_s3_partners_short_interest.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "c6b8ac530007ea21", "metadata": { "collapsed": false }, "source": [ "## Step 1: Authentication\n", "\n", "The first step is to initialize a GsSession with your application ID. For more information on how to create and register an application, check this [tutorial](https://developer.gs.com/p/docs/institutional/platform/getting-started/). In the snippet below, substitute client_id and client_secret with your application ID and secret respectively." ] }, { "cell_type": "code", "execution_count": 1, "id": "db1c365daf064da", "metadata": { "ExecuteTime": { "end_time": "2024-08-20T20:55:42.569859800Z", "start_time": "2024-08-20T20:55:39.923882800Z" }, "collapsed": false }, "outputs": [], "source": [ "from gs_quant.session import GsSession, Environment\n", "\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None)" ] }, { "cell_type": "markdown", "id": "dcd6b4e12461dea8", "metadata": { "collapsed": false }, "source": [ "## Step 2: Define parameters\n", "\n", "Next, we import all required modules and define required parameters:\n", "\n", "1. **The Dataset ID**: S3_PARTNERS_EQUITY_SHORT_INTEREST\n", "2. **A time range** \n", "3. **A list of assets** to query data for" ] }, { "cell_type": "code", "execution_count": null, "id": "b35aecebdb1a47d9", "metadata": { "collapsed": false }, "outputs": [], "source": [ "from gs_quant.data import Dataset\n", "import datetime as dt\n", "\n", "dataset_id = \"S3_PARTNERS_EQUITY_SHORT_INTEREST\"\n", "\n", "dataset = Dataset(dataset_id)\n", "start_date = dt.date(2024, 5, 1)\n", "end_date = dt.date(2024, 8, 1)\n", "bbids = [\"META UW\", \"NVDA UW\", \"AAPL UW\", \"MSFT UW\", \"AMZN UW\", \"GOOG UW\", \"TSLA UW\"]" ] }, { "cell_type": "markdown", "id": "dbe6305116d93bac", "metadata": { "collapsed": false }, "source": [ "**Tip**: You can use the get_coverage function to see the full list of equity securities for which there is data:" ] }, { "cell_type": "code", "execution_count": null, "id": "291c364a533d9dc", "metadata": { "collapsed": false }, "outputs": [], "source": [ "coverage = dataset.get_coverage()" ] }, { "cell_type": "markdown", "id": "1361ea71b49d9289", "metadata": { "collapsed": false }, "source": [ "### Step 3: Get data\n", "\n", "Now, it's time to get data. We will just have to plug in the parameters we defined above\n", "\n", "1. **start**: The start date for the query\n", "2. **end**: The end date for the query\n", "3. **bbid**: Bloomberg IDs of assets. " ] }, { "cell_type": "code", "execution_count": null, "id": "26ce1a7acf1eb902", "metadata": { "collapsed": false }, "outputs": [], "source": [ "s3_data = dataset.get_data(start=start_date, end=end_date, bbid=bbids)" ] }, { "cell_type": "markdown", "id": "cb8ffba3955000af", "metadata": { "collapsed": false }, "source": [ "#### What if I want to pass in other identifiers, such as SEDOLs or ISINs instead of BBIDs? \n", "\n", "You can also pass other identifiers (such as ticker, SEDOL, ISIN, CUSIP, etc).\n", "\n", "Please note certain identifiers may be subject to licensing." ] }, { "cell_type": "code", "execution_count": null, "id": "c914b3b91cd89679", "metadata": { "collapsed": false }, "outputs": [], "source": [ "s3_data = dataset.get_data(start=end_date, end=end_date, sedol=[\"2857817\", \"2206657\"])\n", "s3_data = dataset.get_data(start=end_date, end=end_date, isin=[\"US7134481081\", \"US5801351017\"])" ] }, { "cell_type": "markdown", "id": "703802dc2f6a0ec6", "metadata": { "collapsed": false }, "source": [ "### Step 4: Get individual fields\n", "\n", "You many also want to query a timeseries for a subset of fields in the dataset. Below is a 3-months timeseries daily short interest and short interest percent data for a group of stocks." ] }, { "cell_type": "code", "execution_count": null, "id": "6eed59910d32b3", "metadata": { "collapsed": false }, "outputs": [], "source": [ "daily_short_interest = dataset.get_data(\n", " start=start_date, end=end_date, bbid=bbids, fields=[\"dailyShortInterest\", \"shortInterestPercent\"]\n", ")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/00_data/01_analytics/examples/0000_charting_data.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "\n", "from gs_quant.data import Dataset\n", "from gs_quant.markets import PricingContext\n", "from gs_quant.session import GsSession, Environment" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('read_product_data',))" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "market_date = PricingContext.current.market.date # Determine current market date\n", "\n", "vol_dataset = Dataset('EDRVOL_PERCENT_STANDARD') # Initialize the equity implied volatility dataset\n", "vol_data = vol_dataset.get_data(market_date, market_date, ticker='SPX', tenor='1m', strikeReference='forward')\n", "\n", "strikes = vol_data['relativeStrike']\n", "vols = vol_data['impliedVolatility'] * 100" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "plt.plot(strikes, vols, label='Implied Volatility by Strike')\n", "plt.xlabel('Relative Strike')\n", "plt.ylabel('Implied Volatility')\n", "plt.title('Implied Volatility by Strike')\n", "\n", "plt.show()" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } } ], "metadata": { "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" }, "kernelspec": { "name": "python3", "language": "python", "display_name": "Python 3" }, "pycharm": { "stem_cell": { "cell_type": "raw", "source": [], "metadata": { "collapsed": false } } } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/00_data/01_analytics/examples/0001_exporting_data.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "import datetime as dt\n", "\n", "from gs_quant.data import Dataset\n", "from gs_quant.session import Environment, GsSession" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('read_product_data',))" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "# fetch the data\n", "weather_ds = Dataset(Dataset.GS.WEATHER)\n", "panda_df = weather_ds.get_data(dt.date(2016, 1, 15), dt.date(2016, 1, 16), city=['Boston', 'Austin'])" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "panda_df.to_csv('my_data.csv')" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } } ], "metadata": { "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" }, "kernelspec": { "name": "python3", "language": "python", "display_name": "Python 3" }, "pycharm": { "stem_cell": { "cell_type": "raw", "source": [], "metadata": { "collapsed": false } } } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/00_data/02_workspaces/examples/0001_creating_a_workspace.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true, "pycharm": { "is_executing": false } }, "outputs": [], "source": [ "from gs_quant.api.gs.users import GsUsersApi\n", "from gs_quant.api.gs.workspaces import GsWorkspacesMarketsApi as ws_api\n", "from gs_quant.session import Environment, GsSession\n", "from gs_quant.target.workspaces_markets import *" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "GsSession.use(Environment.PROD, client_id=None, client_secret=None)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Initializing your Workspace\n", "\n", "Determine the basics of your Workspace (i.e name, description and entitlements over who can view and edit)" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "name = 'Example Workspace'\n", "alias = 'example-workspace' # This needs to be unique across all users as to not have conflicting URLs\n", "description = 'This workspace was created as an example.'\n", "user_id = GsUsersApi.get_my_guid()\n", "\n", "# Entitle everyone internal to view but only yourself to edit and change entitlements\n", "entitlements = Entitlements(view=(user_id,), edit=(user_id,), admin=(user_id,))\n", "\n", "components = [] # Empty list of components with some to be added below" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Add Components\n", "\n", "Workspaces currently support many components such as Promo (text), Plots, DataGrids, and Commentary. See all available components\n", "[here](https://developer.gs.com/p/docs/services/data/workspaces-markets/#components).\n", "\n", "To create a component, create the parameters object for the component such as PromoComponentParameters, fill out the required and optional fields.\n", "\n", "Then add the component using these parameters. Let's start with a simple Promo Component." ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "#### Add a Promo Component \n", "\n", "If you want to add simple text to a workspace, this can be done by adding a Promo Component, as seen below." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "promo_params = PromoComponentParameters(height=52, body='Your text here!', transparent=False)\n", "components.append(WorkspaceComponent(id_='promo-1', type_=ComponentType.promo, parameters=promo_params))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Create the Workspace\n", "\n", "Now you are ready to create your workspace. Let us put everything together." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "layout = 'r(c12($0))'\n", "parameters = WorkspaceParameters(layout=layout, components=components)\n", "workspace = Workspace(parameters=parameters, name=name, alias=alias, entitlements=entitlements, description=description)\n", "workspace = ws_api.create_workspace(workspace)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "The above snippet will create a workspace that is now viewable at the URL https://marquee.gs.com/s/markets/{alias}. \n", "Substitute {alias} with the alias you set. Remember to change the alias since the example one probably already exists.\n", "\n", "The layout string controls the layout of your components. In this case, a simple layout with a single component that spans a single row.\n", "Learn more about layouts [here](https://developer.gs.com/p/docs/services/data/workspaces-markets/#layouts). \n" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Updating the Workspace with Additional Components\n", "\n", "Now let us create a Workspace with some plots and a commentary component." ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "#### Add a plot \n", "\n", "Start by creating plots in PlotTool Pro [here](https://marquee.gs.com/s/plottool/new) \n", "\n", "After you have created your plot, grab the plot id from the browser.\n", "* For example, the id for [this plot](https://marquee.gs.com/s/plottool/CH5RJJ9APZMRQ7B7) is CHYYNR2YSD8W21GA\n", " \n", "You want all the underlying components that have entitlements to have the same entitlements as the Workspace, so all components are visible on the Workspace for the intended audience." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# Create the plot component parameters, setting the height to the desired height in pixels. Also, other configurations can be set such as hiding the legend.\n", "plot_params = PlotComponentParameters(height=300, hideLegend=False)\n", "plot_id = 'CHYYNR2YSD8W21GA' # example plot id\n", "\n", "# Add the plot component to the list of components\n", "components.append(WorkspaceComponent(id_=plot_id, type_=ComponentType.plot, parameters=plot_params))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "#### Add a commentary stream" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "channel_1 = 'EQUITIES MACRO'\n", "channel_2 = 'EQUITIES GS BASKETS'\n", "commentary_params = CommentaryComponentParameters(height=300, commentary_channels=(channel_1, channel_2))\n", "components.append(\n", " WorkspaceComponent(id_='streaming-commentary-1', type_=ComponentType.commentary, parameters=commentary_params)\n", ")" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Update the Workspace" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [ { "ename": "MqRequestError", "evalue": "context: POST https://marquee.web.gs.com/v1/workspaces/markets\nstatus: 400, message: \n\n\n\nRequest Body Validation Error\n\n\n\n

400 Bad Request - Request Body Validation Error

\n\r\n

Problem Description

\r\n

\r\n The request did not match any of the requests defined for this endpoint.\r\n

\r\n

Details

\r\n\r\n

Request Definition 1

\r\n

The JSON object in the body does not conform to the schema

\r\n
    \r\n \r\n
  • error: ECMA 262 regex "^(?:\\w{1,256}:){0,10}[\\w.]{1,256}$$" does not match input string "insert your user id"\n level: "error"\n schema: {"loadingURI":"definition:/Token#","pointer":""}\n instance: {"pointer":"/entitlements/admin/0"}\n domain: "validation"\n keyword: "pattern"\n regex: "^(?:\\\\w{1,256}:){0,10}[\\\\w.]{1,256}$$"\n string: "insert your user id"\n
  • \r\n \r\n
\r\n\r\n

Troubleshooting

\r\n
    \r\n
  • Verify that the content type of the request matches the service definition.
  • \r\n
  • Check that the JSON document you are sending matches the schema.
  • \r\n
  • If the service does not define any requests, make sure you are not sending a request body.
  • \r\n
\r\n

Error ID: 7001873889772119441

Dash Services Framework 62.3 [SVN build 8011 at 2021-01-22 16:07]

\n\n\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\n", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mMqRequestError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[0mparameters\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mWorkspaceParameters\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mlayout\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mlayout\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mcomponents\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mcomponents\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mdisclaimer\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mdisclaimer\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[0mnew_workspace\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mWorkspace\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mparameters\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;34m'cashboard'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mname\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mname\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0malias\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0malias\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mentitlements\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mentitle\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mdescription\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mdescription\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 5\u001b[1;33m \u001b[0mcreated_workspace\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mwk\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcreate_workspace\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mnew_workspace\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 6\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;32mH:\\_repos\\gs_quant\\gs_quant\\api\\gs\\workspaces.py\u001b[0m in \u001b[0;36mcreate_workspace\u001b[1;34m(cls, workspace)\u001b[0m\n\u001b[0;32m 49\u001b[0m \u001b[1;33m@\u001b[0m\u001b[0mclassmethod\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 50\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mcreate_workspace\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mcls\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mworkspace\u001b[0m\u001b[1;33m:\u001b[0m \u001b[0mWorkspace\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;33m->\u001b[0m \u001b[0mWorkspace\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 51\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mGsSession\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcurrent\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_post\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34mf'{API}'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mworkspace\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mcls\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mWorkspace\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mrequest_headers\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mWORKSPACES_MARKETS_HEADERS\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 52\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 53\u001b[0m \u001b[1;33m@\u001b[0m\u001b[0mclassmethod\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;32mH:\\_repos\\gs_quant\\gs_quant\\session.py\u001b[0m in \u001b[0;36m_post\u001b[1;34m(self, path, payload, request_headers, cls, include_version, timeout)\u001b[0m\n\u001b[0;32m 230\u001b[0m include_version: bool = True, timeout: int = DEFAULT_TIMEOUT) -> Union[Base, tuple, dict]:\n\u001b[0;32m 231\u001b[0m return self.__request('POST', path, payload=payload, request_headers=request_headers,\n\u001b[1;32m--> 232\u001b[1;33m cls=cls, include_version=include_version, timeout=timeout)\n\u001b[0m\u001b[0;32m 233\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 234\u001b[0m def _delete(self, path: str, payload: Optional[Union[dict, Base]] = None, request_headers: Optional[dict]\n", "\u001b[1;32mH:\\_repos\\gs_quant\\gs_quant\\session.py\u001b[0m in \u001b[0;36m__request\u001b[1;34m(self, method, path, payload, request_headers, cls, try_auth, include_version, timeout)\u001b[0m\n\u001b[0;32m 195\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m__request\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mmethod\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mpath\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mpayload\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mpayload\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mcls\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mcls\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mtry_auth\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mFalse\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 196\u001b[0m \u001b[1;32melif\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[1;36m199\u001b[0m \u001b[1;33m<\u001b[0m \u001b[0mresponse\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mstatus_code\u001b[0m \u001b[1;33m<\u001b[0m \u001b[1;36m300\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 197\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mMqRequestError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mresponse\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mstatus_code\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mresponse\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mtext\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mcontext\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;34m'{} {}'\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mformat\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mmethod\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0murl\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 198\u001b[0m \u001b[1;32melif\u001b[0m \u001b[1;34m'Content-Type'\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mresponse\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mheaders\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 199\u001b[0m \u001b[1;32mif\u001b[0m \u001b[1;34m'application/x-msgpack'\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mresponse\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mheaders\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m'Content-Type'\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mMqRequestError\u001b[0m: context: POST https://marquee.web.gs.com/v1/workspaces/markets\nstatus: 400, message: \n\n\n\nRequest Body Validation Error\n\n\n\n

400 Bad Request - Request Body Validation Error

\n\r\n

Problem Description

\r\n

\r\n The request did not match any of the requests defined for this endpoint.\r\n

\r\n

Details

\r\n\r\n

Request Definition 1

\r\n

The JSON object in the body does not conform to the schema

\r\n
    \r\n \r\n
  • error: ECMA 262 regex "^(?:\\w{1,256}:){0,10}[\\w.]{1,256}$$" does not match input string "insert your user id"\n level: "error"\n schema: {"loadingURI":"definition:/Token#","pointer":""}\n instance: {"pointer":"/entitlements/admin/0"}\n domain: "validation"\n keyword: "pattern"\n regex: "^(?:\\\\w{1,256}:){0,10}[\\\\w.]{1,256}$$"\n string: "insert your user id"\n
  • \r\n \r\n
\r\n\r\n

Troubleshooting

\r\n
    \r\n
  • Verify that the content type of the request matches the service definition.
  • \r\n
  • Check that the JSON document you are sending matches the schema.
  • \r\n
  • If the service does not define any requests, make sure you are not sending a request body.
  • \r\n
\r\n

Error ID: 7001873889772119441

Dash Services Framework 62.3 [SVN build 8011 at 2021-01-22 16:07]

\n\n\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\n" ] } ], "source": [ "workspace.parameters.layout = 'r(c12($0))r(c6($1)c6($2))'\n", "workspace.parameters.components = components\n", "workspace = ws_api.update_workspace(workspace)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/00_data/03_visualizations/examples/0001_creating_a_visualization.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Introduction to Data Visualizations\n", "\n", "The Data Visualizations class `PlotlyViz` allows you to create and visualize different chart objects in Marquee.\n", "This class has been created to provide flexibility to our users when creating and visualizing charts. All while \n", "maintaining Marquee style and utilizing the proper Marquee workflow to persist and entitle these objects.\n", "\n", "At the moment we support 5 different chart types:\n", "\n", " * Bar Charts\n", " * Scatter Charts\n", " * Bubble Charts\n", " * Pie Charts\n", " * Radar Charts\n", " \n", "Let's take a look at a few example and concepts to better understand how to work and create these visualizations.\n", "\n", "First, let's import all necessary libraries:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "from gs_quant.analytics.datagrid import DataRow, DataColumn, DataGrid\n", "from gs_quant.analytics.dataviz import PlotlyViz\n", "from gs_quant.analytics.processors import EntityProcessor, VolatilityProcessor\n", "from gs_quant.analytics.processors.utility_processors import LastProcessor, DivisionProcessor\n", "from gs_quant.api.gs.users import GsUsersApi\n", "from gs_quant.data import DataCoordinate, DataMeasure, DataFrequency, DataDimension\n", "from gs_quant.datetime.relative_date import RelativeDate\n", "from gs_quant.entities.entitlements import Entitlements, EntitlementBlock, User\n", "from gs_quant.markets.securities import Asset, AssetIdentifier" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, "source": [ "Next, let's create our Datagrid, all the visualization example we will be using the following [Datagrid](https://developer.gs.com/docs/gsquant/data/data-analytics/datagrid/overview/) \n", "as data source:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "h:\\virtual\\gs-quant\\lib\\site-packages\\urllib3\\connectionpool.py:1020: InsecureRequestWarning: Unverified HTTPS request is being made to host 'marquee.web.gs.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings\n InsecureRequestWarning,\n", "h:\\virtual\\gs-quant\\lib\\site-packages\\urllib3\\connectionpool.py:1020: InsecureRequestWarning: Unverified HTTPS request is being made to host 'marquee.web.gs.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings\n InsecureRequestWarning,\n", "h:\\virtual\\gs-quant\\lib\\site-packages\\urllib3\\connectionpool.py:1020: InsecureRequestWarning: Unverified HTTPS request is being made to host 'marquee.web.gs.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings\n InsecureRequestWarning,\nh:\\virtual\\gs-quant\\lib\\site-packages\\urllib3\\connectionpool.py:1020: InsecureRequestWarning: Unverified HTTPS request is being made to host 'marquee.web.gs.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings\n InsecureRequestWarning,\nh:\\virtual\\gs-quant\\lib\\site-packages\\urllib3\\connectionpool.py:1020: InsecureRequestWarning: Unverified HTTPS request is being made to host 'marquee.web.gs.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings\n InsecureRequestWarning,\n", "h:\\virtual\\gs-quant\\lib\\site-packages\\urllib3\\connectionpool.py:1020: InsecureRequestWarning: Unverified HTTPS request is being made to host 'marquee.web.gs.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings\n InsecureRequestWarning,\nh:\\virtual\\gs-quant\\lib\\site-packages\\urllib3\\connectionpool.py:1020: InsecureRequestWarning: Unverified HTTPS request is being made to host 'marquee.web.gs.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings\n InsecureRequestWarning,\nh:\\virtual\\gs-quant\\lib\\site-packages\\urllib3\\connectionpool.py:1020: InsecureRequestWarning: Unverified HTTPS request is being made to host 'marquee.web.gs.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings\n InsecureRequestWarning,\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\nDatagrid is currently experimental. The API is likely to change. For questions contact gs-marquee-markets@gs.com\n\n" ] }, { "data": { "text/html": "
\n\n\n \n \n \n \n \n \n \n
\n
", "text/plain": "Empty DataFrame\nColumns: []\nIndex: []" }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Fetching assets to work with\n", "SPX = Asset.get(\"MA4B66MW5E27U8P32SB\", AssetIdentifier.MARQUEE_ID)\n", "SX5E = Asset.get(\"MA4B66MW5E27U8P32SY\", AssetIdentifier.MARQUEE_ID)\n", "UKX = Asset.get(\"MA4B66MW5E27U8NN95P\", AssetIdentifier.MARQUEE_ID)\n", "HSCEI = Asset.get(\"MA4B66MW5E27U8P3294\", AssetIdentifier.MARQUEE_ID)\n", "NKY = Asset.get(\"MA4B66MW5E27U8P32LH\", AssetIdentifier.MARQUEE_ID)\n", "KS200 = Asset.get(\"MA4B66MW5E27U8P32DM\", AssetIdentifier.MARQUEE_ID)\n", "IBOV = Asset.get(\"MA4B66MW5E27U8NN8N6\", AssetIdentifier.MARQUEE_ID)\n", "\n", "# Defining Datagrid rows\n", "rows = [DataRow(SPX), DataRow(SX5E), DataRow(UKX), DataRow(HSCEI), DataRow(NKY), DataRow(KS200), DataRow(IBOV)]\n", "\n", "# Defining column operations\n", "spx_spot = DataCoordinate(measure=DataMeasure.CLOSE_PRICE, frequency=DataFrequency.DAILY)\n", "\n", "implied_vol = DataCoordinate(\n", " measure=DataMeasure.IMPLIED_VOLATILITY,\n", " frequency=DataFrequency.DAILY,\n", " dimensions={DataDimension.TENOR: \"3m\", DataDimension.STRIKE_REFERENCE: \"delta\", DataDimension.RELATIVE_STRIKE: 0.5},\n", ")\n", "\n", "implied_spot = LastProcessor(implied_vol)\n", "realized_vol = LastProcessor(\n", " DivisionProcessor(VolatilityProcessor(spx_spot, w=63, start=RelativeDate(\"-1y\")), dividend=100)\n", ")\n", "\n", "col_0 = DataColumn(name=\"Name\", processor=EntityProcessor(field=\"short_name\"))\n", "col_1 = DataColumn(name=\"Implied Volatility\", processor=implied_spot)\n", "col_2 = DataColumn(name=\"Realized Vol\", processor=realized_vol)\n", "\n", "columns = [col_0, col_1, col_2]\n", "\n", "# Entitlements\n", "# Entitle everyone internal to view but only yourself to edit and change entitlements\n", "user_id = GsUsersApi.get_my_guid()\n", "self_entitlement_block = EntitlementBlock(users=[User.get(user_id=user_id)])\n", "entitlements = Entitlements(view=self_entitlement_block, edit=self_entitlement_block, admin=self_entitlement_block)\n", "\n", "# Datagrid creation\n", "datagrid = DataGrid(name=\"Datagrid For Visualization\", rows=rows, columns=columns, entitlements=entitlements)\n", "\n", "datagrid_id = datagrid.save()\n", "datagrid.initialize()\n", "datagrid.poll()\n", "datagrid.to_frame()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, "source": [ "Once we have our Datagrid we can use it as our data source to create the visualizations. Let's start with a Bar Chart\n", "example:\n", "\n", "### Bar Charts\n", "\n", "To create a bar chart, simply pass in your datagrid and entitlements to the `PlotlyViz` class to initialize the class." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "visualization = PlotlyViz(datagrid=datagrid, entitlements=entitlements)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, "source": [ "Then, call the bar function, passing the parameters that you require for your visualization. In this case we're simply\n", "passing x, y and title. We're passing the column names from our Datagrid to our `x` and `y` parameters as well as a \n", "visualization title:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "visualization.bar(x='Name', y='Realized Vol', title=\"Realized Vol Bar Chart Example\")" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, "source": [ "*Note: All parameters used in our visualization functions are the same parameters you'd use in [Plotly Express](https://plotly.com/python/plotly-express/).\n", "\n", "Next, we can persist our chart and visualize it in Marquee like so:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "viz_id = visualization.save()\n", "# The open() method does not work in Jupyter, but it works fine from your own IDE.\n", "visualization.open()\n", "print(f'Your data visualization should be viewable at: https://marquee.gs.com/s/markets/visualizations/{viz_id}')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, "source": [ "### Scatter Charts\n", "\n", "Similarly, we can create scatter charts like so:\n", "\n", "In our previous Bar chart example, we passed the Datagrid variable directly to `PlotlyViz`. Now, since that Datagrid\n", "is already created, let's simply use it's ID as reference:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "visualization = PlotlyViz(datagrid_id=datagrid_id, entitlements=entitlements)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, "source": [ "Next, use the scatter function and pass the required variables. After that, you should be able to save and open it to\n", "visualize in Marquee:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "visualization.scatter(x='Implied Volatility', y='Realized Vol', color=\"Name\", title=\"Scatter Chart Example\")\n", "viz_id = visualization.save()\n", "# The open() method does not work in Jupyter, but it works fine from your own IDE.\n", "visualization.open()\n", "print(f'Your data visualization should be viewable at: https://marquee.gs.com/s/markets/visualizations/{viz_id}')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Bubble Charts\n", "\n", "Bubble charts are actually scatter charts but with a third variable that specifies the dot size. Here's an example:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "visualization = PlotlyViz(datagrid_id=datagrid_id, entitlements=entitlements)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "Now, call the scatter function with the required variables. After that, you should be able to save and open it to\n", "visualize in Marquee:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Using Realized Vol as the column to use for dot size\n", "visualization.scatter(\n", " x='Implied Volatility', y='Realized Vol', size='Realized Vol', color=\"Name\", title=\"Bubble Chart Example\"\n", ")\n", "viz_id = visualization.save()\n", "# The open() method does not work in Jupyter, but it works fine from your own IDE.\n", "visualization.open()\n", "print(f'Your data visualization should be viewable at: https://marquee.gs.com/s/markets/visualizations/{viz_id}')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Radar Charts\n", "\n", "A useful chart in different scenarios is the Radar Chart. This chart can be created like so:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "visualization = PlotlyViz(datagrid_id=datagrid_id, entitlements=entitlements)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "The function to call this time is called `line_polar`. Call this function with the required variables. After that, you should be able to save and open it to\n", "visualize in Marquee:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "visualization.line_polar(r='Implied Volatility', theta=\"Name\", line_close=True, title=\"Radar Chart\")\n", "viz_id = visualization.save()\n", "# The open() method does not work in Jupyter, but it works fine from your own IDE.\n", "visualization.open()\n", "print(f'Your data visualization should be viewable at: https://marquee.gs.com/s/markets/visualizations/{viz_id}')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, "source": [ "Again, all the parameters used in these visualizations are the same that [Plotly Express](https://plotly.com/python/plotly-express/) uses,\n", "You can also see some useful examples on their site for the visualizations we support: [Bar](https://plotly.com/python/bar-charts/), \n", "[Scatter](https://plotly.com/python/line-and-scatter/), [Bubble](https://plotly.com/python/line-and-scatter/#bubble-scatter-plots), \n", "[Pie](https://plotly.com/python/pie-charts/), [Radar](https://plotly.com/python/radar-chart/)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/00_data/04_screens/examples/00_base_screener_api_tutorial.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "49804acd", "metadata": {}, "source": [ "# GS Quant Base Screener Tutorial" ] }, { "cell_type": "markdown", "id": "614cf75b", "metadata": {}, "source": [ "Base screeners are tools used to explore and compare information about specific assets. These screeners act as permanent data tables containing rows of information about different assets and indices. Each base screener is recomputed once a day, and `gs_quant` screens act as persistent filters for screeners. Each base screener has two main components: the screener schema, which determines what data is permitted to be in the screener, and the screener data, which can be published to the screener after the schema is fixed. \n", "\n", "New features in the `gs_quant` library now allow users with the requisite permissions to create and manipulate screeners using only library functions. Below, we demonstrate how to use each library function and provide examples of common use cases. " ] }, { "cell_type": "markdown", "id": "1f91951a", "metadata": {}, "source": [ "### Imports\n", "\n", "First, we import the basic functions we need to begin manipulating base screeners in `gs_quant`. Additional imports are also used for easier visualization of screener data. \n", "\n", "\n", "To use any of the screener functions in `GsBaseScreenerApi`, users must have an active `GsSession`. Users must also be members of the `PlotScreenerAdmins` group in `gs_quant` in order to use functions such as `create_screener()`, `edit_screener()`, `clear_screener()`, `delete_screener()`, and `publish_to_screener()`. " ] }, { "cell_type": "code", "execution_count": null, "id": "8d926374", "metadata": {}, "outputs": [], "source": [ "# Required imports\n", "from gs_quant.session import GsSession, Environment # Authenticate user\n", "from gs_quant.api.gs.base_screener import GsBaseScreenerApi # Base screener functions\n", "from gs_quant.target.base_screener import (\n", " Screener,\n", " ScreenerParameters,\n", " ScreenerRow,\n", " ScreenerColumn,\n", ") # Screener data classes\n", "from gs_quant.target.common import FilterRequest\n", "\n", "# Visualization imports\n", "import pandas as pd\n", "from gs_quant.api.gs.data_screen import GsDataScreenApi\n", "from gs_quant.target.data_screen import AnalyticsScreen\n", "from time import sleep\n", "\n", "GsSession.use(Environment.PROD) # Initialize GsSession" ] }, { "cell_type": "markdown", "id": "d1838f1c", "metadata": {}, "source": [ "## Overview" ] }, { "cell_type": "markdown", "id": "20958ee6", "metadata": {}, "source": [ "Below is a brief overview of a typical use case: getting a screener, creating a new screener, editing a schema, publishing data to a screener, viewing screener data, clearing a screener, and deleting a screener. For more detail on these actions, continue to the sections below." ] }, { "cell_type": "code", "execution_count": null, "id": "5f3a10dc", "metadata": {}, "outputs": [], "source": [ "existing_schema = GsBaseScreenerApi.get_screener('BSW3GT9DS8A86MTSH3')\n", "\n", "\n", "existing_schema.name = 'Tutorial Overview Screener'\n", "\n", "new_screener = GsBaseScreenerApi.create_screener(existing_schema)\n", "\n", "\n", "new_col = ScreenerColumn(column_name='1m IV', expression='ASSET.implied_volatility(1m, spot, 100)')\n", "\n", "new_screener.parameters.columns = new_screener.parameters.columns + (new_col,)\n", "\n", "\n", "data = {\n", " 'rows': [\n", " {'id': 0, 'Name': 'Obalon Therapeutics Inc', 'BBID': 'DSE', '1m IV': 0.15},\n", " {'id': 1, 'Name': 'Disney Inc', 'BBID': 'DIS', '1m IV': 0.16},\n", " ]\n", "}\n", "\n", "GsBaseScreenerApi.publish_to_screener(new_screener.id, data)\n", "\n", "sleep(1)\n", "\n", "\n", "screen_schema = AnalyticsScreen(\n", " name='Overview Screener View', filter_parameters=FilterRequest(), base_screener=new_screener.id\n", ")\n", "\n", "screen = GsDataScreenApi.create_screen(screen_schema)\n", "\n", "display(pd.DataFrame(GsDataScreenApi.filter_screen(screen.id, screen.filter_parameters)))\n", "\n", "\n", "GsBaseScreenerApi.clear_screener(new_screener.id)\n", "\n", "sleep(1)\n", "\n", "display(pd.DataFrame(GsDataScreenApi.filter_screen(screen.id, screen.filter_parameters)))\n", "\n", "\n", "GsDataScreenApi.delete_screen(screen.id)\n", "\n", "GsBaseScreenerApi.delete_screener(new_screener.id)" ] }, { "cell_type": "markdown", "id": "27fefdea", "metadata": {}, "source": [ "## Viewing Existing Base Screeners" ] }, { "cell_type": "markdown", "id": "97804a6a", "metadata": {}, "source": [ "### Class `Screener`" ] }, { "cell_type": "markdown", "id": "9b0c719c", "metadata": {}, "source": [ "In `gs_quant`, a screener is represented by an object of class `Screener`. More explicitly, a `Screener` object describes the schema of an existing base screener. These objects are often returned when retrieving a screener using its ID or creating a new screener with a specific schema. Some notable fields of this type include: \n", "\n", "\n", "- **`name`** *(str, Required)*: The name of the base screener.\n", "\n", "\n", "- **`parameters`** *(ScreenerParameters, Required)*: An object of type ScreenerParameters that specifies the exact rows (ex. assets or indices) and columns (attributes) that should be visible in this screener. Only data conforming to the ScreenerParameters object of the screener can be published to the screener. This `ScreenerParameters` object also determines how values in the screener are updated over time. \n", "\n", "\n", "- **`id`** *(str, Optional)*: An ID that uniquely identifies this screener, which is permanent. As long as this screener is not deleted, this ID will reference the same screener. This ID can be used to retrieve screener information and data from permanent storage. \n", "\n", "\n", "- **`cron_schedule`** *(str, Required)*: A cron schedule expression to schedule a cron job using this screener. Currently, this feature is not implemented; however, the functions using `Screener` objects still expect a valid cron schedule expression in this location." ] }, { "cell_type": "markdown", "id": "1b4ecf8e", "metadata": {}, "source": [ "### Function `get_screeners()`" ] }, { "cell_type": "markdown", "id": "e38bfcc4", "metadata": {}, "source": [ "When new screeners are created, they are stored persistently. Function `get_screeners()` returns a list of all base screeners available to the user. Each screener or entry in this tuple is a `Screener` object that can be used to retrieve the screener's data, manipulate the screener, or create a new screener. " ] }, { "cell_type": "code", "execution_count": null, "id": "fe113420", "metadata": {}, "outputs": [], "source": [ "# Usage Example: get_screeners()\n", "\n", "all_screeners = GsBaseScreenerApi.get_screeners()\n", "print(all_screeners)\n", "\n", "screener = all_screeners[0]\n", "\n", "print(\n", " '\\nBase Screener Name: %s, Base Screener Column 1: %s, Base Screener ID: %s'\n", " % (screener.name, screener.parameters.columns[0].column_name, screener.id)\n", ")" ] }, { "cell_type": "markdown", "id": "29e18ed4", "metadata": {}, "source": [ "### Function `get_screener()`" ] }, { "cell_type": "markdown", "id": "9ab25b7b", "metadata": {}, "source": [ "Use function `get_screener(screener_id)`to retrieve the `Screener` object for a specific screener using its ID. This function allows a user to search for a specific screener to view its schema and information. " ] }, { "cell_type": "code", "execution_count": null, "id": "b1dbe4fa", "metadata": {}, "outputs": [], "source": [ "# Usage Example: get_screener()\n", "\n", "screener = GsBaseScreenerApi.get_screener('BSW3GT9DS8A86MTSH3')\n", "\n", "print(screener)\n", "\n", "print(\n", " '\\nBase Screener Name: %s, Base Screener Column 1: %s, Base Screener ID: %s'\n", " % (screener.name, screener.parameters.columns[0].column_name, screener.id)\n", ")" ] }, { "cell_type": "markdown", "id": "289b201a", "metadata": {}, "source": [ "## Creating and Deleting Base Screeners " ] }, { "cell_type": "markdown", "id": "22db9ccd", "metadata": {}, "source": [ "### Class `ScreenerRow`" ] }, { "cell_type": "markdown", "id": "b0129a76", "metadata": {}, "source": [ "Class `ScreenerRow` defines a row or set of rows (i.e. assets or indices) that can be viewed in this screener. Note that this class does not publish data to a screener. Rather, `ScreenerRow` objects contain information about possible rows that the base screener's schema should accept. Tuples of `ScreenerRow` objects are added to the `rows` field of `ScreenerParameter` instances to define the rowwise schema of a screener; it is up to the user to then publish corresponding data to the screener in order to make this data visible. Notable fields of this type include: \n", "\n", "\n", "- **`entity_id`**, *(str, Required)*: The unique ID of the entity that this `ScreenerRow` adds to the rowwise schema.\n", "\n", "\n", "- **`entity_type`**, *(str, Required)*: The type of the entity specified in this `ScreenerRow` object. Currently, this field is not in use and can just be set to the string `'asset'`.\n", "\n", "\n", "- **`expand`**, *(bool, Optional)*: In the event that the asset is a positioned entity, such as an index or a mutual fund, a user can opt to enter all assets contained in this entity as their own rows in the schema by setting the `expand` field to be `True`. \n", "\n", "**Note:** Currently, although users specify individual assets for each row in the schema, these specifications are not strictly enforced. The screener will not reject data that is not one of the permitted assets specified in the schema, but it is best practice to create a schema that matches the intended data closely." ] }, { "cell_type": "markdown", "id": "e2a8f007", "metadata": {}, "source": [ "### Class `ScreenerColumn`" ] }, { "cell_type": "markdown", "id": "4582c75d", "metadata": {}, "source": [ "Class `ScreenerColumn` defines a single column to be added to a base screener's schema. Similarly to `ScreenerRow`, this class does not publish data to a screener. Rather, `ScreenerColumn` objects contain information about possible columns that the base screener's schema should accept. Tuples of `ScreenerColumn` objects are added to the `columns` field of `ScreenerParameter` instances to define the columnwise schema of a screener; it is up to the user to then publish corresponding data to the screener in order to make this data visible. Notable fields of this type include: \n", "\n", "\n", "- **`column_name`** *(str, Required)*: The name of the column to add to the base screener's schema.\n", "\n", "\n", "- **`entity_parameter`** *(str, Optional)*: The entity parameter name corresponding to the values in this column, if applicable. Specifying this field allows the screener to be automatically updated once per day with the correct value of this column for each asset. Possible `entity_parameter` values include `id`, `bbid`, `region`, `assetClass`, `currency`, `description`, `exchange`, `name`, `type`, `ric`, `region`, `assetClassificationsCountryName`, `assetClassificationsGicsSector`, and `assetClassificationGicsIndustry`.\n", "\n", "\n", "- **`expression`** *(str, Optional)*: The PlotTool expression to calculate this column, if applicable. For calculated columns, providing a PlotTool expression allows the screener to update once per day with the most recent value of this expression. No specific asset should be provided with this expression, as it will be applied individually to each asset in each row of the screener's data. Use the placeholder `ASSET` for an asset name in the expression where needed (ex. `\"ASSET.implied_volatility(1m, delta_call, 50)\"`).\n", "\n", "\n", "**Note**: Although both the `entity_parameter` and `expression` fields are optional, each individual column must fill out exactly one of these fields. Currently, base screeners do not support entering custom asset values not tied to expressions or entity information. " ] }, { "cell_type": "markdown", "id": "29186dd9", "metadata": {}, "source": [ "### Class `ScreenerParameters`" ] }, { "cell_type": "markdown", "id": "34dbfe0c", "metadata": {}, "source": [ "Class `ScreenerParameters` gathers and defines an entire schema for a base_screener. This schema object is then assigned to the `parameters` field in a `Screener` object when creating a new screen with this specific schema. Notable fields of this type include: \n", "\n", "\n", "- **`name`** *(str, Optional)*: The name of this schema. \n", "\n", "\n", "- **`rows`** *(ScreenerRow tuple, Required)*: A tuple of `ScreenerRow` objects that define each allowed data row in the schema.\n", "\n", "\n", "- **`columns`** *(ScreenerColumn tuple, Required)*: A tuple of `ScreenerColumn` objects that define the allowed columns in the schema." ] }, { "cell_type": "markdown", "id": "188c1e26", "metadata": {}, "source": [ "### Function `create_screener()`" ] }, { "cell_type": "markdown", "id": "743b9be8", "metadata": {}, "source": [ "To create a new base screener, pass a `Screener` object into the function `create_screener(screener)`. The `Screener` object should have the correct specifications for the new base screener, including a `parameters` field with a `ScreenParameters` object populated with the correct schema. The `id` field of the input `Screener` object can be left blank, as this function will return an identical `Screener` object to the input, except it will have its `id` field populated with the ID for the new base screener. \n", "\n", "The returning of the identical `Screener` objects confirms that the new base screener has the intended schema, which is also visible by using the `get_screener()` function with the new screener ID.\n", "\n", "**Note:** Only users who are members of the `PlotScreenerAdmins` group can use the `create_screener()` function to make new screeners." ] }, { "cell_type": "markdown", "id": "1cb13147", "metadata": {}, "source": [ "### Function `delete_screener()`" ] }, { "cell_type": "markdown", "id": "5684a951", "metadata": {}, "source": [ "Pass a screener's unique ID into the function `delete_screener(screener_id)` to permanently delete a base screener and all of its associated data. The screener will no longer be accessible to any user after this action. \n", "\n", "**Note:** Only users who are members of the `PlotScreenerAdmins` group can use the `delete_screener()` function to remove screeners." ] }, { "cell_type": "code", "execution_count": null, "id": "63078318", "metadata": {}, "outputs": [], "source": [ "# Usage Example: create_screener(), delete_screener()\n", "\n", "# Create a Schema\n", "\n", "rows = (\n", " ScreenerRow(entity_id='MAYDMZQXXVXD9JK7', entity_type='asset'),\n", " ScreenerRow(entity_id='MAQJQTVRMKVKX9FZ', entity_type='asset'),\n", ")\n", "\n", "columns = (\n", " ScreenerColumn(column_name='Name', entity_parameter='name'),\n", " ScreenerColumn(column_name='BBID', entity_parameter='bbid'),\n", " ScreenerColumn(column_name='RSI 14 DAY', expression='relative_strength_index(ASSET.spot(), 14d)'),\n", " ScreenerColumn(column_name='Skew', expression='ASSET.skew(1m, delta, 25, normalized)'),\n", ")\n", "\n", "params = ScreenerParameters(rows=rows, columns=columns)\n", "\n", "cron_schedule = '5 4 * * *'\n", "\n", "schema = Screener(name='My New Screener', parameters=params, cron_schedule=cron_schedule)\n", "\n", "# Create a New Screener\n", "\n", "new_screener = GsBaseScreenerApi.create_screener(schema)\n", "\n", "print('Screener Name: %s, Screener ID: %s\\n' % (new_screener.name, new_screener.id))\n", "\n", "# Delete Screener\n", "\n", "GsBaseScreenerApi.delete_screener(new_screener.id)" ] }, { "cell_type": "markdown", "id": "142d742b", "metadata": {}, "source": [ "Additionally, we can use existing `Screener` objects and function `create_screener(screener)` to create new screeners by copying or modifying existing screeners. " ] }, { "cell_type": "code", "execution_count": null, "id": "5b36b194", "metadata": {}, "outputs": [], "source": [ "# Example: Create a New Screener from an Existing Schema and Delete It\n", "\n", "# Retrieve Existing Screener\n", "\n", "screener = GsBaseScreenerApi.get_screener('BSW3GT9DS8A86MTSH3')\n", "\n", "print(\n", " 'Screener ID: %s, Screener Columns: %s\\n'\n", " % (screener.id, [column.column_name for column in screener.parameters.columns])\n", ")\n", "\n", "# Modify Schema\n", "\n", "screener.parameters.columns = (screener.parameters.columns[0],)\n", "\n", "# Create a New Screener\n", "\n", "new_screener = GsBaseScreenerApi.create_screener(screener)\n", "\n", "print(\n", " 'New Screener ID: %s, New Screener Columns: %s'\n", " % (new_screener.id, [column.column_name for column in new_screener.parameters.columns])\n", ")\n", "\n", "# Delete Screener\n", "\n", "GsBaseScreenerApi.delete_screener(new_screener.id)" ] }, { "cell_type": "markdown", "id": "18404d92", "metadata": {}, "source": [ "## Viewing Base Screener Data\n", "\n", "\n", "To view data from a base screener, please create a `gs_quant` screen that sources data from this base screener and uses any desired filters. More information on how to create a screen can be found in the `gs_quant` screens tutorial. \n", "\n", "**Note**: Please wait a short amount of time between alternating operations of editing the base screener and viewing its data to allow time for the base screener to update. Using the python `sleep(seconds)` function in the `time` module between operations is a simple way to ensure consistency when viewing recently edited data from a base screener. Between nearby operations, `sleep(1)` should be sufficient." ] }, { "cell_type": "markdown", "id": "d87d18ef", "metadata": {}, "source": [ "## Publishing Data to Base Screeners and Clearing Screeners" ] }, { "cell_type": "markdown", "id": "bca147f8", "metadata": {}, "source": [ "### Function `clear_screener()`" ] }, { "cell_type": "markdown", "id": "de202b51", "metadata": {}, "source": [ "To clear a base screener of its data, use the function `clear_screener(screener_id)` with the screener ID of the base screener being cleared. The base screener schema will still be permanently stored and accessible with the same ID, but the screener will be empty." ] }, { "cell_type": "markdown", "id": "d65fae34", "metadata": {}, "source": [ "### Data Publishing Format" ] }, { "cell_type": "markdown", "id": "5871eff3", "metadata": {}, "source": [ "After a base screener is created using a schema, data following this schema can be added to the screener. \n", "\n", "\n", "`gs_quant`'s publishing function accepts data as a dictionary containing a single list of dictionaries. Each dictionary in the list identifies a single row (or asset) of data. The keys of the dictionary must match the names of columns in the schema, and the value corresponding to a specific key will be the value entered at this column for a particular asset. Including additional keys within data rows will cause an error, and no data will be published to the screener. Including fewer keys within data rows will cause unspecified columns to have an empty value for this asset. This complete list of dictionaries is then inserted as the value of the `'rows'` key in an outer dictionary. \n", "\n", "\n", "In addition to column name-value pairs within row dictionaries, users can also optionally include a unique `'id'` key and value in each row of data. This `'id'` key allows users to overwrite specific rows with new data after earlier data has already been published to the screener. If no `'id'` key is specified when data is first published, each row will automatically be given a unique ID that can be retrieved when the screener's data is viewed. If a data row is re-published with the same `'id'` value, this data row will be overwritten in favor of newer data. If a data row is re-published without specifying an ID, a new data row will be appended to the screener regardless of whether or not this data already exists in the base screener. \n", "\n", "\n", "Below is an example of a correctly formatted data dictionary. " ] }, { "cell_type": "code", "execution_count": null, "id": "94543cdd", "metadata": {}, "outputs": [], "source": [ "# Example: Data Format\n", "\n", "{\n", " 'rows': [\n", " {\n", " 'id': 0, # Specifies a unique ID\n", " 'Name': 'Obalon Therapeutics Inc',\n", " 'Entity ID': 'ABDFDSCD',\n", " 'BBID': 'DSE',\n", " 'Impliedretailbuynotional': 4.0,\n", " 'Notional': 450.3,\n", " 'Implied / realized': 14.2,\n", " },\n", " {\n", " 'Name': 'Disney Inc', # Does not specify a unique ID\n", " 'Entity ID': 'ABDFDSCe',\n", " 'BBID': 'DIS',\n", " 'Impliedretailbuynotional': 4.0,\n", " 'Notional': 45.0,\n", " 'Implied / realized': 14.0,\n", " },\n", " ]\n", "}" ] }, { "cell_type": "markdown", "id": "02a0004d", "metadata": {}, "source": [ "### Function `publish_to_screener()`" ] }, { "cell_type": "markdown", "id": "56c9df1c", "metadata": {}, "source": [ "Use function `publish_to_screener(screener_id, data)` to publish the specified data to the base screener with the corresponding screener ID. This function returns a copy of the data published, along with additional storage information. \n", "\n", "Any data that includes an `'id'` field will overwrite any existing row with the same `'id'` value or add a new row with this ID if none exists. Any data that is provided without a specified `'id'` value will be appended to any existing screener data. Publishing to a screener mulitple times will not erase any previously published data unless it is overwritten through the use of row IDs. \n", "\n", "**Note:** Only users who are members of the `PlotScreenerAdmins` group can use the `publish_to_screener()` function to add data to existing screeners." ] }, { "cell_type": "code", "execution_count": null, "id": "7bf59ad5", "metadata": {}, "outputs": [], "source": [ "# Usage Example: publish_to_screener(), clear_screener()\n", "\n", "# Create Screener\n", "\n", "rows = (ScreenerRow(entity_id='MAYDMZQXXVXD9JK7', entity_type='asset'),)\n", "\n", "columns = (ScreenerColumn(column_name='Name', entity_parameter='name'),)\n", "\n", "params = ScreenerParameters(rows=rows, columns=columns)\n", "\n", "cron_schedule = '5 4 * * *'\n", "\n", "schema = Screener(name='My New Screener', parameters=params, cron_schedule=cron_schedule)\n", "\n", "screener = GsBaseScreenerApi.create_screener(schema)\n", "\n", "# Publish data to screener\n", "\n", "data = {\n", " 'rows': [\n", " {\n", " 'id': 0, # Specifies a unique ID\n", " 'Name': 'Obalon Therapeutics Inc',\n", " }\n", " ]\n", "}\n", "\n", "GsBaseScreenerApi.publish_to_screener(screener.id, data)\n", "\n", "sleep(1) # Sleep to Ensure Viewing Consistency\n", "\n", "# Create an Empty Screen to View Screener Data\n", "\n", "screen_schema = AnalyticsScreen(name='My View', filter_parameters=FilterRequest(), base_screener=screener.id)\n", "\n", "screen = GsDataScreenApi.create_screen(screen_schema)\n", "\n", "view_data = GsDataScreenApi.filter_screen(screen.id, screen.filter_parameters)\n", "\n", "print('Screener %s with Initial Data:\\n' % (screener.id))\n", "\n", "display(pd.DataFrame(view_data))\n", "\n", "# Re-Publish to a Screener\n", "\n", "new_data = {\n", " 'rows': [\n", " {\n", " 'id': 0, # Overwrite this Row\n", " 'Name': 'MY NEW NAME',\n", " },\n", " {'Name': 'Disney Inc'},\n", " ]\n", "}\n", "\n", "GsBaseScreenerApi.publish_to_screener(screener.id, new_data)\n", "\n", "sleep(1)\n", "\n", "# View Screener Data\n", "\n", "view_data = GsDataScreenApi.filter_screen(screen.id, screen.filter_parameters)\n", "\n", "print('\\nScreener %s with Re-Published Data:\\n' % (screener.id))\n", "\n", "display(pd.DataFrame(view_data))\n", "\n", "# Clear Screener\n", "\n", "GsBaseScreenerApi.clear_screener(screener.id)\n", "\n", "sleep(1)\n", "\n", "view_data = GsDataScreenApi.filter_screen(screen.id, screen.filter_parameters)\n", "\n", "print('\\nCleared Screener %s Data:\\n' % (screener.id))\n", "\n", "display(pd.DataFrame(view_data))\n", "\n", "# Delete Screen and Screener\n", "\n", "GsDataScreenApi.delete_screen(screen.id)\n", "\n", "GsBaseScreenerApi.delete_screener(screener.id)" ] }, { "cell_type": "markdown", "id": "81af2906", "metadata": {}, "source": [ "## Editing Base Screeners" ] }, { "cell_type": "markdown", "id": "1ca52c15", "metadata": {}, "source": [ "### Function `edit_screener()`" ] }, { "cell_type": "markdown", "id": "452b52a5", "metadata": {}, "source": [ "To modify the schema of a base screener, use the function `edit_screener(screener_id, screener)` with the screener ID of the screener to modify and a `Screener` object specifying the schema that this screener should now have. \n", "\n", "\n", "It is best practice to clear the screener of any existing data before making modifications to its schema. Then, users can re-publish their data to take full advantage of the new schema without needing to manually adjust individual rows. \n", "\n", "\n", "`edit_screener(screener_id, screener)` will throw an error if the screener ID provided does not match the ID field in the `Screener` object provided.\n", "\n", "\n", "**Note:** Only users who are members of the `PlotScreenerAdmins` group can use the `edit_screener()` function to modify existing screeners." ] }, { "cell_type": "code", "execution_count": null, "id": "739eedee", "metadata": {}, "outputs": [], "source": [ "# Usage Example: edit_screener()\n", "\n", "# Create Screener\n", "\n", "rows = (ScreenerRow(entity_id='MAYDMZQXXVXD9JK7', entity_type='asset'),)\n", "\n", "columns = (ScreenerColumn(column_name='Name', entity_parameter='name'),)\n", "\n", "params = ScreenerParameters(rows=rows, columns=columns)\n", "\n", "cron_schedule = '5 4 * * *'\n", "\n", "schema = Screener(name='My New Screener', parameters=params, cron_schedule=cron_schedule)\n", "\n", "screener = GsBaseScreenerApi.create_screener(schema)\n", "\n", "print('Screener ID: %s, Columns: %s\\n' % (screener.id, [col.column_name for col in screener.parameters.columns]))\n", "\n", "# Create Screen\n", "\n", "screen_schema = AnalyticsScreen(name='My View', filter_parameters=FilterRequest(), base_screener=screener.id)\n", "\n", "screen = GsDataScreenApi.create_screen(screen_schema)\n", "\n", "# Publish Data and View\n", "\n", "data = {\n", " 'rows': [\n", " {\n", " 'id': 0,\n", " 'Name': 'Obalon Therapeutics Inc',\n", " }\n", " ]\n", "}\n", "\n", "GsBaseScreenerApi.publish_to_screener(screener.id, data)\n", "\n", "sleep(1)\n", "\n", "view_data = GsDataScreenApi.filter_screen(screen.id, screen.filter_parameters)\n", "\n", "print('Screener %s Data:\\n' % (screener.id))\n", "\n", "display(pd.DataFrame(view_data))\n", "\n", "# Clear Data\n", "\n", "GsBaseScreenerApi.clear_screener(screener.id)\n", "\n", "# Edit Screener Schema (Columns)\n", "\n", "screener.parameters.columns = screener.parameters.columns + (\n", " ScreenerColumn(column_name='BBID', entity_parameter='bbid'),\n", ")\n", "\n", "updated_screener = GsBaseScreenerApi.edit_screener(screener.id, screener)\n", "\n", "# View Screener Columns\n", "\n", "print(\n", " '\\nScreener ID: %s, Columns: %s'\n", " % (updated_screener.id, [col.column_name for col in updated_screener.parameters.columns])\n", ")\n", "\n", "# Publish Data and View\n", "\n", "data = {'rows': [{'id': 0, 'Name': 'Obalon Therapeutics Inc', 'BBID': 'DSE'}]}\n", "\n", "GsBaseScreenerApi.publish_to_screener(screener.id, data)\n", "\n", "sleep(1)\n", "\n", "view_data = GsDataScreenApi.filter_screen(screen.id, screen.filter_parameters)\n", "\n", "print('\\nScreener %s Data:\\n' % (updated_screener.id))\n", "\n", "display(pd.DataFrame(view_data))\n", "\n", "# Delete Screen and Screener\n", "\n", "GsDataScreenApi.delete_screen(screen.id)\n", "\n", "GsBaseScreenerApi.delete_screener(updated_screener.id)" ] }, { "cell_type": "markdown", "id": "97f8519d", "metadata": {}, "source": [ "If an additional column is added to the schema without clearing the screener, all previously existing rows will have no value in the new column. The old data in these rows will remain the same. To fill in data for existing rows in the new column, users will need to manually re-publish data for all columns of each existing row using the corresponding row IDs. This method will completely overwrite the data in each specified row to better fit the new schema. " ] }, { "cell_type": "code", "execution_count": null, "id": "ea77601c", "metadata": {}, "outputs": [], "source": [ "# Example: Manually Editing Rows\n", "\n", "# Create Screener\n", "\n", "rows = (ScreenerRow(entity_id='MAYDMZQXXVXD9JK7', entity_type='asset'),)\n", "\n", "columns = (ScreenerColumn(column_name='Name', entity_parameter='name'),)\n", "\n", "params = ScreenerParameters(rows=rows, columns=columns)\n", "\n", "cron_schedule = '5 4 * * *'\n", "\n", "schema = Screener(name='My New Screener', parameters=params, cron_schedule=cron_schedule)\n", "\n", "screener = GsBaseScreenerApi.create_screener(schema)\n", "\n", "print('Screener ID: %s, Columns: %s\\n' % (screener.id, [col.column_name for col in screener.parameters.columns]))\n", "\n", "# Create Screen\n", "\n", "screen_schema = AnalyticsScreen(name='My View', filter_parameters=FilterRequest(), base_screener=screener.id)\n", "\n", "screen = GsDataScreenApi.create_screen(screen_schema)\n", "\n", "# Publish Data and View\n", "\n", "data = {\n", " 'rows': [\n", " {\n", " 'id': 0,\n", " 'Name': 'Obalon Therapeutics Inc',\n", " }\n", " ]\n", "}\n", "\n", "GsBaseScreenerApi.publish_to_screener(screener.id, data)\n", "\n", "sleep(1)\n", "\n", "view_data = GsDataScreenApi.filter_screen(screen.id, screen.filter_parameters)\n", "\n", "print('Screener %s Data:\\n' % (screener.id))\n", "\n", "display(pd.DataFrame(view_data))\n", "\n", "# Edit Screener Schema\n", "\n", "screener.parameters.columns = screener.parameters.columns + (\n", " ScreenerColumn(column_name='BBID', entity_parameter='bbid'),\n", ")\n", "\n", "updated_screener = GsBaseScreenerApi.edit_screener(screener.id, screener)\n", "\n", "sleep(1)\n", "\n", "# View Screener Columns\n", "\n", "print(\n", " '\\nScreener ID: %s, Columns: %s'\n", " % (updated_screener.id, [col.column_name for col in updated_screener.parameters.columns])\n", ")\n", "\n", "# Publish Additional Data\n", "\n", "data = {'rows': [{'id': 1, 'Name': 'Disney Inc', 'BBID': 'DIS'}]}\n", "\n", "GsBaseScreenerApi.publish_to_screener(updated_screener.id, data)\n", "\n", "sleep(1)\n", "\n", "# View Screener Data\n", "\n", "view_data = GsDataScreenApi.filter_screen(screen.id, screen.filter_parameters)\n", "\n", "print('\\nScreener %s Data:\\n' % (updated_screener.id))\n", "\n", "display(pd.DataFrame(view_data))\n", "\n", "# Delete Screen and Screener\n", "\n", "GsDataScreenApi.delete_screen(screen.id)\n", "\n", "GsBaseScreenerApi.delete_screener(updated_screener.id)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/00_data/04_screens/examples/01_screen_api_tutorial.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "fed9c09f", "metadata": {}, "source": [ "# GS Quant Screens Tutorial" ] }, { "cell_type": "markdown", "id": "bb98323a", "metadata": {}, "source": [ "Marquee data screens persistent views of asset data that can be filtered, modified, created, and deleted. New features in the `gs_quant` library now allow users to view the screens provisioned to them, make new screens based on existing screens, filter screens, delete screens, and modify existing screens using only library functions. Below, we demonstrate how to use each library function and provide examples of common use cases. " ] }, { "cell_type": "markdown", "id": "bdafbb35", "metadata": {}, "source": [ "### Imports\n", "\n", "First, we import the basic functions we need to begin manipulating screens in `gs_quant`. Additional imports are also used for easier visualization of screen data. To use any of the screen functions in `GsDataScreenApi`, users must have an active `GsSession`." ] }, { "cell_type": "code", "execution_count": null, "id": "47085e25", "metadata": {}, "outputs": [], "source": [ "# Required imports\n", "from gs_quant.session import GsSession, Environment # Authenticate user\n", "from gs_quant.api.gs.data_screen import GsDataScreenApi # Screen functions\n", "from gs_quant.target.data_screen import AnalyticsScreen, FilterRequest, OrderByBody # Screen data classes\n", "\n", "# Visualization imports\n", "import pandas as pd\n", "import pprint\n", "import copy\n", "\n", "GsSession.use(Environment.PROD) # Initialize GsSession" ] }, { "cell_type": "markdown", "id": "e7113ed9", "metadata": {}, "source": [ "## Overview" ] }, { "cell_type": "markdown", "id": "401abf11", "metadata": {}, "source": [ "Below is a brief overview of a typical use case: getting, modifying, filtering, saving, and deleting a screen. For more detail on these actions, continue to the sections below." ] }, { "cell_type": "code", "execution_count": null, "id": "44177740", "metadata": {}, "outputs": [], "source": [ "existing_screen = GsDataScreenApi.get_screens()[0]\n", "\n", "column_info = GsDataScreenApi.get_column_info(existing_screen.id)\n", "\n", "new_screen_specs = copy.deepcopy(existing_screen)\n", "\n", "new_screen_specs.name = 'New Screen'\n", "new_screen_specs.filter_parameters.filters = ({'columnName': 'Name', 'type': 'Substring', 'q': 'Z'},)\n", "new_screen_specs.filter_parameters.limit = 3\n", "new_screen_specs.filter_parameters.include_columns = ('Name', 'Skew')\n", "\n", "new_screen = GsDataScreenApi.create_screen(new_screen_specs)\n", "\n", "data = GsDataScreenApi.filter_screen(new_screen.id, new_screen.filter_parameters)\n", "\n", "display(pd.DataFrame(data))\n", "\n", "new_screen_specs = copy.deepcopy(existing_screen)\n", "\n", "new_screen_specs.name = 'New Screen'\n", "new_screen_specs.filter_parameters.filters = ({'columnName': 'Name', 'type': 'Substring', 'q': 'Z'},)\n", "new_screen_specs.filter_parameters.limit = 3\n", "new_screen_specs.filter_parameters.include_columns = ('Name', 'Skew')\n", "\n", "new_screen = GsDataScreenApi.create_screen(new_screen_specs)\n", "\n", "data = GsDataScreenApi.filter_screen(new_screen.id, new_screen.filter_parameters)\n", "\n", "display(pd.DataFrame(data))\n", "\n", "new_screen.filter_parameters.include_columns = new_screen.filter_parameters.include_columns + ('BBG Ticker',)\n", "\n", "new_screen = GsDataScreenApi.update_screen(new_screen.id, new_screen)\n", "\n", "data = GsDataScreenApi.filter_screen(new_screen.id, new_screen.filter_parameters)\n", "\n", "display(pd.DataFrame(data))\n", "\n", "GsDataScreenApi.delete_screen(new_screen.id)" ] }, { "cell_type": "markdown", "id": "c436463e", "metadata": {}, "source": [ "## Viewing Screens" ] }, { "cell_type": "markdown", "id": "598e0687", "metadata": {}, "source": [ "### Class `AnalyticsScreen`" ] }, { "cell_type": "markdown", "id": "c82baae6", "metadata": {}, "source": [ "Each instance of class `AnalyticsScreen` represents a screen and contains all information about the screen's applied filters, ordering, columns, identifiers, data sources, and other properties. `AnalyticsScreen` objects are commonly returned when gathering data on existing screens or created by the user to generate or update screens. Some notable fields of this type include: \n", "\n", "\n", "- **`name`** *(str, Required)*: The name of this screen. \n", "\n", "\n", "- **`filter_parameters`** *(FilterRequest, Required)*: The filters applied to this screen's data currently.\n", "\n", "\n", "- **`base_screener`** *(str, Required)*: An identifier of the data source that this screen uses. All data available in a screen comes directly from the corresponding base screener. The screen also applies all specified filters to this data before displaying it to the user.\n", "\n", "\n", "- **`id`** *(str, Optional)*: The unique identifier of this screen. This ID can be used to retrieve screen information from permanent storage. \n", "\n", "\n", "- **`hidden_columns`** *(Tuple[str, ...], Optional)*: Available columns that are currently not shown in this screen. Filtering on a screen allows users to display or hide specific columns. The `hidden_columns` field lists which columns have been excluded from view by the user. " ] }, { "cell_type": "markdown", "id": "bf115fe9", "metadata": {}, "source": [ "### Function `get_screens()`" ] }, { "cell_type": "markdown", "id": "fc0e1d35", "metadata": {}, "source": [ "When new screens are created, they are stored persistently. Function `get_screens()` returns a list of all screens available to the user. Each screen or entry in this list is an `AnalyticsScreen` object that can be used to retrieve the screen's data, manipulate the screen, or create a new screen. " ] }, { "cell_type": "code", "execution_count": null, "id": "fb2215f0", "metadata": {}, "outputs": [], "source": [ "# Usage Example: get_screens()\n", "\n", "all_screens = GsDataScreenApi.get_screens()\n", "print(all_screens)\n", "\n", "screen = all_screens[0]\n", "\n", "print('\\nScreen Name: %s, Base Screener: %s, Screen ID: %s' % (screen.name, screen.base_screener, screen.id))" ] }, { "cell_type": "markdown", "id": "67be928b", "metadata": {}, "source": [ "### Function `get_screen()`" ] }, { "cell_type": "markdown", "id": "24972be6", "metadata": {}, "source": [ "Use function `get_screen(screen_id)`to retrieve the `AnalyticsScreen` object for a specific screen using its screen ID. " ] }, { "cell_type": "code", "execution_count": null, "id": "d75c9e2a", "metadata": {}, "outputs": [], "source": [ "# Usage Example: get_screen()\n", "\n", "screen = GsDataScreenApi.get_screen('SCQC82C1C960G1I30P') # REMOVE:Keep permanent screen for this demo? in prod/dev?\n", "\n", "print(screen)\n", "print('\\nScreen Name: %s, Base Screener: %s, Screen ID: %s' % (screen.name, screen.base_screener, screen.id_))" ] }, { "cell_type": "markdown", "id": "fc83e909", "metadata": {}, "source": [ "## Filtering and Viewing Screen Data " ] }, { "cell_type": "markdown", "id": "bed56e08", "metadata": {}, "source": [ "### Function `get_column_info()`" ] }, { "cell_type": "markdown", "id": "ea24d1ae", "metadata": {}, "source": [ "Before filtering a screen's data for specific assets, we may want to view a description of all columns available in this screen and their properties. Use function `get_column_info(screen_id)` to get information about all columns in a screen, including previously hidden columns. Descriptions of certain column types also include other metrics: \n", "\n", "- For `Enum` type columns, column descriptions include each possible value for the `Enum` as well as the number of times each value appears in the unfiltered data. \n", "\n", "\n", "- For `Number` type columns, column descriptions include the maximum and minimum numeric values present in the unfiltered data.\n", "\n", "\n", "- For `String` type columns, column descriptions are currently empty. If an `Enum` type column has more than 20 values, it is automatically converted to a `String` type column. \n", "\n", "These column properties can help users create more effective filters. The keys of the dictionary returned from `get_column_info()` correspond to the names of each column, and the corresponding values are the description of each column. " ] }, { "cell_type": "code", "execution_count": null, "id": "77faa83d", "metadata": {}, "outputs": [], "source": [ "# Usage Example: get_column_info() #ASK - is any string coluymn w/ <20 values immediately converted to enum? or no?\n", "# Do some enum cols not show the values?\n", "\n", "column_info = GsDataScreenApi.get_column_info('SCQC82C1C960G1I30P')\n", "pprint.pprint(column_info)\n", "\n", "# Example: Number type column description\n", "\n", "num_colname = 'Skew'\n", "print('\\033[4m\\nNumeric Column:\\033[0m %s\\n ' % num_colname)\n", "pprint.pprint(column_info[num_colname])\n", "\n", "# Example: Enum type column description\n", "\n", "enum_colname = 'Entity ID'\n", "print('\\033[4m\\nEnum Column:\\033[0m %s\\n ' % enum_colname)\n", "pprint.pprint(column_info[enum_colname])\n", "\n", "# Example: String type column description\n", "\n", "str_colname = 'Name'\n", "print('\\033[4m\\nString Column:\\033[0m %s\\n ' % str_colname)\n", "pprint.pprint(column_info[str_colname])" ] }, { "cell_type": "markdown", "id": "9a58e4b5", "metadata": {}, "source": [ "### Creating Filters" ] }, { "cell_type": "markdown", "id": "0bb26cdd", "metadata": {}, "source": [ "Using information about our column types and values, we can now create filters that we will apply to screens to view their filtered data. Additionally, we can permanently filter screens by assigning filters to the `filter_parameters` field in screen objects.\n", "\n", "A configuration of filters for a single screen is stored in a `FilterRequest` object. Some notable fields of the `FilterRequest` class include: \n", "\n", "\n", "- **`include_columns`** *(Tuple[str, ...], Optional)*: A tuple of column names. The columns included in this field will be visible in the screen's data. All other columns will be hidden. If nothing is supplied in this field, all columns will be visible.\n", "\n", "\n", "- **`filters`** *(Tuple[dict, ...], Optional)*: A tuple of dictionaries. Each dictionary defines a filter to be applied to the data. Each filter pertains to data in a single specified column. All assets returned from the screen data must meet the requirements of all filters in `filters`. If nothing is supplied in this field, all screen data will be visible. \n", "\n", "\n", "- **`order_by`** *(OrderByBody, Optional)*: An `OrderByBody` object. An instance of `OrderByBody` specifies how the returned data rows of the screen should be ordered after filtering. \n", "\n", "\n", "- **`limit`** *(float, Optional)*: A value specifying the maximum number of results that should be returned from the screen. If the `limit` field has value `n`, the first `n` rows of data will be retrieved based on the ordering in `order_by`.\n", "\n" ] }, { "cell_type": "markdown", "id": "02f743c6", "metadata": {}, "source": [ "Different filters are available based on the different columns and column types in the screen. `Number` type columns support `Range` filters, `Enum` type columns support `Include` filters (which enums to include), and `String` type columns support `Substring` filters. Below is an example of how to generate each type of filter:" ] }, { "cell_type": "code", "execution_count": null, "id": "e43eb480", "metadata": {}, "outputs": [], "source": [ "# Number Columns: Range Filtering\n", "\n", "num_col = \"Skew\"\n", "\n", "range_filter = {'columnName': num_col, 'type': 'Range', 'greaterThanEqual': 0, 'lessThanEqual': 1}\n", "\n", "print('\\033[4mRange Filter Example:\\033[0m\\n')\n", "pprint.pprint(range_filter)\n", "\n", "\n", "# Enum Columns: Include Filtering\n", "\n", "enum_col = \"Entity ID\"\n", "\n", "include_filter = {\n", " 'columnName': enum_col,\n", " 'type': 'Include',\n", " 'values': ['VALUE1', 'VALUE2'],\n", "} # find a screen w/ actual enums\n", "\n", "print('\\n\\033[4mInclude Filter Example:\\033[0m\\n')\n", "pprint.pprint(include_filter)\n", "\n", "# String Columns: Substring Filtering\n", "\n", "str_col = \"Name\"\n", "\n", "substring_filter = {'columnName': str_col, 'type': 'Substring', 'q': 'D'}\n", "\n", "print('\\n\\033[4mSubstring Filter Example:\\033[0m\\n')\n", "pprint.pprint(substring_filter)" ] }, { "cell_type": "markdown", "id": "da8d51ee", "metadata": {}, "source": [ "When creating a `FilterRequest` object, include an `OrderByBody` object to enforce an ordering of the retrieved data rows. Notable fields in these instances include: \n", "\n", "\n", "- **`column_name`** *(str, Optional)*: The name of the column to order by.\n", "\n", "\n", "- **`type`** *(str, Optional)*: The type of ordering to enforce. Must be either `Ascending` or `Descending`." ] }, { "cell_type": "code", "execution_count": null, "id": "e24a4956", "metadata": {}, "outputs": [], "source": [ "# Example: Ascending Order by Skew\n", "\n", "order_by = OrderByBody(column_name='Skew', type='Ascending')\n", "print('Column Name: %s, Row Ordering: %s' % (order_by.column_name, order_by.type))" ] }, { "cell_type": "markdown", "id": "d24113d1", "metadata": {}, "source": [ "### Function `filter_screen()`" ] }, { "cell_type": "markdown", "id": "81116675", "metadata": {}, "source": [ "Use function `filter_screen(screen_id, filter_request)` to view filtered data from a screen. The `filters` parameter of this function is a filter configuration that temporarily overrides the existing filter configuration associated with the given screen. All existing filters applied to this screen are temporarily removed, and the filters supplied in this function will be applied instead." ] }, { "cell_type": "code", "execution_count": null, "id": "92f2184d", "metadata": {}, "outputs": [], "source": [ "# Usage Example: filter_screen()\n", "\n", "# Create a FilterRequest\n", "\n", "filters = (substring_filter, range_filter)\n", "\n", "include_columns = ('Name', 'Skew', 'BBG Ticker', 'Vol Premia')\n", "\n", "limit = 3\n", "\n", "filter_request = FilterRequest(filters=filters, include_columns=include_columns, order_by=order_by, limit=limit)\n", "\n", "# Get Filtered Data\n", "\n", "filtered_data = GsDataScreenApi.filter_screen('SCQC82C1C960G1I30P', filter_request)\n", "\n", "pprint.pprint(filtered_data)\n", "\n", "# Visualize Filtered Data as Dataframe\n", "\n", "df = pd.DataFrame(filtered_data)\n", "\n", "display(df)" ] }, { "cell_type": "markdown", "id": "88ab5796", "metadata": {}, "source": [ "To view filtered data using the screen's existing configuration, pass the value stored in the `filter_parameters` field of the screen's object into `filter_screen()`." ] }, { "cell_type": "code", "execution_count": null, "id": "3d54ff9e", "metadata": {}, "outputs": [], "source": [ "# Example: View Current Configuration Data\n", "\n", "screen = GsDataScreenApi.get_screen('SCQC82C1C960G1I30P')\n", "\n", "filtered_data = GsDataScreenApi.filter_screen(screen.id, screen.filter_parameters)\n", "\n", "pprint.pprint(filtered_data[0:3])" ] }, { "cell_type": "markdown", "id": "f368479e", "metadata": {}, "source": [ "## Creating and Deleting Screens" ] }, { "cell_type": "markdown", "id": "eb42e422", "metadata": {}, "source": [ "### Function `delete_screen()`" ] }, { "cell_type": "markdown", "id": "7bad7dc4", "metadata": {}, "source": [ "Use function `delete_screen()` to permanently delete an existing screen using its ID. Once a screen is deleted, its information and data cannot be retrieved again. However, it is possible that users may still have access to stale data or `AnalyticsScreen` objects remaining from the old screen." ] }, { "cell_type": "markdown", "id": "68f2fa05", "metadata": {}, "source": [ "### Function `create_screen()`" ] }, { "cell_type": "markdown", "id": "9dddc7fb", "metadata": {}, "source": [ "Use `create_screen()` to create new permanent screens using `AnalyticsScreen` objects. To specify a new screen's data source, applied filters, and other attributes, edit the information in the `AnalyticsScreen` instance provided to create the screen. All screens with the same `base_screener` field value will reference the same data source." ] }, { "cell_type": "markdown", "id": "b3f2efc0", "metadata": {}, "source": [ "### Examples: Screen Creation and Deletion" ] }, { "cell_type": "code", "execution_count": null, "id": "7c4866d7", "metadata": {}, "outputs": [], "source": [ "# Usage Example: create_screen() and delete_screen()\n", "\n", "# Create new screen\n", "\n", "filter_request = FilterRequest(filters=({'columnName': 'Name', 'type': 'Substring', 'q': 'A'},), limit=3)\n", "\n", "screen_specs = AnalyticsScreen(name='New Screen', filter_parameters=filter_request, base_screener='BS88M7XNRA1D1FL3OM')\n", "\n", "print('Name: %s, Screen ID: %s' % (screen_specs.name, screen_specs.id))\n", "\n", "new_screen = GsDataScreenApi.create_screen(screen_specs)\n", "\n", "print('Name: %s, Screen ID: %s' % (new_screen.name, new_screen.id))\n", "\n", "# View screen data\n", "\n", "data = GsDataScreenApi.filter_screen(new_screen.id, new_screen.filter_parameters)\n", "\n", "display(pd.DataFrame(data))\n", "\n", "# Delete screen\n", "\n", "GsDataScreenApi.delete_screen(new_screen.id)" ] }, { "cell_type": "markdown", "id": "64c97ba6", "metadata": {}, "source": [ "Additionally, create copies of existing screens by passing their screen objects directly into `create_screen()`. These existing screen objects can also be modified and passed to `create_screen()` to generate new screens that only differ marginally from an existing screen. " ] }, { "cell_type": "code", "execution_count": null, "id": "1fe6783c", "metadata": {}, "outputs": [], "source": [ "# Example: Create and Delete a Copy of an Existing Screen\n", "\n", "# Retrieve an Existing Screen\n", "\n", "existing_screen = GsDataScreenApi.get_screens()[0]\n", "\n", "print('\\033[4mExisting Screen ID and Filters:\\033[0m %s\\n' % existing_screen.id)\n", "pprint.pprint(existing_screen.filter_parameters.filters)\n", "\n", "# Make a Copy\n", "\n", "new_screen = GsDataScreenApi.create_screen(existing_screen)\n", "\n", "print('\\n\\033[4mCopy Screen ID and Filters:\\033[0m %s\\n' % new_screen.id)\n", "pprint.pprint(new_screen.filter_parameters.filters)\n", "\n", "\n", "# Delete Copy\n", "\n", "GsDataScreenApi.delete_screen(new_screen.id)" ] }, { "cell_type": "markdown", "id": "6549b18d", "metadata": {}, "source": [ "## Updating Existing Screens" ] }, { "cell_type": "markdown", "id": "8e2ac731", "metadata": {}, "source": [ "### Function `update_screen()`" ] }, { "cell_type": "markdown", "id": "9e931fd6", "metadata": {}, "source": [ "Use function `update_screen()` to permanently update an existing screen using the ID of the screen and an `AnalyticsScreen` object specifying how the screen should be changed. Note that all previous specifications for the existing screen will be replaced by the information in the `AnalyticsScreen` instance passed into `update_screen()`, including screen names, filters, and data sources. Only the screen ID will remain the same after updating. \n", "\n", "Additionally, `update_screen()` requires that the screen ID passed in must match the ID field of the screen object passed in. An error is thrown if these two values do not match." ] }, { "cell_type": "code", "execution_count": null, "id": "9f5ff8a9", "metadata": {}, "outputs": [], "source": [ "# Usage Example: update_screen()\n", "\n", "# Create a New Screen\n", "\n", "filter_request = FilterRequest()\n", "\n", "screen_spec = AnalyticsScreen(\n", " name='Screen to Update', base_screener='BS88M7XNRA1D1FL3OM', filter_parameters=filter_request\n", ")\n", "\n", "screen = GsDataScreenApi.create_screen(screen_spec)\n", "\n", "print('Name: %s, ID: %s\\n' % (screen.name, screen.id))\n", "pprint.pprint(screen.filter_parameters.filters)\n", "\n", "# Update the New Screen\n", "\n", "updated_filter_request = FilterRequest(filters=({'columnName': 'Name', 'type': 'Substring', 'q': 'A'},))\n", "\n", "screen.filter_parameters = updated_filter_request\n", "\n", "screen.name = 'Updated Screen'\n", "\n", "screen = GsDataScreenApi.update_screen(screen.id, screen)\n", "\n", "print('\\nName: %s, ID: %s\\n' % (screen.name, screen.id))\n", "\n", "pprint.pprint(screen.filter_parameters.filters)\n", "\n", "# Delete the New Screen\n", "\n", "GsDataScreenApi.delete_screen(screen.id)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/00_data/covid/Comparing, Reconciling, and Combining COVID-19 Data Sources.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Comparing, Reconciling, and Combining COVID-19 Data Sources" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Summary\n", "\n", "In this note we use `gs-quant` to compare COVID-19 data sources. To do this, we first retrieve COVID-19-related time \n", "series data and preprocess it, joining different sources together to analyze confirmed cases. \n", "\n", "The contents of this notebook are as follows:\n", "\n", "- [1 - Getting Started](#1---Getting-Started)\n", "- [2 - COVID-19 Data](#2---COVID-19-Data)\n", "- [3 - Comparing Global Sources](#3---Comparing-Global-Sources)\n", "- [4 - Comparing US Sources](#4---Comparing-US-Sources)\n", "- [5 - Comparing subregions, combining with mobility data](#5---Comparing-subregions,-combining-with-mobility-data)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1 - Getting Started" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Start every session with authenticating with your unique client id and secret. For information on\n", "how to get setup on GS Quant, see [Getting Started](/covid/guides/getting-started). Below produced\n", "using gs-quant version 0.8.126" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": false } }, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('read_product_data',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2 - COVID-19 Data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We'll start by defining a general function to load various datasets, which includes regional data, for the past week:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": false } }, "outputs": [], "source": [ "from gs_quant.data import Dataset\n", "import datetime\n", "\n", "\n", "# Note: There is data going back to 2019-12-31, you will need to write your own code to batch data fetching\n", "def get_datasets(datasets):\n", " ds_dict = {}\n", " end = datetime.date(2020, 7, 9)\n", " start = end - datetime.timedelta(weeks=1)\n", "\n", " for dataset in datasets:\n", " try:\n", " df = Dataset(dataset).get_data(start, end)\n", "\n", " keys = [x for x in ['countryId', 'subdivisionId'] if x in df.columns] + ['date']\n", " val_map = {'newConfirmed': 'totalConfirmed', 'newFatalities': 'totalFatalities'}\n", " vals = [x for x in list(val_map.keys()) if x in df.columns]\n", "\n", " df_t = df.groupby(keys).sum().groupby(level=0).cumsum().reset_index()[keys + vals].rename(columns=val_map)\n", " ds_dict[dataset] = df.reset_index().merge(df_t, on=keys, suffixes=('', '_y')).set_index('date')\n", "\n", " except Exception as err:\n", " print(f'Failed to obtain {dataset} with {getattr(err, \"message\", repr(err))}')\n", " return ds_dict" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We create a list of some of the available datasets, and fetch all of them, so that we can compare them." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": false } }, "outputs": [], "source": [ "country_datasets = [\n", " 'COVID19_COUNTRY_DAILY_ECDC',\n", " 'COVID19_COUNTRY_DAILY_WHO',\n", " 'COVID19_COUNTRY_DAILY_WIKI',\n", " 'COVID19_US_DAILY_CDC',\n", "]\n", "df = get_datasets(country_datasets)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next we look at the date ranges of each dataset to determine how much history they have, and ensure they are \n", "up-to-date:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "for name, ds in df.items():\n", " print('{:<30} {} {}'.format(name, ds.index[0].date(), ds.index[-1].date()))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3 - Comparing Global Sources\n", "\n", "Below is a general function to compare the time series of certain columns across datasets:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": false } }, "outputs": [], "source": [ "import re\n", "from typing import Union\n", "\n", "\n", "def compare_time_series(\n", " df, datasets, columns: Union[str, list], grouping: str = 'countryId', suffix_identifier: float = 1\n", "):\n", " columns = [columns] if isinstance(columns, str) else columns\n", " suffixes = list(map(lambda ds_name: '_' + re.findall('\\_([A-Z]+)', ds_name)[-suffix_identifier], datasets))\n", " df_combo = None\n", "\n", " for ds_name in datasets:\n", " ds = df[ds_name]\n", " df_combo = ds if df_combo is None else df_combo\n", " df_suffixes = ('', '_' + re.findall('\\_([A-Z]+)', ds_name)[-suffix_identifier])\n", " df_combo = df_combo.merge(ds, on=['date', grouping], suffixes=df_suffixes)\n", "\n", " return df_combo[[grouping] + [column + suffix for suffix in suffixes for column in columns]]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For example, if we want to compare the time series for total confirmed cases across the WHO, ECDC, and Wikipedia \n", "datasets globally, we can do the following:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "datasets = ['COVID19_COUNTRY_DAILY_ECDC', 'COVID19_COUNTRY_DAILY_WHO', 'COVID19_COUNTRY_DAILY_WIKI']\n", "df_to_compare = compare_time_series(df, datasets, columns='totalConfirmed')\n", "\n", "df_to_compare.describe().style.background_gradient(cmap='Blues', axis=1).format('{:,.2f}')" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "This shows statistical properties for each dataset for all common countries and dates. As we can see, there's some \n", "variation in the data sources. Let's dig in a little further and plot the relationship between the WHO and ECDC for \n", "a number of countries: " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import seaborn as sns\n", "\n", "select_countries = ['GB', 'DE', 'IT', 'ES', 'FR', 'RU']\n", "to_plot = df_to_compare[df_to_compare.countryId.isin(select_countries)]\n", "\n", "sns.lmplot(\n", " x=\"totalConfirmed_ECDC\", y=\"totalConfirmed_WHO\", col=\"countryId\", data=to_plot, col_wrap=3, height=3, fit_reg=False\n", ");" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "As we can see, there is some dispersion between sources for certain countries. For information on the various ISO\n", "country codes, see [this guide](https://developer.gs.com/docs/covid/guides/standards/iso-countries/).\n", "\n", "### 4 - Comparing US Sources\n", "\n", "Now let's take a closer look at the US data, adding in the CDC dataset:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "datasets = [\n", " 'COVID19_US_DAILY_CDC',\n", " 'COVID19_COUNTRY_DAILY_ECDC',\n", " 'COVID19_COUNTRY_DAILY_WHO',\n", " 'COVID19_COUNTRY_DAILY_WIKI',\n", "]\n", "df_to_compare = compare_time_series(df, datasets, columns='totalConfirmed')\n", "\n", "df_to_compare.describe().style.background_gradient(cmap='Blues', axis=1).format('{:,.2f}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As of 21 of May 2020, CDC had the most confirmed cases, followed by Wikipedia, and then ECDC and WHO. This is not \n", "overly surprising given the information collection and validation flows. Now let's examine the last few points:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now let's compare all the series side by side:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "df_to_compare.plot(figsize=(10, 6), title='US')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "\n", "(df_to_compare['totalConfirmed_WHO'] - df_to_compare['totalConfirmed_ECDC']).plot(\n", " figsize=(10, 6), title='Differences vs WHO', label='ECDC'\n", ")\n", "(df_to_compare['totalConfirmed_WHO'] - df_to_compare['totalConfirmed_CDC']).plot(label='CDC')\n", "(df_to_compare['totalConfirmed_WHO'] - df_to_compare['totalConfirmed_WIKI']).plot(label='WIKI')\n", "\n", "plt.legend()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This chart illustrates how the ECDC and CDC map cases versus the WHO. At the start of the epidemic these sources were\n", "much closer, and diverged over time, with CDC leading in reporting for the US versus the ECDC and WHO. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 5 - Comparing subregions, combining with mobility data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, we illustrate how to compare datasets for specific countries (in this case, Italy) at different level of granularity (region, province, etc.) and how to ccombine epidemic data with mobility data from Google.\n", "\n", "As before, we fetch data for Italy, at three levels of granularity." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "datasets = ['COVID19_ITALY_DAILY_DPC', 'COVID19_REGION_DAILY_DPC', 'COVID19_PROVINCE_DAILY_DPC']\n", "df = get_datasets(datasets)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df_to_compare = compare_time_series(df, datasets, columns='totalConfirmed', suffix_identifier=3)\n", "\n", "df_to_compare.describe().style.background_gradient(cmap='Blues', axis=1).format('{:,.2f}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We write a function to compare the data across different geographic subdivisions." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from functools import reduce\n", "import pandas as pd\n", "\n", "\n", "def compare_totals_across_breakdowns(df, data1, data2, column_to_check):\n", " # pick the common indices between the data being compared\n", " common_idx_province = reduce(lambda x, y: x & y, df[data1[0]].groupby(data1[1]).apply(lambda x: x.index).tolist())\n", " common_idx_region = reduce(lambda x, y: x & y, df[data2[0]].groupby(data2[1]).apply(lambda x: x.index).tolist())\n", " idx = common_idx_province & common_idx_region\n", "\n", " # calculate the difference, and rename column\n", " diff = df[data1[0]].groupby(data1[1]).apply(lambda x: x.loc[idx][column_to_check]).T.apply(sum, axis=1) - df[\n", " data2[0]\n", " ].groupby(data2[1]).apply(lambda x: x.loc[idx][column_to_check]).T.apply(sum, axis=1)\n", " diff = pd.DataFrame(diff).rename(columns={0: f'{data1[0]}-{data2[0]}'})\n", " return diff\n", "\n", "\n", "diff1 = compare_totals_across_breakdowns(\n", " df, ('COVID19_ITALY_DAILY_DPC', 'countryId'), ('COVID19_REGION_DAILY_DPC', 'subdivisionId'), 'totalConfirmed'\n", ")\n", "diff2 = compare_totals_across_breakdowns(\n", " df,\n", " ('COVID19_REGION_DAILY_DPC', 'subdivisionId'),\n", " ('COVID19_PROVINCE_DAILY_DPC', 'administrativeRegion'),\n", " 'totalConfirmed',\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We plot the discrepancies below..." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "to_plot = diff1.join(diff2)\n", "\n", "sns.lmplot(\n", " x=\"COVID19_ITALY_DAILY_DPC-COVID19_REGION_DAILY_DPC\",\n", " y=\"COVID19_REGION_DAILY_DPC-COVID19_PROVINCE_DAILY_DPC\",\n", " data=to_plot,\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "... and interestingly, this indicates there is no discrepancy at all when we compare country-level aggregate data with region-level aggregate data, but we do see discrepancies when we compate province-level with region-level data." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, we illustrate how to join region-level data from Italy with mobility data from Google, which allows us to check, for example, how the increase in cases of COVID-19 affected mobility patterns in the population. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from datetime import datetime\n", "\n", "df_mob = Dataset('COVID19_SUBDIVISION_DAILY_GOOGLE').get_data(start_date=datetime(2020, 2, 1).date(), countryId='IT')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df_mob.head(2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We now join mobility data with region-level data in Italy, for the subdivision of Liguria." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def join_dfs(subdivision, mobility, column_to_compare):\n", " df_red = df['COVID19_REGION_DAILY_DPC'][df['COVID19_REGION_DAILY_DPC']['subdivisionId'] == subdivision]\n", " subdivision_name = df_red['subdivisionName'].unique()[0]\n", " df1 = df_red[[column_to_compare]]\n", " df2 = df_mob[df_mob.subdivisionId.isin([subdivision]) & df_mob.group.isin([mobility])]\n", " df_joint = df1.merge(df2, on='date')\n", " return df_joint, mobility, subdivision_name\n", "\n", "\n", "df_joint, mobility, subdivision_name = join_dfs('IT-42', 'retailRecreation', 'newConfirmed')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, we plot a chart comparing mobility data with data on the growth of the epidemic." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df_joint['Change in Mobility'] = df_joint['value'].diff()\n", "df_joint[['newConfirmed', 'value']].rename(\n", " columns={'newConfirmed': 'New Confirmed Cases', 'value': f'Change in {mobility} mobility'}\n", ").plot(\n", " figsize=(\n", " 10,\n", " 7,\n", " ),\n", " grid=True,\n", " title=f'Comparison of new confirmed cases and mobility in {subdivision_name}',\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can observe a dramatic drop in moblity as the rate of new cases began to increase, a pattern that persisted during the peak of the epidemic in the region of Liguria.\n", "\n", "Please reach out to `covid-data@gs.com` with any questions." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Disclaimer\n", "This website may contain links to websites and the content of third parties (\"Third Party Content\"). We do not monitor, \n", "review or update, and do not have any control over, any Third Party Content or third party websites. We make no \n", "representation, warranty or guarantee as to the accuracy, completeness, timeliness or reliability of any \n", "Third Party Content and are not responsible for any loss or damage of any sort resulting from the use of, or for any \n", "failure of, products or services provided at or from a third party resource. If you use these links and the \n", "Third Party Content, you acknowledge that you are doing so entirely at your own risk." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/01_markets/00_securities/examples/0000_get_an_asset_from_identifier.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": false } }, "outputs": [], "source": [ "from gs_quant.markets.securities import SecurityMaster, AssetIdentifier\n", "from gs_quant.session import GsSession, Environment" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterh\n", "\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "asset = SecurityMaster.get_asset('2407966', AssetIdentifier.SEDOL, sort_by_rank=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "asset" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "asset.get_identifiers() # get all identifiers of the asset as of now" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: gs_quant/documentation/01_markets/02_rdates/examples/0000_get_a_date_relative_from_today.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.datetime.relative_date import RelativeDate\n", "from gs_quant.session import GsSession, Environment" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterh\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "my_date = RelativeDate('-1d').apply_rule() # Returns previous day\n", "my_date" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "pycharm": { "stem_cell": { "cell_type": "raw", "source": [], "metadata": { "collapsed": false } } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/01_markets/02_rdates/examples/0001_get_a_date_relative_from_a_base_date.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": false } }, "outputs": [], "source": [ "from datetime import date, timedelta\n", "from gs_quant.datetime.relative_date import RelativeDate\n", "from gs_quant.session import GsSession, Environment" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterh\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "yesterday = date.today() - timedelta(days=1)\n", "my_date = RelativeDate('-1d', base_date=yesterday).apply_rule() # Returns 2 days ago\n", "my_date" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "pycharm": { "stem_cell": { "cell_type": "raw", "source": [], "metadata": { "collapsed": false } } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/01_markets/02_rdates/examples/0002_get_a_date_relative_using_pricing_context.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": { "pycharm": { "is_executing": false } }, "outputs": [], "source": [ "from datetime import date\n", "from datetime import timedelta\n", "from gs_quant.datetime.relative_date import RelativeDate\n", "from gs_quant.markets import PricingContext\n", "from gs_quant.session import GsSession, Environment" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterh\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [ { "data": { "text/plain": [ "datetime.date(2021, 1, 3)" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "yesterday = date.today() - timedelta(days=1)\n", "with PricingContext(pricing_date=yesterday):\n", " my_date = RelativeDate('-1d').apply_rule() # Returns 2 days ago\n", "my_date" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/01_markets/02_rdates/examples/0003_get_a_date_using_a_business_day_calendar.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": false } }, "outputs": [], "source": [ "from gs_quant.target.common import Currency\n", "from gs_quant.datetime.relative_date import RelativeDate\n", "from gs_quant.markets.securities import ExchangeCode\n", "from gs_quant.session import GsSession, Environment" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterh\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "date_a = RelativeDate('-1b').apply_rule(\n", " exchanges=[ExchangeCode.NYSE]\n", ") # Returns previous business day base on NYSE calendar\n", "date_b = RelativeDate('-1b').apply_rule(currencies=[Currency.BRL]) # Returns previous business day base on BRL Currency\n", "print(date_a, date_b)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/01_markets/02_rdates/examples/0004_chaining_relativedate_rules.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true, "pycharm": { "is_executing": false } }, "outputs": [], "source": [ "from datetime import date\n", "\n", "from gs_quant.datetime.relative_date import RelativeDate\n", "from gs_quant.markets.securities import ExchangeCode\n", "from gs_quant.session import GsSession, Environment" ] }, { "cell_type": "code", "execution_count": 1, "outputs": [ { "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0myesterday\u001b[0m\u001b[1;33m:\u001b[0m \u001b[0mdate\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mdate\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mtoday\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;33m-\u001b[0m \u001b[0mtimedelta\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mdays\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 2\u001b[0m \u001b[0mdate\u001b[0m\u001b[1;33m:\u001b[0m \u001b[0mdate\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mRelativeDate\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'-1d'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mbase_date\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0myesterday\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mapply_rule\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;31m# Returns 2 days ago\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mNameError\u001b[0m: name 'date' is not defined" ], "ename": "NameError", "evalue": "name 'date' is not defined", "output_type": "error" } ], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterh\n", "\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('read_product_data',))" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n", "is_executing": false } } }, { "cell_type": "markdown", "source": [ "## Date Rule conventions\n", "A: first day of th year\n", "b: business days of calendar passed, USD by default\n", "d: Gregorian calendar days\n", "e: end of month (ignores number)\n", "m: month\n", "r: end of year\n", "u: business days ignoring USD holidays\n", "v: gets last business day of month (does not ignore number)\n", "x: gets last business day of month (ignores the number)\n", "y: add years, Result will be moved to the next week if falling on a weekend" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "# Returns four business days after the first business day on or after the 15th calendar day of the month\n", "date: date = RelativeDate('14d+0u+4u').apply_rule(exchanges=[ExchangeCode.NYSE])" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } } ], "metadata": { "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" }, "kernelspec": { "name": "python3", "language": "python", "display_name": "Python 3" }, "pycharm": { "stem_cell": { "cell_type": "raw", "source": [], "metadata": { "collapsed": false } } } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/01_markets/02_rdates/examples/0005_passing_your_own_holiday_calendar.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": false } }, "outputs": [], "source": [ "from datetime import date\n", "from gs_quant.datetime.relative_date import RelativeDate\n", "from gs_quant.session import GsSession, Environment" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterh\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "holiday_calendar = [date(2020, 12, 15)]\n", "my_date = RelativeDate('-1b', base_date=date(2020, 12, 16)).apply_rule(holiday_calendar=holiday_calendar)\n", "my_date" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "pycharm": { "stem_cell": { "cell_type": "raw", "source": [], "metadata": { "collapsed": false } } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/01_view-trade-properties.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwap\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": false } }, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Instrument Properties" ] }, "outputs": [], "source": [ "# View IRSwap instrument properties\n", "IRSwap.properties()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# When you create an instance of IRSwap, properties can be set\n", "my_swap = IRSwap(PayReceive.Pay, '5y', Currency.USD)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Instrument Properties" ] }, "outputs": [], "source": [ "# View these property values by calling 'as_dict' on the swap instance\n", "print(my_swap.as_dict())" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/02_get-a-property.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwap\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "my_swap = IRSwap(PayReceive.Receive, '13m', Currency.GBP, notional_amount=5e6)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Instrument Properties" ] }, "outputs": [], "source": [ "print(my_swap.termination_date)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Instrument Properties" ] }, "outputs": [], "source": [ "# you can also see the properties of an instrument by converting that instrument to a dictionary and looking at that\n", "\n", "my_swap.to_dict()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/03_set-a-property.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwap\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "my_swap = IRSwap(PayReceive.Pay, '7y', Currency.EUR)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Instrument Properties" ] }, "outputs": [], "source": [ "my_swap.termination_date = '10y'\n", "print(my_swap.termination_date)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/04_enum-property.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwap\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Some available properties are enums available in the common modules. 'Currency' and 'PayReceive' in this examples are enums. To see possible values in an IDE please use autocomplete (tab)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" }, "tags": [ "Instrument Properties" ] }, "outputs": [], "source": [ "my_swap = IRSwap(PayReceive.Receive, '5y', Currency.GBP, effective_date='1m')\n", "print(list(PayReceive))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Most properties which take an enum also take a string representation of that enum" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Instrument Properties" ] }, "outputs": [], "source": [ "my_swap = IRSwap('Receive', '5y', Currency.GBP, effective_date='1m')\n", "print(my_swap.pay_or_receive)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/05_resolve-a-trade.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "from gs_quant.common import PayReceive\n", "from gs_quant.instrument import IRSwap\n", "from gs_quant.markets import HistoricalPricingContext\n", "from gs_quant.session import Environment, GsSession\n", "\n", "import datetime as dt" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Calling the `resolve()` method will resolve any relative parameters to absolute values and fill in unspecified parameters.\n", "* `resolve()` will change the state of the instrument object in place by default unless argument `in_place` is `False`.\n", "* `resolve` can be used to \"fix\" the instrument parameters when evaluating pricing & risk." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "my_swap = IRSwap(PayReceive.Pay, '5y', fixed_rate='atm+30')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Instrument - Resolve" ] }, "outputs": [], "source": [ "# note fixed_rate and termination_date are resolved to a float and date, respectively\n", "my_swap.resolve()\n", "my_swap.to_dict()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you are resolving under a historical pricing context you will need to set the in_place parameter to False to\n", "return a dictionary of the resolved trades on each date in the pricing context." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Instrument - Resolve", "Contexts - HistoricalPricingContext" ] }, "outputs": [], "source": [ "with HistoricalPricingContext(dt.date(2025, 3, 25), dt.date(2025, 3, 27)):\n", " resolved_swaps = my_swap.resolve(in_place=False)\n", "\n", "resolved_swaps.result()[dt.date(2025, 3, 25)].to_dict()" ] } ], "metadata": { "celltoolbar": "Tags", "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/06_market-data.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import Currency, PayReceive\n", "from gs_quant.instrument import IRSwap\n", "from gs_quant.markets import PricingContext, OverlayMarket, MarketDataCoordinate\n", "from gs_quant.session import GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "### Retrieve market data " ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "Let's create a swaption and retrieve the market data our instrument is sensitive to. To do so we can call `market()` on \n", "our instrument. This will give us the `OverlayMarket` object which contains the market data used to price our \n", "instrument. " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" }, "tags": [ "Market Data - Retrieval" ] }, "outputs": [], "source": [ "swap = IRSwap(PayReceive.Receive, '10y', Currency.EUR, fixed_rate=-0.025)\n", "swap.resolve()\n", "market = swap.market()\n", "\n", "print(f'Base price: {swap.price()}')" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "Then, using the `market_data` attribute, we can access the market data coordinates and values directly" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" }, "tags": [ "Market Data - Retrieval" ] }, "outputs": [], "source": [ "print(f'The value of the coordinate, {market.market_data[0].coordinate} is {market.market_data[0].value}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also get a dictionary of all market data coordinates" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Market Data - Retrieval" ] }, "outputs": [], "source": [ "market.market_data_dict" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "### Overwrite market data" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "We can also amend the market data of our instrument's `OverlayMarket` to pass-in our own market data value. \n", "To do so, we simply overwrite the `MarketDataCoordinate` of the instrument `OverlayMarket` to a given value." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" }, "tags": [ "Market Data - Overwriting" ] }, "outputs": [], "source": [ "c_10y = MarketDataCoordinate.from_string('IR_EUR_SWAP_10Y.ATMRATE')\n", "\n", "print(f'Current value of the EUR 10yr swap point is {market[c_10y]}')\n", "\n", "market[c_10y] = -0.02\n", "\n", "print(f'New value of the EUR 10yr swap point is {market[c_10y]}')\n", "\n", "with PricingContext(market=market):\n", " price_f = swap.price()\n", "\n", "print(f'New price: {price_f.result()}')" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "... or pass in an new `OverlayMarket` all together! Here we create a bespoke market with our own values for the 3m5y \n", "implied volatility and 10y swap rate. Note that the values that are not overwritten will be defaulted to their original \n", "value." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" }, "tags": [ "Market Data - Overwriting" ] }, "outputs": [], "source": [ "from gs_quant.instrument import IRSwaption\n", "\n", "swaption = IRSwaption(PayReceive.Receive, '5y', Currency.EUR, expiration_date='3m')\n", "swaption.resolve()\n", "\n", "print(f'Base price: {swaption.price()}')\n", "\n", "vol_3m5y = MarketDataCoordinate.from_string('IR VOL_EUR-EURIBOR-TELERATE_SWAPTION_5Y,3M')\n", "market_data = {c_10y: 0.01, vol_3m5y: 40 / 1e4}\n", "new_market = OverlayMarket(market_data)\n", "\n", "with PricingContext(market=new_market):\n", " price_f = swaption.price()\n", "\n", "print(f'Price from new market data: {price_f.result()}')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/07_float-with-info.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "pycharm": { "is_executing": false, "name": "#%% md\n" } }, "source": [ "Risk calculation results in gs_quant extend the ResultInfo Class, which contain helpful information about \n", "the risk calculations themselves. \n", "\n", "While you don't need to use these datatypes directly, they contain information about the returned result that \n", "might be useful to you. One of these datatypes is 'FloatWithInfo'.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "from gs_quant.session import Environment, GsSession\n", "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwap" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "swap_5y = IRSwap(PayReceive.Receive, '5y', Currency.EUR, fixed_rate='atm+10')\n", "swap_10y = IRSwap(PayReceive.Receive, '10y', Currency.EUR, fixed_rate='atm+10')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": true, "name": "#%%\n" } }, "outputs": [], "source": [ "# When 'price' is called on an instrument, a 'FloatWithInfo' is returned\n", "res_5y = swap_5y.price()\n", "type(res_5y)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# This datatype has a few members - most simply, the raw value:\n", "res_5y.raw_value" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# The risk unit\n", "res_5y.unit" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# The risk_key, which helpfully provides information of what market, date, scenario and risk_measure were used\n", "# to produce the results\n", "res_5y.risk_key" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# You can add two instances of FloatWithInfo together when units are consistent\n", "res_10y = swap_10y.price()\n", "res_total = res_5y + res_10y\n", "type(res_total)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# You can also add FloatWithInfo and other types of objects, but units won't be checked\\n\",\n", "# For example, adding a FloatWithInfo and float returns a float\\n\",\n", "res_total = res_5y + res_10y.raw_value\n", "type(res_total)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "tags": [ "Results - FloatWithInfo" ], "version": "3.11.5" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/01_view_swap_definition.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Typing question mark after any object, function or method will return docstring\n", "IRSwap?" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/02_swap_trade_construction.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import Environment, GsSession\n", "from gs_quant.common import PayReceive, Currency, DayCountFraction, BusinessDayConvention\n", "from gs_quant.target.common import SwapClearingHouse\n", "from gs_quant.instrument import IRSwap\n", "from gs_quant.markets.portfolio import Portfolio\n", "from datetime import date" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Typing question mark after any object, function or method will return docstring\n", "IRSwap?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# get list of properties of an interest rate swap\n", "IRSwap.properties()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "swaps = Portfolio()\n", "\n", "# you don't need to specify any parameters to get a valid trade. All properties have defaults\n", "swaps.append(IRSwap())" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Enums - PayReceive" ] }, "outputs": [], "source": [ "# pay_or_receive can be a string of 'pay' or 'receive' or the PayReceive enum\n", "# and relates to paying or receiving the fixed leg. defaults to a receiver swap\n", "swaps.append(IRSwap(pay_or_receive=PayReceive.Pay))\n", "swaps.append(IRSwap(pay_or_receive='Pay'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# termination_date is the end date of the swap. It may be a tenor relative to effective_date or a datetime.date.\n", "# defaults to 10y\n", "swaps.append(IRSwap(termination_date=date(2025, 11, 12)))\n", "swaps.append(IRSwap(termination_date='1y'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Enums - Currency" ] }, "outputs": [], "source": [ "# notional currency may be a string or the Currency enum. defaults to USD\n", "swaps.append(IRSwap(notional_currency=Currency.USD))\n", "swaps.append(IRSwap(notional_currency='EUR'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# the effective date is the start date of the swap and may be a tenor relative\n", "# to the active PricingContext.pricing_date or a datetime.date, default is pricing date\n", "swaps.append(IRSwap(effective_date='1y'))\n", "swaps.append(IRSwap(effective_date=date(2019, 11, 12)))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Properties - Solving - Swap fixed rate" ] }, "outputs": [], "source": [ "# fixed_rate is the interest rate on the fixed leg of the swap. Defaults to Par Rate (ATM).\n", "# Can be expressed as 'ATM', 'ATM+25' for 25bp above par, a-100 for 100bp below par, 0.01 for 1%, can also be solved for a PV\n", "swaps.append(IRSwap(fixed_rate='ATM'))\n", "swaps.append(IRSwap(fixed_rate='ATM+50'))\n", "swaps.append(IRSwap(fixed_rate='a-100'))\n", "swaps.append(IRSwap(fixed_rate=0.01))\n", "swaps.append(IRSwap(fixed_rate='10000/pv'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# floating_rate_for_the_initial_calculation_period sets the first fixing on the trade.\n", "# It should be a float in absolute terms so 0.0075 is 75bp. Defaults to the value derived from fwd curve\n", "swaps.append(IRSwap(floating_rate_for_the_initial_calculation_period=0.0075))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Properties - floating_rate_option" ] }, "outputs": [], "source": [ "# floating rate option is the index that is being observed, defaults to LIBOR style index for each ccy,\n", "# 'OIS' will give the default overnight index for the notional ccy\n", "swaps.append(IRSwap(notional_currency='USD', floating_rate_option='USD-ISDA-SWAP RATE'))\n", "swaps.append(IRSwap(notional_currency='USD', floating_rate_option='USD-LIBOR-BBA'))\n", "swaps.append(IRSwap(notional_currency='EUR', floating_rate_option='EUR-EONIA-OIS-COMPOUND'))\n", "swaps.append(IRSwap(notional_currency='GBP', floating_rate_option='OIS'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# floating_rate_designated_maturity is the index term. defaults to the frequency of the floating leg\n", "swaps.append(IRSwap(notional_currency='GBP', floating_rate_designated_maturity='3m'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# floating_rate_spread is a float spread over the index. eg. pay euribor + 1%. defaults to 0\n", "swaps.append(IRSwap(pay_or_receive='receive', notional_currency='EUR', floating_rate_spread=0.01))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# floating_rate_frequency is the accrual frequency of the floating leg defined as a tenor.\n", "# It will drive the floating_rate_designated_maturity if that has not been independently set.\n", "# Defaults to ccy/tenor market standard defaults\n", "swaps.append(IRSwap(floating_rate_frequency='6m'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Enums - DayCountFraction" ] }, "outputs": [], "source": [ "# floating_rate_day_count_fraction can be the enum used here or a string. defaults to ccy market standard defaults\n", "swaps.append(IRSwap(floating_rate_day_count_fraction=DayCountFraction.ACT_OVER_365L_ISDA))\n", "swaps.append(IRSwap(floating_rate_day_count_fraction=DayCountFraction._30E_OVER_360))\n", "swaps.append(IRSwap(floating_rate_day_count_fraction='30/360'))\n", "swaps.append(IRSwap(floating_rate_day_count_fraction='ACT/360'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Enums - BusinessDayConvention" ] }, "outputs": [], "source": [ "# floating_rate_business_day_convention can be the enum used here a the equivalent string\n", "swaps.append(IRSwap(floating_rate_business_day_convention=BusinessDayConvention.Following))\n", "swaps.append(IRSwap(floating_rate_business_day_convention='Modified Following'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# fee is an amount paid. A positive fee will have a negative impact on the PV. Defaults to 0\n", "swaps.append(IRSwap(fee=50000))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# you can specify fee currency and fee date. trades where the fee is paid in a different currency to the\n", "# notional currency are supported. Default fee currency is notional currency\n", "# fee date can be a datetime.date or a tenor. Default fee date is spot dates from the PricingContext.pricing_date\n", "swaps.append(IRSwap(notional_currency=Currency.GBP, fee=50000, fee_currency=Currency.GBP, fee_payment_date='1y'))\n", "swaps.append(\n", " IRSwap(notional_currency=Currency.GBP, fee=1e5, fee_currency=Currency.USD, fee_payment_date=date(2020, 1, 30))\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Enums - SwapClearingHouse" ] }, "outputs": [], "source": [ "# valid clearinghouses are held in the SwapClearingHouse enum\n", "swaps.append(IRSwap(clearing_house=SwapClearingHouse.LCH))\n", "swaps.append(IRSwap(clearing_house=SwapClearingHouse.EUREX))\n", "swaps.append(IRSwap(clearing_house='CME'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# you can specify a name for a trade. This has no economic effect but is useful when extracting results\n", "# from a portfolio object\n", "swaps.append(IRSwap(PayReceive.Receive, '5y', 'gbp', name='GBP5y'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "swaps.price()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# you can express a swap as a dictionary\n", "swap = IRSwap(termination_date='10y', notional_currency='EUR', fixed_rate='ATM+50')\n", "swap_dict = swap.as_dict()\n", "swap_dict" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Instrument - create from dictionary" ] }, "outputs": [], "source": [ "# and you can construct a swap from a dictionary\n", "new_swap = IRSwap.from_dict(swap_dict)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "swap = IRSwap(effective_date='1y')\n", "swap.resolve()\n", "swap.as_dict()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/03_calc_swap_price.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from datetime import date\n", "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwap\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "swap = IRSwap(PayReceive.Receive, date(2055, 1, 15), Currency.EUR)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Metrics - Price" ] }, "outputs": [], "source": [ "swap.price()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/04_calc_swap_risk_measures.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwap\n", "from gs_quant.session import Environment, GsSession\n", "from gs_quant.risk import DollarPrice, IRDelta" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "swap = IRSwap(PayReceive.Pay, '10y', Currency.GBP)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Metrics" ] }, "outputs": [], "source": [ "result = swap.calc((DollarPrice, IRDelta(aggregation_level='Type')))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Results" ] }, "outputs": [], "source": [ "print(result) # all results" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Results" ] }, "outputs": [], "source": [ "print(result[IRDelta(aggregation_level='Type')]) # single measure" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/05_calc_swap_price_in_pricing_context.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwap\n", "from gs_quant.session import Environment, GsSession\n", "from gs_quant.markets import PricingContext\n", "from datetime import date" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "swap = IRSwap(PayReceive.Pay, '10y', notional_currency=Currency.GBP)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Contexts - PricingContext", "Market Data - location" ] }, "outputs": [], "source": [ "# price as of a specific date using nyc close\n", "with PricingContext(pricing_date=date(2019, 1, 15), market_data_location='NYC'):\n", " pv_f = swap.price()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(pv_f.result())" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/06_calc_swap_price_historically.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwap\n", "from gs_quant.markets import HistoricalPricingContext\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Instrument - Resolve" ] }, "outputs": [], "source": [ "# create 2 swaps and fix swap_b parameters by resolving. Swap_a parameters will resolve in HistoricalPricingContext\n", "swap_a = IRSwap(PayReceive.Pay, '30y', Currency.USD, fixed_rate='atm+5')\n", "swap_b = IRSwap(PayReceive.Pay, '30y', Currency.USD, fixed_rate='atm+5')\n", "swap_b.resolve()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Contexts - HistoricalPricingContext" ] }, "outputs": [], "source": [ "# HistoricalPricingContexts can take two dates or you can specify a number of historic business days as we do here\n", "with HistoricalPricingContext(150):\n", " res_a_f = swap_a.price()\n", " res_b_f = swap_b.price()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "res_a = res_a_f.result()\n", "res_b = res_b_f.result()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "\n", "plt.plot(res_a, 'g-', label='Rolling 30y GBP Payer struck atm+5bps')\n", "plt.plot(res_b, 'b-', label='30y GBP Payer struck atm+5bps over time')\n", "plt.xlabel('time')\n", "plt.ylabel('PV ($)')\n", "plt.title('PV over Time')\n", "plt.legend()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/07_calc_swap_risks_in_pricing_context.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwap\n", "from gs_quant.session import Environment, GsSession\n", "from gs_quant.risk import DollarPrice, IRDelta\n", "from gs_quant.markets import PricingContext\n", "from datetime import date" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "swap = IRSwap(PayReceive.Pay, '12y', notional_currency=Currency.GBP)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Metrics - aggregation level", "Metrics" ] }, "outputs": [], "source": [ "# if you set an aggregation level for a risk ladder like IRDelta it will change to be filtered at the market coordinate level\n", "# levels are: type, asset, class, point and quoting style. type is at the metric level.\n", "# in this case when setting the aggregation level to Type the model will do a simple parallel shift rather than calculating\n", "# the perturbated risk and aggregating.\n", "\n", "with PricingContext(pricing_date=date(2019, 1, 15)):\n", " res_f = swap.calc((DollarPrice, IRDelta, IRDelta(aggregation_level='Type')))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(res_f.result()) # retrieve all measures" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Results - Retrieve Specific Measure" ] }, "outputs": [], "source": [ "print(res_f[DollarPrice]) # retrieve specific measure" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/08_calc_swap_risk_historically.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwap\n", "from gs_quant.session import Environment, GsSession\n", "from gs_quant.risk import DollarPrice, IRDelta\n", "from gs_quant.common import AggregationLevel\n", "from gs_quant.markets import HistoricalPricingContext\n", "from datetime import date" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "swap_10bps = IRSwap(PayReceive.Receive, '5y', Currency.EUR, fixed_rate='atm+10')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Contexts - HistoricalPricingContext", "Metrics - aggregation_level" ] }, "outputs": [], "source": [ "with HistoricalPricingContext(date(2020, 3, 2), date(2020, 4, 1), show_progress=True):\n", " res_f = swap_10bps.calc((DollarPrice, IRDelta, IRDelta(aggregation_level=AggregationLevel.Type, currency='local')))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(res_f.result()) # retrieve all results" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Results - historical results" ] }, "outputs": [], "source": [ "print(res_f[DollarPrice]) # retrieve historical prices" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/09_swaption_trade_construction.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from datetime import date\n", "\n", "from gs_quant.common import PayReceive\n", "from gs_quant.instrument import IRSwap, IRSwaption\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.session import Environment, GsSession\n", "from gs_quant.common import SwapSettlement" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "swaptions = Portfolio()\n", "\n", "# you don't need to specify any parameters to get a valid trade. All properties have defaults\n", "swaptions.append(IRSwaption())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# get list of properties of an interest rate swaption\n", "# Many of these properties overlap with the IRSwap properties (outlined in example '010001_swap_trade_construction')\n", "IRSwaption.properties()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Enums - PayReceive" ] }, "outputs": [], "source": [ "# pay_or_receive can be a string of 'pay', 'receive', 'straddle' (an option strategy\n", "# where you enter into both a payer and receiver) or the PayReceive enum\n", "# relates to whether you expect to pay/receive fixed for the underlying. default is 'straddle'\n", "swaptions.append(IRSwaption(pay_or_receive=PayReceive.Receive))\n", "swaptions.append(IRSwaption(pay_or_receive='Receive'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# expiration_date is the date the option expires and may be a tenor relative\n", "# to the active PricingContext.pricing_date or a datetime.date, default is '10y'\n", "swaptions.append(IRSwaption(expiration_date='6m'))\n", "swaptions.append(IRSwaption(expiration_date=date(2022, 2, 12)))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Instrument Properties - Solving - IRSwaption strike" ] }, "outputs": [], "source": [ "# strike is the rate at which the option can be exercised\n", "# It also represents the interest rate on the fixed leg of the swap if the swaption expires ITM. Defaults to Par Rate (ATM).\n", "# Can be expressed as 'ATM', 'ATM+25' for 25bp above par, a-100 for 100bp below par, 0.01 for 1%, you can also solve\n", "# for a specific delta or pv\n", "swaptions.append(IRSwaption(strike='ATM'))\n", "swaptions.append(IRSwaption(strike='ATM+50'))\n", "swaptions.append(IRSwaption(strike='a-100'))\n", "swaptions.append(IRSwaption(strike=0.02))\n", "swaptions.append(IRSwaption(strike='10000/pv', notional_amount=10000))\n", "swaptions.append(IRSwaption(strike='25d', notional_amount=10000))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# effective_date is the start date of the underlying swap and may be a tenor relative\n", "# to the expiration_date or a datetime.date. Default is spot dates from expiration\n", "# For example, for a swaption w/ notional_currency as GBP, spot date is T+0, so effective_date = expiration_date.\n", "# for a swaption w/ notional_currency USD, spot is T+2 days and the effective_date is 2b after expiration_date\n", "swaptions.append(IRSwaption(effective_date='5b'))\n", "swaptions.append(IRSwaption(expiration_date=date(2031, 2, 10), effective_date=date(2031, 2, 12)))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# An IRSwaption's strike will resolve to an IRSwap's fixed_rate if the swaps' paramaters match and\n", "# the swaption's effective_date is equivalent to the swap's effective_date\n", "swap = IRSwap(notional_currency='GBP', effective_date='10y')\n", "swap.resolve()\n", "swaption = IRSwaption(notional_currency='GBP', expiration_date='10y', effective_date='0b')\n", "swaption.resolve()\n", "print(swap.fixed_rate * 100)\n", "print(swaption.strike * 100)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Enums - PayReceive" ] }, "outputs": [], "source": [ "# settlement is the settlement convention of the swaption and can be a string of:\n", "# 'Phys.CLEARED' (enter into cleared swap), 'Cash.PYU' (PYU - Par Yield Unadjusted,\n", "# cash payment calculated with PYU), 'Physical' (enter into a uncleared swap),\n", "# 'Cash.CollatCash' (collateralized, cash settled at expiry) or the SwapSettlement enum\n", "swaptions.append(IRSwaption(settlement=SwapSettlement.Phys_CLEARED))\n", "swaptions.append(IRSwaption(settlement='Cash.PYU'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# premium is the amount to be exchanged for the option contract. A positive premium will have a\n", "# negative impact on the PV. premium is a default is 0.\n", "swaptions.append(IRSwaption(premium=1e4))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# premium_payment_date is the date when premium is exchanged.\n", "# premium_payment_date can be a datetime.date or a tenor. defaulted to spot dates from the\n", "# PricingContext.pricing_date\n", "swaptions.append(IRSwaption(premium_payment_date='5d'))\n", "swaptions.append(IRSwaption(premium_payment_date=date(2020, 2, 13)))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# in some markets, the convention is for premium to be exchanged at expiration\n", "# this can be expressed by changing the premium_payment_date to the swaption's expiration_date\n", "swaptions.append(IRSwaption(premium_payment_date='5y', expiration_date='5y'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "swaptions.price().to_frame()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "tags": [ "Instrument - IRSwaption" ], "version": "3.11.5" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/10_straddle_price.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import PayReceive\n", "from gs_quant.instrument import IRSwaption\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# A swaption straddle represents the purchase of a payer swaption and\n", "# receiver swaption with the same strike and expiration_date\n", "straddle = IRSwaption(pay_or_receive='Straddle')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The price of a straddle is sum of the price of the payer and receiver swaptions\n", "payer = IRSwaption(pay_or_receive=PayReceive.Pay)\n", "receiver = IRSwaption(pay_or_receive=PayReceive.Receive)\n", "\n", "print(straddle.price())\n", "print(payer.price() + receiver.price())" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "tags": [ "Instrument - Swaption Straddle" ], "version": "3.11.5" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/11_midcurve_swaption_price.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import Environment, GsSession\n", "from gs_quant.instrument import IRSwaption" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Construct a midcurve swaption: 6m 2y1y\n", "# expiration_date='6m' - option expires in 6m\n", "# effective_date='2y' - swap starting 2y after expiry (2.5y after trade)\n", "# termination_date='1y' - swap tenor is 1y (swap matures 3.5y post trade)\n", "midcurve = IRSwaption(pay_or_receive='Pay', expiration_date='6m', effective_date='2y', termination_date='1y')\n", "midcurve.price()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# expiration_date can also be specified as IMM date\n", "midcurve = IRSwaption(pay_or_receive='Pay', expiration_date='IMM1', effective_date='2y', termination_date='1y')\n", "midcurve.price()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } }, "tags": [ "Instrument - Rates Midcurve Swaption" ] }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/12_swap_future_cashflows.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import Environment, GsSession\n", "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwap\n", "from gs_quant.risk import Cashflows" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "swap = IRSwap(PayReceive.Receive, '5y', Currency.EUR, fixed_rate='atm+10')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Metrics - Cashflows" ] }, "outputs": [], "source": [ "# returns a dataframe of future cashflows\n", "# note this feature will be expanded to cover portfolios in future releases\n", "swap.calc(Cashflows)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/13_calc_xccy_swap_price.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import Currency\n", "from gs_quant.instrument import IRXccySwap, IRXccySwapFltFlt\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# IRXccySwap is a MTM cross currency swap. IRXccySwapFltFlt doesn't exchange on each payment date\n", "\n", "# spreads are absolute so 0.0005 is 5bp\n", "xswap = IRXccySwap(\n", " payer_currency=Currency.EUR,\n", " receiver_currency=Currency.USD,\n", " effective_date='3m',\n", " termination_date='10y',\n", " payer_spread=-0.0005,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(xswap.price())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "xswap_mtm = IRXccySwapFltFlt(\n", " payer_currency=Currency.EUR,\n", " receiver_currency=Currency.USD,\n", " effective_date='3m',\n", " termination_date='10y',\n", " payer_spread=-0.0005,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(xswap_mtm.price())" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "tags": [ "Instrument - XCcy Swap" ] }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/14_calc_xccy_swap_risk_measures.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import Currency\n", "from gs_quant.instrument import IRXccySwap\n", "from gs_quant.risk import IRDelta, IRXccyDelta, IRXccyDeltaParallel\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "xswap = IRXccySwap(\n", " payer_currency=Currency.EUR, receiver_currency=Currency.USD, effective_date='3m', termination_date='10y'\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Metrics - XCcy" ] }, "outputs": [], "source": [ "delta = xswap.calc((IRDelta, IRXccyDelta))\n", "parallel_delta = xswap.calc(IRXccyDeltaParallel)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(delta) # all results for delta" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(parallel_delta) # all results for parallel delta" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/15_spread_option_grid_pricing.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import IRCMSSpreadOption\n", "from gs_quant.markets import PricingContext\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.risk import IRAnnualImpliedVol, Price\n", "from gs_quant.session import Environment, GsSession\n", "import pandas as pd\n", "\n", "pd.options.display.float_format = '{:,.4f}'.format" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Instrument - IRCMSSpreadOption", "Portfolio - Nested Portfolios" ] }, "outputs": [], "source": [ "# define a set of pairs and option expiries\n", "pairs = [('5y', '2y'), ('10y', '2y')]\n", "expiries = ['3m', '6m', '1y', '2y', '5y', '10y']\n", "portfolios = Portfolio(\n", " [\n", " Portfolio(\n", " [\n", " IRCMSSpreadOption(\n", " termination_date=e,\n", " notional_currency='EUR',\n", " notional_amount=10000,\n", " index1_tenor=p[0],\n", " index2_tenor=p[1],\n", " name=e,\n", " )\n", " for e in expiries\n", " ],\n", " name=p,\n", " )\n", " for p in pairs\n", " ]\n", ")\n", "\n", "# price our list of portfolios\n", "with PricingContext():\n", " result_p = portfolios.calc(Price)\n", " result_v = portfolios.calc(IRAnnualImpliedVol)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "prices = result_p.to_frame()\n", "prices" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "vols = result_v.to_frame() * 10000\n", "vols" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/16_change_discount_curve.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwaption\n", "from gs_quant.session import Environment, GsSession\n", "from gs_quant.markets import PricingContext\n", "import datetime as dt" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "swaption = IRSwaption(PayReceive.Receive, '5y', Currency.EUR, settlement='Cash.CollatCash')\n", "swaption.resolve()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "CSA" ] }, "outputs": [], "source": [ "# price using default OIS rate\n", "with PricingContext(pricing_date=dt.date.today(), csa_term='EUR-OIS'):\n", " price = swaption.price()\n", "\n", "print(price.result())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# price using ESTR\n", "with PricingContext(pricing_date=dt.date.today(), csa_term='EUR-EuroSTR'):\n", " price = swaption.price()\n", "\n", "print(price.result())" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/17_calc_xccy_swap_cashflows.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import IRXccySwap, IRXccySwapFixFix\n", "from gs_quant.risk import Cashflows\n", "from gs_quant.session import Environment, GsSession\n", "\n", "# external users should substitute their client id and secret\n", "client_id = None # Supply your application id\n", "client_secret = None # Supply your client secret\n", "\n", "GsSession.use(Environment.PROD, client_id=client_id, client_secret=client_secret, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Create a Xccy float vs float Swap and a Xccy fix vs fix swap" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "tags": [ "Instrument - XccySwap" ] }, "outputs": [], "source": [ "mtm_swap = IRXccySwap(payer_currency='EUR', receiver_currency='USD', effective_date='3m', termination_date='10y')\n", "fix_swap = IRXccySwapFixFix(\n", " payer_currency='EUR', receiver_currency='USD', termination_date='10y', payer_rate=0.01, receiver_rate=0.015\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Compute cashflows for the 10y EURUSD fix swap " ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "tags": [ "Metrics - Cashflows" ] }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
currencypayment_dateset_dateaccrual_start_dateaccrual_end_datepayment_amountnotionalpayment_typefloating_rate_optionfloating_rate_designated_maturityday_count_fractionspreadratediscount_factor
0EUR2025-07-07None2025-04-022025-07-02-250000.0100000000.0FIXNANA0.25None0.010.993976
1EUR2025-10-06None2025-07-022025-10-02-250000.0100000000.0FIXNANA0.25None0.010.989072
2EUR2026-01-06None2025-10-022026-01-02-250000.0100000000.0FIXNANA0.25None0.010.984584
3EUR2026-04-08None2026-01-022026-04-02-250000.0100000000.0FIXNANA0.25None0.010.980044
4EUR2026-07-06None2026-04-022026-07-02-250000.0100000000.0FIXNANA0.25None0.010.975711
\n", "
" ], "text/plain": [ " currency payment_date set_date accrual_start_date accrual_end_date \\\n", "0 EUR 2025-07-07 None 2025-04-02 2025-07-02 \n", "1 EUR 2025-10-06 None 2025-07-02 2025-10-02 \n", "2 EUR 2026-01-06 None 2025-10-02 2026-01-02 \n", "3 EUR 2026-04-08 None 2026-01-02 2026-04-02 \n", "4 EUR 2026-07-06 None 2026-04-02 2026-07-02 \n", "\n", " payment_amount notional payment_type floating_rate_option \\\n", "0 -250000.0 100000000.0 FIX NA \n", "1 -250000.0 100000000.0 FIX NA \n", "2 -250000.0 100000000.0 FIX NA \n", "3 -250000.0 100000000.0 FIX NA \n", "4 -250000.0 100000000.0 FIX NA \n", "\n", " floating_rate_designated_maturity day_count_fraction spread rate \\\n", "0 NA 0.25 None 0.01 \n", "1 NA 0.25 None 0.01 \n", "2 NA 0.25 None 0.01 \n", "3 NA 0.25 None 0.01 \n", "4 NA 0.25 None 0.01 \n", "\n", " discount_factor \n", "0 0.993976 \n", "1 0.989072 \n", "2 0.984584 \n", "3 0.980044 \n", "4 0.975711 " ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cf_fix = fix_swap.calc(Cashflows)\n", "cf_fix.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Compute cashflows for the forward starting 10y EURUSD float float swap" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
currencypayment_dateset_dateaccrual_start_dateaccrual_end_datepayment_amountnotionalpayment_typefloating_rate_optionfloating_rate_designated_maturityday_count_fractionspreadratediscount_factor
0EUR2025-10-062025-07-022025-07-022025-10-02-501686.798657100000000.0FltEUR-EuroSTR-COMPOUND3m0.255556-0.0002040.0198350.989072
1EUR2026-01-062025-10-022025-10-022026-01-02-471412.499356100000000.0FltEUR-EuroSTR-COMPOUND3m0.255556-0.0002040.0186510.984584
2EUR2026-04-082026-01-022026-01-022026-04-02-446566.760037100000000.0FltEUR-EuroSTR-COMPOUND3m0.250000-0.0002040.0180670.980044
3EUR2026-07-062026-04-022026-04-022026-07-02-451174.776800100000000.0FltEUR-EuroSTR-COMPOUND3m0.252778-0.0002040.0180530.975711
4EUR2026-10-062026-07-022026-07-022026-10-02-464690.352983100000000.0FltEUR-EuroSTR-COMPOUND3m0.255556-0.0002040.0183870.971167
\n", "
" ], "text/plain": [ " currency payment_date set_date accrual_start_date accrual_end_date \\\n", "0 EUR 2025-10-06 2025-07-02 2025-07-02 2025-10-02 \n", "1 EUR 2026-01-06 2025-10-02 2025-10-02 2026-01-02 \n", "2 EUR 2026-04-08 2026-01-02 2026-01-02 2026-04-02 \n", "3 EUR 2026-07-06 2026-04-02 2026-04-02 2026-07-02 \n", "4 EUR 2026-10-06 2026-07-02 2026-07-02 2026-10-02 \n", "\n", " payment_amount notional payment_type floating_rate_option \\\n", "0 -501686.798657 100000000.0 Flt EUR-EuroSTR-COMPOUND \n", "1 -471412.499356 100000000.0 Flt EUR-EuroSTR-COMPOUND \n", "2 -446566.760037 100000000.0 Flt EUR-EuroSTR-COMPOUND \n", "3 -451174.776800 100000000.0 Flt EUR-EuroSTR-COMPOUND \n", "4 -464690.352983 100000000.0 Flt EUR-EuroSTR-COMPOUND \n", "\n", " floating_rate_designated_maturity day_count_fraction spread rate \\\n", "0 3m 0.255556 -0.000204 0.019835 \n", "1 3m 0.255556 -0.000204 0.018651 \n", "2 3m 0.250000 -0.000204 0.018067 \n", "3 3m 0.252778 -0.000204 0.018053 \n", "4 3m 0.255556 -0.000204 0.018387 \n", "\n", " discount_factor \n", "0 0.989072 \n", "1 0.984584 \n", "2 0.980044 \n", "3 0.975711 \n", "4 0.971167 " ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mtm_swap.calc(Cashflows).head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Clone the float float swap keeping spread constant but modifying the rate applied at initiation" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "tags": [ "Instrument - Cloning" ] }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
currencypayment_dateset_dateaccrual_start_dateaccrual_end_datepayment_amountnotionalpayment_typefloating_rate_optionfloating_rate_designated_maturityday_count_fractionspreadratediscount_factor
0EUR2025-10-062025-07-022025-07-022025-10-02-501685.517234100000000.0FltEUR-EuroSTR-COMPOUND3m0.255556-0.0002040.0198350.989072
1EUR2026-01-062025-10-022025-10-022026-01-02-471411.217933100000000.0FltEUR-EuroSTR-COMPOUND3m0.255556-0.0002040.0186510.984584
2EUR2026-04-082026-01-022026-01-022026-04-02-446565.506472100000000.0FltEUR-EuroSTR-COMPOUND3m0.250000-0.0002040.0180670.980044
3EUR2026-07-062026-04-022026-04-022026-07-02-451173.509306100000000.0FltEUR-EuroSTR-COMPOUND3m0.252778-0.0002040.0180530.975711
4EUR2026-10-062026-07-022026-07-022026-10-02-464689.071560100000000.0FltEUR-EuroSTR-COMPOUND3m0.255556-0.0002040.0183870.971167
\n", "
" ], "text/plain": [ " currency payment_date set_date accrual_start_date accrual_end_date \\\n", "0 EUR 2025-10-06 2025-07-02 2025-07-02 2025-10-02 \n", "1 EUR 2026-01-06 2025-10-02 2025-10-02 2026-01-02 \n", "2 EUR 2026-04-08 2026-01-02 2026-01-02 2026-04-02 \n", "3 EUR 2026-07-06 2026-04-02 2026-04-02 2026-07-02 \n", "4 EUR 2026-10-06 2026-07-02 2026-07-02 2026-10-02 \n", "\n", " payment_amount notional payment_type floating_rate_option \\\n", "0 -501685.517234 100000000.0 Flt EUR-EuroSTR-COMPOUND \n", "1 -471411.217933 100000000.0 Flt EUR-EuroSTR-COMPOUND \n", "2 -446565.506472 100000000.0 Flt EUR-EuroSTR-COMPOUND \n", "3 -451173.509306 100000000.0 Flt EUR-EuroSTR-COMPOUND \n", "4 -464689.071560 100000000.0 Flt EUR-EuroSTR-COMPOUND \n", "\n", " floating_rate_designated_maturity day_count_fraction spread rate \\\n", "0 3m 0.255556 -0.000204 0.019835 \n", "1 3m 0.255556 -0.000204 0.018651 \n", "2 3m 0.250000 -0.000204 0.018067 \n", "3 3m 0.252778 -0.000204 0.018053 \n", "4 3m 0.255556 -0.000204 0.018387 \n", "\n", " discount_factor \n", "0 0.989072 \n", "1 0.984584 \n", "2 0.980044 \n", "3 0.975711 \n", "4 0.971167 " ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mtm_swap = mtm_swap.clone(initial_fx_rate=1.2, payer_spread=mtm_swap.payer_spread)\n", "mtm_swap.calc(Cashflows).head()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/18_solve_present_value.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import IRSwaption\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.session import GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Solve for Present Value\n", "Using 1x2 receiver structure - solve for strike on second leg such that pv of leg2 = pv of leg1\n", "\n", "This example takes advantage of intra-portfolio formulae. You could do this in two API requests with:\n", "```python\n", "swaption_1 = IRSwaption( ... )\n", "price = swaption_1.price()\n", "swaption_2 = IRSwaption( ..., strike=f'{price}/pv', ... )\n", "zero_cost_portfolio = Portfolio((swaption_1, swaption_2))\n", "```" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%# buy 1x atmf payer, sell 2x atmf+X payer solving for 0 cost strike\n" }, "tags": [ "Instrument - Solving across multiple instruments" ] }, "outputs": [], "source": [ "zero_cost_portfolio = Portfolio(\n", " (\n", " IRSwaption(\n", " 'Receive',\n", " '30y',\n", " 'USD',\n", " notional_amount=10e6,\n", " expiration_date='3m',\n", " strike='atmf',\n", " buy_sell='Buy',\n", " name='30y_buy',\n", " ),\n", " IRSwaption(\n", " 'Receive',\n", " '30y',\n", " 'USD',\n", " notional_amount=20e6,\n", " expiration_date='3m',\n", " strike='=solvefor([30y_buy].risk.Price,pv)',\n", " buy_sell='Sell',\n", " ),\n", " )\n", ")\n", "\n", "# see the strikes and prices\n", "print([s.strike * 1e4 for s in zero_cost_portfolio.resolve(in_place=False)])\n", "print([s for s in zero_cost_portfolio.price()])" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/19_solve_delta.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import IRSwaption, IRSwap\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.risk import IRDelta\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Solve for Delta\n", "Size swap to match swaption delta" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Instrument - Solving - Size one trade by the risk of another trade" ] }, "outputs": [], "source": [ "port = Portfolio(\n", " (\n", " IRSwaption('Pay', '5y', 'USD', expiration_date='1y', buy_sell='Sell', name='payer_swaption'),\n", " IRSwap(\n", " 'Receive',\n", " '5y',\n", " 'USD',\n", " fixed_rate='atm',\n", " notional_amount='=solvefor([payer_swaption].risk.IRDeltaParallel,bp)',\n", " name='hedge1',\n", " ),\n", " )\n", ")\n", "port.resolve()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "port_delta = port.calc(IRDelta(aggregation_level='Type'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# check that delta is approximately equal\n", "port_delta['payer_swaption'] - port_delta['hedge1']" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/20_fix_float_legs_price.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import IRFixedLeg, IRFloatLeg, IRSwap\n", "from gs_quant.markets import PricingContext\n", "from gs_quant.session import GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Instrument - IRFixedLeg", "Instrument - IRFloatLeg" ] }, "outputs": [], "source": [ "fixed_leg = IRFixedLeg('Buy', fixed_rate=0.05, notional_currency='USD', termination_date='5y', notional_amount=100e6)\n", "\n", "float_leg = IRFloatLeg(\n", " 'Sell', notional_currency='USD', termination_date='5y', floating_rate_spread=40 / 1e4, notional_amount=100e6\n", ")\n", "\n", "swap = IRSwap(\n", " pay_or_receive='Receive',\n", " termination_date='5y',\n", " notional_currency='USD',\n", " fixed_rate=0.05,\n", " floating_rate_spread=40 / 1e4,\n", " principal_exchange='None',\n", " notional_amount=100e6,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "CSA" ] }, "outputs": [], "source": [ "with PricingContext(csa_term='USD-SOFR'):\n", " fixed_price = fixed_leg.price()\n", " float_price = float_leg.price()\n", " swap_price = swap.price()\n", "\n", "print(fixed_price.result() + float_price.result())\n", "print(swap_price.result())" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/21_asset_swap_definition.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import Environment, GsSession\n", "from gs_quant.instrument import IRAssetSwapFxdFlt" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Instrument - IRAssetSwapFxdFlt" ] }, "outputs": [], "source": [ "# The default instrument is a 0 PV asset swap on a US10Y Bond, which price is defaulted to 100\n", "default = IRAssetSwapFxdFlt()\n", "default.resolve()\n", "default.as_dict()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "default.price()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# We may change the underlier bond and price which will resolve the other attributes accordingly\n", "asset_swap = IRAssetSwapFxdFlt(identifier='GB5Y', traded_clean_price=99)\n", "asset_swap.resolve()\n", "asset_swap.as_dict()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "asset_swap.price()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/22_cap_floor.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 99, "metadata": { "ExecuteTime": { "end_time": "2025-07-21T12:55:44.710864900Z", "start_time": "2025-07-21T12:55:44.699341200Z" } }, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "from gs_quant.instrument import IRCapFloor, IRCap, IRFloor" ] }, { "cell_type": "code", "execution_count": 82, "metadata": { "ExecuteTime": { "end_time": "2025-07-21T12:47:57.166676100Z", "start_time": "2025-07-21T12:47:55.177673500Z" } }, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Instrument - IRAssetSwapFxdFlt" ] }, "outputs": [], "source": [ "# Create an interest rate cap using IRCapFloor, setting cap_floor to \"Cap\"\n", "cap = IRCapFloor(\n", " cap_floor=\"Cap\",\n", " notional_currency=\"USD\",\n", " notional_amount=1000,\n", " effective_date='2025-07-18',\n", " termination_date='2030-07-18',\n", " floating_rate_option='USD-LIBOR-BBA',\n", " floating_rate_designated_maturity='3m',\n", " floating_rate_frequency='3m',\n", " strike=0.015, # 1. 5% floor rate\n", ")\n", "cap.resolve(in_place=False).to_dict()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "cap.price()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "Compared that with the output of IRCap, we have similar result." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "ircap = IRCap(\n", " notional_currency=\"USD\",\n", " notional_amount=1000,\n", " effective_date='2025-07-15',\n", " termination_date='2030-07-15',\n", " floating_rate_option='USD-LIBOR-BBA',\n", " floating_rate_designated_maturity='3m',\n", " floating_rate_frequency='3m',\n", " cap_rate=0.015, # 1.5% cap rate\n", ")\n", "ircap.resolve(in_place=False).to_dict()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "ircap.price()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Create an interest rate cap using IRCapFloor, setting cap_floor to \"Floor\"\n", "floor = IRFloor(\n", " notional_currency=\"USD\",\n", " notional_amount=1000,\n", " effective_date='2025-07-15',\n", " termination_date='2030-07-15',\n", " floating_rate_option='USD-LIBOR-BBA',\n", " floating_rate_designated_maturity='3m',\n", " floating_rate_frequency='3m',\n", " floor_rate=0.015, # 1.5% floor rate\n", ")\n", "floor.resolve(in_place=False).to_dict()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "floor.price()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Compare output with IRFloor function\n", "irfloor = IRFloor(\n", " notional_currency=\"USD\",\n", " notional_amount=1000,\n", " effective_date='2025-07-15',\n", " termination_date='2030-07-15',\n", " floating_rate_option='USD-LIBOR-BBA',\n", " floating_rate_designated_maturity='3m',\n", " floating_rate_frequency='3m',\n", " floor_rate=0.015, # 1.5% cap rate\n", ")\n", "irfloor.resolve(in_place=False).to_dict()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "irfloor.price()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.1" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/23_solve_vanna_&_volga.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": { "ExecuteTime": { "end_time": "2025-08-11T12:19:09.294055700Z", "start_time": "2025-08-11T12:19:03.376662500Z" } }, "outputs": [], "source": [ "from gs_quant.instrument import IRSwaption, IRSwap, IRCapFloor\n", "from gs_quant.common import AggregationLevel\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.risk import IRVanna, IRVolga\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "pycharm": { "name": "#%%\n" }, "ExecuteTime": { "end_time": "2025-08-11T12:19:16.393303500Z", "start_time": "2025-08-11T12:19:11.738322600Z" } }, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Solve for Vanna and Volga\n", "Size swap to match swaption vanna" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "tags": [ "Instrument - Solving - Size one trade by the risk of another trade" ], "ExecuteTime": { "end_time": "2025-08-11T12:19:21.346721500Z", "start_time": "2025-08-11T12:19:18.268449900Z" } }, "outputs": [], "source": [ "port = Portfolio(\n", " (\n", " IRSwaption('Pay', '5y', 'USD', expiration_date='1y', buy_sell='Sell', name='payer_swaption'),\n", " IRSwap(\n", " 'Receive',\n", " '5y',\n", " 'USD',\n", " fixed_rate='atm',\n", " notional_amount='=solvefor([payer_swaption].risk.IRDeltaParallel,bp)',\n", " name='hedge1',\n", " ),\n", " )\n", ")\n", "port.resolve()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "port.calc(IRVanna).to_frame()" ] }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "port.calc(IRVolga).to_frame()" ], "metadata": { "collapsed": false } }, { "cell_type": "markdown", "source": [ "Swaption (return) is non-linear (to interest change), that's why we can calculate Vanna and Volga for it, while IRSwap is linear and have it as zero." ], "metadata": { "collapsed": false } }, { "cell_type": "markdown", "source": [ "We can also check IRVanna and IRVolga with IRCapFloor instruments, which return new options for every defined period. " ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "cap = IRCapFloor(\n", " cap_floor=\"Cap\",\n", " notional_currency=\"USD\",\n", " notional_amount=1000,\n", " effective_date='2025-07-18',\n", " termination_date='2030-07-18',\n", " floating_rate_option='USD-LIBOR-BBA',\n", " floating_rate_designated_maturity='3m',\n", " floating_rate_frequency='3m',\n", " strike=0.015, # 1. 5% floor rate\n", ")\n", "cap.resolve(in_place=False).to_dict()" ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "cap.calc(IRVanna)" ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "cap.calc(IRVanna(aggregation_level=AggregationLevel.Type))" ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "cap.calc(IRVolga)" ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "cap.calc(IRVolga(aggregation_level=AggregationLevel.Type))" ], "metadata": { "collapsed": false } } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/01_fx_fwd_trade_construction.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import Environment, GsSession\n", "from gs_quant.instrument import FXForward\n", "from gs_quant.markets.portfolio import Portfolio\n", "from datetime import date\n", "import pandas as pd" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# get list of properties of an fx forward\n", "FXForward.properties()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# in this example we will construct and price a portfolio of FXForwards\n", "fx_fwds = Portfolio()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# pair is the forward's currency pair. It a string of two ccy iso codes, optionally separated with a space (' ')\n", "# The first currency is the base currency and the second is the quote currency\n", "\n", "# In this case, base currency is 'EUR' and quote currency is 'GBP'.\n", "fx_fwds.append(FXForward(pair='EUR GBP', settlement_date='3y'))\n", "fx_fwds.append(FXForward(pair='EURGBP', settlement_date='3y'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# notional_amount is the quantity of the base currency to be exchanged in the future; it can be a string (eg: '100m')\n", "# or a double (10e8)\n", "\n", "# For these two forwards - some amount of GBP will be exchanged for 100mm EUR at a future date\n", "fx_fwds.append(FXForward(pair='EURGBP', notional_amount='100m', settlement_date='3y'))\n", "fx_fwds.append(FXForward(pair='EURGBP', notional_amount=10e8, settlement_date='3y'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# settlement_date is the contract settlement date. It can either be a date or string\n", "fx_fwds.append(FXForward(settlement_date='3y'))\n", "fx_fwds.append(FXForward(settlement_date=date(2023, 9, 25)))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# forward rate is the exchange rate which will be used on the settlement date. It is a double.\n", "# If not set then it will resolve to the fair fx fwd rate\n", "\n", "# In this example, the individual long the FXForward will pay 95mm GBP in exchange for 100mm EUR on the settlement date\n", "fx_fwds.append(FXForward(pair='EURGBP', notional_amount=10e8, forward_rate=0.95, settlement_date='3y'))\n", "\n", "# If not set then it will resolve to the fair fx fwd rate\n", "fx_fwds.append(FXForward(pair='EURGBP', notional_amount=10e8, settlement_date='3y'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pd.DataFrame(fx_fwds.price())" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "tags": [ "Instrument - FXForward" ] }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/02_fx_option_trade_construction.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": false } }, "outputs": [], "source": [ "from gs_quant.session import Environment, GsSession\n", "from gs_quant.instrument import FXOption\n", "from gs_quant.markets.portfolio import Portfolio\n", "import pandas as pd" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# get list of properties of an fx option\n", "FXOption.properties()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# in this example we will construct and price a portfolio of FXOptions\n", "fx_options = Portfolio()\n", "\n", "# you don't need to specify any parameters to get a valid trade. All properties have defaults\n", "fx_options.append(FXOption())" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# buy_sell indicates whether the option is bought (long) or sold (short)\n", "# It can be represented by the BuySell enum or a string\n", "fx_options.append(FXOption(buy_sell='Buy'))\n", "\n", "from gs_quant.common import BuySell\n", "\n", "fx_options.append(FXOption(buy_sell=BuySell.Sell))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# put_call indicates whether the option is a put or call; it can be represented by the OptionType enum or a string\n", "fx_options.append(FXOption(option_type='Put'))\n", "\n", "from gs_quant.common import OptionType\n", "\n", "fx_options.append(FXOption(option_type=OptionType.Call))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# pair is the FXOption's underlying currency pair. It is a string of two ccy iso codes, optionally separated\n", "# with a space (' '). The first currency is the base (transaction) currency and the second is the quote currency\n", "\n", "# In this case, base currency is 'EUR' and quote currency is 'GBP'\n", "# This option gives the purchasor the option to buy EUR at expiration\n", "fx_options.append(FXOption(buy_sell=BuySell.Buy, option_type=OptionType.Call, pair='EUR GBP'))\n", "\n", "# Here, base currency is 'GBP' and quote currency is 'USD'\n", "# This option gives the purchasor the option to sell GBP at expiration\n", "fx_options.append(FXOption(buy_sell=BuySell.Buy, option_type=OptionType.Put, pair='GBPUSD'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# notional_currency is the notional amount's denominated currency\n", "# It can be a Currency enum or a string. By default, notional_currency will match the base currency\n", "\n", "# In this case, notional_currency of a EURGBP Call is EUR.\n", "from gs_quant.common import Currency\n", "\n", "fx_options.append(FXOption(option_type=OptionType.Call, pair='EUR GBP', notional_currency=Currency.EUR))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# notional_amount is the quantity of the base currency to be exchanged in the future; it can be a string (eg: '100mm')\n", "# or a double (10e8). By definition, notional_amount_other_currency is the notional amount in the quoted currency. It can be a Currency enum or a string\n", "# by default, notional_amount_other_currency will be the product of 'notional_amount' and 'strike_price'\n", "\n", "# Here, a EURGBP Call has a notional of 10mm EUR\n", "fx_options.append(\n", " FXOption(option_type=OptionType.Call, pair='EUR GBP', notional_currency=Currency.EUR, notional_amount='10m')\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "\"\"\"\n", "strike_price is the exchange rate stuck for the FXOption's underlying currency pair, which can be specified by a double \n", "or a string. If a string is used, it represents a relative value. When the trade is resolved, we solve for the strike_price \n", "\n", "The specific solver keys are: \n", " - 'S' - current spot rate\n", " - 'F' - forward\n", " - 'ATM' - At the Money \n", " - 'ATMF' - At the Money Forward\n", " - 'D' - Delta Strikes\n", " - 'P' - Premium\n", "\n", "You can use these keys to strike_price with the following formats: \n", " - For S, F, ATM, ATMF: 's*1.1', 'F+10%', '1.05*ATMF+.01'\n", " - For Delta Strikes, specify the option delta: '25D', '20D-.01', etc.\n", " - You can also solve for Premium: P=, P= P=,%, PPct= \n", "\n", "\"\"\"\n", "\n", "# Here the option is bought to purchase 95mm EUR at expiration\n", "fx_options.append(\n", " FXOption(buy_sell=BuySell.Buy, pair='EURGBP', option_type='Call', notional_amount=10e8, strike_price=0.95)\n", ")\n", "\n", "# Here the option is sold to sell 100k of EUR at the current spot rate on expiration\n", "fx_options.append(\n", " FXOption(buy_sell=BuySell.Sell, pair='EURGBP', option_type=OptionType.Put, notional_amount='100k', strike_price='S')\n", ")\n", "\n", "# The option is sold to purchase AUD at the Forward rate + 1%\n", "fx_options.append(FXOption(buy_sell=BuySell.Sell, pair='AUDJPY', option_type=OptionType.Call, strike_price='F+1%'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# notional_amount is the quantity of the base currency to be exchanged in the future; it can be a string (eg: '100mm')\n", "# or a double (10e8). By definition, notional_amount_other_currency is the notional amount in the quoted currency. It\n", "# can be a Currency enum or a string\n", "# by default, notional_amount_other_currency will be the product of 'notional_amount' and 'strike_price'\n", "\n", "# Here, a EURGBP Call has a notional of 10mm EUR\n", "fx_options.append(\n", " FXOption(option_type=OptionType.Call, pair='EUR GBP', notional_currency=Currency.EUR, notional_amount='10m')\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# method_of_settlement indicates whether the option is cash or physically settled and can be either the\n", "# OptionSettlementMethod Enum or a string\n", "from gs_quant.common import OptionSettlementMethod\n", "\n", "fx_options.append(FXOption(method_of_settlement=OptionSettlementMethod.Cash))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# premium_currency is the premium amount's denominated currency\n", "# It can be a Currency enum or a string. By default, premium_currency will match the base currency\n", "\n", "# In this case, premium_currency of a EURGBP Put is EUR\n", "fx_options.append(FXOption(option_type=OptionType.Put, pair='EUR GBP', premium_currency=Currency.EUR))\n", "\n", "# premium_payment_date is the date the premium is exchanged. It can either be a date or string\n", "# It can be set to 's' which indicates spot premium\n", "fx_options.append(FXOption(option_type=OptionType.Call, pair='EUR GBP', premium_payment_date='spot'))\n", "\n", "# or set to 'fwd' or 'forward' to indicate forward premium\n", "fx_options.append(FXOption(option_type=OptionType.Call, pair='EUR GBP', premium_payment_date='fwd'))\n", "\n", "# premium is the price of the option contract. It can be a float or a string\n", "fx_options.append(FXOption(option_type=OptionType.Call, premium=-5e6))\n", "\n", "# It is possible to solve for the strike_price based on a certain Premium\n", "# The below resolves the strike_price such that the option premium is -£5mm\n", "fx_options.append(\n", " FXOption(pair='AUD JPY', option_type=OptionType.Call, strike_price='P=-5mm', premium_currency=Currency.AUD)\n", ")\n", "\n", "# The below resolves the strike_price such that the option premium is 5% of the total amount exchanged if the option\n", "# is exercised\n", "fx_options.append(\n", " FXOption(pair='AUD JPY', option_type=OptionType.Call, strike_price='Premium=5%', premium_currency=Currency.JPY)\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "pd.DataFrame(fx_options.price())" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/03_fx_vol_swap_trade_construction.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": true } }, "outputs": [], "source": [ "from gs_quant.instrument import FXVolatilitySwap" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "\n", "# external users should substitute their client id and secret\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# get list of properties of an fx vol swap\n", "FXVolatilitySwap.properties()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "swap = FXVolatilitySwap(pair='GBPUSD', last_fixing_date='3m', buy_sell='Buy')\n", "swap.resolve()\n", "print(swap.strike_vol * 100)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# forward starting vol swap\n", "fwd_6m6m_swap = FXVolatilitySwap(pair='USDJPY', first_fixing_date='6m', last_fixing_date='1y', buy_sell='Buy')\n", "fwd_6m6m_swap.resolve()\n", "print(fwd_6m6m_swap.as_dict())" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "**Fixing frequency** defaults to 'Daily/Business Days' but many other conventions are supported such as:\n", "* 'Daily/All Days'\n", "* 'Daily/Holiday'\n", "* 'Daily/MonToSat'\n", "* 'Daily/Weighted Lagging'\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Instrument Properties - Fixing Frequency" ] }, "outputs": [], "source": [ "weighted_swap = FXVolatilitySwap(\n", " pair='GBPUSD', last_fixing_date='1y', buy_sell='Buy', fixing_frequency='Daily/Weighted Lagging'\n", ")" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "**Fixing source** defaults to 'WM Company LDN 4pm Mid' but other options include:\n", "* 'GS NYC 3PM'\n", "* 'WM Company NYC 10am Mid'\n", "* 'BFIX LDN 4PM'\n", "* 'WMC Company LDN 1pm Mid'\n", "\n", "We also have bloomberg options as well such as 'BFIX TKO 3pm-m'. \n", "We're happy to confirm the syntax if you are looking for a fixing source that is not mentioned here.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Instrument Properties - Fixing Source" ] }, "outputs": [], "source": [ "bfix = FXVolatilitySwap(pair='GBPUSD', last_fixing_date='1y', buy_sell='Buy', fixing_source='BFIX LDN 4PM')\n", "print(bfix.strike_vol)" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "**Strike vol** defaults to ATM but the following solvers are supported:\n", "\n", "* zero cost vol : p=0\n", "* % Vol = {%}N (i.e. '2N')\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Instrument - Solving - Solving FXVolSwap strike vol" ] }, "outputs": [], "source": [ "# zero cost vol\n", "zero_swap = FXVolatilitySwap(pair='USDNOK', last_fixing_date='3m', buy_sell='Sell', strike_vol='p=0')\n", "zero_swap.resolve()\n", "zero_swap.price()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# 1.25*atm\n", "swap_25 = FXVolatilitySwap(pair='EURUSD', last_fixing_date='3m', buy_sell='Sell', strike_vol='1.25N')\n", "swap_25.resolve()\n", "print(swap_25.strike_vol * 100)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# current spot\n", "swap_spot = FXVolatilitySwap(pair='EURUSD', last_fixing_date='3m', buy_sell='Sell', strike_vol='S')\n", "swap_spot.resolve()\n", "print(swap_spot.strike_vol * 100)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# current spot\n", "swap_spot = FXVolatilitySwap(pair='EURUSD', last_fixing_date='3m', buy_sell='Sell', strike_vol='S')\n", "swap_spot.resolve()\n", "print(swap_spot.strike_vol * 100)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Zero Cost or Fair Vol Strike** - This can be calculated using the measure `FairVolStrike` from the risk package and will calculate the volatility strike that would give 0 premium" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Metrics - FairVolStrike" ] }, "outputs": [], "source": [ "from gs_quant.risk import FairVolStrike\n", "\n", "vol_swap = FXVolatilitySwap(pair='EURUSD', last_fixing_date='3m', strike_vol=0.1)\n", "print(vol_swap.calc(FairVolStrike))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "tags": [ "Instrument - FXVolatilitySwap" ] }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/04_fx_binary_trade_construction.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import FXBinary" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "\n", "# external users should substitute their client id and secret\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "FXBinary?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "binary = FXBinary(pair='USDJPY', buy_sell='Buy', expiration_date='3m')\n", "binary.resolve()\n", "print(binary.as_dict())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Strike Price \n", "\n", "If the strike price is not specified, the current spot is used as the default. Similar to FXOption, the strike price can be specified by a double or a string.\n", "\n", "\n", "The specific solver keys are: \n", "\n", " - 'S' - current spot rate\n", " - 'F' - forward\n", " - 'P' - Premium\n", " - 'D' - Delta \n", "\n", "You can use these keys to strike_price with the following formats: \n", "\n", " - For S, F: 's*1.1', 'F+10%' \n", " - For Delta Strikes, specify the option delta: '25D', '20D-.01', etc.\n", " - You can also solve for Premium: P={target}%\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Instrument - Solving - FX Binary strike solving", "Metrics - FXSpot" ] }, "outputs": [], "source": [ "from gs_quant.risk import FXSpot, FXFwd\n", "\n", "# solve for strike price by setting payout ratio to 10%\n", "binary_10x = FXBinary(\n", " pair='EURUSD', buy_sell='Buy', option_type='Put', expiration_date='1m', strike_price='p=10%', premium=0\n", ")\n", "binary_10x.resolve()\n", "print('strike price:', binary_10x.strike_price)\n", "print('spot level:', binary_10x.calc(FXSpot))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "binary_itm = FXBinary(pair='AUDUSD', buy_sell='Buy', expiration_date='3m', strike_price='1.1*s')\n", "binary_itm.resolve()\n", "print('strike price:', binary_itm.strike_price)\n", "print('spot level:', binary_itm.calc(FXSpot))\n", "binary_itm.calc(FXSpot) * 1.1" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Metrics - FXFwd" ] }, "outputs": [], "source": [ "binary_otm = FXBinary(pair='AUDUSD', buy_sell='Buy', expiration_date='3m', notional_amount='100k', strike_price='F-2%')\n", "binary_otm.resolve()\n", "print('strike price:', binary_otm.strike_price)\n", "print('fwd level:', binary_otm.calc(FXFwd))\n", "binary_otm.calc(FXFwd) * 0.98" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "tags": [ "Instrument - FXBinary" ] }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/05_calc_option_measures.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import Environment, GsSession\n", "from gs_quant.instrument import FXOption\n", "from gs_quant.risk import FXAnnualImpliedVol, FXAnnualATMImpliedVol, Price" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" }, "tags": [ "Metrics - FXAnnualImpliedVol, Metrics - FXAnnualATMImpliedVol", "Results - Multiple Measures" ] }, "outputs": [], "source": [ "fx_option = FXOption(pair='USDJPY', option_type='Put', expiration_date='3m', premium=0)\n", "result = fx_option.calc((Price, FXAnnualImpliedVol, FXAnnualATMImpliedVol)).to_frame()\n", "result" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/06_fx_var_swap_trade_construction.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": true } }, "outputs": [], "source": [ "from gs_quant.instrument import FXVarianceSwap" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "\n", "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# get list of properties of an fx var swap\n", "FXVarianceSwap.properties()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "swap = FXVarianceSwap(pair='GBPUSD', last_fixing_date='3m', buy_sell='Buy')\n", "swap.resolve()\n", "print(swap.strike_vol * 100)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pprint\n", "\n", "pp = pprint.PrettyPrinter(indent=4)\n", "\n", "# forward starting var swap\n", "fwd_6m6m_swap = FXVarianceSwap(pair='USDJPY', first_fixing_date='6m', last_fixing_date='1y', buy_sell='Buy')\n", "fwd_6m6m_swap.resolve()\n", "pp.pprint(fwd_6m6m_swap.as_dict())" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "**Fixing frequency** defaults to 'Daily/Business Days' but many other conventions are supported such as:\n", "* 'Daily/All Days'\n", "* 'Daily/Holiday'\n", "* 'Daily/MonToSat'\n", "* 'Daily/Weighted Lagging'\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "weighted_swap = FXVarianceSwap(\n", " pair='GBPUSD', last_fixing_date='1y', buy_sell='Buy', fixing_frequency='Daily/Weighted Lagging'\n", ")\n", "weighted_swap.resolve()\n", "pp.pprint(weighted_swap.as_dict())" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "**Fixing source** defaults to 'WM Company LDN 4pm Mid' but other options include:\n", "* 'GS NYC 3PM'\n", "* 'WM Company NYC 10am Mid'\n", "* 'BFIX LDN 4PM'\n", "* 'WMC Company LDN 1pm Mid'\n", "\n", "We also have bloomberg options as well such as 'BFIX TKO 3pm-m'. \n", "We're happy to confirm the syntax if you are looking for a fixing source that is not mentioned here.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "bfix = FXVarianceSwap(pair='GBPUSD', last_fixing_date='1y', buy_sell='Buy', fixing_source='BFIX LDN 4PM')\n", "bfix.resolve()\n", "print(bfix.strike_vol)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Zero Cost or Fair Vol Strike** - This can be calculated using the measure `FairVolStrike` from the risk package and will calculate the strike in volatility terms that would give 0 premium" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.risk import FairVolStrike\n", "\n", "vol_swap = FXVarianceSwap(pair='GBPUSD', last_fixing_date='3m', strike_vol=0.1)\n", "print(vol_swap.calc(FairVolStrike))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.1" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/07_fx_knockout_trade_construction.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": false } }, "outputs": [], "source": [ "from gs_quant.session import Environment, GsSession\n", "from gs_quant.instrument import FXKnockout\n", "from gs_quant.markets.portfolio import Portfolio\n", "import pandas as pd" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# get list of properties of an fx knockout - Note the knockout instrument will also do RKO (Reverse Knockout)\n", "FXKnockout.properties()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# in this example we will construct and price a portfolio of FXKnockouts\n", "fx_kos = Portfolio()\n", "\n", "# you don't need to specify any parameters to get a valid trade. All properties have defaults\n", "fx_kos.append(FXKnockout())" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Enums - Knock In / Knock Out" ] }, "outputs": [], "source": [ "# knock_in_or_out controls whether the option exits if the barrier_level is breached (knockout) or whether the option\n", "# only starts once the barrier_level is breached. Switch from a KO to an RKO.\n", "\n", "from gs_quant.common import InOut\n", "\n", "fx_kos.append(FXKnockout(barrier_level=0.95, knock_in_or_out=InOut.Out))\n", "fx_kos.append(FXKnockout(barrier_level=0.6, knock_in_or_out='In'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Enums - Upper barrier / Lower barrier" ] }, "outputs": [], "source": [ "# knock_up_or_down controls whether which direction breaches are measured from\n", "\n", "from gs_quant.common import UpDown\n", "\n", "fx_kos.append(FXKnockout(barrier_level=0.95, knock_up_or_down=UpDown.Up))\n", "fx_kos.append(FXKnockout(barrier_level=0.6, knock_up_or_down='Down'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# buy_sell indicates whether the option is bought (long) or sold (short)\n", "# It can be represented by the BuySell enum or a string\n", "fx_kos.append(FXKnockout(buy_sell='Buy'))\n", "\n", "from gs_quant.common import BuySell\n", "\n", "fx_kos.append(FXKnockout(buy_sell=BuySell.Sell))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# put_call indicates whether the option is a put or call; it can be represented by the OptionType enum or a string\n", "fx_kos.append(FXKnockout(option_type='Put'))\n", "\n", "from gs_quant.common import OptionType\n", "\n", "fx_kos.append(FXKnockout(option_type=OptionType.Call))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# pair is the FXKnockout's underlying currency pair. It is a string of two ccy iso codes, optionally separated\n", "# with a space (' '). The first currency is the base (transaction) currency and the second is the quote currency\n", "\n", "# In this case, base currency is 'EUR' and quote currency is 'GBP'\n", "# This option gives the purchasor the option to buy EUR at expiration\n", "fx_kos.append(FXKnockout(buy_sell=BuySell.Buy, option_type=OptionType.Call, pair='EUR GBP'))\n", "\n", "# Here, base currency is 'GBP' and quote currency is 'USD'\n", "# This option gives the purchasor the option to sell GBP at expiration\n", "fx_kos.append(FXKnockout(buy_sell=BuySell.Buy, option_type=OptionType.Put, pair='GBPUSD'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# notional_currency is the notional amount's denominated currency\n", "# It can be a Currency enum or a string. By default, notional_currency will match the base currency\n", "\n", "# In this case, notional_currency of a EURGBP Call is EUR.\n", "from gs_quant.common import Currency\n", "\n", "fx_kos.append(FXKnockout(option_type=OptionType.Call, pair='EUR GBP', notional_currency=Currency.EUR))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# notional_amount is the quantity of the base currency to be exchanged in the future; it can be a string (eg: '100mm')\n", "# or a double (10e8). By definition, notional_amount_other_currency is the notional amount in the quoted currency. It can be a Currency enum or a string\n", "# by default, notional_amount_other_currency will be the product of 'notional_amount' and 'strike_price'\n", "\n", "# Here, a EURGBP Call has a notional of 10mm EUR\n", "fx_kos.append(\n", " FXKnockout(option_type=OptionType.Call, pair='EUR GBP', notional_currency=Currency.EUR, notional_amount='10m')\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" }, "tags": [ "Instrument Solving - FXKO Strike Solving" ] }, "outputs": [], "source": [ "\"\"\"\n", "strike_price is the exchange rate stuck for the FXOption's underlying currency pair, which can be specified by a double \n", "or a string. If a string is used, it represents a relative value. When the trade is resolved, we solve for the strike_price \n", "\n", "The specific solver keys are: \n", " - 'S' - current spot rate\n", " - 'F' - forward\n", " - 'ATM' - At the Money \n", " - 'ATMF' - At the Money Forward\n", " - 'D' - Delta Strikes\n", " - 'P' - Premium\n", "\n", "You can use these keys to strike_price with the following formats: \n", " - For S, F, ATM, ATMF: 's*1.1', 'F+10%', '1.05*ATMF+.01'\n", " - For Delta Strikes, specify the option delta: '25D', '20D-.01', etc.\n", " - You can also solve for Premium: P=, P= P=,%, PPct= \n", "\n", "\"\"\"\n", "\n", "# Here the option is bought to purchase 95mm EUR at expiration\n", "fx_kos.append(\n", " FXKnockout(buy_sell=BuySell.Buy, pair='EURGBP', option_type='Call', notional_amount=10e8, strike_price=0.95)\n", ")\n", "\n", "# Here the option is sold to sell 100k of EUR at the current spot rate on expiration\n", "fx_kos.append(\n", " FXKnockout(\n", " buy_sell=BuySell.Sell, pair='EURGBP', option_type=OptionType.Put, notional_amount='100k', strike_price='S'\n", " )\n", ")\n", "\n", "# The option is sold to purchase AUD at the Forward rate + 1%\n", "fx_kos.append(FXKnockout(buy_sell=BuySell.Sell, pair='AUDJPY', option_type=OptionType.Call, strike_price='F+1%'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" }, "tags": [ "Enums - OptionSettlementMethod" ] }, "outputs": [], "source": [ "# method_of_settlement indicates whether the option is cash or physically settled and can be either the\n", "# OptionSettlementMethod Enum or a string\n", "from gs_quant.common import OptionSettlementMethod\n", "\n", "fx_kos.append(FXKnockout(method_of_settlement=OptionSettlementMethod.Cash))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# premium_currency is the premium amount's denominated currency\n", "# It can be a Currency enum or a string. By default, premium_currency will match the base currency\n", "\n", "# In this case, premium_currency of a EURGBP Put is EUR\n", "fx_kos.append(FXKnockout(option_type=OptionType.Put, pair='EUR GBP', premium_currency=Currency.EUR))\n", "\n", "# premium_payment_date is the date the premium is exchanged. It can either be a date or string\n", "# It can be set to 's' which indicates spot premium\n", "fx_kos.append(FXKnockout(option_type=OptionType.Call, pair='EUR GBP', premium_payment_date='spot'))\n", "\n", "# or set to 'fwd' or 'forward' to indicate forward premium\n", "fx_kos.append(FXKnockout(option_type=OptionType.Call, pair='EUR GBP', premium_payment_date='fwd'))\n", "\n", "# premium is the price of the option contract. It can be a float or a string\n", "fx_kos.append(FXKnockout(option_type=OptionType.Call, premium=-5e6))\n", "\n", "# It is possible to solve for the strike_price based on a certain Premium\n", "# The below resolves the strike_price such that the option premium is -£5mm\n", "fx_kos.append(\n", " FXKnockout(pair='AUD JPY', option_type=OptionType.Call, strike_price='P=-5mm', premium_currency=Currency.AUD)\n", ")\n", "\n", "# The below resolves the strike_price such that the option premium is 5% of the total amount exchanged if the option\n", "# is exercised\n", "fx_kos.append(\n", " FXKnockout(pair='AUD JPY', option_type=OptionType.Call, strike_price='Premium=5%', premium_currency=Currency.JPY)\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "pd.DataFrame(fx_kos.price())" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } }, "tags": [ "Instrument - FXKnockout" ] }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/08_fx_double_touch_trade_construction.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": false } }, "outputs": [], "source": [ "from gs_quant.session import Environment, GsSession\n", "from gs_quant.instrument import FXDoubleOneTouch\n", "from gs_quant.markets.portfolio import Portfolio\n", "import pandas as pd" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Basic Details" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# get list of properties of an fx knockout - Note the knockout instrument will also do RKO (Reverse Knockout)\n", "FXDoubleOneTouch.properties()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# in this example we will construct and price a portfolio of FX DNTs\n", "fx_dbl_touches = Portfolio()\n", "\n", "# you don't need to specify any parameters to get a valid trade. All properties have defaults\n", "# Note that touch_or_no_touch defaults to \"No Touch\", so the default trade is a DNT not a DOT (see below cell)\n", "fx_dbl_touches.append(FXDoubleOneTouch())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Double **No** Touch vs Double **One** touch\n", "`touch_or_no_touch` controls whether the option pays if either barrier_level is breached (called a Double One Touch or DOT for short). \n", "Or whether the option only pays if neither barrier_level has been breached by the `expiration_date` (called a Double No Touch or DNT)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import TouchNoTouch\n", "\n", "fx_dbl_touches.append(\n", " FXDoubleOneTouch(lower_barrier_level=0.9, upper_barrier_level=1.2, touch_or_no_touch=TouchNoTouch.No_Touch)\n", ")\n", "fx_dbl_touches.append(\n", " FXDoubleOneTouch(lower_barrier_level=0.9, upper_barrier_level=1.2, touch_or_no_touch=TouchNoTouch.Touch)\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Buying vs Selling" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# buy_sell indicates whether the option is bought (long) or sold (short)\n", "# It can be represented by the BuySell enum or a string\n", "fx_dbl_touches.append(FXDoubleOneTouch(buy_sell='Buy'))\n", "\n", "from gs_quant.common import BuySell\n", "\n", "fx_dbl_touches.append(FXDoubleOneTouch(buy_sell=BuySell.Sell))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Currencies involved\n", "`pair` is the `FXDoubleOneTouch`'s underlying currency pair. It is a string of two ccy iso codes, optionally separated with a space (' '). \n", "The first currency is the base (transaction) currency and the second is the quote currency.\n", "The upper and lower barrier levels are then levels corresponding to this FX rate\n", "The option also has a `notional_currency` and `notional_amount` field, this corresponds to the conditional option payout." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "from gs_quant.common import Currency\n", "\n", "# In this case, base currency is 'EUR' and quote currency is 'USD'\n", "# This option gives the purchasor a 100k EUR payout if the barrier levels are not breached\n", "fx_dbl_touches.append(\n", " FXDoubleOneTouch(buy_sell=BuySell.Buy, pair='EUR USD', notional_currency=Currency.EUR, notional_amount=100e3)\n", ")\n", "\n", "# Here, base currency is 'GBP' and quote currency is 'USD'\n", "# This option gives the purchasor a 100k USD payout if either barrier is breached\n", "fx_dbl_touches.append(\n", " FXDoubleOneTouch(\n", " buy_sell=BuySell.Buy,\n", " touch_or_no_touch=\"Touch\",\n", " pair='GBPUSD',\n", " notional_currency=Currency.EUR,\n", " notional_amount=\"100k\",\n", " )\n", ")" ] }, { "cell_type": "markdown", "metadata": { "tags": [ "Instrument - Solving - DNT strikes" ] }, "source": [ "#### Barrier Levels\n", "`upper_barrier_level` and `lower_barrier_level` are the exchange rates stuck for the DOT/DNT's underlying currency pair, which can be specified by a double \n", "or a string with a keyword/letter. If a string is used, it represents a relative value. When the trade is resolved, we solve for the strike_price \n", "\n", "The specific solver keys are: \n", "* 'S' - current spot rate\n", "* 'F' - forward\n", "* 'D' - Delta Strikes of a vanilla option\n", "* 'P' - Premium\n", "\n", "You can use these keys with the following formats: \n", "* For S, F, ATM, ATMF: 's*1.05', 'F+10%', '1.05*ATMF+.01'\n", "* For Delta Strikes, specify the option delta: '25D', '20D-.01', etc.\n", "* You can also solve for Premium: P=, P= P=,%, PPct=\n", "\n", "To have the upper barrier 2% above the current spot either of the following syntaxs would be valid: \n", "`upper_barrier_level=\"s+2%\"` or `upper_barrier_level=\"s*1.02\"`" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Here the option is set to have fixed barrier levels\n", "fx_dbl_touches.append(\n", " FXDoubleOneTouch(pair='EURGBP', expiration_date=\"1m\", lower_barrier_level=0.8, upper_barrier_level=0.95)\n", ")\n", "\n", "# The option is sold have the lower barrier 1% below spot and upper barrier 1% above the fwd\n", "fx_dbl_touches.append(\n", " FXDoubleOneTouch(pair='EURGBP', expiration_date=\"1m\", lower_barrier_level=\"s-1%\", upper_barrier_level=\"f+1%\")\n", ")" ] }, { "cell_type": "markdown", "metadata": { "tags": [ "Instrument Properties - FX Option Premium" ] }, "source": [ "#### Option Premium\n", "`premium_currency` is the premium amount's denominated currency. \n", "It can be a Currency enum or a string. By default, `premium_currency` will match the `notional_currency`. \n", "The `premium` will **default to the fair premium** s.t. that the PresentValue of the option + premium = 0. \n", "You therefore may find it helpful to set the premium explictly to 0 s.t. the PresentValue of the DOT/DNT is just the value of the option. \n", "`premium_payment_date` is the date the premium is exchanged. It can either be a date or string such as \"fwd\" or \"spot\"" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# In this case, the payout and premium currency of the EURGBP DNt will be EUR\n", "fx_dbl_touches.append(FXDoubleOneTouch(pair='EUR GBP', notional_currency='EUR', premium_currency='EUR'))\n", "\n", "# Here we explictly set the premium to 0 so measures like DollarPrice or PresentValue only include the option value\n", "fx_dbl_touches.append(FXDoubleOneTouch(pair='EUR GBP', premium=0, notional_currency='EUR'))\n", "\n", "# Here we set it the option to have forward premium by setting `premium_payment_date` to 'fwd' or 'forward'\n", "fx_dbl_touches.append(FXDoubleOneTouch(pair='EUR GBP', premium_payment_date='fwd'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Premium Solving\n", "It is possible to solve for the level of a single barrier to reach a certain Premium (in % or absolute value) \n", "You can also sovle both barrier levels for a target premium in which case they will be symmetrical spaced from current spot. \n", "Common syntax for solving for permium in % or absolute level include `p=10%` and `p=-200k`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import gs_quant.risk as risk\n", "\n", "# e.g. Solve X in a \"1m USDJPY spot-3%/X DNT\" s.t. that the fair premium is 10% of the notional\n", "upper_solve = FXDoubleOneTouch(\n", " pair='USD JPY', expiration_date='1m', lower_barrier_level='spot-3%', upper_barrier_level=\"p=10%\"\n", ")\n", "calced = upper_solve.calc((risk.FairPremium, risk.ResolvedInstrumentValues))\n", "print(calced[risk.FairPremium], calced[risk.ResolvedInstrumentValues].upper_barrier_level)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# e.g. Solve for a 1m USDJPY DNT with symmetric strikes aorund spot s.t. the premium is 5%\n", "double_solve = FXDoubleOneTouch(\n", " pair='USD JPY', expiration_date='1m', lower_barrier_level='p=5%', upper_barrier_level='p=5%', premium=0\n", ")\n", "calced = double_solve.calc((risk.FairPremiumInPercent, risk.FXSpot, risk.ResolvedInstrumentValues))\n", "resolved = calced[risk.ResolvedInstrumentValues]\n", "print(\n", " calced[risk.FairPremiumInPercent] * 100,\n", " resolved.lower_barrier_level,\n", " calced[risk.FXSpot],\n", " resolved.upper_barrier_level,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "pd.DataFrame(fx_dbl_touches.price())" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } }, "tags": [ "Instrument - FXDoubleOneTouch", "Instrument - FX DNT" ] }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/09_fx_delta_measures.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "98fd95ce-17e8-46e4-b27a-698538c3c003", "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import Currency, OptionType\n", "from gs_quant.markets import PricingContext\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.instrument import FXOption, FXForward\n", "from gs_quant.session import GsSession, Environment\n", "from gs_quant import risk" ] }, { "cell_type": "code", "execution_count": null, "id": "3b2c11bd-c938-4852-b9db-86d4251453fd", "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "id": "1c97dffb-4d87-4063-81b5-73cdcd9305c1", "metadata": {}, "outputs": [], "source": [ "cross = \"GBPJPY\"\n", "premium_currency = Currency.GBP" ] }, { "cell_type": "code", "execution_count": null, "id": "aeed27d1-dde1-499d-b288-bebc2dc8a06e", "metadata": {}, "outputs": [], "source": [ "option = FXOption(\n", " cross,\n", " expiration_date=\"2y\",\n", " option_type=OptionType.Call,\n", " strike_price=\"25d\",\n", " premium_currency=premium_currency,\n", " premium_payment_date=\"spot\",\n", ")\n", "option.resolve()\n", "# Here premium will have resolved to equal the option value" ] }, { "cell_type": "code", "execution_count": null, "id": "8dda27a5-9667-4e6f-9b40-d56e3d9b6acc", "metadata": {}, "outputs": [], "source": [ "spot = option.calc(risk.FXSpot)\n", "spot" ] }, { "cell_type": "code", "execution_count": null, "id": "2b5e92c6-72bc-40f7-b464-7357e01e8a4f", "metadata": {}, "outputs": [], "source": [ "# The CSA terms for FX Fwds won't always be USD-OIS like for derrivs, so we force everything to USD-OIS to be consistent\n", "PricingContext.current = PricingContext(csa_term='USD-OIS')" ] }, { "cell_type": "markdown", "id": "83146b2a-33c1-418c-99d1-4725edff74e9", "metadata": { "tags": [ "Metrics - Black-Scholes Delta" ] }, "source": [ "### Black-Scholes Delta\n", "`FXQuotedDelta` will calculate Black-Scholes delta in % terms and is the delta metric that is used to solve if you specify the option strike in delta like above" ] }, { "cell_type": "code", "execution_count": null, "id": "736eccbb-12fe-4e33-8628-f102668f4711", "metadata": {}, "outputs": [], "source": [ "option.calc(risk.FXQuotedDelta)" ] }, { "cell_type": "markdown", "id": "54966f17-18a0-4c24-8f60-6ff01d01f23e", "metadata": { "tags": [ "Metrics - FXDelta" ] }, "source": [ "### Delta PnL\n", "The `FXDelta` measure captures the USD present value change from the move in spot rates for each USD cross (in normal convention). i.e. `PnL(USD) = dSpot * FXDelta` \n", "Note: USD can be changed for another currency by parameterising the measure `FXDelta(currency='EUR')`" ] }, { "cell_type": "code", "execution_count": null, "id": "1be64972-841e-42f8-a7c2-b691e1272ece", "metadata": {}, "outputs": [], "source": [ "opt_fxdelta = option.calc(risk.FXDelta)\n", "opt_fxdelta.to_frame()" ] }, { "cell_type": "markdown", "id": "53871c7e-324e-4680-9f55-b30315b8f054", "metadata": { "tags": [ "Metrics - Delta as Notional of fwd" ] }, "source": [ "### Delta as Notional of Fwd\n", "To compute the wing delta as the notional amount of an equivalent FX Fwd there are the measures `FXCalcDelta` and `FXDeltaHedge`. \n", "`FXCalcDelta` returns a percentage factor of the option notional amount for the option cross. \n", "`FXDeltaHedge` returns notional amounts of FX Fwds on USD crosses to hedge the option." ] }, { "cell_type": "code", "execution_count": null, "id": "afa2372f-3bed-4895-b82c-2ea5153b4438", "metadata": {}, "outputs": [], "source": [ "# Here this will return a number close to the 0.25 B-S delta\n", "opt_fxcalc_notional = option.calc(risk.FXCalcDelta)\n", "opt_fxcalc_notional" ] }, { "cell_type": "markdown", "id": "99994eeb-757c-4031-a3fa-dfe7b3f509df", "metadata": {}, "source": [ "We can build the FXForward settling on spot date, with that `-1 * FXCalcDelta * notional` and check the FXDelta offsets, i.e. this is the spot trade on the option cross to hedge delta" ] }, { "cell_type": "code", "execution_count": null, "id": "f10b1365-c509-43af-9157-f4df14774d91", "metadata": {}, "outputs": [], "source": [ "fwd = FXForward(cross, \"0b\", \"f\", notional_amount=-1 * opt_fxcalc_notional * option.notional_amount)" ] }, { "cell_type": "code", "execution_count": null, "id": "560ba96b-6da0-403c-8cd7-ebb95549fe0a", "metadata": {}, "outputs": [], "source": [ "fwd_risk = fwd.calc(risk.FXDelta)\n", "fwd_risk" ] }, { "cell_type": "markdown", "id": "e360ec53-d58b-4fd5-b2b7-cf7d5eb84250", "metadata": {}, "source": [ "**Alternatively**: that hedge can be broken down into the two equivalent USDJPY and GBPUSD hedges, the `FXDeltaHedge` measure is already scaled by `-1 * option notional`" ] }, { "cell_type": "code", "execution_count": null, "id": "dc44c7b8-4513-4fc4-966c-7821f1e561b7", "metadata": {}, "outputs": [], "source": [ "opt_fxhedge = option.calc(risk.FXDeltaHedge)\n", "opt_fxhedge" ] }, { "cell_type": "code", "execution_count": null, "id": "4a52955c-1d57-4fd0-ace4-5c8fc71ee878", "metadata": {}, "outputs": [], "source": [ "usd_fwds = [\n", " FXForward(\n", " opt_fxhedge.iloc[i].mkt_asset[4:] + opt_fxhedge.iloc[i].mkt_asset[:3], \"0b\", \"f\", opt_fxhedge.iloc[i].value\n", " )\n", " for i in range(len(opt_fxhedge))\n", "]" ] }, { "cell_type": "code", "execution_count": null, "id": "1ca8402b-d5ee-4607-a4f0-33323fbea298", "metadata": {}, "outputs": [], "source": [ "Portfolio(usd_fwds).calc(risk.FXDelta).aggregate()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "tags": [ "Metrics - FX Delta Measures" ] }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/10_fx_gamma_measures.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "98fd95ce-17e8-46e4-b27a-698538c3c003", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "\n", "from scipy.stats import norm\n", "from gs_quant.common import Currency, OptionType\n", "from gs_quant.markets import PricingContext\n", "from gs_quant.instrument import FXOption\n", "from gs_quant.session import GsSession, Environment\n", "from gs_quant import risk" ] }, { "cell_type": "code", "execution_count": null, "id": "3b2c11bd-c938-4852-b9db-86d4251453fd", "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "id": "1c97dffb-4d87-4063-81b5-73cdcd9305c1", "metadata": {}, "outputs": [], "source": [ "cross = \"USDJPY\"\n", "premium_currency = Currency.JPY" ] }, { "cell_type": "code", "execution_count": null, "id": "aeed27d1-dde1-499d-b288-bebc2dc8a06e", "metadata": {}, "outputs": [], "source": [ "option = FXOption(\n", " cross,\n", " expiration_date=\"2y\",\n", " option_type=OptionType.Call,\n", " strike_price=\"25d\",\n", " premium_currency=premium_currency,\n", " premium_payment_date=\"spot\",\n", " notional_amount=10e6,\n", " notional_currency=Currency.USD,\n", " premium=0,\n", ")\n", "option.resolve()" ] }, { "cell_type": "markdown", "id": "83146b2a-33c1-418c-99d1-4725edff74e9", "metadata": { "tags": [] }, "source": [ "### FXGamma\n", "The only gamma measure currently available for FX is `FXGamma`. It represents the change in delta - wing delta in the underlying currency, scaled by the notional amount - for a 100% move in spot.\n", "\n", "This measure is computed using a central finite difference approximation, where spot shifts for repricing are done under the sticky-delta market model.\n", "\n", "In can be used in the following ways:" ] }, { "cell_type": "code", "execution_count": null, "id": "1fc5cf1f-1611-4dd7-9416-46b858600f42", "metadata": {}, "outputs": [], "source": [ "gamma = option.calc(risk.FXGamma)\n", "spot = option.calc(risk.FXSpot)\n", "delta = option.calc(risk.FXCalcDelta) * option.notional_amount\n", "print(f\"\"\"\n", "Spot : {spot:.4f}\n", "FXGamma (change in wing delta for a 100% move in spot) : {gamma:,.0f} {cross[3:]} / {cross}\n", "FXDelta : {delta:,.0f} {cross[:3]}\n", "\"\"\")" ] }, { "cell_type": "markdown", "id": "9c8a4116-dc15-4cc3-8cc4-7c2eb7b275f4", "metadata": {}, "source": [ "#### 1) Computing change in delta for a unit spot change\n", "Shock spot by 1 and recompute delta; the difference is approximated by `FXGamma / FXSpot`" ] }, { "cell_type": "code", "execution_count": null, "id": "8efcd37d-3bd1-47bf-a04d-41a9a5666c0a", "metadata": {}, "outputs": [], "source": [ "mkt_shock = risk.MarketDataShockBasedScenario(\n", " shocks={risk.MarketDataPattern('FX', 'JPY/USD'): risk.MarketDataShock(risk.MarketDataShockType.Absolute, 1)}\n", ")\n", "with mkt_shock:\n", " shocked_spot = option.calc(risk.FXSpot)\n", " shocked_delta = option.calc(risk.FXCalcDelta) * option.notional_amount\n", "\n", "shocked_spot" ] }, { "cell_type": "code", "execution_count": null, "id": "cdea113c-18c0-48b7-9977-8064cad87f16", "metadata": {}, "outputs": [], "source": [ "print(f\"\"\"\n", "Shocked Delta : {shocked_delta:,.0f}\n", "Delta Diff : {shocked_delta - delta:,.0f}\n", "FXGamma/spot : {gamma / spot:,.0f}\n", "\"\"\")" ] }, { "cell_type": "markdown", "id": "8f60aca9-66ac-4bf0-adcb-917e22600ba1", "metadata": {}, "source": [ "#### 2) Computing change in delta for a percentage spot change\n", "Shock spot by X% and recompute delta; the difference is approximated by `FXGamma * X / 100`" ] }, { "cell_type": "code", "execution_count": null, "id": "247303e9-18e8-4577-b43f-00b4693af8df", "metadata": {}, "outputs": [], "source": [ "shock_size = 0.01\n", "mkt_shock = risk.MarketDataShockBasedScenario(\n", " shocks={\n", " risk.MarketDataPattern('FX', 'JPY/USD'): risk.MarketDataShock(risk.MarketDataShockType.Proportional, shock_size)\n", " }\n", ")\n", "with mkt_shock:\n", " shocked_spot = option.calc(risk.FXSpot)\n", " shocked_delta = option.calc(risk.FXCalcDelta) * option.notional_amount\n", "\n", "shocked_spot" ] }, { "cell_type": "code", "execution_count": null, "id": "caba1236-22d8-40c4-a26c-992e01b9509c", "metadata": {}, "outputs": [], "source": [ "print(f\"\"\"\n", "Shocked Delta : {shocked_delta:,.0f}\n", "Delta Diff : {shocked_delta - delta:,.0f}\n", "FXGamma * {shock_size} : {gamma * shock_size:,.0f}\n", "\"\"\")" ] }, { "cell_type": "code", "execution_count": null, "id": "b49a616c-dc04-48cd-bd7e-bfbaa3c95020", "metadata": {}, "outputs": [], "source": [ "shock_size = 0.0001 # small bump size approximates the gamma FDM calculation, so the difference will match FXGamma more closely now\n", "mkt_shock = risk.MarketDataShockBasedScenario(\n", " shocks={\n", " risk.MarketDataPattern('FX', 'JPY/USD'): risk.MarketDataShock(risk.MarketDataShockType.Proportional, shock_size)\n", " }\n", ")\n", "with mkt_shock:\n", " shocked_spot = option.calc(risk.FXSpot)\n", " shocked_delta = option.calc(risk.FXCalcDelta) * option.notional_amount\n", "\n", "shocked_spot" ] }, { "cell_type": "code", "execution_count": null, "id": "1b61e11a-bb16-4a09-8dc7-312803f27e4a", "metadata": {}, "outputs": [], "source": [ "print(f\"\"\"\n", "Shocked Delta : {shocked_delta:,.0f}\n", "Delta Diff : {shocked_delta - delta:,.0f}\n", "FXGamma * {shock_size} : {gamma * shock_size:,.0f}\n", "\"\"\")" ] }, { "cell_type": "markdown", "id": "fbe1612e-478c-47cd-ad0e-4f3531196f75", "metadata": {}, "source": [ "#### 3) Validating raw gamma (FXGamma/FXSpot) against Black-Scholes gamma" ] }, { "cell_type": "code", "execution_count": null, "id": "47689765-356e-4beb-967f-04b8540c9dac", "metadata": {}, "outputs": [], "source": [ "raw_gamma = gamma / spot\n", "print(f\"\"\"\n", "Raw Gamma (scaled by notional): {raw_gamma:,.0f}\n", "\"\"\")" ] }, { "cell_type": "code", "execution_count": null, "id": "60743f54-7bc1-4624-9fa9-313f8890e8b9", "metadata": {}, "outputs": [], "source": [ "def black_sholes_raw_gamma(spot, strike, T, df_1, df_2, volatility):\n", " r_1 = -np.log(df_1) / T\n", " r_2 = -np.log(df_2) / T\n", " d1 = (np.log(spot / strike) + (r_2 - r_1 + 0.5 * volatility**2) * T) / (volatility * np.sqrt(T))\n", " n_prime_d1 = norm.pdf(d1)\n", " bs_gamma = (np.exp(-r_1 * T) * n_prime_d1) / (spot * volatility * np.sqrt(T))\n", " return float(bs_gamma)\n", "\n", "\n", "vol = option.calc(risk.FXAnnualImpliedVol)\n", "df_jpy = option.calc(risk.FXDiscountFactorOver)\n", "df_usd = option.calc(risk.FXDiscountFactorUnder)" ] }, { "cell_type": "code", "execution_count": null, "id": "cb1837f1-bbed-47aa-ae92-52c5044f39df", "metadata": {}, "outputs": [], "source": [ "bs_raw_gamma = black_sholes_raw_gamma(spot, option.strike_price, 2.0, df_usd, df_jpy, vol)\n", "print(f\"\"\"\n", "Black-Scholes Raw Gamma (scaled by notional): {bs_raw_gamma * option.notional_amount:,.0f}\n", "\"\"\")" ] }, { "cell_type": "markdown", "id": "58fe9c4a-03e2-48c3-98b3-6300dec78e31", "metadata": {}, "source": [ "#### 4) Computing Gamma PnL of holding the option for one day\n", "Raw gamma can be used in the Taylor series expansion term to derive Gamma PnL in the Overlying currency." ] }, { "cell_type": "code", "execution_count": null, "id": "e0768c74-af08-4f19-b698-3fde13e64598", "metadata": {}, "outputs": [], "source": [ "spot_price = option.calc(risk.FairPremium) # in JPY, as option has premium_currency = JPY\n", "spot_shocks = [i / 100 for i in range(-8, 12)]\n", "prices = []\n", "# wrap in PricingContext to send calculations in parallel\n", "with PricingContext():\n", " for spot_shock in spot_shocks:\n", " with risk.MarketDataShockBasedScenario(\n", " shocks={\n", " risk.MarketDataPattern('FX', 'JPY/USD'): risk.MarketDataShock(\n", " risk.MarketDataShockType.Proportional, spot_shock\n", " )\n", " }\n", " ):\n", " prices.append(option.calc(risk.FairPremium))\n", "# prices are in JPY, as option has premium_currency = JPY\n", "prices = [p.result() for p in prices]\n", "ds_array = [spot * shock for shock in spot_shocks]\n", "spots = [float(spot) + ds for ds in ds_array]" ] }, { "cell_type": "markdown", "id": "a456c5b9-e525-45f4-a050-1f781f4f274a", "metadata": {}, "source": [ "Delta is in the notional currency (USD), so `delta * ds` is in JPY, like `spot_price`" ] }, { "cell_type": "code", "execution_count": null, "id": "ebdbefd7-61b5-4483-b144-5fe7f4cd762c", "metadata": {}, "outputs": [], "source": [ "prices_delta_approx = [spot_price + float(delta * ds) for ds in ds_array]" ] }, { "cell_type": "markdown", "id": "5caf3b53-a90f-49b5-b9c9-596a3c44a96b", "metadata": {}, "source": [ "`gamma` is in the underlying currency (USD) and `raw_gamma = gamma / spot`.\n", "\n", "Therefore `raw_gamma * ds` is in the underlying currency and `raw_gamma * (ds ** 2)` is in the overlying currency (JPY) like the rest of the terms." ] }, { "cell_type": "code", "execution_count": null, "id": "f085b733-851f-425f-918d-fd5a0a8a942a", "metadata": {}, "outputs": [], "source": [ "prices_delta_gamma_approx = [spot_price + float(delta * ds) + float(0.5 * raw_gamma * ds**2) for ds in ds_array]" ] }, { "cell_type": "code", "execution_count": null, "id": "eedacd93-46fe-4b67-8763-7bad35c1a581", "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "from matplotlib.ticker import FuncFormatter\n", "\n", "prices_millions = [p / 1e6 for p in prices]\n", "prices_delta_approx_millions = [p / 1e6 for p in prices_delta_approx]\n", "prices_delta_gamma_approx_millions = [p / 1e6 for p in prices_delta_gamma_approx]\n", "\n", "plt.figure(figsize=(12, 8))\n", "\n", "plt.plot(spots, prices_millions, '-', linewidth=2.5, label='Actual Option Price', color='#2E86AB', zorder=3)\n", "plt.plot(\n", " spots,\n", " prices_delta_approx_millions,\n", " '--',\n", " linewidth=2,\n", " label='Delta Approximation (Linear)',\n", " color='#A23B72',\n", " alpha=0.8,\n", " zorder=2,\n", ")\n", "plt.plot(\n", " spots,\n", " prices_delta_gamma_approx_millions,\n", " ':',\n", " linewidth=2.5,\n", " label='Delta-Gamma Approximation, using FXGamma/FXSpot (Quadratic)',\n", " color='#F18F01',\n", " alpha=0.8,\n", " zorder=2,\n", ")\n", "\n", "current_idx = spot_shocks.index(0.0)\n", "current_spot = spots[current_idx]\n", "current_color = '#FF4444'\n", "marker_style = dict(s=120, color=current_color, marker='o', edgecolors='darkred', linewidths=2, zorder=5)\n", "\n", "plt.scatter([current_spot], [prices_millions[current_idx]], label='Current Spot', **marker_style)\n", "plt.scatter([current_spot], [prices_delta_approx_millions[current_idx]], **marker_style)\n", "plt.scatter([current_spot], [prices_delta_gamma_approx_millions[current_idx]], **marker_style)\n", "\n", "plt.axvline(x=current_spot, color=current_color, linestyle='--', alpha=0.3, linewidth=1.5)\n", "\n", "plt.xlabel('Spot Price (USDJPY)', fontsize=12, fontweight='bold')\n", "plt.ylabel('Option Premium (Million JPY)', fontsize=12, fontweight='bold')\n", "plt.title('Option Price: Actual vs Delta & Delta-Gamma Approximations', fontsize=14, fontweight='bold', pad=20)\n", "plt.legend(loc='best', fontsize=10, framealpha=0.9, shadow=True)\n", "plt.grid(True, alpha=0.3, linestyle='--', linewidth=0.5)\n", "\n", "ax = plt.gca()\n", "ax.yaxis.set_major_formatter(FuncFormatter(lambda x, p: f'{x:.2f}'))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.4" }, "tags": [ "Metrics - FX Gamma Measures" ] }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/000306_calc_eq_option_greeks.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import datetime as dt\n", "\n", "from dateutil.relativedelta import relativedelta\n", "\n", "from gs_quant.datetime.date import prev_business_date\n", "from gs_quant.instrument import EqOption, OptionType, OptionStyle\n", "from gs_quant.markets import PricingContext, HistoricalPricingContext\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.risk import EqDelta, EqGamma, EqVega, EqSpot, DollarPrice\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "### Create Eq Options Portfolios\n", "\n", "Let's now create a put-spread and a butterfly using `EqOption`. The underlier is entered as Reuters Instrument Code and \n", "can be one of .SPX, .STOXX50E, .FTSE, .N225. \n", "Strike can be specified as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF.\n", "Other parameters we can instantiate can be uncovers by pressing `shift+tab` on `EqOption`. " ], "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "put_1 = EqOption('.SPX', expiration_date = '3m', strike_price = '95%', option_type = OptionType.Put, \n", " option_style = OptionStyle.European, buy_sell = 'Buy')\n", "put_2 = EqOption('.SPX', expiration_date = '3m', strike_price = '105%', option_type = OptionType.Put, \n", " option_style = OptionStyle.European, buy_sell = 'Sell')\n", "put_spread = Portfolio((put_1, put_2))\n", "\n", "itm_call = EqOption('.SPX', expiration_date = '3m', strike_price = '90%', option_type = OptionType.Call, \n", " option_style=OptionStyle.European, buy_sell = 'Buy')\n", "otm_call = EqOption('.SPX', expiration_date = '3m', strike_price = '110%', option_type = OptionType.Call, \n", " option_style = OptionStyle.European, buy_sell = 'Buy')\n", "atm_call = EqOption('.SPX', expiration_date = '3m', strike_price = 'atm', option_type = OptionType.Call, \n", " number_of_options = 2, option_style = OptionStyle.European, buy_sell = 'Sell')\n", "butterfly = Portfolio((itm_call, otm_call, atm_call))" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "### Compute greeks for both our portfolios at inception and at expiry\n", "\n", "Let's now compute greeks on our portfolios. For an exhaustive list of supported metrics for `EqOption` please refer to \n", "the [Measures](https://developer.gs.com/docs/gsquant/guides/Pricing-and-Risk/measures/) guide.\n" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "trade_date = dt.date.today() - relativedelta(months = 3)\n", "trade_date = prev_business_date(trade_date)\n", "\n", "with PricingContext(pricing_date = trade_date):\n", " put_spread.resolve(in_place = True)\n", " ps_greeks = put_spread.calc((EqDelta, EqGamma, EqVega))\n", " butterfly.resolve(in_place = True)\n", " b_greeks = butterfly.calc((EqDelta, EqGamma, EqVega))\n", "\n", "expiry = prev_business_date(put_1.expiration_date)\n", "\n", "with PricingContext(pricing_date = expiry):\n", " ps_exp = put_spread.calc((EqDelta, EqGamma, EqVega))\n", " b_exp = butterfly.calc((EqDelta, EqGamma, EqVega))\n", " " ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "print(f'Put Spread Delta at inception: {ps_greeks[EqDelta].aggregate():.0f}')\n", "print(f'Put Spread Gamma at inception: {ps_greeks[EqGamma].aggregate():.0f}')\n", "print(f'Put Spread Vega at inception: {ps_greeks[EqVega].aggregate():.0f}')\n", "\n", "print(f'Butterfly Delta at inception: {b_greeks[EqDelta].aggregate():.0f}')\n", "print(f'Butterfly Gamma at inception: {b_greeks[EqGamma].aggregate():.0f}')\n", "print(f'Butterfly Vega at inception: {b_greeks[EqVega].aggregate():.0f}')" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "### Now let's look at our portfolios' PV vs Spot price\n", "\n", "Using `EqSpot` we can track the performance of the index vs our portfolio." ], "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "with HistoricalPricingContext(start = trade_date, end = expiry):\n", " ps_perf = put_spread.calc((EqSpot, DollarPrice))\n", " b_perf = butterfly.calc((EqSpot, DollarPrice))\n", " " ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "from pandas.plotting import register_matplotlib_converters\n", "register_matplotlib_converters()\n", "\n", "ps_perf = ps_perf.aggregate()\n", "fig, ax1 = plt.subplots(figsize = (10, 6))\n", "plt.figure(figsize = (24, 20))\n", "color='tab:blue'\n", "ax1.set_ylabel('Spot', color = color)\n", "ax1.plot(ps_perf[EqSpot], color = color)\n", "ax1.set_title('Put Spread')\n", "ax2 = ax1.twinx()\n", "color = 'tab:orange'\n", "ax2.set_ylabel('$PV', color = color)\n", "ax2.plot(ps_perf[DollarPrice], color = color)\n", "\n", "plt.show()" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "b_perf = b_perf.aggregate()\n", "fig, ax1 = plt.subplots(figsize=(10, 6))\n", "plt.figure(figsize=(24, 20))\n", "color='tab:blue'\n", "ax1.set_ylabel('Spot', color=color)\n", "ax1.plot(b_perf[EqSpot], color=color)\n", "ax1.set_title('Butterfly')\n", "ax2 = ax1.twinx()\n", "color = 'tab:orange'\n", "ax2.set_ylabel('$PV', color=color)\n", "ax2.plot(b_perf[DollarPrice], color=color)\n", "\n", "plt.show()" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } } ], "metadata": { "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" }, "kernelspec": { "name": "python3", "language": "python", "display_name": "Python 3" }, "pycharm": { "stem_cell": { "cell_type": "raw", "source": [], "metadata": { "collapsed": false } } } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/01_calc_eq_option_price.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import EqOption, OptionType, OptionStyle, UnderlierType\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# create a .STOXX50E 3m call option striking at-the-money spot\n", "eq_option = EqOption(\n", " '.STOXX50E',\n", " expiration_date='3m',\n", " strike_price='ATMS',\n", " option_type=OptionType.Call,\n", " option_style=OptionStyle.European,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# calculate local price and dollar price\n", "print('Local price: {:,.4f}'.format(eq_option.price()))\n", "print('Dollar price: {:,.4f}'.format(eq_option.dollar_price()))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Underlier Syntax\n", "\n", "The underlier accepts an underlier as a RIC or BBID identifier. The default is RIC.\n", "\n", "| Syntax | Defintion |\n", "|---------|---------------------|\n", "| 'RIC' | Reuters identifier |\n", "| 'BBID' | Bloomberg identifier |" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Enums - UnderlyierType" ] }, "outputs": [], "source": [ "# resolve using a Bloomberg ID\n", "eq_option_bbid = EqOption(\n", " 'SX5E',\n", " underlier_type=UnderlierType.BBID,\n", " expiration_date='3m',\n", " strike_price='ATMS',\n", " option_type=OptionType.Call,\n", " option_style=OptionStyle.European,\n", ")\n", "\n", "eq_option_bbid.resolve()\n", "eq_option_bbid.as_dict()" ] }, { "cell_type": "markdown", "metadata": { "tags": [ "Instrument - Solving - EqOption strike solving" ] }, "source": [ "#### Strike Syntax\n", "The strike_price syntax allows for an int or a string. The absolute level can be specified using an integer.\n", "\n", "The following solver keys using a string format are accepted: \n", "\n", "| Syntax | Defintion |\n", "|---------|---------------------|\n", "| '%' | Percent of Spot |\n", "| 'ATMS' | At the Money |\n", "| 'ATMF' | At the Money Forward|\n", "| 'D' | Delta Strikes |\n", "| 'P' | Premium |\n", "\n", " - For ATM, ATMF: '1.05*ATMF+.01'\n", " - For Delta Strikes, specify the option delta: '25D', '20D-.01', etc.\n", " - You can also solve for Premium: P=,% " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# resolve with strike at 110% of spot\n", "eq_atm_solver = EqOption(\n", " '.STOXX50E',\n", " expiration_date='3m',\n", " strike_price='ATMS+10%',\n", " option_type=OptionType.Put,\n", " option_style=OptionStyle.European,\n", ")\n", "\n", "eq_atm_solver.resolve()\n", "eq_atm_solver.strike_price" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# resolve with strike at 94.5% of spot\n", "eq_spot_pct = EqOption(\n", " '.STOXX50E',\n", " expiration_date='3m',\n", " strike_price='94.5%',\n", " option_type=OptionType.Put,\n", " option_style=OptionStyle.European,\n", ")\n", "\n", "eq_spot_pct.resolve()\n", "eq_spot_pct.strike_price" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# resolve with strike at spot minus 10\n", "eq_atmf_solver = EqOption(\n", " '.STOXX50E',\n", " expiration_date='1m',\n", " strike_price='ATMF-10',\n", " option_type=OptionType.Put,\n", " option_style=OptionStyle.European,\n", ")\n", "\n", "eq_atmf_solver.resolve()\n", "eq_atmf_solver.strike_price" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# resolve with strike solving for 10% premium\n", "eq_10x = EqOption(\n", " '.STOXX50E',\n", " expiration_date='6m',\n", " strike_price='P=10%',\n", " option_type=OptionType.Put,\n", " option_style=OptionStyle.European,\n", ")\n", "\n", "eq_10x.resolve()\n", "eq_10x.strike_price" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# resolve with strike solving for absolute premium\n", "# 'PriceLocal' and 'PriceUSD' are also valid\n", "eq_abs_prem = EqOption(\n", " '.STOXX50E',\n", " expiration_date='6m',\n", " strike_price='Price=1000',\n", " option_type=OptionType.Put,\n", " option_style=OptionStyle.European,\n", ")\n", "\n", "eq_abs_prem.resolve()\n", "print(eq_abs_prem.price())\n", "eq_abs_prem.strike_price" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "tags": [ "Instrument - EqOption" ] }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/02_calc_eq_option_risk_measures.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import EqOption, OptionType, OptionStyle\n", "from gs_quant.session import Environment, GsSession\n", "import gs_quant.risk as risk" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# create eq option\n", "eq_option = EqOption(\n", " '.STOXX50E',\n", " expiration_date='3m',\n", " strike_price='ATMS',\n", " option_type=OptionType.Call,\n", " option_style=OptionStyle.European,\n", ")\n", "eq_option.as_dict()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# single measure\n", "dollar_delta = eq_option.calc(risk.EqDelta)\n", "dollar_delta" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Metrics - Equity Risks" ] }, "outputs": [], "source": [ "# multiple measures\n", "risks = eq_option.calc((risk.EqDelta, risk.EqGamma, risk.EqVega))\n", "risks" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Results - slice multi risk results" ] }, "outputs": [], "source": [ "# result for a single measure\n", "risks[risk.EqDelta]" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Metrics - parameterized currency" ] }, "outputs": [], "source": [ "# single measure with parameterized currency\n", "euro_delta = eq_option.calc(risk.EqDelta(currency='EUR'))\n", "euro_delta" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/03_calc_eq_option_price_and_risk_in_pricing_context.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from datetime import date\n", "from gs_quant.instrument import EqOption, OptionType, OptionStyle\n", "from gs_quant.markets import PricingContext\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# create Option\n", "eq_option = EqOption(\n", " '.STOXX50E',\n", " expiration_date='3m',\n", " strike_price='ATMS',\n", " option_type=OptionType.Call,\n", " option_style=OptionStyle.European,\n", ")\n", "eq_option.as_dict()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Contexts - PricingContext" ] }, "outputs": [], "source": [ "# price as of a specific date using LDN close\n", "with PricingContext(pricing_date=date(2020, 2, 20), market_data_location='LDN'):\n", " price = eq_option.price()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# get the result\n", "price_result = price.result()\n", "print(price_result)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# calculate the delta as of a specific date using NYC close\n", "with PricingContext(pricing_date=date(2020, 2, 20), market_data_location='NYC'):\n", " delta = eq_option.calc(risk.EqDelta)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# get the result\n", "delta_results = delta.result()\n", "print(delta_results)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/04_calc_eq_option_price_and_risk_historically.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from datetime import date\n", "from gs_quant.instrument import EqOption, OptionType, OptionStyle\n", "from gs_quant.markets import HistoricalPricingContext\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# create Option\n", "eq_option = EqOption(\n", " '.STOXX50E',\n", " expiration_date='3m',\n", " strike_price='ATMS',\n", " option_type=OptionType.Call,\n", " option_style=OptionStyle.European,\n", ")\n", "eq_option.as_dict()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Contexts - HistoricalPricingContext" ] }, "outputs": [], "source": [ "# price daily for dates between 14Jan20 and 21Jan20\n", "with HistoricalPricingContext(date(2020, 1, 14), date(2020, 1, 21)):\n", " previous_prices_holder = eq_option.price()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# get the results\n", "previous_prices = previous_prices_holder.result()\n", "print(previous_prices)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# plot the instrument price over time\n", "import matplotlib.pyplot as plt\n", "from pandas.plotting import register_matplotlib_converters\n", "\n", "register_matplotlib_converters()\n", "\n", "plt.plot(previous_prices)\n", "plt.xlabel('time')\n", "plt.ylabel('price')\n", "plt.title('Price over time')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# calculate delta daily for dates between 20Jan20 and 27Jan20\n", "with HistoricalPricingContext(date(2020, 1, 20), date(2020, 1, 27)):\n", " previous_dollar_deltas_holder = eq_option.calc(risk.EqDelta)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# get the results\n", "previous_dollar_deltas = previous_dollar_deltas_holder.result()\n", "print(previous_dollar_deltas)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# plot the instrument delta over time\n", "import matplotlib.pyplot as plt\n", "from pandas.plotting import register_matplotlib_converters\n", "\n", "register_matplotlib_converters()\n", "\n", "plt.plot(previous_dollar_deltas)\n", "plt.xlabel('time')\n", "plt.ylabel('$delta')\n", "plt.title('$delta over time')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/05_calc_eq_option_portfolio_greeks.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import datetime as dt\n", "\n", "from dateutil.relativedelta import relativedelta\n", "\n", "from gs_quant.datetime.date import prev_business_date\n", "from gs_quant.instrument import EqOption, OptionType, OptionStyle\n", "from gs_quant.markets import PricingContext, HistoricalPricingContext\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.risk import EqDelta, EqGamma, EqVega, EqSpot, DollarPrice\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" }, "tags": [ "Contexts - Market Data Based Shock Scenario" ] }, "outputs": [], "source": [ "# This example uses instrument resolving. For external users this requires a reference spot override using a market scenario when resolving\n", "from gs_quant.risk import MarketDataPattern, MarketDataShock, MarketDataShockType, MarketDataShockBasedScenario\n", "\n", "eq_spot_scenario = MarketDataShockBasedScenario(\n", " shocks={\n", " MarketDataPattern('Eq', '.SPX', 'Reference Spot', mkt_quoting_style='Price'): MarketDataShock(\n", " MarketDataShockType.Override, 3900\n", " )\n", " }\n", ")" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "### Create Eq Options Portfolios\n", "\n", "Let's now create a put-spread and a butterfly using `EqOption`. The underlier is entered as Reuters Instrument Code and \n", "can be one of .SPX, .STOXX50E, .FTSE, .N225. \n", "Strike can be specified as value, percent or at-the-money e.g. 62.5, 95%, ATM-25, ATMF.\n", "Other parameters we can instantiate can be uncovers by pressing `shift+tab` on `EqOption`. " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" }, "tags": [ "Portfolios" ] }, "outputs": [], "source": [ "put_1 = EqOption(\n", " '.SPX',\n", " expiration_date='3m',\n", " strike_price='95%',\n", " option_type=OptionType.Put,\n", " option_style=OptionStyle.European,\n", " buy_sell='Buy',\n", ")\n", "put_2 = EqOption(\n", " '.SPX',\n", " expiration_date='3m',\n", " strike_price='105%',\n", " option_type=OptionType.Put,\n", " option_style=OptionStyle.European,\n", " buy_sell='Sell',\n", ")\n", "put_spread = Portfolio((put_1, put_2))\n", "\n", "itm_call = EqOption(\n", " '.SPX',\n", " expiration_date='3m',\n", " strike_price='90%',\n", " option_type=OptionType.Call,\n", " option_style=OptionStyle.European,\n", " buy_sell='Buy',\n", ")\n", "otm_call = EqOption(\n", " '.SPX',\n", " expiration_date='3m',\n", " strike_price='110%',\n", " option_type=OptionType.Call,\n", " option_style=OptionStyle.European,\n", " buy_sell='Buy',\n", ")\n", "atm_call = EqOption(\n", " '.SPX',\n", " expiration_date='3m',\n", " strike_price='atm',\n", " option_type=OptionType.Call,\n", " number_of_options=2,\n", " option_style=OptionStyle.European,\n", " buy_sell='Sell',\n", ")\n", "butterfly = Portfolio((itm_call, otm_call, atm_call))" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "### Compute greeks for both our portfolios at inception and at expiry\n", "\n", "Let's now compute greeks on our portfolios. For an exhaustive list of supported metrics for `EqOption` please refer to \n", "the [Measures](https://developer.gs.com/docs/gsquant/guides/Pricing-and-Risk/measures/) guide.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" }, "tags": [ "Metrics - Equity Greeks" ] }, "outputs": [], "source": [ "trade_date = dt.date.today() - relativedelta(months=3)\n", "trade_date = prev_business_date(trade_date)\n", "\n", "# apply spot override when resolving\"\n", "with PricingContext(pricing_date=trade_date), eq_spot_scenario:\n", " put_spread.resolve(in_place=True)\n", " ps_greeks = put_spread.calc((EqDelta, EqGamma, EqVega))\n", " butterfly.resolve(in_place=True)\n", " b_greeks = butterfly.calc((EqDelta, EqGamma, EqVega))\n", "\n", "expiry = prev_business_date(put_1.expiration_date)\n", "\n", "with PricingContext(pricing_date=expiry):\n", " ps_exp = put_spread.calc((EqDelta, EqGamma, EqVega))\n", " b_exp = butterfly.calc((EqDelta, EqGamma, EqVega))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "print(f'Put Spread Delta at inception: {ps_greeks[EqDelta].aggregate():.0f}')\n", "print(f'Put Spread Gamma at inception: {ps_greeks[EqGamma].aggregate():.0f}')\n", "print(f'Put Spread Vega at inception: {ps_greeks[EqVega].aggregate():.0f}')\n", "\n", "print(f'Butterfly Delta at inception: {b_greeks[EqDelta].aggregate():.0f}')\n", "print(f'Butterfly Gamma at inception: {b_greeks[EqGamma].aggregate():.0f}')\n", "print(f'Butterfly Vega at inception: {b_greeks[EqVega].aggregate():.0f}')" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "### Now let's look at our portfolios' PV vs Spot price\n", "\n", "Using `EqSpot` we can track the performance of the index vs our portfolio." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "with HistoricalPricingContext(start=trade_date, end=expiry):\n", " ps_perf = put_spread.calc((EqSpot, DollarPrice))\n", " b_perf = butterfly.calc((EqSpot, DollarPrice))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "from pandas.plotting import register_matplotlib_converters\n", "\n", "register_matplotlib_converters()\n", "\n", "ps_perf = ps_perf.aggregate()\n", "fig, ax1 = plt.subplots(figsize=(10, 6))\n", "plt.figure(figsize=(24, 20))\n", "color = 'tab:blue'\n", "ax1.set_ylabel('Spot', color=color)\n", "ax1.plot(ps_perf[EqSpot], color=color)\n", "ax1.set_title('Put Spread')\n", "ax2 = ax1.twinx()\n", "color = 'tab:orange'\n", "ax2.set_ylabel('$PV', color=color)\n", "ax2.plot(ps_perf[DollarPrice], color=color)\n", "\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "b_perf = b_perf.aggregate()\n", "fig, ax1 = plt.subplots(figsize=(10, 6))\n", "plt.figure(figsize=(24, 20))\n", "color = 'tab:blue'\n", "ax1.set_ylabel('Spot', color=color)\n", "ax1.plot(b_perf[EqSpot], color=color)\n", "ax1.set_title('Butterfly')\n", "ax2 = ax1.twinx()\n", "color = 'tab:orange'\n", "ax2.set_ylabel('$PV', color=color)\n", "ax2.plot(b_perf[DollarPrice], color=color)\n", "\n", "plt.show()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/01_cdindex_option_price.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "2a21a3b4", "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import CDIndexOption\n", "from gs_quant.session import Environment, GsSession\n", "from gs_quant.markets import PricingContext\n", "from gs_quant.risk import CDATMSpread, CDFwdSpread, CDImpliedVolatility" ] }, { "cell_type": "code", "execution_count": null, "id": "5b9fa24b", "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "id": "1f5ffbdf", "metadata": {}, "outputs": [], "source": [ "# View properties of a CD Index Option\n", "CDIndexOption.properties()" ] }, { "cell_type": "markdown", "id": "ba34f6c7", "metadata": { "tags": [ "Instrument Properties - credit underlying index" ] }, "source": [ "Available properties (defaults) for determining the underlying index are:\n", "- index_family (iTraxx Europe): 'iTraxx Europe', 'iTraxx Europe FNSE', 'iTraxx Europe XOVER', 'CDX.NA.IG', 'CDX.NA.HY'\n", "- index_series (OTR): OTR\n", "- index_version (latest): latest\n", "\n", "Available properties for determining the option are:\n", "- option_type (Call): 'Call', 'Put'\n", "- strike (0.01): Any\n", "- strike_type (Spread): 'Spread', 'Price'\n", "- expriation_date (next market expiry - third Wednesday of month): Between 1m and 6m\n", "- termination_date (5y next IMM date): 5y\n", "- notional_amount (10000000): Any" ] }, { "cell_type": "code", "execution_count": null, "id": "9a073ff4", "metadata": {}, "outputs": [], "source": [ "# Create a 3 month call on an OTR iTraxx Main 5y underlier with a strike of 65\n", "cd_option = CDIndexOption(\n", " strike=0.0065,\n", " option_type='Call',\n", " expiration_date='19Jan22',\n", " termination_date='5y',\n", " index_family='iTraxx Europe FNSE',\n", " notional_amount=10000000,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "216d883e", "metadata": { "scrolled": true }, "outputs": [], "source": [ "with PricingContext(market_data_location='NYC'):\n", " cd_option.resolve()\n", "cd_option.as_dict()" ] }, { "cell_type": "code", "execution_count": null, "id": "76b7bb37", "metadata": {}, "outputs": [], "source": [ "print(cd_option.price())" ] }, { "cell_type": "markdown", "id": "d7cb184b", "metadata": { "tags": [ "Metrics - Credit metrics" ] }, "source": [ "For index options we can also calculate the ATM (reference) spread, forward spread and implied annual volatility used for pricing the option\n", "\n", "- CDATMSpread: At the money value of the index in spread terms\n", "- CDFwdSpread: Expected forward value of the underlying index at option expiry, in spread terms\n", "- CDImpliedVolatility: Annual volatility for the underlying index hazard rate " ] }, { "cell_type": "code", "execution_count": null, "id": "864b04e5", "metadata": {}, "outputs": [], "source": [ "cd_option.calc(CDATMSpread)" ] }, { "cell_type": "code", "execution_count": null, "id": "b476065f", "metadata": {}, "outputs": [], "source": [ "cd_option.calc(CDFwdSpread)" ] }, { "cell_type": "code", "execution_count": null, "id": "4389869f", "metadata": { "scrolled": true }, "outputs": [], "source": [ "cd_option.calc(CDImpliedVolatility)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "tags": [ "Instrument - CDIndexOption" ] }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/02_cdindex_option_xover_payer_spread.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "7fb27b941602401d91542211134fc71a", "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import CDIndexOption\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.session import Environment, GsSession\n", "from gs_quant.markets import HistoricalPricingContext" ] }, { "cell_type": "code", "execution_count": null, "id": "acae54e37e7d407bbb7b55eff062a284", "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "id": "9a63283cbaf04dbcab1f6479b197f3a8", "metadata": {}, "source": [ "Here we price a payer spread on the iTraxx Crossover OTR index\n", "\n", "We buy a XOVER call and sell an offsetting XOVER call with a higher strike" ] }, { "cell_type": "code", "execution_count": null, "id": "8dd0d8092fe74a7c96281538738b07e2", "metadata": {}, "outputs": [], "source": [ "# Buy 3m XO call k=300\n", "long_leg = CDIndexOption(\n", " index_family='iTraxx Europe XOVER',\n", " strike=0.0300,\n", " option_type='Call',\n", " expiration_date='3m',\n", " termination_date='5y',\n", " buy_sell='Buy',\n", " name='Long_XOVER_Call',\n", ")\n", "# Sell 3m XO call k=350\n", "short_leg = CDIndexOption(\n", " index_family='iTraxx Europe XOVER',\n", " strike=0.0350,\n", " option_type='Call',\n", " expiration_date='3m',\n", " termination_date='5y',\n", " buy_sell='Sell',\n", " name='Short_XOVER_Call',\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "72eea5119410473aa328ad9291626812", "metadata": {}, "outputs": [], "source": [ "payer_spread = Portfolio((long_leg, short_leg))" ] }, { "cell_type": "code", "execution_count": null, "id": "8edb47106e1a46a883d545849b8ab81b", "metadata": { "tags": [ "Contexts - Historical Pricing Context" ] }, "outputs": [], "source": [ "with HistoricalPricingContext(10, show_progress=True):\n", " prices = payer_spread.price()" ] }, { "cell_type": "code", "execution_count": null, "id": "10185d26023b46108eb7d9f57d49d2b3", "metadata": {}, "outputs": [], "source": [ "prices.to_frame()" ] }, { "cell_type": "code", "execution_count": null, "id": "8763a12b2bbd4a93a75aff182afb95dc", "metadata": {}, "outputs": [], "source": [ "# Plot both legs of the payer spraed as well as the overall PV\n", "import matplotlib.pyplot as plt\n", "\n", "fig, ax = plt.subplots()\n", "ax.plot(prices.to_frame()['Long_XOVER_Call'], label='Buy leg, k=300')\n", "ax.plot(prices.to_frame()['Short_XOVER_Call'], label='Sell leg, k=350')\n", "ax.plot(prices.aggregate(), label='Overall cost')\n", "ax.set_xlabel('time')\n", "ax.set_ylabel('PV ($)')\n", "ax.set_title('PV over Time')\n", "ax.legend(bbox_to_anchor=(1, 0.5))\n", "fig.autofmt_xdate()" ] }, { "cell_type": "code", "execution_count": null, "id": "7623eae2785240b9bd12b16a66d81610", "metadata": {}, "outputs": [], "source": [ "prices.aggregate()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "tags": [ "Instrument - Credit Payer Spread" ] }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/03_cdindex_option_risks.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "810b3f51", "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import CDIndexOption\n", "from gs_quant.risk import CDDelta, CDVega, CDTheta, CDGamma\n", "from gs_quant.session import Environment, GsSession\n", "from gs_quant.markets import PricingContext, HistoricalPricingContext" ] }, { "cell_type": "code", "execution_count": null, "id": "a01bc09d", "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics'))" ] }, { "cell_type": "code", "execution_count": null, "id": "075f0ab2", "metadata": {}, "outputs": [], "source": [ "cd_option = CDIndexOption(\n", " index_family='iTraxx Europe XOVER',\n", " strike=0.0300,\n", " option_type='Call',\n", " buy_sell='Sell',\n", " expiration_date='1m',\n", " termination_date='5y',\n", " notional_amount=1,\n", ")\n", "cd_option.as_dict()" ] }, { "cell_type": "markdown", "id": "c392b31c", "metadata": { "tags": [ "Metrics - Credit Greeks" ] }, "source": [ "Index Option specific risk measures are:\n", "- CDDelta - amount of underlying index in local notional to buy in order to hedge the change in Dollar Price due to a 1bp shift in the underlying index spread\n", "- CDGamma - amount of underlying index in local notional to buy in order to hedge the change in option delta due to a 1bp shift in the underlying index spread\n", "- CDVega - change in option Dollar Price due to a 1bp shift in the implied volatility of the underlying index \n", "- CDTheta - change in option Dollar Price over one day, assuming constant vol" ] }, { "cell_type": "code", "execution_count": null, "id": "5b9e49d0", "metadata": {}, "outputs": [], "source": [ "with PricingContext(market_data_location='LDN'):\n", " greeks = cd_option.calc((CDDelta, CDGamma, CDVega, CDTheta))" ] }, { "cell_type": "code", "execution_count": null, "id": "8a42c52b", "metadata": {}, "outputs": [], "source": [ "greeks.result()[CDDelta]" ] }, { "cell_type": "markdown", "id": "9508a54e", "metadata": {}, "source": [ "View historic delta:" ] }, { "cell_type": "code", "execution_count": null, "id": "71dd4b37", "metadata": {}, "outputs": [], "source": [ "with HistoricalPricingContext(10, show_progress=True, is_batch=True):\n", " previous_option_deltas_holder = cd_option.calc(CDDelta)" ] }, { "cell_type": "code", "execution_count": null, "id": "f2752b5d", "metadata": { "scrolled": true }, "outputs": [], "source": [ "previous_option_deltas = previous_option_deltas_holder.result()\n", "print(previous_option_deltas.value)" ] }, { "cell_type": "code", "execution_count": null, "id": "edcb96c4", "metadata": {}, "outputs": [], "source": [ "# plot the instrument delta over time\n", "import matplotlib.pyplot as plt\n", "from pandas.plotting import register_matplotlib_converters\n", "\n", "register_matplotlib_converters()\n", "\n", "fig, ax = plt.subplots()\n", "ax.plot(previous_option_deltas.value)\n", "ax.set_xlabel('time')\n", "ax.set_ylabel('delta')\n", "ax.set_title('delta over time')\n", "fig.autofmt_xdate()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/04_cdindex_price.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "8388f063", "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import CDIndex\n", "from gs_quant.session import Environment, GsSession\n", "from gs_quant.markets import HistoricalPricingContext\n", "from gs_quant.risk import CDATMSpread" ] }, { "cell_type": "code", "execution_count": null, "id": "f7993feb", "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics'))" ] }, { "cell_type": "markdown", "id": "34576b63", "metadata": { "tags": [ "Instrument Properties - Credit Index Families" ] }, "source": [ "#### Available index families for CDIndex\n", "- iTraxx Europe OTR: 3y, 5y, 7y, 10y\n", "- iTraxx Europe XOVER OTR: 5y\n", "- CDX.NA.IG OTR: 5y\n", "- CDX.NA.HY OTR: 3y, 5y, 7y, 10y\n" ] }, { "cell_type": "code", "execution_count": null, "id": "1c092ad7", "metadata": { "scrolled": true }, "outputs": [], "source": [ "# Create an index product on S42 main\n", "itraxx_main = CDIndex(index_family='iTraxx Europe', termination_date='5y', index_series=42)\n", "\n", "itraxx_main.resolve()\n", "itraxx_main.as_dict()" ] }, { "cell_type": "markdown", "id": "76106f9b", "metadata": {}, "source": [ "Also create an index product on iTraxx Xover and price historically alongside iTraxx Main.\n", "\n", "Instead of pricing these, we will calculate the ATM spread" ] }, { "cell_type": "code", "execution_count": null, "id": "22776a0d", "metadata": {}, "outputs": [], "source": [ "itraxx_xover = CDIndex(index_family='iTraxx Europe XOVER', index_series=42)\n", "\n", "with HistoricalPricingContext(10, show_progress=True):\n", " itraxx_main_res_f = itraxx_main.calc(CDATMSpread)\n", " itraxx_xover_res_f = itraxx_xover.calc(CDATMSpread)" ] }, { "cell_type": "code", "execution_count": null, "id": "ac6ff3fe", "metadata": {}, "outputs": [], "source": [ "itraxx_main_res_f.result()" ] }, { "cell_type": "code", "execution_count": null, "id": "d761f1b6", "metadata": {}, "outputs": [], "source": [ "itraxx_main_res = itraxx_main_res_f.result()\n", "itraxx_xover_res = itraxx_xover_res_f.result()" ] }, { "cell_type": "code", "execution_count": null, "id": "9ebae46a", "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "\n", "fig, ax = plt.subplots()\n", "ax.plot(itraxx_main_res, label=\"iTraxx Main\")\n", "ax.plot(itraxx_xover_res, label=\"iTraxx XOVER\")\n", "ax.set_xlabel('time')\n", "ax.set_ylabel('ATM Spread ($)')\n", "ax.set_title('ATM Spread over Time')\n", "ax.legend()\n", "fig.autofmt_xdate()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "tags": [ "Instrument - CDIndex" ] }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/05_cdindex_roll_price.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "ba142b5d", "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import CDIndex\n", "from gs_quant.risk import CDATMSpread\n", "from gs_quant.session import Environment, GsSession\n", "from gs_quant.markets import PricingContext\n", "from gs_quant.markets.portfolio import Portfolio" ] }, { "cell_type": "code", "execution_count": null, "id": "84b1dfa3", "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "id": "378e5921", "metadata": {}, "source": [ "# Rolling a position on XOVER from S41 to S42" ] }, { "cell_type": "code", "execution_count": null, "id": "c17f3f58", "metadata": {}, "outputs": [], "source": [ "xover_41 = CDIndex(\n", " index_family='iTraxx Europe XOVER', index_series=41, buy_sell='Sell', termination_date='5y', name='sell41'\n", ")\n", "xover_42 = CDIndex(\n", " index_family='iTraxx Europe XOVER', index_series=42, buy_sell='Buy', termination_date='5y', name='buy42'\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "a017b8d5", "metadata": {}, "outputs": [], "source": [ "xover_roll = Portfolio((xover_41, xover_42))" ] }, { "cell_type": "code", "execution_count": null, "id": "aced6560", "metadata": { "scrolled": true }, "outputs": [], "source": [ "with PricingContext(market_data_location='LDN'):\n", " roll_spreads = xover_roll.calc(CDATMSpread)\n", " roll_price = xover_roll.price()" ] }, { "cell_type": "code", "execution_count": null, "id": "465eb4ee", "metadata": {}, "outputs": [], "source": [ "df = roll_spreads.to_frame()" ] }, { "cell_type": "code", "execution_count": null, "id": "d5698ddf", "metadata": {}, "outputs": [], "source": [ "df.loc['buy42'] - df.loc['sell41']" ] }, { "cell_type": "code", "execution_count": null, "id": "77f522ef", "metadata": { "tags": [ "Results - aggregating" ] }, "outputs": [], "source": [ "roll_price.aggregate()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "tags": [ "Instrument - Credit Index Rolling" ] }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/tutorials/Instruments.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "Examples require an initialized GsSession and relevant entitlements. `run_analytics` scope is required for the functionality covered in this tutorial. External clients need to substitute thier own client id and client secret below. Please refer to Sessions for details." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## What is an Instrument\n", "\n", "[`Instrument`](https://developer.gs.com/docs/gsquant/api/instrument.html) is a class that inherits from the [`Priceable`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.base.Priceable.html)\n", "class and is used to represent financial objects that can be priced, such as derivative instruments.\n", "\n", "[`Priceable`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.base.Priceable.html#gs_quant.base.Priceable/) exposes several methods common to all instruments, such as [`as_dict()`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.base.Priceable.html#gs_quant.base.Priceable.as_dict) which returns a dictionary of all the public, non-null properties and values and\n", "[`calc(risk_measure)`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.base.Priceable.html#gs_quant.base.Priceable.calc) which is used to evaluate various risk measures. More about the latter in the [Measures](https://developer.gs.com/docs/gsquant/guides/Pricing-and-Risk/measures/) guide.\n", "\n", "`gs-quant` offers a number of [`Instrument`](https://developer.gs.com/docs/gsquant/api/instrument.html#instruments/) implementations, such as [equity options](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.instrument.EqOption.html#gs_quant.instrument.EqOption/), [interest rate swaptions](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.instrument.IRSwaption.html#gs_quant.instrument.IRSwaption/) and [commodity swaps](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.instrument.CommodSwap.html).\n", "Please refer to [supported instruments](#supported-instruments) for a list of externally supported instruments." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## How to Create an Instrument\n", "\n", "Let's now create an instance of an instrument implementation.\n", "For this example, we will create an Interest Rate Swaption, which the [`IRSwaption`](/gsquant/api/classes/gs_quant.instrument.IRSwaption.html#gs_quant.instrument.IRSwaption/) class implements.\n", "\n", "We will start by importing [`IRSwaption`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.instrument.IRSwaption.html#gs_quant.instrument.IRSwaption/) from the [`Instrument`](https://developer.gs.com/docs/gsquant/api/instrument.html#instruments/) package as well as `PayReceive`, `Currency` to represent commonly used constants.\n" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import IRSwaption\n", "from gs_quant.common import PayReceive, Currency" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will now instantiate an [`IRSwaption`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.instrument.IRSwaption.html#gs_quant.instrument.IRSwaption/) object. Note that all [`Instruments`](https://developer.gs.com/docs/gsquant/api/instrument.html#instruments/), including [`IRSwaption`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.instrument.IRSwaption.html#gs_quant.instrument.IRSwaption/), take some non-keyworded required arguments (first 3 in this example),\n", "and some optional keyworded arguments (\\*\\*kwargs). If the optional arguments are not supplied, default market conventions will be used as described in each\n", "instrument's signature. Signatures can be found in the [the Instrument Package](/gsquant/api/instrument.html#instruments/) by clicking\n", "on the desired instrument." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "swaption = IRSwaption(\n", " PayReceive.Receive, '5y', Currency.USD, expiration_date='13m', strike='atm+40', notional_amount=1e8\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now use the [`as_dict()`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.base.Priceable.html#gs_quant.base.Priceable.as_dict) method inherited from [`Priceable`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.base.Priceable.html#) to view all public non-null properties and values of this swaption instance." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "swaption.as_dict()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Instrument Resolution\n", "\n", "The above output shows only the inputs specified, many of which are relative (i.e. expiration date, strike). Calling the [`resolve()`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.base.Priceable.html#gs_quant.base.Priceable.resolve) method will resolve these\n", "parameters to absolute values as well as fill in any defaulted parameters using the [`PricingContext`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.markets.PricingContext.html). Please refer to the above mentioned [Instrument Package](/gsquant/api/instrument.html#instruments/) for each instrument's available parameters and to the [Pricing Context guide](/gsquant/guides/Pricing-and-Risk/pricing-context) for further details on [`PricingContext`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.markets.PricingContext.html)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "swaption.resolve()\n", "swaption.as_dict()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note [`resolve()`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.base.Priceable.html#gs_quant.base.Priceable.resolve) will change the state of the instrument object. In the code snippet above, calling [`resolve()`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.base.Priceable.html#gs_quant.base.Priceable.resolve) mutates several specified relative parameters, for example:\n", "\n", "- `expiration_date`, specified as '13m', was resolved to '2020-11-02'\n", "- `strike` specified as 'atm+40', was resolved to '0.017845989434194357'\n", "\n", "[`resolve()`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.base.Priceable.html#gs_quant.base.Priceable.resolve) will also add any unspecified default parameters - note the additions when calling [`as_dict()`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.base.Priceable.html#gs_quant.base.Priceable.as_dict) before and after [`resolve()`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.base.Priceable.html#gs_quant.base.Priceable.resolve). For example:\n", "\n", "- 'fixed_rate_frequency': '6m'\n", "- 'premium_payment_date': '2019-10-04'\n", "\n", "Accessing any of the unspecified parameters on the unresolved swaption will resolve the swaption in place.\n", "\n", "Additionally, as discussed in the [measures guide](/gsquant/guides/Pricing-and-Risk/measures), if [`resolve()`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.base.Priceable.html#gs_quant.base.Priceable.resolve) is not called prior to calling [`price()`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.base.Priceable.html#gs_quant.base.Priceable.price) or calculating risk, the instrument object will be copied and resolved on the fly without mutating the original swaption object.\n", "\n", "The preferred behavior may depend on the [`PricingContext`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.markets.PricingContext.html#gs_quant.markets.PricingContext/) - more on this in the [Pricing Context](/gsquant/guides/Pricing-and-Risk/pricing-context) guide." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "## Supported Instruments\n", "\n", "Below are the instruments covered, names they are referred to as in gs_quant and brief definitions as well as links to\n", " the technical documentation for each. Each instrument corresponds to a model maintained by Goldman Sachs Securities Division.\n", "\n", "| Instrument | gs_quant name | Description |\n", "| ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------|\n", "| Eq Option | [EqOption](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.instrument.EqOption.html) | An option on an underlying equity security |\n", "| FX Forward | [FXForward](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.instrument.FXForward.html) | An exchange of cashflows in different currencies at a determined future time |\n", "| FX Option | [FXOption](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.instrument.FXOption.html) | An option on an FX Forward |\n", "| FX Binary | [FXBinary](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.instrument.FXBinary.html) | An option where the buyer receives a fixed amount if a certain currency pair fixes above or below a specified level on a specified date. |\n", "| FX Multi Cross Binary | [FXMultiCrossBinary](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.instrument.FXMultiCrossBinary.html) | An option where the buyer receives a fixed amount if each of the currency pairs fixes above or below a specified level on a specified date. |\n", "| FX Volatility Swap | [FXVolatilitySwap](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.instrument.FXVolatilitySwap.html) | An exchange of cashflows based on the realized volatility of the underlying FX cross and a pre-determined fixed volatility level |\n", "| Inflation Swap | [InflationSwap](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.instrument.InflationSwap.html) | A zero coupon vanilla inflation swap of fixed vs floating cashflows adjusted to inflation rate |\n", "| Interest Rate Swap | [IRSwap](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.instrument.IRSwap.html) | A vanilla interest rate swap of fixed vs floating cashflows in the same currency |\n", "| Interest Rate Basis Swap | [IRBasisSwap](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.instrument.IRBasisSwap.html) | An exchange of cashflows from different interest rate indices in the same currency |\n", "| Interest Rate Xccy Swap | [IRXccySwap](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.instrument.IRXccySwap.html) | An exchange of cashflows from different interest rate indices in different currencies |\n", "| Interest Rate Xccy Swap Fix Fix | [IRXccySwapFixFix](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.instrument.IRXccySwapFixFix.html) | An exchange of fixed cashflows in different currencies |\n", "| Interest Rate Xccy Swap Fix Float | [IRXccySwapFixFlt](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.instrument.IRXccySwapFixFlt.html) | A vanilla interest rate swap of fixed vs floating cashflows in different currencies |\n", "| Interest Rate Swaption | [IRSwaption](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.instrument.IRSwaption.html) | An option to enter into a vanilla interest rate swap of fixed vs floating cashflows |\n", "| Interest Rate Cap | [IRCap](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.instrument.IRCap.html) | An instrument in which the buyer receives payments at the end of each period in which the interest rate exceeds the agreed strike price |\n", "| Interest Rate Floor | [IRFloor](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.instrument.IRFloor.html) | An instrument in which the buyer receives payments at the end of each period in which the interest rate is below the agreed strike price |\n", "| Interest Rate CMS Option | [IRCMSOption](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.instrument.IRCMSOption.html) | An option on a single date where the payoff is based on the CMS rate |\n", "| Interest Rate CMS Option Strip | [IRCMSOptionStrip](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.instrument.IRCMSOptionStrip.html) | A strip of CMS Options |\n", "| Interest Rate CMS Spread Option | [IRCMSSpreadOption](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.instrument.IRCMSSpreadOption.html) | An option on a single date where the payoff is dependent on the spread of two CMS rates compared to the strike of the option. |\n", "| Interest Rate CMS Spread Option Strip | [IRCMSSpreadOptionStrip](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.instrument.IRCMSSpreadOptionStrip.html) | A strip of CMS Spread Options |\n", "\n", "Note that `IRDelta` is additional available for Interest Rate Futures and Bond Futures upon request.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Disclaimer\n", "This website may contain links to websites and the content of third parties (\"Third Party Content\"). We do not monitor, review or update, and do not have any control over, any Third Party Content or third party websites. We make no representation, warranty or guarantee as to the accuracy, completeness, timeliness or reliability of any Third Party Content and are not responsible for any loss or damage of any sort resulting from the use of, or for any failure of, products or services provided at or from a third party resource. If you use these links and the Third Party Content, you acknowledge that you are doing so entirely at your own risk.\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/00_instruments_and_measures/tutorials/Measures.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "Examples require an initialized GsSession and relevant entitlements. `run_analytics` scope is required for the functionality covered in this tutorial. External clients need to substitute thier own client id and client secret below. Please refer to Sessions for details." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": true } }, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Measures\n", "\n", "A measure is a metric that can be calculated on an instrument, like a dollar price. Below is a table of supported measures and their definitions.\n", "\n", "| Measure | Definition |\n", "| -------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n", "| [Annuity*](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.Annuity) | Annuity of instrument in local currency (unless currency specified in parameters) |\n", "| [Dollar Price](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.DollarPrice) | Price of the instrument in US Dollars |\n", "| [Price*](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.Price) | Price of the instrument (In local currency unless currency specified in parameters) |\n", "| [ForwardPrice](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.ForwardPrice) | Price of the instrument at expiry in the local currency |\n", "| [CDDelta](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.CDDelta) | Change in option Dollar Price relative to the change in underlying index Dollar Price, due to a 1bp shift in the underlying index spread |\n", "| [CDGamma](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.CDGamma) | Change in option Delta relative to the change in underlying index Delta, due to a 1bp shift in the underlying index spread |\n", "| [CDTheta](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.CDTheta) | Change in option Dollar Price over one day |\n", "| [CDVega](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.CDVega) | Change in option Dollar Price due to a 1bp shift in the implied volatility of the underlying index |\n", "| [EqDelta](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.EqDelta) | Change in Dollar Price (USD present value) due to individual 1% move in the spot price of underlying equity security |\n", "| [EqGamma](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.EqGamma) | Change in EqDelta for a 1% move in the price of the underlying equity security |\n", "| [EqVega](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.EqVega) | Change in Dollar Price (USD present value) due to individual 1bp moves in the implied volatility of the underlying equity security |\n", "| [FairPremium](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.FairPremium) | Fair Premium to be paid on the premium settlement date (in the premium currency) such that the present value would be zero\n", "| [FairVolStrike](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.FairVolstrike) | Fair Volatility Strike of a Var or Vol Swap such that the present value would be zero |\n", "| [FXDelta*](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.FXDelta) | Dollar Price sensitivity of the instrument to a move in the underlying spot such that dSpot \\* FXDelta = PnL |\n", "| [FXGamma*](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.FXGamma) | FXDelta sensitivity of the instrument to a move in the underlying spot such that dSpot \\* FXGamma = dDelta |\n", "| [FXAnnualImpliedVol](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.FXAnnualImpliedVol) | FX daily implied volatility (in percent) |\n", "| [FXAnnualATMImpliedVol](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.FXAnnualATMImpliedVol) | FX daily implied volatility (in basis points) |\n", "| [FXSpot](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.FXSpot) | FX spot reference |\n", "| [FXVega*](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.FXVega) | Change in Dollar Price due to a 1 vol move in the implied volatility of ATM instruments used to build the volatility surface |\n", "| [IRBasis*](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.IRBasis) | Change in Dollar Price (USD present value) due to individual 1bp moves in the interest rate instruments used to build the basis curve(s) |\n", "| [IRDelta*](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.IRDelta) | Change in Dollar Price (USD present value) due to individual 1bp moves in the interest rate instruments used to build the underlying discount curve |\n", "| [IRGamma*](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.IRGamma) | Change in aggregated IRDelta for a aggregated 1bp shift in the interest rate instruments used to build the underlying discount curve |\n", "| [IRVega*](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.IRVega) | Change in Dollar Price (USD present value) due to individual 1bp moves in the implied volatility (IRAnnualImpliedVol) of instruments used to build the volatility surface |\n", "| [IRAnnualImpliedVol](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.IRAnnualImpliedVol) | Interest rate annual implied volatility (in percent) |\n", "| [IRAnnualATMImpliedVol](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.IRAnnualATMImpliedVol) | Interest rate annual implied at-the-money volatility (in percent) |\n", "| [IRDailyImpliedVol](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.IRDailyImpliedVol) | Interest rate daily implied volatility (in basis points) |\n", "| [IRSpotRate](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.IRSpotRate) | Interest rate at-the-money spot rate (in percent) |\n", "| [IRFwdRate](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.IRFwdRate) | Interest rate par rate (in percent) |\n", "| [IRXccyDelta*](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.IRXccyDelta) | Change in Price due to 1bp move in cross currency rates. |\n", "| [InflationDelta*](https://developer.gs.com/docs/gsquant/api/risk.html#gs_quant.risk.InflationDelta) | Change in Price due to 1bp move in inflation curve. |\n", "\n", "Note - * indicates parameterised risk measures. See Parameterised Risk Measure section below.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "## Calculating Price Measures\n", "\n", "Let's price an instrument. For information on how to define an instrument, please refer to the [Instruments](https://developer.gs.com/docs/gsquant/guides/Pricing-and-Risk/instruments/) guide.\n", "\n", "Note, below we resolve the swaption parameters that will be used to price the swaption, thereby mutating the swaption object. If [`resolve()`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.base.Priceable.html#gs_quant.base.Priceable.resolve), is\n", "not called prior to calling [`price()`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.base.Priceable.html#gs_quant.base.Priceable.price), the object will be copied and resolved on the fly without mutating the original swaption object.\n", "The preferred behavior may depend on the [`PricingContext`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.markets.PricingContext.html) - more on this in the [Pricing Context](https://developer.gs.com/docs/gsquant/guides/Pricing-and-Risk/pricing-context/) guide.\n", "\n", "[`price()`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.base.Priceable.html#gs_quant.base.Priceable.price) method will return price in the local currency.\n" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "### IR Swaps" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "from gs_quant.instrument import IRSwap\n", "from gs_quant.common import PayReceive, Currency\n", "\n", "# Creating Swaps - spot starting, forward starting\n", "swap = IRSwap(PayReceive.Receive, '10y', 'GBP', fixed_rate='atm+50', notional_amount=1e8) # running\n", "swap_fwd_start = IRSwap(PayReceive.Pay, '5y', 'EUR', fixed_rate='atm+20', effective_date='3y') # fwd starting" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View Instrument w/ specified relative parameters\n", "swap_fwd_start.as_dict()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Resolve Instrument, View fixed parameters\n", "swap_fwd_start.resolve()\n", "swap_fwd_start.as_dict()" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "### IR Swaptions" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "from gs_quant.instrument import IRSwaption\n", "from gs_quant.common import PayReceive\n", "\n", "swaption = IRSwaption(\n", " PayReceive.Receive, '5y', Currency.USD, expiration_date='13m', strike='atm+40', notional_amount=1e8\n", ")\n", "swaption.resolve()\n", "swaption.price() # local is USD" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "All instruments can also priced in dollars." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "swaption.dollar_price() # USD price" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Calculating Risk Measures\n", "\n", "We can also calculate risk measures for the defined instrument. Please refer to [the Measures Guide](https://developer.gs.com/docs/gsquant/guides/Pricing-and-Risk/measures/) for the supported risk measures.\n", "Calling [`calc(risk_measure)`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.base.Priceable.html#gs_quant.base.Priceable.calc) calculates the value of the risk measure and can return a float, a dataframe or a future thereof, depending on how [`PricingContext`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.markets.PricingContext.html) is being used - more on this in the [Pricing Context guide](https://developer.gs.com/docs/gsquant/guides/Pricing-and-Risk/pricing-context/).\n", "\n", "Calculate a scalar value like implied volatility:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "import gs_quant.risk as risk\n", "\n", "swaption.calc(risk.IRAnnualImpliedVol) * 10000" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Calculate a structured value like vega:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "swaption.calc(risk.IRVega)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Calculate IR Delta (Aggregated Scalar and Ladder) for Swaps" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "from gs_quant.common import AggregationLevel\n", "\n", "ir_risk = swap.calc(\n", " (\n", " risk.IRDelta(aggregation_level=AggregationLevel.Type),\n", " risk.IRDelta(aggregation_level=AggregationLevel.Type, currency='local'),\n", " risk.IRDelta,\n", " )\n", ")\n", "\n", "# Print Risks\n", "print(ir_risk[risk.IRDelta(aggregation_level=AggregationLevel.Type)])\n", "print(ir_risk[risk.IRDelta(aggregation_level=AggregationLevel.Type, currency='local')])\n", "print(ir_risk[risk.IRDelta])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Calculate a conditional risk measure. Show IRDelta Ladder only where exposure >1e-2" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "ird_ladder = ir_risk[risk.IRDelta]\n", "print(ird_ladder[abs(ird_ladder.value) > 1e-2])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "See [measures](#Measures) table for information on units." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Parameterised Risk Measures\n", "\n", "Some risk measures now support extra parameters\n", "\n", "You can now specify which currency you want the price of an instrument to be expressed in" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "eur_swap = IRSwap(PayReceive.Pay, '5y', 'EUR', fixed_rate='atm+20')\n", "price = eur_swap.price(currency='PLN')\n", "print(f'{price} {list(price.unit.keys())[0]}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For some finite difference risk measures (noted * in the table above), you can now pass in the specifics of the calculation methodology.\n", "\n", "\n", "**Parameters supported in finite difference risk measures**\n", "\n", "|Parameter name\t |Type\t |Description|\n", "|-------------------------|--------------------------------------------------|------------------------------------------------------------\n", "|currency\t |string\t |Currency of risk result|\n", "|aggregation_level |gs_quant.target.common.AggregationLevel\t |Level of aggregate shift|\n", "|local_curve\t |bool \t |Change in Price (present value in the denominated currency)|\n", "|finite_difference_method |gs_quant.target.common.FiniteDifferenceParameter\t |Direction and dimension of finite difference|\n", "|mkt_marking_options |gs_quant.target.common.MktMarkingOptions |Market marking mode|\n", "|bump_size\t |float\t |Bump size|\n", "|scale_factor\t |float\t |Scale factor|" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(swap.calc(risk.IRDelta))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Calculate delta using an aggregate 1bp shift on the asset level and using change in Price only in the denominated currency\n", "print(swap.calc(risk.IRDelta(aggregation_level=AggregationLevel.Type, currency='local')))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Disclaimer\n", "This website may contain links to websites and the content of third parties (\"Third Party Content\"). We do not monitor, review or update, and do not have any control over, any Third Party Content or third party websites. We make no representation, warranty or guarantee as to the accuracy, completeness, timeliness or reliability of any Third Party Content and are not responsible for any loss or damage of any sort resulting from the use of, or for any failure of, products or services provided at or from a third party resource. If you use these links and the Third Party Content, you acknowledge that you are doing so entirely at your own risk." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.1" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010000_market_data_in_gs_quant.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# There are currently two ways to access data in gs_quant:\n", "# 1 - gs_quant.data module - which provides programmatic access to The Marquee Data Catalogue,\n", "# which includes a range of market data, gs product data, GIR Data and Execution & Liquidity Data.\n", "# More info here: https://marquee.gs.com/s/discover/data-services\n", "# 2 - Market Objects - which can be used to get both close and live market data\n", "#\n", "# The notebooks in this 01_market_objects directory focusing on accessing data via the Market Object\n", "#" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "# querying data from Market Objects requires the 'run_analytics' scope\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010001_market_objects.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": false } }, "outputs": [], "source": [ "from gs_quant.markets import PricingContext\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# Each PricingContext has a Market Object\n", "mkt = PricingContext.current.market" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# By default, the PricingContext's market is a CloseMarket object\n", "print(mkt.market_type)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# The CloseMarket Object is paramaterized by a location and date\n", "\n", "# The default location is the PricingContext.current.market_data_location\n", "print(mkt.location)\n", "\n", "# The default date is the prior business date to the current pricing date\n", "print(mkt.date)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# There are five types of Market Objects currently supported in gs_quant\n", "from gs_quant.markets import LiveMarket, TimestampedMarket" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The Live Market, which is paramaterized by a location\n", "mkt = LiveMarket()\n", "print(mkt.location)\n", "\n", "# You can set the location by using the PricingLocation Enum or a string\n", "from gs_quant.common import PricingLocation\n", "\n", "mkt.location = PricingLocation.HKG\n", "print(mkt.location)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The TimestampedMarket has a required timestamp argument and optional location\n", "import time as t\n", "\n", "mkt = TimestampedMarket(t.time(), location=PricingLocation.TKO)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [ "\n" ] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010002_marketdatapattern_basics.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "### Market Data Representation\n", "Market data is a key part of gs_quant. The `MarketDataPattern` class allows the user to select a collection of this \n", "observable market data." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "from gs_quant.risk import MarketDataPattern\n", "\n", "MarketDataPattern?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A `MarketDataPattern` can be instantiated with any combination of the following attributes:\n", "\n", "- `mkt_type` - natural category of risk representation\n", "- `mkt_asset` - marked observable\n", "- `mkt_class` - market priceable\n", "- `mkt_point` - unique identifier for the marked observable within a mkt_class\n", "\n", "The highest level of market data classification is the `mkt_type`. Examples of mkt_type include: 'IR', 'IR Vol' and 'FX'" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# you can instantiate a MarketDataPattern with keyword arguments\n", "ir_mdp = MarketDataPattern(mkt_type='IR')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# or positional arguments\n", "fx_mdp2 = MarketDataPattern('FX')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010003_marketdatapattern_construction_ir.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The MarketDataPattern class allows the user to select a collection of observable market data\n", "# MarketDataPattern arguments include: mkt_type, mkt_asset, mkt_class, mkt_point and mkt_quoting_style\n", "from gs_quant.risk import MarketDataPattern" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# A MarketDataPattern defined with 'IR' mkt_type contains market data related to IR Delta for rates instruments\n", "# (such as swaps, eurodollar futures, fras, etc)\n", "\n", "ir = MarketDataPattern(mkt_type='IR')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# The collection of 'IR' market data can be further constrained by setting the'mkt_asset'\n", "# For data with mkt_type 'IR', the mkt_asset represents a rate curve's denominated currency\n", "ir_gbp = MarketDataPattern(mkt_type='IR', mkt_asset='GBP')\n", "ir_jpy = MarketDataPattern('IR', 'JPY')\n", "ir_usd = MarketDataPattern('IR', 'USD')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010004_marketdatapattern_construction_ir_vol.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The MarketDataPattern class allows the user to select a collection of observable market data\n", "# MarketDataPattern arguments include: mkt_type, mkt_asset, mkt_class, mkt_point and mkt_quoting_style\n", "from gs_quant.risk import MarketDataPattern" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# A MarketDataPattern defined with 'IR Vol' mkt_type contains market data related to implied volatility for\n", "# IR Swaptions/Caplets\n", "ir_vol = MarketDataPattern(mkt_type='IR_Vol')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# The collection of 'IR Vol' market data can be further constrained by setting the'mkt_asset'\n", "# For data with mkt_type 'IR Vol', the mkt_asset is a benchmark rate\n", "ir_vol_eur_euribor = MarketDataPattern(mkt_type='IR Vol', mkt_asset='EUR-EURIBOR-TELERATE')\n", "ir_vol_gbp_libor = MarketDataPattern('IR Vol', 'GBP-LIBOR-BBA')\n", "ir_vol_chf_libor = MarketDataPattern('IR Vol', 'CHF-LIBOR-BBA')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Additionally we can select subset of curve instruments using the 'mkt_class'\n", "# valid mkt_classes for 'IR Vol' data are 'swaption' and 'caplet'\n", "ir_vol_gbp_libor_swaption = MarketDataPattern(mkt_type='IR Vol', mkt_asset='GBP-LIBOR-BBA', mkt_class='swaption')\n", "ir_vol_gbp_libor_caplet = MarketDataPattern('IR Vol', 'GBP-LIBOR-BBA', 'caplet')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010005_marketdatapattern_construction_ir_basis.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true, "jupyter": { "outputs_hidden": true } }, "outputs": [], "source": [ "# The MarketDataPattern class allows the user to select a collection of observable market data\n", "# MarketDataPattern arguments include: mkt_type, mkt_asset, mkt_class, mkt_point and mkt_quoting_style\n", "from gs_quant.risk import MarketDataPattern" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# A MarketDataPattern defined with 'IR Basis' mkt_type contains market data for rates spreads such as tenor basis,\n", "# funding/libor basis, etc.\n", "ir_basis = MarketDataPattern(mkt_type='IR Basis')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# The collection of 'IR Basis' market data can be further constrained by setting the 'mkt_asset'\n", "# For data with mkt_type 'IR Basis', the mkt_asset is the basis curve, represented by two currency/tenor pairs\n", "ir_basis_usd_ois1m = MarketDataPattern(mkt_type='IR Basis', mkt_asset='USD OIS/USD-1M')\n", "ir_basis_eur_3m6m = MarketDataPattern('IR Basis', 'EUR-3M/EUR-6M')\n", "ir_basis_gbp_3m6m = MarketDataPattern('IR Basis', 'GBP-3M/GBP-6M')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010006_marketdatapattern_construction_ir_xc.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true, "jupyter": { "outputs_hidden": true } }, "outputs": [], "source": [ "# The MarketDataPattern class allows the user to select a collection of observable market data\n", "# MarketDataPattern arguments include: mkt_type, mkt_asset, mkt_class, mkt_point and mkt_quoting_style\n", "from gs_quant.risk import MarketDataPattern" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# A MarketDataPattern defined with 'IR XC' mkt_type contains market data related to cross currency risk\n", "ir_xc = MarketDataPattern(mkt_type='IR XC')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# The collection of 'IR XC' market data can be further constrained by setting the 'mkt_asset'\n", "# For data with mkt_type 'IR XC', mkt_asset is a single currency, which denotes the cross-currency curve\n", "# (the large majority of which are vs. USD)\n", "ir_xc_eur = MarketDataPattern(mkt_type='IR XC', mkt_asset='EUR')\n", "ir_xc_gbp = MarketDataPattern('IR XC', 'GBP')\n", "ir_xc_chf = MarketDataPattern('IR XC', 'CHF')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010007_marketdatapattern_construction_fx.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# The MarketDataPattern class allows the user to select a collection of observable market data\n", "# MarketDataPattern arguments include: mkt_type, mkt_asset, mkt_class, mkt_point and mkt_quoting_style\n", "from gs_quant.risk import MarketDataPattern" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# A MarketDataPattern defined with 'FX' mkt_type contains FX Spot rate data\n", "fx = MarketDataPattern(mkt_type='FX')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# The collection of 'FX' market data can be further constrained by setting the 'mkt_asset'\n", "# For data with mkt_type 'FX', the mkt_asset is a cross against USD\n", "fx_eur = MarketDataPattern(mkt_type='FX', mkt_asset='USD/EUR')\n", "fx_gbp = MarketDataPattern('FX', 'USD/GBP')\n", "fx_chf = MarketDataPattern('FX', 'CHF/USD')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010008_marketdatapattern_construction_fx_fwd.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The MarketDataPattern class allows the user to select a collection of observable market data\n", "# MarketDataPattern arguments include: mkt_type, mkt_asset, mkt_class, mkt_point and mkt_quoting_style\n", "from gs_quant.risk import MarketDataPattern" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# A MarketDataPattern defined with 'FX Fwd' mkt_type contains FX Forward curve data\n", "fx_fwd = MarketDataPattern(mkt_type='FX Fwd')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# The collection of 'FX Fwd' market data can be further constrained by setting the 'mkt_asset'\n", "# For data with mkt_type 'FX Fwd', the mkt_asset is a cross\n", "# Note - FX Fwd market data are represented as USD crosses\n", "\n", "fx_fwd_eur = MarketDataPattern(mkt_type='FX Fwd', mkt_asset='USD/EUR')\n", "fx_fwd_dkk = MarketDataPattern('FX Fwd', 'DKK/USD')\n", "fx_fwd_jpy = MarketDataPattern('FX Fwd', 'JPY/USD')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010009_marketdatapattern_construction_fx_vol.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true, "jupyter": { "outputs_hidden": true } }, "outputs": [], "source": [ "# The MarketDataPattern class allows the user to select a collection of observable market data\n", "# MarketDataPattern arguments include: mkt_type, mkt_asset, mkt_class, mkt_point and mkt_quoting_style\n", "from gs_quant.risk import MarketDataPattern" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# A MarketDataPattern defined with 'FX Vol' mkt_type contains market data related to the implied volatility for\n", "# FX instruments\n", "fx_vol = MarketDataPattern(mkt_type='FX Vol')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# The collection of 'FX Vol' market data can be further constrained by setting the'mkt_asset'\n", "# For data with mkt_type 'FX Vol', the mkt_asset is a cross\n", "fx_vol_gbp_eur = MarketDataPattern(mkt_type='FX Vol', mkt_asset='GBP/EUR', mkt_class='ATM Vol')\n", "fx_vol_dkk_usd = MarketDataPattern('FX Vol', 'DKK/USD')\n", "fx_vol_jpy_aud = MarketDataPattern('FX Vol', 'JPY/AUD')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010010_marketdatapattern_construction_cd.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The MarketDataPattern class allows the user to select a collection of observable market data\n", "# MarketDataPattern arguments include: mkt_type, mkt_asset, mkt_class, mkt_point and mkt_quoting_style\n", "from gs_quant.risk import MarketDataPattern" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# A MarketDataPattern defined with 'CD' mkt_type contains market data related to credit instruments\n", "# (such as CDS, Index Spreads, Recovery Rates, etc.)\n", "\n", "cd = MarketDataPattern(mkt_type='CD')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# The collection of 'CD' market data can be further constrained by setting the 'mkt_class'\n", "\n", "# CDS Index Products are selected by the 'INDEX' mkt_class\n", "index = MarketDataPattern(mkt_type='CD', mkt_class='INDEX')\n", "# The mkt_asset isolates the series and mt_point indicates maturity\n", "index_series = MarketDataPattern(mkt_type='CD', mkt_asset='ITRAXX EUROPE`36', mkt_class='INDEX', mkt_point='5Y')\n", "\n", "# CDS are selected by the 'CDS' mkt_class and mkt_asset isolates the specific cds curve\n", "cds = MarketDataPattern(mkt_type='CD', mkt_class='CDS')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/01_rollfwd_for_trade.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import datetime as dt\n", "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwaption\n", "from gs_quant.risk import RollFwd\n", "from gs_quant.session import GsSession, Environment" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "swaption = IRSwaption(PayReceive.Receive, '5y', Currency.EUR, expirationDate=dt.date(2029, 10, 8), strike='atm')\n", "base_price = swaption.price()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Contexts - RollFwd" ] }, "outputs": [], "source": [ "swaption.resolve() # fix expiry and maturity\n", "\n", "# RollFwd Scenario - Roll forward 1 month\n", "with RollFwd(date='1m', holiday_calendar='NYC'):\n", " fwd_price = swaption.price()\n", "\n", "print('Base price: {:,.2f}'.format(base_price))\n", "print('Scenario price: {:,.2f}'.format(fwd_price))\n", "print('Diff: {:,.2f}'.format(fwd_price - base_price))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/02_basic_use_of_rollfwd_scenario.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import Environment, GsSession\n", "from gs_quant.instrument import IRSwaption\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.markets import PricingContext\n", "from gs_quant.risk import RollFwd\n", "import matplotlib.pyplot as plt\n", "import pandas as pd\n", "import numpy as np" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Context - RollFwd" ] }, "outputs": [], "source": [ "# basic usage of RollFwd scenario\n", "eur1y10y = IRSwaption('Pay', '10y', 'EUR', expiration_date='1y')\n", "\n", "# care needs to be taken when creating relative trades like the one above.\n", "# If you don't resolve the trade, the resolution of the trade parameters will be done with\n", "# reference to the active pricing context. Under the RollFwd scenario this means that\n", "# if you don't resolve the trade will be a different trade when priced under the rollfwd scenario.\n", "eur1y10y.resolve()\n", "\n", "# Roll forward 1 month\n", "rollfwd_scenario = RollFwd(date='1m', holiday_calendar='LDN')\n", "with rollfwd_scenario:\n", " fwd_price = eur1y10y.price()\n", "\n", "print('Base price: {:,.2f}'.format(eur1y10y.price()))\n", "print('Scenario price: {:,.2f}'.format(fwd_price))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Context - RollFwd" ] }, "outputs": [], "source": [ "# show how the option value will roll down moving forward 66 business days assuming either fwds\n", "# or spot rates are realised.\n", "\n", "short_swaption = IRSwaption('Pay', '5y', 'USD', expirationDate='6m', notionalAmount=1e8)\n", "short_swaption.resolve()\n", "\n", "prices = []\n", "roll_spot_prices = []\n", "with PricingContext():\n", " for bus_days in range(66):\n", " with RollFwd(date=f'{bus_days}b', holiday_calendar='NYC', realise_fwd=True):\n", " prices.append(short_swaption.price())\n", " with RollFwd(date=f'{bus_days}b', holiday_calendar='NYC', realise_fwd=False):\n", " roll_spot_prices.append(short_swaption.price())\n", "\n", "pd.Series([p.result() for p in prices], dtype=np.dtype(float)).plot(\n", " figsize=(10, 6), title=\"Swaption Price Forward in Time\", label='roll to fwd'\n", ")\n", "pd.Series([rp.result() for rp in roll_spot_prices], dtype=np.dtype(float)).plot(figsize=(10, 6), label='roll to spot')\n", "plt.xlabel('Business Days from Pricing Date')\n", "plt.ylabel('PV')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# create a grid of expiry by tenor swaptions showing the pv under the rollfwd scenario minus the base pv.\n", "def calc_risk_matrix(ccy, strike, pay_rec, date, roll_to_fwds, expiries, tenors):\n", " portfolio = Portfolio(\n", " [\n", " IRSwaption(pay_rec, tenor, ccy, expiration_date=expiry, strike=strike, name='{}_{}'.format(expiry, tenor))\n", " for expiry in expiries\n", " for tenor in tenors\n", " ]\n", " )\n", " portfolio.resolve()\n", " with RollFwd(date=date, holiday_calendar='LDN', realise_fwd=roll_to_fwds):\n", " rollfwd_results = portfolio.price()\n", "\n", " base_results = portfolio.price()\n", "\n", " rollfwd_records = [\n", " (rollfwd_results[t.name] - base_results[t.name], t.name.split('_')[0], t.name.split('_')[1]) for t in portfolio\n", " ]\n", " rollfwd_df = pd.DataFrame(rollfwd_records, columns=['Value', 'Expiry', 'Tenor'])\n", "\n", " pivot_df = rollfwd_df.pivot_table(values='Value', index='Expiry', columns='Tenor')\n", " return pivot_df[tenors].reindex(expiries)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ccy = 'EUR'\n", "strike = 'ATM'\n", "pay_rec = 'Pay' # or 'Receive' or 'Straddle'\n", "date = '1m' # enter relative or actual date\n", "roll_to_fwds = True\n", "expiries = ['1m', '2m', '3m', '6m', '9m', '1y', '18m', '2y', '3y', '5y', '7y', '10y']\n", "tenors = ['1y', '2y', '3y', '5y', '7y', '10y', '15y', '20y', '25y', '30y']\n", "\n", "calc_risk_matrix(ccy, strike, pay_rec, date, roll_to_fwds, expiries, tenors)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/03_rollfwd-showing-lifecycling-effects-on-swaps-and-swaptions.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession, Environment\n", "from gs_quant.risk import RollFwd\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.instrument import IRSwap, IRSwaption\n", "from gs_quant.markets import PricingContext\n", "import matplotlib.pyplot as plt\n", "import gs_quant.risk as risk\n", "import pandas as pd" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Context - RollFwd", "Metrics - Cashflows" ] }, "outputs": [], "source": [ "# create a swap which has a 1m floating frequency\n", "swap = IRSwap('Pay', '10y', 'EUR', fixed_rate='ATM-5', floating_rate_frequency='1m', name='EUR10y')\n", "\n", "# resolve the trade as of today to fix the dates and rate\n", "swap.resolve()\n", "\n", "# roll daily for 66 business days assuming both forward curve is realised and spot curve is realised\n", "fwd_price = []\n", "fwd_cash = []\n", "spot_price = []\n", "spot_cash = []\n", "r = range(0, 66, 6)\n", "# by wrapping all the scenarios into one PricingContext we package all the requests into one call to GS\n", "with PricingContext():\n", " for bus_days in r:\n", " with PricingContext(is_async=True), RollFwd(date=f'{bus_days}b', holiday_calendar='LDN', realise_fwd=True):\n", " fwd_price.append(swap.price())\n", " fwd_cash.append(swap.calc(risk.Cashflows))\n", " with PricingContext(is_async=True), RollFwd(date=f'{bus_days}b', holiday_calendar='LDN', realise_fwd=False):\n", " spot_price.append(swap.price())\n", " spot_cash.append(swap.calc(risk.Cashflows))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Carry" ] }, "outputs": [], "source": [ "fwd_pv = pd.Series([p.result() for p in fwd_price], index=r)\n", "spot_pv = pd.Series([p.result() for p in spot_price], index=r)\n", "\n", "# The output of the cashflows measure is a dataframe of the past and implied future cashflows. We could filter by payment date\n", "# but conviently the discount factor is 0 for paid cashflows\n", "cash_fwd = pd.Series([c.result()[c.result().discount_factor == 0].payment_amount.sum() for c in fwd_cash], index=r)\n", "cash_spot = pd.Series([c.result()[c.result().discount_factor == 0].payment_amount.sum() for c in spot_cash], index=r)\n", "\n", "fwd_pv.plot(figsize=(10, 6), title='Swap Carry', label='{} Realise Fwd'.format(swap.name))\n", "spot_pv.plot(label='{} Realise Spot'.format(swap.name))\n", "(fwd_pv + cash_fwd).plot(label='{} Realise Fwd (inc. cash)'.format(swap.name))\n", "(spot_pv + cash_spot).plot(label='{} Realise Spot (inc. cash)'.format(swap.name))\n", "\n", "plt.xlabel('Business Days from Pricing Date')\n", "plt.ylabel('PV')\n", "plt.legend()\n", "plt.show()\n", "# note that the steps represent the move in MTM as the cashflows are paid. The libor fixing is implied from the fwd" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Context - Nested Contexts" ] }, "outputs": [], "source": [ "itm_swaption = IRSwaption('Receive', '10y', 'EUR', strike='ATM+20', expiration_date='1m', name='ITM swaption')\n", "otm_swaption = IRSwaption('Receive', '10y', 'EUR', strike='ATM-20', expiration_date='1m', name='OTM swaption')\n", "port = Portfolio([itm_swaption, otm_swaption])\n", "port.resolve()\n", "\n", "# roll daily for 44 business days assuming both forward curve is realised and spot curve is realised\n", "fwd_results = []\n", "spot_results = []\n", "r = range(0, 44, 4)\n", "# by wrapping all the scenarios into one PricingContext we package all the requests into one call to GS\n", "with PricingContext():\n", " for bus_days in r:\n", " with PricingContext(is_async=True), RollFwd(date=f'{bus_days}b', holiday_calendar='LDN', realise_fwd=True):\n", " fwd_results.append(port.price())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df = pd.DataFrame.from_records(\n", " [[p['ITM swaption'], p['OTM swaption']] for p in fwd_results], index=r, columns=['ITM', 'OTM']\n", ")\n", "df.plot(figsize=(10, 6), secondary_y=['OTM'], title='Swaption Carry', xlabel='Business Days', ylabel='PV')\n", "# note that the OTM swaption prices at 0 post expiry whereas the ITM swaption prices at the value of the swap." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/04_yield_curves_with_rollfwd.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession, Environment\n", "from gs_quant.instrument import IRSwap\n", "from gs_quant.risk import IRFwdRate, RollFwd\n", "from gs_quant.markets.portfolio import Portfolio\n", "import matplotlib.pylab as plt\n", "import pandas as pd\n", "import numpy as np" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Instrument - FRA" ] }, "outputs": [], "source": [ "ccy = 'EUR'\n", "# construct a series of 6m FRAs going out 20y or so\n", "fras = Portfolio(\n", " [\n", " IRSwap(\n", " 'Pay',\n", " '6m',\n", " ccy,\n", " effective_date='{}m'.format(i - 6),\n", " fixed_rate_frequency='6m',\n", " floating_rate_frequency='6m',\n", " )\n", " for i in range(6, 123, 6)\n", " ]\n", ")\n", "fras.resolve()\n", "results = fras.calc(IRFwdRate)\n", "\n", "# get the fwd rates for these fras under the base sceneraio (no shift in time)\n", "base = {fras[i].termination_date: res for i, res in enumerate(results)}\n", "base_series = pd.Series(base, name='base', dtype=np.dtype(float))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "Context - RollFwd" ] }, "outputs": [], "source": [ "# calculate the fwd rates with a shift forward of 6m. This shift keeps fwd rates constant.\n", "# So 5.5y rate today will be 5y rate under the scenario of pricing 6m in the future.\n", "with RollFwd(date='6m', realise_fwd=True, holiday_calendar='LDN'):\n", " results_fwd = fras.calc(IRFwdRate)\n", "with RollFwd(date='6m', realise_fwd=False, holiday_calendar='LDN'):\n", " results_spot = fras.calc(IRFwdRate)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "roll_to_fwds = {fras[i].termination_date: res for i, res in enumerate(results_fwd)}\n", "roll_to_fwds_series = pd.Series(roll_to_fwds, name='roll to fwd', dtype=np.dtype(float))\n", "\n", "roll_to_spot = {fras[i].termination_date: res for i, res in enumerate(results_spot)}\n", "roll_to_spot_series = pd.Series(roll_to_spot, name='roll to spot', dtype=np.dtype(float))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# show the curves, the base in blue, the roll to fwd in green and the roll to spot in orange.\n", "# note blue and green curves are not exactly on top of each other as we aren't using the curve instruments themselves\n", "# but instead using FRAs to show a smooth curve.\n", "base_series.plot(figsize=(20, 10))\n", "roll_to_spot_series.plot()\n", "roll_to_fwds_series.plot()\n", "plt.legend()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } }, "tags": [ "Market Data - Yield Curve" ] }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/05_rollfwd_swap_fwd_rates.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "from gs_quant.markets import PricingContext\n", "from gs_quant.session import GsSession, Environment\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.instrument import IRSwap\n", "from gs_quant.risk import RollFwd, IRFwdRate\n", "import matplotlib.pyplot as plt\n", "import pandas as pd\n", "import numpy as np" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true, "tags": [ "Context - RollFwd" ] }, "outputs": [], "source": [ "# construct a portfolio of swaps with tenors 6m apart and resolve these trades\n", "\n", "tenor = ['42m', '4y', '54m', '5y']\n", "swap_port = [\n", " IRSwap('Pay', t, 'EUR', fixed_rate='ATM', floating_rate_frequency='6m', effective_date='6m', name='EUR6m' + t)\n", " for t in tenor\n", "]\n", "port = Portfolio(swap_port)\n", "port.resolve()\n", "\n", "fwd_results = []\n", "spot_results = []\n", "\n", "# calculate the fwd rate under the assumption spot rates are held constant and that fwd rates are held constant.\n", "# here we calculate the fwd rate every week forward in time.\n", "\n", "with PricingContext():\n", " for week in range(0, 12):\n", " with PricingContext(is_async=True), RollFwd(date=f'{week}w', realise_fwd=True, holiday_calendar='LDN'):\n", " fwd_results.append(port.calc(IRFwdRate))\n", " with PricingContext(is_async=True), RollFwd(date=f'{week}w', realise_fwd=False, holiday_calendar='LDN'):\n", " spot_results.append(port.calc(IRFwdRate))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true, "tags": [ "Carry" ] }, "outputs": [], "source": [ "# graph the results. Note that under the fwd rate constant assumption the fwd rate doesn't change,\n", "# whereas you see the effect of the rolldown of the curve in the spot rate constant assumption.\n", "for swap in swap_port:\n", " s = pd.Series([p[swap.name] for p in spot_results], dtype=np.dtype(float))\n", " s.plot(figsize=(20, 12), title='Swap Carry', label='{} spot curve carry'.format(swap.name))\n", " f = pd.Series([p[swap.name] for p in fwd_results], dtype=np.dtype(float))\n", " f.plot(label='{} fwd curve carry'.format(swap.name))\n", "\n", "plt.xlabel('Weeks')\n", "plt.ylabel('Rate')\n", "plt.legend()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/02_market_shock/010200_marketdatashockbasedscenario_basics.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# The gs_quant risk package contains Scenarios, which allow the user to perform scenario analysis on a set of trades\n", "# 'MarketDataShockBasedScenario' is the most flexible scenario, which allows users to construct bespoke market data\n", "# scenarios\n", "from gs_quant.risk import MarketDataShockBasedScenario\n", "\n", "MarketDataShockBasedScenario?" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false, "pycharm": { "is_executing": false, "name": "#%%\n" } }, "outputs": [], "source": [ "# MarketDataShockBasedScenarios has one property: 'shocks', a mapping of MarketDataPattern to MarketDataShock\n", "MarketDataShockBasedScenario.shocks" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# The MarketDataPattern defines a collection of observable Market Data. Examples for the MarketDataPattern class are\n", "# in gs_quant/examples/01_pricing_and_risk/01_market_objects." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/02_market_shock/010201_rate_spot_shock.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwaption\n", "from gs_quant.session import Environment, GsSession\n", "from gs_quant.risk import MarketDataShockBasedScenario, MarketDataPattern, MarketDataShock, MarketDataShockType" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "ir_spot_scenario = MarketDataShockBasedScenario(\n", " shocks={MarketDataPattern('IR', 'EUR'): MarketDataShock(MarketDataShockType.Absolute, 50 / 10000)}\n", ")" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "swaption = IRSwaption(PayReceive.Receive, '5y', Currency.EUR, expiration_date='13m', strike='atm')\n", "swaption.resolve()\n", "\n", "with ir_spot_scenario:\n", " swaption_scenario_price = swaption.price()\n", "\n", "# Look at the difference between scenario and base prices\n", "print('Base price: {:,.2f}'.format(swaption.price()))\n", "print('Scenario price: {:,.2f}'.format(swaption_scenario_price))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/02_market_shock/010202_rate_curve_shock.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": false } }, "outputs": [], "source": [ "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwaption\n", "from gs_quant.risk import CurveScenario\n", "\n", "from gs_quant.session import Environment, GsSession\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "swaption = IRSwaption(PayReceive.Receive, '5y', Currency.EUR, expiration_date='13m', strike='atm')\n", "swaption.resolve()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" }, "scrolled": true }, "outputs": [], "source": [ "# Price the swaption under a curve move of 5bp parallel shift, 1bp slope shift pivoted at 5y point (up to 50y)\n", "curve_scenario = CurveScenario(annualised_parallel_shift=5, \n", " annualised_slope_shift=1, pivot_point=5, cutoff=50)\n", "\n", "with curve_scenario:\n", " swaption_scenario_price = swaption.price()\n", "\n", "# Look at the difference between scenario and base prices\n", "print('Base price: {:,.2f}'.format(swaption.price()))\n", "print('Scenario price: {:,.2f}'.format(swaption_scenario_price))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.0" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/02_market_shock/010203_rate_vol_shock.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwaption\n", "from gs_quant.session import Environment, GsSession\n", "from gs_quant.risk import MarketDataShockBasedScenario, MarketDataPattern, MarketDataShock, MarketDataShockType" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ir_vol_scenario = MarketDataShockBasedScenario(\n", " shocks={MarketDataPattern('IR Vol'): MarketDataShock(MarketDataShockType.Absolute, 1e-4)}\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "swaption = IRSwaption(PayReceive.Receive, '5y', Currency.EUR, expiration_date='13m', strike='atm')\n", "swaption.resolve()\n", "\n", "with ir_vol_scenario:\n", " swaption_scenario_price = swaption.price()\n", "\n", "# Look at the difference between scenario and base prices\n", "print('Base price: {:,.2f}'.format(swaption.price()))\n", "print('Scenario price: {:,.2f}'.format(swaption_scenario_price))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/02_market_shock/010204_delta_shock_equivalent.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwap\n", "from gs_quant.markets import PricingContext\n", "from gs_quant.session import Environment, GsSession\n", "from gs_quant.risk import (\n", " MarketDataShockBasedScenario,\n", " MarketDataPattern,\n", " MarketDataShock,\n", " MarketDataShockType,\n", " IRDeltaParallel,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Risk measures are short-hand for a set of predefined scenarios. Let's look at how at to get the `IRDeltaParallel` value using a `MarketDataShockBasedScenario`. We calculate delta as a 2-sided 1bp bump." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "swap = IRSwap(PayReceive.Pay, '10y', Currency.USD, notional_amount=10e6)\n", "swap.resolve()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "r_shock_bp = 1 / 10000\n", "ir_scenario_up = MarketDataShockBasedScenario(\n", " {MarketDataPattern('IR', mkt_asset=Currency.USD): MarketDataShock(MarketDataShockType.Absolute, r_shock_bp)}\n", ")\n", "ir_scenario_down = MarketDataShockBasedScenario(\n", " {MarketDataPattern('IR', mkt_asset=Currency.USD): MarketDataShock(MarketDataShockType.Absolute, -r_shock_bp)}\n", ")\n", "with PricingContext(market_data_location='NYC'):\n", " delta = swap.calc(IRDeltaParallel)\n", " with ir_scenario_up:\n", " up = swap.dollar_price()\n", " with ir_scenario_down:\n", " down = swap.dollar_price()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "combined = (up.result() - down.result()) / 2\n", "# should give the same value\n", "print(f'Delta direct={delta.result():.0f}, Delta by shocks={((up.result() - down.result()) / 2):.0f}')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/02_market_shock/010205_vega_shock_equivalent.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwaption\n", "from gs_quant.markets import PricingContext\n", "from gs_quant.session import Environment, GsSession\n", "from gs_quant.risk import (\n", " MarketDataShockBasedScenario,\n", " MarketDataPattern,\n", " MarketDataShock,\n", " MarketDataShockType,\n", " IRVegaParallel,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Risk measures are short-hand for a set of predefined scenarios. Let's look at how at to get the `IRVegaParallel` value using a `MarketDataShockBasedScenario`. We calculate delta as a 2-sided 1bp bump." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "swaption = IRSwaption(PayReceive.Pay, '10y', Currency.USD, notional_amount=10e6)\n", "swaption.resolve()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "r_shock_bp = 1 / 10000\n", "ir_scenario_up = MarketDataShockBasedScenario(\n", " {MarketDataPattern('IR Vol'): MarketDataShock(MarketDataShockType.Absolute, r_shock_bp)}\n", ")\n", "ir_scenario_down = MarketDataShockBasedScenario(\n", " {MarketDataPattern('IR Vol'): MarketDataShock(MarketDataShockType.Absolute, -r_shock_bp)}\n", ")\n", "with PricingContext():\n", " vega = swaption.calc(IRVegaParallel)\n", " with ir_scenario_up:\n", " up = swaption.dollar_price()\n", " with ir_scenario_down:\n", " down = swaption.dollar_price()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "combined = (up.result() - down.result()) / 2\n", "# should give the same value\n", "combined - delta.result()\n", "\n", "print(f'Vega direct={vega.result():.0f}, Vega by shocks={((up.result() - down.result()) / 2):.0f}')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/02_market_shock/010206_eq_spot_shock.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import EqOption\n", "from gs_quant.session import Environment, GsSession\n", "from gs_quant.risk import MarketDataShockBasedScenario, MarketDataPattern, MarketDataShock, MarketDataShockType\n", "from gs_quant.risk import EqDelta" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "o = EqOption('.SPX')\n", "o.resolve()\n", "p = o.price()\n", "d1 = o.calc(EqDelta)\n", "print(f'Base price: {p:,.2f}')\n", "print(f'Equity Delta: {d1:,.2f}')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# shock equity spot up and down\n", "with MarketDataShockBasedScenario(\n", " shocks={\n", " MarketDataPattern(mkt_type='Eq', mkt_class='Spot', mkt_quoting_style='PropSpread'): MarketDataShock(\n", " MarketDataShockType.Override, 0.005\n", " )\n", " }\n", "):\n", " price_up = o.price()\n", "with MarketDataShockBasedScenario(\n", " shocks={\n", " MarketDataPattern(mkt_type='Eq', mkt_class='Spot', mkt_quoting_style='PropSpread'): MarketDataShock(\n", " MarketDataShockType.Override, -0.005\n", " )\n", " }\n", "):\n", " price_down = o.price()\n", "d2 = (price_up - price_down) / 0.01\n", "\n", "print(f'Equity Delta via shocking: {d2:,.2f}')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/02_market_shock/010207_fx_shock_examples.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "c1f25a7c", "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import FXOption\n", "from gs_quant.risk import MarketDataShockBasedScenario, MarketDataPattern, MarketDataShock, MarketDataShockType\n", "from gs_quant.risk import FXSpot, Price, FXAnnualImpliedVol, FXAnnualATMImpliedVol\n", "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None)" ] }, { "cell_type": "code", "execution_count": null, "id": "5efa07c6", "metadata": {}, "outputs": [], "source": [ "fxoption = FXOption(pair='EURUSD', expiration_date='3m', premium=0)\n", "unshifted_res = fxoption.calc((FXSpot, Price, FXAnnualImpliedVol, FXAnnualATMImpliedVol))\n", "unshifted_res.to_frame()" ] }, { "cell_type": "code", "execution_count": null, "id": "a62b8561", "metadata": {}, "outputs": [], "source": [ "# set FX Spot to X\n", "\n", "x = 1.1\n", "\n", "mkt_shock = MarketDataShockBasedScenario(\n", " shocks={MarketDataPattern('FX', 'USD/EUR'): MarketDataShock(MarketDataShockType.Override, x)}\n", ")\n", "\n", "with mkt_shock:\n", " shifted_spot = fxoption.calc(FXSpot)\n", "shifted_spot" ] }, { "cell_type": "code", "execution_count": null, "id": "b55ee1cf", "metadata": {}, "outputs": [], "source": [ "# Bump FX spot by x\n", "\n", "# relative - 5%\n", "x = 0.05\n", "\n", "mkt_shock = MarketDataShockBasedScenario(\n", " shocks={MarketDataPattern('FX', 'USD/EUR'): MarketDataShock(MarketDataShockType.Proportional, x)}\n", ")\n", "\n", "with mkt_shock:\n", " shifted_spot = fxoption.calc(FXSpot)\n", "print(shifted_spot)\n", "print(unshifted_res[FXSpot] * (1 + x))\n", "\n", "# absolute - 0.08\n", "x = 0.08\n", "\n", "mkt_shock = MarketDataShockBasedScenario(\n", " shocks={MarketDataPattern('FX', 'USD/EUR'): MarketDataShock(MarketDataShockType.Absolute, x)}\n", ")\n", "\n", "with mkt_shock:\n", " shifted_spot = fxoption.calc(FXSpot)\n", "print(shifted_spot)\n", "print(unshifted_res[FXSpot] + x)" ] }, { "cell_type": "code", "execution_count": null, "id": "0ffacf30", "metadata": {}, "outputs": [], "source": [ "# Set Strike Vol to x & Bump Strike Vol to x\n", "\n", "x = 0.06\n", "\n", "mkt_shock = MarketDataShockBasedScenario(\n", " shocks={\n", " MarketDataPattern('FX VOL', 'USD/EUR', 'ATM VOL'): MarketDataShock(MarketDataShockType.Override, x),\n", " MarketDataPattern('FX VOL', 'USD/EUR', 'BF 25'): MarketDataShock(MarketDataShockType.Override, 0),\n", " MarketDataPattern('FX VOL', 'USD/EUR', 'RR 25'): MarketDataShock(MarketDataShockType.Override, 0),\n", " }\n", ")\n", "\n", "with mkt_shock:\n", " shifted_vol = fxoption.calc(FXAnnualImpliedVol)\n", "print(shifted_vol)" ] }, { "cell_type": "code", "execution_count": null, "id": "6defbc3d", "metadata": {}, "outputs": [], "source": [ "# Parallel Shift the ATMVol Curve by x\n", "\n", "# relative - 5%\n", "x = 0.05\n", "\n", "mkt_shock = MarketDataShockBasedScenario(\n", " shocks={MarketDataPattern('FX VOL', 'USD/EUR', 'ATM VOL'): MarketDataShock(MarketDataShockType.Proportional, x)}\n", ")\n", "\n", "with mkt_shock:\n", " shifted_vol = fxoption.calc(FXAnnualATMImpliedVol)\n", "print(shifted_vol)\n", "print(unshifted_res[FXAnnualATMImpliedVol] * (1 + x))\n", "\n", "# absolute - 0.01\n", "x = 0.01\n", "\n", "mkt_shock = MarketDataShockBasedScenario(\n", " shocks={MarketDataPattern('FX VOL', 'USD/EUR', 'ATM VOL'): MarketDataShock(MarketDataShockType.Absolute, x)}\n", ")\n", "\n", "with mkt_shock:\n", " shifted_vol = fxoption.calc(FXAnnualATMImpliedVol)\n", "print(shifted_vol)\n", "print(unshifted_res[FXAnnualATMImpliedVol] + x)" ] }, { "cell_type": "code", "execution_count": null, "id": "ecaf3874", "metadata": {}, "outputs": [], "source": [ "# set atmvol at expiry X to y\n", "\n", "x = '3m'\n", "y = 0.62\n", "\n", "mkt_shock = MarketDataShockBasedScenario(\n", " shocks={\n", " MarketDataPattern('FX VOL', 'USD/EUR', 'ATM VOL', mkt_point=[x]): MarketDataShock(\n", " MarketDataShockType.Override, y\n", " )\n", " }\n", ")\n", "\n", "with mkt_shock:\n", " shifted_atmvol = fxoption.calc(FXAnnualATMImpliedVol)\n", "shifted_atmvol" ] }, { "cell_type": "code", "execution_count": null, "id": "d53e3f2c", "metadata": {}, "outputs": [], "source": [ "# Parallel Shift the ATMVol at expiry x by y\n", "\n", "x = '3m'\n", "# relative - 5%\n", "y = 0.05\n", "\n", "mkt_shock = MarketDataShockBasedScenario(\n", " shocks={\n", " MarketDataPattern('FX VOL', 'USD/EUR', 'ATM VOL', mkt_point=[x]): MarketDataShock(\n", " MarketDataShockType.Proportional, y\n", " )\n", " }\n", ")\n", "\n", "with mkt_shock:\n", " shifted_vol = fxoption.calc(FXAnnualATMImpliedVol)\n", "print(shifted_vol)\n", "print(unshifted_res[FXAnnualATMImpliedVol] * (1 + y))\n", "\n", "# absolute - 0.01\n", "y = 0.01\n", "\n", "mkt_shock = MarketDataShockBasedScenario(\n", " shocks={\n", " MarketDataPattern('FX VOL', 'USD/EUR', 'ATM VOL', mkt_point=[x]): MarketDataShock(\n", " MarketDataShockType.Absolute, y\n", " )\n", " }\n", ")\n", "\n", "with mkt_shock:\n", " shifted_vol = fxoption.calc(FXAnnualATMImpliedVol)\n", "print(shifted_vol)\n", "print(unshifted_res[FXAnnualATMImpliedVol] + y)" ] }, { "cell_type": "code", "execution_count": null, "id": "3c14d137", "metadata": {}, "outputs": [], "source": [ "# parallel shift the yield curve\n", "\n", "ir_spot_scenario = MarketDataShockBasedScenario(\n", " shocks={MarketDataPattern('IR', 'EUR'): MarketDataShock(MarketDataShockType.Absolute, 50 / 10000)}\n", ")\n", "\n", "with ir_spot_scenario:\n", " yield_curve_shift = fxoption.price()\n", "\n", "print(yield_curve_shift)\n", "print(unshifted_res[Price])" ] }, { "cell_type": "code", "execution_count": null, "id": "b93b3651", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/03_composite_shocks/010300_rate_composite_shocks.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.risk import RollFwd, MarketDataPattern, MarketDataShock, MarketDataShockBasedScenario, MarketDataShockType\n", "from gs_quant.session import Environment, GsSession\n", "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwap" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# define swap\n", "swap = IRSwap(termination_date='10y', notional_currency=Currency.USD, pay_or_receive=PayReceive.Pay, fixed_rate=0.007)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# 1bp Spot Shock\n", "ir_1bp_scenario = MarketDataShockBasedScenario(\n", " shocks={\n", " MarketDataPattern('IR', 'USD'): MarketDataShock(shock_type=MarketDataShockType.Absolute, value=1e-4),\n", " MarketDataPattern('IR Reset', 'USD-3m'): MarketDataShock(shock_type=MarketDataShockType.Absolute, value=1e-4),\n", " }\n", ")\n", "\n", "# Roll Forward 22b\n", "rollfwd_22b_scenario = RollFwd(date='22b', holiday_calendar='NYC')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Roll Forward 22b and apply 1bp Spot Shock\n", "with rollfwd_22b_scenario, ir_1bp_scenario:\n", " rollfwd_22b_then_ir_1bp_price = swap.price()\n", "\n", "# Look at the difference between scenario and base prices\n", "print('Base price: {:,.2f}'.format(swap.price()))\n", "print('Scenario price: {:,.2f}'.format(rollfwd_22b_then_ir_1bp_price))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/03_composite_shocks/010301_multiple_shocks.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwap\n", "from gs_quant.markets import PricingContext\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.risk import MarketDataPattern, MarketDataShock, MarketDataShockBasedScenario, MarketDataShockType\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": true, "jupyter": { "outputs_hidden": true } }, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": true, "jupyter": { "outputs_hidden": true } }, "outputs": [], "source": [ "# define two swaps\n", "swap1 = IRSwap(\n", " termination_date='10y', notional_currency=Currency.USD, pay_or_receive=PayReceive.Pay, fixed_rate=0.007, name='10y'\n", ")\n", "swap2 = IRSwap(\n", " termination_date='15y', notional_currency=Currency.USD, pay_or_receive=PayReceive.Pay, fixed_rate=0.007, name='15y'\n", ")" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": true, "jupyter": { "outputs_hidden": true } }, "outputs": [], "source": [ "# create a couple of scenarios\n", "\n", "# 1bp Spot Shock\n", "ir_1bp_scenario = MarketDataShockBasedScenario(\n", " shocks={MarketDataPattern('IR', 'USD'): MarketDataShock(shock_type=MarketDataShockType.Absolute, value=1e-4)}\n", ")\n", "\n", "# 2bp Spot Shock\n", "ir_2bp_scenario = MarketDataShockBasedScenario(\n", " shocks={MarketDataPattern('IR', 'USD'): MarketDataShock(shock_type=MarketDataShockType.Absolute, value=1e-4)}\n", ")" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": true, "jupyter": { "outputs_hidden": true } }, "outputs": [], "source": [ "# if we want to apply one scenario to many trades it is best to join those trades into a portfolio and send this in one calc\n", "\n", "portfolio = Portfolio([swap1, swap2], name='portfolio')\n", "\n", "# note you can append (and pop) trades into a portfolio but this is generally less efficient than just creating with list comprehension\n", "# also portfolios can themselves contain portfolios" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": true, "jupyter": { "outputs_hidden": true } }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "H:\\code\\gs_quant\\gs_quant\\risk\\results.py:80: FutureWarning: The provided callable is currently using DataFrameGroupBy.sum. In a future version of pandas, the provided callable will be used directly. To keep current behavior pass the string \"sum\" instead.\n", " pivot_df = df.pivot_table(values=values, index=index, columns=columns, aggfunc=aggfunc)\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
risk_measurePrice
instrument_name
10y2.295177e+07
15y3.253489e+07
\n", "
" ], "text/plain": [ "risk_measure Price\n", "instrument_name \n", "10y 2.295177e+07\n", "15y 3.253489e+07" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# many trades with one scenario\n", "\n", "with ir_1bp_scenario:\n", " prices = portfolio.price()\n", "\n", "prices.to_frame()" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": true, "jupyter": { "outputs_hidden": true } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "22951766.87812186\n", "32534888.041928925\n" ] } ], "source": [ "# now I want to apply 1 scenario to 1 trade and the other scenario to the 2nd trade\n", "# you could do this sequentially and that will work but be slow. Better to nest the scenarios in a overarching context\n", "\n", "with PricingContext(): # this context just wraps the calcs so that they will be run in parallel\n", " with ir_1bp_scenario:\n", " p1 = swap1.price()\n", " with ir_2bp_scenario:\n", " p2 = swap2.price()\n", "\n", "print(p1.result())\n", "print(p2.result())" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true, "jupyter": { "outputs_hidden": true } }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/04_curve_shock/010400_parallel_curve_shock.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": false } }, "outputs": [], "source": [ "from gs_quant.session import Environment, GsSession\n", "from gs_quant.instrument import IRSwaption\n", "from gs_quant.risk import CurveScenario, MarketDataPattern\n", "from gs_quant.markets import MarketDataCoordinate\n", "import matplotlib.pyplot as plt\n", "import pandas as pd" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "swaption = IRSwaption('Receive', '5y', 'USD', expiration_date='13m', strike='atm')\n", "swaption.resolve()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "original_price = swaption.price()\n", "market_data = swaption.market().market_data_dict\n", "print('Base price: {:,.2f}'.format(original_price))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" }, "scrolled": true }, "outputs": [], "source": [ "# Price the swaption under a curve move of 10bp parallel shift\n", "parallel_shift_scenario = CurveScenario(market_data_pattern=MarketDataPattern('IR', 'USD'), parallel_shift=10)\n", "\n", "with parallel_shift_scenario:\n", " swaption_parallel_shift = swaption.price()\n", " market_data_parallel_shift = swaption.market().market_data_dict\n", "print('Price under parallel shift: {:,.2f}'.format(swaption_parallel_shift))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Compare swap rate market data coordinates before and after curve scenario shock\n", "coord = MarketDataCoordinate(mkt_type=\"IR\", mkt_asset=\"USD\")\n", "market_data_df = pd.DataFrame(\n", " [\n", " {\n", " mkt_data: value * 1e4\n", " for mkt_data, value in market_data.items()\n", " if (mkt_data.mkt_type == \"IR\" and mkt_data.mkt_asset == \"USD\" and mkt_data.mkt_class == \"SWAP OIS\")\n", " },\n", " {\n", " mkt_data: value * 1e4\n", " for mkt_data, value in market_data_parallel_shift.items()\n", " if (mkt_data.mkt_type == \"IR\" and mkt_data.mkt_asset == \"USD\" and mkt_data.mkt_class == \"SWAP OIS\")\n", " },\n", " ],\n", " index=['Values', 'Shocked values'],\n", ").transpose()\n", "market_data_df" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Plotting swap rate market data before and after curve scenario shock\n", "swap_curve = pd.DataFrame.from_dict(\n", " {int(''.join(list(filter(str.isdigit, str(v))))): market_data_df.loc[v] for v in market_data_df.index},\n", " orient='index',\n", ")\n", "\n", "swap_curve['Shock'] = swap_curve['Shocked values'] - swap_curve['Values']\n", "swap_curve.plot(figsize=(12, 8), title='USD Swap Curve Before and After Parallel Shock')\n", "plt.xlabel('Tenor (years)')\n", "plt.ylabel('bp')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/04_curve_shock/010401_curve_shock_midpoint.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import pandas as pd\n", "from gs_quant.common import PayReceive\n", "from gs_quant.instrument import IRSwaption\n", "from gs_quant.risk import CurveScenario, MarketDataPattern\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "swaption = IRSwaption(PayReceive.Receive, '5y', 'USD', expiration_date='13m', strike='atm')\n", "swaption.resolve()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "original_price = swaption.price()\n", "# retrieve the market data our instrument is sensitive to.\n", "market_data = swaption.market().market_data_dict\n", "print('Base price: {:,.2f}'.format(original_price))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Price the swaption under a curve shift of 10bp\n", "# Pivot point is the tenor at which curve shift =0 and influences the type and shape of curve shift\n", "# When pivot_point is not set, pivot_point is defaulted to the mid of tenor_end and tenor_start\n", "# tenor_start and tenor_end are mandatory fields to be specified with curve_shift\n", "curve_shift_scenario = CurveScenario(\n", " market_data_pattern=MarketDataPattern('IR', 'USD'), curve_shift=10, tenor_start=15, tenor_end=25\n", ")\n", "\n", "with curve_shift_scenario:\n", " swaption_curve_shift = swaption.price()\n", " market_data_curve_shift = swaption.market().market_data_dict\n", "\n", "print('Price under curve shift: {:,.2f}'.format(swaption_curve_shift))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Compare swap rate market data coordinates before and after curve scenario shock\n", "market_data_df = pd.DataFrame(\n", " [\n", " {\n", " mkt_data: value * 1e4\n", " for mkt_data, value in market_data.items()\n", " if (mkt_data.mkt_type == \"IR\" and mkt_data.mkt_asset == \"USD\" and mkt_data.mkt_class == \"SWAP OIS\")\n", " },\n", " {\n", " mkt_data: value * 1e4\n", " for mkt_data, value in market_data_curve_shift.items()\n", " if (mkt_data.mkt_type == \"IR\" and mkt_data.mkt_asset == \"USD\" and mkt_data.mkt_class == \"SWAP OIS\")\n", " },\n", " ],\n", " index=['Values', 'Shocked values'],\n", ").transpose()\n", "market_data_df" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Plotting swap rate market data before and after curve scenario shock\n", "swap_curve = pd.DataFrame.from_dict(\n", " {int(''.join(list(filter(str.isdigit, str(v))))): market_data_df.loc[v] for v in market_data_df.index},\n", " orient='index',\n", ")\n", "\n", "swap_curve['Shock'] = swap_curve['Shocked values'] - swap_curve['Values']\n", "swap_curve_plot = swap_curve.plot(figsize=(12, 8), title='USD Swap Curve Before and After Midpoint Curve Shock')\n", "plt.xlabel('Tenor (years)')\n", "plt.ylabel('bp')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/04_curve_shock/010402_bear_steepener_curve_shock.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import Environment, GsSession\n", "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwaption\n", "from gs_quant.risk import CurveScenario, MarketDataPattern\n", "import matplotlib.pyplot as plt\n", "import pandas as pd" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "swaption = IRSwaption(PayReceive.Receive, '5y', Currency.USD, expiration_date='13m', strike='atm')\n", "swaption.resolve()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "original_price = swaption.price()\n", "# retrieve the market data our instrument is sensitive to.\n", "market_data = swaption.market().market_data_dict\n", "print('Base price: {:,.2f}'.format(original_price))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Pivot point is the tenor at which curve shift =0 and influences the type and shape of curve shift\n", "# Price the swaption under a bear steepener scenario of 10bp\n", "bear_steepener_scenario = CurveScenario(\n", " market_data_pattern=MarketDataPattern('IR', 'USD'), curve_shift=10, tenor_start=15, tenor_end=25, pivot_point=15\n", ")\n", "\n", "with bear_steepener_scenario:\n", " swaption_bear_steepener = swaption.price()\n", " market_data_bear_steepener = swaption.market().market_data_dict\n", "\n", "print('Price under bear steepener curve shift: {:,.2f}'.format(swaption_bear_steepener))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": false }, "outputs": [], "source": [ "# Compare swap rate market data coordinates before and after curve scenario shock\n", "market_data_df = pd.DataFrame(\n", " [\n", " {\n", " mkt_data: value * 1e4\n", " for mkt_data, value in market_data.items()\n", " if (mkt_data.mkt_type == \"IR\" and mkt_data.mkt_asset == \"USD\" and mkt_data.mkt_class == \"SWAP OIS\")\n", " },\n", " {\n", " mkt_data: value * 1e4\n", " for mkt_data, value in market_data_bear_steepener.items()\n", " if (mkt_data.mkt_type == \"IR\" and mkt_data.mkt_asset == \"USD\" and mkt_data.mkt_class == \"SWAP OIS\")\n", " },\n", " ],\n", " index=['Values', 'Shocked values'],\n", ").transpose()\n", "market_data_df" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "# Plotting swap rate market data before and after curve scenario shock\n", "swap_curve = pd.DataFrame.from_dict(\n", " {int(''.join(list(filter(str.isdigit, str(v))))): market_data_df.loc[v] for v in market_data_df.index},\n", " orient='index',\n", ")\n", "\n", "swap_curve['Shock'] = swap_curve['Shocked values'] - swap_curve['Values']\n", "swap_curve.plot(\n", " figsize=(12, 8),\n", " title='USD Swap Curve Before and After {}bp Bear Steepening Shock'.format(bear_steepener_scenario.curve_shift),\n", ")\n", "plt.xlabel('Tenor (years)')\n", "plt.ylabel('bp')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/04_curve_shock/010403_bull_steepener_curve_shock.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import Environment, GsSession\n", "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwaption\n", "from gs_quant.risk import CurveScenario, MarketDataPattern\n", "import matplotlib.pyplot as plt\n", "import pandas as pd" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "swaption = IRSwaption(PayReceive.Receive, '5y', Currency.USD, expiration_date='13m', strike='atm')\n", "swaption.resolve()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "original_price = swaption.price()\n", "# retrieve the market data our instrument is sensitive to.\n", "market_data = swaption.market().market_data_dict\n", "print('Base price: {:,.2f}'.format(original_price))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Pivot point is the tenor at which curve shift =0 and influences the type and shape of curve shift\n", "# Price the swaption under a bull steepener scenario of 10bp\n", "bull_steepener_scenario = CurveScenario(\n", " market_data_pattern=MarketDataPattern('IR', 'USD'), curve_shift=10, tenor_start=15, tenor_end=25, pivot_point=25\n", ")\n", "\n", "with bull_steepener_scenario:\n", " swaption_bull_steepener = swaption.price()\n", " market_data_bull_steepener = swaption.market().market_data_dict\n", "\n", "print('Price under bull steepener curve shift: {:,.2f}'.format(swaption_bull_steepener))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Compare swap rate market data coordinates before and after curve scenario shock\n", "market_data_df = pd.DataFrame(\n", " [\n", " {\n", " mkt_data: value * 1e4\n", " for mkt_data, value in market_data.items()\n", " if (mkt_data.mkt_type == \"IR\" and mkt_data.mkt_asset == \"USD\" and mkt_data.mkt_class == \"SWAP OIS\")\n", " },\n", " {\n", " mkt_data: value * 1e4\n", " for mkt_data, value in market_data_bull_steepener.items()\n", " if (mkt_data.mkt_type == \"IR\" and mkt_data.mkt_asset == \"USD\" and mkt_data.mkt_class == \"SWAP OIS\")\n", " },\n", " ],\n", " index=['Values', 'Shocked values'],\n", ").transpose()\n", "market_data_df" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Plotting swap rate market data before and after curve scenario shock\n", "swap_curve = pd.DataFrame.from_dict(\n", " {int(''.join(list(filter(str.isdigit, str(v))))): market_data_df.loc[v] for v in market_data_df.index},\n", " orient='index',\n", ")\n", "\n", "swap_curve['Shock'] = swap_curve['Shocked values'] - swap_curve['Values']\n", "swap_curve.plot(\n", " figsize=(12, 8),\n", " title='USD Swap Curve Before and After {}bp bull Steepening Shock'.format(bull_steepener_scenario.curve_shift),\n", ")\n", "plt.xlabel('Tenor (years)')\n", "plt.ylabel('bp')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/04_curve_shock/010404_bear_flattener_curve_shock.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import Environment, GsSession\n", "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwaption\n", "from gs_quant.risk import CurveScenario, MarketDataPattern\n", "import matplotlib.pyplot as plt\n", "import pandas as pd" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "swaption = IRSwaption(PayReceive.Receive, '5y', Currency.USD, expiration_date='13m', strike='atm')\n", "swaption.resolve()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "original_price = swaption.price()\n", "# retrieve the market data our instrument is sensitive to.\n", "market_data = swaption.market().market_data_dict\n", "print('Base price: {:,.2f}'.format(original_price))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Pivot point is the tenor at which curve shift =0 and influences the type and shape of curve shift\n", "# Price the swaption under a bear flattener scenario of -10bp\n", "bear_flattener_scenario = CurveScenario(\n", " market_data_pattern=MarketDataPattern('IR', 'USD'), curve_shift=-10, tenor_start=15, tenor_end=25, pivot_point=25\n", ")\n", "\n", "with bear_flattener_scenario:\n", " swaption_bear_flattener = swaption.price()\n", " market_data_bear_flattener = swaption.market().market_data_dict\n", "\n", "print('Price under bear flattener curve shift: {:,.2f}'.format(swaption_bear_flattener))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Compare swap rate market data coordinates before and after curve scenario shock\n", "market_data_df = pd.DataFrame(\n", " [\n", " {\n", " mkt_data: value * 1e4\n", " for mkt_data, value in market_data.items()\n", " if (mkt_data.mkt_type == \"IR\" and mkt_data.mkt_asset == \"USD\" and mkt_data.mkt_class == \"SWAP OIS\")\n", " },\n", " {\n", " mkt_data: value * 1e4\n", " for mkt_data, value in market_data_bear_flattener.items()\n", " if (mkt_data.mkt_type == \"IR\" and mkt_data.mkt_asset == \"USD\" and mkt_data.mkt_class == \"SWAP OIS\")\n", " },\n", " ],\n", " index=['Values', 'Shocked values'],\n", ").transpose()\n", "market_data_df" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Plotting swap rate market data before and after curve scenario shock\n", "swap_curve = pd.DataFrame.from_dict(\n", " {int(''.join(list(filter(str.isdigit, str(v))))): market_data_df.loc[v] for v in market_data_df.index},\n", " orient='index',\n", ")\n", "\n", "swap_curve['Shock'] = swap_curve['Shocked values'] - swap_curve['Values']\n", "swap_curve.plot(\n", " figsize=(12, 8),\n", " title='USD Swap Curve Before and After {}bp Bear flattening Shock'.format(bear_flattener_scenario.curve_shift),\n", ")\n", "plt.xlabel('Tenor (years)')\n", "plt.ylabel('bp')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/04_curve_shock/010405_bull_flattener_curve_shock.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import Environment, GsSession\n", "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwaption\n", "from gs_quant.risk import CurveScenario, MarketDataPattern\n", "import matplotlib.pyplot as plt\n", "import pandas as pd" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "swaption = IRSwaption(PayReceive.Receive, '5y', Currency.USD, expiration_date='13m', strike='atm')\n", "swaption.resolve()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "original_price = swaption.price()\n", "# retrieve the market data our instrument is sensitive to.\n", "market_data = swaption.market().market_data_dict\n", "print('Base price: {:,.2f}'.format(original_price))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Pivot point is the tenor at which curve shift =0 and influences the type and shape of curve shift\n", "# Price the swaption under a bull flattener scenario of -10bp\n", "bull_flattener_scenario = CurveScenario(\n", " market_data_pattern=MarketDataPattern('IR', 'USD'), curve_shift=-10, tenor_start=15, tenor_end=25, pivot_point=15\n", ")\n", "\n", "with bull_flattener_scenario:\n", " swaption_bull_flattener = swaption.price()\n", " market_data_bull_flattener = swaption.market().market_data_dict\n", "\n", "print('Price under bull flattener curve shift: {:,.2f}'.format(swaption_bull_flattener))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Compare swap rate market data coordinates before and after curve scenario shock\n", "market_data_df = pd.DataFrame(\n", " [\n", " {\n", " mkt_data: value * 1e4\n", " for mkt_data, value in market_data.items()\n", " if (mkt_data.mkt_type == \"IR\" and mkt_data.mkt_asset == \"USD\" and mkt_data.mkt_class == \"SWAP OIS\")\n", " },\n", " {\n", " mkt_data: value * 1e4\n", " for mkt_data, value in market_data_bull_flattener.items()\n", " if (mkt_data.mkt_type == \"IR\" and mkt_data.mkt_asset == \"USD\" and mkt_data.mkt_class == \"SWAP OIS\")\n", " },\n", " ],\n", " index=['Values', 'Shocked values'],\n", ").transpose()\n", "market_data_df" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Plotting swap rate market data before and after curve scenario shock\n", "swap_curve = pd.DataFrame.from_dict(\n", " {int(''.join(list(filter(str.isdigit, str(v))))): market_data_df.loc[v] for v in market_data_df.index},\n", " orient='index',\n", ")\n", "\n", "swap_curve['Shock'] = swap_curve['Shocked values'] - swap_curve['Values']\n", "swap_curve.plot(\n", " figsize=(12, 8),\n", " title='USD Swap Curve Before and After {}bp bull flattening Shock'.format(bull_flattener_scenario.curve_shift),\n", ")\n", "plt.xlabel('Tenor (years)')\n", "plt.ylabel('bp')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/05_market_override/010501_eq_spot_override.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import OptionType, OptionStyle\n", "from gs_quant.instrument import EqOption\n", "from gs_quant.session import Environment, GsSession\n", "from gs_quant.risk import MarketDataShockBasedScenario, MarketDataPattern, MarketDataShock, MarketDataShockType" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "eq_spot_scenario = MarketDataShockBasedScenario(\n", " shocks={\n", " MarketDataPattern('Eq', '.STOXX50E', 'Reference Spot', mkt_quoting_style='Price'): MarketDataShock(\n", " MarketDataShockType.Override, 3800\n", " )\n", " }\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "option = EqOption(\n", " '.STOXX50E', expirationDate='3m', strikePrice='ATM', optionType=OptionType.Call, optionStyle=OptionStyle.European\n", ")\n", "\n", "with eq_spot_scenario:\n", " option_spot_scenario_price = option.price()\n", "\n", "# Look at the difference between scenario and base prices\n", "print('Base price: {:,.2f}'.format(option.price()))\n", "print('Spot scenario price: {:,.2f}'.format(option_spot_scenario_price))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/05_market_override/010502_eq_future_override.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import OptionType, OptionStyle\n", "from gs_quant.instrument import EqOption\n", "from gs_quant.session import Environment, GsSession\n", "from gs_quant.risk import MarketDataShockBasedScenario, MarketDataPattern, MarketDataShock, MarketDataShockType" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "eq_future_scenario = MarketDataShockBasedScenario(\n", " shocks={\n", " MarketDataPattern(\n", " 'Eq Future', '.STOXX50E', 'Reference Spot', ['2021-12-17'], mkt_quoting_style='Price'\n", " ): MarketDataShock(MarketDataShockType.Override, 3800)\n", " }\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "option = EqOption(\n", " '.STOXX50E', expirationDate='3m', strikePrice='ATM', optionType=OptionType.Call, optionStyle=OptionStyle.European\n", ")\n", "\n", "with eq_future_scenario:\n", " option_future_scenario_price = option.price()\n", "\n", "# Look at the difference between scenario and base prices\n", "print('Base price: {:,.2f}'.format(option.price()))\n", "print('Future scenario price: {:,.2f}'.format(option_future_scenario_price))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/06_vol_market_override/010601_eq_vol_override_eod.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import OptionType, OptionStyle\n", "from gs_quant.instrument import EqOption\n", "from gs_quant.markets import PricingContext\n", "from gs_quant.markets.securities import AssetIdentifier\n", "from gs_quant.risk.scenario_utils import *\n", "from gs_quant.session import Environment, GsSession\n", "from gs_quant.target.common import UnderlierType\n", "from datetime import date" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics', 'read_product_data'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Construct a vol override scenario.\n", "\n", "# This example uses a sample vol dataset EDRVOL_PERCENT_EXPIRY_PREMIUM_SAMPLE\n", "# Full dataset and additional info available in the data catalog\n", "# https://marquee.gs.com/s/developer/datasets/EDRVOL_PERCENT_EXPIRY_PREMIUM\n", "\n", "eq_vol_scenario = build_eq_vol_scenario_eod(\n", " 'TMK UN',\n", " 'EDRVOL_PERCENT_EXPIRY_PREMIUM_SAMPLE',\n", " vol_date=date(2019, 6, 3),\n", " asset_name_type=AssetIdentifier.BLOOMBERG_ID,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Define an option and price with and without the vol override scenario\n", "\n", "option = EqOption(\n", " 'TMK UN',\n", " underlierType=UnderlierType.BBID,\n", " expirationDate='3m',\n", " strikePrice=95,\n", " optionType=OptionType.Call,\n", " optionStyle=OptionStyle.European,\n", ")\n", "\n", "with PricingContext(date(2019, 6, 10)):\n", " historic_option_price = option.price()\n", "\n", "with PricingContext(date(2019, 6, 10)), eq_vol_scenario:\n", " historic_option_vol_scenario_price = option.price()\n", "\n", "# Look at the difference between scenario and base prices\n", "\n", "print('Base price: {:,.4f}'.format(historic_option_price.result()))\n", "print('Scenario price: {:,.4f}'.format(historic_option_vol_scenario_price.result()))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/06_vol_market_override/010601_eq_vol_override_intraday.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import OptionType, OptionStyle\n", "from gs_quant.instrument import EqOption\n", "from gs_quant.markets import PricingContext\n", "from gs_quant.markets.securities import AssetIdentifier\n", "from gs_quant.risk.scenario_utils import *\n", "from gs_quant.session import Environment, GsSession\n", "from gs_quant.target.common import UnderlierType\n", "from datetime import date" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics', 'read_product_data'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# construct a vol override scenario.\n", "\n", "# This example uses a sample vol dataset EDRVOL_PERCENT_EXPIRY_INTRADAY_PREMIUM_SAMPLE\n", "# Full dataset and additional info available in the data catalog:\n", "# https://marquee.gs.com/s/developer/datasets/EDRVOL_PERCENT_EXPIRY_INTRADAY_PREMIUM\n", "\n", "eq_vol_scenario = build_eq_vol_scenario_intraday(\n", " 'TMK UN',\n", " 'EDRVOL_PERCENT_EXPIRY_INTRADAY_PREMIUM_SAMPLE',\n", " start_time=datetime(2019, 6, 3, 0, 0, 0),\n", " end_time=datetime(2019, 6, 4, 0, 0, 0),\n", " asset_name_type=AssetIdentifier.BLOOMBERG_ID,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Define an option and price with and without the vol override scenario\n", "\n", "option = EqOption(\n", " 'TMK UN',\n", " underlierType=UnderlierType.BBID,\n", " expirationDate='3m',\n", " strikePrice='ATM',\n", " optionType=OptionType.Call,\n", " optionStyle=OptionStyle.European,\n", ")\n", "\n", "with PricingContext(date(2019, 6, 10)):\n", " historic_option_price = option.price()\n", "\n", "with PricingContext(date(2019, 6, 10)), eq_vol_scenario:\n", " historic_option_vol_scenario_price = option.price()\n", "\n", "# Look at the difference between scenario and base prices\n", "\n", "print('Base price: {:,.2f}'.format(historic_option_price.result()))\n", "print('Scenario price: {:,.2f}'.format(historic_option_vol_scenario_price.result()))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/07_curve_overlay/010700_discount_curve_overlay.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Curve Overlay Context\n", "Scenario contexts enable the users to price and calculate risk using their own curves." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import IRSwaption\n", "from gs_quant.risk import CurveOverlay\n", "from gs_quant.session import Environment, GsSession\n", "from datetime import datetime" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Create and price a swaption\n", "swaption = IRSwaption('Pay', '5y', 'USD', expiration_date='3m')\n", "base_price = swaption.price()\n", "print('Original price: {:,.2f}'.format(base_price))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Curve Overlay Scenario \n", "A predefined scenario used to overlay discount curve or index curve. This allows the user to use customized discount factors to overwrite existing curves on graph.\n", "* discount_factors - Discount factors\n", "* dates - Dates of the discount factors.\n", "* curve_type - Discount curve or index curve.\n", "* rate_option - Rate option of the index curve.\n", "* tenor - Tenor of the index curve.\n", "* csa_term - The funding scenario of the curve.\n", "* denominated - The denominated currency of the curve.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "from gs_quant.markets import PricingContext\n", "from gs_quant.common import MarketBehaviour\n", "\n", "# Read discount factors from a csv file\n", "df = pd.read_csv(r'CurveExample.csv', sep=\",\")\n", "dates = df.MaturityDate.tolist()\n", "dates_reformat = [datetime.strptime(date, \"%d-%b-%Y\").strftime(\"%Y-%m-%d\") for date in dates]\n", "discount_factors = df.DiscountFactor.tolist()\n", "\n", "curve_overlay_scenario = CurveOverlay(\n", " dates=dates_reformat,\n", " discount_factors=discount_factors,\n", " denominated=\"USD\",\n", " csa_term=\"USD-SOFR\",\n", " curve_type=\"Discount Curve\",\n", ")\n", "with PricingContext(market_behaviour=MarketBehaviour.Calibrated):\n", " with curve_overlay_scenario:\n", " price_with_overlay = swaption.price()\n", "p = price_with_overlay.result()\n", "print('Scenario price: {:,.2f}'.format(p))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.10" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/07_curve_overlay/CurveExample.csv ================================================ Curve,CloseDate,MaturityDate,DiscountFactor USD_SOFR_EOD,5/12/2021,13-May-2021,0.999999722 USD_SOFR_EOD,5/12/2021,14-May-2021,0.999999444 USD_SOFR_EOD,5/12/2021,17-May-2021,0.999998794 USD_SOFR_EOD,5/12/2021,18-May-2021,0.999998634 USD_SOFR_EOD,5/12/2021,19-May-2021,0.999998496 USD_SOFR_EOD,5/12/2021,20-May-2021,0.999998379 USD_SOFR_EOD,5/12/2021,21-May-2021,0.999998278 USD_SOFR_EOD,5/12/2021,24-May-2021,0.999997972 USD_SOFR_EOD,5/12/2021,25-May-2021,0.99999781 USD_SOFR_EOD,5/12/2021,26-May-2021,0.999997585 USD_SOFR_EOD,5/12/2021,27-May-2021,0.999997281 USD_SOFR_EOD,5/12/2021,28-May-2021,0.999996878 USD_SOFR_EOD,5/12/2021,31-May-2021,0.999995114 USD_SOFR_EOD,5/12/2021,1-Jun-2021,0.999994438 USD_SOFR_EOD,5/12/2021,2-Jun-2021,0.999993771 USD_SOFR_EOD,5/12/2021,3-Jun-2021,0.999993145 USD_SOFR_EOD,5/12/2021,4-Jun-2021,0.99999259 USD_SOFR_EOD,5/12/2021,7-Jun-2021,0.999991475 USD_SOFR_EOD,5/12/2021,8-Jun-2021,0.999991248 USD_SOFR_EOD,5/12/2021,9-Jun-2021,0.999991072 USD_SOFR_EOD,5/12/2021,10-Jun-2021,0.999990934 USD_SOFR_EOD,5/12/2021,11-Jun-2021,0.99999082 ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/08_multi_scenario/010801_multi_scenario_shock.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "da2a5ed8", "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import IRSwaption\n", "from gs_quant.risk import CurveScenario, MarketDataPattern, MultiScenario\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "id": "71b4f043", "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "id": "2bf27b8b", "metadata": {}, "outputs": [], "source": [ "swaption = IRSwaption('Receive', '5y', 'USD', expiration_date='13m', strike='atm')\n", "swaption.resolve()" ] }, { "cell_type": "code", "execution_count": null, "id": "5956a6da", "metadata": {}, "outputs": [], "source": [ "original_price = swaption.price()\n", "market_data = swaption.market().market_data_dict\n", "print('Base price: {:,.2f}'.format(original_price))" ] }, { "cell_type": "code", "execution_count": null, "id": "88163749", "metadata": {}, "outputs": [], "source": [ "# Create multiple curve scenarios (Multi Scenario only supports a list of the same type of scenario)\n", "shifts = [5, 10, 15]\n", "list_of_scenarios = [\n", " CurveScenario(market_data_pattern=MarketDataPattern('IR', 'USD'), parallel_shift=p, name=f\"parallel shift {p}\")\n", " for p in shifts\n", "]\n", "multi_scenario = MultiScenario(scenarios=list_of_scenarios)" ] }, { "cell_type": "code", "execution_count": null, "id": "159cd685", "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Price the swaption under a multiple curve scenarios\n", "with multi_scenario:\n", " scenario_price = swaption.price()" ] }, { "cell_type": "code", "execution_count": null, "id": "c0c69ce2", "metadata": {}, "outputs": [], "source": [ "scenario_price" ] }, { "cell_type": "code", "execution_count": null, "id": "946b62d3", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/09_measure_scenario/010901_fx_override_example.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "ce061973367468ad", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "from gs_quant.instrument import FXOption\n", "from gs_quant.session import GsSession, Environment\n", "from gs_quant.common import MeasureScenario\n", "from gs_quant.markets import PricingContext\n", "from gs_quant import risk\n", "import datetime as dt" ] }, { "cell_type": "code", "execution_count": null, "id": "8b13e419dcb97542", "metadata": { "ExecuteTime": { "end_time": "2025-07-10T09:24:18.566753800Z", "start_time": "2025-07-10T09:24:12.780395500Z" }, "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "id": "2e9968f7e25e8115", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "source": [ "MeasureScenario class allows us to override inputs such as spot or volatility when pricing an instrument. Here are some examples of how to use it with FX Options." ] }, { "cell_type": "code", "execution_count": null, "id": "1a647b948e0ccc30", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "# Initial FX Option\n", "fx_option = FXOption(pair='EURJPY', expiration_date='3m', strike_price='185')\n", "base_measures = fx_option.calc((risk.FairPremiumInPercent, risk.FXAnnualImpliedVol, risk.FXSpot))\n", "base_measures.to_frame()" ] }, { "cell_type": "code", "execution_count": null, "id": "952616a7c77ff7c2", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "# Override a single input (Spot/Strike Price) with MeasureScenario context manager\n", "with MeasureScenario(FXSpot, 180):\n", " scenario_measures = fx_option.calc((risk.FairPremiumInPercent, risk.FXAnnualImpliedVol, risk.FXSpot))\n", "scenario_measures.to_frame()" ] }, { "cell_type": "markdown", "id": "bec831f54e74bd45", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "source": [ "We can see that the FXSpot value was overridden, and other variables are re-calculated to account for the new spot rate." ] }, { "cell_type": "code", "execution_count": null, "id": "d5aebf58ca8fa787", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "# Override multiple inputs (Spot rate and Price) with MeasureScenario context manager\n", "with MeasureScenario(FXSpot, 180), MeasureScenario(FXAnnualImpliedVol, 0.08):\n", " multi_scenario_measures = fx_option.calc((risk.FairPremiumInPercent, risk.FXAnnualImpliedVol, risk.FXSpot))\n", "multi_scenario_measures.to_frame()" ] }, { "cell_type": "markdown", "id": "85d3535611a8379", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "source": [ "We can also combine the measure override with other context managers, such as PricingContext" ] }, { "cell_type": "code", "execution_count": null, "id": "3fdad1581e368a05", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "# Set up time context for pricing, and override the input\n", "with PricingContext(dt.date(2025, 7, 5)):\n", " with MeasureScenario(FXSpot, 170):\n", " historic_scen_measures = fx_option.calc((risk.FairPremiumInPercent, risk.FXAnnualImpliedVol, risk.FXSpot))\n", "historic_scen_measures.result().to_frame()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.1" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/09_measure_scenario/010902_rates_override_example.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 55, "id": "ce061973367468ad", "metadata": { "ExecuteTime": { "end_time": "2025-07-10T09:28:07.778220600Z", "start_time": "2025-07-10T09:28:07.757061300Z" }, "collapsed": false }, "outputs": [], "source": [ "from gs_quant.instrument import IRSwaption\n", "from gs_quant.risk import IRAnnualImpliedVol, Price, IRFwdRate, IRAnnualATMImpliedVol, IRDelta\n", "from gs_quant.session import GsSession, Environment\n", "from gs_quant.common import MeasureScenario\n", "from gs_quant.markets import PricingContext\n", "import datetime as dt" ] }, { "cell_type": "code", "execution_count": 56, "id": "8b13e419dcb97542", "metadata": { "ExecuteTime": { "end_time": "2025-07-10T09:28:14.582241900Z", "start_time": "2025-07-10T09:28:08.144756700Z" }, "collapsed": false }, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "id": "2e9968f7e25e8115", "metadata": { "collapsed": false }, "source": [ "MeasureScenario class allows us to override inputs such as spot or volatility when pricing an instrument. Here are some examples of how to use it with Interest rate swaption." ] }, { "cell_type": "code", "execution_count": null, "id": "1a647b948e0ccc30", "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Initial IRSwaption\n", "swaption = IRSwaption(notional_currency='EUR', effective_date='1y', termination_date='10y')\n", "swaption.calc((IRAnnualImpliedVol, Price, IRFwdRate, IRAnnualATMImpliedVol)).to_frame()" ] }, { "cell_type": "code", "execution_count": null, "id": "952616a7c77ff7c2", "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Override a single input with MeasureScenario context manager\n", "with MeasureScenario(IRFwdRate, 0.024):\n", " risk = swaption.calc((IRAnnualImpliedVol, Price, IRFwdRate, IRAnnualATMImpliedVol))\n", "risk.to_frame()" ] }, { "cell_type": "markdown", "id": "bec831f54e74bd45", "metadata": { "collapsed": false }, "source": [ "We can see that the Interest rate forward rate value was overridden, and other variables are re-calculated to account for the new rate." ] }, { "cell_type": "markdown", "id": "e566074c80751e50", "metadata": { "collapsed": false }, "source": [ "We might also be interested in comparing the forward rate between the original, up, and down override scenarios." ] }, { "cell_type": "code", "execution_count": null, "id": "72d934f4c7eaa031", "metadata": { "collapsed": false }, "outputs": [], "source": [ "init = swaption.calc((IRDelta(aggregation_level='Type'), Price, IRFwdRate))\n", "init.to_frame()" ] }, { "cell_type": "code", "execution_count": 60, "id": "b9f77b4037c3b693", "metadata": { "ExecuteTime": { "end_time": "2025-07-10T09:28:32.540887600Z", "start_time": "2025-07-10T09:28:29.404805700Z" }, "collapsed": false }, "outputs": [], "source": [ "with MeasureScenario(IRFwdRate, init[IRFwdRate] + 0.0001):\n", " up = swaption.calc((Price, IRFwdRate))\n", "\n", "with MeasureScenario(IRFwdRate, init[IRFwdRate] - 0.0001):\n", " down = swaption.calc((Price, IRFwdRate))" ] }, { "cell_type": "code", "execution_count": null, "id": "f5839afc1037abd1", "metadata": { "collapsed": false }, "outputs": [], "source": [ "print(init[IRFwdRate])\n", "print(up[IRFwdRate])\n", "print(down[IRFwdRate])" ] }, { "cell_type": "markdown", "id": "d24dcf9e6bef345f", "metadata": { "collapsed": false }, "source": [ "The forward rates are close in 3 scenarios, which is expected as the swaption expiry date is in 1 year only. " ] }, { "cell_type": "markdown", "id": "b1e9c04ed2bb53be", "metadata": { "collapsed": false }, "source": [ "We can also override multiple inputs.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "2e7cec45c7b0756a", "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Override multiple inputs (with nested context managers)\n", "with MeasureScenario(IRFwdRate, 0.028):\n", " with MeasureScenario(IRAnnualImpliedVol, 0.009):\n", " risk = swaption.calc((IRAnnualImpliedVol, Price, IRFwdRate, IRAnnualATMImpliedVol))\n", "risk.to_frame()" ] }, { "cell_type": "markdown", "id": "52670824ba328019", "metadata": { "collapsed": false }, "source": [ "We can also combine the measure override with other context managers, such as PricingContext\n" ] }, { "cell_type": "code", "execution_count": null, "id": "a36367b5758d79fd", "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Combine with PricingContext context manager\n", "with PricingContext(dt.date(2025, 7, 5)):\n", " with MeasureScenario(IRAnnualImpliedVol, 0.002):\n", " risk = swaption.calc((IRAnnualImpliedVol, Price, IRFwdRate, IRAnnualATMImpliedVol))\n", "risk.result().to_frame()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/examples/09_measure_scenario/010903_eq_override_example.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 2, "id": "ce061973367468ad", "metadata": { "ExecuteTime": { "end_time": "2025-07-09T16:53:54.617771Z", "start_time": "2025-07-09T16:53:46.811176300Z" }, "collapsed": false }, "outputs": [], "source": [ "from gs_quant.instrument import EqOption\n", "from gs_quant.risk import EqAnnualImpliedVol, Price, EqSpot, EqDelta\n", "from gs_quant.session import GsSession, Environment\n", "from gs_quant.common import MeasureScenario\n", "from gs_quant.markets import PricingContext\n", "import datetime as dt" ] }, { "cell_type": "code", "execution_count": 4, "id": "8b13e419dcb97542", "metadata": { "ExecuteTime": { "end_time": "2025-07-09T16:54:13.152886800Z", "start_time": "2025-07-09T16:54:04.718992900Z" }, "collapsed": false }, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "id": "2e9968f7e25e8115", "metadata": { "collapsed": false }, "source": [ "MeasureScenario class allows us to override inputs such as spot or volatility when pricing an instrument. Here are some examples of how to use it with FX Options." ] }, { "cell_type": "code", "execution_count": null, "id": "1a647b948e0ccc30", "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Initial FX Option\n", "eq_option = EqOption('.SPX')\n", "init_eq_option = eq_option.calc((EqSpot, EqDelta, Price))\n", "init_eq_option.to_frame()" ] }, { "cell_type": "markdown", "id": "353a1c995d13edc9", "metadata": { "collapsed": false }, "source": [ "We can override inputs such as spot or volatility when pricing an instrument. Here are some examples of how to use it with Equities Options. We might be also interested in comparing delta calculated by the system and the approximation derived from the price change. " ] }, { "cell_type": "code", "execution_count": null, "id": "952616a7c77ff7c2", "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Override a single input (Spot) with MeasureScenario context manager\n", "with MeasureScenario(EqSpot, init_eq_option[EqSpot] * 1.01):\n", " eq_risks_up = eq_option.calc((EqSpot, EqDelta, Price))\n", "eq_risks_up.to_frame()" ] }, { "cell_type": "code", "execution_count": null, "id": "68bc3ab1301e268a", "metadata": { "collapsed": false }, "outputs": [], "source": [ "with MeasureScenario(EqSpot, init_eq_option[EqSpot] * 0.99):\n", " eq_risks_down = eq_option.calc((EqSpot, EqDelta, Price))\n", "eq_risks_down.to_frame()" ] }, { "cell_type": "code", "execution_count": null, "id": "fa5824ef58b1450d", "metadata": { "collapsed": false }, "outputs": [], "source": [ "print(init_eq_option[EqDelta] / 100)\n", "print(((eq_risks_up[Price] - eq_risks_down[Price]) / 2))" ] }, { "cell_type": "markdown", "id": "bec831f54e74bd45", "metadata": { "collapsed": false }, "source": [ "We can see that the delta values were close, which is expected." ] }, { "cell_type": "markdown", "id": "6230da08bd5b48b9", "metadata": { "collapsed": false }, "source": [ "We can override multiple inputs this way." ] }, { "cell_type": "code", "execution_count": null, "id": "d5aebf58ca8fa787", "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Override multiple inputs (Spot rate and Annual Implied Volatility) with MeasureScenario context manager and return multiple outputs\n", "with MeasureScenario(EqSpot, 6000):\n", " with MeasureScenario(EqAnnualImpliedVol, 0.2):\n", " eq_risks = eq_option.calc((Price, EqAnnualImpliedVol, EqSpot))\n", "eq_risks.to_frame()" ] }, { "cell_type": "markdown", "id": "85d3535611a8379", "metadata": { "collapsed": false }, "source": [ "We can also combine the measure override with other context managers, such as PricingContext" ] }, { "cell_type": "code", "execution_count": null, "id": "3fdad1581e368a05", "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Set up time context for pricing, and override the input\n", "with PricingContext(dt.date(2025, 7, 5)):\n", " with MeasureScenario(EqAnnualImpliedVol, 0.2):\n", " eq_risks = eq_option.calc((Price, EqAnnualImpliedVol, EqSpot))\n", "eq_risks.result().to_frame()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/tutorials/Pricing_Context.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "Examples require an initialized GsSession and relevant entitlements. External clients need to substitute thier own client id and client secret below. Please refer to Sessions for details." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## What is Pricing Context\n", "\n", "[`PricingContext`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.markets.PricingContext.html) is a class used for controlling pricing, market data and computation behavior when pricing instruments and calculating risk.\n", "It can be used to provide a common context which can be reused for a number of different data access or manipulation functions.\n", "\n", "More specifically, [`PricingContext`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.markets.PricingContext.html) can be used to define pricing date, date for sourcing market data, market data location as well as whether the calculation should be processed\n", "asynchronously or batched.\n", "\n", "[`HistoricalPricingContext`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.markets.HistoricalPricingContext.html) is also available to produce valuations over multiple dates. Both [`PricingContext`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.markets.PricingContext.html) and [`HistoricalPricingContext`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.markets.HistoricalPricingContext.html) are distinct from the `MarketDataContext` which provides a market state as of a given date.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Evaluating Pricing and Risk Using A Context\n", "\n", "Pricing date is used to compute the expiration date and discounting rules for a derivative instrument. For example, the expiration date of a 1-month forward will be 1 month from the current pricing date.\n", "`pricing_date` is different from `market_data_as_of` which is the date for sourcing market data and is defaulted to 1 business day before `pricing_date`.\n", "\n", " For information on how to define an instrument and compute price and risk for it, please refer to Instruments and Measures, respectively, for details. \n", "\n", "Let's import [`PricingContext`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.markets.PricingContext.html) and examine the current pricing date. It is defaulted to today when pricing an instrument.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets import PricingContext\n", "\n", "PricingContext.current.pricing_date" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you wish to change the default behaviour, you can change the default pricing context:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import datetime as dt\n", "from gs_quant.datetime import business_day_offset\n", "\n", "PricingContext.current = PricingContext(\n", " pricing_date=business_day_offset(dt.date.today(), -1, roll='preceding'), market_data_location='NYC'\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We define an [`IRSwaption`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.instrument.IRSwaption.html#gs_quant.instrument.IRSwaption/) below:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import IRSwaption\n", "from gs_quant.common import Currency, PayReceive\n", "\n", "swaption = IRSwaption(\n", " PayReceive.Receive, '5y', Currency.USD, expiration_date='13m', strike='atm+40', notional_amount=1e8\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A [`PricingContext`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.markets.PricingContext.html) may also be used as a context manager, to temporarily change pricing parameters for the scope of a computation:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "custom_date = dt.date(2019, 5, 31)\n", "\n", "with PricingContext(pricing_date=custom_date):\n", " swaption.resolve()\n", " price_f = swaption.price()\n", "\n", "print(swaption.as_dict())\n", "print(price_f.result())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that using a [`PricingContext`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.markets.PricingContext.html) as a context manager has two extra effects:\n", "\n", "1. All calls to [`price()`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.base.Priceable.html#gs_quant.base.Priceable.price), [`calc()`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.base.Priceable.html#gs_quant.base.Priceable.calc) are dispatched as a single request, on context manager exit. This allows for the communication overhead to be borne only once for multiple calculations.\n", "2. The results of these calls will be futures, whose result is the original data type. Thus, `calc(risk.IRDelta)` will return a future whose result will be a pandas DataFrame and\n", "[`dollar_price()`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.base.Priceable.html#gs_quant.base.Priceable.dollar_price), will return a future whose result will be a float.\n", "\n", "PricingContext has optional parameters of `is_async` and `is_batch`, which are discussed [below](#Controlling-Computation-Behavior)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Historical Pricing Context\n", "\n", "[`HistoricalPricingContext`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.markets.HistoricalPricingContext.html) can be used to evaluate instruments for a range of parameters, like the date range below." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets import HistoricalPricingContext\n", "import gs_quant.risk as risk\n", "\n", "start_date = dt.date(2019, 5, 1)\n", "end_date = dt.date(2019, 5, 31)\n", "\n", "with HistoricalPricingContext(start_date, end_date):\n", " swaption_vega_f = swaption.calc(risk.IRVega)\n", "\n", "swaption_vega = swaption_vega_f.result()\n", "swaption_vega.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "## Controlling Computation Behavior\n", "\n", "There are two parameters of [`PricingContext`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.markets.PricingContext.html) which determine how the calculation call should be made: `is_async` and `is_batch`. There are several considerations when deciding what the preferred behavior is.\n", "\n", "### Async\n", "\n", "`is_async` determines whether the request is processed asynchronously. This allows a unit of work to run separately from the primary application thread.\n", "\n", "If `False` (the default) the `__exit__` method of [`PricingContext`](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.markets.PricingContext.html) will block until the results are returned. If `True`, it will return immediately and the user should check the status of the returned futures, or add a callback to them in order to handle results.\n", "\n", "Setting `is_async=True` allows other work to be done while waiting for calculation results. It is best used for processing independent data; asynchronously updating records that are dependent or depended upon may lead to unexpected results.\n", "\n", "### Batch\n", "\n", "Calculation requests are handled by HTTP calls. The gateway which processes these calls has a maximum timeout of 3 minutes.\n", "For calculations that are expected to last longer than 3 minutes, `is_batch` should be set to `True`.\n", "In this mode, the computation is run asynchronously on Goldman Sachs' servers and a thread is started to listen for the results.\n", "\n", "`is_batch` is independent of `is_async` and may be used with `is_async` set either to `True` or `False`.\n", "\n", "Currently `is_batch=True` adds a small overhead to calculations. This will be eliminated in the future, at which point we will likely to make all calculations execute this way, and eliminate the `is_batch` option.\n", "\n", "\n", "### Pricing Context with Async and Batch\n", "\n", "In the example below we will examine the behavior of async and batch. We will price and calculate vega for an Interest Rate Swaption over May 2019 and do\n", "some 'work' while waiting for the result. Based on the output, one can see that some useful work can be done by setting `is_async=True` but changing `is_batch=True` doesn't\n", "offer any additional speed-up." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from time import sleep\n", "\n", "# create swaption\n", "swaption = IRSwaption(\n", " PayReceive.Receive, '5y', Currency.USD, expiration_date='13m', strike='atm+40', notional_amount=1e8\n", ")\n", "\n", "start_date = dt.date(2019, 5, 1)\n", "end_date = dt.date(2019, 5, 31)\n", "\n", "# price over date range with both async and batch = True\n", "with HistoricalPricingContext(start_date, end_date, is_async=True, is_batch=True):\n", " swaption_price = swaption.price()\n", " swaption_vega = swaption.calc(risk.IRVega)\n", "\n", "# Do some work while waiting for results. All futures will be completed on exit of PricingContext\n", "n = 0\n", "while not swaption_price.done():\n", " print(n)\n", " n += 1\n", " sleep(1)\n", "\n", "swaption_prices = swaption_price.result()\n", "swaption_vegas = swaption_vega.result()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Disclaimer\n", "This website may contain links to websites and the content of third parties (\"Third Party Content\"). We do not monitor, review or update, and do not have any control over, any Third Party Content or third party websites. We make no representation, warranty or guarantee as to the accuracy, completeness, timeliness or reliability of any Third Party Content and are not responsible for any loss or damage of any sort resulting from the use of, or for any failure of, products or services provided at or from a third party resource. If you use these links and the Third Party Content, you acknowledge that you are doing so entirely at your own risk." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/01_scenarios_and_contexts/tutorials/Scenarios.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Scenario Context\n", "Scenario contexts enable the user to price and calculate risk under varying market states and pricing environments." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import IRSwaption\n", "from gs_quant.risk import (\n", " MarketDataPattern,\n", " MarketDataShock,\n", " MarketDataShockType,\n", " MarketDataShockBasedScenario,\n", " RollFwd,\n", " CurveScenario,\n", " IndexCurveShift,\n", ")\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Create and price a swaption\n", "swaption = IRSwaption('Pay', '5y', 'USD', expiration_date='3m')\n", "base_price = swaption.price()\n", "base_price" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### MarketDataShockBasedScenario\n", "Allows the user to create a bespoke market data shock" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "MarketDataShockBasedScenario?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Scenario Creation: Shock all points on the Vol Curve by 1bp\n", "ir_vol_scenario = MarketDataShockBasedScenario(\n", " shocks={MarketDataPattern('IR Vol'): MarketDataShock(MarketDataShockType.Absolute, 1e-4)}\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Price swaption under scenario\n", "with ir_vol_scenario:\n", " scenario_price = swaption.price()\n", "\n", "scenario_price" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Swaption Price Comparison: Base vs. Shocked Vol Curves\n", "diff = scenario_price - base_price\n", "diff" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Comparing Parallel Bump Scenario w/ Vega\n", "from gs_quant.risk import IRVegaParallel\n", "\n", "vega = swaption.calc(IRVegaParallel)\n", "vega" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Curve Scenario \n", "A predefined scenario used to modify the shape of the curve with bespoke transformations - by applying both parallel and slope shifts. \n", "* market_data_pattern - Market pattern for matching curve assets (required parameter)\n", "* parallel_shift - A constant (X bps) which shifts all points by the same amount\n", "* curve_shift - A double which represents the net rate change (X bps) between tenorStart and tenorEnd\n", "* pivot_point – The tenor in years (float) at which there is zero rate change, which is between tenor_start and tenor_end inclusive, informing the type of curve shift. If not specified, pivot_point is the midpoint of tenor_start and tenor_end\n", "* tenor_start – The tenor, in years, (float) which is the starting point of the curve shift (required parameter is curve_shift is specified)\n", "* tenor_end – The tenor, in years, (float) which is the end point of the curve shift (required parameter is curve_shift is specified)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "CurveScenario?" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Scenario Creation: modify the Swap Curve by a 5bp parallel shift, 1bp slope shift pivoted at 5y point (up to 50y)\n", "curve_scenario = CurveScenario(\n", " market_data_pattern=MarketDataPattern('IR', 'USD'),\n", " parallel_shift=5,\n", " curve_shift=1,\n", " pivot_point=5,\n", " tenor_end=50,\n", " tenor_start=0,\n", ")\n", "\n", "with curve_scenario:\n", " swaption_scenario_price = swaption.price()\n", "\n", "# Look at the difference between scenario and base prices\n", "print('Base price: {:,.2f}'.format(base_price))\n", "print('Scenario price: {:,.2f}'.format(swaption_scenario_price))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### RollFwd Scenario\n", "A predefined scenario used to evolve market data and trades over a period of time\n", "* date - Absolute or Relative Date to shift markets to\n", "* realise_fwd - Roll along the forward curve or roll in spot space\n", "* holiday_calendar - Calendar to use if relative date is specified in the date parameter" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "RollFwd?" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# RollFwd Scenario - Roll forward 1 month\n", "base_price = swaption.price()\n", "with RollFwd(date='1m', holiday_calendar='NYC', realise_fwd=False):\n", " fwd_price = swaption.price()\n", "\n", "print('Base price: {:,.2f}'.format(base_price))\n", "print('Scenario price: {:,.2f}'.format(fwd_price))\n", "print('Diff: {:,.2f}'.format(fwd_price - base_price))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Index Curve Shift Scenario \n", "A predefined scenario used to modify the shape of the index curve. This allows the user to easily shock the curve including parallel shift and slope shift. Users can even specify which part of the curve for parallel shift through custom bucket. \n", "* market_data_pattern - Market pattern for matching curve assets\n", "* rate_option - Rate option of the index curve.\n", "* tenor - Tenor of the index curve.\n", "* floor - Floor size in bps of the index curve.\n", "* annualised_parallel_shift – Parallel shift in bps.\n", "* annualised_slope_shift – Annual slope shift in bps with pivot = 0.\n", "* cutoff – Cutoff time in years to stop applying the shift.\n", "* bucket_start - Start date for the custom bucket.\n", "* bucket_end - End date for the custom bucket.\n", "* bucket_shift - Bucket shift in bps for the custom bucket." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "IndexCurveShift?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Scenario Creation: modify the index curve of this swaption by a 1bp parallel shift, 1bp slope shift, pivoted default to 0 year with floor value -1bp (up to 50y)\n", "index_curve_shift = IndexCurveShift(\n", " rate_option=\"USD-LIBOR-BBA\", tenor=\"3m\", annualised_parallel_shift=1, annualised_slope_shift=1, floor=-1, cutoff=50\n", ")\n", "\n", "with index_curve_shift:\n", " swaption_scenario_price = swaption.price()\n", "\n", "# Look at the difference between scenario and base prices\n", "print('Base price: {:,.2f}'.format(base_price))\n", "print('Scenario price: {:,.2f}'.format(swaption_scenario_price))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/02_pricing_and_risk/External Demo v1.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "c6fc1426", "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "id": "63822e50", "metadata": {}, "outputs": [], "source": [ "# create what if trade\n", "\n", "from gs_quant.instrument import FXOption, FXEuropeanKnockout\n", "from gs_quant.markets.portfolio import Portfolio, Grid\n", "from gs_quant.markets import PricingContext, LiveMarket\n", "from gs_quant.risk import FXSpot, FXAnnualImpliedVol\n", "import datetime as dt\n", "import pprint\n", "\n", "pp = pprint.PrettyPrinter(indent=4)\n", "\n", "# shift+tab to preview what the parameters should be\n", "leg = FXOption(\n", " pair='EURUSD',\n", " expiration_date='3m',\n", " strike_price='25D',\n", " notional_amount='50m',\n", " premium=0,\n", " option_type='Call',\n", " buy_sell='Buy',\n", ")\n", "pp.pprint(leg.as_dict())\n", "\n", "leg.price()\n", "\n", "# by default the pricing is done as of today with last nights close rolled to today" ] }, { "cell_type": "code", "execution_count": null, "id": "6a273f03", "metadata": {}, "outputs": [], "source": [ "# if we wanted to price a trade historically in time we can do that using an historical pricing context\n", "\n", "from gs_quant.markets import HistoricalPricingContext\n", "\n", "with HistoricalPricingContext(\n", " start=dt.date(2025, 1, 3), end=dt.date(2025, 4, 1), visible_to_gs=True, market_data_location='NYC'\n", "):\n", " h = leg.price()\n", "h.result().plot()" ] }, { "cell_type": "code", "execution_count": null, "id": "ecafb3e2", "metadata": {}, "outputs": [], "source": [ "# when we price an Spot+1% trade historically what does that mean. Well that depends on whether you have resolved the trade\n", "# or not.\n", "# If not then the trade will be moving in time, on each day a new 3m spot+1% option was priced. We may want to fix the\n", "# trade as at the start date\n", "\n", "with PricingContext(pricing_date=dt.date(2025, 1, 3)):\n", " leg2 = leg.resolve(in_place=False)\n", "leg2 = leg2.result()\n", "\n", "# compare the unresolved trade to the resolved one.\n", "pp.pprint(leg2.as_dict())\n", "\n", "\n", "from gs_quant.risk import Price\n", "\n", "with HistoricalPricingContext(start=dt.date(2025, 1, 3), end=dt.date(2025, 4, 1)):\n", " h2 = leg2.calc(Price)\n", "h2.result().plot()" ] }, { "cell_type": "code", "execution_count": null, "id": "1cfeef04-117b-42e6-ab15-16b982e06d87", "metadata": {}, "outputs": [], "source": [ "# We can price live by amending the pricing context as well\n", "\n", "with PricingContext(market=LiveMarket()):\n", " s = leg.calc(FXSpot)\n", "s.result()" ] }, { "cell_type": "code", "execution_count": null, "id": "7530294f", "metadata": {}, "outputs": [], "source": [ "# we have a concept of a portfolio which behaves similarly to a trade so you can pack many trades into one object and price that. Also shows how other metrics can be referenced and called.\n", "\n", "from gs_quant.common import InOut\n", "from gs_quant.risk import FXQuotedDelta\n", "\n", "port = Portfolio(\n", " [\n", " FXEuropeanKnockout(\n", " pair='GBPUSD',\n", " expiration_date='3m',\n", " strike_price='1.25',\n", " knock_in_or_out=InOut.Out,\n", " barrier_level=f'{1.10 + x / 100}',\n", " name=f'{x}',\n", " )\n", " for x in range(25, 35)\n", " ]\n", ")\n", "\n", "\n", "print(len(port))\n", "r = port.calc((FXQuotedDelta, FXAnnualImpliedVol))\n", "r.to_frame()" ] }, { "cell_type": "code", "execution_count": null, "id": "2f8da928-96b4-4881-a06f-571b31251fe0", "metadata": {}, "outputs": [], "source": [ "# screeners are a popular usecase and there is a type of portfolio called a grid which can aid in construction of 2d grids of prices\n", "\n", "# grids take an example trade and then two parameters and the possible values of each\n", "grid = Grid(FXOption(pair='EURUSD', premium=0), 'strike_price', ['10d', '20d'], 'expiration_date', ['1m', '3m', '6m'])\n", "\n", "r = grid.price()\n", "r.to_frame()" ] }, { "cell_type": "code", "execution_count": null, "id": "573b76f2", "metadata": {}, "outputs": [], "source": [ "# we can define and apply scenarios to our pricing\n", "\n", "from gs_quant.risk import MarketDataPattern, MarketDataShock, MarketDataShockType, MarketDataShockBasedScenario\n", "\n", "parallel_shock = MarketDataShockBasedScenario(\n", " shocks={\n", " MarketDataPattern(mkt_type='FX Vol', mkt_asset='USD/EUR', mkt_class='ATM VOL'): MarketDataShock(\n", " MarketDataShockType.Absolute, 0.0001\n", " )\n", " }\n", ")\n", "\n", "with PricingContext(dt.date(2025, 2, 18)):\n", " orig_price = leg.price()\n", " with parallel_shock:\n", " res = leg.price()\n", "print(f'original price: {orig_price.result():.4f}')\n", "print(f'shock price: {res.result():.4f}')" ] }, { "cell_type": "code", "execution_count": null, "id": "6ffe240d", "metadata": {}, "outputs": [], "source": [ "# as well as pricing historically we can project the market forward and price in the future.\n", "\n", "from gs_quant.risk import RollFwd\n", "\n", "carry_scenario = RollFwd(date=dt.date(2025, 3, 15), realise_fwd=True)\n", "with carry_scenario:\n", " res = leg.price()\n", "\n", "print(f'roll fwd price: {res:.4f}')" ] }, { "cell_type": "code", "execution_count": null, "id": "e4672f11", "metadata": { "scrolled": true }, "outputs": [], "source": [ "# sometimes you may want to look at the market data used in a pricing request.\n", "\n", "from gs_quant.markets import PricingContext\n", "\n", "with PricingContext(dt.date(2024, 4, 4)):\n", " market = leg.market()\n", "market.result().market_data_dict" ] }, { "cell_type": "code", "execution_count": null, "id": "39bc9d3e", "metadata": { "scrolled": true }, "outputs": [], "source": [ "%%time\n", "# loading from a dataset\n", "\n", "\n", "from gs_quant.data import Dataset\n", "import datetime as dt\n", "\n", "\n", "dataset = Dataset('MACRO_EVENTS_CALENDAR')\n", "dataset.get_data(dt.datetime(2024, 12, 1), dt.datetime(2025, 12, 31), eventName='ISM Manufacturing PMI')" ] }, { "cell_type": "code", "execution_count": null, "id": "82f92f0e-a699-4835-953e-8f0d0b8b9f67", "metadata": {}, "outputs": [], "source": [ "irfwd_eur = Dataset('IR_SWAP_RATES').get_data(\n", " dt.date(2025, 1, 3),\n", " dt.date(2025, 1, 29),\n", " currency='EUR',\n", " effectiveTenor='1y',\n", " terminationTenor='10y',\n", " floatingRateOption='EUR-EURIBOR-TELERATE',\n", " floatingRateDesignatedMaturity='6m',\n", " clearingHouse='LCH',\n", ")\n", "irfwd_eur['rate']" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/03_portfolios/examples/.ipynb_checkpoints/030000_create_portfolio-checkpoint.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwaption\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "ename": "ModuleNotFoundError", "evalue": "No module named 'gs_quant_analytics_internal'", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)", "\u001b[1;32mC:\\Users\\kwokdo\\AppData\\Local\\Temp/ipykernel_1004/1815385159.py\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 9\u001b[0m \u001b[1;32mimport\u001b[0m \u001b[0mdatetime\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0mdt\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 10\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 11\u001b[1;33m \u001b[1;32mfrom\u001b[0m \u001b[0mgs_quant_analytics_internal\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcomponents\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mfx\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mvanilla_contour_pricer\u001b[0m \u001b[1;32mimport\u001b[0m \u001b[0mGenericPayoffContourPricer\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mVanillaPayoffContourPricer\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 12\u001b[0m \u001b[1;32mfrom\u001b[0m \u001b[0mgs_quant_analytics_internal\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcomponents\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mfx\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mexotic_contour_pricer\u001b[0m \u001b[1;32mimport\u001b[0m \u001b[0mExoticPayoffContourPricer\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 13\u001b[0m \u001b[1;32mfrom\u001b[0m \u001b[0mgs_quant_analytics_internal\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcomponents\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mxasset\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mpayoff_contour_pricer\u001b[0m \u001b[1;32mimport\u001b[0m \u001b[0mPayoffContourPricerParams\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mModuleNotFoundError\u001b[0m: No module named 'gs_quant_analytics_internal'" ] } ], "source": [ "from gs_quant.session import GsSession\n", "from gs_quant.markets import PricingContext,LiveMarket\n", "from gs_quant.datetime import date_range\n", "from gs_quant.markets import PricingContext,LiveMarket\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.risk.results import PricingFuture, PortfolioRiskResult\n", "from gs_quant.target.common import MapParameter\n", "from gs_quant_internal.boltweb import valuation\n", "import datetime as dt\n", "\n", "from gs_quant_analytics_internal.components.fx.vanilla_contour_pricer import GenericPayoffContourPricer,VanillaPayoffContourPricer\n", "from gs_quant_analytics_internal.components.fx.exotic_contour_pricer import ExoticPayoffContourPricer\n", "from gs_quant_analytics_internal.components.xasset.payoff_contour_pricer import PayoffContourPricerParams\n", "from gs_quant_analytics_internal.components.xasset.payoff_contours import PayoffContours\n", "from gs_quant.session import GsSession,Environment\n", "from gs_quant.common import AssetClass\n", "from gs_quant_analytics_internal.xasset.utils import builder_to_priceable_asset, quick_entry_to_portfolio\n", "GsSession.use()\n", "live_pricing = PricingContext(market=LiveMarket('LDN'),market_data_location='LDN',)\n", "opt = tdapi.FXOptionBuilder(\n", " over=\"JPY\",\n", " under='USD',\n", " expiry='2021-12-29',\n", " size='5M',\n", " strike='113.465',\n", " hedgetype='spot')\n", "with live_pricing:\n", " spot=opt.calc(valuation('FXSpot'))\n", " fwd=opt.calc(valuation('FXFwd'))\n", " pricepct = opt.calc(valuation('PricePct'))\n", " volatility = opt.calc(valuation('FXVol'))\n", " winghedge = opt.calc(valuation('FXCalcHedge'))\n", "\n", "opt.valuation_overrides = {\n", " 'FXSpot':spot.result(), \n", " 'FXFwd':fwd.result(),\n", " 'FXVol':volatility.result(),\n", " 'PricePct':pricepct.result()}\n", "opt.hedge = winghedge.result()\n", "with live_pricing:\n", " opt.resolve()\n", " new_portfolio = Portfolio(opt)\n", " new_portfolio.save_as_quote()\n", "print(new_portfolio.id) " ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "swaption1 = IRSwaption(PayReceive.Pay, '5y', Currency.EUR, expiration_date='3m', name='EUR-3m5y')\n", "swaption2 = IRSwaption(PayReceive.Pay, '7y', Currency.EUR, expiration_date='6m', name='EUR-6m7y')" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "portfolio = Portfolio((swaption1, swaption2))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/03_portfolios/examples/030000_create_portfolio.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwaption\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "swaption1 = IRSwaption(PayReceive.Pay, '5y', Currency.EUR, expiration_date='3m', name='EUR-3m5y')\n", "swaption2 = IRSwaption(PayReceive.Pay, '7y', Currency.EUR, expiration_date='6m', name='EUR-6m7y')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "portfolio = Portfolio((swaption1, swaption2))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/03_portfolios/examples/030001_modify_instruments.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwaption\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "swaption1 = IRSwaption(PayReceive.Pay, '5y', Currency.EUR, expiration_date='3m', name='EUR-3m5y Payer')\n", "swaption2 = IRSwaption(PayReceive.Pay, '7y', Currency.EUR, expiration_date='9m', name='EUR-9m7y Payer')\n", "swaption_midcurve = IRSwaption(\n", " PayReceive.Receive, '10y', Currency.EUR, effective_date='10y', expiration_date='6m', name='EUR-6m10y10y Receiver'\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "portfolio = Portfolio((swaption1, swaption2))\n", "print(portfolio.instruments)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "portfolio.pricables = (swaption1, swaption2, swaption_midcurve)\n", "print(portfolio.instruments)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/03_portfolios/examples/030002_extracting_instruments_and_results.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.instrument import IRSwaption\n", "from gs_quant.risk import IRVega, IRDelta, Price\n", "from gs_quant.markets import HistoricalPricingContext, PricingContext\n", "import datetime as dt\n", "import pandas as pd\n", "\n", "pd.options.display.float_format = '{:,.0f}'.format" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# instantiate session if not running in jupyter hub\n", "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Extract instruments " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "with PricingContext(pricing_date=dt.date(2019, 4, 24)):\n", " trade1 = IRSwaption(\n", " pay_or_receive='Pay',\n", " notional_currency='EUR',\n", " expiration_date='6m',\n", " termination_date='3y',\n", " strike='ATMF',\n", " name='payer',\n", " )\n", " trade2 = IRSwaption(\n", " pay_or_receive='Receive',\n", " notional_currency='EUR',\n", " expiration_date='6m',\n", " termination_date='3y',\n", " strike='ATMF+50',\n", " name='receiver',\n", " )\n", " trade2.resolve()\n", "portfolio = Portfolio((trade1, trade2))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# We can reference the instruments in the portfolio in different ways\n", "print(portfolio[1]) # by index\n", "print(portfolio[trade2]) # by instrument object - - note instrument object changes once you resolve it\n", "print(portfolio['receiver']) # by instrument name" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Extract risk results" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "with PricingContext(pricing_date=dt.date(2019, 4, 24)):\n", " results = portfolio.calc((IRVega, IRDelta, Price))\n", "type(results)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# we can extract all the risks for a single trade and it will return a dictionary of results keyed by the risk type\n", "# note that the risks can come back as a number of formats depending on what the risk is. So it could come back as a\n", "# scalar float or as a panda series or as a panda dataframe\n", "results[0]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# we can extract a single risk for a single trade in a number of ways\n", "print('{:,.0f}'.format(results[Price]['receiver'])) # by risk name and trade name\n", "print('{:,.0f}'.format(results[Price][1])) # by risk name and index of trade in portfolio\n", "print('{:,.0f}'.format(results[Price][trade2])) # by risk name and trade object\n", "\n", "# order doesn't matter when extracting results from a PorfolioRiskResult\n", "print('{:,.0f}'.format(results['receiver'][Price]))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# we can aggregate risks\n", "a = results[Price]['payer']\n", "b = results[Price]['receiver']\n", "print('{:,.0f}'.format(a + b))\n", "print('{:,.0f}'.format(results[Price].aggregate()))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# we could return the results as a dataframe\n", "pd.DataFrame(results[Price])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# if the risk is not scalar as in the case of IRDelta we can use a similar pattern but the result will be a dataframe\n", "results[IRDelta]['payer']" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# if we wanted to see the total IRDelta for this trade we could do\n", "results[IRDelta]['payer'].value.sum()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# we can aggregate dataframe results\n", "print('{:,.0f}'.format(results[IRDelta][0].value[1]))\n", "print('{:,.0f}'.format(results[IRDelta][1].value[1]))\n", "results[IRDelta].aggregate()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# when we price with an HistoricalPricingContext we introduce a new dimension to our results\n", "\n", "with HistoricalPricingContext(dt.date(2019, 4, 24), dt.date(2019, 5, 24)):\n", " hist_results = portfolio.calc((IRVega, IRDelta, Price))\n", "\n", "type(hist_results)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# now if we ask for the price of a single trade instead of a float we will receieve a panda series of floats indexed by date\n", "hist_results[Price][trade1]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# we can still aggregate in the same way and the series will be aggregated by date\n", "hist_results[Price].aggregate()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# to get a single price for a specific date we can do\n", "hist_results[Price].aggregate().at[dt.date(2019, 4, 30)]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# if we return a dataframe then the result will be concatinated with an index of the date.\n", "hist_results[IRDelta][0]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.0" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/03_portfolios/examples/030003_resolve_portfolio.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import PayReceive, Currency # import constants\n", "from gs_quant.instrument import IRSwaption # import instruments\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.session import Environment, GsSession # import sessions" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "swaption1 = IRSwaption(PayReceive.Pay, '12m', Currency.EUR, expiration_date='3m', strike='atm+50', name='EUR-3m5y')\n", "swaption2 = IRSwaption(PayReceive.Pay, '5y', Currency.EUR, expiration_date='6m', notional_amount=5e7, name='EUR-6m5y')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "portfolio = Portfolio((swaption1, swaption2))\n", "portfolio.resolve()\n", "print(portfolio['EUR-3m5y'].as_dict()) # resolved instrument in the portfolio" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "pycharm": { "stem_cell": { "cell_type": "raw", "source": [], "metadata": { "collapsed": false } } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/03_portfolios/examples/030004_price_portfolio.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwaption\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "swaption1 = IRSwaption(PayReceive.Pay, '5y', Currency.EUR, expiration_date='3m', name='EUR-3m5y')\n", "swaption2 = IRSwaption(PayReceive.Pay, '8y', Currency.EUR, expiration_date='5w', name='EUR-5w8y')\n", "portfolio = Portfolio((swaption1, swaption2))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "price_result = portfolio.price()\n", "print(price_result.aggregate()) # aggregate portfolio price" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "print(price_result['EUR-3m5y']) # price of specific instrument" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "pycharm": { "stem_cell": { "cell_type": "raw", "source": [], "metadata": { "collapsed": false } } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/03_portfolios/examples/030005_calculate_portfolio_risk.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import PayReceive, Currency # import constants\n", "from gs_quant.instrument import IRSwaption # import instruments\n", "from gs_quant.markets.portfolio import Portfolio\n", "import gs_quant.risk as risk\n", "from gs_quant.session import Environment, GsSession # import sessions" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "swaption1 = IRSwaption(PayReceive.Pay, '5y', Currency.EUR, expiration_date='3m', name='EUR-3m5y')\n", "swaption2 = IRSwaption(PayReceive.Pay, '5y', Currency.EUR, expiration_date='6m', name='EUR-6m5y')\n", "portfolio = Portfolio((swaption1, swaption2))\n", "result = portfolio.calc((risk.DollarPrice, risk.IRDelta))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# portfolio risk\n", "print(result[risk.IRDelta].aggregate())" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# instrument risk\n", "print(result[risk.DollarPrice]['EUR-3m5y'])\n", "print(result['EUR-3m5y'][risk.DollarPrice])" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/03_portfolios/examples/030006_portfolio_grid_calc.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.instrument import IRSwaption\n", "from gs_quant.risk import IRAnnualImpliedVol\n", "import seaborn as sns\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "\n", "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tails = ['1y', '3y', '5y', '10y', '15y', '20y', '25y', '30y']\n", "expiries = ['3m', '6m', '9m', '1y', '18m', '2y', '3y', '4y', '5y']\n", "pay_rec = 'Pay'\n", "ccy = 'EUR'\n", "moneyness = 25" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "portfolios = Portfolio(\n", " [\n", " Portfolio(\n", " [\n", " IRSwaption(\n", " pay_or_receive=pay_rec,\n", " notional_currency=ccy,\n", " termination_date=t,\n", " expiration_date=e,\n", " strike='ATM+{}'.format(moneyness),\n", " name=e,\n", " )\n", " for e in expiries\n", " ],\n", " name=t,\n", " )\n", " for t in tails\n", " ]\n", ")\n", "results = portfolios.calc(IRAnnualImpliedVol)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "frame = results.to_frame('value', 'portfolio_name_0', 'instrument_name') * 10000\n", "frame" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plt.subplots(figsize=(12, 8))\n", "ax = sns.heatmap(frame, annot=True, fmt='.2f', cmap='coolwarm')\n", "ax.set(ylabel='Expiries', xlabel='Tails', title='Implied Vol Grid')\n", "ax.xaxis.tick_top()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/03_portfolios/examples/030007_pnl_explain.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import Environment, GsSession\n", "\n", "client_id = None # Supply your application id\n", "client_secret = None # Supply your client secret\n", "\n", "GsSession.use(Environment.PROD, client_id, client_secret, ('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Create a dummy portfolio" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import IRSwap, IRSwaption\n", "from gs_quant.markets.portfolio import Portfolio\n", "\n", "swap = IRSwap(notional_currency='EUR', termination_date='10y', pay_or_receive='Pay')\n", "swaption = IRSwaption(notional_currency='EUR', termination_date='10y', expiration_date='1y', pay_or_receive='Receive')\n", "\n", "portfolio = Portfolio((swap, swaption))\n", "portfolio.resolve()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Calculate PnlExplain from 1 week ago (5 business days) to today, compare with the dollar price difference and examine the breakdown" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.datetime.date import business_day_offset\n", "from gs_quant.markets import CloseMarket, PricingContext, close_market_date\n", "from gs_quant.risk import DollarPrice, PnlExplain\n", "\n", "to_date = close_market_date()\n", "\n", "# 5 business days ago\n", "from_date = business_day_offset(to_date, -5)\n", "\n", "# A risk measure for calculating PnlExplain from that date\n", "explain = PnlExplain(CloseMarket(date=to_date))\n", "\n", "# Calculate PnlExplain and dollar price from 1 week ago\n", "with PricingContext(pricing_date=from_date):\n", " result = portfolio.calc((DollarPrice, explain))\n", "\n", "# Calculate dollar price with the \"to\" market but \"from\" pricing date\n", "with PricingContext(pricing_date=from_date, market=CloseMarket(date=to_date)):\n", " to_market_price = portfolio.dollar_price()\n", "\n", "# Calculate dollar price with the \"to\" market and pricing date\n", "with PricingContext(pricing_date=to_date):\n", " to_price = portfolio.dollar_price()\n", "\n", "# Compute the time component (PnlExplain does not do this)\n", "time_value = to_price.aggregate() - to_market_price.aggregate()\n", "\n", "print(f'Dollar price difference: {to_price.aggregate() - result[DollarPrice].aggregate():.0f}')\n", "print(f'Pnl explain + time value total: {result[explain].aggregate().value.sum() + time_value:.0f}')\n", "print(f'Pnl explain total: {result[explain].aggregate().value.sum():.0f}')\n", "print(f'Time value total: {time_value:.0f}')\n", "\n", "# Show the PnlExplain breakdown\n", "explain_all = result[explain].aggregate()\n", "explain_all[explain_all.value.abs() > 1.0].round(0)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.0" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/03_portfolios/examples/030008_portflio_from_frame.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": true, "pycharm": { "name": "#%% md\n" } }, "source": [ "### Import Portfolios from Excel\n", "\n", "This example will demonstrate how to create a `Portfolio` from an excel file." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "from gs_quant.session import Environment, GsSession\n", "\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's assume you have an excel file with many different trades on which you would like to compute `gs-quant` analytics.\n", "You can import you file into a pandas dataframe with the below command: " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# import pandas as pd\n", "# data = pd.read_excel(r'path_to_my_file.csv', sheet_name='my_sheet', usecols=None)" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "For the benefit of the analysis here I simulated a dummy dataframe representing the result of your file import." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "import pandas as pd\n", "\n", "data = pd.DataFrame.from_dict(\n", " {\n", " 'name': {0: 'my favourite swap', 1: 'my favourite swaption', 2: None, 3: None, 4: None, 5: None},\n", " 'trade_type': {0: 'Swap', 1: 'Swaption', 2: 'Swaption', 3: 'Swaption', 4: 'Swaption', 5: 'Swaption'},\n", " 'rate': {0: 0.01, 1: None, 2: None, 3: None, 4: None, 5: None},\n", " 'strike': {0: None, 1: '2%', 2: '0.02', 3: '0.02', 4: '0.02', 5: '0.02'},\n", " 'ccy': {0: 'EUR', 1: 'GBP', 2: 'GBP', 3: 'GBP', 4: 'GBP', 5: 'GBP'},\n", " 'freq': {0: '3m/6m', 1: '3m/6m', 2: '3m/6m', 3: '3m/6m', 4: '3m/6m', 5: '3m/6m'},\n", " 'index': {\n", " 0: 'EURIBOR-TELERATE',\n", " 1: 'LIBOR-BBA',\n", " 2: 'LIBOR-BBA',\n", " 3: 'LIBOR-BBA',\n", " 4: 'LIBOR-BBA',\n", " 5: 'LIBOR-BBA',\n", " },\n", " 'expiration_date': {0: '30/06/2021', 1: '30/06/2021', 2: '3d', 3: '30/06/2021', 4: '30/06/2021', 5: '3m'},\n", " 'asset_class': {0: 'rates', 1: 'rates', 2: 'rates', 3: 'rates', 4: 'rates', 5: 'rates'},\n", " }\n", ")" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "In order to support various excel files and formats, the `from_frame` function takes a mapping argument which enables you \n", "to specify which columns of your excel file correspond to the associated `gs_quant` instrument attribute. \n", "You may also specify date formats to expand the list of the ones supported by default." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "from gs_quant.markets.portfolio import Portfolio\n", "\n", "mapper = {\n", " 'type': 'trade_type',\n", " 'fixed_rate': 'rate',\n", " 'pay_ccy': 'ccy',\n", " 'fixed_rate_frequency': lambda row: row['freq'][: row['freq'].index(\"/\")],\n", " 'floating_rate_frequency': lambda row: row['freq'][row['freq'].index(\"/\") + 1 :],\n", " 'floating_rate_option': lambda row: row['ccy'] + '-' + row['index'],\n", "}\n", "\n", "portfolio = Portfolio.from_frame(data, mappings=mapper)\n", "portfolio.to_frame().reset_index(drop=True)" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "If you excel file is in csv format, you may also use the `from_csv` command which executes the two above steps \n", "all together and converts your csv to a `Portfolio` directly." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# portfolio = Portfolio.from_csv(r'path_to_my_file.csv', mappings=mapper)" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "You can now leverage all the risk and pricing functionality of `gs-quant` on your Excel built portfolio!" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.0" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: gs_quant/documentation/03_portfolios/examples/030009_portfolio_risk_result_to_frame.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "e415037a", "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import Environment, GsSession\n", "import gs_quant.risk as risk\n", "from gs_quant.instrument import IRSwap, IRSwaption\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.config import DisplayOptions" ] }, { "cell_type": "code", "execution_count": null, "id": "8e001d1f", "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "id": "e1558474", "metadata": {}, "outputs": [], "source": [ "# portfolio construction\n", "swap_1 = IRSwap('Pay', '5y', 'EUR', fixed_rate=-0.005, name='5y')\n", "swap_2 = IRSwap('Pay', '10y', 'EUR', fixed_rate=-0.005, name='10y')\n", "swap_3 = IRSwap('Pay', '5y', 'USD', fixed_rate=-0.005, name='5y')\n", "swap_4 = IRSwap('Pay', '10y', 'USD', fixed_rate=-0.005, name='10y')\n", "swaption_1 = IRSwaption('Pay', '5y', 'USD', expiration_date='1y', name='5y')\n", "eur_port = Portfolio([swap_1, swap_2], name='EUR')\n", "usd_port = Portfolio([swap_3, swap_4], name='USD')\n", "nested_port = Portfolio([eur_port, usd_port, swaption_1])" ] }, { "cell_type": "code", "execution_count": null, "id": "8b770f93", "metadata": {}, "outputs": [], "source": [ "# risk calculations\n", "eur_port_price = eur_port.price()\n", "nested_port_price = nested_port.price()\n", "nested_port_vega = nested_port.calc(risk.IRVegaParallel)" ] }, { "cell_type": "markdown", "id": "fbf14a17", "metadata": {}, "source": [ "Pivot Parameters\n", "\n", "- inherited from pandas.pivot_table()\n", "- index and column values are the names of instruments and portfolios" ] }, { "cell_type": "code", "execution_count": null, "id": "3dd1164b", "metadata": {}, "outputs": [], "source": [ "# default to_frame()\n", "nested_port_price.to_frame()" ] }, { "cell_type": "code", "execution_count": null, "id": "6469cb11", "metadata": {}, "outputs": [], "source": [ "# to_frame() with no pivot\n", "nested_port_price.to_frame(values=None, columns=None, index=None)" ] }, { "cell_type": "code", "execution_count": null, "id": "454e1851", "metadata": {}, "outputs": [], "source": [ "# modify to_frame() with custom pivot parameters - similar to pandas.pivot_table()\n", "nested_port_price.to_frame(values='value', columns='portfolio_name_0', index='instrument_name')" ] }, { "cell_type": "markdown", "id": "65d6a2f6", "metadata": {}, "source": [ "Aggregation\n", "- inherited from pandas.pivot_table()" ] }, { "cell_type": "code", "execution_count": null, "id": "03514616", "metadata": {}, "outputs": [], "source": [ "swap_5 = IRSwap('Pay', '5y', 'EUR', fixed_rate=-0.005, name='5y')\n", "swap_6 = IRSwap('Pay', '10y', 'EUR', fixed_rate=-0.005, name='5y')\n", "port = Portfolio([swap_5, swap_6])\n", "res = port.price()" ] }, { "cell_type": "code", "execution_count": null, "id": "7e441762", "metadata": {}, "outputs": [], "source": [ "# when instruments have the same name, the values are summed by default\n", "res.to_frame()" ] }, { "cell_type": "code", "execution_count": null, "id": "267db8cd", "metadata": {}, "outputs": [], "source": [ "# change aggregation of values\n", "res.to_frame(aggfunc='mean')" ] }, { "cell_type": "markdown", "id": "4ae17333", "metadata": {}, "source": [ "Show N/A values" ] }, { "cell_type": "code", "execution_count": null, "id": "d15564e6", "metadata": {}, "outputs": [], "source": [ "# to_frame() by default eliminates N/A values from the dataframe result\n", "nested_port_vega.to_frame()" ] }, { "cell_type": "code", "execution_count": null, "id": "a9797837", "metadata": {}, "outputs": [], "source": [ "# pass in display_options to show N/A values\n", "nested_port_vega.to_frame(display_options=DisplayOptions(show_na=True))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/03_portfolios/examples/030010_portfolio_intra_leg_dependencies.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "0af5ba20", "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import IRSwaption\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.session import Environment, GsSession\n", "from gs_quant.common import PayReceive, Currency" ] }, { "cell_type": "code", "execution_count": null, "id": "d5bf201e", "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "id": "6ea95f36", "metadata": {}, "source": [ "### Reference other leg properties in instrument fields\n", "This would be the equivalent of making two requests to resolve the legs serialially\n", "```python\n", "swaption_1y5y = IRSwaption( ... )\n", "swaption_1y4y = IRSwaption( ... )\n", "\n", "swaption_1y5y.resolve()\n", "swaption_1y4y.strike = swaption_1y5y.strike\n", "swaption_1y4y.resolve()\n", "```" ] }, { "cell_type": "code", "execution_count": null, "id": "96cac46c", "metadata": {}, "outputs": [], "source": [ "swaption_1y5y = IRSwaption(PayReceive.Pay, '5y', Currency.EUR, expiration_date='1y', strike=\"atm\", name=\"foo\")\n", "swaption_1y4y = IRSwaption(\n", " PayReceive.Pay, '4y', Currency.EUR, expiration_date='1y', strike=\"=[foo].strike + 5bp\", name=\"bar\"\n", ")\n", "port = Portfolio((swaption_1y5y, swaption_1y4y))" ] }, { "cell_type": "code", "execution_count": null, "id": "00f45d28", "metadata": {}, "outputs": [], "source": [ "port.resolve()" ] }, { "cell_type": "code", "execution_count": null, "id": "c0913ba1", "metadata": {}, "outputs": [], "source": [ "print(port['foo'].strike * 1e4)\n", "print(port['bar'].strike * 1e4)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/03_portfolios/examples/030011_portfolio_from_csv.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "5690b929", "metadata": { "scrolled": true }, "outputs": [], "source": [ "from gs_quant.session import GsSession, Environment\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.instrument import IRSwap" ] }, { "cell_type": "code", "execution_count": null, "id": "c1e82503", "metadata": { "scrolled": true }, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "id": "40ca1564", "metadata": { "scrolled": true }, "outputs": [], "source": [ "IRSwap?" ] }, { "cell_type": "markdown", "id": "e14778db", "metadata": {}, "source": [ "The Portfolio.from_csv mappings is a dictionary mapping a gs-quant instrument field to your csv column name (columns must not have duplicates). Mappings can also be a callable lambda function." ] }, { "cell_type": "code", "execution_count": null, "id": "eb9febfb", "metadata": { "scrolled": true }, "outputs": [], "source": [ "mappers = {\n", " 'type': lambda row: IRSwap.type_.value,\n", " 'asset_class': lambda row: IRSwap.asset_class.value,\n", " 'effective_date': 'EffectiveDate',\n", " 'pay_or_receive': lambda row: 'Pay' if float(row['Notional'].replace(',', '')) < 0 else 'Receive',\n", " 'termination_date': 'EndDate',\n", " 'fixed_rate': 'Coupon/Spread',\n", " 'notional_amount': 'Notional',\n", " 'notional_currency': 'CCY1',\n", " 'roll_convention': lambda row: 'IMM' if row['Roll Conv'] == 'IMM' else None,\n", " 'fixed_rate_frequency': lambda row: '3m' if row['Frequency'] == 'QUARTERLY' else '6m',\n", "}" ] }, { "cell_type": "code", "execution_count": null, "id": "704dabf0", "metadata": { "scrolled": true }, "outputs": [], "source": [ "p = Portfolio.from_csv('my_excel_portfolio.csv', mappers)\n", "p.resolve()" ] }, { "cell_type": "code", "execution_count": null, "id": "b0881ba6", "metadata": { "scrolled": true }, "outputs": [], "source": [ "p[0].as_dict()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/03_portfolios/examples/my_excel_portfolio.csv ================================================ Count,CCY1,Notional,EffectiveDate,Roll Conv,EndDate,LegName,Frequency,RateFixingInfo,RollDate 1,USD,"50,000,000",27-Jan-15,27,27-Jan-25,FIXED,QUARTERLY,NONE,27-Jan-15 2,USD,"-10,500,000",16-Sep-15,16,16-Sep-25,FIXED,QUARTERLY,NONE,16-Sep-15 3,USD,"-500,000",16-Sep-15,16,16-Sep-25,FIXED,QUARTERLY,NONE,16-Sep-15 4,USD,"-1,200,000",16-Sep-15,16,16-Sep-25,FIXED,SEMI,NONE,16-Sep-15 5,USD,"-8,100,000",16-Sep-15,16,16-Sep-25,FIXED,SEMI,NONE,16-Sep-15 6,USD,"-9,000,000",16-Sep-15,16,16-Sep-25,FIXED,SEMI,NONE,16-Sep-15 7,USD,"-2,000,000",16-Sep-15,16,16-Sep-25,FIXED,SEMI,NONE,16-Sep-15 8,USD,"50,000,000",16-Mar-16,IMM,18-Mar-26,FIXED,QUARTERLY,NONE,15-Jun-16 9,USD,"-27,200,000",16-Sep-15,IMM,16-Sep-25,FIXED,QUARTERLY,NONE,15-Jun-16 ================================================ FILE: gs_quant/documentation/03_portfolios/tutorials/Create New Portfolio.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": true, "pycharm": { "name": "#%% md\n" } }, "source": [ "# Create a Marquee Portfolio with GS Quant\n", "\n", "The Marquee Portfolio Service provides a powerful framework for uploading portfolio positions and retrieving analytics including historical performance, factor risk exposure, ESG analytics, and more. GS Quant makes operating the suite of Portfolio Service API's intuitive and fast.\n", "\n", "## Permission Prerequisites\n", "\n", "To execute all the code in this tutorial, you will need the following application scopes:\n", "- **read_product_data**\n", "- **read_financial_data**\n", "- **modify_product_data** (must be requested)\n", "- **modify_financial_data** (must be requested)\n", "- **run_analytics** (must be requested)\n", "- **read_user_profile**\n", "\n", "If you are not yet permissioned for these scopes, please request them on your [My Applications Page](https://developer.gs.com/go/apps/view). If you have any other questions please reach out to the [Marquee sales team](mailto:gs-marquee-sales@gs.com).\n" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "## Step 1: Authenticate and Initialize Your Session\n", "\n", "First you will import the necessary modules and add your client id and client secret." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "import datetime as dt\n", "\n", "from gs_quant.entities.entitlements import Entitlements, EntitlementBlock, User\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.markets.portfolio_manager import PortfolioManager\n", "from gs_quant.markets.report import CustomAUMDataPoint\n", "from gs_quant.markets.position_set import Position, PositionSet\n", "from gs_quant.session import GsSession, Environment\n", "from gs_quant.target.portfolios import RiskAumSource\n", "\n", "client = 'ENTER CLIENT ID'\n", "secret = 'ENTER CLIENT SECRET'\n", "\n", "GsSession.use(\n", " Environment.PROD,\n", " client_id=client,\n", " client_secret=secret,\n", " scopes=(\n", " 'read_product_data read_financial_data modify_product_data modify_financial_data run_analytics read_user_profile',\n", " ),\n", ")\n", "\n", "print('GS Session initialized.')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, "source": [ "## Step 2: Create the Portfolio\n", "\n", "The first step is to create a new, empty portfolio in Marquee." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "portfolio = Portfolio(name='My New Portfolio')\n", "portfolio.save()\n", "\n", "print(f\"Created portfolio '{portfolio.name}' with ID: {portfolio.id}\")" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "Once your portfolio has been saved to Marquee, the `PortfolioManager` class allows users to interact with their Marquee portfolios directly from GS Quant. We will be using `PortfolioManager` to update portfolio positions, entitlements, update custom AUM, and run reports.\n", "\n", "## Step 3: Define Portfolio Entitlements\n", "\n", "By default, an application will have all entitlement permissions to a portfolio it makes. However, if you want to see the portfolio in the Marquee Webpage, you will have to share it with yourself (since your app is a separate user from you). \n", "\n", "Similarly, if you would like others to have access to your portfolio, either Marquee users or other applications, you will need to share the portfolio with them. Let's walk through how to do that:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" }, "scrolled": false }, "outputs": [], "source": [ "portfolio_admin_emails = ['LIST OF ADMIN EMAILS']\n", "portfolio_viewer_emails = ['LIST OF VIEWER EMAILS']\n", "\n", "pm = PortfolioManager(portfolio.id)\n", "pm.share(portfolio_admin_emails, admin=True)\n", "pm.share(portfolio_viewer_emails, admin=False)\n", "\n", "print(f\"Updated entitlements for '{portfolio.name}'\")" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "If you'd like more control on the entitlements (e.g. you want to remove someone), you can use the `Entitlements` class directly. Here's an example:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "portfolio_admin_emails = ['LIST OF ADMIN EMAILS']\n", "portfolio_viewer_emails = ['LIST OF VIEWER EMAILS']\n", "\n", "admin_entitlements = EntitlementBlock(users=User.get_many(emails=portfolio_admin_emails))\n", "view_entitlements = EntitlementBlock(users=User.get_many(emails=portfolio_viewer_emails))\n", "\n", "entitlements = Entitlements(view=view_entitlements, admin=admin_entitlements)\n", "\n", "print(f'Entitlements:\\n{entitlements.to_dict()}')\n", "\n", "pm = PortfolioManager(portfolio.id)\n", "pm.set_entitlements(entitlements)" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "## Step 4: Define Portfolio Positions\n", "\n", "Portfolio positions in Marquee are stored on a holding basis, when means you only upload positions for days where you are rebalancing your portfolio. Take the following set of positions:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "portfolio_position_sets = [\n", " PositionSet(\n", " date=dt.date(day=3, month=5, year=2021),\n", " positions=[Position(identifier='AAPL UW', quantity=25), Position(identifier='GS UN', quantity=50)],\n", " ),\n", " PositionSet(\n", " date=dt.date(day=1, month=7, year=2021),\n", " positions=[Position(identifier='AAPL UW', quantity=26), Position(identifier='GS UN', quantity=51)],\n", " ),\n", "]" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "If these positions were to be uploaded correctly, this portfolio would hold 50 shares of GS UN and 25 shares of AAPL UW from May 3, 2021 to June 30, 2021, and it would hold 51 shares of GS UN and 26 shares of AAPL UW from July 1, 2021 to today." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "pm.update_positions(portfolio_position_sets)\n", "\n", "print(f\"Updated positions for '{portfolio.name}'\")" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, "source": [ "## Step 5: Schedule Reports\n", "By default, creating a portfolio will automatically create a corresponding Performance Report for it as well. If you would like to create a Factor Risk Report for it as well, follow the steps [here](../examples/marquee/00_create_factor_risk_report.ipynb). Then, remember to schedule all the portfolio reports." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "pm.schedule_reports()\n", "\n", "print('All portfolio reports scheduled.')" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "## Step 6: Update Custom AUM (Optional)\n", "The `CustomAUMDataPoint` class is used to represent custom AUM data for a specific date. A list of them can be posted to Marquee using our initialized `PortfolioManager`. If you do not upload custom AUM data for your portfolio and change your portfolio's AUM Source to `Custom AUM`, by default the \"AUM\" (which is used for calculating risk as percent values) will be your portfolio's long exposure." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "performance_report = pm.get_performance_report()\n", "performance_report.set_aum_source(RiskAumSource.Custom_AUM)\n", "custom_aum = [\n", " CustomAUMDataPoint(date=dt.date(2021, 5, 1), aum=100000),\n", " CustomAUMDataPoint(date=dt.date(2021, 7, 1), aum=200000),\n", "]\n", "performance_report.upload_custom_aum(custom_aum, clear_existing_data=False)\n", "\n", "print(f\"Custom AUM for '{portfolio.name} successfully uploaded'\")" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, "source": [ "### You're all set, Congrats! What's next?\n", "\n", "* [Creating and scheduling a factor risk report](../examples/marquee/00_create_factor_risk_report.ipynb)\n", "\n", "* [Updating the portfolio with new positions](../tutorials/Update%20Historical%20Portfolio.ipynb)\n", "\n", "* [Retrieving the portfolio's performance analytics](../tutorials/Pull%20Portfolio%20Performance%20Data.ipynb)\n", "\n", "* [Retrieving the portfolio's factor risk and attribution analytics](../tutorials/Pull%20Portfolio%20Factor%20Risk%20Data.ipynb)\n", "\n", "\n", "*Other questions? Reach out to the [Portfolio Analytics team](mailto:gs-marquee-analytics-support@gs.com)!*\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: gs_quant/documentation/03_portfolios/tutorials/Portfolios.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "Examples require an initialized GsSession and relevant entitlements. `run_analytics` scope is required for the functionality covered in this tutorial. External clients need to substitute thier own client id and client secret below. Please refer to Sessions for details." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## What is a Portfolio\n", "\n", "Portfolios are collections of instruments. They can be used to simplify calculations and syntax when working with many instruments. [All `Priceable` methods](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.base.Priceable.html#gs_quant.base.Priceable/#methods/) like `price`, `as_dict`, and `calc` are also available for a `Portfolio`. When evaluated on a portfolio, a user can still access results for individual instruments in that portfolio." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## How to Create a Portfolio" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's first create a few instruments that will be included in our portfolio. To learn more about instruments and how to create them please see the [instruments guide](https://developer.gs.com/docs/gsquant/guides/Pricing-and-Risk/instruments/) and [tutorial](https://developer.gs.com/docs/gsquant/tutorials/Pricing-and-Risk/1-creating-an-instrument/). " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import IRSwaption\n", "from gs_quant.common import PayReceive, Currency\n", "\n", "swaption1 = IRSwaption(PayReceive.Pay, '5y', Currency.EUR, expiration_date='3m', name='EUR-5y3m')\n", "swaption2 = IRSwaption(PayReceive.Pay, '5y', Currency.EUR, expiration_date='6m', name='EUR-5y6m')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now import `Portfolio` and add the two swaptions we created." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.portfolio import Portfolio\n", "\n", "portfolio = Portfolio((swaption1, swaption2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Portfolio constituents can also be set (or reset) by assiging to instruments." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "portfolio.pricables = (swaption1, swaption2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## How to Refer to Specific Instruments in a Portfolio\n", "\n", "Instruments can be referred to by position, by instrument object and by name. Examples of each below." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "portfolio[0] # by position\n", "portfolio[swaption2] # by instrument object\n", "portfolio['EUR-5y6m'] # by instrument name" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## How to Work with Portfolios\n", "\n", "As mentioned above, working with portfolios is same as working with instruments and the same methods are available including `resolve`, `price`, `calc`. Any calculations on a portfolio can be aggregated on a portfolio level using `aggregate.` Let's look at each below." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "portfolio.resolve() # resolve\n", "portfolio.price().aggregate() # price" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# calculate risk measures\n", "import gs_quant.risk as risk\n", "\n", "result = portfolio.calc((risk.DollarPrice, risk.IRDelta))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Instrument-level results can be accessed through either indexing into the measure or the instrument first. See [above](#How-to-Refer-to-Specific-Instruments-in-a-Portfolio) on ways the instruments can be referred to." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "result[risk.DollarPrice]['EUR-5y3m'] # or\n", "result['EUR-5y3m'][risk.DollarPrice]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Portfolio-level results can be aggregated using `aggregate`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "price = result[risk.DollarPrice].aggregate()\n", "delta = result[risk.IRDelta].aggregate()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Disclaimer\n", "This website may contain links to websites and the content of third parties (\"Third Party Content\"). We do not monitor, review or update, and do not have any control over, any Third Party Content or third party websites. We make no representation, warranty or guarantee as to the accuracy, completeness, timeliness or reliability of any Third Party Content and are not responsible for any loss or damage of any sort resulting from the use of, or for any failure of, products or services provided at or from a third party resource. If you use these links and the Third Party Content, you acknowledge that you are doing so entirely at your own risk." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/03_portfolios/tutorials/Pull Portfolio Factor Risk Data.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "source": [ "# Pull Portfolio Factor Risk and Attribution Data\n", "\n", "When you create and upload positions to a portfolio in Marquee, GS Quant makes it possible to pull factor risk analytics in just a few lines of code.\n", "\n", "## Prerequisites\n", "\n", "Before you begin this tutorial, you must have a portfolio in Marquee with positions and a factor risk report that has already been created, scheduled, and completed. If that's not the case, please navigate to our tutorial on [creating a new portfolio](../tutorials/Create%20New%20Portfolio.ipynb) and/or [updating positions and running reports](../tutorials/Update%20Historical%20Portfolio.ipynb).\n", "\n", "Your application also needs to have the **run_analytics** scope. If it doesn't, please request it on your [My Applications Page](https://developer.gs.com/go/apps/view). If you have any other questions please reach out to the [Marquee sales team](mailto:gs-marquee-sales@gs.com)." ], "metadata": { "collapsed": false } }, { "cell_type": "markdown", "source": [ "## Step 1: Authenticate and Initialize Your Session\n", "\n", "First you will import the necessary modules and add your client id and client secret." ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": 3, "outputs": [], "source": [ "\n", "import datetime as dt\n", "\n", "from gs_quant.api.gs.risk_models import GsRiskModelApi\n", "from gs_quant.markets.portfolio_manager import PortfolioManager\n", "from gs_quant.markets.report import FactorRiskReport\n", "from gs_quant.session import GsSession, Environment\n", "\n", "client = 'ENTER CLIENT ID'\n", "secret = 'ENTER CLIENT SECRET'\n", "\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('run_analytics',))\n", "\n", "print('GS Session initialized.')" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "## Step 2: Get all Portfolio Reports\n", "\n", "The `PortfolioManager` class allows for easy retrieval and scheduling of portfolio reports. Simply running:" ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": 10, "outputs": [], "source": [ "all_reports = PortfolioManager('ENTER PORTFOLIO ID').get_reports()\n", "\n", "print(all_reports)" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "will return a list of `Report` objects that represent the reports associated with the portfolio.\n", "\n", "## Step 3: Retrieve the Relevant Factor Risk Report\n", "\n", "The GS Quant `Report` class is inherited by report subclasses, like `FactorRiskReport` and `PerformanceReport`, each of which corresponds to a type of Marquee report. Each subclass then has additional functions specific to its report type. In this case, we'd like to find the `FactorRiskReport` associated with this portfolio, and leverage its functions to retrieve data. First let's retrieve the list of all risk models we can choose from." ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": 11, "outputs": [], "source": [ "list_of_risk_models = []\n", "for report in all_reports:\n", " if isinstance(report, FactorRiskReport):\n", " list_of_risk_models.append(report.get_risk_model_id())\n", "\n", "print(f'This portfolio has scheduled factor risk reports with the following risk models:\\n {list_of_risk_models}')\n" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "If you would like to pull factor risk data off of a risk model not on this list, you can [create and schedule a new factor risk report](../examples/marquee/00_create_factor_risk_report.ipynb).\n", "\n", "### Quick Tip!\n", "If you would like to see all available risk model IDs to choose from, simply run the following" ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": 12, "outputs": [], "source": [ "risk_models = GsRiskModelApi.get_risk_models(limit=1000)\n", "for risk_model in risk_models:\n", " print(f'{risk_model}\\n')" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "Now let's pick one risk model ID from the list above and find its correlating `FactorRiskReport`." ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": 13, "outputs": [], "source": [ "risk_model_id = 'ENTER RISK MODEL ID'\n", "factor_risk_reports = list(filter(lambda report: isinstance(report, FactorRiskReport) and report.get_risk_model_id() == risk_model_id, all_reports))\n", "if len(factor_risk_reports) == 0:\n", " print(f'This portfolio does not have a factor risk report with the risk model {risk_model_id}. Please create a new factor risk report and schedule it before proceeding.')\n", "factor_risk_report = factor_risk_reports[0]\n", "\n", "print(f'Factor risk model found with ID: \"{factor_risk_report.id}\".')" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "will return a list of `Report` objects that represent the reports associated with the portfolio.\n", "\n", "## Step 4: Pull Report Data\n", "\n", "Now that we have our factor risk report, we can leverage the functionality of the `FactorRiskReport` class to pull attribution and risk data. In this example, let's pull historical data on factor, specific, and total PnL for the year 2020:" ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": 14, "outputs": [], "source": [ "factor_pnl = factor_risk_report.get_factor_pnl(factor_name='Factor', start_date=dt.date(2020, 1, 1), end_date=dt.date(2021, 1, 1))\n", "specific_pnl = factor_risk_report.get_factor_pnl(factor_name='Specific', start_date=dt.date(2020, 1, 1), end_date=dt.date(2021, 1, 1))\n", "total_pnl = factor_risk_report.get_factor_pnl(factor_name='Total', start_date=dt.date(2020, 1, 1), end_date=dt.date(2021, 1, 1))\n", "\n", "print(f'Factor: \\n{factor_pnl.__str__()}')\n", "print(f'Specific: \\n{specific_pnl.__str__()}')\n", "print(f'Total: \\n{total_pnl.__str__()}')" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "Now let's pull the breakdown of proportion of risk among the different factor types over time:" ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": 15, "outputs": [], "source": [ "market_prop_of_risk = factor_risk_report.get_factor_proportion_of_risk(factor_name='Market', start_date=dt.date(2020, 1, 1), end_date=dt.date(2021, 1, 1))\n", "style_prop_of_risk = factor_risk_report.get_factor_proportion_of_risk(factor_name='Style', start_date=dt.date(2020, 1, 1), end_date=dt.date(2021, 1, 1))\n", "industry_prop_of_risk = factor_risk_report.get_factor_proportion_of_risk(factor_name='Industry', start_date=dt.date(2020, 1, 1), end_date=dt.date(2021, 1, 1))\n", "country_prop_of_risk = factor_risk_report.get_factor_proportion_of_risk(factor_name='Country', start_date=dt.date(2020, 1, 1), end_date=dt.date(2021, 1, 1))\n", "\n", "print(f'Market: \\n{market_prop_of_risk.__str__()}')\n", "print(f'Style: \\n{style_prop_of_risk.__str__()}')\n", "print(f'Industry: \\n{industry_prop_of_risk.__str__()}')\n", "print(f'Country: \\n{country_prop_of_risk.__str__()}')" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "### Quick Tip!\n", "If you would like to pull all factor risk data for a list of different factors, you can use the `get_results` function:" ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "factor_and_total_results = factor_risk_report.get_results(factors=['Factor', 'Specific'], start_date=dt.date(2020, 1, 1), end_date=dt.date(2021, 1, 1))\n", "print(factor_and_total_results.__str__())" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "\n", "### You're all set, Congrats! What's next?\n", "\n", "* [Creating and scheduling a new factor risk report](../examples/marquee/00_create_factor_risk_report.ipynb)\n", "\n", "* [Updating the portfolio with new positions](../tutorials/Update%20Historical%20Portfolio.ipynb)\n", "\n", "* [Retrieving the portfolio's performance analytics](../tutorials/Pull%20Portfolio%20Performance%20Data.ipynb)\n", "\n", "\n", "*Other questions? Reach out to the [Portfolio Analytics team](mailto:gs-marquee-analytics-support@gs.com)!*" ], "metadata": { "collapsed": false } } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/03_portfolios/tutorials/Pull Portfolio Performance Data.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": true, "pycharm": { "name": "#%% md\n" } }, "source": [ "# Pull Portfolio Performance Data\n", "\n", "When you create and upload positions to a portfolio in Marquee, GS Quant makes it easy to pull historical performance data with just a few lines of code.\n", "\n", "## Prerequisites\n", "\n", "Before you begin this tutorial, you must have a portfolio in Marquee with positions and reports that have already been scheduled and completed. If that's not the case, please navigate to our tutorial on [creating a new portfolio](../tutorials/Create%20New%20Portfolio.ipynb) and/or [updating positions and running reports](../tutorials/Update%20Historical%20Portfolio.ipynb).\n", "\n", "Your application also needs to have the **run_analytics** scope. If it doesn't, please request it on your [My Applications Page](https://developer.gs.com/go/apps/view). If you have any other questions please reach out to the [Marquee sales team](mailto:gs-marquee-sales@gs.com)." ] }, { "cell_type": "markdown", "source": [ "## Step 1: Authenticate and Initialize Your Session\n", "\n", "First you will import the necessary modules and add your client id and client secret." ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": 4, "outputs": [], "source": [ "\n", "from gs_quant.markets.portfolio_manager import PortfolioManager\n", "from gs_quant.markets.report import PerformanceReport\n", "from gs_quant.session import GsSession, Environment\n", "\n", "client = 'ENTER CLIENT ID'\n", "secret = 'ENTER CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('run_analytics',))" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "## Step 2: Get all Portfolio Reports\n", "\n", "The `PortfolioManager` class allows for easy retrieval and scheduling of portfolio reports. Simply running:" ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": 8, "outputs": [], "source": [ "all_reports = PortfolioManager('ENTER PORTFOLIO ID').get_reports()" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "will return a list of `Report` objects that represent the reports associated with the portfolio.\n", "\n", "## Step 3: Find Portfolio Performance Analytics Report and Pull Data\n", "\n", "The GS Quant `Report` class is inherited by report subclasses, like `FactorRiskReport` and `PerformanceReport`, each of which corresponds to a type of Marquee report. Each subclass then has additional functions specific to its report type. In this case, we'd like to find the `PerformanceReport` associated with this portfolio, and leverage its functions to retrieve data. In this example, we will pull all historical PnL, gross exposure, and net exposure available.\n" ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": 9, "outputs": [], "source": [ "performance_report = list(filter(lambda report: isinstance(report, PerformanceReport), all_reports))[0]\n", "pnl = performance_report.get_pnl()\n", "gross_exposure = performance_report.get_gross_exposure()\n", "net_exposure = performance_report.get_net_exposure()\n", "\n", "print(f'PnL: \\n{pnl.__str__()}')\n", "print(f'Gross Exposure: \\n{gross_exposure.__str__()}')\n", "print(f'Net Exposure: \\n{net_exposure.__str__()}')" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "### Quick Tip!\n", "If you would like to pull multiple performance measures in one dataframe, you can use the `get_many_measures` function:" ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "aggregated_performance_results = performance_report.get_many_measures(measures=['pnl', 'grossExposure', 'netExposure'])\n", "print(aggregated_performance_results)" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "\n", "### Quick Tip!\n", "If you would only like to pull data for a specific date range, all the functions used above also take in function parameters `start_date` and `end_date`, in which you can pass `datetime.date` objects to specify the range you would like.\n", "\n", "\n", "### You're all set, Congrats! What's next?\n", "\n", "* [Creating and scheduling a new factor risk report](../examples/marquee/00_create_factor_risk_report.ipynb)\n", "\n", "* [Updating the portfolio with new positions](../tutorials/Update%20Historical%20Portfolio.ipynb)\n", "\n", "* [Retrieving the portfolio's performance analytics](../tutorials/Pull%20Portfolio%20Performance%20Data.ipynb)\n", "\n", "\n", "*Other questions? Reach out to the [Portfolio Analytics team](mailto:gs-marquee-analytics-support@gs.com)!*" ], "metadata": { "collapsed": false } } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/03_portfolios/tutorials/Pull Portfolio Risk Data.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": true, "pycharm": { "name": "#%% md\n" } }, "source": [ "# Pull Portfolio Performance and Factor Risk Data\n", "\n", "This tutorial uses a pre-populated portfolio (accessible to all internal users) and walks through pulling performance and factor risk data *(for demo, not client distribution, purposes)*." ] }, { "cell_type": "markdown", "source": [ "## Step 1: Authenticate and Initialize Your Session" ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "\n", "import datetime as dt\n", "\n", "from gs_quant.markets.portfolio_manager import PortfolioManager\n", "from gs_quant.markets.report import PerformanceReport, FactorRiskReport\n", "from gs_quant.session import GsSession, Environment\n", "\n", "GsSession.use(Environment.PROD)" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "## Step 2: Get all Portfolio Reports\n", "\n", "The `PortfolioManager` class allows for easy retrieval and scheduling of portfolio reports. Simply running:" ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "all_reports = PortfolioManager('MPZV9A0F1EMQGG79').get_reports()" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "will return a list of `Report` objects that represent the reports associated with the portfolio.\n", "\n", "## Step 3: Find Portfolio Performance Analytics Report and Pull Data\n", "\n", "The GS Quant `Report` class is inherited by report subclasses, like `FactorRiskReport` and `PerformanceReport`, each of which corresponds to a type of Marquee report. Each subclass then has additional functions specific to its report type. In this case, we'd like to find the `PerformanceReport` associated with this portfolio, and leverage its functions to retrieve data. In this example, we will pull all historical PnL, gross exposure, and net exposure available.\n" ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "performance_report = list(filter(lambda report: isinstance(report, PerformanceReport), all_reports))[0]\n", "pnl = performance_report.get_pnl(start_date=dt.date(2021, 1, 1), end_date=dt.date(2021, 6, 1))\n", "gross_exposure = performance_report.get_gross_exposure(start_date=dt.date(2021, 1, 1), end_date=dt.date(2021, 6, 1))\n", "net_exposure = performance_report.get_net_exposure(start_date=dt.date(2021, 1, 1), end_date=dt.date(2021, 6, 1))\n", "\n", "print(f'PnL: \\n{pnl.__str__()}')\n", "print(f'Gross Exposure: \\n{gross_exposure.__str__()}')\n", "print(f'Net Exposure: \\n{net_exposure.__str__()}')" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "## Step 4: Find Factor Risk Report and Pull Data\n", "Now let's find a factor risk report and pull its correlating `FactorRiskReport`." ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "risk_model_id = 'BARRA_USSLOWL'\n", "factor_risk_reports = list(filter(lambda report: isinstance(report, FactorRiskReport) and report.get_risk_model_id() == risk_model_id, all_reports))\n", "if len(factor_risk_reports) == 0:\n", " print(f'This portfolio does not have a factor risk report with the risk model {risk_model_id}. Please create a new factor risk report and schedule it before proceeding.')\n", "factor_risk_report = factor_risk_reports[0]\n", "\n", "print(f'Factor risk model found with ID: \"{factor_risk_report.id}\".')" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "Now that we have our factor risk report, we can leverage the functionality of the `FactorRiskReport` class to pull attribution and risk data. In this example, let's pull historical data on factor, specific, and total PnL for the first five months of 2021:" ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "factor_pnl = factor_risk_report.get_factor_pnl(factor_name='Factor', start_date=dt.date(2021, 1, 1), end_date=dt.date(2021, 6, 1))\n", "specific_pnl = factor_risk_report.get_factor_pnl(factor_name='Specific', start_date=dt.date(2021, 1, 1), end_date=dt.date(2021, 6, 1))\n", "total_pnl = factor_risk_report.get_factor_pnl(factor_name='Total', start_date=dt.date(2021, 1, 1), end_date=dt.date(2021, 6, 1))\n", "\n", "print(f'Factor: \\n{factor_pnl.__str__()}')\n", "print(f'Specific: \\n{specific_pnl.__str__()}')\n", "print(f'Total: \\n{total_pnl.__str__()}')" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "Now let's pull the breakdown of proportion of risk among the different factor types over time:" ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "market_prop_of_risk = factor_risk_report.get_factor_proportion_of_risk(factor_name='Market', start_date=dt.date(2021, 1, 1), end_date=dt.date(2021, 6, 1))\n", "style_prop_of_risk = factor_risk_report.get_factor_proportion_of_risk(factor_name='Style', start_date=dt.date(2021, 1, 1), end_date=dt.date(2021, 6, 1))\n", "industry_prop_of_risk = factor_risk_report.get_factor_proportion_of_risk(factor_name='Industry', start_date=dt.date(2021, 1, 1), end_date=dt.date(2021, 6, 1))\n", "country_prop_of_risk = factor_risk_report.get_factor_proportion_of_risk(factor_name='Country', start_date=dt.date(2021, 1, 1), end_date=dt.date(2021, 6, 1))\n", "\n", "print(f'Market: \\n{market_prop_of_risk.__str__()}')\n", "print(f'Style: \\n{style_prop_of_risk.__str__()}')\n", "print(f'Industry: \\n{industry_prop_of_risk.__str__()}')\n", "print(f'Country: \\n{country_prop_of_risk.__str__()}')" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "\n", "### You're all set, Congrats! What's next?\n", "\n", "* [Creating and scheduling a new factor risk report](../examples/marquee/00_create_factor_risk_report.ipynb)\n", "\n", "* [Updating the portfolio with new positions](../tutorials/Update%20Historical%20Portfolio.ipynb)\n", "\n", "* [Retrieving the portfolio's performance analytics](../tutorials/Pull%20Portfolio%20Performance%20Data.ipynb)\n", "\n", "\n", "*Other questions? Reach out to the [Portfolio Analytics team](mailto:gs-marquee-analytics-support@gs.com)!*" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } } } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/03_portfolios/tutorials/Update Historical Portfolio.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Updating a Historical Marquee Portfolio\n", "\n", "If you already have a portfolio in Marquee, the GS Quant SDK provides a simple and intuitive workflow to update positions and rerun reports.\n", "\n", "## Permission Prerequisites\n", "\n", "To execute all the code in this tutorial, you will need the following application scopes:\n", "- **read_product_data**\n", "- **read_financial_data**\n", "- **modify_financial_data** (must be requested)\n", "- **run_analytics** (must be requested)\n", "\n", "If you are not yet permissioned for these scopes, please request them on your [My Applications Page](https://developer.gs.com/go/apps/view). If you have any other questions please reach out to the [Marquee sales team](mailto:gs-marquee-sales@gs.com).\n", "\n", "You will also need to be an admin on the portfolio you would like to update. If you are not an admin, please ask a portfolio admin to [edit the portfolio's entitlements](../examples/marquee/01_edit_portfolio_entitlements.ipynb) to include you." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 1: Authenticate and Initialize Your Session\n", "\n", "First you will import the necessary modules and add your client id and client secret." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "import itertools\n", "from time import sleep\n", "\n", "from gs_quant.api.gs.assets import GsAssetApi\n", "from gs_quant.api.gs.portfolios import GsPortfolioApi\n", "from gs_quant.common import PositionSet\n", "from gs_quant.markets.portfolio_manager import PortfolioManager\n", "from gs_quant.session import GsSession, Environment\n", "from gs_quant.target.portfolios import Position\n", "\n", "client = 'ENTER CLIENT ID'\n", "secret = 'ENTER CLIENT SECRET'\n", "\n", "\n", "GsSession.use(\n", " Environment.PROD,\n", " client_id=client,\n", " client_secret=secret,\n", " scopes=('read_product_data read_financial_data modify_financial_data run_analytics',),\n", ")\n", "\n", "print('GS Session initialized.')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 2: Define Your Portfolio ID and the Positions You Would Like to Upload\n", "\n", "Portfolio positions in Marquee are stored on a holding basis, when means you only upload positions for days where you are rebalancing your portfolio. Take the following set of positions:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "portfolio_id = 'ENTER PORTFOLIO ID'\n", "positions = [\n", " {'identifier': 'GS UN', 'quantity': 50, 'positionDate': '2020-05-01'},\n", " {'identifier': 'AAPL UW', 'quantity': 25, 'positionDate': '2020-05-01'},\n", " {'identifier': 'GS UN', 'quantity': 51, 'positionDate': '2020-07-01'},\n", " {'identifier': 'AAPL UW', 'quantity': 26, 'positionDate': '2020-07-01'},\n", "]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If these positions were to be uploaded correctly, that portfolio would hold 50 shares of GS UN and 25 shares of AAPL UW from May 1, 2020 to June 30, 2020, and it would hold 51 shares of GS UN and 26 shares of AAPL UW from July 1, 2020 to today (assuming the portfolio was initially empty).\n", "\n", "## Step 3: Format positions\n", "\n", "Now let's proceed with updating these positions to our portfolio. The first step is to resolve the identifiers provided into their corresponding unique Marquee identifiers. In this case, positions are identified by Bloomberg ID, but other identifiers can be used and resolved by adding them to the `fields` parameter in the function `GsAssetApi.resolve_assets`." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "all_identifiers = list(set([p['identifier'] for p in positions]))\n", "results = GsAssetApi.resolve_assets(identifier=all_identifiers, fields=['bbid', 'id'], limit=1)\n", "try:\n", " identifier_to_marquee_id = dict(zip(results.keys(), [a[0]['id'] for a in results.values()]))\n", "except:\n", " unmapped_assets = {k for k, v in results.items() if not v}\n", " raise ValueError('Error in resolving the following identifiers: ' + ', '.join(unmapped_assets))\n", "\n", "print('Position identifiers successfully mapped as the following:')\n", "for mq_id in identifier_to_marquee_id:\n", " print(f'{mq_id} --> {identifier_to_marquee_id[mq_id]}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next we need to rearrange the data in the positions to fit the format expected in the Marquee API." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "portfolio_position_sets = []\n", "\n", "for position_date, positions_on_date in itertools.groupby(positions, lambda x: x['positionDate']):\n", " formatted_positions = tuple(\n", " Position(asset_id=identifier_to_marquee_id[p['identifier']], quantity=p['quantity']) for p in positions_on_date\n", " )\n", " position_set = PositionSet(position_date=position_date, positions=formatted_positions)\n", " portfolio_position_sets.append(position_set)\n", "\n", "print('Portfolio positions successfully formatted.')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 4: Post Positions to the Marquee Portfolio\n", "We're finally ready to update the portfolio with the positions." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "GsPortfolioApi.update_positions(portfolio_id, portfolio_position_sets)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 5: Rerun All Portfolio Reports\n", "\n", "Once your portfolio has new positions, it's time to rerun all reports associated with the portfolio. You can do that easily using the `PortfolioManager` class.\n", "\n", "When running reports, you have the option of running them synchronously (meaning the function `run_reports()` will wait for all reports to finish running to return their results), or asynchronously (meaning the function will return a list of `ReportJobFuture` objects that will store the result of the report job once it is available). This tutorial will leverage the asyncronous feature." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "pm = PortfolioManager(portfolio_id)\n", "\n", "report_results = pm.run_reports(backcast=False, is_async=True)\n", "for future in report_results:\n", " while not future.done():\n", " # you can use this time to perform other calculations or run other functions\n", " sleep(5)\n", " print('~~~~~~~~ Results ~~~~~~~~')\n", " report_result = future.result()\n", " print(report_result.__str__())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Quick Tip!\n", "If you would only like to run reports on a specified range of dates, pass `start_date` and `end_date` datetime.date objects into the `run_reports()` function parameters.\n", "\n", "### You're all set, Congrats! What's next?\n", "\n", "* [Creating and scheduling a new factor risk report](../examples/marquee/00_create_factor_risk_report.ipynb)\n", "\n", "* [Retrieving the portfolio's performance analytics](../tutorials/Pull%20Portfolio%20Performance%20Data.ipynb)\n", "\n", "* [Retrieving the portfolio's factor risk and attribution analytics](../tutorials/Pull%20Portfolio%20Factor%20Risk%20Data.ipynb)\n", "\n", "\n", "*Other questions? Reach out to the [Portfolio Analytics team](mailto:gs-marquee-analytics-support@gs.com)!*" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: gs_quant/documentation/04_backtesting/examples/01_PredefinedAssetEngine/040100_simple_example.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "44d93b81", "metadata": {}, "outputs": [], "source": [ "from gs_quant.backtests.triggers import DateTriggerRequirements, DateTrigger\n", "from gs_quant.backtests.actions import AddTradeAction\n", "from gs_quant.backtests.core import ValuationFixingType\n", "from gs_quant.backtests.strategy import Strategy\n", "from gs_quant.backtests.predefined_asset_engine import PredefinedAssetEngine\n", "from gs_quant.backtests.data_sources import DataManager\n", "from gs_quant.instrument import FXOption\n", "from gs_quant.data import DataFrequency\n", "\n", "from random import random\n", "import pandas as pd\n", "from datetime import datetime, timedelta\n", "\n", "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "id": "2b213057", "metadata": {}, "outputs": [], "source": [ "trigger_times = [\n", " datetime(2021, 1, 4, 14, 43, 0),\n", " datetime(2021, 1, 6, 14, 43, 0),\n", " datetime(2021, 1, 6, 14, 58, 0),\n", " datetime(2021, 1, 7, 13, 28, 0),\n", " datetime(2021, 1, 7, 14, 58, 0),\n", " datetime(2021, 1, 8, 13, 28, 0),\n", " datetime(2021, 1, 13, 13, 28, 0),\n", " datetime(2021, 1, 14, 13, 28, 0),\n", " datetime(2021, 1, 15, 13, 28, 0),\n", " datetime(2021, 1, 15, 14, 13, 0),\n", " datetime(2021, 1, 15, 14, 58, 0),\n", " datetime(2021, 1, 21, 13, 28, 0),\n", " datetime(2021, 1, 22, 14, 43, 0),\n", " datetime(2021, 1, 22, 14, 58, 0),\n", " datetime(2021, 1, 26, 14, 58, 0),\n", " datetime(2021, 1, 27, 13, 28, 0),\n", " datetime(2021, 1, 27, 18, 58, 0),\n", " datetime(2021, 1, 28, 13, 28, 0),\n", " datetime(2021, 1, 28, 14, 58, 0),\n", " datetime(2021, 1, 29, 13, 28, 0),\n", " datetime(2021, 1, 29, 14, 58, 0),\n", " datetime(2021, 2, 1, 14, 43, 0),\n", " datetime(2021, 2, 1, 14, 58, 0),\n", " datetime(2021, 2, 3, 14, 43, 0),\n", " datetime(2021, 2, 3, 14, 58, 0),\n", " datetime(2021, 2, 4, 13, 28, 0),\n", " datetime(2021, 2, 4, 14, 58, 0),\n", " datetime(2021, 2, 5, 13, 28, 0),\n", " datetime(2021, 2, 10, 13, 28, 0),\n", " datetime(2021, 2, 11, 13, 28, 0),\n", " datetime(2021, 2, 12, 14, 58, 0),\n", " datetime(2021, 2, 17, 13, 28, 0),\n", " datetime(2021, 2, 17, 14, 13, 0),\n", " datetime(2021, 2, 18, 13, 28, 0),\n", " datetime(2021, 2, 19, 14, 43, 0),\n", " datetime(2021, 2, 19, 14, 58, 0),\n", " datetime(2021, 2, 23, 14, 58, 0),\n", " datetime(2021, 2, 24, 14, 58, 0),\n", " datetime(2021, 2, 25, 13, 28, 0),\n", " datetime(2021, 2, 26, 13, 28, 0),\n", " datetime(2021, 2, 26, 14, 58, 0),\n", " datetime(2021, 3, 1, 14, 43, 0),\n", " datetime(2021, 3, 1, 14, 58, 0),\n", " datetime(2021, 3, 3, 14, 43, 0),\n", " datetime(2021, 3, 3, 14, 58, 0),\n", " datetime(2021, 3, 4, 13, 28, 0),\n", " datetime(2021, 3, 4, 14, 58, 0),\n", " datetime(2021, 3, 5, 13, 28, 0),\n", " datetime(2021, 3, 10, 13, 28, 0),\n", "]\n", "\n", "start_date, end_date = min(trigger_times), max(trigger_times)" ] }, { "cell_type": "code", "execution_count": null, "id": "6e26342c", "metadata": {}, "outputs": [], "source": [ "# Asset and Prices of asset\n", "# the asset defined here could be anything as we are going to provide the valuation of that asset.\n", "asset = FXOption(buy_sell='Buy', option_type='Put', pair='EURUSD', notional_amount='10m', name='EURUSD')\n", "\n", "\n", "# we need to provide the price of the asset in a data_manager\n", "\n", "data_manager = DataManager()\n", "index = pd.date_range(start_date, end_date.date() + timedelta(days=1), freq='T')\n", "# Here I am constructing random prices for the asset\n", "price = [100]\n", "for i in range(1, len(index)):\n", " price.append(price[i - 1] + (-1 if random() < 0.5 else 1))\n", "prices = pd.Series(price, index=index)\n", "\n", "eod_prices = prices.at_time('16:00').dropna()\n", "\n", "data_manager.add_data_source(prices, DataFrequency.REAL_TIME, asset, ValuationFixingType.PRICE)\n", "\n", "data_manager.add_data_source(eod_prices, DataFrequency.DAILY, asset, ValuationFixingType.PRICE)" ] }, { "cell_type": "code", "execution_count": null, "id": "991db60a", "metadata": {}, "outputs": [], "source": [ "# define the triggers, actions and strategy - this is common across all backtest engine implementations\n", "\n", "# the DateTrigger takes a list of dates when the an action should be taken\n", "trig_req = DateTriggerRequirements(trigger_times)\n", "# we are going to add our asset and hold it for 30 minutes\n", "trigger = DateTrigger(trig_req, AddTradeAction(asset, pd.Timedelta(minutes=30)))\n", "strategy = Strategy(None, trigger)" ] }, { "cell_type": "code", "execution_count": null, "id": "ddee6af3", "metadata": {}, "outputs": [], "source": [ "# construct a PredefinedAssetEngine including our asset prices and then run the backtest\n", "engine = PredefinedAssetEngine(data_manager)\n", "bt = engine.run_backtest(strategy, start=start_date, end=end_date, frequency='D')" ] }, { "cell_type": "code", "execution_count": null, "id": "468d4f3e", "metadata": {}, "outputs": [], "source": [ "# we can look at the performance of the backtest\n", "\n", "bt.performance.plot()" ] }, { "cell_type": "code", "execution_count": null, "id": "efeaa099", "metadata": {}, "outputs": [], "source": [ "# or we can look at a trade ledge which shows the trades that were entered and their individual pnls\n", "bt.trade_ledger()" ] }, { "cell_type": "code", "execution_count": null, "id": "f77f1e1d", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "id": "6f12331a", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/04_backtesting/examples/02_EquityVolEngine/040200_strategy_simple.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession, Environment\n", "from gs_quant.instrument import EqOption, OptionType, OptionStyle\n", "from gs_quant.backtests.strategy import Strategy\n", "from gs_quant.backtests.triggers import *\n", "from gs_quant.backtests.actions import *\n", "from gs_quant.backtests.equity_vol_engine import *\n", "from gs_quant.target.common import UnderlierType\n", "from datetime import date" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Define backtest dates\n", "start_date = date(2019, 9, 4)\n", "end_date = date(2020, 9, 4)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Define instrument for strategy. EqOption or EqVarianceSwap are supported\n", "\n", "# Equity Option\n", "instrument = EqOption(\n", " 'SX5E',\n", " underlier_type=UnderlierType.BBID,\n", " expirationDate='3m',\n", " strikePrice='ATM',\n", " optionType=OptionType.Call,\n", " optionStyle=OptionStyle.European,\n", ")\n", "\n", "# Uncomment to backtest Equity Variance Swap\n", "# instrument = EqVarianceSwap('.STOXX50E', expirationDate='3m', strikePrice='ATM')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Define a periodic trigger and action. Trade and roll the option instrument every 1m\n", "trade_action = EnterPositionQuantityScaledAction(\n", " priceables=instrument,\n", " trade_duration='1m',\n", " trade_quantity=1000,\n", " trade_quantity_type=BacktestTradingQuantityType.quantity,\n", ")\n", "trade_trigger = PeriodicTrigger(\n", " trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'),\n", " actions=trade_action,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Create strategy\n", "strategy = Strategy(initial_portfolio=None, triggers=[trade_trigger])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Run backtest\n", "backtest = EquityVolEngine.run_backtest(strategy, start=start_date, end=end_date)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Plot the performance\n", "pnl = backtest.get_measure_series(FlowVolBacktestMeasure.PNL)\n", "pnl.plot(legend=True, label='PNL')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/04_backtesting/examples/02_EquityVolEngine/040201_strategy_delta_hedged.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession, Environment\n", "from gs_quant.instrument import EqOption, OptionType, OptionStyle\n", "from gs_quant.backtests.strategy import Strategy\n", "from gs_quant.backtests.triggers import *\n", "from gs_quant.backtests.actions import *\n", "from gs_quant.backtests.equity_vol_engine import *\n", "from gs_quant.target.common import UnderlierType\n", "from datetime import date" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Define backtest dates\n", "start_date = date(2019, 9, 4)\n", "end_date = date(2020, 9, 4)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Define option instruments for strategy\n", "option = EqOption(\n", " 'SX5E',\n", " underlierType=UnderlierType.BBID,\n", " expirationDate='3m',\n", " strikePrice='ATM',\n", " optionType=OptionType.Call,\n", " optionStyle=OptionStyle.European,\n", ")\n", "\n", "long_call = EqOption(\n", " 'SX5E',\n", " underlierType=UnderlierType.BBID,\n", " expiration_date='3m',\n", " strike_price='ATM',\n", " option_type=OptionType.Call,\n", " option_style=OptionStyle.European,\n", " buy_sell=BuySell.Buy,\n", ")\n", "short_put = EqOption(\n", " 'SX5E',\n", " underlierType=UnderlierType.BBID,\n", " expiration_date='3m',\n", " strike_price='ATM',\n", " option_type=OptionType.Put,\n", " option_style=OptionStyle.European,\n", " buy_sell=BuySell.Sell,\n", ")\n", "\n", "hedge_portfolio = Portfolio(name='SynFwd', priceables=[long_call, short_put])" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Define a periodic trade trigger and action.\n", "\n", "# Trade and roll 1000 units of the option instrument every 1m\n", "trade_action = EnterPositionQuantityScaledAction(\n", " priceables=option,\n", " trade_duration='1m',\n", " trade_quantity=1000,\n", " trade_quantity_type=BacktestTradingQuantityType.quantity,\n", ")\n", "trade_trigger = PeriodicTrigger(\n", " trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'),\n", " actions=trade_action,\n", ")\n", "\n", "# Hedge option delta every day using the synthetic forward\n", "hedge_action = HedgeAction(EqDelta, priceables=hedge_portfolio, trade_duration='1b')\n", "hedge_trigger = PeriodicTrigger(\n", " trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b'),\n", " actions=hedge_action,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Create strategy\n", "strategy = Strategy(initial_portfolio=None, triggers=[trade_trigger, hedge_trigger])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Run backtest\n", "backtest = EquityVolEngine.run_backtest(strategy, start=start_date, end=end_date)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Plot the performance\n", "pnl = backtest.get_measure_series(FlowVolBacktestMeasure.PNL)\n", "pnl.plot(legend=True, label='PNL')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.6" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/04_backtesting/examples/02_EquityVolEngine/040202_strategy_pnl_decomposition.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession, Environment\n", "from gs_quant.instrument import EqOption, OptionType, OptionStyle\n", "from gs_quant.backtests.strategy import Strategy\n", "from gs_quant.backtests.triggers import *\n", "from gs_quant.backtests.actions import *\n", "from gs_quant.backtests.equity_vol_engine import *\n", "from gs_quant.target.common import UnderlierType\n", "from datetime import date" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Define backtest dates\n", "start_date = date(2019, 9, 4)\n", "end_date = date(2020, 9, 4)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Define option instrument for strategy\n", "option = EqOption(\n", " 'SX5E',\n", " underlierType=UnderlierType.BBID,\n", " expirationDate='3m',\n", " strikePrice='ATM',\n", " optionType=OptionType.Call,\n", " optionStyle=OptionStyle.European,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Define a periodic trade trigger and action. Trade and roll 1000 units of the option instrument every 1m\n", "trade_action = EnterPositionQuantityScaledAction(\n", " priceables=option,\n", " trade_duration='1m',\n", " trade_quantity=1000,\n", " trade_quantity_type=BacktestTradingQuantityType.quantity,\n", ")\n", "trade_trigger = PeriodicTrigger(\n", " trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'),\n", " actions=trade_action,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Create strategy\n", "strategy = Strategy(initial_portfolio=None, triggers=[trade_trigger])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Run backtest\n", "backtest = EquityVolEngine.run_backtest(strategy, start=start_date, end=end_date)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Plot the performance\n", "pnl = backtest.get_measure_series(FlowVolBacktestMeasure.PNL)\n", "pnl.plot(legend=True, label='PNL Total')\n", "\n", "pnl_carry = backtest.get_measure_series(FlowVolBacktestMeasure.PNL_carry)\n", "pnl_carry.plot(legend=True, label='PNL Carry')\n", "\n", "pnl_vol = backtest.get_measure_series(FlowVolBacktestMeasure.PNL_vol)\n", "pnl_vol.plot(legend=True, label='PNL Vol')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/04_backtesting/examples/02_EquityVolEngine/040203_strategy_with_signals.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession, Environment\n", "from gs_quant.instrument import EqOption, OptionType, OptionStyle\n", "from gs_quant.backtests.strategy import Strategy\n", "from gs_quant.backtests.triggers import *\n", "from gs_quant.backtests.actions import *\n", "from gs_quant.backtests.equity_vol_engine import *\n", "from gs_quant.target.common import UnderlierType\n", "from datetime import date" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Define backtest dates\n", "start_date = date(2019, 9, 4)\n", "end_date = date(2020, 9, 4)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Define option instrument for strategy\n", "option = EqOption(\n", " 'SX5E',\n", " underlierType=UnderlierType.BBID,\n", " expirationDate='3m',\n", " strikePrice='ATM',\n", " optionType=OptionType.Call,\n", " optionStyle=OptionStyle.European,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Define a periodic trade action. Trade 1000 units of the option instrument and hold for 1m\n", "trade_action = EnterPositionQuantityScaledAction(\n", " priceables=option,\n", " trade_duration='1m',\n", " trade_quantity=1000,\n", " trade_quantity_type=BacktestTradingQuantityType.quantity,\n", ")\n", "\n", "# Define an entry signal trigger. This is a list of dates when the entry signal is true.\n", "# When entry signal is true, the strategy will enter a position (only if no position is already held)\n", "entry_dates = (\n", " date(2019, 9, 20),\n", " date(2019, 10, 18),\n", " date(2019, 11, 15),\n", " date(2019, 12, 20),\n", " date(2020, 1, 17),\n", " date(2020, 2, 21),\n", ")\n", "entry_trigger = AggregateTrigger(\n", " trigger_requirements=AggregateTriggerRequirements(\n", " [DateTriggerRequirements(dates=entry_dates), PortfolioTriggerRequirements('len', 0, TriggerDirection.EQUAL)]\n", " ),\n", " actions=trade_action,\n", ")\n", "\n", "# Define an exit signal trigger. This is a list of dates when the exit signal is true.\n", "# When the exit signal is true the strategy will exit the full position (only if already holding a position)\n", "exit_dates = (date(2019, 11, 4), date(2020, 3, 2))\n", "exit_trigger = AggregateTrigger(\n", " trigger_requirements=AggregateTriggerRequirements(\n", " [DateTriggerRequirements(dates=exit_dates), PortfolioTriggerRequirements('len', 0, TriggerDirection.ABOVE)]\n", " ),\n", " actions=ExitTradeAction(),\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Create strategy\n", "strategy = Strategy(initial_portfolio=None, triggers=[entry_trigger, exit_trigger])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Run backtest\n", "backtest = EquityVolEngine.run_backtest(strategy, start=start_date, end=end_date)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Plot the performance\n", "pnl = backtest.get_measure_series(FlowVolBacktestMeasure.PNL)\n", "pnl.plot(legend=True, label='PNL Total')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/04_backtesting/examples/02_EquityVolEngine/040204_strategy_market_model.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "3285e18c-43b9-403a-b9b4-c1b9d364ebfe", "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession, Environment\n", "from gs_quant.instrument import EqOption, OptionType, OptionStyle\n", "from gs_quant.backtests.strategy import Strategy\n", "from gs_quant.backtests.triggers import *\n", "from gs_quant.backtests.actions import *\n", "from gs_quant.backtests.equity_vol_engine import *\n", "from gs_quant.target.common import UnderlierType\n", "from datetime import date" ] }, { "cell_type": "code", "execution_count": null, "id": "b745f15f-2962-4930-b3f2-6c90aee2b79a", "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "id": "3c167482-fd99-4a2b-8972-d235ca583c1f", "metadata": {}, "outputs": [], "source": [ "# Define backtest dates\n", "start_date = date(2019, 9, 4)\n", "end_date = date(2020, 9, 4)" ] }, { "cell_type": "code", "execution_count": null, "id": "997f3de3-1f8a-4f07-939c-f8ba74323351", "metadata": {}, "outputs": [], "source": [ "# Define option instruments for strategy\n", "instrument = EqOption(\n", " 'SX5E',\n", " underlierType=UnderlierType.BBID,\n", " expirationDate='3m',\n", " strikePrice='ATM',\n", " optionType=OptionType.Call,\n", " optionStyle=OptionStyle.European,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "2fc22284-6ef6-455d-bcfa-050583f5dd7d", "metadata": {}, "outputs": [], "source": [ "# Define a periodic trigger and action. Trade and roll the option instrument every 1m\n", "trade_action = EnterPositionQuantityScaledAction(\n", " priceables=instrument,\n", " trade_duration='1m',\n", " trade_quantity=1000,\n", " trade_quantity_type=BacktestTradingQuantityType.quantity,\n", ")\n", "trade_trigger = PeriodicTrigger(\n", " trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'),\n", " actions=trade_action,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "ee16b5f8-ada2-4ce1-bf71-508a2dcfe9ef", "metadata": {}, "outputs": [], "source": [ "# Create strategy\n", "strategy = Strategy(initial_portfolio=None, triggers=[trade_trigger])" ] }, { "cell_type": "code", "execution_count": null, "id": "0b8a48e8-eb9b-49e2-a817-19acc28e20cb", "metadata": {}, "outputs": [], "source": [ "# Run backtest with SFK market model (the default). Other options are SD and SVR\n", "backtest = EquityVolEngine.run_backtest(strategy, start=start_date, end=end_date, market_model='SFK')\n", "pnl_sfk = backtest.get_measure_series(FlowVolBacktestMeasure.PNL)\n", "delta_sfk = backtest.get_measure_series(FlowVolBacktestMeasure.delta)\n", "gamma_sfk = backtest.get_measure_series(FlowVolBacktestMeasure.gamma)\n", "vega_sfk = backtest.get_measure_series(FlowVolBacktestMeasure.vega)" ] }, { "cell_type": "code", "execution_count": null, "id": "31536684-42dd-41d5-ad87-a287e484dfb6", "metadata": {}, "outputs": [], "source": [ "# Run backtest with SVR market model\n", "backtest = EquityVolEngine.run_backtest(strategy, start=start_date, end=end_date, market_model='SVR')\n", "pnl_svr = backtest.get_measure_series(FlowVolBacktestMeasure.PNL)\n", "delta_svr = backtest.get_measure_series(FlowVolBacktestMeasure.delta)\n", "gamma_svr = backtest.get_measure_series(FlowVolBacktestMeasure.gamma)\n", "vega_svr = backtest.get_measure_series(FlowVolBacktestMeasure.vega)" ] }, { "cell_type": "code", "execution_count": null, "id": "fb3d4a7b-6a57-4519-bf9f-6495011464c8", "metadata": {}, "outputs": [], "source": [ "# Market model does not affect performance calculation\n", "relative_pnl_diff = (pnl_svr - pnl_sfk) / pnl_sfk\n", "relative_pnl_diff.plot(legend=True, label='Relative PnL diff')" ] }, { "cell_type": "code", "execution_count": null, "id": "dd264604-3072-4ade-881f-7b4ef1836525", "metadata": {}, "outputs": [], "source": [ "# Market model affects delta and gamma calculations\n", "relative_delta_diff = (delta_svr - delta_sfk) / delta_sfk\n", "relative_delta_diff.plot(legend=True, label='Relative delta diff')" ] }, { "cell_type": "code", "execution_count": null, "id": "585b3d09-4675-41ed-9032-39ccb76704a1", "metadata": {}, "outputs": [], "source": [ "relative_gamma_diff = (gamma_svr - gamma_sfk) / gamma_sfk\n", "relative_gamma_diff.plot(legend=True, label='Relative gamma diff')" ] }, { "cell_type": "code", "execution_count": null, "id": "27a00835-d940-4960-9498-536298674603", "metadata": {}, "outputs": [], "source": [ "# Market model does not affect the calculation of any other risk measures\n", "relative_vega_diff = (vega_svr - vega_sfk) / vega_sfk\n", "relative_vega_diff.plot(legend=True, label='Relative vega diff')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.6" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/04_backtesting/examples/02_EquityVolEngine/040205_strategy_scaled_add_action.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "a53fde7e-f24f-4cb0-9962-9ed075790628", "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "\n", "GsSession.use()" ] }, { "cell_type": "code", "execution_count": null, "id": "6a9bc9fb-3914-49fc-b3cf-4e59d22fd8a2", "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import OptionStyle\n", "from gs_quant.backtests.strategy import Strategy\n", "from gs_quant.backtests.triggers import *\n", "from gs_quant.backtests.actions import *\n", "from gs_quant.backtests.equity_vol_engine import *\n", "from datetime import datetime, date\n", "\n", "import gs_quant.risk as risk" ] }, { "cell_type": "code", "execution_count": null, "id": "2a6175aa-bd47-4cd3-bead-dcebe4f5ea8d", "metadata": {}, "outputs": [], "source": [ "# Initialize session\n", "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "id": "5e2c6435-b2a0-4824-ae5c-4d4ef0f42ee4", "metadata": {}, "outputs": [], "source": [ "# Define backtest dates\n", "start_date = date(2024, 4, 1)\n", "end_date = datetime.today().date()" ] }, { "cell_type": "code", "execution_count": null, "id": "62435b59-ec26-4d5b-9b74-fd72396a468d", "metadata": {}, "outputs": [], "source": [ "# Define instruments for strategy\n", "# Portfolio of two eq options\n", "call = EqOption(\n", " '.STOXX50E',\n", " expiration_date='1m',\n", " strike_price='ATM',\n", " option_type=OptionType.Call,\n", " option_style=OptionStyle.European,\n", " name='call',\n", ")\n", "put = EqOption(\n", " '.STOXX50E',\n", " expiration_date='1m',\n", " strike_price='ATM',\n", " option_type=OptionType.Put,\n", " option_style=OptionStyle.European,\n", " name='put',\n", ")\n", "portfolio = Portfolio(name='portfolio', priceables=[call, put])" ] }, { "cell_type": "markdown", "id": "ea1412f0-055e-4954-9d05-527e93018181", "metadata": {}, "source": [ "### Entering a position monthly" ] }, { "cell_type": "code", "execution_count": null, "id": "adb4fc9d-1c23-40a6-b109-3ca8425ad623", "metadata": {}, "outputs": [], "source": [ "# Trade the position monthly without any scaling\n", "trade_action = AddTradeAction(priceables=portfolio, trade_duration='1m', name='act')\n", "trade_trigger = PeriodicTrigger(\n", " trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'),\n", " actions=trade_action,\n", ")\n", "\n", "strategy = Strategy(None, trade_trigger)\n", "\n", "# Run backtest daily\n", "engine = EquityVolEngine\n", "backtest = engine.run_backtest(strategy, start=start_date, end=end_date)" ] }, { "cell_type": "code", "execution_count": null, "id": "932678aa-cf61-4c71-b057-55a3ab81c241", "metadata": {}, "outputs": [], "source": [ "pnl = backtest.get_measure_series(FlowVolBacktestMeasure.PNL)\n", "pnl.plot(legend=True, label='PNL')" ] }, { "cell_type": "markdown", "id": "5d3c2217-f45b-4c84-a64f-afdc9e91940f", "metadata": {}, "source": [ "### Scaling quantity" ] }, { "cell_type": "code", "execution_count": null, "id": "7998da4c-0a08-4698-97af-169654f1f11a", "metadata": {}, "outputs": [], "source": [ "# Scale the position quantity (no. of options) by 2\n", "trade_action_scaled = AddScaledTradeAction(\n", " priceables=portfolio,\n", " trade_duration='1m',\n", " scaling_type=ScalingActionType.size,\n", " scaling_risk=None,\n", " scaling_level=2,\n", " name='scaled_act',\n", ")\n", "trade_trigger_scaled = PeriodicTrigger(\n", " trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'),\n", " actions=trade_action_scaled,\n", ")\n", "\n", "strategy = Strategy(None, trade_trigger_scaled)\n", "\n", "# Run backtest daily\n", "engine = EquityVolEngine\n", "backtest = engine.run_backtest(strategy, start=start_date, end=end_date)" ] }, { "cell_type": "code", "execution_count": null, "id": "14d7e2f3-8f06-4a89-9486-9f5fb35363f1", "metadata": {}, "outputs": [], "source": [ "# note the performance is twice the above graph\n", "pnl = backtest.get_measure_series(FlowVolBacktestMeasure.PNL)\n", "pnl.plot(legend=True, label='PNL')" ] }, { "cell_type": "markdown", "id": "b52d3f47-ab87-4074-ae34-3ef3bb7c54c9", "metadata": {}, "source": [ "### Scaling a risk measure" ] }, { "cell_type": "code", "execution_count": null, "id": "2dff6a31-583b-4858-8c7c-ef259f1a6659", "metadata": {}, "outputs": [], "source": [ "# Initial and final Vega of the portfolio\n", "with PricingContext(start_date):\n", " start_vega = portfolio.calc(risk.EqVega)\n", "\n", "with PricingContext(end_date):\n", " end_vega = portfolio.calc(risk.EqVega)\n", "\n", "print(start_vega.aggregate())\n", "print(end_vega.aggregate())" ] }, { "cell_type": "code", "execution_count": null, "id": "01384407-a6c2-4083-8577-f8a98468a429", "metadata": {}, "outputs": [], "source": [ "# Scale the position vega to 100\n", "trade_action_scaled = AddScaledTradeAction(\n", " priceables=portfolio,\n", " trade_duration='1m',\n", " scaling_type=ScalingActionType.risk_measure,\n", " scaling_risk=risk.EqVega,\n", " scaling_level=100,\n", " name='scaled_vega_act',\n", ")\n", "trade_trigger_scaled = PeriodicTrigger(\n", " trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'),\n", " actions=trade_action_scaled,\n", ")\n", "\n", "strategy = Strategy(None, trade_trigger_scaled)\n", "\n", "# Run backtest daily\n", "engine = EquityVolEngine\n", "backtest = engine.run_backtest(strategy, start=start_date, end=end_date)" ] }, { "cell_type": "code", "execution_count": null, "id": "10ef8a2a-b290-40dc-ad05-e070897652c3", "metadata": {}, "outputs": [], "source": [ "# note the scaling should be approx the 100/vega\n", "pnl = backtest.get_measure_series(FlowVolBacktestMeasure.PNL)\n", "pnl.plot(legend=True, label='PNL')" ] }, { "cell_type": "markdown", "id": "b5984b12-10ce-498a-9f78-335bd135aabe", "metadata": {}, "source": [ "### NAV Trading" ] }, { "cell_type": "code", "execution_count": null, "id": "417169e2-addf-4c0b-a355-cff4f35b106a", "metadata": {}, "outputs": [], "source": [ "# Create a NAV-based action that starts with 100 available to spend\n", "start_date = date(2024, 1, 1)\n", "end_date = datetime.today().date()\n", "\n", "trade_action_scaled = EnterPositionQuantityScaledAction(\n", " priceables=portfolio,\n", " trade_duration='1m',\n", " trade_quantity=100,\n", " trade_quantity_type=BacktestTradingQuantityType.NAV,\n", " name='nav_act',\n", ")\n", "trade_trigger_scaled = PeriodicTrigger(\n", " trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'),\n", " actions=trade_action_scaled,\n", ")\n", "\n", "strategy = Strategy(None, trade_trigger_scaled)\n", "\n", "# Run backtest daily\n", "engine = EquityVolEngine\n", "backtest = engine.run_backtest(strategy, start=start_date, end=end_date)" ] }, { "cell_type": "code", "execution_count": null, "id": "3832acf6-370d-4a77-ab57-219134a9ee6f", "metadata": {}, "outputs": [], "source": [ "pnl = backtest.get_measure_series(FlowVolBacktestMeasure.PNL)\n", "pnl.plot(legend=True, label='PNL')" ] }, { "cell_type": "code", "execution_count": null, "id": "19ba18f5-e905-4183-b47c-cd6cc7c74b0d", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/04_backtesting/examples/03_GenericEngine/040300_strategy_periodic_trigger.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from datetime import datetime, date\n", "import pandas as pd\n", "from gs_quant.instrument import FXOption\n", "from gs_quant.common import BuySell, OptionType\n", "from gs_quant.backtests.triggers import PeriodicTrigger, PeriodicTriggerRequirements\n", "from gs_quant.backtests.actions import AddTradeAction\n", "from gs_quant.backtests.generic_engine import GenericEngine\n", "from gs_quant.backtests.strategy import Strategy\n", "from gs_quant.risk import Price" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Initialize session\n", "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Buy $100k 2y USDJPY ATMF call, roll monthly" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Define backtest dates\n", "start_date = date(2021, 6, 1)\n", "end_date = datetime.today().date()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Define instrument for strategy\n", "\n", "# FX Option\n", "call = FXOption(\n", " buy_sell=BuySell.Buy,\n", " option_type=OptionType.Call,\n", " pair='USDJPY',\n", " strike_price='ATMF',\n", " expiration_date='2y',\n", " name='2y_call',\n", " premium=0,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Periodic trigger: based on frequency\n", "freq = '1m'\n", "trig_req = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency=freq)\n", "\n", "# hold the trade for 1m\n", "actions = AddTradeAction(call, freq)\n", "\n", "# starting with empty portfolio (first arg to Strategy), apply actions on trig_req\n", "triggers = PeriodicTrigger(trig_req, actions)\n", "strategy = Strategy(None, triggers)\n", "\n", "# run backtest daily\n", "GE = GenericEngine()\n", "backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View backtest trade ledger\n", "backtest.trade_ledger()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View results summary\n", "backtest.result_summary" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View Mark to Market\n", "pd.DataFrame({'Generic backtester': backtest.result_summary[Price]}).plot(figsize=(10, 6), title='Mark to market')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View Performance\n", "pd.DataFrame({'Generic backtester': backtest.result_summary['Cumulative Cash'] + backtest.result_summary[Price]}).plot(\n", " figsize=(10, 6), title='Performance'\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: gs_quant/documentation/04_backtesting/examples/03_GenericEngine/040301_strategy_risk_trigger.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from datetime import datetime, date\n", "import pandas as pd\n", "from gs_quant.instrument import FXOption, FXForward\n", "from gs_quant.common import BuySell, OptionType, AggregationLevel\n", "from gs_quant.backtests.triggers import (\n", " PeriodicTrigger,\n", " PeriodicTriggerRequirements,\n", " StrategyRiskTrigger,\n", " RiskTriggerRequirements,\n", " TriggerDirection,\n", ")\n", "from gs_quant.backtests.actions import AddTradeAction, HedgeAction\n", "from gs_quant.backtests.generic_engine import GenericEngine\n", "from gs_quant.backtests.strategy import Strategy\n", "from gs_quant.risk import Price, FXDelta" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Initialize session\n", "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Buy $100k 1y EURUSD ATMF call, roll monthly, delta hedge when breach of +50k" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Define backtest dates\n", "start_date = date(2021, 6, 1)\n", "end_date = datetime.today().date()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Define instrument for strategy\n", "\n", "# FX Option\n", "call = FXOption(\n", " buy_sell=BuySell.Buy,\n", " option_type=OptionType.Call,\n", " pair='EURUSD',\n", " strike_price='ATMF',\n", " expiration_date='1y',\n", " notional_amount=1e5,\n", " name='1y_call',\n", " premium=0,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Let's look at FX Delta for strategy without hedging\n", "\n", "# Define frequency for adding trade\n", "freq_add = '1m'\n", "trig_req = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency=freq_add)\n", "action_add = AddTradeAction(call, freq_add)\n", "\n", "# Starting with empty portfolio (first arg to Strategy), apply actions in order on trig_req\n", "triggers = PeriodicTrigger(trig_req, action_add)\n", "strategy = Strategy(None, triggers)\n", "\n", "# Run backtest daily\n", "GE = GenericEngine()\n", "backtest = GE.run_backtest(\n", " strategy,\n", " start=start_date,\n", " end=end_date,\n", " frequency='1b',\n", " show_progress=True,\n", " risks=[Price, FXDelta(aggregation_level=AggregationLevel.Type, currency='USD')],\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View FX Delta risk\n", "b1 = pd.DataFrame(\n", " {'No Hedge': backtest.result_summary[FXDelta(aggregation_level=AggregationLevel.Type, currency='USD')]}\n", ")\n", "b1.plot(figsize=(10, 6), title='FX Delta')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View results summary\n", "backtest.result_summary" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Let's hedge delta when it is above +50k\n", "\n", "# Define frequency for adding trade\n", "freq_add = '1m'\n", "trig_req_add = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency=freq_add)\n", "action_add = AddTradeAction(call, freq_add)\n", "trigger_add = PeriodicTrigger(trig_req_add, action_add)\n", "\n", "# Define risk to hedge\n", "hedge_risk = FXDelta(aggregation_level='Type', currency='USD')\n", "\n", "# Define FX Forward to hedge with\n", "fwd_hedge = FXForward(pair='EURUSD', settlement_date='1y', notional_amount=1e5, name='1y_forward')\n", "action_hedge = HedgeAction(hedge_risk, fwd_hedge)\n", "\n", "# Define hedge triggers\n", "trig_req_hedge = RiskTriggerRequirements(risk=hedge_risk, trigger_level=50e3, direction=TriggerDirection.ABOVE)\n", "trigger_risk = StrategyRiskTrigger(trig_req_hedge, action_hedge)\n", "\n", "# Starting with empty portfolio (first arg to Strategy), apply actions in order on trig_req\n", "strategy = Strategy(None, [trigger_add, trigger_risk])\n", "\n", "# Run backtest daily\n", "GE = GenericEngine()\n", "backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View FX Delta risk\n", "b2 = pd.DataFrame(\n", " {'Hedge above +50k': backtest.result_summary[FXDelta(aggregation_level=AggregationLevel.Type, currency='USD')]}\n", ")\n", "b2.plot(figsize=(10, 6), title='FX Delta')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View backtest trade ledger\n", "backtest.trade_ledger()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Let's compare the two results\n", "b1.columns = ['No Hedge']\n", "b2.columns = ['Hedge above +50k']\n", "pd.concat([b1, b2], axis=1).plot(figsize=(10, 6))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: gs_quant/documentation/04_backtesting/examples/03_GenericEngine/040302_strategy_delta_hedge_FX.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from datetime import datetime, date\n", "import pandas as pd\n", "from gs_quant.instrument import FXOption, FXForward\n", "from gs_quant.common import BuySell, OptionType, AggregationLevel\n", "from gs_quant.backtests.triggers import PeriodicTrigger, PeriodicTriggerRequirements\n", "from gs_quant.backtests.actions import AddTradeAction, HedgeAction\n", "from gs_quant.backtests.generic_engine import GenericEngine\n", "from gs_quant.backtests.strategy import Strategy\n", "from gs_quant.risk import Price, FXDelta" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Initialize session\n", "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Buy $100k 2y USDJPY ATMF call, roll monthly, delta hedge monthly" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Define backtest dates\n", "start_date = date(2021, 6, 1)\n", "end_date = datetime.today().date()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Define instrument for strategy\n", "\n", "# FX Option\n", "call = FXOption(\n", " buy_sell=BuySell.Buy,\n", " option_type=OptionType.Call,\n", " pair='USDJPY',\n", " strike_price='ATMF',\n", " expiration_date='2y',\n", " notional_amount=1e6,\n", " name='2y_call',\n", " premium=0,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Risk Trigger: based on frequency threshold, delta hedge by Forward trade\n", "\n", "# Define frequency for adding trade\n", "freq_add = '1m'\n", "trig_req = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency=freq_add)\n", "action_add = AddTradeAction(call, freq_add)\n", "\n", "# Define trade to hedge FX Delta\n", "freq_hedge = '1m'\n", "fwd_hedge = FXForward(pair='USDJPY', settlement_date='2y', name='2y_forward')\n", "hedge_risk = FXDelta(currency='USD', aggregation_level='Type')\n", "action_hedge = HedgeAction(hedge_risk, fwd_hedge, freq_hedge)\n", "\n", "# starting with empty portfolio (first arg to Strategy), apply actions in order on trig_req\n", "triggers = PeriodicTrigger(trig_req, [action_add, action_hedge])\n", "strategy = Strategy(None, triggers)\n", "\n", "# run backtest daily\n", "GE = GenericEngine()\n", "backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View backtest trade ledger\n", "backtest.trade_ledger()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View results summary\n", "backtest.result_summary" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View Mark to Market\n", "pd.DataFrame({'Generic backtester': backtest.result_summary[Price]}).plot(figsize=(10, 6), title='Mark to market')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View Performance\n", "pd.DataFrame({'Generic backtester': backtest.result_summary['Cumulative Cash'] + backtest.result_summary[Price]}).plot(\n", " figsize=(10, 6), title='Performance'\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View FX Delta risk\n", "pd.DataFrame(\n", " {'Generic backtester': backtest.result_summary[FXDelta(aggregation_level=AggregationLevel.Type, currency='USD')]}\n", ").plot(figsize=(10, 6), title='FX Delta')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: gs_quant/documentation/04_backtesting/examples/03_GenericEngine/040303_strategy_delta_hedge_Rates.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from datetime import datetime, date\n", "from gs_quant.instrument import IRSwap, IRSwaption\n", "from gs_quant.backtests.triggers import PeriodicTrigger, PeriodicTriggerRequirements\n", "from gs_quant.backtests.actions import AddTradeAction, HedgeAction\n", "from gs_quant.backtests.generic_engine import GenericEngine\n", "from gs_quant.backtests.strategy import Strategy\n", "from gs_quant.risk import Price, IRDelta\n", "from gs_quant.common import Currency" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Initialize session\n", "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Sell swaption monthly, delta hedge monthly" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Define backtest dates\n", "start_date = date(2021, 6, 1)\n", "end_date = datetime.today().date()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Define instrument for strategy\n", "\n", "# IR Swaption\n", "option = IRSwaption(expiration_date='1m', termination_date='30y', notional_currency=Currency.EUR, buy_sell='Sell')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Risk Trigger: based on frequency threshold, delta hedge by swap trade\n", "\n", "# Define frequency for adding trade\n", "freq_add = '1m'\n", "trig_req = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency=freq_add)\n", "action_add = AddTradeAction(option, 'expiration_date')\n", "\n", "# Define trade to hedge Delta\n", "freq_hedge = '1m'\n", "swap_hedge = IRSwap(termination_date='30y', notional_currency=Currency.EUR, notional_amount=100e6, name='30yhedge')\n", "hedge_risk = IRDelta(aggregation_level='Type', currency=Currency.USD)\n", "action_hedge = HedgeAction(hedge_risk, swap_hedge)\n", "\n", "# Starting with empty portfolio (first arg to Strategy), apply actions in order on trig_req\n", "triggers = PeriodicTrigger(trig_req, [action_add, action_hedge])\n", "strategy = Strategy(None, triggers)\n", "\n", "# Run backtest daily\n", "GE = GenericEngine()\n", "# The results will be in local ccy, EUR in this case. To change them to USD, specify result_ccy in the backtest args\n", "backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View backtest trade ledger\n", "backtest.trade_ledger()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View results summary\n", "backtest.result_summary" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Plot results\n", "df = backtest.result_summary\n", "df['Performance'] = df[Price] + df['Cumulative Cash']\n", "df.plot(figsize=(10, 10)).legend(bbox_to_anchor=(1, 1))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: gs_quant/documentation/04_backtesting/examples/03_GenericEngine/040304_strategy_mean_reversion.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from datetime import datetime, date\n", "import pandas as pd\n", "from gs_quant.instrument import IRSwap\n", "from gs_quant.backtests.triggers import MeanReversionTrigger, MeanReversionTriggerRequirements\n", "from gs_quant.backtests.actions import AddTradeAction\n", "from gs_quant.backtests.generic_engine import GenericEngine\n", "from gs_quant.backtests.strategy import Strategy\n", "from gs_quant.backtests.data_sources import GenericDataSource, MissingDataStrategy\n", "from gs_quant.risk import Price\n", "from gs_quant.common import Currency, PayReceive\n", "from gs_quant.data import Dataset" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Initialize session\n", "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics', 'read_product_data'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Buy 10y EUR payers" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Define backtest dates\n", "start_date = date(2021, 6, 1)\n", "end_date = datetime.today().date()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Define instrument for strategy\n", "\n", "# IR Swap\n", "swap = IRSwap(\n", " pay_or_receive=PayReceive.Pay,\n", " termination_date='10y',\n", " notional_currency=Currency.EUR,\n", " notional_amount=1e4,\n", " fixed_rate='ATM',\n", " name='swap_10y',\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Dataset from Marquee Data Catalog: https://marquee.gs.com/s/developer/datasets/SWAPRATES_STANDARD" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Get data set for 10y EUR swap rates\n", "ds = Dataset('SWAPRATES_STANDARD')\n", "data = ds.get_data(start_date, assetId=['MA5WM2QWRVMYKDK0'])\n", "data_10y = data.loc[data['tenor'] == '10y']\n", "data_10y.head()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": true } }, "outputs": [], "source": [ "# Mean Reversion Trigger\n", "\n", "# Define our actions: add the swap\n", "action = AddTradeAction(swap)\n", "\n", "# Define data source\n", "s = pd.Series(data_10y['rate'].to_dict())\n", "data_source = GenericDataSource(s, MissingDataStrategy.fill_forward)\n", "\n", "# Define bounds\n", "z_score_bound = 2\n", "rolling_mean_window = 30\n", "rolling_std_window = 30\n", "\n", "# Build trigger requirements\n", "# Sell when value hits z_score_bound on up side, Buy when value hits z_score_bound on down side\n", "# Close out position when value crosses mean for the rolling_mean_window\n", "trig_req = MeanReversionTriggerRequirements(data_source, z_score_bound, rolling_mean_window, rolling_std_window)\n", "trigger = MeanReversionTrigger(trig_req, action)\n", "strategy = Strategy(None, trigger)\n", "\n", "# Run backtest daily\n", "GE = GenericEngine()\n", "backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View backtest trade ledger\n", "backtest.trade_ledger()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View results summary\n", "backtest.result_summary" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View Performance\n", "pd.DataFrame({'Generic backtester': backtest.result_summary['Cumulative Cash'] + backtest.result_summary[Price]}).plot(\n", " figsize=(10, 6), title='Performance'\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: gs_quant/documentation/04_backtesting/examples/03_GenericEngine/040305_strategy_exit_trade_action.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from datetime import date, datetime\n", "from gs_quant.instrument import IRSwaption\n", "from gs_quant.common import BuySell, AggregationLevel, Currency\n", "from gs_quant.backtests.triggers import StrategyRiskTrigger, RiskTriggerRequirements, TriggerDirection\n", "from gs_quant.backtests.actions import AddTradeAction, ExitTradeAction\n", "from gs_quant.backtests.generic_engine import GenericEngine\n", "from gs_quant.backtests.strategy import Strategy\n", "from gs_quant.risk import Price, IRDelta\n", "import pandas as pd" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Initialize session\n", "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Define backtest dates\n", "start_date = date(2021, 6, 1)\n", "end_date = datetime.today().date()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Define instruments for strategy\n", "\n", "# USD swaption\n", "swaption = IRSwaption(\n", " expiration_date='6m',\n", " termination_date='10y',\n", " notional_currency=Currency.USD,\n", " buy_sell=BuySell.Sell,\n", " strike='=solvefor(25e3,bp)',\n", " name='swaption10y',\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Start with USD swaption on start_date, rebalance when IR Delta breaches +75k" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Define risk to hedge\n", "rebal_risk = IRDelta(aggregation_level=AggregationLevel.Type, currency='USD')\n", "\n", "# Define trade actions when breach\n", "action_exit = ExitTradeAction()\n", "action_add = AddTradeAction(swaption)\n", "\n", "# Define rebalance triggers\n", "trig_req = RiskTriggerRequirements(risk=rebal_risk, trigger_level=1e3, direction=TriggerDirection.ABOVE)\n", "# Order is important here, we first want to exit all positions in portfolio, then add the new trade\n", "trigger_risk = StrategyRiskTrigger(trig_req, [action_exit, action_add])\n", "\n", "# Starting with a swaption (first arg to Strategy), apply actions in order\n", "strategy = Strategy(swaption, trigger_risk)\n", "\n", "# Run backtest daily\n", "GE = GenericEngine()\n", "backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View results summary\n", "backtest.result_summary" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View backtest trade ledger - this includes all trades that were added and removed excluding starting portfolio\n", "backtest.trade_ledger()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View Mark to Market\n", "pd.DataFrame({'Generic backtester': backtest.result_summary[Price]}).plot(figsize=(10, 6), title='Mark to market')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View Performance\n", "pd.DataFrame({'Generic backtester': backtest.result_summary['Cumulative Cash'] + backtest.result_summary[Price]}).plot(\n", " figsize=(10, 6), title='Performance'\n", ")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: gs_quant/documentation/04_backtesting/examples/03_GenericEngine/040306_strategy_mkt_trigger.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": true } }, "outputs": [], "source": [ "from datetime import date\n", "import pandas as pd\n", "from gs_quant.instrument import FXOption\n", "from gs_quant.common import BuySell, OptionType\n", "from gs_quant.backtests.triggers import MktTrigger, MktTriggerRequirements, TriggerDirection\n", "from gs_quant.backtests.actions import AddTradeAction\n", "from gs_quant.backtests.generic_engine import GenericEngine\n", "from gs_quant.backtests.strategy import Strategy\n", "from gs_quant.backtests.data_sources import GenericDataSource, MissingDataStrategy\n", "from gs_quant.data import Dataset\n", "from gs_quant.risk import Price" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": true } }, "outputs": [], "source": [ "# Initialize session\n", "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics', 'read_product_data'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Buy 1m USDCNH Put if spot below 6.42" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": true } }, "outputs": [], "source": [ "# Define backtest dates\n", "start_date = date(2021, 8, 1)\n", "end_date = date(2022, 2, 1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": true } }, "outputs": [], "source": [ "# Define instrument for strategy\n", "\n", "# FX Option\n", "put = FXOption(\n", " buy_sell=BuySell.Buy,\n", " option_type=OptionType.Put,\n", " pair='USDCNH',\n", " strike_price='ATMF',\n", " expiration_date='1m',\n", " name='1m_put',\n", " premium=0,\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### Dataset from Marquee Data Catalog: https://marquee.gs.com/s/developer/datasets/FXSPOT_PREMIUM" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "is_executing": true } }, "outputs": [], "source": [ "# Get data set for USDCNH spot\n", "ds = Dataset('FXSPOT_PREMIUM')\n", "data = ds.get_data(start_date, assetId=['MAEFRJZ9NYGDDR41'])\n", "s = pd.Series(data['spot'].to_dict())\n", "data_source = GenericDataSource(s, MissingDataStrategy.fill_forward)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View historical spot data\n", "s.plot(figsize=(10, 6), title='USDCNH Spot')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Define trade to add\n", "action_add = AddTradeAction(put)\n", "\n", "# Define market trigger strategy\n", "mkt_trigger = MktTrigger(MktTriggerRequirements(data_source, 6.42, TriggerDirection.BELOW), action_add)\n", "strategy = Strategy(None, mkt_trigger)\n", "\n", "# Run backtest daily\n", "GE = GenericEngine()\n", "backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View backtest trade ledger\n", "backtest.trade_ledger()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View results summary\n", "backtest.result_summary" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View Performance\n", "pd.DataFrame({'Generic backtester': backtest.result_summary['Cumulative Cash'] + backtest.result_summary[Price]}).plot(\n", " figsize=(10, 6), title='Performance'\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: gs_quant/documentation/04_backtesting/examples/03_GenericEngine/040307_strategy_seagull_bullish.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.common import AggregationLevel\n", "from gs_quant.instrument import IRSwaption\n", "from gs_quant.backtests.triggers import PeriodicTrigger, PeriodicTriggerRequirements\n", "from gs_quant.backtests.actions import AddTradeAction\n", "from gs_quant.backtests.strategy import Strategy\n", "from gs_quant.backtests.generic_engine import GenericEngine\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.markets import HistoricalPricingContext\n", "from gs_quant.risk import IRDelta, Price, IRFwdRate, IRDailyImpliedVol, DollarPrice\n", "from datetime import date\n", "from gs_quant.datetime import business_day_offset" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Initialize session\n", "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Seagull params\n", "start = date(2020, 1, 1)\n", "end = business_day_offset(date.today(), -1, roll='preceding')\n", "\n", "# Add trades at what frequency\n", "freq_int = 1\n", "ramp_up_freq = 'w'\n", "freq = str(freq_int) + ramp_up_freq\n", "ramp_up_mult = 5\n", "ramp_up_period = ramp_up_mult * freq_int - 1\n", "holding_period = str(ramp_up_period) + ramp_up_freq\n", "strategy_notional = 100e6\n", "\n", "print(freq, holding_period)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Buy call spread, sell out-of-the-money put" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Portfolio def\n", "\n", "ccy = 'USD'\n", "expiry = '1y'\n", "tail = '10y'\n", "rate_option = 'USD-LIBOR-BBA'\n", "pay_rec = ['Receiver', 'Receive', 'Pay']\n", "strikes = ['A-50', 'A-25', 'A+50']\n", "direction = [-1, 1, -1]\n", "notional_inception = round(strategy_notional / (ramp_up_period + 1), 0)\n", "\n", "port = []\n", "for i in range(len(pay_rec)):\n", " opt = IRSwaption(\n", " pay_or_receive=pay_rec[i],\n", " termination_date=tail,\n", " notional_currency=ccy,\n", " expiration_date=expiry,\n", " notional_amount=direction[i] * notional_inception,\n", " strike=strikes[i],\n", " floating_rate_option=rate_option,\n", " name='swaption' + str(i),\n", " )\n", " port.append(opt)\n", "\n", "csa_string = ccy + '-1'\n", "\n", "seagull = Portfolio(port, name='Seagull')\n", "seagull.to_frame().transpose()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Define deltas\n", "myParallelDelta = IRDelta(currency='local', aggregation_level=AggregationLevel.Type)\n", "\n", "# Define backtest\n", "trade_action = AddTradeAction(seagull, holding_period)\n", "trigger = PeriodicTrigger(PeriodicTriggerRequirements(start_date=start, end_date=end, frequency=freq), [trade_action])\n", "strategy = Strategy(None, trigger)\n", "\n", "# Select an engine\n", "GE = GenericEngine()\n", "backtest = GE.run_backtest(\n", " strategy,\n", " start=start,\n", " end=end,\n", " frequency=freq,\n", " risks=[Price, myParallelDelta],\n", " show_progress=True,\n", " csa_term=csa_string,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "backtest_summary = backtest.result_summary\n", "backtest_summary" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "backtest_dates_reduced = backtest_summary.index\n", "\n", "with HistoricalPricingContext(dates=backtest_dates_reduced, show_progress=True):\n", " ir_rates = seagull[0].calc([IRFwdRate, IRDailyImpliedVol])\n", " entry = seagull.calc([DollarPrice])\n", "\n", "\n", "premium = entry.result().to_frame()\n", "raw_data = ir_rates.result().to_frame()\n", "raw_data[IRFwdRate] *= 1e4\n", "raw_data['entry_premium'] = premium.iloc[:, 0] + premium.iloc[:, 1] + premium.iloc[:, 2]\n", "raw_data.describe()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "(backtest_summary['Cumulative Cash'] / 1e6).plot(\n", " title='Rolling Seagull vs Delta Proxy', legend=True, label='Strategy', figsize=(20, 10)\n", ")\n", "(backtest_summary.iloc[5, 1] * (raw_data[IRFwdRate] - raw_data[IRFwdRate][0]) / 1e6).plot(\n", " legend=True, label=f'Delta Proxy ({round(backtest_summary.iloc[5, 1] / 1e3, 0)}k per bp)'\n", ")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: gs_quant/documentation/04_backtesting/examples/03_GenericEngine/040308_rebalance_action.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "66f09374-d47a-440a-919a-33eeb87cf4da", "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import OptionStyle\n", "from gs_quant.backtests.strategy import Strategy\n", "from gs_quant.backtests.triggers import *\n", "from gs_quant.backtests.actions import *\n", "from gs_quant.backtests.equity_vol_engine import *\n", "from gs_quant.backtests.generic_engine import GenericEngine\n", "from gs_quant.target.measures import Price\n", "from datetime import date" ] }, { "cell_type": "code", "execution_count": null, "id": "4711538f-454f-43a4-ab94-687d39782579", "metadata": {}, "outputs": [], "source": [ "# Initialize session\n", "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "id": "eb25d59a-8c1d-4e53-a92f-e28937ca34e6", "metadata": {}, "outputs": [], "source": [ "# Define backtest dates\n", "start_date = date(2022, 1, 1)\n", "end_date = date(2022, 2, 1)" ] }, { "cell_type": "code", "execution_count": null, "id": "09e44a2e-fdb5-4781-b65a-aa4b968948f5", "metadata": {}, "outputs": [], "source": [ "# Define instruments for strategy\n", "# Portfolio of two eq options\n", "spx_opt = EqOption(\n", " '.SPX',\n", " expiration_date='2m',\n", " strike_price='ATM',\n", " option_type=OptionType.Call,\n", " option_style=OptionStyle.European,\n", " name='spx',\n", ")\n", "ndx_opt = EqOption(\n", " '.NDX',\n", " expiration_date='2m',\n", " strike_price='ATM',\n", " option_type=OptionType.Call,\n", " option_style=OptionStyle.European,\n", " name='ndx',\n", ")" ] }, { "cell_type": "markdown", "id": "f6068e9e-e104-4816-ab42-609ed3be3adf", "metadata": {}, "source": [ "## RebalanceAction rebalances a trade's quantity according to a custom function" ] }, { "cell_type": "markdown", "id": "12c81ab7-d0ab-462f-be30-fd1f45edf12a", "metadata": {}, "source": [ "#### This function returns the no. of .NDX options held" ] }, { "cell_type": "code", "execution_count": null, "id": "95e58596-1fdd-4e35-b057-49193b180830", "metadata": {}, "outputs": [], "source": [ "def match_ndx_holding(state, backtest, info):\n", " port = backtest.portfolio_dict\n", " current_ndx_notional = sum(\n", " [x.number_of_options for x in port[state].all_instruments if isinstance(x, EqOption) and x.underlier == '.NDX']\n", " )\n", "\n", " return current_ndx_notional" ] }, { "cell_type": "markdown", "id": "9fdebe41-933b-4ee5-a6cf-d0b3cac8ff79", "metadata": {}, "source": [ "#### AddTradeAction adds .NDX options daily, RebalanceAction rebalances the .SPX option according to the total number of .NDX options held weekly" ] }, { "cell_type": "code", "execution_count": null, "id": "4fdd6cdd-7368-4c68-bb25-3c1fcc112e40", "metadata": {}, "outputs": [], "source": [ "# Add NDX options daily\n", "add_trig_req = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b')\n", "add_ndx = AddTradeAction(ndx_opt, '2m', name='Action1')\n", "add_trigger = PeriodicTrigger(add_trig_req, add_ndx)\n", "\n", "# Rebalance SPX option holdings weekly\n", "rebalance_trig_req = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1w')\n", "# Need to resolve an option before rebalancing it. In this case we want to rebalance the option we are starting with\n", "with PricingContext(start_date):\n", " spx_opt.resolve()\n", "rebalance_spx = RebalanceAction(spx_opt, 'number_of_options', match_ndx_holding)\n", "rebalance_trigger = PeriodicTrigger(rebalance_trig_req, rebalance_spx)\n", "\n", "strategy = Strategy(spx_opt, [add_trigger, rebalance_trigger])" ] }, { "cell_type": "markdown", "id": "c03ab015-0f64-4ef0-b2b7-0c69381e8e7d", "metadata": {}, "source": [ "#### Run the strategy" ] }, { "cell_type": "code", "execution_count": null, "id": "1005273b-7d8e-4170-9ba0-42317b0deb5d", "metadata": {}, "outputs": [], "source": [ "# run backtest daily\n", "GE = GenericEngine()\n", "backtest = GE.run_backtest(strategy, start=start_date, risks=[Price], end=end_date, frequency='1b', show_progress=True)" ] }, { "cell_type": "markdown", "id": "892047e1-7813-4b87-afe0-9f192f1f03c2", "metadata": {}, "source": [ "#### See total no. of options by underlier" ] }, { "cell_type": "code", "execution_count": null, "id": "376e40c5-5341-4ebf-b304-86b1b2a7d2ef", "metadata": {}, "outputs": [], "source": [ "ts = backtest.strategy_as_time_series()" ] }, { "cell_type": "code", "execution_count": null, "id": "a3ca6a7e-87b7-44cd-ad97-6ca4b30ffb42", "metadata": {}, "outputs": [], "source": [ "ndx = ts[~ts.index.get_level_values('Instrument Name').str.contains('spx')]\n", "ndx = ndx.groupby('Pricing Date').agg({('Static Instrument Data', 'number_of_options'): ['sum']})" ] }, { "cell_type": "code", "execution_count": null, "id": "de82e4d0-7086-4c8f-a8c2-fef3803ad52e", "metadata": {}, "outputs": [], "source": [ "spx = ts[~ts.index.get_level_values('Instrument Name').str.contains('ndx')]\n", "spx = spx.groupby('Pricing Date').agg({('Static Instrument Data', 'number_of_options'): ['sum']})" ] }, { "cell_type": "code", "execution_count": null, "id": "54e4bd65-3f5b-439e-aad6-6e2c8b397e02", "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "\n", "%matplotlib inline\n", "plt.plot(ndx['Static Instrument Data']['number_of_options']['sum'], label='NDX')\n", "plt.plot(spx['Static Instrument Data']['number_of_options']['sum'], label='SPX')\n", "\n", "plt.legend(prop={'size': 15})" ] }, { "cell_type": "code", "execution_count": null, "id": "5b32a18f-6377-43c2-9868-eedb3a40e8af", "metadata": {}, "outputs": [], "source": [ "# View Mark to Market\n", "pd.DataFrame({'Generic backtester': backtest.result_summary[Price]}).plot(figsize=(10, 6), title='Mark to market')" ] }, { "cell_type": "code", "execution_count": null, "id": "578c4bf6-f080-4e34-ab1d-ac2a6831d5ab", "metadata": {}, "outputs": [], "source": [ "# View Performance\n", "pd.DataFrame({'Generic backtester': backtest.result_summary['Cumulative Cash'] + backtest.result_summary[Price]}).plot(\n", " figsize=(10, 6), title='Performance'\n", ")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.6" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/04_backtesting/examples/03_GenericEngine/040309_inflation_hedging_strategy.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from datetime import datetime, date\n", "import pandas as pd\n", "from gs_quant.instrument import InflationSwap\n", "from gs_quant.backtests.triggers import PeriodicTrigger, PeriodicTriggerRequirements\n", "from gs_quant.backtests.actions import AddTradeAction, HedgeAction\n", "from gs_quant.backtests.generic_engine import GenericEngine\n", "from gs_quant.backtests.strategy import Strategy\n", "from gs_quant.risk import Price, InflationDelta" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Initialize session\n", "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Inflation strategy hedging a 50y inflation swap with a 30y inflation swap." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Define backtest dates\n", "start_date = date(2024, 1, 3)\n", "end_date = datetime.today().date()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Define instruments for strategy\n", "\n", "# 50y inflation swap, 30y inflation swap which we will use to hedge\n", "infl_hedge = InflationSwap(\n", " pay_or_receive='Pay', termination_date='30y', notional_currency='EUR', notional_amount=100e6, name='30yhedge'\n", ")\n", "infl = InflationSwap(\n", " pay_or_receive='Receive', termination_date='50y', notional_currency='EUR', notional_amount=100e6, name='50y'\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Periodic trigger: based on frequency\n", "freq = '1b'\n", "trig_req = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency=freq)\n", "\n", "actions = [AddTradeAction(infl, '1m'), HedgeAction(InflationDelta(aggregation_level='Type'), infl_hedge, freq)]\n", "triggers = PeriodicTrigger(trig_req, actions)\n", "strategy = Strategy(None, triggers)\n", "\n", "# run backtest daily\n", "GE = GenericEngine()\n", "backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View backtest trade ledger\n", "backtest.trade_ledger()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View results summary\n", "backtest.result_summary" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View Mark to Market\n", "pd.DataFrame({'Generic backtester': backtest.result_summary[Price]}).plot(figsize=(10, 6), title='Mark to market')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View Performance\n", "pd.DataFrame({'Generic backtester': backtest.result_summary['Cumulative Cash'] + backtest.result_summary[Price]}).plot(\n", " figsize=(10, 6), title='Performance'\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: gs_quant/documentation/04_backtesting/examples/03_GenericEngine/040310_initial_swap_hedging_strategy_varying_CSA.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from datetime import datetime, date\n", "import pandas as pd\n", "from gs_quant.instrument import IRSwap\n", "from gs_quant.backtests.triggers import PeriodicTrigger, PeriodicTriggerRequirements\n", "from gs_quant.backtests.actions import HedgeAction\n", "from gs_quant.backtests.generic_engine import GenericEngine\n", "from gs_quant.backtests.strategy import Strategy\n", "from gs_quant.risk import Price, IRDelta" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Initialize session\n", "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Inflation strategy hedging a 50y inflation swap with a 30y inflation swap." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Define backtest dates\n", "start_date = date(2024, 1, 3)\n", "end_date = datetime.today().date()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Define instruments for strategy\n", "\n", "# initial holding of 50y swap hedge with daily 30y swaps where the CMS of the main swap and the hedge are different\n", "swap_hedge = IRSwap(\n", " pay_or_receive='Receive',\n", " termination_date='30y',\n", " notional_currency='EUR',\n", " notional_amount=100e6,\n", " fixed_rate='ATM',\n", " name='30yhedge',\n", ")\n", "swap = IRSwap(\n", " pay_or_receive='Pay',\n", " termination_date='50y',\n", " notional_currency='EUR',\n", " notional_amount=100e6,\n", " fixed_rate='ATM',\n", " name='50y',\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Periodic trigger: based on frequency\n", "freq = \"1b\"\n", "\n", "trig_req = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency=freq)\n", "actions_hedge = HedgeAction(IRDelta(aggregation_level='Type'), swap_hedge, csa_term='EUR-OIS')\n", "triggers = PeriodicTrigger(trig_req, actions_hedge)\n", "strategy = Strategy([swap], triggers)\n", "\n", "# run backtest daily\n", "GE = GenericEngine()\n", "backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View backtest trade ledger\n", "backtest.trade_ledger()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View results summary\n", "backtest.result_summary" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View Mark to Market\n", "pd.DataFrame({'Generic backtester': backtest.result_summary[Price]}).plot(figsize=(10, 6), title='Mark to market')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# View Performance\n", "pd.DataFrame({'Generic backtester': backtest.result_summary['Cumulative Cash'] + backtest.result_summary[Price]}).plot(\n", " figsize=(10, 6), title='Performance'\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: gs_quant/documentation/04_backtesting/examples/03_GenericEngine/040311_handling_holidays.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from datetime import date\n", "from gs_quant.instrument import IRSwap\n", "from gs_quant.backtests.triggers import PeriodicTrigger, PeriodicTriggerRequirements\n", "from gs_quant.backtests.actions import AddTradeAction\n", "from gs_quant.backtests.generic_engine import GenericEngine\n", "from gs_quant.backtests.strategy import Strategy" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Initialize session\n", "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Simple backtest with no holiday calendar handling." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Define backtest dates\n", "start_date = date(2024, 5, 3)\n", "end_date = date(2024, 6, 3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Define strategy -- this strategy adds a swap every day holding forever.\n", "\n", "trig_req = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b')\n", "irswap = IRSwap('Pay', '10y', 'USD', notional_amount=10000, name='10y')\n", "actions = AddTradeAction(irswap)\n", "triggers = PeriodicTrigger(trig_req, actions)\n", "strategy = Strategy(None, triggers)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# run backtest daily\n", "GE = GenericEngine()\n", "backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True)\n", "backtest.result_summary\n", "\n", "# Note that May 27th appears in result summary" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# we can see that a trade was opened on 27th May 2024 dispite it being a holiday because we didn't say anything about holidays in our strategy\n", "date(2024, 5, 27) in list(backtest.trade_ledger()['Open'])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# now change the trigger not to trigger on exclude May 27th as it was memorial day\n", "holiday = (date(2024, 5, 27),)\n", "\n", "trig_req_exclude_hol = PeriodicTriggerRequirements(\n", " start_date=start_date, end_date=end_date, frequency='1b', calendar=holiday\n", ")\n", "triggers_exc_hol = PeriodicTrigger(trig_req_exclude_hol, actions)\n", "strategy_exc_hol = Strategy(None, triggers_exc_hol)\n", "\n", "backtest2 = GE.run_backtest(strategy_exc_hol, start=start_date, end=end_date, frequency='1b', show_progress=True)\n", "backtest2.result_summary\n", "\n", "# note May 27th still appears on result summary as valuing trades is done on the schedule created by run_backtest which didn't include this holiday" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# we can see that no trade was opened on 27th May 2024 because the trigger knew it wasn't a valid day\n", "date(2024, 5, 27) in list(backtest2.trade_ledger()['Open'])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# now lets run the strategy above excluding holidays at the run_backtest level\n", "backtest3 = GE.run_backtest(\n", " strategy_exc_hol, start=start_date, end=end_date, frequency='1b', show_progress=True, holiday_calendar=holiday\n", ")\n", "backtest3.result_summary\n", "\n", "# now we can see that May 27th is excluded from the result_summary. No trades were created on this date and no risks run." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Hang on. What if the action specifies an exit date for a trade and that falls on a holiday!?\n", "\n", "actions_roll_daily = AddTradeAction(irswap, '1b')\n", "triggers_roll_daily = PeriodicTrigger(trig_req_exclude_hol, actions_roll_daily)\n", "strategy_roll_daily = Strategy(None, triggers_roll_daily)\n", "\n", "backtest4 = GE.run_backtest(\n", " strategy_roll_daily, start=start_date, end=end_date, frequency='1b', show_progress=True, holiday_calendar=holiday\n", ")\n", "backtest4.result_summary\n", "\n", "# now we see that May 27th is back... That is because the action didn't know that it couldn't exit a trade on that date." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "date(2024, 5, 27) in list(backtest4.trade_ledger()['Open'])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "date(2024, 5, 27) in list(backtest4.trade_ledger()['Close'])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# so now lets tell the action that 27th May is a holiday\n", "\n", "actions_roll_daily_hol = AddTradeAction(irswap, '1b', holiday_calendar=holiday)\n", "triggers_roll_daily_hol = PeriodicTrigger(trig_req_exclude_hol, actions_roll_daily_hol)\n", "strategy_roll_daily_hol = Strategy(None, triggers_roll_daily_hol)\n", "\n", "backtest5 = GE.run_backtest(\n", " strategy_roll_daily_hol,\n", " start=start_date,\n", " end=end_date,\n", " frequency='1b',\n", " show_progress=True,\n", " holiday_calendar=holiday,\n", ")\n", "backtest5.result_summary\n", "\n", "\n", "# success" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/04_backtesting/examples/03_GenericEngine/040312_strategy_scaled_add_action.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "a53fde7e-f24f-4cb0-9962-9ed075790628", "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "\n", "GsSession.use()" ] }, { "cell_type": "code", "execution_count": null, "id": "6a9bc9fb-3914-49fc-b3cf-4e59d22fd8a2", "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import OptionStyle\n", "from gs_quant.backtests.strategy import Strategy\n", "from gs_quant.backtests.triggers import *\n", "from gs_quant.backtests.actions import *\n", "from gs_quant.backtests.equity_vol_engine import *\n", "from gs_quant.backtests.generic_engine import GenericEngine\n", "from gs_quant.risk import Price\n", "from datetime import datetime, date\n", "\n", "import gs_quant.risk as risk" ] }, { "cell_type": "code", "execution_count": null, "id": "2a6175aa-bd47-4cd3-bead-dcebe4f5ea8d", "metadata": {}, "outputs": [], "source": [ "# Initialize session\n", "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "code", "execution_count": null, "id": "5e2c6435-b2a0-4824-ae5c-4d4ef0f42ee4", "metadata": {}, "outputs": [], "source": [ "# Define backtest dates\n", "start_date = date(2024, 4, 1)\n", "end_date = datetime.today().date()" ] }, { "cell_type": "code", "execution_count": null, "id": "62435b59-ec26-4d5b-9b74-fd72396a468d", "metadata": {}, "outputs": [], "source": [ "# Define instruments for strategy\n", "# Portfolio of two eq options\n", "call = EqOption(\n", " '.STOXX50E',\n", " expiration_date='1m',\n", " strike_price='ATM',\n", " option_type=OptionType.Call,\n", " option_style=OptionStyle.European,\n", " name='call',\n", ")\n", "put = EqOption(\n", " '.STOXX50E',\n", " expiration_date='1m',\n", " strike_price='ATM',\n", " option_type=OptionType.Put,\n", " option_style=OptionStyle.European,\n", " name='put',\n", ")\n", "portfolio = Portfolio(name='portfolio', priceables=[call, put])" ] }, { "cell_type": "markdown", "id": "ea1412f0-055e-4954-9d05-527e93018181", "metadata": {}, "source": [ "### Entering a position monthly" ] }, { "cell_type": "code", "execution_count": null, "id": "adb4fc9d-1c23-40a6-b109-3ca8425ad623", "metadata": {}, "outputs": [], "source": [ "# Trade the position monthly without any scaling\n", "trade_action = AddTradeAction(priceables=portfolio, trade_duration='1m', name='act')\n", "trade_trigger = PeriodicTrigger(\n", " trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'),\n", " actions=trade_action,\n", ")\n", "\n", "strategy = Strategy(None, trade_trigger)\n", "\n", "# Run backtest daily\n", "GE = GenericEngine()\n", "backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True)" ] }, { "cell_type": "code", "execution_count": null, "id": "932678aa-cf61-4c71-b057-55a3ab81c241", "metadata": {}, "outputs": [], "source": [ "# View results summary\n", "backtest.result_summary" ] }, { "cell_type": "code", "execution_count": null, "id": "9c126b67-aecf-4493-9ac6-397b984852ed", "metadata": {}, "outputs": [], "source": [ "# View backtest trade ledger\n", "backtest.trade_ledger()" ] }, { "cell_type": "code", "execution_count": null, "id": "3b612c5d-3855-4a23-bff9-cca08fdfd767", "metadata": {}, "outputs": [], "source": [ "# View Mark to Market\n", "pd.DataFrame({'Generic backtester': backtest.result_summary[Price]}).plot(figsize=(10, 6), title='Mark to market')" ] }, { "cell_type": "code", "execution_count": null, "id": "a1d2cf05-0622-4899-af32-be9dc0fad840", "metadata": {}, "outputs": [], "source": [ "# View Performance\n", "pd.DataFrame({'Generic backtester': backtest.result_summary['Cumulative Cash'] + backtest.result_summary[Price]}).plot(\n", " figsize=(10, 6), title='Performance'\n", ")" ] }, { "cell_type": "markdown", "id": "5d3c2217-f45b-4c84-a64f-afdc9e91940f", "metadata": {}, "source": [ "### Scaling quantity" ] }, { "cell_type": "code", "execution_count": null, "id": "7998da4c-0a08-4698-97af-169654f1f11a", "metadata": {}, "outputs": [], "source": [ "# Scale the position quantity (no. of options) by 2\n", "trade_action_scaled = AddScaledTradeAction(\n", " priceables=portfolio,\n", " trade_duration='1m',\n", " scaling_type=ScalingActionType.size,\n", " scaling_risk=None,\n", " scaling_level=2,\n", " name='scaled_act',\n", ")\n", "trade_trigger_scaled = PeriodicTrigger(\n", " trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'),\n", " actions=trade_action_scaled,\n", ")\n", "\n", "strategy = Strategy(None, trade_trigger_scaled)\n", "\n", "# Run backtest daily\n", "GE = GenericEngine()\n", "backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True)" ] }, { "cell_type": "code", "execution_count": null, "id": "14d7e2f3-8f06-4a89-9486-9f5fb35363f1", "metadata": {}, "outputs": [], "source": [ "# View results summary. Note that MtM and cash payments are double\n", "backtest.result_summary" ] }, { "cell_type": "code", "execution_count": null, "id": "f1d811a8-2ca1-467d-8d1a-a8b164fbfd23", "metadata": {}, "outputs": [], "source": [ "# View backtest trade ledger. Note that all values are double\n", "backtest.trade_ledger()" ] }, { "cell_type": "code", "execution_count": null, "id": "9f805bfe-9f5f-402e-a21e-0e08e40a55de", "metadata": {}, "outputs": [], "source": [ "# View Mark to Market\n", "pd.DataFrame({'Generic backtester': backtest.result_summary[Price]}).plot(figsize=(10, 6), title='Mark to market')" ] }, { "cell_type": "code", "execution_count": null, "id": "bd2576e9-de1b-4955-80fa-632c02897e68", "metadata": {}, "outputs": [], "source": [ "# View Performance\n", "pd.DataFrame({'Generic backtester': backtest.result_summary['Cumulative Cash'] + backtest.result_summary[Price]}).plot(\n", " figsize=(10, 6), title='Performance'\n", ")" ] }, { "cell_type": "markdown", "id": "b52d3f47-ab87-4074-ae34-3ef3bb7c54c9", "metadata": {}, "source": [ "### Scaling a risk measure" ] }, { "cell_type": "code", "execution_count": null, "id": "2dff6a31-583b-4858-8c7c-ef259f1a6659", "metadata": {}, "outputs": [], "source": [ "# Initial and final Vega of the portfolio\n", "with PricingContext(start_date):\n", " start_vega = portfolio.calc(risk.EqVega)\n", "\n", "with PricingContext(end_date):\n", " end_vega = portfolio.calc(risk.EqVega)\n", "\n", "print(start_vega.aggregate())\n", "print(end_vega.aggregate())" ] }, { "cell_type": "code", "execution_count": null, "id": "01384407-a6c2-4083-8577-f8a98468a429", "metadata": {}, "outputs": [], "source": [ "# Scale the position vega to 100\n", "trade_action_scaled = AddScaledTradeAction(\n", " priceables=portfolio,\n", " trade_duration='1m',\n", " scaling_type=ScalingActionType.risk_measure,\n", " scaling_risk=risk.EqVega,\n", " scaling_level=100,\n", " name='scaled_vega_act',\n", ")\n", "trade_trigger_scaled = PeriodicTrigger(\n", " trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'),\n", " actions=trade_action_scaled,\n", ")\n", "\n", "strategy = Strategy(None, trade_trigger_scaled)\n", "\n", "# Run backtest daily\n", "GE = GenericEngine()\n", "backtest = GE.run_backtest(\n", " strategy, start=start_date, end=end_date, frequency='1b', show_progress=True, risks=risk.EqVega\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "10ef8a2a-b290-40dc-ad05-e070897652c3", "metadata": {}, "outputs": [], "source": [ "# View results summary. Note that vega on first of month is 100\n", "backtest.result_summary" ] }, { "cell_type": "code", "execution_count": null, "id": "25b76af9-3b23-4b56-9307-b88dc7c3c5ef", "metadata": {}, "outputs": [], "source": [ "# View backtest trade ledger. Note that all values are around 10x larger than without scaling\n", "backtest.trade_ledger()" ] }, { "cell_type": "code", "execution_count": null, "id": "4ccd5bcd-bd52-4fb2-ba29-e2c72c0d3892", "metadata": {}, "outputs": [], "source": [ "# View Mark to Market\n", "pd.DataFrame({'Generic backtester': backtest.result_summary[Price]}).plot(figsize=(10, 6), title='Mark to market')" ] }, { "cell_type": "code", "execution_count": null, "id": "f14aabf9-1964-4002-9a4a-48032438f53d", "metadata": {}, "outputs": [], "source": [ "# View Performance\n", "pd.DataFrame({'Generic backtester': backtest.result_summary['Cumulative Cash'] + backtest.result_summary[Price]}).plot(\n", " figsize=(10, 6), title='Performance'\n", ")" ] }, { "cell_type": "markdown", "id": "b5984b12-10ce-498a-9f78-335bd135aabe", "metadata": {}, "source": [ "### NAV Trading" ] }, { "cell_type": "code", "execution_count": null, "id": "417169e2-addf-4c0b-a355-cff4f35b106a", "metadata": {}, "outputs": [], "source": [ "# Create a NAV-based action that starts with 100 available to spend\n", "start_date = date(2024, 1, 1)\n", "end_date = datetime.today().date()\n", "\n", "trade_action_scaled = EnterPositionQuantityScaledAction(\n", " priceables=portfolio,\n", " trade_duration='1m',\n", " trade_quantity=100,\n", " trade_quantity_type=BacktestTradingQuantityType.NAV,\n", " name='nav_act',\n", ")\n", "trade_trigger_scaled = PeriodicTrigger(\n", " trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'),\n", " actions=trade_action_scaled,\n", ")\n", "\n", "strategy = Strategy(None, trade_trigger_scaled)\n", "\n", "# Run backtest daily\n", "GE = GenericEngine()\n", "backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True)" ] }, { "cell_type": "code", "execution_count": null, "id": "3832acf6-370d-4a77-ab57-219134a9ee6f", "metadata": {}, "outputs": [], "source": [ "# View results summary. The action trades the starting cash and only uses proceeds from selling options after that: note that Cumulative Cash is always the initial trade quantity\n", "backtest.result_summary" ] }, { "cell_type": "code", "execution_count": null, "id": "3050b635-46dc-4725-bebb-e017b69efc47", "metadata": {}, "outputs": [], "source": [ "# View backtest trade ledger\n", "backtest.trade_ledger()" ] }, { "cell_type": "code", "execution_count": null, "id": "aeaf8396-b874-40b3-a4e3-78c9959aeb9a", "metadata": {}, "outputs": [], "source": [ "# View Mark to Market. If zero right before an order, future orders are all zero\n", "pd.DataFrame({'Generic backtester': backtest.result_summary[Price]}).plot(figsize=(10, 6), title='Mark to market')" ] }, { "cell_type": "code", "execution_count": null, "id": "a0db5cf7-1e40-4505-8324-740028aebc30", "metadata": {}, "outputs": [], "source": [ "# View Performance. Maximum loss is 100 if there is no short selling\n", "pd.DataFrame({'Generic backtester': backtest.result_summary['Cumulative Cash'] + backtest.result_summary[Price]}).plot(\n", " figsize=(10, 6), title='Performance'\n", ")" ] }, { "cell_type": "markdown", "id": "600d7d36-eb13-4099-ab3d-7525683836b4", "metadata": {}, "source": [ "### Add Scaled Action with Transaction Cost" ] }, { "cell_type": "code", "execution_count": null, "id": "38f96947-0729-410d-95e8-169b0e6a0fe0", "metadata": {}, "outputs": [], "source": [ "# Scale the position quantity (no. of options) by 2 with a transaction cost of 1 per trade\n", "trade_action_scaled_tc = AddScaledTradeAction(\n", " priceables=portfolio,\n", " trade_duration='1m',\n", " scaling_type=ScalingActionType.size,\n", " scaling_risk=None,\n", " scaling_level=2,\n", " name='scaled_act',\n", " transaction_cost=ConstantTransactionModel(1),\n", ")\n", "trade_trigger_scaled_tc = PeriodicTrigger(\n", " trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'),\n", " actions=trade_action_scaled_tc,\n", ")\n", "\n", "strategy = Strategy(None, trade_trigger_scaled_tc)\n", "\n", "# Run backtest daily\n", "GE = GenericEngine()\n", "backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True)" ] }, { "cell_type": "code", "execution_count": null, "id": "f9fca96e-a17f-41b1-9a40-502036aa6753", "metadata": {}, "outputs": [], "source": [ "# View results summary. we see the transaction costs\n", "backtest.result_summary" ] }, { "cell_type": "code", "execution_count": null, "id": "0b954e01-bd97-43f1-9188-fad34a2c149a", "metadata": {}, "outputs": [], "source": [ "# View backtest trade ledger\n", "backtest.trade_ledger()" ] }, { "cell_type": "code", "execution_count": null, "id": "bcbd056e-8760-48dc-885f-3e1a1413d44f", "metadata": {}, "outputs": [], "source": [ "# View Mark to Market\n", "pd.DataFrame({'Generic backtester': backtest.result_summary[Price]}).plot(figsize=(10, 6), title='Mark to market')" ] }, { "cell_type": "code", "execution_count": null, "id": "c633620b-85e5-4862-9172-1dd39bf5e162", "metadata": {}, "outputs": [], "source": [ "pd.DataFrame(\n", " [backtest.result_summary['Cumulative Cash'] + backtest.result_summary[Price], backtest.result_summary['Total']],\n", " index=(['Without Transaction Cost', 'With Transaction Cost']),\n", ").T.plot()" ] }, { "cell_type": "code", "execution_count": null, "id": "c0c84855-26c1-4b29-9b18-af611b3e0f0c", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/04_backtesting/examples/03_GenericEngine/040313_chained_actions.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "431469a8-b15b-4a5d-8559-f8561ccb75f1", "metadata": {}, "outputs": [], "source": [ "import warnings\n", "\n", "warnings.filterwarnings('ignore')" ] }, { "cell_type": "code", "execution_count": null, "id": "a53fde7e-f24f-4cb0-9962-9ed075790628", "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "\n", "GsSession.use()" ] }, { "cell_type": "code", "execution_count": null, "id": "6a9bc9fb-3914-49fc-b3cf-4e59d22fd8a2", "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import FXOption\n", "from gs_quant.backtests.strategy import Strategy\n", "from gs_quant.backtests.triggers import *\n", "from gs_quant.backtests.actions import *\n", "from gs_quant.backtests.equity_vol_engine import *\n", "from gs_quant.backtests.generic_engine import GenericEngine\n", "from datetime import date" ] }, { "cell_type": "code", "execution_count": null, "id": "2a6175aa-bd47-4cd3-bead-dcebe4f5ea8d", "metadata": {}, "outputs": [], "source": [ "# Initialize session\n", "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "id": "3911f5d4-3a4b-4fca-9404-315dfa03aea9", "metadata": {}, "source": [ "#### If we have a periodic trigger which triggers every 1w and has an add trade action which has a trade duration of 1w sometimes you can end up with a situation where you have 2 trades or 0 trades. \n", "#### This is because the schedule of 1w dates for the trigger will derive a schedule of 1w dates and then adjust each date for holidays. Where as if you take a specific date and add a week this will not take note of the holiday and therefore may give a different date." ] }, { "cell_type": "code", "execution_count": null, "id": "5e2c6435-b2a0-4824-ae5c-4d4ef0f42ee4", "metadata": {}, "outputs": [], "source": [ "# Define backtest dates\n", "start_date = date(2024, 3, 24)\n", "end_date = date(2024, 5, 1)" ] }, { "cell_type": "code", "execution_count": null, "id": "2f30447d-103c-4792-adec-8c7f2516b617", "metadata": {}, "outputs": [], "source": [ "call = FXOption(pair='EURUSD', expiration_date='3m', option_type='Call', name='call')" ] }, { "cell_type": "markdown", "id": "f62f44c9-d432-4212-9313-966c9e7b52ec", "metadata": {}, "source": [ "### Entering a position weekly" ] }, { "cell_type": "code", "execution_count": null, "id": "adb4fc9d-1c23-40a6-b109-3ca8425ad623", "metadata": {}, "outputs": [], "source": [ "# To demonstrate create add trade action with a duration of 1w on a periodic trigger of 1w across a holiday\n", "hol_cal = (dt.date(2024, 3, 29), dt.date(2024, 4, 1))\n", "trade_action = AddTradeAction(priceables=call, trade_duration='1w', name='weekly_duration', holiday_calendar=hol_cal)\n", "trade_trigger = PeriodicTrigger(\n", " trigger_requirements=PeriodicTriggerRequirements(\n", " start_date=start_date, end_date=end_date, frequency='1w', calendar=hol_cal\n", " ),\n", " actions=trade_action,\n", ")\n", "\n", "strategy = Strategy(None, trade_trigger)\n", "\n", "# Run backtest daily\n", "GE = GenericEngine()\n", "backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True)" ] }, { "cell_type": "code", "execution_count": null, "id": "9c126b67-aecf-4493-9ac6-397b984852ed", "metadata": {}, "outputs": [], "source": [ "# View backtest trade ledger\n", "backtest.trade_ledger()" ] }, { "cell_type": "markdown", "id": "2fd8f5f6-7521-4182-a0ec-193f39553860", "metadata": {}, "source": [ "#### note that the second trade closes on the 4th April but the third trade opens on the 5th." ] }, { "cell_type": "markdown", "id": "7248340c-13b8-4c98-b837-1a5e4a12b88f", "metadata": {}, "source": [ "In order to fix this we can set the trade duration to \"next schedule\".\n", "This will have the effect of forcing the close date to line up with the open date of the following trade.\n", "\n", "This should only be used if you want the trade duration of the trade and the frequency of the trigger to line up \n", "and the frequency is not daily. Thereby chaining (or rolling) the positions." ] }, { "cell_type": "code", "execution_count": null, "id": "a1d2cf05-0622-4899-af32-be9dc0fad840", "metadata": {}, "outputs": [], "source": [ "hol_cal = (dt.date(2024, 3, 29), dt.date(2024, 4, 1))\n", "trade_action = AddTradeAction(\n", " priceables=call, trade_duration='next schedule', name='weekly_duration', holiday_calendar=hol_cal\n", ")\n", "trade_trigger = PeriodicTrigger(\n", " trigger_requirements=PeriodicTriggerRequirements(\n", " start_date=start_date, end_date=end_date, frequency='1w', calendar=hol_cal\n", " ),\n", " actions=trade_action,\n", ")\n", "\n", "strategy = Strategy(None, trade_trigger)\n", "\n", "# Run backtest daily\n", "GE = GenericEngine()\n", "backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True)" ] }, { "cell_type": "code", "execution_count": null, "id": "13b2bdc3-c710-4cd3-a020-23e8f1351151", "metadata": {}, "outputs": [], "source": [ "# View backtest trade ledger\n", "backtest.trade_ledger()" ] }, { "cell_type": "markdown", "id": "ca734a86-f37f-497e-bd96-34044a95d70a", "metadata": {}, "source": [ "#### now note that all the trades start and end dates line up" ] }, { "cell_type": "code", "execution_count": null, "id": "294b7063-3aff-4322-a400-070bc0b1ce35", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.6" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/04_backtesting/examples/03_GenericEngine/040314_gradual_entries.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "2fac05c0-0169-4681-87b1-8abc3130be69", "metadata": {}, "outputs": [], "source": [ "import datetime as dt\n", "import pandas as pd\n", "from gs_quant.instrument import EqOption\n", "from gs_quant.backtests.triggers import (\n", " MktTriggerRequirements,\n", " MktTrigger,\n", " TriggerDirection,\n", " AggregateTrigger,\n", " AggregateTriggerRequirements,\n", " AggType,\n", " TradeCountTriggerRequirements,\n", ")\n", "from gs_quant.backtests.actions import AddScaledTradeAction, ScalingActionType, ExitAllPositionsAction\n", "from gs_quant.backtests.generic_engine import GenericEngine\n", "from gs_quant.backtests.strategy import Strategy\n", "from gs_quant.backtests.data_sources import GenericDataSource, MissingDataStrategy\n", "from gs_quant.data import Dataset\n", "from gs_quant.risk import EqVega" ] }, { "cell_type": "code", "execution_count": null, "id": "c12af18a-fd16-46cd-bcaa-7abc1bb79472", "metadata": {}, "outputs": [], "source": [ "# Initialize session\n", "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics'))" ] }, { "cell_type": "markdown", "id": "f7a11125-e78c-483d-9e0d-e776c7b3653e", "metadata": {}, "source": [ "#### This notebook shows to do partial/ gradual buys of instruments based on signal. \n", "\n", "E.g. “backtest buying a VIX 3m put where I buy 100k vega for each day that the SPX is above 6500 upto a max of 1m vega, exit portfolio if SPX below 6500” \n" ] }, { "cell_type": "code", "execution_count": null, "id": "95ba3c63-8214-4ad7-90c6-9ca0fb0c95f0", "metadata": {}, "outputs": [], "source": [ "# Define backtest dates\n", "start_date = dt.date(2024, 9, 1)\n", "end_date = dt.date(2025, 9, 25)" ] }, { "cell_type": "code", "execution_count": null, "id": "5820ab11-e661-4896-b9c0-0c1b92211054", "metadata": {}, "outputs": [], "source": [ "# Get data set for SPX spot\n", "ds = Dataset('TREOD')\n", "data = ds.get_data(start_date, assetId=['MA4B66MW5E27U8P32SB'])\n", "s = pd.Series(data['closePrice'].to_dict())\n", "data_source = GenericDataSource(s, MissingDataStrategy.fill_forward)" ] }, { "cell_type": "code", "execution_count": null, "id": "52abb547-ccff-44d6-becb-c463ac8fadb9", "metadata": {}, "outputs": [], "source": [ "opt = EqOption(underlier='.VIX', expiration_date='3m', option_type='put')" ] }, { "cell_type": "code", "execution_count": null, "id": "6e5b276d-0301-4ad4-8e7e-237c36bd3752", "metadata": {}, "outputs": [], "source": [ "# Define trade to add\n", "trade_action_scaled = AddScaledTradeAction(\n", " priceables=opt,\n", " trade_duration='3m',\n", " scaling_type=ScalingActionType.risk_measure,\n", " scaling_risk=EqVega,\n", " scaling_level=100000,\n", " name='EnterAction',\n", ")\n", "\n", "# Define a trigger which will trigger if the SPX is above 6500 and the number of trades is less than 10\n", "\n", "spx_above_6500 = MktTriggerRequirements(data_source, 6500, TriggerDirection.ABOVE)\n", "trade_count_below_10 = TradeCountTriggerRequirements(10, TriggerDirection.BELOW)\n", "enter_trigger = AggregateTrigger(\n", " AggregateTriggerRequirements([spx_above_6500, trade_count_below_10], AggType.ALL_OF), trade_action_scaled\n", ")\n", "\n", "# Define action to sell all instruments\n", "exit_action = ExitAllPositionsAction(name='ExitAction')\n", "\n", "# Define a trigger which will trigger if the SPX is below 6500\n", "exit_trigger = MktTrigger(MktTriggerRequirements(data_source, 6500, TriggerDirection.BELOW), exit_action)\n", "\n", "strategy = Strategy(None, [enter_trigger, exit_trigger])" ] }, { "cell_type": "code", "execution_count": null, "id": "30cb7401-9f16-489f-bb8c-ba2cd3be53e7", "metadata": {}, "outputs": [], "source": [ "# Run backtest daily\n", "GE = GenericEngine()\n", "backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True)" ] }, { "cell_type": "code", "execution_count": null, "id": "0f226b56-3d7e-4c14-8804-195da95e134b", "metadata": {}, "outputs": [], "source": [ "backtest.trade_ledger()" ] }, { "cell_type": "code", "execution_count": null, "id": "0969d751-ca47-4130-9f99-f706841c35ac", "metadata": {}, "outputs": [], "source": [ "s.plot()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/04_backtesting/tutorials/Backtesting.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "Examples require an initialized GsSession and relevant entitlements. External clients need to substitute thier own client id and client secret below. Please refer to [Sessions](https://developer.gs.com/docs/gsquant/Authentication/gs-session/) for details." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=None, client_secret=None, scopes=('run_analytics',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Backtesting\n", "\n", "The backtesting framework is a language for describing backtests that's (1) intuitive (2) cross-asset and instrument agnostic (3) available natively in python for users to change and extend. It's compatible with a variety of calculation engines - eliminating need for specific syntax knowledge - and uses the gs risk API as the default calculation engine.\n", "\n", "The language consists of several basic components:\n", "* [Trigger](#Trigger)\n", "* [Action](#Action)\n", "* [Strategy](#Strategy)\n", "* [Calculation Engine](#Calculation-Engine)\n", "* [Backtest](#Backtest)\n", "\n", "In this tutorial, we will examine the components in the context of looking at a basic vol selling strategy where we sell a 1m10y USD straddle daily." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Trigger\n", "\n", "A trigger can be used to amend a strategy based on a set of requirements and the current state of the backtest. Each trigger has an associated action or set of actions that are applied when the trigger is active. A backtest can have multiple triggers.\n", "\n", "Below are the triggers we currently offer:\n", "* `PeriodicTrigger`: triggers at a regular interval (time/date). e.g.: every month end\n", "* `MktTrigger`: triggers when market data, absolute or change, hits some target e.g. whenever SPX 1m vol crosses 10. Check out our [data catalog](https://marquee.gs.com/s/discover/data-services/catalog) for sources of market data that can be leveraged here\n", "* `StrategyRiskTrigger`: triggers when the current risk of the strategy hits some target e.g. delta crosses $10m\n", "\n", "Since in our example, we are selling an instrument periodically e.g. (daily), we will use a single `PeriodicTrigger.` Let's define the `PeriodicTriggerRequirements` first and then talk about actions, since we need to specify at least a single action to happen when this trigger is triggered." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.backtests.triggers import PeriodicTrigger, PeriodicTriggerRequirements\n", "from datetime import date\n", "\n", "start_date, end_date = date(2020, 1, 1), date(2020, 12, 1)\n", "# define dates on which actions will be triggered (1b=every business day here)\n", "trig_req = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Action\n", "\n", "Actions define how the portfolio is updated when a trigger is triggered. Below are the actions we currently offer:\n", "* `AddTradeAction`: adds trade to a strategy\n", "* `ExitTradeAction`: removes trade from a strategy\n", "* `HedgeAction`: add trade that's a hedge for a risk\n", "\n", "Since in our example we are selling a straddle, we only want a single `AddTradeAction` to supply to our trigger. Let's define it." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.backtests.actions import AddTradeAction\n", "from gs_quant.common import PayReceive, Currency\n", "from gs_quant.instrument import IRSwaption\n", "\n", "# straddle is the position we'll be adding (note it's a short position since buy_sell='Sell')\n", "straddle = IRSwaption(\n", " PayReceive.Straddle, '10y', Currency.USD, expiration_date='1m', notional_amount=1e8, buy_sell='Sell'\n", ")\n", "\n", "# this action specifies we will add the straddle when this action is triggered and hold it until expiration_date\n", "action = AddTradeAction(straddle, 'expiration_date')\n", "\n", "# we will now combine our trigger requirement and action to produce a PeriodicTrigger\n", "# Note, action can be a list if there are multiple actions\n", "trigger = PeriodicTrigger(trig_req, action)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Strategy\n", "\n", "A `Strategy` combines all the information needed to run a backtest. It has 2 components: an initial trade or portfolio and a trigger or set of triggers. \n", "\n", "In our example, we don't have a starting portfolio and we have a single trigger we defined in the previous step. Let's put these together." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.backtests.strategy import Strategy\n", "\n", "# in this case we have a single trigger but this can be a list of triggers as well\n", "strategy = Strategy(None, trigger)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Calculation engine\n", "\n", "The final piece is calculate engine - the underlying engine/source of calculations you want to run your backtest. \n", "\n", "Once you have defined your backtest in the steps above, you can view which available engines support your strategy. There may be multiple as different engines have been developed and optimized for certain types of backtesting. For example, the `EquityVolEngine` is optimized for fast volatility backtesting for a specific set of equity underlyers. That said, `GenericEngine` will support majority of use cases, running calculations using gs-quant's risk apis. You can check which available engines support your strategy using the function below:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "strategy.get_available_engines()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Alternatively, you check whether a particular engine supports your strategy:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.backtests.generic_engine import GenericEngine\n", "\n", "ge = GenericEngine()\n", "ge.supports_strategy(strategy)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As a final step, let's put everything together to run the backtest. The frequency here indicates the frequency of calculations. You can also supply additional risks you may want to calculate as part of your backtest (greeks or dollar price, for example)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "backtest = ge.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True)\n", "backtest" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Backtest\n", "\n", "* to view the results of `backtest.risks` (by default, `Price`) (all in local ccy) and the cumulative cashflows evaluated on each backtested date: `backtest.results_summary`\n", "* to view status for each trade: `backtest.trade_ledger()`\n", "* to view and further evaluate the portfolio on any given backtested date: `backtest.portfolio_dict`\n", "* to view the number of total api calls and number of calculations performed: `backtest.calc_calls` and `backtest.calculations`\n", "\n", "Here we'll use `backtest.results_summary` to plot mark to market and performance of the strategy" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "backtest.trade_ledger()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "backtest.result_summary" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "from gs_quant.risk import Price\n", "\n", "pd.DataFrame({'Generic backtester': backtest.result_summary[Price]}).plot(figsize=(10, 6), title='Mark to market')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pd.DataFrame({'Generic backtester': backtest.result_summary['Cumulative Cash'] + backtest.result_summary[Price]}).plot(\n", " figsize=(10, 6), title='Performance'\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you have feedback on further expanding this framework, please reach out to `gs-quant-dev@gs.com`" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/04_backtesting/tutorials/Basic backtest walkthrough.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 56, "id": "be4586c8-61a8-4380-910c-0631a7f19ff6", "metadata": {}, "outputs": [], "source": [ "from gs_quant.instrument import FXOption, FXForward\n", "from gs_quant.backtests.triggers import (\n", " PeriodicTrigger,\n", " PeriodicTriggerRequirements,\n", " StrategyRiskTrigger,\n", " RiskTriggerRequirements,\n", " TriggerDirection,\n", ")\n", "from gs_quant.backtests.actions import AddTradeAction, HedgeAction\n", "from gs_quant.backtests.backtest_objects import ScaledTransactionModel\n", "from gs_quant.backtests.generic_engine import GenericEngine\n", "from gs_quant.backtests.strategy import Strategy\n", "from gs_quant.risk import FXDelta\n", "\n", "import datetime as dt" ] }, { "cell_type": "code", "execution_count": 2, "id": "fcc30104-6f26-48cd-837c-da5021936cfe", "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "\n", "GsSession.use()" ] }, { "cell_type": "markdown", "id": "e6626743-dc9f-427c-bd2a-9b4e7ffb0708", "metadata": {}, "source": [ "Simplest backtest. Buy a trade on a periodic basis. Hold that trade for some number of days and exit the trade.\n", "\n", "Here we buy an option every day between 4 Aug and 4 Sep and we hold each of those options for 1 week. We convert this idea into a Strategy object." ] }, { "cell_type": "code", "execution_count": 3, "id": "5663a431-1acf-4c51-8c4f-ac8063163038", "metadata": {}, "outputs": [], "source": [ "opt = FXOption(pair='EURUSD', expiration_date='3m', strike_price='25d', option_type='Call', premium=0)\n", "\n", "action = AddTradeAction(priceables=opt, trade_duration='1w')\n", "trigger = PeriodicTrigger(\n", " trigger_requirements=PeriodicTriggerRequirements(\n", " start_date=dt.date(2025, 8, 4), end_date=dt.date(2025, 9, 4), frequency='1b'\n", " ),\n", " actions=action,\n", ")\n", "\n", "strategy = Strategy(initial_portfolio=None, triggers=trigger)" ] }, { "cell_type": "markdown", "id": "0b286ba1-9a01-410e-b6ed-cefed2c8f530", "metadata": {}, "source": [ "strategy objects can be run by Engines such as the GenericEngine" ] }, { "cell_type": "code", "execution_count": 4, "id": "e37d9bba-31e0-451d-8767-2c8bce8c6e50", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "100%|███████████████████████████████████████████████████████████████████████████████████| 110/110 [00:18<00:00, 5.83it/s]\n", "100%|█████████████████████████████████████████████████████████████████████████████████████| 19/19 [00:06<00:00, 3.09it/s]\n" ] } ], "source": [ "ge = GenericEngine()\n", "backtest = ge.run_backtest(strategy=strategy, start=dt.date(2025, 8, 4), end=dt.date(2025, 9, 4), frequency='1b')" ] }, { "cell_type": "markdown", "id": "2c8d461d-59cb-4768-a7c1-d4c3405628aa", "metadata": {}, "source": [ "when we run a backtest the engine will move through time from the start to the end and for each date determine if a trigger has triggered and if\n", "it has it will apply the action(s) associated with the trigger. We can see that the backtest result object hold a dictionary of every day in the\n", "backtest and the portfolio holding on each date." ] }, { "cell_type": "code", "execution_count": 5, "id": "ecedf61f-3f16-46bf-bb81-3745539dc28c", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "defaultdict(gs_quant.markets.portfolio.Portfolio,\n", " {datetime.date(2025, 8, 4): Portfolio(1 instrument(s)),\n", " datetime.date(2025, 8, 5): Portfolio(2 instrument(s)),\n", " datetime.date(2025, 8, 6): Portfolio(3 instrument(s)),\n", " datetime.date(2025, 8, 7): Portfolio(4 instrument(s)),\n", " datetime.date(2025, 8, 8): Portfolio(5 instrument(s)),\n", " datetime.date(2025, 8, 11): Portfolio(5 instrument(s)),\n", " datetime.date(2025, 8, 12): Portfolio(5 instrument(s)),\n", " datetime.date(2025, 8, 13): Portfolio(5 instrument(s)),\n", " datetime.date(2025, 8, 14): Portfolio(5 instrument(s)),\n", " datetime.date(2025, 8, 15): Portfolio(5 instrument(s)),\n", " datetime.date(2025, 8, 18): Portfolio(5 instrument(s)),\n", " datetime.date(2025, 8, 19): Portfolio(5 instrument(s)),\n", " datetime.date(2025, 8, 20): Portfolio(5 instrument(s)),\n", " datetime.date(2025, 8, 21): Portfolio(5 instrument(s)),\n", " datetime.date(2025, 8, 22): Portfolio(5 instrument(s)),\n", " datetime.date(2025, 8, 25): Portfolio(5 instrument(s)),\n", " datetime.date(2025, 8, 26): Portfolio(5 instrument(s)),\n", " datetime.date(2025, 8, 27): Portfolio(5 instrument(s)),\n", " datetime.date(2025, 8, 28): Portfolio(5 instrument(s)),\n", " datetime.date(2025, 8, 29): Portfolio(5 instrument(s)),\n", " datetime.date(2025, 9, 1): Portfolio(5 instrument(s)),\n", " datetime.date(2025, 9, 2): Portfolio(5 instrument(s)),\n", " datetime.date(2025, 9, 3): Portfolio(5 instrument(s)),\n", " datetime.date(2025, 9, 4): Portfolio(5 instrument(s))})" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "backtest.portfolio_dict" ] }, { "cell_type": "markdown", "id": "b8619115-7cad-4c04-8368-0b345994d1db", "metadata": {}, "source": [ "you can see here that on the first day we have a single option then over the week we add an option but this tops out at 5 options as after this\n", "point the we are exiting the positions.\n", "\n", "the backtest object contains all the positions as above but also all the risks and pvs required for the backtest in the results. It also has\n", "some useful utilities like the result_summary and the trade_ledger" ] }, { "cell_type": "code", "execution_count": 6, "id": "2b4b4237-0685-4762-aa8e-11e29bb80dde", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
PriceCumulative CashTransaction CostsTotal
2025-08-047.039538e+05-7.039538e+0500.000000e+00
2025-08-051.384686e+06-1.401329e+060-1.664271e+04
2025-08-062.438426e+06-2.120211e+0603.182143e+05
2025-08-073.064023e+06-2.849922e+0602.141012e+05
2025-08-083.987985e+06-3.570582e+0604.174028e+05
2025-08-112.955388e+06-3.617959e+060-6.625704e+05
2025-08-123.478294e+06-3.509605e+060-3.131080e+04
2025-08-133.906875e+06-3.460997e+0604.458788e+05
2025-08-143.141030e+06-3.517508e+060-3.764782e+05
2025-08-153.605119e+06-3.542721e+0606.239791e+04
2025-08-182.817288e+06-3.498368e+060-6.810794e+05
2025-08-192.913192e+06-3.585282e+060-6.720894e+05
2025-08-202.956274e+06-3.771344e+060-8.150702e+05
2025-08-212.624439e+06-3.935025e+060-1.310585e+06
2025-08-223.989135e+06-3.956594e+0603.254059e+04
2025-08-253.759709e+06-3.873152e+060-1.134430e+05
2025-08-263.234885e+06-3.920626e+060-6.857406e+05
2025-08-272.738868e+06-4.047281e+060-1.308413e+06
2025-08-283.193984e+06-3.945057e+060-7.510734e+05
2025-08-293.582485e+06-4.036871e+060-4.543858e+05
2025-09-013.694458e+06-4.100455e+060-4.059970e+05
2025-09-023.208602e+06-4.153800e+060-9.451980e+05
2025-09-033.280696e+06-4.037686e+060-7.569907e+05
2025-09-042.838070e+06-4.147867e+060-1.309797e+06
\n", "
" ], "text/plain": [ " Price Cumulative Cash Transaction Costs Total\n", "2025-08-04 7.039538e+05 -7.039538e+05 0 0.000000e+00\n", "2025-08-05 1.384686e+06 -1.401329e+06 0 -1.664271e+04\n", "2025-08-06 2.438426e+06 -2.120211e+06 0 3.182143e+05\n", "2025-08-07 3.064023e+06 -2.849922e+06 0 2.141012e+05\n", "2025-08-08 3.987985e+06 -3.570582e+06 0 4.174028e+05\n", "2025-08-11 2.955388e+06 -3.617959e+06 0 -6.625704e+05\n", "2025-08-12 3.478294e+06 -3.509605e+06 0 -3.131080e+04\n", "2025-08-13 3.906875e+06 -3.460997e+06 0 4.458788e+05\n", "2025-08-14 3.141030e+06 -3.517508e+06 0 -3.764782e+05\n", "2025-08-15 3.605119e+06 -3.542721e+06 0 6.239791e+04\n", "2025-08-18 2.817288e+06 -3.498368e+06 0 -6.810794e+05\n", "2025-08-19 2.913192e+06 -3.585282e+06 0 -6.720894e+05\n", "2025-08-20 2.956274e+06 -3.771344e+06 0 -8.150702e+05\n", "2025-08-21 2.624439e+06 -3.935025e+06 0 -1.310585e+06\n", "2025-08-22 3.989135e+06 -3.956594e+06 0 3.254059e+04\n", "2025-08-25 3.759709e+06 -3.873152e+06 0 -1.134430e+05\n", "2025-08-26 3.234885e+06 -3.920626e+06 0 -6.857406e+05\n", "2025-08-27 2.738868e+06 -4.047281e+06 0 -1.308413e+06\n", "2025-08-28 3.193984e+06 -3.945057e+06 0 -7.510734e+05\n", "2025-08-29 3.582485e+06 -4.036871e+06 0 -4.543858e+05\n", "2025-09-01 3.694458e+06 -4.100455e+06 0 -4.059970e+05\n", "2025-09-02 3.208602e+06 -4.153800e+06 0 -9.451980e+05\n", "2025-09-03 3.280696e+06 -4.037686e+06 0 -7.569907e+05\n", "2025-09-04 2.838070e+06 -4.147867e+06 0 -1.309797e+06" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "backtest.result_summary" ] }, { "cell_type": "markdown", "id": "13f6a66e-eec9-4b4b-b35d-5909dcf7a7f8", "metadata": {}, "source": [ "Now we are going to take this basic example and extend it. I want to extend the strategy I had above to also hedge the delta risk. In order\n", "to do this I am going to add a hedge action. A hedge action required the risk you wish to hedge and the instrument you wish to hedge that\n", "risk with. When the hedge action is triggered the engine will calculate the risk and scale the hedge instrument such that the risk is hedged." ] }, { "cell_type": "code", "execution_count": 7, "id": "3d9cc0e0-9104-441f-88e1-3d588f1e5b0b", "metadata": {}, "outputs": [], "source": [ "hedge_action = HedgeAction(\n", " risk=FXDelta(aggregation_level='Type'),\n", " priceables=FXForward(pair='EURUSD', settlement_date='2b'),\n", " trade_duration='1b',\n", ") # we are holding our hedge for one day. So each day a new fwd is being added as a hedge.\n", "\n", "# Note that we supplied an aggregation level to the risk to ensure the risk comes back as a scalar value. Finer control of risks can be\n", "# achieved by using a risk transformer and supplying this.\n", "# Having created our hedge action we can now add it to the list of actions to be run when our trigger is triggered.\n", "\n", "hedge_trigger = PeriodicTrigger(\n", " trigger_requirements=PeriodicTriggerRequirements(\n", " start_date=dt.date(2025, 8, 4), end_date=dt.date(2025, 9, 4), frequency='1b'\n", " ),\n", " actions=[action, hedge_action],\n", ") # now when the trigger triggers both actions will be done in order.\n", "\n", "hedge_strategy = Strategy(initial_portfolio=None, triggers=hedge_trigger)" ] }, { "cell_type": "code", "execution_count": 8, "id": "859e2ef8-79de-4991-861f-a16bf84bddb6", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "100%|███████████████████████████████████████████████████████████████████████████████████| 268/268 [00:22<00:00, 11.77it/s]\n", "100%|█████████████████████████████████████████████████████████████████████████████████████| 42/42 [00:09<00:00, 4.42it/s]\n" ] } ], "source": [ "hedge_backtest = ge.run_backtest(\n", " strategy=hedge_strategy, start=dt.date(2025, 8, 4), end=dt.date(2025, 9, 4), frequency='1b'\n", ")" ] }, { "cell_type": "markdown", "id": "985cd029-741c-44c1-9bce-6e850401b483", "metadata": {}, "source": [ "if we check the result summary for this backtest we can check that the delta is 0 on all dates." ] }, { "cell_type": "code", "execution_count": 9, "id": "e2bec6c2-72ea-4913-876f-e4f4f86d28de", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
PriceFXDelta(aggregation_level:Type)Cumulative CashTransaction CostsTotal
2025-08-047.039538e+050.000000e+00-7.039538e+0500.000000
2025-08-051.384686e+060.000000e+00-1.408141e+060-23454.304906
2025-08-062.438426e+060.000000e+00-2.382851e+06055574.368854
2025-08-073.064023e+060.000000e+00-3.024605e+06039417.710660
2025-08-083.987985e+06-1.490116e-08-4.028740e+060-40754.340909
2025-08-112.955388e+060.000000e+00-3.261150e+060-305761.320428
2025-08-123.478294e+06-1.490116e-08-3.831561e+060-353266.819324
2025-08-133.906875e+060.000000e+00-4.270443e+060-363567.721780
2025-08-143.141030e+060.000000e+00-3.617677e+060-476646.930316
2025-08-153.605119e+061.490116e-08-4.124625e+060-519505.288948
2025-08-182.817288e+060.000000e+00-3.624033e+060-806744.825889
2025-08-192.913192e+060.000000e+00-3.791480e+060-878288.088792
2025-08-202.956274e+061.490116e-08-3.816132e+060-859858.766961
2025-08-212.624439e+060.000000e+00-3.433863e+060-809423.092240
2025-08-223.989135e+060.000000e+00-4.499546e+060-510410.859932
2025-08-253.759709e+061.490116e-08-4.406246e+060-646536.348574
2025-08-263.234885e+06-1.490116e-08-3.690361e+060-455476.104749
2025-08-272.738868e+060.000000e+00-3.241891e+060-503023.803534
2025-08-283.193984e+060.000000e+00-3.653487e+060-459503.049236
2025-08-293.582485e+060.000000e+00-4.064240e+060-481755.283012
2025-09-013.694458e+060.000000e+00-4.205189e+060-510730.793778
2025-09-023.208602e+06-1.490116e-08-3.698486e+060-489883.853533
2025-09-033.280696e+060.000000e+00-3.764776e+060-484079.900233
2025-09-042.838070e+060.000000e+00-3.413359e+060-575289.380348
\n", "
" ], "text/plain": [ " Price FXDelta(aggregation_level:Type) Cumulative Cash \\\n", "2025-08-04 7.039538e+05 0.000000e+00 -7.039538e+05 \n", "2025-08-05 1.384686e+06 0.000000e+00 -1.408141e+06 \n", "2025-08-06 2.438426e+06 0.000000e+00 -2.382851e+06 \n", "2025-08-07 3.064023e+06 0.000000e+00 -3.024605e+06 \n", "2025-08-08 3.987985e+06 -1.490116e-08 -4.028740e+06 \n", "2025-08-11 2.955388e+06 0.000000e+00 -3.261150e+06 \n", "2025-08-12 3.478294e+06 -1.490116e-08 -3.831561e+06 \n", "2025-08-13 3.906875e+06 0.000000e+00 -4.270443e+06 \n", "2025-08-14 3.141030e+06 0.000000e+00 -3.617677e+06 \n", "2025-08-15 3.605119e+06 1.490116e-08 -4.124625e+06 \n", "2025-08-18 2.817288e+06 0.000000e+00 -3.624033e+06 \n", "2025-08-19 2.913192e+06 0.000000e+00 -3.791480e+06 \n", "2025-08-20 2.956274e+06 1.490116e-08 -3.816132e+06 \n", "2025-08-21 2.624439e+06 0.000000e+00 -3.433863e+06 \n", "2025-08-22 3.989135e+06 0.000000e+00 -4.499546e+06 \n", "2025-08-25 3.759709e+06 1.490116e-08 -4.406246e+06 \n", "2025-08-26 3.234885e+06 -1.490116e-08 -3.690361e+06 \n", "2025-08-27 2.738868e+06 0.000000e+00 -3.241891e+06 \n", "2025-08-28 3.193984e+06 0.000000e+00 -3.653487e+06 \n", "2025-08-29 3.582485e+06 0.000000e+00 -4.064240e+06 \n", "2025-09-01 3.694458e+06 0.000000e+00 -4.205189e+06 \n", "2025-09-02 3.208602e+06 -1.490116e-08 -3.698486e+06 \n", "2025-09-03 3.280696e+06 0.000000e+00 -3.764776e+06 \n", "2025-09-04 2.838070e+06 0.000000e+00 -3.413359e+06 \n", "\n", " Transaction Costs Total \n", "2025-08-04 0 0.000000 \n", "2025-08-05 0 -23454.304906 \n", "2025-08-06 0 55574.368854 \n", "2025-08-07 0 39417.710660 \n", "2025-08-08 0 -40754.340909 \n", "2025-08-11 0 -305761.320428 \n", "2025-08-12 0 -353266.819324 \n", "2025-08-13 0 -363567.721780 \n", "2025-08-14 0 -476646.930316 \n", "2025-08-15 0 -519505.288948 \n", "2025-08-18 0 -806744.825889 \n", "2025-08-19 0 -878288.088792 \n", "2025-08-20 0 -859858.766961 \n", "2025-08-21 0 -809423.092240 \n", "2025-08-22 0 -510410.859932 \n", "2025-08-25 0 -646536.348574 \n", "2025-08-26 0 -455476.104749 \n", "2025-08-27 0 -503023.803534 \n", "2025-08-28 0 -459503.049236 \n", "2025-08-29 0 -481755.283012 \n", "2025-09-01 0 -510730.793778 \n", "2025-09-02 0 -489883.853533 \n", "2025-09-03 0 -484079.900233 \n", "2025-09-04 0 -575289.380348 " ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hedge_backtest.result_summary" ] }, { "cell_type": "markdown", "id": "747fe3e4-55cd-4d69-ac17-ed7f74641e9c", "metadata": {}, "source": [ "now we are going to change this so that instead of buying an option every day we are going to buy every week but still hedge every day. In\n", "order to do this we are going to have two different triggers, one daily and one weekly with different actions." ] }, { "cell_type": "code", "execution_count": 10, "id": "3477dd7e-e8ba-4fe4-8606-a24867073829", "metadata": {}, "outputs": [], "source": [ "weekly_trigger = PeriodicTrigger(\n", " PeriodicTriggerRequirements(start_date=dt.date(2025, 8, 4), end_date=dt.date(2025, 9, 4), frequency='1w'),\n", " actions=action,\n", ")\n", "daily_trigger = PeriodicTrigger(\n", " PeriodicTriggerRequirements(start_date=dt.date(2025, 8, 4), end_date=dt.date(2025, 9, 4), frequency='1b'),\n", " actions=hedge_action,\n", ")\n", "\n", "hedge_weekly_option_strategy = Strategy(initial_portfolio=None, triggers=[weekly_trigger, daily_trigger])" ] }, { "cell_type": "code", "execution_count": 11, "id": "41a2bdf3-d5f7-4918-99db-aa09d9768511", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "100%|█████████████████████████████████████████████████████████████████████████████████████| 96/96 [00:13<00:00, 7.00it/s]\n", "100%|█████████████████████████████████████████████████████████████████████████████████████| 27/27 [00:03<00:00, 7.41it/s]\n" ] } ], "source": [ "hedge_weekly_option_backtest = ge.run_backtest(\n", " strategy=hedge_weekly_option_strategy, start=dt.date(2025, 8, 4), end=dt.date(2025, 9, 4), frequency='1b'\n", ")" ] }, { "cell_type": "markdown", "id": "862b0f31-cad5-444a-89cc-470147508535", "metadata": {}, "source": [ "if we look at the trade ledger for this backtest we can see that options are bought weekly but hedges are still done daily" ] }, { "cell_type": "code", "execution_count": 13, "id": "68924907-74d0-4b8c-88ab-6f2e9b6b8d5e", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
OpenCloseOpen ValueClose ValueLong ShortStatusTrade PnL
Action1_Priceable0_2025-08-042025-08-042025-08-11-703953.846673637865.502946-1closed-66088.343728
Action1_Priceable0_2025-08-112025-08-112025-08-18-685241.75833685891.313071-1closed649.554742
Action1_Priceable0_2025-08-182025-08-182025-08-25-641537.745143747562.463969-1closed106024.718826
Action1_Priceable0_2025-08-252025-08-252025-09-01-664120.360966608390.108042-1closed-55730.252924
Action1_Priceable0_2025-09-012025-09-01None-671974.3047190-1openNone
Action2_Priceable0_2025-08-042025-08-042025-08-050.000042-6811.598662-1closed-6811.59862
Action2_Priceable0_2025-08-052025-08-052025-08-06-0.000076-127463.975776-1closed-127463.975852
Action2_Priceable0_2025-08-062025-08-062025-08-07-0.00006830876.67724-1closed30876.677172
Action2_Priceable0_2025-08-072025-08-072025-08-080.000019-76216.870412-1closed-76216.870393
Action2_Priceable0_2025-08-082025-08-082025-08-110.000098180086.64404-1closed180086.644138
Action2_Priceable0_2025-08-112025-08-112025-08-120.000069-153650.797851-1closed-153650.797782
Action2_Priceable0_2025-08-122025-08-122025-08-13-0.000055-114033.030923-1closed-114033.030979
Action2_Priceable0_2025-08-132025-08-132025-08-140.000085168857.202827-1closed168857.202912
Action2_Priceable0_2025-08-142025-08-142025-08-15-0.000076-114516.597115-1closed-114516.597192
Action2_Priceable0_2025-08-152025-08-152025-08-180.000118108502.142566-1closed108502.142684
Action2_Priceable0_2025-08-182025-08-182025-08-190.000105-17902.333232-1closed-17902.333127
Action2_Priceable0_2025-08-192025-08-192025-08-200.00009735228.088101-1closed35228.088198
Action2_Priceable0_2025-08-202025-08-202025-08-21-0.000089113097.124562-1closed113097.124474
Action2_Priceable0_2025-08-212025-08-212025-08-220.000051-205754.054176-1closed-205754.054125
Action2_Priceable0_2025-08-222025-08-222025-08-250.0000221953.316272-1closed1953.316294
Action2_Priceable0_2025-08-252025-08-252025-08-260.000049133874.524524-1closed133874.524574
Action2_Priceable0_2025-08-262025-08-262025-08-270.000089100261.657657-1closed100261.657746
Action2_Priceable0_2025-08-272025-08-272025-08-28-0.000054-85779.392115-1closed-85779.392169
Action2_Priceable0_2025-08-282025-08-282025-08-29-0.000085-55443.513444-1closed-55443.513529
Action2_Priceable0_2025-08-292025-08-292025-09-01-0.000073-13212.896855-1closed-13212.896928
Action2_Priceable0_2025-09-012025-09-012025-09-02-0.000107101496.323024-1closed101496.322917
Action2_Priceable0_2025-09-022025-09-022025-09-03-0.00001-32662.887524-1closed-32662.887534
Action2_Priceable0_2025-09-032025-09-032025-09-040.00007986025.151973-1closed86025.152052
Action2_Priceable0_2025-09-042025-09-04None-0.0000770-1openNone
\n", "
" ], "text/plain": [ " Open Close Open Value \\\n", "Action1_Priceable0_2025-08-04 2025-08-04 2025-08-11 -703953.846673 \n", "Action1_Priceable0_2025-08-11 2025-08-11 2025-08-18 -685241.75833 \n", "Action1_Priceable0_2025-08-18 2025-08-18 2025-08-25 -641537.745143 \n", "Action1_Priceable0_2025-08-25 2025-08-25 2025-09-01 -664120.360966 \n", "Action1_Priceable0_2025-09-01 2025-09-01 None -671974.304719 \n", "Action2_Priceable0_2025-08-04 2025-08-04 2025-08-05 0.000042 \n", "Action2_Priceable0_2025-08-05 2025-08-05 2025-08-06 -0.000076 \n", "Action2_Priceable0_2025-08-06 2025-08-06 2025-08-07 -0.000068 \n", "Action2_Priceable0_2025-08-07 2025-08-07 2025-08-08 0.000019 \n", "Action2_Priceable0_2025-08-08 2025-08-08 2025-08-11 0.000098 \n", "Action2_Priceable0_2025-08-11 2025-08-11 2025-08-12 0.000069 \n", "Action2_Priceable0_2025-08-12 2025-08-12 2025-08-13 -0.000055 \n", "Action2_Priceable0_2025-08-13 2025-08-13 2025-08-14 0.000085 \n", "Action2_Priceable0_2025-08-14 2025-08-14 2025-08-15 -0.000076 \n", "Action2_Priceable0_2025-08-15 2025-08-15 2025-08-18 0.000118 \n", "Action2_Priceable0_2025-08-18 2025-08-18 2025-08-19 0.000105 \n", "Action2_Priceable0_2025-08-19 2025-08-19 2025-08-20 0.000097 \n", "Action2_Priceable0_2025-08-20 2025-08-20 2025-08-21 -0.000089 \n", "Action2_Priceable0_2025-08-21 2025-08-21 2025-08-22 0.000051 \n", "Action2_Priceable0_2025-08-22 2025-08-22 2025-08-25 0.000022 \n", "Action2_Priceable0_2025-08-25 2025-08-25 2025-08-26 0.000049 \n", "Action2_Priceable0_2025-08-26 2025-08-26 2025-08-27 0.000089 \n", "Action2_Priceable0_2025-08-27 2025-08-27 2025-08-28 -0.000054 \n", "Action2_Priceable0_2025-08-28 2025-08-28 2025-08-29 -0.000085 \n", "Action2_Priceable0_2025-08-29 2025-08-29 2025-09-01 -0.000073 \n", "Action2_Priceable0_2025-09-01 2025-09-01 2025-09-02 -0.000107 \n", "Action2_Priceable0_2025-09-02 2025-09-02 2025-09-03 -0.00001 \n", "Action2_Priceable0_2025-09-03 2025-09-03 2025-09-04 0.000079 \n", "Action2_Priceable0_2025-09-04 2025-09-04 None -0.000077 \n", "\n", " Close Value Long Short Status Trade PnL \n", "Action1_Priceable0_2025-08-04 637865.502946 -1 closed -66088.343728 \n", "Action1_Priceable0_2025-08-11 685891.313071 -1 closed 649.554742 \n", "Action1_Priceable0_2025-08-18 747562.463969 -1 closed 106024.718826 \n", "Action1_Priceable0_2025-08-25 608390.108042 -1 closed -55730.252924 \n", "Action1_Priceable0_2025-09-01 0 -1 open None \n", "Action2_Priceable0_2025-08-04 -6811.598662 -1 closed -6811.59862 \n", "Action2_Priceable0_2025-08-05 -127463.975776 -1 closed -127463.975852 \n", "Action2_Priceable0_2025-08-06 30876.67724 -1 closed 30876.677172 \n", "Action2_Priceable0_2025-08-07 -76216.870412 -1 closed -76216.870393 \n", "Action2_Priceable0_2025-08-08 180086.64404 -1 closed 180086.644138 \n", "Action2_Priceable0_2025-08-11 -153650.797851 -1 closed -153650.797782 \n", "Action2_Priceable0_2025-08-12 -114033.030923 -1 closed -114033.030979 \n", "Action2_Priceable0_2025-08-13 168857.202827 -1 closed 168857.202912 \n", "Action2_Priceable0_2025-08-14 -114516.597115 -1 closed -114516.597192 \n", "Action2_Priceable0_2025-08-15 108502.142566 -1 closed 108502.142684 \n", "Action2_Priceable0_2025-08-18 -17902.333232 -1 closed -17902.333127 \n", "Action2_Priceable0_2025-08-19 35228.088101 -1 closed 35228.088198 \n", "Action2_Priceable0_2025-08-20 113097.124562 -1 closed 113097.124474 \n", "Action2_Priceable0_2025-08-21 -205754.054176 -1 closed -205754.054125 \n", "Action2_Priceable0_2025-08-22 1953.316272 -1 closed 1953.316294 \n", "Action2_Priceable0_2025-08-25 133874.524524 -1 closed 133874.524574 \n", "Action2_Priceable0_2025-08-26 100261.657657 -1 closed 100261.657746 \n", "Action2_Priceable0_2025-08-27 -85779.392115 -1 closed -85779.392169 \n", "Action2_Priceable0_2025-08-28 -55443.513444 -1 closed -55443.513529 \n", "Action2_Priceable0_2025-08-29 -13212.896855 -1 closed -13212.896928 \n", "Action2_Priceable0_2025-09-01 101496.323024 -1 closed 101496.322917 \n", "Action2_Priceable0_2025-09-02 -32662.887524 -1 closed -32662.887534 \n", "Action2_Priceable0_2025-09-03 86025.151973 -1 closed 86025.152052 \n", "Action2_Priceable0_2025-09-04 0 -1 open None " ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hedge_weekly_option_backtest.trade_ledger()" ] }, { "cell_type": "markdown", "id": "2ea54ad1-a7a7-4013-aa29-b21b4f6cd58f", "metadata": {}, "source": [ "it would be useful if the name of the trades was more human readable.\n", "We can control this by naming the trade and the actions. Then the name of the instrument would be the action name_trade name_date the trade was\n", "entered." ] }, { "cell_type": "code", "execution_count": 16, "id": "dd2448dd-e335-42a2-b659-2248e66f0280", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "100%|█████████████████████████████████████████████████████████████████████████████████████| 96/96 [00:17<00:00, 5.57it/s]\n", "100%|█████████████████████████████████████████████████████████████████████████████████████| 27/27 [00:08<00:00, 3.21it/s]\n" ] } ], "source": [ "opt = FXOption(pair='EURUSD', expiration_date='3m', strike_price='25d', premium=0, name='EURUSD call')\n", "add_action = AddTradeAction(priceables=opt, trade_duration='1w', name='add action')\n", "fwd = FXForward(pair='EURUSD', settlement_date='2b', name='spot hedge')\n", "named_hedge_action = HedgeAction(\n", " risk=FXDelta(aggregation_level='Type'), priceables=fwd, trade_duration='1b', name='hedge action'\n", ")\n", "\n", "weekly_trigger = PeriodicTrigger(\n", " PeriodicTriggerRequirements(start_date=dt.date(2025, 8, 4), end_date=dt.date(2025, 9, 4), frequency='1w'),\n", " actions=add_action,\n", ")\n", "daily_trigger = PeriodicTrigger(\n", " PeriodicTriggerRequirements(start_date=dt.date(2025, 8, 4), end_date=dt.date(2025, 9, 4), frequency='1b'),\n", " actions=named_hedge_action,\n", ")\n", "\n", "hedge_weekly_option_strategy = Strategy(initial_portfolio=None, triggers=[weekly_trigger, daily_trigger])\n", "hedge_weekly_option_backtest = ge.run_backtest(\n", " strategy=hedge_weekly_option_strategy, start=dt.date(2025, 8, 4), end=dt.date(2025, 9, 4), frequency='1b'\n", ")" ] }, { "cell_type": "code", "execution_count": 17, "id": "2691a8a3-c784-4d3d-be4c-42c1be7a40d9", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
OpenCloseOpen ValueClose ValueLong ShortStatusTrade PnL
add action_EURUSD call_2025-08-042025-08-042025-08-11-703953.846673637865.502946-1closed-66088.343728
add action_EURUSD call_2025-08-112025-08-112025-08-18-685241.75833685891.313071-1closed649.554742
add action_EURUSD call_2025-08-182025-08-182025-08-25-641537.745143747562.463969-1closed106024.718826
add action_EURUSD call_2025-08-252025-08-252025-09-01-664120.360966608390.108042-1closed-55730.252924
add action_EURUSD call_2025-09-012025-09-01None-671974.3047190-1openNone
hedge action_spot hedge_2025-08-042025-08-042025-08-050.000042-6811.598662-1closed-6811.59862
hedge action_spot hedge_2025-08-052025-08-052025-08-06-0.000076-127463.975776-1closed-127463.975852
hedge action_spot hedge_2025-08-062025-08-062025-08-07-0.00006830876.67724-1closed30876.677172
hedge action_spot hedge_2025-08-072025-08-072025-08-080.000019-76216.870412-1closed-76216.870393
hedge action_spot hedge_2025-08-082025-08-082025-08-110.000098180086.64404-1closed180086.644138
hedge action_spot hedge_2025-08-112025-08-112025-08-120.000069-153650.797851-1closed-153650.797782
hedge action_spot hedge_2025-08-122025-08-122025-08-13-0.000055-114033.030923-1closed-114033.030978
hedge action_spot hedge_2025-08-132025-08-132025-08-140.000085168857.202827-1closed168857.202912
hedge action_spot hedge_2025-08-142025-08-142025-08-15-0.000076-114516.597122-1closed-114516.597199
hedge action_spot hedge_2025-08-152025-08-152025-08-180.000118108502.142565-1closed108502.142683
hedge action_spot hedge_2025-08-182025-08-182025-08-190.000104-17902.333232-1closed-17902.333128
hedge action_spot hedge_2025-08-192025-08-192025-08-200.00009735228.088102-1closed35228.088199
hedge action_spot hedge_2025-08-202025-08-202025-08-21-0.000088113097.124563-1closed113097.124475
hedge action_spot hedge_2025-08-212025-08-212025-08-220.000054-205754.054176-1closed-205754.054122
hedge action_spot hedge_2025-08-222025-08-222025-08-250.0000221953.316272-1closed1953.316294
hedge action_spot hedge_2025-08-252025-08-252025-08-260.000049133874.524524-1closed133874.524573
hedge action_spot hedge_2025-08-262025-08-262025-08-270.000088100261.657657-1closed100261.657745
hedge action_spot hedge_2025-08-272025-08-272025-08-28-0.000054-85779.392115-1closed-85779.392169
hedge action_spot hedge_2025-08-282025-08-282025-08-29-0.000085-55443.513444-1closed-55443.513529
hedge action_spot hedge_2025-08-292025-08-292025-09-01-0.000073-13212.896855-1closed-13212.896928
hedge action_spot hedge_2025-09-012025-09-012025-09-020.000107101496.32281-1closed101496.322917
hedge action_spot hedge_2025-09-022025-09-022025-09-03-0.00001-32662.887524-1closed-32662.887534
hedge action_spot hedge_2025-09-032025-09-032025-09-040.00007986025.151973-1closed86025.152052
hedge action_spot hedge_2025-09-042025-09-04None-0.0000770-1openNone
\n", "
" ], "text/plain": [ " Open Close Open Value \\\n", "add action_EURUSD call_2025-08-04 2025-08-04 2025-08-11 -703953.846673 \n", "add action_EURUSD call_2025-08-11 2025-08-11 2025-08-18 -685241.75833 \n", "add action_EURUSD call_2025-08-18 2025-08-18 2025-08-25 -641537.745143 \n", "add action_EURUSD call_2025-08-25 2025-08-25 2025-09-01 -664120.360966 \n", "add action_EURUSD call_2025-09-01 2025-09-01 None -671974.304719 \n", "hedge action_spot hedge_2025-08-04 2025-08-04 2025-08-05 0.000042 \n", "hedge action_spot hedge_2025-08-05 2025-08-05 2025-08-06 -0.000076 \n", "hedge action_spot hedge_2025-08-06 2025-08-06 2025-08-07 -0.000068 \n", "hedge action_spot hedge_2025-08-07 2025-08-07 2025-08-08 0.000019 \n", "hedge action_spot hedge_2025-08-08 2025-08-08 2025-08-11 0.000098 \n", "hedge action_spot hedge_2025-08-11 2025-08-11 2025-08-12 0.000069 \n", "hedge action_spot hedge_2025-08-12 2025-08-12 2025-08-13 -0.000055 \n", "hedge action_spot hedge_2025-08-13 2025-08-13 2025-08-14 0.000085 \n", "hedge action_spot hedge_2025-08-14 2025-08-14 2025-08-15 -0.000076 \n", "hedge action_spot hedge_2025-08-15 2025-08-15 2025-08-18 0.000118 \n", "hedge action_spot hedge_2025-08-18 2025-08-18 2025-08-19 0.000104 \n", "hedge action_spot hedge_2025-08-19 2025-08-19 2025-08-20 0.000097 \n", "hedge action_spot hedge_2025-08-20 2025-08-20 2025-08-21 -0.000088 \n", "hedge action_spot hedge_2025-08-21 2025-08-21 2025-08-22 0.000054 \n", "hedge action_spot hedge_2025-08-22 2025-08-22 2025-08-25 0.000022 \n", "hedge action_spot hedge_2025-08-25 2025-08-25 2025-08-26 0.000049 \n", "hedge action_spot hedge_2025-08-26 2025-08-26 2025-08-27 0.000088 \n", "hedge action_spot hedge_2025-08-27 2025-08-27 2025-08-28 -0.000054 \n", "hedge action_spot hedge_2025-08-28 2025-08-28 2025-08-29 -0.000085 \n", "hedge action_spot hedge_2025-08-29 2025-08-29 2025-09-01 -0.000073 \n", "hedge action_spot hedge_2025-09-01 2025-09-01 2025-09-02 0.000107 \n", "hedge action_spot hedge_2025-09-02 2025-09-02 2025-09-03 -0.00001 \n", "hedge action_spot hedge_2025-09-03 2025-09-03 2025-09-04 0.000079 \n", "hedge action_spot hedge_2025-09-04 2025-09-04 None -0.000077 \n", "\n", " Close Value Long Short Status \\\n", "add action_EURUSD call_2025-08-04 637865.502946 -1 closed \n", "add action_EURUSD call_2025-08-11 685891.313071 -1 closed \n", "add action_EURUSD call_2025-08-18 747562.463969 -1 closed \n", "add action_EURUSD call_2025-08-25 608390.108042 -1 closed \n", "add action_EURUSD call_2025-09-01 0 -1 open \n", "hedge action_spot hedge_2025-08-04 -6811.598662 -1 closed \n", "hedge action_spot hedge_2025-08-05 -127463.975776 -1 closed \n", "hedge action_spot hedge_2025-08-06 30876.67724 -1 closed \n", "hedge action_spot hedge_2025-08-07 -76216.870412 -1 closed \n", "hedge action_spot hedge_2025-08-08 180086.64404 -1 closed \n", "hedge action_spot hedge_2025-08-11 -153650.797851 -1 closed \n", "hedge action_spot hedge_2025-08-12 -114033.030923 -1 closed \n", "hedge action_spot hedge_2025-08-13 168857.202827 -1 closed \n", "hedge action_spot hedge_2025-08-14 -114516.597122 -1 closed \n", "hedge action_spot hedge_2025-08-15 108502.142565 -1 closed \n", "hedge action_spot hedge_2025-08-18 -17902.333232 -1 closed \n", "hedge action_spot hedge_2025-08-19 35228.088102 -1 closed \n", "hedge action_spot hedge_2025-08-20 113097.124563 -1 closed \n", "hedge action_spot hedge_2025-08-21 -205754.054176 -1 closed \n", "hedge action_spot hedge_2025-08-22 1953.316272 -1 closed \n", "hedge action_spot hedge_2025-08-25 133874.524524 -1 closed \n", "hedge action_spot hedge_2025-08-26 100261.657657 -1 closed \n", "hedge action_spot hedge_2025-08-27 -85779.392115 -1 closed \n", "hedge action_spot hedge_2025-08-28 -55443.513444 -1 closed \n", "hedge action_spot hedge_2025-08-29 -13212.896855 -1 closed \n", "hedge action_spot hedge_2025-09-01 101496.32281 -1 closed \n", "hedge action_spot hedge_2025-09-02 -32662.887524 -1 closed \n", "hedge action_spot hedge_2025-09-03 86025.151973 -1 closed \n", "hedge action_spot hedge_2025-09-04 0 -1 open \n", "\n", " Trade PnL \n", "add action_EURUSD call_2025-08-04 -66088.343728 \n", "add action_EURUSD call_2025-08-11 649.554742 \n", "add action_EURUSD call_2025-08-18 106024.718826 \n", "add action_EURUSD call_2025-08-25 -55730.252924 \n", "add action_EURUSD call_2025-09-01 None \n", "hedge action_spot hedge_2025-08-04 -6811.59862 \n", "hedge action_spot hedge_2025-08-05 -127463.975852 \n", "hedge action_spot hedge_2025-08-06 30876.677172 \n", "hedge action_spot hedge_2025-08-07 -76216.870393 \n", "hedge action_spot hedge_2025-08-08 180086.644138 \n", "hedge action_spot hedge_2025-08-11 -153650.797782 \n", "hedge action_spot hedge_2025-08-12 -114033.030978 \n", "hedge action_spot hedge_2025-08-13 168857.202912 \n", "hedge action_spot hedge_2025-08-14 -114516.597199 \n", "hedge action_spot hedge_2025-08-15 108502.142683 \n", "hedge action_spot hedge_2025-08-18 -17902.333128 \n", "hedge action_spot hedge_2025-08-19 35228.088199 \n", "hedge action_spot hedge_2025-08-20 113097.124475 \n", "hedge action_spot hedge_2025-08-21 -205754.054122 \n", "hedge action_spot hedge_2025-08-22 1953.316294 \n", "hedge action_spot hedge_2025-08-25 133874.524573 \n", "hedge action_spot hedge_2025-08-26 100261.657745 \n", "hedge action_spot hedge_2025-08-27 -85779.392169 \n", "hedge action_spot hedge_2025-08-28 -55443.513529 \n", "hedge action_spot hedge_2025-08-29 -13212.896928 \n", "hedge action_spot hedge_2025-09-01 101496.322917 \n", "hedge action_spot hedge_2025-09-02 -32662.887534 \n", "hedge action_spot hedge_2025-09-03 86025.152052 \n", "hedge action_spot hedge_2025-09-04 None " ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hedge_weekly_option_backtest.trade_ledger()" ] }, { "cell_type": "markdown", "id": "98566c1f-97aa-4034-8288-ac36ac9ce788", "metadata": {}, "source": [ "that's great but in reality we are trading all these instrument at mid which isn't very likely. We should add some transaction costs\n", "into the backtest to get something which mirrors reality better" ] }, { "cell_type": "code", "execution_count": 27, "id": "058fca5d-8068-48d7-ada6-b819e96bd64c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "100%|█████████████████████████████████████████████████████████████████████████████████████| 96/96 [00:18<00:00, 5.26it/s]\n", "100%|█████████████████████████████████████████████████████████████████████████████████████| 27/27 [00:13<00:00, 1.95it/s]\n" ] } ], "source": [ "t_cost = ScaledTransactionModel(\n", " 'notional_amount', 0.00005\n", ") # We are going to add a 0.5bp transaction cost. We could have charge scaled by risk as well\n", "add_action_tcost = AddTradeAction(priceables=opt, trade_duration='1w', name='add action', transaction_cost=t_cost)\n", "named_hedge_action_tcost = HedgeAction(\n", " risk=FXDelta(aggregation_level='Type'),\n", " priceables=fwd,\n", " trade_duration='1b',\n", " name='hedge action',\n", " transaction_cost=t_cost,\n", ")\n", "\n", "weekly_trigger_tcost = PeriodicTrigger(\n", " PeriodicTriggerRequirements(start_date=dt.date(2025, 8, 4), end_date=dt.date(2025, 9, 4), frequency='1w'),\n", " actions=add_action_tcost,\n", ")\n", "daily_trigger_tcost = PeriodicTrigger(\n", " PeriodicTriggerRequirements(start_date=dt.date(2025, 8, 4), end_date=dt.date(2025, 9, 4), frequency='1b'),\n", " actions=named_hedge_action_tcost,\n", ")\n", "\n", "hedge_weekly_option_strategy_tcost = Strategy(\n", " initial_portfolio=None, triggers=[weekly_trigger_tcost, daily_trigger_tcost]\n", ")\n", "hedge_weekly_option_backtest_tcost = ge.run_backtest(\n", " strategy=hedge_weekly_option_strategy_tcost, start=dt.date(2025, 8, 4), end=dt.date(2025, 9, 4), frequency='1b'\n", ")" ] }, { "cell_type": "code", "execution_count": 28, "id": "1c5dca41-84cf-4f4e-849c-c89846c1f85a", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
PriceFXDelta(aggregation_level:Type)Cumulative CashTransaction CostsTotal
2025-08-04703953.8466310.000000e+00-7.039538e+05-6051.326333-6051.326333
2025-08-05687311.1404640.000000e+00-7.107654e+05-8152.522301-31606.827207
2025-08-06854096.726246-3.725290e-09-8.382294e+05-10459.8432805407.461751
2025-08-07816927.5581110.000000e+00-8.073527e+05-12918.673655-3343.859500
2025-08-08872578.0985123.725290e-09-8.835696e+05-15413.046675-26404.562434
2025-08-11685241.7582610.000000e+00-7.508592e+05-27756.869337-93374.336621
2025-08-12828486.3446213.725290e-09-9.045100e+05-30066.308412-106089.987243
2025-08-13941680.8965670.000000e+00-1.018543e+06-32748.850904-109611.008628
2025-08-14749260.4694480.000000e+00-8.496859e+05-35363.040760-135788.422853
2025-08-15856628.3845190.000000e+00-9.642024e+05-37917.845073-145491.909092
2025-08-18641537.7450390.000000e+00-8.113467e+05-50344.674601-220153.667502
2025-08-19644700.8710140.000000e+00-8.292491e+05-52490.475935-237038.675995
2025-08-20613688.2059250.000000e+00-7.940210e+05-54613.777845-234946.554981
2025-08-21511073.2686070.000000e+00-6.809239e+05-56539.135316-226389.725156
2025-08-22776543.772222-3.725290e-09-8.866779e+05-58690.329946-168824.470327
2025-08-25664120.3609160.000000e+00-8.012825e+05-71027.812708-208189.945069
2025-08-26564999.5365620.000000e+00-6.674080e+05-73018.588822-175427.020925
2025-08-27455386.8550340.000000e+00-5.671463e+05-74700.472260-186459.928287
2025-08-28548765.1590180.000000e+00-6.529257e+05-76370.066582-180530.610827
2025-08-29600466.9539450.000000e+00-7.083692e+05-78259.895976-186162.158810
2025-09-01671974.3046120.000000e+00-7.851663e+05-90314.567163-203506.572755
2025-09-02573496.5792880.000000e+00-6.836700e+05-92322.836654-202496.244769
2025-09-03607131.2232360.000000e+00-7.163329e+05-94248.276676-203449.928287
2025-09-04503559.3305160.000000e+00-6.303077e+05-96084.215916-222832.608351
\n", "
" ], "text/plain": [ " Price FXDelta(aggregation_level:Type) Cumulative Cash \\\n", "2025-08-04 703953.846631 0.000000e+00 -7.039538e+05 \n", "2025-08-05 687311.140464 0.000000e+00 -7.107654e+05 \n", "2025-08-06 854096.726246 -3.725290e-09 -8.382294e+05 \n", "2025-08-07 816927.558111 0.000000e+00 -8.073527e+05 \n", "2025-08-08 872578.098512 3.725290e-09 -8.835696e+05 \n", "2025-08-11 685241.758261 0.000000e+00 -7.508592e+05 \n", "2025-08-12 828486.344621 3.725290e-09 -9.045100e+05 \n", "2025-08-13 941680.896567 0.000000e+00 -1.018543e+06 \n", "2025-08-14 749260.469448 0.000000e+00 -8.496859e+05 \n", "2025-08-15 856628.384519 0.000000e+00 -9.642024e+05 \n", "2025-08-18 641537.745039 0.000000e+00 -8.113467e+05 \n", "2025-08-19 644700.871014 0.000000e+00 -8.292491e+05 \n", "2025-08-20 613688.205925 0.000000e+00 -7.940210e+05 \n", "2025-08-21 511073.268607 0.000000e+00 -6.809239e+05 \n", "2025-08-22 776543.772222 -3.725290e-09 -8.866779e+05 \n", "2025-08-25 664120.360916 0.000000e+00 -8.012825e+05 \n", "2025-08-26 564999.536562 0.000000e+00 -6.674080e+05 \n", "2025-08-27 455386.855034 0.000000e+00 -5.671463e+05 \n", "2025-08-28 548765.159018 0.000000e+00 -6.529257e+05 \n", "2025-08-29 600466.953945 0.000000e+00 -7.083692e+05 \n", "2025-09-01 671974.304612 0.000000e+00 -7.851663e+05 \n", "2025-09-02 573496.579288 0.000000e+00 -6.836700e+05 \n", "2025-09-03 607131.223236 0.000000e+00 -7.163329e+05 \n", "2025-09-04 503559.330516 0.000000e+00 -6.303077e+05 \n", "\n", " Transaction Costs Total \n", "2025-08-04 -6051.326333 -6051.326333 \n", "2025-08-05 -8152.522301 -31606.827207 \n", "2025-08-06 -10459.843280 5407.461751 \n", "2025-08-07 -12918.673655 -3343.859500 \n", "2025-08-08 -15413.046675 -26404.562434 \n", "2025-08-11 -27756.869337 -93374.336621 \n", "2025-08-12 -30066.308412 -106089.987243 \n", "2025-08-13 -32748.850904 -109611.008628 \n", "2025-08-14 -35363.040760 -135788.422853 \n", "2025-08-15 -37917.845073 -145491.909092 \n", "2025-08-18 -50344.674601 -220153.667502 \n", "2025-08-19 -52490.475935 -237038.675995 \n", "2025-08-20 -54613.777845 -234946.554981 \n", "2025-08-21 -56539.135316 -226389.725156 \n", "2025-08-22 -58690.329946 -168824.470327 \n", "2025-08-25 -71027.812708 -208189.945069 \n", "2025-08-26 -73018.588822 -175427.020925 \n", "2025-08-27 -74700.472260 -186459.928287 \n", "2025-08-28 -76370.066582 -180530.610827 \n", "2025-08-29 -78259.895976 -186162.158810 \n", "2025-09-01 -90314.567163 -203506.572755 \n", "2025-09-02 -92322.836654 -202496.244769 \n", "2025-09-03 -94248.276676 -203449.928287 \n", "2025-09-04 -96084.215916 -222832.608351 " ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hedge_weekly_option_backtest_tcost.result_summary" ] }, { "cell_type": "markdown", "id": "28b1eed4-a466-4ca8-9976-3c17427ab54e", "metadata": {}, "source": [ "lets plot the pnl of the strategy with and without the transaction costs." ] }, { "cell_type": "code", "execution_count": 30, "id": "f9652b0c-6a39-4cf3-ab6d-99536594c7a6", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAm0AAAGdCAYAAABAcUQ6AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAAB94klEQVR4nO3dd3gU1dfA8e/uJpveK4EUei9KCaF3BFRQsIuI2AAbqKC+9gaCP3svIGLHLmKhIxBa6CWh1xBCgFTS975/TLIkkEAStibn8zz7ZJK5M3Mu2w53btEppRRCCCGEEMKh6e0dgBBCCCGEuDRJ2oQQQgghnIAkbUIIIYQQTkCSNiGEEEIIJyBJmxBCCCGEE5CkTQghhBDCCUjSJoQQQgjhBCRpE0IIIYRwAi72DqA2MZlMJCcn4+Pjg06ns3c4QgghhKgCpRRZWVlERESg1ztue5YkbRaUnJxMZGSkvcMQQgghRA0cOXKEBg0a2DuMSknSZkE+Pj6A9qT7+vraORohhBBCVEVmZiaRkZHm73FHJUmbBZXeEvX19ZWkTQghhHAyjt61yXFv3AohhBBCCDNJ2oQQQgghnIAkbUIIIYQQTkCSNiGEEEIIJyBJmxBCCCGEE5CkTQghhBDCCUjSJoQQQgjhBCRpE0IIIYRwApK0CSGEEEI4AUnahBBCCCGcgCRtQgghhBBOQJI2IYQQQggnIEmbqFhRPsS/D0c32DsSIYQQQgAu9g5AOKj492HxC9p22xthwPPgV9+uIQkhhBB1mbS0iYrt+ffc9rYf4L1OsOw1KDhrv5iEEEKIOkySNnGhvAw4sk7bvmEORHaFwrOw7FV4vwts/wmUsm+MQgghRB0jSZu40IEVoIohsDG0HgF3/Q0jPwffBpBxBH68C2YPgeRN9o5UCCGEqDMkaRMX2rtY+9lkgPZTp4O2o+CB9dDnKXD1hMPx8Elf+HUiZJ2wX6xCCCFEHSFJmyhPqTJJW//y+4ye0GcqPLBBG5yAgs1fwbtXwso3tRGnQgghhLAKSdpEeaf2QsZhMBghpkfFZfzqw8hPYdwiqN8RCrJh0fNaf7ddf0h/NyGEEMIKJGkT5ZW2skXFgdHr4mUjO2uJ23Ufg089OHMQvr8d5lwDKdutHqoQQghRl0jSJsrbV8mt0cro9dD+Zu2Waa/HwcUdDv4Hn/WH1ETrxSmEEELUMZK0iXMK8+DAf9p24yombaXcvKHf09pghag4KMqDxS9aPkYhhBCijpKkTZxzOB6KcsE7HMJa1+wc/lFwzdug00PSn+fmexNCCCHEZZGkTZxT9taoTlfz84Q0hw63aduLnpeBCUIIIYQFSNImzikdhNC43+Wfq88TYHCDQ6vOnVcIIYQQNSZJm9BkJkPqTkAHjftxKjufZ37dzpr9p2p2Pr8G0OUebXvR82AyWSpSIYQQok6SpE1o9i3Rfta/EjwDmb3qIHPXHOLWT9fw2X/7UTW5xdnzUXDzhRPbYMfPlo1XCCGEqGMkaRMa861RbdTo4sRUAEwKXv5zF498v5ncguLqndMzELo/pG0veQmKCiwVrRBCCFHnSNImwFR8rqWtSX+S03PZdTwTnQ4eG9QMF72O3zYnM/LD1Rw5fbZ6544dD16h2sS7m760eOhCCCFEXSFJ23nef/99YmJicHd3JzY2lnXr6sCUFcmbIC8d3PygfieWlLSyXRkVwAP9mvL13bEEeRnZeTyTa95byco9aVU/t5s39J6ibS+fAQU5lo9fCCGEqAMkaSvj+++/Z/LkyTz33HNs3LiR9u3bM3jwYFJTU+0dmnWV3hpt1BsMLiwtSdr6tQgFILZREH882IN2DfxIP1vIHbPW8umKavRzu3IMBMRA9glY86EVKiCEEELUfpK0lfHGG29wzz33MHbsWFq1asVHH32Ep6cns2bNsndo1rV3kfazSX9yC4pZuVdrSevfMtRcJMLfgx/ui2NUxwaYFLyyYBcPf1fFfm4uRuj7tLa96m04e9rSNRBCCCFqPUnaShQUFJCQkMCAAQPMf9Pr9QwYMID4+Hg7RgacPgDzJ8OJHZY/d+4ZOLZB227cn/j9aeQXmYjwc6d5mE+5ou6uBmaOaseLw1vjotfx+5Zkrq9qP7c2IyGsDeRnwso3LF8PIYQQopaTpK1EWloaxcXFhIWFlft7WFgYKSkpFR6Tn59PZmZmuYdVLH4BNnyutVJZ2v7loEwQ3Bz8I1m8q+TWaMtQdBWsiqDT6bgjLoav744l2NvIrqr2c9Prof9z2vbaTyDjmKVrIoQQQtRqkrRdhmnTpuHn52d+REZGWudC3R/Wfm77Ec4csuy5yyxdpZQy92fr3yLsIged6+fWvkw/t09W7Lt4P7emAyG6OxTnw/LplqqBEEIIUSdI0lYiODgYg8HAiRMnyv39xIkThIeHV3jMk08+SUZGhvlx5MgR6wQXcQU06guqGOLft9x5lSo3P1tiShbJGXm4u+qJaxx0ycPr+Xnw/X1x3FDSz+3VBYk89N1mzhYUVXyATneutW3TV3Byt4UqIoQQQtR+krSVMBqNdOzYkcWLz62TaTKZWLx4MXFxcRUe4+bmhq+vb7mH1fR4RPu58UvIqcaUGxdzMgkyj2lrhEZ3M0/10b1xMO6uhiqdwt3VwIxR7XippJ/bH1uSuf6Di/Rzi4qF5kO1W7JLXrJMPYQQQog6QJK2MiZPnsynn37KnDlz2LVrF+PHjycnJ4exY8faOzRo2FtrcSvKhXWfWOacpbdGY7qD0ZPFu7RWxn5lRo1WhU6nY3RcDN/c05VgbyOJKVkMf38VqZl5FR/Q7xlAB7t+h2MJl1EBIYQQou6QpK2Mm266iddff51nn32WDh06sHnzZv7+++8LBifYhU4H3R/Rttd+DPnZl3/OMrdGT+cUsOlIOnBufrbq6tIwkD8e7EGzMG9O5xTw2coDFRcMawXtb9G2F71Qo2sJIYQQdY0kbed54IEHOHToEPn5+axdu5bY2Fh7h3ROy2sgsLG2esHGy1wSqjAXDq3Stpv0Z1lSKkpBy3q+1PPzqPFp6/l58OSQlgB8veYQ6WcrWW+0zxNgMMKB5bBvaY2vJ4QQQtQVkrQ5gWPpuUz/K5FdJ3LOLcAe/97lLcB+aBUU5YFvfQhpYV4gvn8NW9nK6tM8hJb1fMkpKGbO6kpGuwZEQ6dx2vai58FkuuzrCiGEELWZJG1OYNqCXXy0fB9frDqo3Vb0DtcGEGz/seYn3VuyQHzjfhSaFCt2nwSq35+tIjqdjgl9GgMwe/UBcvIrGU3a6zEwesPxzbDrt8u+rhBCCFGbSdLmBMZ0iwHg183HOJOvg67jtR0r36p5C1WZpas2HDxDVl4RQV5G2jfwv9xwARjath4xQZ6kny3k23WHKy7kFQzdHtS2F78ExYUWubYQQghRG0nS5gQ6RQfQOsKX/CIT360/Ap3uAjc/SEuC3X9X/4QZR7VjdXpo1Iclidqo0d7NQzDoL1wFoSYMeh3399Za2z777wD5RZWsURo3ETyD4PQ+2Py1Ra4thBBC1EaStDkBnU7HnSWtbXPjD1Lk6g2d79J2rnxTmyS3OkpHjdbvBB4BZfqzWXaU7HVX1ifc152UzDx+2VjJslVuPtDrcW172XQoqMI6pkIIIUQdJEmbk7imfQSBXkaSM/JYuPMExI7XJsU9ug4OV3NB+zJLVx1My2H/yRxc9Dp6Ngu2aMxuLgbu7tkQgI+W76PYVEly2eku8IuCrOOWm4NOCCGEqGUkaXMS7q4GbumirW36xeqD4BMGHW7Vdq58s+onKi6Cfcu07cb9zasgdI4JxNfd1XIBl7ilSxQBnq4cPHWWBduOV1zIxQ36PqVtr3wDcs9YPA4hhBDC2UnS5kRu7xqNQa9j7YHT7EzO1Drx6/Sw519I2V61kxxLgPwMcPeH+leak7b+Fhg1WhEvNxfu7Ka1tn2w7CILyre7EUJaQl6GNsBCCCGEEOVI0uZE6vl5cFVrbfH6OasPQlBjaDVc27nq7aqdpPTWaOO+ZBcq1h44BdR8FYSqGNMtGi+jgV3HM1mWdLLiQnoDDChZTH7NB5C212rxCCGEEM5IkjYnc2f3GKBk+o+cgnNLW23/Cc5UMpFtWWWWrlq55ySFxYqGwV40CvG2SrwA/p5GbusaDcD7Sy+SjDW7CpoMgOIC+GtK9QdYCCGEELWYJG1O5oLpPyI6QKO+oIq1VRIu5uzpcwu0N+7H4l3ardG+za3Xylbq7h4NMRr0bDh0hnUHTldcSKeDITO05a32LYbE+VaPSwghhHAWkrQ5GZ1OZ55s96s1hygqNkGPSdrOjXMhJ63yg/cvBRSEtsLkE8HSJOv2Zysr1NedUZ0aAJdobQtqDN0f1rb/fhIKcqwemxBCCOEMJGlzQteWTP9xLD2XRbtOQMNeEHEFFOXC2o8rP7DM0lXbjmWQll2At5sLnWMCbRL3/b0ao9fB8t0n2X4so/KCPSZrU4BkHIH//meT2IQQQghHJ0mbEyo7/cfsVQe124qlrW3rPoH87AsPUqrc/GylE+r2bBqM0cU2L4OoIE+uaR8BwIfL9lVe0OgJQ6Zr26vekUEJQgghBJK0Oa2y03/sOp4JLa6GwMaQlw4b51x4QOpObfJaFw+I6mZeusqao0YrMr5kIfkF24+z/2QFyWWp5kOh6SAwFcJfj8ugBCGEEHWeJG1O6oLpP/SGc33BVr8HRQXlDygdNRrTgxO5sP1YJjod9LHBIISyWoT7MqBlGEppqyRUSqeDIa9pqz7sWwK7frddkEIIIYQDkqTNiZVO//HLppLpP9rfDN7hkJUM2+aVL1zm1ujSkluj7Rv4E+LjZsOINRP6aq1tv2w6RnJ6buUFAxtBj0e0bRmUIIQQoo6TpM2JlZ3+4/sNR7TloOImaDtXvQUmk7ZdkAOHVmvbjc/1Z7P1rdFSV0YFENcoiMJixaf/7b944R6TwD8KMo/Bipm2CVAIIYRwQJK0ObGy03/MjS+Z/qPjWHDzg7TdsPsvreDBVdqEtX5R5Pk1YuUebVoQeyVtcK617bt1RziVnV95QVcPbe420G77ntxtg+iEEEIIxyNJm5O7YPoPd1/oPE7bufLN80aN9mPtwTPkFhYT5utG6whfu8Xdo0kw7Rr4kVtYzBerD168cPMh0HSwDEoQQghRp0nS5uTKTv9hTn66jtc68B9dr90WLbN01ZJd50aN6nQ6O0Ss0el0TCgZSTpn9UGy8govfsCQ6Vqd9i+Dnb9aPT4hhBDC0UjSVguUTv+xZn/J9B/eoXDFbdrOv6fCqT2gM6Aa9irTny3MjhFrBrUKp3GIF5l5RXy99vDFCwc2OjcX3d9PVTwXnRBCCFGLSdJWC1ww/QdAtwdBp4eUbdrvkV3Yk2ng6JlcjC56ujcJsk+wZej1Osb3aQLAZ/8dIK+w+OIH9HgE/KO10bErZlg/QCGEEMKBSNJWS5RO//Hr5pLpPwIbQasR5wo07s+Skla2bo2D8DS62D7ICgzvEEF9fw/SsvOZl3D04oXLDkqIfx9OJlk/QCGEEMJBSNJWS3SKDqBVPV/yCkum/4Bzc5wBNOnHkl32neqjIq4GPff2agTAx8v3aSNgL6b5VdBsCJiKYIEMShBCCFF3SNJWS+h0OnNrm3n6j3rtYeCL0GMS6f5t2HDoNAB9bbwKwqXc1DmSYG8jR8/k8sfW5EsfMGQ6uLjDgeWw4xfrByiEEEI4AEnaapHy039orWp0fxgGPM/yPWmYFDQP8yEy0NO+gZ7H3dXA2O4NAfhg6T5Mpku0ngXEQI/J2vY/T0F+lnUDFEIIIRyAJG21iLurgZs7l07/caDcvtL+bP1aOlYrW6nRcdH4uLmwJzVbm2/uUro/rCVvWcdhuQxKEEIIUftJ0lbLXDD9B1BUbGJZ0knAsfqzleXr7sod3aIBeG/pXtSl+qq5up8blLDmA0hNtHKEQgghhH1J0lbLRPifm/7jy/iDAGw8nE5GbiH+nq5cEelvv+Au4a7uDfFwNbD1aAbLd5+89AHNBkPzYSWDEh6TQQlCCCFqNUnaaqHS9Uh/2aRN/1F6a7RPsxBcDI77lAd5u3FbbBQA7yzec+nWNoCrpmmDEg7+B9t/snKEQgghhP047je4qLHOMeWn/1iSqPUR6+ugt0bLurdXI9xc9Gw8nM7qfacufUBANPR8VNv+5/9kUIIQQohaS5K2Wqjs9B+frNjP7hPZGPQ6ejcLsW9gVRDq684tXc61tlVJt4cgoCFkp8Cy6VaMTgghhLAfSdpqqWvbRxDg6crpnAIAOkYH4O9ptHNUVXNf70YYDXrWHjjN2v1VaG1zdYehM7XtNR/CiZ3WDVAIIYSwA0naail3V4O5xQqgvxPcGi1Vz8+DGzs3AODdJXurdlDTgdDialDFslKCEEKIWkmStlqsdPoPgP4OOj9bZe7v3RgXvY6Ve9NIOHSmagcNfhVcPODQStj2o3UDFEIIIWxMkrZaLMLfg49v78ibN7WnSaiPvcOplgYBnozqWNraVsW+bQHR0KtkUMK//wd5mVaKTgghhLA9SdpquQGtwrjuigb2DqNGJvRpgkGvY1nSSbYcSa/aQd0egsBGkH1CBiUIIYSoVSRpEw4rKsiTER3qA9VobXNxgyElgxLWfiSDEoQQQtQakrQJhzaxb2P0Oli0K5XtxzKqdlDTAdDympJBCbJSghBCiNpBkjbh0BqFeHNN+wgA3qvqSFKAwdNKBiWsgm3zrBSdEEIIYTuStAmH90DfJuh08PeOFJJSqrjigX8k9H5c2/73acirYiudEEII4aAkaRMOr2mYD0Pb1AOq0bcNIO4BCGoigxKEEELUCpK0CafwQL8mAPy57Th7U7OrdpCLGwyZoW2v/RhStlspOiGEEML6JGkTTqFlPV8GtQpDKXh/aTX6tjXpDy2vlUEJQgghnJ4kbcJpPNS/KQC/bT7GwbScqh84+FVw9YTD8bD1eytFJ4QQQliXJG3CabSp70e/FqGYqtva5h8JvcoMSshNt0p8QgghhDVJ0iacyoMlfdt+3nSMI6fPVv3AuAcgqCnknIRl06wUnRBCCGE9krQJp3JFVAA9mwZTbFJ8sGxf1Q90McLQkkEJ6z6BlG3WCVAIIYSwEknahNN5uKRv248JRziWnlv1Axv3g1YjQJngz8fAZLJOgEIIIYQVSNImnE6nmEDiGgVRWKz4eHk1WtugZFCCFxxZA1u/s06AQgghhBVI0iacUulI0u/WH+FEZl7VD/SrD72naNv/PiODEoQQQjgNSdqEU+raKJAuMYEUFJn4ePn+ah48AYKbwdk0WPqqdQIUQgghLEySNuGUdDodD/bXRpJ+vfYQJ7Pyq36wixGGztS2138Kx7daIUIhhBDCsiRpE06rR5NgrojyJ7/IxGf/VbO1rVEfaH29NihhgQxKEEII4fgkaRNOS6fT8VA/rW/bl/GHOJVdjdY2gEEvlwxKWAtbvrVChEIIIYTl2DVpi4mJQafTlXtMnz69XJmtW7fSs2dP3N3diYyMZMaMGRecZ968ebRo0QJ3d3fatm3LggULyu1XSvHss89Sr149PDw8GDBgAHv27ClX5vTp09x22234+vri7+/PuHHjyM6u4sLkwm76NA+hbX0/cguL+Xzlgeod7Fcf+kzVthc+C7lnLB+gEEIIYSF2b2l78cUXOX78uPnx4IMPmvdlZmYyaNAgoqOjSUhIYObMmTz//PN88skn5jKrV6/mlltuYdy4cWzatIkRI0YwYsQItm/fbi4zY8YM3nnnHT766CPWrl2Ll5cXgwcPJi/v3KjD2267jR07drBw4ULmz5/PihUruPfee23zjyBqTKfTmVdJ+DL+EOlnC6p3gtjxENxcG5Sw5BUrRCiEEEJYiLKj6Oho9eabb1a6/4MPPlABAQEqPz/f/LepU6eq5s2bm3+/8cYb1bBhw8odFxsbq+677z6llFImk0mFh4ermTNnmvenp6crNzc39e233yqllNq5c6cC1Pr1681l/vrrL6XT6dSxY8eqXJ+MjAwFqIyMjCofIy6fyWRSV721QkVPna/+929S9U+wb5lSz/kq9by/Usc2WTw+IYQQjs1Zvr/t3tI2ffp0goKCuOKKK5g5cyZFRUXmffHx8fTq1Quj0Wj+2+DBg0lKSuLMmTPmMgMGDCh3zsGDBxMfHw/AgQMHSElJKVfGz8+P2NhYc5n4+Hj8/f3p1KmTucyAAQPQ6/WsXbu20tjz8/PJzMws9xC2V7a1bfaqA2TmFVbvBI16Q5uRMihBCCGEQ7Nr0vbQQw/x3XffsXTpUu677z5effVVpkyZYt6fkpJCWFhYuWNKf09JSblombL7yx5XWZnQ0NBy+11cXAgMDDSXqci0adPw8/MzPyIjI6tcd2FZV7UOp2moN1l5RcxZdbD6Jxj0Mhi94eh62Py1xeMTQgghLpfFk7YnnnjigsEF5z8SExMBmDx5Mn369KFdu3bcf//9/O9//+Pdd98lP7+aowDt5MknnyQjI8P8OHLkiL1DqrP0eh0PlqyS8PmqA2TnF13iiPP4RkCfJ7TtRc/B2dMWjlAIIYS4PBZP2h599FF27dp10UejRo0qPDY2NpaioiIOHjwIQHh4OCdOnChXpvT38PDwi5Ypu7/scZWVSU1NLbe/qKiI06dPm8tUxM3NDV9f33IPYT/D2tajUYgX6WcLmRt/qPoniL0fQlrA2VOw5GXLByiEEEJcBosnbSEhIbRo0eKij7J91MravHkzer3efKsyLi6OFStWUFh4ro/SwoULad68OQEBAeYyixcvLneehQsXEhcXB0DDhg0JDw8vVyYzM5O1a9eay8TFxZGenk5CQoK5zJIlSzCZTMTGxlrgX0XYgkGv44G+Wt+2z/7bz9mCara2GVxh6Ova9oZZkLzJwhEKIYQQNWe3Pm3x8fG89dZbbNmyhf379/P1118zadIkbr/9dnNCduutt2I0Ghk3bhw7duzg+++/5+2332by5Mnm8zz88MP8/fff/O9//yMxMZHnn3+eDRs28MADDwBaJ/VHHnmEl19+md9//51t27Zxxx13EBERwYgRIwBo2bIlV111Fffccw/r1q1j1apVPPDAA9x8881ERETY/N9G1Ny17SOIDvLkVE4B36w9XP0TNOwJbW8AFPz5qAxKEEII4TjsNWw1ISFBxcbGKj8/P+Xu7q5atmypXn31VZWXl1eu3JYtW1SPHj2Um5ubql+/vpo+ffoF5/rhhx9Us2bNlNFoVK1bt1Z//vlnuf0mk0k988wzKiwsTLm5uan+/furpKTyU0OcOnVK3XLLLcrb21v5+vqqsWPHqqysrGrVyVmGDNd23687rKKnzlcdX1qocguKqn+CjGSlXqmvTQOy4QvLByiEEMKhOMv3t04ppeydONYWmZmZ+Pn5kZGRIf3b7Kiw2ESfmcs4lp7L89e04s7uDat/kvj34Z+nwCMQHkwAz0DLByqEEMIhOMv3t93naRPC0lwNeib0bQzAR8v3k19UXP2TdLkXQlpC7mlY8pKFIxRCCCGqT5I2USuN6tiAen7upGTmMW/D0eqfwOAKw0oHJcyGYxstG6AQQghRTZK0iVrJzcXA/b211rYPl+2joKgGAwpiekDbG5FBCUIIIRyBJG2i1rqpcyShPm4cS8/ll001aG0DGPQSGH0geSNs+tKyAQohhBDVIEmbqLXcXQ3c20ubyPn9pfsoKq5BS5lPOPR9Stte9LyslCCEEMJuJGkTtdptsdEEexs5fPosv21OrtlJutwLoa0h9wwsfsGyAQohhBBVJEmbqNU8jAbu7qm1tr23dC/FphrMcGNwOTcoIWEOHE24eHkhhBDCCiRpE7Xe6K7RBHi6ciAth/lba9jaFt0N2t0MKFjwKJhqMI2IEEIIcRkkaRO1npebC+N6aBPsvrtkL6aatLYBDHwR3Hy1NUk3zrFghEIIIcSlSdIm6oQ7usXg6+7C3tRs/tqeUrOT+IRB3//Tthe9ADmnLBegEEIIcQmStIk6wdfdlbHdS1vb9tS8ta3z3RDWBvLSYfHzFotPCCGEuBRJ2kSdcVf3hni7uZCYksXCXSdqdhKDCwwtGZSw8Us4st5yAQohhBAXIUmbqDP8PF25s1sMoLW2KVXD1rboOGh/i7YtgxKEEELYiCRtok65q0dDPI0Gth/LZGlSas1PVDoo4fgW+HOyLHElhBDC6iRpE3VKoJeR0XHRALy9eG/NW9u8Q+HqNwEdJHwBv94PxUUWi1MIIYQ4nyRtos65p2cj3F31bDmSzn970mp+orajYNTnoHeBrd/DvDFQlG+5QIUQQogyJGkTdU6wtxu3xWqtbe8svoy+bQBtRsJNX4HBDRLnw7c3Q8FZC0UqhBBCnCNJm6iT7uvVCKOLng2HzhC//zLnW2s+BG77AVw9Yd8S+Gok5GVaJlAhhBCihCRtok4K9XXnls6RgNbadtka9YHRv4KbHxxeDV8Oh7OnL/+8QgghRAlJ2kSddV/vxrgadKzZf5p1ByyQYEXFwpjfwSMQkjfCF8Mgq4bzwQkhhBDnkaRN1FkR/h7c0ElrbXt3iQVa2wAiOsDYv8A7HFJ3wuwhkH7EMucWQghRp0nSJuq08b0b46LX8d+eNDYePmOZk4a2gLv+Ar8oOL1PS9xO7bPMuYUQQtRZkrSJOi0y0JPrr6wPwLuW6NtWKrCRlrgFNYGMI1ridmKn5c4vhBCizpGkTdR5E/s2waDXsTTpJFuPplvuxH4NtFulYW0g+wR8MRSSN1nu/EIIIeoUSdpEnRcd5MXw9hEAvLtkr2VP7h0KY/6A+h0h9wzMuRYOxVv2GkIIIeoESdqEACb2a4JOBwt3nmBnsoXnWPMMhDt+g+gekJ8Jc6/T5nMTQgghqkGSNiGAxiHeXN1Oa217b6kF+7aVcvOB2+ZBkwFQlAvf3ATLZ8CW72DPQjiaAKcPaJPyXs4KDUIIIWotnbqsNXxEWZmZmfj5+ZGRkYGvr6+9wxHVtPtEFoPeXAHAv5N60SzMx/IXKcqHn8bBrj8qL6N31VrnPALBM0jb9izdDgLfCGhxNRhcLR+fEELUQc7y/e1i7wCEcBTNwnwY0iacv7an8N6SvbxzyxWWv4iLG4z6AtZ8AMe3wNlT2iP3jPaz8CyYCrWBC9kXmZi379PQ+3HLxyeEEMJhSUubBTlLpi4qtyM5g2HvrESng0WTe9M4xNu2ARTmastfmZO502V+Pw2n98PehVor3KTtYPSybXxCCFELOcv3t7S0CVFG6wg/BrYKY+HOE7y/dC9v3NjBtgG4eoBffe1RkeIiePdKSD8Em76C2PtsG58QQgi7kYEIQpznoX5NAfhtczIH03LsHM15DC7Q7UFte/V7WhInhBCiTpCkTYjztG3gR9/mIRSbFB8ss/C8bZZwxe3gGQwZh2HHL/aORgghhI1I0iZEBR7sr7W2/bzxGEdOn7VzNOdx9YDY+7XtVW/LFCFCCFFHSNImRAWujAqgZ9NgikyKD5c74GLvnceBqxec2AZ7F9s7GiGEEDYgSZsQlXiwpG/bvA1HSE7PtXM05/EMhI53atur3rJnJEIIIWxEkjYhKtGlYSBdGwVSWKz42BFb2+ImgN4FDv6nragghBCiVpOkTYiLKB1J+u36I6Rm5tk5mvP4NYC2N2rbq960byxCCCGsTpI2IS4irnEQnaIDKCgy8fGK/fYO50LdH9Z+7poPaVZYM1UIIYTDkKRNiIvQ6XQ8VDKS9Ou1h0jLzrdzROcJbQHNhgAKVr9j72iEEEJYkSRtQlxCz6bBtI/0J6/QxKf/OWBrW49HtJ9bvoOsFLuGIoQQwnokaRPiEnQ6HQ/3bwLA3PhDnM4psHNE54nqCpFdobhAW4heCCFErSRJmxBV0Ld5KG3q+3K2oJhZKw/YO5wLlba2bZgNeRl2DUUIIYR1SNImRBXodDrzvG1frD5IxtlCO0d0nqaDIaQF5GfChln2jkYIIYQVSNImRBUNbBlGi3AfsvOLmL3awVrb9Hro9pC2veZDKHSw6UmEEEJcNknahKgivf5ca9uslQfIynOw1ra2N4Bvfcg+AVu/s3c0QgghLEySNiGqYUibcJqEepOZV8SX8YfsHU55LkboOkHbXvUOmIrtG48QQgiLkqRNiGrQWtu0kaSf/befnPwiO0d0no5jwN0PTu+DxPn2jkYIIYQFSdImRDVd3S6CRsFenDlbyFdrHKy1zc0HOt+jba98C5SyazhCCCEsR5I2IarJoNcxoa/W2vbJiv3kFjjYbcjY+8HFHZI3aovJCyGEqBUkaROiBoZ3iCAq0JNTOQU89/t2lCO1aHmHQIfbtO2Vb9k1FCGEEJYjSZsQNeBq0PPi8NbodfDDhqN8sGyfvUMqr9uDoNPDvsVwfKu9oxFCCGEBkrQJUUN9mofy/LWtAZj5TxK/b0m2c0RlBDaEViO07VVv2zUUIYQQliFJmxCX4Y64GMb1aAjAY/O2sOHgaTtHVEbp0lY7foYzB+0ZiRBCCAuQpE2Iy/TU0JYMbBVGQZGJe77cwMG0HHuHpKnXHhr1BWWC1e/ZOxohhBCXyWpJ2yuvvEK3bt3w9PTE39+/wjKHDx9m2LBheHp6EhoayuOPP05RUfl5r5YtW8aVV16Jm5sbTZo04YsvvrjgPO+//z4xMTG4u7sTGxvLunXryu3Py8tj4sSJBAUF4e3tzciRIzlx4kS1YxGiIga9jrdv7kC7Bn6cOVvI2C/WcyanwN5haUpb2zZ9BTlpdg1FCCHE5bFa0lZQUMANN9zA+PHjK9xfXFzMsGHDKCgoYPXq1cyZM4cvvviCZ5991lzmwIEDDBs2jL59+7J582YeeeQR7r77bv755x9zme+//57Jkyfz3HPPsXHjRtq3b8/gwYNJTU01l5k0aRJ//PEH8+bNY/ny5SQnJ3P99ddXKxYhLsbT6MJnYzpR39+DA2k53Dc3gfwiB5gKpGFvqNcBinJh7cf2jkYIIcTlUFY2e/Zs5efnd8HfFyxYoPR6vUpJSTH/7cMPP1S+vr4qPz9fKaXUlClTVOvWrcsdd9NNN6nBgwebf+/SpYuaOHGi+ffi4mIVERGhpk2bppRSKj09Xbm6uqp58+aZy+zatUsBKj4+vsqxVEVGRoYCVEZGRpWPEbVL4vFM1ebZv1X01Pnq4W83KpPJZO+QlNr+s1LP+So1LUqpvCx7RyOEEA7HWb6/7danLT4+nrZt2xIWFmb+2+DBg8nMzGTHjh3mMgMGDCh33ODBg4mPjwe01ryEhIRyZfR6PQMGDDCXSUhIoLCwsFyZFi1aEBUVZS5TlVgqkp+fT2ZmZrmHqNuah/vwwe1X4qLX8evmZN5ctMfeIUHLayGwEeSlw6a59o5GCCFEDdktaUtJSSmXJAHm31NSUi5aJjMzk9zcXNLS0iguLq6wTNlzGI3GC/rVnV/mUrFUZNq0afj5+ZkfkZGRVam6qOV6Ng3h5RFtAHhn8R5+Sjhq34D0Bm3eNoAVr8PexfaNRwghRI1UK2l74okn0Ol0F30kJiZaK1aH8+STT5KRkWF+HDlyxN4hCQdxc5coxvdpDMATP28lft8p+wbU/lYIaQFn0+Cr6+HXiZB7xr4xCSGEqJZqJW2PPvoou3btuuijUaNGVTpXeHj4BSM4S38PDw+/aBlfX188PDwIDg7GYDBUWKbsOQoKCkhPT79omUvFUhE3Nzd8fX3LPYQo9fig5gxrV4/CYsV9czewNzXbfsG4usPdi6HLfYAONn8F78fCrvn2i0mIqijIgW0/Qm66vSMRwu6qlbSFhITQokWLiz6MRmOVzhUXF8e2bdvKjfJcuHAhvr6+tGrVylxm8eLyt3IWLlxIXFwcAEajkY4dO5YrYzKZWLx4sblMx44dcXV1LVcmKSmJw4cPm8tUJRYhqkuv1/G/G9pzZZQ/mXlFjP1iHWnZ+fYLyM0bhs6Au/6GoKaQfQK+vw3m3QnZJ+0XlxAXs+J1+GkcfNoPTjnYcnFC2JjV+rQdPnyYzZs3c/jwYYqLi9m8eTObN28mO1trbRg0aBCtWrVi9OjRbNmyhX/++Yenn36aiRMn4ubmBsD999/P/v37mTJlComJiXzwwQf88MMPTJo0yXydyZMn8+mnnzJnzhx27drF+PHjycnJYezYsQD4+fkxbtw4Jk+ezNKlS0lISGDs2LHExcXRtWvXKsciRE24uxr49I5ORAV6cuR0Lvd8uYG8QjtPBRLVFe5fCT0mgc4AO36B97vA1nngSAvfC6GU9voEOL0PPusPh1bb5tr52fJ+EI7HWsNSx4wZo4ALHkuXLjWXOXjwoBoyZIjy8PBQwcHB6tFHH1WFhYXlzrN06VLVoUMHZTQaVaNGjdTs2bMvuNa7776roqKilNFoVF26dFFr1qwptz83N1dNmDBBBQQEKE9PT3Xdddep48ePlytTlVguxVmGDAvb25uapdo9/4+KnjpfTfgqQRUXO8BUIEopdWyjUh9006YEec5Xqa9vVCrjmL2jEkKTskN7Xb4YotTHvbXtF4KU2vyd9a5ZcFapv55Q6jk/pd7uoNSiF7U4RK3mLN/fOqXkvxKWkpmZiZ+fHxkZGdK/TVxgzf5TjP58LYXFivt7N+aJIS3sHZKmqABWvQXLZ4CpENx8YdDLcOUdoNPZOzpRly2fAUtfgWZXwajZ8Mt9sOt3bV/vqdDnScu+RpM3w8/3QlrShftCWkCbkdD6eghuYrlrCofgLN/fkrRZkLM86cJ+ftl0lEnfbwFg0oBm3NY1imBvB7kFn7oLfpsIxxK03xv2hmvfgYAYu4Yl6rCPe8HxLXDtu9p/IkwmWPyC9p8MgDajYPj72kCby1FcBKvehGXTwVQE3mEw7A0oyoPtP8PehVBcZmm68HbQ5notgQuIvrxrC4fgLN/fkrRZkLM86cK+3lq0m7dKJt3V66BzTCBXtQlncOtwIvw97BucqRjWfAhLXtaWvnL1hP7PQpd7tfnehLCV9MPwVlvQ6eGxPeAVfG5fwhz4c7KWYEV2hZu/Lr+/Ok7vh5/vg6Mla1a3vBaufgu8gs6VyU2HpAWw/SfYtxRUmX6p9TuVtMCNAN+ImsUg7M5Zvr8labMgZ3nShX0ppfgy/hA/Jhxl27GMcvvaR/pzVetwhrQJJybYy04Roo3S+/0hOLRS+z0yVrs95VfffjGJumXNh/D3ExDVDe7668L9+5fB93dAfobWGnzrPAhpVvXzKwUb58DfT0FhjtYtYOhMaHfTxW+55pzSbtHu+BkO/IfWXRtAB9HdoPV10GIY+NST7gVOxFm+vyVpsyBnedKF4zh65ix/b0/hnx0pbDh0ptxgtRbhPgxuHc6QtuE0D/NBZ+svAJMJEmbDwuegIAuaDoLb5tk2BlF3fXE1HPwPBr8KcRMrLnMyCb6+AdIPgbsf3DgXGvW+9LmzTsAfD8Huv7XfY3rCiA/AP6p6MWadgJ2/aS1wR9aU3+fuDyHNtUdw6c9m4BcJerstRiQq4Szf35K0WZCzPOnCMaVm5fHvjhP8syOF+H2nKDKde2vGBHkyuE04Q9rUo30DP9smcKmJ8GE37ZbQ2L8hOs521xZ1U84peL0JKBM8vOXi/SqzT8J3t2q3N/UucM3bcMXtlZff9Qf88TCcPQUGI/R/DrpOuPxEKuOoNj3J9p8heRPnWuDO4+oJwU1LErlm2gCH4OYQ2BAMrpcXg6MqyoecNMhJ1Z6vs2kQcSWEOshgLJzn+1uSNgtyliddOL70swUs2pXK39tTWLHnJAVFJvO+yEAPZo3pTNMwH9sF9MfDkPAFRMXB2L/kto8jUEpbiswz0N6RWN6mr7RBMWFtYfzKS5cvzIPfJmgtXgA9JkO/Z8onYnmZ2u3WzV9rv4e1hes/hrDWlo+/MBdO7dVaAk8maaNRT+7W/mYqrPgYvSsENiqfyIU00ybCNnpaPsbLZTJpLZw5JyE7VUvIctLObWef1PblpEJexoXHu/vBxPXgE3bhPjtwlu9vSdosyFmedOFccvKLWJqkJXBLE1PJKShmYKswPr2jk+2CyEyGd67QRtPdOg+aDbLdtUXF/n4K1nwAPR+Fvv9Xu265fXuL1vG/z5PQ54mqHWMywbJpsGKG9nurEXDdR+DqAQdXwS/3Q8ZhQAfdH4a+T4GLjUduFxfBmQPlE7mTiZC2R+tXVyGddtu29Paq+XZrM/AIsGn4ZB6H/Uth72Lt59lqrKmsM4BXCHiHaMld1nFt9O+oz60XbzU4y/e3JG0W5CxPunBee1OzGfDGcvQ6+G9qP+rbcrTpv8/A6ne0For7VtSuJMEZvdcZ0nZr2y2uhus+1pYqc3YFOTCjkfYfhPtXQXib6h2/+Vv4/UGtRat+J20FkPj3AaUlP9d9rA0YcCQmE2QeOy+R260ld7mnKz/OO+xcIhfS4ty2d5hlWsML8+Dwai1J27cUUneU329w067lHQJeoSU/K9oO1fr4lX5mJG/SliVTJrj9Z2jS//JjvUzO8v0tSZsFOcuTLpzbbZ+tYdXeU0zo05gpV9mwT8jZ0/B2e8jPhJGfQ9tRtru2KK8wF16N0L709K5aghLaGm75xvnn1dv5G/xwB/hHa/3ZapJ8HFwJ390Geenn/nbFaLhqGrjZsFuBJeSkaUncyaRzidzJJMhKrvwYN7+S26znDYLwj774f7aU0q61b4mWqB1apSXPZjqI6ACN+0PjfhDZpeb98P6aCms/0l6vE9ZoLaJ25Czf35K0WZCzPOnCuf29PYX7v0ogyMvI6if74eZiw/nTls+EpS9rfW8mrqu9Hacd3bEEraXCMwhu+R6+vw2yT2i/3/glxPSwd4Q19/O9sPV7iHsABr9S8/Ok7dUGKOSegWve0qbhqE3yMrXbqmlJ5fvOnTmoJfMVcfHQVnMom8gFNdaSwb1LtGTt/GTQp15JktYXGvUtP3/d5cb/fqx2vZ6PavNB2pGzfH9L0mZBzvKkC+dWVGyi14ylJGfk8eZN7bnuiga2u3h+NrzTQetgfPVb0Gms7a4tzkmYo01Z0bA3jPkdMo5pCcrxzdoIyqGvO+dzU1wIMxtrHdctMVLZZAJU3ZoYujAPTu8raZ3bXWYQxJ7yqzpUxsVdu31c2poW2tJ6A492/QHf3661Ft//n3YtO3GW728XewcghKgeF4OeW2OjeP3f3XwZf8i2SZubN/R8DP6eCstfg/Y32/22Rp10Yrv2M7yt9tOvvjaq9/cHtBGU8x+B1J3aHGfO1Bp68D8tYfMM1m69Xa662O/S1V0bEXv+qNjiIm205/mDIE7vA58IrV9Z435awmar93SLq6H5UG3QyfxJcOeCuvmcVYMkbUI4oZs6R/H24j1sOpzO9mMZtKnvZ7uLdxqrdezOOAzrPtFG4gnbSilJ2sLKdNI3emp9DUNbasuQrftE+1K+YY7zTAuS+Kf2s8XQutU6ZgsGF+1WaFBjYKi9o9HodDBkBuxfDofjYdNc6DjG3lE5NElphXBCIT5uDG1bD4Av4w/a9uIubuemYVj5ZsVzMAnrUQpOlIziO39kpU4HvR6Hm78BVy84sELr+5aaaPs4q8tkKpO0XWPfWITt+Edq068ALHxWm99NVEqSNiGc1B1x0QD8tjmZ9LNV6KtiSe1v1joz556B1e/a9tp1XcYRbb1Nvav2HFSkxTC4e6E2xcWZA/DZANj9j23jrK7kjdrcXUZvaNjL3tEIW4q9X7vVn5cO//6fvaNxaJK0CeGkrowKoFU9X/KLTPyw4YhtL643QL+nte34D7RZ0IVtlN4aDWkOLsbKy4W1hnuWQXQPbe3Yb26ClW+Bo449S5yv/Ww6UOuXJeoOQ8nyY+i0kcP7lto7IoclSZsQTkqn05lb275acxiTycZfxi2v0dYPLMyB//5n22vXZScq6M9WGa8gGP0LdBwLKFj0HPxynzbC0NHsKknaWlxt3ziEfdTvCF3u0bb/nKzNRSguIEmbEE5seIf6+Lq7cPj0WZbvtnFfEJ0OBjynbW+YBemHbXv9uiplm/azqisFuBi1ecqGvq4tJbT1e/hiqLYkkaM4maRNSaF31VraRN3U72ltXrjT++U/gpWQpE0IJ+ZhNHBDp0jADgMSABr10eYKKy6AZdNtf/26qDotbWV1uUdrdfMIKJmct6/20xGU3hpt1FtbSFzUTe5+MOQ1bXvlW1oyL8qRpE0IJ3d7V+0W6bLdJzl86qztA+hf0tq25VvnGKXozPKz4fQBbbt0jrbqaNQb7lmirVOZdRxmD4Wt8ywbY03IrVFRquW10HSwtjTbH4+UTJAsSknSJoSTaxjsRa9mISgFX609ZPsAGnTUvmyVCZa8ZPvr1yWpOwEF3uHgFVyzcwQ2gnELodlV2rqSP98Ni16w35djxjFt5Cg6baJVUbfpdDB0Jrh6aovVb/7a3hE5FEnahKgF7ihpbfthwxHyCottH0C/Z0Cn125zHXWQW261UXX7s1XG3Veby637I9rvK9/QlsHKz7q889ZE0gLtZ2QX8Amz/fWF4wmIhj5PatsLn4GcNPvG40AkaROiFujbIpQGAR6kny3k9y3Jlz7A0kJbQLubte3FL9j++nVFTfuzVURvgIEvwPWfgsENdv8Fnw08d/vVVnb9of2UW6OirK7jIaytNhfkv0/bOxqHIUmbELWAQa8z922bG38IZY+5uPo8oY3+O7Ac9i+z/fXrgpTz1hy1hHY3auuWeofDyV3aAIUDKyx3/ovJPQMHV2rbLYbZ5prCORhctVHP6LT+svuX2zsihyBJmxC1xI2dIjG66Nl2LIPNR9JtH0BANHS6S9te/KLjTuLqrEymc8tXWaKlrawGHeHepRBxhZZIzb0O1n9m2WtUZPc/oIohtFXJmphClNGgE3Qep23Pn+SY8wvamCRtQtQSgV5GrmkXAWitbXbR6zFtzctjCeemcRCWceaANpGxwQ2Cmlj+/L4RWotb2xvAVAR/Pqp9URYXWv5apeTWqLiU/s+Cdxic3qf1vazjJGkTohYpXSFh/tbjnMrOt30A3qEQN0HbXvIymOwwKKK2Ku3PFtpCW/bHGlw9tD5uA54HdNqkyXOvg5xTlr9WwVnYu1jbllujojLl5m57E07utm88diZJmxC1SPtIf9o38KOg2MT3tl6PtFS3B7UJXE8marPvC8so7c8WZsH+bBXR6aDHJLjlW23x9oP/af3cTuy07HX2L4WiXPCLgnrtLXtuUbu0GgFNBmqTeM+fVKe7XkjSJkQtMzouBoCv1xym2NbrkYL2P+Mek7Ttpa9CkR1a/Gqj0pa2y53uo6qaD4G7F0FADKQfgs8HQuICy53fPKHuMC1RFKIyOh0Mex1cPODQStj8jb0jshtJ2oSoZa5uV48AT1eOpeeyJDHVPkF0uVdbQzDjCGyYbZ8YahtLTvdRVaEt4Z6lENMTCrK1udz++9/lt3QUF2lTjIDcGhVVExBzboR67ml7R2M3krQJUcu4uxq4sbMd1yMFrW9U7yna9oqZ2vJLoubyMiD9sLZtq5a2Up6B2pqlne8GlDYy+Ke7oTC35uc8vFobpeoRCFFxFgtV1HJxE2FCvNYFo46SpE2IWuj22Gh0OvhvTxr7T9opYbpitLZk0tk0WPOhfWKoLUqn+vBtoPUXtDWDKwz7Hwx7A/QusP1HmD0EMms4kXPprdHmQ603qELUPgZXCG5q7yjsSpI2IWqhyEBP+jUPBWDuGjtN/2Fwhb7/p22vfgfO1t1bGpctxcb92SrTeRyM/lVrIUveBJ/0haMbqncOpSDxT21bbo0KUS2StAlRS40umf7jx4SjnC0osk8Qra/XRjvmZ2rD9UXNnChZc9SW/dkq07An3LNEmxA3OwVmD4Ut1RglfHwzZB7V5vNr3NdqYQpRG0nSJkQt1atpCDFBnmTlFfHrJjusRwqg12uTYwKs+6Tmt9PqOkdpaSsV2BDG/avd3izOh1/uhX+fqdq8fKW3Rpv01/o+CiGqTJI2IWopfZn1SL+MP2if9UgBmg7UOpsX5cHyGfaJwZmZiiF1l7Zt7TnaqsPNB276Gno+qv2++h349hbIy7z4ceZbo7IKghDVJUmbELXYDR0jcXfVk5iSxYZDZ+wThE4H/Z/Ttjd+Caf22ScOZ3VqnzYJraun1sLlSEpbUkd+Di7usOcf+GxA5c/xqX3aovR6F2g2yLaxClELSNImRC3m5+nK8Pb1AfjSXuuRAkTHQdNB2uLgS1+xXxzOqLQ/W2gr0BvsG0tl2o7S1i31iYC0JPi0H+xfdmG50rVGY3raZxSsEE5OkjYharnSAQl/bz9Oalae/QLp94z2c/tPcHyr/eJwNo7Wn60y9a+Ee5dC/U6Qlw5zr4e1n5SfiFdGjQpxWSRpE6KWa1Pfj47RARQWK75bZ6f1SAHqtYM2I7XtJS/ZLw5nY4+VEGrKJxzu/BPa3ay1qv71OMx/BIoKICsFjq7TyknSJkSNSNImRB1wR9y5AQn77DXZLmjztukMsOdfOBRvvzicibmlzYEGIVyMqztc9xEMfBHQQcIXMHcEbJyr7a/fCXwj7BigEM5LkjYh6oAhberRJNSbtOwCRn64moRDdproNqgxXDla2178wuWvYVnbnT0NWSXTpIS2sm8s1aHTQfeH4dYfwM0XDq2CpS9r+6SVTYgak6RNiDrA6KLn+3u70j7Sn/Szhdz66Vr+3p5in2B6T9VGGh6Ohz0L7RODs0gpGYTgHw3uvvaNpSaaDYK7F0FAmVGvLa+xXzxCODlJ2oSoI4K83fj2nlj6twglv8jE+K8T7LOgvG8EdLlX2178IphMto/BWZxwslujFQlprq2g0PZGiB1f59eOFOJySNImRB3iaXTh49EduTU2CqXg2d92MP2vREwmG9+m7DFJu212Yhvs+Nm213YmpQvFO8MghIvxDISRn8KQ6faORAinJkmbEHWMi0HPKyPa8NigZgB8tHwfk3/YTEGRDVu8PAOh20Pa9pKXobjQdtd2JqW3Rx19ug8hhE1I0iZEHaTT6XigX1NmjmqHi17Hr5uTGfvFOjLzbJg8dR0PXiFw5gBsmmu76zqL4kI4mahtO3tLmxDCIiRpE6IOu6FTJJ/f2Rkvo4FVe09x40fxpGTYaAJeN2/o+Zi2vXwGFOba5rrOIm0PFBeA0UcbiCCEqPMkaROijuvdLITv74sjxMeNxJQsrv9gFbtPZNnm4p3Ggl8UZB2HdZ/Y5prOwjypbmttjU8hRJ0nnwRCCNrU9+Pn8d1oFOJFckYeoz5czZr9p6x/YRc36POEtr3yTcjLsP41nYX0ZxNCnEeSNiEEAJGBnvx0fzc6RgeQmVfEHZ+vY/7WZOtfuP3NENwccs/A6netfz1n4UzLVwkhbEKSNiGEWYCXka/vjmVw6zAKik08+O0mPl95wLoX1Rugf8li8vEfQHaqda/nLJxt+SohhNVJ0iaEKMfd1cAHt3Xkzm4xKAUvzd/JS/N3WncutxZXQ/2OUJgD//3PetdxFtmpkJMK6CC0pb2jEUI4CEnahBAXMOh1PHdNK54c0gKAz1ce4MPl+6x3QZ0O+j+rbW+YBWcOWe9azqC0P1tQYzB62TcWIYTDkKRNCFEhnU7Hfb0b8+Lw1gDMWnmAvMJi612wUR9o2Fub5mJZHZ85X/qzCSEqIEmbEOKibu0SRYSfO6dyCvh9s5UHJvR/Tvu59TtITbTutRyZuT+bJG1CiHOslrS98sordOvWDU9PT/z9/Ssso9PpLnh899135cosW7aMK6+8Ejc3N5o0acIXX3xxwXnef/99YmJicHd3JzY2lnXr1pXbn5eXx8SJEwkKCsLb25uRI0dy4sSJcmUOHz7MsGHD8PT0JDQ0lMcff5yioqLL+jcQojZwMegZ0y0GgFmrDqCUFfu2Neio9W9TJljykvWu4+jMLW0yCEEIcY7VkraCggJuuOEGxo8ff9Fys2fP5vjx4+bHiBEjzPsOHDjAsGHD6Nu3L5s3b+aRRx7h7rvv5p9//jGX+f7775k8eTLPPfccGzdupH379gwePJjU1HMj0CZNmsQff/zBvHnzWL58OcnJyVx//fXm/cXFxQwbNoyCggJWr17NnDlz+OKLL3j22Wct9w8ihBO7uXMUHq4GElOyWL3PyvO39XsGdHpInA9HE6x7LUdUlA9pu7XtsNb2jUUI4ViUlc2ePVv5+flVuA9Qv/zyS6XHTpkyRbVu3brc32666SY1ePBg8+9dunRREydONP9eXFysIiIi1LRp05RSSqWnpytXV1c1b948c5ldu3YpQMXHxyullFqwYIHS6/UqJSXFXObDDz9Uvr6+Kj8/v8p1zcjIUIDKyMio8jFCOItnft2moqfOV3fNXmf9i/18v1LP+Sr1xTXWv5ajSd6s1X1apFImk72jEaJOcJbvb7v3aZs4cSLBwcF06dKFWbNmlbv1Eh8fz4ABA8qVHzx4MPHx8YDWmpeQkFCujF6vZ8CAAeYyCQkJFBYWlivTokULoqKizGXi4+Np27YtYWFh5a6TmZnJjh07Ko09Pz+fzMzMcg8haqs7S26RLk5MZf/JbOterM8ToHeFA8th/zLrXsvRpJS5NarT2TcWIYRDsWvS9uKLL/LDDz+wcOFCRo4cyYQJE3j33XMzoqekpJRLpADCwsLIzMwkNzeXtLQ0iouLKyyTkpJiPofRaLygX935ZSo6R+m+ykybNg0/Pz/zIzIysnr/AEI4kUYh3vRvEQrA7FUHrXuxgGjoPE7bXvwiWLMfnaM5UfIfRRmEIIQ4T7WStieeeKLCwQNlH4mJVR/x9cwzz9C9e3euuOIKpk6dypQpU5g5c2a1K2EvTz75JBkZGebHkSNH7B2SEFY1rkdDAH5MOErG2ULrXqznY+DqBccStP5tdcWJkjnaZLoPIcR5qpW0Pfroo+zateuij0aNGtU4mNjYWI4ePUp+fj4A4eHhF4zyPHHiBL6+vnh4eBAcHIzBYKiwTHh4uPkcBQUFpKenX7RMReco3VcZNzc3fH19yz2EqM3iGgfRItyH3MJivl1/2LoX8w6BuAna9uKXwGTFOeIchVIy3YcQolLVStpCQkJo0aLFRR9Go7HGwWzevJmAgADc3NwAiIuLY/HixeXKLFy4kLi4OACMRiMdO3YsV8ZkMrF48WJzmY4dO+Lq6lquTFJSEocPHzaXiYuLY9u2beVGnC5cuBBfX19atWpV4/oIUdvodDruKmltm7P6IIXFJutesNuD4BEAaUmw5btLl3d2Wcch9zToDBAiy1cJIcqzWp+2w4cPs3nzZg4fPkxxcTGbN29m8+bNZGdrHZj/+OMPPvvsM7Zv387evXv58MMPefXVV3nwwQfN57j//vvZv38/U6ZMITExkQ8++IAffviBSZMmmctMnjyZTz/9lDlz5rBr1y7Gjx9PTk4OY8eOBcDPz49x48YxefJkli5dSkJCAmPHjiUuLo6uXbsCMGjQIFq1asXo0aPZsmUL//zzD08//TQTJ040J5BCCM217SMI9jZyPCOPv7dX3ufTItz9oEfJ+33ZNG06jNqstJUtuCm4uts3FiGE47HWsNQxY8Yo4ILH0qVLlVJK/fXXX6pDhw7K29tbeXl5qfbt26uPPvpIFRcXlzvP0qVLVYcOHZTRaFSNGjVSs2fPvuBa7777roqKilJGo1F16dJFrVmzptz+3NxcNWHCBBUQEKA8PT3Vddddp44fP16uzMGDB9WQIUOUh4eHCg4OVo8++qgqLCysVp2dZciwEJfrjX+TVPTU+Wr4eyutf7GCs0q93lybBiP+Q+tfz55WvK7Vc95d9o5EiDrFWb6/dUrVpWFZ1pWZmYmfnx8ZGRnSv03Uaiez8uk+fQkFxSZ+ntCNK6MCrHvBDbNg/iTwDIaHt4Cbt3WvZy/zxsKOn2HA8+daGIUQVucs3992n6dNCOF8QnzcuLZDBACfrzxg/QteMRoCG8HZNFjzofWvZy+yfJUQ4iIkaRNC1Mhd3bUBCX9vT+FYeq51L2Zwhb7/p22vfgfOnrbu9eyhMBdO7dW2ZeSoEKICkrQJIWqkVYQv3RoHUWxSfLn6oPUv2Pp6rQUqPxNWvmn969la6k5QJu0WsHfYpcsLIeocSdqEEDVW2tr2zbrD5OQXWfdiej30f1bbXvcJZCZb93q2VnZ+Nlm+SghRAUnahBA11q9FKDFBnmTlFfHTxqPWv2DTgRAVB0V5sPw161/Plsz92eTWqBCiYpK0CSFqTK/XMbaktW32qoOYTFYejK7TQf/ntO2Nc+HUPutez5bMLW0yCEEIUTFJ2oQQl2VUxwb4urtwIC2HJYmplz7gckXHQdNBoIph6SvWv54tKHVuofiw1vaNRQjhsCRpE0JcFi83F27pEgXArFU2mP4DoN8z2s/tP8Hxrba5pjWlH4b8DNC7QnBze0cjhHBQkrQJIS7bHd1iMOh1rN53ip3Jmda/YL120Gaktr3kJetfz9pKW9lCmoNLzddvFkLUbpK0CSEuW31/D65qEw7YsLWt7/9pC6vv+RcOxdvmmtYigxCEEFUgSZsQwiLG9dAGJPy+OZmTWTZY2D2oMVx5h7a9+AWtX5izStmm/ZRJdYUQFyFJmxDCIq6MCqBDpD8FxSa+WnPINhftPQVc3OFwPOxZaJtrWoO0tAkhqkCSNiGExZS2tn299hB5hcXWv6BvBHS5V9te/CKYTNa/pqXlZ8PpklvKMt2HEOIiJGkTQljMVW3CqefnTlp2Ab9vsdGKBT0mgZsvnNgGO362zTUtKXUnoMA7HLyC7R2NEMKBSdImhLAYV4OeMd1iAJi18gDKFv3MPAOh20Pa9pKXobjQ+te0JOnPJoSoIknahBAWdUvnKDxcDSSmZBG/75RtLtp1PHiFwJkDsGmuba5pKdKfTQhRRZK0CSEsys/TlVEdGwDw+UobTf/h5g09H9O2l8+AwlzbXNcSarh8VbFJ2WaUrhDCYUjSJoSwuLHdYwBYnJjKgbQc21y001jwi4Ks47DuE9tc83KZTGWWr6peS9tL83fS5dVFfLvusBUCE0I4IknahBAW1yjEm34tQgGYbavJdl3coM8T2vZ/b0COjW7NXo7UnVCYAwY3CGpSrUOX7z6JUvDMr9tZvS/NSgEKIRyJJG1CCKsonf5j3oajZJy10eCA9jdDaCvIS4dfxzv2hLun9sG3N2vbUV3B4FLlQ7Pzi8wtmEUmxfivNtquRVMIYTeStAkhrKJb4yBahPuQW1jMd+ttdAtPb4DrPtZarvb8A/Hv2+a61ZWaCLOHQsYRrYVtxIfVOjzxuLa+a4iPGx0i/cnILWTcnPVk5DrZyFkhRLVI0iaEsAqdTsdd3bXWtjmrD1JUbKOJb+u1g6te1bYXPQdHN9jmulV1fAt8MRSyU7RWwbF/gV/9ap1iR7KWtLWt78cnd3Qkws+d/SdzeOCbjbb7dxZC2JwkbUIIq7m2QwRBXkaSM/L4a3uK7S7caRy0Gg6mIpg3FnLP2O7aF3NkPcy5Bs6egogr4M4/wTu02qfZkZwBQOsIX0J93Pl0TCc8XA38tyeNl//cZemohRAOQpI2IYTVuLsauK1rNACzbDUgAUCng2vfBf9oyDgMvz9o//5tB1fC3BGQlwGRXeGO37SJgWtgZ8nt0Vb1fAFoHeHHmzd1AOCL1Qdtt/arEMKmJGkTQljV6K7RGA16Nh1OZ+NhG7Z4ufvBDbNB7wq7/oD1n9nu2ufbswi+GgkF2dCwN4z+WYuvBgqLTexOyQa0ZK3UVW3CeXxwcwCe+30Hq/fKiFIhahtJ2oQQVhXi48a1HSIAbWkrm6rfEQa+qG3/85TWn8zWds3XRokW5UHTwXDrD2D0qvHp9pzIpqDYhI+bC5GBHuX2TejTmBEdIig2KcZ/XTtGlOYWFDN/azLZ+UX2DkUIu5OkTQhhdaUDEv7ansKxdBuvVtB1PDQfCsUFMO9OyM+y3bW3/Qg/3AGmQq2P3U1fgav7ZZ2y9NZoywhfdDpduX06nY7pI9txRVTJiNIv1ttuuhUreXfJHh74ZhO3fLLG6esixOWSpE0IYXWtInyJaxREsUnx5eqDtr24TgfD3wffBnB6P/zxiG36t238En66G1QxtLsZRs4CF+Nln7bsIISKuLsa+GR0J21EaVoOE7/ZSKGTjihVSvHb5mQAth3LYPSstTKtiajTJGkTQthE6WS73647TI6tb3V5BsKoWaAzwPYfrb+o/NqPtcEPKOh0lzYPWzUmz72Y0uk+yvZnO1+IjxufjemMp9HAyr1pvDR/p0WubWtbjmZwLD0XD1cDgV5Gth7N4I7PJXETdZckbUIIm+jXIpSYIE8y84r4aeNR2wcQFQv9n9G2F0yBE1ZKZFa+CX9N0bbjHoBhb4DeMh+1JpNilzlpq7ilrVSrCF/ziNIv4w8xN/6gRWKwpQXbjgMwoFUYX98dS4CnK1uOZnDHrHVk5kniJuoeSdqEEDah1+sYW9K3bfaqg5hMdpiCo9vD0Lg/FOVq/dsKLNhRXylY8gosel77vdcUGPSydnvWQo6eySUrvwijQU+TUO9Llh/cOpwpV2kjSp//Yycr9zjPiFKlFH9u1ZK2YW3DaVnPl6/v7oq/pytbjqRzx+fryJLETdQxkrQJIWxmVMcG+Li7cCAth6VJqbYPQK/XlrnyDoe0JK3FzRKUgn+fhhUztN/7Pwf9/s+iCRuc68/WLNwbV0PVPr7H927M9VfUp9ikmPB1AvtPZls0JmspvTXqaTTQp7k2AXGrCF++vjsWPw9XNh9JZ8wsSdxE3SJJmxDCZrzcXLilSxQAn9t6+o9S3iEw8jPQ6WHzV7Dlu5qfq7gQEv+Er66H+Pe0vw2ZAT0nWybW85j7s9Wr+hxvOp2OV69vy5VR/mTmFTFuzganGIX551ZtAEL/lmG4uxrMf28d4WdO3DYeTufO2estPh3I9mMZPPfbdv7ZkYKy96TMQpQhSZsQwqbGdIvBoNexet8pdpVMX2FzDXtC76na9vzJkLanesef3A3/PgNvtILvboV9S7Qk8Np3IfY+y8dbwrwSwiX6s53P3dXAx6M7Ud/fgwNpOUz4JsGhR5QqpViwTVv2bFjbehfsb1Pfj6/GxeLr7kLCoTPcOWudRRK3I6fP8sh3m7j63ZXMiT/EfXMTGP7+KpYmpUryJhyCJG1CCJuq7+/BVa3DATtMtltWr8chpicU5mj92wovMX9cfjZsnAufD4L3O8PqdyAnFbxCoNuDMGEtXHmHVUO+1HQfF6ONKO2Ep9HAqr2nePEPxx1RuvlIOsfSc/EyGujTPKTCMm0b+PHV3bH4uLuw4dAZ7pq9vsajkk/nFPDiHzvp/7/l/FoyxUjPpsF4Gg1sPZrB2NnrGfVRPKv3OU+fQFE7SdImhLC5u0qm//htczIns/LtE4TeoN0m9QyGE9vhn/+7sIxScHgN/DYRXm8Gvz8AR9ZqrWrNroKbvobJu7QBByHNrBpuWnY+JzLz0emgRb3qJ20ALev58vbNV6DTwdw1h/jSQUeUlg5AOP/W6PnaNfDnq3Gx+Li5sO7gacZ+sZ6zBVVP3HILinl/6V56z1jKrFUHKCg20aNJMPMf7MHccbH8N6Uv9/RsiJuLnoRDZ7j107Xc+ukaEg6dvuw6ClETkrQJIWzuyih/2kf6U1Bs4uu1dlzc3Cccrv9E297wOez4RdvOOgGr3ob3u8CswbDpK61FLrCxNshg0k649XtoeTUYXG0S6s6S/mwxQV54u9V8zreBrcKYelULAF74Yyf/7TlpkfgsRbs1WjJqtN2Ft0bP1z7Sny/HddEStwOnuasKiVtRsYlv1x2m98ylzPwniaz8IlpH+DJ3XBe+ujuWNvW1PoNB3m7837BWrJjSlzvionE1aLf1R34Yz52z17HtaMblV1iIatApuVFvMZmZmfj5+ZGRkYGvb83+JyxEXfH7lmQe+nYTwd5GVk7td9EWFatb9AKsfAPcfCGmB+z+R1vJAMDVE1qNgCtHQ1ScxUeEVtWHy/bx2t+JDGtXj/dvvfKyzqWU4tF5W/h54zF83F34dWJ3GodcegoRW9h4+AzXf7AaL6OBhGcGVvl1sfHwGe74XOvbFtcoiFl3dsbDWP5YpRT/7jzBjL8T2XdSm+6lQYAHjw9uzjXtItDrL/7cHj1zlveW7GVewlGKS6asGdQqjMmDmtEiXD7znZmzfH9LS5sQwi6GtAmnnp87adkF/LEl2b7B9P0/iOwK+ZmQtEBL2Op3gmvehkeT4LoPIbqb3RI2uLz+bOfT6XRMu74tnaIDyMor4u45G0g/W3DZ57WEBVvPTahbnUT+yqgA5tzVBS+jgfj9pxg3Zz25BcXm/RsOnmbUR/HcNzeBfSdzCPB05dmrW7H40d4M71D/kgkbQIMAT6aPbMfiyb25/or66HTw784TDHn7Px78dhP7nGQ6FeG8JGkTQtiFq0HPHXExgDb9h10b/Q0u2jJXLa/RVjGYsAbuWQwd7wR3x/hfd+nI0YstX1Udbi4GPhrd8dyI0q/tv0apyXTu1ujQCkaNXkrH6HOJ2+p9p7jnyw1sP5bBPV9uYNRH8SQcOoO7q56JfRuzfEpf7urREDeX6rfwxgR78cZNHfj3kV4Ma1sPpeCPLckMfGM5j/6whcOnzlb7nEJUhdwetSBnaV4VwlFknC2k67TF5BYW883dsXRrEmzvkBxSTn4RbZ7/B6Vg/f8NIMTHzWLn3nU8k1EfrianoJjbYqN4eUQbdHZqUazprdHzrT94mjGz1nG2TEubXgc3dY7kkQHNCPN1t1TIgNYK+ubCPSzadQIAF72Om7tE8lC/poRa+FrCOpzl+1ta2oQQduPn6cqojg0AmLXKjtN/OLjElEyUglAfN4smbFB+ROnXaw/zZbz9Bob8WcNbo+frHBPIF2O74FnSp21QqzD+ndSLade3s3jCBlrr52djOvHrxO70bBpMkUnx1ZrD9Jq5lOl/JTrFZMbCOUjSJoSwqzu7xwCwODGVA2kWXAu0FtlZxUXia2pAqzCeMI8o3cGK3bYfUWoyKf4qHTVag1uj5+vSMJB/HunFP4/04pM7OtEk1Oeyz3kpHSL9mTsulu/u7cqVUf7kFZr4aPk+es5YwvtL91ZrOhIhKiJJmxDCrhqHeNOvRShKwWxpbatQ6fJV1V0JoTru7dWIUR0bYFIw8ZuN7E21baf6TUfSSc7Iw9vNhV7NKp5Qt7oiAz1pHm79ZO18XRsF8dP4bnx6Ryeah/mQmVfEzH+S6DVjGV/GH6SgyHFXoxCOTZI2IYTd3dVdm2x33oajciupAuY1Ry00CKEiOp2OV65rQ+eY0hGl6206otR8a7RlqH2nf7EQnU7HwFZhLHi4J2/e1J7IQA/SsvN59rcd9H9jGb9sOjdtiBBVJUmbEMLuujcJokW4D7mFxXy3/rC9w3EohcUmklKyAOvdHi3l5mLgo9s70iDAg4OnzjL+K9uMKDWZFH9tr/moUUdm0Ou47ooGLJ7ch5eGtybEx40jp3OZ9P0Whr79Hwt3npB1TUWVSdImhLA7nU5nbm2bs/ogRQ68mLmt7TuZTUGxCW83FyIDPK1+vSBvNz4f09k839mzv+2welKx6cgZjlv41qijMbroGR0Xw/LH+zDlqub4uruQdCKLe77cwMgPVxO/75S9QxROQJI2IYRDuLZDBEFeRpIz8vh7R4q9w3EYO46V9Ger51ulCWAtoXm4D+/coo0o/XbdYb5YfdCq1/tzq/Z8D7zMUaPOwNPowoQ+TfhvSj/G92mMu6uejYfTueXTNdwxax3bj8nSWKJykrQJIRyCu6uB27pGA9pku0Jji0EIFenfMoynhrQE4KX5O1mWlGqV61zuhLrOys/TlalXtWDF430Z3TUaF72OFbtPcvW7K5n49UZZXUFUSJI2IYTDuL1rFEaDnk2H09l4+Iy9w3EIO49bbvmq6rq7Z0NuKBlR+uA3m9ibmmXxa2w6coaUzDx83Fzo2bTuTa4c6uvOSyPasPjR3ozoEIFOB39uO86gN1cw9cetJKfn2jtE4UAkaRNCOIxQH3euaR8BwCxpbUMpZZ6jzdYtbaD1NXz5ujZ0iQkkK7+IcXM2cCbHsiNK51toQl1nFx3kxVs3X8FfD/dkQMtQik2K7zccoc/ry3h5/k5OW/jfXTgnSdqEEA7lrh4xAPy1PaXOtzIcPZNLZl4RrgYdTW0wOWxF3FwMfHj7lTQI8ODQqbOM/zrBYvOMlb01aokJdWuDFuG+fDamMz+NjyO2YSAFRSY+W3mAXjOW8tai3WTnywS9dZkkbUIIh9I6wo+ujQIpNinmxB+0dzh2VdqfrVmYD0YX+31cl44o9XZzYc3+0zz3+3aLjCjdePgMJzLztVujzererdGL6RgdyHf3dmXOXV1oHeFLdn4Rby3aQ68ZS/l85QHyCosvfRJR60jSJoRwOON6NALg27WHyanDLQs7k7X+bK3q2X8B6+bhPrx7yxXodfDtuiPMXnXwss9Zemt0YKsw3Fzq7q3Ryuh0Ono3C+GPB3rw3q1X0CjYi9M5Bbw0fyf9Xl/GD+uPyPQ4dYwkbUIIh9O/RSjRQZ5k5hXx88aj9g7HbnZYec3R6urbIpSnhmojSl/+cydLL2NEaW2eUNfS9HodV7eL4N9JvZh+fVvq+bmTnJHHlJ+2MvitFSzYdlwm6K0jrJa0HTx4kHHjxtGwYUM8PDxo3Lgxzz33HAUF5TtTbt26lZ49e+Lu7k5kZCQzZsy44Fzz5s2jRYsWuLu707ZtWxYsWFBuv1KKZ599lnr16uHh4cGAAQPYs2dPuTKnT5/mtttuw9fXF39/f8aNG0d2dvkh1VWJRQhhfXq9jrHdYgCYteogpjq63M/O4yVJW33rLV9VXeN6NOSmTpGYFDz0zSb2nKjZiNIEuTVabS4GPTd3iWLpY314elhLAjxd2Xcyhwlfb2T4+6v4b89JSd5qOaslbYmJiZhMJj7++GN27NjBm2++yUcffcRTTz1lLpOZmcmgQYOIjo4mISGBmTNn8vzzz/PJJ5+Yy6xevZpbbrmFcePGsWnTJkaMGMGIESPYvn27ucyMGTN45513+Oijj1i7di1eXl4MHjyYvLw8c5nbbruNHTt2sHDhQubPn8+KFSu49957qxWLEMJ2bugUiY+7CwfSci6rRcdZnc4p4HiG9hnWwg6LnldGp9Px0og2dGl4bkRpTUY2/im3RmvM3dXA3T0bsWJKXx7q3xQvo4GtRzMY/fk6bv10LZtkupzaS9nQjBkzVMOGDc2/f/DBByogIEDl5+eb/zZ16lTVvHlz8+833nijGjZsWLnzxMbGqvvuu08ppZTJZFLh4eFq5syZ5v3p6enKzc1Nffvtt0oppXbu3KkAtX79enOZv/76S+l0OnXs2LEqx3IpGRkZClAZGRlVPkYIUbmX5+9Q0VPnq1s/jbd3KDa3Yneqip46X/WescTeoVToVHa+6vHaYhU9db668aPVKr+wuMrHFhebVOeXF6roqfPVop0pVoyybkjLylMv/L5DNX1qgYqeOl9FT52v7p6zXiWlZNo7NKfhLN/fNu3TlpGRQWBgoPn3+Ph4evXqhdFoNP9t8ODBJCUlcebMGXOZAQMGlDvP4MGDiY+PB+DAgQOkpKSUK+Pn50dsbKy5THx8PP7+/nTq1MlcZsCAAej1etauXVvlWM6Xn59PZmZmuYcQwnLGdItBr4NVe0+x63jden+d68/mOLdGywr0MjJrTGd83FxYe+A0z/5W9RGlGw6dITVLuzXaow5OqGtpQd5uPHtNK5Y+3ocbOzVAr4OFO08w+K0VTP5hM0dOn7V3iMJCbJa07d27l3fffZf77rvP/LeUlBTCwsLKlSv9PSUl5aJlyu4ve1xlZUJDQ8vtd3FxITAw8JLXKXuN802bNg0/Pz/zIzIy8mL/BEKIamoQ4MmQNlon9dmr6tZku/acVLeqmob58M6t2ojS79YfYVYVR5SWzs02sLXcGrWk+v4ezBjVnn8n9WJIm3CUgp83HqPf/5bx3G/bOZmVb+8QxWWqdtL2xBNPoNPpLvpITEwsd8yxY8e46qqruOGGG7jnnnssFry9Pfnkk2RkZJgfR44csXdIQtQ6pZPt/ro5mbTsuvOls6N0ug8HTtoA+jYP5f+GtQLglT93sjTx4v0Py06oe3U7GTVqDU1Cffjw9o78/kB3ejYNprBYMSf+EL1mLOX1f5LIyC20d4iihlyqe8Cjjz7KnXfeedEyjRo1Mm8nJyfTt29funXrdkGn/vDwcE6cOFHub6W/h4eHX7RM2f2lf6tXr165Mh06dDCXSU0t/0FSVFTE6dOnL3mdstc4n5ubG25ubhXuE0JYxpVRAbSP9GfLkXS+WnOIRwY0s3dIVne2oIj9aTmA40z3cTF3dY9hz4ksvlt/hAe/3cTPE7rRLKziwRPmW6PuLvRoEmLjSOuWdg38mTsultV703jtnyS2HEnnvaV7mbvmEOP7NGZMXAwexqq1dBYUmTiVk09aVgEns/NIyyogK7+IAE9Xgr3dCPI2EuztRqCXEVeDzCZmLdVO2kJCQggJqdob7dixY/Tt25eOHTsye/Zs9PryT2RcXBz/93//R2FhIa6urgAsXLiQ5s2bExAQYC6zePFiHnnkEfNxCxcuJC4uDoCGDRsSHh7O4sWLzUlaZmYma9euZfz48eZzpKenk5CQQMeOHQFYsmQJJpOJ2NjYKscihLA9nU7HXd1jePi7zXxV8mVT22+pJaZkoRSE+LgR6uNu73AuSafT8eLwNhxIy2HtgdOMm7Oe3yb2INDLeEHZP7cmAzCoVbhdV3moS7o1CebXxkH8u/MEr/+TxJ7UbKb/lcislQd4qH9TrowK4GR2Piez8kk772fp9pmzVW+d8/d0JchLS+LKJnRB3kaCvNwI8THSItwXL7dqpyB1nk5VtedoNR07dow+ffoQHR3NnDlzMBjOfciWtlxlZGTQvHlzBg0axNSpU9m+fTt33XUXb775pnk6jtWrV9O7d2+mT5/OsGHD+O6773j11VfZuHEjbdq0AeC1115j+vTpzJkzh4YNG/LMM8+wdetWdu7cibu79oE3ZMgQTpw4wUcffURhYSFjx46lU6dOfPPNN1WO5VIyMzPx8/MjIyMDX1/H/9+xEM6isNhErxlLOZ6Rx8xR7bihU+3uPzp3zSGe+XU7vZuFMOeuLvYOp8rO5BQw4oNVHDp1li4NA/lqXGy5xKzYpIibtpjUrHxm3dmJfi3CLnI2YQ3FJsWvm47x5qLdHD1TvbV9XfQ6gryNhPhoyZi3mwtnzhZwKruAtOwCTufkU9UpFZuEerPgoZ4Ok7g7y/e31dLchQsXsnfvXvbu3UuDBg3K7SvNE/38/Pj333+ZOHEiHTt2JDg4mGeffbZcktStWze++eYbnn76aZ566imaNm3Kr7/+ak7YAKZMmUJOTg733nsv6enp9OjRg7///tucsAF8/fXXPPDAA/Tv3x+9Xs/IkSN55513zPurEosQwj5cDXruiIvhtb8T+XzlAUZ1bIBOp7N3WFZTunyVM9waLSvAy8jnYzpx3furWXfgNE//uo3XRrYzP1cbDp6WW6N2ZtDrGNmxAVe3r8d3647w6X/7ySs0EVySjIX4uBHi7WZOzEr/Fuzthr+HK3p95e+7YpMi/WwBp3IKSMvOJy27gFPZ+ZzKLuBUTj4ns7SfSSlZ7E3N5sv4g9zds1Gl5xMXslpLW13kLJm6EM4o/WwBcdOWkFtYzDf3xNKtce2dKmL4eyvZcjSD92+9kmFO2Fl/WVIqd32xHpOCp4e1NH8xP/fbdubEH2LklQ34343t7RylsJfv1x9m6k/b8HF3YfnjfSu8jW5rzvL97RjtkkIIcQn+nkZGdqwPwKyVtXf6j6JiE4kp2tJQjj5ytDJ9mofydOmI0gW7WJJ4gmKTYsF2bfokGTVat43qGEnLer5k5RXx9qLd9g7HqUjSJoRwGmO7NwRgcWIqB0pGV9Y2+07mkF9kwtvNhehAT3uHU2Nju8dwS5colIKHvt3MN2sPcTIrH193F7o3qb2tpOLSDHodzwxrCcBXaw+zNzX7EkeIUpK0CSGcRuMQb/o2D0Ep+KKWTra787jWn61lPZ+L9h9ydNqI0tZ0bRRIdn4Rz/y2A4BBrWXUqNBGtA5oGUaxSfHqgl32DsdpyDtHCOFUxvXQ+kfNSzhaKycJ3XHMsZevqg5Xg54Pb+tIdNC5FsNhbeXWqNA8NbQFLnodSxJT+W/PSXuH4xQkaRNCOJXuTYJoHubD2YJivl9/2N7hWFzpmqOt6jlnf7bzaSNKOxPg6UqDAA+5NSrMGoV4MzouGoCX5++iuKrzhdRhkrQJIZyKTqczL201Z/UhiopN9g3IgpRS7Dzu+GuOVleTUG+WPd6Xfx7pJbdGRTkP92+Kn4crSSey+H69LAV5KfLuEUI4neEd6hPoZeRYei7/7Dhx6QOcxLH0XDJyC3E16CpdBspZ+Xm4ygz44gL+nkYe7t8UgDcWJpGVV/u6PFiSJG1CCKfj7mrg9tgoAD5fud/O0VhO6a3RJqE+0iIl6ozRcdE0CvYiLbuAD5bts3c4Dk0+FYQQTun2uGhcDTo2Hk5n0+Ez9g7HIkqTNmdbCUGIy+Fq0PPkUG0KkM9XHuDI6bN2jshxSdImhHBKoT7uXNM+AoBZqw7aNxgL2SlJm6ijBrQMpVvjIAqKTLz2d6K9w3FYkrQJIZzWuB7aZLsLth0nOb16i187otI1R2vLyFEhqkqn0/F/w1qi08H8rcdJOHTa3iE5JEnahBBOq3WEH10bBVJsUsyJP2jvcC7LmZwCkjPygNo1clSIqmod4ceNHSMBeHH+LkwyBcgFJGkTQji1u0qWtvp27WHOFhTZOZqaK53qIzrIEx93VztHI4R9PDq4GZ5GA1uOpPPH1mR7h+NwJGkTQji1/i3DiA7yJDOviJ8Sjto7nBrbUXJrVPqzibos1MedCX0aA/DaX4nkFhTbOSLHIkmbEMKpGfQ67uwWA8DsVQed9pZKbVsJQYiaurtnIyL83EnOyKtVU/pYgiRtQgind0OnSHzcXNiflsOy3an2DqdGzo0cdf41R4W4HO6uBqYOaQHAB8v2kZqZZ+eIHIckbUIIp+ft5sLNXbQOzJ+vPGDnaKovt6CYfSezAbk9KgTAte0j6BDpz9mCYv737257h+MwJGkTQtQKd8TFoNfBqr2nSEzJtHc41ZKYkolJQbC3kRAfN3uHI4Td6XQ6nrlam3D3h4Qj5j6fdZ0kbUKIWiEy0JOr2oQDMMvJWtvOLRLvh06ns3M0QjiGjtGBXN2uHkrBy/N3oZRz9le1JEnahBC1Rulku79uTuZ4hvNMtivLVwlRsalXtcDooid+/ykW7XLO/qqWJEmbEKLWuDIqgE7RARQUmXjm1+1O8z9zGTkqRMUiAz3N/xl7dcEuCopMdo7IviRpE0LUGjqdjleua4urQceiXanM33rc3iFdUlGxicTj0tImRGUm9GlMsLeRA2k5fLXmkL3DsStJ2oQQtUrzcB8m9m0CwPO/7+B0ToGdI7q4A2k55BeZ8DIaiAnysnc4QjgcH3dXJg9sDsDbi/eQftax39PWJEmbEKLWmdCnCc3DfDiVU8BL83faO5yLKr012rKeL3q9DEIQoiI3dY6kRbgPGbmFvLVoj73DsRtJ2oQQtY7RRc9ro9qh18Evm46xNMlxOzCXTmUgi8QLUTmDXsfTw1rRJSaQ66+sb+9w7EaSNiFErdQh0t+8mPz//byN7HzHXEx+p/RnE6JKejQN5vv7utKugb+9Q7EbSdqEELXW5EHNiAr0JDkjjxl/J9o7nAsopcpM9yHLVwlxKXV9HkNJ2oQQtZan0YXp17cF4Mv4Q6w7cNrOEZV3IC2H9LOFuOh1NA3ztnc4QggHJ0mbEKJW69YkmJs7a+uSPvHTVvIKi+0ckWbDwdPc/MkaANpH+uPmYrBzREIIRydJmxCi1ntyaEtCfdzYn5bDO4vtO/JMKcUXqw5w8ydrSM3Kp0moNzNHtbNrTEII5yBJmxCi1vPzcOXlEW0A+HjFfrYfs8/i02cLipj0/Wae/2MnRSbFsHb1+G1idxqFyK1RIcSlSdImhKgTBrUOZ1i7ehSbFFN+3EphsW2XwzmQlsN176/m183JJdMXtOS9W67Ay83FpnEIIZyXJG1CiDrj+Wta4+/pys7jmXz6336bXXfhzhNc++5Kkk5kEeztxjd3x3J3z0Z1fiScEKJ6JGkTQtQZIT5uPDOsFQBvLdrDvpPZVr1esUnx+j9J3PPlBrLyi+gYHcCfD/UgtlGQVa8rhKidJGkTQtQp119Zn17NQigoMvHET1sxmZRVrnM6p4A7Z6/jvaV7AbizWwzf3tOVMF93q1xPCFH7SdImhKhTdDodr17XBk+jgfUHz/D12kMWv8bWo+lc8+5K/tuThoergbdv7sDz17bG6CIfuUKImpNPECFEndMgwJOpV7UAYPpfiRxLz7XYub9bd5hRH8ZzLD2XmCBPfpnYjeEd6u5aiUIIy5GkTQhRJ43uGk2n6AByCor5v1+2odTl3SbNKyxm6o9beeLnbRQUmxjQMozfHuhBi3BZU1QIYRmStAkh6iS9Xsf0ke0wGvQsSzrJr5uP1eg8ZwuKWH/wNDd8FM/3G46g18Hjg5vzyeiO+Hm4WjhqIURdJhMECSHqrCah3jw8oCkz/0nihT920rNpCMHebpWWP51TwI7kDHYkZ7IjOZOdyRnsT8uhtJEuwNOVd265gp5NQ2xUAyFEXSJJmxCiTru3VyPmbz3OruOZPP/7Dt679UqUUhxLzy2XnO1IzuR4Rl6F5wjxcaNzTABPDW1JgwBPG9dACFFXSNImhKjTXA16Zo5qx/D3VzF/63GOZ6xm38ls0s8WVlg+JsiT1hF+tIrwpVWEL60jfAn1kWk8hBDWJ0mbEKLOa1Pfj3t6NuKj5ftIOHQGABe9jqZhPrQuScxaR/jRsp4PPu7ST00IYR+StAkhBDB5YDPCfN3wMrrQKsKXpmHeuLkY7B2WEEKYSdImhBCA0UXP2O4N7R2GEEJUSqb8EEIIIYRwApK0CSGEEEI4AUnahBBCCCGcgCRtQgghhBBOQJI2IYQQQggnIEmbEEIIIYQTkKRNCCGEEMIJSNImhBBCCOEEJGkTQgghhHACkrQJIYQQQjgBSdqEEEIIIZyAJG1CCCGEEE5AkjYhhBBCCCfgYu8AahOlFACZmZl2jkQIIYQQVVX6vV36Pe6oJGmzoKysLAAiIyPtHIkQQgghqisrKws/Pz97h1EpnXL0tNKJmEwmkpOT8fHxQafT2TscQPvfQ2RkJEeOHMHX19fe4ViN1LP2qSt1lXrWLs5WT2eLt6YuVU+lFFlZWURERKDXO27PMWlpsyC9Xk+DBg3sHUaFfH19a/UbspTUs/apK3WVetYuzlZPZ4u3pi5WT0duYSvluOmkEEIIIYQwk6RNCCGEEMIJSNJWy7m5ufHcc8/h5uZm71CsSupZ+9SVuko9axdnq6ezxVtTtaWeMhBBCCGEEMIJSEubEEIIIYQTkKRNCCGEEMIJSNImhBBCCOEEJGkTQgghhHACkrRZyLRp0+jcuTM+Pj6EhoYyYsQIkpKSypXJy8tj4sSJBAUF4e3tzciRIzlx4oR5/5YtW7jllluIjIzEw8ODli1b8vbbb5c7x7Jly9DpdBc8UlJSLhqfUopnn32WevXq4eHhwYABA9izZ0+5Mrt372b48OEEBwfj6+tLjx49WLp06SXr+fvvv9OzZ0/c3d2JjIzklVdeKVfPTp06XRCvwWCodfW87rrrmDJlCo0bN8bd3Z1mzZrRp08fh65nRXWNioqiWbNmuLi4MGLECKD8a9fT05OgoCACAgLw8PCgRYsWPPbYY0732r3mmmu47rrraNu2rbmu579Ho6OjK4zZ1dXVaerZo0cP+vfvT7169fDy8qJDhw7Mnj27XD0HDhzIsGHDiImJQafT8fjjjzvd81mVetrzs6hJkya0bdu23GfK33//Xa6e3bp1Y8SIERf9jujfvz8eHh7odDr0ej3169fn66+/vmS833zzzWU/Lxs3bmTgwIH4+/sTFBTEvffeS3Z29kWfl6p8djra668q9azI1q1by9VzxowZ5fZ/8cUXF8Tr7u5+yfOeXwFhAYMHD1azZ89W27dvV5s3b1ZDhw5VUVFRKjs721zm/vvvV5GRkWrx4sVqw4YNqmvXrqpbt27m/Z9//rl66KGH1LJly9S+ffvU3LlzlYeHh3r33XfNZZYuXaoAlZSUpI4fP25+FBcXXzS+6dOnKz8/P/Xrr7+qLVu2qGuvvVY1bNhQ5ebmmss0bdpUDR06VG3ZskXt3r1bTZgwQXl6eqrjx49XWs9BgwYpvV6vbrrpJrV9+3b17bffKoPBoAICAsz1bNy4sTIYDOZY33jjDXX33XfXunpGREQoV1dX9eeff6p9+/ap22+/XRkMBvXpp586bD3Pr2t8fLyKiopSgYGBqn///mr48OFKqfKv3a+++ko1adJEdejQQR04cEDNnTtXGY1G1a9fP6d6TgcPHqy8vb3Vu+++qwYPHqyGDx9+wXu0c+fOqnPnzuZYZ86cqdzc3NSYMWOcpp7NmjVTvr6+atGiRWrv3r3qrbfeUoAKDg4217NNmzYqIiJCffvttyo8PFzdfPPNTvdZVJV62vOzKCQkRAUHB6uEhATzd4SLi4saNGiQuZ6tW7dWOp1OzZs3r9LviLi4ODV69Gj1ww8/qNdff125uroqnU6n/vjjj3LxhoaGqrlz56o1a9aoadOmKTc3N7Vx48YaPy/Hjh1TAQEB6v7771eJiYlq3bp1qlu3bmrkyJHlzlOTz05Hev1VtZ7ny8jIUGFhYeq2224z19PDw0N9/PHH5jKzZ89Wvr6+5eJNSUm56HnPJ0mblaSmpipALV++XCmlVHp6unJ1dVXz5s0zl9m1a5cCVHx8fKXnmTBhgurbt6/599IX6pkzZ6oci8lkUuHh4WrmzJnmv6Wnpys3Nzf17bffKqWUOnnypALUihUrzGUyMzMVoBYuXFjpuV977TUFqEWLFpnPq9frVUREhLnMq6++WifqGRISckE9r7/+enXbbbc5TT2VOvfaLU1kqvLave6669Ttt99e7jyOXtey79ExY8aooUOHXrKev/zyi9LpdOrgwYNOWc/S8+p0unIxl61ndHS0evPNNy84T22opyN9FpX+m7/zzjvm/a6uruXqWdXviKCgIDV27Nhy8c6YMaNcufM/i6ob78cff6xCQ0PLJUVbt25VgNqzZ0+l8VXls9ORXn81recHH3ygAgICVH5+vvlvU6dOVc2bNzf/Pnv2bOXn51fleCsit0etJCMjA4DAwEAAEhISKCwsZMCAAeYyLVq0ICoqivj4+Iuep/QcZXXo0IF69eoxcOBAVq1addFYDhw4QEpKSrlr+/n5ERsba752UFAQzZs358svvyQnJ4eioiI+/vhjQkND6dixY6XnXr16NQBhYWHmeppMJpKTkzlz5gwA9erVA2DIkCFERkYyfPhwduzYUevqWVxcTGBgYLnn08PDg5UrVzpNPUtjBMyTUF7qtbtp0yZWr15N7969LziPI9f1/Pdoenr6Jd+jn3/+OQMGDCA6Otpp65mQkIBSilatWlVaz8rO4+z1dKTPIoPBAGi3+XJycli3bh2FhYUEBweb61nV56VsvUu99tpr5eI9/7OouvHm5+djNBrLLabu4eEBUOl5oWqfnY70+qtpPePj4+nVqxdGo9H8t8GDB5OUlGSuJ0B2djbR0dGVvv4uRZI2KzCZTDzyyCN0796dNm3aAJCSkoLRaMTf379c2bCwsErvwa9evZrvv/+ee++91/y3evXq8dFHH/HTTz/x008/ERkZSZ8+fdi4cWOl8ZSev/RNU9G1dTodixYtYtOmTfj4+ODu7s4bb7zB33//TUBAQKX1XLVqFWFhYeXq6erqWu66zZs3p2HDhowYMYKvvvoKk8lEt27dOHr0aK2q5+DBgzl79iy7du3CZDKxcOFCfv75Z44fP+4U9Syta+lrt7RcZa/d48eP89hjj9GpUycmTpzI3Xffbd7n6HWt6D1a+mFd2Xs0OTmZv/76y+nr+euvvwIwfvz4Sq91vtpST0f5LDKZTEyaNInOnTtz6NAhfHx8GDx4MAD//vtvuXpe6nn57rvvyMjIYOzYseZ4O3fujK+vL2+99RYNGjSgd+/e/Pjjj+bPourGC9CvXz9SUlKYOXMmBQUFnDlzhieeeAKg0vNW9bOzKvW01euvJvUsPXdF5y173ebNmzNr1ix+++23Cl9/VXJZ7XSiQvfff7+Kjo5WR44cMf/t66+/Vkaj8YKynTt3VlOmTLng79u2bVPBwcHqpZdeuuT1evXqZb419dVXXykvLy/zY8WKFWrVqlUKUMnJyeWOu+GGG9SNN96olNKaja+99lo1ZMgQtXLlSpWQkKDGjx+v6tevbz6uVatW5vNeddVV6v7771fu7u7q1ltvLVfP0ib+nTt3VljPgoIC1bhxY/X000/XqnqmpqYqf39/BSiDwaCaNWumJkyYoNzd3Z2inkqVf+2OGTNGDR8+vNLXbrt27dTYsWPVJ598ogIDA9U333yjlHKe127Z9+iYMWNUx44dL/oeffXVV1VQUJD59ocz1nPJkiXKaDQqFxeXSut5/u2p2lpPpez3WVQa78CBA831fPnll5Ver7+gnnq9Xrm6uprfo6W2bdum/Pz8lKurq5ozZ065fampqWr48OFKr9crg8GgPDw8VLNmzZS7u3uNnxeltM+9sLAwZTAYlNFoVI899pgKCwtT06dPr/R5qe53hL1ffzWpp1JKDRw4UN17773lzrtjx44L6llW2ddfVUnSZmETJ05UDRo0UPv37y/398WLF1d4/z0qKkq98cYb5f62Y8cOFRoaqp566qkqXfOxxx5TXbt2VUppfT/27Nljfpw9e1bt27dPAWrTpk3ljuvVq5d66KGHlFJKLVq0SOn1epWRkVGuTJMmTdS0adOUUkodPHjQfN4xY8aoBg0aqBEjRpg7q5etJ6BOnz5daT1HjRqlhgwZUivr+dprr6mjR48qk8mkpkyZoho3buzw9Tx69OgFr93SpK0qr92XXnpJNWvWzKleu2Xfo2PGjFHdunWrtJ7/+9//VJMmTdQjjzyilHKu92hpPZctW6a8vLzUpEmTLvp8lv3SrM31LGXrz6J27dqpBg0aqLlz55arZ+n7rGHDhuXqGRERoZ566il19OhR83l27NihAgIClKura7mO7ufLzc1VR48eVY8++qiKiIhQrVq1qvHzUlZKSorKyspS2dnZSq/Xqx9++MEc7+V+R9j79VfdepY+L6NHjy5XT6W0/zycX8/zjRo1St18881Vqp9SkrRZjMlkUhMnTlQRERFq9+7dF+wv7WT6448/mv+WmJh4QSfT7du3q9DQUPX4449X+doDBgxQ11133UVjCw8PV6+//rr5bxkZGeU6X/7+++9Kr9errKyscsc2a9ZMvfLKK5XWs7TzZUFBgbme53cyPb+eRUVFKiYmRnl6etbqehYUFKjIyEiHrmdFdS1VmrRV5bX7wgsvqHr16jnVa7essgMRKqrn+++/rwC1bds2p3uPKqV12vby8lLvvffeJZ/P0i/N2l5PpWz7WVT6mREQEKB27959QT1L461Xr565npV9R5QmbO+9916V4u3fv7/y8vJSTz75ZJXjPf95qcjnn3+uPD09yyXGl/vZae/XX1Xreb7z66mUUk8++WS5gQjnKyoqUs2bN1eTJk2qtMz5JGmzkPHjxys/Pz+1bNmycsN5z549ay5z//33q6ioKLVkyRK1YcMGFRcXp+Li4sz7t23bpkJCQtTtt99e7hypqanmMm+++ab69ddf1Z49e9S2bdvUww8/rPR6vXlkTmWmT5+u/P391W+//aa2bt2qhg8fXm6Y88mTJ1VQUJC6/vrr1ebNm1VSUpJ67LHHlKurq9q8eXOl9UxKSlLBwcHqlltuUdu3b1ffffedcnFxUYGBgeZ6RkZGqpYtW6p9+/aphIQEddVVVylAXXPNNbWqnm3btlXNmzdX+/btUytWrFBdunRRer1e3XjjjQ5bz4rqumzZMrVw4UI1dOhQ1adPH7Vp0yY1atQo82t3ypQpqkWLFuqKK65Qu3fvVp999pny9PRUnp6eTvXaLa1rfHy8uuaaa1SfPn3UqFGjVHh4+AXv0dtvv13FxsY65Xv0xx9/VB4eHuqxxx4zx3vHHXeoBg0amOvZtWtX1a5dO7Vp0yZVr149NWbMGBUQEKCGDx9eq+ppz8+imJgYpdfr1b///quOHz9uTr6GDx9urmf79u0VoD799NNKvyP8/f2VwWBQDz74oNqyZYvasmWL2rZtmzp16pQ53hkzZqj33ntPLViwQI0aNUoBKjw8/KJJx6WeF6WUevfdd1VCQoJKSkpS7733nvLw8FBvv/12ufPU5LPTkV5/Va3n+dLT01VYWJgaPXq0uZ6enp7lWkJfeOEF9c8//5hffzfffLNyd3dXO3bsuOi5y5KkzUJKm3vPf8yePdtcJjc3V02YMEEFBAQoT09Pdd1115Wbd+i5556r8BzR0dHmMq+99ppq3Lixcnd3V4GBgapPnz5qyZIll4zPZDKpZ555RoWFhSk3NzfVv39/lZSUVK7M+vXr1aBBg1RgYKDy8fFRXbt2VQsWLKhSPZs2barc3NxU/fr11UsvvVSuno0bN1b169dXRqNRhYWFqSZNmtTKevbs2dO8PygoSLVr187h63mxup7/KK2r0WhUPj4+ysPDQ/n6+qorrrhCDR061OHrWtV6AuXeo0lJScrDw0N98sknTv0ePf8RERFhrufgwYOrdIyz19Oen0WVxde6dWtzPbt06aKGDRtW7e8IQPXu3dscb0REhNLpdApQLi4uauDAgerYsWOX/byMHj1aBQYGKqPRqNq1a6e+/PLLC85Tk89OR3v9VaWeFdmyZYvq0aOHuZ6lfeBKPfLIIyoqKsr8+hs6dOhF586riE4ppRBCCCGEEA5NpvwQQgghhHACkrQJIYQQQjgBSdqEEEIIIZyAJG1CCCGEEE5AkjYhhBBCCCcgSZsQQgghhBOQpE0IIYQQwglI0iaEEEII4QQkaRNCCCGEcAKStAkhhBBCOAFJ2oQQQgghnIAkbUIIIYQQTuD/AZPSgf4u6CRfAAAAAElFTkSuQmCC", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "hedge_weekly_option_backtest_tcost.result_summary['Total'].plot()\n", "hedge_weekly_option_backtest.result_summary['Total'].plot()" ] }, { "cell_type": "markdown", "id": "5698cf7f-7503-4857-bb09-faaea78b1cb9", "metadata": {}, "source": [ "we can see that even a relatively low transaction cost of 0.5bp is having a significant effect. Perhaps we are hedging too frequently.\n", "We should go back to basics and look at exactly how much delta the options are producing.\n", "We construct a backtest which just buys the calls and hedges on inception and when running the backtest we add the FXDelta risk to the risks to be calculated so\n", "we can look at how much delta we are running" ] }, { "cell_type": "code", "execution_count": 51, "id": "5bb34a4c-e30b-489f-9119-59ab4c96265d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "100%|█████████████████████████████████████████████████████████████████████████████████████| 96/96 [00:14<00:00, 6.69it/s]\n", "100%|███████████████████████████████████████████████████████████████████████████████████████| 8/8 [00:01<00:00, 4.68it/s]\n" ] } ], "source": [ "weekly_hedge_action = HedgeAction(\n", " risk=FXDelta(aggregation_level='Type'),\n", " priceables=FXForward(pair='EURUSD', settlement_date='1w'),\n", " trade_duration='1w',\n", " transaction_cost=t_cost,\n", " name='initial hedge',\n", ")\n", "# Note we are now hedging with a 1w fwd and holding for a week to tie in to the option schedule\n", "weekly_option_and_hedge_trigger = PeriodicTrigger(\n", " PeriodicTriggerRequirements(start_date=dt.date(2025, 8, 4), end_date=dt.date(2025, 9, 4), frequency='1w'),\n", " actions=[add_action_tcost, initial_hedge_action],\n", ")\n", "weekly_option_strategy = Strategy(initial_portfolio=None, triggers=weekly_option_and_hedge_trigger)\n", "weekly_option_backtest = ge.run_backtest(\n", " strategy=weekly_option_strategy,\n", " start=dt.date(2025, 8, 4),\n", " end=dt.date(2025, 9, 4),\n", " frequency='1b',\n", " risks=[FXDelta(aggregation_level='Type')],\n", ")" ] }, { "cell_type": "code", "execution_count": 52, "id": "dfc4c7c5-2176-4bf0-9ff5-ab50954fa897", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
PriceFXDelta(aggregation_level:Type)Cumulative CashTransaction CostsTotal
2025-08-04703953.8466590.000000e+00-703953.846659-6051.957748-6051.957748
2025-08-05680489.942334-3.667943e+04-703953.846659-6051.957748-29515.862072
2025-08-06719584.6303784.109436e+06-703953.846659-6051.9577489578.825971
2025-08-07708224.3415582.986270e+06-703953.846659-6051.957748-1781.462848
2025-08-08697124.3846184.814721e+06-703953.846659-6051.957748-12881.419788
2025-08-11685241.7582730.000000e+00-780309.659995-18155.376168-113223.277890
2025-08-12674788.1548014.145061e+06-780309.659995-18155.376168-123676.881362
2025-08-13692719.8344447.446583e+06-780309.659995-18155.376168-105745.201719
2025-08-14624955.1887572.773939e+06-780309.659995-18155.376168-173509.847406
2025-08-15631145.4206496.254668e+06-780309.659995-18155.376168-167319.615514
2025-08-18641537.7451130.000000e+00-877905.875414-30269.758474-266637.888774
2025-08-19626809.6763304.167375e+05-877905.875414-30269.758474-281365.957557
2025-08-20630341.541006-4.598743e+05-877905.875414-30269.758474-277834.092882
2025-08-21643348.160861-3.544384e+06-877905.875414-30269.758474-264827.473027
2025-08-22661875.6829494.048457e+06-877905.875414-30269.758474-246299.950939
2025-08-25664120.3610720.000000e+00-907536.570217-42405.183716-285821.392861
2025-08-26698915.609748-3.066615e+06-907536.570217-42405.183716-251026.144184
2025-08-27706335.164311-6.185495e+06-907536.570217-42405.183716-243606.589622
2025-08-28679163.920997-3.317354e+06-907536.570217-42405.183716-270777.832936
2025-08-29665284.655358-1.787266e+06-907536.570217-42405.183716-284657.098575
2025-09-01671974.3047510.000000e+00-920687.388437-54549.799385-303262.883071
2025-09-02674996.386998-2.699085e+06-920687.388437-54549.799385-300240.800823
2025-09-03671249.433688-1.665711e+06-920687.388437-54549.799385-303987.754133
2025-09-04660967.206777-4.492958e+06-920687.388437-54549.799385-314269.981044
\n", "
" ], "text/plain": [ " Price FXDelta(aggregation_level:Type) Cumulative Cash \\\n", "2025-08-04 703953.846659 0.000000e+00 -703953.846659 \n", "2025-08-05 680489.942334 -3.667943e+04 -703953.846659 \n", "2025-08-06 719584.630378 4.109436e+06 -703953.846659 \n", "2025-08-07 708224.341558 2.986270e+06 -703953.846659 \n", "2025-08-08 697124.384618 4.814721e+06 -703953.846659 \n", "2025-08-11 685241.758273 0.000000e+00 -780309.659995 \n", "2025-08-12 674788.154801 4.145061e+06 -780309.659995 \n", "2025-08-13 692719.834444 7.446583e+06 -780309.659995 \n", "2025-08-14 624955.188757 2.773939e+06 -780309.659995 \n", "2025-08-15 631145.420649 6.254668e+06 -780309.659995 \n", "2025-08-18 641537.745113 0.000000e+00 -877905.875414 \n", "2025-08-19 626809.676330 4.167375e+05 -877905.875414 \n", "2025-08-20 630341.541006 -4.598743e+05 -877905.875414 \n", "2025-08-21 643348.160861 -3.544384e+06 -877905.875414 \n", "2025-08-22 661875.682949 4.048457e+06 -877905.875414 \n", "2025-08-25 664120.361072 0.000000e+00 -907536.570217 \n", "2025-08-26 698915.609748 -3.066615e+06 -907536.570217 \n", "2025-08-27 706335.164311 -6.185495e+06 -907536.570217 \n", "2025-08-28 679163.920997 -3.317354e+06 -907536.570217 \n", "2025-08-29 665284.655358 -1.787266e+06 -907536.570217 \n", "2025-09-01 671974.304751 0.000000e+00 -920687.388437 \n", "2025-09-02 674996.386998 -2.699085e+06 -920687.388437 \n", "2025-09-03 671249.433688 -1.665711e+06 -920687.388437 \n", "2025-09-04 660967.206777 -4.492958e+06 -920687.388437 \n", "\n", " Transaction Costs Total \n", "2025-08-04 -6051.957748 -6051.957748 \n", "2025-08-05 -6051.957748 -29515.862072 \n", "2025-08-06 -6051.957748 9578.825971 \n", "2025-08-07 -6051.957748 -1781.462848 \n", "2025-08-08 -6051.957748 -12881.419788 \n", "2025-08-11 -18155.376168 -113223.277890 \n", "2025-08-12 -18155.376168 -123676.881362 \n", "2025-08-13 -18155.376168 -105745.201719 \n", "2025-08-14 -18155.376168 -173509.847406 \n", "2025-08-15 -18155.376168 -167319.615514 \n", "2025-08-18 -30269.758474 -266637.888774 \n", "2025-08-19 -30269.758474 -281365.957557 \n", "2025-08-20 -30269.758474 -277834.092882 \n", "2025-08-21 -30269.758474 -264827.473027 \n", "2025-08-22 -30269.758474 -246299.950939 \n", "2025-08-25 -42405.183716 -285821.392861 \n", "2025-08-26 -42405.183716 -251026.144184 \n", "2025-08-27 -42405.183716 -243606.589622 \n", "2025-08-28 -42405.183716 -270777.832936 \n", "2025-08-29 -42405.183716 -284657.098575 \n", "2025-09-01 -54549.799385 -303262.883071 \n", "2025-09-02 -54549.799385 -300240.800823 \n", "2025-09-03 -54549.799385 -303987.754133 \n", "2025-09-04 -54549.799385 -314269.981044 " ] }, "execution_count": 52, "metadata": {}, "output_type": "execute_result" } ], "source": [ "weekly_option_backtest.result_summary" ] }, { "cell_type": "markdown", "id": "05eeadc9-9634-44ae-88b6-58400a547d0f", "metadata": {}, "source": [ "maybe we can try a strategy which only hedges if the abs(delta) is greater than 4e6.\n", "To do this instead of hedging on a periodic basis we need a different type of trigger which triggers dependent on the risk on the strategy.\n", "This is the StrategyRiskTrigger" ] }, { "cell_type": "code", "execution_count": 53, "id": "f20bed2a-2d0d-4657-ad4c-d60982154b80", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "100%|█████████████████████████████████████████████████████████████████████████████████████| 96/96 [00:16<00:00, 5.91it/s]\n", "100%|█████████████████████████████████████████████████████████████████████████████████████| 20/20 [00:10<00:00, 1.98it/s]\n" ] } ], "source": [ "risk_hedge_trigger_up = StrategyRiskTrigger(\n", " RiskTriggerRequirements(\n", " risk=FXDelta(aggregation_level='Type'), trigger_level=4e6, direction=TriggerDirection.ABOVE\n", " ),\n", " actions=named_hedge_action_tcost,\n", ")\n", "risk_hedge_trigger_down = StrategyRiskTrigger(\n", " RiskTriggerRequirements(\n", " risk=FXDelta(aggregation_level='Type'), trigger_level=-4e6, direction=TriggerDirection.BELOW\n", " ),\n", " actions=named_hedge_action_tcost,\n", ")\n", "risk_hedge_strategy = Strategy(\n", " initial_portfolio=None, triggers=[weekly_option_and_hedge_trigger, risk_hedge_trigger_up, risk_hedge_trigger_down]\n", ")\n", "risk_hedge_backtest = ge.run_backtest(\n", " strategy=risk_hedge_strategy, start=dt.date(2025, 8, 4), end=dt.date(2025, 9, 4), frequency='1b'\n", ")" ] }, { "cell_type": "code", "execution_count": 54, "id": "8eb40fed-96b5-4ce5-b6a0-068ffa6099ae", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
OpenCloseOpen ValueClose ValueLong ShortStatusTrade PnL
Action6_Priceable0_2025-08-042025-08-042025-08-110.000015-28979.558009-1closed-28979.557994
Action6_Priceable0_2025-08-112025-08-112025-08-180.000056-141949.783377-1closed-141949.78332
Action6_Priceable0_2025-08-182025-08-182025-08-250.00003-113072.7977-1closed-113072.79767
Action6_Priceable0_2025-08-252025-08-252025-09-01-0.00010650433.378489-1closed50433.378383
Action6_Priceable0_2025-09-012025-09-01None-0.0000320-1openNone
add action_EURUSD call_2025-08-042025-08-042025-08-11-703953.846673637865.502946-1closed-66088.343728
add action_EURUSD call_2025-08-112025-08-112025-08-18-685241.75833685891.313071-1closed649.554742
add action_EURUSD call_2025-08-182025-08-182025-08-25-641537.745143747562.463969-1closed106024.718826
add action_EURUSD call_2025-08-252025-08-252025-09-01-664120.360966608390.108042-1closed-55730.252924
add action_EURUSD call_2025-09-012025-09-01None-671974.3047190-1openNone
hedge action_spot hedge_2025-08-042025-08-042025-08-050.00.0-1closed0.0
hedge action_spot hedge_2025-08-062025-08-062025-08-07-0.0000115048.991118-1closed5048.991107
hedge action_spot hedge_2025-08-082025-08-082025-08-110.00001833553.625264-1closed33553.625282
hedge action_spot hedge_2025-08-112025-08-112025-08-120.00.0-1closed0.0
hedge action_spot hedge_2025-08-122025-08-122025-08-13-0.000009-18791.163959-1closed-18791.163968
hedge action_spot hedge_2025-08-132025-08-132025-08-140.00002244184.569597-1closed44184.56962
hedge action_spot hedge_2025-08-152025-08-152025-08-180.00002724885.006478-1closed24885.006505
hedge action_spot hedge_2025-08-182025-08-182025-08-190.00.0-1closed0.0
hedge action_spot hedge_2025-08-222025-08-222025-08-250.000003312.639056-1closed312.639059
hedge action_spot hedge_2025-08-252025-08-252025-08-260.00.0-1closed0.0
hedge action_spot hedge_2025-08-272025-08-272025-08-280.00002234800.42608-1closed34800.426102
hedge action_spot hedge_2025-09-012025-09-012025-09-020.00.0-1closed0.0
hedge action_spot hedge_2025-09-042025-09-04None0.000020-1openNone
\n", "
" ], "text/plain": [ " Open Close Open Value \\\n", "Action6_Priceable0_2025-08-04 2025-08-04 2025-08-11 0.000015 \n", "Action6_Priceable0_2025-08-11 2025-08-11 2025-08-18 0.000056 \n", "Action6_Priceable0_2025-08-18 2025-08-18 2025-08-25 0.00003 \n", "Action6_Priceable0_2025-08-25 2025-08-25 2025-09-01 -0.000106 \n", "Action6_Priceable0_2025-09-01 2025-09-01 None -0.000032 \n", "add action_EURUSD call_2025-08-04 2025-08-04 2025-08-11 -703953.846673 \n", "add action_EURUSD call_2025-08-11 2025-08-11 2025-08-18 -685241.75833 \n", "add action_EURUSD call_2025-08-18 2025-08-18 2025-08-25 -641537.745143 \n", "add action_EURUSD call_2025-08-25 2025-08-25 2025-09-01 -664120.360966 \n", "add action_EURUSD call_2025-09-01 2025-09-01 None -671974.304719 \n", "hedge action_spot hedge_2025-08-04 2025-08-04 2025-08-05 0.0 \n", "hedge action_spot hedge_2025-08-06 2025-08-06 2025-08-07 -0.000011 \n", "hedge action_spot hedge_2025-08-08 2025-08-08 2025-08-11 0.000018 \n", "hedge action_spot hedge_2025-08-11 2025-08-11 2025-08-12 0.0 \n", "hedge action_spot hedge_2025-08-12 2025-08-12 2025-08-13 -0.000009 \n", "hedge action_spot hedge_2025-08-13 2025-08-13 2025-08-14 0.000022 \n", "hedge action_spot hedge_2025-08-15 2025-08-15 2025-08-18 0.000027 \n", "hedge action_spot hedge_2025-08-18 2025-08-18 2025-08-19 0.0 \n", "hedge action_spot hedge_2025-08-22 2025-08-22 2025-08-25 0.000003 \n", "hedge action_spot hedge_2025-08-25 2025-08-25 2025-08-26 0.0 \n", "hedge action_spot hedge_2025-08-27 2025-08-27 2025-08-28 0.000022 \n", "hedge action_spot hedge_2025-09-01 2025-09-01 2025-09-02 0.0 \n", "hedge action_spot hedge_2025-09-04 2025-09-04 None 0.00002 \n", "\n", " Close Value Long Short Status \\\n", "Action6_Priceable0_2025-08-04 -28979.558009 -1 closed \n", "Action6_Priceable0_2025-08-11 -141949.783377 -1 closed \n", "Action6_Priceable0_2025-08-18 -113072.7977 -1 closed \n", "Action6_Priceable0_2025-08-25 50433.378489 -1 closed \n", "Action6_Priceable0_2025-09-01 0 -1 open \n", "add action_EURUSD call_2025-08-04 637865.502946 -1 closed \n", "add action_EURUSD call_2025-08-11 685891.313071 -1 closed \n", "add action_EURUSD call_2025-08-18 747562.463969 -1 closed \n", "add action_EURUSD call_2025-08-25 608390.108042 -1 closed \n", "add action_EURUSD call_2025-09-01 0 -1 open \n", "hedge action_spot hedge_2025-08-04 0.0 -1 closed \n", "hedge action_spot hedge_2025-08-06 5048.991118 -1 closed \n", "hedge action_spot hedge_2025-08-08 33553.625264 -1 closed \n", "hedge action_spot hedge_2025-08-11 0.0 -1 closed \n", "hedge action_spot hedge_2025-08-12 -18791.163959 -1 closed \n", "hedge action_spot hedge_2025-08-13 44184.569597 -1 closed \n", "hedge action_spot hedge_2025-08-15 24885.006478 -1 closed \n", "hedge action_spot hedge_2025-08-18 0.0 -1 closed \n", "hedge action_spot hedge_2025-08-22 312.639056 -1 closed \n", "hedge action_spot hedge_2025-08-25 0.0 -1 closed \n", "hedge action_spot hedge_2025-08-27 34800.42608 -1 closed \n", "hedge action_spot hedge_2025-09-01 0.0 -1 closed \n", "hedge action_spot hedge_2025-09-04 0 -1 open \n", "\n", " Trade PnL \n", "Action6_Priceable0_2025-08-04 -28979.557994 \n", "Action6_Priceable0_2025-08-11 -141949.78332 \n", "Action6_Priceable0_2025-08-18 -113072.79767 \n", "Action6_Priceable0_2025-08-25 50433.378383 \n", "Action6_Priceable0_2025-09-01 None \n", "add action_EURUSD call_2025-08-04 -66088.343728 \n", "add action_EURUSD call_2025-08-11 649.554742 \n", "add action_EURUSD call_2025-08-18 106024.718826 \n", "add action_EURUSD call_2025-08-25 -55730.252924 \n", "add action_EURUSD call_2025-09-01 None \n", "hedge action_spot hedge_2025-08-04 0.0 \n", "hedge action_spot hedge_2025-08-06 5048.991107 \n", "hedge action_spot hedge_2025-08-08 33553.625282 \n", "hedge action_spot hedge_2025-08-11 0.0 \n", "hedge action_spot hedge_2025-08-12 -18791.163968 \n", "hedge action_spot hedge_2025-08-13 44184.56962 \n", "hedge action_spot hedge_2025-08-15 24885.006505 \n", "hedge action_spot hedge_2025-08-18 0.0 \n", "hedge action_spot hedge_2025-08-22 312.639059 \n", "hedge action_spot hedge_2025-08-25 0.0 \n", "hedge action_spot hedge_2025-08-27 34800.426102 \n", "hedge action_spot hedge_2025-09-01 0.0 \n", "hedge action_spot hedge_2025-09-04 None " ] }, "execution_count": 54, "metadata": {}, "output_type": "execute_result" } ], "source": [ "risk_hedge_backtest.trade_ledger()" ] }, { "cell_type": "markdown", "id": "1b3ef088-41e6-4a7e-be2c-70b30776935d", "metadata": {}, "source": [ "so now we can see that we hedging on a sparse schedule and if we look at the result summary we can see that the delta is not always zero but that\n", "the cumulative transaction costs are lower." ] }, { "cell_type": "code", "execution_count": 55, "id": "8589b96c-582f-4b8b-bf12-78caee06eff8", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
PriceFXDelta(aggregation_level:Type)Cumulative CashTransaction CostsTotal
2025-08-04703953.8466590.000000e+00-703953.846659-6051.957748-6051.957748
2025-08-05680489.942334-3.667943e+04-703953.846659-6051.957748-29515.862072
2025-08-06719584.6303890.000000e+00-703953.846670-6257.5776939373.206026
2025-08-07708224.3415582.986270e+06-698904.855552-6463.1976372856.288369
2025-08-08697124.3846000.000000e+00-698904.855533-6704.107429-8484.578362
2025-08-11685241.7582730.000000e+00-741707.043607-19048.435640-75513.720973
2025-08-12674788.1548100.000000e+00-741707.043616-19255.838305-86174.727110
2025-08-13692719.8344229.313226e-10-760498.207553-19835.838794-87614.211925
2025-08-14624955.1887572.773939e+06-716313.637955-20208.436618-111566.885817
2025-08-15631145.4206219.313226e-10-716313.637928-20521.395873-105689.613179
2025-08-18641537.7451130.000000e+00-789024.846869-32948.737433-180435.839189
2025-08-19626809.6763314.167375e+05-789024.846869-32948.737433-195163.907971
2025-08-20630341.541006-4.598743e+05-789024.846869-32948.737433-191632.043296
2025-08-21643348.160860-3.544384e+06-789024.846869-32948.737433-178625.423442
2025-08-22661875.6829460.000000e+00-789024.846865-33151.307018-160300.470937
2025-08-25664120.3610720.000000e+00-818342.902612-45489.301845-199711.843385
2025-08-26698915.609748-3.066615e+06-818342.902612-45489.301845-164916.594709
2025-08-27706335.1642890.000000e+00-818342.902590-45798.836086-157806.574388
2025-08-28679163.920997-3.317354e+06-783542.476510-46108.370327-150486.925841
2025-08-29665284.655358-1.787266e+06-783542.476510-46108.370327-164366.191480
2025-09-01671974.3047510.000000e+00-796693.294730-58252.985996-182971.975975
2025-09-02674996.386999-2.699085e+06-796693.294730-58252.985996-179949.893727
2025-09-03671249.433689-1.665711e+06-796693.294730-58252.985996-183696.847037
2025-09-04660967.2067570.000000e+00-796693.294710-58477.796095-194203.884048
\n", "
" ], "text/plain": [ " Price FXDelta(aggregation_level:Type) Cumulative Cash \\\n", "2025-08-04 703953.846659 0.000000e+00 -703953.846659 \n", "2025-08-05 680489.942334 -3.667943e+04 -703953.846659 \n", "2025-08-06 719584.630389 0.000000e+00 -703953.846670 \n", "2025-08-07 708224.341558 2.986270e+06 -698904.855552 \n", "2025-08-08 697124.384600 0.000000e+00 -698904.855533 \n", "2025-08-11 685241.758273 0.000000e+00 -741707.043607 \n", "2025-08-12 674788.154810 0.000000e+00 -741707.043616 \n", "2025-08-13 692719.834422 9.313226e-10 -760498.207553 \n", "2025-08-14 624955.188757 2.773939e+06 -716313.637955 \n", "2025-08-15 631145.420621 9.313226e-10 -716313.637928 \n", "2025-08-18 641537.745113 0.000000e+00 -789024.846869 \n", "2025-08-19 626809.676331 4.167375e+05 -789024.846869 \n", "2025-08-20 630341.541006 -4.598743e+05 -789024.846869 \n", "2025-08-21 643348.160860 -3.544384e+06 -789024.846869 \n", "2025-08-22 661875.682946 0.000000e+00 -789024.846865 \n", "2025-08-25 664120.361072 0.000000e+00 -818342.902612 \n", "2025-08-26 698915.609748 -3.066615e+06 -818342.902612 \n", "2025-08-27 706335.164289 0.000000e+00 -818342.902590 \n", "2025-08-28 679163.920997 -3.317354e+06 -783542.476510 \n", "2025-08-29 665284.655358 -1.787266e+06 -783542.476510 \n", "2025-09-01 671974.304751 0.000000e+00 -796693.294730 \n", "2025-09-02 674996.386999 -2.699085e+06 -796693.294730 \n", "2025-09-03 671249.433689 -1.665711e+06 -796693.294730 \n", "2025-09-04 660967.206757 0.000000e+00 -796693.294710 \n", "\n", " Transaction Costs Total \n", "2025-08-04 -6051.957748 -6051.957748 \n", "2025-08-05 -6051.957748 -29515.862072 \n", "2025-08-06 -6257.577693 9373.206026 \n", "2025-08-07 -6463.197637 2856.288369 \n", "2025-08-08 -6704.107429 -8484.578362 \n", "2025-08-11 -19048.435640 -75513.720973 \n", "2025-08-12 -19255.838305 -86174.727110 \n", "2025-08-13 -19835.838794 -87614.211925 \n", "2025-08-14 -20208.436618 -111566.885817 \n", "2025-08-15 -20521.395873 -105689.613179 \n", "2025-08-18 -32948.737433 -180435.839189 \n", "2025-08-19 -32948.737433 -195163.907971 \n", "2025-08-20 -32948.737433 -191632.043296 \n", "2025-08-21 -32948.737433 -178625.423442 \n", "2025-08-22 -33151.307018 -160300.470937 \n", "2025-08-25 -45489.301845 -199711.843385 \n", "2025-08-26 -45489.301845 -164916.594709 \n", "2025-08-27 -45798.836086 -157806.574388 \n", "2025-08-28 -46108.370327 -150486.925841 \n", "2025-08-29 -46108.370327 -164366.191480 \n", "2025-09-01 -58252.985996 -182971.975975 \n", "2025-09-02 -58252.985996 -179949.893727 \n", "2025-09-03 -58252.985996 -183696.847037 \n", "2025-09-04 -58477.796095 -194203.884048 " ] }, "execution_count": 55, "metadata": {}, "output_type": "execute_result" } ], "source": [ "risk_hedge_backtest.result_summary" ] }, { "cell_type": "code", "execution_count": null, "id": "8a64c520-9803-47f5-b1e9-9672676f9c12", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/05_factor_models/01_Factor_Models.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "0674ed16", "metadata": {}, "source": [ "# Querying Factor Models\n", "\n", "The GS Quant `FactorRiskModel` class allows users to access vendor factor models such as Barra. The `FactorRiskModel` interface supports date-based querying of the factor risk model outputs such as factor returns, covariance matrix and specific risk for assets.\n", "\n", "In this tutorial, we’ll look at querying available risk models, their coverage universe, and how to access the returns and volatility of factors in the model. We also show how to query factor exposures (z-scores), specific risk and total risk for a given set of assets in the model's universe.\n", "\n", "The factor returns represent the regression outputs of the model for each day. The definitions of each factor vary depending on the model. More details can be found in the [Marquee Data Catalog](https://marquee.gs.com/s/discover/data-services/catalog?query=factor+risk+model)." ] }, { "cell_type": "markdown", "id": "c995452e", "metadata": {}, "source": [ "## Step 1: Authenticate and Initialize Your Session\n", "\n", "First you will import the necessary modules and add your client id and client secret." ] }, { "cell_type": "code", "execution_count": null, "id": "ae1681e6", "metadata": {}, "outputs": [], "source": [ "import datetime as dt\n", "import warnings\n", "import matplotlib.pyplot as plt\n", "import matplotlib.ticker as mtick\n", "import pandas as pd\n", "import seaborn as sns\n", "\n", "from gs_quant.timeseries import beta\n", "from gs_quant.session import GsSession, Environment\n", "from gs_quant.models.risk_model import FactorRiskModel\n", "from gs_quant.models.risk_model import Measure, RiskModelUniverseIdentifierRequest as Identifier, DataAssetsRequest\n", "\n", "client = None\n", "secret = None\n", "\n", "## External users must fill in their client ID and secret below and comment out the lines below\n", "\n", "# client = 'ENTER CLIENT ID'\n", "# secret = 'ENTER CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret)\n", "warnings.filterwarnings(\"ignore\", category=RuntimeWarning)\n", "\n", "print('GS Session initialized.')" ] }, { "cell_type": "markdown", "id": "floral-apollo", "metadata": {}, "source": [ "## Step 2: Pull Risk Model Coverage\n", "\n", "Popular third party risk models that have been onboarded onto Marquee for programmatic access are below.\n", "\n", "| Risk Model Name | Risk Model Id | Description|\n", "|-------------------------------------|--------------------|-------------\n", "| Barra USMED Short (BARRA_USMEDS) | BARRA_USMEDS |Barra (MSCI) US Total Market Equity Model for medium-term investors. Includes all styles from the long-term model plus additional factors for investment horizons between 1 month and 1 year (responsive variant).|\n", "| Barra USSLOW Long (BARRA_USSLOWL) | BARRA_USSLOWL |Barra (MSCI) US Total Market Equity Model for long-term investors. Designed with a focus on portfolio construction and reporting for long investment horizons (stable variant).|\n", "| Barra GEMLT Long (BARRA_GEMLTL) | BARRA_GEMLTL |Barra (MSCI) Global Total Market Equity Model for long-term investors. Designed with a focus on portfolio construction and reporting for global equity investors (stable variant).|\n", "| Barra US Fast (BARRA_USFAST) | BARRA_USFAST |Barra (MSCI) US Equity Trading Model for short-term investors. Includes all styles from the medium-term model plus additional factors for shorter investment horizons.|\n", "| Wolfe Developed Markets All-Cap v1 | WOLFE_QES_DM_AC_1 | Wolfe's Developed Markets All-Cap model is intended for global portfolios with an emphasis on Developed Markets. The model combines next generation factors like short interest and interest rate sensitivity with conventional factors like value and growth.|\n", "| Wolfe US TMT v2 | WOLFE_QES_US_TMT_2 | Wolfe's US TMT model is intended for sector portfolios. The model uses sector-specific factors in a TMT estimation universe to explain more systematic risk and return relative to a broad market model.|\n", "| Wolfe Europe All-Cap v2.1 | WOLFE_QES_EU_AC_21 | Wolfe's Europe All-Cap model is intended for European portfolios with a focus on the developed markets. The model combines next generation factors like short interest and interest rate sensitivity with conventional factors like value and growth.|\n", "| Wolfe US Healthcare v2 | WOLFE_QES_US_HC_2 | Wolfe's US Healthcare model is intended for sector portfolios. The model uses sector-specific factors in a Healthcare estimation universe to explain more systematic risk and return relative to a broad market model.|\n", "\n", "After selecting a risk model, we can create an instance of the risk model to pull information on the model coverage such as the available dates, asset coverage universe, available factors and model description. The `RiskModelCoverage` enum of the model indicates whether the scope of the universe is Global, Region or Country and the `Term` enum refers to the horizon of the model." ] }, { "cell_type": "code", "execution_count": null, "id": "social-attendance", "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "model_id = 'RISK MODEL ID'\n", "factor_model = FactorRiskModel.get(model_id)\n", "\n", "# Check available history for a factor model to decide start and end dates\n", "available_days = factor_model.get_dates()\n", "\n", "print(f'Data available for {model_id} from {available_days[0]} to {available_days[-1]}')\n", "print(\n", " f'{model_id}:\\n - Name: {factor_model.name}\\n - Description: {factor_model.description}\\n - Coverage: {factor_model.coverage.value}\\n - Horizon: {factor_model.term.value}'\n", ")\n", "print(f'For all info https://marquee.gs.com/v1/risk/models/{model_id}')" ] }, { "cell_type": "markdown", "id": "median-individual", "metadata": {}, "source": [ "## Step 3: Query Factor Data\n", "\n", "The following parameters are required for querying factor data:\n", "\n", "* `start_date` - date or datetime that is a business day\n", "* `end_date` - date or datetime that is a business day. If an end date is not specified, it will default to the last available date" ] }, { "cell_type": "markdown", "id": "documentary-trail", "metadata": {}, "source": [ "##### Get Available Factors\n", "For each model, we can retrieve a list of factors available. Each factor has a `name`, `id`, `type` and `factorCategory`.\n", "\n", "A factor's `factorCategory` can be one of the following:\n", "* Style - balance sheet and market metrics\n", "* Industry - an asset's line of business (i.e. Barra uses GICS classification)\n", "* Country - reference an asset’s exchange country location\n" ] }, { "cell_type": "code", "execution_count": null, "id": "improving-recycling", "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "model_id = 'RISK MODEL ID'\n", "factor_model = FactorRiskModel.get(model_id)\n", "\n", "start_date = dt.date(2021, 1, 4) # Set your start date, e.g: dt.date(2021, 1, 4)\n", "end_date = dt.date(2021, 1, 26) # Set your end date, e.g: dt.date(2021, 1, 26)\n", "\n", "available_factors = factor_model.get_factor_data(start_date, end_date).set_index('identifier')\n", "available_factors.sort_values(by=['factorCategory'])" ] }, { "cell_type": "markdown", "id": "cosmetic-contrary", "metadata": {}, "source": [ "##### Get All Factor Returns\n", "\n", "To query factor returns, we can either use `get_factor_returns_by_name` to retrieve the returns with names or `get_factor_returns_by_id` to get the returns with factor ids. We can leverage [the timeseries package](https://developer.gs.com/docs/gsquant/data/data-analytics/timeseries/) to transform and visualize the results." ] }, { "cell_type": "code", "execution_count": null, "id": "equal-thailand", "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "model_id = 'RISK MODEL ID'\n", "factor_model = FactorRiskModel.get(model_id)\n", "\n", "date = dt.date(2020, 1, 4) # Set your date, e.g. dt.date(2020, 1, 4)\n", "\n", "factor_returns = factor_model.get_factor_returns_by_name(date)\n", "fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(15, 5))\n", "\n", "factor_returns[['Growth', 'Momentum', 'Size']].cumsum().plot(\n", " title='Factor Performance over Time for Risk Model', ax=ax[0]\n", ")\n", "factor_beta = beta(factor_returns['Growth'], factor_returns['Momentum'], 63, prices=False)\n", "factor_beta.plot(title='3m Rolling Beta of Growth to Momentum', ax=ax[1])\n", "fig.autofmt_xdate()\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "removable-boards", "metadata": {}, "source": [ "##### Covariance Matrix\n", "\n", "The covariance matrix represents an N-factor by N-factor matrix with the diagonal representing the variance of each factor for each day. The covariance matrix is in daily variance units." ] }, { "cell_type": "code", "execution_count": null, "id": "bibliographic-medicare", "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "model_id = 'RISK MODEL ID'\n", "factor_model = FactorRiskModel.get(model_id)\n", "\n", "start_date = dt.date(2021, 1, 4) # Set your start date, e.g: dt.date(2021, 1, 4)\n", "end_date = dt.date(2021, 1, 26) # Set your end date, e.g: dt.date(2021, 1, 26)\n", "\n", "cov_matrix = factor_model.get_covariance_matrix(start_date, end_date) * 100\n", "\n", "# set display options below--set max_rows and max_columns to None to return full dataframe\n", "max_rows = 10\n", "max_columns = 7\n", "pd.set_option('display.max_rows', max_rows)\n", "pd.set_option('display.max_columns', max_columns)\n", "\n", "# get the last available matrix\n", "round(cov_matrix.loc['DATE: e.g. 2021-02-26'], 6)" ] }, { "cell_type": "markdown", "id": "operating-hearing", "metadata": {}, "source": [ "##### Factor Correlation and Volatility\n", "\n", "The `Factor` Class allows for quick analytics for a specified factor to easily support comparing one factor across different models or to another factor.\n", "\n", "The factor volatility and correlation functions use the covariance matrix for calculations:\n", "* Volatility is the square root of the diagonal\n", "* Correlation is derived from the covariance matrix by dividing the cov(x,y) by the vol(x) * vol(y)" ] }, { "cell_type": "code", "execution_count": null, "id": "outdoor-security", "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "model_id = 'RISK MODEL ID'\n", "factor_model = FactorRiskModel.get(model_id)\n", "\n", "momentum = factor_model.get_factor('Momentum')\n", "growth = factor_model.get_factor('Growth')\n", "\n", "start_date = dt.date(2021, 1, 4) # Set your start date, e.g: dt.date(2021, 1, 4)\n", "end_date = dt.date(2021, 1, 26) # Set your end date, e.g: dt.date(2021, 1, 26)\n", "\n", "vol = momentum.volatility(start_date, end_date)\n", "corr = momentum.correlation(growth, start_date, end_date)\n", "\n", "# plot\n", "fig, ax1 = plt.subplots()\n", "ax2 = ax1.twinx()\n", "ax1.plot(vol.index, corr * 100, 'g-', label='Momentum vs Growth Correlation (LHS)')\n", "ax1.yaxis.set_major_formatter(mtick.PercentFormatter())\n", "ax2.yaxis.set_major_formatter(mtick.PercentFormatter())\n", "ax2.plot(vol.index, vol * 1e4, 'b-', label='Momentum Volatility (RHS)')\n", "plt.xticks(vol.index.values[::30])\n", "fig.legend(loc=\"lower right\", bbox_to_anchor=(0.75, -0.10))\n", "fig.autofmt_xdate()\n", "plt.title('Momentum vs Growth Historical Factor Analysis')\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "21845fe4", "metadata": {}, "source": [ "#### Quick Hint:\n", "\n", "You can also get factor volatility for many factors all at once!" ] }, { "cell_type": "code", "execution_count": null, "id": "10df2a94", "metadata": {}, "outputs": [], "source": [ "factor_model.get_factor_volatility(start_date, end_date)" ] }, { "cell_type": "markdown", "id": "identified-terminology", "metadata": {}, "source": [ "## Step 4: Query Asset Data\n", "\n", "The factor risk represents the beta coefficient that can be attributed to the model, whereas the specific (residual) risk refers to the error term that is not explained by the model.\n", "\n", "| Measure | Definition |\n", "|----------------------------|---------------|\n", "| `Specific Risk` | Annualized idiosyncratic risk or error term which is not attributable to factors in percent units |\n", "| `Total Risk` | Annualized risk which is the sum of specific and factor risk in percent units |\n", "| `Historical Beta` | The covariance of the residual returns relative to the model's estimation universe or benchmark (i.e results of a one factor model) |\n", "| `Residual Variance` | Daily error variance that is not explained by the model which is equal to $$\\frac{({\\frac{\\text{Specific Risk}}{100}})^2}{252}$$ |\n", "| `Universe Factor Exposure` | Z-score for each factor relative to the model's estimation universe |\n", "| `Predicted Beta` | The beta coefficient derived from predictive models; it estimates the asset’s expected sensitivity to market or factor fluctuations, reflecting forecast risk exposure |\n", "| `Daily Return` | The daily percentage change in an asset’s price, representing short-term performance fluctuations |\n", "| `R Squared` | The proportion of asset return variance explained by the model’s risk factors, expressed as a percentage |\n", "| `Factor Return` | The portion of the asset return that is attributed to the risk factor(s) |\n", "| `Dividend Yield` | The annual dividend income expressed as a percentage of the asset's current price |\n", "\n", "We can retrieve an asset universe on a given date by passing in an empty list and a `RiskModelUniverseIdentifierRequest` to the `DataAssetsRequest`." ] }, { "cell_type": "markdown", "id": "spatial-problem", "metadata": {}, "source": [ "##### Get Risk Model Universe Coverage\n", "\n", "Note: \n", "You can query asset-related risk model data with the folloing identifiers:\n", "|Identifier |\n", "|-----------|\n", "|BBID |\n", "|BCID |\n", "|SEDOL |\n", "| CUSIP|\n", "|ISIN |\n", "|GSID |, bcid, sedol, cusip, isin, gsid" ] }, { "cell_type": "code", "execution_count": null, "id": "short-kennedy", "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "model_id = 'RISK MODEL ID'\n", "factor_model = FactorRiskModel.get(model_id)\n", "\n", "date = dt.date(2021, 1, 4) # Set your date, e.g: dt.date(2021, 1, 4)\n", "\n", "asset_universe_for_request = DataAssetsRequest(Identifier.gsid, []) # entire universe\n", "universe_on_date = factor_model.get_asset_universe(date, assets=asset_universe_for_request)\n", "\n", "# set display options below--set max_rows to None to return full list of identifiers\n", "max_rows = 10\n", "pd.set_option('display.max_rows', max_rows)\n", "universe_on_date" ] }, { "cell_type": "markdown", "id": "relevant-brooklyn", "metadata": {}, "source": [ "##### Query Aggregated Risk\n", "\n", "For asset data, we can query for a specific measure or pull data for a list of measures over a range of dates." ] }, { "cell_type": "code", "execution_count": null, "id": "copyrighted-elements", "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "model_id = 'RISK MODEL ID'\n", "factor_model = FactorRiskModel.get(model_id)\n", "\n", "asset_bbid = 'ASSET BBID (e.g. \"AAPL UW\")'\n", "\n", "start_date = dt.date(2021, 1, 4) # Set your start date, e.g: dt.date(2021, 1, 4)\n", "end_date = dt.date(2021, 1, 26) # Set your end date, e.g: dt.date(2021, 1, 26)\n", "\n", "# get risk\n", "universe_for_request = DataAssetsRequest(Identifier.bbid, [asset_bbid])\n", "specific_risk = factor_model.get_specific_risk(start_date, end_date, universe_for_request)\n", "total_risk = factor_model.get_total_risk(start_date, end_date, universe_for_request)\n", "factor_risk = total_risk - specific_risk\n", "\n", "plt.stackplot(\n", " total_risk.index, specific_risk[asset_bbid], factor_risk[asset_bbid], labels=['Specific Risk', 'Factor Risk']\n", ")\n", "plt.title(f'{asset_bbid} Risk')\n", "plt.xticks(total_risk.index.values[::50])\n", "plt.legend(loc='upper right')\n", "plt.gcf().autofmt_xdate()\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "frequent-lithuania", "metadata": {}, "source": [ "##### Query Factor Exposures (z-scores)\n", "\n", "When querying the asset factor exposures, set the `limit_factor` to True to receive only non zero exposures." ] }, { "cell_type": "code", "execution_count": null, "id": "micro-processor", "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "model_id = 'RISK MODEL ID'\n", "factor_model = FactorRiskModel.get(model_id)\n", "\n", "asset_bbid = 'ASSET BBID (e.g. \"AAPL UW\")'\n", "universe_for_request = DataAssetsRequest(Identifier.bbid, [asset_bbid])\n", "\n", "start_date = dt.date(2021, 1, 4) # Set your start date, e.g: dt.date(2021, 1, 4)\n", "end_date = dt.date(2021, 1, 26) # Set your end date, e.g: dt.date(2021, 1, 26)\n", "\n", "factor_exposures = factor_model.get_universe_factor_exposure(start_date, end_date, universe_for_request)\n", "\n", "available_factors = factor_model.get_factor_data(dt.date(2020, 1, 4)).set_index('identifier')\n", "available_factors.sort_values(by=['factorCategory']).tail()\n", "\n", "factor_exposures.columns = [available_factors.loc[x]['name'] for x in factor_exposures.columns]\n", "\n", "sns.boxplot(data=factor_exposures[['Beta', 'Momentum', 'Growth', 'Profitability']])\n", "plt.title(f'Distribution of {asset_bbid} Factor Exposures since 1/4/20')\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "greatest-graham", "metadata": {}, "source": [ "##### Query Multiple Asset Measures" ] }, { "cell_type": "code", "execution_count": null, "id": "subtle-young", "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "model_id = 'RISK MODEL ID'\n", "factor_model = FactorRiskModel.get(model_id)\n", "\n", "# get multiple measures across a date range for a universe specified\n", "start_date = dt.date(2021, 1, 4) # Set your start date, e.g: dt.date(2021, 1, 4)\n", "end_date = dt.date(2021, 1, 26) # Set your end date, e.g: dt.date(2021, 1, 26)\n", "\n", "asset_bbid = 'ASSET BBID (e.g. \"AAPL UW\")'\n", "universe_for_request = DataAssetsRequest(Identifier.bbid, [asset_bbid])\n", "\n", "data_measures = [\n", " Measure.Universe_Factor_Exposure,\n", " Measure.Asset_Universe,\n", " Measure.Historical_Beta,\n", " Measure.Specific_Risk,\n", " Measure.Factor_Name,\n", " Measure.Factor_Id,\n", "]\n", "asset_risk_data = factor_model.get_data(data_measures, start_date, end_date, universe_for_request, limit_factors=True)\n", "\n", "for i in range(len(asset_risk_data.get('results'))):\n", " date = asset_risk_data.get('results')[i].get('date')\n", " universe = asset_risk_data.get('results')[i].get('assetData').get('universe')\n", " factor_exposure = asset_risk_data.get('results')[i].get('assetData').get('factorExposure')\n", " factor_id_to_name = asset_risk_data.get('results')[i].get('factorData')\n", " # Create a mapping of factorId to factorName\n", " factor_id_to_name_map = {item['factorId']: item['factorName'] for item in factor_id_to_name}\n", " # Replace factor IDs with factor names in factor_exposure\n", " factor_exposure_with_names = [\n", " {factor_id_to_name_map.get(fid, fid): value for fid, value in exposure.items()} for exposure in factor_exposure\n", " ]\n", " historical_beta = asset_risk_data.get('results')[i].get('assetData').get('historicalBeta')\n", " specific_risk = asset_risk_data.get('results')[i].get('assetData').get('specificRisk')\n", " print(f'date: {date}')\n", " print(f'universe: {universe}')\n", " print(f'factor id to factor exposure: {factor_exposure}')\n", " print(f'factor name to factor exposure: {factor_exposure_with_names}')\n", " print(f'historical beta: {historical_beta}')\n", " print(f'specific risk: {specific_risk}')\n", " print('\\n')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/05_factor_models/02_Upload_Factor_Models.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Uploading Factor Risk Models\n", "\n", "The GS Quant `FactorRiskModel` class gives users the power to upload their own risk models to Marquee for seamless integration with the Marquee Portfolio Analytics and Plot Tool Pro suite. After uploading a custom `FactorRiskModel`, users can access their factor model data programmatically using GS Quant, visualize their factor risk model data with Plot Tool Pro, or run historical factor attribution analysis on equity portfolios through the lens of their uploaded factor risk model with GS Quant's `Portfolio` class." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 1: Authenticate and Initialize Your Session\n", "\n", "First you will import the necessary modules and add your client id and client secret." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import warnings\n", "\n", "from gs_quant.session import GsSession, Environment\n", "from gs_quant.models.risk_model import (\n", " FactorRiskModel,\n", " RiskModelCalendar,\n", " Term,\n", " CoverageType,\n", " UniverseIdentifier,\n", " FactorType,\n", " RiskModelFactor,\n", ")\n", "import datetime as dt\n", "\n", "\n", "client = None\n", "secret = None\n", "\n", "## External users must fill in their client ID and secret below and comment out the lines below\n", "\n", "# client = 'ENTER CLIENT ID'\n", "# secret = 'ENTER CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret)\n", "warnings.filterwarnings(\"ignore\", category=RuntimeWarning)\n", "\n", "print('GS Session initialized.')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 2: Create a Factor Model\n", "\n", "Input fields to create the initial Factor Risk Model object\n", "\n", "| Attribute | Can be Modified? | Description \n", "|----------------------------------|------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n", "| id | No | Model ID |\n", "| name | Yes | Name of model |\n", "| description | Yes | Longer description of model |\n", "| term | Yes | Term or horizon of model. One of: Long, Medium, Short, Trading, Daily |\n", "| coverage | Yes | Geographical coverage of assets within model universe. One of: Global, Region, Region Excluding Countries, Country |\n", "| vendor | Yes | Who creates the model |\n", "| version | Yes | Version of model |\n", "| universe_identifier | No | Identifier used to upload the model's asset universe. One of: sedol, cusip, isin, bcid, gsid |\n", "| expected_update_time (optional) | Yes | Time (in UTC) data is expected to be published. |\n", "| entitlements (optional) | Yes | Who can view, execute (display access in Marquee), query (via API), or upload data to the risk model. By default, the application that creates the risk model is granted admin entitlements, which include all these rights and the ability to edit entitlements. |\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "model_id = 'VENDOR_COVERAGE_MODELNAME_TERM_VERSION'\n", "model_name = 'My Risk Model'\n", "description = 'My Custom Factor Risk Model'\n", "term = Term.Medium\n", "coverage = CoverageType.Country\n", "universe_identifier = UniverseIdentifier.sedol\n", "expected_update_time = dt.time(6, 0, 0)\n", "vendor = 'Goldman Sachs'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Create the Factor Risk Model" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "model = FactorRiskModel(\n", " id_=model_id,\n", " name=model_name,\n", " description=description,\n", " coverage=coverage,\n", " term=term,\n", " universe_identifier=universe_identifier,\n", " vendor=vendor,\n", " expected_update_time=expected_update_time,\n", " version=1,\n", ")\n", "\n", "model.save()\n", "print(f\"Successfully create a factor risk model with ID: {model.id}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### A note on Model IDs\n", "\n", "Model IDs are unique identifiers in Marquee that adhere to a standardized format, ensuring clarity and consistency for clients. The format is:\n", "\n", "`VENDOR_COVERAGE_MODELNAME_TERM_VERSION`\n", "\n", "- **Vendor**: The name of the entity or organization creating the model.\n", "- **Coverage**: The geographical asset coverage of the model (e.g., \"GLOBAL\", \"APAC\", \"EU\", \"US\", \"UK\", \"EM\" (or Emerging), \"DM\" (for Developed Markets), \"WW\" (for World Wide) etc).\n", "- **Term**: 1-3 character(s) denoting the time horizon of the model (e.g., \"L\", \"S\", \"M\", \"TRD\" for \"Long\", \"Short\", \"Medium\", or \"Trading\" respectively). Variations of these can also take the form of \"MH\" or \"MT\" for Medium Horizon and Medium term. \n", "- **Version**: The version number of the model, starting from 1 and incrementing with updates.\n", "\n", "This format is designed to be intuitive and easily recognizable for clients, making it simple to identify the purpose and scope of a model at a glance." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 3: Upload a Calendar To Your Model\n", "\n", "The calendar associated with the Factor Risk Model contains the dates which the risk model should have posted data on to be considered \"complete.\" The calendar can go further back as well as forward in time than the data that is currently posted for the calendar, but there cannot be any gaps in the data posted to the risk model according to the calendar. Please note that the `upload_calendar` function overwrites the previous calendar if one exists, so be sure to include all dates you want in the calendar." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "calendar = RiskModelCalendar(\n", " [\n", " '2021-01-29',\n", " '2021-01-28',\n", " '2021-01-27',\n", " '2021-01-26',\n", " '2021-01-25',\n", " '2021-01-22',\n", " '2021-01-21',\n", " '2021-01-20',\n", " '2021-01-19',\n", " '2021-01-18',\n", " '2021-01-15',\n", " '2021-01-14',\n", " '2021-01-13',\n", " '2021-01-12',\n", " '2021-01-11',\n", " '2021-01-08',\n", " '2021-01-07',\n", " '2021-01-06',\n", " '2021-01-05',\n", " '2021-01-04',\n", " '2021-01-01',\n", " ]\n", ")\n", "\n", "model.upload_calendar(calendar)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### How to update a calendar? \n", "To update a calendar, you can use the same `upload_calendar` method with a new list of dates. The new list will replace the existing calendar. If you want to add new dates without removing the existing ones, you must first retrieve the current calendar using `model.get_calendar()` and then append the new dates before uploading.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Get the existing calendar\n", "existing_calendar = model.get_calendar().business_dates\n", "\n", "# Append new dates\n", "new_dates = ['2021-01-30', '2021-01-31']\n", "updated_business_dates = list(existing_calendar) + new_dates\n", "\n", "model.upload_calendar(RiskModelCalendar(updated_business_dates))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 4: Upload Data To Your Model\n", "\n", "Once the calendar is posted for a model, we can start uploading data to it.\n", "\n", "The data must be uploaded in a specific format, which is defined in the `RiskModelData` class. The data must be uploaded for each date in the calendar. \n", "\n", "Below are the components of RiskModelData\n", "\n", "#### 1. Factor Data\n", "\n", "\n", "| Field | Description \n", "|------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------\n", "| factorId | Unique ID of the factor (must be < 20 characters). It must map consistently to the same factor across every date. |\n", "| factorName | Name of the factor. It can be any string and should be consistent across every date. |\n", "| factorCategoryId | Unique Id of the category that the factor belongs to (must be < 20 characters). It must map consistently to the same factor category across every date. |\n", "| factorCategory | Name of the category that the factor belongs to (Style, Industry, Market, Currency, etc.). |\n", "| factorReturn | Daily return of the factor in percent units (i.e. for 10%, use 10). |" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "factor_data = [\n", " {\n", " \"factorId\": \"1\",\n", " \"factorName\": \"Factor 1\",\n", " \"factorCategoryId\": \"RI\",\n", " \"factorCategory\": \"Style\",\n", " \"factorReturn\": 0.39,\n", " },\n", " {\n", " \"factorId\": \"2\",\n", " \"factorName\": \"Factor 2\",\n", " \"factorCategoryId\": \"RI\",\n", " \"factorCategory\": \"Style\",\n", " \"factorReturn\": 1.99,\n", " },\n", " {\n", " \"factorId\": \"3\",\n", " \"factorName\": \"Factor 3\",\n", " \"factorCategoryId\": \"RI\",\n", " \"factorCategory\": \"Style\",\n", " \"factorReturn\": 0.29,\n", " },\n", " {\n", " \"factorId\": \"4\",\n", " \"factorName\": \"Factor 4\",\n", " \"factorCategoryId\": \"MKT\",\n", " \"factorCategory\": \"Market\",\n", " \"factorReturn\": -0.9,\n", " },\n", " {\n", " \"factorId\": \"5\",\n", " \"factorName\": \"Factor 5\",\n", " \"factorCategoryId\": \"IND\",\n", " \"factorCategory\": \"Industry\",\n", " \"factorReturn\": 0.2,\n", " },\n", "]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### 2. Asset Data\n", "\n", "The asset data can be categorized into different components: \n", " - **Factor Exposures**: factor loadings/zscores of assets in the universe.\n", " - **Asset risk data**: Total risk, specific risk and specific return\n", " - **Asset betas**: Historical beta and model-predicted beta\n", " - **Market data**: Daily return, price, market capitalization, trading volumes, and dividend yields\n", " \n", " \n", "| Field | Optional? | Description \n", "|--------------------------|-----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n", "| universe | No | The model universe uploaded as an array of identifiers. The identifier type must match the model's universe identifier |\n", "| factorExposure | No | Array of dictionaries of factorId to the factor exposure of each asset in the universe. There is no need to include a factorId in the map if the asset has zero exposure to the factor.|\n", "| specificRisk | No | Array of annualized specific risk. Represented in percent units (i.e. for 10%, use 10). The order in this array must match the order of the universe array. |\n", "| totalRisk | No | Array of annualized total risk. Represented in percent units (e.g., for 10%, use 10). The order in this array must match the order of the universe array. |\n", "| specificReturn | No | Array of specific returns. Assign `None` for missing or N/A values. The order of this array must match the order of the universe array.|\n", "| estimationUniverseWeight | No | Array of weights in the estimation universe. The order of this array must match the order of the universe array. Assign `None` if an asset is not in the estimation universe.|\n", "| predictedBeta | No | Array of predicted betas. Assign `None` for missing or N/A values. The order of this array must match the order of the universe array.|\n", "| historicalBeta | No | Array of historical betas. Assign `None` for missing or N/A values. The order of this array must match the order of the universe array.|\n", "| globalPredictedBeta | Yes | Array of predicted betas relative to a global market. This measure is not applicable to all models. Assign `None` for missing or N/A values. The order of this array must match the order of the universe array. |\n", "| dailyReturn | Yes | Array of asset daily returns. Represented in percent units (e.g., for 10%, use 10). Assign `None` for missing or N/A values. The order in this array must match the order of the universe array. |\n", "| price | Yes | Array of asset prices. Assign `None` for missing or N/A values. The order in this array must match the order of the universe array. |\n", "| currency | Yes | Array of currencies. Assign `None` for missing or N/A values. The order in this array must match the order of the universe array.|\n", "| capitalization | Yes | Array of asset market capitalizations in trading currency. Assign `None` for missing or N/A values. The order in this array must match the order of the universe array. |\n", "| dividendYield | Yes | Array of dividend yields. Assign `None` for missing or N/A values. The ordering of this array must correspond to the ordering of the universe array |\n", "| tradingVolume | Yes | Array of asset daily trading volumes. Assign `None` for missing or N/A values. The ordering of this array must correspond to the ordering of the universe array |\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "asset_data = {\n", " \"universe\": [\"ASSET1\", \"ASSET2\", \"ASSET3\", \"ASSET4\", \"ASSET5\"],\n", " \"factorExposure\": [\n", " {\"1\": 0.48760401340992243, \"2\": -0.590034096365603},\n", " {\"3\": -0.8017494714963063, \"4\": 0.7862451506530919},\n", " {\n", " \"5\": -0.253403252938764,\n", " },\n", " {\n", " \"1\": 1.7,\n", " \"2\": 0.08459557023760955,\n", " \"3\": 1.8459557023760955,\n", " \"4\": 0.8459557023760955,\n", " \"5\": -1.8459557023760955,\n", " },\n", " {\"2\": -0.607987433173778},\n", " ],\n", " \"totalRisk\": [14.324714301179963, 17.071151010504597, 13.874122072602503, 14.479219370106968, 15.6372307010737],\n", " \"specificRisk\": [6.267696111058748, 7.348880522500647, 7.866871501833286, 5.835214680084606, 11.76129276971361],\n", " \"specificReturn\": [\n", " 0.5659048855060385,\n", " 0.37595374172320617,\n", " -0.24473722135505294,\n", " -0.9616000320458231,\n", " -0.7699674832309915,\n", " ],\n", " \"predictedBeta\": [\n", " 0.8376437537112529,\n", " 0.8825284596124832,\n", " 0.8658162312132138,\n", " 1.1663832117848132,\n", " 1.1638549355761407,\n", " ],\n", " \"historicalBeta\": [\n", " 0.9699683237602925,\n", " 1.184349884914826,\n", " 1.1478458610614193,\n", " 1.1073962331683949,\n", " 0.8610028132979528,\n", " ],\n", " \"estimationUniverseWeight\": [None, 0.5, 0.2, 0.3, None],\n", " \"dailyReturn\": [\n", " 0.36864638955606754,\n", " -0.16247541720716152,\n", " -0.14959565439528988,\n", " 0.3449230613583272,\n", " 0.4624063629113383,\n", " ],\n", " \"currency\": [\"USD\", \"USD\", \"USD\", \"USD\", \"USD\"],\n", " \"price\": [146.85268872446377, 112.66584268302705, 102.8189727997599, 122.91632409053749, 131.95674622727046],\n", " \"capitalization\": [2310528821.941124, 8391014828.775409, 7744577643.201652, 1873806225.329452, 6606600945.467762],\n", " \"issuerMarketCap\": [\n", " 8694261819.837845,\n", " 7063154593.331768,\n", " 1761291245.7391672,\n", " 4251457616.7812405,\n", " 5264161805.272642,\n", " ],\n", " \"dividendYield\": [4.578537816528908, 3.7261774596135204, 3.4343027973124207, 3.413227950682553, 2.175273338412861],\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### 3. Covariance Matrix\n", "\n", "The covariance matrix is represented as a 2D array, where each row corresponds to a factor, and the elements in the inner arrays represent the covariances (in daily variance units) between that factor and every other factor. The ordering of both the rows and columns aligns with the ordering of the factor data list in the payload. The first array below contains variance-covariance data for the factor with ID \"1\" and the first element is the variance of the factor itself. The second element is the covariance between the first and second factor, and so on. The covariance matrix must be a square matrix, meaning it has the same number of rows and columns." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "covariance_matrix = [\n", " [0.9864, 0.026, 0.0965, 0.067, 0.0502],\n", " [0.026, 0.6192, 0.0272, 0.0437, 0.0185],\n", " [0.0965, 0.0272, 0.8771, 0.0863, 0.0407],\n", " [0.067, 0.0437, 0.0863, 0.9589, 0.0333],\n", " [0.0502, 0.0185, 0.0407, 0.0333, 0.5216],\n", "]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### 4. Risk Free Rates \n", "\n", "The currency rates data is represented as an object containing information about currency exchange rates and risk-free rates. It includes the following components: `currency`, an array of strings representing the currency tickers; `exchangeRate`, an array of daily USD exchange rates, where the ordering corresponds to the ordering of the currencies; and `riskFreeRate`, an array of annualized monthly risk-free rates (in percentage), also ordered to match the currencies. If the model has a numeraire different from USD, the exchangeRate will be an array of daily exchange rates in that currency\n", "\n", "- currency: Array of currency tickers\n", "- exchangeRate: Array of daily USD (or the risk model numeraire if not USD) exchange rates, ordered to match the currencies\n", "- riskFreeRate: Array of annualized monthly risk-free rates (in percentage), ordered to match the currencies" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "currency_data = {\n", " 'currency': ['USD', 'EUR', 'GBP'],\n", " 'exchangeRate': [1.0, 0.85, 0.75],\n", " 'riskFreeRate': [0.01, 0.005, 0.007],\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### 5. Issuer Specific Covariance\n", "\n", "The issuer-specific covariance data is represented as an object that captures the covariance between two assets in daily variance units. It consists of three components: universeId1, an array of strings representing the first set of universe identifiers, which must exist in the asset universe; universeId2, an array of strings representing the second set of universe identifiers, also required to exist in the asset universe; and covariance, an array of numbers where each value represents the covariance between the corresponding identifiers at the same index in universeId1 and universeId2.\n", "\n", "- universeId1: Array of assets with issuer specific covariance to the asset in universeId2 at the same index. Each asset must also be present in the Asset Data universe\n", "- universeId1: Array of assets with issuer specific covariance to the asset in universeId1 at the same index. Each asset must also be present in the Asset Data universe\n", "- covariance: Array of the covariance between universeId1 and universeId2 at the same index. In daily variance units" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "issuer_specific_covariance = {\n", " 'universeId1': ['ASSET1', 'ASSET2'],\n", " 'universeId2': ['ASSET3', 'ASSET5'],\n", " 'covariance': [0.03754, 0.01234],\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 6. Factor Portfolios\n", "\n", "The factor portfolios data is represented as an object that defines a set of assets combining to form a portfolio representing a single factor. This portfolio has unit exposure to the specified factor and zero exposure to all other factors, making it useful for mapping intraday factor return data. It consists of two components: `universe`, an array of strings representing the universe of the factor portfolios, which must be a subset of the model asset universe; and `portfolio`, an array of dictionaries containing factor identifiers and their corresponding portfolio weights.\n", "\n", "The portfolios array contains objects with the following fields:\n", "- factorId: The ID of the factor corresponding to the factor data's factorIds\n", "- weights: An array of weights for each asset ID in the universe, corresponding to the ordering of the universe. The weights must sum to 1 and can include zero values (null values are not allowed)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "factor_portfolios = {\n", " \"universe\": [\"ASSET2\", \"ASSET3\", \"ASSET4\"],\n", " \"portfolio\": [\n", " {\"factorId\": \"1\", \"weights\": [0.3545899480088869, 0.2880743474246568, 0.35733570456645636]},\n", " {\"factorId\": \"2\", \"weights\": [0.4390053364378467, 0.2907941789867976, 0.2702004845753557]},\n", " {\"factorId\": \"3\", \"weights\": [0.3547375024699844, 0.29918415302858037, 0.3460783445014351]},\n", " {\"factorId\": \"4\", \"weights\": [0.5821881487398893, 0.06066318669175055, 0.35714866456836014]},\n", " {\"factorId\": \"5\", \"weights\": [0.4078609724431924, 0.39878265902389126, 0.1933563685329163]},\n", " ],\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "\n", "### Upload Data\n", "\n", "Now we are ready to upload risk model data. Note you can only upload on dates that are in the model's calendar.\n", "The data must be uploaded in a specific format, which is defined in the `RiskModelData` class. The data must be uploaded for each date in the calendar, one date at a time.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "data = {\n", " 'date': '2021-01-13',\n", " 'factorData': factor_data,\n", " 'covarianceMatrix': covariance_matrix,\n", " 'assetData': asset_data,\n", " 'currencyRatesData': currency_data,\n", " 'issuerSpecificCovariance': issuer_specific_covariance,\n", " 'factorPortfolios': factor_portfolios,\n", "}\n", "\n", "risk_model = FactorRiskModel.get(model_id)\n", "risk_model.upload_data(data)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Important\n", "\n", "For large payloads, use the `max_asset_batch_size` parameter to split the data into smaller batches. This is particularly important for large models (asset universe larger than 10,000), as it helps prevent timeouts during the upload process. Typically, a batch_size of `10000` works well. \n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "risk_model.upload_data(data, max_asset_batch_size=1000)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 5: Check which days have data posted" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `get_dates` method returns a list of dates for which data has been posted to the risk model. This is useful for verifying whether the data upload was successful. Upon completing the historical data backfill, all dates returned by this method should match the dates in the model's calendar. If any dates are missing, it may indicate that the data upload was incomplete or has not occurred yet.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dates_posted = risk_model.get_dates()\n", "print(dates_posted)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 6: Uploading Partial data\n", "The `upload_data` method can be used to upload partial data, which is useful for updating subsets of data without re-uploading the entire dataset. Data that can be uploaded partially includes `factorData` and `covarianceMatrix`, `assetData`, `factorPortfolios`, and `issuerSpecificCovariance`. Note that `factorData` and `covarianceMatrix` must already be uploaded when uploading partial data. If not, you must upload them first. The partial data must conform to the same structure as the full data upload but can represent a subset of the complete dataset.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Uploading factor portfolios separately\n", "factor_portfolios = {'date': '2021-01-13', 'factorPortfolios': factor_portfolios}\n", "\n", "risk_model.upload_data(factor_portfolios, max_asset_batch_size=1000)\n", "\n", "# Uploading issuer specific covariance separately\n", "issuer_specific_covariance = {'date': '2021-01-13', 'issuerSpecificCovariance': issuer_specific_covariance}\n", "risk_model.upload_data(issuer_specific_covariance, max_asset_batch_size=1000)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 7: Enhance Factor Descriptions and Tool Tips\n", "\n", "The last step is adding tooltips and descriptions to the risk model factors. We highly encourage you to do this for every non-binary factor in your model (such as style factors) so that Marquee UI users of your model can leverage the tooltips and descriptions to better understand how the factors were constructed and what they represent. The snippet below should be repeated for each factor for which you are updating descriptions and tooltips. The `identifier` field must match the `factorId` in the factor data you uploaded earlier." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "identifier = '3'\n", "tooltip = 'Short description that appears when you hover over the factor name on our UI.'\n", "description = 'Longer description that appears on the portfolio drill-down page of this factor.'\n", "glossary_description = 'Longest description to describe the factor in depth on our risk model glossary page.'\n", "\n", "factor = RiskModelFactor(\n", " identifier=identifier,\n", " type_=FactorType.Factor,\n", " tooltip=tooltip,\n", " description=description,\n", " glossary_description=glossary_description,\n", ")\n", "\n", "risk_model.save_factor_metadata(factor)" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.8" } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/01_basket_composition_data/0000_get_latest_basket_composition.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket.get_latest_position_set().to_frame()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/01_basket_composition_data/0001_get_basket_composition_for_date.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import datetime as dt\n", "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket.get_position_set_for_date(dt.date(2021, 1, 7)).to_frame()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/01_basket_composition_data/0002_get_basket_composition_for_date_range.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import datetime as dt\n", "import pandas as pd\n", "\n", "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "position_sets = basket.get_position_sets(dt.date(2021, 1, 7), dt.date(2021, 1, 7))\n", "position_sets = pd.concat([position_set.to_frame() for position_set in position_sets])" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/01_basket_composition_data/0003_get_full_basket_composition_history.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "\n", "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "position_sets = basket.get_position_sets()\n", "position_sets = pd.concat([position_set.to_frame() for position_set in position_sets])" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/01_basket_composition_data/0004_get_basket_composition_dates.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket.get_position_dates()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/01_basket_composition_data/0005_get_basket_composition_dataset_coverage.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.indices_utils import AssetClass, BasketType, get_constituents_dataset_coverage\n", "from gs_quant.session import Environment, GsSession\n", "\n", "client = 'CLIENT_ID'\n", "secret = 'CLIENT_SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "# Equity Custom Baskets\n", "GSBASKETCONSTITUENTS_COVERAGE = get_constituents_dataset_coverage()\n", "\n", "# Equity Research Baskets\n", "GIRBASKETCONSTITUENTS_COVERAGE = get_constituents_dataset_coverage(basket_type=BasketType.RESEARCH_BASKET)\n", "\n", "# Credit Custom Baskets\n", "GSCREDITBASKETCONSTITUENTS_COVERAGE = get_constituents_dataset_coverage(asset_class=AssetClass.Credit)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/02_basket_pricing_data/0000_get_latest_basket_close_price.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket.get_latest_close_price()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/02_basket_pricing_data/0001_get_basket_close_price_for_date.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import datetime as dt\n", "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket.get_close_price_for_date(dt.date(2021, 1, 7))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/02_basket_pricing_data/0002_get_basket_close_price_for_dates.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import datetime as dt\n", "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket.get_close_prices(dt.date(2021, 1, 7), dt.date(2021, 3, 27))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/02_basket_pricing_data/0003_get_full_basket_close_price_history.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket.get_close_prices()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/03_basket_creation/0000_clone_basket_position_set.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.baskets import Basket\n", "from gs_quant.markets.indices_utils import ReturnType\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data read_user_profile',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "parent_basket = Basket.get('GSMBXXXX')\n", "new_basket = parent_basket.clone()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you'd like, you may now create a new basket in Marquee using this composition. See the Basket Create Tutorial for a more nuanced example/explanation on how to do this (you may skip step 3 in this case)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "new_basket.ticker = 'GSMBCLNE'\n", "new_basket.name = 'Clone of GSMBXXXX'\n", "new_basket.currency = 'USD'\n", "new_basket.return_type = ReturnType.PRICE_RETURN\n", "\n", "new_basket.create()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/03_basket_creation/position_set/0000_create_position_set_using_position_weights.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.position_set import Position, PositionSet\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "positions = [Position(identifier='AAPL UW', weight=0.5), Position(identifier='MSFT UW', weight=0.5)]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "position_set = PositionSet(positions)\n", "position_set.resolve()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/03_basket_creation/position_set/0001_create_position_set_using_position_quantities.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.position_set import Position, PositionSet\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "positions = [Position(identifier='AAPL UW', quantity=100), Position(identifier='MSFT UW', quantity=100)]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "position_set = PositionSet(positions)\n", "position_set.resolve()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/03_basket_creation/position_set/0002_create_position_set_of_equal_weight_from_list_of_identifiers.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.position_set import PositionSet\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "identifiers = ['AAPL UW', 'MSFT UW']" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "position_set = PositionSet.from_list(identifiers)\n", "position_set.resolve()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/03_basket_creation/position_set/0003_equalize_position_set_weights.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.position_set import Position, PositionSet\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "position_set = PositionSet([Position(identifier='AAPL UW', weight=0.3), Position(identifier='MSFT UW', weight=0.7)])\n", "position_set.resolve()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "position_set.equalize_position_weights()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/03_basket_creation/position_set/0004_add_position_to_existing_position_set.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.position_set import Position, PositionSet\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "position_set = PositionSet([Position(identifier='MSFT UW', quantity=200)])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "position_set.positions += Position(\n", " identifier='AAPL UW', weight=800\n", ") # can alternatively specify 'quantity' instead of 'weight'\n", "position_set.resolve()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/03_basket_creation/position_set/0005_create_position_set_from_list_of_dictionaries.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.position_set import PositionSet\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# can alternatively specify 'quantity' instead of 'weight'\n", "positions = [{'identifier': 'AAPL UW', 'weight': 0.5}, {'identifier': 'MSFT UW', 'weight': 0.5}]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "position_set = PositionSet.from_dicts(positions)\n", "position_set.resolve()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/03_basket_creation/position_set/0006_create_position_set_from_dataframe.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.position_set import PositionSet\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Note:** You may create/upload your positions in dataframe format however you'd like (e.g., import from a local excel file). This is a simplified example." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# can alternatively specify 'quantity' instead of 'weight'\n", "positions = [{'identifier': 'AAPL UW', 'weight': 0.5}, {'identifier': 'MSFT UW', 'weight': 0.5}]\n", "positions_df = pd.DataFrame(positions)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "position_set = PositionSet.from_frame(positions_df)\n", "position_set.resolve()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/03_basket_creation/position_set/0007_create_position_set_from_csv.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "\n", "from gs_quant.markets.position_set import PositionSet\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "positions_df = pd.read_csv('positions_data.csv')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "position_set = PositionSet.from_frame(positions_df)\n", "position_set.resolve()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/03_basket_creation/position_set/0008_create_position_set_from_excel.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "\n", "from gs_quant.markets.position_set import PositionSet\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Your excel file must be formatted in one of three ways (column names included). The third example will assign each position equal weight.\n", "\n", "\n", "
1. Position Weight2. Position Quantity3. Position Identifiers
\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
identifierweight
AAPL UW0.4
MSFT UW0.6
\n", "
\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
identifierquantity
AAPL UW100
MSFT UW100
\n", "
\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
identifier
AAPL UW
MSFT UW
\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "positions_df = pd.read_excel('positions_data.xlsx')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "position_set = PositionSet.from_frame(positions_df)\n", "position_set.resolve()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/03_basket_creation/position_set/0009_fetch_position_quantities_from_weights.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from datetime import date\n", "\n", "from gs_quant.markets.position_set import Currency, Position, PositionSet\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "positions = [Position(identifier='AAPL UW', weight=0.3), Position(identifier='MSFT UW', weight=0.7)]\n", "notional = 10000000\n", "date = date(2023, 3, 14)\n", "\n", "position_set = PositionSet(positions=positions, reference_notional=notional, date=date)\n", "position_set.resolve()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "position_set.price(\n", " currency=Currency.EUR, use_unadjusted_close_price=False\n", ") # Currency is an optional parameters and will default to USD" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/03_basket_creation/position_set/0010_fetch_position_weights_from_quantities.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "from datetime import date\n", "\n", "from gs_quant.markets.position_set import Position, PositionSet, PositionSetWeightingStrategy\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "positions = [Position(identifier='AAPL UW', quantity=100), Position(identifier='MSFT UW', quantity=100)]\n", "date = date(2023, 3, 14)\n", "\n", "position_set = PositionSet(positions=positions, date=date)\n", "position_set.resolve()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "position_set.price(\n", " use_unadjusted_close_price=False, weighting_strategy=PositionSetWeightingStrategy.Quantity\n", ") # Weighting strategy is an optional parameter and will default based on the position information if not passed in" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/03_basket_creation/position_set/0011_redistribute_position_weights.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from gs_quant.markets.position_set import Position, PositionSet\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "position_set = PositionSet(\n", " positions=[Position(identifier='AAPL UW', weight=0.3), Position(identifier='MSFT UW', weight=0.3)]\n", ")" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "If you have a weighted position set that does not add up to 1, you can call `redistribute_weights` in order to\n", "redistribute the remaining or additional weights proportionally among each position.\n", "\n", "#### Note: This function assumes the position set is one-sided." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "position_set.redistribute_weights()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/03_basket_creation/position_set/0012_create_historical_position_sets_from_excel.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import datetime as dt\n", "import pandas as pd\n", "\n", "from gs_quant.markets.position_set import Position, PositionSet\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "If you have historical positions that you'd like to bring into Marquee, you should create an individual position\n", "set per each position date. The below examples show the three valid ways to do this (column names/format included)\n", "\n", "### Step 1: Define your file format\n", "#### Option 1: Create historical position sets by weight\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
identifierweightdate
AAPL UW0.42022-06-03
MSFT UW0.62022-06-03
AAPL UW0.42023-01-04
MSFT UW0.22023-01-04
IBM UN0.42023-01-04
" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "columns = ['identifier', 'weight', 'date']\n", "equalize = False" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "#### Option 2: Create historical position sets by quantity\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
identifierquantitydate
AAPL UW1002022-06-03
MSFT UW1002022-06-03
AAPL UW2002023-01-04
MSFT UW3002023-01-04
IBM UN2002023-01-04
" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "columns = ['identifier', 'quantity', 'date']\n", "equalize = False" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "#### Option 3: Create equally-weighted historical position sets\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
identifierdate
AAPL UW2022-06-03
MSFT UW2022-06-03
AAPL UW2023-01-04
MSFT UW2023-01-04
IBM UN2023-01-04
" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "columns = ['identifier', 'date']\n", "equalize = True" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Step 2: Upload your excel file and convert your positions into position sets" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "positions_data = pd.read_excel('positions_data.xlsx', engine='openpyxl')\n", "all_positions = positions_data[columns]\n", "\n", "positions_dict, position_sets = {}, []\n", "for i, row in all_positions.iterrows():\n", " date = row.get('date')\n", " if date not in positions_dict:\n", " positions_dict[date] = set()\n", " positions_dict[date].add(\n", " Position(identifier=row.get('identifier'), weight=row.get('weight', None), quantity=row.get('quantity', None))\n", " )\n", "\n", "for date in positions_dict:\n", " position_date = dt.datetime.strptime(str(date), '%Y-%m-%d %H:%M:%S').date()\n", " position_set = PositionSet(positions=positions_dict[date], date=position_date)\n", " if equalize:\n", " position_set.equalize_position_weights()\n", " position_sets.append(position_set)\n", "\n", "print(position_sets)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/04_basket_corporate_actions_data/0000_get_full_basket_corporate_actions_history.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data read_user_profile',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket.get_corporate_actions()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/04_basket_corporate_actions_data/0001_get_basket_corporate_actions_for_dates.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import datetime as dt\n", "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data read_user_profile',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket.get_corporate_actions(start=dt.date(2021, 1, 7), end=dt.date(2021, 3, 27))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/04_basket_corporate_actions_data/0002_get_basket_upcoming_corporate_actions.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import datetime as dt\n", "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data read_user_profile',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket.get_corporate_actions(start=dt.date.today())" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/04_basket_corporate_actions_data/0003_get_specific_corporate_actions_types.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.baskets import Basket\n", "from gs_quant.markets.indices_utils import CorporateActionType\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data read_user_profile',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You may choose any combination of the following corporate action types:\n", "\n", "* **Aquisition:** CorporateActionType.*ACQUISITION*\n", "* **Cash Dividend:** CorporateActionType.*CASH_DIVIDEND*\n", "* **Identifier Change:** CorporateActionType.*IDENTIFIER_CHANGE*\n", "* **Rights Issue:** CorporateActionType.*RIGHTS_ISSUE*\n", "* **Share Change:** CorporateActionType.*SHARE_CHANGE*\n", "* **Special Dividend:** CorporateActionType.*SPECIAL_DIVIDEND*\n", "* **Spin Off:** CorporateActionType.*SPIN_OFF*\n", "* **Stock Dividend:** CorporateActionType.*STOCK_DIVIDEND*\n", "* **Stock Split:** CorporateActionType.*STOCK_SPLIT*" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket.get_corporate_actions(ca_type=[CorporateActionType.IDENTIFIER_CHANGE, CorporateActionType.RIGHTS_ISSUE])" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/05_basket_fundamentals_data/0000_get_basket_fundamentals_history.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data read_user_profile',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket.get_fundamentals() # default period is one year, default period direction is forward" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/05_basket_fundamentals_data/0001_get_basket_fundamentals_for_dates.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import datetime as dt\n", "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data read_user_profile',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket.get_fundamentals(start=dt.date(2021, 1, 7), end=dt.date(2021, 3, 27))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/05_basket_fundamentals_data/0001_get_basket_fundamentals_specific_period_and_direction.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import datetime as dt\n", "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_financial_data read_product_data read_content',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket.get_fundamentals(start=dt.date(2021, 1, 7), end=dt.date(2021, 3, 27))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/05_basket_fundamentals_data/0002_get_basket_fundamentals_for_date_range.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.baskets import Basket\n", "from gs_quant.markets.indices_utils import FundamentalMetricPeriod, FundamentalMetricPeriodDirection\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_financial_data read_product_data read_content',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You may choose one of the following periods:\n", "\n", "* **1 year:** FundamentalMetricPeriod.*ONE_YEAR*\n", "* **2 years:** FundamentalMetricPeriod.*TWO_YEARS*\n", "* **3 years:** FundamentalMetricPeriod.*THREE_YEARS*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You may choose one of the following period directions:\n", "\n", "* **Forward:** FundamentalMetricPeriodDirection.*FORWARD*\n", "* **Trailing:** FundamentalMetricPeriodDirection.*TRAILING*" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket.get_fundamentals(period=FundamentalMetricPeriod.TWO_YEARS, direction=FundamentalMetricPeriodDirection.TRAILING)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/05_basket_fundamentals_data/0002_get_basket_fundamentals_specific_period_and_direction.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.data.fields import DataMeasure\n", "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data read_user_profile',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You may choose one of the following periods:\n", "\n", "* **1 year:** DataMeasure.*ONE_YEAR*\n", "* **2 years:** DataMeasure.*TWO_YEARS*\n", "* **3 years:** DataMeasure.*THREE_YEARS*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You may choose one of the following period directions:\n", "\n", "* **Forward:** DataMeasure.*FORWARD*\n", "* **Trailing:** DataMeasure.*TRAILING*" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket.get_fundamentals(period=DataMeasure.TWO_YEARS, direction=DataMeasure.TRAILING)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/05_basket_fundamentals_data/0003_get_specific_basket_fundamentals_metrics.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.data.fields import DataMeasure\n", "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data read_user_profile',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You may choose any combinations of the following metrics:\n", "\n", "* **Dividend Yield:** DataMeasure.*DIVIDEND_YIELD*\n", "* **Earnings per Share:** DataMeasure.*EARNINGS_PER_SHARE*\n", "* **Earnings per Share Positive:** DataMeasure.*EARNINGS_PER_SHARE_POSITIVE*\n", "* **Net Debt to EBITDA:** DataMeasure.*NET_DEBT_TO_EBITDA*\n", "* **Price to Book:** DataMeasure.*PRICE_TO_BOOK*\n", "* **Price to Cash:** DataMeasure.*PRICE_TO_CASH*\n", "* **Price to Earnings:** DataMeasure.*PRICE_TO_EARNINGS*\n", "* **Price to Earnings Positive:** DataMeasure.*PRICE_TO_EARNINGS_POSITIVE*\n", "* **Price to Sales:** DataMeasure.*PRICE_TO_SALES*\n", "* **Return on Equity:** DataMeasure.*RETURN_ON_EQUITY*\n", "* **Sales per Share:** DataMeasure.*SALES_PER_SHARE*" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket.get_fundamentals(metrics=[DataMeasure.PRICE_TO_CASH, DataMeasure.SALES_PER_SHARE])" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/06_basket_reports/0000_create_new_factor_risk_report.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_user_profile',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket.add_factor_risk_report(\n", " risk_model_id='AXUS4M', fx_hedged=True\n", ") # substitute desired risk model and fx_hedged value" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/06_basket_reports/0001_delete_existing_factor_risk_report.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_user_profile',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket.delete_factor_risk_report(\n", " risk_model_id='AXUS4M'\n", ") # substitute risk model id for the one corresponding to the report you'd like to delete" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/06_basket_reports/0002_get_existing_factor_risk_report.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket.get_factor_risk_report(\n", " risk_model_id='AXUS4M'\n", ") # substitute risk model id for the one corresponding to the report you'd like to retrieve" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/06_basket_reports/0003_get_all_existing_basket_reports.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket.get_reports()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/06_basket_reports/0004_get_status_of_all_existing_basket_reports.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket.get_status_of_reports()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/06_basket_reports/0005_poll_status_of_most_recent_basket_create_report.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_user_profile',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket.poll_status(\n", " timeout=300, step=20\n", ") # timeout/step are optional - default behavior will be to check status every 30 sec for <= 10 min" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/06_basket_reports/0006_poll_report_status_using_report_id.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket.poll_report(\n", " report_id='R1234567890', timeout=300, step=20\n", ") # timeout/step are optional - default behavior will be to check status every 30 sec for <= 10 min" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/07_basket_permissions/0000_get_basket_permissions.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "### What are the four levels of basket permissions?\n", "\n", "1. **Admin:** Basket admins can view all basket information, edit metadata, submit requests to update basket composition, and permission other users to the basket\n", "2. **Edit:** Editors may view basket data and edit details such as name, description, etc.\n", "3. **Rebalance:** Rebalance permissions enable a user to view basket data and submit and approve rebalance submissions\n", "4. **View:** Viewers are able to see most basket information, but are not able to modify the basket in any way" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_user_profile',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket.entitlements.to_frame()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/07_basket_permissions/0001_permission_application_to_basket.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.entities.entitlements import User\n", "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(\n", " Environment.PROD, client_id=client, client_secret=secret, scopes=('read_user_profile modify_product_data',)\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First, retrieve the user object by inserting the app id that you'd like to permission. Then, you may update the entitlements block corresponding to the level of permissioning you'd like to modify (e.g., *'entitlements.admin'*, *'entitlements.edit'*, etc.)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "application = User.get(user_id='application_id')\n", "\n", "basket.entitlements.view.users += [application] # update the entitlements block 'users' property\n", "\n", "basket.update()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/07_basket_permissions/0002_permission_user_to_basket_by_email.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.entities.entitlements import User\n", "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_user_profile ',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First, retrieve the user object by inputting the user email that you'd like to permission. Then, you may update the entitlements block corresponding to the level of permissioning you'd like to modify (e.g., *'entitlements.view'*, *'entitlements.rebalance'*, etc.)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "user = User.get(email='user_email@example.com')\n", "\n", "basket.entitlements.admin.users += [user] # update the entitlements block 'users' property\n", "\n", "basket.update()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/07_basket_permissions/0003_permission_group_to_basket.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.entities.entitlements import Group\n", "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(\n", " Environment.PROD, client_id=client, client_secret=secret, scopes=('read_user_profile modify_product_data',)\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First, retrieve the group object by inputting the group id that you'd like to permission. Then, you may update the entitlements block corresponding to the level of permissioning you'd like to modify (e.g., *'entitlements.view'*, *'entitlements.rebalance'*, etc.)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "group = Group.get(group_id='example.group')\n", "\n", "basket.entitlements.admin.groups += [group] # update the entitlements block 'groups' property\n", "\n", "basket.update()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/07_basket_permissions/0004_remove_basket_permissions.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.entities.entitlements import User\n", "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(\n", " Environment.PROD, client_id=client, client_secret=secret, scopes=('read_user_profile modify_product_data',)\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX') # substitute input with any identifier for a basket" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First, retrieve the user object by inputting the user id that you'd like to unpermission (you may alternatively retrieve a user by email, or can retrieve a group instead. Refer to the other examples in this folder for examples of this). Then, you may update the entitlements block corresponding to the level of permissioning you'd like to modify (e.g., *'entitlements.view'*, *'entitlements.rebalance'*, etc.)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "user = User.get(user_id='user_id')\n", "\n", "basket.entitlements.admin.users.remove(user) # update the entitlements block 'groups' property\n", "\n", "basket.update()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/07_basket_permissions/0005_get_your_permissioned_baskets.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.indices_utils import get_my_baskets\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "get_my_baskets()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/08_flagship_baskets/0000_get_flagship_baskets.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.indices_utils import get_flagship_baskets\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "get_flagship_baskets()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/08_flagship_baskets/0001_get_flagship_baskets_on_date.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import datetime as dt\n", "\n", "from gs_quant.markets.indices_utils import get_flagship_baskets\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "get_flagship_baskets(as_of=dt.datetime(2021, 1, 7, 11, 0, 0))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/08_flagship_baskets/0002_get_flagship_baskets_containing_assets.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.indices_utils import get_flagships_with_assets\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "get_flagships_with_assets(\n", " identifiers=['AAPL UW', 'MSFT UW']\n", ") # insert list of assets using any common identifier (bbid, ric, etc.)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/08_flagship_baskets/0003_get_flagship_baskets_containing_assets_on_date.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import datetime as dt\n", "\n", "from gs_quant.markets.indices_utils import get_flagships_with_assets\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "get_flagships_with_assets(\n", " identifiers=['AAPL UW, MSFT UW'], as_of=dt.date(2021, 1, 7)\n", ") # insert list of assets using any common identifier (bbid, ric, etc.)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/08_flagship_baskets/0004_get_flagship_baskets_prices.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.indices_utils import get_flagships_performance\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "get_flagships_performance()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/08_flagship_baskets/0005_get_flagship_baskets_prices_for_date_range.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import datetime as dt\n", "\n", "from gs_quant.markets.indices_utils import get_flagships_performance\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "get_flagships_performance(start=dt.date(2021, 1, 7), end=dt.date(2021, 3, 27))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/08_flagship_baskets/0006_get_flagship_baskets_compositions.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.indices_utils import get_flagships_constituents\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "get_flagships_constituents()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/08_flagship_baskets/0007_get_flagship_baskets_compositions_for_date_range.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import datetime as dt\n", "\n", "from gs_quant.markets.indices_utils import get_flagships_constituents\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "get_flagships_constituents(start=dt.date(2021, 1, 7), end=dt.date(2021, 3, 27))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/08_flagship_baskets/0008_filter_flagship_baskets_by_basket_type.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.indices_utils import *\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You may choose any combination of the following basket types:\n", "\n", "* **Custom Basket:** BasketType.*CUSTOM_BASKET*\n", "* **Research Basket:** BasketType.*RESEARCH_BASKET*\n", "\n", "These options will work with any of the following functions:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "get_flagship_baskets(basket_type=[BasketType.CUSTOM_BASKET])\n", "\n", "get_flagships_with_assets(identifiers=['AAPL UW'], basket_type=[BasketType.CUSTOM_BASKET])\n", "\n", "get_flagships_performance(basket_type=[BasketType.RESEARCH_BASKET])\n", "\n", "get_flagships_constituents(basket_type=[BasketType.RESEARCH_BASKET])" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/08_flagship_baskets/0009_filter_flagship_baskets_by_region.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.indices_utils import *\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You may choose any combination of the following regions:\n", "\n", "* **Americas:** Region.*AMERICAS*\n", "* **Asia:** Region.*ASIA*\n", "* **EM:** Region.*EM*\n", "* **Europe:** Region.*EUROPE*\n", "* **Global:** Region.*GLOBAL*\n", "\n", "These options will work with any of the following functions:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "get_flagship_baskets(region=[Region.ASIA])\n", "\n", "get_flagships_with_assets(identifiers=['AAPL UW'], region=[Region.AMERICAS])\n", "\n", "get_flagships_performance(region=[Region.EUROPE, Region.GLOBAL])\n", "\n", "get_flagships_constituents(region=[Region.EM])" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/08_flagship_baskets/0010_filter_flagship_baskets_by_styles.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.indices_utils import *\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You may choose any combination of the following styles:\n", "\n", "#### Custom Basket Styles\n", "* **Ad Hoc Desk Work:** CustomBasketStyles.*AD_HOC_DESK_WORK*\n", "* **Client Constructed/Wrapper:** CustomBasketStyles.*CLIENT_CONSTRUCTED_WRAPPER*\n", "* **Consumer:** CustomBasketStyles.*CONSUMER*\n", "* **Energy:** CustomBasketStyles.*ENERGY*\n", "* **Enhanced Index Solutions:** CustomBasketStyles.*ENHANCED_INDEX_SOLUTIONS*\n", "* **ESG:** CustomBasketStyles.*ESG*\n", "* **Factors:** CustomBasketStyles.*FACTORS*\n", "* **Financials:** CustomBasketStyles.*FINANCIALS*\n", "* **Flagship:** CustomBasketStyles.*FLAGSHIP*\n", "* **Geographic:** CustomBasketStyles.*GEOGRAPHIC*\n", "* **Growth:** CustomBasketStyles.*GROWTH*\n", "* **Health Care:** CustomBasketStyles.*HEALTHCARE*\n", "* **Hedging:** CustomBasketStyles.*HEDGING*\n", "* **Industrials:** CustomBasketStyles.*INDUSTRIALS*\n", "* **Materials:** CustomBasketStyles.*MATERIALS*\n", "* **Momentum:** CustomBasketStyles.*MOMENTUM*\n", "* **PIPG:** CustomBasketStyles.*PIPG*\n", "* **Sectors/Industries:** CustomBasketStyles.*SECTORS_INDUSTRIES*\n", "* **Size:** CustomBasketStyles.*SIZE*\n", "* **Structured One Delta:** CustomBasketStyles.*STRUCTURED_ONE_DELTA*\n", "* **Thematic:** CustomBasketStyles.*THEMATIC*\n", "* **TMT:** CustomBasketStyles.*TMT*\n", "* **Utilities:** CustomBasketStyles.*UTILITIES*\n", "* **Value:** CustomBasketStyles.*VALUE*\n", "* **Volatility:** CustomBasketStyles.*VOLATILITY*\n", "\n", "#### Research Basket Styles\n", "* **Asia ex-Japan:** ResearchBasketStyles.*ASIA_EX_JAPAN*\n", "* **Equity Thematic:** ResearchBasketStyles.*EQUITY_THEMATIC*\n", "* **Europe:** ResearchBasketStyles.*EUROPE*\n", "* **Fund Ownership:** ResearchBasketStyles.*FUND_OWNERSHIP*\n", "* **Fundamentals:** ResearchBasketStyles.*FUNDAMENTALS*\n", "* **FX/Oil:** ResearchBasketStyles.*FX_OIL*\n", "* **Geographical Exposure:** ResearchBasketStyles.*GEOGRAPHICAL_EXPOSURE*\n", "* **Hedge Fund:** ResearchBasketStyles.*HEDGE_FUND*\n", "* **Investment Profile (IP) Factors:** ResearchBasketStyles.*IP_FACTORS*\n", "* **Japan:** ResearchBasketStyles.*JAPAN*\n", "* **Macro:** ResearchBasketStyles.*MACRO*\n", "* **Macro Slice/Styles:** ResearchBasketStyles.*MACRO_SLICE_STYLES*\n", "* **Mutual Fund:** ResearchBasketStyles.*MUTUAL_FUND*\n", "* **Positioning:** ResearchBasketStyles.*POSITIONING*\n", "* **Portfolio Strategy:** ResearchBasketStyles.*PORTFOLIO_STRATEGY*\n", "* **Risk & Liquidity:** ResearchBasketStyles.*RISK_AND_LIQUIDITY*\n", "* **Sector:** ResearchBasketStyles.*SECTOR*\n", "* **Shareholder Return:** ResearchBasketStyles.*SHAREHOLDER_RETURN*\n", "* **Style, Factor and Fundamental:** ResearchBasketStyles.*STYLE_FACTOR_AND_FUNDAMENTAL*\n", "* **Style/Themes:** ResearchBasketStyles.*STYLES_THEMES*\n", "* **Tactical Research:** ResearchBasketStyles.*TACTICAL_RESEARCH*\n", "* **Thematic:** ResearchBasketStyles.*THEMATIC*\n", "* **US:** ResearchBasketStyles.*US*\n", "* **Wavefront Components:** ResearchBasketStyles.*WAVEFRONT_COMPONENTS*\n", "* **Wavefront Pairs:** ResearchBasketStyles.*WAVEFRONT_PAIRS*\n", "* **Wavefronts:** ResearchBasketStyles.*WAVEFRONTS*\n", "\n", "These options will work with any of the following functions:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "get_flagship_baskets(styles=[CustomBasketStyles.FACTORS, ResearchBasketStyles.WAVEFRONTS])\n", "\n", "get_flagships_with_assets(\n", " identifiers=['AAPL UW'],\n", " region=[Region.AMERICAS],\n", " styles=[CustomBasketStyles.FACTORS, ResearchBasketStyles.WAVEFRONTS],\n", ")\n", "\n", "get_flagships_performance(\n", " region=[Region.EUROPE, Region.GLOBAL], styles=[CustomBasketStyles.FACTORS, ResearchBasketStyles.WAVEFRONTS]\n", ")\n", "\n", "get_flagships_constituents(region=[Region.EM], styles=[CustomBasketStyles.FACTORS, ResearchBasketStyles.WAVEFRONTS])" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/examples/08_flagship_baskets/0011_filter_flagship_baskets_by_ticker.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from gs_quant.markets.indices_utils import *\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "tickers = ['GSTHHVIP', 'GSCBLMOM '] # Insert list of basket tickers\n", "\n", "get_flagship_baskets(ticker=tickers)\n", "\n", "get_flagships_with_assets(identifiers=['AAPL UW'], ticker=tickers)\n", "\n", "get_flagships_performance(ticker=tickers)\n", "\n", "get_flagships_constituents(ticker=tickers)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/06_baskets/tutorials/Basket Backcast.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": true, "pycharm": { "name": "#%% md\n" } }, "source": [ "# Uploading Custom History when Creating a Custom Basket\n", "\n", "When creating a custom basket, Marquee by default will publish up to five years of pricing and composition history based\n", "on the composition entered during creation. If you choose to bypass this behavior by setting the `default_backcast`\n", "parameter to `False`, you may upload your own custom history any time after the basket has been created. This tutorial\n", "will show you how to submit a basket backcast, including best practices to avoid mapping errors, and more!\n", "\n", "**Note:** You must specify this setting during creation in order to perform the below steps. If you'd like to submit\n", "custom history for a basket that has already been created and did not originally select this option, you'll need to\n", "create a new basket." ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 1: Authenticate & Initialize your session\n", "\n", "First you will import the necessary modules and add your client id and client secret." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "import datetime as dt\n", "\n", "from gs_quant.api.gs.reports import GsReportApi\n", "from gs_quant.markets.baskets import Basket\n", "from gs_quant.markets.position_set import Currency, Position, PositionSet, PositionSetWeightingStrategy\n", "from gs_quant.session import Environment, GsSession\n", "\n", "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('modify_product_data',))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 2: Extract your positions into dated position sets\n", "\n", "Next you'll need to create a PositionSet for each date your basket had position changes historically. Examples of\n", "position changes are anything that affect the price or composition of your basket. For example, periodic weighted\n", "rebalances, identifier changes or other corporate actions, adds/drops, etc.\n", "\n", "When Marquee goes to upload your history, we'll convert your position weights into quantities and carry those forward\n", "until the next date with position changes occurs. If you do not have position weights and only have quantities at the\n", "time of upload, make sure to follow step #4.\n", "\n", "For the sake of simplicity in this tutorial, we'll show a very basic example of position sets that may represent\n", "a basket's composition history. However based on your own personal Input/Output preferences and setup, you'll likely want to make\n", "some adjustments in order to aggregate your positions into unique PositionSet objects per date. See\n", "[position_set examples](../examples/03_basket_creation/position_set/0012_create_historical_position_sets_from_excel.ipynb)\n", "for alternate methods." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "positions = [Position(identifier='AAPL UW', weight=0.5), Position(identifier='MSFT UW', weight=0.5)]\n", "\n", "pos_set_1 = PositionSet(positions, dt.date(2021, 6, 3))\n", "pos_set_2 = PositionSet(positions, dt.date(2022, 1, 2))\n", "pos_set_3 = PositionSet(positions, dt.date(2022, 6, 1))\n", "pos_set_4 = PositionSet(positions, dt.date(2023, 1, 4))\n", "\n", "position_sets = [pos_set_1, pos_set_2, pos_set_3, pos_set_4]" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 3: Resolve your position sets and check for unmapped assets\n", "\n", "Now that you have your position sets created, it's time to resolve your positions historically. This will help confirm\n", "that each asset identifier can be mapped as of the provided position date.\n", "\n", "If you face any mapping issues, double check that your identifiers are valid. Consider identifier changes (e.g.,\n", "FB UW -> META UW), listed status, IPOs, etc. If you believe you have corrected all assets with these issues and are still\n", "experiencing problems mapping your positions, you may remove these identifiers or email the\n", "[baskets support team](mailto:gs-marquee-baskets-support@gs.com?subject=Failed%20To%20Map%20Positions%20for%20Basket%20Backcast)\n", "for assistance." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "for position_set in position_sets:\n", " position_set.resolve()\n", " if position_set.unresolved_positions is not None and len(position_set.unresolved_positions):\n", " print(\n", " f'Error resolving assets on {position_set.date.strftime(\"%Y-%m-%d\")}: {position_set.unresolved_positions}'\n", " )\n", " \"\"\" Uncomment the below to removed unresolved positions from your position set \"\"\"\n", " # position_set.remove_unresolved_positions()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 4 (Optional): Convert position quantities to weights\n", "\n", "Basket backcasts require each position to have a specified weight. If you only have historical quantities, you may\n", "call the `price` function on each position set in order to extract these weights via Marquee. Otherwise you may skip\n", "this step.\n", "\n", "Similar to step #3, you can double check if any assets are unable to be priced and either remove these from your\n", "position set or contact the [baskets support team](mailto:gs-marquee-baskets-support@gs.com?subject=Failed%20To%20Price%20Positions%20for%20Basket%20Backcast)\n", "for help." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "currency = Currency.USD # replace with desired currency\n", "\n", "for position_set in position_sets:\n", " position_set.price(\n", " currency=currency, use_unadjusted_close_price=False, weighting_strategy=PositionSetWeightingStrategy.Quantity\n", " )\n", " if position_set.unpriced_positions is not None and len(position_set.unpriced_positions):\n", " print(f'Error pricing assets on {position_set.date.strftime(\"%Y-%m-%d\")}: {position_set.unpriced_positions}')\n", " \"\"\" Uncomment the below to removed unpriced positions from your position set \"\"\"\n", " # position_set.remove_unpriced_positions()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Quick Tip!\n", "If you had to remove positions from any position set for one of the reasons described above, the total weight of\n", "each position might no longer add up to 1. If this is the case, you can call `redistribute_weights` on any misweighted\n", "position sets, which will redistribute the remaining weights proportionally among each position." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "for position_set in position_sets:\n", " position_set.redistribute_weights()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 5: Fetch your basket and upload your history\n", "\n", "Once your positions have all been mapped and assigned weights, you're ready to submit to Marquee! Fetch your basket\n", "and call `upload_position_history` using the position sets from above." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX')\n", "basket.upload_position_history(position_sets)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 6 (Optional): Reschedule or Cancel failed report jobs\n", "\n", "There are several cases that can cause a backcast report to fail, including long history length, large number of\n", "positions/rebalance frequency, historical errors for assets in Marquee, and more. The above script will catch as\n", "many issues as possible prior to upload but there are sometimes still failures that require investigation from the\n", "support team to fix.\n", "\n", "If you find that your backcasts are failing, you can reschedule any failed report jobs using the below snippet to catch\n", "for intermittent errors such as timeouts or memory issues on Marquee side. If the errors persist, you may reach out\n", "to the [baskets support team](mailto:gs-marquee-baskets-support@gs.com) for further information.\n", "\n", "If your backcast reports are failing due a non-Marquee issue and you need to re-upload your position history, you'll\n", "need to cancel any failed report jobs in order to resubmit your adjusted positions. Then you may repeat steps #1-5." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "backcast_reports = GsReportApi.get_reports(position_source_id=basket.get_marquee_id(), report_type='Basket Backcast')\n", "report_ids, report_job_ids = [r.id for r in backcast_reports], []\n", "for report_id in report_ids:\n", " report_jobs = GsReportApi.get_report_jobs(report_id)\n", " report_job_ids = [rj.get('id') for rj in report_jobs if rj.get('status') == 'error']\n", "\n", "\"\"\" Use this to RESCHEDULE failed report jobs \"\"\"\n", "# for report_job in report_job_ids:\n", "# print(f'Rescheduling report job {report_job.get(\"id\")}')\n", "# GsReportApi.reschedule_report_job(report_job.get('id'))\n", "\n", "\"\"\" Use this to CANCEL failed report jobs \"\"\"\n", "# for report_job in report_job_ids:\n", "# print(f'Cancelling report job {report_job.get(\"id\")}')\n", "# GsReportApi.cancel_report_job(report_job.get('id'))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/06_baskets/tutorials/Basket Create.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Creating a Custom Basket\n", "\n", "Welcome to the basket creation tutorial! Marquee allows you to create your own tradable basket ticker and manage it through the platform. When you create a basket it automatically gets published to Marquee, and you may also publish it to Bloomberg, Reuters, and Factset. This basket will tick live.\n", "\n", "Creating a basket requires enhanced levels of permissioning. If you are not yet permissioned to create baskets please reach out to your sales coverage or to the [Marquee sales team](mailto:gs-marquee-sales@gs.com)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 1: Authenticate & Initialize your session\n", "\n", "First you will import the necessary modules and add your client id and client secret." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "\n", "from gs_quant.markets.baskets import Basket\n", "from gs_quant.markets.indices_utils import ReturnType\n", "from gs_quant.markets.position_set import Position, PositionSet\n", "from gs_quant.session import Environment, GsSession\n", "\n", "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(\n", " Environment.PROD,\n", " client_id=client,\n", " client_secret=secret,\n", " scopes=('read_product_data read_user_profile modify_product_data',),\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 2: Define your basket metadata, publishing options, pricing options, & return type\n", "\n", "In this step you are going to define all the specifications needed to create your basket. First, instantiate an empty basket object and then you may begin defining it's settings. The below list contains all the parameters you may set.\n", "\n", "| Parameter Name | Required? | Default Value | Description |\n", "|:-------------------|:-----------|:--------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n", "|name |**Required**|-- | Display name of the basket |\n", "|ticker |**Required**|-- | Associated 8-character basket identifier (must be prefixed with \"GS\" in order to publish to Bloomberg). If you would like to request a custom prefix instead of using the default GSMB prefix please reach out to the [baskets team](mailto:gs-marquee-baskets@gs.com) |\n", "|currency |**Required**|-- | Denomination you want your basket to tick in. This can not be changed once your basket has been created |\n", "|return_type |**Required**|-- | Determines the index calculation methodology with respect to dividend reinvestment. One of Price Return, Gross Return, Total Return |\n", "|position_set |**Required**|-- | Information of constituents associated with the basket. You may provide the weight or quantity for each position. If neither is provided we will distribute the total weight evenly among each position. Please also note that any fractional shares will be rounded up to whole numbers. |\n", "|cash_reinvestment_treatment|Optional|Reinvest At Open| How to treat cash acquisitions, regular dividends, and special dividends for basket underliers. |\n", "|description |Optional |-- | Free text description of basket. Description provided will be indexed in the search service for free text relevance match. |\n", "|divisor |Optional |-- | Divisor to be applied to the overall position set. You need not set this unless you want to change the divisor to a specific number, which will in turn change the basket price (current notional/divisor). This might impact price continuity. |\n", "|initial_price |Optional |100 | Initial price the basket should start ticking at |\n", "|target_notional |Optional |10,000,000 | Target notional for the position set |\n", "|publish_to_bloomberg|Optional |True | If you'd like us to publish your basket to Bloomberg |\n", "|publish_to_reuters |Optional |False | If you'd like us to publish your basket to Reuters |\n", "|publish_to_factset |Optional |False | If you'd like us to publish your basket to Factset |\n", "|historical_methodology|Optional |Backcast | Preferred methodology to be applied when publishing basket history.
  • **Backcast**: Apply current composition backwards up to 5 years, assuming constituent shares remained constant
  • **Backtest**: Reset composition based on the selected criteria and rebalance frequency over selected period of time. Must specify these options in `backtest_parameters`.
  • **Custom History**: Upload your own basket history after the basket has been created. *Note: Previously this was indicated by setting* `default_backcast = False`
|\n", "|backtest_parameters |Optional |-- | Rules applied when performing a historical backtest.
  • **Weighting Mechanism**: On each rebalance date, whether to reset positions to have equal weights or restore based on original position set values.
  • **Time Horizon**: Time horizon for which to generate historical backtest. (5 years is the only supported option at this time.)
  • **Frequency**: Frequency at which to recalculate position shares (Weekly, Monthly, Quarterly, Annually.)
|\n", "|reweight |Optional |False | If you'd like us to reweight positions if input weights don't add up to 1 upon submission |\n", "|weighting_strategy |Optional |-- | Strategy used to price the position set (will be inferred if not indicated). One of Equal, Market Capitalization, Quantity, Weight |\n", "|allow_ca_restricted_assets|Optional|False | Allow your basket to have constituents that will not be corporate action adjusted in the future (You will recieve a message indicating if this action is needed when attempting to create your basket) |\n", "|allow_limited_access_assets|Optional|False | Allow basket to have constituents that GS has limited access to (You will recieve a message indicating if this action is needed when attempting to create your basket) |" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "my_basket = Basket()\n", "\n", "my_basket.name = 'My New Custom Basket'\n", "my_basket.ticker = 'GSMBXXXX'\n", "my_basket.currency = 'USD'\n", "my_basket.publish_to_reuters = True\n", "\n", "my_basket.return_type = ReturnType.PRICE_RETURN" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Quick Tip!\n", "At any point, you may call the get_details() method on your basket, which will print the current state of the basket object. We recommend doing this throughout the creation process to ensure there are not any discrepancies between your preferences and the current basket settings." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "my_basket.get_details() # prints out each parameters on the basket" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 3: Define your basket's composition\n", "\n", "Now you will decide what your basket composition is. If you'd like to include several positions, you may define the composition using your preferred input method (e.g., uploading an excel file) but it must then be converted to a dictionary or pandas dataframe.\n", "\n", "Your dataframe must have a column entitled 'identifier', which holds any commonly accepted identifier such as BloombergId, Cusip, Ticker, etc. for each position. You may also have a column entitled 'quantity' to store the number of shares for each position, or a column named 'weight' to represent the weight of each. If the second column is missing, we will later assign equal weight to each position when you submit your basket for creation.\n", "\n", "After uploading your composition and converting it to a dataframe, make sure to rename your columns to match our specifications if they aren't in the correct format already, and then you may use it to create a valid Position Set. You should then call get_positions() to make sure that your positions have all been mapped correctly, and can then store this composition on the basket." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "positions_df = pd.read_excel('path/to/excel.xlsx') # example of uploading composition from excel document\n", "positions_df.columns = ['identifier', 'weight'] # replace weight column with 'quantity' if using number of shares\n", "position_set = PositionSet.from_frame(positions_df)\n", "position_set.resolve()\n", "\n", "position_set.get_positions() # returns a dataframe with each position's identifier, name, Marquee unique identifier, and weight/quantity\n", "\n", "my_basket.position_set = position_set" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Quick Tip!\n", "Wanting to quickly add one or two positions to a position set without having to modify your dataframe? You can add to a position set by inputting an identifier and an optional weight/quantity to a Position object and modify the position set directly, like below. Refer to the [position_set examples](../examples/03_basket_creation/position_set/0004_add_position_to_existing_position_set.ipynb) section for more tips like this!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "positions_to_add = [Position('AAPL UW', weight=0.1), Position('MSFT UW', weight=0.1)]\n", "position_set.positions += positions_to_add\n", "\n", "my_basket.position_set = position_set" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 4: Create your basket\n", "\n", "Once you've ensured that your basket has been set up to your satisfaction, you're ready to officially create and publish to Marquee! Once you call create on your new basket, you may poll its status to make sure that it has processed successfully. This will check the report status every 30 seconds for 10 minutes by default, but you can override this option if you prefer as shown below. If you'd like to view your basket on the Marquee site, you can retrieve the link to your page by calling get_url()." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "my_basket.get_details() # we highly recommend verifying the basket state looks correct before calling create!\n", "\n", "my_basket.create()\n", "\n", "my_basket.poll_status(\n", " timeout=120, step=20\n", ") # optional: constantly checks create status until report succeeds, fails, or the poll times out (this example checks every 20 seconds for 2 minutes)\n", "\n", "my_basket.get_url() # will return a url to your Marquee basket page ex. https://marquee.gs.com/s/products/MA9B9TEMQ2RW16K9/summary" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 5: Update your basket's entitlements\n", "\n", "The application you use to create your basket will initially be the only one permissioned to view, edit, and submit rebalance requests. If you'd like to entitle other users or groups with view or admin access, you may update your basket's permissions at any time.\n", "\n", "In order to add or remove permissions for a specific user, you will need either their Marquee user id or email. You may also permission groups using their group id. See the snippet below, or refer to the [baskets permissions examples](../examples/07_basket_permissions/0001_permission_application_to_basket.ipynb) for more options." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.entities.entitlements import User\n", "\n", "user = User.get(user_id='application_id')\n", "basket.entitlements.view.users += [\n", " user\n", "] # update the desired entitlements block ('edit', 'admin', etc) 'users' property\n", "\n", "basket.update()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### You're all set, Congrats! What's next?\n", "\n", "* [How do I upload my basket's historical composition?](./Basket%20Backcast.ipynb)\n", "\n", "* [How do I retrieve composition data for my basket?](../examples/01_basket_composition_data/0000_get_latest_basket_composition.ipynb)\n", "\n", "* [How do I retrieve pricing data for my basket?](../examples/02_basket_pricing_data/0000_get_latest_basket_close_price.ipynb)\n", "\n", "* [How do I change my basket's current composition?](./Basket%20Rebalance.ipynb)\n", " \n", "* [How do I make other changes to my basket (name, description, etc.)?](./Basket%20Edit.ipynb)\n", "\n", "* [What else can I do with my basket?](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.markets.baskets.Basket.html#gs_quant.markets.baskets.Basket)\n", "\n", "Other questions? Reach out to the [baskets team](mailto:gs-marquee-baskets-support@gs.com) anytime!" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/tutorials/Basket Edit.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Editing a Custom Basket\n", "\n", "Editing a custom basket is very similar to the creation process. You simply fetch your basket, set its properties to match your preferences, and call update! Please note that not all settings are modifiable after creation (see the list of options below). Additionally, if you'd like to adjust your basket's composition or price settings, please refer to the [rebalance tutorial](./Basket%20Rebalance.ipynb) instead." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 1: Authenticate & Initialize your session\n", "\n", "First you will import the necessary modules and add your client id and client secret." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession\n", "\n", "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(\n", " Environment.PROD,\n", " client_id=client,\n", " client_secret=secret,\n", " scopes=('read_product_data read_user_profile modify_product_data',),\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 2: Fetch your basket and set your changes\n", "\n", "Next you will fetch the basket you'd like to edit by passing in any of its identifiers such as BloombergId, Cusip, Ticker, etc. If this is a basket you or someone from your organization created, please make sure your application has edit/admin entitlements or you will not be able to modify it. You will then be ready to change any of the properties listed below.\n", "\n", "| Parameter Name |Description |\n", "|:--------------------|:-----------|\n", "|name |Display name of the basket|\n", "|description |Free text description of basket. Description provided will be indexed in the search service for free text relevance match.|\n", "|publish_to_bloomberg |If you'd like us to publish your basket to Bloomberg|\n", "|publish_to_reuters |If you'd like us to publish your basket to Reuters |\n", "|publish_to_factset |If you'd like us to publish your basket to Factset |\n", "|include_price_history|Republish price history based on current composition|\n", "|cash_reinvestment_treatment|How to treat cash acquisitions, regular dividends, and special dividends for basket underliers.|\n", "|historical_methodology|Preferred methodology to be applied when publishing basket history.
  • **Backcast**: Apply current composition backwards up to 5 years, assuming constituent shares remained constant
  • **Backtest**: Reset composition based on the selected criteria and rebalance frequency over selected period of time. Must specify these options in `backtest_parameters`.
  • **Custom History**: Upload your own basket history after the basket has been created. *Note: Previously this was indicated by setting* `default_backcast = False`
|\n", "|backtest_parameters |Rules applied when performing a historical backtest.
  • **Weighting Mechanism**: On each rebalance date, whether to reset positions to have equal weights or restore based on original position set values.
  • **Time Horizon**: Time horizon for which to generate historical backtest. (5 years is the only supported option at this time.)
  • **Frequency**: Frequency at which to recalculate position shares (Weekly, Monthly, Quarterly, Annually.)
|" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX')\n", "\n", "basket.description = 'My New and Improved basket description'\n", "basket.publish_to_reuters = True" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 3: Update your basket's entitlements\n", "\n", "The application you use to create your basket will initially be the only one permissioned to view, edit, and submit rebalance requests. If you'd like to entitle other users or groups with view or admin access, you may update your basket's permissions at any time.\n", "\n", "In order to add or remove permissions for a specific user, you will need either their Marquee user id or email. You may also permission groups using their group id. See the snippet below, or refer to the [baskets permissions examples](../examples/07_basket_permissions/0001_permission_application_to_basket.ipynb) for more options." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.entities.entitlements import Group, User\n", "\n", "user = User.get(user_id='application_id')\n", "basket.entitlements.view.users.remove(user)\n", "\n", "group = Group.get(group_id='group_id')\n", "basket.entitlements.admin.groups += [group]\n", "\n", "basket.entitlements.to_frame() # call to verify the entitlement changes are now reflected properly" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 4: Submit your changes\n", "\n", "Once you've ensured that your basket has been updated to your satisfaction, you're ready to officially submit these changes to Marquee! Once you call update on the basket, you may poll its status to make sure that it has processed successfully. This will check the report status every 30 seconds for 10 minutes by default, but you can override this option if you prefer as shown below." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "my_basket.get_details() # call to make sure that your changes are all reflected properly\n", "\n", "my_basket.update()\n", "\n", "my_basket.poll_status(\n", " timeout=120, step=20\n", ") # optional: constantly checks edit status until report succeeds, fails, or the poll times out (this example checks every 20 seconds for 2 minutes)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/06_baskets/tutorials/Basket Rebalance.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Rebalancing a Custom Basket\n", "\n", "Here we will go through how to modify your basket's current composition. You may concurrently modify basket details, pricing options, and publishing preferences." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 1: Authenticate & Initialize your session\n", "\n", "First you will import the necessary modules and add your client id and client secret." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.position_set import PositionSet\n", "from gs_quant.markets.baskets import Basket\n", "from gs_quant.session import Environment, GsSession\n", "\n", "client = 'CLIENT ID'\n", "secret = 'CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret, scopes=('read_product_data read_user_profile',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 2: Fetch your basket and set your changes\n", "\n", "Next you will fetch the basket you'd like to update by passing in any of its identifiers such as BloombergId, Cusip, Ticker, etc. If this is a basket you or someone from your organization created, please make sure your application has admin entitlements or you will not be able to submit the rebalance request. You will then be ready to change any of the properties listed below.\n", "\n", "| Parameter Name | Required? | Description |\n", "|:-------------------|:-----------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n", "|position_set |**Required**| Information of constituents associated with the basket. You may provide the weight or quantity for each position. If neither is provided we will distribute the total weight evenly among each position. Please also note that any fractional shares will be rounded up to whole numbers. |\n", "|divisor |Optional | Divisor to be applied to the overall position set. Ideally, you should not to change this as it will cause a price deviation on the basket. |\n", "|initial_price |Optional | Price you'd like to reset the basket to. Ideally, you should not to do this as it will cause a price deviation on the basket. |\n", "|publish_to_bloomberg|Optional | If you'd like us to publish your basket to Bloomberg |\n", "|publish_to_reuters |Optional | If you'd like us to publish your basket to Reuters |\n", "|publish_to_factset |Optional | If you'd like us to publish your basket to Factset |\n", "|include_price_history|Optional | Republish price history based on current composition when publishing to Bloomberg |\n", "|reweight |Optional | If you'd like us to reweight positions if input weights don't add up to 1 upon submission |\n", "|weighting_strategy |Optional | Strategy used to price the position set (will be inferred if not indicated). One of Equal, Market Capitalization, Quantity, Weight |\n", "|allow_ca_restricted_assets|Optional| Allow your basket to have constituents that will not be corporate action adjusted in the future (You will recieve a message indicating if this action is needed when attempting to rebalance your basket) |\n", "|allow_limited_access_assets|Optional| Allow basket to have constituents that GS has limited access to (You will recieve a message indicating if this action is needed when attempting to rebalance your basket) |\n", "|cash_reinvestment_treatment|How to treat cash acquisitions, regular dividends, and special dividends for basket underliers.|\n", "|historical_methodology|Preferred methodology to be applied when publishing basket history.
  • **Backcast**: Apply current composition backwards up to 5 years, assuming constituent shares remained constant
  • **Backtest**: Reset composition based on the selected criteria and rebalance frequency over selected period of time. Must specify these options in `backtest_parameters`.
  • **Custom History**: Upload your own basket history after the basket has been created. *Note: Previously this was indicated by setting* `default_backcast = False`
|\n", "|backtest_parameters |Rules applied when performing a historical backtest.
  • **Weighting Mechanism**: On each rebalance date, whether to reset positions to have equal weights or restore based on original position set values.
  • **Time Horizon**: Time horizon for which to generate historical backtest. (5 years is the only supported option at this time.)
  • **Frequency**: Frequency at which to recalculate position shares (Weekly, Monthly, Quarterly, Annually.)
|" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket = Basket.get('GSMBXXXX')\n", "\n", "basket.publish_to_bloomberg = True\n", "\n", "positions_df = pd.read_excel('path/to/excel.xlsx') # example composition upload from a local excel file\n", "positions_df.columns = ['identifier', 'weight'] # replace weight column with 'quantity' if using number of shares\n", "position_set = PositionSet.from_frame(positions_df)\n", "position_set.resolve()\n", "\n", "position_set.get_positions() # returns a dataframe with each position's identifier, name, Marquee unique identifier, and weight/quantity\n", "\n", "my_basket.position_set = position_set" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 3: Submit your changes\n", "\n", "Once you've ensured that your basket composition has been adjusted to your satisfaction, you're ready to officially submit these changes to Marquee! Once you call update on the basket, this request will be sent for approval. You can check on the approval status by calling get_rebalance_approval_status(). The rebalance will begin processing once the request is approved, where you can then poll its status to make sure that it has processed successfully. This will check the report status every 30 seconds for 10 minutes by default, but you can override this option if you prefer as shown below." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket.update() # submits the rebalance request to Marquee\n", "\n", "basket.get_rebalance_approval_status() # check approval status of most recent rebalance submission\n", "\n", "basket.poll_status(\n", " timeout=120, step=20\n", ") # optional: constantly checks rebalance status after request is approved until report succeeds, fails, or the poll times out (this example checks every 20 seconds for 2 minutes)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Not happy with the new composition you submitted?\n", "\n", "If your most recent rebalance request is not yet approved, you may either update your composition and submit a new rebalance request using the steps listed above, or you can simply cancel the request." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basket.cancel_rebalance()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/07_index/README.md ================================================ ## Pre Requisites for STS Indices To use the Index functionality on **STS Indices**, your application needs to have the following access #### Accessing STS Indices Your app should have *view access* to the STS index you intend to use. To request access, please [send an email](mailto:gs-marquee-sts-support@ny.email.gs.com?subject=Request%20to%20access%20%20via%20API&body=I%20would%20like%20to%20access%20the%20index%20%20via%20my%20app%20) to the Marquee STS Team. #### Accessing STS Datasets Your app needs to have access to the following datasets: 1. [STSLEVELS](https://marquee.gs.com/s/developer/datasets/STSLEVELS) - Official Values of STS Indices 2. [STS_INDICATIVE_LEVELS](https://marquee.gs.com/s/developer/datasets/STS_INDICATIVE_LEVELS) - Indicative Values of STS Indices 3. [STS_FUNDAMENTALS](https://marquee.gs.com/s/developer/datasets/STS_FUNDAMENTALS) - Fundamental metrics of STS Indices 4. [STS_UNDERLIER_WEIGHTS](https://marquee.gs.com/s/developer/datasets/STS_UNDERLIER_WEIGHTS) - Weights of index underliers of STS Indices 5. [STS_UNDERLIER_ATTRIBUTION](https://marquee.gs.com/s/developer/datasets/STS_UNDERLIER_ATTRIBUTION) - Attribution of index underliers 6. [STSCONSTITUENTS](https://marquee.gs.com/s/developer/datasets/STSCONSTITUENTS) - Bottom level index constituents and associated weights You can request access by going to the Dataset Catalog Page linked above. Note - Please skip this if you are an internal user ================================================ FILE: gs_quant/documentation/07_index/examples/0000_get_index_Marquee_url.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "c2ec89ec", "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.index import Index\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "code", "execution_count": null, "id": "de70c758", "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None)" ] }, { "cell_type": "code", "execution_count": null, "id": "f384ea49", "metadata": {}, "outputs": [], "source": [ "index = Index.get('GSXXXXXX') # substitute input with any identifier for an Index\n", "index.get_url() # url to view index on Marquee" ] }, { "cell_type": "markdown", "id": "7fb27b941602401d91542211134fc71a", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, "source": [ "*Have any other questions? Reach out to the [Marquee STS team](mailto:gs-marquee-sts-support@gs.com)!*" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.6" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/07_index/examples/0001_get_index_close_prices.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "d78f4f5c", "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.index import Index, PriceType\n", "from gs_quant.session import Environment, GsSession\n", "import datetime as dt" ] }, { "cell_type": "markdown", "id": "7fb27b941602401d91542211134fc71a", "metadata": { "collapsed": false }, "source": [ "## Pre Requisites\n", "To use below functionality on **STS Indices**, your application needs to have access to the following datasets:\n", "1. [STSLEVELS](https://marquee.gs.com/s/developer/datasets/STSLEVELS) - Official Values of STS Indices\n", "2. [STS_INDICATIVE_LEVELS](https://marquee.gs.com/s/developer/datasets/STS_INDICATIVE_LEVELS) - Indicative Values of STS Indices\n", "\n", "You can request access by going to the Dataset Catalog Page linked above.\n", "\n", "Note - Please skip this if you are an internal user" ] }, { "cell_type": "code", "execution_count": null, "id": "c6eff76d", "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "id": "3ac14d1f", "metadata": {}, "outputs": [], "source": [ "index = Index.get('GSXXXXXX') # substitute input with any identifier for an index" ] }, { "cell_type": "markdown", "id": "97a0847c", "metadata": {}, "source": [ "#### Close price functions supports the following price types" ] }, { "cell_type": "markdown", "id": "ad9bf45e", "metadata": {}, "source": [ "You may choose one of the following price types:\n", "\n", "- **Official Price:** PriceType.OFFICIAL_PRICE\n", "- **Indicative Price** PriceType.INDICATIVE_CLOSE_PRICE - Currently supports STS indices only.\n", "\n", "Default returns the official close price" ] }, { "cell_type": "code", "execution_count": null, "id": "7e037f7a", "metadata": {}, "outputs": [], "source": [ "index.get_latest_close_price(\n", " price_type=[PriceType.OFFICIAL_CLOSE_PRICE]\n", ") # returns latest official levels for the index." ] }, { "cell_type": "code", "execution_count": null, "id": "5e179014", "metadata": {}, "outputs": [], "source": [ "index.get_close_price_for_date(\n", " dt.date(2021, 1, 7), price_type=[PriceType.OFFICIAL_CLOSE_PRICE]\n", ") # returns official levels for a given date." ] }, { "cell_type": "code", "execution_count": null, "id": "bdd5f86d", "metadata": {}, "outputs": [], "source": [ "index.get_close_prices(\n", " start=dt.date(2021, 1, 7), end=dt.date(2021, 3, 27), price_type=[PriceType.OFFICIAL_CLOSE_PRICE]\n", ") # returns official levels for a given date range." ] }, { "cell_type": "code", "execution_count": null, "id": "f7f9f147", "metadata": {}, "outputs": [], "source": [ "index.get_close_prices(price_type=[PriceType.OFFICIAL_CLOSE_PRICE]) # returns all the official levels of the index." ] }, { "cell_type": "markdown", "id": "b5a88445", "metadata": {}, "source": [ "#### STS indices can use PriceType.INDICATIVE_CLOSE_PRICE as well to get the indicative levels" ] }, { "cell_type": "code", "execution_count": null, "id": "8ceaa6e6", "metadata": {}, "outputs": [], "source": [ "index.get_latest_close_price(\n", " price_type=[PriceType.OFFICIAL_CLOSE_PRICE, PriceType.INDICATIVE_CLOSE_PRICE]\n", ") # returns latest indicative and official levels of the index." ] }, { "cell_type": "code", "execution_count": null, "id": "3b129435", "metadata": {}, "outputs": [], "source": [ "index.get_close_price_for_date(\n", " dt.date(2021, 1, 7), price_type=[PriceType.OFFICIAL_CLOSE_PRICE, PriceType.INDICATIVE_CLOSE_PRICE]\n", ") # returns both indicative and official levels of the index for a given date." ] }, { "cell_type": "code", "execution_count": null, "id": "371146cb", "metadata": {}, "outputs": [], "source": [ "index.get_close_prices(\n", " start=dt.date(2021, 1, 7),\n", " end=dt.date(2021, 3, 27),\n", " price_type=[PriceType.OFFICIAL_CLOSE_PRICE, PriceType.INDICATIVE_CLOSE_PRICE],\n", ") # returns both indicative and official levels of the index for a given date range." ] }, { "cell_type": "code", "execution_count": null, "id": "ebc1ad33", "metadata": {}, "outputs": [], "source": [ "index.get_close_prices(\n", " price_type=[PriceType.OFFICIAL_CLOSE_PRICE, PriceType.INDICATIVE_CLOSE_PRICE]\n", ") # returns all the indicative and official levels of the index." ] }, { "cell_type": "markdown", "id": "acae54e37e7d407bbb7b55eff062a284", "metadata": { "collapsed": false }, "source": [ "*Have any other questions? Reach out to the [Marquee STS team](mailto:gs-marquee-sts-support@gs.com)!*" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.6" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/07_index/examples/0002_get_index_fundamentals_data.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "c2ec89ec", "metadata": {}, "outputs": [], "source": [ "import datetime as dt\n", "\n", "from gs_quant.data.fields import DataMeasure\n", "from gs_quant.markets.index import Index\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "markdown", "id": "9eb4d9e4", "metadata": {}, "source": [ "## Pre Requisites\n", "To use below functionality on **STS Indices**, your application needs to have access to the following datasets:\n", "1. [STS_FUNDAMENTALS](https://marquee.gs.com/s/developer/datasets/STS_FUNDAMENTALS) - Fundamental metrics of STS Indices\n", "\n", "You can request access by going to the Dataset Catalog Page linked above.\n", "\n", "Note - Please skip this if you are an internal user" ] }, { "cell_type": "code", "execution_count": null, "id": "de70c758", "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "id": "f384ea49", "metadata": {}, "outputs": [], "source": [ "index = Index.get('GSXXXXXX') # substitute input with any identifier for a single stock STS index." ] }, { "cell_type": "code", "execution_count": null, "id": "d619009b", "metadata": {}, "outputs": [], "source": [ "index.get_fundamentals() # default period is one year, default period direction is forward, default metrics are all available metrics" ] }, { "cell_type": "code", "execution_count": null, "id": "8c98e5fc", "metadata": {}, "outputs": [], "source": [ "index.get_fundamentals(\n", " start=dt.date(2021, 1, 7), end=dt.date(2021, 3, 27)\n", ") # get fundamentals for the specified date range" ] }, { "cell_type": "markdown", "id": "e4024bc8", "metadata": {}, "source": [ "You may choose one of the following periods:\n", "\n", "- **1 year:** DataMeasure.ONE_YEAR\n", "- **2 years:** DataMeasure.TWO_YEARS\n", "- **3 years:** DataMeasure.THREE_YEARS\n", "\n", "You may choose one of the following period directions:\n", "\n", "- **Forward:** DataMeasure.FORWARD\n", "- **Trailing:** DataMeasure.TRAILING" ] }, { "cell_type": "code", "execution_count": null, "id": "b2257277", "metadata": {}, "outputs": [], "source": [ "index.get_fundamentals(period=DataMeasure.TWO_YEARS, direction=DataMeasure.TRAILING)" ] }, { "cell_type": "markdown", "id": "369b0fed", "metadata": {}, "source": [ "You may choose any combinations of the following metrics:\n", "\n", "- **Dividend Yield:** DataMeasure.DIVIDEND_YIELD\n", "- **Earnings per Share:** DataMeasure.EARNINGS_PER_SHARE\n", "- **Earnings per Share Positive:** DataMeasure.EARNINGS_PER_SHARE_POSITIVE\n", "- **Net Debt to EBITDA:** DataMeasure.NET_DEBT_TO_EBITDA\n", "- **Price to Book:** DataMeasure.PRICE_TO_BOOK\n", "- **Price to Cash:** DataMeasure.PRICE_TO_CASH\n", "- **Price to Earnings:** DataMeasure.PRICE_TO_EARNINGS\n", "- **Price to Earnings Positive:** DataMeasure.PRICE_TO_EARNINGS_POSITIVE\n", "- **Price to Sales:** DataMeasure.PRICE_TO_SALES\n", "- **Return on Equity:** DataMeasure.RETURN_ON_EQUITY\n", "- **Sales per Share:** DataMeasure.SALES_PER_SHARE" ] }, { "cell_type": "code", "execution_count": null, "id": "37118eed", "metadata": {}, "outputs": [], "source": [ "index.get_fundamentals(metrics=[DataMeasure.PRICE_TO_CASH, DataMeasure.SALES_PER_SHARE])" ] }, { "cell_type": "markdown", "id": "55047ebb", "metadata": {}, "source": [ "*Have any other questions? Reach out to the [Marquee STS team](mailto:gs-marquee-sts-support@gs.com)!*" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/07_index/examples/0003_get_index_constituents.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "d78f4f5c", "metadata": {}, "outputs": [], "source": [ "import datetime as dt\n", "from gs_quant.markets.index import Index\n", "from gs_quant.session import Environment, GsSession" ] }, { "cell_type": "markdown", "id": "b50c33ff-4e90-4ba3-828c-1ca0f06e05f0", "metadata": {}, "source": [ "## Pre Requisites\n", "To use below functionality on **STS Indices**, your application needs to have access to the following datasets:\n", "1. [STSCONSTITUENTS](https://marquee.gs.com/s/developer/datasets/STSCONSTITUENTS) - Bottom level index constituents and associated weights\n", "\n", "You can request access by going to the Dataset Catalog Page linked above.\n", "\n", "Note - Please skip this if you are an internal user" ] }, { "cell_type": "code", "execution_count": null, "id": "c6eff76d", "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "id": "3ac14d1f", "metadata": {}, "outputs": [], "source": [ "index = Index.get('GSXXXXXX') # substitute input with any identifier for an index" ] }, { "cell_type": "markdown", "id": "61adac51", "metadata": {}, "source": [ "## Get Constituents\n", "\n", "Returns a pandas dataframe of the constituents" ] }, { "cell_type": "code", "execution_count": null, "id": "109fef12", "metadata": {}, "outputs": [], "source": [ "index.get_latest_constituents() # returns the latest constituents of the index." ] }, { "cell_type": "code", "execution_count": null, "id": "a6cf3b37", "metadata": {}, "outputs": [], "source": [ "index.get_constituents_for_date(dt.date(2021, 7, 1)) # returns the constituents of the index for the specified date" ] }, { "cell_type": "code", "execution_count": null, "id": "da4c497a", "metadata": {}, "outputs": [], "source": [ "index.get_constituents(\n", " dt.date(2021, 6, 1), dt.date(2021, 6, 10)\n", ") # returns the constituents sets of the index for the specified date range" ] }, { "cell_type": "markdown", "id": "69d5708d", "metadata": {}, "source": [ "## Get Constituents as Instruments\n", "\n", "Returns the constituents as Instrument objects" ] }, { "cell_type": "code", "execution_count": null, "id": "d0644e0a", "metadata": {}, "outputs": [], "source": [ "index.get_latest_constituent_instruments() # returns the latest constituents of the index as instrument objects." ] }, { "cell_type": "code", "execution_count": null, "id": "18d10cd3", "metadata": {}, "outputs": [], "source": [ "index.get_constituent_instruments_for_date(\n", " dt.date(2021, 7, 1)\n", ") # returns the constituents of the index for the specified date as Instrument objects" ] }, { "cell_type": "code", "execution_count": null, "id": "79a818b8", "metadata": {}, "outputs": [], "source": [ "index.get_constituent_instruments(\n", " dt.date(2021, 6, 1), dt.date(2021, 6, 10)\n", ") # returns the constituents sets of the index for the specified date range as Instrument objects" ] }, { "cell_type": "markdown", "id": "3d66e679", "metadata": {}, "source": [ "*Have any other questions? Reach out to the [Marquee STS team](mailto:gs-marquee-sts-support@gs.com)!*" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/07_index/examples/0004_get_index_weights_and_attribution.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "9e4d4a4b", "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import Environment, GsSession\n", "from gs_quant.markets.index import Index" ] }, { "cell_type": "markdown", "id": "3027d017", "metadata": {}, "source": [ "## Pre Requisites\n", "To use below functionality on **STS Indices**, your application needs to have access to the following datasets:\n", "4. [STS_UNDERLIER_WEIGHTS](https://marquee.gs.com/s/developer/datasets/STS_UNDERLIER_WEIGHTS) - Weights of index underliers of STS Indices\n", "5. [STS_UNDERLIER_ATTRIBUTION](https://marquee.gs.com/s/developer/datasets/STS_UNDERLIER_ATTRIBUTION) - Attribution of index underliers\n", "\n", "You can request access by going to the Dataset Catalog Page linked above.\n", "\n", "Note - Please skip this if you are an internal user" ] }, { "cell_type": "code", "execution_count": null, "id": "f6a8d144", "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "id": "827dc4fc", "metadata": {}, "outputs": [], "source": [ "strategy_index = Index.get('GSXXXXXX') # substitute input with any identifier for an index" ] }, { "cell_type": "markdown", "id": "f88343e4", "metadata": {}, "source": [ "### These functions currently supports STS indices only" ] }, { "cell_type": "code", "execution_count": null, "id": "d39c29b5", "metadata": {}, "outputs": [], "source": [ "# Returns a pandas dataframe containing latest weights of the immediate underliers of the index\n", "strategy_index.get_underlier_weights()" ] }, { "cell_type": "code", "execution_count": null, "id": "b3758a0e", "metadata": {}, "outputs": [], "source": [ "# Returns a pandas dataframe containing latest attribution of the immediate underliers of the index\n", "strategy_index.get_underlier_attribution()" ] }, { "cell_type": "code", "execution_count": null, "id": "ae9d3263", "metadata": {}, "outputs": [], "source": [ "# Get the latest weights and attribution of the underliers at all levels\n", "strategy_index.get_underlier_tree().to_frame()" ] }, { "cell_type": "markdown", "id": "6994f631", "metadata": {}, "source": [ "*Have any other questions? Reach out to the [Marquee STS team](mailto:gs-marquee-sts-support@gs.com)!*" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/07_index/examples/0005_get_index_as_a_tree.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "18a55a4a", "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import Environment, GsSession\n", "from gs_quant.markets.index import Index" ] }, { "cell_type": "markdown", "id": "7fb27b941602401d91542211134fc71a", "metadata": { "collapsed": false }, "source": [ "## Pre Requisites\n", "To use below functionality on **STS Indices**, your application needs to have access to the following datasets:\n", "1. [STS_UNDERLIER_WEGHTS](https://marquee.gs.com/s/developer/datasets/STS_UNDERLIER_WEGHTS) - Weights of index underliers of STS Indices\n", "5. [STS_UNDERLIER_WEGHTS](https://marquee.gs.com/s/developer/datasets/STS_UNDERLIER_WEGHTS) - Attribution of index underliers\n", "\n", "You can request access by going to the Dataset Catalog Page linked above.\n", "\n", "Note - Please skip this if you are an internal user" ] }, { "cell_type": "code", "execution_count": null, "id": "14915132", "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('read_product_data',))" ] }, { "cell_type": "code", "execution_count": null, "id": "315d7921", "metadata": {}, "outputs": [], "source": [ "strategy_index = Index.get('GSXXXXXX') # substitute input with any identifier for an index" ] }, { "cell_type": "markdown", "id": "20b48849", "metadata": {}, "source": [ "### These functions currently supports STS indices only" ] }, { "cell_type": "code", "execution_count": null, "id": "5e5df9ff", "metadata": {}, "outputs": [], "source": [ "# Returns the root node of the underlier tree formed by the index\n", "strategy_index.get_underlier_tree()" ] }, { "cell_type": "markdown", "id": "9dcdf9da", "metadata": {}, "source": [ "To know more about how to use the AssetTreeNode object, please refer to the example notebook for the Tree Entity class, found in gs_quant/documentation/08_tree_entity" ] }, { "cell_type": "code", "execution_count": null, "id": "15dfe760", "metadata": {}, "outputs": [], "source": [ "# Returns the full tree formed by the the index as a pandas dataframe\n", "strategy_index.get_underlier_tree().to_frame()" ] }, { "cell_type": "code", "execution_count": null, "id": "acae54e37e7d407bbb7b55eff062a284", "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# This functionality requires treelib to be installed\n", "# Prints the tree structure formed by the Index for easy visualisation\n", "strategy_index.visualise_tree(visualise_by='name')\n", "\n", "# If data is missing for any field, then assetId will be used instead\n", "strategy_index.visualise_tree(visualise_by='bbid')\n", "\n", "strategy_index.visualise_tree(visualise_by='id')" ] }, { "cell_type": "code", "execution_count": null, "id": "900625d8", "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# To refresh and rebuild the tree\n", "strategy_index.get_underlier_tree(refresh_tree=True)" ] }, { "cell_type": "markdown", "id": "9a63283cbaf04dbcab1f6479b197f3a8", "metadata": { "collapsed": false }, "source": [ "*Have any other questions? Reach out to the [Marquee STS team](mailto:gs-marquee-sts-support@gs.com)!*" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.6" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/07_index/tutorials/GS Quant STS Index Tutorial.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "fcb3ad26", "metadata": {}, "source": [ "# GS Quant STS Index Tutorial" ] }, { "cell_type": "markdown", "id": "7f178e4f", "metadata": {}, "source": [ "Welcome to the GS Quant Index tutorial! This tutorial will walk you through using the Index class functionality in GS Quant with a focus on STS Indices.\n", "\n", "Marquee [Systematic Trading Strategy (STS)](https://marquee.gs.com/welcome/products/index-solutions/systematic-trading-strategies) offering provides a variety of information for STS Indices including prices, compositions and PnL attribution.\n", "GS Quant makes accessing this data via API intuitive and fast.\n", "\n", "In this tutorial, you will fetch an STS Index and learn how to:\n", "1. Get strategy values\n", "3. Get bottom level composition\n", "4. Get full underlier tree\n", "5. Get factor risk data\n", "2. Get fundamentals metrics\n", "\n", "\n", "Please ensure that you have followed the setup instructions for GS Quant mentioned [here](https://developer.gs.com/docs/gsquant/getting-started/). " ] }, { "cell_type": "markdown", "id": "5e4f17b6", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "## Pre Requisites\n", "To use below functionality on **STS Indices**, your application needs to have access to the following datasets:\n", "1. [STSLEVELS](https://marquee.gs.com/s/developer/datasets/STSLEVELS) - Official Values of STS Indices\n", "2. [STS_INDICATIVE_LEVELS](https://marquee.gs.com/s/developer/datasets/STS_INDICATIVE_LEVELS) - Indicative Values of STS Indices\n", "3. [STS_FUNDAMENTALS](https://marquee.gs.com/s/developer/datasets/STS_FUNDAMENTALS) - Fundamental metrics of STS Indices\n", "4. [STS_UNDERLIER_WEIGHTS](https://marquee.gs.com/s/developer/datasets/STS_UNDERLIER_WEIGHTS) - Weights of index underliers of STS Indices\n", "5. [STS_UNDERLIER_ATTRIBUTION](https://marquee.gs.com/s/developer/datasets/STS_UNDERLIER_ATTRIBUTION) - Attribution of index underliers\n", "6. [STSCONSTITUENTS](https://marquee.gs.com/s/developer/datasets/STSCONSTITUENTS) - Bottom level index constituents and associated weights\n", "\n", "You can request access by going to the Dataset Catalog Page linked above.\n", "\n", "**Note** - If you're using GS Quant as an internal user, you need to raise the above for yourself. " ] }, { "cell_type": "markdown", "id": "8b88245b", "metadata": {}, "source": [ "# First Steps\n", "### 1. Authenticate & Initialize your session\n", "\n", "First you will import the necessary modules and add your client id and client secret. To checke your app's authentication, follow our [Getting Started guide.](https://developer.gs.com/docs/gsquant/authentication/gs-session/)" ] }, { "cell_type": "code", "execution_count": null, "id": "5171db43", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "from gs_quant.markets.index import Index\n", "from gs_quant.markets.indices_utils import *\n", "from gs_quant.data.fields import DataMeasure\n", "from gs_quant.session import Environment, GsSession\n", "\n", "import datetime as dt" ] }, { "cell_type": "code", "execution_count": null, "id": "f758e77f", "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('read_product_data',))" ] }, { "cell_type": "markdown", "id": "4069e261", "metadata": {}, "source": [ "### 2. Fetch the index information\n", "\n", "#### Load the index\n", "Next you will fetch the index you'd like to work with, by passing in any of its identifiers such as Bloomberg Id, Ticker, etc.\n", "In this tutorial, we're using some sample STS indices:\n", "1. GSMBMA5S - A multi-asset strategy\n", "2. GSISM37E - A strategy based on single stocks\n", "\n", "To access any STS Index, your app should have access to view that index. Please note that **apps do not automatically inherit the access of their owners**. This means that your apps need to be separately granted access to the required STS Indices.\n", "\n", "To request access to the above samples, please [send an email](mailto:gs-sts-marquee-permissioning-global@gs.com?cc=gs-marquee-sts-support@gs.com&subject=Request%20to%20access%20%20via%20API&body=I%20would%20like%20to%20access%20the%20index%20%20via%20my%20app%20) to our team mentioning your app's id.\n", "\n", "If you app already has access to some other STS indices, you can use them as well. Just replace the ticker below with the ticker of your index." ] }, { "cell_type": "code", "execution_count": null, "id": "b5d12812", "metadata": {}, "outputs": [], "source": [ "ma_index = Index.get('GSMBMA5S') # Substitute input with the identifier for an Index\n", "sstk_index = Index.get('GSISM37E')" ] }, { "cell_type": "markdown", "id": "b9ea63af", "metadata": {}, "source": [ "#### Get the Index URL on Marquee" ] }, { "cell_type": "code", "execution_count": null, "id": "5bcff23b-fd83-45e8-9b36-7cb3d0d51bc2", "metadata": {}, "outputs": [], "source": [ "# Retrieves the url of the product page of the index on Marquee\n", "ma_index.get_url()" ] }, { "cell_type": "code", "execution_count": null, "id": "a7580ce9", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "start_date = dt.date(2022, 3, 1)\n", "end_date = dt.date(2022, 4, 1)" ] }, { "cell_type": "markdown", "id": "6aca8516", "metadata": {}, "source": [ "# Strategy Values of an STS Index\n", "\n", "In general, all Indices support official close prices but for STS Indices, official and indicatvive strategy values are supported. " ] }, { "cell_type": "markdown", "id": "29b00c8b", "metadata": {}, "source": [ "The strategy values of an STS Index can be obtained by using the `get_close_prices` function. You may choose one of the following price types:\n", "\n", "**Official Strategy Values**: PriceType.OFFICIAL_PRICE
\n", "**Indicative Strategy Values**: PriceType.INDICATIVE_CLOSE_PRICE - _Currently available for STS indices only._" ] }, { "cell_type": "markdown", "id": "dad27eeb-a6b2-4d73-8702-4d34625576b8", "metadata": {}, "source": [ "### Get Official Close Prices" ] }, { "cell_type": "code", "execution_count": null, "id": "cc5ac3e0", "metadata": {}, "outputs": [], "source": [ "# Returns strategy values between start and end date. When no price_type is passed, it defaults to official strategy values\n", "ma_index.get_close_prices(start=start_date, end=end_date)\n", "\n", "# Returns official strategy values between start and end date\n", "sstk_index.get_close_prices(start=start_date, end=end_date, price_type=[PriceType.OFFICIAL_CLOSE_PRICE])" ] }, { "cell_type": "markdown", "id": "66a19bca-d28b-4c1d-ad60-1d5e9b0c4e0a", "metadata": {}, "source": [ "### Get Indicative Close Prices" ] }, { "cell_type": "code", "execution_count": null, "id": "cb897e4e-e5a9-4a19-adec-bafb22a198a9", "metadata": {}, "outputs": [], "source": [ "# Returns indicative strategy values between start and end date\n", "ma_index.get_close_prices(start=start_date, end=end_date, price_type=[PriceType.INDICATIVE_CLOSE_PRICE])" ] }, { "cell_type": "markdown", "id": "5ecd4b35-7d4e-4c35-9903-76c6aea3c067", "metadata": {}, "source": [ "### Get all close prices" ] }, { "cell_type": "code", "execution_count": null, "id": "6b204b40-50b9-458b-b130-7ca636eebac8", "metadata": {}, "outputs": [], "source": [ "# Returns official and indicative strategy values between start and end date for the STS index\n", "ma_index.get_close_prices(\n", " start=start_date, end=end_date, price_type=[PriceType.OFFICIAL_CLOSE_PRICE, PriceType.INDICATIVE_CLOSE_PRICE]\n", ")" ] }, { "cell_type": "markdown", "id": "3236cb73-6ed7-4ba1-96c3-39836cce872f", "metadata": {}, "source": [ "# Composition Data of STS Index\n", "\n", "The composition of an STS Index can be understood as a tree structure - the index is the topmost node, and it has some child nodes.\n", "These child nodes can either be bottom level assets or have child nodes of their own.\n", "The intermediate child nodes (i.e. those which have their own children) are called **underliers**. While the bottom level assets are called **constituents**.\n", "\n", "![Tree](./images/index_composition_diagram.png)" ] }, { "cell_type": "markdown", "id": "0d1f82b0", "metadata": {}, "source": [ "\n", "\n", "### Get Constituents\n", "\n", "The constituents of an Index are the bottom level assets. Fetch the constituents of the index using the `get_constituents` method.\n", "\n", "You can also get convert the constituents of an index into Instrument objects." ] }, { "cell_type": "code", "execution_count": null, "id": "96b365a6-1dce-48e2-b3dc-ec2b078f5312", "metadata": {}, "outputs": [], "source": [ "# Returns constituents of the index as a pandas DataFrame object\n", "sstk_index.get_constituents_for_date(date=start_date)" ] }, { "cell_type": "code", "execution_count": null, "id": "16cf7e9b-470e-489c-89be-100049a2459e", "metadata": {}, "outputs": [], "source": [ "# Returns constituents of the index as a list of instrument class objects\n", "sstk_instruments = sstk_index.get_constituent_instruments_for_date(date=start_date)" ] }, { "cell_type": "markdown", "id": "bbb6eb5b", "metadata": {}, "source": [ "### Get Underlier Tree\n", "\n", "You can fetch the underlier tree of an index using the `get_underlier_tree` method. This method returns the composition tree alongwith the weights and attributions of the nodes." ] }, { "cell_type": "code", "execution_count": null, "id": "31d01dda", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Returns the top node of the tree structure formed by the index\n", "ma_index.get_underlier_tree()" ] }, { "cell_type": "code", "execution_count": null, "id": "b548eec0", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Returns the tree structure formed by the index as a pandas dataframe\n", "ma_index.get_underlier_tree().to_frame()" ] }, { "cell_type": "markdown", "id": "df0d7595-7936-4602-8fca-fd889193748b", "metadata": {}, "source": [ "We proVisualisation functions require installing the `treelib` package, so let's go ahead and install it.\n", "\n", "*Note:* This is part of GS Quant's `notebook` dependencies. You can run `pip install gs-quant[notebook]` to install all of them." ] }, { "cell_type": "code", "execution_count": null, "id": "f62f6e5b-f674-4b72-a8f5-f702949fd794", "metadata": {}, "outputs": [], "source": [ "!pip install treelib" ] }, { "cell_type": "code", "execution_count": null, "id": "5c9b479f", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Prints the tree structure formed by the Index for easy visualisation\n", "ma_index.visualise_tree(visualise_by='name')" ] }, { "cell_type": "code", "execution_count": null, "id": "7b8717fb", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# If data is missing for any field, then assetId will be used instead\n", "ma_index.visualise_tree(visualise_by='bbid')" ] }, { "cell_type": "code", "execution_count": null, "id": "8df35d26-0238-45af-bf04-dd532685f7c4", "metadata": {}, "outputs": [], "source": [ "ma_index.visualise_tree(visualise_by='id')" ] }, { "cell_type": "markdown", "id": "8bddb2cb", "metadata": {}, "source": [ "### Get Underlier Weights and Attribution\n", "The underliers of an Index are the intermediate nodes in the composition tree.\n", "\n", "You can fetch the weights and attribution of the underliers one level down using `get_underlier_weights` and `get_underlier_attribution` methods." ] }, { "cell_type": "code", "execution_count": null, "id": "b14ecab1", "metadata": {}, "outputs": [], "source": [ "# Returns immediate underlier weights (one level down) as a pandas dataframe\n", "ma_index.get_underlier_weights()" ] }, { "cell_type": "code", "execution_count": null, "id": "8cdbbf43-62c8-4f9e-b486-b42d970cec58", "metadata": {}, "outputs": [], "source": [ "# Returns immediate underlier attribution (one level down) as a pandas dataframe\n", "ma_index.get_underlier_attribution()" ] }, { "cell_type": "markdown", "id": "d1c35b26", "metadata": {}, "source": [ "# Fundamental Metrics of STS Index" ] }, { "cell_type": "markdown", "id": "c76799d2", "metadata": {}, "source": [ "Single Stock STS Indices offer Fundamental Metrics data via API, which can be obtained using the `get_fundamentals` function.\n", "\n", "You may choose one of the following periods:\n", "\n", "- **1 year:** DataMeasure.ONE_YEAR\n", "- **2 years:** DataMeasure.TWO_YEARS\n", "- **3 years:** DataMeasure.THREE_YEARS\n", "\n", "You may choose one of the following period directions:\n", "\n", "- **Forward:** DataMeasure.FORWARD\n", "- **Trailing:** DataMeasure.TRAILING\n", "\n", "You may choose any combination of the following metrics:\n", "\n", "- **Dividend Yield:** DataMeasure.DIVIDEND_YIELD\n", "- **Earnings per Share:** DataMeasure.EARNINGS_PER_SHARE\n", "- **Earnings per Share Positive:** DataMeasure.EARNINGS_PER_SHARE_POSITIVE\n", "- **Net Debt to EBITDA:** DataMeasure.NET_DEBT_TO_EBITDA\n", "- **Price to Book:** DataMeasure.PRICE_TO_BOOK\n", "- **Price to Cash:** DataMeasure.PRICE_TO_CASH\n", "- **Price to Earnings:** DataMeasure.PRICE_TO_EARNINGS\n", "- **Price to Earnings Positive:** DataMeasure.PRICE_TO_EARNINGS_POSITIVE\n", "- **Price to Sales:** DataMeasure.PRICE_TO_SALES\n", "- **Return on Equity:** DataMeasure.RETURN_ON_EQUITY\n", "- **Sales per Share:** DataMeasure.SALES_PER_SHARE" ] }, { "cell_type": "markdown", "id": "92be04e7", "metadata": {}, "source": [ "### Get Fundamentals" ] }, { "cell_type": "code", "execution_count": null, "id": "7899fcb1", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false }, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Returns fundamentals data between start and end date for the STS index\n", "sstk_index.get_fundamentals(start=start_date, end=end_date)" ] }, { "cell_type": "code", "execution_count": null, "id": "45d2bce5-df5a-4ce9-a7d1-a35da7685db3", "metadata": {}, "outputs": [], "source": [ "# Returns fundamentals data between start and end date for the STS index for one year period with trailing direction and Price to Cash metric\n", "sstk_index.get_fundamentals(\n", " start=start_date,\n", " end=end_date,\n", " period=DataMeasure.ONE_YEAR,\n", " direction=DataMeasure.TRAILING,\n", " metrics=[DataMeasure.PRICE_TO_CASH, DataMeasure.SALES_PER_SHARE],\n", ")" ] }, { "cell_type": "markdown", "id": "3b52580e", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "### You're all set, Congrats!\n", "\n", "*Have any other questions? Reach out to the [Marquee STS team](mailto:gs-marquee-sts-support@gs.com)!*" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/08_tree_entity/tree_entity.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 2, "id": "14962dd6", "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import Environment, GsSession\n", "from gs_quant.entities.tree_entity import TreeHelper" ] }, { "cell_type": "code", "execution_count": 3, "id": "d6001d21", "metadata": {}, "outputs": [], "source": [ "# external users should substitute their client id and secret; please skip this step if using internal jupyterhub\n", "GsSession.use(Environment.PROD, client_id=None, client_secret=None, scopes=('read_product_data',))" ] }, { "cell_type": "markdown", "id": "bd085370", "metadata": {}, "source": [ "### Tree class usage" ] }, { "cell_type": "code", "execution_count": null, "id": "f7e8ba3b", "metadata": {}, "outputs": [], "source": [ "# Initialise the TreeHelper with the MarqueeID of the root Asset and the dataset to be used to build the tree.\n", "# In this example, we are building an STS index tree, so we use STS index datasets.\n", "\n", "tree = TreeHelper(\n", " 'MAM9TYYXXXXXXXXX', tree_underlier_dataset='STS_UNDERLIER_WEIGHTS', underlier_column='underlyingAssetId'\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "b9069bde", "metadata": {}, "outputs": [], "source": [ "# Builds the tree with the underlier dataset passed\n", "tree.build_tree()" ] }, { "cell_type": "code", "execution_count": null, "id": "8b287a90", "metadata": {}, "outputs": [], "source": [ "# Populate weights and attributions of the asset with the appropriate dataset and column name.\n", "# In this example, we use the datasets for STS indices.\n", "\n", "tree.populate_weights(dataset='STS_UNDERLIER_WEIGHTS', weight_column='weight')\n", "\n", "tree.populate_attribution(dataset='STS_UNDERLIER_ATTRIBUTION', attribution_column='absoluteAttribution')" ] }, { "cell_type": "code", "execution_count": null, "id": "79f3f22e", "metadata": {}, "outputs": [], "source": [ "# Get tree as a df with the contents stored at this point\n", "tree.to_frame()" ] }, { "cell_type": "code", "execution_count": null, "id": "7fb27b941602401d91542211134fc71a", "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Prints the tree structure formed by the Index for easy visualisation\n", "tree.get_visualisation(visualise_by='asset_name')" ] }, { "cell_type": "code", "execution_count": null, "id": "acae54e37e7d407bbb7b55eff062a284", "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# This feature needs treelib to be installed.\n", "tree.get_visualisation(visualise_by='bbid')" ] }, { "cell_type": "code", "execution_count": null, "id": "528eb668", "metadata": {}, "outputs": [], "source": [ "# Get the time at which the tree was built\n", "tree.update_time" ] }, { "cell_type": "code", "execution_count": null, "id": "4e77945d", "metadata": {}, "outputs": [], "source": [ "# This feature needs the notebook package of gs-quant to be installed.\n", "# You can install it using 'pip install gs-quant[notebook]'\n", "tree.get_visualisation(visualise_by='bbid')" ] }, { "cell_type": "code", "execution_count": null, "id": "0fc17c69", "metadata": {}, "outputs": [], "source": [ "# Get the time at which the tree was built\n", "tree.update_time" ] }, { "cell_type": "markdown", "id": "de5a16b6", "metadata": {}, "source": [ "### Accessing the child nodes" ] }, { "cell_type": "code", "execution_count": null, "id": "a4a2dbce", "metadata": {}, "outputs": [], "source": [ "# Accessing the root node of the tree\n", "root_node = tree.root" ] }, { "cell_type": "code", "execution_count": null, "id": "d13fdcd8", "metadata": {}, "outputs": [], "source": [ "# Accessing the child nodes of the root node.\n", "child_nodes_depth_1 = root_node.direct_underlier_assets_as_nodes" ] }, { "cell_type": "markdown", "id": "f8a15958", "metadata": {}, "source": [ "Each of these child nodes are TreeNode objects themselves, so their children can be accessed using the same technique. This can be used to recursively explore the full tree. " ] }, { "cell_type": "code", "execution_count": null, "id": "13230d97", "metadata": {}, "outputs": [], "source": [ "# Accessing the child nodes of the first child node of the root.\n", "child_nodes_depth_2 = child_nodes_depth_1[0].direct_underlier_assets_as_nodes" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.6" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/09_security_master/Security Master SDK.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "34a4c60d-fc51-494f-91f2-b90815ad05d4", "metadata": {}, "source": [ "# Goldman Sachs Marquee Security Master" ] }, { "cell_type": "markdown", "id": "22e2f1c2-e053-4f21-849d-ba7f88ea8667", "metadata": { "tags": [] }, "source": [ "## Introduction\n", "This notebook provides examples using the GS-Quant Security Master SDK. The examples will help users resolve current identifiers to historical identifiers, retrieve detailed security data, and perform various queries.

\n", "**Security Master developer documentation is available here:**\n", "[https://developer.gs.com/p/docs/services/data/securities-master/](https://developer.gs.com/p/docs/services/data/securities-master/)

\n", "**GS-Quant SDK documentation is available here:**\n", "[https://developer.gs.com/docs/gsquant/data/accessing-data/secmaster_sdk/](https://developer.gs.com/docs/gsquant/data/accessing-data/secmaster_sdk/)

" ] }, { "cell_type": "code", "execution_count": null, "id": "695687a2-dce9-4d4d-bd90-cf90b98d19c7", "metadata": { "tags": [] }, "outputs": [], "source": [ "import pandas as pd\n", "import datetime as dt\n", "\n", "from IPython.display import Markdown\n", "from pprint import pprint\n", "from gs_quant.target.secmaster import SecMasterAssetType\n", "from gs_quant.api.gs.secmaster import GsSecurityMasterApi\n", "from gs_quant.markets.securities import SecurityIdentifier\n", "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id=\"INSERT HERE\", client_secret=\"INSERT HERE\")" ] }, { "cell_type": "markdown", "id": "b1f49cfd-2469-4ab5-bc3f-e41266b2d8e1", "metadata": {}, "source": [ "## Sec Master ID Sorter\n", "In the following example is a function using the Get Many Securities method where the user can provide any identifier and have them resolved to an immutable Sec Master ID ('id') along with other market standard symbology." ] }, { "cell_type": "code", "execution_count": null, "id": "48a840db-28f4-4d9e-ac89-e6930b92727a", "metadata": {}, "outputs": [], "source": [ "def sec_master_id_sorter(identifiers):\n", " \"\"\"\n", " Fetch security data and normalize the identifiers.\n", "\n", " :param identifiers: List of security identifiers\n", " :return: DataFrame containing normalized identifiers\n", " \"\"\"\n", " data = GsSecurityMasterApi.get_many_securities(identifier=identifiers, isPrimary=True)\n", " results = pd.DataFrame(data['results'])\n", " return pd.json_normalize(results['identifiers'])\n", "\n", "\n", "# Example usage\n", "identifiers = [\"GOOGL UW\", \"VOD.N\", \"US67066G1040\"]\n", "normalized_identifiers = sec_master_id_sorter(identifiers)\n", "pd.DataFrame(normalized_identifiers)" ] }, { "cell_type": "markdown", "id": "2eb80c58-12e4-41bc-aa05-cdf96a1f5de8", "metadata": {}, "source": [ "## Map Current Symbology to Historical Symbology\n", "The following example is a function using the Get Many Securities method that resolves current symbology to historical symbology, ensuring that the user has correct identifiers as of a given point in time." ] }, { "cell_type": "code", "execution_count": null, "id": "adb107a1-493c-45c2-af05-070ab40871fa", "metadata": {}, "outputs": [], "source": [ "def get_historical_ids(identifiers, date, listed=True, id_type='bbid'):\n", " \"\"\"\n", " Fetch historical identifiers for multiple securities.\n", "\n", " :param identifiers: List of current identifiers of the securities.\n", " :param date: The date for which the historical identifiers are required.\n", " :param listed: Whether to filter by listed securities.\n", " :param id_type: The type of identifier to retrieve.\n", " :return: Dictionary mapping each identifier to its historical identifier.\n", " \"\"\"\n", " historical_ids = {}\n", " for identifier in identifiers:\n", " try:\n", " data = GsSecurityMasterApi.get_many_securities(identifier=[identifier], isPrimary=True)\n", " if not data.get('results'):\n", " raise ValueError(f\"No security found for identifier {identifier}\")\n", "\n", " secm_id = data['results'][0]['id']\n", " historical_data = GsSecurityMasterApi.get_identifiers(secmaster_id=secm_id)\n", "\n", " match = next(\n", " (\n", " record['value']\n", " for record in historical_data\n", " if dt.datetime.strptime(record['startDate'], '%Y-%m-%d').date()\n", " <= date\n", " <= (\n", " dt.datetime.strptime(record['endDate'], '%Y-%m-%d').date()\n", " if record['endDate'] != '9999-99-99'\n", " else dt.date.today()\n", " )\n", " and (not listed or record['type'] == id_type)\n", " ),\n", " None,\n", " )\n", " historical_ids[identifier] = match or identifier\n", " except Exception as e:\n", " print(f\"Error fetching historical ID for {identifier}: {e}\")\n", " historical_ids[identifier] = None\n", " return historical_ids\n", "\n", "\n", "# Example usage\n", "identifiers = [\"META UW\", \"GOOG UW\", \"XYZ UN\"]\n", "query_date = dt.date(2017, 1, 1)\n", "\n", "try:\n", " historical_ids = get_historical_ids(identifiers, query_date)\n", " for identifier, historical_id in historical_ids.items():\n", " print(f\"Historical ID for {identifier} as of {query_date}: {historical_id}\")\n", "except ValueError as e:\n", " print(f\"Error: {e}\")" ] }, { "cell_type": "markdown", "id": "bfd14a6a-7927-4f67-bda5-7b67f2ae699e", "metadata": {}, "source": [ "## Query by Identifier\n", "This example allows the user to quickly retrieve the latest available information about a specific security." ] }, { "cell_type": "code", "execution_count": null, "id": "263c5bf5-9338-4c11-a6a8-cd7c28c778b4", "metadata": {}, "outputs": [], "source": [ "data = GsSecurityMasterApi.get_security_data(id_value=\"GS UN\", id_type=SecurityIdentifier.BBID)\n", "pprint(data)" ] }, { "cell_type": "markdown", "id": "8c4a68e7-5e14-4dbb-8d35-8162a32d8e21", "metadata": {}, "source": [ "## Query by Attribute\n", "This example allows the user to specify an asset type or set of identifiers to retrieve matching securities." ] }, { "cell_type": "code", "execution_count": null, "id": "3c01274f-20b5-48f2-a050-3068ce3e0b8c", "metadata": {}, "outputs": [], "source": [ "data = GsSecurityMasterApi.get_many_securities(type_=SecMasterAssetType.Common_Stock, limit=1)\n", "results = data['results']\n", "pprint(results)" ] }, { "cell_type": "markdown", "id": "e398e263-5c5c-4517-9617-1d9e36ffd731", "metadata": {}, "source": [ "## Get All Securities\n", "This example is an expansive layer over 'Query by Attribute' and is tailored for retrieving an extensive list of assets within a specific type.\n", "It simplifies the user experience by automatically handling the scrolling.\n", "The output below shows the first two results for Dutch Certificates. This example was selected to optimize speed since there are comparably few assets of this type." ] }, { "cell_type": "code", "execution_count": null, "id": "e0e00289-a159-43a6-a4aa-e288f7934d49", "metadata": {}, "outputs": [], "source": [ "data = GsSecurityMasterApi.get_all_securities(type_=SecMasterAssetType.Dutch_Cert)\n", "pprint(data['results'][0:2])" ] }, { "cell_type": "markdown", "id": "c3b9d49e-5c55-4548-b696-277bcb7f0a94", "metadata": {}, "source": [ "## Get Identifier History\n", "This example provides a chronological account of how a particular identifier has changed for a security over time.\n", "This method only accepts 'id' as an input, which is the immutable Sec Master ID." ] }, { "cell_type": "code", "execution_count": null, "id": "cc3acf88-109f-4a61-959c-e31fe78055a8", "metadata": {}, "outputs": [], "source": [ "secm_id = \"GSPD227284E459\"\n", "pd.DataFrame(GsSecurityMasterApi.get_identifiers(secmaster_id=secm_id))" ] }, { "cell_type": "markdown", "id": "fb699965-a231-476d-ae8e-2de27b0dee77", "metadata": {}, "source": [ "## Map Securities\n", "This example converts between different types of security identifiers and accounts for historical changes." ] }, { "cell_type": "code", "execution_count": null, "id": "0391f211-7631-4d46-bcb5-241dcd5925ca", "metadata": {}, "outputs": [], "source": [ "pd.DataFrame(\n", " GsSecurityMasterApi.map(\n", " SecurityIdentifier.GSID,\n", " [\"40602012\"],\n", " start_date=\"2007-01-01\",\n", " end_date=\"2024-10-29\",\n", " output_types=[SecurityIdentifier.BBID, SecurityIdentifier.CUSIP, SecurityIdentifier.SEDOL],\n", " )\n", ")" ] }, { "cell_type": "markdown", "id": "29a1a54c-9286-41b0-afd6-a2eda9d49ebf", "metadata": {}, "source": [ "## Search Securities\n", "This example allows for full-text searches across many fields, including identifiers and company names." ] }, { "cell_type": "code", "execution_count": null, "id": "5cf5fc0b-53c7-478c-9f6c-55d6331edff8", "metadata": {}, "outputs": [], "source": [ "results = GsSecurityMasterApi.search(\n", " q=\"TESLA\", is_primary=True, active_listing=True, type_=SecMasterAssetType.Common_Stock\n", ")\n", "[\n", " f\"The bbid is: {data['identifiers']['bbid']} and the issuer name is: {data['issuer']['name']} and the type is: {data['type']}.\"\n", " for data in results\n", "]" ] }, { "cell_type": "markdown", "id": "6cc58f33-3768-4c4f-b6d7-15594f4e8c9c", "metadata": {}, "source": [ "## Deltas - Get Recent Changes\n", "This example retrieves the list of recent identifier changes within a specified time range." ] }, { "cell_type": "code", "execution_count": null, "id": "29454b40-bc16-444b-9541-3dfa1596c6af", "metadata": {}, "outputs": [], "source": [ "start_time = dt.datetime.now() - dt.timedelta(hours=1)\n", "data = GsSecurityMasterApi.get_deltas(raw=False, start_time=start_time)\n", "pd.DataFrame(data['results'])" ] }, { "cell_type": "markdown", "id": "450cac0b-7360-4a83-a366-0b66bb412248", "metadata": {}, "source": [ "## Issuers - Capital Structure\n", "This example provides all listed securities types for a given issuer. " ] }, { "cell_type": "code", "execution_count": null, "id": "54f6356a-a632-4357-b451-e9bb20ac76ac", "metadata": {}, "outputs": [], "source": [ "bbid = \"META UW\"\n", "data = GsSecurityMasterApi.get_capital_structure(id_value=bbid, id_type=SecurityIdentifier.BBID)\n", "res = data['results'][0]\n", "display(Markdown(f\"Capital structure for {bbid} matched {res['issuerName']}\"))\n", "display(pd.DataFrame([data['assetTypesTotal']]))\n", "for t in res['types']:\n", " display(Markdown(f\"**{t}**\"))\n", " display(pd.DataFrame(res['types'][t][:5]))" ] }, { "cell_type": "markdown", "id": "7351a972-cc65-4387-b18b-a3e210a104dd", "metadata": {}, "source": [ "## Exchanges\n", "This example retrieves detailed information about exchanges." ] }, { "cell_type": "code", "execution_count": null, "id": "9b5df6a8-547b-4c2b-bbf4-8064b8433e75", "metadata": {}, "outputs": [], "source": [ "data = GsSecurityMasterApi.get_exchanges()\n", "pd.DataFrame(data['results'][:5])" ] }, { "cell_type": "markdown", "id": "4986c8c5-d0ee-436e-ad08-919b72721a05", "metadata": {}, "source": [ "### Exchanges - Query by Exchange Code\n", "This example retrieves detailed information about exchanges by selected exchange codes." ] }, { "cell_type": "code", "execution_count": null, "id": "1ea8dcb6-5f57-47ca-b20a-7bc59f87c9e2", "metadata": {}, "outputs": [], "source": [ "data = GsSecurityMasterApi.get_exchanges(mic=\"XBUE\")\n", "pd.DataFrame(data['results'])" ] }, { "cell_type": "markdown", "id": "ef9b840f-0688-489e-b207-cf4327f584bc", "metadata": {}, "source": [ "### Exchanges - Get Exchange Code History\n", "This example provides the history of exchange codes.\n", "This method only accepts gsExchangeId as an input." ] }, { "cell_type": "code", "execution_count": null, "id": "2106a6f0-8578-458c-83cf-73af2500ccf0", "metadata": {}, "outputs": [], "source": [ "results = GsSecurityMasterApi.get_exchange_identifiers_history(gs_exchange_id=2)\n", "pd.DataFrame(results)" ] }, { "cell_type": "markdown", "id": "5ae9dd3a3e0d75ac", "metadata": { "collapsed": false }, "source": [ "## Listed Derivatives by Underlyer\n", "This example provides the listed derivatives for a given underlying security.\n", "This method only accepts 'id' as an input, which is the immutable Sec Master ID." ] }, { "cell_type": "code", "execution_count": null, "id": "cf9bfbdf004b8c63", "metadata": { "collapsed": false }, "outputs": [], "source": [ "underlyers = GsSecurityMasterApi.get_securities_by_underlyers(\n", " id_value=\"GSPD100E0\", limit=5, type_=SecMasterAssetType.Equity_Option\n", ")\n", "print(f\"Offset key is: {underlyers.get('offsetKey', 'No offset key returned')}.\")\n", "display(pd.DataFrame(underlyers['results']))\n", "\n", "if 'offsetKey' in underlyers:\n", " underlyers = GsSecurityMasterApi.get_securities_by_underlyers(\n", " id_value=\"GSPD100E0\", limit=5, type_=SecMasterAssetType.Equity_Option, offset_key=underlyers['offsetKey']\n", " )\n", " print(f\"Offset key is: {underlyers.get('offsetKey', 'No offset key returned')}.\")\n", " display(pd.DataFrame(underlyers['results']))" ] }, { "cell_type": "markdown", "id": "ae4b7b1c7bf29b1a", "metadata": { "collapsed": false }, "source": [ "### Listed Derivatives by Underlyer - fetch all matching securities\n", "This example gets all matching listed derivatives" ] }, { "cell_type": "code", "execution_count": null, "id": "2043ebbfaf7ddff7", "metadata": { "collapsed": false }, "outputs": [], "source": [ "underlyers = GsSecurityMasterApi.get_securities_by_underlyers(\n", " id_value=\"GSPD100E0\", type_=SecMasterAssetType.Equity_Option, scroll_all_pages=True\n", ")\n", "print(f\"Total assets count is: {underlyers.get('totalResults', 'No totalResults returned')}.\")\n", "display(pd.DataFrame(underlyers['results']))" ] }, { "cell_type": "markdown", "id": "f7705fd742bf8278", "metadata": { "collapsed": false }, "source": [ "### Listed Derivatives by Underlyer - Currency and Country Code Filter\n", "This example filters listed derivatives by currency and country code.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "aeb1d0666440513c", "metadata": { "collapsed": false }, "outputs": [], "source": [ "underlyers = GsSecurityMasterApi.get_securities_by_underlyers(\n", " id_value=\"GSPD100E0\", limit=5, type_=SecMasterAssetType.Equity_Option, currency='USD', country_code='US'\n", ")\n", "\n", "display(pd.DataFrame(underlyers['results']))" ] }, { "cell_type": "markdown", "id": "935b7da8ea4c1b50", "metadata": { "collapsed": false }, "source": [ "### Listed Derviatives by Underlyer - Effective Date Filter\n", "This example filters listed derivatives by effective date." ] }, { "cell_type": "code", "execution_count": null, "id": "753dc7694abe28ff", "metadata": { "collapsed": false }, "outputs": [], "source": [ "underlyers = GsSecurityMasterApi.get_securities_by_underlyers(\n", " id_value=\"GSPD100E0\",\n", " limit=5,\n", " type_=SecMasterAssetType.Equity_Option,\n", " effective_date='2023-01-01',\n", " country_code='US',\n", ")\n", "display(pd.DataFrame(underlyers['results']))" ] }, { "cell_type": "markdown", "id": "3bf6098b8a896034", "metadata": { "collapsed": false }, "source": [ "### Listed Derivatives by Underlyer - Inactive Filter\n", "This example includes inactive listed derivatives in the results." ] }, { "cell_type": "code", "execution_count": null, "id": "599c4f6601e35096", "metadata": { "collapsed": false }, "outputs": [], "source": [ "underlyers = GsSecurityMasterApi.get_securities_by_underlyers(\n", " id_value=\"GSPD100E0\", limit=5, type_=SecMasterAssetType.Equity_Option, include_inactive=True, country_code='US'\n", ")\n", "display(pd.DataFrame(underlyers['results']))" ] }, { "cell_type": "code", "execution_count": null, "id": "e59a4ce1bcdf63d1", "metadata": { "collapsed": false }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.2" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/09_security_master/Security Master.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "b9e06507", "metadata": {}, "source": [ "**NOTE**: Legacy documentation\n", "\n", "## Introduction\n", "A Security Master provides financial product reference data and is a foundational building block of any quantitative financial analytics engine. The core component of Goldman Sachs' Security Master is symbology, the data necessary to map from one identifier to another. Clients can retrieve this data via the GS Quant SDK." ] }, { "cell_type": "markdown", "id": "0d9844f7", "metadata": {}, "source": [ "## Getting Started\n", "Set up a GsSession. You must input the client_id and client_secret of an application with access to Security Master. For more information on the product and on how to get access, please contact [GS Data Services](mailto:data-services@gs.com)." ] }, { "cell_type": "code", "execution_count": null, "id": "333f29f9", "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession\n", "\n", "GsSession.use(client_id='client_id', client_secret='client_secret')" ] }, { "cell_type": "markdown", "id": "be357aad", "metadata": {}, "source": [ "Set source to Security Master (or else it will default to Asset Service)." ] }, { "cell_type": "code", "execution_count": null, "id": "a9288aa9", "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.securities import SecurityMaster, SecurityMasterSource\n", "\n", "SecurityMaster.set_source(SecurityMasterSource.SECURITY_MASTER)" ] }, { "cell_type": "markdown", "id": "e23cde3a", "metadata": {}, "source": [ "## Querying\n", "Retrieve a security by identifier. The get_asset method returns identifiers at a point in time specified by the `as_of` parameter (which should be set to 2021-01-01 or later, given the history available in Security Master)." ] }, { "cell_type": "code", "execution_count": null, "id": "08cc971f", "metadata": {}, "outputs": [], "source": [ "import datetime\n", "from gs_quant.markets.securities import SecurityIdentifier, SecurityMaster\n", "\n", "asset = SecurityMaster.get_asset('GS UN', SecurityIdentifier.BBID, as_of=datetime.date(2021, 1, 5))\n", "print(asset, '\\n')\n", "asset.get_identifiers()" ] }, { "cell_type": "markdown", "id": "a00d2241", "metadata": {}, "source": [ "You can use SecurityIdentifier members to pull out identifier values." ] }, { "cell_type": "code", "execution_count": null, "id": "31696f61", "metadata": {}, "outputs": [], "source": [ "asset.get_identifiers()[SecurityIdentifier.BBID.value]" ] }, { "cell_type": "markdown", "id": "397888ae", "metadata": {}, "source": [ "## Bulk Querying\n", "\n", "#### History\n", "You can retrieve identifier history (from 2021-01-01 onwards) for a list of assets. The following example also shows a ticker change." ] }, { "cell_type": "code", "execution_count": null, "id": "002a5ab4", "metadata": {}, "outputs": [], "source": [ "import datetime\n", "from gs_quant.markets.securities import SecurityIdentifier, SecurityMaster\n", "\n", "ids = ['CTLP UW', 'TSLA UW', 'FB UW']\n", "as_of = datetime.datetime(2021, 6, 5) # as-of-date used to map input IDs to assets\n", "start = datetime.datetime(2000, 1, 5) # get identifier entries with update time after this time\n", "end = as_of # get identifier entries with update time before this time\n", "identifiers = SecurityMaster.get_identifiers(ids, SecurityIdentifier.BBID, as_of=as_of, start=start, end=end)\n", "[identifier for identifier in identifiers['CTLP UW'] if identifier['type'] == SecurityIdentifier.TICKER.value]" ] }, { "cell_type": "markdown", "id": "93597208", "metadata": {}, "source": [ "#### Point in Time\n", "\n", "You can retrieve point-in-time identifiers without specifying individual assets." ] }, { "cell_type": "code", "execution_count": null, "id": "5629db2b", "metadata": {}, "outputs": [], "source": [ "import datetime\n", "from gs_quant.markets.securities import AssetType, SecurityMaster\n", "from gs_quant.target.common import AssetClass\n", "\n", "as_of = datetime.datetime(2021, 5, 1)\n", "identifiers = SecurityMaster.get_all_identifiers(AssetClass.Equity, [AssetType.STOCK], as_of=as_of)\n", "identifiers[next(iter(identifiers))] # show identifiers for one of the retrieved securities" ] }, { "cell_type": "markdown", "id": "7fb27b941602401d91542211134fc71a", "metadata": { "collapsed": false }, "source": [ "You can also use a generator to retrieve each page of results when needed - just call `next()`." ] }, { "cell_type": "code", "execution_count": null, "id": "acae54e37e7d407bbb7b55eff062a284", "metadata": { "collapsed": false }, "outputs": [], "source": [ "import datetime\n", "from gs_quant.markets.securities import SecurityMaster\n", "\n", "generator = SecurityMaster.get_all_identifiers_gen(as_of=datetime.datetime(2021, 5, 1))\n", "page_1 = next(generator)\n", "page_2 = next(generator)\n", "page_3 = next(generator)\n", "# and so on... (keep in mind that a generator will raise StopIteration when it is done)" ] }, { "cell_type": "markdown", "id": "9a63283cbaf04dbcab1f6479b197f3a8", "metadata": { "collapsed": false }, "source": [ "## Mapping Between Identifiers\n", "\n", "To map from one ID to other IDs for the same security, use the `map_identifiers` function.\n", "For example, if we have a security \"GS UN\" and want to find its CUSIP over a certain date range: " ] }, { "cell_type": "code", "execution_count": null, "id": "8dd0d8092fe74a7c96281538738b07e2", "metadata": { "collapsed": false }, "outputs": [], "source": [ "import datetime\n", "from gs_quant.markets.securities import SecurityMaster\n", "\n", "start = datetime.date(2021, 10, 11)\n", "end = datetime.date(2021, 10, 15)\n", "SecurityMaster.map_identifiers([\"GS UN\"], [SecurityIdentifier.CUSIP], start, end)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.10" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/10_one_delta/Hedger/01_Performance_Hedger.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "# Performance Hedge\n", "\n", "The Performance Hedge provides a framework to replicate an asset's performance without\n", "direct exposure by using historical performance correlation.\n", "\n", "## Step 1: Authenticate and Initialize Your Session\n", "\n", "First you will import the necessary modules and add your client id and client secret." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "import datetime as dt\n", "from IPython.display import display\n", "import warnings\n", "\n", "from gs_quant.markets.hedge import (\n", " HedgeConstraints,\n", " HedgeExclusions,\n", " Constraint,\n", " PerformanceHedge,\n", " PerformanceHedgeParameters,\n", ")\n", "from gs_quant.markets.position_set import Position, PositionSet\n", "from gs_quant.session import GsSession, Environment\n", "from gs_quant.timeseries.helper import Window\n", "from gs_quant.timeseries.econometrics import correlation\n", "\n", "client = None\n", "secret = None\n", "\n", "## External users must fill in their client ID and secret below and comment out the lines below\n", "\n", "# client = 'ENTER CLIENT ID'\n", "# secret = 'ENTER CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret)\n", "warnings.filterwarnings(\"ignore\", category=RuntimeWarning)\n", "\n", "print('GS Session initialized.')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 2: Define Your Positions to Hedge and Your Hedge Universe\n", "\n", "The hedger takes in the initial portfolio as a `PositionSet` object. You can define your positions\n", "as a list of identifiers with quantities or alternatively, as a list of identifiers with weights, along with a\n", "reference notional value. The `date` corresponds to the hedge date.\n", "\n", "In addition, you need to define your universe of candidates for hedge constituents as a list of identifiers.\n", "\n", "*GS Quant will resolve all identifiers (Bloomberg IDs, SEDOLs, RICs, etc) historically as of the hedge date*" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "positions = PositionSet(\n", " date=dt.date(day=9, month=4, year=2025),\n", " positions=[Position(identifier='AAPL UW', quantity=26), Position(identifier='GS UN', quantity=51)],\n", ")\n", "\n", "positions.resolve()\n", "\n", "universe = ['SPY']" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 3: Define Your Hedge Exclusions\n", "\n", "The `HedgeExclusions` class offers a clean way to specify any assets, countries, regions, sectors, and/or industries you\n", "would like to exclude from your hedge. Each attribute takes in a list of strings. In this example, let's try excluding\n", "Goldman Sachs and all assets in Mexico, Europe, the Utilities sector, the Airlines industry." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "exclusions = HedgeExclusions(\n", " assets=['GS UN'], countries=['Mexico'], regions=['Europe'], sectors=['Utilities'], industries=['Airlines']\n", ")" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 4: Define Your Hedge Constraints\n", "\n", "Rather than excluding an industry or region entirely, you can also constrain how much each makes up in\n", "your hedge, and you can do so by leveraging the `HedgeConstraints` class. Each attribute takes in a list of `Constraint`\n", "objects, each of which has a `name` attribute, along with `minimum` and `maximum` that should be expressed as positive\n", "numbers between 0 and 100.\n", "\n", "In addition to constraining assets, countries, regions, sectors, and industries, it's also\n", "possible to constrain your hedge to only include assets that have an ESG score between a specified range. All data is\n", "pulled as of your hedge date from the GIR SUSTAIN ESG Headline Metrics Dataset. For more information on the various\n", "ESG metrics available, please visit the dataset page\n", "[here](https://marquee.gs.com/s/developer/datasets/ESG_HEADLINE_METRICS).\n", "\n", "In this example, let's constrain our hedge to only include at most 20% Software assets by weight. In addition, let's\n", "request a hedge with only assets that have a G Headline Percentile Score of above 75%." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "constraints = HedgeConstraints(\n", " sectors=[Constraint(constraint_name='Software', minimum=0, maximum=100)],\n", " esg=[Constraint(constraint_name='gPercentile', minimum=0, maximum=100)],\n", ")" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 5: Define Any Other Parameters\n", "\n", "The `PerformanceHedgeParameters` wraps all the performance hedge parameters, including the positions, exclusions, and\n", "constraints, into an object to be passed into a `PerformanceHedge`. Along with the parameters defined above, the\n", "following optional parameters can also be passed in:\n", "\n", "| Parameter | Description | Type| Default Value|\n", "|-----------------|---------------|-------------|-------------\n", "| `observation_start_date` | Date on which to start the observation of historical performance correlation |`datetime.date`| One year before the hedge date |\n", "| `sampling_period` | The length of time in between return samples |`str`| 'Daily' |\n", "| `max_leverage` | Maximum percentage of the notional that can be used to hedge | `float` | 100 |\n", "| `percentage_in_cash` | Percentage of the hedge notional that will be in cash | `float` | None |\n", "| `explode_universe` | Explode the assets in the universe into their underliers to be used as the hedge universe | `boolean` | True |\n", "| `exclude_target_assets` | Exclude assets in the target composition from being in the hedge |`boolean`| True |\n", "| `exclude_corporate_actions_types` | Set of of corporate actions to be excluded in the hedge |`List[CorporateActionsTypes]`| None |\n", "| `exclude_hard_to_borrow_assets` | Whether hard to borrow assets should be excluded in the universe | `boolean` | False |\n", "| `exclude_restricted_assets` | Whether to exclude assets in restricted trading lists | `float` | False |\n", "| `max_adv_percentage` | Maximum percentage notional to average daily dollar volume allowed for any hedge constituent | `float` | 15 |\n", "| `max_return_deviation` | Maximum percentage difference in annualized return between the target and the hedge result |`float`| 5 |\n", "| `max_weight` | Maximum weight of any constituent in hedge |`float`| 100 |\n", "| `min_market_cap` | Lowest market cap allowed for any hedge constituent | `float` | None |\n", "| `max_market_cap` | Highest market cap allowed for any hedge constituent | `float` | None |\n", "| `market_participation_rate` | Maximum market participation rate used to estimate the cost of trading a portfolio of stocks | `float` | 10 |\n", "| `lasso_weight` | Value of the lasso hyperparameter for machine learning hedges |`float`| 0 |\n", "| `ridge_weight` | Value of the ridge hyperparameter for machine learning hedges |`float`| 0 |\n", "| `benchmarks` | List of benchmarks to compare the backtest of your hedge against for correlation and performance( eg ['SPX', 'RAY'] ) |`List[String]`| None |\n" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "parameters = PerformanceHedgeParameters(\n", " initial_portfolio=positions,\n", " universe=universe,\n", " exclusions=exclusions,\n", " constraints=constraints,\n", ")" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 6: Calculate Your Hedge\n", "\n", "It's finally time to run the parameters into the Marquee Hedger. Once defined, a `PerformanceHedge` can be calculated\n", "in just one line.\n" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "hedge = PerformanceHedge(parameters)\n", "all_results = hedge.calculate()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Note:\n", "The above code can fail with an error message along the lines of:\n", "\"Error calculating hedge: Size( Universe Price Curves ) : Universe has no price curves.. Please adjust your constraints and try again.\"\n", "\n", "This means that no optimization was found, modify your hedge parameters by loosening your constraints and try again." ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 7: Pull Hedge Results\n", "\n", "That's it! Once calculated, you can pull the results right from the `PerformanceHedge` object.\n", "\n", "### Constituents\n", "Let's pull the constituents metadata of the resulting hedge:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "hedge_constituents = hedge.get_constituents()\n", "display(hedge_constituents)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Stats\n", "\n", "Next let's pull a table of general stats like transaction cost, annualized volatility, annualized return, and more:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "stats = hedge.get_statistics()\n", "display(stats)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, "source": [ "### Backtest Performance\n", "\n", "It's also possible to pull a timeseries of the performance of both the initial portfolio and the hedge for the\n", "observation period:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "backtest_performance = hedge.get_backtest_performance()\n", "\n", "backtest_performance.plot(title='Backtest Performance')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "If benchmarks are provided the performance curves for the benchmarks are returned as well\n", "\n", "### Backtest Correlation\n", "\n", "It's also possible to pull a timeseries of the correlation between the hedge and portfolio for the\n", "observation period by leveraging GS Quant econometric function `correlation`:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "backtest_correlation = correlation(backtest_performance['Portfolio'], backtest_performance['Hedge'], Window(44, 0))\n", "backtest_correlation.plot(title='Backtest Correlation')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "If benchmarks are provided the correlation curves for the benchmarks are returned as well\n", "\n", "### You're all set; Congrats!\n", "\n", "*Other questions? Reach out to the [Portfolio Analytics team](mailto:gs-marquee-analytics-support@gs.com)!*" ] } ], "metadata": { "kernelspec": { "display_name": "venv312 (3.12.1)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.1" } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/10_one_delta/Hedger/02_Factor_Hedger_(Axioma_Portfolio_Optimizer).ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": true, "jupyter": { "outputs_hidden": true }, "pycharm": { "name": "#%% md\n" } }, "source": [ "# Portfolio Optimizer\n", "\n", "The portfolio optimizer brings together the power of the Axioma Portfolio Optimizer with Marquee's risk analytics infrastructure\n", "to make minimizing your portfolio's factor risk possible within the same ecosystem.\n", "\n", "The optimizer supports two hedging modes:\n", "- **Unidirectional Hedger**: Generates only short positions to hedge your portfolio (default mode)\n", "- **Bidirectional Hedger**: Generates both long and short positions for more flexible factor risk management\n", "\n", "To use the optimizer, you must have a license to the Axioma Portfolio Optimizer. Please reach out to the\n", "[Marquee sales team](mailto:gs-marquee-sales@ny.email.gs.com?Subject=Portfolio Optimizer Trial Request)\n", "to learn more about how to get a license or how to bring an existing license to Marquee.\n", "\n", "## Step 1: Authenticate and Initialize Your Session\n", "\n", "First you will import the necessary modules and add your client id and client secret." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "import datetime as dt\n", "import pandas as pd\n", "\n", "from IPython.display import display\n", "import warnings\n", "from gs_quant.markets.optimizer import (\n", " OptimizerUniverse,\n", " FactorConstraint,\n", " AssetConstraint,\n", " SectorConstraint,\n", " IndustryConstraint,\n", " OptimizerSettings,\n", " OptimizerStrategy,\n", " OptimizerConstraints,\n", " OptimizerObjective,\n", " OptimizerType,\n", " OptimizerObjectiveTerm,\n", " OptimizerObjectiveParameters,\n", " ConstraintPriorities,\n", " PrioritySetting,\n", " CountryConstraint,\n", " HedgeTarget,\n", ")\n", "from gs_quant.markets.position_set import Position, PositionSet\n", "from gs_quant.markets.securities import Asset, AssetIdentifier\n", "from gs_quant.models.risk_model import FactorRiskModel\n", "from gs_quant.session import GsSession, Environment\n", "from gs_quant.target.hedge import CorporateActionsTypes\n", "from gs_quant.markets.factor_analytics import FactorAnalytics\n", "from gs_quant.markets.portfolio_manager import PortfolioManager\n", "from gs_quant.markets.report import ReturnFormat\n", "from gs_quant.api.gs.hedges import GsHedgeApi\n", "\n", "\n", "client = None\n", "secret = None\n", "\n", "## External users must fill in their client ID and secret below and comment out the line below\n", "\n", "# client = 'ENTER CLIENT ID'\n", "# secret = 'ENTER CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret)\n", "warnings.filterwarnings(\"ignore\", category=RuntimeWarning)\n", "\n", "print('GS Session initialized.')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 2: Define Your Initial Position Set\n", "\n", "You have two options for defining the initial holdings to optimize:\n", "\n", "**Option A**: Create positions manually using identifiers and weights/quantities\n", "**Option B**: Load positions from an existing Marquee portfolio\n", "\n", "Choose the option that best fits your workflow. Both approaches will create a `PositionSet` object that can be used in the optimization.\n", "\n", "*GS Quant will resolve all identifiers (Bloomberg IDs, SEDOLs, RICs, etc) historically as of the optimization date.*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Option A: Create New Position Set\n", "\n", "Use the `PositionSet` class to define positions manually with identifiers and weights or quantities." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Option A: Create positions manually\n", "position_set = PositionSet(\n", " date=dt.date(day=23, month=4, year=2024),\n", " reference_notional=10_000_000,\n", " positions=[Position(identifier='AAPL UW', weight=0.4), Position(identifier='GS UN', weight=0.6)],\n", ")\n", "\n", "position_set.resolve()\n", "print(f'Created position set with {len(position_set.positions)} positions')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Option B: Load Positions from Marquee Portfolio\n", "\n", "Load positions from an existing Marquee portfolio using the portfolio ID. This is useful when you want to optimize an existing portfolio that's already managed in Marquee." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Option B: Load from existing Marquee portfolio using positions data endpoint\n", "portfolio_id = 'YOUR PORTFOLIO ID HERE'\n", "optimization_date = dt.date(day=11, month=11, year=2025)\n", "\n", "# Load portfolio positions using the positions data endpoint\n", "pm = PortfolioManager(portfolio_id)\n", "\n", "# Get positions data for the specific date with all relevant fields\n", "portfolio_report = pm.get_performance_report()\n", "\n", "positions = portfolio_report.get_portfolio_constituents(\n", " start_date=optimization_date,\n", " end_date=optimization_date,\n", " fields=['grossWeight', 'quantity'],\n", " return_format=ReturnFormat.JSON,\n", ")\n", "\n", "# Create position set from the filtered positions\n", "position_set = PositionSet(\n", " date=optimization_date,\n", " positions=[\n", " Position(\n", " asset_id=row['assetId'],\n", " identifier=row['assetId'],\n", " quantity=row.get('quantity'),\n", " weight=row.get('grossWeight'),\n", " )\n", " for row in positions\n", " ],\n", ")\n", "print(f'Created position set with {len(position_set.positions)} positions')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Step 2.1: Analyze Initial Portfolio Risk Exposures\n", "\n", "Analyze the risk exposures of the initial portfolio using the Marquee PreTrade." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Set parameters for factor analysis in PreTrade\n", "risk_model_id = 'AXIOMA_AXUS4S'\n", "currency = 'USD'\n", "participation_rate = 0.15\n", "\n", "analytics = FactorAnalytics(risk_model_id=risk_model_id, currency=currency, participation_rate=participation_rate)\n", "\n", "print('Running risk analysis on initial portfolio...')\n", "try:\n", " initial_analysis = analytics.get_factor_analysis(position_set)\n", "\n", " print('\\nInitial Portfolio Summary:')\n", " summary_table = analytics.create_exposure_summary_table(initial_analysis)\n", " display(summary_table)\n", "except Exception as e:\n", " print('\\nERROR: Factor analysis failed\\n')\n", " print(f'{str(e)}')\n", " raise" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Step 2.2: Visualize Initial Portfolio Style Factor Exposures\n", "\n", "View the top contributing style factors in your initial portfolio." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig = analytics.create_style_factor_chart(initial_analysis, rows=5, title='Initial Portfolio - Style Factor Exposures')\n", "fig.show()\n", "\n", "print('\\nStyle factors displayed (e.g., Volatility, Market Sensitivity, Momentum, Size, Value, etc.)')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Step 2.3: Historical Performance Metrics\n", "\n", "View dynamic performance metrics for the initial portfolio over the past year." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Create dynamic performance chart with time series data from liquidity endpoint\n", "# Shows cumulative PnL and normalized performance over time\n", "performance_chart = analytics.create_dynamic_performance_chart(\n", " initial_analysis, title='Initial Portfolio - Historical Performance Metrics'\n", ")\n", "performance_chart.show()\n", "\n", "print('\\nUse the dropdown menu above to toggle between:')\n", "print('- Cumulative PnL')\n", "print('- Normalized Performance')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 3: Define Your Optimizer Universe\n", "\n", "An optimizer universe corresponds to the assets that can be used when constructing an optimization, which can be created\n", "using the `OptimizerUniverse` class:\n", "\n", "| Parameter | Description | Type| Default Value|\n", "|-----------------|---------------|-------------|-------------\n", "| `assets` | Assets to include in the universe. |`List[Asset]`| N/A |\n", "| `explode_composites` | Explode indices, ETFs, and baskets and include their underliers in the universe. |`boolean`| `True` |\n", "| `exclude_initial_position_set_assets` | Exclude assets in the initial holdings from the optimization. | `boolean` | `False` |\n", "| `exclude_corporate_actions_types` | Set of of corporate actions to be excluded in the universe. |`List[CorporateActionsTypes]`| `[]` |\n", "| `exclude_hard_to_borrow_assets` | Exclude hard to borrow assets from the universe. | `boolean` | `False` |\n", "| `exclude_restricted_assets` | Exclude assets on restricted trading lists from the universe. | `boolean` | `False` |\n", "| `min_market_cap` | Lowest market cap allowed for any universe constituent. | `float` | `None` |\n", "| `max_market_cap` | Highest market cap allowed for any universe constituent. | `float` | `None` |" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "asset_list = ['SPY US']\n", "asset_list_resolved = [Asset.get(x, AssetIdentifier.BLOOMBERG_COMPOSITE_ID) for x in asset_list]\n", "\n", "universe = OptimizerUniverse(\n", " assets=asset_list_resolved,\n", " explode_composites=True,\n", " exclude_initial_position_set_assets=True,\n", " exclude_restricted_assets=True,\n", " exclude_hard_to_borrow_assets=True,\n", " exclude_corporate_actions_types=[CorporateActionsTypes.Mergers],\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 4: Define Your Risk Model and Factor Risk Constraints\n", "\n", "You can run the optimizer using a factor risk model of your choice, so long as you have a license for it, by leveraging\n", "the `FactorRiskModel` class. For any factor in the risk model, you can set more granular constraints on the optimized\n", "portfolio's exposure to the factor.\n", "\n", "In this example, let's use the Axioma AXUS4S model and limit the final exposure to Volatility be $10,000 and\n", "the final exposure of Market Sensitivity to be 5,000." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Pulling Factor Names from Risk Models\n", "\n", "You can run the code below to pull the factor names for your model. " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "risk_model = FactorRiskModel.get('AXIOMA_AXUS4S')\n", "\n", "risk_model_factors = risk_model.get_many_factors(dt.date(2025, 4, 9))\n", "risk_model_factors_names = pd.DataFrame([x.name for x in risk_model_factors])\n", "pd.set_option('display.max_rows', 87)\n", "print(risk_model_factors_names)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Set your Factor Constraints\n", "\n", "Note that different models might have different sets of factor names, if the code below doesn't work please confirm that the factor name used exists in the Risk Model as shown above. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "factor_constraints = [\n", " FactorConstraint(risk_model.get_factor('Volatility'), 10000),\n", " FactorConstraint(risk_model.get_factor('Market Sensitivity'), 5000),\n", "]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 5: Define Other Optimization Constraints\n", "\n", "Outside factor-specific constraints, it's also possible to limit the holding value of individual assets, assets\n", "belonging to a particular GICS sector, and/or assets in a particular country of domicile in the optimization.\n", "\n", "In this example, let's constrain the optimization to have 0-50% Microsoft and limit the optimization's notional\n", "coming from Energy and Health Care assets to each be 0-80%. We also limit exposure from gics industry (one level more granular than sector) to be from [0, 50%]" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "asset_constraints = [AssetConstraint(Asset.get('MSFT UW', AssetIdentifier.BLOOMBERG_ID), 0, 50)]\n", "\n", "sector_constraints = [SectorConstraint('Energy', 0, 80), SectorConstraint('Health Care', 0, 80)]\n", "industry_constraints = [IndustryConstraint('Energy Equipment & Services', 0, 50)]\n", "country_constraints = [CountryConstraint('United States', 0, 100)]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 6: Configure Your Optimization Settings\n", "\n", "All other settings for the optimization can be set via the `OptimizerSettings` class:\n", "\n", "### Unidirectional Hedger (Default)\n", "Returns only short positions to hedge the target portfolio. Use `allow_long_short=False` (default).\n", "\n", "### Bidirectional Hedger\n", "Returns both long and short positions for more flexible hedging. Use `allow_long_short=True` and specify `gross_notional` and `net_notional`.\n", "\n", "| Parameter | Description | Type| Default Value|\n", "|--------------------|---------------|-------------|-------------\n", "| `notional` | For unidirectional hedges: max notional of hedge (all short positions). For bidirectional: overridden by `gross_notional` |`float`| `10000000` |\n", "| `allow_long_short` | Enable bidirectional hedge mode with both long and short positions |`boolean`| `False` |\n", "| `gross_notional` | Total absolute notional (\\|long\\| + \\|short\\|). Only for bidirectional mode. |`float`| `None` |\n", "| `net_notional` | Net notional (long - short). Required for bidirectional mode. Use 0 for market neutral. |`float`| `None` |\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Define Constraint Priorities\n", "\n", "Priority of the constraint range from 0-5 (prioritized in that order). \n", "The optimization will fail if it cannot meet a constraint with 0 priority. \n", "A constraint with priority of 1-5 can be called a relaxed constraint, which means that the optimization will make its best effort to meet the constraint but will not fail if it cannot. \\\n", "A constraint with a lower priority will take precedence over a constraint with a higher priority. \n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "constraint_priorities = ConstraintPriorities(\n", " style_factor_exposures=PrioritySetting.ZERO,\n", " min_sector_weights=PrioritySetting.ONE,\n", " max_sector_weights=PrioritySetting.ONE,\n", " min_industry_weights=PrioritySetting.ONE,\n", " max_industry_weights=PrioritySetting.ONE,\n", " min_country_weights=PrioritySetting.ONE,\n", " max_country_weights=PrioritySetting.ONE,\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### OPTION A: Unidirectional Hedger Configuration\n", "\n", "Let's put above parameters in action. In the cell below we configure a **unidirectional hedger** that:\n", "\n", " * Uses tight style exposure constraints\n", " * Can relax sector, industry, country weight constraints if needed to find a feasible solution\n", " * Allows 0-100 names in the hedge\n", " * Sets weight range for constituents from hedge from [1%, 99%]\n", " * Limits max daily ADV to less than 15%\n", " * Uses one-sided hedge (short positions only) as allow_long_short is turned off\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Configure unidirectional hedger (short positions only)\n", "settings = OptimizerSettings(\n", " allow_long_short=False, # unidirectional mode: hedge contains only short positions\n", " constraint_priorities=constraint_priorities,\n", " min_names=0,\n", " max_names=100,\n", " min_weight_per_constituent=0.01,\n", " max_weight_per_constituent=0.99,\n", " max_adv=15,\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### OPTION B: Bidirectional Hedger Configuration\n", "\n", "For a **bidirectional hedge** that can take both long and short positions, configure the settings as follows:\n", "\n", "**Key differences:**\n", "- Set `allow_long_short=True` to enable both long and short positions\n", "- Use `gross_notional` to control total hedge size (|long| + |short|)\n", "- Use `net_notional` to control net exposure (long - short)\n", " - Set `net_notional=0` for market neutral hedge\n", " - Set `net_notional > 0` for net long bias\n", " - Set `net_notional < 0` for net short bias\n", "\n", "\n", "**Examples:**\n", "- For a net long bias example: gross_notional=20M, net_notional=10M (e.g., 15M long + 5M short)\n", "- For a net short bias example: gross_notional=20M, net_notional=-10M (e.g., 5M long + 15M short)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Example: Bidirectional Hedger with Market Neutral Configuration\n", "settings = OptimizerSettings(\n", " allow_long_short=True, # Enable bidirectional mode\n", " gross_notional=20_000_000, # Total |long| + |short| = 20M\n", " net_notional=0, # Market neutral: long - short = 0 (e.g., 10M long + 10M short)\n", " constraint_priorities=constraint_priorities,\n", " max_names=600,\n", " max_adv=15,\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 7: Create and Run a Strategy\n", "\n", "It's finally time to take all these parameters and construct an optimizer strategy using the `OptimizerStrategy` class in which all the building blocks configured from previous steps are put together. \n", "\n", " * initial portfolio\n", " * style, sector, industry and country constraints\n", " * constraint priorities\n", " * optimization settings with number of names, weight limits and liquitdy limits on a one sided hedge\n", " * optimization objectives to minimize on factor risk only\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "constraints = OptimizerConstraints(\n", " asset_constraints=asset_constraints,\n", " sector_constraints=sector_constraints,\n", " factor_constraints=factor_constraints,\n", " industry_constraints=industry_constraints,\n", ")\n", "\n", "# define specific risk penalty to be 0 to optimize on factor risk only\n", "risk_term = OptimizerObjectiveTerm(params={'specific_weight': 0, 'factor_weight': 1})\n", "objective_parameters = OptimizerObjectiveParameters(terms=[risk_term])\n", "\n", "strategy = OptimizerStrategy(\n", " initial_position_set=position_set,\n", " constraints=constraints,\n", " settings=settings,\n", " universe=universe,\n", " risk_model=risk_model,\n", " objective=OptimizerObjective.MINIMIZE_FACTOR_RISK,\n", " objective_parameters=objective_parameters,\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Option A: Run the hedge with option to save and share it" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Run the optimization and capture the request/response for saving later\n", "strategy_request, optimization_response = strategy.run_save_share(\n", " optimizer_type=OptimizerType.AXIOMA_PORTFOLIO_OPTIMIZER\n", ")\n", "\n", "# Get hedge and hedged portfolio\n", "hedge = strategy.get_optimization() # Returns just the optimization results as a PositionSet object\n", "hedged_portfolio = strategy.get_optimized_position_set()\n", "\n", "print(f'\\nHedge Portfolio: {len(hedge.positions)} positions')\n", "hedge_df = pd.DataFrame(\n", " [\n", " {\n", " 'Asset': p.identifier,\n", " 'Quantity': f'{p.quantity:.0f}' if p.quantity else '',\n", " 'Weight': f'{p.weight:.2%}' if p.weight else '',\n", " }\n", " for p in hedge.positions\n", " ]\n", ")\n", "display(hedge_df)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Option B: Run the hedge with NO option to save and share it" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Run the optimization and capture the request/response for saving later\n", "strategy.run(optimizer_type=OptimizerType.AXIOMA_PORTFOLIO_OPTIMIZER)\n", "\n", "# Get hedge and hedged portfolio\n", "hedge = strategy.get_optimization() # Returns just the optimization results as a PositionSet object\n", "hedged_portfolio = strategy.get_optimized_position_set()\n", "\n", "print(f'\\nHedge Portfolio: {len(hedge.positions)} positions')\n", "hedge_df = pd.DataFrame(\n", " [\n", " {\n", " 'Asset': p.identifier,\n", " 'Quantity': f'{p.quantity:.0f}' if p.quantity else '',\n", " 'Weight': f'{p.weight:.2%}' if p.weight else '',\n", " }\n", " for p in hedge.positions\n", " ]\n", ")\n", "display(hedge_df)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Analyze Hedge Composition\n", "\n", "View detailed exposure metrics for the hedge, including gross/net/long/short exposures. This is especially useful for long/short hedges." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Get detailed exposure summary\n", "exposure_summary = strategy.get_hedge_exposure_summary()\n", "\n", "print('=' * 80)\n", "print('HEDGE EXPOSURE SUMMARY')\n", "print('=' * 80)\n", "\n", "# Display hedge information\n", "hedge_info = exposure_summary['hedge']\n", "print(f\"\\nHedge Mode: {hedge_info.get('mode', 'N/A')}\")\n", "print(f\"Number of Positions: {hedge_info.get('number_of_positions', 0)}\")\n", "print(f\"Gross Exposure: ${hedge_info.get('gross_exposure', 0):,.0f}\")\n", "print(f\"Net Exposure: ${hedge_info.get('net_exposure', 0):,.0f}\")\n", "print(f\"Long Exposure: ${hedge_info.get('long_exposure', 0):,.0f}\")\n", "print(f\"Short Exposure: ${hedge_info.get('short_exposure', 0):,.0f}\")\n", "\n", "# Display target portfolio information\n", "print('\\n' + '-' * 80)\n", "print('TARGET PORTFOLIO')\n", "print('-' * 80)\n", "target_info = exposure_summary['target']\n", "print(f\"Number of Positions: {target_info.get('number_of_positions', 0)}\")\n", "print(f\"Gross Exposure: ${target_info.get('gross_exposure', 0):,.0f}\")\n", "print(f\"Net Exposure: ${target_info.get('net_exposure', 0):,.0f}\")\n", "\n", "# Display hedged portfolio information\n", "print('\\n' + '-' * 80)\n", "print('HEDGED TARGET PORTFOLIO (Target + Hedge)')\n", "print('-' * 80)\n", "hedged_info = exposure_summary['hedged_target']\n", "print(f\"Number of Positions: {hedged_info.get('number_of_positions', 0)}\")\n", "print(f\"Gross Exposure: ${hedged_info.get('gross_exposure', 0):,.0f}\")\n", "print(f\"Net Exposure: ${hedged_info.get('net_exposure', 0):,.0f}\")\n", "print(f\"Long Exposure: ${hedged_info.get('long_exposure', 0):,.0f}\")\n", "print(f\"Short Exposure: ${hedged_info.get('short_exposure', 0):,.0f}\")\n", "print('=' * 80)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Split Hedge Constituents by Direction\n", "\n", "For bidirectional hedges, view the long and short positions separately to understand each side of the hedge." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Split hedge into long and short positions\n", "constituents_by_direction = strategy.get_hedge_constituents_by_direction()\n", "\n", "summary = constituents_by_direction['summary']\n", "long_positions = constituents_by_direction['long_positions']\n", "short_positions = constituents_by_direction['short_positions']\n", "\n", "print(\"\\nHedge Breakdown:\")\n", "print(f\" Long Positions: {summary['num_long']} positions, Total Notional: ${summary['total_long_notional']:,.0f}\")\n", "print(f\" Short Positions: {summary['num_short']} positions, Total Notional: ${summary['total_short_notional']:,.0f}\")\n", "\n", "if len(long_positions) > 0:\n", " print(\"\\nTop 10 Long Positions:\")\n", " display(long_positions[['assetId', 'name', 'notional']].head(10))\n", "else:\n", " print(\"\\nNo long positions in hedge (Unidirectional mode: short positions only)\")\n", "\n", "if len(short_positions) > 0:\n", " print(\"\\nTop 10 Short Positions:\")\n", " display(short_positions[['assetId', 'name', 'notional']].head(10))\n", "else:\n", " print(\"\\nNo short positions in hedge\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Step 7.1: Analyze Hedged Portfolio Performance\n", "\n", "View the cumulative PnL performance of the hedged portfolio (target + hedge combined)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "performance_df = strategy.get_cumulative_pnl_performance(HedgeTarget.HEDGED_TARGET)\n", "\n", "if not performance_df.empty:\n", " hedge_mode = 'Bidirectional' if settings.allow_long_short else 'Unidirectional (Short Positions Only)'\n", " print(f'\\nHedge Mode: {hedge_mode}')\n", " print('Hedged Portfolio Cumulative PnL Performance:')\n", " fig = analytics.create_performance_chart(\n", " performance_df, metric='cumulativePnl', title=f'Hedged Portfolio - Cumulative PnL ({hedge_mode})'\n", " )\n", " fig.show()\n", "else:\n", " print('No performance data available')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Step 7.2: Visualize Hedged Portfolio Style Factor Exposures\n", "\n", "View the style factor exposures after hedging." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Create style factor chart (excludes GICS classification data)\n", "hedged_factor_exposures = strategy.get_style_factor_exposures(HedgeTarget.HEDGED_TARGET)\n", "hedged_analysis = analytics.convert_hedge_factor_exposures(hedged_factor_exposures)\n", "\n", "hedge_mode = 'Bidirectional' if settings.allow_long_short else 'Unidirectional'\n", "fig = analytics.create_style_factor_chart(\n", " hedged_analysis, rows=5, title=f'Hedged Portfolio - Style Factor Exposures ({hedge_mode} Hedge)'\n", ")\n", "fig.show()\n", "\n", "print('\\nStyle factors displayed (e.g., Volatility, Market Sensitivity, Momentum, Size, Value, etc.)')\n", "print(f'Hedge Mode: {hedge_mode}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Step 7.3: Compare Initial vs Hedged Portfolio\n", "\n", "Compare key risk metrics between the initial and hedged portfolios." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Display performance summary from strategy in separate tables\n", "hedge_mode = 'Bidirectional Hedger' if settings.allow_long_short else 'Unidirectional Hedger (Short Positions Only)'\n", "print(f'\\nPerformance Summary from Hedge Calculation ({hedge_mode}):')\n", "try:\n", " summary = strategy.get_performance_summary()\n", "\n", " # Optionally display all metrics in one combined table\n", " print('\\n--- All Metrics (Combined) ---')\n", " display(summary['combined'])\n", "\n", " if settings.allow_long_short:\n", " print('\\nNote: Bidirectional hedge includes both long and short positions for more flexible factor management.')\n", " else:\n", " print('\\nNote: Unidirectional hedge contains only short positions to offset the target portfolio.')\n", "\n", "except Exception as e:\n", " print(f'Could not retrieve performance summary: {str(e)}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Side-by-Side Factor Comparison\n", "\n", "Compare all style factor exposures between initial and hedged portfolios using a grouped bar chart." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Create side-by-side comparison of all style factors\n", "hedge_mode = 'Bidirectional' if settings.allow_long_short else 'Unidirectional'\n", "print(f'Creating Style Factor Comparison Chart ({hedge_mode} Hedge)...\\n')\n", "comparison_chart = analytics.create_factor_heatmap_comparison(\n", " initial_analysis, hedged_analysis, title=f'Style Factor Comparison: Initial vs Hedged Target ({hedge_mode})'\n", ")\n", "comparison_chart.show()\n", "\n", "print('\\nThis chart displays all style factors with side-by-side bars for easy comparison.')\n", "print('Blue bars = Initial Portfolio | Green bars = Hedged Target Portfolio')\n", "print('Factors are sorted by initial portfolio exposure (largest to smallest by absolute value).')\n", "print('A vertical line at zero helps identify positive vs negative exposures.')\n", "print(f'\\nHedge Mode: {hedge_mode}')\n", "if settings.allow_long_short:\n", " print(' - Bidirectional hedge can take both long and short positions')\n", "else:\n", " print(' - Unidirectional hedge contains only short positions')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Note:\n", "If code above fails to find optimization, relax the constraints and try again." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 9: Save Your Hedge to Marquee\n", "\n", "Once you're satisfied with your hedge results, you can save them to Marquee for future reference and analysis. This will create a hedge group that can be accessed through the Marquee UI." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Save the hedge using the strategy's built-in save_to_marquee method\n", "# This method handles building the payload and posting to the API\n", "\n", "saved_hedge_response = strategy.save_to_marquee(\n", " strategy_request=strategy_request,\n", " optimization_response=optimization_response,\n", " hedge_name=\"Factor Hedge\",\n", " group_name=\"My Factor Hedge Group\",\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Step 9.1: Share Your Hedge with Other Users (Optional)\n", "\n", "After saving your hedge, you can share it with other users or groups by updating the entitlements. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# For view-only access:\n", "hedge_view_emails = [\"YOUR EMAIL\"]\n", "\n", "# Uncomment the following lines to share the hedge:\n", "if saved_hedge_response and saved_hedge_response.get('id'):\n", " hedge_group_id = saved_hedge_response['id']\n", " share_response = GsHedgeApi.share_hedge_group(\n", " hedge_group_id=hedge_group_id,\n", " view_emails=hedge_view_emails,\n", " strategy_request=strategy_request,\n", " optimization_response=optimization_response,\n", " admin_emails=[], # Add admin emails here if needed\n", " )" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "## Step 10: Create a Basket or Portfolio With Your Results\n", "\n", "Now that you have saved your hedge, you can also create a basket or portfolio with your optimization results by following these tutorials:\n", "\n", "- [Create a Basket](https://nbviewer.org/github/goldmansachs/gs-quant/blob/master/gs_quant/documentation/06_baskets/tutorials/Basket%20Create.ipynb)\n", "- [Create a Portfolio](https://nbviewer.org/github/goldmansachs/gs-quant/blob/master/gs_quant/documentation/10_one_delta/scripts/portfolios/01_Create%20New%20Historical%20Portfolio.ipynb)\n", "\n", "*Other questions? Reach out to the [Portfolio Analytics team](mailto:gs-marquee-analytics-support@gs.com)!*\n" ] } ], "metadata": { "kernelspec": { "display_name": "venv312 (3.12.1)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.1" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: gs_quant/documentation/10_one_delta/Hedger/03_Continuous_Optimization.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Quant Backtesting Workflow in Marquee\n", "\n", "This notebook demonstrates how to implement continuous portfolio optimizations using GS Quant." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 1: Authenticate and Initialize your Session \n", "\n", "First you will import the necessary modules and add your client id and client secret." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import datetime as dt\n", "import time\n", "from math import copysign\n", "\n", "import pandas as pd\n", "\n", "from gs_quant.datetime.relative_date import RelativeDate\n", "from gs_quant.markets.position_set import Position, PositionSet\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.markets.portfolio_manager import PortfolioManager\n", "from gs_quant.markets.securities import Asset, AssetIdentifier\n", "from gs_quant.markets.report import FactorRiskReport, ReturnFormat\n", "from gs_quant.models.risk_model import FactorRiskModel\n", "from gs_quant.session import GsSession\n", "from gs_quant.target.common import PositionSetWeightingStrategy\n", "\n", "from gs_quant.target.hedge import CorporateActionsTypes\n", "from gs_quant.markets.optimizer import (\n", " OptimizerStrategy,\n", " OptimizerUniverse,\n", " AssetConstraint,\n", " FactorConstraint,\n", " SectorConstraint,\n", " OptimizerSettings,\n", " OptimizerConstraints,\n", " OptimizerObjective,\n", " OptimizerType,\n", ")\n", "\n", "pd.set_option('display.width', 1000)\n", "\n", "client = None\n", "secret = None\n", "\n", "## External users must fill in their client ID and secret below and comment out the line below\n", "# client = 'ENTER CLIENT ID'\n", "# secret = 'ENTER CLIENT SECRET'\n", "\n", "GsSession.use(client_id=client, client_secret=secret)\n", "\n", "print('GS Session initialized.')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Quick Hint:\n", "If you don't already have a portfolio set up, follow the instructions in [01_Create_Backcasted_Portfolio](../Portfolios/01_Create_Backcasted_Portfolio.ipynb) to create a snapshot portfolio and backcast it. \n", "Alternatively, if you have a historical position set, you can also create a historical portfolio in [02_Create_New_Historical_Portfolio](../Portfolios/02_Create_New_Historical_Portfolio.ipynb)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 2: Load your Portfolio" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "Load your portfolio and define hedge parameters\n", "\n", "##### Note - The start date for your continuous optimization must be **after** the first position set uploaded in your source portfolio." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "port_id = 'YOUR PORTFOLIO ID' # put in your existing portfolio id\n", "start_date = dt.date(2024, 1, 2)\n", "hedge_notional_pct = 1.0\n", "universe = ['SPX']\n", "rebalance_freq = '1m'\n", "risk_model_id = 'AXIOMA_AXWW4M'\n", "apply_factor_constraints_on_total = True" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "port_object = Portfolio.get(port_id)\n", "port_manager = PortfolioManager(port_id)\n", "print(f'Using portfolio {port_object.name} with id {port_id} as a source portfolio')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 3: Create a Portfolio for your Optimization\n", "\n", "\n", "You will first create a portfolio with a custom name and share it with a list of emails.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "opt_port_object = Portfolio(name=f'{port_object.name} - L/S Hedge')\n", "opt_port_object.save()\n", "opt_port_id = opt_port_object.id\n", "print(f'Created portfolio {opt_port_object.name} with id {opt_port_id}')\n", "opt_port_manager = PortfolioManager(opt_port_id)\n", "opt_port_manager.share(['your.email@yourcompany.com', 'your.colleague@yourcompany.com'], admin=True)\n", "opt_port_manager.set_tag_name_hierarchy(['source'])\n", "\n", "opt_risk_report = FactorRiskReport(risk_model_id=risk_model_id)\n", "opt_risk_report.set_position_source(opt_port_id)\n", "opt_risk_report.save()\n", "\n", "opt_port_manager.update_portfolio_tree()\n", "print(f'Using portfolio {opt_port_object.name} with id {opt_port_id} as the optimization portfolio')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Risk and performance information about your source portfolio is required in order to run the continuous optimization. \n", "\n", "We'll first make sure that your source portfolio's factor risk and performance reports have finished successfully." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print('Ensuring performance and risk calculations are complete on the source...')\n", "perf_report = port_manager.get_performance_report()\n", "perf_report.get_most_recent_job().wait_for_completion()\n", "\n", "risk_report = port_manager.get_factor_risk_report(risk_model_id=risk_model_id)\n", "risk_report.get_most_recent_job().wait_for_completion()\n", "\n", "factor_exposure_data = risk_report.get_factor_exposure(start_date=start_date, end_date=start_date)\n", "factor_exposure_map = factor_exposure_data.to_dict(orient='records')[0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 4: Setting Up Optimization Parameters\n", "\n", "Now that you have a position set, you can get a hedge according to your liking.\n", "`prepare_factor_constraints` below will pull constraints defined at a portfolio level to apply on the hedge." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "def prepare_factor_constraints(factor_constraints, port_factor_exposure_map):\n", " \"\"\"Given factor constraints defined on the total portfolio and factor exposures of the core portfolio,\n", " return constraints to be applied on the hedge\"\"\"\n", " new_constraints = []\n", " for fc in factor_constraints:\n", " old = fc.max_exposure\n", " new = port_factor_exposure_map.get(fc.factor.name, 0) - fc.max_exposure\n", " print('Changing factor constraint for ', fc.factor.name, 'from ', old, 'to ', new)\n", " new_constraints.append(\n", " FactorConstraint(fc.factor, port_factor_exposure_map.get(fc.factor.name, 0) - fc.max_exposure)\n", " )\n", " return new_constraints" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that you have a position set, you can get a hedge according to your liking. We have put in some sample settings below. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "hedge_universe = OptimizerUniverse(\n", " assets=[Asset.get(a, AssetIdentifier.BLOOMBERG_ID) for a in universe],\n", " explode_composites=True,\n", " exclude_corporate_actions_types=[CorporateActionsTypes.Mergers],\n", ")\n", "\n", "risk_model = FactorRiskModel.get(risk_model_id)\n", "\n", "asset_constraints = [\n", " AssetConstraint(Asset.get('MSFT UW', AssetIdentifier.BLOOMBERG_ID), 0, 5),\n", " AssetConstraint(Asset.get('AAPL UW', AssetIdentifier.BLOOMBERG_ID), 0, 5),\n", "]\n", "\n", "# Specify the constraints on factor exposure of the Total Optimized Portfolio\n", "factor_constraints = [\n", " FactorConstraint(risk_model.get_factor('Size'), 0),\n", "]\n", "\n", "if apply_factor_constraints_on_total:\n", " hedge_factor_constraints = prepare_factor_constraints(factor_constraints, factor_exposure_map)\n", "else:\n", " hedge_factor_constraints = factor_constraints\n", "\n", "sector_constraints = [SectorConstraint('Energy', 0, 30), SectorConstraint('Health Care', 0, 30)]\n", "constraints = OptimizerConstraints(\n", " asset_constraints=asset_constraints,\n", " factor_constraints=hedge_factor_constraints,\n", " sector_constraints=sector_constraints,\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 5: Continuous Optimization Loop\n", "\n", "With our initial setup done and settings configured, we are now ready to launch a flow that will continuously optimize our portfolio at our desired frequency.\n", "\n", "We will take the positions from the previous rebalance, utilize performance analytics to get the latest positions, and then optimize the portfolio again. We will then update the portfolio with the new positions and schedule reports for the next rebalance date.\n", "\n", "This operation of moving your portfolio forward using performance analytics relies solely on availability of the underlying assets. \n", "\n", "##### Note - The Optimizer is designed to work with prices adjusted for events such as dividends, splits, and corporate actions. This means it reflects the true economic value of assets after such adjustments. In contrast, our portfolio analytics operate on the raw (unadjusted) closing prices to provide a more detailed, day‐to‐day picture of historical trade activity. To avoid inconsistencies, we recommend passing position sets based solely on weight (which is independent of price adjustments) to derive the optimization output also in terms of weights" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "port_manager = PortfolioManager(port_id)\n", "start = start_date\n", "max_end = RelativeDate(\"-1b\", dt.date.today()).apply_rule(exchanges=['NYSE'])\n", "start_time = time.time()\n", "rebal = start_date\n", "\n", "optimizer_runs = []\n", "\n", "while rebal < max_end:\n", " print(f'Moving to rebalance date {rebal}')\n", "\n", " source_port_perf_report = port_manager.get_performance_report()\n", " latest_source_port_pos_data = source_port_perf_report.get_portfolio_constituents(\n", " start_date=rebal,\n", " end_date=rebal,\n", " fields=['quantity', 'grossWeight'],\n", " prefer_rebalance_positions=True,\n", " return_format=ReturnFormat.JSON,\n", " )\n", " latest_source_port_exp = source_port_perf_report.get_gross_exposure(start_date=rebal, end_date=rebal)[\n", " 'grossExposure'\n", " ][0]\n", " latest_source_position_set = PositionSet(\n", " date=rebal,\n", " reference_notional=latest_source_port_exp,\n", " positions=[\n", " Position(\n", " asset_id=p['assetId'],\n", " identifier=p['assetId'],\n", " # We recommend using gross weight to find your reference weight, like below\n", " weight=copysign(p.get('grossWeight', 0), p.get('quantity', 0)),\n", " tags=[{'source': 'Portfolio'}],\n", " )\n", " for p in latest_source_port_pos_data\n", " ],\n", " )\n", " settings = OptimizerSettings(\n", " notional=hedge_notional_pct * latest_source_position_set.reference_notional, # 40% of your original portfolio\n", " allow_long_short=True,\n", " )\n", "\n", " if factor_constraints and apply_factor_constraints_on_total:\n", " source_port_risk_report = port_manager.get_factor_risk_report(risk_model_id=risk_model_id)\n", "\n", " factor_exposure_data = risk_report.get_factor_exposure(\n", " start_date=latest_source_position_set.date, end_date=latest_source_position_set.date\n", " )\n", " factor_exposure_map = factor_exposure_data.to_dict(orient='records')[0]\n", " hedge_factor_constraints = prepare_factor_constraints(factor_constraints, factor_exposure_map)\n", " else:\n", " hedge_factor_constraints = factor_constraints\n", "\n", " constraints = OptimizerConstraints(\n", " asset_constraints=asset_constraints,\n", " factor_constraints=hedge_factor_constraints,\n", " sector_constraints=sector_constraints,\n", " )\n", " strategy = OptimizerStrategy(\n", " initial_position_set=latest_source_position_set,\n", " constraints=constraints,\n", " settings=settings,\n", " universe=hedge_universe,\n", " risk_model=risk_model,\n", " objective=OptimizerObjective.MINIMIZE_FACTOR_RISK,\n", " )\n", " print('Optimizing...')\n", " # Using adjusted prices in the Optimizer to reflect post-corporate action values\n", " strategy.run(optimizer_type=OptimizerType.AXIOMA_PORTFOLIO_OPTIMIZER)\n", " print('Optimization complete')\n", " optimizer_runs.append(strategy)\n", " optimization_result = strategy._OptimizerStrategy__result['hedge']\n", " optimization = PositionSet(\n", " date=strategy.initial_position_set.date,\n", " reference_notional=optimization_result['netExposure'],\n", " positions=[\n", " Position(identifier=asset.get('bbid', asset['name']), asset_id=asset['assetId'], weight=asset['weight'])\n", " for asset in optimization_result['constituents']\n", " ],\n", " )\n", " # Applying unadjusted close prices for portfolio analytics to preserve historical price granularity\n", " optimization.price(\n", " use_unadjusted_close_price=True, weighting_strategy=PositionSetWeightingStrategy.Weight, fallbackDate='5d'\n", " )\n", " for p in optimization.positions:\n", " p.add_tag('source', 'Optimization')\n", " # Again using unadjusted prices on source positions for consistency in analytics\n", " latest_source_position_set.price(\n", " use_unadjusted_close_price=True, weighting_strategy=PositionSetWeightingStrategy.Weight, fallbackDate='5d'\n", " )\n", " combined_pset = PositionSet(\n", " date=optimization.date,\n", " positions=[\n", " Position(identifier=p.identifier, asset_id=p.asset_id, quantity=p.quantity, tags=p.tags)\n", " for p in latest_source_position_set.positions + optimization.positions\n", " ],\n", " )\n", " opt_port_manager.update_positions([combined_pset])\n", " if not opt_port_manager.get_portfolio_tree().sub_portfolios:\n", " opt_port_manager.update_portfolio_tree()\n", " start = rebal\n", " rebal = min(max_end, RelativeDate(rebalance_freq, start).apply_rule())\n", " print(f'Scheduling reports to calculate performance till {rebal}...')\n", " opt_port_manager.schedule_reports(start_date=start, end_date=rebal)\n", " time.sleep(2)\n", "\n", "print(f'Done! Processing completed in {time.time() - start_time} seconds')\n", "\n", "print(f'As a reminder your optimization can be found in your \"{opt_port_object.name}\" portfolio with id {opt_port_id}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You have successfully completed a basic run of our Quant Backtesting Workflow. \n", "\n", "For questions, please reach out to [Marquee Sales](mailto:gs-marquee-sales@gs.com)!" ] } ], "metadata": { "kernelspec": { "display_name": "tutorial-env", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: gs_quant/documentation/10_one_delta/Portfolios/01_Create_Backcasted_Portfolio.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Create a Marquee Backcasted Portfolio with GS Quant\n", "\n", "The Marquee Portfolio Service provides a powerful framework for uploading portfolio positions and retrieving analytics including historical performance, factor risk exposure, ESG analytics, and more. GS Quant makes operating the suite of Portfolio Service API's intuitive and fast." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 1: Authenticate and Initialize Your Session\n", "\n", "First you will import the necessary modules and add your client id and client secret." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "import datetime as dt\n", "import warnings\n", "\n", "from gs_quant.datetime import business_day_offset\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.markets.portfolio_manager import PortfolioManager\n", "from gs_quant.markets.position_set import PositionSet\n", "from gs_quant.markets.report import FactorRiskReport, ThematicReport\n", "from gs_quant.models.risk_model import FactorRiskModel\n", "from gs_quant.session import GsSession, Environment\n", "from gs_quant.entities.entitlements import Entitlements, EntitlementBlock, User\n", "\n", "\n", "client = None\n", "secret = None\n", "\n", "## External users must fill in their client ID and secret below and comment out the line below\n", "\n", "# client = 'ENTER CLIENT ID'\n", "# secret = 'ENTER CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret)\n", "warnings.filterwarnings(\"ignore\", category=RuntimeWarning)\n", "\n", "print('GS Session initialized.')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 2: Create the Portfolio\n", "\n", "The first step is to create a new, empty portfolio in Marquee." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "portfolio = Portfolio(name='My New Backcasted Portfolio')\n", "portfolio.save()\n", "\n", "print(f\"Created portfolio '{portfolio.name}' with ID: {portfolio.id}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Once your portfolio has been saved to Marquee, the `PortfolioManager` class allows users to interact with their Marquee portfolios directly from GS Quant. We will be using `PortfolioManager` to update portfolio positions, entitlements, update custom AUM, and run reports." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pm = PortfolioManager(portfolio.id)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 3: Define Portfolio Entitlements (Optional)\n", "\n", "By default, an application will have all entitlement permissions to a portfolio it makes.\n", "However, if you would like to share the portfolio with others, either Marquee users or other\n", "applications, you will need to specify them in the entitlements parameter of the portfolio.\n", "Let's walk through how we convert a list of admin and viewer emails into an `Entitlements` object:\n", "\n", "*Note: If you would like to see this portfolio on your Marquee webpage, you'll need to add your account\n", "email address into the `portfolio_admin_emails` list*" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "portfolio_admin_emails = ['LIST OF ADMIN EMAILS']\n", "portfolio_viewer_emails = ['LIST OF VIEWER EMAILS']\n", "\n", "admin_entitlements = EntitlementBlock(users=User.get_many(emails=portfolio_admin_emails))\n", "view_entitlements = EntitlementBlock(users=User.get_many(emails=portfolio_viewer_emails))\n", "\n", "entitlements = Entitlements(view=view_entitlements, admin=admin_entitlements)\n", "\n", "print(f'Entitlements:\\n{entitlements.to_dict()}')\n", "\n", "pm.set_entitlements(entitlements)\n", "\n", "print(f\"Updated entitlements for '{portfolio.name}'\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 4: Define Portfolio Positions\n", "\n", "Portfolio positions in Marquee are stored on a holding basis, when means you only upload positions for days where you are rebalancing your portfolio. Depending on your quantity type, take the following example positions:" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Uploading Positions Using Shares\n", "Portfolio quantity type uses share counts." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "positions = [\n", " {'identifier': 'AAPL UW', 'quantity': 25},\n", " {'identifier': 'GS UN', 'quantity': 50},\n", " {'identifier': 'META UW', 'quantity': 25},\n", " {'identifier': 'AMZN UN', 'quantity': 50},\n", " {'identifier': 'MSFT UW', 'quantity': 25},\n", " {'identifier': 'AZN UW', 'quantity': 50},\n", "]" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Uploading Positions Using Exposures\n", "Portfolio quantity type uses notional values." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "positions = [\n", " {'identifier': 'AAPL UW', 'notional': 25000},\n", " {'identifier': 'GS UN', 'notional': 50000},\n", " {'identifier': 'META UW', 'notional': 25000},\n", " {'identifier': 'AMZN UN', 'notional': 50000},\n", " {'identifier': 'MSFT UW', 'notional': 25000},\n", " {'identifier': 'AZN UW', 'notional': 50000},\n", "]" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "For uploading using quantities or exposures, resolve the positions in your portfolio as of last business day." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "portfolio_position_sets = [\n", " PositionSet.from_dicts(date=business_day_offset(dt.date.today(), -1, roll='forward'), positions=positions)\n", "]\n", "\n", "for pos_set in portfolio_position_sets:\n", " pos_set.resolve()\n", "\n", "print(\"All positions have been resolved and saved.\")" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Uploading Positions Using Weights\n", "Portfolio quantity type uses weight values." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "positions = [\n", " {'identifier': 'AAPL UW', 'weight': 0.25},\n", " {'identifier': 'GS UN', 'weight': 0.25},\n", " {'identifier': 'META UW', 'weight': 0.25},\n", " {'identifier': 'AMZN UN', 'weight': 0.25},\n", "]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For uploading using weights, resolve the positions in your portfolio as of last business day and using a desired target notional." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "portfolio_position_sets = [\n", " PositionSet.from_dicts(\n", " date=business_day_offset(dt.date.today(), -1, roll='forward'), reference_notional=15000, positions=positions\n", " )\n", "]\n", "\n", "for pos_set in portfolio_position_sets:\n", " pos_set.resolve()\n", "\n", "print(\"All positions have been resolved and saved.\")" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Price Positions (Weights & Exposures)\n", "When uploading positions by weights or exposures they must priced and converted to share quantities before being uploaded to the portfolio.\n", "Select the currency you want to price your positions with. If you do not specify, default is USD." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "currency = 'USD'\n", "\n", "for pos_set in portfolio_position_sets:\n", " pos_set.price(currency)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Define Currency for portfolio" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from gs_quant.common import Currency\n", "\n", "pm = PortfolioManager(portfolio.id)\n", "pm.set_currency(Currency.USD) # Set the currency using PortfolioManager" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 5: Upload the positions onto the portfolio:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pm.update_positions(portfolio_position_sets)\n", "\n", "print(f\"Successfully updated positions for portfolio {pm.portfolio_id}\\n\")\n", "print(f\"Latest updated set of positions for {pm.portfolio_id}: \\n{pm.get_latest_position_set().get_positions()}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 6: Create Factor Risk and/or Thematic Reports (Optional)\n", "\n", "By default, creating a portfolio will automatically create a corresponding performance report for it as well.\n", "If you would like to create a factor risk and/or thematic report (more documentation on reports found [here](https://developer.gs.com/p/docs/services/portfolio/programmatic-access/reports/))\n", "for it as well, run the following:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "risk_model_id = 'RISK MODEL ID'\n", "\n", "# Add a factor risk report\n", "risk_report = FactorRiskReport(risk_model_id=risk_model_id)\n", "risk_report.set_position_source(portfolio.id)\n", "risk_report.save()\n", "\n", "# Add a thematic report\n", "thematic_report = ThematicReport()\n", "thematic_report.set_position_source(portfolio.id)\n", "thematic_report.save()\n", "\n", "print('All portfolio reports created.')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Quick Tip!\n", "\n", "*Explore the different factor risk models available in Marquee in our [Data Catalog](https://marquee.gs.com/s/discover/data-services/catalog?Category=Factor+Risk+Model)*\n", "\n", "*Premium clients get access to many more risk models (including premium vendors like MSCI Barra),\n", "while non-premium clients get access to a limited suite of models. To see which models you have access to,\n", "simply run the following:*" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "risk_models = FactorRiskModel.get_many(limit=100)\n", "for risk_model in risk_models:\n", " print(f'{risk_model.name}: {risk_model.id}\\n')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 7: Schedule Reports\n", "\n", "Now, it's schedule all the portfolio reports. Once this is done and reports are completed, you can programmatically retrieve factor risk and attribution data for your portfolio.\n", "\n", "When scheduling reports, you have two options:\n", "- Backcast the report: Take the earliest date with positions in the portfolio / basket and run the report on the positions held then with a start date before the earliest position date and an end date\n", " of the earliest position date. This option is ideal for snapshot portfolios.\n", "- Do not backcast the report: Set the start date as a date that has positions in the portfolio or basket and an end date after that (best practice is to set it to T-1). In this case the\n", " report will run on positions held as of each day in the date range. This option is ideal for historical portfolios." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "pm.schedule_reports(backcast=True)\n", "\n", "print('All portfolio reports scheduled.')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note: Cannot Schedule backcasted report with no position sets on the report job end date." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/10_one_delta/Portfolios/02_Create_New_Historical_Portfolio.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Create a Marquee Historical Portfolio with GS Quant\n", "\n", "The Marquee Portfolio Service provides a powerful framework for uploading portfolio positions and retrieving analytics including historical performance, factor risk exposure, ESG analytics, and more. GS Quant makes operating the suite of Portfolio Service API's intuitive and fast." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 1: Authenticate and Initialize Your Session\n", "\n", "First you will import the necessary modules and add your client id and client secret." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import datetime as dt\n", "\n", "from gs_quant.entities.entitlements import Entitlements, EntitlementBlock, User\n", "from gs_quant.markets.portfolio import Portfolio\n", "from gs_quant.markets.portfolio_manager import PortfolioManager\n", "from gs_quant.markets.position_set import PositionSet, Position\n", "from gs_quant.markets.report import FactorRiskReport, ThematicReport, CustomAUMDataPoint\n", "from gs_quant.markets.securities import SecurityMaster, AssetIdentifier\n", "from gs_quant.session import GsSession, Environment\n", "from gs_quant.target.portfolios import RiskAumSource\n", "from gs_quant.models.risk_model import FactorRiskModel\n", "from gs_quant.common import PositionTag\n", "\n", "\n", "client = None\n", "secret = None\n", "\n", "## External users must fill in their client ID and secret below and comment out the line below\n", "\n", "# client = 'ENTER CLIENT ID'\n", "# secret = 'ENTER CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret)\n", "\n", "print('GS Session initialized.')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 2: Create the Portfolio\n", "\n", "The first step is to create a new, empty portfolio in Marquee." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "portfolio = Portfolio(name='My New Historical Portfolio')\n", "portfolio.save()\n", "\n", "print(f\"Created portfolio '{portfolio.name}' with ID: {portfolio.id}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Once your portfolio has been saved to Marquee, the `PortfolioManager` class allows users to interact with their Marquee portfolios directly from GS Quant. We will be using `PortfolioManager` to update portfolio positions, entitlements, update custom AUM, and run reports." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pm = PortfolioManager(portfolio.id)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 3: Define Portfolio Entitlements (Optional)\n", "\n", "By default, an application will have all entitlement permissions to a portfolio it makes.\n", "However, if you would like to share the portfolio with others, either Marquee users or other\n", "applications, you will need to specify them in the entitlements parameter of the portfolio.\n", "Let's walk through how we convert a list of admin and viewer emails into an `Entitlements` object:\n", "\n", "*Note: If you would like to see this portfolio on your Marquee webpage, you'll need to add your account\n", "email address into the `portfolio_admin_emails` list*" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "portfolio_admin_emails = ['LIST OF ADMIN EMAILS']\n", "portfolio_viewer_emails = ['LIST OF VIEWER EMAILS']\n", "\n", "admin_entitlements = EntitlementBlock(users=User.get_many(emails=portfolio_admin_emails))\n", "view_entitlements = EntitlementBlock(users=User.get_many(emails=portfolio_viewer_emails))\n", "\n", "entitlements = Entitlements(view=view_entitlements, admin=admin_entitlements)\n", "\n", "print(f'Entitlements:\\n{entitlements.to_dict()}')\n", "\n", "pm.set_entitlements(entitlements)\n", "\n", "print(f\"Updated entitlements for '{portfolio.name}'\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 4: Define Portfolio Positions\n", "\n", "Portfolio positions in Marquee are stored on a holding basis, when means you only upload positions for days where you are rebalancing your portfolio. Take the following set of positions:" ] }, { "cell_type": "markdown", "source": [ "### Uploading Positions Using Shares\n", "Portfolio quantity type uses share counts." ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "portfolio_position_sets = [\n", " PositionSet(\n", " date=dt.date(day=3, month=5, year=2021),\n", " positions=[\n", " Position(identifier='AAPL UW', quantity=25, tags=[PositionTag(name='Analyst', value='Marcus Goldman')]),\n", " Position(identifier='GS UN', quantity=50, tags=[PositionTag(name='Analyst', value='Samuel Sachs')]),\n", " ],\n", " ),\n", " PositionSet(\n", " date=dt.date(day=1, month=7, year=2021),\n", " positions=[\n", " Position(identifier='AAPL UW', quantity=25, tags=[PositionTag(name='Analyst', value='Marcus Goldman')]),\n", " Position(identifier='GS UN', quantity=50, tags=[PositionTag(name='Analyst', value='Samuel Sachs')]),\n", " ],\n", " ),\n", "]" ], "metadata": { "collapsed": false } }, { "cell_type": "markdown", "source": [ "### Uploading Positions Using Exposures\n", "Portfolio quantity type uses notional values." ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "portfolio_position_sets = [\n", " PositionSet(\n", " date=dt.date(day=3, month=5, year=2021),\n", " positions=[\n", " Position(identifier='AAPL UW', notional=25000, tags=[PositionTag(name='Analyst', value='Marcus Goldman')]),\n", " Position(identifier='GS UN', notional=50000, tags=[PositionTag(name='Analyst', value='Samuel Sachs')]),\n", " ],\n", " ),\n", " PositionSet(\n", " date=dt.date(day=1, month=7, year=2021),\n", " positions=[\n", " Position(identifier='AAPL UW', notional=30000, tags=[PositionTag(name='Analyst', value='Marcus Goldman')]),\n", " Position(identifier='GS UN', notional=75000, tags=[PositionTag(name='Analyst', value='Samuel Sachs')]),\n", " ],\n", " ),\n", "]" ], "metadata": { "collapsed": false } }, { "cell_type": "markdown", "source": [ "### Uploading Positions Using Weights\n", "Portfolio quantity type uses weight values." ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "portfolio_position_sets = [\n", " PositionSet(\n", " date=dt.date(day=3, month=5, year=2021),\n", " reference_notional=15000,\n", " positions=[\n", " Position(identifier='AAPL UW', weight=0.5, tags=[PositionTag(name='Analyst', value='Marcus Goldman')]),\n", " Position(identifier='GS UN', weight=0.5, tags=[PositionTag(name='Analyst', value='Samuel Sachs')]),\n", " ],\n", " ),\n", " PositionSet(\n", " date=dt.date(day=1, month=7, year=2021),\n", " reference_notional=20000,\n", " positions=[\n", " Position(identifier='AAPL UW', weight=0.5, tags=[PositionTag(name='Analyst', value='Marcus Goldman')]),\n", " Position(identifier='GS UN', weight=0.5, tags=[PositionTag(name='Analyst', value='Samuel Sachs')]),\n", " ],\n", " ),\n", "]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Resolve the positions in your portfolio as of last business day." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "for pos_set in portfolio_position_sets:\n", " pos_set.resolve()\n", " if len(pos_set.unresolved_positions) > 0:\n", " print(\n", " f'The following positions on {pos_set.date} could not be resolved: {[p.identifier for p in pos_set.unresolved_positions]}'\n", " )\n", " else:\n", " print(f\"All positions on {pos_set.date} have been resolved and saved.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Price Positions (Weights & Exposures)\n", "When uploading positions by weights or exposures they must priced and converted to share quantities before being uploaded to the portfolio.\n", "Select the currency you want to price your positions with. If you do not specify, default is USD." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "currency = 'USD'\n", "\n", "for pos_set in portfolio_position_sets:\n", " pos_set.price(currency)" ] }, { "cell_type": "markdown", "source": [ "## Step 5: Update Portfolio" ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "pm.update_positions(portfolio_position_sets)\n", "\n", "print(f\"Successfully updated positions for portfolio {pm.portfolio_id}\\n\")\n", "print(f\"Latest updated set of positions for {pm.portfolio_id}: \\n{pm.get_latest_position_set().get_positions()}\")" ], "metadata": { "collapsed": false } }, { "cell_type": "markdown", "metadata": {}, "source": [ "If these positions were to be uploaded correctly using share quantities, this portfolio would hold 50 shares of GS UN and 25 shares of AAPL UW from May 3, 2021 to June 30, 2021, and it would hold 51 shares of GS UN and 26 shares of AAPL UW from July 1, 2021 to today.\n", "\n", "#### Do you have your positions as a dataframe?\n", "\n", "If you have a day's positions in a dataframe with columns `identifer` (string values), `quantity` (float values),\n", "and optionally `tags` (dictionary values), you can turn them into a `PositionSet` object by using the\n", "`PositionSet.from_frame()` function:\n", "\n", "`position_set = PositionSet.from_frame(positions_df, datetime_date)`\n", "\n", "You can also substitute the `quantity` column with either `notional` (float values) or `weight` (float values) columns." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# positions_df = DATAFRAME WITH POSITIONS\n", "# datetime_date = POSITIONS' DATE\n", "\n", "portfolio_position_sets = []\n", "portfolio_position_sets.append(PositionSet.from_frame(positions_df, datetime_date))\n", "\n", "for pos_set in portfolio_position_sets:\n", " pos_set.resolve()\n", " if len(pos_set.unresolved_positions) > 0:\n", " print(\n", " f'The following positions on {pos_set.date} could not be resolved: {[p.identifier for p in pos_set.unresolved_positions]}'\n", " )\n", " else:\n", " print(f\"All positions on {pos_set.date} have been resolved and saved.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 6: Create Factor Risk and/or Thematic Reports (Optional)\n", "\n", "By default, creating a portfolio will automatically create a corresponding performance report for it as well.\n", "If you would like to create a factor risk and/or thematic report (more documentation on reports found [here](https://developer.gs.com/p/docs/services/portfolio/programmatic-access/reports/))\n", "for it as well, run the following:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "risk_model_id = 'RISK_MODEL_ID_HERE'\n", "risk_report = FactorRiskReport(risk_model_id=risk_model_id)\n", "risk_report.set_position_source(portfolio.id)\n", "risk_report.save()\n", "\n", "# Add a thematic report\n", "thematic_report = ThematicReport()\n", "thematic_report.set_position_source(portfolio.id)\n", "thematic_report.save()\n", "\n", "print('All portfolio reports created.')" ] }, { "cell_type": "markdown", "source": [ "If you want to analyze your portfolio's tracking error relative to a benchmark and its active return against a benchmark, you can create an active factor risk report by including an additional parameter, `benchmark_id`, when creating a `FactorRiskReport`, as shown below::\n", "\n" ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "benchmark = SecurityMaster.get_asset(id_value='BENCHMARK TICKER HERE', id_type=AssetIdentifier.TICKER)\n", "\n", "# Add an active factor risk report with a benchmark of your choice\n", "active_risk_report = FactorRiskReport(risk_model_id=risk_model_id, benchmark_id=benchmark.get_marquee_id())\n", "active_risk_report.set_position_source(portfolio.id)\n", "active_risk_report.save()" ], "metadata": { "collapsed": false } }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Quick Tip!\n", "\n", "*Explore the different factor risk models available in Marquee in our [Data Catalog](https://marquee.gs.com/s/discover/data-services/catalog?Category=Factor+Risk+Model)*\n", "\n", "*Premium clients get access to many more risk models from industry-leading commercial \n", "risk model vendors such as MSCI Barra, Axioma (SimCorp), and Wolfe_Research, \n", "while non-premium clients get access to a limited suite of models. To see which models \n", "you have access to, simply run the following:*" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "risk_models = FactorRiskModel.get_many(limit=100)\n", "for risk_model in risk_models:\n", " print(f'{risk_model.name}: {risk_model.id}\\n')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 7: Schedule Reports\n", "\n", "Now, let's schedule all the portfolio reports. Once this is done and reports are completed, you can programmatically retrieve factor risk and attribution data for your portfolio.\n", "\n", "When scheduling reports, you have two options:\n", "- Backcast the report: Take the earliest date with positions in the portfolio and run the report on the positions held then with a start date before the earliest position date and an end date\n", " of the earliest position date. This option is ideal for snapshot portfolios.\n", "- Do not backcast the report: Set the start date as a date that has positions in the portfolio and an end date after that (best practice is to set it to T-1). In this case the\n", " report will run on positions held as of each day in the date range. This option is ideal for historical portfolios." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pm.schedule_reports(backcast=False)\n", "\n", "print('All portfolio reports have been scheduled.')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 8: Update Custom AUM/NAV (Optional)\n", "The `CustomAUMDataPoint` class is used to represent custom AUM data for a specific date. A list of them can be posted to Marquee using our initialized `PortfolioManager`. If you do not upload custom AUM data for your portfolio and change your portfolio's AUM Source to `Custom AUM`, by default the \"AUM\" (which is used for calculating risk as percent values) will be your portfolio's long exposure." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "performance_report = pm.get_performance_report()\n", "performance_report.set_aum_source(RiskAumSource.Custom_AUM)\n", "custom_aum = [\n", " CustomAUMDataPoint(date=dt.date(2021, 5, 1), aum=100000),\n", " CustomAUMDataPoint(date=dt.date(2021, 7, 1), aum=200000),\n", "]\n", "performance_report.upload_custom_aum(custom_aum, clear_existing_data=False)\n", "\n", "print(f\"Custom AUM for '{portfolio.name} successfully uploaded'\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*Other questions? Reach out to the [Portfolio Analytics team](mailto:gs-marquee-analytics-support@gs.com)!*\n" ] } ], "metadata": { "kernelspec": { "display_name": "tutorial-env", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: gs_quant/documentation/10_one_delta/Portfolios/03_Update_Historical_Portfolio.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Updating a Historical Marquee Portfolio\n", "\n", "If you already have a portfolio in Marquee, the GS Quant SDK provides a simple and intuitive workflow to update positions and rerun reports.\n", "\n", "You will need to be an admin on the portfolio you would like to update. If you are not an admin, please ask a portfolio admin to [edit the portfolio's entitlements](../examples/marquee/01_edit_portfolio_entitlements.ipynb) to include you." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 1: Authenticate and Initialize Your Session\n", "\n", "First you will import the necessary modules and add your client id and client secret." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "import datetime as dt\n", "\n", "from gs_quant.markets.portfolio_manager import PortfolioManager\n", "from gs_quant.session import GsSession, Environment\n", "from gs_quant.markets.position_set import PositionSet\n", "\n", "client = None\n", "secret = None\n", "scopes = None\n", "\n", "## External users must fill in their client ID and secret below and comment out the line below\n", "\n", "# client = 'ENTER CLIENT ID'\n", "# secret = 'ENTER CLIENT SECRET'\n", "\n", "GsSession.use(\n", " Environment.PROD,\n", " client_id=client,\n", " client_secret=secret,\n", ")\n", "\n", "print('GS Session initialized.')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 2: Define Your Portfolio ID and the Positions You Would Like to Upload\n", "\n", "Portfolio positions in Marquee are stored on a holding basis, when means you only upload positions for days where you are rebalancing your portfolio. \n", "\n", "Positions can be uploaded using the following identifiers:\n", "\n", "| Identifier | Descriptions |\n", "|-----------------------------------|---------------------------------------------------------------------------|\n", "| BLOOMBERG ID = \"BBID\" | Bloomberg identifier and exchange code (GS UN) |\n", "| SEDOL = \"SEDOL\" | LSE Stock Exchange Daily Official List code (2407966) |\n", "| CUSIP = \"CUSIP\" | Committee on Uniform Security Identification Procedures code (38141G104) |\n", "| ISIN = \"ISIN\" | International Securities Identification Number (US38141G1040) |\n", "| GSID = \"GSID\" | Goldman Sachs Identifier |\n", "| REUTERS ID = \"RIC\" | Thompson Reuters Instrument Code (RIC), (GS.N) |\n", "| TICKER = \"TICKER\" | Exchange ticker (GS) |\n", "\n", " Depending on your quantity type, you can upload by shares, weights, or exposures.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "portfolio_id = 'PORTFOLIO ID'" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Uploading Positions Using Shares\n", "Portfolio quantity type uses share counts." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "portfolio_position_sets = [\n", " PositionSet.from_dicts(\n", " date=dt.date(day=3, month=5, year=2021),\n", " positions=[{'identifier': 'AAPL UW', 'quantity': 25}, {'identifier': 'GS UN', 'quantity': 50}],\n", " ),\n", " PositionSet.from_dicts(\n", " date=dt.date(day=1, month=7, year=2021),\n", " positions=[{'identifier': 'AAPL UW', 'quantity': 25}, {'identifier': 'GS UN', 'quantity': 50}],\n", " ),\n", "]" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Uploading Positions Using Exposures\n", "Portfolio quantity type uses notional values." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "portfolio_position_sets = [\n", " PositionSet.from_dicts(\n", " date=dt.date(day=3, month=5, year=2021),\n", " positions=[{'identifier': 'AAPL UW', 'notional': 2500}, {'identifier': 'GS UN', 'notional': 5000}],\n", " ),\n", " PositionSet.from_dicts(\n", " date=dt.date(day=1, month=7, year=2021),\n", " positions=[{'identifier': 'AAPL UW', 'notional': 2500}, {'identifier': 'GS UN', 'notional': 500}],\n", " ),\n", "]" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Uploading Positions Using Weights\n", "Portfolio quantity type uses weight values." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "portfolio_position_sets = [\n", " PositionSet.from_dicts(\n", " date=dt.date(day=3, month=5, year=2021),\n", " reference_notional=10000,\n", " positions=[{'identifier': 'AAPL UW', 'weight': 0.5}, {'identifier': 'GS UN', 'weight': 0.5}],\n", " ),\n", " PositionSet.from_dicts(\n", " date=dt.date(day=1, month=7, year=2021),\n", " reference_notional=10000,\n", " positions=[{'identifier': 'AAPL UW', 'weight': 0.5}, {'identifier': 'GS UN', 'weight': 0.5}],\n", " ),\n", "]" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Resolve Positions" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "for pos_set in portfolio_position_sets:\n", " pos_set.resolve()\n", " if len(pos_set.unresolved_positions) > 0:\n", " print(\n", " f'The following positions on {pos_set.date} could not be resolved: {[p.identifier for p in pos_set.unresolved_positions]}'\n", " )\n", " else:\n", " print(f\"All positions on {pos_set.date} have been resolved and saved.\")" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Price Positions (Weights & Exposures)\n", "When uploading positions by weights or exposures they must priced and converted to share quantities before being uploaded to the portfolio.\n", "Select the currency you want to price your positions with. If you do not specify, default is USD." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "currency = 'USD'\n", "\n", "for pos_set in portfolio_position_sets:\n", " pos_set.price(currency)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 3: Post Positions to the Marquee Portfolio" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "pm = PortfolioManager(portfolio_id)\n", "pm.update_positions(portfolio_position_sets)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Have your positions as a dataframe?\n", "\n", "If you have a day's positions in a dataframe with columns `identifer` (string values), `quantity` (float values),\n", "and optionally `tags` (dictionary values), you can turn them into a `PositionSet` object by using the\n", "`PositionSet.from_frame()` function:\n", "\n", "`position_set = PositionSet.from_frame(positions_df, datetime_date)`\n", "\n", "You can also substitute the `quantity` column with either `notional` (float values) or `weight` (float values) columns." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# positions_df = DATAFRAME WITH POSITIONS\n", "# datetime_date = POSITIONS' DATE\n", "\n", "portfolio_position_sets = []\n", "portfolio_position_sets.append(PositionSet.from_frame(positions_df, datetime_date))\n", "\n", "for pos_set in portfolio_position_sets:\n", " pos_set.resolve()\n", " if len(pos_set.unresolved_positions) > 0:\n", " print(\n", " f'The following positions on {pos_set.date} could not be resolved: {[p.identifier for p in pos_set.unresolved_positions]}'\n", " )\n", " else:\n", " print(f\"All positions on {pos_set.date} have been resolved and saved.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 4: Reschedule All Portfolio Reports\n", "\n", "Now that the portfolio has new positions, it's time to rerun all reports associated with the portfolio so your performance, risk, and other analytics reflect these new positions." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "pm.schedule_reports()\n", "\n", "print('All portfolio reports scheduled.')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*Other questions? Reach out to the [Portfolio Analytics team](mailto:gs-marquee-analytics-support@gs.com)!*" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: gs_quant/documentation/10_one_delta/Portfolios/04_Get_Factor_Attribution_Data.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": true, "pycharm": { "name": "#%% md\n" } }, "source": [ "# Step 1: Pull Portfolio Factor Attribution Data with GS Quant\n", "\n", "## Authenticate and Initialize Your Session\n", "\n", "First you will import the necessary modules and add your client id and client secret." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "import pandas as pd\n", "from IPython.display import display\n", "\n", "from gs_quant.markets.portfolio_manager import PortfolioManager\n", "from gs_quant.markets.report import FactorRiskUnit, FactorRiskTableMode\n", "from gs_quant.markets.securities import SecurityMaster, AssetIdentifier\n", "from gs_quant.session import GsSession, Environment\n", "\n", "client = None\n", "secret = None\n", "\n", "## External users must fill in their client ID and secret below and comment out the line below\n", "\n", "# client = 'ENTER CLIENT ID'\n", "# secret = 'ENTER CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret)\n", "\n", "print('GS Session initialized.')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 2: Get Portfolio Factor Risk Report" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "risk_report = PortfolioManager('ENTER PORTFOLIO ID').get_factor_risk_report(\n", " risk_model_id='ENTER RISK MODEL ID', benchmark_id=None\n", ")\n", "\n", "print(f'Factor risk report found with ID: {risk_report.id}')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "Want to query data for an active risk report? Leverage the `SecurityMaster` class to retrieve the benchmark identifier and\n", "pass it into the `get_factor_risk_report` function:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "benchmark = SecurityMaster.get_asset(id_value='SPX', id_type=AssetIdentifier.BLOOMBERG_ID)\n", "\n", "risk_report = PortfolioManager('ENTER PORTFOLIO ID').get_factor_risk_report(\n", " risk_model_id='ENTER RISK MODEL ID', benchmark_id=benchmark.get_marquee_id()\n", ")\n", "\n", "print(f'Factor risk report found with ID: {risk_report.id}')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 3: Get Current Portfolio Attribution\n", "\n", "Once your risk report is scheduled as of the latest business day, you can view updated attribution broken down by factor category:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "attr_data = risk_report.get_view(\n", " start_date=risk_report.earliest_start_date, end_date=risk_report.latest_end_date, unit=FactorRiskUnit.Notional\n", ")\n", "category_table = attr_data.get('factorCategoriesTable')\n", "category_df = pd.DataFrame(category_table).filter(items=['name', 'pnl', 'minExposure', 'maxExposure', 'avgExposure'])\n", "\n", "display(category_df)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "It is also possible to get a similar table for all the factors in a factor category. In this case, let's drill down into the Style factors:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "factor_tables = attr_data.get('factorCategoryTables')\n", "factor_tables = [f for f in factor_tables if f.get('factorCategory') == 'Style']\n", "factor_df = pd.DataFrame(factor_tables[0].get('factors')).filter(\n", " items=['name', 'pnl', 'minExposure', 'maxExposure', 'avgExposure']\n", ")\n", "\n", "display(factor_df)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "You can also generate a table that shows you the factor PnL over a date range at the asset level. Let's see the factor PnL for each asset for the factors Country, Beta, and Earnings Quality:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "pnl_table = risk_report.get_table(\n", " mode=FactorRiskTableMode.Pnl,\n", " start_date=risk_report.earliest_start_date,\n", " end_date=risk_report.latest_end_date,\n", " factors=[\n", " \"Country\",\n", " \"Beta\",\n", " \"Earnings Quality\",\n", " ], # Skip passing in a value here to get a table with all model factors\n", ")\n", "\n", "display(pd.DataFrame(pnl_table))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 4: Historical Portfolio Factor Performance\n", "\n", "`get_factor_pnl` allows you to pull historical factor performance for a list of factors, as well as aggregations like factor, specific, and total risk:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "pnl = risk_report.get_factor_pnl(\n", " factor_names=['Factor', 'Specific', 'Total', 'Market', 'Country', 'Industry', 'Style'],\n", " start_date=risk_report.earliest_start_date,\n", " end_date=risk_report.latest_end_date,\n", " unit=FactorRiskUnit.Notional,\n", ")\n", "pnl_overview = pnl.filter(items=['Date', 'Total']).set_index('Date')\n", "\n", "pnl_overview.cumsum().plot(title='PnL')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "This makes it easy to break down PnL over time and how it was attributed to various systematic risk factors:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "pnl_overview = pnl.filter(items=['Date', 'Factor', 'Specific', 'Total']).set_index('Date')\n", "pnl_overview.cumsum().plot(title='PnL Overview')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "And dissect that further by factor attribution further by category..." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "pnl_by_type = pnl.filter(items=['Date', 'Market', 'Country', 'Industry', 'Style', 'Specific']).set_index('Date')\n", "\n", "pnl_by_type.cumsum().plot(title='PnL by Factor Category')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 5: Historical Factor Exposure\n", "\n", "For each day, it's possible to pull your portfolio's exposure to specific factors..." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "category_exposures = risk_report.get_factor_exposure(\n", " factor_names=['Market', 'Industry', 'Style'],\n", " start_date=risk_report.earliest_start_date,\n", " end_date=risk_report.latest_end_date,\n", " unit=FactorRiskUnit.Notional,\n", ").set_index('Date')\n", "\n", "category_exposures.plot(title='Exposures to Factor Categories')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "Or get the exposures to all factors in a given category:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "category_exposures = risk_report.get_factor_exposure(\n", " factor_categories=['Style'],\n", " start_date=risk_report.earliest_start_date,\n", " end_date=risk_report.latest_end_date,\n", " unit=FactorRiskUnit.Notional,\n", ").set_index('Date')\n", "\n", "category_exposures.plot(title='Exposures to Style Factors')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "*Other questions? Reach out to the [Portfolio Analytics team](mailto:gs-marquee-analytics-support@gs.com)!*" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/10_one_delta/Portfolios/05_Manage_a_Fund_of_Funds.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": true, "pycharm": { "name": "#%% md\n" } }, "source": [ "# Managing a Fund of Funds\n", "\n", "If you have a Marquee portfolio with position tags, you can easily turn it into a fund of funds,\n", "with which you can create and leverage performance, risk, and thematic reports for a subset of portfolio\n", "positions based off their tags.\n", "\n", "This tutorial assumes you already have a portfolio with positions tags. To learn more about how to create a portfolio\n", "in Marquee using GS Quant, visit our tutorial\n", "[here](https://developer.gs.com/p/docs/services/portfolio/programmatic-access/upload/).\n", "\n", "## Step 1: Authenticate and Initialize Your Session\n", "\n", "First you will import the necessary modules and add your client id and client secret." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "from gs_quant.markets.portfolio_manager import PortfolioManager\n", "from gs_quant.session import GsSession, Environment\n", "\n", "\n", "client = None\n", "secret = None\n", "\n", "## External users must fill in their client ID and secret below and comment out the line below\n", "# client = 'ENTER CLIENT ID'\n", "# secret = 'ENTER CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret)\n", "\n", "print('GS Session initialized.')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 2: Turn your portfolio into a fund of funds\n", "\n", "Let's imagine your portfolio has the following tags on its positions:\n", "\n", "| Tag Name | Possible Tag Values |\n", "|-----------------|---------------\n", "| `Sector` | `Fintech` , `Banks` , `Software` |\n", "| `Analyst` | `Marcus` , `Samuel` |\n", "\n", "When creating a fund of funds, the portfolio's tag name hierarchy is a list of the portfolio's\n", "tag names in an order that determines how the fund of funds is segmented. In this example, say we want to analyze\n", "the portfolio by `Sector`, and then `Analyst`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "pm = PortfolioManager(portfolio_id='YOUR PORTFOLIO ID')\n", "pm.set_tag_name_hierarchy(['Sector', 'Analyst'])\n", "\n", "print(f\"Your portfolio has the following Tag Hierarchy: {pm.get_tag_name_hierarchy()}\")" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "The result will be the following list of sub-portfolios:\n", "\n", "| Sub-Portfolios |\n", "|-----------------\n", "| `Sector`: `Fintech` |\n", "| `Sector`: `Banks` |\n", "| `Sector`: `Software` |\n", "| `Sector`: `Fintech` , `Analyst`: `Marcus` |\n", "| `Sector`: `Banks` , `Analyst`: `Marcus` |\n", "| `Sector`: `Software` , `Analyst`: `Marcus` |\n", "| `Sector`: `Fintech` , `Analyst`: `Samuel` |\n", "| `Sector`: `Banks` , `Analyst`: `Samuel` |\n", "| `Sector`: `Software` , `Analyst`: `Samuel` |\n", "\n", "You can retrieve this list as a dictionary using the following function:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "print(pm.get_all_fund_of_fund_tags())" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 3: Retrieve Sub-Portfolio Analytics\n", "\n", "Now we have a list of dictionaries in which each dictionary\n", "corresponds to a particular sub-portfolio. These dictionaries can be passed to retrieve the relevant\n", "reports for the sub-portfolio:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "subportfolio_reports = pm.get_reports(tags={'Sector': 'Banks', 'Analyst': 'Marcus'})" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "From here, you can pull analytics from these reports as you would for any other portfolio. For more information\n", "on leveraging reports, visit our tutorials [here](https://developer.gs.com/p/docs/services/portfolio/programmatic-access/reports/).\n", "\n", "At any point, if you create/edit/delete reports on your main portfolio or change the tag name hierarchy on\n", "the portfolio, it's important to update your fund of funds so all your sub-portfolio reports are in sync\n", "with your main portfolio reports. You can do so by running this line:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "pm.update_portfolio_tree()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "*Other questions? Reach out to the [Portfolio Analytics team](mailto:gs-marquee-analytics-support@gs.com)!*\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/10_one_delta/Portfolios/06_Get_Factor_Risk_Data.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": true, "pycharm": { "name": "#%% md\n" } }, "source": [ "# Pull Portfolio Factor Risk Data with GS Quant\n", "\n", "## Step 1: Authenticate and Initialize Your Session\n", "\n", "First you will import the necessary modules and add your client id and client secret." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "import pandas as pd\n", "from IPython.display import display\n", "import datetime as dt\n", "\n", "from gs_quant.markets.portfolio_manager import PortfolioManager\n", "from gs_quant.markets.report import FactorRiskUnit, FactorRiskTableMode\n", "from gs_quant.markets.securities import SecurityMaster, AssetIdentifier\n", "from gs_quant.session import GsSession, Environment\n", "\n", "client = None\n", "secret = None\n", "\n", "\n", "## External users must fill in their client ID and secret below and comment out the line below\n", "# client = 'ENTER CLIENT ID'\n", "# secret = 'ENTER CLIENT SECRET'\n", "\n", "GsSession.use(\n", " Environment.PROD,\n", " client_id=client,\n", " client_secret=secret,\n", ")\n", "\n", "print('GS Session initialized.')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 2: Get Portfolio Factor Risk Report" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "risk_report = PortfolioManager('ENTER PORTFOLIO ID').get_factor_risk_report(risk_model_id='RISK MODEL ID')\n", "\n", "print(f'Factor risk report found with ID: {risk_report.id}')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, "source": [ "Want to query data for an active risk report? Leverage the `SecurityMaster` class to retrieve the benchmark identifier and\n", "pass it into the `get_factor_risk_report` function:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "benchmark = SecurityMaster.get_asset(id_value='SPX', id_type=AssetIdentifier.BLOOMBERG_ID)\n", "\n", "risk_report = PortfolioManager('ENTER PORTFOLIO ID').get_factor_risk_report(\n", " risk_model_id='AXIOMA_AXUSWW4', benchmark_id=benchmark.get_marquee_id()\n", ")\n", "\n", "print(f'Factor risk report found with ID: {risk_report.id}')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 3: Current Portfolio Risk\n", "\n", "Once your risk report is scheduled as of the latest business day, you can view updated risk data broken down by factor category:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "category_table = risk_report.get_view(\n", " start_date=risk_report.latest_end_date, end_date=risk_report.latest_end_date, unit=FactorRiskUnit.Notional\n", ").get('factorCategoriesTable')\n", "\n", "category_df = pd.DataFrame(category_table).filter(\n", " items=[\n", " 'name',\n", " 'proportionOfRisk',\n", " 'marginalContributionToRiskPercent',\n", " 'relativeMarginalContributionToRisk',\n", " 'exposure',\n", " 'avgProportionOfRisk',\n", " ]\n", ")\n", "category_df.rename(\n", " columns={\n", " 'proportionOfRisk': 'Prop. of Risk',\n", " 'marginalContributionToRiskPercent': 'MCTR Percent',\n", " 'relativeMarginalContributionToRisk': 'MCTR (USD)',\n", " 'exposure': 'Exposure (USD)',\n", " 'avgProportionOfRisk': 'Avg Prop. of Risk',\n", " },\n", " inplace=True,\n", ")\n", "\n", "display(category_df)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "It is also possible to get a similar table for all the factors in a factor category. In this case, let's pull risk data for all the Style factors:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "factor_table = risk_report.get_view(\n", " factor_category='Style',\n", " start_date=risk_report.latest_end_date,\n", " end_date=risk_report.latest_end_date,\n", " unit=FactorRiskUnit.Notional,\n", ").get('factorsTable')\n", "\n", "factor_df = pd.DataFrame(factor_table).filter(\n", " items=[\n", " 'name',\n", " 'proportionOfRisk',\n", " 'marginalContributionToRiskPercent',\n", " 'relativeMarginalContributionToRisk',\n", " 'exposure',\n", " 'avgProportionOfRisk',\n", " ]\n", ")\n", "factor_df.rename(\n", " columns={\n", " 'proportionOfRisk': 'Prop. of Risk',\n", " 'marginalContributionToRiskPercent': 'MCTR %',\n", " 'relativeMarginalContributionToRisk': 'MCTR (USD)',\n", " 'exposure': 'Exposure (USD)',\n", " 'avgProportionOfRisk': 'Avg Prop. of Risk',\n", " },\n", " inplace=True,\n", ")\n", "\n", "display(factor_df)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "For an asset-level risk breakdown for a set of factors, leverage the get_table() function on the FactorRiskReport class. In this case, let's see the Z-Scores for the factors Beta, Dividend Yield, and Downside Risk:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "zscore_table = risk_report.get_table(\n", " mode=FactorRiskTableMode.ZScore,\n", " factors=[\"Beta\", \"Dividend Yield\", \"Downside Risk\"],\n", " date=dt.date(2025, 1, 29), # Define the date for which you are looking to query the report on\n", ")\n", "\n", "display(pd.DataFrame(zscore_table))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 4: Historical Portfolio Risk\n", "\n", "First let's pull the daily annualized risk across the duration of your portfolio:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "risk_data = risk_report.get_results(\n", " start_date=risk_report.earliest_start_date, end_date=risk_report.latest_end_date, unit=FactorRiskUnit.Notional\n", ")\n", "\n", "historical_risk = risk_data[risk_data['factor'] == 'Total'][['date', 'annualRisk']].set_index('date')\n", "\n", "historical_risk = pd.DataFrame(historical_risk)\n", "\n", "historical_risk.plot(title='Annualized Risk % (ex-ante)')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "For each day, you can see what percent of your risk is contributed to factor risk and what percent is idiosyncratic:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "historical_risk = risk_data[risk_data['factor'].isin(['Specific', 'Factor'])][\n", " ['date', 'factor', 'annualRisk']\n", "].set_index('date')\n", "\n", "# Formatting the data for plotting\n", "historical_risk = historical_risk.pivot(columns='factor', values='annualRisk')\n", "historical_risk.reset_index(inplace=True)\n", "historical_risk.rename(columns={'Factor': 'Factor Risk', 'Specific': 'Specific Risk'}, inplace=True)\n", "\n", "historical_risk.plot(x='date', y=['Factor Risk', 'Specific Risk'], title='Factor and Specific Risk')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "It's even possible to break down that factor risk further by category.\n", "\n", "Let's start by just seeing the universe of **Factor Categories** in our risk report." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "risk_data = risk_report.get_results(\n", " start_date=risk_report.earliest_start_date, end_date=risk_report.latest_end_date, unit=FactorRiskUnit.Notional\n", ")\n", "\n", "print(risk_data[\"factorCategory\"].unique())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Factor categories allow to plot factor risk that falls under the specificied universe." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "factor_categories = ['Style', 'Market']\n", "\n", "prop_of_risk = risk_report.get_factor_proportion_of_risk(\n", " factor_categories=factor_categories,\n", " start_date=risk_report.earliest_start_date,\n", " end_date=risk_report.latest_end_date,\n", ").set_index('Date')\n", "\n", "prop_of_risk.plot(title=f'Proportion of Risk By Factor Categories: {factor_categories}')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "And by factors within a category. In this case, let's try the Style factors:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "factor_categories = ['Style']\n", "factor_names = ['Dividend Yield', 'Earnings Yield', 'Growth']\n", "\n", "prop_of_risk = risk_report.get_factor_proportion_of_risk(\n", " factor_categories=factor_categories,\n", " factor_names=factor_names,\n", " start_date=risk_report.earliest_start_date,\n", " end_date=risk_report.latest_end_date,\n", ").set_index('Date')\n", "\n", "prop_of_risk.plot(title='Proportion of Risk of Style Factors')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "*Other questions? Reach out to the [Portfolio Analytics team](mailto:gs-marquee-analytics-support@gs.com)!*" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/10_one_delta/Portfolios/07_Get_Portfolio_Performance_Analytics.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Pull Performance Report Results with GS Quant\n", "\n", "## Step 1: Authenticate and Initialize Your Session\n", "\n", "First you will import the necessary modules and add your client id and client secret." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "from IPython.display import display\n", "import warnings\n", "\n", "from gs_quant.markets.portfolio_manager import PortfolioManager\n", "from gs_quant.markets.securities import Asset, AssetIdentifier\n", "from gs_quant.session import GsSession, Environment\n", "\n", "\n", "client = None\n", "secret = None\n", "\n", "\n", "## External users must fill in their client ID and secret below and comment out the line below\n", "# client = 'ENTER CLIENT ID'\n", "# secret = 'ENTER CLIENT SECRET'\n", "\n", "GsSession.use(\n", " Environment.PROD,\n", " client_id=client,\n", " client_secret=secret,\n", ")\n", "warnings.filterwarnings(\"ignore\", category=RuntimeWarning)\n", "\n", "\n", "print('GS Session initialized.')" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "pm = PortfolioManager('ENTER PORTFOLIO ID')\n", "performance_report = pm.get_performance_report()\n", "start_date = performance_report.earliest_start_date\n", "end_date = performance_report.latest_end_date" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Step 2: Pull daily exposures and PnL data: \n", "\n", "Now that we have our performance report, we can leverage the unique functionalities of the PerformanceReport class to pull daily exposure and PnL data:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "all_exposures = performance_report.get_many_measures(\n", " start_date=start_date, end_date=end_date, measures=['pnl', 'grossExposure', 'netExposure']\n", ")\n", "\n", "print(all_exposures)\n", "all_exposures.plot(title='Performance Breakdown')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Step 3: Pull Brinson Attribution Data \n", "Now let's pull Brinson Attribution data to analyze the PnL of your portfolio compared to a benchmark, which can be any equity ETF, Index, or Basket in Marquee:\n", "\n", "*Important Note: An error in the code below might occur if time period for which you are querying is too long*" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "asset = Asset.get(id_value='MXWO', id_type=AssetIdentifier.TICKER)\n", "\n", "brinson_attribution_results = performance_report.get_brinson_attribution(\n", " benchmark=asset.get_marquee_id(), include_interaction=True, start_date=start_date, end_date=end_date\n", ")\n", "\n", "display(pd.DataFrame(brinson_attribution_results))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Step 4: Pull Asset Level Performance Data\n", "\n", "We can pull asset level data from the performance report.\n", "\n", "You can extract asset level data of quantity, netWeight, netExposure, etc.., using `get_positions_data`.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "asset_level_data = performance_report.get_positions_data(\n", " start=performance_report.earliest_start_date, end=performance_report.earliest_start_date\n", ")\n", "\n", "asset_level_data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also pull asset level contribution to PnL using the `pnl_contribution` function used below." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pnl_contribution = performance_report.get_pnl_contribution(\n", " start_date=performance_report.earliest_start_date, end_date=performance_report.latest_end_date\n", ")\n", "\n", "pnl_contribution" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/10_one_delta/Portfolios/08_Get_ESG_Analytics.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": true, "pycharm": { "name": "#%% md\n" } }, "source": [ "# Pull Marquee ESG Analytics with GS Quant\n", "\n", "Portfolio ESG Analytics are driven by the Goldman Sachs GIR SUSTAIN Headline Metrics dataset. Using data from third-party ESG data providers, the GIR SUSTAIN team has identified what it believes to be the most relevant Environmental & Social (E&S) exposures for a company’s sector and Governance (G) exposure relative to its region and the global SUSTAIN ESG universe. These metrics form the framework through which to evaluate corporate ESG engagement. We caution at the outset that this data should not be viewed as signalling 'good' or 'bad' ESG or overall performance. Instead, the framework highlights a company’s peer-relative performance on the key ESG metrics that the team believes to be the most relevant for that sector as a basis for further analysis.\n", "\n", "Please note that for companies with multiple share classes, SUSTAIN ESG data will only be provided for the primary share class. If other share classes are included in your portfolio, the ESG metrics will be represented as N/A and aggregate metrics will not include them." ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 1: Authenticate and Initialize Your Session\n", "\n", "First you will import the necessary modules and add your client id and client secret." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "import datetime as dt\n", "import warnings\n", "\n", "from gs_quant.api.gs.esg import ESGMeasure, ESGCard\n", "from gs_quant.markets.portfolio_manager import PortfolioManager\n", "from gs_quant.session import GsSession, Environment\n", "from IPython.display import display\n", "\n", "client = None\n", "secret = None\n", "\n", "## External users must fill in their client ID and secret below and comment out the line below\n", "\n", "# client = 'ENTER CLIENT ID'\n", "# secret = 'ENTER CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret)\n", "\n", "warnings.filterwarnings(\"ignore\", category=RuntimeWarning)\n", "\n", "\n", "print('GS Session initialized.')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 2: Define Your Entity\n", "\n", "ESG can be pulled from any object that inherits from the `PositionedEntity` class, such as `PortfolioManager`, `Index` or `Basket`. In this example, we will get ESG data for a Marquee portfolio." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "pm = PortfolioManager('ENTER YOUR PORTFOLIO ID')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "Now we will walk through some ESG analytics you can pull from your portfolio. For all of the following examples, if no date is provided, the results will be as of the last previous business day.\n", "\n", "## Step 3: Get Portfolio's Weight Averaged ESG Percentile Values\n", "\n", "Easily pull your weighted average percentile values of your portfolio on a given day:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "print('Portfolio Summmary:')\n", "display(pm.get_esg_summary(pricing_date=dt.date(2021, 9, 1)))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, "source": [ "### Quintile Breakdown\n", "\n", "If you want to see what percent of your portfolio has an ESG percentile value between 0-20%, 20%-40%,\n", "and so on, it's possible to pull that information for your requested ESG measure:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "print('Quintile Breakdown:')\n", "display(pm.get_esg_quintiles(measure=ESGMeasure.ES_PERCENTILE, pricing_date=dt.date(2021, 9, 1)))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Breakdown by Region and Sector\n", "\n", "View your portfolio's weighted average ESG percentile value by GIR SUSTAIN subsector..." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "print('Sector Breakdown:')\n", "display(pm.get_esg_by_sector(measure=ESGMeasure.ES_PERCENTILE, pricing_date=dt.date(2021, 9, 1)))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Breakdown by Region and Sector\n", "\n", "and by GIR SUSTAIN region:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "print('Region Breakdown:')\n", "display(pm.get_esg_by_region(measure=ESGMeasure.ES_PERCENTILE, pricing_date=dt.date(2021, 9, 1)))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, "source": [ "### Top Ten and Bottom Ten Ranked\n", "\n", "Get a list of your ten positions with the highest ESG percentile values..." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "print('Top Ten Ranked:')\n", "display(pm.get_esg_top_ten(measure=ESGMeasure.ES_PERCENTILE, pricing_date=dt.date(2021, 9, 1)))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, "source": [ "and a list of your ten positions with the lowest ESG percentile values:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "print('Bottom Ten Ranked:')\n", "display(pm.get_esg_bottom_ten(measure=ESGMeasure.ES_PERCENTILE, pricing_date=dt.date(2021, 9, 1)))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Quick Tip!\n", "If you would like to pull data for multiple measures at once, you can leverage the `get_all_esg_data` function:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "aggregated_esg_results = pm.get_all_esg_data(\n", " measures=[ESGMeasure.ES_PERCENTILE, ESGMeasure.G_PERCENTILE],\n", " cards=[ESGCard.QUINTILES, ESGCard.MEASURES_BY_SECTOR],\n", " pricing_date=dt.date(2021, 9, 1),\n", ")\n", "print(aggregated_esg_results)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### You're all set, Congrats!" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/10_one_delta/Portfolios/09_Get_Carbon_Analytics.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": true, "pycharm": { "name": "#%% md\n" } }, "source": [ "# Pull Marquee Carbon Analytics Data with GS Quant\n", "\n", "### Permission Prerequisites\n", "\n", "To execute all the code in this tutorial, you will need the following application scopes:\n", "- **read_financial_data**\n", "\n", "## Step 1: Authenticate and Initialize Your Session\n", "\n", "First you will import the necessary modules and add your client id and client secret." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "import warnings\n", "\n", "from IPython.display import display\n", "from gs_quant.api.gs.assets import GsAssetApi\n", "from gs_quant.api.gs.carbon import CarbonCoverageCategory, CarbonEmissionsIntensityType\n", "from gs_quant.markets.portfolio_manager import PortfolioManager\n", "from gs_quant.session import GsSession, Environment\n", "\n", "client = None\n", "secret = None\n", "\n", "## External users must fill in their client ID and secret below and comment out the line below\n", "\n", "# client = 'ENTER CLIENT ID'\n", "# secret = 'ENTER CLIENT SECRET'\n", "\n", "GsSession.use(\n", " Environment.PROD,\n", " client_id=client,\n", " client_secret=secret,\n", ")\n", "\n", "warnings.filterwarnings(\"ignore\", category=RuntimeWarning)\n", "\n", "print('GS Session initialized.')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 2: Define Your Entity\n", "\n", "Carbon Analytics can be pulled from any object that inherits from the `PositionedEntity` class, such as `PortfolioManager`, `Index` or `Basket`. In this example, we will get analytics for a Marquee portfolio." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "pm = PortfolioManager('YOUR PORTFOLIO ID')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 3: Pull Carbon Analytics\n", "\n", "There are various parameters each of these methods take in.\n", " - reporting_year - takes 'Latest' and last 4 complete years i.e., from T-2 to T-5. defaults to Latest\n", " - currency - uses 'Currency' enum, defaults to the entity currency.\n", " - include_estimates - Parameter to choose if estimated emissions are to be included or not, defaults to false.\n", " - use_historical_data - Parameter to choose historical positions or backcast latest composition, defaults to false.\n", " - normalize_emissions - Parameter to normalize entity notional to 1,000,000 in denominated currency passed.\n", " - analytics_view - Parameter to view analytics using long component or short component of the portfolio\n", " - scope - uses 'CarbonScope' enum with totalGHG, scope1, scope2 values, defaults to totalGHG.\n", " - coverage_category - category for data coverage, uses 'CarbonCoverageCategory' enum with weights and numberOfCompanies, defaults to weights.\n", " - target_coverage_category - category for SBTI and Net Zero Targets, uses 'CarbonTargetCoverageCategory' enum with portfolioEmissions and capitalAllocated, defaults to portfolioEmissions.\n", " - classification - classification to group financed emissions, uses CarbonEmissionsAllocationCategory with sector, industry and region, defaults to sector\n", " - intensity_metric - intensity metric to query attribution for, uses CarbonEmissionsIntensityType with enterprise value, marketcap and revenue. defaults to enterprise value.\n", " - benchmark_id - Marquee identifier for the benchmark to do attribution analysis with." ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Data Coverage\n", "\n", "Pull the data coverage for a reporting year based on weights or number of companies in the entity. You can choose to include estimated emissions, choose between using historical compositions or backcasting latest composition" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "print('Data Coverage:')\n", "display(pm.get_carbon_coverage(include_estimates=True, coverage_category=CarbonCoverageCategory.NUMBER_OF_COMPANIES))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### SBTI and Net Zero Emissions Target Coverage\n", "\n", "Pull Science Based Target Coverage and Net Zero Emissions Target Coverage for a reporting year based on capital allocated or portfolio emissions." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "print('Science Based Target and Net Zero Emissions Target Coverage:')\n", "display(pm.get_carbon_sbti_netzero_coverage(include_estimates=True))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Financed Emissions and Emissions Intensity profile\n", "\n", "Pull Financed Emissions profile and intensity metrics for a reporting year in respective denomination. Other parameters are to include estimates, use historical data, normalize emissions." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "print('Financed Emissions and Intensity Profile:')\n", "display(pm.get_carbon_emissions(include_estimates=True))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Financed Emissions by sector, industry and region\n", "\n", "Aggregate financed emissions and capital for each of the categories for a reporting year in respective denomination. We can pass a scope to look at specific scope." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "print('Financed Emissions by Sector:')\n", "display(pm.get_carbon_emissions_allocation(include_estimates=True))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Attribution analysis with benchmark\n", "\n", "Pull up brinson attribution analysis for sector allocation and security selection with respect to benchmark. Pass in the benchmark id, intensity type.\n", "\n", "Benchmark can be either an asset id or a portfolio id. For an asset, get the asset id by resolving the identifier and, for a portfolio use the portfolio id.\n", "\n", "Resolve asset id from identifier:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "identifier = 'SPX'\n", "mqids = GsAssetApi.resolve_assets(identifier=[identifier], fields=['id'], limit=1)\n", "try:\n", " benchmark_id = mqids[identifier][0]['id']\n", "except:\n", " raise ValueError('Error in resolving the following identifier: ' + identifier)\n", "\n", "print('Attribution Analysis:')\n", "display(\n", " pm.get_carbon_attribution_table(\n", " benchmark_id=benchmark_id, include_estimates=True, intensity_metric=CarbonEmissionsIntensityType.EI_REVENUE\n", " )\n", ")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/10_one_delta/Portfolios/10_Get_Thematic_Analytics.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": true, "pycharm": { "name": "#%% md\n" } }, "source": [ "# Pull Portfolio Thematic Analytics with GS Quant\n", "\n", "## Permission Prerequisites\n", "\n", "To execute all the code in this tutorial, you will need the following application scopes:\n", "- **read_product_data**\n", "- **read_financial_data**\n", "- **run_analytics** (must be requested)\n", "\n", "## Step 1: Authenticate and Initialize Your Session\n", "\n", "First you will import the necessary modules and add your client id and client secret." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "import datetime as dt\n", "from IPython.display import display\n", "\n", "from gs_quant.markets.portfolio_manager import PortfolioManager\n", "from gs_quant.session import GsSession, Environment\n", "import pandas as pd\n", "\n", "client = None\n", "secret = None\n", "\n", "## External users must fill in their client ID and secret below and comment out the line below\n", "\n", "# client = 'ENTER CLIENT ID'\n", "# secret = 'ENTER CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret)\n", "\n", "print('GS Session initialized.')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 2: Get Portfolio Thematic Report" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "thematic_report = PortfolioManager('ENTER YOUR PORTFOLI ID').get_thematic_report()\n", "\n", "print(f'Thematic report found with ID: {thematic_report.id}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 3: Current Thematic Exposure to All Baskets\n", "\n", "Once your thematic report is scheduled as of the latest business day, you can view your portfolio's current exposure and\n", "beta to every flagship basket in our thematic factor model in just a few lines of code:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "thematic_exposures = thematic_report.get_all_thematic_exposures(\n", " start_date=thematic_report.latest_end_date, end_date=thematic_report.latest_end_date\n", ")\n", "\n", "pd.set_option('display.max_colwidth', 0)\n", "\n", "display(thematic_exposures)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 4: Get Thematic Exposure Breakdown for a Basket\n", "\n", "Interested in a more granular breakdown of your exposure to a particular basket? Pull your thematic breakdown by asset\n", "on a desired date:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "date = dt.date(\n", " 2024, 12, 12\n", ") # If date is not in range of portfolio's position data there will not be any thematic data returned.\n", "\n", "thematic_breakdown = thematic_report.get_thematic_breakdown(date, \"BASKET'S MARQUEE ID\")\n", "\n", "display(thematic_breakdown)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 5: Historical Thematic Exposure\n", "\n", "You can also pull the historical change in your thematic exposure to a basket:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "historical_exposures = thematic_report.get_all_thematic_exposures(\n", " start_date=thematic_report.earliest_start_date,\n", " end_date=thematic_report.latest_end_date,\n", " basket_ids=[\"BASKET'S MARQUEE ID\"],\n", ")[['Date', 'Thematic Exposure']]\n", "\n", "\n", "historical_exposures.plot(title='Historical Exposure to GSXUSTAY')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*Other questions? Reach out to the [Portfolio Analytics team](mailto:gs-marquee-analytics-support@gs.com)!*" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: gs_quant/documentation/10_one_delta/Reports/01_Factor_Risk_Report.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": true, "pycharm": { "name": "#%% md\n" } }, "source": [ "# Factor Risk Reports\n", "\n", "Factor risk reports run historical factor risk analyses for your portfolio or basket over a specified date range by leveraging a factor risk model of your choice.\n", "\n", "## Step 1: Authenticate and Initialize Your Session\n", "\n", "First you will import the necessary modules and add your client id and client secret." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "import datetime as dt\n", "from time import sleep\n", "\n", "import pandas as pd\n", "\n", "from gs_quant.markets.report import FactorRiskReport\n", "from gs_quant.models.risk_model import FactorRiskModel\n", "from gs_quant.session import GsSession, Environment\n", "\n", "client = None\n", "secret = None\n", "scopes = None\n", "\n", "## External users must fill in their client ID and secret below and comment out the line below\n", "# client = 'ENTER CLIENT ID'\n", "# secret = 'ENTER CLIENT SECRET'\n", "\n", "GsSession.use(\n", " Environment.PROD,\n", " client_id=client,\n", " client_secret=secret,\n", ")\n", "\n", "print('GS Session initialized.')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 2: Create a New Factor Risk Report\n", "\n", "#### Already have a factor risk report?\n", "\n", "If you want to skip creating a new report and continue this tutorial with an existing factor risk report, run the following and skip to Step 3:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "risk_report_id = 'FACTOR RISK REPORT ID'\n", "\n", "risk_report = FactorRiskReport.get(risk_report_id)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When creating a factor risk report, you must specify the risk model you would like to use.\n", "\n", "\n", "If you would like to see all available risk model IDs to choose from, run the following:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "risk_models = FactorRiskModel.get_many()\n", "for risk_model in risk_models:\n", " print(f'{risk_model.id}\\n')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this tutorial, we'll create a factor risk report leveraging the Barra USSLOW Long model. If you would like to calculate\n", "risk in relation to a benchmark, you can add an index, basket, or ETF to your `FactorRiskReport` object:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "entity_id = 'PORTFOLIO ID'\n", "risk_model_id = 'RISK MODEL ID'\n", "\n", "\n", "risk_report = FactorRiskReport(\n", " risk_model_id=risk_model_id,\n", " fx_hedged=True,\n", ")\n", "\n", "risk_report.set_position_source(entity_id)\n", "risk_report.save()\n", "\n", "print(f'A new factor risk report for entity \"{entity_id}\" has been made with ID \"{risk_report.id}\".')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 3: Schedule the Report\n", "\n", "When scheduling reports, you have two options:\n", "- Backcast the report: Take the earliest date with positions in the portfolio / basket and run the report on the positions held then with a start date before the earliest position date and an end date\n", " of the earliest position date\n", "- Do not backcast the report: Set the start date as a date that has positions in the portfolio or basket and an end date after that (best practice is to set it to T-1). In this case the\n", " report will run on positions held as of each day in the date range\n", "\n", "In this case, let's try scheduling the report without backcasting:\n", "\n", "### Note:\n", "Cannot Schedule backcasted report with no position sets on the report job end date" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "start_date = dt.date(2025, 4, 1)\n", "end_date = dt.date(2025, 4, 17)\n", "\n", "risk_report.schedule(start_date=start_date, end_date=end_date, backcast=True)\n", "\n", "print(f'Report \"{risk_report.id}\" has been scheduled.')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Alternative Step 3: Run the Report\n", "\n", "Depending on the size of your portfolio and the length of the schedule range, it usually takes anywhere from a couple seconds to a couple minutes for your report to finish executing.\n", "Only after that can you successfully pull the results from that report. If you would rather run the report and pull the results immediately after they are ready, you can leverage the `run`\n", "function.\n", "\n", "You can run a report synchronously or asynchronously.\n", "- Synchronous: the Python script will stall at the `run` function line and wait for the report to finish. The `run` function will then return a dataframe with the report results\n", "- Asynchronously: the Python script will not stall at the `run` function line. The `run` function will return a `ReportJobFuture` object that will contain the report results when they are ready.\n", "\n", "In this example, let's run the report asynchronously and wait for the results:\n", "\n", "### Note:\n", "Cannot Schedule historical report with no position sets before the report job start date" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "start_date = dt.date(2025, 4, 2)\n", "end_date = dt.date(2025, 4, 7)\n", "\n", "report_result_future = risk_report.run(start_date=start_date, end_date=end_date, backcast=False, is_async=True)\n", "\n", "while not report_result_future.done():\n", " print('Waiting for report results...')\n", " sleep(5)\n", "\n", "print('\\nReport results done! Here they are...')\n", "print(report_result_future.result())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_date = dt.date(2025, 2, 6)\n", "end_date = dt.date(2025, 3, 31)\n", "\n", "report_result_future = risk_report.run(start_date=start_date, end_date=end_date, backcast=False, is_async=True)\n", "\n", "while not report_result_future.done():\n", " print('Waiting for report results...')\n", " sleep(5)\n", "\n", "print('\\nReport results done! Here they are...')\n", "print(report_result_future.result())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 4: Pull Report Results\n", "\n", "Now that we have our completed factor risk report, we can leverage the unique functionalities of the `FactorRiskReport` class to pull attribution and risk data. \n", "\n", "This `get_results` function will return the date indexed data as shown in the dummy table below:\n", "\n", "|date |factor |factorCategory|exposure|pnl|dailyRisk|annualRisk|proportionOfRisk|relativeMarginalContributionToRisk|aum|marginalContributionToRiskPercent|\n", "|----------|------------|---------------|-----------|--------------|--------------|------------|------------|-------------|----|-------|\n", "|2025-04-10|\tSpecific|\tAggregations|\t0.000000|\t-186.921292|\t116.350041|\t1846.999635|\t0.235141|\t895.635113|\t0.0|\tNaN|\n", "|2025-04-11|\tSpecific|\tAggregations|\t0.000000|\t-17.822047|\t117.014467|\t1857.547084|\t0.230338|\t891.503322|\t0.0|\tNaN|\n", "|2025-04-14|\tSpecific|\tAggregations|\t0.000000|\t73.498315|\t118.745143|\t1885.020702|\t0.225550|\t895.235643|\t0.0|\tNaN|\n", "|2025-04-15|\tSpecific|\tAggregations|\t0.000000|\t58.400802|\t119.321131|\t1894.164234|\t0.223980|\t896.442371|\t2000000.0|\t0.044822|\n", "|2025-04-16|\tSpecific|\tAggregations|\t0.000000|\t23.749292|\t116.631423|\t1851.466441|\t0.222957|\t874.230618|\t1000000.0|\t0.087423|" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "factor_and_total_results = risk_report.get_results(\n", " factors=['Factor', 'Specific'], start_date=dt.date(2025, 4, 10), end_date=dt.date(2025, 4, 17)\n", ")\n", "factor_and_total_results" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Quick Tip & Plotting!\n", "You can pull historical data on factor, specific, and total PnL:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "pnl = risk_report.get_factor_pnl(factor_names=['Factor', 'Specific', 'Total'], start_date=start_date, end_date=end_date)\n", "pnl.set_index('Date', inplace=True)\n", "pnl.index = pd.to_datetime(pnl.index)\n", "\n", "pnl.cumsum().plot(title='Risk Attribution Breakdown')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now let's pull the breakdown of proportion of risk among the different factor types over time:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "prop_of_risk = risk_report.get_factor_proportion_of_risk(\n", " factor_names=['Market', 'Style', 'Industry', 'Country'], start_date=start_date, end_date=end_date\n", ")\n", "prop_of_risk.set_index('Date', inplace=True)\n", "prop_of_risk.index = pd.to_datetime(prop_of_risk.index)\n", "\n", "prop_of_risk.plot(title='Factor Proportion of Risk Breakdown')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Please find more functions to pull specific factor risk data available [here](https://developer.gs.com/docs/gsquant/api/classes/gs_quant.markets.report.FactorRiskReport.html#gs_quant.markets.report.FactorRiskReport).**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### You're all set; Congrats!\n", "\n", "*Other questions? Reach out to the [Portfolio Analytics team](mailto:gs-marquee-analytics-support@gs.com)!*\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: gs_quant/documentation/10_one_delta/Reports/02_Performance_Report.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "# Performance Reports\n", "\n", "Performance reports run historical analyses on measures like exposure and PnL for your portfolio over a specified date range." ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 1: Authenticate and Initialize Your Session\n", "\n", "First you will import the necessary modules and add your client id and client secret." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "import datetime as dt\n", "import pandas as pd\n", "from time import sleep\n", "\n", "from gs_quant.markets.portfolio_manager import PortfolioManager\n", "from gs_quant.markets.report import PerformanceReport\n", "from gs_quant.markets.securities import Asset, AssetIdentifier\n", "from gs_quant.session import GsSession, Environment\n", "\n", "client = None\n", "secret = None\n", "\n", "## External users must fill in their client ID and secret below and comment out the line below\n", "# client = 'ENTER CLIENT ID'\n", "# secret = 'ENTER CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret)\n", "\n", "print('GS Session initialized.')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 2: Get Performance Report\n", "\n", "When creating a Marquee portfolio, a corresponding performance report for it is automatically created. Thus, we can leverage the `PortfolioManager` class to pull all the reports associated with your portfolio and find the performance report." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "portfolio_id = 'PORTFOLIO ID'\n", "\n", "all_reports = PortfolioManager(portfolio_id).get_reports()\n", "performance_report = list(filter(lambda report: isinstance(report, PerformanceReport), all_reports))[0]\n", "\n", "print(f'Performance report for portfolio \"{portfolio_id}\" has been found with ID \"{performance_report.id}\".')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 3: Schedule the Report\n", "\n", "When scheduling reports, you have two options:\n", "- Backcast the report: Take the earliest date with positions in the portfolio / basket and run the report on the positions held then with a start date before the earliest position date and an end date\n", " of the earliest position date\n", "- Do not backcast the report: Set the start date as a date that has positions in the portfolio or basket and an end date after that (best practice is to set it to T-1). In this case the\n", " report will run on positions held as of each day in the date range\n", "\n", "In this case, let's try scheduling the report without backcasting:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "start_date = dt.date(2025, 4, 18)\n", "end_date = dt.date(2025, 4, 23)\n", "\n", "performance_report.schedule(start_date=start_date, end_date=end_date, backcast=False)\n", "\n", "print(\n", " f'A new performance report for portfolio \"{performance_report.position_source_id}\" has been made with ID \"{performance_report.id}\".'\n", ")" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Alternative Step 3: Run the Report\n", "\n", "Depending on the size of your portfolio and the length of the schedule range, it usually takes anywhere from a couple seconds to a couple minutes for your report to finish executing.\n", "Only after that can you successfully pull the results from that report. If you would rather run the report and pull the results immediately after they are ready, you can leverage the `run`\n", "function of the `Report` class.\n", "\n", "You can run a report synchronously or asynchronously.\n", "- Synchronous: the Python script will stall at the `run` function line and wait for the report to finish. The `run` function will then return a dataframe with the report results\n", "- Asynchronously: the Python script will not stall at the `run` function line. The `run` function will return a `ReportJobFuture` object that will contain the report results when they are ready.\n", "\n", "In this example, let's run the report asynchronously and wait for the results:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "start_date = dt.date(2025, 2, 10)\n", "end_date = dt.date(2025, 2, 12)\n", "\n", "report_result_future = performance_report.run(start_date=start_date, end_date=end_date, backcast=False, is_async=True)\n", "\n", "while not report_result_future.done():\n", " print('Waiting for report results...')\n", " sleep(5)\n", "\n", "print('\\nReport results done! Here they are...')\n", "print(report_result_future.result())" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 4: Pull Report Results\n", "\n", "Now that we have our performance report, we can leverage the unique functionalities of the `PerformanceReport` class to pull daily exposure and PnL data:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "all_exposures = performance_report.get_many_measures(\n", " measures=['pnl', 'grossExposure', 'netExposure'],\n", " start_date=performance_report.earliest_start_date,\n", " end_date=performance_report.latest_end_date,\n", ")\n", "\n", "print(all_exposures)\n", "all_exposures.plot(title='Performance Breakdown')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "Leverage the Brinson Attribution function in the `PerformanceReport` class to compare the PnL of your portfolio to a benchmark, which can be any equity ETF, Index, or Basket in Marquee:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "start_date = performance_report.earliest_start_date # Can also set to: dt.date(YYYY, MM, DD)\n", "end_date = performance_report.latest_end_date # Can also set to: dt.date(YYYY, MM, DD)\n", "\n", "asset = Asset.get(id_value='SPY UP', id_type=AssetIdentifier.BLOOMBERG_ID)\n", "\n", "brinson_attribution_results = performance_report.get_brinson_attribution(\n", " benchmark=asset.get_marquee_id(), include_interaction=True, start_date=start_date, end_date=end_date\n", ")\n", "\n", "display(pd.DataFrame(brinson_attribution_results))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can extract asset level data of quantity, netWeight, netExposure, etc.., using `get_positions_data`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "asset_level_data = performance_report.get_positions_data(\n", " start=performance_report.earliest_start_date, end=performance_report.earliest_start_date\n", ")\n", "\n", "asset_level_data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also pull asset level contribution to PnL using the `pnl_contribution` function used below." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pnl_contribution = performance_report.get_pnl_contribution(\n", " start_date=performance_report.earliest_start_date, end_date=performance_report.latest_end_date\n", ")\n", "\n", "pnl_contribution" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### You're all set; Congrats!\n", "\n", "*Other questions? Reach out to the [Portfolio Analytics team](mailto:gs-marquee-analytics-support@gs.com)!*" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/10_one_delta/Reports/03_Thematic_Report.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": true, "pycharm": { "name": "#%% md\n" } }, "source": [ "# Thematic Reports\n", "\n", "Thematic reports run historical analyses on the exposure of a portfolio to various Goldman Sachs Flagship Thematic baskets over a specified date range.\n", "\n", "## Step 1: Authenticate and Initialize Your Session\n", "\n", "First you will import the necessary modules and add your client id and client secret." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "import datetime as dt\n", "from time import sleep\n", "\n", "from gs_quant.markets.portfolio_manager import PortfolioManager\n", "from gs_quant.markets.report import ThematicReport\n", "from gs_quant.markets.securities import Asset, AssetIdentifier\n", "from gs_quant.session import GsSession, Environment\n", "\n", "client = None\n", "secret = None\n", "\n", "## External users must fill in their client ID and secret below and comment out the line below\n", "# client = 'ENTER CLIENT ID'\n", "# secret = 'ENTER CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret)\n", "\n", "print('GS Session initialized.')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 2: Create a New Thematic Report\n", "\n", "#### Already have a thematic report?\n", "\n", "If you want to skip creating a new report and continue this tutorial with an existing thematic report, run the following and skip to Step 3:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "portfolio_id = 'ENTER PORTFOLIO ID'\n", "\n", "thematic_report = PortfolioManager(portfolio_id).get_thematic_report()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The only parameter necessary in creating a new thematic report is the unique Marquee identifier of the portfolio on which you would like to run thematic analytics." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "portfolio_id = 'ENTER PORTFOLIO ID'\n", "\n", "thematic_report = ThematicReport()\n", "thematic_report.set_position_source(portfolio_id)\n", "thematic_report.save()\n", "\n", "print(f'A new thematic report for portfolio \"{portfolio_id}\" has been made with ID \"{thematic_report.id}\".')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 3: Schedule the Report\n", "\n", "When scheduling reports, you have two options:\n", "- Backcast the report: Take the earliest date with positions in the portfolio / basket and run the report on the positions held then with a start date before the earliest position date and an end date\n", " of the earliest position date\n", "- Do not backcast the report: Set the start date as a date that has positions in the portfolio or basket and an end date after that (best practice is to set it to T-1). In this case the\n", " report will run on positions held as of each day in the date range\n", "\n", "In this case, let's try scheduling the report without backcasting:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "start_date = dt.date(2021, 1, 4)\n", "end_date = dt.date(2021, 8, 4)\n", "\n", "thematic_report.schedule(start_date=start_date, end_date=end_date, backcast=False)\n", "\n", "print(f'Report \"{thematic_report.id}\" has been scheduled.')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Alternative Step 3: Run the Report\n", "\n", "Depending on the size of your portfolio and the length of the schedule range, it usually takes anywhere from a couple seconds to half a minute for your report to finish executing.\n", "Only after that can you successfully pull the results from that report. If you would rather run the report and pull the results immediately after they are ready, you can leverage the `run`\n", "function.\n", "\n", "You can run a report synchronously or asynchronously.\n", "- Synchronous: the Python script will stall at the `run` function line and wait for the report to finish. The `run` function will then return a dataframe with the report results\n", "- Asynchronously: the Python script will not stall at the `run` function line. The `run` function will return a `ReportJobFuture` object that will contain the report results when they are ready.\n", "\n", "In this example, let's run the report asynchronously and wait for the results:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "start_date = dt.date(2021, 1, 4)\n", "end_date = dt.date(2021, 8, 4)\n", "\n", "report_result_future = thematic_report.run(start_date=start_date, end_date=end_date, backcast=False, is_async=True)\n", "\n", "while not report_result_future.done():\n", " print('Waiting for report results...')\n", " sleep(5)\n", "\n", "print('\\nReport results done! Here they are...')\n", "print(report_result_future.result())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Step 3: Pull Report Results\n", "\n", "Now that we have our factor risk report, we can leverage the unique functionalities of the `ThematicReport` class to pull exposure and PnL data. Let's get the historical changes in thematic exposure and beta to a GS basket:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "basket = Asset.get(id_value='BASKET ID', id_type=AssetIdentifier.TICKER)\n", "thematic_exposures = thematic_report.get_thematic_data(\n", " start_date=start_date, end_date=end_date, basket_ids=[basket.get_marquee_id()]\n", ")\n", "\n", "print(f'Thematic Exposures: \\n{thematic_exposures.__str__()}')\n", "thematic_exposures.plot(title='Thematic Data Breakdown')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### You're all set; Congrats!\n", "\n", "*Other questions? Reach out to the [Portfolio Analytics team](mailto:gs-marquee-analytics-support@gs.com)!*" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: gs_quant/documentation/11_macro_models/01_Query_Macro_Models.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "# Discover your Macro risk with Quant Insight models\n", "\n", "Quant Insight's Macro risk models provide an insight in the relationship between movement in an asset price and\n", "macroeconomic factors such as economic growth, monetary policy and commodity prices. The goal is to understand how much\n", "of the movement in the asset price is attributable to those macroeconomic factors. The GS quant class `MacroRiskModel`\n", "provides an array of functions that query macro risk model data. In this tutorial, we will look at querying all the\n", "available macro risk model data." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 1: Authenticate and Initialize Your Session\n", "\n", "First you will import the necessary modules and add your client id and client secret." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession, Environment\n", "\n", "from gs_quant.models.risk_model import (\n", " MacroRiskModel,\n", " DataAssetsRequest,\n", " RiskModelUniverseIdentifierRequest as UniverseIdentifier,\n", " FactorType,\n", " RiskModelUniverseIdentifierRequest,\n", " Unit,\n", ")\n", "\n", "from gs_quant.markets.portfolio_manager import PortfolioManager\n", "from gs_quant.markets.factor import Factor\n", "\n", "\n", "import datetime as dt\n", "import matplotlib.pyplot as plt\n", "import pandas as pd\n", "import numpy as np\n", "from itertools import groupby, cycle\n", "import seaborn as sns\n", "import warnings\n", "from IPython.display import display\n", "from typing import Dict\n", "\n", "\n", "client = None\n", "secret = None\n", "\n", "## External users must fill in their client ID and secret below and comment out the line below\n", "\n", "# client = 'YOUR CLIENT ID'\n", "# secret = 'YOUR SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret)\n", "warnings.filterwarnings(\"ignore\", category=RuntimeWarning)\n", "\n", "print('GS Session initialized.')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 2: Get All Available Macro Factor Categories\n", "\n", "We can leverage `get_factor_data` in the `MacroRiskModel` class to get all the available macro factor categories\n", "in the model.\n", "\n", "#### Macro Factor Data\n", "\n", "Macro Factors are grouped within a factor category.\n", "Here are some examples of macro factor categories along with a list of macro factors in that category:\n", "\n", "| Macro Factor Category | Macro Factors |\n", "|-----------------------|------------------------------------------------------------|\n", "| Inflation | US 5Y Infl. Expec., US 2Y Infl. Expec., US 10Y Infl. Expec.|\n", "| Economic Growth | Japan GDP, Euro GDP, China GDP |\n", "| CB Rate Expectations | Fed Rate Expectations |\n", "| Energy | WTI |\n", "| Risk Aversion | VDAX, VIX, VXEEM, Gold Silver Ratio |\n", "\n", "For more macro factor categories and their descriptions, see the factor glossary." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "start_date = dt.date(2025, 2, 4)\n", "\n", "model_id = \"RISK MODEL ID\" # Such as \"QI_US_EQUITY_LT\"\n", "model = MacroRiskModel.get(model_id)\n", "\n", "factor_category_data = model.get_factor_data(start_date=start_date, factor_type=FactorType.Factor)\n", "\n", "factor_category_data_reshaped = (\n", " factor_category_data.set_index(\"factorCategoryId\")\n", " .drop(columns={\"name\", \"type\", \"identifier\"})\n", " .drop_duplicates()\n", " .rename_axis(\"Factor Category Id\")\n", " .rename(columns={\"factorCategory\": \"Factor Category\"})\n", ")\n", "\n", "display(factor_category_data_reshaped)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 3: Get All Available Macro Factors\n", "\n", "Within each macro factor category, we have several macro factors that are grouped together." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "start_date = dt.date(2025, 2, 4)\n", "\n", "model_id = \"RISK_MODEL_ID\" # Such as \"QI_US_EQUITY_LT\"\n", "model = MacroRiskModel.get(model_id)\n", "\n", "# Get the factors in dataframe with name, and type\n", "factor_data = model.get_factor_data(start_date=start_date, factor_type=FactorType.Factor)\n", "\n", "factor_data_reshaped = (\n", " factor_data.rename(columns={\"name\": \"Factor\", \"factorCategory\": \"Factor Category\"})\n", " .sort_values(by=[\"Factor Category\"])\n", " .drop(columns={\"type\", \"factorCategoryId\", \"identifier\"})\n", " .set_index(\"Factor Category\")\n", " .stack()\n", " .to_frame()\n", " .droplevel(level=1)\n", " .rename(columns={0: \"Factor\"})\n", ")\n", "display(factor_data_reshaped)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "Further, for a more granular view, we can get all the macro factors that are grouped within a factor category.\n", "Below is a list of all macro factors in the factorCategory \"Risk Aversion\"." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "macro_factors_in_risk_aversion = factor_data_reshaped.groupby(\"Factor Category\").get_group(\"Risk Aversion\")\n", "display(macro_factors_in_risk_aversion)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 4: Get your Portfolio Exposure to Macro Factors and Categories\n", "\n", "The metrics below are instrumental in providing insight in the relationship between movement of asset price and macro\n", "factors. These key metrics are outlined below:\n", "\n", "| Metric | Description |\n", "|----------------------------|---------------|\n", "| `Universe Macro Factor Sensitivity` | Percentage change in asset price for 1 standard deviation move up in the macro factor. |\n", "| `R Squared (Model Confidence)` | Gauge of how sensitive the asset is to macroeconomic forces. Typically values above 65% are considered to show strong explanatory power or ‘confidence’. |\n", "| `Fair Value Gap` | The difference between the actual price of an asset and the Qi Model Value. This is quoted both in absolute (in percentage) and in standard deviation terms. |\n", "| `Specific Risk` | Annualized asset volatility. |\n", "\n", "\n", "Given a portfolio, we can get its cash exposure to a 1 standard deviation move up in each macro factor. The exposures are\n", "calculated by multiplying each asset notional amount to their sensitivity to each factor, which gives us individual asset\n", " exposure to each macro factor. We then aggregate per asset exposures to get portfolio cash exposure to each macro \\\n", " factor.\n", "We can leverage the function `get_macro_exposure_table` of the `PortfolioManager` class. Note that exposure is expressed\n", "in the currency of the notional value of the portfolio.\n", "\n", "We can get portfolio exposure to both macro factor categories and macro factors. We can thus gain an insight\n", "in which factor categories and macro factors within these categories are driving the portfolio.\n", "\n", "#### Exposure to Macro Factor Categories\n", "\n", "We need to pass the following parameters to the function:\n", "* `macro_risk_model`: The model to base your exposure data on\n", "* `date`: date for which to get exposure\n", "* `group_by_factor_category`: whether to get exposure per factor category.\n", "\n", "The result will be sorted from top positive drivers to top negative drivers\n", "\n", "Note that, in the result, the macro factor categories are ranked from most positive exposure to most negative portfolio exposure\n", "\n", "Below:\n", "* We get asset level exposure to each factor category.\n", "* The last row of the resulting dataframe is the portfolio exposure to each factor category (asset level exposure aggregated)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Get the model\n", "model_id = \"RISK MODEL ID\" # Such as \"QI_US_EQUITY_LT\"\n", "model = MacroRiskModel.get(model_id)\n", "\n", "# Input the portfolio id\n", "portfolio_id = \"YOUR PORTFOLIO ID\"\n", "pm = PortfolioManager(portfolio_id)\n", "date = dt.date(2025, 2, 4)\n", "\n", "# Get Per Asset Factor Category Exposure Dataframe\n", "exposure_factor_category_df = pm.get_macro_exposure(model=model, date=date, factor_type=FactorType.Category)\n", "exposure_factor_category_df.loc[\"Total Factor Category Exposure\", \"Asset Name\"] = \"Total\"\n", "exposure_factor_category_df = exposure_factor_category_df.set_index(\"Asset Name\", drop=True)\n", "\n", "# Get the Portfolio Total\n", "total = exposure_factor_category_df.loc[\"Total\", :].to_frame().drop(\"Notional\")\n", "\n", "# Display the Dataframes.\n", "styles = [\n", " {'selector': \"caption\", 'props': [(\"font-size\", \"200%\"), (\"font-weight\", \"bold\"), (\"color\", 'black')]},\n", " {'selector': 'thead', 'props': [(\"background-color\", \"silver\"), (\"font-size\", \"13px\")]},\n", " {\n", " 'selector': 'tr',\n", " 'props': [\n", " (\"background-color\", \"whitesmoke\"),\n", " (\"font-size\", \"13px\"),\n", " (\"border\", \"solid\"),\n", " (\"border-width\", \"0.001em\"),\n", " ],\n", " },\n", " {'selector': 'td', 'props': [(\"background-color\", \"aliceblue\"), (\"font-size\", \"13px\")]},\n", "]\n", "exposure_styler = (\n", " exposure_factor_category_df.style.set_caption(\"Portfolio Cash Exposure to Factor Categories\")\n", " .format('${:,.0f}')\n", " .applymap(lambda v: \"color:red;\" if v < 0 else None)\n", " .set_table_styles(styles)\n", ")\n", "styles.pop(0)\n", "total_styler = (\n", " total.style.format('${:,.0f}').applymap(lambda v: \"color:red;\" if v < 0 else None).set_table_styles(styles)\n", ")\n", "display(exposure_styler)\n", "display(total_styler)\n", "\n", "# Plot\n", "fig, ax = plt.subplots(constrained_layout=True)\n", "sns.barplot(data=total.reset_index(), x=\"Factor Category\", y='Total', ax=ax)\n", "ax.set_xlabel(\"Factor Category\")\n", "ax.set_ylabel(\"Portfolio Notional Change for 1 std Move in the Factor Category\", fontsize='medium')\n", "ax.set_title(\"Portfolio Exposure to Factor Categories\")\n", "ax.tick_params(axis='x', labelrotation=50)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "#### Exposure to Macro Factors\n", "Once we get portfolio exposure to factor categories, we can get a granular view of the portfolio exposure to individual macro factors within that factor category.\n", "\n", "Here are the user inputs:\n", "* `model`: The model we are using.\n", "* `factor_categories`: A list of factor categories whose factors we want exposure for. If empty, it will default to returning exposure to the top 2 positive and negative macro drivers\n", "* `portfolio_id`: The portfolio we are getting macro exposure for.\n", "* `date`: Date " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Get the model\n", "model_id = \"RISK MODEL ID\" # Such as \"QI_US_EQUITY_LT\"\n", "model = MacroRiskModel.get(model_id)\n", "\n", "# Input the portfolio id\n", "portfolio_id = \"YOUR PORTFOLIO ID\"\n", "pm = PortfolioManager(portfolio_id)\n", "date = dt.date(2025, 2, 4)\n", "\n", "# Add Factor categories whose factors you want exposure for\n", "factor_categories = []\n", "factor_categories_data = model.get_many_factors(\n", " start_date=date, end_date=date, factor_names=factor_categories, factor_type=FactorType.Category\n", ")\n", "\n", "# Get Per Asset Factor Exposure dataframe\n", "exp_factor_df = pm.get_macro_exposure(\n", " model=model, date=date, factor_type=FactorType.Factor, factor_categories=factor_categories_data\n", ")\n", "exp_factor_df.loc[\"Total Factor Exposure\", (\"Asset Information\", \"Asset Name\")] = \"Total\"\n", "exp_factor_df = exp_factor_df.set_index((\"Asset Information\", \"Asset Name\"), drop=True)\n", "\n", "# Total Portfolio Exposure\n", "total = exp_factor_df.loc[\"Total\", :].to_frame().drop((\"Asset Information\", \"Notional\"))\n", "\n", "# Display the Dataframes.\n", "styles = [\n", " {'selector': \"caption\", 'props': [(\"font-size\", \"200%\"), (\"font-weight\", \"bold\"), (\"color\", 'black')]},\n", " {'selector': 'thead', 'props': [(\"background-color\", \"silver\"), (\"font-size\", \"13px\")]},\n", " {\n", " 'selector': 'tr',\n", " 'props': [\n", " (\"background-color\", \"whitesmoke\"),\n", " (\"font-size\", \"13px\"),\n", " (\"border\", \"solid\"),\n", " (\"border-width\", \"0.001em\"),\n", " ],\n", " },\n", " {'selector': 'td', 'props': [(\"background-color\", \"aliceblue\"), (\"font-size\", \"13px\")]},\n", "]\n", "exposure_styler = (\n", " exp_factor_df.style.set_caption(\"Portfolio Cash Exposure to Macro Factors\")\n", " .format('${:,.0f}')\n", " .applymap(lambda v: \"color:red;\" if v < 0 else None)\n", " .set_table_styles(styles)\n", ")\n", "styles.pop(0)\n", "total_styler = (\n", " total.style.format('${:,.0f}').applymap(lambda v: \"color:red;\" if v < 0 else None).set_table_styles(styles)\n", ")\n", "display(exposure_styler)\n", "display(total_styler)\n", "\n", "# Plot the portfolio percent change if every factor category shifted 1 std\n", "fig, ax = plt.subplots(constrained_layout=True)\n", "sns.barplot(data=total.sort_values(by=[\"Total\"], ascending=False).reset_index(), x=\"Factor\", y='Total', ax=ax)\n", "ax.set_xlabel(\"Macro Factor\")\n", "ax.set_ylabel(\"Portfolio Notional Change for 1 std Move in the Macro Factor\", fontsize='medium')\n", "ax.set_title(\"Portfolio Exposure to Macro Factors\")\n", "ax.tick_params(axis='x', labelrotation=60)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "\n", "## Step 5: Run empirical stress tests to observe impact on portfolio\n", "\n", "We can bump up or down any combination of macro factors and see the impact on the portfolio. Since your portfolio's\n", "sensitivities to each macro factor are independent of each other, we can isolate a (or a combination of) macro factor categories and factors,\n", "and assign a shift up or down in standard deviation and observe the change in the portfolio notional value.\n", "\n", "Below, we define a class `MacroScenario` that takes in a date and a dictionary of macro factor categories or macro factors and their suggested standard\n", "deviation shifts." ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "class MacroScenario:\n", " def __init__(self, date: dt.date, factor_std_shifts: Dict[Factor, float]):\n", " self.date = date\n", " self.factor_std_shifts = factor_std_shifts\n", "\n", "\n", "def macro_stress_test(\n", " exposure_df: pd.DataFrame, scenario: MacroScenario, is_factor_category_stress: bool\n", ") -> pd.DataFrame:\n", " \"\"\"\n", " For a given portfolio, find how shifts in standard deviation in the selected macro factors affects\n", " the portfolio's notional value.\n", " :param exposure_df: exposure of the portfolio to the requested macro factors/factor categories\n", " :param scenario: scenario to stress test for. Contains date and a dict of factors and respective shifts in standard\n", " deviation\n", " :is_factor_category_stress: whether we are stress testing for shifts in factor categories or shifts in factors.\n", " :return: portfolio notional change and change in portfolio exposure for each macro factor\n", " \"\"\"\n", "\n", " factors_and_std = scenario.factor_std_shifts\n", " factors_to_shift = {factor.name: factor.category for factor in factors_and_std.keys()}\n", " total_row_name = \"Total Factor Category Exposure\" if is_factor_category_stress else \"Total Factor Exposure\"\n", " notional_column_name = \"Notional\" if is_factor_category_stress else (\"Asset Information\", \"Notional\")\n", " portfolio_notional = exposure_df.loc[total_row_name, notional_column_name]\n", " exposure_df = (\n", " exposure_df[list(factors_to_shift.keys())]\n", " if is_factor_category_stress\n", " else exposure_df[list(zip(factors_to_shift.values(), factors_to_shift.keys()))]\n", " )\n", "\n", " fact_name = 'Factor Category' if is_factor_category_stress else 'Factor'\n", " col_1_name = f\"Exposure to {fact_name} Before Stress Test\"\n", " col_2_name = f\"Exposure to {fact_name} After Stress Test\"\n", " portfolio_change_df = pd.DataFrame(\n", " np.zeros((4, len(set(exposure_df.columns.tolist())))),\n", " index=[\"Standard Deviation Shift\", col_1_name, col_2_name, \"Percent Change After Stress Test\"],\n", " columns=exposure_df.columns.tolist(),\n", " )\n", "\n", " for factor, std_shift in factors_and_std.items():\n", " factor_name = factor.name if is_factor_category_stress else (factors_to_shift[factor.name], factor.name)\n", " old_exposure = exposure_df.loc[total_row_name, factor_name]\n", " new_exposure = old_exposure * std_shift\n", " portfolio_change_df.loc[\"Standard Deviation Shift\", factor_name] = std_shift\n", " portfolio_change_df.loc[col_1_name, factor_name] = old_exposure\n", " portfolio_change_df.loc[col_2_name, factor_name] = new_exposure\n", " portfolio_change_df.loc[\"Percent Change After Stress Test\", factor_name] = (\n", " new_exposure / portfolio_notional\n", " ) * 100\n", "\n", " total_name = \"Total\" if is_factor_category_stress else (\"Total\", \"Total\")\n", " total_sum = portfolio_change_df.agg(np.sum, axis=\"columns\").to_frame().rename(columns={0: total_name})\n", "\n", " portfolio_change_df = pd.concat([portfolio_change_df, total_sum], axis='columns')\n", " portfolio_change_df.loc[\"Standard Deviation Shift\", total_name] = np.nan\n", "\n", " return portfolio_change_df" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, "source": [ "### Factor Standard Deviation\n", "\n", "Before the stress test, we can first determine, for each factor, the value of one shift in standard deviation in absolute terms. We can leverage the function `get_factor_standard_deviation` of the `MacroRiskModel` class." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "from gs_quant.models.risk_model import MacroRiskModel\n", "import datetime as dt\n", "import pandas as pd\n", "\n", "start_date = dt.date(2025, 2, 4)\n", "end_date = dt.date(2025, 2, 5)\n", "\n", "date = model.get_dates(start_date, end_date)[-1]\n", "\n", "model_id = \"RISK MODEL ID\" # Such as \"QI_US_EQUITY_LT\"\n", "model = MacroRiskModel.get(model_id)\n", "\n", "factor_category_shift_std = {\"Inflation\": 3, \"Real Rates\": 3, \"Economic Growth\": -3}\n", "\n", "factor_data = model.get_factor_data(date, date)\n", "factor_data = factor_data.loc[factor_data['factorCategory'].isin(list(factor_category_shift_std.keys())), :]\n", "factor_data = factor_data.set_index(\"name\")\n", "\n", "factor_std_df = model.get_factor_standard_deviation(start_date=date, end_date=date, factors=factor_data.index.tolist())\n", "\n", "factor_std_df = (\n", " factor_std_df.set_axis(\n", " pd.MultiIndex.from_tuples([(factor_data.loc[f, 'factorCategory'], f) for f in factor_std_df.columns.values]),\n", " axis=1,\n", " )\n", " .rename_axis((\"Factor Category\", \"Factor\"), axis=1)\n", " .sort_index(level=0, axis=1)\n", " .round(3)\n", " .T.rename(columns={date.isoformat(): \"Value of 1 STD\"})\n", ")\n", "\n", "display(\n", " (\n", " factor_std_df.style.set_caption(f\"One Standard Deviation of Factors on {date.isoformat()}\")\n", " .format(\"{:.3f}\", na_rep=\"N/A\")\n", " .background_gradient()\n", " .set_table_styles(\n", " [dict(selector=\"caption\", props=[(\"font-size\", \"120%\"), (\"font-weight\", \"bold\"), (\"color\", 'black')])]\n", " )\n", " )\n", ")" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Stress Test Shifts in Factor Categories\n", "\n", "We can stress test for shifts in factor categories and observe the impact on our portfolio. \n", "\n", "Here are the user inputs:\n", "* `model_id`: The model we are using\n", "* `portfolio_id`: Portfolio we are stress testing for.\n", "* `factors_shifts_std`: Map of factor categories and their standard deviation scenario shift" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "date = dt.date(2025, 4, 23)\n", "\n", "model_id = \"RISK MODEL ID\"\n", "model = MacroRiskModel.get(model_id)\n", "\n", "portfolio_id = \"YOUR PORTFOLIO ID\"\n", "pm = PortfolioManager(portfolio_id)\n", "\n", "factor_category_shift_std = {\"Inflation\": 0.5, \"Real Rates\": 3, \"Economic Growth\": -2}\n", "many_factor_categories = model.get_many_factors(\n", " start_date=date, end_date=date, factor_names=list(factor_category_shift_std.keys()), factor_type=FactorType.Category\n", ")\n", "std_dict = {factor: factor_category_shift_std[factor.name] for factor in many_factor_categories}\n", "\n", "# Create a MacroScenario object\n", "scenario = MacroScenario(date, std_dict)\n", "\n", "# First, get the portfolio factor category exposure\n", "exposure_df = pm.get_macro_exposure(\n", " model=model, date=scenario.date, factor_type=FactorType.Category, factor_categories=many_factor_categories\n", ")\n", "\n", "# Then calculate portfolio change given std shifts in the factor categories in `scenario`\n", "portfolio_change = macro_stress_test(exposure_df, scenario, is_factor_category_stress=True).T.rename_axis(\n", " \"Factor Category\"\n", ")\n", "\n", "# Display\n", "styles = [dict(selector=\"caption\", props=[(\"font-size\", \"120%\"), (\"font-weight\", \"bold\"), (\"color\", 'black')])]\n", "display(\n", " (\n", " portfolio_change.style.set_caption(\"Stress Test: Shifts in Factor Categories and their Impact on Portfolio\")\n", " .format(\n", " {\n", " \"Percent Change After Stress Test\": \"{:.3f}%\",\n", " \"Exposure to Factor Category Before Stress Test\": \"${:,.0f}\",\n", " \"Exposure to Factor Category After Stress Test\": \"${:,.0f}\",\n", " \"Standard Deviation Shift\": \"{:.2f}\",\n", " },\n", " na_rep=\"N/A\",\n", " )\n", " .background_gradient()\n", " .set_table_styles(styles)\n", " )\n", ")" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Stress Test Shifts in Factors\n", "\n", "We can also stress test for shifts in individual factors and observe the impact on our portfolio. \n", "\n", "Here are the user inputs:\n", "* `model_id`: The model we are using\n", "* `portfolio_id`: Portfolio we are stress testing for.\n", "* `factors_shifts_std`: Map of factors and their standard deviation scenario shifts" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Get the model\n", "model_id = \"RISK MODEL ID\" # Such as \"QI_US_EQUITY_LT\"\n", "model = MacroRiskModel.get(model_id)\n", "\n", "# Input the portfolio id\n", "portfolio_id = \"YOUR PORTFOLIO ID\"\n", "pm = PortfolioManager(portfolio_id)\n", "date = dt.date(2025, 2, 4)\n", "\n", "factors_shift_std = {\n", " \"US 2Y Infl. Expec.\": 0.5,\n", " \"US 5Y Infl. Expec.\": 0.5,\n", " \"US 10Y Infl. Expec.\": 0.5,\n", " \"USD 10Y Real Rate\": 3.0,\n", " \"JPY 10Y Real Rate\": 3.0,\n", " \"EUR 10Y Real Rate\": 3.0,\n", "}\n", "\n", "many_factors = model.get_many_factors(\n", " start_date=date, end_date=date, factor_names=list(factors_shift_std.keys()), factor_type=FactorType.Factor\n", ")\n", "std_dict = {factor: factors_shift_std[factor.name] for factor in many_factors}\n", "\n", "# MacroScenario object\n", "scenario = MacroScenario(date, std_dict)\n", "\n", "# First, get the portfolio factor exposure\n", "exposure_df = pm.get_macro_exposure(model=model, date=scenario.date, factor_type=FactorType.Factor).sort_values(\n", " by=[\"Total Factor Exposure\"], axis=1, ascending=False\n", ")\n", "\n", "# Then calculate portfolio change given std shifts in the factors in `scenario`\n", "portfolio_change = macro_stress_test(exposure_df, scenario, is_factor_category_stress=False).T.rename_axis(\n", " [\"Factor Category\", \"Factor\"]\n", ")\n", "\n", "# Display\n", "styles = [dict(selector=\"caption\", props=[(\"font-size\", \"130%\"), (\"font-weight\", \"bold\"), (\"color\", 'black')])]\n", "display(\n", " (\n", " portfolio_change.style.set_caption(\"Stress Test: Shifts in Factors and their Impact on Portfolio\")\n", " .format(\n", " {\n", " \"Percent Change After Stress Test\": \"{:.3f}%\",\n", " \"Exposure to Factor Before Stress Test\": \"${:,.0f}\",\n", " \"Exposure to Factor After Stress Test\": \"${:,.0f}\",\n", " \"Standard Deviation Shift\": \"{:.2f}\",\n", " },\n", " na_rep=\"N/A\",\n", " )\n", " .background_gradient()\n", " .set_table_styles(styles)\n", " )\n", ")" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 6: Which assets in the portfolio are in a Macro Regime?\n", "\n", "In a macro regime, macro factors are the most significant in explaining the movement in asset price. To determine which\n", "assets are in a macro regime, we look at the R Squared value, the proportion of the movement in asset price that is explained\n", "by the macro factors. Assets with R Squared values above 65% are in a macro regime while values below 65% are driven\n", "primarily by micro and idiosyncratic risk.\n", "\n", "Once an asset is in a macro regime, we can look at the fair value gap to determine if it is rich or cheap.\n", "The fair value gap metric is the difference between the spot price of an asset and the asset model value. An asset is\n", " undervalued when its fair value gap is negative while it is overvalued when it is a positive value.\n", "\n", "Given a portfolio, we can find out how many assets are in a macro regime at a specific date. We can leverage the function\n", "`get_fair_value_gap` and `get_r_squared` to determine the regime of each asset in the portfolio as well as its fair value\n", "gap. Note that the fair value gap data is available both in absolute terms (in percentage) and in standard deviation terms.\n", "\n", "Here are the user inputs:\n", "* `PORTFOLIO ID`: Portfolio we are using. \n", "* `RISK MODEL ID`: The model we are using to get r_squared and fair value gap data." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Time range\n", "start_date = dt.date(2025, 2, 4)\n", "end_date = dt.date(2025, 4, 23)\n", "\n", "# Model\n", "model_id = \"QI_US_EQUITY_LT\" # Such as \"QI_US_EQUITY_LT\"\n", "model = MacroRiskModel.get(model_id)\n", "\n", "# Input the portfolio id\n", "portfolio_id = \"MPPP95AWA1E5V24D\"\n", "pm = PortfolioManager(portfolio_id)\n", "positions = pm.get_latest_position_set().positions\n", "\n", "universe = [position.identifier for position in positions]\n", "universe_for_request = DataAssetsRequest(RiskModelUniverseIdentifierRequest.bbid, universe)\n", "\n", "# Get fair value gap and r squared for the assets in the portfolio\n", "fvg_std_df = model.get_fair_value_gap(\n", " start_date=start_date, end_date=end_date, fair_value_gap_unit=Unit.STANDARD_DEVIATION, assets=universe_for_request\n", ")\n", "r_squared_df = model.get_r_squared(start_date=start_date, end_date=end_date, assets=universe_for_request)\n", "\n", "date = dt.date(2024, 4, 20).strftime(\"%Y-%m-%d\")\n", "fvg_one_date_df = fvg_std_df.loc[date, :].to_frame().rename(columns={date: \"Fair Value Gap\"})\n", "r_squared_one_date_df = r_squared_df.loc[date, :].to_frame().rename(columns={date: \"R Squared\"})\n", "fvg_rsq_df = r_squared_one_date_df.join(fvg_one_date_df).reset_index().round(2)\n", "\n", "# Get the Macro cheap assets\n", "macro_cheap_df = (\n", " fvg_rsq_df[(fvg_rsq_df[\"R Squared\"] > 65) & (fvg_rsq_df[\"Fair Value Gap\"] < 0)]\n", " .set_index(\"index\")\n", " .rename_axis(\"Asset\")\n", " .sort_values(by=\"Fair Value Gap\", ascending=True)\n", ")\n", "\n", "# Plot Quadrant of assets that are macro cheap/rich and micro\n", "fig, ax = plt.subplots(figsize=(20, 15), constrained_layout=True)\n", "sns.scatterplot(data=fvg_rsq_df, x=\"R Squared\", y='Fair Value Gap', hue='index', legend=False)\n", "ax.set_title(f\"Macro Rich vs Macro Cheap Assets on {date}\")\n", "ax.set_xlabel(\"R Squared (Model Confidence) in %\")\n", "ax.set_ylabel(\"Fair Value Gap (sigma)\")\n", "ax.set_xlim(0, 100)\n", "ax.set_ylim(-3, 3)\n", "\n", "\n", "def annotate_plot(data, ax, index):\n", " for i in range(data.shape[0]):\n", " if data[index][i] == 'DHR UN':\n", " ax.text(data[\"R Squared\"][i], data[\"Fair Value Gap\"][i], s=data[index][i], size='x-large', color='r')\n", " else:\n", " ax.text(data[\"R Squared\"][i], data[\"Fair Value Gap\"][i], s=data[index][i], size='large')\n", "\n", "\n", "annotate_plot(fvg_rsq_df, ax, \"index\")\n", "\n", "for pair, label in {\n", " (75, 2.2): \"Macro Rich\",\n", " (25, 2.2): \"Micro Regime\",\n", " (25, -2.2): \"Micro Regime\",\n", " (72, -2.2): \"Macro Cheap\",\n", "}.items():\n", " ax.text(x=pair[0], y=pair[1], s=label, alpha=0.7, fontsize='xx-large', color='g', fontweight='bold')\n", "ax.axhline(y=0, color='k', linestyle='dashed')\n", "ax.axvline(x=65, color='k', linestyle='dashed')\n", "\n", "# Plot Just macro cheap assets\n", "fig, ax = plt.subplots(figsize=(18, 7), constrained_layout=True)\n", "sns.scatterplot(data=macro_cheap_df.reset_index(), x=\"R Squared\", y=\"Fair Value Gap\", hue=\"Asset\", legend=False)\n", "annotate_plot(macro_cheap_df.reset_index(), ax, \"Asset\")\n", "ax.set_title(f\"Macro Cheap Assets on {end_date}\")\n", "ax.set_ylabel(\"Fair Value Gap\")\n", "ax.set_xlabel(\"R Squared (in %)\")\n", "ax.text(x=75, y=-2.0, s=\"Macro Cheap\", alpha=0.7, fontsize='xx-large', color='g', fontweight='bold')\n", "\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "The first plot above classifies assets into 3 groups: macro cheap, macro rich and assets in a micro regime. The second plot shows the bottom right of the first\n", "plot, i.e all the assets that are macro cheap. " ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "\n", "## Step 7: Asset Sensitivity to Factors\n", "\n", "The asset Sensitivity to a macro factor is the percentage change in the asset price for 1 standard deviation move\n", "in that macro factor. We can leverage the function `get_universe_sensitivity` of the `MacroRiskModel` class to get a\n", "time series of daily asset sensitivity to macro factors for a list of assets.\n", "\n", "Here are the parameters that are needed:\n", "* `start_date` and `end_date`: Time range for which to get sensitivity for. \n", "* `model_id`: The model we are using.\n", "* `asset`: Asset to get macro sensitivity for.\n", "\n", "Below, we are querying a time series of daily sensitivity data over the time range for one of the macro cheap assets in the portfolio. " ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "#### Asset Sensitivity to Factor Categories" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Time range\n", "start_date = dt.date(2025, 2, 4)\n", "end_date = dt.date(2025, 2, 5)\n", "\n", "# Model\n", "model_id = \"RISK MODEL ID\" # Such as \"QI_US_EQUITY_LT\"\n", "model = MacroRiskModel.get(model_id)\n", "\n", "# Universe\n", "asset = \"DHR UN\"\n", "universe = [asset]\n", "universe_for_request = DataAssetsRequest(UniverseIdentifier.bbid, universe)\n", "\n", "# Get asset sensitivity to factor categories\n", "factor_sens_df = model.get_universe_sensitivity(\n", " start_date=start_date,\n", " end_date=end_date,\n", " assets=universe_for_request,\n", " factor_type=FactorType.Category,\n", " get_factors_by_name=True,\n", ")\n", "factor_sens_df = (\n", " factor_sens_df.droplevel(1).sort_values(by=[asset], ascending=False, axis=1).rename_axis(\"Factor Category\", axis=1)\n", ")\n", "\n", "# Display the dataframe\n", "factor_sens_to_display = factor_sens_df.T.rename(\n", " columns={asset: f\"Percent Change in Asset Price for 1 STD shift in each Factor Category for {asset}\"}\n", ")\n", "display(\n", " (\n", " factor_sens_to_display.style.set_caption(f\"Sensitivity of {asset} to Factor Categories on {start_date}\")\n", " .format(\"{:.2f}%\")\n", " .applymap(lambda v: \"color:red;\" if v < 0 else None)\n", " .set_table_styles(\n", " [\n", " {'selector': \"caption\", 'props': [(\"font-size\", \"100%\"), (\"font-weight\", \"bold\"), (\"color\", 'black')]},\n", " {'selector': 'table', 'props': [('width', '100px')]},\n", " {\n", " 'selector': 'thead',\n", " 'props': [(\"background-color\", \"silver\"), (\"font-size\", \"12px\"), (\"width\", \"400px\")],\n", " },\n", " {\n", " 'selector': 'tr',\n", " 'props': [\n", " (\"background-color\", \"gainsboro\"),\n", " (\"font-size\", \"12px\"),\n", " (\"border\", \"solid\"),\n", " (\"border-width\", \"1px\"),\n", " ],\n", " },\n", " {'selector': 'td', 'props': [(\"background-color\", \"aliceblue\"), (\"font-size\", \"12px\")]},\n", " ]\n", " )\n", " )\n", ")\n", "\n", "# Plot the portfolio percent change if every factor category shifted 1 std\n", "fig, ax = plt.subplots(constrained_layout=True)\n", "sns.barplot(data=factor_sens_df.T.reset_index(), x=\"Factor Category\", y=asset, ax=ax, palette=exp_palette)\n", "ax.set_title(f\"Expected % change in {asset} Price for 1 std Move in Factor Category on {end_date}\")\n", "ax.set_xlabel(\"Factor Category\")\n", "ax.set_ylabel(\"\")\n", "ax.set_yticks([], [])\n", "ax.tick_params(axis='x', labelrotation=30)\n", "\n", "labls = factor_sens_df.T.reset_index()[\"Factor Category\"].values.tolist()\n", "values = factor_sens_df.T.reset_index()[asset].values.tolist()\n", "for i in range(len(labls)):\n", " if values[i] > 0:\n", " ax.text(i, values[i] + 0.01, s=f'{round(values[i], 1)}%', ha='center', fontsize='x-small')\n", " else:\n", " ax.text(i, values[i] - 0.08, s=f'{round(values[i], 1)}%', ha='center', fontsize='x-small')\n", "\n", "sns.despine(left=True)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "#### Asset Sensitivity to Factors\n", "\n", "Once we know an asset sensitivity to factor categories, we can also query its sensitivity to individual macro factors within a factor category.\n", "\n", "Here are the user inputs:\n", "* `model`: The model we are using.\n", "* `factor_categories`: A list of factor categories whose factors we want sensitivity for. If empty, it will default to returning sensitivity to the top 2 positive and negative macro drivers\n", "* `start_date` and `end_date`: Time range to get sensitivity data for an asset" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Time range\n", "start_date = dt.date(2025, 2, 4)\n", "end_date = dt.date(2025, 2, 5)\n", "\n", "# Model\n", "model_id = \"RISK MODEL ID\" # Such as \"QI_US_EQUITY_LT\"\n", "model = MacroRiskModel.get(model_id)\n", "\n", "# Select factor_categories\n", "factor_categories = []\n", "\n", "# Universe\n", "asset = \"DHR UN\"\n", "universe = [asset]\n", "universe_for_request = DataAssetsRequest(UniverseIdentifier.bbid, universe)\n", "factor_data = model.get_factor_data(start_date=start_date, end_date=end_date).set_index('name')\n", "\n", "# Get asset sensitivity to factors as a dataframe\n", "factor_sens_df = model.get_universe_sensitivity(\n", " start_date=start_date, end_date=end_date, assets=universe_for_request, get_factors_by_name=True\n", ")\n", "factor_sens_df = factor_sens_df.set_axis(\n", " pd.MultiIndex.from_tuples([(factor_data.loc[x, 'factorCategory'], x) for x in factor_sens_df.columns]), axis=1\n", ").droplevel(1)\n", "\n", "if not factor_categories:\n", " # Get top drivers\n", " top_drivers = (\n", " model.get_universe_sensitivity(\n", " start_date=start_date,\n", " end_date=end_date,\n", " assets=universe_for_request,\n", " factor_type=FactorType.Category,\n", " get_factors_by_name=True,\n", " )\n", " .droplevel(1)\n", " .sort_values(by=asset, axis=1, ascending=False)\n", " .columns.values.tolist()\n", " )\n", " factor_categories = top_drivers[0:2] + top_drivers[-2:None]\n", "\n", "factor_sens_df = (\n", " factor_sens_df[factor_categories]\n", " .T.rename_axis([\"Factor Category\", \"Factor\"])\n", " .sort_values(by=asset, ascending=False)\n", " .rename(columns={asset: f\"Percent Change in Asset Price for 1 STD shift in each Factor for {asset}\"})\n", ")\n", "\n", "# Display the dataframe\n", "display(\n", " (\n", " factor_sens_df.style.set_caption(f\"Sensitivity of {asset} to Factors on {start_date}\")\n", " .format(\"{:.2f}%\")\n", " .applymap(lambda v: \"color:red;\" if v < 0 else None)\n", " .set_table_styles(\n", " [\n", " {'selector': \"caption\", 'props': [(\"font-size\", \"100%\"), (\"font-weight\", \"bold\"), (\"color\", 'black')]},\n", " {'selector': 'table', 'props': [('width', '100px')]},\n", " {\n", " 'selector': 'thead',\n", " 'props': [(\"background-color\", \"silver\"), (\"font-size\", \"12px\"), (\"width\", \"400px\")],\n", " },\n", " {\n", " 'selector': 'tr',\n", " 'props': [\n", " (\"background-color\", \"gainsboro\"),\n", " (\"font-size\", \"12px\"),\n", " (\"border\", \"solid\"),\n", " (\"border-width\", \"1px\"),\n", " ],\n", " },\n", " {'selector': 'td', 'props': [(\"background-color\", \"aliceblue\"), (\"font-size\", \"12px\")]},\n", " ]\n", " )\n", " )\n", ")\n", "\n", "# Plot the portfolio percent change if every factor category shifted 1 std\n", "fig, ax = plt.subplots(constrained_layout=True)\n", "sns.barplot(\n", " data=factor_sens_df.reset_index(),\n", " x=\"Factor\",\n", " y=f\"Percent Change in Asset Price for 1 STD shift in each Factor for {asset}\",\n", " ax=ax,\n", " palette=exp_palette,\n", ")\n", "ax.set_title(f\"Expected % change in {asset} Price for 1 std Move in Factor Category on {end_date}\")\n", "ax.set_xlabel(\"Macro Factor\")\n", "ax.set_ylabel(\"\")\n", "ax.set_yticks([], [])\n", "ax.tick_params(axis='x', labelrotation=60)\n", "\n", "labls = factor_sens_df.reset_index()[\"Factor\"].values.tolist()\n", "values = factor_sens_df.reset_index()[\n", " f\"Percent Change in Asset Price for 1 STD shift in each Factor for {asset}\"\n", "].values.tolist()\n", "for i in range(len(labls)):\n", " if values[i] > 0:\n", " ax.text(i, values[i] + 0.01, s=f'{round(values[i], 1)}%', ha='center', fontsize='x-small')\n", " else:\n", " ax.text(i, values[i] - 0.03, s=f'{round(values[i], 1)}%', ha='center', fontsize='x-small')\n", "\n", "sns.despine(left=True)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, "source": [ "### Historical Asset Sensitivity to Factor Categories\n", "\n", "We can query asset sensitivity to factor categories over time. To get sensitivity to factor categories, make sure that the factor_type parameter in the function `get_universe_sensitivity` is set to `FactorType.Category`.\n", "\n", "If the factor_categories parameter is not specified, the top drivers of the asset will be shown instead.\n", "\n", "On the plot, she shaded area represents the time range when the asset was in a macro regime." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Time range\n", "start_date = dt.date(2025, 2, 4)\n", "end_date = dt.date(2025, 2, 5)\n", "\n", "# Model\n", "model_id = \"RISK MODEL ID\" # Such as \"QI_US_EQUITY_LT\"\n", "model = MacroRiskModel.get(model_id)\n", "\n", "# Asset and factor categories\n", "asset = \"DHR UN\"\n", "factor_categories = []\n", "\n", "universe = [asset]\n", "universe_for_request = DataAssetsRequest(UniverseIdentifier.bbid, universe)\n", "\n", "# Get asset sensitivity to factor category (note that factor_type is set to `Category`)\n", "factor_category_sens_df = model.get_universe_sensitivity(\n", " start_date=start_date,\n", " end_date=end_date,\n", " assets=universe_for_request,\n", " factor_type=FactorType.Category,\n", " get_factors_by_name=True,\n", ")\n", "\n", "# If no factor categories passed in, get the top drivers as of end_date\n", "if not factor_categories:\n", " top_drivers = (\n", " factor_category_sens_df.loc[(asset, end_date.strftime(\"%Y-%m-%d\")), :]\n", " .to_frame()\n", " .droplevel(1, axis=1)\n", " .sort_values(by=asset, ascending=False)\n", " .index.values.tolist()\n", " )\n", " factor_categories = top_drivers[0:2] + top_drivers[-2:None]\n", "factor_category_sens_df = factor_category_sens_df[factor_categories].droplevel(0)\n", "\n", "# Get date range when `asset` was in a macro regime\n", "r_squared_historical = (\n", " model.get_r_squared(\n", " start_date=start_date, end_date=end_date, assets=DataAssetsRequest(UniverseIdentifier.bbid, [asset])\n", " )\n", " .reset_index()\n", " .rename(columns={\"index\": \"Date\"})\n", ")\n", "macro_regime_dates = r_squared_historical.loc[r_squared_historical[asset] > 65].index.tolist()\n", "temp_list = cycle(macro_regime_dates)\n", "next(temp_list)\n", "groups = groupby(macro_regime_dates, key=lambda j: j + 1 == next(temp_list))\n", "macro_regime_date_range = [list(v) + [next(next(groups)[1])] for k, v in groups if k]\n", "\n", "fig, ax = plt.subplots(constrained_layout=True)\n", "factor_category_sens_df.reset_index().rename(columns={\"index\": \"Date\"}).plot(ax=ax, x=\"Date\")\n", "ax.set_title(f\"Sensitivity of {universe[0]} to Factor Categories on {end_date} Over Time\")\n", "ax.set_ylabel(\"Percentage change in asset price for 1 std shift\")\n", "ax.axhline(y=0, linestyle='dashed')\n", "for date_range in macro_regime_date_range:\n", " ax.axvspan(date_range[0], date_range[-1], color='aliceblue')\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, "source": [ "### Historical Asset Sensitivity to Factors\n", "\n", "We can query asset sensitivity to indiviual factors within a factor category over time. The `factor_type` parameter in the function `get_universe_sensitivity` is by default set to `FactorType.Factor` to specify that we want exposure to individual factors.\n", "\n", "If the `factors` parameter is not specified, the factors to which the asset has the most exposure will be returned. On the plot, the shaded area represents a time period when the asset was in a macro regime." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Time range\n", "start_date = dt.date(2025, 2, 4)\n", "end_date = dt.date(2025, 2, 5)\n", "\n", "# Model\n", "model_id = \"RISK MODEL ID\" # Such as \"QI_US_EQUITY_LT\"\n", "model = MacroRiskModel.get(model_id)\n", "\n", "# Asset and factors\n", "asset = \"DHR UN\"\n", "factors = []\n", "\n", "universe = [asset]\n", "universe_for_request = DataAssetsRequest(UniverseIdentifier.bbid, universe)\n", "\n", "# Get asset factor sensitivity data\n", "factor_sens_df = model.get_universe_sensitivity(\n", " start_date=start_date, end_date=end_date, assets=universe_for_request, get_factors_by_name=True\n", ")\n", "\n", "# If no factors passed, get the factors with the highest positive/negative sensitivity\n", "if not factors:\n", " top_drivers = (\n", " factor_sens_df.loc[(asset, end_date.strftime(\"%Y-%m-%d\")), :]\n", " .to_frame()\n", " .droplevel(1, axis=1)\n", " .sort_values(by=asset, ascending=False)\n", " .index.values.tolist()\n", " )\n", " factors = top_drivers[0:2] + top_drivers[-2:None]\n", "\n", "factor_sens_df = factor_sens_df[factors].droplevel(0)\n", "\n", "# Get date range where this asset was in a macro regime\n", "r_squared_historical = (\n", " model.get_r_squared(\n", " start_date=start_date, end_date=end_date, assets=DataAssetsRequest(UniverseIdentifier.bbid, [asset])\n", " )\n", " .reset_index()\n", " .rename(columns={\"index\": \"Date\"})\n", ")\n", "macro_regime_dates = r_squared_historical.loc[r_squared_historical[asset] > 65].index.tolist()\n", "temp_list = cycle(macro_regime_dates)\n", "next(temp_list)\n", "groups = groupby(macro_regime_dates, key=lambda j: j + 1 == next(temp_list))\n", "macro_regime_date_range = [list(v) + [next(next(groups)[1])] for k, v in groups if k]\n", "\n", "fig, ax = plt.subplots(constrained_layout=True)\n", "factor_sens_df.reset_index().rename(columns={\"index\": \"Date\"}).plot(ax=ax, x='Date')\n", "ax.set_title(f\"Sensitivity of {universe[0]} to Macro Factors on {end_date} Over Time\")\n", "ax.set_ylabel(\"Percentage change in asset price for 1 std shift\")\n", "ax.axhline(y=0, linestyle='dashed')\n", "for date_range in macro_regime_date_range:\n", " ax.axvspan(date_range[0], date_range[-1], color='aliceblue')\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Regime Shift over Time\n", "\n", "In a time range, we can pinpoint points in time when macro factors have primarily explained the variance in asset price over a period of time for some assets in our portfolio. \n", "In other words, we can observe when R Squared for an asset was above 65% and when it was below 65%. \n", "\n", "Here are the user inputs:\n", "* `model_id`: The model we are using.\n", "* `universe`: A list of assets to get historical R Squared data for.\n", "* `start_date` and `end_date`: Time range to get R Squared data" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Time range\n", "start_date = dt.date(2025, 2, 4)\n", "end_date = dt.date(2025, 2, 5)\n", "\n", "# Model\n", "model_id = \"RISK MODEL ID\" # Such as \"QI_US_EQUITY_LT\"\n", "model = MacroRiskModel.get(model_id)\n", "\n", "universe = [\"DHR UN\", \"AMZN UN\"]\n", "universe_for_request = DataRequest(RiskModelUniverseIdentifierRequest.bbid, universe)\n", "\n", "r_squared_df = model.get_r_squared(start_date=start_date, end_date=end_date, assets=universe_for_request)\n", "\n", "fig, ax = plt.subplots(constrained_layout=True)\n", "r_squared_df.plot(ax=ax)\n", "ax.set_title(f\"Regime Shift from {start_date} to {end_date}\")\n", "ax.set_ylabel(\"RSquared (in %)\")\n", "ax.axhline(y=65, linestyle='dashed')\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, "source": [ "### Historical Fair Value Gap\n", "\n", "The fair value gap is the difference between the model value and asset price. Note that a negative fair value gap (model value < asset price) means that the asset is rich, while a model value > asset price means that the asset is cheap." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Time range\n", "start_date = dt.date(2025, 2, 4)\n", "end_date = dt.date(2025, 2, 5)\n", "\n", "# Model\n", "model_id = \"RISK MODEL ID\" # Such as \"QI_US_EQUITY_LT\"\n", "model = MacroRiskModel.get(model_id)\n", "\n", "asset_name = \"DHR UN\"\n", "universe = [asset_name]\n", "universe_for_request = DataRequest(RiskModelUniverseIdentifierRequest.bbid, universe)\n", "\n", "fair_value_gap_std_df = model.get_fair_value_gap(\n", " start_date=start_date, end_date=end_date, fair_value_gap_unit=Unit.STANDARD_DEVIATION, assets=universe_for_request\n", ")\n", "\n", "# Get date range where this asset was in a macro regime\n", "r_squared_historical = (\n", " model.get_r_squared(\n", " start_date=start_date, end_date=end_date, assets=DataAssetsRequest(UniverseIdentifier.bbid, universe)\n", " )\n", " .reset_index()\n", " .rename(columns={\"index\": \"Date\"})\n", ")\n", "macro_regime_dates = r_squared_historical.loc[r_squared_historical[asset_name] > 65].index.tolist()\n", "temp_list = cycle(macro_regime_dates)\n", "next(temp_list)\n", "groups = groupby(macro_regime_dates, key=lambda j: j + 1 == next(temp_list))\n", "macro_regime_date_range = [list(v) + [next(next(groups)[1])] for k, v in groups if k]\n", "\n", "fig, ax = plt.subplots(figsize=(15, 7), dpi=150, constrained_layout=True)\n", "fair_value_gap_std_df.reset_index().rename(columns={\"index\": \"Date\"}).iloc[:, 0:4].plot(ax=ax, x='Date')\n", "ax.set_title(f\"Fair Value Gap from {start_date} to {end_date}\", fontsize='x-large', fontweight='bold')\n", "ax.set_ylabel(\"Fair Value Gap (sigma)\", fontsize='large')\n", "ax.axhline(y=0, linestyle='dashed')\n", "\n", "for date_range in macro_regime_date_range:\n", " ax.axvspan(date_range[0], date_range[-1], color='aliceblue')\n", "plt.show()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/11_macro_models/02_Upload_Macro_Models.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "# Macro Models\n", "\n", "The GS Quant `MacroRiskModel` class gives users the power to upload their own risk models to Marquee for seamless integration with the Marquee Portfolio Analytics and Plot Tool Pro suite. After uploading a custom `MacroRiskModel`, users can access their Macro model data programmatically using GS Quant." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 1: Authenticate and Initialize Your Session\n", "\n", "First you will import the necessary modules and add your client id and client secret." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession, Environment\n", "\n", "from gs_quant.models.risk_model import MacroRiskModel, RiskModelCalendar, Term, CoverageType, UniverseIdentifier\n", "\n", "client = None\n", "secret = None\n", "\n", "## External users must fill in their client ID and secret below and comment out the line below\n", "\n", "# client = 'YOUR CLIENT ID'\n", "# secret = 'YOUR SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret)\n", "\n", "print('GS Session initialized.')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 2: Create a Macro Model\n", "\n", "Input fields to create the initial Macro Risk Model object\n", "\n", "| Attribute |Can be Modified |Description\n", "|-----------------|-------------------|-------------\n", "| id | No |Model id|\n", "| name | Yes |Name of model|\n", "| description | Yes |Longer description of model|\n", "| term | Yes |Term or horizon of model. One of: Long, Medium, Short|\n", "| coverage | Yes |Geographical coverage of assets within model universe. One of: Global, Region, Region Excluding Countries, Country|\n", "| vendor | Yes |Who creates the model|\n", "| version | Yes |Version of model|\n", "| identifier | No |Identifier used to upload the model's asset universe. One of: sedol, cusip, bcid, gsid|\n", "| entitlements | Yes |Who can manage, edit, and view the risk model|\n" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "model_id = 'MY_MODEL'\n", "model_name = 'My Risk Model'\n", "description = 'My Custom Macro Risk Model'\n", "term = Term.Medium\n", "coverage = CoverageType.Country\n", "universe_identifier = UniverseIdentifier.sedol\n", "vendor = 'Goldman Sachs'" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "model = MacroRiskModel(\n", " id_=model_id,\n", " name=model_name,\n", " description=description,\n", " coverage=coverage,\n", " term=term,\n", " universe_identifier=universe_identifier,\n", " vendor=vendor,\n", " version=1,\n", ")\n", "\n", "model.save()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 3: Upload a Calendar To Your Model\n", "The calendar associated with the Macro Risk Model contains the dates which the risk model should have posted data on to be considered \"complete.\" The calendar can go further back as well as forward in time than the data that is currently posted for the calendar, but there cannot be any gaps in the data posted to the risk model according to the calendar." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "calendar = RiskModelCalendar(\n", " [\n", " '2021-01-29',\n", " '2021-01-28',\n", " '2021-01-27',\n", " '2021-01-26',\n", " '2021-01-25',\n", " '2021-01-22',\n", " '2021-01-21',\n", " '2021-01-20',\n", " '2021-01-19',\n", " '2021-01-18',\n", " '2021-01-15',\n", " '2021-01-14',\n", " '2021-01-13',\n", " '2021-01-12',\n", " '2021-01-11',\n", " '2021-01-08',\n", " '2021-01-07',\n", " '2021-01-06',\n", " '2021-01-05',\n", " '2021-01-04',\n", " '2021-01-01',\n", " ]\n", ")\n", "\n", "model.upload_calendar(calendar)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 4: Upload Data To Your Model\n", "\n", "Once the calendar is posted for a model, we can start uploading data to it. We can supply data multiple ways:\n", "\n", "1. Upload total data one day at a time\n", "2. Upload partial data one day at a time\n", "\n", "For a complete day of data, we need three things, defined in `RiskModelData`\n", "1. Factor Data\n", " - factorId: Can be any string, but needs to map consistently to the same factor across every date\n", " - factorName: Can be any string, will be the display name of the factor, should be consistent across every date\n", " - factorCategoryId: Id of the category that the factor belongs to\n", " - factorCategory: Name of the category that the factor belongs to, will be the display name of the category (Style, Industry, Market, Currency, ect.)\n", " - factorReturn: Daily return of the factor in percent units\n", "2. Asset Data\n", " - universe: Array of assets in the universe\n", " - factorExposure: Array of dictionaries that map factorId to the factor exposure of that asset, corresponds to ordering of asset universe\n", " - specificRisk: Array of annualized specific risk in percent units, corresponds to ordering of asset universe (null values not allowed)\n", " - totalRisk: (optional) Array of total risk in percent units, corresponds to ordering of asset universe (null values not allowed)\n", " - historicalBeta: (optional) Array of historical beta, corresponds to ordering of asset universe (null values not allowed)\n", "\n", "### Step 5: Upload Full Data" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "data = {\n", " 'date': '2021-01-13', # Note: You can only upload to dates in your risk model's calendar\n", " 'assetData': {\n", " 'universe': ['B02V2Q0', '6560713', 'B3Q15X5', '0709954'],\n", " 'specificRisk': [12.09, 45.12, 3.09, 1.0],\n", " 'factorExposure': [\n", " {'1': 0.23, '2': 0.023},\n", " {'1': 0.023, '2': 2.09, '3': 0.3},\n", " {'1': 0.063, '2': 2.069, '3': 0.73},\n", " {'2': 0.067, '3': 0.93},\n", " ],\n", " 'totalRisk': [12.7, 45.5, 12.7, 10.3],\n", " },\n", " 'factorData': [\n", " {\n", " 'factorId': '1',\n", " 'factorName': 'USD',\n", " 'factorCategory': 'Currency',\n", " 'factorCategoryId': 'CUR',\n", " 'factorReturn': 0.5,\n", " },\n", " {\n", " 'factorId': '2',\n", " 'factorName': 'JPY 1Y Basis Swap',\n", " 'factorCategory': 'GDP',\n", " 'factorCategoryId': 'GDP',\n", " 'factorReturn': 0.3,\n", " },\n", " {\n", " 'factorId': '3',\n", " 'factorName': 'US HY',\n", " 'factorCategory': 'Credit Spreads',\n", " 'factorCategoryId': 'CDS',\n", " 'factorReturn': 0.2,\n", " },\n", " ],\n", "}\n", "\n", "model.upload_data(data)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## Step 6: Query Data From Model\n", "\n", "Once the data is uploaded, you can query it back using the same class" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "from gs_quant.models.risk_model import Measure, DataAssetsRequest\n", "import datetime as dt\n", "\n", "model = MacroRiskModel.get(model_id)\n", "# get multiple measures across a date range for a universe specified\n", "start_date = dt.date(2021, 1, 13)\n", "end_date = dt.date(2021, 1, 13)\n", "\n", "universe_for_request = DataAssetsRequest(\n", " universe_identifier.value, []\n", ") # an empty assets request returns the full universe\n", "data_measures = [\n", " Measure.Universe_Factor_Exposure,\n", " Measure.Asset_Universe,\n", " Measure.Specific_Risk,\n", " Measure.Total_Risk,\n", " Measure.Factor_Id,\n", " Measure.Factor_Name,\n", " Measure.Factor_Category,\n", " Measure.Factor_Category_Id,\n", " Measure.Factor_Return,\n", "]\n", "\n", "macro_factor_data = model.get_data(data_measures, start_date, end_date, universe_for_request, limit_factors=True)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/12_scenarios/01_Custom_Factor_Shocks.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": true, "pycharm": { "name": "#%% md\n" } }, "source": [ "# What is a Scenario?\n", "\n", "A scenario is a set of parameters that define a market condition. Those parameters drive the factor returns that define\n", "that scenario, and we can then use those factor returns, along with the current factor exposures of a portfolio,\n", "to calculate the theoretical factor performance.\n", "\n", "What Types of Scenarios Exist?\n", "\n", "## Custom Factor Shocks\n", "\n", "This type of scenario shocks explicitly defined risk model factors up or down a certain percent. These scenarios must be risk-model specific and have two variations:\n", "\n", "1. **No Propagation**: This means the factors are shocked exactly as specified\n", "2. **Propagation**: all the factor shocks defined in the scenario are applied normally, but we also shock all other factors by a calculable amount as defined by the correlation between that factor and the factors that are shocked\n", "\n", "### How do I create a custom factor shock scenario?\n", "\n", "Let's create a custom factor shock scenario in which the Value factor in the global Axioma risk model rises by 5%,\n", "the Growth factor moves down by -5%, and the Medium-Term Momentum drops by -2%." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 1: Initialize a GsSession\n", "\n", "The initial step is to import all required modules and initialize a GsSession with your application ID and secret." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "from gs_quant.session import GsSession, Environment\n", "from gs_quant.markets.scenario import (\n", " FactorScenario,\n", " FactorShockParameters,\n", " FactorShock,\n", " FactorScenarioType,\n", ")\n", "from gs_quant.entities.entity import ScenarioCalculationMeasure\n", "from gs_quant.markets.portfolio_manager import PortfolioManager\n", "from gs_quant.entities.entitlements import Entitlements, EntitlementBlock, User\n", "import datetime as dt\n", "\n", "client = None\n", "secret = None\n", "\n", "## External users must fill in their client ID and secret below and comment out the lines below\n", "\n", "# client = 'ENTER CLIENT ID'\n", "# secret = 'ENTER CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret)\n", "\n", "print('GS Session initialized.')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, "source": [ "## Step 2: Define custom factor shocks parameters\n", "\n", "Next, we define the set of parameters that encapsulate the key components of our custom factor shock scenario:\n", " - A valid risk model ID (see [full list of available risk models here](https://marquee.gs.com/s/discover/data-services/catalog?Category=Factor+Risk+Model)).\n", " - A set of factors and their corresponding shocks in %, defined as a list of `FactorShock` object.\n", " - Whether these shocks are propagated according to the correlation of factors in the risk model\n", "\n", "These three components will be encapsulated in a `FactorShockParameters` object. You make take a quick look at [historical factor returns](https://developer.gs.com/p/docs/services/risk/factor-models/get-factor-model-data/#:~:text=Get%20All%20Factor%20Returns) to guide you in\n", "determining factor shocks. Below, we are defining two sets of factor shock parameters with similar attributes\n", "except for the `propagate_shocks` component." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "risk_model_id = \"RISK MODEL ID\"\n", "factor_1 = \"FACTOR 1\" # E.g. Value\n", "factor_2 = \"FACTOR 2\" # E.g. Growth\n", "factor_3 = \"FACTOR 3\" # E.g. Medium-Term Momentum\n", "\n", "factor_shocks = [\n", " FactorShock(factor=factor_1, shock=5),\n", " FactorShock(factor=factor_2, shock=-5),\n", " FactorShock(factor=factor_3, shock=-2),\n", "]\n", "\n", "\n", "factor_shock_parameters_no_propagation = FactorShockParameters(\n", " factor_shocks=factor_shocks, risk_model=risk_model_id, propagate_shocks=False\n", ")\n", "\n", "factor_shock_parameters_with_propagation = FactorShockParameters(\n", " factor_shocks=factor_shocks, risk_model=risk_model_id, propagate_shocks=True\n", ")" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, "source": [ "## Step 3: Set Scenario entitlements\n", "\n", "By default, an application will have all entitlement permissions to a scenario it creates. If you would like\n", "to share the scenario with other Marquee users at your firm or other applications, you will need to specify them in the\n", " `entitlements` attribute of the scenario. Let's walk through how we convert a list of admin, edit and viewer emails\n", " into an `Entitlements` object:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "admin_emails = [\"ADMIN EMAILS\"]\n", "edit_emails = [\"EDIT EMAILS\"]\n", "view_emails = [\"VIEW EMAILS\"]\n", "\n", "admin_entitlements = EntitlementBlock(users=User.get_many(emails=admin_emails))\n", "edit_entitlements = EntitlementBlock(users=User.get_many(emails=edit_emails))\n", "view_entitlements = EntitlementBlock(users=User.get_many(emails=view_emails))\n", "\n", "scenario_entitlements = Entitlements(view=view_entitlements, edit=edit_entitlements, admin=admin_entitlements)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, "source": [ "## Step 4: Create a custom factor shock scenario\n", "\n", "Now we are ready to create a factor shock scenario. We will need:\n", "\n", "- A name for the scenario\n", "- The type of the scenario. Since we are creating a factor shock scenario, the type will be `FactorScenarioType.Factor_Shock`\n", "- The parameters of the scenario\n", "- Scenario entitlements\n", "- A description for the scenario\n", "\n", "Below, we will create two scenarios: one factor shock scenario with no propagation, the second one with propagation." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "factor_shock_scenario_no_propagation_name = f\"Sample factor shock scenario with {risk_model_id} with no propagation\"\n", "factor_shock_scenario_no_propagation = FactorScenario(\n", " name=factor_shock_scenario_no_propagation_name,\n", " type=FactorScenarioType.Factor_Shock,\n", " parameters=factor_shock_parameters_no_propagation,\n", " description=f\"Sample custom shock scenario with {risk_model_id}\",\n", " entitlements=scenario_entitlements,\n", ")\n", "factor_shock_scenario_no_propagation.save()\n", "print(factor_shock_scenario_no_propagation)\n", "\n", "factor_shock_scenario_propagation_name = f\"Sample factor shock scenario with {risk_model_id} with propagation\"\n", "factor_shock_scenario_with_propagation = FactorScenario(\n", " name=factor_shock_scenario_propagation_name,\n", " type=FactorScenarioType.Factor_Shock,\n", " parameters=factor_shock_parameters_with_propagation,\n", " description=f\"Sample custom shock scenario with {risk_model_id}\",\n", " entitlements=scenario_entitlements,\n", ")\n", "factor_shock_scenario_with_propagation.save()\n", "print(factor_shock_scenario_with_propagation)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, "source": [ "## FAQs:\n", "\n", "### How do I get my Scenarios or Scenarios that are shared with me?\n", "\n", "You can get saved scenarios that you created or that were shared with you:\n", "- By its unique Marquee ID\n", "- By its name\n", "- By applying filters such as `risk_model`, `type` of the scenario, the `shocked_factors`, or whether the shocks are propagated\n", "- For a historical simulation scenario, you can filter by a `start_date` and `end_date`." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Get by id\n", "scenario_by_id = FactorScenario.get(\"SCENARIO ID\")\n", "\n", "# Get all my factor Shock scenarios (and those that are shared with me) with risk model AXIOMA_AXWW4M and whose\n", "# factor shocks are propagated to all factors\n", "many_scenarios = FactorScenario.get_many(\n", " risk_model=risk_model_id, propagated_shocks=True, type=FactorScenarioType.Factor_Shock\n", ")\n", "# Get all my factor Shock scenarios (and those that are shared with me) with risk model AXIOMA_AXWW4M that shocks the\n", "# Value factor\n", "many_scenarios = FactorScenario.get_many(\n", " risk_model=risk_model_id, shocked_factors=[\"Value\"], type=FactorScenarioType.Factor_Shock\n", ")\n", "\n", "# Get all my historical simulation scenarios that are within the requested start and end date\n", "historical_simulation_scenarios = FactorScenario.get_many(\n", " type=\"Factor Historical Simulation\", start_date=dt.date(2023, 1, 1), end_date=dt.date(2024, 1, 1)\n", ")" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, "source": [ "### How do I get GS pre-scanned scenarios?\n", "\n", "To get GS pre-scanned scenarios, you can additionally filter by the tag \"GS\". See below for some examples" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Get 3 factor shocks scenarios with risk model AXIOMA_AXWW4M that propagate factor shocks\n", "pre_canned_factor_shock_scenarios = FactorScenario.get_many(\n", " risk_model=risk_model_id, propagated_shocks=True, type=FactorScenarioType.Factor_Shock, tags=[\"GS\"], limit=3\n", ")\n", "print(pre_canned_factor_shock_scenarios)\n", "\n", "# Get 3 historical simulation that are within selected start and end date\n", "pre_canned_historical_simulation_scenarios = FactorScenario.get_many(\n", " type=\"Factor Historical Simulation\",\n", " start_date=dt.date(2010, 1, 1),\n", " end_date=dt.date(2024, 1, 1),\n", " tags=[\"GS\"],\n", " limit=3,\n", ")\n", "print(pre_canned_historical_simulation_scenarios)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, "source": [ "### Running Scenario(s) on a Portfolio\n", "\n", "To see the estimated impact of scenarios on your portfolio, run the `get_factor_scenario_analytics` function in the\n", "`PortfolioManager` class, which is the interface for managing your portfolios in Marquee.\n", "\n", "A scenario calculation request requires the following components:\n", "\n", "- A date: date on which to run a scenario. We will run the scenario(s) on your portfolio holdings as of this date.\n", "- Scenarios: List of scenarios to run. Can either be a list of unique Marquee IDs, or a scenario object itself.\n", "- Risk Model: The risk model to run a scenario with.\n", "- Measures: Metrics to return, which include:\n", " 1. Total portfolio estimated factor PnL\n", " 2. Total portfolio PnL by GICS sector & industry\n", " 3. Total portfolio PnL by region\n", " 4. Total portfolio PnL by the direction (LONG/SHORT) of your portfolio holdings,\n", " 5. Estimated performance of each asset in your portfolio." ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "portfolio_id = \"PORTFOLIO ID\"\n", "pm = PortfolioManager(portfolio_id)\n", "\n", "scenario_analytics = pm.get_factor_scenario_analytics(\n", " scenarios=[\"SCENARIO ID\"],\n", " date=dt.date(2024, 4, 10),\n", " measures=[\n", " ScenarioCalculationMeasure.SUMMARY,\n", " ScenarioCalculationMeasure.ESTIMATED_FACTOR_PNL,\n", " ScenarioCalculationMeasure.ESTIMATED_PNL_BY_SECTOR,\n", " ScenarioCalculationMeasure.ESTIMATED_PNL_BY_REGION,\n", " ScenarioCalculationMeasure.ESTIMATED_PNL_BY_DIRECTION,\n", " ScenarioCalculationMeasure.ESTIMATED_PNL_BY_ASSET,\n", " ],\n", " risk_model=risk_model_id,\n", ")\n", "summary = scenario_analytics.get('summary')\n", "factor_pnl = scenario_analytics.get('factorPnl')\n", "sector_aggregations = scenario_analytics.get('bySectorAggregations')\n", "region_aggregations = scenario_analytics.get(\"byRegionAggregations\")\n", "by_asset_aggregations = scenario_analytics.get('byAsset')" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } }, "source": [ "### Run Scenario(s) on a Basket\n", "\n", "We can also run one or multiple scenarios on an existing equity custom basket and pull scenario analytics data." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "from gs_quant.markets.baskets import Basket\n", "\n", "basket_ticker = \"BASKET TICKER\"\n", "basket = Basket.get(basket_ticker)\n", "\n", "basket_scenario_analytics = basket.get_factor_scenario_analytics(\n", " scenarios=[factor_shock_scenario_no_propagation, factor_shock_scenario_with_propagation, \"YOUR SCENARIO\"],\n", " date=dt.date(2024, 4, 1),\n", " measures=[\n", " ScenarioCalculationMeasure.SUMMARY,\n", " ScenarioCalculationMeasure.ESTIMATED_FACTOR_PNL,\n", " ScenarioCalculationMeasure.ESTIMATED_PNL_BY_SECTOR,\n", " ScenarioCalculationMeasure.ESTIMATED_PNL_BY_REGION,\n", " ScenarioCalculationMeasure.ESTIMATED_PNL_BY_DIRECTION,\n", " ScenarioCalculationMeasure.ESTIMATED_PNL_BY_ASSET,\n", " ],\n", " risk_model=risk_model_id,\n", ")\n", "\n", "summary = basket_scenario_analytics.get('summary')\n", "factor_pnl = basket_scenario_analytics.get('factorPnl')\n", "sector_aggregations = basket_scenario_analytics.get('bySectorAggregations')\n", "region_aggregations = basket_scenario_analytics.get(\"byRegionAggregations\")\n", "by_asset_aggregations = basket_scenario_analytics.get('byAsset')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: gs_quant/documentation/12_scenarios/02_Historical_Scenarios.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Historical Factor Scenario\n", "\n", "In a historical factor scenario, the aim is to replicate the market conditions during a significant or\n", "topical historical event and estimate the impact on your portfolio today. The first step is to define the time period\n", " of the historical event. Then, factor returns across the time period are geometrically aggregated, and these aggregated\n", " returns are the factor shocks that will be used to derive the estimated impact on the performance of your portfolio." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 1: Initialize a GsSession\n", "\n", "The initial step is to import all required modules and initialize a GsSession with your application ID and secret." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.session import GsSession, Environment\n", "from gs_quant.markets.scenario import FactorScenario, FactorScenarioType, HistoricalSimulationParameters\n", "from gs_quant.entities.entity import ScenarioCalculationMeasure\n", "from gs_quant.markets.portfolio_manager import PortfolioManager\n", "from gs_quant.entities.entitlements import Entitlements, EntitlementBlock, User\n", "import datetime as dt\n", "\n", "client = None\n", "secret = None\n", "\n", "## External users must fill in their client ID and secret below and comment out the lines below\n", "\n", "# client = 'ENTER CLIENT ID'\n", "# secret = 'ENTER CLIENT SECRET'\n", "\n", "GsSession.use(Environment.PROD, client_id=client, client_secret=secret)\n", "\n", "print('GS Session initialized.')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 2: How do I create a historical factor scenario?\n", "\n", "Similar to factor shock scenarios, we will need to provide:\n", "- A name for the scenario\n", "- The type of the scenario. In this case, the scenario type is `FactorScenarioType.Factor_Historical_Simulation`\n", "- The parameters of the scenario\n", "- Scenario entitlements\n", "- A description for the scenario\n", "\n", "The parameters are defined in a `HistoricalSimulationParameters` object and will define the time\n", "period of the historical simulation scenario:\n", "\n", "- Start Date: The start date of the historical event simulation period\n", "- End Date: The end date of the historical event simulation period.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_date = dt.date(2023, 1, 1)\n", "end_date = dt.date(2024, 1, 1)\n", "\n", "admin_emails = [\"ADMIN EMAILS\"]\n", "edit_emails = [\"EDIT EMAILS\"]\n", "view_emails = [\"VIEW EMAILS\"]\n", "\n", "admin_entitlements = EntitlementBlock(users=User.get_many(emails=admin_emails))\n", "edit_entitlements = EntitlementBlock(users=User.get_many(emails=edit_emails))\n", "view_entitlements = EntitlementBlock(users=User.get_many(emails=view_emails))\n", "\n", "scenario_entitlements = Entitlements(view=view_entitlements, edit=edit_entitlements, admin=admin_entitlements)\n", "\n", "date_range_parameters = HistoricalSimulationParameters(start_date=start_date, end_date=end_date)\n", "historical_scenario_name = (\n", " f\"Example historical simulation scenario from {start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}\"\n", ")\n", "historical_replay_scenario = FactorScenario(\n", " name=historical_scenario_name,\n", " type=FactorScenarioType.Factor_Historical_Simulation,\n", " parameters=date_range_parameters,\n", " description=f\"Sample historical simulation scenario from {start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}\",\n", " entitlements=scenario_entitlements,\n", ")\n", "\n", "historical_replay_scenario.save()\n", "print(historical_replay_scenario)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## FAQs:\n", "\n", "### How do I get my Scenarios or Scenarios that are shared with me?\n", "\n", "You can get saved scenarios that you created or that were shared with you:\n", "- By its unique Marquee ID\n", "- By its name\n", "- By applying filters such as `risk_model`, `type` of the scenario, the `shocked_factors`, or whether the shocks are propagated\n", "- For a historical simulation scenario, you can filter by a `start_date` and `end_date`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "risk_model_id = \"RISK MODEL ID\"\n", "\n", "# Get by id\n", "scenario_by_id = FactorScenario.get(\"SCENARIO ID\")\n", "\n", "# Get all my factor Shock scenarios (and those that are shared with me) with risk model AXIOMA_AXWW4M and whose\n", "# factor shocks are propagated to all factors\n", "many_scenarios = FactorScenario.get_many(\n", " risk_model=risk_model_id, propagated_shocks=True, type=FactorScenarioType.Factor_Shock\n", ")\n", "# Get all my factor Shock scenarios (and those that are shared with me) with risk model AXIOMA_AXWW4M that shocks the\n", "# Value factor\n", "many_scenarios = FactorScenario.get_many(\n", " risk_model=risk_model_id, shocked_factors=[\"Value\"], type=FactorScenarioType.Factor_Shock\n", ")\n", "\n", "# Get all my historical simulation scenarios that are within the requested start and end date\n", "historical_simulation_scenarios = FactorScenario.get_many(\n", " type=\"Factor Historical Simulation\", start_date=dt.date(2023, 1, 1), end_date=dt.date(2024, 1, 1)\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### How do I get GS pre-scanned scenarios?\n", "\n", "To get GS pre-scanned scenarios, you can additionally filter by the tag \"GS\". See below for some examples" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Get 3 factor shocks scenarios with risk model AXIOMA_AXWW4M that propagate factor shocks\n", "pre_canned_factor_shock_scenarios = FactorScenario.get_many(\n", " risk_model=risk_model_id, propagated_shocks=True, type=FactorScenarioType.Factor_Shock, tags=[\"GS\"], limit=3\n", ")\n", "print(pre_canned_factor_shock_scenarios)\n", "\n", "# Get 3 historical simulation that are within selected start and end date\n", "pre_canned_historical_simulation_scenarios = FactorScenario.get_many(\n", " type=\"Factor Historical Simulation\",\n", " start_date=dt.date(2010, 1, 1),\n", " end_date=dt.date(2024, 1, 1),\n", " tags=[\"GS\"],\n", " limit=3,\n", ")\n", "print(pre_canned_historical_simulation_scenarios)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Running Scenario(s) on a Portfolio\n", "\n", "To see the estimated impact of scenarios on your portfolio, run the `get_factor_scenario_analytics` function in the\n", "`PortfolioManager` class, which is the interface for managing your portfolios in Marquee.\n", "\n", "A scenario calculation request requires the following components:\n", "\n", "- A date: date on which to run a scenario. We will run the scenario(s) on your portfolio holdings as of this date.\n", "- Scenarios: List of scenarios to run. Can either be a list of unique Marquee IDs, or a scenario object itself.\n", "- Risk Model: The risk model to run a scenario with.\n", "- Measures: Metrics to return, which include:\n", " 1. Total portfolio estimated factor PnL\n", " 2. Total portfolio PnL by GICS sector & industry\n", " 3. Total portfolio PnL by region\n", " 4. Total portfolio PnL by the direction (LONG/SHORT) of your portfolio holdings,\n", " 5. Estimated performance of each asset in your portfolio." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "portfolio_id = \"PORTFOLIO ID\"\n", "pm = PortfolioManager(portfolio_id)\n", "\n", "scenario_analytics = pm.get_factor_scenario_analytics(\n", " scenarios=[\"SCENARIO ID\"],\n", " date=dt.date(2024, 4, 10),\n", " measures=[\n", " ScenarioCalculationMeasure.SUMMARY,\n", " ScenarioCalculationMeasure.ESTIMATED_FACTOR_PNL,\n", " ScenarioCalculationMeasure.ESTIMATED_PNL_BY_SECTOR,\n", " ScenarioCalculationMeasure.ESTIMATED_PNL_BY_REGION,\n", " ScenarioCalculationMeasure.ESTIMATED_PNL_BY_DIRECTION,\n", " ScenarioCalculationMeasure.ESTIMATED_PNL_BY_ASSET,\n", " ],\n", " risk_model=risk_model_id,\n", ")\n", "summary = scenario_analytics.get('summary')\n", "factor_pnl = scenario_analytics.get('factorPnl')\n", "sector_aggregations = scenario_analytics.get('bySectorAggregations')\n", "region_aggregations = scenario_analytics.get(\"byRegionAggregations\")\n", "by_asset_aggregations = scenario_analytics.get('byAsset')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Run Scenario(s) on a Basket\n", "\n", "We can also run one or multiple scenarios on an existing equity custom basket and pull scenario analytics data." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from gs_quant.markets.baskets import Basket\n", "\n", "basket_ticker = \"BASKET TICKER\"\n", "basket = Basket.get(basket_ticker)\n", "\n", "basket_scenario_analytics = basket.get_factor_scenario_analytics(\n", " scenarios=[\"YOUR SCENARIOS\"],\n", " date=dt.date(2024, 4, 1),\n", " measures=[\n", " ScenarioCalculationMeasure.SUMMARY,\n", " ScenarioCalculationMeasure.ESTIMATED_FACTOR_PNL,\n", " ScenarioCalculationMeasure.ESTIMATED_PNL_BY_SECTOR,\n", " ScenarioCalculationMeasure.ESTIMATED_PNL_BY_REGION,\n", " ScenarioCalculationMeasure.ESTIMATED_PNL_BY_DIRECTION,\n", " ScenarioCalculationMeasure.ESTIMATED_PNL_BY_ASSET,\n", " ],\n", " risk_model=risk_model_id,\n", ")\n", "\n", "summary = basket_scenario_analytics.get('summary')\n", "factor_pnl = basket_scenario_analytics.get('factorPnl')\n", "sector_aggregations = basket_scenario_analytics.get('bySectorAggregations')\n", "region_aggregations = basket_scenario_analytics.get(\"byRegionAggregations\")\n", "by_asset_aggregations = basket_scenario_analytics.get('byAsset')" ] } ], "metadata": { "kernelspec": { "display_name": "tutorial-env", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: gs_quant/documentation/Contents.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "d1e67001", "metadata": {}, "source": [ "# Contents" ] }, { "cell_type": "markdown", "id": "d1e67002", "metadata": {}, "source": [ "## 📂 00_data" ] }, { "cell_type": "markdown", "id": "d1e67003", "metadata": {}, "source": [ "- 📁 **00_datasets**\n", " - 📁 examples\n", " - 📄 [0000_query_dataset.ipynb](00_data/00_datasets/examples/0000_query_dataset.ipynb)\n", " - 📄 [0001_get_dataset_coverage.ipynb](00_data/00_datasets/examples/0001_get_dataset_coverage.ipynb)\n", " - 📄 [0002_using_data_contexts.ipynb](00_data/00_datasets/examples/0002_using_data_contexts.ipynb)\n", " - 📄 [0003_get_data_using_coordinate.ipynb](00_data/00_datasets/examples/0003_get_data_using_coordinate.ipynb)\n", " - 📄 [0004_get_all_assets_from_region.ipynb](00_data/00_datasets/examples/0004_get_all_assets_from_region.ipynb)\n", " - 📄 [0005_get_vol_surface.ipynb](00_data/00_datasets/examples/0005_get_vol_surface.ipynb)\n", " - 📄 [0006_ptp_dataset.ipynb](00_data/00_datasets/examples/0006_ptp_dataset.ipynb)\n", " - 📄 [0007_query_fxvol.script.ipynb](00_data/00_datasets/examples/0007_query_fxvol.script.ipynb)\n", " - 📄 [0008_s3_partners_short_interest.ipynb](00_data/00_datasets/examples/0008_s3_partners_short_interest.ipynb)\n", "- 📁 **01_analytics**\n", " - 📁 examples\n", " - 📄 [0000_charting_data.ipynb](00_data/01_analytics/examples/0000_charting_data.ipynb)\n", " - 📄 [0001_exporting_data.ipynb](00_data/01_analytics/examples/0001_exporting_data.ipynb)\n", "- 📁 **02_workspaces**\n", " - 📁 examples\n", " - 📄 [0001_creating_a_workspace.ipynb](00_data/02_workspaces/examples/0001_creating_a_workspace.ipynb)\n", "- 📁 **03_visualizations**\n", " - 📁 examples\n", " - 📄 [0001_creating_a_visualization.ipynb](00_data/03_visualizations/examples/0001_creating_a_visualization.ipynb)\n", "- 📁 **04_screens**\n", " - 📁 examples\n", " - 📄 [00_base_screener_api_tutorial.ipynb](00_data/04_screens/examples/00_base_screener_api_tutorial.ipynb)\n", " - 📄 [01_screen_api_tutorial.ipynb](00_data/04_screens/examples/01_screen_api_tutorial.ipynb)\n", "- 📁 **covid**\n", " - 📄 [Comparing, Reconciling, and Combining COVID-19 Data Sources.ipynb](00_data/covid/Comparing,%20Reconciling,%20and%20Combining%20COVID-19%20Data%20Sources.ipynb)\n" ] }, { "cell_type": "markdown", "id": "d1e67004", "metadata": {}, "source": [ "## 📂 01_markets" ] }, { "cell_type": "markdown", "id": "d1e67005", "metadata": {}, "source": [ "- 📁 **00_securities**\n", " - 📁 examples\n", " - 📄 [0000_get_an_asset_from_identifier.ipynb](01_markets/00_securities/examples/0000_get_an_asset_from_identifier.ipynb)\n", "- 📁 **02_rdates**\n", " - 📁 examples\n", " - 📄 [0000_get_a_date_relative_from_today.ipynb](01_markets/02_rdates/examples/0000_get_a_date_relative_from_today.ipynb)\n", " - 📄 [0001_get_a_date_relative_from_a_base_date.ipynb](01_markets/02_rdates/examples/0001_get_a_date_relative_from_a_base_date.ipynb)\n", " - 📄 [0002_get_a_date_relative_using_pricing_context.ipynb](01_markets/02_rdates/examples/0002_get_a_date_relative_using_pricing_context.ipynb)\n", " - 📄 [0003_get_a_date_using_a_business_day_calendar.ipynb](01_markets/02_rdates/examples/0003_get_a_date_using_a_business_day_calendar.ipynb)\n", " - 📄 [0004_chaining_relativedate_rules.ipynb](01_markets/02_rdates/examples/0004_chaining_relativedate_rules.ipynb)\n", " - 📄 [0005_passing_your_own_holiday_calendar.ipynb](01_markets/02_rdates/examples/0005_passing_your_own_holiday_calendar.ipynb)\n" ] }, { "cell_type": "markdown", "id": "d1e67006", "metadata": {}, "source": [ "## 📂 02_pricing_and_risk" ] }, { "cell_type": "markdown", "id": "d1e67007", "metadata": {}, "source": [ "- 📁 **00_instruments_and_measures**\n", " - 📁 examples\n", " - 📁 00_instrument_basics\n", " - 📄 [01_view-trade-properties.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/01_view-trade-properties.ipynb)\n", " - 📄 [02_get-a-property.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/02_get-a-property.ipynb)\n", " - 📄 [03_set-a-property.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/03_set-a-property.ipynb)\n", " - 📄 [04_enum-property.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/04_enum-property.ipynb)\n", " - 📄 [05_resolve-a-trade.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/05_resolve-a-trade.ipynb)\n", " - 📄 [06_market-data.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/06_market-data.ipynb)\n", " - 📄 [07_float-with-info.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/07_float-with-info.ipynb)\n", " - 📁 01_rates\n", " - 📄 [01_view_swap_definition.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/01_view_swap_definition.ipynb)\n", " - 📄 [02_swap_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/02_swap_trade_construction.ipynb)\n", " - 📄 [03_calc_swap_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/03_calc_swap_price.ipynb)\n", " - 📄 [04_calc_swap_risk_measures.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/04_calc_swap_risk_measures.ipynb)\n", " - 📄 [05_calc_swap_price_in_pricing_context.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/05_calc_swap_price_in_pricing_context.ipynb)\n", " - 📄 [06_calc_swap_price_historically.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/06_calc_swap_price_historically.ipynb)\n", " - 📄 [07_calc_swap_risks_in_pricing_context.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/07_calc_swap_risks_in_pricing_context.ipynb)\n", " - 📄 [08_calc_swap_risk_historically.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/08_calc_swap_risk_historically.ipynb)\n", " - 📄 [09_swaption_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/09_swaption_trade_construction.ipynb)\n", " - 📄 [10_straddle_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/10_straddle_price.ipynb)\n", " - 📄 [11_midcurve_swaption_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/11_midcurve_swaption_price.ipynb)\n", " - 📄 [12_swap_future_cashflows.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/12_swap_future_cashflows.ipynb)\n", " - 📄 [13_calc_xccy_swap_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/13_calc_xccy_swap_price.ipynb)\n", " - 📄 [14_calc_xccy_swap_risk_measures.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/14_calc_xccy_swap_risk_measures.ipynb)\n", " - 📄 [15_spread_option_grid_pricing.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/15_spread_option_grid_pricing.ipynb)\n", " - 📄 [16_change_discount_curve.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/16_change_discount_curve.ipynb)\n", " - 📄 [17_calc_xccy_swap_cashflows.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/17_calc_xccy_swap_cashflows.ipynb)\n", " - 📄 [18_solve_present_value.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/18_solve_present_value.ipynb)\n", " - 📄 [19_solve_delta.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/19_solve_delta.ipynb)\n", " - 📄 [20_fix_float_legs_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/20_fix_float_legs_price.ipynb)\n", " - 📄 [21_asset_swap_definition.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/21_asset_swap_definition.ipynb)\n", " - 📄 [22_cap_floor.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/22_cap_floor.ipynb)\n", " - 📄 [23_solve_vanna_&_volga.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/23_solve_vanna_&_volga.ipynb)\n", " - 📁 02_fx\n", " - 📄 [01_fx_fwd_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/01_fx_fwd_trade_construction.ipynb)\n", " - 📄 [02_fx_option_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/02_fx_option_trade_construction.ipynb)\n", " - 📄 [03_fx_vol_swap_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/03_fx_vol_swap_trade_construction.ipynb)\n", " - 📄 [04_fx_binary_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/04_fx_binary_trade_construction.ipynb)\n", " - 📄 [05_calc_option_measures.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/05_calc_option_measures.ipynb)\n", " - 📄 [06_fx_var_swap_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/06_fx_var_swap_trade_construction.ipynb)\n", " - 📄 [07_fx_knockout_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/07_fx_knockout_trade_construction.ipynb)\n", " - 📄 [08_fx_double_touch_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/08_fx_double_touch_trade_construction.ipynb)\n", " - 📄 [09_fx_delta_measures.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/09_fx_delta_measures.ipynb)\n", " - 📄 [10_fx_gamma_measures.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/10_fx_gamma_measures.ipynb)\n", " - 📁 03_eq\n", " - 📄 [01_calc_eq_option_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/01_calc_eq_option_price.ipynb)\n", " - 📄 [02_calc_eq_option_risk_measures.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/02_calc_eq_option_risk_measures.ipynb)\n", " - 📄 [03_calc_eq_option_price_and_risk_in_pricing_context.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/03_calc_eq_option_price_and_risk_in_pricing_context.ipynb)\n", " - 📄 [04_calc_eq_option_price_and_risk_historically.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/04_calc_eq_option_price_and_risk_historically.ipynb)\n", " - 📄 [05_calc_eq_option_portfolio_greeks.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/05_calc_eq_option_portfolio_greeks.ipynb)\n", " - 📁 04_credit\n", " - 📄 [01_cdindex_option_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/01_cdindex_option_price.ipynb)\n", " - 📄 [02_cdindex_option_xover_payer_spread.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/02_cdindex_option_xover_payer_spread.ipynb)\n", " - 📄 [03_cdindex_option_risks.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/03_cdindex_option_risks.ipynb)\n", " - 📄 [04_cdindex_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/04_cdindex_price.ipynb)\n", " - 📄 [05_cdindex_roll_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/05_cdindex_roll_price.ipynb)\n", " - 📁 tutorials\n", " - 📄 [Instruments.ipynb](02_pricing_and_risk/00_instruments_and_measures/tutorials/Instruments.ipynb)\n", " - 📄 [Measures.ipynb](02_pricing_and_risk/00_instruments_and_measures/tutorials/Measures.ipynb)\n", "- 📁 **01_scenarios_and_contexts**\n", " - 📁 examples\n", " - 📁 00_market_objects\n", " - 📄 [010000_market_data_in_gs_quant.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010000_market_data_in_gs_quant.ipynb)\n", " - 📄 [010001_market_objects.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010001_market_objects.ipynb)\n", " - 📄 [010002_marketdatapattern_basics.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010002_marketdatapattern_basics.ipynb)\n", " - 📄 [010003_marketdatapattern_construction_ir.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010003_marketdatapattern_construction_ir.ipynb)\n", " - 📄 [010004_marketdatapattern_construction_ir_vol.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010004_marketdatapattern_construction_ir_vol.ipynb)\n", " - 📄 [010005_marketdatapattern_construction_ir_basis.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010005_marketdatapattern_construction_ir_basis.ipynb)\n", " - 📄 [010006_marketdatapattern_construction_ir_xc.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010006_marketdatapattern_construction_ir_xc.ipynb)\n", " - 📄 [010007_marketdatapattern_construction_fx.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010007_marketdatapattern_construction_fx.ipynb)\n", " - 📄 [010008_marketdatapattern_construction_fx_fwd.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010008_marketdatapattern_construction_fx_fwd.ipynb)\n", " - 📄 [010009_marketdatapattern_construction_fx_vol.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010009_marketdatapattern_construction_fx_vol.ipynb)\n", " - 📄 [010010_marketdatapattern_construction_cd.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010010_marketdatapattern_construction_cd.ipynb)\n", " - 📁 01_rollfwd_shock\n", " - 📄 [01_rollfwd_for_trade.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/01_rollfwd_for_trade.ipynb)\n", " - 📄 [02_basic_use_of_rollfwd_scenario.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/02_basic_use_of_rollfwd_scenario.ipynb)\n", " - 📄 [03_rollfwd-showing-lifecycling-effects-on-swaps-and-swaptions.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/03_rollfwd-showing-lifecycling-effects-on-swaps-and-swaptions.ipynb)\n", " - 📄 [04_yield_curves_with_rollfwd.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/04_yield_curves_with_rollfwd.ipynb)\n", " - 📄 [05_rollfwd_swap_fwd_rates.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/05_rollfwd_swap_fwd_rates.ipynb)\n", " - 📁 02_market_shock\n", " - 📄 [010200_marketdatashockbasedscenario_basics.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/02_market_shock/010200_marketdatashockbasedscenario_basics.ipynb)\n", " - 📄 [010201_rate_spot_shock.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/02_market_shock/010201_rate_spot_shock.ipynb)\n", " - 📄 [010203_rate_vol_shock.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/02_market_shock/010203_rate_vol_shock.ipynb)\n", " - 📄 [010204_delta_shock_equivalent.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/02_market_shock/010204_delta_shock_equivalent.ipynb)\n", " - 📄 [010205_vega_shock_equivalent.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/02_market_shock/010205_vega_shock_equivalent.ipynb)\n", " - 📄 [010206_eq_spot_shock.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/02_market_shock/010206_eq_spot_shock.ipynb)\n", " - 📄 [010207_fx_shock_examples.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/02_market_shock/010207_fx_shock_examples.ipynb)\n", " - 📁 03_composite_shocks\n", " - 📄 [010300_rate_composite_shocks.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/03_composite_shocks/010300_rate_composite_shocks.ipynb)\n", " - 📄 [010301_multiple_shocks.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/03_composite_shocks/010301_multiple_shocks.ipynb)\n", " - 📁 04_curve_shock\n", " - 📄 [010400_parallel_curve_shock.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/04_curve_shock/010400_parallel_curve_shock.ipynb)\n", " - 📄 [010401_curve_shock_midpoint.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/04_curve_shock/010401_curve_shock_midpoint.ipynb)\n", " - 📄 [010402_bear_steepener_curve_shock.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/04_curve_shock/010402_bear_steepener_curve_shock.ipynb)\n", " - 📄 [010403_bull_steepener_curve_shock.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/04_curve_shock/010403_bull_steepener_curve_shock.ipynb)\n", " - 📄 [010404_bear_flattener_curve_shock.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/04_curve_shock/010404_bear_flattener_curve_shock.ipynb)\n", " - 📄 [010405_bull_flattener_curve_shock.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/04_curve_shock/010405_bull_flattener_curve_shock.ipynb)\n", " - 📁 05_market_override\n", " - 📄 [010501_eq_spot_override.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/05_market_override/010501_eq_spot_override.ipynb)\n", " - 📄 [010502_eq_future_override.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/05_market_override/010502_eq_future_override.ipynb)\n", " - 📁 06_vol_market_override\n", " - 📄 [010601_eq_vol_override_eod.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/06_vol_market_override/010601_eq_vol_override_eod.ipynb)\n", " - 📄 [010601_eq_vol_override_intraday.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/06_vol_market_override/010601_eq_vol_override_intraday.ipynb)\n", " - 📁 07_curve_overlay\n", " - 📄 [010700_discount_curve_overlay.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/07_curve_overlay/010700_discount_curve_overlay.ipynb)\n", " - 📁 08_multi_scenario\n", " - 📄 [010801_multi_scenario_shock.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/08_multi_scenario/010801_multi_scenario_shock.ipynb)\n", " - 📁 09_measure_scenario\n", " - 📄 [010901_fx_override_example.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/09_measure_scenario/010901_fx_override_example.ipynb)\n", " - 📄 [010902_rates_override_example.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/09_measure_scenario/010902_rates_override_example.ipynb)\n", " - 📄 [010903_eq_override_example.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/09_measure_scenario/010903_eq_override_example.ipynb)\n", " - 📁 tutorials\n", " - 📄 [Pricing_Context.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/tutorials/Pricing_Context.ipynb)\n", " - 📄 [Scenarios.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/tutorials/Scenarios.ipynb)\n", "- 📄 [External Demo v1.ipynb](02_pricing_and_risk/External%20Demo%20v1.ipynb)\n" ] }, { "cell_type": "markdown", "id": "d1e67008", "metadata": {}, "source": [ "## 📂 03_portfolios" ] }, { "cell_type": "markdown", "id": "d1e67009", "metadata": {}, "source": [ "- 📁 **examples**\n", " - 📄 [030000_create_portfolio.ipynb](03_portfolios/examples/030000_create_portfolio.ipynb)\n", " - 📄 [030001_modify_instruments.ipynb](03_portfolios/examples/030001_modify_instruments.ipynb)\n", " - 📄 [030002_extracting_instruments_and_results.ipynb](03_portfolios/examples/030002_extracting_instruments_and_results.ipynb)\n", " - 📄 [030003_resolve_portfolio.ipynb](03_portfolios/examples/030003_resolve_portfolio.ipynb)\n", " - 📄 [030004_price_portfolio.ipynb](03_portfolios/examples/030004_price_portfolio.ipynb)\n", " - 📄 [030005_calculate_portfolio_risk.ipynb](03_portfolios/examples/030005_calculate_portfolio_risk.ipynb)\n", " - 📄 [030006_portfolio_grid_calc.ipynb](03_portfolios/examples/030006_portfolio_grid_calc.ipynb)\n", " - 📄 [030007_pnl_explain.ipynb](03_portfolios/examples/030007_pnl_explain.ipynb)\n", " - 📄 [030008_portflio_from_frame.ipynb](03_portfolios/examples/030008_portflio_from_frame.ipynb)\n", " - 📄 [030009_portfolio_risk_result_to_frame.ipynb](03_portfolios/examples/030009_portfolio_risk_result_to_frame.ipynb)\n", " - 📄 [030010_portfolio_intra_leg_dependencies.ipynb](03_portfolios/examples/030010_portfolio_intra_leg_dependencies.ipynb)\n", " - 📄 [030011_portfolio_from_csv.ipynb](03_portfolios/examples/030011_portfolio_from_csv.ipynb)\n", "- 📁 **tutorials**\n", " - 📄 [Create New Portfolio.ipynb](03_portfolios/tutorials/Create%20New%20Portfolio.ipynb)\n", " - 📄 [Portfolios.ipynb](03_portfolios/tutorials/Portfolios.ipynb)\n", " - 📄 [Update Historical Portfolio.ipynb](03_portfolios/tutorials/Update%20Historical%20Portfolio.ipynb)\n" ] }, { "cell_type": "markdown", "id": "d1e67010", "metadata": {}, "source": [ "## 📂 04_backtesting" ] }, { "cell_type": "markdown", "id": "d1e67011", "metadata": {}, "source": [ "- 📁 **examples**\n", " - 📁 01_PredefinedAssetEngine\n", " - 📄 [040100_simple_example.ipynb](04_backtesting/examples/01_PredefinedAssetEngine/040100_simple_example.ipynb)\n", " - 📁 02_EquityVolEngine\n", " - 📄 [040200_strategy_simple.ipynb](04_backtesting/examples/02_EquityVolEngine/040200_strategy_simple.ipynb)\n", " - 📄 [040201_strategy_delta_hedged.ipynb](04_backtesting/examples/02_EquityVolEngine/040201_strategy_delta_hedged.ipynb)\n", " - 📄 [040202_strategy_pnl_decomposition.ipynb](04_backtesting/examples/02_EquityVolEngine/040202_strategy_pnl_decomposition.ipynb)\n", " - 📄 [040203_strategy_with_signals.ipynb](04_backtesting/examples/02_EquityVolEngine/040203_strategy_with_signals.ipynb)\n", " - 📄 [040204_strategy_market_model.ipynb](04_backtesting/examples/02_EquityVolEngine/040204_strategy_market_model.ipynb)\n", " - 📄 [040205_strategy_scaled_add_action.ipynb](04_backtesting/examples/02_EquityVolEngine/040205_strategy_scaled_add_action.ipynb)\n", " - 📁 03_GenericEngine\n", " - 📄 [040300_strategy_periodic_trigger.ipynb](04_backtesting/examples/03_GenericEngine/040300_strategy_periodic_trigger.ipynb)\n", " - 📄 [040301_strategy_risk_trigger.ipynb](04_backtesting/examples/03_GenericEngine/040301_strategy_risk_trigger.ipynb)\n", " - 📄 [040302_strategy_delta_hedge_FX.ipynb](04_backtesting/examples/03_GenericEngine/040302_strategy_delta_hedge_FX.ipynb)\n", " - 📄 [040303_strategy_delta_hedge_Rates.ipynb](04_backtesting/examples/03_GenericEngine/040303_strategy_delta_hedge_Rates.ipynb)\n", " - 📄 [040304_strategy_mean_reversion.ipynb](04_backtesting/examples/03_GenericEngine/040304_strategy_mean_reversion.ipynb)\n", " - 📄 [040305_strategy_exit_trade_action.ipynb](04_backtesting/examples/03_GenericEngine/040305_strategy_exit_trade_action.ipynb)\n", " - 📄 [040306_strategy_mkt_trigger.ipynb](04_backtesting/examples/03_GenericEngine/040306_strategy_mkt_trigger.ipynb)\n", " - 📄 [040307_strategy_seagull_bullish.ipynb](04_backtesting/examples/03_GenericEngine/040307_strategy_seagull_bullish.ipynb)\n", " - 📄 [040308_rebalance_action.ipynb](04_backtesting/examples/03_GenericEngine/040308_rebalance_action.ipynb)\n", " - 📄 [040309_inflation_hedging_strategy.ipynb](04_backtesting/examples/03_GenericEngine/040309_inflation_hedging_strategy.ipynb)\n", " - 📄 [040310_initial_swap_hedging_strategy_varying_CSA.ipynb](04_backtesting/examples/03_GenericEngine/040310_initial_swap_hedging_strategy_varying_CSA.ipynb)\n", " - 📄 [040311_handling_holidays.ipynb](04_backtesting/examples/03_GenericEngine/040311_handling_holidays.ipynb)\n", " - 📄 [040312_strategy_scaled_add_action.ipynb](04_backtesting/examples/03_GenericEngine/040312_strategy_scaled_add_action.ipynb)\n", " - 📄 [040313_chained_actions.ipynb](04_backtesting/examples/03_GenericEngine/040313_chained_actions.ipynb)\n", " - 📄 [040314_gradual_entries.ipynb](04_backtesting/examples/03_GenericEngine/040314_gradual_entries.ipynb)\n", "- 📁 **tutorials**\n", " - 📄 [Backtesting.ipynb](04_backtesting/tutorials/Backtesting.ipynb)\n", " - 📄 [Basic backtest walkthrough.ipynb](04_backtesting/tutorials/Basic%20backtest%20walkthrough.ipynb)\n" ] }, { "cell_type": "markdown", "id": "d1e67012", "metadata": {}, "source": [ "## 📂 05_factor_models" ] }, { "cell_type": "markdown", "id": "d1e67013", "metadata": {}, "source": [ "- 📄 [01_Factor_Models.ipynb](05_factor_models/01_Factor_Models.ipynb)\n", "- 📄 [02_Upload_Factor_Models.ipynb](05_factor_models/02_Upload_Factor_Models.ipynb)\n" ] }, { "cell_type": "markdown", "id": "d1e67014", "metadata": {}, "source": [ "## 📂 06_baskets" ] }, { "cell_type": "markdown", "id": "d1e67015", "metadata": {}, "source": [ "- 📁 **examples**\n", " - 📁 01_basket_composition_data\n", " - 📄 [0000_get_latest_basket_composition.ipynb](06_baskets/examples/01_basket_composition_data/0000_get_latest_basket_composition.ipynb)\n", " - 📄 [0001_get_basket_composition_for_date.ipynb](06_baskets/examples/01_basket_composition_data/0001_get_basket_composition_for_date.ipynb)\n", " - 📄 [0002_get_basket_composition_for_date_range.ipynb](06_baskets/examples/01_basket_composition_data/0002_get_basket_composition_for_date_range.ipynb)\n", " - 📄 [0003_get_full_basket_composition_history.ipynb](06_baskets/examples/01_basket_composition_data/0003_get_full_basket_composition_history.ipynb)\n", " - 📄 [0004_get_basket_composition_dates.ipynb](06_baskets/examples/01_basket_composition_data/0004_get_basket_composition_dates.ipynb)\n", " - 📄 [0005_get_basket_composition_dataset_coverage.ipynb](06_baskets/examples/01_basket_composition_data/0005_get_basket_composition_dataset_coverage.ipynb)\n", " - 📁 02_basket_pricing_data\n", " - 📄 [0000_get_latest_basket_close_price.ipynb](06_baskets/examples/02_basket_pricing_data/0000_get_latest_basket_close_price.ipynb)\n", " - 📄 [0001_get_basket_close_price_for_date.ipynb](06_baskets/examples/02_basket_pricing_data/0001_get_basket_close_price_for_date.ipynb)\n", " - 📄 [0002_get_basket_close_price_for_dates.ipynb](06_baskets/examples/02_basket_pricing_data/0002_get_basket_close_price_for_dates.ipynb)\n", " - 📄 [0003_get_full_basket_close_price_history.ipynb](06_baskets/examples/02_basket_pricing_data/0003_get_full_basket_close_price_history.ipynb)\n", " - 📁 03_basket_creation\n", " - 📄 [0000_clone_basket_position_set.ipynb](06_baskets/examples/03_basket_creation/0000_clone_basket_position_set.ipynb)\n", " - 📁 position_set\n", " - 📄 [0000_create_position_set_using_position_weights.ipynb](06_baskets/examples/03_basket_creation/position_set/0000_create_position_set_using_position_weights.ipynb)\n", " - 📄 [0001_create_position_set_using_position_quantities.ipynb](06_baskets/examples/03_basket_creation/position_set/0001_create_position_set_using_position_quantities.ipynb)\n", " - 📄 [0002_create_position_set_of_equal_weight_from_list_of_identifiers.ipynb](06_baskets/examples/03_basket_creation/position_set/0002_create_position_set_of_equal_weight_from_list_of_identifiers.ipynb)\n", " - 📄 [0003_equalize_position_set_weights.ipynb](06_baskets/examples/03_basket_creation/position_set/0003_equalize_position_set_weights.ipynb)\n", " - 📄 [0004_add_position_to_existing_position_set.ipynb](06_baskets/examples/03_basket_creation/position_set/0004_add_position_to_existing_position_set.ipynb)\n", " - 📄 [0005_create_position_set_from_list_of_dictionaries.ipynb](06_baskets/examples/03_basket_creation/position_set/0005_create_position_set_from_list_of_dictionaries.ipynb)\n", " - 📄 [0006_create_position_set_from_dataframe.ipynb](06_baskets/examples/03_basket_creation/position_set/0006_create_position_set_from_dataframe.ipynb)\n", " - 📄 [0007_create_position_set_from_csv.ipynb](06_baskets/examples/03_basket_creation/position_set/0007_create_position_set_from_csv.ipynb)\n", " - 📄 [0008_create_position_set_from_excel.ipynb](06_baskets/examples/03_basket_creation/position_set/0008_create_position_set_from_excel.ipynb)\n", " - 📄 [0009_fetch_position_quantities_from_weights.ipynb](06_baskets/examples/03_basket_creation/position_set/0009_fetch_position_quantities_from_weights.ipynb)\n", " - 📄 [0010_fetch_position_weights_from_quantities.ipynb](06_baskets/examples/03_basket_creation/position_set/0010_fetch_position_weights_from_quantities.ipynb)\n", " - 📄 [0011_redistribute_position_weights.ipynb](06_baskets/examples/03_basket_creation/position_set/0011_redistribute_position_weights.ipynb)\n", " - 📄 [0012_create_historical_position_sets_from_excel.ipynb](06_baskets/examples/03_basket_creation/position_set/0012_create_historical_position_sets_from_excel.ipynb)\n", " - 📁 04_basket_corporate_actions_data\n", " - 📄 [0000_get_full_basket_corporate_actions_history.ipynb](06_baskets/examples/04_basket_corporate_actions_data/0000_get_full_basket_corporate_actions_history.ipynb)\n", " - 📄 [0001_get_basket_corporate_actions_for_dates.ipynb](06_baskets/examples/04_basket_corporate_actions_data/0001_get_basket_corporate_actions_for_dates.ipynb)\n", " - 📄 [0002_get_basket_upcoming_corporate_actions.ipynb](06_baskets/examples/04_basket_corporate_actions_data/0002_get_basket_upcoming_corporate_actions.ipynb)\n", " - 📄 [0003_get_specific_corporate_actions_types.ipynb](06_baskets/examples/04_basket_corporate_actions_data/0003_get_specific_corporate_actions_types.ipynb)\n", " - 📁 05_basket_fundamentals_data\n", " - 📄 [0000_get_basket_fundamentals_history.ipynb](06_baskets/examples/05_basket_fundamentals_data/0000_get_basket_fundamentals_history.ipynb)\n", " - 📄 [0001_get_basket_fundamentals_for_dates.ipynb](06_baskets/examples/05_basket_fundamentals_data/0001_get_basket_fundamentals_for_dates.ipynb)\n", " - 📄 [0002_get_basket_fundamentals_specific_period_and_direction.ipynb](06_baskets/examples/05_basket_fundamentals_data/0002_get_basket_fundamentals_specific_period_and_direction.ipynb)\n", " - 📄 [0003_get_specific_basket_fundamentals_metrics.ipynb](06_baskets/examples/05_basket_fundamentals_data/0003_get_specific_basket_fundamentals_metrics.ipynb)\n", " - 📁 06_basket_reports\n", " - 📄 [0000_create_new_factor_risk_report.ipynb](06_baskets/examples/06_basket_reports/0000_create_new_factor_risk_report.ipynb)\n", " - 📄 [0001_delete_existing_factor_risk_report.ipynb](06_baskets/examples/06_basket_reports/0001_delete_existing_factor_risk_report.ipynb)\n", " - 📄 [0002_get_existing_factor_risk_report.ipynb](06_baskets/examples/06_basket_reports/0002_get_existing_factor_risk_report.ipynb)\n", " - 📄 [0003_get_all_existing_basket_reports.ipynb](06_baskets/examples/06_basket_reports/0003_get_all_existing_basket_reports.ipynb)\n", " - 📄 [0004_get_status_of_all_existing_basket_reports.ipynb](06_baskets/examples/06_basket_reports/0004_get_status_of_all_existing_basket_reports.ipynb)\n", " - 📄 [0005_poll_status_of_most_recent_basket_create_report.ipynb](06_baskets/examples/06_basket_reports/0005_poll_status_of_most_recent_basket_create_report.ipynb)\n", " - 📄 [0006_poll_report_status_using_report_id.ipynb](06_baskets/examples/06_basket_reports/0006_poll_report_status_using_report_id.ipynb)\n", " - 📁 07_basket_permissions\n", " - 📄 [0000_get_basket_permissions.ipynb](06_baskets/examples/07_basket_permissions/0000_get_basket_permissions.ipynb)\n", " - 📄 [0001_permission_application_to_basket.ipynb](06_baskets/examples/07_basket_permissions/0001_permission_application_to_basket.ipynb)\n", " - 📄 [0002_permission_user_to_basket_by_email.ipynb](06_baskets/examples/07_basket_permissions/0002_permission_user_to_basket_by_email.ipynb)\n", " - 📄 [0003_permission_group_to_basket.ipynb](06_baskets/examples/07_basket_permissions/0003_permission_group_to_basket.ipynb)\n", " - 📄 [0004_remove_basket_permissions.ipynb](06_baskets/examples/07_basket_permissions/0004_remove_basket_permissions.ipynb)\n", " - 📄 [0005_get_your_permissioned_baskets.ipynb](06_baskets/examples/07_basket_permissions/0005_get_your_permissioned_baskets.ipynb)\n", " - 📁 08_flagship_baskets\n", " - 📄 [0000_get_flagship_baskets.ipynb](06_baskets/examples/08_flagship_baskets/0000_get_flagship_baskets.ipynb)\n", " - 📄 [0001_get_flagship_baskets_on_date.ipynb](06_baskets/examples/08_flagship_baskets/0001_get_flagship_baskets_on_date.ipynb)\n", " - 📄 [0002_get_flagship_baskets_containing_assets.ipynb](06_baskets/examples/08_flagship_baskets/0002_get_flagship_baskets_containing_assets.ipynb)\n", " - 📄 [0003_get_flagship_baskets_containing_assets_on_date.ipynb](06_baskets/examples/08_flagship_baskets/0003_get_flagship_baskets_containing_assets_on_date.ipynb)\n", " - 📄 [0004_get_flagship_baskets_prices.ipynb](06_baskets/examples/08_flagship_baskets/0004_get_flagship_baskets_prices.ipynb)\n", " - 📄 [0005_get_flagship_baskets_prices_for_date_range.ipynb](06_baskets/examples/08_flagship_baskets/0005_get_flagship_baskets_prices_for_date_range.ipynb)\n", " - 📄 [0006_get_flagship_baskets_compositions.ipynb](06_baskets/examples/08_flagship_baskets/0006_get_flagship_baskets_compositions.ipynb)\n", " - 📄 [0007_get_flagship_baskets_compositions_for_date_range.ipynb](06_baskets/examples/08_flagship_baskets/0007_get_flagship_baskets_compositions_for_date_range.ipynb)\n", " - 📄 [0008_filter_flagship_baskets_by_basket_type.ipynb](06_baskets/examples/08_flagship_baskets/0008_filter_flagship_baskets_by_basket_type.ipynb)\n", " - 📄 [0009_filter_flagship_baskets_by_region.ipynb](06_baskets/examples/08_flagship_baskets/0009_filter_flagship_baskets_by_region.ipynb)\n", " - 📄 [0010_filter_flagship_baskets_by_styles.ipynb](06_baskets/examples/08_flagship_baskets/0010_filter_flagship_baskets_by_styles.ipynb)\n", " - 📄 [0011_filter_flagship_baskets_by_ticker.ipynb](06_baskets/examples/08_flagship_baskets/0011_filter_flagship_baskets_by_ticker.ipynb)\n", "- 📁 **tutorials**\n", " - 📄 [Basket Backcast.ipynb](06_baskets/tutorials/Basket%20Backcast.ipynb)\n", " - 📄 [Basket Create.ipynb](06_baskets/tutorials/Basket%20Create.ipynb)\n", " - 📄 [Basket Edit.ipynb](06_baskets/tutorials/Basket%20Edit.ipynb)\n", " - 📄 [Basket Rebalance.ipynb](06_baskets/tutorials/Basket%20Rebalance.ipynb)\n" ] }, { "cell_type": "markdown", "id": "d1e67016", "metadata": {}, "source": [ "## 📂 07_index" ] }, { "cell_type": "markdown", "id": "d1e67017", "metadata": {}, "source": [ "- 📄 [README.md](07_index/README.md)\n", "- 📁 **examples**\n", " - 📄 [0000_get_index_Marquee_url.ipynb](07_index/examples/0000_get_index_Marquee_url.ipynb)\n", " - 📄 [0001_get_index_close_prices.ipynb](07_index/examples/0001_get_index_close_prices.ipynb)\n", " - 📄 [0002_get_index_fundamentals_data.ipynb](07_index/examples/0002_get_index_fundamentals_data.ipynb)\n", " - 📄 [0003_get_index_constituents.ipynb](07_index/examples/0003_get_index_constituents.ipynb)\n", " - 📄 [0004_get_index_weights_and_attribution.ipynb](07_index/examples/0004_get_index_weights_and_attribution.ipynb)\n", " - 📄 [0005_get_index_as_a_tree.ipynb](07_index/examples/0005_get_index_as_a_tree.ipynb)\n", "- 📁 **tutorials**\n", " - 📄 [GS Quant STS Index Tutorial.ipynb](07_index/tutorials/GS%20Quant%20STS%20Index%20Tutorial.ipynb)\n" ] }, { "cell_type": "markdown", "id": "d1e67018", "metadata": {}, "source": [ "## 📂 08_tree_entity" ] }, { "cell_type": "markdown", "id": "d1e67019", "metadata": {}, "source": [ "- 📄 [tree_entity.ipynb](08_tree_entity/tree_entity.ipynb)\n" ] }, { "cell_type": "markdown", "id": "d1e67020", "metadata": {}, "source": [ "## 📂 09_security_master" ] }, { "cell_type": "markdown", "id": "d1e67021", "metadata": {}, "source": [ "- 📄 [Security Master SDK.ipynb](09_security_master/Security%20Master%20SDK.ipynb)\n", "- 📄 [Security Master.ipynb](09_security_master/Security%20Master.ipynb)\n" ] }, { "cell_type": "markdown", "id": "d1e67022", "metadata": {}, "source": [ "## 📂 10_one_delta" ] }, { "cell_type": "markdown", "id": "d1e67023", "metadata": {}, "source": [ "- 📁 **Hedger**\n", " - 📄 [01_Performance_Hedger.ipynb](10_one_delta/Hedger/01_Performance_Hedger.ipynb)\n", " - 📄 [02_Factor_Hedger_(Axioma_Portfolio_Optimizer).ipynb](10_one_delta/Hedger/02_Factor_Hedger_(Axioma_Portfolio_Optimizer).ipynb)\n", " - 📄 [03_Continuous_Optimization.ipynb](10_one_delta/Hedger/03_Continuous_Optimization.ipynb)\n", "- 📁 **Portfolios**\n", " - 📄 [01_Create_Backcasted_Portfolio.ipynb](10_one_delta/Portfolios/01_Create_Backcasted_Portfolio.ipynb)\n", " - 📄 [02_Create_New_Historical_Portfolio.ipynb](10_one_delta/Portfolios/02_Create_New_Historical_Portfolio.ipynb)\n", " - 📄 [03_Update_Historical_Portfolio.ipynb](10_one_delta/Portfolios/03_Update_Historical_Portfolio.ipynb)\n", " - 📄 [04_Get_Factor_Attribution_Data.ipynb](10_one_delta/Portfolios/04_Get_Factor_Attribution_Data.ipynb)\n", " - 📄 [05_Manage_a_Fund_of_Funds.ipynb](10_one_delta/Portfolios/05_Manage_a_Fund_of_Funds.ipynb)\n", " - 📄 [06_Get_Factor_Risk_Data.ipynb](10_one_delta/Portfolios/06_Get_Factor_Risk_Data.ipynb)\n", " - 📄 [07_Get_Portfolio_Performance_Analytics.ipynb](10_one_delta/Portfolios/07_Get_Portfolio_Performance_Analytics.ipynb)\n", " - 📄 [08_Get_ESG_Analytics.ipynb](10_one_delta/Portfolios/08_Get_ESG_Analytics.ipynb)\n", " - 📄 [09_Get_Carbon_Analytics.ipynb](10_one_delta/Portfolios/09_Get_Carbon_Analytics.ipynb)\n", " - 📄 [10_Get_Thematic_Analytics.ipynb](10_one_delta/Portfolios/10_Get_Thematic_Analytics.ipynb)\n", "- 📁 **Reports**\n", " - 📄 [01_Factor_Risk_Report.ipynb](10_one_delta/Reports/01_Factor_Risk_Report.ipynb)\n", " - 📄 [02_Performance_Report.ipynb](10_one_delta/Reports/02_Performance_Report.ipynb)\n", " - 📄 [03_Thematic_Report.ipynb](10_one_delta/Reports/03_Thematic_Report.ipynb)\n" ] }, { "cell_type": "markdown", "id": "d1e67024", "metadata": {}, "source": [ "## 📂 11_macro_models" ] }, { "cell_type": "markdown", "id": "d1e67025", "metadata": {}, "source": [ "- 📄 [01_Query_Macro_Models.ipynb](11_macro_models/01_Query_Macro_Models.ipynb)\n", "- 📄 [02_Upload_Macro_Models.ipynb](11_macro_models/02_Upload_Macro_Models.ipynb)\n" ] }, { "cell_type": "markdown", "id": "d1e67026", "metadata": {}, "source": [ "## 📂 12_scenarios" ] }, { "cell_type": "markdown", "id": "d1e67027", "metadata": {}, "source": [ "- 📄 [01_Custom_Factor_Shocks.ipynb](12_scenarios/01_Custom_Factor_Shocks.ipynb)\n", "- 📄 [02_Historical_Scenarios.ipynb](12_scenarios/02_Historical_Scenarios.ipynb)\n" ] } ], "metadata": {}, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/Index.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "06d212c0", "metadata": {}, "source": [ "# Index" ] }, { "cell_type": "markdown", "id": "8bdc7267", "metadata": {}, "source": [ "- **CSA** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/16_change_discount_curve.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/16_change_discount_curve.ipynb) cell 3\n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/20_fix_float_legs_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/20_fix_float_legs_price.ipynb) cell 3\n", "- **Carry** - \n", " [02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/03_rollfwd-showing-lifecycling-effects-on-swaps-and-swaptions.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/03_rollfwd-showing-lifecycling-effects-on-swaps-and-swaptions.ipynb) cell 3\n", " [02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/05_rollfwd_swap_fwd_rates.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/05_rollfwd_swap_fwd_rates.ipynb) cell 3\n", "- **Context - Nested Contexts** - \n", " [02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/03_rollfwd-showing-lifecycling-effects-on-swaps-and-swaptions.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/03_rollfwd-showing-lifecycling-effects-on-swaps-and-swaptions.ipynb) cell 4\n", "- **Context - RollFwd** - \n", " [02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/02_basic_use_of_rollfwd_scenario.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/02_basic_use_of_rollfwd_scenario.ipynb) cell 2\n", " [02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/02_basic_use_of_rollfwd_scenario.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/02_basic_use_of_rollfwd_scenario.ipynb) cell 3\n", " [02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/03_rollfwd-showing-lifecycling-effects-on-swaps-and-swaptions.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/03_rollfwd-showing-lifecycling-effects-on-swaps-and-swaptions.ipynb) cell 2\n", " [02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/04_yield_curves_with_rollfwd.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/04_yield_curves_with_rollfwd.ipynb) cell 3\n", " [02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/05_rollfwd_swap_fwd_rates.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/05_rollfwd_swap_fwd_rates.ipynb) cell 2\n", "- **Contexts - Historical Pricing Context** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/02_cdindex_option_xover_payer_spread.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/02_cdindex_option_xover_payer_spread.ipynb) cell 5\n", "- **Contexts - HistoricalPricingContext** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/04_calc_eq_option_price_and_risk_historically.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/04_calc_eq_option_price_and_risk_historically.ipynb) cell 3\n", "- **Contexts - Market Data Based Shock Scenario** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/05_calc_eq_option_portfolio_greeks.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/05_calc_eq_option_portfolio_greeks.ipynb) cell 2\n", "- **Contexts - PricingContext** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/03_calc_eq_option_price_and_risk_in_pricing_context.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/03_calc_eq_option_price_and_risk_in_pricing_context.ipynb) cell 3\n", "- **Contexts - RollFwd** - \n", " [02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/01_rollfwd_for_trade.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/01_rollfwd_for_trade.ipynb) cell 3\n", "- **Enums - BusinessDayConvention** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/02_swap_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/02_swap_trade_construction.ipynb) cell 16\n", "- **Enums - Currency** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/02_swap_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/02_swap_trade_construction.ipynb) cell 7\n", "- **Enums - DayCountFraction** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/02_swap_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/02_swap_trade_construction.ipynb) cell 15\n", "- **Enums - Knock In / Knock Out** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/07_fx_knockout_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/07_fx_knockout_trade_construction.ipynb) cell 4\n", "- **Enums - OptionSettlementMethod** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/07_fx_knockout_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/07_fx_knockout_trade_construction.ipynb) cell 12\n", "- **Enums - PayReceive** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/02_swap_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/02_swap_trade_construction.ipynb) cell 5\n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/09_swaption_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/09_swaption_trade_construction.ipynb) cell 4\n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/09_swaption_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/09_swaption_trade_construction.ipynb) cell 9\n", "- **Enums - SwapClearingHouse** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/02_swap_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/02_swap_trade_construction.ipynb) cell 19\n", "- **Enums - UnderlyierType** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/01_calc_eq_option_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/01_calc_eq_option_price.ipynb) cell 5\n", "- **Enums - Upper barrier / Lower barrier** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/07_fx_knockout_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/07_fx_knockout_trade_construction.ipynb) cell 5\n", "- **Instrument - CDIndex** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/04_cdindex_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/04_cdindex_price.ipynb)\n", "- **Instrument - CDIndexOption** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/01_cdindex_option_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/01_cdindex_option_price.ipynb)\n", "- **Instrument - Cloning** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/17_calc_xccy_swap_cashflows.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/17_calc_xccy_swap_cashflows.ipynb) cell 8\n", "- **Instrument - Credit Index Rolling** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/05_cdindex_roll_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/05_cdindex_roll_price.ipynb)\n", "- **Instrument - Credit Payer Spread** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/02_cdindex_option_xover_payer_spread.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/02_cdindex_option_xover_payer_spread.ipynb)\n", "- **Instrument - EqOption** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/01_calc_eq_option_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/01_calc_eq_option_price.ipynb)\n", "- **Instrument - FRA** - \n", " [02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/04_yield_curves_with_rollfwd.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/04_yield_curves_with_rollfwd.ipynb) cell 2\n", "- **Instrument - FX DNT** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/08_fx_double_touch_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/08_fx_double_touch_trade_construction.ipynb)\n", "- **Instrument - FXBinary** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/04_fx_binary_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/04_fx_binary_trade_construction.ipynb)\n", "- **Instrument - FXDoubleOneTouch** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/08_fx_double_touch_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/08_fx_double_touch_trade_construction.ipynb)\n", "- **Instrument - FXForward** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/01_fx_fwd_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/01_fx_fwd_trade_construction.ipynb)\n", "- **Instrument - FXKnockout** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/07_fx_knockout_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/07_fx_knockout_trade_construction.ipynb)\n", "- **Instrument - FXVolatilitySwap** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/03_fx_vol_swap_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/03_fx_vol_swap_trade_construction.ipynb)\n", "- **Instrument - IRAssetSwapFxdFlt** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/21_asset_swap_definition.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/21_asset_swap_definition.ipynb) cell 2\n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/22_cap_floor.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/22_cap_floor.ipynb) cell 2\n", "- **Instrument - IRCMSSpreadOption** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/15_spread_option_grid_pricing.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/15_spread_option_grid_pricing.ipynb) cell 2\n", "- **Instrument - IRFixedLeg** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/20_fix_float_legs_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/20_fix_float_legs_price.ipynb) cell 2\n", "- **Instrument - IRFloatLeg** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/20_fix_float_legs_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/20_fix_float_legs_price.ipynb) cell 2\n", "- **Instrument - Rates Midcurve Swaption** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/11_midcurve_swaption_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/11_midcurve_swaption_price.ipynb)\n", "- **Instrument - Resolve** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/06_calc_swap_price_historically.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/06_calc_swap_price_historically.ipynb) cell 2\n", "- **Instrument - Solving - DNT strikes** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/08_fx_double_touch_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/08_fx_double_touch_trade_construction.ipynb) cell 11\n", "- **Instrument - Solving - EqOption strike solving** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/01_calc_eq_option_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/01_calc_eq_option_price.ipynb) cell 6\n", "- **Instrument - Solving - FX Binary strike solving** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/04_fx_binary_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/04_fx_binary_trade_construction.ipynb) cell 5\n", "- **Instrument - Solving - Size one trade by the risk of another trade** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/19_solve_delta.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/19_solve_delta.ipynb) cell 3\n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/23_solve_vanna_&_volga.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/23_solve_vanna_&_volga.ipynb) cell 3\n", "- **Instrument - Solving - Solving FXVolSwap strike vol** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/03_fx_vol_swap_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/03_fx_vol_swap_trade_construction.ipynb) cell 10\n", "- **Instrument - Solving across multiple instruments** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/18_solve_present_value.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/18_solve_present_value.ipynb) cell 3\n", "- **Instrument - XCcy Swap** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/13_calc_xccy_swap_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/13_calc_xccy_swap_price.ipynb)\n", "- **Instrument - XccySwap** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/17_calc_xccy_swap_cashflows.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/17_calc_xccy_swap_cashflows.ipynb) cell 2\n", "- **Instrument - create from dictionary** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/02_swap_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/02_swap_trade_construction.ipynb) cell 23\n", "- **Instrument Properties** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/01_view-trade-properties.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/01_view-trade-properties.ipynb) cell 2\n", " [02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/01_view-trade-properties.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/01_view-trade-properties.ipynb) cell 4\n", " [02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/02_get-a-property.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/02_get-a-property.ipynb) cell 3\n", " [02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/02_get-a-property.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/02_get-a-property.ipynb) cell 4\n", " [02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/03_set-a-property.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/03_set-a-property.ipynb) cell 3\n", " [02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/04_enum-property.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/04_enum-property.ipynb) cell 3\n", " [02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/04_enum-property.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/04_enum-property.ipynb) cell 5\n", "- **Instrument Properties - Credit Index Families** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/04_cdindex_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/04_cdindex_price.ipynb) cell 2\n", "- **Instrument Properties - FX Option Premium** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/08_fx_double_touch_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/08_fx_double_touch_trade_construction.ipynb) cell 13\n", "- **Instrument Properties - Fixing Frequency** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/03_fx_vol_swap_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/03_fx_vol_swap_trade_construction.ipynb) cell 6\n", "- **Instrument Properties - Fixing Source** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/03_fx_vol_swap_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/03_fx_vol_swap_trade_construction.ipynb) cell 8\n", "- **Instrument Properties - Solving - IRSwaption strike** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/09_swaption_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/09_swaption_trade_construction.ipynb) cell 6\n", "- **Instrument Properties - credit underlying index** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/01_cdindex_option_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/01_cdindex_option_price.ipynb) cell 3\n", "- **Instrument Solving - FXKO Strike Solving** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/07_fx_knockout_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/07_fx_knockout_trade_construction.ipynb) cell 11\n", "- **Market Data - Overwriting** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/06_market-data.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/06_market-data.ipynb) cell 11\n", " [02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/06_market-data.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/06_market-data.ipynb) cell 13\n", "- **Market Data - Retrieval** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/06_market-data.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/06_market-data.ipynb) cell 4\n", " [02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/06_market-data.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/06_market-data.ipynb) cell 6\n", " [02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/06_market-data.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/06_market-data.ipynb) cell 8\n", "- **Market Data - Yield Curve** - \n", " [02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/04_yield_curves_with_rollfwd.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/04_yield_curves_with_rollfwd.ipynb)\n", "- **Market Data - location** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/05_calc_swap_price_in_pricing_context.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/05_calc_swap_price_in_pricing_context.ipynb) cell 3\n", "- **Metrics** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/04_calc_swap_risk_measures.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/04_calc_swap_risk_measures.ipynb) cell 3\n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/07_calc_swap_risks_in_pricing_context.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/07_calc_swap_risks_in_pricing_context.ipynb) cell 3\n", "- **Metrics - Black-Scholes Delta** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/09_fx_delta_measures.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/09_fx_delta_measures.ipynb) cell 6\n", "- **Metrics - Cashflows** - \n", " [02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/03_rollfwd-showing-lifecycling-effects-on-swaps-and-swaptions.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/03_rollfwd-showing-lifecycling-effects-on-swaps-and-swaptions.ipynb) cell 2\n", "- **Metrics - Credit Greeks** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/03_cdindex_option_risks.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/03_cdindex_option_risks.ipynb) cell 3\n", "- **Metrics - Credit metrics** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/01_cdindex_option_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/01_cdindex_option_price.ipynb) cell 7\n", "- **Metrics - Delta as Notional of fwd** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/09_fx_delta_measures.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/09_fx_delta_measures.ipynb) cell 10\n", "- **Metrics - Equity Greeks** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/05_calc_eq_option_portfolio_greeks.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/05_calc_eq_option_portfolio_greeks.ipynb) cell 6\n", "- **Metrics - Equity Risks** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/02_calc_eq_option_risk_measures.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/02_calc_eq_option_risk_measures.ipynb) cell 4\n", "- **Metrics - FX Delta Measures** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/09_fx_delta_measures.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/09_fx_delta_measures.ipynb)\n", "- **Metrics - FX Gamma Measures** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/10_fx_gamma_measures.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/10_fx_gamma_measures.ipynb)\n", "- **Metrics - FXAnnualImpliedVol, Metrics - FXAnnualATMImpliedVol** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/05_calc_option_measures.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/05_calc_option_measures.ipynb) cell 2\n", "- **Metrics - FXDelta** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/09_fx_delta_measures.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/09_fx_delta_measures.ipynb) cell 8\n", "- **Metrics - FXFwd** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/04_fx_binary_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/04_fx_binary_trade_construction.ipynb) cell 7\n", "- **Metrics - FXSpot** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/04_fx_binary_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/04_fx_binary_trade_construction.ipynb) cell 5\n", "- **Metrics - FairVolStrike** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/03_fx_vol_swap_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/03_fx_vol_swap_trade_construction.ipynb) cell 15\n", "- **Metrics - Price** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/03_calc_swap_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/03_calc_swap_price.ipynb) cell 3\n", "- **Metrics - XCcy** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/14_calc_xccy_swap_risk_measures.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/14_calc_xccy_swap_risk_measures.ipynb) cell 3\n", "- **Metrics - aggregation level** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/07_calc_swap_risks_in_pricing_context.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/07_calc_swap_risks_in_pricing_context.ipynb) cell 3\n", "- **Metrics - aggregation_level** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/08_calc_swap_risk_historically.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/08_calc_swap_risk_historically.ipynb) cell 3\n", "- **Metrics - parameterized currency** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/02_calc_eq_option_risk_measures.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/02_calc_eq_option_risk_measures.ipynb) cell 6\n", "- **Portfolio - Nested Portfolios** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/15_spread_option_grid_pricing.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/15_spread_option_grid_pricing.ipynb) cell 2\n", "- **Portfolios** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/05_calc_eq_option_portfolio_greeks.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/05_calc_eq_option_portfolio_greeks.ipynb) cell 4\n", "- **Properties - Solving - Swap fixed rate** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/02_swap_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/02_swap_trade_construction.ipynb) cell 9\n", "- **Properties - floating_rate_option** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/02_swap_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/02_swap_trade_construction.ipynb) cell 11\n", "- **Results** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/04_calc_swap_risk_measures.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/04_calc_swap_risk_measures.ipynb) cell 4\n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/04_calc_swap_risk_measures.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/04_calc_swap_risk_measures.ipynb) cell 5\n", "- **Results - Multiple Measures** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/05_calc_option_measures.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/05_calc_option_measures.ipynb) cell 2\n", "- **Results - Retrieve Specific Measure** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/07_calc_swap_risks_in_pricing_context.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/07_calc_swap_risks_in_pricing_context.ipynb) cell 5\n", "- **Results - aggregating** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/05_cdindex_roll_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/05_cdindex_roll_price.ipynb) cell 8\n", "- **Results - historical results** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/08_calc_swap_risk_historically.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/08_calc_swap_risk_historically.ipynb) cell 5\n", "- **Results - slice multi risk results** - \n", " [02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/02_calc_eq_option_risk_measures.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/02_calc_eq_option_risk_measures.ipynb) cell 5\n" ] } ], "metadata": {}, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: gs_quant/documentation/README.md ================================================ # Documentation Overview gs_quant documentation is organized by core functionality outlined below. Within each group, you will find tutorials introducing core ideas and functions (this is a great place to start) and examples providing a searchable library of short specific snippets. - 📁 **00_data** - 📁 00_datasets - 📁 examples - 📄 [0000_query_dataset.ipynb](00_data/00_datasets/examples/0000_query_dataset.ipynb) - 📄 [0001_get_dataset_coverage.ipynb](00_data/00_datasets/examples/0001_get_dataset_coverage.ipynb) - 📄 [0002_using_data_contexts.ipynb](00_data/00_datasets/examples/0002_using_data_contexts.ipynb) - 📄 [0003_get_data_using_coordinate.ipynb](00_data/00_datasets/examples/0003_get_data_using_coordinate.ipynb) - 📄 [0004_get_all_assets_from_region.ipynb](00_data/00_datasets/examples/0004_get_all_assets_from_region.ipynb) - 📄 [0005_get_vol_surface.ipynb](00_data/00_datasets/examples/0005_get_vol_surface.ipynb) - 📄 [0006_ptp_dataset.ipynb](00_data/00_datasets/examples/0006_ptp_dataset.ipynb) - 📄 [0007_query_fxvol.script.ipynb](00_data/00_datasets/examples/0007_query_fxvol.script.ipynb) - 📄 [0008_s3_partners_short_interest.ipynb](00_data/00_datasets/examples/0008_s3_partners_short_interest.ipynb) - 📁 01_analytics - 📁 examples - 📄 [0000_charting_data.ipynb](00_data/01_analytics/examples/0000_charting_data.ipynb) - 📄 [0001_exporting_data.ipynb](00_data/01_analytics/examples/0001_exporting_data.ipynb) - 📁 02_workspaces - 📁 examples - 📄 [0001_creating_a_workspace.ipynb](00_data/02_workspaces/examples/0001_creating_a_workspace.ipynb) - 📁 03_visualizations - 📁 examples - 📄 [0001_creating_a_visualization.ipynb](00_data/03_visualizations/examples/0001_creating_a_visualization.ipynb) - 📁 04_screens - 📁 examples - 📄 [00_base_screener_api_tutorial.ipynb](00_data/04_screens/examples/00_base_screener_api_tutorial.ipynb) - 📄 [01_screen_api_tutorial.ipynb](00_data/04_screens/examples/01_screen_api_tutorial.ipynb) - 📁 covid - 📄 [Comparing, Reconciling, and Combining COVID-19 Data Sources.ipynb](00_data/covid/Comparing,%20Reconciling,%20and%20Combining%20COVID-19%20Data%20Sources.ipynb) - 📁 **01_markets** - 📁 00_securities - 📁 examples - 📄 [0000_get_an_asset_from_identifier.ipynb](01_markets/00_securities/examples/0000_get_an_asset_from_identifier.ipynb) - 📁 02_rdates - 📁 examples - 📄 [0000_get_a_date_relative_from_today.ipynb](01_markets/02_rdates/examples/0000_get_a_date_relative_from_today.ipynb) - 📄 [0001_get_a_date_relative_from_a_base_date.ipynb](01_markets/02_rdates/examples/0001_get_a_date_relative_from_a_base_date.ipynb) - 📄 [0002_get_a_date_relative_using_pricing_context.ipynb](01_markets/02_rdates/examples/0002_get_a_date_relative_using_pricing_context.ipynb) - 📄 [0003_get_a_date_using_a_business_day_calendar.ipynb](01_markets/02_rdates/examples/0003_get_a_date_using_a_business_day_calendar.ipynb) - 📄 [0004_chaining_relativedate_rules.ipynb](01_markets/02_rdates/examples/0004_chaining_relativedate_rules.ipynb) - 📄 [0005_passing_your_own_holiday_calendar.ipynb](01_markets/02_rdates/examples/0005_passing_your_own_holiday_calendar.ipynb) - 📁 **02_pricing_and_risk** - 📁 00_instruments_and_measures - 📁 examples - 📁 00_instrument_basics - 📄 [01_view-trade-properties.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/01_view-trade-properties.ipynb) - 📄 [02_get-a-property.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/02_get-a-property.ipynb) - 📄 [03_set-a-property.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/03_set-a-property.ipynb) - 📄 [04_enum-property.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/04_enum-property.ipynb) - 📄 [05_resolve-a-trade.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/05_resolve-a-trade.ipynb) - 📄 [06_market-data.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/06_market-data.ipynb) - 📄 [07_float-with-info.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/00_instrument_basics/07_float-with-info.ipynb) - 📁 01_rates - 📄 [01_view_swap_definition.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/01_view_swap_definition.ipynb) - 📄 [02_swap_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/02_swap_trade_construction.ipynb) - 📄 [03_calc_swap_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/03_calc_swap_price.ipynb) - 📄 [04_calc_swap_risk_measures.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/04_calc_swap_risk_measures.ipynb) - 📄 [05_calc_swap_price_in_pricing_context.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/05_calc_swap_price_in_pricing_context.ipynb) - 📄 [06_calc_swap_price_historically.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/06_calc_swap_price_historically.ipynb) - 📄 [07_calc_swap_risks_in_pricing_context.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/07_calc_swap_risks_in_pricing_context.ipynb) - 📄 [08_calc_swap_risk_historically.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/08_calc_swap_risk_historically.ipynb) - 📄 [09_swaption_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/09_swaption_trade_construction.ipynb) - 📄 [10_straddle_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/10_straddle_price.ipynb) - 📄 [11_midcurve_swaption_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/11_midcurve_swaption_price.ipynb) - 📄 [12_swap_future_cashflows.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/12_swap_future_cashflows.ipynb) - 📄 [13_calc_xccy_swap_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/13_calc_xccy_swap_price.ipynb) - 📄 [14_calc_xccy_swap_risk_measures.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/14_calc_xccy_swap_risk_measures.ipynb) - 📄 [15_spread_option_grid_pricing.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/15_spread_option_grid_pricing.ipynb) - 📄 [16_change_discount_curve.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/16_change_discount_curve.ipynb) - 📄 [17_calc_xccy_swap_cashflows.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/17_calc_xccy_swap_cashflows.ipynb) - 📄 [18_solve_present_value.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/18_solve_present_value.ipynb) - 📄 [19_solve_delta.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/19_solve_delta.ipynb) - 📄 [20_fix_float_legs_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/20_fix_float_legs_price.ipynb) - 📄 [21_asset_swap_definition.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/21_asset_swap_definition.ipynb) - 📄 [22_cap_floor.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/22_cap_floor.ipynb) - 📄 [23_solve_vanna_&_volga.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/01_rates/23_solve_vanna_&_volga.ipynb) - 📁 02_fx - 📄 [01_fx_fwd_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/01_fx_fwd_trade_construction.ipynb) - 📄 [02_fx_option_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/02_fx_option_trade_construction.ipynb) - 📄 [03_fx_vol_swap_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/03_fx_vol_swap_trade_construction.ipynb) - 📄 [04_fx_binary_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/04_fx_binary_trade_construction.ipynb) - 📄 [05_calc_option_measures.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/05_calc_option_measures.ipynb) - 📄 [06_fx_var_swap_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/06_fx_var_swap_trade_construction.ipynb) - 📄 [07_fx_knockout_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/07_fx_knockout_trade_construction.ipynb) - 📄 [08_fx_double_touch_trade_construction.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/08_fx_double_touch_trade_construction.ipynb) - 📄 [09_fx_delta_measures.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/09_fx_delta_measures.ipynb) - 📄 [10_fx_gamma_measures.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/02_fx/10_fx_gamma_measures.ipynb) - 📁 03_eq - 📄 [01_calc_eq_option_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/01_calc_eq_option_price.ipynb) - 📄 [02_calc_eq_option_risk_measures.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/02_calc_eq_option_risk_measures.ipynb) - 📄 [03_calc_eq_option_price_and_risk_in_pricing_context.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/03_calc_eq_option_price_and_risk_in_pricing_context.ipynb) - 📄 [04_calc_eq_option_price_and_risk_historically.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/04_calc_eq_option_price_and_risk_historically.ipynb) - 📄 [05_calc_eq_option_portfolio_greeks.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/03_eq/05_calc_eq_option_portfolio_greeks.ipynb) - 📁 04_credit - 📄 [01_cdindex_option_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/01_cdindex_option_price.ipynb) - 📄 [02_cdindex_option_xover_payer_spread.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/02_cdindex_option_xover_payer_spread.ipynb) - 📄 [03_cdindex_option_risks.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/03_cdindex_option_risks.ipynb) - 📄 [04_cdindex_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/04_cdindex_price.ipynb) - 📄 [05_cdindex_roll_price.ipynb](02_pricing_and_risk/00_instruments_and_measures/examples/04_credit/05_cdindex_roll_price.ipynb) - 📁 tutorials - 📄 [Instruments.ipynb](02_pricing_and_risk/00_instruments_and_measures/tutorials/Instruments.ipynb) - 📄 [Measures.ipynb](02_pricing_and_risk/00_instruments_and_measures/tutorials/Measures.ipynb) - 📁 01_scenarios_and_contexts - 📁 examples - 📁 00_market_objects - 📄 [010000_market_data_in_gs_quant.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010000_market_data_in_gs_quant.ipynb) - 📄 [010001_market_objects.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010001_market_objects.ipynb) - 📄 [010002_marketdatapattern_basics.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010002_marketdatapattern_basics.ipynb) - 📄 [010003_marketdatapattern_construction_ir.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010003_marketdatapattern_construction_ir.ipynb) - 📄 [010004_marketdatapattern_construction_ir_vol.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010004_marketdatapattern_construction_ir_vol.ipynb) - 📄 [010005_marketdatapattern_construction_ir_basis.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010005_marketdatapattern_construction_ir_basis.ipynb) - 📄 [010006_marketdatapattern_construction_ir_xc.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010006_marketdatapattern_construction_ir_xc.ipynb) - 📄 [010007_marketdatapattern_construction_fx.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010007_marketdatapattern_construction_fx.ipynb) - 📄 [010008_marketdatapattern_construction_fx_fwd.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010008_marketdatapattern_construction_fx_fwd.ipynb) - 📄 [010009_marketdatapattern_construction_fx_vol.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010009_marketdatapattern_construction_fx_vol.ipynb) - 📄 [010010_marketdatapattern_construction_cd.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/00_market_objects/010010_marketdatapattern_construction_cd.ipynb) - 📁 01_rollfwd_shock - 📄 [01_rollfwd_for_trade.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/01_rollfwd_for_trade.ipynb) - 📄 [02_basic_use_of_rollfwd_scenario.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/02_basic_use_of_rollfwd_scenario.ipynb) - 📄 [03_rollfwd-showing-lifecycling-effects-on-swaps-and-swaptions.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/03_rollfwd-showing-lifecycling-effects-on-swaps-and-swaptions.ipynb) - 📄 [04_yield_curves_with_rollfwd.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/04_yield_curves_with_rollfwd.ipynb) - 📄 [05_rollfwd_swap_fwd_rates.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/01_rollfwd_shock/05_rollfwd_swap_fwd_rates.ipynb) - 📁 02_market_shock - 📄 [010200_marketdatashockbasedscenario_basics.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/02_market_shock/010200_marketdatashockbasedscenario_basics.ipynb) - 📄 [010201_rate_spot_shock.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/02_market_shock/010201_rate_spot_shock.ipynb) - 📄 [010203_rate_vol_shock.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/02_market_shock/010203_rate_vol_shock.ipynb) - 📄 [010204_delta_shock_equivalent.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/02_market_shock/010204_delta_shock_equivalent.ipynb) - 📄 [010205_vega_shock_equivalent.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/02_market_shock/010205_vega_shock_equivalent.ipynb) - 📄 [010206_eq_spot_shock.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/02_market_shock/010206_eq_spot_shock.ipynb) - 📄 [010207_fx_shock_examples.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/02_market_shock/010207_fx_shock_examples.ipynb) - 📁 03_composite_shocks - 📄 [010300_rate_composite_shocks.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/03_composite_shocks/010300_rate_composite_shocks.ipynb) - 📄 [010301_multiple_shocks.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/03_composite_shocks/010301_multiple_shocks.ipynb) - 📁 04_curve_shock - 📄 [010400_parallel_curve_shock.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/04_curve_shock/010400_parallel_curve_shock.ipynb) - 📄 [010401_curve_shock_midpoint.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/04_curve_shock/010401_curve_shock_midpoint.ipynb) - 📄 [010402_bear_steepener_curve_shock.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/04_curve_shock/010402_bear_steepener_curve_shock.ipynb) - 📄 [010403_bull_steepener_curve_shock.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/04_curve_shock/010403_bull_steepener_curve_shock.ipynb) - 📄 [010404_bear_flattener_curve_shock.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/04_curve_shock/010404_bear_flattener_curve_shock.ipynb) - 📄 [010405_bull_flattener_curve_shock.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/04_curve_shock/010405_bull_flattener_curve_shock.ipynb) - 📁 05_market_override - 📄 [010501_eq_spot_override.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/05_market_override/010501_eq_spot_override.ipynb) - 📄 [010502_eq_future_override.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/05_market_override/010502_eq_future_override.ipynb) - 📁 06_vol_market_override - 📄 [010601_eq_vol_override_eod.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/06_vol_market_override/010601_eq_vol_override_eod.ipynb) - 📄 [010601_eq_vol_override_intraday.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/06_vol_market_override/010601_eq_vol_override_intraday.ipynb) - 📁 07_curve_overlay - 📄 [010700_discount_curve_overlay.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/07_curve_overlay/010700_discount_curve_overlay.ipynb) - 📁 08_multi_scenario - 📄 [010801_multi_scenario_shock.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/08_multi_scenario/010801_multi_scenario_shock.ipynb) - 📁 09_measure_scenario - 📄 [010901_fx_override_example.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/09_measure_scenario/010901_fx_override_example.ipynb) - 📄 [010902_rates_override_example.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/09_measure_scenario/010902_rates_override_example.ipynb) - 📄 [010903_eq_override_example.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/examples/09_measure_scenario/010903_eq_override_example.ipynb) - 📁 tutorials - 📄 [Pricing_Context.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/tutorials/Pricing_Context.ipynb) - 📄 [Scenarios.ipynb](02_pricing_and_risk/01_scenarios_and_contexts/tutorials/Scenarios.ipynb) - 📄 [External Demo v1.ipynb](02_pricing_and_risk/External%20Demo%20v1.ipynb) - 📁 **03_portfolios** - 📁 examples - 📄 [030000_create_portfolio.ipynb](03_portfolios/examples/030000_create_portfolio.ipynb) - 📄 [030001_modify_instruments.ipynb](03_portfolios/examples/030001_modify_instruments.ipynb) - 📄 [030002_extracting_instruments_and_results.ipynb](03_portfolios/examples/030002_extracting_instruments_and_results.ipynb) - 📄 [030003_resolve_portfolio.ipynb](03_portfolios/examples/030003_resolve_portfolio.ipynb) - 📄 [030004_price_portfolio.ipynb](03_portfolios/examples/030004_price_portfolio.ipynb) - 📄 [030005_calculate_portfolio_risk.ipynb](03_portfolios/examples/030005_calculate_portfolio_risk.ipynb) - 📄 [030006_portfolio_grid_calc.ipynb](03_portfolios/examples/030006_portfolio_grid_calc.ipynb) - 📄 [030007_pnl_explain.ipynb](03_portfolios/examples/030007_pnl_explain.ipynb) - 📄 [030008_portflio_from_frame.ipynb](03_portfolios/examples/030008_portflio_from_frame.ipynb) - 📄 [030009_portfolio_risk_result_to_frame.ipynb](03_portfolios/examples/030009_portfolio_risk_result_to_frame.ipynb) - 📄 [030010_portfolio_intra_leg_dependencies.ipynb](03_portfolios/examples/030010_portfolio_intra_leg_dependencies.ipynb) - 📄 [030011_portfolio_from_csv.ipynb](03_portfolios/examples/030011_portfolio_from_csv.ipynb) - 📁 tutorials - 📄 [Create New Portfolio.ipynb](03_portfolios/tutorials/Create%20New%20Portfolio.ipynb) - 📄 [Portfolios.ipynb](03_portfolios/tutorials/Portfolios.ipynb) - 📄 [Update Historical Portfolio.ipynb](03_portfolios/tutorials/Update%20Historical%20Portfolio.ipynb) - 📁 **04_backtesting** - 📁 examples - 📁 01_PredefinedAssetEngine - 📄 [040100_simple_example.ipynb](04_backtesting/examples/01_PredefinedAssetEngine/040100_simple_example.ipynb) - 📁 02_EquityVolEngine - 📄 [040200_strategy_simple.ipynb](04_backtesting/examples/02_EquityVolEngine/040200_strategy_simple.ipynb) - 📄 [040201_strategy_delta_hedged.ipynb](04_backtesting/examples/02_EquityVolEngine/040201_strategy_delta_hedged.ipynb) - 📄 [040202_strategy_pnl_decomposition.ipynb](04_backtesting/examples/02_EquityVolEngine/040202_strategy_pnl_decomposition.ipynb) - 📄 [040203_strategy_with_signals.ipynb](04_backtesting/examples/02_EquityVolEngine/040203_strategy_with_signals.ipynb) - 📄 [040204_strategy_market_model.ipynb](04_backtesting/examples/02_EquityVolEngine/040204_strategy_market_model.ipynb) - 📄 [040205_strategy_scaled_add_action.ipynb](04_backtesting/examples/02_EquityVolEngine/040205_strategy_scaled_add_action.ipynb) - 📁 03_GenericEngine - 📄 [040300_strategy_periodic_trigger.ipynb](04_backtesting/examples/03_GenericEngine/040300_strategy_periodic_trigger.ipynb) - 📄 [040301_strategy_risk_trigger.ipynb](04_backtesting/examples/03_GenericEngine/040301_strategy_risk_trigger.ipynb) - 📄 [040302_strategy_delta_hedge_FX.ipynb](04_backtesting/examples/03_GenericEngine/040302_strategy_delta_hedge_FX.ipynb) - 📄 [040303_strategy_delta_hedge_Rates.ipynb](04_backtesting/examples/03_GenericEngine/040303_strategy_delta_hedge_Rates.ipynb) - 📄 [040304_strategy_mean_reversion.ipynb](04_backtesting/examples/03_GenericEngine/040304_strategy_mean_reversion.ipynb) - 📄 [040305_strategy_exit_trade_action.ipynb](04_backtesting/examples/03_GenericEngine/040305_strategy_exit_trade_action.ipynb) - 📄 [040306_strategy_mkt_trigger.ipynb](04_backtesting/examples/03_GenericEngine/040306_strategy_mkt_trigger.ipynb) - 📄 [040307_strategy_seagull_bullish.ipynb](04_backtesting/examples/03_GenericEngine/040307_strategy_seagull_bullish.ipynb) - 📄 [040308_rebalance_action.ipynb](04_backtesting/examples/03_GenericEngine/040308_rebalance_action.ipynb) - 📄 [040309_inflation_hedging_strategy.ipynb](04_backtesting/examples/03_GenericEngine/040309_inflation_hedging_strategy.ipynb) - 📄 [040310_initial_swap_hedging_strategy_varying_CSA.ipynb](04_backtesting/examples/03_GenericEngine/040310_initial_swap_hedging_strategy_varying_CSA.ipynb) - 📄 [040311_handling_holidays.ipynb](04_backtesting/examples/03_GenericEngine/040311_handling_holidays.ipynb) - 📄 [040312_strategy_scaled_add_action.ipynb](04_backtesting/examples/03_GenericEngine/040312_strategy_scaled_add_action.ipynb) - 📄 [040313_chained_actions.ipynb](04_backtesting/examples/03_GenericEngine/040313_chained_actions.ipynb) - 📄 [040314_gradual_entries.ipynb](04_backtesting/examples/03_GenericEngine/040314_gradual_entries.ipynb) - 📁 tutorials - 📄 [Backtesting.ipynb](04_backtesting/tutorials/Backtesting.ipynb) - 📄 [Basic backtest walkthrough.ipynb](04_backtesting/tutorials/Basic%20backtest%20walkthrough.ipynb) - 📁 **05_factor_models** - 📄 [01_Factor_Models.ipynb](05_factor_models/01_Factor_Models.ipynb) - 📄 [02_Upload_Factor_Models.ipynb](05_factor_models/02_Upload_Factor_Models.ipynb) - 📁 **06_baskets** - 📁 examples - 📁 01_basket_composition_data - 📄 [0000_get_latest_basket_composition.ipynb](06_baskets/examples/01_basket_composition_data/0000_get_latest_basket_composition.ipynb) - 📄 [0001_get_basket_composition_for_date.ipynb](06_baskets/examples/01_basket_composition_data/0001_get_basket_composition_for_date.ipynb) - 📄 [0002_get_basket_composition_for_date_range.ipynb](06_baskets/examples/01_basket_composition_data/0002_get_basket_composition_for_date_range.ipynb) - 📄 [0003_get_full_basket_composition_history.ipynb](06_baskets/examples/01_basket_composition_data/0003_get_full_basket_composition_history.ipynb) - 📄 [0004_get_basket_composition_dates.ipynb](06_baskets/examples/01_basket_composition_data/0004_get_basket_composition_dates.ipynb) - 📄 [0005_get_basket_composition_dataset_coverage.ipynb](06_baskets/examples/01_basket_composition_data/0005_get_basket_composition_dataset_coverage.ipynb) - 📁 02_basket_pricing_data - 📄 [0000_get_latest_basket_close_price.ipynb](06_baskets/examples/02_basket_pricing_data/0000_get_latest_basket_close_price.ipynb) - 📄 [0001_get_basket_close_price_for_date.ipynb](06_baskets/examples/02_basket_pricing_data/0001_get_basket_close_price_for_date.ipynb) - 📄 [0002_get_basket_close_price_for_dates.ipynb](06_baskets/examples/02_basket_pricing_data/0002_get_basket_close_price_for_dates.ipynb) - 📄 [0003_get_full_basket_close_price_history.ipynb](06_baskets/examples/02_basket_pricing_data/0003_get_full_basket_close_price_history.ipynb) - 📁 03_basket_creation - 📄 [0000_clone_basket_position_set.ipynb](06_baskets/examples/03_basket_creation/0000_clone_basket_position_set.ipynb) - 📁 position_set - 📄 [0000_create_position_set_using_position_weights.ipynb](06_baskets/examples/03_basket_creation/position_set/0000_create_position_set_using_position_weights.ipynb) - 📄 [0001_create_position_set_using_position_quantities.ipynb](06_baskets/examples/03_basket_creation/position_set/0001_create_position_set_using_position_quantities.ipynb) - 📄 [0002_create_position_set_of_equal_weight_from_list_of_identifiers.ipynb](06_baskets/examples/03_basket_creation/position_set/0002_create_position_set_of_equal_weight_from_list_of_identifiers.ipynb) - 📄 [0003_equalize_position_set_weights.ipynb](06_baskets/examples/03_basket_creation/position_set/0003_equalize_position_set_weights.ipynb) - 📄 [0004_add_position_to_existing_position_set.ipynb](06_baskets/examples/03_basket_creation/position_set/0004_add_position_to_existing_position_set.ipynb) - 📄 [0005_create_position_set_from_list_of_dictionaries.ipynb](06_baskets/examples/03_basket_creation/position_set/0005_create_position_set_from_list_of_dictionaries.ipynb) - 📄 [0006_create_position_set_from_dataframe.ipynb](06_baskets/examples/03_basket_creation/position_set/0006_create_position_set_from_dataframe.ipynb) - 📄 [0007_create_position_set_from_csv.ipynb](06_baskets/examples/03_basket_creation/position_set/0007_create_position_set_from_csv.ipynb) - 📄 [0008_create_position_set_from_excel.ipynb](06_baskets/examples/03_basket_creation/position_set/0008_create_position_set_from_excel.ipynb) - 📄 [0009_fetch_position_quantities_from_weights.ipynb](06_baskets/examples/03_basket_creation/position_set/0009_fetch_position_quantities_from_weights.ipynb) - 📄 [0010_fetch_position_weights_from_quantities.ipynb](06_baskets/examples/03_basket_creation/position_set/0010_fetch_position_weights_from_quantities.ipynb) - 📄 [0011_redistribute_position_weights.ipynb](06_baskets/examples/03_basket_creation/position_set/0011_redistribute_position_weights.ipynb) - 📄 [0012_create_historical_position_sets_from_excel.ipynb](06_baskets/examples/03_basket_creation/position_set/0012_create_historical_position_sets_from_excel.ipynb) - 📁 04_basket_corporate_actions_data - 📄 [0000_get_full_basket_corporate_actions_history.ipynb](06_baskets/examples/04_basket_corporate_actions_data/0000_get_full_basket_corporate_actions_history.ipynb) - 📄 [0001_get_basket_corporate_actions_for_dates.ipynb](06_baskets/examples/04_basket_corporate_actions_data/0001_get_basket_corporate_actions_for_dates.ipynb) - 📄 [0002_get_basket_upcoming_corporate_actions.ipynb](06_baskets/examples/04_basket_corporate_actions_data/0002_get_basket_upcoming_corporate_actions.ipynb) - 📄 [0003_get_specific_corporate_actions_types.ipynb](06_baskets/examples/04_basket_corporate_actions_data/0003_get_specific_corporate_actions_types.ipynb) - 📁 05_basket_fundamentals_data - 📄 [0000_get_basket_fundamentals_history.ipynb](06_baskets/examples/05_basket_fundamentals_data/0000_get_basket_fundamentals_history.ipynb) - 📄 [0001_get_basket_fundamentals_for_dates.ipynb](06_baskets/examples/05_basket_fundamentals_data/0001_get_basket_fundamentals_for_dates.ipynb) - 📄 [0002_get_basket_fundamentals_specific_period_and_direction.ipynb](06_baskets/examples/05_basket_fundamentals_data/0002_get_basket_fundamentals_specific_period_and_direction.ipynb) - 📄 [0003_get_specific_basket_fundamentals_metrics.ipynb](06_baskets/examples/05_basket_fundamentals_data/0003_get_specific_basket_fundamentals_metrics.ipynb) - 📁 06_basket_reports - 📄 [0000_create_new_factor_risk_report.ipynb](06_baskets/examples/06_basket_reports/0000_create_new_factor_risk_report.ipynb) - 📄 [0001_delete_existing_factor_risk_report.ipynb](06_baskets/examples/06_basket_reports/0001_delete_existing_factor_risk_report.ipynb) - 📄 [0002_get_existing_factor_risk_report.ipynb](06_baskets/examples/06_basket_reports/0002_get_existing_factor_risk_report.ipynb) - 📄 [0003_get_all_existing_basket_reports.ipynb](06_baskets/examples/06_basket_reports/0003_get_all_existing_basket_reports.ipynb) - 📄 [0004_get_status_of_all_existing_basket_reports.ipynb](06_baskets/examples/06_basket_reports/0004_get_status_of_all_existing_basket_reports.ipynb) - 📄 [0005_poll_status_of_most_recent_basket_create_report.ipynb](06_baskets/examples/06_basket_reports/0005_poll_status_of_most_recent_basket_create_report.ipynb) - 📄 [0006_poll_report_status_using_report_id.ipynb](06_baskets/examples/06_basket_reports/0006_poll_report_status_using_report_id.ipynb) - 📁 07_basket_permissions - 📄 [0000_get_basket_permissions.ipynb](06_baskets/examples/07_basket_permissions/0000_get_basket_permissions.ipynb) - 📄 [0001_permission_application_to_basket.ipynb](06_baskets/examples/07_basket_permissions/0001_permission_application_to_basket.ipynb) - 📄 [0002_permission_user_to_basket_by_email.ipynb](06_baskets/examples/07_basket_permissions/0002_permission_user_to_basket_by_email.ipynb) - 📄 [0003_permission_group_to_basket.ipynb](06_baskets/examples/07_basket_permissions/0003_permission_group_to_basket.ipynb) - 📄 [0004_remove_basket_permissions.ipynb](06_baskets/examples/07_basket_permissions/0004_remove_basket_permissions.ipynb) - 📄 [0005_get_your_permissioned_baskets.ipynb](06_baskets/examples/07_basket_permissions/0005_get_your_permissioned_baskets.ipynb) - 📁 08_flagship_baskets - 📄 [0000_get_flagship_baskets.ipynb](06_baskets/examples/08_flagship_baskets/0000_get_flagship_baskets.ipynb) - 📄 [0001_get_flagship_baskets_on_date.ipynb](06_baskets/examples/08_flagship_baskets/0001_get_flagship_baskets_on_date.ipynb) - 📄 [0002_get_flagship_baskets_containing_assets.ipynb](06_baskets/examples/08_flagship_baskets/0002_get_flagship_baskets_containing_assets.ipynb) - 📄 [0003_get_flagship_baskets_containing_assets_on_date.ipynb](06_baskets/examples/08_flagship_baskets/0003_get_flagship_baskets_containing_assets_on_date.ipynb) - 📄 [0004_get_flagship_baskets_prices.ipynb](06_baskets/examples/08_flagship_baskets/0004_get_flagship_baskets_prices.ipynb) - 📄 [0005_get_flagship_baskets_prices_for_date_range.ipynb](06_baskets/examples/08_flagship_baskets/0005_get_flagship_baskets_prices_for_date_range.ipynb) - 📄 [0006_get_flagship_baskets_compositions.ipynb](06_baskets/examples/08_flagship_baskets/0006_get_flagship_baskets_compositions.ipynb) - 📄 [0007_get_flagship_baskets_compositions_for_date_range.ipynb](06_baskets/examples/08_flagship_baskets/0007_get_flagship_baskets_compositions_for_date_range.ipynb) - 📄 [0008_filter_flagship_baskets_by_basket_type.ipynb](06_baskets/examples/08_flagship_baskets/0008_filter_flagship_baskets_by_basket_type.ipynb) - 📄 [0009_filter_flagship_baskets_by_region.ipynb](06_baskets/examples/08_flagship_baskets/0009_filter_flagship_baskets_by_region.ipynb) - 📄 [0010_filter_flagship_baskets_by_styles.ipynb](06_baskets/examples/08_flagship_baskets/0010_filter_flagship_baskets_by_styles.ipynb) - 📄 [0011_filter_flagship_baskets_by_ticker.ipynb](06_baskets/examples/08_flagship_baskets/0011_filter_flagship_baskets_by_ticker.ipynb) - 📁 tutorials - 📄 [Basket Backcast.ipynb](06_baskets/tutorials/Basket%20Backcast.ipynb) - 📄 [Basket Create.ipynb](06_baskets/tutorials/Basket%20Create.ipynb) - 📄 [Basket Edit.ipynb](06_baskets/tutorials/Basket%20Edit.ipynb) - 📄 [Basket Rebalance.ipynb](06_baskets/tutorials/Basket%20Rebalance.ipynb) - 📁 **07_index** - 📄 [README.md](07_index/README.md) - 📁 examples - 📄 [0000_get_index_Marquee_url.ipynb](07_index/examples/0000_get_index_Marquee_url.ipynb) - 📄 [0001_get_index_close_prices.ipynb](07_index/examples/0001_get_index_close_prices.ipynb) - 📄 [0002_get_index_fundamentals_data.ipynb](07_index/examples/0002_get_index_fundamentals_data.ipynb) - 📄 [0003_get_index_constituents.ipynb](07_index/examples/0003_get_index_constituents.ipynb) - 📄 [0004_get_index_weights_and_attribution.ipynb](07_index/examples/0004_get_index_weights_and_attribution.ipynb) - 📄 [0005_get_index_as_a_tree.ipynb](07_index/examples/0005_get_index_as_a_tree.ipynb) - 📁 tutorials - 📄 [GS Quant STS Index Tutorial.ipynb](07_index/tutorials/GS%20Quant%20STS%20Index%20Tutorial.ipynb) - 📁 **08_tree_entity** - 📄 [tree_entity.ipynb](08_tree_entity/tree_entity.ipynb) - 📁 **09_security_master** - 📄 [Security Master SDK.ipynb](09_security_master/Security%20Master%20SDK.ipynb) - 📄 [Security Master.ipynb](09_security_master/Security%20Master.ipynb) - 📁 **10_one_delta** - 📁 Hedger - 📄 [01_Performance_Hedger.ipynb](10_one_delta/Hedger/01_Performance_Hedger.ipynb) - 📄 [02_Factor_Hedger_(Axioma_Portfolio_Optimizer).ipynb](10_one_delta/Hedger/02_Factor_Hedger_(Axioma_Portfolio_Optimizer).ipynb) - 📄 [03_Continuous_Optimization.ipynb](10_one_delta/Hedger/03_Continuous_Optimization.ipynb) - 📁 Portfolios - 📄 [01_Create_Backcasted_Portfolio.ipynb](10_one_delta/Portfolios/01_Create_Backcasted_Portfolio.ipynb) - 📄 [02_Create_New_Historical_Portfolio.ipynb](10_one_delta/Portfolios/02_Create_New_Historical_Portfolio.ipynb) - 📄 [03_Update_Historical_Portfolio.ipynb](10_one_delta/Portfolios/03_Update_Historical_Portfolio.ipynb) - 📄 [04_Get_Factor_Attribution_Data.ipynb](10_one_delta/Portfolios/04_Get_Factor_Attribution_Data.ipynb) - 📄 [05_Manage_a_Fund_of_Funds.ipynb](10_one_delta/Portfolios/05_Manage_a_Fund_of_Funds.ipynb) - 📄 [06_Get_Factor_Risk_Data.ipynb](10_one_delta/Portfolios/06_Get_Factor_Risk_Data.ipynb) - 📄 [07_Get_Portfolio_Performance_Analytics.ipynb](10_one_delta/Portfolios/07_Get_Portfolio_Performance_Analytics.ipynb) - 📄 [08_Get_ESG_Analytics.ipynb](10_one_delta/Portfolios/08_Get_ESG_Analytics.ipynb) - 📄 [09_Get_Carbon_Analytics.ipynb](10_one_delta/Portfolios/09_Get_Carbon_Analytics.ipynb) - 📄 [10_Get_Thematic_Analytics.ipynb](10_one_delta/Portfolios/10_Get_Thematic_Analytics.ipynb) - 📁 Reports - 📄 [01_Factor_Risk_Report.ipynb](10_one_delta/Reports/01_Factor_Risk_Report.ipynb) - 📄 [02_Performance_Report.ipynb](10_one_delta/Reports/02_Performance_Report.ipynb) - 📄 [03_Thematic_Report.ipynb](10_one_delta/Reports/03_Thematic_Report.ipynb) - 📁 **11_macro_models** - 📄 [01_Query_Macro_Models.ipynb](11_macro_models/01_Query_Macro_Models.ipynb) - 📄 [02_Upload_Macro_Models.ipynb](11_macro_models/02_Upload_Macro_Models.ipynb) - 📁 **12_scenarios** - 📄 [01_Custom_Factor_Shocks.ipynb](12_scenarios/01_Custom_Factor_Shocks.ipynb) - 📄 [02_Historical_Scenarios.ipynb](12_scenarios/02_Historical_Scenarios.ipynb) - 📄 [Contents.ipynb](Contents.ipynb) - 📄 [Index.ipynb](Index.ipynb) - 📄 [README.md](README.md) ================================================ FILE: gs_quant/documentation/git ================================================ ================================================ FILE: gs_quant/entities/__init__.py ================================================ ================================================ FILE: gs_quant/entities/entitlements.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import logging from typing import List, Dict import pandas as pd from pydash import get from gs_quant.api.gs.groups import GsGroupsApi from gs_quant.api.gs.users import GsUsersApi from gs_quant.common import Entitlements as TargetEntitlements from gs_quant.errors import MqValueError, MqRequestError from gs_quant.target.groups import Group as TargetGroup _logger = logging.getLogger(__name__) class User: def __init__(self, user_id: str, name: str = None, email: str = None, company: str = None): self.__id = user_id self.__email = email self.__name = name self.__company = company def __eq__(self, other): return self.id == other.id def __hash__(self): return hash(self.id) ^ hash(self.name) @property def id(self): return self.__id @property def email(self): return self.__email @property def name(self): return self.__name @property def company(self): return self.__company @classmethod def get(cls, user_id: str = None, name: str = None, email: str = None): """ Resolve a user ID, name, email, and/or company into a single User object :param user_id: User's unique GS Marquee User ID :param name: User's name (formatted 'Last Name, First Name') :param email: User's email address :return: A Marquee User object that corresponds to requested parameters """ if all(arg is None for arg in [user_id, name, email]): raise MqValueError('Please specify a user id, name, or email address') user_id = user_id[5:] if user_id and user_id.startswith('guid:') else user_id results = GsUsersApi.get_users( user_ids=[user_id] if user_id else None, user_names=[name] if name else None, user_emails=[email] if email else None, ) if len(results) > 1: raise MqValueError('Error: This request resolves to more than one user in Marquee') if len(results) == 0: raise MqValueError('Error: No user found') return User(user_id=results[0].id, name=results[0].name, email=results[0].email, company=results[0].company) @classmethod def get_many( cls, user_ids: List[str] = None, names: List[str] = None, emails: List[str] = None, companies: List[str] = None ): """ Resolve requested parameters into a list of User objects :param user_ids: User's unique GS Marquee User IDs :param names: User's names (formatted 'Last Name, First Name') :param emails: User's email addresses :param companies: User's companies :return: A list of User objects that corresponds to requested parameters """ user_ids = user_ids if user_ids else [] names = names if names else [] emails = [email.lower() for email in emails] if emails else [] companies = companies if companies else [] if not user_ids + names + emails + companies: return [] user_ids = [id_[5:] if id_.startswith('guid:') else id_ for id_ in user_ids] results = GsUsersApi.get_users( user_ids=user_ids, user_names=names, user_emails=emails, user_companies=companies ) all_users = [] for user in results: all_users.append(User(user_id=user.id, name=user.name, email=user.email, company=user.company)) return all_users def save(self): raise NotImplementedError class Group: def __init__(self, group_id: str, name: str, entitlements=None, description: str = None, tags: List = None): self.__id = group_id self.__name = name self.__entitlements = entitlements self.__description = description self.__tags = tags def __eq__(self, other): return self.id == other.id def __hash__(self): return hash(self.id) ^ hash(self.name) @property def id(self): return self.__id @property def name(self): return self.__name @name.setter def name(self, value: str): self.__name = value @property def entitlements(self): return self.__entitlements @entitlements.setter def entitlements(self, value): self.__entitlements = value @property def description(self): return self.__description @description.setter def description(self, value: str): self.__description = value @property def tags(self): return self.__tags @tags.setter def tags(self, value: List): self.__tags = value @classmethod def get(cls, group_id: str): """ Resolve a group ID into a single Group object :param group_id: Group's unique GS Marquee ID :return: A Group object that corresponds to requested ID """ group_id = group_id[6:] if group_id and group_id.startswith('group:') else group_id result = GsGroupsApi.get_group(group_id=group_id) return Group( group_id=result.id, name=result.name, entitlements=Entitlements.from_target(result.entitlements) if result.entitlements else None if result.entitlements else None, description=result.description, tags=result.tags, ) @classmethod def get_many(cls, group_ids: List[str] = None, names: List[str] = None): """ Resolve requested parameters into a list of Group objects :param group_ids: Group's unique GS Marquee IDs :param names: Group's names :return: A list of Group objects that corresponds to requested parameters """ group_ids = group_ids if group_ids else [] names = names if names else [] if not group_ids + names: return [] group_ids = [id_[6:] if id_.startswith('group:') else id_ for id_ in group_ids] results = GsGroupsApi.get_groups(ids=group_ids, names=names) all_groups = [] for group in results: all_groups.append( Group( group_id=group.id, name=group.name, entitlements=Entitlements.from_target(group.entitlements) if group.entitlements else None, description=group.description, tags=group.tags, ) ) return all_groups def save(self): """ If the group id already exists in Marquee, update it. If not, create a new Marquee Group """ if self._group_exists(): _logger.info(f'Updating group "{self.id}"') result = GsGroupsApi.update_group(group_id=self.id, group=self.to_target()) else: _logger.info(f'Creating group "{self.id}"') result = GsGroupsApi.create_group(group=self.to_target()) return Group( group_id=result.id, name=result.name, entitlements=Entitlements.from_target(result.entitlements) if result.entitlements else None if result.entitlements else None, description=result.description, tags=result.tags, ) def _group_exists(self): try: Group.get(self.id) return True except MqRequestError as e: if e.status == 404: return False else: raise e def delete(self): """ Delete this group from Marquee """ GsGroupsApi.delete_group(self.id) _logger.info(f'Group "{self.id}" deleted from Marquee.') def get_users(self) -> List[User]: """ Get a list of all users in this group """ users = GsGroupsApi.get_users_in_group(self.id) return [ User(user_id=user.get('id'), name=user.get('name'), email=user.get('email'), company=user.get('company')) for user in users ] def add_users(self, users: List[User]): """ Add a list of users to a group :param users: List of User objects """ user_ids = [user.id for user in users] GsGroupsApi.add_users_to_group(group_id=self.id, user_ids=user_ids) _logger.info(f'Users added to "{self.name}".') def delete_users(self, users: List[User]): """ Remove a list of users to a group :param users: List of User objects """ user_ids = [user.id for user in users] GsGroupsApi.delete_users_from_group(group_id=self.id, user_ids=user_ids) _logger.info(f'Users removed from "{self.name}".') def to_dict(self): """ Return a Group object as a dictionary """ return { 'name': self.name, 'id': self.id, 'description': self.description, 'entitlements': self.entitlements.to_dict() if self.entitlements else None, 'tags': self.tags, } def to_target(self): """ Return a Group object as a target object """ return TargetGroup( name=self.name, id=self.id, description=self.description, entitlements=self.entitlements.to_target() if self.entitlements else None, tags=self.tags, ) class EntitlementBlock: def __init__( self, users: List[User] = None, groups: List[Group] = None, roles: List[str] = None, unconverted_tokens: List[str] = None, ): self.__users = list(set(users)) if users else [] self.__groups = list(set(groups)) if groups else [] self.__roles = list(set(roles)) if roles else [] self.__unconverted_tokens = unconverted_tokens def __eq__(self, other) -> bool: if not isinstance(other, EntitlementBlock): return False for prop in ['users', 'groups', 'roles']: slf, oth = get(self, prop), get(other, prop) if not (slf is None and oth is None) and not slf == oth: return False return True @property def users(self): return self.__users @users.setter def users(self, value: List[User]): self.__users = list(set(value)) @property def groups(self): return self.__groups @groups.setter def groups(self, value: List[Group]): self.__groups = list(set(value)) @property def roles(self): return self.__roles @roles.setter def roles(self, value: List[str]): self.__roles = list(set(value)) @property def unconverted_tokens(self) -> List[str]: return self.__unconverted_tokens def is_empty(self): return len(self.users + self.groups + self.roles) == 0 def to_list(self, as_dicts: bool = False, action: str = None, include_all_tokens: bool = False): if as_dicts: all_entitled = [] for user in self.users: all_entitled.append(dict(action=action, type='user', name=user.name, id=user.id)) for group in self.groups: all_entitled.append(dict(action=action, type='group', name=group.name, id=group.id)) for role in self.roles: all_entitled.append(dict(action=action, type='role', name=role, id=role)) return all_entitled else: unconverted_tokens = self.unconverted_tokens or [] if include_all_tokens else [] return ( [f'guid:{user.id}' for user in self.users] + [f'group:{group.id}' for group in self.groups] + [f'role:{role}' for role in self.roles] + unconverted_tokens ) class Entitlements: def __init__( self, admin: EntitlementBlock = None, delete: EntitlementBlock = None, display: EntitlementBlock = None, upload: EntitlementBlock = None, edit: EntitlementBlock = None, execute: EntitlementBlock = None, plot: EntitlementBlock = None, query: EntitlementBlock = None, rebalance: EntitlementBlock = None, trade: EntitlementBlock = None, view: EntitlementBlock = None, ): self.__admin = admin if admin else EntitlementBlock() self.__delete = delete if delete else EntitlementBlock() self.__display = display if display else EntitlementBlock() self.__upload = upload if upload else EntitlementBlock() self.__edit = edit if edit else EntitlementBlock() self.__execute = execute if execute else EntitlementBlock() self.__plot = plot if plot else EntitlementBlock() self.__query = query if query else EntitlementBlock() self.__rebalance = rebalance if rebalance else EntitlementBlock() self.__trade = trade if trade else EntitlementBlock() self.__view = view if view else EntitlementBlock() def __eq__(self, other) -> bool: if not isinstance(other, Entitlements): return False for prop in [ 'admin', 'delete', 'display', 'upload', 'edit', 'execute', 'plot', 'query', 'rebalance', 'view', 'trade', ]: slf, oth = get(self, prop), get(other, prop) if not (slf is None and oth is None) and not slf == oth: return False return True @property def admin(self): return self.__admin @admin.setter def admin(self, value: EntitlementBlock): self.__admin = value @property def delete(self): return self.__delete @delete.setter def delete(self, value: EntitlementBlock): self.__delete = value @property def display(self): return self.__display @display.setter def display(self, value: EntitlementBlock): self.__display = value @property def upload(self): return self.__upload @upload.setter def upload(self, value: EntitlementBlock): self.__upload = value @property def edit(self): return self.__edit @edit.setter def edit(self, value: EntitlementBlock): self.__edit = value @property def execute(self): return self.__execute @execute.setter def execute(self, value: EntitlementBlock): self.__execute = value @property def plot(self): return self.__plot @plot.setter def plot(self, value: EntitlementBlock): self.__plot = value @property def query(self): return self.__query @query.setter def query(self, value: EntitlementBlock): self.__query = value @property def rebalance(self): return self.__rebalance @rebalance.setter def rebalance(self, value: EntitlementBlock): self.__rebalance = value @property def trade(self): return self.__trade @trade.setter def trade(self, value: EntitlementBlock): self.__trade = value @property def view(self): return self.__view @view.setter def view(self, value: EntitlementBlock): self.__view = value def to_target(self, include_all_tokens: bool = False) -> TargetEntitlements: """ Return Entitlement object as a target object :return: Entitlements as a target object """ target_entitlements = TargetEntitlements.default_instance() if not self.admin.is_empty(): target_entitlements.admin = self.admin.to_list(include_all_tokens=include_all_tokens) if not self.delete.is_empty(): target_entitlements.delete = self.delete.to_list(include_all_tokens=include_all_tokens) if not self.display.is_empty(): target_entitlements.display = self.display.to_list(include_all_tokens=include_all_tokens) if not self.upload.is_empty(): target_entitlements.upload = self.upload.to_list(include_all_tokens=include_all_tokens) if not self.edit.is_empty(): target_entitlements.edit = self.edit.to_list(include_all_tokens=include_all_tokens) if not self.execute.is_empty(): target_entitlements.execute = self.execute.to_list(include_all_tokens=include_all_tokens) if not self.plot.is_empty(): target_entitlements.plot = self.plot.to_list(include_all_tokens=include_all_tokens) if not self.query.is_empty(): target_entitlements.query = self.query.to_list(include_all_tokens=include_all_tokens) if not self.rebalance.is_empty(): target_entitlements.rebalance = self.rebalance.to_list(include_all_tokens=include_all_tokens) if not self.trade.is_empty(): target_entitlements.trade = self.trade.to_list(include_all_tokens=include_all_tokens) if not self.view.is_empty(): target_entitlements.view = self.view.to_list(include_all_tokens=include_all_tokens) return target_entitlements def to_dict(self) -> Dict: """ Return Entitlement object as a dictionary :return: Entitlements as a dictionary """ return self.to_target().as_dict() def to_frame(self) -> pd.DataFrame: all_entitled = [] all_entitled += self.admin.to_list(True, 'admin') all_entitled += self.delete.to_list(True, 'delete') all_entitled += self.display.to_list(True, 'display') all_entitled += self.upload.to_list(True, 'upload') all_entitled += self.edit.to_list(True, 'edit') all_entitled += self.execute.to_list(True, 'execute') all_entitled += self.plot.to_list(True, 'plot') all_entitled += self.query.to_list(True, 'query') all_entitled += self.rebalance.to_list(True, 'rebalance') all_entitled += self.trade.to_list(True, 'trade') all_entitled += self.view.to_list(True, 'view') return pd.DataFrame(all_entitled) @classmethod def from_target(cls, entitlements: TargetEntitlements): """ Create an Entitlement object from a target object :param entitlements: Entitlements as a target object :return: A new Entitlements object with all specified entitlements """ entitlements = TargetEntitlements.default_instance() if entitlements is None else entitlements return cls.from_dict(entitlements.as_dict()) @classmethod def from_dict(cls, entitlements: Dict): """ Create an Entitlement object from a dictionary object :param entitlements: Entitlements as a dictionary object :return: A new Entitlements object with all specified entitlements """ entitlement_kwargs, token_map = {}, {} user_ids, group_ids = set(), set() for token_set in entitlements.values(): for t in token_set: if t.startswith('guid:'): user_ids.add(t) elif t.startswith('group:'): group_ids.add(t) elif t.startswith('role:'): token_map[t] = t[5:] all_users = User.get_many(user_ids=list(user_ids)) all_groups = Group.get_many(group_ids=list(group_ids)) for u in all_users: token_map[f'guid:{u.id}'] = u for g in all_groups: token_map[f'group:{g.id}'] = g for action, token_set in entitlements.items(): users, groups, roles, unconverted_tokens = [], [], [], [] for t in token_set: if t in token_map.keys(): if t in user_ids: users.append(token_map.get(t)) elif t in group_ids: groups.append(token_map.get(t)) else: roles.append(token_map.get(t)) else: unconverted_tokens.append(t) unconverted_tokens = unconverted_tokens if len(unconverted_tokens) else None if users or groups or roles or unconverted_tokens: entitlement_kwargs[action] = EntitlementBlock( users=users, groups=groups, roles=roles, unconverted_tokens=unconverted_tokens ) return Entitlements(**entitlement_kwargs) ================================================ FILE: gs_quant/entities/entity.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import logging import time from abc import ABCMeta, abstractmethod from dataclasses import dataclass from enum import Enum from typing import Dict, List, Optional, Tuple, Union import deprecation import pandas as pd from pydash import get from gs_quant.api.gs.assets import GsAssetApi from gs_quant.api.gs.carbon import ( CarbonCard, GsCarbonApi, CarbonTargetCoverageCategory, CarbonScope, CarbonEmissionsAllocationCategory, CarbonEmissionsIntensityType, CarbonCoverageCategory, CarbonEntityType, CarbonAnalyticsView, ) from gs_quant.api.gs.data import GsDataApi from gs_quant.api.gs.esg import ESGMeasure, GsEsgApi, ESGCard from gs_quant.api.gs.indices import GsIndexApi from gs_quant.api.gs.portfolios import GsPortfolioApi from gs_quant.api.gs.reports import GsReportApi from gs_quant.api.gs.thematics import ThematicMeasure, GsThematicApi, Region from gs_quant.api.gs.scenarios import GsFactorScenarioApi from gs_quant.common import DateLimit, PositionType, Currency from gs_quant.data import DataCoordinate, DataFrequency, DataMeasure from gs_quant.data.coordinate import DataDimensions from gs_quant.entities.entitlements import Entitlements from gs_quant.errors import MqError, MqValueError from gs_quant.markets.indices_utils import BasketType, IndicesDatasets from gs_quant.markets.position_set import PositionSet from gs_quant.markets.report import ( PerformanceReport, FactorRiskReport, Report, ThematicReport, flatten_results_into_df, get_thematic_breakdown_as_df, ReturnFormat, ) from gs_quant.markets.scenario import Scenario from gs_quant.session import GsSession from gs_quant.target.data import DataQuery from gs_quant.target.reports import ReportStatus, ReportType from gs_quant.entities.entity_utils import _explode_data _logger = logging.getLogger(__name__) class EntityType(Enum): ASSET = 'asset' BACKTEST = 'backtest' COUNTRY = 'country' HEDGE = 'hedge' KPI = 'kpi' PORTFOLIO = 'portfolio' REPORT = 'report' RISK_MODEL = 'risk_model' SUBDIVISION = 'subdivision' DATASET = 'dataset' SCENARIO = 'scenario' @dataclass class EntityKey: id_: str entity_type: EntityType class EntityIdentifier(Enum): pass class ScenarioCalculationType(Enum): FACTOR_SCENARIO = "Factor Scenario" class ScenarioCalculationMeasure(Enum): SUMMARY = "Summary" ESTIMATED_FACTOR_PNL = "Factor Pnl" ESTIMATED_PNL_BY_SECTOR = "By Sector Pnl Aggregations" ESTIMATED_PNL_BY_REGION = "By Region Pnl Aggregations" ESTIMATED_PNL_BY_DIRECTION = "By Direction Pnl Aggregations" ESTIMATED_PNL_BY_ASSET = "By Asset Pnl" class Entity(metaclass=ABCMeta): """Base class for any first-class entity""" _entity_to_endpoint = { EntityType.ASSET: 'assets', EntityType.COUNTRY: 'countries', EntityType.SUBDIVISION: 'countries/subdivisions', EntityType.KPI: 'kpis', EntityType.PORTFOLIO: 'portfolios', EntityType.RISK_MODEL: 'risk/models', EntityType.DATASET: 'data/datasets', } def __init__(self, id_: str, entity_type: EntityType, entity: Optional[Dict] = None): self.__id: str = id_ self.__entity_type: EntityType = entity_type self.__entity: Dict = entity @property @abstractmethod def data_dimension(self) -> str: pass @classmethod @abstractmethod def entity_type(cls) -> EntityType: pass @classmethod def get( cls, id_value: str, id_type: Union[EntityIdentifier, str], entity_type: Optional[Union[EntityType, str]] = None ): id_type = id_type.value if isinstance(id_type, Enum) else id_type if entity_type is None: entity_type = cls.entity_type() endpoint = cls._entity_to_endpoint[entity_type] else: entity_type = entity_type.value if isinstance(entity_type, Enum) else entity_type endpoint = cls._entity_to_endpoint[EntityType(entity_type)] if entity_type == 'asset': from gs_quant.markets.securities import SecurityMaster, AssetIdentifier return SecurityMaster.get_asset(id_value, AssetIdentifier.MARQUEE_ID) if id_type == 'MQID': result = GsSession.current.sync.get(f'/{endpoint}/{id_value}') else: result = get(GsSession.current.sync.get(f'/{endpoint}?{id_type.lower()}={id_value}'), 'results.0') if result: return cls._get_entity_from_type(result, EntityType(entity_type)) @classmethod def _get_entity_from_type(cls, entity: Dict, entity_type: EntityType = None): id_ = entity.get('id') entity_type = entity_type or cls.entity_type() if entity_type == EntityType.COUNTRY: return Country(id_, entity=entity) if entity_type == EntityType.KPI: return KPI(id_, entity=entity) if entity_type == EntityType.SUBDIVISION: return Subdivision(id_, entity=entity) if entity_type == EntityType.RISK_MODEL: return RiskModelEntity(id_, entity=entity) def get_marquee_id(self) -> str: return self.__id def get_entity(self) -> Optional[Dict]: return self.__entity def get_unique_entity_key(self) -> EntityKey: return EntityKey(self.get_marquee_id(), self.__entity_type) def get_data_coordinate( self, measure: Union[DataMeasure, str], dimensions: Optional[DataDimensions] = None, frequency: DataFrequency = DataFrequency.DAILY, availability=None, ) -> DataCoordinate: id_ = self.get_marquee_id() dimensions = dimensions or {} dimensions[self.data_dimension] = id_ measure = measure if isinstance(measure, str) else measure.value available: Dict = GsDataApi.get_data_providers(id_, availability).get(measure, {}) if frequency == DataFrequency.DAILY: daily_dataset_id = available.get(DataFrequency.DAILY) return DataCoordinate( dataset_id=daily_dataset_id, measure=measure, dimensions=dimensions, frequency=frequency ) if frequency == DataFrequency.REAL_TIME: rt_dataset_id = available.get(DataFrequency.REAL_TIME) return DataCoordinate(dataset_id=rt_dataset_id, measure=measure, dimensions=dimensions, frequency=frequency) def get_entitlements(self): entitlements_dict = self.get_entity().get('entitlements') if entitlements_dict is None: raise ValueError('This entity does not have entitlements.') return Entitlements.from_dict(entitlements_dict) class Country(Entity): class Identifier(EntityIdentifier): MARQUEE_ID = 'MQID' NAME = 'name' def __init__(self, id_: str, entity: Optional[Dict] = None): super().__init__(id_, EntityType.COUNTRY, entity) @property def data_dimension(self) -> str: return 'countryId' @classmethod def entity_type(cls) -> EntityType: return EntityType.COUNTRY @classmethod def get_by_identifier(cls, id_value: str, id_type: Identifier) -> Optional['Entity']: super().get(id_value, id_type) def get_name(self) -> Optional[str]: return get(self.get_entity(), 'name') def get_region(self) -> Optional[str]: return get(self.get_entity(), 'region') def get_sub_region(self): return get(self.get_entity(), 'subRegion') def get_region_code(self): return get(self.get_entity(), 'regionCode') def get_sub_region_code(self): return get(self.get_entity(), 'subRegionCode') def get_alpha3(self): return get(self.get_entity(), 'xref.alpha3') def get_bbid(self): return get(self.get_entity(), 'xref.bbid') def get_alpha2(self): return get(self.get_entity(), 'xref.alpha2') def get_country_code(self): return get(self.get_entity(), 'xref.countryCode') class Subdivision(Entity): class Identifier(EntityIdentifier): MARQUEE_ID = 'MQID' name = 'name' def __init__(self, id_: str, entity: Optional[Dict] = None): super().__init__(id_, EntityType.SUBDIVISION, entity) @property def data_dimension(self) -> str: return 'subdivisionId' @classmethod def entity_type(cls) -> EntityType: return EntityType.SUBDIVISION @classmethod def get_by_identifier(cls, id_value: str, id_type: Identifier) -> Optional['Entity']: super().get(id_value, id_type) def get_name(self) -> Optional[str]: return get(self.get_entity(), 'name') class KPI(Entity): class Identifier(EntityIdentifier): MARQUEE_ID = "MQID" name = 'name' def __init__(self, id_: str, entity: Optional[Dict] = None): super().__init__(id_, EntityType.KPI, entity) @property def data_dimension(self) -> str: return 'kpiId' @classmethod def entity_type(cls) -> EntityType: return EntityType.KPI @classmethod def get_by_identifier(cls, id_value: str, id_type: Identifier) -> Optional['Entity']: super().get(id_value, id_type) def get_name(self) -> Optional[str]: return get(self.get_entity(), 'name') def get_category(self) -> Optional[str]: return get(self.get_entity(), 'category') def get_sub_category(self): return get(self.get_entity(), 'subCategory') class RiskModelEntity(Entity): class Identifier(EntityIdentifier): MARQUEE_ID = "MQID" name = 'name' def __init__(self, id_: str, entity: Optional[Dict] = None): super().__init__(id_, EntityType.RISK_MODEL, entity) @property def data_dimension(self) -> str: return 'riskModel' @classmethod def entity_type(cls) -> EntityType: return EntityType.RISK_MODEL @classmethod def get_by_identifier(cls, id_value: str, id_type: Identifier) -> Optional['Entity']: super().get(id_value, id_type) def get_name(self) -> Optional[str]: return get(self.get_entity(), 'name') def get_coverage(self) -> Optional[str]: return get(self.get_entity(), 'coverage') def get_term(self) -> Optional[str]: return get(self.get_entity(), 'term') def get_vendor(self) -> Optional[str]: return get(self.get_entity(), 'vendor') class PositionedEntity(metaclass=ABCMeta): def __init__(self, id_: str, entity_type: EntityType): self.__id: str = id_ self.__entity_type: EntityType = entity_type @property def id(self) -> str: return self.__id @property def positioned_entity_type(self) -> EntityType: return self.__entity_type def get_entitlements(self) -> Entitlements: if self.positioned_entity_type == EntityType.PORTFOLIO: response = GsPortfolioApi.get_portfolio(self.id) elif self.positioned_entity_type == EntityType.ASSET: response = GsAssetApi.get_asset(self.id) else: raise NotImplementedError return Entitlements.from_target(response.entitlements) def get_latest_position_set(self, position_type: PositionType = PositionType.CLOSE) -> PositionSet: if self.positioned_entity_type == EntityType.ASSET: response = GsAssetApi.get_latest_positions(self.id, position_type) return PositionSet.from_target(response) if self.positioned_entity_type == EntityType.PORTFOLIO: response = GsPortfolioApi.get_latest_positions(portfolio_id=self.id, position_type=position_type.value) return PositionSet.from_target(response) raise NotImplementedError def get_position_set_for_date(self, date: dt.date, position_type: PositionType = PositionType.CLOSE) -> PositionSet: if self.positioned_entity_type == EntityType.ASSET: response = GsAssetApi.get_asset_positions_for_date(self.id, date, position_type) if len(response) == 0: _logger.info("No positions available for {}".format(date)) return PositionSet([], date=date) return PositionSet.from_target(response[0]) if self.positioned_entity_type == EntityType.PORTFOLIO: response = GsPortfolioApi.get_positions_for_date( portfolio_id=self.id, position_date=date, position_type=position_type.value ) return PositionSet.from_target(response) if response else None raise NotImplementedError def get_position_sets( self, start: dt.date = DateLimit.LOW_LIMIT.value, end: dt.date = dt.date.today(), position_type: PositionType = PositionType.CLOSE, ) -> List[PositionSet]: if self.positioned_entity_type == EntityType.ASSET: response = GsAssetApi.get_asset_positions_for_dates(self.id, start, end, position_type) if len(response) == 0: _logger.info("No positions available in the date range {} - {}".format(start, end)) return [] return [PositionSet.from_target(position_set) for position_set in response] if self.positioned_entity_type == EntityType.PORTFOLIO: response = GsPortfolioApi.get_positions(portfolio_id=self.id, start_date=start, end_date=end) return [PositionSet.from_target(position_set) for position_set in response] raise NotImplementedError def update_positions(self, position_sets: List[PositionSet], net_positions: bool = True): if self.positioned_entity_type == EntityType.PORTFOLIO: if not position_sets: return currency = GsPortfolioApi.get_portfolio(self.id).currency new_sets = [] for pos_set in position_sets: positions_are_missing_quantities = len([p for p in pos_set.positions if p.quantity is None]) > 0 if positions_are_missing_quantities: pos_set.price(currency) new_sets.append(pos_set) GsPortfolioApi.update_positions( portfolio_id=self.id, position_sets=[p.to_target() for p in new_sets], net_positions=net_positions ) time.sleep(3) else: raise NotImplementedError def get_positions_data( self, start: dt.date = DateLimit.LOW_LIMIT.value, end: dt.date = dt.date.today(), fields: [str] = None, position_type: PositionType = PositionType.CLOSE, ) -> List[Dict]: if self.positioned_entity_type == EntityType.ASSET: return GsIndexApi.get_positions_data(self.id, start, end, fields, position_type) if self.positioned_entity_type == EntityType.PORTFOLIO: raise MqError( 'Please use the get_positions_data function on the portfolio performance report using the ' 'PerformanceReport class' ) raise NotImplementedError def get_last_positions_data( self, fields: [str] = None, position_type: PositionType = PositionType.CLOSE ) -> List[Dict]: if self.positioned_entity_type == EntityType.ASSET: return GsIndexApi.get_last_positions_data(self.id, fields, position_type) if self.positioned_entity_type == EntityType.PORTFOLIO: raise MqError( 'Please use the get_positions_data function on the portfolio performance report using the ' 'PerformanceReport class' ) raise NotImplementedError def get_position_dates(self) -> Tuple[dt.date, ...]: if self.positioned_entity_type == EntityType.PORTFOLIO: return GsPortfolioApi.get_position_dates(portfolio_id=self.id) if self.positioned_entity_type == EntityType.ASSET: return GsAssetApi.get_position_dates(asset_id=self.id) raise NotImplementedError def get_reports(self, tags: Dict = None) -> List[Report]: if self.positioned_entity_type == EntityType.PORTFOLIO: reports_as_target = GsPortfolioApi.get_reports(portfolio_id=self.id, tags=tags) elif self.positioned_entity_type == EntityType.ASSET: reports_as_target = GsAssetApi.get_reports(asset_id=self.id) else: raise NotImplementedError report_objects = [] for report in reports_as_target: if report.type == ReportType.Portfolio_Performance_Analytics: report_objects.append(PerformanceReport.from_target(report)) elif report.type in [ReportType.Portfolio_Factor_Risk, ReportType.Asset_Factor_Risk]: report_objects.append(FactorRiskReport.from_target(report)) elif report.type in [ReportType.Portfolio_Thematic_Analytics, ReportType.Asset_Thematic_Analytics]: report_objects.append(ThematicReport.from_target(report)) else: report_objects.append(Report.from_target(report)) return report_objects def get_status_of_reports(self, tags: Dict = None) -> pd.DataFrame: reports = self.get_reports(tags) reports_dict = { 'Name': [r.name for r in reports], 'ID': [r.id for r in reports], 'Latest Execution Time': [r.latest_execution_time for r in reports], 'Latest End Date': [r.latest_end_date for r in reports], "Status": [r.status for r in reports], 'Percentage Complete': [r.percentage_complete for r in reports], } return pd.DataFrame.from_dict(reports_dict) def get_factor_risk_reports(self, fx_hedged: bool = None, tags: Dict = None) -> List[FactorRiskReport]: if self.positioned_entity_type in [EntityType.PORTFOLIO, EntityType.ASSET]: position_source_type = self.positioned_entity_type.value.capitalize() reports = GsReportApi.get_reports( limit=100, position_source_type=position_source_type, position_source_id=self.id, report_type=f'{position_source_type} Factor Risk', tags=tags, scroll='1m', ) if fx_hedged: reports = [report for report in reports if report.parameters.fx_hedged == fx_hedged] if len(reports) == 0: raise MqError(f'This {position_source_type} has no factor risk reports that match your parameters.') return [FactorRiskReport.from_target(report) for report in reports] raise NotImplementedError def get_factor_risk_report( self, risk_model_id: str = None, fx_hedged: bool = None, benchmark_id: str = None, tags: Dict = None ) -> FactorRiskReport: position_source_type = self.positioned_entity_type.value.capitalize() reports = self.get_factor_risk_reports(fx_hedged=fx_hedged, tags=tags) if risk_model_id: reports = [report for report in reports if report.parameters.risk_model == risk_model_id] reports = [report for report in reports if report.parameters.benchmark == benchmark_id] if len(reports) == 0: raise MqError( f'This {position_source_type} has no factor risk reports that match ' 'your parameters. Please edit the risk model ID, fxHedged, and/or benchmark value in the ' 'function parameters.' ) if len(reports) > 1: raise MqError( f'This {position_source_type} has more than one factor risk report that matches ' 'your parameters. Please specify the risk model ID, fxHedged, and/or benchmark value in the ' 'function parameters.' ) return reports[0] def get_thematic_report(self, tags: Dict = None) -> ThematicReport: if self.positioned_entity_type in [EntityType.PORTFOLIO, EntityType.ASSET]: position_source_type = self.positioned_entity_type.value.capitalize() reports = GsReportApi.get_reports( limit=100, position_source_type=position_source_type, position_source_id=self.id, report_type=f'{position_source_type} Thematic Analytics', tags=tags, ) reports = [report for report in reports if report.parameters.tags == tags] if len(reports) == 0: raise MqError(f'This {position_source_type} has no thematic analytics report.') return ThematicReport.from_target(reports[0]) raise NotImplementedError def poll_report(self, report_id: str, timeout: int = 600, step: int = 30) -> ReportStatus: poll = True timeout = 1800 if timeout > 1800 else timeout step = 15 if step < 15 else step end = dt.datetime.now() + dt.timedelta(seconds=timeout) while poll and dt.datetime.now() <= end: try: status = Report.get(report_id).status if status not in {ReportStatus.error, ReportStatus.cancelled, ReportStatus.done}: _logger.info(f'Report is {status} as of {dt.datetime.now().isoformat()}') time.sleep(step) else: poll = False if status == ReportStatus.error: raise MqError( f'Report {report_id} has failed for {self.id}. \ Please reach out to the Marquee team for assistance.' ) elif status == ReportStatus.cancelled: _logger.info( f'Report {report_id} has been cancelled. Please reach out to the \ Marquee team if you believe this is a mistake.' ) return status else: _logger.info(f'Report {report_id} is now complete') return status except Exception as err: raise MqError(f'Could not fetch report status with error {err}') raise MqError( 'The report is taking longer than expected to complete. \ Please check again later or reach out to the Marquee team for assistance.' ) def get_all_esg_data( self, measures: List[ESGMeasure] = None, cards: List[ESGCard] = None, pricing_date: dt.date = None, benchmark_id: str = None, ) -> Dict: """ Get all ESG Data :param measures: list of ESG Measures to include in results :param cards: list of ESG Cards to include in results :param pricing_date: optional pricing date; defaults to last previous business day :param benchmark_id: optional benchmark asset ID to include in results :return: a dictionary of results """ return GsEsgApi.get_esg( entity_id=self.id, pricing_date=pricing_date, cards=cards if cards else [c for c in ESGCard], measures=measures if measures else [m for m in ESGMeasure], benchmark_id=benchmark_id, ) def get_esg_summary(self, pricing_date: dt.date = None) -> pd.DataFrame: summary_data = GsEsgApi.get_esg(entity_id=self.id, pricing_date=pricing_date, cards=[ESGCard.SUMMARY]).get( 'summary' ) return pd.DataFrame(summary_data) def get_esg_quintiles(self, measure: ESGMeasure, pricing_date: dt.date = None) -> pd.DataFrame: """ Get breakdown of entity by weight in each percentile quintile for requested ESG measure :param measure: ESG Measure :param pricing_date: optional pricing date; defaults to last previous business day :return: a Pandas DataFrame with results """ quintile_data = ( GsEsgApi.get_esg( entity_id=self.id, pricing_date=pricing_date, cards=[ESGCard.QUINTILES], measures=[measure] ) .get('quintiles')[0] .get('results') ) df = pd.DataFrame(quintile_data) return df.filter(items=['description', 'gross', 'long', 'short']) def get_esg_by_sector(self, measure: ESGMeasure, pricing_date: dt.date = None) -> pd.DataFrame: """ Get breakdown of entity by sector, along with the weighted average score of the compositions in each sector :param measure: ESG Measure :param pricing_date: optional pricing date; defaults to last previous business day :return: a Pandas DataFrame with results """ return self._get_esg_breakdown(ESGCard.MEASURES_BY_SECTOR, measure, pricing_date) def get_esg_by_region(self, measure: ESGMeasure, pricing_date: dt.date = None) -> pd.DataFrame: """ Get breakdown of entity by region, along with the weighted average score of the compositions in each region :param measure: ESG Measure :param pricing_date: optional pricing date; defaults to last previous business day :return: a Pandas DataFrame with results """ return self._get_esg_breakdown(ESGCard.MEASURES_BY_REGION, measure, pricing_date) def get_esg_top_ten(self, measure: ESGMeasure, pricing_date: dt.date = None): """ Get entity constituents with the ten highest ESG percentile values :param measure: ESG Measure :param pricing_date: optional pricing date; defaults to last previous business day :return: a Pandas DataFrame with results """ return self._get_esg_ranked_card(ESGCard.TOP_TEN_RANKED, measure, pricing_date) def get_esg_bottom_ten(self, measure: ESGMeasure, pricing_date: dt.date = None) -> pd.DataFrame: """ Get entity constituents with the ten lowest ESG percentile values :param measure: ESG Measure :param pricing_date: optional pricing date; defaults to last previous business day :return: a Pandas DataFrame with results """ return self._get_esg_ranked_card(ESGCard.BOTTOM_TEN_RANKED, measure, pricing_date) def _get_esg_ranked_card(self, card: ESGCard, measure: ESGMeasure, pricing_date: dt.date = None) -> pd.DataFrame: data = ( GsEsgApi.get_esg(entity_id=self.id, pricing_date=pricing_date, cards=[card], measures=[measure]) .get(card.value)[0] .get('results') ) return pd.DataFrame(data) def _get_esg_breakdown(self, card: ESGCard, measure: ESGMeasure, pricing_date: dt.date = None) -> pd.DataFrame: sector_data = ( GsEsgApi.get_esg(entity_id=self.id, pricing_date=pricing_date, cards=[card], measures=[measure]) .get(card.value)[0] .get('results') ) return pd.DataFrame(sector_data) def get_carbon_analytics( self, benchmark_id: str = None, reporting_year: str = 'Latest', currency: Currency = None, include_estimates: bool = False, use_historical_data: bool = False, normalize_emissions: bool = False, cards: List[CarbonCard] = [c for c in CarbonCard], analytics_view: CarbonAnalyticsView = CarbonAnalyticsView.LONG, ) -> Dict: return GsCarbonApi.get_carbon_analytics( entity_id=self.id, benchmark_id=benchmark_id, reporting_year=reporting_year, currency=currency, include_estimates=include_estimates, use_historical_data=use_historical_data, normalize_emissions=normalize_emissions, cards=cards, analytics_view=analytics_view, ) def get_carbon_coverage( self, reporting_year: str = 'Latest', include_estimates: bool = False, use_historical_data: bool = False, coverage_category: CarbonCoverageCategory = CarbonCoverageCategory.WEIGHTS, analytics_view: CarbonAnalyticsView = CarbonAnalyticsView.LONG, ) -> pd.DataFrame: coverage = ( self.get_carbon_analytics( reporting_year=reporting_year, include_estimates=include_estimates, use_historical_data=use_historical_data, cards=[CarbonCard.COVERAGE], analytics_view=analytics_view, ) .get(CarbonCard.COVERAGE.value) .get(coverage_category.value, {}) .get(CarbonEntityType.PORTFOLIO.value, {}) ) return pd.DataFrame(coverage) def get_carbon_sbti_netzero_coverage( self, reporting_year: str = 'Latest', include_estimates: bool = False, use_historical_data: bool = False, target_coverage_category: CarbonTargetCoverageCategory = CarbonTargetCoverageCategory.PORTFOLIO_EMISSIONS, analytics_view: CarbonAnalyticsView = CarbonAnalyticsView.LONG, ) -> pd.DataFrame: coverage = ( self.get_carbon_analytics( reporting_year=reporting_year, include_estimates=include_estimates, use_historical_data=use_historical_data, cards=[CarbonCard.SBTI_AND_NET_ZERO_TARGETS], analytics_view=analytics_view, ) .get(CarbonCard.SBTI_AND_NET_ZERO_TARGETS.value) .get(target_coverage_category.value, {}) ) coverage = { target: target_coverage.get(CarbonEntityType.PORTFOLIO.value, {}) for target, target_coverage in coverage.items() } return pd.DataFrame(coverage) def get_carbon_emissions( self, currency: Currency = None, include_estimates: bool = False, use_historical_data: bool = False, normalize_emissions: bool = False, scope: CarbonScope = CarbonScope.TOTAL_GHG, analytics_view: CarbonAnalyticsView = CarbonAnalyticsView.LONG, ) -> pd.DataFrame: emissions = ( self.get_carbon_analytics( currency=currency, include_estimates=include_estimates, use_historical_data=use_historical_data, normalize_emissions=normalize_emissions, cards=[CarbonCard.EMISSIONS], analytics_view=analytics_view, ) .get(CarbonCard.EMISSIONS.value) .get(scope.value, {}) .get(CarbonEntityType.PORTFOLIO.value, []) ) return pd.DataFrame(emissions) def get_carbon_emissions_allocation( self, reporting_year: str = 'Latest', currency: Currency = None, include_estimates: bool = False, use_historical_data: bool = False, normalize_emissions: bool = False, scope: CarbonScope = CarbonScope.TOTAL_GHG, classification: CarbonEmissionsAllocationCategory = CarbonEmissionsAllocationCategory.GICS_SECTOR, analytics_view: CarbonAnalyticsView = CarbonAnalyticsView.LONG, ) -> pd.DataFrame: allocation = ( self.get_carbon_analytics( reporting_year=reporting_year, currency=currency, include_estimates=include_estimates, use_historical_data=use_historical_data, normalize_emissions=normalize_emissions, cards=[CarbonCard.ALLOCATIONS], analytics_view=analytics_view, ) .get(CarbonCard.ALLOCATIONS.value) .get(scope.value, {}) .get(CarbonEntityType.PORTFOLIO.value, {}) .get(classification.value) ) return pd.DataFrame(allocation).rename(columns={'name': classification.value}) def get_carbon_attribution_table( self, benchmark_id: str, reporting_year: str = 'Latest', currency: Currency = None, include_estimates: bool = False, use_historical_data: bool = False, scope: CarbonScope = CarbonScope.TOTAL_GHG, intensity_metric: CarbonEmissionsIntensityType = CarbonEmissionsIntensityType.EI_ENTERPRISE_VALUE, analytics_view: CarbonAnalyticsView = CarbonAnalyticsView.LONG, ) -> pd.DataFrame: attribution = ( self.get_carbon_analytics( benchmark_id=benchmark_id, reporting_year=reporting_year, currency=currency, include_estimates=include_estimates, use_historical_data=use_historical_data, cards=[CarbonCard.ATTRIBUTION], analytics_view=analytics_view, ) .get(CarbonCard.ATTRIBUTION.value) .get(scope.value, []) ) attribution_table = [] for entry in attribution: new_entry = { 'sector': entry.get('sector'), 'weightPortfolio': entry.get('weightPortfolio'), 'weightBenchmark': entry.get('weightBenchmark'), 'weightComparison': entry.get('weightComparison'), } new_entry.update(entry.get(intensity_metric.value, {})) attribution_table.append(new_entry) return pd.DataFrame(attribution_table) def get_thematic_exposure( self, basket_identifier: str, notional: int = 10000000, start: dt.date = DateLimit.LOW_LIMIT.value, end: dt.date = dt.date.today(), ) -> pd.DataFrame: if not self.positioned_entity_type == EntityType.ASSET: raise NotImplementedError response = GsAssetApi.resolve_assets(identifier=[basket_identifier], fields=['id', 'type'], limit=1)[ basket_identifier ] _id, _type = get(response, '0.id'), get(response, '0.type') if len(response) == 0 or _id is None: raise MqValueError(f'Basket could not be found using identifier {basket_identifier}.') if _type not in BasketType.to_list(): raise MqValueError(f'Asset {basket_identifier} of type {_type} is not a Custom or Research Basket.') query = DataQuery(where={'assetId': self.id, 'basketId': _id}, start_date=start, end_date=end) response = GsDataApi.query_data(query=query, dataset_id=IndicesDatasets.COMPOSITE_THEMATIC_BETAS.value) df = [] for r in response: df.append( { 'date': r['date'], 'assetId': r['assetId'], 'basketId': r['basketId'], 'thematicExposure': r['beta'] * notional, } ) df = pd.DataFrame(df) return df.set_index('date') def get_thematic_beta( self, basket_identifier: str, start: dt.date = DateLimit.LOW_LIMIT.value, end: dt.date = dt.date.today() ) -> pd.DataFrame: if not self.positioned_entity_type == EntityType.ASSET: raise NotImplementedError response = GsAssetApi.resolve_assets(identifier=[basket_identifier], fields=['id', 'type'], limit=1)[ basket_identifier ] _id, _type = get(response, '0.id'), get(response, '0.type') if len(response) == 0 or _id is None: raise MqValueError(f'Basket could not be found using identifier {basket_identifier}.') if _type not in BasketType.to_list(): raise MqValueError(f'Asset {basket_identifier} of type {_type} is not a Custom or Research Basket.') query = DataQuery(where={'assetId': self.id, 'basketId': _id}, start_date=start, end_date=end) response = GsDataApi.query_data(query=query, dataset_id=IndicesDatasets.COMPOSITE_THEMATIC_BETAS.value) df = [] for r in response: df.append( {'date': r['date'], 'assetId': r['assetId'], 'basketId': r['basketId'], 'thematicBeta': r['beta']} ) df = pd.DataFrame(df) return df.set_index('date') @deprecation.deprecated( deprecated_in="0.9.110", details="Please use the same function using the ThematicReport class" ) def get_all_thematic_exposures( self, start_date: dt.date = None, end_date: dt.date = None, basket_ids: List[str] = None, regions: List[Region] = None, ) -> pd.DataFrame: results = GsThematicApi.get_thematics( entity_id=self.id, start_date=start_date, end_date=end_date, basket_ids=basket_ids, regions=regions, measures=[ThematicMeasure.ALL_THEMATIC_EXPOSURES], ) return flatten_results_into_df(results) @deprecation.deprecated( deprecated_in="0.9.110", details="Please use the same function using the ThematicReport class" ) def get_top_five_thematic_exposures( self, start_date: dt.date = None, end_date: dt.date = None, basket_ids: List[str] = None, regions: List[Region] = None, ) -> pd.DataFrame: results = GsThematicApi.get_thematics( entity_id=self.id, start_date=start_date, end_date=end_date, basket_ids=basket_ids, regions=regions, measures=[ThematicMeasure.TOP_FIVE_THEMATIC_EXPOSURES], ) return flatten_results_into_df(results) @deprecation.deprecated( deprecated_in="0.9.110", details="Please use the same function using the ThematicReport class" ) def get_bottom_five_thematic_exposures( self, start_date: dt.date = None, end_date: dt.date = None, basket_ids: List[str] = None, regions: List[Region] = None, ) -> pd.DataFrame: results = GsThematicApi.get_thematics( entity_id=self.id, start_date=start_date, end_date=end_date, basket_ids=basket_ids, regions=regions, measures=[ThematicMeasure.BOTTOM_FIVE_THEMATIC_EXPOSURES], ) return flatten_results_into_df(results) def get_thematic_breakdown(self, date: dt.date, basket_id: str) -> pd.DataFrame: """ Get a by-asset breakdown of a portfolio or basket's thematic exposure to a particular flagship basket on a particular date :param date: date :param basket_id: GS flagship basket's unique Marquee ID :return: a Pandas DataFrame with results """ return get_thematic_breakdown_as_df(entity_id=self.id, date=date, basket_id=basket_id) def get_factor_scenario_analytics( self, scenarios: List[Scenario], date: dt.date, measures: List[ScenarioCalculationMeasure], risk_model: str = None, return_format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[Dict, Union[Dict, pd.DataFrame]]: """Given a list of factor scenarios (historical simulation and/or custom shocks), return the estimated pnl of the given positioned entity. :param scenarios: List of factor-based scenarios :param date: date to run scenarios. :param measures: which metrics to return :param risk_model: valid risk model ID :param return_format: whether to return data formatted in a dataframe or as a dict **Examples** >>> from gs_quant.session import GsSession, Environment >>> from gs_quant.markets.portfolio_manager import PortfolioManager, ReturnFormat >>> from gs_quant.entities.entity import ScenarioCalculationMeasure, PositionedEntity >>> from gs_quant.markets.scenario import Scenario Get scenarios >>> covid_19_omicron = Scenario.get_by_name("Covid 19 Omicron (v2)") # historical simulation >>> custom_shock = Scenario.get_by_name("Shocking factor by x% (Propagated)") # custom shock >>> risk_model = "RISK_MODEL_ID" # valid risk model ID Instantiate your positionedEntity. Here, we are using one of its subclasses, PortfolioManager >>> pm = PortfolioManager(portfolio_id="PORTFOLIO_ID") Set the date you wish to run your scenario on >>> date = dt.date(2023, 3, 7) Run scenario and get estimated impact on your positioned entity >>> scenario_analytics = pm.get_factor_scenario_analytics( ... scenarios=[covid_19_omicron, beta_propagated], ... date=date, ... measures=[ScenarioCalculationMeasure.SUMMARY, ... ScenarioCalculationMeasure.ESTIMATED_FACTOR_PNL, ... ScenarioCalculationMeasure.ESTIMATED_PNL_BY_SECTOR, ... ScenarioCalculationMeasure.ESTIMATED_PNL_BY_REGION, ... ScenarioCalculationMeasure.ESTIMATED_PNL_BY_DIRECTION, ... ScenarioCalculationMeasure.ESTIMATED_PNL_BY_ASSET], ... risk_model=risk_model) By default, the result will be returned in a dict with keys as the measures/metrics requested and values as the scenario calculation results formatted in a dataframe. To get the results in a dict, specify the return format as JSON """ risk_report = self.get_factor_risk_report(risk_model_id=risk_model) id_to_scenario_map = {scenario.id: scenario for scenario in scenarios} scenario_ids = list(id_to_scenario_map.keys()) calculation_request = { "date": date, "scenarioIds": scenario_ids, "reportId": risk_report.id, "measures": [m.value for m in measures], "riskModel": risk_model, "type": "Factor Scenario", } results = GsFactorScenarioApi.calculate_scenario(calculation_request) scenarios = [id_to_scenario_map.get(sc_id) for sc_id in results.get('scenarios')] calculation_results = results.get('results') if return_format == ReturnFormat.JSON: return dict(zip(scenarios, calculation_results)) result = {} all_data = {} [ all_data.update({result_type: []}) for result_type in [ 'summary', 'factorPnl', 'bySectorAggregations', 'byRegionAggregations', 'byDirectionAggregations', 'byAsset', ] ] for i, calc_result in enumerate(calculation_results): for result_type in [ 'summary', 'factorPnl', 'bySectorAggregations', 'byRegionAggregations', 'byDirectionAggregations', 'byAsset', ]: scenario_metadata_map = { "scenarioId": scenarios[i].id, "scenarioName": scenarios[i].name, "scenarioType": scenarios[i].type.value, } if result_type == 'summary': calc_result.get(result_type).update(scenario_metadata_map) all_data.get(result_type).append(calc_result.get(result_type)) else: [data_map.update(scenario_metadata_map) for data_map in calc_result.get(result_type, [])] [all_data.get(result_type).append(element) for element in calc_result.get(result_type, [])] for result_type, result_label in { "summary": "summary", "factorPnl": "factorCategories", "bySectorAggregations": "sectors", "byRegionAggregations": "countries", "byDirectionAggregations": "direction", "byAsset": "byAsset", }.items(): estimated_pnl_results_as_json = all_data.get(result_type) if estimated_pnl_results_as_json: estimated_pnl_df = pd.DataFrame.from_dict(estimated_pnl_results_as_json) estimated_pnl_df = estimated_pnl_df.apply(_explode_data, axis=1, parent_label=result_label) if isinstance(estimated_pnl_df, pd.Series): estimated_pnl_df = pd.concat(estimated_pnl_df.values, ignore_index=True) estimated_pnl_df.columns = estimated_pnl_df.columns.map( lambda x: { "factorCategories": "Factor Category", "factors": "Factor", "sectors": "Sector", "countries": "Country", "industries": "Industry", "direction": "Direction", "scenarioId": "Scenario ID", "scenarioName": "Scenario Name", "scenarioType": "Scenario Type", "assetId": "Asset ID", "name": "Asset Name", "bbid": "BBID", "factorExposure": "Factor Exposure", "factorShock": "Factor Shock (%)", "exposure": "Exposure", "estimatedPnl": "Estimated Pnl", "estimatedPerformance": "Estimated Performance (%)", "stressedMarketValue": "Stressed Market Value", }.get(x, x) ) column_order = [ col for col in [ "Scenario ID", "Scenario Name", "Scenario Type", "Asset ID", "BBID", "Asset Name", "Factor Category", "Factor", "Factor Exposure", "Factor Shock (%)", "Sector", "Industry", "Country", "Direction", "Exposure", "Estimated Pnl", "Estimated Performance (%)", "Stressed Market Value", ] if col in estimated_pnl_df.columns.tolist() ] estimated_pnl_df = estimated_pnl_df[column_order] result[result_type] = estimated_pnl_df return result ================================================ FILE: gs_quant/entities/entity_utils.py ================================================ """ Copyright 2024 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from typing import Union import pandas as pd def _explode_data(data: pd.Series, parent_label: str) -> Union[pd.DataFrame, pd.Series]: parent_to_child_map = { "factorCategories": "factors", "factors": "byAsset", "sectors": "industries", "industries": None, "countries": None, "direction": None, } labels_to_ignore_map = { "factorCategories": ["factorExposure", "estimatedPnl", "factors"], "factors": ["factorExposure", "estimatedPnl", "byAsset"], "sectors": ["exposure", "estimatedPnl", "industries"], "industries": [], "countries": [], "direction": [], "byAsset": [], } data = data.rename({'name': parent_label}) if parent_label in parent_to_child_map.keys() else data child_label = parent_to_child_map.get(parent_label) if child_label and child_label in data.index.values: child_df = pd.DataFrame(data[child_label]) child_df = child_df.apply(_explode_data, axis=1, parent_label=child_label) data = data.drop(labels=labels_to_ignore_map.get(parent_label)) if isinstance(child_df, pd.Series): child_df = pd.concat(child_df.values, ignore_index=True) child_df = child_df.assign(**data.to_dict()) return child_df return data ================================================ FILE: gs_quant/entities/tree_entity.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import pandas as pd import datetime as dt from typing import Optional from pydash import get from gs_quant.api.gs.assets import GsAsset, GsAssetApi from gs_quant.data import Dataset from gs_quant.errors import MqValueError class AssetTreeNode: def __init__(self, id, depth: Optional[int] = 0, date: Optional[dt.date] = None, asset: Optional[GsAsset] = None): self.id = id self.date = date # date for which the Tree is constructed. If None, it is set to the latest available date. self.depth = depth # depth of the node with respect to the root node self.asset = asset self.name = get(self.asset, 'name') self.bbid = get(get(self.asset, 'xref'), 'bbid') self.asset_type = get(self.asset, 'type') self.data = {} self.constituents_df = pd.DataFrame() self.direct_underlier_assets_as_nodes = [] # holds the child nodes as AssetTreeNode objects. def __str__(self): result = self.bbid if self.bbid is not None else self.id return f'Tree Node - {result}' def to_frame(self) -> pd.DataFrame: if len(self.constituents_df) > 0: return self.constituents_df else: self.constituents_df = ( self.__build_constituents_df(pd.DataFrame()) .drop_duplicates() .sort_values(by='depth') .reset_index(drop=True) ) return self.constituents_df def populate_values(self, dataset, value_column, underlier_column): ds = Dataset(dataset) query = ds.get_data(start=self.date, end=self.date, assetId=[self.id]) if len(query) > 0: for node in self.direct_underlier_assets_as_nodes: value = query.loc[query[underlier_column] == node.id][value_column].iloc[0] node.data[value_column] = value node.populate_values(dataset, value_column, underlier_column) def build_tree(self, dataset, underlier_column): """ Build the full tree and return the root node """ query = self.__get_direct_underliers(self.id, dataset) if len(query) > 0: all_ids = query[underlier_column].tolist() all_assets = GsAssetApi.get_many_assets(id=all_ids) asset_lookup = {mq_id: asset_obj for mq_id, asset_obj in zip(all_ids, all_assets)} for i_, row in query.iterrows(): underlier = row[underlier_column] if underlier not in asset_lookup: raise Exception("Unable to find {}".format(underlier)) child_node = AssetTreeNode(underlier, self.depth + 1, self.date, asset_lookup[underlier]) child_node.build_tree(dataset, underlier_column) self.direct_underlier_assets_as_nodes.append(child_node) def __get_direct_underliers(self, asset_id, dataset) -> pd.DataFrame: """ Queries the dataset for the date passed during initialisation. If date isn't passed, returns the data of the latest available date. """ ds = Dataset(dataset) if self.date: query = ds.get_data(start=self.date, end=self.date, assetId=[asset_id]).drop_duplicates() else: query = ds.get_data(assetId=[asset_id]).drop_duplicates() if len(query) > 0: self.date = query.index.max().date() query = query[query.index == query.index.max()].reset_index() return query def __build_constituents_df(self, constituents_df) -> pd.DataFrame: for node in self.direct_underlier_assets_as_nodes: data = { 'date': self.date, 'assetName': self.name, 'assetId': self.id, 'assetBbid': self.bbid, 'underlyingAssetName': node.name, 'underlyingAssetId': node.id, 'underlyingAssetBbid': node.bbid, 'depth': node.depth, } for key, value in node.data.items(): data[key] = value constituents_df = constituents_df.append(pd.DataFrame(data, index=[0])) d = node.__build_constituents_df(pd.DataFrame()) if len(d) > 0: constituents_df = constituents_df.append(d) return constituents_df class TreeHelper: def __init__( self, id, date: Optional[dt.date] = None, tree_underlier_dataset: Optional[str] = None, underlier_column: Optional[str] = 'underlyingAssetId', ): self.id = id self.root = AssetTreeNode(self.id, 0, date, GsAssetApi.get_asset(asset_id=self.id)) self.date = self.root.date self.update_time = dt.datetime.now() self.constituents_df = pd.DataFrame() self.tree_built = False self.__tree_underlier_dataset = tree_underlier_dataset self.__underlier_column = underlier_column def populate_weights(self, dataset, weight_column: Optional[str] = 'weight'): if not self.tree_built: self.build_tree() self.root.data['weight'] = 1 self.root.populate_values(dataset, weight_column, self.__underlier_column) def populate_attribution(self, dataset, attribution_column: Optional[str] = 'absoluteAttribution'): if not self.tree_built: self.build_tree() self.root.data['absoluteAttribution'] = 1 self.root.populate_values(dataset, attribution_column, self.__underlier_column) def to_frame(self) -> pd.DataFrame: """ Retrieve constituents of the full tree. If it has already been fetched once, it is stored and returned when called later in the future. :return: dataframe with constituents of the full tree, with parent AssetID, underlying AssetID, depth and weight **Usage** Retrieve constituents of the full tree. """ if not self.tree_built: self.build_tree() self.constituents_df = self.root.to_frame() if len(self.constituents_df) > 0: return self.constituents_df else: raise MqValueError('No constituents found for the asset') def build_tree(self): if not self.tree_built: self.root.build_tree(self.__tree_underlier_dataset, self.__underlier_column) self.tree_built = True self.update_time = dt.datetime.now() def get_tree(self) -> AssetTreeNode: """ Build the full tree and return the root node of the full-fledged tree. If the tree has been built already, return it on future calls. :return: AssetTreeNode object of the root node, with a list attribute direct_underlier_assets_as_nodes that holds the child AssetTreeNode object. **Usage** Root AssetTreeNode object of the tree entity """ if not self.tree_built: self.build_tree() return self.root def get_visualisation(self, visualise_by: str = 'name'): try: from treelib import Tree except ModuleNotFoundError: raise RuntimeError('You must install treelib to be able use this function.') if not self.tree_built: self.build_tree() if visualise_by in ['name', 'bbid', 'id']: # Each entry in the BFS queue is an array having the node and the prefix value for that node. # The prefix is the path from root to the parent of that node, and is empty for the root node. # This definition of prefix allows using the same node in multiple branches. bfs_queue = [[self.root, '']] tree_vis = Tree() while len(bfs_queue) != 0: node, prefix = bfs_queue.pop(0) node_id = prefix + '-' + node.id node_name = getattr(node, visualise_by) if str(node_name) == 'None': node_name = f'NA ({node.id})' if prefix == '': tree_vis.create_node(node_name, node_id) else: tree_vis.create_node(node_name, node_id, parent=prefix) for c in node.direct_underlier_assets_as_nodes: bfs_queue.append([c, node_id]) else: raise MqValueError('visualise_by argument has to be either name, id or bbid') return tree_vis.show() ================================================ FILE: gs_quant/errors.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import sys class MqError(Exception): """Base class for errors in this module""" pass class MqValueError(MqError, ValueError): pass class MqTypeError(MqError): pass class MqWrappedError(MqError): pass class MqRequestError(MqError): def __init__(self, status, message, context=None): self.status = status self.message = message self.context = context def __str__(self): prepend = 'context: {}\n'.format(self.context) if self.context else '' result = '{}status: {}, message: {}'.format(prepend, self.status, self.message) if sys.version_info.major < 3: result = result.encode('ascii', 'ignore') return result class MqAuthenticationError(MqRequestError): pass class MqAuthorizationError(MqRequestError): pass class MqUninitialisedError(MqError): pass # Creating errors based on status code to be able to effectively use backoff decorator class MqRateLimitedError(MqRequestError): pass class MqTimeoutError(MqRequestError): pass class MqInternalServerError(MqRequestError): pass def error_builder(status, message, context=None): if status == 401: return MqAuthenticationError(status, message, context) elif status == 403: return MqAuthorizationError(status, message, context) elif status == 429: return MqRateLimitedError(status, message, context) elif status == 500: return MqInternalServerError(status, message, context) elif status == 504: return MqTimeoutError(status, message, context) else: return MqRequestError(status, message, context) ================================================ FILE: gs_quant/instrument/__init__.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from .core import Instrument, Security, DummyInstrument from gs_quant.target.instrument import * from gs_quant.target.common import SwapClearingHouse, SwapSettlement from .overrides import * ================================================ FILE: gs_quant/instrument/core.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import inspect import logging import warnings from copy import deepcopy from typing import Iterable, Optional, Tuple, Union from dataclasses_json import global_config from gs_quant.api.gs.parser import GsParserApi from gs_quant.api.gs.risk import GsRiskApi from gs_quant.base import get_enum_value, InstrumentBase, Priceable, Scenario from gs_quant.common import AssetClass, AssetType, XRef, RiskMeasure, MultiScenario from gs_quant.markets import HistoricalPricingContext, MarketDataCoordinate, PricingContext from gs_quant.priceable import PriceableImpl from gs_quant.risk import ( FloatWithInfo, DataFrameWithInfo, SeriesWithInfo, ResolvedInstrumentValues, DEPRECATED_MEASURES, ) from gs_quant.risk.results import ErrorValue, MultipleRiskMeasureFuture, PricingFuture, MultipleScenarioFuture _logger = logging.getLogger(__name__) class Instrument(PriceableImpl, InstrumentBase): PROVIDER = GsRiskApi __instrument_mappings = {} def __repr__(self): return f'{self.__class__.__name__}{"(" + self.name + ")" if self.name else ""}' @classmethod def __asset_class_and_type_to_instrument(cls): if not cls.__instrument_mappings: import gs_quant.target.instrument as instrument_ # noqa instrument_classes = [ c for _, c in inspect.getmembers(instrument_, inspect.isclass) if issubclass(c, Instrument) and c is not Instrument ] cls.__instrument_mappings[(AssetClass.Cash, AssetType.Currency)] = instrument_.Forward for clazz in instrument_classes: instrument = clazz.default_instance() cls.__instrument_mappings[(instrument.asset_class, instrument.type)] = clazz return cls.__instrument_mappings @property def provider(self): return self.PROVIDER def resolve(self, in_place: bool = True) -> Optional[Union[PriceableImpl, PricingFuture, dict]]: """ Resolve non-supplied properties of an instrument **Examples** >>> from gs_quant.instrument import IRSwap >>> >>> swap = IRSwap('Pay', '10y', 'USD') >>> rate = swap.fixedRate rate is None >>> swap.resolve() >>> rate = swap.fixedRate rates is now the solved fixed rate """ is_historical = isinstance(PricingContext.current, HistoricalPricingContext) def handle_result(result: Optional[Union[ErrorValue, InstrumentBase]]) -> Optional[PriceableImpl]: ret = None if in_place else result if isinstance(result, ErrorValue): _logger.error('Failed to resolve instrument fields: ' + result.error) ret = {result.risk_key.date: None} if is_historical else None elif result is None: _logger.error('Unknown error resolving instrument fields') ret = {dt.date.today(): self} if is_historical else self elif in_place: self.from_instance(result) return ret if in_place and is_historical: raise RuntimeError('Cannot resolve in place under a HistoricalPricingContext') if in_place and len([i for i in Scenario.path if isinstance(i, MultiScenario)]): raise RuntimeError('Cannot resolve in place under a MultiScenario Context') return self.calc(ResolvedInstrumentValues, fn=handle_result) def calc( self, risk_measure: Union[RiskMeasure, Iterable[RiskMeasure]], fn=None ) -> Union[ DataFrameWithInfo, ErrorValue, FloatWithInfo, PriceableImpl, PricingFuture, SeriesWithInfo, Tuple[MarketDataCoordinate, ...], ]: """ Calculate the value of the risk_measure :param risk_measure: the risk measure to compute, e.g. IRDelta (from gs_quant.risk) :param fn: post-processing function (optional) :return: a float or dataframe, depending on whether the value is scalar or structured, or a future thereof (depending on how PricingContext is being used) **Examples** >>> from gs_quant.instrument import IRCap >>> from gs_quant.risk import IRDelta >>> >>> cap = IRCap('1y', 'USD') >>> delta = cap.calc(IRDelta) delta is a dataframe >>> from gs_quant.instrument import EqOption >>> from gs_quant.risk import EqDelta >>> >>> option = EqOption('.SPX', '3m', 'ATMF', 'Call', 'European') >>> delta = option.calc(EqDelta) delta is a float >>> from gs_quant.markets import PricingContext >>> >>> cap_usd = IRCap('1y', 'USD') >>> cap_eur = IRCap('1y', 'EUR') >>> with PricingContext(): >>> usd_delta_f = cap_usd.calc(IRDelta) >>> eur_delta_f = cap_eur.calc(IRDelta) >>> >>> usd_delta = usd_delta_f.result() >>> eur_delta = eur_delta_f.result() usd_delta_f and eur_delta_f are futures, usd_delta and eur_delta are dataframes """ def get_inst_futures(curr_measure): return ( MultipleScenarioFuture( self, multi_scenario.scenarios, (curr_measure.pricing_context.calc(self, curr_measure),) ) if multi_scenario else curr_measure.pricing_context.calc(self, curr_measure) ) single_measure = isinstance(risk_measure, RiskMeasure) multi_scenario = next((i for i in Scenario.path if isinstance(i, MultiScenario)), None) with self._pricing_context: future = ( get_inst_futures(risk_measure) if single_measure else MultipleRiskMeasureFuture(self, {r: get_inst_futures(r) for r in risk_measure}) ) # Warn on use of deprecated measures def warning_on_one_line(msg, category, _filename, _lineno, _file=None, _line=None): return f'{category.__name__}:{msg}' for measure in (risk_measure,) if single_measure else risk_measure: if measure.name in DEPRECATED_MEASURES.keys(): message = ( '{0} risk measure is deprecated. Please use {1} instead and pass in arguments to describe ' 'risk measure specifics.\n'.format(measure.name, DEPRECATED_MEASURES[measure.name]) ) warnings.simplefilter('once') warnings.formatwarning = warning_on_one_line warnings.warn(message, DeprecationWarning) warnings.simplefilter('ignore') if fn is not None: ret = PricingFuture() def cb(f): try: ret.set_result(fn(f.result())) except Exception as e: ret.set_exception(e) future.add_done_callback(cb) future = ret return future if self._return_future else future.result() @classmethod def from_dict(cls, values: dict): if not values: return instrument = cls if hasattr(cls, 'asset_class') else None if instrument is None: builder_type = values.get('$type') or values.get('builder', values.get('defn', {})).get('$type') values_used = values.get('builder', values.get('defn', values)) if builder_type: from gs_quant_internal.base import decode_quill_value return decode_quill_value(values_used) asset_class_field = next((f for f in ('asset_class', 'assetClass') if f in values), None) if not asset_class_field: raise ValueError('assetClass/asset_class not specified') if 'type' not in values: raise ValueError('type not specified') asset_type = values.pop('type') asset_class = values.pop(asset_class_field) security_types = (None, '', 'Security') default_type = Security if asset_type in security_types and asset_class in security_types else None instrument = Instrument.__asset_class_and_type_to_instrument().get( (get_enum_value(AssetClass, asset_class), get_enum_value(AssetType, asset_type)), default_type ) if instrument is None: raise ValueError('unable to build instrument') return instrument.from_dict(values) @classmethod def from_quick_entry(cls, text: str, asset_class: Optional[AssetClass] = None): if not asset_class: try: inst = cls.default_instance() asset_class = inst.asset_class except AttributeError: pass if not asset_class: res = GsParserApi.get_instrument_from_text(text) if len(res): # multiple instruments returned instrument = res.pop(0) else: raise ValueError('Could not resolve instrument') else: instrument = GsParserApi.get_instrument_from_text_asset_class(text, asset_class.value) try: return cls.from_dict(instrument) except AttributeError: raise ValueError('Invalid instrument specification') @classmethod def from_asset_ids(cls, asset_ids: Tuple[str, ...]) -> Tuple[InstrumentBase, ...]: from gs_quant.api.gs.assets import GsAssetApi instruments = GsAssetApi.get_instruments_for_asset_ids(asset_ids) try: inst = cls.default_instance() asset_class = inst.asset_class asset_type = inst.type if not all(i.asset_class == asset_class and i.type == asset_type for i in instruments): raise ValueError(f'Instrument(s) not all of type {cls.__name__}') except AttributeError: pass return instruments @classmethod def from_asset_id(cls, asset_id: str) -> InstrumentBase: return cls.from_asset_ids((asset_id,))[0] @staticmethod def compose(components: Iterable): return {c.risk_key.date if isinstance(c, ErrorValue) else c.resolution_key.date: c for c in components} def flip(self, in_place: bool = True): return self.scale(-1, in_place) def scale(self, scaling: float, in_place: bool = True, check_resolved=True): if scaling is None: return self if not hasattr(self, 'scale_in_place'): raise NotImplementedError(f'scale_in_place not implemented on {type(self).__name__}') if in_place: self.scale_in_place(scaling, check_resolved=check_resolved) return new_inst = deepcopy(self) new_inst.scale(scaling, check_resolved=check_resolved) return new_inst class DummyInstrument(Instrument): def __init__(self, dummy_result: Union[str, float] = None): super().__init__() self.dummy_result = dummy_result @property def dummy_result(self) -> Union[str, float]: return self.__dummy_result @dummy_result.setter def dummy_result(self, value: Union[str, float]): self.__dummy_result = value @property def type(self) -> AssetType: return AssetType.Any class Security(XRef, Instrument): """A security, specified by a well-known identifier""" def __init__( self, ticker: str = None, bbid: str = None, ric: str = None, isin: str = None, cusip: str = None, prime_id: str = None, quantity: float = 1, ): """ Create a security by passing one identifier only and, optionally, a quantity :param ticker: Exchange ticker :param bbid: Bloomberg identifier :param isin: International Security Number :param cusip: CUSIP :param prime_id: Prime (GS internal) identifier :param quantity: Quantity (number of contracts for exchange-traded instruments, notional for bonds) """ if len(tuple(filter(None, (f is not None for f in (ticker, bbid, isin, cusip, prime_id))))) > 1: raise ValueError('Only specify one identifier') XRef.__init__(self, ticker=ticker, bbid=bbid, ric=ric, isin=isin, cusip=cusip, prime_id=prime_id) Instrument.__init__(self) self.quantity_ = quantity @classmethod def from_dict(cls, env): return cls(**{k: v for k, v in env.items() if k in inspect.signature(cls).parameters}) def encode_instrument(instrument: Optional[Instrument]) -> Optional[dict]: if instrument is not None: return instrument.to_dict() def encode_instruments(instruments: Optional[Iterable[Instrument]]) -> Optional[Iterable[Optional[dict]]]: if instruments is not None: return [encode_instrument(i) for i in instruments] global_config.decoders[Instrument] = Instrument.from_dict global_config.decoders[InstrumentBase] = Instrument.from_dict global_config.encoders[Instrument] = encode_instrument global_config.encoders[Priceable] = encode_instrument global_config.encoders[Optional[Priceable]] = encode_instrument global_config.encoders[Optional[Instrument]] = encode_instrument global_config.encoders[Optional[InstrumentBase]] = encode_instrument ================================================ FILE: gs_quant/instrument/overrides.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ ================================================ FILE: gs_quant/interfaces/__init__.py ================================================ ================================================ FILE: gs_quant/interfaces/algebra.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import abc class AlgebraicType(abc.ABC): @abc.abstractmethod def __add__(self, other): ... def __radd__(self, other): return self.__add__(other) @abc.abstractmethod def __sub__(self, other): ... @abc.abstractmethod def __mul__(self, other): ... def __rmul__(self, other): return self.__mul__(other) @abc.abstractmethod def __div__(self, other): ... ================================================ FILE: gs_quant/json_convertors.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import re from dataclasses import MISSING, fields from typing import Optional, Union, Iterable, Dict, Tuple, Any import pandas as pd from dataclasses_json import config from dateutil.parser import isoparse __valid_date_formats = ( '%Y-%m-%d', # '2020-07-28' '%d%b%y', # '28Jul20' '%d%b%Y', # '28Jul2020' '%d-%b-%y', # '28-Jul-20' '%d/%m/%Y', ) # '28/07/2020 DateOrDateTime = Union[dt.date, dt.datetime] def encode_date_or_str(value: Optional[Union[str, dt.date]]) -> Optional[str]: return value.isoformat() if isinstance(value, dt.date) else value def decode_optional_date(value: Optional[str]) -> Optional[dt.date]: # from dataclasses-json 0.6.5 onwards the global config for type T will be applied to Optional[T] # So this decoder would become redundant, to allow any version we simply return if it's already a date if value is None or isinstance(value, dt.date): return value elif isinstance(value, str): decoded_date_str = __try_decode_valid_date_formats(value) if decoded_date_str is not None: return decoded_date_str raise ValueError(f'Cannot convert {value} to date') def decode_optional_time(value: Optional[str]) -> Optional[dt.time]: # from dataclasses-json 0.6.5 onwards the global config for type T will be applied to Optional[T] # So this decoder would become redundant, to allow any version we simply return if it's already a time if value is None or isinstance(value, dt.time): return value elif isinstance(value, str): return dt.time.fromisoformat(value) raise ValueError(f'Cannot convert {value} to date') def encode_optional_time(value: Optional[Union[str, dt.time]]) -> Optional[str]: return value.isoformat() if isinstance(value, dt.time) else value def decode_date_tuple(blob: Tuple[str, ...]): return tuple(decode_optional_date(s) for s in blob) if isinstance(blob, (tuple, list)) else None def encode_date_tuple(values: Tuple[Optional[Union[str, dt.date]], ...]): return ( tuple(encode_date_or_str(value) if isinstance(value, (str, dt.date)) else None for value in values) if values is not None else None ) def decode_iso_date_or_datetime(value: Any) -> Union[Tuple[DateOrDateTime, ...], DateOrDateTime]: if isinstance(value, (tuple, list)): return tuple(decode_iso_date_or_datetime(v) for v in value) if isinstance(value, (dt.date, dt.datetime)): return value elif isinstance(value, str): if len(value) == 10: return decode_optional_date(value) else: return optional_from_isodatetime(value) raise TypeError(f'Cannot convert {value} to date or datetime') def optional_from_isodatetime(datetime: Union[str, dt.datetime, None]) -> Optional[dt.datetime]: if datetime is None or isinstance(datetime, dt.datetime): return datetime return dt.datetime.fromisoformat(datetime.replace('Z', '')) def optional_to_isodatetime(datetime: Optional[dt.datetime]): return f'{dt.datetime.isoformat(datetime, timespec="seconds")}Z' if datetime is not None else None optional_datetime_config = config(encoder=optional_to_isodatetime, decoder=optional_from_isodatetime) optional_date_config = config(encoder=encode_date_or_str, decoder=decode_optional_date) def decode_dict_date_key(value): return {dt.date.fromisoformat(d): v for d, v in value.items()} if value is not None else None def decode_dict_date_key_or_float(value): if value is not None: return decode_dict_date_key(value) if isinstance(value, dict) else decode_float_or_str(value) return None def decode_dict_dict_date_key(value): return ( { k: {dt.date.fromisoformat(d): v for d, v in val.items()} if val is not None else None for k, val in value.items() } if value is not None else None ) def decode_dict_date_value(value): return {k: dt.date.fromisoformat(d) for k, d in value.items()} if value is not None else None def decode_datetime_tuple(blob: Tuple[str, ...]): return tuple(optional_from_isodatetime(s) for s in blob) if isinstance(blob, (tuple, list)) else None def __try_decode_valid_date_formats(value: str) -> Optional[dt.date]: for fmt in __valid_date_formats: try: return dt.datetime.strptime(value, fmt).date() except ValueError: pass return None def decode_date_or_str(value: Union[dt.date, float, str]) -> Optional[Union[dt.date, str]]: if value is None or isinstance(value, dt.date): return value elif isinstance(value, float): # Assume it's an Excel date if value > 59: value -= 1 # Excel leap year bug, 1900 is not a leap year! return (dt.datetime(1899, 12, 31) + dt.timedelta(days=value)).date() elif isinstance(value, str): # Try the supported string date formats decoded_date_str = __try_decode_valid_date_formats(value) # Assume it's a tenor return value if decoded_date_str is None else decoded_date_str raise TypeError(f'Cannot convert {value} to date') def encode_datetime(value: Optional[dt.datetime]) -> Optional[str]: if value is None: return value try: iso_formatted = value.isoformat(timespec='milliseconds') except TypeError: # Pandas Timestamp objects don't take timespec, will raise TypeError (as of 1.2.4) iso_formatted = value.isoformat() return iso_formatted if value.tzinfo else iso_formatted + 'Z' # Make sure to be explict about timezone def decode_datetime(value: Optional[Union[int, str]]) -> Optional[dt.datetime]: if value is None or isinstance(value, dt.datetime): return value if isinstance(value, int): return dt.datetime.fromtimestamp(value / 1000) elif isinstance(value, str): matcher = re.search('\\.([0-9]*)Z$', value) if matcher: sub_seconds = matcher.group(1) if len(sub_seconds) > 6: value = re.sub(matcher.re, '.{}Z'.format(sub_seconds[:6]), value) return isoparse(value) raise TypeError(f'Cannot convert {value} to datetime') def decode_float_or_str(value: Optional[Union[float, int, str]]) -> Optional[Union[float, str]]: if value is None: return value elif isinstance(value, float): return value elif isinstance(value, int): return float(value) elif isinstance(value, str): try: return float(value) except ValueError: # Assume it's a strike or similar, e.g. 'ATM' return value raise TypeError(f'Cannot convert {value} to float') def decode_instrument(value: Optional[Dict]): from gs_quant.instrument import Instrument return Instrument.from_dict(value) if value else None def decode_named_instrument(value: Optional[Union[Iterable[Dict], dict]]): from gs_quant.instrument import Instrument if isinstance(value, (list, tuple)): return tuple(decode_named_instrument(v) for v in value) elif isinstance(value, dict) and 'portfolio_name' in value.keys(): return decode_named_portfolio(value) return Instrument.from_dict(value) if value else None def decode_named_portfolio(value): from gs_quant.markets.portfolio import Portfolio return Portfolio([decode_named_instrument(v) for v in value['instruments']], name=value['portfolio_name']) def encode_named_instrument(obj): from gs_quant.markets.portfolio import Portfolio if isinstance(obj, (list, tuple)): return tuple(encode_named_instrument(o) for o in obj) elif isinstance(obj, Portfolio): return encode_named_portfolio(obj) return obj.as_dict() def encode_named_portfolio(obj): return {'portfolio_name': obj.name, 'instruments': tuple(encode_named_instrument(o) for o in obj.all_instruments)} def encode_pandas_series(obj): series_dict = pd.Series.to_dict(obj) if isinstance(next(iter(series_dict)), (dt.date, dt.datetime)): series_dict = {k.isoformat(): v for k, v in series_dict.items()} return series_dict def decode_pandas_series(value: dict): dated_dict = {decode_iso_date_or_datetime(k): v for k, v in value.items()} return pd.Series(dated_dict) def decode_quote_report(value: Optional[dict]): from gs_quant.quote_reports.core import quote_report_from_dict return quote_report_from_dict(value) if value else None def decode_quote_reports(value: Optional[Iterable[Dict]]): from gs_quant.quote_reports.core import quote_reports_from_dicts return quote_reports_from_dicts(value) if value else None def decode_custom_comment(value: Optional[dict]): from gs_quant.quote_reports.core import custom_comment_from_dict return custom_comment_from_dict(value) if value else None def decode_custom_comments(value: Optional[Iterable[Dict]]): from gs_quant.quote_reports.core import custom_comments_from_dicts return custom_comments_from_dicts(value) if value else None def decode_hedge_type(value: Optional[dict]): from gs_quant.quote_reports.core import hedge_type_from_dict return hedge_type_from_dict(value) if value else None def decode_hedge_types(value: Optional[Iterable[Dict]]): from gs_quant.quote_reports.core import hedge_type_from_dicts return hedge_type_from_dicts(value) if value else None def encode_dictable(o): return o if o is None else o.to_dict() def encode_named_dictable(o): d = encode_dictable(o) if d is not None: d['type'] = type(o).__name__ return d def _get_dc_type(cls, name_field: str, allow_missing: bool): type_field = list(filter(lambda f: f.name in (name_field, f'{name_field}_'), fields(cls))) if len(type_field) == 0: if allow_missing: return None raise ValueError(f'Class {cls} has no "{name_field}" property') def_value = type_field[0].default if def_value == MISSING or def_value is None: raise ValueError('No default value for "class_type" field on class') return def_value def _value_decoder(type_to_cls_map, explicit_cls=None, str_mapper=None): def decode_value(value): if value is None or 'null' == value: return None if isinstance(value, (list, tuple)): return tuple(decode_value(v) for v in value) if isinstance(value, str): return str_mapper(value) if str_mapper is not None else value if isinstance(value, (float, int, dt.date)): return value if not isinstance(value, dict): raise TypeError(f'Cannot decode object of type: {type(value)}') if explicit_cls is not None: return explicit_cls.from_dict(value) else: if 'class_type' not in value: raise ValueError(f'Object has no "class_type" property {value}') obj_type = value['class_type'] if obj_type not in type_to_cls_map: raise ValueError(f'No class mapping for object type: "{obj_type}"') try: return type_to_cls_map[obj_type].from_dict(value) except Exception as e: raise ValueError(f'Failed to de-serialise {type_to_cls_map[obj_type]} from value {value}') from e return decode_value def dc_decode(*classes, name_field='class_type', allow_missing=False): mappings = ((_get_dc_type(cls, name_field, allow_missing), cls) for cls in classes) type_to_cls_map = dict((k, v) for k, v in mappings if k is not None) return _value_decoder(type_to_cls_map, None) ================================================ FILE: gs_quant/json_convertors_common.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import copy from enum import Enum from typing import Tuple, Dict, Optional, Union from gs_quant.base import RiskMeasureParameter from gs_quant.common import RiskMeasure, ParameterisedRiskMeasure from gs_quant import common from gs_quant import risk def gsq_rm_for_name(name: str) -> Optional[RiskMeasure]: if name is None or name not in dir(risk): return None return getattr(risk, name) def encode_risk_measure(rm: RiskMeasure) -> Dict: result = rm.as_dict(as_camel_case=True) if rm.parameters is not None: result['parameters'] = rm.parameters.as_dict(as_camel_case=True) return result def encode_risk_measure_tuple(blob: Tuple[RiskMeasure, ...]) -> Tuple[Dict, ...]: return tuple(encode_risk_measure(rm) for rm in blob) def _decode_param(data: dict) -> Optional[RiskMeasureParameter]: params = data.get('parameters', None) if params is not None and isinstance(params, dict) and 'parameterType' in params: cls_name = params['parameterType'] + 'Parameter' parameter_cls = getattr(common, cls_name) parameter = parameter_cls(**{k: v for k, v in params.items() if k != 'parameterType'}) return parameter return None def _decode_gsq_risk_measure(data: dict) -> Optional[RiskMeasure]: def _enum_or_str_equal(a: Optional[Union[Enum, str]], b: Optional[Union[Enum, str]]): return (a is None and b is None) or (str(a).lower() == str(b).lower()) name = data.get('name', None) gsq_rm = gsq_rm_for_name(name) if gsq_rm is None: return None asset_class = data.get('assetClass', None) measure_type = data.get('measureType', None) if _enum_or_str_equal(asset_class, gsq_rm.asset_class) and _enum_or_str_equal(measure_type, gsq_rm.measure_type): result = copy.copy(gsq_rm) param = _decode_param(data) if param: result.parameters = param return result return None def decode_risk_measure(data: Dict) -> RiskMeasure: result = _decode_gsq_risk_measure(data) if result is not None: return result if 'parameters' in data: result = ParameterisedRiskMeasure.from_dict(data) result.parameters = _decode_param(data) else: result = RiskMeasure.from_dict(data) return result def decode_risk_measure_tuple(blob: Tuple[Dict, ...]) -> Tuple[RiskMeasure, ...]: return tuple(decode_risk_measure(s) for s in blob) if isinstance(blob, (tuple, list)) else None ================================================ FILE: gs_quant/json_encoder.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from enum import Enum import json import pandas as pd from gs_quant.base import Base, Market from gs_quant.json_convertors import encode_date_or_str, encode_datetime def encode_default(o): if isinstance(o, dt.datetime): return encode_datetime(o) if isinstance(o, dt.date): return encode_date_or_str(o) elif isinstance(o, dt.time): return o.isoformat(timespec='milliseconds') elif isinstance(o, Enum): return o.value elif isinstance(o, (Base, Market)): return o.to_dict() elif isinstance(o, pd.DataFrame): return o.to_json() class JSONEncoder(json.JSONEncoder): def default(self, o): ret = encode_default(o) return super().default(o) if ret is None else ret ================================================ FILE: gs_quant/markets/__init__.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from .core import PricingCache, PricingContext, PositionContext from .historical import HistoricalPricingContext, BackToTheFuturePricingContext from .markets import * ================================================ FILE: gs_quant/markets/baskets.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import json import logging from copy import deepcopy from enum import Enum from functools import wraps from typing import List, Optional, Union, Tuple import pandas as pd from pydash import has, get, set_ from gs_quant.api.gs.assets import GsAsset, GsAssetApi from gs_quant.api.gs.data import GsDataApi from gs_quant.api.gs.indices import GsIndexApi from gs_quant.api.gs.reports import GsReportApi from gs_quant.api.gs.users import GsUsersApi from gs_quant.common import ( DateLimit, PositionType, EqBasketBacktestParameters, EqBasketHistoryMethodology, BloombergPublishParameters, CashReinvestmentTreatment, CashReinvestmentTreatmentType, EqBasketRebalanceCalendar, AssetClass, ) from gs_quant.data.fields import DataMeasure from gs_quant.entities.entitlements import Entitlements as BasketEntitlements from gs_quant.entities.entity import EntityType, PositionedEntity from gs_quant.errors import MqError, MqValueError from gs_quant.json_encoder import JSONEncoder from gs_quant.markets.indices_utils import ( BasketType, IndicesDatasets, ReturnType, WeightingStrategy, CorporateActionType, ) from gs_quant.markets.position_set import PositionSet from gs_quant.markets.securities import Asset, AssetType as SecAssetType from gs_quant.session import GsSession from gs_quant.target.data import DataQuery from gs_quant.target.indices import ( CustomBasketsCreateInputs, CustomBasketsPricingParameters, PublishParameters, IndicesPositionInput, IndicesPositionSet, CustomBasketsBackcastInputs, CustomBasketsRebalanceAction, CustomBasketRiskParams, IndicesCurrency, CustomBasketsEditInputs, CustomBasketsResponse, CustomBasketsRebalanceInputs, ) from gs_quant.target.reports import Report, ReportStatus _logger = logging.getLogger(__name__) class ErrorMessage(Enum): NON_ADMIN = 'You are not permitted to perform this action on this basket. Please make sure \ the basket owner has entitled your application properly if you believe this is a mistake' NON_INTERNAL = 'You are not permitted to access this basket setting.' RESTRICTED_ATTRIBUTE = 'You are not permitted to access this basket setting' UNINITIALIZED = 'Basket class object must be initialized using one of an existing basket\'s \ identifiers to perform this action' UNMODIFIABLE = 'This property can not be modified since the basket has already been created' def _validate(*error_msgs): """Confirms initialization is complete and checks for errors before calling function""" def _outer(fn): @wraps(fn) def _inner(self, *args, **kwargs): if has(self, '_Basket__error_messages') and self._Basket__error_messages is not None: if len(self._Basket__error_messages) < 1: self._Basket__finish_initialization() for error_msg in error_msgs: if error_msg in self._Basket__error_messages: raise MqError(error_msg.value) return fn(self, *args, **kwargs) return _inner return _outer class Basket(Asset, PositionedEntity): """ Basket which tracks an evolving portfolio of securities, and can be traded through cash or derivatives markets """ def __init__(self, gs_asset: GsAsset = None, **kwargs): self.__error_messages = None if gs_asset: if gs_asset.type.value not in BasketType.to_list(): raise MqValueError(f'Failed to initialize. Asset {gs_asset.id} is not a basket') self.__id = gs_asset.id self.__initial_entitlements = gs_asset.entitlements asset_entity: dict = json.loads(json.dumps(gs_asset.as_dict(), cls=JSONEncoder)) Asset.__init__( self, gs_asset.id, gs_asset.asset_class, gs_asset.name, exchange=gs_asset.exchange, currency=gs_asset.currency, entity=asset_entity, ) PositionedEntity.__init__(self, gs_asset.id, EntityType.ASSET) self.__populate_current_attributes_for_existing_basket(gs_asset) else: self.__populate_default_attributes_for_new_basket(**kwargs) self.__error_messages = set([]) if get(kwargs, '_finish_init', False): self.__finish_initialization() @classmethod def get(cls, identifier: str, **kwargs): """ Fetch an existing basket :param identifier: Any common identifier for a basket (ric, ticker, etc.) :return: Basket object **Usage** Get existing basket instance **Examples** Get basket details: >>> from gs_quant.markets.baskets import Basket >>> >>> basket = Basket.get("GSMBXXXX") """ gs_asset = cls.__get_gs_asset(identifier) return cls(gs_asset=gs_asset, _finish_init=get(kwargs, '_finish_init', True)) @_validate() def get_details(self) -> pd.DataFrame: """ Get basket details :return: dataframe containing current basket properties **Usage** Get basket's current state **Examples** Get basket details: >>> from gs_quant.markets.baskets import Basket >>> >>> basket = Basket.get("GSMBXXXX") >>> basket.get_details() """ props = list( CustomBasketsPricingParameters.properties().union( PublishParameters.properties(), CustomBasketsCreateInputs.properties() ) ) props = sorted(props) details = [{'name': k, 'value': get(self, k)} for k in props if has(self, k)] return pd.DataFrame(details) def create(self) -> dict: """ Create a new custom basket in Marquee :return: dictionary containing asset id and report id **Usage** Create a new custom basket in Marquee **See also** :func:`get_details` :func:`poll_status` :func:`update` """ inputs, pricing, publish = {}, {}, {} for prop in CustomBasketsCreateInputs.properties(): set_(inputs, prop, get(self, prop)) for prop in CustomBasketsPricingParameters.properties(): set_(pricing, prop, get(self, prop)) for prop in PublishParameters.properties(): set_(publish, prop, get(self, prop)) set_(inputs, 'position_set', self.position_set.to_target(common=False)) set_(inputs, 'pricing_parameters', CustomBasketsPricingParameters(**pricing)) set_(inputs, 'publish_parameters', PublishParameters(**publish)) create_inputs = CustomBasketsCreateInputs(**inputs) response = GsIndexApi.create(create_inputs) gs_asset = GsAssetApi.get_asset(response.asset_id) self.__latest_create_report = GsReportApi.get_report(response.report_id) self.__init__(gs_asset=gs_asset, _finish_init=True) return response.as_dict() @_validate(ErrorMessage.UNINITIALIZED) def clone(self): """ Retrieve a clone of an existing basket :return: New basket instance with position set identical to current basket **Usage** Clone an existing basket's position set in a new basket instance prior to creation **Examples** Clone current basket: >>> from gs_quant.markets.baskets import Basket >>> >>> parent_basket = Basket.get("GSMBXXXX") >>> clone = parent_basket.clone() **See also** :func:`create` """ position_set = deepcopy(self.position_set) return Basket(position_set=position_set, clone_parent_id=self.id, parent_basket=self.ticker) @_validate(ErrorMessage.UNINITIALIZED, ErrorMessage.NON_ADMIN) def update(self) -> dict: """ Update your custom basket :return: dictionary containing asset id and report id **Usage** Make updates to your basket's metadata, pricing options, publishing options, or composition **See also** :func:`get_details` :func:`poll_status` :func:`create` """ edit_inputs, rebal_inputs = self.__get_updates() response = None init_entitlements = BasketEntitlements.from_target(self.__initial_entitlements) if not init_entitlements == self.__entitlements: response = GsAssetApi.update_asset_entitlements( self.id, self.__entitlements.to_target(include_all_tokens=True) ) if edit_inputs is None and rebal_inputs is None: if response: return response raise MqValueError('Update failed: Nothing on the basket was changed') elif edit_inputs is not None and rebal_inputs is None: response = GsIndexApi.edit(self.id, edit_inputs) elif rebal_inputs is not None and edit_inputs is None: response = GsIndexApi.rebalance(self.id, rebal_inputs) else: response = self.__edit_and_rebalance(edit_inputs, rebal_inputs) gs_asset = GsAssetApi.get_asset(self.id) self.__latest_create_report = GsReportApi.get_report(response.report_id) self.__init__(gs_asset=gs_asset, _finish_init=True) return response.as_dict() @_validate(ErrorMessage.UNINITIALIZED, ErrorMessage.NON_ADMIN) def upload_position_history(self, position_sets: List[PositionSet]) -> dict: """ Upload basket composition history :param position_sets: list of dated position sets :return: dictionary containing asset id and report id **Usage** Upload your basket's historical composition after it's been created **Examples** Upload composition history from a list of identifiers: >>> from datetime import date >>> from gs_quant.markets.baskets import Basket >>> from gs_quant.markets.position_set import PositionSet >>> >>> first_position_set = PositionSet.from_list(['BBID1', 'BBID2'], date(2020, 1, 1)) >>> second_position_set = PositionSet.from_list(['BBID1','BBID2', 'BBID3'], date(2021, 1, 1)) >>> >>> basket = Basket.get("GSMBXXXX") >>> basket.upload_position_history([first_position_set, second_position_set]) **See also** :class:`PositionSet` """ if self.default_backcast: raise MqValueError('Unable to upload position history: option must be set during basket creation') historical_position_sets = [] for position_set in position_sets: self.__validate_position_set(position_set) positions = [IndicesPositionInput(p.asset_id, p.weight) for p in position_set.positions] historical_position_sets.append(IndicesPositionSet(tuple(positions), position_set.date)) response = GsIndexApi.backcast(self.id, CustomBasketsBackcastInputs(tuple(historical_position_sets))) return response.as_dict() @_validate(ErrorMessage.UNINITIALIZED) def poll_status(self, timeout: int = 600, step: int = 30) -> ReportStatus: """ Polls the status of the basket's most recent create/edit/rebalance report :param timeout: how many seconds you'd like to poll for (default is 600 sec) :param step: how frequently you'd like to check the report's status (default is every 30 sec) :return: Report status **Usage** Poll the status of a newly created or updated basket **Examples** Poll most recent create/update report status: >>> from gs_quant.markets.baskets import Basket >>> >>> basket = Basket.get("GSMBXXXX") >>> basket.poll_status(timeout=120, step=20) **See also** :func:`create` :func:`update` """ report = get(self, '__latest_create_report', self.__get_latest_create_report()) report_id = get(report, 'id') return self.poll_report(report_id, timeout, step) @_validate(ErrorMessage.UNINITIALIZED) def get_latest_rebalance_data(self) -> dict: """ Retrieve the most recent rebalance data for a basket **Usage** Retrieve the most recent rebalance data for a basket **Examples** Retrieve the most recent rebalance data for a basket >>> from gs_quant.markets.baskets import Basket >>> >>> basket = Basket.get("GSMBXXXX") >>> basket.get_latest_rebalance_data() **See also** :func:`get_latest_rebalance_date` """ return GsIndexApi.last_rebalance_data(self.id) @_validate(ErrorMessage.UNINITIALIZED) def get_latest_rebalance_date(self) -> dt.date: """ Retrieve the most recent rebalance date for a basket :return: dictionary **Usage** Retrieve the most recent rebalance date for a basket **Examples** Retrieve the most recent rebalance date for a basket >>> from gs_quant.markets.baskets import Basket >>> >>> basket = Basket.get("GSMBXXXX") >>> basket.get_latest_rebalance_date() **See also** :func:`get_latest_rebalance_data` """ last_rebalance = GsIndexApi.last_rebalance_data(self.id) return dt.datetime.strptime(last_rebalance['date'], '%Y-%m-%d').date() @_validate(ErrorMessage.UNINITIALIZED) def get_rebalance_approval_status(self) -> str: """ Retrieve the most recent rebalance submission's approval status :return: current approval status **Usage** Retrieve the most recent rebalance submission's approval status **Examples** Retrieve the most recent rebalance submission's approval status >>> from gs_quant.markets.baskets import Basket >>> >>> basket = Basket.get("GSMBXXXX") >>> basket.get_rebalance_approval_status() **See also** :func:`cancel_rebalance` :func:`poll_report` """ last_approval = GsIndexApi.last_rebalance_approval(self.id) return get(last_approval, 'status') @_validate(ErrorMessage.NON_ADMIN) def cancel_rebalance(self) -> dict: """ Cancel the most recent rebalance submission **Usage** Cancel the basket's most recent rebalance submission if it has not yet been approved **Examples** Cancel the basket's most recent rebalance submission >>> from gs_quant.markets.baskets import Basket >>> >>> basket = Basket.get("GSMBXXXX") >>> basket.cancel_rebalance() **See also** :func:`get_rebalance_approval_status` :func:`update` """ return GsIndexApi.cancel_rebalance(self.id, CustomBasketsRebalanceAction.default_instance()) @_validate(ErrorMessage.UNINITIALIZED) def get_corporate_actions( self, start: dt.date = DateLimit.LOW_LIMIT.value, end: dt.date = dt.date.today() + dt.timedelta(days=10), ca_type: List[CorporateActionType] = CorporateActionType.to_list(), ) -> pd.DataFrame: """ Retrieve corporate actions for a basket across a date range :param start: start date (default minimum date value) :param end: end date (default is maximum date value) :param ca_type: list of corporate action types (default is all) :return: dataframe with corporate actions information **Usage** Retrieve corporate actions for a basket across a date range **Examples** Retrieve historical acquisition corporate actions for a basket >>> from gs_quant.markets.baskets import Basket >>> from gs_quant.markets.indices_utils import CorporateActionType >>> >>> basket = Basket.get("GSMBXXXX") >>> basket.get_corporate_actions(ca_type=[CorporateActionType.ACQUISITION]) **See also** :func:`get_fundamentals` """ where = dict(assetId=self.id, corporateActionType=ca_type) query = DataQuery(where=where, start_date=start, end_date=end) response = GsDataApi.query_data(query=query, dataset_id=IndicesDatasets.CORPORATE_ACTIONS.value) return pd.DataFrame(response) @_validate(ErrorMessage.UNINITIALIZED) def get_fundamentals( self, start: dt.date = DateLimit.LOW_LIMIT.value, end: dt.date = dt.date.today(), period: DataMeasure = DataMeasure.ONE_YEAR.value, direction: DataMeasure = DataMeasure.FORWARD.value, metrics: List[DataMeasure] = DataMeasure.list_fundamentals(), ) -> pd.DataFrame: """ Retrieve fundamentals data for a basket across a date range :param start: start date (default minimum date value) :param end: end date (default is today) :param period: period for the relevant metric (default is 1y) :param direction: direction of the outlook period (default is forward) :param metrics: list of fundamentals metrics (default is all) :return: dataframe with fundamentals information **Usage** Retrieve fundamentals data for a basket across a date range **Examples** Retrieve historical dividend yield data for a basket >>> from gs_quant.data.fields import DataMeasure >>> from gs_quant.markets.baskets import Basket >>> >>> basket = Basket.get("GSMBXXXX") >>> basket.get_fundamentals(metrics=[DataMeasure.DIVIDEND_YIELD]) **See also** :func:`get_corporate_actions` """ where = dict(assetId=self.id, period=period, periodDirection=direction, metric=metrics) query = DataQuery(where=where, start_date=start, end_date=end) response = GsDataApi.query_data(query=query, dataset_id=IndicesDatasets.BASKET_FUNDAMENTALS.value) return pd.DataFrame(response) @_validate(ErrorMessage.UNINITIALIZED) def get_live_date(self) -> Optional[dt.date]: """ Retrieve basket's live date **Usage** Retrieve basket's live date **Examples** >>> from gs_quant.markets.baskets import Basket >>> >>> basket = Basket.get("GSMBXXXX") >>> basket.get_live_date() """ return self.__live_date def get_type(self) -> Optional[SecAssetType]: """ Retrieve basket type **Usage** Retrieve basket type **Examples** >>> from gs_quant.markets.baskets import Basket >>> >>> basket = Basket.get("GSMBXXXX") >>> basket.get_type() """ if self.__gs_asset_type: return SecAssetType[self.__gs_asset_type.name.upper()] @_validate(ErrorMessage.UNINITIALIZED) def get_latest_position_set( self, position_type: PositionType = PositionType.CLOSE, source: str = "Basket" ) -> PositionSet: if self.positioned_entity_type == EntityType.ASSET: response = GsAssetApi.get_latest_positions(self.id, position_type) return PositionSet.from_target(response, source=source) raise NotImplementedError @_validate(ErrorMessage.UNINITIALIZED) def get_position_set_for_date( self, date: dt.date, position_type: PositionType = PositionType.CLOSE, source: str = "Basket" ) -> PositionSet: if self.positioned_entity_type == EntityType.ASSET: response = GsAssetApi.get_asset_positions_for_date(self.id, date, position_type) if len(response) == 0: _logger.info("No positions available for {}".format(date)) return PositionSet([], date=date) return PositionSet.from_target(response[0], source=source) raise NotImplementedError @_validate(ErrorMessage.UNINITIALIZED) def get_position_sets( self, start: dt.date = DateLimit.LOW_LIMIT.value, end: dt.date = dt.date.today(), position_type: PositionType = PositionType.CLOSE, source: str = "Basket", ) -> List[PositionSet]: if self.positioned_entity_type == EntityType.ASSET: response = GsAssetApi.get_asset_positions_for_dates(self.id, start, end, position_type) if len(response) == 0: _logger.info("No positions available in the date range {} - {}".format(start, end)) return [] return [PositionSet.from_target(position_set, source=source) for position_set in response] raise NotImplementedError @_validate(ErrorMessage.UNINITIALIZED) def get_url(self) -> str: """ Retrieve url to basket's product page in Marquee **Usage** Retrieve url to basket's product page in Marquee **Examples** >>> from gs_quant.markets.baskets import Basket >>> >>> basket = Basket.get("GSMBXXXX") >>> basket.get_url() """ env = '-dev' if 'dev' in get(GsSession, 'current.domain', '') else '' env = '-qa' if 'qa' in get(GsSession, 'current.domain', '') else env return f'https://marquee{env}.gs.com/s/products/{self.id}/summary' @_validate(ErrorMessage.UNINITIALIZED, ErrorMessage.NON_ADMIN) def add_factor_risk_report(self, risk_model_id: str, fx_hedged: bool): """ Create and schedule a new factor risk report for your basket :param risk_model_id: risk model identifier :param fx_hedged: Assume basket is FX hedged **Usage** Create and schedule a new factor risk report for your basket **Examples** >>> from gs_quant.markets.baskets import Basket >>> >>> basket = Basket.get("GSMBXXXX") >>> basket.add_factor_risk_report('AXUS4M', True) **See also** :func:`delete_factor_risk_report` """ payload = CustomBasketRiskParams(risk_model=risk_model_id, fx_hedged=fx_hedged) return GsIndexApi.update_risk_reports(payload) @_validate(ErrorMessage.UNINITIALIZED, ErrorMessage.NON_ADMIN) def delete_factor_risk_report(self, risk_model_id: str): """ Delete an existing factor risk report for your basket :param risk_model_id: risk model identifier for the report you'd like to delete **Usage** Delete an existing factor risk report for your basket **Examples** >>> from gs_quant.markets.baskets import Basket >>> >>> basket = Basket.get("GSMBXXXX") >>> basket.delete_factor_risk_report('AXUS4M') **See also** :func:`add_factor_risk_report` """ payload = CustomBasketRiskParams(risk_model=risk_model_id, delete=True) return GsIndexApi.update_risk_reports(payload) @property def allow_ca_restricted_assets(self) -> Optional[bool]: """Allow basket to have constituents that will not be corporate action adjusted in the future""" return self.__allow_ca_restricted_assets @allow_ca_restricted_assets.setter @_validate(ErrorMessage.NON_ADMIN) def allow_ca_restricted_assets(self, value: bool): self.__allow_ca_restricted_assets = value @property def allow_limited_access_assets(self) -> Optional[bool]: """Allow basket to have constituents that GS has limited access to""" return self.__allow_limited_access_assets @allow_limited_access_assets.setter @_validate(ErrorMessage.NON_ADMIN) def allow_limited_access_assets(self, value: bool): self.__allow_limited_access_assets = value @property def asset_class(self) -> Optional[AssetClass]: """Asset class of the basket""" return self.__asset_class @asset_class.setter @_validate(ErrorMessage.UNMODIFIABLE) def asset_class(self, value: AssetClass): self.__asset_class = value @property def benchmark(self) -> Optional[str]: """Benchmark for a basket""" return self.__benchmark @benchmark.setter @_validate(ErrorMessage.NON_ADMIN, ErrorMessage.NON_INTERNAL) def benchmark(self, value: str): self.__benchmark = value @property def backtest_parameters(self) -> Optional[EqBasketBacktestParameters]: """Backtest parameters for a basket""" return self.__backtest_parameters @backtest_parameters.setter @_validate(ErrorMessage.NON_ADMIN) def backtest_parameters(self, value: EqBasketBacktestParameters): if value is not None: self.__historical_methodology = EqBasketHistoryMethodology.Backtest self.__backtest_parameters = value @property def bloomberg_publish_parameters(self) -> Optional[BloombergPublishParameters]: """Bloomberg publish overrides for a basket""" return self.__bloomberg_publish_parameters @bloomberg_publish_parameters.setter @_validate(ErrorMessage.NON_ADMIN, ErrorMessage.NON_INTERNAL) def bloomberg_publish_parameters(self, value: BloombergPublishParameters): self.__bloomberg_publish_parameters = value @property def cash_reinvestment_treatment(self) -> Optional[CashReinvestmentTreatment]: """Cash reinvestment treatment options for a basket""" return self.__cash_reinvestment_treatment @cash_reinvestment_treatment.setter @_validate(ErrorMessage.NON_ADMIN) def cash_reinvestment_treatment(self, value: Union[CashReinvestmentTreatment, CashReinvestmentTreatmentType]): if isinstance(value, CashReinvestmentTreatmentType): self.__cash_reinvestment_treatment = CashReinvestmentTreatment( cash_acquisition_treatment=value, regular_dividend_treatment=value, special_dividend_treatment=value ) else: self.__cash_reinvestment_treatment = value @property def clone_parent_id(self) -> Optional[str]: """Marquee Id of the source basket, in case basket composition is sourced from another marquee basket""" return self.__clone_parent_id @property def currency(self) -> Optional[IndicesCurrency]: """Denomination of the basket""" return self.__currency @currency.setter @_validate(ErrorMessage.UNMODIFIABLE) def currency(self, value: IndicesCurrency): self.__currency = value @property def default_backcast(self) -> Optional[bool]: """If basket should be backcasted using the current composition""" return self.__default_backcast @default_backcast.setter @_validate(ErrorMessage.UNMODIFIABLE) def default_backcast(self, value: bool): if not value: self.__historical_methodology = EqBasketHistoryMethodology.Custom self.__default_backcast = value @property def description(self) -> Optional[str]: """Free text description of basket""" return self.__description @description.setter @_validate(ErrorMessage.NON_ADMIN) def description(self, value: str): self.__description = value @property @_validate() def divisor(self) -> Optional[float]: """Divisor to be applied to the overall position set""" return self.__divisor @divisor.setter @_validate(ErrorMessage.NON_ADMIN) def divisor(self, value: float): self.__initial_price = None self.__divisor = value @property @_validate() def entitlements(self) -> Optional[BasketEntitlements]: """Basket entitlements""" return self.__entitlements @entitlements.setter @_validate(ErrorMessage.NON_ADMIN) def entitlements(self, value: BasketEntitlements): self.__entitlements = value @property def flagship(self) -> Optional[bool]: """If the basket is flagship (internal only)""" return self.__flagship @flagship.setter @_validate(ErrorMessage.NON_INTERNAL) def flagship(self, value: bool): self.__flagship = value @property def hedge_id(self) -> Optional[str]: """Marquee Id of the source hedge, in case current basket composition is sourced from marquee hedge""" return self.__hedge_id @property def historical_methodology(self) -> Optional[EqBasketHistoryMethodology]: """Historical methodology for a basket""" return self.__historical_methodology @historical_methodology.setter @_validate(ErrorMessage.NON_ADMIN) def historical_methodology(self, value: EqBasketHistoryMethodology): self.__default_backcast = value != EqBasketHistoryMethodology.Custom self.__historical_methodology = value @property def include_price_history(self) -> Optional[bool]: """Include full price history when publishing to Bloomberg""" return self.__include_price_history @include_price_history.setter @_validate(ErrorMessage.NON_ADMIN) def include_price_history(self, value: bool): self.__include_price_history = value @property @_validate() def initial_price(self) -> Optional[float]: """Initial price the basket it should start ticking at""" return self.__initial_price @initial_price.setter @_validate(ErrorMessage.NON_ADMIN) def initial_price(self, value: float): self.__divisor = None self.__initial_price = value @property def name(self) -> Optional[str]: """Display name of the basket (must be <= 24 characters)""" return self.__name @name.setter @_validate(ErrorMessage.NON_ADMIN) def name(self, value: str): if len(value) > 24: _logger.info(f'Basket name of {len(value)} characters is too long (must be <= 24 characters).') self.__name = value @property def parent_basket(self) -> Optional[str]: """Ticker of the source basket, in case current basket composition is sourced from another marquee basket""" if has(self, '__clone_parent_id') and not has(self, '__parent_basket'): self.__parent_basket = get(GsAssetApi.get_asset(self.__clone_parent_id), 'id') return self.__parent_basket @parent_basket.setter @_validate(ErrorMessage.UNMODIFIABLE) def parent_basket(self, value: str): self.__clone_parent_id = get(self.__get_gs_asset(value), 'id') self.__parent_basket = value @property @_validate() def position_set(self) -> Optional[PositionSet]: """Information of constituents associated with the basket""" return self.__position_set @position_set.setter @_validate(ErrorMessage.NON_ADMIN) def position_set(self, value: PositionSet): self.__validate_position_set(value) self.__position_set = value @property def preferred_risk_model(self) -> Optional[str]: """Preferred risk model for a basket""" return self.__preferred_risk_model @preferred_risk_model.setter @_validate(ErrorMessage.NON_ADMIN, ErrorMessage.NON_INTERNAL) def preferred_risk_model(self, value: str): self.__preferred_risk_model = value @property @_validate() def publish_to_bloomberg(self) -> Optional[bool]: """If the basket should be published to Bloomberg""" return self.__publish_to_bloomberg @publish_to_bloomberg.setter @_validate(ErrorMessage.NON_ADMIN) def publish_to_bloomberg(self, value: bool): self.__publish_to_bloomberg = value @property @_validate() def publish_to_factset(self) -> Optional[bool]: """If the basket should be published to Factset""" return self.__publish_to_factset @publish_to_factset.setter @_validate(ErrorMessage.NON_ADMIN) def publish_to_factset(self, value: bool): self.__publish_to_factset = value @property def publish_to_reuters(self) -> Optional[bool]: """If the basket should be published to Reuters""" return self.__publish_to_reuters @publish_to_reuters.setter @_validate(ErrorMessage.NON_ADMIN) def publish_to_reuters(self, value: bool): self.__publish_to_reuters = value @property def rebalance_calendar(self) -> Optional[EqBasketRebalanceCalendar]: """Expected rebalance calender/frequency a basket""" return self.__rebalance_calendar @rebalance_calendar.setter @_validate(ErrorMessage.NON_ADMIN, ErrorMessage.NON_INTERNAL) def rebalance_calendar(self, value: EqBasketRebalanceCalendar): self.__rebalance_calendar = value @property def return_type(self) -> Optional[ReturnType]: """Determines the index calculation methodology with respect to dividend reinvestment""" return self.__return_type @return_type.setter @_validate(ErrorMessage.NON_ADMIN) def return_type(self, value: ReturnType): self.__return_type = value @property def reweight(self) -> Optional[bool]: """To reweight positions if input weights don't add up to 1""" return self.__reweight @reweight.setter @_validate(ErrorMessage.NON_ADMIN) def reweight(self, value: bool): self.__reweight = value @property def target_notional(self) -> Optional[float]: """Target notional for the position set""" return self.__target_notional @target_notional.setter @_validate(ErrorMessage.NON_ADMIN) def target_notional(self, value: float): self.__target_notional = value @property def ticker(self) -> Optional[str]: """Associated 8-character basket identifier""" return self.__ticker @ticker.setter @_validate(ErrorMessage.UNMODIFIABLE) def ticker(self, value: str): self.__validate_ticker(value) self.__ticker = value @property def weighting_strategy(self) -> Optional[WeightingStrategy]: """Strategy used to price the position set""" return self.__weighting_strategy @weighting_strategy.setter @_validate(ErrorMessage.NON_ADMIN) def weighting_strategy(self, value: WeightingStrategy): self.__weighting_strategy = value @property def pricing_date(self) -> Optional[dt.date]: """Pricing date for a rebalance, default to prior day""" return self.__pricing_date @pricing_date.setter @_validate(ErrorMessage.NON_ADMIN, ErrorMessage.RESTRICTED_ATTRIBUTE) def pricing_date(self, value: dt.date): self.__pricing_date = value @property def action_date(self) -> Optional[dt.date]: """Basket action date (user's current date based on timezone)""" return self.__action_date @action_date.setter @_validate(ErrorMessage.NON_ADMIN, ErrorMessage.RESTRICTED_ATTRIBUTE) def action_date(self, value: dt.date): self.__action_date = value @property def allow_system_approval(self) -> Optional[bool]: """To allow system to verify whether the basket is in position or not and approve rebalance without manual intervention when the basket is not in position. Default is false.""" return self.__allow_system_approval @allow_system_approval.setter @_validate(ErrorMessage.NON_ADMIN, ErrorMessage.RESTRICTED_ATTRIBUTE) def allow_system_approval(self, value: bool): self.__allow_system_approval = value def __edit_and_rebalance( self, edit_inputs: CustomBasketsEditInputs, rebal_inputs: CustomBasketsRebalanceInputs ) -> CustomBasketsResponse: """If updates require edit and rebalance, rebal will not be scheduled until/if edit report succeeds""" _logger.info( 'Current update request requires multiple reports. Your rebalance request will be submitted \ once the edit report has completed. Submitting basket edits now...' ) response = GsIndexApi.edit(self.id, edit_inputs) report_id = response.report_id self.__latest_create_report = GsReportApi.get_report(report_id) report_status = self.poll_report(report_id, timeout=600, step=15) if report_status != ReportStatus.done: raise MqError( f'The basket edit report\'s status is {report_status}. The current rebalance request will \ not be submitted in the meantime.' ) _logger.info('Your basket edits have completed successfully. Submitting rebalance request now...') response = GsIndexApi.rebalance(self.id, rebal_inputs) return response def __finish_initialization(self): """Fetches remaining data not retrieved during basket initialization""" if has(self, 'id'): if not has(self, '__initial_positions'): position_set = GsAssetApi.get_latest_positions(self.id, PositionType.ANY) position_set = PositionSet.from_target(position_set) self.__position_set = position_set self.__divisor = get(position_set, 'divisor') self.__initial_positions = set(deepcopy(self.__position_set.positions)) set_(self.__initial_state, 'divisor', self.__divisor) set_(self.__initial_state, 'position_set', self.__position_set) if not has(self.__initial_state, 'initial_price'): initial_price = GsIndexApi.initial_price(self.id, dt.date.today()) self.__initial_price = get(initial_price, 'price') set_(self.__initial_state, 'initial_price', self.__initial_price) if not has(self.__initial_state, 'publish_to_bloomberg'): report_params = self.__get_latest_create_report().parameters self.__publish_to_bloomberg = report_params.publish_to_bloomberg self.__publish_to_factset = report_params.publish_to_factset self.__publish_to_reuters = report_params.publish_to_reuters self.__backtest_parameters = report_params.backtest_parameters self.__bloomberg_publish_parameters = report_params.bloomberg_publish_parameters set_(self.__initial_state, 'publish_to_bloomberg', self.__publish_to_bloomberg) set_(self.__initial_state, 'publish_to_factset', self.__publish_to_factset) set_(self.__initial_state, 'publish_to_reuters', self.__publish_to_reuters) set_(self.__initial_state, 'bloomberg_publish_parameters', self.__bloomberg_publish_parameters) set_(self.__initial_state, 'backtest_parameters', self.__backtest_parameters) if not has(self, '__entitlements'): self.__entitlements = BasketEntitlements.from_target(self.__initial_entitlements) self.__set_error_messages() @staticmethod def __get_gs_asset(identifier: str) -> GsAsset: """Resolves basket identifier during initialization""" response = GsAssetApi.resolve_assets(identifier=[identifier], fields=['id'], limit=1)[identifier] if len(response) == 0 or get(response, '0.id') is None: raise MqValueError(f'Basket could not be found using identifier {identifier}') return GsAssetApi.get_asset(get(response, '0.id')) def __get_latest_create_report(self) -> Report: """Used to find basket's most recent price/publish info""" report = GsReportApi.get_reports( limit=1, position_source_id=self.id, report_type='Basket Create', order_by='>latestExecutionTime' ) return get(report, '0') def __get_updates(self) -> Tuple[Optional[CustomBasketsEditInputs], Optional[CustomBasketsRebalanceInputs]]: """Compares initial and current basket state to determine if updates require edit/rebalance""" edit_inputs, eligible_for_edit = {}, False rebal_inputs, eligible_for_rebal = {}, False pricing, pricing_updated = {}, False publish, publish_updated = {}, False positions = set(self.position_set.positions) positions_updated = self.__initial_positions != positions for prop in CustomBasketsEditInputs.properties(): if prop not in ['action_date'] and get(self.__initial_state, prop) != get(self, prop): eligible_for_edit = True set_(edit_inputs, prop, get(self, prop)) for prop in CustomBasketsRebalanceInputs.properties(): if prop not in ['position_set', 'allow_system_approval', 'action_date'] and get( self.__initial_state, prop ) != get(self, prop): eligible_for_rebal = True if prop != 'position_set': set_(rebal_inputs, prop, get(self, prop)) for prop in CustomBasketsPricingParameters.properties(): if get(self.__initial_state, prop) != get(self, prop): pricing_updated = True set_(pricing, prop, get(self, prop)) for prop in PublishParameters.properties(): if get(self.__initial_state, prop) != get(self, prop): publish_updated = True set_(publish, prop, get(self, prop)) # Since edit and rebalance have some shared inputs, we need to decide which action(s) to perform # 1. rebalance if pricing/positions have changed or if rebalance specific metadata has been updated # 2. edit if edit metadata has changed, or if only publishing options have changed (rebal not required) should_rebal = pricing_updated or positions_updated or (eligible_for_rebal and not eligible_for_edit) should_edit = (publish_updated and not should_rebal) or eligible_for_edit # handle nested objects and make sure required/default values are input correctly if should_rebal: if positions_updated: set_(rebal_inputs, 'position_set', self.position_set.to_target(common=False)) if pricing_updated: set_(rebal_inputs, 'pricing_parameters', CustomBasketsPricingParameters(**pricing)) if publish_updated: set_(rebal_inputs, 'publish_parameters', PublishParameters(**publish)) if should_edit: set_(edit_inputs, 'publish_parameters', PublishParameters(**publish)) edit_inputs = CustomBasketsEditInputs(**edit_inputs) if should_edit else None rebal_inputs = CustomBasketsRebalanceInputs(**rebal_inputs) if should_rebal else None return edit_inputs, rebal_inputs def __populate_current_attributes_for_existing_basket(self, gs_asset: GsAsset): """Current basket settings for existing basket""" self.__benchmark = get(gs_asset, 'parameters.benchmark') self.__cash_reinvestment_treatment = get(gs_asset, 'parameters.cashReinvestmentTreatment') self.__clone_parent_id = get(gs_asset, 'parameters.cloneParentId') self.__default_backcast = get(gs_asset, 'parameters.defaultBackcast') self.__description = get(gs_asset, 'description') self.__flagship = get(gs_asset, 'parameters.flagship') self.__gs_asset_type = get(gs_asset, 'type') self.__hedge_id = get(gs_asset, 'parameters.hedgeId') self.__historical_methodology = get(gs_asset, 'parameters.historicalMethodology') self.__include_price_history = False self.__live_date = get(gs_asset, 'liveDate') self.__preferred_risk_model = get(gs_asset, 'parameters.preferredRiskModel') self.__rebalance_calendar = get(gs_asset, 'parameters.rebalanceFrequency') self.__return_type = get(gs_asset, 'parameters.indexCalculationType') self.__ticker = get(gs_asset, 'xref.ticker') self.__initial_state = {} for prop in CustomBasketsEditInputs.properties().union( CustomBasketsRebalanceInputs.properties(), CustomBasketsPricingParameters.properties(), PublishParameters.properties(), ): set_(self.__initial_state, prop, get(self, prop)) def __populate_default_attributes_for_new_basket(self, **kwargs): """Default basket settings prior to creation""" self.__allow_ca_restricted_assets = get(kwargs, 'allow_ca_restricted_assets') self.__allow_limited_access_assets = get(kwargs, 'allow_limited_access_assets') self.__backtest_parameters = get(kwargs, 'backtest_parameters') self.__benchmark = get(kwargs, 'benchmark') self.__cash_reinvestment_treatment = get( kwargs, 'cash_reinvestment_treatment', CashReinvestmentTreatment( cash_acquisition_treatment=CashReinvestmentTreatmentType.Reinvest_At_Open, regular_dividend_treatment=CashReinvestmentTreatmentType.Reinvest_At_Open, special_dividend_treatment=CashReinvestmentTreatmentType.Reinvest_At_Open, ), ) self.__clone_parent_id = get(kwargs, 'clone_parent_id') self.__currency = get(kwargs, 'currency') self.__default_backcast = get(kwargs, 'default_backcast', True) self.__description = get(kwargs, 'description') self.__divisor = get(kwargs, 'divisor') self.__hedge_id = get(kwargs, 'hedge_id') self.__historical_methodology = get(kwargs, 'historical_methodology', EqBasketHistoryMethodology.Backcast) self.__include_price_history = get(kwargs, 'include_price_history', False) self.__initial_price = get(kwargs, 'initial_price', 100) if self.__divisor is None else None self.__name = get(kwargs, 'name') self.__parent_basket = get(kwargs, 'parent_basket') if self.__parent_basket is not None and self.__clone_parent_id is None: self.__clone_parent_id = get(self.__get_gs_asset(self.__parent_basket), 'id') self.__position_set = get(kwargs, 'position_set') self.__preferred_risk_model = get(kwargs, 'preferred_risk_model') self.__publish_to_bloomberg = get(kwargs, 'publish_to_bloomberg', True) self.__publish_to_factset = get(kwargs, 'publish_to_factset', False) self.__publish_to_reuters = get(kwargs, 'publish_to_reuters', False) self.__rebalance_calendar = get(kwargs, 'rebalance_calendar') self.__return_type = get(kwargs, 'return_type') self.__target_notional = get(kwargs, 'target_notional', 10000000) self.__ticker = get(kwargs, 'ticker') def __set_error_messages(self): """Errors to check for based on current user/basket state""" if len(get(self, '__error_messages', [])) > 0: return errors = [] user_tokens = get(GsUsersApi.get_current_user_info(), 'tokens', []) if 'internal' not in user_tokens: errors.append(ErrorMessage.NON_INTERNAL) if 'internal' not in user_tokens and 'group:EqBasketRestrictedAttributes' not in user_tokens: errors.append(ErrorMessage.RESTRICTED_ATTRIBUTE) if not has(self, 'id'): errors.append(ErrorMessage.UNINITIALIZED) else: errors.append(ErrorMessage.UNMODIFIABLE) tokens = set(get(self.__initial_entitlements, 'admin', [])) if not any(t in user_tokens for t in tokens): errors.append(ErrorMessage.NON_ADMIN) self.__error_messages = set(errors) @staticmethod def __validate_position_set(position_set: PositionSet): position_set.resolve() neg_pos = [p.identifier for p in position_set.positions if (p.weight or 1) < 0 or (p.quantity or 1) < 0] if len(neg_pos): raise MqValueError( f'Position weights/quantities must be positive. Found negative values for date \ {position_set.date}: {neg_pos}' ) if position_set.unresolved_positions is not None and len(position_set.unresolved_positions): raise MqValueError( f'Error in resolving the following identifiers for date {position_set.date}: \ {[p.identifier for p in position_set.unresolved_positions]}' ) @staticmethod def __validate_ticker(ticker: str): """Blocks ticker setter if entry is invalid""" if not len(ticker) == 8: raise MqValueError('Invalid ticker: must be 8 characters') GsIndexApi.validate_ticker(ticker) if not ticker[:2] == 'GS': _logger.info( 'Remember to prefix your ticker with \'GS\' if you\'d like to \ publish your basket to Bloomberg' ) ================================================ FILE: gs_quant/markets/core.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import asyncio import datetime as dt import logging import sys import weakref from abc import ABCMeta from concurrent.futures import ThreadPoolExecutor from inspect import signature from itertools import zip_longest, takewhile from typing import Optional, Union, Type from tqdm import tqdm from gs_quant.base import InstrumentBase, RiskKey, Scenario, get_enum_value from gs_quant.common import PricingLocation, RiskMeasure, PricingDateAndMarketDataAsOf from gs_quant.context_base import ContextBaseWithDefault from gs_quant.datetime.date import business_day_offset, today from gs_quant.risk import ( CompositeScenario, DataFrameWithInfo, ErrorValue, FloatWithInfo, MarketDataScenario, StringWithInfo, ) from gs_quant.risk.results import PricingFuture from gs_quant.session import GsSession from gs_quant.target.risk import RiskPosition, RiskRequest, RiskRequestParameters from gs_quant.tracing import Tracer from .markets import CloseMarket, LiveMarket, Market, close_market_date, OverlayMarket, RelativeMarket from ..api.risk import GenericRiskApi _logger = logging.getLogger(__name__) CacheResult = Union[DataFrameWithInfo, FloatWithInfo, StringWithInfo] class PricingCache(metaclass=ABCMeta): """ Weakref cache for instrument calcs """ __cache = weakref.WeakKeyDictionary() @classmethod def clear(cls): __cache = weakref.WeakKeyDictionary() @classmethod def get(cls, risk_key: RiskKey, instrument: InstrumentBase) -> Optional[CacheResult]: return cls.__cache.get(instrument, {}).get(risk_key) @classmethod def put(cls, risk_key: RiskKey, instrument: InstrumentBase, result: CacheResult): if not isinstance(result, ErrorValue) and not isinstance(risk_key.market, LiveMarket): cls.__cache.setdefault(instrument, {})[risk_key] = result @classmethod def drop(cls, instrument: InstrumentBase): if instrument in cls.__cache: cls.__cache.pop(instrument) class PricingContext(ContextBaseWithDefault): """ A context for controlling pricing and market data behaviour """ def __init__( self, pricing_date: Optional[dt.date] = None, market_data_location: Optional[Union[PricingLocation, str]] = None, is_async: bool = None, is_batch: bool = None, use_cache: bool = None, visible_to_gs: Optional[bool] = None, request_priority: Optional[int] = None, csa_term: Optional[str] = None, timeout: Optional[int] = None, market: Optional[Market] = None, show_progress: Optional[bool] = None, use_server_cache: Optional[bool] = None, market_behaviour: Optional[str] = 'ContraintsBased', set_parameters_only: bool = False, use_historical_diddles_only: Optional[bool] = None, provider: Optional[Type[GenericRiskApi]] = None, ): """ The methods on this class should not be called directly. Instead, use the methods on the instruments, as per the examples :param pricing_date: the date for pricing calculations. Default is today :param market_data_location: the location for sourcing market data ('NYC', 'LDN' or 'HKG' (defaults to LDN) :param is_async: if True, return (a future) immediately. If False, block (defaults to False) :param is_batch: use for calculations expected to run longer than 3 mins, to avoid timeouts. It can be used with is_async=True|False (defaults to False) :param use_cache: store results in the pricing cache (defaults to False) :param visible_to_gs: are the contents of risk requests visible to GS (defaults to False) :param request_priority: the priority of risk requests :param csa_term: the csa under which the calculations are made. Default is local ccy ois index :param timeout: the timeout for batch operations :param market: a Market object :param show_progress: add a progress bar (tqdm) :param use_server_cache: cache query results on the GS servers :param market_behaviour: the behaviour to build the curve for pricing ('ContraintsBased' or 'Calibrated' (defaults to ContraintsBased)) :param set_parameters_only: if true don't stop embedded pricing contexts submitting their jobs. :param use_historical_diddles_only: if true only use historical diddles :param provider: GenericRiskApi implementation to use for pricing requests **Examples** To change the market data location of the default context: >>> from gs_quant.markets import PricingContext >>> >>> PricingContext.current = PricingContext(market_data_location='LDN') For a blocking, synchronous request: >>> from gs_quant.instrument import IRCap >>> cap = IRCap('5y', 'GBP') >>> >>> with PricingContext(): >>> price_f = cap.dollar_price() >>> >>> price = price_f.result() For an asynchronous request: >>> with PricingContext(is_async=True): >>> price_f = cap.dollar_price() >>> >>> while not price_f.done: >>> ... """ super().__init__() if ( market and market_data_location and market.location is not get_enum_value(PricingLocation, market_data_location) ): raise ValueError('market.location and market_data_location cannot be different') if not market and pricing_date and pricing_date > dt.date.today() + dt.timedelta(5): # We allow a small tolerance to rolling over weekends/holidays # We should use a calendar but not everyone has access raise ValueError( 'The PricingContext does not support a pricing_date in the future. Please use the RollFwd Scenario ' 'to roll the pricing_date to a future date' ) if market: market_date = None if isinstance(market, OverlayMarket) or isinstance(market, CloseMarket): market_date = getattr(market, 'date', None) or getattr(market.market, 'date', None) if isinstance(market, RelativeMarket): market_date = ( market.market.from_market.date if market.market.from_market.date > dt.date.today() else market.market.to_market.date ) if market_date: if market_date > dt.date.today(): raise ValueError( 'The PricingContext does not support a market dated in the future. Please use the RollFwd ' 'Scenario to roll the pricing_date to a future date' ) if not market_data_location: if market: market_data_location = market.location market_data_location = get_enum_value(PricingLocation, market_data_location) self.__pricing_date = pricing_date self.__csa_term = csa_term self.__market_behaviour = market_behaviour self.__is_async = is_async self.__is_batch = is_batch self.__timeout = timeout self.__use_cache = use_cache self.__visible_to_gs = visible_to_gs self.__request_priority = request_priority self.__market_data_location = market_data_location self.__market = market self.__show_progress = show_progress self.__use_server_cache = use_server_cache self.__provider = provider self.__max_per_batch = None self.__max_concurrent = None self.__dates_per_batch = None self.__use_historical_diddles_only = use_historical_diddles_only self.__set_parameters_only = set_parameters_only self.__pending = {} self._group_by_date = True self.__attrs_on_entry = {} def __save_attrs_to(self, attr_dict): attr_dict['pricing_date'] = self.__pricing_date attr_dict['csa_term'] = self.__csa_term attr_dict['market_behaviour'] = self.__market_behaviour attr_dict['is_batch'] = self.__is_batch attr_dict['is_async'] = self.__is_async attr_dict['timeout'] = self.__timeout attr_dict['use_cache'] = self.__use_cache attr_dict['visible_to_gs'] = self.__visible_to_gs attr_dict['request_priority'] = self.__request_priority attr_dict['market_data_location'] = self.__market_data_location attr_dict['market'] = self.__market attr_dict['show_progress'] = self.__show_progress attr_dict['use_server_cache'] = self.__use_server_cache attr_dict['provider'] = self.__provider attr_dict['_max_concurrent'] = self.__max_concurrent attr_dict['_max_per_batch'] = self.__max_per_batch attr_dict['_dates_per_batch'] = self.__dates_per_batch attr_dict['use_historical_diddles_only'] = self.__use_historical_diddles_only def _inherited_val(self, parameter, default=None, from_active=False): if from_active: # some properties are inherited from the active context if self != self.active_context and getattr(self.active_context, parameter) is not None: return getattr(self.active_context, parameter) if not self.is_entered and (not PricingContext.has_prior or self is not PricingContext.prior): # if not yet entered, get property from current (would-be prior) so that getters still display correctly if ( PricingContext.current is not self and PricingContext.current and getattr(PricingContext.current, parameter) is not None ): return getattr(PricingContext.current, parameter) else: # if entered, inherit from the prior if ( PricingContext.has_prior and PricingContext.prior is not self and getattr(PricingContext.prior, parameter) is not None ): return getattr(PricingContext.prior, parameter) # default if nothing to inherit return default def _on_enter(self): self.__save_attrs_to(self.__attrs_on_entry) self.__market_data_location = self.market_data_location self.__pricing_date = self.pricing_date self.__market = self.market self.__csa_term = self.csa_term self.__market_behaviour = self.market_behaviour self.__is_async = self.is_async self.__is_batch = self.is_batch self.__timeout = self.timeout self.__use_cache = self.use_cache self.__visible_to_gs = self.visible_to_gs self.__request_priority = self.request_priority self.__show_progress = self.show_progress self.__use_server_cache = self.use_server_cache self.__provider = self.provider self.__max_concurrent = self._max_concurrent self.__max_per_batch = self._max_per_batch self.__dates_per_batch = self._dates_per_batch self.__use_historical_diddles_only = self.use_historical_diddles_only def __reset_atts(self): self.__pricing_date = self.__attrs_on_entry.get('pricing_date') self.__csa_term = self.__attrs_on_entry.get('csa_term') self.__market_behaviour = self.__attrs_on_entry.get('market_behaviour') self.__is_async = self.__attrs_on_entry.get('is_async') self.__is_batch = self.__attrs_on_entry.get('is_batch') self.__timeout = self.__attrs_on_entry.get('timeout') self.__use_cache = self.__attrs_on_entry.get('use_cache') self.__visible_to_gs = self.__attrs_on_entry.get('visible_to_gs') self.__request_priority = self.__attrs_on_entry.get('request_priority') self.__market_data_location = self.__attrs_on_entry.get('market_data_location') self.__market = self.__attrs_on_entry.get('market') self.__show_progress = self.__attrs_on_entry.get('show_progress') self.__use_server_cache = self.__attrs_on_entry.get('use_server_cache') self.__provider = self.__attrs_on_entry.get('provider') self.__max_concurrent = self.__attrs_on_entry.get('_max_concurrent') self.__max_per_batch = self.__attrs_on_entry.get('_max_per_batch') self.__dates_per_batch = self.__attrs_on_entry.get('_dates_per_batch') self.__attrs_on_entry = {} def _on_exit(self, exc_type, exc_val, exc_tb): try: if exc_val: raise exc_val else: self.__calc() finally: self.__reset_atts() def __calc(self): def run_requests(requests_: list, provider_, create_event_loop: bool, pc_attrs: dict, span): if create_event_loop: asyncio.set_event_loop(asyncio.new_event_loop()) provider_.populate_pending_futures( requests_, session, self.__pending, max_concurrent=pc_attrs['_max_concurrent'], progress_bar=progress_bar, timeout=pc_attrs['timeout'], span=span, cache_impl=PricingCache if pc_attrs['use_cache'] else None, is_async=pc_attrs['is_async'], ) # Group requests optimally requests_by_provider = {} for key, instrument in self.__pending.keys(): dates_markets, measures = ( requests_by_provider.setdefault(key.provider, {}) .setdefault((key.params, key.scenario), {}) .setdefault(instrument, (set(), set())) ) dates_markets.add((key.date, key.market)) measures.add(key.risk_measure) requests_for_provider = {} if requests_by_provider: session = GsSession.current request_visible_to_gs = session.is_internal() if self.__visible_to_gs is None else self.__visible_to_gs for provider, by_params_scenario in requests_by_provider.items(): grouped_requests = {} for (params, scenario), positions_by_dates_markets_measures in by_params_scenario.items(): for instrument, (dates_markets, risk_measures) in positions_by_dates_markets_measures.items(): grouped_requests.setdefault( (params, scenario, tuple(sorted(dates_markets)), tuple(sorted(risk_measures))), [] ).append(instrument) requests = [] # Restrict to 1,000 instruments and 1 date in a batch, until server side changes are made for (params, scenario, dates_markets, risk_measures), instruments in grouped_requests.items(): date_chunk_size = ( (self._dates_per_batch if self._group_by_date else self._max_per_batch) if provider.batch_dates else len(dates_markets) ) for insts_chunk in [ tuple(filter(None, i)) for i in zip_longest(*[iter(instruments)] * self._max_per_batch) ]: for dates_chunk in [ tuple(filter(None, i)) for i in zip_longest(*[iter(dates_markets)] * date_chunk_size) ]: requests.append( RiskRequest( tuple( RiskPosition( instrument=i, quantity=i.instrument_quantity, instrument_name=i.name ) for i in insts_chunk ), risk_measures, parameters=params, wait_for_results=not self.__is_batch, scenario=scenario, pricing_and_market_data_as_of=tuple( PricingDateAndMarketDataAsOf(pricing_date=d, market=m) for d, m in dates_chunk ), request_visible_to_gs=request_visible_to_gs, use_cache=self.__use_server_cache, priority=self.__request_priority, ) ) requests_for_provider[provider] = requests show_status = self.__show_progress and ( len(requests_for_provider) > 1 or len(next(iter(requests_for_provider.values()))) > 1 ) request_pool = ( ThreadPoolExecutor(len(requests_for_provider)) if len(requests_for_provider) > 1 or self.__is_async else None ) progress_bar = ( tqdm(total=len(self.__pending), position=0, maxinterval=1, file=sys.stdout) if show_status else None ) completion_futures = [] # Requests might get dispatched asynchronously and the PricingContext gets cleaned up on exit. # We should use a saved state of the object when dispatching async requests, except for self.__pending # All attributes are immutable, so a shared dictionary is sufficient. __pending remains shared. attrs_for_request = {} self.__save_attrs_to(attrs_for_request) all_futures_count = len(requests_for_provider) span = Tracer.active_span() if self.__is_async and span and span.is_recording(): # if we are in async mode we can't be certain that the `span` here will still be recording by the time # the request_pool threads execute. So we create a sub-span to track the dispatch activity. # We only finish that span when all futures are complete # e.g. would have been a problem if you did # with Tracer('blah'), PricingContext(is_async=True): # something.calc(risk.SomeRisk) sub_scope = Tracer.start_active_span('async-request-dispatch') span = sub_scope.span def handle_fut_res(f): nonlocal all_futures_count all_futures_count -= 1 if all_futures_count == 0: Tracer.activate_span(span, finish_on_close=True).close() for provider, requests in requests_for_provider.items(): if request_pool: completion_future = request_pool.submit( run_requests, requests, provider, True, attrs_for_request, span ) if self.__is_async: completion_future.add_done_callback(handle_fut_res) else: # We append to this list when not async as these are the ones we wait on before returning completion_futures.append(completion_future) else: run_requests(requests, provider, False, attrs_for_request, span) # Wait on results if not async, so exceptions are surfaced if request_pool: request_pool.shutdown(False) all(f.result() for f in completion_futures) def __risk_key(self, risk_measure: RiskMeasure, provider: type) -> RiskKey: return RiskKey(provider, self.__pricing_date, self.__market, self._parameters, self._scenario, risk_measure) @property def _parameters(self) -> RiskRequestParameters: return RiskRequestParameters( csa_term=self.__csa_term, raw_results=True, market_behaviour=self.__market_behaviour, use_historical_diddles_only=self.use_historical_diddles_only, ) @property def _scenario(self) -> Optional[MarketDataScenario]: scenarios = Scenario.path if not scenarios: return None return MarketDataScenario( scenario=scenarios[0] if len(scenarios) == 1 else CompositeScenario(scenarios=tuple(reversed(scenarios))) ) @property def active_context(self): # active context cannot be below self on the stack - this also prevents infinite recursion when inheriting path = takewhile(lambda x: x != self, reversed(PricingContext.path)) return next((c for c in path if c.is_entered and not c.set_parameters_only), self) @property def is_current(self) -> bool: return self == PricingContext.current @property def _max_concurrent(self) -> int: return self.__max_concurrent if self.__max_concurrent else self._inherited_val('_max_concurrent', default=1000) @_max_concurrent.setter def _max_concurrent(self, value): self.__max_concurrent = value @property def _max_per_batch(self) -> int: return self.__max_per_batch if self.__max_per_batch else self._inherited_val('_max_per_batch', default=1000) @_max_per_batch.setter def _max_per_batch(self, value): self.__max_per_batch = value @property def _dates_per_batch(self) -> int: return self.__dates_per_batch if self.__dates_per_batch else self._inherited_val('_dates_per_batch', default=1) @_dates_per_batch.setter def _dates_per_batch(self, value): self.__dates_per_batch = value @property def is_async(self) -> bool: if self.__is_async is not None: return self.__is_async return self._inherited_val('is_async', default=False) @property def is_batch(self) -> bool: return self.__is_batch if self.__is_batch else self._inherited_val('is_batch', default=False) @property def market(self) -> Market: return ( self.__market if self.__market else CloseMarket( date=close_market_date(self.market_data_location, self.pricing_date, CloseMarket.roll_hr_and_min), location=self.market_data_location, ) ) @property def market_data_location(self) -> PricingLocation: return ( self.__market_data_location if self.__market_data_location else self._inherited_val('market_data_location', from_active=True, default=PricingLocation.LDN) ) @property def csa_term(self) -> str: return self.__csa_term if self.__csa_term else self._inherited_val('csa_term') @property def show_progress(self) -> bool: return self.__show_progress if self.__show_progress else self._inherited_val('show_progress', default=False) @property def timeout(self) -> int: return self.__timeout if self.__timeout else self._inherited_val('timeout') @property def request_priority(self) -> int: return self.__request_priority if self.__request_priority else self._inherited_val('request_priority') @property def use_server_cache(self) -> bool: return ( self.__use_server_cache if self.__use_server_cache is not None else self._inherited_val('use_server_cache', False) ) @property def provider(self) -> Type[GenericRiskApi]: return self.__provider if self.__provider is not None else self._inherited_val('provider', None) @property def market_behaviour(self) -> str: return ( self.__market_behaviour if self.__market_behaviour else self._inherited_val('market_behaviour', default='ContraintsBased') ) @property def pricing_date(self) -> dt.date: """Pricing date""" if self.__pricing_date is not None: return self.__pricing_date default_pricing_date = business_day_offset(today(self.market_data_location), 0, roll='preceding') return self._inherited_val('pricing_date', default=default_pricing_date) @property def use_cache(self) -> bool: """Cache results""" return self.__use_cache if self.__use_cache else self._inherited_val('use_cache', default=False) @property def visible_to_gs(self) -> Optional[bool]: """Request contents visible to GS""" return self.__visible_to_gs if self.__visible_to_gs else self._inherited_val('visible_to_gs') @property def set_parameters_only(self) -> bool: return self.__set_parameters_only @property def use_historical_diddles_only(self) -> bool: if self.__use_historical_diddles_only is not None: return self.__use_historical_diddles_only return self._inherited_val('use_historical_diddles_only', default=False) def clone(self, **kwargs): clone_kwargs = {k: getattr(self, k, None) for k in signature(self.__init__).parameters.keys()} clone_kwargs.update(kwargs) return self.__class__(**clone_kwargs) def _calc(self, instrument: InstrumentBase, risk_key: RiskKey) -> PricingFuture: pending = self.active_context.__pending from gs_quant.instrument import DummyInstrument if isinstance(instrument, DummyInstrument): return PricingFuture(StringWithInfo(value=instrument.dummy_result, risk_key=risk_key)) future = pending.get((risk_key, instrument)) if future is None: future = PricingFuture() cached_result = PricingCache.get(risk_key, instrument) if self.use_cache else None if cached_result is not None: future.set_result(cached_result) else: pending[(risk_key, instrument)] = future return future def calc(self, instrument: InstrumentBase, risk_measure: RiskMeasure) -> PricingFuture: """ Calculate the risk measure for the instrument. Do not use directly, use via instruments :param instrument: The instrument :param risk_measure: The measure we wish to calculate :return: A PricingFuture whose result will be the calculation result **Examples** >>> from gs_quant.instrument import IRSwap >>> from gs_quant.risk import IRDelta >>> >>> swap = IRSwap('Pay', '10y', 'USD', fixed_rate=0.01) >>> delta = swap.calc(IRDelta) """ provider = instrument.provider if self.provider is None else self.provider return self._calc(instrument, self.__risk_key(risk_measure, provider)) class PositionContext(ContextBaseWithDefault): """ A context for controlling portfolio position behaviour """ def __init__(self, position_date: Optional[dt.date] = None): """ The methods on this class should not be called directly. Instead, use the methods on the portfolios, as per the examples :param position_date: the date for pricing calculations. Default is today **Examples** To change the position date of the default context: >>> from gs_quant.markets import PositionContext >>> import datetime >>> >>> PricingContext.current = PositionContext(datetime.date(2021, 1, 2)) For a pricing a portfolio with positions held on a specific date: >>> from gs_quant.markets.portfolio import Portfolio >>> portfolio = Portfolio.get(portfolio_id='MQPORTFOLIOID') >>> >>> with PositionContext(): >>> portfolio.price() >>> For an asynchronous request: >>> with PositionContext(), PricingContext(is_async=True): >>> price_f = portfolio.price() >>> >>> while not price_f.done: >>> ... """ super().__init__() if position_date: if position_date > dt.date.today(): raise ValueError("The PositionContext does not support a position_date in the future") self.__position_date = ( position_date if position_date else business_day_offset(dt.date.today(), 0, roll='preceding') ) @property def position_date(self): return self.__position_date @classmethod def default_value(cls) -> object: return PositionContext() def clone(self, **kwargs): clone_kwargs = {k: getattr(self, k, None) for k in signature(self.__init__).parameters.keys()} clone_kwargs.update(kwargs) return self.__class__(**clone_kwargs) ================================================ FILE: gs_quant/markets/factor.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import math from enum import auto, Enum from typing import Dict, Union import numpy as np import pandas as pd from gs_quant.api.gs.risk_models import ( GsFactorRiskModelApi, RiskModelDataMeasure, RiskModelDataAssetsRequest, IntradayFactorDataSource, ) from gs_quant.data.core import DataContext from gs_quant.datetime import date, time from gs_quant.models.risk_model_utils import ( get_covariance_matrix_dataframe, build_factor_volatility_dataframe, build_factor_data_map, build_pfp_data_dataframe, ) from gs_quant.target.risk_models import RiskModelUniverseIdentifierRequest class ReturnFormat(Enum): """Alternative format for data to be returned from get_data functions""" JSON = auto() DATA_FRAME = auto() class Factor: def __init__( self, risk_model_id: str, id_: str, type_: str, name: str = None, category: str = None, tooltip: str = None, description: str = None, glossary_description: str = None, ): self.__risk_model_id = risk_model_id self.__id = id_ self.__name = name self.__type = type_ self.__category = category self.__tooltip = tooltip self.__description = description self.__glossary_description = glossary_description @property def id(self): return self.__id @property def name(self): return self.__name @property def type(self) -> str: return self.__type @property def category(self) -> str: return self.__category @property def tooltip(self): return self.__tooltip @property def description(self): return self.__description @property def glossary_description(self): return self.__glossary_description @property def risk_model_id(self): return self.__risk_model_id def covariance( self, factor: 'Factor', start_date: date = DataContext.current.start_date, end_date: date = DataContext.current.end_date, format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[Dict, pd.DataFrame]: """Retrieve a Dataframe or Dictionary of date->covariance values between this factor and another for a date range""" covariance_data_raw = GsFactorRiskModelApi.get_risk_model_data( self.risk_model_id, start_date, end_date, measures=[ RiskModelDataMeasure.Covariance_Matrix, RiskModelDataMeasure.Factor_Name, RiskModelDataMeasure.Factor_Id, ], factors=list({self.name, factor.name}), limit_factors=False, ).get('results') covariance_data_raw = get_covariance_matrix_dataframe(covariance_data_raw) covariance_data_df = covariance_data_raw.stack().loc[pd.IndexSlice[:, self.name, factor.name]] * 252 if format == ReturnFormat.JSON: return covariance_data_df.to_dict() return covariance_data_df.to_frame(name="covariance") def variance( self, start_date: date = DataContext.current.start_date, end_date: date = DataContext.current.end_date, format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[Dict, pd.DataFrame]: """Retrieve a Dataframe or Dictionary of date->variance values for a factor over a date range""" variance_raw_data = self.covariance(self, start_date, end_date, ReturnFormat.DATA_FRAME).rename( columns={"covariance": "variance"} ) if format == ReturnFormat.JSON: return variance_raw_data.squeeze().to_dict() return variance_raw_data def volatility( self, start_date: date = DataContext.current.start_date, end_date: date = DataContext.current.end_date, format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[Dict, pd.DataFrame]: """Retrieve a Dataframe or Dictionary of date->volatility values for a factor over a date range""" volatility_raw_data = GsFactorRiskModelApi.get_risk_model_data( self.risk_model_id, start_date, end_date, measures=[ RiskModelDataMeasure.Factor_Volatility, RiskModelDataMeasure.Factor_Id, RiskModelDataMeasure.Factor_Name, ], factors=[self.name], limit_factors=False, ).get('results') volatility_data_df = build_factor_volatility_dataframe(volatility_raw_data, True, None) * math.sqrt(252) if format == ReturnFormat.JSON: return volatility_data_df.squeeze(axis=1).to_dict() return volatility_data_df def correlation( self, other_factor, start_date: date = DataContext.current.start_date, end_date: date = DataContext.current.end_date, format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[Dict, pd.DataFrame]: """Retrieve a Dataframe or Dictionary of date->correlation values between this factor and another for a date range""" raw_data = GsFactorRiskModelApi.get_risk_model_data( self.risk_model_id, start_date, end_date, measures=[ RiskModelDataMeasure.Covariance_Matrix, RiskModelDataMeasure.Factor_Name, RiskModelDataMeasure.Factor_Id, ], factors=[self.name, other_factor.name], limit_factors=False, ).get('results') covariance_data_raw = get_covariance_matrix_dataframe(raw_data) * 252 numerator = covariance_data_raw.stack().loc[pd.IndexSlice[:, self.name, other_factor.name]] denominator = np.sqrt( covariance_data_raw.stack().loc[pd.IndexSlice[:, self.name, self.name]] * covariance_data_raw.stack().loc[pd.IndexSlice[:, other_factor.name, other_factor.name]] ) correlation_df = numerator / denominator if format == ReturnFormat.JSON: return correlation_df.to_dict() return correlation_df.to_frame(name="correlation") def returns( self, start_date: date = DataContext.current.start_date, end_date: date = DataContext.current.end_date, format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[Dict, pd.DataFrame]: """Retrieve a Dataframe or Dictionary of date->factor return values for a date range""" factor_returns_raw = GsFactorRiskModelApi.get_risk_model_data( self.risk_model_id, start_date, end_date, measures=[ RiskModelDataMeasure.Factor_Return, RiskModelDataMeasure.Factor_Name, RiskModelDataMeasure.Factor_Id, ], factors=[self.name], limit_factors=False, ).get('results') factor_returns_formatted = build_factor_data_map( factor_returns_raw, 'factorName', self.risk_model_id, RiskModelDataMeasure.Factor_Return, None ) factor_returns_df = factor_returns_formatted.rename(columns={self.name: 'return'}).rename_axis(None, axis=1) if format == ReturnFormat.JSON: return factor_returns_df.squeeze().to_dict() return factor_returns_df def intraday_returns( self, start_time: time = DataContext.current.start_time, end_time: time = DataContext.current.end_time, data_source: Union[IntradayFactorDataSource, str] = IntradayFactorDataSource.GS_FMP, format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[Dict, pd.DataFrame]: """Retrieve a Dataframe or Dictionary of timestamp->factor return values for a time range""" max_interval = dt.timedelta(hours=23, minutes=59, seconds=59) current_start = start_time all_data = [] while current_start < end_time: current_end = min(current_start + max_interval, end_time) factor_returns_batch = GsFactorRiskModelApi.get_risk_model_factor_data_intraday( self.risk_model_id, current_start, current_end, data_source=data_source, factors=[self.name] ) all_data.extend(factor_returns_batch) current_start = current_end + dt.timedelta(seconds=1) # Move to the next interval factor_returns_df = pd.DataFrame(all_data) try: factor_returns_df.set_index(['time'], inplace=True) except KeyError: factor_returns_df = pd.DataFrame() factor_returns_df.drop(columns=["factorCategory", "factor", "factorId"], errors='ignore', inplace=True) if format == ReturnFormat.JSON: return factor_returns_df.squeeze().to_dict() return factor_returns_df def mimicking_portfolio( self, start_date: date = DataContext.current.start_date, end_date: date = DataContext.current.end_date, assets: RiskModelDataAssetsRequest = RiskModelDataAssetsRequest( identifier=RiskModelUniverseIdentifierRequest.bbid, universe=() ), format: ReturnFormat = ReturnFormat.DATA_FRAME, ): """Retrieves a timeseries of factor mimicking portfolios for a date range.""" results = GsFactorRiskModelApi.get_risk_model_data( self.risk_model_id, start_date, end_date, assets=assets, measures=[ RiskModelDataMeasure.Factor_Portfolios, RiskModelDataMeasure.Factor_Id, RiskModelDataMeasure.Factor_Name, ], factors=[self.name], limit_factors=False, ).get('results') factor_portfolio_df = build_pfp_data_dataframe(results, True) factor_portfolio_df = factor_portfolio_df.reset_index() factor_portfolio_df = factor_portfolio_df.pivot(index="date", columns="identifier", values=self.name) if format.value == ReturnFormat.JSON.value: return factor_portfolio_df.to_dict(orient='index') return factor_portfolio_df ================================================ FILE: gs_quant/markets/factor_analytics.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import logging from typing import Dict, List import pandas as pd import plotly.graph_objects as go from gs_quant.api.gs.risk import GsRiskApi from gs_quant.markets.position_set import PositionSet from gs_quant.errors import MqValueError _logger = logging.getLogger(__name__) class FactorAnalytics: """ Helper class for performing style factor analysis and visualizations. Provides methods to analyze style factor exposures, performance metrics, and create visualizations. """ def __init__(self, risk_model_id: str, currency: str = 'USD', participation_rate: float = 0.1): """ Initialize FactorAnalytics helper. :param risk_model_id: Risk model identifier (e.g., 'AXIOMA_AXUS4S', 'BARRA_EFM_USALTL') :param currency: Currency for analysis (default: USD) :param participation_rate: Market participation rate (default: 0.1 = 10%) """ self.risk_model_id = risk_model_id self.currency = currency self.participation_rate = participation_rate def get_factor_analysis(self, position_set: PositionSet) -> Dict: """ Get style factor analysis for a position set using the liquidity endpoint. Returns risk metrics and style factor exposures. :param position_set: Portfolio positions to analyze :return: Factor analysis results dictionary with style factors """ if not position_set or not position_set.positions: raise MqValueError("Position set is empty") if not position_set.date: raise MqValueError("Position set must have a date") unresolved = [p for p in position_set.positions if not p.asset_id] if unresolved: _logger.info(f"Resolving {len(unresolved)} unresolved positions...") position_set.resolve() api_positions = [] position_mapping = {} for position in position_set.positions: if not position.asset_id: _logger.warning(f"Skipping unresolved position: {position.identifier}") continue position_mapping[position.asset_id] = position.identifier if position.quantity is not None: api_positions.append({"assetId": position.asset_id, "quantity": position.quantity}) elif position.weight is not None: api_positions.append({"assetId": position.asset_id, "weight": position.weight * 100}) else: _logger.warning(f"Position {position.identifier} has no quantity or weight") if not api_positions: raise MqValueError("No valid positions to analyze") notional = position_set.reference_notional if position_set.reference_notional else None try: results = GsRiskApi.get_liquidity_and_factor_analysis( positions=api_positions, risk_model=self.risk_model_id, date=position_set.date, currency=self.currency, participation_rate=self.participation_rate, notional=notional, measures=[ "Time Series Data", "Risk Buckets", "Factor Risk Buckets", "Factor Exposure Buckets", "Exposure Buckets", ], ) return results except MqValueError as e: error_msg = str(e) if 'missing in marquee' in error_msg.lower(): import re asset_ids_match = re.findall(r'MA[A-Z0-9]+', error_msg) if asset_ids_match: problematic_positions = [] for asset_id in asset_ids_match: identifier = position_mapping.get(asset_id, f"Unknown (Asset ID: {asset_id})") problematic_positions.append(f"{identifier} ({asset_id})") raise MqValueError( f"Factor analysis failed due to asset resolution issues.\n\n" f"The following positions could not be found in Marquee:\n" f"{chr(10).join([' - ' + pos for pos in problematic_positions])}\n\n" f"Please verify:\n" f"1. Asset identifiers are correct (e.g., 'AAPL UW' for Bloomberg tickers)\n" f"2. Positions are available in the Marquee system\n" f"3. Asset IDs were resolved correctly\n\n" f"Original error: {error_msg}" ) raise except Exception as e: _logger.error(f"Factor analysis failed: {str(e)}") raise def convert_hedge_factor_exposures(self, style_factors: List) -> Dict: """ Convert hedge result style factor exposures to factor analysis format. This allows reusing visualization methods with data from the hedge API. :param style_factors: Style factor exposures from hedge result (style, sector, country) :return: Dictionary in the same format as get_factor_analysis() for visualization """ if not style_factors: raise MqValueError("Style factor exposures data is empty") if not style_factors: _logger.warning("No style factor data in hedge result") sub_factors = [{'name': item['factor'], 'value': item['exposure']} for item in style_factors] return { 'factorExposureBuckets': [{'name': 'Style', 'subFactors': sub_factors}], 'notional': 0, 'currency': 'USD', 'riskBuckets': [], } def create_exposure_bar_chart(self, exposures: Dict[str, float], title: str, horizontal: bool = True) -> go.Figure: """ Create a bar chart for style factor exposures with color coding. Note: This method is intended for style factors only. :param exposures: Dictionary of factor names to exposure values :param title: Chart title :param horizontal: If True, creates horizontal bars; otherwise vertical :return: Plotly figure """ if not exposures: return go.Figure().add_annotation(text="No data available", showarrow=False) names = list(exposures.keys()) values = list(exposures.values()) colors = ['green' if v >= 0 else 'red' for v in values] fig = go.Figure() if horizontal: fig.add_trace( go.Bar( y=names, x=values, orientation='h', marker_color=colors, text=[f'{v:,.0f}' for v in values], textposition='outside', showlegend=False, ) ) fig.update_xaxes(title="Exposure") fig.update_yaxes(title="") else: fig.add_trace( go.Bar( x=names, y=values, marker_color=colors, text=[f'{v:,.0f}' for v in values], textposition='outside', showlegend=False, ) ) fig.update_xaxes(title="") fig.update_yaxes(title="Exposure") if horizontal: chart_height = max(300, len(names) * 40 + 150) else: chart_height = 500 fig.update_layout(title=title, height=chart_height, margin=dict(l=200 if horizontal else 50, r=50, t=80, b=50)) return fig def create_style_factor_chart( self, factor_analysis: Dict, rows: int = None, title: str = "Style Factor Exposures" ) -> go.Figure: """ Create a bar chart showing positive and negative style factor exposures. :param factor_analysis: Factor analysis results from liquidity endpoint :param title: Chart title :return: Plotly figure showing top positive and negative style factors """ if 'factorExposureBuckets' not in factor_analysis: return go.Figure().add_annotation(text="No style factor data available", showarrow=False) style_factors = {} for bucket in factor_analysis['factorExposureBuckets']: if bucket.get('name') == 'Style': for sub_factor in bucket.get('subFactors', []): factor_name = sub_factor.get('name') factor_value = sub_factor.get('value', 0) if factor_name: style_factors[factor_name] = factor_value break if not style_factors: return go.Figure().add_annotation(text="No style factor data available", showarrow=False) positive_factors = {k: v for k, v in style_factors.items() if v > 0} negative_factors = {k: v for k, v in style_factors.items() if v < 0} # most negative first - ascending order by value) top_negative_limit = rows if rows is not None else None top_negative = dict(sorted(negative_factors.items(), key=lambda x: x[1])[:top_negative_limit]) # descending order - highest first) top_positive_limit = rows if rows is not None else None top_positive_items = sorted(positive_factors.items(), key=lambda x: x[1], reverse=True)[:top_positive_limit] # Reverse to get ascending order for display (lowest to highest) top_positive = dict(reversed(top_positive_items)) selected_factors = {**top_negative, **top_positive} if not selected_factors: return go.Figure().add_annotation(text="No style factor data available", showarrow=False) total_factors = len(style_factors) subset_title = title if rows is not None: subset_title = f"{title} (Top {rows} Positive & Top {rows} Negative, {total_factors} Total)" return self.create_exposure_bar_chart(selected_factors, subset_title, horizontal=True) def create_exposure_summary_table(self, factor_analysis: Dict) -> pd.DataFrame: """ Create a summary table of key portfolio metrics. :param factor_analysis: Factor analysis results from liquidity endpoint :return: Pandas DataFrame with summary metrics """ notional = factor_analysis.get('notional', 0) currency = factor_analysis.get('currency', 'USD') risk_buckets = {bucket['name']: bucket['value'] for bucket in factor_analysis.get('riskBuckets', [])} data = { 'Metric': ['Notional', 'Currency', 'Market Risk', 'Specific Risk', 'Sector Risk', 'Style Risk'], 'Value': [ f"${notional:,.0f}", currency, f"{risk_buckets.get('Market', 0):.4f}", f"{risk_buckets.get('Specific', 0):.4f}", f"{risk_buckets.get('Sector', 0):.4f}", f"{risk_buckets.get('Style', 0):.4f}", ], } return pd.DataFrame(data, index=None) def create_performance_chart( self, performance_data: pd.DataFrame, metric: str = 'cumulativePnl', title: str = "Performance" ) -> go.Figure: """ Create a time series performance chart. :param performance_data: DataFrame with date and performance columns :param metric: Column name to plot :param title: Chart title :return: Plotly figure """ if performance_data.empty: return go.Figure().add_annotation(text="No performance data available", showarrow=False) fig = go.Figure() fig.add_trace( go.Scatter( x=performance_data['date'] if 'date' in performance_data.columns else performance_data.index, y=performance_data[metric] if metric in performance_data.columns else performance_data.iloc[:, 0], mode='lines', name=metric, line=dict(width=2), ) ) fig.update_layout( title=title, xaxis_title="Date", yaxis_title=metric.replace('_', ' ').title(), height=500, hovermode='x unified', ) return fig def create_dynamic_performance_chart( self, factor_analysis: Dict, title: str = "Portfolio Performance Metrics" ) -> go.Figure: """ Create a dynamic chart with toggleable performance metrics from timeseriesData. Shows cumulative PnL and normalized performance. :param factor_analysis: Factor analysis results from liquidity endpoint :param title: Chart title :return: Plotly figure with dropdown menu for cumulative PnL and normalized performance """ timeseries_data = factor_analysis.get('timeseriesData', []) if not timeseries_data: return go.Figure().add_annotation( text="No time series data available. Ensure 'Time Series Data' measure is included.", showarrow=False, font=dict(size=14), ) total_data = None for item in timeseries_data: if item.get('name') == 'total': total_data = item break cumulative_pnl_raw = total_data.get('cumulativePnl', []) normalized_performance_raw = total_data.get('normalizedPerformance', []) if not cumulative_pnl_raw and not normalized_performance_raw: return go.Figure().add_annotation( text="No cumulative PnL or normalized performance data available.", showarrow=False, font=dict(size=14) ) cumulative_dates = [] cumulative_values = [] for item in cumulative_pnl_raw: if len(item) == 2 and isinstance(item[0], str): cumulative_dates.append(item[0]) cumulative_values.append(item[1]) normalized_dates = [] normalized_values = [] for item in normalized_performance_raw: if len(item) == 2 and isinstance(item[0], str): normalized_dates.append(item[0]) normalized_values.append(item[1]) if not cumulative_dates and cumulative_values: cumulative_dates = list(range(len(cumulative_values))) if not normalized_dates and normalized_values: normalized_dates = list(range(len(normalized_values))) fig = go.Figure() if cumulative_values: fig.add_trace( go.Scatter( x=cumulative_dates, y=cumulative_values, mode='lines', name='Cumulative PnL', visible=True, line=dict(width=2, color='blue'), ) ) if normalized_values: fig.add_trace( go.Scatter( x=normalized_dates, y=normalized_values, mode='lines', name='Normalized Performance', visible=False, line=dict(width=2, color='green'), ) ) buttons = [ dict( label="Cumulative PnL", method="update", args=[{"visible": [True, False]}, {"yaxis.title.text": "Cumulative PnL ($)"}], ), dict( label="Normalized Performance", method="update", args=[{"visible": [False, True]}, {"yaxis.title.text": "Normalized Performance"}], ), ] fig.update_layout( title=title, xaxis_title="Date", yaxis_title="Cumulative PnL ($)", height=500, hovermode='x unified', updatemenus=[ dict( buttons=buttons, direction="down", pad={"r": 10, "t": 10}, showactive=True, x=0.11, xanchor="left", y=1.15, yanchor="top", ) ], annotations=[ dict(text="Select Metric:", showarrow=False, x=0.01, xref="paper", y=1.13, yref="paper", align="left") ], ) return fig def create_factor_heatmap_comparison( self, initial_analysis: Dict, hedged_analysis: Dict, title: str = "Style Factor Comparison: Initial vs Hedged" ) -> go.Figure: """ Create a grouped bar chart comparing style factor exposures. Shows side-by-side bars for easy comparison. :param initial_analysis: Factor analysis for initial portfolio :param hedged_analysis: Factor analysis for hedged portfolio :param title: Chart title :return: Plotly figure with grouped bar chart """ def extract_style_factors(analysis): for bucket in analysis.get('factorExposureBuckets', []): if bucket.get('name') == 'Style': return {sf['name']: sf['value'] for sf in bucket.get('subFactors', [])} return {} initial_factors = extract_style_factors(initial_analysis) hedged_factors = extract_style_factors(hedged_analysis) all_factors = set(initial_factors.keys()) | set(hedged_factors.keys()) if not all_factors: return go.Figure().add_annotation(text="No factor data available", showarrow=False) sorted_factors = sorted(all_factors, key=lambda f: abs(initial_factors.get(f, 0)), reverse=True) initial_values = [initial_factors.get(f, 0) for f in sorted_factors] hedged_values = [hedged_factors.get(f, 0) for f in sorted_factors] fig = go.Figure() fig.add_trace( go.Bar( name='Initial Portfolio', y=sorted_factors, x=initial_values, orientation='h', marker_color='#4472C4', text=[f'{v:,.0f}' for v in initial_values], textposition='outside', textfont=dict(size=10), ) ) # Hedged portfolio bars fig.add_trace( go.Bar( name='Hedged Portfolio', y=sorted_factors, x=hedged_values, orientation='h', marker_color='#70AD47', text=[f'{v:,.0f}' for v in hedged_values], textposition='outside', textfont=dict(size=10), ) ) # Calculate dynamic height based on number of factors chart_height = max(500, len(sorted_factors) * 35 + 150) fig.update_layout( title=title, xaxis_title="Exposure Value", yaxis_title="", height=chart_height, barmode='group', bargap=0.15, bargroupgap=0.1, legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1), margin=dict(l=200, r=100, t=100, b=50), ) # Add vertical line at x=0 for reference fig.add_vline(x=0, line_width=1, line_dash="dash", line_color="gray") return fig ================================================ FILE: gs_quant/markets/hedge.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import logging from collections import defaultdict from enum import Enum from typing import Union, List, Dict, Optional import numpy as np import pandas as pd from dateutil.relativedelta import relativedelta from gs_quant.api.gs.assets import GsAssetApi from gs_quant.api.gs.hedges import GsHedgeApi from gs_quant.errors import MqValueError from gs_quant.markets.position_set import PositionSet from gs_quant.session import GsSession from gs_quant.target.hedge import HedgeObjective, CorporateActionsTypes _logger = logging.getLogger(__name__) class FactorExposureCategory(Enum): COUNTRY = 'country' SECTOR = 'sector' INDUSTRY = 'industry' STYLE = 'style' class ConstraintType(Enum): ASSET = "Asset" COUNTRY = "Country" REGION = "Region" SECTOR = "Sector" INDUSTRY = "Industry" ESG = "Esg" class HedgeExclusions: """ List of assets, countries, regions, sectors, and industries to exclude from the hedge universe """ def __init__( self, assets: List[str] = None, countries: List[str] = None, regions: List[str] = None, sectors: List[str] = None, industries: List[str] = None, ): self.__assets = assets self.__countries = countries self.__regions = regions self.__sectors = sectors self.__industries = industries @property def assets(self) -> List[str]: return self.__assets @assets.setter def assets(self, value: List[str]): self.__assets = value @property def countries(self) -> List[str]: return self.__countries @countries.setter def countries(self, value: List[str]): self.__countries = value @property def regions(self) -> List[str]: return self.__regions @regions.setter def regions(self, value: List[str]): self.__regions = value @property def sectors(self) -> List[str]: return self.__sectors @sectors.setter def sectors(self, value: List[str]): self.__sectors = value @property def industries(self) -> List[str]: return self.__industries @industries.setter def industries(self, value: List[str]): self.__industries = value def to_dict(self): response = {} all_constraints = [] if self.countries: all_constraints = all_constraints + self._get_exclusions(self.countries, ConstraintType.COUNTRY) if self.regions: all_constraints = all_constraints + self._get_exclusions(self.regions, ConstraintType.REGION) if self.sectors: all_constraints = all_constraints + self._get_exclusions(self.sectors, ConstraintType.SECTOR) if self.industries: all_constraints = all_constraints + self._get_exclusions(self.industries, ConstraintType.INDUSTRY) if len(all_constraints) > 0: response['classificationConstraints'] = all_constraints if self.assets: response['assetConstraints'] = self._get_exclusions(self.assets, ConstraintType.ASSET) return response @staticmethod def _get_exclusions(exclusions_list: List, constraint_type: ConstraintType): return [ Constraint(constraint_name=exclusion, constraint_type=constraint_type, minimum=0, maximum=0).to_dict() for exclusion in exclusions_list ] class Constraint: def __init__( self, constraint_name: str, minimum: float = 0, maximum: float = 100, constraint_type: Optional[ConstraintType] = None, ): self.__constraint_name = constraint_name self.__minimum = minimum self.__maximum = maximum self.__constraint_type = constraint_type @property def constraint_name(self) -> str: return self.__constraint_name @constraint_name.setter def constraint_name(self, value: str): self.__constraint_name = value @property def minimum(self) -> float: return self.__minimum @minimum.setter def minimum(self, value: float): self.__minimum = value @property def maximum(self) -> float: return self.__maximum @maximum.setter def maximum(self, value: float): self.__maximum = value @property def constraint_type(self) -> Optional[ConstraintType]: return self.__constraint_type @constraint_type.setter def constraint_type(self, value: ConstraintType): self.__constraint_type = value @classmethod def from_dict(cls, as_dict: Dict): if as_dict.get('type') is not None: constraint_type = ConstraintType(as_dict.get('type')) elif as_dict.get('assetId') is not None: constraint_type = ConstraintType.ASSET else: constraint_type = ConstraintType.ESG return Constraint( constraint_name=as_dict.get('name') or as_dict.get('assetId'), constraint_type=constraint_type, minimum=as_dict.get('min'), maximum=as_dict.get('max'), ) def to_dict(self): response = {'name': self.constraint_name, 'min': self.minimum, 'max': self.maximum} if self.constraint_type != ConstraintType.ESG and self.constraint_type != ConstraintType.ASSET: response['type'] = self.constraint_type.value if self.constraint_type == ConstraintType.ASSET: response['assetId'] = response['name'] response.pop('name') return response class HedgeConstraints: """ List of assets, countries, sectors, and industries to constrain in the hedge universe """ def __init__( self, assets: List[Constraint] = None, countries: List[Constraint] = None, regions: List[Constraint] = None, sectors: List[Constraint] = None, industries: List[Constraint] = None, esg: List[Constraint] = None, ): for con in assets or []: con.constraint_type = ConstraintType.ASSET for con in regions or []: con.constraint_type = ConstraintType.REGION for con in countries or []: con.constraint_type = ConstraintType.COUNTRY for con in sectors or []: con.constraint_type = ConstraintType.SECTOR for con in industries or []: con.constraint_type = ConstraintType.INDUSTRY for con in esg or []: con.constraint_type = ConstraintType.ESG self.__assets = assets self.__countries = countries self.__regions = regions self.__sectors = sectors self.__industries = industries self.__esg = esg @property def assets(self) -> List[Constraint]: return self.__assets @assets.setter def assets(self, value: List[Constraint]): self.__assets = value @property def countries(self) -> List[Constraint]: return self.__countries @countries.setter def countries(self, value: List[Constraint]): self.__countries = value @property def regions(self) -> List[Constraint]: return self.__regions @regions.setter def regions(self, value: List[Constraint]): self.__regions = value @property def sectors(self) -> List[Constraint]: return self.__sectors @sectors.setter def sectors(self, value: List[Constraint]): self.__sectors = value @property def industries(self) -> List[Constraint]: return self.__industries @industries.setter def industries(self, value: List[Constraint]): self.__industries = value @property def esg(self) -> List[Constraint]: return self.__esg @esg.setter def esg(self, value: List[Constraint]): self.__esg = value def to_dict(self): response = {} classification_constraints = [] for constraint_type in [self.countries, self.regions, self.sectors, self.industries]: if constraint_type: classification_constraints += [con.to_dict() for con in constraint_type] if len(classification_constraints) > 0: response['classificationConstraints'] = classification_constraints esg_constraints = [con.to_dict() for con in self.esg] if self.esg else [] if len(esg_constraints) > 0: response['esgConstraints'] = esg_constraints asset_constraints = [con.to_dict() for con in self.assets] if self.assets else [] if len(asset_constraints) > 0: response['assetConstraints'] = asset_constraints return response class PerformanceHedgeParameters: """Parameters for a performance replication hedge calculation.""" def __init__( self, initial_portfolio: PositionSet, universe: List[str], exclusions: Optional[HedgeExclusions] = None, constraints: Optional[HedgeConstraints] = None, observation_start_date: dt.date = None, sampling_period: str = 'Daily', max_leverage: float = 100, percentage_in_cash: Optional[float] = None, explode_universe: bool = True, exclude_target_assets: bool = True, exclude_corporate_actions_types: Optional[List[Union[CorporateActionsTypes, str]]] = None, exclude_hard_to_borrow_assets: bool = False, exclude_restricted_assets: bool = False, max_adv_percentage: float = 15, max_return_deviation: float = 5, max_weight: float = 100, min_market_cap: Optional[float] = None, max_market_cap: Optional[float] = None, market_participation_rate: float = 10, lasso_weight: float = 0, ridge_weight: float = 0, benchmarks: List[str] = None, ): self.__initial_portfolio = initial_portfolio self.__universe = universe self.__exclusions = exclusions self.__constraints = constraints self.__observation_start_date = observation_start_date self.__sampling_period = sampling_period self.__max_leverage = max_leverage self.__percentage_in_cash = percentage_in_cash self.__explode_universe = explode_universe self.__exclude_target_assets = exclude_target_assets self.__exclude_corporate_actions_types = exclude_corporate_actions_types self.__exclude_hard_to_borrow_assets = exclude_hard_to_borrow_assets self.__exclude_restricted_assets = exclude_restricted_assets self.__max_adv_percentage = max_adv_percentage self.__max_return_deviation = max_return_deviation self.__max_weight = max_weight self.__min_market_cap = min_market_cap self.__max_market_cap = max_market_cap self.__market_participation_rate = market_participation_rate self.__lasso_weight = lasso_weight self.__ridge_weight = ridge_weight self.__benchmarks = benchmarks @property def initial_portfolio(self) -> PositionSet: """The set of positions that make up the hedge target.""" return self.__initial_portfolio @initial_portfolio.setter def initial_portfolio(self, value: PositionSet): self.__initial_portfolio = value @property def universe(self) -> List[str]: """A list of asset identifiers (asset IDs, bloomberg IDs, tickers, SEDOLs, etc) that make up the universe, which are resolved as of the observation end date.""" return self.__universe @universe.setter def universe(self, value: List[str]): self.__universe = value @property def observation_start_date(self) -> dt.date: """ISO 8601-formatted date""" return self.__observation_start_date @observation_start_date.setter def observation_start_date(self, value: dt.date): self.__observation_start_date = value @property def exclusions(self) -> Optional[HedgeExclusions]: """Assets, countries, sectors, and industries to exclude from the hedge""" return self.__exclusions @exclusions.setter def exclusions(self, value: HedgeExclusions): self.__exclusions = value @property def constraints(self) -> Optional[HedgeConstraints]: """Assets, countries, sectors, and industries to constrain in the hedge""" return self.__constraints @constraints.setter def constraints(self, value: HedgeConstraints): self.__constraints = value @property def sampling_period(self) -> str: """The length of time in between return samples.""" return self.__sampling_period @sampling_period.setter def sampling_period(self, value: str): self.__sampling_period = value @property def max_leverage(self) -> float: """Maximum percentage of the notional that can be used to hedge.""" return self.__max_leverage @max_leverage.setter def max_leverage(self, value: float): self.__max_leverage = value @property def percentage_in_cash(self) -> Optional[float]: """Percentage of the hedge notional that will be in cash.""" return self.__percentage_in_cash @percentage_in_cash.setter def percentage_in_cash(self, value: float): self.__percentage_in_cash = value @property def explode_universe(self) -> bool: """Explode the assets in the universe into their underliers to be used as the hedge universe.""" return self.__explode_universe @explode_universe.setter def explode_universe(self, value: bool): self.__explode_universe = value @property def exclude_target_assets(self) -> bool: """Exclude assets in the target composition from being in the hedge.""" return self.__exclude_target_assets @exclude_target_assets.setter def exclude_target_assets(self, value: bool): self.__exclude_target_assets = value @property def exclude_corporate_actions_types(self) -> Optional[List[Union[CorporateActionsTypes, str]]]: """Set of of corporate actions to be excluded in the hedge""" return self.__exclude_corporate_actions_types @exclude_corporate_actions_types.setter def exclude_corporate_actions_types(self, value: List[Union[CorporateActionsTypes, str]]): self.__exclude_corporate_actions_types = value @property def exclude_hard_to_borrow_assets(self) -> bool: """Whether hard to borrow assets should be excluded in the universe or not. True for exclude.""" return self.__exclude_hard_to_borrow_assets @exclude_hard_to_borrow_assets.setter def exclude_hard_to_borrow_assets(self, value: bool): self.__exclude_hard_to_borrow_assets = value @property def exclude_restricted_assets(self) -> bool: """Whether to include assets in restricted trading lists or not.""" return self.__exclude_restricted_assets @exclude_restricted_assets.setter def exclude_restricted_assets(self, value: bool): self.__exclude_restricted_assets = value @property def max_adv_percentage(self) -> float: """Maximum percentage notional to average daily dollar volume allowed for any hedge constituent.""" return self.__max_adv_percentage @max_adv_percentage.setter def max_adv_percentage(self, value: float): self.__max_adv_percentage = value @property def max_return_deviation(self) -> float: """Maximum percentage difference in annualized return between the target and the hedge result.""" return self.__max_return_deviation @max_return_deviation.setter def max_return_deviation(self, value: float): self.__max_return_deviation = value @property def max_weight(self) -> float: """Maximum weight of any constituent in hedge.""" return self.__max_weight @max_weight.setter def max_weight(self, value: float): self.__max_weight = value @property def min_market_cap(self) -> Optional[float]: """Lowest market cap allowed for any hedge constituent.""" return self.__min_market_cap @min_market_cap.setter def min_market_cap(self, value: float): self.__min_market_cap = value @property def max_market_cap(self) -> Optional[float]: """Highest market cap allowed for any hedge constituent.""" return self.__max_market_cap @max_market_cap.setter def max_market_cap(self, value: float): self.__max_market_cap = value @property def market_participation_rate(self) -> float: """Maximum market participation rate used to estimate the cost of trading a portfolio of stocks. This does not effect the optimization.""" return self.__market_participation_rate @market_participation_rate.setter def market_participation_rate(self, value: float): self.__market_participation_rate = value @property def benchmarks(self) -> List[str]: """Marquee unique identifiers of assets to be used as benchmarks.""" return self.__benchmarks @benchmarks.setter def benchmarks(self, value: List[str]): self.__benchmarks = value @property def lasso_weight(self) -> float: """Value of the lasso hyperparameter for machine learning hedges.""" return self.__lasso_weight @lasso_weight.setter def lasso_weight(self, value: float): self.__lasso_weight = value @property def ridge_weight(self) -> float: """Value of the ridge hyperparameter for machine learning hedges""" return self.__ridge_weight @ridge_weight.setter def ridge_weight(self, value: float): self.__ridge_weight = value def to_dict(self, resolved_identifiers): positions_to_price = [] for position in self.initial_portfolio.positions: pos_to_price = {'assetId': position.asset_id} if position.quantity: pos_to_price['quantity'] = position.quantity if position.weight: pos_to_price['weight'] = position.weight positions_to_price.append(pos_to_price) payload = { 'positions': positions_to_price, 'parameters': { 'currency': 'USD', 'pricingDate': self.initial_portfolio.date.strftime('%Y-%m-%d'), 'useUnadjustedClosePrice': True, 'frequency': 'End Of Day', }, } if self.initial_portfolio.reference_notional: payload['parameters']['targetNotional'] = self.initial_portfolio.reference_notional payload['parameters']['weightingStrategy'] = "Weight" try: price_results = GsSession.current.sync.post('/price/positions', payload) except Exception as e: raise MqValueError(f'There was an error pricing your positions: {e}') if 'errorMessage' in price_results: raise MqValueError(f'There was an error pricing your positions: {price_results["errorMessage"]}') if self.initial_portfolio.reference_notional is None: self.initial_portfolio.reference_notional = price_results.get('actualNotional') positions_as_dict = [ {'assetId': p['assetId'], 'quantity': p['quantity']} for p in price_results.get('positions', []) ] # Resolve any assets in the hedge universe, asset constraints, and asset exclusions hedge_date = self.initial_portfolio.date self.universe = [resolved_identifiers.get(asset, [{'id': asset}])[0].get('id') for asset in self.universe] if self.benchmarks is not None: self.benchmarks = [ resolved_identifiers.get(asset, [{'id': asset}])[0].get('id') for asset in self.benchmarks ] if self.exclusions is not None: if self.exclusions.assets is not None: self.exclusions.assets = [ resolved_identifiers.get(asset, [{'id': asset}])[0].get('id') for asset in self.exclusions.assets ] if self.constraints is not None and self.constraints.assets is not None: for con in self.constraints.assets: if len(resolved_identifiers.get(con.constraint_name, [])) > 0: con.constraint_name = resolved_identifiers.get(con.constraint_name)[0].get( 'id', con.constraint_name ) # Parse and return dictionary observation_start_date = self.observation_start_date or hedge_date - relativedelta(years=1) as_dict = { 'hedgeTarget': {'positions': positions_as_dict}, 'universe': self.universe, 'notional': self.initial_portfolio.reference_notional, 'observationStartDate': observation_start_date.strftime("%Y-%m-%d"), 'observationEndDate': hedge_date.strftime("%Y-%m-%d"), 'backtestStartDate': observation_start_date.strftime("%Y-%m-%d"), 'backtestEndDate': hedge_date.strftime("%Y-%m-%d"), 'samplingPeriod': self.sampling_period, 'maxLeverage': self.max_leverage, 'explodeUniverse': self.explode_universe, 'excludeTargetAssets': self.exclude_target_assets, 'excludeHardToBorrowAssets': self.exclude_hard_to_borrow_assets, 'excludeRestrictedAssets': self.exclude_hard_to_borrow_assets, 'maxAdvPercentage': self.max_adv_percentage, 'maxReturnDeviation': self.max_return_deviation, 'maxWeight': self.max_weight, 'marketParticipationRate': self.market_participation_rate, 'useMachineLearning': True, 'lassoWeight': self.lasso_weight, 'ridgeWeight': self.ridge_weight, } exclusions_as_dict = self.exclusions.to_dict() if self.exclusions else {} constraints_as_dict = self.constraints.to_dict() if self.constraints else {} if 'classificationConstraints' in exclusions_as_dict or 'classificationConstraints' in constraints_as_dict: exclusions = exclusions_as_dict.get('classificationConstraints', []) constraints = constraints_as_dict.get('classificationConstraints', []) as_dict['classificationConstraints'] = exclusions + constraints if 'assetConstraints' in exclusions_as_dict or 'assetConstraints' in constraints_as_dict: exclusions = exclusions_as_dict.get('assetConstraints', []) constraints = constraints_as_dict.get('assetConstraints', []) as_dict['assetConstraints'] = exclusions + constraints if 'esgConstraints' in constraints_as_dict: as_dict['esgConstraints'] = constraints_as_dict.get('esgConstraints', []) if self.percentage_in_cash is not None: as_dict['percentageInCash'] = self.percentage_in_cash if self.exclude_corporate_actions_types: as_dict['excludeCorporateActionTypes'] = [x.value for x in self.exclude_corporate_actions_types] if self.min_market_cap is not None: as_dict['minMarketCap'] = self.min_market_cap if self.max_market_cap is not None: as_dict['maxMarketCap'] = self.max_market_cap if self.benchmarks is not None and len(self.benchmarks): as_dict['benchmarks'] = self.benchmarks return as_dict def resolve_identifiers_in_payload(self, hedge_date) -> tuple: """ The hedge payload has identifiers which need to be resolved The resolved values here are used to convert back from asset Id to provided identifier for benchmark curves """ identifiers = [identifier for identifier in self.universe] if self.exclusions is not None and self.exclusions.assets is not None: identifiers = identifiers + [asset for asset in self.exclusions.assets] if self.benchmarks is not None: identifiers = identifiers + [asset for asset in self.benchmarks] if self.constraints is not None: if self.constraints.assets is not None: identifiers = identifiers + [asset.constraint_name for asset in self.constraints.assets] resolver = GsAssetApi.resolve_assets(identifier=identifiers, fields=['id'], as_of=hedge_date) return resolver class Hedge: """ A Marquee hedge. """ def __init__(self, parameters, objective: HedgeObjective): self.__parameters = parameters self.__objective = objective self.__result = {} @property def parameters(self): return self.__parameters @parameters.setter def parameters(self, value): self.__parameters = value @property def objective(self): return self.__objective @property def result(self) -> Dict: return self.__result def calculate(self) -> Dict: """ Calculates the hedge :return: a dictionary with calculation results """ resolved_identifiers = self.parameters.resolve_identifiers_in_payload(self.parameters.initial_portfolio.date) params = self.parameters.to_dict(resolved_identifiers) results = GsHedgeApi.calculate_hedge({'objective': self.objective.value, 'parameters': params}) if 'errorMessage' in results and 'result' not in results: raise MqValueError( f"Error calculating hedge: {results['errorMessage']}. Please adjust your constraints and try again." ) calculation_results = results.get('result') formatted_results = self._format_hedge_calculate_results(calculation_results) formatted_results = self._enhance_result_with_benchmark_curves( formatted_results, calculation_results.get('benchmarks', []), resolved_identifiers ) self.__result = formatted_results return formatted_results def get_constituents(self) -> pd.DataFrame: """ Get metadata for hedge constituents :return: a DataFrame with results """ constituents = self.result.get('Hedge', {}).get('Constituents', []) formatted_constituents = [] for row in constituents: formatted_row = {} for key in row: formatted_row[key[0].capitalize() + ''.join(map(lambda x: x if x.islower() else f' {x}', key[1:]))] = ( row[key] ) formatted_constituents.append(formatted_row) return pd.DataFrame(formatted_constituents) def get_statistics(self) -> pd.DataFrame: """ Get all statistics available for the portfolio, hedge, and hedged portfolio :return: a Pandas DataFrame with results """ results = {'Portfolio': {}, 'Hedge': {}, 'Hedged Portfolio': {}} for key in self.result: for inner_key in self.result[key]: if isinstance(self.result[key][inner_key], float): results[key][inner_key] = self.result[key][inner_key] return pd.DataFrame(results) def get_backtest_performance(self) -> pd.DataFrame: """ Get the backtest performance timeseries :return: a Pandas DataFrame with results """ return self._get_timeseries('Backtest Performance') def get_backtest_correlation(self) -> pd.DataFrame: """ Get the backtest_correlation timeseries of the hedge :return: a Pandas DataFrame with results """ return self._get_timeseries('Backtest Correlation') def _get_timeseries(self, timeseries_name: str) -> pd.DataFrame: results = {} for key in self.result: if timeseries_name in self.result[key]: ts = self.result[key][timeseries_name] for data in ts: date = data[0] if date in results: results[date][key] = data[1] else: results[date] = {'Date': date, key: data[1]} return pd.DataFrame(results.values()).set_index('Date') @staticmethod def _format_hedge_calculate_results(calculation_results): renamed_results = { 'Portfolio': calculation_results.get('target'), 'Hedge': calculation_results.get('hedge'), 'Hedged Portfolio': calculation_results.get('hedgedTarget'), } formatted_results = {} for key in renamed_results: formatted_results[key] = Hedge.format_dictionary_key_to_readable_format(renamed_results[key]) return formatted_results @staticmethod def _enhance_result_with_benchmark_curves(formatted_results, benchmark_results, resolver): asset_id_to_provided_identifier_map = dict( (x['id'], provided_identifier) for provided_identifier, marquee_assets in resolver.items() for x in marquee_assets ) if len(benchmark_results): for x in benchmark_results: benchmark_asset_id = asset_id_to_provided_identifier_map[x['assetId']] formatted_results[benchmark_asset_id] = Hedge.format_dictionary_key_to_readable_format(x) return formatted_results @staticmethod def format_dictionary_key_to_readable_format(renamed_results): formatted_results = {} for inner_key in renamed_results: formatted_results[ inner_key[0].capitalize() + ''.join(map(lambda x: x if x.islower() else f' {x}', inner_key[1:])) ] = renamed_results[inner_key] return formatted_results @staticmethod def find_optimal_hedge(hedge_query: dict, hyperparams: dict, metric: str) -> Union[dict, float]: """ This function is designed to find the 'best' hedge from a list of hedges that are computed using a grid search over all hyperparameters passed in - where 'best' is defined by the metric argument passed in and whether we want to minimize or maximize this metric. :param hedge_query: dict, hedge data that is sent to the Marquee API as input to the new performance hedger :param hyperparams: dict, keys are hyperparameters (Concentration or Diversity) that map to lists of the values to use for one of these hyperparameters when running the new performance hedger. :param metric: str, the metric we want to optimize i.e. 'holdingError' or 'rSquared' :return: dict, float, and List, the best hedge found using the algorithm, the value of the metric being optimized, and the List of optimal hyperparameters """ hedge_results = {} opt_map = Hedge.create_optimization_mappings() optimization_type = opt_map[metric] _logger.info( f'We are trying to {optimization_type} {metric} and will return the optimized hedge & metric value...' ) hyperparam_grid = [(x, y) for x in hyperparams['Concentration'] for y in hyperparams['Diversity']] for pair in hyperparam_grid: hedge_params = hedge_query['parameters'] hedge_params.lasso_weight, hedge_params.ridge_weight = pair[0], pair[1] hedge_query['parameters'] = hedge_params results = GsHedgeApi.calculate_hedge(hedge_query) _logger.info(f'Current Hedge is using the following values for Concentration/Diversity: {pair}') curr_results, curr_pair = results['result']['hedge'], pair hedge_results[curr_results[metric]] = (curr_results, curr_pair) _logger.info(f'Current Hedge value for {metric}: {curr_results[metric] * 100:.3}%') optimized_metric = min(hedge_results.keys()) if optimization_type == 'minimize' else max(hedge_results.keys()) optimized_hedge, optimized_hyperparams = hedge_results[optimized_metric][0], hedge_results[optimized_metric][1] return optimized_hedge, optimized_metric, optimized_hyperparams @staticmethod def create_optimization_mappings() -> dict: """ This function is designed to construct a mapping between metrics a user can choose to optimize when calling the New Performance Hedger and the way the metric should be optimized. :param none: :return: dict, the dictionary containing a mapping between metrics to optimize and how they should be optimized """ opt_dict = { 'rSquared': 'maximize', 'correlation': 'maximize', 'holdingError': 'minimize', 'trackingError': 'minimize', 'transactionCost': 'minimize', 'annualizedReturn': 'maximize', } return opt_dict @staticmethod def construct_portfolio_weights_and_asset_numbers(results: dict) -> Union[dict, List]: """ Function used to retrieve the constructed portfolio from a performance hedge, sort it, then calculate the weights for all assets and total number of assets and return these results. :param results: dict, the results of the performance hedge request made to the Marquee API :return: dict, list, list - the portfolio, portfolio weights, and number of assets """ portfolio = results["result"]["hedge"]["constituents"] portfolio.sort(key=lambda x: x['weight'], reverse=True) weights = [asset['weight'] for asset in portfolio] asset_numbers = list(range(len(portfolio))) return portfolio, weights, asset_numbers @staticmethod def asset_id_diffs(portfolio_asset_ids, thomson_reuters_asset_ids): """ Function designed to find the assets that are contained in the portfolio but that we don't have Thomson Reuters data for. :param portfolio_asset_ids: list, the list of MQIDs representing all of the assets that we are computing rebalance costs for :param thomson_reuters_asset_ids: list, the list of MQIDs representing all of the assets that we have Thomson Reuters data for :return: list, the assets that we don't have Thomson Reuters data for and should exclude in the transaction cost calculations """ diffs = list(set(portfolio_asset_ids) - set(thomson_reuters_asset_ids)) return diffs @staticmethod def create_transaction_cost_data_structures( portfolio_asset_ids, portfolio_quantities, thomson_reuters_eod_data, backtest_dates ): """ Function designed to create the data structures necessary to compute transaction costs based on rebalancing a portfolio of assets. :param portfolio_asset_ids: list, the asset_ids for each asset in the underlying portfolio that we want to compute transaction costs for :param portfolio_quantities: list, the number of shares for each asset in the underlying portfolio that we want to compute transaction costs for :param thomson_reuters_eod_data: Dataset, the data used to fetch prices for assets - in this case from Thomson Reuters :param backtest_dates: list, the dates that the portfolio is held for (that we want to compute rebalance costs for) :return: Union[list, dict], the data structures necessary for computing transaction (rebalance) costs """ thomson_reuters_asset_ids = [ asset_id for asset_id in thomson_reuters_eod_data.get_data( backtest_dates[-1], backtest_dates[-1], assetId=portfolio_asset_ids )['assetId'] ] diffs = Hedge.asset_id_diffs(portfolio_asset_ids, thomson_reuters_asset_ids) for diff in diffs: portfolio_asset_ids.remove(diff) # Map asset_id to quantity of shares from portfolio, while excluding assets that are found in the diffs since # there is no Thomson Reuters data on them id_quantity_map = {} for idx, asset_id in enumerate(portfolio_asset_ids): if asset_id not in diffs: id_quantity_map[asset_id] = portfolio_quantities[idx] # Map asset_id to list of prices of that asset over the transaction_cost_dates we want id_prices_map = defaultdict(lambda: list()) prices_df = pd.DataFrame() for date in backtest_dates: data = thomson_reuters_eod_data.get_data(date, date, assetId=portfolio_asset_ids) prices_df = prices_df.append(data) for asset_id in portfolio_asset_ids: id_prices_map[asset_id] = list(prices_df.loc[prices_df['assetId'] == asset_id]['closePrice']) # Create list representing notional of each day in transaction_cost_days and map asset_ids to notional of each # asset on each day id_to_notional_map = {} notionals_assets = [ abs(np.asarray(id_prices_map[asset_id]) * id_quantity_map[asset_id]) for asset_id in portfolio_asset_ids ] # Mapping asset_id to notionals of each day of that asset_id for idx, asset_id in enumerate(portfolio_asset_ids): id_to_notional_map[asset_id] = list(notionals_assets[idx]) total_notionals_each_day = list(np.sum(notionals_assets, axis=0)) # Create map of asset_ids to weights of total portfolio on each day id_to_weight_map = {} for idx, asset_id in enumerate(portfolio_asset_ids): id_to_weight_map[asset_id] = [ i / j for i, j in zip(id_to_notional_map[portfolio_asset_ids[idx]], total_notionals_each_day) ] return id_quantity_map, id_prices_map, id_to_notional_map, id_to_weight_map @staticmethod def t_cost(basis_points, notional_traded): """ Function designed to compute the transaction costs associated with trading a notional amount of an asset to rebalance a portfolio. :param basis_points: float, the number of basis points to use as an approximation when computing transaction costs to trade each asset that is rebalanced and that will be converted to a percentage (e.g. 20.43) :param notional_traded: float, notional amount of the asset that is being traded to rebalance the portfolio :return: float, the total transaction cost of trading a particular notional amount of an asset """ return (basis_points * 1e-4) * notional_traded @staticmethod def compute_notional_traded(notional_on_the_day, prev_weight, curr_weight): """ Function used to compute the notional amount (USD) of an asset that will be traded on a particular day, using the weights of the asset from the previous day and the weights & notional from the current day. :param notional_on_the_day: float, notional amount of the asset that the portfolio contains on the current day :param prev_weight: float, the weighting of the corresponding asset (of the entire portfolio) on the previous day :param curr_weight: float, the weighting of the corresponding asset (of the entire portfolio) on the current day :return: float, the net notional amount of the asset traded on the current day """ return sum([np.abs(curr_weight - prev_weight) * notional_on_the_day]) @staticmethod def compute_tcosts(basis_points, asset_weights, asset_notionals, backtest_dates, portfolio_asset_ids): """ Function to compute cumulative transaction costs associated with rebalancing a portfolio. In particular, for each day on which we compute the cumulative the rebalancing costs (USD), the weights of the constituents of the portfolio on the previous day are used to calculate how much of the notional amount of each asset is traded to execute the rebalance. :param basis_points: float, the number of basis points to use as an approximation when computing transaction costs to trade each asset that is rebalanced and that will be converted to a percentage (e.g. 20.43) :param asset_weights: dict, the dictionary mapping of asset_ids (MQIDs) to a list of weights where each weight represents the weighting of the corresponding asset (of the entire portfolio) on each day in the backtest period :param asset_notionals: dict, the dictionary mapping of asset_ids (MQIDs) to a list of floats where each float represents the notional amount of the corresponding asset (of the entire portfolio) on each day in the backtest period :param backtest_dates: list, the dates that the portfolio is held for (that we want to compute rebalance costs for) :param portfolio_asset_ids: list, the list of MQIDs representing all of the assets that we are computing rebalancing costs for :return: pd.Series, the cumulative transaction costs associated with rebalancing the portfolio across the backtest period """ tcosts_each_day = [] for idx, date in enumerate(backtest_dates): tcost_today = 0 for asset_id in portfolio_asset_ids: prev_weights = asset_weights[asset_id][0] if idx == 0 else asset_weights[asset_id][idx - 1] notional_on_the_day, curr_weights = asset_notionals[asset_id][idx], asset_weights[asset_id][idx] notional_to_trade = Hedge.compute_notional_traded(notional_on_the_day, prev_weights, curr_weights) transaction_cost = Hedge.t_cost(basis_points, notional_to_trade) tcost_today += transaction_cost tcosts_each_day.append(abs(tcost_today)) cum_tcosts = pd.Series(np.cumsum(tcosts_each_day)) return cum_tcosts class PerformanceHedge(Hedge): def __init__(self, parameters: PerformanceHedgeParameters = None, **kwargs): super().__init__(parameters, HedgeObjective.Replicate_Performance) ================================================ FILE: gs_quant/markets/historical.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from typing import Iterable, Optional, Tuple, Union, Type from gs_quant.base import InstrumentBase, RiskKey from gs_quant.common import RiskMeasure from gs_quant.datetime.date import date_range from gs_quant.risk import RollFwd, MarketDataScenario from gs_quant.risk.results import HistoricalPricingFuture, PricingFuture from .core import PricingContext from .markets import CloseMarket from ..api.risk import GenericRiskApi class HistoricalPricingContext(PricingContext): """ A context for producing valuations over multiple dates """ def __init__( self, start: Optional[Union[int, dt.date]] = None, end: Optional[Union[int, dt.date]] = None, calendars: Union[str, Tuple] = (), dates: Optional[Iterable[dt.date]] = None, is_async: bool = None, is_batch: bool = None, use_cache: bool = None, visible_to_gs: bool = None, request_priority: Optional[int] = None, csa_term: str = None, market_data_location: Optional[str] = None, timeout: Optional[int] = None, show_progress: Optional[bool] = None, use_server_cache: Optional[bool] = None, provider: Optional[Type[GenericRiskApi]] = None, ): """ A context for producing valuations over multiple dates :param start: start date :param end: end date (defaults to today) :param calendars: holiday calendars :param dates: a custom iterable of dates :param is_async: return immediately (True) or wait for results (False) (defaults to False) :param is_batch: use for calculations expected to run longer than 3 mins, to avoid timeouts. It can be used with is_async=True|False (defaults to False) :param use_cache: store results in the pricing cache (defaults to False) :param visible_to_gs: are the contents of risk requests visible to GS (defaults to False) :param csa_term: the csa under which the calculations are made. Default is local ccy ois index :param market_data_location: the location for sourcing market data ('NYC', 'LDN' or 'HKG' (defaults to LDN) :param timeout: the timeout for batch operations :param show_progress: add a progress bar (tqdm) :param use_server_cache: cache query results on the GS servers **Examples** >>> from gs_quant.instrument import IRSwap >>> >>> ir_swap = IRSwap('Pay', '10y', 'DKK') >>> with HistoricalPricingContext(10): >>> price_f = ir_swap.price() >>> >>> price_series = price_f.result() """ super().__init__( is_async=is_async, is_batch=is_batch, use_cache=use_cache, visible_to_gs=visible_to_gs, request_priority=request_priority, csa_term=csa_term, market_data_location=market_data_location, timeout=timeout, show_progress=show_progress, use_server_cache=use_server_cache, use_historical_diddles_only=True, provider=provider, ) if start is not None: if dates is not None: raise ValueError('Must supply start or dates, not both') if end is None: end = dt.date.today() self.__date_range = tuple(date_range(start, end, calendars=calendars)) elif dates is not None: self.__date_range = tuple(dates) else: raise ValueError('Must supply start or dates') def _market(self, date: dt.date, location: str) -> CloseMarket: return CloseMarket(location=location, date=date, check=True) def calc(self, instrument: InstrumentBase, risk_measure: RiskMeasure) -> PricingFuture: futures = [] provider = instrument.provider if self.provider is None else self.provider scenario = self._scenario parameters = self._parameters location = self.market.location for date in self.__date_range: risk_key = RiskKey(provider, date, self._market(date, location), parameters, scenario, risk_measure) futures.append(self._calc(instrument, risk_key)) return HistoricalPricingFuture(futures) @property def date_range(self): return self.__date_range class BackToTheFuturePricingContext(HistoricalPricingContext): """ A context for producing valuations over multiple dates both in the past and into the future """ def __init__( self, start: Optional[Union[int, dt.date]] = None, end: Optional[Union[int, dt.date]] = None, calendars: Union[str, Tuple] = (), dates: Optional[Iterable[dt.date]] = None, roll_to_fwds: bool = True, is_async: bool = None, is_batch: bool = None, use_cache: bool = None, visible_to_gs: bool = None, csa_term: str = None, market_data_location: Optional[str] = None, timeout: Optional[int] = None, show_progress: Optional[bool] = None, name: Optional[str] = None, provider: Optional[Type[GenericRiskApi]] = None, ): """ A context for producing valuations over multiple dates :param start: start date :param end: end date (defaults to today) :param calendars: holiday calendars :param dates: a custom iterable of dates :param roll_to_fwds: if True then for future dates assume fwd curve is realised. If False assume spot rates are realised. (defaults to True) :param is_async: return immediately (True) or wait for results (False) (defaults to False) :param is_batch: use for calculations expected to run longer than 3 mins, to avoid timeouts. It can be used with is_async=True|False (defaults to False) :param use_cache: store results in the pricing cache (defaults to False) :param visible_to_gs: are the contents of risk requests visible to GS (defaults to False) :param csa_term: the csa under which the calculations are made. Default is local ccy ois index :param market_data_location: the location for sourcing market data ('NYC', 'LDN' or 'HKG' (defaults to LDN) **Examples** assuming today is between dt.date(2020, 7, 6) and dt.date(2020, 7, 14) >>> from gs_quant.instrument import IRSwap >>> >>> ir_swap = IRSwap('Pay', '10y', 'DKK') >>> with BackToTheFuturePricingContext(dt.date(2020, 7, 6), dt.date(2020, 7, 14), roll_to_fwds=True): >>> price_f = ir_swap.price() >>> >>> price_series = price_f.result() """ super().__init__( start=start, end=end, calendars=calendars, dates=dates, is_async=is_async, is_batch=is_batch, use_cache=use_cache, visible_to_gs=visible_to_gs, csa_term=csa_term, market_data_location=market_data_location, timeout=timeout, show_progress=show_progress, provider=provider, ) self._roll_to_fwds = roll_to_fwds self.name = name if start is not None: if dates is not None: raise ValueError('Must supply start or dates, not both') if end is None: end = dt.date.today() self.__date_range = tuple(date_range(start, end, calendars=calendars)) elif dates is not None: self.__date_range = tuple(dates) else: raise ValueError('Must supply start or dates') def calc(self, instrument: InstrumentBase, risk_measure: RiskMeasure) -> PricingFuture: futures = [] provider = instrument.provider if self.provider is None else self.provider base_scenario = self._scenario parameters = self._parameters location = self.market.location base_market = self.market for date in self.__date_range: if date > self.pricing_date: scenario = MarketDataScenario(RollFwd(date=date, realise_fwd=self._roll_to_fwds, name=self.name)) risk_key = RiskKey(provider, date, base_market, parameters, scenario, risk_measure) futures.append(self._calc(instrument, risk_key)) else: risk_key = RiskKey( provider, date, self._market(date, location), parameters, base_scenario, risk_measure ) futures.append(self._calc(instrument, risk_key)) return HistoricalPricingFuture(futures) ================================================ FILE: gs_quant/markets/index.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import json import pandas as pd from typing import Dict, Optional, List, Tuple from pydash import get from gs_quant.api.gs.assets import GsAsset, GsAssetApi from gs_quant.api.gs.data import GsDataApi from gs_quant.common import AssetClass, Currency, DateLimit from gs_quant.data.fields import DataMeasure from gs_quant.entities.entity import EntityType, PositionedEntity from gs_quant.errors import MqValueError from gs_quant.instrument import Instrument from gs_quant.json_encoder import JSONEncoder from gs_quant.markets.securities import Asset, AssetType from gs_quant.markets.indices_utils import ReturnType, STSIndexType, IndicesDatasets, PriceType from gs_quant.target.data import DataQuery from gs_quant.entities.tree_entity import AssetTreeNode, TreeHelper class Index(Asset, PositionedEntity): """ Index which tracks an evolving portfolio of securities, and can be traded through cash or derivatives markets. Includes support for STS indices. """ def __init__( self, id_: str, asset_class: AssetClass, name: str, exchange: Optional[str] = None, currency: Optional[Currency] = None, entity: Optional[Dict] = None, ): Asset.__init__(self, id_, asset_class, name, exchange, currency, entity=entity) PositionedEntity.__init__(self, id_, EntityType.ASSET) if entity: self.asset_type = AssetType(entity['type']) else: self.asset_type = AssetType.INDEX if self.__is_sts_index(): self.tree_helper = TreeHelper(id_, tree_underlier_dataset='STS_UNDERLIER_WEIGHTS') self.tree_df = pd.DataFrame() def __str__(self): return self.name def get_type(self) -> AssetType: return self.asset_type def get_currency(self) -> Optional[Currency]: return self.currency def get_return_type(self) -> ReturnType: if self.parameters is None or self.parameters.index_return_type is None: return ReturnType.TOTAL_RETURN return ReturnType(self.parameters.index_return_type) @classmethod def get(cls, identifier: str) -> Optional['Index']: """ Fetch an existing index :param identifier: Any common identifier for an index(ric, ticker, etc.) :return: Index object **Usage** Get existing Index instance **Examples** Get index details: >>> from gs_quant.markets.index import Index >>> >>> index = Index.get("GSMBXXXX") """ gs_asset = cls.__get_gs_asset(identifier) asset_entity: Dict = json.loads(json.dumps(gs_asset.as_dict(), cls=JSONEncoder)) if gs_asset.type.value in STSIndexType.to_list() or gs_asset.type.value == 'Index': return cls( gs_asset.id, gs_asset.asset_class, gs_asset.name, exchange=gs_asset.exchange, currency=gs_asset.currency, entity=asset_entity, ) else: raise MqValueError(f'{identifier} is not an Index identifier') def get_fundamentals( self, start: dt.date = DateLimit.LOW_LIMIT.value, end: dt.date = dt.date.today(), period: Optional[DataMeasure] = None, direction: DataMeasure = DataMeasure.FORWARD.value, metrics: List[DataMeasure] = DataMeasure.list_fundamentals(), ) -> pd.DataFrame: """ Retrieve fundamentals data for an index across a date range. Currently supports STS indices only :param start: start date (default is 1 January, 1970) :param end: end date (default is today) :param period: period for the relevant metric. Can be one of ONE_YEAR('1y'), TWO_YEARS('2y'), \ THREE_YEARS('3y') (default is all periods) :param direction: direction of the outlook period. Can be one of 'forward' or 'trailing' (default is forward) :param metrics: list of fundamentals metrics. (default is all) :return: dataframe with fundamentals information **Usage** Retrieve fundamentals data for an index across a date range **Examples** Retrieve historical dividend yield data for an index >>> from gs_quant.markets.index import Index >>> from gs_quant.data.fields import DataMeasure >>> >>> index = Index.get("GSMBXXXX") >>> index.get_fundamentals(metrics=[DataMeasure.DIVIDEND_YIELD]) """ if self.__is_sts_index(): where = dict(assetId=self.id, periodDirection=direction, metric=metrics) if period: where["period"] = period query = DataQuery(where=where, start_date=start, end_date=end) response = GsDataApi.query_data(query=query, dataset_id=IndicesDatasets.STS_FUNDAMENTALS.value) return pd.DataFrame(response) else: raise MqValueError('This method currently supports STS indices only') def get_latest_close_price(self, price_type: List[PriceType] = None) -> pd.DataFrame: """ Retrieve latest close prices for an index. Only STS indices support indicative prices. :param price_type: Type of prices to return. Default returns official close price :return: dataframe with latest close price **Usage** Retrieve latest close prices for an index **Examples** >>> from gs_quant.markets.index import Index >>> >>> index = Index.get("GSMBXXXX") >>> index.get_latest_close_price([PriceType.OFFICIAL_CLOSE_PRICE, PriceType.INDICATIVE_CLOSE_PRICE]) """ if (not price_type) or (price_type == [PriceType.OFFICIAL_CLOSE_PRICE]): return super().get_latest_close_price() prices = pd.DataFrame() if PriceType.OFFICIAL_CLOSE_PRICE in price_type: official_level = super().get_latest_close_price() prices['date'] = official_level.index prices['closePrice'] = official_level[0] if PriceType.INDICATIVE_CLOSE_PRICE in price_type: if self.__is_sts_index(): where = dict(assetId=self.id) query = DataQuery(where=where) response = GsDataApi.last_data(query=query, dataset_id=IndicesDatasets.STS_INDICATIVE_LEVELS.value) indicative_level = pd.DataFrame(response).iloc[-1:][['date', 'indicativeClosePrice']] prices['date'] = indicative_level['date'].iat[0] prices['indicativeClosePrice'] = indicative_level['indicativeClosePrice'].iat[0] else: raise MqValueError('PriceType.INDICATIVE_CLOSE_PRICE currently supports STS indices only') return prices def get_close_price_for_date( self, date: dt.date = dt.date.today(), price_type: List[PriceType] = None ) -> pd.DataFrame: """ Retrieve close prices for an index. Only STS indices support indicative prices. :param date: date of the required prices (default is today) :param price_type: Type of prices to return. Default returns official close price :return: dataframe with date's close prices **Usage** Retrieve the close prices for an index for a given date **Examples** >>> from gs_quant.markets.index import Index >>> >>> index = Index.get("GSMBXXXX") >>> index.get_close_price_for_date(dt.date(2021, 1, 7), \ [PriceType.OFFICIAL_CLOSE_PRICE, PriceType.INDICATIVE_CLOSE_PRICE]) """ if (not price_type) or (price_type == [PriceType.OFFICIAL_CLOSE_PRICE]): return super().get_close_price_for_date(date) prices = pd.DataFrame() if PriceType.OFFICIAL_CLOSE_PRICE in price_type: official_level = super().get_close_price_for_date(date) prices['date'] = official_level.index prices['closePrice'] = official_level[0] if PriceType.INDICATIVE_CLOSE_PRICE in price_type: if self.__is_sts_index(): response = self.__query_indicative_levels_dataset(start=date, end=date) indicative_level = pd.DataFrame(response) prices['date'] = indicative_level['date'].iat[0] prices['indicativeClosePrice'] = indicative_level['indicativeClosePrice'].iat[0] else: raise MqValueError('PriceType.INDICATIVE_CLOSE_PRICE currently supports STS indices only') return prices def get_close_prices( self, start: dt.date = DateLimit.LOW_LIMIT.value, end: dt.date = dt.date.today(), price_type: List[PriceType] = None, ) -> pd.DataFrame: """ Retrieve close prices for an index for a date range. Only STS indices support indicative prices. :param start: start date (default is 1 January, 1970) :param end: end date (default is today) :param price_type: Type of prices to return. Default returns official close price :return: dataframe with the close price between start and end date **Usage** Retrieve the close prices for an index for a given date range. **Examples** >>> from gs_quant.markets.index import Index >>> >>> index = Index.get("GSMBXXXX") >>> index.get_close_prices(dt.date(2021, 1, 7), dt.date(2021, 3, 27), \ [PriceType.OFFICIAL_CLOSE_PRICE, PriceType.INDICATIVE_CLOSE_PRICE]) """ if (not price_type) or (price_type == [PriceType.OFFICIAL_CLOSE_PRICE]): return super().get_close_prices(start, end) prices = pd.DataFrame() if self.__is_sts_index(): if price_type == [PriceType.INDICATIVE_CLOSE_PRICE]: indicative_level = self.__query_indicative_levels_dataset(start=start, end=end) indicative_level = indicative_level.drop(['updateTime', 'assetId'], axis=1) indicative_level = indicative_level.astype({'date': 'datetime64[ns]'}) prices['date'] = indicative_level['date'] prices['indicativeClosePrice'] = indicative_level['indicativeClosePrice'] return prices official_level = super().get_close_prices(start=start, end=end).to_frame('closePrice') indicative_level = self.__query_indicative_levels_dataset(start=start, end=end) official_level = official_level.reset_index() indicative_level = indicative_level.drop(['updateTime', 'assetId'], axis=1) indicative_levels = indicative_level.astype({'date': official_level.dtypes['date']}) merged = pd.merge(official_level, indicative_levels, on='date', how='outer') return merged else: raise MqValueError('PriceType.INDICATIVE_CLOSE_PRICE currently supports STS indices only') def get_underlier_tree(self, refresh_tree: Optional[bool] = False) -> AssetTreeNode: """ Get the root node of the tree formed by the Index, as an AssetTreeNode object. :param refresh_tree: Refresh the underliers information of the entire tree Please refer to the documenation of the AssetTreeNode class for more information. Currently supports STS indices only. :return: root node of the tree formed by the Index, as an AssetTreeNode object. **Examples** >>> from gs_quant.markets.index import Index >>> >>> index = Index.get("GSMBXXXX") >>> index.get_underlier_tree() """ if self.__is_sts_index(): if (not self.tree_helper.tree_built) or refresh_tree: self.tree_helper.build_tree() self.tree_helper.populate_weights('STS_UNDERLIER_WEIGHTS') self.tree_helper.populate_attribution('STS_UNDERLIER_ATTRIBUTION') self.tree_df = self.tree_helper.to_frame() return self.tree_helper.root else: raise MqValueError('This method currently supports STS indices only') def get_underlier_weights(self) -> pd.DataFrame: """ Get the weights of the immediate (one-level-down) underliers of the Index. Currently supports STS indices only. :return: pandas DataFrame with the weights of the immediate underliers. **Examples** >>> from gs_quant.markets.index import Index >>> >>> index = Index.get("GSMBXXXX") >>> index.get_underlier_weights() """ if self.__is_sts_index(): if len(self.tree_df) == 0: self.get_underlier_tree() return self.tree_df.loc[self.tree_df.depth == 1].drop( columns=['absoluteAttribution', 'assetId', 'assetName', 'depth'] ) else: raise MqValueError('This method currently supports STS indices only') def get_underlier_attribution(self) -> pd.DataFrame: """ Get the attribution of the immediate (one-level-down) underliers of the Index. Currently supports STS indices only. :return: pandas DataFrame with the attribution of the immediate underliers. **Examples** >>> from gs_quant.markets.index import Index >>> >>> index = Index.get("GSMBXXXX") >>> index.get_underlier_attribution() """ if self.__is_sts_index(): if len(self.tree_df) == 0: self.get_underlier_tree() return self.tree_df.loc[self.tree_df.depth == 1].drop(columns=['weight', 'assetId', 'assetName', 'depth']) else: raise MqValueError('This method currently supports STS indices only') def visualise_tree(self, visualise_by: Optional[str] = 'asset_name'): """ Visualise the tree by printing the structure of the entire tree. The visualise_by argument can be either 'asset_name' or 'bbid'. Currently supports STS indices only. :param visualise_by: Parameter to visualise by. Default uses Asset Name to label the nodes :return: treelib Tree object, which can be printed to see the tree structure. **Examples** >>> from gs_quant.markets.index import Index >>> >>> index = Index.get("GSMBXXXX") >>> print(index.visualise_tree()) """ if self.__is_sts_index(): return self.tree_helper.get_visualisation(visualise_by) else: raise MqValueError('This method currently supports STS indices only') def get_latest_constituents(self) -> pd.DataFrame: """ Fetch the latest constituents of the index in a pandas dataframe. :return: pandas dataframe with the index constituents, weights and other details. **Usage** Get the latest constituents of the index **Examples** Get latest index constituents: >>> from gs_quant.markets.index import Index >>> >>> index = Index.get("GSMBXXXX") >>> index.get_latest_constituents() """ return self.get_latest_position_set().get_positions() def get_constituents_for_date(self, date: dt.date = dt.date.today()) -> pd.DataFrame: """ Fetch the constituents of the index in a pandas dataframe for a the given date. :return: pandas dataframe with the index constituents, weights and other details. **Usage** Get the constituents of the index for the given date **Examples** Get index constituents: >>> import datetime as dt >>> from gs_quant.markets.index import Index >>> >>> index = Index.get("GSMBXXXX") >>> index.get_constituents_for_date(dt.date(2021, 7, 1)) """ return self.get_position_set_for_date(date).get_positions() def get_constituents( self, start: dt.date = DateLimit.LOW_LIMIT.value, end: dt.date = dt.date.today() ) -> List[pd.DataFrame]: """ Fetch the constituents of the index in a pandas dataframe for the given date range :return: pandas dataframe with the index constituents, weights and other details. **Usage** Get the constituents of the index for the given date range **Examples** Get index constituents: >>> import datetime as dt >>> from gs_quant.markets.index import Index >>> >>> index = Index.get("GSMBXXXX") >>> index.get_constituents(dt.date(2021, 6, 1), dt.date(2021, 6, 10)) """ return [position_set.get_positions() for position_set in self.get_position_sets(start, end)] def get_latest_constituent_instruments(self) -> Tuple[Instrument, ...]: """ Fetch the latest constituents of the index as instrument objects. :return: A Tuple of instrument objects **Usage** Get the latest constituents of the index **Examples** Get latest index constituent instruments: >>> from gs_quant.markets.index import Index >>> >>> index = Index.get("GSMBXXXX") >>> index.get_latest_constituent_instruments() """ return GsAssetApi.get_instruments_for_positions(self.get_latest_position_set().to_target().positions) def get_constituent_instruments_for_date(self, date: dt.date = dt.date.today()) -> Tuple[Instrument, ...]: """ Fetch the constituents of the index for a given date as instrument objects. :return: A Tuple of instrument objects **Usage** Get the constituents of the index for the given date as instrument objects **Examples** Get index constituent instruments: >>> import datetime as dt >>> from gs_quant.markets.index import Index >>> >>> index = Index.get("GSMBXXXX") >>> index.get_constituent_instruments_for_date(dt.date(2021, 7, 1)) """ return GsAssetApi.get_instruments_for_positions(self.get_position_set_for_date(date).to_target().positions) def get_constituent_instruments( self, start: dt.date = DateLimit.LOW_LIMIT.value, end: dt.date = dt.date.today() ) -> Tuple[Tuple[Instrument, ...], ...]: """ Fetch the constituents of the index as instrument objects for the given date range :return: Tuple of Tuples, containing all instrument objects for each date. **Usage** Get the constituents of the index for the given date range as instrument objects. **Examples** Get index constituent instruments: >>> import datetime as dt >>> from gs_quant.markets.index import Index >>> >>> index = Index.get("GSMBXXXX") >>> index.get_constituent_instruments(dt.date(2021, 6, 1), dt.date(2021, 6, 10)) """ position_sets = self.get_position_sets(start, end) return [ GsAssetApi.get_instruments_for_positions(position_set.to_target().positions) for position_set in position_sets ] def __is_sts_index(self) -> bool: """Checks if is an STS get_index""" if self.get_type().value in STSIndexType.to_list(): return True return False def __query_indicative_levels_dataset(self, start=None, end=None) -> pd.DataFrame: where = dict(assetId=self.id) if start is None: query = DataQuery(where=where) else: query = DataQuery(where=where, start_date=start, end_date=end) response = GsDataApi.query_data(query=query, dataset_id=IndicesDatasets.STS_INDICATIVE_LEVELS.value) indicative_level = pd.DataFrame(response) if len(indicative_level) == 0: indicative_level['date'] = '' indicative_level['assetId'] = '' indicative_level['updateTime'] = '' indicative_level['indicativeClosePrice'] = '' return indicative_level @staticmethod def __get_gs_asset(identifier: str) -> GsAsset: """Resolves index identifier during initialization""" response = GsAssetApi.resolve_assets(identifier=[identifier], fields=['id'], limit=1)[identifier] if len(response) == 0 or get(response, '0.id') is None: raise MqValueError(f'Asset could not be found using identifier {identifier}') return GsAssetApi.get_asset(get(response, '0.id')) ================================================ FILE: gs_quant/markets/indices_utils.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import pandas as pd from enum import Enum from functools import partial, reduce from pydash import get from time import sleep from typing import Dict, List, Optional, Union from gs_quant.api.gs.assets import GsAssetApi from gs_quant.api.gs.data import GsDataApi from gs_quant.api.gs.monitors import GsMonitorsApi from gs_quant.api.utils import ThreadPoolManager from gs_quant.base import EnumBase from gs_quant.common import AssetClass from gs_quant.datetime.date import prev_business_date from gs_quant.session import GsSession from gs_quant.target.data import DataQuery QUERY_LIMIT = 1000 class BasketType(EnumBase, Enum): """Basket Types""" CUSTOM_BASKET = 'Custom Basket' RESEARCH_BASKET = 'Research Basket' def __repr__(self): return self.value @classmethod def to_list(cls): return [basket_type.value for basket_type in cls] class CorporateActionType(EnumBase, Enum): """Different types of corporate actions""" ACQUISITION = 'Acquisition' CASH_DIVIDEND = 'Cash Dividend' IDENTIFIER_CHANGE = 'Identifier Change' RIGHTS_ISSUE = 'Rights Issue' SHARE_CHANGE = 'Share Change' SPECIAL_DIVIDEND = 'Special Dividend' SPIN_OFF = 'Spin Off' STOCK_DIVIDEND = 'Stock Dividend' STOCK_SPLIT = 'Stock Split' def __repr__(self): return self.value @classmethod def to_list(cls): return [ca_type.value for ca_type in cls] class CustomBasketStyles(EnumBase, Enum): """Styles for Custom Baskets""" AD_HOC_DESK_WORK = 'Ad Hoc Desk Work' CLIENT_CONSTRUCTED_WRAPPER = 'Client Constructed/Wrapper' CONSUMER = 'Consumer' ENERGY = 'Energy' ENHANCED_INDEX_SOLUTIONS = 'Enhanced Index Solutions' ESG = 'ESG' FACTORS = 'Factors' FINANCIALS = 'Financials' FLAGSHIP = 'Flagship' GEOGRAPHIC = 'Geographic' GROWTH = 'Growth' HEALTHCARE = 'Health Care' HEDGING = 'Hedging' INDUSTRIALS = 'Industrials' MATERIALS = 'Materials' MOMENTUM = 'Momentum' PIPG = 'PIPG' SECTORS_INDUSTRIES = 'Sectors/Industries' SIZE = 'Size' STRUCTURED_ONE_DELTA = 'Structured One Delta' THEMATIC = 'Thematic' TMT = 'TMT' UTILITIES = 'Utilities' VALUE = 'Value' VOLATILITY = 'Volatility' def __repr__(self): return self.value class IndicesDatasets(EnumBase, Enum): """Indices Datasets""" BASKET_FUNDAMENTALS = 'BASKET_FUNDAMENTALS' COMPOSITE_THEMATIC_BETAS = 'COMPOSITE_THEMATIC_BETAS' CREDIT_EOD_PRICING_V1_STANDARD = 'CREDIT_EOD_PRICING_V1_STANDARD' CORPORATE_ACTIONS = 'CA' GIRBASKETCONSTITUENTS = 'GIRBASKETCONSTITUENTS' GSBASKETCONSTITUENTS = 'GSBASKETCONSTITUENTS' GSCB_FLAGSHIP = 'GSCB_FLAGSHIP' GSCREDITBASKETCONSTITUENTS = 'GSCREDITBASKETCONSTITUENTS' STS_FUNDAMENTALS = 'STS_FUNDAMENTALS' STS_INDICATIVE_LEVELS = 'STS_INDICATIVE_LEVELS' THEMATIC_FACTOR_BETAS_STANDARD = 'THEMATIC_FACTOR_BETAS_V2_STANDARD' def __repr__(self): return self.value class PriceType(EnumBase, Enum): """Index Price Types""" INDICATIVE_CLOSE_PRICE = 'indicativeClosePrice' OFFICIAL_CLOSE_PRICE = 'officialClosePrice' def __repr__(self): return self.value @classmethod def to_list(cls): return [price_type for price_type in cls] class Region(EnumBase, Enum): """Region of the index""" AMERICAS = 'Americas' ASIA = 'Asia' EM = 'EM' EUROPE = 'Europe' GLOBAL = 'Global' def __repr__(self): return self.value class ResearchBasketStyles(EnumBase, Enum): """Styles for Research Baskets""" ASIA_EX_JAPAN = 'Asia ex-Japan' EQUITY_THEMATIC = 'Equity Thematic' EUROPE = 'Europe' FUND_OWNERSHIP = 'Fund Ownership' FUNDAMENTALS = 'Fundamentals' FX_OIL = 'FX/Oil' GEOGRAPHICAL_EXPOSURE = 'Geographical Exposure' HEDGE_FUND = 'Hedge Fund' IP_FACTORS = 'Investment Profile (IP) Factors' JAPAN = 'Japan' MACRO = 'Macro' MACRO_SLICE_STYLES = 'Macro Slice/Styles' MUTUAL_FUND = 'Mutual Fund' POSITIONING = 'Positioning' PORTFOLIO_STRATEGY = 'Portfolio Strategy' RISK_AND_LIQUIDITY = 'Risk & Liquidity' SECTOR = 'Sector' SHAREHOLDER_RETURN = 'Shareholder Return' STYLE_FACTOR_AND_FUNDAMENTAL = 'Style, Factor and Fundamental' STYLES_THEMES = 'Style/Themes' TACTICAL_RESEARCH = 'Tactical Research' THEMATIC = 'Thematic' US = 'US' WAVEFRONT_COMPONENTS = 'Wavefront Components' WAVEFRONT_PAIRS = 'Wavefront Pairs' WAVEFRONTS = 'Wavefronts' def __repr__(self): return self.value class ReturnType(EnumBase, Enum): """Determines the index calculation methodology with respect to dividend reinvestment""" GROSS_RETURN = 'Gross Return' PRICE_RETURN = 'Price Return' TOTAL_RETURN = 'Total Return' def __repr__(self): return self.value class STSIndexType(EnumBase, Enum): """STS Types""" ACCESS = 'Access' MULTI_ASSET_ALLOCATION = 'Multi-Asset Allocation' RISK_PREMIA = 'Risk Premia' SYSTEMATIC_HEDGING = 'Systematic Hedging' def __repr__(self): return self.value @classmethod def to_list(cls): return [sts_type.value for sts_type in cls] class WeightingStrategy(EnumBase, Enum): """Strategy used to price the index's position set""" EQUAL = 'Equal' MARKET_CAPITALIZATION = 'Market Capitalization' QUANTITY = 'Quantity' WEIGHT = 'Weight' def __repr__(self): return self.value def get_my_baskets(user_id: str = None) -> Optional[pd.DataFrame]: """ Retrieve a list of baskets a user is permissioned to :param user_id: Marquee user/app ID (default is current application's id) :return: dataframe of baskets user has access to **Usage** Retrieve a list of baskets a user is permissioned to **Examples** Retrieve a list of baskets the current user is permissioned to >>> from gs_quant.markets.indices_utils import * >>> >>> get_my_baskets() """ user_id = user_id if user_id is not None else GsSession.current.client_id tag = f'Custom Basket:{user_id}' response = GsMonitorsApi.get_monitors(tags=tag) if len(response): row_groups = get(response, '0.parameters.row_groups') my_baskets = [] for row_group in row_groups: entity_ids = [entity.id for entity in row_group.entity_ids] baskets = GsAssetApi.get_many_assets_data(id=entity_ids, fields=['id', 'ticker', 'name', 'liveDate']) my_baskets += [ dict( monitor_name=row_group.name, id=get(basket, 'id'), ticker=get(basket, 'ticker'), name=get(basket, 'name'), live_date=get(basket, 'liveDate'), ) for basket in baskets ] return pd.DataFrame(my_baskets) def __get_baskets( fields: List[str] = [], basket_type: List[BasketType] = BasketType.to_list(), asset_class: List[AssetClass] = [AssetClass.Equity], region: List[Region] = None, styles: List[Union[CustomBasketStyles, ResearchBasketStyles]] = None, as_of: dt.datetime = None, **kwargs, ) -> Dict: default_fields = set(['id', 'name', 'ticker', 'region', 'type', 'description', 'styles', 'liveDate', 'assetClass']) query, fields = {}, list(set(fields).union(default_fields)) for k, v in kwargs.items(): query[k] = v if region: query['region'] = region if styles: query['styles'] = styles query = dict( fields=fields, type=basket_type, asset_class=asset_class, is_pair_basket=[False], flagship=[True], **query ) return GsAssetApi.get_many_assets_data_scroll(**query, as_of=as_of, limit=QUERY_LIMIT, scroll='1m') def __get_dataset_id(asset_class: AssetClass, basket_type: BasketType, data_type: str) -> str: if asset_class == AssetClass.Equity or asset_class == AssetClass.Equity.value: if data_type == 'price': return IndicesDatasets.GSCB_FLAGSHIP.value elif basket_type == BasketType.CUSTOM_BASKET or basket_type == BasketType.CUSTOM_BASKET.value: return IndicesDatasets.GSBASKETCONSTITUENTS.value else: return IndicesDatasets.GIRBASKETCONSTITUENTS.value raise NotImplementedError(f'{data_type} data for {asset_class} baskets is unsupported at this time') def get_flagship_baskets( fields: List[str] = [], basket_type: List[BasketType] = BasketType.to_list(), asset_class: List[AssetClass] = [AssetClass.Equity], region: List[Region] = None, styles: List[Union[CustomBasketStyles, ResearchBasketStyles]] = None, as_of: dt.datetime = None, **kwargs, ) -> pd.DataFrame: """ Retrieve flagship baskets :param fields: Fields to retrieve in addition to mqid, name, ticker, region, basket type, \ description, styles, live date, and asset class :param basket_type: Basket type(s) :param asset_class: Asset class (defaults to Equity) :param region: Basket region(s) :param styles: Basket style(s) :param as_of: Datetime for which to retrieve baskets (defaults to current time) :return: flagship baskets **Usage** Retrieve a list of flagship baskets **Examples** Retrieve a list of flagship baskets >>> from gs_quant.markets.indices_utils import * >>> >>> get_flagship_baskets() **See also** :func:`get_flagships_with_assets` :func:`get_flagships_performance` :func:`get_flagships_constituents` """ response = __get_baskets( fields=fields, as_of=as_of, basket_type=basket_type, asset_class=asset_class, region=region, styles=styles, **kwargs, ) return pd.DataFrame(response) def get_flagships_with_assets( identifiers: List[str], fields: List[str] = [], basket_type: List[BasketType] = BasketType.to_list(), asset_class: List[AssetClass] = [AssetClass.Equity], region: List[Region] = None, styles: List[Union[CustomBasketStyles, ResearchBasketStyles]] = None, as_of: dt.datetime = None, **kwargs, ) -> pd.DataFrame: """ Retrieve a list of flagship baskets containing specified assets :param identifiers: List of asset identifiers :param fields: Fields to retrieve in addition to mqid, name, ticker, region, basket type, \ description, styles, live date, and asset class :param basket_type: Basket type(s) :param asset_class: Asset class (defaults to Equity) :param region: Basket region(s) :param styles: Basket style(s) :param as_of: Datetime for which to retrieve baskets (defaults to current time) :return: flagship baskets containing specified assets **Usage** Retrieve a list of flagship baskets containing specified assets **Examples** Retrieve a list of flagship custom baskets containing 'AAPL UW' single stock >>> from gs_quant.markets.indices_utils import * >>> >>> get_flagships_with_assets(identifiers=['AAPL UW'], basket_type=[BasketType.CUSTOM_BASKET]) **See also** :func:`get_flagship_baskets` :func:`get_flagships_performance` :func:`get_flagships_constituents` """ response = GsAssetApi.resolve_assets(identifier=identifiers, fields=['id'], limit=1) mqids = [get(asset, '0.id') for asset in response.values()] response = __get_baskets( fields=fields, as_of=as_of, basket_type=basket_type, asset_class=asset_class, region=region, styles=styles, underlying_asset_ids=mqids, **kwargs, ) return pd.DataFrame(response) def get_flagships_performance( fields: List[str] = [], basket_type: List[BasketType] = BasketType.to_list(), asset_class: List[AssetClass] = [AssetClass.Equity], region: List[Region] = None, styles: List[Union[CustomBasketStyles, ResearchBasketStyles]] = None, start: dt.date = None, end: dt.date = None, **kwargs, ) -> pd.DataFrame: """ Retrieve performance data for flagship baskets :param fields: Fields to retrieve in addition to bbid, mqid, name, region, basket type, \ styles, live date, and asset class :param basket_type: Basket type(s) :param asset_class: Asset class (defaults to Equity) :param region: Basket region(s) :param styles: Basket style(s) :param start: Date for which to retrieve pricing (defaults to previous business day) :param end: Date for which to retrieve pricing (defaults to previous business day) :return: pricing data for flagship baskets **Usage** Retrieve performance data for flagship baskets **Examples** Retrieve performance data for flagship Asia custom baskets >>> from gs_quant.markets.indices_utils import * >>> >>> get_flagships_performance(basket_type=[BasketType.CUSTOM_BASKET], region=[Region.ASIA]) **See also** :func:`get_flagships_with_assets` :func:`get_flagship_baskets` :func:`get_flagships_constituents` """ start, end = start or prev_business_date(), end or prev_business_date() assets = __get_baskets( fields=fields, basket_type=basket_type, asset_class=asset_class, region=region, styles=styles, **kwargs ) baskets = {b.get('id'): b for b in assets} dataset_id = __get_dataset_id(asset_class=asset_class[0], basket_type=basket_type[0], data_type='price') coverage = GsDataApi.get_coverage(dataset_id=dataset_id, fields=['id']) mqids = [b.get('assetId') for b in coverage if b.get('assetId') in baskets.keys()] batches = [mqids[i * 500 : (i + 1) * 500] for i in range((len(mqids) + 500 - 1) // 500)] response, performance = [], [] for b in batches: response += GsDataApi.query_data( query=DataQuery(where={'assetId': b}, startDate=start, endDate=end), dataset_id=dataset_id ) for b in response: data = baskets.get(b.get('assetId')) b.update(data) b.pop('assetId') b.pop('updateTime') performance.append(b) return pd.DataFrame(performance) def get_flagships_constituents( fields: List[str] = [], basket_type: List[BasketType] = BasketType.to_list(), asset_class: List[AssetClass] = [AssetClass.Equity], region: List[Region] = None, styles: List[Union[CustomBasketStyles, ResearchBasketStyles]] = None, start: dt.date = None, end: dt.date = None, **kwargs, ) -> pd.DataFrame: """ Retrieve flagship baskets constituents :param fields: Fields to retrieve in addition to mqid, name, ticker, region, basket type, \ styles, live date, and asset class :param basket_type: Basket type(s) :param asset_class: Asset class (defaults to Equity) :param region: Basket region(s) :param styles: Basket style(s) :param start: Start date for which to retrieve constituents (defaults to previous business day) :param end: End date for which to retrieve constituents (defaults to previous business day) :return: flagship baskets constituents **Usage** Retrieve flagship baskets constituents **Examples** Retrieve a list of flagship baskets constituents >>> from gs_quant.markets.indices_utils import * >>> >>> get_flagships_constituents() **See also** :func:`get_flagships_with_assets` :func:`get_flagships_performance` :func:`get_flagship_baskets` """ start, end = start or prev_business_date(), end or prev_business_date() basket_fields = list( set(fields).union(set(['id', 'name', 'ticker', 'region', 'type', 'styles', 'liveDate', 'assetClass'])) ) fields = list(set(fields).union(set(['id']))) response = __get_baskets( fields=['id'], basket_type=basket_type, asset_class=asset_class, region=region, styles=styles, **kwargs ) basket_ids = [b.get('id') for b in response] cov_dataset_id = __get_dataset_id(asset_class=asset_class[0], basket_type=basket_type[0], data_type='price') coverage = GsDataApi.get_coverage(dataset_id=cov_dataset_id, fields=basket_fields, include_history=True) basket_map = {b['assetId']: {**b, 'constituents': []} for b in coverage if b['assetId'] in basket_ids} basket_dataset_query_map, constituents_data, tasks = {}, [], [] # get appropriate dataset for each basket for b in basket_map.values(): dataset_id = __get_dataset_id(asset_class=b['assetClass'], basket_type=b['type'], data_type='constituents') if dataset_id: basket_dataset_query_map[dataset_id] = basket_dataset_query_map.get(dataset_id, []) + [b['assetId']] # query constituents in batches of 25 for ds, ids in basket_dataset_query_map.items(): batches = [ids[i * 25 : (i + 1) * 25] for i in range((len(ids) + 25 - 1) // 25)] for batch in batches: tasks.append( partial( GsDataApi.query_data, query=DataQuery(where={'assetId': batch}, startDate=start, endDate=end), dataset_id=ds, ) ) # run 5 parallel dataset queries at a time tasks = [tasks[i * 2 : (i + 1) * 2] for i in range((len(tasks) + 2 - 1) // 2)] for task in tasks: constituents_data += ThreadPoolManager.run_async(task) sleep(1) constituents_data = reduce(lambda a, b: a + b, constituents_data) # fetch asset positions data # fetch asset positions data in batches to avoid exceeding API limits asset_ids = list(set([row['underlyingAssetId'] for row in constituents_data])) asset_data = [] # Process in batches of 100 to stay below scroll size limit batch_size = 100 for i in range(0, len(asset_ids), batch_size): batch = asset_ids[i : i + batch_size] asset_data += GsAssetApi.get_many_assets_data_scroll( id=batch, fields=fields, limit=QUERY_LIMIT, scroll='1m', source="Basket" ) asset_data_map = {get(asset, 'id'): asset for asset in asset_data} for row in constituents_data: basket_id = get(row, 'assetId', '') asset_id = get(row, 'underlyingAssetId', '') asset_id_map = get(asset_data_map, asset_id, {}) for f in fields: row[f] = get(asset_id_map, f) basket_map[basket_id]['constituents'].append(row) return pd.DataFrame([r for r in basket_map.values() if r is not None]) def get_constituents_dataset_coverage( basket_type: BasketType = BasketType.CUSTOM_BASKET, asset_class: AssetClass = AssetClass.Equity, as_of: dt.datetime = None, ) -> pd.DataFrame: """ Retrieve a list of baskets covered by constituents datasets :param basket_type: Basket type :param asset_class: Asset class (defaults to Equity) :param as_of: Date for which to retrieve coverage :return: baskets covered by the constituents dataset **Usage** Retrieve a list of baskets covered by constituents datasets **Examples** Retrieve basket constituent dataset coverage >>> from gs_quant.markets.indices_utils import * >>> >>> GSBASKETCONSTITUENTS_COVERAGE = get_constituents_dataset_coverage() >>> GIRBASKETCONSTITUENTS_COVERAGE = get_constituents_dataset_coverage(basket_type=BasketType.RESEARCH_BASKET) >>> GSCREDITBASKETCONSTITUENTS_COVERAGE = get_constituents_dataset_coverage(asset_class=AssetClass.Credit) **See also** :func:`get_flagships_constituents` """ query = dict( fields=['id', 'name', 'region', 'ticker', 'type', 'assetClass'], type=[basket_type], asset_class=[asset_class], is_pair_basket=[False], listed=[True], ) if asset_class != AssetClass.Equity: query.pop('is_pair_basket') response = GsAssetApi.get_many_assets_data_scroll(**query, as_of=as_of, limit=QUERY_LIMIT, scroll='1m') return pd.DataFrame(response) ================================================ FILE: gs_quant/markets/markets.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import re from typing import Mapping, Optional, Tuple, Union from gs_quant.base import Market, RiskKey from gs_quant.common import ( CloseMarket as _CloseMarket, LiveMarket as _LiveMarket, OverlayMarket as _OverlayMarket, RelativeMarket as _RelativeMarket, TimestampedMarket as _TimestampedMarket, RefMarket as _RefMarket, PricingLocation, ) from gs_quant.datetime.date import prev_business_date, location_to_tz_mapping from gs_quant.target.data import ( MarketDataCoordinate as __MarketDataCoordinate, MarketDataCoordinateValue as __MarketDataCoordinateValue, ) def historical_risk_key(risk_key: RiskKey) -> RiskKey: market = LocationOnlyMarket(risk_key.market.location) return RiskKey(risk_key.provider, None, market, risk_key.params, risk_key.scenario, risk_key.risk_measure) def market_location(location: Optional[PricingLocation] = None) -> PricingLocation: """Determine PricingLocation and ensure passed optional 'location' does not conflict with the current PricingContext's market_data_location :param location: optional PricingLocation :return: PricingLocation """ from .core import PricingContext default = PricingContext.current.market_data_location if location is None: return default or PricingLocation.LDN else: return location def close_market_date( location: Optional[Union[PricingLocation, str]] = None, date: Optional[dt.date] = None, roll_hr_and_min: Tuple[int, int] = (24, 0), ) -> dt.date: """Determine market data date based on current location (to infer calendar) and current pricing date :param location: date location, used for the roll timezone :param date: pricing date :param roll_hr_and_min: tuple of the hour and minute of the expected market data availability time in the location timezone :return: close market date """ from .core import PricingContext date = date or PricingContext.current.pricing_date location_tz = location_to_tz_mapping[PricingLocation(location)] now_time = dt.datetime.now().astimezone(location_tz).replace(tzinfo=None) hr_offset = roll_hr_and_min[0] min_offset = roll_hr_and_min[1] roll_time = dt.datetime(date.year, date.month, date.day).replace(tzinfo=None) + dt.timedelta( hours=hr_offset, minutes=min_offset ) if now_time < roll_time: # Don't use the calendars argument here as external users do not (yet) have access to that dataset date = prev_business_date(date) return date class MarketDataCoordinate(__MarketDataCoordinate): def __repr__(self): ret = "_".join(f or '' for f in (self.mkt_type, self.mkt_asset, self.mkt_class)) if self.mkt_point: ret += '_' + ','.join(self.mkt_point) if self.mkt_quoting_style: ret += f'.{self.mkt_quoting_style}' return ret @classmethod def from_string(cls, value: str): from gs_quant.api.gs.data import GsDataApi ret = GsDataApi._coordinate_from_str(value) if len(ret.mkt_point) == 1: # Unfortunately _,; have all been used as delimiters in various places ret.mkt_point = tuple(re.split('[,_;]', ret.mkt_point[0])) return ret class MarketDataCoordinateValue(__MarketDataCoordinateValue): def __repr__(self): return f'{self.coordinate} --> {self.value}' Coordinates = Tuple[MarketDataCoordinate, ...] MarketDataMap = Mapping[MarketDataCoordinate, float] class LocationOnlyMarket(Market): def __init__(self, location: Optional[Union[str, PricingLocation]]): self.__location = ( location if isinstance(location, PricingLocation) or location is None else PricingLocation(location) ) @property def market(self): return None @property def location(self) -> PricingLocation: return self.__location class CloseMarket(Market): """Market Object which captures market data based on market_location and close_market_date """ __date_cache = {} roll_hr_and_min = (24, 0) # tuple of today's expected hr/min of market data availability in the location tz def __init__( self, date: Optional[dt.date] = None, location: Optional[Union[str, PricingLocation]] = None, check: Optional[bool] = True, ): self.__date = date self.__location = ( location if isinstance(location, PricingLocation) or location is None else PricingLocation(location) ) self.check = check def __repr__(self): return f'{self.date} ({self.location.value})' @property def market(self): return _CloseMarket(date=self.date, location=self.location) def to_dict(self): return {'date': self.date, 'location': self.location, 'marketType': 'CloseMarket'} def __hash__(self): return hash((self.date, self.location)) def __eq__(self, other): return isinstance(other, CloseMarket) and self.date == other.date and self.location == other.location @property def location(self) -> PricingLocation: if self.__location is not None and not self.check: return self.__location else: return market_location(self.__location) @property def date(self) -> dt.date: if self.__date is not None and not self.check: return self.__date else: return close_market_date(self.location, self.__date, self.roll_hr_and_min) class TimestampedMarket(Market): """Market Object which captures market data based on location and timestamp """ def __init__(self, timestamp: dt.datetime, location: Optional[Union[str, PricingLocation]] = None): self.__timestamp = timestamp self.__location = ( location if isinstance(location, PricingLocation) or location is None else PricingLocation(location) ) def __repr__(self): return f'{self.__timestamp} ({self.location.value})' @property def market(self): return _TimestampedMarket(timestamp=self.__timestamp, location=self.location) @property def location(self) -> PricingLocation: return market_location(self.__location) class LiveMarket(Market): """Market Object which captures market data based on location and time at runtime """ def __init__(self, location: Optional[Union[str, PricingLocation]] = None): self.__location = ( location if isinstance(location, PricingLocation) or location is None else PricingLocation(location) ) def __repr__(self): return f'Live ({self.location.value})' @property def location(self) -> PricingLocation: return market_location(self.__location) @property def market(self): return _LiveMarket(location=self.location) class OverlayMarket(Market): """Market Object which overlays a base Market object (eg: CloseMarket, LiveMarket or TimestampedMarket) with a MarketDataMap (a map of market coordinate to float) """ def __init__( self, market_data: Optional[MarketDataMap] = None, base_market: Optional[Market] = None, binary_mkt_data: Optional[str] = None, ): market_data = market_data or {} self.__base_market = base_market or CloseMarket() self.__market_data = dict(filter(lambda elem: elem[1] != 'redacted', market_data.items())) self.__market_model_data = binary_mkt_data self.__redacted_coordinates = tuple(key for (key, value) in market_data.items() if value == 'redacted') def __getitem__(self, item): if isinstance(item, str): item = MarketDataCoordinate.from_string(item) return self.__market_data.get(item) def __setitem__(self, key, value): if isinstance(key, str): key = MarketDataCoordinate.from_string(key) if key in self.redacted_coordinates: raise KeyError(f'{key} cannot be overridden') self.__market_data[key] = value def __repr__(self): return f'Overlay ({id(self)}): {repr(self.__base_market)}' @property def market_data(self) -> Tuple[MarketDataCoordinateValue, ...]: return tuple(MarketDataCoordinateValue(coordinate=c, value=v) for c, v in self.__market_data.items()) @property def market_model_data(self) -> str: return self.__market_model_data @property def market_data_dict(self) -> MarketDataMap: return {p.coordinate: p.value for p in self.market_data} @property def location(self) -> PricingLocation: return self.__base_market.location @property def market(self): return _OverlayMarket( base_market=self.__base_market.market, market_data=self.market_data, market_model_data=self.market_model_data, ) @property def coordinates(self) -> Coordinates: return tuple(self.__market_data.keys()) @property def redacted_coordinates(self) -> Coordinates: return self.__redacted_coordinates class RefMarket(Market): """Market Object which represents a Reference to a Market""" def __init__(self, market_ref: str): self.__market = _RefMarket(market_ref=str(market_ref)) def __repr__(self): return f'Market Ref ({self.__market.market_ref})' @property def market(self): return self.__market @property def location(self) -> PricingLocation: return market_location() class RelativeMarket(Market): """Market Object which captures the change between two Market Objects (to_market and from_market) """ def __init__(self, from_market: Market, to_market: Market): self.__from_market = from_market self.__to_market = to_market def __repr__(self): return f'{repr(self.__from_market)} -> {repr(self.__to_market)}' @property def market(self): return _RelativeMarket(from_market=self.__from_market.market, to_market=self.__to_market.market) @property def location(self) -> PricingLocation: return self.__from_market.location if self.__from_market.location == self.__to_market.location else None ================================================ FILE: gs_quant/markets/optimizer.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import logging from enum import Enum from functools import wraps from typing import List, Dict, Optional, Union, Final from dateutil.relativedelta import relativedelta from gs_quant.api.gs.hedges import GsHedgeApi from gs_quant.api.gs.assets import GsAssetApi from gs_quant.errors import MqValueError from gs_quant.markets.factor import Factor from gs_quant.markets.position_set import PositionSet, Position from gs_quant.markets.securities import Asset from gs_quant.models.risk_model import FactorRiskModel from gs_quant.session import GsSession from gs_quant.target.hedge import CorporateActionsTypes import pandas as pd import numpy as np import math import datetime as dt _logger = logging.getLogger(__name__) def resolve_assets_in_batches( identifiers: List[str], fields: List[str] = None, as_of_date: dt.date = dt.date.today(), batch_size: int = 100, **kwargs, ) -> List[Dict]: all_fields = ["id", "name", "bbid"] if fields: all_fields += fields identifiers_batches = ( np.array_split(identifiers, math.ceil(len(identifiers) / batch_size)) if len(identifiers) > batch_size else [identifiers] ) all_assets_resolved = {} for batch in identifiers_batches: res = GsAssetApi.resolve_assets( identifier=list(batch), as_of=dt.datetime.combine(as_of_date, dt.datetime.min.time()), fields=all_fields, limit=1, **kwargs, ) all_assets_resolved = {**all_assets_resolved, **res} assets_resolved_as_records = [] for identifier in all_assets_resolved: if all_assets_resolved[identifier]: assets_resolved_as_records.append({"identifier": identifier, **all_assets_resolved[identifier][0]}) return assets_resolved_as_records class OptimizationConstraintUnit(Enum): DECIMAL = 'Decimal' NOTIONAL = 'Notional' PERCENT = 'Percent' class HedgeTarget(Enum): HEDGED_TARGET = "hedgedTarget" HEDGE = "hedge" TARGET = "target" class OptimizerObjective(Enum): MINIMIZE_FACTOR_RISK = 'Minimize Factor Risk' class OptimizerRiskType(Enum): VARIANCE = 'Variance' class OptimizerObjectiveTerm: DEFAULT_RISK_PARAMS: Final = { 'factor_weight': 1, 'specific_weight': 1, 'risk_type': OptimizerRiskType.VARIANCE, } def __init__(self, weight: float = 1, params: Dict[str, float] = DEFAULT_RISK_PARAMS): self.__weight = weight self.__params = {**self.DEFAULT_RISK_PARAMS, **params} @property def params(self) -> Dict: return self.__params @params.setter def params(self, params: Dict[str, float]): self.__params = {**self.DEFAULT_RISK_PARAMS, **params} @property def weight(self) -> float: return self.__weight @weight.setter def weight(self, weight: float): self.__weight = weight def to_dict(self) -> Dict: payload = { 'factorWeight': self.__params['factor_weight'], 'specificWeight': self.__params['specific_weight'], 'riskType': self.__params['risk_type'].value, 'weight': self.__weight, } return payload class OptimizerObjectiveParameters: def __init__( self, objective: OptimizerObjective = OptimizerObjective.MINIMIZE_FACTOR_RISK, terms: List[OptimizerObjectiveTerm] = [OptimizerObjectiveTerm.DEFAULT_RISK_PARAMS], ): self.__objective = objective self.__terms = terms @property def objective(self): return self.__objective @objective.setter def objective(self, objective: OptimizerObjective): self.__objective = objective @property def terms(self): return self.__terms @terms.setter def terms(self, terms: List[OptimizerObjectiveTerm]): self.__terms = terms def to_dict(self): if len(self.__terms) != 1: raise MqValueError('Only single risk term is supported') return {'parameters': self.__terms[0].to_dict()} class OptimizerType(Enum): AXIOMA_PORTFOLIO_OPTIMIZER = 'Axioma Portfolio Optimizer' class PrioritySetting(Enum): ZERO = '0' ONE = '1' TWO = '2' THREE = '3' FOUR = '4' FIVE = '5' class TurnoverNotionalType(Enum): NET = 'Net' LONG = 'Long' GROSS = 'Gross' class AssetUniverse: def __init__(self, identifiers: List[str], asset_ids: List[str] = None, as_of_date: dt.date = dt.date.today()): self.__identifiers = identifiers self.__as_of_date = as_of_date self.__asset_ids = asset_ids @property def identifiers(self): return self.__identifiers @identifiers.setter def identifiers(self, identifiers: List[str]): self.__identifiers = identifiers @property def asset_ids(self): return self.__asset_ids @asset_ids.setter def asset_ids(self, asset_ids: List[str]): self.__asset_ids = asset_ids @property def as_of_date(self): return self.__as_of_date @as_of_date.setter def as_of_date(self, date: dt.date): self.__as_of_date = date def resolve(self): if not self.__asset_ids: assets_resolved_as_records = resolve_assets_in_batches( identifiers=self.identifiers, as_of_date=self.as_of_date, batch_size=250 ) assets_resolved_df = ( pd.DataFrame(assets_resolved_as_records).set_index("identifier").reindex(self.identifiers) ) self.asset_ids = assets_resolved_df['id'].values.tolist() class AssetConstraint: def __init__( self, asset: Union[Asset, str], minimum: float = 0, maximum: float = 100, unit: OptimizationConstraintUnit = OptimizationConstraintUnit.PERCENT, ): self.__asset = asset self.__minimum = minimum self.__maximum = maximum self.__unit = unit @property def asset(self) -> Union[Asset, str]: return self.__asset @asset.setter def asset(self, value: Union[Asset, str]): self.__asset = value @property def minimum(self) -> float: return self.__minimum @minimum.setter def minimum(self, value: float): self.__minimum = value @property def maximum(self) -> float: return self.__maximum @maximum.setter def maximum(self, value: float): self.__maximum = value @property def unit(self) -> OptimizationConstraintUnit: return self.__unit @unit.setter def unit(self, value: OptimizationConstraintUnit): self.__unit = value def to_dict(self): return { 'assetId': self.asset if isinstance(self.asset, str) else self.asset.get_marquee_id(), 'min': self.minimum * 100 if self.unit == OptimizationConstraintUnit.DECIMAL else self.minimum, 'max': self.maximum * 100 if self.unit == OptimizationConstraintUnit.DECIMAL else self.maximum, } @classmethod def build_many_constraints( cls, asset_constraints: Union[pd.DataFrame, List[Dict]], as_of_date: dt.date = dt.date.today(), fail_on_unresolved_positions: bool = True, **kwargs, ): """Create many asset constraints from a dataframe or a list of dictionaries :param asset_constraints: dataframe or list of dictionaries containing the asset constraints :param as_of_date: the date on which to resolve the assets :param fail_on_unresolved_positions: whether to raise an error if any assets cannot be resolved :param kwargs: additional arguments to pass to the resolve_assets_in_batches function. :return: list of AssetConstraint objects :raises MqValueError: if the input is missing required columns "identifier", "minimum", "maximum", or "unit" :raises MqValueError: if any assets cannot be resolved and fail_on_unresolved_positions is True :raises MqValueError: if the input asset constraints are in more than one unit **Examples** 1. The input is a list of dictionaries >>> asset_constraints = AssetConstraint.build_many_constraints( >>> [{"identifier": "AAPL UW", "minimum": 0, "maximum": 5, "unit": "Percent"}, >>> {"identifier": "MSFT UW", "minimum": 0, "maximum": 5, "unit": "Percent"}, >>> {"identifier": "NVDA UW", "minimum": 0, "maximum": 5, "unit": "Percent"} >>> ], >>> as_of_date=dt.date(2025, 2, 24)) 2. The input is a dataframe >>> asset_constraints = AssetConstraint.build_many_constraints( >>> pd.DataFrame([{"identifier": "AAPL UW", "minimum": 0, "maximum": 5, "unit": "Percent"}, >>> {"identifier": "MSFT UW", "minimum": 0, "maximum": 5, "unit": "Percent"}, >>> {"identifier": "NVDA UW", "minimum": 0, "maximum": 5, "unit": "Percent"}]), >>> as_of_date=dt.date(2025, 2, 24) >>> ) Additional arguments can also be provided to the function and these will be used to resolve assets. Below we are adding a `type` and `assetClass` argument to tell the internal GS Security master to only return Single Stocks, Equity Indices, and ETFs. >>> asset_constraints = AssetConstraint.build_many_constraints( >>> pd.DataFrame([{"identifier": "AAPL UW", "minimum": 0, "maximum": 5, "unit": "Percent"}, >>> {"identifier": "MSFT UW", "minimum": 0, "maximum": 5, "unit": "Percent"}, >>> {"identifier": "NVDA UW", "minimum": 0, "maximum": 5, "unit": "Percent"}]), >>> as_of_date=dt.date(2025, 2, 24), >>> fail_on_unresolved_positions=True, >>> type=["Single Stock", "Index", "ETF"], >>> assetClass=["Equity"] >>> ) """ asset_constraints_df = ( pd.DataFrame(asset_constraints) if isinstance(asset_constraints, list) else asset_constraints ) missing_columns = [ col for col in ['identifier', 'minimum', "maximum", "unit"] if col not in asset_constraints_df.columns ] if missing_columns: raise MqValueError(f"The input is missing required columns: {', '.join(missing_columns)}") if len(set(asset_constraints_df['unit'].values.tolist())) > 1: raise MqValueError('All asset constraints must be in the same unit') if 'assetId' not in asset_constraints_df: identifiers = asset_constraints_df['identifier'].values.tolist() assets_resolved_as_records = resolve_assets_in_batches( identifiers=identifiers, as_of_date=as_of_date, batch_size=250, **kwargs ) asset_constraints_df = pd.merge( asset_constraints_df, pd.DataFrame(assets_resolved_as_records), on='identifier', how='left' ) if fail_on_unresolved_positions and asset_constraints_df['id'].isna().any(): missing_ids = asset_constraints_df[asset_constraints_df['id'].isna()]['identifier'].values.tolist() raise MqValueError( f"The following identifiers could not be resolved on {as_of_date.strftime('%Y-%m-%d')}: " f"{', '.join(missing_ids)}" ) else: asset_constraints_df = asset_constraints_df[asset_constraints_df['id'].notna()] asset_constraints_df = asset_constraints_df.rename(columns={'id': 'assetId'})[ ['assetId', 'minimum', 'maximum', 'unit'] ] asset_constraints_df = asset_constraints_df.to_dict(orient='records') return [ cls( asset=row.get('assetId'), minimum=row.get('minimum'), maximum=row.get('maximum'), unit=OptimizationConstraintUnit(row.get('unit')), ) for row in asset_constraints_df ] class CountryConstraint: def __init__( self, country_name: str, minimum: float = 0, maximum: float = 100, unit: OptimizationConstraintUnit = OptimizationConstraintUnit.PERCENT, ): """ Constrain notional held in any particular country in the resulting optimization :param country_name: country name :param minimum: minimum :param maximum: maximum :param unit: the unit in which the min and max values are passed in with (defaults to percent) """ if unit not in [OptimizationConstraintUnit.PERCENT, OptimizationConstraintUnit.DECIMAL]: raise MqValueError('Country constraints can only be set by percent or decimal.') self.__country_name = country_name self.__minimum = minimum self.__maximum = maximum self.__unit = unit @property def country_name(self) -> str: return self.__country_name @country_name.setter def country_name(self, value: str): self.__country_name = value @property def minimum(self) -> float: return self.__minimum @minimum.setter def minimum(self, value: float): self.__minimum = value @property def maximum(self) -> float: return self.__maximum @maximum.setter def maximum(self, value: float): self.__maximum = value @property def unit(self) -> OptimizationConstraintUnit: return self.__unit @unit.setter def unit(self, value: OptimizationConstraintUnit): if value not in [OptimizationConstraintUnit.PERCENT, OptimizationConstraintUnit.DECIMAL]: raise MqValueError('Country constraints can only be set by percent or decimal.') self.__unit = value def to_dict(self): return { 'type': 'Country', 'name': self.country_name, 'min': self.minimum * 100 if self.unit == OptimizationConstraintUnit.DECIMAL else self.minimum, 'max': self.maximum * 100 if self.unit == OptimizationConstraintUnit.DECIMAL else self.maximum, } @classmethod def build_many_constraints(cls, country_constraints: Union[pd.DataFrame, List[Dict]]): """ Create many country constraints from a dataframe or a list of dictionaries :param country_constraints: dataframe or list of dictionaries containing the country constraints :return: list of CountryConstraint objects :raises MqValueError: if the input is missing required columns "country", "minimum", "maximum", or "unit" :raises MqValueError: if the input country constraints are in more than one unit **Examples** 1. The input is a list of dictionaries >>> country_constraints = CountryConstraint.build_many_constraints( >>> [{"country": "USA", "minimum": 0, "maximum": 5, "unit": "Percent"}, >>> {"country": "Canada", "minimum": 0, "maximum": 5, "unit": "Percent"}, >>> {"country": "Germany", "minimum": 0, "maximum": 5, "unit": "Percent"} >>> ]) 2. The input is a dataframe >>> country_constraints = CountryConstraint.build_many_constraints( >>> pd.DataFrame([{"country": "USA", "minimum": 0, "maximum": 5, "unit": "Percent"}, >>> {"country": "Canada", "minimum": 0, "maximum": 5, "unit": "Percent"}, >>> {"country": "Germany", "minimum": 0, "maximum": 5, "unit": "Percent"}]) >>> ) """ country_constraints = ( pd.DataFrame(country_constraints) if isinstance(country_constraints, list) else country_constraints ) missing_columns = [ col for col in ['country', 'minimum', 'maximum', 'unit'] if col not in country_constraints.columns ] if missing_columns: raise MqValueError(f"The input is missing required columns: {', '.join(missing_columns)}") country_constraints_as_records = country_constraints.to_dict(orient='records') return [ cls( country_name=row.get('country'), minimum=row.get('minimum'), maximum=row.get('maximum'), unit=OptimizationConstraintUnit(row.get('unit')), ) for row in country_constraints_as_records ] class SectorConstraint: def __init__( self, sector_name: str, minimum: float = 0, maximum: float = 100, unit: OptimizationConstraintUnit = OptimizationConstraintUnit.PERCENT, ): """ Constrain notional held in any particular GICS Sector in the resulting optimization :param sector_name: sector name :param minimum: minimum :param maximum: maximum :param unit: the unit in which the min and max values are passed in with (defaults to percent) """ if unit not in [OptimizationConstraintUnit.PERCENT, OptimizationConstraintUnit.DECIMAL]: raise MqValueError('Sector constraints can only be set by percent or decimal.') self.__sector_name = sector_name self.__minimum = minimum self.__maximum = maximum self.__unit = unit @property def sector_name(self) -> str: return self.__sector_name @sector_name.setter def sector_name(self, value: str): self.__sector_name = value @property def minimum(self) -> float: return self.__minimum @minimum.setter def minimum(self, value: float): self.__minimum = value @property def maximum(self) -> float: return self.__maximum @maximum.setter def maximum(self, value: float): self.__maximum = value @property def unit(self) -> OptimizationConstraintUnit: return self.__unit @unit.setter def unit(self, value: OptimizationConstraintUnit): if value not in [OptimizationConstraintUnit.PERCENT, OptimizationConstraintUnit.DECIMAL]: raise MqValueError('Sector constraints can only be set by percent.') self.__unit = value def to_dict(self): return { 'type': 'Sector', 'name': self.sector_name, 'min': self.minimum * 100 if self.unit == OptimizationConstraintUnit.DECIMAL else self.minimum, 'max': self.maximum * 100 if self.unit == OptimizationConstraintUnit.DECIMAL else self.maximum, } @classmethod def build_many_constraints(cls, sector_constraints: Union[pd.DataFrame, List[Dict]]): """ Create many sector constraints from a dataframe or a list of dictionaries :param sector_constraints: dataframe or list of dictionaries containing the sector constraints :return: list of SectorConstraint objects :raises MqValueError: if the input is missing required columns "sector", "minimum", "maximum", or "unit" :raises MqValueError: if the input sector constraints are in more than one unit **Examples** 1. The input is a list of dictionaries >>> sector_constraints = SectorConstraint.build_many_constraints( >>> [{"sector": "Technology", "minimum": 0, "maximum": 5, "unit": "Percent"}, >>> {"sector": "Healthcare", "minimum": 0, "maximum": 5, "unit": "Percent"}, >>> {"sector": "Finance", "minimum": 0, "maximum": 5, "unit": "Percent"} >>> ]) 2. The input is a dataframe >>> sector_constraints = SectorConstraint.build_many_constraints( >>> pd.DataFrame([{"sector": "Technology", "minimum": 0, "maximum": 5, "unit": "Percent"}, >>> {"sector": "Healthcare", "minimum": 0, "maximum": 5, "unit": "Percent"}, >>> {"sector": "Finance", "minimum": 0, "maximum": 5, "unit": "Percent"}]) >>> ) """ sector_constraints = ( pd.DataFrame(sector_constraints) if isinstance(sector_constraints, list) else sector_constraints ) missing_columns = [ col for col in ['sector', 'minimum', 'maximum', 'unit'] if col not in sector_constraints.columns ] if missing_columns: raise MqValueError(f"The input is missing required columns: {', '.join(missing_columns)}") sector_constraints_as_records = sector_constraints.to_dict(orient='records') return [ cls( sector_name=row.get('sector'), minimum=row.get('minimum'), maximum=row.get('maximum'), unit=OptimizationConstraintUnit(row.get('unit')), ) for row in sector_constraints_as_records ] class IndustryConstraint: def __init__( self, industry_name: str, minimum: float = 0, maximum: float = 100, unit: OptimizationConstraintUnit = OptimizationConstraintUnit.PERCENT, ): """ Constrain notional held in any particular GICS Industry in the resulting optimization :param industry_name: industry name :param minimum: minimum :param maximum: maximum :param unit: the unit in which the min and max values are passed in with (defaults to percent) """ if unit not in [OptimizationConstraintUnit.PERCENT, OptimizationConstraintUnit.DECIMAL]: raise MqValueError('Industry constraints can only be set by percent or decimal.') self.__industry_name = industry_name self.__minimum = minimum self.__maximum = maximum self.__unit = unit @property def industry_name(self) -> str: return self.__industry_name @industry_name.setter def industry_name(self, value: str): self.__industry_name = value @property def minimum(self) -> float: return self.__minimum @minimum.setter def minimum(self, value: float): self.__minimum = value @property def maximum(self) -> float: return self.__maximum @maximum.setter def maximum(self, value: float): self.__maximum = value @property def unit(self) -> OptimizationConstraintUnit: return self.__unit @unit.setter def unit(self, value: OptimizationConstraintUnit): if value not in [OptimizationConstraintUnit.PERCENT, OptimizationConstraintUnit.DECIMAL]: raise MqValueError('Industry constraints can only be set by percent.') self.__unit = value def to_dict(self): return { 'type': 'Industry', 'name': self.industry_name, 'min': self.minimum * 100 if self.unit == OptimizationConstraintUnit.DECIMAL else self.minimum, 'max': self.maximum * 100 if self.unit == OptimizationConstraintUnit.DECIMAL else self.maximum, } @classmethod def build_many_constraints(cls, industry_constraints: Union[pd.DataFrame, List[Dict]]): """ Create many industry constraints from a dataframe or a list of dictionaries :param industry_constraints: dataframe or list of dictionaries containing the industry constraints :return: list of IndustryConstraint objects :raises MqValueError: if the input is missing required columns "industry", "minimum", "maximum", or "unit" :raises MqValueError: if the input industry constraints are in more than one unit **Examples** 1. The input is a list of dictionaries >>> industry_constraints = IndustryConstraint.build_many_constraints( >>> [{"industry": "Software", "minimum": 0, "maximum": 5, "unit": "Percent"}, >>> {"industry": "Pharmaceuticals", "minimum": 0, "maximum": 5, "unit": "Percent"}, >>> {"industry": "Banking", "minimum": 0, "maximum": 5, "unit": "Percent"} >>> ]) 2. The input is a dataframe >>> industry_constraints = IndustryConstraint.build_many_constraints( >>> pd.DataFrame([{"industry": "Software", "minimum": 0, "maximum": 5, "unit": "Percent"}, >>> {"industry": "Pharmaceuticals", "minimum": 0, "maximum": 5, "unit": "Percent"}, >>> {"industry": "Banking", "minimum": 0, "maximum": 5, "unit": "Percent"}]) >>> ) """ industry_constraints = ( pd.DataFrame(industry_constraints) if isinstance(industry_constraints, list) else industry_constraints ) missing_columns = [ col for col in ['industry', 'minimum', 'maximum', 'unit'] if col not in industry_constraints.columns ] if missing_columns: raise MqValueError(f"The input is missing required columns: {', '.join(missing_columns)}") industry_constraints_as_records = industry_constraints.to_dict(orient='records') return [ cls( industry_name=row.get('industry'), minimum=row.get('minimum'), maximum=row.get('maximum'), unit=OptimizationConstraintUnit(row.get('unit')), ) for row in industry_constraints_as_records ] class FactorConstraint: def __init__(self, factor: Factor, max_exposure: float): """ Constrain a factor by a max exposure :param factor: the factor to constrain :param max_exposure: the maximum exposure to the factor in the final portfolio """ self.__factor = factor self.__max_exposure = max_exposure @property def factor(self) -> Factor: return self.__factor @factor.setter def factor(self, value: Factor): self.__factor = value @property def max_exposure(self) -> float: return self.__max_exposure @max_exposure.setter def max_exposure(self, value: float): self.__max_exposure = value def to_dict(self): return {'factor': self.factor.name, 'exposure': self.max_exposure} @classmethod def build_many_constraints(cls, factor_constraints: Union[pd.DataFrame, List[Dict]], risk_model_id: str): """ Create many factor constraints from a dataframe or a list of dictionaries :param factor_constraints: dataframe or list of dictionaries containing the factor constraints :param risk_model_id: the id of the risk model :return: list of FactorConstraint objects :raises MqValueError: if the input is missing required columns "factor" or "exposure" **Examples** 1. The input is a list of dictionaries >>> factor_constraints = FactorConstraint.build_many_constraints( >>> [{"factor": "Value", "exposure": 5000}, >>> {"factor": "Growth", "exposure": 1000}, >>> {"factor": "Beta", "exposure": 10000} >>> ], "BARRA_USFAST") 2. The input is a dataframe >>> factor_constraints = FactorConstraint.build_many_constraints( >>> pd.DataFrame([{"factor": "Value", "exposure": 5000}, >>> {"factor": "Growth", "exposure": 1000}, >>> {"factor": "Beta", "exposure": 10000} >>> ]), "BARRA_USFAST") """ factor_constraints_df = ( pd.DataFrame(factor_constraints) if isinstance(factor_constraints, list) else factor_constraints ) missing_columns = [col for col in ['factor', 'exposure'] if col not in factor_constraints_df.columns] if missing_columns: raise MqValueError(f"The input is missing required columns: {', '.join(missing_columns)}") risk_model = FactorRiskModel.get(risk_model_id) factors = risk_model.get_many_factors(factor_names=factor_constraints_df['factor'].values.tolist()) name_to_factor_obj = [{"factor": f.name, "factorObj": f} for f in factors] name_to_factor_obj_df = pd.DataFrame(name_to_factor_obj) factor_constraints_df = factor_constraints_df.merge(name_to_factor_obj_df, on='factor', how='inner') factor_constraints_df = factor_constraints_df[["factorObj", "exposure"]].rename(columns={"factorObj": "factor"}) all_constraints = factor_constraints_df.to_dict(orient='records') return [cls(factor=row.get('factor'), max_exposure=row.get('exposure')) for row in all_constraints] class OptimizerUniverse: def __init__( self, assets: Union[List[Asset], AssetUniverse] = None, explode_composites: bool = True, exclude_initial_position_set_assets: bool = True, exclude_corporate_actions_types: List[CorporateActionsTypes] = [], exclude_hard_to_borrow_assets: bool = False, exclude_restricted_assets: bool = False, min_market_cap: float = None, max_market_cap: float = None, ): """ The universe of assets with which to construct an optimization :param assets: list of assets to include in the universe :param explode_composites: explode composites in the universe to include their constituents in the universe :param exclude_initial_position_set_assets: exclude assets in the initial holdings :param exclude_corporate_actions_types: exclude assets included under the list of corporate action types :param exclude_hard_to_borrow_assets: exclude assets with a borrow cost greater than or equal to 200 bps :param exclude_restricted_assets: exclude restricted assets :param min_market_cap: exclude assets below the requested minimum market cap :param max_market_cap: exclude assets above the requested maximum market cap specify the identifier type """ self.__assets = assets self.__explode_composites = explode_composites self.__exclude_initial_position_set_assets = exclude_initial_position_set_assets self.__exclude_corporate_actions_types = exclude_corporate_actions_types self.__exclude_hard_to_borrow_assets = exclude_hard_to_borrow_assets self.__exclude_restricted_assets = exclude_restricted_assets self.__min_market_cap = min_market_cap self.__max_market_cap = max_market_cap @property def assets(self) -> List[Asset]: return self.__assets @assets.setter def assets(self, assets: List[Asset]): self.__assets = assets @property def explode_composites(self) -> bool: return self.__explode_composites @explode_composites.setter def explode_composites(self, value: bool): self.__explode_composites = value @property def exclude_initial_position_set_assets(self) -> bool: return self.__exclude_initial_position_set_assets @exclude_initial_position_set_assets.setter def exclude_initial_position_set_assets(self, value: bool): self.__exclude_initial_position_set_assets = value @property def exclude_corporate_actions_types(self) -> List[CorporateActionsTypes]: return self.__exclude_corporate_actions_types @exclude_corporate_actions_types.setter def exclude_corporate_actions_types(self, value: List[CorporateActionsTypes]): self.__exclude_corporate_actions_types = value @property def exclude_hard_to_borrow_assets(self) -> bool: return self.__exclude_hard_to_borrow_assets @exclude_hard_to_borrow_assets.setter def exclude_hard_to_borrow_assets(self, value: bool): self.__exclude_hard_to_borrow_assets = value @property def exclude_restricted_assets(self) -> bool: return self.__exclude_restricted_assets @exclude_restricted_assets.setter def exclude_restricted_assets(self, value: bool): self.__exclude_restricted_assets = value @property def min_market_cap(self) -> float: return self.__min_market_cap @min_market_cap.setter def min_market_cap(self, value: float): self.__min_market_cap = value @property def max_market_cap(self) -> float: return self.__max_market_cap @max_market_cap.setter def max_market_cap(self, value: float): self.__max_market_cap = value def to_dict(self): if isinstance(self.assets, AssetUniverse): self.assets.resolve() asset_ids = self.assets.asset_ids else: asset_ids = [asset.get_marquee_id() for asset in self.assets] as_dict = { 'hedgeUniverse': {'assetIds': asset_ids, 'assetTypes': []}, 'excludeCorporateActions': len(self.exclude_corporate_actions_types) != 0, 'excludeCorporateActionsTypes': [x.value for x in self.exclude_corporate_actions_types], 'excludeHardToBorrowAssets': self.exclude_hard_to_borrow_assets, 'excludeRestrictedAssets': self.exclude_restricted_assets, 'excludeTargetAssets': self.exclude_initial_position_set_assets, 'explodeUniverse': self.explode_composites, } if self.min_market_cap: as_dict['minMarketCap'] = self.min_market_cap if self.max_market_cap: as_dict['maxMarketCap'] = self.max_market_cap return as_dict class MaxFactorProportionOfRiskConstraint: def __init__( self, max_factor_proportion_of_risk: float, unit: OptimizationConstraintUnit = OptimizationConstraintUnit.PERCENT, ): if unit not in [OptimizationConstraintUnit.PERCENT, OptimizationConstraintUnit.DECIMAL]: raise MqValueError('Max Factor Proportion of Risk can only be set by percent or decimal.') if unit == OptimizationConstraintUnit.PERCENT: max_factor_proportion_of_risk = max_factor_proportion_of_risk / 100 self.__max_factor_proportion_of_risk = max_factor_proportion_of_risk self.__unit = unit @property def max_factor_proportion_of_risk(self) -> float: return self.__max_factor_proportion_of_risk @max_factor_proportion_of_risk.setter def max_factor_proportion_of_risk(self, value: float): self.__max_factor_proportion_of_risk = value class MaxProportionOfRiskByGroupConstraint: def __init__( self, factors: List[Factor], max_factor_proportion_of_risk: float, unit: OptimizationConstraintUnit = OptimizationConstraintUnit.PERCENT, ): """ Constrain the maximum proportion of risk coming from a group of factors in the final optimized result. :param factors: the list of factors :param max_factor_proportion_of_risk: the maximum proportion of risk :param unit: unit of proportion of risk """ if unit not in [OptimizationConstraintUnit.PERCENT, OptimizationConstraintUnit.DECIMAL]: raise MqValueError('Max Factor Proportion of Risk can only be set by percent or decimal.') if unit == OptimizationConstraintUnit.PERCENT: max_factor_proportion_of_risk = max_factor_proportion_of_risk / 100 self.__factors = factors self.__max_factor_proportion_of_risk = max_factor_proportion_of_risk self.__unit = unit @property def factors(self) -> List[Factor]: return self.__factors @factors.setter def factors(self, value: List[Factor]): self.__factors = value @property def max_factor_proportion_of_risk(self) -> float: return self.__max_factor_proportion_of_risk @max_factor_proportion_of_risk.setter def max_factor_proportion_of_risk(self, value: float): self.__max_factor_proportion_of_risk = value def to_dict(self): return {'factors': [f.name for f in self.factors], 'max': self.max_factor_proportion_of_risk} class OptimizerConstraints: def __init__( self, asset_constraints: List[AssetConstraint] = [], country_constraints: List[CountryConstraint] = [], sector_constraints: List[SectorConstraint] = [], industry_constraints: List[IndustryConstraint] = [], factor_constraints: List[FactorConstraint] = [], max_factor_proportion_of_risk: MaxFactorProportionOfRiskConstraint = None, max_proportion_of_risk_by_groups: List[MaxProportionOfRiskByGroupConstraint] = None, ): """Set of Constraints for the optimizer :param asset_constraints: list of asset constraints :param country_constraints: list of country constraints :param sector_constraints: list of sector constraints :param industry_constraints: list of industry constraints :param factor_constraints: list of factor constraints :param max_factor_proportion_of_risk: maximum proportion of risk :param max_proportion_of_risk_by_groups: maximum proportion of risk by groups """ self.__asset_constraints = asset_constraints self.__country_constraints = country_constraints self.__sector_constraints = sector_constraints self.__industry_constraints = industry_constraints self.__factor_constraints = factor_constraints self.__max_factor_proportion_of_risk = max_factor_proportion_of_risk self.__max_proportion_of_risk_by_groups = max_proportion_of_risk_by_groups @property def asset_constraints(self) -> List[AssetConstraint]: return self.__asset_constraints @asset_constraints.setter def asset_constraints(self, value: List[AssetConstraint]): self.__asset_constraints = value @property def country_constraints(self) -> List[CountryConstraint]: return self.__country_constraints @country_constraints.setter def country_constraints(self, value: List[CountryConstraint]): self.__country_constraints = value @property def sector_constraints(self) -> List[SectorConstraint]: return self.__sector_constraints @sector_constraints.setter def sector_constraints(self, value: List[SectorConstraint]): self.__sector_constraints = value @property def industry_constraints(self) -> List[IndustryConstraint]: return self.__industry_constraints @industry_constraints.setter def industry_constraints(self, value: List[IndustryConstraint]): self.__industry_constraints = value @property def factor_constraints(self) -> List[FactorConstraint]: return self.__factor_constraints @factor_constraints.setter def factor_constraints(self, value: List[FactorConstraint]): self.__factor_constraints = value @property def max_factor_proportion_of_risk(self) -> MaxFactorProportionOfRiskConstraint: return self.__max_factor_proportion_of_risk @max_factor_proportion_of_risk.setter def max_factor_proportion_of_risk(self, value: MaxFactorProportionOfRiskConstraint): self.__max_factor_proportion_of_risk = value @property def max_proportion_of_risk_by_groups(self) -> List[MaxProportionOfRiskByGroupConstraint]: return self.__max_proportion_of_risk_by_groups @max_proportion_of_risk_by_groups.setter def max_proportion_of_risk_by_groups(self, value: List[MaxProportionOfRiskByGroupConstraint]): self.__max_proportion_of_risk_by_groups = value def to_dict(self): types = set([c.unit for c in self.asset_constraints]) if len(types) > 1: raise MqValueError('All asset constraints need to have the same unit') constrain_by_notional = len(self.asset_constraints) > 0 and types.pop() == OptimizationConstraintUnit.NOTIONAL classification_constraints = self.country_constraints + self.sector_constraints + self.industry_constraints as_dict = { 'assetConstraints': [c.to_dict() for c in self.asset_constraints], 'classificationConstraints': [c.to_dict() for c in classification_constraints], 'factorConstraints': [c.to_dict() for c in self.factor_constraints], 'constrainAssetsByNotional': constrain_by_notional, } if self.max_factor_proportion_of_risk: as_dict['maxFactorMCTR'] = self.max_factor_proportion_of_risk.max_factor_proportion_of_risk if self.max_proportion_of_risk_by_groups: as_dict['maxFactorMCTRByGroup'] = [g.to_dict() for g in self.max_proportion_of_risk_by_groups] return as_dict class ConstraintPriorities: def __init__( self, min_sector_weights: PrioritySetting = None, max_sector_weights: PrioritySetting = None, min_industry_weights: PrioritySetting = None, max_industry_weights: PrioritySetting = None, min_region_weights: PrioritySetting = None, max_region_weights: PrioritySetting = None, min_country_weights: PrioritySetting = None, max_country_weights: PrioritySetting = None, style_factor_exposures: PrioritySetting = None, ): """ Priority of the constraint from 0-5 (prioritized in that order). The optimization will fail if it cannot meet a constraint with 0 priority. A constraint with priority of 1-5 can be called a relaxed constraint, which means that the optimization will make its best effort to meet the constraint but will not fail if it cannot. A constraint with a lower priority will take precedence over a constraint with a higher priority. :param min_sector_weights: constraint priority of the minimum sector weight constraints :param max_sector_weights: constraint priority of the maximum sector weight constraints :param min_industry_weights: constraint priority of the minimum industry weight constraints :param max_industry_weights: constraint priority of the maximum industry weight constraints :param min_region_weights: constraint priority of the minimum region weight constraints :param max_region_weights: constraint priority of the maximum region weight constraints :param min_country_weights: constraint priority of the minimum country weight constraints :param max_country_weights: constraint priority of the maximum country weight constraints :param style_factor_exposures: constraint priority of the style factor exposure constraints """ self.__min_sector_weights = min_sector_weights self.__max_sector_weights = max_sector_weights self.__min_industry_weights = min_industry_weights self.__max_industry_weights = max_industry_weights self.__min_region_weights = min_region_weights self.__max_region_weights = max_region_weights self.__min_country_weights = min_country_weights self.__max_country_weights = max_country_weights self.__style_factor_exposures = style_factor_exposures @property def min_sector_weights(self) -> PrioritySetting: return self.__min_sector_weights @min_sector_weights.setter def min_sector_weights(self, value: PrioritySetting): self.__min_sector_weights = value @property def max_sector_weights(self) -> PrioritySetting: return self.__max_sector_weights @max_sector_weights.setter def max_sector_weights(self, value: PrioritySetting): self.__max_sector_weights = value @property def min_industry_weights(self) -> PrioritySetting: return self.__min_industry_weights @min_industry_weights.setter def min_industry_weights(self, value: PrioritySetting): self.__min_industry_weights = value @property def max_industry_weights(self) -> PrioritySetting: return self.__max_industry_weights @max_industry_weights.setter def max_industry_weights(self, value: PrioritySetting): self.__max_industry_weights = value @property def min_region_weights(self) -> PrioritySetting: return self.__min_region_weights @min_region_weights.setter def min_region_weights(self, value: PrioritySetting): self.__min_region_weights = value @property def max_region_weights(self) -> PrioritySetting: return self.__max_region_weights @max_region_weights.setter def max_region_weights(self, value: PrioritySetting): self.__max_region_weights = value @property def min_country_weights(self) -> PrioritySetting: return self.__min_country_weights @min_country_weights.setter def min_country_weights(self, value: PrioritySetting): self.__min_country_weights = value @property def max_country_weights(self) -> PrioritySetting: return self.__max_country_weights @max_country_weights.setter def max_country_weights(self, value: PrioritySetting): self.__max_country_weights = value @property def style_factor_exposures(self) -> PrioritySetting: return self.__style_factor_exposures @style_factor_exposures.setter def style_factor_exposures(self, value: PrioritySetting): self.__style_factor_exposures = value def to_dict(self) -> Dict: as_dict = ( { 'minSectorWeights': self.min_sector_weights, 'maxSectorWeights': self.max_sector_weights, 'minIndustryWeights': self.min_industry_weights, 'maxIndustryWeights': self.max_industry_weights, 'minRegionWeights': self.min_region_weights, 'maxRegionWeights': self.max_region_weights, 'minCountryWeights': self.min_country_weights, 'maxCountryWeights': self.max_country_weights, 'styleExposures': self.style_factor_exposures, } if self is not None else {} ) as_dict = {k: as_dict[k].value for k in as_dict.keys() if as_dict[k] is not None} return as_dict if len(as_dict.keys()) > 0 else None class OptimizerSettings: def __init__( self, notional: float = 10000000, allow_long_short: bool = False, gross_notional: float = None, net_notional: float = None, min_names: float = 0, max_names: float = 100, min_weight_per_constituent: float = None, max_weight_per_constituent: float = None, max_adv: float = 15, constraint_priorities: ConstraintPriorities = None, ): """ Optimizer settings for factor hedging. **Unidirectional Hedger** (default): - Returns only short positions to hedge the target portfolio - Use when you want to add short-only positions to reduce factor risk - Set `allow_long_short=False` (default) - Only `notional` parameter is used (gross = net for short-only positions) **Bidirectional Hedger**: - Returns both long and short positions to hedge the target portfolio - Provides more flexibility in hedging factor exposures - Set `allow_long_short=True` - Specify `gross_notional` to control total hedge size - Must specify `net_notional` for target net exposure (use 0 for market neutral) :param notional: For unidirectional hedger: the notional of the hedge (all short positions). For bidirectional hedger: will be overridden by gross_notional if allow_long_short=True. :param allow_long_short: Enable bidirectional hedge mode. When True, the hedge can contain both long and short positions. When False (default), hedge contains only short positions. :param gross_notional: Total absolute notional (|long| + |short|) for the hedge. Only applicable when allow_long_short=True. If not specified when allow_long_short=True, defaults to the value of `notional`. Example: gross_notional=20M with net_notional=10M means 15M long + 5M short :param net_notional: Net notional (long - short) for the hedge. Required when allow_long_short=True. Use 0 for market neutral hedge. :param min_names: Minimum number of assets in the hedge. :param max_names: Maximum number of assets in the hedge. :param min_weight_per_constituent: Minimum absolute weight of each constituent in the hedge (as decimal, positive value). The optimizer uses this as an absolute value constraint. Example: 0.01 means each position must be at least 1% (in absolute value) :param max_weight_per_constituent: Maximum absolute weight of each constituent in the hedge (as decimal, positive value). The optimizer uses this as an absolute value constraint. Example: 0.05 means each position cannot exceed 5% (in absolute value) :param max_adv: Maximum percentage of average daily volume that can be traded for any constituent. :param constraint_priorities: Priority settings for classification constraints (sectors, countries, etc.) """ self.__notional = notional self.__allow_long_short = allow_long_short self.__gross_notional = gross_notional self.__net_notional = net_notional self.__min_names = min_names self.__max_names = max_names self.__min_weight_per_constituent = min_weight_per_constituent self.__max_weight_per_constituent = max_weight_per_constituent self.__max_adv = max_adv self.__constraint_priorities = constraint_priorities # Validate settings self._validate_settings() def _validate_settings(self): """Validate optimizer settings for consistency and proper usage""" # Validate weight constraints are positive if self.__min_weight_per_constituent is not None and self.__min_weight_per_constituent < 0: self.__min_weight_per_constituent = 0 raise Warning("min_weight_per_constituent cannot be negative. Setting to 0.") if self.__max_weight_per_constituent is not None and self.__max_weight_per_constituent < 0: raise MqValueError( "max_weight_per_constituent must be a positive value (absolute weight constraint). " f"Current value: {self.__max_weight_per_constituent}. " "The optimizer interprets weight constraints as absolute values." ) if ( self.__min_weight_per_constituent is not None and self.__max_weight_per_constituent is not None and self.__min_weight_per_constituent > self.__max_weight_per_constituent ): raise MqValueError( f"min_weight_per_constituent ({self.__min_weight_per_constituent}) cannot be greater than " f"max_weight_per_constituent ({self.__max_weight_per_constituent})" ) # Validation for bidirectional mode if self.__allow_long_short: # When both gross_notional and net_notional are set, validate the relationship if self.__gross_notional is not None and self.__net_notional is not None: if abs(self.__net_notional) > self.__gross_notional: raise MqValueError( "Invalid notional configuration: |net_notional| ({abs(self.__net_notional)}) " f"cannot be greater than gross_notional ({self.__gross_notional}). " "Formula: gross_notional = |long| + |short|, net_notional = long - short" ) # Allow bidirectional mode with only notional set (hedge notional use case) # No additional validation needed for this case else: # Unidirectional hedger mode validation # Prevent setting long/short parameters in unidirectional mode if ( self.__gross_notional is not None and self.__net_notional is not None and self.__gross_notional != self.__net_notional ): raise MqValueError( "Cannot set gross_notional != net_notional when allow_long_short=False. " "Use 'notional' parameter for unidirectional hedger mode, or set allow_long_short=True " "to enable bidirectional hedging with gross_notional and net_notional." ) @property def notional(self) -> float: return self.__notional @notional.setter def notional(self, value: float): self.__notional = value self._validate_settings() @property def allow_long_short(self) -> bool: return self.__allow_long_short @allow_long_short.setter def allow_long_short(self, value: bool): self.__allow_long_short = value self._validate_settings() @property def gross_notional(self) -> float: """ Total absolute notional for the hedge (|long| + |short|). Only applicable when allow_long_short=True. """ return self.__gross_notional @gross_notional.setter def gross_notional(self, value: float): self.__gross_notional = value self._validate_settings() @property def net_notional(self) -> float: """ Net notional for the hedge (long - short). Only applicable when allow_long_short=True. Set to 0 for market neutral hedge. """ return self.__net_notional @net_notional.setter def net_notional(self, value: float): self.__net_notional = value self._validate_settings() @property def min_names(self) -> float: return self.__min_names @min_names.setter def min_names(self, value: float): self.__min_names = value @property def min_weight_per_constituent(self) -> float: return self.__min_weight_per_constituent @min_weight_per_constituent.setter def min_weight_per_constituent(self, value: float): self.__min_weight_per_constituent = value self._validate_settings() @property def max_weight_per_constituent(self) -> float: return self.__max_weight_per_constituent @max_weight_per_constituent.setter def max_weight_per_constituent(self, value: float): self.__max_weight_per_constituent = value self._validate_settings() @property def max_names(self) -> float: return self.__max_names @max_names.setter def max_names(self, value: float): self.__max_names = value @property def max_adv(self) -> float: return self.__max_adv @max_adv.setter def max_adv(self, value: float): self.__max_adv = value @property def constraint_priorities(self) -> ConstraintPriorities: return self.__constraint_priorities @constraint_priorities.setter def constraint_priorities(self, value: ConstraintPriorities): self.__constraint_priorities = value def to_dict(self): """ Convert optimizer settings to dictionary format for API payload. Handles both unidirectional and bidirectional hedger configurations. """ # Start with common parameters as_dict = {'minNames': self.min_names, 'maxNames': self.max_names, 'maxAdvPercentage': self.max_adv} # Configure notional parameters based on hedger mode if self.__allow_long_short: # Bidirectional Hedger mode as_dict['allowLongShort'] = self.__allow_long_short # Use grossNotional and netNotional if both are set if self.__gross_notional is not None and self.__net_notional is not None: as_dict['grossNotional'] = self.__gross_notional as_dict['netNotional'] = self.__net_notional # Otherwise, use hedgeNotional if only notional is set elif self.__notional is not None: as_dict['hedgeNotional'] = self.__notional else: # Unidirectional Hedger mode (default) # In unidirectional mode, use hedgeNotional (all short positions) as_dict['hedgeNotional'] = self.__notional # Explicitly set allowLongShort to False for clarity as_dict['allowLongShort'] = False # Add weight constraints if specified if self.min_weight_per_constituent is not None: as_dict['minWeight'] = self.min_weight_per_constituent * 100 if self.max_weight_per_constituent is not None: as_dict['maxWeight'] = self.max_weight_per_constituent * 100 # Add constraint priorities if specified if self.constraint_priorities: as_dict['constraintPrioritySettings'] = self.constraint_priorities.to_dict() return as_dict class TurnoverConstraint: def __init__( self, turnover_portfolio: PositionSet, max_turnover_percent: float, turnover_notional_type: Optional[TurnoverNotionalType] = None, ): """ Specifying a list of positions and max turnover from those positions in the optimization result :param turnover_portfolio: turnover portfolio :param max_turnover_percent: max turnover as a percent (ex: 80 = a minimal overlap of 20% in notional of the specified positions and the optimization """ self.__turnover_portfolio = turnover_portfolio self.__max_turnover_percent = max_turnover_percent self.__turnover_notional_type = turnover_notional_type @property def turnover_portfolio(self) -> PositionSet: return self.__turnover_portfolio @turnover_portfolio.setter def turnover_portfolio(self, value: PositionSet): self.__turnover_portfolio = value @property def max_turnover_percent(self) -> float: return self.__max_turnover_percent @max_turnover_percent.setter def max_turnover_percent(self, value: float): self.__max_turnover_percent = value @property def turnover_notional_type(self): return self.__turnover_notional_type @turnover_notional_type.setter def turnover_notional_type(self, value: Optional[TurnoverNotionalType]): self.__turnover_notional_type = value def to_dict(self): positions = self.turnover_portfolio.positions payload = { 'turnoverPortfolio': [{'assetId': p.asset_id, 'quantity': p.quantity} for p in positions], 'maxTurnoverPercentage': self.max_turnover_percent, } if self.turnover_notional_type: payload['turnoverNotionalType'] = self.turnover_notional_type.value return payload def _ensure_completed(func): @wraps(func) def wrapper(*args, **kwargs): self = args[0] if self._OptimizerStrategy__result is None: raise MqValueError('Please run the optimization before calling this method') return func(*args, **kwargs) return wrapper class OptimizerStrategy: VERBOSE_ERROR_MSG: Final = { 'Missing asset xref': lambda e: f"We noticed some underlying asset meta data error: {e}", 'ERROR: Could not find solution.': lambda e: ( f"Potential infeasible inputs. {e}. Please relax your constraint or contact Marquee team for assistance." ), } def __init__( self, initial_position_set: PositionSet, universe: OptimizerUniverse, risk_model: FactorRiskModel, constraints: OptimizerConstraints = None, turnover: TurnoverConstraint = None, settings: OptimizerSettings = None, objective: OptimizerObjective = OptimizerObjective.MINIMIZE_FACTOR_RISK, objective_parameters: OptimizerObjectiveParameters = None, ): """ A strategy that can be passed into the optimizer and run :param initial_position_set: a position set correlating to your original holdings as of a specific date :param universe: universe from which to choose optimization assets :param risk_model: risk model with which to calculate risk :param constraints: constraints for the optimization :param turnover: turnover constraints for the optimization :param settings: settings for the optimization :param objective: objective for the optimization: """ self.__initial_position_set = initial_position_set self.__universe = universe self.__risk_model = risk_model self.__constraints = constraints self.__turnover = turnover self.__settings = settings self.__objective = objective self.__result = None self.__objective_parameters = objective_parameters @property def initial_position_set(self) -> PositionSet: return self.__initial_position_set @initial_position_set.setter def initial_position_set(self, value: PositionSet): self.__initial_position_set = value @property def universe(self) -> OptimizerUniverse: return self.__universe @universe.setter def universe(self, value: OptimizerUniverse): self.__universe = value @property def risk_model(self) -> FactorRiskModel: return self.__risk_model @risk_model.setter def risk_model(self, value: FactorRiskModel): self.__risk_model = value @property def constraints(self) -> OptimizerConstraints: return self.__constraints @constraints.setter def constraints(self, value: OptimizerConstraints): self.__constraints = value @property def turnover(self) -> TurnoverConstraint: return self.__turnover @turnover.setter def turnover(self, value: TurnoverConstraint): self.__turnover = value @property def settings(self) -> OptimizerSettings: return self.__settings @settings.setter def settings(self, value: OptimizerSettings): self.__settings = value @property def objective(self) -> OptimizerObjective: return self.__objective @objective.setter def objective(self, value: OptimizerObjective): self.__objective = value @property def objective_parameters(self): return self.__objective_parameters @objective_parameters.setter def objective_parameters(self): return self.__objetive_parameters def to_dict(self, fail_on_unpriced_positions: bool = True): """Converts input to suitable json payload for optimizer. Does not modify initial_position_set""" if self.constraints is None: self.constraints = OptimizerConstraints() if self.settings is None: self.settings = OptimizerSettings() backtest_start_date = self.initial_position_set.date - relativedelta(years=1) positions_frame = self.initial_position_set.to_frame() if self.initial_position_set.reference_notional: positions_as_dict = positions_frame[['asset_id', 'weight']] else: positions_as_dict = positions_frame[['asset_id', 'quantity']] positions_as_dict = positions_as_dict.rename(columns={'asset_id': 'assetId'}).to_dict(orient='records') parameters = { 'hedgeTarget': {'positions': positions_as_dict}, 'hedgeDate': self.initial_position_set.date.strftime('%Y-%m-%d'), 'backtestStartDate': backtest_start_date.strftime('%Y-%m-%d'), 'backtestEndDate': self.initial_position_set.date.strftime('%Y-%m-%d'), 'comparisons': [], 'fxHedged': False, 'marketParticipationRate': 10, } constraints = self.constraints.to_dict() for key in constraints: if constraints[key] is not None: parameters[key] = constraints[key] settings = self.settings.to_dict() for key in settings: if settings[key] is not None: parameters[key] = settings[key] universe = self.universe.to_dict() for key in universe: if universe[key] is not None: parameters[key] = universe[key] parameters['riskModel'] = self.risk_model.id if self.turnover: if self.turnover.turnover_portfolio.reference_notional is not None: self.turnover.turnover_portfolio.price() turnover_dict = self.turnover.to_dict() for key in turnover_dict: if turnover_dict[key] is not None: parameters[key] = turnover_dict[key] # Price initial_position_set if needed if self.initial_position_set.reference_notional is not None: parameters['targetNotional'] = self.initial_position_set.reference_notional if self.__objective_parameters is not None: parameters['hedgeObjectiveParameters'] = self.__objective_parameters.to_dict() payload = { 'positions': positions_as_dict, 'parameters': { 'currency': 'USD', 'pricingDate': self.initial_position_set.date.strftime('%Y-%m-%d'), 'useUnadjustedClosePrice': False, # Optimizer uses adjusted prices for all calculations 'frequency': 'End Of Day', 'priceRegardlessOfAssetsMissingPrices': not fail_on_unpriced_positions, 'fallbackDate': '5d', }, } if self.initial_position_set.reference_notional is not None: payload['parameters']['targetNotional'] = self.initial_position_set.reference_notional try: price_results = GsSession.current.sync.post('/price/positions', payload) except Exception as e: raise MqValueError(f'There was an error pricing your positions: {e}') if 'errorMessage' in price_results: if len(price_results.get('assetIdsMissingPrices', [])) > 0: _logger.warning( f'Marquee is missing prices on {self.initial_position_set.date} for ' f'the following assets: {price_results["assetIdsMissingPrices"]}. ' ) raise MqValueError(f'There was an error pricing your positions: {price_results["errorMessage"]}') if self.initial_position_set.reference_notional is None: parameters['targetNotional'] = price_results.get('actualNotional') else: parameters['hedgeTarget']['positions'] = [ {'assetId': p['assetId'], 'quantity': p['quantity']} for p in price_results.get('positions', []) ] return {'objective': self.objective.value, 'parameters': parameters} def handle_error(self, error_message: str) -> List: for key, val in self.VERBOSE_ERROR_MSG.items(): if error_message.startswith(key): return [val(error_message), True] # predefined return [error_message, False] def run( self, optimizer_type: OptimizerType = OptimizerType.AXIOMA_PORTFOLIO_OPTIMIZER, fail_on_unpriced_positions: bool = True, ): """ Run an optimization strategy, after which you can use the .get_optimization or get_optimized_position_set functions to pull results :param optimizer_type: optimizer type :param fail_on_unpriced_positions: whether to fail the calculations if some of the portfolio positions do not have pricing data in Marquee. If set to false, unpriced assets will be sifted out before the optimization is run """ if optimizer_type is None: raise MqValueError('You must pass an optimizer type.') if optimizer_type == OptimizerType.AXIOMA_PORTFOLIO_OPTIMIZER: strategy_as_dict = self.to_dict(fail_on_unpriced_positions) counter = 5 predefined_error = False while counter > 0: try: optimization_results = GsHedgeApi.calculate_hedge(strategy_as_dict) if optimization_results.get('result') is None: if 'errorMessage' in optimization_results: error_message = optimization_results['errorMessage'] verbose_message, predefined_error = self.handle_error(error_message) if predefined_error: counter = 0 raise MqValueError(f"The optimizer returns an error: {verbose_message}. ") else: raise MqValueError( f"The optimizer returned an error: " f"{optimization_results.get('errorMessage')}. " f"Please adjust the constraints" f" or contact the Marquee team for assistance" ) elif counter == 1: raise MqValueError( 'Error calculating an optimization. Please contact the Marquee team for assistance.' ) counter -= 1 else: self.__result = optimization_results['result'] counter = 0 except Exception as e: if predefined_error: raise e if counter == 1: raise MqValueError( 'Error calculating an optimization. Please contact the Marquee team for assistance.' ) counter -= 1 def run_save_share( self, optimizer_type: OptimizerType = OptimizerType.AXIOMA_PORTFOLIO_OPTIMIZER, fail_on_unpriced_positions: bool = True, ): """ Run an optimization strategy, after which you can use the .get_optimization or get_optimized_position_set functions to pull results :param optimizer_type: optimizer type :param fail_on_unpriced_positions: whether to fail the calculations if some of the portfolio positions do not have pricing data in Marquee. If set to false, unpriced assets will be sifted out before the optimization is run :return: tuple of (strategy_as_dict, optimization_results) - the request payload and response from the optimizer """ if optimizer_type is None: raise MqValueError('You must pass an optimizer type.') if optimizer_type == OptimizerType.AXIOMA_PORTFOLIO_OPTIMIZER: strategy_as_dict = self.to_dict(fail_on_unpriced_positions) counter = 5 predefined_error = False while counter > 0: try: optimization_results = GsHedgeApi.calculate_hedge(strategy_as_dict) if optimization_results.get('result') is None: if 'errorMessage' in optimization_results: error_message = optimization_results['errorMessage'] verbose_message, predefined_error = self.handle_error(error_message) if predefined_error: counter = 0 raise MqValueError(f"The optimizer returns an error: {verbose_message}. ") else: raise MqValueError( f"The optimizer returned an error: " f"{optimization_results.get('errorMessage')}. " f"Please adjust the constraints" f" or contact the Marquee team for assistance" ) elif counter == 1: raise MqValueError( 'Error calculating an optimization. Please contact the Marquee team for assistance.' ) counter -= 1 else: self.__result = optimization_results['result'] counter = 0 except Exception as e: if predefined_error: raise e if counter == 1: raise MqValueError( 'Error calculating an optimization. Please contact the Marquee team for assistance.' ) counter -= 1 # Return both the request and response for easy hedge saving return strategy_as_dict, optimization_results def __construct_position_set_from_hedge_result(self, result_key: str, by_weight: bool = True): result = self.__result[result_key] return PositionSet( date=self.initial_position_set.date, reference_notional=result['netExposure'] if by_weight else None, positions=[ Position( identifier=asset.get('bbid', asset['name']), asset_id=asset['assetId'], quantity=asset['shares'] if not by_weight else None, weight=asset['weight'], ) for asset in result['constituents'] ], ) @_ensure_completed def get_optimization(self, by_weight: bool = False): """ Get the optimization results :param by_weight: whether to return position set with weights instead of quantities """ return self.__construct_position_set_from_hedge_result('hedge', by_weight) @_ensure_completed def get_optimized_position_set(self, by_weight: bool = False): """ Get the optimized position set, which is a result of applying the optimization to the target :param by_weight: whether to return position set with weights instead of quantities """ return self.__construct_position_set_from_hedge_result('hedgedTarget', by_weight) @_ensure_completed def get_hedge_exposure_summary(self) -> Dict: """ Get a summary of the hedge exposures including gross, net, long, and short exposures. This is particularly useful for bidirectional hedges to understand the composition. :return: Dictionary containing exposure metrics for target, hedge, and hedged target **Example Output for bidirectional Hedger:** { 'hedge': { 'gross_exposure': 20000000, 'net_exposure': 10000000, 'long_exposure': 15000000, 'short_exposure': 5000000, 'number_of_positions': 25, 'mode': 'bidirectional' }, 'target': { 'gross_exposure': 10000000, 'net_exposure': 10000000, 'long_exposure': 10000000, 'short_exposure': 0, 'number_of_positions': 1 }, 'hedged_target': { 'gross_exposure': 30000000, 'net_exposure': 20000000, 'long_exposure': 25000000, 'short_exposure': 5000000, 'number_of_positions': 26 } } """ if self.__result is None: raise MqValueError('Please run the optimization before calling this method') def get_exposure_dict(result_key: str) -> Dict: """Extract exposure metrics from result""" if result_key not in self.__result: return None result = self.__result[result_key] exposure_dict = { 'gross_exposure': result.get('grossExposure'), 'net_exposure': result.get('netExposure'), 'long_exposure': result.get('longExposure', 0), 'short_exposure': result.get('shortExposure', 0), 'number_of_positions': result.get('numberOfPositions'), } # Add mode information for hedge if result_key == 'hedge': if result.get('longExposure', 0) > 0: exposure_dict['mode'] = 'bidirectional' else: exposure_dict['mode'] = 'unidirectional (short positions only)' return exposure_dict summary = { 'hedge': get_exposure_dict('hedge'), 'target': get_exposure_dict('target'), 'hedged_target': get_exposure_dict('hedgedTarget'), } return summary @_ensure_completed def get_hedge_constituents_by_direction(self) -> Dict: """ For bidirectional hedges, split the hedge constituents into long and short positions. This is useful to analyze the long and short sides of the hedge separately. :return: Dictionary with 'long_positions' and 'short_positions' DataFrames **Example:** { 'long_positions': DataFrame with positive notional positions, 'short_positions': DataFrame with negative notional positions, 'summary': { 'num_long': 12, 'num_short': 13, 'total_long_notional': 15000000, 'total_short_notional': -5000000 } } """ if self.__result is None: raise MqValueError('Please run the optimization before calling this method') if self.__result.get('hedge') is None: raise MqValueError('The optimization result does not contain hedge data') constituents = self.__result.get('hedge').get('constituents', []) if not constituents: return { 'long_positions': pd.DataFrame(), 'short_positions': pd.DataFrame(), 'summary': {'num_long': 0, 'num_short': 0, 'total_long_notional': 0, 'total_short_notional': 0}, } # Convert to DataFrame df = pd.DataFrame(constituents) # Split by notional sign long_positions = df[df['notional'] > 0].copy() if 'notional' in df.columns else pd.DataFrame() short_positions = df[df['notional'] < 0].copy() if 'notional' in df.columns else pd.DataFrame() # Calculate summary statistics summary = { 'num_long': len(long_positions), 'num_short': len(short_positions), 'total_long_notional': long_positions['notional'].sum() if len(long_positions) > 0 else 0, 'total_short_notional': short_positions['notional'].sum() if len(short_positions) > 0 else 0, } return {'long_positions': long_positions, 'short_positions': short_positions, 'summary': summary} def get_cumulative_pnl_performance(self, target: HedgeTarget = HedgeTarget.HEDGED_TARGET) -> Dict: """ Get the cumulative PnL performance results of the optimization :param target: the target to get performance for (hedgedTarget, hedge, or target) :return: a pandas dataframe with performance results with columns ['date', 'cumulativePnl'] """ if self.__result is None: raise MqValueError('Please run the optimization before calling this method') if self.__result.get(target.value) is None: raise MqValueError(f'The optimization result does not contain {target.value} data') cumulative_pnl = self.__result.get(target.value).get('cumulativePnl') df = pd.DataFrame(cumulative_pnl, columns=['date', 'cumulativePnl']) df['date'] = pd.to_datetime(df['date']) return df def get_style_factor_exposures(self, target: HedgeTarget = HedgeTarget.HEDGED_TARGET) -> Dict: """ Get the style factor exposures from the hedge result :param target: the target to get factor exposures for (hedgedTarget, hedge, or target) :return: a dictionary with style factor exposures """ if self.__result is None: raise MqValueError('Please run the optimization before calling this method') if self.__result.get(target.value) is None: raise MqValueError(f'The optimization result does not contain {target.value} data') if self.__result.get(target.value).get('factorExposures') is None: raise MqValueError(f'The optimization result does not contain factor exposures for {target.value}') factor_exposures = self.__result.get(target.value).get('factorExposures').get('style', []) return factor_exposures def get_risk_buckets(self, target: HedgeTarget = HedgeTarget.HEDGED_TARGET) -> Dict: """ Get the risk buckets from the hedge result :param target: the target to get risk buckets for (hedgedTarget, hedge, or target) :return: a dictionary with risk buckets """ if self.__result is None: raise MqValueError('Please run the optimization before calling this method') if self.__result.get(target.value) is None: raise MqValueError(f'The optimization result does not contain {target.value} data') if self.__result.get(target.value).get('riskBuckets') is None: raise MqValueError(f'The optimization result does not contain risk buckets for {target.value}') risk_buckets = self.__result.get(target.value).get('riskBuckets') return {"risk_buckets": risk_buckets} def get_transaction_and_liquidity_constituents_performance( self, target: HedgeTarget = HedgeTarget.HEDGED_TARGET ) -> Dict: """ Get the constituents performance results of the optimization :param target: the target to get performance for (hedgedTarget, hedge, or target) :return: a pandas dataframe with performance results """ if self.__result is None: raise MqValueError('Please run the optimization before calling this method') if self.__result.get(target.value) is None: raise MqValueError(f'The optimization result does not contain {target.value} data') constituents = self.__result.get(target.value).get('constituents') filtered_constituents = [] keys_to_keep = { "name", "assetId", "bbid", "notional", "shares", "price", "weight", "currency", "transactionCost", "marginalCost", "advPercentage", "borrowCost", } for constituent in constituents: filtered_constituent = {key: value for key, value in constituent.items() if key in keys_to_keep} filtered_constituents.append(filtered_constituent) return pd.DataFrame(filtered_constituents) def get_performance_summary(self) -> Dict: """ Get the performance summary results of the optimization. Returns a dictionary containing multiple DataFrames for easy display: - 'risk': Risk metrics comparison - 'performance': Performance metrics comparison - 'transaction_cost': Transaction cost metrics comparison - 'comparison': Comparison with initial portfolio - 'combined': All metrics in a single table """ if self.__result is None: raise MqValueError('Please run the optimization before calling this method') if self.__result.get(HedgeTarget.HEDGED_TARGET.value) is None: raise MqValueError('The optimization result does not contain hedgedTarget data') if self.__result.get(HedgeTarget.TARGET.value) is None: raise MqValueError('The optimization result does not contain target data') target = self.__result.get(HedgeTarget.TARGET.value) hedged_target = self.__result.get(HedgeTarget.HEDGED_TARGET.value) # Build individual DataFrames for each category # Risk Metrics Table risk_df = pd.DataFrame( { 'Metric': ['Annualized Volatility', 'Specific Risk', 'Factor Risk', 'Factor Risk Delta'], 'Initial Portfolio': [ target.get("volatility"), target.get("specificExposure"), target.get("systematicExposure"), None, ], 'Hedged Portfolio': [ hedged_target.get("volatility"), hedged_target.get("specificExposure"), hedged_target.get("systematicExposure"), ( hedged_target.get("systematicExposure") - target.get("systematicExposure") if hedged_target.get("systematicExposure") is not None and target.get("systematicExposure") is not None else None ), ], } ) # Performance Metrics Table performance_df = pd.DataFrame( { 'Metric': ['PnL', 'PnL Delta'], 'Initial Portfolio': [target.get("totalPnl"), None], 'Hedged Portfolio': [ hedged_target.get("totalPnl"), ( hedged_target.get("totalPnl") - target.get("totalPnl") if hedged_target.get("totalPnl") is not None and target.get("totalPnl") is not None else None ), ], } ) # Transaction Cost Table transaction_cost_df = pd.DataFrame( { 'Metric': ['Market Impact', 'Borrow Cost (bps)'], 'Initial Portfolio': [target.get("transactionCost"), None], 'Hedged Portfolio': [hedged_target.get("transactionCost"), hedged_target.get("borrowCostBps")], } ) # Comparison Table comparison_df = pd.DataFrame( { 'Metric': ['Overlap with Core'], 'Initial Portfolio': [None], 'Hedged Portfolio': [hedged_target.get("exposureOverlapWithTarget")], } ) # Combined Table combined_df = pd.concat( [ risk_df.assign(Category='Risk'), performance_df.assign(Category='Performance'), transaction_cost_df.assign(Category='Transaction Cost'), comparison_df.assign(Category='Comparison'), ], ignore_index=True, ) combined_df = combined_df[['Category', 'Metric', 'Initial Portfolio', 'Hedged Portfolio']] return { 'risk': risk_df, 'performance': performance_df, 'transaction_cost': transaction_cost_df, 'comparison': comparison_df, 'combined': combined_df, } def build_hedge_payload( self, strategy_request: Dict, optimization_response: Dict, hedge_name: str = "Custom Hedge", group_name: str = "New Hedge Group", ) -> Dict: """ Build the payload for saving a hedge to Marquee API. This method takes the request sent to the optimizer and the response received, and constructs the proper payload for the save hedge API. :param strategy_request: The strategy_as_dict from run() - the request payload sent to optimizer :param optimization_response: The optimization_results from run() - the response from optimizer :param hedge_name: Name for the individual hedge :param group_name: Name for the hedge group :return: Payload ready to POST to /v1/hedges/groups """ # The save hedge payload is simply: # - parameters: the request that was sent to the optimizer # - result: the response received from the optimizer payload = { "name": group_name, "objective": strategy_request.get("objective", "Minimize Factor Risk"), "hedges": [ { "name": hedge_name, "objective": strategy_request.get("objective", "Minimize Factor Risk"), "parameters": strategy_request.get("parameters", {}), "result": optimization_response.get("result", {}), } ], } return payload def save_to_marquee( self, strategy_request: Dict, optimization_response: Dict, hedge_name: str = "Custom Hedge", group_name: str = "New Hedge Group", ) -> Dict: """ Save the hedge to Marquee via API POST. :param strategy_request: The strategy_as_dict from run() - the request payload sent to optimizer :param optimization_response: The optimization_results from run() - the response from optimizer :param hedge_name: Name for the individual hedge :param group_name: Name for the hedge group :return: API response with hedge group details including ID Example: >>> strategy_request, optimization_response = strategy.run() >>> saved_hedge = strategy.save_to_marquee( ... strategy_request, ... optimization_response, ... hedge_name="Factor Hedge", ... group_name="My Hedge Group" ... ) >>> print(f"Saved hedge ID: {saved_hedge['id']}") """ # Build the payload payload = self.build_hedge_payload( strategy_request=strategy_request, optimization_response=optimization_response, hedge_name=hedge_name, group_name=group_name, ) url = "/hedges/groups" try: response = GsSession.current.sync.post(url, payload=payload) result = response hedge_group_id = result.get('id', 'N/A') print("Hedge saved successfully!") print(f" Hedge Group ID: {hedge_group_id}") print(f" Name: {result.get('name', 'N/A')}") print(f" Created Time: {result.get('createdTime', 'N/A')}") # Display link to Marquee UI if hedge_group_id != 'N/A': marquee_url = f"https://marquee.gs.com/s/hedging/multi/{hedge_group_id}" print("\n View in Marquee UI:") print(f" {marquee_url}") return result except Exception as e: print(f"✗ Failed to save hedge: {e}") if hasattr(e, 'response'): print(f" Response: {e.response.text}") raise MqValueError(f"Failed to save hedge to Marquee: {e}") ================================================ FILE: gs_quant/markets/portfolio.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicablNe law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import logging import re from dataclasses import dataclass from itertools import chain from typing import Iterable, Optional, Tuple, Union from urllib.parse import quote import deprecation import numpy as np import pandas as pd from more_itertools import unique_everseen from gs_quant.api.gs.assets import GsAssetApi from gs_quant.api.gs.portfolios import GsPortfolioApi from gs_quant.base import InstrumentBase from gs_quant.common import RiskMeasure, RiskPosition from gs_quant.instrument import Instrument, AssetType from gs_quant.markets import HistoricalPricingContext, OverlayMarket, PricingContext, PositionContext from gs_quant.priceable import PriceableImpl from gs_quant.risk import ResolvedInstrumentValues from gs_quant.risk.results import CompositeResultFuture, PortfolioRiskResult, PortfolioPath, PricingFuture from gs_quant.target.portfolios import Portfolio as MarqueePortfolio from gs_quant.target.portfolios import Position, PositionSet, RiskRequest, PricingDateAndMarketDataAsOf _logger = logging.getLogger(__name__) @dataclass class Portfolio(PriceableImpl): """A collection of instruments Portfolio holds a collection of instruments in order to run pricing and risk scenarios """ def __init__( self, priceables: Optional[Union[PriceableImpl, Iterable[PriceableImpl], dict]] = (), name: Optional[str] = None ): """ Creates a portfolio object which can be used to hold instruments :param priceables: constructed with an instrument, portfolio, iterable of either, or a dictionary where key is name and value is a priceable """ super().__init__() if isinstance(priceables, dict): priceables_list = [] for name, priceable in priceables.items(): priceable.name = name priceables_list.append(priceable) self.priceables = priceables_list else: self.priceables = priceables self.name = name self.__id = None self.__quote_id = None def __repr__(self): name = 'Portfolio' inst_desc = f'{len(self.all_instruments) if self.priceables else "0"} instrument(s)' if self.name: name += f'({self.name}, {inst_desc})' else: name += f'({inst_desc})' return name def _to_records(self): def get_name(obj, idx): if isinstance(obj, InstrumentBase) and hasattr(obj, 'type_'): type_name = obj.type_.name if isinstance(obj.type_, AssetType) else obj.type_ else: type_name = 'Portfolio' return f'{type_name}_{idx}' if obj.name is None else obj.name stack = [(None, self)] records = [] while stack: temp_records = [] parent, portfolio = stack.pop() current_record = {} if len(records) == 0 else records.pop(0) for idx, priceable in enumerate(portfolio.__priceables): path = parent + PortfolioPath(idx) if parent is not None else PortfolioPath(idx) priceable_name = get_name(priceable, idx) if isinstance(priceable, Portfolio): stack.insert(0, (path, priceable)) temp_records.append({**current_record, f'portfolio_name_{len(path) - 1}': priceable_name}) else: temp_records.append({**current_record, 'instrument_name': priceable_name}) records.extend(temp_records) return records def __getitem__(self, item): if isinstance(item, (int, slice)): return self.__priceables[item] elif isinstance(item, PortfolioPath): return item(self, rename_to_parent=True) else: values = ( tuple(self[p] for it in item for p in self.paths(it)) if isinstance(item, list) else tuple(self[p] for p in self.paths(item)) ) return values[0] if len(values) == 1 else values def __contains__(self, item): if isinstance(item, PriceableImpl): return any(item in p.__priceables for p in self.all_portfolios + (self,)) elif isinstance(item, str): return any(item in p.__priceables_by_name for p in self.all_portfolios + (self,)) else: return False def __len__(self): return len(self.__priceables) def __iter__(self): return iter(self.__priceables) def __hash__(self): hash_code = hash(self.name) ^ hash(self.__id) for priceable in self.__priceables: hash_code ^= hash(priceable) return hash_code def __eq__(self, other): if not isinstance(other, Portfolio): return False for path in self.all_paths: try: if path(self) != path(other): return False except (IndexError, TypeError): # indexerror occurs when two portfolios are of different lengths # typeerror: instrument is not subscriptable occurs when two portfolios are of different depths return False return True def __add__(self, other): if not isinstance(other, Portfolio): raise ValueError('Can only add instances of Portfolio') return Portfolio(self.__priceables + other.__priceables) @property def __position_context(self) -> PositionContext: return PositionContext.current if PositionContext.current.is_entered else PositionContext.default_value() @property def id(self) -> str: return self.__id @property def quote_id(self) -> str: return self.__quote_id @property def priceables(self) -> Tuple[PriceableImpl, ...]: return self.__priceables @priceables.setter def priceables(self, priceables: Union[PriceableImpl, Iterable[PriceableImpl]]): self.__priceables = (priceables,) if isinstance(priceables, PriceableImpl) else tuple(priceables) self.__priceables_by_name = {} for idx, i in enumerate(self.__priceables): if i and i.name: self.__priceables_by_name.setdefault(i.name, []).append(idx) @priceables.deleter def priceables(self): self.__priceables = None self.__priceables_by_name = None @property def instruments(self) -> Tuple[Instrument, ...]: return tuple(unique_everseen(i for i in self.__priceables if isinstance(i, Instrument))) @property def all_instruments(self) -> Tuple[Instrument, ...]: instr = chain(self.instruments, chain.from_iterable(p.all_instruments for p in self.all_portfolios)) return tuple(unique_everseen(instr)) @property def portfolios(self) -> Tuple[PriceableImpl, ...]: return tuple(i for i in self.__priceables if isinstance(i, Portfolio)) @property def all_portfolios(self) -> Tuple[PriceableImpl, ...]: stack = list(self.portfolios) portfolios = list(unique_everseen(stack)) while stack: portfolio = stack.pop() if portfolio in portfolios: continue sub_portfolios = portfolio.portfolios portfolios.extend(sub_portfolios) stack.extend(sub_portfolios) return tuple(unique_everseen(portfolios)) def subset(self, paths: Iterable[PortfolioPath], name=None): # Do our paths represent a single portfolio? paths_tuple = tuple(paths) if len(paths_tuple) == 1 and isinstance(self[paths_tuple[0]], Portfolio): return self[paths_tuple[0]] else: return Portfolio(tuple(self[p] for p in paths_tuple), name=name) @staticmethod def __from_internal_positions(id_type: str, positions_id, activity_type: str): instruments = GsPortfolioApi.get_instruments_by_position_type(id_type, positions_id, activity_type) return Portfolio(instruments, name=positions_id) @staticmethod def from_eti(eti: str): return Portfolio.__from_internal_positions('ETI', quote(eti, safe=''), 'trade') @staticmethod def from_book(book: str, book_type: str = 'risk', activity_type: str = 'position'): return Portfolio.__from_internal_positions(book_type, book, activity_type) @staticmethod def from_asset_id(asset_id: str, date=None): asset = GsAssetApi.get_asset(asset_id) response = ( GsAssetApi.get_asset_positions_for_date(asset_id, date) if date else GsAssetApi.get_latest_positions(asset_id) ) response = response[0] if isinstance(response, tuple) else response positions = response.positions if isinstance(response, PositionSet) else response['positions'] instruments = GsAssetApi.get_instruments_for_positions(positions) ret = Portfolio(instruments, name=asset.name) ret.__id = asset_id return ret @staticmethod def from_asset_name(name: str): asset = GsAssetApi.get_asset_by_name(name) return Portfolio.load_from_portfolio_id(asset.id) @classmethod def get(cls, portfolio_id: str = None, portfolio_name: str = None, query_instruments: Optional[bool] = False): if portfolio_name: portfolio = GsPortfolioApi.get_portfolio_by_name(portfolio_name) portfolio_id = portfolio.id position_date = PositionContext.current.position_date if PositionContext.is_entered else dt.date.today() portfolio = GsPortfolioApi.get_portfolio(portfolio_id) ret = Portfolio(name=portfolio.name) ret.__id = portfolio_id if query_instruments: ret._get_instruments(position_date, True) return ret @classmethod @deprecation.deprecated( deprecated_in='0.8.293', details='from_portfolio_id is now deprecated, please use Portfolio.get(portfolio_id=portfolio_id) instead.', ) def from_portfolio_id(cls, portfolio_id: str): return cls.get(portfolio_id=portfolio_id) @classmethod @deprecation.deprecated( deprecated_in='0.8.293', details='from_portfolio_name is now deprecated, please use ' 'Portfolio.get(portfolio_name=portfolio_name) instead.', ) def from_portfolio_name(cls, name: str): return cls.get(portfolio_name=name) @staticmethod def from_quote(quote_id: str): instruments = GsPortfolioApi.get_instruments_by_workflow_id(quote_id) ret = Portfolio(instruments, name=quote_id) ret.__quote_id = quote_id return ret def save(self, overwrite: Optional[bool] = False): if self.portfolios: raise ValueError('Cannot save portfolios with nested portfolios') if self.__id: if not overwrite: raise ValueError(f'Portfolio with id {id} already exists. Use overwrite=True to overwrite') else: if not self.name: raise ValueError('name not set') self.__id = GsPortfolioApi.create_portfolio(MarqueePortfolio('USD', self.name)).id _logger.info(f'Created Marquee portfolio {self.name} with id {self.__id}') position_set = PositionSet( position_date=self.__position_context.position_date, positions=tuple( Position(asset_id=GsAssetApi.get_or_create_asset_from_instrument(i)) for i in self.instruments ), ) if len(position_set.positions) > 0: GsPortfolioApi.update_positions(self.__id, [position_set]) def save_as_quote(self, overwrite: Optional[bool] = False) -> str: if self.portfolios: raise ValueError('Cannot save portfolios with nested portfolios') pricing_context = self._pricing_context with pricing_context: pricing_date = PricingContext.current.pricing_date market = PricingContext.current.market request = RiskRequest( tuple(RiskPosition(instrument=i, quantity=i.instrument_quantity) for i in self.instruments), (ResolvedInstrumentValues,), pricing_and_market_data_as_of=(PricingDateAndMarketDataAsOf(pricing_date=pricing_date, market=market),), ) if self.__quote_id: if not overwrite: raise ValueError(f'Quote with id {self.__quote_id} already exists. Use overwrite=True to overwrite') else: GsPortfolioApi.update_quote(self.__quote_id, request) _logger.info(f'Updated quote with id {self.__quote_id}') else: self.__quote_id = GsPortfolioApi.save_quote(request) _logger.info(f'Created quote with id {self.__quote_id}') return self.__quote_id def save_to_shadowbook(self, name: str): if self.portfolios: raise ValueError('Cannot save portfolios with nested portfolios') pricing_context = self._pricing_context with pricing_context: pricing_date = PricingContext.current.pricing_date market = PricingContext.current.market request = RiskRequest( tuple(RiskPosition(instrument=i, quantity=i.instrument_quantity) for i in self.instruments), (ResolvedInstrumentValues,), pricing_and_market_data_as_of=(PricingDateAndMarketDataAsOf(pricing_date=pricing_date, market=market),), ) status = GsPortfolioApi.save_to_shadowbook(request, name) print(f'Save to shadowbook status - {status}') @classmethod def from_frame(cls, data: pd.DataFrame, mappings: dict = None): def get_value(this_row: pd.Series, attribute: str): value = mappings.get(attribute, attribute) return value(this_row) if callable(value) else this_row.get(value) instruments = [] mappings = mappings or {} data = data.replace({np.nan: None}) for row in (r for _, r in data.iterrows() if any(v for v in r.values if v is not None)): instrument = None for init_keys in (('asset_class', 'type'), ('$type',)): init_values = tuple(filter(None, (get_value(row, k) for k in init_keys))) if len(init_keys) == len(init_values): instrument = Instrument.from_dict(dict(zip(init_keys, init_values))) instrument = instrument.from_dict({p: get_value(row, p) for p in instrument.properties()}) break if instrument: instruments.append(instrument) else: raise ValueError('Neither asset_class/type nor $type specified') return cls(instruments) @classmethod def from_csv(cls, csv_file: str, mappings: Optional[dict] = None): data = pd.read_csv(csv_file, skip_blank_lines=True).replace({np.nan: None}) reg = re.compile(r'\.[0-9]') dupelist = [re.sub(reg, '', word) for word in data.columns if reg.search(word)] if len(dupelist): raise ValueError(f'Duplicate column values {dupelist}') return cls.from_frame(data, mappings) def scale(self, scaling: int, in_place: bool = True): instruments = self._get_instruments(self.__position_context.position_date, in_place, False) if in_place: for inst in self.all_instruments: inst.scale(scaling, in_place) else: return Portfolio([inst.scale(scaling, in_place) for inst in instruments]) def append(self, priceables: Union[PriceableImpl, Iterable[PriceableImpl]]): self.priceables += (priceables,) if isinstance(priceables, PriceableImpl) else tuple(priceables) def pop(self, item) -> PriceableImpl: priceable = self[item] self.priceables = [inst for inst in self.instruments if inst != priceable] return priceable def extend(self, portfolio: Iterable): self.priceables += tuple([p for p in portfolio]) def to_frame(self, mappings: Optional[dict] = None) -> pd.DataFrame: def to_records(portfolio: Portfolio) -> list: records = [] for priceable in portfolio.priceables: if isinstance(priceable, Portfolio): records.extend(to_records(priceable)) else: as_dict = priceable.as_dict() if not hasattr(priceable, 'asset_class'): as_dict['$type'] = priceable.type_ records.append( dict(chain(as_dict.items(), (('instrument', priceable), ('portfolio', portfolio.name)))) ) return records df = pd.DataFrame.from_records(to_records(self)).set_index(['portfolio', 'instrument']) all_columns = df.columns.to_list() columns = sorted(c for c in all_columns if c not in ('asset_class', 'type', '$type')) for asset_column in ('$type', 'type', 'asset_class'): if asset_column in all_columns: columns = [asset_column] + columns df = df[columns] mappings = mappings or {} for key, value in mappings.items(): if isinstance(value, str): df[key] = df[value] elif callable(value): df[key] = len(df) * [None] df[key] = df.apply(value, axis=1) return df def to_csv(self, csv_file: str, mappings: Optional[dict] = None, ignored_cols: Optional[list] = None): port_df = self.to_frame(mappings or {}) port_df = port_df[np.setdiff1d(port_df.columns, ignored_cols or [])] port_df = port_df.reset_index(drop=True) port_df.to_csv(csv_file) @property def all_paths(self) -> Tuple[PortfolioPath, ...]: paths = () stack = [(None, self)] while stack: parent, portfolio = stack.pop() for idx, priceable in enumerate(portfolio.__priceables): path = parent + PortfolioPath(idx) if parent is not None else PortfolioPath(idx) if isinstance(priceable, Portfolio): stack.insert(0, (path, priceable)) else: paths += (path,) return paths def paths(self, key: Union[str, PriceableImpl]) -> Tuple[PortfolioPath, ...]: if not isinstance(key, (str, Instrument, Portfolio)): raise ValueError('key must be a name or Instrument or Portfolio') if isinstance(key, str): idx = self.__priceables_by_name.get(key) else: idx = [] for p_idx, p in enumerate(self.__priceables): if p == key or getattr(p, "unresolved", None) == key: idx.append(p_idx) paths = tuple(PortfolioPath(i) for i in idx) if idx else () for path, porfolio in ( (PortfolioPath(i), p) for i, p in enumerate(self.__priceables) if isinstance(p, Portfolio) ): paths += tuple(path + sub_path for sub_path in porfolio.paths(key)) return paths def resolve(self, in_place: bool = True) -> Optional[Union[PricingFuture, PriceableImpl, dict]]: priceables = self._get_instruments(self.__position_context.position_date, in_place, True) pricing_context = self._pricing_context with pricing_context: futures = [p.resolve(in_place) for p in priceables] if not in_place: ret = {} if isinstance(PricingContext.current, HistoricalPricingContext) else Portfolio(name=self.name) result_future = PricingFuture() if self._return_future else None def cb(future: CompositeResultFuture): if isinstance(ret, Portfolio): ret.priceables = [f.result() for f in future.futures] else: priceables_by_date = {} for future in futures: for date, priceable in future.result().items(): priceables_by_date.setdefault(date, []).append(priceable) for date, priceables in priceables_by_date.items(): if any(p for p in priceables if not isinstance(p, PriceableImpl)): _logger.error(f'Error resolving on {date}, skipping that date') else: ret[date] = Portfolio(priceables, name=self.name) if result_future: result_future.set_result(ret) CompositeResultFuture(futures).add_done_callback(cb) return result_future or ret def market(self) -> Union[OverlayMarket, PricingFuture, dict]: """ Market Data map of coordinates and values. Note that this is not yet supported on all instruments ***Examples** >>> from gs_quant.markets.portfolio import Portfolio >>> >>> portfolio = Portfolio(...) >>> market = portfolio.market() """ pricing_context = self._pricing_context instruments = self._get_instruments(self.__position_context.position_date, False, False) with pricing_context: futures = [i.market() for i in instruments] result_future = PricingFuture() def cb(future: CompositeResultFuture): def update_market_data(all_market_data, this_market_data): for coordinate, value in this_market_data.items(): existing_value = all_market_data.setdefault(coordinate, value) if abs(existing_value - value) > 1e-6: raise ValueError(f'Conflicting values for {coordinate}: {existing_value} vs {value}') results = [f.result() for f in future.futures] is_historical = isinstance(results[0], dict) market_data = None if is_historical else {} overlay_markets = {} if is_historical else None for result in results: if market_data is not None: update_market_data(market_data, result.market_data_dict) else: for market in result.values(): update_market_data(overlay_markets.setdefault(market, {}), market.market_data) if market_data: ret = OverlayMarket(base_market=results[0], market_data=market_data) else: ret = { base_market.date: OverlayMarket(base_market=base_market, market_data=market_data) for base_market, market_data in overlay_markets.items() } if result_future: result_future.set_result(ret) CompositeResultFuture(futures).add_done_callback(cb) return result_future if self._return_future else result_future.result() def calc(self, risk_measure: Union[RiskMeasure, Iterable[RiskMeasure]], fn=None) -> PortfolioRiskResult: priceables = self._get_instruments(self.__position_context.position_date, False, True) with self._pricing_context: # PortfolioRiskResult should hold a copy of the portfolio instead of a reference to the portfolio # this is to prevent the portfolio object within portfolioriskresult to hold a reference to the portfolio # object should it later be modified in place (eg: resolution) return PortfolioRiskResult( self.clone(), (risk_measure,) if isinstance(risk_measure, RiskMeasure) else risk_measure, [p.calc(risk_measure, fn=fn) for p in priceables], ) def _get_instruments(self, position_date: dt.date, in_place: bool, return_priceables: bool = True): if self.id: dates_prior = list(filter(lambda date: date < position_date, GsPortfolioApi.get_position_dates(self.id))) if len(dates_prior) == 0: raise ValueError('Your portfolio has no positions on the PositionContext date') date = max(dates_prior) response = GsPortfolioApi.get_positions_for_date(self.id, date) positions = response.positions if response else [] instruments = GsAssetApi.get_instruments_for_positions(positions) if in_place: self.__priceables = instruments return instruments return self.__priceables if return_priceables else self.all_instruments def clone(self, clone_instruments: bool = False): portfolio_clone = Portfolio( [ p.clone(clone_instruments) if isinstance(p, Portfolio) else p.clone() if clone_instruments else p for p in self.__priceables ], name=self.name, ) portfolio_clone.__id = self.__id portfolio_clone.__quote_id = self.__quote_id return portfolio_clone @dataclass class Grid(Portfolio): """A grid of instruments A grid is a type of portfolio which represents a grid of similar instruments """ def __init__( self, priceable: PriceableImpl, x_param: str, x_values: Iterable, y_param: str, y_values: Iterable, name: Optional[str] = None, ): x_overrides = [{x_param: v, 'name': v} for v in x_values] y_overrides = [{y_param: v} for v in y_values] super().__init__( [ Portfolio([priceable.clone(**{**x, **y}) for x in x_overrides], name=next(iter(y.values()))) for y in y_overrides ], name, ) ================================================ FILE: gs_quant/markets/portfolio_manager.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import logging import traceback from time import sleep from typing import List, Union, Dict import deprecation import numpy as np import pandas as pd from gs_quant.api.gs.portfolios import GsPortfolioApi from gs_quant.api.gs.reports import GsReportApi from gs_quant.common import Currency, PositionType from gs_quant.entities.entitlements import Entitlements, EntitlementBlock, User from gs_quant.entities.entity import PositionedEntity, EntityType, ScenarioCalculationMeasure from gs_quant.errors import MqError from gs_quant.errors import MqValueError from gs_quant.markets.factor import Factor from gs_quant.markets.portfolio_manager_utils import ( build_exposure_df, build_portfolio_constituents_df, build_sensitivity_df, get_batched_dates, ) from gs_quant.markets.report import PerformanceReport, ReportJobFuture from gs_quant.markets.scenario import FactorScenario from gs_quant.models.risk_model import MacroRiskModel, ReturnFormat, FactorType, FactorRiskModel from gs_quant.target.portfolios import RiskAumSource, PortfolioTree from gs_quant.target.risk_models import RiskModelDataAssetsRequest, RiskModelUniverseIdentifierRequest _logger = logging.getLogger(__name__) @deprecation.deprecated( deprecated_in='1.0.10', details='portfolio_manager.CustomAUMDataPoint is now deprecated, please use report.CustomAUMDataPoint instead.', ) class CustomAUMDataPoint: """ Custom AUM Data Point represents a portfolio's AUM value for a specific date """ def __init__(self, date: dt.date, aum: float): self.__date = date self.__aum = aum @property def date(self) -> dt.date: return self.__date @date.setter def date(self, value: dt.date): self.__date = value @property def aum(self) -> float: return self.__aum @aum.setter def aum(self, value: float): self.__aum = value class PortfolioManager(PositionedEntity): """ Portfolio Manager is used to manage Marquee portfolios (setting entitlements, running and retrieving reports, etc) """ def __init__(self, portfolio_id: str): """ Initialize a Portfolio Manager :param portfolio_id: Portfolio ID """ self.__portfolio_id = portfolio_id PositionedEntity.__init__(self, portfolio_id, EntityType.PORTFOLIO) @property def portfolio_id(self) -> str: return self.__portfolio_id @portfolio_id.setter def portfolio_id(self, value: str): self.__portfolio_id = value def get_performance_report(self, tags: Dict = None) -> PerformanceReport: """ Get performance report associated with a portfolio :param tags: If the portfolio is a fund of funds, pass in a dictionary corresponding to the tag values to retrieve results for a sub-portfolio :return: returns the PerformanceReport associated with portfolio if one exists """ reports = GsReportApi.get_reports( limit=500, position_source_type='Portfolio', position_source_id=self.id, report_type='Portfolio Performance Analytics', tags=tags, scroll='1m', ) # If tags is set to None, it returns all PPA reports for the portfolio, # and returning reports[0] can return any one PPA, so specifically if tags is None it means we are # looking for the root PPA, we need to explicitly filter out and get the one report where tags is None if tags is None: reports = [report for report in reports if report.parameters.tags is None] if len(reports) == 0: raise MqError('No performance report found.') return PerformanceReport.from_target(reports[0]) def schedule_reports( self, start_date: dt.date = None, end_date: dt.date = None, backcast: bool = False, months_per_batch: int = None ): """ Schedule all reports associated with a portfolio. If months_per_batch is passed and reports are historical, then schedule them in batches of bathc_period months. Backcasted reported can't be batched. :param start_date: start date of report job (optional) :param end_date: end date of report job (optional) :param backcast: true if reports should be backcasted; defaults to false :param months_per_batch: batch size of historical report job schedules in number of months; defaults to false if set, historical reports are scheduled in batches of size less than or equal to the given number of months **Examples** >>> pm = PortfolioManager("PORTFOLIO ID") >>> pm.schedule_reports(backcast=False) for a portfolio having hisotry greater than 1 yr, use months_per_batch to schedule reports in batches of 6 months >>> pm = PortfolioManager("PORTFOLIO ID") >>> pm.schedule_reports(backcast=False, months_per_batch=6) """ if months_per_batch is None or backcast: if backcast: print( 'Batching of schedule reports is only supported for historical reports. ' 'Backcasted reports will be scheduled for the full date range' ) GsPortfolioApi.schedule_reports(self.__portfolio_id, start_date, end_date, backcast=backcast) else: # Process input if months_per_batch <= 0: raise MqValueError(f'Invalid input, months_per_batch {months_per_batch} should be greater than 0') if end_date is None or start_date is None: hints = self.get_schedule_dates(backcast=backcast) start_date = hints[0] if start_date is None else start_date end_date = hints[1] if end_date is None else end_date if start_date >= end_date: raise MqValueError(f'Invalid input, start date {start_date} should be before end date {end_date}') # Validate input date range against position dates position_dates = self.get_position_dates() position_dates = [p for p in position_dates if end_date >= p >= start_date] if len(position_dates) == 0: raise MqValueError('Portfolio does not have any positions in the given date range') if start_date not in position_dates: # There should be positions on the start date for historical reports raise MqError( 'Cannot schedule historical report because the first set of positions within the ' f'scheduling date range need to be on this date {start_date}' ) else: # preparation for the first batch position_dates.remove(start_date) if end_date not in position_dates: # make sure to process the last batch position_dates.append(end_date) # Create batches using sliding window batch_boundaries = [start_date] prev_date = start_date for i, d in enumerate(position_dates): current_batch = d - prev_date if current_batch.days > months_per_batch * 30 and i > 0: # split the current window at the previous date prev_date = position_dates[i - 1] batch_boundaries.append(prev_date) batch_boundaries.append(end_date) # Schedule reports in batches print(f'Scheduling reports from {start_date} to {end_date} in {len(batch_boundaries) - 1} batches') for i in range(len(batch_boundaries) - 1): print(f'Scheduling for {batch_boundaries[i]} to {batch_boundaries[i + 1]}') GsPortfolioApi.schedule_reports( self.__portfolio_id, batch_boundaries[i], batch_boundaries[i + 1], backcast=backcast ) if i > 0 and i % 10 == 0: sleep(6) def run_reports( self, start_date: dt.date = None, end_date: dt.date = None, backcast: bool = False, is_async: bool = True, months_per_batch: int = None, ) -> List[Union[pd.DataFrame, ReportJobFuture]]: """ Run all reports associated with a portfolio :param start_date: start date of report job :param end_date: end date of report job :param backcast: true if reports should be backcasted; defaults to false :param is_async: true if reports should run asynchronously; defaults to true :param months_per_batch: batch size of historical report job schedules in number of months; defaults to false if set, historical reports are scheduled in batches of size less than or equal to the given number of months :return: if is_async is true, returns a list of ReportJobFuture objects; if is_async is false, returns a list of dataframe objects containing report results for all portfolio results **Examples** >>> pm = PortfolioManager("PORTFOLIO ID") >>> report_results = pm.run_reports(backcast=True) for longer hisotry, use months_per_batch to schedule reports in batches of 6 months >>> pm = PortfolioManager("PORTFOLIO ID") >>> pm.schedule_reports(backcast=False, months_per_batch=6) """ self.schedule_reports(start_date, end_date, backcast, months_per_batch) reports = self.get_reports() report_futures = [report.get_most_recent_job() for report in reports] if is_async: return report_futures counter = 100 while counter > 0: is_done = [future.done() for future in report_futures] if False not in is_done: return [job_future.result() for job_future in report_futures] sleep(6) raise MqValueError( f'Your reports for Portfolio {self.__portfolio_id} are taking longer than expected ' f'to finish. Please contact the Marquee Analytics team at ' f'gs-marquee-analytics-support@gs.com' ) def set_entitlements(self, entitlements: Entitlements): """ Set the entitlements of a portfolio :param entitlements: Entitlements object **Examples** >>> portfolio_admin_emails = ['LIST OF ADMIN EMAILS'] >>> portfolio_viewer_emails = ['LIST OF VIEWER EMAILS'] >>> admin_entitlements = EntitlementBlock(users=User.get_many(emails=portfolio_admin_emails)) >>> view_entitlements = EntitlementBlock(users=User.get_many(emails=portfolio_viewer_emails)) >>> >>> entitlements = Entitlements( >>> view=view_entitlements, >>> admin=admin_entitlements >>> ) >>> >>> pm = PortfolioManager("PORTFOLIO ID") >>> pm.set_entitlements(entitlements) **See Also** :func: PortfolioManager.share :func: PortfolioManager.get_entitlements """ entitlements_as_target = entitlements.to_target() portfolio_as_target = GsPortfolioApi.get_portfolio(self.__portfolio_id) portfolio_as_target.entitlements = entitlements_as_target GsPortfolioApi.update_portfolio(portfolio_as_target) def share(self, emails: List[str], admin: bool = False): """ Share a portfolio with a list of emails :param emails: list of emails to share the portfolio with :param admin: true if the users should have admin access; defaults to false **Examples** >>> pm = PortfolioManager("PORTFOLIO ID") >>> pm.share(['EMAIL1', 'EMAIL2']) """ current_entitlements = self.get_entitlements() users = User.get_many(emails=emails) found_emails = [user.email for user in users] if len(found_emails) != len(emails): missing_emails = [email for email in emails if email not in found_emails] raise MqValueError(f'Users with emails {missing_emails} not found') if admin: current_entitlements.admin = EntitlementBlock(users=set(current_entitlements.admin.users + users)) current_entitlements.view = EntitlementBlock(users=set(current_entitlements.view.users + users)) self.set_entitlements(current_entitlements) def set_currency(self, currency: Currency): """ Set the currency of a portfolio :param currency: Currency """ portfolio_as_target = GsPortfolioApi.get_portfolio(self.__portfolio_id) portfolio_as_target.currency = currency GsPortfolioApi.update_portfolio(portfolio_as_target) def get_tag_name_hierarchy(self) -> List: """ Get the list of tags by name by which a portfolio's fund of funds are structured in that order :return: a list of tag names """ portfolio = GsPortfolioApi.get_portfolio(self.portfolio_id) return list(portfolio.tag_name_hierarchy) if portfolio.tag_name_hierarchy else None def set_tag_name_hierarchy(self, tag_names: List): """ Set the list of tags by name by which a portfolio's fund of funds are structured in that order :param tag_names: a list of tag names in order of the new fund of funds structure """ portfolio = GsPortfolioApi.get_portfolio(self.portfolio_id) portfolio.tag_name_hierarchy = tag_names GsPortfolioApi.update_portfolio(portfolio) def update_portfolio_tree(self): """ After a modification is made on your portfolio (reports are added/modified, the tag name hierarchy is changed, etc), run this function so those changes reflect across all the portfolio's sub-portfolios """ GsPortfolioApi.update_portfolio_tree(self.portfolio_id) def get_portfolio_tree(self) -> PortfolioTree: """ Get the portfolio tree of a fund of funds portfolio """ return GsPortfolioApi.get_portfolio_tree(self.portfolio_id) def get_all_fund_of_fund_tags(self) -> List: """ If the portfolio is a fund of funds, this function will retrieve a list of dictionaries of all the tag sets associated with the sub-portfolios in the portfolio. :return: a list of tags as dictionaries """ tag_dicts = [] for r in self.get_reports(): if r.parameters.tags is not None: tags_as_dict = {tag.name: tag.value for tag in r.parameters.tags} if tags_as_dict not in tag_dicts: tag_dicts.append(tags_as_dict) tag_dicts.sort(key=lambda dictionary: len(dictionary.keys())) return tag_dicts def get_schedule_dates(self, backcast: bool = False) -> List[dt.date]: """ Get recommended start and end dates for a portfolio report scheduling job :param backcast: true if reports should be backcasted :return: a list of two dates, the first is the suggested start date and the second is the suggested end date """ return GsPortfolioApi.get_schedule_dates(self.id, backcast) @deprecation.deprecated( deprecated_in='1.0.10', details='PortfolioManager.get_aum_source is now deprecated, please use ' 'PerformanceReport.get_aum_source instead.', ) def get_aum_source(self) -> RiskAumSource: """ Get portfolio AUM Source :return: aum source """ portfolio = GsPortfolioApi.get_portfolio(self.portfolio_id) return portfolio.aum_source if portfolio.aum_source is not None else RiskAumSource.Long @deprecation.deprecated( deprecated_in='1.0.10', details='PortfolioManager.set_aum_source is now deprecated, please use ' 'PerformanceReport.set_aum_source instead.', ) def set_aum_source(self, aum_source: RiskAumSource): """ Set portfolio AUM Source :param aum_source: aum source for portfolio :return: aum source """ portfolio = GsPortfolioApi.get_portfolio(self.portfolio_id) portfolio.aum_source = aum_source GsPortfolioApi.update_portfolio(portfolio) @deprecation.deprecated( deprecated_in='1.0.10', details='PortfolioManager.get_custom_aum is now deprecated, please use ' 'PerformanceReport.get_custom_aum instead.', ) def get_custom_aum(self, start_date: dt.date = None, end_date: dt.date = None) -> List[CustomAUMDataPoint]: """ Get AUM data for portfolio :param start_date: start date :param end_date: end date :return: list of AUM data between the specified range """ aum_data = GsPortfolioApi.get_custom_aum(self.portfolio_id, start_date, end_date) return [ CustomAUMDataPoint(date=dt.datetime.strptime(data['date'], '%Y-%m-%d'), aum=data['aum']) for data in aum_data ] @deprecation.deprecated( deprecated_in='1.0.10', details='PortfolioManager.get_aum is now deprecated, please use PerformanceReport.get_aum instead.', ) def get_aum(self, start_date: dt.date, end_date: dt.date): """ Get AUM data for portfolio :param start_date: start date :param end_date: end date :return: dictionary of dates with corresponding AUM values """ aum_source = self.get_aum_source() if aum_source == RiskAumSource.Custom_AUM: aum = self.get_custom_aum(start_date=start_date, end_date=end_date) return {aum_point.date.strftime('%Y-%m-%d'): aum_point.aum for aum_point in aum} if aum_source == RiskAumSource.Long: aum = self.get_performance_report().get_long_exposure(start_date=start_date, end_date=end_date) return {row['date']: row['longExposure'] for index, row in aum.iterrows()} if aum_source == RiskAumSource.Short: aum = self.get_performance_report().get_short_exposure(start_date=start_date, end_date=end_date) return {row['date']: row['shortExposure'] for index, row in aum.iterrows()} if aum_source == RiskAumSource.Gross: aum = self.get_performance_report().get_gross_exposure(start_date=start_date, end_date=end_date) return {row['date']: row['grossExposure'] for index, row in aum.iterrows()} if aum_source == RiskAumSource.Net: aum = self.get_performance_report().get_net_exposure(start_date=start_date, end_date=end_date) return {row['date']: row['netExposure'] for index, row in aum.iterrows()} @deprecation.deprecated( deprecated_in='1.0.10', details='PortfolioManager.upload_custom_aum is now deprecated, please use ' 'PerformanceReport.upload_custom_aum instead.', ) def upload_custom_aum(self, aum_data: List[CustomAUMDataPoint], clear_existing_data: bool = None): """ Add AUM data for portfolio :param aum_data: list of AUM data to upload :param clear_existing_data: delete all previously uploaded AUM data for the portfolio (defaults to false) """ formatted_aum_data = [{'date': data.date.strftime('%Y-%m-%d'), 'aum': data.aum} for data in aum_data] GsPortfolioApi.upload_custom_aum(self.portfolio_id, formatted_aum_data, clear_existing_data) @deprecation.deprecated( deprecated_in="0.9.110", details="Please use the get_pnl_contribution on your portfolio's performance report using" "the PerformanceReport class", ) def get_pnl_contribution( self, start_date: dt.date = None, end_date: dt.date = None, currency: Currency = None, tags: Dict = None ) -> pd.DataFrame: """ Get PnL Contribution of your portfolio broken down by constituents :param start_date: optional start date :param end_date: optional end date :param currency: optional currency; defaults to your portfolio's currency :param tags: If the portfolio is a fund of funds, pass in a dictionary corresponding to the tag values to retrieve results for a sub-portfolio :return: a Pandas DataFrame of results """ performance_report_id = None if tags is None else self.get_performance_report(tags).id return pd.DataFrame( GsPortfolioApi.get_attribution(self.portfolio_id, start_date, end_date, currency, performance_report_id) ) def get_macro_exposure( self, model: MacroRiskModel, date: dt.date, factor_type: FactorType, factor_categories: List[Factor] = [], get_factors_by_name: bool = True, tags: Dict = None, return_format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[Dict, pd.DataFrame]: """ Get portfolio and asset exposure to macro factors or macro factor categories :param model: the macro risk model :param date: date for which to get exposure :param factor_type: whether to get exposure to factor categories or factors. :param factor_categories: Get portfolio exposure to these factor categories. Must be valid Factor Categories. If factor_type is Factor, get exposure to factors that are grouped in these factor categories. If empty, return exposure to all factor categories/factors. :param get_factors_by_name: whether to identify factors by their name or identifier :param tags: If the portfolio is a fund of funds, pass in a dictionary corresponding to the tag values to retrieve results for a sub-portfolio :param return_format: whether to return a dict or a pandas dataframe :return: a Pandas Dataframe or a Dict of portfolio exposure to macro factors **Examples** >>> model = MacroRiskModel.get(\"MODEL\") >>> pm = PortfolioManager("PORTFOLIO ID") >>> exposure_dataframe = pm.get_macro_exposure( >>> model=model, >>> date=dt.date(2022, 1, 1), >>> factor_type=FactorType.Factor >>> ).sort_values( >>> by=[\"Total Factor Exposure\"], >>> axis=1, >>> ascending=False >>> ) """ performance_report = self.get_performance_report(tags) # Get portfolio constituents constituents_and_notional_df = build_portfolio_constituents_df(performance_report, date).rename( columns={"name": "Asset Name", "netExposure": "Notional"} ) # Query universe sensitivity universe = constituents_and_notional_df.index.dropna().tolist() universe_sensitivities_df = build_sensitivity_df(universe, model, date, factor_type, get_factors_by_name) # Remove assets without exposure assets_with_exposure = list(universe_sensitivities_df.index.values) if not assets_with_exposure: logging.warning("The Portfolio is not exposed to any of the requested macro factors") return pd.DataFrame() constituents_and_notional_df = constituents_and_notional_df.loc[assets_with_exposure] factor_data = ( model.get_factor_data(date, date, factor_type=FactorType.Factor) if factor_type == FactorType.Factor else pd.DataFrame() ) exposure_df = build_exposure_df( constituents_and_notional_df, universe_sensitivities_df, factor_categories, factor_data, get_factors_by_name ) if return_format == ReturnFormat.JSON: return exposure_df.to_dict() return exposure_df def get_factor_scenario_analytics( self, scenarios: List[FactorScenario], date: dt.date, measures: List[ScenarioCalculationMeasure], risk_model: str = None, return_format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[Dict, Union[Dict, pd.DataFrame]]: """Given a list of factor scenarios (historical simulation and/or custom shocks), return the estimated pnl of the given positioned entity. :param scenarios: List of factor-based scenarios :param date: date to run scenarios. :param measures: which metrics to return :param risk_model: valid risk model ID :param return_format: whether to return data formatted in a dataframe or as a dict **Examples** >>> from gs_quant.session import GsSession, Environment >>> from gs_quant.markets.portfolio_manager import PortfolioManager, ReturnFormat >>> from gs_quant.entities.entity import ScenarioCalculationMeasure, PositionedEntity >>> from gs_quant.markets.scenario import Scenario Get scenarios >>> covid_19_omicron = Scenario.get_by_name("Covid 19 Omicron (v2)") # historical simulation >>> custom_shock = Scenario.get_by_name("Shocking factor by x% (Propagated)") # custom shock >>> risk_model = "RISK_MODEL_ID" # valid risk model ID Instantiate your positionedEntity. Here, we are using one of its subclasses, PortfolioManager >>> pm = PortfolioManager(portfolio_id="PORTFOLIO_ID") Set the date you wish to run your scenario on >>> date = dt.date(2023, 3, 7) Run scenario and get estimated impact on your positioned entity >>> scenario_analytics = pm.get_factor_scenario_analytics( ... scenarios=[covid_19_omicron, beta_propagated], ... date=date, ... measures=[ScenarioCalculationMeasure.SUMMARY, ... ScenarioCalculationMeasure.ESTIMATED_FACTOR_PNL, ... ScenarioCalculationMeasure.ESTIMATED_PNL_BY_SECTOR, ... ScenarioCalculationMeasure.ESTIMATED_PNL_BY_REGION, ... ScenarioCalculationMeasure.ESTIMATED_PNL_BY_DIRECTION, ... ScenarioCalculationMeasure.ESTIMATED_PNL_BY_ASSET], ... risk_model=risk_model) By default, the result will be returned in a dict with keys as the measures/metrics requested and values as the scenario calculation results formatted in a dataframe. To get the results in a dict, specify the return format as JSON """ return super().get_factor_scenario_analytics(scenarios, date, measures, risk_model, return_format) def get_risk_model_predicted_beta( self, start_date: dt.date, end_date: dt.date, risk_model_id: str, default_beta_value: int = 1, tags: Dict = None ) -> pd.DataFrame: """ Get the predicted beta of a portfolio as estimated by the provided risk model. The beta is calculated using the factor risk model data. The function retrieves the factor risk report associated with the portfolio and the risk model ID, and then calculates the predicted beta for each date in the specified range. :param start_date: Start date for the beta calculation :param end_date: End date for the beta calculation :param risk_model_id: Risk model ID to be used for the calculation :param default_beta_value: Default beta value to be used if no data is available for a specific date :param tags: If the portfolio is a fund of funds, pass in a dictionary corresponding to the tag values to retrieve results for a sub-portfolio :return: A DataFrame containing the predicted beta for each date in the specified range **Examples** >>> pm = PortfolioManager("PORTFOLIO ID") >>> beta_df = pm.get_risk_model_predicted_beta(start_date=dt.date(2023, 1, 1), ... end_date=dt.date(2023, 12, 31), risk_model_id="RISK_MODEL_ID") >>> print(beta_df) >>> # Output: date beta 2023-01-01 1.05 2023-01-02 1.10 2023-01-03 1.02 """ performance_report = self.get_performance_report(tags=tags) risk_model = FactorRiskModel.get(risk_model_id) risk_model_dates: List[dt.date] = risk_model.get_dates(start_date=start_date, end_date=end_date) batched_dates = get_batched_dates(risk_model_dates, batch_size=10) risk_model_predicted_beta_timeseries = pd.DataFrame() asset_level_betas = pd.DataFrame() portfolio_position_net_weights = pd.DataFrame() for batch in batched_dates: start_date, end_date = batch[0], batch[-1] try: date_wise_net_weight = performance_report.get_position_net_weights( start_date=start_date, end_date=end_date, asset_metadata_fields=["gsid"], include_all_business_days=True, position_type=PositionType.CLOSE, ) date_wise_net_weight = date_wise_net_weight.dropna().pivot_table( index='positionDate', columns='gsid', values='netWeight', aggfunc='sum' ) portfolio_position_gsids = [gsid for gsid in date_wise_net_weight.columns.tolist() if gsid is not None] asset_level_betas_batch = risk_model.get_predicted_beta( start_date=start_date, end_date=end_date, assets=RiskModelDataAssetsRequest( identifier=RiskModelUniverseIdentifierRequest.gsid, universe=tuple(portfolio_position_gsids) ), ) if asset_level_betas_batch.isna().all().all(): logging.warning( f"Risk model predicted beta not available for risk model {risk_model_id} for dates: {batch}" ) continue else: asset_level_betas = pd.concat([asset_level_betas, asset_level_betas_batch], axis=0) portfolio_position_net_weights = pd.concat( [portfolio_position_net_weights, date_wise_net_weight], axis=0 ) except Exception as ex: raise MqError( f"Risk model predicted beta cannot be calculated for risk model {risk_model_id} " f"from {start_date} to {end_date} because of exception: {ex}, traceback: {traceback.format_exc()}" ) if asset_level_betas.empty: return pd.DataFrame() asset_level_betas = asset_level_betas.fillna(default_beta_value) risk_model_predicted_beta_timeseries = asset_level_betas * portfolio_position_net_weights risk_model_predicted_beta_timeseries = ( risk_model_predicted_beta_timeseries.sum(axis=1).rename('beta').replace({0: np.nan}).ffill() ) return risk_model_predicted_beta_timeseries.reset_index().rename(columns={'index': 'date', 0: 'beta'}) ================================================ FILE: gs_quant/markets/portfolio_manager_utils.py ================================================ """ Copyright 2021 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.markets.report import PerformanceReport from gs_quant.models.risk_model import ( MacroRiskModel, DataAssetsRequest, RiskModelUniverseIdentifierRequest as UniverseIdentifierRequest, FactorType, ) from gs_quant.api.gs.assets import GsAssetApi from gs_quant.errors import MqValueError from typing import List, Dict import numpy as np import pandas as pd import datetime as dt def build_macro_portfolio_exposure_df( df_constituents_and_notional: pd.DataFrame, universe_sensitivities_df: pd.DataFrame, factor_dict: Dict, factor_category_dict: Dict, factors_by_name: bool, ) -> pd.DataFrame: if factors_by_name: universe_sensitivities_df = universe_sensitivities_df.rename(columns=factor_dict) columns_to_keep = factor_dict.values() if factors_by_name else factor_dict.keys() columns_to_drop = list(set(universe_sensitivities_df.columns.values) - set(columns_to_keep)) universe_sensitivities_df = universe_sensitivities_df.drop(columns=columns_to_drop) notional_df = df_constituents_and_notional.rename(columns={"name": "Asset Name", "netExposure": "Notional"}) gsids_with_exposure = list(universe_sensitivities_df.index.values) if not gsids_with_exposure: print(f"The portfolio is not exposed to any of the requested macro factors {', '.join(columns_to_keep)}") return pd.DataFrame() notional_df = notional_df.loc[gsids_with_exposure] # Multiply asset notional by asset sensitivity for each factor column_names = universe_sensitivities_df.columns.values.tolist() universe_sensitivities_df /= 100 for column in column_names: universe_sensitivities_df[column] = universe_sensitivities_df[column] * notional_df['Notional'] universe_sensitivities_df = notional_df.merge(universe_sensitivities_df, on='Asset Identifier') if factor_category_dict: # In case we want to group factors by the macro factor categories they belong in # Two-level column labels (Macro Factor Category -> Macro Factor) universe_sensitivities_df.columns = pd.MultiIndex.from_tuples( map( lambda factor: (factor_category_dict.get(factor, "Asset Information"), factor), universe_sensitivities_df.columns, ), names=("Macro Factor Category", "Macro Factor"), ) # Aggregate columns to get portfolio total exposure to each macro factor sum_over_factors_df = ( universe_sensitivities_df.loc[:, universe_sensitivities_df.columns != ("Asset Information", "Asset Name")] .agg(np.sum, axis="index") .to_frame() .transpose() ) # Append total portfolio exposure to universe_sensitivities df portfolio_exposure_df = pd.concat([universe_sensitivities_df, sum_over_factors_df]) # Group and Aggregate by Factor Category to get portfolio-level exposure to each macro Factor Category sum_over_categories_df = ( sum_over_factors_df.transpose().groupby("Macro Factor Category").apply(np.sum).transpose() ) factor_and_category_zip = dict( zip(portfolio_exposure_df.columns.get_level_values(1), portfolio_exposure_df.columns.get_level_values(0)) ) portfolio_exposure_per_category = list( map(lambda x: sum_over_categories_df.loc[0, factor_and_category_zip[x]], factor_and_category_zip.keys()) ) # Append exposure df with total portfolio macro Factor Category exposure portfolio_exposure_df = pd.concat( [ portfolio_exposure_df, pd.DataFrame( portfolio_exposure_per_category, index=portfolio_exposure_df.columns.tolist(), columns=["Portfolio Exposure Per Macro Factor Category"], ).transpose(), ] ) portfolio_exposure_df = portfolio_exposure_df.rename(index={0: "Portfolio Exposure Per Macro Factor"}) portfolio_exposure_df.loc["Portfolio Exposure Per Macro Factor", ("Asset Information", "Asset Name")] = np.nan portfolio_exposure_df.loc[ "Portfolio Exposure Per Macro Factor Category", ("Asset Information", "Asset Name") ] = np.nan # Sort macro Factor Categories by total exposure portfolio_exposure_df = portfolio_exposure_df.sort_values( by=["Portfolio Exposure Per Macro Factor Category"], axis='columns', ascending=False ) name_col = portfolio_exposure_df.loc[:, ("Asset Information", "Asset Name")] notional_col = portfolio_exposure_df.loc[:, ("Asset Information", "Notional")] portfolio_exposure_df = portfolio_exposure_df.drop( columns=[("Asset Information", "Asset Name"), ("Asset Information", "Notional")] ) portfolio_exposure_df = pd.concat([name_col, notional_col, portfolio_exposure_df], axis="columns") else: sum_over_factors_df = ( universe_sensitivities_df.loc[:, universe_sensitivities_df.columns != 'Asset Name'] .agg(np.sum, axis="index") .to_frame() .transpose() ) portfolio_exposure_df = pd.concat([universe_sensitivities_df, sum_over_factors_df]) portfolio_exposure_df = portfolio_exposure_df.rename(index={0: "Portfolio Exposure Per Macro Factor"}) portfolio_exposure_df.loc["Portfolio Exposure Per Macro Factor", "Asset Name"] = np.nan portfolio_exposure_df = portfolio_exposure_df.sort_values( by=["Portfolio Exposure Per Macro Factor"], axis='columns', ascending=False ) name_col = portfolio_exposure_df['Asset Name'] portfolio_exposure_df = portfolio_exposure_df.drop(columns=['Asset Name']) portfolio_exposure_df.insert(loc=0, column='Asset Name', value=name_col) portfolio_exposure_df.index.name = 'Asset Identifier' return portfolio_exposure_df def build_portfolio_constituents_df(performance_report: PerformanceReport, date: dt.date) -> pd.DataFrame: constituents_df = performance_report.get_portfolio_constituents( fields=['netExposure'], start_date=date, end_date=date ) if constituents_df.empty: raise MqValueError( f"Macro Exposure can't be calculated as the portfolio constituents could not be found on" f" the requested date {date}. Make sure the portfolio performance report is up to date." ) constituents_df = constituents_df[["assetId", "netExposure"]] constituents_df = constituents_df.dropna().set_index("assetId").rename_axis("Asset Identifier") assets_data = GsAssetApi.get_many_assets_data_scroll( fields=['name', 'gsid', 'id'], as_of=dt.datetime(date.year, date.month, date.day), limit=1000, id=constituents_df.index.tolist(), ) assets_data_df = ( pd.DataFrame.from_records(assets_data) .set_index("id") .fillna(value={"name": "Name not available"}) .rename_axis("Asset Identifier") ) # Merge the constituents dataframe and asset data dataframe to get gsid, asset name, notional constituents_and_notional_df = ( assets_data_df.merge(constituents_df, on='Asset Identifier') .reset_index(drop=True) .set_index("gsid") .rename_axis("Asset Identifier") .sort_index() ) return constituents_and_notional_df def build_sensitivity_df( universe: List, model: MacroRiskModel, date: dt.date, factor_type: FactorType, by_name: bool ) -> pd.DataFrame: universe_sensitivities_df = model.get_universe_sensitivity( start_date=date, end_date=date, assets=DataAssetsRequest(UniverseIdentifierRequest.gsid, universe), factor_type=factor_type, get_factors_by_name=by_name, ) if universe_sensitivities_df.empty: print(f"None of the assets in the portfolio are exposed to the factors in model {model.id} ") return pd.DataFrame() universe_sensitivities_df = ( universe_sensitivities_df.reset_index(level=1, drop=True).rename_axis("Asset Identifier").sort_index() ) return universe_sensitivities_df def build_exposure_df( notional_df: pd.DataFrame, universe_sensitivities_df: pd.DataFrame, factor_categories: List, factor_data: pd.DataFrame, by_name: bool, ) -> pd.DataFrame: # Multiply sensitivity with notional columns = universe_sensitivities_df.columns.values.tolist() universe_sensitivities_df /= 100 for column in columns: universe_sensitivities_df[column] = universe_sensitivities_df[column] * notional_df['Notional'] if factor_data.empty: if factor_categories: categories_names = [f.name for f in factor_categories] if by_name else [f.id for f in factor_categories] universe_sensitivities_df = universe_sensitivities_df[categories_names] universe_sensitivities_df = pd.concat( [ universe_sensitivities_df, universe_sensitivities_df.agg("sum").to_frame().rename(columns={0: "Total Factor Category Exposure"}).T, ] ) universe_sensitivities_df = universe_sensitivities_df.sort_values( by="Total Factor Category Exposure", axis=1, ascending=False ) notional_df = pd.concat( [ notional_df, notional_df[["Notional"]].agg("sum").to_frame().rename(columns={0: "Total Factor Category Exposure"}).T, ] ) exposure_df = notional_df.join(universe_sensitivities_df).rename_axis("Factor Category", axis=1) else: factor_data = factor_data.set_index("name") if by_name else factor_data.set_index("identifier") new_columns = ( [(factor_data.loc[f, 'factorCategory'], f) for f in universe_sensitivities_df.columns.values] if by_name else [(factor_data.loc[f, 'factorCategoryId'], f) for f in universe_sensitivities_df.columns.values] ) universe_sensitivities_df = universe_sensitivities_df.set_axis( pd.MultiIndex.from_tuples(new_columns), axis=1 ).rename_axis(("Factor Category", "Factor"), axis=1) universe_sensitivities_df = pd.concat( [ universe_sensitivities_df, universe_sensitivities_df.agg("sum").to_frame().rename(columns={0: "Total Factor Exposure"}).T, ] ) universe_sensitivities_df = universe_sensitivities_df.sort_values( by=["Total Factor Exposure"], axis=1, ascending=False ) # Only return factors that are grouped in the factor categories that we passed; if empty return all factors if factor_categories: categories_names = [f.name for f in factor_categories] if by_name else [f.id for f in factor_categories] universe_sensitivities_df = universe_sensitivities_df[categories_names] notional_df = pd.concat( [ notional_df, notional_df[["Notional"]].agg("sum").to_frame().rename(columns={0: "Total Factor Exposure"}).T, ] ).set_axis( pd.MultiIndex.from_tuples([("Asset Information", "Asset Name"), ("Asset Information", "Notional")]), axis=1 ) # Merge universe sensitivity with notional df exposure_df = notional_df.join(universe_sensitivities_df).rename_axis(("Factor Category", "Factor"), axis=1) return exposure_df def get_batched_dates(dates: List[dt.date], batch_size: int = 90) -> List[List[dt.date]]: """ Split a list of dates into batches of a specified size. :param dates: List of dates to be split into batches :param batch_size: Size of each batch :return: List of lists, where each sublist contains a batch of dates """ return [dates[i : i + batch_size] for i in range(0, len(dates), batch_size)] ================================================ FILE: gs_quant/markets/position_set.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import logging import math from time import time from typing import Dict, List, Union, Optional import numpy as np import pandas as pd from pydash import get from gs_quant.api.gs.assets import GsAssetApi from gs_quant.api.gs.price import GsPriceApi from gs_quant.common import ( Position as CommonPosition, PositionPriceInput, PositionSet as CommonPositionSet, PositionTag as PositionTagTarget, Currency, PositionSetWeightingStrategy, MarketDataFrequency, ) from gs_quant.errors import MqValueError, MqRequestError from gs_quant.markets.position_set_utils import ( _get_asset_temporal_xrefs, _group_temporal_xrefs_into_discrete_time_ranges, _resolve_many_assets, ) from gs_quant.models.risk_model_utils import _repeat_try_catch_request from gs_quant.target.positions_v2_pricing import ( PositionsPricingParameters, PositionsRequest, PositionSetRequest, PositionsPricingRequest, ) from gs_quant.target.price import PriceParameters, PositionSetPriceInput, PositionPriceResponse _logger = logging.getLogger(__name__) class PositionTag(PositionTagTarget): @classmethod def from_dict(cls, tag_dict: Dict): if len(tag_dict) > 1: raise MqValueError('PositionTag.from_dict only accepts a single key-value pair') return cls(name=list(tag_dict.keys())[0], value=list(tag_dict.values())[0]) class Position: def __init__( self, identifier: str, weight: float = None, quantity: float = None, notional: float = None, name: str = None, asset_id: str = None, tags: Optional[List[Union[PositionTag, Dict]]] = None, ): self.__identifier = identifier self.__weight = weight self.__quantity = quantity self.__notional = notional self.__name = name self.__asset_id = asset_id if tags is not None: self.__tags = [PositionTag.from_dict(tag) if isinstance(tag, dict) else tag for tag in tags] else: self.__tags = tags self.__restricted, self.__hard_to_borrow = None, None def __eq__(self, other) -> bool: if not isinstance(other, Position): return False for prop in ['asset_id', 'weight', 'notional', 'quantity', 'tags']: # Calculations from V2 sometimes add insignificant decimals places slf = get(self, prop) oth = get(other, prop) if prop in ['weight', 'notional', 'quantity']: if not (slf is None or oth is None) and not round(slf, 5) == round(oth, 5): return False elif not (slf is None and oth is None) and not slf == oth: return False return True def __hash__(self): return hash(self.asset_id) ^ hash(self.identifier) @property def identifier(self) -> str: return self.__identifier @identifier.setter def identifier(self, value: str): self.__identifier = value @property def weight(self) -> float: return self.__weight @weight.setter def weight(self, value: float): self.__weight = value @property def quantity(self) -> float: return self.__quantity @quantity.setter def quantity(self, value: float): self.__quantity = value @property def notional(self) -> float: return self.__notional @notional.setter def notional(self, value: float): self.__notional = value @property def name(self) -> str: return self.__name @name.setter def name(self, value: str): self.__name = value @property def asset_id(self) -> str: return self.__asset_id @asset_id.setter def asset_id(self, value: str): self.__asset_id = value @property def tags(self) -> List[PositionTag]: return self.__tags @tags.setter def tags(self, value: List[PositionTag]): self.__tags = value @property def hard_to_borrow(self) -> bool: return self.__hard_to_borrow @hard_to_borrow.setter def _hard_to_borrow(self, value: bool): self.__hard_to_borrow = value @property def restricted(self) -> bool: return self.__restricted @restricted.setter def _restricted(self, value: bool): self.__restricted = value def add_tag(self, name: str, value: str): if self.tags is None: self.tags = [] if not any(tag.name == name for tag in self.tags): self.tags.append(PositionTag(name=name, value=value)) else: raise MqValueError(f'Position already has tag with name {name}') def tags_as_dict(self): return {tag.name: tag.value for tag in self.tags} def as_dict(self, tags_as_keys: bool = False) -> Dict: position_dict = dict( identifier=self.identifier, weight=self.weight, quantity=self.quantity, notional=self.notional, name=self.name, asset_id=self.asset_id, restricted=self.restricted, ) if self.tags and tags_as_keys: position_dict.update(self.tags_as_dict()) else: position_dict['tags'] = self.tags return {k: v for k, v in position_dict.items() if v is not None} @classmethod def from_dict(cls, position_dict: Dict, add_tags: bool = True): fields = [k.lower() for k in position_dict.keys()] if 'id' in fields and 'asset_id' in fields: raise MqValueError('Position cannot have both id and asset_id') if 'id' in fields: position_dict['asset_id'] = position_dict.pop('id') position_fields = ['identifier', 'weight', 'quantity', 'notional', 'name', 'asset_id'] tag_dict = {k: v for k, v in position_dict.items() if k not in position_fields} return cls( identifier=position_dict['identifier'], weight=position_dict.get('weight'), notional=position_dict.get('notional'), quantity=position_dict.get('quantity'), name=position_dict.get('name'), asset_id=position_dict.get('asset_id'), tags=[PositionTag(name=k, value=v) for k, v in tag_dict.items()] if add_tags else position_dict.get('tags'), ) def clone(self): return Position.from_dict(self.as_dict(tags_as_keys=True), add_tags=True) def to_target(self, common: bool = True) -> Union[CommonPosition, PositionPriceInput]: """Returns Position type defined in target file for API payloads""" if common: tags_as_target = self.tags if self.tags else None return CommonPosition(self.asset_id, quantity=self.quantity, tags=tags_as_target) return PositionPriceInput(self.asset_id, quantity=self.quantity, weight=self.weight, notional=self.notional) class PositionSet: """ Position Sets hold a collection of positions associated with a particular date """ def __init__( self, positions: List[Position], date: dt.date = dt.date.today(), divisor: float = None, reference_notional: float = None, unresolved_positions: List[Position] = None, unpriced_positions: List[Position] = None, ): if reference_notional is not None: for p in positions: if p.weight is None: raise MqValueError('Position set with reference notionals must have weights for every position.') if p.notional is not None: raise MqValueError('Position sets with reference notionals cannot have positions with notional.') if p.quantity is not None: raise MqValueError('Position sets with reference notionals cannot have positions with quantities.') self.__positions = positions self.__date = date self.__divisor = divisor self.__reference_notional = reference_notional self.__unresolved_positions = unresolved_positions if unresolved_positions is not None else [] self.__unpriced_positions = unpriced_positions if unpriced_positions is not None else [] def __eq__(self, other) -> bool: if len(self.positions) != len(other.positions): return False if self.date != other.date: return False if self.reference_notional != other.reference_notional: return False positions = self.positions positions.sort(key=lambda position: position.asset_id) other_positions = other.positions other_positions.sort(key=lambda position: position.asset_id) for i in range(0, len(positions)): if positions[i] != other_positions[i]: return False return True @property def positions(self) -> List[Position]: return self.__positions @positions.setter def positions(self, value: List[Position]): self.__positions = value @property def date(self) -> dt.date: return self.__date @date.setter def date(self, value: dt.date): self.__date = value @property def divisor(self) -> float: return self.__divisor @property def reference_notional(self) -> float: return self.__reference_notional @reference_notional.setter def reference_notional(self, value: float): self.__reference_notional = value @property def unresolved_positions(self) -> List[Position]: return self.__unresolved_positions @property def unpriced_positions(self) -> List[Position]: return self.__unpriced_positions def clone(self, keep_reference_notional: bool = False): """Create a clone of the current position set :param keep_reference_notional: Whether to keep the reference notional of the original position set in case it has both quantity and reference notional """ frame = self.to_frame(add_tags=True) ref_notional = self.reference_notional if 'quantity' in frame.columns and ref_notional is not None: if keep_reference_notional: frame = frame.drop(columns=['quantity']) else: ref_notional = None return PositionSet.from_frame( frame, date=self.date, reference_notional=ref_notional, divisor=self.divisor, add_tags=True ) def get_positions(self) -> pd.DataFrame: """ Retrieve formatted positions :return: DataFrame of positions for position set **Usage** View position set position info **Examples** Get position set positions: >>> from gs_quant.markets.position_set import Position, PositionSet >>> >>> my_positions = [Position(identifier='AAPL UW'), Position(identifier='MSFT UW')] >>> position_set = PositionSet(positions=my_positions) >>> position_set.get_positions() **See also** :func:`get_unresolved_positions` :func:`get_unpriced_positions` :func:`resolve` :func:`price` """ positions = [p.as_dict() for p in self.positions] return pd.DataFrame(positions) def get_unresolved_positions(self) -> pd.DataFrame: """ Retrieve formatted unresolved positions :return: DataFrame of unresolved positions for position set **Usage** View position set unresolved position info **Examples** Get position set unresolved positions: >>> from gs_quant.markets.position_set import Position, PositionSet >>> >>> my_positions = [Position(identifier='AAPL UW'), Position(identifier='MSFT UW')] >>> position_set = PositionSet(positions=my_positions) >>> position_set.resolve() >>> position_set.get_unresolved_positions() **See also** :func:`get_positions` :func:`get_unpriced_positions` :func:`resolve` :func:`price` """ positions = [p.as_dict() for p in self.unresolved_positions] return pd.DataFrame(positions) def remove_unresolved_positions(self): """ Remove unresolved positions from your position set **Usage** Remove unresolved positions from your position set **Examples** Remove unresolved positions from your position set: >>> from gs_quant.markets.position_set import Position, PositionSet >>> >>> my_positions = [Position(identifier='AAPL UW'), Position(identifier='MSFT UW')] >>> position_set = PositionSet(positions=my_positions) >>> position_set.resolve() >>> position_set.remove_unresolved_positions() **See also** :func:`get_positions` :func:`get_unpriced_positions` :func:`resolve` :func:`price` :func:`remove_unpriced_positions` :func:`get_unresolved_positions` """ self.positions = [p for p in self.positions if p.asset_id is not None] self.__unresolved_positions = None def get_unpriced_positions(self) -> pd.DataFrame: """ Retrieve formatted unpriced positions :return: DataFrame of unpriced positions for position set **Usage** View position set unpriced position info **Examples** Get position set unpriced positions: >>> import datetime as dt >>> from gs_quant.markets.position_set import Position, PositionSet >>> >>> my_positions = [Position(identifier='AAPL UW', quantity=100), Position(identifier='MSFT UW', quantity=100)] >>> position_set = PositionSet(positions=my_positions) >>> position_set.resolve() >>> position_set.price() >>> position_set.get_unpriced_positions() **See also** :func:`get_positions` :func:`get_unresolved_positions` :func:`resolve` :func:`price` """ positions = [p.as_dict() for p in self.unpriced_positions] return pd.DataFrame(positions) def remove_unpriced_positions(self): """ Remove unpriced positions from your position set **Usage** Remove unpriced positions from your position set **Examples** Remove unpriced positions from your position set: >>> from gs_quant.markets.position_set import Position, PositionSet >>> >>> my_positions = [Position(identifier='AAPL UW'), Position(identifier='MSFT UW')] >>> position_set = PositionSet(positions=my_positions) >>> position_set.resolve() >>> position_set.remove_unpriced_positions() **See also** :func:`get_positions` :func:`get_unpriced_positions` :func:`resolve` :func:`price` :func:`get_unresolved_positions` :func:`remove_unresolved_positions` """ self.__unpriced_positions = None def get_restricted_positions(self) -> pd.DataFrame: """ Retrieve formatted RTL positions :return: DataFrame of RTL positions for position set **Usage** View position set RTL position info **Examples** Get position set RTL positions: >>> import datetime as dt >>> from gs_quant.markets.position_set import Position, PositionSet >>> >>> my_positions = [Position(identifier='AAPL UW', quantity=100), Position(identifier='MSFT UW', quantity=100)] >>> position_set = PositionSet(positions=my_positions) >>> position_set.resolve() >>> position_set.get_restricted_positions() **See also** :func:`get_positions` :func:`resolve` :func:`price` :func:`remove_restricted_positions` :func:`get_hard_to_borrow_positions` :func:`remove_hard_to_borrow_positions` """ positions = [p.as_dict() for p in self.positions if p.restricted] return pd.DataFrame(positions) def remove_restricted_positions(self): """ Remove RTL positions from your position set **Usage** Remove RTL positions from your position set **Examples** Remove RTL positions from your position set: >>> from gs_quant.markets.position_set import Position, PositionSet >>> >>> my_positions = [Position(identifier='AAPL UW'), Position(identifier='MSFT UW')] >>> position_set = PositionSet(positions=my_positions) >>> position_set.resolve() >>> position_set.remove_restricted_positions() **See also** :func:`get_positions` :func:`resolve` :func:`price` :func:`get_restricted_positions` :func:`get_hard_to_borrow_positions` :func:`remove_hard_to_borrow_positions` """ self.positions = [p for p in self.positions if p.restricted is not True] def get_hard_to_borrow_positions(self) -> pd.DataFrame: """ Retrieve formatted htb positions :return: DataFrame of htb positions for position set **Usage** View position set htb position info **Examples** Get position set htb positions: >>> import datetime as dt >>> from gs_quant.markets.position_set import Position, PositionSet >>> >>> my_positions = [Position(identifier='AAPL UW', quantity=100), Position(identifier='MSFT UW', quantity=100)] >>> position_set = PositionSet(positions=my_positions) >>> position_set.resolve() >>> position_set.price() >>> position_set.get_hard_to_borrow_positions() **See also** :func:`get_positions` :func:`resolve` :func:`price` :func:`get_restricted_positions` :func:`remove_restricted_positions` :func:`remove_hard_to_borrow_positions` """ positions = [p.as_dict() for p in self.positions if p.hard_to_borrow] return pd.DataFrame(positions) def remove_hard_to_borrow_positions(self): """ Remove hard to borrow positions from your position set **Usage** Remove hard to borrow positions from your position set **Examples** Remove hard to borrow positions from your position set: >>> from gs_quant.markets.position_set import Position, PositionSet >>> >>> my_positions = [Position(identifier='AAPL UW'), Position(identifier='MSFT UW')] >>> position_set = PositionSet(positions=my_positions) >>> position_set.resolve() >>> position_set.price() >>> position_set.remove_hard_to_borrow_positions() **See also** :func:`get_positions` :func:`resolve` :func:`price` :func:`get_restricted_positions` :func:`remove_restricted_positions` :func:`get_hard_to_borrow_positions` """ self.positions = [p for p in self.positions if p.hard_to_borrow is not True] def equalize_position_weights(self): """ Assigns equal weight to each position in position set **Usage** Assigns equal weight to each position in position set **Examples** Assign equal weight to each position in position set: >>> from gs_quant.markets.position_set import Position, PositionSet >>> >>> my_positions = [Position(identifier='AAPL UW'), Position(identifier='MSFT UW')] >>> position_set = PositionSet(positions=my_positions) >>> position_set.equalize_position_weights() **See also** :func:`get_positions` :func:`redistribute_weights` """ weight = 1 / len(self.positions) equally_weighted_positions = [] for p in self.positions: p.weight = weight p.quantity = None p.notional = None equally_weighted_positions.append(p) self.positions = equally_weighted_positions def to_frame(self, add_tags: bool = False) -> pd.DataFrame: """ Retrieve formatted position set :return: DataFrame of position set info **Usage** View position set info **Examples** Retrieve formatted position set: >>> from gs_quant.markets.position_set import Position, PositionSet >>> >>> my_positions = [Position(identifier='AAPL UW', quantity=100), Position(identifier='MSFT UW', quantity=100)] >>> position_set = PositionSet(positions=my_positions) >>> position_set.to_frame() Retrieve tags in the pd DataFrame: >>> from gs_quant.markets.position_set import PositionSet >>> pset = PositionSet.from_dicts([ >>> {'identifier': 'AAPL UW', 'quantity': 100, 'MyTag': 'Name 1'}, >>> {'identifier': 'AAPL UW', 'quantity': 100, 'MyTag': 'Name 2'}, >>> {'identifier': 'META UW', 'quantity': 100, 'MyTag': 'Name 1'} >>> ], add_tags=True) >>> pset.to_frame(add_tags=True) **See also** :func:`from_frame` :func:`from_dicts` :func:`from_list` """ positions = [] for p in self.positions: position = dict(date=self.date.isoformat()) if self.divisor is not None: position.update(dict(divisor=self.divisor)) position.update(p.as_dict(tags_as_keys=add_tags)) positions.append(position) return pd.DataFrame(positions) def resolve(self, **kwargs): """ Resolve any unmapped positions **Usage** Resolve any unmapped positions **Examples** Resolve any unmapped positions: >>> from gs_quant.markets.position_set import Position, PositionSet >>> >>> my_positions = [Position(identifier='AAPL UW'), Position(identifier='MSFT UW')] >>> position_set = PositionSet(positions=my_positions) >>> position_set.resolve() **See also** :func:`get_positions` :func:`get_unresolved_positions` :func:`get_unpriced_positions` :func:`price` """ unresolved_positions = [p.identifier for p in self.positions if p.asset_id is None] if len(unresolved_positions): [id_map, unresolved_positions] = self.__resolve_identifiers(unresolved_positions, self.date, **kwargs) self.__unresolved_positions = [p for p in self.positions if p.identifier in unresolved_positions] resolved_positions = [] for p in self.positions: if p.identifier in id_map: asset = get(id_map, p.identifier.replace('.', '\\.')) p.asset_id = get(asset, 'id') p.name = get(asset, 'name') p._restricted = get(asset, 'restricted') if p.asset_id is not None: resolved_positions.append(p) self.positions = resolved_positions def redistribute_weights(self): """ Redistribute position weights proportionally for a one-sided position set **Usage** Redistribute position weights proportionally for a one-sided position set **Examples** Redistribute position weights proportionally for a one-sided position set: >>> from gs_quant.markets.position_set import Position, PositionSet >>> >>> my_positions = [Position(identifier='AAPL UW', weight=0.3), Position(identifier='MSFT UW', weight=0.3)] >>> position_set = PositionSet(positions=my_positions) >>> position_set.redistribute_weights() **See also** :func:`get_positions` :func:`equalize_position_weights` :func:`get_unpriced_positions` :func:`price` """ total_weight = 0 new_weights, unweighted = [], [] for p in self.positions: if p.weight is None: unweighted.append(p.identifier) else: total_weight += p.weight if len(unweighted): raise MqValueError(f'Cannot reweight as some positions are missing weights: {unweighted}') weight_to_distribute = 1 - total_weight if total_weight < 0 else total_weight - 1 for p in self.positions: p.weight = p.weight - (p.weight / total_weight) * weight_to_distribute p.quantity = None p.notional = None new_weights.append(p) self.positions = new_weights def price( self, currency: Optional[Currency] = Currency.USD, use_unadjusted_close_price: bool = True, weighting_strategy: Optional[PositionSetWeightingStrategy] = None, handle_long_short: bool = False, fail_on_unpriced_positions: bool = False, **kwargs, ): """ Fetch positions weights from quantities, or vice versa :param currency: Reference notional currency (defaults to USD if not passed in) :param use_unadjusted_close_price: Use adjusted or unadjusted close prices (defaults to unadjusted) :param weighting_strategy: Quantity or Weighted weighting strategy (defaults based on positions info) :param handle_long_short: Whether to handle the loss of directionality in weights that comes from pricing using gross notional. Useful when input position iset is a long/short. Note, this also sets the reference notional to Gross Notional if not already so :param fail_on_unpriced_positions: Whether to raise an exception if any positions are unpriced :param kwargs: Additional parameters to pass to the pricing API **Usage** Fetch positions weights from quantities, or vice versa **Examples** Fetch position weights from quantities: >>> import datetime as dt >>> from gs_quant.markets.position_set import Position, PositionSet, PositionSetWeightingStrategy >>> >>> my_positions = [Position(identifier='AAPL UW', quantity=100), Position(identifier='MSFT UW', quantity=100)] >>> position_set = PositionSet(positions=my_positions, date= dt.date(2023, 3, 16)) >>> position_set.resolve() >>> position_set.price(weighting_strategy=PositionSetWeightingStrategy.Quantity) Fetch position quantities from weights: >>> import datetime as dt >>> from gs_quant.markets.position_set import Position, PositionSet, PositionSetWeightingStrategy >>> >>> my_positions = [Position(identifier='AAPL UW', weight=0.5), Position(identifier='MSFT UW', weight=0.5)] >>> position_set = PositionSet(positions=my_positions, date= dt.date(2023, 3, 16), reference_notional=10000000) >>> position_set.resolve() >>> position_set.price(weighting_strategy=PositionSetWeightingStrategy.Weight) Fetch position weights from exposures: >>> import datetime as dt >>> from gs_quant.markets.position_set import Position, PositionSet, PositionSetWeightingStrategy >>> >>> my_positions = [Position(identifier='AAPL UW', notional=10000), >>> Position(identifier='MSFT UW', notional=10000)] >>> position_set = PositionSet(positions=my_positions, date= dt.date(2023, 3, 16)) >>> position_set.resolve() >>> position_set.price(weighting_strategy=PositionSetWeightingStrategy.Notional) **See also** :func:`get_unpriced_positions` :func:`get_unresolved_positions` :func:`resolve` """ weighting_strategy = self.__get_default_weighting_strategy( self.positions, self.reference_notional, weighting_strategy ) positions = self.__convert_positions_for_pricing(self.positions, weighting_strategy) if 'fractional_shares' not in kwargs: should_allow_fractional_shares = ( True if weighting_strategy == PositionSetWeightingStrategy.Notional else False ) else: should_allow_fractional_shares = kwargs.pop('fractional_shares') price_parameters = PriceParameters( currency=currency, divisor=self.divisor, frequency=MarketDataFrequency.End_Of_Day, target_notional=self.reference_notional, notional_type='Gross', pricing_date=self.date, price_regardless_of_assets_missing_prices=True, weighting_strategy=weighting_strategy, use_unadjusted_close_price=use_unadjusted_close_price, fractional_shares=should_allow_fractional_shares, ) if 'dataset' in kwargs: price_parameters.asset_data_set_id = kwargs['dataset'] price_parameters.frequency = None for k, v in kwargs.items(): price_parameters.__setattr__(k, v) results = GsPriceApi.price_positions(PositionSetPriceInput(positions=positions, parameters=price_parameters)) position_result_map = {f'{p.asset_id}{self.__hash_position_tag_list(p.tags)}': p for p in results.positions} priced_positions, unpriced_positions = [], [] for p in self.positions: asset_key = f'{p.asset_id}{self.__hash_position_tag_list(p.tags)}' if asset_key in position_result_map: pos: PositionPriceResponse = position_result_map.get(asset_key) p.quantity = pos.quantity w = pos.weight p.notional = pos.notional if handle_long_short: # In case of long/short positions, we need to convert the returned gross weight to reference weight w = math.copysign(w, pos.notional) p.weight = w p._hard_to_borrow = pos.hard_to_borrow priced_positions.append(p) else: unpriced_positions.append(p) if fail_on_unpriced_positions and unpriced_positions: raise MqValueError( f'Failed to price positions: ' f'{", ".join([p.identifier for p in unpriced_positions])} on {self.date}. Please' f'contat Marquee Support for assistance.' ) self.positions = priced_positions self.__unpriced_positions = unpriced_positions if handle_long_short: # Set notional to gross notional because in case of L/S pricing the API normalizes all weights wrt gross self.reference_notional = results.gross_notional def get_subset(self, copy: bool = True, **kwargs): """Extract a subset of the position set based on values of tags. Not that weights are returned with respect to original position set. Use redistribute_weights function. For more advanced filtering, use .to_frame() to get the frame as a pandas DataFrame. **Usage** Given a position set that has tags, extract a subset of the positions based on the values of one or more of the tags. **Examples** Extract a subset of the position set based on the value of a single tag: >>> from gs_quant.markets.position_set import Position, PositionSet >>> from gs_quant.target.common import PositionTag >>> pset = PositionSet.from_dicts([ >>> {'identifier': 'AAPL UW', 'quantity': 1000, 'MyTag': 'Name 1', 'MyOtherTag': 'Class 1'}, >>> {'identifier': 'MSFT UW', 'quantity': 2000, 'MyTag': 'Name 2', 'MyOtherTag': 'Class 1'}, >>> {'identifier': 'GOOGL UW', 'quantity': 3000, 'MyTag': 'Name 1', 'MyOtherTag': 'Class 2'} >>> ]) >>> >>> subset = pset.get_subset(MyTag='Name 1') Extract a subset of the position set based on the values of multiple tags: >>> subset = pset.get_subset(MyTag='Name 1', MyOtherTag='Class 2') """ subset = [] for p in self.positions: if not p.tags: raise MqValueError(f'PositionSet has position {p.identifier} that does not have tags') tags_dict = p.tags_as_dict() if all(tags_dict.get(k) == v for k, v in kwargs.items()): subset.append(p if not copy else p.clone()) return PositionSet(positions=subset, date=self.date, reference_notional=self.reference_notional) def to_target(self, common: bool = True) -> Union[CommonPositionSet, List[PositionPriceInput]]: """Returns PostionSet type defined in target file for API payloads""" positions = tuple(p.to_target(common) for p in self.positions) return CommonPositionSet(positions, self.date) if common else list(positions) @classmethod def from_target(cls, position_set: CommonPositionSet, source: Optional[str] = None): """Create PostionSet instance from PostionSet type defined in target file""" positions = position_set.positions mqids = [position.asset_id for position in positions] position_data = cls.__get_positions_data(mqids, source=source) converted_positions = [] for p in positions: asset = get(position_data, p.asset_id) tags = p.tags if p.tags else None position = Position( identifier=get(asset, 'bbid'), name=get(asset, 'name'), asset_id=p.asset_id, quantity=p.quantity, tags=tags, ) converted_positions.append(position) return cls(converted_positions, position_set.position_date, position_set.divisor) @classmethod def from_list(cls, positions: List[str], date: dt.date = dt.date.today()): """ Create equally-weighted PostionSet instance from a list of identifiers **Usage** Create equally-weighted PostionSet instance from a list of identifiers **Examples** Create equally-weighted PostionSet instance from a list of identifiers: >>> from gs_quant.markets.position_set import PositionSet >>> >>> identifiers = ['AAPL UW', 'MSFT UW'] >>> position_set = PositionSet.from_list(positions=identifiers) **See also** :func:`get_positions` :func:`resolve` :func:`from_dicts` :func:`from_frame` :func:`to_frame` """ weight = 1 / len(positions) converted_positions = [Position(identifier=p, weight=weight) for p in positions] return cls(converted_positions, date) @classmethod def from_dicts( cls, positions: List[Dict], date: dt.date = dt.date.today(), reference_notional: float = None, add_tags: bool = False, ): """ Create PostionSet instance from a list of position-object-like dictionaries **Usage** Create PostionSet instance from a list of position-object-like dictionaries **Examples** Create PostionSet instance from a list of position-object-like dictionaries: >>> from gs_quant.markets.position_set import PositionSet >>> >>> my_positions = [{'identifier': 'AAPL UW', 'weight': 0.5}, {'identifier': 'AAPL UW', 'weight': 0.5}] >>> position_set = PositionSet.from_dicts(positions=my_positions) **See also** :func:`get_positions` :func:`resolve` :func:`from_list` :func:`from_frame` :func:`to_frame` """ positions_df = pd.DataFrame(positions) return cls.from_frame(positions_df, date, reference_notional, add_tags=add_tags) @classmethod def from_frame( cls, positions: pd.DataFrame, date: dt.date = dt.date.today(), reference_notional: float = None, divisor: float = None, add_tags: bool = False, ): """ Create PostionSet instance from a dataframe of positions **Usage** Create PostionSet instance from a dataframe of positions **Examples** Create PostionSet instance from a dataframe of positions: >>> import pandas as pd >>> from gs_quant.markets.position_set import PositionSet >>> >>> my_positions = [{'identifier': 'AAPL UW', 'weight': 0.5}, {'identifier': 'AAPL UW', 'weight': 0.5}] >>> positions_df = pd.DataFrame(my_positions) >>> position_set = PositionSet.from_frame(positions=positions_df) **See also** :func:`get_positions` :func:`resolve` :func:`from_list` :func:`from_dicts` :func:`to_frame` """ positions.columns = cls.__normalize_position_columns(positions) tag_columns = cls.__get_tag_columns(positions) if add_tags else [] positions = positions[~positions['identifier'].isna()] equalize = not ( 'quantity' in positions.columns.str.lower() or 'weight' in positions.columns.str.lower() or 'notional' in positions.columns.str.lower() ) equal_weight = 1 / len(positions) positions_list = [] for row in positions.to_dict(orient='records'): positions_list.append( Position( identifier=row.get('identifier'), asset_id=row.get('id'), name=row.get('name'), weight=equal_weight if equalize else row.get('weight'), quantity=None if equalize else row.get('quantity'), notional=None if equalize else row.get('notional'), tags=list(PositionTag(tag, get(row, tag)) for tag in tag_columns) if len(tag_columns) else None, ) ) return cls(positions_list, date, reference_notional=reference_notional, divisor=divisor) @staticmethod def __get_tag_columns(positions: pd.DataFrame) -> List[str]: return [ c for c in positions.columns if c.lower() not in ['identifier', 'id', 'quantity', 'notional', 'weight', 'date', 'restricted'] ] @staticmethod def __normalize_position_columns(positions: pd.DataFrame) -> List[str]: columns = [] if 'asset_id' in positions.columns and 'id' not in positions.columns: positions = positions.rename(columns={'asset_id': 'id'}) for c in positions.columns: columns.append( c.lower() if c.lower() in ['identifier', 'id', 'quantity', 'notional', 'weight', 'date', 'restricted'] else c ) return columns @staticmethod def __resolve_identifiers(identifiers: List[str], date: dt.date, **kwargs) -> List: unmapped_assets = [] id_map = {} batch_size = 500 logging.debug(f'Resolving positions in {len(identifiers) / batch_size} batches') for i in range(0, len(identifiers), batch_size): identifier_batch = identifiers[i : i + batch_size] response = GsAssetApi.resolve_assets( identifier=identifier_batch, fields=['name', 'id', 'tradingRestriction'], limit=1, as_of=date, **kwargs ) for identifier in response: if response[identifier] is not None and len(response[identifier]) > 0: id_map[identifier] = { 'id': response[identifier][0]['id'], 'name': response[identifier][0]['name'], 'restricted': response[identifier][0].get('tradingRestriction'), } else: unmapped_assets.append(identifier) if len(unmapped_assets) > 0: logging.info( f'Error in resolving the following identifiers: {unmapped_assets}. Sifting them out and ' f'resolving the rest...' ) return [id_map, unmapped_assets] @staticmethod def __get_positions_data(mqids: List[str], source: Optional[str] = None) -> Dict: response = GsAssetApi.get_many_assets_data(id=mqids, fields=['id', 'name', 'bbid'], source=source) data = {} for asset in response: data[get(asset, 'id')] = dict(name=get(asset, 'name'), bbid=get(asset, 'bbid')) return data @staticmethod def __get_default_weighting_strategy( positions: List[Position], reference_notional: float = None, weighting_strategy: Optional[PositionSetWeightingStrategy] = None, ) -> PositionSetWeightingStrategy: missing_weights = [p.identifier for p in positions if p.weight is None] missing_quantities = [p.identifier for p in positions if p.quantity is None] missing_exposures = [p.identifier for p in positions if p.notional is None] if weighting_strategy is None: if len(missing_weights) and len(missing_quantities) and len(missing_exposures): raise MqValueError( f'Unable to determine weighting strategy due to missing weights for \ {missing_weights}, missing quantities for {missing_quantities}, and missing exposures \ for {missing_exposures}' ) if not len(missing_weights) and (reference_notional is not None or len(missing_quantities)): weighting_strategy = PositionSetWeightingStrategy.Weight elif not len(missing_exposures): weighting_strategy = PositionSetWeightingStrategy.Notional else: weighting_strategy = PositionSetWeightingStrategy.Quantity use_quantity = weighting_strategy == PositionSetWeightingStrategy.Quantity use_weight = weighting_strategy == PositionSetWeightingStrategy.Weight use_exposure = weighting_strategy == PositionSetWeightingStrategy.Notional if ( (use_weight and len(missing_weights)) or (use_quantity and len(missing_quantities)) or (use_exposure and len(missing_exposures)) ): raise MqValueError( f'You must input a {weighting_strategy.value} for the following positions: \ {missing_weights if use_weight else (missing_exposures if use_exposure else missing_quantities)}' ) if use_weight and reference_notional is None: raise MqValueError('You must specify a reference notional in order to price by weight.') return weighting_strategy @staticmethod def __convert_positions_for_pricing( positions: List[Position], weighting_strategy: PositionSetWeightingStrategy ) -> List[PositionPriceInput]: position_inputs, missing_ids = [], [] use_quantity = weighting_strategy == PositionSetWeightingStrategy.Quantity use_weight = weighting_strategy == PositionSetWeightingStrategy.Weight use_exposure = weighting_strategy == PositionSetWeightingStrategy.Notional for p in positions: if p.asset_id is None: missing_ids.append(p.identifier) else: position_inputs.append( PositionPriceInput( asset_id=p.asset_id, weight=p.weight if use_weight else None, notional=p.notional if use_exposure else None, quantity=p.quantity if use_quantity else None, tags=p.tags, ) ) if len(missing_ids): raise MqValueError( f'Positions: {missing_ids} are missing asset ids. Resolve your position \ set or remove unmapped identifiers.' ) return position_inputs @staticmethod def __hash_position_tag_list(position_tags: List[PositionTag]) -> str: hashed_results = '' if position_tags is not None: for tag in position_tags: hashed_results = hashed_results + tag.name + '-' + tag.value return hashed_results @staticmethod def to_frame_many(position_sets: List['PositionSet']) -> pd.DataFrame: """Returns dataframe of position sets""" position_sets = pd.DataFrame(position_sets, columns=["position_sets"]) for field in ['date', 'divisor', 'reference_notional']: position_sets[field] = [getattr(pos, field, None) for pos in position_sets['position_sets']] position_sets['positions'] = [pos.positions for pos in position_sets['position_sets']] position_sets = position_sets[position_sets['positions'].apply(lambda x: len(x) > 0)] position_sets = position_sets.explode('positions') position_sets['positions'] = [pos.as_dict() for pos in position_sets['positions']] for field in ['name', 'asset_id', 'identifier', 'weight', 'notional', 'restricted', 'quantity', 'tags']: position_sets[field] = [pos.get(field) for pos in position_sets['positions']] columns_to_drop = ["position_sets", "positions"] position_sets = position_sets.drop(columns=columns_to_drop) return position_sets @staticmethod @np.vectorize def __build_positions_from_frame( names: pd.Series = None, identifiers: pd.Series = None, asset_ids: pd.Series = None, weights: pd.Series = None, notionals: pd.Series = None, quantities: pd.Series = None, restricted: pd.Series = None, hard_to_borrow: pd.Series = None, tags: pd.Series = None, ): position = Position( asset_id=asset_ids, name=names, identifier=identifiers, weight=weights if weights else None, notional=notionals if notionals else None, quantity=quantities if quantities else None, tags=tags, ) position._restricted = restricted position._hard_to_borrow = hard_to_borrow return position @classmethod def resolve_many(cls, position_sets: List['PositionSet'], **kwargs): """ Resolve positions on each holding date into Marquee assets. Positions sets will be updated inplace. Each resolved position will have a unique Marquee ID. :param position_sets: Positions sets in a list. :param kwargs: Additional parameters to send to the GS Resolver API. **Usage** >>> from gs_quant.markets.position_set import PositionSet, Position, PositionTag >>> import datetime as dt >>> import pandas as pd The input to the function can be a list of `PositionSet` object. >>> position_set_list = [ ... PositionSet(date=dt.date(2024, 5, 1), ... reference_notional=1000, ... positions=[Position(identifier='GS UN', ... weight=0.5, ... tags=[PositionTag(name="tag1", value="tagvalue1")]), ... Position(identifier='AAPL UW', ... weight=0.5, ... ags=[PositionTag(name="tag2", value="tagvalue2")])]), ... PositionSet(date=dt.date(2024, 5, 1), ... reference_notional=1000, ... positions=[Position(identifier='GS UN', ... weight=0.5, ... tags=[PositionTag(name="tag1", value="tagvalue1")]), ... Position(identifier='AAPL UW', ... weight=0.5, ... tags=[PositionTag(name="tag2", value="tagvalue2")])]) ... ] >>> PositionSet.resolve_many(position_set_list) **See also** :func:`price_many` """ position_sets_df = cls.to_frame_many(position_sets) if "name" in position_sets_df.columns.tolist(): position_sets_df = position_sets_df.drop(columns="name") if "asset_id" in position_sets_df.columns.tolist(): position_sets_df = position_sets_df.drop(columns="asset_id") position_sets_df = position_sets_df.dropna(how='all', axis=1) position_sets_attributes = position_sets_df.columns.tolist() if "quantity" in position_sets_attributes and "weight" in position_sets_attributes: raise MqValueError("Cannot have both weight and quantity in position sets") if "quantity" in position_sets_attributes and "notional" in position_sets_attributes: raise MqValueError("Cannot have both weight and notional in position sets") asset_temporal_xrefs_df, asset_identifier_type = _get_asset_temporal_xrefs(position_sets_df) _group_temporal_xrefs_into_discrete_time_ranges(asset_temporal_xrefs_df) resolved_assets_results_df = _resolve_many_assets(asset_temporal_xrefs_df, asset_identifier_type, **kwargs) position_sets_df = pd.merge( position_sets_df, resolved_assets_results_df[ ["assetId", asset_identifier_type, "name", "asOfDate", "tradingRestriction", "startDate", "endDate"] ], how="left", left_on="identifier", right_on=asset_identifier_type, ) position_sets_df["date"] = pd.to_datetime(position_sets_df["date"]) # Fill N/A for startDate and endDate so they are not filtered out (for instance if some positions # did not have xrefs or were not resolved. These should be included in the unresolved positions group" position_sets_df['startDate'] = position_sets_df['startDate'].fillna(position_sets_df['date']) position_sets_df['endDate'] = position_sets_df['endDate'].fillna(position_sets_df['date']) position_sets_df = position_sets_df[ (position_sets_df["startDate"] <= position_sets_df["date"]) & (position_sets_df["date"] <= position_sets_df["endDate"]) ] position_sets_df = ( position_sets_df.drop(columns=[asset_identifier_type, "asOfDate", "startDate", "endDate"]) .rename(columns={"tradingRestriction": "restricted"}) .fillna(np.nan) .replace([np.nan], [None]) ) # Build position sets if 'reference_notional' in position_sets_df.columns.tolist(): if "quantity" in position_sets_df.columns.tolist(): position_sets_df = position_sets_df.drop(columns='quantity') weights_df = position_sets_df['weight'] if 'weight' in position_sets_df.columns.tolist() else None quantities_df = position_sets_df['quantity'] if 'quantity' in position_sets_df.columns.tolist() else None notionals_df = position_sets_df['notional'] if 'notional' in position_sets_df.columns.tolist() else None tags_df = position_sets_df['tags'] if 'tags' in position_sets_df.columns.tolist() else None all_positions = cls.__build_positions_from_frame( names=position_sets_df['name'], identifiers=position_sets_df['identifier'], asset_ids=position_sets_df['assetId'], weights=weights_df, notionals=notionals_df, quantities=quantities_df, restricted=position_sets_df['restricted'], tags=tags_df, ) position_sets_df['positions'] = all_positions position_sets_grouped_by_date = position_sets_df.groupby('date') for position_set in position_sets: if not isinstance(position_set.date, dt.date): position_set.date = pd.Timestamp(position_set.date).to_pydatetime().date() positions_on_holding_date_df = position_sets_grouped_by_date.get_group(position_set.date) position_set.positions = positions_on_holding_date_df.loc[ ~positions_on_holding_date_df['assetId'].isna(), 'positions' ].tolist() unresolved_positions = positions_on_holding_date_df.loc[ positions_on_holding_date_df['assetId'].isna(), 'positions' ].tolist() if unresolved_positions: position_set.__unresolved_positions = unresolved_positions @classmethod def price_many( cls, position_sets: List['PositionSet'], currency: Optional[Currency] = Currency.USD, weighting_strategy: PositionSetWeightingStrategy = None, carryover_positions_for_missing_dates: bool = False, should_reweight: bool = False, allow_fractional_shares: bool = False, allow_partial_pricing: bool = False, batch_size: int = 20, **kwargs, ): """Fetch position weights from quantities or vice versa for a list of position sets. This function modifies the input position sets inplace :param position_sets: Positions sets in a list. :param currency: Currency to use to price. Defaults to USD. :param weighting_strategy: The weighting strategy to use. Should be weight or quantity. If None, infers weighting strategy from positions metadata :param carryover_positions_for_missing_dates: Broadcast previous positions onto dates with missing positions. Defaults to False :param should_reweight: Ensures total weight across positions on a holding date equals 1. Defaults to False :param allow_fractional_shares: Whether to allow fractional shares. Defaults to False if the weighting strategy is Quantity or Weight. Set to True if the weighting strategy is Notional. :param allow_partial_pricing: whether to price a subset of positions in case of errors :param batch_size: Size of position sets to send to the pricing API per request. Defaults to 30 :param kwargs: Additional parameters to pass to the GS pricing API **Usage** The function expects a required input of positions sets, which can be a dataframe of the format below or a list of `PositionSet` object >>> from gs_quant.markets.position_set import PositionSet, Position, PositionTag >>> import datetime as dt >>> import pandas as pd The input to the function is a list of `PositoinSet` >>> position_set_list = [ ... PositionSet(date=dt.date(2024, 5, 1), ... reference_notional=1000, ... positions=[Position(identifier='GS UN', ... weight=0.5, ... tags=[PositionTag(name="tag1", value="tagvalue1")]), ... Position(identifier='AAPL UW', ... weight=0.5, ... tags=[PositionTag(name="tag2", value="tagvalue2")])]), ... PositionSet(date=dt.date(2024, 5, 1), ... reference_notional=1000, ... positions=[Position(identifier='GS UN', ... weight=0.5, ... tags=[PositionTag(name="tag1", value="tagvalue1")]), ... Position(identifier='AAPL UW', ... weight=0.5, ... tags=[PositionTag(name="tag2", value="tagvalue2")])]) ... ] >>> PositionSet.price_many(position_set_list) :func:`resolve_many` """ position_sets_to_price_df = cls.to_frame_many(position_sets) position_sets_to_price_df = position_sets_to_price_df.dropna(how='all', axis=1) position_sets_column_attributes = position_sets_to_price_df.columns.tolist() if "quantity" in position_sets_column_attributes and "weight" in position_sets_column_attributes: raise MqValueError("Cannot have both weight and quantity in position sets") if "notional" in position_sets_column_attributes and "weight" in position_sets_column_attributes: raise MqValueError("Cannot have both weight and notional in position sets") if not weighting_strategy: if "weight" in position_sets_column_attributes and "reference_notional" in position_sets_column_attributes: weighting_strategy = PositionSetWeightingStrategy.Weight elif "notional" in position_sets_column_attributes: weighting_strategy = PositionSetWeightingStrategy.Notional else: weighting_strategy = PositionSetWeightingStrategy.Quantity if weighting_strategy not in [ PositionSetWeightingStrategy.Quantity, PositionSetWeightingStrategy.Weight, PositionSetWeightingStrategy.Notional, ]: raise MqValueError("Can only specify a weighting strategy of weight, notional, or quantity") if ( weighting_strategy == PositionSetWeightingStrategy.Quantity and "quantity" not in position_sets_column_attributes ): raise MqValueError( "Unable to price positions without position weights and daily reference notional or position quantities" ) should_allow_fractional_shares = ( True if weighting_strategy == PositionSetWeightingStrategy.Notional else allow_fractional_shares ) position_pricing_parameters = PositionsPricingParameters( currency=currency.value, weighting_strategy=weighting_strategy.value, carryover_positions_for_missing_dates=carryover_positions_for_missing_dates, should_reweight=should_reweight, allow_fractional_shares=should_allow_fractional_shares, ) if kwargs: [setattr(position_pricing_parameters, arg, value) for arg, value in kwargs.items()] if weighting_strategy == PositionSetWeightingStrategy.Weight: positions_with_missing_weights = position_sets_to_price_df[position_sets_to_price_df['weight'].isna()] if not positions_with_missing_weights.empty: _logger.warning("Some positions do not have weights. These will be filtered out") elif weighting_strategy == PositionSetWeightingStrategy.Notional: positions_with_missing_exposures = position_sets_to_price_df[position_sets_to_price_df['notional'].isna()] if not positions_with_missing_exposures.empty: _logger.warning("Some positions do not have exposures. These will be filtered out") elif weighting_strategy == PositionSetWeightingStrategy.Quantity: positions_with_missing_quantities = position_sets_to_price_df[position_sets_to_price_df['quantity'].isna()] if not positions_with_missing_quantities.empty: _logger.warning("Some positions do not have quantities. These will be filtered out") if "weight" not in position_sets_column_attributes: position_sets_to_price_df['weight'] = None if "notional" not in position_sets_column_attributes: position_sets_to_price_df['notional'] = None if "quantity" not in position_sets_column_attributes: position_sets_to_price_df['quantity'] = None position_sets_to_price_df['positions'] = np.vectorize( lambda asset_id, weight, quantity, notional: PositionsRequest( asset_id=asset_id, weight=weight, quantity=quantity, notional=notional ) )( position_sets_to_price_df['asset_id'], position_sets_to_price_df['weight'], position_sets_to_price_df['quantity'], position_sets_to_price_df['notional'], ) # build positionSets requests position_sets_grouped_by_date = position_sets_to_price_df.groupby("date") all_pos_sets = [] for date, pos_df in position_sets_grouped_by_date: all_pos_sets.append( PositionSetRequest( date=date, positions=pos_df['positions'].tolist(), target_notional=pos_df['reference_notional'].iat[0] if weighting_strategy == PositionSetWeightingStrategy.Weight else None, ) ) batches = np.array_split(all_pos_sets, math.ceil(len(all_pos_sets) / batch_size)) all_pricing_results = [] start = time() for batch_idx, batch in enumerate(batches): _logger.info(f"Pricing batch {batch_idx} of {len(batches)}") try: payload = PositionsPricingRequest(parameters=position_pricing_parameters, position_sets=tuple(batch)) pricing_results = _repeat_try_catch_request( GsPriceApi.price_many_positions, number_retries=3, return_result=True, verbose=False, pricing_request=payload, ) all_pricing_results += pricing_results except MqRequestError as request_exception: earliest_pos_set_in_batch = batch[0] latest_pos_set_in_batch = batch[-1] _logger.error( f"An error occurred while pricing positions on holding dates " f"{earliest_pos_set_in_batch.date} to {latest_pos_set_in_batch.date}: " f"{request_exception}.Consider batching position sets or reducing the batch" f"size" ) if not allow_partial_pricing: raise request_exception _logger.info(f"Total time to price positions is {time() - start} seconds") next_start = time() date_to_priced_position_sets = { dt.datetime.strptime(pos_set.get('date'), '%Y-%m-%d').date(): pos_set for pos_set in all_pricing_results } for input_position_set in position_sets: if not isinstance(input_position_set.date, dt.date): # use pandas to infer format of date input_position_set.date = pd.to_datetime(input_position_set.date).date() if not date_to_priced_position_sets.get(input_position_set.date): input_position_set.__unpriced_positions = list(input_position_set.positions) input_position_set.positions = None continue priced_position_set = date_to_priced_position_sets.get(input_position_set.date) position_date = dt.datetime.strptime(priced_position_set.get('date'), '%Y-%m-%d').date() priced_positions_df = pd.DataFrame(priced_position_set.get('positions')) if weighting_strategy == PositionSetWeightingStrategy.Weight: column_from_initial_position_sets_to_merge_by = "weight" column_from_priced_positions_results_to_merge_by = "referenceWeight" elif weighting_strategy == PositionSetWeightingStrategy.Notional: column_from_initial_position_sets_to_merge_by = "notional" column_from_priced_positions_results_to_merge_by = "notional" else: column_from_initial_position_sets_to_merge_by = "quantity" column_from_priced_positions_results_to_merge_by = "quantity" priced_positions_df = priced_positions_df.drop_duplicates( subset=['assetId', column_from_priced_positions_results_to_merge_by] ) df_to_merge_left = position_sets_to_price_df.loc[ position_sets_to_price_df['date'] == position_date, : ].copy() df_to_merge_right = priced_positions_df.copy() # Rounding column quantities to avoid minute discrepancies in price/bulk calculations. df_to_merge_left[column_from_initial_position_sets_to_merge_by] = df_to_merge_left[ column_from_initial_position_sets_to_merge_by ].round(5) df_to_merge_right[column_from_priced_positions_results_to_merge_by] = df_to_merge_right[ column_from_priced_positions_results_to_merge_by ].round(5) priced_positions_df = pd.merge( df_to_merge_left, df_to_merge_right, how="left", left_on=['asset_id', column_from_initial_position_sets_to_merge_by], right_on=['assetId', column_from_priced_positions_results_to_merge_by], suffixes=("_original", None), ) if weighting_strategy == PositionSetWeightingStrategy.Weight: unpriced_positions_df = priced_positions_df[priced_positions_df['weight'].isna()] priced_positions_df = priced_positions_df[~priced_positions_df['weight'].isna()] else: unpriced_positions_df = priced_positions_df[priced_positions_df['quantity'].isna()] priced_positions_df = priced_positions_df[~priced_positions_df["quantity"].isna()] positions = [] for record in priced_positions_df.to_dict('records'): curr_position = Position( asset_id=record.get('assetId'), identifier=record.get('identifier'), name=record.get('name'), weight=record.get('weight'), notional=record.get('notional'), quantity=record.get('quantity'), tags=record.get('tags'), ) curr_position._restricted = record.get('restricted') positions.append(curr_position) unpriced_positions = [ Position( asset_id=unpriced_record.get('asset_id'), identifier=unpriced_record.get('identifier'), name=unpriced_record.get('name'), weight=unpriced_record.get('weight_original'), quantity=unpriced_record.get('quantity_original'), notional=unpriced_record.get('notional_original'), ) for unpriced_record in unpriced_positions_df.to_dict('records') ] input_position_set.positions = positions input_position_set.__unpriced_positions = unpriced_positions _logger.info(f"Total time to process pricing results is {time() - next_start} seconds") ================================================ FILE: gs_quant/markets/position_set_utils.py ================================================ """ Copyright 2024 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.api.gs.assets import GsAssetApi from typing import Tuple import pandas as pd import numpy as np import datetime as dt import math def _get_asset_temporal_xrefs(position_sets_df: pd.DataFrame) -> Tuple[pd.DataFrame, str]: """Helper function to get temporal xrefs for assets in a position set""" universe = list(set(position_sets_df['identifier'].tolist())) batches = np.array_split(universe, math.ceil(len(universe) / 500)) earliest_position_date = position_sets_df['date'].min() earliest_position_date = pd.Timestamp(earliest_position_date).to_pydatetime() results = [] for batch in batches: cur_result = GsAssetApi.get_many_asset_xrefs(list(batch), limit=500) results += cur_result asset_xrefs_final = [] for res in results: xrefs_list = res.get('xrefs') if not xrefs_list: continue all_xrefs = [] for item in xrefs_list: if dt.datetime.strptime(item.get('endDate'), '%Y-%m-%d') >= earliest_position_date: new_xref_map = { 'assetId': res.get('assetId'), 'startDate': item.get('startDate'), 'endDate': item.get('endDate'), } new_xref_map.update(**item.get('identifiers')) all_xrefs.append(new_xref_map) asset_xrefs_final += all_xrefs xref_df = pd.DataFrame(asset_xrefs_final) # Infer identifier type all_possible_identifier_types = [ "ticker", "bbid", "bcid", "ric", "cusip", "isin", "sedol", "gss", "gsid", "primeId", "gsn", ] identifiers_found = set(xref_df.columns.tolist()) & set(all_possible_identifier_types) inferred_identifier_type = None largest_count = 0 for each_id_type in identifiers_found: number_of_matches = len(set(xref_df[each_id_type]) & set(universe)) if number_of_matches > largest_count: inferred_identifier_type = each_id_type largest_count = number_of_matches xref_df = xref_df.dropna(subset=inferred_identifier_type) xref_df = xref_df.fillna(value={'delisted': 'no'}) xref_df = xref_df.loc[xref_df['delisted'] == 'no', :] return xref_df, inferred_identifier_type def _group_temporal_xrefs_into_discrete_time_ranges(xref_df: pd.DataFrame): """Helper function that group asset xref data with overlapping temporal history""" def group_fn(df): # Find where the next group should start df = df.sort_values(by="endDate") # Groups should have non-overlapping time intervals/start_date, end_date where_next_group_should_start = df['startDate'].shift(-1) > df['endDate'] # Assign group numbers based on where the next groups starts group_numbers = where_next_group_should_start.cumsum().shift(1).fillna(0).astype(int) return group_numbers xref_df['startDate'] = [dt.datetime.strptime(x, '%Y-%m-%d') for x in xref_df['startDate']] xref_df['endDate'] = [dt.datetime.strptime(x, '%Y-%m-%d') for x in xref_df['endDate']] groups = group_fn(xref_df) xref_df['group'] = groups def _resolve_many_assets(historical_xref_df: pd.DataFrame, identifier_type: str, **kwargs) -> pd.DataFrame: """Given a dataframe with temporal xref asset data, resolve""" all_dfs = [] xref_group_by = historical_xref_df.groupby('group') for _, grouped_df in xref_group_by: unmapped = [] all_results = [] as_of = min(grouped_df['endDate']) identifiers = grouped_df[identifier_type].tolist() batches = np.array_split(identifiers, math.ceil(len(identifiers) / 500)) resolved_positions = {} for batch in batches: curr_batch_map = GsAssetApi.resolve_assets( identifier=list(batch), as_of=as_of, limit=500, fields=['name', 'id', identifier_type, 'tradingRestriction'], **kwargs, ) resolved_positions = {**resolved_positions, **curr_batch_map} for asset_identifier, asset_resolved_data in resolved_positions.items(): if asset_resolved_data: all_results.append(asset_resolved_data[0]) else: unmapped += [{identifier_type: asset_identifier}] df = pd.DataFrame(all_results + unmapped) df['asOfDate'] = as_of df = pd.merge( df, grouped_df, how="inner", left_on=["id", identifier_type], right_on=["assetId", identifier_type] )[["assetId", "name", identifier_type, "tradingRestriction", "asOfDate", "startDate", "endDate"]] all_dfs.append(df) final_df = pd.concat(all_dfs) if all_dfs else pd.DataFrame() return final_df ================================================ FILE: gs_quant/markets/report.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from enum import Enum, auto import numpy as np from time import sleep from typing import Tuple, Union, List, Dict, OrderedDict import scipy.stats as st import pandas as pd from dateutil.relativedelta import relativedelta from inflection import titleize from gs_quant.api.gs.data import GsDataApi from gs_quant.api.gs.portfolios import GsPortfolioApi from gs_quant.api.gs.reports import GsReportApi, FactorRiskTableMode from gs_quant.api.gs.thematics import Region, GsThematicApi, ThematicMeasure from gs_quant.common import PositionType, ReportParameters, Currency, PositionTag from gs_quant.datetime import business_day_offset, prev_business_date from gs_quant.errors import MqValueError from gs_quant.markets.report_utils import _get_ppaa_batches from gs_quant.target.coordinates import MDAPIDataBatchResponse from gs_quant.target.data import DataQuery, DataQueryResponse from gs_quant.target.reports import Report as TargetReport, ReportType, PositionSourceType, ReportStatus from gs_quant.target.portfolios import RiskAumSource class ReturnFormat(Enum): """Alternative format for data to be returned from get_data functions""" JSON = auto() DATA_FRAME = auto() class ReportDataset(Enum): PPA_DATASET = "PPA" PPAA_DATASET = "PPAA" PFR_DATASET = "PFR" PFRA_DATASET = "PFRA" AFR_DATASET = "AFR" AFRA_DATASET = "AFRA" ATA_DATASET = "ATA" ATAA_DATASET = "ATAA" PTA_DATASET = "PTA" PTAA_DATASET = "PTAA" PORTFOLIO_CONSTITUENTS = "PORTFOLIO_CONSTITUENTS" class FactorRiskViewsMode(Enum): Risk = 'Risk' Attribution = 'Attribution' class FactorRiskResultsMode(Enum): Portfolio = 'Portfolio' Positions = 'Positions' class FactorRiskUnit(Enum): Percent = 'Percent' Notional = 'Notional' class AttributionAggregationType(Enum): Arithmetic = 'arithmetic' Geometric = 'geometric' class AggregationCategoryType(Enum): Sector = 'assetClassificationsGicsSector' Industry = 'assetClassificationsGicsIndustry' Region = 'region' Country = 'assetClassificationsCountryName' class CustomAUMDataPoint: """ Custom AUM Data Point represents a portfolio's AUM value for a specific date """ def __init__(self, date: dt.date, aum: float): self.__date = date self.__aum = aum @property def date(self) -> dt.date: return self.__date @date.setter def date(self, value: dt.date): self.__date = value @property def aum(self) -> float: return self.__aum @aum.setter def aum(self, value: float): self.__aum = value class ReportJobFuture: """Report job future that monitors report status and results""" def __init__(self, report_id: str, job_id: str, report_type: ReportType, start_date: dt.date, end_date: dt.date): self.__report_id = report_id self.__job_id = job_id self.__report_type = report_type self.__start_date = start_date self.__end_date = end_date @property def job_id(self) -> str: return self.__job_id @property def end_date(self) -> dt.date: return self.__end_date def status(self) -> ReportStatus: """ :return: the status of the report job """ job = GsReportApi.get_report_job(self.__job_id) return ReportStatus(job.get('status')) def done(self) -> bool: """ :return: true if the report job is in the following states: "done", "error", or "cancelled". Returns false otherwise """ return self.status() in [ReportStatus.done, ReportStatus.error, ReportStatus.cancelled] def result(self): """ :return: a Pandas DataFrame containing the results of the report job """ status = self.status() if status == ReportStatus.cancelled: raise MqValueError('This report job in status "cancelled". Cannot retrieve results.') if status == ReportStatus.error: raise MqValueError('This report job is in status "error". Cannot retrieve results.') if status != ReportStatus.done: raise MqValueError('This report job is not done. Cannot retrieve results.') if self.__report_type in [ReportType.Portfolio_Factor_Risk, ReportType.Asset_Factor_Risk]: results = GsReportApi.get_factor_risk_report_results( risk_report_id=self.__report_id, start_date=self.__start_date, end_date=self.__end_date ) return pd.DataFrame(results) if self.__report_type == ReportType.Portfolio_Performance_Analytics: query = DataQuery( where={'reportId': self.__report_id}, start_date=self.__start_date, end_date=self.__end_date ) results = GsDataApi.query_data(query=query, dataset_id=ReportDataset.PPA_DATASET.value) return pd.DataFrame(results) return None def wait_for_completion(self, sleep_time: int = 10, max_retries: int = 10, error_on_timeout: bool = True) -> bool: """Periodically query status and sleep till the status become done. If error_on_timeout is false, returns boolean value indicating if the job is done or not""" retries = 0 while not self.done() and retries < max_retries: sleep(sleep_time) retries += 1 if retries == max_retries: if error_on_timeout: raise MqValueError( f'Report job {self.__job_id} is taking longer than expected to finish. ' f'Please contact the Marquee Analytics team at gs-marquee-analytics-support@gs.com ' 'if the issue persists.' ) else: print(f'Report job {self.__job_id} is taking longer than expected to finish.') return False return True def reschedule(self): GsReportApi.reschedule_report_job(self.__job_id) class Report: """General report class""" def __init__( self, report_id: str = None, name: str = None, position_source_id: str = None, position_source_type: Union[str, PositionSourceType] = None, report_type: Union[str, ReportType] = None, parameters: ReportParameters = None, earliest_start_date: dt.date = None, latest_end_date: dt.date = None, latest_execution_time: dt.datetime = None, status: Union[str, ReportStatus] = ReportStatus.new, percentage_complete: float = None, ): self.__id = report_id self.__name = name self.__position_source_id = position_source_id self.__position_source_type = ( position_source_type if isinstance(position_source_type, PositionSourceType) or position_source_type is None else PositionSourceType(position_source_type) ) self.__type = ( report_type if isinstance(report_type, ReportType) or report_type is None else ReportType(report_type) ) self.__parameters = parameters self.__earliest_start_date = earliest_start_date self.__latest_end_date = latest_end_date self.__latest_execution_time = latest_execution_time self.__status = status if isinstance(status, ReportStatus) else ReportStatus(status) self.__percentage_complete = percentage_complete @property def id(self) -> str: return self.__id @property def name(self) -> str: return self.__name @property def position_source_id(self) -> str: return self.__position_source_id @position_source_id.setter def position_source_id(self, value: str): self.__position_source_id = value @property def position_source_type(self) -> PositionSourceType: return self.__position_source_type @position_source_type.setter def position_source_type(self, value: Union[str, PositionSourceType]): self.__position_source_type = value if isinstance(value, PositionSourceType) else PositionSourceType(value) @property def type(self) -> ReportType: return self.__type @type.setter def type(self, value: Union[str, ReportType]): self.__type = value if isinstance(value, ReportType) else ReportType(value) @property def parameters(self) -> ReportParameters: return self.__parameters @parameters.setter def parameters(self, value: ReportParameters): self.__parameters = value @property def earliest_start_date(self) -> dt.date: return self.__earliest_start_date @property def latest_end_date(self) -> dt.date: return self.__latest_end_date @property def latest_execution_time(self) -> dt.datetime: return self.__latest_execution_time @property def status(self) -> ReportStatus: return self.__status @property def percentage_complete(self) -> float: return self.__percentage_complete @classmethod def get(cls, report_id: str, acceptable_types: List[ReportType] = None): return cls.from_target(GsReportApi.get_report(report_id)) @classmethod def from_target(cls, report: TargetReport): return Report( report_id=report.id, name=report.name, position_source_id=report.position_source_id, position_source_type=report.position_source_type, report_type=report.type, parameters=report.parameters, earliest_start_date=report.earliest_start_date, latest_end_date=report.latest_end_date, latest_execution_time=report.latest_execution_time, status=report.status, percentage_complete=report.percentage_complete, ) def save(self): """Create a report in Marquee if it doesn't exist. Update the report if it does.""" target_report = TargetReport( name=self.name, position_source_id=self.position_source_id, position_source_type=self.position_source_type, type_=self.type, parameters=self.parameters if self.parameters else ReportParameters(), ) if self.id: target_report.id = self.id GsReportApi.update_report(target_report) else: report = GsReportApi.create_report(target_report) self.__id = report.id def delete(self): """Delete a report from Marquee""" GsReportApi.delete_report(self.id) def set_position_source(self, entity_id: str): """Set position source type and position source ID""" is_portfolio = entity_id.startswith('MP') self.position_source_type = 'Portfolio' if is_portfolio else 'Asset' self.position_source_id = entity_id if isinstance(self, FactorRiskReport): self.type = ReportType.Portfolio_Factor_Risk if is_portfolio else ReportType.Asset_Factor_Risk if isinstance(self, ThematicReport): self.type = ReportType.Portfolio_Thematic_Analytics if is_portfolio else ReportType.Asset_Thematic_Analytics def get_most_recent_job(self): """Retrieve the most current report job""" jobs = GsReportApi.get_report_jobs(self.id) most_current_job = sorted(jobs, key=lambda i: i['createdTime'], reverse=True)[0] return ReportJobFuture( report_id=self.id, job_id=most_current_job.get('id'), report_type=ReportType(most_current_job.get('reportType')), start_date=dt.datetime.strptime(most_current_job.get('startDate'), "%Y-%m-%d").date(), end_date=dt.datetime.strptime(most_current_job.get('endDate'), "%Y-%m-%d").date(), ) def schedule(self, start_date: dt.date = None, end_date: dt.date = None, backcast: bool = None): """ Schedule a report with the given date range :param start_date: start date (optional) :param end_date: end date (optional) :param backcast: set to true if the report should be backcasted """ if None in [self.id, self.__position_source_id]: raise MqValueError('Can only schedule reports with valid IDs and Position Source IDs.') if self.position_source_type != PositionSourceType.Portfolio and None in [start_date, end_date]: raise MqValueError('Must specify schedule start and end dates for report.') if None in [start_date, end_date]: position_dates = GsPortfolioApi.get_position_dates(self.position_source_id) if len(position_dates) == 0: raise MqValueError('Cannot schedule reports for a portfolio with no positions.') if start_date is None: start_date = ( business_day_offset(min(position_dates) - relativedelta(years=1), -1, roll='forward') if backcast else min(position_dates) ) if end_date is None: end_date = min(position_dates) if backcast else business_day_offset(dt.date.today(), -1, roll='forward') GsReportApi.schedule_report(report_id=self.id, start_date=start_date, end_date=end_date, backcast=backcast) def run(self, start_date: dt.date = None, end_date: dt.date = None, backcast: bool = False, is_async: bool = True): """ Run a report with the given date range :param start_date: start date (optional) :param end_date: end date (optional) :param backcast: set to true if the report should be backcasted; defaults to false :param is_async: return immediately (true) or wait for results (false); defaults to true """ self.schedule(start_date, end_date, backcast) counter = 5 while counter > 0: try: job_future = self.get_most_recent_job() if is_async: return job_future counter = 100 while counter > 0: if job_future.done(): return job_future.result() sleep(6) raise MqValueError( f'Your report {self.id} is taking longer than expected to finish. Please contact the ' 'Marquee Analytics team at gs-marquee-analytics-support@gs.com' ) except IndexError: counter -= 1 status = Report.get(self.id).status if status == ReportStatus.waiting: raise MqValueError( f'Your report {self.id} is stuck in "waiting" status and therefore cannot be run at this time.' ) raise MqValueError( f'Your report {self.id} is taking longer to run than expected. ' 'Please reach out to the Marquee Analytics team at gs-marquee-analytics-support@gs.com ' 'for assistance.' ) class PerformanceReport(Report): """ Historical analyses on measures like PnL and exposure of a position source over a date range """ def __init__( self, report_id: str = None, name: str = None, position_source_id: str = None, position_source_type: Union[str, PositionSourceType] = None, parameters: ReportParameters = None, earliest_start_date: dt.date = None, latest_end_date: dt.date = None, latest_execution_time: dt.datetime = None, status: Union[str, ReportStatus] = ReportStatus.new, percentage_complete: float = None, **kwargs, ): """ Historical analyses on measures like PnL and exposure of a portfolio over a date range :param report_id: Marquee report ID :param name: report name :param position_source_id: position source ID :param position_source_type: position source (i.e. 'Portfolio', 'Asset', or 'Hedge') :param parameters: parameters of the report :param earliest_start_date: start date of report :param latest_end_date: end date of report :param latest_execution_time: date of the latest execution :param status: status of of report (i.e. 'ready', 'executing', or 'done') :param percentage_complete: percent of the report that is complete **Examples** >>> performance_report = PerformanceReport( >>> position_source_type=PositionSourceType.Portfolio, >>> position_source_id='PORTFOLIOID' >>> ) """ super().__init__( report_id, name, position_source_id, position_source_type, ReportType.Portfolio_Performance_Analytics, parameters, earliest_start_date, latest_end_date, latest_execution_time, status, percentage_complete, ) @classmethod def get(cls, report_id: str, **kwargs): """ Get a performance report from the unique report identifier :param report_id: Marquee report ID :return: returns a PerformanceReport object that correlates to the Marquee report """ return cls.from_target(GsReportApi.get_report(report_id)) @classmethod def from_target(cls, report: TargetReport): if report.type != ReportType.Portfolio_Performance_Analytics: raise MqValueError('This report is not a performance report.') return PerformanceReport( report_id=report.id, name=report.name, position_source_id=report.position_source_id, position_source_type=report.position_source_type, report_type=report.type, parameters=report.parameters, earliest_start_date=report.earliest_start_date, latest_end_date=report.latest_end_date, latest_execution_time=report.latest_execution_time, status=report.status, percentage_complete=report.percentage_complete, ) def get_pnl( self, start_date: dt.date = None, end_date: dt.date = None, unit: FactorRiskUnit = FactorRiskUnit.Notional ) -> pd.DataFrame: """ Get historical portfolio PnL :param start_date: start date :param end_date: end date :param unit: return the results in terms of notional or percent (defaults to notional) :return: returns a Pandas DataFrame with the results """ return self.get_pnl_measure("pnl", unit, start_date, end_date) def get_long_exposure(self, start_date: dt.date = None, end_date: dt.date = None) -> pd.DataFrame: """ Get historical portfolio long exposure :param start_date: start date :param end_date: end date :return: returns a Pandas DataFrame with the results """ return self.get_measure("longExposure", start_date, end_date) def get_short_exposure(self, start_date: dt.date = None, end_date: dt.date = None) -> pd.DataFrame: """ Get historical portfolio short exposure :param start_date: start date :param end_date: end date :return: returns a Pandas DataFrame with the results """ return self.get_measure("shortExposure", start_date, end_date) def get_asset_count(self, start_date: dt.date = None, end_date: dt.date = None) -> pd.DataFrame: """ Get historical portfolio asset count :param start_date: start date :param end_date: end date :return: returns a Pandas DataFrame with the results """ return self.get_measure("assetCount", start_date, end_date) def get_turnover(self, start_date: dt.date = None, end_date: dt.date = None) -> pd.DataFrame: """ Get historical portfolio turnover :param start_date: start date :param end_date: end date :return: returns a Pandas DataFrame with the results """ return self.get_measure("turnover", start_date, end_date) def get_asset_count_long(self, start_date: dt.date = None, end_date: dt.date = None) -> pd.DataFrame: """ Get historical portfolio long asset count :param start_date: start date :param end_date: end date :return: returns a Pandas DataFrame with the results """ return self.get_measure("assetCountLong", start_date, end_date) def get_asset_count_short( self, start_date: dt.date = None, end_date: dt.date = None ) -> Union[MDAPIDataBatchResponse, DataQueryResponse, tuple, list]: """ Get historical portfolio short asset count :param start_date: start date :param end_date: end date :return: returns a Pandas DataFrame with the results """ return self.get_measure("assetCountShort", start_date, end_date) def get_net_exposure(self, start_date: dt.date = None, end_date: dt.date = None) -> pd.DataFrame: """ Get historical portfolio net exposure :param start_date: start date :param end_date: end date :return: returns a Pandas DataFrame with the results """ return self.get_measure("netExposure", start_date, end_date) def get_gross_exposure(self, start_date: dt.date = None, end_date: dt.date = None) -> pd.DataFrame: """ Get historical portfolio gross exposure :param start_date: start date :param end_date: end date :return: returns a Pandas DataFrame with the results """ return self.get_measure("grossExposure", start_date, end_date) def get_position_net_weights( self, start_date: dt.date, end_date: dt.date, asset_metadata_fields: List[str] = ["id", "name", "ticker"], include_all_business_days: bool = True, position_type: PositionType = None, ) -> pd.DataFrame: """ Get the net weight of each position in the portfolio for the given date range. :param start_date: start date from which to retrieve the net weight data :param end_date: end date until which to retrieve the net weight data :param asset_metadata_fields: List of fields to include in the result. Default is ["id", "name", "ticker"]. :param include_all_business_days: If True, include all business days in the date range, even if there is no position set uploaded on those days. If False, only include dates where there are uploaded positions. :param position_type: The type of position to retrieve. If None, all positions are retrieved. :return: A DataFrame with the dates as the index and the positions as the columns. The values in the DataFrame are the net weights of the positions on the corresponding dates. """ asset_metadata_fields.append("netWeight") try: return pd.DataFrame( self.get_positions_data( start=start_date, end=end_date, fields=asset_metadata_fields, include_all_business_days=include_all_business_days, position_type=position_type, ) ) except Exception as e: raise MqValueError(f"Error retrieving net weight data: {e}") def get_trading_pnl( self, start_date: dt.date = None, end_date: dt.date = None, unit: FactorRiskUnit = FactorRiskUnit.Notional ) -> pd.DataFrame: """ Get historical portfolio trading PnL :param start_date: start date :param end_date: end date :param unit: return the results in terms of notional or percent (defaults to notional) :return: returns a Pandas DataFrame with the results """ return self.get_pnl_measure("tradingPnl", unit, start_date, end_date) def get_trading_cost_pnl( self, start_date: dt.date = None, end_date: dt.date = None, unit: FactorRiskUnit = FactorRiskUnit.Notional ) -> pd.DataFrame: """ Get historical portfolio trading cost PnL :param start_date: start date :param end_date: end date :param unit: return the results in terms of notional or percent (defaults to notional) :return: returns a Pandas DataFrame with the results """ return self.get_pnl_measure("tradingCostPnl", unit, start_date, end_date) def get_servicing_cost_long_pnl( self, start_date: dt.date = None, end_date: dt.date = None, unit: FactorRiskUnit = FactorRiskUnit.Notional ) -> pd.DataFrame: """ Get historical portfolio servicing cost long PnL :param start_date: start date :param end_date: end date :param unit: return the results in terms of notional or percent (defaults to notional) :return: returns a Pandas DataFrame with the results """ return self.get_pnl_measure("servicingCostLongPnl", unit, start_date, end_date) def get_servicing_cost_short_pnl( self, start_date: dt.date = None, end_date: dt.date = None, unit: FactorRiskUnit = FactorRiskUnit.Notional ) -> pd.DataFrame: """ Get historical portfolio servicing cost short PnL :param start_date: start date :param end_date: end date :param unit: return the results in terms of notional or percent (defaults to notional) :return: returns a Pandas DataFrame with the results """ return self.get_pnl_measure("servicingCostShortPnl", unit, start_date, end_date) def get_asset_count_priced(self, start_date: dt.date = None, end_date: dt.date = None) -> pd.DataFrame: """ Get historical portfolio asset count priced :param start_date: start date :param end_date: end date :return: returns a Pandas DataFrame with the results """ return self.get_measure("assetCountPriced", start_date, end_date) def get_measure( self, field: str, start_date: dt.date = None, end_date: dt.date = None, return_format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[Dict, pd.DataFrame]: """ Get historical portfolio metrics :param field: the entity property to be returned :param start_date: start date :param end_date: end date :return: returns a Pandas DataFrame with the results """ fields = (field,) where = {'reportId': self.id} query = DataQuery(where=where, fields=fields, start_date=start_date, end_date=end_date) results = GsDataApi.query_data(query=query, dataset_id=ReportDataset.PPA_DATASET.value) return pd.DataFrame(results) if return_format == ReturnFormat.DATA_FRAME else results def get_pnl_measure(self, field: str, unit: FactorRiskUnit, start_date: dt.date, end_date: dt.date): measure = self.get_measure(field, start_date, end_date) if unit == FactorRiskUnit.Notional: return measure else: aggregated_pnl = get_pnl_percent(self, measure, field, start_date, end_date) return pd.merge(measure.drop(columns=[field]), aggregated_pnl, left_on='date', right_index=True).rename( columns={'return': field} ) def get_many_measures( self, measures: Tuple[str, ...] = None, start_date: dt.date = None, end_date: dt.date = None, return_format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[Dict, pd.DataFrame]: """ Get many historical portfolio metrics :param measures: a list of metrics :param start_date: start date :param end_date: end date :return: returns a Pandas DataFrame with the results """ if measures is None: measures = [] fields = tuple(measure for measure in measures) where = {'reportId': self.id} query = DataQuery(where=where, fields=fields, start_date=start_date, end_date=end_date) results = GsDataApi.query_data(query=query, dataset_id=ReportDataset.PPA_DATASET.value) return pd.DataFrame(results) if return_format == ReturnFormat.DATA_FRAME else results def get_aum_source(self) -> RiskAumSource: """ Get AUM Source for the portfolio associated with the performance report :return: aum source """ portfolio = GsPortfolioApi.get_portfolio(self.position_source_id) return portfolio.aum_source if portfolio.aum_source is not None else RiskAumSource.Long def set_aum_source(self, aum_source: RiskAumSource): """ Set AUM Source for the portfolio associated with the performance report :param aum_source: aum source for portfolio :return: aum source """ portfolio = GsPortfolioApi.get_portfolio(self.position_source_id) portfolio.aum_source = aum_source GsPortfolioApi.update_portfolio(portfolio) def get_custom_aum(self, start_date: dt.date = None, end_date: dt.date = None) -> List[CustomAUMDataPoint]: """ Get AUM data for performance report :param start_date: start date :param end_date: end date :return: list of AUM data between the specified range """ aum_data = GsReportApi.get_custom_aum(self.id, start_date, end_date) return [ CustomAUMDataPoint(date=dt.datetime.strptime(data['date'], '%Y-%m-%d'), aum=data['aum']) for data in aum_data ] def get_aum(self, start_date: dt.date, end_date: dt.date): """ Get AUM data for performance report :param start_date: start date :param end_date: end date :return: dictionary of dates with corresponding AUM values """ aum_source = self.get_aum_source() if aum_source == RiskAumSource.Custom_AUM: aum = self.get_custom_aum(start_date=start_date, end_date=end_date) return {aum_point.date.strftime('%Y-%m-%d'): aum_point.aum for aum_point in aum} if aum_source == RiskAumSource.Long: aum = self.get_long_exposure(start_date=start_date, end_date=end_date) return {row['date']: row['longExposure'] for index, row in aum.iterrows()} if aum_source == RiskAumSource.Short: aum = self.get_short_exposure(start_date=start_date, end_date=end_date) return {row['date']: row['shortExposure'] for index, row in aum.iterrows()} if aum_source == RiskAumSource.Gross: aum = self.get_gross_exposure(start_date=start_date, end_date=end_date) return {row['date']: row['grossExposure'] for index, row in aum.iterrows()} if aum_source == RiskAumSource.Net: aum = self.get_net_exposure(start_date=start_date, end_date=end_date) return {row['date']: row['netExposure'] for index, row in aum.iterrows()} def upload_custom_aum(self, aum_data: List[CustomAUMDataPoint], clear_existing_data: bool = None): """ Add AUM data for portfolio corresponding to the performance report :param aum_data: list of AUM data to upload :param clear_existing_data: delete all previously uploaded AUM data for the portfolio (defaults to false) """ formatted_aum_data = [{'date': data.date.strftime('%Y-%m-%d'), 'aum': data.aum} for data in aum_data] GsReportApi.upload_custom_aum(self.id, formatted_aum_data, clear_existing_data) def get_positions_data( self, start: dt.date = None, end: dt.date = dt.date.today(), fields: [str] = None, include_all_business_days: bool = False, position_type: PositionType = None, ) -> List[Dict]: return GsPortfolioApi.get_positions_data( self.position_source_id, start, end, fields, performance_report_id=self.id, include_all_business_days=include_all_business_days, position_type=position_type, ) raise NotImplementedError def get_portfolio_constituents( self, fields: List[str] = None, start_date: dt.date = None, end_date: dt.date = None, prefer_rebalance_positions: bool = False, return_format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[Dict, pd.DataFrame]: """ Get historical portfolio constituents :param fields: list of fields to include in the results :param start_date: start date :param end_date: end date :param prefer_rebalance_positions: If both Holding and Rebalance entries are present, prefer rebalance entries :param return_format: return format; defaults to a Pandas DataFrame, but can be manually set to ReturnFormat.JSON :return: Portfolio constituent data for each day in the requested date range """ where = {'reportId': self.id} asset_count = self.get_asset_count(start_date, end_date) if asset_count.empty: return pd.DataFrame() if return_format == ReturnFormat.DATA_FRAME else {} date_batches = _get_ppaa_batches(asset_count, 3000000) queries = [ DataQuery(where=where, fields=fields, start_date=dates_batch[0], end_date=dates_batch[1]) for dates_batch in date_batches ] results = [ GsDataApi.query_data(query=query, dataset_id=ReportDataset.PORTFOLIO_CONSTITUENTS.value) for query in queries ] results = sum(results, []) if prefer_rebalance_positions: rebalance_dates = set() for result in results: if result['entryType'] == 'Rebalance': rebalance_dates.add(result['date']) results = [ result for result in results if result['date'] not in rebalance_dates or result['entryType'] == 'Rebalance' ] return pd.DataFrame(results) if return_format == ReturnFormat.DATA_FRAME else results def get_pnl_contribution( self, start_date: dt.date = None, end_date: dt.date = None, currency: Currency = None ) -> pd.DataFrame: """ Get PnL Contribution broken down by constituents :param start_date: optional start date :param end_date: optional end date :param currency: optional currency; defaults to your portfolio's currency :return: a Pandas DataFrame of results """ return pd.DataFrame( GsPortfolioApi.get_attribution(self.position_source_id, start_date, end_date, currency, self.id) ) def get_brinson_attribution( self, benchmark: str = None, currency: Currency = None, include_interaction: bool = False, aggregation_type: AttributionAggregationType = AttributionAggregationType.Arithmetic, aggregation_category: AggregationCategoryType = None, start_date: dt.date = None, end_date: dt.date = None, return_format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[Dict, pd.DataFrame]: """ Get PnL analytics called Brinson Attribution :param benchmark: benchmark's unique Marquee identifier :param currency: currency of results; if none passed results default to your portfolio's currency :param include_interaction: show interaction independent of security selection :param aggregation_type: either artihmetic or geometric :param aggregation_category: aggregate results by sector, industry, region or country name :param start_date: start date :param end_date: end date :param return_format: return format; defaults to a Pandas DataFrame, but can be manually set to ReturnFormat.JSON :return: Portfolio Brinson Attribution data for the requested date range **Examples** >>> brinson_attribution_results = performance_report.get_brinson_attribution ( >>> benchmark=asset.get_marquee_id(), >>> include_interaction=True, >>> start_date=performance_report.earliest_start_date, >>> end_date=performance_report.latest_end_date, >>> ) >>> display(pd.DataFrame(brinson_attribution_results)) """ results = GsReportApi.get_brinson_attribution_results( portfolio_id=self.position_source_id, benchmark=benchmark, currency=currency, include_interaction=include_interaction, aggregation_type=aggregation_type.value, aggregation_category=aggregation_category.value if aggregation_category else None, start_date=start_date, end_date=end_date, ) if return_format == ReturnFormat.DATA_FRAME: rows = results.get('results') rows_data_frame = pd.DataFrame(rows) rows_data_frame = rows_data_frame.rename(columns=lambda c: titleize(c)) return rows_data_frame return results class FactorRiskReport(Report): """ Historical analyses on both the risk and attribution of a portfolio or asset to various factors determined by the specified risk model """ def __init__( self, risk_model_id: str = None, fx_hedged: bool = True, benchmark_id: str = None, report_id: str = None, name: str = None, position_source_id: str = None, position_source_type: Union[str, PositionSourceType] = None, report_type: Union[str, ReportType] = None, earliest_start_date: dt.date = None, latest_end_date: dt.date = None, latest_execution_time: dt.datetime = None, status: Union[str, ReportStatus] = ReportStatus.new, percentage_complete: float = None, tags: Tuple[PositionTag, ...] = None, **kwargs, ): """ Historical analyses on both the risk and attribution of a portfolio or asset to various factors determined by the specified risk model :param risk_model_id: risk model ID :param fx_hedged: if position source is FX hedged :param benchmark_id: optional benchmark asset ID to include in results :param report_id: Marquee report ID :param name: report name :param position_source_id: position source ID :param position_source_type: position source (i.e. 'Portfolio', 'Asset', or 'Hedge') :param report_type: report type (i.e. 'Asset Factor Risk' or 'Portfolio Factor Risk') :param earliest_start_date: start date of report :param latest_end_date: end date of report :param latest_execution_time: date of the latest execution :param status: status of of report (i.e. 'ready', 'executing', or 'done') :param percentage_complete: percent of the report that is complete :param tags: tags of the report **Examples** >>> risk_report = FactorRiskReport( >>> risk_model_id='RISKMODELID', >>> fx_hedged=True, >>> benchmark_id=benchmark.get_marquee_id(), >>> position_source_id='PORTFOLIOID', >>> position_source_type=PositionSourceType.Portfolio >>> ) """ if position_source_id and not position_source_type: position_source_type = ( PositionSourceType.Portfolio if position_source_id.startswith('MP') else PositionSourceType.Asset ) if position_source_type and not report_type: report_type = ( ReportType.Portfolio_Factor_Risk if position_source_type is PositionSourceType.Portfolio else ReportType.Asset_Factor_Risk ) super().__init__( report_id, name, position_source_id, position_source_type, report_type, ReportParameters(risk_model=risk_model_id, fx_hedged=fx_hedged, benchmark=benchmark_id, tags=tags), earliest_start_date, latest_end_date, latest_execution_time, status, percentage_complete, ) @classmethod def get(cls, report_id: str, **kwargs): """ Get a factor risk report from the unique report identifier :param report_id: Marquee report ID :return: returns a FactorRiskReport object that correlates to the Marquee report """ return cls.from_target(GsReportApi.get_report(report_id)) @classmethod def from_target(cls, report: TargetReport): if report.type not in [ReportType.Portfolio_Factor_Risk, ReportType.Asset_Factor_Risk]: raise MqValueError('This report is not a factor risk report.') return FactorRiskReport( risk_model_id=report.parameters.risk_model, fx_hedged=report.parameters.fx_hedged, benchmark_id=report.parameters.benchmark, report_id=report.id, position_source_id=report.position_source_id, position_source_type=report.position_source_type, report_type=report.type, earliest_start_date=report.earliest_start_date, latest_end_date=report.latest_end_date, status=report.status, percentage_complete=report.percentage_complete, tags=report.parameters.tags, ) def get_risk_model_id(self) -> str: """ :return: the ID of the risk model associated with the factor risk report """ return self.parameters.risk_model def get_benchmark_id(self) -> str: """ :return: the unique Marquee identifier of the benchmark associated with the factor risk report """ return self.parameters.benchmark def get_results( self, mode: FactorRiskResultsMode = FactorRiskResultsMode.Portfolio, factors: List[str] = None, factor_categories: List[str] = None, start_date: dt.date = None, end_date: dt.date = None, currency: Currency = None, return_format: ReturnFormat = ReturnFormat.DATA_FRAME, unit: FactorRiskUnit = FactorRiskUnit.Notional, ) -> Union[Dict, pd.DataFrame]: """ Get the raw results associated with the factor risk report :param mode: results mode; defaults to the portfolio level :param factors: optional list of factors; defaults to all of them :param factor_categories: optional list of factor categories; defaults to all of them :param start_date: start date :param end_date: end date :param currency: currency :param return_format: return format; defaults to a Pandas DataFrame, but can be manually set to ReturnFormat.JSON :param: unit: return the results in terms of notional or percent (defaults to notional) :return: risk report results **Examples** >>> factor_and_total_results = risk_report.get_results( >>> factors=['Factor', 'Specific'], >>> start_date=dt.date(2022, 1, 1), >>> end_date=dt.date(2021, 1, 1) >>> ) >>> print(factor_and_total_results) """ results = GsReportApi.get_factor_risk_report_results( risk_report_id=self.id, view=mode.value, factors=factors, factor_categories=factor_categories, currency=currency, start_date=start_date, end_date=end_date, unit=unit.value, ) return pd.DataFrame(results) if return_format == ReturnFormat.DATA_FRAME else results def get_view( self, factor: str = None, factor_category: str = None, start_date: dt.date = None, end_date: dt.date = None, currency: Currency = None, unit: FactorRiskUnit = FactorRiskUnit.Notional, ) -> Dict: """ Get the results associated with the factor risk report as seen on the Marquee user interface :param factor: optional factor name :param factor_category: optional factor category :param start_date: start date :param end_date: end date :param currency: currency :param: unit: return the results in terms of notional or percent (defaults to notional) :return: risk report results **Examples** >>> category_table = risk_report.get_view( >>> start_date=risk_report.latest_end_date, >>> end_date=risk_report.latest_end_date, >>> unit=FactorRiskUnit.Notional >>> ).get('factorCategoriesTable') >>> category_df = pd.DataFrame(category_table).filter(items=[ >>> 'name', >>> 'proportionOfRisk', >>> 'marginalContributionToRiskPercent', >>> 'relativeMarginalContributionToRisk', >>> 'exposure', >>> 'avgProportionOfRisk' >>> ]) >>> display(category_df) """ return GsReportApi.get_factor_risk_report_view( risk_report_id=self.id, factor=factor, factor_category=factor_category, currency=currency, start_date=start_date, end_date=end_date, unit=unit.value, ) def get_table( self, mode: FactorRiskTableMode, factors: List[str] = None, factor_categories: List[str] = None, date: dt.date = None, start_date: dt.date = None, end_date: dt.date = None, unit: FactorRiskUnit = None, currency: Currency = None, return_format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[Dict, pd.DataFrame]: """ Get the results associated with the factor risk report formatted for the asset level table on the interface :param mode: tables mode :param factors: optional list of factors to filter by; defaults to all :param factor_categories: optional list of factor categories to filter by; defaults to all :param date: date for modes requiring snapshot data (defaults to the latest available date) :param start_date: start date for modes requiring date range (defaults to 1 month before the end date) :param end_date: end date for modes requiring date range (defaults to the latest available date) :param unit: return the results in terms of notional or percent (defaults to notional) :param currency: currency :return: risk report table at asset level **Examples** >>> pnl_table = risk_report.get_table( >>> mode=FactorRiskTableMode.Pnl, >>> start_date=risk_report.earliest_start_date, >>> end_date=risk_report.latest_end_date >>> ) >>> display(pd.DataFrame(pnl_table)) """ # setting default values to start_date and end_date if they are None if start_date is None and end_date is None: start_date = ( self.latest_end_date - relativedelta(months=1) if mode == FactorRiskTableMode.Pnl else self.latest_end_date ) end_date = self.latest_end_date elif start_date is None: start_date = end_date - relativedelta(months=1) if mode == FactorRiskTableMode.Pnl else end_date elif end_date is None: end_date = start_date if mode != FactorRiskTableMode.Pnl else self.latest_end_date table = GsReportApi.get_factor_risk_report_table( risk_report_id=self.id, mode=mode, unit=unit.value if unit else None, currency=currency, date=date, start_date=start_date, end_date=end_date, ) if 'table' not in table and 'warning' in table: raise MqValueError(table.get('warning')) if return_format == ReturnFormat.DATA_FRAME: column_info = table.get('table').get('metadata').get('columnInfo') column_info[0].update({'columns': ['name', 'symbol', 'sector']}) rows = table.get('table').get('rows') sorted_columns = _filter_table_by_factor_and_category(column_info, factors, factor_categories) sorted_columns = list(OrderedDict.fromkeys(sorted_columns)) rows_data_frame = pd.DataFrame(rows) rows_data_frame = rows_data_frame.reindex(columns=sorted_columns) rows_data_frame = rows_data_frame.set_index('name') return rows_data_frame return table def get_factor_pnl( self, mode: FactorRiskResultsMode = FactorRiskResultsMode.Portfolio, factor_names: List[str] = None, factor_categories: List[str] = None, start_date: dt.date = None, end_date: dt.date = None, currency: Currency = None, unit: FactorRiskUnit = FactorRiskUnit.Notional, ) -> pd.DataFrame: """ Get historical factor PnL :param mode: results mode; defaults to the portfolio level :param factor_names: optional list of factor names; defaults to all of them :param factor_categories: optional list of factor categories; defaults to all of them :param start_date: start date :param end_date: end date :param currency: currency :param unit: return the results in terms of notional or percent (defaults to notional) :return: a Pandas DataFrame with the results """ factor_names_to_query = factor_names if unit == FactorRiskUnit.Percent and factor_names_to_query is not None: factor_names_to_query.append('Total') factor_data = self.get_results( mode=mode, factors=factor_names_to_query, factor_categories=factor_categories, start_date=start_date, end_date=end_date, currency=currency, return_format=ReturnFormat.JSON, unit=unit if self.position_source_type != PositionSourceType.Portfolio else FactorRiskUnit.Notional, ) if unit == FactorRiskUnit.Notional or self.position_source_type != PositionSourceType.Portfolio: return _format_multiple_factor_table(factor_data, 'pnl') else: if factor_names is None: factor_names = list(set([x.get('factor') for x in factor_data])) all_reports = GsPortfolioApi.get_reports(self.position_source_id, None) performance_reports = [ PerformanceReport.get(r.id) for r in all_reports if r.type_ == ReportType.Portfolio_Performance_Analytics ] performance_report = [r for r in performance_reports if r.parameters.tags == self.parameters.tags][0] aum_df = format_aum_for_return_calculation(performance_report, start_date, end_date) total_data = [d for d in factor_data if d.get('factor') == 'Total'] # Total pnl must be retieved when using smoothening to calculate Pnl % if len(total_data) == 0: total_data = self.get_results( mode=mode, factors=['Total'], start_date=start_date, end_date=end_date, currency=currency, return_format=ReturnFormat.JSON, unit=FactorRiskUnit.Notional, ) smoothened_factor_data = {} for factor_name in factor_names: selected_factor_data = [d for d in factor_data if d.get('factor') == factor_name] start_date = dt.datetime.strptime(min([d['date'] for d in selected_factor_data]), '%Y-%m-%d').date() smoothened_factor_data[factor_name] = get_factor_pnl_percent_for_single_factor( selected_factor_data, total_data, aum_df, start_date ) result = pd.DataFrame(smoothened_factor_data).reset_index().rename(columns={'date': 'Date'}) return result.loc[result['Date'] >= start_date.strftime("%Y-%m-&d")] def get_factor_exposure( self, mode: FactorRiskResultsMode = FactorRiskResultsMode.Portfolio, factor_names: List[str] = None, factor_categories: List[str] = None, start_date: dt.date = None, end_date: dt.date = None, currency: Currency = None, unit: FactorRiskUnit = FactorRiskUnit.Notional, ) -> pd.DataFrame: """ Get historical factor exposure :param mode: results mode; defaults to the portfolio level :param factor_names: optional list of factor names; defaults to all of them :param factor_categories: optional list of factor categories; defaults to all of them :param start_date: start date :param end_date: end date :param currency: currency :param: unit: return the results in terms of notional or percent (defaults to notional) :return: a Pandas DataFrame with the results """ factor_data = self.get_results( mode=mode, factors=factor_names, factor_categories=factor_categories, start_date=start_date, end_date=end_date, currency=currency, return_format=ReturnFormat.JSON, unit=unit, ) return _format_multiple_factor_table(factor_data, 'exposure') def get_factor_proportion_of_risk( self, factor_names: List[str] = None, factor_categories: List[str] = None, start_date: dt.date = None, end_date: dt.date = None, currency: Currency = None, ) -> pd.DataFrame: """ Get historical factor proportion of risk :param factor_names: optional list of factor names; defaults to all of them :param factor_categories: optional list of factor categories; defaults to all of them :param start_date: start date :param end_date: end date :param currency: currency :return: a Pandas DataFrame with the results """ factor_data = self.get_results( factors=factor_names, factor_categories=factor_categories, start_date=start_date, end_date=end_date, currency=currency, return_format=ReturnFormat.JSON, ) return _format_multiple_factor_table(factor_data, 'proportionOfRisk') def get_annual_risk( self, factor_names: List[str] = None, start_date: dt.date = None, end_date: dt.date = None, currency: Currency = None, ) -> pd.DataFrame: """ Get historical annual risk :param factor_names: optional list of factor names; must be from the following: "Factor", "Specific", "Total" :param start_date: start date :param end_date: end date :param currency: currency :return: a Pandas DataFrame with the results """ factor_data = self.get_results( factors=factor_names, start_date=start_date, end_date=end_date, currency=currency, return_format=ReturnFormat.JSON, ) return _format_multiple_factor_table(factor_data, 'annualRisk') def get_daily_risk( self, factor_names: List[str] = None, start_date: dt.date = None, end_date: dt.date = None, currency: Currency = None, ) -> pd.DataFrame: """ Get historical daily risk :param factor_names: optional list of factor names; must be from the following: "Factor", "Specific", "Total" :param start_date: start date :param end_date: end date :param currency: currency :return: a Pandas DataFrame with the results """ factor_data = self.get_results( factors=factor_names, start_date=start_date, end_date=end_date, currency=currency, return_format=ReturnFormat.JSON, ) return _format_multiple_factor_table(factor_data, 'dailyRisk') def get_ex_ante_var( self, confidence_interval: float = 95.0, start_date: dt.date = None, end_date: dt.date = None, currency: Currency = None, ) -> pd.DataFrame: """ Get ex-ante Value at Risk as defined by the risk model :param confidence_interval: the VaR confidence interval as a percent :param start_date: start date :param end_date: end date :param currency: currency :return: a Pandas DataFrame with the results """ factor_data = self.get_results( factors=['Total'], start_date=start_date, end_date=end_date, currency=currency, return_format=ReturnFormat.JSON, ) z_score = st.norm.ppf(confidence_interval / 100) for data in factor_data: data['var'] = data['dailyRisk'] * z_score return _format_multiple_factor_table(factor_data, 'var') def _format_multiple_factor_table(factor_data: List[Dict], key: str) -> pd.DataFrame: formatted_data = {} for data in factor_data: date = data['date'] if date in formatted_data: formatted_data[date][data['factor']] = data[key] else: formatted_data[date] = {'Date': date, data['factor']: data[key]} return pd.DataFrame(formatted_data.values()) class ThematicReport(Report): """ Historical analyses on the exposure of a portfolio to various GS Flagship Thematic baskets over a date range """ def __init__( self, report_id: str = None, name: str = None, position_source_id: str = None, parameters: ReportParameters = None, position_source_type: Union[str, PositionSourceType] = None, report_type: Union[str, ReportType] = None, earliest_start_date: dt.date = None, latest_end_date: dt.date = None, latest_execution_time: dt.datetime = None, status: Union[str, ReportStatus] = ReportStatus.new, percentage_complete: float = None, **kwargs, ): """ Historical analyses on the exposure of a portfolio to various GS Flagship Thematic baskets over a date range :param report_id: Marquee report ID :param name: report name :param position_source_id: position source ID :param parameters: parameters of the report :param position_source_type: position source (i.e. 'Portfolio', 'Asset', or 'Hedge') :param report_type: report type (i.e. 'Portfolio Thematic Analytics' or 'Asset Thematic Analytics') :param earliest_start_date: start date of report :param latest_end_date: end date of report :param latest_execution_time: date of the latest execution :param status: status of of report (i.e. 'ready', 'executing', or 'done') :param percentage_complete: percent of the report that is complete **Examples** >>> thematic_report = ThematicReport( >>> report_id='REPORTID', >>> position_source_type=PositionSourceType.Portfolio, >>> position_source_id='PORTFOLIOID', >>> parameters=None >>> ) """ if position_source_id and not position_source_type: position_source_type = ( PositionSourceType.Portfolio if position_source_id.startswith('MP') else PositionSourceType.Asset ) if position_source_type and not report_type: report_type = ( ReportType.Portfolio_Thematic_Analytics if (position_source_type is PositionSourceType.Portfolio) else (ReportType.Asset_Thematic_Analytics) ) super().__init__( report_id, name, position_source_id, position_source_type, report_type, parameters, earliest_start_date, latest_end_date, latest_execution_time, status, percentage_complete, ) @classmethod def get(cls, report_id: str, **kwargs): """ Get a thematic report from the unique report identifier :param report_id: Marquee report ID :return: returns a ThematicReport object that correlates to the Marquee report """ return cls.from_target(GsReportApi.get_report(report_id)) @classmethod def from_target(cls, report: TargetReport): if report.type not in [ReportType.Portfolio_Thematic_Analytics, ReportType.Asset_Thematic_Analytics]: raise MqValueError('This report is not a thematic report.') return ThematicReport( report_id=report.id, name=report.name, position_source_id=report.position_source_id, parameters=report.parameters, position_source_type=report.position_source_type, report_type=report.type, earliest_start_date=report.earliest_start_date, latest_end_date=report.latest_end_date, latest_execution_time=report.latest_execution_time, status=report.status, percentage_complete=report.percentage_complete, ) def get_thematic_data( self, start_date: dt.date = None, end_date: dt.date = None, basket_ids: List[str] = None ) -> pd.DataFrame: """ Get all results from the thematic report for a date range :param start_date: start date :param end_date: end date :param basket_ids: optional list of thematic basket IDs to include; defaults to all of them :return: a Pandas DataFrame with results """ results = self._get_measures( ["thematicExposure", "grossExposure"], start_date, end_date, basket_ids, ReturnFormat.JSON ) for result in results: result['thematicBeta'] = result['thematicExposure'] / result['grossExposure'] return pd.DataFrame(results).filter(items=['date', 'thematicExposure', 'thematicBeta']) def get_thematic_exposure( self, start_date: dt.date = None, end_date: dt.date = None, basket_ids: List[str] = None ) -> pd.DataFrame: """ Get portfolio historical exposure to GS Flagship Thematic baskets :param start_date: start date :param end_date: end date :param basket_ids: optional list of thematic basket IDs to include; defaults to all of them :return: a Pandas DataFrame with results """ return self._get_measures(["thematicExposure"], start_date, end_date, basket_ids) def get_thematic_betas( self, start_date: dt.date = None, end_date: dt.date = None, basket_ids: List[str] = None ) -> pd.DataFrame: """ Get portfolio historical beta to GS Flagship Thematic baskets :param start_date: start date :param end_date: end date :param basket_ids: optional list of thematic basket IDs to include; defaults to all of them :return: a Pandas DataFrame with results """ results = self._get_measures( ["thematicExposure", "grossExposure"], start_date, end_date, basket_ids, ReturnFormat.JSON ) for result in results: result['thematicBeta'] = result['thematicExposure'] / result['grossExposure'] result.pop('thematicExposure') result.pop('grossExposure') return pd.DataFrame(results) def _get_measures( self, fields: List, start_date: dt.date = None, end_date: dt.date = None, basket_ids: List[str] = None, return_format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[Dict, pd.DataFrame]: where = {'reportId': self.id} if basket_ids: where['basketId'] = basket_ids dataset = ( ReportDataset.PTA_DATASET.value if self.position_source_type == PositionSourceType.Portfolio else ReportDataset.ATA_DATASET.value ) query = DataQuery(where=where, fields=fields, start_date=start_date, end_date=end_date) results = GsDataApi.query_data(query=query, dataset_id=dataset) return pd.DataFrame(results) if return_format == ReturnFormat.DATA_FRAME else results def get_all_thematic_exposures( self, start_date: dt.date = None, end_date: dt.date = None, basket_ids: List[str] = None, regions: List[Region] = None, ) -> pd.DataFrame: """ Get all portfolio thematic analytics for GS Flagshop Thematic baskets :param start_date: start date :param end_date: end date :param basket_ids: optional list of thematic basket IDs to include; defaults to all of them :param regions: regions by which to filter flagship thematic baskets; defaults to all regions :return: a Pandas DataFrame with results """ results = GsThematicApi.get_thematics( entity_id=self.position_source_id, start_date=start_date, end_date=end_date, basket_ids=basket_ids, regions=regions, measures=[ThematicMeasure.ALL_THEMATIC_EXPOSURES], ) return flatten_results_into_df(results) def get_top_five_thematic_exposures( self, start_date: dt.date = None, end_date: dt.date = None, basket_ids: List[str] = None, regions: List[Region] = None, ) -> pd.DataFrame: """ Get portfolio thematic analytics for the five GS Flagship Thematic baskets with the highest thematic exposures :param start_date: start date :param end_date: end date :param basket_ids: optional list of thematic basket IDs to include; defaults to all of them :param regions: regions by which to filter flagship thematic baskets; defaults to all regions :return: a Pandas DataFrame with the top 5 results """ results = GsThematicApi.get_thematics( entity_id=self.position_source_id, start_date=start_date, end_date=end_date, basket_ids=basket_ids, regions=regions, measures=[ThematicMeasure.TOP_FIVE_THEMATIC_EXPOSURES], ) return flatten_results_into_df(results) def get_bottom_five_thematic_exposures( self, start_date: dt.date = None, end_date: dt.date = None, basket_ids: List[str] = None, regions: List[Region] = None, ) -> pd.DataFrame: """ Get portfolio thematic analytics for the five GS Flagship Thematic baskets with the lowest thematic exposures :param start_date: start date :param end_date: end date :param basket_ids: optional list of thematic basket IDs to include; defaults to all of them :param regions: regions by which to filter flagship thematic baskets; defaults to all regions :return: a Pandas DataFrame with the bottom 5 results """ results = GsThematicApi.get_thematics( entity_id=self.position_source_id, start_date=start_date, end_date=end_date, basket_ids=basket_ids, regions=regions, measures=[ThematicMeasure.BOTTOM_FIVE_THEMATIC_EXPOSURES], ) return flatten_results_into_df(results) def get_thematic_breakdown(self, date: dt.date, basket_id: str) -> pd.DataFrame: """ Get a by-asset breakdown of a portfolio or basket's thematic exposure to a particular flagship basket on a particular date :param date: date :param basket_id: GS flagship basket's unique Marquee ID :return: a Pandas DataFrame with results """ return get_thematic_breakdown_as_df(entity_id=self.position_source_id, date=date, basket_id=basket_id) def get_thematic_breakdown_as_df(entity_id: str, date: dt.date, basket_id: str) -> pd.DataFrame: """ Get a by-asset breakdown of a portfolio or basket's thematic exposure to a particular flagship basket on a particular data and return as a Pandas DataFrame :param entity_id: position source ID; i.e. portfolio or basket :param date: date :param basket_id: GS flagship basket's unique Marquee ID :return: a Pandas DataFrame with results """ results = GsThematicApi.get_thematics( entity_id=entity_id, start_date=date, end_date=date, basket_ids=[basket_id], measures=[ThematicMeasure.THEMATIC_BREAKDOWN_BY_ASSET], ) breakdown = ( results[0] .get(ThematicMeasure.THEMATIC_BREAKDOWN_BY_ASSET.value, [{}])[0] .get(ThematicMeasure.THEMATIC_BREAKDOWN_BY_ASSET.value, []) ) formatted_breakdown = [] for data in breakdown: formatted_data = {titleize(k): data[k] for k in data} formatted_breakdown.append(formatted_data) return pd.DataFrame(formatted_breakdown) def flatten_results_into_df(results: List): """ Flatten a list of thematic data into a Pandas DataFrame :param results: a list of thematic data by date :return: Pandas DataFrame with all results formatted """ all_results = [] for result in results: date = result['date'] for key in result: if isinstance(result[key], list): for thematic_data in result[key]: all_results.append({'Date': date, **{titleize(k): thematic_data[k] for k in thematic_data}}) all_results = pd.DataFrame(all_results).rename(columns={'Basket': 'Basket Id'}) return pd.DataFrame(all_results) def get_pnl_percent( performance_report: PerformanceReport, pnl_df: pd.DataFrame, field: str, start_date: dt.datetime.date, end_date: dt.datetime.date, ): aum_df = format_aum_for_return_calculation(performance_report, start_date, end_date) is_first_data_point_on_start_date = pnl_df['date'].iloc[[0]].values[0] == start_date.strftime('%Y-%m-%d') return_series = generate_daily_returns(aum_df, pnl_df, 'aum', field, is_first_data_point_on_start_date) return (return_series.add(1).cumprod() - 1).multiply(100) def get_factor_pnl_percent_for_single_factor(factor_data, total_data, aum_df, start_date): pnl_df = format_factor_pnl_for_return_calculation(factor_data, total_data) is_start_date_first_data_point = pnl_df['date'].iloc[[0]].values[0] == start_date.strftime('%Y-%m-%d') return generate_daily_returns(aum_df, pnl_df, 'aum', 'pnl', is_start_date_first_data_point) def format_factor_pnl_for_return_calculation(factor_data: list, total_data: list): pnl_df = pd.DataFrame(factor_data)[['date', 'pnl']] total_returns_df = pd.DataFrame(total_data)[['date', 'pnl']] total_returns_df = total_returns_df.rename(columns={'pnl': 'totalPnl'}) pnl_df = pd.merge(pnl_df, total_returns_df, how='inner', on=['date']) return pnl_df def format_aum_for_return_calculation( performance_report: PerformanceReport, start_date: dt.datetime.date, end_date: dt.datetime.date ): aum_as_dict = performance_report.get_aum(start_date=prev_business_date(start_date), end_date=end_date) aum_df = pd.DataFrame(aum_as_dict.items(), columns=['date', 'aum']) return aum_df def generate_daily_returns( aum_df: pd.DataFrame, pnl_df: pd.DataFrame, aum_col_key: str, pnl_col_key: str, is_start_date_first_data_point: bool ): # Returns are defined as Pnl today divided by AUM yesterday. if is_start_date_first_data_point: pnl_df.loc[0, pnl_col_key] = 0 if 'totalPnl' in pnl_df.columns: pnl_df.loc[0, 'totalPnl'] = 0 df = pd.merge(pnl_df, aum_df, how='outer', on='date') df = df.set_index('date') df = df.sort_index() df[aum_col_key] = df[aum_col_key].ffill() df['return'] = df[pnl_col_key].div(df[aum_col_key].shift(1)) if 'totalPnl' in df.columns: df['totalPnl'] = df['totalPnl'].div(df[aum_col_key].shift(1)) df = df.fillna(0) df['return'] = __smooth_percent_returns(df['return'].to_numpy(), df['totalPnl'].to_numpy()).tolist() return_series = pd.Series(df['return'], name="return").dropna() return return_series def __smooth_percent_returns(daily_factor_returns: np.array, daily_total_returns: np.array) -> np.array: """ When attribution (in weights) are decomposed among multiple factors (like a group of risk model factors or categories), simple geometric aggregation will not preserve additivity. In other words, the geometric sum of factor PnL from Factor and Specific will NOT add up to the factor PnL from Total. The Carino log linking formula calculates the coefficients for each node, and after that multiplication is done, the results can be aggregated to calculate cumulative PnL: https://rdrr.io/github/R-Finance/PortfolioAttribution/man/Carino.html βt = A * αt where βt is the linking coefficient on date t where A is the log scaling factor (which is constant throughout the timeseries) where αt is the perturbation factor on date t A = (Rp - Rb) / (ln(1 + Rp) - ln(1 + Rb)) where Rp is the total portfolio return ; Rb is the total benchmark return αt = (ln(1 + Rpt) - ln(1 + Rbt)) / (Rpt - Rbt) where Rpt is portfolio return on t and Rbp is benchmark return on t For this use case benchmark returns are set to 0 for every day. """ total_return = np.prod(daily_total_returns + 1) - 1 log_scaling_factor = total_return / (np.log(1 + total_return)) if total_return != 0 else 1 perturbation_factors = np.log(1 + daily_total_returns) / daily_total_returns perturbation_factors = np.nan_to_num(perturbation_factors, nan=1) return np.cumsum(daily_factor_returns * log_scaling_factor * perturbation_factors * 100) def _filter_table_by_factor_and_category(column_info: Dict, factors: List, factor_categories: List): if factors is None and factor_categories is None: sorted_columns = [] for column_group in column_info: sorted_columns = sorted_columns + column_group.get('columns') else: sorted_columns = column_info[0].get('columns') + column_info[1].get('columns') if factors is not None: sorted_columns = sorted_columns + factors if factor_categories is not None: for column_group in [ column_group for column_group in column_info if column_group.get('columnGroup') in factor_categories ]: sorted_columns = sorted_columns + column_group.get('columns') return sorted_columns ================================================ FILE: gs_quant/markets/report_utils.py ================================================ """ Copyright 2021 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import pandas as pd import math from typing import List from pandas.tseries.offsets import BDay def _get_ppaa_batches(asset_count: pd.DataFrame, max_row_limit: int) -> List[List[dt.date]]: start_row = asset_count.iloc[0] end_row = asset_count.iloc[-1] avg_positions = start_row['assetCount'] + end_row['assetCount'] / 2 start_date = dt.datetime.strptime(start_row['date'], '%Y-%m-%d').date() end_date = dt.datetime.strptime(end_row['date'], '%Y-%m-%d').date() # multiply by 5 because of # fields: pnl, exposure, asset id, report id, date days_per_batch = math.ceil(max_row_limit / (avg_positions * 5)) return _batch_dates(start_date, end_date, days_per_batch) def _batch_dates(start_date: dt.date, end_date: dt.date, batch_size: int) -> List[List[dt.date]]: if (start_date - end_date).days < batch_size: return [[start_date, end_date]] date_list = [] curr_end = start_date while end_date > curr_end: curr_end = (start_date + BDay(batch_size)).date() curr_end = curr_end if curr_end < end_date else end_date date_batches = [start_date, curr_end] date_list.append(date_batches) start_date = curr_end + dt.timedelta(days=1) return date_list ================================================ FILE: gs_quant/markets/scenario.py ================================================ """ Copyright 2024 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.api.gs.scenarios import GsFactorScenarioApi from gs_quant.markets.factor import Factor from gs_quant.target.risk import Scenario as TargetScenario, FactorScenarioType from gs_quant.entities.entitlements import Entitlements from gs_quant.errors import MqValueError from enum import Enum from typing import List, Union, Dict from pydash import get from copy import deepcopy import datetime as dt import pandas as pd class ScenarioCalculationType(Enum): FACTOR_SCENARIO = "Factor Scenario" class FactorShock: """Marquee Factor Shock""" def __init__(self, factor: Union[str, Factor], shock: float): self.__factor = factor self.__shock = shock def __eq__(self, other) -> bool: if not isinstance(other, FactorShock): return False factor_name = self.factor.name if isinstance(self.factor, Factor) else self.factor other_factor_name = other.factor.name if isinstance(other.factor, Factor) else other.factor return factor_name == other_factor_name and self.shock == other.shock def __repr__(self): return '%r(factor=%r, shock=%r)' % (self.__class__.__name__, self.factor, self.shock) @property def factor(self) -> Union[str, Factor]: """Get factor being shocked""" return self.__factor @property def shock(self) -> float: """Get factor being shocked""" return self.__shock @shock.setter def shock(self, shock: float): self.__shock = shock def to_dict(self): return {"factor": self.factor.name if isinstance(self.factor, Factor) else self.factor, "shock": self.shock} @classmethod def from_dict(cls, obj): return FactorShock(factor=obj.get("factor"), shock=obj.get("shock")) class FactorShockParameters: def __init__(self, factor_shocks: List[FactorShock] = None, propagate_shocks: bool = None, risk_model: str = None): self.__factor_shocks = factor_shocks self.__propagate_shocks = propagate_shocks self.__risk_model = risk_model def __eq__(self, other) -> bool: if not isinstance(other, FactorShockParameters): return False return ( self.factor_shocks == other.factor_shocks and self.propagate_shocks == other.propagate_shocks and self.risk_model == other.risk_model ) def __repr__(self): return '%r(risk_model=%r, propagate_shocks=%r, factor_shocks=%r)' % ( self.__class__.__name__, self.risk_model, self.propagate_shocks, self.factor_shocks, ) @property def factor_shocks(self) -> List[FactorShock]: return self.__factor_shocks @factor_shocks.setter def factor_shocks(self, factor_shocks: Union[List[FactorShock], Dict, pd.DataFrame]): if isinstance(factor_shocks, pd.DataFrame): factor_shocks_as_dict = factor_shocks.to_dict(orient='split') self.__factor_shocks = [ FactorShock(factor=f, shock=s) for f, s in zip(factor_shocks_as_dict.get('columns'), factor_shocks_as_dict.get('data')) ] elif isinstance(factor_shocks, Dict): self.__factor_shocks = [FactorShock(factor=k, shock=v) for k, v in factor_shocks.items()] else: self.__factor_shocks = factor_shocks @property def propagate_shocks(self) -> bool: return self.__propagate_shocks @propagate_shocks.setter def propagate_shocks(self, propagate_shocks: bool): self.__propagate_shocks = propagate_shocks @property def risk_model(self) -> str: return self.__risk_model @classmethod def from_dict(cls, obj: Dict) -> 'FactorShockParameters': return cls( factor_shocks=[FactorShock.from_dict(f_shock) for f_shock in obj.get('factorShocks')], risk_model=obj.get("riskModel"), propagate_shocks=obj.get("propagateShocks"), ) def to_dict(self) -> Dict: return { "riskModel": self.risk_model, "propagateShocks": self.propagate_shocks, "factorShocks": [f_shock.to_dict() for f_shock in self.factor_shocks], } class HistoricalSimulationParameters: def __init__(self, start_date: dt.date = None, end_date: dt.date = None): self.__start_date = start_date self.__end_date = end_date def __eq__(self, other) -> bool: if not isinstance(other, HistoricalSimulationParameters): return False return self.start_date == other.start_date and self.end_date == other.end_date def __repr__(self): return '%r(start_date=%r, end_date=%r)' % (self.__class__.__name__, self.start_date, self.end_date) @property def start_date(self) -> dt.date: return self.__start_date @start_date.setter def start_date(self, start_date: dt.date): self.__start_date = start_date @property def end_date(self) -> dt.date: return self.__end_date @end_date.setter def end_date(self, end_date: dt.date): self.__end_date = end_date @classmethod def from_dict(cls, obj: Dict) -> 'HistoricalSimulationParameters': return cls( start_date=dt.datetime.strptime(obj.get('startDate'), "%Y-%m-%d").date(), end_date=dt.datetime.strptime(obj.get('endDate'), "%Y-%m-%d").date(), ) def to_dict(self) -> Dict: return {"startDate": self.start_date, "endDate": self.end_date} ScenarioParameters = Union[FactorShockParameters, HistoricalSimulationParameters] class FactorScenario: """Marquee Factor-based Scenario""" def __init__( self, name: str, type: Union[str, FactorScenarioType], parameters: Union[Dict, HistoricalSimulationParameters, FactorShockParameters], entitlements: Union[Dict, Entitlements] = None, id_: str = None, description: str = None, tags: List[str] = None, ): self.__id = id_ self.__name = name self.__type = type self.__description = description self.__parameters = ( parameters if any( [isinstance(parameters, FactorShockParameters), isinstance(parameters, HistoricalSimulationParameters)] ) else FactorShockParameters.from_dict(parameters) if type == FactorScenarioType.Factor_Shock else HistoricalSimulationParameters.from_dict(parameters) ) self.__entitlements = entitlements self.__tags = tags def __repr__(self): instance_repr = '%r(id=%r, name=%r, description=%r, type=%r, parameters=%r, entitlements=%r, tags=%r)' % ( self.__class__.__name__, self.id, self.name, self.description, self.type, self.parameters.__repr__(), self.entitlements.__repr__(), self.tags, ) return instance_repr def __str__(self): s = "{}('id={}', 'name={}', 'description={}', 'type={}', 'parameters={}'".format( self.__class__.__name__, self.id, self.name, self.description, self.type.value, self.parameters.__repr__() ) s += ")" return s @property def id(self) -> str: return self.__id @property def name(self) -> str: return self.__name @name.setter def name(self, name: str): self.__name = name @property def type(self) -> Union[str, FactorScenarioType]: return self.__type @property def description(self) -> str: return self.__description @description.setter def description(self, description: str): self.__description = description @property def parameters(self) -> ScenarioParameters: return self.__parameters @parameters.setter def parameters(self, parameters: ScenarioParameters): self.__parameters = parameters @property def entitlements(self) -> Entitlements: return self.__entitlements @entitlements.setter def entitlements(self, entitlements: Union[Dict, Entitlements]): self.__entitlements = entitlements @property def tags(self) -> List[str]: return self.__tags @tags.setter def tags(self, tags: List[str]): self.__tags = tags @classmethod def from_dict(cls, scenario_as_dict: Dict) -> 'FactorScenario': scenario_data = { "name": scenario_as_dict.get('name'), "description": scenario_as_dict.get('description'), "id_": scenario_as_dict.get('id'), "type": scenario_as_dict.get('type'), "parameters": get(scenario_as_dict, 'parameters', None), "entitlements": get(scenario_as_dict, 'entitlements', None), "tags": scenario_as_dict.get('tags'), } return cls(**scenario_data) @classmethod def from_target(cls, target_scenario: TargetScenario): parameters = ( FactorShockParameters.from_dict(target_scenario.parameters) if target_scenario.type == FactorScenarioType.Factor_Shock else HistoricalSimulationParameters.from_dict(target_scenario.parameters) ) scenario = cls( id_=target_scenario.id, name=target_scenario.name, type=target_scenario.type, parameters=parameters, entitlements=Entitlements.from_target(target_scenario.entitlements), description=target_scenario.description, tags=target_scenario.tags, ) return scenario @classmethod def get(cls, scenario_id: str) -> 'FactorScenario': """ Get a scenario by its name :param scenario_id: The ID of the scenario :return: Instance of factor scenario :func:`get_by_name` :func:`get_many` """ scenario = GsFactorScenarioApi.get_scenario(scenario_id) return cls.from_target(scenario) @classmethod def get_by_name(cls, scenario_name: str) -> 'FactorScenario': """ Get a scenario by its name :param scenario_name: The name of the scenario :return: Instance of factor scenario :func:`get_many` :func:`get` """ scenario = GsFactorScenarioApi.get_scenario_by_name(scenario_name) return cls.from_target(scenario) @classmethod def get_many( cls, ids: List[str] = None, names: List[str] = None, type: Union[str, FactorScenarioType] = None, risk_model: str = None, shocked_factors: List[str] = None, shocked_factor_categories: List[str] = None, propagated_shocks: bool = None, start_date: dt.date = None, end_date: dt.date = None, tags: List[str] = None, limit: int = 100, ) -> List['FactorScenario']: """ Get many factor scenarios from Marquee :param ids: List of unique Marquee Identifiers :param names: Names of requested scenarios :param type: The type of the scenario :param risk_model: Filter by the risk model :param shocked_factors: Return scenarios that shock requested factors. :param shocked_factor_categories: Filter scenarios that shock factors in the requested factor categories :param propagated_shocks: Return factor shock scenarios that propagate initial factor shocks to other non-shocked factors :param start_date: Return historical simulation scenarios with an start date that is greater or equal than this date. This is only applicable to historical simulation :param end_date: Return historical simulation scenarios with an end date that is lower or equal than this date. This is only applicable to historical simulation :param tags: Returns scenario with any of the requested tags :param limit: limit of number of models in response :return: list of Factor Shock scenario **Usage** >>> many_factor_shock_scenarios = FactorScenario.get_many(risk_model="MODEL_ID", ... propagated_shocks=True, ... type="Factor Shock") >>> value_shocks_scenarios = FactorScenario.get_many(risk_model="AXIOMA_AXWW4M", ... shocked_factors=["Value"], ... type="Factor Shock") >>> many_historical_simulation_scenarios = FactorScenario.get_many(type="Factor Historical Simulation", ... start_date=dt.date(2010, 1, 1), ... end_date=dt.date(2024, 1, 1)) **See also** :func:`get_by_name` :func:`get` """ many_scenarios_as_dict = GsFactorScenarioApi.get_many_scenarios( ids=ids, names=names, type=type.value if isinstance(type, FactorScenarioType) else type, risk_model=risk_model, shocked_factors=shocked_factors, shocked_factor_categories=shocked_factor_categories, start_date=start_date, end_date=end_date, tags=tags, limit=limit, ) all_scenarios = [cls.from_target(scenario_as_target) for scenario_as_target in many_scenarios_as_dict] if propagated_shocks is not None: return [ scenario for scenario in all_scenarios if (scenario.parameters.propagate_shocks == propagated_shocks) or scenario.type != FactorScenarioType.Factor_Shock ] return all_scenarios def save(self): """Update factor scenario or Create it if it does not exist""" target_scenario = TargetScenario( name=self.name, type_=self.type, description=self.description if self.description else None, parameters=self.parameters.to_dict(), entitlements=self.entitlements.to_target() if self.entitlements else None, tags=tuple(self.tags) if self.tags else (), ) if self.id: target_scenario.id_ = self.id GsFactorScenarioApi.update_scenario(target_scenario) else: scenario = GsFactorScenarioApi.create_scenario(target_scenario) self.__id = scenario.id def delete(self): """Deletes factor scenario from Marquee""" if not self.id: raise MqValueError("Cannot delete scenario that has not been created in Marquee") GsFactorScenarioApi.delete_scenario(self.id) def clone(self): """ Clones an existing scenario :return: New scenario instance with identical parameters **Usage** >>> from gs_quant.markets.scenario import FactorScenario >>> >>> initial_scenario = FactorScenario.get("SCENARIO_ID") >>> clone = initial_scenario.clone() >>> clone.save() **See also** :func:`save` """ parameters = deepcopy(self.parameters) return FactorScenario( name=f"{self.name} copy", description=self.description, type=self.type, parameters=parameters ) Scenario = Union[FactorScenario] ================================================ FILE: gs_quant/markets/screens.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import logging from enum import Enum, unique from typing import Union, Tuple import pandas as pd from pydash import set_, get from gs_quant.api.gs.screens import GsScreenApi from gs_quant.errors import MqValueError from gs_quant.target.assets_screener import ( AssetScreenerCreditRequestFilters, AssetScreenerRequest, AssetScreenerRequestFilterLimits, AssetScreenerRequestStringOptions, ) from gs_quant.target.screens import Screen as TargetScreen, ScreenParameters as TargetScreenParameters from gs_quant.common import Currency as CurrencyImport logging.root.setLevel('INFO') class RangeFilter: """Respresents asset filters that are ranges""" def __init__(self, min_: Union[float, str] = None, max_: Union[float, str] = None): self.__min = min_ self.__max = max_ def __str__(self) -> str: range_filter = f'{{Min: {self.min}, Max: {self.max}}}' return range_filter @property def min(self) -> Union[float, str]: return self.__min @min.setter def min(self, value: Union[float, str]): self.__min = value @property def max(self) -> Union[float, str]: return self.__max @max.setter def max(self, value: Union[float, str]): self.__max = value @unique class CheckboxType(Enum): INCLUDE = "Include" EXCLUDE = "Exclude" @unique class Sector(Enum): COMMUNICATION_SERVICES = "Communication Services" CONSUMER_DISCRETIONARY = "Consumer Discretionary" CONSUMER_STAPLES = "Consumer Staples" ENERGY = "Energy" FINANCIALS = "Financials" HEALTH_CARE = "Health Care" INDUSTRIALS = "Industrials" INFORMATION_TECHNOLOGY = "Information Technology" MATERIALS = "Materials" REAL_ESTATE = "Real Estate" UTILITIES = "Utilities" @unique class Seniority(Enum): JUNIOR_SUBORDINATE = "Junior Subordinate" SENIOR = "Senior" SENIOR_SUBORDINATE = "Senior Subordinate" SUBORDINATE = "Subordinate" @unique class Direction(Enum): BUY = "Buy" SELL = "Sell" @unique class Currency(CurrencyImport, Enum): pass class CheckboxFilter: """Represents asset filters that have multiple enumerated options""" def __init__(self, checkbox_type: CheckboxType = None, selections: Tuple[Enum, ...] = None): self.__selections = selections self.__checkbox_type = checkbox_type def __str__(self) -> str: checkbox_filter = f'{{Type: {self.checkbox_type}, Selections: {self.selections}}}' return checkbox_filter @property def checkbox_type(self) -> CheckboxType: return self.__checkbox_type @checkbox_type.setter def checkbox_type(self, value: CheckboxType): self.__checkbox_type = value @property def selections(self) -> Tuple[Enum, ...]: return self.__selections @selections.setter def selections(self, value: Tuple[Enum, ...]): self.__selections = value def add(self, new_selections: Tuple[Enum, ...]): new_selections = set(new_selections) old_selections = set(self.selections) self.selections = tuple(set(new_selections).union(set(old_selections))) def remove(self, remove_selections: Tuple[Enum, ...]): remove_selections = set(remove_selections) old_selections = set(self.selections) self.selections = tuple(old_selections.difference(remove_selections)) class ScreenFilters: def __init__( self, face_value: float = 1000000, direction: str = "Buy", liquidity_score: RangeFilter = RangeFilter(), gs_charge_bps: RangeFilter = RangeFilter(), gs_charge_dollars: RangeFilter = RangeFilter(), duration: RangeFilter = RangeFilter(), yield_: RangeFilter = RangeFilter(), spread: RangeFilter = RangeFilter(), z_spread: RangeFilter = RangeFilter(), g_spread: RangeFilter = RangeFilter(), mid_price: RangeFilter = RangeFilter(), maturity: RangeFilter = RangeFilter(), amount_outstanding: RangeFilter = RangeFilter(), letter_rating: RangeFilter = RangeFilter(), seniority: CheckboxFilter = CheckboxFilter(), currency: CheckboxFilter = CheckboxFilter(), sector: CheckboxFilter = CheckboxFilter(), ): self.__face_value = face_value self.__direction = direction self.__liquidity_score = liquidity_score self.__gs_charge_bps = gs_charge_bps self.__gs_charge_dollars = gs_charge_dollars self.__duration = duration self.__yield_ = yield_ self.__spread = spread self.__z_spread = z_spread self.__g_spread = g_spread self.__mid_price = mid_price self.__maturity = maturity self.__amount_outstanding = amount_outstanding self.__rating = letter_rating self.__seniority = seniority self.__currency = currency self.__sector = sector def __str__(self) -> str: to_return = {} filter_names = self.__dict__.keys() for name in filter_names: if self.__dict__[name]: to_return[name] = self.__dict__[name].__str__() return str(to_return) @property def face_value(self) -> float: """Face value of the bond.""" return self.__face_value @face_value.setter def face_value(self, value: float): self.__face_value = value @property def direction(self) -> str: """Whether the position is a buy or sell.""" return self.__direction @direction.setter def direction(self, value: str): self.__direction = value @property def liquidity_score(self) -> RangeFilter: """Liquidity score assigned to buying/selling the bond.""" return self.__liquidity_score @liquidity_score.setter def liquidity_score(self, value: RangeFilter): self.__validate_range_settings(min_=1, max_=6, value=self.__liquidity_score) self.__liquidity_score = value @property def gs_charge_bps(self) -> RangeFilter: """Goldman Sachs' indicative charge of the bond (bps).""" return self.__gs_charge_bps @gs_charge_bps.setter def gs_charge_bps(self, value: RangeFilter): self.__validate_range_settings(min_=0, max_=10, value=self.__gs_charge_bps) self.__gs_charge_bps = value @property def gs_charge_dollars(self) -> RangeFilter: """Goldman Sachs' indicative charge of the bond (dollars).""" return self.__gs_charge_dollars @gs_charge_dollars.setter def gs_charge_dollars(self, value: RangeFilter): self.__validate_range_settings(min_=0, max_=2, value=self.__gs_charge_dollars) self.__gs_charge_dollars = value @property def duration(self) -> RangeFilter: """Measure of a bond's price sensitivity to changes in interest rates.""" return self.__duration @duration.setter def duration(self, value: RangeFilter): self.__validate_range_settings(min_=0, max_=20, value=self.__duration) self.__duration = value @property def yield_(self) -> RangeFilter: """Return an investor realizes on a bond sold at the mid price.""" return self.__yield_ @yield_.setter def yield_(self, value: RangeFilter): self.__validate_range_settings(min_=0, max_=10, value=self.__yield_) self.__yield_ = value @property def spread(self) -> RangeFilter: """Spread between the yields of a debt security and its benchmark when both are purchased at bid price.""" return self.__spread @spread.setter def spread(self, value: RangeFilter): self.__validate_range_settings(min_=0, max_=1000, value=self.__spread) self.__spread = value @property def z_spread(self) -> RangeFilter: """Zero volatility spread of a bond.""" return self.__z_spread @z_spread.setter def z_spread(self, value: RangeFilter): self.__z_spread = value @property def g_spread(self) -> RangeFilter: """Difference between yield on treasury bonds and yield on corporate bonds of same maturity.""" return self.__g_spread @g_spread.setter def g_spread(self, value: RangeFilter): self.__g_spread = value @property def mid_price(self) -> RangeFilter: """Mid price.""" return self.__mid_price @mid_price.setter def mid_price(self, value: RangeFilter): self.__validate_range_settings(min_=0, max_=200, value=self.__mid_price) self.__mid_price = value @property def maturity(self) -> RangeFilter: """Length of time bond owner will receive interest payments on the investment.""" return self.__maturity @maturity.setter def maturity(self, value: RangeFilter): self.__validate_range_settings(min_=0, max_=40, value=self.__maturity) self.__maturity = value @property def amount_outstanding(self) -> RangeFilter: """Aggregate principal amount of the total number of bonds not redeemed or otherwise discharged.""" return self.__amount_outstanding @amount_outstanding.setter def amount_outstanding(self, value: RangeFilter): self.__validate_range_settings(min_=0, max_=1000000000, value=self.__amount_outstanding) self.__amount_outstanding = value @property def rating(self) -> RangeFilter: """S&P rating given to a bond.""" return self.__rating @rating.setter def rating(self, value: RangeFilter): self.__rating = value @property def seniority(self) -> CheckboxFilter: """Seniority of the bond.""" return self.__seniority @seniority.setter def seniority(self, value: CheckboxFilter): self.__seniority = value @property def currency(self) -> CheckboxFilter: """Currency of the bond.""" return self.__currency @currency.setter def currency(self, value: CheckboxFilter): self.__currency = value @property def sector(self) -> CheckboxFilter: """Sector / industry of the bond.""" return self.__sector @sector.setter def sector(self, value: CheckboxFilter): self.__sector = value @staticmethod def __validate_range_settings(min_: int, max_: int, value: RangeFilter): if value.min is None and value.max is None: return if value.min < min_ or value.max > max_: raise MqValueError(f'Please ensure your min and max values are in the range of {min} <= x <= {max}') class Screen: """ "Private variables""" def __init__(self, filters: ScreenFilters = None, screen_id: str = None, name: str = None): if not filters: self.__filters = ScreenFilters() else: self.__filters = filters self.__id = screen_id self.__name = name if name is not None else f"Screen {dt.date.today().strftime('%d-%b-%Y')}" @property def id(self) -> str: return self.__id @property def name(self) -> str: return self.__name @name.setter def name(self, name: str): self.__name = name @property def filters(self) -> ScreenFilters: return self.__filters @filters.setter def filters(self, filters: ScreenFilters): self.__filters = filters @classmethod def get(cls, screen_id: str): screen = GsScreenApi.get_screen(screen_id=screen_id) return Screen.__from_target(screen) def calculate(self, format_: str = None): """Applies screen filters, returning assets that satisfy the condition(s)""" filters = self.__to_target_filters() payload = AssetScreenerRequest(filters=filters) assets = GsScreenApi.calculate(payload) dataframe = pd.DataFrame(assets) if format_ == 'json': return dataframe['results'].to_json(indent=4) if format_ == 'csv': return dataframe.to_csv() return dataframe def save(self): """Create a screen using GsScreenApi if it doesn't exist. Update the report if it does.""" parameters = self.__to_target_parameters() target_screen = TargetScreen(name=self.name, parameters=parameters) if self.id: target_screen.id = self.id GsScreenApi.update_screen(target_screen) else: screen = GsScreenApi.create_screen(target_screen) self.__id = screen.id logging.info(f'New screen created with ID: {self.id} \n') def delete(self): """Hits GsScreensApi to delete a report""" GsScreenApi.delete_screen(self.id) @classmethod def __from_target(cls, screen): return Screen(filters=screen.parameters, screen_id=screen.id, name=screen.name) def __to_target_filters(self) -> AssetScreenerCreditRequestFilters: payload = {} filters = self.__set_up_filters() for name in filters: if name == 'face_value' or name == 'direction': payload[name] = filters[name] elif isinstance(filters[name], RangeFilter): payload[name] = AssetScreenerRequestFilterLimits(min_=filters[name].min, max_=filters[name].max) elif isinstance(filters[name], CheckboxFilter): if filters[name].selections and filters[name].checkbox_type: payload[name] = AssetScreenerRequestStringOptions( options=filters[name].selections, type_=filters[name].checkbox_type ) return AssetScreenerCreditRequestFilters(**payload) def __set_up_filters(self) -> dict: filters = {} for prop in AssetScreenerCreditRequestFilters.properties(): set_(filters, prop, get(self.__filters, prop)) return filters def __to_target_parameters(self) -> TargetScreenParameters: payload = {} parameters = self.__set_up_parameters() for name in parameters: if name == 'face_value' or name == 'direction': payload[name] = parameters[name] elif isinstance(parameters[name], RangeFilter): payload[name] = AssetScreenerRequestFilterLimits(min_=parameters[name].min, max_=parameters[name].max) elif isinstance(parameters[name], CheckboxFilter): if parameters[name].selections and parameters[name].checkbox_type: payload[name] = parameters[name].selections return TargetScreenParameters(**payload) def __set_up_parameters(self) -> dict: filter_to_parameter = { 'face_value': 'face_value', 'direction': 'direction', 'gs_liquidity_score': 'liquidity_score', 'gs_charge_bps': 'gs_charge_bps', 'gs_charge_dollars': 'gs_charge_dollars', 'modified_duration': 'duration', 'yield_to_convention': 'yield_', 'spread_to_benchmark': 'spread', 'z_spread': 'z_spread', 'g_spread': 'g_spread', 'bval_mid_price': 'mid_price', 'maturity': 'maturity', 'amount_outstanding': 'amount_outstanding', 'rating_standard_and_poors': 'rating', 'seniority': 'seniority', 'currency': 'currency', 'sector': 'sector', 'issue_date': 'issue_date', } parameters = {} for prop in TargetScreenParameters.properties(): set_(parameters, prop, get(self.__filters, filter_to_parameter[prop])) return parameters ================================================ FILE: gs_quant/markets/securities.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import calendar import datetime as dt import json import logging import threading import time from abc import ABCMeta, abstractmethod from collections import defaultdict from copy import deepcopy from enum import auto, Enum from functools import partial from typing import Tuple, Generator, Iterable, Optional, Dict, List, Union import backoff import cachetools import pandas as pd from dateutil.relativedelta import relativedelta from pydash import get from gs_quant.api.gs.assets import GsAsset, GsIdType, GsAssetApi from gs_quant.api.gs.data import GsDataApi from gs_quant.api.utils import ThreadPoolManager from gs_quant.base import get_enum_value from gs_quant.common import AssetClass, AssetParameters, AssetType as GsAssetType, Currency, DateLimit from gs_quant.context_base import nullcontext from gs_quant.data import DataMeasure, DataFrequency, Dataset, AssetMeasure from gs_quant.data.coordinate import DataDimensions, DateOrDatetime from gs_quant.data.core import IntervalFrequency, DataAggregationOperator from gs_quant.entities.entity import Entity, EntityIdentifier, EntityType, PositionedEntity from gs_quant.errors import MqValueError, MqTypeError, MqRequestError from gs_quant.json_encoder import JSONEncoder from gs_quant.markets import PricingContext from gs_quant.markets.indices_utils import BasketType, IndicesDatasets from gs_quant.session import GsSession from gs_quant.target.data import DataQuery from gs_quant.tracing import Tracer _logger = logging.getLogger(__name__) class ExchangeCode(Enum): """Exchange enumeration Exchange codes representing global venues where Securities are listed and traded """ NASDAQ = "NASD" # Nasdaq Global Stock Market NYSE = "NYSE" # New York Stock Exchange class AssetType(Enum): """Asset type enumeration Enumeration of different types of asset or security. """ #: Index which tracks an evolving portfolio of securities, and can be traded through cash or derivatives markets INDEX = "Index" #: Exchange traded fund which tracks an evolving portfolio of securities and is listed on an exchange to be #: traded as a security ETF = "ETF" #: Bespoke basket which provides exposure to a customized collection of assets with levels published daily; can be #: traded on swap and rebalanced programmatically CUSTOM_BASKET = "Custom Basket" #: Bespoke basket which provides exposure to a customized collection of assets with levels published daily; #: basket composition maintained by Goldman Sachs Investment Research RESEARCH_BASKET = "Research Basket" #: Listed equities which provide access to equity holding in a company and participation in dividends and other #: distributions in common, preferred or other variants which provide different investor rights STOCK = "Single Stock" #: Standardized listed contract which provides delivery of an asset at a pre-defined forward date and can be #: settled in cash or physical form FUTURE = "Future" #: FX cross or currency pair CROSS = "Cross" #: Currency CURRENCY = "Currency" #: Rate RATE = "Rate" #: Cash CASH = "Cash" #: Weather Index WEATHER_INDEX = "Weather Index" #: Swap SWAP = "Swap" #: Swaption SWAPTION = "Swaption" #: Option OPTION = "Option" #: Binary BINARY = "Binary" #: Commodity Reference Price COMMODITY_REFERENCE_PRICE = "Commodity Reference Price" # COMMODITY NATURAL GAS Hub COMMODITY_NATURAL_GAS_HUB = "Commodity Natural Gas Hub" # COMMODITY EU NATURAL GAS Hub COMMODITY_EU_NATURAL_GAS_HUB = "Commodity EU Natural Gas Hub" #: Commodity Power Node COMMODITY_POWER_NODE = "Commodity Power Node" #: Commodity Power Aggregated Nodes COMMODITY_POWER_AGGREGATED_NODES = "Commodity Power Aggregated Nodes" #: Bond BOND = "Bond" #: Future Market FUTURE_MARKET = "Future Market" #: Future Contract FUTURE_CONTRACT = "Future Contract" #: Commodity COMMODITY = "Commodity" #: Crypto CRYPTOCURRENCY = "Cryptocurrency" #: Forward FORWARD = "Forward" #: Fund FUND = "Fund" #: Default Swap DEFAULT_SWAP = "Default Swap" #: Systematic Hedging SYSTEMATIC_HEDGING = 'Systematic Hedging' #: Access ACCESS = 'Access' #: Risk Premia RISK_PREMIA = 'Risk Premia' #: Multi Asset Allocation MULTI_ASSET_ALLOCATION = 'Multi-Asset Allocation' # Sec Master types ADR = 'ADR' GDR = 'GDR' DUTCH_CERT = 'Dutch Cert' NYRS = 'NY Reg Shrs' RECEIPT = 'Receipt' UNIT = 'Unit' MUTUAL_FUND = 'Mutual Fund' RIGHT = 'Right' PREFERRED = 'Preferred' MISC = 'Misc.' REIT = 'REIT' PRIVATE_COMP = 'Private Comp' PREFERENCE = 'Preference' LIMITED_PARTNERSHIP = 'Ltd Part' TRACKING_STOCK = 'Tracking Stk' ROYALTY_TRUST = 'Royalty Trst' CLOSED_END_FUND = 'Closed-End Fund' OPEN_END_FUND = 'Open-End Fund' FUND_OF_FUNDS = 'Fund of Funds' MLP = 'MLP' STAPLED_SECURITY = 'Stapled Security' SAVINGS_SHARE = 'Savings Share' EQUITY_WRT = 'Equity WRT' # ETF already defined SAVINGS_PLAN = 'Savings Plan' EQUITY_INDEX = 'Equity Index' COMMON_STOCK = 'Common Stock' class AssetIdentifier(EntityIdentifier): """Asset type enumeration Enumeration of different security identifiers """ MARQUEE_ID = "MQID" #: Goldman Sachs Marquee identifier code (MA4B66MW5E27UAHKG34) REUTERS_ID = "RIC" #: Thompson Reuters Instrument Code (RIC), (GS.N) BLOOMBERG_ID = "BBID" #: Bloomberg identifier and exchange code (GS UN) BLOOMBERG_COMPOSITE_ID = "BCID" #: Bloomberg composite identifier and exchange code (GS US) CUSIP = "CUSIP" #: Committee on Uniform Security Identification Procedures code (38141G104) ISIN = "ISIN" #: International Securities Identification Number (US38141G1040) SEDOL = "SEDOL" #: LSE Stock Exchange Daily Official List code (2407966) TICKER = "TICKER" #: Exchange ticker (GS) PLOT_ID = "PLOT_ID" #: ID for Marquee PlotTool GSID = "GSID" NAME = 'NAME' #: Name of the asset ('US Treasury 20y GOVN') class SecurityIdentifier(EntityIdentifier): GSID = "gsid" RCIC = "rcic" RIC = "ric" ID = "id" CUSIP = "cusip" CUSIP8 = "cusip8" CINS = "cins" SEDOL = "sedol" ISIN = "isin" TICKER = "ticker" BBID = "bbid" BCID = "bcid" GSS = "gss" PRIMEID = "primeId" BBG = "bbg" ASSET_ID = "assetId" ANY = "identifiers" BARRA_ID = "barraId" AXIOMA_ID = "axiomaId" class ReturnType(Enum): """Index return type Represents different index return types or funding models """ EXCESS_RETURN = "Excess Return" # Returns are excess of funding rate in denominated currency TOTAL_RETURN = "Total Return" # Returns are inclusive of funding rate in denominated currency class Asset(Entity, metaclass=ABCMeta): def __init__( self, id_: str, asset_class: AssetClass, name: str, exchange: Optional[str] = None, currency: Optional[str] = None, parameters: AssetParameters = None, entity: Optional[Dict] = None, ): super().__init__(id_, EntityType.ASSET, entity=entity) self.__id = id_ self.asset_class = asset_class self.name = name self.exchange = exchange self.currency = currency self.parameters = parameters self.entity = entity def get_marquee_id(self): return self.__id def get_url(self) -> str: """ Retrieve url to asset's product page on Marquee """ env = '-dev-ext.web' if 'dev' in get(GsSession, 'current.domain', '') else '' env = '-qa' if 'qa' in get(GsSession, 'current.domain', '') else env return f'https://marquee{env}.gs.com/s/products/{self.get_marquee_id()}/summary' def get_identifiers(self, as_of: dt.date = None) -> dict: """ Get asset identifiers :param as_of: As of date for query :return: dict of identifiers **Usage** Get asset identifiers as of a given date. Where the identifiers are temporal (and can change over time), this function will return the identifiers as of that point in time. If no date is provided as a parameter, will use the current PricingContext. **Examples** Get current asset identifiers: >>> gs = SecurityMaster.get_asset("GS", AssetIdentifier.TICKER) >>> gs.get_identifiers() Get identifiers as of 1Jan18: >>> gs.get_identifiers(dt.date(2018,1,1)) Use PricingContext to determine as of date: >>> with PricingContext(dt.date(2018,1,1)) as ctx: >>> gs.get_identifiers() **See also** :class:`AssetIdentifier` :func:`get_asset` """ if not as_of: current = PricingContext.current if not current.is_entered: with current: as_of = current.pricing_date else: as_of = current.pricing_date if isinstance(as_of, dt.datetime): as_of = as_of.date() valid_ids = set(item.value for item in AssetIdentifier) xrefs = GsAssetApi.get_asset_xrefs(self.get_marquee_id()) identifiers = {} for xref in xrefs: start_date = xref.startDate end_date = xref.endDate if start_date <= as_of <= end_date: identifiers = {k.upper(): v for k, v in xref.identifiers.as_dict().items() if k.upper() in valid_ids} return identifiers @cachetools.cached( cachetools.TTLCache(256, 600), lambda s, id_type, as_of=None: cachetools.keys.hashkey(s.get_marquee_id(), id_type, as_of), threading.RLock(), ) def get_identifier(self, id_type: AssetIdentifier, as_of: dt.date = None): """ Get asset identifier :param as_of: As of date for query :param id_type: requested id type :return: identifier value **Usage** Get asset identifier as of a given date. Where the identifiers are temporal (and can change over time), this function will return the identifier as of that point in time. If no date is provided as a parameter, will use the current PricingContext. **Examples** Get current SEDOL: >>> import datetime as dt >>> >>> gs = SecurityMaster.get_asset("GS", AssetIdentifier.TICKER) >>> gs.get_identifier(AssetIdentifier.SEDOL) Get SEDOL as of 1Jan18: >>> gs.get_identifier(AssetIdentifier.SEDOL, as_of=dt.date(2018,1,1)) Use PricingContext to determine as of date: >>> with PricingContext(dt.date(2018,1,1)) as ctx: >>> gs.get_identifier(AssetIdentifier.SEDOL) **See also** :class:`AssetIdentifier` :func:`get_asset_identifiers` """ if id_type == AssetIdentifier.MARQUEE_ID: return self.get_marquee_id() ids = self.get_identifiers(as_of=as_of) return ids.get(id_type.value) def get_asset_measures(self) -> List[AssetMeasure]: """ Get asset measures :return: A list consisting of the following measures available for an asset: type, frequency, datasetField. For more details check the fields defined in class: 'AssetMeasures' **Usage** Get list of measures available for an asset **Examples** >>> from gs_quant.markets.securities import SecurityMaster,AssetIdentifier >>> >>> asset = SecurityMaster.get_asset("USDJPY", AssetIdentifier.BLOOMBERG_ID) >>> asset.get_asset_measures() **See also** :class:`AssetMeasures` """ availability_response = GsSession.current.sync.get(f'/data/measures/{self.get_marquee_id()}/availability') final_measure_set = set() if availability_response['data']: for measure_set in availability_response['data']: asset_measures = AssetMeasure.from_dict(measure_set) if {'type', 'frequency', 'datasetField'} <= measure_set.keys(): final_measure_set.add(asset_measures) return list(final_measure_set) def get_data_series( self, measure: DataMeasure, dimensions: Optional[DataDimensions] = None, frequency: Optional[DataFrequency] = None, start: Optional[DateOrDatetime] = None, end: Optional[DateOrDatetime] = None, dates: List[dt.date] = None, operator: DataAggregationOperator = None, ) -> pd.Series: """ Get asset series :param measure: measure to get as series :param dimensions: dimensions to query (e.g. tenor) :param frequency: data frequency to query :param start: start of the series :param end: end of the series :return: timeseries of given measure **Usage** Get a given timeseries for the asset **Examples** Get close price series: >>> from gs_quant.markets.securities import SecurityMaster >>> from gs_quant.data import DataMeasure >>> >>> gs = SecurityMaster.get_asset("GS", AssetIdentifier.TICKER) >>> gs.get_data_series(DataMeasure.CLOSE_PRICE) **See also** :class:`DataMeasure` """ coordinate = self.get_data_coordinate(measure, dimensions, frequency) if coordinate is None: raise MqValueError(f"No data coordinate found for parameters: {measure, dimensions, frequency}") elif coordinate.dataset_id is None: raise MqValueError(f"Measure '{measure.value}' not found for asset: {self.__id}") return coordinate.get_series(start=start, end=end, dates=dates, operator=operator) def get_latest_close_price(self) -> float: coordinate = self.get_data_coordinate(DataMeasure.CLOSE_PRICE, None, DataFrequency.DAILY) if coordinate is None: raise MqValueError( f"No data co-ordinate found for these parameters: \ {DataMeasure.CLOSE_PRICE, None, DataFrequency.DAILY}" ) return coordinate.last_value() def get_close_price_for_date(self, date: dt.date) -> pd.Series: return self.get_data_series(DataMeasure.CLOSE_PRICE, None, DataFrequency.DAILY, date, date) def get_close_prices(self, start: dt.date = DateLimit.LOW_LIMIT.value, end: dt.date = dt.date.today()) -> pd.Series: """ Get close price series :return: timeseries of close prices **Usage** Get close prices for an asset **Examples** Get close price series: >>> from gs_quant.markets.securities import SecurityMaster >>> >>> gs = SecurityMaster.get_asset("GS", AssetIdentifier.TICKER) >>> gs.get_close_prices() **See also** :class:`DataMeasure` :func:`get_data_series` """ return self.get_data_series(DataMeasure.CLOSE_PRICE, None, DataFrequency.DAILY, start, end) def get_hloc_prices( self, start: dt.date = DateLimit.LOW_LIMIT.value, end: dt.date = dt.date.today(), interval_frequency: IntervalFrequency = IntervalFrequency.DAILY, ) -> pd.DataFrame: """ Get high, low, open, close (hloc) prices :return: dataframe indexed by datetimes bucketed by the given interval_frequency with High, Low, Open, and Close columns **Usage** Get high, low, open, and close prices for an asset for the given interval frequency. **Examples** Get hloc price series: >>> from gs_quant.markets.securities import SecurityMaster >>> >>> gs = SecurityMaster.get_asset("GS", AssetIdentifier.TICKER) >>> gs.get_hloc_prices() **See also** :class:`DataMeasure` :func:`get_close_prices` """ if self.asset_class == AssetClass.Equity: if interval_frequency == IntervalFrequency.DAILY: dates = None use_field = False elif interval_frequency == IntervalFrequency.MONTHLY: d = dt.date(start.year, start.month, 1) dates = [d, dt.date(d.year, d.month, calendar.monthrange(d.year, d.month)[-1])] d += relativedelta(months=1) while d < end: dates.append(dt.date(d.year, d.month, calendar.monthrange(d.year, d.month)[-1])) d += relativedelta(months=1) dates.append(end) use_field = True else: raise MqValueError(f'Unsupported IntervalFrequency {interval_frequency.value} for get_hloc_prices') tasks = [ partial( self.get_data_series, DataMeasure.ADJUSTED_HIGH_PRICE, None, DataFrequency.DAILY, start, end, dates=dates, operator=DataAggregationOperator.MAX if use_field else None, ), partial( self.get_data_series, DataMeasure.ADJUSTED_LOW_PRICE, None, DataFrequency.DAILY, start, end, dates=dates, operator=DataAggregationOperator.MIN if use_field else None, ), partial( self.get_data_series, DataMeasure.ADJUSTED_OPEN_PRICE, None, DataFrequency.DAILY, start, end, dates=dates, operator=DataAggregationOperator.FIRST if use_field else None, ), partial( self.get_data_series, DataMeasure.ADJUSTED_CLOSE_PRICE, None, DataFrequency.DAILY, start, end, dates=dates, operator=DataAggregationOperator.LAST if use_field else None, ), ] results = ThreadPoolManager.run_async(tasks) df = pd.DataFrame({'high': results[0], 'low': results[1], 'open': results[2], 'close': results[3]}) elif self.asset_class == AssetClass.FX: if interval_frequency != IntervalFrequency.DAILY: raise MqValueError('Unsupported IntervalFrequency for FX asset class.') ds = Dataset('FX_HLOC') df = ds.get_data(start=start, end=end, assetId=self.get_marquee_id()) df = df.drop(columns=['assetId', 'updateTime']).reindex(columns=['high', 'low', 'open', 'close']) else: raise MqValueError('Unsupported AssetClass for HLOC data.') return df.dropna() @abstractmethod def get_type(self) -> AssetType: """Overridden by sub-classes to return security type""" @classmethod def entity_type(cls) -> EntityType: return EntityType.ASSET @property def data_dimension(self) -> str: return 'assetId' @classmethod def get( cls, id_value: str, id_type: AssetIdentifier, as_of: Union[dt.date, dt.datetime] = None, exchange_code: ExchangeCode = None, asset_type: AssetType = None, sort_by_rank: bool = False, ) -> Optional['Asset']: asset = SecurityMaster.get_asset(id_value, id_type, as_of, exchange_code, asset_type, sort_by_rank) return asset class SecMasterAsset(Asset): def __init__( self, id_: str, asset_type: AssetType, asset_class: AssetClass, name: str, exchange: Optional[str] = None, currency: Optional[str] = None, parameters: AssetParameters = None, entity: Optional[Dict] = None, ): Asset.__init__( self, id_, asset_class=asset_class, name=name, exchange=exchange, currency=currency, parameters=parameters, entity=entity, ) self.__asset_type = asset_type self.__cached_identifiers = None def get_type(self) -> AssetType: return self.__asset_type def get_marquee_id(self): marquee_id = self.get_identifier(SecurityIdentifier.ASSET_ID) self.__id = marquee_id # Updates Marquee Id in case it changes from context change if marquee_id is None: current = PricingContext.current if not current.is_entered: with current: current_pricing_date = current.pricing_date else: current_pricing_date = current.pricing_date raise MqValueError( f"Current SecMasterAsset does not have a Marquee Id as of {current_pricing_date}. " f"Perhaps asset did not exist at that time, or is a not an exchange-level asset." ) return marquee_id def get_identifier(self, id_type: Union[AssetIdentifier, SecurityIdentifier], as_of: dt.date = None): # Add an exception since original get_identifier() takes id_type: AssetIdentifier if not isinstance(id_type, SecurityIdentifier): raise MqTypeError( f"""Expected id_type: SecurityIdentifier.enum for Assets sourced from SecurityMaster. Received: {id_type}""" ) if id_type == SecurityIdentifier.GSID: return self.entity['identifiers'].get(SecurityIdentifier.GSID.value) if id_type == SecurityIdentifier.ID: return self.entity['id'] ids = self.get_identifiers(as_of=as_of) return ids.get(id_type.value, None) def get_identifiers(self, as_of: dt.date = None) -> dict: # Cache identifiers if not already there if self.__cached_identifiers is None: self.__load_identifiers() # Retrieve from cached identifiers if as_of is None: current = PricingContext.current if not current.is_entered: with current: as_of = current.pricing_date else: as_of = current.pricing_date identifiers = dict() for id_type in SecurityIdentifier: id_history = self.__cached_identifiers.get(id_type.value) if id_history is not None: for xref in id_history: if xref["start_date"] <= as_of <= xref["end_date"]: identifiers[id_type.value] = xref["value"] break # Add GSID and ID as it is not exposed in Get Identifiers History identifiers[SecurityIdentifier.ID.value] = self.entity.get('id') identifiers[SecurityIdentifier.GSID.value] = self.entity.get('identifiers').get(SecurityIdentifier.GSID.value) # Mainly for currencies, where assetId is not exposed in Get Identifiers History if SecurityIdentifier.ASSET_ID.value not in identifiers and self.__asset_type == AssetType.CURRENCY: identifiers[SecurityIdentifier.ASSET_ID.value] = self.entity.get("identifiers").get("assetId") # TODO: BCID and BBID are not exposed in Get Identifiers History. return identifiers def get_data_series( self, measure: DataMeasure, dimensions: Optional[DataDimensions] = None, frequency: Optional[DataFrequency] = None, start: Optional[DateOrDatetime] = None, end: Optional[DateOrDatetime] = None, dates: List[dt.date] = None, operator: DataAggregationOperator = None, ) -> pd.Series: """ Will be also called by Asset.get_close_prices(), Asset.get_close_price_for_date(). """ coordinate = self.get_data_coordinate(measure, dimensions, frequency) if coordinate is None: raise MqValueError(f"No data coordinate found for parameters:{measure, dimensions, frequency}") range_start, range_end = coordinate.get_range(start, end) if self.__is_validate_range(start=range_start, end=range_end): with PricingContext(range_start): return super(SecMasterAsset, self).get_data_series( measure=measure, dimensions=dimensions, frequency=frequency, start=range_start, end=range_end, dates=dates, operator=operator, ) def get_hloc_prices( self, start: dt.date = DateLimit.LOW_LIMIT.value, end: dt.date = dt.date.today(), interval_frequency: IntervalFrequency = IntervalFrequency.DAILY, ) -> pd.DataFrame: if self.__is_validate_range(start=start, end=end): with PricingContext(start): return super(SecMasterAsset, self).get_hloc_prices( start=start, end=end, interval_frequency=interval_frequency ) def __is_validate_range(self, start: DateOrDatetime, end: DateOrDatetime = dt.date.today()) -> bool: """ Validates that only one Marquee Id exist in start and end. - This function will return True if only one Marquee id exists in range. - This function will raise MqValueError if either many Marquee Ids exists in or none exist in input range or the Id at start_date is not equal to id at end_date. Example: __cached_identifiers = {"assetId" : [{start: 2020-01-01, end: 2020-01-05, value: "marqueeId1"} {start: 2020-01-06, end: 2020-10-10, value: "marqueeId2"}]} args = {start: 2020-01-01, end: 2020-01-07} return: MqValueError """ if self.__cached_identifiers is None: self.__load_identifiers() if isinstance(start, dt.datetime): start_date = start.date else: start_date = start if isinstance(end, dt.datetime): end_date = end.date else: end_date = end with PricingContext(start_date): start_marquee_id = self.get_marquee_id() with PricingContext(end_date): end_marquee_id = self.get_marquee_id() if start_marquee_id is None or end_marquee_id is None or start_marquee_id != end_marquee_id: raise MqValueError( f"Asset's Marquee Id is either none or different. start:[{start_date}->{start_marquee_id}] to " f"end=[{end_date}->{end_marquee_id}]." ) marquee_id_xref = self.__cached_identifiers.get(SecurityIdentifier.ASSET_ID.value) marquee_ids = set() output_range_start = None output_range_end = None overlap_ranges = defaultdict(list) for xref in marquee_id_xref: if end_date < xref['start_date'] or start_date > xref['end_date']: # Skip xrefs that are outside range continue marquee_id = xref.get("value") range_start = max(start_date, xref['start_date']) range_end = min(end_date, xref['end_date']) if range_start <= range_end: marquee_ids.add(marquee_id) overlap_ranges[marquee_id].append([range_start.strftime("%Y-%m-%d"), range_end.strftime("%Y-%m-%d")]) output_range_start = ( min(output_range_start, range_start) if output_range_start is not None else output_range_start ) output_range_end = ( max(output_range_end, range_end) if output_range_end is not None else output_range_end ) if len(marquee_ids) > 1: raise MqValueError( f"Asset has multiple Marquee ids between [start,end]=[{start_date},{end_date}] due to corporate " f"actions. Try limiting the range over a single Marquee id. Marquee Ids found: {overlap_ranges}." ) if len(marquee_ids) == 0: raise MqValueError( f"Asset was not assigned Marquee Id over range [start,end]=[{start_date},{end_date}]. " f"Perhaps asset did not exist at that range, or is a not an exchange-level asset." ) return True def __load_identifiers(self) -> None: if self.__cached_identifiers is None: r = GsSession.current.sync.get(f'/markets/securities/{self.entity["id"]}/identifiers') results = r['results'] xrefs = defaultdict(list) for temporal_xref in results: id_type = temporal_xref['type'] xref_dict = { "start_date": dt.datetime.strptime(temporal_xref['startDate'], "%Y-%m-%d").date(), "update_date": temporal_xref['updateTime'], "value": temporal_xref['value'], } if temporal_xref['endDate'] == "9999-99-99": xref_dict['end_date'] = dt.datetime.max.date() else: xref_dict['end_date'] = dt.datetime.strptime(temporal_xref['endDate'], "%Y-%m-%d").date() xrefs[id_type].append(xref_dict) self.__cached_identifiers = xrefs class Stock(Asset): """Base Security Type Represents a financial asset which can be held in a portfolio, or has an observable price fixing which can be referenced in a derivative transaction """ def __init__( self, id_: str, name: str, exchange: Optional[str] = None, currency: Optional[Currency] = None, entity: Optional[Dict] = None, ): Asset.__init__(self, id_, AssetClass.Equity, name, exchange, currency, entity=entity) def get_type(self) -> AssetType: return AssetType.STOCK def get_currency(self) -> Optional[Currency]: return self.currency def get_thematic_beta( self, basket_identifier: str, start: dt.date = DateLimit.LOW_LIMIT.value, end: dt.date = dt.date.today() ) -> pd.DataFrame: response = GsAssetApi.resolve_assets(identifier=[basket_identifier], fields=['id', 'type'], limit=1)[ basket_identifier ] _id, _type = get(response, '0.id'), get(response, '0.type') if len(response) == 0 or _id is None: raise MqValueError(f'Basket could not be found using identifier {basket_identifier}.') if _type not in BasketType.to_list(): raise MqValueError(f'Asset {basket_identifier} of type {_type} is not a Custom or Research Basket.') query = DataQuery( where={'gsid': self.get_identifier(AssetIdentifier.GSID, end), 'basketId': _id}, start_date=start, end_date=end, ) response = GsDataApi.query_data(query=query, dataset_id=IndicesDatasets.THEMATIC_FACTOR_BETAS_STANDARD.value) df = [] for r in response: df.append({'date': r['date'], 'gsid': r['gsid'], 'basketId': r['basketId'], 'thematicBeta': r['beta']}) df = pd.DataFrame(df) return df.set_index('date') class Cross(Asset): """Base Security Type Represents a financial asset which can be held in a portfolio, or has an observable price fixing which can be referenced in a derivative transaction """ def __init__( self, id_: str, name: str, entity: Optional[Dict] = None, asset_class: Optional[Union[AssetClass, str]] = AssetClass.FX, ): if isinstance(asset_class, str): asset_class = get_enum_value(AssetClass, asset_class) Asset.__init__(self, id_, asset_class, name, entity=entity) def get_type(self) -> AssetType: return AssetType.CROSS class Future(Asset): """Future Security Type Represents a standardized listed contract which provides delivery of an asset at a pre-defined forward date and can be settled in cash or physical form """ def __init__( self, id_: str, asset_class: Union[AssetClass, str], name: str, currency: Optional[Currency] = None, entity: Optional[Dict] = None, ): if isinstance(asset_class, str): asset_class = get_enum_value(AssetClass, asset_class) Asset.__init__(self, id_, asset_class, name, currency=currency, entity=entity) def get_type(self) -> AssetType: return AssetType.FUTURE def get_currency(self) -> Optional[Currency]: return self.currency class Currency(Asset): """Base Security Type Represents a financial asset which can be held in a portfolio, or has an observable price fixing which can be referenced in a derivative transaction """ def __init__(self, id_: str, name: str, entity: Optional[Dict] = None): Asset.__init__(self, id_, AssetClass.Cash, name, entity=entity) def get_type(self) -> AssetType: return AssetType.CURRENCY class Rate(Asset): """Base Security Type Represents a financial asset which can be held in a portfolio, or has an observable price fixing which can be referenced in a derivative transaction """ def __init__(self, id_: str, name: str, entity: Optional[Dict] = None): Asset.__init__(self, id_, AssetClass.Rates, name, entity=entity) def get_type(self) -> AssetType: return AssetType.RATE class Cash(Asset): """Cash Security Type Represents a financial asset which can be held in a portfolio, or has an observable price fixing which can be referenced in a derivative transaction """ def __init__(self, id_: str, name: str, entity: Optional[Dict] = None): Asset.__init__(self, id_, AssetClass.Cash, name, entity=entity) def get_type(self) -> AssetType: return AssetType.CASH class WeatherIndex(Asset): """Weather Index Type Represents an underlying index on a weather derivative, including where the data (e.g. CPD) has been collected, an actual physical reference point (weather station) and various fall back arrangements. """ def __init__(self, id_: str, name: str, entity: Optional[Dict] = None): Asset.__init__(self, id_, AssetClass.Commod, name, entity=entity) def get_type(self) -> AssetType: return AssetType.WEATHER_INDEX class CommodityReferencePrice(Asset): """Commodity Reference Price Represents an underlying index for commodities in the event that no ISDA Commodity Reference Price exists. Includes base, details, unit, currency and exchange id or publication etc. """ def __init__(self, id_: str, name: str, entity: Optional[Dict] = None): Asset.__init__(self, id_, AssetClass.Commod, name, entity=entity) def get_type(self) -> AssetType: return AssetType.COMMODITY_REFERENCE_PRICE class CommodityNaturalGasHub(Asset): """Commodity Natural Gas Hub Represents a distinct location in commodity Natural Gas markets """ def __init__(self, id_: str, name: str, entity: Optional[Dict] = None): Asset.__init__(self, id_, AssetClass.Commod, name, entity=entity) def get_type(self) -> AssetType: return AssetType.COMMODITY_NATURAL_GAS_HUB class CommodityEUNaturalGasHub(Asset): """Commodity EU Natural Gas Hub Represents a virtual/physical hub in EU Natural Gas markets """ def __init__(self, id_: str, name: str, entity: Optional[Dict] = None): Asset.__init__(self, id_, AssetClass.Commod, name, entity=entity) def get_type(self) -> AssetType: return AssetType.COMMODITY_EU_NATURAL_GAS_HUB class Cryptocurrency(Asset): """Cryptocurrency Represents a cryptocurrency """ def __init__(self, id_: str, asset_class: Union[AssetClass, str], name: str, entity: Optional[Dict] = None): Asset.__init__(self, id_, asset_class, name, entity=entity) def get_type(self) -> AssetType: return AssetType.CRYPTOCURRENCY class CommodityPowerNode(Asset): """Commodity Power Node Represents a distinct location in commodity power markets """ def __init__(self, id_: str, name: str, entity: Optional[Dict] = None): Asset.__init__(self, id_, AssetClass.Commod, name, entity=entity) def get_type(self) -> AssetType: return AssetType.COMMODITY_POWER_NODE class CommodityPowerAggregatedNodes(Asset): """Commodity Power Aggregated Nodes Represents a group of locations in commodity power markets """ def __init__(self, id_: str, name: str, entity: Optional[Dict] = None): Asset.__init__(self, id_, AssetClass.Commod, name, entity=entity) def get_type(self) -> AssetType: return AssetType.COMMODITY_POWER_AGGREGATED_NODES class Commodity(Asset): """Commodity Represents a commodity. """ def __init__(self, id_: str, name: str, entity: Optional[Dict] = None): Asset.__init__(self, id_, AssetClass.Commod, name, entity=entity) def get_type(self) -> AssetType: return AssetType.COMMODITY class Bond(Asset): """Bond Represents a bond. """ def __init__(self, id_: str, name: str, asset_class: AssetClass = AssetClass.Credit, entity: Optional[Dict] = None): Asset.__init__(self, id_, asset_class, name, entity=entity) def get_type(self) -> AssetType: return AssetType.BOND class Fund(Asset): """Fund Represents a fund. """ def __init__(self, id_: str, name: str, asset_class: AssetClass, entity: Optional[Dict] = None): Asset.__init__(self, id_, asset_class, name, entity=entity) def get_type(self) -> AssetType: return AssetType.FUND class FutureMarket(Asset): """Future Market Represents a future market """ def __init__(self, id_: str, asset_class: Union[AssetClass, str], name: str, entity: Optional[Dict] = None): if isinstance(asset_class, str): asset_class = get_enum_value(AssetClass, asset_class) Asset.__init__(self, id_, asset_class, name, entity=entity) def get_type(self) -> AssetType: return AssetType.FUTURE_MARKET class FutureContract(Asset): """Future Contract Represents a future contract """ def __init__(self, id_: str, asset_class: Union[AssetClass, str], name: str, entity: Optional[Dict] = None): if isinstance(asset_class, str): asset_class = get_enum_value(AssetClass, asset_class) Asset.__init__(self, id_, asset_class, name, entity=entity) def get_type(self) -> AssetType: return AssetType.FUTURE_CONTRACT class Swap(Asset): """Swap Instrument Type Represents a Swap Instrument """ def __init__(self, id_: str, asset_class: Union[AssetClass, str], name: str, entity: Optional[Dict] = None): if isinstance(asset_class, str): asset_class = get_enum_value(AssetClass, asset_class) Asset.__init__(self, id_, asset_class, name, entity=entity) def get_type(self) -> AssetType: return AssetType.SWAP class Option(Asset): """Option Instrument Type Represents an Option Instrument """ def __init__(self, id_: str, asset_class: Union[AssetClass, str], name: str, entity: Optional[Dict] = None): if isinstance(asset_class, str): asset_class = get_enum_value(AssetClass, asset_class) Asset.__init__(self, id_, asset_class, name, entity=entity) def get_type(self) -> AssetType: return AssetType.OPTION class Forward(Asset): def __init__(self, id_: str, asset_class: Union[AssetClass, str], name: str, entity: Optional[Dict] = None): if isinstance(asset_class, str): asset_class = get_enum_value(AssetClass, asset_class) Asset.__init__(self, id_, asset_class, name, entity=entity) def get_type(self) -> AssetType: return AssetType.FORWARD class ETF(Asset, PositionedEntity): """ETF Asset ETF which tracks an evolving portfolio of securities, and can be traded on exchange """ def __init__( self, id_: str, asset_class: AssetClass, name: str, exchange: Optional[str] = None, currency: Optional[Currency] = None, entity: Optional[Dict] = None, ): Asset.__init__(self, id_, asset_class, name, exchange, currency, entity=entity) PositionedEntity.__init__(self, id_, EntityType.ASSET) def get_type(self) -> AssetType: return AssetType.ETF def get_currency(self) -> Optional[Currency]: return self.currency class Swaption(Asset): """Swaption Represents a swaption. """ def __init__(self, id_: str, name: str, entity: Optional[Dict] = None): Asset.__init__(self, id_, AssetClass.Rates, name, entity=entity) def get_type(self) -> AssetType: return AssetType.SWAPTION class Binary(Asset): """Binary Represents a binary. """ def __init__(self, id_: str, name: str, asset_class: AssetClass, entity: Optional[Dict] = None): Asset.__init__(self, id_, asset_class, name, entity=entity) def get_type(self) -> AssetType: return AssetType.BINARY class DefaultSwap(Asset): """DefaultSwap Represents a default swap. """ def __init__(self, id_: str, name: str, entity: Optional[Dict] = None): Asset.__init__(self, id_, AssetClass.Credit, name, entity=entity) def get_type(self) -> AssetType: return AssetType.DEFAULT_SWAP class XccySwapMTM(Asset): """XccySwapMTM Represents a cross-currency mark-to-market swap. """ def __init__(self, id_: str, name: str, entity: Optional[Dict] = None): Asset.__init__(self, id_, AssetClass.Rates, name, entity=entity) def get_type(self) -> AssetType: return AssetType.XccySwapMTM class MutualFund(Asset): """MutualFund Represents a mutual fund asset. """ def __init__(self, id_: str, name: str, asset_class: AssetClass, entity: Optional[Dict] = None): Asset.__init__(self, id_, asset_class, name, entity=entity) def get_type(self) -> AssetType: return AssetType.MUTUAL_FUND class SecurityMasterSource(Enum): ASSET_SERVICE = auto() SECURITY_MASTER = auto() class Security: def __init__(self, json: dict): for k, v in json.items(): if k == 'identifiers': self._ids = {inner_k: inner_v for inner_k, inner_v in v.items()} else: setattr(self, k, v) def __str__(self): return str({k: v for k, v in self.__dict__.items() if not k.startswith("_")}) def get_identifiers(self): return deepcopy(self._ids) @backoff.on_exception(backoff.expo, MqRequestError, giveup=lambda e: e.status != 429) def _get_with_retries(url, payload): return GsSession.current.sync.get(url, payload=payload) class SecurityMaster: """Security Master The SecurityMaster class provides an interface to security lookup functions. This allows querying and retrieval of different security types (assets) based on a variety of different identifiers through point-in-time lookups. Uses the current PricingContext to provide as of dates if optional arguments are not provided. Will return the relevant asset subclass depending on the type of the security **See also** :class:`Asset` """ _source = SecurityMasterSource.ASSET_SERVICE _page_size = 1000 @classmethod def __gs_asset_to_asset(cls, gs_asset: GsAsset) -> Asset: asset_type = gs_asset.type.value asset_entity: Dict = json.loads(json.dumps(gs_asset.as_dict(), cls=JSONEncoder)) if asset_type in (GsAssetType.Single_Stock.value,): return Stock(gs_asset.id, gs_asset.name, gs_asset.exchange, gs_asset.currency, entity=asset_entity) if asset_type in (GsAssetType.ETF.value,): return ETF( gs_asset.id, gs_asset.assetClass, gs_asset.name, gs_asset.exchange, gs_asset.currency, entity=asset_entity, ) if asset_type in ( GsAssetType.Index.value, GsAssetType.Access.value, GsAssetType.Multi_Asset_Allocation.value, GsAssetType.Risk_Premia.value, GsAssetType.Systematic_Hedging.value, ): from gs_quant.markets.index import Index return Index( gs_asset.id, gs_asset.assetClass, gs_asset.name, gs_asset.exchange, gs_asset.currency, entity=asset_entity, ) if asset_type in (GsAssetType.Custom_Basket.value, GsAssetType.Research_Basket.value): from gs_quant.markets.baskets import Basket return Basket(gs_asset=gs_asset) if asset_type in (GsAssetType.Future.value,): return Future(gs_asset.id, gs_asset.assetClass, gs_asset.name, gs_asset.currency, entity=asset_entity) if asset_type in (GsAssetType.Cross.value,): return Cross(gs_asset.id, gs_asset.name, entity=asset_entity, asset_class=gs_asset.assetClass) if asset_type in (GsAssetType.Currency.value,): return Currency(gs_asset.id, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.Rate.value,): return Rate(gs_asset.id, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.Cash.value,): return Cash(gs_asset.id, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.WeatherIndex.value,): return WeatherIndex(gs_asset.id, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.Swap.value,): return Swap(gs_asset.id, gs_asset.assetClass, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.Option.value,): return Option(gs_asset.id, gs_asset.assetClass, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.CommodityReferencePrice.value,): return CommodityReferencePrice(gs_asset.id, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.CommodityNaturalGasHub.value,): return CommodityNaturalGasHub(gs_asset.id, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.CommodityEUNaturalGasHub.value,): return CommodityEUNaturalGasHub(gs_asset.id, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.CommodityPowerNode.value,): return CommodityPowerNode(gs_asset.id, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.CommodityPowerAggregatedNodes.value,): return CommodityPowerAggregatedNodes(gs_asset.id, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.Bond.value,): return Bond(gs_asset.id, gs_asset.name, gs_asset.assetClass or AssetClass.Credit, entity=asset_entity) if asset_type in (GsAssetType.Commodity.value,): return Commodity(gs_asset.id, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.FutureMarket.value,): return FutureMarket(gs_asset.id, gs_asset.assetClass, gs_asset.name, entity=asset_entity) if asset_type in (GsAssetType.FutureContract.value,): return FutureContract(gs_asset.id, gs_asset.assetClass, gs_asset.name, entity=asset_entity) # workaround as casing is being migrated if asset_type == GsAssetType.Cryptocurrency.value: return Cryptocurrency(gs_asset.id, gs_asset.assetClass, gs_asset.name, entity=asset_entity) if asset_type == GsAssetType.Forward.value: return Forward(gs_asset.id, gs_asset.assetClass, gs_asset.name, entity=asset_entity) if asset_type == GsAssetType.Fund.value: return Fund(gs_asset.id, gs_asset.name, gs_asset.assetClass, entity=asset_entity) if asset_type == GsAssetType.Default_Swap.value: return DefaultSwap(gs_asset.id, gs_asset.name, entity=asset_entity) if asset_type == GsAssetType.Swaption.value: return Swaption(gs_asset.id, gs_asset.name, entity=asset_entity) if asset_type == GsAssetType.Binary.value: return Binary(gs_asset.id, gs_asset.name, gs_asset.assetClass, entity=asset_entity) if asset_type == GsAssetType.XccySwapMTM.value: return XccySwapMTM(gs_asset.id, gs_asset.name, entity=asset_entity) if asset_type == GsAssetType.Mutual_Fund.value: return MutualFund(gs_asset.id, gs_asset.name, gs_asset.asset_class, entity=asset_entity) raise TypeError(f'unsupported asset type {asset_type}') @classmethod def __asset_type_to_gs_types(cls, asset_type: AssetType) -> Tuple[GsAssetType, ...]: asset_map = { AssetType.STOCK: (GsAssetType.Single_Stock,), AssetType.INDEX: ( GsAssetType.Index, GsAssetType.Multi_Asset_Allocation, GsAssetType.Risk_Premia, GsAssetType.Access, ), AssetType.ETF: (GsAssetType.ETF, GsAssetType.ETN), AssetType.CUSTOM_BASKET: (GsAssetType.Custom_Basket,), AssetType.RESEARCH_BASKET: (GsAssetType.Research_Basket,), AssetType.FUTURE: (GsAssetType.Future,), AssetType.RATE: (GsAssetType.Rate,), } return asset_map.get(asset_type) @classmethod def set_source(cls, source: SecurityMasterSource): cls._source = source @classmethod def get_asset_query( cls, id_value: Union[str, List[str]], id_type: Union[AssetIdentifier, SecurityIdentifier], as_of: Union[dt.date, dt.datetime] = None, exchange_code: ExchangeCode = None, asset_type: AssetType = None, ) -> Tuple[Dict, dt.datetime]: if not as_of: current = PricingContext.current if not current.is_entered: with current: as_of = current.pricing_date else: as_of = current.pricing_date if isinstance(as_of, dt.date): as_of = dt.datetime.combine(as_of, dt.time(0, 0), dt.timezone.utc) query = {"id" if id_type == AssetIdentifier.MARQUEE_ID else id_type.value.lower(): id_value} if exchange_code is not None: query['exchange'] = exchange_code.value if asset_type is not None: query['type'] = [t.value for t in cls.__asset_type_to_gs_types(asset_type)] return query, as_of @classmethod def _get_asset_results(cls, results, sort_by_rank) -> Asset: if sort_by_rank: result = get(results, '0') if result: result = GsAsset.from_dict(result) else: result = next(iter(results), None) if result: return cls.__gs_asset_to_asset(result) return None @classmethod def _get_many_assets_results(cls, results) -> List[Asset]: if results is not None: return [cls.__gs_asset_to_asset(result) for result in results] return [] @classmethod def get_asset( cls, id_value: str, id_type: Union[AssetIdentifier, SecurityIdentifier], as_of: Union[dt.date, dt.datetime] = None, exchange_code: ExchangeCode = None, asset_type: AssetType = None, sort_by_rank: bool = True, fields: Optional[List[str]] = None, ) -> Asset: """ Get an asset by identifier and identifier type :param id_value: identifier value :param id_type: identifier type :param exchange_code: exchange code :param asset_type: asset type :param as_of: As of date for query :param sort_by_rank: whether to sort assets by rank. This flag is ignored when using SecMasterContext :param fields: asset fields to return :return: Asset object or None **Usage** Get asset object using a specified identifier and identifier type. Where the identifiers are temporal (and can change over time), will use the current MarketContext to evaluate based on the specified date. **Examples** Get asset by bloomberg id: >>> gs = SecurityMaster.get_asset("GS UN", AssetIdentifier.BLOOMBERG_ID) Get asset by ticker and exchange code: >>> gs = SecurityMaster.get_asset("GS", AssetIdentifier.TICKER, exchange_code=ExchangeCode.NYSE) Get asset by ticker and asset type: >>> spx = SecurityMaster.get_asset("SPX", AssetIdentifier.TICKER, asset_type=AssetType.INDEX) **See also** :class:`AssetIdentifier` :func:`get_many_assets` """ if cls._source == SecurityMasterSource.SECURITY_MASTER: if not isinstance(id_type, SecurityIdentifier): raise MqTypeError('expected a security identifier') if exchange_code or asset_type: raise NotImplementedError('argument not implemented for Security Master (supported in Asset Service)') return cls._get_security_master_asset(id_value, id_type, as_of=as_of, fields=fields) if id_type is AssetIdentifier.MARQUEE_ID: gs_asset = GsAssetApi.get_asset(id_value) return cls.__gs_asset_to_asset(gs_asset) query, as_of = cls.get_asset_query(id_value, id_type, as_of, exchange_code, asset_type) if sort_by_rank: results = GsAssetApi.get_many_assets(as_of=as_of, return_type=dict, order_by=['>rank'], **query) return cls._get_asset_results(results, sort_by_rank) else: results = GsAssetApi.get_many_assets(as_of=as_of, **query) return cls._get_asset_results(results, sort_by_rank) @classmethod async def get_asset_async( cls, id_value: str, id_type: Union[AssetIdentifier, SecurityIdentifier], as_of: Union[dt.date, dt.datetime] = None, exchange_code: ExchangeCode = None, asset_type: AssetType = None, sort_by_rank: bool = True, fields: Optional[List[str]] = None, ) -> Asset: """ Get an asset by identifier and identifier type :param id_value: identifier value :param id_type: identifier type :param exchange_code: exchange code :param asset_type: asset type :param as_of: As of date for query :param sort_by_rank: whether to sort assets by rank. This flag is ignored when using SecMasterContext :param fields: asset fields to return :return: Asset object or None **Usage** Get asset object using a specified identifier and identifier type. Where the identifiers are temporal (and can change over time), will use the current MarketContext to evaluate based on the specified date. **Examples** Get asset by bloomberg id: >>> gs = await SecurityMaster.get_asset_async("GS UN", AssetIdentifier.BLOOMBERG_ID) Get asset by ticker and exchange code: >>> gs = await SecurityMaster.get_asset_async("GS", AssetIdentifier.TICKER, exchange_code=ExchangeCode.NYSE) Get asset by ticker and asset type: >>> spx = await SecurityMaster.get_asset_async("SPX", AssetIdentifier.TICKER, asset_type=AssetType.INDEX) **See also** :class:`AssetIdentifier` :func:`get_many_assets` """ if cls._source == SecurityMasterSource.SECURITY_MASTER: if not isinstance(id_type, SecurityIdentifier): raise MqTypeError('expected a security identifier') if exchange_code or asset_type: raise NotImplementedError('argument not implemented for Security Master (supported in Asset Service)') return await cls._get_security_master_asset_async(id_value, id_type, as_of=as_of, fields=fields) if id_type is AssetIdentifier.MARQUEE_ID: gs_asset = await GsAssetApi.get_asset_async(id_value) return cls.__gs_asset_to_asset(gs_asset) query, as_of = cls.get_asset_query(id_value, id_type, as_of, exchange_code, asset_type) if sort_by_rank: results = await GsAssetApi.get_many_assets_async(as_of=as_of, return_type=dict, order_by=['>rank'], **query) return cls._get_asset_results(results, sort_by_rank) else: results = await GsAssetApi.get_many_assets_async(as_of=as_of, **query) return cls._get_asset_results(results, sort_by_rank) @classmethod def get_many_assets( cls, id_values: List[str], id_type: AssetIdentifier, limit: int = 100, as_of: Union[dt.date, dt.datetime] = None, exchange_code: ExchangeCode = None, sort_by_rank: bool = True, ) -> List[Asset]: """ Get an asset by identifier and identifier type :param id_values: identifier values :param id_type: identifiers type :param limit: max number of results :param exchange_code: exchange code :param as_of: As of date for query :param sort_by_rank: whether to sort assets by rank. This flag is ignored when using SecMasterContext :return: list of Asset objects or None **Usage** Get asset object using a specified identifier and identifier type. Where the identifiers are temporal (and can change over time), will use the current MarketContext to evaluate based on the specified date. **Examples** Get asset by bloomberg id: >>> gs = SecurityMaster.get_many_assets(["GS UN", "MSFT UW"], AssetIdentifier.BLOOMBERG_ID) Get asset by ticker and exchange code: >>> gs = SecurityMaster.get_many_assets(["GS", "MSFT"], AssetIdentifier.TICKER, exchange_code=ExchangeCode.NYSE) Get asset by ticker and asset type: >>> spx = SecurityMaster.get_many_assets(["SPX"], AssetIdentifier.TICKER, asset_type=AssetType.INDEX) **See also** :class:`AssetIdentifier` :func:`get_many_assets` """ span = Tracer.active_span() tracer = Tracer('SecurityMaster.get_many_assets') if span and span.is_recording() else nullcontext() with tracer as scope: if scope and scope.span: scope.span.set_tag(f'request.ids.{id_type.value}', len(id_values) if id_values else 0) query, as_of = cls.get_asset_query(id_values, id_type, as_of, exchange_code) if sort_by_rank: results = GsAssetApi.get_many_assets(as_of=as_of, order_by=['>rank'], limit=limit, **query) else: results = GsAssetApi.get_many_assets(as_of=as_of, limit=limit, **query) return cls._get_many_assets_results(results) @classmethod async def get_many_assets_async( cls, id_values: List[str], id_type: AssetIdentifier, limit: int = 100, as_of: Union[dt.date, dt.datetime] = None, exchange_code: ExchangeCode = None, sort_by_rank: bool = True, ) -> List[Asset]: """ Get an asset by identifier and identifier type :param id_values: identifier values :param id_type: identifiers type :param limit: max number of results :param exchange_code: exchange code :param as_of: As of date for query :param sort_by_rank: whether to sort assets by rank. This flag is ignored when using SecMasterContext :return: list of Asset objects or None **Usage** Get asset object using a specified identifier and identifier type. Where the identifiers are temporal (and can change over time), will use the current MarketContext to evaluate based on the specified date. **Examples** Get asset by bloomberg id: >>> gs = await SecurityMaster.get_many_assets_async(["GS UN"], AssetIdentifier.BLOOMBERG_ID) Get asset by ticker and exchange code: >>> gs = await SecurityMaster.get_many_assets_async(["GS"], AssetIdentifier.TICKER, >>> exchange_code=ExchangeCode.NYSE) Get asset by ticker and asset type: >>> spx = await SecurityMaster.get_many_assets_async(["SPX"], AssetIdentifier.TICKER, >>> asset_type=AssetType.INDEX) **See also** :class:`AssetIdentifier` :func:`get_many_assets` """ span = Tracer.active_span() tracer = Tracer('SecurityMaster.get_many_assets_async') if span and span.is_recording() else nullcontext() with tracer as scope: if scope and scope.span: scope.span.set_tag(f'request.ids.{id_type.value}', len(id_values) if id_values else 0) query, as_of = cls.get_asset_query(id_values, id_type, as_of, exchange_code) if sort_by_rank: results = await GsAssetApi.get_many_assets_async(as_of=as_of, order_by=['>rank'], limit=limit, **query) else: results = await GsAssetApi.get_many_assets_async(as_of=as_of, limit=limit, **query) return cls._get_many_assets_results(results) @classmethod def _get_security_master_asset_params( cls, id_value: str, id_type: SecurityIdentifier, as_of: Union[dt.date, dt.datetime] = None, fields: Optional[List[str]] = None, ) -> dict: as_of = as_of or dt.datetime(2100, 1, 1) type_ = id_type.value params = { type_: id_value, 'asOfDate': as_of.strftime('%Y-%m-%d'), # TODO: update endpoint to take times } if fields is not None: request_fields = {'identifiers', 'assetClass', 'type', 'currency', 'exchange', 'id'} request_fields.update(fields) params['fields'] = request_fields return params @classmethod def _get_security_master_asset_response(cls, response) -> SecMasterAsset: if response['totalResults'] == 0: return None asset_dict = response['results'][0] asset_id = asset_dict['identifiers'].get("assetId", None) # Converting dict to Asset Class asset_name = asset_dict.get('name', None) asset_exchange = asset_dict.get("exchange").get("name", None) if "exchange" in asset_dict else None asset_currency = asset_dict.get('currency', None) try: asset_type = AssetType(asset_dict['type']) asset_class = AssetClass(asset_dict['assetClass']) return SecMasterAsset( id_=asset_id, asset_type=asset_type, asset_class=asset_class, name=asset_name, exchange=asset_exchange, currency=asset_currency, entity=asset_dict, ) except ValueError: raise NotImplementedError( f"Not yet implemented for AssetType={asset_dict['type']}, AssetClass={asset_dict['assetClass']}." ) @classmethod def _get_security_master_asset( cls, id_value: str, id_type: SecurityIdentifier, as_of: Union[dt.date, dt.datetime] = None, fields: Optional[List[str]] = None, ) -> SecMasterAsset: params = cls._get_security_master_asset_params(id_value, id_type, as_of, fields) response = GsSession.current.sync.get('/markets/securities', payload=params) return cls._get_security_master_asset_response(response) @classmethod async def _get_security_master_asset_async( cls, id_value: str, id_type: SecurityIdentifier, as_of: Union[dt.date, dt.datetime] = None, fields: Optional[List[str]] = None, ) -> SecMasterAsset: params = cls._get_security_master_asset_params(id_value, id_type, as_of, fields) response = await GsSession.current.async_.get('/markets/securities', payload=params) return cls._get_security_master_asset_response(response) @classmethod def get_identifiers( cls, id_values: List[str], id_type: SecurityIdentifier, as_of: dt.datetime = None, start: dt.datetime = None, end: dt.datetime = None, ) -> dict: """ Get identifiers for given assets. :param id_values: identifier values e.g. ['GS UN'] :param id_type: identifier type e.g. BBID :param as_of: point in time to use for resolving given ids to assets :param start: restrict results to ids updated after this time :param end: restrict results to ids updated before this time :return: dict from IDs (of id_type) to available identifiers """ if cls._source != SecurityMasterSource.SECURITY_MASTER: raise NotImplementedError("method not available when using Asset Service") as_of = as_of or dt.datetime.now() start = start or dt.datetime(1970, 1, 1) end = end or dt.datetime(2100, 1, 1) type_ = id_type.value params = { type_: id_values, 'fields': ['id', 'identifiers'], 'asOfDate': as_of.strftime('%Y-%m-%d'), # TODO: update endpoint to take times } r = GsSession.current.sync.get('/markets/securities', payload=params) id_map = {} for asset in r['results']: id_map[asset['identifiers'][type_]] = asset['id'] if len(id_map) == 0: return {} output = {} for k, v in id_map.items(): r = GsSession.current.sync.get(f'/markets/securities/{v}/identifiers') piece = [] for e in r['results']: piece.append(e) output[k] = piece return output @staticmethod def asset_type_to_str(asset_class: AssetClass, asset_type: AssetType): if asset_type == AssetType.STOCK: return "Common Stock" if asset_type == AssetType.INDEX and asset_class == AssetClass.Equity: return "Equity Index" return asset_type.value @classmethod def get_all_identifiers_gen( cls, class_: AssetClass = None, types: Optional[List[AssetType]] = None, as_of: dt.datetime = None, *, id_type: SecurityIdentifier = SecurityIdentifier.ID, use_offset_key=True, sleep=0.5, ) -> Generator[dict, None, None]: """ Get identifiers for all matching assets. Returns a generator iterator so that the caller can load each page of results using next(). :param class_: if not None, restrict results to assets of given class e.g. Equity :param types: if not None, restrict results to given types e.g. Stock :param as_of: point in time for which identifiers are fetched :param id_type: identifier type to use for keys of results :param use_offset_key: whether to use offset keys for pagination (required for large result sets) :param sleep: seconds to sleep between API calls (to avoid server-side throttling) :return: a generator iterator that yields dicts from id (of the id_type) to available identifiers """ if cls._source != SecurityMasterSource.SECURITY_MASTER: raise NotImplementedError("method not available when using Asset Service") as_of = as_of or dt.datetime.now() if types is not None: p = partial(cls.asset_type_to_str, class_) types = set(map(p, types)) params = { 'fields': ['id', 'identifiers', 'assetClass', 'type'], 'asOfDate': as_of.date(), 'limit': cls._page_size, 'type': types, } while True: r = _get_with_retries('/markets/securities', params) if r['totalResults'] == 0: return output = {} for e in r['results']: # TODO: perform assetClass filtering on server side once assetClass is supported (just Equties is # supported for now) if class_ is None or e['assetClass'] == class_.value: box = e['identifiers'] box['id'] = e['id'] # copy top-level security id into result key = box[id_type.value] if key in box: _logger.debug(f'encountered duplicate key {key}') output[key] = box yield output if use_offset_key: if 'offsetKey' not in r: return params['offsetKey'] = r['offsetKey'] else: params['offset'] = params.get('offset', 0) + cls._page_size if params['offset'] + params['limit'] > 10000: _logger.warning('reached result size limit; enable use of offset keys to retrieve all results') return time.sleep(sleep) @classmethod def get_all_identifiers( cls, class_: AssetClass = None, types: Optional[List[AssetType]] = None, as_of: dt.datetime = None, *, id_type: SecurityIdentifier = SecurityIdentifier.ID, use_offset_key=True, sleep=0.5, ) -> Dict[str, dict]: """ Get identifiers for all matching assets. :param class_: if not None, restrict results to assets of given class e.g. Equity :param types: if not None, restrict results to given types e.g. Stock :param as_of: point in time for which identifiers are fetched :param id_type: identifier type to use for keys of results :param use_offset_key: whether to use offset keys for pagination (required for large result sets) :param sleep: seconds to sleep between API calls (to avoid server-side throttling) :return: dict from id (of the id_type) to available identifiers """ gen = cls.get_all_identifiers_gen( class_, types, as_of, id_type=id_type, use_offset_key=use_offset_key, sleep=sleep ) accumulator = dict() while True: try: accumulator.update(next(gen)) except StopIteration: return accumulator @classmethod def map_identifiers( cls, input_type: SecurityIdentifier, ids: Iterable[str], output_types: Iterable[SecurityIdentifier] = frozenset([SecurityIdentifier.GSID]), start_date: dt.date = None, end_date: dt.date = None, as_of_date: dt.date = None, ) -> Dict[dt.date, dict]: """ Map to other identifier types, from given IDs. :param input_type: type of input IDs :param ids: security IDs :param output_types: types of IDs to map to :param start_date: first as-of date (defaults to current date) :param end_date: last as-of date (defaults to current date) :param as_of_date: an exact as-of date for mapping :return: dict containing mappings for as-of date(s) **Examples** Get CUSIP for GS UN: >>> result = SecurityMaster.map_identifiers(SecurityIdentifier.BBID, ["GS UN"], [SecurityIdentifier.CUSIP]) Get Bloomberg ticker for 104563 as-of a past date: >>> result = SecurityMaster.map_identifiers(SecurityIdentifier.GSID, ["104563"], [SecurityIdentifier.BBG], ... as_of_date=dt.date(2021, 4, 19)) """ if isinstance(ids, str): raise MqTypeError("expected an iterable of strings e.g. list of strings") def get_asset_id_type(type_: SecurityIdentifier): try: return GsIdType[type_.value] except KeyError: raise MqValueError(f'unsupported type {type_.value}') if cls._source == SecurityMasterSource.ASSET_SERVICE: output_types = list(output_types) if len(output_types) != 1: raise MqValueError('provide exactly one output type') if (start_date or end_date) is not None: raise MqValueError('use as_of_date instead of start_date and/or end_date') if as_of_date is None: as_of_date = dt.date.today() input_type = get_asset_id_type(input_type) output_type = get_asset_id_type(output_types[0]) as_of = None if as_of_date is None else dt.datetime.combine(as_of_date, dt.time(tzinfo=dt.timezone.utc)) result = GsAssetApi.map_identifiers(input_type, output_type, list(ids), as_of=as_of, multimap=True) if len(result) == 0: return result inner = {k: {output_type.name: v} for k, v in result.items()} return {as_of.strftime('%Y-%m-%d'): inner} assert cls._source == SecurityMasterSource.SECURITY_MASTER params = { input_type.value: list(ids), 'toIdentifiers': [identifier.value for identifier in output_types], 'compact': True, } if as_of_date is not None: if (start_date or end_date) is not None: raise MqValueError('provide (start date / end date) or as-of date, but not both') params['startDate'] = as_of_date params['endDate'] = as_of_date if start_date is not None: params['startDate'] = start_date if end_date is not None: params['endDate'] = end_date r = _get_with_retries('/markets/securities/map', params) results = r['results'] if isinstance(results, dict): return results output = dict() date_format = '%Y-%m-%d' date_delta = dt.timedelta(days=1) for row in results: current = dt.datetime.strptime(row['startDate'], date_format) end = dt.datetime.strptime(row['endDate'], date_format) while current <= end: outer = output.setdefault(current, dict()) inner = outer.setdefault(row["input"], dict()) output_type = row["outputType"] output_value = row["outputValue"] if output_type == "ric": if SecurityIdentifier.RIC in output_types: values = inner.setdefault('ric', []) if output_value not in values: values.append(output_value) if SecurityIdentifier.ASSET_ID in output_types and "assetId" in row: values = inner.setdefault('assetId', []) asset_id = row['assetId'] if asset_id not in values: values.append(asset_id) elif output_type == "bbg": if SecurityIdentifier.BBG in output_types: if SecurityIdentifier.BBG.value not in inner: inner[SecurityIdentifier.BBG.value] = [] inner[SecurityIdentifier.BBG.value].append(output_value) if SecurityIdentifier.BBID in output_types: exchange = row.get('exchange') if SecurityIdentifier.BBID.value not in inner: inner[SecurityIdentifier.BBID.value] = [] if exchange is not None: inner[SecurityIdentifier.BBID.value].append(f"{output_value} {exchange}") else: inner[SecurityIdentifier.BBID.value].append(f"{output_value}") if SecurityIdentifier.BCID in output_types: composite_exchange = row.get('compositeExchange') if composite_exchange is not None: if SecurityIdentifier.BCID.value not in inner: inner[SecurityIdentifier.BCID.value] = [] inner[SecurityIdentifier.BCID.value].append(f"{output_value} {composite_exchange}") else: if SecurityIdentifier(output_type) in output_types: if output_type not in inner: inner[output_type] = [] if output_value not in inner[output_type]: inner[output_type].append(output_value) current += date_delta # much faster to run strftime (once for each date) at the end return {k.strftime(date_format): v for k, v in output.items()} ================================================ FILE: gs_quant/models/__init__.py ================================================ """ Copyright 2020 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ ================================================ FILE: gs_quant/models/epidemiology.py ================================================ """ Copyright 2020 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from abc import ABC, abstractmethod from typing import Type, Union import numpy as np from lmfit import minimize, Parameters, report_fit from scipy.integrate import odeint """ Statistical models for the transmission of infectious diseases """ class CompartmentalModel(ABC): @classmethod @abstractmethod def calibrate(cls, xs, t, parameters) -> tuple: """Should be of form callable(y, t, ...) See https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.odeint.html""" raise NotImplementedError @classmethod @abstractmethod def get_parameters(cls, *args, **kwargs) -> tuple: raise NotImplementedError class SIR(CompartmentalModel): """SIR Model""" @classmethod def calibrate(cls, xs: tuple, t: float, parameters: Union[Parameters, tuple]) -> tuple: """ SIR model derivatives at t. :param xs: variables that we are solving for, i.e. [S]usceptible, [I]nfected, [R]emoved :param t: time parameter, inactive for this model :param parameters: parameters of the model (not including initial conditions), i.e. beta, gamma, N :return: tuple, the derivatives dSdt, dIdt, dRdt of each of the S, I, R variables """ s, i, r = xs if isinstance(parameters, Parameters): beta = parameters['beta'].value gamma = parameters['gamma'].value N = parameters['N'].value elif isinstance(parameters, tuple): beta, gamma, N = parameters else: raise ValueError("Cannot recognize parameter input") dSdt = -beta * s * i / N dIdt = beta * s * i / N - gamma * i dRdt = gamma * i return dSdt, dIdt, dRdt @classmethod def get_parameters( cls, S0: float, I0: float, R0: float, N: float, beta: float = 0.2, gamma: float = 0.1, beta_max: float = 10, gamma_max: float = 1, S0_fixed: bool = True, S0_max: float = 1e6, beta_fixed: bool = False, gamma_fixed: bool = False, R0_fixed: bool = True, R0_max: float = 1e6, I0_fixed: bool = True, I0_max: float = 1e6, ) -> tuple: """ Produce a set of parameters for the SIR model. :param S0: initial number of susceptible in the population :param I0: initial number of infected in the population, usually set to 1 :param R0: initial number of recovered/removed in the population, usually set to 0 :param N: size of the population :param beta: transmission rate parameter :param gamma: recovery rate parameter :param beta_max: maximum value to consider for beta during parameter fitting :param gamma_max: maximum value of gamma to consider during parameter fitting :param S0_fixed: whether to keep S0 fixed during fitting :param S0_max: maximum value of S0 to consider during parameter fitting :param R0_fixed: whether to keep R0 fixed during fitting :param R0_max: maximum value of R0 to consider during parameter fitting :param I0_fixed: whether to keep I0 fixed during fitting :param I0_max: maximum value of I0 to consider during parameter fitting :return: tuple[Parameters, list]: (parameters, a list of the names of the variables for initial conditions) """ parameters = Parameters() parameters.add('N', value=N, min=0, max=N, vary=False) parameters.add('S0', value=S0, min=0, max=S0_max, vary=not S0_fixed) parameters.add('I0', value=I0, min=0, max=I0_max, vary=not I0_fixed) parameters.add('R0', value=R0, min=0, max=R0_max, vary=not R0_fixed) parameters.add('beta', value=beta, min=0, max=beta_max, vary=not beta_fixed) parameters.add('gamma', value=gamma, min=0, max=gamma_max, vary=not gamma_fixed) initial_conditions = ['S0', 'I0', 'R0'] return parameters, initial_conditions class SEIR(CompartmentalModel): """SEIR Model""" @classmethod def calibrate(cls, xs: tuple, t: float, parameters: Union[Parameters, tuple]) -> tuple: """ SEIR model derivatives at t. :param xs: variables that we are solving for, i.e. [S]usceptible, [E]xposed, [I]nfected, [R]emoved :param t: time parameter, inactive for this model :param parameters: parameters of the model (not including initial conditions), i.e. beta, gamma, sigma, N :return: tuple, the derivatives dSdt, dEdt, dIdt, dRdt of each of the S, E, I, R variables """ s, e, i, r = xs if isinstance(parameters, Parameters): beta = parameters['beta'].value gamma = parameters['gamma'].value sigma = parameters['sigma'].value N = parameters['N'].value elif isinstance(parameters, tuple): beta, gamma, sigma, N = parameters else: raise ValueError("Cannot recognize parameter input") dSdt = -beta * s * i / N dEdt = beta * s * i / N - sigma * e dIdt = sigma * e - gamma * i dRdt = gamma * i return dSdt, dEdt, dIdt, dRdt @classmethod def get_parameters( cls, S0: float, E0: float, I0: float, R0: float, N: float, beta: float = 0.2, gamma: float = 0.1, sigma: float = 0.2, beta_max: float = 10, gamma_max: float = 1, sigma_max: float = 1, beta_fixed: bool = False, gamma_fixed: bool = False, sigma_fixed: bool = False, S0_fixed: bool = True, S0_max: float = 1e6, R0_fixed: bool = True, R0_max: float = 1e6, I0_fixed: bool = True, I0_max: float = 1e6, E0_fixed: bool = True, E0_max: float = 1e6, ) -> tuple: """ Produce a set of parameters for the SIR model. :param S0: initial number of susceptible in the population :param E0: initial number of exposed in the population :param I0: initial number of infected in the population, usually set to 1 :param R0: initial number of recovered/removed in the population, usually set to 0 :param N: size of the population :param beta: transmission rate parameter :param gamma: recovery rate parameter :param sigma: parameter controlling transition from exposed to infectious :param beta_max: maximum value to consider for beta during parameter fitting :param gamma_max: maximum value of gamma to consider during parameter fitting :param sigma_max: maximum value of gamma to consider during parameter fitting :param S0_fixed: whether to keep S0 fixed during fitting :param S0_max: maximum value of S0 to consider during parameter fitting :param E0_fixed: whether to keep E0 fixed during fitting :param E0_max: maximum value of E0 to consider during parameter fitting :param R0_fixed: whether to keep R0 fixed during fitting :param R0_max: maximum value of R0 to consider during parameter fitting :param I0_fixed: whether to keep I0 fixed during fitting :param I0_max: maximum value of I0 to consider during parameter fitting :return: tuple[Parameters, list]: (parameters, a list of the names of the variables for initial conditions) """ parameters = Parameters() parameters.add('N', value=N, min=0, max=N, vary=False) parameters.add('S0', value=S0, min=0, max=S0_max, vary=not S0_fixed) parameters.add('E0', value=E0, min=0, max=E0_max, vary=not E0_fixed) parameters.add('I0', value=I0, min=0, max=I0_max, vary=not I0_fixed) parameters.add('R0', value=R0, min=0, max=R0_max, vary=not R0_fixed) parameters.add('beta', value=beta, min=0, max=beta_max, vary=not beta_fixed) parameters.add('gamma', value=gamma, min=0, max=gamma_max, vary=not gamma_fixed) parameters.add('sigma', value=sigma, min=0, max=sigma_max, vary=not sigma_fixed) initial_conditions = ['S0', 'E0', 'I0', 'R0'] return parameters, initial_conditions def switch(t: float, T: float, eta: float = 0, xi: float = 0.1, nu: float = 0) -> float: """ Return a time-dependent factor that decreases exponentially, to scale parameters that are affected at some time point T. Ex. quarantine control measures were instated at some point T > t0, where t0 is the first day in available data. After this point, transmission rate was effectively reduced. This is modeled as a time-varying exponential decrease from 1 (no effect) to some fixed coefficient eta (the full effect) (ie. at time t0 it is 1 * beta, and by time T + nu, it is around eta * beta). If quarantine reduces transmission rate by 60%, eta would be 0.4. The paramater xi controls the steepness of this decrease, the nu controls the shift relative to time T at which the decrease peaks (ie. effects from quarantine measures at time T are visible at T+5 so nu=5). If nu=0, steepest point is at time T. To disable the 'switch', set eta = 1.0 :param t: current time step :param T: some fixed time step at which the regime 'switches' - T is relative to t0, which is positioned at 0 NOTE: this is not necessarily the steepest point of decrease. That is controlled by nu. :param eta: optional - the proportional reduction in transmission rate after control measures (ie. quarantine) :param xi: optional - the slope of the exponential decrease :param nu: optional - the shift of the exponential decrease :return: """ return eta + (1 - eta) / (1 + np.exp(xi * (t - T - nu))) class SEIRCM(CompartmentalModel): """SEIR Model from https://www.medrxiv.org/content/10.1101/2020.03.04.20031104v1.full.pdf with cumulative cases (C) and cumulative fatalities (M)""" @classmethod def calibrate(cls, xs: tuple, t: float, parameters: Union[Parameters, tuple]) -> tuple: """ SEIRCM model derivatives at t. :param xs: variables that we are solving for i.e. [S]usceptible, [E]xposed, [I]nfected, [R]emoved, [C]ases, [M]ortality :param t: time parameter :param parameters: parameters of the model (not including initial conditions) i.e. beta, gamma, sigma, eta, epsilon :return: the derivatives of each of the S, E, I, R, C, M variables """ s, e, i, r, c, m = xs if isinstance(parameters, Parameters): beta = parameters['beta'].value # transmission rate gamma = parameters['gamma'].value # removal rate sigma = parameters['sigma'].value # infection rate eta = parameters['eta'].value # control measure redux epsilon = parameters['sigma'].value # case fatality rate T_quarantine = parameters['T'].value # time of quarantine policy, relative to t=0 (first reported case) elif isinstance(parameters, tuple): beta, gamma, sigma, eta, epsilon, T_quarantine = parameters else: raise ValueError("Cannot recognize parameter input") # total population N = s + e + i + r # if T_quarantine is 0, we are not considering effect of quarantine policy so scale factor is fixed at 1 quarantine_factor = switch(t, T_quarantine, eta=eta) if T_quarantine else 1 dSdt = -(quarantine_factor * beta) * s * i / N # susceptible -> exposed dEdt = (quarantine_factor * beta) * s * i / N - sigma * e # exposed -> infected AND recorded cases dIdt = sigma * e - gamma * i # infected -> removed (recovered AND dead) dRdt = (1 - epsilon) * gamma * i # recovered (from removed) dCdt = sigma * e # recorded cases (from infected) dMdt = epsilon * gamma * i # dead (from removed) return dSdt, dEdt, dIdt, dRdt, dCdt, dMdt @classmethod def get_parameters( cls, S0: float, E0: float, I0: float, R0: float, C0: float, M0: float, T_quarantine: float = 0, beta: float = 0.2, gamma: float = 0.1, sigma: float = 0.2, eta: float = 0.6, epsilon: float = 0.02, beta_max: float = 10, gamma_max: float = 1, sigma_max: float = 1, eta_max: float = 1, epsilon_max: float = 1, beta_fixed: bool = False, gamma_fixed: bool = False, sigma_fixed: bool = False, eta_fixed: bool = False, epsilon_fixed: bool = False, S0_fixed: bool = True, S0_max: float = 1e6, R0_fixed: bool = True, R0_max: float = 1e6, I0_fixed: bool = True, I0_max: float = 1e6, E0_fixed: bool = True, E0_max: float = 1e6, C0_fixed: bool = True, C0_max: float = 1e6, M0_fixed: bool = True, M0_max: float = 1e6, ) -> tuple: """ Produce a set of parameters for the SIERCM model. :param S0: initial number of susceptible in the population :param E0: initial number of exposed in the population :param I0: initial number of infected in the population, usually set to 1 :param R0: initial number of recovered/removed in the population, usually set to 0 :param C0: initial number of cumulative infected cases :param M0: initial number of cumulative fatalities :param T_quarantine: relative time at which quarantine policy goes in effect. If 0, not used. :param beta: transmission rate parameter :param gamma: recovery rate parameter :param sigma: parameter controlling transition from exposed to infectious :param eta: parameter controlling proportion by which quarantine measure reduces rate of transmission :param epsilon: parameter controlling rate of death :param beta_max: maximum value to consider for beta during parameter fitting :param gamma_max: maximum value of gamma to consider during parameter fitting :param sigma_max: maximum value to consider for sigma during parameter fitting :param eta_max: maximum value to consider for eta during parameter fitting :param epsilon_max: maximum value to consider for epsilon during parameter fitting :param beta_fixed: whether to keep beta fixed during fitting :param gamma_fixed: whether to keep gamma fixed during fitting :param sigma_fixed: whether to keep sigma fixed during fitting :param eta_fixed: whether to keep eta fixed during fitting :param epsilon_fixed: whether to keep epsilon fixed during fitting :param S0_fixed: whether to keep S0 fixed during fitting :param S0_max: maximum value of S0 to consider during parameter fitting :param E0_fixed: whether to keep E0 fixed during fitting :param E0_max: maximum value of E0 to consider during parameter fitting :param R0_fixed: whether to keep R0 fixed during fitting :param R0_max: maximum value of R0 to consider during parameter fitting :param I0_fixed: whether to keep I0 fixed during fitting :param I0_max: maximum value of I0 to consider during parameter fitting :param C0_fixed: whether to keep C0 fixed during fitting :param C0_max: maximum value of C0 to consider during parameter fitting :param M0_fixed: whether to keep M0 fixed during fitting :param M0_max: maximum value of M0 to consider during parameter fitting :return: tuple[Parameters, list]: (parameters, a list of the names of the variables for initial conditions) """ parameters = Parameters() parameters.add('T', value=T_quarantine, min=0, max=T_quarantine, vary=False) parameters.add('S0', value=S0, min=0, max=S0_max, vary=not S0_fixed) parameters.add('E0', value=E0, min=0, max=E0_max, vary=not E0_fixed) parameters.add('I0', value=I0, min=0, max=I0_max, vary=not I0_fixed) parameters.add('R0', value=R0, min=0, max=R0_max, vary=not R0_fixed) parameters.add('C0', value=C0, min=0, max=C0_max, vary=not C0_fixed) parameters.add('M0', value=M0, min=0, max=M0_max, vary=not M0_fixed) parameters.add('beta', value=beta, min=0, max=beta_max, vary=not beta_fixed) parameters.add('gamma', value=gamma, min=0, max=gamma_max, vary=not gamma_fixed) parameters.add('sigma', value=sigma, min=0, max=sigma_max, vary=not sigma_fixed) parameters.add('eta', value=eta, min=0, max=eta_max, vary=not eta_fixed) parameters.add('epsilon', value=epsilon, min=0, max=epsilon_max, vary=not epsilon_fixed) initial_conditions = ['S0', 'E0', 'I0', 'R0', 'C0', 'M0'] return parameters, initial_conditions class SEIRCMAgeStratified(CompartmentalModel): """Age-structured SEIRCM Model from https://www.medrxiv.org/content/10.1101/2020.03.04.20031104v1.full.pdf""" @classmethod def calibrate(cls, y: list, t: float, parameters: Parameters) -> list: """ SEIR model derivatives at t. :param y: variables that we are solving for i.e. [S]usceptible, [E]xposed, [I]nfected, [R]emoved, [C]ases, [M]ortality :param t: time parameter :param parameters: parameters of the model (not including initial conditions) i.e. beta, gamma, sigma, eta, epsilon :return: the derivatives dydt of each of the variable in y """ beta = parameters['beta'].value # transmission rate gamma = parameters['gamma'].value # removal rate sigma = parameters['sigma'].value # infection rate eta = parameters['eta'].value # control measure redux T_quarantine = parameters['T'].value # time of quarantine policy, relative to t=0 (first reported case) K = parameters['K'].value # number of age groups # y is of dimension (6 * K) x 1, where the first K are 'S', next K are 'E', and so on for each age group... assert len(y) == 6 * K, f'Error: SEIRCM states not organized into {K} age groups!' dydt = [0] * len(y) def epsilon(k): # case fatality rate per age group return parameters[f'epsilon_{k}'].value N = sum(y[: 4 * K]) # sum(S) + sum(E) + sum(I) + sum(R) where sum is over age groups I_total = sum(y[2 * K : 3 * K]) # total number of infectious people across age groups s, e, i = lambda k: y[k], lambda k: y[K + k], lambda k: y[2 * K + k] # if T_quarantine is 0, we are not considering effect of quarantine policy so scale factor is fixed at 1 quarantine_factor = switch(t, T_quarantine, eta=eta) if T_quarantine else 1 for k in range(K): dydt[k] = -(quarantine_factor * beta) * s(k) * I_total / N # susceptible -> exposed dydt[K + k] = (quarantine_factor * beta) * s(k) * I_total / N - sigma * e(k) # exposed -> infected dydt[2 * K + k] = sigma * e(k) - gamma * i(k) # infected -> removed dydt[3 * K + k] = (1 - epsilon(k)) * gamma * i(k) # -> cumulative recovered (from removed) dydt[4 * K + k] = sigma * e(k) # -> cumulative recorded cases (from infected) dydt[5 * K + k] = epsilon(k) * gamma * i(k) # -> cumulative fatalities (from removed) return dydt @classmethod def get_parameters( cls, S0: np.ndarray, E0: np.ndarray, I0: np.ndarray, R0: np.ndarray, C0: np.ndarray, M0: np.ndarray, K: int, T_quarantine: float = 0, beta: float = 0.2, gamma: float = 0.1, sigma: float = 0.2, eta: float = 0.6, epsilon: np.ndarray = None, beta_max: float = 10, gamma_max: float = 1, sigma_max: float = 1, eta_max: float = 1, epsilon_max: float = 1, beta_fixed: bool = False, gamma_fixed: bool = False, sigma_fixed: bool = False, eta_fixed: bool = False, epsilon_fixed: bool = False, S0_fixed: bool = True, S0_max: float = 1e6, R0_fixed: bool = True, R0_max: float = 1e6, I0_fixed: bool = True, I0_max: float = 1e6, E0_fixed: bool = True, E0_max: float = 1e6, C0_fixed: bool = True, C0_max: float = 1e6, M0_fixed: bool = True, M0_max: float = 1e6, ) -> tuple: """ Produce a set of parameters for the age-stratified SIERCM model. :param S0: initial number of susceptible in the population per age group :param E0: initial number of exposed in the population per age group :param I0: initial number of infected in the population per age group :param R0: initial number of recovered/removed in the population per age group :param C0: initial number of cumulative infected cases per age group :param M0: initial number of cumulative fatalities per age group :param K: number of age groups :param T_quarantine: relative time at which quarantine policy goes in effect. If 0, not used. :param beta: transmission rate parameter :param gamma: removal rate parameter :param sigma: parameter controlling transition from exposed to infectious :param eta: parameter controlling proportion by which quarantine measure reduces rate of transmission :param epsilon: parameter controlling rate of death per age group. Recovery rate == 1 - epsilon :param beta_max: maximum value to consider for beta during parameter fitting :param gamma_max: maximum value of gamma to consider during parameter fitting :param sigma_max: maximum value to consider for sigma during parameter fitting :param eta_max: maximum value to consider for eta during parameter fitting :param epsilon_max: maximum value to consider for epsilon during parameter fitting :param beta_fixed: whether to keep beta fixed during fitting :param gamma_fixed: whether to keep gamma fixed during fitting :param sigma_fixed: whether to keep sigma fixed during fitting :param eta_fixed: whether to keep eta fixed during fitting :param epsilon_fixed: whether to keep epsilon fixed during fitting :param S0_fixed: whether to keep S0 fixed during fitting :param S0_max: maximum value of S0 to consider during parameter fitting :param E0_fixed: whether to keep E0 fixed during fitting :param E0_max: maximum value of E0 to consider during parameter fitting :param R0_fixed: whether to keep R0 fixed during fitting :param R0_max: maximum value of R0 to consider during parameter fitting :param I0_fixed: whether to keep I0 fixed during fitting :param I0_max: maximum value of I0 to consider during parameter fitting :param C0_fixed: whether to keep C0 fixed during fitting :param C0_max: maximum value of C0 to consider during parameter fitting :param M0_fixed: whether to keep M0 fixed during fitting :param M0_max: maximum value of M0 to consider during parameter fitting :return: tuple[Parameters, list]: (parameters, a list of the names of the variables for initial conditions) """ parameters = Parameters() parameters.add('K', value=K, min=0, max=K, vary=False) parameters.add('T', value=T_quarantine, min=-1, max=T_quarantine, vary=False) parameters.add('beta', value=beta, min=0, max=beta_max, vary=not beta_fixed) parameters.add('gamma', value=gamma, min=0, max=gamma_max, vary=not gamma_fixed) parameters.add('sigma', value=sigma, min=0, max=sigma_max, vary=not sigma_fixed) parameters.add('eta', value=eta, min=0, max=eta_max, vary=not eta_fixed) # add parameters that vary by age group for k in range(K): parameters.add(f'epsilon_{k}', value=epsilon[k], min=0, max=epsilon_max, vary=not epsilon_fixed) # add initial state conditions that vary by age group initial_conditions = [] for param, param_value, param_value_max, param_fixed in [ ('S0', S0, S0_max, S0_fixed), ('E0', E0, E0_max, E0_fixed), ('I0', I0, I0_max, I0_fixed), ('R0', R0, R0_max, R0_fixed), ('C0', C0, C0_max, C0_fixed), ('M0', M0, M0_max, M0_fixed), ]: for k in range(K): parameters.add(f'{param}_{k}', value=param_value[k], min=0, max=param_value_max, vary=not param_fixed) initial_conditions.append(f'{param}_{k}') return parameters, initial_conditions class EpidemicModel: """Class to perform solutions and parameter-fitting of epidemic models""" def __init__( self, model: Type[CompartmentalModel], parameters: tuple = None, data: np.array = None, initial_conditions: list = None, fit_method: str = 'leastsq', error: callable = None, fit_period: float = None, ): """ A class to standardize fitting and solving epidemiological models. :param model: the model to use, currently a class in the form of SIR, SEIR above :param parameters: tuple, parameters to use for the model, defaults to the output of [model].get_parameters :param data: np.array, data that can be used to calibrate the model :param initial_conditions: list, initial conditions for the model :param fit_method: str, the method to use to minimize the (given) error. Available methods are those in the lmfit.minimizer.minimize function. Default is Levenberg-Marquardt least squares minimization. :param error: callable, control which residuals (and in what form) to minimize for fitting. :param fit_period: float, how far back to fit the data, defaults to fitting all data """ self.model = model self.parameters = parameters self.data = data self.initial_conditions = initial_conditions self.fit_method = fit_method self.error = error self.fit_period = fit_period self.result = None self.fitted_parameters = None def solve(self, time_range: np.ndarray, initial_conditions: Union[list, tuple], parameters) -> np.ndarray: """ Integrate the model ODEs to get a solution. :param time_range: the time range to solve for :param initial_conditions: the initial conditions for the solution :param parameters: the parameters for the solution :return: """ x = odeint(self.model.calibrate, initial_conditions, time_range, args=(parameters,)) x = np.array(x) return x def residual(self, parameters: Parameters, time_range: np.arange, data: np.ndarray) -> np.ndarray: """ Obtain fit error (to minimize). :param parameters: parameters to use (which we are usually minimizing the residual for) :param time_range: time range for solution (over which we obtain the residual) :param data: data to fit the models too (i.e. compute residuals in terms of) :return: """ initial_conditions = [] for variable in self.initial_conditions: initial_conditions.append(parameters[variable].value) # obtain solution given current initial conditions and parameters solution = self.solve(time_range, initial_conditions, parameters) # compute residual, using custom error function if it has been passed in residual = solution - data if self.error is None else self.error(solution, data, parameters) if self.fit_period is not None: residual = residual[-self.fit_period :] return residual.ravel() def fit( self, time_range: np.arange = None, parameters: Union[Parameters, tuple] = None, initial_conditions: list = None, residual=None, verbose: bool = False, data: np.array = None, fit_period: float = None, ): """ Fit the model based on data in the form np.array([X1,...,Xn]) """ if data is None: if self.data is None: raise ValueError("No data to fit the model on!") data = self.data if initial_conditions is not None: self.initial_conditions = initial_conditions if self.initial_conditions is None: raise ValueError("No initial conditions to fit the model with!") if parameters is None: if self.parameters is None: raise ValueError("No parameters to fit the model with!") parameters = self.parameters if time_range is None: time_range = np.arange(data.shape[0]) if fit_period is not None: self.fit_period = fit_period if residual is None: residual = self.residual result = minimize(residual, parameters, args=(time_range, data), method=self.fit_method) self.result = result self.fitted_parameters = result.params.valuesdict() if verbose: report_fit(result) return result ================================================ FILE: gs_quant/models/risk_model.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import math from enum import Enum, auto from typing import List, Dict, Tuple, Union import pandas as pd import numpy as np import logging import deprecation from gs_quant.common import Currency from gs_quant.api.gs.risk_models import GsFactorRiskModelApi, GsRiskModelApi, IntradayFactorDataSource from gs_quant.base import EnumBase from gs_quant.data import DataMeasure from gs_quant.errors import MqValueError, MqRequestError from gs_quant.markets.factor import Factor from gs_quant.markets.securities import SecurityMaster, AssetIdentifier from gs_quant.models.risk_model_utils import ( build_pfp_data_dataframe, get_closest_date_index, upload_model_data, get_optional_data_as_dataframe, get_universe_size, get_covariance_matrix_dataframe, build_factor_data_map, build_asset_data_map, build_factor_id_to_name_map, only_factor_data_is_present, batch_and_upload_partial_data, build_factor_volatility_dataframe, batch_and_upload_coverage_data, ) from gs_quant.target.risk_models import ( RiskModel as RiskModelBuilder, RiskModelEventType, RiskModelData, RiskModelCalendar, RiskModelDataAssetsRequest as DataAssetsRequest, RiskModelDataMeasure as Measure, RiskModelCoverage as CoverageType, RiskModelUniverseIdentifier as UniverseIdentifier, Entitlements, RiskModelTerm as Term, RiskModelUniverseIdentifierRequest, Factor as RiskModelFactor, RiskModelType, RiskModelDataMeasure, RiskModelDataAssetsRequest, ) class ReturnFormat(Enum): """Alternative format for data to be returned from get_data functions""" JSON = auto() DATA_FRAME = auto() class Unit(Enum): """Units in which to return a risk model data measure""" PERCENT = auto() STANDARD_DEVIATION = auto() class FactorType(EnumBase, Enum): """Factor represents a risk factor and Category represents a risk factor category""" Factor = 'Factor' Category = 'Category' def __repr__(self): return self.value class RiskModel: """Risk Model Class""" def __init__(self, id_: str, name: str): self.__id: str = id_ self.__name: str = name @property def id(self) -> str: """Get risk model id""" return self.__id @property def name(self) -> str: """Get risk model name""" return self.__name @name.setter def name(self, name: str): """Set risk model name""" self.__name = name def __str__(self): return self.id def __repr__(self): s = "{}('{}','{}'".format(self.__class__.__name__, self.id, self.name) s += ")" return s class MarqueeRiskModel(RiskModel): """Marquee Risk Model Class""" def __init__( self, id_: str, name: str, type_: Union[str, RiskModelType], vendor: str, version: float, coverage: CoverageType, universe_identifier: UniverseIdentifier, term: Term, universe_size: int = None, entitlements: Union[Dict, Entitlements] = None, description: str = None, expected_update_time: dt.time = None, ): super().__init__(id_, name) self.__type: RiskModelType = type_ if type_ and isinstance(type_, RiskModelType) else RiskModelType(type_) self.__vendor = vendor self.__version = version self.__coverage = coverage self.__universe_identifier = universe_identifier self.__term = term self.__universe_size = universe_size self.__entitlements: Entitlements = ( entitlements if entitlements and isinstance(entitlements, Entitlements) else Entitlements.from_dict(entitlements) if entitlements and isinstance(entitlements, Dict) else None ) self.__description: str = description self.__expected_update_time = expected_update_time @property def type(self) -> RiskModelType: """Get risk model type""" return self.__type @type.setter def type(self, type_: RiskModelType): """Set risk model type""" self.__type = type_ @property def vendor(self) -> str: """Get risk model vendor""" return self.__vendor @vendor.setter def vendor(self, vendor): """Set risk model vendor""" self.__vendor = vendor @property def version(self) -> float: """Get risk model version""" return self.__version @version.setter def version(self, version: float): """Set risk model version""" self.__version = version @property def coverage(self) -> CoverageType: """Get risk model coverage""" return self.__coverage @coverage.setter def coverage(self, coverage: CoverageType): """Set risk model coverage""" self.__coverage = coverage @property def universe_identifier(self) -> UniverseIdentifier: """Get risk model universe identifier""" return self.__universe_identifier @property def term(self) -> Term: """Get risk model term""" return self.__term @term.setter def term(self, term: Term): """Set risk model term""" self.__term = term @property def description(self) -> str: """Get risk model description""" return self.__description @description.setter def description(self, description: str): """Set risk model description""" self.__description = description @property def universe_size(self) -> int: """Get risk model universe size""" return self.__universe_size @universe_size.setter def universe_size(self, universe_size: int): """Set risk model universe size""" self.__universe_size = universe_size @property def entitlements(self) -> Entitlements: """Get risk model entitlements""" return self.__entitlements @entitlements.setter def entitlements(self, entitlements: Union[Entitlements, Dict]): """Set risk model entitlements""" self.__entitlements = entitlements @property def expected_update_time(self) -> dt.time: """Get risk model expected update time""" return self.__expected_update_time @expected_update_time.setter def expected_update_time(self, expected_update_time: dt.time): """Set expected update time""" self.__expected_update_time = expected_update_time def delete(self): """Delete existing risk model object from Marquee""" return GsRiskModelApi.delete_risk_model(self.id) def get_dates( self, start_date: dt.date = None, end_date: dt.date = None, event_type: RiskModelEventType = None ) -> List[dt.date]: """Get dates between start_date and end_date for which risk model data is present :param start_date: List returned including and after start_date :param end_date: List returned up to and including end_date :param event_type: Which event type to retrieve dates for :return: A list of dates where risk model data is present **Usage** Get all the dates for which risk model data is present over start_date and end_date (inclusive). **Examples** >>> from gs_quant.models.risk_model import FactorRiskModel, RiskModelEventType >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> dates = model.get_dates(start_date, end_date) **See also** :func:`get_missing_dates` :func:`get_calendar` :func:`get_most_recent_date_from_calendar` """ return [ dt.datetime.strptime(date, "%Y-%m-%d").date() for date in GsRiskModelApi.get_risk_model_dates(self.id, start_date, end_date, event_type=event_type) ] def get_calendar(self, start_date: dt.date = None, end_date: dt.date = None) -> RiskModelCalendar: """Get risk model calendar for existing risk model between start and end date :param start_date: List returned including and after start_date :param end_date: List returned up to and including end_date :return: RiskModelCalendar for model **Usage** Get the risk model calendar. **Examples** >>> from gs_quant.models.risk_model import FactorRiskModel >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> model_calendar = model.get_calendar(start_date, end_date) **See also** :func:`get_most_recent_date_from_calendar` :func:`get_dates` :func:`get_missing_dates` """ calendar = GsRiskModelApi.get_risk_model_calendar(self.id) if not start_date and not end_date: return calendar start_idx = get_closest_date_index(start_date, calendar.business_dates, 'after') if start_date else 0 end_idx = ( get_closest_date_index(end_date, calendar.business_dates, 'before') if end_date else len(calendar.business_dates) ) return RiskModelCalendar(calendar.business_dates[start_idx : end_idx + 1]) def upload_calendar(self, calendar: RiskModelCalendar): """Upload risk model calendar to existing risk model :param calendar: RiskModelCalendar containing list of dates where model data is expected """ return GsRiskModelApi.upload_risk_model_calendar(self.id, calendar) def get_missing_dates(self, start_date: dt.date = None, end_date: dt.date = None) -> List[dt.date]: """Get any dates where data is not published according to expected days returned from the risk model calendar :param start_date: Date to truncate missing dates at :param end_date: Date to truncate missing dates at If no end_date is provided, end_date defaults to T-1 date according to the risk model calendar **Usage** Get dates with missing data between start date and end date. **Examples** >>> from gs_quant.models.risk_model import FactorRiskModel >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> dates = model.get_missing_dates(start_date, end_date) **See also** :func:`get_dates` :func:`get_calendar` """ posted_dates = self.get_dates() if not start_date: start_date = posted_dates[0] if not end_date: end_date = dt.date.today() - dt.timedelta(days=1) calendar = [ dt.datetime.strptime(date, "%Y-%m-%d").date() for date in self.get_calendar(start_date=start_date, end_date=end_date).business_dates ] return [date for date in calendar if date not in posted_dates] def get_most_recent_date_from_calendar(self) -> dt.date: """Get T-1 date according to risk model calendar""" yesterday = dt.date.today() - dt.timedelta(1) calendar = self.get_calendar(end_date=yesterday).business_dates return dt.datetime.strptime(calendar[len(calendar) - 1], '%Y-%m-%d').date() def save(self): """Upload current Risk Model object to Marquee""" model = RiskModelBuilder( self.coverage, self.id, self.name, self.term, self.universe_identifier, self.vendor, self.version, type_=self.type, description=self.description, entitlements=self.entitlements, universe_size=self.universe_size, expected_update_time=self.expected_update_time.strftime('%H:%M:%S') if self.expected_update_time else None, ) try: GsRiskModelApi.create_risk_model(model) except MqRequestError: GsRiskModelApi.update_risk_model(model) @classmethod def get(cls, model_id: str): """Get a risk model from Marquee :param model_id: risk model id corresponding to Marquee Risk Model :return: Risk Model object """ model = GsRiskModelApi.get_risk_model(model_id) return cls.from_target(model) def get_asset_universe( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get asset universe data for existing risk model :param start_date: Start date for data request. Must be equal to end_date if universe array in DataAssetsRequest is empty. :param end_date: End date for data request. Must be equal to start_date if universe array in DataAssetsRequest is empty. :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request :param format: Which format to return the results in :return: Risk model universe data in a pandas dataframe or dict **Usage** Get the assets covered by the model between start date and end date. **Examples** >>> from gs_quant.models.risk_model import FactorRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier, ReturnFormat >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> universe = ['GS UN'] >>> model = FactorRiskModel.get("MODEL_ID") >>> asset_universe = model.get_asset_universe(start_date, end_date, ... DataAssetsRequest(UniverseIdentifier.bbid, universe)) **See also** :func:`get_specific_risk` :func:`get_universe_factor_exposure` :func:`get_total_risk` """ if not assets.universe and not end_date: end_date = start_date results = self.get_data( start_date=start_date, end_date=end_date, assets=assets, measures=[Measure.Asset_Universe], limit_factors=False, ).get('results') dates = [dt.datetime.strptime((data.get('date')), '%Y-%m-%d').date() for data in results] universe = [data.get('assetData').get('universe') for data in results] dates_to_universe = dict(zip(dates, universe)) if format == ReturnFormat.DATA_FRAME: return pd.DataFrame.from_dict(dates_to_universe, orient='index').T return dates_to_universe def get_factor(self, name: str, start_date: dt.date = None, end_date: dt.date = None) -> Factor: """Get risk model factor from its name :param name: Factor name associated with risk model :param start_date: Start date of when to search for factor (optional, default to last month) :param end_date: End date of when to search for factor (optional, default to today) :return: Factor object """ name_matches = [ f for f in self.get_factor_data(start_date=start_date, end_date=end_date, format=ReturnFormat.JSON) if f['name'] == name ] if not name_matches: raise MqValueError(f'Factor with name {name} does not in exist in risk model {self.id}') factor = name_matches.pop() return Factor( risk_model_id=self.id, id_=factor['identifier'], type_=factor['type'], name=factor.get('name'), category=factor.get('factorCategory'), tooltip=factor.get('tooltip'), description=factor.get('description'), glossary_description=factor.get('glossaryDescription'), ) def get_many_factors( self, start_date: dt.date = None, end_date: dt.date = None, factor_names: List[str] = None, factor_ids: List[str] = None, factor_type: FactorType = None, ) -> List[Factor]: """Get risk model factors :param start_date: Start date of when to search for factors (optional, default to last month) :param end_date: End date of when to search for factors :param factor_names: The list of names of factors to get. All names must be valid factor names. If both factor_names and factor_ids are empty, all the factors will be returned. :param factor_ids: The list of ids of factors to get. All ids must be valid factor ids. If both factor_names and factor_ids are empty, all the factors will be returned :param factor_type: Whether to return factors or factor categories. If unspecified, all the factors of all types will be returned :return: A list of Factor objects **Usage** Given a list of factor names and/or ids, return the factor objects that represent the factor requested. If no factor names or ids are provided, all the factors will be returned. **Examples** >>> from gs_quant.models.risk_model import FactorRiskModel, FactorType >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> many_factors = model.get_many_factors(start_date, end_date, factor_names=["factor1", "factor2"], ... factor_type=FactorType.Factor) >>> **See also** :func:`get_factor` :func:`get_factor_data` """ factors_from_model = self.get_factor_data( start_date=start_date, end_date=end_date, factor_type=factor_type, format=ReturnFormat.JSON ) name_matches = [] if not factor_names and not factor_ids: name_matches = factors_from_model else: for f in factors_from_model: if factor_names: if f["name"] in factor_names: name_matches.append(f) factor_names.remove(f["name"]) if factor_ids: if f["identifier"] in factor_ids: name_matches.append(f) factor_ids.remove(f["identifier"]) if factor_names or factor_ids: raise MqValueError( f'Factor names: {factor_names} and factor ids: {factor_ids} not in model' f' {self.id} for date range requested' ) return [ Factor( risk_model_id=self.id, id_=f['identifier'], type_=f['type'], name=f.get('name'), category=f.get('factorCategory'), tooltip=f.get('tooltip'), description=f.get('description'), glossary_description=f.get('glossaryDescription'), ) for f in name_matches ] def save_factor_metadata(self, factor_metadata: RiskModelFactor): """Add metadata to a factor in a risk model :param factor_metadata: factor metadata object """ try: GsFactorRiskModelApi.update_risk_model_factor(self.id, factor_metadata) except MqRequestError: GsFactorRiskModelApi.create_risk_model_factor(self.id, factor_metadata) def delete_factor_metadata(self, factor_id: str): """Delete a factor's metadata from a risk model :param factor_id: factor id associated with risk model's factor """ GsFactorRiskModelApi.delete_risk_model_factor(self.id, factor_id) def get_intraday_factor_data( self, start_time: dt.datetime = dt.datetime.now() - dt.timedelta(hours=3), end_time: dt.datetime = dt.datetime.now(), factors: List[str] = None, factor_ids: List[str] = None, data_source: Union[IntradayFactorDataSource, str] = None, category_filter: List[str] = None, format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get intraday factor data for existing risk model :param start_time: Start time for data request :param end_time: End time for data request :param factors: Filter the results based on factor name. :param factor_ids: List of factor ids associated with risk model :param data_source: Data source to use for the request. :param category_filter: Filter the results to those having one of the specified categories. \ Default is to return all results :param format: Which format to return the results in :return: Risk model intraday factor data **Usage** **Examples** This will get the factor data for the past 3 hours. >>> from gs_quant.models.risk_model import FactorRiskModel >>> import datetime as dt >>> >>> model = FactorRiskModel.get("MODEL_ID") >>> factor_data = model.get_intraday_factor_data() This will get the factor data within the range start_time and end_time >>> from gs_quant.models.risk_model import FactorRiskModel >>> import datetime as dt >>> >>> model = FactorRiskModel.get("MODEL_ID") >>> factor_data = model.get_intraday_factor_data(start_time=dt.datetime(), end_date=dt.date("2023-02-03")) This will return factor data for factors with the specified identifiers >>> from gs_quant.models.risk_model import FactorRiskModel >>> import datetime as dt >>> >>> model = FactorRiskModel.get("MODEL_ID") >>> factor_data = model.get_intraday_factor_data(factorIds=["1", "2", "3"]) This will filter factor data based on factor names >>> from gs_quant.models.risk_model import FactorRiskModel >>> import datetime as dt >>> >>> model = FactorRiskModel.get("MODEL_ID") >>> factor_data = model.get_intraday_factor_data(factors=["Factor name a", "Factor name b", "Factor name c"]) This will return factor data for factors in the specified factor categories >>> from gs_quant.models.risk_model import FactorRiskModel >>> import datetime as dt >>> >>> model = FactorRiskModel.get("MODEL_ID") >>> factor_data = model.get_intraday_factor_data(category_filter=["Factor category 1", "Factor category 2"]) **See also** :func:`get_many_factors :func:`get_factor_returns_by_name` """ factor_categories = category_filter if category_filter else [] intraday_factor_data = GsFactorRiskModelApi.get_risk_model_factor_data_intraday( self.id, start_time=start_time, end_time=end_time, factor_ids=factor_ids, factors=factors, data_source=data_source, factor_categories=factor_categories, ) if format == ReturnFormat.DATA_FRAME: intraday_factor_data = pd.DataFrame(intraday_factor_data) return intraday_factor_data def get_factor_data( self, start_date: dt.date = None, end_date: dt.date = None, identifiers: List[str] = None, include_performance_curve: bool = False, category_filter: List[str] = None, name_filter: List[str] = None, factor_type: FactorType = None, format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get factor data for existing risk model :param start_date: Start date for data request :param end_date: End date for data request :param identifiers: List of factor ids associated with risk model :param include_performance_curve: Include the performance curve of the factors :param category_filter: Filter the results to those having one of the specified categories. \ Default is to return all results :param factor_type: The type of factor. :param format: which format to return the results in :param name_filter: Filter the results based on factor name. :return: Risk model factor data **Usage** Get factor data for factors whose ids are specified in identifiers. **Examples** This will get the factor data for the past 1 month. >>> from gs_quant.models.risk_model import FactorRiskModel >>> import datetime as dt >>> >>> model = FactorRiskModel.get("MODEL_ID") >>> factor_data = model.get_factor_data() This will get the factor data within the range start_date and end_date >>> from gs_quant.models.risk_model import FactorRiskModel >>> import datetime as dt >>> >>> model = FactorRiskModel.get("MODEL_ID") >>> factor_data = model.get_factor_data(start_date=dt.date("2023-01-01"), end_date=dt.date("2023-02-03")) This will return factor data for factors with the specified identifiers >>> from gs_quant.models.risk_model import FactorRiskModel >>> import datetime as dt >>> >>> model = FactorRiskModel.get("MODEL_ID") >>> factor_data = model.get_factor_data(identifiers=["1", "2", "3"]) This will filter factor data based on factor names >>> from gs_quant.models.risk_model import FactorRiskModel >>> import datetime as dt >>> >>> model = FactorRiskModel.get("MODEL_ID") >>> factor_data = model.get_factor_data(name_filter=["Factor name a", "Factor name b", "Factor name c"]) This will return factor data for factors in the specified factor categories >>> from gs_quant.models.risk_model import FactorRiskModel >>> import datetime as dt >>> >>> model = FactorRiskModel.get("MODEL_ID") >>> factor_data = model.get_factor_data(category_filter=["Factor category 1", "Factor category 2"]) This will return all the factors of type "Factor" (won't return data on factor categories) >>> from gs_quant.models.risk_model import FactorRiskModel >>> import datetime as dt >>> >>> model = FactorRiskModel.get("MODEL_ID") >>> factor_data = model.get_factor_data(factor_type=FactorType.Factor) This will only return higher level factor category data. >>> from gs_quant.models.risk_model import FactorRiskModel >>> import datetime as dt >>> >>> model = FactorRiskModel.get("MODEL_ID") >>> factor_data = model.get_factor_data(factor_type=FactorType.Category) **See also** :func:`get_many_factors :func:`get_factor_returns_by_name` """ factor_categories = category_filter if category_filter else [] if factor_type == FactorType.Category: if factor_categories: raise ValueError('Category filter is not applicable while requesting the FactorType as Category') factor_categories = ['Aggregations'] factor_data = GsFactorRiskModelApi.get_risk_model_factor_data( self.id, start_date, end_date, identifiers, include_performance_curve, factor_categories, name_filter, ) if factor_type == FactorType.Factor: if 'Aggregations' in factor_categories: raise ValueError("Aggregations should not be passed while requesting the FactorType as Factor") factor_data = [factor for factor in factor_data if factor['type'] == factor_type.value] if format == ReturnFormat.DATA_FRAME: factor_data = pd.DataFrame(factor_data) return factor_data def get_factor_returns_by_name( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = None, factors: List[str] = [], format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[Dict, pd.DataFrame]: """Get factor return data for existing risk model keyed by name :param start_date: Start date for data request :param end_date: End date for data request :param assets: DataAssetsRequest object with identifier and list of assets to limit the factors by :param factors: The factors to get factor return data for. If empty, the data for all factors is returned :param format: Which format to return the results in :return: Factor returns by name **Usage** Get factor returns between start date and end date. **Examples** >>> from gs_quant.models.risk_model import FactorRiskModel >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> factor_returns = model.get_factor_returns_by_name(start_date, end_date) **See also** :func:`get_factor_returns_by_id` :func:`get_factor_data` """ return self._get_factor_data_measure(Measure.Factor_Return, start_date, end_date, assets, True, factors, format) def get_factor_returns_by_id( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = None, factors: List[str] = [], format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[Dict, pd.DataFrame]: """Get factor return data for existing risk model keyed by factor id :param start_date: Start date for data request :param end_date: End date for data request :param assets: DataAssetsRequest object with identifier and list of assets to limit the factors by :param factors: The factors to get factor return data for. If empty, the data for all factors is returned :param format: Which format to return the results in :return: Factor returns by factor id **Usage** Get factor returns between start date and end date. **Examples** >>> from gs_quant.models.risk_model import FactorRiskModel >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> factor_returns = model.get_factor_returns_by_id(start_date, end_date) **See also** :func:`get_factor_returns_by_name` :func:`get_factor_data` """ return self._get_factor_data_measure( Measure.Factor_Return, start_date, end_date, assets, False, factors, format ) def _get_factor_data_measure( self, requested_measure: RiskModelDataMeasure, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = None, factors_by_name=True, factors: List[str] = [], format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: limit_factors = False measures = [requested_measure, Measure.Factor_Name, Measure.Factor_Id] if assets: measures += [Measure.Universe_Factor_Exposure, Measure.Asset_Universe] limit_factors = True results = self.get_data( start_date=start_date, end_date=end_date, assets=assets, measures=measures, limit_factors=limit_factors ).get('results') identifier = 'factorName' if factors_by_name else 'factorId' factor_data_df = build_factor_data_map(results, identifier, self.id, requested_measure, factors=factors) return factor_data_df if format == ReturnFormat.DATA_FRAME else factor_data_df.to_dict() def _get_asset_data_measure( self, requested_measure: RiskModelDataMeasure, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: results = self.get_data( start_date=start_date, end_date=end_date, assets=assets, measures=[requested_measure, Measure.Asset_Universe], limit_factors=False, ).get('results') measure_data = build_asset_data_map(results, assets.universe, requested_measure, {}) if format == ReturnFormat.DATA_FRAME: measure_data = pd.DataFrame(measure_data) return measure_data def get_universe_exposure( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), factors: List[Union[str, Factor]] = None, get_factors_by_name: bool = False, format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get universe factor exposure data for existing risk model :param start_date: Start date for data request :param end_date: End date for data request :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request :param factors: List of factors to limit the exposure by. If empty, the data for all factors is returned. :param get_factors_by_name: Return results keyed by factor name instead of ID :param format: Which format to return the results in :return: Factor exposure for assets requested **Usage** Given a list of assets, return their exposure to factor between start date and end date. **Examples** >>> from gs_quant.models.risk_model import FactorRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> universe = ['GS UN'] >>> model = FactorRiskModel.get("MODEL_ID") >>> universe_exposure = model.get_universe_exposure(start_date, end_date, ... DataAssetsRequest(UniverseIdentifier.bbid, universe)) **See also** :func:`get_asset_universe` :func:`get_factor_returns_by_name` :func:`get_covariance_matrix` """ results = self.get_data( start_date=start_date, end_date=end_date, assets=assets, factors=factors, measures=[Measure.Factor_Name, Measure.Factor_Id, Measure.Universe_Factor_Exposure, Measure.Asset_Universe], limit_factors=False, ).get('results') factor_map = {} if get_factors_by_name: factor_map = build_factor_id_to_name_map(results) factor_exposure = build_asset_data_map(results, assets.universe, Measure.Universe_Factor_Exposure, factor_map) if format == ReturnFormat.DATA_FRAME: factor_exposure = pd.DataFrame.from_dict( {(i, j): factor_exposure[i][j] for i in factor_exposure.keys() for j in factor_exposure[i].keys()}, orient='index', ) return factor_exposure def get_specific_risk( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get specific risk data for existing risk model :param start_date: Start date for data request :param end_date: End date for data request :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request :param format: Which format to return the results in :return: Specific risk for assets requested **Usage** Get specific risk data for assets specified in `assets` between `start_date` and `end_date` **Examples** >>> from gs_quant.models.risk_model import MacroRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> specific_risk = model.get_specific_risk(start_date, end_date, ... DataAssetsRequest(UniverseIdentifier.bbid, ['GS UN'])) **See also** :func:`get_specific_return` :func:`get_total_risk` """ return self._get_asset_data_measure(Measure.Specific_Risk, start_date, end_date, assets, format) def get_factor_standard_deviation( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = None, factors: List[str] = [], factors_by_name: bool = True, format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get factor standard deviation data for existing risk model keyed by name or id :param start_date: start date for data request :param end_date: end date for data request :param assets: DataAssetsRequest object with identifier and list of assets to limit the factors by :param factors: the factors to get standard deviation for. If empty, the data for all factors is returned :param factors_by_name: whether to identify factors by their name or id. If true, the list of factors must \ be a list of factor names. Otherwise, it should be a list of factor ids :param format: which format to return the results in :return: factor standard deviation **Usage** Get the value of one standard deviation of the factors specified in `factors` **Examples** >>> from gs_quant.models.risk_model import MarqueeRiskModel >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = MarqueeRiskModel.get("MODEL_ID") >>> factor_standard_deviation = model.get_factor_standard_deviation(start_date, end_date, ... factors=['factor1', 'factor2']) **See also** :func:`get_specific_risk` :func:`get_factor_mean` """ return self._get_factor_data_measure( Measure.Factor_Standard_Deviation, start_date, end_date, assets, factors_by_name, factors, format ) def get_factor_mean( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = None, factors: List[str] = [], factors_by_name: bool = True, format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get factor mean data for existing risk model keyed by name or id :param start_date: start date for data request :param end_date: end date for data request :param assets: DataAssetsRequest object with identifier and list of assets to limit the factors by :param factors: the factors to get standard deviation for. If empty, the data for all factors is returned :param factors_by_name: whether to identify factors by their name or id. If true, the list of factors must \ be a list of factor names. Otherwise, it should be a list of factor ids :param format: which format to return the results in :return: factor mean **Usage** Get the value of one mean of the factors specified in `factors` **Examples** >>> from gs_quant.models.risk_model import MarqueeRiskModel >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = MarqueeRiskModel.get("MODEL_ID") >>> factor_mean = model.get_factor_mean(start_date, end_date, factors=['factor1', 'factor2']) **See also** :func:`get_specific_risk` :func:`get_factor_standard_deviation` """ return self._get_factor_data_measure( Measure.Factor_Mean, start_date, end_date, assets, factors_by_name, factors, format ) def get_factor_cross_sectional_mean( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = None, factors: List[str] = [], factors_by_name: bool = True, format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> (Union)[List[Dict], pd.DataFrame]: """ Get factor cross-sectional mean data for existing risk model keyed by name or id :param start_date: start date for data request :param end_date: end date for data request :param assets: DataAssetsRequest object with identifier and list of assets to limit the factors by :param factors: the factors to get standard deviation for. If empty, the data for all factors is returned :param factors_by_name: whether to identify factors by their name or id. If true, the list of factors must \ be a list of factor names. Otherwise, it should be a list of factor ids :param format: which format to return the results in :return: factor cross-sectional mean **Usage** Get the value of one cross-sectional mean of the factors specified in `factors` **Examples** >>> from gs_quant.models.risk_model import MarqueeRiskModel >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = MarqueeRiskModel.get("MODEL_ID") >>> factor_cross_sectional_mean = model.get_factor_cross_sectional_mean(start_date, end_date, factors=['factor1', 'factor2']) **See also** :func:`get_factor_mean` :func:`get_factor_cross_sectional_standard_deviation` """ return self._get_factor_data_measure( Measure.Factor_Cross_Sectional_Mean, start_date, end_date, assets, factors_by_name, factors, format ) def get_factor_cross_sectional_standard_deviation( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = None, factors: List[str] = [], factors_by_name: bool = True, format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get factor cross-sectional standard deviation data for existing risk model keyed by name or id :param start_date: start date for data request :param end_date: end date for data request :param assets: DataAssetsRequest object with identifier and list of assets to limit the factors by :param factors: the factors to get standard deviation for. If empty, the data for all factors is returned :param factors_by_name: whether to identify factors by their name or id. If true, the list of factors must \ be a list of factor names. Otherwise, it should be a list of factor ids :param format: which format to return the results in :return: factor cross-sectional standard deviation **Usage** Get the value of one cross-sectional standard deviation of the factors specified in `factors` **Examples** >>> from gs_quant.models.risk_model import MarqueeRiskModel >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = MarqueeRiskModel.get("MODEL_ID") >>> factor_cross_sectional_sd = model.get_factor_cross_sectional_standard_deviation(start_date, end_date, factors=['factor1', 'factor2']) **See also** :func:`get_factor_standard_deviation` :func:`get_factor_cross_sectional_mean` """ return self._get_factor_data_measure( Measure.Factor_Cross_Sectional_Standard_Deviation, start_date, end_date, assets, factors_by_name, factors, format, ) def get_data( self, measures: List[Measure], start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), factors: List[Union[str, Factor]] = None, limit_factors: bool = True, ) -> Dict: """Get data for multiple measures for existing risk model :param measures: list of measures for general risk model data request :param start_date: start date for data request :param end_date: end date for data request :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request :param factors: Only include data for these factors. If empty, all factors will be included. :param limit_factors: limit factors included in factorData and covariance matrix to only include factors which the input universe has non-zero exposure to :return: risk model data or MqRequestError if query is too large for the service """ if factors: factors = [f.name if isinstance(f, Factor) else f for f in factors] try: return GsFactorRiskModelApi.get_risk_model_data( model_id=self.id, start_date=start_date, end_date=end_date, assets=assets, factors=factors, measures=measures, limit_factors=limit_factors, ) except MqRequestError as e: if e.status > 499: logging.warning( f"Potential timeout in request for model {self.id}. Consider adding a retry or" f" batching request if error persists" ) raise MqRequestError( e.status, f"timeout while getting model data between {start_date} and {end_date} " f" for {self.id}, consider batching request", ) raise e def upload_data(self, data: Union[RiskModelData, Dict], max_asset_batch_size: int = 10000, aws_upload: bool = True): """Upload risk model data to existing risk model in Marquee :param data: complete or partial risk model data for uploading on given date includes: date, and one or more of: factorData, assetData, covarianceMatrix, issuerSpecificCovariance, factorPortfolios and currencyRatesData. Look at risk model upload documentation for further information on what data can be grouped together if asset data size is above the max asset batch size :param max_asset_batch_size: size of payload to batch with. Defaults to 20000 assets which works well for models that have factor ids ranging from 1- 3 characters in length. For models with longer factor ids, consider batching with a smaller max asset batch size If upload universe is over max_asset_batch_size, will batch data in chunks of max_asset_batch_size assets :param aws_upload: If true, uploads risk model data to AWS This function takes risk model data, and if partial requests are necessary, will upload data by 1. factor data (includes covariance matrix if factor model) 2. asset data in batches of max_asset_batch_size 3. issuer specific covariance data in batches of max_asset_batch_size / 2 due to the structure of this data 4. factor portfolio data in batches of max_asset_batch_size / 2 due to the structure of this data In the case of repeat identifiers on a given data, the repeated data will replace existing data """ data = data.as_dict() if type(data) is RiskModelData else data full_data_present = 'factorData' in data.keys() and 'assetData' in data.keys() only_factor_data_present = only_factor_data_is_present(self.type, data) target_universe_size = 0 if only_factor_data_present else get_universe_size(data) make_partial_request = target_universe_size > max_asset_batch_size or not full_data_present if target_universe_size: logging.info(f'Target universe size for upload: {target_universe_size}') if make_partial_request: batch_and_upload_partial_data(self.id, data, max_asset_batch_size, aws_upload=aws_upload) else: logging.info('Uploading model data in one request') upload_model_data(self.id, data, aws_upload=aws_upload) @deprecation.deprecated(deprecated_in="0.9.42", details="Please use upload_data instead") def upload_partial_data(self, data: Union[RiskModelData, dict], final_upload: bool = None): """Upload partial risk model data to existing risk model in Marquee :param data: partial risk model data for uploading on given date :param final_upload: if this is the last upload for the batched subset of data The models factorData and covarianceMatrix must be uploaded first on given date if repeats in partial upload, newer posted data will replace existing data on upload day """ upload_model_data(self.id, data, partial_upload=True, final_upload=final_upload) def upload_asset_coverage_data(self, date: dt.date = None, batch_size: int = 100): """Upload to the coverage dataset for given risk model and date :param date: Date to upload coverage data for, default date is last date from risk model calendar :param batch_size: Number of assets to upload in one request Posting to the coverage dataset within the last 5 days will enable the risk model to be seen in the Marquee UI dropdown for users with "execute" capabilities """ if not date: date = self.get_dates()[-1] gsid_list = self.get_asset_universe( date, assets=DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), format=ReturnFormat.JSON ).get(date) if not gsid_list: raise MqRequestError(404, f'No asset data found on {date}') batch_and_upload_coverage_data(date, gsid_list, self.id, batch_size) @classmethod def from_target(cls, model): uid = model.universe_identifier return MarqueeRiskModel( id_=model.id, name=model.name, coverage=model.coverage if isinstance(model.coverage, CoverageType) else CoverageType(model.coverage), term=model.term if isinstance(model.term, Term) else Term(model.term), universe_identifier=uid if isinstance(uid, UniverseIdentifier) else UniverseIdentifier(uid) if uid else None, vendor=model.vendor, version=model.version, type_=model.type_ if not model.type_ or isinstance(model.type_, RiskModelType) else RiskModelType(model.type_), entitlements=model.entitlements, description=model.description, expected_update_time=dt.datetime.strptime(model.expected_update_time, "%H:%M:%S").time() if model.expected_update_time else None, ) @classmethod def from_many_targets(cls, models: Tuple[RiskModelBuilder, ...]): return [cls.from_target(model) for model in models] def __str__(self): return self.id def __repr__(self): s = "{}('{}','{}','{}','{}','{}','{}','{}', '{}".format( self.__class__.__name__, self.id, self.name, self.coverage, self.term, self.universe_identifier, self.vendor, self.version, self.type, ) if self.universe_size: s += ", universe_size={}".format(self) if self.entitlements: s += ", entitlements={}".format(self) if self.description: s += ", description={}".format(self) if self.expected_update_time: s += ", expected_update_time={}".format(self) s += ")" return s class FactorRiskModel(MarqueeRiskModel): """Factor Risk Model used for calculating asset level factor risk""" def __init__( self, id_: str, name: str, coverage: CoverageType, term: Term, universe_identifier: UniverseIdentifier, vendor: str, version: float, universe_size: int = None, entitlements: Union[Dict, Entitlements] = None, description: str = None, expected_update_time: dt.time = None, ): """Create new factor risk model object :param id_: risk model id (cannot be changed) :param name: risk model name :param coverage: coverage of risk model asset universe :param term: horizon term :param universe_identifier: identifier used in asset universe upload (cannot be changed) :param vendor: risk model vendor :param version: version of model :param universe_size: total rough expected universe size (rounding up to nearest 1k) :param entitlements: entitlements associated with risk model :param description: risk model description :param expected_update_time: time when risk model daily data is expected to be uploaded :return: FactorRiskModel object """ super().__init__( id_, name, RiskModelType.Factor, vendor, version, coverage, universe_identifier, term, universe_size=universe_size, entitlements=entitlements, description=description, expected_update_time=expected_update_time, ) @classmethod def from_target(cls, model: RiskModelBuilder): uid = model.universe_identifier return FactorRiskModel( model.id, model.name, model.coverage if isinstance(model.coverage, CoverageType) else CoverageType(model.coverage), model.term if isinstance(model.term, Term) else Term(model.term), uid if isinstance(uid, UniverseIdentifier) else UniverseIdentifier(uid) if uid else None, model.vendor, model.version, universe_size=model.universe_size, entitlements=model.entitlements, description=model.description, expected_update_time=dt.datetime.strptime(model.expected_update_time, "%H:%M:%S").time() if model.expected_update_time else None, ) @classmethod def get_many( cls, ids: List[str] = None, terms: List[str] = None, vendors: List[str] = None, names: List[str] = None, coverages: List[str] = None, limit: int = None, ) -> list: """Get many factor risk models from Marquee :param ids: list of model identifiers in Marquee :param terms: list of model terms :param vendors: list of model vendors :param names: list of model names :param coverages: list of model coverages :param limit: limit of number of models in response :return: list of Factor Risk Model object """ models = GsRiskModelApi.get_risk_models( ids=ids, terms=terms, vendors=vendors, names=names, coverages=coverages, limit=limit, types=[RiskModelType.Factor.value], ) return cls.from_many_targets(models) def get_total_risk( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get total risk data for existing risk model :param start_date: start date for data request :param end_date: end date for data request :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request :param format: which format to return the results in :return: total risk for assets requested **Usage** Get total risk data for assets specified in `assets` between `start_date` and `end_date` **Examples** >>> from gs_quant.models.risk_model import FactorRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier, ReturnFormat >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> total_risk = model.get_total_risk(start_date, end_date, ... DataAssetsRequest(UniverseIdentifier.bbid, ['GS UN'])) **See also** :func:`get_specific_risk` :func:`get_specific_return` :func:`get_historical_beta` """ return self._get_asset_data_measure(Measure.Total_Risk, start_date, end_date, assets, format) def get_historical_beta( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get historical beta data for existing risk model :param start_date: start date for data request :param end_date: end date for data request :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request :param format: which format to return the results in :return: historical beta for assets requested **Usage** Get historical beta data for assets specified in `assets` between `start_date` and `end_date` **Examples** >>> from gs_quant.models.risk_model import MacroRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier, ReturnFormat >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> historical_beta = model.get_historical_beta(start_date, end_date, ... DataAssetsRequest(UniverseIdentifier.bbid, ['GS UN'])) **See also** :func:`get_predicted_beta` :func:`get_global_predicted_beta` """ return self._get_asset_data_measure(Measure.Historical_Beta, start_date, end_date, assets, format) def get_predicted_beta( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get predicted beta data for an existing risk model :param start_date: start date for data request :param end_date: end date for data request :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request :param format: which format to return the results in :return: predicted beta for assets requested **Usage** Get predicted beta data for assets specified in `assets` between `start_date` and `end_date` **Examples** >>> from gs_quant.models.risk_model import MacroRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = MacroRiskModel.get("MODEL_ID") >>> predicted_beta = model.get_predicted_beta(start_date, end_date, ... DataAssetsRequest(UniverseIdentifier.bbid, ['GS UN'])) **See also** :func:`get_historical_beta` :func:`get_global_predicted_beta` """ return self._get_asset_data_measure(Measure.Predicted_Beta, start_date, end_date, assets, format) def get_global_predicted_beta( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get global predicted beta data for an existing risk model :param start_date: start date for data request :param end_date: end date for data request :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request :param format: which format to return the results in :return: global predicted beta for assets requested **Usage** Get global predicted beta for assets specified in `assets` between `start_date` and `end_date` **Examples** >>> from gs_quant.models.risk_model import MacroRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> global_predicted_beta = model.get_global_predicted_beta(start_date, end_date, ... DataAssetsRequest(UniverseIdentifier.bbid, ['GS UN'])) **See also** :func:`get_predicted_beta` :func:`get_historical_beta` """ return self._get_asset_data_measure(Measure.Global_Predicted_Beta, start_date, end_date, assets, format) def get_daily_return( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get daily asset total return data :param start_date: Start date for data request :param end_date: End date for data request :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request :param format: Which format to return the results in :return: Daily asset total return data **Usage** Get daily return data for assets specified in `assets` between `start_date` and `end_date` **Example** >>> from gs_quant.models.risk_model import FactorRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier >>> import datetime as dt >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> daily_returns = model.get_daily_return(start_date, end_date, ... DataAssetsRequest(UniverseIdentifier.bbid, ['GS UN'])) **See also** :func:`get_specific_risk` :func:`get_universe_factor_exposure` :func: `get_specific_return` """ return self._get_asset_data_measure(Measure.Daily_Return, start_date, end_date, assets, format) def get_specific_return( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get specific return data for existing risk model :param start_date: Start date for data request :param end_date: End date for data request :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request :param format: Which format to return the results in :return: Specific return data **Usage** Get specific return data for assets specified in `assets` between `start_date` and `end_date` **Example** >>> from gs_quant.models.risk_model import FactorRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier >>> import datetime as dt >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> specific_returns = model.get_specific_return(start_date, end_date, ... DataAssetsRequest(UniverseIdentifier.bbid, ['GS UN'])) **See also** :func:`get_specific_risk` :func:`get_universe_factor_exposure` """ return self._get_asset_data_measure(Measure.Specific_Return, start_date, end_date, assets, format) def get_bid_ask_spread( self, start_date: dt.date, end_date: dt.date = None, days: int = 0, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get bid ask spread data for assets covered by the risk model :param start_date: Start date for data request :param end_date: End date for data request :param days: Get bid ask spread for a past average of specific days :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request :param format: Which format to return the results in :return: Bid Ask Spread Data **Usage** Get Bid Ask Spread data for assets specified in `assets` between `start_date` and `end_date` **Example** >>> from gs_quant.models.risk_model import FactorRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier >>> import datetime as dt >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> bid_ask_spread_30d = model.get_bid_ask_spread(start_date, end_date, 30, \ ... DataAssetsRequest(UniverseIdentifier.bbid, ['GS UN'])) **See also** :func:`get_composite_volume` :func:`get_trading_volume` """ try: requested_measure = Measure.Bid_Ask_Spread if not days else Measure[f'Bid_Ask_Spread_{days}d'] return super()._get_asset_data_measure(requested_measure, start_date, end_date, assets, format) except KeyError: raise ValueError(f'Bid Ask Spread data is not available for the requested days: {days}') def get_trading_volume( self, start_date: dt.date, end_date: dt.date = None, days: int = 0, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get trading volume data for assets covered by the risk model :param start_date: Start date for data request :param end_date: End date for data request :param days: Get trading volume for a past average of specific days :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request :param format: Which format to return the results in :return: Trading Volume data **Usage** Get trading volume data for assets specified in `assets` between `start_date` and `end_date` **Example** >>> from gs_quant.models.risk_model import FactorRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier >>> import datetime as dt >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> trading_volume_60d = model.get_trading_volume(start_date, end_date, 60, ... DataAssetsRequest(UniverseIdentifier.bbid, ['GS UN'])) **See also** :func:`get_composite_volume` :func:`get_traded_value` """ try: requested_measure = Measure.Trading_Volume if not days else Measure[f'Trading_Volume_{days}d'] return super()._get_asset_data_measure(requested_measure, start_date, end_date, assets, format) except KeyError: raise ValueError(f'Trading volume data is not available for the requested days: {days}') def get_traded_value( self, start_date: dt.date, end_date: dt.date = None, days: int = 30, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get traded value data for assets covered by the risk model :param start_date: Start date for data request :param end_date: End date for data request :param days: Get traded value for a past average of specific days :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request :param format: Which format to return the results in :return: Traded Value Data **Usage** Get traded value data for assets specified in `assets` between `start_date` and `end_date` **Example** >>> from gs_quant.models.risk_model import FactorRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier >>> import datetime as dt >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> traded_value_30d = model.get_traded_value(start_date, end_date, ... assets = DataAssetsRequest(UniverseIdentifier.bbid, ['GS UN'])) **See also** :func:`get_composite_value` :func:`get_trading_volume` """ try: requested_measure = Measure.Traded_Value_30d if not days else Measure[f'Traded_Value_{days}d'] return super()._get_asset_data_measure(requested_measure, start_date, end_date, assets, format) except KeyError: raise ValueError(f'Traded Value data is not available for the requested days: {days}') def get_composite_volume( self, start_date: dt.date, end_date: dt.date = None, days: int = 0, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get composite volume data for assets covered by the risk model :param start_date: Start date for data request :param end_date: End date for data request :param days: Get composite volume for a past average of specific days :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request :param format: Which format to return the results in :return: Composite volume data **Usage** Get composite volume data for assets specified in `assets` between `start_date` and `end_date` **Example** >>> from gs_quant.models.risk_model import FactorRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier >>> import datetime as dt >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> composite_volume_30d = model.get_composite_volume(start_date, end_date, 30, ... DataAssetsRequest(UniverseIdentifier.bbid, ['GS UN'])) **See also** :func:`get_composite_value` :func:`get_traded_value` """ try: requested_measure = Measure.Composite_Volume if not days else Measure[f'Composite_Volume_{days}d'] return super()._get_asset_data_measure(requested_measure, start_date, end_date, assets, format) except KeyError: raise ValueError(f'Composite Volume data is not available for the requested days: {days}') def get_composite_value( self, start_date: dt.date, end_date: dt.date = None, days: int = 30, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get composite value data for assets covered by the risk model :param start_date: Start date for data request :param end_date: End date for data request :param days: Get composite value for a past average of specific days :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request :param format: Which format to return the results in :return: Composite Value data **Usage** Get composite value data for assets specified in `assets` between `start_date` and `end_date` **Example** >>> from gs_quant.models.risk_model import FactorRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier >>> import datetime as dt >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> composite_value_30d = model.get_composite_value(start_date, end_date, ... assets=DataAssetsRequest(UniverseIdentifier.bbid, ['GS UN'])) **See also** :func:`get_traded_value` :func:`get_composite_volume` """ try: requested_measure = Measure.Composite_Value_30d if not days else Measure[f'Composite_Value_{days}d'] return super()._get_asset_data_measure(requested_measure, start_date, end_date, assets, format) except KeyError: raise ValueError(f'Composite Value data is not available for the requested days: {days}') def get_issuer_market_cap( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get issuer market capitalization for assets covered by the risk model :param start_date: Start date for data request :param end_date: End date for data request :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request :param format: Which format to return the results in :return: Issue Market cap data **Usage** Get issuer market capitalization for assets specified in `assets` between `start_date` and `end_date` **Example** >>> from gs_quant.models.risk_model import FactorRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier >>> import datetime as dt >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> issuer_market_cap = model.get_issuer_market_cap(start_date, end_date, ... DataAssetsRequest(UniverseIdentifier.bbid, ['GS UN'])) **See also** :func:`get_asset_price` :func:`get_asset_capitalization` :func:`get_currency` """ return super()._get_asset_data_measure(Measure.Issuer_Market_Cap, start_date, end_date, assets, format) def get_asset_price( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get asset price data for assets covered by the risk model :param start_date: Start date for data request :param end_date: End date for data request :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request :param format: Which format to return the results in :return: Asset price data **Usage** Get prices for assets specified in `assets` between `start_date` and `end_date` **Example** >>> from gs_quant.models.risk_model import FactorRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier >>> import datetime as dt >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> asset_price = model.get_asset_price(start_date, end_date, ... DataAssetsRequest(UniverseIdentifier.bbid, ['GS UN'])) **See also** :func:`get_asset_capitalization` :func:`get_currency` """ return super()._get_asset_data_measure(Measure.Price, start_date, end_date, assets, format) def get_asset_capitalization( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get asset capitalization data for assets covered by the risk model :param start_date: Start date for data request :param end_date: End date for data request :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request :param format: Which format to return the results in :return: Asset Capitalization data **Usage** Get asset capitalization data for assets specified in `assets` between `start_date` and `end_date` **Example** >>> from gs_quant.models.risk_model import FactorRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier >>> import datetime as dt >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> asset_capitalization = model.get_asset_capitalization(start_date, end_date, ... DataAssetsRequest(UniverseIdentifier.bbid, ['GS UN'])) **See also** :func:`get_issuer_market_cap` :func:`get_currency` """ return super()._get_asset_data_measure(Measure.Capitalization, start_date, end_date, assets, format) def get_currency( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get currency data for assets covered by the risk model :param start_date: Start date for data request :param end_date: End date for data request :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request :param format: Which format to return the results in :return: Currency data **Usage** Get currency data for assets specified in `assets` between `start_date` and `end_date` **Example** >>> from gs_quant.models.risk_model import FactorRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier >>> import datetime as dt >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> currency = model.get_currency(start_date, end_date, ... DataAssetsRequest(UniverseIdentifier.bbid, ['GS UN'])) **See also** :func:`get_asset_price` :func:`get_issuer_market_cap` """ return super()._get_asset_data_measure(Measure.Currency, start_date, end_date, assets, format) def get_unadjusted_specific_risk( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get asset unadjusted specific risk data for assets covered by the risk model :param start_date: Start date for data request :param end_date: End date for data request :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request :param format: Which format to return the results in :return: Unadjusted Specific Risk data **Usage** Get unadjusted specific risk data for assets specified in `assets` between `start_date` and `end_date` **Example** >>> from gs_quant.models.risk_model import FactorRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier >>> import datetime as dt >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> unadjusted_specific_risk = model.get_unadjusted_specific_risk(start_date, end_date, ... DataAssetsRequest(UniverseIdentifier.bbid, ['GS UN'])) **See also** :func:`get_specific_risk` :func:`get_total_risk` """ return super()._get_asset_data_measure(Measure.Unadjusted_Specific_Risk, start_date, end_date, assets, format) def get_dividend_yield( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get dividend yield data for assets covered by the risk model :param start_date: Start date for data request :param end_date: End date for data request :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request :param format: Which format to return the results in :return: Asset Divident Yield **Usage** Get dividend yield data for assets specified in `assets` between `start_date` and `end_date` **Example** >>> from gs_quant.models.risk_model import FactorRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier >>> import datetime as dt >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> dividend_yield = model.get_dividend_yield(start_date, end_date, ... DataAssetsRequest(UniverseIdentifier.bbid, ['GS UN'])) **See also** :func:`get_asset_capitalization` :func:`get_issuer_market_cap` """ return super()._get_asset_data_measure(Measure.Dividend_Yield, start_date, end_date, assets, format) def get_universe_factor_exposure( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), factors: List[Union[str, Factor]] = None, get_factors_by_name: bool = False, format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get universe factor exposure data for existing risk model :param start_date: start date for data request :param end_date: end date for data request :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request :param factors: list of factors to return exposure for. If left None, will return all applicable factors :param get_factors_by_name: return results keyed by factor name instead of ID :param format: which format to return the results in :return: factor exposure for assets requested **Usage** Given a list of assets, return their exposure to factors between start date and end date. **Examples** >>> from gs_quant.models.risk_model import FactorRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> universe = ['GS UN'] >>> model = FactorRiskModel.get("MODEL_ID") >>> universe_exposure = model.get_universe_factor_exposure(start_date, end_date, ... DataAssetsRequest(UniverseIdentifier.bbid, universe)) **See also** :func:`get_asset_universe` :func:`get_specific_risk` """ return super().get_universe_exposure( start_date, end_date, assets, factors=factors, get_factors_by_name=get_factors_by_name, format=format ) def _build_covariance_matrix_measure( self, covariance_matrix_type: Measure, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = None, format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[Dict, pd.DataFrame]: measure_to_field_name_map = { Measure.Covariance_Matrix: 'covarianceMatrix', Measure.Unadjusted_Covariance_Matrix: 'unadjustedCovarianceMatrix', Measure.Pre_VRA_Covariance_Matrix: 'preVRACovarianceMatrix', } limit_factors = True if assets else False measures = [covariance_matrix_type, Measure.Factor_Name, Measure.Factor_Id] if assets: measures += [Measure.Universe_Factor_Exposure, Measure.Asset_Universe] results = self.get_data( start_date=start_date, end_date=end_date, assets=assets, measures=measures, limit_factors=limit_factors ).get('results') covariance_data = ( results if format == ReturnFormat.JSON else get_covariance_matrix_dataframe( results, covariance_matrix_key=measure_to_field_name_map[covariance_matrix_type] ) ) return covariance_data def get_covariance_matrix( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = None, format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[Dict, pd.DataFrame]: """ Get covariance matrix data for existing risk model :param start_date: start date for data request :param end_date: end date for data request :param assets: DataAssetsRequest object with identifier and list of assets to limit the covariance matrix by :param format: which format to return the results in :return: covariance matrix of daily factor returns **Usage** Get the covariance matrix of daily factor returns **Examples** >>> from gs_quant.models.risk_model import MacroRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> covariance_matrix = model.get_covariance_matrix(start_date, end_date, ... DataAssetsRequest(UniverseIdentifier.bbid, ['GS UN'])) **See also** :func:`get_factor_returns_by_name` :func:`get_factor_returns_by_id` """ return self._build_covariance_matrix_measure(Measure.Covariance_Matrix, start_date, end_date, assets, format) def get_unadjusted_covariance_matrix( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = None, format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[Dict, pd.DataFrame]: """ Get covariance matrix data for existing risk model :param start_date: start date for data request :param end_date: end date for data request :param assets: DataAssetsRequest object with identifier and list of assets to limit the covariance matrix by :param format: which format to return the results in :return: covariance matrix of daily factor returns **Usage** Get the covariance matrix of daily factor returns **Examples** >>> from gs_quant.models.risk_model import MacroRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> covariance_matrix = model.get_unadjusted_covariance_matrix(start_date, end_date, ... DataAssetsRequest(UniverseIdentifier.bbid, ['GS UN'])) **See also** :func:`get_factor_returns_by_name` :func:`get_factor_returns_by_id` """ return self._build_covariance_matrix_measure( Measure.Unadjusted_Covariance_Matrix, start_date, end_date, assets, format ) def get_pre_vra_covariance_matrix( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = None, format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[Dict, pd.DataFrame]: """ Get covariance matrix data for existing risk model :param start_date: start date for data request :param end_date: end date for data request :param assets: DataAssetsRequest object with identifier and list of assets to limit the covariance matrix by :param format: which format to return the results in :return: covariance matrix of daily factor returns **Usage** Get the covariance matrix of daily factor returns **Examples** >>> from gs_quant.models.risk_model import MacroRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> covariance_matrix = model.get_covariance_matrix(start_date, end_date, ... DataAssetsRequest(UniverseIdentifier.bbid, ['GS UN'])) **See also** :func:`get_factor_returns_by_name` :func:`get_factor_returns_by_id` """ return self._build_covariance_matrix_measure( Measure.Pre_VRA_Covariance_Matrix, start_date, end_date, assets, format ) def _build_currency_rates_data( self, rows: List[Dict], currencies: List[Currency], rates_key: str, format: ReturnFormat ) -> Union[Dict, pd.DataFrame]: currency_rates_key = "currencyRatesData" currency_rates_df = get_optional_data_as_dataframe(rows, currency_rates_key) if currencies: currency_rates_df = currency_rates_df.loc[ currency_rates_df['currency'].isin([cur.value for cur in currencies]) ] if format == ReturnFormat.DATA_FRAME: return currency_rates_df return currency_rates_df.to_dict() def get_risk_free_rate( self, start_date: dt.date, end_date: dt.date = None, currencies: List[Currency] = [], format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[Dict, pd.DataFrame]: """Get risk-free rates for existing risk model :param start_date: start date for data request :param end_date: end date for data request :param currencies: return risk-free rates for these currencies. If empty returns data for all currencies :param format: which format to return the results in :return: risk-free rates data **Usage** Get risk-free rates data between `start_date` and `end_date` **Examples** >>> from gs_quant.models.risk_model import FactorRiskModel >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> risk_free_rates = model.get_risk_free_rate(start_date, end_date)) **See also** :func:`get_currency_exchange_rate` """ results = self.get_data( measures=[RiskModelDataMeasure.Risk_Free_Rate], start_date=start_date, end_date=end_date, limit_factors=False, ).get('results') return self._build_currency_rates_data(results, rates_key="riskFreeRate", currencies=currencies, format=format) def get_currency_exchange_rate( self, start_date: dt.date, end_date: dt.date = None, currencies: List[Currency] = [], format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[Dict, pd.DataFrame]: """Get currency exchange rates for existing risk model :param start_date: start date for data request :param end_date: end date for data request :param currencies: return currency exchange rates for these currencies. If empty returns data for all currencies :param format: which format to return the results in :return: currency exchange rates data **Usage** Get currency exchange rates data between `start_date` and `end_date` **Examples** >>> from gs_quant.models.risk_model import FactorRiskModel >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> currency_exchange_rates = model.get_currency_exchange_rate(start_date, end_date)) **See also** :func:`get_risk_free_rate` """ results = self.get_data( measures=[RiskModelDataMeasure.Currency_Exchange_Rate], start_date=start_date, end_date=end_date, limit_factors=False, ).get('results') return self._build_currency_rates_data(results, rates_key="exchangeRate", currencies=currencies, format=format) def get_factor_volatility( self, start_date: dt.date, end_date: dt.date = None, factors: List[str] = None, get_factors_by_name: bool = True, format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[Dict, pd.DataFrame]: """ Get factor volatility data for existing risk model :param start_date: start date for data request :param end_date: end date for data request :param factors: The factors to get factor return data for. If empty, the data for all factors is returned :param get_factors_by_name: get results keyed by factor name instead of ID :param format: which format to return the results in :return: factor volatility data **Usage** Get the factor volatility data for daily factor returns **Examples** >>> from gs_quant.models.risk_model import MacroRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> factor_volatility_by_name = model.get_factor_volatility(start_date, end_date, ... factors=["factor1", "factor2"]) >>> factor_volatility_by_id = model.get_factor_volatility(start_date=start_date, end_date=end_date, ... factors=["1", "2", "3"], get_factors_by_name=False) **See also** :func:`get_factor_returns_by_name` :func:`get_factor_returns_by_id` """ if factors is None: factors = [] measures = [Measure.Factor_Volatility, Measure.Factor_Name, Measure.Factor_Id] results = self.get_data(start_date=start_date, end_date=end_date, measures=measures, limit_factors=False).get( 'results' ) if format == ReturnFormat.JSON and not get_factors_by_name and not factors: return results else: factor_volatility_df = build_factor_volatility_dataframe(results, get_factors_by_name, factors) return factor_volatility_df if format == ReturnFormat.DATA_FRAME else factor_volatility_df.to_dict() def get_estimation_universe_weights( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[Dict, pd.DataFrame]: """ Get estimation universe data for existing risk model :param start_date: Start date for data request. Must be equal to end_date if universe array in DataAssetsRequest is empty. :param end_date: End date for data request. Must be equal to start_date if universe array in DataAssetsRequest is empty. :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request :param format: Which format to return the results in :return: Estimation Universe data in a pandas dataframe or dict **Usage** Get the assets that are in the estimation universe of the model and their weights. **Examples** >>> from gs_quant.models.risk_model import FactorRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> universe = ['GS UN'] >>> model = FactorRiskModel.get("MODEL_ID") >>> estimation_universe_weights = model.get_estimation_universe_weights(start_date, end_date, ... DataAssetsRequest(UniverseIdentifier.bbid, universe)) **See also** :func:`get_asset_universe` :func:`get_specific_risk` """ return self._get_asset_data_measure(Measure.Estimation_Universe_Weight, start_date, end_date, assets, format) def get_issuer_specific_covariance( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[Dict, pd.DataFrame]: """ Get issuer specific covariance data for existing risk model :param start_date: start date for data request :param end_date: end date for data request :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request :param format: which format to return the results in :return: issuer specific covariance matrix (covariance of assets with the same issuer) **Usage** Get the covariance of assets with the same issuer. **Examples** >>> from gs_quant.models.risk_model import MacroRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> issuer_specific_covariance = model.get_issuer_specific_covariance(start_date, end_date, ... DataAssetsRequest(UniverseIdentifier.bbid, ['GS UN'])) **See also** :func:`get_factor_portfolios` """ isc = self.get_data( start_date=start_date, end_date=end_date, assets=assets, measures=[Measure.Issuer_Specific_Covariance], limit_factors=False, ).get('results') isc_data = ( isc if format == ReturnFormat.JSON else get_optional_data_as_dataframe(isc, 'issuerSpecificCovariance') ) return isc_data def get_factor_portfolios( self, start_date: dt.date, end_date: dt.date = None, factors: List[str] = None, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), get_factors_by_name: bool = False, format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[Dict, pd.DataFrame]: """ Get factor portfolios data for existing risk model :param start_date: start date for data request :param end_date: end date for data request :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request: :param factors: The factors to get factor portfolio data for. If empty, the data for all factors is returned :param get_factors_by_name: whether to denote the results by factor name (True) or ID (False) :param format: which format to return the results in :return: factor portfolios data **Usage** Get the factor portfolios data between `start_date` and `end_date` **Examples** >>> from gs_quant.models.risk_model import MacroRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> factor_portfolios = model.get_factor_portfolios(start_date, end_date, ... DataAssetsRequest(UniverseIdentifier.bbid, ['GS UN'])) **See also** :func:`get_issuer_specific_covariance` """ results = self.get_data( start_date=start_date, end_date=end_date, assets=assets, factors=factors, measures=[Measure.Factor_Id, Measure.Factor_Name, Measure.Factor_Portfolios], limit_factors=False, ).get('results') pfp_data = build_pfp_data_dataframe( results, return_df=format is ReturnFormat.DATA_FRAME, get_factors_by_name=get_factors_by_name ) return pfp_data def get_asset_contribution_to_risk( self, asset_identifier: str, date: dt.date, asset_identifier_type=RiskModelUniverseIdentifierRequest.bbid, get_factors_by_name: bool = True, format: ReturnFormat = ReturnFormat.DATA_FRAME, ): """ Get factor proportion of risk and marginal contribution to risk for each factor in the model :param asset_identifier: asset identifier :param date: date :param asset_identifier_type: type corresponding to the identifier passed in :param get_factors_by_name: whether to denote the results by factor name (True) or ID (False) :param format: which format to return the results in :return: factor proportion of risk and marginal contribution to risk breakdown **Usage** Get the factor contribution breakdown for any asset covered by the risk model **Examples** >>> from gs_quant.models.risk_model import FactorRiskModel >>> import datetime as dt >>> >>> date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> mctr_breakdown = model.get_asset_contribution_to_risk('AAPL UW', date) """ # Get spot price for the asset as of date security = SecurityMaster.get_asset(asset_identifier, AssetIdentifier(asset_identifier_type.value.upper())) spot_data_coordinate = security.get_data_coordinate(DataMeasure.SPOT_PRICE) series = spot_data_coordinate.get_series(start=date, end=date) if len(series) == 0: raise MqValueError(f'{asset_identifier} has no end of day price available on {date.strftime("%Y-%m-%d")}') spot_price = series.iloc[0] # Get risk model data for the asset as of date data_measures = [ RiskModelDataMeasure.Asset_Universe, RiskModelDataMeasure.Total_Risk, RiskModelDataMeasure.Universe_Factor_Exposure, RiskModelDataMeasure.Covariance_Matrix, RiskModelDataMeasure.Factor_Name, RiskModelDataMeasure.Factor_Id, RiskModelDataMeasure.Factor_Category, ] risk_results = self.get_data( data_measures, date, date, RiskModelDataAssetsRequest(identifier=asset_identifier_type, universe=[asset_identifier]), limit_factors=True, ) if len(risk_results['results'][0]['assetData']['totalRisk']) == 0: raise MqValueError(f'{asset_identifier} is not covered by {self.id} on {date.strftime("%Y-%m-%d")}') total_risk = risk_results['results'][0]['assetData']['totalRisk'][0] var = total_risk / 100 * spot_price / math.sqrt(252) covariance_matrix = np.array(risk_results['results'][0]['covarianceMatrix']) factor_z_scores = risk_results['results'][0]['assetData']['factorExposure'][0] # Calculate results factors = risk_results['results'][0]['factorData'] factor_ids_in_order = [f['factorId'] for f in factors] exp = np.array([factor_z_scores[key] * spot_price for key in factor_ids_in_order]) cov_x_exp = np.dot(covariance_matrix, exp) mctr = np.divide(cov_x_exp, var) rmctr = np.multiply(exp, mctr) annualized_rmctr = np.multiply(rmctr, math.sqrt(252) / spot_price * 100) annualized_factor_rmctr_sum = np.sum(annualized_rmctr) final_results = [] for i in range(0, len(mctr)): final_results.append( { 'Factor': factors[i]['factorName'] if get_factors_by_name else factors[i]['factorId'], 'Factor Category': factors[i]['factorCategory'], 'Proportion of Risk (%)': annualized_rmctr[i] / total_risk * 100, 'MCTR (%)': annualized_rmctr[i], } ) final_results.append( { 'Factor': 'Specific' if get_factors_by_name else 'SPC', 'Factor Category': 'Specific', 'Proportion of Risk (%)': (total_risk - annualized_factor_rmctr_sum) / total_risk * 100, 'MCTR (%)': total_risk - annualized_factor_rmctr_sum, } ) return pd.DataFrame(final_results) if format == ReturnFormat.DATA_FRAME else final_results def get_asset_factor_attribution( self, asset_identifier: str, start_date: dt.date, end_date: dt.date, asset_identifier_type=RiskModelUniverseIdentifierRequest.bbid, get_factors_by_name: bool = True, format: ReturnFormat = ReturnFormat.DATA_FRAME, ): """ Get attribution by factor for an asset for a desired date range :param asset_identifier: asset identifier :param start_date: start date :param end_date: end date :param asset_identifier_type: type corresponding to the identifier passed in :param get_factors_by_name: whether to denote the results by factor name (True) or ID (False) :param format: which format to return the results in :return: factor attribution breakdown **Usage** Get the factor attribution (in percent) breakdown for any asset covered by the risk model **Examples** >>> from gs_quant.models.risk_model import FactorRiskModel >>> import datetime as dt >>> >>> start_date = dt.date(2022, 5, 2) >>> end_date = dt.date(2022, 5, 2) >>> model = FactorRiskModel.get("MODEL_ID") >>> attribution_breakdown = model.get_asset_factor_attribution('AAPL UW', start_date, end_date) """ # Get risk model data for the asset data_measures = [ RiskModelDataMeasure.Asset_Universe, RiskModelDataMeasure.Universe_Factor_Exposure, RiskModelDataMeasure.Factor_Volatility, RiskModelDataMeasure.Factor_Return, RiskModelDataMeasure.Factor_Name, RiskModelDataMeasure.Factor_Id, RiskModelDataMeasure.Factor_Category, ] risk_results = self.get_data( data_measures, start_date, end_date, RiskModelDataAssetsRequest(identifier=asset_identifier_type, universe=[asset_identifier]), limit_factors=True, )['results'] if len(risk_results) == 0: raise MqValueError(f'{asset_identifier} is not covered by {self.id}') if len(risk_results) < 2: raise MqValueError('Attribution cannot be calculated with only one day of risk data') factor_id_to_name_map = {} factor_attribution = [] for i in range(1, len(risk_results)): attribution_on_date = {'Date': risk_results[i]['date']} for factor_data in risk_results[i]['factorData']: factor = factor_data['factorName'] if get_factors_by_name else factor_data['factorId'] attribution_on_date[factor] = factor_data['factorReturn'] factor_id_to_name_map[factor_data['factorId']] = factor_data['factorName'] previous_factor_exposures = risk_results[i - 1]['assetData']['factorExposure'][0] for factor_id in previous_factor_exposures: factor = factor_id_to_name_map[factor_id] if get_factors_by_name else factor_id factor_exposure = previous_factor_exposures[factor_id] attribution_on_date[factor] *= factor_exposure factor_attribution.append(attribution_on_date) return pd.DataFrame(factor_attribution) if format == ReturnFormat.DATA_FRAME else factor_attribution def __repr__(self): s = "{}('{}','{}','{}','{}','{}','{}','{}'".format( self.__class__.__name__, self.id, self.name, self.coverage, self.term, self.universe_identifier, self.vendor, self.version, ) if self.universe_size: s += ", universe_size={}".format(self) if self.entitlements: s += ", entitlements={}".format(self) if self.description: s += ", description={}".format(self) if self.expected_update_time: s += ", expected_update_time={}".format(self) s += ")" return s class MacroRiskModel(MarqueeRiskModel): """Macro Risk Model used for sensitivity analysis""" def __init__( self, id_: str, name: str, coverage: CoverageType, term: Term, universe_identifier: UniverseIdentifier, vendor: str, version: float, universe_size: int = None, entitlements: Union[Dict, Entitlements] = None, description: str = None, expected_update_time: dt.time = None, ): """Create new Macro risk model object :param id_: risk model id (cannot be changed) :param name: risk model name :param coverage: coverage of risk model asset universe :param universe_identifier: identifier used in asset universe upload (cannot be changed) :param vendor: risk model vendor :param version: version of model :param universe_size: total rough expected universe size (rounding up to nearest 1k) :param entitlements: entitlements associated with risk model :param description: risk model description :param expected_update_time: time when risk model daily data is expected to be uploaded :return: MacroRiskModel object """ super().__init__( id_, name, RiskModelType.Macro, vendor, version, coverage, universe_identifier, term, universe_size=universe_size, entitlements=entitlements, description=description, expected_update_time=expected_update_time, ) @classmethod def from_target(cls, model: RiskModelBuilder): uid = model.universe_identifier return MacroRiskModel( model.id, model.name, model.coverage if isinstance(model.coverage, CoverageType) else CoverageType(model.coverage), model.term if isinstance(model.term, Term) else Term(model.term), uid if isinstance(uid, UniverseIdentifier) else UniverseIdentifier(uid) if uid else None, model.vendor, model.version, universe_size=model.universe_size, entitlements=model.entitlements, description=model.description, expected_update_time=dt.datetime.strptime(model.expected_update_time, "%H:%M:%S").time() if model.expected_update_time else None, ) @classmethod def get_many( cls, ids: List[str] = None, terms: List[str] = None, vendors: List[str] = None, names: List[str] = None, coverages: List[str] = None, limit: int = None, ): """Get many Macro risk models from Marquee :param ids: list of model identifiers in Marquee :param terms: list of model terms :param vendors: list of model vendors :param names: list of model names :param coverages: list of model coverages :param limit: limit of number of models in response :return: list of Macro Risk Model object """ models = GsRiskModelApi.get_risk_models( ids=ids, terms=terms, vendors=vendors, names=names, coverages=coverages, types=[RiskModelType.Macro.value], limit=limit, ) return cls.from_many_targets(models) def get_universe_sensitivity( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), factor_type: FactorType = FactorType.Factor, get_factors_by_name: bool = False, format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get universe factor or factor category sensitivity data for existing macro risk model :param start_date: start date for data request :param end_date: end date for data request :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request :param factor_type: If factor type is factor, return factor sensitivity. Otherwise, return factor category sensitivity. :param get_factors_by_name: return results keyed by factor name instead of ID :param format: which format to return the results in :return: Factor or Factor Category sensitivity for assets requested **Usage** Given a list of assets, return their sensitivity to macro factors (when the factor_type is `Factor`) or macro factor categories (when the factor_type is Category). **Examples** >>> from gs_quant.models.risk_model import MacroRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = MacroRiskModel.get("MODEL_ID") >>> sensitivity = model.get_universe_sensitivity(start_date, end_date, ... DataAssetsRequest(UniverseIdentifier.bbid, ['GS UN'])) **See also** :func:`get_r_squared` :func:`factor_standard_deviation` :func:`factor_z_score` """ sensitivity_df = ( super().get_universe_exposure( start_date, end_date, assets, get_factors_by_name=get_factors_by_name, format=format ) if factor_type == FactorType.Factor else super().get_universe_exposure(start_date, end_date, assets, get_factors_by_name=get_factors_by_name) ) if factor_type == FactorType.Factor or sensitivity_df.empty: return sensitivity_df factor_data = self.get_factor_data(start_date, end_date) factor_data = factor_data.set_index("name") if get_factors_by_name else factor_data.set_index("identifier") columns = ( [(factor_data.loc[f, "factorCategory"], f) for f in sensitivity_df.columns.values] if get_factors_by_name else [(factor_data.loc[f, "factorCategoryId"], f) for f in sensitivity_df.columns.values] ) sensitivity_df = sensitivity_df.set_axis(pd.MultiIndex.from_tuples(columns), axis=1) factor_categories = list(set(sensitivity_df.columns.get_level_values(0).values)) factor_category_sens_df = pd.concat( [ sensitivity_df[factor_category].agg(np.sum, axis=1).to_frame().rename(columns={0: factor_category}) for factor_category in factor_categories ], axis=1, ) if format == ReturnFormat.JSON: factor_category_sens_df = factor_category_sens_df.to_dict(orient='index') factor_category_sens_dict = {} for key, value in factor_category_sens_df.items(): temp_dict = factor_category_sens_dict.get(key[0], {}) temp_dict[key[1]] = value factor_category_sens_dict[key[0]] = temp_dict return factor_category_sens_dict return factor_category_sens_df def get_r_squared( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """Get R Squared data for existing macro risk model :param start_date: start date for data request :param end_date: end date for data request :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request :param format: The format to return the results in (Dataframe or Dict) :return: R Squared for assets requested **Usage** Given a list of assets, return the r squared for each asset between `start_date` and `end_date`. **Examples** >>> start = dt.date(2022, 1, 1) >>> end = dt.date(2022, 5, 2) >>> model = MacroRiskModel.get("MODEL_ID") >>> r_squared_df = model.get_r_squared(start, end, DataAssetsRequest(UniverseIdentifier.bbid, ['GS UN'])) To get the data in dict format, specify the return format to JSON >>> r_squared_dict = model.get_r_squared(start, end, ... DataAssetsRequest(RiskModelUniverseIdentifierRequest.bbid, ['GS UN'], ... ReturnFormat.JSON) **See also** :func:`get_fair_value_gap` :func:`factor_standard_deviation` :func:`factor_z_score` """ return self._get_asset_data_measure(Measure.R_Squared, start_date, end_date, assets, format) def get_fair_value_gap( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), fair_value_gap_unit: Unit = Unit.STANDARD_DEVIATION, format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get fair value gap data for a list of assets between start date and end date :param start_date: The start date for data request. :param end_date: The end date for data request. :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request. :param fair_value_gap_unit: Return the fair value gap value expressed in this unit. The default is standard deviation. :param format: The format to return the data in. The default is pandas Dataframe. :return: Fair Value Gap for the assets requested **Usage** Given a list of assets, return the fair value gap for each asset between `start_date` and `end_date`. The data can be returned as a pandas DataFrame or as a dict. **Examples** Get the fair value gap data in a dataframe format between start and end. >>> from gs_quant.models.risk_model import MacroRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier, ReturnFormat >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = MacroRiskModel.get("MODEL_ID") >>> fvg_df = model.get_fair_value_gap(start_date, end_date, ... DataAssetsRequest(UniverseIdentifier.bbid, ['GS UN'])) To get the data in percentage, specify the fair_value_gap_unit to PERCENT. >>> fvg_df = model.get_fair_value_gap(start_date, end_date, ... DataAssetsRequest(UniverseIdentifier.bbid, ['GS UN']), Unit.PERCENT) **See also** :func:`get_r_squared` :func:`factor_standard_deviation` :func:`factor_z_score` """ return self._get_asset_data_measure( Measure.Fair_Value_Gap_Standard_Deviation if fair_value_gap_unit == Unit.STANDARD_DEVIATION else Measure.Fair_Value_Gap_Percent, start_date, end_date, assets, format, ) def get_factor_z_score( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = None, factors: List[str] = [], factors_by_name: bool = True, format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get factor z score data for existing risk model keyed by name or id :param start_date: start date for data request :param end_date: end date for data request :param assets: DataAssetsRequest object with identifier and list of assets to limit the factors by :param factors: the factors to get z score data for. If empty, the data for all factors is returned :param factors_by_name: whether to identify factors by their name or id. If true, the list of factors must \ be a list of factor names. Otherwise, it should be a list of factor ids :param format: which format to return the results in :return: factor z score **Usage** Get the z score of the factors specified in `factors` **Examples** >>> from gs_quant.models.risk_model import MacroRiskModel >>> import datetime as dt >>> >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = MacroRiskModel.get("MODEL_ID") >>> factor_z_score = model.get_factor_z_score(start_date, end_date, factors=['factor1', 'factor2']) **See also** :func:`get_r_squared` :func:`factor_standard_deviation` :func:`factor_z_score` """ return self._get_factor_data_measure( Measure.Factor_Z_Score, start_date, end_date, assets, factors_by_name, factors, format ) def get_model_price( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """ Get model price data for existing risk model :param start_date: Start date for data request :param end_date: End date for data request :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request :param format: Which format to return the results in :return: Model price data **Usage** Get model price data for assets specified in `assets` between `start_date` and `end_date` **Example** >>> from gs_quant.models.risk_model import FactorRiskModel, DataAssetsRequest, \ ... RiskModelUniverseIdentifierRequest as UniverseIdentifier >>> import datetime as dt >>> start_date = dt.date(2022, 1, 1) >>> end_date = dt.date(2022, 5, 2) >>> model = MacroRiskModel.get("MODEL_ID") >>> model_price = model.get_model_price(start_date, end_date, ... DataAssetsRequest(UniverseIdentifier.bbid, ['GS UN'])) **See also** :func:`get_r_squared` :func:`get_fair_value_gap` """ return super()._get_asset_data_measure(Measure.Model_Price, start_date, end_date, assets, format) def __repr__(self): s = "{}('{}','{}','{}','{}','{}','{}'".format( self.__class__.__name__, self.id, self.name, self.coverage, self.universe_identifier, self.vendor, self.version, ) if self.universe_size: s += ", universe_size={}".format(self) if self.entitlements: s += ", entitlements={}".format(self) if self.description: s += ", description={}".format(self) if self.expected_update_time: s += ", expected_update_time={}".format(self) s += ")" return s class ThematicRiskModel(MarqueeRiskModel): """Thematic Risk Model used for calculating exposure to thematic flagship baskets""" def __init__( self, id_: str, name: str, coverage: CoverageType, term: Term, universe_identifier: UniverseIdentifier, vendor: str, version: float, universe_size: int = None, entitlements: Union[Dict, Entitlements] = None, description: str = None, expected_update_time: dt.time = None, ): """Create new Thematic risk model object :param id_: risk model id (cannot be changed) :param name: risk model name :param coverage: coverage of risk model asset universe :param term: horizon term :param universe_identifier: identifier used in asset universe upload (cannot be changed) :param vendor: risk model vendor :param version: version of model :param universe_size: total rough expected universe size (rounding up to nearest 1k) :param entitlements: entitlements associated with risk model :param description: risk model description :param expected_update_time: time when risk model daily data is expected to be uploaded :return: Thematic Risk Model object """ super().__init__( id_, name, RiskModelType.Thematic, vendor, version, coverage, universe_identifier, term, universe_size=universe_size, entitlements=entitlements, description=description, expected_update_time=expected_update_time, ) @classmethod def from_target(cls, model: RiskModelBuilder): uid = model.universe_identifier return ThematicRiskModel( model.id, model.name, model.coverage if isinstance(model.coverage, CoverageType) else CoverageType(model.coverage), model.term if isinstance(model.term, Term) else Term(model.term), uid if isinstance(uid, UniverseIdentifier) else UniverseIdentifier(uid) if uid else None, model.vendor, model.version, universe_size=model.universe_size, entitlements=model.entitlements, description=model.description, expected_update_time=dt.datetime.strptime(model.expected_update_time, "%H:%M:%S").time() if model.expected_update_time else None, ) @classmethod def get_many( cls, ids: List[str] = None, terms: List[str] = None, vendors: List[str] = None, names: List[str] = None, coverages: List[str] = None, limit: int = None, ): """Get a Thematic risk model from Marquee :param ids: list of model identifiers in Marquee :param terms: list of model terms :param vendors: list of model vendors :param names: list of model names :param coverages: list of model coverages :param limit: limit of number of models in response :return: Macro Risk Model object """ models = GsRiskModelApi.get_risk_models( ids=ids, terms=terms, vendors=vendors, names=names, coverages=coverages, types=[RiskModelType.Thematic.value], limit=limit, ) return cls.from_many_targets(models) def get_universe_sensitivity( self, start_date: dt.date, end_date: dt.date = None, assets: DataAssetsRequest = DataAssetsRequest(RiskModelUniverseIdentifierRequest.gsid, []), get_factors_by_name: bool = False, format: ReturnFormat = ReturnFormat.DATA_FRAME, ) -> Union[List[Dict], pd.DataFrame]: """Get universe sensitivity data for existing thematic risk model :param start_date: start date for data request :param end_date: end date for data request :param assets: DataAssetsRequest object with identifier and list of assets to retrieve for request :param get_factors_by_name: return results keyed by factor name instead of ID :param format: which format to return the results in :return: basket sensitivity for assets requested """ return super().get_universe_exposure( start_date, end_date, assets, get_factors_by_name=get_factors_by_name, format=format ) def __repr__(self): s = "{}('{}','{}','{}','{}','{}','{}'".format( self.__class__.__name__, self.id, self.name, self.coverage, self.universe_identifier, self.vendor, self.version, ) if self.universe_size: s += ", universe_size={}".format(self) if self.entitlements: s += ", entitlements={}".format(self) if self.description: s += ", description={}".format(self) if self.expected_update_time: s += ", expected_update_time={}".format(self) s += ")" return s ================================================ FILE: gs_quant/models/risk_model_utils.py ================================================ """ Copyright 2021 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import logging import math import pydash import random from time import sleep from typing import List, Union import pandas as pd from gs_quant.api.gs.data import GsDataApi from gs_quant.api.gs.risk_models import GsFactorRiskModelApi from gs_quant.errors import MqRequestError from gs_quant.target.risk_models import RiskModelData, RiskModelType as Type from gs_quant.target.risk_models import RiskModelDataMeasure as Measure def _map_measure_to_field_name(measure: Measure): measure_to_field = { Measure.Specific_Risk: 'specificRisk', Measure.Total_Risk: 'totalRisk', Measure.Historical_Beta: 'historicalBeta', Measure.Predicted_Beta: 'predictedBeta', Measure.Global_Predicted_Beta: 'globalPredictedBeta', Measure.Daily_Return: 'dailyReturn', Measure.Specific_Return: 'specificReturn', Measure.Estimation_Universe_Weight: 'estimationUniverseWeight', Measure.R_Squared: 'rSquared', Measure.Fair_Value_Gap_Standard_Deviation: 'fairValueGapStandardDeviation', Measure.Fair_Value_Gap_Percent: 'fairValueGapPercent', Measure.Universe_Factor_Exposure: 'factorExposure', Measure.Factor_Return: 'factorReturn', Measure.Factor_Standard_Deviation: 'factorStandardDeviation', Measure.Factor_Z_Score: 'factorZScore', Measure.Bid_Ask_Spread: 'bidAskSpread', Measure.Bid_Ask_Spread_30d: 'bidAskSpread30d', Measure.Bid_Ask_Spread_60d: 'bidAskSpread60d', Measure.Bid_Ask_Spread_90d: 'bidAskSpread90d', Measure.Trading_Volume: 'tradingVolume', Measure.Trading_Volume_30d: 'tradingVolume30d', Measure.Trading_Volume_60d: 'tradingVolume60d', Measure.Trading_Volume_90d: 'tradingVolume90d', Measure.Traded_Value_30d: 'tradedValue30d', Measure.Composite_Volume: 'compositeVolume', Measure.Composite_Volume_30d: 'compositeVolume30d', Measure.Composite_Volume_60d: 'compositeVolume60d', Measure.Composite_Volume_90d: 'compositeVolume90d', Measure.Composite_Value_30d: 'compositeValue30d', Measure.Issuer_Market_Cap: 'issuerMarketCap', Measure.Capitalization: 'capitalization', Measure.Currency: 'currency', Measure.Dividend_Yield: 'dividendYield', Measure.Price: 'price', Measure.Unadjusted_Specific_Risk: 'unadjustedSpecificRisk', Measure.Model_Price: 'modelPrice', Measure.Factor_Mean: 'factorMean', Measure.Factor_Cross_Sectional_Mean: 'factorCrossSectionalMean', Measure.Factor_Cross_Sectional_Standard_Deviation: 'factorCrossSectionalStandardDeviation', } return measure_to_field.get(measure, '') def build_factor_id_to_name_map(results: List) -> dict: risk_model_factor_data = {} for row in results: for factor in row.get('factorData', []): factor_id = factor['factorId'] if not risk_model_factor_data.get(factor_id): risk_model_factor_data[factor_id] = factor['factorName'] return risk_model_factor_data def build_asset_data_map( results: List, requested_universe: tuple, requested_measure: Measure, factor_map: dict ) -> dict: if not results: return {} data_field = _map_measure_to_field_name(requested_measure) # if full universe is requested then pull the universe from the results. universe = pydash.get(results, '0.assetData.universe', []) if not requested_universe else list(requested_universe) data_map = {} for asset in universe: date_list = {} for row in results: if asset in row.get('assetData').get('universe'): i = row.get('assetData').get('universe').index(asset) if data_field == 'factorExposure': exposures = row.get('assetData').get(data_field)[i] date_list[row.get('date')] = {factor_map.get(f, f): v for f, v in exposures.items()} else: date_list[row.get('date')] = row.get('assetData').get(data_field)[i] data_map[asset] = date_list return data_map def build_factor_data_map( results: List, identifier: str, risk_model_id: str, requested_measure: Measure, factors: List[str] = [] ) -> Union[dict, pd.DataFrame]: field_name = _map_measure_to_field_name(requested_measure) if not field_name: raise NotImplementedError(f"{requested_measure.value} is currently not yet supported") data_list = [] for row in results: date = row.get('date') factor_data = row.get('factorData') for factor_map in factor_data: data_list.append( {"date": date, identifier: factor_map.get(identifier), field_name: factor_map.get(field_name)} ) factor_data_df = pd.DataFrame(data_list) factor_data_df = factor_data_df.pivot(index="date", columns=identifier, values=field_name) # if factors, only return data for those factors if factors: missing_factors = set(factors) - set(factor_data_df.columns.tolist()) if missing_factors: raise ValueError( f'Factors(s) with {identifier}(s) {", ".join(missing_factors)} do not exist in ' f'risk model {risk_model_id}. Make sure the factor {identifier}(s) are correct' ) factor_data_df = factor_data_df[factors] return factor_data_df def build_pfp_data_dataframe( results: List, return_df: bool = True, get_factors_by_name: bool = True ) -> Union[pd.DataFrame, list]: factor_data_df = pd.DataFrame(results)[["date", "factorData"]] factor_data_df = factor_data_df.explode('factorData') factor_data_df['factorId'] = factor_data_df['factorData'].apply(lambda x: x.get('factorId')) factor_data_df['factorName'] = factor_data_df['factorData'].apply(lambda x: x.get('factorName')) factor_data_df = factor_data_df.drop(columns='factorData').set_index("date") pfp_list = [] identifier_col_name = "assetId" if not get_factors_by_name else 'identifier' for row in results: factor_map_on_date = factor_data_df.loc[row.get('date'), ['factorId', 'factorName']] if isinstance(factor_map_on_date, pd.Series): factor_map_on_date = factor_map_on_date.to_frame().T factor_map_on_date = factor_map_on_date.to_dict(orient='list') factor_id_to_name_map = dict(zip(factor_map_on_date.get('factorId'), factor_map_on_date.get('factorName'))) pfp_map = dict() pfp_map[identifier_col_name] = row.get('factorPortfolios').get('universe') for factor in row.get('factorPortfolios').get('portfolio'): factor_id = factor.get('factorId') if get_factors_by_name: pfp_map[factor_id_to_name_map.get(factor_id)] = factor.get('weights') else: pfp_map[f'factorId: {factor_id}'] = factor.get('weights') pfp_map['date'] = row.get('date') if not return_df: pfp_list.append(pfp_map) else: weights_df = pd.DataFrame(pfp_map) pfp_list.append(weights_df) if not return_df: return pfp_list results = pd.concat(pfp_list) if pfp_list else pd.DataFrame({}) return results.set_index("date") def get_optional_data_as_dataframe(results: List, optional_data_key: str) -> pd.DataFrame: cov_list = [] date_list = [] for row in results: matrix_df = pd.DataFrame(row.get(optional_data_key)) cov_list.append(matrix_df) date_list.append(row.get('date')) results = pd.concat(cov_list, keys=date_list) if cov_list else pd.DataFrame({}) return results def get_covariance_matrix_dataframe( results: List[dict], covariance_matrix_key: str = 'covarianceMatrix' ) -> pd.DataFrame: cov_list = [] date_list = [] for row in results: matrix_df = pd.DataFrame(row.get(covariance_matrix_key)) factor_names = [data.get('factorName') for data in row.get('factorData')] matrix_df.columns = factor_names matrix_df.index = factor_names cov_list.append(matrix_df) date_list.append(row.get('date')) results = pd.concat(cov_list, keys=date_list) if cov_list else pd.DataFrame({}) return results def build_factor_volatility_dataframe(results: List, group_by_name: bool, factors: List[str]) -> pd.DataFrame: data = [] dates = [] for row in results: dates.append(row.get('date')) data.append(row.get('factorVolatility')) df = pd.DataFrame(data, index=dates) if group_by_name: factor_id_to_name_map = build_factor_id_to_name_map(results) df = df.rename(columns=factor_id_to_name_map) if factors: missing_factors = set(factors) - set(df.columns.tolist()) if missing_factors: raise ValueError( f'Factors(s): {", ".join(missing_factors)} do not exist in the risk model. ' f'Make sure the factors are correct.' ) else: return df[factors] return df def get_closest_date_index(date: dt.date, dates: List[str], direction: str) -> int: for i in range(50): for index in range(len(dates)): if direction == 'before': next_date = (date - dt.timedelta(days=i)).strftime('%Y-%m-%d') else: next_date = (date + dt.timedelta(days=i)).strftime('%Y-%m-%d') if next_date == dates[index]: return index return -1 def divide_request(data, n): for i in range(0, len(data), n): yield data[i : i + n] def batch_and_upload_partial_data_use_target_universe_size(model_id: str, data: dict, max_asset_size: int): """Takes in total risk model data for one day and batches requests according to asset data size, returns a list of messages from resulting post calls""" date = data.get('date') _upload_factor_data_if_present(model_id, data, date) sleep(random.uniform(3, 7)) _batch_data_if_present(model_id, data, max_asset_size, date) def _upload_factor_data_if_present(model_id: str, data: dict, date: str, **kwargs): aws_upload = kwargs.get('aws_upload', None) if data.get('factorData'): factor_data = {'date': date, 'factorData': data.get('factorData')} if data.get('covarianceMatrix'): factor_data['covarianceMatrix'] = data.get('covarianceMatrix') if data.get('unadjustedCovarianceMatrix') and aws_upload: factor_data['unadjustedCovarianceMatrix'] = data.get('unadjustedCovarianceMatrix') if data.get('preVRACovarianceMatrix') and aws_upload: factor_data['preVRACovarianceMatrix'] = data.get('preVRACovarianceMatrix') logging.info('Uploading factor data') _repeat_try_catch_request( GsFactorRiskModelApi.upload_risk_model_data, model_id=model_id, model_data=factor_data, partial_upload=True, **kwargs, ) def _batch_data_if_present(model_id: str, data, max_asset_size, date): if data.get('assetData'): asset_data_list, target_size = _batch_input_data({'assetData': data.get('assetData')}, max_asset_size) for asset_data in asset_data_list: _repeat_try_catch_request( GsFactorRiskModelApi.upload_risk_model_data, model_id=model_id, model_data={'assetData': asset_data, 'date': date}, partial_upload=True, target_universe_size=target_size, ) sleep(random.uniform(3, 7)) if 'issuerSpecificCovariance' in data.keys() or 'factorPortfolios' in data.keys(): for optional_key in ['issuerSpecificCovariance', 'factorPortfolios']: if data.get(optional_key): optional_data = data.get(optional_key) optional_data_list, target_size = _batch_input_data({optional_key: optional_data}, max_asset_size // 2) logging.info(f'{optional_key} being uploaded for {date}...') for optional_data in optional_data_list: _repeat_try_catch_request( GsFactorRiskModelApi.upload_risk_model_data, model_id=model_id, model_data={optional_key: optional_data, 'date': date}, partial_upload=True, target_universe_size=target_size, ) sleep(random.uniform(3, 7)) def only_factor_data_is_present(model_type: Type, data: dict) -> bool: if model_type == Type.Macro or model_type == Type.Thematic: if len(data.keys()) == 2 and 'factorData' in data.keys(): return True else: if len(data.keys()) == 3 and 'factorData' in data.keys() and 'covarianceMatrix' in data.keys(): return True return False def batch_and_upload_partial_data(model_id: str, data: dict, max_asset_size: int, **kwargs): """Takes in total risk model data for one day and batches requests according to asset data size, returns a list of messages from resulting post calls""" date = data.get('date') _upload_factor_data_if_present(model_id, data, date, **kwargs) sleep(random.uniform(3, 7)) if data.get('currencyRatesData'): _repeat_try_catch_request( GsFactorRiskModelApi.upload_risk_model_data, model_id=model_id, model_data={"currencyRatesData": data.get('currencyRatesData'), 'date': date}, partial_upload=True, final_upload=True, **kwargs, ) sleep(random.uniform(3, 7)) for risk_model_data_type in ["assetData", "issuerSpecificCovariance", "factorPortfolios"]: _repeat_try_catch_request( _batch_data_v2, model_id=model_id, data=data.get(risk_model_data_type), data_type=risk_model_data_type, max_asset_size=max_asset_size, date=date, **kwargs, ) sleep(random.uniform(3, 7)) def _batch_data_v2(model_id: str, data: dict, data_type: str, max_asset_size: int, date: Union[str, dt.date], **kwargs): if data: if data_type in ["issuerSpecificCovariance", "factorPortfolios"]: max_asset_size //= 2 data_list, _ = _batch_input_data({data_type: data}, max_asset_size) for i, data_chunk in enumerate(data_list): final_upload = i == len(data_list) - 1 res = GsFactorRiskModelApi.upload_risk_model_data( model_id=model_id, model_data={data_type: data_chunk, 'date': date}, partial_upload=True, final_upload=final_upload, **kwargs, ) logging.info(res) def batch_and_upload_coverage_data(date: dt.date, gsid_list: list, model_id: str, batch_size: int): update_time = dt.datetime.today().strftime("%Y-%m-%dT%H:%M:%SZ") request_array = [ {'date': date.strftime('%Y-%m-%d'), 'gsid': gsid, 'riskModel': model_id, 'updateTime': update_time} for gsid in set(gsid_list) ] logging.info(f"Uploading {len(request_array)} gsids to asset coverage dataset") list_of_requests = list(divide_request(request_array, batch_size)) logging.info(f"Uploading in {len(list_of_requests)} batches of {batch_size} gsids") [ _repeat_try_catch_request(GsDataApi.upload_data, data=data, dataset_id="RISK_MODEL_ASSET_COVERAGE") for data in list_of_requests ] def upload_model_data(model_id: str, data: dict, **kwargs): _repeat_try_catch_request(GsFactorRiskModelApi.upload_risk_model_data, model_id=model_id, model_data=data, **kwargs) def risk_model_data_to_json(risk_model_data: RiskModelData) -> dict: risk_model_data = risk_model_data.to_json() risk_model_data['assetData'] = risk_model_data.get('assetData').to_json() if risk_model_data.get('factorPortfolios'): risk_model_data['factorPortfolios'] = risk_model_data.get('factorPortfolios').to_json() risk_model_data['factorPortfolios']['portfolio'] = [ portfolio.to_json() for portfolio in risk_model_data.get('factorPortfolios').get('portfolio') ] if risk_model_data.get('issuerSpecificCovariance'): risk_model_data['issuerSpecificCovariance'] = risk_model_data.get('issuerSpecificCovariance').to_json() return risk_model_data def get_universe_size(data_to_split: dict) -> int: # takes any chunk of risk model data and returns the universe size if 'assetData' in data_to_split.keys(): return len(data_to_split.get('assetData').get('universe')) data_splitted = list(data_to_split.values()) for data in data_splitted: if isinstance(data, str): continue if 'universe' in data.keys(): return len(data.get('universe')) if 'universeId1' in data.keys(): return len(data.get('universeId1')) raise ValueError(f'No universe found for data {data_to_split}') def _batch_input_data(input_data: dict, max_asset_size: int): data_key = list(input_data.keys())[0] target_universe_size = get_universe_size(input_data) split_num = ( math.ceil(target_universe_size / max_asset_size) if math.ceil(target_universe_size / max_asset_size) else 1 ) split_idx = math.ceil(target_universe_size / split_num) batched_data_list = [] for i in range(split_num): if data_key == 'assetData': data_batched = _batch_asset_input( input_data.get('assetData'), i, split_idx, split_num, target_universe_size ) elif data_key == 'factorPortfolios': data_batched = _batch_pfp_input( input_data.get('factorPortfolios'), i, split_idx, split_num, target_universe_size ) else: data_batched = _batch_isc_input( input_data.get('issuerSpecificCovariance'), i, split_idx, split_num, target_universe_size ) batched_data_list.append(data_batched) return batched_data_list, target_universe_size def _batch_asset_input(input_data: dict, i: int, split_idx: int, split_num: int, target_universe_size: int) -> dict: end_idx = (i + 1) * split_idx if split_num != i + 1 else target_universe_size + 1 asset_data_subset = { 'universe': input_data.get('universe')[i * split_idx : end_idx], 'specificRisk': input_data.get('specificRisk')[i * split_idx : end_idx], 'factorExposure': input_data.get('factorExposure')[i * split_idx : end_idx], } optional_fields = list(input_data.keys()) [optional_fields.remove(required_field) for required_field in ["universe", "specificRisk", "factorExposure"]] for optional_input in optional_fields: if input_data.get(optional_input): asset_data_subset[optional_input] = input_data.get(optional_input)[i * split_idx : end_idx] return asset_data_subset def _batch_pfp_input(input_data: dict, i: int, split_idx: int, split_num: int, target_universe_size: int) -> dict: end_idx = (i + 1) * split_idx if split_num != i + 1 else target_universe_size + 1 pfp_data_subset = dict() universe_slice = input_data.get('universe')[i * split_idx : end_idx] pfp_data_subset['universe'] = universe_slice portfolio_slice = list() for portfolio in input_data.get('portfolio'): factor_id = portfolio.get('factorId') weights_slice = portfolio.get('weights')[i * split_idx : end_idx] portfolio_slice.append({"factorId": factor_id, "weights": weights_slice}) pfp_data_subset['portfolio'] = portfolio_slice return pfp_data_subset def _batch_isc_input(input_data: dict, i: int, split_idx: int, split_num: int, target_universe_size: int) -> dict: end_idx = (i + 1) * split_idx if split_num != i + 1 else target_universe_size + 1 return { 'universeId1': input_data.get('universeId1')[i * split_idx : end_idx], 'universeId2': input_data.get('universeId2')[i * split_idx : end_idx], 'covariance': input_data.get('covariance')[i * split_idx : end_idx], } def _repeat_try_catch_request( input_function, number_retries: int = 5, return_result: bool = False, verbose: bool = True, **kwargs ): t = 3.0 errors = [] for i in range(number_retries): try: result = input_function(**kwargs) if result: if return_result: return result logging.info(result) errors.clear() break except MqRequestError as e: errors.append(e) if e.status < 500 and e.status != 429: raise e elif i < number_retries - 1: sleep_time = math.pow(2.2, t) t += 1 if verbose: logging.warning(f'Exception caught while making request: {e}, retrying in {int(sleep_time)}') sleep(sleep_time) else: if verbose: logging.warning(f'Maximum number of retries: {number_retries} triggered') except Exception as unknown_exception: errors.append(unknown_exception) if i < number_retries - 1: sleep_time = math.pow(2.2, t) t += 1 if verbose: logging.warning(f'Unknown exception caught: {unknown_exception}, retrying in {int(sleep_time)}') sleep(sleep_time) else: if verbose: logging.warning(f'Maximum number of retries: {number_retries} triggered') if errors: raise errors.pop() ================================================ FILE: gs_quant/priceable.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import logging from abc import ABC from typing import Union, Optional from gs_quant.base import Priceable from gs_quant.context_base import nullcontext from gs_quant.markets import MarketDataCoordinate, PricingContext, CloseMarket, OverlayMarket from gs_quant.risk import DataFrameWithInfo, DollarPrice, FloatWithInfo, Price, SeriesWithInfo, MarketData from gs_quant.risk.results import PricingFuture, PortfolioRiskResult, ErrorValue __asset_class_and_type_to_instrument = {} _logger = logging.getLogger(__name__) class PriceableImpl(Priceable, ABC): @property def _pricing_context(self) -> PricingContext: pricing_context = PricingContext.current return pricing_context if not (pricing_context.is_entered or pricing_context.is_async) else nullcontext() @property def _return_future(self) -> bool: pricing_context = self._pricing_context return not isinstance(pricing_context, PricingContext) or ( pricing_context.is_async or pricing_context.is_entered ) def dollar_price(self) -> Union[FloatWithInfo, PortfolioRiskResult, PricingFuture, SeriesWithInfo]: """ Present value in USD :return: a float or a future, depending on whether the current PricingContext is async, or has been entered **Examples** >>> from gs_quant.instrument import IRCap >>> >>> cap = IRCap('1y', 'EUR') >>> price = cap.dollar_price() price is the present value in USD (a float) >>> cap_usd = IRCap('1y', 'USD') >>> cap_eur = IRCap('1y', 'EUR') >>> >>> from gs_quant.markets import PricingContext >>> >>> with PricingContext(): >>> price_usd_f = cap_usd.dollar_price() >>> price_eur_f = cap_eur.dollar_price() >>> >>> price_usd = price_usd_f.result() >>> price_eur = price_eur_f.result() price_usd_f and price_eur_f are futures, price_usd and price_eur are floats """ return self.calc(DollarPrice) def price(self, currency=None) -> Union[FloatWithInfo, PortfolioRiskResult, PricingFuture, SeriesWithInfo]: """ Present value in local currency. Note that this is not yet supported on all instruments ***Examples** >>> from gs_quant.instrument import IRSwap >>> >>> swap = IRSwap('Pay', '10y', 'EUR') >>> price = swap.price() price is the present value in EUR (a float) """ return self.calc(Price(currency=currency)) if currency else self.calc(Price) def market(self) -> Union[OverlayMarket, PricingFuture]: """ Market Data map of coordinates and values. Note that this is not yet supported on all instruments ***Examples** >>> from gs_quant.instrument import IRSwap >>> >>> swap = IRSwap('Pay', '10y', 'EUR') >>> market = swap.market() """ def handle_result( result: Optional[Union[DataFrameWithInfo, ErrorValue, PricingFuture]], ) -> [OverlayMarket, dict]: if isinstance(result, ErrorValue): return result properties = MarketDataCoordinate.properties() is_historical = result.index.name == 'date' location = PricingContext.current.market_data_location def extract_market_data(this_result: DataFrameWithInfo): market_data = {} for _, row in this_result.iterrows(): coordinate_values = {p: row.get(p) for p in properties} mkt_point = coordinate_values.get('mkt_point') if mkt_point is not None: coordinate_values['mkt_point'] = tuple(coordinate_values['mkt_point'].split(';')) # return 'redacted' as coordinate value if its a redacted coordinate market_data[MarketDataCoordinate.from_dict(coordinate_values)] = ( row['value'] if row['permissions'] == 'Granted' else 'redacted' ) return market_data if is_historical: return { date: OverlayMarket( base_market=CloseMarket(date=date, location=location), market_data=extract_market_data(result.loc[date]), ) for date in set(result.index) } else: return OverlayMarket(base_market=result.risk_key.market, market_data=extract_market_data(result)) return self.calc(MarketData, fn=handle_result) ================================================ FILE: gs_quant/quote_reports/__init__.py ================================================ ================================================ FILE: gs_quant/quote_reports/core.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from typing import Dict, Any, Iterable, Union from dataclasses_json.cfg import _GlobalConfig from gs_quant.base import CustomComments from gs_quant.workflow import ( VisualStructuringReport, BinaryImageComments, HyperLinkImageComments, CustomDeltaHedge, DeltaHedge, HedgeTypes, ) global_config = _GlobalConfig() def quote_report_from_dict(quote_report_dict: Union[Dict[str, Any], VisualStructuringReport]): if quote_report_dict is not None: if isinstance(quote_report_dict, VisualStructuringReport): return quote_report_dict type = quote_report_dict.get('reportType') if 'VisualStructuringReport' == type: report = VisualStructuringReport.from_dict(quote_report_dict) return report return None def quote_reports_from_dicts(quote_report_dicts: Iterable[Dict[str, Any]]): if quote_report_dicts is not None: reports = [] for quote_report_dict in quote_report_dicts: report = quote_report_from_dict(quote_report_dict) reports.append(report) return reports return None def custom_comment_from_dict(in_dict: Union[Dict[str, Any], CustomComments]): if in_dict is not None: if isinstance(in_dict, CustomComments): return in_dict type = in_dict.get('commentType') if 'binaryImageComments' == type: out = BinaryImageComments.from_dict(in_dict) return out if 'hyperLinkImageComments' == type: out = HyperLinkImageComments.from_dict(in_dict) return out return None def custom_comments_from_dicts(in_dicts: Iterable[Dict[str, Any]]): if in_dicts is not None: comments = [] for in_dict in in_dicts: report = custom_comment_from_dict(in_dict) comments.append(report) return comments return None def hedge_type_from_dict(hedge_type_dict: Union[Dict[str, Any], HedgeTypes]): if hedge_type_dict is not None: if isinstance(hedge_type_dict, HedgeTypes): return hedge_type_dict type = hedge_type_dict.get('type') if 'CustomDeltaHedge' == type: hedge_type = CustomDeltaHedge.from_dict(hedge_type_dict) return hedge_type if 'DeltaHedge' == type: hedge_type = DeltaHedge.from_dict(hedge_type_dict) return hedge_type return None def hedge_type_from_dicts(in_dicts: Iterable[Dict[str, Any]]): if in_dicts is not None: hedge_types = [] for in_dict in in_dicts: hedge_type = hedge_type_from_dict(in_dict) hedge_types.append(hedge_type) return hedge_types return None ================================================ FILE: gs_quant/risk/__init__.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from .core import * from .measures import * from .scenarios import MarketDataShockBasedScenario from gs_quant.target.portfolios import LiquidityRequest from gs_quant.target.risk import LiborFallbackScenario, CarryScenario, CompositeScenario, CurveScenario, IndexCurveShift,\ LiquidityResponse, MarketDataPattern, MarketDataScenario, MarketDataShock, MarketDataShockType, RiskRequest, RollFwd,\ CurveOverlay, MultiScenario from gs_quant.target.measures import * ================================================ FILE: gs_quant/risk/core.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import itertools from abc import ABCMeta, abstractmethod from concurrent.futures import Future from copy import copy from dataclasses import dataclass, fields from typing import Iterable, Optional, Union, Tuple, Dict, Callable, List import gs_quant import pandas as pd from dataclasses_json import dataclass_json from gs_quant.base import RiskKey from gs_quant.config import DisplayOptions from gs_quant.datetime import point_sort_order __column_sort_fns = {'label1': point_sort_order, 'mkt_point': point_sort_order, 'point': point_sort_order} __risk_columns = ('date', 'time', 'mkt_type', 'mkt_asset', 'mkt_class', 'mkt_point') class ResultInfo(metaclass=ABCMeta): def __init__( self, risk_key: RiskKey, unit: Optional[dict] = None, error: Optional[Union[str, dict]] = None, request_id: Optional[str] = None, ): self.__risk_key = risk_key self.__unit = unit self.__error = error self.__request_id = request_id @property @abstractmethod def raw_value(self): ... @property def risk_key(self) -> RiskKey: return self.__risk_key @property def unit(self) -> dict: """The units of this result""" return self.__unit @property def error(self) -> Union[str, dict]: """Any error associated with this result""" return self.__error @property def request_id(self) -> Optional[str]: """The request Id associated with this result""" return self.__request_id @staticmethod def composition_info(components: Iterable): from gs_quant.markets.markets import historical_risk_key dates = [] values = [] errors = {} risk_key = None unit = None for component in components: date = component.risk_key.date risk_key = historical_risk_key(component.risk_key) if risk_key is None else risk_key if risk_key.market.location != component.risk_key.market.location: raise ValueError('Cannot compose results with different markets') if isinstance(component, (ErrorValue, Exception)): errors[date] = component elif isinstance(component, UnsupportedValue): values.append(component) dates.append(date) unit = None else: values.append(component.raw_value) dates.append(date) unit = unit or component.unit return dates, values, errors, risk_key, unit class ErrorValue(ResultInfo): def __init__(self, risk_key: RiskKey, error: Union[str, dict], request_id: Optional[str] = None): super().__init__(risk_key, error=error, request_id=request_id) def __repr__(self): return self.error def __getattr__(self, item): # only called if self.item doesn't exist raise AttributeError(f'ErrorValue object has no attribute {item}. Error was {self.error}') @property def raw_value(self): return None def _to_records(self, extra_dict, display_options: DisplayOptions = None): return [{**extra_dict, 'value': self}] class UnsupportedValue(ResultInfo): def __init__(self, risk_key: RiskKey, request_id: Optional[str] = None): super().__init__(risk_key, request_id=request_id) def __repr__(self): return 'Unsupported Value' @property def raw_value(self): return 'Unsupported Value' @staticmethod def compose(components: Iterable): dates, values, errors, risk_key, unit = ResultInfo.composition_info(components) return SeriesWithInfo( pd.Series(index=pd.DatetimeIndex(dates).date, data=values), risk_key=risk_key, unit=unit, error=errors ) def _to_records(self, extra_dict, display_options: DisplayOptions = None): if display_options is not None and not isinstance(display_options, DisplayOptions): raise TypeError("display_options must be of type DisplayOptions") options = display_options if display_options is not None else gs_quant.config.display_options show_na = options.show_na return [{**extra_dict, 'value': self}] if show_na else [] class ScalarWithInfo(ResultInfo, metaclass=ABCMeta): def __init__( self, risk_key: RiskKey, value: Union[float, str, dict], unit: Optional[dict] = None, error: Optional[Union[str, dict]] = None, request_id: Optional[str] = None, ): ResultInfo.__init__(self, risk_key, unit=unit, error=error, request_id=request_id) float.__init__(value) def __reduce__(self): return self.__class__, (self.risk_key, self.raw_value, self.unit, self.error, self.request_id) @property @abstractmethod def raw_value(self): ... @staticmethod def compose(components: Iterable): dates, values, errors, risk_key, unit = ResultInfo.composition_info(components) return SeriesWithInfo( pd.Series(index=pd.DatetimeIndex(dates).date, data=values), risk_key=risk_key, unit=unit, error=errors ) def _to_records(self, extra_dict, display_options: DisplayOptions = None): return [{**extra_dict, 'value': self}] class FloatWithInfo(ScalarWithInfo, float): def __new__( cls, risk_key: RiskKey, value: Union[float, str], unit: dict = None, error: Optional[str] = None, request_id: Optional[str] = None, ): return float.__new__(cls, value) @property def raw_value(self) -> float: return float(self) def __str__(self): return float.__repr__(self) def __repr__(self): if self.error: return self.error else: res = float.__repr__(self) if self.unit and isinstance(self.unit, dict): # Build unit string from dict like {'A': 1, 'B': -1} -> "A/B" numerator = [] denominator = [] for unit_name, power in self.unit.items(): if power > 0: if power == 1: numerator.append(unit_name) else: numerator.append(f"{unit_name}^{power}") elif power < 0: if power == -1: denominator.append(unit_name) else: denominator.append(f"{unit_name}^{abs(power)}") if numerator and denominator: unit_str = f"{'*'.join(numerator)}/{'*'.join(denominator)}" elif numerator: unit_str = '*'.join(numerator) elif denominator: unit_str = f"1/{'*'.join(denominator)}" else: unit_str = "" return f"{res} ({unit_str})" if unit_str else res return res def __add__(self, other): if isinstance(other, FloatWithInfo): if self.unit == other.unit: return FloatWithInfo( combine_risk_key(self.risk_key, other.risk_key), self.raw_value + other.raw_value, self.unit ) else: raise ValueError('FloatWithInfo unit mismatch') return super(FloatWithInfo, self).__add__(other) def __mul__(self, other): if isinstance(other, FloatWithInfo): return FloatWithInfo( combine_risk_key(self.risk_key, other.risk_key), self.raw_value * other.raw_value, self.unit ) else: return FloatWithInfo(self.risk_key, self.raw_value * other, self.unit) def to_frame(self): return self class StringWithInfo(ScalarWithInfo, str): def __new__( cls, risk_key: RiskKey, value: Union[float, str], unit: Optional[dict] = None, error: Optional[str] = None, request_id: Optional[str] = None, ): return str.__new__(cls, value) @property def raw_value(self) -> str: return str(self) def __repr__(self): return self.error if self.error else str.__repr__(self) class DictWithInfo(ScalarWithInfo, dict): def __new__( cls, risk_key: RiskKey, value: Union[float, str, dict], unit: Optional[dict] = None, error: Optional[str] = None, request_id: Optional[str] = None, ): return dict.__new__(cls, value) def __init__( self, risk_key: RiskKey, value: Union[float, str, dict], unit: Optional[dict] = None, error: Optional[Union[str, dict]] = None, request_id: Optional[str] = None, ): dict.__init__(self, value) ResultInfo.__init__(self, risk_key, unit=unit, error=error, request_id=request_id) @property def raw_value(self) -> dict: return dict(self) def __repr__(self): return self.error if self.error else dict.__repr__(self) class SeriesWithInfo(pd.Series, ResultInfo): _internal_names = pd.DataFrame._internal_names + [ '_ResultInfo__' + i for i in dir(ResultInfo) if isinstance(getattr(ResultInfo, i), property) ] _internal_names_set = set(_internal_names) def __init__( self, *args, risk_key: Optional[RiskKey] = None, unit: Optional[dict] = None, error: Optional[Union[str, dict]] = None, request_id: Optional[str] = None, **kwargs, ): pd.Series.__init__(self, *args, **kwargs) ResultInfo.__init__(self, risk_key, unit=unit, error=error, request_id=request_id) def __repr__(self): if self.error: return pd.Series.__repr__(self) + "\nErrors: " + str(self.error) return pd.Series.__repr__(self) @property def _constructor(self): return SeriesWithInfo @property def _constructor_expanddim(self): return DataFrameWithInfo @property def raw_value(self) -> pd.Series: return pd.Series(self) @staticmethod def compose(components: Iterable): dates, values, errors, risk_key, unit = ResultInfo.composition_info(components) return SeriesWithInfo( pd.Series(index=pd.DatetimeIndex(dates).date, data=values), risk_key=risk_key, unit=unit, error=errors ) def _to_records(self, extra_dict, display_options: DisplayOptions = None): df = pd.DataFrame(self).reset_index() df.columns = ['dates', 'value'] records = df.to_dict('records') records = [dict(item, **{**extra_dict}) for item in records] return records def __mul__(self, other): new_result = pd.Series.__mul__(self, other) ResultInfo.__init__( new_result, risk_key=self.risk_key, unit=self.unit, error=self.error, request_id=self.request_id ) return new_result def copy_with_resultinfo(self, deep=True): return SeriesWithInfo( self.raw_value.copy(deep=deep), risk_key=self.risk_key, unit=self.unit, error=self.error, request_id=self.request_id, ) class DataFrameWithInfo(pd.DataFrame, ResultInfo): _internal_names = pd.DataFrame._internal_names + [ '_ResultInfo__' + i for i in dir(ResultInfo) if isinstance(getattr(ResultInfo, i), property) ] _internal_names_set = set(_internal_names) def __init__( self, *args, risk_key: Optional[RiskKey] = None, unit: Optional[dict] = None, error: Optional[Union[str, dict]] = None, request_id: Optional[str] = None, **kwargs, ): pd.DataFrame.__init__(self, *args, **kwargs) ResultInfo.__init__(self, risk_key, unit=unit, error=error, request_id=request_id) def __repr__(self): if self.error: return pd.DataFrame.__repr__(self) + "\nErrors: " + str(self.errors) return pd.DataFrame.__repr__(self) @property def _constructor(self): return DataFrameWithInfo @property def _constructor_sliced(self): return SeriesWithInfo @property def raw_value(self) -> pd.DataFrame: if self.empty: return pd.DataFrame(self) df = self.copy() if isinstance(self.index.values[0], dt.date): df.index.name = 'dates' df = df.reset_index() return pd.DataFrame(df) @staticmethod def compose(components: Iterable): dates, values, errors, risk_key, unit = ResultInfo.composition_info(components) df = pd.concat(v.assign(date=d) for d, v in zip(dates, values)).set_index('date') return DataFrameWithInfo(df, risk_key=risk_key, unit=unit, error=errors) def to_frame(self): return self def _to_records(self, extra_dict, display_options: DisplayOptions = None): if self.empty: if display_options is not None and not isinstance(display_options, DisplayOptions): raise TypeError("display_options must be of type DisplayOptions") options = display_options if display_options is not None else gs_quant.config.display_options show_na = options.show_na return [{**extra_dict, 'value': None}] if show_na else [] return [dict(item, **{**extra_dict}) for item in self.raw_value.to_dict('records')] def copy_with_resultinfo(self, deep=True): return DataFrameWithInfo( self.raw_value.copy(deep=deep), risk_key=self.risk_key, unit=self.unit, error=self.error, request_id=self.request_id, ) def filter_by_coord(self, coordinate): from gs_quant.markets import MarketDataCoordinate df = self.copy_with_resultinfo() for att in [i.name for i in fields(MarketDataCoordinate)]: if getattr(coordinate, att) is not None: if isinstance(getattr(coordinate, att), str): df = df[getattr(df, att) == getattr(coordinate, att)] else: df = df[getattr(df, att).isin(getattr(coordinate, att))] return df @dataclass_json @dataclass class MQVSValidationTarget: env: Optional[str] = None operator: Optional[str] = None mqGroups: Optional[Tuple[str, ...]] = None users: Optional[Tuple[str, ...]] = None assetClasses: Optional[Tuple[str, ...]] = None assets: Optional[Tuple[str, ...]] = None legTypes: Optional[Tuple[str, ...]] = None legFields: Optional[Dict[str, str]] = None @dataclass_json @dataclass class MQVSValidatorDefn: validatorType: str targets: Tuple[MQVSValidationTarget, ...] args: Dict[str, str] groupId: Optional[str] = None groupIndex: Optional[int] = None groupMethod: Optional[str] = None class MQVSValidatorDefnsWithInfo(ResultInfo): validators: Tuple[MQVSValidatorDefn, ...] def __init__( self, risk_key: RiskKey, value: Union[MQVSValidatorDefn, Tuple[MQVSValidatorDefn, ...]], unit: Optional[dict] = None, error: Optional[Union[str, dict]] = None, request_id: Optional[str] = None, ): ResultInfo.__init__(self, risk_key, unit=unit, error=error, request_id=request_id) if value and isinstance(value, tuple): self.validators = value elif value and isinstance(value, MQVSValidatorDefn): self.validators = tuple([value]) @property def raw_value(self): return self.validators def aggregate_risk( results: Iterable[Union[DataFrameWithInfo, Future]], threshold: Optional[float] = None, allow_heterogeneous_types: bool = False, ) -> pd.DataFrame: """ Combine the results of multiple InstrumentBase.calc() calls, into a single result :param results: An iterable of Dataframes and/or Futures (returned by InstrumentBase.calc()) :param threshold: exclude values whose absolute value falls below this threshold :param allow_heterogeneous_types: allow Series to be converted to DataFrames before aggregating :return: A Dataframe with the aggregated results **Examples** >>> from gs_quant.instrument import IRCap, IRFloor >>> from gs_quant.markets import PricingContext >>> from gs_quant.risk import IRDelta, IRVega >>> >>> cap = IRCap('5y', 'GBP') >>> floor = IRFloor('5y', 'GBP') >>> instruments = (cap, floor) >>> >>> with PricingContext(): >>> delta_f = [inst.calc(IRDelta) for inst in instruments] >>> vega_f = [inst.calc(IRVega) for inst in (cap, floor)] >>> >>> delta = aggregate_risk(delta_f, threshold=0.1) >>> vega = aggregate_risk(vega_f) delta_f and vega_f are lists of futures, where the result will be a Dataframe delta and vega are Dataframes, representing the merged risk of the individual instruments """ def get_df(result_obj): if isinstance(result_obj, Future): result_obj = result_obj.result() if isinstance(result_obj, pd.Series) and allow_heterogeneous_types: return pd.DataFrame(result_obj.raw_value).T return result_obj.raw_value dfs = [get_df(r) for r in results] result = pd.concat(dfs).fillna(0) result = result.groupby([c for c in result.columns if c != 'value'], as_index=False).sum() if threshold is not None: result = result[result.value.abs() > threshold] return sort_risk(result) ResultType = Union[None, dict, tuple, DataFrameWithInfo, FloatWithInfo, SeriesWithInfo] def aggregate_results( results: Iterable[ResultType], allow_mismatch_risk_keys=False, allow_heterogeneous_types=False ) -> ResultType: unit = None risk_key = None results = tuple(results) if not len(results): return None for result in results: if isinstance(result, Exception): raise Exception if result.error: raise ValueError('Cannot aggregate results in error') if not allow_heterogeneous_types and not isinstance(result, type(results[0])): raise ValueError(f'Cannot aggregate heterogeneous types: {type(result)} vs {type(results[0])}') if result.unit: if unit and unit != result.unit: raise ValueError(f'Cannot aggregate results with different units for {result.risk_key.risk_measure}') unit = unit or result.unit if ( not allow_mismatch_risk_keys and risk_key and risk_key.ex_historical_diddle != result.risk_key.ex_historical_diddle ): raise ValueError('Cannot aggregate results with different pricing keys') risk_key = risk_key or result.risk_key inst = next(iter(results)) if isinstance(inst, dict): return dict((k, aggregate_results([r[k] for r in results])) for k in inst.keys()) elif isinstance(inst, tuple): return tuple(set(itertools.chain.from_iterable(results))) elif isinstance(inst, FloatWithInfo): return FloatWithInfo(risk_key, sum(results), unit=unit) elif isinstance(inst, SeriesWithInfo): return SeriesWithInfo(sum(results), risk_key=risk_key, unit=unit) elif isinstance(inst, DataFrameWithInfo): return DataFrameWithInfo( aggregate_risk(results, allow_heterogeneous_types=allow_heterogeneous_types), risk_key=risk_key, unit=unit ) def subtract_risk(left: DataFrameWithInfo, right: DataFrameWithInfo) -> pd.DataFrame: """Subtract bucketed risk. Dimensions must be identical :param left: Results to substract from :param right: Results to substract **Examples** >>> from gs_quant.datetime.date import business_day_offset >>> from gs_quant.instrument IRSwap >>> from gs_quant.markets import PricingContext >>> from gs_quant.risk import IRDelta >>> import datetime as dt >>> >>> ir_swap = IRSwap('Pay', '10y', 'USD') >>> delta_today = ir_swap.calc(IRDelta) >>> >>> with PricingContext(pricing_date=business_day_offset(dt.date.today(), -1, roll='preceding')): >>> delta_yday_f = ir_swap.calc(IRDelta) >>> >>> delta_diff = subtract_risk(delta_today, delta_yday_f.result()) """ assert left.columns.names == right.columns.names assert 'value' in left.columns.names right_negated = copy(right) right_negated.value *= -1 return aggregate_risk((left, right_negated)) def sort_values(data: Iterable, columns: Tuple[str, ...], by: Tuple[str, ...]) -> Iterable: indices = tuple(columns.index(c) for c in by if c in columns) fns: List[Optional[Callable[[any], Optional[float]]]] = [None] * len(columns) for idx in indices: fns[idx] = __column_sort_fns.get(columns[idx]) def cmp(row) -> tuple: return tuple((fns[i](row[i]) or 0) if fns[i] else row[i] for i in indices) return sorted(data, key=cmp) def sort_risk(df: pd.DataFrame, by: Tuple[str, ...] = __risk_columns) -> pd.DataFrame: """ Sort bucketed risk :param df: Input Dataframe :param by: Columns to sort by :return: A sorted Dataframe """ columns = tuple(df.columns) data = sort_values(df.values, columns, by) df_fields = [f for f in by if f in columns] df_fields.extend(f for f in columns if f not in df_fields) result = pd.DataFrame.from_records(data, columns=columns)[df_fields] if 'date' in result: result = result.set_index('date') return result def combine_risk_key(key_1: RiskKey, key_2: RiskKey) -> RiskKey: """ Combine two risk keys (key_1, key_2) into a new RiskKey :type key_1: RiskKey :type key_2: RiskKey """ def get_field_value(field_name: str): return getattr(key_1, field_name) if getattr(key_1, field_name) == getattr(key_2, field_name) else None return RiskKey( get_field_value("provider"), get_field_value("date"), get_field_value("market"), get_field_value("params"), get_field_value("scenario"), get_field_value("risk_measure"), ) ================================================ FILE: gs_quant/risk/measures.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from typing import Union from gs_quant.base import Market from gs_quant.common import AssetClass, AggregationLevel, RiskMeasure, RiskMeasureType, RiskMeasureUnit from gs_quant.target.measures import IRBasis, IRVega, IRDelta, IRXccyDelta, InflationDelta DEPRECATED_MEASURES = {} class __RelativeRiskMeasure(RiskMeasure): def __init__( self, to_market: Market, asset_class: Union[AssetClass, str] = None, measure_type: Union[RiskMeasureType, str] = None, unit: Union[RiskMeasureUnit, str] = None, value: Union[float, str] = None, name: str = None, ): super().__init__(asset_class=asset_class, measure_type=measure_type, unit=unit, value=value) self.__to_market = to_market self.name = name @property def pricing_context(self): from gs_quant.markets import PricingContext, RelativeMarket current = PricingContext.current return current.clone(market=RelativeMarket(from_market=current.market, to_market=self.__to_market)) class PnlExplain(__RelativeRiskMeasure): """Pnl Explained""" def __init__(self, to_market: Market): super().__init__(to_market, measure_type=RiskMeasureType.PnlExplain, name=RiskMeasureType.PnlExplain.value) class PnlExplainClose(PnlExplain): def __init__(self): from gs_quant.markets import CloseMarket super().__init__(CloseMarket()) class PnlExplainLive(PnlExplain): def __init__(self): from gs_quant.markets import LiveMarket super().__init__(LiveMarket()) class PnlPredictLive(__RelativeRiskMeasure): """Pnl Predicted""" def __init__(self): from gs_quant.markets import LiveMarket super().__init__(LiveMarket(), measure_type=RiskMeasureType.PnlPredict, name=RiskMeasureType.PnlPredict.value) # Defining Parameterised Risk Measures IRBasisParallel = IRBasis(aggregation_level=AggregationLevel.Asset, name='IRBasisParallel') InflationDeltaParallel = InflationDelta(aggregation_level=AggregationLevel.Type, name='InflationDeltaParallel') IRDeltaParallel = IRDelta(aggregation_level=AggregationLevel.Asset, name='IRDeltaParallel') IRDeltaLocalCcy = IRDelta(currency='local', name='IRDeltaLocalCcy') IRXccyDeltaParallel = IRXccyDelta(aggregation_level=AggregationLevel.Type, name='IRXccyDeltaParallel') IRVegaParallel = IRVega(aggregation_level=AggregationLevel.Asset, name='IRVegaParallel') IRVegaLocalCcy = IRVega(currency='local', name='IRVegaLocalCcy') ================================================ FILE: gs_quant/risk/result_handlers.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import logging from typing import Iterable, Optional, Union from gs_quant.base import InstrumentBase, RiskKey from gs_quant.common import RiskMeasure, AssetClass, RiskMeasureType from gs_quant.risk.measures import PnlExplain from .core import ( DataFrameWithInfo, ErrorValue, UnsupportedValue, FloatWithInfo, SeriesWithInfo, StringWithInfo, sort_values, MQVSValidatorDefnsWithInfo, MQVSValidatorDefn, DictWithInfo, ) _logger = logging.getLogger(__name__) def __dataframe_handler( result: Iterable, mappings: tuple, risk_key: RiskKey, request_id: Optional[str] = None ) -> DataFrameWithInfo: first_row = next(iter(result), None) if first_row is None: return DataFrameWithInfo(risk_key=risk_key, request_id=request_id) columns = () indices = [False] * len(first_row.keys()) mappings_lookup = {v: k for k, v in mappings} for idx, src in enumerate(first_row.keys()): if src in mappings_lookup: indices[idx] = True columns += ((mappings_lookup[src]),) records = tuple( sort_values((tuple(v for i, v in enumerate(r.values()) if indices[i]) for r in result), columns, columns) ) df = DataFrameWithInfo(records, risk_key=risk_key, request_id=request_id) df.columns = columns return df def __dataframe_handler_unsorted( result: Iterable, mappings: tuple, date_cols: tuple, risk_key: RiskKey, request_id: Optional[str] = None ) -> DataFrameWithInfo: first_row = next(iter(result), None) if first_row is None: return DataFrameWithInfo(risk_key=risk_key, request_id=request_id) records = ([row.get(field_from) for field_to, field_from in mappings] for row in result) df = DataFrameWithInfo(records, risk_key=risk_key, request_id=request_id) df.columns = [m[0] for m in mappings] for dt_col in date_cols: df[dt_col] = df[dt_col].map(lambda x: dt.datetime.strptime(x, '%Y-%m-%d').date() if isinstance(x, str) else x) return df def cashflows_handler( result: dict, risk_key: RiskKey, _instrument: InstrumentBase, request_id: Optional[str] = None ) -> DataFrameWithInfo: mappings = ( ('currency', 'currency'), ('payment_date', 'payDate'), ('set_date', 'setDate'), ('accrual_start_date', 'accStart'), ('accrual_end_date', 'accEnd'), ('payment_amount', 'payAmount'), ('notional', 'notional'), ('payment_type', 'paymentType'), ('floating_rate_option', 'index'), ('floating_rate_designated_maturity', 'indexTerm'), ('day_count_fraction', 'dayCountFraction'), ('spread', 'spread'), ('rate', 'rate'), ('discount_factor', 'discountFactor'), ) date_cols = ('payment_date', 'set_date', 'accrual_start_date', 'accrual_end_date') return __dataframe_handler_unsorted(result['cashflows'], mappings, date_cols, risk_key, request_id=request_id) def error_handler( result: dict, risk_key: RiskKey, instrument: InstrumentBase, request_id: Optional[str] = None ) -> ErrorValue: error = result.get('errorString', 'Unknown error') if request_id: error += f'. request Id={request_id}' _logger.error(f'Error while computing {risk_key.risk_measure} on {instrument} for {risk_key.date}: {error}') return ErrorValue(risk_key, error, request_id=request_id) def leg_definition_handler( result: dict, risk_key: RiskKey, instrument: InstrumentBase, request_id: Optional[str] = None ) -> InstrumentBase: return instrument.resolved(result, risk_key) def message_handler( result: dict, risk_key: RiskKey, _instrument: InstrumentBase, request_id: Optional[str] = None ) -> Union[StringWithInfo, ErrorValue]: message = result.get('message') if message is None: return ErrorValue(risk_key, "No result returned", request_id=request_id) else: return StringWithInfo(risk_key, message, request_id=request_id) def number_and_unit_handler( result: dict, risk_key: RiskKey, _instrument: InstrumentBase, request_id: Optional[str] = None ) -> FloatWithInfo: return FloatWithInfo(risk_key, result.get('value', float('nan')), unit=result.get('unit'), request_id=request_id) def required_assets_handler( result: dict, risk_key: RiskKey, _instrument: InstrumentBase, request_id: Optional[str] = None ) -> DataFrameWithInfo: mappings = (('mkt_type', 'type'), ('mkt_asset', 'asset')) return __dataframe_handler(result['requiredAssets'], mappings, risk_key, request_id=request_id) def dict_risk_handler( result: dict, risk_key: RiskKey, _instrument: InstrumentBase, request_id: Optional[str] = None ) -> DictWithInfo: return DictWithInfo(risk_key, result, unit=None, request_id=request_id) def risk_handler( result: dict, risk_key: RiskKey, _instrument: InstrumentBase, request_id: Optional[str] = None ) -> Union[DataFrameWithInfo, FloatWithInfo]: if result.get('children'): # result with leg valuations classes = [] if result.get('val'): classes.append({'path': 'parent', 'value': result.get('val')}) for key, val in result.get('children').items(): classes.append({'path': key, 'value': val}) mappings = (('path', 'path'), ('value', 'value')) return __dataframe_handler(classes, mappings, risk_key, request_id=request_id) else: return FloatWithInfo(risk_key, result.get('val', float('nan')), unit=result.get('unit'), request_id=request_id) def risk_by_class_handler( result: dict, risk_key: RiskKey, _instrument: InstrumentBase, request_id: Optional[str] = None ) -> Union[DataFrameWithInfo, FloatWithInfo]: # TODO Remove this once we migrate parallel USD IRDelta measures types = [c['type'] for c in result['classes']] # list of risk by class measures exposed in gs-quant external_risk_by_class_val = ['IRBasisParallel', 'IRDeltaParallel', 'IRVegaParallel', 'PnlExplain'] if str(risk_key.risk_measure.name) in external_risk_by_class_val and len(types) <= 2 and len(set(types)) == 1: return FloatWithInfo( risk_key, sum(result.get('values', (float('nan'),))), unit=result.get('unit'), request_id=request_id ) else: classes = [] skip = [] crosses_idx = next((i for i, c in enumerate(result['classes']) if c['type'] == 'CROSSES'), None) for idx, (clazz, value) in enumerate(zip(result['classes'], result['values'])): mkt_type = clazz['type'] if 'SPIKE' in mkt_type or 'JUMP' in mkt_type: skip.append(idx) if crosses_idx is not None: result['classes'][crosses_idx]['value'] += value clazz.update({'value': value}) for idx, clazz in enumerate(result['classes']): if idx not in skip: classes.append(clazz) mappings = (('mkt_type', 'type'), ('mkt_asset', 'asset'), ('value', 'value')) if isinstance(risk_key.risk_measure, PnlExplain): return __dataframe_handler_unsorted(classes, mappings, (), risk_key, request_id=request_id) else: return __dataframe_handler(classes, mappings, risk_key, request_id=request_id) def risk_vector_handler( result: dict, risk_key: RiskKey, _instrument: InstrumentBase, request_id: Optional[str] = None ) -> DataFrameWithInfo: assets = result['asset'] # Handle equity risk measures which are really scalars if len(assets) == 1 and risk_key.risk_measure.name.startswith('Eq'): return FloatWithInfo(risk_key, assets[0], request_id=request_id) for points, value in zip(result['points'], assets): points.update({'value': value}) mappings = ( ('mkt_type', 'type'), ('mkt_asset', 'asset'), ('mkt_class', 'class_'), ('mkt_point', 'point'), ('mkt_quoting_style', 'quoteStyle'), ('value', 'value'), ) return __dataframe_handler(result['points'], mappings, risk_key, request_id=request_id) def fixing_table_handler( result: dict, risk_key: RiskKey, _instrument: InstrumentBase, request_id: Optional[str] = None ) -> SeriesWithInfo: rows = result['fixingTableRows'] dates = [] values = [] for row in rows: dates.append(dt.date.fromisoformat(row["fixingDate"])) values.append(row["fixing"]) return SeriesWithInfo(values, index=dates, risk_key=risk_key, request_id=request_id) def simple_valtable_handler( result: dict, risk_key: RiskKey, _instrument: InstrumentBase, request_id: Optional[str] = None ) -> DataFrameWithInfo: def get_value(value): handler = result_handlers.get(value.get('$type')) return handler(value, risk_key, _instrument, request_id) raw_res = result['rows'] # simplevaltable's values contain all the information on units which needs to be extracted into the dataframe df = DataFrameWithInfo( [(res['label'], get_value(res['value'])) for res in raw_res], risk_key=risk_key, request_id=request_id, unit=raw_res[0]['value'].get('unit'), ) df.columns = ['label', 'value'] return df def canonical_projection_table_handler( result: dict, risk_key: RiskKey, _instrument: InstrumentBase, request_id: Optional[str] = None ) -> DataFrameWithInfo: mappings = ( ('asset_class', 'assetClass'), ('asset', 'asset'), ('asset_family', 'assetFamily'), ('asset_sub_family', 'assetSubFamily'), ('product', 'product'), ('product_family', 'productFamily'), ('product_sub_family', 'productSubFamily'), ('side', 'side'), ('size', 'size'), ('size_unit', 'sizeUnit'), ('quote_level', 'quoteLevel'), ('quote_unit', 'quoteUnit'), ('start_date', 'startDate'), ('end_date', 'endDate'), ('expiration_date', 'expiryDate'), ('strike', 'strike'), ('strike_unit', 'strikeUnit'), ('option_type', 'optionType'), ('option_style', 'optionStyle'), ('tenor', 'tenor'), ('tenor_unit', 'tenorUnit'), ('premium_currency', 'premiumCcy'), ('currency', 'currency'), ) date_cols = ('start_date', 'end_date', 'expiration_date') return __dataframe_handler_unsorted(result['rows'], mappings, date_cols, risk_key, request_id) def risk_float_handler( result: dict, risk_key: RiskKey, _instrument: InstrumentBase, request_id: Optional[str] = None ) -> FloatWithInfo: return FloatWithInfo(risk_key, result['values'][0], request_id=request_id) def map_coordinate_to_column(coordinate_struct, tag): updated_struct = { tag + "_" + k: v for k, v in coordinate_struct.items() if k in ['type', 'asset', 'class_', 'point', 'quoteStyle'] } raw_point = updated_struct.get('point', '') point = ';'.join(raw_point) if isinstance(raw_point, list) else raw_point updated_struct['point'] = point return updated_struct def __is_single_row_2nd_order_risk(risk_key: RiskKey): return ( risk_key is not None and isinstance(risk_key.risk_measure, RiskMeasure) and risk_key.risk_measure.asset_class == AssetClass.Rates and risk_key.risk_measure.measure_type in (RiskMeasureType.ParallelGamma, RiskMeasureType.ParallelGammaLocalCcy) ) def mdapi_second_order_table_handler( result: dict, risk_key: RiskKey, _instrument: InstrumentBase, request_id: Optional[str] = None ) -> Union[DataFrameWithInfo, FloatWithInfo]: if len(result['values']) == 1 and __is_single_row_2nd_order_risk(risk_key): return risk_float_handler(result, risk_key, _instrument, request_id) coordinate_pairs = [] if len(result['innerPoints']) != len(result['outerPoints']): raise Exception("Found inner and outer points of different size") for inner, outer, value in zip(result['innerPoints'], result['outerPoints'], result['values']): row_dict = dict(map_coordinate_to_column(inner, 'inner'), **map_coordinate_to_column(outer, 'outer')) row_dict.update({'value': value}) coordinate_pairs.append(row_dict) mappings = ( ('inner_mkt_type', 'inner_type'), ('inner_mkt_asset', 'inner_asset'), ('inner_mkt_class', 'inner_class_'), ('inner_mkt_point', 'inner_point'), ('inner_mkt_quoting_style', 'inner_quotingStyle'), ('outer_mkt_type', 'outer_type'), ('outer_mkt_asset', 'outer_asset'), ('outer_mkt_class', 'outer_class_'), ('outer_mkt_point', 'outer_point'), ('outer_mkt_quoting_style', 'outer_quotingStyle'), ('value', 'value'), ('permissions', 'permissions'), ) return __dataframe_handler(coordinate_pairs, mappings, risk_key, request_id=request_id) def mdapi_table_handler( result: dict, risk_key: RiskKey, _instrument: InstrumentBase, request_id: Optional[str] = None ) -> DataFrameWithInfo: coordinates = [] for r in result['rows']: raw_point = r['coordinate'].get('point', '') point = ';'.join(raw_point) if isinstance(raw_point, list) else raw_point r['coordinate'].update({'point': point}) r['coordinate'].update({'value': r.get('value', None)}) r['coordinate'].update({'permissions': r['permissions']}) coordinates.append(r['coordinate']) mappings = ( ('mkt_type', 'type'), ('mkt_asset', 'asset'), ('mkt_class', 'assetClass'), ('mkt_point', 'point'), ('mkt_quoting_style', 'quotingStyle'), ('value', 'value'), ('permissions', 'permissions'), ) return __dataframe_handler(coordinates, mappings, risk_key, request_id=request_id) def mmapi_table_handler( result: dict, risk_key: RiskKey, _instrument: InstrumentBase, request_id: Optional[str] = None ) -> DataFrameWithInfo: coordinates = [] for r in result['rows']: raw_point = r['modelCoordinate'].get('point', '') point = ';'.join(raw_point) if isinstance(raw_point, list) else raw_point r['modelCoordinate'].update({'point': point}) raw_tags = r['modelCoordinate'].get('tags', '') tags = ';'.join(raw_tags) if isinstance(raw_tags, list) else raw_tags r['modelCoordinate'].update({'tags': tags}) rows = r['value'].get('value', '') DataPoints = [] for row in rows: DataPoints.append([dt.date.fromisoformat(row["date"]), row["value"]]) r['modelCoordinate'].update({'value': DataPoints}) coordinates.append(r['modelCoordinate']) mappings = ( ('mkt_type', 'type'), ('mkt_asset', 'asset'), ('mkt_point', 'point'), ('mkt_tags', 'tags'), ('mkt_quoting_style', 'quotingStyle'), ('value', 'value'), ) return __dataframe_handler(coordinates, mappings, risk_key, request_id=request_id) def mmapi_pca_table_handler( result: dict, risk_key: RiskKey, _instrument: InstrumentBase, request_id: Optional[str] = None ) -> DataFrameWithInfo: coordinates = [] for r in result['rows']: raw_point = r['coordinate'].get('point', '') point = ';'.join(raw_point) if isinstance(raw_point, list) else raw_point r['coordinate'].update({'point': point}) r['coordinate'].update({'value': r['value']}) r['coordinate'].update({'layer1': r['layer1']}) r['coordinate'].update({'layer2': r['layer2']}) r['coordinate'].update({'layer3': r['layer3']}) r['coordinate'].update({'layer4': r['layer4']}) r['coordinate'].update({'level': r['level']}) r['coordinate'].update({'sensitivity': r['sensitivity']}) r['coordinate'].update({'irDelta': r['irDelta']}) r['coordinate'].update({'endDate': r['endDate']}) coordinates.append(r['coordinate']) mappings = ( ('mkt_type', 'type'), ('mkt_asset', 'asset'), ('mkt_class', 'assetClass'), ('mkt_point', 'point'), ('mkt_quoting_style', 'quotingStyle'), ('value', 'value'), ('layer1', 'layer1'), ('layer2', 'layer2'), ('layer3', 'layer3'), ('layer4', 'layer4'), ('level', 'level'), ('sensitivity', 'sensitivity'), ('irDelta', 'irDelta'), ('endDate', 'endDate'), ) return __dataframe_handler(coordinates, mappings, risk_key, request_id=request_id) def mmapi_pca_hedge_table_handler( result: dict, risk_key: RiskKey, _instrument: InstrumentBase, request_id: Optional[str] = None ) -> DataFrameWithInfo: coordinates = [] for r in result['rows']: raw_point = r['coordinate'].get('point', '') point = ';'.join(raw_point) if isinstance(raw_point, list) else raw_point r['coordinate'].update({'point': point}) r['coordinate'].update({'size': r.get('size')}) r['coordinate'].update({'fixedRate': r.get('fixedRate')}) r['coordinate'].update({'irDelta': r.get('irDelta')}) coordinates.append(r['coordinate']) mappings = ( ('mkt_type', 'type'), ('mkt_asset', 'asset'), ('mkt_class', 'assetClass'), ('mkt_point', 'point'), ('mkt_quoting_style', 'quotingStyle'), ('size', 'size'), ('fixedRate', 'fixedRate'), ('irDelta', 'irDelta'), ) return __dataframe_handler(coordinates, mappings, risk_key, request_id=request_id) def mqvs_validators_handler( result: dict, risk_key: RiskKey, _instrument: InstrumentBase, request_id: Optional[str] = None ) -> MQVSValidatorDefnsWithInfo: validators = [MQVSValidatorDefn.from_dict(r) for r in result['validators']] return MQVSValidatorDefnsWithInfo(risk_key, tuple(validators), request_id=request_id) def market_handler( result: dict, risk_key: RiskKey, _instrument: InstrumentBase, request_id: Optional[str] = None ) -> StringWithInfo: return StringWithInfo(risk_key, result.get('marketRef'), request_id=request_id) def unsupported_handler( _result: dict, risk_key: RiskKey, _instrument: InstrumentBase, request_id: Optional[str] = None ) -> UnsupportedValue: return UnsupportedValue(risk_key, request_id=request_id) result_handlers = { 'Error': error_handler, 'IRPCashflowTable': cashflows_handler, 'LegDefinition': leg_definition_handler, 'Message': message_handler, 'MDAPITable': mdapi_table_handler, 'MMAPITable': mmapi_table_handler, 'MMAPIPCATable': mmapi_pca_table_handler, 'MMAPIPCAHedgeTable': mmapi_pca_hedge_table_handler, 'MQVSValidators': mqvs_validators_handler, 'NumberAndUnit': number_and_unit_handler, 'PriceGrid': dict_risk_handler, 'RequireAssets': required_assets_handler, 'Risk': risk_handler, 'RiskByClass': risk_by_class_handler, 'RiskVector': risk_vector_handler, 'FixingTable': fixing_table_handler, 'Table': simple_valtable_handler, 'CanonicalProjectionTable': canonical_projection_table_handler, 'RiskSecondOrderVector': mdapi_second_order_table_handler, 'RiskTheta': risk_float_handler, 'Market': market_handler, 'Unsupported': unsupported_handler, } ================================================ FILE: gs_quant/risk/results.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import copy import datetime as dt import logging import operator as op import weakref from concurrent.futures import Future from itertools import chain from typing import Any, Iterable, Mapping, Optional, Tuple, Union import pandas as pd from gs_quant.base import Priceable, RiskKey, Sentinel, InstrumentBase, is_instance_or_iterable, is_iterable, Scenario from gs_quant.common import RiskMeasure from gs_quant.config import DisplayOptions from gs_quant.risk import ( DataFrameWithInfo, ErrorValue, UnsupportedValue, FloatWithInfo, SeriesWithInfo, ResultInfo, ScalarWithInfo, aggregate_results, ) from gs_quant.risk.transform import Transformer from more_itertools import unique_everseen _logger = logging.getLogger(__name__) def get_default_pivots( cls: str, has_dates: bool, multi_measures: bool, multi_scen: bool, simple_port: bool = None, ori_cols=None ): if cls == 'MultipleScenarioResult': return 'value', 'scenario', 'dates' if has_dates else None elif cls == 'MultipleRiskMeasureResult': return 'value', ('risk_measure', 'scenario') if multi_scen else 'risk_measure', 'dates' if has_dates else None elif cls == 'PortfolioRiskResult': if ori_cols is None: raise ValueError('columns of dataframe required to get default pivots') portfolio_names = list(filter(lambda x: 'portfolio_name_' in x, ori_cols)) port_and_inst_names = portfolio_names + ['instrument_name'] pivot_rules = [ # has_dates, multi_measures, simple_port, multi_scen # output: (value,index,columns) [True, True, None, False, ('value', 'dates', port_and_inst_names + ['risk_measure'])], [True, False, None, False, ('value', 'dates', port_and_inst_names)], [False, False, False, False, ('value', portfolio_names, 'instrument_name')], [False, None, None, False, ('value', port_and_inst_names, 'risk_measure')], [True, True, None, True, ('value', 'dates', port_and_inst_names + ['risk_measure', 'scenario'])], [True, False, None, True, ('value', 'dates', port_and_inst_names + ['scenario'])], [False, True, None, True, ('value', port_and_inst_names, ['risk_measure', 'scenario'])], [False, False, None, True, ('value', port_and_inst_names, 'scenario')], ] def match(rule_value, check_value) -> bool: if rule_value is None: return True elif callable(rule_value): return rule_value(check_value) else: return rule_value == check_value for rule in pivot_rules: [rule_has_dates, rule_multi_measures, rule_simple_port, rule_multi_scen, rule_output] = rule if ( match(rule_has_dates, has_dates) and match(rule_multi_measures, multi_measures) and match(rule_simple_port, simple_port) and match(rule_multi_scen, multi_scen) ): return rule_output return None, None, None def pivot_to_frame(df, values, index, columns, aggfunc): try: pivot_df = df.pivot_table(values=values, index=index, columns=columns, aggfunc=aggfunc) except ValueError: raise RuntimeError('Unable to successfully pivot data') try: # attempt to correct order of index if index is not None: idx = df.set_index(list(pivot_df.index.names)).index.unique() pivot_df = pivot_df.reindex(index=idx) if columns is not None: cols = df.set_index(list(pivot_df.columns.names)).index.unique() pivot_df = pivot_df.reindex(columns=cols) return pivot_df except KeyError: return pivot_df def _compose(lhs: ResultInfo, rhs: ResultInfo) -> ResultInfo: if isinstance(lhs, ScalarWithInfo): if isinstance(rhs, ScalarWithInfo): return rhs if lhs.risk_key.date == rhs.risk_key.date else lhs.compose((lhs, rhs)) elif isinstance(rhs, SeriesWithInfo): return lhs.compose((lhs,)).combine_first(rhs).sort_index() elif isinstance(lhs, SeriesWithInfo): if isinstance(rhs, SeriesWithInfo): return rhs.combine_first(lhs).sort_index() elif isinstance(rhs, ScalarWithInfo): return rhs.compose((rhs,)).combine_first(lhs).sort_index() elif isinstance(lhs, DataFrameWithInfo): if lhs.index.name != 'date': lhs = lhs.assign(date=lhs.risk_key.date).set_index('date') if isinstance(rhs, DataFrameWithInfo): if rhs.index.name != 'date': rhs = rhs.assign(date=rhs.risk_key.date).set_index('date') return lhs.loc[set(lhs.index) - set(rhs.index)].append(rhs).sort_index() elif isinstance(lhs, MultipleRiskMeasureResult): if isinstance(rhs, MultipleRiskMeasureResult): return lhs + rhs raise RuntimeError(f'{lhs} and {rhs} cannot be composed') def _value_for_date( result: Union[DataFrameWithInfo, SeriesWithInfo], date: Union[Iterable, dt.date] ) -> Union[DataFrameWithInfo, ErrorValue, FloatWithInfo, SeriesWithInfo]: from gs_quant.markets import CloseMarket if result.empty: return result # if the result is a dataframe try to preserve the type # if a dataframe has only 1 row selected it otherwise gets turned into a series raw_value = ( result.loc[[date]] if isinstance(result, DataFrameWithInfo) and isinstance(date, dt.date) else result.loc[date] ) key = result.risk_key risk_key = RiskKey( key.provider, date if isinstance(date, dt.date) else tuple(date), CloseMarket(date=date, location=key.market.location if isinstance(key.market, CloseMarket) else None), key.params, key.scenario, key.risk_measure, ) unit = result.unit error = result.error if isinstance(raw_value, DataFrameWithInfo): raw_value = raw_value.raw_value.set_index('dates') raw_value = raw_value.reset_index(drop=True) if isinstance(date, dt.date) else raw_value elif isinstance(raw_value, float): unit = result.unit.get(date, result.unit) if unit else None return _get_value_with_info(raw_value, risk_key, unit, error) def _get_value_with_info(value, risk_key, unit, error): if isinstance(value, (ErrorValue, UnsupportedValue)): return value elif isinstance(value, pd.DataFrame): return DataFrameWithInfo(value, risk_key=risk_key, unit=unit, error=error) elif isinstance(value, pd.Series): return SeriesWithInfo(value.raw_value, risk_key=risk_key, unit=unit, error=error) else: return FloatWithInfo(risk_key, value, unit=unit, error=error) def _risk_keys_compatible(lhs, rhs) -> bool: from gs_quant.markets import historical_risk_key while isinstance(lhs, MultipleRiskMeasureResult): lhs = next(iter(lhs.values())) while isinstance(rhs, MultipleRiskMeasureResult): rhs = next(iter(rhs.values())) return historical_risk_key(lhs.risk_key).ex_measure == historical_risk_key(rhs.risk_key).ex_measure def _value_for_measure_or_scen(res: dict, item: Union[Iterable, RiskMeasure, Scenario]) -> dict: result = copy.copy(res) if isinstance(item, Iterable): for value in list(result): if value not in item: del result[value] else: for value in list(result): if value != item: del result[value] return result class PricingFuture(Future): __RESULT_SENTINEL = Sentinel('PricingFuture') def __init__(self, result: Optional[Any] = __RESULT_SENTINEL): super().__init__() self.__pricing_context = None if result is not self.__RESULT_SENTINEL: self.set_result(result) else: from gs_quant.markets import PricingContext self.__pricing_context = weakref.ref(PricingContext.current.active_context) def __add__(self, other): if isinstance(other, (int, float)): operand = other elif isinstance(other, self.__class__): operand = other.result() else: raise ValueError(f'Cannot add {self.__class__.__name__} and {other.__class__.name}') return self.__class__(_compose(self.result(), operand)) def __mul__(self, other): if isinstance(other, (int, float)): return self.__class__(self.result() * other) else: raise ValueError('Can only multiply by an int or float') def result(self, timeout=None): """Return the result of the call that the future represents. :param timeout: The number of seconds to wait for the result if the future isn't done. If None, then there is no limit on the wait time. Returns: The result of the call that the future represents. Raises: CancelledError: If the future was cancelled. TimeoutError: If the future didn't finish executing before the given timeout. Exception: If the call raised then that exception will be raised. """ if not self.done(): pricing_context = self.__pricing_context() if self.__pricing_context else None if pricing_context is not None and pricing_context.is_entered: raise RuntimeError('Cannot evaluate results under the same pricing context being used to produce them') return super().result(timeout=timeout) class CompositeResultFuture(PricingFuture): def __init__(self, futures: Iterable[PricingFuture]): super().__init__() self.__futures = tuple(futures) self.__pending = set() for future in self.__futures: if not future.done(): future.add_done_callback(self.__cb) self.__pending.add(future) if not self.__pending: self._set_result() def __getitem__(self, item): return self.result()[item] def __cb(self, future: PricingFuture): self.__pending.discard(future) if not self.__pending: self._set_result() def _set_result(self): self.set_result([f.result() for f in self.__futures]) @property def futures(self) -> Tuple[PricingFuture, ...]: return self.__futures class MultipleRiskMeasureResult(dict): def __init__(self, instrument, dict_values: Iterable): super().__init__(dict_values) self.__instrument = instrument def __getitem__(self, item): if is_instance_or_iterable(item, dt.date): if all(isinstance(v, (DataFrameWithInfo, SeriesWithInfo)) for v in self.values()): return MultipleRiskMeasureResult( self.__instrument, ((k, _value_for_date(v, item)) for k, v in self.items()) ) elif all(isinstance(v, MultipleScenarioResult) for v in self.values()): return MultipleRiskMeasureResult(self.__instrument, ((k, v[item]) for k, v in self.items())) else: raise ValueError('Can only index by date on historical results') elif is_instance_or_iterable(item, Scenario): if all(isinstance(v, MultipleScenarioResult) for v in self.values()): return MultipleRiskMeasureResult( self.__instrument, ((k, _value_for_measure_or_scen(v, item)) for k, v in self.items()) ) else: raise ValueError('Can only index by scenario on multiple scenario results') else: return super().__getitem__(item) def __mul__(self, other): if isinstance(other, (int, float)): return self.__op(op.mul, other) else: return ValueError('Can only multiply by an int or float') def __add__(self, other): if isinstance(other, (int, float)): return self.__op(op.add, other) elif isinstance(other, MultipleRiskMeasureResult): if not _risk_keys_compatible(self, other): raise ValueError('Results must have matching scenario and location') instruments_equal = self.__instrument == other.__instrument self_dt = [list(self.values())[0].risk_key.date] if len(self.dates) == 0 else self.dates other_dt = [list(other.values())[0].risk_key.date] if len(other.dates) == 0 else other.dates dates_overlap = not set(self_dt).isdisjoint(other_dt) if not set(self.keys()).isdisjoint(other.keys()) and instruments_equal and dates_overlap: raise ValueError('Results overlap on risk measures, instruments or dates') all_keys = set(chain(self.keys(), other.keys())) if not instruments_equal: from gs_quant.markets.portfolio import Portfolio return PortfolioRiskResult( Portfolio((self.__instrument, other.__instrument)), all_keys, tuple( MultipleRiskMeasureFuture( r.__instrument, {k: PricingFuture(r[k]) if k in r else None for k in all_keys} ) for r in (self, other) ), ) else: results = {} for result in (self, other): for key in all_keys: if key in result: results[key] = _compose(results[key], result[key]) if key in results else result[key] return MultipleRiskMeasureResult(self.__instrument, results) else: raise ValueError('Can only add instances of MultipleRiskMeasureResult or int, float') def __op(self, operator, operand): values = {} for key, value in self.items(): if isinstance(value, SeriesWithInfo) or isinstance(value, DataFrameWithInfo): new_value = value.copy_with_resultinfo() if not value.empty: new_value.value = operator(value.value, operand) elif isinstance(value, pd.DataFrame) or isinstance(value, pd.Series): new_value = value.copy() new_value.value = operator(value.value, operand) else: new_value = operator(value, operand) values[key] = new_value return MultipleRiskMeasureResult(self.__instrument, values) @property def instrument(self): return self.__instrument @property def dates(self) -> Tuple[dt.date, ...]: dates = set() for value in self.values(): if isinstance(value, (DataFrameWithInfo, SeriesWithInfo)): if all([isinstance(i, dt.date) for i in value.index]): dates.update(value.index) return tuple(sorted(dates)) @property def _multi_scen_key(self) -> Iterable[Scenario]: for value in self.values(): if isinstance(value, MultipleScenarioResult): return tuple(value.scenarios) return tuple() def to_frame( self, values='default', index='default', columns='default', aggfunc="sum", display_options: DisplayOptions = None, ): df = pd.DataFrame.from_records(self._to_records({}, display_options=display_options)) if values is None and index is None and columns is None: return df elif values == 'default' and index == 'default' and columns == 'default': if 'mkt_type' in df.columns: return df.set_index('risk_measure') values, columns, index = get_default_pivots( 'MultipleRiskMeasureResult', multi_measures=True, has_dates='dates' in df.columns, multi_scen='scenario' in df.columns, ) else: values = 'value' if values == 'default' or values == ['value'] else values index = None if index == 'default' else index columns = None if columns == 'default' else columns return pivot_to_frame(df, values, index, columns, aggfunc) def _to_records(self, extra_dict, display_options: DisplayOptions = None): return list( chain.from_iterable( [dict(item, **{'risk_measure': rm}) for item in self[rm]._to_records(extra_dict, display_options)] for rm in self ) ) class MultipleRiskMeasureFuture(CompositeResultFuture): def __init__(self, instrument: InstrumentBase, measures_to_futures: Mapping[RiskMeasure, PricingFuture]): self.__measures_to_futures = measures_to_futures self.__instrument = instrument super().__init__(measures_to_futures.values()) def __add__(self, other): result = self.result() + other.result() if isinstance(other, MultipleRiskMeasureFuture) else other return MultipleRiskMeasureFuture(self.__instrument, {k: PricingFuture(v) for k, v in result.items()}) def _set_result(self): self.set_result( MultipleRiskMeasureResult( self.__instrument, zip(self.__measures_to_futures.keys(), (f.result() for f in self.futures)) ) ) @property def measures_to_futures(self) -> Mapping[RiskMeasure, PricingFuture]: return self.__measures_to_futures class MultipleScenarioFuture(CompositeResultFuture): def __init__(self, instrument: InstrumentBase, scenarios: Iterable[Scenario], futures: Iterable[PricingFuture]): self.__instrument = instrument self.__scenarios = scenarios super().__init__(futures) def _set_result(self): res = next(iter(self.futures)).result() values = ( tuple(res[res['label'] == r]['value'] for r in tuple(unique_everseen(res['label'].values))) if res.index.name == 'date' else tuple(v for v in res['value']) ) val_w_info = tuple(_get_value_with_info(v, res.risk_key, res.unit, res.error) for v in values) self.set_result(MultipleScenarioResult(self.__instrument, {k: v for k, v in zip(self.__scenarios, val_w_info)})) class MultipleScenarioResult(dict): def __init__(self, instrument, dict_values: Iterable): super().__init__(dict_values) self.__instrument = instrument def __getitem__(self, item): if is_instance_or_iterable(item, dt.date): if all(isinstance(v, (DataFrameWithInfo, SeriesWithInfo)) for v in self.values()): return MultipleScenarioResult( self.__instrument, ((k, _value_for_date(v, item)) for k, v in self.items()) ) else: raise ValueError('Can only index by date on historical results') return super().__getitem__(item) def to_frame( self, values='default', index='default', columns='default', aggfunc="sum", display_options: DisplayOptions = None, ): df = pd.DataFrame.from_records(self._to_records({}, display_options=display_options)) if values is None and index is None and columns is None: return df elif values == 'default' and index == 'default' and columns == 'default': if 'mkt_type' in df.columns: return df.set_index('scenario') values, columns, index = get_default_pivots( 'MultipleScenarioResult', has_dates='dates' in df.columns, multi_measures=False, multi_scen=True ) else: values = 'value' if values == 'default' or values == ['value'] else values index = None if index == 'default' else index columns = None if columns == 'default' else columns return pivot_to_frame(df, values, index, columns, aggfunc) @property def instrument(self): return self.__instrument @property def scenarios(self): return self.keys() def _to_records(self, extra_dict, display_options: DisplayOptions = None): return list( chain.from_iterable( [dict(item, **{'scenario': scen}) for item in self[scen]._to_records(extra_dict, display_options)] for scen in self ) ) class HistoricalPricingFuture(CompositeResultFuture): def _set_result(self): results = [f.result() for f in self.futures] base = next((r for r in results if not isinstance(r, (ErrorValue, Exception))), None) if base is None: _logger.error(f'Historical pricing failed: {results[0]}') self.set_result(results[0]) else: if isinstance(base, MultipleRiskMeasureResult): result = MultipleRiskMeasureResult( base.instrument, {k: base[k].compose(r[k] for r in results) for k in base.keys()} ) else: result = base.compose(results) self.set_result(result) class PortfolioPath: def __init__(self, path): self.__path = (path,) if isinstance(path, int) else path def __repr__(self): return repr(self.__path) def __iter__(self): return iter(self.__path) def __len__(self): return len(self.__path) def __add__(self, other): return PortfolioPath(self.__path + other.__path) def __eq__(self, other): return self.__path == other.__path def __hash__(self): return hash(self.__path) def __call__(self, target, rename_to_parent: Optional[bool] = False): parent = None path = list(self.__path) while path: elem = path.pop(0) parent = target if len(self) - len(path) > 1 else None target = target.futures[elem] if isinstance(target, CompositeResultFuture) else target[elem] if isinstance(target, PricingFuture) and path: target = target.result() if rename_to_parent and parent and getattr(parent, 'name', None) and not isinstance(target, InstrumentBase): target = copy.copy(target) target.name = parent.name return target @property def path(self): return self.__path class PortfolioRiskResult(CompositeResultFuture): def __init__(self, portfolio, risk_measures: Iterable[RiskMeasure], futures: Iterable[PricingFuture]): super().__init__(futures) self.__portfolio = portfolio self.__risk_measures = tuple(risk_measures) def __getitem__(self, item): futures = [] if is_instance_or_iterable(item, RiskMeasure): '''Slicing a list of risk measures''' if isinstance(item, Iterable): if any(it not in self.risk_measures for it in item): raise ValueError('{} not computed'.format(item)) else: if item not in self.risk_measures: raise ValueError('{} not computed'.format(item)) if len(self.risk_measures) == 1: return self else: for idx, priceable in enumerate(self.portfolio): result = self.__result(PortfolioPath(idx)) if isinstance(result, PortfolioRiskResult): futures.append(result[item]) else: futures.append( MultipleRiskMeasureFuture( priceable, {k: PricingFuture(v) for k, v in _value_for_measure_or_scen(result, item).items()}, ) ) risk_measure = tuple(item) if isinstance(item, Iterable) else (item,) return PortfolioRiskResult(self.__portfolio, risk_measure, futures) elif is_instance_or_iterable(item, Scenario): '''Slicing a list of scenarios''' if isinstance(item, Iterable) and any(it not in self._multi_scen_key for it in item): raise ValueError('{} not computed'.format(item)) else: if item not in self._multi_scen_key: raise ValueError('{} not computed'.format(item)) if len(self._multi_scen_key) == 0: # field not used if not multiscenario return self else: for idx, priceable in enumerate(self.portfolio): result = self.__result(PortfolioPath(idx)) if isinstance(result, PortfolioRiskResult): futures.append(result[item]) elif isinstance(result, MultipleRiskMeasureResult): futures.append( MultipleRiskMeasureFuture( priceable, {k: PricingFuture(_value_for_measure_or_scen(result[k], item)) for k in result}, ) ) elif isinstance(result, MultipleScenarioResult): futures.append(PricingFuture(_value_for_measure_or_scen(result, item))) return PortfolioRiskResult(self.__portfolio, self.risk_measures, futures) elif is_instance_or_iterable(item, dt.date): for idx, _ in enumerate(self.portfolio): result = self.__result(PortfolioPath(idx)) if isinstance(result, (MultipleRiskMeasureResult, PortfolioRiskResult, MultipleScenarioResult)): futures.append(PricingFuture(result[item])) elif isinstance(result, (DataFrameWithInfo, SeriesWithInfo)): futures.append(PricingFuture(_value_for_date(result, item))) else: raise RuntimeError('Can only index by date on historical results') return PortfolioRiskResult(self.__portfolio, self.risk_measures, futures) elif is_iterable(item, InstrumentBase): '''Slicing a list/tuple of instruments (not an Portfolio iterable)''' return self.subset(item) # Inputs from excel always becomes a list # Catch list length = 1 so that it doesn't return a sub-PortfolioRiskResult elif isinstance(item, list) and len(item) == 1: return self.__results(items=item[0]) else: return self.__results(items=item) def __contains__(self, item): if isinstance(item, RiskMeasure): return item in self.__risk_measures elif isinstance(item, dt.date): return item in self.dates else: return item in self.__portfolio def __repr__(self): ret = f'{self.__risk_measures} Results' if self.__portfolio.name: ret += f' for {self.__portfolio.name}' return ret + f' ({len(self)})' def __len__(self): return len(self.futures) def __iter__(self): return iter(self.__results()) def __mul__(self, other): if isinstance(other, (int, float)): return PortfolioRiskResult(self.__portfolio, self.__risk_measures, [f * other for f in self.futures]) else: return ValueError('Can only multiply by an int or float') def __add__(self, other): def as_multiple_result_futures(portfolio_result): if len(portfolio_result.__risk_measures) > 1: return portfolio_result mr_futures = [] for p, f in zip(portfolio_result.__portfolio, portfolio_result.futures): if isinstance(f, PortfolioRiskResult): mr_futures.append(as_multiple_result_futures(f)) elif isinstance(f, MultipleRiskMeasureFuture): mr_futures.append(f) else: mr_futures.append(MultipleRiskMeasureFuture(p, {portfolio_result.__risk_measures[0]: f})) return PortfolioRiskResult(portfolio_result.__portfolio, portfolio_result.__risk_measures, mr_futures) def set_value(dest_result, src_result, src_risk_measure): for priceable, future in zip(dest_result.__portfolio, dest_result.futures): if isinstance(future, PortfolioRiskResult): set_value(future, src_result, src_risk_measure) else: try: value = src_result[priceable] value = value[src_risk_measure] if isinstance(value, MultipleRiskMeasureResult) else value future.result()[src_risk_measure] = value except KeyError: pass def first_value(portfolio_result): if len(portfolio_result.__risk_measures) > 1: return next(iter(portfolio_result[next(iter(portfolio_result.portfolio.all_instruments))].values())) else: return portfolio_result[next(iter(portfolio_result.__portfolio.all_instruments))] if isinstance(other, (int, float)): return PortfolioRiskResult(self.__portfolio, self.__risk_measures, [f + other for f in self.futures]) elif isinstance(other, PortfolioRiskResult): if not _risk_keys_compatible(first_value(self), first_value(other)) and not set( self.__portfolio.all_instruments ).isdisjoint(other.__portfolio.all_instruments): raise ValueError('Results must have matching scenario and location') self_dt = (first_value(self).risk_key.date,) if len(self.dates) == 0 else self.dates other_dt = (first_value(other).risk_key.date,) if len(other.dates) == 0 else other.dates dates_overlap = not set(self_dt).isdisjoint(other_dt) if ( not set(self.__risk_measures).isdisjoint(other.__risk_measures) and dates_overlap and not set(self.__portfolio.all_instruments).isdisjoint(other.__portfolio.all_instruments) ): raise ValueError('Results overlap on risk measures, instruments or dates') self_futures = as_multiple_result_futures(self).futures other_futures = as_multiple_result_futures(other).futures if self.__portfolio is other.__portfolio or self.__portfolio == other.__portfolio: portfolio = self.__portfolio futures = [future + other_future for future, other_future in zip(self_futures, other_futures)] else: portfolio = self.__portfolio + other.__portfolio futures = self_futures + other_futures ret = PortfolioRiskResult(portfolio, set(chain(self.risk_measures, other.risk_measures)), futures) if portfolio is not self.__portfolio and len(ret.risk_measures) > 1: # Now fill in overlapping values for dest, src in ((self, other), (other, self)): for risk_measure in (m for m in src.risk_measures if dest == self or m not in dest.risk_measures): set_value(ret, src, risk_measure) return ret else: raise ValueError('Can only add instances of PortfolioRiskResult or int, float') @property def portfolio(self): return self.__portfolio @property def risk_measures(self) -> Tuple[RiskMeasure, ...]: return self.__risk_measures @property def dates(self) -> Tuple[dt.date, ...]: dates = set() for result in self.__results(): if isinstance(result, (MultipleRiskMeasureResult, PortfolioRiskResult)): if all([isinstance(i, dt.date) for i in result.dates]): dates.update(result.dates) elif isinstance(result, (pd.DataFrame, pd.Series)): if all([isinstance(i, dt.date) for i in result.index]): dates.update(result.index) try: return tuple(sorted(dates)) except TypeError: return tuple() @property def _multi_scen_key(self) -> Iterable[Scenario]: for result in self.__results(): if isinstance(result, MultipleScenarioResult): return tuple(result.scenarios) elif isinstance(result, (MultipleRiskMeasureResult, PortfolioRiskResult)): return result._multi_scen_key return tuple() def result(self, timeout: Optional[int] = None): super().result(timeout=timeout) return self def subset(self, items: Iterable[Union[int, str, PortfolioPath, Priceable]], name: Optional[str] = None): paths = tuple(chain.from_iterable((i,) if isinstance(i, PortfolioPath) else self.__paths(i) for i in items)) sub_portfolio = self.__portfolio.subset(paths, name=name) return PortfolioRiskResult(sub_portfolio, self.risk_measures, [p(self.futures) for p in paths]) def transform(self, risk_transformation: Transformer = None): if risk_transformation is None: return self elif len(self.__risk_measures) > 1: return MultipleRiskMeasureResult( self.portfolio, ((r, self[r].transform(risk_transformation)) for r in self.__risk_measures) ) elif len(self.__risk_measures) == 1: flattened_results = risk_transformation.apply(self.__results()) futures = [] for result in flattened_results: transformed_future = PricingFuture() transformed_future.set_result(result) transformed_future.done() futures.append(transformed_future) return PortfolioRiskResult(self.portfolio, self.risk_measures, futures) else: return self def aggregate( self, allow_mismatch_risk_keys=False, allow_heterogeneous_types=False ) -> Union[float, pd.DataFrame, pd.Series, MultipleRiskMeasureResult]: if len(self.__risk_measures) > 1: return MultipleRiskMeasureResult(self.portfolio, ((r, self[r].aggregate()) for r in self.__risk_measures)) else: return aggregate_results( self.__results(), allow_mismatch_risk_keys=allow_mismatch_risk_keys, allow_heterogeneous_types=allow_heterogeneous_types, ) def _to_records(self, display_options: DisplayOptions = None): def get_records(rec): temp = [] for f in rec.futures: if ( isinstance(f.result(), ResultInfo) or isinstance(f.result(), MultipleRiskMeasureResult) or isinstance(f.result(), MultipleScenarioResult) ): temp.append(f.result()) else: temp.extend(get_records(f.result())) return temp future_records = get_records(self) portfolio_records = self.__portfolio._to_records() records = [] if len(future_records) == len(portfolio_records): for i in range(len(future_records)): records.extend(future_records[i]._to_records({**portfolio_records[i]}, display_options)) return records def to_frame( self, values='default', index='default', columns='default', aggfunc="sum", display_options: DisplayOptions = None, ): final_records = self._to_records(display_options=display_options) if len(final_records) > 0: ori_df = pd.DataFrame.from_records(final_records) if 'risk_measure' not in ori_df.columns.values: ori_df['risk_measure'] = self.risk_measures[0] else: return df_cols = list(ori_df.columns.values) # fill n/a values for different sub-portfolio depths cols_except_value = [c for c in df_cols if c != 'value'] ori_df[cols_except_value] = ori_df[cols_except_value].fillna("N/A") has_dt = True if 'dates' in df_cols else False other_cols = sorted([p for p in df_cols if 'portfolio' in p]) + ['instrument_name', 'risk_measure'] other_cols = other_cols + ['dates'] if has_dt else other_cols val_cols = [col for col in df_cols if col not in other_cols] if 'value' in val_cols and val_cols[-1] != 'value': val_cols = [col for col in val_cols if col != 'value'] + ['value'] sorted_col = other_cols + val_cols ori_df = ori_df[sorted_col] if values is None and index is None and columns is None: # to_frame(None, None, None) return ori_df elif values == 'default' and index == 'default' and columns == 'default': # to_frame() multi_scen = len(self._multi_scen_key) > 1 has_bucketed = True if 'mkt_type' in df_cols else False has_cashflows = True if 'payment_amount' in df_cols else False multi_rm = True if len(self.risk_measures) > 1 else False port_depth_one = True if len(max(self.portfolio.all_paths, key=len)) == 1 else False if has_bucketed or has_cashflows: return ori_df.set_index(other_cols) else: values, index, columns = get_default_pivots( 'PortfolioRiskResult', has_dates=has_dt, multi_measures=multi_rm, simple_port=port_depth_one, multi_scen=multi_scen, ori_cols=df_cols, ) else: # user defined pivoting values = 'value' if values == 'default' or values == ['value'] else values return pivot_to_frame(ori_df, values, index, columns, aggfunc) def __paths(self, items: Union[int, slice, str, Priceable]) -> Tuple[PortfolioPath, ...]: if isinstance(items, int): return (PortfolioPath(items),) elif isinstance(items, slice): return tuple(PortfolioPath(i) for i in range(len(self.__portfolio))[items]) elif isinstance(items, (str, Priceable)): paths = self.__portfolio.paths(items) # will enter in here only if trying to slice an unresolved portfolio with a resolved instrument if not paths and isinstance(items, InstrumentBase) and items.unresolved: paths = self.__portfolio.paths(items.unresolved) if not paths: raise KeyError(f'{items} not in portfolio') key = items.resolution_key.ex_measure paths = tuple(p for p in paths if self.__result(p, self.risk_measures[0]).risk_key.ex_measure == key) if not paths: raise KeyError(f'Cannot slice {items} which is resolved in a different pricing context') return paths def __results(self, items: Optional[Union[int, slice, str, Priceable]] = None): if items is None: return tuple(self.__result(p) for p in self.__portfolio.all_paths) paths = self.__paths(items) if not paths: raise KeyError(f'{items}') return self.__result(paths[0]) if not isinstance(items, slice) else self.subset(paths) def __result(self, path: PortfolioPath, risk_measure: Optional[RiskMeasure] = None): res = path(self.futures).result() if len(self.risk_measures) == 1 and not risk_measure: risk_measure = self.risk_measures[0] return ( res[risk_measure] if risk_measure and isinstance(res, (MultipleRiskMeasureResult, PortfolioRiskResult)) else res ) def get(self, item, default): try: value = self.__getitem__(item) except (KeyError, ValueError): value = default return value ================================================ FILE: gs_quant/risk/scenario_utils.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from gs_quant.markets.securities import AssetIdentifier, SecurityMaster from gs_quant.risk.scenarios import MarketDataVolShockScenario from gs_quant.data import Dataset def build_eq_vol_scenario_intraday( asset_name: str, source_dataset: str, ref_spot: float = None, asset_name_type: AssetIdentifier = AssetIdentifier.REUTERS_ID, start_time: dt.datetime = dt.datetime.now() - dt.timedelta(hours=1), end_time: dt.datetime = dt.datetime.now(), ) -> MarketDataVolShockScenario: asset = SecurityMaster.get_asset(asset_name, asset_name_type) vol_dataset = Dataset(source_dataset) vol_data = vol_dataset.get_data( assetId=[asset.get_marquee_id()], strikeReference='forward', startTime=start_time, endTime=end_time ) asset_ric = asset.get_identifier(AssetIdentifier.REUTERS_ID) return MarketDataVolShockScenario.from_dataframe(asset_ric, vol_data, ref_spot) def build_eq_vol_scenario_eod( asset_name: str, source_dataset: str, ref_spot: float = None, asset_name_type: AssetIdentifier = AssetIdentifier.REUTERS_ID, vol_date: dt.date = dt.date.today(), ) -> MarketDataVolShockScenario: asset = SecurityMaster.get_asset(asset_name, asset_name_type) vol_dataset = Dataset(source_dataset) vol_data = vol_dataset.get_data( assetId=[asset.get_marquee_id()], strikeReference='forward', startDate=vol_date, endDate=vol_date ) asset_ric = asset.get_identifier(AssetIdentifier.REUTERS_ID) return MarketDataVolShockScenario.from_dataframe(asset_ric, vol_data, ref_spot) ================================================ FILE: gs_quant/risk/scenarios.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from typing import Mapping, Optional import pandas as pd from gs_quant.target.risk import ( MarketDataPattern, MarketDataShock, MarketDataPatternAndShock, MarketDataShockBasedScenario as __MarketDataShockBasedScenario, MarketDataVolShockScenario as __MarketDataVolShockScenario, MarketDataVolSlice, MarketDataShockType, ) class MarketDataShockBasedScenario(__MarketDataShockBasedScenario): def __init__(self, shocks: Mapping[MarketDataPattern, MarketDataShock], name: Optional[str] = None): super().__init__(tuple(MarketDataPatternAndShock(p, s) for p, s in shocks.items()), name=name) class MarketDataVolShockScenario(__MarketDataVolShockScenario): @classmethod def from_dataframe(cls, asset_ric: str, df: pd.DataFrame, ref_spot: float = None, name=None): """ Create a MarketDataVolShockScenario using an input DataFrame containing expiry dates, strikes and vol levels :param asset_ric: the RIC of the asset :param df: input data frame. Expects a DataFrame indexed by date/time and containing columns expirationDate, absoluteStrike and impliedVolatility. :param ref_spot: the current reference spot level :param name: name :return: MarketDataVolShockScenario """ last_datetime = max(list(df.index)) df_filtered = df.loc[df.index == last_datetime] df_grouped = df_filtered.groupby(['expirationDate']) vol_slices = [] for key in df_grouped.groups: value = df_grouped.get_group(key) df_sorted = value.sort_values(['absoluteStrike']) strikes = list(df_sorted.absoluteStrike) levels = list(df_sorted.impliedVolatility) vol_slice = MarketDataVolSlice(key.date(), strikes, levels) vol_slices.append(vol_slice) scenario = MarketDataVolShockScenario( MarketDataPattern('Eq Vol', asset_ric), MarketDataShockType.Override, vol_slices, ref_spot, name=name ) return scenario ================================================ FILE: gs_quant/risk/transform.py ================================================ """ Copyright 2022 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from abc import ABC, abstractmethod from dataclasses import dataclass from dataclasses_json import dataclass_json from typing import Generic, Callable, Sequence, Any, TypeVar, Iterable, Union, Optional from gs_quant.risk.core import ResultType, DataFrameWithInfo, SeriesWithInfo, FloatWithInfo _InputT = TypeVar('_InputT') _ResultT = TypeVar('_ResultT') class Transformer(ABC, Generic[_InputT, _ResultT]): @abstractmethod def apply(self, data: _InputT, *args, **kwargs) -> _ResultT: pass class GenericResultWithInfoTransformer(Transformer[ResultType, ResultType]): def __init__(self, fn: Callable[[ResultType, Sequence[Any]], ResultType]): self.__fn = fn def apply(self, data: ResultType, *args, **kwargs) -> ResultType: return self.__fn(data, *args, **kwargs) @dataclass_json @dataclass class ResultWithInfoAggregator(Transformer[Iterable[ResultType], FloatWithInfo]): risk_col: str = 'value' filter_coord: Optional[object] = None def apply( self, results: Iterable[Union[float, FloatWithInfo, SeriesWithInfo, DataFrameWithInfo]], *args, **kwargs ) -> Iterable[Union[float, FloatWithInfo]]: flattened_results = [] for result in results: if isinstance(result, float): flattened_results.append(result) else: if isinstance(result, FloatWithInfo): val = result.raw_value elif isinstance(result, SeriesWithInfo): val = getattr(result, self.risk_col).sum() elif isinstance(result, DataFrameWithInfo): if result.empty: val = 0 elif self.filter_coord is not None: df = result.filter_by_coord(self.filter_coord) val = getattr(df, self.risk_col).sum() else: val = getattr(result, self.risk_col).sum() else: raise ValueError(f'Aggregation of {type(result).__name__} not currently supported.') risk_key = result.risk_key unit = result.unit error = result.error flattened_results.append(FloatWithInfo(value=val, risk_key=risk_key, unit=unit, error=error)) return flattened_results ================================================ FILE: gs_quant/session.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on ans "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import asyncio import inspect import itertools import json import logging import os import ssl import sys from abc import abstractmethod from configparser import ConfigParser from contextlib import asynccontextmanager from enum import Enum, auto, unique from typing import Optional, Union, Iterable, Any import backoff import certifi import httpx import msgpack import pandas as pd import requests import requests.adapters import requests.cookies import urllib3 from gs_quant import version as APP_VERSION from gs_quant.base import Base from gs_quant.context_base import ContextBase, nullcontext from gs_quant.errors import MqError, MqRequestError, MqAuthenticationError, MqUninitialisedError, error_builder from gs_quant.json_encoder import JSONEncoder, encode_default from gs_quant.tracing import Tracer, TracingScope, Tags logger = logging.getLogger(__name__) API_VERSION = 'v1' DEFAULT_APPLICATION = 'gs-quant' DEFAULT_TIMEOUT = 65 @unique class Environment(Enum): DEV = auto() QA = auto() PROD = auto() class CustomHttpAdapter(requests.adapters.HTTPAdapter): # "Transport adapter" that allows us to use custom ssl_context. __ssl_ctx = None @classmethod def ssl_context(cls) -> ssl.SSLContext: if cls.__ssl_ctx is None: cls.__ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) cls.__ssl_ctx.check_hostname = False cls.__ssl_ctx.verify_mode = 0 cls.__ssl_ctx.options |= 0x4 # OP_LEGACY_SERVER_CONNECT cls.__ssl_ctx.load_default_certs() cls.__ssl_ctx.load_verify_locations(certifi.where()) return cls.__ssl_ctx def init_poolmanager(self, connections, maxsize=100, block=False): self.poolmanager = urllib3.poolmanager.PoolManager( num_pools=connections, maxsize=maxsize, block=block, ssl_context=self.ssl_context() ) class Domain: MDS_US_EAST = "MdsDomainEast" MDS_WEB = "MdsWebDomain" APP = "AppDomain" class _SyncSessionAPI: """Synchronous HTTP interface for GsSession. Access via ``GsSession.current.sync``.""" def __init__(self, session: 'GsSession'): self._session = session def get( self, path: str, payload: Optional[Union[dict, 'Base']] = None, request_headers: Optional[dict] = None, cls: Optional[type] = None, include_version: Optional[bool] = True, timeout: Optional[int] = DEFAULT_TIMEOUT, return_request_id: Optional[bool] = False, domain: Optional[str] = None, ) -> Union['Base', tuple, dict]: return self._session._get( path, payload=payload, request_headers=request_headers, cls=cls, include_version=include_version, timeout=timeout, return_request_id=return_request_id, domain=domain, ) def post( self, path: str, payload: Optional[Union[dict, bytes, 'Base', 'pd.DataFrame']] = None, request_headers: Optional[dict] = None, cls: Optional[type] = None, include_version: Optional[bool] = True, timeout: Optional[int] = DEFAULT_TIMEOUT, return_request_id: Optional[bool] = False, domain: Optional[str] = None, ) -> Union['Base', tuple, dict]: return self._session._post( path, payload=payload, request_headers=request_headers, cls=cls, include_version=include_version, timeout=timeout, return_request_id=return_request_id, domain=domain, ) def put( self, path: str, payload: Optional[Union[dict, 'Base']] = None, request_headers: Optional[dict] = None, cls: Optional[type] = None, include_version: Optional[bool] = True, timeout: Optional[int] = DEFAULT_TIMEOUT, return_request_id: Optional[bool] = False, domain: Optional[str] = None, ) -> Union['Base', tuple, dict]: return self._session._put( path, payload=payload, request_headers=request_headers, cls=cls, include_version=include_version, timeout=timeout, return_request_id=return_request_id, domain=domain, ) def delete( self, path: str, payload: Optional[Union[dict, 'Base']] = None, request_headers: Optional[dict] = None, cls: Optional[type] = None, include_version: Optional[bool] = True, timeout: Optional[int] = DEFAULT_TIMEOUT, return_request_id: Optional[bool] = False, use_body: Optional[bool] = False, domain: Optional[str] = None, ) -> Union['Base', tuple, dict]: return self._session._delete( path, payload=payload, request_headers=request_headers, cls=cls, include_version=include_version, timeout=timeout, return_request_id=return_request_id, use_body=use_body, domain=domain, ) class _AsyncSessionAPI: """Asynchronous HTTP interface for GsSession. Access via ``GsSession.current.async_``.""" def __init__(self, session: 'GsSession'): self._session = session async def get( self, path: str, payload: Optional[Union[dict, 'Base']] = None, request_headers: Optional[dict] = None, cls: Optional[type] = None, include_version: Optional[bool] = True, timeout: Optional[int] = DEFAULT_TIMEOUT, return_request_id: Optional[bool] = False, domain: Optional[str] = None, ) -> Union['Base', tuple, dict]: return await self._session._get_async( path, payload=payload, request_headers=request_headers, cls=cls, include_version=include_version, timeout=timeout, return_request_id=return_request_id, domain=domain, ) async def post( self, path: str, payload: Optional[Union[dict, bytes, 'Base', 'pd.DataFrame']] = None, request_headers: Optional[dict] = None, cls: Optional[type] = None, include_version: Optional[bool] = True, timeout: Optional[int] = DEFAULT_TIMEOUT, return_request_id: Optional[bool] = False, domain: Optional[str] = None, ) -> Union['Base', tuple, dict]: return await self._session._post_async( path, payload=payload, request_headers=request_headers, cls=cls, include_version=include_version, timeout=timeout, return_request_id=return_request_id, domain=domain, ) async def put( self, path: str, payload: Optional[Union[dict, 'Base']] = None, request_headers: Optional[dict] = None, cls: Optional[type] = None, include_version: Optional[bool] = True, timeout: Optional[int] = DEFAULT_TIMEOUT, return_request_id: Optional[bool] = False, domain: Optional[str] = None, ) -> Union['Base', tuple, dict]: return await self._session._put_async( path, payload=payload, request_headers=request_headers, cls=cls, include_version=include_version, timeout=timeout, return_request_id=return_request_id, domain=domain, ) async def delete( self, path: str, payload: Optional[Union[dict, 'Base']] = None, request_headers: Optional[dict] = None, cls: Optional[type] = None, include_version: Optional[bool] = True, timeout: Optional[int] = DEFAULT_TIMEOUT, return_request_id: Optional[bool] = False, use_body: Optional[bool] = False, domain: Optional[str] = None, ) -> Union['Base', tuple, dict]: return await self._session._delete_async( path, payload=payload, request_headers=request_headers, cls=cls, include_version=include_version, timeout=timeout, return_request_id=return_request_id, use_body=use_body, domain=domain, ) @asynccontextmanager async def connect_websocket( self, path: str, headers: Optional[dict] = None, include_version=True, domain: Optional[str] = None, **kwargs: Any, ): async with self._session._connect_websocket( path, headers=headers, include_version=include_version, domain=domain, **kwargs ) as ws: yield ws class GsSession(ContextBase): __config = None class Scopes(Enum): READ_CONTENT = 'read_content' READ_FINANCIAL_DATA = 'read_financial_data' READ_PRODUCT_DATA = 'read_product_data' READ_USER_PROFILE = 'read_user_profile' MODIFY_CONTENT = 'modify_content' MODIFY_FINANCIAL_DATA = 'modify_financial_data' MODIFY_PRODUCT_DATA = 'modify_product_data' MODIFY_USER_PROFILE = 'modify_user_profile' RUN_ANALYTICS = 'run_analytics' EXECUTE_TRADES = 'execute_trades' @classmethod def get_default(cls): return ( cls.READ_CONTENT.value, cls.READ_PRODUCT_DATA.value, cls.READ_FINANCIAL_DATA.value, cls.READ_USER_PROFILE.value, ) def __init__( self, domain: str, environment: str = None, api_version: str = API_VERSION, application: str = DEFAULT_APPLICATION, verify=True, http_adapter: requests.adapters.HTTPAdapter = None, application_version=APP_VERSION, proxies=None, redirect_to_mds=False, ): super().__init__() self._session = None self._session_async = None self._sync_api: Optional['_SyncSessionAPI'] = None self._async_api: Optional['_AsyncSessionAPI'] = None self.domain = domain self._orig_domain = domain if environment in tuple(x.name for x in Environment): self.environment = Environment[environment] elif isinstance(domain, Environment): self.environment = domain else: self.environment = Environment.DEV self.api_version = api_version self.application = application self.verify = verify if http_adapter is None: if ssl.OPENSSL_VERSION_INFO >= (3, 0, 0): self.http_adapter = CustomHttpAdapter() else: self.http_adapter = requests.adapters.HTTPAdapter(pool_maxsize=100) else: self.http_adapter = http_adapter self.application_version = application_version self.proxies = proxies self.mounts = {key: httpx.HTTPTransport(proxy=val) for key, val in proxies} if proxies else None self.redirect_to_mds = redirect_to_mds @backoff.on_exception( lambda: backoff.expo(factor=2), (requests.exceptions.HTTPError, requests.exceptions.Timeout), max_tries=5 ) @backoff.on_predicate(lambda: backoff.expo(factor=2), lambda x: x.status_code in (500, 502, 503, 504), max_tries=5) @abstractmethod def _authenticate(self): raise NotImplementedError("Must implement _authenticate") def _authenticate_async(self): if self._has_async_session(): self._session_async.headers.update([(k, v) for k, v in self._session.headers.items()]) for cookie in self._session.cookies: self._session_async.cookies.set(cookie.name, cookie.value, domain=cookie.domain) def _authenticate_all_sessions(self): self._authenticate() self._authenticate_async() def _on_enter(self): self.__close_on_exit = self._session is None if not self._session: self.init() def _on_exit(self, exc_type, exc_val, exc_tb): if self.__close_on_exit: self._session = None self._session_async = None def _has_async_session(self) -> bool: return self._session_async and not self._session_async.is_closed def _init_async(self): if not self._has_async_session(): self._session_async = httpx.AsyncClient( follow_redirects=True, verify=CustomHttpAdapter.ssl_context(), mounts=self.mounts ) self._session_async.headers.update({'X-Application': self.application}) self._session_async.headers.update({'X-Version': self.application_version}) self._authenticate_async() async def _on_aenter(self): self.__close_on_exit = self._session is None self.__close_on_exit_async = not self._has_async_session() if self.__close_on_exit: self.init() if self.__close_on_exit_async: self._init_async() async def _on_aexit(self, exc_type, exc_val, exc_tb): if self.__close_on_exit_async: if self._has_async_session(): await self._session_async.aclose() self._session_async = None if self.__close_on_exit: self._session = None def init(self): if not self._session: self._session = requests.Session() if self.http_adapter is not None: self._session.mount('https://', self.http_adapter) if self.proxies is not None: self._session.proxies = self.proxies self._session.verify = self.verify self._session.headers.update({'X-Application': self.application}) self._session.headers.update({'X-Version': self.application_version}) self._authenticate() if self._orig_domain == Domain.APP: self.post_to_activity_service() def close(self): self._session: requests.Session if self._session: # don't close a shared adapter if self.http_adapter is None: self._session.close() self._session = None if self._session_async: try: asyncio.run(self._close_async()) except Exception: pass async def _close_async(self): if self._session_async: await self._session_async.aclose() self._session_async = None def __del__(self): self.close() @staticmethod def __unpack(results: Union[dict, list], cls: type) -> Union[Base, tuple, dict]: if issubclass(cls, Base): if isinstance(results, list): return tuple(None if r is None else cls.from_dict(r) for r in results) else: return None if results is None else cls.from_dict(results) else: if isinstance(results, list): return tuple(cls(**r) for r in results) else: return cls(**results) def _build_url(self, domain: Optional[str], path: str, include_version: Optional[bool]): if not domain: domain = self.domain url = '{}{}{}'.format(domain, '/' + self.api_version if include_version else '', path) return url def _build_request_params( self, method: str, path: str, url: str, payload: Optional[Union[dict, str, bytes, Base, pd.DataFrame]], request_headers: Optional[dict], timeout: Optional[int], use_body: bool, data_key: str, tracing_scope: Optional[TracingScope], ) -> dict: is_dataframe = isinstance(payload, pd.DataFrame) if not is_dataframe: payload = payload or {} kwargs = {'timeout': timeout} if tracing_scope: tracing_scope.span.set_tag('path', path) tracing_scope.span.set_tag('timeout', timeout) tracing_scope.span.set_tag(Tags.HTTP_URL, url) tracing_scope.span.set_tag(Tags.HTTP_METHOD, method) tracing_scope.span.set_tag('span.kind', 'client') if method in ('GET', 'DELETE') and not use_body: kwargs['params'] = payload if tracing_scope or request_headers: headers = self._session.headers.copy() if request_headers: headers.update(request_headers) if tracing_scope: Tracer.inject(headers) kwargs['headers'] = headers elif method in ('POST', 'PUT') or (method in ('GET', 'DELETE') and use_body): headers = self._session.headers.copy() if request_headers: headers.update(request_headers) if tracing_scope: Tracer.inject(headers) if 'Content-Type' not in headers: headers.update({'Content-Type': 'application/json; charset=utf-8'}) if tracing_scope: tracing_scope.span.set_tag('request.content.type', headers.get('Content-Type')) use_msgpack = headers.get('Content-Type') == 'application/x-msgpack' if use_msgpack: headers['Accept'] = headers.get('Content-Type') kwargs['headers'] = headers if is_dataframe or payload: kwargs[data_key] = ( payload if isinstance(payload, (str, bytes)) else msgpack.dumps(payload, default=encode_default) if use_msgpack else json.dumps(payload, cls=JSONEncoder) ) else: raise MqError('not implemented') return kwargs def _parse_response( self, request_id, response, method: str, url: str, cls: Optional[type], return_request_id: Optional[bool] ): if not 199 < response.status_code < 300: reason = response.reason if hasattr(response, 'reason') else response.reason_phrase err_msg = reason if response.headers.get('Content-Type') == 'text/html' else f'{reason}: {response.text}' raise error_builder(response.status_code, err_msg, context=f'{request_id}: {method} {url}') elif 'Content-Type' in response.headers: if 'application/x-msgpack' in response.headers['Content-Type']: ret = msgpack.unpackb(response.content, raw=False) elif 'application/json' in response.headers['Content-Type']: ret = json.loads(response.text) else: ret = {'raw': response} if cls and ret: if isinstance(ret, dict) and 'results' in ret: ret['results'] = self.__unpack(ret['results'], cls) else: ret = self.__unpack(ret, cls) return (ret, request_id) if return_request_id else ret else: ret = {'raw': response} if return_request_id: ret['request_id'] = request_id return ret def __request( self, method: str, path: str, payload: Optional[Union[dict, str, bytes, Base, pd.DataFrame]] = None, request_headers: Optional[dict] = None, cls: Optional[type] = None, try_auth: Optional[bool] = True, include_version: Optional[bool] = True, timeout: Optional[int] = DEFAULT_TIMEOUT, return_request_id: Optional[bool] = False, use_body: bool = False, domain: Optional[str] = None, ) -> Union[Base, tuple, dict]: span = Tracer.active_span() url = self._build_url(domain, path, include_version) tracer = Tracer(url) if span and span.is_recording() else nullcontext() with tracer as scope: kwargs = self._build_request_params( method, path, url, payload, request_headers, timeout, use_body, "data", scope ) response = self._session.request(method, url, **kwargs) request_id = response.headers.get('x-dash-requestid') logger.debug('Handling response for [Request ID]: %s [Method]: %s [URL]: %s', request_id, method, url) if scope: scope.span.set_tag(Tags.HTTP_STATUS_CODE, response.status_code) if response.status_code > 399: scope.span.set_tag('error', True) scope.span.set_tag('dash.request.id', request_id) scope.span.set_tag('response.content.type', response.headers.get('Content-Type')) if response.status_code == 401: # Expired token or other authorization issue if not try_auth: raise MqRequestError(response.status_code, response.text, context=f'{request_id}: {method} {url}') self._authenticate() return self.__request( method, path, payload=payload, request_headers=request_headers, cls=cls, try_auth=False, include_version=include_version, timeout=timeout, return_request_id=return_request_id, use_body=use_body, domain=domain, ) return self._parse_response(request_id, response, method, url, cls, return_request_id) async def __request_async( self, method: str, path: str, payload: Optional[Union[dict, str, bytes, Base, pd.DataFrame]] = None, request_headers: Optional[dict] = None, cls: Optional[type] = None, try_auth: Optional[bool] = True, include_version: Optional[bool] = True, timeout: Optional[int] = DEFAULT_TIMEOUT, return_request_id: Optional[bool] = False, use_body: bool = False, domain: Optional[str] = None, ) -> Union[Base, tuple, dict]: self._init_async() span = Tracer.active_span() url = self._build_url(domain, path, include_version) tracer = Tracer(f'http:/{path}') if span and span.is_recording() else nullcontext() with tracer as scope: kwargs = self._build_request_params( method, path, url, payload, request_headers, timeout, use_body, "content", scope ) response = await self._session_async.request(method, url, **kwargs) request_id = response.headers.get('x-dash-requestid') if scope: scope.span.set_tag(Tags.HTTP_STATUS_CODE, response.status_code) scope.span.set_tag('dash.request.id', request_id) logger.debug('Handling response for [Request ID]: %s [Method]: %s [URL]: %s', request_id, method, url) if response.status_code == 401: # Expired token or other authorization issue logger.debug(f"request: {request_id}, status: {response.status_code}, message: {response.text}, URL: {url}") if not try_auth: raise MqRequestError(response.status_code, response.text, context=f'{request_id}: {method} {url}') self._authenticate_all_sessions() res = await self.__request_async( method, path, payload=payload, request_headers=request_headers, cls=cls, try_auth=False, include_version=include_version, timeout=timeout, return_request_id=return_request_id, use_body=use_body, domain=domain, ) return res return self._parse_response(request_id, response, method, url, cls, return_request_id) def _get( self, path: str, payload: Optional[Union[dict, Base]] = None, request_headers: Optional[dict] = None, cls: Optional[type] = None, include_version: Optional[bool] = True, timeout: Optional[int] = DEFAULT_TIMEOUT, return_request_id: Optional[bool] = False, domain: Optional[str] = None, ) -> Union[Base, tuple, dict]: return self.__request( 'GET', path, payload=payload, request_headers=request_headers, cls=cls, include_version=include_version, timeout=timeout, return_request_id=return_request_id, domain=domain, ) async def _get_async( self, path: str, payload: Optional[Union[dict, Base]] = None, request_headers: Optional[dict] = None, cls: Optional[type] = None, include_version: Optional[bool] = True, timeout: Optional[int] = DEFAULT_TIMEOUT, return_request_id: Optional[bool] = False, domain: Optional[str] = None, ) -> Union[Base, tuple, dict]: ret = await self.__request_async( 'GET', path, payload=payload, request_headers=request_headers, cls=cls, include_version=include_version, timeout=timeout, return_request_id=return_request_id, domain=domain, ) return ret def _post( self, path: str, payload: Optional[Union[dict, bytes, Base, pd.DataFrame]] = None, request_headers: Optional[dict] = None, cls: Optional[type] = None, include_version: Optional[bool] = True, timeout: Optional[int] = DEFAULT_TIMEOUT, return_request_id: Optional[bool] = False, domain: Optional[str] = None, ) -> Union[Base, tuple, dict]: return self.__request( 'POST', path, payload=payload, request_headers=request_headers, cls=cls, include_version=include_version, timeout=timeout, return_request_id=return_request_id, domain=domain, ) async def _post_async( self, path: str, payload: Optional[Union[dict, bytes, Base, pd.DataFrame]] = None, request_headers: Optional[dict] = None, cls: Optional[type] = None, include_version: Optional[bool] = True, timeout: Optional[int] = DEFAULT_TIMEOUT, return_request_id: Optional[bool] = False, domain: Optional[str] = None, ) -> Union[Base, tuple, dict]: ret = await self.__request_async( 'POST', path, payload=payload, request_headers=request_headers, cls=cls, include_version=include_version, timeout=timeout, return_request_id=return_request_id, domain=domain, ) return ret def _delete( self, path: str, payload: Optional[Union[dict, Base]] = None, request_headers: Optional[dict] = None, cls: Optional[type] = None, include_version: Optional[bool] = True, timeout: Optional[int] = DEFAULT_TIMEOUT, return_request_id: Optional[bool] = False, use_body: Optional[bool] = False, domain: Optional[str] = None, ) -> Union[Base, tuple, dict]: return self.__request( 'DELETE', path, payload=payload, request_headers=request_headers, cls=cls, include_version=include_version, timeout=timeout, return_request_id=return_request_id, use_body=use_body, domain=domain, ) async def _delete_async( self, path: str, payload: Optional[Union[dict, Base]] = None, request_headers: Optional[dict] = None, cls: Optional[type] = None, include_version: Optional[bool] = True, timeout: Optional[int] = DEFAULT_TIMEOUT, return_request_id: Optional[bool] = False, use_body: Optional[bool] = False, domain: Optional[str] = None, ) -> Union[Base, tuple, dict]: ret = await self.__request_async( 'DELETE', path, payload=payload, request_headers=request_headers, cls=cls, include_version=include_version, timeout=timeout, return_request_id=return_request_id, use_body=use_body, domain=domain, ) return ret def _put( self, path: str, payload: Optional[Union[dict, Base]] = None, request_headers: Optional[dict] = None, cls: Optional[type] = None, include_version: Optional[bool] = True, timeout: Optional[int] = DEFAULT_TIMEOUT, return_request_id: Optional[bool] = False, domain: Optional[str] = None, ) -> Union[Base, tuple, dict]: return self.__request( 'PUT', path, payload=payload, request_headers=request_headers, cls=cls, include_version=include_version, timeout=timeout, return_request_id=return_request_id, domain=domain, ) async def _put_async( self, path: str, payload: Optional[Union[dict, Base]] = None, request_headers: Optional[dict] = None, cls: Optional[type] = None, include_version: Optional[bool] = True, timeout: Optional[int] = DEFAULT_TIMEOUT, return_request_id: Optional[bool] = False, domain: Optional[str] = None, ) -> Union[Base, tuple, dict]: ret = await self.__request_async( 'PUT', path, payload=payload, request_headers=request_headers, cls=cls, include_version=include_version, timeout=timeout, return_request_id=return_request_id, domain=domain, ) return ret @asynccontextmanager async def _connect_websocket( self, path: str, headers: Optional[dict] = None, include_version=True, domain: Optional[str] = None, **kwargs: Any, ): span = Tracer.active_span() trace = Tracer(f'wss:/{path}') if span and span.is_recording() else nullcontext() with trace as scope: tracing_headers = {} Tracer.inject(tracing_headers) headers = {**headers, **tracing_headers} if headers else tracing_headers async with self._connect_websocket_raw(path, headers, include_version, domain, **kwargs) as websocket: if scope and scope.span: if hasattr(websocket, 'request_headers'): # For websockets < 14 scope.span.set_tag('wss.host', websocket.request_headers.get('host')) else: scope.span.set_tag('wss.host', websocket.request.headers.get('host')) yield websocket def _connect_websocket_raw( self, path: str, headers: Optional[dict] = None, include_version=True, domain: Optional[str] = None, **kwargs: Any, ): import websockets _WEBSOCKETS_VERSION = tuple(int(x) for x in websockets.__version__.split(".")) version_path = '/' + self.api_version if include_version else '' url = f'{domain}{version_path}{path}' if domain else f'ws{self.domain[4:]}{version_path}{path}' extra_headers = self._headers() + list((headers or {}).items()) # websockets 14 supports python >= 3.9 only. When we drop support for python 3.8 we can clean this up if _WEBSOCKETS_VERSION >= (14, 0): ws_library_headers = {"additional_headers": extra_headers} else: ws_library_headers = {"extra_headers": extra_headers, "read_limit": 2**32} return websockets.connect( url, max_size=2**32, ssl=CustomHttpAdapter.ssl_context() if url.startswith('wss') else None, **ws_library_headers, **kwargs, ) def _headers(self): headers = [] if self._session: for k, v in self._session.headers.items(): if k.upper() in ('AUTHORIZATION', 'X-MARQUEE-CSRF-TOKEN', 'X-APPLICATION', 'X-VERSION'): headers.append((k, v)) cookies = () if self._session.cookies: if "MarqueeLogin" in self._session.cookies: cookies += (f"MarqueeLogin={self._session.cookies['MarqueeLogin']}",) if "MARQUEE-CSRF-TOKEN" in self._session.cookies: cookies += (f"MARQUEE-CSRF-TOKEN={self._session.cookies['MARQUEE-CSRF-TOKEN']}",) if 'GSSSO' in self._session.cookies: cookies += (f"GSSSO={self._session.cookies['GSSSO']}",) if cookies: headers += [('Cookie', "; ".join(cookies))] return headers def _get_mds_domain(self): env_config = GsSession._config_for_environment(self.environment.name) current_domain = self.domain.replace('marquee.web', 'marquee') # remove .web from prod domain if self.environment.name == Environment.QA.name: current_domain = self.domain.replace('marquee-qa.web', 'marquee-qa') # remove .web from qa domain is_mds_web = current_domain == Domain.MDS_WEB is_env_mds_web = current_domain == env_config['MdsWebDomain'] is_env_marquee_web = current_domain == env_config['MarqueeWebDomain'] if is_mds_web or is_env_mds_web or is_env_marquee_web: return env_config['MdsWebDomain'] else: return env_config['MdsDomainEast'] def _get_web_domain(self): env_config = GsSession._config_for_environment(self.environment.name) return env_config['MarqueeWebDomain'] @classmethod def _config_for_environment(cls, environment): if cls.__config is None: cls.__config = ConfigParser() cls.__config.read(os.path.join(os.path.dirname(inspect.getfile(cls)), 'config.ini')) return cls.__config[environment] @classmethod def use( cls, environment_or_domain: Union[Environment, str] = Environment.PROD, client_id: Optional[str] = None, client_secret: Optional[str] = None, scopes: Optional[Union[Iterable[Union[Scopes, str]], str]] = (), api_version: str = API_VERSION, application: str = DEFAULT_APPLICATION, http_adapter: requests.adapters.HTTPAdapter = None, use_mds: bool = False, domain: Domain = Domain.APP, ) -> None: environment_or_domain = ( environment_or_domain.name if isinstance(environment_or_domain, Environment) else environment_or_domain ) if domain is None: raise MqError("None is not a valid domain.") domain = Domain.MDS_US_EAST if use_mds else domain session = cls.get( environment_or_domain, client_id=client_id, client_secret=client_secret, scopes=scopes, api_version=api_version, application=application, http_adapter=http_adapter, domain=domain, ) session.init() cls.current = session def post_to_activity_service(self): params = { 'featureApplication': self.application, 'gsQuantVersion': self.application_version, 'pythonVersion': f'{sys.version_info.major}.{sys.version_info.minor}', } try: self._session.post( f'{self.domain}/{self.api_version}/activities', verify=self.verify, headers={'Content-Type': 'application/json; charset=utf-8'}, data=json.dumps( { 'action': 'Initiated', 'kpis': [{'id': 'gsqInitiated', 'value': 1}], 'resource': 'GSQuant', 'parameters': params, } ), ) except Exception: pass @classmethod def get( cls, environment_or_domain: Union[Environment, str] = Environment.PROD, client_id: Optional[str] = None, client_secret: Optional[str] = None, scopes: Optional[Union[Iterable[Union[Scopes, str]], str]] = (), token: str = '', is_gssso: bool = False, is_marquee_login: bool = False, api_version: str = API_VERSION, application: str = DEFAULT_APPLICATION, http_adapter: requests.adapters.HTTPAdapter = None, application_version: str = APP_VERSION, domain: Domain = Domain.APP, is_jwt_login: bool = False, ) -> 'GsSession': """Return an instance of the appropriate session type for the given credentials""" environment_or_domain = ( environment_or_domain.name if isinstance(environment_or_domain, Environment) else environment_or_domain ) if client_id is not None: if isinstance(scopes, str): scopes = (scopes,) scopes = (scope if isinstance(scope, str) else scope.value for scope in scopes) scopes = tuple(set(itertools.chain(scopes, cls.Scopes.get_default()))) return OAuth2Session( environment_or_domain, client_id, client_secret, scopes, api_version=api_version, application=application, http_adapter=http_adapter, domain=domain, ) elif token: if is_gssso: try: return PassThroughGSSSOSession( environment_or_domain, token, api_version=api_version, application=application, http_adapter=http_adapter, ) except NameError: raise MqUninitialisedError('This option requires gs_quant_auth to be installed') elif is_marquee_login: return MQLoginSession( environment_or_domain, domain=domain, api_version=api_version, http_adapter=http_adapter, application_version=application_version, application=application, mq_login_token=token, ) elif is_jwt_login: return MQLoginSession( environment_or_domain, domain=domain, api_version=api_version, http_adapter=http_adapter, application_version=application_version, application=application, jwt_token=token, ) else: return PassThroughSession( environment_or_domain, token, api_version=api_version, application=application, http_adapter=http_adapter, domain=domain, ) else: try: return MQLoginSession( environment_or_domain, domain=domain, api_version=api_version, http_adapter=http_adapter, application_version=application_version, application=application, mq_login_token=token, ) except NameError: raise MqUninitialisedError( 'Unable to obtain MarqueeLogin token. Please use client_id and client_secret to make the query' ) def is_internal(self) -> bool: return False @property def sync(self) -> '_SyncSessionAPI': """Synchronous HTTP interface. Use in non-async code.""" if self._sync_api is None: self._sync_api = _SyncSessionAPI(self) return self._sync_api @property def async_(self) -> '_AsyncSessionAPI': """Asynchronous HTTP interface. Use with ``await`` inside async functions.""" if self._async_api is None: self._async_api = _AsyncSessionAPI(self) return self._async_api class OAuth2Session(GsSession): def __init__( self, environment, client_id, client_secret, scopes, api_version=API_VERSION, application=DEFAULT_APPLICATION, http_adapter=None, domain=Domain.APP, ): if environment not in (Environment.PROD.name, Environment.QA.name, Environment.DEV.name): env_config = self._config_for_environment(Environment.DEV.name) url = environment else: env_config = self._config_for_environment(environment) url = env_config[domain] super().__init__(url, environment, api_version=api_version, application=application, http_adapter=http_adapter) self.auth_url = env_config['AuthURL'] self.client_id = client_id self.client_secret = client_secret self.scopes = scopes self._orig_domain = domain if environment == Environment.DEV.name or (url != env_config['AppDomain'] and not domain == Domain.MDS_US_EAST): import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) self.verify = False def _authenticate(self): auth_data = { 'grant_type': 'client_credentials', 'client_id': self.client_id, 'client_secret': self.client_secret, 'scope': ' '.join(self.scopes), } reply = self._session.post(self.auth_url, data=auth_data, verify=self.verify) if reply.status_code != 200: raise MqAuthenticationError(reply.status_code, reply.text, context=self.auth_url) response = json.loads(reply.text) self._session.headers.update({'Authorization': 'Bearer {}'.format(response['access_token'])}) def _headers(self): return [('Authorization', self._session.headers['Authorization'])] class PassThroughSession(GsSession): __config = None @classmethod def domain_and_verify(cls, environment_or_domain: str, domain: Optional[str]): if cls.__config is None: cls.__config = ConfigParser() cls.__config.read(os.path.join(os.path.dirname(inspect.getfile(cls)), 'config.ini')) verify = False try: domain = cls.__config[environment_or_domain][domain] verify = True except KeyError: domain = environment_or_domain return domain, verify def __init__( self, environment: str, token, api_version=API_VERSION, application=DEFAULT_APPLICATION, http_adapter=None, domain=None, ): domain = domain if domain is not None else 'AppDomain' domain, verify = self.domain_and_verify(environment, domain) super().__init__( domain, environment, api_version=api_version, application=application, verify=verify, http_adapter=http_adapter, ) self._orig_domain = domain self.token = token def _authenticate(self): self._session.headers.update({'Authorization': 'Bearer {}'.format(self.token)}) def _headers(self): return [('Authorization', self._session.headers['Authorization'])] try: from gs_quant_auth.kerberos.session_kerberos import KerberosSessionMixin class KerberosSession(KerberosSessionMixin, GsSession): def __init__( self, environment_or_domain: str, api_version: str = API_VERSION, application: str = DEFAULT_APPLICATION, http_adapter: requests.adapters.HTTPAdapter = None, application_version: str = APP_VERSION, ): domain, verify = self.domain_and_verify(environment_or_domain) GsSession.__init__( self, domain, environment_or_domain, api_version=api_version, application=application, verify=verify, http_adapter=http_adapter, application_version=application_version, ) class PassThroughGSSSOSession(KerberosSessionMixin, GsSession): def __init__( self, environment: str, token, api_version=API_VERSION, application=DEFAULT_APPLICATION, http_adapter=None, csrf_token=None, ): domain, verify = self.domain_and_verify(environment) GsSession.__init__( self, domain, environment, api_version=api_version, application=application, verify=verify, http_adapter=http_adapter, ) self.token = token self.csrf_token = csrf_token def _authenticate(self): if not (self.token and self.csrf_token): self._handle_cookies(self.token) return cookie = requests.cookies.create_cookie(domain='.gs.com', name='GSSSO', value=self.token) self._session.cookies.set_cookie(cookie) if self.csrf_token: cookie = requests.cookies.create_cookie( domain='.gs.com', name='MARQUEE-CSRF-TOKEN', value=self.csrf_token ) self._session.cookies.set_cookie(cookie) self._session.headers.update({'X-MARQUEE-CSRF-TOKEN': self.csrf_token}) except ModuleNotFoundError: pass try: from gs_quant_auth.kerberos.session_kerberos import MQLoginMixin class MQLoginSession(MQLoginMixin, GsSession): def __init__( self, environment_or_domain: str, domain: str = Domain.APP, api_version: str = API_VERSION, application: str = DEFAULT_APPLICATION, http_adapter: requests.adapters.HTTPAdapter = None, application_version: str = APP_VERSION, mq_login_token=None, jwt_token=None, ): selected_domain, verify = self.domain_and_verify(environment_or_domain) if domain == Domain.MDS_WEB: env_config = self._config_for_environment(environment_or_domain) selected_domain = env_config[domain] self.mq_login_token = mq_login_token self.jwt_token = jwt_token GsSession.__init__( self, selected_domain, environment_or_domain, api_version=api_version, application=application, verify=verify, http_adapter=http_adapter, application_version=application_version, ) self._orig_domain = domain def _authenticate(self): if self.jwt_token: self._session.headers.update({'Authorization': 'Bearer {}'.format(self.jwt_token)}) super()._authenticate() except ModuleNotFoundError: pass ================================================ FILE: gs_quant/target/__init__.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ __name__ = 'target' ================================================ FILE: gs_quant/target/assets.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.base import * from gs_quant.common import * import datetime from typing import Dict, Optional, Tuple, Union from dataclasses import dataclass, field from dataclasses_json import LetterCase, config, dataclass_json from enum import Enum class AllocatorType(EnumBase, Enum): """Allocator type defines the type of investor company managing an asset""" Advisor = 'Advisor' Consultant_Institutional = 'Consultant (Institutional)' Endowment = 'Endowment' Family_Office_Multi = 'Family Office (Multi)' Family_Office_Single = 'Family Office (Single)' Foundation = 'Foundation' Fund_of_Funds = 'Fund of Funds' Healthcare_System = 'Healthcare System' Insurance_Company = 'Insurance Company' Outsourced_CIO = 'Outsourced CIO' Pension_Private = 'Pension (Private)' Pension_Public = 'Pension (Public)' Platform = 'Platform' Private_Bank = 'Private Bank' Prop_Capital_OVER_Commercial_Bank = 'Prop Capital/Commercial Bank' Registered_Investment_Advisor = 'Registered Investment Advisor' Sovereign_Wealth_Fund = 'Sovereign Wealth Fund' class Commodities(EnumBase, Enum): """Commodity asset""" Aluminium = 'Aluminium' Aluminium_Alloy = 'Aluminium Alloy' Chicago_Ethanol = 'Chicago Ethanol' Coal = 'Coal' Coffee = 'Coffee' Copper = 'Copper' Corn = 'Corn' Cotton = 'Cotton' Crude_Palm_Oil = 'Crude Palm Oil' Diesel_Fuel = 'Diesel Fuel' Electricity = 'Electricity' Emissions = 'Emissions' Ethylene = 'Ethylene' Freight = 'Freight' Fuel_Oil = 'Fuel Oil' Gas_Oil = 'Gas Oil' Gasoline = 'Gasoline' Gold = 'Gold' Heating_Oil = 'Heating Oil' Iron_Ore = 'Iron Ore' Jet_Fuel = 'Jet Fuel' Lead = 'Lead' Lean_Hogs = 'Lean Hogs' NGL = 'NGL' Naphtha = 'Naphtha' Natural_Gas = 'Natural Gas' Nickel = 'Nickel' Oil = 'Oil' Palladium = 'Palladium' Platinum = 'Platinum' Polypropylene = 'Polypropylene' Primary_Aluminium = 'Primary Aluminium' Silver = 'Silver' Soybean_Meal = 'Soybean Meal' Soybean_Oil = 'Soybean Oil' Soybeans = 'Soybeans' Sugar = 'Sugar' Tin = 'Tin' Ultra_Low_Sulphur_Diesel = 'Ultra Low Sulphur Diesel' Wheat = 'Wheat' White_Sugar = 'White Sugar' Zinc = 'Zinc' class CommodityFamily(EnumBase, Enum): """Commodity Family""" Base_Metal = 'Base Metal' Gas = 'Gas' Oil = 'Oil' Oil_Products = 'Oil Products' class CommoditySubFamily(EnumBase, Enum): """Commodity SubFamily""" Crude = 'Crude' Fuel = 'Fuel' Heat = 'Heat' NG = 'NG' @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AssetMetadata(Base): version_timestamp: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Benchmark(Base): asset_id: Optional[str] = field(default=None, metadata=field_metadata) value: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CommodityEUNaturalGasHub(Base): region: Optional[str] = field(default=None, metadata=field_metadata) hub_type: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CommodityNaturalGasHub(Base): region: Optional[str] = field(default=None, metadata=field_metadata) pipelines: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) platts_codes: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CommodityPowerAggregatedNodes(Base): ISO: Optional[str] = field(default=None, metadata=field_metadata) aggregate_type: Optional[str] = field(default=None, metadata=field_metadata) location_name: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CommodityPowerNode(Base): ISO: Optional[str] = field(default=None, metadata=field_metadata) location_name: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CommodityReferencePriceParameters(Base): commodity_base: Optional[str] = field(default=None, metadata=field_metadata) commodity_details: Optional[str] = field(default=None, metadata=field_metadata) currency: Optional[str] = field(default=None, metadata=field_metadata) unit: Optional[str] = field(default=None, metadata=field_metadata) exchange_id: Optional[str] = field(default=None, metadata=field_metadata) publication: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FutureContract(Base): future_market_marquee_id: Optional[str] = field(default=None, metadata=field_metadata) contract: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FutureMarket(Base): exchange: Optional[str] = field(default=None, metadata=field_metadata) period_frequency: Optional[str] = field(default=None, metadata=field_metadata) product_group: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class NumberRange(Base): lower_bound: Optional[float] = field(default=None, metadata=field_metadata) upper_bound: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class People(Base): portfolio_managers: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class WeatherIndexParameters(Base): data_provider: Optional[str] = field(default=None, metadata=field_metadata) weather_station: Optional[str] = field(default=None, metadata=field_metadata) reference_level_amount: Optional[float] = field(default=None, metadata=field_metadata) reference_level_unit: Optional[str] = field(default=None, metadata=field_metadata) weather_station_fallback: Optional[str] = field(default=None, metadata=field_metadata) weather_station_second_fallback: Optional[str] = field(default=None, metadata=field_metadata) alternative_data_provider: Optional[str] = field(default=None, metadata=field_metadata) synoptic_data_fallback: Optional[str] = field(default=None, metadata=field_metadata) adjustment_to_fallback_weather_station: Optional[str] = field(default=None, metadata=field_metadata) primary_disruption_fallbacks: Optional[str] = field(default=None, metadata=field_metadata) secondary_disruption_fallbacks: Optional[str] = field(default=None, metadata=field_metadata) final_edited_data: Optional[bool] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AssetStats(Base): last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) period: Optional[AssetStatsPeriod] = field(default=None, metadata=field_metadata) type_: Optional[AssetStatsType] = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) stats: Optional[PerformanceStats] = field(default=None, metadata=field_metadata) start_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) end_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CommodConfigParameters(Base): infra: str = field(default=None, metadata=field_metadata) field_history: Tuple[DictBase, ...] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CreditBasketParameters(Base): index_calculation_type: Optional[IndexCalculationType] = field(default=None, metadata=field_metadata) currency: Optional[Currency] = field(default=None, metadata=field_metadata) initial_pricing_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) index_notes: Optional[str] = field(default=None, metadata=field_metadata) on_behalf_of: Optional[str] = field(default=None, metadata=field_metadata) valuation_source: Optional[BasketValuationSource] = field(default=None, metadata=field_metadata) quote_time: Optional[str] = field(default=None, metadata=field_metadata) official_side: Optional[Side] = field(default=None, metadata=field_metadata) quoting_type: Optional[QuoteType] = field(default=None, metadata=field_metadata) weighting_type: Optional[WeightingType] = field(default=None, metadata=field_metadata) close_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) clone_parent_id: Optional[str] = field(default=None, metadata=field_metadata) hedge_id: Optional[str] = field(default=None, metadata=field_metadata) portfolio_id: Optional[str] = field(default=None, metadata=field_metadata) index_approval_ids: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) credit_basket_type: Optional[CreditBasketType] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class HedgeFundParameters(Base): aum: Optional[float] = field(default=None, metadata=field_metadata) strategy_aum: Optional[float] = field(default=None, metadata=field_metadata) aum_range: Optional[NumberRange] = field(default=None, metadata=field_metadata) strategy_aum_range: Optional[NumberRange] = field(default=None, metadata=field_metadata) disclaimers: Optional[str] = field(default=None, metadata=field_metadata) market_cap_category: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) marketing_status: Optional[str] = field(default=None, metadata=field_metadata) preferences: Optional[DictBase] = field(default=None, metadata=field_metadata) regional_focus: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) risk_taking_model: Optional[str] = field(default=None, metadata=field_metadata) strategy: Optional[Strategy] = field(default=None, metadata=field_metadata) strategy_description: Optional[str] = field(default=None, metadata=field_metadata) targeted_gross_exposure: Optional[NumberRange] = field(default=None, metadata=field_metadata) targeted_net_exposure: Optional[NumberRange] = field(default=None, metadata=field_metadata) targeted_num_of_positions_short: Optional[NumberRange] = field(default=None, metadata=field_metadata) targeted_num_of_positions_long: Optional[NumberRange] = field(default=None, metadata=field_metadata) turnover: Optional[str] = field(default=None, metadata=field_metadata) vehicle_type: Optional[str] = field(default=None, metadata=field_metadata) last_returns_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) open_to_sma: Optional[bool] = field(default=None, metadata=config(field_name='openToSMA', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SecuritiesLendingLoan(Base): asset_id: str = field(default=None, metadata=field_metadata) fund_id: str = field(default=None, metadata=field_metadata) lender_id: str = field(default=None, metadata=field_metadata) borrower_id: str = field(default=None, metadata=field_metadata) loan_status: Optional[str] = field(default=None, metadata=field_metadata) settlement_status: Optional[str] = field(default=None, metadata=field_metadata) collateral_type: Optional[str] = field(default=None, metadata=field_metadata) loan_currency: Optional[Currency] = field(default=None, metadata=field_metadata) adjustment_ind: Optional[bool] = field(default=None, metadata=field_metadata) country_of_issue: Optional[str] = field(default=None, metadata=field_metadata) input_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) effective_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) security_settle_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) cash_settle_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) term_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) return_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ShareClassParameters(Base): active_liquidity_fee: Optional[float] = field(default=None, metadata=field_metadata) additional_provisions: Optional[str] = field(default=None, metadata=field_metadata) benchmark: Optional[Benchmark] = field(default=None, metadata=field_metadata) class_fees: Optional[float] = field(default=None, metadata=field_metadata) class_type: Optional[str] = field(default=None, metadata=field_metadata) early_redemption_fee: Optional[float] = field(default=None, metadata=field_metadata) expense_ratio_gross: Optional[float] = field(default=None, metadata=field_metadata) expense_ratio_net: Optional[float] = field(default=None, metadata=field_metadata) share_class_type: Optional[str] = field(default=None, metadata=field_metadata) gate: Optional[float] = field(default=None, metadata=field_metadata) gate_type: Optional[str] = field(default=None, metadata=field_metadata) hurdle: Optional[float] = field(default=None, metadata=field_metadata) hurdle_type: Optional[str] = field(default=None, metadata=field_metadata) investment_manager: Optional[str] = field(default=None, metadata=field_metadata) investment_type: Optional[str] = field(default=None, metadata=field_metadata) institutional_share_class: Optional[bool] = field(default=None, metadata=field_metadata) lockup: Optional[float] = field(default=None, metadata=field_metadata) lockup_type: Optional[str] = field(default=None, metadata=field_metadata) management_fee: Optional[float] = field(default=None, metadata=field_metadata) minimum_subscription: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=field_metadata) number_of_shares: Optional[float] = field(default=None, metadata=field_metadata) performance_fee: Optional[float] = field(default=None, metadata=field_metadata) redemption_notice_period: Optional[float] = field(default=None, metadata=field_metadata) redemption_period: Optional[str] = field(default=None, metadata=field_metadata) share_class_currency: Optional[str] = field(default=None, metadata=field_metadata) side_pocket: Optional[str] = field(default=None, metadata=field_metadata) status: Optional[str] = field(default=None, metadata=field_metadata) sub_category: Optional[str] = field(default=None, metadata=field_metadata) term_type: Optional[str] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class TemporalPeople(Base): start_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) end_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) people: Optional[People] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class TemporalXRef(Base): start_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) end_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) identifiers: Optional[XRef] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AssetGetRequestPathSchema(Base): limit: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) offset: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) scroll: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) scroll_id: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) as_of_time: Optional[Tuple[datetime.datetime, ...]] = field(default=None, metadata=field_metadata) field_: Optional[Tuple[str, ...]] = field(default=None, metadata=config(field_name='field', exclude=exclude_none)) fields: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) order_by: Optional[Tuple[Union[DictBase, str], ...]] = field(default=None, metadata=field_metadata) next_rebalance_date: Optional[Tuple[datetime.date, ...]] = field(default=None, metadata=field_metadata) asset_classifications_digital_asset_class: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) sectors: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) ticker: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) valoren: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) asset_parameters_payer_currency: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) styles: Optional[Tuple[Tuple[str, ...], ...]] = field(default=None, metadata=field_metadata) short_name: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) currency: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) rating_moodys: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) identifier: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) rcic: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name_raw: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) asset_parameters_receiver_currency: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) primary_country_ric: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) strike_price: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) listed: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) tags: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) delisted: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) asset_parameters_floating_rate_option: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) pair_calculation: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) mic: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) rating_fitch: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) cusip: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) is_legacy_pair_basket: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) last_updated_by_id: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) expiration_date: Optional[Tuple[datetime.date, ...]] = field(default=None, metadata=field_metadata) asset_parameters_index: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) asset_classifications_digital_asset_sector: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) display_id: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) isin: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) asset_parameters_strike_type: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) created_by_id: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) asset_parameters_cap_floor: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) asset_classifications_underliers_asset_class: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) last_updated_since: Optional[Tuple[datetime.datetime, ...]] = field(default=None, metadata=field_metadata) option_type: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) exchange: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) coin_metrics_id: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) asset_class: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) ric: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) trading_restriction: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) bbid: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) underlying_asset_ids: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) bbid_equivalent: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) asset_parameters_index1_tenor: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) owner_id: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) asset_classifications_is_primary: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) tsdb_shortname: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) live_date: Optional[Tuple[Union[Op, datetime.date], ...]] = field(default=None, metadata=field_metadata) prime_id: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) description: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) asset_classifications_is_country_primary: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) sedol: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) default_backcast: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) wpk: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) id_: Optional[Tuple[str, ...]] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) bcid: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) settlement_date: Optional[Tuple[datetime.date, ...]] = field(default=None, metadata=field_metadata) last_rebalance_date: Optional[Tuple[datetime.date, ...]] = field(default=None, metadata=field_metadata) rating_second_highest: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) region: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) asset_parameters_payer_spread: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) rating_linear: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) type_: Optional[Tuple[Union[AssetType, RiskModelType], ...]] = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) asset_parameters_index2_tenor: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) rating_standard_and_poors: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) asset_classifications_digital_asset_subsector: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) asset_parameters_fee_currency: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) asset_parameters_receiver_spread: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Asset(Base): asset_class: AssetClass = field(default=None, metadata=field_metadata) type_: AssetType = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) name: str = field(default=None, metadata=field_metadata) active: Optional[bool] = field(default=None, metadata=field_metadata) classifications: Optional[AssetClassifications] = field(default=None, metadata=field_metadata) created_by_id: Optional[str] = field(default=None, metadata=field_metadata) created_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) currency: Optional[Currency] = field(default=None, metadata=field_metadata) default_quantity: Optional[float] = field(default=None, metadata=field_metadata) description: Optional[str] = field(default=None, metadata=field_metadata) economic_terms_hash: Optional[str] = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) entitlement_exclusions: Optional[EntitlementExclusions] = field(default=None, metadata=field_metadata) exchange: Optional[str] = field(default=None, metadata=field_metadata) id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) identifiers: Optional[Tuple[Identifier, ...]] = field(default=None, metadata=field_metadata) last_updated_by_id: Optional[str] = field(default=None, metadata=field_metadata) last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) listed: Optional[bool] = field(default=None, metadata=field_metadata) live_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) key: Optional[str] = field(default=None, metadata=field_metadata) key_map: Optional[DictBase] = field(default=None, metadata=field_metadata) owner_id: Optional[str] = field(default=None, metadata=field_metadata) parameters: Optional[DictBase] = field(default=None, metadata=field_metadata) asset_stats: Optional[Tuple[AssetStats, ...]] = field(default=None, metadata=field_metadata) people: Optional[People] = field(default=None, metadata=field_metadata) people_history: Optional[Tuple[TemporalPeople, ...]] = field(default=None, metadata=field_metadata) rank: Optional[float] = field(default=None, metadata=field_metadata) region: Optional[Region] = field(default=None, metadata=field_metadata) report_ids: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) sectors: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) short_name: Optional[str] = field(default=None, metadata=field_metadata) styles: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) tags: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) underliers: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) underlying_asset_ids: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) xrefs: Optional[Tuple[TemporalXRef, ...]] = field(default=None, metadata=field_metadata) xref: Optional[XRef] = field(default=None, metadata=field_metadata) metadata: Optional[AssetMetadata] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AssetToInstrumentResponse(Base): asset_id: str = field(default=None, metadata=field_metadata) name: str = field(default=None, metadata=field_metadata) instrument: InstrumentBase = field(default=None, metadata=field_metadata) size_field: Optional[str] = field(default=None, metadata=field_metadata) ================================================ FILE: gs_quant/target/assets_screener.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.base import * from gs_quant.common import * import datetime from typing import Dict, Optional, Tuple, Union from dataclasses import dataclass, field from dataclasses_json import LetterCase, config, dataclass_json @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AssetScreenerCreditResponseItem(Base): asset_id: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) entitlement_exclusions: Optional[EntitlementExclusions] = field(default=None, metadata=field_metadata) cusip: Optional[str] = field(default=None, metadata=field_metadata) isin: Optional[str] = field(default=None, metadata=field_metadata) bbid: Optional[str] = field(default=None, metadata=field_metadata) currency: Optional[Currency] = field(default=None, metadata=field_metadata) region: Optional[Region] = field(default=None, metadata=field_metadata) industry: Optional[str] = field(default=None, metadata=field_metadata) seniority: Optional[str] = field(default=None, metadata=field_metadata) ticker: Optional[str] = field(default=None, metadata=field_metadata) rating_standard_and_poors: Optional[str] = field(default=None, metadata=field_metadata) gs_liquidity_score: Optional[float] = field(default=None, metadata=field_metadata) amount_outstanding: Optional[float] = field(default=None, metadata=field_metadata) maturity: Optional[float] = field(default=None, metadata=field_metadata) bval_mid_price: Optional[float] = field(default=None, metadata=field_metadata) yield_to_convention: Optional[float] = field(default=None, metadata=field_metadata) modified_duration: Optional[float] = field(default=None, metadata=field_metadata) spread_to_benchmark: Optional[float] = field(default=None, metadata=field_metadata) g_spread: Optional[float] = field(default=None, metadata=field_metadata) z_spread: Optional[float] = field(default=None, metadata=field_metadata) charge_in_dollars: Optional[str] = field(default=None, metadata=field_metadata) charge_in_bps: Optional[str] = field(default=None, metadata=field_metadata) gs_indicative_benchmark_isin: Optional[str] = field(default=None, metadata=field_metadata) gs_indicative_buy_price: Optional[float] = field(default=None, metadata=field_metadata) gs_indicative_buy_quantity: Optional[float] = field(default=None, metadata=field_metadata) gs_indicative_buy_spread: Optional[float] = field(default=None, metadata=field_metadata) gs_indicative_buy_yield: Optional[float] = field(default=None, metadata=field_metadata) gs_indicative_pricing_convention: Optional[str] = field(default=None, metadata=field_metadata) gs_indicative_sell_price: Optional[float] = field(default=None, metadata=field_metadata) gs_indicative_sell_quantity: Optional[float] = field(default=None, metadata=field_metadata) gs_indicative_sell_spread: Optional[float] = field(default=None, metadata=field_metadata) gs_indicative_sell_yield: Optional[float] = field(default=None, metadata=field_metadata) science_based_target: Optional[str] = field(default=None, metadata=field_metadata) net_zero_emissions_target: Optional[str] = field(default=None, metadata=field_metadata) emissions_intensity_enterprise_value: Optional[float] = field(default=None, metadata=field_metadata) emissions_intensity_revenue: Optional[float] = field(default=None, metadata=field_metadata) g_percentile: Optional[float] = field(default=None, metadata=field_metadata) g_regional_percentile: Optional[float] = field(default=None, metadata=field_metadata) es_percentile: Optional[float] = field(default=None, metadata=field_metadata) es_disclosure_percentage: Optional[float] = field(default=None, metadata=field_metadata) es_momentum_percentile: Optional[float] = field(default=None, metadata=field_metadata) direction: Optional[str] = field(default=None, metadata=field_metadata) face_value: Optional[float] = field(default=None, metadata=field_metadata) indicative_short_financing_label: Optional[str] = field(default=None, metadata=field_metadata) indicative_long_financing_label: Optional[str] = field(default=None, metadata=field_metadata) country_of_risk: Optional[str] = field(default=None, metadata=field_metadata) payment_rank: Optional[str] = field(default=None, metadata=field_metadata) industry_sector: Optional[str] = field(default=None, metadata=field_metadata) industry_group: Optional[str] = field(default=None, metadata=field_metadata) industry_sub_group: Optional[str] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AssetScreenerCreditResponse(Base): total_results: int = field(default=None, metadata=field_metadata) results: Optional[Tuple[AssetScreenerCreditResponseItem, ...]] = field(default=None, metadata=field_metadata) scroll_id: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AssetScreenerRequest(Base): asset_class: Optional[AssetClass] = field(default=None, metadata=field_metadata) type_: Optional[AssetType] = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) scroll: Optional[str] = field(default=None, metadata=field_metadata) scroll_id: Optional[str] = field(default=None, metadata=field_metadata) limit: Optional[int] = field(default=None, metadata=field_metadata) offset: Optional[int] = field(default=None, metadata=field_metadata) filters: Optional[AssetScreenerCreditRequestFilters] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) ================================================ FILE: gs_quant/target/backtests.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.base import * from gs_quant.common import * import datetime from typing import Dict, Optional, Tuple, Union from dataclasses import dataclass, field from dataclasses_json import LetterCase, config, dataclass_json from enum import Enum class BacktestRiskMeasureType(EnumBase, Enum): """The type of measure to perform risk on. e.g. Greeks""" Price = 'Price' Price_Percent = 'Price Percent' Delta = 'Delta' Gamma = 'Gamma' Vega = 'Vega' Forward = 'Forward' Implied_Volatility = 'Implied Volatility' Fair_Variance = 'Fair Variance' Strike_Level = 'Strike Level' Spot = 'Spot' Price_ATMS = 'Price ATMS' Price_ATMF_Volatility = 'Price ATMF Volatility' Barrier_Level = 'Barrier Level' Strike_Level_Adj_Implied = 'Strike Level Adj Implied' Strike_Level_Adj_Realised = 'Strike Level Adj Realised' class BacktestTradingQuantityType(EnumBase, Enum): """The trading quantity unit of a backtest strategy""" notional = 'notional' quantity = 'quantity' vega = 'vega' gamma = 'gamma' NAV = 'NAV' premium = 'premium' vegaNotional = 'vegaNotional' class BacktestType(EnumBase, Enum): """Backtest type differentiates the backtest type.""" Basket = 'Basket' Volatility = 'Volatility' Volatility_Flow = 'Volatility Flow' Enhanced_Beta = 'Enhanced Beta' ISelect = 'ISelect' class EquityMarketModel(EnumBase, Enum): """Market model for pricing""" SFK = 'SFK' SD = 'SD' SVR = 'SVR' class FlowVolBacktestMeasure(EnumBase, Enum): """Metric which can be calculated using Flow Vol. Backtester""" ALL_MEASURES = 'ALL MEASURES' PNL_spot = 'PNL_spot' PNL_vol = 'PNL_vol' PNL_carry = 'PNL_carry' PNL_delta = 'PNL_delta' PNL_gamma = 'PNL_gamma' PNL_higher_order_spot = 'PNL_higher_order_spot' PNL_higher_order_vol = 'PNL_higher_order_vol' PNL_theta = 'PNL_theta' PNL_transactions = 'PNL_transactions' PNL_unexplained = 'PNL_unexplained' PNL_vega = 'PNL_vega' PNL = 'PNL' delta = 'delta' gamma = 'gamma' vega = 'vega' NAV = 'NAV' @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BacktestComparison(Base): id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) correlation: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BacktestRebalanceParameters(Base): frequency_period: Optional[str] = field(default=None, metadata=field_metadata) frequency: Optional[int] = field(default=None, metadata=field_metadata) day_of_week: Optional[str] = field(default=None, metadata=field_metadata) day_of_month: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BacktestSignalSeriesItem(Base): date: Optional[datetime.date] = field(default=None, metadata=field_metadata) value: Optional[bool] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) BacktestStrategySignal = Dict[str, float] @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BacktestStrategyTransactionCostModelFixed(Base): cost: Optional[float] = field(default=None, metadata=field_metadata) type_: Optional[str] = field(init=False, default='FixedCostModel', metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BacktestStrategyTransactionCostModelScaled(Base): scaling_level: Optional[float] = field(default=None, metadata=field_metadata) scaling_quantity_type: Optional[str] = field(default=None, metadata=field_metadata) type_: Optional[str] = field(init=False, default='ScaledCostModel', metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BaseIndexRefData(Base): default: Optional[str] = field(default=None, metadata=field_metadata) enum: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BuySellRefData(Base): default: Optional[str] = field(default=None, metadata=field_metadata) enum: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DeltaHedgeParameters(Base): frequency: str = field(default=None, metadata=field_metadata) fixing_time: Optional[str] = field(default=None, metadata=field_metadata) notional: Optional[float] = field(default=None, metadata=field_metadata) delta_type: Optional[str] = field(init=False, default='BlackScholes', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DeltaHedgingParameters(Base): enabled: bool = field(default=None, metadata=field_metadata) frequency: str = field(default=None, metadata=field_metadata) fixing_time: str = field(default=None, metadata=field_metadata) notional_percentage: float = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EnhancedBetaUnderlier(Base): asset_id: str = field(default=None, metadata=field_metadata) month_add: Optional[float] = field(default=None, metadata=field_metadata) valid_months: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) is_included: Optional[bool] = field(default=None, metadata=field_metadata) weight_scale: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EnhancedBetaUnderlierRefData(Base): asset_id: Optional[str] = field(default=None, metadata=field_metadata) valid_months: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) current: Optional[bool] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EntityCorrelation(Base): primary_id: Optional[str] = field(default=None, metadata=field_metadata) secondary_id: Optional[str] = field(default=None, metadata=field_metadata) correlation: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ExpirationRefData(Base): default: Optional[str] = field(default=None, metadata=field_metadata) enum: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FixingTimeRefData(Base): default: Optional[str] = field(default=None, metadata=field_metadata) enum: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FrequencyRefData(Base): default: Optional[str] = field(default=None, metadata=field_metadata) enum: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class HistoricalUnderlier(Base): asset_id: str = field(default=None, metadata=field_metadata) weight: Optional[float] = field(default=None, metadata=field_metadata) date: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class LookBackPeriodRefData(Base): default: Optional[str] = field(default=None, metadata=field_metadata) enum: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class NotionalPercentageRefData(Base): default: Optional[float] = field(default=None, metadata=field_metadata) min_: Optional[float] = field(default=None, metadata=config(field_name='min', exclude=exclude_none)) max_: Optional[float] = field(default=None, metadata=config(field_name='max', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class OptionStrikeTypeRefData(Base): default: Optional[str] = field(default=None, metadata=field_metadata) enum: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class OptionTypeRefData(Base): default: Optional[str] = field(default=None, metadata=field_metadata) enum: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ScalingMethodRefData(Base): default: Optional[str] = field(default=None, metadata=field_metadata) enum: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class StrikeRefData(Base): default: Optional[float] = field(default=None, metadata=field_metadata) min_: Optional[float] = field(default=None, metadata=config(field_name='min', exclude=exclude_none)) max_: Optional[float] = field(default=None, metadata=config(field_name='max', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class TradeInMethodRefData(Base): default: Optional[str] = field(default=None, metadata=field_metadata) enum: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class TradeInTimeRefData(Base): default: Optional[str] = field(default=None, metadata=field_metadata) enum: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class VolatilityWeightedWeightingModifier(Base): em_aalpha: Optional[float] = field(default=None, metadata=config(field_name='EMAalpha', exclude=exclude_none)) look_back_period: Optional[str] = field(default=None, metadata=field_metadata) use_log_return: Optional[bool] = field(default=False, metadata=field_metadata) name: Optional[str] = field(init=False, default='Volatility Weighted', metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class VolatilityWeightedWeightingModifierRefData(Base): em_aalpha: Optional[DictBase] = field(default=None, metadata=config(field_name='EMAalpha', exclude=exclude_none)) look_back_period: Optional[DictBase] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BacktestStrategyUnderlierHedge(Base): risk_details: Optional[DeltaHedgeParameters] = field(default=None, metadata=field_metadata) quantity_percentage: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BasketBacktestParameters(Base): underliers: Tuple[Union[float, str], ...] = field(default=None, metadata=field_metadata) rebalance_parameters: Optional[BacktestRebalanceParameters] = field(default=None, metadata=field_metadata) weighting_modifiers: Optional[Tuple[VolatilityWeightedWeightingModifier, ...]] = field(default=None, metadata=field_metadata) weighting_strategy: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CurrencyRefData(Base): default: Optional[Currency] = field(default=None, metadata=field_metadata) enum: Optional[Tuple[Currency, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DeltaHedgingRefData(Base): fixing_time: Optional[FixingTimeRefData] = field(default=None, metadata=field_metadata) frequency: Optional[FrequencyRefData] = field(default=None, metadata=field_metadata) notional_percentage: Optional[NotionalPercentageRefData] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EnhancedBetaBacktestParameters(Base): underliers: Tuple[EnhancedBetaUnderlier, ...] = field(default=None, metadata=field_metadata) roll_start: float = field(default=None, metadata=field_metadata) roll_end: float = field(default=None, metadata=field_metadata) base_index: str = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ISelectBacktestParameters(Base): max_leverage: float = field(default=None, metadata=field_metadata) underliers: Tuple[HistoricalUnderlier, ...] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class OptionBacktestUnderlier(Base): buy_sell: str = field(default=None, metadata=field_metadata) expiration: str = field(default=None, metadata=field_metadata) option_type: str = field(default=None, metadata=field_metadata) option_strike_type: str = field(default=None, metadata=field_metadata) strike: float = field(default=None, metadata=field_metadata) underlying_asset_id: str = field(default=None, metadata=field_metadata) notional_percentage: Optional[float] = field(default=None, metadata=field_metadata) delta_hedging: Optional[DeltaHedgingParameters] = field(default=None, metadata=field_metadata) trade_in_time: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PerformanceRange(Base): horizon: Optional[str] = field(default=None, metadata=field_metadata) stats: Optional[PerformanceStats] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class UnderlyingAssetIdDataRefData(Base): asset_id: Optional[str] = field(default=None, metadata=field_metadata) fixing_time: Optional[FixingTimeRefData] = field(default=None, metadata=field_metadata) frequency: Optional[FrequencyRefData] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BacktestPerformanceDecomposition(Base): name: Optional[str] = field(default=None, metadata=field_metadata) performance: Optional[Tuple[FieldValueMap, ...]] = field(default=None, metadata=field_metadata) stats: Optional[PerformanceStats] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BacktestRisk(Base): name: Optional[str] = field(default=None, metadata=field_metadata) timeseries: Optional[Tuple[FieldValueMap, ...]] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BacktestRiskPosition(Base): instrument: DictBase = field(default=None, metadata=field_metadata) quantity: Optional[float] = field(default=None, metadata=field_metadata) market_model: Optional[EquityMarketModel] = field(default=None, metadata=field_metadata) expiry_date_mode: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BacktestStrategyTransactionCostModelAggregate(Base): models: Optional[Tuple[Union[BacktestStrategyTransactionCostModelFixed, BacktestStrategyTransactionCostModelScaled], ...]] = field(default=None, metadata=field_metadata) aggregation_type: Optional[str] = field(default=None, metadata=field_metadata) type_: Optional[str] = field(init=False, default='AggregateCostModel', metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BacktestStrategyTransactionCost(Base): entry: Optional[Union[BacktestStrategyTransactionCostModelAggregate, BacktestStrategyTransactionCostModelFixed, BacktestStrategyTransactionCostModelScaled]] = field(default=None, metadata=field_metadata) exit_: Optional[Union[BacktestStrategyTransactionCostModelAggregate, BacktestStrategyTransactionCostModelFixed, BacktestStrategyTransactionCostModelScaled]] = field(default=None, metadata=config(field_name='exit', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BacktestTradingParameters(Base): quantity_type: Optional[BacktestTradingQuantityType] = field(default=None, metadata=field_metadata) quantity: Optional[Union[BacktestStrategySignal, float]] = field(default=None, metadata=field_metadata) trade_in_method: Optional[str] = field(default=None, metadata=field_metadata) roll_frequency: Optional[str] = field(default=None, metadata=field_metadata) roll_date_mode: Optional[str] = field(default=None, metadata=field_metadata) scaling_method: Optional[str] = field(default=None, metadata=field_metadata) trade_in_signals: Optional[Tuple[BacktestSignalSeriesItem, ...]] = field(default=None, metadata=field_metadata) trade_out_signals: Optional[Tuple[BacktestSignalSeriesItem, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BasketBacktestRefData(Base): currency: Optional[CurrencyRefData] = field(default=None, metadata=field_metadata) look_back_period: Optional[LookBackPeriodRefData] = field(default=None, metadata=field_metadata) weighting_strategy: Optional[DictBase] = field(default=None, metadata=field_metadata) weighting_modifiers: Optional[DictBase] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ComparisonBacktestResult(Base): stats: Optional[PerformanceStats] = field(default=None, metadata=field_metadata) performance: Optional[Tuple[FieldValueMap, ...]] = field(default=None, metadata=field_metadata) id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EnhancedBetaRefData(Base): look_back_period: Optional[LookBackPeriodRefData] = field(default=None, metadata=field_metadata) currency: Optional[CurrencyRefData] = field(default=None, metadata=field_metadata) base_index: Optional[BaseIndexRefData] = field(default=None, metadata=field_metadata) MASJ8W49Y02X9CGS: Optional[DictBase] = field(default=None, metadata=field_metadata) MAAHST8JED9B607H: Optional[DictBase] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class UnderlyingAssetIdRefData(Base): default: Optional[str] = field(default=None, metadata=field_metadata) enum: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) data: Optional[Tuple[UnderlyingAssetIdDataRefData, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class VolatilityBacktestParameters(Base): underliers: Tuple[OptionBacktestUnderlier, ...] = field(default=None, metadata=field_metadata) trade_in_method: Optional[str] = field(default=None, metadata=field_metadata) scaling_method: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BacktestResult(Base): backtest_id: Optional[str] = field(default=None, metadata=field_metadata) performance: Optional[Tuple[FieldValueMap, ...]] = field(default=None, metadata=field_metadata) portfolio: Optional[Tuple[FieldValueMap, ...]] = field(default=None, metadata=field_metadata) stats: Optional[PerformanceStats] = field(default=None, metadata=field_metadata) performance_decompositions: Optional[Tuple[BacktestPerformanceDecomposition, ...]] = field(default=None, metadata=field_metadata) risks: Optional[Tuple[BacktestRisk, ...]] = field(default=None, metadata=field_metadata) events: Optional[Tuple[BacktestRisk, ...]] = field(default=None, metadata=field_metadata) history: Optional[Tuple[PerformanceRange, ...]] = field(default=None, metadata=field_metadata) underlier_correlation: Optional[Tuple[EntityCorrelation, ...]] = field(default=None, metadata=field_metadata) comparisons: Optional[Tuple[BacktestComparison, ...]] = field(default=None, metadata=field_metadata) backtest_version: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BacktestRiskRequest(Base): positions: Tuple[BacktestRiskPosition, ...] = field(default=None, metadata=field_metadata) measures: Tuple[BacktestRiskMeasureType, ...] = field(default=None, metadata=field_metadata) start_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) end_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) dates: Optional[Tuple[datetime.date, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BacktestStrategyUnderlierTransactionCost(Base): trade: Optional[BacktestStrategyTransactionCost] = field(default=None, metadata=field_metadata) hedge: Optional[BacktestStrategyTransactionCost] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class VolBacktestRefData(Base): buy_sell: Optional[BuySellRefData] = field(default=None, metadata=field_metadata) currency: Optional[CurrencyRefData] = field(default=None, metadata=field_metadata) delta_hedging: Optional[DeltaHedgingRefData] = field(default=None, metadata=field_metadata) delta_strike: Optional[StrikeRefData] = field(default=None, metadata=field_metadata) notional_percentage: Optional[NotionalPercentageRefData] = field(default=None, metadata=field_metadata) expiration: Optional[ExpirationRefData] = field(default=None, metadata=field_metadata) look_back_period: Optional[LookBackPeriodRefData] = field(default=None, metadata=field_metadata) option_type: Optional[OptionTypeRefData] = field(default=None, metadata=field_metadata) option_strike_type: Optional[OptionStrikeTypeRefData] = field(default=None, metadata=field_metadata) relative_strike: Optional[StrikeRefData] = field(default=None, metadata=field_metadata) strike: Optional[StrikeRefData] = field(default=None, metadata=field_metadata) scaling_method: Optional[ScalingMethodRefData] = field(default=None, metadata=field_metadata) underlying_asset_id: Optional[UnderlyingAssetIdRefData] = field(default=None, metadata=field_metadata) trade_in_method: Optional[TradeInMethodRefData] = field(default=None, metadata=field_metadata) trade_in_time: Optional[TradeInTimeRefData] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BacktestRefData(Base): id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) volatility: Optional[DictBase] = field(default=None, metadata=field_metadata) enhanced_beta: Optional[EnhancedBetaRefData] = field(default=None, metadata=config(field_name='enhanced_beta', exclude=exclude_none)) basket: Optional[BasketBacktestRefData] = field(default=None, metadata=field_metadata) owner_id: Optional[str] = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) entitlement_exclusions: Optional[EntitlementExclusions] = field(default=None, metadata=field_metadata) last_updated_by_id: Optional[str] = field(default=None, metadata=field_metadata) last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BacktestStrategyUnderlier(Base): instrument: DictBase = field(default=None, metadata=field_metadata) market_model: EquityMarketModel = field(default=None, metadata=field_metadata) notional_percentage: Optional[float] = field(default=None, metadata=field_metadata) expiry_date_mode: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=field_metadata) hedge: Optional[BacktestStrategyUnderlierHedge] = field(default=None, metadata=field_metadata) transaction_cost: Optional[BacktestStrategyUnderlierTransactionCost] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class VolatilityFlowBacktestParameters(Base): trading_parameters: BacktestTradingParameters = field(default=None, metadata=field_metadata) index_initial_value: float = field(default=None, metadata=field_metadata) underliers: Optional[Tuple[BacktestStrategyUnderlier, ...]] = field(default=None, metadata=field_metadata) measures: Optional[Tuple[FlowVolBacktestMeasure, ...]] = field(default=('ALL MEASURES',), metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Backtest(Base): name: str = field(default=None, metadata=field_metadata) type_: BacktestType = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) asset_class: AssetClass = field(default=None, metadata=field_metadata) cost_netting: Optional[bool] = field(default=False, metadata=field_metadata) created_by_id: Optional[str] = field(default=None, metadata=field_metadata) created_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) currency: Optional[Currency] = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) entitlement_exclusions: Optional[EntitlementExclusions] = field(default=None, metadata=field_metadata) id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) last_updated_by_id: Optional[str] = field(default=None, metadata=field_metadata) last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) mq_symbol: Optional[str] = field(default=None, metadata=field_metadata) owner_id: Optional[str] = field(default=None, metadata=field_metadata) report_ids: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) parameters: Optional[Union[BasketBacktestParameters, EnhancedBetaBacktestParameters, ISelectBacktestParameters, VolatilityBacktestParameters, VolatilityFlowBacktestParameters]] = field(default=None, metadata=field_metadata) start_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) end_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) version: Optional[float] = field(default=None, metadata=field_metadata) cash_accrual: Optional[bool] = field(default=True, metadata=field_metadata) ================================================ FILE: gs_quant/target/base_screener.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.base import * from gs_quant.common import * import datetime from typing import Dict, Optional, Tuple, Union from dataclasses import dataclass, field from dataclasses_json import LetterCase, config, dataclass_json @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ScreenerColumn(Base): column_name: str = field(default=None, metadata=field_metadata) entity_parameter: Optional[str] = field(default=None, metadata=field_metadata) expression: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ScreenerRow(Base): entity_id: str = field(default=None, metadata=field_metadata) entity_type: str = field(default=None, metadata=field_metadata) universe: Optional[bool] = field(default=None, metadata=field_metadata) expand: Optional[bool] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ScreenerData(Base): rows: Optional[Tuple[DataRow, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ScreenerParameters(Base): rows: Tuple[ScreenerRow, ...] = field(default=None, metadata=field_metadata) columns: Tuple[ScreenerColumn, ...] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Screener(Base): name: str = field(default=None, metadata=field_metadata) parameters: ScreenerParameters = field(default=None, metadata=field_metadata) id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) created_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) created_by_id: Optional[str] = field(default=None, metadata=field_metadata) last_updated_by_id: Optional[str] = field(default=None, metadata=field_metadata) owner_id: Optional[str] = field(default=None, metadata=field_metadata) description: Optional[str] = field(default=None, metadata=field_metadata) cron_schedule: Optional[str] = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) ================================================ FILE: gs_quant/target/charts.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.base import * from gs_quant.common import * import datetime from typing import Dict, Optional, Tuple, Union from dataclasses import dataclass, field from dataclasses_json import LetterCase, config, dataclass_json from enum import Enum class ChartAnnotationFontStyle(EnumBase, Enum): """Style of the font.""" italic = 'italic' normal = 'normal' class ChartAnnotationFontWeight(EnumBase, Enum): """Weight of the font.""" bold = 'bold' normal = 'normal' class ChartAnnotationLineType(EnumBase, Enum): """Type of the line.""" dashed = 'dashed' solid = 'solid' class ChartAnnotationRangeLabelType(EnumBase, Enum): """Type of the range label.""" withPct = 'withPct' withoutPct = 'withoutPct' class ChartAnnotationTextAlign(EnumBase, Enum): """Alignment of the text.""" center = 'center' left = 'left' right = 'right' class ChartAnnotationTextDecoration(EnumBase, Enum): """Decoration of the text.""" none = 'none' underline = 'underline' strike_through = 'strike-through' class ChartAnnotationType(EnumBase, Enum): """Type of the annotation.""" arrow = 'arrow' circle = 'circle' line = 'line' oval = 'oval' range = 'range' rect = 'rect' text = 'text' class ChartFill(EnumBase, Enum): """Chart Fill Type""" _None = 'None' Solid = 'Solid' Gradient = 'Gradient' class ChartLineDrawType(EnumBase, Enum): """Line Draw Type""" Area = 'Area' Bars = 'Bars' Candlesticks = 'Candlesticks' Lines = 'Lines' _None = 'None' StepAfter = 'StepAfter' StepBefore = 'StepBefore' StepLinear = 'StepLinear' Volumes = 'Volumes' class ChartLineType(EnumBase, Enum): """Line Type""" Bubble = 'Bubble' Solid = 'Solid' Knotted = 'Knotted' Dashed = 'Dashed' OHLC = 'OHLC' class ChartRegressionStrokeType(EnumBase, Enum): """Chart Regression Stroke Type""" Line = 'Line' Dash = 'Dash' class ChartRegressionType(EnumBase, Enum): """Chart Regression Type""" Linear = 'Linear' Exponential = 'Exponential' PlotData = 'PlotData' class ChartType(EnumBase, Enum): """Chart Type""" bar = 'bar' line = 'line' scatter = 'scatter' @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ChartDisplaySettings(Base): scatter_fill: Optional[bool] = field(default=None, metadata=field_metadata) bar_chart_type: Optional[str] = field(default=None, metadata=field_metadata) bar_padding: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ChartLabelSettings(Base): hide_last_value_labels: Optional[bool] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ChartProperties(Base): x: Optional[str] = field(default=None, metadata=field_metadata) x_label: Optional[str] = field(default=None, metadata=field_metadata) y: Optional[str] = field(default=None, metadata=field_metadata) y_label: Optional[str] = field(default=None, metadata=field_metadata) color: Optional[str] = field(default=None, metadata=field_metadata) shape: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ChartShare(Base): guids: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) version: Optional[int] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ChartTime(Base): start: Optional[str] = field(default=None, metadata=field_metadata) end: Optional[str] = field(default=None, metadata=field_metadata) timezone: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ParameterField(Base): field_: str = field(default=None, metadata=config(field_name='field', exclude=exclude_none)) values: Tuple[str, ...] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class XAxisSettings(Base): auto_fit_range_to_data: Optional[bool] = field(default=None, metadata=field_metadata) label: Optional[str] = field(default=None, metadata=field_metadata) show_grid_lines: Optional[bool] = field(default=None, metadata=field_metadata) ignore_nil_date: Optional[bool] = field(default=None, metadata=field_metadata) x_axis_date_format: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class YAxisSettings(Base): decimal_precision: Optional[int] = field(default=None, metadata=field_metadata) id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) label: Optional[str] = field(default=None, metadata=field_metadata) label_format: Optional[str] = field(default=None, metadata=field_metadata) max_: Optional[float] = field(default=None, metadata=config(field_name='max', exclude=exclude_none)) min_: Optional[float] = field(default=None, metadata=config(field_name='min', exclude=exclude_none)) show_grid_lines: Optional[bool] = field(default=None, metadata=field_metadata) hide: Optional[bool] = field(default=None, metadata=field_metadata) invert_axis: Optional[bool] = field(default=None, metadata=field_metadata) show_label: Optional[bool] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ChartAnnotation(Base): id_: str = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) type_: Optional[ChartAnnotationType] = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) chart_type: Optional[ChartType] = field(default=None, metadata=field_metadata) start_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) end_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) start_value: Optional[str] = field(default=None, metadata=field_metadata) end_value: Optional[str] = field(default=None, metadata=field_metadata) color: Optional[str] = field(default=None, metadata=field_metadata) is_proportional: Optional[bool] = field(default=False, metadata=field_metadata) fill_color: Optional[str] = field(default=None, metadata=field_metadata) font_size: Optional[float] = field(default=None, metadata=field_metadata) font_style: Optional[ChartAnnotationFontStyle] = field(default=None, metadata=field_metadata) font_weight: Optional[ChartAnnotationFontWeight] = field(default=None, metadata=field_metadata) label: Optional[str] = field(default=None, metadata=field_metadata) line_height: Optional[float] = field(default=None, metadata=field_metadata) line_type: Optional[ChartAnnotationLineType] = field(default=None, metadata=field_metadata) line_width: Optional[float] = field(default=None, metadata=field_metadata) radius: Optional[float] = field(default=None, metadata=field_metadata) range_label_type: Optional[ChartAnnotationRangeLabelType] = field(default=None, metadata=field_metadata) text_align: Optional[ChartAnnotationTextAlign] = field(default=None, metadata=field_metadata) text_decoration: Optional[ChartAnnotationTextDecoration] = field(default=None, metadata=field_metadata) text_width: Optional[float] = field(default=None, metadata=field_metadata) text_height: Optional[float] = field(default=None, metadata=field_metadata) y_axis_index: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) class ChartControls(DictBase): pass @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ChartExpression(Base): axis: Optional[str] = field(default=None, metadata=field_metadata) color: Optional[str] = field(default=None, metadata=field_metadata) delete_gap: Optional[bool] = field(default=None, metadata=field_metadata) delete_weekends: Optional[bool] = field(default=None, metadata=field_metadata) digits: Optional[float] = field(default=None, metadata=field_metadata) disable: Optional[bool] = field(default=None, metadata=field_metadata) fill: Optional[ChartFill] = field(default=None, metadata=field_metadata) has_x_grid: Optional[bool] = field(default=None, metadata=field_metadata) has_y_grid: Optional[bool] = field(default=None, metadata=field_metadata) show_statistics: Optional[bool] = field(default=None, metadata=field_metadata) hide: Optional[bool] = field(default=None, metadata=field_metadata) label: Optional[str] = field(default=None, metadata=field_metadata) line_type: Optional[ChartLineType] = field(default=None, metadata=field_metadata) line_draw_type: Optional[ChartLineDrawType] = field(default=None, metadata=field_metadata) line_transparency: Optional[float] = field(default=None, metadata=field_metadata) line_width: Optional[float] = field(default=None, metadata=field_metadata) x_label: Optional[str] = field(default=None, metadata=field_metadata) y_label: Optional[str] = field(default=None, metadata=field_metadata) default_gradient: Optional[bool] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ChartRegression(Base): type_: Optional[ChartRegressionType] = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) line_data_index: Optional[int] = field(default=None, metadata=field_metadata) is_visible: Optional[bool] = field(default=None, metadata=field_metadata) show_statistical_info: Optional[bool] = field(default=None, metadata=field_metadata) stroke_color: Optional[str] = field(default=None, metadata=field_metadata) stroke_type: Optional[ChartRegressionStrokeType] = field(default=None, metadata=field_metadata) stroke_width: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ConstructorParameter(Base): type_: str = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) values: Optional[Tuple[ParameterField, ...]] = field(default=None, metadata=field_metadata) options: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class TemplateVariable(Base): name: str = field(default=None, metadata=field_metadata) display_name: str = field(default=None, metadata=field_metadata) constructor_type: str = field(default=None, metadata=field_metadata) parameters: ConstructorParameter = field(default=None, metadata=field_metadata) default_value: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) hide: Optional[bool] = field(default=False, metadata=field_metadata) tooltip: Optional[str] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Chart(Base): name: str = field(default=None, metadata=field_metadata) id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) owner_id: Optional[str] = field(default=None, metadata=field_metadata) created_by_id: Optional[str] = field(default=None, metadata=field_metadata) created_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) last_updated_by_id: Optional[str] = field(default=None, metadata=field_metadata) last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) entitlement_exclusions: Optional[EntitlementExclusions] = field(default=None, metadata=field_metadata) title: Optional[str] = field(default=None, metadata=field_metadata) subtitle: Optional[str] = field(default=None, metadata=field_metadata) rank: Optional[int] = field(default=None, metadata=field_metadata) folder_name: Optional[str] = field(default=None, metadata=field_metadata) description: Optional[str] = field(default=None, metadata=field_metadata) description_history: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) expressions: Optional[Tuple[ChartExpression, ...]] = field(default=None, metadata=field_metadata) chart_type: Optional[ChartType] = field(default=None, metadata=field_metadata) chart_properties: Optional[Tuple[ChartProperties, ...]] = field(default=None, metadata=field_metadata) regression_properties: Optional[Tuple[ChartRegression, ...]] = field(default=None, metadata=field_metadata) real_time: Optional[bool] = field(default=None, metadata=field_metadata) conversation_id: Optional[str] = field(default=None, metadata=field_metadata) show_controls_toolbar: Optional[bool] = field(default=None, metadata=field_metadata) interval: Optional[str] = field(default=None, metadata=field_metadata) relative_start_date: Optional[str] = field(default=None, metadata=field_metadata) relative_end_date: Optional[str] = field(default=None, metadata=field_metadata) start_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) end_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) start_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) end_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) tags: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) auto_tags: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) show_statistics: Optional[bool] = field(default=None, metadata=field_metadata) copy_from_id: Optional[str] = field(default=None, metadata=field_metadata) version: Optional[int] = field(default=None, metadata=field_metadata) draft_view_id: Optional[str] = field(default=None, metadata=field_metadata) label_settings: Optional[ChartLabelSettings] = field(default=None, metadata=field_metadata) time_settings: Optional[ChartTime] = field(default=None, metadata=field_metadata) x_axis_settings: Optional[XAxisSettings] = field(default=None, metadata=field_metadata) display_settings: Optional[ChartDisplaySettings] = field(default=None, metadata=field_metadata) y_axes_settings: Optional[Tuple[YAxisSettings, ...]] = field(default=None, metadata=field_metadata) annotations: Optional[Tuple[ChartAnnotation, ...]] = field(default=None, metadata=field_metadata) template_variables: Optional[DictBase] = field(default=None, metadata=field_metadata) parameters: Optional[ChartControls] = field(default=None, metadata=field_metadata) controls: Optional[Tuple[Union[ChartControls, bool, float, str], ...]] = field(default=None, metadata=field_metadata) ai: Optional[str] = field(default=None, metadata=field_metadata) ai_user_prompt: Optional[str] = field(default=None, metadata=field_metadata) ================================================ FILE: gs_quant/target/common.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.base import * import datetime from typing import Optional, Tuple, Union from dataclasses import dataclass, field from dataclasses_json import LetterCase, config, dataclass_json from enum import Enum class AccrualConvention(EnumBase, Enum): Adjusted = 'Adjusted' Unadjusted = 'Unadjusted' class AccumOrDecum(EnumBase, Enum): Accum = 'Accum' Decum = 'Decum' Non_Standard = 'Non-Standard' class AccumulatorType(EnumBase, Enum): Terminating = 'Terminating' Non_Terminating = 'Non-Terminating' class AggregationLevel(EnumBase, Enum): """Aggregation Level""" Type = 'Type' Asset = 'Asset' Class = 'Class' Point = 'Point' class AssetClass(EnumBase, Enum): """Asset classification of security. Assets are classified into broad groups which exhibit similar characteristics and behave in a consistent way under different market conditions""" Cash = 'Cash' Commod = 'Commod' Credit = 'Credit' Cross_Asset = 'Cross Asset' Digital_Asset = 'Digital Asset' Debt = 'Debt' Econ = 'Econ' Equity = 'Equity' Fund = 'Fund' FX = 'FX' ListedDerivative = 'ListedDerivative' Mortgage = 'Mortgage' Rates = 'Rates' Repo = 'Repo' Loan = 'Loan' Social = 'Social' Cryptocurrency = 'Cryptocurrency' class AssetStatsPeriod(EnumBase, Enum): """The period used to produce date range.""" _1y = '1y' _3y = '3y' _5y = '5y' _10y = '10y' class AssetStatsType(EnumBase, Enum): """Is it rolling, none etc.""" Rolling = 'Rolling' Calendar = 'Calendar' YTD = 'YTD' class AssetType(EnumBase, Enum): """Asset type differentiates the product categorization or contract type""" Access = 'Access' Accumulator = 'Accumulator' AccumulatorScheduleLeg = 'AccumulatorScheduleLeg' AssetSwapFxdFlt = 'AssetSwapFxdFlt' AssetSwapFxdFxd = 'AssetSwapFxdFxd' Any = 'Any' AsianOption = 'AsianOption' Autoroll = 'Autoroll' AveragePriceOption = 'AveragePriceOption' Barrier = 'Barrier' Basis = 'Basis' BasisSwap = 'BasisSwap' Benchmark = 'Benchmark' Benchmark_Rate = 'Benchmark Rate' Binary = 'Binary' BinaryDoubleKnockout = 'BinaryDoubleKnockout' Bond = 'Bond' Bond_Forward = 'Bond Forward' BondFuture = 'BondFuture' BondFutureOption = 'BondFutureOption' BondOption = 'BondOption' Calendar_Spread = 'Calendar Spread' Cap = 'Cap' CapOrFloor = 'CapOrFloor' Cash = 'Cash' Certificate = 'Certificate' CD = 'CD' Cliquet = 'Cliquet' CMSOption = 'CMSOption' CMSOptionStrip = 'CMSOptionStrip' CMSSpreadOption = 'CMSSpreadOption' CMSSpreadOptionStrip = 'CMSSpreadOptionStrip' Coin = 'Coin' Commodity = 'Commodity' CommodityReferencePrice = 'CommodityReferencePrice' CommodVarianceSwap = 'CommodVarianceSwap' CommodityPowerNode = 'CommodityPowerNode' CommodityPowerAggregatedNodes = 'CommodityPowerAggregatedNodes' CommodityEUNaturalGasHub = 'CommodityEUNaturalGasHub' CommodityNaturalGasHub = 'CommodityNaturalGasHub' Company = 'Company' ContractDivOption = 'ContractDivOption' Convertible = 'Convertible' CorrelationSwap = 'CorrelationSwap' CorrelationSwapLeg = 'CorrelationSwapLeg' Credit_Basket = 'Credit Basket' Cross = 'Cross' CSL = 'CSL' Currency = 'Currency' Custom_Basket = 'Custom Basket' Cryptocurrency = 'Cryptocurrency' Default_Swap = 'Default Swap' Digital = 'Digital' DiscreteLock = 'DiscreteLock' DoubleKnockout = 'DoubleKnockout' DoubleTouch = 'DoubleTouch' DualDoubleKnockout = 'DualDoubleKnockout' DualDoubleKnockoutLeg = 'DualDoubleKnockoutLeg' Economic = 'Economic' Endowment = 'Endowment' Equity_Basket = 'Equity Basket' EuropeanKnockout = 'EuropeanKnockout' ETF = 'ETF' ETN = 'ETN' Event = 'Event' FRA = 'FRA' FixedLeg = 'FixedLeg' Fixing = 'Fixing' FloatLeg = 'FloatLeg' Floor = 'Floor' Forward = 'Forward' ForwardVarianceSwap = 'ForwardVarianceSwap' ForwardVolatilityAgreement = 'ForwardVolatilityAgreement' Fund = 'Fund' Future = 'Future' FutureContract = 'FutureContract' FutureMarket = 'FutureMarket' FutureOption = 'FutureOption' FutureStrategy = 'FutureStrategy' FXForward = 'FXForward' Hedge_Fund = 'Hedge Fund' Index = 'Index' IndexOption = 'IndexOption' IndexSwap = 'IndexSwap' InflationSwap = 'InflationSwap' Inter_Commodity_Spread = 'Inter-Commodity Spread' InvoiceSpread = 'InvoiceSpread' Knockout = 'Knockout' ListedOption = 'ListedOption' ListedOptionPeriod = 'ListedOptionPeriod' ListedSwap = 'ListedSwap' ListedSwapPeriod = 'ListedSwapPeriod' LockedLadder = 'LockedLadder' MacroBasket = 'MacroBasket' Market_Location = 'Market Location' MLF = 'MLF' Multi_Asset_Allocation = 'Multi-Asset Allocation' MultiCrossBinary = 'MultiCrossBinary' MultiCrossBinaryLeg = 'MultiCrossBinaryLeg' MultiCrossDoubleBinary = 'MultiCrossDoubleBinary' MultiCrossDoubleBinaryLeg = 'MultiCrossDoubleBinaryLeg' MultiCrossDoubleTouch = 'MultiCrossDoubleTouch' MultiCrossDoubleTouchLeg = 'MultiCrossDoubleTouchLeg' Mutual_Fund = 'Mutual Fund' Native_Asset = 'Native Asset' Note = 'Note' OneTouch = 'OneTouch' Option = 'Option' OptionLeg = 'OptionLeg' OptionPeriod = 'OptionPeriod' OptionStrategy = 'OptionStrategy' Peer_Group = 'Peer Group' Pension_Fund = 'Pension Fund' Pivot = 'Pivot' PivotScheduleLeg = 'PivotScheduleLeg' Preferred_Stock = 'Preferred Stock' Physical = 'Physical' Precious_Metal = 'Precious Metal' Precious_Metal_Swap = 'Precious Metal Swap' Precious_Metal_RFQ = 'Precious Metal RFQ' QuantoOption = 'QuantoOption' Reference_Entity = 'Reference Entity' Research_Basket = 'Research Basket' Rate = 'Rate' Risk_Premia = 'Risk Premia' Roll = 'Roll' Securities_Lending_Loan = 'Securities Lending Loan' Share_Class = 'Share Class' Single_Stock = 'Single Stock' ShiftingBermForward = 'ShiftingBermForward' Swap = 'Swap' SwapData = 'SwapData' SwapLeg = 'SwapLeg' SwapLPeriod = 'SwapLPeriod' SwapPeriod = 'SwapPeriod' SwapStrategy = 'SwapStrategy' Swaption = 'Swaption' Synthetic = 'Synthetic' SyntheticDateInfo = 'SyntheticDateInfo' Systematic_Hedging = 'Systematic Hedging' Tarf = 'Tarf' TarfScheduleLeg = 'TarfScheduleLeg' Token = 'Token' VarianceSwap = 'VarianceSwap' VolatilityKnockout = 'VolatilityKnockout' VolatilitySwap = 'VolatilitySwap' VolVarSwap = 'VolVarSwap' WeatherIndex = 'WeatherIndex' WindowDoubleKnockout = 'WindowDoubleKnockout' WindowKnockout = 'WindowKnockout' WorstOf = 'WorstOf' WorstOfLeg = 'WorstOfLeg' WorstOfKO = 'WorstOfKO' WorstOfKOLeg = 'WorstOfKOLeg' XccySwap = 'XccySwap' XccySwapFixFix = 'XccySwapFixFix' XccySwapFixFlt = 'XccySwapFixFlt' XccySwapMTM = 'XccySwapMTM' SyntheticOETTerms = 'SyntheticOETTerms' SyntheticSchedule = 'SyntheticSchedule' SyntheticLeg = 'SyntheticLeg' class AswType(EnumBase, Enum): """Asset Swap Type""" Par = 'Par' Proceeds = 'Proceeds' class BasketAction(EnumBase, Enum): """Indicates what was the action taken on basket - create/edit/rebalance""" Create = 'Create' Edit = 'Edit' Rebalance = 'Rebalance' class BasketValuationSource(EnumBase, Enum): """The source of basket pricing""" GS = 'GS' BVAL = 'BVAL' CBBT = 'CBBT' class BestWorst(EnumBase, Enum): Best = 'Best' Worst = 'Worst' class BondStrikeType(EnumBase, Enum): """The type of the bond strike - price, yield etc""" Price = 'Price' Yield = 'Yield' class BusinessDayConvention(EnumBase, Enum): """Business Day Convention""" Following = 'Following' Modified_Following = 'Modified Following' Previous = 'Previous' Unadjusted = 'Unadjusted' class BuySell(EnumBase, Enum): """Buy or Sell side of contract""" Buy = 'Buy' Sell = 'Sell' class CDOptionType(EnumBase, Enum): """Credit Option Type""" Call = 'Call' Put = 'Put' Straddle = 'Straddle' Payer = 'Payer' Receiver = 'Receiver' Digital_Call = 'Digital Call' Digital_Put = 'Digital Put' class CapOrFloor(EnumBase, Enum): Cap = 'Cap' Floor = 'Floor' Straddle = 'Straddle' class CashReinvestmentTreatmentType(EnumBase, Enum): """Cash reinvestment treatment for cash acquisitions and dividends.""" Reinvest_At_Open = 'Reinvest At Open' Add_To_Index = 'Add To Index' class CommodMeanRule(EnumBase, Enum): """Commodity mean rule""" Do_Not_Remove = 'Do Not Remove' Remove_Calculated = 'Remove Calculated' Remove_Fixed = 'Remove Fixed' class CommodUnit(EnumBase, Enum): """A coding scheme value to identify the unit of measure (e.g. Therms) in which the undelryer is denominated.""" Lot = 'Lot' MegaWattHour = 'MegaWattHour' Metric_Ton = 'Metric Ton' Million_British_Thermal_Units = 'Million British Thermal Units' Oil_Barrel = 'Oil Barrel' Troy_Pound = 'Troy Pound' US_Gallon = 'US Gallon' class CommoditySector(EnumBase, Enum): """The sector of the commodity""" Base_metals = 'Base metals' Precious_metals = 'Precious metals' Energy = 'Energy' Agriculturals = 'Agriculturals' Power = 'Power' class CountryCode(EnumBase, Enum): """ISO Country code""" AU = 'AU' CX = 'CX' CC = 'CC' HM = 'HM' NF = 'NF' NZ = 'NZ' CK = 'CK' NU = 'NU' TK = 'TK' JP = 'JP' JN = 'JN' EU = 'EU' ER = 'ER' EZ = 'EZ' AT = 'AT' BE = 'BE' FI = 'FI' FR = 'FR' GF = 'GF' PF = 'PF' TF = 'TF' GP = 'GP' MQ = 'MQ' YT = 'YT' NC = 'NC' RE = 'RE' SH = 'SH' PM = 'PM' WF = 'WF' DE = 'DE' GE = 'GE' GR = 'GR' IE = 'IE' IT = 'IT' LU = 'LU' NL = 'NL' AW = 'AW' AN = 'AN' PT = 'PT' ES = 'ES' BY = 'BY' CH = 'CH' SE = 'SE' SW = 'SW' DK = 'DK' FO = 'FO' NO = 'NO' BV = 'BV' SJ = 'SJ' LI = 'LI' GB = 'GB' UK = 'UK' AI = 'AI' IO = 'IO' KY = 'KY' FK = 'FK' GI = 'GI' MS = 'MS' PN = 'PN' GS = 'GS' TC = 'TC' VG = 'VG' JE = 'JE' _02 = '02' US = 'US' AS = 'AS' GU = 'GU' MP = 'MP' PR = 'PR' UM = 'UM' VI = 'VI' CA = 'CA' AR = 'AR' BA = 'BA' BD = 'BD' BG = 'BG' BS = 'BS' BM = 'BM' BO = 'BO' BR = 'BR' CL = 'CL' CN = 'CN' CO = 'CO' CR = 'CR' CZ = 'CZ' DO = 'DO' EC = 'EC' EG = 'EG' GA = 'GA' GT = 'GT' HK = 'HK' HR = 'HR' HU = 'HU' IL = 'IL' IM = 'IM' IR = 'IR' IS = 'IS' JO = 'JO' KE = 'KE' KR = 'KR' KZ = 'KZ' LB = 'LB' LK = 'LK' LT = 'LT' MA = 'MA' MH = 'MH' ML = 'ML' MO = 'MO' MT = 'MT' MX = 'MX' MY = 'MY' NI = 'NI' OM = 'OM' PA = 'PA' PD = 'PD' PE = 'PE' PH = 'PH' PK = 'PK' PL = 'PL' QA = 'QA' RO = 'RO' RU = 'RU' SA = 'SA' SG = 'SG' SI = 'SI' SK = 'SK' SV = 'SV' TH = 'TH' TN = 'TN' TP = 'TP' TR = 'TR' TW = 'TW' UA = 'UA' UY = 'UY' VE = 'VE' VN = 'VN' ZA = 'ZA' BH = 'BH' EE = 'EE' GH = 'GH' ME = 'ME' RS = 'RS' ZM = 'ZM' ZW = 'ZW' TT = 'TT' AE = 'AE' KW = 'KW' BB = 'BB' LV = 'LV' GG = 'GG' CY = 'CY' CI = 'CI' MU = 'MU' PY = 'PY' HN = 'HN' BZ = 'BZ' NA = 'NA' FJ = 'FJ' BW = 'BW' DZ = 'DZ' MN = 'MN' SN = 'SN' TZ = 'TZ' AD = 'AD' AG = 'AG' AL = 'AL' AM = 'AM' AO = 'AO' AZ = 'AZ' BF = 'BF' BI = 'BI' BJ = 'BJ' BN = 'BN' BT = 'BT' CD = 'CD' CF = 'CF' CG = 'CG' CM = 'CM' CU = 'CU' CV = 'CV' CS = 'CS' DJ = 'DJ' DM = 'DM' EH = 'EH' ET = 'ET' FM = 'FM' GD = 'GD' GL = 'GL' GM = 'GM' GN = 'GN' GQ = 'GQ' GW = 'GW' GY = 'GY' HT = 'HT' ID = 'ID' IN = 'IN' IQ = 'IQ' JM = 'JM' KG = 'KG' KH = 'KH' KI = 'KI' KM = 'KM' KN = 'KN' KP = 'KP' LA = 'LA' LC = 'LC' LR = 'LR' LS = 'LS' LY = 'LY' MC = 'MC' MD = 'MD' MG = 'MG' MK = 'MK' MM = 'MM' MR = 'MR' MV = 'MV' MW = 'MW' MZ = 'MZ' NE = 'NE' NG = 'NG' NP = 'NP' NR = 'NR' PG = 'PG' PW = 'PW' RW = 'RW' SB = 'SB' SC = 'SC' SD = 'SD' SL = 'SL' SM = 'SM' SO = 'SO' SR = 'SR' ST = 'ST' SY = 'SY' SZ = 'SZ' TD = 'TD' TG = 'TG' TJ = 'TJ' TL = 'TL' TM = 'TM' TO = 'TO' TV = 'TV' UG = 'UG' UZ = 'UZ' VA = 'VA' VC = 'VC' VU = 'VU' WS = 'WS' YE = 'YE' class CreditBasketType(EnumBase, Enum): """Credit basket type with regards to its constituents""" Corporate_Bond = 'Corporate Bond' CDS = 'CDS' Treasury = 'Treasury' Mixed = 'Mixed' Unknown = 'Unknown' class CreditOptionStrikeType(EnumBase, Enum): Spread_Adj = 'Spread Adj' Delta = 'Delta' class CreditOptionType(EnumBase, Enum): Payer = 'Payer' Receiver = 'Receiver' class Currency(EnumBase, Enum): """Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)""" _ = '' ACU = 'ACU' ADP = 'ADP' AED = 'AED' AFA = 'AFA' ALL = 'ALL' AMD = 'AMD' ANG = 'ANG' AOA = 'AOA' AOK = 'AOK' AON = 'AON' ARA = 'ARA' ARS = 'ARS' ARZ = 'ARZ' ATS = 'ATS' AUD = 'AUD' AUZ = 'AUZ' AWG = 'AWG' AZM = 'AZM' AZN = 'AZN' B03 = 'B03' BAD = 'BAD' BAK = 'BAK' BAM = 'BAM' BBD = 'BBD' BDN = 'BDN' BDT = 'BDT' BEF = 'BEF' BGL = 'BGL' BGN = 'BGN' BHD = 'BHD' BIF = 'BIF' BMD = 'BMD' BND = 'BND' BOB = 'BOB' BR6 = 'BR6' BRE = 'BRE' BRF = 'BRF' BRL = 'BRL' BRR = 'BRR' BSD = 'BSD' BTC = 'BTC' BTN = 'BTN' BTR = 'BTR' BWP = 'BWP' BYN = 'BYN' BYR = 'BYR' BZD = 'BZD' C23 = 'C23' CAC = 'CAC' CAD = 'CAD' CAZ = 'CAZ' CCI = 'CCI' CDF = 'CDF' CFA = 'CFA' CHF = 'CHF' CHZ = 'CHZ' CLF = 'CLF' CLP = 'CLP' CLZ = 'CLZ' CNH = 'CNH' CNO = 'CNO' CNY = 'CNY' CNZ = 'CNZ' COP = 'COP' COZ = 'COZ' CPB = 'CPB' CPI = 'CPI' CRC = 'CRC' CUP = 'CUP' CVE = 'CVE' CYP = 'CYP' CZH = 'CZH' CZK = 'CZK' DAX = 'DAX' DEM = 'DEM' DIJ = 'DIJ' DJF = 'DJF' DJE = 'DJE' DKK = 'DKK' DOP = 'DOP' DZD = 'DZD' E51 = 'E51' E52 = 'E52' E53 = 'E53' E54 = 'E54' ECI = 'ECI' ECS = 'ECS' ECU = 'ECU' EEK = 'EEK' EF0 = 'EF0' EGP = 'EGP' ESP = 'ESP' ETB = 'ETB' ETH = 'ETH' EUR = 'EUR' EUZ = 'EUZ' F06 = 'F06' FED = 'FED' FIM = 'FIM' FJD = 'FJD' FKP = 'FKP' FRF = 'FRF' FT1 = 'FT1' GBP = 'GBP' GBZ = 'GBZ' GEK = 'GEK' GEL = 'GEL' GHC = 'GHC' GHS = 'GHS' GHY = 'GHY' GIP = 'GIP' GLD = 'GLD' GLR = 'GLR' GMD = 'GMD' GNF = 'GNF' GQE = 'GQE' GRD = 'GRD' GTQ = 'GTQ' GWP = 'GWP' GYD = 'GYD' HKB = 'HKB' HKD = 'HKD' HNL = 'HNL' HRK = 'HRK' HSI = 'HSI' HTG = 'HTG' HUF = 'HUF' IDB = 'IDB' IDO = 'IDO' IDR = 'IDR' IEP = 'IEP' IGP = 'IGP' ILS = 'ILS' INO = 'INO' INP = 'INP' INR = 'INR' IPA = 'IPA' IPX = 'IPX' IQD = 'IQD' IRR = 'IRR' IRS = 'IRS' ISI = 'ISI' ISK = 'ISK' ISO = 'ISO' ITL = 'ITL' J05 = 'J05' JMD = 'JMD' JNI = 'JNI' JOD = 'JOD' JPY = 'JPY' JPZ = 'JPZ' JZ9 = 'JZ9' KES = 'KES' KGS = 'KGS' KHR = 'KHR' KMF = 'KMF' KOR = 'KOR' KPW = 'KPW' KRW = 'KRW' KWD = 'KWD' KYD = 'KYD' KZT = 'KZT' LAK = 'LAK' LBA = 'LBA' LBP = 'LBP' LHY = 'LHY' LKR = 'LKR' LRD = 'LRD' LSL = 'LSL' LSM = 'LSM' LTL = 'LTL' LUF = 'LUF' LVL = 'LVL' LYD = 'LYD' MAD = 'MAD' MDL = 'MDL' MGA = 'MGA' MGF = 'MGF' MKD = 'MKD' MMK = 'MMK' MNT = 'MNT' MOP = 'MOP' MRO = 'MRO' MRU = 'MRU' MTL = 'MTL' MTP = 'MTP' MUR = 'MUR' MVR = 'MVR' MWK = 'MWK' MXB = 'MXB' MXN = 'MXN' MXP = 'MXP' MXW = 'MXW' MXZ = 'MXZ' MYO = 'MYO' MYR = 'MYR' MZM = 'MZM' MZN = 'MZN' NAD = 'NAD' ND3 = 'ND3' NGF = 'NGF' NGI = 'NGI' NGN = 'NGN' NIC = 'NIC' NIO = 'NIO' NLG = 'NLG' NOK = 'NOK' NOZ = 'NOZ' NPR = 'NPR' NZD = 'NZD' NZZ = 'NZZ' O08 = 'O08' OMR = 'OMR' PAB = 'PAB' PEI = 'PEI' PEN = 'PEN' PEZ = 'PEZ' PGK = 'PGK' PHP = 'PHP' PKR = 'PKR' PLN = 'PLN' PLZ = 'PLZ' PSI = 'PSI' PTE = 'PTE' PYG = 'PYG' QAR = 'QAR' QOF = 'QOF' R2K = 'R2K' ROL = 'ROL' RON = 'RON' RSD = 'RSD' RUB = 'RUB' RUF = 'RUF' RUR = 'RUR' RWF = 'RWF' SAR = 'SAR' SBD = 'SBD' SCR = 'SCR' SDG = 'SDG' SDP = 'SDP' SDR = 'SDR' SEK = 'SEK' SET = 'SET' SGD = 'SGD' SGS = 'SGS' SHP = 'SHP' SIL = 'SIL' SIT = 'SIT' SKK = 'SKK' SLE = 'SLE' SLL = 'SLL' SRD = 'SRD' SRG = 'SRG' SSI = 'SSI' STD = 'STD' SUR = 'SUR' SVC = 'SVC' SVT = 'SVT' SYP = 'SYP' SZL = 'SZL' T21 = 'T21' T51 = 'T51' T52 = 'T52' T53 = 'T53' T54 = 'T54' T55 = 'T55' T71 = 'T71' TE0 = 'TE0' TED = 'TED' TF9 = 'TF9' THB = 'THB' THO = 'THO' TMM = 'TMM' TND = 'TND' TNT = 'TNT' TOF = 'TOF' TOP = 'TOP' TPE = 'TPE' TPX = 'TPX' TRB = 'TRB' TRL = 'TRL' TRY = 'TRY' TRZ = 'TRZ' TTD = 'TTD' TWD = 'TWD' TZS = 'TZS' UAH = 'UAH' UCB = 'UCB' UDI = 'UDI' UFC = 'UFC' UFZ = 'UFZ' UGS = 'UGS' UGX = 'UGX' USB = 'USB' USD = 'USD' UVR = 'UVR' UYP = 'UYP' UYU = 'UYU' UZS = 'UZS' VAC = 'VAC' VEB = 'VEB' VEF = 'VEF' VES = 'VES' VND = 'VND' VUV = 'VUV' WST = 'WST' XAF = 'XAF' XAG = 'XAG' XAU = 'XAU' XPD = 'XPD' XPT = 'XPT' XCD = 'XCD' XDR = 'XDR' XEU = 'XEU' XOF = 'XOF' XPF = 'XPF' YDD = 'YDD' YER = 'YER' YUD = 'YUD' YUN = 'YUN' ZAL = 'ZAL' ZAR = 'ZAR' ZAZ = 'ZAZ' ZMK = 'ZMK' ZMW = 'ZMW' ZRN = 'ZRN' ZRZ = 'ZRZ' ZWD = 'ZWD' ZWL = 'ZWL' AUd = 'AUd' BWp = 'BWp' EUr = 'EUr' GBp = 'GBp' ILs = 'ILs' KWd = 'KWd' MWk = 'MWk' SGd = 'SGd' SZl = 'SZl' USd = 'USd' ZAr = 'ZAr' class CurrencyName(EnumBase, Enum): """Currency Names""" United_States_Dollar = 'United States Dollar' Australian_Dollar = 'Australian Dollar' Canadian_Dollar = 'Canadian Dollar' Swiss_Franc = 'Swiss Franc' Yuan_Renminbi_Hong_Kong = 'Yuan Renminbi (Hong Kong)' Czech_Republic_Koruna = 'Czech Republic Koruna' Euro = 'Euro' Pound_Sterling = 'Pound Sterling' Japanese_Yen = 'Japanese Yen' South_Korean_Won = 'South Korean Won' Malasyan_Ringgit = 'Malasyan Ringgit' Norwegian_Krone = 'Norwegian Krone' New_Zealand_Dollar = 'New Zealand Dollar' Polish_Zloty = 'Polish Zloty' Russian_Rouble = 'Russian Rouble' Swedish_Krona = 'Swedish Krona' South_African_Rand = 'South African Rand' Yuan_Renminbi_Onshore = 'Yuan Renminbi (Onshore)' class DayCountFraction(EnumBase, Enum): """Day Count Fraction""" ACT_OVER_360 = 'ACT/360' ACT_OVER_360_ISDA = 'ACT/360 ISDA' ACT_OVER_365_Fixed = 'ACT/365 (Fixed)' ACT_OVER_365_Fixed_ISDA = 'ACT/365 Fixed ISDA' ACT_OVER_365L_ISDA = 'ACT/365L ISDA' ACT_OVER_ACT_ISDA = 'ACT/ACT ISDA' ACT_OVER_ACT_ISMA = 'ACT/ACT ISMA' _30_OVER_360 = '30/360' _30E_OVER_360 = '30E/360' class EqBasketHistoryMethodology(EnumBase, Enum): """Whether basket history should be backcasted, backtested, or uploaded manually after creation.""" Backcast = 'Backcast' Backtest = 'Backtest' Custom = 'Custom' class EqBasketHolidayCalendar(EnumBase, Enum): """Holiday calendar by exchange.""" NYSE = 'NYSE' LNSE = 'LNSE' HKGE = 'HKGE' class EqBasketRebalanceFrequencyRule(EnumBase, Enum): """Expected rebalance frequency for a basket.""" First_Biz_Day_of_Month = 'First Biz Day of Month' Last_Biz_Day_of_Month = 'Last Biz Day of Month' First_Biz_Day_of_Quarter = 'First Biz Day of Quarter' Last_Biz_Day_of_Quarter = 'Last Biz Day of Quarter' Last_Biz_Day_of_Quarter_first_Month = 'Last Biz Day of Quarter first Month' Biweekly_Based_on_Last_Rebal = 'Biweekly Based on Last Rebal' Custom = 'Custom' class FVAStrikeType(EnumBase, Enum): Delta_Neutral = 'Delta Neutral' ATM_Forward = 'ATM Forward' Delta = 'Delta' class FVAUnderlyingOptionType(EnumBase, Enum): Straddle = 'Straddle' Call = 'Call' Put = 'Put' class FallbackType(EnumBase, Enum): """Different Rules for Libor Fallback""" RFR = 'RFR' LastFixing = 'LastFixing' class Field(EnumBase, Enum): """Field to be returned""" investmentRate = 'investmentRate' startingEmmaLegalEntityId = 'startingEmmaLegalEntityId' bvalMidPrice = 'bvalMidPrice' mdapiClass = 'mdapiClass' totalNotionalUSD = 'totalNotionalUSD' bidUnadjusted = 'bidUnadjusted' poRisk = 'poRisk' navTargetQuantity = 'navTargetQuantity' invertedCross = 'invertedCross' aggressiveFillsPercentage = 'aggressiveFillsPercentage' future10yrMarketCap = 'future10yrMarketCap' vehicleType = 'vehicleType' totalFatalitiesByState = 'totalFatalitiesByState' newActive = 'newActive' dailyRisk = 'dailyRisk' energy = 'energy' currentConstituentsNetDebtToEbitda = 'currentConstituentsNetDebtToEbitda' sunshineDailyForecast = 'sunshineDailyForecast' sentimentScore = 'sentimentScore' customerBuySell = 'customerBuySell' assetParametersUnderlierType = 'assetParametersUnderlierType' annualizedZCRate = 'annualizedZCRate' fxQuotedDeltaNoPremiumAdjustment = 'fxQuotedDeltaNoPremiumAdjustment' settlementRequested = 'settlementRequested' _0 = '0' _1 = '1' _2 = '2' _3 = '3' correlation = 'correlation' exposure = 'exposure' size = 'size' _4 = '4' _5 = '5' _6 = '6' _7 = '7' marketDataAsset = 'marketDataAsset' _8 = '8' _9 = '9' buy75cents = 'buy75cents' unadjustedHigh = 'unadjustedHigh' sourceImportance = 'sourceImportance' closingYield = 'closingYield' wind = 'wind' sc16 = 'sc16' sc15 = 'sc15' sc12 = 'sc12' sc11 = 'sc11' primaryVwapInLimitUnrealizedBps = 'primaryVwapInLimitUnrealizedBps' displayName = 'displayName' minutesToTrade100Pct = 'minutesToTrade100Pct' sc14 = 'sc14' cumulativeVolumeInShares = 'cumulativeVolumeInShares' sc13 = 'sc13' newFatalities = 'newFatalities' buy50bps = 'buy50bps' numStaffedBeds = 'numStaffedBeds' upfrontPayment = 'upfrontPayment' annualizedRMCTRisk = 'annualizedRMCTRisk' arrivalMidRealizedCash = 'arrivalMidRealizedCash' sc10 = 'sc10' lowLeverage = 'lowLeverage' sc05 = 'sc05' lastTradingDateRule = 'lastTradingDateRule' a = 'a' sc04 = 'sc04' manualPricingTrader = 'manualPricingTrader' b = 'b' sc07 = 'sc07' c = 'c' yieldToMaturity = 'yieldToMaturity' sc06 = 'sc06' address = 'address' sc01 = 'sc01' leg2PaymentFrequency = 'leg2PaymentFrequency' sc03 = 'sc03' sc02 = 'sc02' geographyName = 'geographyName' borrower = 'borrower' shareClassType = 'shareClassType' settlePrice = 'settlePrice' currentConstituentsDividendYield = 'currentConstituentsDividendYield' assetParametersPutAmount = 'assetParametersPutAmount' assetParametersUnderlier = 'assetParametersUnderlier' performanceContribution = 'performanceContribution' nextRebalanceDate = 'nextRebalanceDate' sc09 = 'sc09' assetClassificationsDigitalAssetClass = 'assetClassificationsDigitalAssetClass' mktClass = 'mktClass' sc08 = 'sc08' collateralization = 'collateralization' futureMonthU26 = 'futureMonthU26' fxCalcDelta = 'fxCalcDelta' futureMonthU25 = 'futureMonthU25' futureMonthU24 = 'futureMonthU24' futureMonthU23 = 'futureMonthU23' futureMonthU22 = 'futureMonthU22' statementId = 'statementId' futureMonthU21 = 'futureMonthU21' assetParametersSettlementDate = 'assetParametersSettlementDate' modifiedDuration = 'modifiedDuration' impliedRetailSellPctShares = 'impliedRetailSellPctShares' vol180d = 'vol180d' fxQuotedDelta = 'fxQuotedDelta' shortRatesContribution = 'shortRatesContribution' impliedNormalVolatility = 'impliedNormalVolatility' solarGeneration = 'solarGeneration' requestedSide = 'requestedSide' mtmPrice = 'mtmPrice' swapSpreadChange = 'swapSpreadChange' realizedArrivalPerformanceUSD = 'realizedArrivalPerformanceUSD' portfolioAssets = 'portfolioAssets' tcmCostHorizon3Hour = 'tcmCostHorizon3Hour' exchangeRate = 'exchangeRate' potentialBedCapInc = 'potentialBedCapInc' numberCovered = 'numberCovered' numberOfPositions = 'numberOfPositions' openUnadjusted = 'openUnadjusted' strikeTime = 'strikeTime' askPrice = 'askPrice' eventId = 'eventId' sectors = 'sectors' additionalPriceNotationType = 'additionalPriceNotationType' grossInvestmentQtd = 'grossInvestmentQtd' annualizedRisk = 'annualizedRisk' estimatedHoldingTimeShort = 'estimatedHoldingTimeShort' impliedRetailBuyShares = 'impliedRetailBuyShares' outrightBid = 'outrightBid' midcurvePremium = 'midcurvePremium' volumeComposite = 'volumeComposite' sharpeQtd = 'sharpeQtd' clearingExceptionOrExemptionIndicator = 'clearingExceptionOrExemptionIndicator' estimatedHoldingTimeLong = 'estimatedHoldingTimeLong' external = 'external' trackerName = 'trackerName' sell50cents = 'sell50cents' tradePrice = 'tradePrice' cleared = 'cleared' primeIdNumeric = 'primeIdNumeric' buy8bps = 'buy8bps' totalNotionalLocal = 'totalNotionalLocal' cid = 'cid' totalConfirmedSeniorHome = 'totalConfirmedSeniorHome' ctdFwdPrice = 'ctdFwdPrice' sinkFactor = 'sinkFactor' assetParametersNotionalAmountInOtherCurrency = 'assetParametersNotionalAmountInOtherCurrency' assetParametersPair = 'assetParametersPair' temperatureForecast = 'temperatureForecast' primaryAssetClass = 'primaryAssetClass' bidHigh = 'bidHigh' pnlQtd = 'pnlQtd' buy50cents = 'buy50cents' impliedRetailPctNotional = 'impliedRetailPctNotional' sell4bps = 'sell4bps' receiverDayCountFraction = 'receiverDayCountFraction' auctionClosePercentage = 'auctionClosePercentage' targetPrice = 'targetPrice' bosInBpsDescription = 'bosInBpsDescription' lowPrice = 'lowPrice' adv22DayPct = 'adv22DayPct' contractMonthLetters = 'contractMonthLetters' gateType = 'gateType' currentRatio = 'currentRatio' matchedMaturitySwapSpread12m = 'matchedMaturitySwapSpread12m' priceRangeInTicksLabel = 'priceRangeInTicksLabel' ticker = 'ticker' notionalUnit = 'notionalUnit' tcmCostHorizon1Day = 'tcmCostHorizon1Day' approval = 'approval' testMeasure = 'testMeasure' fwdSpreadMode = 'fwdSpreadMode' optionLockOutPeriod = 'optionLockOutPeriod' executionTime = 'executionTime' retailActivity = 'retailActivity' sourceValueForecast = 'sourceValueForecast' leg2Spread = 'leg2Spread' annualizedMCTRisk = 'annualizedMCTRisk' shortConvictionLarge = 'shortConvictionLarge' leg1FloatingRateIndex = 'leg1FloatingRateIndex' ccgName = 'ccgName' multiTags = 'multiTags' bidGSpread = 'bidGSpread' dollarExcessReturn = 'dollarExcessReturn' gsn = 'gsn' tradeEndDate = 'tradeEndDate' receiverRateOption = 'receiverRateOption' gss = 'gss' percentOfMediandv1m = 'percentOfMediandv1m' lendables = 'lendables' sell75cents = 'sell75cents' optionAdjustedSpread = 'optionAdjustedSpread' optionAdjustedSwapSpread = 'optionAdjustedSwapSpread' bosInTicksLabel = 'bosInTicksLabel' positionSourceId = 'positionSourceId' buy1bps = 'buy1bps' activeIDAtClient = 'activeIDAtClient' buy3point5bps = 'buy3point5bps' gsSustainRegion = 'gsSustainRegion' absoluteReturnWtd = 'absoluteReturnWtd' deploymentId = 'deploymentId' tradedActiveID = 'tradedActiveID' assetParametersSeniority = 'assetParametersSeniority' askSpread = 'askSpread' flow = 'flow' futureMonthH26 = 'futureMonthH26' loanRebate = 'loanRebate' futureMonthH25 = 'futureMonthH25' period = 'period' indexCreateSource = 'indexCreateSource' futureMonthH24 = 'futureMonthH24' futureMonthH23 = 'futureMonthH23' futureMonthH22 = 'futureMonthH22' futureMonthH21 = 'futureMonthH21' nonUsdOis = 'nonUsdOis' realTWIContribution = 'realTWIContribution' averageExposure = 'averageExposure' mktAsset = 'mktAsset' leg2IndexLocation = 'leg2IndexLocation' twapUnrealizedBps = 'twapUnrealizedBps' fwdEbookPointSpreadAllInMultBid = 'fwdEbookPointSpreadAllInMultBid' quantityUnitOfMeasure = 'quantityUnitOfMeasure' lastUpdatedMessage = 'lastUpdatedMessage' loanValue = 'loanValue' optionAdjustedOISSpread = 'optionAdjustedOISSpread' clientFwdSpreadMultiplier = 'clientFwdSpreadMultiplier' totalReturnPrice = 'totalReturnPrice' valueCurrency = 'valueCurrency' weightedPercentInModel = 'weightedPercentInModel' initLoanSpreadRequired = 'initLoanSpreadRequired' electionPeriod = 'electionPeriod' fundingAskPrice = 'fundingAskPrice' historicalBeta = 'historicalBeta' bondRiskPremiumIndex = 'bondRiskPremiumIndex' hitRateYtd = 'hitRateYtd' girGsdeerGsfeer = 'girGsdeerGsfeer' numUnits = 'numUnits' assetParametersReceiverFrequency = 'assetParametersReceiverFrequency' expenseRatioGrossBps = 'expenseRatioGrossBps' relativePayoffWtd = 'relativePayoffWtd' ctdPrice = 'ctdPrice' paceOfRollNow = 'paceOfRollNow' product = 'product' leg2ReturnType = 'leg2ReturnType' agentLenderFee = 'agentLenderFee' baseUSDFwdRevenue = 'baseUSDFwdRevenue' assetParametersTradeAs = 'assetParametersTradeAs' disseminationId = 'disseminationId' optionStrikePrice = 'optionStrikePrice' precipitationType = 'precipitationType' lowerBound = 'lowerBound' entity = 'entity' active1yrMarketCap = 'active1yrMarketCap' arrivalMidNormalized = 'arrivalMidNormalized' underlyingAsset2 = 'underlyingAsset2' underlyingAsset1 = 'underlyingAsset1' legalEntity = 'legalEntity' performanceFee = 'performanceFee' orderState = 'orderState' actualDataQuality = 'actualDataQuality' indexRatio = 'indexRatio' traderDescription = 'traderDescription' queueInLotsLabel = 'queueInLotsLabel' adv10DayPct = 'adv10DayPct' longConvictionMedium = 'longConvictionMedium' relativeHitRateWtd = 'relativeHitRateWtd' dailyTrackingError = 'dailyTrackingError' sell140cents = 'sell140cents' sell10bps = 'sell10bps' aggressiveOffsetFromLast = 'aggressiveOffsetFromLast' longitude = 'longitude' newIcu = 'newIcu' marketCap = 'marketCap' entryType = 'entryType' weightedAverageMid = 'weightedAverageMid' clusterRegion = 'clusterRegion' valoren = 'valoren' indexName = 'indexName' averageExecutionPrice = 'averageExecutionPrice' assetParametersNumberOfOptions = 'assetParametersNumberOfOptions' usdFwdRevenue = 'usdFwdRevenue' proceedsAssetOISSwapSpread1m = 'proceedsAssetOISSwapSpread1m' payoffWtd = 'payoffWtd' basis = 'basis' investmentRateTrend = 'investmentRateTrend' grossInvestmentMtd = 'grossInvestmentMtd' _200 = '200' hedgeId = 'hedgeId' _201 = '201' sharpeMtd = 'sharpeMtd' _202 = '202' _203 = '203' tcmCostHorizon8Day = 'tcmCostHorizon8Day' _204 = '204' residualVariance = 'residualVariance' _205 = '205' restrictInternalDerivedData = 'restrictInternalDerivedData' _206 = '206' _207 = '207' _208 = '208' adv5DayPct = 'adv5DayPct' _209 = '209' midpointFillsPercentage = 'midpointFillsPercentage' riskPackages = 'riskPackages' openInterest = 'openInterest' turnoverCompositeUnadjusted = 'turnoverCompositeUnadjusted' fwdPoints = 'fwdPoints' relativeReturnWtd = 'relativeReturnWtd' units = 'units' payerRateOption = 'payerRateOption' assetClassificationsRiskCountryName = 'assetClassificationsRiskCountryName' extMktPoint3 = 'extMktPoint3' _210 = '210' _211 = '211' matchedMaturitySwapSpread = 'matchedMaturitySwapSpread' _212 = '212' cityName = 'cityName' _213 = '213' hourlyBucket = 'hourlyBucket' _214 = '214' averageImpliedVolatility = 'averageImpliedVolatility' totalHospitalizedWithSymptoms = 'totalHospitalizedWithSymptoms' _215 = '215' _216 = '216' _217 = '217' daysOpenRealizedCash = 'daysOpenRealizedCash' _218 = '218' _219 = '219' adjustedHighPrice = 'adjustedHighPrice' proceedsAssetOISSwapSpread = 'proceedsAssetOISSwapSpread' m2RPrice = 'm2RPrice' extMktPoint1 = 'extMktPoint1' direction = 'direction' extMktPoint2 = 'extMktPoint2' subRegionCode = 'subRegionCode' assetParametersFixedRate = 'assetParametersFixedRate' tsdbSyncedSymbol = 'tsdbSyncedSymbol' factorProportionOfRisk = 'factorProportionOfRisk' isEstimatedReturn = 'isEstimatedReturn' valueForecast = 'valueForecast' totalIcu = 'totalIcu' positionSourceType = 'positionSourceType' previousCloseUnrealizedCash = 'previousCloseUnrealizedCash' minimumDenomination = 'minimumDenomination' assetParametersStrikePriceRelative = 'assetParametersStrikePriceRelative' futureValueNotional = 'futureValueNotional' participationRate = 'participationRate' obfr = 'obfr' _220 = '220' _221 = '221' _222 = '222' buy9point5bps = 'buy9point5bps' _223 = '223' specificReturn = 'specificReturn' _224 = '224' _225 = '225' optionLockPeriod = 'optionLockPeriod' _226 = '226' esMomentumPercentile = 'esMomentumPercentile' _227 = '227' _228 = '228' exchangeCurrency = 'exchangeCurrency' advPercentage = 'advPercentage' _229 = '229' leg1AveragingMethod = 'leg1AveragingMethod' activeIDLatest = 'activeIDLatest' turnoverComposite = 'turnoverComposite' forecastDate = 'forecastDate' internalIndexCalcRegion = 'internalIndexCalcRegion' positionType = 'positionType' subAssetClass = 'subAssetClass' shortInterest = 'shortInterest' referencePeriod = 'referencePeriod' adjustedVolume = 'adjustedVolume' underlyingAssetIdType = 'underlyingAssetIdType' ctdFwdYield = 'ctdFwdYield' assetParametersStart = 'assetParametersStart' secDB = 'secDB' memoryUsed = 'memoryUsed' bpeQualityStars = 'bpeQualityStars' leg = 'leg' _230 = '230' _231 = '231' _232 = '232' ctd = 'ctd' _233 = '233' _234 = '234' _235 = '235' _236 = '236' _237 = '237' _238 = '238' _239 = '239' intendedParticipationRate = 'intendedParticipationRate' leg1PaymentType = 'leg1PaymentType' tradingPnl = 'tradingPnl' collateralValueRequired = 'collateralValueRequired' buy45bps = 'buy45bps' freeFloatMarketCapRatio = 'freeFloatMarketCapRatio' priceToEarningsPositive = 'priceToEarningsPositive' outrightAsk = 'outrightAsk' assetParametersPayerCurrency = 'assetParametersPayerCurrency' forecast = 'forecast' forecastValue = 'forecastValue' meanDailyVolume5d = 'meanDailyVolume5d' _240 = '240' pnl = 'pnl' _241 = '241' _242 = '242' _243 = '243' factorZScore = 'factorZScore' volumeInLimit = 'volumeInLimit' _244 = '244' isTerritory = 'isTerritory' fwdEbookRiskDirClientSellUnder = 'fwdEbookRiskDirClientSellUnder' _245 = '245' meanDailyVolume22d = 'meanDailyVolume22d' leg2DeliveryPoint = 'leg2DeliveryPoint' _246 = '246' _247 = '247' _248 = '248' _249 = '249' tcmCostHorizon4Day = 'tcmCostHorizon4Day' styles = 'styles' shortName = 'shortName' resetFrequency1 = 'resetFrequency1' buy4bps = 'buy4bps' resetFrequency2 = 'resetFrequency2' currentConstituentsPriceToSales = 'currentConstituentsPriceToSales' otherPriceTerm = 'otherPriceTerm' bidGspread = 'bidGspread' tradedMktFwdPointsMid = 'tradedMktFwdPointsMid' openPrice = 'openPrice' rfqState = 'rfqState' psId = 'psId' hitRateMtd = 'hitRateMtd' _250 = '250' _251 = '251' _252 = '252' _253 = '253' fairVolatility = 'fairVolatility' _254 = '254' dollarCross = 'dollarCross' _255 = '255' portfolioType = 'portfolioType' _256 = '256' optionExpirationRule = 'optionExpirationRule' _257 = '257' _258 = '258' _259 = '259' currency = 'currency' clusterClass = 'clusterClass' sell50bps = 'sell50bps' futureMonthM21 = 'futureMonthM21' bidSize = 'bidSize' coordinateId = 'coordinateId' arrivalMid = 'arrivalMid' _260 = '260' _261 = '261' _262 = '262' marginalContributionToRiskPercent = 'marginalContributionToRiskPercent' _263 = '263' _264 = '264' _265 = '265' _266 = '266' assetParametersExchangeCurrency = 'assetParametersExchangeCurrency' _267 = '267' candidateName = 'candidateName' _268 = '268' _269 = '269' positionDate = 'positionDate' impliedLognormalVolatility = 'impliedLognormalVolatility' vwapInLimitUnrealizedCash = 'vwapInLimitUnrealizedCash' ratingMoodys = 'ratingMoodys' futureMonthM26 = 'futureMonthM26' futureMonthM25 = 'futureMonthM25' futureMonthM24 = 'futureMonthM24' futureMonthM23 = 'futureMonthM23' developmentStatus = 'developmentStatus' futureMonthM22 = 'futureMonthM22' flowPct = 'flowPct' source = 'source' _270 = '270' _271 = '271' assetClassificationsCountryCode = 'assetClassificationsCountryCode' _272 = '272' settleDrop = 'settleDrop' _273 = '273' _274 = '274' _275 = '275' _276 = '276' dataSetSubCategory = 'dataSetSubCategory' _277 = '277' _278 = '278' sell9point5bps = 'sell9point5bps' _279 = '279' quantityBucket = 'quantityBucket' optionStyleSDR = 'optionStyleSDR' assetParametersIdentifier = 'assetParametersIdentifier' initialIndexLevel = 'initialIndexLevel' cvaDollarChargeAsk = 'cvaDollarChargeAsk' oeName = 'oeName' given = 'given' leg2DayCountConvention = 'leg2DayCountConvention' liquidityScoreSell = 'liquidityScoreSell' delistingDate = 'delistingDate' weight = 'weight' accruedInterest = 'accruedInterest' businessScope = 'businessScope' _280 = '280' _281 = '281' _282 = '282' wtdDegreeDays = 'wtdDegreeDays' _283 = '283' _284 = '284' _285 = '285' absoluteWeight = 'absoluteWeight' _286 = '286' _287 = '287' _288 = '288' measure = 'measure' _289 = '289' return30d = 'return30d' temperatureHourlyForecast = 'temperatureHourlyForecast' icebergTipRateType = 'icebergTipRateType' sharpeYtd = 'sharpeYtd' windSpeedForecast = 'windSpeedForecast' grossInvestmentYtd = 'grossInvestmentYtd' yieldPrice = 'yieldPrice' paymentFrequencyPeriodMultiplier2 = 'paymentFrequencyPeriodMultiplier2' paymentFrequencyPeriodMultiplier1 = 'paymentFrequencyPeriodMultiplier1' leg1TotalNotionalUnit = 'leg1TotalNotionalUnit' absoluteAttribution = 'absoluteAttribution' issuePrice = 'issuePrice' quantityCcy = 'quantityCcy' askHigh = 'askHigh' expectedDataQuality = 'expectedDataQuality' regionName = 'regionName' _290 = '290' _291 = '291' _292 = '292' _293 = '293' _294 = '294' _295 = '295' _296 = '296' valueRevised = 'valueRevised' _297 = '297' _298 = '298' _299 = '299' discretionUpperBound = 'discretionUpperBound' adjustedTradePrice = 'adjustedTradePrice' forecastTime = 'forecastTime' isoSubdivisionCodeAlpha2 = 'isoSubdivisionCodeAlpha2' ctdConversionFactor = 'ctdConversionFactor' impliedRetailNotionalw5kFilter = 'impliedRetailNotionalw5kFilter' proceedsAssetSwapSpread = 'proceedsAssetSwapSpread' isADR = 'isADR' issueDate = 'issueDate' serviceId = 'serviceId' yes = 'yes' gScore = 'gScore' impliedRetailNotionalw10kFilter = 'impliedRetailNotionalw10kFilter' marketValue = 'marketValue' entityId = 'entityId' notionalCurrency1 = 'notionalCurrency1' netDebtToEbitda = 'netDebtToEbitda' numUnitsUpper = 'numUnitsUpper' notionalCurrency2 = 'notionalCurrency2' inLimitParticipationRate = 'inLimitParticipationRate' spotMarketBid = 'spotMarketBid' pressureForecast = 'pressureForecast' paid = 'paid' fixedRate = 'fixedRate' short = 'short' time = 'time' buy4point5bps = 'buy4point5bps' sell30cents = 'sell30cents' eventEndDateTime = 'eventEndDateTime' leg1PaymentFrequency = 'leg1PaymentFrequency' cmId = 'cmId' taxonomy = 'taxonomy' buy45cents = 'buy45cents' measures = 'measures' seasonalAdjustment = 'seasonalAdjustment' clientDescription = 'clientDescription' assetParametersNotionalAmount = 'assetParametersNotionalAmount' currentConstituentsEarningsPerShare = 'currentConstituentsEarningsPerShare' rankWtd = 'rankWtd' underlyer = 'underlyer' createdTime = 'createdTime' return1yr = 'return1yr' matchingOrderFwdPointBid = 'matchingOrderFwdPointBid' identifier = 'identifier' priceUnit = 'priceUnit' tradeReportRefId = 'tradeReportRefId' subdivisionId = 'subdivisionId' tokenId = 'tokenId' primaryMarketVolume = 'primaryMarketVolume' unadjustedLow = 'unadjustedLow' buy160cents = 'buy160cents' portfolioId = 'portfolioId' zSpread = 'zSpread' floatingRateResetFrequencyPeriod2 = 'floatingRateResetFrequencyPeriod2' capFloorAtmFwdRate = 'capFloorAtmFwdRate' esPercentile = 'esPercentile' tdapi = 'tdapi' floatingRateResetFrequencyPeriod1 = 'floatingRateResetFrequencyPeriod1' locationCode = 'locationCode' yieldToConvention = 'yieldToConvention' rcic = 'rcic' nameRaw = 'nameRaw' simonAssetTags = 'simonAssetTags' hitRateQtd = 'hitRateQtd' primaryVolumeInLimit = 'primaryVolumeInLimit' precipitationDailyForecastPercent = 'precipitationDailyForecastPercent' aumEnd = 'aumEnd' mktFwdPointMid = 'mktFwdPointMid' premium = 'premium' low = 'low' crossGroup = 'crossGroup' reportRunTime = 'reportRunTime' fiveDayPriceChangeBps = 'fiveDayPriceChangeBps' holdings = 'holdings' precipitationDailyForecast = 'precipitationDailyForecast' fixedRecoveryCDSFinalPrice = 'fixedRecoveryCDSFinalPrice' priceMethod = 'priceMethod' assetParametersFixedRateFrequency = 'assetParametersFixedRateFrequency' oisXccy = 'oisXccy' clientFwdPointsBid = 'clientFwdPointsBid' daysOpen = 'daysOpen' buy110cents = 'buy110cents' highInterestCoverage = 'highInterestCoverage' averageSpreadBps = 'averageSpreadBps' buy55cents = 'buy55cents' assetParametersReceiverCurrency = 'assetParametersReceiverCurrency' underlyingAssetIdSDR = 'underlyingAssetIdSDR' futureMonthQ26 = 'futureMonthQ26' issueSize = 'issueSize' futureMonthQ25 = 'futureMonthQ25' futureMonthQ24 = 'futureMonthQ24' futureMonthQ23 = 'futureMonthQ23' futureMonthQ22 = 'futureMonthQ22' pendingLoanCount = 'pendingLoanCount' futureMonthQ21 = 'futureMonthQ21' priceSpotStopLossUnit = 'priceSpotStopLossUnit' priceRangeInTicksDescription = 'priceRangeInTicksDescription' tradeVolume = 'tradeVolume' primaryCountryRic = 'primaryCountryRic' chargeInBps = 'chargeInBps' optionExpirationFrequency = 'optionExpirationFrequency' assetParametersIndexVersion = 'assetParametersIndexVersion' assetParametersCommodityReferencePrice = 'assetParametersCommodityReferencePrice' isActive = 'isActive' useMachineLearning = 'useMachineLearning' growthScore = 'growthScore' bufferThreshold = 'bufferThreshold' buy120cents = 'buy120cents' matchedMaturitySwapRate = 'matchedMaturitySwapRate' underlyingAssetName = 'underlyingAssetName' primaryVwap = 'primaryVwap' exchangeTypeId = 'exchangeTypeId' basisSwapRate = 'basisSwapRate' exchangeCode = 'exchangeCode' group = 'group' assetParametersTerminationDate = 'assetParametersTerminationDate' estimatedSpread = 'estimatedSpread' yieldChangeOnDay = 'yieldChangeOnDay' created = 'created' autoTags = 'autoTags' tcmCost = 'tcmCost' sustainJapan = 'sustainJapan' historyStartDate = 'historyStartDate' bidSpread = 'bidSpread' usdQuantity = 'usdQuantity' currentConstituentsPriceToEarningsPositive = 'currentConstituentsPriceToEarningsPositive' percentageComplete = 'percentageComplete' marketSymbol = 'marketSymbol' hedgeTrackingError = 'hedgeTrackingError' assetParametersPutCurrency = 'assetParametersPutCurrency' termStatus = 'termStatus' windSpeedType = 'windSpeedType' strikePrice = 'strikePrice' lowInterestCoverage = 'lowInterestCoverage' parAssetSwapSpread12m = 'parAssetSwapSpread12m' tradeReportId = 'tradeReportId' adjustedOpenPrice = 'adjustedOpenPrice' countryId = 'countryId' point = 'point' pnlMtd = 'pnlMtd' totalReturns = 'totalReturns' lender = 'lender' annReturn1Year = 'annReturn1Year' ctdFwdDv01 = 'ctdFwdDv01' effYield7Day = 'effYield7Day' meetingDate = 'meetingDate' alias = 'alias' calendarSpreadMispricing = 'calendarSpreadMispricing' buy140cents = 'buy140cents' priceNotation2Type = 'priceNotation2Type' fundFocus = 'fundFocus' relativeStrike = 'relativeStrike' fwdEbookRiskDirClientBuyOver = 'fwdEbookRiskDirClientBuyOver' flagship = 'flagship' additionalPriceNotation = 'additionalPriceNotation' factorCategory = 'factorCategory' equityDelta = 'equityDelta' grossWeight = 'grossWeight' listed = 'listed' sell7bps = 'sell7bps' earningsRecordType = 'earningsRecordType' mean = 'mean' askYield = 'askYield' shockStyle = 'shockStyle' impliedRetailShares = 'impliedRetailShares' methodology = 'methodology' buy25cents = 'buy25cents' amountOutstanding = 'amountOutstanding' marketPnl = 'marketPnl' sustainAsiaExJapan = 'sustainAsiaExJapan' sell6point5bps = 'sell6point5bps' neighbourAssetId = 'neighbourAssetId' countIdeasYtd = 'countIdeasYtd' simonIntlAssetTags = 'simonIntlAssetTags' path = 'path' preferredRiskModel = 'preferredRiskModel' vwapUnrealizedCash = 'vwapUnrealizedCash' payoffMtd = 'payoffMtd' spread2 = 'spread2' bosInBpsLabel = 'bosInBpsLabel' spread1 = 'spread1' bosInBps = 'bosInBps' pointClass = 'pointClass' fxSpot = 'fxSpot' currentConstituentsPriceToBook = 'currentConstituentsPriceToBook' restrictNamedIndividuals = 'restrictNamedIndividuals' pricedBy = 'pricedBy' hedgeVolatility = 'hedgeVolatility' tags = 'tags' population = 'population' underlyingAssetId = 'underlyingAssetId' realLongRatesContribution = 'realLongRatesContribution' pctprices_return = 'pctprices_return' domain = 'domain' buy80cents = 'buy80cents' forwardTenor = 'forwardTenor' averagePrice = 'averagePrice' assetParametersTotalQuantity = 'assetParametersTotalQuantity' impliedRetailPctShares = 'impliedRetailPctShares' expectedUpdateTime = 'expectedUpdateTime' targetPriceRealizedBps = 'targetPriceRealizedBps' leg2FixedRate = 'leg2FixedRate' shareClassAssets = 'shareClassAssets' annuity = 'annuity' totalCount = 'totalCount' quoteType = 'quoteType' corporateActionStatus = 'corporateActionStatus' peggedTipSize = 'peggedTipSize' uid = 'uid' esPolicyPercentile = 'esPolicyPercentile' usdOis = 'usdOis' term = 'term' restrictInternalGsNtk = 'restrictInternalGsNtk' fairValueGapStandardDeviation = 'fairValueGapStandardDeviation' tcmCostParticipationRate100Pct = 'tcmCostParticipationRate100Pct' relativeUniverse = 'relativeUniverse' measureIdx = 'measureIdx' executedQuantity = 'executedQuantity' fredId = 'fredId' twiContribution = 'twiContribution' cloudCoverType = 'cloudCoverType' delisted = 'delisted' currentConstituentsPriceToCash = 'currentConstituentsPriceToCash' regionalFocus = 'regionalFocus' volumePrimary = 'volumePrimary' assetParametersPayerDesignatedMaturity = 'assetParametersPayerDesignatedMaturity' buy30cents = 'buy30cents' numLegs = 'numLegs' fundingBidPrice = 'fundingBidPrice' series = 'series' sell3bps = 'sell3bps' settlementPrice = 'settlementPrice' quarter = 'quarter' outrightMarketBid = 'outrightMarketBid' sell18bps = 'sell18bps' assetParametersFloatingRateOption = 'assetParametersFloatingRateOption' TRSAskPrice = 'TRSAskPrice' realizedVwapPerformanceBps = 'realizedVwapPerformanceBps' voteShare = 'voteShare' servicingCostShortPnl = 'servicingCostShortPnl' totalConfirmed = 'totalConfirmed' isLive = 'isLive' currentConstituentsEarningsPerSharePositive = 'currentConstituentsEarningsPerSharePositive' economicForecast = 'economicForecast' plotId = 'plotId' clusterDescription = 'clusterDescription' concentrationLimit = 'concentrationLimit' windSpeed = 'windSpeed' observationHour = 'observationHour' signal = 'signal' borrowerId = 'borrowerId' dataProduct = 'dataProduct' buy7point5bps = 'buy7point5bps' limitPrice = 'limitPrice' bmPrimeId = 'bmPrimeId' dataType = 'dataType' count = 'count' conviction = 'conviction' benchmarkMaturity = 'benchmarkMaturity' grossFlowNormalized = 'grossFlowNormalized' buy14bps = 'buy14bps' factorId = 'factorId' futureMonthV26 = 'futureMonthV26' stsFxCurrency = 'stsFxCurrency' futureMonthV25 = 'futureMonthV25' bidChange = 'bidChange' month = 'month' futureMonthV24 = 'futureMonthV24' investmentWtd = 'investmentWtd' futureMonthV23 = 'futureMonthV23' fixOrderRoutingRegion = 'fixOrderRoutingRegion' futureMonthV22 = 'futureMonthV22' futureMonthV21 = 'futureMonthV21' expiration = 'expiration' leg2ResetFrequency = 'leg2ResetFrequency' controversyScore = 'controversyScore' proceedAssetSwapSpread = 'proceedAssetSwapSpread' debtEquityRatio = 'debtEquityRatio' concentrationLevel = 'concentrationLevel' weightOfFaceValue = 'weightOfFaceValue' importance = 'importance' assetClassificationsGicsSector = 'assetClassificationsGicsSector' stsAssetName = 'stsAssetName' figi = 'figi' netExposureClassification = 'netExposureClassification' settlementMethod = 'settlementMethod' receiverDesignatedMaturity = 'receiverDesignatedMaturity' title = 'title' xRefTypeId = 'xRefTypeId' duration = 'duration' load = 'load' highLeverage = 'highLeverage' alpha = 'alpha' datasetId = 'datasetId' company = 'company' settlementFrequency = 'settlementFrequency' distAvg7Day = 'distAvg7Day' inRiskModel = 'inRiskModel' dailyNetShareholderFlowsPercent = 'dailyNetShareholderFlowsPercent' filledNotionalLocal = 'filledNotionalLocal' everHospitalized = 'everHospitalized' lastRebalanceApprovalId = 'lastRebalanceApprovalId' meetingNumber = 'meetingNumber' midGspread = 'midGspread' daysOpenUnrealizedBps = 'daysOpenUnrealizedBps' longLevel = 'longLevel' dataDescription = 'dataDescription' temperatureType = 'temperatureType' isSpecialDay = 'isSpecialDay' tradedUSDDiscountFactor = 'tradedUSDDiscountFactor' gsideid = 'gsideid' repoRate = 'repoRate' division = 'division' cloudCoverDailyForecast = 'cloudCoverDailyForecast' windSpeedDailyForecast = 'windSpeedDailyForecast' executionVenueType = 'executionVenueType' assetParametersFloatingRateDayCountFraction = 'assetParametersFloatingRateDayCountFraction' firstCloseDate = 'firstCloseDate' tradeAction = 'tradeAction' action = 'action' ctdYield = 'ctdYield' arrivalHaircutVwapNormalized = 'arrivalHaircutVwapNormalized' priceComponent = 'priceComponent' queueClockTimeDescription = 'queueClockTimeDescription' assetParametersReceiverDayCountFraction = 'assetParametersReceiverDayCountFraction' percentMidExecutionQuantity = 'percentMidExecutionQuantity' deltaStrike = 'deltaStrike' cloudCover = 'cloudCover' assetParametersNotionalCurrency = 'assetParametersNotionalCurrency' buy18bps = 'buy18bps' valueActual = 'valueActual' upi = 'upi' tradeRejectionReason = 'tradeRejectionReason' shortInterestNotional = 'shortInterestNotional' fixedRate1 = 'fixedRate1' collateralCurrency = 'collateralCurrency' originalCountry = 'originalCountry' fixedRate2 = 'fixedRate2' field = 'field' dailyMarkToMarketPnl = 'dailyMarkToMarketPnl' geographicFocus = 'geographicFocus' daysOpenRealizedBps = 'daysOpenRealizedBps' hedgeGroupId = 'hedgeGroupId' fxRiskPremiumIndex = 'fxRiskPremiumIndex' skew = 'skew' status = 'status' notionalCurrency = 'notionalCurrency' sustainEmergingMarkets = 'sustainEmergingMarkets' eventDateTime = 'eventDateTime' leg1DesignatedMaturity = 'leg1DesignatedMaturity' clientName = 'clientName' totalPrice = 'totalPrice' onBehalfOf = 'onBehalfOf' testType = 'testType' accruedInterestStandard = 'accruedInterestStandard' futureMonthZ26 = 'futureMonthZ26' futureMonthZ25 = 'futureMonthZ25' ccgCode = 'ccgCode' shortExposure = 'shortExposure' leg1FixedPaymentCurrency = 'leg1FixedPaymentCurrency' map = 'map' arrivalHaircutVwap = 'arrivalHaircutVwap' executionDays = 'executionDays' recallDueDate = 'recallDueDate' mktFwdSpreadMultiplier = 'mktFwdSpreadMultiplier' impliedRetailNotionalw2kFilter = 'impliedRetailNotionalw2kFilter' forward = 'forward' strike = 'strike' spreadLimit = 'spreadLimit' sopr = 'sopr' otherPaymentAmount = 'otherPaymentAmount' productScope = 'productScope' redemptionPeriod = 'redemptionPeriod' assetParametersIssuerType = 'assetParametersIssuerType' currency1 = 'currency1' currency2 = 'currency2' _300 = '300' previousCloseRealizedBps = 'previousCloseRealizedBps' _301 = '301' _302 = '302' daysSinceReported = 'daysSinceReported' _303 = '303' eventStatus = 'eventStatus' _304 = '304' _305 = '305' _306 = '306' vwapInLimit = 'vwapInLimit' _307 = '307' fwdDuration = 'fwdDuration' _308 = '308' _309 = '309' _return = 'return' isPairBasket = 'isPairBasket' notionalAmount = 'notionalAmount' optionPremiumAmount = 'optionPremiumAmount' payOrReceive = 'payOrReceive' impliedRetailSellPctNotional = 'impliedRetailSellPctNotional' totalSevere = 'totalSevere' _310 = '310' unexecutedNotionalUSD = 'unexecutedNotionalUSD' _311 = '311' expectedResidualPercentage = 'expectedResidualPercentage' _312 = '312' _313 = '313' maturityDate = 'maturityDate' _314 = '314' traceAdvSell = 'traceAdvSell' _315 = '315' _316 = '316' eventName = 'eventName' _317 = '317' addressLine2 = 'addressLine2' _318 = '318' indicationOfOtherPriceAffectingTerm = 'indicationOfOtherPriceAffectingTerm' _319 = '319' unadjustedBid = 'unadjustedBid' backtestType = 'backtestType' gsdeer = 'gsdeer' assetParametersIssuer = 'assetParametersIssuer' gRegionalPercentile = 'gRegionalPercentile' coverageChecked = 'coverageChecked' oisXccyExSpike = 'oisXccyExSpike' chargeInEntityCurrency = 'chargeInEntityCurrency' totalRisk = 'totalRisk' mnav = 'mnav' marketVolume = 'marketVolume' swapAnnuity = 'swapAnnuity' parAssetSwapSpread = 'parAssetSwapSpread' currYield7Day = 'currYield7Day' pressure = 'pressure' shortDescription = 'shortDescription' factorProfile = 'factorProfile' daysToCover30day = 'daysToCover30day' futureMonthZ24 = 'futureMonthZ24' feed = 'feed' futureMonthZ23 = 'futureMonthZ23' _320 = '320' mktPoint1 = 'mktPoint1' futureMonthZ22 = 'futureMonthZ22' _321 = '321' futureMonthZ21 = 'futureMonthZ21' _322 = '322' futureMonthZ20 = 'futureMonthZ20' _323 = '323' _324 = '324' assetParametersCommoditySector = 'assetParametersCommoditySector' priceNotation2 = 'priceNotation2' _325 = '325' _326 = '326' _327 = '327' marketBufferThreshold = 'marketBufferThreshold' priceNotation3 = 'priceNotation3' _328 = '328' _329 = '329' mktPoint3 = 'mktPoint3' mktPoint2 = 'mktPoint2' leg2Type = 'leg2Type' mktPoint4 = 'mktPoint4' degreeDaysType = 'degreeDaysType' sentiment = 'sentiment' investmentIncome = 'investmentIncome' groupType = 'groupType' forwardPointImm = 'forwardPointImm' twap = 'twap' clientShortName = 'clientShortName' groupCategory = 'groupCategory' bidPlusAsk = 'bidPlusAsk' foreignCcyRate = 'foreignCcyRate' electionOdds = 'electionOdds' windDirectionForecast = 'windDirectionForecast' commoditiesRelated = 'commoditiesRelated' requireAnonClientName = 'requireAnonClientName' _330 = '330' _331 = '331' _332 = '332' _333 = '333' _334 = '334' _335 = '335' _336 = '336' _337 = '337' _338 = '338' _339 = '339' pricingLocation = 'pricingLocation' beta = 'beta' lastReturnsEndDate = 'lastReturnsEndDate' upfrontPaymentDate = 'upfrontPaymentDate' sell1point5bps = 'sell1point5bps' longExposure = 'longExposure' sell4point5bps = 'sell4point5bps' tcmCostParticipationRate20Pct = 'tcmCostParticipationRate20Pct' venueType = 'venueType' currentActivityIndicator = 'currentActivityIndicator' multiAssetClassSwap = 'multiAssetClassSwap' assetParametersMultiplier = 'assetParametersMultiplier' tradedPrice = 'tradedPrice' medianDailyVolume5d = 'medianDailyVolume5d' _340 = '340' _341 = '341' _342 = '342' _343 = '343' deltaChangeId = 'deltaChangeId' _344 = '344' implementationId = 'implementationId' _345 = '345' _346 = '346' _347 = '347' leg1FixedPayment = 'leg1FixedPayment' _348 = '348' esNumericScore = 'esNumericScore' _349 = '349' inBenchmark = 'inBenchmark' actionSDR = 'actionSDR' nearbyContractRule = 'nearbyContractRule' quantityFrequency = 'quantityFrequency' countIdeasQtd = 'countIdeasQtd' knockOutPrice = 'knockOutPrice' spreadCurrency1 = 'spreadCurrency1' spreadCurrency2 = 'spreadCurrency2' currentSupply = 'currentSupply' ctdAssetId = 'ctdAssetId' _350 = '350' _351 = '351' buy10bps = 'buy10bps' _352 = '352' precipitation = 'precipitation' _353 = '353' _354 = '354' _355 = '355' impliedRetailSellShares = 'impliedRetailSellShares' _356 = '356' _357 = '357' _358 = '358' valueType = 'valueType' _359 = '359' betaAdjustedNetExposure = 'betaAdjustedNetExposure' pairCalculation = 'pairCalculation' estimatedRodVolume = 'estimatedRodVolume' sell14bps = 'sell14bps' _10 = '10' _11 = '11' _12 = '12' _13 = '13' excessReturnPrice = 'excessReturnPrice' _14 = '14' _15 = '15' _16 = '16' _17 = '17' _18 = '18' _19 = '19' fxPnl = 'fxPnl' fixingDate = 'fixingDate' leg2FloatingRateIndex = 'leg2FloatingRateIndex' _360 = '360' _361 = '361' _362 = '362' _363 = '363' _364 = '364' _365 = '365' _366 = '366' assetClassificationsGicsIndustryGroup = 'assetClassificationsGicsIndustryGroup' _367 = '367' meanDailyVolume10d = 'meanDailyVolume10d' _368 = '368' indexConstituents = 'indexConstituents' _369 = '369' lendingSecId = 'lendingSecId' dollarDuration = 'dollarDuration' equityTheta = 'equityTheta' dv01 = 'dv01' startDate = 'startDate' _20 = '20' _21 = '21' fwdTier = 'fwdTier' _22 = '22' _23 = '23' mixedSwap = 'mixedSwap' swaptionPremium = 'swaptionPremium' _24 = '24' _25 = '25' _26 = '26' snowfall = 'snowfall' liquidityBucketBuy = 'liquidityBucketBuy' dayOpen = 'dayOpen' _27 = '27' mic = 'mic' hurdleType = 'hurdleType' _28 = '28' latitude = 'latitude' _29 = '29' mid = 'mid' impliedRepo = 'impliedRepo' _370 = '370' _371 = '371' long = 'long' _372 = '372' _373 = '373' firstExecutionTime = 'firstExecutionTime' shares = 'shares' _374 = '374' _375 = '375' coveredBond = 'coveredBond' regionCode = 'regionCode' buy20cents = 'buy20cents' _376 = '376' _377 = '377' _378 = '378' longWeight = 'longWeight' calculationTime = 'calculationTime' liquidityBucketSell = 'liquidityBucketSell' _379 = '379' daysOpenUnrealizedCash = 'daysOpenUnrealizedCash' temperature = 'temperature' averageRealizedVariance = 'averageRealizedVariance' leg1CommodityUnderlyerId = 'leg1CommodityUnderlyerId' ratingFitch = 'ratingFitch' financialReturnsScore = 'financialReturnsScore' transitionPlanTransparencyPercentage = 'transitionPlanTransparencyPercentage' yearOrQuarter = 'yearOrQuarter' _30 = '30' _31 = '31' _32 = '32' nonSymbolDimensions = 'nonSymbolDimensions' _33 = '33' commoditiesForecast = 'commoditiesForecast' _34 = '34' _35 = '35' covid19ByState = 'covid19ByState' _36 = '36' _37 = '37' _38 = '38' _39 = '39' percentageExpectedResidual = 'percentageExpectedResidual' hospitalName = 'hospitalName' _380 = '380' _381 = '381' _382 = '382' buy90cents = 'buy90cents' _383 = '383' _384 = '384' _385 = '385' _386 = '386' _387 = '387' periodType = 'periodType' _388 = '388' assetClassificationsCountryName = 'assetClassificationsCountryName' totalHospitalized = 'totalHospitalized' _389 = '389' peggedRefillInterval = 'peggedRefillInterval' fatalitiesProbable = 'fatalitiesProbable' tenorCurveBucket = 'tenorCurveBucket' _40 = '40' administrativeRegion = 'administrativeRegion' _41 = '41' open = 'open' _42 = '42' _43 = '43' _44 = '44' daysToCover10day = 'daysToCover10day' _45 = '45' cusip = 'cusip' totalConfirmedByState = 'totalConfirmedByState' _46 = '46' ideaActivityTime = 'ideaActivityTime' _47 = '47' crowdedScore = 'crowdedScore' _48 = '48' _49 = '49' tagsToExclude = 'tagsToExclude' windAttribute = 'windAttribute' spreadOptionAtmFwdRate = 'spreadOptionAtmFwdRate' netExposure = 'netExposure' optionEntitlement = 'optionEntitlement' _390 = '390' _391 = '391' _392 = '392' _393 = '393' _394 = '394' _395 = '395' _396 = '396' _397 = '397' _398 = '398' isLegacyPairBasket = 'isLegacyPairBasket' _399 = '399' issuerType = 'issuerType' buy70cents = 'buy70cents' strikeReference = 'strikeReference' assetCount = 'assetCount' matchingOrderFwdPointAsk = 'matchingOrderFwdPointAsk' _50 = '50' s3Float = 's3Float' _51 = '51' isOrderInLimit = 'isOrderInLimit' _52 = '52' _53 = '53' assetParametersLastFixingDate = 'assetParametersLastFixingDate' _54 = '54' fundamentalMetric = 'fundamentalMetric' _55 = '55' _56 = '56' quoteStatusId = 'quoteStatusId' assetParametersMethodOfSettlement = 'assetParametersMethodOfSettlement' _57 = '57' absoluteValue = 'absoluteValue' closingReport = 'closingReport' redemptionNoticePeriod = 'redemptionNoticePeriod' _58 = '58' previousTotalConfirmed = 'previousTotalConfirmed' _59 = '59' longTenor = 'longTenor' multiplier = 'multiplier' buy40cents = 'buy40cents' assetCountPriced = 'assetCountPriced' voteDirection = 'voteDirection' impliedRepoRate = 'impliedRepoRate' settlementCurrency = 'settlementCurrency' wtdDegreeDaysForecast = 'wtdDegreeDaysForecast' indicationOfCollateralization = 'indicationOfCollateralization' futureMonthN26 = 'futureMonthN26' _60 = '60' lendingPartnerFee = 'lendingPartnerFee' futureMonthN25 = 'futureMonthN25' _61 = '61' futureMonthN24 = 'futureMonthN24' _62 = '62' primaryVwapRealizedBps = 'primaryVwapRealizedBps' futureMonthN23 = 'futureMonthN23' _63 = '63' futureMonthN22 = 'futureMonthN22' _64 = '64' futureMonthN21 = 'futureMonthN21' _65 = '65' _66 = '66' _67 = '67' _68 = '68' _69 = '69' breakEvenInflation = 'breakEvenInflation' pnlYtd = 'pnlYtd' leg1ReturnType = 'leg1ReturnType' tenor2 = 'tenor2' resetFrequency = 'resetFrequency' assetParametersPayerFrequency = 'assetParametersPayerFrequency' degreeDaysForecast = 'degreeDaysForecast' isManuallySilenced = 'isManuallySilenced' buy3bps = 'buy3bps' lastUpdatedById = 'lastUpdatedById' legalEntityAcct = 'legalEntityAcct' targetShareholderMeetingDate = 'targetShareholderMeetingDate' assetParametersForwardPrice = 'assetParametersForwardPrice' _70 = '70' _71 = '71' _72 = '72' paceOfRollp0 = 'paceOfRollp0' _73 = '73' _74 = '74' controversyPercentile = 'controversyPercentile' leg1NotionalCurrency = 'leg1NotionalCurrency' _75 = '75' complianceEffectiveTime = 'complianceEffectiveTime' expirationDate = 'expirationDate' _76 = '76' _77 = '77' _78 = '78' _79 = '79' floatingRateDayCountFraction = 'floatingRateDayCountFraction' callLastDate = 'callLastDate' factorReturn = 'factorReturn' passiveFlowRatio = 'passiveFlowRatio' composite5DayAdv = 'composite5DayAdv' squeezeRisk = 'squeezeRisk' marginalContributionToRisk = 'marginalContributionToRisk' closeDate = 'closeDate' temperatureHourForecast = 'temperatureHourForecast' newIdeasWtd = 'newIdeasWtd' assetClassSDR = 'assetClassSDR' yieldToWorst = 'yieldToWorst' assetParametersForwardRate = 'assetParametersForwardRate' _80 = '80' closingPrice = 'closingPrice' clientFwdPointsAsk = 'clientFwdPointsAsk' _81 = '81' turnoverCompositeAdjusted = 'turnoverCompositeAdjusted' comment = 'comment' sourceSymbol = 'sourceSymbol' _82 = '82' _83 = '83' _84 = '84' askUnadjusted = 'askUnadjusted' appliedSpeedBump = 'appliedSpeedBump' _85 = '85' _86 = '86' restrictExternalDerivedData = 'restrictExternalDerivedData' _87 = '87' _88 = '88' _89 = '89' askChange = 'askChange' countIdeasMtd = 'countIdeasMtd' endDate = 'endDate' sunshine = 'sunshine' contractType = 'contractType' momentumType = 'momentumType' specificRisk = 'specificRisk' chargeInQuoteConventionTwo = 'chargeInQuoteConventionTwo' assetParametersIndex = 'assetParametersIndex' freeFloatMarketCap = 'freeFloatMarketCap' shortMomentum = 'shortMomentum' mdapi = 'mdapi' payoffQtd = 'payoffQtd' loss = 'loss' midcurveVol = 'midcurveVol' sell6bps = 'sell6bps' tradingCostPnl = 'tradingCostPnl' priceNotationType = 'priceNotationType' price = 'price' paymentQuantity = 'paymentQuantity' _90 = '90' strategyAum = 'strategyAum' defensive = 'defensive' _91 = '91' _92 = '92' _93 = '93' assetParametersCallAmount = 'assetParametersCallAmount' _94 = '94' _95 = '95' outrightMarketAsk = 'outrightMarketAsk' _96 = '96' _97 = '97' _98 = '98' redemptionDate = 'redemptionDate' _99 = '99' leg2NotionalCurrency = 'leg2NotionalCurrency' subRegion = 'subRegion' productId = 'productId' currentConstituentsSalesPerShare = 'currentConstituentsSalesPerShare' benchmark = 'benchmark' nvtAdj = 'nvtAdj' tcmCostParticipationRate15Pct = 'tcmCostParticipationRate15Pct' fiscalYear = 'fiscalYear' recallDate = 'recallDate' internal = 'internal' gender = 'gender' assetClassificationsGicsIndustry = 'assetClassificationsGicsIndustry' adjustedBidPrice = 'adjustedBidPrice' lowUnadjusted = 'lowUnadjusted' MACSSecondaryAssetClass = 'MACSSecondaryAssetClass' confirmedPerMillion = 'confirmedPerMillion' aggregatedUsdSpotExposure = 'aggregatedUsdSpotExposure' exchangeRateBasis = 'exchangeRateBasis' assetClassOrgDrgname = 'assetClassOrgDrgname' dataSourceId = 'dataSourceId' integratedScore = 'integratedScore' buy7bps = 'buy7bps' arrivalMidUnrealizedCash = 'arrivalMidUnrealizedCash' knockInPrice = 'knockInPrice' event = 'event' isIntradayAuction = 'isIntradayAuction' locationName = 'locationName' coupon = 'coupon' percentageAuctionExecutedQuantity = 'percentageAuctionExecutedQuantity' avgYield7Day = 'avgYield7Day' referenceRateEur = 'referenceRateEur' originalDisseminationId = 'originalDisseminationId' totalOnVent = 'totalOnVent' twapUnrealizedCash = 'twapUnrealizedCash' stsCreditMarket = 'stsCreditMarket' assetClassificationsDigitalAssetSector = 'assetClassificationsDigitalAssetSector' weightOfMarketValue = 'weightOfMarketValue' onsCode = 'onsCode' passiveTouchFillsPercentage = 'passiveTouchFillsPercentage' seniority = 'seniority' inflationDelta = 'inflationDelta' leg1Index = 'leg1Index' highUnadjusted = 'highUnadjusted' relativeMarginalContributionToRisk = 'relativeMarginalContributionToRisk' submissionEvent = 'submissionEvent' TVProductMnemonic = 'TVProductMnemonic' avgTradeRateLabel = 'avgTradeRateLabel' lastActivityDate = 'lastActivityDate' disseminationTime = 'disseminationTime' priceToCash = 'priceToCash' buy10cents = 'buy10cents' fwdEbookPointSpreadAllInMultAsk = 'fwdEbookPointSpreadAllInMultAsk' realizedMarketCapRatio = 'realizedMarketCapRatio' failed = 'failed' navSpread = 'navSpread' venueMIC = 'venueMIC' dollarTotalReturn = 'dollarTotalReturn' blockUnit = 'blockUnit' emissionsIntensityEnterpriseValue = 'emissionsIntensityEnterpriseValue' midSpread = 'midSpread' istatProvinceCode = 'istatProvinceCode' totalRecoveredByState = 'totalRecoveredByState' displayId = 'displayId' repurchaseRate = 'repurchaseRate' dataSource = 'dataSource' totalBeingTested = 'totalBeingTested' clearedOrBilateral = 'clearedOrBilateral' cvaMultiplier = 'cvaMultiplier' metricName = 'metricName' emissionsIntensityRevenue = 'emissionsIntensityRevenue' askGspread = 'askGspread' forecastHour = 'forecastHour' leg2PaymentType = 'leg2PaymentType' calSpreadMisPricing = 'calSpreadMisPricing' totalTestedNegative = 'totalTestedNegative' impliedRetailNotional = 'impliedRetailNotional' rate366 = 'rate366' currentConstituentsReturnOnEquity = 'currentConstituentsReturnOnEquity' platform = 'platform' rate365 = 'rate365' fixedRateFrequency = 'fixedRateFrequency' rate360 = 'rate360' medianDailyVolume22d = 'medianDailyVolume22d' dailyNetMarkToMarketPnl = 'dailyNetMarkToMarketPnl' globalPredictedBeta = 'globalPredictedBeta' notionalQuantity2 = 'notionalQuantity2' notionalQuantity1 = 'notionalQuantity1' isContinuous = 'isContinuous' value = 'value' payerDesignatedMaturity = 'payerDesignatedMaturity' productType = 'productType' mdv22Day = 'mdv22Day' nvtAdjFf90 = 'nvtAdjFf90' twapRealizedBps = 'twapRealizedBps' testMeasureLabel = 'testMeasureLabel' quantity = 'quantity' reportId = 'reportId' indexWeight = 'indexWeight' MACSPrimaryAssetClass = 'MACSPrimaryAssetClass' traded = 'traded' trader = 'trader' leg2PriceType = 'leg2PriceType' floatingRateResetFrequencyPeriodMultiplier2 = 'floatingRateResetFrequencyPeriodMultiplier2' totalActive = 'totalActive' floatingRateResetFrequencyPeriodMultiplier1 = 'floatingRateResetFrequencyPeriodMultiplier1' gsid2 = 'gsid2' matchedMaturityOISSwapSpread = 'matchedMaturityOISSwapSpread' currentConstituentsPriceToEarnings = 'currentConstituentsPriceToEarnings' valuationDate = 'valuationDate' restrictGsFederation = 'restrictGsFederation' positionSource = 'positionSource' tcmCostHorizon6Hour = 'tcmCostHorizon6Hour' commodityReferencePrice = 'commodityReferencePrice' buy200cents = 'buy200cents' vwapUnrealizedBps = 'vwapUnrealizedBps' priceToBook = 'priceToBook' isin = 'isin' fwdEbookRiskSpreadMultBid = 'fwdEbookRiskSpreadMultBid' transitionPlanTransparencyPercentile = 'transitionPlanTransparencyPercentile' assetParametersStrikeType = 'assetParametersStrikeType' plId = 'plId' lastReturnsStartDate = 'lastReturnsStartDate' collateralValueVariance = 'collateralValueVariance' year = 'year' forecastPeriod = 'forecastPeriod' callFirstDate = 'callFirstDate' dataSetIds = 'dataSetIds' economicTermsHash = 'economicTermsHash' numBeds = 'numBeds' sell20bps = 'sell20bps' clientType = 'clientType' percentageCloseExecutedQuantity = 'percentageCloseExecutedQuantity' averageFillPriceExcludingFees = 'averageFillPriceExcludingFees' macaulayDuration = 'macaulayDuration' availableInventory = 'availableInventory' est1DayCompletePct = 'est1DayCompletePct' relativeHitRateYtd = 'relativeHitRateYtd' gSpread = 'gSpread' rai = 'rai' impliedRetailBuyPctNotional = 'impliedRetailBuyPctNotional' createdById = 'createdById' marketDataType = 'marketDataType' realShortRatesContribution = 'realShortRatesContribution' metricCategory = 'metricCategory' assetParametersCapFloor = 'assetParametersCapFloor' annualizedCarry = 'annualizedCarry' valuePrevious = 'valuePrevious' transmissionClassification = 'transmissionClassification' avgTradeRate = 'avgTradeRate' shortLevel = 'shortLevel' version = 'version' categoryType = 'categoryType' policyRateExpectation = 'policyRateExpectation' uploadDate = 'uploadDate' blockOffFacility = 'blockOffFacility' unrealizedVwapPerformanceUSD = 'unrealizedVwapPerformanceUSD' paceOfRollp75 = 'paceOfRollp75' earningsPerSharePositive = 'earningsPerSharePositive' numIcuBeds = 'numIcuBeds' bucketVolumeInPercentage = 'bucketVolumeInPercentage' estimatedTradingCost = 'estimatedTradingCost' assetClassificationsUnderliersAssetClass = 'assetClassificationsUnderliersAssetClass' eid = 'eid' calculationRegion = 'calculationRegion' relativeReturnQtd = 'relativeReturnQtd' assessedTestMeasure = 'assessedTestMeasure' mktQuotingStyle = 'mktQuotingStyle' expirationTenor = 'expirationTenor' tradedPriceNoMarkup = 'tradedPriceNoMarkup' priceLimit = 'priceLimit' marketModelId = 'marketModelId' receiverFrequency = 'receiverFrequency' realizedCorrelation = 'realizedCorrelation' issueStatus = 'issueStatus' collateralValueActual = 'collateralValueActual' atmFwdRate = 'atmFwdRate' tcmCostParticipationRate75Pct = 'tcmCostParticipationRate75Pct' close = 'close' vol30d = 'vol30d' esProductImpactScore = 'esProductImpactScore' equityVega = 'equityVega' executedFillQuantity = 'executedFillQuantity' lenderPayment = 'lenderPayment' fiveDayMove = 'fiveDayMove' realizedMarketCap = 'realizedMarketCap' valueFormat = 'valueFormat' xrefs = 'xrefs' windChillForecast = 'windChillForecast' assetParametersTenor = 'assetParametersTenor' targetNotional = 'targetNotional' fillLegId = 'fillLegId' rationale = 'rationale' realizedTwapPerformanceBps = 'realizedTwapPerformanceBps' lastUpdatedSince = 'lastUpdatedSince' totalTests = 'totalTests' equitiesContribution = 'equitiesContribution' fwdEbookPointSpreadMultAsk = 'fwdEbookPointSpreadMultAsk' simonId = 'simonId' congestion = 'congestion' leg2CommodityInstrumentId = 'leg2CommodityInstrumentId' notes = 'notes' totalProbableSeniorHome = 'totalProbableSeniorHome' eventCategory = 'eventCategory' averageFillRate = 'averageFillRate' cins = 'cins' unadjustedOpen = 'unadjustedOpen' criticality = 'criticality' bidAskSpread = 'bidAskSpread' arrivalMidUnrealizedBps = 'arrivalMidUnrealizedBps' optionType = 'optionType' terminationDate = 'terminationDate' queriesPerSecond = 'queriesPerSecond' liquidityType = 'liquidityType' creditLimit = 'creditLimit' rankQtd = 'rankQtd' combinedKey = 'combinedKey' girFxForecast = 'girFxForecast' effectiveTenor = 'effectiveTenor' girCommoditiesForecast = 'girCommoditiesForecast' relativeHumidityDailyForecast = 'relativeHumidityDailyForecast' std30DaysSubsidizedYield = 'std30DaysSubsidizedYield' annualizedTrackingError = 'annualizedTrackingError' futureMonthF26 = 'futureMonthF26' futureMonthF25 = 'futureMonthF25' volSwap = 'volSwap' futureMonthF24 = 'futureMonthF24' heatIndexDailyForecast = 'heatIndexDailyForecast' futureMonthF23 = 'futureMonthF23' realFCI = 'realFCI' blockTradesAndLargeNotionalOffFacilitySwaps = 'blockTradesAndLargeNotionalOffFacilitySwaps' futureMonthF22 = 'futureMonthF22' buy1point5bps = 'buy1point5bps' futureMonthF21 = 'futureMonthF21' expirationSettlementDate = 'expirationSettlementDate' absoluteReturnQtd = 'absoluteReturnQtd' grossExposure = 'grossExposure' volume = 'volume' adv = 'adv' shortConvictionMedium = 'shortConvictionMedium' completeTestMeasure = 'completeTestMeasure' percentPricesReturn = 'percentPricesReturn' fxQuotedVega = 'fxQuotedVega' exchange = 'exchange' esPolicyScore = 'esPolicyScore' rollVolumeStd = 'rollVolumeStd' temperatureDailyForecast = 'temperatureDailyForecast' relativePayoffQtd = 'relativePayoffQtd' onLoanPercentage = 'onLoanPercentage' fxCalcDeltaNoPremiumAdjustment = 'fxCalcDeltaNoPremiumAdjustment' twapRemainingSlices = 'twapRemainingSlices' fairVariance = 'fairVariance' hitRateWtd = 'hitRateWtd' previousCloseRealizedCash = 'previousCloseRealizedCash' estimationUniverseWeight = 'estimationUniverseWeight' realizedVolatility = 'realizedVolatility' unexecutedQuantity = 'unexecutedQuantity' clientOutrightBid = 'clientOutrightBid' proceedsAssetSwapSpread1m = 'proceedsAssetSwapSpread1m' cloneParentId = 'cloneParentId' windSpeedHourlyForecast = 'windSpeedHourlyForecast' impliedRetailSellNotional = 'impliedRetailSellNotional' etfFlowRatio = 'etfFlowRatio' assetParametersReceiverRateOption = 'assetParametersReceiverRateOption' buy60cents = 'buy60cents' securitySubTypeId = 'securitySubTypeId' coinMetricsId = 'coinMetricsId' TRSNotional = 'TRSNotional' denominated = 'denominated' message = 'message' stsRatesCountry = 'stsRatesCountry' sell65cents = 'sell65cents' assetParametersPremiumPaymentDate = 'assetParametersPremiumPaymentDate' horizon = 'horizon' wouldIfGoodLevel = 'wouldIfGoodLevel' bufferThresholdRequired = 'bufferThresholdRequired' faceValue = 'faceValue' rollVolumeHist = 'rollVolumeHist' counterPartyStatus = 'counterPartyStatus' composite22DayAdv = 'composite22DayAdv' percentageFarExecutedQuantity = 'percentageFarExecutedQuantity' tradingCentre = 'tradingCentre' loanSpreadRequired = 'loanSpreadRequired' fixingRequested = 'fixingRequested' assetClass = 'assetClass' assetClassificationsVendor = 'assetClassificationsVendor' sovereignSpreadContribution = 'sovereignSpreadContribution' ric = 'ric' bucketEndTime = 'bucketEndTime' rateType = 'rateType' totalFatalitiesSeniorHome = 'totalFatalitiesSeniorHome' loanStatus = 'loanStatus' shortWeight = 'shortWeight' geographyId = 'geographyId' sell7point5bps = 'sell7point5bps' nav = 'nav' fiscalQuarter = 'fiscalQuarter' versionString = 'versionString' payoffYtd = 'payoffYtd' marketImpact = 'marketImpact' eventType = 'eventType' fillPrice = 'fillPrice' assetCountLong = 'assetCountLong' sell180cents = 'sell180cents' expirationDateRule = 'expirationDateRule' updateSeconds = 'updateSeconds' _400 = '400' _401 = '401' _402 = '402' spot = 'spot' _403 = '403' _404 = '404' _405 = '405' _406 = '406' _407 = '407' applicationId = 'applicationId' indicativeClosePrice = 'indicativeClosePrice' _408 = '408' swapSpread = 'swapSpread' _409 = '409' tradingRestriction = 'tradingRestriction' assetParametersPayOrReceive = 'assetParametersPayOrReceive' priceSpotEntryUnit = 'priceSpotEntryUnit' unrealizedArrivalPerformanceBps = 'unrealizedArrivalPerformanceBps' city = 'city' assetParametersIndexSeries = 'assetParametersIndexSeries' pnlWtd = 'pnlWtd' covariance = 'covariance' bucketVolumeInShares = 'bucketVolumeInShares' commodityForecast = 'commodityForecast' valid = 'valid' stsCommodity = 'stsCommodity' initialPricingDate = 'initialPricingDate' indicationOfEndUserException = 'indicationOfEndUserException' _410 = '410' _411 = '411' windDirectionHourlyForecast = 'windDirectionHourlyForecast' _412 = '412' esScore = 'esScore' _413 = '413' _yield = 'yield' _414 = '414' numberOfPositionsExploded = 'numberOfPositionsExploded' fatalitiesUnderlyingConditionsPresent = 'fatalitiesUnderlyingConditionsPresent' _415 = '415' priceRangeInTicks = 'priceRangeInTicks' swapPointsMarketAsk = 'swapPointsMarketAsk' _416 = '416' _417 = '417' _418 = '418' paceOfRollp25 = 'paceOfRollp25' _419 = '419' dayCloseRealizedUSD = 'dayCloseRealizedUSD' pctChange = 'pctChange' brightnessType = 'brightnessType' futureMonth3M = 'futureMonth3M' fwdEbookRiskSpreadMultAsk = 'fwdEbookRiskSpreadMultAsk' numberOfRolls = 'numberOfRolls' isoCountryCodeNumeric = 'isoCountryCodeNumeric' priceType = 'priceType' realizedVwapPerformanceUSD = 'realizedVwapPerformanceUSD' orderSide = 'orderSide' tradingDesk = 'tradingDesk' _420 = '420' _421 = '421' fuelType = 'fuelType' _422 = '422' bbid = 'bbid' _423 = '423' vegaNotionalAmount = 'vegaNotionalAmount' _424 = '424' _425 = '425' _426 = '426' _427 = '427' _428 = '428' _429 = '429' fatalitiesUnderlyingConditionsAbsent = 'fatalitiesUnderlyingConditionsAbsent' effectiveDate = 'effectiveDate' TRSBidPrice = 'TRSBidPrice' capped = 'capped' rating = 'rating' optionCurrency = 'optionCurrency' isCloseAuction = 'isCloseAuction' volatility = 'volatility' assetClassificationsDigitalAssetMarket = 'assetClassificationsDigitalAssetMarket' avgVentUtil = 'avgVentUtil' _430 = '430' underlyingAssetIds = 'underlyingAssetIds' buy6point5bps = 'buy6point5bps' vwapInLimitRealizedCash = 'vwapInLimitRealizedCash' _431 = '431' _432 = '432' estimatedClosingAuctionVolume = 'estimatedClosingAuctionVolume' _433 = '433' _434 = '434' _435 = '435' sell2bps = 'sell2bps' _436 = '436' _437 = '437' _438 = '438' annualRisk = 'annualRisk' _439 = '439' eti = 'eti' vwapInLimitRealizedBps = 'vwapInLimitRealizedBps' rankMtd = 'rankMtd' marketBuffer = 'marketBuffer' futureMonthJ24 = 'futureMonthJ24' lastUploadedTime = 'lastUploadedTime' futureMonthJ23 = 'futureMonthJ23' oeId = 'oeId' futureMonthJ22 = 'futureMonthJ22' futureMonthJ21 = 'futureMonthJ21' bbidEquivalent = 'bbidEquivalent' initBufferThresholdRequired = 'initBufferThresholdRequired' leg2DesignatedMaturity = 'leg2DesignatedMaturity' matchedMaturityOISSwapRate = 'matchedMaturityOISSwapRate' fairPrice = 'fairPrice' participationRateInLimit = 'participationRateInLimit' extMktClass = 'extMktClass' _440 = '440' _441 = '441' priceCurrency = 'priceCurrency' _442 = '442' failedCount = 'failedCount' _443 = '443' leg1IndexLocation = 'leg1IndexLocation' _444 = '444' _445 = '445' _446 = '446' supraStrategy = 'supraStrategy' _447 = '447' _448 = '448' dayCountConvention = 'dayCountConvention' _449 = '449' roundedNotionalAmount1 = 'roundedNotionalAmount1' roundedNotionalAmount2 = 'roundedNotionalAmount2' factorSource = 'factorSource' futureMonthJ26 = 'futureMonthJ26' lendingSecType = 'lendingSecType' futureMonthJ25 = 'futureMonthJ25' exposureOrgDrgname = 'exposureOrgDrgname' leverage = 'leverage' factorExposure = 'factorExposure' forecastDay = 'forecastDay' optionFamily = 'optionFamily' generatorOutput = 'generatorOutput' priceSpotStopLossValue = 'priceSpotStopLossValue' kpiId = 'kpiId' windGeneration = 'windGeneration' percentageMidExecutedQuantity = 'percentageMidExecutedQuantity' staticVolumeForecast = 'staticVolumeForecast' borrowCost = 'borrowCost' knockOutDirection = 'knockOutDirection' screenId = 'screenId' riskModel = 'riskModel' assetParametersVendor = 'assetParametersVendor' assetParametersIndex1Tenor = 'assetParametersIndex1Tenor' isPublic = 'isPublic' fairValue = 'fairValue' openTime = 'openTime' pressureHourlyForecast = 'pressureHourlyForecast' localCcyRate = 'localCcyRate' endUserException = 'endUserException' sell90cents = 'sell90cents' executionVenue = 'executionVenue' nonStandardizedPricingIndicator = 'nonStandardizedPricingIndicator' primaryVwapInLimitRealizedBps = 'primaryVwapInLimitRealizedBps' approveRebalance = 'approveRebalance' adjustedClosePrice = 'adjustedClosePrice' lmsId = 'lmsId' rebateRate = 'rebateRate' sell130cents = 'sell130cents' speedBumpDelay = 'speedBumpDelay' priceUnitOfMeasure1 = 'priceUnitOfMeasure1' sell32bps = 'sell32bps' paceOfRollp50 = 'paceOfRollp50' priceMoveVsArrival = 'priceMoveVsArrival' strikeRelative = 'strikeRelative' pressureType = 'pressureType' buy40bps = 'buy40bps' priceNotation = 'priceNotation' strategy = 'strategy' vintageName = 'vintageName' priceUnitOfMeasure2 = 'priceUnitOfMeasure2' issueStatusDate = 'issueStatusDate' lenderIncome = 'lenderIncome' settlementCcy = 'settlementCcy' pbClientId = 'pbClientId' istatRegionCode = 'istatRegionCode' sell9bps = 'sell9bps' shortInterestPercent = 'shortInterestPercent' ownerId = 'ownerId' composite10DayAdv = 'composite10DayAdv' maxLoanBalance = 'maxLoanBalance' ideaActivityType = 'ideaActivityType' sell60cents = 'sell60cents' ideaSource = 'ideaSource' compositeFigi = 'compositeFigi' everOnVent = 'everOnVent' otcVolume = 'otcVolume' buy15cents = 'buy15cents' unadjustedAsk = 'unadjustedAsk' dynamicVolumeForecast = 'dynamicVolumeForecast' margin = 'margin' contributionName = 'contributionName' givenPlusPaid = 'givenPlusPaid' lastFillPrice = 'lastFillPrice' soprOut = 'soprOut' clientSpotAsk = 'clientSpotAsk' shortConvictionSmall = 'shortConvictionSmall' daysToCover90day = 'daysToCover90day' upfrontPaymentCurrency = 'upfrontPaymentCurrency' spotSettlementDate = 'spotSettlementDate' matrixOrder = 'matrixOrder' dayClose = 'dayClose' dateIndex = 'dateIndex' payerDayCountFraction = 'payerDayCountFraction' assetClassificationsIsPrimary = 'assetClassificationsIsPrimary' breakEvenInflationChange = 'breakEvenInflationChange' buy130cents = 'buy130cents' dwiContribution = 'dwiContribution' asset2Id = 'asset2Id' economicForecasts = 'economicForecasts' averageFillPrice = 'averageFillPrice' depthSpreadScore = 'depthSpreadScore' sell10cents = 'sell10cents' secType = 'secType' subAccount = 'subAccount' buy65cents = 'buy65cents' bondCdsBasis = 'bondCdsBasis' s3Utilization = 's3Utilization' vendor = 'vendor' passMessage = 'passMessage' dataSet = 'dataSet' totalNotionalQuantity2 = 'totalNotionalQuantity2' totalNotionalQuantity1 = 'totalNotionalQuantity1' notionalAmount2 = 'notionalAmount2' notionalAmount1 = 'notionalAmount1' queueingTime = 'queueingTime' annReturn5Year = 'annReturn5Year' volumeStartOfDay = 'volumeStartOfDay' priceNotation3Type = 'priceNotation3Type' assetParametersFloatingRateDesignatedMaturity = 'assetParametersFloatingRateDesignatedMaturity' impliedRetailBuyPctShares = 'impliedRetailBuyPctShares' executedNotionalLocal = 'executedNotionalLocal' tsdbShortname = 'tsdbShortname' businessSponsor = 'businessSponsor' unexplained = 'unexplained' seasonalAdjustmentShort = 'seasonalAdjustmentShort' metric = 'metric' ask = 'ask' closePrice = 'closePrice' endTime = 'endTime' sell100cents = 'sell100cents' executionTimestamp = 'executionTimestamp' buy180cents = 'buy180cents' predictedBeta = 'predictedBeta' absoluteStrike = 'absoluteStrike' liquidity = 'liquidity' sell3point5bps = 'sell3point5bps' liquidityScoreBuy = 'liquidityScoreBuy' paymentFrequency = 'paymentFrequency' expenseRatioNetBps = 'expenseRatioNetBps' metricType = 'metricType' rankYtd = 'rankYtd' leg1Spread = 'leg1Spread' coverageRegion = 'coverageRegion' absoluteReturnYtd = 'absoluteReturnYtd' dayCountConvention2 = 'dayCountConvention2' degreeDays = 'degreeDays' fwdPointsAsk = 'fwdPointsAsk' turnoverAdjusted = 'turnoverAdjusted' priceSpotTargetValue = 'priceSpotTargetValue' marketDataPoint = 'marketDataPoint' numOfFunds = 'numOfFunds' ebcsOutrightMid = 'ebcsOutrightMid' tradeTime = 'tradeTime' executionId = 'executionId' turnoverUnadjusted = 'turnoverUnadjusted' leg1FloatingIndex = 'leg1FloatingIndex' hedgeAnnualizedVolatility = 'hedgeAnnualizedVolatility' benchmarkCurrency = 'benchmarkCurrency' futuresContract = 'futuresContract' name = 'name' aum = 'aum' leg1DayCountConvention = 'leg1DayCountConvention' cbsCode = 'cbsCode' folderName = 'folderName' apiUsage = 'apiUsage' twapInterval = 'twapInterval' factorPnl = 'factorPnl' parameters = 'parameters' paymentFrequencyPeriod1 = 'paymentFrequencyPeriod1' uniqueId = 'uniqueId' optionExpirationDate = 'optionExpirationDate' paymentFrequencyPeriod2 = 'paymentFrequencyPeriod2' swaptionAtmFwdRate = 'swaptionAtmFwdRate' liveDate = 'liveDate' volumeForecastAdjustment = 'volumeForecastAdjustment' corporateActionType = 'corporateActionType' primeId = 'primeId' description = 'description' assetClassificationsIsCountryPrimary = 'assetClassificationsIsCountryPrimary' rebateRateLimit = 'rebateRateLimit' spotAsk = 'spotAsk' swapPointsMarketBid = 'swapPointsMarketBid' extId = 'extId' underLyingAssetsCoverage = 'underLyingAssetsCoverage' factor = 'factor' daysOnLoan = 'daysOnLoan' longConvictionSmall = 'longConvictionSmall' sell40cents = 'sell40cents' relativePayoffYtd = 'relativePayoffYtd' gsfeer = 'gsfeer' relativeHitRateQtd = 'relativeHitRateQtd' wam = 'wam' wal = 'wal' backtestId = 'backtestId' dirtyPrice = 'dirtyPrice' darkWouldRefPrice = 'darkWouldRefPrice' corporateSpreadContribution = 'corporateSpreadContribution' relativeHumidityHourlyForecast = 'relativeHumidityHourlyForecast' multipleScore = 'multipleScore' betaAdjustedExposure = 'betaAdjustedExposure' momentum = 'momentum' isAnnualized = 'isAnnualized' dividendPoints = 'dividendPoints' brightness = 'brightness' factorStandardDeviation = 'factorStandardDeviation' assetParametersReceiverDesignatedMaturity = 'assetParametersReceiverDesignatedMaturity' bosInTicksDescription = 'bosInTicksDescription' testId = 'testId' risk = 'risk' impliedCorrelation = 'impliedCorrelation' normalizedPerformance = 'normalizedPerformance' overnightNewsEndTime = 'overnightNewsEndTime' bytesConsumed = 'bytesConsumed' swaptionVol = 'swaptionVol' estimatedClosingVolume = 'estimatedClosingVolume' issuer = 'issuer' dividendYield = 'dividendYield' marketType = 'marketType' numUnitsLower = 'numUnitsLower' sourceOrigin = 'sourceOrigin' proceedsAssetSwapSpread3m = 'proceedsAssetSwapSpread3m' totalQuantity = 'totalQuantity' internalUser = 'internalUser' factSetRegionalId = 'factSetRegionalId' sell40bps = 'sell40bps' redemptionOption = 'redemptionOption' notionalUnit2 = 'notionalUnit2' notionalUnit1 = 'notionalUnit1' sedol = 'sedol' roundingCostPnl = 'roundingCostPnl' midYield = 'midYield' unexecutedNotionalLocal = 'unexecutedNotionalLocal' classifications = 'classifications' sustainGlobal = 'sustainGlobal' endingDate = 'endingDate' proceedsAssetSwapSpread12m = 'proceedsAssetSwapSpread12m' rvtAdj90 = 'rvtAdj90' grossInvestmentWtd = 'grossInvestmentWtd' annReturn3Year = 'annReturn3Year' sharpeWtd = 'sharpeWtd' discountFactor = 'discountFactor' swapPointsBid = 'swapPointsBid' relativeReturnMtd = 'relativeReturnMtd' exchangeCalendar = 'exchangeCalendar' priceChangeOnDay = 'priceChangeOnDay' buy100cents = 'buy100cents' forwardPoint = 'forwardPoint' increment = 'increment' fci = 'fci' enabled = 'enabled' recallQuantity = 'recallQuantity' strikePriceCurrency = 'strikePriceCurrency' fxPositioning = 'fxPositioning' openToPortableAlpha = 'openToPortableAlpha' gsidEquivalent = 'gsidEquivalent' categories = 'categories' extMktAsset = 'extMktAsset' quotingStyle = 'quotingStyle' isInPosition = 'isInPosition' errorMessage = 'errorMessage' compoundedFixedRate = 'compoundedFixedRate' midPrice = 'midPrice' proceedsAssetSwapSpread6m = 'proceedsAssetSwapSpread6m' stsEmDm = 'stsEmDm' TimeinYears = 'TimeinYears' embeddedOption = 'embeddedOption' tcmCostHorizon2Day = 'tcmCostHorizon2Day' ageBand = 'ageBand' returnsEnabled = 'returnsEnabled' runId = 'runId' queueInLots = 'queueInLots' tenderOfferExpirationDate = 'tenderOfferExpirationDate' assetParametersExpirationTime = 'assetParametersExpirationTime' midcurveAnnuity = 'midcurveAnnuity' lendingFundNavTrend = 'lendingFundNavTrend' cloudCoverForecast = 'cloudCoverForecast' tcmCostParticipationRate5Pct = 'tcmCostParticipationRate5Pct' defaultBackcast = 'defaultBackcast' assetParametersNumberOfShares = 'assetParametersNumberOfShares' lockup = 'lockup' lockupType = 'lockupType' newsOnIntensity = 'newsOnIntensity' priceFormingContinuationData = 'priceFormingContinuationData' adjustedShortInterest = 'adjustedShortInterest' newHospitalized = 'newHospitalized' assetParametersStrike = 'assetParametersStrike' buy35cents = 'buy35cents' impliedRetailBuyNotional = 'impliedRetailBuyNotional' leg2TotalNotional = 'leg2TotalNotional' assetParametersEffectiveDate = 'assetParametersEffectiveDate' annReturn10Year = 'annReturn10Year' numAdultIcuBeds = 'numAdultIcuBeds' daysToExpiration = 'daysToExpiration' continuationEvent = 'continuationEvent' leg2CommodityUnderlyerId = 'leg2CommodityUnderlyerId' fillPriceExcludingFees = 'fillPriceExcludingFees' wiId = 'wiId' marketCapCategory = 'marketCapCategory' historicalVolume = 'historicalVolume' buy5cents = 'buy5cents' eventStartDate = 'eventStartDate' leg1FixedRate = 'leg1FixedRate' transitionPerformancePercentage = 'transitionPerformancePercentage' equityGamma = 'equityGamma' rptId = 'rptId' grossIncome = 'grossIncome' emId = 'emId' assetCountInModel = 'assetCountInModel' stsCreditRegion = 'stsCreditRegion' minTemperature = 'minTemperature' bucketStartTime = 'bucketStartTime' medianDailyVolume10d = 'medianDailyVolume10d' fillType = 'fillType' closeTime = 'closeTime' failPct = 'failPct' isoCountryCodeAlpha2 = 'isoCountryCodeAlpha2' isoCountryCodeAlpha3 = 'isoCountryCodeAlpha3' assetParametersOptionType = 'assetParametersOptionType' amount = 'amount' lendingFundAcct = 'lendingFundAcct' fwdPricingSource = 'fwdPricingSource' rebate = 'rebate' electionType = 'electionType' relativeHitRateMtd = 'relativeHitRateMtd' impliedVolatility = 'impliedVolatility' spread = 'spread' variance = 'variance' wtdDegreeDaysDailyForecast = 'wtdDegreeDaysDailyForecast' swaptionAnnuity = 'swaptionAnnuity' latestEndDate = 'latestEndDate' buy6bps = 'buy6bps' g10Currency = 'g10Currency' humidityForecast = 'humidityForecast' relativePeriod = 'relativePeriod' user = 'user' fwdEbookPointSpreadMultBid = 'fwdEbookPointSpreadMultBid' customer = 'customer' leg1ResetFrequency = 'leg1ResetFrequency' queueClockTimeLabel = 'queueClockTimeLabel' settlementResolved = 'settlementResolved' paceOfRollp100 = 'paceOfRollp100' assetClassificationsGicsSubIndustry = 'assetClassificationsGicsSubIndustry' dewPointHourlyForecast = 'dewPointHourlyForecast' locationType = 'locationType' facetDivisionalReportingGroupId = 'facetDivisionalReportingGroupId' realizedTwapPerformanceUSD = 'realizedTwapPerformanceUSD' swapRate = 'swapRate' algoExecutionStyle = 'algoExecutionStyle' mktFwdPointBid = 'mktFwdPointBid' clientContact = 'clientContact' minTemperatureHour = 'minTemperatureHour' tradingCurrency = 'tradingCurrency' totalByOnset = 'totalByOnset' agencySwapSpread = 'agencySwapSpread' rank = 'rank' mixedSwapOtherReportedSDR = 'mixedSwapOtherReportedSDR' humidity = 'humidity' dataSetCategory = 'dataSetCategory' vwapRealizedBps = 'vwapRealizedBps' buy9bps = 'buy9bps' totalTested = 'totalTested' fatalitiesConfirmed = 'fatalitiesConfirmed' universeId1 = 'universeId1' fwdPointsBid = 'fwdPointsBid' assetParametersPayerDayCountFraction = 'assetParametersPayerDayCountFraction' universeId2 = 'universeId2' bbg = 'bbg' bidLow = 'bidLow' bucketizePrice = 'bucketizePrice' fairVarianceVolatility = 'fairVarianceVolatility' cleanPrice = 'cleanPrice' covid19 = 'covid19' clientExposure = 'clientExposure' leg2TotalNotionalUnit = 'leg2TotalNotionalUnit' sell45cents = 'sell45cents' gsSustainSubSector = 'gsSustainSubSector' sinkable = 'sinkable' isReal = 'isReal' maxTemperatureHour = 'maxTemperatureHour' leg2AveragingMethod = 'leg2AveragingMethod' pricingDate = 'pricingDate' jsn = 'jsn' sell160cents = 'sell160cents' firstExerciseDate = 'firstExerciseDate' spotBid = 'spotBid' knockInDirection = 'knockInDirection' finalCloseDate = 'finalCloseDate' dayCloseUnrealizedUSD = 'dayCloseUnrealizedUSD' tenor = 'tenor' pricingConvention = 'pricingConvention' dealableAuto = 'dealableAuto' popularity = 'popularity' floatingRateOption = 'floatingRateOption' tradedNeutralSpotMid = 'tradedNeutralSpotMid' hedgeValueType = 'hedgeValueType' assetParametersClearingHouse = 'assetParametersClearingHouse' disclaimer = 'disclaimer' payerFrequency = 'payerFrequency' assetParametersOptionStyle = 'assetParametersOptionStyle' loanFee = 'loanFee' deploymentVersion = 'deploymentVersion' buy16bps = 'buy16bps' tradeDayCount = 'tradeDayCount' transitionPerformancePercentile = 'transitionPerformancePercentile' transactionType = 'transactionType' priceToSales = 'priceToSales' secMasterId = 'secMasterId' newIdeasQtd = 'newIdeasQtd' subdivisionName = 'subdivisionName' adjustedAskPrice = 'adjustedAskPrice' fwdPointsMarketAsk = 'fwdPointsMarketAsk' factorUniverse = 'factorUniverse' arrivalRt = 'arrivalRt' internalIndexCalcAgent = 'internalIndexCalcAgent' excessMarginValue = 'excessMarginValue' transactionCost = 'transactionCost' centralBankSwapRate = 'centralBankSwapRate' previousNewConfirmed = 'previousNewConfirmed' unrealizedVwapPerformanceBps = 'unrealizedVwapPerformanceBps' degreeDaysDailyForecast = 'degreeDaysDailyForecast' positionAmount = 'positionAmount' heatIndexHourlyForecast = 'heatIndexHourlyForecast' maRank = 'maRank' fxPositioningSource = 'fxPositioningSource' vol60d = 'vol60d' eventStartDateTime = 'eventStartDateTime' impliedVolatilityByDeltaStrike = 'impliedVolatilityByDeltaStrike' mqSymbol = 'mqSymbol' numTotalUnits = 'numTotalUnits' corporateAction = 'corporateAction' leg1PriceType = 'leg1PriceType' assetParametersPayerRateOption = 'assetParametersPayerRateOption' sell20cents = 'sell20cents' leg2FixedPaymentCurrency = 'leg2FixedPaymentCurrency' gRegionalScore = 'gRegionalScore' hardToBorrow = 'hardToBorrow' sell5bps = 'sell5bps' rollVwap = 'rollVwap' wpk = 'wpk' bespokeSwap = 'bespokeSwap' assetParametersExpirationDate = 'assetParametersExpirationDate' countryName = 'countryName' carry = 'carry' startingDate = 'startingDate' loanId = 'loanId' onboarded = 'onboarded' liquidityScore = 'liquidityScore' longRatesContribution = 'longRatesContribution' sourceDateSpan = 'sourceDateSpan' annYield6Month = 'annYield6Month' underlyingDataSetId = 'underlyingDataSetId' closeUnadjusted = 'closeUnadjusted' valueUnit = 'valueUnit' voiceCurveReason = 'voiceCurveReason' quantityUnit = 'quantityUnit' adjustedLowPrice = 'adjustedLowPrice' isMomentum = 'isMomentum' longConvictionLarge = 'longConvictionLarge' spotTier = 'spotTier' oad = 'oad' rate = 'rate' couponType = 'couponType' client = 'client' esgDetailedMetric = 'esgDetailedMetric' markToRT = 'markToRT' convictionList = 'convictionList' passiveEtfRatio = 'passiveEtfRatio' futureMonthG26 = 'futureMonthG26' futureMonthG25 = 'futureMonthG25' futureMonthG24 = 'futureMonthG24' futureMonthG23 = 'futureMonthG23' typeOfReturn = 'typeOfReturn' futureMonthG22 = 'futureMonthG22' servicingCostLongPnl = 'servicingCostLongPnl' excessMarginPercentage = 'excessMarginPercentage' futureMonthG21 = 'futureMonthG21' totalMild = 'totalMild' realizedArrivalPerformanceBps = 'realizedArrivalPerformanceBps' precipitationDailyForecastInches = 'precipitationDailyForecastInches' exchangeId = 'exchangeId' leg2FixedPayment = 'leg2FixedPayment' indicativeAvailability = 'indicativeAvailability' tcmCostHorizon20Day = 'tcmCostHorizon20Day' assetClassificationsDigitalAssetIndustry = 'assetClassificationsDigitalAssetIndustry' realm = 'realm' goneManual = 'goneManual' gate = 'gate' bid = 'bid' hedgeValue = 'hedgeValue' isSeasonallyAdjusted = 'isSeasonallyAdjusted' orderStartTime = 'orderStartTime' isAggressive = 'isAggressive' floatingRateDesignatedMaturity = 'floatingRateDesignatedMaturity' percentageNearExecutedQuantity = 'percentageNearExecutedQuantity' orderId = 'orderId' hospitalType = 'hospitalType' aggregatePnl = 'aggregatePnl' dayCloseRealizedBps = 'dayCloseRealizedBps' precipitationHourlyForecast = 'precipitationHourlyForecast' forwardPriceNg = 'forwardPriceNg' marketCapUSD = 'marketCapUSD' auctionFillsPercentage = 'auctionFillsPercentage' highPrice = 'highPrice' absoluteShares = 'absoluteShares' fixedRateDayCountFraction = 'fixedRateDayCountFraction' model = 'model' unrealizedTwapPerformanceUSD = 'unrealizedTwapPerformanceUSD' id = 'id' maturity = 'maturity' deltaChange = 'deltaChange' index = 'index' finalIndexLevel = 'finalIndexLevel' unrealizedArrivalPerformanceUSD = 'unrealizedArrivalPerformanceUSD' icebergSlippage = 'icebergSlippage' sell120cents = 'sell120cents' futureMonthX26 = 'futureMonthX26' assetTypes = 'assetTypes' futureMonthX25 = 'futureMonthX25' bcid = 'bcid' mktPoint = 'mktPoint' futureMonthX24 = 'futureMonthX24' restrictionStartDate = 'restrictionStartDate' touchLiquidityScore = 'touchLiquidityScore' futureMonthX23 = 'futureMonthX23' futureMonthX22 = 'futureMonthX22' factorCategoryId = 'factorCategoryId' securityTypeId = 'securityTypeId' futureMonthX21 = 'futureMonthX21' vintage = 'vintage' investmentYtd = 'investmentYtd' leg2Notional = 'leg2Notional' sell1bps = 'sell1bps' sell200cents = 'sell200cents' expectedCompletionDate = 'expectedCompletionDate' spreadOptionVol = 'spreadOptionVol' sell80cents = 'sell80cents' impliedRetailPctAdv = 'impliedRetailPctAdv' inflationSwapRate = 'inflationSwapRate' activeQueries = 'activeQueries' sell45bps = 'sell45bps' gsLiquidityScore = 'gsLiquidityScore' embededOption = 'embededOption' chargeInLocalCurrency = 'chargeInLocalCurrency' eventSource = 'eventSource' qisPermNo = 'qisPermNo' settlement = 'settlement' shareclassId = 'shareclassId' feature2 = 'feature2' feature3 = 'feature3' settlementCurrency2 = 'settlementCurrency2' stsCommoditySector = 'stsCommoditySector' exceptionStatus = 'exceptionStatus' overnightNewsIntensity = 'overnightNewsIntensity' salesCoverage = 'salesCoverage' feature1 = 'feature1' tcmCostParticipationRate10Pct = 'tcmCostParticipationRate10Pct' eventTime = 'eventTime' positionSourceName = 'positionSourceName' covid19Vaccine = 'covid19Vaccine' deliveryDate = 'deliveryDate' settlementCurrency1 = 'settlementCurrency1' cyclical = 'cyclical' interestRate = 'interestRate' side = 'side' dynamicHybridAggressiveStyle = 'dynamicHybridAggressiveStyle' complianceRestrictedStatus = 'complianceRestrictedStatus' borrowFee = 'borrowFee' everIcu = 'everIcu' noWorseThanLevel = 'noWorseThanLevel' updateTime = 'updateTime' loanSpread = 'loanSpread' tcmCostHorizon12Hour = 'tcmCostHorizon12Hour' dewPoint = 'dewPoint' researchCommission = 'researchCommission' buy2bps = 'buy2bps' assetClassificationsRiskCountryCode = 'assetClassificationsRiskCountryCode' newIdeasMtd = 'newIdeasMtd' varSwapByExpiry = 'varSwapByExpiry' sellDate = 'sellDate' aumStart = 'aumStart' fwdEbookRiskDirClientSellOver = 'fwdEbookRiskDirClientSellOver' feedbackType = 'feedbackType' assetParametersSettlement = 'assetParametersSettlement' maxTemperature = 'maxTemperature' acquirerShareholderMeetingDate = 'acquirerShareholderMeetingDate' countryExchange = 'countryExchange' cusip8 = 'cusip8' countIdeasWtd = 'countIdeasWtd' arrivalRtNormalized = 'arrivalRtNormalized' reportType = 'reportType' sourceURL = 'sourceURL' estimatedReturn = 'estimatedReturn' tradedFwdPoints = 'tradedFwdPoints' high = 'high' sourceLastUpdate = 'sourceLastUpdate' sunshineForecast = 'sunshineForecast' quantityMW = 'quantityMW' sell70cents = 'sell70cents' sell110cents = 'sell110cents' pnodeId = 'pnodeId' price1 = 'price1' price2 = 'price2' referenceRate = 'referenceRate' humidityType = 'humidityType' prevCloseAsk = 'prevCloseAsk' level = 'level' impliedVolatilityByExpiration = 'impliedVolatilityByExpiration' hurdle = 'hurdle' assetParametersFixedRateDayCountFraction = 'assetParametersFixedRateDayCountFraction' esMomentumScore = 'esMomentumScore' leg1CommodityInstrumentId = 'leg1CommodityInstrumentId' leg2Index = 'leg2Index' netWeight = 'netWeight' portfolioManagers = 'portfolioManagers' bosInTicks = 'bosInTicks' assetParametersCouponType = 'assetParametersCouponType' swapPointsAsk = 'swapPointsAsk' expectedResidualQuantity = 'expectedResidualQuantity' clientSwapPointsBid = 'clientSwapPointsBid' rollDate = 'rollDate' dynamicHybridSpeed = 'dynamicHybridSpeed' capFloorVol = 'capFloorVol' targetQuantity = 'targetQuantity' submitter = 'submitter' no = 'no' notional = 'notional' esDisclosurePercentage = 'esDisclosurePercentage' closeExecutedQuantityPercentage = 'closeExecutedQuantityPercentage' twapRealizedCash = 'twapRealizedCash' isOpenAuction = 'isOpenAuction' leg1Type = 'leg1Type' wetBulbTempHourlyForecast = 'wetBulbTempHourlyForecast' cleanupPrice = 'cleanupPrice' externalRejectReason = 'externalRejectReason' total = 'total' filledNotionalUSD = 'filledNotionalUSD' assetId = 'assetId' blockTradeElectionIndicator = 'blockTradeElectionIndicator' testStatus = 'testStatus' mktType = 'mktType' covidDisrupted = 'covidDisrupted' lastUpdatedTime = 'lastUpdatedTime' yield30Day = 'yield30Day' optionPutPremium = 'optionPutPremium' buy28bps = 'buy28bps' proportionOfRisk = 'proportionOfRisk' futureMonthK23 = 'futureMonthK23' futureMonthK22 = 'futureMonthK22' futureMonthK21 = 'futureMonthK21' primaryEntityId = 'primaryEntityId' cross = 'cross' ideaStatus = 'ideaStatus' inCode = 'inCode' contractSubtype = 'contractSubtype' sri = 'sri' fxForecast = 'fxForecast' fixingTimeLabel = 'fixingTimeLabel' isETF = 'isETF' _100 = '100' _101 = '101' _102 = '102' _103 = '103' _104 = '104' fillId = 'fillId' excessReturns = 'excessReturns' _105 = '105' _106 = '106' dollarReturn = 'dollarReturn' orderInLimit = 'orderInLimit' expiryTime = 'expiryTime' _107 = '107' returnOnEquity = 'returnOnEquity' _108 = '108' _109 = '109' futureMonthK26 = 'futureMonthK26' futureMonthK25 = 'futureMonthK25' futureMonthK24 = 'futureMonthK24' restrictionEndDate = 'restrictionEndDate' queueInLotsDescription = 'queueInLotsDescription' volumeLimit = 'volumeLimit' objective = 'objective' navPrice = 'navPrice' leg1UnderlyingAsset = 'leg1UnderlyingAsset' _110 = '110' bbgid = 'bbgid' _111 = '111' _112 = '112' _113 = '113' privatePlacementType = 'privatePlacementType' _114 = '114' hedgeNotional = 'hedgeNotional' _115 = '115' dailyReturn = 'dailyReturn' _116 = '116' askLow = 'askLow' intendedPRate = 'intendedPRate' _117 = '117' _118 = '118' _119 = '119' expiry = 'expiry' assetParametersIndexFamily = 'assetParametersIndexFamily' avgMonthlyYield = 'avgMonthlyYield' periodDirection = 'periodDirection' prevRptId = 'prevRptId' earningsPerShare = 'earningsPerShare' strikePercentage = 'strikePercentage' esProductImpactPercentile = 'esProductImpactPercentile' vwapRealizedCash = 'vwapRealizedCash' parAssetSwapSpread1m = 'parAssetSwapSpread1m' prevCloseBid = 'prevCloseBid' minimumIncrement = 'minimumIncrement' tcmCostHorizon16Day = 'tcmCostHorizon16Day' investmentMtd = 'investmentMtd' settlementDate = 'settlementDate' weightedAverageMidNormalized = 'weightedAverageMidNormalized' _120 = '120' windowLength = 'windowLength' _121 = '121' _122 = '122' salesPerShare = 'salesPerShare' _123 = '123' _124 = '124' _125 = '125' unadjustedClose = 'unadjustedClose' _126 = '126' _127 = '127' _128 = '128' _129 = '129' loanDate = 'loanDate' matchedMaturitySwapSpread1m = 'matchedMaturitySwapSpread1m' collateralPercentageActual = 'collateralPercentageActual' vwapInLimitUnrealizedBps = 'vwapInLimitUnrealizedBps' rSquared = 'rSquared' metricValue = 'metricValue' autoExecState = 'autoExecState' totalRecovered = 'totalRecovered' relativeReturnYtd = 'relativeReturnYtd' _130 = '130' tickServer = 'tickServer' _131 = '131' _132 = '132' _133 = '133' clientOutrightAsk = 'clientOutrightAsk' _134 = '134' cumulativeVolumeInPercentage = 'cumulativeVolumeInPercentage' _135 = '135' underlyingRic = 'underlyingRic' _136 = '136' _137 = '137' _138 = '138' _139 = '139' realTimeRestrictionStatus = 'realTimeRestrictionStatus' tradeType = 'tradeType' settlementType = 'settlementType' netChange = 'netChange' percentOfIssueOutstanding = 'percentOfIssueOutstanding' numberOfUnderliers = 'numberOfUnderliers' swapType = 'swapType' forecastType = 'forecastType' leg1Notional = 'leg1Notional' sellSettleDate = 'sellSettleDate' _140 = '140' _141 = '141' _142 = '142' _143 = '143' _144 = '144' _145 = '145' _146 = '146' _147 = '147' newIdeasYtd = 'newIdeasYtd' managementFee = 'managementFee' _148 = '148' _149 = '149' parAssetSwapSpread3m = 'parAssetSwapSpread3m' sell36bps = 'sell36bps' matchedMaturitySwapSpread3m = 'matchedMaturitySwapSpread3m' sourceId = 'sourceId' country = 'country' optionPremiumCurrency = 'optionPremiumCurrency' vwap = 'vwap' touchSpreadScore = 'touchSpreadScore' lastRebalanceDate = 'lastRebalanceDate' ratingSecondHighest = 'ratingSecondHighest' sell24bps = 'sell24bps' _150 = '150' _151 = '151' _152 = '152' frequency = 'frequency' _153 = '153' _154 = '154' activityId = 'activityId' _155 = '155' estimatedImpact = 'estimatedImpact' sell35cents = 'sell35cents' _156 = '156' loanSpreadBucket = 'loanSpreadBucket' _157 = '157' _158 = '158' coronavirusGlobalActivityTracker = 'coronavirusGlobalActivityTracker' _159 = '159' underlyers = 'underlyers' assetParametersPricingLocation = 'assetParametersPricingLocation' eventDescription = 'eventDescription' icebergMaxSize = 'icebergMaxSize' assetParametersCoupon = 'assetParametersCoupon' details = 'details' sector = 'sector' mktFwdPointAsk = 'mktFwdPointAsk' avgBedUtilRate = 'avgBedUtilRate' buy20bps = 'buy20bps' indexLevel = 'indexLevel' epidemic = 'epidemic' mctr = 'mctr' exchangeTime = 'exchangeTime' historicalClose = 'historicalClose' fipsCode = 'fipsCode' _160 = '160' chargeInQuoteConvention = 'chargeInQuoteConvention' _161 = '161' buy32bps = 'buy32bps' _162 = '162' _163 = '163' ideaId = 'ideaId' commentStatus = 'commentStatus' marginalCost = 'marginalCost' _164 = '164' _165 = '165' _166 = '166' _167 = '167' _168 = '168' clientWeight = 'clientWeight' _169 = '169' leg1DeliveryPoint = 'leg1DeliveryPoint' sell5cents = 'sell5cents' liqWkly = 'liqWkly' unrealizedTwapPerformanceBps = 'unrealizedTwapPerformanceBps' region = 'region' temperatureHour = 'temperatureHour' upperBound = 'upperBound' sell55cents = 'sell55cents' spreadToBenchmark = 'spreadToBenchmark' _170 = '170' _171 = '171' numPediIcuBeds = 'numPediIcuBeds' _172 = '172' bidYield = 'bidYield' _173 = '173' assetParametersStrikePrice = 'assetParametersStrikePrice' _174 = '174' expectedResidual = 'expectedResidual' _175 = '175' fairValueGapPercent = 'fairValueGapPercent' _176 = '176' optionPremium = 'optionPremium' _177 = '177' _178 = '178' _179 = '179' ownerName = 'ownerName' parAssetSwapSpread6m = 'parAssetSwapSpread6m' zScore = 'zScore' sell12bps = 'sell12bps' eventStartTime = 'eventStartTime' matchedMaturitySwapSpread6m = 'matchedMaturitySwapSpread6m' turnover = 'turnover' priceSpotTargetUnit = 'priceSpotTargetUnit' coverage = 'coverage' gPercentile = 'gPercentile' _180 = '180' rvtAdj = 'rvtAdj' _181 = '181' _182 = '182' cloudCoverHourlyForecast = 'cloudCoverHourlyForecast' _183 = '183' assetParametersPayerSpread = 'assetParametersPayerSpread' _184 = '184' lendingFundNav = 'lendingFundNav' sourceOriginalCategory = 'sourceOriginalCategory' percentCloseExecutionQuantity = 'percentCloseExecutionQuantity' _185 = '185' latestExecutionTime = 'latestExecutionTime' _186 = '186' _187 = '187' arrivalMidRealizedBps = 'arrivalMidRealizedBps' _188 = '188' _189 = '189' location = 'location' scenarioId = 'scenarioId' terminationTenor = 'terminationTenor' queueClockTime = 'queueClockTime' discretionLowerBound = 'discretionLowerBound' tcmCostParticipationRate50Pct = 'tcmCostParticipationRate50Pct' ratingLinear = 'ratingLinear' previousCloseUnrealizedBps = 'previousCloseUnrealizedBps' _190 = '190' _191 = '191' subAssetClassForOtherCommodity = 'subAssetClassForOtherCommodity' _192 = '192' forwardPrice = 'forwardPrice' _193 = '193' type = 'type' fwdPointsMarketBid = 'fwdPointsMarketBid' _194 = '194' strikeRef = 'strikeRef' _195 = '195' _196 = '196' _197 = '197' cumulativePnl = 'cumulativePnl' _198 = '198' shortTenor = 'shortTenor' sell28bps = 'sell28bps' fundClass = 'fundClass' _199 = '199' unadjustedVolume = 'unadjustedVolume' buy36bps = 'buy36bps' positionIdx = 'positionIdx' cvaDollarChargeBid = 'cvaDollarChargeBid' midZSpread = 'midZSpread' windChillHourlyForecast = 'windChillHourlyForecast' secName = 'secName' impliedVolatilityByRelativeStrike = 'impliedVolatilityByRelativeStrike' assetParametersIndex2Tenor = 'assetParametersIndex2Tenor' percentADV = 'percentADV' referenceRateUSD = 'referenceRateUSD' leg1TotalNotional = 'leg1TotalNotional' contract = 'contract' nvtAdj90 = 'nvtAdj90' paymentFrequency1 = 'paymentFrequency1' paymentFrequency2 = 'paymentFrequency2' bespoke = 'bespoke' repoTenor = 'repoTenor' sell15cents = 'sell15cents' quoteId = 'quoteId' investmentQtd = 'investmentQtd' openToSMA = 'openToSMA' heatIndexForecast = 'heatIndexForecast' ratingStandardAndPoors = 'ratingStandardAndPoors' qualityStars = 'qualityStars' leg2FloatingIndex = 'leg2FloatingIndex' sourceTicker = 'sourceTicker' primaryVwapUnrealizedBps = 'primaryVwapUnrealizedBps' assetParametersCreditIndexSeries = 'assetParametersCreditIndexSeries' gsid = 'gsid' lendingFund = 'lendingFund' assetClassificationsDigitalAssetSubsector = 'assetClassificationsDigitalAssetSubsector' sensitivity = 'sensitivity' clientSwapPointsAsk = 'clientSwapPointsAsk' embeddedOptionType = 'embeddedOptionType' domainsData = 'domainsData' dayCount = 'dayCount' sell16bps = 'sell16bps' relativeBreakEvenInflationChange = 'relativeBreakEvenInflationChange' sell25cents = 'sell25cents' varSwap = 'varSwap' buy5point5bps = 'buy5point5bps' blockLargeNotional = 'blockLargeNotional' sell2point5bps = 'sell2point5bps' capacity = 'capacity' sectorsRaw = 'sectorsRaw' chargeInDollars = 'chargeInDollars' primaryVwapInLimit = 'primaryVwapInLimit' shareclassPrice = 'shareclassPrice' fwdEbookRiskDirClientBuyUnder = 'fwdEbookRiskDirClientBuyUnder' tradeSize = 'tradeSize' priceSpotEntryValue = 'priceSpotEntryValue' buy8point5bps = 'buy8point5bps' symbolDimensions = 'symbolDimensions' buy24bps = 'buy24bps' auctionCloseQuantity = 'auctionCloseQuantity' sidePocket = 'sidePocket' observation = 'observation' optionTypeSDR = 'optionTypeSDR' isEntity = 'isEntity' scenarioGroupId = 'scenarioGroupId' averageImpliedVariance = 'averageImpliedVariance' avgTradeRateDescription = 'avgTradeRateDescription' fraction = 'fraction' assetCountShort = 'assetCountShort' collateralPercentageRequired = 'collateralPercentageRequired' spotMarketAsk = 'spotMarketAsk' sell5point5bps = 'sell5point5bps' date = 'date' zipCode = 'zipCode' totalStdReturnSinceInception = 'totalStdReturnSinceInception' sourceCategory = 'sourceCategory' volumeUnadjusted = 'volumeUnadjusted' passiveRatio = 'passiveRatio' priceToEarnings = 'priceToEarnings' orderDepth = 'orderDepth' annYield3Month = 'annYield3Month' netFlowStd = 'netFlowStd' eZeroPriceWhenTraded = 'eZeroPriceWhenTraded' assetParametersFeeCurrency = 'assetParametersFeeCurrency' encodedStats = 'encodedStats' buy5bps = 'buy5bps' runTime = 'runTime' askSize = 'askSize' absoluteReturnMtd = 'absoluteReturnMtd' std30DaysUnsubsidizedYield = 'std30DaysUnsubsidizedYield' assetParametersReceiverSpread = 'assetParametersReceiverSpread' resource = 'resource' averageRealizedVolatility = 'averageRealizedVolatility' traceAdvBuy = 'traceAdvBuy' newConfirmed = 'newConfirmed' tax = 'tax' sell8bps = 'sell8bps' bidPrice = 'bidPrice' optionCallPremium = 'optionCallPremium' s3ShortInterestPercentFloat = 's3ShortInterestPercentFloat' sell8point5bps = 'sell8point5bps' targetPriceUnrealizedBps = 'targetPriceUnrealizedBps' clientSpotBid = 'clientSpotBid' assetParametersCallCurrency = 'assetParametersCallCurrency' esNumericPercentile = 'esNumericPercentile' leg2UnderlyingAsset = 'leg2UnderlyingAsset' csaTerms = 'csaTerms' relativePayoffMtd = 'relativePayoffMtd' dailyNetShareholderFlows = 'dailyNetShareholderFlows' buy2point5bps = 'buy2point5bps' cai = 'cai' executedNotionalUSD = 'executedNotionalUSD' systemTime = 'systemTime' totalHomeIsolation = 'totalHomeIsolation' stationName = 'stationName' passPct = 'passPct' openingReport = 'openingReport' dailyShortInterest = 'dailyShortInterest' eventTimestamp = 'eventTimestamp' tcm = 'tcm' midcurveAtmFwdRate = 'midcurveAtmFwdRate' precipitationForecast = 'precipitationForecast' equityRiskPremiumIndex = 'equityRiskPremiumIndex' fatalitiesUnderlyingConditionsUnknown = 'fatalitiesUnderlyingConditionsUnknown' tradeDate = 'tradeDate' buy12bps = 'buy12bps' clearingHouse = 'clearingHouse' dayCloseUnrealizedBps = 'dayCloseUnrealizedBps' stsRatesMaturity = 'stsRatesMaturity' stsIncludeSstkAnalytics = 'stsIncludeSstkAnalytics' nonOwnerId = 'nonOwnerId' dividendsPerShare = 'dividendsPerShare' liqDly = 'liqDly' contributorRole = 'contributorRole' totalFatalities = 'totalFatalities' internalRejectReason = 'internalRejectReason' adjustedClose = 'adjustedClose' averageValue = 'averageValue' avgInterestRate = 'avgInterestRate' basisDuration = 'basisDuration' bestMonthDate = 'bestMonthDate' bloombergTicker = 'bloombergTicker' capexDepreciation = 'capexDepreciation' capexSales = 'capexSales' cashConversion = 'cashConversion' category = 'category' convexity = 'convexity' countryCode = 'countryCode' croci = 'croci' currentValue = 'currentValue' dacf = 'dacf' dailyVolatility = 'dailyVolatility' divYield = 'divYield' dpsGrowth = 'dpsGrowth' drawdownOverReturn = 'drawdownOverReturn' ebitdaGrowth = 'ebitdaGrowth' ebitdaMargin = 'ebitdaMargin' ebitGrowth = 'ebitGrowth' ebitMargin = 'ebitMargin' evGci = 'evGci' fcfConversion = 'fcfConversion' fcfYield = 'fcfYield' gci = 'gci' grossProfTotAssets = 'grossProfTotAssets' historicCPR = 'historicCPR' incrementalMargin = 'incrementalMargin' industry = 'industry' informationRatio = 'informationRatio' interestCover = 'interestCover' lastChange = 'lastChange' lastChangePct = 'lastChangePct' lastDate = 'lastDate' lastValue = 'lastValue' liborMatchedMaturitySwap = 'liborMatchedMaturitySwap' liborOAS = 'liborOAS' liborProceedsASW = 'liborProceedsASW' liborzSpread = 'liborzSpread' manEarningGrowthMeas = 'manEarningGrowthMeas' marginalRiskContribution = 'marginalRiskContribution' maxDrawdown = 'maxDrawdown' netDebtEbitda = 'netDebtEbitda' netDebtEquity = 'netDebtEquity' niGrowth = 'niGrowth' niMargin = 'niMargin' oisMatchedMaturitySwap = 'oisMatchedMaturitySwap' oisProceedsASW = 'oisProceedsASW' oiszSpread = 'oiszSpread' optionStyle = 'optionStyle' payup = 'payup' preTaxProfitGrowth = 'preTaxProfitGrowth' riskPremiaStyles = 'riskPremiaStyles' roce = 'roce' rolldown = 'rolldown' salesGrowth = 'salesGrowth' sharpeRatio = 'sharpeRatio' totalDebtCapital = 'totalDebtCapital' totalDebtTotalAsset = 'totalDebtTotalAsset' totalReturn = 'totalReturn' unleveredFcfYield = 'unleveredFcfYield' worstMonthDate = 'worstMonthDate' class FiniteDifferenceMethod(EnumBase, Enum): """Direction and dimension of finite difference""" Up = 'Up' Centered = 'Centered' Down = 'Down' CenteredSecondOrder = 'CenteredSecondOrder' class Format(EnumBase, Enum): """Alternative format for data to be returned in""" Json = 'Json' Excel = 'Excel' MessagePack = 'MessagePack' Pdf = 'Pdf' class FrequencyInterval(EnumBase, Enum): """Frequency interval.""" Weekly = 'Weekly' Monthly = 'Monthly' Quarterly = 'Quarterly' Annually = 'Annually' class InOut(EnumBase, Enum): In = 'In' Out = 'Out' class IndexCalculationType(EnumBase, Enum): """Quote type that is used for the bond price""" Price_Return = 'Price Return' class IndexNotTradingReasons(EnumBase, Enum): """Reasons the index was not traded""" Cost = 'Cost' Client_does_not_like_the_construction = 'Client does not like the construction' Basket_created_prematurely = 'Basket created prematurely' Economics_of_the_basket_changed__client_no_longer_interested_in_trading = 'Economics of the basket changed: client no longer interested in trading' GS_booking_OVER_operational_issues = 'GS booking/operational issues' _ = '' class KnockoutConvention(EnumBase, Enum): """Knockout convention""" Continuous = 'Continuous' Brazil = 'Brazil' INR = 'INR' Korean = 'Korean' Malaysia = 'Malaysia' Philippines = 'Philippines' Taipei = 'Taipei' class LiquidityMeasure(EnumBase, Enum): """A list of the different liquidity measures to choose from.""" Summary = 'Summary' Constituent_Transaction_Costs = 'Constituent Transaction Costs' Constituents = 'Constituents' Largest_Holdings_By_Weight = 'Largest Holdings By Weight' Least_Liquid_Holdings = 'Least Liquid Holdings' ADV_Percent_Buckets = 'ADV Percent Buckets' Market_Cap_Buckets = 'Market Cap Buckets' Region_Buckets = 'Region Buckets' Country_Buckets = 'Country Buckets' Sector_Buckets = 'Sector Buckets' Industry_Buckets = 'Industry Buckets' Currency_Buckets = 'Currency Buckets' Risk_Buckets = 'Risk Buckets' Factor_Risk_Buckets = 'Factor Risk Buckets' Exposure_Buckets = 'Exposure Buckets' Factor_Exposure_Buckets = 'Factor Exposure Buckets' Percent_Of_Trade_Complete_Over_Time = 'Percent Of Trade Complete Over Time' Execution_Cost_With_Different_Time_Horizons = 'Execution Cost With Different Time Horizons' Participation_Rate_With_Different_Time_Horizons = 'Participation Rate With Different Time Horizons' Risk_With_Different_Time_Horizons = 'Risk With Different Time Horizons' Historical_ADV_Percent_Curve = 'Historical ADV Percent Curve' Time_Series_Data = 'Time Series Data' class LongShort(EnumBase, Enum): """Client long or short on tarf""" Long = 'Long' Short = 'Short' class MarketBehaviour(EnumBase, Enum): ContraintsBased = 'ContraintsBased' Calibrated = 'Calibrated' class MarketDataFrequency(EnumBase, Enum): Real_Time = 'Real Time' End_Of_Day = 'End Of Day' class MarketDataShockType(EnumBase, Enum): """Market data shock type""" Absolute = 'Absolute' Proportional = 'Proportional' Invalid = 'Invalid' Override = 'Override' StdDev = 'StdDev' AutoDefault = 'AutoDefault' CSWFFR = 'CSWFFR' StdVolFactor = 'StdVolFactor' StdVolFactorProportional = 'StdVolFactorProportional' class MarketDataVendor(EnumBase, Enum): Goldman_Sachs = 'Goldman Sachs' Thomson_Reuters = 'Thomson Reuters' Solactive = 'Solactive' Bloomberg = 'Bloomberg' Axioma = 'Axioma' Goldman_Sachs_Prime_Services = 'Goldman Sachs Prime Services' Goldman_Sachs_Global_Investment_Research = 'Goldman Sachs Global Investment Research' National_Weather_Service = 'National Weather Service' WM = 'WM' Hedge_Fund_Research__Inc_ = 'Hedge Fund Research, Inc.' London_Stock_Exchange = 'London Stock Exchange' Goldman_Sachs_MDFarm = 'Goldman Sachs MDFarm' PredictIt = 'PredictIt' Iowa_Electronic_Markets = 'Iowa Electronic Markets' RealClearPolitics = 'RealClearPolitics' _538 = '538' FiveThirtyEight = 'FiveThirtyEight' Opinium = 'Opinium' YouGov = 'YouGov' MorningStar = 'MorningStar' Survation = 'Survation' Survation__YouGov = 'Survation, YouGov' European_Centre_for_Disease_Prevention_and_Control = 'European Centre for Disease Prevention and Control' Centers_for_Disease_Control_and_Prevention = 'Centers for Disease Control and Prevention' Johns_Hopkins_University = 'Johns Hopkins University' Google = 'Google' National_Health_Service = 'National Health Service' World_Health_Organization = 'World Health Organization' Wikipedia = 'Wikipedia' StarSchema = 'StarSchema' Covid_Working_Group = 'Covid Working Group' CovidTracking = 'CovidTracking' Bing = 'Bing' FRED = 'FRED' Institute_for_Health_Metrics_and_Evaluation = 'Institute for Health Metrics and Evaluation' Refinitiv = 'Refinitiv' Goldman_Sachs_Global_Investment_Research__Refinitiv = 'Goldman Sachs Global Investment Research, Refinitiv' EPFR = 'EPFR' Coin_Metrics = 'Coin Metrics' MSCI = 'MSCI' MuniNet = 'MuniNet' Rearc_via_AWS_Data_Exchange = 'Rearc via AWS Data Exchange' Bank_of_Japan = 'Bank of Japan' Wolfe_Research = 'Wolfe Research' Qontigo = 'Qontigo' Quant_Insight = 'Quant Insight' FactSet_via_AWS_Data_Exchange = 'FactSet via AWS Data Exchange' Rearc = 'Rearc' FactSet = 'FactSet' WorldScope = 'WorldScope' GS_Muni = 'GS Muni' BlackRock = 'BlackRock' S3_Partners = 'S3 Partners' Turnleaf_Analytics = 'Turnleaf Analytics' Alpharoc = 'Alpharoc' LSEG = 'LSEG' ETF_GLobal = 'ETF GLobal' Northfield = 'Northfield' Haver_Analytics = 'Haver Analytics' Fineon = 'Fineon' ICE = 'ICE' CME = 'CME' Cboe = 'Cboe' Rose_AI = 'Rose.AI' QuantCube = 'QuantCube' Mkt_MediaStats = 'Mkt MediaStats' class NewOrUnwind(EnumBase, Enum): """New or unwnd of product""" New = 'New' Unwind = 'Unwind' Non_Standard = 'Non-Standard' class NotionalOrStrike(EnumBase, Enum): """Notional or Strke on target adjustment""" Notional = 'Notional' Strike = 'Strike' NA = 'NA' class OptionExerciseStyle(EnumBase, Enum): """How the option is exercised (e.g. Auto, Manual)""" Auto = 'Auto' Manual = 'Manual' class OptionExpiryType(EnumBase, Enum): _1m = '1m' _2m = '2m' _3m = '3m' _4m = '4m' _5m = '5m' _6m = '6m' class OptionSettlementMethod(EnumBase, Enum): """How the option is settled (e.g. Cash, Physical)""" Cash = 'Cash' Physical = 'Physical' ElectDfltCash = 'ElectDfltCash' ElectDfltPhys = 'ElectDfltPhys' NetShares = 'NetShares' class OptionStrikeType(EnumBase, Enum): Relative = 'Relative' Delta = 'Delta' class OptionStyle(EnumBase, Enum): """Option Exercise Style""" European = 'European' American = 'American' Bermudan = 'Bermudan' Asian = 'Asian' class OptionType(EnumBase, Enum): """Option Type""" Call = 'Call' Put = 'Put' Forward = 'Forward' Binary_Call = 'Binary Call' Binary_Put = 'Binary Put' Digital_Call = 'Digital Call' Digital_Put = 'Digital Put' class PCOActionType(EnumBase, Enum): """Types of PCO Actions""" Generate_Orders = 'Generate Orders' Update_Parameters = 'Update Parameters' Update_Client_Data = 'Update Client Data' Update_Open_Hedge_Notional = 'Update Open Hedge Notional' Add_Order = 'Add Order' class PCOCurrencyType(EnumBase, Enum): """Currency Type Options for PCO""" Exposure = 'Exposure' Base = 'Base' Local = 'Local' class PCOOrigin(EnumBase, Enum): """Origin of PCO Report""" PCOGui = 'PCOGui' PCOBackend = 'PCOBackend' class PayReceive(EnumBase, Enum): """Pay or receive fixed""" Pay = 'Pay' Payer = 'Payer' Receive = 'Receive' Receiver = 'Receiver' Straddle = 'Straddle' Rec = 'Rec' class PaymentFrequency(EnumBase, Enum): Every_Return = 'Every Return' Maturity = 'Maturity' class PayoutType(EnumBase, Enum): """Delayed or Immediate payout""" Delayed = 'Delayed' Immediate = 'Immediate' class Period(EnumBase, Enum): """A coding scheme to define a period corresponding to a quantity amount""" Month = 'Month' Quarter = 'Quarter' Hour = 'Hour' Day = 'Day' BusinessDay = 'BusinessDay' class PositionSetWeightingStrategy(EnumBase, Enum): """Strategy used to price the position set.""" Equal = 'Equal' Market_Capitalization = 'Market Capitalization' Quantity = 'Quantity' Weight = 'Weight' Notional = 'Notional' class PricingLocation(EnumBase, Enum): """Based on the location of the exchange. Called 'Native Region' in SecDB""" NYC = 'NYC' LDN = 'LDN' TKO = 'TKO' HKG = 'HKG' class PrincipalExchange(EnumBase, Enum): """How principal is exchanged""" _None = 'None' Both = 'Both' First = 'First' Last = 'Last' class ProductCode(EnumBase, Enum): """Override the clearing destination/symbol""" CME__BB = 'CME::BB' CME__BK = 'CME::BK' CME__BY = 'CME::BY' CME__BZ = 'CME::BZ' CME__CL = 'CME::CL' CME__CL_BZ = 'CME::CL-BZ' CME__CS = 'CME::CS' CME__CY = 'CME::CY' CME__HK = 'CME::HK' CME__HO = 'CME::HO' CME__HOB = 'CME::HOB' CME__HO_CL = 'CME::HO-CL' CME__MP = 'CME::MP' CME__NG = 'CME::NG' CME__NLS = 'CME::NLS' CME__RB = 'CME::RB' CME__RBB = 'CME::RBB' CME__RB_BZ = 'CME::RB-BZ' CME__RB_CL = 'CME::RB-CL' CME__RH = 'CME::RH' CME__RL = 'CME::RL' CME__RM = 'CME::RM' CME__WS = 'CME::WS' CME_ICE__RB_B = 'CME-ICE::RB-B' ICE__B = 'ICE::B' ICE__BNB = 'ICE::BNB' ICE__BTD = 'ICE::BTD' ICE__G = 'ICE::G' ICE__G_B = 'ICE::G-B' ICE__HBT = 'ICE::HBT' ICE__HNG = 'ICE::HNG' ICE__HOF = 'ICE::HOF' ICE__I = 'ICE::I' ICE__N = 'ICE::N' ICE__N_B = 'ICE::N-B' ICE__O = 'ICE::O' ICE__O_B = 'ICE::O-B' ICE__R = 'ICE::R' ICE__RBR = 'ICE::RBR' ICE__T = 'ICE::T' ICE__T_B = 'ICE::T-B' ICE__ULA = 'ICE::ULA' ICE__ULC = 'ICE::ULC' ICE__ULD = 'ICE::ULD' ICE__ULM = 'ICE::ULM' ICE__WTB = 'ICE::WTB' LME__MAL = 'LME::MAL' LME__MNI = 'LME::MNI' LME__MPB = 'LME::MPB' LME__MZN = 'LME::MZN' OTC__BRT = 'OTC::BRT' OTC__GO = 'OTC::GO' OTC__GO_BRT = 'OTC::GO-BRT' OTC__HO = 'OTC::HO' OTC__HO_BRT = 'OTC::HO-BRT' OTC__HO_GO = 'OTC::HO-GO' OTC__HO_WTI = 'OTC::HO-WTI' OTC__MAL = 'OTC::MAL' OTC__MNI = 'OTC::MNI' OTC__MPB = 'OTC::MPB' OTC__MZN = 'OTC::MZN' OTC__NG = 'OTC::NG' OTC__RB = 'OTC::RB' OTC__RB_BRT = 'OTC::RB-BRT' OTC__RB_HO = 'OTC::RB-HO' OTC__RB_WTI = 'OTC::RB-WTI' OTC__WTI = 'OTC::WTI' OTC__WTI_BRT = 'OTC::WTI-BRT' class ProductType(EnumBase, Enum): """Product type of basket""" Flow = 'Flow' MPS = 'MPS' PWM = 'PWM' Volatility = 'Volatility' Single_Stock = 'Single Stock' Indexified_Basket = 'Indexified Basket' class QuoteType(EnumBase, Enum): """Quote type that is used for the bond price""" Dirty_Price = 'Dirty Price' Clean_Price = 'Clean Price' BM_Spread = 'BM Spread' Yield = 'Yield' Z_Spread = 'Z-Spread' G_Spread = 'G-Spread' TRS = 'TRS' TRS_w_OVER__Funding = 'TRS w/ Funding' TRS_w_OVER__Full_Funding = 'TRS w/ Full Funding' class Region(EnumBase, Enum): """Regional classification for the asset""" _ = '' Americas = 'Americas' Asia = 'Asia' EM = 'EM' Europe = 'Europe' Global = 'Global' class ReportJobPriority(EnumBase, Enum): """Report job priority.""" High = 'High' Normal = 'Normal' class ReturnStyle(EnumBase, Enum): """Return calculation style""" Rate_of_Return = 'Rate of Return' Price_Return = 'Price Return' class ReturnType(EnumBase, Enum): """Sum or Product of periodic return, relevant only if paying at maturity""" Sum = 'Sum' Product = 'Product' class RiskMeasureType(EnumBase, Enum): """The type of measure to perform risk on. e.g. Greeks""" Annual_ATM_Implied_Volatility = 'Annual ATM Implied Volatility' Annual_ATMF_Implied_Volatility = 'Annual ATMF Implied Volatility' Annual_Implied_Volatility = 'Annual Implied Volatility' AnnuityLocalCcy = 'AnnuityLocalCcy' ATM_Spread = 'ATM Spread' BaseCPI = 'BaseCPI' Basis = 'Basis' BSPrice = 'BSPrice' BSPricePct = 'BSPricePct' CRIF_IRCurve = 'CRIF IRCurve' Cashflows = 'Cashflows' CDIForward = 'CDIForward' CDIIndexDelta = 'CDIIndexDelta' CDIIndexVega = 'CDIIndexVega' CDIOptionPremium = 'CDIOptionPremium' CDIOptionPremiumFlatFwd = 'CDIOptionPremiumFlatFwd' CDIOptionPremiumFlatVol = 'CDIOptionPremiumFlatVol' CDISpot = 'CDISpot' CDISpreadDV01 = 'CDISpreadDV01' CDIUpfrontPrice = 'CDIUpfrontPrice' Compounded_Fixed_Rate = 'Compounded Fixed Rate' Correlation = 'Correlation' Cross_Multiplier = 'Cross Multiplier' Cross = 'Cross' Daily_Implied_Volatility = 'Daily Implied Volatility' Delta = 'Delta' DeltaLocalCcy = 'DeltaLocalCcy' Description = 'Description' Dollar_Price = 'Dollar Price' DV01 = 'DV01' ExpiryInYears = 'ExpiryInYears' FairPremium = 'FairPremium' FairPremiumPct = 'FairPremiumPct' Fair_Price = 'Fair Price' FairVarStrike = 'FairVarStrike' FairVolStrike = 'FairVolStrike' FinalCPI = 'FinalCPI' Forward_Price = 'Forward Price' Forward_Rate = 'Forward Rate' Forward_Spread = 'Forward Spread' FX_BF_25_Vol = 'FX BF 25 Vol' FX_Calculated_Delta = 'FX Calculated Delta' FX_Calculated_Delta_No_Premium_Adjustment = 'FX Calculated Delta No Premium Adjustment' FX_Discount_Factor_Over = 'FX Discount Factor Over' FX_Discount_Factor_Under = 'FX Discount Factor Under' FX_Hedge_Delta = 'FX Hedge Delta' FX_Premium = 'FX Premium' FX_Premium_Pct = 'FX Premium Pct' FX_Premium_Pct_Flat_Fwd = 'FX Premium Pct Flat Fwd' FX_Quoted_Delta_No_Premium_Adjustment = 'FX Quoted Delta No Premium Adjustment' FX_Quoted_Vega = 'FX Quoted Vega' FX_Quoted_Vega_Bps = 'FX Quoted Vega Bps' FX_RR_25_Vol = 'FX RR 25 Vol' FXSpotVal = 'FXSpotVal' Price = 'Price' Gamma = 'Gamma' GammaLocalCcy = 'GammaLocalCcy' Implied_Volatility = 'Implied Volatility' InflationDelta = 'InflationDelta' Inflation_Compounding_Period = 'Inflation Compounding Period' Inflation_Delta_in_Bps = 'Inflation Delta in Bps' Local_Currency_Accrual_in_Cents = 'Local Currency Accrual in Cents' Local_Currency_Annuity = 'Local Currency Annuity' Market_Data = 'Market Data' Market = 'Market' Market_Data_Assets = 'Market Data Assets' MV = 'MV' NonUSDOisDomesticRate = 'NonUSDOisDomesticRate' OAS = 'OAS' OisFXSpreadRateExcludingSpikes = 'OisFXSpreadRateExcludingSpikes' OisFXSpreadRate = 'OisFXSpreadRate' ParallelBasis = 'ParallelBasis' ParallelDelta = 'ParallelDelta' ParallelDeltaLocalCcy = 'ParallelDeltaLocalCcy' ParallelDiscountDelta = 'ParallelDiscountDelta' ParallelDiscountDeltaLocalCcy = 'ParallelDiscountDeltaLocalCcy' ParallelInflationDelta = 'ParallelInflationDelta' ParallelIndexDelta = 'ParallelIndexDelta' ParallelIndexDeltaLocalCcy = 'ParallelIndexDeltaLocalCcy' ParallelInflationDeltaLocalCcy = 'ParallelInflationDeltaLocalCcy' ParallelXccyDelta = 'ParallelXccyDelta' ParallelXccyDeltaLocalCcy = 'ParallelXccyDeltaLocalCcy' ParallelGamma = 'ParallelGamma' ParallelGammaLocalCcy = 'ParallelGammaLocalCcy' ParallelVega = 'ParallelVega' ParallelVegaLocalCcy = 'ParallelVegaLocalCcy' Points = 'Points' Premium_In_Cents = 'Premium In Cents' Premium = 'Premium' Probability_Of_Exercise = 'Probability Of Exercise' Resolved_Instrument_Values = 'Resolved Instrument Values' PNL = 'PNL' PnlExplain = 'PnlExplain' PnlExplainLocalCcy = 'PnlExplainLocalCcy' PnlPredict = 'PnlPredict' PV = 'PV' QuotedDelta = 'QuotedDelta' RFRFXRate = 'RFRFXRate' RFRFXSpreadRate = 'RFRFXSpreadRate' RFRFXSpreadRateExcludingSpikes = 'RFRFXSpreadRateExcludingSpikes' Spot = 'Spot' Spot_Rate = 'Spot Rate' Spread = 'Spread' Strike = 'Strike' StrikePts = 'StrikePts' Theta = 'Theta' USDOisDomesticRate = 'USDOisDomesticRate' Vanna = 'Vanna' Vega = 'Vega' VegaLocalCcy = 'VegaLocalCcy' Volga = 'Volga' Volatility = 'Volatility' XccyDelta = 'XccyDelta' class RiskMeasureUnit(EnumBase, Enum): """The unit of change of underlying in the risk computation.""" Percent = 'Percent' Dollar = 'Dollar' BPS = 'BPS' Pips = 'Pips' class RiskModelType(EnumBase, Enum): """Marquee risk model type""" Factor = 'Factor' Macro = 'Macro' Thematic = 'Thematic' class ScenarioType(EnumBase, Enum): """Type of Scenario""" Spot_Vol = 'Spot Vol' Greeks = 'Greeks' class SettlementType(EnumBase, Enum): """Settlement Type""" Cash = 'Cash' Physical = 'Physical' class Side(EnumBase, Enum): """Official side of an index""" Bid = 'Bid' Ask = 'Ask' Mid = 'Mid' class Strategy(EnumBase, Enum): """More specific descriptor of a fund's investment approach. Same view permissions as the asset""" Active_Extension = 'Active Extension' Active_Trading = 'Active Trading' Activist = 'Activist' Asset_Backed_Lending = 'Asset Backed Lending' Buyout = 'Buyout' Co_Invest__OVER__SPV = 'Co-Invest / SPV' Commodity = 'Commodity' Commodities = 'Commodities' Composite = 'Composite' Conservative = 'Conservative' Convert_Arb = 'Convert Arb' Convertible_Arbitrage = 'Convertible Arbitrage' Credit_Arbitrage = 'Credit Arbitrage' Cross_Capital_Structure = 'Cross-Capital-Structure' CTA__OVER__Managed_Futures = 'CTA / Managed Futures' Currency = 'Currency' Direct_Lending = 'Direct Lending' Discretionary = 'Discretionary' Discretionary_Thematic = 'Discretionary Thematic' Distressed = 'Distressed' Distressed__OVER__Special_Situations = 'Distressed / Special Situations' Distressed_Securities = 'Distressed Securities' Distressed_OVER_Restructuring = 'Distressed/Restructuring' Diversified = 'Diversified' Equity_Hedge = 'Equity Hedge' Equity_Market_Neutral = 'Equity Market Neutral' Equity_Only = 'Equity Only' Event_Driven = 'Event-Driven' Fixed_Income_Arb = 'Fixed Income Arb' Fixed_Income_Asset_Backed = 'Fixed Income-Asset Backed' Fixed_Income_Corporate = 'Fixed Income-Corporate' Fixed_Income_Sovereign = 'Fixed Income-Sovereign' Fundamental_Growth = 'Fundamental Growth' Fundamental_Value = 'Fundamental Value' General = 'General' General_Multi_Strategy = 'General Multi-Strategy' Generalist = 'Generalist' Growth_Equity = 'Growth Equity' Hybrid__OVER__Illiquid = 'Hybrid / Illiquid' Long__OVER__Short = 'Long / Short' Macro = 'Macro' Market_Defensive = 'Market Defensive' Merger_Arb = 'Merger Arb' Merger_Arbitrage = 'Merger Arbitrage' Multi_Strategy = 'Multi-Strategy' Quantitative_Directional = 'Quantitative Directional' Real_Estate__OVER__Real_Asset = 'Real Estate / Real Asset' Real_Estate__OVER__Real_Asset_Credit = 'Real Estate / Real Asset Credit' Relative_Value_Arbitrage = 'Relative Value Arbitrage' Risk_Premia = 'Risk Premia' Sector___Energy_OVER_Basic_Materials = 'Sector - Energy/Basic Materials' Sector___Healthcare = 'Sector - Healthcare' Sector___Technology = 'Sector - Technology' Sector___Technology_OVER_Healthcare = 'Sector - Technology/Healthcare' Sector_Specific = 'Sector-Specific' Short_Bias = 'Short Bias' Special_Situations = 'Special Situations' Specialty_Finance = 'Specialty Finance' Stat_Arb = 'Stat Arb' Statistical_Arbitrage = 'Statistical Arbitrage' Strategic = 'Strategic' Structured = 'Structured' Systematic = 'Systematic' Systematic_Diversified = 'Systematic Diversified' Vol_Arb__OVER__Options = 'Vol Arb / Options' Volatility = 'Volatility' Volatility_Target_10 = 'Volatility Target 10' Volatility_Target_12 = 'Volatility Target 12' Volatility_Target_15 = 'Volatility Target 15' Yield_Alternative = 'Yield Alternative' class StrikeMethodType(EnumBase, Enum): Spread = 'Spread' Delta = 'Delta' Percentage_of_Price = 'Percentage of Price' Fixed = 'Fixed' class SwapClearingHouse(EnumBase, Enum): """Swap Clearing House""" LCH = 'LCH' EUREX = 'EUREX' JSCC = 'JSCC' CME = 'CME' NONE = 'NONE' class SwapSettlement(EnumBase, Enum): """Swap Settlement Type""" Phys_CLEARED = 'Phys.CLEARED' Physical = 'Physical' Cash_CollatCash = 'Cash.CollatCash' Cash_PYU = 'Cash.PYU' class SwapType(EnumBase, Enum): Eq_CFD_Standard = 'Eq CFD Standard' Eq_Swap = 'Eq Swap' Eq_Swap_OET_Asymmetric = 'Eq Swap OET Asymmetric' Eq_Swap_OET_Simple = 'Eq Swap OET Simple' Eq_Swap_Term = 'Eq Swap Term' Eq_Synthetic_OET = 'Eq Synthetic OET' class TargetPaymentType(EnumBase, Enum): Capped = 'Capped' Full = 'Full' _None = 'None' class TargetType(EnumBase, Enum): """Target type for accural redemption forward""" Big_Figures = 'Big Figures' Amount = 'Amount' Num_Of_ITM_Fixes = 'Num Of ITM Fixes' class TouchNoTouch(EnumBase, Enum): """Indicates Touch or NoTouch""" Touch = 'Touch' No_Touch = 'No Touch' class TradeAs(EnumBase, Enum): """Option trade as (i.e. listed, otc, lookalike etc)""" Listed = 'Listed' Listed_Look_alike_OTC = 'Listed Look alike OTC' Flex = 'Flex' OTC = 'OTC' class TradeType(EnumBase, Enum): """Direction""" Buy = 'Buy' Sell = 'Sell' class UnderlierType(EnumBase, Enum): """Type of underlyer""" BBID = 'BBID' BID = 'BID' CUSIP = 'CUSIP' ISIN = 'ISIN' SEDOL = 'SEDOL' RIC = 'RIC' Ticker = 'Ticker' class UpDown(EnumBase, Enum): Up = 'Up' Down = 'Down' class ValuationTime(EnumBase, Enum): """The time of valuation, e.g. for an option""" MktClose = 'MktClose' MktOpen = 'MktOpen' SQ = 'SQ' MktPreOpen = 'MktPreOpen' MktPrevClose = 'MktPrevClose' Hedge_Unwind = 'Hedge Unwind' TWAP_PM = 'TWAP PM' class VarianceConvention(EnumBase, Enum): """Specifies whether the variance is Annualized or Total""" Annualized = 'Annualized' Total = 'Total' class WeightingType(EnumBase, Enum): """Weighting type that is used for the bond price""" Notional = 'Notional' Market_Value = 'Market Value' Dollar_Duration = 'Dollar Duration' class YesNo(EnumBase, Enum): Yes = 'Yes' No = 'No' @dataclass class SingleMarket(Base): pass @dataclass class BaseMarket(SingleMarket): pass @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AssetIdPriceable(Priceable): asset_id: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AssetScreenerCreditStandardAndPoorsRatingOptions(Base): min_: Optional[str] = field(default=None, metadata=config(field_name='min', exclude=exclude_none)) max_: Optional[str] = field(default=None, metadata=config(field_name='max', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AssetScreenerRequestFilterDateLimits(Base): min_: Optional[datetime.date] = field(default=None, metadata=config(field_name='min', exclude=exclude_none)) max_: Optional[datetime.date] = field(default=None, metadata=config(field_name='max', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AssetScreenerRequestFilterLimits(Base): min_: Optional[float] = field(default=None, metadata=config(field_name='min', exclude=exclude_none)) max_: Optional[float] = field(default=None, metadata=config(field_name='max', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AssetScreenerRequestStringOptions(Base): options: tuple = field(default=None, metadata=field_metadata) type_: str = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AssetValueParameters(Base): max_: Optional[float] = field(default=None, metadata=config(field_name='max', exclude=exclude_none)) min_: Optional[float] = field(default=None, metadata=config(field_name='min', exclude=exclude_none)) increment: Optional[float] = field(default=None, metadata=field_metadata) value: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BasketPositionTradeOnMetadata(Base): pair_calculation: Optional[str] = field(default=None, metadata=field_metadata) quantity: Optional[float] = field(default=None, metadata=field_metadata) direction: Optional[str] = field(default=None, metadata=field_metadata) underlier_close_price: Optional[float] = field(default=None, metadata=field_metadata) underlier_quantity: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BlendedBenchmarkAttribute(Base): benchmark_id: Optional[str] = field(default=None, metadata=field_metadata) weight: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BloombergPublishParameters(Base): description: Optional[str] = field(default=None, metadata=field_metadata) description_without_url: Optional[str] = field(default=None, metadata=field_metadata) long_name: Optional[str] = field(default=None, metadata=field_metadata) close_precision: Optional[float] = field(default=2, metadata=field_metadata) tick_precision: Optional[float] = field(default=4, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CSLDate(Base): date_value: Optional[datetime.date] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CSLDouble(Base): double_value: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CSLFXCross(Base): string_value: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CSLIndex(Base): string_value: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CSLSimpleSchedule(Base): fixing_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) settlement_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CSLStock(Base): string_value: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CSLString(Base): string_value: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CSLSymCaseNamedParam(Base): sym_case_value: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False, order=True) class CurrencyParameter(RiskMeasureParameter): value: Optional[str] = field(default=None, metadata=field_metadata) parameter_type: Optional[str] = field(init=False, default='Currency', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CurveOverlay(Scenario): dates: Optional[Tuple[datetime.date, ...]] = field(default=None, metadata=field_metadata) discount_factors: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) denominated: Optional[str] = field(default=None, metadata=field_metadata) csa_term: Optional[str] = field(default=None, metadata=field_metadata) tenor: Optional[str] = field(default=None, metadata=field_metadata) rate_option: Optional[str] = field(default=None, metadata=field_metadata) curve_type: Optional[str] = field(default=None, metadata=field_metadata) subtract_base: Optional[bool] = field(default=None, metadata=field_metadata) scenario_type: Optional[str] = field(init=False, default='CurveOverlay', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) class DataRow(DictBase): pass @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DateRange(Base): end_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) start_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False, order=True) class DoubleParameter(RiskMeasureParameter): value: Optional[float] = field(default=None, metadata=field_metadata) parameter_type: Optional[str] = field(init=False, default='Double', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqBasketRebalanceDateOverride(Base): new_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) existing_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ISelectNewUnit(Base): id_: str = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) new_units: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ISelectNewWeight(Base): id_: str = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) new_weight: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Identifier(Base): type_: Optional[str] = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) value: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class LiquidityReportParameters(Base): title: Optional[str] = field(default=None, metadata=field_metadata) email: Optional[str] = field(default=None, metadata=field_metadata) trading_desk: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False, order=True) class ListOfNumberParameter(RiskMeasureParameter): values: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) parameter_type: Optional[str] = field(init=False, default='ListOfNumber', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False, order=True) class ListOfStringParameter(RiskMeasureParameter): values: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) parameter_type: Optional[str] = field(init=False, default='ListOfString', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False, order=True) class MapParameter(RiskMeasureParameter): value: Optional[DictBase] = field(default=None, metadata=field_metadata) parameter_type: Optional[str] = field(init=False, default='Map', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MarketDataCoordinate(Base): mkt_type: Optional[str] = field(default=None, metadata=field_metadata) mkt_asset: Optional[str] = field(default=None, metadata=field_metadata) mkt_class: Optional[str] = field(default=None, metadata=field_metadata) mkt_point: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) mkt_quoting_style: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MarketDataVolSlice(Base): date: datetime.date = field(default=None, metadata=field_metadata) strikes: Tuple[float, ...] = field(default=None, metadata=field_metadata) levels: Tuple[float, ...] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MktMarkingOptions(Base): mode: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Op(Base): gte: Optional[Union[datetime.date, float]] = field(default=None, metadata=field_metadata) lte: Optional[Union[datetime.date, float]] = field(default=None, metadata=field_metadata) lt: Optional[Union[datetime.date, float]] = field(default=None, metadata=field_metadata) gt: Optional[Union[datetime.date, float]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class OrderByBody(Base): column_name: str = field(default=None, metadata=field_metadata) type_: str = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PCOBenchmarkOptions(Base): name: Optional[str] = field(default=None, metadata=field_metadata) target_ratio: Optional[str] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PCOExposureAdjustments(Base): nav_adjustment: Optional[str] = field(default=None, metadata=field_metadata) net_subscription_redemption: Optional[str] = field(default=None, metadata=field_metadata) net_subscription_redemption_limits: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) adjustment_vs_subscription_redemption: Optional[str] = field(default=None, metadata=field_metadata) adjustment_vs_subscription_redemption_limits: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PCOMtMHistoricalData(Base): value: Optional[str] = field(default=None, metadata=field_metadata) timestamp: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PCONetSubscription(Base): confirmed: Optional[str] = field(default=None, metadata=field_metadata) estimated: Optional[str] = field(default=None, metadata=field_metadata) id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PCOSettlementsData(Base): timestamp: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) settlement: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PCOTargetDeviationData(Base): value: Optional[str] = field(default=None, metadata=field_metadata) timestamp: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PatternPropertiesDateTime(Base): start: Optional[str] = field(default=None, metadata=field_metadata) end: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PerformanceStats(Base): alpha: Optional[float] = field(default=None, metadata=field_metadata) annualized_return: Optional[float] = field(default=None, metadata=field_metadata) annualized_volatility: Optional[float] = field(default=None, metadata=field_metadata) average_return: Optional[float] = field(default=None, metadata=field_metadata) average_value: Optional[float] = field(default=None, metadata=field_metadata) average_volume_last_month: Optional[float] = field(default=None, metadata=field_metadata) best_month: Optional[float] = field(default=None, metadata=field_metadata) best_month_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) beta: Optional[float] = field(default=None, metadata=field_metadata) close_price: Optional[float] = field(default=None, metadata=field_metadata) correlation: Optional[float] = field(default=None, metadata=field_metadata) cumulative_return: Optional[float] = field(default=None, metadata=field_metadata) current_value: Optional[float] = field(default=None, metadata=field_metadata) drawdown_over_return: Optional[float] = field(default=None, metadata=field_metadata) high: Optional[float] = field(default=None, metadata=field_metadata) high_eod: Optional[float] = field(default=None, metadata=field_metadata) last_change: Optional[float] = field(default=None, metadata=field_metadata) last_change_pct: Optional[float] = field(default=None, metadata=field_metadata) last_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) last_value: Optional[float] = field(default=None, metadata=field_metadata) low: Optional[float] = field(default=None, metadata=field_metadata) low_eod: Optional[float] = field(default=None, metadata=field_metadata) max_draw_down: Optional[float] = field(default=None, metadata=field_metadata) max_draw_down_duration: Optional[int] = field(default=None, metadata=field_metadata) open_price: Optional[float] = field(default=None, metadata=field_metadata) positive_months: Optional[float] = field(default=None, metadata=field_metadata) sharpe_ratio: Optional[float] = field(default=None, metadata=field_metadata) sortino_ratio: Optional[float] = field(default=None, metadata=field_metadata) worst_month: Optional[float] = field(default=None, metadata=field_metadata) worst_month_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) total_return: Optional[float] = field(default=None, metadata=field_metadata) volume: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PositionTag(Base): name: str = field(default=None, metadata=field_metadata) value: str = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class RefMarket(BaseMarket): market_ref: Optional[str] = field(default=None, metadata=field_metadata) market_type: Optional[str] = field(init=False, default='RefMarket', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ReportSubscriptionParameters(Base): frequency: object = field(default=None, metadata=field_metadata) recipients: Tuple[str, ...] = field(default=None, metadata=field_metadata) pfr_report_id: str = field(default=None, metadata=field_metadata) day_of_week: Optional[float] = field(default=None, metadata=field_metadata) day_of_month: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SimpleParty(Base): party_type: Optional[str] = field(default=None, metadata=field_metadata) party_name: Optional[str] = field(default=None, metadata=field_metadata) party_book: Optional[str] = field(default=None, metadata=field_metadata) party_oe_id: Optional[str] = field(default=None, metadata=config(field_name='partyOEId', exclude=exclude_none)) party_oe_name: Optional[str] = field(default=None, metadata=config(field_name='partyOEName', exclude=exclude_none)) party_root_oe_id: Optional[str] = field(default=None, metadata=config(field_name='partyRootOEId', exclude=exclude_none)) party_root_oe_name: Optional[str] = field(default=None, metadata=config(field_name='partyRootOEName', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False, order=True) class StringParameter(RiskMeasureParameter): value: Optional[str] = field(default=None, metadata=field_metadata) parameter_type: Optional[str] = field(init=False, default='String', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SystematicHedgeThreshold(Base): factor: str = field(default=None, metadata=field_metadata) condition: str = field(default=None, metadata=field_metadata) value: float = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class TimeFilter(Base): start_hours: str = field(default=None, metadata=field_metadata) end_hours: str = field(default=None, metadata=field_metadata) time_zone: str = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class UserTag(Base): name: str = field(default=None, metadata=field_metadata) added_on: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) added_by_id: Optional[str] = field(default=None, metadata=field_metadata) removed: Optional[bool] = field(default=None, metadata=field_metadata) removed_on: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) removed_by_id: Optional[str] = field(default=None, metadata=field_metadata) removal_reason: Optional[str] = field(default=None, metadata=field_metadata) category: Optional[str] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class WeightedPosition(Base): asset_id: str = field(default=None, metadata=field_metadata) weight: float = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class XRef(Base): ric: Optional[str] = field(default=None, metadata=field_metadata) rcic: Optional[str] = field(default=None, metadata=field_metadata) eid: Optional[str] = field(default=None, metadata=field_metadata) gsideid: Optional[str] = field(default=None, metadata=field_metadata) gsid: Optional[str] = field(default=None, metadata=field_metadata) gsid_equivalent: Optional[str] = field(default=None, metadata=field_metadata) cid: Optional[str] = field(default=None, metadata=field_metadata) bbid: Optional[str] = field(default=None, metadata=field_metadata) bcid: Optional[str] = field(default=None, metadata=field_metadata) delisted: Optional[str] = field(default=None, metadata=field_metadata) bbid_equivalent: Optional[str] = field(default=None, metadata=field_metadata) cusip: Optional[str] = field(default=None, metadata=field_metadata) gss: Optional[str] = field(default=None, metadata=field_metadata) isin: Optional[str] = field(default=None, metadata=field_metadata) jsn: Optional[str] = field(default=None, metadata=field_metadata) prime_id: Optional[str] = field(default=None, metadata=field_metadata) sedol: Optional[str] = field(default=None, metadata=field_metadata) ticker: Optional[str] = field(default=None, metadata=field_metadata) valoren: Optional[str] = field(default=None, metadata=field_metadata) wpk: Optional[str] = field(default=None, metadata=field_metadata) gsn: Optional[str] = field(default=None, metadata=field_metadata) sec_name: Optional[str] = field(default=None, metadata=field_metadata) cross: Optional[str] = field(default=None, metadata=field_metadata) simon_id: Optional[str] = field(default=None, metadata=field_metadata) em_id: Optional[str] = field(default=None, metadata=field_metadata) cm_id: Optional[str] = field(default=None, metadata=field_metadata) lms_id: Optional[str] = field(default=None, metadata=field_metadata) tdapi: Optional[str] = field(default=None, metadata=field_metadata) mdapi: Optional[str] = field(default=None, metadata=field_metadata) mdapi_class: Optional[str] = field(default=None, metadata=field_metadata) mic: Optional[str] = field(default=None, metadata=field_metadata) sf_id: Optional[str] = field(default=None, metadata=field_metadata) dollar_cross: Optional[str] = field(default=None, metadata=field_metadata) mq_symbol: Optional[str] = field(default=None, metadata=field_metadata) primary_country_ric: Optional[str] = field(default=None, metadata=field_metadata) pnode_id: Optional[str] = field(default=None, metadata=field_metadata) wi_id: Optional[str] = field(default=None, metadata=field_metadata) ps_id: Optional[str] = field(default=None, metadata=field_metadata) pl_id: Optional[str] = field(default=None, metadata=field_metadata) exchange_code: Optional[str] = field(default=None, metadata=field_metadata) plot_id: Optional[str] = field(default=None, metadata=field_metadata) cins: Optional[str] = field(default=None, metadata=field_metadata) bbgid: Optional[str] = field(default=None, metadata=field_metadata) display_id: Optional[str] = field(default=None, metadata=field_metadata) tsdb_shortname: Optional[str] = field(default=None, metadata=field_metadata) coin_metrics_id: Optional[str] = field(default=None, metadata=field_metadata) sec_master_id: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AssetClassifications(Base): risk_country_name: Optional[str] = field(default=None, metadata=field_metadata) risk_country_code: Optional[str] = field(default=None, metadata=field_metadata) country_name: Optional[str] = field(default=None, metadata=field_metadata) country_code: Optional[str] = field(default=None, metadata=field_metadata) listing_country_name: Optional[str] = field(default=None, metadata=field_metadata) is_primary: Optional[bool] = field(default=None, metadata=field_metadata) is_country_primary: Optional[bool] = field(default=None, metadata=field_metadata) gics_sector: Optional[str] = field(default=None, metadata=field_metadata) gics_industry_group: Optional[str] = field(default=None, metadata=field_metadata) gics_industry: Optional[str] = field(default=None, metadata=field_metadata) gics_sub_industry: Optional[str] = field(default=None, metadata=field_metadata) naics_classification_code: Optional[str] = field(default=None, metadata=field_metadata) naics_industry_description: Optional[str] = field(default=None, metadata=field_metadata) bbg_industry_sector: Optional[str] = field(default=None, metadata=field_metadata) bbg_industry_group: Optional[str] = field(default=None, metadata=field_metadata) bbg_industry_sub_group: Optional[str] = field(default=None, metadata=field_metadata) rating_moodys: Optional[str] = field(default=None, metadata=field_metadata) rating_fitch: Optional[str] = field(default=None, metadata=field_metadata) rating_standard_and_poors: Optional[str] = field(default=None, metadata=field_metadata) rating_second_highest: Optional[str] = field(default=None, metadata=field_metadata) rating_linear: Optional[float] = field(default=None, metadata=field_metadata) commod_template: Optional[str] = field(default=None, metadata=field_metadata) security_subtype: Optional[str] = field(default=None, metadata=field_metadata) region: Optional[Region] = field(default=None, metadata=field_metadata) vendor: Optional[str] = field(default=None, metadata=field_metadata) underliers_asset_class: Optional[AssetClass] = field(default=None, metadata=field_metadata) digital_asset_market: Optional[str] = field(default=None, metadata=field_metadata) digital_asset_sector: Optional[str] = field(default=None, metadata=field_metadata) digital_asset_industry: Optional[str] = field(default=None, metadata=field_metadata) digital_asset_class: Optional[str] = field(default=None, metadata=field_metadata) digital_asset_subsector: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AssetScreenerBbgRequestFilter(Base): country_of_risk: Optional[AssetScreenerRequestStringOptions] = field(default=None, metadata=field_metadata) payment_rank: Optional[AssetScreenerRequestStringOptions] = field(default=None, metadata=field_metadata) industry_sector: Optional[AssetScreenerRequestStringOptions] = field(default=None, metadata=field_metadata) industry_group: Optional[AssetScreenerRequestStringOptions] = field(default=None, metadata=field_metadata) industry_sub_group: Optional[AssetScreenerRequestStringOptions] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AssetScreenerCarbonRequestFilter(Base): science_based_target: Optional[AssetScreenerRequestStringOptions] = field(default=None, metadata=field_metadata) net_zero_emissions_target: Optional[AssetScreenerRequestStringOptions] = field(default=None, metadata=field_metadata) emissions_intensity_enterprise_value: Optional[AssetScreenerRequestFilterLimits] = field(default=None, metadata=field_metadata) emissions_intensity_revenue: Optional[AssetScreenerRequestFilterLimits] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AssetScreenerESGRequestFilter(Base): g_percentile: Optional[AssetScreenerRequestFilterLimits] = field(default=None, metadata=field_metadata) g_regional_percentile: Optional[AssetScreenerRequestFilterLimits] = field(default=None, metadata=field_metadata) es_percentile: Optional[AssetScreenerRequestFilterLimits] = field(default=None, metadata=field_metadata) es_disclosure_percentage: Optional[AssetScreenerRequestFilterLimits] = field(default=None, metadata=field_metadata) es_momentum_percentile: Optional[AssetScreenerRequestFilterLimits] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AssetScreenerPbRequestFilter(Base): indicative_short_financing_label: Optional[AssetScreenerRequestStringOptions] = field(default=None, metadata=field_metadata) indicative_long_financing_label: Optional[AssetScreenerRequestStringOptions] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BasketPositionTradeOn(Base): ticker: Optional[str] = field(default=None, metadata=field_metadata) marquee_id: Optional[str] = field(default=None, metadata=field_metadata) metadata: Optional[BasketPositionTradeOnMetadata] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CSLCurrency(Base): string_value: Optional[Currency] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CSLDateArray(Base): date_values: Optional[Tuple[CSLDate, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CSLDateArrayNamedParam(Base): date_values: Optional[Tuple[CSLDate, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CSLDoubleArray(Base): double_values: Optional[Tuple[CSLDouble, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CSLFXCrossArray(Base): fx_cross_values: Optional[Tuple[CSLFXCross, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CSLIndexArray(Base): index_values: Optional[Tuple[CSLIndex, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CSLSimpleScheduleArray(Base): simple_schedule_values: Optional[Tuple[CSLSimpleSchedule, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CSLStockArray(Base): stock_values: Optional[Tuple[CSLStock, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CSLStringArray(Base): string_values: Optional[Tuple[CSLString, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CarryScenario(Scenario): date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) time_shift: Optional[int] = field(default=None, metadata=field_metadata) roll_to_fwds: Optional[bool] = field(default=True, metadata=field_metadata) holiday_calendar: Optional[PricingLocation] = field(default=None, metadata=field_metadata) scenario_type: Optional[str] = field(init=False, default='CarryScenario', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CashReinvestmentTreatment(Base): regular_dividend_treatment: Optional[CashReinvestmentTreatmentType] = field(default=None, metadata=field_metadata) special_dividend_treatment: Optional[CashReinvestmentTreatmentType] = field(default=None, metadata=field_metadata) cash_acquisition_treatment: Optional[CashReinvestmentTreatmentType] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CloseMarket(BaseMarket): date: datetime.date = field(default=None, metadata=field_metadata) location: PricingLocation = field(default=None, metadata=field_metadata) market_type: Optional[str] = field(init=False, default='CloseMarket', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CommodPrice(Base): unit: Optional[CommodUnit] = field(default=None, metadata=field_metadata) price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) currency: Optional[CurrencyName] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EntitlementExclusions(Base): view: Optional[Tuple[Tuple[str, ...], ...]] = field(default=None, metadata=field_metadata) edit: Optional[Tuple[Tuple[str, ...], ...]] = field(default=None, metadata=field_metadata) admin: Optional[Tuple[Tuple[str, ...], ...]] = field(default=None, metadata=field_metadata) rebalance: Optional[Tuple[Tuple[str, ...], ...]] = field(default=None, metadata=field_metadata) execute: Optional[Tuple[Tuple[str, ...], ...]] = field(default=None, metadata=field_metadata) trade: Optional[Tuple[Tuple[str, ...], ...]] = field(default=None, metadata=field_metadata) upload: Optional[Tuple[Tuple[str, ...], ...]] = field(default=None, metadata=field_metadata) query: Optional[Tuple[Tuple[str, ...], ...]] = field(default=None, metadata=field_metadata) performance_details: Optional[Tuple[Tuple[str, ...], ...]] = field(default=None, metadata=field_metadata) plot: Optional[Tuple[Tuple[str, ...], ...]] = field(default=None, metadata=field_metadata) delete: Optional[Tuple[Tuple[str, ...], ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Entitlements(Base): view: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) edit: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) admin: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) rebalance: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) execute: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) trade: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) upload: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) query: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) performance_details: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) plot: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) delete: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) display: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqBasketBacktestParameters(Base): weighting_mechanism: str = field(default=None, metadata=field_metadata) time_horizon: str = field(default=None, metadata=field_metadata) frequency_interval: FrequencyInterval = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqBasketRebalanceCustomFrequencyRule(Base): frequency_interval: FrequencyInterval = field(default=None, metadata=field_metadata) every: float = field(default=1, metadata=field_metadata) on: Union[float, str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) class FieldValueMap(DictBase): pass @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FilterRequest(Base): scroll: Optional[str] = field(default=None, metadata=field_metadata) scroll_id: Optional[str] = field(default=None, metadata=field_metadata) include_columns: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) filters: Optional[Tuple[DictBase, ...]] = field(default=None, metadata=field_metadata) order_by: Optional[OrderByBody] = field(default=None, metadata=field_metadata) limit: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FilteredData(Base): total_results: Optional[float] = field(default=None, metadata=field_metadata) results: Optional[Tuple[DataRow, ...]] = field(default=None, metadata=field_metadata) scroll: Optional[str] = field(default=None, metadata=field_metadata) scroll_id: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False, order=True) class FiniteDifferenceParameter(RiskMeasureParameter): aggregation_level: Optional[AggregationLevel] = field(default=None, metadata=field_metadata) currency: Optional[str] = field(default=None, metadata=field_metadata) local_curve: Optional[bool] = field(default=None, metadata=field_metadata) bump_size: Optional[float] = field(default=None, metadata=field_metadata) finite_difference_method: Optional[FiniteDifferenceMethod] = field(default=None, metadata=field_metadata) scale_factor: Optional[float] = field(default=None, metadata=field_metadata) mkt_marking_options: Optional[MktMarkingOptions] = field(default=None, metadata=field_metadata) parameter_type: Optional[str] = field(init=False, default='FiniteDifference', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ISelectNewParameter(Base): early_unwind_after: Optional[float] = field(default=None, metadata=field_metadata) early_unwind_applicable: Optional[str] = field(default=None, metadata=field_metadata) expiry_date_rule: Optional[str] = field(default=None, metadata=field_metadata) option_target_expiry_parameter: Optional[Union[AssetValueParameters, float]] = field(default=None, metadata=field_metadata) option_early_unwind_days: Optional[float] = field(default=None, metadata=field_metadata) in_alpha: Optional[Union[bool, int]] = field(default=None, metadata=field_metadata) is_fsr_target_factor: Optional[Union[bool, int]] = field(default=None, metadata=config(field_name='isFSRTargetFactor', exclude=exclude_none)) fsr_max_ratio: Optional[float] = field(default=None, metadata=field_metadata) fsr_min_ratio: Optional[float] = field(default=None, metadata=field_metadata) module_enabled: Optional[Union[bool, int]] = field(default=None, metadata=field_metadata) trend_signal_0: Optional[Union[bool, int]] = field(default=None, metadata=config(field_name='trendSignal_0', exclude=exclude_none)) trend_signal_1: Optional[Union[bool, int]] = field(default=None, metadata=config(field_name='trendSignal_1', exclude=exclude_none)) trend_signal_2: Optional[Union[bool, int]] = field(default=None, metadata=config(field_name='trendSignal_2', exclude=exclude_none)) trend_signal_3: Optional[Union[bool, int]] = field(default=None, metadata=config(field_name='trendSignal_3', exclude=exclude_none)) module_name: Optional[str] = field(default=None, metadata=field_metadata) target_strike: Optional[float] = field(default=None, metadata=field_metadata) strike_method: Optional[StrikeMethodType] = field(default=None, metadata=field_metadata) option_expiry: Optional[OptionExpiryType] = field(default=None, metadata=field_metadata) bloomberg_id: Optional[str] = field(default=None, metadata=field_metadata) id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) stock_id: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[str] = field(default=None, metadata=field_metadata) future_id: Optional[str] = field(default=None, metadata=field_metadata) ric: Optional[str] = field(default=None, metadata=field_metadata) new_weight: Optional[float] = field(default=None, metadata=field_metadata) execution_participation_rate: Optional[float] = field(default=None, metadata=field_metadata) new_shares: Optional[float] = field(default=None, metadata=field_metadata) new_lots: Optional[float] = field(default=None, metadata=field_metadata) execution_start_time: Optional[DictBase] = field(default=None, metadata=field_metadata) execution_end_time: Optional[DictBase] = field(default=None, metadata=field_metadata) execution_style: Optional[str] = field(default=None, metadata=field_metadata) execution_timezone: Optional[str] = field(default=None, metadata=field_metadata) notional: Optional[Union[AssetValueParameters, float]] = field(default=None, metadata=field_metadata) unwind_pct: Optional[Union[AssetValueParameters, float]] = field(default=None, metadata=field_metadata) leverage: Optional[float] = field(default=None, metadata=field_metadata) quantity: Optional[float] = field(default=None, metadata=field_metadata) hedge_ratio: Optional[float] = field(default=None, metadata=field_metadata) option_type: Optional[OptionType] = field(default=None, metadata=field_metadata) option_strike_type: Optional[OptionStrikeType] = field(default=None, metadata=field_metadata) credit_option_type: Optional[CreditOptionType] = field(default=None, metadata=field_metadata) credit_option_strike_type: Optional[CreditOptionStrikeType] = field(default=None, metadata=field_metadata) strike_relative: Optional[Union[AssetValueParameters, float]] = field(default=None, metadata=field_metadata) trade_type: Optional[TradeType] = field(default=None, metadata=field_metadata) signal: Optional[float] = field(default=None, metadata=field_metadata) new_signal: Optional[float] = field(default=None, metadata=field_metadata) new_min_weight: Optional[float] = field(default=None, metadata=field_metadata) new_max_weight: Optional[float] = field(default=None, metadata=field_metadata) min_weight: Optional[float] = field(default=None, metadata=field_metadata) max_weight: Optional[float] = field(default=None, metadata=field_metadata) weight_smoothing_window: Optional[float] = field(default=None, metadata=field_metadata) election: Optional[str] = field(default=None, metadata=field_metadata) base_date: Optional[str] = field(default=None, metadata=field_metadata) commodity: Optional[str] = field(default=None, metadata=field_metadata) component_weight: Optional[float] = field(default=None, metadata=field_metadata) contract_nearby_number: Optional[float] = field(default=None, metadata=field_metadata) expiration_schedule: Optional[str] = field(default=None, metadata=field_metadata) fixing_type: Optional[str] = field(default=None, metadata=field_metadata) last_eligible_date: Optional[float] = field(default=None, metadata=field_metadata) num_roll_days: Optional[float] = field(default=None, metadata=field_metadata) roll_end: Optional[float] = field(default=None, metadata=field_metadata) roll_start: Optional[float] = field(default=None, metadata=field_metadata) roll_type: Optional[str] = field(default=None, metadata=field_metadata) valid_contract_expiry: Optional[str] = field(default=None, metadata=field_metadata) rtl: Optional[float] = field(default=None, metadata=field_metadata) current_weight: Optional[float] = field(default=None, metadata=field_metadata) chg_weight: Optional[float] = field(default=None, metadata=field_metadata) new_units: Optional[float] = field(default=None, metadata=field_metadata) country: Optional[str] = field(default=None, metadata=field_metadata) region: Optional[str] = field(default=None, metadata=field_metadata) adv: Optional[float] = field(default=None, metadata=field_metadata) tadv: Optional[float] = field(default=None, metadata=field_metadata) hadv: Optional[float] = field(default=None, metadata=field_metadata) market_cap: Optional[float] = field(default=None, metadata=field_metadata) market_classification: Optional[str] = field(default=None, metadata=field_metadata) new_signal_param_a: Optional[float] = field(default=None, metadata=config(field_name='NewSignalParamA', exclude=exclude_none)) new_signal_param_b: Optional[float] = field(default=None, metadata=config(field_name='NewSignalParamB', exclude=exclude_none)) signal_param_a: Optional[float] = field(default=None, metadata=config(field_name='SignalParamA', exclude=exclude_none)) signal_param_b: Optional[float] = field(default=None, metadata=config(field_name='SignalParamB', exclude=exclude_none)) inv_vol_cap: Optional[float] = field(default=None, metadata=field_metadata) newinv_vol_cap: Optional[float] = field(default=None, metadata=field_metadata) units_scaling_factor: Optional[float] = field(default=None, metadata=field_metadata) new_units_scaling_factor: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class LiborFallbackScenario(Scenario): date: Optional[datetime.date] = field(default=None, metadata=field_metadata) fallback_type: Optional[FallbackType] = field(default=FallbackType.RFR, metadata=field_metadata) discounting: Optional[bool] = field(default=False, metadata=field_metadata) cash_flows: Optional[bool] = field(default=True, metadata=field_metadata) scenario_type: Optional[str] = field(init=False, default='LiborFallbackScenario', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class LiveMarket(BaseMarket): location: PricingLocation = field(default=None, metadata=field_metadata) market_type: Optional[str] = field(init=False, default='LiveMarket', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MarketDataCoordinateValue(Base): coordinate: MarketDataCoordinate = field(default=None, metadata=field_metadata) value: float = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MarketDataPattern(Base): mkt_type: Optional[str] = field(default=None, metadata=field_metadata) mkt_asset: Optional[str] = field(default=None, metadata=field_metadata) mkt_class: Optional[str] = field(default=None, metadata=field_metadata) mkt_point: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) mkt_quoting_style: Optional[str] = field(default=None, metadata=field_metadata) is_active: Optional[bool] = field(default=None, metadata=field_metadata) is_investment_grade: Optional[bool] = field(default=None, metadata=field_metadata) currency: Optional[Currency] = field(default=None, metadata=field_metadata) country_code: Optional[CountryCode] = field(default=None, metadata=field_metadata) gics_sector: Optional[str] = field(default=None, metadata=field_metadata) gics_industry_group: Optional[str] = field(default=None, metadata=field_metadata) gics_industry: Optional[str] = field(default=None, metadata=field_metadata) gics_sub_industry: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MarketDataShock(Base): shock_type: MarketDataShockType = field(default=None, metadata=field_metadata) value: float = field(default=None, metadata=field_metadata) precision: Optional[float] = field(default=None, metadata=field_metadata) cap: Optional[float] = field(default=None, metadata=field_metadata) floor: Optional[float] = field(default=None, metadata=field_metadata) coordinate_cap: Optional[float] = field(default=None, metadata=field_metadata) coordinate_floor: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PCOBenchmark(Base): selected: Optional[str] = field(default=None, metadata=field_metadata) options: Optional[Tuple[PCOBenchmarkOptions, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PCOCashBalance(Base): local_currency: Optional[Currency] = field(default=None, metadata=field_metadata) cash_balance_limits: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) cash_reserve: Optional[str] = field(default=None, metadata=field_metadata) long_threshold: Optional[str] = field(default=None, metadata=field_metadata) short_threshold: Optional[str] = field(default=None, metadata=field_metadata) holding: Optional[str] = field(default=None, metadata=field_metadata) t0: Optional[str] = field(default=None, metadata=field_metadata) t1: Optional[str] = field(default=None, metadata=field_metadata) t2: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PCOCurrencyWeight(Base): currency: Optional[Currency] = field(default=None, metadata=field_metadata) weight: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PCOParameterValues(Base): currency: Optional[Currency] = field(default=None, metadata=field_metadata) value: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PCOSettlements(Base): currency: Optional[Currency] = field(default=None, metadata=field_metadata) data: Optional[Tuple[PCOSettlementsData, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PCOShareClass(Base): total_net_assets: Optional[str] = field(default=None, metadata=field_metadata) estimated_switch: Optional[str] = field(default=None, metadata=field_metadata) estimated_net_subscription_effective_date: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) confirmed_net_subscription_effective_date: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) estimated_net_dividend: Optional[str] = field(default=None, metadata=field_metadata) confirmed_net_dividend: Optional[str] = field(default=None, metadata=field_metadata) net_subscriptions: Optional[Tuple[PCONetSubscription, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PCOTargetDeviation(Base): currency: Optional[Currency] = field(default=None, metadata=field_metadata) data: Optional[Tuple[PCOTargetDeviationData, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PCOUnrealisedMarkToMarket(Base): total: Optional[str] = field(default=None, metadata=field_metadata) next_settlement_date: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) next_settlement: Optional[str] = field(default=None, metadata=field_metadata) next_roll_date: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) historical_data: Optional[Tuple[PCOMtMHistoricalData, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PerformanceStatsRequest(Base): annualized_return: Optional[Op] = field(default=None, metadata=field_metadata) annualized_volatility: Optional[Op] = field(default=None, metadata=field_metadata) best_month: Optional[Op] = field(default=None, metadata=field_metadata) max_draw_down: Optional[Op] = field(default=None, metadata=field_metadata) max_draw_down_duration: Optional[Op] = field(default=None, metadata=field_metadata) positive_months: Optional[Op] = field(default=None, metadata=field_metadata) sharpe_ratio: Optional[Op] = field(default=None, metadata=field_metadata) sortino_ratio: Optional[Op] = field(default=None, metadata=field_metadata) worst_month: Optional[Op] = field(default=None, metadata=field_metadata) average_return: Optional[Op] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PositionPriceInput(Base): asset_id: str = field(default=None, metadata=field_metadata) quantity: Optional[float] = field(default=None, metadata=field_metadata) weight: Optional[float] = field(default=None, metadata=field_metadata) notional: Optional[float] = field(default=None, metadata=field_metadata) tags: Optional[Tuple[PositionTag, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PositionsRequest(Base): asset_id: str = field(default=None, metadata=field_metadata) weight: Optional[float] = field(default=None, metadata=field_metadata) quantity: Optional[float] = field(default=None, metadata=field_metadata) notional: Optional[float] = field(default=None, metadata=field_metadata) tags: Optional[Tuple[PositionTag, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class RiskRequestParameters(Base): csa_term: Optional[str] = field(default=None, metadata=field_metadata) raw_results: Optional[bool] = field(default=False, metadata=field_metadata) use_historical_diddles_only: Optional[bool] = field(default=False, metadata=field_metadata) market_behaviour: Optional[MarketBehaviour] = field(default=MarketBehaviour.ContraintsBased, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class RollFwd(Scenario): date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) realise_fwd: Optional[bool] = field(default=True, metadata=field_metadata) holiday_calendar: Optional[PricingLocation] = field(default=None, metadata=field_metadata) scenario_type: Optional[str] = field(init=False, default='RollFwd', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class TimestampedMarket(BaseMarket): timestamp: datetime.datetime = field(default=None, metadata=field_metadata) location: PricingLocation = field(default=None, metadata=field_metadata) base_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) market_type: Optional[str] = field(init=False, default='TimestampedMarket', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AssetScreenerCreditRequestFilters(Base): face_value: Optional[float] = field(default=None, metadata=field_metadata) direction: Optional[str] = field(default=None, metadata=field_metadata) liquidity_score: Optional[AssetScreenerRequestFilterLimits] = field(default=None, metadata=field_metadata) gs_charge_bps: Optional[AssetScreenerRequestFilterLimits] = field(default=None, metadata=field_metadata) gs_charge_dollars: Optional[AssetScreenerRequestFilterLimits] = field(default=None, metadata=field_metadata) duration: Optional[AssetScreenerRequestFilterLimits] = field(default=None, metadata=field_metadata) bbg_data: Optional[AssetScreenerBbgRequestFilter] = field(default=None, metadata=field_metadata) carbon_data: Optional[AssetScreenerCarbonRequestFilter] = field(default=None, metadata=field_metadata) esg_data: Optional[AssetScreenerESGRequestFilter] = field(default=None, metadata=field_metadata) issue_date: Optional[AssetScreenerRequestFilterDateLimits] = field(default=None, metadata=field_metadata) yield_: Optional[AssetScreenerRequestFilterLimits] = field(default=None, metadata=config(field_name='yield', exclude=exclude_none)) spread: Optional[AssetScreenerRequestFilterLimits] = field(default=None, metadata=field_metadata) z_spread: Optional[AssetScreenerRequestFilterLimits] = field(default=None, metadata=field_metadata) g_spread: Optional[AssetScreenerRequestFilterLimits] = field(default=None, metadata=field_metadata) mid_price: Optional[AssetScreenerRequestFilterLimits] = field(default=None, metadata=field_metadata) maturity: Optional[AssetScreenerRequestFilterLimits] = field(default=None, metadata=field_metadata) pb_data: Optional[AssetScreenerPbRequestFilter] = field(default=None, metadata=field_metadata) amount_outstanding: Optional[AssetScreenerRequestFilterLimits] = field(default=None, metadata=field_metadata) rating_standard_and_poors: Optional[AssetScreenerRequestStringOptions] = field(default=None, metadata=field_metadata) seniority: Optional[AssetScreenerRequestStringOptions] = field(default=None, metadata=field_metadata) ticker: Optional[AssetScreenerRequestStringOptions] = field(default=None, metadata=field_metadata) cusip: Optional[AssetScreenerRequestStringOptions] = field(default=None, metadata=field_metadata) isin: Optional[AssetScreenerRequestStringOptions] = field(default=None, metadata=field_metadata) currency: Optional[AssetScreenerRequestStringOptions] = field(default=None, metadata=field_metadata) gs_indicative_buy_price: Optional[AssetScreenerRequestFilterLimits] = field(default=None, metadata=field_metadata) gs_indicative_buy_quantity: Optional[AssetScreenerRequestFilterLimits] = field(default=None, metadata=field_metadata) gs_indicative_buy_spread: Optional[AssetScreenerRequestFilterLimits] = field(default=None, metadata=field_metadata) gs_indicative_buy_yield: Optional[AssetScreenerRequestFilterLimits] = field(default=None, metadata=field_metadata) gs_indicative_sell_price: Optional[AssetScreenerRequestFilterLimits] = field(default=None, metadata=field_metadata) gs_indicative_sell_quantity: Optional[AssetScreenerRequestFilterLimits] = field(default=None, metadata=field_metadata) gs_indicative_sell_spread: Optional[AssetScreenerRequestFilterLimits] = field(default=None, metadata=field_metadata) gs_indicative_sell_yield: Optional[AssetScreenerRequestFilterLimits] = field(default=None, metadata=field_metadata) region: Optional[AssetScreenerRequestStringOptions] = field(default=None, metadata=field_metadata) sector: Optional[AssetScreenerRequestStringOptions] = field(default=None, metadata=field_metadata) industry: Optional[AssetScreenerRequestStringOptions] = field(default=None, metadata=field_metadata) coupon_type: Optional[AssetScreenerRequestStringOptions] = field(default=None, metadata=field_metadata) private_placement_type: Optional[AssetScreenerRequestStringOptions] = field(default=None, metadata=field_metadata) universe: Optional[tuple] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AssetStatsRequest(Base): last_updated_time: Optional[DateRange] = field(default=None, metadata=field_metadata) period: Optional[AssetStatsPeriod] = field(default=None, metadata=field_metadata) type_: Optional[AssetStatsType] = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) stats: Optional[PerformanceStatsRequest] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CSLCurrencyArray(Base): currency_values: Optional[Tuple[CSLCurrency, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CSLSchedule(Base): first_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) last_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) calendar_name: Optional[str] = field(default=None, metadata=field_metadata) period: Optional[str] = field(default=None, metadata=field_metadata) delay: Optional[str] = field(default=None, metadata=field_metadata) business_day_convention: Optional[str] = field(default=None, metadata=field_metadata) day_count_convention: Optional[str] = field(default=None, metadata=field_metadata) days_per_term: Optional[str] = field(default=None, metadata=field_metadata) delay_business_day_convention: Optional[str] = field(default=None, metadata=field_metadata) delay_calendar_name: Optional[str] = field(default=None, metadata=field_metadata) has_reset_date: Optional[bool] = field(default=None, metadata=field_metadata) term_formula: Optional[str] = field(default=None, metadata=field_metadata) extra_dates: Optional[Tuple[CSLDateArrayNamedParam, ...]] = field(default=None, metadata=field_metadata) extra_dates_by_offset: Optional[Tuple[CSLSymCaseNamedParam, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CurveScenario(Scenario): market_data_pattern: Optional[MarketDataPattern] = field(default=None, metadata=field_metadata) parallel_shift: Optional[float] = field(default=None, metadata=field_metadata) curve_shift: Optional[float] = field(default=None, metadata=field_metadata) pivot_point: Optional[float] = field(default=None, metadata=field_metadata) tenor_start: Optional[float] = field(default=None, metadata=field_metadata) tenor_end: Optional[float] = field(default=None, metadata=field_metadata) shock_type: Optional[MarketDataShockType] = field(default=None, metadata=field_metadata) scenario_type: Optional[str] = field(init=False, default='CurveScenario', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqBasketRebalanceFrequency(Base): frequency_rule: Union[EqBasketRebalanceCustomFrequencyRule, EqBasketRebalanceFrequencyRule, str] = field(default=None, metadata=field_metadata) holiday_calendar: Optional[EqBasketHolidayCalendar] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FieldFilterMap(Base): issue_status_date: Optional[Union[Tuple[datetime.date, ...], datetime.date]] = field(default=None, metadata=field_metadata) internal_index_calc_region: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) last_returns_start_date: Optional[Union[Tuple[datetime.date, ...], datetime.date]] = field(default=None, metadata=field_metadata) pl_id: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) amount_outstanding: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) asset_classifications_gics_sub_industry: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) mdapi_class: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) data_set_ids: Optional[Union[Tuple[Tuple[str, ...], ...], Tuple[str, ...]]] = field(default=None, metadata=field_metadata) call_first_date: Optional[Union[Tuple[datetime.date, ...], datetime.date]] = field(default=None, metadata=field_metadata) pb_client_id: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_start: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) owner_id: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) sec_db: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=config(field_name='secDB', exclude=exclude_none)) economic_terms_hash: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) objective: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) simon_intl_asset_tags: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) private_placement_type: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) hedge_notional: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) rank: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) data_set_category: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) pair_calculation: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_index_family: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) created_by_id: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) vehicle_type: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) market_data_type: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_payer_day_count_fraction: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) point_class: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_cap_floor: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_underlier_type: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_payer_currency: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) minimum_increment: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[Tuple[datetime.date, ...], datetime.date]] = field(default=None, metadata=field_metadata) hedge_volatility: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) version: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) tags: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_classifications_gics_industry_group: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) market_data_asset: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_classifications_is_primary: Optional[Union[Tuple[bool, ...], bool]] = field(default=None, metadata=field_metadata) styles: Optional[Union[Tuple[Tuple[str, ...], ...], Tuple[str, ...]]] = field(default=None, metadata=field_metadata) asset_parameters_total_quantity: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) short_name: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_classifications_underliers_asset_class: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) eid: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) calculation_region: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) jsn: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) mkt_quoting_style: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) mic: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) hurdle_type: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) ps_id: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) final_close_date: Optional[Union[Tuple[Union[Op, datetime.date], ...], Union[Op, datetime.date]]] = field(default=None, metadata=field_metadata) issue_status: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) region_code: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) dollar_cross: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) portfolio_type: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) vendor: Optional[Union[Tuple[Union[MarketDataVendor, str], ...], Union[MarketDataVendor, str]]] = field(default=None, metadata=field_metadata) popularity: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) term: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) currency: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) real_time_restriction_status: Optional[Union[Tuple[Tuple[str, ...], ...], Tuple[str, ...]]] = field(default=None, metadata=field_metadata) rating_fitch: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_clearing_house: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) non_symbol_dimensions: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_option_style: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) share_class_type: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_put_amount: Optional[Union[Tuple[Union[float, str], ...], Union[float, str]]] = field(default=None, metadata=field_metadata) asset_parameters_underlier: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_floating_rate_designated_maturity: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) next_rebalance_date: Optional[Union[Op, Tuple[datetime.date, ...], datetime.date]] = field(default=None, metadata=field_metadata) target_notional: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) asset_parameters_tenor: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) mkt_class: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_classifications_digital_asset_class: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) delisted: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) last_updated_since: Optional[Union[Tuple[datetime.datetime, ...], datetime.datetime]] = field(default=None, metadata=field_metadata) regional_focus: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_payer_designated_maturity: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) tsdb_shortname: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) seasonal_adjustment_short: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_exchange_currency: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_classifications_country_name: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) management_fee: Optional[Union[Tuple[Union[Op, float], ...], Union[Op, float]]] = field(default=None, metadata=field_metadata) asset_parameters_settlement_date: Optional[Union[Op, Tuple[datetime.date, ...], datetime.date]] = field(default=None, metadata=field_metadata) rating_moodys: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) simon_id: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) development_status: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) cusip: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) notes: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) tags_to_exclude: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_floating_rate_option: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) internal_index_calc_agent: Optional[Union[Tuple[bool, ...], bool]] = field(default=None, metadata=field_metadata) last_rebalance_date: Optional[Union[Op, Tuple[datetime.date, ...], datetime.date]] = field(default=None, metadata=field_metadata) rating_second_highest: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_classifications_country_code: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) frequency: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) option_type: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) data_set_sub_category: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) is_live: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) is_legacy_pair_basket: Optional[Union[Tuple[bool, ...], bool]] = field(default=None, metadata=field_metadata) issuer_type: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) plot_id: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_pricing_location: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_coupon: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) asset_parameters_identifier: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_last_fixing_date: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) data_product: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) mq_symbol: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) sectors: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_method_of_settlement: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) redemption_notice_period: Optional[Union[Tuple[Union[Op, float], ...], Union[Op, float]]] = field(default=None, metadata=field_metadata) multiplier: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) asset_parameters_payer_rate_option: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) market_data_point: Optional[Union[Tuple[Tuple[str, ...], ...], Tuple[str, ...]]] = field(default=None, metadata=field_metadata) external: Optional[Union[Tuple[bool, ...], bool]] = field(default=None, metadata=field_metadata) sts_fx_currency: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) wpk: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) hedge_annualized_volatility: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) fix_order_routing_region: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) name: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) aum: Optional[Union[Tuple[Union[Op, float], ...], Union[Op, float]]] = field(default=None, metadata=field_metadata) asset_parameters_expiration_date: Optional[Union[Op, Tuple[Union[datetime.date, str], ...], Union[datetime.date, str]]] = field(default=None, metadata=field_metadata) exchange: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) folder_name: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) region: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) cid: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) onboarded: Optional[Union[Tuple[bool, ...], bool]] = field(default=None, metadata=field_metadata) live_date: Optional[Union[Tuple[Union[Op, datetime.date], ...], Union[Op, datetime.date]]] = field(default=None, metadata=field_metadata) issue_price: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) sink_factor: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) underlying_data_set_id: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_classifications_gics_sector: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) prime_id: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_payer_frequency: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_notional_amount_in_other_currency: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) sts_asset_name: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_pair: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) description: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_classifications_is_country_primary: Optional[Union[Tuple[bool, ...], bool]] = field(default=None, metadata=field_metadata) title: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) net_exposure_classification: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_strike_price: Optional[Union[Tuple[Union[float, str], ...], Union[float, str]]] = field(default=None, metadata=field_metadata) coupon_type: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) last_updated_by_id: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_forward_price: Optional[Union[Tuple[Union[float, str], ...], Union[float, str]]] = field(default=None, metadata=field_metadata) clone_parent_id: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) company: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) gate_type: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) issue_date: Optional[Union[Tuple[datetime.date, ...], datetime.date]] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[Tuple[datetime.date, ...], datetime.date]] = field(default=None, metadata=field_metadata) coverage: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) ticker: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_receiver_rate_option: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) coin_metrics_id: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) call_last_date: Optional[Union[Tuple[datetime.date, ...], datetime.date]] = field(default=None, metadata=field_metadata) sts_rates_country: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_payer_spread: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_premium_payment_date: Optional[Union[Op, Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) latest_execution_time: Optional[Union[Tuple[datetime.datetime, ...], datetime.datetime]] = field(default=None, metadata=field_metadata) asset_parameters_forward_rate: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) asset_classifications_digital_asset_industry: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_receiver_designated_maturity: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) gate: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) multi_tags: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) gsn: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) gss: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) rating_linear: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) asset_class: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_classifications_vendor: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) cm_id: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_index: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) type_: Optional[Union[Tuple[Union[AssetType, RiskModelType], ...], Union[AssetType, RiskModelType]]] = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) gsideid: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) ric: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) mdapi: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) issuer: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) position_source_id: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) measures: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_floating_rate_day_count_fraction: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) first_close_date: Optional[Union[Tuple[Union[Op, datetime.date], ...], Union[Op, datetime.date]]] = field(default=None, metadata=field_metadata) asset_parameters_notional_amount: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) strategy_aum: Optional[Union[Tuple[Union[Op, float], ...], Union[Op, float]]] = field(default=None, metadata=field_metadata) action: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) id_: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) asset_parameters_call_amount: Optional[Union[Tuple[Union[float, str], ...], Union[float, str]]] = field(default=None, metadata=field_metadata) asset_parameters_seniority: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) redemption_date: Optional[Union[Tuple[datetime.date, ...], datetime.date]] = field(default=None, metadata=field_metadata) identifier: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) sec_name: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) index_create_source: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) sub_region: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_index2_tenor: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_receiver_day_count_fraction: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_notional_currency: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) sedol: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) mkt_asset: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) open_to_sma: Optional[Union[Tuple[bool, ...], bool]] = field(default=None, metadata=config(field_name='openToSMA', exclude=exclude_none)) rating_standard_and_poors: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_types: Optional[Union[Tuple[AssetType, ...], Tuple[Tuple[AssetType, ...], ...]]] = field(default=None, metadata=field_metadata) bcid: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_credit_index_series: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) vintage: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) gsid: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_classifications_digital_asset_subsector: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) tdapi: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) last_updated_message: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) trading_restriction: Optional[Union[Tuple[bool, ...], bool]] = field(default=None, metadata=field_metadata) rcic: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) status: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) name_raw: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_pay_or_receive: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) domains_data: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) client_name: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_index_series: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) asset_classifications_gics_industry: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) on_behalf_of: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) increment: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) accrued_interest_standard: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) enabled: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) sectors_raw: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) sts_commodity: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) sts_commodity_sector: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) open_to_portable_alpha: Optional[Union[Tuple[bool, ...], bool]] = field(default=None, metadata=field_metadata) asset_parameters_receiver_frequency: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) position_source_name: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) gsid_equivalent: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) categories: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) symbol_dimensions: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) ext_mkt_asset: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_fixed_rate_frequency: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) coupon: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) compliance_restricted_status: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) side_pocket: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) quoting_style: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) is_entity: Optional[Union[Tuple[bool, ...], bool]] = field(default=None, metadata=field_metadata) scenario_group_id: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_trade_as: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) redemption_period: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) sts_credit_market: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_issuer_type: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) bbid: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_classifications_risk_country_code: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_classifications_digital_asset_sector: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) sts_em_dm: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_receiver_currency: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) issue_size: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) returns_enabled: Optional[Union[Tuple[bool, ...], bool]] = field(default=None, metadata=field_metadata) asset_parameters_settlement: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) seniority: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) primary_country_ric: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_expiration_time: Optional[Union[Op, Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) is_pair_basket: Optional[Union[Tuple[bool, ...], bool]] = field(default=None, metadata=field_metadata) asset_parameters_index_version: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) asset_classifications_digital_asset_market: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_commodity_reference_price: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) default_backcast: Optional[Union[Tuple[bool, ...], bool]] = field(default=None, metadata=field_metadata) asset_parameters_number_of_shares: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) use_machine_learning: Optional[Union[Tuple[bool, ...], bool]] = field(default=None, metadata=field_metadata) performance_fee: Optional[Union[Tuple[Union[Op, float], ...], Union[Op, float]]] = field(default=None, metadata=field_metadata) report_type: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) lockup: Optional[Union[Tuple[Union[Op, float], ...], Union[Op, float]]] = field(default=None, metadata=field_metadata) lockup_type: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) underlying_asset_ids: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) encoded_stats: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_fee_currency: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) pnode_id: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) backtest_type: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_issuer: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) exchange_code: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_strike: Optional[Union[Tuple[Union[float, str], ...], Union[float, str]]] = field(default=None, metadata=field_metadata) oe_id: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_termination_date: Optional[Union[Tuple[Union[datetime.date, str], ...], Union[datetime.date, str]]] = field(default=None, metadata=field_metadata) resource: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) bbid_equivalent: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_receiver_spread: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) valoren: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_effective_date: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) hurdle: Optional[Union[Tuple[Union[Op, float], ...], Union[Op, float]]] = field(default=None, metadata=field_metadata) asset_parameters_fixed_rate_day_count_fraction: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_number_of_options: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) auto_tags: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) short_description: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) ext_mkt_class: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) mkt_point1: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) portfolio_managers: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_commodity_sector: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) hedge_tracking_error: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) supra_strategy: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_coupon_type: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_put_currency: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) term_status: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) market_cap_category: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) wi_id: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_call_currency: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) mkt_point3: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) display_id: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) mkt_point2: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) mkt_point4: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) risk_packages: Optional[Union[Tuple[Tuple[str, ...], ...], Tuple[str, ...]]] = field(default=None, metadata=field_metadata) units: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) em_id: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) sts_credit_region: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) ext_mkt_point3: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_classifications_risk_country_name: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_vendor: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_index1_tenor: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) mkt_type: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) alias: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) is_public: Optional[Union[Tuple[bool, ...], bool]] = field(default=None, metadata=field_metadata) ext_mkt_point1: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) product_type: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) ext_mkt_point2: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) sub_region_code: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) last_returns_end_date: Optional[Union[Tuple[datetime.date, ...], datetime.date]] = field(default=None, metadata=field_metadata) asset_parameters_fixed_rate: Optional[Union[Tuple[Union[float, str], ...], Union[float, str]]] = field(default=None, metadata=field_metadata) asset_parameters_option_type: Optional[Union[Tuple[Union[OptionType, str], ...], Union[OptionType, str]]] = field(default=None, metadata=field_metadata) tsdb_synced_symbol: Optional[Union[Tuple[bool, ...], bool]] = field(default=None, metadata=field_metadata) position_source_type: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_multiplier: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) cross: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) lms_id: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) flagship: Optional[Union[Tuple[bool, ...], bool]] = field(default=None, metadata=field_metadata) minimum_denomination: Optional[Union[Tuple[float, ...], float]] = field(default=None, metadata=field_metadata) in_code: Optional[Union[Tuple[bool, ...], bool]] = field(default=None, metadata=field_metadata) asset_parameters_strike_price_relative: Optional[Union[Tuple[Union[float, str], ...], Union[float, str]]] = field(default=None, metadata=field_metadata) sts_rates_maturity: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) sts_include_sstk_analytics: Optional[Union[Tuple[bool, ...], bool]] = field(default=None, metadata=field_metadata) position_source: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) listed: Optional[Union[Tuple[bool, ...], bool]] = field(default=None, metadata=field_metadata) non_owner_id: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) latest_end_date: Optional[Union[Tuple[datetime.date, ...], datetime.date]] = field(default=None, metadata=field_metadata) shock_style: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) g10_currency: Optional[Union[Tuple[bool, ...], bool]] = field(default=None, metadata=field_metadata) strategy: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) vintage_name: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) methodology: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) isin: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) asset_parameters_strike_type: Optional[Union[Tuple[str, ...], str]] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class IndexCurveShift(Scenario): market_data_pattern: Optional[MarketDataPattern] = field(default=None, metadata=field_metadata) freeze_pattern: Optional[MarketDataPattern] = field(default=None, metadata=field_metadata) annualised_parallel_shift: Optional[float] = field(default=None, metadata=field_metadata) annualised_slope_shift: Optional[float] = field(default=None, metadata=field_metadata) cutoff: Optional[float] = field(default=None, metadata=field_metadata) floor: Optional[float] = field(default=None, metadata=field_metadata) tenor: Optional[str] = field(default=None, metadata=field_metadata) rate_option: Optional[str] = field(default=None, metadata=field_metadata) bucket_shift: Optional[float] = field(default=None, metadata=field_metadata) bucket_start: Optional[datetime.date] = field(default=None, metadata=field_metadata) bucket_end: Optional[datetime.date] = field(default=None, metadata=field_metadata) scenario_type: Optional[str] = field(init=False, default='IndexCurveShift', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MarketDataPatternAndShock(Base): pattern: MarketDataPattern = field(default=None, metadata=field_metadata) shock: MarketDataShock = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MarketDataVolShockScenario(Scenario): pattern: MarketDataPattern = field(default=None, metadata=field_metadata) shock_type: MarketDataShockType = field(default=None, metadata=field_metadata) vol_levels: Tuple[MarketDataVolSlice, ...] = field(default=None, metadata=field_metadata) ref_spot: float = field(default=None, metadata=field_metadata) scenario_type: Optional[str] = field(init=False, default='MarketDataVolShockScenario', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PCOExposureLeg(Base): local_to_base_rate: Optional[str] = field(default=None, metadata=field_metadata) local_nav_limits: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) base_nav_limits: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) all_approved_hedge_ratio: Optional[str] = field(default=None, metadata=field_metadata) show_all_approved_hedge_ratio: Optional[bool] = field(default=None, metadata=field_metadata) hedge_ratio: Optional[str] = field(default=None, metadata=field_metadata) exposure_ratio: Optional[str] = field(default=None, metadata=field_metadata) local_currency: Optional[Currency] = field(default=None, metadata=field_metadata) target_ratio: Optional[str] = field(default=None, metadata=field_metadata) benchmark: Optional[PCOBenchmark] = field(default=None, metadata=field_metadata) long_rebalance_threshold: Optional[str] = field(default=None, metadata=field_metadata) short_rebalance_threshold: Optional[str] = field(default=None, metadata=field_metadata) base_nav: Optional[str] = field(default=None, metadata=field_metadata) local_nav: Optional[str] = field(default=None, metadata=field_metadata) base_fx_forward: Optional[str] = field(default=None, metadata=field_metadata) local_fx_forward: Optional[str] = field(default=None, metadata=field_metadata) auto_roll: Optional[bool] = field(default=None, metadata=field_metadata) exposure_currencies: Optional[Tuple[Currency, ...]] = field(default=None, metadata=field_metadata) roll_date: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PCOSecurityWeight(Base): security_type: Optional[str] = field(default=None, metadata=field_metadata) currency_weights: Optional[Tuple[PCOCurrencyWeight, ...]] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class User(Base): company: str = field(default=None, metadata=field_metadata) id_: str = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) country: str = field(default=None, metadata=field_metadata) city: str = field(default=None, metadata=field_metadata) region: str = field(default=None, metadata=field_metadata) email: str = field(default=None, metadata=field_metadata) name: str = field(default=None, metadata=field_metadata) internal: Optional[bool] = field(default=None, metadata=field_metadata) system_user: Optional[bool] = field(default=None, metadata=field_metadata) app_user: Optional[bool] = field(default=None, metadata=field_metadata) analytics_id: Optional[str] = field(default=None, metadata=field_metadata) eaa_company: Optional[str] = field(default=None, metadata=field_metadata) root_oe_id: Optional[str] = field(default=None, metadata=config(field_name='rootOEId', exclude=exclude_none)) oe_id: Optional[str] = field(default=None, metadata=field_metadata) drg_id: Optional[str] = field(default=None, metadata=field_metadata) root_oe_name: Optional[str] = field(default=None, metadata=config(field_name='rootOEName', exclude=exclude_none)) oe_name: Optional[str] = field(default=None, metadata=field_metadata) oe_alias: Optional[int] = field(default=None, metadata=field_metadata) coverage: Optional[Tuple[DictBase, ...]] = field(default=None, metadata=field_metadata) internal_email: Optional[str] = field(default=None, metadata=field_metadata) kerberos: Optional[str] = field(default=None, metadata=field_metadata) first_name: Optional[str] = field(default=None, metadata=field_metadata) last_name: Optional[str] = field(default=None, metadata=field_metadata) internal_id: Optional[str] = field(default=None, metadata=config(field_name='internalID', exclude=exclude_none)) mi_fidii_trade_idea_declined: Optional[str] = field(default=None, metadata=config(field_name='miFIDIITradeIdeaDeclined', exclude=exclude_none)) department_code: Optional[str] = field(default=None, metadata=field_metadata) department_name: Optional[str] = field(default=None, metadata=field_metadata) division_name: Optional[str] = field(default=None, metadata=field_metadata) business_unit: Optional[str] = field(default=None, metadata=field_metadata) title: Optional[str] = field(default=None, metadata=field_metadata) pmd: Optional[bool] = field(default=None, metadata=field_metadata) login: Optional[str] = field(default=None, metadata=field_metadata) tokens: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) roles: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) groups: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) created_by_id: Optional[str] = field(default=None, metadata=field_metadata) created_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) last_updated_by_id: Optional[str] = field(default=None, metadata=field_metadata) last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) app_managers: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) contact_sources: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AssetParameters(Base): basket_type: Optional[str] = field(default=None, metadata=field_metadata) style: Optional[str] = field(default=None, metadata=field_metadata) index_calculation_type: Optional[str] = field(default=None, metadata=field_metadata) index_return_type: Optional[str] = field(default=None, metadata=field_metadata) index_divisor: Optional[float] = field(default=None, metadata=field_metadata) currency: Optional[Currency] = field(default=None, metadata=field_metadata) quote_currency: Optional[Currency] = field(default=None, metadata=field_metadata) index_initial_price: Optional[float] = field(default=None, metadata=field_metadata) initial_pricing_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) expiration_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) expiration_location: Optional[str] = field(default=None, metadata=field_metadata) number_of_shares: Optional[float] = field(default=None, metadata=field_metadata) option_style: Optional[str] = field(default=None, metadata=field_metadata) option_type: Optional[OptionType] = field(default=None, metadata=field_metadata) settlement_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) settlement_type: Optional[str] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) put_currency: Optional[Currency] = field(default=None, metadata=field_metadata) put_amount: Optional[float] = field(default=None, metadata=field_metadata) automatic_exercise: Optional[bool] = field(default=None, metadata=field_metadata) call_amount: Optional[float] = field(default=None, metadata=field_metadata) call_currency: Optional[Currency] = field(default=None, metadata=field_metadata) exercise_time: Optional[str] = field(default=None, metadata=field_metadata) multiplier: Optional[float] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) premium: Optional[float] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) callable_: Optional[bool] = field(default=None, metadata=config(field_name='callable', exclude=exclude_none)) puttable: Optional[bool] = field(default=None, metadata=field_metadata) perpetual: Optional[bool] = field(default=None, metadata=field_metadata) seniority: Optional[str] = field(default=None, metadata=field_metadata) coupon_type: Optional[str] = field(default=None, metadata=field_metadata) index: Optional[str] = field(default=None, metadata=field_metadata) index_term: Optional[str] = field(default=None, metadata=field_metadata) index_margin: Optional[float] = field(default=None, metadata=field_metadata) coupon: Optional[float] = field(default=None, metadata=field_metadata) issue_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) issuer: Optional[str] = field(default=None, metadata=field_metadata) issuer_country_code: Optional[str] = field(default=None, metadata=field_metadata) issuer_type: Optional[str] = field(default=None, metadata=field_metadata) issue_size: Optional[float] = field(default=None, metadata=field_metadata) commodity_sector: Optional[CommoditySector] = field(default=None, metadata=field_metadata) pricing_location: Optional[PricingLocation] = field(default=None, metadata=field_metadata) contract_months: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) g10_currency: Optional[bool] = field(default=None, metadata=field_metadata) portfolio_id: Optional[str] = field(default=None, metadata=field_metadata) hedge_id: Optional[str] = field(default=None, metadata=field_metadata) ultimate_ticker: Optional[str] = field(default=None, metadata=field_metadata) strategy: Optional[str] = field(default=None, metadata=field_metadata) exchange_currency: Optional[Currency] = field(default=None, metadata=field_metadata) region: Optional[str] = field(default=None, metadata=field_metadata) delivery_point: Optional[str] = field(default=None, metadata=field_metadata) pricing_index: Optional[str] = field(default=None, metadata=field_metadata) common_code: Optional[str] = field(default=None, metadata=field_metadata) issuer_id: Optional[str] = field(default=None, metadata=field_metadata) contract_month: Optional[str] = field(default=None, metadata=field_metadata) bloomberg_collateral_classification: Optional[str] = field(default=None, metadata=field_metadata) bloomberg_publish_parameters: Optional[BloombergPublishParameters] = field(default=None, metadata=field_metadata) load_type: Optional[str] = field(default=None, metadata=field_metadata) contract_unit: Optional[str] = field(default=None, metadata=field_metadata) index_approval_ids: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) is_pair_basket: Optional[bool] = field(default=None, metadata=field_metadata) is_legacy_pair_basket: Optional[bool] = field(default=None, metadata=field_metadata) fixed_rate_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) floating_rate_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) forward_price: Optional[float] = field(default=None, metadata=field_metadata) pair_calculation: Optional[str] = field(default=None, metadata=field_metadata) pay_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) receive_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) pay_frequency: Optional[str] = field(default=None, metadata=field_metadata) receive_frequency: Optional[str] = field(default=None, metadata=field_metadata) resettable_leg: Optional[PayReceive] = field(default=None, metadata=field_metadata) inflation_lag: Optional[str] = field(default=None, metadata=field_metadata) fx_index: Optional[str] = field(default=None, metadata=field_metadata) index_notes: Optional[str] = field(default=None, metadata=field_metadata) index_not_trading_reasons: Optional[IndexNotTradingReasons] = field(default=None, metadata=field_metadata) trade_as: Optional[str] = field(default=None, metadata=field_metadata) clone_parent_id: Optional[str] = field(default=None, metadata=field_metadata) on_behalf_of: Optional[str] = field(default=None, metadata=field_metadata) index_calculation_agent: Optional[str] = field(default=None, metadata=field_metadata) product_type: Optional[ProductType] = field(default=None, metadata=field_metadata) vendor: Optional[str] = field(default=None, metadata=field_metadata) call_first_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) call_last_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) amount_outstanding: Optional[float] = field(default=None, metadata=field_metadata) covered_bond: Optional[bool] = field(default=None, metadata=field_metadata) issue_status: Optional[str] = field(default=None, metadata=field_metadata) issue_status_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) issue_price: Optional[float] = field(default=None, metadata=field_metadata) sinkable: Optional[bool] = field(default=None, metadata=field_metadata) sink_factor: Optional[float] = field(default=None, metadata=field_metadata) accrued_interest_standard: Optional[float] = field(default=None, metadata=field_metadata) redemption_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) redemption_price: Optional[float] = field(default=None, metadata=field_metadata) redemption_amount: Optional[float] = field(default=None, metadata=field_metadata) redemption_percent: Optional[float] = field(default=None, metadata=field_metadata) private_placement_type: Optional[str] = field(default=None, metadata=field_metadata) minimum_piece: Optional[float] = field(default=None, metadata=field_metadata) minimum_increment: Optional[float] = field(default=None, metadata=field_metadata) next_coupon_payment: Optional[datetime.date] = field(default=None, metadata=field_metadata) minimum_denomination: Optional[float] = field(default=None, metadata=field_metadata) default_backcast: Optional[bool] = field(default=None, metadata=field_metadata) index_precision: Optional[float] = field(default=None, metadata=field_metadata) official_side: Optional[Side] = field(default=None, metadata=field_metadata) valuation_source: Optional[BasketValuationSource] = field(default=None, metadata=field_metadata) close_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) credit_index_series: Optional[str] = field(default=None, metadata=field_metadata) reference_entity: Optional[str] = field(default=None, metadata=field_metadata) restructuring_type: Optional[str] = field(default=None, metadata=field_metadata) underlying_type: Optional[str] = field(default=None, metadata=field_metadata) underlier: Optional[str] = field(default=None, metadata=field_metadata) next_rebalance_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) last_rebalance_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) last_rebalance_approval_id: Optional[str] = field(default=None, metadata=field_metadata) target_notional: Optional[float] = field(default=None, metadata=field_metadata) rebalance_frequency: Optional[EqBasketRebalanceFrequency] = field(default=None, metadata=field_metadata) historical_methodology: Optional[EqBasketHistoryMethodology] = field(default=None, metadata=field_metadata) cash_reinvestment_treatment: Optional[CashReinvestmentTreatment] = field(default=None, metadata=field_metadata) benchmark: Optional[str] = field(default=None, metadata=field_metadata) attribution_dataset_id: Optional[str] = field(init=False, default='STSATTR', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CSLScheduleArray(Base): schedule_values: Optional[Tuple[CSLSchedule, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqBasketRebalanceCalendar(Base): frequency_rule: Optional[EqBasketRebalanceFrequency] = field(default=None, metadata=field_metadata) date_override: Optional[EqBasketRebalanceDateOverride] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MarketDataShockBasedScenario(Scenario): shocks: Tuple[MarketDataPatternAndShock, ...] = field(default=None, metadata=field_metadata) scenario_type: Optional[str] = field(init=False, default='MarketDataShockBasedScenario', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class OverlayMarket(SingleMarket): base_market: BaseMarket = field(default=None, metadata=field_metadata) market_data: Tuple[MarketDataCoordinateValue, ...] = field(default=None, metadata=field_metadata) market_model_data: Optional[str] = field(default=None, metadata=field_metadata) market_type: Optional[str] = field(init=False, default='OverlayMarket', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PCOExposure(Base): last_data_updated_date_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) nav_includes_fx_hedges: Optional[bool] = field(default=None, metadata=field_metadata) use_fx_rate_on_base_fx_forward: Optional[bool] = field(default=None, metadata=field_metadata) last_generate_orders_date_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) legs: Optional[Tuple[PCOExposureLeg, ...]] = field(default=None, metadata=field_metadata) adjustments: Optional[PCOExposureAdjustments] = field(default=None, metadata=field_metadata) ratio_mode: Optional[str] = field(default=None, metadata=field_metadata) hedge_calc_currency: Optional[PCOCurrencyType] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PCOSecurity(Base): weights: Optional[PCOSecurityWeight] = field(default=None, metadata=field_metadata) base_currency_exposure_limits: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) hedge_ratio: Optional[str] = field(default=None, metadata=field_metadata) local_currency: Optional[Currency] = field(default=None, metadata=field_metadata) base_currency: Optional[Currency] = field(default=None, metadata=field_metadata) base_currency_exposure: Optional[str] = field(default=None, metadata=field_metadata) local_currency_exposure: Optional[str] = field(default=None, metadata=field_metadata) security_name: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class RiskMeasure(Base): asset_class: Optional[AssetClass] = field(default=None, metadata=field_metadata) measure_type: Optional[RiskMeasureType] = field(default=None, metadata=field_metadata) unit: Optional[RiskMeasureUnit] = field(default=None, metadata=field_metadata) parameters: Optional[RiskMeasureParameter] = field(default=None, metadata=field_metadata) value: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DataSetFieldMap(Base): data_set_id: str = field(default=None, metadata=field_metadata) field_: str = field(default=None, metadata=config(field_name='field', exclude=exclude_none)) results_field: str = field(default=None, metadata=field_metadata) risk_measure: RiskMeasure = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EntityQuery(Base): format_: Optional[Format] = field(default=None, metadata=config(field_name='format', exclude=exclude_none)) where: Optional[FieldFilterMap] = field(default=None, metadata=field_metadata) as_of_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) last_updated_since: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) date: Optional[datetime.date] = field(default=None, metadata=field_metadata) time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) delay: Optional[int] = field(default=None, metadata=field_metadata) order_by: Optional[Tuple[Union[DictBase, str], ...]] = field(default=None, metadata=field_metadata) scroll: Optional[str] = field(default=None, metadata=field_metadata) scroll_id: Optional[str] = field(default=None, metadata=field_metadata) fields: Optional[Tuple[Union[DictBase, str], ...]] = field(default=None, metadata=field_metadata) limit: Optional[int] = field(default=None, metadata=field_metadata) offset: Optional[int] = field(default=None, metadata=field_metadata) vendor: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MeasureScenario(Scenario): risk_measure: RiskMeasure = field(default=None, metadata=field_metadata) value: float = field(default=None, metadata=field_metadata) scenario_type: Optional[str] = field(init=False, default='MeasureScenario', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PCOSecurityBreakdown(Base): breakdown_currency_type: Optional[PCOCurrencyType] = field(default=None, metadata=field_metadata) last_security_breakdown_update_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) securities: Optional[Tuple[PCOSecurity, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Position(Base): asset_id: Optional[str] = field(default=None, metadata=field_metadata) quantity: Optional[float] = field(default=None, metadata=field_metadata) weight: Optional[float] = field(default=None, metadata=field_metadata) notional: Optional[float] = field(default=None, metadata=field_metadata) party_to: Optional[SimpleParty] = field(default=None, metadata=field_metadata) party_from: Optional[SimpleParty] = field(default=None, metadata=field_metadata) trade_on: Optional[BasketPositionTradeOn] = field(default=None, metadata=field_metadata) external_ids: Optional[Tuple[DictBase, ...]] = field(default=None, metadata=field_metadata) margin_ids: Optional[Tuple[DictBase, ...]] = field(default=None, metadata=field_metadata) tags: Optional[Tuple[PositionTag, ...]] = field(default=None, metadata=field_metadata) instrument: Optional[InstrumentBase] = field(default=None, metadata=field_metadata) description: Optional[str] = field(default=None, metadata=field_metadata) error: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class RelativeMarket(Base): from_market: SingleMarket = field(default=None, metadata=field_metadata) to_market: SingleMarket = field(default=None, metadata=field_metadata) market_type: Optional[str] = field(init=False, default='RelativeMarket', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CompositeScenario(Scenario): scenarios: Optional[Tuple[Scenario, ...]] = field(default=None, metadata=field_metadata) scenario_type: Optional[str] = field(init=False, default='CompositeScenario', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MultiScenario(Scenario): scenarios: Optional[Tuple[Scenario, ...]] = field(default=None, metadata=field_metadata) scenario_type: Optional[str] = field(init=False, default='MultiScenario', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PositionSetRequest(Base): date: datetime.date = field(default=None, metadata=field_metadata) positions: Tuple[PositionsRequest, ...] = field(default=None, metadata=field_metadata) target_notional: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PricingDateAndMarketDataAsOf(Base): pricing_date: datetime.date = field(default=None, metadata=field_metadata) market_data_as_of: Optional[Union[datetime.date, datetime.datetime]] = field(default=None, metadata=field_metadata) market: Optional[Market] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class LiquidityRequest(Base): notional: Optional[float] = field(default=None, metadata=field_metadata) positions: Optional[Union[Tuple[Position, ...], Tuple[WeightedPosition, ...]]] = field(default=None, metadata=field_metadata) risk_model: Optional[str] = field(default=None, metadata=field_metadata) date: Optional[datetime.date] = field(default=None, metadata=field_metadata) currency: Optional[Currency] = field(default=None, metadata=field_metadata) participation_rate: Optional[float] = field(default=None, metadata=field_metadata) execution_horizon: Optional[float] = field(default=None, metadata=field_metadata) execution_start_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) execution_end_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) benchmark_id: Optional[str] = field(default=None, metadata=field_metadata) measures: Optional[Tuple[LiquidityMeasure, ...]] = field(default=None, metadata=field_metadata) time_series_benchmark_ids: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) time_series_start_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) time_series_end_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) format_: Optional[Format] = field(default=None, metadata=config(field_name='format', exclude=exclude_none)) report_parameters: Optional[LiquidityReportParameters] = field(default=None, metadata=field_metadata) explode_positions: Optional[bool] = field(default=False, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PositionSet(Base): positions: Tuple[Position, ...] = field(default=None, metadata=field_metadata) position_date: datetime.date = field(default=None, metadata=field_metadata) id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) last_update_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) type_: Optional[str] = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) divisor: Optional[float] = field(default=None, metadata=field_metadata) weighting_strategy: Optional[PositionSetWeightingStrategy] = field(default=None, metadata=field_metadata) last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class RiskPosition(Base): instrument: Priceable = field(default=None, metadata=field_metadata) instrument_name: Optional[str] = field(default=None, metadata=field_metadata) quantity: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class RiskRequest(Base): positions: Tuple[RiskPosition, ...] = field(default=None, metadata=field_metadata) measures: Tuple[RiskMeasure, ...] = field(default=None, metadata=field_metadata) pricing_and_market_data_as_of: Optional[Tuple[PricingDateAndMarketDataAsOf, ...]] = field(default=None, metadata=field_metadata) pricing_location: Optional[PricingLocation] = field(default=None, metadata=field_metadata) wait_for_results: Optional[bool] = field(default=False, metadata=field_metadata) scenario: Optional[MarketDataScenario] = field(default=None, metadata=field_metadata) parameters: Optional[RiskRequestParameters] = field(default=None, metadata=field_metadata) request_visible_to_gs: Optional[bool] = field(default=False, metadata=field_metadata) use_cache: Optional[bool] = field(default=False, metadata=field_metadata) priority: Optional[int] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ReportParameters(Base): approval_id: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(default=None, metadata=field_metadata) transaction_cost_model: Optional[str] = field(default=None, metadata=field_metadata) trading_cost: Optional[float] = field(default=None, metadata=field_metadata) servicing_cost_long: Optional[float] = field(default=None, metadata=field_metadata) servicing_cost_short: Optional[float] = field(default=None, metadata=field_metadata) region: Optional[str] = field(default=None, metadata=field_metadata) risk_model: Optional[str] = field(default=None, metadata=field_metadata) benchmark: Optional[str] = field(default=None, metadata=field_metadata) blended_benchmark: Optional[Tuple[BlendedBenchmarkAttribute, ...]] = field(default=None, metadata=field_metadata) tags: Optional[Tuple[PositionTag, ...]] = field(default=None, metadata=field_metadata) aggregate_by_tag_name: Optional[str] = field(default=None, metadata=field_metadata) fx_hedged: Optional[bool] = field(default=None, metadata=field_metadata) include_risk_free_attribution: Optional[bool] = field(default=None, metadata=field_metadata) rebalance_calendar: Optional[EqBasketRebalanceCalendar] = field(default=None, metadata=field_metadata) cash_reinvestment_treatment: Optional[CashReinvestmentTreatment] = field(default=None, metadata=field_metadata) historical_methodology: Optional[EqBasketHistoryMethodology] = field(default=None, metadata=field_metadata) backtest_parameters: Optional[EqBasketBacktestParameters] = field(default=None, metadata=field_metadata) bloomberg_publish_parameters: Optional[BloombergPublishParameters] = field(default=None, metadata=field_metadata) publish_to_bloomberg: Optional[bool] = field(default=None, metadata=field_metadata) publish_to_reuters: Optional[bool] = field(default=None, metadata=field_metadata) publish_to_factset: Optional[bool] = field(default=None, metadata=field_metadata) is_silent_update: Optional[bool] = field(default=None, metadata=field_metadata) include_price_history: Optional[bool] = field(default=None, metadata=field_metadata) index_update: Optional[bool] = field(default=None, metadata=field_metadata) index_rebalance: Optional[bool] = field(default=None, metadata=field_metadata) export_rebalance: Optional[bool] = field(default=None, metadata=field_metadata) index_source_id: Optional[str] = field(default=None, metadata=field_metadata) basket_action: Optional[BasketAction] = field(default=None, metadata=field_metadata) api_domain: Optional[bool] = field(default=None, metadata=field_metadata) initial_price: Optional[float] = field(default=None, metadata=field_metadata) stock_level_exposures: Optional[bool] = field(default=None, metadata=field_metadata) explode_positions: Optional[bool] = field(default=None, metadata=field_metadata) scenario_id: Optional[str] = field(default=None, metadata=field_metadata) scenario_ids: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) scenario_group_id: Optional[str] = field(default=None, metadata=field_metadata) scenario_type: Optional[ScenarioType] = field(default=None, metadata=field_metadata) market_model_id: Optional[str] = field(default=None, metadata=field_metadata) risk_measures: Optional[Tuple[RiskMeasure, ...]] = field(default=None, metadata=field_metadata) initial_pricing_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) backcast: Optional[bool] = field(default=None, metadata=field_metadata) risk_request: Optional[RiskRequest] = field(default=None, metadata=field_metadata) subscription_parameters: Optional[ReportSubscriptionParameters] = field(default=None, metadata=field_metadata) thresholds: Optional[Tuple[SystematicHedgeThreshold, ...]] = field(default=None, metadata=field_metadata) frequency: Optional[str] = field(default=None, metadata=field_metadata) multi_hedge_id: Optional[str] = field(default=None, metadata=field_metadata) participation_rate: Optional[float] = field(default=None, metadata=field_metadata) approve_rebalance: Optional[bool] = field(default=None, metadata=field_metadata) auto_approved_rebalance: Optional[bool] = field(default=None, metadata=field_metadata) use_risk_request_batch_mode: Optional[bool] = field(default=None, metadata=field_metadata) limited_access_assets: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) corporate_action_restricted_assets: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) backcast_dates: Optional[Tuple[datetime.date, ...]] = field(default=None, metadata=field_metadata) base_currency: Optional[Currency] = field(default=None, metadata=field_metadata) local_currency: Optional[Currency] = field(default=None, metadata=field_metadata) fund_calendar: Optional[str] = field(default=None, metadata=field_metadata) calculation_currency: Optional[PCOCurrencyType] = field(default=None, metadata=field_metadata) hedge_settlement_interval: Optional[Tuple[PCOParameterValues, ...]] = field(default=None, metadata=field_metadata) hedge_settlement_day: Optional[Tuple[PCOParameterValues, ...]] = field(default=None, metadata=field_metadata) roll_horizon: Optional[Tuple[PCOParameterValues, ...]] = field(default=None, metadata=field_metadata) roll_split_days: Optional[int] = field(default=None, metadata=field_metadata) pnl_currency: Optional[Tuple[PCOParameterValues, ...]] = field(default=None, metadata=field_metadata) nav_publication_period: Optional[Tuple[PCOParameterValues, ...]] = field(default=None, metadata=field_metadata) roll_date_zero_threshold: Optional[bool] = field(default=None, metadata=field_metadata) unrealised_mark_to_market: Optional[PCOUnrealisedMarkToMarket] = field(default=None, metadata=field_metadata) target_deviation: Optional[Tuple[PCOTargetDeviation, ...]] = field(default=None, metadata=field_metadata) cash_balances: Optional[Tuple[PCOCashBalance, ...]] = field(default=None, metadata=field_metadata) exposure: Optional[PCOExposure] = field(default=None, metadata=field_metadata) pco_share_class: Optional[PCOShareClass] = field(default=None, metadata=field_metadata) settlements: Optional[Tuple[PCOSettlements, ...]] = field(default=None, metadata=field_metadata) show_cash: Optional[bool] = field(default=None, metadata=field_metadata) show_exposure: Optional[bool] = field(default=None, metadata=field_metadata) enable_rfq: Optional[bool] = field(default=None, metadata=config(field_name='enableRFQ', exclude=exclude_none)) fixing_descriptions: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) pco_origin: Optional[PCOOrigin] = field(default=None, metadata=field_metadata) pco_action_type: Optional[PCOActionType] = field(default=None, metadata=field_metadata) security_breakdown: Optional[PCOSecurityBreakdown] = field(default=None, metadata=field_metadata) version: Optional[str] = field(default=None, metadata=field_metadata) roll_currency: Optional[Tuple[PCOParameterValues, ...]] = field(default=None, metadata=field_metadata) user_modified_fields: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) use_live_market: Optional[bool] = field(default=None, metadata=field_metadata) basket_ready_for_trade: Optional[bool] = field(default=None, metadata=field_metadata) allow_in_position_rebalance: Optional[bool] = field(default=None, metadata=field_metadata) weighting_strategy: Optional[PositionSetWeightingStrategy] = field(default=None, metadata=field_metadata) on_behalf_of: Optional[str] = field(default=None, metadata=field_metadata) dependency_report_ids: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ReportScheduleRequest(Base): parameters: Optional[ReportParameters] = field(default=None, metadata=field_metadata) end_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) start_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) use_close_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) priority: Optional[ReportJobPriority] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) ================================================ FILE: gs_quant/target/content.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.base import * from gs_quant.common import * import datetime from typing import Dict, Optional, Tuple, Union from dataclasses import dataclass, field from dataclasses_json import LetterCase, config, dataclass_json from enum import Enum class Division(EnumBase, Enum): SECDIV = 'SECDIV' IBD = 'IBD' RISK = 'RISK' GIR = 'GIR' EO = 'EO' KENSHO = 'KENSHO' class InvestmentRecommendationDirection(EnumBase, Enum): Buy = 'Buy' Hold = 'Hold' Sell = 'Sell' Strategy = 'Strategy' class Language(EnumBase, Enum): """ISO 639-1 language code for the content piece""" an = 'an' ar = 'ar' _as = 'as' av = 'av' ay = 'ay' az = 'az' ba = 'ba' be = 'be' bg = 'bg' bh = 'bh' bi = 'bi' bm = 'bm' bn = 'bn' bo = 'bo' br = 'br' bs = 'bs' ca = 'ca' ce = 'ce' ch = 'ch' co = 'co' cr = 'cr' cs = 'cs' cu = 'cu' cv = 'cv' cy = 'cy' da = 'da' de = 'de' dv = 'dv' dz = 'dz' ee = 'ee' el = 'el' en = 'en' eo = 'eo' es = 'es' et = 'et' eu = 'eu' fa = 'fa' ff = 'ff' fi = 'fi' fj = 'fj' fo = 'fo' fr = 'fr' fy = 'fy' ga = 'ga' gd = 'gd' gl = 'gl' gn = 'gn' gu = 'gu' gv = 'gv' ha = 'ha' he = 'he' hi = 'hi' ho = 'ho' hr = 'hr' ht = 'ht' hu = 'hu' hy = 'hy' hz = 'hz' ia = 'ia' id = 'id' ie = 'ie' ig = 'ig' ii = 'ii' ik = 'ik' io = 'io' _is = 'is' it = 'it' iu = 'iu' ja = 'ja' jv = 'jv' ka = 'ka' kg = 'kg' ki = 'ki' kj = 'kj' kk = 'kk' kl = 'kl' km = 'km' kn = 'kn' ko = 'ko' kr = 'kr' ks = 'ks' ku = 'ku' kv = 'kv' kw = 'kw' ky = 'ky' la = 'la' lb = 'lb' lg = 'lg' li = 'li' ln = 'ln' lo = 'lo' lt = 'lt' lu = 'lu' lv = 'lv' mg = 'mg' mh = 'mh' mi = 'mi' mk = 'mk' ml = 'ml' mn = 'mn' mr = 'mr' ms = 'ms' mt = 'mt' my = 'my' na = 'na' nb = 'nb' nd = 'nd' ne = 'ne' ng = 'ng' nl = 'nl' nn = 'nn' no = 'no' nr = 'nr' nv = 'nv' ny = 'ny' oc = 'oc' oj = 'oj' om = 'om' _or = 'or' os = 'os' pa = 'pa' pi = 'pi' pl = 'pl' ps = 'ps' pt = 'pt' qu = 'qu' rm = 'rm' rn = 'rn' ro = 'ro' ru = 'ru' rw = 'rw' sa = 'sa' sc = 'sc' sd = 'sd' se = 'se' sg = 'sg' si = 'si' sk = 'sk' sl = 'sl' sm = 'sm' sn = 'sn' so = 'so' sq = 'sq' sr = 'sr' ss = 'ss' st = 'st' su = 'su' sv = 'sv' sw = 'sw' ta = 'ta' te = 'te' tg = 'tg' th = 'th' ti = 'ti' tk = 'tk' tl = 'tl' tn = 'tn' to = 'to' tr = 'tr' ts = 'ts' tt = 'tt' tw = 'tw' ty = 'ty' ug = 'ug' uk = 'uk' ur = 'ur' uz = 'uz' ve = 've' vi = 'vi' vo = 'vo' wa = 'wa' wo = 'wo' xh = 'xh' yi = 'yi' yo = 'yo' za = 'za' zh = 'zh' zu = 'zu' class Origin(EnumBase, Enum): """Where the content originated from""" WEB = 'WEB' API = 'API' EMAIL = 'EMAIL' BLOG = 'BLOG' ARTICLE = 'ARTICLE' class QueryableStatus(EnumBase, Enum): """Status/state of a content piece that can be queried by a user""" Draft = 'Draft' Published = 'Published' Replaced = 'Replaced' @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ChannelMetadata(Base): name: Optional[str] = field(default=None, metadata=field_metadata) channel_visibility: Optional[object] = field(default=None, metadata=field_metadata) restricted: Optional[bool] = field(default=None, metadata=field_metadata) streaming: Optional[bool] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Content(Base): body: str = field(default=None, metadata=field_metadata) mime_type: object = field(default=None, metadata=field_metadata) encoding: object = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) class Object(DictBase): pass @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Author(Base): id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=field_metadata) division: Optional[Division] = field(default=None, metadata=field_metadata) email: Optional[str] = field(default=None, metadata=field_metadata) title: Optional[str] = field(default=None, metadata=field_metadata) kerberos: Optional[str] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BulkDeleteContentResponse(Base): status: Optional[int] = field(default=None, metadata=field_metadata) message: Optional[str] = field(default=None, metadata=field_metadata) data: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Certification(Base): submission_id: str = field(default=None, metadata=field_metadata) version: str = field(default=None, metadata=field_metadata) submission_state: object = field(default=None, metadata=field_metadata) allowed_distribution: Tuple[Object, ...] = field(default=None, metadata=field_metadata) etask_process_instance_id: Optional[str] = field(default=None, metadata=field_metadata) tags: Optional[Tuple[None, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DeleteContentResponse(Base): status: Optional[int] = field(default=None, metadata=field_metadata) message: Optional[str] = field(default=None, metadata=field_metadata) data: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class InvestmentRecommendationAsset(Base): asset_id: str = field(default=None, metadata=field_metadata) direction: Optional[InvestmentRecommendationDirection] = field(default=None, metadata=field_metadata) currency: Optional[str] = field(default=None, metadata=field_metadata) price: Optional[float] = field(default=None, metadata=field_metadata) price_target: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class InvestmentRecommendationCustomAsset(Base): asset_name: str = field(default=None, metadata=field_metadata) direction: Optional[InvestmentRecommendationDirection] = field(default=None, metadata=field_metadata) currency: Optional[str] = field(default=None, metadata=field_metadata) price: Optional[float] = field(default=None, metadata=field_metadata) price_target: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ContentResponse(Base): id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) version: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) entitlement_exclusions: Optional[EntitlementExclusions] = field(default=None, metadata=field_metadata) created_by_id: Optional[str] = field(default=None, metadata=field_metadata) created_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) channels: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) content: Optional[Content] = field(default=None, metadata=field_metadata) language: Optional[Language] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ContentUpdateRequest(Base): name: Optional[str] = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) entitlement_exclusions: Optional[EntitlementExclusions] = field(default=None, metadata=field_metadata) content: Optional[Content] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class InvestmentRecommendations(Base): assets: Tuple[InvestmentRecommendationAsset, ...] = field(default=None, metadata=field_metadata) custom_assets: Optional[Tuple[InvestmentRecommendationCustomAsset, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BulkContentUpdateRequestItem(Base): id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) update: Optional[ContentUpdateRequest] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ContentAuditFields(Base): id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) version: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) entitlement_exclusions: Optional[EntitlementExclusions] = field(default=None, metadata=field_metadata) created_by_id: Optional[str] = field(default=None, metadata=field_metadata) authors: Optional[Tuple[Author, ...]] = field(default=None, metadata=field_metadata) created_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ContentParameters(Base): author_ids: Tuple[str, ...] = field(default=None, metadata=field_metadata) language: Language = field(default=None, metadata=field_metadata) status: Optional[object] = field(default=None, metadata=field_metadata) source: Optional[Division] = field(default=None, metadata=field_metadata) tags: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) slug: Optional[str] = field(default=None, metadata=field_metadata) attachments: Optional[Tuple[Content, ...]] = field(default=None, metadata=field_metadata) certification: Optional[Certification] = field(default=None, metadata=field_metadata) certification_type: Optional[object] = field(default=None, metadata=field_metadata) asset_ids: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) origin: Optional[Origin] = field(default=None, metadata=field_metadata) investment_recommendations: Optional[InvestmentRecommendations] = field(default=None, metadata=field_metadata) is_flow: Optional[bool] = field(default=None, metadata=field_metadata) is_research_summary: Optional[bool] = field(default=None, metadata=field_metadata) is_restricted: Optional[bool] = field(default=None, metadata=field_metadata) post_sharing_type: Optional[object] = field(default=None, metadata=field_metadata) channels_metadata: Optional[Tuple[ChannelMetadata, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class GetManyContentsResponse(Base): status: Optional[int] = field(default=None, metadata=field_metadata) message: Optional[str] = field(default=None, metadata=field_metadata) data: Optional[Tuple[ContentResponse, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BulkContentUpdateResponse(Base): status: Optional[int] = field(default=None, metadata=field_metadata) message: Optional[str] = field(default=None, metadata=field_metadata) data: Optional[Tuple[ContentAuditFields, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ContentCreateRequest(Base): name: str = field(default=None, metadata=field_metadata) entitlements: Entitlements = field(default=None, metadata=field_metadata) entitlement_exclusions: EntitlementExclusions = field(default=None, metadata=field_metadata) content: Content = field(default=None, metadata=field_metadata) parameters: ContentParameters = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ContentCreateResponse(Base): status: Optional[int] = field(default=None, metadata=field_metadata) message: Optional[str] = field(default=None, metadata=field_metadata) data: Optional[ContentAuditFields] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ContentUpdateResponse(Base): status: Optional[int] = field(default=None, metadata=field_metadata) message: Optional[str] = field(default=None, metadata=field_metadata) data: Optional[ContentAuditFields] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) ================================================ FILE: gs_quant/target/coordinates.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.base import * from gs_quant.common import * import datetime from typing import Dict, Optional, Tuple, Union from dataclasses import dataclass, field from dataclasses_json import LetterCase, config, dataclass_json from enum import Enum class MDAPIQueryField(EnumBase, Enum): ask = 'ask' bid = 'bid' mid = 'mid' expectedDataQuality = 'expectedDataQuality' actualDataQuality = 'actualDataQuality' @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MDAPIDataQuery(Base): market_data_coordinates: Tuple[MarketDataCoordinate, ...] = field(default=None, metadata=field_metadata) format_: Optional[Format] = field(default=None, metadata=config(field_name='format', exclude=exclude_none)) pricing_location: Optional[PricingLocation] = field(default=None, metadata=field_metadata) selector_function: Optional[str] = field(default=None, metadata=field_metadata) samples: Optional[int] = field(default=None, metadata=field_metadata) interval: Optional[str] = field(default=None, metadata=field_metadata) vendor: Optional[MarketDataVendor] = field(default=None, metadata=field_metadata) start_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) end_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) start_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) end_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) real_time: Optional[bool] = field(default=True, metadata=field_metadata) fields: Optional[Tuple[MDAPIQueryField, ...]] = field(default=None, metadata=field_metadata) time_filter: Optional[TimeFilter] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MDAPIDataQueryResponse(Base): data: Optional[Tuple[FieldValueMap, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MDAPIDataBatchResponse(Base): request_id: Optional[str] = field(default=None, metadata=field_metadata) responses: Optional[Tuple[MDAPIDataQueryResponse, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) ================================================ FILE: gs_quant/target/countries.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.base import * from gs_quant.common import * import datetime from typing import Dict, Optional, Tuple, Union from dataclasses import dataclass, field from dataclasses_json import LetterCase, config, dataclass_json @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CountryXref(Base): alpha2: str = field(default=None, metadata=field_metadata) alpha3: str = field(default=None, metadata=field_metadata) country_code: str = field(default=None, metadata=field_metadata) bbid: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Country(Base): name: str = field(default=None, metadata=field_metadata) id_: str = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) xref: CountryXref = field(default=None, metadata=field_metadata) region: str = field(default=None, metadata=field_metadata) sub_region: str = field(default=None, metadata=field_metadata) region_code: str = field(default=None, metadata=field_metadata) sub_region_code: str = field(default=None, metadata=field_metadata) created_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) created_by_id: Optional[str] = field(default=None, metadata=field_metadata) last_updated_by_id: Optional[str] = field(default=None, metadata=field_metadata) owner_id: Optional[str] = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Subdivision(Base): name: str = field(default=None, metadata=field_metadata) id_: str = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) country_id: str = field(default=None, metadata=field_metadata) created_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) created_by_id: Optional[str] = field(default=None, metadata=field_metadata) last_updated_by_id: Optional[str] = field(default=None, metadata=field_metadata) owner_id: Optional[str] = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) ================================================ FILE: gs_quant/target/data.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.base import * from gs_quant.common import * import datetime from typing import Dict, Optional, Tuple, Union from dataclasses import dataclass, field from dataclasses_json import LetterCase, config, dataclass_json from enum import Enum class DataSetType(EnumBase, Enum): """Type of the dataset""" Alloy = 'Alloy' Snowflake = 'Snowflake' PDL = 'PDL' LHSnowflake = 'LHSnowflake' NativeSnowflake = 'NativeSnowflake' class DelayExclusionType(EnumBase, Enum): """Type of the delay exclusion""" LAST_DAY_OF_THE_MONTH = 'LAST_DAY_OF_THE_MONTH' class DevelopmentStatus(EnumBase, Enum): """The status of development of this dataset. Controls rate limit on query/upload.""" Development = 'Development' Production = 'Production' class FieldFormat(EnumBase, Enum): """Format to apply on field validation. Currently supports a subset of built-in formats (from JSON schema specification).""" date = 'date' date_time = 'date-time' class MarketDataMeasure(EnumBase, Enum): Last = 'Last' Curve = 'Curve' Close_Change = 'Close Change' Previous_Close = 'Previous Close' class MeasureEntityType(EnumBase, Enum): """Entity type associated with a measure.""" ASSET = 'ASSET' BACKTEST = 'BACKTEST' KPI = 'KPI' COUNTRY = 'COUNTRY' SUBDIVISION = 'SUBDIVISION' REPORT = 'REPORT' HEDGE = 'HEDGE' PORTFOLIO = 'PORTFOLIO' RISK_MODEL = 'RISK_MODEL' @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AdvancedFilter(Base): column: str = field(default=None, metadata=field_metadata) operator: str = field(default=None, metadata=field_metadata) value: Optional[float] = field(default=None, metadata=field_metadata) values: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) format_: Optional[str] = field(default=None, metadata=config(field_name='format', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AlloyService(Base): base_url: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ColumnEnumPair(Base): column: Optional[str] = field(default=None, metadata=field_metadata) enum_key: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DataSetCondition(Base): column: str = field(default=None, metadata=field_metadata) operator: str = field(default=None, metadata=field_metadata) value: Optional[float] = field(default=None, metadata=field_metadata) values: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DataSetDefaults(Base): start_seconds: Optional[float] = field(default=None, metadata=field_metadata) end_seconds: Optional[float] = field(default=None, metadata=field_metadata) delay_seconds: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DataSetFieldEntityAttributes(Base): in_code: Optional[bool] = field(default=None, metadata=field_metadata) is_entity: Optional[bool] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DataSetFieldEntityClassifications(Base): groups: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) data_set_id: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DataSetFieldEntityNumberParameters(Base): maximum: Optional[int] = field(default=None, metadata=field_metadata) minimum: Optional[int] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EntityMetadata(Base): created_by_id: Optional[str] = field(default=None, metadata=field_metadata) created_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) last_updated_by_id: Optional[str] = field(default=None, metadata=field_metadata) last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ErrorInfo(Base): status_code: int = field(default=None, metadata=field_metadata) reason_phrase: str = field(default=None, metadata=field_metadata) title: Optional[str] = field(default=None, metadata=field_metadata) messages: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FieldLinkSelector(Base): field_selector: Optional[str] = field(default=None, metadata=field_metadata) description: Optional[str] = field(default=None, metadata=field_metadata) display_name: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MDAPI(Base): type_: str = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) quoting_styles: Tuple[DictBase, ...] = field(default=None, metadata=field_metadata) class_: Optional[str] = field(default=None, metadata=config(field_name='class', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MarketDataField(Base): name: Optional[str] = field(default=None, metadata=field_metadata) mapping: Optional[str] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MarketDataFilteredField(Base): field_: Optional[str] = field(default=None, metadata=config(field_name='field', exclude=exclude_none)) default_value: Optional[str] = field(default=None, metadata=field_metadata) default_numerical_value: Optional[float] = field(default=None, metadata=field_metadata) default_boolean_value: Optional[bool] = field(default=None, metadata=field_metadata) numerical_values: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) values: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) multi_measure: Optional[bool] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @dataclass class MeasureBacktest(Base): pass @dataclass class MeasureKpi(Base): pass @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MidPrice(Base): bid_column: Optional[str] = field(default=None, metadata=field_metadata) ask_column: Optional[str] = field(default=None, metadata=field_metadata) mid_column: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ParserEntity(Base): only_normalized_fields: Optional[bool] = field(default=None, metadata=field_metadata) quotes: Optional[bool] = field(default=None, metadata=field_metadata) trades: Optional[bool] = field(default=None, metadata=field_metadata) only_mqtick_fields: Optional[bool] = field(default=None, metadata=field_metadata) include_trd_flg_proc: Optional[bool] = field(default=None, metadata=field_metadata) keep_raw_fields: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) include_mmt: Optional[bool] = field(default=None, metadata=field_metadata) include_currency: Optional[bool] = field(default=None, metadata=field_metadata) separate_quote_exchange_codes: Optional[bool] = field(default=None, metadata=field_metadata) trade_ids: Optional[bool] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class QueryProcessor(Base): processor_name: Optional[str] = field(default=None, metadata=field_metadata) manual_processor_name: Optional[str] = field(default=None, metadata=field_metadata) params: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class RemapFieldPair(Base): field_: Optional[str] = field(default=None, metadata=config(field_name='field', exclude=exclude_none)) remap_to: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ResponseInfo(Base): request_id: Optional[str] = field(default=None, metadata=field_metadata) messages: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class StoredEntity(Base): stored_processor_name: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SymbolFilterLink(Base): entity_field: Optional[str] = field(default=None, metadata=field_metadata) entity_type: Optional[str] = field(init=False, default='MktCoordinate', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AlloyConfig(Base): data_service: Optional[AlloyService] = field(default=None, metadata=field_metadata) coverage_service: Optional[AlloyService] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DBConfig(Base): db: Optional[str] = field(default=None, metadata=field_metadata) drg_id: Optional[str] = field(default=None, metadata=field_metadata) id_column: Optional[str] = field(default=None, metadata=config(field_name='IdColumn', exclude=exclude_none)) date_time_column: Optional[str] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DataFilter(Base): field_: str = field(default=None, metadata=config(field_name='field', exclude=exclude_none)) values: Tuple[str, ...] = field(default=None, metadata=field_metadata) column: Optional[str] = field(default=None, metadata=field_metadata) where: Optional[DataSetCondition] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DataSetCoverageProperties(Base): prefixes: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) prefix_type: Optional[str] = field(default=None, metadata=field_metadata) asset_classes: Optional[Tuple[AssetClass, ...]] = field(default=None, metadata=field_metadata) asset_types: Optional[Tuple[AssetType, ...]] = field(default=None, metadata=field_metadata) entity_types: Optional[Tuple[MeasureEntityType, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DataSetDelay(Base): until_seconds: float = field(default=None, metadata=field_metadata) at_time_zone: str = field(default=None, metadata=field_metadata) when: Optional[Tuple[DelayExclusionType, ...]] = field(default=None, metadata=field_metadata) history_up_to_seconds: Optional[float] = field(default=None, metadata=field_metadata) history_up_to_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) history_up_to_months: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DataSetFieldEntityStringParameters(Base): enum: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) format_: Optional[FieldFormat] = field(default=None, metadata=config(field_name='format', exclude=exclude_none)) pattern: Optional[str] = field(default=r'^[\w ]{1,256}$', metadata=field_metadata) max_length: Optional[int] = field(default=None, metadata=field_metadata) min_length: Optional[int] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DataSetTransforms(Base): redact_columns: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) round_columns: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) remap_fields: Optional[Tuple[RemapFieldPair, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) class FieldFilterMapDataQuery(DictBase): pass @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FieldLink(Base): entity_identifier: Optional[str] = field(default=None, metadata=field_metadata) prefix: Optional[str] = field(default=None, metadata=field_metadata) additional_entity_fields: Optional[Tuple[FieldLinkSelector, ...]] = field(default=None, metadata=field_metadata) entity_type: Optional[str] = field(init=False, default='Asset', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MarketDataMapping(Base): asset_class: Optional[AssetClass] = field(default=None, metadata=field_metadata) query_type: Optional[str] = field(default=None, metadata=field_metadata) description: Optional[str] = field(default=None, metadata=field_metadata) scale: Optional[float] = field(default=None, metadata=field_metadata) frequency: Optional[MarketDataFrequency] = field(default=None, metadata=field_metadata) measures: Optional[Tuple[MarketDataMeasure, ...]] = field(default=None, metadata=field_metadata) data_set: Optional[str] = field(default=None, metadata=field_metadata) vendor: Optional[MarketDataVendor] = field(default=None, metadata=field_metadata) fields: Optional[Tuple[MarketDataField, ...]] = field(default=None, metadata=field_metadata) rank: Optional[float] = field(default=None, metadata=field_metadata) filtered_fields: Optional[Tuple[MarketDataFilteredField, ...]] = field(default=None, metadata=field_metadata) asset_types: Optional[Tuple[AssetType, ...]] = field(default=None, metadata=field_metadata) entity_type: Optional[MeasureEntityType] = field(default=None, metadata=field_metadata) backtest_entity: Optional[MeasureBacktest] = field(default=None, metadata=field_metadata) kpi_entity: Optional[MeasureKpi] = field(default=None, metadata=field_metadata) multi_measure: Optional[bool] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ProcessorEntity(Base): filters: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) parsers: Optional[Tuple[ParserEntity, ...]] = field(default=None, metadata=field_metadata) deduplicate: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) enum_type: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) enums: Optional[Tuple[ColumnEnumPair, ...]] = field(default=None, metadata=field_metadata) fill_fwd: Optional[str] = field(default=None, metadata=field_metadata) stored: Optional[Tuple[StoredEntity, ...]] = field(default=None, metadata=field_metadata) additional_processors: Optional[Tuple[QueryProcessor, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SymbolFilterDimension(Base): field_: Optional[str] = field(default=None, metadata=config(field_name='field', exclude=exclude_none)) field_description: Optional[str] = field(default=None, metadata=field_metadata) symbol_filter_link: Optional[SymbolFilterLink] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ComplexFilter(Base): operator: str = field(default=None, metadata=field_metadata) simple_filters: Tuple[DataFilter, ...] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DataGroup(Base): context: Optional[FieldValueMap] = field(default=None, metadata=field_metadata) data: Optional[Tuple[FieldValueMap, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DataQuery(Base): id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) data_set_id: Optional[str] = field(default=None, metadata=field_metadata) format_: Optional[Format] = field(default=None, metadata=config(field_name='format', exclude=exclude_none)) where: Optional[FieldFilterMapDataQuery] = field(default=None, metadata=field_metadata) vendor: Optional[MarketDataVendor] = field(default=None, metadata=field_metadata) start_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) end_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) start_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) page: Optional[int] = field(default=None, metadata=field_metadata) page_size: Optional[int] = field(default=None, metadata=field_metadata) end_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) relative_start_date: Optional[str] = field(default=None, metadata=field_metadata) relative_end_date: Optional[str] = field(default=None, metadata=field_metadata) adjust_as_of: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) as_of_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) id_as_of_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) use_temporal_x_ref: Optional[bool] = field(default=False, metadata=field_metadata) restrict_secondary_identifier: Optional[bool] = field(default=False, metadata=field_metadata) since: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) dates: Optional[Tuple[datetime.date, ...]] = field(default=None, metadata=field_metadata) times: Optional[Tuple[datetime.datetime, ...]] = field(default=None, metadata=field_metadata) delay: Optional[int] = field(default=None, metadata=field_metadata) intervals: Optional[int] = field(default=None, metadata=field_metadata) samples: Optional[int] = field(default=None, metadata=field_metadata) limit: Optional[int] = field(default=None, metadata=field_metadata) polling_interval: Optional[int] = field(default=None, metadata=field_metadata) group_by: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) grouped: Optional[bool] = field(default=None, metadata=field_metadata) fields: Optional[Tuple[Union[DictBase, str], ...]] = field(default=None, metadata=field_metadata) restrict_fields: Optional[bool] = field(default=False, metadata=field_metadata) entity_filter: Optional[FieldFilterMapDataQuery] = field(default=None, metadata=field_metadata) interval: Optional[str] = field(default=None, metadata=field_metadata) distinct_consecutive: Optional[bool] = field(default=False, metadata=field_metadata) time_filter: Optional[TimeFilter] = field(default=None, metadata=field_metadata) use_field_alias: Optional[bool] = field(default=False, metadata=field_metadata) remap_schema_to_alias: Optional[bool] = field(default=False, metadata=field_metadata) show_linked_dimensions: Optional[bool] = field(default=True, metadata=field_metadata) use_project_processor: Optional[bool] = field(default=False, metadata=field_metadata) snapshot: Optional[bool] = field(default=False, metadata=field_metadata) search_until: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) markout: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) offset_to_exchange_open: Optional[str] = field(default=None, metadata=field_metadata) offset_to_exchange_close: Optional[str] = field(default=None, metadata=field_metadata) multi_trading_session: Optional[bool] = field(default=None, metadata=field_metadata) multi_session: Optional[bool] = field(default=None, metadata=field_metadata) quote_consolidation: Optional[bool] = field(default=None, metadata=field_metadata) consolidation: Optional[bool] = field(default=None, metadata=field_metadata) primary: Optional[bool] = field(default=None, metadata=field_metadata) time_index: Optional[str] = field(default=None, metadata=field_metadata) empty_intervals: Optional[bool] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DataSetFieldEntity(Base): name: str = field(default=None, metadata=field_metadata) description: str = field(default=None, metadata=field_metadata) type_: str = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) classifications: Optional[DataSetFieldEntityClassifications] = field(default=None, metadata=field_metadata) unique: Optional[bool] = field(default=False, metadata=field_metadata) field_java_type: Optional[str] = field(default=None, metadata=field_metadata) parameters: Optional[Union[DataSetFieldEntityNumberParameters, DataSetFieldEntityStringParameters]] = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) metadata: Optional[EntityMetadata] = field(default=None, metadata=field_metadata) attributes: Optional[DataSetFieldEntityAttributes] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DataSetParameters(Base): frequency: str = field(default=None, metadata=field_metadata) category: Optional[str] = field(default=None, metadata=field_metadata) sub_category: Optional[str] = field(default=None, metadata=field_metadata) methodology: Optional[str] = field(default=None, metadata=field_metadata) coverage: Optional[str] = field(default=None, metadata=field_metadata) coverages: Optional[Tuple[AssetType, ...]] = field(default=None, metadata=field_metadata) notes: Optional[str] = field(default=None, metadata=field_metadata) history: Optional[str] = field(default=None, metadata=field_metadata) sample_start: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) sample_end: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) published_date: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) history_date: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(default=None, metadata=field_metadata) owner_ids: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) support_ids: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) support_distribution_list: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) apply_market_data_entitlements: Optional[bool] = field(default=None, metadata=field_metadata) upload_data_policy: Optional[str] = field(default=None, metadata=field_metadata) logical_db: Optional[str] = field(default=None, metadata=field_metadata) sla: Optional[str] = field(default=None, metadata=field_metadata) symbol_strategy: Optional[str] = field(default=None, metadata=field_metadata) underlying_data_set_id: Optional[str] = field(default=None, metadata=field_metadata) immutable: Optional[bool] = field(default=None, metadata=field_metadata) include_in_catalog: Optional[bool] = field(default=False, metadata=field_metadata) coverage_enabled: Optional[bool] = field(default=True, metadata=field_metadata) use_created_time_for_upload: Optional[bool] = field(default=None, metadata=field_metadata) apply_entity_entitlements: Optional[bool] = field(default=None, metadata=field_metadata) development_status: Optional[DevelopmentStatus] = field(default=None, metadata=field_metadata) internal_owned: Optional[bool] = field(default=None, metadata=field_metadata) cr_limit_read: Optional[int] = field(default=None, metadata=field_metadata) cr_limit_write: Optional[int] = field(default=None, metadata=field_metadata) alloy_config: Optional[AlloyConfig] = field(default=None, metadata=field_metadata) snowflake_config: Optional[DBConfig] = field(default=None, metadata=field_metadata) external_distribution: Optional[bool] = field(default=None, metadata=field_metadata) snowflake_db: Optional[str] = field(init=False, default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DataSetTransformation(Base): transforms: DataSetTransforms = field(default=None, metadata=field_metadata) condition: Optional[DataSetCondition] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DeleteCoverageQuery(Base): where: Optional[FieldFilterMapDataQuery] = field(default=None, metadata=field_metadata) delete_all: Optional[bool] = field(default=False, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FieldColumnPair(Base): field_: Optional[str] = field(default=None, metadata=config(field_name='field', exclude=exclude_none)) column: Optional[str] = field(default=None, metadata=field_metadata) field_description: Optional[str] = field(default=None, metadata=field_metadata) link: Optional[FieldLink] = field(default=None, metadata=field_metadata) aliases: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) display_alias: Optional[str] = field(default=None, metadata=field_metadata) resolvable: Optional[bool] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class HistoryFilter(Base): absolute_start: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) absolute_end: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) relative_start_seconds: Optional[float] = field(default=None, metadata=field_metadata) relative_end_seconds: Optional[float] = field(default=None, metadata=field_metadata) delay: Optional[Union[DataSetDelay, Tuple[DataSetDelay, ...]]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DataQueryResponse(Base): type_: str = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) request_id: Optional[str] = field(default=None, metadata=field_metadata) error_message: Optional[str] = field(default=None, metadata=field_metadata) id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) total_pages: Optional[int] = field(default=None, metadata=field_metadata) data_set_id: Optional[str] = field(default=None, metadata=field_metadata) entity_type: Optional[MeasureEntityType] = field(default=None, metadata=field_metadata) delay: Optional[int] = field(default=None, metadata=field_metadata) data: Optional[Tuple[FieldValueMap, ...]] = field(default=None, metadata=field_metadata) groups: Optional[Tuple[DataGroup, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DataSetCatalogEntry(Base): id_: str = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) name: str = field(default=None, metadata=field_metadata) vendor: str = field(default=None, metadata=field_metadata) fields: DictBase = field(default=None, metadata=field_metadata) description: Optional[str] = field(default=None, metadata=field_metadata) short_description: Optional[str] = field(default=None, metadata=field_metadata) data_product: Optional[str] = field(default=None, metadata=field_metadata) terms: Optional[str] = field(default=None, metadata=field_metadata) internal_only: Optional[bool] = field(default=None, metadata=field_metadata) actions: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) default_start_seconds: Optional[float] = field(default=None, metadata=field_metadata) identifier_mapper_name: Optional[str] = field(default=None, metadata=field_metadata) identifier_updater_name: Optional[str] = field(default=None, metadata=field_metadata) default_delay_minutes: Optional[float] = field(default=None, metadata=field_metadata) apply_market_data_entitlements: Optional[bool] = field(default=None, metadata=field_metadata) sample: Optional[Tuple[FieldValueMap, ...]] = field(default=None, metadata=field_metadata) parameters: Optional[DataSetParameters] = field(default=None, metadata=field_metadata) tags: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) created_time: Optional[str] = field(default=None, metadata=field_metadata) last_updated_time: Optional[str] = field(default=None, metadata=field_metadata) start_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) mdapi: Optional[MDAPI] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DataSetDimensions(Base): time_field: str = field(default=None, metadata=field_metadata) symbol_dimensions: Tuple[str, ...] = field(default=None, metadata=field_metadata) transaction_time_field: Optional[str] = field(default=None, metadata=field_metadata) symbol_dimension_properties: Optional[Tuple[FieldColumnPair, ...]] = field(default=None, metadata=field_metadata) non_symbol_dimensions: Optional[Tuple[FieldColumnPair, ...]] = field(default=None, metadata=field_metadata) symbol_dimension_link: Optional[FieldLink] = field(default=None, metadata=field_metadata) linked_dimensions: Optional[Tuple[FieldLinkSelector, ...]] = field(default=None, metadata=field_metadata) symbol_filter_dimensions: Optional[Tuple[SymbolFilterDimension, ...]] = field(default=None, metadata=field_metadata) key_dimensions: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) measures: Optional[Tuple[FieldColumnPair, ...]] = field(default=None, metadata=field_metadata) entity_dimension: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DataSetFieldEntityBulkRequest(Base): fields: Tuple[DataSetFieldEntity, ...] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EntityFilter(Base): operator: Optional[str] = field(default=None, metadata=field_metadata) simple_filters: Optional[Tuple[DataFilter, ...]] = field(default=None, metadata=field_metadata) complex_filters: Optional[Tuple[ComplexFilter, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DataSetFilters(Base): entity_filter: Optional[EntityFilter] = field(default=None, metadata=field_metadata) row_filters: Optional[Tuple[DataFilter, ...]] = field(default=None, metadata=field_metadata) advanced_filters: Optional[Tuple[AdvancedFilter, ...]] = field(default=None, metadata=field_metadata) history_filter: Optional[HistoryFilter] = field(default=None, metadata=field_metadata) time_filter: Optional[TimeFilter] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DataSetEntity(Base): id_: str = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) name: str = field(default=None, metadata=field_metadata) organization_id: Optional[str] = field(default=None, metadata=field_metadata) description: Optional[str] = field(default=None, metadata=field_metadata) short_description: Optional[str] = field(default=None, metadata=field_metadata) mappings: Optional[Tuple[MarketDataMapping, ...]] = field(default=None, metadata=field_metadata) vendor: Optional[MarketDataVendor] = field(default=None, metadata=field_metadata) mdapi: Optional[MDAPI] = field(default=None, metadata=field_metadata) data_product: Optional[str] = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) query_processors: Optional[ProcessorEntity] = field(default=None, metadata=field_metadata) parameters: Optional[DataSetParameters] = field(default=None, metadata=field_metadata) dimensions: Optional[DataSetDimensions] = field(default=None, metadata=field_metadata) coverage_properties: Optional[DataSetCoverageProperties] = field(default=None, metadata=field_metadata) defaults: Optional[DataSetDefaults] = field(default=None, metadata=field_metadata) filters: Optional[DataSetFilters] = field(default=None, metadata=field_metadata) transformations: Optional[Tuple[DataSetTransformation, ...]] = field(default=None, metadata=field_metadata) created_by_id: Optional[str] = field(default=None, metadata=field_metadata) created_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) last_updated_by_id: Optional[str] = field(default=None, metadata=field_metadata) last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) tags: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) type_: Optional[DataSetType] = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) ================================================ FILE: gs_quant/target/data_screen.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.base import * from gs_quant.common import * import datetime from typing import Dict, Optional, Tuple, Union from dataclasses import dataclass, field from dataclasses_json import LetterCase, config, dataclass_json @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AnalyticsScreen(Base): name: str = field(default=None, metadata=field_metadata) filter_parameters: FilterRequest = field(default=None, metadata=field_metadata) base_screener: str = field(default=None, metadata=field_metadata) id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) created_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) created_by_id: Optional[str] = field(default=None, metadata=field_metadata) last_updated_by_id: Optional[str] = field(default=None, metadata=field_metadata) owner_id: Optional[str] = field(default=None, metadata=field_metadata) description: Optional[str] = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) hidden_columns: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) ================================================ FILE: gs_quant/target/groups.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.base import * from gs_quant.common import * import datetime from typing import Dict, Optional, Tuple, Union from dataclasses import dataclass, field from dataclasses_json import LetterCase, config, dataclass_json @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class GroupWithMembersCount(Base): members_count: Optional[int] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class UpdateGroupMembershipRequest(Base): user_ids: Tuple[str, ...] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class UserCoverage(Base): name: str = field(default=None, metadata=field_metadata) email: str = field(default=None, metadata=field_metadata) app: Optional[str] = field(default=None, metadata=field_metadata) phone: Optional[str] = field(default=None, metadata=field_metadata) guid: Optional[str] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class GroupResponse(Base): results: Tuple[GroupWithMembersCount, ...] = field(default=None, metadata=field_metadata) total_results: int = field(default=None, metadata=field_metadata) scroll_id: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CreateGroupRequest(Base): id_: str = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) name: str = field(default=None, metadata=field_metadata) description: Optional[str] = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) oe_id: Optional[str] = field(default=None, metadata=field_metadata) owner_id: Optional[str] = field(default=None, metadata=field_metadata) tags: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class UpdateGroupRequest(Base): name: Optional[str] = field(default=None, metadata=field_metadata) description: Optional[str] = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) oe_id: Optional[str] = field(default=None, metadata=field_metadata) owner_id: Optional[str] = field(default=None, metadata=field_metadata) tags: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Group(Base): id_: str = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) name: str = field(default=None, metadata=field_metadata) description: Optional[str] = field(default=None, metadata=field_metadata) created_by_id: Optional[str] = field(default=None, metadata=field_metadata) last_updated_by_id: Optional[str] = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) owner_id: Optional[str] = field(default=None, metadata=field_metadata) oe_id: Optional[str] = field(default=None, metadata=field_metadata) tags: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) ================================================ FILE: gs_quant/target/hedge.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.base import * from gs_quant.common import * import datetime from typing import Dict, Optional, Tuple, Union from dataclasses import dataclass, field from dataclasses_json import LetterCase, config, dataclass_json from enum import Enum class CorporateActionsTypes(EnumBase, Enum): """Types of corporate actions in the hedge""" Mergers = 'Mergers' Quote_lot_adjustments = 'Quote lot adjustments' Rights = 'Rights' Spinoffs = 'Spinoffs' Cash_dividends = 'Cash dividends' Stock_splits = 'Stock splits' Reorganization = 'Reorganization' class HedgeObjective(EnumBase, Enum): """The objective of the hedge.""" Minimize_Factor_Risk = 'Minimize Factor Risk' Replicate_Performance = 'Replicate Performance' class HedgeUniverseAssetType(EnumBase, Enum): """Type of assets that will be added to the hedge universe.""" Custom_Basket = 'Custom Basket' ETF = 'ETF' Research_Basket = 'Research Basket' Single_Stock = 'Single Stock' class HedgerComparisonType(EnumBase, Enum): Asset = 'Asset' Portfolio = 'Portfolio' Hedge = 'Hedge' class HedgerConstraintPrioritySetting(EnumBase, Enum): """Priority of the constraint from 0-5 (prioritized in that order). The optimization will fail if it cannot meet a constraint with 0 priority. A constraint with priority of 1-5 can be called a relaxed constraint, which means that the optimization will make its best effort to meet the constraint but will not fail if it cannot.""" _0 = '0' _1 = '1' _2 = '2' _3 = '3' _4 = '4' _5 = '5' class SamplingPeriod(EnumBase, Enum): """The length of time in between return samples.""" Daily = 'Daily' Weekly = 'Weekly' @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AssetConstraint(Base): asset_id: str = field(default=None, metadata=field_metadata) max_: float = field(default=None, metadata=config(field_name='max', exclude=exclude_none)) min_: float = field(default=None, metadata=config(field_name='min', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @dataclass class BasketConditions(Base): pass @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ClassificationConstraint(Base): type_: str = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) name: str = field(default=None, metadata=field_metadata) max_: float = field(default=None, metadata=config(field_name='max', exclude=exclude_none)) min_: float = field(default=None, metadata=config(field_name='min', exclude=exclude_none)) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ESGConstraint(Base): name: str = field(default=None, metadata=field_metadata) max_: float = field(default=None, metadata=config(field_name='max', exclude=exclude_none)) min_: float = field(default=None, metadata=config(field_name='min', exclude=exclude_none)) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FactorConstraint(Base): factor: str = field(default=None, metadata=field_metadata) exposure: float = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FactorExposure(Base): factor: str = field(default=None, metadata=field_metadata) exposure: float = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FactorMCTRByGroupConstraint(Base): factors: Tuple[str, ...] = field(default=None, metadata=field_metadata) max_: float = field(default=None, metadata=config(field_name='max', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class HedgerComparisonProperties(Base): hedge_value_type: str = field(default=None, metadata=field_metadata) hedge_value: float = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FactorExposures(Base): country: Tuple[FactorExposure, ...] = field(default=None, metadata=field_metadata) industry: Tuple[FactorExposure, ...] = field(default=None, metadata=field_metadata) sector: Tuple[FactorExposure, ...] = field(default=None, metadata=field_metadata) style: Tuple[FactorExposure, ...] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FactorHedgeUniverse(Base): asset_ids: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) asset_types: Optional[Tuple[HedgeUniverseAssetType, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FactorHedgerConstraintPrioritySettings(Base): min_sector_weights: Optional[HedgerConstraintPrioritySetting] = field(default=None, metadata=field_metadata) max_sector_weights: Optional[HedgerConstraintPrioritySetting] = field(default=None, metadata=field_metadata) min_industry_weights: Optional[HedgerConstraintPrioritySetting] = field(default=None, metadata=field_metadata) max_industry_weights: Optional[HedgerConstraintPrioritySetting] = field(default=None, metadata=field_metadata) min_region_weights: Optional[HedgerConstraintPrioritySetting] = field(default=None, metadata=field_metadata) max_region_weights: Optional[HedgerConstraintPrioritySetting] = field(default=None, metadata=field_metadata) min_country_weights: Optional[HedgerConstraintPrioritySetting] = field(default=None, metadata=field_metadata) max_country_weights: Optional[HedgerConstraintPrioritySetting] = field(default=None, metadata=field_metadata) style_exposures: Optional[HedgerConstraintPrioritySetting] = field(default=None, metadata=field_metadata) country_exposures: Optional[HedgerConstraintPrioritySetting] = field(default=None, metadata=field_metadata) region_exposures: Optional[HedgerConstraintPrioritySetting] = field(default=None, metadata=field_metadata) industry_exposures: Optional[HedgerConstraintPrioritySetting] = field(default=None, metadata=field_metadata) sector_exposures: Optional[HedgerConstraintPrioritySetting] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class HedgeConstituent(Base): asset_id: str = field(default=None, metadata=field_metadata) name: str = field(default=None, metadata=field_metadata) weight: float = field(default=None, metadata=field_metadata) currency: Currency = field(default=None, metadata=field_metadata) country: Optional[str] = field(default=None, metadata=field_metadata) correlation: Optional[float] = field(default=None, metadata=field_metadata) transaction_cost: Optional[float] = field(default=None, metadata=field_metadata) marginal_cost: Optional[float] = field(default=None, metadata=field_metadata) borrow_cost: Optional[float] = field(default=None, metadata=field_metadata) shares: Optional[float] = field(default=None, metadata=field_metadata) price: Optional[float] = field(default=None, metadata=field_metadata) multiplier: Optional[float] = field(default=None, metadata=field_metadata) notional: Optional[float] = field(default=None, metadata=field_metadata) bbid: Optional[str] = field(default=None, metadata=field_metadata) adv_percentage: Optional[float] = field(default=None, metadata=field_metadata) sector: Optional[str] = field(default=None, metadata=field_metadata) industry: Optional[str] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FactorHedgerResultPositions(Base): beta_exposure: float = field(default=None, metadata=field_metadata) daily_va_r: float = field(default=None, metadata=field_metadata) factor_exposures: FactorExposures = field(default=None, metadata=field_metadata) specific_exposure: float = field(default=None, metadata=field_metadata) systematic_exposure: float = field(default=None, metadata=field_metadata) total_risk: float = field(default=None, metadata=field_metadata) volatility: float = field(default=None, metadata=field_metadata) net_exposure: float = field(default=None, metadata=field_metadata) constituents: Optional[Tuple[HedgeConstituent, ...]] = field(default=None, metadata=field_metadata) number_of_positions: Optional[float] = field(default=None, metadata=field_metadata) cumulative_pnl: Optional[Tuple[Tuple[Union[datetime.date, float], ...], ...]] = field(default=None, metadata=field_metadata) transaction_cost: Optional[float] = field(default=None, metadata=field_metadata) borrow_cost_bps: Optional[float] = field(default=None, metadata=field_metadata) max_drawdown: Optional[float] = field(default=None, metadata=field_metadata) gross_exposure: Optional[float] = field(default=None, metadata=field_metadata) long_exposure: Optional[float] = field(default=None, metadata=field_metadata) short_exposure: Optional[float] = field(default=None, metadata=field_metadata) tracking_error: Optional[float] = field(default=None, metadata=field_metadata) correlation: Optional[float] = field(default=None, metadata=field_metadata) exposure_overlap_with_target: Optional[float] = field(default=None, metadata=field_metadata) total_pnl: Optional[float] = field(default=None, metadata=field_metadata) turnover_percentage: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class HedgeBenchmark(Base): asset_id: str = field(default=None, metadata=field_metadata) cumulative_pnl: Optional[Tuple[Tuple[Union[datetime.date, float], ...], ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class HedgeGetManyRequestPathSchema(Base): limit: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) offset: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) scroll: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) scroll_id: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) ids: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) order_by: Optional[Tuple[Union[DictBase, str], ...]] = field(default=None, metadata=field_metadata) hedge_tracking_error: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) hedge_volatility: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) tags: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) last_updated_by_id: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) created_by_id: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) target_notional: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) owner_id: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) hedge_annualized_volatility: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) name: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) description: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) id_: Optional[Tuple[str, ...]] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) objective: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) hedge_notional: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PerformanceHedgeResult(Base): target: DictBase = field(default=None, metadata=field_metadata) hedge: Optional[DictBase] = field(default=None, metadata=field_metadata) hedged_target: Optional[DictBase] = field(default=None, metadata=field_metadata) benchmarks: Optional[Tuple[DictBase, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FactorHedgeResult(Base): hedge: FactorHedgerResultPositions = field(default=None, metadata=field_metadata) hedged_target: FactorHedgerResultPositions = field(default=None, metadata=field_metadata) target: Optional[FactorHedgerResultPositions] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class HedgerComparison(Base): entity_id: str = field(default=None, metadata=field_metadata) entity_type: HedgerComparisonType = field(default=None, metadata=field_metadata) hedge_properties: HedgerComparisonProperties = field(default=None, metadata=field_metadata) result: Optional[Union[FactorHedgeResult, PerformanceHedgeResult]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Target(Base): id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) tags: Optional[Tuple[PositionTag, ...]] = field(default=None, metadata=field_metadata) parent_sts_id: Optional[str] = field(default=None, metadata=field_metadata) positions: Optional[Tuple[Position, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FactorHedgeParameters(Base): risk_model: str = field(default=None, metadata=field_metadata) target_notional: float = field(default=None, metadata=field_metadata) hedge_notional: float = field(default=None, metadata=field_metadata) hedge_target: Target = field(default=None, metadata=field_metadata) hedge_universe: FactorHedgeUniverse = field(default=None, metadata=field_metadata) hedge_date: datetime.date = field(default=None, metadata=field_metadata) backtest_start_date: datetime.date = field(default=None, metadata=field_metadata) backtest_end_date: datetime.date = field(default=None, metadata=field_metadata) fx_hedged: Optional[bool] = field(default=None, metadata=field_metadata) exclude_target_assets: Optional[bool] = field(default=None, metadata=field_metadata) exclude_corporate_actions: Optional[bool] = field(default=None, metadata=field_metadata) exclude_corporate_actions_types: Optional[Tuple[CorporateActionsTypes, ...]] = field(default=None, metadata=field_metadata) exclude_hard_to_borrow_assets: Optional[bool] = field(default=None, metadata=field_metadata) exclude_restricted_assets: Optional[bool] = field(default=None, metadata=field_metadata) max_adv_percentage: Optional[float] = field(default=None, metadata=field_metadata) explode_universe: Optional[bool] = field(default=None, metadata=field_metadata) min_names: Optional[float] = field(default=None, metadata=field_metadata) max_names: Optional[float] = field(default=None, metadata=field_metadata) max_trades: Optional[float] = field(default=None, metadata=field_metadata) min_weight: Optional[float] = field(default=None, metadata=field_metadata) max_weight: Optional[float] = field(default=None, metadata=field_metadata) min_market_cap: Optional[float] = field(default=None, metadata=field_metadata) max_market_cap: Optional[float] = field(default=None, metadata=field_metadata) max_factor_mctr: Optional[float] = field(default=None, metadata=config(field_name='maxFactorMCTR', exclude=exclude_none)) max_factor_mctr_by_group: Optional[Tuple[FactorMCTRByGroupConstraint, ...]] = field(default=None, metadata=config(field_name='maxFactorMCTRByGroup', exclude=exclude_none)) market_participation_rate: Optional[float] = field(default=10, metadata=field_metadata) asset_constraints: Optional[Tuple[AssetConstraint, ...]] = field(default=None, metadata=field_metadata) constrain_assets_by_notional: Optional[bool] = field(default=None, metadata=field_metadata) allow_long_short: Optional[bool] = field(default=None, metadata=field_metadata) only_reweight_target_composition: Optional[bool] = field(default=None, metadata=field_metadata) factor_constraints: Optional[Tuple[FactorConstraint, ...]] = field(default=None, metadata=field_metadata) classification_constraints: Optional[Tuple[ClassificationConstraint, ...]] = field(default=None, metadata=field_metadata) esg_constraints: Optional[Tuple[ESGConstraint, ...]] = field(default=None, metadata=field_metadata) constraint_priority_settings: Optional[FactorHedgerConstraintPrioritySettings] = field(default=None, metadata=field_metadata) comparisons: Optional[Tuple[HedgerComparison, ...]] = field(default=None, metadata=field_metadata) turnover_portfolio_id: Optional[str] = field(default=None, metadata=field_metadata) turnover_tags: Optional[Tuple[PositionTag, ...]] = field(default=None, metadata=field_metadata) max_turnover_percentage: Optional[float] = field(default=None, metadata=field_metadata) turnover_notional_type: Optional[str] = field(default=None, metadata=field_metadata) return_type: Optional[ReturnType] = field(default=None, metadata=field_metadata) is_best_basket: Optional[bool] = field(default=None, metadata=field_metadata) is_sector_etf: Optional[bool] = field(default=None, metadata=config(field_name='isSectorETF', exclude=exclude_none)) is_sts: Optional[bool] = field(default=None, metadata=config(field_name='isSTS', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PerformanceHedgeParameters(Base): hedge_target: Target = field(default=None, metadata=field_metadata) universe: Tuple[str, ...] = field(default=None, metadata=field_metadata) notional: float = field(default=None, metadata=field_metadata) observation_start_date: datetime.date = field(default=None, metadata=field_metadata) observation_end_date: datetime.date = field(default=None, metadata=field_metadata) backtest_start_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) backtest_end_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) sampling_period: Optional[SamplingPeriod] = field(default=SamplingPeriod.Weekly, metadata=field_metadata) max_leverage: Optional[float] = field(default=None, metadata=field_metadata) percentage_in_cash: Optional[float] = field(default=None, metadata=field_metadata) explode_universe: Optional[bool] = field(default=None, metadata=field_metadata) exclude_target_assets: Optional[bool] = field(default=None, metadata=field_metadata) exclude_corporate_actions: Optional[bool] = field(default=None, metadata=field_metadata) exclude_corporate_actions_types: Optional[Tuple[CorporateActionsTypes, ...]] = field(default=None, metadata=field_metadata) exclude_hard_to_borrow_assets: Optional[bool] = field(default=None, metadata=field_metadata) exclude_restricted_assets: Optional[bool] = field(default=None, metadata=field_metadata) max_adv_percentage: Optional[float] = field(default=None, metadata=field_metadata) max_return_deviation: Optional[float] = field(default=None, metadata=field_metadata) max_weight: Optional[float] = field(default=None, metadata=field_metadata) min_weight: Optional[float] = field(default=None, metadata=field_metadata) min_market_cap: Optional[float] = field(default=None, metadata=field_metadata) max_market_cap: Optional[float] = field(default=None, metadata=field_metadata) market_participation_rate: Optional[float] = field(default=10, metadata=field_metadata) asset_constraints: Optional[Tuple[AssetConstraint, ...]] = field(default=None, metadata=field_metadata) classification_constraints: Optional[Tuple[ClassificationConstraint, ...]] = field(default=None, metadata=field_metadata) esg_constraints: Optional[Tuple[ESGConstraint, ...]] = field(default=None, metadata=field_metadata) benchmarks: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) use_machine_learning: Optional[bool] = field(default=False, metadata=field_metadata) lasso_weight: Optional[float] = field(default=None, metadata=field_metadata) ridge_weight: Optional[float] = field(default=None, metadata=field_metadata) return_type: Optional[ReturnType] = field(default=None, metadata=field_metadata) is_sts: Optional[bool] = field(default=None, metadata=config(field_name='isSTS', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Hedge(Base): name: str = field(default=None, metadata=field_metadata) parameters: Union[FactorHedgeParameters, PerformanceHedgeParameters] = field(default=None, metadata=field_metadata) id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) owner_id: Optional[str] = field(default=None, metadata=field_metadata) created_by_id: Optional[str] = field(default=None, metadata=field_metadata) created_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) last_updated_by_id: Optional[str] = field(default=None, metadata=field_metadata) last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) tags: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) description: Optional[str] = field(default=None, metadata=field_metadata) objective: Optional[HedgeObjective] = field(default=None, metadata=field_metadata) result: Optional[Union[FactorHedgeResult, PerformanceHedgeResult]] = field(default=None, metadata=field_metadata) comparison_results: Optional[Tuple[HedgerComparison, ...]] = field(default=None, metadata=field_metadata) ================================================ FILE: gs_quant/target/indices.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.base import * from gs_quant.common import * import datetime from typing import Dict, Optional, Tuple, Union from dataclasses import dataclass, field from dataclasses_json import LetterCase, config, dataclass_json from enum import Enum class ApprovalStatus(EnumBase, Enum): """Current status of an approval""" Draft = 'Draft' Cancelled = 'Cancelled' Submitted = 'Submitted' Approved = 'Approved' Approving = 'Approving' Rejected = 'Rejected' Locked = 'Locked' Error = 'Error' class ISelectActionType(EnumBase, Enum): """Workflow actions that exist in ISelect""" GetStatus = 'GetStatus' ValidateRebalance = 'ValidateRebalance' ToggleConstraints = 'ToggleConstraints' GenerateSubmissionConfirm = 'GenerateSubmissionConfirm' GenerateSubmissionConfirmWithWaiver = 'GenerateSubmissionConfirmWithWaiver' SubmitRebalance = 'SubmitRebalance' CancelRebalance = 'CancelRebalance' SubmitDraft = 'SubmitDraft' CancelDraft = 'CancelDraft' AgeDraft = 'AgeDraft' RerunChecks = 'RerunChecks' TraderApproves = 'TraderApproves' TraderRejects = 'TraderRejects' ClientVerifierApproves = 'ClientVerifierApproves' ClientVerifierRejects = 'ClientVerifierRejects' ClearException = 'ClearException' ClientAcknowledges = 'ClientAcknowledges' AllowWaiver = 'AllowWaiver' DisableWaiver = 'DisableWaiver' class IndicesCurrency(EnumBase, Enum): """Currencies supported for Indices""" USD = 'USD' EUR = 'EUR' GBP = 'GBP' CAD = 'CAD' AUD = 'AUD' BRL = 'BRL' CHF = 'CHF' CNH = 'CNH' CNY = 'CNY' CNO = 'CNO' DKK = 'DKK' HKD = 'HKD' IDR = 'IDR' ILS = 'ILS' INR = 'INR' JPY = 'JPY' KRW = 'KRW' KWD = 'KWD' MXN = 'MXN' MYR = 'MYR' NOK = 'NOK' NZD = 'NZD' PHP = 'PHP' PLN = 'PLN' RUB = 'RUB' SAR = 'SAR' SEK = 'SEK' SGD = 'SGD' THB = 'THB' TRY = 'TRY' TWD = 'TWD' VND = 'VND' ZAR = 'ZAR' @dataclass class IndicesConstructRequestTypes(Base): pass @dataclass class IndicesConstructResponseTypes(Base): pass @dataclass class IndicesRebalanceActionTypes(Base): pass @dataclass class IndicesRebalanceInputTypes(Base): pass @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ApprovalComment(Base): timestamp: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) message: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CryptoBasketParameters(Base): initial_price: float = field(default=None, metadata=field_metadata) target_notional: float = field(default=None, metadata=field_metadata) divisor: float = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CustomBasketRiskParams(Base): risk_model: Optional[str] = field(default=None, metadata=field_metadata) fx_hedged: Optional[bool] = field(default=None, metadata=field_metadata) delete: Optional[bool] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CustomBasketsRebalanceAction(IndicesRebalanceActionTypes): comment: Optional[str] = field(default=None, metadata=field_metadata) action_type: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CustomBasketsResponse(IndicesConstructResponseTypes): status: Optional[str] = field(default=None, metadata=field_metadata) report_id: Optional[str] = field(default=None, metadata=field_metadata) asset_id: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ISelectActionRequest(IndicesRebalanceActionTypes): action_comment: str = field(default=None, metadata=field_metadata) trader_attestations: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) user_action: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ISelectError(Base): status_code: Optional[str] = field(default=None, metadata=config(field_name='Status_Code', exclude=exclude_none)) validation_messages: Optional[Tuple[str, ...]] = field(default=None, metadata=config(field_name='Validation_Messages', exclude=exclude_none)) date_validation_errors: Optional[Tuple[str, ...]] = field(default=None, metadata=config(field_name='Date_Validation_Errors', exclude=exclude_none)) index_parameters_errors: Optional[Tuple[str, ...]] = field(default=None, metadata=config(field_name='Index_Parameters_Errors', exclude=exclude_none)) underlyer_validation_errors: Optional[Tuple[str, ...]] = field(default=None, metadata=config(field_name='Underlyer_Validation_Errors', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ISelectIndexParameter(Base): name: Optional[str] = field(default=None, metadata=field_metadata) value: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ISelectIndexParameters(Base): name: Optional[str] = field(default=None, metadata=field_metadata) value: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ISelectSeries(Base): data: Optional[tuple] = field(default=None, metadata=field_metadata) identifier: Optional[str] = field(default=None, metadata=field_metadata) identifier_type: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class IndicesPositionInput(Base): asset_id: str = field(default=None, metadata=field_metadata) weight: float = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Link(Base): title: Optional[str] = field(default=None, metadata=field_metadata) source: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CreditCustomBasketPricingParameters(Base): quote_source: BasketValuationSource = field(default=None, metadata=field_metadata) quote_time: str = field(default='16:00:00', metadata=field_metadata) quote_side: Side = field(default='Mid', metadata=field_metadata) quoting_type: QuoteType = field(default=None, metadata=field_metadata) weighting_type: WeightingType = field(default=None, metadata=field_metadata) currency: Optional[Currency] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CustomBasketsPricingParameters(Base): currency: Optional[IndicesCurrency] = field(default=None, metadata=field_metadata) asset_data_set_id: Optional[str] = field(default=None, metadata=field_metadata) divisor: Optional[float] = field(default=None, metadata=field_metadata) fx_data_set_id: Optional[str] = field(default=None, metadata=field_metadata) fallback_date: Optional[str] = field(default=None, metadata=field_metadata) initial_price: Optional[float] = field(default=None, metadata=field_metadata) target_notional: Optional[float] = field(default=None, metadata=field_metadata) pricing_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) vendor: Optional[MarketDataVendor] = field(default=None, metadata=field_metadata) weighting_strategy: Optional[PositionSetWeightingStrategy] = field(default=None, metadata=field_metadata) reweight: Optional[bool] = field(default=False, metadata=field_metadata) asset_overwrite_data_set_id: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CustomBasketsRiskScheduleInputs(Base): risk_models: Optional[Tuple[CustomBasketRiskParams, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class GIRDomain(Base): document_links: Optional[Tuple[Link, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ISelectConstituentColumn(Base): id_: str = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) field_: str = field(default=None, metadata=config(field_name='field', exclude=exclude_none)) name: str = field(default=None, metadata=field_metadata) aggregator_string: Optional[str] = field(default=None, metadata=field_metadata) class_: Optional[str] = field(default=None, metadata=config(field_name='class', exclude=exclude_none)) filter_: Optional[str] = field(default=None, metadata=config(field_name='filter', exclude=exclude_none)) formatter_string: Optional[str] = field(default=None, metadata=field_metadata) ID: Optional[int] = field(default=None, metadata=field_metadata) max_width: Optional[int] = field(default=None, metadata=field_metadata) min_width: Optional[int] = field(default=None, metadata=field_metadata) precision: Optional[int] = field(default=None, metadata=field_metadata) sortable: Optional[int] = field(default=None, metadata=field_metadata) tooltip: Optional[str] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class IndicesPositionSet(Base): positions: Tuple[IndicesPositionInput, ...] = field(default=None, metadata=field_metadata) position_date: datetime.date = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CryptoBasketCreateInputs(IndicesConstructRequestTypes): ticker: str = field(default=None, metadata=field_metadata) name: str = field(default=None, metadata=field_metadata) position_set: Tuple[PositionPriceInput, ...] = field(default=None, metadata=field_metadata) asset_class: AssetClass = field(default='Digital Asset', metadata=field_metadata) parameters: CryptoBasketParameters = field(default=None, metadata=field_metadata) description: Optional[str] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CryptoBasketRebalanceInputs(IndicesRebalanceInputTypes): asset_class: AssetClass = field(default='Digital Asset', metadata=field_metadata) position_set: Tuple[PositionPriceInput, ...] = field(default=None, metadata=field_metadata) parameters: CryptoBasketParameters = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CustomBasketsBackcastInputs(Base): position_set: Tuple[IndicesPositionSet, ...] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DynamicConstructionResponse(IndicesConstructResponseTypes): action: Optional[object] = field(default=None, metadata=field_metadata) columns: Optional[Tuple[ISelectConstituentColumn, ...]] = field(default=None, metadata=field_metadata) constituent_validations: Optional[tuple] = field(default=None, metadata=field_metadata) date_validation_status: Optional[str] = field(default=None, metadata=field_metadata) types: Optional[tuple] = field(default=None, metadata=field_metadata) date_validations: Optional[tuple] = field(default=None, metadata=field_metadata) new_parameters: Optional[Tuple[ISelectNewParameter, ...]] = field(default=None, metadata=field_metadata) index_type: Optional[str] = field(default=None, metadata=field_metadata) index_parameter_definitions: Optional[tuple] = field(default=None, metadata=field_metadata) index_metadata: Optional[tuple] = field(default=None, metadata=field_metadata) index_parameters: Optional[tuple] = field(default=None, metadata=field_metadata) index_parameter_validation: Optional[tuple] = field(default=None, metadata=field_metadata) status: Optional[object] = field(default=None, metadata=field_metadata) valid: Optional[int] = field(default=None, metadata=field_metadata) validation_messages: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ISelectRebalance(Base): new_weights: Optional[Tuple[ISelectNewWeight, ...]] = field(default=None, metadata=field_metadata) rebalance_date: Optional[str] = field(default=None, metadata=field_metadata) observation_date: Optional[str] = field(default=None, metadata=field_metadata) new_parameters: Optional[Tuple[ISelectNewParameter, ...]] = field(default=None, metadata=field_metadata) index_parameters: Optional[Tuple[ISelectIndexParameters, ...]] = field(default=None, metadata=field_metadata) waiver_requested: Optional[bool] = field(default=None, metadata=field_metadata) unwind_missing_constituents: Optional[bool] = field(default=True, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ISelectRequest(IndicesRebalanceInputTypes): rebalance_date: str = field(default=None, metadata=field_metadata) use_new_rebalance_interface: bool = field(default=None, metadata=field_metadata) new_parameters: Optional[Tuple[ISelectNewParameter, ...]] = field(default=None, metadata=field_metadata) index_parameters: Optional[Tuple[ISelectIndexParameter, ...]] = field(default=None, metadata=field_metadata) new_weights: Optional[Tuple[ISelectNewWeight, ...]] = field(default=None, metadata=field_metadata) new_units: Optional[Tuple[ISelectNewUnit, ...]] = field(default=None, metadata=field_metadata) observation_date: Optional[str] = field(default=None, metadata=field_metadata) entry_type: Optional[str] = field(default=None, metadata=field_metadata) request_counter: Optional[int] = field(default=0, metadata=field_metadata) waiver_requested: Optional[bool] = field(default=None, metadata=field_metadata) presubmit: Optional[bool] = field(default=None, metadata=field_metadata) requester_id: Optional[str] = field(default=None, metadata=field_metadata) custom_basket_import: Optional[str] = field(default=None, metadata=field_metadata) action: Optional[str] = field(default=None, metadata=field_metadata) unwind_missing_constituents: Optional[bool] = field(default=True, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ISelectResponse(Base): action: Optional[ISelectActionType] = field(default=None, metadata=config(field_name='Action', exclude=exclude_none)) action_comment: Optional[str] = field(default=None, metadata=config(field_name='ActionComment', exclude=exclude_none)) asset_name: Optional[str] = field(default=None, metadata=field_metadata) asset_short_name: Optional[str] = field(default=None, metadata=field_metadata) available_action_confirms: Optional[Tuple[Tuple[str, ...], ...]] = field(default=None, metadata=field_metadata) available_actions: Optional[Tuple[Union[DictBase, ISelectActionType], ...]] = field(default=None, metadata=field_metadata) available_rebalance_dates: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) constituent_validations: Optional[tuple] = field(default=None, metadata=field_metadata) date_validation_status: Optional[str] = field(default=None, metadata=field_metadata) date_validations: Optional[tuple] = field(default=None, metadata=field_metadata) entry_mode: Optional[str] = field(default=None, metadata=field_metadata) entry_type: Optional[str] = field(default=None, metadata=field_metadata) internal_rebalance: Optional[int] = field(default=None, metadata=field_metadata) index_parameter_definitions: Optional[tuple] = field(default=None, metadata=field_metadata) index_parameters: Optional[tuple] = field(default=None, metadata=field_metadata) index_parameter_validation: Optional[tuple] = field(default=None, metadata=field_metadata) new_units: Optional[Tuple[ISelectNewUnit, ...]] = field(default=None, metadata=field_metadata) new_weights: Optional[Tuple[ISelectNewWeight, ...]] = field(default=None, metadata=field_metadata) notification_date: Optional[str] = field(default=None, metadata=field_metadata) rebalance_date: Optional[str] = field(default=None, metadata=field_metadata) rebalance_determination_date: Optional[str] = field(default=None, metadata=field_metadata) reb_determination_index_level: Optional[float] = field(default=None, metadata=field_metadata) request_counter: Optional[int] = field(default=None, metadata=field_metadata) series: Optional[ISelectSeries] = field(default=None, metadata=field_metadata) status: Optional[object] = field(default=None, metadata=field_metadata) submission_data: Optional[tuple] = field(default=None, metadata=field_metadata) submission_data_columns: Optional[Tuple[ISelectConstituentColumn, ...]] = field(default=None, metadata=field_metadata) submission_text: Optional[str] = field(default=None, metadata=field_metadata) valid: Optional[int] = field(default=None, metadata=field_metadata) validation_messages: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class IndicesDynamicConstructInputs(IndicesConstructRequestTypes): index_type: str = field(default=None, metadata=field_metadata) new_parameters: Tuple[ISelectNewParameter, ...] = field(default=None, metadata=field_metadata) index_parameters: Tuple[ISelectIndexParameters, ...] = field(default=None, metadata=field_metadata) index_metadata: Tuple[ISelectIndexParameters, ...] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PublishParameters(Base): publish_to_bloomberg: bool = field(default=False, metadata=field_metadata) include_price_history: bool = field(default=False, metadata=field_metadata) publish_to_reuters: Optional[bool] = field(default=False, metadata=field_metadata) publish_to_factset: Optional[bool] = field(default=False, metadata=field_metadata) historical_methodology: Optional[EqBasketHistoryMethodology] = field(default=None, metadata=field_metadata) backtest_parameters: Optional[EqBasketBacktestParameters] = field(default=None, metadata=field_metadata) bloomberg_publish_parameters: Optional[BloombergPublishParameters] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CreditCustomBasketCreateInputs(IndicesConstructRequestTypes): ticker: str = field(default=None, metadata=field_metadata) name: str = field(default=None, metadata=field_metadata) pricing_parameters: CreditCustomBasketPricingParameters = field(default=None, metadata=field_metadata) position_set: Tuple[PositionPriceInput, ...] = field(default=None, metadata=field_metadata) return_type: IndexCalculationType = field(default='Price Return', metadata=field_metadata) styles: Tuple[str, ...] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(default='Credit', metadata=field_metadata) description: Optional[str] = field(default=None, metadata=field_metadata) related_content: Optional[GIRDomain] = field(default=None, metadata=field_metadata) portfolio_id: Optional[str] = field(default=None, metadata=field_metadata) publish_parameters: Optional[PublishParameters] = field(default=None, metadata=field_metadata) index_notes: Optional[str] = field(default=None, metadata=field_metadata) flagship: Optional[bool] = field(default=False, metadata=field_metadata) on_behalf_of: Optional[str] = field(default=None, metadata=field_metadata) clone_parent_id: Optional[str] = field(default=None, metadata=field_metadata) hedge_id: Optional[str] = field(default=None, metadata=field_metadata) credit_basket_type: Optional[CreditBasketType] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CreditCustomBasketEditInputs(Base): name: str = field(default=None, metadata=field_metadata) description: str = field(default=None, metadata=field_metadata) publish_parameters: PublishParameters = field(default=None, metadata=field_metadata) pricing_parameters: CreditCustomBasketPricingParameters = field(default=None, metadata=field_metadata) flagship: bool = field(default=False, metadata=field_metadata) on_behalf_of: str = field(default=None, metadata=field_metadata) styles: Tuple[str, ...] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(default='Credit', metadata=field_metadata) related_content: Optional[GIRDomain] = field(default=None, metadata=field_metadata) portfolio_id: Optional[str] = field(default=None, metadata=field_metadata) return_type: Optional[IndexCalculationType] = field(default='Price Return', metadata=field_metadata) position_set: Optional[Tuple[PositionPriceInput, ...]] = field(default=None, metadata=field_metadata) index_notes: Optional[str] = field(default=None, metadata=field_metadata) clone_parent_id: Optional[str] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CreditCustomBasketRebalanceInputs(IndicesRebalanceInputTypes): asset_class: AssetClass = field(default='Credit', metadata=field_metadata) position_set: Tuple[PositionPriceInput, ...] = field(default=None, metadata=field_metadata) publish_parameters: Optional[PublishParameters] = field(default=None, metadata=field_metadata) pricing_parameters: Optional[CreditCustomBasketPricingParameters] = field(default=None, metadata=field_metadata) portfolio_id: Optional[str] = field(default=None, metadata=field_metadata) hedge_id: Optional[str] = field(default=None, metadata=field_metadata) save_as_draft: Optional[bool] = field(default=False, metadata=field_metadata) credit_basket_type: Optional[CreditBasketType] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class IndicesBackcastInputs(Base): parameters: CustomBasketsBackcastInputs = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CustomBasketsCreateInputs(IndicesConstructRequestTypes): ticker: str = field(default=None, metadata=field_metadata) name: str = field(default=None, metadata=field_metadata) pricing_parameters: CustomBasketsPricingParameters = field(default=None, metadata=field_metadata) position_set: Tuple[PositionPriceInput, ...] = field(default=None, metadata=field_metadata) return_type: str = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(default='Equity', metadata=field_metadata) description: Optional[str] = field(default=None, metadata=field_metadata) styles: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) related_content: Optional[GIRDomain] = field(default=None, metadata=field_metadata) portfolio_id: Optional[str] = field(default=None, metadata=field_metadata) hedge_id: Optional[str] = field(default=None, metadata=field_metadata) clone_parent_id: Optional[str] = field(default=None, metadata=field_metadata) cash_reinvestment_treatment: Optional[CashReinvestmentTreatment] = field(default=None, metadata=field_metadata) publish_parameters: Optional[PublishParameters] = field(default=None, metadata=field_metadata) index_notes: Optional[str] = field(default=None, metadata=field_metadata) flagship: Optional[bool] = field(default=None, metadata=field_metadata) on_behalf_of: Optional[str] = field(default=None, metadata=field_metadata) allow_limited_access_assets: Optional[bool] = field(default=False, metadata=field_metadata) allow_ca_restricted_assets: Optional[bool] = field(default=False, metadata=config(field_name='allowCARestrictedAssets', exclude=exclude_none)) vendor: Optional[str] = field(default=None, metadata=field_metadata) default_backcast: Optional[bool] = field(default=True, metadata=field_metadata) action_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) preferred_risk_model: Optional[str] = field(default=None, metadata=field_metadata) rebalance_calendar: Optional[EqBasketRebalanceCalendar] = field(default=None, metadata=field_metadata) benchmark: Optional[str] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CustomBasketsEditInputs(Base): asset_class: Optional[AssetClass] = field(default='Equity', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=field_metadata) description: Optional[str] = field(default=None, metadata=field_metadata) styles: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) related_content: Optional[GIRDomain] = field(default=None, metadata=field_metadata) publish_parameters: Optional[PublishParameters] = field(default=None, metadata=field_metadata) index_notes: Optional[str] = field(default=None, metadata=field_metadata) index_not_trading_reasons: Optional[IndexNotTradingReasons] = field(default=None, metadata=field_metadata) flagship: Optional[bool] = field(default=None, metadata=field_metadata) clone_parent_id: Optional[str] = field(default=None, metadata=field_metadata) hedge_id: Optional[str] = field(default=None, metadata=field_metadata) portfolio_id: Optional[str] = field(default=None, metadata=field_metadata) vendor: Optional[str] = field(default=None, metadata=field_metadata) action_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) preferred_risk_model: Optional[str] = field(default=None, metadata=field_metadata) rebalance_calendar: Optional[EqBasketRebalanceCalendar] = field(default=None, metadata=field_metadata) benchmark: Optional[str] = field(default=None, metadata=field_metadata) on_behalf_of: Optional[str] = field(default=None, metadata=field_metadata) cash_reinvestment_treatment: Optional[CashReinvestmentTreatment] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CustomBasketsRebalanceInputs(Base): asset_class: Optional[AssetClass] = field(default='Equity', metadata=field_metadata) position_set: Optional[Tuple[PositionPriceInput, ...]] = field(default=None, metadata=field_metadata) publish_parameters: Optional[PublishParameters] = field(default=None, metadata=field_metadata) pricing_parameters: Optional[CustomBasketsPricingParameters] = field(default=None, metadata=field_metadata) allow_limited_access_assets: Optional[bool] = field(default=False, metadata=field_metadata) allow_ca_restricted_assets: Optional[bool] = field(default=False, metadata=config(field_name='allowCARestrictedAssets', exclude=exclude_none)) allow_system_approval: Optional[bool] = field(default=False, metadata=field_metadata) clone_parent_id: Optional[str] = field(default=None, metadata=field_metadata) hedge_id: Optional[str] = field(default=None, metadata=field_metadata) portfolio_id: Optional[str] = field(default=None, metadata=field_metadata) save_as_draft: Optional[bool] = field(default=False, metadata=field_metadata) allow_in_position_rebalance: Optional[bool] = field(default=False, metadata=field_metadata) action_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) preferred_risk_model: Optional[str] = field(default=None, metadata=field_metadata) rebalance_calendar: Optional[EqBasketRebalanceCalendar] = field(default=None, metadata=field_metadata) benchmark: Optional[str] = field(default=None, metadata=field_metadata) on_behalf_of: Optional[str] = field(default=None, metadata=field_metadata) cash_reinvestment_treatment: Optional[CashReinvestmentTreatment] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class IndicesEditInputTypes(Base): parameters: Union[CreditCustomBasketEditInputs, CustomBasketsEditInputs] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class IndicesEditInputs(Base): parameters: CustomBasketsEditInputs = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class IndicesRebalanceInputs(IndicesRebalanceInputTypes): parameters: Union[CustomBasketsRebalanceInputs, ISelectRebalance] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ApprovalCustomBasketResponse(Base): id_: str = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) positions_to_rebalance: PositionSet = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) status: Optional[ApprovalStatus] = field(default=None, metadata=field_metadata) parent_id: Optional[str] = field(default=None, metadata=field_metadata) created_by_id: Optional[str] = field(default=None, metadata=field_metadata) created_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) approved_by_id: Optional[str] = field(default=None, metadata=field_metadata) approved_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) submitted_by_id: Optional[str] = field(default=None, metadata=field_metadata) submitted_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) comments: Optional[Tuple[ApprovalComment, ...]] = field(default=None, metadata=field_metadata) notifyees: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) action_type: Optional[object] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) ================================================ FILE: gs_quant/target/instrument.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.base import * from gs_quant.common import * import datetime from typing import Dict, Optional, Tuple, Union from dataclasses import dataclass, field from dataclasses_json import LetterCase, config, dataclass_json from gs_quant.instrument.core import Instrument @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqSyntheticDateInfo(Instrument): date: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Equity, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.SyntheticDateInfo, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqSyntheticOETTerms(Instrument): client_fee: Optional[str] = field(default=None, metadata=field_metadata) client_notice: Optional[str] = field(default=None, metadata=field_metadata) client_terms: Optional[str] = field(default=None, metadata=field_metadata) client_fee_at: Optional[float] = field(default=None, metadata=field_metadata) firm_fee: Optional[str] = field(default=None, metadata=field_metadata) firm_notice: Optional[str] = field(default=None, metadata=field_metadata) firm_terms: Optional[str] = field(default=None, metadata=field_metadata) firm_fee_at: Optional[float] = field(default=None, metadata=field_metadata) oet_type: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Equity, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.SyntheticOETTerms, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXCorrelationSwapLeg(Instrument): pair: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.CorrelationSwapLeg, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AssetRef(Instrument): buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) product_code: Optional[ProductCode] = field(default=None, metadata=field_metadata) size: Optional[float] = field(default=None, metadata=field_metadata) asset_id: Optional[str] = field(default=None, metadata=field_metadata) number_of_options: Optional[float] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Cross_Asset, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Any, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Bond(Instrument): buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) identifier: Optional[str] = field(default=None, metadata=field_metadata) identifier_type: Optional[UnderlierType] = field(default=None, metadata=field_metadata) size: Optional[float] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Cross_Asset, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Bond, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Cash(Instrument): currency: Optional[Currency] = field(default=None, metadata=field_metadata) payment_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Cash, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Cash, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CommodIndexSwap(Instrument): index: Optional[str] = field(default=None, metadata=field_metadata) quantity: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) quantity_unit: Optional[str] = field(default=None, metadata=field_metadata) settlement_days: Optional[int] = field(default=None, metadata=field_metadata) settlement_days_after: Optional[str] = field(default=None, metadata=field_metadata) settlement_days_type: Optional[str] = field(default=None, metadata=field_metadata) effective_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) termination_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) reset_frequency: Optional[str] = field(default=None, metadata=field_metadata) reset_day: Optional[int] = field(default=None, metadata=field_metadata) reset_frequency_multiplier: Optional[int] = field(default=None, metadata=field_metadata) index_name_source: Optional[str] = field(default=None, metadata=field_metadata) execution_method_name: Optional[str] = field(default=None, metadata=field_metadata) execution_method_start_time: Optional[str] = field(default=None, metadata=field_metadata) execution_method_end_time: Optional[str] = field(default=None, metadata=field_metadata) execution_method_location: Optional[str] = field(default=None, metadata=field_metadata) reinvestment_flag: Optional[YesNo] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Commod, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.IndexSwap, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CommodListedOptionPeriod(Instrument): contract: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) quantity: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Commod, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.ListedOptionPeriod, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CommodListedSwapPeriod(Instrument): contract: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) quantity: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Commod, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.ListedSwapPeriod, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CommodOTCOptionPeriod(Instrument): start: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) end: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) contract: Optional[str] = field(default=None, metadata=field_metadata) quantity: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Commod, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.OptionPeriod, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CommodOTCSwapLeg(Instrument): fixing_currency: Optional[CurrencyName] = field(default=None, metadata=field_metadata) leg_description: Optional[str] = field(default=None, metadata=field_metadata) contract: Optional[str] = field(default=None, metadata=field_metadata) fixing_currency_source: Optional[str] = field(default=None, metadata=field_metadata) underlier: Optional[str] = field(default=None, metadata=field_metadata) quantity_multiplier: Optional[int] = field(default=None, metadata=field_metadata) fixed_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Commod, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.SwapLeg, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CommodOTCSwapPeriod(Instrument): start: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) end: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) quantity: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Commod, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.SwapPeriod, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CommodSwapData(Instrument): commodity: Optional[str] = field(default=None, metadata=field_metadata) quantity: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) contract: Optional[str] = field(default=None, metadata=field_metadata) fixing_currency_source: Optional[str] = field(default=None, metadata=field_metadata) start: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) floating_type: Optional[str] = field(default=None, metadata=field_metadata) number_of_periods: Optional[int] = field(default=None, metadata=field_metadata) quantity_unit: Optional[str] = field(default=None, metadata=field_metadata) fixed_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) settlement: Optional[str] = field(default=None, metadata=field_metadata) fixing_currency: Optional[str] = field(default=None, metadata=field_metadata) fixed_price_unit: Optional[str] = field(default=None, metadata=field_metadata) commodity_reference_price: Optional[str] = field(default=None, metadata=field_metadata) end: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) quantity_period: Optional[Period] = field(default=None, metadata=field_metadata) strategy: Optional[str] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) buy_sells: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) underlier_short_name: Optional[str] = field(default=None, metadata=field_metadata) settlement_days: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) settlement_days_type: Optional[str] = field(default=None, metadata=field_metadata) settlement_days_from: Optional[str] = field(default=None, metadata=field_metadata) settlement_frequency: Optional[str] = field(default=None, metadata=field_metadata) currency_summary: Optional[CurrencyName] = field(default=None, metadata=field_metadata) native_quantity_unit: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Commod, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.SwapData, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqAsianOption(Instrument): underlier: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) option_style: Optional[OptionStyle] = field(default=None, metadata=field_metadata) number_of_options: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) valuation_time: Optional[str] = field(default=None, metadata=field_metadata) method_of_settlement: Optional[OptionSettlementMethod] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) holiday_calendar: Optional[str] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) notional: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) denominated: Optional[Currency] = field(default=None, metadata=field_metadata) payment_holidays: Optional[Currency] = field(default=None, metadata=field_metadata) composite_ccy: Optional[Currency] = field(default=None, metadata=field_metadata) accumstart_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) margin_id: Optional[str] = field(default=None, metadata=field_metadata) fx_data_source: Optional[str] = field(default=None, metadata=field_metadata) termination_date_bdc: Optional[str] = field(default=None, metadata=field_metadata) observation_date_bdc: Optional[str] = field(default=None, metadata=field_metadata) averaging_date_disruption: Optional[str] = field(default=None, metadata=field_metadata) strike_adjust_type: Optional[str] = field(default=None, metadata=field_metadata) use_dividend_dates: Optional[str] = field(default=None, metadata=field_metadata) include_special_div: Optional[str] = field(default=None, metadata=field_metadata) strike_weights_customized: Optional[str] = field(default=None, metadata=field_metadata) asian_performance: Optional[str] = field(default=None, metadata=field_metadata) use_contract_div: Optional[str] = field(default=None, metadata=field_metadata) use_net_divs: Optional[str] = field(default=None, metadata=field_metadata) asian_dates_customized: Optional[str] = field(default=None, metadata=field_metadata) expiry_settlement_days: Optional[str] = field(default=None, metadata=field_metadata) strike_fixing_dates_customized: Optional[str] = field(default=None, metadata=field_metadata) fixed_share: Optional[str] = field(default=None, metadata=field_metadata) asian_weights_customized: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Equity, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.AsianOption, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqAutoroll(Instrument): underlier: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) first_fixing_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) last_fixing_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fixing_frequency: Optional[str] = field(default=None, metadata=field_metadata) trigger_level: Optional[float] = field(default=None, metadata=field_metadata) buffer_level: Optional[float] = field(default=None, metadata=field_metadata) local_return_cap: Optional[float] = field(default=None, metadata=field_metadata) upside_leverage: Optional[float] = field(default=None, metadata=field_metadata) initial_fixing_override: Optional[float] = field(default=None, metadata=field_metadata) apply_trigger_level_shift: Optional[str] = field(default=None, metadata=field_metadata) trigger_level_shift: Optional[float] = field(default=None, metadata=field_metadata) notional: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) business_day_calendar: Optional[str] = field(default=None, metadata=field_metadata) payment_currency: Optional[Currency] = field(default=None, metadata=field_metadata) settlement_delay: Optional[str] = field(default=None, metadata=field_metadata) underlier_type: Optional[UnderlierType] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) premium: Optional[float] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Equity, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Autoroll, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqBarrier(Instrument): underlier: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) option_type: Optional[OptionType] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) barrier_start_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) barrier_end_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) barrier_level: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) knock_up_or_down: Optional[UpDown] = field(default=None, metadata=field_metadata) barrier_frequency: Optional[str] = field(default=None, metadata=field_metadata) knock_in_or_out: Optional[InOut] = field(default=None, metadata=field_metadata) number_of_options: Optional[float] = field(default=None, metadata=field_metadata) multiplier: Optional[float] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) currency: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Equity, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Barrier, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqBinary(Instrument): underlier: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) option_type: Optional[OptionType] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) currency: Optional[str] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Equity, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Binary, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqCliquet(Instrument): return_style: Optional[ReturnStyle] = field(default=ReturnStyle.Rate_of_Return, metadata=field_metadata) last_valuation_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) underlier_type: Optional[UnderlierType] = field(default=None, metadata=field_metadata) underlier: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) payment_frequency: Optional[PaymentFrequency] = field(default=PaymentFrequency.Maturity, metadata=field_metadata) global_cap: Optional[float] = field(default=1000000.0, metadata=field_metadata) first_valuation_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) currency: Optional[Currency] = field(default=None, metadata=field_metadata) global_floor: Optional[float] = field(default=-1000000.0, metadata=field_metadata) strike_price: Optional[float] = field(default=None, metadata=field_metadata) return_type: Optional[ReturnType] = field(default=ReturnType.Sum, metadata=field_metadata) valuation_period: Optional[str] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) premium: Optional[float] = field(default=0.0, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) number_of_options: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Equity, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Cliquet, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqContractDivOption(Instrument): underlier: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) option_type: Optional[OptionType] = field(default=None, metadata=field_metadata) option_style: Optional[OptionStyle] = field(default=None, metadata=field_metadata) number_of_options: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) exchange: Optional[str] = field(default=None, metadata=field_metadata) multiplier: Optional[float] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium: Optional[float] = field(default=0.0, metadata=field_metadata) premium_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) valuation_time: Optional[ValuationTime] = field(default=None, metadata=field_metadata) method_of_settlement: Optional[OptionSettlementMethod] = field(default=None, metadata=field_metadata) underlier_type: Optional[UnderlierType] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) trade_as: Optional[TradeAs] = field(default=None, metadata=field_metadata) future_contract: Optional[str] = field(default=None, metadata=field_metadata) underlier_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) expiry_settlement_days: Optional[str] = field(default=None, metadata=field_metadata) expiry_settle_date: Optional[str] = field(default=None, metadata=field_metadata) accum_start_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Equity, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.ContractDivOption, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) def scale_in_place(self, scaling: Optional[float] = None, check_resolved: bool = True): if scaling is None or scaling == 1: return if self.unresolved is None: if check_resolved: raise RuntimeError('Can only scale resolved instruments') if self.number_of_options is None or self.buy_sell is None: raise RuntimeError('Can only scale unresolved instruments with the buysell and primary size fields set') if any(a is not None and not isinstance(a, (int, float)) for a in [self.number_of_options]): raise RuntimeError('All specified size fields must be numeric') self.number_of_options *= abs(scaling) if scaling < 0: flip_dict = {BuySell.Buy: BuySell.Sell, BuySell.Sell: BuySell.Buy} self.buy_sell = flip_dict[self.buy_sell] return @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqConvertibleBond(Instrument): underlier: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) underlier_type: Optional[UnderlierType] = field(default=None, metadata=field_metadata) premium_settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) ref_currency: Optional[Currency] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) quantity: Optional[float] = field(default=None, metadata=field_metadata) isin: Optional[str] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) denominated: Optional[Currency] = field(default=None, metadata=field_metadata) multiplier: Optional[float] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Equity, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Convertible, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqDigital(Instrument): underlier: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) option_type: Optional[OptionType] = field(default=None, metadata=field_metadata) option_style: Optional[OptionStyle] = field(default=None, metadata=field_metadata) number_of_options: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) exchange: Optional[str] = field(default=None, metadata=field_metadata) multiplier: Optional[float] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium: Optional[float] = field(default=0.0, metadata=field_metadata) premium_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) valuation_time: Optional[ValuationTime] = field(default=None, metadata=field_metadata) method_of_settlement: Optional[OptionSettlementMethod] = field(default=None, metadata=field_metadata) underlier_type: Optional[UnderlierType] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) trade_as: Optional[TradeAs] = field(default=None, metadata=field_metadata) future_contract: Optional[str] = field(default=None, metadata=field_metadata) underlier_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) expiry_settlement_days: Optional[str] = field(default=None, metadata=field_metadata) expiry_settle_date: Optional[str] = field(default=None, metadata=field_metadata) binary_position: Optional[str] = field(default=None, metadata=field_metadata) binary_width_bps: Optional[float] = field(default=None, metadata=field_metadata) denominated: Optional[Currency] = field(default=None, metadata=field_metadata) payment_style: Optional[str] = field(default=None, metadata=field_metadata) strict_barrier: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Equity, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Digital, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqForward(Instrument): underlier: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) underlier_type: Optional[UnderlierType] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) forward_price: Optional[float] = field(default=None, metadata=field_metadata) number_of_shares: Optional[int] = field(default=1, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Equity, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Forward, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqForwardVarianceSwap(Instrument): underlier: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) underlier_type: Optional[UnderlierType] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) variance_cap: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) days_in_contract: Optional[float] = field(default=None, metadata=field_metadata) valuation_time: Optional[ValuationTime] = field(default=None, metadata=field_metadata) denominated: Optional[Currency] = field(default=None, metadata=field_metadata) fixing_schedule_dates: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) holiday_calendar: Optional[str] = field(default=None, metadata=field_metadata) initial_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) quantity: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) initial_spot: Optional[float] = field(default=None, metadata=field_metadata) expiry_settlement_days: Optional[str] = field(default=None, metadata=field_metadata) initial_spot_valuation_time: Optional[ValuationTime] = field(default=None, metadata=field_metadata) force_forward_tradable: Optional[bool] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Equity, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.ForwardVarianceSwap, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqFuture(Instrument): identifier: Optional[str] = field(default=None, metadata=field_metadata) identifier_type: Optional[UnderlierType] = field(default=None, metadata=field_metadata) underlier: Optional[str] = field(default=None, metadata=field_metadata) exchange: Optional[str] = field(default=None, metadata=field_metadata) premium_settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) strike_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) multiplier: Optional[float] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) quantity: Optional[float] = field(default=None, metadata=field_metadata) currency: Optional[Currency] = field(default=None, metadata=field_metadata) traded_price: Optional[float] = field(default=0.0, metadata=field_metadata) total_quantity: Optional[float] = field(default=None, metadata=field_metadata) trade_as: Optional[str] = field(default=None, metadata=field_metadata) denominated: Optional[Currency] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Equity, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Future, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqLockedLadder(Instrument): buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[str] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium: Optional[float] = field(default=0.0, metadata=field_metadata) underlier: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) currency: Optional[Currency] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) first_valuation_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) last_valuation_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) valuation_period: Optional[str] = field(default=None, metadata=field_metadata) strike_price: Optional[float] = field(default=None, metadata=field_metadata) payment_frequency: Optional[PaymentFrequency] = field(default=PaymentFrequency.Maturity, metadata=field_metadata) ladder_multipliers: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) weights: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Equity, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.LockedLadder, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqOption(Instrument): underlier: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) option_type: Optional[OptionType] = field(default=None, metadata=field_metadata) option_style: Optional[OptionStyle] = field(default=None, metadata=field_metadata) number_of_options: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) exchange: Optional[str] = field(default=None, metadata=field_metadata) multiplier: Optional[float] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium: Optional[float] = field(default=0.0, metadata=field_metadata) premium_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) valuation_time: Optional[ValuationTime] = field(default=None, metadata=field_metadata) method_of_settlement: Optional[OptionSettlementMethod] = field(default=None, metadata=field_metadata) underlier_type: Optional[UnderlierType] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) trade_as: Optional[TradeAs] = field(default=None, metadata=field_metadata) future_contract: Optional[str] = field(default=None, metadata=field_metadata) underlier_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) expiry_settlement_days: Optional[str] = field(default=None, metadata=field_metadata) expiry_settle_date: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Equity, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Option, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) def scale_in_place(self, scaling: Optional[float] = None, check_resolved: bool = True): if scaling is None or scaling == 1: return if self.unresolved is None: if check_resolved: raise RuntimeError('Can only scale resolved instruments') if self.number_of_options is None or self.buy_sell is None: raise RuntimeError('Can only scale unresolved instruments with the buysell and primary size fields set') if any(a is not None and not isinstance(a, (int, float)) for a in [self.number_of_options]): raise RuntimeError('All specified size fields must be numeric') self.number_of_options *= abs(scaling) if scaling < 0: flip_dict = {BuySell.Buy: BuySell.Sell, BuySell.Sell: BuySell.Buy} self.buy_sell = flip_dict[self.buy_sell] return @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqOptionLeg(Instrument): method_of_settlement: Optional[OptionSettlementMethod] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) option_style: Optional[OptionStyle] = field(default=None, metadata=field_metadata) multiplier: Optional[float] = field(default=None, metadata=field_metadata) number_of_options: Optional[float] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) valuation_time: Optional[ValuationTime] = field(default=None, metadata=field_metadata) option_type: Optional[OptionType] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium: Optional[float] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) trade_as: Optional[TradeAs] = field(default=None, metadata=field_metadata) exchange: Optional[str] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Equity, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.OptionLeg, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqQuantoOption(Instrument): underlier: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) option_type: Optional[OptionType] = field(default=None, metadata=field_metadata) number_of_options: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium: Optional[float] = field(default=0.0, metadata=field_metadata) valuation_time: Optional[ValuationTime] = field(default=None, metadata=field_metadata) underlier_type: Optional[UnderlierType] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) future_contract: Optional[str] = field(default=None, metadata=field_metadata) premium_settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) expiry_settlement_days: Optional[str] = field(default=None, metadata=field_metadata) denominated: Optional[Currency] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Equity, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.QuantoOption, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqStock(Instrument): identifier: Optional[str] = field(default=None, metadata=field_metadata) identifier_type: Optional[UnderlierType] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) traded_price: Optional[float] = field(default=0.0, metadata=field_metadata) currency: Optional[Currency] = field(default=None, metadata=field_metadata) quantity: Optional[float] = field(default=None, metadata=field_metadata) settlement_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Equity, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Single_Stock, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqSyntheticSchedule(Instrument): period: Optional[str] = field(default=None, metadata=field_metadata) delay: Optional[str] = field(default=None, metadata=field_metadata) date_rule: Optional[str] = field(default=None, metadata=field_metadata) dates: Optional[Tuple[EqSyntheticDateInfo, ...]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Equity, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.SyntheticSchedule, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqVarianceSwap(Instrument): underlier: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) underlier_type: Optional[UnderlierType] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) variance_cap: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) days_in_contract: Optional[float] = field(default=None, metadata=field_metadata) valuation_time: Optional[ValuationTime] = field(default=None, metadata=field_metadata) denominated: Optional[Currency] = field(default=None, metadata=field_metadata) fixing_schedule_dates: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) holiday_calendar: Optional[str] = field(default=None, metadata=field_metadata) initial_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) quantity: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) initial_spot: Optional[float] = field(default=None, metadata=field_metadata) expiry_settlement_days: Optional[str] = field(default=None, metadata=field_metadata) initial_spot_source_flag: Optional[float] = field(default=None, metadata=field_metadata) force_forward_tradable: Optional[bool] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Equity, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.VarianceSwap, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) def scale_in_place(self, scaling: Optional[float] = None, check_resolved: bool = True): if scaling is None or scaling == 1: return if self.unresolved is None: if check_resolved: raise RuntimeError('Can only scale resolved instruments') if self.quantity is None or self.buy_sell is None: raise RuntimeError('Can only scale unresolved instruments with the buysell and primary size fields set') if any(a is not None and not isinstance(a, (int, float)) for a in [self.quantity]): raise RuntimeError('All specified size fields must be numeric') self.quantity *= abs(scaling) if scaling < 0: flip_dict = {BuySell.Buy: BuySell.Sell, BuySell.Sell: BuySell.Buy} self.buy_sell = flip_dict[self.buy_sell] return @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqVolatilitySwap(Instrument): underlier: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) underlier_type: Optional[UnderlierType] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) variance_cap: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) days_in_contract: Optional[float] = field(default=None, metadata=field_metadata) valuation_time: Optional[ValuationTime] = field(default=None, metadata=field_metadata) denominated: Optional[Currency] = field(default=None, metadata=field_metadata) fixing_schedule_dates: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) holiday_calendar: Optional[str] = field(default=None, metadata=field_metadata) initial_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) quantity: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) initial_spot: Optional[float] = field(default=None, metadata=field_metadata) expiry_settlement_days: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Equity, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.VolatilitySwap, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXAccumulatorScheduleLeg(Instrument): strike: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) european_knock_in: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) knock_out_level: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) fixing_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) leverage_ratio: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.AccumulatorScheduleLeg, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXBinary(Instrument): pair: Optional[str] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) option_type: Optional[OptionType] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) expiration_time: Optional[str] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[str] = field(default=None, metadata=field_metadata) settlement_rate_option: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Binary, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) def scale_in_place(self, scaling: Optional[float] = None, check_resolved: bool = True): if scaling is None or scaling == 1: return if self.unresolved is None: if check_resolved: raise RuntimeError('Can only scale resolved instruments') if self.notional_amount is None or self.buy_sell is None: raise RuntimeError('Can only scale unresolved instruments with the buysell and primary size fields set') if any(a is not None and not isinstance(a, (int, float)) for a in [self.notional_amount]): raise RuntimeError('All specified size fields must be numeric') self.notional_amount *= abs(scaling) if scaling < 0: flip_dict = {BuySell.Buy: BuySell.Sell, BuySell.Sell: BuySell.Buy} self.buy_sell = flip_dict[self.buy_sell] return @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXBinaryDoubleKnockout(Instrument): pair: Optional[str] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) option_type: Optional[OptionType] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) settlement_rate_option: Optional[str] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) expiration_time: Optional[str] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[str] = field(default=None, metadata=field_metadata) knock_in_or_out: Optional[InOut] = field(default=None, metadata=field_metadata) lower_barrier_level: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) upper_barrier_level: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) knockout_convention: Optional[KnockoutConvention] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.BinaryDoubleKnockout, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXCorrelationSwap(Instrument): legs: Tuple[FXCorrelationSwapLeg, ...] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) strike_corr: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) first_fixing_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) last_fixing_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fixing_source: Optional[str] = field(default=None, metadata=field_metadata) fixing_frequency: Optional[str] = field(default=None, metadata=field_metadata) calculate_mean_return: Optional[float] = field(default=0.0, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=0, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.CorrelationSwap, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) def scale_in_place(self, scaling: Optional[float] = None, check_resolved: bool = True): if scaling is None or scaling == 1: return if self.unresolved is None: if check_resolved: raise RuntimeError('Can only scale resolved instruments') if self.notional_amount is None or self.buy_sell is None: raise RuntimeError('Can only scale unresolved instruments with the buysell and primary size fields set') if any(a is not None and not isinstance(a, (int, float)) for a in [self.notional_amount]): raise RuntimeError('All specified size fields must be numeric') self.notional_amount *= abs(scaling) if scaling < 0: flip_dict = {BuySell.Buy: BuySell.Sell, BuySell.Sell: BuySell.Buy} self.buy_sell = flip_dict[self.buy_sell] return @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXDoubleKnockout(Instrument): pair: Optional[str] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) option_type: Optional[OptionType] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) settlement_rate_option: Optional[str] = field(default=None, metadata=field_metadata) method_of_settlement: Optional[OptionSettlementMethod] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) expiration_time: Optional[str] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[str] = field(default=None, metadata=field_metadata) knock_in_or_out: Optional[InOut] = field(default=None, metadata=field_metadata) lower_barrier_level: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) upper_barrier_level: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) knockout_convention: Optional[KnockoutConvention] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.DoubleKnockout, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXDoubleOneTouch(Instrument): pair: Optional[str] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) settlement_rate_option: Optional[str] = field(default=None, metadata=field_metadata) method_of_settlement: Optional[OptionSettlementMethod] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) expiration_time: Optional[str] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[str] = field(default=None, metadata=field_metadata) lower_barrier_level: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) upper_barrier_level: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) payout_type: Optional[PayoutType] = field(default=None, metadata=field_metadata) knockout_convention: Optional[KnockoutConvention] = field(default=None, metadata=field_metadata) touch_or_no_touch: Optional[TouchNoTouch] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.DoubleTouch, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXDualDoubleKnockoutLeg(Instrument): pair: Optional[str] = field(default=None, metadata=field_metadata) lower_barrier_level: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) upper_barrier_level: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) knockout_convention: Optional[KnockoutConvention] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.DualDoubleKnockoutLeg, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXEuropeanKnockout(Instrument): pair: Optional[str] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) option_type: Optional[OptionType] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) expiration_date: Optional[str] = field(default=None, metadata=field_metadata) expiration_time: Optional[str] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[str] = field(default=None, metadata=field_metadata) settlement_rate_option: Optional[str] = field(default=None, metadata=field_metadata) method_of_settlement: Optional[OptionSettlementMethod] = field(default=None, metadata=field_metadata) barrier_level: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) knock_up_or_down: Optional[UpDown] = field(default=None, metadata=field_metadata) knock_in_or_out: Optional[InOut] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.EuropeanKnockout, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXForward(Instrument): pair: Optional[str] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) forward_rate: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) notional_amount_in_other_currency: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Forward, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) def scale_in_place(self, scaling: Optional[float] = None, check_resolved: bool = True): if scaling is None or scaling == 1: return if self.unresolved is None: if check_resolved: raise RuntimeError('Can only scale resolved instruments') if self.notional_amount is None or self.buy_sell is None: raise RuntimeError('Can only scale unresolved instruments with the buysell and primary size fields set') if any(a is not None and not isinstance(a, (int, float)) for a in [self.notional_amount, self.notional_amount_in_other_currency]): raise RuntimeError('All specified size fields must be numeric') self.notional_amount *= abs(scaling) if check_resolved or self.notional_amount_in_other_currency is not None: self.notional_amount_in_other_currency *= abs(scaling) if scaling < 0: flip_dict = {BuySell.Buy: BuySell.Sell, BuySell.Sell: BuySell.Buy} self.buy_sell = flip_dict[self.buy_sell] return @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXForwardVolatilityAgreement(Instrument): pair: Optional[str] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) strike_vol: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fixing_source: Optional[str] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) underlying_expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) underlying_settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) strike_type: Optional[FVAStrikeType] = field(default=None, metadata=field_metadata) underlying_payoff: Optional[FVAUnderlyingOptionType] = field(default=None, metadata=field_metadata) underlying_fixing_source: Optional[str] = field(default=None, metadata=field_metadata) underlying_premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[str] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=0, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.ForwardVolatilityAgreement, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXKnockout(Instrument): pair: Optional[str] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) option_type: Optional[OptionType] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) settlement_rate_option: Optional[str] = field(default=None, metadata=field_metadata) method_of_settlement: Optional[OptionSettlementMethod] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) expiration_time: Optional[str] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[str] = field(default=None, metadata=field_metadata) knock_in_or_out: Optional[InOut] = field(default=None, metadata=field_metadata) knock_up_or_down: Optional[UpDown] = field(default=None, metadata=field_metadata) barrier_level: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) knockout_convention: Optional[KnockoutConvention] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Knockout, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXMultiCrossBinaryLeg(Instrument): pair: Optional[str] = field(default=None, metadata=field_metadata) option_type: Optional[OptionType] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) fixing_source: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.MultiCrossBinaryLeg, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXMultiCrossDoubleBinaryLeg(Instrument): pair: Optional[str] = field(default=None, metadata=field_metadata) lower_barrier_level: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) upper_barrier_level: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) fixing_source: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.MultiCrossDoubleBinaryLeg, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXMultiCrossDoubleOneTouchLeg(Instrument): pair: Optional[str] = field(default=None, metadata=field_metadata) lower_barrier_level: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) upper_barrier_level: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) knockout_convention: Optional[KnockoutConvention] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.MultiCrossDoubleTouchLeg, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXOneTouch(Instrument): pair: Optional[str] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) settlement_rate_option: Optional[str] = field(default=None, metadata=field_metadata) method_of_settlement: Optional[OptionSettlementMethod] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) expiration_time: Optional[str] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[str] = field(default=None, metadata=field_metadata) knock_up_or_down: Optional[UpDown] = field(default=None, metadata=field_metadata) knockout_convention: Optional[KnockoutConvention] = field(default=None, metadata=field_metadata) touch_or_no_touch: Optional[TouchNoTouch] = field(default=None, metadata=field_metadata) payout_type: Optional[PayoutType] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.OneTouch, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) def scale_in_place(self, scaling: Optional[float] = None, check_resolved: bool = True): if scaling is None or scaling == 1: return if self.unresolved is None: if check_resolved: raise RuntimeError('Can only scale resolved instruments') if self.notional_amount is None or self.buy_sell is None: raise RuntimeError('Can only scale unresolved instruments with the buysell and primary size fields set') if any(a is not None and not isinstance(a, (int, float)) for a in [self.notional_amount]): raise RuntimeError('All specified size fields must be numeric') self.notional_amount *= abs(scaling) if scaling < 0: flip_dict = {BuySell.Buy: BuySell.Sell, BuySell.Sell: BuySell.Buy} self.buy_sell = flip_dict[self.buy_sell] return @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXOption(Instrument): pair: Optional[str] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) option_type: Optional[OptionType] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) notional_amount_in_other_currency: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) settlement_rate_option: Optional[str] = field(default=None, metadata=field_metadata) method_of_settlement: Optional[OptionSettlementMethod] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) expiration_time: Optional[str] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[str] = field(default=None, metadata=field_metadata) exercise_style: Optional[OptionExerciseStyle] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Option, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) def scale_in_place(self, scaling: Optional[float] = None, check_resolved: bool = True): if scaling is None or scaling == 1: return if self.unresolved is None: if check_resolved: raise RuntimeError('Can only scale resolved instruments') if self.notional_amount is None or self.buy_sell is None: raise RuntimeError('Can only scale unresolved instruments with the buysell and primary size fields set') if any(a is not None and not isinstance(a, (int, float)) for a in [self.notional_amount, self.notional_amount_in_other_currency]): raise RuntimeError('All specified size fields must be numeric') self.notional_amount *= abs(scaling) if check_resolved or self.notional_amount_in_other_currency is not None: self.notional_amount_in_other_currency *= abs(scaling) if scaling < 0: flip_dict = {BuySell.Buy: BuySell.Sell, BuySell.Sell: BuySell.Buy} self.buy_sell = flip_dict[self.buy_sell] return @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXOptionLeg(Instrument): buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) option_type: Optional[OptionType] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) notional_amount_in_other_currency: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[str] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) settlement_rate_option: Optional[str] = field(default=None, metadata=field_metadata) method_of_settlement: Optional[OptionSettlementMethod] = field(default=None, metadata=field_metadata) expiration_time: Optional[str] = field(default=None, metadata=field_metadata) exercise_style: Optional[OptionExerciseStyle] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.OptionLeg, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXPivotScheduleLeg(Instrument): notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) fixing_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) lower_leverage_ratio: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) lower_knock_in: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) lower_strike: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) pivot: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) upper_strike: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) upper_knock_in: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) upper_leverage_ratio: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.PivotScheduleLeg, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXShiftingBermForward(Instrument): pair: Optional[str] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) notional_amount_in_other_currency: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) window_start_date: Optional[str] = field(default=None, metadata=field_metadata) exercise_decision_freq: Optional[str] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.ShiftingBermForward, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXTarfScheduleLeg(Instrument): profit_strike: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) loss_strike: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) fixing_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) european_knock_in: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) leverage_ratio: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.TarfScheduleLeg, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXVarianceSwap(Instrument): pair: Optional[str] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) strike_vol: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) first_fixing_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) last_fixing_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fixing_source: Optional[str] = field(default=None, metadata=field_metadata) fixing_frequency: Optional[str] = field(default=None, metadata=field_metadata) annualization_factor: Optional[float] = field(default=None, metadata=field_metadata) calculate_mean_return: Optional[float] = field(default=0.0, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=0, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.VarianceSwap, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) def scale_in_place(self, scaling: Optional[float] = None, check_resolved: bool = True): if scaling is None or scaling == 1: return if self.unresolved is None: if check_resolved: raise RuntimeError('Can only scale resolved instruments') if self.notional_amount is None or self.buy_sell is None: raise RuntimeError('Can only scale unresolved instruments with the buysell and primary size fields set') if any(a is not None and not isinstance(a, (int, float)) for a in [self.notional_amount]): raise RuntimeError('All specified size fields must be numeric') self.notional_amount *= abs(scaling) if scaling < 0: flip_dict = {BuySell.Buy: BuySell.Sell, BuySell.Sell: BuySell.Buy} self.buy_sell = flip_dict[self.buy_sell] return @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXVolatilityKnockout(Instrument): pair: Optional[str] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) option_type: Optional[OptionType] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) first_fixing_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) settlement_rate_option: Optional[str] = field(default=None, metadata=field_metadata) method_of_settlement: Optional[OptionSettlementMethod] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) expiration_time: Optional[str] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[str] = field(default=None, metadata=field_metadata) knock_in_or_out: Optional[InOut] = field(default=None, metadata=field_metadata) barrier_level: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.VolatilityKnockout, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXVolatilitySwap(Instrument): pair: Optional[str] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) strike_vol: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) first_fixing_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) last_fixing_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fixing_source: Optional[str] = field(default=None, metadata=field_metadata) fixing_frequency: Optional[str] = field(default=None, metadata=field_metadata) annualization_factor: Optional[float] = field(default=None, metadata=field_metadata) calculate_mean_return: Optional[float] = field(default=0.0, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=0, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.VolatilitySwap, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) def scale_in_place(self, scaling: Optional[float] = None, check_resolved: bool = True): if scaling is None or scaling == 1: return if self.unresolved is None: if check_resolved: raise RuntimeError('Can only scale resolved instruments') if self.notional_amount is None or self.buy_sell is None: raise RuntimeError('Can only scale unresolved instruments with the buysell and primary size fields set') if any(a is not None and not isinstance(a, (int, float)) for a in [self.notional_amount]): raise RuntimeError('All specified size fields must be numeric') self.notional_amount *= abs(scaling) if scaling < 0: flip_dict = {BuySell.Buy: BuySell.Sell, BuySell.Sell: BuySell.Buy} self.buy_sell = flip_dict[self.buy_sell] return @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXWindowDoubleKnockout(Instrument): pair: Optional[str] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) option_type: Optional[OptionType] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) settlement_rate_option: Optional[str] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) expiration_time: Optional[str] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[str] = field(default=None, metadata=field_metadata) knock_in_or_out: Optional[InOut] = field(default=None, metadata=field_metadata) lower_barrier_level: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) upper_barrier_level: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) knockout_convention: Optional[KnockoutConvention] = field(default=None, metadata=field_metadata) upper_barrier_start_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) upper_barrier_end_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) lower_barrier_start_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) lower_barrier_end_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.WindowDoubleKnockout, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXWindowKnockout(Instrument): pair: Optional[str] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) option_type: Optional[OptionType] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) settlement_rate_option: Optional[str] = field(default=None, metadata=field_metadata) method_of_settlement: Optional[OptionSettlementMethod] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) expiration_time: Optional[str] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[str] = field(default=None, metadata=field_metadata) knock_in_or_out: Optional[InOut] = field(default=None, metadata=field_metadata) knock_up_or_down: Optional[UpDown] = field(default=None, metadata=field_metadata) barrier_level: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) knockout_convention: Optional[KnockoutConvention] = field(default=None, metadata=field_metadata) barrier_start_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) barrier_end_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.WindowKnockout, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXWorstOfKOLeg(Instrument): pair: Optional[str] = field(default=None, metadata=field_metadata) option_type: Optional[OptionType] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) knock_up_or_down: Optional[UpDown] = field(default=None, metadata=field_metadata) knock_in_or_out: Optional[InOut] = field(default=None, metadata=field_metadata) barrier_level: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) knockout_convention: Optional[KnockoutConvention] = field(default=None, metadata=field_metadata) settlement_rate_option: Optional[str] = field(default=None, metadata=field_metadata) expiration_time: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.WorstOfKOLeg, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXWorstOfLeg(Instrument): pair: Optional[str] = field(default=None, metadata=field_metadata) notional_scale_factor: Optional[float] = field(default=None, metadata=field_metadata) option_type: Optional[OptionType] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) settlement_rate_option: Optional[str] = field(default=None, metadata=field_metadata) method_of_settlement: Optional[OptionSettlementMethod] = field(default=None, metadata=field_metadata) expiration_time: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.WorstOfLeg, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Forward(Instrument): currency: Optional[Currency] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Cash, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Forward, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class IRBondFuture(Instrument): identifier: Optional[str] = field(default=None, metadata=field_metadata) identifier_type: Optional[UnderlierType] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) underlier: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) currency: Optional[Currency] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) exchange: Optional[str] = field(default=None, metadata=field_metadata) traded_price: Optional[float] = field(default=None, metadata=field_metadata) trade_settle: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) description: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Rates, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.BondFuture, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class IRCap(Instrument): termination_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) effective_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) floating_rate_option: Optional[str] = field(default=None, metadata=field_metadata) floating_rate_designated_maturity: Optional[str] = field(default=None, metadata=field_metadata) floating_rate_frequency: Optional[str] = field(default=None, metadata=field_metadata) floating_rate_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) floating_rate_business_day_convention: Optional[BusinessDayConvention] = field(default=None, metadata=field_metadata) cap_rate: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fee: Optional[float] = field(default=0.0, metadata=field_metadata) fee_currency: Optional[Currency] = field(default=None, metadata=field_metadata) fee_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Rates, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Cap, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class IRCapFloor(Instrument): cap_floor: Optional[CapOrFloor] = field(default=None, metadata=field_metadata) termination_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) effective_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) floating_rate_option: Optional[str] = field(default=None, metadata=field_metadata) floating_rate_designated_maturity: Optional[str] = field(default=None, metadata=field_metadata) floating_rate_frequency: Optional[str] = field(default=None, metadata=field_metadata) floating_rate_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) floating_rate_business_day_convention: Optional[BusinessDayConvention] = field(default=None, metadata=field_metadata) strike: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fee: Optional[float] = field(default=None, metadata=field_metadata) fee_currency: Optional[Currency] = field(default=None, metadata=field_metadata) fee_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Rates, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.CapOrFloor, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class IRFloor(Instrument): termination_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) effective_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) floating_rate_option: Optional[str] = field(default=None, metadata=field_metadata) floating_rate_designated_maturity: Optional[str] = field(default=None, metadata=field_metadata) floating_rate_frequency: Optional[str] = field(default=None, metadata=field_metadata) floating_rate_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) floating_rate_business_day_convention: Optional[BusinessDayConvention] = field(default=None, metadata=field_metadata) floor_rate: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fee: Optional[float] = field(default=0.0, metadata=field_metadata) fee_currency: Optional[Currency] = field(default=None, metadata=field_metadata) fee_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Rates, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Floor, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class InflationSwap(Instrument): pay_or_receive: Optional[PayReceive] = field(default=None, metadata=field_metadata) termination_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) effective_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) index: Optional[str] = field(default=None, metadata=field_metadata) floating_rate_business_day_convention: Optional[BusinessDayConvention] = field(default=None, metadata=field_metadata) fixed_rate: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) fixed_rate_business_day_convention: Optional[BusinessDayConvention] = field(default=None, metadata=field_metadata) fee: Optional[float] = field(default=0.0, metadata=field_metadata) base_cpi: Optional[float] = field(default=None, metadata=field_metadata) clearing_house: Optional[SwapClearingHouse] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Rates, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.InflationSwap, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) def scale_in_place(self, scaling: Optional[float] = None, check_resolved: bool = True): if scaling is None or scaling == 1: return if self.unresolved is None: if check_resolved: raise RuntimeError('Can only scale resolved instruments') if self.notional_amount is None or self.pay_or_receive is None: raise RuntimeError('Can only scale unresolved instruments with the buysell and primary size fields set') if any(a is not None and not isinstance(a, (int, float)) for a in [self.notional_amount, self.fee]): raise RuntimeError('All specified size fields must be numeric') self.notional_amount *= abs(scaling) if scaling < 0: flip_dict = {PayReceive.Pay: PayReceive.Receive, PayReceive.Receive: PayReceive.Pay} self.pay_or_receive = flip_dict[self.pay_or_receive] if check_resolved or self.fee is not None: self.fee *= -1 return @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class InstrumentsRepoIRDiscreteLock(Instrument): buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) underlier: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) underlier_type: Optional[UnderlierType] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) currency: Optional[Currency] = field(default=None, metadata=field_metadata) spot_clean_price: Optional[float] = field(default=None, metadata=field_metadata) settlement: Optional[str] = field(default=None, metadata=field_metadata) repo_rate: Optional[float] = field(default=None, metadata=field_metadata) forward_clean_price: Optional[float] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Repo, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Bond_Forward, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MetalForward(Instrument): pair: Optional[str] = field(default=None, metadata=field_metadata) delivery_location: Optional[str] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) quantity: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) quantity_unit: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Commod, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Forward, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CDIndex(Instrument): buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) clearinghouse: Optional[SwapClearingHouse] = field(default=None, metadata=field_metadata) effective_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) first_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) first_roll_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fee: Optional[float] = field(default=0.0, metadata=field_metadata) fee_currency: Optional[Currency] = field(default=None, metadata=field_metadata) fee_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) termination_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) index_family: Optional[str] = field(default=None, metadata=field_metadata) index_for_basis: Optional[str] = field(default=None, metadata=field_metadata) index_series: Optional[float] = field(default=None, metadata=field_metadata) index_version: Optional[float] = field(default=None, metadata=field_metadata) isda_docs: Optional[str] = field(default='2014', metadata=config(field_name='ISDADocs', exclude=exclude_none)) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Credit, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Index, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CDIndexOption(Instrument): automatic_exercise: Optional[float] = field(default=0.0, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) clearinghouse: Optional[SwapClearingHouse] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) earliest_exercise_time: Optional[str] = field(default=None, metadata=field_metadata) earliest_exercise_time_centre: Optional[str] = field(default=None, metadata=field_metadata) effective_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) exercise_date_business_day_convention: Optional[BusinessDayConvention] = field(default='Following', metadata=field_metadata) exercise_holidays: Optional[str] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) expiration_time: Optional[str] = field(default=None, metadata=field_metadata) expiration_time_centre: Optional[str] = field(default=None, metadata=field_metadata) premium: Optional[float] = field(default=0.0, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) first_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) first_roll_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) index_family: Optional[str] = field(default=None, metadata=field_metadata) index_for_basis: Optional[str] = field(default=None, metadata=field_metadata) index_series: Optional[float] = field(default=None, metadata=field_metadata) index_version: Optional[float] = field(default=None, metadata=field_metadata) isda_docs: Optional[str] = field(default='2014', metadata=config(field_name='ISDADocs', exclude=exclude_none)) termination_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) option_type: Optional[CDOptionType] = field(default=None, metadata=field_metadata) method_of_settlement: Optional[OptionSettlementMethod] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) fixed_rate: Optional[float] = field(default=None, metadata=field_metadata) strike: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) strike_type: Optional[str] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Credit, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.IndexOption, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CommodListedOption(Instrument): buy_sells: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) venue: Optional[str] = field(default=None, metadata=field_metadata) underlier: Optional[str] = field(default=None, metadata=field_metadata) start: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) end: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) quantity: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) native_quantity_unit: Optional[str] = field(default=None, metadata=field_metadata) premium: Optional[str] = field(default=None, metadata=field_metadata) premium_unit: Optional[str] = field(default=None, metadata=field_metadata) strategy: Optional[str] = field(default=None, metadata=field_metadata) strikes: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) quantity_period: Optional[str] = field(default=None, metadata=field_metadata) option_types: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) number_of_periods: Optional[int] = field(default=None, metadata=field_metadata) period_details: Optional[Tuple[CommodListedOptionPeriod, ...]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Commod, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.ListedOption, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CommodListedSwap(Instrument): buy_sells: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) venue: Optional[str] = field(default=None, metadata=field_metadata) commodity: Optional[str] = field(default=None, metadata=field_metadata) start: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) end: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) quantity: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) native_quantity_unit: Optional[str] = field(default=None, metadata=field_metadata) fixing_currency: Optional[str] = field(default=None, metadata=field_metadata) strategy: Optional[str] = field(default=None, metadata=field_metadata) number_of_periods: Optional[int] = field(default=None, metadata=field_metadata) period_details: Optional[Tuple[CommodListedSwapPeriod, ...]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Commod, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.ListedSwap, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CommodOTCOptionLeg(Instrument): option_type: Optional[OptionType] = field(default=None, metadata=field_metadata) fixing_currency: Optional[CurrencyName] = field(default=None, metadata=field_metadata) premium: Optional[CommodPrice] = field(default=None, metadata=field_metadata) leg_description: Optional[str] = field(default=None, metadata=field_metadata) contract: Optional[str] = field(default=None, metadata=field_metadata) fixing_currency_source: Optional[str] = field(default=None, metadata=field_metadata) strike: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) underlier: Optional[str] = field(default=None, metadata=field_metadata) premium_settlement: Optional[str] = field(default=None, metadata=field_metadata) quantity_multiplier: Optional[int] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Commod, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.OptionLeg, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CommodOTCSwap(Instrument): quantity: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) legs: Optional[Tuple[CommodOTCSwapLeg, ...]] = field(default=None, metadata=field_metadata) start: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) end: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) number_of_periods: Optional[int] = field(default=None, metadata=field_metadata) quantity_unit: Optional[str] = field(default=None, metadata=field_metadata) quantity_period: Optional[Period] = field(default=None, metadata=field_metadata) strategy: Optional[str] = field(default=None, metadata=field_metadata) settlement: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Commod, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.SwapStrategy, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CommodOption(Instrument): commodity: Optional[str] = field(default=None, metadata=field_metadata) number_of_periods: Optional[int] = field(default=None, metadata=field_metadata) quantity_unit: Optional[str] = field(default=None, metadata=field_metadata) currency_summary: Optional[CurrencyName] = field(default=None, metadata=field_metadata) option_types: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) option_type: Optional[str] = field(default=None, metadata=field_metadata) strike_unit: Optional[str] = field(default=None, metadata=field_metadata) strikes: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) end: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) buy_sells: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) underlier_short_name: Optional[str] = field(default=None, metadata=field_metadata) settlement: Optional[str] = field(default=None, metadata=field_metadata) settlement_frequency: Optional[str] = field(default=None, metadata=field_metadata) settlement_days: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) settlement_days_type: Optional[str] = field(default=None, metadata=field_metadata) settlement_days_from: Optional[str] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) strike_currency: Optional[CurrencyName] = field(default=None, metadata=field_metadata) quantity: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) contract: Optional[str] = field(default=None, metadata=field_metadata) fixing_currency_source: Optional[str] = field(default=None, metadata=field_metadata) strike: Optional[str] = field(default=None, metadata=field_metadata) start: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) floating_type: Optional[str] = field(default=None, metadata=field_metadata) fixing_currency: Optional[str] = field(default=None, metadata=field_metadata) commodity_reference_price: Optional[str] = field(default=None, metadata=field_metadata) quantity_period: Optional[str] = field(default=None, metadata=field_metadata) strategy: Optional[str] = field(default=None, metadata=field_metadata) premium: Optional[str] = field(default=None, metadata=field_metadata) premium_unit: Optional[str] = field(default=None, metadata=field_metadata) premium_type: Optional[str] = field(default=None, metadata=field_metadata) period_details: Optional[Tuple[CommodOTCOptionPeriod, ...]] = field(default=None, metadata=field_metadata) native_quantity_unit: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Commod, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Option, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CommodSwap(Instrument): commodity: Optional[str] = field(default=None, metadata=field_metadata) quantity: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) contract: Optional[str] = field(default=None, metadata=field_metadata) fixing_currency_source: Optional[str] = field(default=None, metadata=field_metadata) start: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) floating_type: Optional[str] = field(default=None, metadata=field_metadata) number_of_periods: Optional[int] = field(default=None, metadata=field_metadata) quantity_unit: Optional[str] = field(default=None, metadata=field_metadata) fixed_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) settlement: Optional[str] = field(default=None, metadata=field_metadata) fixing_currency: Optional[str] = field(default=None, metadata=field_metadata) fixed_price_unit: Optional[str] = field(default=None, metadata=field_metadata) commodity_reference_price: Optional[str] = field(default=None, metadata=field_metadata) end: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) quantity_period: Optional[Period] = field(default=None, metadata=field_metadata) strategy: Optional[str] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) buy_sells: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) underlier_short_name: Optional[str] = field(default=None, metadata=field_metadata) settlement_days: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) settlement_days_type: Optional[str] = field(default=None, metadata=field_metadata) settlement_days_from: Optional[str] = field(default=None, metadata=field_metadata) settlement_frequency: Optional[str] = field(default=None, metadata=field_metadata) currency_summary: Optional[CurrencyName] = field(default=None, metadata=field_metadata) period_details: Optional[Tuple[CommodOTCSwapPeriod, ...]] = field(default=None, metadata=field_metadata) native_quantity_unit: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Commod, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Swap, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CommodVolVarSwap(Instrument): notional_currency: Optional[CurrencyName] = field(default=None, metadata=field_metadata) notional: Optional[float] = field(default=1.0, metadata=field_metadata) floating_rate_is_capped: Optional[str] = field(default=None, metadata=field_metadata) end_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) margined: Optional[float] = field(default=None, metadata=field_metadata) market_disruption_agreement: Optional[str] = field(default=None, metadata=field_metadata) mean_rule: Optional[CommodMeanRule] = field(default=None, metadata=field_metadata) divisor: Optional[str] = field(default=None, metadata=field_metadata) fixed_mean: Optional[float] = field(default=None, metadata=field_metadata) first_fixing: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) floating_rate_cap: Optional[float] = field(default=None, metadata=field_metadata) fx_fixing_source: Optional[str] = field(default=None, metadata=field_metadata) annualization_factor: Optional[float] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) contract: Optional[str] = field(default=None, metadata=field_metadata) strike: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) swap_type: Optional[str] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fixing_currency: Optional[CurrencyName] = field(default=None, metadata=field_metadata) asset_fixing_source: Optional[str] = field(default=None, metadata=field_metadata) sampling_frequency: Optional[str] = field(default=None, metadata=field_metadata) variance_convention: Optional[VarianceConvention] = field(default=None, metadata=field_metadata) asset: Optional[str] = field(default=None, metadata=field_metadata) start_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Commod, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.VolVarSwap, metadata=config(field_name='type', exclude=exclude_none)) extra_sampling_calendars: Optional[str] = field(init=False, default='--Blank--', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqOptionStrategy(Instrument): underlier: Union[float, str] = field(default=None, metadata=field_metadata) strategy: str = field(default=None, metadata=field_metadata) legs: Tuple[EqOptionLeg, ...] = field(default=None, metadata=field_metadata) underlier_type: Optional[UnderlierType] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) option_type: Optional[OptionType] = field(default=None, metadata=field_metadata) option_style: Optional[OptionStyle] = field(default=None, metadata=field_metadata) number_of_options: Optional[float] = field(default=None, metadata=field_metadata) multiplier: Optional[float] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium: Optional[float] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) valuation_time: Optional[ValuationTime] = field(default=None, metadata=field_metadata) method_of_settlement: Optional[OptionSettlementMethod] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) exchange: Optional[str] = field(default=None, metadata=field_metadata) trade_as: Optional[TradeAs] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Equity, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.OptionStrategy, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqSyntheticLeg(Instrument): schedule_type: Optional[str] = field(default=None, metadata=field_metadata) first_roll_date: Optional[str] = field(default=None, metadata=field_metadata) first_roll_date_day_of_month: Optional[float] = field(default=None, metadata=field_metadata) payment_schedule: Optional[Tuple[EqSyntheticSchedule, ...]] = field(default=None, metadata=field_metadata) reset_schedule: Optional[Tuple[EqSyntheticSchedule, ...]] = field(default=None, metadata=field_metadata) valuation_schedule: Optional[Tuple[EqSyntheticSchedule, ...]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Equity, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.SyntheticLeg, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FRA(Instrument): buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) clearing_house: Optional[SwapClearingHouse] = field(default=None, metadata=field_metadata) clearing_legally_binding: Optional[float] = field(default=None, metadata=field_metadata) day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) fee: Optional[float] = field(default=0.0, metadata=field_metadata) fee_currency: Optional[Currency] = field(default=None, metadata=field_metadata) fee_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fixed_rate: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) frequency: Optional[str] = field(default=None, metadata=field_metadata) calendar: Optional[str] = field(default=None, metadata=field_metadata) rate_option: Optional[str] = field(default=None, metadata=field_metadata) maturity: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) payment_delay: Optional[str] = field(default=None, metadata=field_metadata) roll_convention: Optional[str] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) spread: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) effective_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Rates, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.FRA, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXAccumulator(Instrument): pair: Optional[str] = field(default=None, metadata=field_metadata) new_or_unwind: Optional[NewOrUnwind] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) leverage_ratio: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) european_knock_in: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) strike: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) knock_out_level: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) accum_or_decum: Optional[AccumOrDecum] = field(default=None, metadata=field_metadata) accumulator_type: Optional[AccumulatorType] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[str] = field(default=None, metadata=field_metadata) first_fixing_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) coupon_frequency: Optional[str] = field(default=None, metadata=field_metadata) guaranteed_coupons: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) number_of_expiry: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) fixing_rate_option: Optional[str] = field(default=None, metadata=field_metadata) method_of_settlement: Optional[OptionSettlementMethod] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) settlement_rate_option: Optional[str] = field(default=None, metadata=field_metadata) schedules: Optional[Tuple[FXAccumulatorScheduleLeg, ...]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Accumulator, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXDualDoubleKnockout(Instrument): legs: Tuple[FXDualDoubleKnockoutLeg, ...] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) option_type: Optional[OptionType] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) settlement_rate_option: Optional[str] = field(default=None, metadata=field_metadata) method_of_settlement: Optional[OptionSettlementMethod] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) expiration_time: Optional[str] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.DualDoubleKnockout, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXMultiCrossBinary(Instrument): legs: Tuple[FXMultiCrossBinaryLeg, ...] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) expiration_time: Optional[str] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.MultiCrossBinary, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXMultiCrossDoubleBinary(Instrument): legs: Tuple[FXMultiCrossDoubleBinaryLeg, ...] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) expiration_time: Optional[str] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.MultiCrossDoubleBinary, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXMultiCrossDoubleOneTouch(Instrument): legs: Tuple[FXMultiCrossDoubleOneTouchLeg, ...] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) method_of_settlement: Optional[OptionSettlementMethod] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) expiration_time: Optional[str] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[str] = field(default=None, metadata=field_metadata) payout_type: Optional[PayoutType] = field(default=None, metadata=field_metadata) touch_or_no_touch: Optional[TouchNoTouch] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.MultiCrossDoubleTouch, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXOptionStrategy(Instrument): pair: Optional[str] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) strategy_name: Optional[str] = field(default=None, metadata=field_metadata) legs: Optional[Tuple[FXOptionLeg, ...]] = field(default=None, metadata=field_metadata) option_type: Optional[OptionType] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) notional_amount_in_other_currency: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) strike_price: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) settlement_rate_option: Optional[str] = field(default=None, metadata=field_metadata) method_of_settlement: Optional[OptionSettlementMethod] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) expiration_time: Optional[str] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[str] = field(default=None, metadata=field_metadata) exercise_style: Optional[OptionExerciseStyle] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.OptionStrategy, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXPivot(Instrument): pair: Optional[str] = field(default=None, metadata=field_metadata) new_or_unwind: Optional[NewOrUnwind] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) lower_leverage_ratio: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) lower_knock_in: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) lower_strike: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) pivot: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) upper_strike: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) upper_knock_in: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) upper_leverage_ratio: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) fixing_rate_option: Optional[str] = field(default=None, metadata=field_metadata) method_of_settlement: Optional[OptionSettlementMethod] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[str] = field(default=None, metadata=field_metadata) number_of_expiry: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) coupon_frequency: Optional[str] = field(default=None, metadata=field_metadata) first_fixing_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) target_type: Optional[TargetType] = field(default=None, metadata=field_metadata) target: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) schedules: Optional[Tuple[FXPivotScheduleLeg, ...]] = field(default=None, metadata=field_metadata) target_adj_notional_or_strike: Optional[NotionalOrStrike] = field(default=None, metadata=field_metadata) payment_on_hitting_target: Optional[TargetPaymentType] = field(default=None, metadata=field_metadata) settlement_rate_option: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Pivot, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXTarf(Instrument): pair: Optional[str] = field(default=None, metadata=field_metadata) new_or_unwind: Optional[NewOrUnwind] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) profit_strike: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) loss_strike: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) fixing_rate_option: Optional[str] = field(default=None, metadata=field_metadata) method_of_settlement: Optional[OptionSettlementMethod] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[str] = field(default=None, metadata=field_metadata) long_or_short: Optional[LongShort] = field(default=None, metadata=field_metadata) european_knock_in: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) number_of_expiry: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) coupon_frequency: Optional[str] = field(default=None, metadata=field_metadata) first_fixing_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) leverage_ratio: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) target_type: Optional[TargetType] = field(default=None, metadata=field_metadata) target: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) schedules: Optional[Tuple[FXTarfScheduleLeg, ...]] = field(default=None, metadata=field_metadata) target_adj_notional_or_strike: Optional[NotionalOrStrike] = field(default=None, metadata=field_metadata) payment_on_hitting_target: Optional[TargetPaymentType] = field(default=None, metadata=field_metadata) settlement_rate_option: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Tarf, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXWorstOf(Instrument): legs: Tuple[FXWorstOfLeg, ...] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) best_or_worst: Optional[BestWorst] = field(default=None, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.WorstOf, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FXWorstOfKO(Instrument): legs: Tuple[FXWorstOfKOLeg, ...] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) best_or_worst: Optional[BestWorst] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) method_of_settlement: Optional[OptionSettlementMethod] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_currency: Optional[Currency] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.FX, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.WorstOfKO, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class IRAssetSwapFxdFlt(Instrument): asw_type: Optional[AswType] = field(default=None, metadata=field_metadata) clearing_house: Optional[SwapClearingHouse] = field(default=None, metadata=field_metadata) fee: Optional[float] = field(default=None, metadata=field_metadata) fee_currency: Optional[Currency] = field(default=None, metadata=field_metadata) fee_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fixed_rate_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) fixed_first_stub: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fixed_rate_frequency: Optional[str] = field(default=None, metadata=field_metadata) fixed_holidays: Optional[str] = field(default=None, metadata=field_metadata) fixed_rate_business_day_convention: Optional[BusinessDayConvention] = field(default=None, metadata=field_metadata) fixed_rate: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) floating_rate_currency: Optional[Currency] = field(default=None, metadata=field_metadata) floating_rate_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) floating_first_stub: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) floating_rate_frequency: Optional[str] = field(default=None, metadata=field_metadata) floating_rate_fx: Optional[float] = field(default=None, metadata=field_metadata) floating_holidays: Optional[str] = field(default=None, metadata=field_metadata) floating_maturity: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) floating_rate_business_day_convention: Optional[BusinessDayConvention] = field(default=None, metadata=field_metadata) identifier: Optional[str] = field(default=None, metadata=field_metadata) identifier_type: Optional[str] = field(default=None, metadata=field_metadata) floating_rate_option: Optional[str] = field(default=None, metadata=field_metadata) floating_rate_designated_maturity: Optional[str] = field(default=None, metadata=field_metadata) termination_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) pay_or_receive: Optional[PayReceive] = field(default=None, metadata=field_metadata) roll_convention: Optional[str] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) floating_rate_spread: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) traded_clean_price: Optional[float] = field(default=100.0, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Rates, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.AssetSwapFxdFlt, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class IRAssetSwapFxdFxd(Instrument): asw_type: Optional[AswType] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) fee: Optional[float] = field(default=None, metadata=field_metadata) fee_currency: Optional[Currency] = field(default=None, metadata=field_metadata) fee_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fixed_rate_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) fixed_first_stub: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fixed_rate_frequency: Optional[str] = field(default=None, metadata=field_metadata) fixed_holidays: Optional[str] = field(default=None, metadata=field_metadata) fixed_rate_business_day_convention: Optional[BusinessDayConvention] = field(default=None, metadata=field_metadata) fixed_rate: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) coupon: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) fixed_rate_currency: Optional[Currency] = field(default=None, metadata=field_metadata) asset_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) asset_first_stub: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) asset_frequency: Optional[str] = field(default=None, metadata=field_metadata) asset_holidays: Optional[str] = field(default=None, metadata=field_metadata) asset_business_day_convention: Optional[BusinessDayConvention] = field(default=None, metadata=field_metadata) identifier: Optional[str] = field(default=None, metadata=field_metadata) identifier_type: Optional[str] = field(default=None, metadata=field_metadata) asset_maturity: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fixed_maturity: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) roll_convention: Optional[str] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) fixed_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) clean_price: Optional[float] = field(default=100.0, metadata=field_metadata) settlement_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Rates, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.AssetSwapFxdFxd, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class IRBasisSwap(Instrument): termination_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) effective_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) principal_exchange: Optional[PrincipalExchange] = field(default=None, metadata=field_metadata) payer_spread: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) payer_rate_option: Optional[str] = field(default=None, metadata=field_metadata) payer_designated_maturity: Optional[str] = field(default=None, metadata=field_metadata) payer_frequency: Optional[str] = field(default=None, metadata=field_metadata) payer_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) payer_business_day_convention: Optional[BusinessDayConvention] = field(default=None, metadata=field_metadata) receiver_spread: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) receiver_rate_option: Optional[str] = field(default=None, metadata=field_metadata) receiver_designated_maturity: Optional[str] = field(default=None, metadata=field_metadata) receiver_frequency: Optional[str] = field(default=None, metadata=field_metadata) receiver_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) receiver_business_day_convention: Optional[BusinessDayConvention] = field(default=None, metadata=field_metadata) fee: Optional[float] = field(default=0.0, metadata=field_metadata) fee_currency: Optional[Currency] = field(default=None, metadata=field_metadata) fee_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) clearing_house: Optional[SwapClearingHouse] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Rates, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.BasisSwap, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class IRBondOption(Instrument): underlier: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) option_type: Optional[OptionType] = field(default=None, metadata=field_metadata) effective_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) strike: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) strike_type: Optional[BondStrikeType] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fee: Optional[float] = field(default=0.0, metadata=field_metadata) fee_currency: Optional[Currency] = field(default=None, metadata=field_metadata) fee_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) settlement: Optional[SettlementType] = field(default=None, metadata=field_metadata) underlier_type: Optional[UnderlierType] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Rates, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.BondOption, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class IRCMSOption(Instrument): cap_floor: Optional[str] = field(default=None, metadata=field_metadata) termination_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) effective_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) strike: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) index: Optional[str] = field(default=None, metadata=field_metadata) rate_option: Optional[str] = field(default=None, metadata=field_metadata) multiplier: Optional[float] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fee: Optional[float] = field(default=0.0, metadata=field_metadata) fee_currency: Optional[Currency] = field(default=None, metadata=field_metadata) start_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fee_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Rates, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.CMSOption, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class IRCMSOptionStrip(Instrument): cap_floor: Optional[str] = field(default=None, metadata=field_metadata) termination_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) effective_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) strike: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) index: Optional[str] = field(default=None, metadata=field_metadata) floating_rate_frequency: Optional[str] = field(default=None, metadata=field_metadata) floating_rate_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) floating_rate_business_day_convention: Optional[BusinessDayConvention] = field(default=None, metadata=field_metadata) reset_delay: Optional[str] = field(default=None, metadata=field_metadata) multiplier: Optional[float] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fee: Optional[float] = field(default=0.0, metadata=field_metadata) fee_currency: Optional[Currency] = field(default=None, metadata=field_metadata) fee_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Rates, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.CMSOptionStrip, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class IRCMSSpreadOption(Instrument): cap_floor: Optional[str] = field(default=None, metadata=field_metadata) termination_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) effective_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) strike: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) index1_tenor: Optional[str] = field(default=None, metadata=field_metadata) index2_tenor: Optional[str] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fee: Optional[float] = field(default=0.0, metadata=field_metadata) fee_currency: Optional[Currency] = field(default=None, metadata=field_metadata) fee_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Rates, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.CMSSpreadOption, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class IRCMSSpreadOptionStrip(Instrument): cap_floor: Optional[str] = field(default=None, metadata=field_metadata) termination_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) effective_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) strike: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) index1: Optional[str] = field(default=None, metadata=field_metadata) index2: Optional[str] = field(default=None, metadata=field_metadata) floating_rate_frequency: Optional[str] = field(default=None, metadata=field_metadata) floating_rate_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) floating_rate_business_day_convention: Optional[BusinessDayConvention] = field(default=None, metadata=field_metadata) reset_delay: Optional[str] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fee: Optional[float] = field(default=0.0, metadata=field_metadata) fee_currency: Optional[Currency] = field(default=None, metadata=field_metadata) fee_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Rates, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.CMSSpreadOptionStrip, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class IRFixedLeg(Instrument): buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) fixed_rate_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) fixed_first_stub: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fixed_rate_frequency: Optional[str] = field(default=None, metadata=field_metadata) fixed_holidays: Optional[str] = field(default=None, metadata=field_metadata) fixed_last_stub: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fixed_rate_business_day_convention: Optional[BusinessDayConvention] = field(default=None, metadata=field_metadata) fixed_rate: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) termination_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) principal_exchange: Optional[PrincipalExchange] = field(default=None, metadata=field_metadata) roll_convention: Optional[str] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) effective_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Rates, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.FixedLeg, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class IRFloatLeg(Instrument): buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) floating_rate_for_the_initial_calculation_period: Optional[float] = field(default=None, metadata=field_metadata) floating_rate_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) floating_first_stub: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) floating_rate_frequency: Optional[str] = field(default=None, metadata=field_metadata) floating_holidays: Optional[str] = field(default=None, metadata=field_metadata) floating_last_stub: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) floating_rate_business_day_convention: Optional[BusinessDayConvention] = field(default=None, metadata=field_metadata) floating_rate_option: Optional[str] = field(default=None, metadata=field_metadata) floating_rate_designated_maturity: Optional[str] = field(default=None, metadata=field_metadata) termination_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) principal_exchange: Optional[PrincipalExchange] = field(default=None, metadata=field_metadata) roll_convention: Optional[str] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) floating_rate_spread: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) effective_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Rates, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.FloatLeg, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class IRSwap(Instrument): pay_or_receive: Optional[PayReceive] = field(default=None, metadata=field_metadata) termination_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) effective_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) principal_exchange: Optional[PrincipalExchange] = field(default=None, metadata=field_metadata) floating_rate_for_the_initial_calculation_period: Optional[float] = field(default=None, metadata=field_metadata) floating_rate_option: Optional[str] = field(default=None, metadata=field_metadata) floating_rate_designated_maturity: Optional[str] = field(default=None, metadata=field_metadata) floating_rate_spread: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) floating_rate_frequency: Optional[str] = field(default=None, metadata=field_metadata) floating_rate_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) floating_rate_business_day_convention: Optional[BusinessDayConvention] = field(default=None, metadata=field_metadata) fixed_rate: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) fixed_rate_frequency: Optional[str] = field(default=None, metadata=field_metadata) fixed_rate_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) fixed_rate_business_day_convention: Optional[BusinessDayConvention] = field(default=None, metadata=field_metadata) fee: Optional[float] = field(default=0.0, metadata=field_metadata) fee_currency: Optional[Currency] = field(default=None, metadata=field_metadata) fee_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) clearing_house: Optional[SwapClearingHouse] = field(default=None, metadata=field_metadata) fixed_first_stub: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) floating_first_stub: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fixed_last_stub: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) floating_last_stub: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fixed_holidays: Optional[str] = field(default=None, metadata=field_metadata) floating_holidays: Optional[str] = field(default=None, metadata=field_metadata) roll_convention: Optional[str] = field(default=None, metadata=field_metadata) fixed_rate_accrual_convention: Optional[AccrualConvention] = field(default=None, metadata=field_metadata) floating_rate_accrual_convention: Optional[AccrualConvention] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Rates, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Swap, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) def scale_in_place(self, scaling: Optional[float] = None, check_resolved: bool = True): if scaling is None or scaling == 1: return if self.unresolved is None: if check_resolved: raise RuntimeError('Can only scale resolved instruments') if self.notional_amount is None or self.pay_or_receive is None: raise RuntimeError('Can only scale unresolved instruments with the buysell and primary size fields set') if any(a is not None and not isinstance(a, (int, float)) for a in [self.notional_amount, self.fee]): raise RuntimeError('All specified size fields must be numeric') self.notional_amount *= abs(scaling) if scaling < 0: flip_dict = {PayReceive.Pay: PayReceive.Receive, PayReceive.Receive: PayReceive.Pay} self.pay_or_receive = flip_dict[self.pay_or_receive] if check_resolved or self.fee is not None: self.fee *= -1 return @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class IRSwaption(Instrument): pay_or_receive: Optional[PayReceive] = field(default=None, metadata=field_metadata) termination_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) effective_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) expiration_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) floating_rate_option: Optional[str] = field(default=None, metadata=field_metadata) floating_rate_designated_maturity: Optional[str] = field(default=None, metadata=field_metadata) floating_rate_spread: Optional[float] = field(default=None, metadata=field_metadata) floating_rate_frequency: Optional[str] = field(default=None, metadata=field_metadata) floating_rate_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) floating_rate_business_day_convention: Optional[BusinessDayConvention] = field(default=None, metadata=field_metadata) fixed_rate_frequency: Optional[str] = field(default=None, metadata=field_metadata) fixed_rate_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) fixed_rate_business_day_convention: Optional[BusinessDayConvention] = field(default=None, metadata=field_metadata) strike: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fee: Optional[float] = field(default=0.0, metadata=field_metadata) fee_currency: Optional[Currency] = field(default=None, metadata=field_metadata) fee_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) clearing_house: Optional[SwapClearingHouse] = field(default=None, metadata=field_metadata) settlement: Optional[SwapSettlement] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Rates, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Swaption, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) def scale_in_place(self, scaling: Optional[float] = None, check_resolved: bool = True): if scaling is None or scaling == 1: return if self.unresolved is None: if check_resolved: raise RuntimeError('Can only scale resolved instruments') if self.notional_amount is None or self.buy_sell is None: raise RuntimeError('Can only scale unresolved instruments with the buysell and primary size fields set') if any(a is not None and not isinstance(a, (int, float)) for a in [self.notional_amount, self.fee]): raise RuntimeError('All specified size fields must be numeric') self.notional_amount *= abs(scaling) if scaling < 0: flip_dict = {BuySell.Buy: BuySell.Sell, BuySell.Sell: BuySell.Buy} self.buy_sell = flip_dict[self.buy_sell] if check_resolved or self.fee is not None: self.fee *= -1 return @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class IRXccySwap(Instrument): termination_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) notional_amount: Optional[float] = field(default=None, metadata=field_metadata) effective_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) principal_exchange: Optional[PrincipalExchange] = field(default=None, metadata=field_metadata) payer_currency: Optional[Currency] = field(default=None, metadata=field_metadata) payer_spread: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) payer_rate_option: Optional[str] = field(default=None, metadata=field_metadata) payer_designated_maturity: Optional[str] = field(default=None, metadata=field_metadata) payer_frequency: Optional[str] = field(default=None, metadata=field_metadata) payer_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) payer_business_day_convention: Optional[BusinessDayConvention] = field(default=None, metadata=field_metadata) receiver_currency: Optional[Currency] = field(default=None, metadata=field_metadata) receiver_spread: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) receiver_rate_option: Optional[str] = field(default=None, metadata=field_metadata) receiver_designated_maturity: Optional[str] = field(default=None, metadata=field_metadata) receiver_frequency: Optional[str] = field(default=None, metadata=field_metadata) receiver_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) receiver_business_day_convention: Optional[BusinessDayConvention] = field(default=None, metadata=field_metadata) fee: Optional[float] = field(default=0.0, metadata=field_metadata) fee_currency: Optional[Currency] = field(default=None, metadata=field_metadata) fee_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) initial_fx_rate: Optional[float] = field(default=None, metadata=field_metadata) payer_first_stub: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) receiver_first_stub: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) payer_last_stub: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) receiver_last_stub: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) payer_holidays: Optional[str] = field(default=None, metadata=field_metadata) receiver_holidays: Optional[str] = field(default=None, metadata=field_metadata) notional_reset_side: Optional[PayReceive] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Rates, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.XccySwapMTM, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class IRXccySwapFixFix(Instrument): termination_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) notional_amount: Optional[float] = field(default=None, metadata=field_metadata) receiver_notional_amount: Optional[float] = field(default=None, metadata=field_metadata) effective_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) principal_exchange: Optional[PrincipalExchange] = field(default=None, metadata=field_metadata) payer_currency: Optional[Currency] = field(default=None, metadata=field_metadata) payer_rate: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) payer_frequency: Optional[str] = field(default=None, metadata=field_metadata) payer_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) payer_business_day_convention: Optional[BusinessDayConvention] = field(default=None, metadata=field_metadata) receiver_currency: Optional[Currency] = field(default=None, metadata=field_metadata) receiver_rate: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) receiver_frequency: Optional[str] = field(default=None, metadata=field_metadata) receiver_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) receiver_business_day_convention: Optional[BusinessDayConvention] = field(default=None, metadata=field_metadata) fee: Optional[float] = field(default=0.0, metadata=field_metadata) fee_currency: Optional[Currency] = field(default=None, metadata=field_metadata) fee_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Rates, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.XccySwapFixFix, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class IRXccySwapFixFlt(Instrument): pay_or_receive: Optional[PayReceive] = field(default=None, metadata=field_metadata) termination_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) effective_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) principal_exchange: Optional[PrincipalExchange] = field(default=None, metadata=field_metadata) floating_rate_currency: Optional[Currency] = field(default=None, metadata=field_metadata) floating_rate_for_the_initial_calculation_period: Optional[float] = field(default=None, metadata=field_metadata) floating_rate_option: Optional[str] = field(default=None, metadata=field_metadata) floating_rate_designated_maturity: Optional[str] = field(default=None, metadata=field_metadata) floating_rate_spread: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) floating_rate_frequency: Optional[str] = field(default=None, metadata=field_metadata) floating_rate_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) floating_rate_business_day_convention: Optional[BusinessDayConvention] = field(default=None, metadata=field_metadata) fixed_rate_currency: Optional[Currency] = field(default=None, metadata=field_metadata) fixed_rate: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) fixed_rate_frequency: Optional[str] = field(default=None, metadata=field_metadata) fixed_rate_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) fixed_rate_business_day_convention: Optional[BusinessDayConvention] = field(default=None, metadata=field_metadata) fee: Optional[float] = field(default=0.0, metadata=field_metadata) fee_currency: Optional[Currency] = field(default=None, metadata=field_metadata) fee_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fixed_first_stub: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) floating_first_stub: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fixed_last_stub: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) floating_last_stub: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fixed_holidays: Optional[str] = field(default=None, metadata=field_metadata) floating_holidays: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Rates, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.XccySwapFixFlt, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class IRXccySwapFltFlt(Instrument): termination_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) effective_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) principal_exchange: Optional[PrincipalExchange] = field(default=None, metadata=field_metadata) payer_currency: Optional[Currency] = field(default=None, metadata=field_metadata) payer_spread: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) payer_rate_option: Optional[str] = field(default=None, metadata=field_metadata) payer_designated_maturity: Optional[str] = field(default=None, metadata=field_metadata) payer_frequency: Optional[str] = field(default=None, metadata=field_metadata) payer_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) payer_business_day_convention: Optional[BusinessDayConvention] = field(default=None, metadata=field_metadata) receiver_currency: Optional[Currency] = field(default=None, metadata=field_metadata) receiver_spread: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) receiver_rate_option: Optional[str] = field(default=None, metadata=field_metadata) receiver_designated_maturity: Optional[str] = field(default=None, metadata=field_metadata) receiver_frequency: Optional[str] = field(default=None, metadata=field_metadata) receiver_day_count_fraction: Optional[DayCountFraction] = field(default=None, metadata=field_metadata) receiver_business_day_convention: Optional[BusinessDayConvention] = field(default=None, metadata=field_metadata) fee: Optional[float] = field(default=0.0, metadata=field_metadata) fee_currency: Optional[Currency] = field(default=None, metadata=field_metadata) fee_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) payer_first_stub: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) receiver_first_stub: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) payer_last_stub: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) receiver_last_stub: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) payer_holidays: Optional[str] = field(default=None, metadata=field_metadata) receiver_holidays: Optional[str] = field(default=None, metadata=field_metadata) receiver_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Rates, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.XccySwap, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MacroBasket(Instrument): cap_floor: Optional[str] = field(default=None, metadata=field_metadata) termination_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) notional_currency: Optional[Currency] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) effective_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) strike: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) index: Optional[str] = field(default=None, metadata=field_metadata) rate_option: Optional[str] = field(default=None, metadata=field_metadata) multiplier: Optional[float] = field(default=None, metadata=field_metadata) premium: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) premium_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fee: Optional[float] = field(default=0.0, metadata=field_metadata) fee_currency: Optional[Currency] = field(default=None, metadata=field_metadata) start_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) fee_payment_date: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Cross_Asset, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.MacroBasket, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CommodOTCOption(Instrument): buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) quantity: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) start: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) number_of_periods: Optional[int] = field(default=None, metadata=field_metadata) quantity_unit: Optional[str] = field(default=None, metadata=field_metadata) settlement: Optional[str] = field(default=None, metadata=field_metadata) premium_summary: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) legs: Optional[Tuple[CommodOTCOptionLeg, ...]] = field(default=None, metadata=field_metadata) end: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) quantity_period: Optional[Period] = field(default=None, metadata=field_metadata) strategy: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Commod, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.OptionStrategy, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EqSynthetic(Instrument): underlier: Union[float, str] = field(default=None, metadata=field_metadata) expiry: Optional[Union[datetime.date, str]] = field(default=None, metadata=field_metadata) currency: Optional[Currency] = field(default=None, metadata=field_metadata) swap_type: Optional[SwapType] = field(default=SwapType.Eq_Synthetic_OET, metadata=field_metadata) buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) underlier_type: Optional[UnderlierType] = field(default=None, metadata=field_metadata) effective_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) num_of_underlyers: Optional[float] = field(default=None, metadata=field_metadata) rate_tenor: Optional[str] = field(default=None, metadata=field_metadata) forwardstartingfixednotional: Optional[float] = field(default=None, metadata=field_metadata) forwardstartingnotionalprecision: Optional[float] = field(default=None, metadata=field_metadata) commission: Optional[float] = field(default=None, metadata=field_metadata) commission_units: Optional[str] = field(default=None, metadata=field_metadata) quantity: Optional[str] = field(default=None, metadata=field_metadata) strike: Optional[float] = field(default=None, metadata=field_metadata) initial_valuation_date: Optional[str] = field(default=None, metadata=field_metadata) schedule_type: Optional[str] = field(default=None, metadata=field_metadata) oet_terms: Optional[Tuple[EqSyntheticOETTerms, ...]] = field(default=None, metadata=field_metadata) dividend_pay_ratio: Optional[float] = field(default=None, metadata=field_metadata) trade_date: Optional[str] = field(default=None, metadata=field_metadata) strike_date: Optional[str] = field(default=None, metadata=field_metadata) fx_data_source: Optional[str] = field(default=None, metadata=field_metadata) rate_option: Optional[str] = field(default=None, metadata=field_metadata) settlement_currency: Optional[Currency] = field(default=None, metadata=field_metadata) settlement_delay: Optional[str] = field(default=None, metadata=field_metadata) eq_leg: Optional[Tuple[EqSyntheticLeg, ...]] = field(default=None, metadata=field_metadata) funding_leg: Optional[Tuple[EqSyntheticLeg, ...]] = field(default=None, metadata=field_metadata) valuation_bdc: Optional[float] = field(default=None, metadata=field_metadata) payment_bdc: Optional[float] = field(default=None, metadata=field_metadata) commission_type: Optional[str] = field(default=None, metadata=field_metadata) designated_maturity: Optional[str] = field(default=None, metadata=field_metadata) reset_delay: Optional[str] = field(default=None, metadata=field_metadata) payment_delay: Optional[str] = field(default=None, metadata=field_metadata) compounded_funding: Optional[float] = field(default=None, metadata=field_metadata) generate_programmed_dates: Optional[float] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Equity, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.Synthetic, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class InvoiceSpread(Instrument): buy_sell: Optional[BuySell] = field(default=None, metadata=field_metadata) notional_amount: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) underlier: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) swap: Optional[IRSwap] = field(default=None, metadata=field_metadata) future: Optional[IRBondFuture] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Rates, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.InvoiceSpread, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CSLPython(Instrument): class_name: Optional[str] = field(default=None, metadata=field_metadata) denominated: Optional[Currency] = field(default=None, metadata=field_metadata) double_params: Optional[Tuple[CSLDouble, ...]] = field(default=None, metadata=field_metadata) date_params: Optional[Tuple[CSLDate, ...]] = field(default=None, metadata=field_metadata) string_params: Optional[Tuple[CSLString, ...]] = field(default=None, metadata=field_metadata) simple_schedule_params: Optional[Tuple[CSLSimpleSchedule, ...]] = field(default=None, metadata=field_metadata) schedule_params: Optional[Tuple[CSLSchedule, ...]] = field(default=None, metadata=field_metadata) currency_params: Optional[Tuple[CSLCurrency, ...]] = field(default=None, metadata=field_metadata) stock_params: Optional[Tuple[CSLStock, ...]] = field(default=None, metadata=field_metadata) index_params: Optional[Tuple[CSLIndex, ...]] = field(default=None, metadata=field_metadata) fx_cross_params: Optional[Tuple[CSLFXCross, ...]] = field(default=None, metadata=field_metadata) double_array_params: Optional[Tuple[CSLDoubleArray, ...]] = field(default=None, metadata=field_metadata) date_array_params: Optional[Tuple[CSLDateArray, ...]] = field(default=None, metadata=field_metadata) string_array_params: Optional[Tuple[CSLStringArray, ...]] = field(default=None, metadata=field_metadata) simple_schedule_array_params: Optional[Tuple[CSLSimpleScheduleArray, ...]] = field(default=None, metadata=field_metadata) schedule_array_params: Optional[Tuple[CSLScheduleArray, ...]] = field(default=None, metadata=field_metadata) currency_array_params: Optional[Tuple[CSLCurrencyArray, ...]] = field(default=None, metadata=field_metadata) stock_array_params: Optional[Tuple[CSLStockArray, ...]] = field(default=None, metadata=field_metadata) index_array_params: Optional[Tuple[CSLIndexArray, ...]] = field(default=None, metadata=field_metadata) fx_cross_array_params: Optional[Tuple[CSLFXCrossArray, ...]] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(init=False, default=AssetClass.Cross_Asset, metadata=field_metadata) type_: Optional[AssetType] = field(init=False, default=AssetType.CSL, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) ================================================ FILE: gs_quant/target/measures.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import copy import pandas as pd from gs_quant.common import AssetClass, CurrencyParameter, FiniteDifferenceParameter, StringParameter, \ ListOfStringParameter, ListOfNumberParameter, MapParameter from gs_quant.common import ParameterisedRiskMeasure, RiskMeasure from gs_quant.target.risk import RiskMeasureType, RiskMeasureUnit class RiskMeasureWithCurrencyParameter(ParameterisedRiskMeasure): @property def currency(self): return self.parameters.value if self.parameters else None def __call__(self, currency=None, name=None): # hack to prevent ParameterisedRiskMeasure input into pandas LocIndexer as a callable function that returns # output for indexing (https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.loc.html) if isinstance(currency, (pd.Series, pd.DataFrame)): return self clone = copy.copy(self) if name: clone.name = name if currency is None and clone.parameters is not None: currency = clone.parameters.currency param = CurrencyParameter(value=currency) clone.parameters = param return clone class RiskMeasureWithDoubleParameter(ParameterisedRiskMeasure): @property def double(self): return self.parameters.value if self.parameters else None def __call__(self, double=None, name=None): # hack to prevent ParameterisedRiskMeasure input into pandas LocIndexer as a callable function that returns # output for indexing (https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.loc.html) if isinstance(double, (pd.Series, pd.DataFrame)): return self clone = copy.copy(self) if name: clone.name = name if double is None and clone.parameters is not None: double = clone.parameters.double param = DoubleParameter(value=double) clone.parameters = param return clone class RiskMeasureWithListOfNumberParameter(ParameterisedRiskMeasure): @property def list_of_number(self): return self.parameters.values if self.parameters else None def __call__(self, list_of_number=None, name=None): # hack to prevent ParameterisedRiskMeasure input into pandas LocIndexer as a callable function that returns # output for indexing (https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.loc.html) if isinstance(list_of_number, (pd.Series, pd.DataFrame)): return self clone = copy.copy(self) if name: clone.name = name if list_of_number is None and clone.parameters is not None: list_of_number = clone.parameters.list_of_number param = ListOfNumberParameter(values=list_of_number) clone.parameters = param return clone class RiskMeasureWithListOfStringParameter(ParameterisedRiskMeasure): @property def list_of_string(self): return self.parameters.values if self.parameters else None def __call__(self, list_of_string=None, name=None): # hack to prevent ParameterisedRiskMeasure input into pandas LocIndexer as a callable function that returns # output for indexing (https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.loc.html) if isinstance(list_of_string, (pd.Series, pd.DataFrame)): return self clone = copy.copy(self) if name: clone.name = name if list_of_string is None and clone.parameters is not None: list_of_string = clone.parameters.list_of_string param = ListOfStringParameter(values=list_of_string) clone.parameters = param return clone class RiskMeasureWithMapParameter(ParameterisedRiskMeasure): @property def map(self): return self.parameters.value if self.parameters else None def __call__(self, map=None, name=None): # hack to prevent ParameterisedRiskMeasure input into pandas LocIndexer as a callable function that returns # output for indexing (https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.loc.html) if isinstance(map, (pd.Series, pd.DataFrame)): return self clone = copy.copy(self) if name: clone.name = name if map is None and clone.parameters is not None: map = clone.parameters.map param = MapParameter(value=map) clone.parameters = param return clone class RiskMeasureWithStringParameter(ParameterisedRiskMeasure): @property def string(self): return self.parameters.value if self.parameters else None def __call__(self, string=None, name=None): # hack to prevent ParameterisedRiskMeasure input into pandas LocIndexer as a callable function that returns # output for indexing (https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.loc.html) if isinstance(string, (pd.Series, pd.DataFrame)): return self clone = copy.copy(self) if name: clone.name = name if string is None and clone.parameters is not None: string = clone.parameters.string param = StringParameter(value=string) clone.parameters = param return clone class RiskMeasureWithFiniteDifferenceParameter(ParameterisedRiskMeasure): @property def aggregation_level(self): return self.parameters.aggregation_level if self.parameters else None @property def bump_size(self): return self.parameters.bump_size if self.parameters else None @property def currency(self): return self.parameters.currency if self.parameters else None @property def finite_difference_method(self): return self.parameters.finite_difference_method if self.parameters else None @property def local_curve(self): return self.parameters.local_curve if self.parameters else None @property def mkt_marking_options(self): return self.parameters.mkt_marking_options if self.parameters else None @property def scale_factor(self): return self.parameters.scale_factor if self.parameters else None def __call__(self, aggregation_level=None, bump_size=None, currency=None, finite_difference_method=None, local_curve=None, mkt_marking_options=None, scale_factor=None, name=None): # hack to prevent ParameterisedRiskMeasure input into pandas LocIndexer as a callable function that returns # output for indexing (https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.loc.html) if isinstance(aggregation_level, (pd.Series, pd.DataFrame)): return self clone = copy.copy(self) if name: clone.name = name if aggregation_level is None and clone.parameters is not None: aggregation_level = clone.parameters.aggregation_level if bump_size is None and clone.parameters is not None: bump_size = clone.parameters.bump_size if currency is None and clone.parameters is not None: currency = clone.parameters.currency if finite_difference_method is None and clone.parameters is not None: finite_difference_method = clone.parameters.finite_difference_method if local_curve is None and clone.parameters is not None: local_curve = clone.parameters.local_curve if mkt_marking_options is None and clone.parameters is not None: mkt_marking_options = clone.parameters.mkt_marking_options if scale_factor is None and clone.parameters is not None: scale_factor = clone.parameters.scale_factor param = FiniteDifferenceParameter(aggregation_level=aggregation_level,bump_size=bump_size,currency=currency,finite_difference_method=finite_difference_method,local_curve=local_curve,mkt_marking_options=mkt_marking_options,scale_factor=scale_factor) clone.parameters = param return clone Annuity = RiskMeasureWithCurrencyParameter(name="Annuity", asset_class=AssetClass("Rates"), measure_type=RiskMeasureType("AnnuityLocalCcy")) Annuity.__doc__ = "Annuity" BaseCPI = RiskMeasure(name="BaseCPI", measure_type=RiskMeasureType("BaseCPI")) BaseCPI.__doc__ = "Base CPI" CDATMSpread = RiskMeasure(name="CDATMSpread", asset_class=AssetClass("Credit"), measure_type=RiskMeasureType("ATM Spread")) CDATMSpread.__doc__ = "Credit ATM Spread" CDDelta = RiskMeasure(name="CDDelta", asset_class=AssetClass("Credit"), measure_type=RiskMeasureType("Delta")) CDDelta.__doc__ = "Credit Delta" CDFwdSpread = RiskMeasure(name="CDFwdSpread", asset_class=AssetClass("Credit"), measure_type=RiskMeasureType("Forward Spread")) CDFwdSpread.__doc__ = "Credit Forward Spread" CDGamma = RiskMeasure(name="CDGamma", asset_class=AssetClass("Credit"), measure_type=RiskMeasureType("Gamma")) CDGamma.__doc__ = "CDIndexGamma" CDIForward = RiskMeasure(name="CDIForward", asset_class=AssetClass("Credit"), measure_type=RiskMeasureType("CDIForward")) CDIForward.__doc__ = "CDS Index Forward Quote in the Prevailing Quoting Style." CDIIndexDelta = RiskMeasure(name="CDIIndexDelta", asset_class=AssetClass("Credit"), measure_type=RiskMeasureType("CDIIndexDelta")) CDIIndexDelta.__doc__ = "CDS Index Delta." CDIIndexVega = RiskMeasure(name="CDIIndexVega", asset_class=AssetClass("Credit"), measure_type=RiskMeasureType("CDIIndexVega")) CDIIndexVega.__doc__ = "CDS Index Vega." CDIOptionPremium = RiskMeasure(name="CDIOptionPremium", asset_class=AssetClass("Credit"), measure_type=RiskMeasureType("CDIOptionPremium")) CDIOptionPremium.__doc__ = "CDS Index Option Premium" CDIOptionPremiumFlatFwd = RiskMeasure(name="CDIOptionPremiumFlatFwd", asset_class=AssetClass("Credit"), measure_type=RiskMeasureType("CDIOptionPremiumFlatFwd")) CDIOptionPremiumFlatFwd.__doc__ = "CDS Index Option Premium assuming Flat Forwards." CDIOptionPremiumFlatVol = RiskMeasure(name="CDIOptionPremiumFlatVol", asset_class=AssetClass("Credit"), measure_type=RiskMeasureType("CDIOptionPremiumFlatVol")) CDIOptionPremiumFlatVol.__doc__ = "CDS Index Option Premium assuming Flat Volatilities." CDISpot = RiskMeasure(name="CDISpot", asset_class=AssetClass("Credit"), measure_type=RiskMeasureType("CDISpot")) CDISpot.__doc__ = "CDS Index Spot Quote in the Prevailing Quoting Style." CDISpreadDV01 = RiskMeasure(name="CDISpreadDV01", asset_class=AssetClass("Credit"), measure_type=RiskMeasureType("CDISpreadDV01")) CDISpreadDV01.__doc__ = "CDS Index Rates DV01." CDIUpfrontPrice = RiskMeasure(name="CDIUpfrontPrice", asset_class=AssetClass("Credit"), measure_type=RiskMeasureType("CDIUpfrontPrice")) CDIUpfrontPrice.__doc__ = "CDS Index Upfront Price." CDImpliedVolatility = RiskMeasure(name="CDImpliedVolatility", asset_class=AssetClass("Credit"), measure_type=RiskMeasureType("Implied Volatility")) CDImpliedVolatility.__doc__ = "CDImpliedVolatility" CDIndexVega = RiskMeasure(name="CDIndexVega", asset_class=AssetClass("Credit"), measure_type=RiskMeasureType("Vega")) CDIndexVega.__doc__ = "CDIndexVega" CDTheta = RiskMeasure(name="CDTheta", asset_class=AssetClass("Credit"), measure_type=RiskMeasureType("Theta")) CDTheta.__doc__ = "CDTheta" CDVega = RiskMeasure(name="CDVega", asset_class=AssetClass("Credit"), measure_type=RiskMeasureType("Vega")) CDVega.__doc__ = "Credit Vega" CRIFIRCurve = RiskMeasure(name="CRIFIRCurve", measure_type=RiskMeasureType("CRIF IRCurve")) CRIFIRCurve.__doc__ = "CRIF IR Curve" Cashflows = RiskMeasure(name="Cashflows", measure_type=RiskMeasureType("Cashflows")) Cashflows.__doc__ = "Cashflows" CommodDelta = RiskMeasure(name="CommodDelta", asset_class=AssetClass("Commod"), measure_type=RiskMeasureType("Delta")) CommodDelta.__doc__ = "Commod Delta" CommodImpliedVol = RiskMeasure(name="CommodImpliedVol", asset_class=AssetClass("Commod"), measure_type=RiskMeasureType("Volatility")) CommodImpliedVol.__doc__ = "Commod Implied Volatility" CommodTheta = RiskMeasure(name="CommodTheta", asset_class=AssetClass("Commod"), measure_type=RiskMeasureType("Theta")) CommodTheta.__doc__ = "Commod Theta" CommodVega = RiskMeasure(name="CommodVega", asset_class=AssetClass("Commod"), measure_type=RiskMeasureType("Vega")) CommodVega.__doc__ = "Commod Vega" CompoundedFixedRate = RiskMeasure(name="CompoundedFixedRate", measure_type=RiskMeasureType("Compounded Fixed Rate")) CompoundedFixedRate.__doc__ = "CompoundedFixedRate" Cross = RiskMeasure(name="Cross", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("Cross")) Cross.__doc__ = "Cross" CrossMultiplier = RiskMeasure(name="CrossMultiplier", measure_type=RiskMeasureType("Cross Multiplier")) CrossMultiplier.__doc__ = "CrossMultiplier" Description = RiskMeasure(name="Description", measure_type=RiskMeasureType("Description")) Description.__doc__ = "Description" DollarPrice = RiskMeasure(name="DollarPrice", measure_type=RiskMeasureType("Dollar Price")) DollarPrice.__doc__ = "Price of the instrument in US Dollars" EqAnnualImpliedVol = RiskMeasure(name="EqAnnualImpliedVol", asset_class=AssetClass("Equity"), measure_type=RiskMeasureType("Annual Implied Volatility"), unit=RiskMeasureUnit("Percent")) EqAnnualImpliedVol.__doc__ = "Equity Annual Implied Volatility (%)" EqDelta = RiskMeasureWithCurrencyParameter(name="EqDelta", asset_class=AssetClass("Equity"), measure_type=RiskMeasureType("Delta")) EqDelta.__doc__ = "Change in Dollar Price (USD present value) due to individual 1% move in the spot price of underlying equity security" EqGamma = RiskMeasureWithCurrencyParameter(name="EqGamma", asset_class=AssetClass("Equity"), measure_type=RiskMeasureType("Gamma")) EqGamma.__doc__ = "Change in EqDelta for a 1% move in the price of the underlying equity security" EqSpot = RiskMeasure(name="EqSpot", asset_class=AssetClass("Equity"), measure_type=RiskMeasureType("Spot")) EqSpot.__doc__ = "Equity Spot" EqTheta = RiskMeasureWithCurrencyParameter(name="EqTheta", asset_class=AssetClass("Equity"), measure_type=RiskMeasureType("Theta")) EqTheta.__doc__ = "Change in Dollar Price over one day" EqVega = RiskMeasureWithCurrencyParameter(name="EqVega", asset_class=AssetClass("Equity"), measure_type=RiskMeasureType("Vega")) EqVega.__doc__ = "Change in Dollar Price (USD present value) due to individual 1bp moves in the implied volatility of the underlying equity security" ExpiryInYears = RiskMeasure(name="ExpiryInYears", measure_type=RiskMeasureType("ExpiryInYears")) ExpiryInYears.__doc__ = "Time to Expiry expressed in fractional Years." FX25DeltaButterflyVolatility = RiskMeasure(name="FX25DeltaButterflyVolatility", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("FX BF 25 Vol")) FX25DeltaButterflyVolatility.__doc__ = "The volatility of a 25 delta butterfly" FX25DeltaRiskReversalVolatility = RiskMeasure(name="FX25DeltaRiskReversalVolatility", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("FX RR 25 Vol")) FX25DeltaRiskReversalVolatility.__doc__ = "The volatility of a 25 delta risk reversal" FXAnnualATMImpliedVol = RiskMeasure(name="FXAnnualATMImpliedVol", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("Annual ATM Implied Volatility"), unit=RiskMeasureUnit("Percent")) FXAnnualATMImpliedVol.__doc__ = "FX Annual ATM Implied Volatility" FXAnnualImpliedVol = RiskMeasure(name="FXAnnualImpliedVol", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("Annual Implied Volatility"), unit=RiskMeasureUnit("Percent")) FXAnnualImpliedVol.__doc__ = "FX Annual Implied Volatility" FXBlackScholes = RiskMeasure(name="FXBlackScholes", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("BSPrice")) FXBlackScholes.__doc__ = "FXBlackScholes" FXBlackScholesPct = RiskMeasure(name="FXBlackScholesPct", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("BSPricePct")) FXBlackScholesPct.__doc__ = "FXBlackScholes in Percent of Notional" FXCalcDelta = RiskMeasure(name="FXCalcDelta", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("FX Calculated Delta")) FXCalcDelta.__doc__ = "FXCalcDelta" FXCalcDeltaNoPremAdj = RiskMeasure(name="FXCalcDeltaNoPremAdj", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("FX Calculated Delta No Premium Adjustment")) FXCalcDeltaNoPremAdj.__doc__ = "FXCalcDeltaNoPremAdj" FXDelta = RiskMeasureWithFiniteDifferenceParameter(name="FXDelta", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("Delta")) FXDelta.__doc__ = "Dollar Price sensitivity of the instrument to a move in the underlying spot such that dSpot * FXDelta = PnL" FXDeltaHedge = RiskMeasure(name="FXDeltaHedge", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("FX Hedge Delta")) FXDeltaHedge.__doc__ = "Size of the spot trade in the underlying currency needed to hedge the USD delta on a per-cross basis" FXDiscountFactorOver = RiskMeasure(name="FXDiscountFactorOver", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("FX Discount Factor Over")) FXDiscountFactorOver.__doc__ = "Discount Factor to Maturity in the Over Currency of the FX Pair" FXDiscountFactorUnder = RiskMeasure(name="FXDiscountFactorUnder", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("FX Discount Factor Under")) FXDiscountFactorUnder.__doc__ = "Discount Factor to Maturity in the Under Currency of the FX Pair" FXFwd = RiskMeasure(name="FXFwd", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("Forward Rate")) FXFwd.__doc__ = "FXFwd" FXGamma = RiskMeasure(name="FXGamma", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("Gamma")) FXGamma.__doc__ = "FXDelta sensitivity of the instrument to a move in the underlying spot such that dSpot * FXGamma = dDelta" FXImpliedCorrelation = RiskMeasure(name="FXImpliedCorrelation", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("Correlation")) FXImpliedCorrelation.__doc__ = "Correlation and Vol information" FXPoints = RiskMeasure(name="FXPoints", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("Points")) FXPoints.__doc__ = "FXPoints" FXPremium = RiskMeasure(name="FXPremium", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("FX Premium")) FXPremium.__doc__ = "FXPremium" FXPremiumPct = RiskMeasure(name="FXPremiumPct", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("FX Premium Pct")) FXPremiumPct.__doc__ = "FXPremium in Percent of Notional" FXPremiumPctFlatFwd = RiskMeasure(name="FXPremiumPctFlatFwd", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("FX Premium Pct Flat Fwd")) FXPremiumPctFlatFwd.__doc__ = "FXPremium in Percent of Notional in a Flat Forward environment" FXQuotedDelta = RiskMeasure(name="FXQuotedDelta", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("QuotedDelta")) FXQuotedDelta.__doc__ = "FXQuotedDelta" FXQuotedDeltaNoPremAdj = RiskMeasure(name="FXQuotedDeltaNoPremAdj", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("FX Quoted Delta No Premium Adjustment")) FXQuotedDeltaNoPremAdj.__doc__ = "FXQuotedDeltaNoPremAdj" FXQuotedVega = RiskMeasure(name="FXQuotedVega", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("FX Quoted Vega")) FXQuotedVega.__doc__ = "FXQuotedVega" FXQuotedVegaBps = RiskMeasure(name="FXQuotedVegaBps", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("FX Quoted Vega Bps")) FXQuotedVegaBps.__doc__ = "FXQuotedVegaBps" FXSpot = RiskMeasure(name="FXSpot", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("Spot")) FXSpot.__doc__ = "FX spot reference" FXSpotVal = RiskMeasure(name="FXSpotVal", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("FXSpotVal")) FXSpotVal.__doc__ = "FXSpot in trade cross convention" FXStrikePts = RiskMeasure(name="FXStrikePts", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("StrikePts")) FXStrikePts.__doc__ = "FXPoints in trade cross convention" FXVega = RiskMeasureWithFiniteDifferenceParameter(name="FXVega", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("Vega")) 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" FairPremium = RiskMeasureWithCurrencyParameter(name="FairPremium", measure_type=RiskMeasureType("FairPremium")) FairPremium.__doc__ = "Fair Premium is the instrument present value discounted to the premium settlement date" FairPremiumInPercent = RiskMeasure(name="FairPremiumInPercent", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("FairPremiumPct"), unit=RiskMeasureUnit("Percent")) FairPremiumInPercent.__doc__ = "The instrument present value discounted to the premium settlement excluding any embedded premium date as a percentage" FairPrice = RiskMeasure(name="FairPrice", asset_class=AssetClass("Commod"), measure_type=RiskMeasureType("Fair Price")) FairPrice.__doc__ = "FairPrice" FairVarStrike = RiskMeasure(name="FairVarStrike", measure_type=RiskMeasureType("FairVarStrike")) FairVarStrike.__doc__ = "Fair Variance Strike Value of a Variance Swap" FairVolStrike = RiskMeasure(name="FairVolStrike", measure_type=RiskMeasureType("FairVolStrike")) FairVolStrike.__doc__ = "Fair Volatility Strike Value of a Variance Swap" ForwardPrice = RiskMeasure(name="ForwardPrice", measure_type=RiskMeasureType("Forward Price"), unit=RiskMeasureUnit("BPS")) ForwardPrice.__doc__ = " Price of the instrument at expiry in the local currency" IRAnnualATMImpliedVol = RiskMeasure(name="IRAnnualATMImpliedVol", asset_class=AssetClass("Rates"), measure_type=RiskMeasureType("Annual ATMF Implied Volatility"), unit=RiskMeasureUnit("Percent")) IRAnnualATMImpliedVol.__doc__ = "Interest rate annual implied at-the-money volatility (in percent)" IRAnnualImpliedVol = RiskMeasure(name="IRAnnualImpliedVol", asset_class=AssetClass("Rates"), measure_type=RiskMeasureType("Annual Implied Volatility"), unit=RiskMeasureUnit("Percent")) IRAnnualImpliedVol.__doc__ = "Interest rate annual implied volatility (in percent)" IRBasis = RiskMeasureWithFiniteDifferenceParameter(name="IRBasis", asset_class=AssetClass("Rates"), measure_type=RiskMeasureType("Basis")) IRBasis.__doc__ = "Change in Dollar Price (USD present value) due to individual 1bp moves in the interest rate instruments used to build the basis curve(s)" IRDailyImpliedVol = RiskMeasure(name="IRDailyImpliedVol", asset_class=AssetClass("Rates"), measure_type=RiskMeasureType("Daily Implied Volatility"), unit=RiskMeasureUnit("BPS")) IRDailyImpliedVol.__doc__ = "Interest rate daily implied volatility (in basis points)" IRDelta = RiskMeasureWithFiniteDifferenceParameter(name="IRDelta", asset_class=AssetClass("Rates"), measure_type=RiskMeasureType("Delta")) IRDelta.__doc__ = "Change in Dollar Price (USD present value) due to individual 1bp moves in the interest rate instruments used to build the underlying discount curve" IRDiscountDeltaParallel = RiskMeasure(name="IRDiscountDeltaParallel", asset_class=AssetClass("Rates"), measure_type=RiskMeasureType("ParallelDiscountDelta")) IRDiscountDeltaParallel.__doc__ = "Parallel Discount Delta" IRDiscountDeltaParallelLocalCcy = RiskMeasure(name="IRDiscountDeltaParallelLocalCcy", asset_class=AssetClass("Rates"), measure_type=RiskMeasureType("ParallelDiscountDeltaLocalCcy")) IRDiscountDeltaParallelLocalCcy.__doc__ = "Parallel Discount Delta (Local Ccy)" IRFwdRate = RiskMeasure(name="IRFwdRate", asset_class=AssetClass("Rates"), measure_type=RiskMeasureType("Forward Rate"), unit=RiskMeasureUnit("Percent")) IRFwdRate.__doc__ = "Interest rate par rate (in percent)" IRGamma = RiskMeasure(name="IRGamma", asset_class=AssetClass("Rates"), measure_type=RiskMeasureType("Gamma")) IRGamma.__doc__ = "IRGamma" IRGammaParallel = RiskMeasure(name="IRGammaParallel", asset_class=AssetClass("Rates"), measure_type=RiskMeasureType("ParallelGamma")) IRGammaParallel.__doc__ = "Change in aggregated IRDelta for a aggregated 1bp shift in the interest rate instruments used to build the underlying discount curve" IRGammaParallelLocalCcy = RiskMeasure(name="IRGammaParallelLocalCcy", asset_class=AssetClass("Rates"), measure_type=RiskMeasureType("ParallelGammaLocalCcy")) IRGammaParallelLocalCcy.__doc__ = "Interest Rate Parallel Gamma (Local Ccy)" IRSpotRate = RiskMeasure(name="IRSpotRate", asset_class=AssetClass("Rates"), measure_type=RiskMeasureType("Spot Rate"), unit=RiskMeasureUnit("Percent")) IRSpotRate.__doc__ = "Interest rate at-the-money spot rate (in percent)" IRVanna = RiskMeasure(name="IRVanna", asset_class=AssetClass("Rates"), measure_type=RiskMeasureType("Vanna")) IRVanna.__doc__ = "Interest Rate Vanna (USD)" IRVega = RiskMeasureWithFiniteDifferenceParameter(name="IRVega", asset_class=AssetClass("Rates"), measure_type=RiskMeasureType("Vega")) IRVega.__doc__ = "Change in Dollar Price (USD present value) due to individual 1bp moves in the implied volatility (IRAnnualImpliedVol) of instruments used to build the volatility surface" IRVolga = RiskMeasure(name="IRVolga", asset_class=AssetClass("Rates"), measure_type=RiskMeasureType("Volga")) IRVolga.__doc__ = "Interest Rate Volga (USD)" IRXccyDelta = RiskMeasureWithFiniteDifferenceParameter(name="IRXccyDelta", asset_class=AssetClass("Rates"), measure_type=RiskMeasureType("XccyDelta")) IRXccyDelta.__doc__ = "Change in Price due to 1bp move in cross currency rates." InflDeltaParallelLocalCcyInBps = RiskMeasure(name="InflDeltaParallelLocalCcyInBps", asset_class=AssetClass("Rates"), measure_type=RiskMeasureType("Inflation Delta in Bps")) InflDeltaParallelLocalCcyInBps.__doc__ = "Inflation Delta" InflMaturityCPI = RiskMeasure(name="InflMaturityCPI", asset_class=AssetClass("Rates"), measure_type=RiskMeasureType("FinalCPI")) InflMaturityCPI.__doc__ = "InflMaturityCPI" Infl_CompPeriod = RiskMeasure(name="Infl_CompPeriod", asset_class=AssetClass("Rates"), measure_type=RiskMeasureType("Inflation Compounding Period")) Infl_CompPeriod.__doc__ = "Infl_CompPeriod" InflationDelta = RiskMeasureWithFiniteDifferenceParameter(name="InflationDelta", asset_class=AssetClass("Rates"), measure_type=RiskMeasureType("InflationDelta")) InflationDelta.__doc__ = "Change in Price due to 1bp move in inflation curve." LightningDV01 = RiskMeasure(name="LightningDV01", measure_type=RiskMeasureType("DV01")) LightningDV01.__doc__ = "LightningDV01" LightningOAS = RiskMeasure(name="LightningOAS", measure_type=RiskMeasureType("OAS")) LightningOAS.__doc__ = "LightningOAS" LocalAnnuityInCents = RiskMeasure(name="LocalAnnuityInCents", asset_class=AssetClass("Rates"), measure_type=RiskMeasureType("Local Currency Accrual in Cents")) LocalAnnuityInCents.__doc__ = "Local Currency Accrual in Cents" Market = RiskMeasure(name="Market", measure_type=RiskMeasureType("Market")) Market.__doc__ = "Market" MarketData = RiskMeasure(name="MarketData", measure_type=RiskMeasureType("Market Data")) MarketData.__doc__ = "Market Data" MarketDataAssets = RiskMeasure(name="MarketDataAssets", measure_type=RiskMeasureType("Market Data Assets")) MarketDataAssets.__doc__ = "MarketDataAssets" NonUSDOisDomRate = RiskMeasure(name="NonUSDOisDomRate", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("NonUSDOisDomesticRate")) NonUSDOisDomRate.__doc__ = "NonUSDOisDomRate" OisFXSprExSpkRate = RiskMeasure(name="OisFXSprExSpkRate", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("OisFXSpreadRateExcludingSpikes")) OisFXSprExSpkRate.__doc__ = "OisFXSprExSpkRate" OisFXSprRate = RiskMeasure(name="OisFXSprRate", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("OisFXSpreadRate")) OisFXSprRate.__doc__ = "OisFXSprRate" ParSpread = RiskMeasure(name="ParSpread", asset_class=AssetClass("Rates"), measure_type=RiskMeasureType("Spread")) ParSpread.__doc__ = "Par Spread" PremiumCents = RiskMeasure(name="PremiumCents", asset_class=AssetClass("Rates"), measure_type=RiskMeasureType("Premium In Cents")) PremiumCents.__doc__ = "PremiumCents" PremiumSummary = RiskMeasure(name="PremiumSummary", asset_class=AssetClass("Commod"), measure_type=RiskMeasureType("Premium")) PremiumSummary.__doc__ = "PremiumSummary" Price = RiskMeasureWithCurrencyParameter(name="Price", measure_type=RiskMeasureType("PV")) Price.__doc__ = "Present Value" PricePips = RiskMeasureWithCurrencyParameter(name="PricePips", measure_type=RiskMeasureType("Price"), unit=RiskMeasureUnit("Pips")) PricePips.__doc__ = "Present value in pips" ProbabilityOfExercise = RiskMeasure(name="ProbabilityOfExercise", measure_type=RiskMeasureType("Probability Of Exercise")) ProbabilityOfExercise.__doc__ = "Probability Of Exercise" RFRFXRate = RiskMeasure(name="RFRFXRate", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("RFRFXRate")) RFRFXRate.__doc__ = "RFRFXRate" RFRFXSprExSpkRate = RiskMeasure(name="RFRFXSprExSpkRate", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("RFRFXSpreadRateExcludingSpikes")) RFRFXSprExSpkRate.__doc__ = "RFRFXSprExSpkRate" RFRFXSprRate = RiskMeasure(name="RFRFXSprRate", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("RFRFXSpreadRate")) RFRFXSprRate.__doc__ = "RFRFXSprRate" ResolvedInstrumentValues = RiskMeasure(name="ResolvedInstrumentValues", measure_type=RiskMeasureType("Resolved Instrument Values")) ResolvedInstrumentValues.__doc__ = "Resolved InstrumentBase Values" Theta = RiskMeasure(name="Theta", measure_type=RiskMeasureType("Theta")) Theta.__doc__ = "Theta" USDOisDomRate = RiskMeasure(name="USDOisDomRate", asset_class=AssetClass("FX"), measure_type=RiskMeasureType("USDOisDomesticRate")) USDOisDomRate.__doc__ = "USDOisDomRate" ================================================ FILE: gs_quant/target/monitor.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.base import * from gs_quant.common import * import datetime from typing import Dict, Optional, Tuple, Union from dataclasses import dataclass, field from dataclasses_json import LetterCase, config, dataclass_json from enum import Enum class AvailableUnitTypes(EnumBase, Enum): """Enum listing supported unit types""" percentage = 'percentage' percentageWithSymbol = 'percentageWithSymbol' bps = 'bps' bp = 'bp' x = 'x' class EntitiesSupported(EnumBase, Enum): """Enum listing supported entities""" assets = 'assets' tds = 'tds' class ParameterPeriod(EnumBase, Enum): """Enum listing supported parameter periods""" _1d = '1d' _1w = '1w' _1m = '1m' _3m = '3m' _6m = '6m' _1y = '1y' _2y = '2y' _5y = '5y' _10y = '10y' _30y = '30y' mtd = 'mtd' ytd = 'ytd' class ParameterRender(EnumBase, Enum): """Enum listing supported column definition render types""" bar = 'bar' boxplot = 'boxplot' chart = 'chart' color = 'color' default = 'default' direction = 'direction' heatmap = 'heatmap' hidden = 'hidden' multiColumnHeatmap = 'multiColumnHeatmap' progress = 'progress' range = 'range' scale = 'scale' simpleCandlestick = 'simpleCandlestick' sparkline = 'sparkline' stackedBarChart = 'stackedBarChart' text = 'text' treemap = 'treemap' triColor = 'triColor' class RateIds(EnumBase, Enum): """Enum listing supported rate ids""" USD = 'USD' EUR = 'EUR' JPY = 'JPY' GBP = 'GBP' CAD = 'CAD' AUD = 'AUD' class SortDirection(EnumBase, Enum): """Enum with available sort directions""" asc = 'asc' desc = 'desc' default = 'default' class SortType(EnumBase, Enum): """Enum listing supported sort types""" value = 'value' abs = 'abs' class WipiFilterOperation(EnumBase, Enum): """Enum listing supported operations for wipi filters.""" eq = 'eq' ne = 'ne' gt = 'gt' lt = 'lt' gte = 'gte' lte = 'lte' last = 'last' class WipiFilterType(EnumBase, Enum): """Enum listing supported wipi filter types.""" AND = 'AND' OR = 'OR' @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ColumnProperty(Base): column_name: Optional[str] = field(default=None, metadata=field_metadata) property_: Optional[str] = field(default=None, metadata=config(field_name='property', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) class FieldMap(DictBase): pass @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Historical(Base): value: Optional[Union[float, str]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MonitorResponseData(Base): id_: str = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) result: DictBase = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Movers(Base): column_name: str = field(default=None, metadata=field_metadata) top: Optional[float] = field(default=None, metadata=field_metadata) bottom: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ColumnFormat(Base): precision: float = field(default=None, metadata=field_metadata) unit: Optional[AvailableUnitTypes] = field(default=None, metadata=field_metadata) human_readable: Optional[bool] = field(default=None, metadata=field_metadata) multiplier: Optional[float] = field(default=None, metadata=field_metadata) axis_key: Optional[str] = field(default=None, metadata=field_metadata) show_tooltip: Optional[bool] = field(default=None, metadata=field_metadata) low_color: Optional[str] = field(default=None, metadata=field_metadata) high_color: Optional[str] = field(default=None, metadata=field_metadata) mid_color: Optional[str] = field(default=None, metadata=field_metadata) low_value: Optional[float] = field(default=None, metadata=field_metadata) high_value: Optional[float] = field(default=None, metadata=field_metadata) mid_value: Optional[float] = field(default=None, metadata=field_metadata) hide_value: Optional[bool] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ColumnMappings(Base): column_name: Optional[str] = field(default=None, metadata=field_metadata) parameters: Optional[FieldMap] = field(default=None, metadata=field_metadata) color: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ColumnOperation(Base): column_names: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) function_name: Optional[str] = field(default=None, metadata=field_metadata) type_: Optional[str] = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) parameters: Optional[FieldMap] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ExportParameters(Base): tokens: Tuple[str, ...] = field(default=None, metadata=field_metadata) data_set_id: Optional[str] = field(default=None, metadata=field_metadata) fields: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) label: Optional[str] = field(default=None, metadata=field_metadata) start_date: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Function(Base): measure: str = field(default=None, metadata=field_metadata) frequency: str = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=field_metadata) start_date: Optional[str] = field(default=None, metadata=field_metadata) end_date: Optional[str] = field(default=None, metadata=field_metadata) start_time: Optional[str] = field(default=None, metadata=field_metadata) end_time: Optional[str] = field(default=None, metadata=field_metadata) should_use_search_until: Optional[bool] = field(default=None, metadata=field_metadata) fields: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) parameters: Optional[FieldMap] = field(default=None, metadata=field_metadata) where: Optional[FieldMap] = field(default=None, metadata=field_metadata) vendor: Optional[str] = field(default=None, metadata=field_metadata) data_set_id: Optional[str] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class RateRow(Base): period: ParameterPeriod = field(default=None, metadata=field_metadata) last: float = field(default=None, metadata=field_metadata) change: float = field(default=None, metadata=field_metadata) std: float = field(default=None, metadata=field_metadata) slope: float = field(default=None, metadata=field_metadata) historical: Optional[Historical] = field(default=None, metadata=field_metadata) percentage_change: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Sort(Base): column_name: str = field(default=None, metadata=field_metadata) type_: Optional[SortType] = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) direction: Optional[SortDirection] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class WipiRequestFilter(Base): column: str = field(default=None, metadata=field_metadata) operation: WipiFilterOperation = field(default=None, metadata=field_metadata) value: Union[float, str] = field(default=None, metadata=field_metadata) type_: Optional[WipiFilterType] = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ColumnDefinition(Base): render: ParameterRender = field(default=None, metadata=field_metadata) name: str = field(default=None, metadata=field_metadata) enable_cell_flashing: Optional[bool] = field(default=None, metadata=field_metadata) entity_property: Optional[str] = field(default=None, metadata=field_metadata) function: Optional[Function] = field(default=None, metadata=field_metadata) format_: Optional[ColumnFormat] = field(default=None, metadata=config(field_name='format', exclude=exclude_none)) width: Optional[float] = field(default=None, metadata=field_metadata) column_property: Optional[ColumnProperty] = field(default=None, metadata=field_metadata) column_operation: Optional[ColumnOperation] = field(default=None, metadata=field_metadata) expression: Optional[str] = field(default=None, metadata=field_metadata) expressions: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) start_date: Optional[str] = field(default=None, metadata=field_metadata) end_date: Optional[str] = field(default=None, metadata=field_metadata) tooltip: Optional[str] = field(default=None, metadata=field_metadata) parent_column_name: Optional[str] = field(default=None, metadata=field_metadata) primary: Optional[bool] = field(default=None, metadata=field_metadata) pivots: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) disable_cell_tooltips: Optional[bool] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class EntityId(Base): id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) column_mappings: Optional[Tuple[ColumnMappings, ...]] = field(default=None, metadata=field_metadata) color: Optional[str] = field(default=None, metadata=field_metadata) route_url: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class RatesResponseData(Base): name: RateIds = field(default=None, metadata=field_metadata) id_: str = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) rows: Tuple[RateRow, ...] = field(default=None, metadata=field_metadata) libor_id: Optional[str] = field(default=None, metadata=config(field_name='libor_id', exclude=exclude_none)) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class RowGroup(Base): name: str = field(default=None, metadata=field_metadata) entity_ids: Tuple[EntityId, ...] = field(default=None, metadata=field_metadata) movers: Optional[Movers] = field(default=None, metadata=field_metadata) sort: Optional[Sort] = field(default=None, metadata=field_metadata) export: Optional[ExportParameters] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MonitorParameters(Base): column_definitions: Tuple[ColumnDefinition, ...] = field(default=None, metadata=field_metadata) row_groups: Tuple[RowGroup, ...] = field(default=None, metadata=field_metadata) export: Optional[ExportParameters] = field(default=None, metadata=field_metadata) ignore_business_day_logic: Optional[bool] = field(default=None, metadata=field_metadata) horizontal_scroll: Optional[bool] = field(default=None, metadata=field_metadata) mid_value_average: Optional[bool] = field(default=None, metadata=field_metadata) aggregate_queries: Optional[bool] = field(default=None, metadata=field_metadata) row_heatmap: Optional[bool] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Monitor(Base): name: str = field(default=None, metadata=field_metadata) type_: EntitiesSupported = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) parameters: Optional[MonitorParameters] = field(default=None, metadata=field_metadata) created_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) created_by_id: Optional[str] = field(default=None, metadata=field_metadata) last_updated_by_id: Optional[str] = field(default=None, metadata=field_metadata) owner_id: Optional[str] = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) folder_name: Optional[str] = field(default=None, metadata=field_metadata) polling_time: Optional[float] = field(default=None, metadata=field_metadata) tags: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) ================================================ FILE: gs_quant/target/portfolios.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.base import * from gs_quant.common import * import datetime from typing import Dict, Optional, Tuple, Union from dataclasses import dataclass, field from dataclasses_json import LetterCase, config, dataclass_json from enum import Enum from gs_quant.target.reports import Report class ActiveWeightType(EnumBase, Enum): """Weight type used to calculate active holdings.""" Net = 'Net' Gross = 'Gross' class ClientPositionFilter(EnumBase, Enum): """Filter used to select client positions from GRDB. 'oeId' selects all positions associated with the provided oeId. 'oeIdOrClientAccounts' selects positions associated with their the provided oeId or ClientAccounts. 'clientAccounts' selects positions only associated with those provided accounts.""" oeId = 'oeId' clientAccounts = 'clientAccounts' oeIdOrClientAccounts = 'oeIdOrClientAccounts' class PortfolioType(EnumBase, Enum): """Portfolio type differentiates the portfolio categorization""" Securities_Lending = 'Securities Lending' Draft_Portfolio = 'Draft Portfolio' Draft_Bond = 'Draft Bond' PCO_Portfolio = 'PCO Portfolio' PCO_Share_Class = 'PCO Share Class' class RefreshInterval(EnumBase, Enum): """These intervals determine how often a portfolio is refreshed""" Daily = 'Daily' Start_Of_Week = 'Start Of Week' End_Of_Week = 'End Of Week' Start_Of_Month = 'Start Of Month' End_Of_Month = 'End Of Month' class RiskAumSource(EnumBase, Enum): """Source of AUM for portfolio risk calculations.""" Gross = 'Gross' Long = 'Long' Short = 'Short' Custom_AUM = 'Custom AUM' Net = 'Net' @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SecDbBookDetail(Base): book_id: Optional[str] = field(default=None, metadata=field_metadata) book_type: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CreditPreTradePortfolioParameters(Base): date: Optional[datetime.date] = field(default=None, metadata=field_metadata) currency: Optional[Currency] = field(default=None, metadata=field_metadata) reference_id: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class GRDBPortfolioParameters(Base): oe_id: str = field(default=None, metadata=field_metadata) client_name: str = field(default=None, metadata=field_metadata) increment: str = field(default=None, metadata=field_metadata) risk_packages: Tuple[str, ...] = field(default=None, metadata=field_metadata) enabled: str = field(default=None, metadata=field_metadata) is_live: str = field(default=None, metadata=field_metadata) client_account_names: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) oasis_account_names: Optional[Tuple[str, ...]] = field(default=None, metadata=config(field_name='OasisAccountNames', exclude=exclude_none)) client_position_filter: Optional[ClientPositionFilter] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PCOTrade(Base): fill_rate: Optional[str] = field(default=None, metadata=field_metadata) include_in_hedge: Optional[bool] = field(default=None, metadata=field_metadata) settlement_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) spot_ref: Optional[str] = field(default=None, metadata=field_metadata) notes: Optional[str] = field(default=None, metadata=field_metadata) creation_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) base_currency: Optional[Currency] = field(default=None, metadata=field_metadata) quote_currency: Optional[Currency] = field(default=None, metadata=field_metadata) base_currency_notional: Optional[str] = field(default=None, metadata=field_metadata) quote_currency_notional: Optional[str] = field(default=None, metadata=field_metadata) trade_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) fixing_ref: Optional[str] = field(default=None, metadata=field_metadata) side: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class TemporalPortfolioParameters(Base): return_type: Optional[ReturnType] = field(default=None, metadata=field_metadata) refresh_interval: Optional[RefreshInterval] = field(default=None, metadata=field_metadata) active_weight_type: Optional[ActiveWeightType] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PCOPortfolioParameters(Base): base_currency: Optional[Currency] = field(default=None, metadata=field_metadata) local_currency: Optional[Currency] = field(default=None, metadata=field_metadata) fund_calendar: Optional[str] = field(default=None, metadata=field_metadata) calculation_currency: Optional[PCOCurrencyType] = field(default=None, metadata=field_metadata) hedge_settlement_interval: Optional[Tuple[PCOParameterValues, ...]] = field(default=None, metadata=field_metadata) hedge_settlement_day: Optional[Tuple[PCOParameterValues, ...]] = field(default=None, metadata=field_metadata) roll_horizon: Optional[Tuple[PCOParameterValues, ...]] = field(default=None, metadata=field_metadata) pnl_currency: Optional[Tuple[PCOParameterValues, ...]] = field(default=None, metadata=field_metadata) nav_publication_period: Optional[Tuple[PCOParameterValues, ...]] = field(default=None, metadata=field_metadata) roll_date_zero_threshold: Optional[bool] = field(default=None, metadata=field_metadata) unrealised_mark_to_market: Optional[PCOUnrealisedMarkToMarket] = field(default=None, metadata=field_metadata) target_deviation: Optional[Tuple[PCOTargetDeviation, ...]] = field(default=None, metadata=field_metadata) cash_balances: Optional[Tuple[PCOCashBalance, ...]] = field(default=None, metadata=field_metadata) exposure: Optional[PCOExposure] = field(default=None, metadata=field_metadata) pco_share_class: Optional[PCOShareClass] = field(default=None, metadata=field_metadata) settlements: Optional[Tuple[PCOSettlements, ...]] = field(default=None, metadata=field_metadata) show_cash: Optional[bool] = field(default=None, metadata=field_metadata) show_exposure: Optional[bool] = field(default=None, metadata=field_metadata) enable_rfq: Optional[bool] = field(default=None, metadata=config(field_name='enableRFQ', exclude=exclude_none)) fixing_descriptions: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) pco_origin: Optional[PCOOrigin] = field(default=None, metadata=field_metadata) version: Optional[str] = field(default=None, metadata=field_metadata) trades: Optional[Tuple[PCOTrade, ...]] = field(default=None, metadata=field_metadata) investment_ratio: Optional[str] = field(default=None, metadata=field_metadata) roll_currency: Optional[Tuple[PCOParameterValues, ...]] = field(default=None, metadata=field_metadata) param_version: Optional[str] = field(default=None, metadata=field_metadata) security_breakdown: Optional[PCOSecurityBreakdown] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Portfolio(Base): currency: Currency = field(default=None, metadata=field_metadata) name: str = field(default=None, metadata=field_metadata) id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) created_by_id: Optional[str] = field(default=None, metadata=field_metadata) created_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) description: Optional[str] = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) entitlement_exclusions: Optional[EntitlementExclusions] = field(default=None, metadata=field_metadata) identifiers: Optional[Tuple[Identifier, ...]] = field(default=None, metadata=field_metadata) last_updated_by_id: Optional[str] = field(default=None, metadata=field_metadata) last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) owner_id: Optional[str] = field(default=None, metadata=field_metadata) report_ids: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) scenario_ids: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) short_name: Optional[str] = field(default=None, metadata=field_metadata) underlying_portfolio_ids: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) tags: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) type_: Optional[PortfolioType] = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) parameters: Optional[Union[CreditPreTradePortfolioParameters, GRDBPortfolioParameters, LiquidityRequest, PCOPortfolioParameters, TemporalPortfolioParameters, Tuple[SecDbBookDetail, ...]]] = field(default=None, metadata=field_metadata) aum_source: Optional[RiskAumSource] = field(default=None, metadata=field_metadata) tag_name_hierarchy: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PortfolioTree(Base): tag_name: Optional[str] = field(default=None, metadata=field_metadata) tag_value: Optional[str] = field(default=None, metadata=field_metadata) leaf_tag_name: Optional[str] = field(default=None, metadata=field_metadata) reports: Optional[Tuple[Report, ...]] = field(default=None, metadata=field_metadata) # Using forward reference via string instead of Lazy annotations because latter doesn't work very well with # dataclasses - https://bugs.python.org/issue39442 sub_portfolios: Optional[Tuple['PortfolioTree', ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) ================================================ FILE: gs_quant/target/positions_v2_pricing.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.base import * from gs_quant.common import * import datetime from typing import Dict, Optional, Tuple, Union from dataclasses import dataclass, field from dataclasses_json import LetterCase, config, dataclass_json @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PositionsPricingParameters(Base): currency: str = field(default=None, metadata=field_metadata) weighting_strategy: str = field(default=None, metadata=field_metadata) carryover_positions_for_missing_dates: Optional[bool] = field(default=False, metadata=field_metadata) should_reweight: Optional[bool] = field(default=False, metadata=field_metadata) allow_fractional_shares: Optional[bool] = field(default=True, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PositionsPricingRequest(Base): parameters: PositionsPricingParameters = field(default=None, metadata=field_metadata) position_sets: Tuple[PositionSetRequest, ...] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) ================================================ FILE: gs_quant/target/price.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.base import * from gs_quant.common import * import datetime from typing import Dict, Optional, Tuple, Union from dataclasses import dataclass, field from dataclasses_json import LetterCase, config, dataclass_json from enum import Enum class BasketPricingEngine(EnumBase, Enum): """Pricing engine or baskets""" Solactive = 'Solactive' Midas = 'Midas' @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PositionPriceResponse(Base): asset_id: Optional[str] = field(default=None, metadata=field_metadata) quantity: Optional[float] = field(default=None, metadata=field_metadata) reference_weight: Optional[float] = field(default=None, metadata=field_metadata) weight: Optional[float] = field(default=None, metadata=field_metadata) fx_spot: Optional[float] = field(default=None, metadata=field_metadata) spot: Optional[float] = field(default=None, metadata=field_metadata) multiplier: Optional[float] = field(default=None, metadata=field_metadata) notional: Optional[float] = field(default=None, metadata=field_metadata) market_cap: Optional[float] = field(default=None, metadata=field_metadata) borrow_cost: Optional[float] = field(default=None, metadata=field_metadata) hard_to_borrow: Optional[bool] = field(default=None, metadata=field_metadata) composite5_day_adv: Optional[float] = field(default=None, metadata=field_metadata) composite10_day_adv: Optional[float] = field(default=None, metadata=field_metadata) composite22_day_adv: Optional[float] = field(default=None, metadata=field_metadata) adv5_day_pct: Optional[float] = field(default=None, metadata=field_metadata) adv10_day_pct: Optional[float] = field(default=None, metadata=field_metadata) adv22_day_pct: Optional[float] = field(default=None, metadata=field_metadata) median_daily_volume22_day: Optional[float] = field(default=None, metadata=field_metadata) tags: Optional[Tuple[PositionTag, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PriceParameters(Base): currency: Currency = field(default=None, metadata=field_metadata) asset_data_set_id: Optional[str] = field(default=None, metadata=field_metadata) divisor: Optional[float] = field(default=None, metadata=field_metadata) fractional_shares: Optional[bool] = field(default=False, metadata=field_metadata) price_regardless_of_assets_missing_prices: Optional[bool] = field(default=False, metadata=field_metadata) frequency: Optional[MarketDataFrequency] = field(default=None, metadata=field_metadata) fx_data_set_id: Optional[str] = field(default=None, metadata=field_metadata) initial_price: Optional[float] = field(default=None, metadata=field_metadata) target_notional: Optional[float] = field(default=None, metadata=field_metadata) pricing_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) fallback_date: Optional[str] = field(default=None, metadata=field_metadata) vendor: Optional[MarketDataVendor] = field(default=None, metadata=field_metadata) use_unadjusted_close_price: Optional[bool] = field(default=False, metadata=field_metadata) use_exchange_currency: Optional[bool] = field(default=False, metadata=field_metadata) weighting_strategy: Optional[PositionSetWeightingStrategy] = field(default=None, metadata=field_metadata) notional_type: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PositionSetPriceInput(Base): positions: Tuple[PositionPriceInput, ...] = field(default=None, metadata=field_metadata) parameters: PriceParameters = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PositionSetPriceResponse(Base): positions: Optional[Tuple[PositionPriceResponse, ...]] = field(default=None, metadata=field_metadata) divisor: Optional[float] = field(default=None, metadata=field_metadata) initial_price: Optional[float] = field(default=None, metadata=field_metadata) target_notional: Optional[float] = field(default=None, metadata=field_metadata) actual_notional: Optional[float] = field(default=None, metadata=field_metadata) long_notional: Optional[float] = field(default=None, metadata=field_metadata) short_notional: Optional[float] = field(default=None, metadata=field_metadata) gross_notional: Optional[float] = field(default=None, metadata=field_metadata) net_notional: Optional[float] = field(default=None, metadata=field_metadata) type_: Optional[str] = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) error_message: Optional[str] = field(default=None, metadata=field_metadata) asset_ids_missing_prices: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) asset_ids_missing_fx_fixings: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) asset_ids_missing_market_caps: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) unsupported_currencies: Optional[Tuple[Currency, ...]] = field(default=None, metadata=field_metadata) asset_ids_missing_multiplier: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) pricing_engine: Optional[BasketPricingEngine] = field(default=None, metadata=field_metadata) pricing_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) ================================================ FILE: gs_quant/target/reports.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.base import * from gs_quant.common import * import datetime from typing import Dict, Optional, Tuple, Union from dataclasses import dataclass, field from dataclasses_json import LetterCase, config, dataclass_json from enum import Enum class PositionSourceType(EnumBase, Enum): """Source object for position data""" Portfolio = 'Portfolio' Asset = 'Asset' Backtest = 'Backtest' RiskRequest = 'RiskRequest' Hedge = 'Hedge' class ReportGenerateTemplateId(EnumBase, Enum): """The Report Template ID to generate the report from.""" analytics_factor_attribution_pdf = 'analytics-factor-attribution-pdf' analytics_factor_risk_pdf = 'analytics-factor-risk-pdf' class ReportMeasures(EnumBase, Enum): """Enums for measures to be outputted for the report""" _ = '' pnl = 'pnl' longExposure = 'longExposure' shortExposure = 'shortExposure' assetCount = 'assetCount' turnover = 'turnover' assetCountLong = 'assetCountLong' assetCountShort = 'assetCountShort' netExposure = 'netExposure' grossExposure = 'grossExposure' tradingPnl = 'tradingPnl' tradingCostPnl = 'tradingCostPnl' servicingCostLongPnl = 'servicingCostLongPnl' servicingCostShortPnl = 'servicingCostShortPnl' exposure = 'exposure' sensitivity = 'sensitivity' mctr = 'mctr' annualizedMCTRisk = 'annualizedMCTRisk' annualizedRMCTRisk = 'annualizedRMCTRisk' poRisk = 'poRisk' price = 'price' basePrice = 'basePrice' class ReportStatus(EnumBase, Enum): """Status of report run""" new = 'new' ready = 'ready' executing = 'executing' calculating = 'calculating' done = 'done' error = 'error' cancelled = 'cancelled' waiting = 'waiting' queued = 'queued' class ReportType(EnumBase, Enum): """Type of report to execute""" Portfolio_Performance_Analytics = 'Portfolio Performance Analytics' Portfolio_Factor_Risk = 'Portfolio Factor Risk' Portfolio_Aging = 'Portfolio Aging' Portfolio_Thematic_Analytics = 'Portfolio Thematic Analytics' Asset_Factor_Risk = 'Asset Factor Risk' Asset_Thematic_Analytics = 'Asset Thematic Analytics' Basket_Create = 'Basket Create' Basket_Backcast = 'Basket Backcast' Basket_Rebalance_Auto_Approval = 'Basket Rebalance Auto Approval' Scenario = 'Scenario' Iselect_Backtest = 'Iselect Backtest' Backtest_Run = 'Backtest Run' Analytics = 'Analytics' Risk_Calculation = 'Risk Calculation' Factor_Overview_Email = 'Factor Overview Email' FactSet_Fundamentals_Analytics = 'FactSet Fundamentals Analytics' PCO = 'PCO' Systematic_Hedging = 'Systematic Hedging' @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AumTimeseriesDataPoint(Base): date: Optional[datetime.date] = field(default=None, metadata=field_metadata) aum: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AumTimeseries(Base): data: Optional[Tuple[AumTimeseriesDataPoint, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ParametersOverrides(Base): csa_term: Optional[str] = field(default=None, metadata=field_metadata) pricing_location: Optional[PricingLocation] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ReportGenerateRequest(Base): end_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) report_id: Optional[str] = field(default=None, metadata=field_metadata) start_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) template_id: Optional[ReportGenerateTemplateId] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ReportRescheduleRequest(Base): report_ids: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ReportWithParametersOverrides(Base): report_id: Optional[str] = field(default=None, metadata=field_metadata) parameters: Optional[ParametersOverrides] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ReportToggleEntityRequest(Base): type_: Optional[str] = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) user: Optional[User] = field(default=None, metadata=field_metadata) is_delete: Optional[bool] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Report(Base): position_source_id: str = field(default=None, metadata=field_metadata) position_source_type: PositionSourceType = field(default=None, metadata=field_metadata) type_: ReportType = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) parameters: ReportParameters = field(default=None, metadata=field_metadata) calculation_time: Optional[float] = field(default=None, metadata=field_metadata) data_set_id: Optional[str] = field(default=None, metadata=field_metadata) asset_id: Optional[str] = field(default=None, metadata=field_metadata) created_by_id: Optional[str] = field(default=None, metadata=field_metadata) created_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) earliest_start_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) entitlement_exclusions: Optional[EntitlementExclusions] = field(default=None, metadata=field_metadata) id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) last_updated_by_id: Optional[str] = field(default=None, metadata=field_metadata) last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) measures: Optional[Tuple[ReportMeasures, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=field_metadata) owner_id: Optional[str] = field(default=None, metadata=field_metadata) status: Optional[ReportStatus] = field(default=None, metadata=field_metadata) latest_execution_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) latest_end_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) percentage_complete: Optional[float] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ReportBatchScheduleRequest(Base): reports: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) reports_with_parameters_overrides: Optional[Tuple[ReportWithParametersOverrides, ...]] = field(default=None, metadata=field_metadata) time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) end_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) use_close_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) start_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) parameters: Optional[ReportParameters] = field(default=None, metadata=field_metadata) priority: Optional[ReportJobPriority] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ReportJob(Base): start_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) end_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) use_close_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) elapsed_time: Optional[float] = field(default=None, metadata=field_metadata) percentage_complete: Optional[float] = field(default=None, metadata=field_metadata) execution_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) measures: Optional[Tuple[ReportMeasures, ...]] = field(default=None, metadata=field_metadata) parameters: Optional[ReportParameters] = field(default=None, metadata=field_metadata) parent_id: Optional[str] = field(default=None, metadata=field_metadata) position_source_id: Optional[str] = field(default=None, metadata=field_metadata) position_source_type: Optional[PositionSourceType] = field(default=None, metadata=field_metadata) report_id: Optional[str] = field(default=None, metadata=field_metadata) report_type: Optional[ReportType] = field(default=None, metadata=field_metadata) status: Optional[ReportStatus] = field(default=None, metadata=field_metadata) owner_id: Optional[str] = field(default=None, metadata=field_metadata) created_by_id: Optional[str] = field(default=None, metadata=field_metadata) created_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) report_ids: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) priority: Optional[ReportJobPriority] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) ================================================ FILE: gs_quant/target/risk.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.base import * from gs_quant.common import * import datetime from typing import Dict, Optional, Tuple, Union from dataclasses import dataclass, field from dataclasses_json import LetterCase, config, dataclass_json from enum import Enum class FactorRiskTableMode(EnumBase, Enum): """View the data in the tables endpoint in the specified mode.""" Exposure = 'Exposure' ZScore = 'ZScore' Pnl = 'Pnl' Mctr = 'Mctr' class FactorScenarioType(EnumBase, Enum): """Marquee factor scenario type""" Factor_Shock = 'Factor Shock' Factor_Historical_Simulation = 'Factor Historical Simulation' class OptimizationStatus(EnumBase, Enum): """Optimization status.""" Running = 'Running' Completed = 'Completed' class OptimizationType(EnumBase, Enum): """Pretrade optimization algorithm type.""" APEX = 'APEX' class OptimizationUrgency(EnumBase, Enum): """Parameter which controls the urgency of executing the basket from very low to very high. Very High urgency tilts the schedule towards the benchmark, whereas Very Low would minimise cost, carrying a relatively higher risk to the benchmark.""" VERY_LOW = 'VERY_LOW' LOW = 'LOW' MEDIUM = 'MEDIUM' HIGH = 'HIGH' VERY_HIGH = 'VERY_HIGH' class RiskViewsUnit(EnumBase, Enum): """View data return type.""" Notional = 'Notional' Percent = 'Percent' class ShockUnit(EnumBase, Enum): """The unit of the shocks passed in.""" percent = 'percent' standardDeviation = 'standardDeviation' @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AdvCurveTick(Base): date: Optional[datetime.date] = field(default=None, metadata=field_metadata) value: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ExecutionCostForHorizon(Base): minutes_expired: Optional[int] = field(default=None, metadata=field_metadata) execution_cost: Optional[float] = field(default=None, metadata=field_metadata) execution_cost_long: Optional[float] = field(default=None, metadata=field_metadata) execution_cost_short: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FactorHistoricalSimulationScenarioParameters(Base): start_date: datetime.date = field(default=None, metadata=field_metadata) end_date: datetime.date = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FactorShock(Base): factor: str = field(default=None, metadata=field_metadata) shock: float = field(default=None, metadata=field_metadata) factor_category: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class LiquidityBucket(Base): name: Optional[str] = field(default=None, metadata=field_metadata) description: Optional[str] = field(default=None, metadata=field_metadata) net_exposure: Optional[float] = field(default=None, metadata=field_metadata) gross_exposure: Optional[float] = field(default=None, metadata=field_metadata) net_weight: Optional[float] = field(default=None, metadata=field_metadata) gross_weight: Optional[float] = field(default=None, metadata=field_metadata) transaction_cost: Optional[float] = field(default=None, metadata=field_metadata) marginal_cost: Optional[float] = field(default=None, metadata=field_metadata) adv22_day_pct: Optional[float] = field(default=None, metadata=field_metadata) number_of_positions: Optional[float] = field(default=None, metadata=field_metadata) beta_adjusted_exposure: Optional[float] = field(default=None, metadata=field_metadata) long_weight: Optional[float] = field(default=None, metadata=field_metadata) long_exposure: Optional[float] = field(default=None, metadata=field_metadata) long_transaction_cost: Optional[float] = field(default=None, metadata=field_metadata) long_marginal_cost: Optional[float] = field(default=None, metadata=field_metadata) long_adv22_day_pct: Optional[float] = field(default=None, metadata=field_metadata) long_number_of_positions: Optional[float] = field(default=None, metadata=field_metadata) long_beta_adjusted_exposure: Optional[float] = field(default=None, metadata=field_metadata) short_weight: Optional[float] = field(default=None, metadata=field_metadata) short_exposure: Optional[float] = field(default=None, metadata=field_metadata) short_transaction_cost: Optional[float] = field(default=None, metadata=field_metadata) short_marginal_cost: Optional[float] = field(default=None, metadata=field_metadata) short_adv22_day_pct: Optional[float] = field(default=None, metadata=field_metadata) short_number_of_positions: Optional[float] = field(default=None, metadata=field_metadata) short_beta_adjusted_exposure: Optional[float] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class LiquidityFactor(Base): name: Optional[str] = field(default=None, metadata=field_metadata) value: Optional[float] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class LiquiditySummarySection(Base): adv10_day_pct: Optional[float] = field(default=None, metadata=field_metadata) adv22_day_pct: Optional[float] = field(default=None, metadata=field_metadata) adv5_day_pct: Optional[float] = field(default=None, metadata=field_metadata) annualized_risk: Optional[float] = field(default=None, metadata=field_metadata) annualized_tracking_error: Optional[float] = field(default=None, metadata=field_metadata) beta: Optional[float] = field(default=None, metadata=field_metadata) beta_adjusted_exposure: Optional[float] = field(default=None, metadata=field_metadata) beta_adjusted_net_exposure: Optional[float] = field(default=None, metadata=field_metadata) bid_ask_spread: Optional[float] = field(default=None, metadata=field_metadata) correlation: Optional[float] = field(default=None, metadata=field_metadata) daily_risk: Optional[float] = field(default=None, metadata=field_metadata) daily_tracking_error: Optional[float] = field(default=None, metadata=field_metadata) est1_day_complete_pct: Optional[float] = field(default=None, metadata=field_metadata) five_day_price_change_bps: Optional[float] = field(default=None, metadata=field_metadata) gross_exposure: Optional[float] = field(default=None, metadata=field_metadata) marginal_cost: Optional[float] = field(default=None, metadata=field_metadata) market_cap: Optional[float] = field(default=None, metadata=field_metadata) minutes_to_trade100_pct: Optional[float] = field(default=None, metadata=field_metadata) net_exposure: Optional[float] = field(default=None, metadata=field_metadata) number_of_positions: Optional[float] = field(default=None, metadata=field_metadata) percent_in_benchmark: Optional[object] = field(default=None, metadata=field_metadata) transaction_cost: Optional[float] = field(default=None, metadata=field_metadata) weight_of_top_five_positions: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class OptimizationAssetAnalyticsDaily(Base): asset_id: str = field(default=None, metadata=field_metadata) trade_day_number: int = field(default=None, metadata=field_metadata) total_cost: float = field(default=None, metadata=field_metadata) total_variance_contribution: float = field(default=None, metadata=field_metadata) total_portfolio_risk_on_day: float = field(default=None, metadata=field_metadata) total_risk: float = field(default=None, metadata=field_metadata) cratos: float = field(default=None, metadata=field_metadata) adv: float = field(default=None, metadata=field_metadata) cluster_id: int = field(default=None, metadata=field_metadata) cluster_label: str = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class OptimizationAssetAnalyticsDayOne(Base): asset_id: str = field(default=None, metadata=field_metadata) auction_trade_percentage: float = field(default=None, metadata=field_metadata) auction_pov_percentage: float = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class OptimizationAssetAnalyticsIntraday(Base): period_number: int = field(default=None, metadata=field_metadata) trade_day_number: int = field(default=None, metadata=field_metadata) period_start_time: datetime.datetime = field(default=None, metadata=field_metadata) period_end_time: datetime.datetime = field(default=None, metadata=field_metadata) is_trading: bool = field(default=None, metadata=field_metadata) buy: float = field(default=None, metadata=field_metadata) sell: float = field(default=None, metadata=field_metadata) gross: float = field(default=None, metadata=field_metadata) net: float = field(default=None, metadata=field_metadata) trade_absolute: float = field(default=None, metadata=field_metadata) asset_id: str = field(default=None, metadata=field_metadata) volume: float = field(default=None, metadata=field_metadata) volatility: float = field(default=None, metadata=field_metadata) fx: float = field(default=None, metadata=field_metadata) price_local: float = field(default=None, metadata=field_metadata) currency: str = field(default=None, metadata=field_metadata) total_cost_spread: float = field(default=None, metadata=field_metadata) total_cost_volatility: float = field(default=None, metadata=field_metadata) total_cost_permanent: float = field(default=None, metadata=field_metadata) beta_historical: float = field(default=None, metadata=field_metadata) mcr: float = field(default=None, metadata=field_metadata) total_cost: float = field(default=None, metadata=field_metadata) adv_percentage: float = field(default=None, metadata=field_metadata) country: str = field(default=None, metadata=field_metadata) industry: str = field(default=None, metadata=field_metadata) sector: str = field(default=None, metadata=field_metadata) spread: float = field(default=None, metadata=field_metadata) region: str = field(default=None, metadata=field_metadata) region_minor: str = field(default=None, metadata=field_metadata) quantity: int = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class OptimizationCloseAuctionAnalytics(Base): exchange_city: str = field(default=None, metadata=field_metadata) trade_absolute: float = field(default=None, metadata=field_metadata) trade_net: float = field(default=None, metadata=field_metadata) gross: float = field(default=None, metadata=field_metadata) net: float = field(default=None, metadata=field_metadata) auction_pov_percentage: float = field(default=None, metadata=field_metadata) close_auction_start_time: datetime.datetime = field(default=None, metadata=field_metadata) number_of_assets: int = field(default=None, metadata=field_metadata) close_auction_trade_percentage: float = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class OptimizationClusterAnalytics(Base): cluster_id: int = field(default=None, metadata=field_metadata) cluster_label: str = field(default=None, metadata=field_metadata) gross: float = field(default=None, metadata=field_metadata) total_cost_bps: float = field(default=None, metadata=field_metadata) total_risk_bps: float = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class OptimizationClusterAnalyticsIntradayItem(Base): cluster_id: int = field(default=None, metadata=field_metadata) cluster_label: str = field(default=None, metadata=field_metadata) adv_percentage: float = field(default=None, metadata=field_metadata) gross_percentage: float = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class OptimizationEodCashPositionsItem(Base): trade_day_num: str = field(default=None, metadata=field_metadata) net: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class OptimizationExcludedAsset(Base): asset_id: str = field(default=None, metadata=field_metadata) security_type: str = field(default=None, metadata=field_metadata) quantity: int = field(default=None, metadata=field_metadata) reason: str = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class OptimizationFactorAnalyticsItem(Base): period_number: int = field(default=None, metadata=field_metadata) trade_day_number: int = field(default=None, metadata=field_metadata) period_start_time: datetime.datetime = field(default=None, metadata=field_metadata) period_end_time: datetime.datetime = field(default=None, metadata=field_metadata) factors: Tuple[DictBase, ...] = field(default=None, metadata=field_metadata) time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class OptimizationPortfolioAnalyticsDaily(Base): trade_day_number: int = field(default=None, metadata=field_metadata) estimated_cost_bps: float = field(default=None, metadata=field_metadata) completion_rate_percent: float = field(default=None, metadata=field_metadata) mean_expected_cost_versus_benchmark: float = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class OptimizationPortfolioAnalyticsIntraday(Base): period_number: int = field(default=None, metadata=field_metadata) trade_day_number: int = field(default=None, metadata=field_metadata) period_start_time: datetime.datetime = field(default=None, metadata=field_metadata) period_end_time: datetime.datetime = field(default=None, metadata=field_metadata) time: datetime.datetime = field(default=None, metadata=field_metadata) sell: float = field(default=None, metadata=field_metadata) buy: float = field(default=None, metadata=field_metadata) gross: float = field(default=None, metadata=field_metadata) net: float = field(default=None, metadata=field_metadata) trade_absolute: float = field(default=None, metadata=field_metadata) total_cost_spread: float = field(default=None, metadata=field_metadata) total_cost_volatility: float = field(default=None, metadata=field_metadata) total_cost_permanent: float = field(default=None, metadata=field_metadata) total_cost: float = field(default=None, metadata=field_metadata) adv_average_percentage: float = field(default=None, metadata=field_metadata) total_risk: float = field(default=None, metadata=field_metadata) factor_risk: float = field(default=None, metadata=field_metadata) specific_risk: float = field(default=None, metadata=field_metadata) diagonal_risk: float = field(default=None, metadata=field_metadata) total_risk_objective: float = field(default=None, metadata=field_metadata) factor_risk_objective: float = field(default=None, metadata=field_metadata) specific_risk_objective: float = field(default=None, metadata=field_metadata) diagonal_risk_objective: float = field(default=None, metadata=field_metadata) total_risk_bps: float = field(default=None, metadata=field_metadata) trade_percentage_cumulative_sum: float = field(default=None, metadata=field_metadata) net_period_percentage: float = field(default=None, metadata=field_metadata) total_cost_budget_percentage: float = field(default=None, metadata=field_metadata) total_risk_percentage: float = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class OptimizationPortfolioSummarySection(Base): position: float = field(default=None, metadata=field_metadata) number_of_assets: int = field(default=None, metadata=field_metadata) diagonal_risk: float = field(default=None, metadata=field_metadata) total_risk: float = field(default=None, metadata=field_metadata) factor_risk: float = field(default=None, metadata=field_metadata) specific_risk: float = field(default=None, metadata=field_metadata) historical_beta: float = field(default=None, metadata=field_metadata) spread: float = field(default=None, metadata=field_metadata) total_risk_bps: float = field(default=None, metadata=field_metadata) adv_average_percentage: float = field(default=None, metadata=field_metadata) adv_max_percentage: float = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class OptimizationTradedPosition(Base): asset_id: str = field(default=None, metadata=field_metadata) quantity: int = field(default=None, metadata=field_metadata) position: int = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PRateForHorizon(Base): minutes_expired: Optional[int] = field(default=None, metadata=field_metadata) participation_rate: Optional[float] = field(default=None, metadata=field_metadata) participation_rate_long: Optional[float] = field(default=None, metadata=field_metadata) participation_rate_short: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class RiskAtHorizon(Base): minutes_expired: Optional[int] = field(default=None, metadata=field_metadata) risk: Optional[int] = field(default=None, metadata=field_metadata) risk_long: Optional[float] = field(default=None, metadata=field_metadata) risk_short: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ScenarioGetManyRequestPathSchema(Base): id_: Optional[Tuple[str, ...]] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) limit: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) offset: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) propagate_shocks: Optional[Tuple[bool, ...]] = field(default=None, metadata=field_metadata) short_name: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) tags: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) factor_scenario_type: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) historical_simulation_end_date: Optional[Tuple[datetime.date, ...]] = field(default=None, metadata=field_metadata) last_updated_by_id: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) created_by_id: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) shocked_factor: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) owner_id: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) description: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) historical_simulation_start_date: Optional[Tuple[datetime.date, ...]] = field(default=None, metadata=field_metadata) shocked_factor_category: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) scenario_group_id: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class TradeCompleteAtHorizon(Base): minutes_expired: Optional[int] = field(default=None, metadata=field_metadata) positions_complete: Optional[int] = field(default=None, metadata=field_metadata) positions_complete_pct: Optional[float] = field(default=None, metadata=field_metadata) notional_complete_pct: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class FactorShockScenarioParameters(Base): risk_model: str = field(default=None, metadata=field_metadata) factor_shocks: Tuple[FactorShock, ...] = field(default=None, metadata=field_metadata) propagate_shocks: bool = field(default=None, metadata=field_metadata) shock_unit: Optional[ShockUnit] = field(default=ShockUnit.percent, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class LiquidityConstituent(Base): asset_id: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=field_metadata) exchange: Optional[str] = field(default=None, metadata=field_metadata) quantity: Optional[float] = field(default=None, metadata=field_metadata) gross_weight: Optional[float] = field(default=None, metadata=field_metadata) net_weight: Optional[float] = field(default=None, metadata=field_metadata) currency: Optional[Currency] = field(default=None, metadata=field_metadata) gross_exposure: Optional[float] = field(default=None, metadata=field_metadata) net_exposure: Optional[float] = field(default=None, metadata=field_metadata) transaction_cost: Optional[float] = field(default=None, metadata=field_metadata) marginal_cost: Optional[float] = field(default=None, metadata=field_metadata) country: Optional[str] = field(default=None, metadata=field_metadata) region: Optional[Region] = field(default=None, metadata=field_metadata) type_: Optional[AssetType] = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) market_cap_bucket: Optional[object] = field(default=None, metadata=field_metadata) est1_day_complete_pct: Optional[float] = field(default=None, metadata=field_metadata) in_benchmark: Optional[bool] = field(default=None, metadata=field_metadata) in_risk_model: Optional[bool] = field(default=None, metadata=field_metadata) in_cost_predict_model: Optional[bool] = field(default=None, metadata=field_metadata) beta: Optional[float] = field(default=None, metadata=field_metadata) daily_risk: Optional[float] = field(default=None, metadata=field_metadata) annualized_risk: Optional[float] = field(default=None, metadata=field_metadata) one_day_price_change_pct: Optional[float] = field(default=None, metadata=field_metadata) beta_adjusted_exposure: Optional[float] = field(default=None, metadata=field_metadata) adv_bucket: Optional[object] = field(default=None, metadata=field_metadata) settlement_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) transition_plan_transparency_percentile: Optional[float] = field(default=None, metadata=field_metadata) transition_performance_percentile: Optional[float] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class LiquidityFactorCategory(Base): name: Optional[str] = field(default=None, metadata=field_metadata) sub_factors: Optional[Tuple[LiquidityFactor, ...]] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class LiquiditySummary(Base): total: LiquiditySummarySection = field(default=None, metadata=field_metadata) long: Optional[LiquiditySummarySection] = field(default=None, metadata=field_metadata) short: Optional[LiquiditySummarySection] = field(default=None, metadata=field_metadata) long_vs_short: Optional[LiquiditySummarySection] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class OptimizationClusterAnalyticsIntraday(Base): time: datetime.datetime = field(default=None, metadata=field_metadata) period_number: int = field(default=None, metadata=field_metadata) trade_day_number: int = field(default=None, metadata=field_metadata) clusters: Tuple[OptimizationClusterAnalyticsIntradayItem, ...] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class OptimizationEodCashPositions(Base): currency: str = field(default=None, metadata=field_metadata) positions: Tuple[OptimizationEodCashPositionsItem, ...] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class OptimizationPortfolioCharacteristics(Base): sell: OptimizationPortfolioSummarySection = field(default=None, metadata=field_metadata) buy: OptimizationPortfolioSummarySection = field(default=None, metadata=field_metadata) net: OptimizationPortfolioSummarySection = field(default=None, metadata=field_metadata) gross: OptimizationPortfolioSummarySection = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ShockScopeFilter(Base): region: Optional[Region] = field(default=None, metadata=field_metadata) asset_id: Optional[str] = field(default=None, metadata=field_metadata) gics_sector: Optional[str] = field(default=None, metadata=field_metadata) country_code: Optional[CountryCode] = field(default=None, metadata=field_metadata) gics_industry_group: Optional[str] = field(default=None, metadata=field_metadata) gics_industry: Optional[str] = field(default=None, metadata=field_metadata) gics_sub_industry: Optional[str] = field(default=None, metadata=field_metadata) is_investment_grade: Optional[bool] = field(default=None, metadata=field_metadata) tenor: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class LiquidityTableRow(Base): asset_id: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=field_metadata) adv22_day_pct: Optional[float] = field(default=None, metadata=field_metadata) shares: Optional[float] = field(default=None, metadata=field_metadata) net_weight: Optional[float] = field(default=None, metadata=field_metadata) gross_weight: Optional[float] = field(default=None, metadata=field_metadata) gross_exposure: Optional[float] = field(default=None, metadata=field_metadata) net_exposure: Optional[float] = field(default=None, metadata=field_metadata) transaction_cost: Optional[float] = field(default=None, metadata=field_metadata) marginal_cost: Optional[float] = field(default=None, metadata=field_metadata) one_day_price_change_pct: Optional[float] = field(default=None, metadata=field_metadata) normalized_performance: Optional[Tuple[Tuple[Union[datetime.date, float], ...], ...]] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class LiquidityTimeSeriesItem(Base): name: Optional[str] = field(default=None, metadata=field_metadata) normalized_performance: Optional[Tuple[Tuple[Union[datetime.date, float], ...], ...]] = field(default=None, metadata=field_metadata) annualized_return: Optional[Tuple[Tuple[Union[datetime.date, float], ...], ...]] = field(default=None, metadata=field_metadata) annualized_correlation: Optional[Tuple[Tuple[Union[datetime.date, float], ...], ...]] = field(default=None, metadata=field_metadata) annualized_volatility: Optional[Tuple[Tuple[Union[datetime.date, float], ...], ...]] = field(default=None, metadata=field_metadata) annualized_sharp_ratio: Optional[Tuple[Tuple[Union[datetime.date, float], ...], ...]] = field(default=None, metadata=field_metadata) annualized_tracking_error: Optional[Tuple[Tuple[Union[datetime.date, float], ...], ...]] = field(default=None, metadata=field_metadata) max_drawdown: Optional[Tuple[Tuple[Union[datetime.date, float], ...], ...]] = field(default=None, metadata=field_metadata) net_exposure: Optional[Tuple[Tuple[Union[datetime.date, float], ...], ...]] = field(default=None, metadata=field_metadata) cumulative_pnl: Optional[Tuple[Tuple[Union[datetime.date, float], ...], ...]] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class OptimizationFactorAnalyticsIntraday(Base): country: Tuple[OptimizationFactorAnalyticsItem, ...] = field(default=None, metadata=field_metadata) sector: Tuple[OptimizationFactorAnalyticsItem, ...] = field(default=None, metadata=field_metadata) domestic_china: Tuple[OptimizationFactorAnalyticsItem, ...] = field(default=None, metadata=field_metadata) market: Tuple[OptimizationFactorAnalyticsItem, ...] = field(default=None, metadata=field_metadata) currency: Tuple[OptimizationFactorAnalyticsItem, ...] = field(default=None, metadata=field_metadata) industry: Tuple[OptimizationFactorAnalyticsItem, ...] = field(default=None, metadata=field_metadata) risk: Tuple[OptimizationFactorAnalyticsItem, ...] = field(default=None, metadata=field_metadata) cluster_classification: Tuple[OptimizationFactorAnalyticsItem, ...] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class OptimizationTradeSchedule(Base): period_number: int = field(default=None, metadata=field_metadata) trade_day_number: int = field(default=None, metadata=field_metadata) period_start_time: datetime.datetime = field(default=None, metadata=field_metadata) period_end_time: datetime.datetime = field(default=None, metadata=field_metadata) traded_positions: Tuple[OptimizationTradedPosition, ...] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ShockScope(Base): asset_class: Optional[AssetClass] = field(default=None, metadata=field_metadata) type_: Optional[str] = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) filter_: Optional[ShockScopeFilter] = field(default=None, metadata=config(field_name='filter', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class LiquidityResponse(Base): as_of_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) risk_model: Optional[str] = field(default=None, metadata=field_metadata) notional: Optional[float] = field(default=None, metadata=field_metadata) currency: Optional[Currency] = field(default=None, metadata=field_metadata) report: Optional[str] = field(default=None, metadata=field_metadata) summary: Optional[LiquiditySummary] = field(default=None, metadata=field_metadata) constituent_transaction_costs: Optional[Tuple[LiquidityConstituent, ...]] = field(default=None, metadata=field_metadata) constituents: Optional[Tuple[LiquidityConstituent, ...]] = field(default=None, metadata=field_metadata) largest_holdings_by_weight: Optional[Tuple[LiquidityTableRow, ...]] = field(default=None, metadata=field_metadata) least_liquid_holdings: Optional[Tuple[LiquidityTableRow, ...]] = field(default=None, metadata=field_metadata) adv_buckets: Optional[Tuple[LiquidityBucket, ...]] = field(default=None, metadata=field_metadata) region_buckets: Optional[Tuple[LiquidityBucket, ...]] = field(default=None, metadata=field_metadata) country_buckets: Optional[Tuple[LiquidityBucket, ...]] = field(default=None, metadata=field_metadata) sector_buckets: Optional[Tuple[LiquidityBucket, ...]] = field(default=None, metadata=field_metadata) industry_buckets: Optional[Tuple[LiquidityBucket, ...]] = field(default=None, metadata=field_metadata) market_cap_buckets: Optional[Tuple[LiquidityBucket, ...]] = field(default=None, metadata=field_metadata) currency_buckets: Optional[Tuple[LiquidityBucket, ...]] = field(default=None, metadata=field_metadata) execution_costs_with_different_time_horizons: Optional[Tuple[ExecutionCostForHorizon, ...]] = field(default=None, metadata=field_metadata) time_to_trade_with_different_participation_rates: Optional[Tuple[PRateForHorizon, ...]] = field(default=None, metadata=field_metadata) risk_over_time: Optional[Tuple[RiskAtHorizon, ...]] = field(default=None, metadata=field_metadata) trade_complete_percent_over_time: Optional[Tuple[TradeCompleteAtHorizon, ...]] = field(default=None, metadata=field_metadata) adv_percent_over_time: Optional[Tuple[AdvCurveTick, ...]] = field(default=None, metadata=field_metadata) risk_buckets: Optional[Tuple[LiquidityFactor, ...]] = field(default=None, metadata=field_metadata) factor_risk_buckets: Optional[Tuple[LiquidityFactorCategory, ...]] = field(default=None, metadata=field_metadata) exposure_buckets: Optional[Tuple[LiquidityFactor, ...]] = field(default=None, metadata=field_metadata) factor_exposure_buckets: Optional[Tuple[LiquidityFactorCategory, ...]] = field(default=None, metadata=field_metadata) timeseries_data: Optional[Tuple[LiquidityTimeSeriesItem, ...]] = field(default=None, metadata=field_metadata) assets_not_in_risk_model: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) assets_not_in_cost_predict_model: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) assets_without_compositions: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) error_message: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class OptimizationAnalytics(Base): portfolio_characteristics: OptimizationPortfolioCharacteristics = field(default=None, metadata=field_metadata) asset_analytics_daily: Tuple[OptimizationAssetAnalyticsDaily, ...] = field(default=None, metadata=field_metadata) portfolio_analytics_daily: Tuple[OptimizationPortfolioAnalyticsDaily, ...] = field(default=None, metadata=field_metadata) assets_excluded: Tuple[OptimizationExcludedAsset, ...] = field(default=None, metadata=field_metadata) constraints_consultations: Tuple[DictBase, ...] = field(default=None, metadata=field_metadata) factor_analytics_intraday: OptimizationFactorAnalyticsIntraday = field(default=None, metadata=field_metadata) asset_analytics_intraday: Tuple[OptimizationAssetAnalyticsIntraday, ...] = field(default=None, metadata=field_metadata) portfolio_analytics_intraday: Tuple[OptimizationPortfolioAnalyticsIntraday, ...] = field(default=None, metadata=field_metadata) cluster_analytics_intraday: Tuple[OptimizationClusterAnalyticsIntraday, ...] = field(default=None, metadata=field_metadata) cluster_analytics: Tuple[OptimizationClusterAnalytics, ...] = field(default=None, metadata=field_metadata) eod_cash_positions: Tuple[OptimizationEodCashPositions, ...] = field(default=None, metadata=field_metadata) asset_analytics_day_one: Optional[Tuple[OptimizationAssetAnalyticsDayOne, ...]] = field(default=None, metadata=field_metadata) close_auction_analytics: Optional[Tuple[OptimizationCloseAuctionAnalytics, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Shock(Base): scope: ShockScope = field(default=None, metadata=field_metadata) value: float = field(default=None, metadata=field_metadata) type_: str = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class OptimizationResult(Base): created_by_id: str = field(default=None, metadata=field_metadata) created_time: datetime.datetime = field(default=None, metadata=field_metadata) entitlements: Entitlements = field(default=None, metadata=field_metadata) entitlement_exclusions: EntitlementExclusions = field(default=None, metadata=field_metadata) id_: str = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) last_updated_by_id: str = field(default=None, metadata=field_metadata) last_updated_time: datetime.datetime = field(default=None, metadata=field_metadata) owner_id: str = field(default=None, metadata=field_metadata) analytics: OptimizationAnalytics = field(default=None, metadata=field_metadata) status: OptimizationStatus = field(default=None, metadata=field_metadata) trade_schedule: Optional[Tuple[OptimizationTradeSchedule, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Scenario(Base): name: str = field(default=None, metadata=field_metadata) shocks: Optional[Tuple[Shock, ...]] = field(default=None, metadata=field_metadata) created_by_id: Optional[str] = field(default=None, metadata=field_metadata) created_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) description: Optional[str] = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) entitlement_exclusions: Optional[EntitlementExclusions] = field(default=None, metadata=field_metadata) id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) identifiers: Optional[Tuple[Identifier, ...]] = field(default=None, metadata=field_metadata) last_updated_by_id: Optional[str] = field(default=None, metadata=field_metadata) last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) scenario_group_id: Optional[str] = field(default=None, metadata=field_metadata) short_name: Optional[str] = field(default=None, metadata=field_metadata) owner_id: Optional[str] = field(default=None, metadata=field_metadata) tags: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) type_: Optional[FactorScenarioType] = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) parameters: Optional[DictBase] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class OptimizationRequest(Base): positions: Tuple[Position, ...] = field(default=None, metadata=field_metadata) execution_start_time: datetime.datetime = field(default=None, metadata=field_metadata) execution_end_time: datetime.datetime = field(default=None, metadata=field_metadata) parameters: DictBase = field(default=None, metadata=field_metadata) type_: OptimizationType = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) created_by_id: Optional[str] = field(default=None, metadata=field_metadata) created_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) entitlement_exclusions: Optional[EntitlementExclusions] = field(default=None, metadata=field_metadata) id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) last_updated_by_id: Optional[str] = field(default=None, metadata=field_metadata) last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) owner_id: Optional[str] = field(default=None, metadata=field_metadata) wait_for_results: Optional[bool] = field(default=False, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) ================================================ FILE: gs_quant/target/risk_models.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.base import * from gs_quant.common import * import datetime from typing import Dict, Optional, Tuple, Union from dataclasses import dataclass, field from dataclasses_json import LetterCase, config, dataclass_json from enum import Enum class RiskModelCoverage(EnumBase, Enum): """Allowed risk model coverages""" Country = 'Country' Global = 'Global' Market_Type = 'Market Type' Region = 'Region' class RiskModelDataMeasure(EnumBase, Enum): """A list of the different risk model data measures to choose from.""" Asset_Universe = 'Asset Universe' Historical_Beta = 'Historical Beta' Total_Risk = 'Total Risk' Specific_Risk = 'Specific Risk' Specific_Return = 'Specific Return' Daily_Return = 'Daily Return' Estimation_Universe_Weight = 'Estimation Universe Weight' Residual_Variance = 'Residual Variance' Predicted_Beta = 'Predicted Beta' Global_Predicted_Beta = 'Global Predicted Beta' Universe_Factor_Exposure = 'Universe Factor Exposure' R_Squared = 'R Squared' Fair_Value_Gap_Percent = 'Fair Value Gap Percent' Fair_Value_Gap_Standard_Deviation = 'Fair Value Gap Standard Deviation' Factor_Id = 'Factor Id' Factor_Name = 'Factor Name' Factor_Category_Id = 'Factor Category Id' Factor_Category = 'Factor Category' Factor_Return = 'Factor Return' Factor_Standard_Deviation = 'Factor Standard Deviation' Factor_Z_Score = 'Factor Z Score' Factor_Volatility = 'Factor Volatility' Covariance_Matrix = 'Covariance Matrix' Issuer_Specific_Covariance = 'Issuer Specific Covariance' Factor_Portfolios = 'Factor Portfolios' Bid_Ask_Spread = 'Bid Ask Spread' Bid_Ask_Spread_30d = 'Bid Ask Spread 30d' Bid_Ask_Spread_60d = 'Bid Ask Spread 60d' Bid_Ask_Spread_90d = 'Bid Ask Spread 90d' Trading_Volume = 'Trading Volume' Trading_Volume_30d = 'Trading Volume 30d' Trading_Volume_60d = 'Trading Volume 60d' Trading_Volume_90d = 'Trading Volume 90d' Traded_Value_30d = 'Traded Value 30d' Composite_Volume = 'Composite Volume' Composite_Volume_30d = 'Composite Volume 30d' Composite_Volume_60d = 'Composite Volume 60d' Composite_Volume_90d = 'Composite Volume 90d' Composite_Value_30d = 'Composite Value 30d' Issuer_Market_Cap = 'Issuer Market Cap' Price = 'Price' Model_Price = 'Model Price' Capitalization = 'Capitalization' Currency = 'Currency' Unadjusted_Specific_Risk = 'Unadjusted Specific Risk' Dividend_Yield = 'Dividend Yield' Pre_VRA_Covariance_Matrix = 'Pre VRA Covariance Matrix' Unadjusted_Covariance_Matrix = 'Unadjusted Covariance Matrix' Risk_Free_Rate = 'Risk Free Rate' Currency_Exchange_Rate = 'Currency Exchange Rate' Factor_Mean = 'Factor Mean' Factor_Cross_Sectional_Standard_Deviation = 'Factor Cross Sectional Standard Deviation' Factor_Cross_Sectional_Mean = 'Factor Cross Sectional Mean' class RiskModelEventType(EnumBase, Enum): """Event type for risk model class.""" Risk_Model = 'Risk Model' Risk_Model_PFP_Data = 'Risk Model PFP Data' Risk_Model_ISC_Data = 'Risk Model ISC Data' Risk_Model_AWS = 'Risk Model AWS' Risk_Model_PFP_Data_AWS = 'Risk Model PFP Data AWS' Risk_Model_ISC_Data_AWS = 'Risk Model ISC Data AWS' class RiskModelLogicalDb(EnumBase, Enum): QSAR_AX_NYC = 'QSAR_AX_NYC' STUDIO_DAILY = 'STUDIO_DAILY' class RiskModelTerm(EnumBase, Enum): """Allowed risk model terms""" Trading = 'Trading' Daily = 'Daily' Short = 'Short' Medium = 'Medium' Long = 'Long' class RiskModelUniverseIdentifier(EnumBase, Enum): """The identifier which the risk model is uploaded by.""" sedol = 'sedol' bcid = 'bcid' cusip = 'cusip' gsid = 'gsid' isin = 'isin' class RiskModelUniverseIdentifierRequest(EnumBase, Enum): """The identifier which the risk model is queried by.""" gsid = 'gsid' bbid = 'bbid' bcid = 'bcid' cusip = 'cusip' sedol = 'sedol' ric = 'ric' ticker = 'ticker' primeId = 'primeId' isin = 'isin' @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Factor(Base): identifier: str = field(default=None, metadata=field_metadata) type_: str = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) description: Optional[str] = field(default=None, metadata=field_metadata) glossary_description: Optional[str] = field(default=None, metadata=field_metadata) tooltip: Optional[str] = field(default=None, metadata=field_metadata) created_by_id: Optional[str] = field(default=None, metadata=field_metadata) created_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) last_updated_by_id: Optional[str] = field(default=None, metadata=field_metadata) last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class RiskModelCalendar(Base): business_dates: Tuple[datetime.date, ...] = field(default=None, metadata=field_metadata) created_by_id: Optional[str] = field(default=None, metadata=field_metadata) created_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) last_updated_by_id: Optional[str] = field(default=None, metadata=field_metadata) last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class RiskModelCurrencyRatesData(Base): currency: Tuple[str, ...] = field(default=None, metadata=field_metadata) exchange_rate: Tuple[float, ...] = field(default=None, metadata=field_metadata) risk_free_rate: Tuple[float, ...] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class RiskModelFactorData(Base): factor_id: str = field(default=None, metadata=field_metadata) factor_name: str = field(default=None, metadata=field_metadata) factor_category_id: str = field(default=None, metadata=field_metadata) factor_category: str = field(default=None, metadata=field_metadata) factor_return: float = field(default=None, metadata=field_metadata) factor_standard_deviation: Optional[float] = field(default=None, metadata=field_metadata) factor_cross_sectional_standard_deviation: Optional[float] = field(default=None, metadata=field_metadata) factor_mean: Optional[float] = field(default=None, metadata=field_metadata) factor_cross_sectional_mean: Optional[float] = field(default=None, metadata=field_metadata) factor_z_score: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) RiskModelFactorExposure = Dict[str, float] @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class RiskModelFactorPortfolio(Base): factor_id: str = field(default=None, metadata=field_metadata) weights: Tuple[float, ...] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class RiskModelIssuerSpecificCovarianceData(Base): universe_id1: Tuple[str, ...] = field(default=None, metadata=field_metadata) universe_id2: Tuple[str, ...] = field(default=None, metadata=field_metadata) covariance: Tuple[float, ...] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class RiskModelAssetData(Base): universe: Tuple[str, ...] = field(default=None, metadata=field_metadata) specific_risk: Tuple[float, ...] = field(default=None, metadata=field_metadata) factor_exposure: Tuple[RiskModelFactorExposure, ...] = field(default=None, metadata=field_metadata) unadjusted_specific_risk: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) specific_return: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) daily_return: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) estimation_universe_weight: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) residual_variance: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) historical_beta: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) predicted_beta: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) global_predicted_beta: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) total_risk: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) bid_ask_spread: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) bid_ask_spread30d: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) bid_ask_spread60d: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) bid_ask_spread90d: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) trading_volume: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) trading_volume30d: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) trading_volume60d: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) trading_volume90d: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) traded_value30d: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) composite_volume: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) composite_volume30d: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) composite_volume60d: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) composite_volume90d: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) composite_value30d: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) issuer_market_cap: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) price: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) capitalization: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) currency: Optional[Tuple[Currency, ...]] = field(default=None, metadata=field_metadata) dividend_yield: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) r_squared: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) fair_value_gap_percent: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) fair_value_gap_standard_deviation: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) model_price: Optional[Tuple[float, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class RiskModelCoverageRequest(Base): asset_ids: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) portfolio_id: Optional[str] = field(default=None, metadata=field_metadata) as_of_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) sort_by_term: Optional[RiskModelTerm] = field(default=None, metadata=field_metadata) vendor: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class RiskModelDataAssetsRequest(Base): identifier: RiskModelUniverseIdentifierRequest = field(default=None, metadata=field_metadata) universe: Tuple[str, ...] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class RiskModelFactorPortfoliosData(Base): universe: Tuple[str, ...] = field(default=None, metadata=field_metadata) portfolio: Tuple[RiskModelFactorPortfolio, ...] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class RiskModel(Base): coverage: RiskModelCoverage = field(default=None, metadata=field_metadata) id_: str = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) name: str = field(default=None, metadata=field_metadata) term: RiskModelTerm = field(default=None, metadata=field_metadata) universe_identifier: RiskModelUniverseIdentifier = field(default=None, metadata=field_metadata) vendor: str = field(default=None, metadata=field_metadata) version: float = field(default=None, metadata=field_metadata) type_: RiskModelType = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) created_by_id: Optional[str] = field(default=None, metadata=field_metadata) created_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) description: Optional[str] = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) last_updated_by_id: Optional[str] = field(default=None, metadata=field_metadata) last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) expected_update_time: Optional[str] = field(default=None, metadata=field_metadata) owner_id: Optional[str] = field(default=None, metadata=field_metadata) universe_size: Optional[float] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class RiskModelData(Base): date: datetime.date = field(default=None, metadata=field_metadata) asset_data: Optional[RiskModelAssetData] = field(default=None, metadata=field_metadata) factor_data: Optional[Tuple[RiskModelFactorData, ...]] = field(default=None, metadata=field_metadata) covariance_matrix: Optional[Tuple[Tuple[float, ...], ...]] = field(default=None, metadata=field_metadata) pre_vra_covariance_matrix: Optional[Tuple[Tuple[float, ...], ...]] = field(default=None, metadata=config(field_name='preVRACovarianceMatrix', exclude=exclude_none)) unadjusted_covariance_matrix: Optional[Tuple[Tuple[float, ...], ...]] = field(default=None, metadata=field_metadata) currency_rates_data: Optional[RiskModelCurrencyRatesData] = field(default=None, metadata=field_metadata) issuer_specific_covariance: Optional[RiskModelIssuerSpecificCovarianceData] = field(default=None, metadata=field_metadata) factor_portfolios: Optional[RiskModelFactorPortfoliosData] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class RiskModelDataRequest(Base): start_date: datetime.date = field(default=None, metadata=field_metadata) end_date: datetime.date = field(default=None, metadata=field_metadata) measures: Tuple[RiskModelDataMeasure, ...] = field(default=None, metadata=field_metadata) assets: Optional[RiskModelDataAssetsRequest] = field(default=None, metadata=field_metadata) factors: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) limit_factors: Optional[bool] = field(default=True, metadata=field_metadata) base_currency_factor: Optional[str] = field(default=None, metadata=field_metadata) format_: Optional[Format] = field(default=Format.Json, metadata=config(field_name='format', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class RiskModelDataResponse(Base): results: Tuple[RiskModelData, ...] = field(default=None, metadata=field_metadata) total_results: int = field(default=None, metadata=field_metadata) missing_dates: Optional[Tuple[datetime.date, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) ================================================ FILE: gs_quant/target/scenarios.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.base import * from gs_quant.common import * import datetime from typing import Dict, Optional, Tuple, Union from dataclasses import dataclass, field from dataclasses_json import LetterCase, config, dataclass_json @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ByAssetPnlResult(Base): asset_id: str = field(default=None, metadata=field_metadata) name: str = field(default=None, metadata=field_metadata) bbid: Optional[str] = field(default=None, metadata=field_metadata) sector: Optional[str] = field(default=None, metadata=field_metadata) industry: Optional[str] = field(default=None, metadata=field_metadata) country: Optional[str] = field(default=None, metadata=field_metadata) direction: Optional[str] = field(default=None, metadata=field_metadata) exposure: Optional[float] = field(default=None, metadata=field_metadata) estimated_pnl: Optional[float] = field(default=None, metadata=field_metadata) estimated_performance: Optional[float] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ErroredScenario(Base): id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) error_message: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SummaryResult(Base): estimated_pnl: float = field(default=None, metadata=field_metadata) estimated_performance: Optional[float] = field(default=None, metadata=field_metadata) stressed_market_value: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PnlResult(Base): name: str = field(default=None, metadata=field_metadata) estimated_pnl: float = field(default=None, metadata=field_metadata) factor_exposure: Optional[float] = field(default=None, metadata=field_metadata) exposure: Optional[float] = field(default=None, metadata=field_metadata) factor_shock: Optional[float] = field(default=None, metadata=field_metadata) by_asset: Optional[ByAssetPnlResult] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ScenarioResponse(Base): summary: Optional[SummaryResult] = field(default=None, metadata=field_metadata) factor_pnl: Optional[PnlResult] = field(default=None, metadata=field_metadata) by_sector_aggregations: Optional[PnlResult] = field(default=None, metadata=field_metadata) by_region_aggregations: Optional[PnlResult] = field(default=None, metadata=field_metadata) by_direction_aggregations: Optional[PnlResult] = field(default=None, metadata=field_metadata) by_asset: Optional[ByAssetPnlResult] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ScenarioCalculationResponse(Base): scenarios: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) errored_scenarios: Optional[Tuple[ErroredScenario, ...]] = field(default=None, metadata=field_metadata) results: Optional[Tuple[ScenarioResponse, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ScenarioCalculationAPIRequest(Base): scenario_ids: Tuple[str, ...] = field(default=None, metadata=field_metadata) measures: tuple = field(default=None, metadata=field_metadata) date: Optional[datetime.date] = field(default=None, metadata=field_metadata) risk_model: Optional[str] = field(default=None, metadata=field_metadata) entity_id: Optional[str] = field(default=None, metadata=field_metadata) position_set: Optional[PositionSetRequest] = field(default=None, metadata=field_metadata) report_id: Optional[str] = field(default=None, metadata=field_metadata) type_: Optional[str] = field(init=False, default='Factor Scenario', metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) ================================================ FILE: gs_quant/target/screens.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.base import * from gs_quant.common import * import datetime from typing import Dict, Optional, Tuple, Union from dataclasses import dataclass, field from dataclasses_json import LetterCase, config, dataclass_json @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ScreenerQueryBuilder(Base): filters: Optional[AssetScreenerCreditRequestFilters] = field(default=None, metadata=field_metadata) asset_class: Optional[str] = field(init=False, default='Credit', metadata=field_metadata) type_: Optional[str] = field(init=False, default='Bond', metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Screen(Base): name: str = field(default=None, metadata=field_metadata) query_builder: ScreenerQueryBuilder = field(default=None, metadata=field_metadata) id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) active: Optional[bool] = field(default=None, metadata=field_metadata) owner_id: Optional[str] = field(default=None, metadata=field_metadata) created_by_id: Optional[str] = field(default=None, metadata=field_metadata) created_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) last_updated_by_id: Optional[str] = field(default=None, metadata=field_metadata) last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) ================================================ FILE: gs_quant/target/secmaster.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.base import * from gs_quant.common import * import datetime from typing import Dict, Optional, Tuple, Union from dataclasses import dataclass, field from dataclasses_json import LetterCase, config, dataclass_json from enum import Enum class SecMasterAssetType(EnumBase, Enum): """Asset type differentiates the product categorization or contract type.""" ETF = 'ETF' ETN = 'ETN' Future = 'Future' Index = 'Index' Option = 'Option' Preferred_Stock = 'Preferred Stock' Single_Stock = 'Single Stock' Common_Stock = 'Common Stock' ADR = 'ADR' GDR = 'GDR' Dutch_Cert = 'Dutch Cert' NY_Reg_Shrs = 'NY Reg Shrs' Receipt = 'Receipt' Unit = 'Unit' Mutual_Fund = 'Mutual Fund' Right = 'Right' Preferred = 'Preferred' Misc_ = 'Misc.' REIT = 'REIT' Private_Comp = 'Private Comp' Preference = 'Preference' Ltd_Part = 'Ltd Part' Tracking_Stk = 'Tracking Stk' Royalty_Trst = 'Royalty Trst' Closed_End_Fund = 'Closed-End Fund' Open_End_Fund = 'Open-End Fund' Fund_of_Funds = 'Fund of Funds' MLP = 'MLP' Stapled_Security = 'Stapled Security' Savings_Share = 'Savings Share' Equity_WRT = 'Equity WRT' Savings_Plan = 'Savings Plan' Equity_Index = 'Equity Index' Municipal_Bond = 'Municipal Bond' Corporate_Bond = 'Corporate Bond' Government_Bond = 'Government Bond' Agency_Bond = 'Agency Bond' Currency = 'Currency' Equity_Option = 'Equity Option' Financial_index_future_ = 'Financial index future.' Single_Stock_Future = 'Single Stock Future' Pool = 'Pool' Certificate_Of_Deposit = 'Certificate Of Deposit' Debt_Structured_Note = 'Debt Structured Note' Agency_CMO = 'Agency CMO' Future_Option = 'Future Option' Convertible_Bond = 'Convertible Bond' Austrian_Crt = 'Austrian Crt' BDR = 'BDR' Belgium_Cert = 'Belgium Cert' CDR = 'CDR' EDR = 'EDR' German_Cert = 'German Cert' IDR = 'IDR' RDC = 'RDC' Swiss_Cert = 'Swiss Cert' Canadian_DR = 'Canadian DR' Singapore_DR = 'Singapore DR' To_Be_Announced = 'To Be Announced' Basket = 'Basket' Physical_index_future_ = 'Physical index future.' class SecMasterCorporateActionStatus(EnumBase, Enum): """Status of corporate actions.""" Normal = 'Normal' Completed = 'Completed' Pending = 'Pending' Deleted = 'Deleted' Incomplete = 'Incomplete' Missing_Adj_Factor = 'Missing Adj Factor' Not_Quoted_By_Exchange = 'Not Quoted By Exchange' Missing_Terms = 'Missing Terms' Cancelled = 'Cancelled' Lapsed = 'Lapsed' Proposed = 'Proposed' Withdrawn = 'Withdrawn' All = 'All' class SecMasterCorporateActionType(EnumBase, Enum): """Types of corporate actions.""" Cash_Dividend = 'Cash Dividend' Merger = 'Merger' Spinoff = 'Spinoff' Split = 'Split' Rights = 'Rights' Reorganization = 'Reorganization' Special_Adjustment = 'Special Adjustment' Quote_Lot_Adjustment = 'Quote Lot Adjustment' Currency_Adjustment = 'Currency Adjustment' All = 'All' @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SecMasterAuditFields(Base): last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) last_updated_by_id: Optional[str] = field(default=None, metadata=field_metadata) created_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) created_by_id: Optional[str] = field(default=None, metadata=field_metadata) owner_id: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) class SecMasterIdentifiers(DictBase): pass @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SecMasterRecord(Base): recordid: Optional[float] = field(default=None, metadata=field_metadata) action_type: Optional[str] = field(default=None, metadata=field_metadata) instrument_value: Optional[float] = field(default=None, metadata=field_metadata) data_value: Optional[float] = field(default=None, metadata=field_metadata) term_type: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SecMasterResourceCompany(Base): company_id: Optional[float] = field(default=None, metadata=field_metadata) company_name: Optional[str] = field(default=None, metadata=field_metadata) identifiers: Optional[DictBase] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=field_metadata) issuer_id: Optional[str] = field(default=None, metadata=field_metadata) SecMasterSources = Dict[str, str] @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SecMasterTemporalCompany(Base): gs_company_id: str = field(default=None, metadata=field_metadata) id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) start_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) end_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ExchangeGetRequestPathSchema(Base): gs_exchange_id: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) mic: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) operating_mic: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) ric_suffix_code: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) ric_exchange_code: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) bbg_exchange_code: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) country: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) fields: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) as_of_date: Optional[Tuple[datetime.date, ...]] = field(default=None, metadata=field_metadata) limit: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) offset: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) offset_key: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SecMasterAssetSources(Base): id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) asset_class: Optional[str] = field(default=None, metadata=field_metadata) product: Optional[SecMasterSources] = field(default=None, metadata=field_metadata) exchange: Optional[SecMasterSources] = field(default=None, metadata=field_metadata) company: Optional[SecMasterSources] = field(default=None, metadata=field_metadata) classifications: Optional[SecMasterSources] = field(default=None, metadata=field_metadata) identifiers: Optional[SecMasterSources] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SecMasterCorporateAction(Base): actionid: Optional[str] = field(default=None, metadata=field_metadata) eventid: Optional[str] = field(default=None, metadata=field_metadata) gsid: Optional[str] = field(default=None, metadata=field_metadata) event_type: Optional[SecMasterCorporateActionType] = field(default=None, metadata=field_metadata) announce_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) effective_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) multiplicative_adjust: Optional[float] = field(default=None, metadata=field_metadata) additive_adjust: Optional[float] = field(default=None, metadata=field_metadata) event_status: Optional[SecMasterCorporateActionStatus] = field(default=None, metadata=field_metadata) records: Optional[Tuple[SecMasterRecord, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SecMasterExchange(Base): gs_exchange_id: str = field(default=None, metadata=field_metadata) id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=field_metadata) country: Optional[str] = field(default=None, metadata=field_metadata) timezone: Optional[str] = field(default=None, metadata=field_metadata) type_: Optional[str] = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) identifiers: Optional[SecMasterIdentifiers] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SecMasterGetActionsRequestPathSchema(Base): event_type: Optional[Tuple[SecMasterCorporateActionType, ...]] = field(default=None, metadata=field_metadata) event_status: Optional[Tuple[SecMasterCorporateActionStatus, ...]] = field(default=None, metadata=field_metadata) gsid: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) event_id: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) corp_action_id: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) effective_date: Optional[Tuple[datetime.date, ...]] = field(default=None, metadata=field_metadata) as_of_time: Optional[Tuple[datetime.datetime, ...]] = field(default=None, metadata=field_metadata) effective_date_from: Optional[Tuple[datetime.date, ...]] = field(default=None, metadata=field_metadata) effective_date_to: Optional[Tuple[datetime.date, ...]] = field(default=None, metadata=field_metadata) offset_key: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) limit: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SecMasterGetCapitalStructureRequestPathSchema(Base): gsid: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) ticker: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) bbid: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) ric: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) rcic: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) cusip: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) sedol: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) isin: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) gss: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) prime_id: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) issuer_id: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) type_: Optional[Tuple[str, ...]] = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) as_of_time: Optional[Tuple[datetime.datetime, ...]] = field(default=None, metadata=field_metadata) is_primary: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) effective_date: Optional[Tuple[datetime.date, ...]] = field(default=None, metadata=field_metadata) limit: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) offset_key: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SecMasterGetRequestPathSchema(Base): identifier: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) gsid: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) ticker: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) bbg: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) bbid: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) ric: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) rcic: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) cusip: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) cins: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) sedol: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) isin: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) gss: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) prime_id: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) type_: Optional[Tuple[SecMasterAssetType, ...]] = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) country_code: Optional[Tuple[CountryCode, ...]] = field(default=None, metadata=field_metadata) is_primary: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) all_listings: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) effective_date: Optional[Tuple[datetime.date, ...]] = field(default=None, metadata=field_metadata) as_of_time: Optional[Tuple[datetime.datetime, ...]] = field(default=None, metadata=field_metadata) limit: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) offset_key: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SecMasterResourceExchange(Base): name: Optional[str] = field(default=None, metadata=field_metadata) identifiers: Optional[SecMasterIdentifiers] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SecMasterResourceProduct(Base): name: Optional[str] = field(default=None, metadata=field_metadata) identifiers: Optional[SecMasterIdentifiers] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SecMasterTemporalProduct(Base): gsid: str = field(default=None, metadata=field_metadata) id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) start_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) end_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=field_metadata) country: Optional[str] = field(default=None, metadata=field_metadata) primary_exchange_id: Optional[str] = field(default=None, metadata=field_metadata) type_: Optional[SecMasterAssetType] = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) subtype: Optional[str] = field(default=None, metadata=field_metadata) source: Optional[str] = field(default=None, metadata=field_metadata) flag: Optional[bool] = field(default=None, metadata=field_metadata) update_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SecMasterAsset(Base): id_: str = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) effective_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) start_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=field_metadata) asset_class: Optional[AssetClass] = field(default=None, metadata=field_metadata) type_: Optional[SecMasterAssetType] = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) product: Optional[SecMasterResourceProduct] = field(default=None, metadata=field_metadata) exchange: Optional[SecMasterResourceExchange] = field(default=None, metadata=field_metadata) currency: Optional[str] = field(default=None, metadata=field_metadata) company: Optional[SecMasterResourceCompany] = field(default=None, metadata=field_metadata) issuer: Optional[SecMasterResourceCompany] = field(default=None, metadata=field_metadata) classifications: Optional[AssetClassifications] = field(default=None, metadata=field_metadata) identifiers: Optional[SecMasterIdentifiers] = field(default=None, metadata=field_metadata) tags: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) active_listing: Optional[bool] = field(default=None, metadata=field_metadata) last_active_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) entitlement_exclusions: Optional[EntitlementExclusions] = field(default=None, metadata=field_metadata) audit_fields: Optional[SecMasterAuditFields] = field(default=None, metadata=field_metadata) field_sources: Optional[SecMasterAssetSources] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SecMasterResponseActions(Base): results: Optional[Tuple[SecMasterCorporateAction, ...]] = field(default=None, metadata=field_metadata) total_results: Optional[float] = field(default=None, metadata=field_metadata) offset_key: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SecMasterResponseMulti(Base): request_id: Optional[str] = field(default=None, metadata=field_metadata) results: Optional[Tuple[Union[SecMasterExchange, SecMasterTemporalCompany, SecMasterTemporalProduct], ...]] = field(default=None, metadata=field_metadata) total_results: Optional[float] = field(default=None, metadata=field_metadata) offset_key: Optional[str] = field(default=None, metadata=field_metadata) limit: Optional[int] = field(default=None, metadata=field_metadata) offset: Optional[int] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SecMasterResponseAssets(Base): results: Optional[Tuple[SecMasterAsset, ...]] = field(default=None, metadata=field_metadata) total_results: Optional[float] = field(default=None, metadata=field_metadata) offset_key: Optional[str] = field(default=None, metadata=field_metadata) limit: Optional[int] = field(default=None, metadata=field_metadata) offset: Optional[int] = field(default=None, metadata=field_metadata) update_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) ================================================ FILE: gs_quant/target/trades.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.base import * from gs_quant.common import * import datetime from typing import Dict, Optional, Tuple, Union from dataclasses import dataclass, field from dataclasses_json import LetterCase, config, dataclass_json from enum import Enum class MqexsAssetClass(EnumBase, Enum): """Asset classification of security. Assets are classified into broad groups which exhibit similar characteristics and behave in a consistent way under different market conditions""" Commod = 'Commod' FX = 'FX' Equity = 'Equity' Earon = 'Earon' FICC = 'FICC' Prime = 'Prime' IOI = 'IOI' Rates = 'Rates' PCO = 'PCO' class MqexsAssetClassExt(EnumBase, Enum): """Asset classification of security. Assets are classified into broad groups which exhibit similar characteristics and behave in a consistent way under different market conditions""" Commod = 'Commod' FX = 'FX' Equity = 'Equity' class MqexsClearer(EnumBase, Enum): """The clearer code""" CME = 'CME' ICE = 'ICE' NFX = 'NFX' OTC = 'OTC' LME = 'LME' CME_ICE = 'CME-ICE' class MqexsCurrencyExt(EnumBase, Enum): """Currency, ISO 4217 currency code or exchange quote modifier (e.g. GBP vs GBp)""" _ = '' ACU = 'ACU' ADP = 'ADP' AED = 'AED' AFA = 'AFA' ALL = 'ALL' AMD = 'AMD' ANG = 'ANG' AOA = 'AOA' AOK = 'AOK' AON = 'AON' ARA = 'ARA' ARS = 'ARS' ARZ = 'ARZ' ATS = 'ATS' AUD = 'AUD' AUZ = 'AUZ' AZM = 'AZM' B03 = 'B03' BAD = 'BAD' BAK = 'BAK' BAM = 'BAM' BBD = 'BBD' BDN = 'BDN' BDT = 'BDT' BEF = 'BEF' BGL = 'BGL' BGN = 'BGN' BHD = 'BHD' BIF = 'BIF' BMD = 'BMD' BND = 'BND' BR6 = 'BR6' BRE = 'BRE' BRF = 'BRF' BRL = 'BRL' BRR = 'BRR' BSD = 'BSD' BTC = 'BTC' BTN = 'BTN' BTR = 'BTR' BWP = 'BWP' BYR = 'BYR' BZD = 'BZD' C23 = 'C23' CAC = 'CAC' CAD = 'CAD' CAZ = 'CAZ' CCI = 'CCI' CDF = 'CDF' CFA = 'CFA' CHF = 'CHF' CHZ = 'CHZ' CLF = 'CLF' CLP = 'CLP' CLZ = 'CLZ' CNH = 'CNH' CNO = 'CNO' CNY = 'CNY' CNZ = 'CNZ' COP = 'COP' COZ = 'COZ' CPB = 'CPB' CPI = 'CPI' CRC = 'CRC' CUP = 'CUP' CVE = 'CVE' CYP = 'CYP' CZH = 'CZH' CZK = 'CZK' DAX = 'DAX' DEM = 'DEM' DIJ = 'DIJ' DJF = 'DJF' DKK = 'DKK' DOP = 'DOP' DZD = 'DZD' E51 = 'E51' E52 = 'E52' E53 = 'E53' E54 = 'E54' ECI = 'ECI' ECS = 'ECS' ECU = 'ECU' EEK = 'EEK' EF0 = 'EF0' EGP = 'EGP' ESP = 'ESP' ETB = 'ETB' EUR = 'EUR' EUZ = 'EUZ' F06 = 'F06' FED = 'FED' FIM = 'FIM' FJD = 'FJD' FKP = 'FKP' FRF = 'FRF' FT1 = 'FT1' GBP = 'GBP' GBZ = 'GBZ' GEK = 'GEK' GHC = 'GHC' GHS = 'GHS' GHY = 'GHY' GIP = 'GIP' GMD = 'GMD' GNF = 'GNF' GQE = 'GQE' GRD = 'GRD' GTQ = 'GTQ' GWP = 'GWP' GYD = 'GYD' HKB = 'HKB' HKD = 'HKD' HNL = 'HNL' HRK = 'HRK' HSI = 'HSI' HTG = 'HTG' HUF = 'HUF' IDB = 'IDB' IDO = 'IDO' IDR = 'IDR' IEP = 'IEP' IGP = 'IGP' ILS = 'ILS' INO = 'INO' INP = 'INP' INR = 'INR' IPA = 'IPA' IPX = 'IPX' IQD = 'IQD' IRR = 'IRR' IRS = 'IRS' ISI = 'ISI' ISK = 'ISK' ISO = 'ISO' ITL = 'ITL' J05 = 'J05' JMD = 'JMD' JNI = 'JNI' JOD = 'JOD' JPY = 'JPY' JPZ = 'JPZ' JZ9 = 'JZ9' KES = 'KES' KGS = 'KGS' KHR = 'KHR' KMF = 'KMF' KOR = 'KOR' KPW = 'KPW' KRW = 'KRW' KWD = 'KWD' KYD = 'KYD' KZT = 'KZT' LAK = 'LAK' LBA = 'LBA' LBP = 'LBP' LHY = 'LHY' LKR = 'LKR' LRD = 'LRD' LSL = 'LSL' LSM = 'LSM' LTL = 'LTL' LUF = 'LUF' LVL = 'LVL' LYD = 'LYD' MAD = 'MAD' MDL = 'MDL' MGF = 'MGF' MKD = 'MKD' MMK = 'MMK' MNT = 'MNT' MOP = 'MOP' MRO = 'MRO' MTP = 'MTP' MUR = 'MUR' MVR = 'MVR' MWK = 'MWK' MXB = 'MXB' MXN = 'MXN' MXP = 'MXP' MXW = 'MXW' MXZ = 'MXZ' MYO = 'MYO' MYR = 'MYR' MZM = 'MZM' MZN = 'MZN' NAD = 'NAD' ND3 = 'ND3' NGF = 'NGF' NGI = 'NGI' NGN = 'NGN' NIC = 'NIC' NLG = 'NLG' NOK = 'NOK' NOZ = 'NOZ' NPR = 'NPR' NZD = 'NZD' NZZ = 'NZZ' O08 = 'O08' OMR = 'OMR' PAB = 'PAB' PEI = 'PEI' PEN = 'PEN' PEZ = 'PEZ' PGK = 'PGK' PHP = 'PHP' PKR = 'PKR' PLN = 'PLN' PLZ = 'PLZ' PSI = 'PSI' PTE = 'PTE' PYG = 'PYG' QAR = 'QAR' R2K = 'R2K' ROL = 'ROL' RON = 'RON' RSD = 'RSD' RUB = 'RUB' RUF = 'RUF' RUR = 'RUR' RWF = 'RWF' SAR = 'SAR' SBD = 'SBD' SCR = 'SCR' SDP = 'SDP' SDR = 'SDR' SEK = 'SEK' SET = 'SET' SGD = 'SGD' SHP = 'SHP' SKK = 'SKK' SLL = 'SLL' SRG = 'SRG' SSI = 'SSI' STD = 'STD' SUR = 'SUR' SVC = 'SVC' SVT = 'SVT' SYP = 'SYP' SZL = 'SZL' T21 = 'T21' T51 = 'T51' T52 = 'T52' T53 = 'T53' T54 = 'T54' T55 = 'T55' T71 = 'T71' TE0 = 'TE0' TED = 'TED' TF9 = 'TF9' THB = 'THB' THO = 'THO' TMM = 'TMM' TND = 'TND' TNT = 'TNT' TOP = 'TOP' TPE = 'TPE' TPX = 'TPX' TRB = 'TRB' TRL = 'TRL' TRY = 'TRY' TRZ = 'TRZ' TTD = 'TTD' TWD = 'TWD' TZS = 'TZS' UAH = 'UAH' UCB = 'UCB' UDI = 'UDI' UFC = 'UFC' UFZ = 'UFZ' UGS = 'UGS' UGX = 'UGX' USB = 'USB' USD = 'USD' UVR = 'UVR' UYP = 'UYP' UYU = 'UYU' VAC = 'VAC' VEB = 'VEB' VEF = 'VEF' VES = 'VES' VND = 'VND' VUV = 'VUV' WST = 'WST' XAF = 'XAF' XCD = 'XCD' XDR = 'XDR' XEU = 'XEU' XOF = 'XOF' XPF = 'XPF' YDD = 'YDD' YER = 'YER' YUD = 'YUD' YUN = 'YUN' ZAL = 'ZAL' ZAR = 'ZAR' ZAZ = 'ZAZ' ZMK = 'ZMK' ZMW = 'ZMW' ZRN = 'ZRN' ZRZ = 'ZRZ' ZWD = 'ZWD' AUd = 'AUd' BWp = 'BWp' EUr = 'EUr' GBp = 'GBp' ILs = 'ILs' KWd = 'KWd' MWk = 'MWk' SGd = 'SGd' SZl = 'SZl' USd = 'USd' ZAr = 'ZAr' class MqexsErrorSeverity(EnumBase, Enum): """The severity of the error, which can be a warning or a fatal error""" Pass = 'Pass' Warning = 'Warning' Fatal = 'Fatal' FatalException = 'FatalException' class MqexsOtcSettlementType(EnumBase, Enum): """OTC settlement type""" Bullet = 'Bullet' Calendar_Month_Average = 'Calendar Month Average' Trade_Month_Average = 'Trade Month Average' Physical = 'Physical' CalMonth_Avg_No_Roll_Adjust = 'CalMonth Avg No Roll Adjust' Heren_Month_Ahead = 'Heren Month Ahead' Heren_Day_Ahead = 'Heren Day Ahead' Argus_Month_Ahead = 'Argus Month Ahead' Argus_Day_Ahead = 'Argus Day Ahead' Multi = 'Multi' class MqexsPricingType(EnumBase, Enum): """The source to be used to get a quote""" CommodStream = 'CommodStream' eAronStream = 'eAronStream' FXStreaming = 'FXStreaming' Heat = 'Heat' Indicative = 'Indicative' LiquidityStream = 'LiquidityStream' OneDeltaStream = 'OneDeltaStream' OptionChain = 'OptionChain' RFQ = 'RFQ' RFQSales = 'RFQSales' GstRFQ = 'GstRFQ' Roll = 'Roll' RFQRollSales = 'RFQRollSales' Swap = 'Swap' RFQSwapSales = 'RFQSwapSales' GstSwapRFQ = 'GstSwapRFQ' TradableStream = 'TradableStream' UnifiedRFQ = 'UnifiedRFQ' PCORFQ = 'PCORFQ' class MqexsSide(EnumBase, Enum): """Field represents the order or trade action.""" Buy = 'Buy' Sell = 'Sell' BuySell = 'BuySell' SellBuy = 'SellBuy' Above = 'Above' Below = 'Below' _ = '' @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MqexsErrorInfo(Base): error_code: str = field(default=None, metadata=field_metadata) error_msg: str = field(default=None, metadata=field_metadata) error_severity: Optional[MqexsErrorSeverity] = field(default=None, metadata=field_metadata) asset_class: Optional[MqexsAssetClass] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MqexsProductDetails(Base): name: str = field(default=None, metadata=field_metadata) contract_code: Optional[str] = field(default=None, metadata=field_metadata) clearer: Optional[MqexsClearer] = field(default=None, metadata=field_metadata) settlement_type: Optional[MqexsOtcSettlementType] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MqexsTradeDetails(Base): side: MqexsSide = field(default=None, metadata=field_metadata) quantity: str = field(default=None, metadata=field_metadata) unit_price: str = field(default=None, metadata=field_metadata) currency: MqexsCurrencyExt = field(default=None, metadata=field_metadata) settlement_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) quantity_lots: Optional[str] = field(default=None, metadata=field_metadata) quantity_unit: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MqexsTradeExt(Base): id_: str = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) trade_details: MqexsTradeDetails = field(default=None, metadata=field_metadata) product_details: MqexsProductDetails = field(default=None, metadata=field_metadata) quote_id: str = field(default=None, metadata=field_metadata) asset_class: MqexsAssetClassExt = field(default=None, metadata=field_metadata) created_by_id: str = field(default=None, metadata=field_metadata) created_time: datetime.datetime = field(default=None, metadata=field_metadata) last_updated_time: datetime.datetime = field(default=None, metadata=field_metadata) last_updated_by_id: str = field(default=None, metadata=field_metadata) inquiry_id: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MqexsTradesWErrorExt(Base): trades: Optional[Tuple[MqexsTradeExt, ...]] = field(default=None, metadata=field_metadata) errors: Optional[Tuple[MqexsErrorInfo, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) ================================================ FILE: gs_quant/target/workflow_quote.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.base import * from gs_quant.common import * import datetime from typing import Dict, Optional, Tuple, Union from dataclasses import dataclass, field from dataclasses_json import LetterCase, config, dataclass_json from enum import Enum class Encoding(EnumBase, Enum): HTML = 'HTML' URL = 'URL' Unicode = 'Unicode' Base64 = 'Base64' Hex = 'Hex' ASCII = 'ASCII' class HedgeModel(EnumBase, Enum): Smile = 'Smile' BlackScholes = 'BlackScholes' class ImgType(EnumBase, Enum): APNG = 'APNG' AVIF = 'AVIF' GIF = 'GIF' JPEG = 'JPEG' PNG = 'PNG' SVG = 'SVG' WEBP = 'WEBP' class MarketRefOverrideType(EnumBase, Enum): """Market Ref Override Type Spot or Fwd""" Spot = 'Spot' Fwd = 'Fwd' class OverlayType(EnumBase, Enum): """Type""" Payout = 'Payout' Price = 'Price' MtM = 'MtM' Delta = 'Delta' Vega = 'Vega' Theta = 'Theta' RelativeCheapness = 'RelativeCheapness' ProbabilityDistribution = 'ProbabilityDistribution' RealisedProbability = 'RealisedProbability' MacroEvents = 'MacroEvents' MicroEvents = 'MicroEvents' Gamma = 'Gamma' Volatility = 'Volatility' VolatilityDistribution = 'VolatilityDistribution' EarlyExercise = 'EarlyExercise' _None = 'None' class PriceFormat(EnumBase, Enum): """display unit for price""" Absolute = 'Absolute' Relative = 'Relative' Cents = 'Cents' Bps = 'Bps' Percentage = 'Percentage' class RelativeExpiryType(EnumBase, Enum): Relative = 'Relative' Fixed = 'Fixed' class RelativeStrikeType(EnumBase, Enum): Relative_Delta = 'Relative Delta' Relative_Spot = 'Relative Spot' Relative_Fwd = 'Relative Fwd' Fixed = 'Fixed' @dataclass class HedgeTypes(Base): pass @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CustomDeltaHedge(HedgeTypes): amount: float = field(default=None, metadata=field_metadata) type_: Optional[str] = field(init=False, default='CustomDeltaHedge', metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) class GenericResponse(DictBase): pass @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class HyperLinkImageComments(CustomComments): url: Optional[str] = field(default=None, metadata=field_metadata) comment_type: Optional[str] = field(init=False, default='hyperLinkImageComments', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SalesPremiumAdjustment(Base): value: Optional[float] = field(default=None, metadata=field_metadata) unit: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SolvingTarget(Base): constraint: Optional[float] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class WorkflowEntitlement(Base): type_: str = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) value: str = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BinaryImageComments(CustomComments): data: Optional[str] = field(default=None, metadata=field_metadata) img_type: Optional[ImgType] = field(default=None, metadata=field_metadata) encoding: Optional[Encoding] = field(default=None, metadata=field_metadata) comment_type: Optional[str] = field(init=False, default='binaryImageComments', metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ChartingParameters(Base): spot_style: Optional[str] = field(default=None, metadata=field_metadata) overlay: Optional[OverlayType] = field(default=None, metadata=field_metadata) underlay: Optional[OverlayType] = field(default=None, metadata=field_metadata) description: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DeltaHedge(HedgeTypes): model: Optional[HedgeModel] = field(default=None, metadata=field_metadata) type_: Optional[str] = field(init=False, default='DeltaHedge', metadata=config(field_name='type', exclude=exclude_none)) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MarketRefOverride(Base): asset: Optional[str] = field(default=None, metadata=field_metadata) value: Optional[float] = field(default=None, metadata=field_metadata) mkt_ref_override_type: Optional[MarketRefOverrideType] = field(default=None, metadata=field_metadata) ref_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class OverlayParameters(Base): overlay_type: Optional[OverlayType] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SolvingInfo(Base): target: Optional[SolvingTarget] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class StrategyDescription(Base): strategy_type: Optional[str] = field(default=None, metadata=field_metadata) long_short: Optional[LongShort] = field(default=LongShort.Long, metadata=field_metadata) assets: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) asset_classes: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class WorkflowEntitlements(Base): version: Optional[int] = field(default=None, metadata=field_metadata) readers: Optional[Tuple[WorkflowEntitlement, ...]] = field(default=None, metadata=field_metadata) writers: Optional[Tuple[WorkflowEntitlement, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MarketDataParameters(Base): max_history: Optional[datetime.date] = field(default=None, metadata=field_metadata) timestamp: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) spot_ref: Optional[float] = field(default=None, metadata=field_metadata) mkt_ref_override: Optional[Tuple[MarketRefOverride, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class VisualStructuringReport(QuoteReport): report_type: Optional[str] = field(default='VisualStructuringReport', metadata=field_metadata) position_set_id: Optional[str] = field(default=None, metadata=field_metadata) quick_entry_text: Optional[str] = field(default=None, metadata=field_metadata) as_of_date: Optional[datetime.date] = field(default=None, metadata=field_metadata) market_data_parameters: Optional[MarketDataParameters] = field(default=None, metadata=field_metadata) overlay_parameters: Optional[OverlayParameters] = field(default=None, metadata=field_metadata) solving_info: Optional[SolvingInfo] = field(default=None, metadata=field_metadata) charting_parameters: Optional[ChartingParameters] = field(default=None, metadata=field_metadata) comments: Optional[Tuple[CustomComments, ...]] = field(default=None, metadata=field_metadata) strategy_description: Optional[StrategyDescription] = field(default=None, metadata=field_metadata) asset_class: Optional[str] = field(default=None, metadata=field_metadata) hedge_instruction: Optional[HedgeTypes] = field(default=None, metadata=field_metadata) sales_premium_adjustment: Optional[SalesPremiumAdjustment] = field(default=None, metadata=field_metadata) price_format: Optional[PriceFormat] = field(default=None, metadata=field_metadata) strike_and_barrier_type: Optional[RelativeStrikeType] = field(default=None, metadata=field_metadata) expiry_type: Optional[RelativeExpiryType] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SaveQuoteRequest(Base): positions: Tuple[PositionSet, ...] = field(default=None, metadata=field_metadata) measures: Tuple[RiskMeasure, ...] = field(default=None, metadata=field_metadata) pricing_and_market_data_as_of: Optional[Tuple[PricingDateAndMarketDataAsOf, ...]] = field(default=None, metadata=field_metadata) pricing_location: Optional[PricingLocation] = field(default=None, metadata=field_metadata) scenario: Optional[MarketDataScenario] = field(default=None, metadata=field_metadata) parameters: Optional[RiskRequestParameters] = field(default=None, metadata=field_metadata) reports: Optional[Tuple[QuoteReport, ...]] = field(default=None, metadata=field_metadata) shared_users: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) comments: Optional[str] = field(default=None, metadata=field_metadata) description: Optional[str] = field(default=None, metadata=field_metadata) original_workflow_id: Optional[str] = field(default=None, metadata=field_metadata) is_sharing_parent: Optional[bool] = field(default=None, metadata=field_metadata) entitlements: Optional[WorkflowEntitlements] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class WorkflowPosition(Base): id_: str = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) position_sets: Optional[Tuple[PositionSet, ...]] = field(default=None, metadata=field_metadata) reports: Optional[Tuple[QuoteReport, ...]] = field(default=None, metadata=field_metadata) comments: Optional[str] = field(default=None, metadata=field_metadata) original_workflow_id: Optional[str] = field(default=None, metadata=field_metadata) description: Optional[str] = field(default=None, metadata=field_metadata) entitlements: Optional[WorkflowEntitlements] = field(default=None, metadata=field_metadata) creator: Optional[str] = field(default=None, metadata=field_metadata) originating_system: Optional[str] = field(default=None, metadata=field_metadata) is_read_only: Optional[bool] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class WorkflowPositionsResponse(Base): results: Optional[Tuple[WorkflowPosition, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) ================================================ FILE: gs_quant/target/workspaces_markets.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.base import * from gs_quant.common import * import datetime from typing import Dict, Optional, Tuple, Union from dataclasses import dataclass, field from dataclasses_json import LetterCase, config, dataclass_json from enum import Enum class ComponentType(EnumBase, Enum): """Enum listing supported component types.""" article = 'article' assetPlot = 'assetPlot' chart = 'chart' barChart = 'barChart' commentary = 'commentary' commentaryPromo = 'commentaryPromo' container = 'container' datagrid = 'datagrid' dataviz = 'dataviz' empty = 'empty' legend = 'legend' market = 'market' monitor = 'monitor' plot = 'plot' promo = 'promo' rates = 'rates' relatedLinks = 'relatedLinks' research = 'research' screener = 'screener' selector = 'selector' separator = 'separator' stackedBarChart = 'stackedBarChart' treemap = 'treemap' video = 'video' webinar = 'webinar' class WorkspaceType(EnumBase, Enum): """Enum listing support workspace types.""" cashboard = 'cashboard' multiplot = 'multiplot' study = 'study' @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ComponentSelection(Base): selector_id: str = field(default=None, metadata=field_metadata) tag: str = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ContainerComponentParameters(Base): component_id: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class LegendItem(Base): color: str = field(default=None, metadata=field_metadata) icon: str = field(default=None, metadata=field_metadata) name: str = field(default=None, metadata=field_metadata) tooltip: Optional[str] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MarketComponentParameters(Base): height: float = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) class NotificationTokenBody(DictBase): pass @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PromoComponentParameters(Base): height: float = field(default=None, metadata=field_metadata) transparent: Optional[bool] = field(default=None, metadata=field_metadata) body: Optional[str] = field(default=None, metadata=field_metadata) size: Optional[str] = field(default=None, metadata=field_metadata) hide_border: Optional[bool] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SeparatorComponentParameters(Base): height: float = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=field_metadata) size: Optional[str] = field(default=None, metadata=field_metadata) show_more_url: Optional[str] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class VideoComponentParameters(Base): replay_url: str = field(default=None, metadata=field_metadata) date: Optional[str] = field(default=None, metadata=field_metadata) description: Optional[str] = field(default=None, metadata=field_metadata) height: Optional[float] = field(default=None, metadata=field_metadata) title: Optional[str] = field(default=None, metadata=field_metadata) transparent: Optional[bool] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class WebinarProvider(Base): url: str = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class WebinarSpeaker(Base): name: str = field(default=None, metadata=field_metadata) title: str = field(default=None, metadata=field_metadata) author_url: Optional[str] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class WorkspaceDate(Base): days: float = field(default=None, metadata=field_metadata) text: str = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class WorkspaceTab(Base): id_: str = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) name: str = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ArticleComponentParameters(Base): height: float = field(default=None, metadata=field_metadata) tooltip: Optional[str] = field(default=None, metadata=field_metadata) commentary_channels: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) commentary_to_desktop_link: Optional[bool] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class AssetPlotComponentParameters(Base): height: float = field(default=None, metadata=field_metadata) tooltip: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class BarChartComponentParameters(Base): height: float = field(default=None, metadata=field_metadata) tooltip: Optional[str] = field(default=None, metadata=field_metadata) hide_legend: Optional[bool] = field(default=None, metadata=field_metadata) chart_name: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ChartComponentParameters(Base): height: float = field(default=None, metadata=field_metadata) ids: Tuple[str, ...] = field(default=None, metadata=field_metadata) tooltip: Optional[str] = field(default=None, metadata=field_metadata) hide_legend: Optional[bool] = field(default=None, metadata=field_metadata) chart_name: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CommentaryComponentParameters(Base): height: float = field(default=None, metadata=field_metadata) tooltip: Optional[str] = field(default=None, metadata=field_metadata) commentary_channels: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) commentary_to_desktop_link: Optional[bool] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class CommentaryPromoComponentParameters(Base): height: Optional[float] = field(default=None, metadata=field_metadata) tooltip: Optional[str] = field(default=None, metadata=field_metadata) commentary_channels: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) transparent: Optional[bool] = field(default=None, metadata=field_metadata) body: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class DataGridComponentParameters(Base): height: float = field(default=None, metadata=field_metadata) tooltip: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class LegendComponentParameters(Base): height: float = field(default=None, metadata=field_metadata) items: Tuple[LegendItem, ...] = field(default=None, metadata=field_metadata) position: Optional[str] = field(default=None, metadata=field_metadata) transparent: Optional[bool] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class MonitorComponentParameters(Base): height: float = field(default=None, metadata=field_metadata) tooltip: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class NotificationTokens(Base): tokens: NotificationTokenBody = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class PlotComponentParameters(Base): height: float = field(default=None, metadata=field_metadata) tooltip: Optional[str] = field(default=None, metadata=field_metadata) hide_legend: Optional[bool] = field(default=None, metadata=field_metadata) plot_frequency_mode: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ResearchComponentParameters(Base): height: float = field(default=None, metadata=field_metadata) tooltip: Optional[str] = field(default=None, metadata=field_metadata) commentary_channels: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) commentary_to_desktop_link: Optional[bool] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class ScreenerComponentParameters(Base): height: float = field(default=None, metadata=field_metadata) tooltip: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SelectorComponentOption(Base): id_: str = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) name: str = field(default=None, metadata=field_metadata) tags: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class TreemapComponentParameters(Base): height: float = field(default=None, metadata=field_metadata) tooltip: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class WebinarComponentParameters(Base): date: str = field(default=None, metadata=field_metadata) date_text: str = field(default=None, metadata=field_metadata) description: str = field(default=None, metadata=field_metadata) provider: WebinarProvider = field(default=None, metadata=field_metadata) replay_url: str = field(default=None, metadata=field_metadata) title: str = field(default=None, metadata=field_metadata) height: Optional[float] = field(default=None, metadata=field_metadata) hosts: Optional[Tuple[WebinarSpeaker, ...]] = field(default=None, metadata=field_metadata) password: Optional[str] = field(default=None, metadata=field_metadata) series: Optional[str] = field(default=None, metadata=field_metadata) speakers: Optional[Tuple[WebinarSpeaker, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class RelatedLink(Base): type_: str = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) name: str = field(default=None, metadata=field_metadata) link: str = field(default=None, metadata=field_metadata) description: Optional[str] = field(default=None, metadata=field_metadata) notification_properties: Optional[NotificationTokens] = field(default=None, metadata=field_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class SelectorComponentParameters(Base): height: float = field(default=None, metadata=field_metadata) container_ids: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) default_option_index: Optional[float] = field(default=None, metadata=field_metadata) options: Optional[Tuple[SelectorComponentOption, ...]] = field(default=None, metadata=field_metadata) title: Optional[str] = field(default=None, metadata=field_metadata) tooltip: Optional[str] = field(default=None, metadata=field_metadata) width: Optional[float] = field(default=None, metadata=field_metadata) parent_selector_id: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class RelatedLinksComponentParameters(Base): height: float = field(default=None, metadata=field_metadata) links: Tuple[RelatedLink, ...] = field(default=None, metadata=field_metadata) title: str = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class WorkspaceCallToAction(Base): actions: Tuple[RelatedLink, ...] = field(default=None, metadata=field_metadata) text: str = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class WorkspaceComponent(Base): id_: str = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) type_: ComponentType = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) hide: Optional[bool] = field(default=None, metadata=field_metadata) tags: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) selections: Optional[Tuple[ComponentSelection, ...]] = field(default=None, metadata=field_metadata) container_ids: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) parameters: Optional[Union[ArticleComponentParameters, AssetPlotComponentParameters, BarChartComponentParameters, ChartComponentParameters, CommentaryComponentParameters, CommentaryPromoComponentParameters, ContainerComponentParameters, DataGridComponentParameters, LegendComponentParameters, MarketComponentParameters, MonitorComponentParameters, PlotComponentParameters, PromoComponentParameters, RelatedLinksComponentParameters, ResearchComponentParameters, ScreenerComponentParameters, SelectorComponentParameters, SeparatorComponentParameters, TreemapComponentParameters, VideoComponentParameters, WebinarComponentParameters]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class WorkspaceParameters(Base): layout: str = field(default=None, metadata=field_metadata) components: Tuple[WorkspaceComponent, ...] = field(default=None, metadata=field_metadata) call_to_action: Optional[WorkspaceCallToAction] = field(default=None, metadata=field_metadata) can_share: Optional[bool] = field(default=None, metadata=field_metadata) date: Optional[WorkspaceDate] = field(default=None, metadata=field_metadata) disclaimer: Optional[str] = field(default=None, metadata=field_metadata) maintainers: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) parent_collection_id: Optional[str] = field(default=None, metadata=field_metadata) tabs: Optional[Tuple[WorkspaceTab, ...]] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=name_metadata) @handle_camel_case_args @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass(unsafe_hash=True, repr=False) class Workspace(Base): parameters: WorkspaceParameters = field(default=None, metadata=field_metadata) id_: Optional[str] = field(default=None, metadata=config(field_name='id', exclude=exclude_none)) alias: Optional[str] = field(default=None, metadata=field_metadata) name: Optional[str] = field(default=None, metadata=field_metadata) type_: Optional[WorkspaceType] = field(default=None, metadata=config(field_name='type', exclude=exclude_none)) tags: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) created_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) last_updated_time: Optional[datetime.datetime] = field(default=None, metadata=field_metadata) created_by_id: Optional[str] = field(default=None, metadata=field_metadata) last_updated_by_id: Optional[str] = field(default=None, metadata=field_metadata) owner_id: Optional[str] = field(default=None, metadata=field_metadata) entitlements: Optional[Entitlements] = field(default=None, metadata=field_metadata) folder_name: Optional[str] = field(default=None, metadata=field_metadata) description: Optional[str] = field(default=None, metadata=field_metadata) children_aliases: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata) ================================================ FILE: gs_quant/test/__init__.py ================================================ ================================================ FILE: gs_quant/test/analytics/__init__.py ================================================ ================================================ FILE: gs_quant/test/analytics/test_datagrid.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import pytest from gs_quant.session import GsSession, Environment from gs_quant.analytics.datagrid import DataGrid, DataColumn, DataRow from gs_quant.analytics.processors import EntityProcessor, ChangeProcessor, AppendProcessor from gs_quant.data import DataCoordinate, DataMeasure, DataFrequency from gs_quant.datetime.relative_date import RelativeDate from gs_quant.test.utils.datagrid_test_utils import get_test_entity def test_simple_datagrid(): name = 'Testing' spx = get_test_entity('MA4B66MW5E27U8P32SB') rows = [ DataRow(spx), ] columns = [DataColumn(name="Name", processor=EntityProcessor(field="short_name"))] datagrid = DataGrid(name=name, rows=rows, columns=columns) assert datagrid.name == name assert datagrid.rows == rows assert datagrid.columns == columns assert datagrid.is_initialized is False assert len(datagrid.results) == 0 datagrid.initialize() assert datagrid.is_initialized is True assert len(datagrid.results) > 0 def test_rdate_datagrid(mocker): mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) name = 'Testing' SPX = get_test_entity('MA4B66MW5E27U8P32SB') close = DataCoordinate( measure=DataMeasure.CLOSE_PRICE, frequency=DataFrequency.DAILY, ) last_trade_price = DataCoordinate( measure=DataMeasure.TRADE_PRICE, frequency=DataFrequency.REAL_TIME, ) rows = [ DataRow(SPX), ] columns = [ DataColumn( name="1d Chg (RT)", processor=ChangeProcessor( AppendProcessor(close, last_trade_price, start=RelativeDate("-1d", base_date=dt.date(2021, 1, 22))) ), ) ] datagrid = DataGrid(name=name, rows=rows, columns=columns) start_date = datagrid.columns[0].processor.children['a'].start assert start_date.base_date == RelativeDate('-1d', base_date=dt.date(2021, 1, 22)).base_date assert start_date.rule == RelativeDate('-1d').rule datagrid.initialize() datagrid.poll() assert str(datagrid._data_queries[0].query.start) == '2021-01-21' as_dict = datagrid.as_dict() start = as_dict['parameters']['columns'][0]['parameters']['a']['parameters']['start'] assert start['type'] == 'relativeDate' assert start['value'] == {'rule': '-1d', 'baseDate': '2021-01-22'} # Check that base_date is not persisted when not passed in. columns = [ DataColumn( name="1d Chg (RT)", processor=ChangeProcessor(AppendProcessor(close, last_trade_price, start=RelativeDate("-1d"))), ) ] datagrid = DataGrid(name=name, rows=rows, columns=columns) as_dict = datagrid.as_dict() start = as_dict['parameters']['columns'][0]['parameters']['a']['parameters']['start'] assert start['type'] == 'relativeDate' assert start['type'] == 'relativeDate' assert start['value'] == {'rule': '-1d'} if __name__ == '__main__': pytest.main(args=["test_datagrid.py"]) ================================================ FILE: gs_quant/test/analytics/test_sorting_and_filtering.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import numpy as np import pytest from gs_quant.analytics.core.processor_result import ProcessorResult from gs_quant.analytics.datagrid import DataGrid, DataColumn from gs_quant.analytics.datagrid.data_cell import DataCell from gs_quant.analytics.datagrid.utils import SortOrder, DataGridSort, DataGridFilter, FilterOperation, FilterCondition from gs_quant.test.utils.datagrid_test_utils import get_test_entity class TestSortingAndFiltering: def get_test_entities(self): spx = get_test_entity('MA4B66MW5E27U8P32SB') aapl = get_test_entity('MARCRZHY163GQ4H3') amzn = get_test_entity('MASGTFZYNA7PMQ3H') return spx, aapl, amzn def get_test_datagrid(self): spx, aapl, amzn = self.get_test_entities() datagrid = DataGrid('Test DataGrid', rows=[], columns=[DataColumn('Name', None), DataColumn('Value', None)]) results = [ [DataCell('Name', None, None, None, 0, 0), DataCell('Value', None, None, None, 1, 0)], [DataCell('Name', None, None, None, 0, 1), DataCell('Value', None, None, None, 1, 1)], [DataCell('Name', None, None, None, 0, 2), DataCell('Value', None, None, None, 1, 2)], ] results[0][0].value = ProcessorResult(True, spx.name) results[0][1].value = ProcessorResult(True, np.float64(10)) results[1][0].value = ProcessorResult(True, aapl.name) results[1][1].value = ProcessorResult(True, np.float64(0)) results[2][0].value = ProcessorResult(True, amzn.name) results[2][1].value = ProcessorResult(True, np.float64(-10)) datagrid.results = results return datagrid def test_post_processing_base_case(self): spx, aapl, amzn = self.get_test_entities() datagrid = self.get_test_datagrid() df = datagrid._post_process() # Check no filters/sorts assert df['Name'].iloc[0] == spx.name assert df['Name'].iloc[1] == aapl.name assert df['Name'].iloc[2] == amzn.name assert df['Value'].iloc[0] == 10 assert df['Value'].iloc[1] == 0 assert df['Value'].iloc[2] == -10 def test_sorting(self): # Test 1: simple name in ascending order spx, aapl, amzn = self.get_test_entities() datagrid = self.get_test_datagrid() datagrid.add_sort(DataGridSort('Name')) df = datagrid._post_process() assert df['Name'].iloc[0] == amzn.name assert df['Name'].iloc[1] == aapl.name assert df['Name'].iloc[2] == spx.name assert df['Value'].iloc[0] == -10 assert df['Value'].iloc[1] == 0 assert df['Value'].iloc[2] == 10 # Test 2: simple name in descending order spx, aapl, amzn = self.get_test_entities() datagrid = self.get_test_datagrid() datagrid.sorts = [DataGridSort('Name', order=SortOrder.DESCENDING)] df = datagrid._post_process() assert df['Name'].iloc[0] == spx.name assert df['Name'].iloc[1] == aapl.name assert df['Name'].iloc[2] == amzn.name assert df['Value'].iloc[0] == 10 assert df['Value'].iloc[1] == 0 assert df['Value'].iloc[2] == -10 # Test 3: Sort values in ascending order spx, aapl, amzn = self.get_test_entities() datagrid = self.get_test_datagrid() datagrid.sorts = [DataGridSort('Value')] df = datagrid._post_process() assert df['Name'].iloc[0] == amzn.name assert df['Name'].iloc[1] == aapl.name assert df['Name'].iloc[2] == spx.name assert df['Value'].iloc[0] == -10 assert df['Value'].iloc[1] == 0 assert df['Value'].iloc[2] == 10 def test_filtering(self): spx, aapl, amzn = self.get_test_entities() # Test 1: Test equals number datagrid = self.get_test_datagrid() datagrid.add_filter(DataGridFilter('Value', FilterOperation.EQUALS, 0)) df = datagrid._post_process() assert df['Name'].iloc[0] == aapl.name assert df['Value'].iloc[0] == 0 assert len(df) == 1 # Test 2: Test equals numbers datagrid = self.get_test_datagrid() datagrid.add_filter(DataGridFilter('Value', FilterOperation.EQUALS, [0, 10])) df = datagrid._post_process() assert df['Name'].iloc[0] == spx.name assert df['Value'].iloc[0] == 10 assert df['Name'].iloc[1] == aapl.name assert df['Value'].iloc[1] == 0 assert len(df) == 2 # Test 3: Test greater than 0 and less than 0 datagrid = self.get_test_datagrid() datagrid.add_filter(DataGridFilter('Value', FilterOperation.GREATER_THAN, 0)) datagrid.add_filter(DataGridFilter('Value', FilterOperation.LESS_THAN, 0)) df = datagrid._post_process() assert len(df) == 0 # Test 3: Test greater than 0 or less than 0 datagrid = self.get_test_datagrid() datagrid.add_filter(DataGridFilter('Value', FilterOperation.GREATER_THAN, 0)) datagrid.add_filter(DataGridFilter('Value', FilterOperation.LESS_THAN, 0, FilterCondition.OR)) datagrid.sorts = [DataGridSort('Value', order=SortOrder.DESCENDING)] # Filtering can give different order df = datagrid._post_process() assert df['Name'].iloc[0] == spx.name assert df['Value'].iloc[0] == 10 assert df['Name'].iloc[1] == amzn.name assert df['Value'].iloc[1] == -10 assert len(df) == 2 if __name__ == '__main__': pytest.main(args=["test_sorting_and_filtering.py"]) ================================================ FILE: gs_quant/test/analytics/test_workspace.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import pytest from gs_quant.analytics.workspaces import PlotComponent, WorkspaceRow, Workspace, WorkspaceColumn def test_layout_creation(): plot_component_1 = PlotComponent(200, id_='CHCHF6NW1KXKFDAG') plot_component_2 = PlotComponent(200, id_='CHCHF6NW1KXKFDAG') plot_component_3 = PlotComponent(550, id_='CHCHF6NW1KXKFDAG') plot_component_4 = PlotComponent(550, id_='CHCHF6NW1KXKFDAG', width=8) # Case 1: Simple layout rows = [WorkspaceRow(components=[plot_component_1, plot_component_2])] workspace = Workspace(rows=rows, alias='testing-something', name='Testing') workspace = workspace.as_dict() assert workspace['parameters']['layout'] == 'r(c6($0)c6($1))' # Case 2: Extra columns equal spaced layout rows = [ WorkspaceRow( components=[ WorkspaceColumn( components=[ WorkspaceRow(components=[plot_component_1]), WorkspaceRow(components=[plot_component_2]), ] ), plot_component_3, ] ) ] workspace = Workspace(rows=rows, alias='testing-something', name='Testing') workspace = workspace.as_dict() assert workspace['parameters']['layout'] == 'r(c6(r(c12($0))r(c12($1)))c6($2))' # Case 3: Simple non-equal layout rows = [WorkspaceRow(components=[plot_component_4, plot_component_2])] workspace = Workspace(rows=rows, alias='testing-something', name='Testing') workspace = workspace.as_dict() assert workspace['parameters']['layout'] == 'r(c8($0)c4($1))' # Case 4: Non-equal spacing rows = [ WorkspaceRow( components=[ WorkspaceColumn( width=8, components=[ WorkspaceRow(components=[plot_component_1]), WorkspaceRow(components=[plot_component_2]), ], ), plot_component_3, ] ) ] workspace = Workspace(rows=rows, alias='testing-something', name='Testing') workspace = workspace.as_dict() assert workspace['parameters']['layout'] == 'r(c8(r(c12($0))r(c12($1)))c4($2))' def test_layout_parsing(): plot_component_1 = PlotComponent(1, id_='CHCHF6NW1KXKFDAG') # 0 is not supported for height plot_component_2 = PlotComponent(2, id_='CHCHF6NW1KXKFDAG') plot_component_3 = PlotComponent(3, id_='CHCHF6NW1KXKFDAG') # Case 1: Single Component rows = [WorkspaceRow(components=[plot_component_1])] workspace = Workspace(rows=rows, alias='testing-something', name='Testing') workspace = Workspace.from_dict(workspace.as_dict()) assert isinstance(workspace.rows[0], WorkspaceRow) assert isinstance(workspace.rows[0].components[0], PlotComponent) assert workspace.rows[0].components[0].width == 12 assert workspace.rows[0].components[0].height == 1 # Case 2: 2 Components rows = [WorkspaceRow(components=[plot_component_1, plot_component_2])] workspace = Workspace(rows=rows, alias='testing-something', name='Testing') workspace = Workspace.from_dict(workspace.as_dict()) assert isinstance(workspace.rows[0], WorkspaceRow) assert isinstance(workspace.rows[0].components[0], PlotComponent) assert workspace.rows[0].components[0].width == 6 assert workspace.rows[0].components[0].height == 1 assert isinstance(workspace.rows[0], WorkspaceRow) assert isinstance(workspace.rows[0].components[1], PlotComponent) assert workspace.rows[0].components[1].width == 6 assert workspace.rows[0].components[1].height == 2 # Case 3: Nested Columns rows = [ WorkspaceRow( components=[ WorkspaceColumn( components=[ WorkspaceRow(components=[plot_component_1]), WorkspaceRow(components=[plot_component_2]), ] ), plot_component_3, ] ) ] workspace = Workspace(rows=rows, alias='testing-something', name='Testing') workspace = Workspace.from_dict(workspace.as_dict()) assert isinstance(workspace.rows[0], WorkspaceRow) assert isinstance(workspace.rows[0].components[0], WorkspaceColumn) assert workspace.rows[0].components[0].width == 6 assert len(workspace.rows[0].components[0].components) == 2 assert workspace.rows[0].components[0].components[0].components[0].height == 1 assert workspace.rows[0].components[0].components[1].components[0].height == 2 assert workspace.rows[0].components[1].height == 3 if __name__ == '__main__': pytest.main(args=["test_workspace.py"]) ================================================ FILE: gs_quant/test/api/__init__.py ================================================ ================================================ FILE: gs_quant/test/api/backtests_xasset/__init__.py ================================================ ================================================ FILE: gs_quant/test/api/backtests_xasset/json_encoders/__init__.py ================================================ ================================================ FILE: gs_quant/test/api/backtests_xasset/json_encoders/response_datatypes/__init__.py ================================================ ================================================ FILE: gs_quant/test/api/backtests_xasset/json_encoders/response_datatypes/test_risk_result_datatype_encoders.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import numpy as np import pandas as pd from pandas._testing import assert_series_equal, assert_frame_equal # noqa from gs_quant.api.gs.backtests_xasset.json_encoders.response_datatypes.risk_result_datatype_encoders import ( encode_series_result, encode_dataframe_result, decode_series_result, decode_dataframe_result, ) def test_encode_series_result(): d = {'a': 1, 'b': 2, 'c': 3} s = pd.Series(data=d, index=['a', 'b', 'c'], name='test') enc = encode_series_result(s) assert enc == {'index': ('a', 'b', 'c'), 'name': 'test', 'values': (1, 2, 3)} def test_encode_dataframe_result(): df = pd.DataFrame(np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), columns=['a', 'b', 'c']) enc = encode_dataframe_result(df) assert enc == {'index': (0, 1, 2), 'columns': ('a', 'b', 'c'), 'values': ((1, 2, 3), (4, 5, 6), (7, 8, 9))} def test_decode_series_result(): enc = {'index': ('2024-06-12', '2024-06-13', '2024-06-14'), 'name': 'test', 'values': (1, 2, 3)} s = decode_series_result(enc) assert_series_equal( s, pd.Series( data=[1, 2, 3], index=[dt.date(2024, 6, 12), dt.date(2024, 6, 13), dt.date(2024, 6, 14)], name='test' ), ) def test_decode_dataframe_result(): enc = { 'index': ('2024-06-12', '2024-06-13', '2024-06-14'), 'columns': ('a', 'b', 'c'), 'values': ((1, 2, 3), (4, 5, 6), (7, 8, 9)), } df = decode_dataframe_result(enc) assert_frame_equal( df, pd.DataFrame( np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), columns=['a', 'b', 'c'], index=[dt.date(2024, 6, 12), dt.date(2024, 6, 13), dt.date(2024, 6, 14)], dtype=np.int64, ), ) ================================================ FILE: gs_quant/test/api/backtests_xasset/json_encoders/response_datatypes/test_risk_result_encoders.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import pandas as pd from pandas._testing import assert_series_equal from gs_quant.api.gs.backtests_xasset.json_encoders.response_datatypes.risk_result_encoders import ( map_result_to_datatype, decode_risk_result, ) from gs_quant.api.gs.backtests_xasset.response_datatypes.risk_result import RefType, RiskResultsByDate from gs_quant.api.gs.backtests_xasset.response_datatypes.risk_result_datatypes import ( FloatWithData, StringWithData, VectorWithData, MatrixWithData, ) def test_map_result_to_datatype(): assert map_result_to_datatype(5) == FloatWithData assert map_result_to_datatype(5.0) == FloatWithData assert map_result_to_datatype("abc") == StringWithData assert map_result_to_datatype(pd.Series([])) == VectorWithData assert map_result_to_datatype(pd.DataFrame([])) == MatrixWithData def test_decode_risk_result(): encoded_series_result_1 = {'index': ('a', 'b', 'c'), 'name': 'test', 'values': (1, 2, 3)} encoded_series_result_2 = {'index': ('a', 'b', 'c'), 'name': 'test', 'values': (1, 2, 3)} risk_result = { 'refs': {'legId': 'uuid_1', 'riskMeasure': 'uuid_2'}, 'result': { '2024-06-11': {'unit': 'EUR', 'result': encoded_series_result_1, 'type': 'vector'}, '2024-06-12': {'unit': 'EUR', 'result': encoded_series_result_2, 'type': 'vector'}, }, } decoded = decode_risk_result(risk_result) assert isinstance(decoded, RiskResultsByDate) assert decoded.refs == {RefType.LEG_ID: 'uuid_1', RefType.RISK_MEASURE: 'uuid_2'} assert isinstance(decoded.result, dict) assert len(decoded.result) == 2 assert isinstance(decoded.result[dt.date(2024, 6, 11)], VectorWithData) assert decoded.result[dt.date(2024, 6, 11)].unit == 'EUR' assert_series_equal( decoded.result[dt.date(2024, 6, 11)].result, pd.Series(data=[1, 2, 3], index=['a', 'b', 'c'], name='test') ) ================================================ FILE: gs_quant/test/api/backtests_xasset/json_encoders/test_request_encoders.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.api.gs.backtests_xasset.json_encoders.request_encoders import legs_decoder, legs_encoder from gs_quant.common import AssetClass, AssetType from gs_quant.instrument import FXOption, EqOption def test_legs_decoder(): fx_leg_1 = {"pair": "EURUSD", "assetClass": "FX", "type": "Option", "name": "leg_0"} fx_leg_2 = {"pair": "GBPUSD", "assetClass": "FX", "type": "Option"} eq_leg = {"underlier": ".SPX", "assetClass": "Equity", "type": "Option", "name": "test_eq"} [inst_1, inst_2, inst_3] = legs_decoder([fx_leg_1, fx_leg_2, eq_leg]) assert isinstance(inst_1, FXOption) assert inst_1.name == "leg_0" assert inst_1.pair == "EURUSD" assert isinstance(inst_2, FXOption) assert inst_2.name == "leg_1" assert inst_2.pair == "GBPUSD" assert isinstance(inst_3, EqOption) assert inst_3.name == "test_eq" assert inst_3.underlier == ".SPX" def test_legs_encoder(): fx_leg_1 = FXOption(pair="EURUSD", name="leg_0") fx_leg_2 = FXOption(pair="GBPUSD") eq_leg = EqOption(underlier=".SPX", name="test_eq") [inst_1, inst_2, inst_3] = legs_encoder([fx_leg_1, fx_leg_2, eq_leg]) assert isinstance(inst_1, dict) assert inst_1["assetClass"] == AssetClass.FX assert inst_1["type"] == AssetType.Option assert inst_1["pair"] == "EURUSD" assert isinstance(inst_2, dict) assert inst_2["assetClass"] == AssetClass.FX assert inst_2["type"] == AssetType.Option assert inst_2["pair"] == "GBPUSD" assert isinstance(inst_3, dict) assert inst_3["assetClass"] == AssetClass.Equity assert inst_3["type"] == AssetType.Option assert inst_3["underlier"] == ".SPX" ================================================ FILE: gs_quant/test/api/backtests_xasset/json_encoders/test_response_encoders.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.api.gs.backtests_xasset.json_encoders.response_encoders import decode_basic_bt_transactions from gs_quant.api.gs.backtests_xasset.response_datatypes.backtest_datatypes import TransactionDirection from gs_quant.common import Currency from gs_quant.instrument import EqOption import datetime as dt def test_decode_basic_bt_transactions(): server_response_minimum = { dt.date(2025, 5, 1).isoformat(): [ { 'portfolio': (EqOption(underlier='.SPX', expiration_date='2025-12-19', strike_price=5000).as_dict(),), } ] } assert len(decode_basic_bt_transactions(server_response_minimum)) == 1 assert len(decode_basic_bt_transactions(server_response_minimum, decode_instruments=False)) == 1 server_response = { dt.date(2025, 5, 1).isoformat(): [ { 'portfolio': (EqOption(underlier='.SPX', expiration_date='2025-12-19', strike_price=5000).as_dict(),), 'currency': 'USD', 'portfolio_price': 100, 'cost': 0.5, 'direction': 'Entry', 'quantity': 25, } ] } decoded_response = decode_basic_bt_transactions(server_response) transaction = decoded_response[dt.date(2025, 5, 1)][0] assert transaction.portfolio_price == 100 assert transaction.cost == 0.5 assert transaction.currency == Currency.USD assert transaction.direction == TransactionDirection.Entry assert transaction.quantity == 25 ================================================ FILE: gs_quant/test/api/backtests_xasset/response_datatypes/test_backtest_datatypes.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import dataclasses import pytest from gs_quant.api.gs.backtests_xasset.response_datatypes.backtest_datatypes import ( Transaction, AdditionalResults, DateConfig, Trade, Configuration, TransactionCostConfig, FixedCostModel, ScaledCostModel, TransactionCostScalingType, AggregateCostModel, CostAggregationType, ) def test_request_types(): cls = (Transaction, AdditionalResults, DateConfig, Trade, TransactionCostConfig, Configuration) for c in cls: assert dataclasses.is_dataclass(c) def test_model_addition(): f1 = FixedCostModel(1) f2 = FixedCostModel(2) s1 = ScaledCostModel(10, TransactionCostScalingType.Vega) s2 = ScaledCostModel(20, TransactionCostScalingType.Vega) s3 = ScaledCostModel(20, TransactionCostScalingType.Delta) assert f1 + f2 == FixedCostModel(3) assert s1 + s2 == ScaledCostModel(30, TransactionCostScalingType.Vega) assert f1 + s1 == AggregateCostModel((f1, s1), CostAggregationType.Sum) assert f2 + s2 == AggregateCostModel((f2, s2), CostAggregationType.Sum) assert (f1 + s1) + (f2 + s2) == AggregateCostModel((f1, s1, f2, s2), CostAggregationType.Sum) assert s1 + s3 == AggregateCostModel((s1, s3), CostAggregationType.Sum) assert AggregateCostModel((f1, s1), CostAggregationType.Min) + AggregateCostModel( (f2, s2), CostAggregationType.Min ) == AggregateCostModel((f1, s1, f2, s2), CostAggregationType.Min) with pytest.raises(TypeError): s1 + 1 with pytest.raises(TypeError): s1 + 'a' with pytest.raises(TypeError): s1 + AggregateCostModel((f1,), CostAggregationType.Sum) with pytest.raises(TypeError): AggregateCostModel((f1, s1), CostAggregationType.Min) + AggregateCostModel((f2, s2), CostAggregationType.Max) ================================================ FILE: gs_quant/test/api/backtests_xasset/response_datatypes/test_risk_result.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import dataclasses from gs_quant.api.gs.backtests_xasset.response_datatypes.risk_result import RiskResultsByDate, RiskResultsError def test_request_types(): cls = (RiskResultsByDate, RiskResultsError) for c in cls: assert dataclasses.is_dataclass(c) ================================================ FILE: gs_quant/test/api/backtests_xasset/response_datatypes/test_risk_result_datatypes.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import dataclasses import pytest from gs_quant.api.gs.backtests_xasset.response_datatypes.risk_result_datatypes import ( RiskResultWithData, FloatWithData, StringWithData, VectorWithData, MatrixWithData, ) def test_request_types(): cls = (RiskResultWithData, FloatWithData, StringWithData, VectorWithData, MatrixWithData) for c in cls: assert dataclasses.is_dataclass(c) assert issubclass(c, RiskResultWithData) def test_arithmetics(): assert FloatWithData(result=2) + FloatWithData(result=3) == FloatWithData(result=5) assert FloatWithData(result=2) - FloatWithData(result=3) == FloatWithData(result=-1) assert FloatWithData(result=2) * FloatWithData(result=3) == FloatWithData(result=6) assert FloatWithData(result=2, unit='EUR') / FloatWithData(result=3, unit='EUR') == FloatWithData( result=2 / 3, unit='EUR' ) assert 3 + FloatWithData(result=2, unit='EUR') == FloatWithData(result=5, unit='EUR') with pytest.raises(TypeError): FloatWithData(result=2) + 'a' with pytest.raises(ValueError): FloatWithData(result=2, unit='EUR') + FloatWithData(result=3, unit='USD') assert StringWithData(result='ab') + StringWithData(result='cd') == StringWithData(result='abcd') assert StringWithData(result='ab') + 'cd' == StringWithData(result='abcd') ================================================ FILE: gs_quant/test/api/backtests_xasset/test_request.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import dataclasses from gs_quant.api.gs.backtests_xasset.request import RiskRequest, BasicBacktestRequest, GenericBacktestRequest def test_request_types(): cls = (RiskRequest, BasicBacktestRequest, GenericBacktestRequest) for c in cls: assert dataclasses.is_dataclass(c) ================================================ FILE: gs_quant/test/api/backtests_xasset/test_response.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import dataclasses from gs_quant.api.gs.backtests_xasset.response import RiskResponse, BasicBacktestResponse, GenericBacktestResponse def test_response_types(): cls = (RiskResponse, BasicBacktestResponse, GenericBacktestResponse) for c in cls: assert dataclasses.is_dataclass(c) ================================================ FILE: gs_quant/test/api/test_assets.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import dateutil.parser as dup import pytest import testfixtures from gs_quant.api.gs.assets import GsAssetApi, GsAsset, GsTemporalXRef, ENABLE_ASSET_CACHING from gs_quant.session import Environment, GsSession from gs_quant.common import PositionType, XRef from gs_quant.target.assets import FieldFilterMap, Position, PositionSet, EntityQuery def test_get_asset(mocker): marquee_id = 'MQA1234567890' mock_response = GsAsset(id=marquee_id, assetClass='Equity', type='Single Stock', name='Test Asset') # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', return_value=mock_response) # run test response = GsAssetApi.get_asset(marquee_id) GsSession.current.sync.get.assert_called_with('/assets/{id}'.format(id=marquee_id), cls=GsAsset) assert response == mock_response def test_get_many_assets(mocker, monkeypatch): marquee_id_1 = 'MQA1234567890' marquee_id_2 = 'MQA4567890123' query = {'id': [marquee_id_1, marquee_id_2]} as_of = dt.datetime.utcnow() inputs = EntityQuery(where=FieldFilterMap(**query), fields=None, asOfTime=as_of, limit=100) mock_response = { 'results': ( GsAsset.from_dict({'id': marquee_id_1, 'assetClass': 'Equity', 'type': 'Single Stock', 'name': 'Test 1'}), GsAsset.from_dict({'id': marquee_id_2, 'assetClass': 'Equity', 'type': 'Single Stock', 'name': 'Test 2'}), ) } expected_response = ( GsAsset(id=marquee_id_1, assetClass='Equity', type='Single Stock', name='Test 1'), GsAsset(id=marquee_id_2, assetClass='Equity', type='Single Stock', name='Test 2'), ) # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'post', return_value=mock_response) # run test monkeypatch.delenv(ENABLE_ASSET_CACHING, raising=False) response = GsAssetApi.get_many_assets(id=[marquee_id_1, marquee_id_2], as_of=as_of) GsSession.current.sync.post.assert_called_with('/assets/query', cls=GsAsset, payload=inputs) assert response == expected_response monkeypatch.setenv(ENABLE_ASSET_CACHING, '1') # run 2x with cache on response = GsAssetApi.get_many_assets(id=[marquee_id_1, marquee_id_2], as_of=as_of) assert response == expected_response response = GsAssetApi.get_many_assets(id=[marquee_id_1, marquee_id_2], as_of=as_of) assert response == expected_response def test_get_asset_xrefs(mocker): marquee_id = 'MQA1234567890' mock_response = { 'xrefs': ( { 'startDate': '1952-01-01', 'endDate': '2018-12-31', 'identifiers': {'ric': '.GSTHHOLD', 'bbid': 'GSTHHOLD', 'cusip': '9EQ24FOLD', 'ticker': 'GSTHHOLD'}, }, { 'startDate': '2019-01-01', 'endDate': '2952-12-31', 'identifiers': { 'ric': '.GSTHHVIP', 'bbid': 'GSTHHVIP', 'cusip': '9EQ24FPE5', 'ticker': 'GSTHHVIP', }, }, ) } expected_response = ( GsTemporalXRef( dt.date(1952, 1, 1), dt.date(2018, 12, 31), XRef( ric='.GSTHHOLD', bbid='GSTHHOLD', cusip='9EQ24FOLD', ticker='GSTHHOLD', ), ), GsTemporalXRef( dt.date(2019, 1, 1), dt.date(2952, 12, 31), XRef( ric='.GSTHHVIP', bbid='GSTHHVIP', cusip='9EQ24FPE5', ticker='GSTHHVIP', ), ), ) # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', return_value=mock_response) # run test response = GsAssetApi.get_asset_xrefs(marquee_id) GsSession.current.sync.get.assert_called_with('/assets/{id}/xrefs'.format(id=marquee_id)) testfixtures.compare(response, expected_response) def test_get_asset_positions_for_date(mocker): marquee_id = 'MQA1234567890' position_date = dt.date(2019, 2, 19) mock_response = { 'results': ( { 'id': 'mock1', 'positionDate': '2019-02-19', 'lastUpdateTime': '2019-02-19T12:10:32.401Z', 'positions': [{'assetId': 'MQA123', 'quantity': 0.3}, {'assetId': 'MQA456', 'quantity': 0.7}], 'type': 'open', 'divisor': 100, }, { 'id': 'mock2', 'positionDate': '2019-02-19', 'lastUpdateTime': '2019-02-20T05:04:32.981Z', 'positions': [{'assetId': 'MQA123', 'quantity': 0.4}, {'assetId': 'MQA456', 'quantity': 0.6}], 'type': 'close', 'divisor': 120, }, ) } expected_response = ( PositionSet( id_='mock1', position_date=dt.date(2019, 2, 19), last_update_time=dup.parse('2019-02-19T12:10:32.401Z'), positions=(Position(assetId='MQA123', quantity=0.3), Position(assetId='MQA456', quantity=0.7)), type_='open', divisor=100, ), PositionSet( id_='mock2', position_date=dt.date(2019, 2, 19), last_update_time=dup.parse('2019-02-20T05:04:32.981Z'), positions=(Position(assetId='MQA123', quantity=0.4), Position(assetId='MQA456', quantity=0.6)), type_='close', divisor=120, ), ) # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', return_value=mock_response) # run test response = GsAssetApi.get_asset_positions_for_date(marquee_id, position_date) GsSession.current.sync.get.assert_called_with( '/assets/{id}/positions/{date}'.format(id=marquee_id, date=position_date) ) testfixtures.compare(response, expected_response) mock_response = { 'results': [ { 'id': 'mock', 'positionDate': '2019-02-19', 'lastUpdateTime': '2019-02-20T05:04:32.981Z', 'positions': [{'assetId': 'MQA123', 'quantity': 0.4}, {'assetId': 'MQA456', 'quantity': 0.6}], 'type': 'close', 'divisor': 120, } ] } expected_response = ( PositionSet( id_='mock', position_date=dt.date(2019, 2, 19), last_update_time=dup.parse('2019-02-20T05:04:32.981Z'), positions=(Position(assetId='MQA123', quantity=0.4), Position(assetId='MQA456', quantity=0.6)), type_='close', divisor=120, ), ) # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', return_value=mock_response) # run test response = GsAssetApi.get_asset_positions_for_date(marquee_id, position_date, PositionType.CLOSE) testfixtures.compare(response, expected_response) GsSession.current.sync.get.assert_called_with( '/assets/{id}/positions/{date}?type=close'.format(id=marquee_id, date=position_date) ) if __name__ == "__main__": pytest.main(args=[__file__]) ================================================ FILE: gs_quant/test/api/test_backtests.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.api.gs.backtests import GsBacktestApi, Backtest from gs_quant.session import Environment, GsSession def test_get_many_backtests(mocker): id_1 = 'BT1' id_2 = 'BT2' mock_response = { 'results': ( Backtest.from_dict({'id': id_1, 'assetClass': 'Commod', 'type': 'Basket', 'name': 'Example Backtest 1'}), Backtest.from_dict({'id': id_2, 'assetClass': 'Commod', 'type': 'Basket', 'name': 'Example Backtest 2'}), ), 'totalResults': 2, } expected_response = ( Backtest(id=id_1, assetClass="Commod", type="Basket", name='Example Backtest 1'), Backtest(id=id_2, assetClass="Commod", type="Basket", name='Example Backtest 2'), ) # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', return_value=mock_response) # run test response = GsBacktestApi.get_many_backtests() GsSession.current.sync.get.assert_called_with('/backtests?limit=100', cls=Backtest) assert response == expected_response def test_get_backtest(mocker): id_1 = 'BT1' mock_response = Backtest(id=id_1, assetClass="Commod", type="Basket", name='Example Backtest') # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', return_value=mock_response) # run test response = GsBacktestApi.get_backtest(id_1) GsSession.current.sync.get.assert_called_with('/backtests/{id}'.format(id=id_1), cls=Backtest) assert response == mock_response def test_create_backtest(mocker): id_1 = 'BT1' backtest = Backtest(id=id_1, assetClass="Commod", type="Basket", name='Example Backtest') # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'post', return_value=backtest) # run test response = GsBacktestApi.create_backtest(backtest) request_headers = {'Content-Type': 'application/json;charset=utf-8', 'Accept': 'application/json;charset=utf-8'} GsSession.current.sync.post.assert_called_with( '/backtests', backtest, request_headers=request_headers, cls=Backtest ) assert response == backtest def test_update_backtest(mocker): id_1 = 'BT1' backtest = Backtest(id=id_1, assetClass="Commod", type="Basket", name='Example Backtest') # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'put', return_value=backtest) # run test response = GsBacktestApi.update_backtest(backtest) request_headers = {'Content-Type': 'application/json;charset=utf-8', 'Accept': 'application/json;charset=utf-8'} GsSession.current.sync.put.assert_called_with( '/backtests/{id}'.format(id=id_1), backtest, request_headers=request_headers, cls=Backtest ) assert response == backtest def test_delete_backtest(mocker): id_1 = 'BT1' mock_response = "Successfully deleted backtest." # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'delete', return_value=mock_response) # run test response = GsBacktestApi.delete_backtest(id_1) GsSession.current.sync.delete.assert_called_with('/backtests/{id}'.format(id=id_1)) assert response == mock_response def test_schedule_backtest(mocker): id_1 = 'BT1' mock_response = "Successfully scheduled backtest." # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'post', return_value=mock_response) # run test response = GsBacktestApi.schedule_backtest(backtest_id=id_1) GsSession.current.sync.post.assert_called_with('/backtests/{id}/schedule'.format(id=id_1)) assert response == mock_response ================================================ FILE: gs_quant/test/api/test_base_screener.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.api.gs.base_screener import GsBaseScreenerApi from gs_quant.session import Environment, GsSession from gs_quant.target.base_screener import Screener, ScreenerRow, ScreenerColumn, ScreenerParameters def test_get_all_screeners(mocker): rows = (ScreenerRow(entity_id='MA33ZVRPT84HRK5A', entity_type='index', universe=True, expand=True),) cols1 = (ScreenerColumn(column_name='Name', entity_parameter='name'),) cols2 = (ScreenerColumn(column_name='BBID', entity_parameter='bbid'),) screener1_params = ScreenerParameters(rows=rows, columns=cols1) screener2_params = ScreenerParameters(rows=rows, columns=cols2) screener1 = Screener(name='Screener1', parameters=screener1_params) screener2 = Screener(name='Screener2', parameters=screener2_params) mock_response = {'totalResults': 2, 'results': (screener1, screener2)} expected_response = (screener1, screener2) # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', return_value=mock_response) # run test response = GsBaseScreenerApi.get_screeners() GsSession.current.sync.get.assert_called_with('/data/screeners', cls=Screener) assert response == expected_response def test_get_screen(mocker): rows = (ScreenerRow(entity_id='MA33ZVRPT84HRK5A', entity_type='index', universe=True, expand=True),) cols = (ScreenerColumn(column_name='Name', entity_parameter='name'),) screener_params = ScreenerParameters(rows=rows, columns=cols) screener_id = 'id' mock_response = Screener(name='Screener', parameters=screener_params, id_=screener_id) # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', return_value=mock_response) # run test response = GsBaseScreenerApi.get_screener(screener_id) GsSession.current.sync.get.assert_called_with('/data/screeners/{id}'.format(id=screener_id), cls=Screener) assert response == mock_response def test_create_screener(mocker): rows = (ScreenerRow(entity_id='MA33ZVRPT84HRK5A', entity_type='index', universe=True, expand=True),) cols = (ScreenerColumn(column_name='Name', entity_parameter='name'),) screener_params = ScreenerParameters(rows=rows, columns=cols) mock_input = Screener(name='Screener', parameters=screener_params, id_=None) mock_response = Screener(name='Screener', parameters=screener_params, id_='1') # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'post', return_value=mock_response) # run test response = GsBaseScreenerApi.create_screener(mock_input) request_headers = {'Content-Type': 'application/json;charset=utf-8'} GsSession.current.sync.post.assert_called_with( '/data/screeners', mock_input, request_headers=request_headers, cls=Screener ) assert response == mock_response def test_edit_screener(mocker): rows = (ScreenerRow(entity_id='MA33ZVRPT84HRK5A', entity_type='index', universe=True, expand=True),) new_cols = (ScreenerColumn(column_name='BBID', entity_parameter='bbid'),) screener_params = ScreenerParameters(rows=rows, columns=new_cols) screener_id = 'id' mock_screen = Screener(name='Screener', parameters=screener_params, id_=screener_id) # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'put', return_value=mock_screen) # run test response = GsBaseScreenerApi.edit_screener(screener_id, mock_screen) request_headers = {'Content-Type': 'application/json;charset=utf-8'} GsSession.current.sync.put.assert_called_with( '/data/screeners/{id}'.format(id=screener_id), mock_screen, request_headers=request_headers, cls=Screener ) assert response == mock_screen def test_publish_to_screener(mocker): screener_id = 'id' publish_data = {'rows': [{"Name": "Obalon Therapeutics Inc", "Entity ID": "ABDFDSCD"}]} mock_response = { "requestId": "", "timestamp": "2022-06-10T19:46:54.945Z", "data": [ { "name": "Obalon Therapeutics Inc", "entityId": "ABDFDSCD", "active": True, "lastUpdatedTime": "2022-06-10T19:46:54.862Z", } ], } expected_result = [ { "name": "Obalon Therapeutics Inc", "entityId": "ABDFDSCD", "active": True, "lastUpdatedTime": "2022-06-10T19:46:54.862Z", } ] # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'post', return_value=mock_response) # run test response = GsBaseScreenerApi.publish_to_screener(screener_id, publish_data) request_headers = {'Content-Type': 'application/json;charset=utf-8'} GsSession.current.sync.post.assert_called_with( '/data/screeners/{id}/publish'.format(id=screener_id), publish_data, request_headers=request_headers ) assert response == expected_result def test_clear_screener(mocker): screener_id = 'id' mock_response = {'requestId': '1', 'timestamp': '2022-08-01T16:56:11.241Z', 'success': True} # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'post', return_value=mock_response) # run test response = GsBaseScreenerApi.clear_screener(screener_id) request_headers = {'Content-Type': 'application/json;charset=utf-8'} GsSession.current.sync.post.assert_called_with( '/data/screeners/{id}/clear'.format(id=screener_id), {}, request_headers=request_headers ) assert response == mock_response def test_delete_screener(mocker): screener_id = 'id' mock_response = None # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'delete', return_value=mock_response) # run test response = GsBaseScreenerApi.delete_screener(screener_id) GsSession.current.sync.delete.assert_called_with('/data/screeners/{id}'.format(id=screener_id)) assert response == mock_response ================================================ FILE: gs_quant/test/api/test_cache.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from unittest import mock import pandas as pd import gs_quant.risk as risk from gs_quant.api.gs.risk import GsRiskApi from gs_quant.instrument import IRSwap, IRSwaption from gs_quant.markets import HistoricalPricingContext, PricingCache, PricingContext from gs_quant.session import Environment, GsSession def set_session(): from gs_quant.session import OAuth2Session OAuth2Session.init = mock.MagicMock(return_value=None) GsSession.use(Environment.PROD, 'client_id', 'secret') PricingCache.clear() def test_cache_addition_removal(): set_session() p1 = IRSwap('Pay', '10y', 'DKK') with mock.patch('gs_quant.api.gs.risk.GsRiskApi._exec') as mocker: mocker.return_value = [[[[{'$type': 'Risk', 'val': 0.07}]]]] with PricingContext(use_cache=True) as pc: p1.price() price_key = pc._PricingContext__risk_key(risk.Price, p1.provider) delta_key = pc._PricingContext__risk_key(risk.IRDelta, p1.provider) assert PricingCache.get(price_key, p1) assert not PricingCache.get(delta_key, p1) # Assert that deleting the cached instrument removes it from the PricingCache # N.B, this may not work when debugging tests del p1 del mocker import gc gc.collect() p2 = IRSwap('Pay', '10y', 'DKK') with PricingContext.current as cur: p2_price_key = cur._PricingContext__risk_key(risk.Price, p2.provider) # assert not PricingCache.get(p2_price_key) with mock.patch('gs_quant.api.gs.risk.GsRiskApi._exec') as mocker: mocker.return_value = [[[[{'$type': 'Risk', 'val': 0.07}]]]] with PricingContext(use_cache=True): p2_price = p2.price() assert PricingCache.get(p2_price_key, p2) == p2_price.result() # Assert that running under a scenario does not retrieve the base result with mock.patch('gs_quant.api.gs.risk.GsRiskApi._exec') as mocker: mocker.return_value = [[[[{'$type': 'Risk', 'val': 0.08}]]]] with risk.RollFwd(date='1m'), PricingContext(use_cache=True) as spc: # Don't want the price without the scenario scenario_risk_key = spc._PricingContext__risk_key(risk.Price, p2.provider) assert not PricingCache.get(scenario_risk_key, p2) scenario_price = p2.price() assert PricingCache.get(scenario_risk_key, p2) == scenario_price.result() with PricingContext(use_cache=True) as pc, risk.RollFwd(date='1m'): cached_scenario_price = PricingCache.get(pc._PricingContext__risk_key(risk.Price, p2.provider), p2) # Check that we get the cached scenario price assert cached_scenario_price == scenario_price.result() # Check the base result is still correct assert PricingCache.get(p2_price_key, p2) == p2_price.result() # Assert that caching respects parameters, such as csa with mock.patch('gs_quant.api.gs.risk.GsRiskApi._exec') as mocker: mocker.return_value = [[[[{'$type': 'Risk', 'val': 0.08}]]]] with PricingContext(use_cache=True, csa_term='INVALID') as pc: # Don't want the price with default csa assert not PricingCache.get(pc._PricingContext__risk_key(risk.Price, p2.provider), p2) csa_price = p2.price() with PricingContext(use_cache=True, csa_term='INVALID') as pc: cached_csa_price = PricingCache.get(pc._PricingContext__risk_key(risk.Price, p2.provider), p2) # Check that we get the cached csa price assert cached_csa_price == csa_price.result() # Check the base result is still correct assert PricingCache.get(p2_price_key, p2) == p2_price.result() # Change a property and assert that p2 is no longer cached p2.notional_currency = 'EUR' assert not PricingCache.get(p2_price_key, p2) @mock.patch.object(GsRiskApi, '_exec') def test_cache_subset(mocker): set_session() ir_swap = IRSwap('Pay', '10y', 'DKK') values = [{'$type': 'Risk', 'val': 0.01}] mocker.return_value = [[[values]], [[values]]] dates = (dt.date(2019, 10, 7), dt.date(2019, 10, 8)) with HistoricalPricingContext(dates=dates, use_cache=True): price_f = ir_swap.price() price_f.result() for date in dates: with PricingContext(pricing_date=date, use_historical_diddles_only=True) as pc: risk_key = pc._PricingContext__risk_key(risk.Price, ir_swap.provider) cached_scalar = PricingCache.get(risk_key, ir_swap) assert cached_scalar assert isinstance(cached_scalar, float) with PricingContext(pricing_date=dt.date(2019, 10, 9)) as pc: risk_key = pc._PricingContext__risk_key(risk.Price, ir_swap.provider) cached2 = PricingCache.get(risk_key, ir_swap) assert cached2 is None values = [ { '$type': 'RiskVector', 'asset': [0.01, 0.015], 'points': [ {'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '1y'}, {'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '2y'}, ], } ] # Check that we can return the same values from the cache, after calculating once (with return values set to None) for return_values in ([[[values]], [[values]], [[values]]], None): mocker.return_value = return_values with HistoricalPricingContext(dates=dates, use_cache=True): risk_f = ir_swap.calc(risk.IRDelta) risk_frame = risk_f.result() assert isinstance(risk_frame, pd.DataFrame) assert len(risk_frame.index.unique()) == len(dates) @mock.patch.object(GsRiskApi, '_exec') def test_multiple_measures(mocker): day = [ [ [ { '$type': 'RiskVector', 'asset': [0.01, 0.015], 'points': [ {'type': 'IR Vol', 'asset': 'USD-LIBOR-BBA', 'class_': 'Swap', 'point': '1y'}, {'type': 'IR Vol', 'asset': 'USD-LIBOR-BBA', 'class_': 'Swap', 'point': '2y'}, ], } ] ], [ [ { '$type': 'RiskVector', 'asset': [0.01, 0.015], 'points': [ {'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '1y'}, {'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '2y'}, ], } ], ], [[{'$type': 'Risk', 'val': 0.01}]], ] mocker.return_value = [day, day, day] set_session() ir_swaption = IRSwaption('Pay', '10y', 'USD') dates = (dt.date(2019, 10, 7), dt.date(2019, 10, 8), dt.date(2019, 10, 9)) with HistoricalPricingContext(dates=dates, use_cache=True): ir_swaption.price() ir_swaption.calc(risk.IRDelta) ir_swaption.calc(risk.IRVega) # make sure all the risk measures got cached correctly for date in dates: with PricingContext(pricing_date=date, use_historical_diddles_only=True) as pc: for risk_measure in (risk.Price, risk.IRDelta, risk.IRVega): val = PricingCache.get(pc._PricingContext__risk_key(risk_measure, ir_swaption.provider), ir_swaption) assert val is not None with PricingContext(pricing_date=dt.date(2019, 10, 11), use_historical_diddles_only=True) as pc: for risk_measure in (risk.Price, risk.IRDelta, risk.IRVega): val = PricingCache.get(pc._PricingContext__risk_key(risk_measure, ir_swaption.provider), ir_swaption) assert val is None ================================================ FILE: gs_quant/test/api/test_carbon.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import pytest from gs_quant.api.gs.carbon import GsCarbonApi, CarbonCard from gs_quant.common import Currency from gs_quant.session import GsSession, Environment def test_get_carbon_data(mocker): mock_response = { "coverage": { "weights": { "portfolio": { "scope1": { "reportedVerified": 27.172942402143175, "reportedUnverified": 72.82705759785682, "estimated": 0, "missing": 0, } }, "benchmark": { "scope1": { "reportedVerified": 56.951789932735934, "reportedUnverified": 19.955009583898327, "estimated": 18.412744312759482, "missing": 4.680456170606256, } }, }, "numberOfCompanies": { "portfolio": { "scope1": {"reportedVerified": 50, "reportedUnverified": 50, "estimated": 0, "missing": 0} }, "benchmark": { "scope1": { "reportedVerified": 34.257425742574256, "reportedUnverified": 29.702970297029704, "estimated": 28.91089108910891, "missing": 7.128712871287124, } }, }, }, "emissions": { "scope1": { "portfolio": [ { "year": "2016", "portfolioEmissions": 0.009388646556910485, "emissionsIntensityRevenue": 0.2706793856871246, "emissionsIntensityEnterpriseValue": 0.03388271250693313, "emissionsIntensityMarketCap": 0.11190632050536728, }, { "year": "2017", "portfolioEmissions": 0.008619992844290039, "emissionsIntensityRevenue": 0.2412522591903448, "emissionsIntensityEnterpriseValue": 0.030906851992767197, "emissionsIntensityMarketCap": 0.1066210865476519, }, { "year": "2018", "portfolioEmissions": 0.008893662072152794, "emissionsIntensityRevenue": 0.21887783180015072, "emissionsIntensityEnterpriseValue": 0.03144114560735717, "emissionsIntensityMarketCap": 0.16376957940863043, }, { "year": "2019", "portfolioEmissions": 0.008896070619613242, "emissionsIntensityRevenue": 0.2225589953607619, "emissionsIntensityEnterpriseValue": 0.03199235113825498, "emissionsIntensityMarketCap": 0.13300579690144726, }, { "year": "2021", "portfolioEmissions": 0.005462648973310796, "emissionsIntensityRevenue": 0.17671239978936054, "emissionsIntensityEnterpriseValue": 0.019184671815761833, "emissionsIntensityMarketCap": 0.08433051410609066, }, ], "benchmark": [ { "year": "2016", "portfolioEmissions": 10.7359742295746, "emissionsIntensityRevenue": 128.48937740484533, "emissionsIntensityEnterpriseValue": 37.874021067055146, "emissionsIntensityMarketCap": 65.55320800814903, }, { "year": "2017", "portfolioEmissions": 10.053210673655316, "emissionsIntensityRevenue": 125.70320189825257, "emissionsIntensityEnterpriseValue": 35.24939382150576, "emissionsIntensityMarketCap": 57.499118590386026, }, { "year": "2018", "portfolioEmissions": 9.795527406862018, "emissionsIntensityRevenue": 111.70530289875649, "emissionsIntensityEnterpriseValue": 34.343535288823446, "emissionsIntensityMarketCap": 54.01561424453475, }, { "year": "2019", "portfolioEmissions": 8.006501437233737, "emissionsIntensityRevenue": 104.83823370420704, "emissionsIntensityEnterpriseValue": 28.07322928722559, "emissionsIntensityMarketCap": 42.148992638800664, }, { "year": "2021", "portfolioEmissions": 7.419956698372355, "emissionsIntensityRevenue": 104.38248106531313, "emissionsIntensityEnterpriseValue": 26.429401913564423, "emissionsIntensityMarketCap": 40.70785737756474, }, ], } }, } # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', return_value=mock_response) # run test response = GsCarbonApi.get_carbon_analytics( entity_id='MPRE78YG4J918ERD', benchmark_id='MA4B66MW5E27U8P32SB', include_estimates=True, currency=Currency.GBP, cards=[CarbonCard.COVERAGE, CarbonCard.EMISSIONS], ) GsSession.current.sync.get.assert_called_with( '/carbon/MPRE78YG4J918ERD?benchmark=MA4B66MW5E27U8P32SB&reportingYear=Latest¤cy=GBP' '&includeEstimates=true&useHistoricalData=false&normalizeEmissions=false&card=coverage&card=emissions' '&analyticsView=Long' ) assert response == mock_response if __name__ == '__main__': pytest.main(args=[__file__]) ================================================ FILE: gs_quant/test/api/test_content.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from base64 import b64decode from unittest import mock import pytest from gs_quant.api.gs.content import GsContentApi, OrderBy from gs_quant.session import GsSession, Environment from gs_quant.target.content import GetManyContentsResponse from gs_quant.test.fixtures.content import ContentFixtures def set_session(): from gs_quant.session import OAuth2Session OAuth2Session.init = mock.MagicMock(return_value=None) GsSession.use(Environment.QA, 'client_id', 'secret') @pytest.mark.parametrize( 'description, channels, asset_ids, author_ids, tags, offset, limit, order_by, expected_uri, expected_exception', [ ('Limit is too large, expect exception', set(), set(), set(), set(), 0, 1001, None, None, ValueError), ("Offset is too small, expect exception", set(), set(), set(), set(), -1, None, None, None, ValueError), ('Default params (none specified)', None, None, None, None, None, None, None, '/content', None), ( 'Normal case, multiple parameters', ('channel-1', 'channel-2'), {'asset-id-1'}, {'author-id-1'}, {'tag-1'}, None, None, None, '/content?channel=channel-1&channel=channel-2&asset_id=asset-id-1&author_id=author-id-1&tag=tag-1', None, ), ( "With offset, limit, and orderBy", set(), set(), set(), set(), 2, 12, {'direction': OrderBy.ASC, 'field': 'some-field'}, '/content?offset=2&limit=12&order_by= list: module_path = 'gs_quant.target.' + module_name __import__(module_path) module = sys.modules[module_path] return [m for n, m in inspect.getmembers(module) if inspect.isclass(m) and issubclass(m, Base) and m is not Base] def test_enum(): ccy = copy.deepcopy(Currency.HUF) assert ccy == Currency.HUF gbp_pickled = pickle.dumps(Currency.GBP) assert pickle.loads(gbp_pickled) == Currency.GBP def test_classes(): for module_name in ( 'assets', 'backtests', 'charts', 'common', 'content', 'coordinates', 'countries', 'data', 'hedge', 'indices', 'instrument', 'monitor', 'portfolios', 'reports', 'risk', 'trades', 'workspaces_markets', ): for typ in classes(module_name): if typ.__module__ != module_name: continue fields_by_name = typ._fields_by_name() if not fields_by_name: continue obj = typ.default_instance() for fld in fields_by_name.values(): _ = object.__getattribute__(obj, fld.name) if fld.init: setattr(obj, fld.name, None) ================================================ FILE: gs_quant/test/api/test_thread_manager.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from functools import partial import pytest from gs_quant.api.utils import ThreadPoolManager from gs_quant.session import GsSession class NullContextManager(object): def __init__(self, dummy_resource=None): self.dummy_resource = dummy_resource def __enter__(self): return self.dummy_resource def __exit__(self, *args): pass def dummy_function(number: int): return number def test_thread_manager(): func_0 = partial(dummy_function, 5) func_1 = partial(dummy_function, 0) func_2 = partial(dummy_function, 10) GsSession.current = NullContextManager() results = ThreadPoolManager.run_async([func_0, func_1, func_2]) assert results[0] == 5 assert results[1] == 0 assert results[2] == 10 if __name__ == '__main__': pytest.main(args=[__file__]) ================================================ FILE: gs_quant/test/api/test_users.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from unittest import mock import pytest from gs_quant.api.gs.users import GsUsersApi from gs_quant.session import Environment, GsSession from gs_quant.target.reports import User def test_get_users(mocker): mock_response = { "totalResults": 1, "results": [ { "internal": True, "systemUser": False, "appUser": False, "analyticsId": "id123", "city": "New York", "company": "Goldman Sachs Group", "rootOEId": "id123", "rootOEIndustry": {"name": "Multi-Business Organization"}, "oeIndustry": {"name": "Multi-Business Organization"}, "oeAlias": 17670, "country": "US", "coverage": [ {"phone": "+1 855-MARQUE-0", "name": "Marquee Help Desk", "email": "gs-marquee-help@gs.com"} ], "email": "jane.doe@gs.com", "kerberos": "doeja", "id": "id", "name": "Doe, Jane", "firstName": "Jane", "lastName": "Doe", "internalID": "doeja", "region": "Americas", "departmentCode": "Y362", "departmentName": "Marquee Engineering", "divisionName": "Global Banking & Markets", "businessUnit": "Marquee Engineering", "title": "Analyst", "login": "doeja", "tokens": ["active"], "roles": ["MarqueeResearchExperienceEligible"], "groups": ["3AV5PPWJBS4B89X76"], "expertiseTags": [], } ], } # mock GsSession from gs_quant.session import OAuth2Session OAuth2Session.init = mock.MagicMock(return_value=None) GsSession.use(Environment.QA, 'client_id', 'secret') mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', return_value=mock_response) # run test response = GsUsersApi.get_users(user_ids=['doeja'], user_emails=['jane.doe@gs.com']) GsSession.current.sync.get.assert_called_with('/users?&id=doeja&email=jane.doe@gs.com&limit=100&offset=0', cls=User) assert response == mock_response['results'] def test_get_current_user_info(mocker): mock_response = { "internal": True, "systemUser": False, "appUser": False, "analyticsId": "id123", "city": "New York", "company": "Goldman Sachs Group", "rootOEId": "id123", "rootOEIndustry": {"name": "Multi-Business Organization"}, "oeIndustry": {"name": "Multi-Business Organization"}, "oeAlias": 17670, "country": "US", "coverage": [{"phone": "+1 855-MARQUE-0", "name": "Marquee Help Desk", "email": "gs-marquee-help@gs.com"}], "email": "jane.doe@gs.com", "kerberos": "doeja", "id": "id", "name": "Doe, Jane", "firstName": "Jane", "lastName": "Doe", "internalID": "doeja", "region": "Americas", "departmentCode": "Y362", "departmentName": "Marquee Engineering", "divisionName": "Global Banking & Markets", "businessUnit": "Marquee Engineering", "title": "Analyst", "login": "doeja", "tokens": ["active"], "roles": ["MarqueeResearchExperienceEligible"], "groups": ["3AV5PPWJBS4B89X76"], "expertiseTags": [], } # mock GsSession from gs_quant.session import OAuth2Session OAuth2Session.init = mock.MagicMock(return_value=None) GsSession.use(Environment.QA, 'client_id', 'secret') mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', return_value=mock_response) # run test response = GsUsersApi.get_current_user_info() GsSession.current.sync.get.assert_called_with('/users/self') assert response == mock_response if __name__ == '__main__': pytest.main(args=[__file__]) ================================================ FILE: gs_quant/test/backtest/__init__.py ================================================ ================================================ FILE: gs_quant/test/backtest/test_backtest_eq_vol_engine.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from unittest import mock import datetime as dt from gs_quant.api.gs.backtests_xasset.apis import GsBacktestXassetApi from gs_quant.api.gs.backtests_xasset.request import BasicBacktestRequest from gs_quant.api.gs.backtests_xasset.response import BasicBacktestResponse from gs_quant.api.gs.backtests_xasset.response_datatypes.backtest_datatypes import ( DateConfig, Trade, TransactionCostConfig, TradingCosts, FixedCostModel, Configuration, RollDateMode, StrategyHedge, ) from gs_quant.backtests.backtest_objects import ConstantTransactionModel from gs_quant.backtests.strategy import Strategy from gs_quant.backtests.triggers import ( PeriodicTrigger, PeriodicTriggerRequirements, DateTriggerRequirements, AggregateTrigger, AggregateTriggerRequirements, PortfolioTriggerRequirements, TriggerDirection, ) from gs_quant.backtests.actions import ( EnterPositionQuantityScaledAction, HedgeAction, ExitPositionAction, AddTradeAction, AddScaledTradeAction, ScalingActionType, ) from gs_quant.backtests.equity_vol_engine import EquityVolEngine from gs_quant.common import BuySell from gs_quant.instrument import EqOption from gs_quant.markets.portfolio import Portfolio from gs_quant.risk import EqDelta from gs_quant.session import GsSession, Environment from gs_quant.target.backtests import ( OptionStyle, OptionType, BacktestTradingQuantityType, FlowVolBacktestMeasure, EquityMarketModel, ) import pytest import pandas as pd from gs_quant.common import TradeAs # Tests in this module intentionally exercise the deprecated EnterPositionQuantityScaledAction and # ExitPositionAction classes to verify the equity vol engine still supports them. pytestmark = pytest.mark.filterwarnings("ignore::DeprecationWarning") def set_session(): from gs_quant.session import OAuth2Session OAuth2Session.init = mock.MagicMock(return_value=None) GsSession.use(Environment.QA, 'client_id', 'secret') def api_mock_data() -> BasicBacktestResponse: pnl_response = { 'measures': { 'PNL': { '2019-02-18': {'result': 0, 'type': 'float'}, '2019-02-19': {'result': -0.18, 'type': 'float'}, '2019-02-20': {'result': -0.27, 'type': 'float'}, } }, 'portfolio': { "2019-02-18": [ { "expirationDate": "2019-05-20", "optionType": "Call", "strikePrice": 3244.79, "assetClass": "Equity", "underlier": ".STOXX50E", "currency": "EUR", "type": "Option", "settlementType": "Cash", "optionStyle": "European", "buySell": "Buy", "numberOfOptions": 1, }, { "assetClass": "Equity", "expirationDate": "2019-05-20", "strikePrice": 3244.79, "underlier": ".STOXX50E", "type": "Synthetic Forward", "currency": "EUR", "buySell": "Sell", "quantity": 0.38264157605861704, }, ] }, 'transactions': { "2019-02-18": [ { "portfolio": [ { "expirationDate": "2019-05-20", "optionType": "Call", "strikePrice": 3244.79, "assetClass": "Equity", "underlier": ".STOXX50E", "currency": "EUR", "type": "Option", "settlementType": "Cash", "optionStyle": "European", "buySell": "Buy", "numberOfOptions": 1, } ], "portfolio_price": 51.49543980712991, "cost": 0, "currency": None, "direction": "Entry", "quantity": 1, }, { "portfolio": [ { "assetClass": "Equity", "expirationDate": "2019-05-20", "strikePrice": 3244.79, "underlier": ".STOXX50E", "type": "Synthetic Forward", "currency": "EUR", "buySell": "Sell", "quantity": 0.38264157605861704, } ], "portfolio_price": -67.01178502458444, "cost": 0, "currency": None, "direction": "Entry", "quantity": -0.38264157605861704, }, ] }, 'additional_results': None, } return BasicBacktestResponse.from_dict_custom(pnl_response, decode_instruments=False) def mock_api_response(mocker, mock_result: BasicBacktestResponse): mocker.return_value = mock_result @mock.patch.object(GsBacktestXassetApi, 'calculate_basic_backtest') def test_eq_vol_engine_result(mocker): # 1. setup strategy start_date = dt.date(2019, 2, 18) end_date = dt.date(2019, 2, 20) option = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, name='option', ) long_call = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, buy_sell=BuySell.Buy, number_of_options=1, ) short_put = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Put, option_style=OptionStyle.European, buy_sell=BuySell.Sell, number_of_options=1, ) hedge_portfolio = Portfolio(name='SynFwd', priceables=[long_call, short_put]) action = EnterPositionQuantityScaledAction(priceables=option, trade_duration='1m', name='action') trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'), actions=action, ) hedgetrigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b'), actions=HedgeAction(EqDelta, priceables=hedge_portfolio, trade_duration='1b', name='hedge_action'), ) strategy = Strategy(initial_portfolio=None, triggers=[trigger, hedgetrigger]) # 2. setup mock api response mock_api_response(mocker, api_mock_data()) # 3. when run backtest set_session() backtest_result = EquityVolEngine.run_backtest(strategy, start_date, end_date) # 4. assert response data = [{'date': d, 'value': v.result} for d, v in api_mock_data().measures[FlowVolBacktestMeasure.PNL].items()] df = pd.DataFrame.from_records(data) df.date = pd.to_datetime(df.date) expected_pnl = df.set_index('date').value assert expected_pnl.equals(backtest_result.get_measure_series(FlowVolBacktestMeasure.PNL)) @mock.patch.object(GsBacktestXassetApi, 'calculate_basic_backtest') def test_engine_mapping_basic(mocker): # 1. setup strategy start_date = dt.date(2019, 2, 18) end_date = dt.date(2019, 2, 20) option = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, number_of_options=1, name='option', ) long_call = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, buy_sell=BuySell.Buy, number_of_options=1, ) short_put = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Put, option_style=OptionStyle.European, buy_sell=BuySell.Sell, number_of_options=1, ) hedge_portfolio = Portfolio(name='SynFwd', priceables=[long_call, short_put]) action = EnterPositionQuantityScaledAction(priceables=option, trade_duration='1m', name='action') trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'), actions=action, ) hedgetrigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b'), actions=HedgeAction(EqDelta, priceables=hedge_portfolio, trade_duration='1b', name='hedge_action'), ) strategy = Strategy(initial_portfolio=None, triggers=[trigger, hedgetrigger]) # 2. setup mock api response response = BasicBacktestResponse.from_dict_custom( { 'measures': { 'PNL': { '2019-02-18': {'result': 0, 'type': 'float'}, '2019-02-19': {'result': -0.18, 'type': 'float'}, '2019-02-20': {'result': -0.27, 'type': 'float'}, } }, 'portfolio': {}, 'transactions': {}, } ) mock_api_response(mocker, response) # 3. when run backtest set_session() EquityVolEngine.run_backtest(strategy, start_date, end_date) # 4. assert API call backtest = BasicBacktestRequest( dates=DateConfig(start_date=start_date, end_date=end_date), trades=( Trade( legs=tuple(action.priceables), buy_frequency='1m', holding_period='1m', buy_dates=None, exit_dates=None, quantity=1, quantity_type=BacktestTradingQuantityType.quantity, ), ), measures=(FlowVolBacktestMeasure.ALL_MEASURES,), delta_hedge_frequency=None, hedge=StrategyHedge(frequency='1b'), transaction_costs=TransactionCostConfig( trade_cost_model=TradingCosts(entry=FixedCostModel(0), exit=FixedCostModel(0)), hedge_cost_model=TradingCosts(entry=FixedCostModel(0), exit=FixedCostModel(0)), ), configuration=Configuration( market_model=EquityMarketModel.SFK, cash_accrual=True, combine_roll_signal_entries=False ), ) mocker.assert_called_with(backtest, decode_instruments=False) @mock.patch.object(GsBacktestXassetApi, 'calculate_basic_backtest') def test_engine_mapping_trade_quantity(mocker): # 1. setup strategy start_date = dt.date(2019, 2, 18) end_date = dt.date(2019, 2, 20) option = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, number_of_options=1, name='option', ) long_call = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, buy_sell=BuySell.Buy, number_of_options=1, ) short_put = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Put, option_style=OptionStyle.European, buy_sell=BuySell.Sell, number_of_options=1, ) hedge_portfolio = Portfolio(name='SynFwd', priceables=[long_call, short_put]) action = EnterPositionQuantityScaledAction( priceables=option, trade_duration='1m', trade_quantity=12345, trade_quantity_type=BacktestTradingQuantityType.notional, name='action', ) trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'), actions=action, ) hedgetrigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b'), actions=HedgeAction(EqDelta, priceables=hedge_portfolio, trade_duration='1b', name='hedge_action'), ) strategy = Strategy(initial_portfolio=None, triggers=[trigger, hedgetrigger]) # 2. setup mock api response mock_api_response(mocker, api_mock_data()) # 3. when run backtest set_session() EquityVolEngine.run_backtest(strategy, start_date, end_date) # 4. assert API call backtest = BasicBacktestRequest( dates=DateConfig(start_date=start_date, end_date=end_date), trades=( Trade( legs=tuple(action.priceables), buy_frequency='1m', holding_period='1m', buy_dates=None, exit_dates=None, quantity=12345, quantity_type=BacktestTradingQuantityType.notional, ), ), measures=(FlowVolBacktestMeasure.ALL_MEASURES,), delta_hedge_frequency=None, hedge=StrategyHedge(frequency='1b'), transaction_costs=TransactionCostConfig( trade_cost_model=TradingCosts(entry=FixedCostModel(0), exit=FixedCostModel(0)), hedge_cost_model=TradingCosts(entry=FixedCostModel(0), exit=FixedCostModel(0)), ), configuration=Configuration( market_model=EquityMarketModel.SFK, cash_accrual=True, combine_roll_signal_entries=False ), ) mocker.assert_called_with(backtest, decode_instruments=False) @mock.patch.object(GsBacktestXassetApi, 'calculate_basic_backtest') def test_engine_mapping_with_signals(mocker): # 1. setup strategy start_date = dt.date(2019, 2, 18) end_date = dt.date(2019, 2, 27) option = EqOption( '.STOXX50E', expirationDate='3m', strikePrice='ATM', optionType=OptionType.Call, optionStyle=OptionStyle.European, name='option', number_of_options=1, ) entry_action = EnterPositionQuantityScaledAction( priceables=option, trade_duration='1m', trade_quantity=12345, trade_quantity_type=BacktestTradingQuantityType.notional, name='action', ) entry_signal_series = pd.Series(data={dt.date(2019, 2, 19): 1}) entry_dates = entry_signal_series[entry_signal_series > 0].keys() entry_trigger = AggregateTrigger( AggregateTriggerRequirements( triggers=[ DateTriggerRequirements(dates=entry_dates), PortfolioTriggerRequirements('len', 0, TriggerDirection.EQUAL), ] ), actions=entry_action, ) exit_signal_series = pd.Series(data={dt.date(2019, 2, 20): 1}) exit_dates = exit_signal_series[exit_signal_series > 0].keys() exit_trigger = AggregateTrigger( AggregateTriggerRequirements( triggers=[ DateTriggerRequirements(dates=exit_dates), PortfolioTriggerRequirements('len', 0, TriggerDirection.ABOVE), ] ), actions=ExitPositionAction(name='exit_action'), ) strategy = Strategy(initial_portfolio=None, triggers=[entry_trigger, exit_trigger]) # 2. setup mock api response mock_api_response(mocker, api_mock_data()) # 3. when run backtest set_session() EquityVolEngine.run_backtest(strategy, start_date, end_date) # 4. assert API call backtest = BasicBacktestRequest( dates=DateConfig(start_date=start_date, end_date=end_date), trades=( Trade( legs=tuple(entry_action.priceables), buy_frequency='1m', holding_period='1m', buy_dates=tuple(entry_dates), exit_dates=tuple(exit_dates), quantity=12345, quantity_type=BacktestTradingQuantityType.notional, ), ), measures=(FlowVolBacktestMeasure.ALL_MEASURES,), delta_hedge_frequency=None, hedge=None, transaction_costs=TransactionCostConfig( trade_cost_model=TradingCosts(entry=FixedCostModel(0), exit=FixedCostModel(0)) ), configuration=Configuration( market_model=EquityMarketModel.SFK, cash_accrual=True, combine_roll_signal_entries=False ), ) mocker.assert_called_with(backtest, decode_instruments=False) @mock.patch.object(GsBacktestXassetApi, 'calculate_basic_backtest') def test_engine_mapping_trade_quantity_nav(mocker): # 1. setup strategy start_date = dt.date(2019, 2, 18) end_date = dt.date(2019, 2, 20) option = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, number_of_options=1, name='option', ) long_call = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, buy_sell=BuySell.Buy, number_of_options=1, ) short_put = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Put, option_style=OptionStyle.European, buy_sell=BuySell.Sell, number_of_options=1, ) hedge_portfolio = Portfolio(name='SynFwd', priceables=[long_call, short_put]) action = EnterPositionQuantityScaledAction( priceables=option, trade_duration='1m', trade_quantity=12345, trade_quantity_type=BacktestTradingQuantityType.NAV, name='action', ) trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'), actions=action, ) hedgetrigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b'), actions=HedgeAction(EqDelta, priceables=hedge_portfolio, trade_duration='1b', name='hedge_action'), ) strategy = Strategy(initial_portfolio=None, triggers=[trigger, hedgetrigger]) # 2. setup mock api response mock_api_response(mocker, api_mock_data()) # 3. when run backtest set_session() EquityVolEngine.run_backtest(strategy, start_date, end_date) # 4. assert API call backtest = BasicBacktestRequest( dates=DateConfig(start_date=start_date, end_date=end_date), trades=( Trade( legs=tuple(action.priceables), buy_frequency='1m', holding_period='1m', buy_dates=None, exit_dates=None, quantity=12345, quantity_type=BacktestTradingQuantityType.NAV, ), ), measures=(FlowVolBacktestMeasure.ALL_MEASURES,), delta_hedge_frequency=None, hedge=StrategyHedge(frequency='1b'), transaction_costs=TransactionCostConfig( trade_cost_model=TradingCosts(entry=FixedCostModel(0), exit=FixedCostModel(0)), hedge_cost_model=TradingCosts(entry=FixedCostModel(0), exit=FixedCostModel(0)), ), configuration=Configuration( market_model=EquityMarketModel.SFK, cash_accrual=True, combine_roll_signal_entries=False ), ) mocker.assert_called_with(backtest, decode_instruments=False) @mock.patch.object(GsBacktestXassetApi, 'calculate_basic_backtest') def test_engine_mapping_listed_expiry_date(mocker): # 1. setup strategy start_date = dt.date(2019, 2, 18) end_date = dt.date(2019, 2, 20) option = EqOption( '.STOXX50E', expiration_date='3m@listed', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, number_of_options=1, name='option', ) long_call = EqOption( '.STOXX50E', expiration_date='3m@listed', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, buy_sell=BuySell.Buy, number_of_options=1, ) short_put = EqOption( '.STOXX50E', expiration_date='3m@listed', strike_price='ATM', option_type=OptionType.Put, option_style=OptionStyle.European, buy_sell=BuySell.Sell, number_of_options=1, ) hedge_portfolio = Portfolio(name='SynFwd', priceables=[long_call, short_put]) action = EnterPositionQuantityScaledAction(priceables=option, trade_duration='1m', name='action') trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'), actions=action, ) hedgetrigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b'), actions=HedgeAction(EqDelta, priceables=hedge_portfolio, trade_duration='1b', name='hedge_action'), ) strategy = Strategy(initial_portfolio=None, triggers=[trigger, hedgetrigger]) # 2. setup mock api response mock_api_response(mocker, api_mock_data()) # 3. when run backtest set_session() EquityVolEngine.run_backtest(strategy, start_date, end_date) # 4. assert API call action.priceables[0].expiration_date = action.priceables[0].expiration_date.replace('@listed', '') action.priceables[0].trade_as = TradeAs.Listed backtest = BasicBacktestRequest( dates=DateConfig(start_date=start_date, end_date=end_date), trades=( Trade( legs=tuple(action.priceables), buy_frequency='1m', holding_period='1m', buy_dates=None, exit_dates=None, quantity=1, quantity_type=BacktestTradingQuantityType.quantity, ), ), measures=(FlowVolBacktestMeasure.ALL_MEASURES,), delta_hedge_frequency=None, hedge=StrategyHedge(frequency='1b'), transaction_costs=TransactionCostConfig( trade_cost_model=TradingCosts(entry=FixedCostModel(0), exit=FixedCostModel(0)), hedge_cost_model=TradingCosts(entry=FixedCostModel(0), exit=FixedCostModel(0)), ), configuration=Configuration( market_model=EquityMarketModel.SFK, cash_accrual=True, combine_roll_signal_entries=False ), ) mocker.assert_called_with(backtest, decode_instruments=False) @mock.patch.object(GsBacktestXassetApi, 'calculate_basic_backtest') def test_engine_mapping_listed_roll_date(mocker): # 1. setup strategy start_date = dt.date(2019, 2, 18) end_date = dt.date(2019, 2, 20) option = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, number_of_options=1, name='option', ) action = EnterPositionQuantityScaledAction(priceables=option, trade_duration='1m@listed', name='action') trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements( start_date=start_date, end_date=end_date, frequency='1m@listed' ), actions=action, ) strategy = Strategy( initial_portfolio=None, triggers=[ trigger, ], ) # 2. setup mock api response mock_api_response(mocker, api_mock_data()) # 3. when run backtest set_session() EquityVolEngine.run_backtest(strategy, start_date, end_date) # 4. assert API call backtest = BasicBacktestRequest( dates=DateConfig(start_date=start_date, end_date=end_date), trades=( Trade( legs=tuple(action.priceables), buy_frequency='1m', holding_period='1m', buy_dates=None, exit_dates=None, quantity=1, quantity_type=BacktestTradingQuantityType.quantity, ), ), measures=(FlowVolBacktestMeasure.ALL_MEASURES,), transaction_costs=TransactionCostConfig( trade_cost_model=TradingCosts(entry=FixedCostModel(0), exit=FixedCostModel(0)) ), configuration=Configuration( market_model=EquityMarketModel.SFK, cash_accrual=True, combine_roll_signal_entries=False, roll_date_mode=RollDateMode.Listed, ), ) mocker.assert_called_with(backtest, decode_instruments=False) @mock.patch.object(GsBacktestXassetApi, 'calculate_basic_backtest') def test_engine_mapping_market_model(mocker): # 1. setup strategy start_date = dt.date(2019, 2, 18) end_date = dt.date(2019, 2, 20) option = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, number_of_options=1, name='option', ) long_call = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, buy_sell=BuySell.Buy, number_of_options=1, ) short_put = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Put, option_style=OptionStyle.European, buy_sell=BuySell.Sell, number_of_options=1, ) hedge_portfolio = Portfolio(name='SynFwd', priceables=[long_call, short_put]) action = EnterPositionQuantityScaledAction(priceables=option, trade_duration='1m', name='action') trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'), actions=action, ) hedgetrigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b'), actions=HedgeAction(EqDelta, priceables=hedge_portfolio, trade_duration='1b', name='hedge_action'), ) strategy = Strategy(initial_portfolio=None, triggers=[trigger, hedgetrigger]) # 2. setup mock api response mock_api_response(mocker, api_mock_data()) # 3. when run backtest set_session() EquityVolEngine.run_backtest(strategy, start_date, end_date, market_model=EquityMarketModel.SVR) # 4. assert response backtest = BasicBacktestRequest( dates=DateConfig(start_date=start_date, end_date=end_date), trades=( Trade( legs=tuple(action.priceables), buy_frequency='1m', holding_period='1m', buy_dates=None, exit_dates=None, quantity=1, quantity_type=BacktestTradingQuantityType.quantity, ), ), measures=(FlowVolBacktestMeasure.ALL_MEASURES,), delta_hedge_frequency=None, hedge=StrategyHedge(frequency='1b'), transaction_costs=TransactionCostConfig( trade_cost_model=TradingCosts(entry=FixedCostModel(0), exit=FixedCostModel(0)), hedge_cost_model=TradingCosts(entry=FixedCostModel(0), exit=FixedCostModel(0)), ), configuration=Configuration( market_model=EquityMarketModel.SVR, cash_accrual=True, combine_roll_signal_entries=False ), ) mocker.assert_called_with(backtest, decode_instruments=False) @mock.patch.object(GsBacktestXassetApi, 'calculate_basic_backtest') def test_engine_mapping_portfolio(mocker): # 1. setup strategy start_date = dt.date(2019, 2, 18) end_date = dt.date(2019, 2, 20) call = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, number_of_options=1, name='option_call', ) put = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Put, option_style=OptionStyle.European, number_of_options=1, name='option_put', ) portfolio = Portfolio(name='portfolio', priceables=[call, put]) long_call = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, buy_sell=BuySell.Buy, ) short_put = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Put, option_style=OptionStyle.European, buy_sell=BuySell.Sell, ) hedge_portfolio = Portfolio(name='SynFwd', priceables=[long_call, short_put]) action = EnterPositionQuantityScaledAction(priceables=portfolio, trade_duration='1m', name='action') trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'), actions=action, ) hedgetrigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b'), actions=HedgeAction(EqDelta, priceables=hedge_portfolio, trade_duration='1b', name='hedge_action'), ) strategy = Strategy(initial_portfolio=None, triggers=[trigger, hedgetrigger]) # 2. setup mock api response mock_api_response(mocker, api_mock_data()) # 3. when run backtest set_session() EquityVolEngine.run_backtest(strategy, start_date, end_date) # 4. assert response backtest = BasicBacktestRequest( dates=DateConfig(start_date=start_date, end_date=end_date), trades=( Trade( legs=tuple(action.priceables), buy_frequency='1m', holding_period='1m', buy_dates=None, exit_dates=None, quantity=1, quantity_type=BacktestTradingQuantityType.quantity, ), ), measures=(FlowVolBacktestMeasure.ALL_MEASURES,), delta_hedge_frequency=None, hedge=StrategyHedge(frequency='1b'), transaction_costs=TransactionCostConfig( trade_cost_model=TradingCosts(entry=FixedCostModel(0), exit=FixedCostModel(0)), hedge_cost_model=TradingCosts(entry=FixedCostModel(0), exit=FixedCostModel(0)), ), configuration=Configuration( market_model=EquityMarketModel.SFK, cash_accrual=True, combine_roll_signal_entries=False ), ) mocker.assert_called_with(backtest, decode_instruments=False) def test_supports_strategy(): # 1. Valid strategy start_date = dt.date(2019, 2, 18) end_date = dt.date(2019, 2, 20) option = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, name='option', ) long_call = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, buy_sell=BuySell.Buy, ) short_put = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Put, option_style=OptionStyle.European, buy_sell=BuySell.Sell, ) hedge_portfolio = Portfolio(name='SynFwd', priceables=[long_call, short_put]) action = EnterPositionQuantityScaledAction(priceables=option, trade_duration='1m', name='action') trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'), actions=action, ) hedge_trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b'), actions=HedgeAction(EqDelta, priceables=hedge_portfolio, trade_duration='1b', name='hedge_action'), ) strategy = Strategy(initial_portfolio=None, triggers=[trigger, hedge_trigger]) assert EquityVolEngine.supports_strategy(strategy) # 2. Invalid - no trade action trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'), actions=None, ) strategy = Strategy(initial_portfolio=None, triggers=[trigger]) assert not EquityVolEngine.supports_strategy(strategy) # 3. Invalid - no trade quantity action = EnterPositionQuantityScaledAction( priceables=option, trade_duration='1m', trade_quantity=None, name='action' ) trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'), actions=action, ) strategy = Strategy(initial_portfolio=None, triggers=[trigger]) assert not EquityVolEngine.supports_strategy(strategy) # 4. Invalid - no trade quantity type action = EnterPositionQuantityScaledAction( priceables=option, trade_duration='1m', trade_quantity_type=None, name='action' ) trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'), actions=action, ) strategy = Strategy(initial_portfolio=None, triggers=[trigger]) assert not EquityVolEngine.supports_strategy(strategy) # 5. Invalid - mismatch trade duration and trigger period action = EnterPositionQuantityScaledAction(priceables=option, trade_duration='2m', name='action') trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'), actions=action, ) strategy = Strategy(initial_portfolio=None, triggers=[trigger]) assert not EquityVolEngine.supports_strategy(strategy) # 6. Invalid - mismatch hedge trade duration and trigger period action = EnterPositionQuantityScaledAction(priceables=option, trade_duration='1m', name='action') trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'), actions=action, ) hedge_trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='B'), actions=HedgeAction(EqDelta, priceables=hedge_portfolio, trade_duration='M', name='hedge_action'), ) strategy = Strategy(initial_portfolio=None, triggers=[trigger, hedge_trigger]) assert not EquityVolEngine.supports_strategy(strategy) # 7. Invalid - non-daily hedge trade action = EnterPositionQuantityScaledAction(priceables=option, trade_duration='1m', name='action') trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'), actions=action, ) hedge_trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='M'), actions=HedgeAction(EqDelta, priceables=hedge_portfolio, trade_duration='M', name='hedge_action'), ) strategy = Strategy(initial_portfolio=None, triggers=[trigger, hedge_trigger]) assert EquityVolEngine.supports_strategy(strategy) # 8. Invalid - expiration date modifiers must be the same option_listed = EqOption( '.STOXX50E', expirationDate='3m@listed', strikePrice='ATM', optionType=OptionType.Call, optionStyle=OptionStyle.European, name='option', ) action = EnterPositionQuantityScaledAction(priceables=[option, option_listed], trade_duration='1m', name='action') trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'), actions=action, ) strategy = Strategy(initial_portfolio=None, triggers=[trigger]) assert not EquityVolEngine.supports_strategy(strategy) # 9. Invalid - expiration date modifier not in [otc, listed] option_invalid = EqOption( '.STOXX50E', expirationDate='3m@invalid', strikePrice='ATM', optionType=OptionType.Call, name='option' ) action = EnterPositionQuantityScaledAction(priceables=[option_invalid], trade_duration='1m', name='action') trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'), actions=action, ) strategy = Strategy(initial_portfolio=None, triggers=[trigger]) assert not EquityVolEngine.supports_strategy(strategy) # 10. Invalid - hedging without synthetic forward (not a portfolio) hedge_portfolio = Portfolio(name='SynFwd', priceables=[long_call]) hedge_trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b'), actions=HedgeAction(EqDelta, priceables=hedge_portfolio, trade_duration='1b', name='hedge_action'), ) strategy = Strategy(initial_portfolio=None, triggers=[trigger, hedge_trigger]) assert not EquityVolEngine.supports_strategy(strategy) # 11. Invalid - hedging without synthetic forward (two calls) long_call_2 = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, buy_sell=BuySell.Buy, name='option', ) hedge_portfolio = Portfolio(name='SynFwd', priceables=[long_call, long_call_2]) hedge_trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b'), actions=HedgeAction(EqDelta, priceables=hedge_portfolio, trade_duration='1b', name='hedge_action'), ) strategy = Strategy(initial_portfolio=None, triggers=[trigger, hedge_trigger]) assert not EquityVolEngine.supports_strategy(strategy) # 12. Invalid - hedging without synthetic forward (more than two options) hedge_portfolio = Portfolio(name='SynFwd', priceables=[long_call, short_put, long_call_2]) hedge_trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b'), actions=HedgeAction(EqDelta, priceables=hedge_portfolio, trade_duration='1b', name='hedge_action'), ) strategy = Strategy(initial_portfolio=None, triggers=[trigger, hedge_trigger]) assert not EquityVolEngine.supports_strategy(strategy) # 13. Invalid - hedging without synthetic forward (properties mismatch) long_call_2m = EqOption( '.STOXX50E', expiration_date='2m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, buy_sell=BuySell.Buy, name='option_1', ) short_put_4m = EqOption( '.STOXX50E', expiration_date='4m', strike_price='ATM', option_type=OptionType.Put, option_style=OptionStyle.European, buy_sell=BuySell.Sell, name='option_2', ) hedge_portfolio = Portfolio(name='SynFwd', priceables=[long_call_2m, short_put_4m]) hedge_trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b'), actions=HedgeAction(EqDelta, priceables=hedge_portfolio, trade_duration='1b', name='hedge_action'), ) strategy = Strategy(initial_portfolio=None, triggers=[trigger, hedge_trigger]) assert not EquityVolEngine.supports_strategy(strategy) # 14. Valid - AddTradeAction add_trade_action = AddTradeAction(priceables=option, trade_duration='1m', name='action') trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'), actions=add_trade_action, ) strategy = Strategy(initial_portfolio=None, triggers=[trigger]) assert EquityVolEngine.supports_strategy(strategy) # 15. Valid - AddScaledTradeAction add_scaled_trade_action = AddScaledTradeAction( priceables=option, trade_duration='1m', scaling_type=ScalingActionType.size, scaling_level=2, name='action' ) trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'), actions=add_scaled_trade_action, ) strategy = Strategy(initial_portfolio=None, triggers=[trigger]) assert EquityVolEngine.supports_strategy(strategy) # 16. Invalid - transaction_costs not supported add_trade_action_tc = AddTradeAction( priceables=option, trade_duration='1m', transaction_cost=ConstantTransactionModel(100), name='action' ) trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'), actions=add_trade_action_tc, ) strategy = Strategy(initial_portfolio=None, triggers=[trigger]) assert EquityVolEngine.supports_strategy(strategy) # 17. Valid - transaction_costs supported if 0 add_trade_action_tc0 = AddTradeAction( priceables=option, trade_duration='1m', transaction_cost=ConstantTransactionModel(0), name='action' ) trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'), actions=add_trade_action_tc0, ) strategy = Strategy(initial_portfolio=None, triggers=[trigger]) assert EquityVolEngine.supports_strategy(strategy) # 18. Invalid - instrument non unit contract size option_with_mult = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, multiplier=100, name='option', ) action = AddTradeAction(priceables=option_with_mult, trade_duration='1m', name='action') trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'), actions=action, ) strategy = Strategy(initial_portfolio=None, triggers=[trigger]) assert not EquityVolEngine.supports_strategy(strategy) # 19. Invalid - instrument non unit contract count option_with_contracts = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, number_of_options=100, name='option', ) action = AddTradeAction(priceables=option_with_contracts, trade_duration='1m', name='action') trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'), actions=action, ) strategy = Strategy(initial_portfolio=None, triggers=[trigger]) assert not EquityVolEngine.supports_strategy(strategy) @mock.patch.object(GsBacktestXassetApi, 'calculate_basic_backtest') def test_engine_mapping_basic_leg_size(mocker): # 1. setup strategy start_date = dt.date(2019, 2, 18) end_date = dt.date(2019, 2, 20) option = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, name='option', number_of_options=0, ) long_call = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, buy_sell=BuySell.Buy, ) short_put = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Put, option_style=OptionStyle.European, buy_sell=BuySell.Sell, ) hedge_portfolio = Portfolio(name='SynFwd', priceables=[long_call, short_put]) action = EnterPositionQuantityScaledAction(priceables=option, trade_duration='1m', name='action') trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'), actions=action, ) hedgetrigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b'), actions=HedgeAction(EqDelta, priceables=hedge_portfolio, trade_duration='1b', name='hedge_action'), ) strategy = Strategy(initial_portfolio=None, triggers=[trigger, hedgetrigger]) # 2. setup mock api response mock_api_response(mocker, api_mock_data()) # 3. when run backtest set_session() EquityVolEngine.run_backtest(strategy, start_date, end_date) # 4. assert API call backtest = BasicBacktestRequest( dates=DateConfig(start_date=start_date, end_date=end_date), trades=( Trade( legs=tuple(action.priceables), buy_frequency='1m', holding_period='1m', buy_dates=None, exit_dates=None, quantity=1, quantity_type=BacktestTradingQuantityType.quantity, ), ), measures=(FlowVolBacktestMeasure.ALL_MEASURES,), delta_hedge_frequency=None, hedge=StrategyHedge(frequency='1b'), transaction_costs=TransactionCostConfig( trade_cost_model=TradingCosts(entry=FixedCostModel(0), exit=FixedCostModel(0)), hedge_cost_model=TradingCosts(entry=FixedCostModel(0), exit=FixedCostModel(0)), ), configuration=Configuration( market_model=EquityMarketModel.SFK, cash_accrual=True, combine_roll_signal_entries=False ), ) mocker.assert_called_with(backtest, decode_instruments=False) @mock.patch.object(GsBacktestXassetApi, 'calculate_basic_backtest') def test_engine_mapping_fixed_expiry(mocker): # 1. setup strategy start_date = dt.date(2019, 2, 18) end_date = dt.date(2019, 2, 20) option_call = EqOption( '.STOXX50E', expiration_date=dt.date(2020, 3, 20), strike_price='ATM', option_type=OptionType.Call, number_of_options=1, name='c', ) option_put = EqOption( '.STOXX50E', expiration_date="2020-03-20", strike_price='ATM', option_type=OptionType.Put, number_of_options=1, name='p', ) straddle = (option_call, option_put) action = EnterPositionQuantityScaledAction( priceables=straddle, trade_duration='1m', trade_quantity=12345, trade_quantity_type=BacktestTradingQuantityType.notional, name='action', ) trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'), actions=action, ) strategy = Strategy(initial_portfolio=None, triggers=[trigger]) # 2. setup mock api response mock_api_response(mocker, api_mock_data()) # 3. when run backtest set_session() EquityVolEngine.run_backtest(strategy, start_date, end_date) # 4. assert API call backtest = BasicBacktestRequest( dates=DateConfig(start_date=start_date, end_date=end_date), trades=( Trade( legs=tuple(action.priceables), buy_frequency='1m', holding_period='1m', buy_dates=None, exit_dates=None, quantity=12345, quantity_type=BacktestTradingQuantityType.notional, ), ), measures=(FlowVolBacktestMeasure.ALL_MEASURES,), delta_hedge_frequency=None, hedge=None, transaction_costs=TransactionCostConfig( trade_cost_model=TradingCosts(entry=FixedCostModel(0), exit=FixedCostModel(0)) ), configuration=Configuration( market_model=EquityMarketModel.SFK, cash_accrual=True, combine_roll_signal_entries=False ), ) mocker.assert_called_with(backtest, decode_instruments=False) @mock.patch.object(GsBacktestXassetApi, 'calculate_basic_backtest') def test_engine_mapping_delta_hedge(mocker): # 1. setup strategy start_date = dt.date(2019, 2, 18) end_date = dt.date(2019, 2, 20) option = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, number_of_options=1, name='option', ) long_call = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, buy_sell=BuySell.Buy, number_of_options=1, ) short_put = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Put, option_style=OptionStyle.European, buy_sell=BuySell.Sell, number_of_options=1, ) hedge_portfolio = Portfolio(name='SynFwd', priceables=[long_call, short_put]) action = EnterPositionQuantityScaledAction(priceables=option, trade_duration='1m', name='action') trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'), actions=action, ) hedgetrigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='5b'), actions=HedgeAction( EqDelta, priceables=hedge_portfolio, trade_duration='5b', risk_percentage=50, name='hedge_action' ), ) strategy = Strategy(initial_portfolio=None, triggers=[trigger, hedgetrigger]) # 2. setup mock api response response = BasicBacktestResponse.from_dict_custom( { 'measures': { 'PNL': { '2019-02-18': {'result': 0, 'type': 'float'}, '2019-02-19': {'result': -0.18, 'type': 'float'}, '2019-02-20': {'result': -0.27, 'type': 'float'}, } }, 'portfolio': {}, 'transactions': {}, } ) mock_api_response(mocker, response) # 3. when run backtest set_session() EquityVolEngine.run_backtest(strategy, start_date, end_date) # 4. assert API call backtest = BasicBacktestRequest( dates=DateConfig(start_date=start_date, end_date=end_date), trades=( Trade( legs=tuple(action.priceables), buy_frequency='1m', holding_period='1m', buy_dates=None, exit_dates=None, quantity=1, quantity_type=BacktestTradingQuantityType.quantity, ), ), measures=(FlowVolBacktestMeasure.ALL_MEASURES,), delta_hedge_frequency=None, hedge=StrategyHedge(frequency='5b', risk_percentage=50), transaction_costs=TransactionCostConfig( trade_cost_model=TradingCosts(entry=FixedCostModel(0), exit=FixedCostModel(0)), hedge_cost_model=TradingCosts(entry=FixedCostModel(0), exit=FixedCostModel(0)), ), configuration=Configuration( market_model=EquityMarketModel.SFK, cash_accrual=True, combine_roll_signal_entries=False ), ) mocker.assert_called_with(backtest, decode_instruments=False) ================================================ FILE: gs_quant/test/backtest/test_backtest_flow_vol.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from unittest import mock import gs_quant.target.backtests as backtests from gs_quant.api.gs.backtests import GsBacktestApi from gs_quant.backtests.core import Backtest from gs_quant.backtests.core import TradeInMethod from gs_quant.backtests.strategy_systematic import StrategySystematic from gs_quant.base import Base from gs_quant.common import AssetClass, Currency from gs_quant.instrument import EqOption from gs_quant.session import Environment, GsSession from gs_quant.target.backtests import DeltaHedgeParameters, BacktestTradingQuantityType, BacktestRisk underlierList = [ EqOption("MA4B66MW5E27U8P32SB", "3m", 3000, 'Call', 'European'), EqOption("MA4B66MW5E27U8P32SB", "3m", 3000, 'Put', 'European'), ] hedge = DeltaHedgeParameters(frequency='Daily') strategy = StrategySystematic( name="Mock Test", underliers=underlierList, delta_hedge=hedge, quantity=1, quantity_type=BacktestTradingQuantityType.notional, trade_in_method=TradeInMethod.FixedRoll, roll_frequency='1m', use_xasset_backtesting_service=False, ) def set_session(): from gs_quant.session import OAuth2Session OAuth2Session.init = mock.MagicMock(return_value=None) GsSession.use(Environment.QA, 'client_id', 'secret') @mock.patch.object(GsBacktestApi, 'run_backtest') def test_eqstrategies_backtest(mocker): start_date = dt.date(2019, 6, 3) end_date = dt.date(2019, 6, 5) data = [ { 'name': 'Delta', 'timeseries': [ {'date': '2019-02-18', 'price': 0}, {'date': '2019-02-19', 'price': 0}, {'date': '2019-02-20', 'price': -14256.398}, ], } ] delta = BacktestRisk.from_dict( { 'name': 'Delta', 'timeseries': [ {'date': '2019-02-18', 'price': 0}, {'date': '2019-02-19', 'price': -0.000000000058}, {'date': '2019-02-20', 'price': 0.000000000262}, ], } ) vega = BacktestRisk.from_dict( { 'name': 'Vega', 'timeseries': [ {'date': '2019-02-18', 'price': -22225.061886411593}, {'date': '2019-02-19', 'price': -50889.171772423098}, {'date': '2019-02-20', 'price': 44266.899784030174}, ], } ) gamma = BacktestRisk.from_dict( { 'name': 'Gamma', 'timeseries': [ {'date': '2019-02-18', 'price': 24834.106837453899}, {'date': '2019-02-19', 'price': 41164.597344927679}, {'date': '2019-02-20', 'price': 94404.04327008425}, ], } ) theta = BacktestRisk.from_dict( { 'name': 'Theta', 'timeseries': [ {'date': '2019-02-18', 'price': -57618.719522251748}, {'date': '2019-02-19', 'price': -113749.56356887663}, {'date': '2019-02-20', 'price': -169875.93826522797}, ], } ) risk_data = (delta, vega, gamma, theta) mock_response = backtests.BacktestResult('BT1', performance=data, risks=risk_data, stats=None, backtest_version=1) set_session() mocker.return_value = mock_response result = strategy.backtest(start_date, end_date) assert result == mock_response trading_parameters = backtests.BacktestTradingParameters( quantity=1, quantity_type=BacktestTradingQuantityType.notional.value, trade_in_method=TradeInMethod.FixedRoll.value, roll_frequency='1m', ) l1 = backtests.BacktestStrategyUnderlier( instrument=underlierList[0], notional_percentage=100, hedge=backtests.BacktestStrategyUnderlierHedge(risk_details=hedge), market_model='SFK', ) l2 = backtests.BacktestStrategyUnderlier( instrument=underlierList[1], notional_percentage=100, hedge=backtests.BacktestStrategyUnderlierHedge(risk_details=hedge), market_model='SFK', ) underliers = [l1, l2] backtest_parameters_class: Base = getattr(backtests, 'VolatilityFlowBacktestParameters') backtest_parameter_args = { 'trading_parameters': trading_parameters, 'underliers': underliers, 'trade_in_method': TradeInMethod.FixedRoll, 'scaling_method': None, 'index_initial_value': 0.0, } backtest_parameters = backtest_parameters_class.from_dict(backtest_parameter_args) params_dict = backtest_parameters.as_dict() params_dict["measures"] = [backtests.FlowVolBacktestMeasure.ALL_MEASURES] params = backtest_parameters_class.from_dict(params_dict) backtest = Backtest( name="Mock Test", mq_symbol="Mock Test", parameters=params, start_date=start_date, end_date=end_date, type='Volatility Flow', asset_class=AssetClass.Equity, currency=Currency.USD, cost_netting=False, ) mocker.assert_called_with(backtest, None) ================================================ FILE: gs_quant/test/backtest/test_backtest_predefined.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import zoneinfo import gs_quant.backtests.predefined_asset_engine import datetime as dt from gs_quant.backtests.actions import AddTradeAction from gs_quant.backtests.strategy import Strategy from gs_quant.backtests.triggers import OrdersGeneratorTrigger, DateTriggerRequirements, DateTrigger from gs_quant.backtests.backtest_objects import PredefinedAssetBacktest from gs_quant.backtests.predefined_asset_engine import PredefinedAssetEngine from gs_quant.backtests.data_sources import DataManager import pandas as pd import numpy as np from gs_quant.data.core import DataFrequency from gs_quant.backtests.core import ValuationFixingType from gs_quant.instrument import IRBondFuture from gs_quant.backtests.order import OrderMarketOnClose, OrderTWAP, TimeWindow from unittest import mock import gs_quant.datetime class ExampleTestTrigger(OrdersGeneratorTrigger): def __init__(self): super().__init__() def generate_orders(self, time: dt.datetime, backtest: PredefinedAssetBacktest = None) -> list: date = time.date() if date == dt.date(2021, 1, 5): return [ OrderMarketOnClose( instrument=IRBondFuture(currency='EUR', name='TestRic'), quantity=1, generation_time=time, execution_date=date, source='Test', ) ] else: return [] def get_trigger_times(self) -> list: return [dt.time(10, 0, 0)] class FuturesExample(OrdersGeneratorTrigger): def __init__(self): super().__init__() def get_trigger_times(self): """generate orders at 9:30am every day""" return [dt.time(9, 30)] def generate_orders(self, state: dt.datetime, backtest: PredefinedAssetBacktest = None) -> list: date = state.date() contract = IRBondFuture(currency='EUR', name='TestRic') orders = [] units_to_trade = 1 """ enter trade is a TWAP order between 10 and 10:30 """ exec_start = dt.datetime.combine(date, dt.time(10)) exec_end = dt.datetime.combine(date, dt.time(10, 30)) orders.append( OrderTWAP( instrument=contract, quantity=units_to_trade, generation_time=state, window=TimeWindow(start=exec_start, end=exec_end), source=str(self.__class__), ) ) """ exit the intraday quantity at TWAP between 14 and 14:30 """ twap_start = dt.datetime.combine(date, dt.time(14)) twap_end = dt.datetime.combine(date, dt.time(14, 30)) orders.append( OrderTWAP( instrument=contract, quantity=units_to_trade * -1, generation_time=state, window=TimeWindow(start=twap_start, end=twap_end), source=str(self.__class__), ) ) return orders def test_backtest_predefined_timezone_aware(): tz = 'Europe/London' start_dt = '2021-01-01T08:00' end_dt = '2021-12-31T17:00' states = ( pd.bdate_range(start_dt, end_dt, freq='1h', tz=tz).to_series().between_time('08:00', '17:00').index.tolist() ) trigger_dates = pd.bdate_range(start_dt, end_dt, freq='1h', tz=tz).to_series().at_time('17:00').index.tolist() data = np.random.default_rng().standard_normal(len(states)) s_rt = pd.Series(index=states, data=data) s_eod = s_rt.at_time('17:00') s_eod.index = s_eod.index.date generic_bond_future = IRBondFuture(currency='EUR', name='EURBond') add_trade_action = AddTradeAction(generic_bond_future) simple_date_trigger_requirement = DateTriggerRequirements(dates=trigger_dates) simple_date_trigger = DateTrigger(trigger_requirements=simple_date_trigger_requirement, actions=[add_trade_action]) data_manager = DataManager() data_manager.add_data_source( pd.Series(index=states, data=data), DataFrequency.REAL_TIME, generic_bond_future, ValuationFixingType.PRICE ) data_manager.add_data_source(s_eod, DataFrequency.DAILY, generic_bond_future, ValuationFixingType.PRICE) # instantiate a new strategy strategy = Strategy(None, triggers=simple_date_trigger) engine = PredefinedAssetEngine(data_mgr=data_manager, tz=zoneinfo.ZoneInfo(tz)) backtest = engine.run_backtest(strategy=strategy, start=states[0], end=states[-1], states=states) assert len(backtest.trade_ledger()) == 364 def test_backtest_predefined(): # Test simple MOC order trigger = ExampleTestTrigger() strategy = Strategy(initial_portfolio=None, triggers=[trigger]) start = dt.date(2021, 1, 4) mid = dt.date(2021, 1, 5) end = dt.date(2021, 1, 6) # these mocks are needed as the date functions need a GSSession gs_quant.backtests.predefined_asset_engine.is_business_day = mock.Mock(return_value=True) gs_quant.backtests.predefined_asset_engine.business_day_offset = mock.Mock(return_value=mid) data_mgr = DataManager() underlying = IRBondFuture(currency='EUR', name='TestRic') close_prices = pd.Series(dtype=float) close_prices[start] = 1 close_prices[mid] = 1.5 close_prices[end] = 2 data_mgr.add_data_source(close_prices, DataFrequency.DAILY, underlying, ValuationFixingType.PRICE) engine = PredefinedAssetEngine(data_mgr=data_mgr) backtest = engine.run_backtest(strategy, start=start, end=end) perf = backtest.performance holdings = backtest.historical_holdings cash_asset = backtest.cash_asset # 100 on the initial date assert perf[start] == 100 # 100 on the next day as we traded MOC assert perf[mid] == 100 assert holdings[mid][cash_asset] == 100 - 1.5 assert holdings[mid][underlying] == 1 # 100.5 = 98.5 (cash) + 2 ( test asset) assert holdings[end][cash_asset] == 100 - 1.5 assert holdings[end][underlying] == 1 assert perf[end] == 100.5 # Test TWAP orders with no ON positions twap_entry_mid = 16 twap_exit_mid = 25 twap_entry_end = 30 twap_exit_end = 40 data_twap = { dt.datetime.combine(mid, dt.time(10, 30)): twap_entry_mid, dt.datetime.combine(mid, dt.time(14, 30)): twap_exit_mid, dt.datetime.combine(end, dt.time(10, 30)): twap_entry_end, dt.datetime.combine(end, dt.time(14, 30)): twap_exit_end, } data_mgr.add_data_source(pd.Series(data_twap), DataFrequency.REAL_TIME, underlying, ValuationFixingType.PRICE) trigger = FuturesExample() strategy = Strategy(initial_portfolio=None, triggers=[trigger]) engine = PredefinedAssetEngine(data_mgr=data_mgr, tz=zoneinfo.ZoneInfo('Europe/London')) backtest = engine.run_backtest(strategy, start=start, end=end) perf = backtest.performance holdings = backtest.historical_holdings weights = backtest.historical_weights cash_asset = backtest.cash_asset # start: 100 assert perf[start] == 100 # mid: 100 + (twap_exit - twap_entry) assert perf[mid] == 100 - twap_entry_mid + twap_exit_mid assert holdings[mid][cash_asset] == perf[mid] assert underlying not in holdings[mid] assert weights[mid][cash_asset] == 1 assert underlying not in weights[mid] # 100.5 = 98.5 (cash) + 2 ( test asset) assert perf[end] == 100 - twap_entry_mid + twap_exit_mid - twap_entry_end + twap_exit_end assert holdings[end][cash_asset] == perf[end] assert underlying not in holdings[end] assert weights[end][cash_asset] == 1 assert underlying not in weights[end] ================================================ FILE: gs_quant/test/backtest/test_generic_engine.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import json from unittest.mock import patch import numpy as np import pandas as pd from gs_quant.backtests.actions import ( AddTradeAction, HedgeAction, ExitTradeAction, AddScaledTradeAction, ScalingActionType, ExitAllPositionsAction, AddWeightedTradeAction, ) from gs_quant.backtests.backtest_objects import ( ScaledTransactionModel, AggregateTransactionModel, TransactionAggType, ConstantTransactionModel, BackTest, ) from gs_quant.backtests.data_sources import GenericDataSource, MissingDataStrategy, GsDataSource from gs_quant.backtests.generic_engine import GenericEngine from gs_quant.backtests.strategy import Strategy from gs_quant.backtests.triggers import ( TriggerDirection, DateTrigger, DateTriggerRequirements, NotTrigger, RiskTriggerRequirements, StrategyRiskTrigger, PeriodicTriggerRequirements, PeriodicTrigger, MktTrigger, MktTriggerRequirements, IntradayPeriodicTrigger, IntradayTriggerRequirements, AggregateTrigger, AggregateTriggerRequirements, PortfolioTrigger, PortfolioTriggerRequirements, MeanReversionTrigger, MeanReversionTriggerRequirements, NotTriggerRequirements, ) from gs_quant.common import Currency, PayReceive, OptionType, OptionStyle from gs_quant.instrument import FXOption, FXForward, IRSwaption, IRSwap, EqOption from gs_quant.json_encoder import JSONEncoder from gs_quant.markets import PricingContext from gs_quant.markets.portfolio import Portfolio from gs_quant.risk import Price, FXDelta, DollarPrice, IRDelta from gs_quant.target.measures import EqDelta, EqVega from gs_quant.test.utils.mock_calc import MockCalc def mock_pricing_context(self): context = PricingContext(set_parameters_only=True, is_batch=False, show_progress=True) return context def instrument_name(test_name: str, leg_name: str): return f'{test_name}_{leg_name}' @patch.object(GenericEngine, 'new_pricing_context', mock_pricing_context) def test_generic_engine_simple(mocker): with MockCalc(mocker): start_date = dt.date(2021, 12, 1) # end_date = dt.date(2021, 12, 3) # Define trade call = FXOption( buy_sell='Buy', option_type='Call', pair='USDJPY', strike_price='ATMF', notional_amount=1e5, expiration_date='2y', name=instrument_name('test_generic_engine_simple', '2y_call'), ) # Periodic trigger: based on frequency freq = '1m' # trig_req = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency=freq) trig_req = DateTriggerRequirements(dates=[start_date]) actions = AddTradeAction(call, freq, name='Action1') # starting with empty portfolio (first arg to Strategy), apply actions on trig_req triggers = DateTrigger(trig_req, actions) strategy = Strategy(None, triggers) # run backtest daily engine = GenericEngine() backtest = engine.run_backtest( strategy, states=[dt.date(2021, 12, 1), dt.date(2021, 12, 2), dt.date(2021, 12, 3)], show_progress=True ) summary = backtest.result_summary assert len(summary) == 3 assert round(summary[Price].sum()) == 2424 assert round(summary['Cumulative Cash'].iloc[-1]) == 0 @patch.object(GenericEngine, 'new_pricing_context', mock_pricing_context) def test_hedge_action_risk_trigger(mocker): with MockCalc(mocker): start_date = dt.date(2021, 12, 1) # end_date = dt.date(2021, 12, 3) # Define trade call = FXOption( buy_sell='Buy', option_type='Call', pair='USDJPY', strike_price='ATMF', notional_amount=1e5, expiration_date='2y', name=instrument_name('test_hedge_action_risk_trigger', '2y_call'), ) hedge_risk = FXDelta(aggregation_level='Type') fwd_hedge = FXForward( pair='USDJPY', settlement_date='2y', notional_amount=1e5, name=instrument_name('test_hedge_action_risk_trigger', '2y_forward'), ) trig_req = RiskTriggerRequirements(risk=hedge_risk, trigger_level=0, direction=TriggerDirection.ABOVE) action_hedge = HedgeAction(hedge_risk, fwd_hedge, '2b', name='HedgeAction1') triggers = StrategyRiskTrigger(trig_req, action_hedge) with PricingContext(pricing_date=start_date, use_historical_diddles_only=True): fut = call.resolve(in_place=False) call = fut.result() strategy = Strategy(call, triggers) # run backtest daily engine = GenericEngine() # backtest = engine.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True) backtest = engine.run_backtest( strategy, states=[dt.date(2021, 12, 1), dt.date(2021, 12, 2), dt.date(2021, 12, 3)], show_progress=True ) summary = backtest.result_summary assert len(summary) == 3 assert round(summary[hedge_risk].sum()) == 0 assert round(summary['Cumulative Cash'].iloc[-1]) == -7090 assert Price in summary.columns @patch.object(GenericEngine, 'new_pricing_context', mock_pricing_context) def test_hedge_without_risk(mocker): with MockCalc(mocker): # Define trade call = FXOption( buy_sell='Buy', option_type='Call', pair='USDJPY', strike_price='ATMF', notional_amount=1e5, expiration_date='2y', name=instrument_name('test_hedge_without_risk', '2y_call'), ) trig_req = PeriodicTriggerRequirements( start_date=dt.date(2021, 12, 1), end_date=dt.date(2021, 12, 3), frequency='1b' ) action = AddTradeAction(call, '1b', name='AddAction1') hedge_risk = FXDelta(aggregation_level='Type') fwd_hedge = FXForward( pair='USDJPY', settlement_date='2y', notional_amount=1e5, name=instrument_name('test_hedge_without_risk', '2y_forward'), ) hedge_trig_req = PeriodicTriggerRequirements( start_date=dt.date(2021, 11, 1), end_date=dt.date(2022, 1, 1), frequency='1b' ) action_hedge = HedgeAction(hedge_risk, fwd_hedge, '2b', name='HedgeAction1') triggers = [PeriodicTrigger(trig_req, action), PeriodicTrigger(hedge_trig_req, action_hedge)] strategy = Strategy(None, triggers) # run backtest daily engine = GenericEngine() # backtest = engine.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True) backtest = engine.run_backtest( strategy, states=[dt.date(2021, 12, 1), dt.date(2021, 12, 2), dt.date(2021, 12, 3)], show_progress=True ) summary = backtest.result_summary assert len(summary) == 3 assert round(summary[hedge_risk].sum()) == 0 assert round(summary['Cumulative Cash'].iloc[-1]) == -6579 assert Price in summary.columns s = pd.Series( { dt.date(2021, 10, 1): 0.984274, dt.date(2021, 10, 4): 1.000706, dt.date(2021, 10, 5): 1.044055, dt.date(2021, 10, 6): 1.095361, dt.date(2021, 10, 7): 1.129336, dt.date(2021, 10, 8): 1.182954, dt.date(2021, 10, 12): 1.200108, dt.date(2021, 10, 13): 1.220607, dt.date(2021, 10, 14): 1.172837, dt.date(2021, 10, 15): 1.163660, dt.date(2021, 10, 18): 1.061084, dt.date(2021, 10, 19): 1.025012, dt.date(2021, 10, 20): 1.018035, dt.date(2021, 10, 21): 1.080751, dt.date(2021, 10, 22): 1.069340, dt.date(2021, 10, 25): 1.033413, } ) @patch.object(GenericEngine, 'new_pricing_context', mock_pricing_context) def test_mkt_trigger_data_sources(mocker): with MockCalc(mocker): action = AddTradeAction( IRSwaption(notional_currency='USD', expiration_date='1y', termination_date='1y'), 'expiration_date', name='Action1', ) data_source = GenericDataSource(s, MissingDataStrategy.fill_forward) mkt_trigger = MktTrigger(MktTriggerRequirements(data_source, 1.1, TriggerDirection.ABOVE), action) strategy = Strategy(None, mkt_trigger) engine = GenericEngine() backtest = engine.run_backtest(strategy, states=list(s.index), show_progress=True) summary = backtest.result_summary ledger = backtest.trade_ledger() assert len(summary) == 12 assert len(ledger) == 6 assert round(summary[Price].sum()) == 25163614 assert round(summary['Cumulative Cash'].iloc[-1]) == -2153015 @patch.object(GenericEngine, 'new_pricing_context', mock_pricing_context) def test_exit_action_noarg(mocker): with MockCalc(mocker): start_date = dt.date(2021, 12, 6) end_date = dt.date(2021, 12, 10) # Define trade irswap = IRSwap(PayReceive.Receive, '10y', Currency.USD, notional_amount=1e5, name='swap') trig_req_add = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b') trig_req_exit = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='2b') actions_add = AddTradeAction(irswap, name='Action1') actions_exit = ExitTradeAction(name='ExitAction1') triggers = [PeriodicTrigger(trig_req_add, actions_add), PeriodicTrigger(trig_req_exit, actions_exit)] strategy = Strategy(None, triggers) # run backtest daily engine = GenericEngine() # backtest = engine.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True) backtest = engine.run_backtest( strategy, states=[ dt.date(2021, 12, 6), dt.date(2021, 12, 7), dt.date(2021, 12, 8), dt.date(2021, 12, 9), dt.date(2021, 12, 10), ], end=end_date, show_progress=True, ) trade_ledger = backtest.trade_ledger().to_dict('index') assert trade_ledger['Action1_swap_2021-12-06']['Open'] == dt.date(2021, 12, 6) assert trade_ledger['Action1_swap_2021-12-06']['Close'] == dt.date(2021, 12, 6) assert trade_ledger['Action1_swap_2021-12-07']['Open'] == dt.date(2021, 12, 7) assert trade_ledger['Action1_swap_2021-12-07']['Close'] == dt.date(2021, 12, 8) @patch.object(GenericEngine, 'new_pricing_context', mock_pricing_context) def test_exit_action_emptyresults(mocker): with MockCalc(mocker): start_date = dt.date(2021, 12, 6) end_date = dt.date(2021, 12, 10) # Define trade irswap = IRSwap(PayReceive.Receive, '10y', Currency.USD, notional_amount=1e5, name='swap') trig_req_add = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='2b') trig_req_exit = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b') actions_add = AddTradeAction(irswap, name='Action1') actions_exit = ExitTradeAction(name='ExitAction1') triggers = [PeriodicTrigger(trig_req_add, actions_add), PeriodicTrigger(trig_req_exit, actions_exit)] strategy = Strategy(None, triggers) # run backtest daily engine = GenericEngine() # backtest = engine.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True) backtest = engine.run_backtest( strategy, states=[ dt.date(2021, 12, 6), dt.date(2021, 12, 7), dt.date(2021, 12, 8), dt.date(2021, 12, 9), dt.date(2021, 12, 10), ], end=end_date, show_progress=True, ) trade_ledger = backtest.trade_ledger().to_dict('index') assert trade_ledger['Action1_swap_2021-12-06']['Open'] == dt.date(2021, 12, 6) assert trade_ledger['Action1_swap_2021-12-06']['Close'] == dt.date(2021, 12, 6) assert trade_ledger['Action1_swap_2021-12-08']['Open'] == dt.date(2021, 12, 8) assert trade_ledger['Action1_swap_2021-12-08']['Close'] == dt.date(2021, 12, 8) assert trade_ledger['Action1_swap_2021-12-10']['Open'] == dt.date(2021, 12, 10) assert trade_ledger['Action1_swap_2021-12-10']['Close'] == dt.date(2021, 12, 10) @patch.object(GenericEngine, 'new_pricing_context', mock_pricing_context) def test_exit_action_bytradename(mocker): with MockCalc(mocker): start_date = dt.date(2021, 12, 6) end_date = dt.date(2021, 12, 10) # Define trade irswap1 = IRSwap(PayReceive.Receive, '10y', Currency.USD, notional_amount=1e5, name='swap1') irswap2 = IRSwap(PayReceive.Pay, '5y', Currency.USD, notional_amount=1e5, name='swap2') trig_req_add = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b') trig_req_exit = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='2b') actions_add = AddTradeAction([irswap1, irswap2], name='Action1') actions_exit = ExitTradeAction('swap1', name='ExitAction1') triggers = [PeriodicTrigger(trig_req_add, actions_add), PeriodicTrigger(trig_req_exit, actions_exit)] strategy = Strategy(None, triggers) # run backtest daily engine = GenericEngine() # backtest = engine.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True) backtest = engine.run_backtest( strategy, states=[ dt.date(2021, 12, 6), dt.date(2021, 12, 7), dt.date(2021, 12, 8), dt.date(2021, 12, 9), dt.date(2021, 12, 10), ], end=end_date, show_progress=True, ) trade_ledger = backtest.trade_ledger().to_dict('index') assert trade_ledger['Action1_swap1_2021-12-06']['Open'] == dt.date(2021, 12, 6) assert trade_ledger['Action1_swap1_2021-12-06']['Close'] == dt.date(2021, 12, 6) assert trade_ledger['Action1_swap1_2021-12-07']['Open'] == dt.date(2021, 12, 7) assert trade_ledger['Action1_swap1_2021-12-07']['Close'] == dt.date(2021, 12, 8) assert trade_ledger['Action1_swap2_2021-12-06']['Status'] == 'open' assert trade_ledger['Action1_swap2_2021-12-07']['Status'] == 'open' assert trade_ledger['Action1_swap2_2021-12-10']['Status'] == 'open' @patch.object(GenericEngine, 'new_pricing_context', mock_pricing_context) def test_add_scaled_action(mocker): with MockCalc(mocker): start_date = dt.date(2021, 12, 6) end_date = dt.date(2021, 12, 10) scale_factor = 7 # Define instruments for strategy # Portfolio of two eq options call = EqOption( '.STOXX50E', expiration_date='1m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, name='call', ) put = EqOption( '.STOXX50E', expiration_date='1m', strike_price='ATM', option_type=OptionType.Put, option_style=OptionStyle.European, name='put', ) portfolio = Portfolio(name='portfolio', priceables=[call, put]) # Trade the position monthly without any scaling trade_action = AddScaledTradeAction(priceables=portfolio, trade_duration='1m', name='QuantityScaledAction1') trade_trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b'), actions=trade_action, ) strategy = Strategy(None, trade_trigger) GE = GenericEngine() backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True) summary = backtest.result_summary ledger = backtest.trade_ledger() assert len(summary) == 5 assert len(ledger) == 10 assert round(summary[Price].sum()) == 2715 assert round(summary['Cumulative Cash'].iloc[-1]) == -922 # Trade the position monthly and scale the quantity of the trade trade_action_scaled = AddScaledTradeAction( priceables=portfolio, trade_duration='1m', scaling_level=scale_factor, name='QuantityScaledAction2' ) trade_trigger_scaled = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b'), actions=trade_action_scaled, ) strategy_scaled = Strategy(None, trade_trigger_scaled) backtest_scaled = GE.run_backtest( strategy_scaled, start=start_date, end=end_date, frequency='1b', show_progress=True ) summary_scaled = backtest_scaled.result_summary ledger_scaled = backtest_scaled.trade_ledger() # Price and cash should scale linearly assert len(summary_scaled) == len(summary) assert len(ledger_scaled) == len(ledger) assert round(summary_scaled[Price].sum()) == round(summary[Price].sum() * scale_factor) assert round(summary_scaled['Cumulative Cash'].iloc[-1]) == round( summary['Cumulative Cash'].iloc[-1] * scale_factor ) @patch.object(GenericEngine, 'new_pricing_context', mock_pricing_context) def test_scaled_transaction_cost(mocker): with MockCalc(mocker): start_date = dt.date(2024, 5, 6) end_date = dt.date(2024, 5, 10) # notional based transaction cost. charge 1/10000th of the notional transaction_cost = ScaledTransactionModel('notional_amount', 0.0001) swap = IRSwap(notional_currency=Currency.GBP, notional_amount='50k', termination_date='1y', name='GBP1y') trade_action = AddTradeAction( priceables=swap, trade_duration='1m', name='Action1', transaction_cost=transaction_cost ) trade_trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b'), actions=trade_action, ) strategy = Strategy(None, trade_trigger) GE = GenericEngine() backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True) summary = backtest.result_summary ledger = backtest.trade_ledger() assert len(summary) == 5 assert len(ledger) == 5 assert round(summary[Price].sum()) == 90 assert round(summary['Transaction Costs'].iloc[-1]) == 50000 * 0.0001 * 5 * -1 @patch.object(GenericEngine, 'new_pricing_context', mock_pricing_context) def test_agg_transaction_cost(mocker): with MockCalc(mocker): start_date = dt.date(2024, 5, 6) end_date = dt.date(2024, 5, 10) # aggregation based transaction cost. charge 1/10000th of the notional + 1 for evey transaction models = tuple([ScaledTransactionModel('notional_amount', 0.0001), ConstantTransactionModel(1)]) transaction_cost = AggregateTransactionModel(models, TransactionAggType.SUM) swap = IRSwap(notional_currency=Currency.GBP, notional_amount='50k', termination_date='1y', name='GBP1y') trade_action = AddTradeAction( priceables=swap, trade_duration='1m', name='Action1', transaction_cost=transaction_cost ) trade_trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b'), actions=trade_action, ) strategy = Strategy(None, trade_trigger) GE = GenericEngine() backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True) summary = backtest.result_summary ledger = backtest.trade_ledger() assert len(summary) == 5 assert len(ledger) == 5 assert round(summary[Price].sum()) == 90 assert round(summary['Transaction Costs'].iloc[-1]) == (50000 * 0.0001 * 5 * -1) - 5 @patch.object(GenericEngine, 'new_pricing_context', mock_pricing_context) def test_risk_scaled_transaction_cost(mocker): with MockCalc(mocker): start_date = dt.date(2023, 6, 5) end_date = dt.date(2023, 6, 9) # risk based transaction cost. Charge a 2 times the delta. transaction_cost = ScaledTransactionModel(IRDelta(aggregation_level='Type'), 2) swap = IRSwap(notional_currency=Currency.GBP, notional_amount='50k', termination_date='1y', name='GBP1y') trade_action = AddTradeAction( priceables=swap, trade_duration='1m', name='Action1', transaction_cost=transaction_cost ) trade_trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b'), actions=trade_action, ) strategy = Strategy(None, trade_trigger) GE = GenericEngine() backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True) summary = backtest.result_summary ledger = backtest.trade_ledger() assert len(summary) == 5 assert len(ledger) == 5 assert round(summary[Price].sum()) == -64 assert round(summary['Transaction Costs'].iloc[-1]) == -62 @patch.object(GenericEngine, 'new_pricing_context', mock_pricing_context) def test_hedge_transaction_costs(mocker): with MockCalc(mocker): start_date = dt.date(2023, 6, 5) end_date = dt.date(2023, 6, 9) scaled_transaction_cost = ScaledTransactionModel('number_of_options', 1) fixed_transaction_cost = ConstantTransactionModel(0) opt = EqOption( underlier='.SPX', option_type=OptionType.Call, number_of_options=1, expiration_date='3m', name=instrument_name('test_hedge_transaction_costs', 'SPX_opt'), ) trade_action = AddTradeAction( priceables=opt, trade_duration='1m', name='Action1', transaction_cost=fixed_transaction_cost ) trade_trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'), actions=trade_action, ) hedge_port = Portfolio( [ EqOption( underlier='.SPX', option_type=OptionType.Call, number_of_options=1, expiration_date='3m', name=instrument_name('test_hedge_transaction_costs', 'syn_fwd_call'), ), EqOption( underlier='.SPX', option_type=OptionType.Put, number_of_options=1, expiration_date='3m', name=instrument_name('test_hedge_transaction_costs', 'syn_fwd_put'), ), ] ) hedge_action = HedgeAction( risk=EqDelta, priceables=hedge_port, trade_duration='1b', transaction_cost=scaled_transaction_cost, name='HedgeAction1', ) hedge_trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b'), actions=hedge_action, ) strategy = Strategy(None, [trade_trigger, hedge_trigger]) GE = GenericEngine() backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True) summary = backtest.result_summary assert len(summary) == 5 total_expected_transaction_costs = 0 cur_bought_hedges = 0 for d, port in backtest.portfolio_dict.items(): # sale of prior hedges total_expected_transaction_costs += cur_bought_hedges cur_bought_hedges = sum(i.number_of_options for i in port.priceables if i.name.startswith('Scaled')) # buy next hedge total_expected_transaction_costs += cur_bought_hedges np.testing.assert_almost_equal(-summary['Transaction Costs'][d], total_expected_transaction_costs) @patch.object(GenericEngine, 'new_pricing_context', mock_pricing_context) def test_exit_transaction_costs(mocker): with MockCalc(mocker): start_date = dt.date(2024, 6, 3) end_date = dt.date(2024, 6, 7) scaled_cost = 2 scaled_transaction_cost = ScaledTransactionModel('number_of_options', scaled_cost) fixed_cost = 5 fixed_transaction_cost = ConstantTransactionModel(fixed_cost) opt = EqOption( underlier='.SPX', option_type=OptionType.Call, number_of_options=1, expiration_date='3m', name='SPX_opt' ) trade_action = AddTradeAction( priceables=opt, trade_duration='1b', transaction_cost=scaled_transaction_cost, transaction_cost_exit=fixed_transaction_cost, name='Action1', ) trade_trigger = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b'), actions=trade_action, ) strategy = Strategy(None, trade_trigger) GE = GenericEngine() backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True) summary = backtest.result_summary assert len(summary) == 5 total_expected_transaction_costs = 0 for d, port in backtest.portfolio_dict.items(): # sale of prior trades total_expected_transaction_costs += 0 if d == start_date else fixed_cost cur_bought = sum(i.number_of_options for i in port.priceables) # buy next trade total_expected_transaction_costs += scaled_cost * cur_bought np.testing.assert_almost_equal(-summary['Transaction Costs'][d], total_expected_transaction_costs) @patch.object(GenericEngine, 'new_pricing_context', mock_pricing_context) def test_add_scaled_action_nav(mocker): with MockCalc(mocker): start_date = dt.date(2021, 12, 6) end_date = dt.date(2021, 12, 10) initial_cash = 100 # Define instruments for strategy call = EqOption( '.STOXX50E', expiration_date='1m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, name='call', ) # NAV trading strategy with specified initial cash trade_action_scaled = AddScaledTradeAction( priceables=call, trade_duration='1b', scaling_level=initial_cash, scaling_type=ScalingActionType.NAV, name='QuantityScaledAction1', ) trade_trigger_scaled = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b'), actions=trade_action_scaled, ) strategy = Strategy(None, trade_trigger_scaled) GE = GenericEngine() backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True) summary = backtest.result_summary ledger = backtest.trade_ledger().to_dict('index') # Start with initial cash and only use sale proceeds to buy new options np.testing.assert_almost_equal(ledger['QuantityScaledAction1_call_2021-12-06']['Open Value'], -initial_cash) np.testing.assert_almost_equal( ledger['QuantityScaledAction1_call_2021-12-07']['Open Value'], -ledger['QuantityScaledAction1_call_2021-12-06']['Close Value'], ) np.testing.assert_almost_equal( ledger['QuantityScaledAction1_call_2021-12-08']['Open Value'], -ledger['QuantityScaledAction1_call_2021-12-07']['Close Value'], ) np.testing.assert_almost_equal( ledger['QuantityScaledAction1_call_2021-12-09']['Open Value'], -ledger['QuantityScaledAction1_call_2021-12-08']['Close Value'], ) np.testing.assert_almost_equal( ledger['QuantityScaledAction1_call_2021-12-10']['Open Value'], -ledger['QuantityScaledAction1_call_2021-12-09']['Close Value'], ) # Total cash spent is the initial cash throughout the entire strategy np.testing.assert_almost_equal(summary['Cumulative Cash'].iloc[0], -initial_cash) for c in summary['Cumulative Cash']: np.testing.assert_almost_equal(c, summary['Cumulative Cash'].iloc[0]) def nav_scaled_action_transaction_cost_test_for_agg_type(mocker, agg_type): with MockCalc(mocker): start_date = dt.date(2021, 12, 6) end_date = dt.date(2021, 12, 10) initial_cash = 100 # Define instruments for strategy call = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, name='call', ) scaled_transaction_cost = ScaledTransactionModel(EqVega, 1.2) fixed_transaction_cost = ConstantTransactionModel(1) agg_tc = AggregateTransactionModel((scaled_transaction_cost, fixed_transaction_cost), agg_type) # NAV trading strategy with specified initial cash trade_action_scaled = AddScaledTradeAction( priceables=call, trade_duration='1b', scaling_level=initial_cash, scaling_type=ScalingActionType.NAV, transaction_cost=agg_tc, name=f'QuantityScaledAction2_{agg_type}', ) trade_trigger_scaled = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b'), actions=trade_action_scaled, ) strategy = Strategy(None, trade_trigger_scaled) GE = GenericEngine() backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True) summary = backtest.result_summary ledger = backtest.trade_ledger().to_dict('index') tc = backtest.transaction_costs # Prior close value minus today's transaction costs equal today's value of options bought np.testing.assert_almost_equal( ledger[f'QuantityScaledAction2_{agg_type}_call_2021-12-06']['Open Value'], -initial_cash - tc[dt.date(2021, 12, 6)], ) np.testing.assert_almost_equal( ledger[f'QuantityScaledAction2_{agg_type}_call_2021-12-07']['Open Value'], -ledger[f'QuantityScaledAction2_{agg_type}_call_2021-12-06']['Close Value'] - tc[dt.date(2021, 12, 7)], ) np.testing.assert_almost_equal( ledger[f'QuantityScaledAction2_{agg_type}_call_2021-12-08']['Open Value'], -ledger[f'QuantityScaledAction2_{agg_type}_call_2021-12-07']['Close Value'] - tc[dt.date(2021, 12, 8)], ) np.testing.assert_almost_equal( ledger[f'QuantityScaledAction2_{agg_type}_call_2021-12-09']['Open Value'], -ledger[f'QuantityScaledAction2_{agg_type}_call_2021-12-08']['Close Value'] - tc[dt.date(2021, 12, 9)], ) np.testing.assert_almost_equal( ledger[f'QuantityScaledAction2_{agg_type}_call_2021-12-10']['Open Value'], -ledger[f'QuantityScaledAction2_{agg_type}_call_2021-12-09']['Close Value'] - tc[dt.date(2021, 12, 10)], ) # Cash spent on trades + Transaction costs are equal to the initial cash throughout the entire strategy total_cash_spent = summary['Cumulative Cash'] + summary['Transaction Costs'] np.testing.assert_almost_equal(total_cash_spent.iloc[0], -initial_cash) for c in total_cash_spent: np.testing.assert_almost_equal(c, total_cash_spent.iloc[0]) @patch.object(GenericEngine, 'new_pricing_context', mock_pricing_context) def test_add_scaled_action_nav_with_transaction_costs(mocker): for agg_type in (TransactionAggType.SUM, TransactionAggType.MAX, TransactionAggType.MIN): nav_scaled_action_transaction_cost_test_for_agg_type(mocker, agg_type) @patch.object(GenericEngine, 'new_pricing_context', mock_pricing_context) def test_generic_engine_custom_price_measure(mocker): with MockCalc(mocker): trig_date = dt.date(2021, 12, 1) call = EqOption( '.STOXX50E', expiration_date='1y', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, name='call', ) freq = '1m' trig_req = DateTriggerRequirements(dates=[trig_date]) actions = AddTradeAction(call, freq, name='Action1') triggers = DateTrigger(trig_req, actions) strategy = Strategy(None, triggers) engine = GenericEngine(price_measure=DollarPrice) backtest = engine.run_backtest( strategy, states=[dt.date(2021, 12, 1), dt.date(2021, 12, 2), dt.date(2021, 12, 3)], show_progress=True ) summary = backtest.result_summary assert len(summary) == 3 assert round(summary[DollarPrice].sum()) == 804 assert round(summary['Cumulative Cash'].iloc[-1]) == -291 def test_serialisation(mocker): call = FXOption( buy_sell='Buy', option_type='Call', pair='USDJPY', strike_price='ATMF', notional_amount=1e5, expiration_date='2y', name='2y_call', ) hedge = FXForward(pair='USDJPY', settlement_date='2y', name='2y_hedge') d1 = dt.date(2023, 4, 11) d2 = dt.date(2023, 12, 25) d3 = dt.date(2024, 2, 29) dt1 = dt.datetime(2024, 4, 11, 9, 17, 34) dt2 = dt.datetime(2024, 4, 12, 10, 7, 57) sample_data_source = GsDataSource('DATASET_ABC', 'ASSET123', d1, d2) generic_data_source = GenericDataSource(s, MissingDataStrategy.fill_forward) # Actions to check add_trade_action_1 = AddTradeAction(call, '1m', name='Action1') add_trade_action_2 = AddTradeAction(call, d1, name='Action2') add_trade_action_3 = AddTradeAction(call, d2, name='Action3') hedge_action = HedgeAction(FXDelta(aggregation_level='type'), hedge, name='HedgeAction') exit_trade_action = ExitTradeAction('2y_call', name='ExitAction1') exit_all_trades_action = ExitAllPositionsAction(name='ExitEverything') add_scaled_trade = AddScaledTradeAction( priceables=call, trade_duration='1b', scaling_level=100, scaling_type=ScalingActionType.NAV, name='QuantityScaledAction1', ) # Triggers to check (randomly assign actions to triggers to cover all above actions) date_trigger = DateTrigger(DateTriggerRequirements(dates=(dt.date(2021, 12, 1),)), add_trade_action_1) periodic_trigger = PeriodicTrigger(PeriodicTriggerRequirements(d1, d2, "3m", (d3,)), add_trade_action_2) triggers = ( date_trigger, periodic_trigger, DateTrigger(DateTriggerRequirements(dates=(dt.date(2021, 12, 1),)), [add_trade_action_3, hedge_action]), PeriodicTrigger(PeriodicTriggerRequirements(d1, d2, "3m", (d3,))), IntradayPeriodicTrigger(IntradayTriggerRequirements(dt1.time(), dt2.time(), 5.0)), MktTrigger(MktTriggerRequirements(generic_data_source, 1.1, TriggerDirection.BELOW), exit_trade_action), StrategyRiskTrigger(RiskTriggerRequirements(DollarPrice, -1.4, TriggerDirection.BELOW), exit_all_trades_action), AggregateTrigger(AggregateTriggerRequirements((date_trigger, periodic_trigger)), add_scaled_trade), NotTrigger(NotTriggerRequirements(date_trigger)), PortfolioTrigger(PortfolioTriggerRequirements('len', 2)), MeanReversionTrigger(MeanReversionTriggerRequirements(sample_data_source, 1.5, 0, 5)), ) strategy = Strategy(None, triggers) # Test to_dict() strategy_dict = strategy.to_dict() reconstituted_strategy = Strategy.from_dict(strategy_dict) assert reconstituted_strategy == strategy # Test to_json() strategy_json = json.dumps(strategy.to_dict(), cls=JSONEncoder) reconstituted_strategy = Strategy.from_json(strategy_json) assert reconstituted_strategy == strategy @patch.object(GenericEngine, 'new_pricing_context', mock_pricing_context) def test_initial_portfolio(mocker): with MockCalc(mocker): irswap = IRSwap(PayReceive.Pay, '10y', Currency.USD, notional_amount=10000, name='10y') actions = AddTradeAction(irswap, '1b') new_ir_swap = IRSwap(PayReceive.Pay, '2y', Currency.USD, notional_amount=10000, name='2y') newer_ir_swap = IRSwap(PayReceive.Pay, '5y', Currency.USD, notional_amount=10000, name='5y') initial_port = { dt.date(2024, 5, 3): irswap, dt.date(2024, 5, 24): [irswap, new_ir_swap], dt.date(2024, 6, 14): [new_ir_swap, newer_ir_swap], } trig_req = PeriodicTriggerRequirements( start_date=dt.date(2024, 5, 3), end_date=dt.date(2024, 7, 5), frequency='1b' ) triggers = PeriodicTrigger(trig_req, actions) strategy = Strategy(initial_port, triggers) engine = GenericEngine() pricing_dates = [ dt.date(2024, 5, 3), dt.date(2024, 5, 10), dt.date(2024, 5, 17), dt.date(2024, 5, 24), dt.date(2024, 5, 31), dt.date(2024, 6, 7), dt.date(2024, 6, 14), dt.date(2024, 6, 21), dt.date(2024, 6, 28), dt.date(2024, 7, 5), ] backtest = BackTest(strategy, pricing_dates, [Price], Price) engine._resolve_initial_portfolio(initial_port, backtest, dt.date(2024, 5, 3), pricing_dates, None) port_dict = backtest.portfolio_dict assert len(port_dict[dt.date(2024, 5, 3)]) == 1 assert len(port_dict[dt.date(2024, 5, 24)]) == 2 assert len(port_dict[dt.date(2024, 6, 14)]) == 2 assert len(port_dict[dt.date(2024, 7, 5)]) == 2 assert port_dict[dt.date(2024, 5, 24)][0].name == '10y_2024-05-24' assert port_dict[dt.date(2024, 5, 24)][1].name == '2y_2024-05-24' assert port_dict[dt.date(2024, 6, 14)][0].name == '2y_2024-06-14' assert port_dict[dt.date(2024, 6, 14)][1].name == '5y_2024-06-14' @patch.object(GenericEngine, 'new_pricing_context', mock_pricing_context) def test_add_scaled_trade_action_with_quantity_signal(mocker): with MockCalc(mocker): start_date = dt.date(2021, 12, 6) end_date = dt.date(2021, 12, 10) # Define instruments for strategy call = EqOption( '.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, name='call', ) trade_action_scaled = AddScaledTradeAction( priceables=call, trade_duration='1b', scaling_level={start_date: 13, end_date: 21}, scaling_type=ScalingActionType.size, name='AddScaledTradeAction1', ) trade_trigger_scaled = PeriodicTrigger( trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1b'), actions=trade_action_scaled, ) strategy = Strategy(None, trade_trigger_scaled) GE = GenericEngine() backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True) portfolio_by_date = {d: p for d, p in backtest.portfolio_dict.items() if isinstance(d, dt.date)} assert all(len(p) == 1 for p in portfolio_by_date.values()) quantity_by_date = {d: p.all_instruments[0].number_of_options for d, p in portfolio_by_date.items()} expected_quantity_by_date = { dt.date(2021, 12, 6): 13, dt.date(2021, 12, 7): 13, dt.date(2021, 12, 8): 13, dt.date(2021, 12, 9): 13, dt.date(2021, 12, 10): 21, } assert quantity_by_date == expected_quantity_by_date @patch.object(GenericEngine, 'new_pricing_context', mock_pricing_context) def test_add_weighted_trade_action(mocker): """ Test AddWeightedTradeAction which scales instruments in a portfolio so that each has equal risk contribution based on a specified risk measure. """ with MockCalc(mocker): start_date = dt.date(2021, 12, 6) end_date = dt.date(2021, 12, 10) # Create a portfolio of equity options with different characteristics # Each option will be scaled to have equal risk (EqDelta) contribution # Use simple names that match patterns in existing mock data call = EqOption( '.STOXX50E', expiration_date='1m', strike_price='ATM', option_type=OptionType.Call, option_style=OptionStyle.European, name='call', ) put = EqOption( '.STOXX50E', expiration_date='1m', strike_price='ATM', option_type=OptionType.Put, option_style=OptionStyle.European, name='put', ) # Create portfolio of instruments to be weighted weighted_portfolio = Portfolio([call, put]) # Define the weighted trade action # This will scale each instrument so they all have equal EqDelta risk weighted_action = AddWeightedTradeAction( priceables=weighted_portfolio, trade_duration='1m', # Hold trades for 1 month name='WeightedAction1', scaling_risk=EqDelta, # Risk measure to equalize across instruments total_size=20000.0, # Total risk budget to distribute equally ) # Verify action is created correctly assert weighted_action.scaling_risk == EqDelta assert weighted_action.total_size == 20000.0 assert len(weighted_action.priceables) == 2 # Verify priceables are named correctly with action prefix priceable_names = [p.name for p in weighted_action.priceables] assert all('WeightedAction1' in name for name in priceable_names) # Verify calc type is semi_path_dependent from gs_quant.backtests.backtest_utils import CalcType assert weighted_action.calc_type == CalcType.semi_path_dependent # Define periodic trigger - add weighted trades daily trigger_req = PeriodicTriggerRequirements( start_date=start_date, end_date=end_date, frequency='1b', ) trigger = PeriodicTrigger(trigger_req, weighted_action) # Create strategy with no initial portfolio strategy = Strategy(None, trigger) # Verify the strategy is created correctly assert len(strategy.triggers) == 1 assert len(strategy.triggers[0].actions) == 1 assert isinstance(strategy.triggers[0].actions[0], AddWeightedTradeAction) ================================================ FILE: gs_quant/test/backtest/test_triggers.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from gs_quant.backtests.actions import AddTradeAction from gs_quant.backtests.triggers import ( DateTriggerRequirements, DateTrigger, AggregateTriggerRequirements, AggregateTrigger, AggType, NotTrigger, NotTriggerRequirements, ) from gs_quant.instrument import IRSwap, IRSwaption def test_date_trigger(): # Test DateTrigger action = AddTradeAction(IRSwap()) trigger = DateTrigger( DateTriggerRequirements([dt.date(2021, 11, 9), dt.date(2021, 11, 10), dt.date(2021, 11, 11)]), [action] ) assert not trigger.has_triggered(dt.datetime(2021, 11, 10, 14, 0)) assert trigger.has_triggered(dt.date(2021, 11, 10)) trigger = DateTrigger( DateTriggerRequirements( [dt.datetime(2021, 11, 9, 14, 0), dt.datetime(2021, 11, 10, 14, 0), dt.datetime(2021, 11, 11, 14, 0)] ), [action], ) assert trigger.has_triggered(dt.datetime(2021, 11, 10, 14, 0)) assert not trigger.has_triggered(dt.date(2021, 11, 10)) trigger = DateTrigger( DateTriggerRequirements( [dt.datetime(2021, 11, 9, 14, 0), dt.datetime(2021, 11, 10, 14, 0), dt.datetime(2021, 11, 11, 14, 0)], entire_day=True, ), [action], ) assert trigger.has_triggered(dt.datetime(2021, 11, 10, 14, 0)) assert trigger.has_triggered(dt.date(2021, 11, 10)) def test_aggregate_triggger(): # Test Aggregate Trigger action_1 = AddTradeAction(IRSwap()) action_2 = AddTradeAction(IRSwaption()) trigger_1 = DateTrigger( DateTriggerRequirements([dt.date(2021, 11, 9), dt.date(2021, 11, 10), dt.date(2021, 11, 11)]), [action_1] ) trigger_2 = DateTrigger( DateTriggerRequirements([dt.date(2021, 11, 8), dt.date(2021, 11, 10), dt.date(2021, 11, 12)]), [action_2] ) agg_trigger = AggregateTrigger(AggregateTriggerRequirements([trigger_1, trigger_2], aggregate_type=AggType.ALL_OF)) assert not agg_trigger.has_triggered(dt.date(2021, 11, 9)) assert agg_trigger.has_triggered(dt.date(2021, 11, 10)) assert len(agg_trigger.actions) == 0 agg_trigger = AggregateTrigger( AggregateTriggerRequirements([trigger_1, trigger_2], aggregate_type=AggType.ANY_OF), [action_1, action_2] ) assert agg_trigger.has_triggered(dt.date(2021, 11, 8)) assert isinstance(agg_trigger.actions[1].priceables[0], IRSwaption) assert agg_trigger.has_triggered(dt.date(2021, 11, 10)) assert len(agg_trigger.actions) == 2 def test_not_triggger(): # Test Not Trigger action = AddTradeAction(IRSwap()) not_action = AddTradeAction(IRSwaption()) trigger = DateTrigger( DateTriggerRequirements([dt.date(2021, 11, 9), dt.date(2021, 11, 10), dt.date(2021, 11, 11)]), [action] ) not_trigger = NotTrigger(NotTriggerRequirements(trigger), [not_action]) assert not_trigger.has_triggered(dt.date(2021, 11, 8)) assert isinstance(not_trigger.actions[0].priceables[0], IRSwaption) assert not not_trigger.has_triggered(dt.date(2021, 11, 10)) ================================================ FILE: gs_quant/test/calc_cache/request00e305681f1f021b52ef57f22d4975f1.json ================================================ { "request_id": "4_Action1_SPX_opt_2024-06-06Price2024Jun07_Action1_SPX_opt_2024-06-05Price2024Jun06_Action1_SPX_opt_2024-06-04Price2024Jun05_Action1_SPX_opt_2024-06-03Price2024Jun04", "request_hash": "00e305681f1f021b52ef57f22d4975f1", "type": "MockCalc", "tests": [ "test_exit_transaction_costs" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":16448.823232831626}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":17394.03931208903}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":20727.544149823272}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":15860.139946933508}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request02db37fadb8c59eb98457c277883b4af.json ================================================ { "request_id": "4_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-09Price2021Dec10_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-08Price2021Dec09_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-07Price2021Dec08_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-06Price2021Dec07", "request_hash": "02db37fadb8c59eb98457c277883b4af", "type": "MockCalc", "tests": [ "test_add_scaled_action_nav_with_transaction_costs" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":57.29826410449691}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":71.6067550722692}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":96.58210204775983}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":132.57078084954878}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request03cc64abac61cde741b4b5bf90fc3b0f.json ================================================ { "request_id": "5_QuantityScaledAction1_callResolvedInstrumentValues2021Dec10_QuantityScaledAction1_callResolvedInstrumentValues2021Dec09_QuantityScaledAction1_callResolvedInstrumentValues2021Dec08_QuantityScaledAction1_callResolvedInstrumentValues2021Dec07_QuantityScaledAction1_callResolvedInstrumentValues2021Dec06", "request_hash": "03cc64abac61cde741b4b5bf90fc3b0f", "type": "MockCalc", "tests": [ "test_add_scaled_action_nav" ], "mocked_data": "[[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-01-10\",\"expirySettleDate\":\"2022-01-12\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-14\",\"premiumSettlementDate\":\"2021-12-14\",\"settlementDate\":\"2021-12-14\",\"strikePrice\":4199.16,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-01-10\",\"expirySettleDate\":\"2022-01-12\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-13\",\"premiumSettlementDate\":\"2021-12-13\",\"settlementDate\":\"2021-12-13\",\"strikePrice\":4208.3,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-01-10\",\"expirySettleDate\":\"2022-01-12\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-10\",\"premiumSettlementDate\":\"2021-12-10\",\"settlementDate\":\"2021-12-10\",\"strikePrice\":4233.09,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-01-07\",\"expirySettleDate\":\"2022-01-11\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-09\",\"premiumSettlementDate\":\"2021-12-09\",\"settlementDate\":\"2021-12-09\",\"strikePrice\":4276.2,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"SQ\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-01-06\",\"expirySettleDate\":\"2022-01-10\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-08\",\"premiumSettlementDate\":\"2021-12-08\",\"settlementDate\":\"2021-12-08\",\"strikePrice\":4137.11,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request04e5386077a564977740976627ab683d.json ================================================ { "request_id": "1_Action1_callResolvedInstrumentValues2021Dec01", "request_hash": "04e5386077a564977740976627ab683d", "type": "MockCalc", "tests": [ "test_generic_engine_custom_price_measure" ], "mocked_data": "[[[[{\"$type\": \"LegDefinition\", \"assetClass\": \"Equity\", \"buySell\": \"Buy\", \"expirationDate\": \"2022-12-01\", \"methodOfSettlement\": \"Cash\", \"multiplier\": 1.0, \"numberOfOptions\": 1.0, \"optionStyle\": \"European\", \"optionType\": \"Call\", \"premium\": 0.0, \"premiumCurrency\": \"EUR\", \"premiumPaymentDate\": \"2021-12-03\", \"settlementDate\": \"2021-12-03\", \"strikePrice\": 4179.15, \"tradeAs\": \"OTC\", \"type\": \"Option\", \"underlier\": \".STOXX50E\", \"underlierType\": \"RIC\", \"valuationTime\": \"MktClose\"}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request093e1ad35f25cb43fcad999f9adf8c50.json ================================================ { "request_id": "1_5yPrice(value:MYR)today", "request_hash": "093e1ad35f25cb43fcad999f9adf8c50", "type": "MockCalc", "tests": [ "test_currency_params" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"Malaysian Ringgit\": 1.0}, \"val\": 40400773.28824528, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 1072}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request0b8e0fc36f0a438251dafabc0b82b1a7.json ================================================ { "request_id": "1_5y-10yIRDeltatoday", "request_hash": "0b8e0fc36f0a438251dafabc0b82b1a7", "type": "MockCalc", "tests": [ "test_transformation" ], "mocked_data": "[[[[{\"$type\": \"RiskVector\", \"asset\": [-222.89487023773194, -4.956045434570313, -257.4838070983887, -177.14621021118165, -91.26560692596436, -257.14357314300537, -167.4526831756592, -79.40924196472169, -190.63751578826904, -100.05053580322266, -13.794751002502442, -257.2969256896973, -167.37277214050295, -82.13771909637451, 4.5745849609375e-06, -9.979248046875e-07, -2.288818359375e-08, -5.79833984375e-08, 1.220703125e-07, 6.8511962890625e-07, 2.899169921875e-08, 2.44140625e-08, 6.103515625e-09, -1352.4900552017214, -1.220703125e-08, 43703.8483506012, -7.161747689819336, 2.076456838989258, -0.34110171508789067, 0.002102958679199219], \"points\": [{\"asset\": \"USD\", \"class_\": \"CASH OIS\", \"path\": \"\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -222.89487023773194}, {\"asset\": \"USD\", \"class_\": \"CASH OIS\", \"path\": \"\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -4.956045434570313}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"DEC23\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -257.4838070983887}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"DEC24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -177.14621021118165}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"DEC25\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -91.26560692596436}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"JUN23\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -257.14357314300537}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"JUN24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -167.4526831756592}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"JUN25\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -79.40924196472169}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"MAR24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -190.63751578826904}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"MAR25\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -100.05053580322266}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"MAR26\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -13.794751002502442}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"SEP23\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -257.2969256896973}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"SEP24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -167.37277214050295}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"SEP25\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -82.13771909637451}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 4.5745849609375e-06}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -9.979248046875e-07}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -2.288818359375e-08}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -5.79833984375e-08}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 1.220703125e-07}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 6.8511962890625e-07}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 2.899169921875e-08}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 2.44140625e-08}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 6.103515625e-09}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -1352.4900552017214}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -1.220703125e-08}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 43703.8483506012}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -7.161747689819336}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 2.076456838989258}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -0.34110171508789067}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.002102958679199219}], \"unit\": {\"Basis Point\": -1.0, \"United States Dollar\": 1.0}}], [{\"$type\": \"RiskVector\", \"asset\": [-184.73840417480469, -8.77245535583496, -207.95269998779298, -143.03823208312988, -73.69721851501465, -207.67791540222169, -135.14822338867188, -63.985803250122075, -154.75724452819824, -81.03719011230469, -11.249553323364259, -207.80164810791015, -135.06701018981934, -66.26003693847656, 81531.38265041505, -3.6325968200683594, 0.5570179443359375, -0.04483084716796875, -0.001169952392578125, 0.0002566375732421875, -1.82647705078125e-05, -1.348876953125e-06, 1.861572265625e-07, -1093.6778789459229, -1.220703125e-08, -1410.6222074768068, -1718.8486226135255, -2064.6295195007324, -2415.8543660308837, -2725.7318683441163], \"points\": [{\"asset\": \"USD\", \"class_\": \"CASH OIS\", \"path\": \"\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -184.73840417480469}, {\"asset\": \"USD\", \"class_\": \"CASH OIS\", \"path\": \"\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -8.77245535583496}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"DEC23\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -207.95269998779298}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"DEC24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -143.03823208312988}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"DEC25\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -73.69721851501465}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"JUN23\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -207.67791540222169}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"JUN24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -135.14822338867188}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"JUN25\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -63.985803250122075}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"MAR24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -154.75724452819824}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"MAR25\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -81.03719011230469}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"MAR26\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -11.249553323364259}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"SEP23\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -207.80164810791015}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"SEP24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -135.06701018981934}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"SEP25\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -66.26003693847656}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 81531.38265041505}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -3.6325968200683594}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.5570179443359375}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -0.04483084716796875}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -0.001169952392578125}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0002566375732421875}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -1.82647705078125e-05}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -1.348876953125e-06}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 1.861572265625e-07}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -1093.6778789459229}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -1.220703125e-08}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -1410.6222074768068}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -1718.8486226135255}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -2064.6295195007324}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -2415.8543660308837}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -2725.7318683441163}], \"unit\": {\"Basis Point\": -1.0, \"United States Dollar\": 1.0}}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request0c583177f98e8c0c7dba07ae2674c05a.json ================================================ { "request_id": "5_QuantityScaledAction2_TransactionAggType.SUM_callResolvedInstrumentValues2021Dec10_QuantityScaledAction2_TransactionAggType.SUM_callResolvedInstrumentValues2021Dec09_QuantityScaledAction2_TransactionAggType.SUM_callResolvedInstrumentValues2021Dec08_QuantityScaledAction2_TransactionAggType.SUM_callResolvedInstrumentValues2021Dec07_QuantityScaledAction2_TransactionAggType.SUM_callResolvedInstrumentValues2021Dec06", "request_hash": "0c583177f98e8c0c7dba07ae2674c05a", "type": "MockCalc", "tests": [ "test_add_scaled_action_nav_with_transaction_costs" ], "mocked_data": "[[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-03-10\",\"expirySettleDate\":\"2022-03-14\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-14\",\"premiumSettlementDate\":\"2021-12-14\",\"settlementDate\":\"2021-12-14\",\"strikePrice\":4199.16,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-03-09\",\"expirySettleDate\":\"2022-03-11\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-13\",\"premiumSettlementDate\":\"2021-12-13\",\"settlementDate\":\"2021-12-13\",\"strikePrice\":4208.3,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-03-08\",\"expirySettleDate\":\"2022-03-10\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-10\",\"premiumSettlementDate\":\"2021-12-10\",\"settlementDate\":\"2021-12-10\",\"strikePrice\":4233.09,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-03-07\",\"expirySettleDate\":\"2022-03-09\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-09\",\"premiumSettlementDate\":\"2021-12-09\",\"settlementDate\":\"2021-12-09\",\"strikePrice\":4276.2,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-03-07\",\"expirySettleDate\":\"2022-03-09\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-08\",\"premiumSettlementDate\":\"2021-12-08\",\"settlementDate\":\"2021-12-08\",\"strikePrice\":4137.11,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request0f156bd153459340c4bdb832aa95a1d0.json ================================================ { "request_id": "3_Action1_swap_2021-12-10Price2021Dec10_Action1_swap_2021-12-08Price2021Dec08_Action1_swap_2021-12-06Price2021Dec06", "request_hash": "0f156bd153459340c4bdb832aa95a1d0", "type": "MockCalc", "tests": [ "test_exit_action_emptyresults" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 0.0, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": -3.637978807091713e-12, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 0.0, \"children\": {}}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request102fef2814650c2f6ab945bb5f3cee73.json ================================================ { "request_id": "1_Scaled_test_hedge_action_risk_trigger_2y_forward_2021-12-01_HedgeAction1_Priceable0FXDelta(aggregation_level:Type)-Price2021Dec02", "request_hash": "102fef2814650c2f6ab945bb5f3cee73", "type": "MockCalc", "tests": [ "test_hedge_action_risk_trigger" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"children\":{},\"val\":-462.7499536218238}]],[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":-3599.444492554292}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request12e470a54f4aa7c362e1014136a5d35d.json ================================================ { "request_id": "10_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-10EqVega-Price2021Dec13_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-10EqVega-Price2021Dec10_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-09EqVega-Price2021Dec10_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-09EqVega-Price2021Dec09_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-08EqVega-Price2021Dec09_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-08EqVega-Price2021Dec08_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-07EqVega-Price2021Dec08_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-07EqVega-Price2021Dec07_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-06EqVega-Price2021Dec07_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-06EqVega-Price2021Dec06", "request_hash": "12e470a54f4aa7c362e1014136a5d35d", "type": "MockCalc", "tests": [ "test_add_scaled_action_nav_with_transaction_costs" ], "mocked_data": "[[[[{\"$type\":\"RiskVector\",\"asset\":[9.150956641405065],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":136.33227024891383}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[9.375839066009503],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":146.128150719897}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[9.308270163128327],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":139.6638341786846}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[9.364713983673719],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":148.6879474878436}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[9.280815108233655],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":133.0040505502546}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[9.44452859793062],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":152.16613197021397}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[9.322611019976902],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":126.26577286726146}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[9.450558948643739],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":154.42940273089567}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[8.9650452521228],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":247.1158307476799}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[9.219948721114196],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":175.3389997719548}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request14c45549336e6d4b159e517b21517d18.json ================================================ { "request_id": "1_Action1_test_hedge_transaction_costs_SPX_optResolvedInstrumentValues2023Jun05", "request_hash": "14c45549336e6d4b159e517b21517d18", "type": "MockCalc", "tests": [ "test_hedge_transaction_costs" ], "mocked_data": "[[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2023-09-05\",\"expirySettleDate\":\"2023-09-07\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":100.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"USD\",\"premiumPaymentDate\":\"2023-06-07\",\"premiumSettlementDate\":\"2023-06-07\",\"settlementDate\":\"2023-06-07\",\"strikePrice\":4292.06,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".SPX\",\"underlierCurrency\":\"USD\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request159fa95af878447538185c20cedbd92c.json ================================================ { "request_id": "4_AddScaledTradeAction1_call_2021-12-09Price2021Dec10_AddScaledTradeAction1_call_2021-12-08Price2021Dec09_AddScaledTradeAction1_call_2021-12-07Price2021Dec08_AddScaledTradeAction1_call_2021-12-06Price2021Dec07", "request_hash": "159fa95af878447538185c20cedbd92c", "type": "MockCalc", "tests": [ "test_add_scaled_trade_action_with_quantity_signal" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":1815.6298443228998}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":1729.0526571533096}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":1641.455047274399}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":3212.5057997198387}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request16d7d8f484a35cf5804f799fc6592d0b.json ================================================ { "request_id": "2_5y-10y-5y-10yIRDelta2020Jan15_5y-10y-5y-10yIRDelta2020Jan14", "request_hash": "16d7d8f484a35cf5804f799fc6592d0b", "type": "MockCalc", "tests": [ "test_bucketed_risks" ], "mocked_data": "[[[[{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -1667.2435386363984}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -4.070281982421875e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -2462.7084261684417}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -3846.405987095642}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -2488.7893071540834}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -2519.677621138382}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -2488.380242152786}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -2517.527723149872}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -2489.5775452148437}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -2143.756374926758}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 1468.3243078819276}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": -51.812072123718266}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 0.00475096549987793}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.00017612953186035157}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": -7.365074157714844e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -1.247406005859375e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -5174.5843409442905}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -9.689331054687501e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 1000.1658372463227}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 6.641387939453125e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 605.7532153579713}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"60Y\", \"quoteStyle\": \"\", \"value\": -2.288818359375e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -8465.067800811768}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"70Y\", \"quoteStyle\": \"\", \"value\": 2.3651123046875002e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 62627.19566747055}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"80Y\", \"quoteStyle\": \"\", \"value\": -1.232147216796875e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 33412.22667660065}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": -8079.8609189914705}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -0.9749629940032959}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -0.9778602062225342}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -2.7306365966796878e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 1.1444091796875001e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 2.9535293579101565e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 1.0018539428710938e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 0.0006735057830810547}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 1.0027313232421875e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -2.0742034912109376e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -9.925079345703126e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": -3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": -3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": -3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": -3.814697265625e-10}], \"asset\": [-1667.2435386363984, -4.070281982421875e-06, -2462.7084261684417, -3846.405987095642, -2488.7893071540834, -2519.677621138382, -2488.380242152786, -2517.527723149872, -2489.5775452148437, -2143.756374926758, 1468.3243078819276, -51.812072123718266, 0.00475096549987793, 0.00017612953186035157, -7.365074157714844e-05, -1.247406005859375e-07, -5174.5843409442905, -9.689331054687501e-08, 1000.1658372463227, 6.641387939453125e-07, 605.7532153579713, -2.288818359375e-08, -8465.067800811768, 2.3651123046875002e-08, 62627.19566747055, -1.232147216796875e-07, 33412.22667660065, -8079.8609189914705, -0.9749629940032959, -0.9778602062225342, -2.7306365966796878e-05, 1.1444091796875001e-09, 2.9535293579101565e-05, 1.0018539428710938e-05, 0.0006735057830810547, 1.0027313232421875e-05, -2.0742034912109376e-05, -9.925079345703126e-06, 0.0, 3.814697265625e-10, -3.814697265625e-10, 3.814697265625e-10, 0.0, 0.0, -3.814697265625e-10, -7.62939453125e-10, 0.0, 0.0, 0.0, -3.814697265625e-10, -7.62939453125e-10, 3.814697265625e-10, 3.814697265625e-10, 7.62939453125e-10, -3.814697265625e-10], \"calculationTime\": 3886, \"queueingTime\": -15522}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -1678.8433957893371}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -4.8713684082031255e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -2474.410205470276}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -3846.011891560364}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -2506.0381377838135}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -2529.1688358398437}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -2505.699619178009}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -2526.3692599868778}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -2506.79551877594}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -2155.1359160346988}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": -21468.502088909914}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 139139.37289584122}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 12231.370052542878}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -1991.4383884399415}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 407.6922475692749}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -34.1978889251709}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -5276.816720574188}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -0.17461036682128908}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 997.7741626136781}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 0.02245984573364258}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -1009.4126821357728}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"60Y\", \"quoteStyle\": \"\", \"value\": -0.001241937255859375}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -141.2341786392212}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"70Y\", \"quoteStyle\": \"\", \"value\": -5.01190185546875e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": -882.8048536857606}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"80Y\", \"quoteStyle\": \"\", \"value\": 3.5079956054687503e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": -1257.728958581543}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 6768.944076137543}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -2.8821612434387207}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -2.890725910949707}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -8.072128295898438e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 8.731155395507813e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 2.961578369140625e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 0.0019910003662109376}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 2.9642486572265625e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -6.131668090820313e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -2.934112548828125e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": -1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": -1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -2.2888183593750002e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -2.2888183593750002e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 2.2888183593750002e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}], \"asset\": [-1678.8433957893371, -4.8713684082031255e-06, -2474.410205470276, -3846.011891560364, -2506.0381377838135, -2529.1688358398437, -2505.699619178009, -2526.3692599868778, -2506.79551877594, -2155.1359160346988, -21468.502088909914, 139139.37289584122, 12231.370052542878, -1991.4383884399415, 407.6922475692749, -34.1978889251709, -5276.816720574188, -0.17461036682128908, 997.7741626136781, 0.02245984573364258, -1009.4126821357728, -0.001241937255859375, -141.2341786392212, -5.01190185546875e-05, -882.8048536857606, 3.5079956054687503e-06, -1257.728958581543, 6768.944076137543, -2.8821612434387207, -2.890725910949707, -8.072128295898438e-05, 3.0517578125e-09, 8.731155395507813e-05, 2.961578369140625e-05, 0.0019910003662109376, 2.9642486572265625e-05, -6.131668090820313e-05, -2.934112548828125e-05, 0.0, 1.52587890625e-09, -1.52587890625e-09, 1.52587890625e-09, 0.0, 0.0, -1.52587890625e-09, -2.2888183593750002e-09, 7.62939453125e-10, 0.0, 0.0, -7.62939453125e-10, -2.2888183593750002e-09, 7.62939453125e-10, 7.62939453125e-10, 2.2888183593750002e-09, -7.62939453125e-10], \"calculationTime\": 3886, \"queueingTime\": -15522}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"CASH\", \"point\": \"1 DAY\", \"quoteStyle\": \"\", \"value\": 1.8385246376037598}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -1969.865845122528}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -2810.3176155670167}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -4219.953209059906}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -2842.4875506408694}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -2854.024418951416}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -2848.0048637680056}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -2886.5105300987243}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -2839.899758507538}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -2472.875695514679}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 1684.098280041504}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": -57.29909285430909}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": -0.3124637001037598}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.01659071273803711}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": -0.00036334228515625}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -3.31756591796875e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -6062.694590010071}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 4.15802001953125e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 875.1608808868409}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 4.608154296875e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 582.3982613975526}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"60Y\", \"quoteStyle\": \"\", \"value\": -2.7465820312500003e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -10446.682122909546}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"70Y\", \"quoteStyle\": \"\", \"value\": -2.5177001953125e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 70509.19486003838}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"80Y\", \"quoteStyle\": \"\", \"value\": 1.2817382812500002e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 37952.422977085116}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": -9287.682026733399}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -1.9162440155029299}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -1.9219383514404298}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -5.366973876953125e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 5.8050537109375003e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 1.969451904296875e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 0.0013237457275390625}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 1.970977783203125e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -4.076766967773438e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -1.9509124755859376e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 2.2888183593750002e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 2.2888183593750002e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": -2.2888183593750002e-09}], \"asset\": [1.8385246376037598, -1969.865845122528, -2810.3176155670167, -4219.953209059906, -2842.4875506408694, -2854.024418951416, -2848.0048637680056, -2886.5105300987243, -2839.899758507538, -2472.875695514679, 1684.098280041504, -57.29909285430909, -0.3124637001037598, 0.01659071273803711, -0.00036334228515625, -3.31756591796875e-05, -6062.694590010071, 4.15802001953125e-07, 875.1608808868409, 4.608154296875e-07, 582.3982613975526, -2.7465820312500003e-08, -10446.682122909546, -2.5177001953125e-07, 70509.19486003838, 1.2817382812500002e-07, 37952.422977085116, -9287.682026733399, -1.9162440155029299, -1.9219383514404298, -5.366973876953125e-05, 1.52587890625e-09, 5.8050537109375003e-05, 1.969451904296875e-05, 0.0013237457275390625, 1.970977783203125e-05, -4.076766967773438e-05, -1.9509124755859376e-05, 0.0, 0.0, 1.52587890625e-09, 0.0, 0.0, 0.0, 0.0, 0.0, 7.62939453125e-10, 0.0, 0.0, -7.62939453125e-10, 1.52587890625e-09, 2.2888183593750002e-09, 7.62939453125e-10, 2.2888183593750002e-09, -2.2888183593750002e-09], \"calculationTime\": 5986, \"queueingTime\": -15522}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"CASH\", \"point\": \"1 DAY\", \"quoteStyle\": \"\", \"value\": 4.022582872009277}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -1973.0425727935792}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -2815.7062319717406}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -4227.521759669495}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -2849.312306376648}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -2858.161654199219}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -2855.84428085022}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -2891.739000332642}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -2846.300032234192}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -2475.4387336791992}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": -25444.30742972107}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 155109.04739747927}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 13840.15085191803}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -2228.4155918121337}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 453.98474373931884}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -37.88409556274414}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -6072.632028569031}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -0.23216055908203126}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 974.0558401077271}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 0.02855625762939453}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -1146.7572319107055}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"60Y\", \"quoteStyle\": \"\", \"value\": -0.001268865966796875}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -794.4982891555786}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"70Y\", \"quoteStyle\": \"\", \"value\": -9.530029296875001e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": -946.510503755188}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"80Y\", \"quoteStyle\": \"\", \"value\": 6.738281250000001e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": -2451.244335346985}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 8284.516380827332}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -4.158213191223145}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -4.170569792175293}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -0.00011646118164062501}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 0.0001259674072265625}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 4.273529052734375e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 0.0028725036621093753}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 4.276885986328125e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -8.846588134765625e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -4.233245849609375e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": -3.0517578125e-09}], \"asset\": [4.022582872009277, -1973.0425727935792, -2815.7062319717406, -4227.521759669495, -2849.312306376648, -2858.161654199219, -2855.84428085022, -2891.739000332642, -2846.300032234192, -2475.4387336791992, -25444.30742972107, 155109.04739747927, 13840.15085191803, -2228.4155918121337, 453.98474373931884, -37.88409556274414, -6072.632028569031, -0.23216055908203126, 974.0558401077271, 0.02855625762939453, -1146.7572319107055, -0.001268865966796875, -794.4982891555786, -9.530029296875001e-05, -946.510503755188, 6.738281250000001e-06, -2451.244335346985, 8284.516380827332, -4.158213191223145, -4.170569792175293, -0.00011646118164062501, 3.0517578125e-09, 0.0001259674072265625, 4.273529052734375e-05, 0.0028725036621093753, 4.276885986328125e-05, -8.846588134765625e-05, -4.233245849609375e-05, 0.0, 0.0, 1.52587890625e-09, 0.0, 0.0, 0.0, 0.0, 0.0, 1.52587890625e-09, 0.0, 0.0, 0.0, 3.0517578125e-09, 3.0517578125e-09, 0.0, 3.0517578125e-09, -3.0517578125e-09], \"calculationTime\": 5986, \"queueingTime\": -15522}]]], [[[{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -1690.4274549369813}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -4.1370391845703126e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -2431.593033273697}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -3786.9843693626403}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -2482.780788569641}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -2595.327604275513}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -2482.307131084442}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -2469.640842456055}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -2483.5299607940674}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -2166.135343655777}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 1478.9622806194307}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": -51.95791416511536}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": -0.03469709815979004}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.0002544200897216797}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": -8.063964843750001e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -1.4389038085937501e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -5157.727432756425}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -1.865386962890625e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 990.278335987854}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 5.889892578125e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 736.5468615589142}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"60Y\", \"quoteStyle\": \"\", \"value\": -1.25885009765625e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -8696.432763245773}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"70Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 62440.1311512455}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"80Y\", \"quoteStyle\": \"\", \"value\": -1.461029052734375e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 33411.28173765144}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": -8116.772429734802}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -1.0129762042999269}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -1.0167831504821778}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -0.00028923263549804687}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 1.9073486328125e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 1.0453033447265626e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 1.0632324218750001e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 0.00028649139404296876}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 8.663177490234375e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": 0.00011826019287109375}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -1.0531234741210937e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": -3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": -3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": -3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [-1690.4274549369813, -4.1370391845703126e-06, -2431.593033273697, -3786.9843693626403, -2482.780788569641, -2595.327604275513, -2482.307131084442, -2469.640842456055, -2483.5299607940674, -2166.135343655777, 1478.9622806194307, -51.95791416511536, -0.03469709815979004, 0.0002544200897216797, -8.063964843750001e-05, -1.4389038085937501e-06, -5157.727432756425, -1.865386962890625e-07, 990.278335987854, 5.889892578125e-07, 736.5468615589142, -1.25885009765625e-08, -8696.432763245773, 1.52587890625e-08, 62440.1311512455, -1.461029052734375e-07, 33411.28173765144, -8116.772429734802, -1.0129762042999269, -1.0167831504821778, -0.00028923263549804687, 1.9073486328125e-09, 1.0453033447265626e-05, 1.0632324218750001e-05, 0.00028649139404296876, 8.663177490234375e-05, 0.00011826019287109375, -1.0531234741210937e-05, 0.0, 0.0, 3.814697265625e-10, 0.0, 0.0, 0.0, 0.0, -3.814697265625e-10, 0.0, -3.814697265625e-10, -3.814697265625e-10, 0.0, 3.814697265625e-10, 0.0, 0.0, -3.814697265625e-10, 0.0], \"calculationTime\": 2086, \"queueingTime\": -8318}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -1702.1810805397035}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -5.011749267578125e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -2444.868805497742}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -3789.494718639374}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -2499.9662538970947}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -2599.2179568969727}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -2499.5690949165346}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -2481.1379633499146}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -2500.6865667884827}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -2175.6149885375976}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": -22053.57870413742}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 138585.68797151643}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 12498.341227219391}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -2039.943051603699}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 418.2003437355042}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -35.16492511367798}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -5264.257787085724}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -0.2032023864746094}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 991.6761070083619}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 0.023092861938476563}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -873.0462442237855}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"60Y\", \"quoteStyle\": \"\", \"value\": -0.0007514205932617188}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -619.1557262870789}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"70Y\", \"quoteStyle\": \"\", \"value\": -5.632858276367188e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": -348.2752563781738}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"80Y\", \"quoteStyle\": \"\", \"value\": 3.467559814453125e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": -1793.3002309722901}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 7300.043917005158}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -2.951691698455811}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -2.962784684753418}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -0.0008427909851074219}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 5.340576171875e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 3.0458831787109376e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 3.098297119140625e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 0.0008348014831542969}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 0.0002524337768554688}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": 0.00034459686279296876}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -3.0686187744140625e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [-1702.1810805397035, -5.011749267578125e-06, -2444.868805497742, -3789.494718639374, -2499.9662538970947, -2599.2179568969727, -2499.5690949165346, -2481.1379633499146, -2500.6865667884827, -2175.6149885375976, -22053.57870413742, 138585.68797151643, 12498.341227219391, -2039.943051603699, 418.2003437355042, -35.16492511367798, -5264.257787085724, -0.2032023864746094, 991.6761070083619, 0.023092861938476563, -873.0462442237855, -0.0007514205932617188, -619.1557262870789, -5.632858276367188e-05, -348.2752563781738, 3.467559814453125e-06, -1793.3002309722901, 7300.043917005158, -2.951691698455811, -2.962784684753418, -0.0008427909851074219, 5.340576171875e-09, 3.0458831787109376e-05, 3.098297119140625e-05, 0.0008348014831542969, 0.0002524337768554688, 0.00034459686279296876, -3.0686187744140625e-05, 0.0, 0.0, 1.52587890625e-09, 0.0, 0.0, 0.0, 0.0, -1.52587890625e-09, 0.0, -7.62939453125e-10, -7.62939453125e-10, 0.0, 1.52587890625e-09, 0.0, 0.0, -7.62939453125e-10, 0.0], \"calculationTime\": 2086, \"queueingTime\": -8318}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"CASH\", \"point\": \"1 DAY\", \"quoteStyle\": \"\", \"value\": 1.953647787475586}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -2021.654846144867}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -2837.5144553802493}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -4316.762227175141}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -2870.856843566895}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -2882.6234098114014}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -2877.2317279357912}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -2914.1873167549134}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -2868.0600389816286}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -2484.2367954421998}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 1697.7549697944642}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": -58.98864248580933}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": -0.2841819961547852}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.020826010131835937}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": -0.0004878494262695313}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -2.7230072021484376e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -5492.3295814102175}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 6.9427490234375e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 408.04524015197757}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 3.44085693359375e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 836.7955548400879}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"60Y\", \"quoteStyle\": \"\", \"value\": 2.13623046875e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -10565.023507267762}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"70Y\", \"quoteStyle\": \"\", \"value\": 3.753662109375e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 69746.63193368455}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"80Y\", \"quoteStyle\": \"\", \"value\": -2.7313232421875e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 38275.40590349732}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": -9243.928722629547}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -2.0324371337890628}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -2.040075395965576}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -0.0005803176879882813}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 6.103515625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 2.09716796875e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 2.1333312988281252e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 0.0005748146057128906}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 0.00017381668090820314}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": 0.0002372772216796875}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -2.112884521484375e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": 7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [1.953647787475586, -2021.654846144867, -2837.5144553802493, -4316.762227175141, -2870.856843566895, -2882.6234098114014, -2877.2317279357912, -2914.1873167549134, -2868.0600389816286, -2484.2367954421998, 1697.7549697944642, -58.98864248580933, -0.2841819961547852, 0.020826010131835937, -0.0004878494262695313, -2.7230072021484376e-05, -5492.3295814102175, 6.9427490234375e-07, 408.04524015197757, 3.44085693359375e-07, 836.7955548400879, 2.13623046875e-07, -10565.023507267762, 3.753662109375e-07, 69746.63193368455, -2.7313232421875e-07, 38275.40590349732, -9243.928722629547, -2.0324371337890628, -2.040075395965576, -0.0005803176879882813, 6.103515625e-09, 2.09716796875e-05, 2.1333312988281252e-05, 0.0005748146057128906, 0.00017381668090820314, 0.0002372772216796875, -2.112884521484375e-05, 0.0, 7.62939453125e-10, 0.0, -7.62939453125e-10, 7.62939453125e-10, 0.0, 0.0, 7.62939453125e-10, 0.0, 0.0, 0.0, 0.0, -7.62939453125e-10, 0.0, -7.62939453125e-10, 0.0, 0.0], \"calculationTime\": 4893, \"queueingTime\": -8318}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"CASH\", \"point\": \"1 DAY\", \"quoteStyle\": \"\", \"value\": 4.230279399108887}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -2022.904193765259}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -2840.3832717819214}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -4316.708795300293}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -2875.0211539001466}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -2884.1831563629153}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -2882.340762391663}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -2916.8764971588134}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -2871.8497097885133}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -2485.547314588928}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": -25240.868663520814}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 154112.73422039187}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 13916.807480566407}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -2247.4538974853517}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 459.34853724365234}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -38.620221380615234}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -5539.929966027832}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -0.2598369415283203}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 539.0027855133056}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 0.031217132568359375}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -894.9394072052003}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"60Y\", \"quoteStyle\": \"\", \"value\": -0.0013491973876953126}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -973.3587597335816}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"70Y\", \"quoteStyle\": \"\", \"value\": -0.00011527862548828125}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": -999.7791904037476}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"80Y\", \"quoteStyle\": \"\", \"value\": 7.60345458984375e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": -2265.847222779846}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 7722.4546251083375}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -4.366976580810547}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -4.383388459777832}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -0.0012468933105468751}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 1.220703125e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 4.5059204101562504e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 4.583740234375e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 0.0012350708007812501}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 0.00037346954345703125}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": 0.0005098236083984375}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -4.539794921875e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": 3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": -3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [4.230279399108887, -2022.904193765259, -2840.3832717819214, -4316.708795300293, -2875.0211539001466, -2884.1831563629153, -2882.340762391663, -2916.8764971588134, -2871.8497097885133, -2485.547314588928, -25240.868663520814, 154112.73422039187, 13916.807480566407, -2247.4538974853517, 459.34853724365234, -38.620221380615234, -5539.929966027832, -0.2598369415283203, 539.0027855133056, 0.031217132568359375, -894.9394072052003, -0.0013491973876953126, -973.3587597335816, -0.00011527862548828125, -999.7791904037476, 7.60345458984375e-06, -2265.847222779846, 7722.4546251083375, -4.366976580810547, -4.383388459777832, -0.0012468933105468751, 1.220703125e-08, 4.5059204101562504e-05, 4.583740234375e-05, 0.0012350708007812501, 0.00037346954345703125, 0.0005098236083984375, -4.539794921875e-05, 0.0, 3.0517578125e-09, 0.0, -3.0517578125e-09, 3.0517578125e-09, 0.0, 0.0, 3.0517578125e-09, 0.0, 0.0, 0.0, 0.0, -3.0517578125e-09, 0.0, -3.0517578125e-09, 0.0, 0.0], \"calculationTime\": 4893, \"queueingTime\": -8318}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request18e1caaae1f91d18c754005f495ade4a.json ================================================ { "request_id": "10_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-10EqVega-Price2021Dec13_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-10EqVega-Price2021Dec10_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-09EqVega-Price2021Dec10_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-09EqVega-Price2021Dec09_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-08EqVega-Price2021Dec09_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-08EqVega-Price2021Dec08_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-07EqVega-Price2021Dec08_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-07EqVega-Price2021Dec07_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-06EqVega-Price2021Dec07_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-06EqVega-Price2021Dec06", "request_hash": "18e1caaae1f91d18c754005f495ade4a", "type": "MockCalc", "tests": [ "test_add_scaled_action_nav_with_transaction_costs" ], "mocked_data": "[[[[{\"$type\":\"RiskVector\",\"asset\":[9.150956641405065],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":136.33227024891383}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[9.375839066009503],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":146.128150719897}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[9.308270163128327],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":139.6638341786846}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[9.364713983673719],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":148.6879474878436}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[9.280815108233655],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":133.0040505502546}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[9.44452859793062],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":152.16613197021397}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[9.322611019976902],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":126.26577286726146}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[9.450558948643739],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":154.42940273089567}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[8.9650452521228],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":247.1158307476799}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[9.219948721114196],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":175.3389997719548}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request1bddc8fc32ddb8342e4bc48029eeddf1.json ================================================ { "request_id": "6_Action1_Priceable0ResolvedInstrumentValues2021Oct15_Action1_Priceable0ResolvedInstrumentValues2021Oct14_Action1_Priceable0ResolvedInstrumentValues2021Oct13_Action1_Priceable0ResolvedInstrumentValues2021Oct12_Action1_Priceable0ResolvedInstrumentValues2021Oct08_Action1_Priceable0ResolvedInstrumentValues2021Oct07", "request_hash": "1bddc8fc32ddb8342e4bc48029eeddf1", "type": "MockCalc", "tests": [ "test_mkt_trigger_data_sources" ], "mocked_data": "[[[[{\"payOrReceive\": \"Straddle\", \"terminationDate\": \"2023-10-19\", \"notionalCurrency\": \"USD\", \"effectiveDate\": \"2022-10-19\", \"expirationDate\": \"2022-10-17\", \"floatingRateOption\": \"USD-SOFR-COMPOUND\", \"floatingRateDesignatedMaturity\": \"1y\", \"floatingRateSpread\": 0.0, \"floatingRateFrequency\": \"1y\", \"floatingRateBusinessDayConvention\": \"Modified Following\", \"fixedRateFrequency\": \"1y\", \"fixedRateBusinessDayConvention\": \"Modified Following\", \"premiumPaymentDate\": \"2021-10-19\", \"fee\": 0.0, \"feeCurrency\": \"USD\", \"feePaymentDate\": \"2021-10-19\", \"clearingHouse\": \"LCH\", \"settlement\": \"Phys.CLEARED\", \"buySell\": \"Buy\", \"assetClass\": \"Rates\", \"type\": \"Swaption\", \"notionalAmount\": 100000000.0, \"strike\": 0.0062344352411400124, \"premium\": 0.0, \"$type\": \"LegDefinition\"}]]], [[[{\"payOrReceive\": \"Straddle\", \"terminationDate\": \"2023-10-18\", \"notionalCurrency\": \"USD\", \"effectiveDate\": \"2022-10-18\", \"expirationDate\": \"2022-10-14\", \"floatingRateOption\": \"USD-SOFR-COMPOUND\", \"floatingRateDesignatedMaturity\": \"1y\", \"floatingRateSpread\": 0.0, \"floatingRateFrequency\": \"1y\", \"floatingRateBusinessDayConvention\": \"Modified Following\", \"fixedRateFrequency\": \"1y\", \"fixedRateBusinessDayConvention\": \"Modified Following\", \"premiumPaymentDate\": \"2021-10-18\", \"fee\": 0.0, \"feeCurrency\": \"USD\", \"feePaymentDate\": \"2021-10-18\", \"clearingHouse\": \"LCH\", \"settlement\": \"Phys.CLEARED\", \"buySell\": \"Buy\", \"assetClass\": \"Rates\", \"type\": \"Swaption\", \"notionalAmount\": 100000000.0, \"strike\": 0.005912610173544656, \"premium\": 0.0, \"$type\": \"LegDefinition\"}]]], [[[{\"payOrReceive\": \"Straddle\", \"terminationDate\": \"2023-10-17\", \"notionalCurrency\": \"USD\", \"effectiveDate\": \"2022-10-17\", \"expirationDate\": \"2022-10-13\", \"floatingRateOption\": \"USD-SOFR-COMPOUND\", \"floatingRateDesignatedMaturity\": \"1y\", \"floatingRateSpread\": 0.0, \"floatingRateFrequency\": \"1y\", \"floatingRateBusinessDayConvention\": \"Modified Following\", \"fixedRateFrequency\": \"1y\", \"fixedRateBusinessDayConvention\": \"Modified Following\", \"premiumPaymentDate\": \"2021-10-15\", \"fee\": 0.0, \"feeCurrency\": \"USD\", \"feePaymentDate\": \"2021-10-15\", \"clearingHouse\": \"LCH\", \"settlement\": \"Phys.CLEARED\", \"buySell\": \"Buy\", \"assetClass\": \"Rates\", \"type\": \"Swaption\", \"notionalAmount\": 100000000.0, \"strike\": 0.005927856419648328, \"premium\": 0.0, \"$type\": \"LegDefinition\"}]]], [[[{\"payOrReceive\": \"Straddle\", \"terminationDate\": \"2023-10-14\", \"notionalCurrency\": \"USD\", \"effectiveDate\": \"2022-10-14\", \"expirationDate\": \"2022-10-12\", \"floatingRateOption\": \"USD-SOFR-COMPOUND\", \"floatingRateDesignatedMaturity\": \"1y\", \"floatingRateSpread\": 0.0, \"floatingRateFrequency\": \"1y\", \"floatingRateBusinessDayConvention\": \"Modified Following\", \"fixedRateFrequency\": \"1y\", \"fixedRateBusinessDayConvention\": \"Modified Following\", \"premiumPaymentDate\": \"2021-10-14\", \"fee\": 0.0, \"feeCurrency\": \"USD\", \"feePaymentDate\": \"2021-10-14\", \"clearingHouse\": \"LCH\", \"settlement\": \"Phys.CLEARED\", \"buySell\": \"Buy\", \"assetClass\": \"Rates\", \"type\": \"Swaption\", \"notionalAmount\": 100000000.0, \"strike\": 0.005450034696874547, \"premium\": 0.0, \"$type\": \"LegDefinition\"}]]], [[[{\"payOrReceive\": \"Straddle\", \"terminationDate\": \"2023-10-13\", \"notionalCurrency\": \"USD\", \"effectiveDate\": \"2022-10-13\", \"expirationDate\": \"2022-10-11\", \"floatingRateOption\": \"USD-SOFR-COMPOUND\", \"floatingRateDesignatedMaturity\": \"1y\", \"floatingRateSpread\": 0.0, \"floatingRateFrequency\": \"1y\", \"floatingRateBusinessDayConvention\": \"Modified Following\", \"fixedRateFrequency\": \"1y\", \"fixedRateBusinessDayConvention\": \"Modified Following\", \"premiumPaymentDate\": \"2021-10-12\", \"fee\": 0.0, \"feeCurrency\": \"USD\", \"feePaymentDate\": \"2021-10-12\", \"clearingHouse\": \"LCH\", \"settlement\": \"Phys.CLEARED\", \"buySell\": \"Buy\", \"assetClass\": \"Rates\", \"type\": \"Swaption\", \"notionalAmount\": 100000000.0, \"strike\": 0.004971900689859405, \"premium\": 0.0, \"$type\": \"LegDefinition\"}]]], [[[{\"payOrReceive\": \"Straddle\", \"terminationDate\": \"2023-10-12\", \"notionalCurrency\": \"USD\", \"effectiveDate\": \"2022-10-12\", \"expirationDate\": \"2022-10-07\", \"floatingRateOption\": \"USD-SOFR-COMPOUND\", \"floatingRateDesignatedMaturity\": \"1y\", \"floatingRateSpread\": 0.0, \"floatingRateFrequency\": \"1y\", \"floatingRateBusinessDayConvention\": \"Modified Following\", \"fixedRateFrequency\": \"1y\", \"fixedRateBusinessDayConvention\": \"Modified Following\", \"premiumPaymentDate\": \"2021-10-12\", \"fee\": 0.0, \"feeCurrency\": \"USD\", \"feePaymentDate\": \"2021-10-12\", \"clearingHouse\": \"LCH\", \"settlement\": \"Phys.CLEARED\", \"buySell\": \"Buy\", \"assetClass\": \"Rates\", \"type\": \"Swaption\", \"notionalAmount\": 100000000.0, \"strike\": 0.0046700887854908835, \"premium\": 0.0, \"$type\": \"LegDefinition\"}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request1c86709bb6a5113c228f95eba201756c.json ================================================ { "request_id": "17_Action1_swap2_2021-12-10Price2021Dec10_Action1_swap2_2021-12-09Price2021Dec10_Action1_swap2_2021-12-09Price2021Dec09_Action1_swap1_2021-12-09Price2021Dec09_Action1_swap2_2021-12-08Price2021Dec10_Action1_swap2_2021-12-08Price2021Dec09_Action1_swap2_2021-12-08Price2021Dec08_Action1_swap2_2021-12-07Price2021Dec10_Action1_swap2_2021-12-07Price2021Dec09_Action1_swap2_2021-12-07Price2021Dec08_Action1_swap2_2021-12-07Price2021Dec07_Action1_swap1_2021-12-07Price2021Dec07_Action1_swap2_2021-12-06Price2021Dec10_Action1_swap2_2021-12-06Price2021Dec09_Action1_swap2_2021-12-06Price2021Dec08_Action1_swap2_2021-12-06Price2021Dec07_Action1_swap2_2021-12-06Price2021Dec06", "request_hash": "1c86709bb6a5113c228f95eba201756c", "type": "MockCalc", "tests": [ "test_exit_action_bytradename" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":-9.094947017729282e-13}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":-108.88520219778547}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":9.094947017729282e-13}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":0.0}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":-370.755507604762}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":-261.818170672057}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":9.094947017729282e-13}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":-193.73634925238912}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":-84.96413213280084}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":176.4843770760217}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":-9.094947017729282e-13}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":0.0}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":71.39092484135926}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":179.94176294462432}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":440.8959510002169}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":264.6822821086753}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":9.094947017729282e-13}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request1ce838a52895a8e0f92d5bb55bd34a39.json ================================================ { "request_id": "1_5y-10yIRDelta2020Jan14", "request_hash": "1ce838a52895a8e0f92d5bb55bd34a39", "type": "MockCalc", "tests": [ "test_bucketed_risks" ], "mocked_data": "[[[[{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"CASH\", \"point\": \"1 DAY\", \"quoteStyle\": \"\", \"value\": 1.953647787475586}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -2021.654846144867}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -2837.5144553802493}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -4316.762227175141}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -2870.856843566895}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -2882.6234098114014}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -2877.2317279357912}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -2914.1873167549134}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -2868.0600389816286}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -2484.2367954421998}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 1697.7549697944642}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": -58.98864248580933}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": -0.2841819961547852}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.020826010131835937}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": -0.0004878494262695313}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -2.7230072021484376e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -5492.3295814102175}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 6.9427490234375e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 408.04524015197757}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 3.44085693359375e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 836.7955548400879}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"60Y\", \"quoteStyle\": \"\", \"value\": 2.13623046875e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -10565.023507267762}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"70Y\", \"quoteStyle\": \"\", \"value\": 3.753662109375e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 69746.63193368455}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"80Y\", \"quoteStyle\": \"\", \"value\": -2.7313232421875e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 38275.40590349732}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": -9243.928722629547}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -2.0324371337890628}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -2.040075395965576}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -0.0005803176879882813}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 6.103515625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 2.09716796875e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 2.1333312988281252e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 0.0005748146057128906}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 0.00017381668090820314}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": 0.0002372772216796875}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -2.112884521484375e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": 7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [1.953647787475586, -2021.654846144867, -2837.5144553802493, -4316.762227175141, -2870.856843566895, -2882.6234098114014, -2877.2317279357912, -2914.1873167549134, -2868.0600389816286, -2484.2367954421998, 1697.7549697944642, -58.98864248580933, -0.2841819961547852, 0.020826010131835937, -0.0004878494262695313, -2.7230072021484376e-05, -5492.3295814102175, 6.9427490234375e-07, 408.04524015197757, 3.44085693359375e-07, 836.7955548400879, 2.13623046875e-07, -10565.023507267762, 3.753662109375e-07, 69746.63193368455, -2.7313232421875e-07, 38275.40590349732, -9243.928722629547, -2.0324371337890628, -2.040075395965576, -0.0005803176879882813, 6.103515625e-09, 2.09716796875e-05, 2.1333312988281252e-05, 0.0005748146057128906, 0.00017381668090820314, 0.0002372772216796875, -2.112884521484375e-05, 0.0, 7.62939453125e-10, 0.0, -7.62939453125e-10, 7.62939453125e-10, 0.0, 0.0, 7.62939453125e-10, 0.0, 0.0, 0.0, 0.0, -7.62939453125e-10, 0.0, -7.62939453125e-10, 0.0, 0.0], \"calculationTime\": 4424, \"queueingTime\": 18}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"CASH\", \"point\": \"1 DAY\", \"quoteStyle\": \"\", \"value\": 4.230279399108887}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -2022.904193765259}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -2840.3832717819214}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -4316.708795300293}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -2875.0211539001466}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -2884.1831563629153}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -2882.340762391663}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -2916.8764971588134}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -2871.8497097885133}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -2485.547314588928}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": -25240.868663520814}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 154112.73422039187}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 13916.807480566407}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -2247.4538974853517}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 459.34853724365234}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -38.620221380615234}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -5539.929966027832}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -0.2598369415283203}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 539.0027855133056}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 0.031217132568359375}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -894.9394072052003}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"60Y\", \"quoteStyle\": \"\", \"value\": -0.0013491973876953126}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -973.3587597335816}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"70Y\", \"quoteStyle\": \"\", \"value\": -0.00011527862548828125}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": -999.7791904037476}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"80Y\", \"quoteStyle\": \"\", \"value\": 7.60345458984375e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": -2265.847222779846}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 7722.4546251083375}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -4.366976580810547}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -4.383388459777832}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -0.0012468933105468751}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 1.220703125e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 4.5059204101562504e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 4.583740234375e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 0.0012350708007812501}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 0.00037346954345703125}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": 0.0005098236083984375}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -4.539794921875e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": 3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": -3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [4.230279399108887, -2022.904193765259, -2840.3832717819214, -4316.708795300293, -2875.0211539001466, -2884.1831563629153, -2882.340762391663, -2916.8764971588134, -2871.8497097885133, -2485.547314588928, -25240.868663520814, 154112.73422039187, 13916.807480566407, -2247.4538974853517, 459.34853724365234, -38.620221380615234, -5539.929966027832, -0.2598369415283203, 539.0027855133056, 0.031217132568359375, -894.9394072052003, -0.0013491973876953126, -973.3587597335816, -0.00011527862548828125, -999.7791904037476, 7.60345458984375e-06, -2265.847222779846, 7722.4546251083375, -4.366976580810547, -4.383388459777832, -0.0012468933105468751, 1.220703125e-08, 4.5059204101562504e-05, 4.583740234375e-05, 0.0012350708007812501, 0.00037346954345703125, 0.0005098236083984375, -4.539794921875e-05, 0.0, 3.0517578125e-09, 0.0, -3.0517578125e-09, 3.0517578125e-09, 0.0, 0.0, 3.0517578125e-09, 0.0, 0.0, 0.0, 0.0, -3.0517578125e-09, 0.0, -3.0517578125e-09, 0.0, 0.0], \"calculationTime\": 4424, \"queueingTime\": 18}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request1d4539a3acd22c90869eab6aa89349e3.json ================================================ { "request_id": "2_TestCommodDelta2020Jan15_TestCommodDelta2020Jan14", "request_hash": "1d4539a3acd22c90869eab6aa89349e3", "type": "MockCalc", "tests": [ "test_bucketed_risks" ], "mocked_data": "[[[[{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"CMD NRG\", \"asset\": \"WTI\", \"class_\": \"FUTURE\", \"point\": \"Z24\", \"quoteStyle\": \"Price\", \"value\": -1630.8292216583252}], \"asset\": [-1630.8292216583252], \"calculationTime\": 2170, \"queueingTime\": -10164}]]], [[[{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"CMD NRG\", \"asset\": \"WTI\", \"class_\": \"FUTURE\", \"point\": \"Z24\", \"quoteStyle\": \"Price\", \"value\": -1630.8292216583252}], \"asset\": [-1630.8292216583252], \"calculationTime\": 2170, \"queueingTime\": -10164}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request21cb074ade64e87b2f347dcb5f3e1ca0.json ================================================ { "request_id": "1_5y-10y-5y-10yIRFwdRatetoday", "request_hash": "21cb074ade64e87b2f347dcb5f3e1ca0", "type": "MockCalc", "tests": [ "test_unnamed_portfolio" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"Rate\": 1.0}, \"val\": 0.013165585678999942, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 541}], [{\"$type\": \"Risk\", \"unit\": {\"Rate\": 1.0}, \"val\": 0.01685949177199996, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 541}], [{\"$type\": \"Risk\", \"unit\": {\"Rate\": 1.0}, \"val\": 0.025741618434707812, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 541}], [{\"$type\": \"Risk\", \"unit\": {\"Rate\": 1.0}, \"val\": 0.026454974268659327, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 541}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request2799d7b0273add0629fbc071aefb6009.json ================================================ { "request_id": "1_1y-3m-6m-5y-10yIRAnnualImpliedVol2020Jan14", "request_hash": "2799d7b0273add0629fbc071aefb6009", "type": "MockCalc", "tests": [ "test_unsupported_error_datums" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"Rate Normal Volatility\": 1.0}, \"val\": 0.006052933894743938, \"children\": {}, \"calculationTime\": 20, \"queueingTime\": 43}], [{\"$type\": \"Risk\", \"unit\": {\"Rate Normal Volatility\": 1.0}, \"val\": 0.005639384959057612, \"children\": {}, \"calculationTime\": 20, \"queueingTime\": 43}], [{\"$type\": \"Risk\", \"unit\": {\"Rate Normal Volatility\": 1.0}, \"val\": 0.005811521714097704, \"children\": {}, \"calculationTime\": 20, \"queueingTime\": 43}], [{\"$type\": \"Unsupported\", \"calculationTime\": 20, \"queueingTime\": 43}], [{\"$type\": \"Unsupported\", \"calculationTime\": 20, \"queueingTime\": 43}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request27dad63f70c50190d762787d5415fb1f.json ================================================ { "request_id": "1_swap1-swap2-swap3DollarPrice-IRDelta2020Oct15", "request_hash": "27dad63f70c50190d762787d5415fb1f", "type": "MockCalc", "tests": [ "test_results_with_resolution" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": -2.7939677238464355e-09, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 31954}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 1.203387156607548e-09, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 31954}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 5.446293356857973e-10, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 31954}]], [[{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -1610.8654129793867}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": 0.00020813103765249252}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -944.2529693827964}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 8.335337042808533e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 7.355120033025742e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN22\", \"quoteStyle\": \"\", \"value\": 6.007961928844452e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 7.05057755112648e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR22\", \"quoteStyle\": \"\", \"value\": 6.159767508506775e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": 5.893874913454056e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP22\", \"quoteStyle\": \"\", \"value\": 4.364177584648132e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 98393.28328403691}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": -8.241739124059677e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 7.81891867518425e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -3.086403012275696e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 4.828907549381256e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 4.3259933590888977e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": -7.450580596923828e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": 6.025657057762146e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -8.475035429000854e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": -4.6566128730773926e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 4.3725594878196716e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 1.210719347000122e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -1.5506520867347717e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -3.2475218176841736e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 2.342136576771736e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": -0.0001231953501701355}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 0.0003539244644343853}], \"asset\": [-1610.8654129793867, 0.00020813103765249252, -944.2529693827964, 8.335337042808533e-06, 7.355120033025742e-06, 6.007961928844452e-06, 7.05057755112648e-06, 6.159767508506775e-06, 5.893874913454056e-06, 4.364177584648132e-06, 98393.28328403691, -8.241739124059677e-06, 7.81891867518425e-06, -3.086403012275696e-06, 4.828907549381256e-07, 4.3259933590888977e-07, -7.450580596923828e-09, 6.025657057762146e-07, -8.475035429000854e-08, -4.6566128730773926e-09, 4.3725594878196716e-07, 1.210719347000122e-08, -1.5506520867347717e-07, -3.2475218176841736e-06, 2.342136576771736e-05, -0.0001231953501701355, 0.0003539244644343853], \"calculationTime\": 0, \"queueingTime\": 31954}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"CASH\", \"point\": \"1 DAY\", \"quoteStyle\": \"\", \"value\": -0.005112663142569363}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -2194.8334883273064}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -3119.4595025484914}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 0.0026566582281142477}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 0.0046742334927432246}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"JUN22\", \"quoteStyle\": \"\", \"value\": 1.4241183362901212e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -1128.5826292692964}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"MAR22\", \"quoteStyle\": \"\", \"value\": -0.0058084454934578394}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -0.004799278561584652}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"SEP22\", \"quoteStyle\": \"\", \"value\": 1.3636180572211742e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 129123.06715399623}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": -1.5242402581498027e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 1.00949140265584e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -3.40107292868197e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 4.29007550701499e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -4.512700252234936e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": 4.590922500938177e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -1.6275802627205848e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": -8.670404553413392e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 1.7178356647491455e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -1.362835057079792e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"60Y\", \"quoteStyle\": \"\", \"value\": -2.8279423713684084e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -1.5087467152625324e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"70Y\", \"quoteStyle\": \"\", \"value\": 4.181764088571072e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 1.5872676530852915e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"80Y\", \"quoteStyle\": \"\", \"value\": 2.9783835634589197e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": -0.00010374159994535149}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 0.00045306714158505204}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -0.0005823579014278948}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -0.0001942241296172142}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": 1.545906998217106e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -7.808115333318711e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 3.148149698972702e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN22\", \"quoteStyle\": \"\", \"value\": 6.984919309616089e-13}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 3.1839124858379367e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR22\", \"quoteStyle\": \"\", \"value\": 5.025230348110199e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": 6.984919309616089e-13}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP22\", \"quoteStyle\": \"\", \"value\": 7.916241884231568e-13}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": -9.313225746154786e-14}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": -4.656612873077393e-14}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -4.656612873077393e-14}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": -4.656612873077393e-14}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": 4.656612873077393e-14}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -9.313225746154786e-14}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": -4.656612873077393e-14}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": -1.862645149230957e-13}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 1.862645149230957e-13}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -9.313225746154786e-14}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -9.313225746154786e-14}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": -1.396983861923218e-13}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 9.313225746154786e-14}], \"asset\": [-0.005112663142569363, -2194.8334883273064, -3119.4595025484914, 0.0026566582281142477, 0.0046742334927432246, 1.4241183362901212e-05, -1128.5826292692964, -0.0058084454934578394, -0.004799278561584652, 1.3636180572211742e-05, 129123.06715399623, -1.5242402581498027e-05, 1.00949140265584e-05, -3.40107292868197e-06, 4.29007550701499e-07, -4.512700252234936e-08, 4.590922500938177e-07, -1.6275802627205848e-07, -8.670404553413392e-07, 1.7178356647491455e-07, -1.362835057079792e-07, -2.8279423713684084e-08, -1.5087467152625324e-06, 4.181764088571072e-08, 1.5872676530852915e-05, 2.9783835634589197e-08, -0.00010374159994535149, 0.00045306714158505204, -0.0005823579014278948, -0.0001942241296172142, 1.545906998217106e-08, -7.808115333318711e-09, 3.148149698972702e-09, 6.984919309616089e-13, 3.1839124858379367e-09, 5.025230348110199e-09, 6.984919309616089e-13, 7.916241884231568e-13, -9.313225746154786e-14, -4.656612873077393e-14, 0.0, -4.656612873077393e-14, -4.656612873077393e-14, 0.0, 4.656612873077393e-14, 0.0, -9.313225746154786e-14, -4.656612873077393e-14, -1.862645149230957e-13, 1.862645149230957e-13, -9.313225746154786e-14, -9.313225746154786e-14, 0.0, -1.396983861923218e-13, 9.313225746154786e-14], \"calculationTime\": 0, \"queueingTime\": 31954}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -1882.0940785487635}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": 1.907252403907478e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -2921.9159996171215}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 3.297881733392796}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 4.607977276842401}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN22\", \"quoteStyle\": \"\", \"value\": 2.1519158971832133}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -1100.965332232093}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR22\", \"quoteStyle\": \"\", \"value\": 2.5282175463496244}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": 3.7941538739234213}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP22\", \"quoteStyle\": \"\", \"value\": 1.6806182312299265}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 119741.11978655425}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": -1.678008989147935}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 0.23194543872850482}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -0.016721192980720664}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 4.535972650628537e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -5.407352349720895e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": 29.397815031274504}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 5.035098409280181e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 41.46961771813967}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": -2.9055976774543524e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 50.85071252977909}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"60Y\", \"quoteStyle\": \"\", \"value\": -4.0765502490103245e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 61.40542322877893}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"70Y\", \"quoteStyle\": \"\", \"value\": 5.653252126649022e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 71.92507764688098}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"80Y\", \"quoteStyle\": \"\", \"value\": 2.9981844127178196e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 80.87457384629741}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 98.5444972626982}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -0.00017722225775942206}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -5.9105987334623934e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": 4.704506136476994e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -2.3762229830026627e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 9.580515325069427e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN22\", \"quoteStyle\": \"\", \"value\": 2.3283064365386963e-13}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 9.689480066299438e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR22\", \"quoteStyle\": \"\", \"value\": 1.5292549505829812e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": 2.0954757928848269e-13}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP22\", \"quoteStyle\": \"\", \"value\": 2.0954757928848269e-13}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": -4.656612873077393e-14}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -2.3283064365386964e-14}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -2.3283064365386964e-14}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": -2.3283064365386964e-14}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 2.3283064365386964e-14}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -2.3283064365386964e-14}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": -2.3283064365386964e-14}], \"asset\": [-1882.0940785487635, 1.907252403907478e-06, -2921.9159996171215, 3.297881733392796, 4.607977276842401, 2.1519158971832133, -1100.965332232093, 2.5282175463496244, 3.7941538739234213, 1.6806182312299265, 119741.11978655425, -1.678008989147935, 0.23194543872850482, -0.016721192980720664, 4.535972650628537e-05, -5.407352349720895e-06, 29.397815031274504, 5.035098409280181e-07, 41.46961771813967, -2.9055976774543524e-07, 50.85071252977909, -4.0765502490103245e-07, 61.40542322877893, 5.653252126649022e-07, 71.92507764688098, 2.9981844127178196e-07, 80.87457384629741, 98.5444972626982, -0.00017722225775942206, -5.9105987334623934e-05, 4.704506136476994e-09, -2.3762229830026627e-09, 9.580515325069427e-10, 2.3283064365386963e-13, 9.689480066299438e-10, 1.5292549505829812e-09, 2.0954757928848269e-13, 2.0954757928848269e-13, -4.656612873077393e-14, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -2.3283064365386964e-14, -2.3283064365386964e-14, 0.0, -2.3283064365386964e-14, 2.3283064365386964e-14, -2.3283064365386964e-14, 0.0, 0.0, 0.0, -2.3283064365386964e-14], \"calculationTime\": 0, \"queueingTime\": 31954}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request286b0ac010bfdb78f33dfd318679886a.json ================================================ { "request_id": "3_Action1_swap_2021-12-10-Action1_swap_2021-12-09Price2021Dec10_Action1_swap_2021-12-08-Action1_swap_2021-12-07Price2021Dec08_Action1_swap_2021-12-06Price2021Dec06", "request_hash": "286b0ac010bfdb78f33dfd318679886a", "type": "MockCalc", "tests": [ "test_exit_action_noarg" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":0.0}],[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":75.69557051901756}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":-1.8189894035458565e-12}],[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":-646.199254763831}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":3.637978807091713e-12}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request2a045cde3970ab7b348df05e95e7b9c3.json ================================================ { "request_id": "10_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-10EqVega2021Dec13_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-10EqVega2021Dec10_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-09EqVega2021Dec10_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-09EqVega2021Dec09_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-08EqVega2021Dec09_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-08EqVega2021Dec08_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-07EqVega2021Dec08_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-07EqVega2021Dec07_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-06EqVega2021Dec07_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-06EqVega2021Dec06", "request_hash": "2a045cde3970ab7b348df05e95e7b9c3", "type": "MockCalc", "tests": [ "test_add_scaled_action_nav_with_transaction_costs" ], "mocked_data": "[[[[{\"$type\":\"RiskVector\",\"asset\":[2.7068325498850756],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[2.7733522691528925],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[3.5172008494147304],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[3.5385285773477517],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[4.754303236987471],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[4.8381691005917675],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[6.9471833496683395],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[7.042529784026264],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[4.761402842428943],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[4.8967839886102595],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request2cf5ebd3b922341e1de6d9831f8f6c79.json ================================================ { "request_id": "2_test_hedge_action_risk_trigger_2y_forward_2021-12-02_HedgeAction1_Priceable0FXDelta(aggregation_level:Type)-Price2021Dec03_test_hedge_action_risk_trigger_2y_forward_2021-12-02_HedgeAction1_Priceable0FXDelta(aggregation_level:Type)-Price2021Dec02", "request_hash": "2cf5ebd3b922341e1de6d9831f8f6c79", "type": "MockCalc", "tests": [ "test_hedge_action_risk_trigger" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"children\":{},\"val\":884.4121274159988}]],[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":6683.710253389552}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"val\":885.3939510299824}]],[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":1.862645149230957e-9}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request2d4fd08fb9b3dcc46d38954586f8cab3.json ================================================ { "request_id": "5_Action1_swapResolvedInstrumentValues2021Dec10_Action1_swapResolvedInstrumentValues2021Dec09_Action1_swapResolvedInstrumentValues2021Dec08_Action1_swapResolvedInstrumentValues2021Dec07_Action1_swapResolvedInstrumentValues2021Dec06", "request_hash": "2d4fd08fb9b3dcc46d38954586f8cab3", "type": "MockCalc", "tests": [ "test_exit_action_noarg" ], "mocked_data": "[[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Rates\",\"effectiveDate\":\"2021-12-14\",\"fee\":0.0,\"feeCurrency\":\"USD\",\"feePaymentDate\":\"2021-12-14\",\"fixedFirstStub\":\"2021-12-14\",\"fixedHolidays\":\"New York,London\",\"fixedLastStub\":\"2031-12-14\",\"fixedRate\":0.015378184493270055,\"fixedRateAccrualConvention\":\"Adjusted\",\"fixedRateBusinessDayConvention\":\"Modified Following\",\"fixedRateDayCountFraction\":\"30/360\",\"fixedRateFrequency\":\"6m\",\"floatingFirstStub\":\"2021-12-14\",\"floatingHolidays\":\"New York,London\",\"floatingLastStub\":\"2031-12-14\",\"floatingRateAccrualConvention\":\"Adjusted\",\"floatingRateBusinessDayConvention\":\"Modified Following\",\"floatingRateDesignatedMaturity\":\"3m\",\"floatingRateFrequency\":\"3m\",\"floatingRateOption\":\"USD-LIBOR-BBA\",\"floatingRateSpread\":0.0,\"notionalAmount\":100000.0,\"notionalCurrency\":\"USD\",\"payOrReceive\":\"Receive\",\"principalExchange\":\"None\",\"rollConvention\":\"Default\",\"terminationDate\":\"2031-12-14\",\"type\":\"Swap\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Rates\",\"effectiveDate\":\"2021-12-13\",\"fee\":0.0,\"feeCurrency\":\"USD\",\"feePaymentDate\":\"2021-12-13\",\"fixedFirstStub\":\"2021-12-13\",\"fixedHolidays\":\"New York,London\",\"fixedLastStub\":\"2031-12-13\",\"fixedRate\":0.01545536426173,\"fixedRateAccrualConvention\":\"Adjusted\",\"fixedRateBusinessDayConvention\":\"Modified Following\",\"fixedRateDayCountFraction\":\"30/360\",\"fixedRateFrequency\":\"6m\",\"floatingFirstStub\":\"2021-12-13\",\"floatingHolidays\":\"New York,London\",\"floatingLastStub\":\"2031-12-13\",\"floatingRateAccrualConvention\":\"Adjusted\",\"floatingRateBusinessDayConvention\":\"Modified Following\",\"floatingRateDayCountFraction\":\"ACT/360\",\"floatingRateDesignatedMaturity\":\"3m\",\"floatingRateFrequency\":\"3m\",\"floatingRateOption\":\"USD-LIBOR-BBA\",\"floatingRateSpread\":0.0,\"notionalAmount\":100000.0,\"notionalCurrency\":\"USD\",\"payOrReceive\":\"Receive\",\"principalExchange\":\"None\",\"rollConvention\":\"Default\",\"terminationDate\":\"2031-12-13\",\"type\":\"Swap\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Rates\",\"effectiveDate\":\"2021-12-10\",\"fee\":0.0,\"feeCurrency\":\"USD\",\"feePaymentDate\":\"2021-12-10\",\"fixedFirstStub\":\"2021-12-10\",\"fixedHolidays\":\"New York,London\",\"fixedLastStub\":\"2031-12-10\",\"fixedRate\":0.016092787122729938,\"fixedRateAccrualConvention\":\"Adjusted\",\"fixedRateBusinessDayConvention\":\"Modified Following\",\"fixedRateDayCountFraction\":\"30/360\",\"fixedRateFrequency\":\"6m\",\"floatingFirstStub\":\"2021-12-10\",\"floatingHolidays\":\"New York,London\",\"floatingLastStub\":\"2031-12-10\",\"floatingRateAccrualConvention\":\"Adjusted\",\"floatingRateBusinessDayConvention\":\"Modified Following\",\"floatingRateDayCountFraction\":\"ACT/360\",\"floatingRateDesignatedMaturity\":\"3m\",\"floatingRateFrequency\":\"3m\",\"floatingRateOption\":\"USD-LIBOR-BBA\",\"floatingRateSpread\":0.0,\"notionalAmount\":100000.0,\"notionalCurrency\":\"USD\",\"payOrReceive\":\"Receive\",\"principalExchange\":\"None\",\"rollConvention\":\"Default\",\"terminationDate\":\"2031-12-10\",\"type\":\"Swap\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Rates\",\"effectiveDate\":\"2021-12-09\",\"fee\":0.0,\"feeCurrency\":\"USD\",\"feePaymentDate\":\"2021-12-09\",\"fixedFirstStub\":\"2021-12-09\",\"fixedHolidays\":\"New York,London\",\"fixedLastStub\":\"2031-12-09\",\"fixedRate\":0.015398724136309936,\"fixedRateAccrualConvention\":\"Adjusted\",\"fixedRateBusinessDayConvention\":\"Modified Following\",\"fixedRateDayCountFraction\":\"30/360\",\"fixedRateFrequency\":\"6m\",\"floatingFirstStub\":\"2021-12-09\",\"floatingHolidays\":\"New York,London\",\"floatingLastStub\":\"2031-12-09\",\"floatingRateAccrualConvention\":\"Adjusted\",\"floatingRateBusinessDayConvention\":\"Modified Following\",\"floatingRateDayCountFraction\":\"ACT/360\",\"floatingRateDesignatedMaturity\":\"3m\",\"floatingRateFrequency\":\"3m\",\"floatingRateOption\":\"USD-LIBOR-BBA\",\"floatingRateSpread\":0.0,\"notionalAmount\":100000.0,\"notionalCurrency\":\"USD\",\"payOrReceive\":\"Receive\",\"principalExchange\":\"None\",\"rollConvention\":\"Default\",\"terminationDate\":\"2031-12-09\",\"type\":\"Swap\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Rates\",\"effectiveDate\":\"2021-12-08\",\"fee\":0.0,\"feeCurrency\":\"USD\",\"feePaymentDate\":\"2021-12-08\",\"fixedFirstStub\":\"2021-12-08\",\"fixedHolidays\":\"New York,London\",\"fixedLastStub\":\"2031-12-08\",\"fixedRate\":0.01495929132108002,\"fixedRateAccrualConvention\":\"Adjusted\",\"fixedRateBusinessDayConvention\":\"Modified Following\",\"fixedRateDayCountFraction\":\"30/360\",\"fixedRateFrequency\":\"6m\",\"floatingFirstStub\":\"2021-12-08\",\"floatingHolidays\":\"New York,London\",\"floatingLastStub\":\"2031-12-08\",\"floatingRateAccrualConvention\":\"Adjusted\",\"floatingRateBusinessDayConvention\":\"Modified Following\",\"floatingRateDayCountFraction\":\"ACT/360\",\"floatingRateDesignatedMaturity\":\"3m\",\"floatingRateFrequency\":\"3m\",\"floatingRateOption\":\"USD-LIBOR-BBA\",\"floatingRateSpread\":0.0,\"notionalAmount\":100000.0,\"notionalCurrency\":\"USD\",\"payOrReceive\":\"Receive\",\"principalExchange\":\"None\",\"rollConvention\":\"Default\",\"terminationDate\":\"2031-12-08\",\"type\":\"Swap\"}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request2f3646d722cf8712bd4a452a2768482f.json ================================================ { "request_id": "1_EUR1y-EUR2y-EUR3yIRFwdRate2020Oct15", "request_hash": "2f3646d722cf8712bd4a452a2768482f", "type": "MockCalc", "tests": [ "test_duplicate_instrument" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"Rate\": 1.0}, \"val\": -0.005378011672427782, \"children\": {}, \"calculationTime\": 373, \"queueingTime\": 439}], [{\"$type\": \"Risk\", \"unit\": {\"Rate\": 1.0}, \"val\": -0.005224409462412383, \"children\": {}, \"calculationTime\": 373, \"queueingTime\": 439}], [{\"$type\": \"Risk\", \"unit\": {\"Rate\": 1.0}, \"val\": -0.0051899501984013115, \"children\": {}, \"calculationTime\": 373, \"queueingTime\": 439}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request363421aeeef6464692365989d32bb4f9.json ================================================ { "request_id": "1_5yDollarPrice-Price2020Jan14", "request_hash": "363421aeeef6464692365989d32bb4f9", "type": "MockCalc", "tests": [ "test_one_portfolio" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 3660454.298656369, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 129}]], [[{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 3290317.2121213092, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 129}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request36fb828c98daf78a60a1d02431710592.json ================================================ { "request_id": "1_1y-3m-6m-5y-10yIRVega(aggregation_level:Asset, currency:local)-Price2020Jan14", "request_hash": "36fb828c98daf78a60a1d02431710592", "type": "MockCalc", "tests": [ "test_empty_calc_request" ], "mocked_data": "[[[[{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 18951.379622470093}], \"values\": [18951.379622470093], \"calculationTime\": 926, \"queueingTime\": -3787}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 9546.483379703379}], \"values\": [9546.483379703379], \"calculationTime\": 926, \"queueingTime\": -3787}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 13453.388908411027}], \"values\": [13453.388908411027], \"calculationTime\": 926, \"queueingTime\": -3787}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 0.0}], \"values\": [0.0], \"calculationTime\": 926, \"queueingTime\": -3787}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 0.0}], \"values\": [0.0], \"calculationTime\": 926, \"queueingTime\": -3787}]], [[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 1159341.7952729776, \"children\": {}, \"calculationTime\": 162, \"queueingTime\": -3787}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 544183.8212896035, \"children\": {}, \"calculationTime\": 162, \"queueingTime\": -3787}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 790300.4555458546, \"children\": {}, \"calculationTime\": 162, \"queueingTime\": -3787}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 9321685.065826869, \"children\": {}, \"calculationTime\": 162, \"queueingTime\": -3787}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 19280407.561757438, \"children\": {}, \"calculationTime\": 162, \"queueingTime\": -3787}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request3a4db3779266fe6fc7e31eead46d9996.json ================================================ { "request_id": "15_Action1_GBP1y_2024-05-10Price2024May10_Action1_GBP1y_2024-05-09Price2024May10_Action1_GBP1y_2024-05-09Price2024May09_Action1_GBP1y_2024-05-08Price2024May10_Action1_GBP1y_2024-05-08Price2024May09_Action1_GBP1y_2024-05-08Price2024May08_Action1_GBP1y_2024-05-07Price2024May10_Action1_GBP1y_2024-05-07Price2024May09_Action1_GBP1y_2024-05-07Price2024May08_Action1_GBP1y_2024-05-07Price2024May07_Action1_GBP1y_2024-05-06Price2024May10_Action1_GBP1y_2024-05-06Price2024May09_Action1_GBP1y_2024-05-06Price2024May08_Action1_GBP1y_2024-05-06Price2024May07_Action1_GBP1y_2024-05-06Price2024May06", "request_hash": "3a4db3779266fe6fc7e31eead46d9996", "type": "MockCalc", "tests": [ "test_agg_transaction_cost", "test_scaled_transaction_cost" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":0.0}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":-7.679812935579321}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":0.0}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":6.03453455370618}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":13.669386985698111}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":0.0}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":4.795125535352781}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":12.384840342876487}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":-1.2136689731237311}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":0.0}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":16.25046584872962}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":23.84028699719056}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":10.237032427438407}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":11.449348174920942}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":0.0}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request3ad3d9a8afd809af0b2b00617d46758d.json ================================================ { "request_id": "1_10y@0DollarPrice2020Oct15", "request_hash": "3ad3d9a8afd809af0b2b00617d46758d", "type": "MockCalc", "tests": [ "test_single_instrument" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 7391261.222935775, \"children\": {}, \"calculationTime\": 157, \"queueingTime\": 267}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request3ae2e32d798a86cd2605b23e17e8fb03.json ================================================ { "request_id": "1_5y-10yIRDelta-Price2020Jan14", "request_hash": "3ae2e32d798a86cd2605b23e17e8fb03", "type": "MockCalc", "tests": [ "test_diff_types_risk_measures" ], "mocked_data": "[[[[{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -1690.4274549369813}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -4.1370391845703126e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -2431.593033273697}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -3786.9843693626403}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -2482.780788569641}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -2595.327604275513}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -2482.307131084442}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -2469.640842456055}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -2483.5299607940674}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -2166.135343655777}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 1478.9622806194307}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": -51.95791416511536}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": -0.03469709815979004}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.0002544200897216797}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": -8.063964843750001e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -1.4389038085937501e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -5157.727432756425}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -1.865386962890625e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 990.278335987854}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 5.889892578125e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 736.5468615589142}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"60Y\", \"quoteStyle\": \"\", \"value\": -1.25885009765625e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -8696.432763245773}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"70Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 62440.1311512455}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"80Y\", \"quoteStyle\": \"\", \"value\": -1.461029052734375e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 33411.28173765144}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": -8116.772429734802}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -1.0129762042999269}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -1.0167831504821778}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -0.00028923263549804687}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 1.9073486328125e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 1.0453033447265626e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 1.0632324218750001e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 0.00028649139404296876}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 8.663177490234375e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": 0.00011826019287109375}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -1.0531234741210937e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": -3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": -3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": -3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [-1690.4274549369813, -4.1370391845703126e-06, -2431.593033273697, -3786.9843693626403, -2482.780788569641, -2595.327604275513, -2482.307131084442, -2469.640842456055, -2483.5299607940674, -2166.135343655777, 1478.9622806194307, -51.95791416511536, -0.03469709815979004, 0.0002544200897216797, -8.063964843750001e-05, -1.4389038085937501e-06, -5157.727432756425, -1.865386962890625e-07, 990.278335987854, 5.889892578125e-07, 736.5468615589142, -1.25885009765625e-08, -8696.432763245773, 1.52587890625e-08, 62440.1311512455, -1.461029052734375e-07, 33411.28173765144, -8116.772429734802, -1.0129762042999269, -1.0167831504821778, -0.00028923263549804687, 1.9073486328125e-09, 1.0453033447265626e-05, 1.0632324218750001e-05, 0.00028649139404296876, 8.663177490234375e-05, 0.00011826019287109375, -1.0531234741210937e-05, 0.0, 0.0, 3.814697265625e-10, 0.0, 0.0, 0.0, 0.0, -3.814697265625e-10, 0.0, -3.814697265625e-10, -3.814697265625e-10, 0.0, 3.814697265625e-10, 0.0, 0.0, -3.814697265625e-10, 0.0], \"calculationTime\": 1003, \"queueingTime\": -146}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -1702.1810805397035}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -5.011749267578125e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -2444.868805497742}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -3789.494718639374}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -2499.9662538970947}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -2599.2179568969727}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -2499.5690949165346}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -2481.1379633499146}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -2500.6865667884827}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -2175.6149885375976}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": -22053.57870413742}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 138585.68797151643}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 12498.341227219391}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -2039.943051603699}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 418.2003437355042}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -35.16492511367798}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -5264.257787085724}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -0.2032023864746094}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 991.6761070083619}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 0.023092861938476563}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -873.0462442237855}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"60Y\", \"quoteStyle\": \"\", \"value\": -0.0007514205932617188}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -619.1557262870789}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"70Y\", \"quoteStyle\": \"\", \"value\": -5.632858276367188e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": -348.2752563781738}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"80Y\", \"quoteStyle\": \"\", \"value\": 3.467559814453125e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": -1793.3002309722901}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 7300.043917005158}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -2.951691698455811}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -2.962784684753418}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -0.0008427909851074219}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 5.340576171875e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 3.0458831787109376e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 3.098297119140625e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 0.0008348014831542969}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 0.0002524337768554688}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": 0.00034459686279296876}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -3.0686187744140625e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [-1702.1810805397035, -5.011749267578125e-06, -2444.868805497742, -3789.494718639374, -2499.9662538970947, -2599.2179568969727, -2499.5690949165346, -2481.1379633499146, -2500.6865667884827, -2175.6149885375976, -22053.57870413742, 138585.68797151643, 12498.341227219391, -2039.943051603699, 418.2003437355042, -35.16492511367798, -5264.257787085724, -0.2032023864746094, 991.6761070083619, 0.023092861938476563, -873.0462442237855, -0.0007514205932617188, -619.1557262870789, -5.632858276367188e-05, -348.2752563781738, 3.467559814453125e-06, -1793.3002309722901, 7300.043917005158, -2.951691698455811, -2.962784684753418, -0.0008427909851074219, 5.340576171875e-09, 3.0458831787109376e-05, 3.098297119140625e-05, 0.0008348014831542969, 0.0002524337768554688, 0.00034459686279296876, -3.0686187744140625e-05, 0.0, 0.0, 1.52587890625e-09, 0.0, 0.0, 0.0, 0.0, -1.52587890625e-09, 0.0, -7.62939453125e-10, -7.62939453125e-10, 0.0, 1.52587890625e-09, 0.0, 0.0, -7.62939453125e-10, 0.0], \"calculationTime\": 1003, \"queueingTime\": -146}]], [[{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 3290317.2121213092, \"children\": {}, \"calculationTime\": 42, \"queueingTime\": -146}], [{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 9587809.11540649, \"children\": {}, \"calculationTime\": 42, \"queueingTime\": -146}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request3b59bb4f49f8e379b9b054736bc6fb94.json ================================================ { "request_id": "5_HedgeAction1_test_hedge_transaction_costs_syn_fwd_call-HedgeAction1_test_hedge_transaction_costs_syn_fwd_putResolvedInstrumentValues2023Jun09_HedgeAction1_test_hedge_transaction_costs_syn_fwd_call-HedgeAction1_test_hedge_transaction_costs_syn_fwd_putResolvedInstrumentValues2023Jun08_HedgeAction1_test_hedge_transaction_costs_syn_fwd_call-HedgeAction1_test_hedge_transaction_costs_syn_fwd_putResolvedInstrumentValues2023Jun07_HedgeAction1_test_hedge_transaction_costs_syn_fwd_call-HedgeAction1_test_hedge_transaction_costs_syn_fwd_putResolvedInstrumentValues2023Jun06_HedgeAction1_test_hedge_transaction_costs_syn_fwd_call-HedgeAction1_test_hedge_transaction_costs_syn_fwd_putResolvedInstrumentValues2023Jun05", "request_hash": "3b59bb4f49f8e379b9b054736bc6fb94", "type": "MockCalc", "tests": [ "test_hedge_transaction_costs" ], "mocked_data": "[[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2023-09-11\",\"expirySettleDate\":\"2023-09-13\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":100.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"USD\",\"premiumPaymentDate\":\"2023-06-13\",\"premiumSettlementDate\":\"2023-06-13\",\"settlementDate\":\"2023-06-13\",\"strikePrice\":4298.03,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".SPX\",\"underlierCurrency\":\"USD\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}],[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2023-09-11\",\"expirySettleDate\":\"2023-09-13\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":100.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Put\",\"premium\":0.0,\"premiumCurrency\":\"USD\",\"premiumPaymentDate\":\"2023-06-13\",\"premiumSettlementDate\":\"2023-06-13\",\"settlementDate\":\"2023-06-13\",\"strikePrice\":4298.03,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".SPX\",\"underlierCurrency\":\"USD\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2023-09-08\",\"expirySettleDate\":\"2023-09-12\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":100.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"USD\",\"premiumPaymentDate\":\"2023-06-12\",\"premiumSettlementDate\":\"2023-06-12\",\"settlementDate\":\"2023-06-12\",\"strikePrice\":4282.4,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".SPX\",\"underlierCurrency\":\"USD\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}],[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2023-09-08\",\"expirySettleDate\":\"2023-09-12\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":100.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Put\",\"premium\":0.0,\"premiumCurrency\":\"USD\",\"premiumPaymentDate\":\"2023-06-12\",\"premiumSettlementDate\":\"2023-06-12\",\"settlementDate\":\"2023-06-12\",\"strikePrice\":4282.4,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".SPX\",\"underlierCurrency\":\"USD\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2023-09-07\",\"expirySettleDate\":\"2023-09-11\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":100.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"USD\",\"premiumPaymentDate\":\"2023-06-09\",\"premiumSettlementDate\":\"2023-06-09\",\"settlementDate\":\"2023-06-09\",\"strikePrice\":4271.85,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".SPX\",\"underlierCurrency\":\"USD\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}],[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2023-09-07\",\"expirySettleDate\":\"2023-09-11\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":100.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Put\",\"premium\":0.0,\"premiumCurrency\":\"USD\",\"premiumPaymentDate\":\"2023-06-09\",\"premiumSettlementDate\":\"2023-06-09\",\"settlementDate\":\"2023-06-09\",\"strikePrice\":4271.85,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".SPX\",\"underlierCurrency\":\"USD\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2023-09-06\",\"expirySettleDate\":\"2023-09-08\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":100.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"USD\",\"premiumPaymentDate\":\"2023-06-08\",\"premiumSettlementDate\":\"2023-06-08\",\"settlementDate\":\"2023-06-08\",\"strikePrice\":4283.84,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".SPX\",\"underlierCurrency\":\"USD\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}],[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2023-09-06\",\"expirySettleDate\":\"2023-09-08\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":100.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Put\",\"premium\":0.0,\"premiumCurrency\":\"USD\",\"premiumPaymentDate\":\"2023-06-08\",\"premiumSettlementDate\":\"2023-06-08\",\"settlementDate\":\"2023-06-08\",\"strikePrice\":4283.84,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".SPX\",\"underlierCurrency\":\"USD\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2023-09-05\",\"expirySettleDate\":\"2023-09-07\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":100.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"USD\",\"premiumPaymentDate\":\"2023-06-07\",\"premiumSettlementDate\":\"2023-06-07\",\"settlementDate\":\"2023-06-07\",\"strikePrice\":4292.06,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".SPX\",\"underlierCurrency\":\"USD\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}],[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2023-09-05\",\"expirySettleDate\":\"2023-09-07\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":100.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Put\",\"premium\":0.0,\"premiumCurrency\":\"USD\",\"premiumPaymentDate\":\"2023-06-07\",\"premiumSettlementDate\":\"2023-06-07\",\"settlementDate\":\"2023-06-07\",\"strikePrice\":4292.06,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".SPX\",\"underlierCurrency\":\"USD\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request3ce9c60acb3eb164709a3f173c1fa3f2.json ================================================ { "request_id": "5_AddScaledTradeAction1_callResolvedInstrumentValues2021Dec10_AddScaledTradeAction1_callResolvedInstrumentValues2021Dec09_AddScaledTradeAction1_callResolvedInstrumentValues2021Dec08_AddScaledTradeAction1_callResolvedInstrumentValues2021Dec07_AddScaledTradeAction1_callResolvedInstrumentValues2021Dec06", "request_hash": "3ce9c60acb3eb164709a3f173c1fa3f2", "type": "MockCalc", "tests": [ "test_add_scaled_trade_action_with_quantity_signal" ], "mocked_data": "[[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-03-10\",\"expirySettleDate\":\"2022-03-14\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-14\",\"premiumSettlementDate\":\"2021-12-14\",\"settlementDate\":\"2021-12-14\",\"strikePrice\":4199.16,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-03-09\",\"expirySettleDate\":\"2022-03-11\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-13\",\"premiumSettlementDate\":\"2021-12-13\",\"settlementDate\":\"2021-12-13\",\"strikePrice\":4208.3,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-03-08\",\"expirySettleDate\":\"2022-03-10\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-10\",\"premiumSettlementDate\":\"2021-12-10\",\"settlementDate\":\"2021-12-10\",\"strikePrice\":4233.09,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-03-07\",\"expirySettleDate\":\"2022-03-09\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-09\",\"premiumSettlementDate\":\"2021-12-09\",\"settlementDate\":\"2021-12-09\",\"strikePrice\":4276.2,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-03-07\",\"expirySettleDate\":\"2022-03-09\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-08\",\"premiumSettlementDate\":\"2021-12-08\",\"settlementDate\":\"2021-12-08\",\"strikePrice\":4137.11,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request3d02eb9418db9237797173d4f68b4425.json ================================================ { "request_id": "3_Action1_test_generic_engine_simple_2y_call_2021-12-01Price2021Dec03_Action1_test_generic_engine_simple_2y_call_2021-12-01Price2021Dec02_Action1_test_generic_engine_simple_2y_call_2021-12-01Price2021Dec01", "request_hash": "3d02eb9418db9237797173d4f68b4425", "type": "MockCalc", "tests": [ "test_generic_engine_simple" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":537.6278704525321}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":1885.940412188589}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":0.0}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request404a1334920b60ed517c9383e2dcc32c.json ================================================ { "request_id": "1_test_hedge_action_risk_trigger_2y_callResolvedInstrumentValues2021Dec01", "request_hash": "404a1334920b60ed517c9383e2dcc32c", "type": "MockCalc", "tests": [ "test_hedge_action_risk_trigger" ], "mocked_data": "[[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"FX\",\"buySell\":\"Buy\",\"callAmount\":\"100000\",\"callCurrency\":\"USD\",\"exerciseStyle\":\"Manual\",\"expirationDate\":\"2023-11-30\",\"expirationTime\":\"NYC\",\"methodOfSettlement\":\"Physical\",\"notionalAmount\":100000.0,\"notionalAmountInOtherCurrency\":11054466.772999996,\"notionalCurrency\":\"USD\",\"optionType\":\"Call\",\"pair\":\"USD JPY\",\"premium\":-4176.647676080006,\"premiumCurrency\":\"USD\",\"premiumPaymentDate\":\"2023-12-04\",\"putAmount\":\"11054466.772999996319\",\"putCurrency\":\"JPY\",\"settlementDate\":\"2023-12-04\",\"strikePrice\":110.54466772999997,\"type\":\"Option\"}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request4329d6a92dec3a15646a3298c22755b0.json ================================================ { "request_id": "5_Action1_GBP1yResolvedInstrumentValues2024May10_Action1_GBP1yResolvedInstrumentValues2024May09_Action1_GBP1yResolvedInstrumentValues2024May08_Action1_GBP1yResolvedInstrumentValues2024May07_Action1_GBP1yResolvedInstrumentValues2024May06", "request_hash": "4329d6a92dec3a15646a3298c22755b0", "type": "MockCalc", "tests": [ "test_agg_transaction_cost", "test_scaled_transaction_cost" ], "mocked_data": "[[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Rates\",\"effectiveDate\":\"2024-05-10\",\"fee\":0.0,\"feeCurrency\":\"GBP\",\"feePaymentDate\":\"2024-05-14\",\"fixedFirstStub\":\"2024-05-10\",\"fixedHolidays\":\"London\",\"fixedLastStub\":\"2025-05-10\",\"fixedRate\":0.04889173608915542,\"fixedRateAccrualConvention\":\"Adjusted\",\"fixedRateBusinessDayConvention\":\"Modified Following\",\"fixedRateFrequency\":\"1y\",\"floatingFirstStub\":\"2024-05-10\",\"floatingHolidays\":\"London\",\"floatingLastStub\":\"2025-05-10\",\"floatingRateAccrualConvention\":\"Adjusted\",\"floatingRateBusinessDayConvention\":\"Modified Following\",\"floatingRateDesignatedMaturity\":\"1y\",\"floatingRateFrequency\":\"1y\",\"floatingRateOption\":\"GBP-SONIA-COMPOUND\",\"floatingRateSpread\":0.0,\"notionalAmount\":50000.0,\"notionalCurrency\":\"GBP\",\"payOrReceive\":\"Receive\",\"principalExchange\":\"None\",\"rollConvention\":\"Default\",\"terminationDate\":\"2025-05-10\",\"type\":\"Swap\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Rates\",\"effectiveDate\":\"2024-05-09\",\"fee\":0.0,\"feeCurrency\":\"GBP\",\"feePaymentDate\":\"2024-05-13\",\"fixedFirstStub\":\"2024-05-09\",\"fixedHolidays\":\"London\",\"fixedLastStub\":\"2025-05-09\",\"fixedRate\":0.0487754582882034,\"fixedRateAccrualConvention\":\"Adjusted\",\"fixedRateBusinessDayConvention\":\"Modified Following\",\"fixedRateFrequency\":\"1y\",\"floatingFirstStub\":\"2024-05-09\",\"floatingHolidays\":\"London\",\"floatingLastStub\":\"2025-05-09\",\"floatingRateAccrualConvention\":\"Adjusted\",\"floatingRateBusinessDayConvention\":\"Modified Following\",\"floatingRateDesignatedMaturity\":\"1y\",\"floatingRateFrequency\":\"1y\",\"floatingRateOption\":\"GBP-SONIA-COMPOUND\",\"floatingRateSpread\":0.0,\"notionalAmount\":50000.0,\"notionalCurrency\":\"GBP\",\"payOrReceive\":\"Receive\",\"principalExchange\":\"None\",\"rollConvention\":\"Default\",\"terminationDate\":\"2025-05-09\",\"type\":\"Swap\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Rates\",\"effectiveDate\":\"2024-05-08\",\"fee\":0.0,\"feeCurrency\":\"GBP\",\"feePaymentDate\":\"2024-05-10\",\"fixedFirstStub\":\"2024-05-08\",\"fixedHolidays\":\"London\",\"fixedLastStub\":\"2025-05-08\",\"fixedRate\":0.04908837541949462,\"fixedRateAccrualConvention\":\"Adjusted\",\"fixedRateBusinessDayConvention\":\"Modified Following\",\"fixedRateFrequency\":\"1y\",\"floatingFirstStub\":\"2024-05-08\",\"floatingHolidays\":\"London\",\"floatingLastStub\":\"2025-05-08\",\"floatingRateAccrualConvention\":\"Adjusted\",\"floatingRateBusinessDayConvention\":\"Modified Following\",\"floatingRateDesignatedMaturity\":\"1y\",\"floatingRateFrequency\":\"1y\",\"floatingRateOption\":\"GBP-SONIA-COMPOUND\",\"floatingRateSpread\":0.0,\"notionalAmount\":50000.0,\"notionalCurrency\":\"GBP\",\"payOrReceive\":\"Receive\",\"principalExchange\":\"None\",\"rollConvention\":\"Default\",\"terminationDate\":\"2025-05-08\",\"type\":\"Swap\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Rates\",\"effectiveDate\":\"2024-05-07\",\"fee\":0.0,\"feeCurrency\":\"GBP\",\"feePaymentDate\":\"2024-05-09\",\"fixedFirstStub\":\"2024-05-07\",\"fixedHolidays\":\"London\",\"fixedLastStub\":\"2025-05-07\",\"fixedRate\":0.049084760866603405,\"fixedRateAccrualConvention\":\"Adjusted\",\"fixedRateBusinessDayConvention\":\"Modified Following\",\"fixedRateFrequency\":\"1y\",\"floatingFirstStub\":\"2024-05-07\",\"floatingHolidays\":\"London\",\"floatingLastStub\":\"2025-05-07\",\"floatingRateAccrualConvention\":\"Adjusted\",\"floatingRateBusinessDayConvention\":\"Modified Following\",\"floatingRateDesignatedMaturity\":\"1y\",\"floatingRateFrequency\":\"1y\",\"floatingRateOption\":\"GBP-SONIA-COMPOUND\",\"floatingRateSpread\":0.0,\"notionalAmount\":50000.0,\"notionalCurrency\":\"GBP\",\"payOrReceive\":\"Receive\",\"principalExchange\":\"None\",\"rollConvention\":\"Default\",\"terminationDate\":\"2025-05-07\",\"type\":\"Swap\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Rates\",\"effectiveDate\":\"2024-05-07\",\"fee\":0.0,\"feeCurrency\":\"GBP\",\"feePaymentDate\":\"2024-05-08\",\"fixedFirstStub\":\"2024-05-07\",\"fixedHolidays\":\"London\",\"fixedLastStub\":\"2025-05-07\",\"fixedRate\":0.049324987600446714,\"fixedRateAccrualConvention\":\"Adjusted\",\"fixedRateBusinessDayConvention\":\"Modified Following\",\"fixedRateFrequency\":\"1y\",\"floatingFirstStub\":\"2024-05-07\",\"floatingHolidays\":\"London\",\"floatingLastStub\":\"2025-05-07\",\"floatingRateAccrualConvention\":\"Adjusted\",\"floatingRateBusinessDayConvention\":\"Modified Following\",\"floatingRateDesignatedMaturity\":\"1y\",\"floatingRateFrequency\":\"1y\",\"floatingRateOption\":\"GBP-SONIA-COMPOUND\",\"floatingRateSpread\":0.0,\"notionalAmount\":50000.0,\"notionalCurrency\":\"GBP\",\"payOrReceive\":\"Receive\",\"principalExchange\":\"None\",\"rollConvention\":\"Default\",\"terminationDate\":\"2025-05-07\",\"type\":\"Swap\"}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request43642a3bd1d558d1e0a4197c502075f5.json ================================================ { "request_id": "10_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-10EqVega-Price2021Dec13_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-10EqVega-Price2021Dec10_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-09EqVega-Price2021Dec10_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-09EqVega-Price2021Dec09_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-08EqVega-Price2021Dec09_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-08EqVega-Price2021Dec08_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-07EqVega-Price2021Dec08_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-07EqVega-Price2021Dec07_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-06EqVega-Price2021Dec07_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-06EqVega-Price2021Dec06", "request_hash": "43642a3bd1d558d1e0a4197c502075f5", "type": "MockCalc", "tests": [ "test_add_scaled_action_nav_with_transaction_costs" ], "mocked_data": "[[[[{\"$type\":\"RiskVector\",\"asset\":[9.150956641405065],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":136.33227024891383}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[9.375839066009503],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":146.128150719897}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[9.308270163128327],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":139.6638341786846}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[9.364713983673719],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":148.6879474878436}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[9.280815108233655],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":133.0040505502546}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[9.44452859793062],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":152.16613197021397}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[9.322611019976902],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":126.26577286726146}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[9.450558948643739],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":154.42940273089567}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[8.9650452521228],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":247.1158307476799}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[9.219948721114196],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":175.3389997719548}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request443b19a6e8283a3bb5063918acfa3d6f.json ================================================ { "request_id": "1_trade_1-trade_2IRDeltatoday", "request_hash": "443b19a6e8283a3bb5063918acfa3d6f", "type": "MockCalc", "tests": [ "test_aggregation_with_identical_trades" ], "mocked_data": "[[[[{\"$type\": \"RiskVector\", \"asset\": [229.09247408065798, 2.8789520263671876e-06, 1715.0184191249848, -11.352838433456421, 1723.4493214931488, -21.151166704559326, 1582.0171100387574, -11.660831447219849, 1721.6388081504822, -0.4486804557800293, 46.241724896240235, -2.30587933921814, 0.004267978286743164, 0.0006296958923339844, -8.557510375976562e-05, 1.7349243164062501e-06, -124.18974158058167, 2.164459228515625e-06, -240.78980239791872, 3.490447998046875e-07, -97.98692370834351, 4.573822021484375e-07, -394.74861560783387, 3.8795471191406255e-06, -193.19905610961916, -2.5165557861328127e-06, -49540.849921316534, -223.3832664279938, -1.1246547832489013, -1.1264001190185546, -3.814697265625e-10, 0.0, -3.814697265625e-10, -3.814697265625e-10, 3.814697265625e-10, 0.0, 0.0, 0.0, 0.0, 0.0, -3.814697265625e-10, 3.814697265625e-10, 3.814697265625e-10, -3.814697265625e-10, -3.814697265625e-10, 0.0, 3.814697265625e-10, -3.814697265625e-10, -3.814697265625e-10, 0.0, -7.62939453125e-10, 0.0, 1.52587890625e-09, 0.0, 3.814697265625e-10, 0.0, -3.814697265625e-10, 3.814697265625e-10], \"points\": [{\"asset\": \"EUR\", \"class_\": \"CASH\", \"path\": \"\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 229.09247408065798}, {\"asset\": \"EUR\", \"class_\": \"CASH\", \"path\": \"\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 2.8789520263671876e-06}, {\"asset\": \"EUR\", \"class_\": \"FRA\", \"path\": \"\", \"point\": \"DEC23\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 1715.0184191249848}, {\"asset\": \"EUR\", \"class_\": \"FRA\", \"path\": \"\", \"point\": \"DEC24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -11.352838433456421}, {\"asset\": \"EUR\", \"class_\": \"FRA\", \"path\": \"\", \"point\": \"JUN23\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 1723.4493214931488}, {\"asset\": \"EUR\", \"class_\": \"FRA\", \"path\": \"\", \"point\": \"JUN24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -21.151166704559326}, {\"asset\": \"EUR\", \"class_\": \"FRA\", \"path\": \"\", \"point\": \"MAR24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 1582.0171100387574}, {\"asset\": \"EUR\", \"class_\": \"FRA\", \"path\": \"\", \"point\": \"MAR25\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -11.660831447219849}, {\"asset\": \"EUR\", \"class_\": \"FRA\", \"path\": \"\", \"point\": \"SEP23\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 1721.6388081504822}, {\"asset\": \"EUR\", \"class_\": \"FRA\", \"path\": \"\", \"point\": \"SEP24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -0.4486804557800293}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 46.241724896240235}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -2.30587933921814}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.004267978286743164}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0006296958923339844}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -8.557510375976562e-05}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 1.7349243164062501e-06}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -124.18974158058167}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 2.164459228515625e-06}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -240.78980239791872}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 3.490447998046875e-07}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -97.98692370834351}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"60Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 4.573822021484375e-07}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -394.74861560783387}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"70Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 3.8795471191406255e-06}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -193.19905610961916}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"80Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -2.5165557861328127e-06}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -49540.849921316534}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -223.3832664279938}, {\"asset\": \"USD\", \"class_\": \"CASH OIS\", \"path\": \"\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -1.1246547832489013}, {\"asset\": \"USD\", \"class_\": \"CASH OIS\", \"path\": \"\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -1.1264001190185546}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"DEC23\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -3.814697265625e-10}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"DEC24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"DEC25\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -3.814697265625e-10}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"JUN23\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -3.814697265625e-10}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"JUN24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 3.814697265625e-10}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"JUN25\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"MAR24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"MAR25\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"MAR26\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"SEP23\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"SEP24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -3.814697265625e-10}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"SEP25\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 3.814697265625e-10}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 3.814697265625e-10}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -3.814697265625e-10}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -3.814697265625e-10}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 3.814697265625e-10}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -3.814697265625e-10}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -3.814697265625e-10}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -7.62939453125e-10}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 1.52587890625e-09}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 3.814697265625e-10}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -3.814697265625e-10}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 3.814697265625e-10}], \"unit\": {\"Basis Point\": -1.0, \"United States Dollar\": 1.0}}], [{\"$type\": \"RiskVector\", \"asset\": [229.09247408065798, 2.8789520263671876e-06, 1715.0184191249848, -11.352838433456421, 1723.4493214931488, -21.151166704559326, 1582.0171100387574, -11.660831447219849, 1721.6388081504822, -0.4486804557800293, 46.241724896240235, -2.30587933921814, 0.004267978286743164, 0.0006296958923339844, -8.557510375976562e-05, 1.7349243164062501e-06, -124.18974158058167, 2.164459228515625e-06, -240.78980239791872, 3.490447998046875e-07, -97.98692370834351, 4.573822021484375e-07, -394.74861560783387, 3.8795471191406255e-06, -193.19905610961916, -2.5165557861328127e-06, -49540.849921316534, -223.3832664279938, -1.1246547832489013, -1.1264001190185546, -3.814697265625e-10, 0.0, -3.814697265625e-10, -3.814697265625e-10, 3.814697265625e-10, 0.0, 0.0, 0.0, 0.0, 0.0, -3.814697265625e-10, 3.814697265625e-10, 3.814697265625e-10, -3.814697265625e-10, -3.814697265625e-10, 0.0, 3.814697265625e-10, -3.814697265625e-10, -3.814697265625e-10, 0.0, -7.62939453125e-10, 0.0, 1.52587890625e-09, 0.0, 3.814697265625e-10, 0.0, -3.814697265625e-10, 3.814697265625e-10], \"points\": [{\"asset\": \"EUR\", \"class_\": \"CASH\", \"path\": \"\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 229.09247408065798}, {\"asset\": \"EUR\", \"class_\": \"CASH\", \"path\": \"\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 2.8789520263671876e-06}, {\"asset\": \"EUR\", \"class_\": \"FRA\", \"path\": \"\", \"point\": \"DEC23\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 1715.0184191249848}, {\"asset\": \"EUR\", \"class_\": \"FRA\", \"path\": \"\", \"point\": \"DEC24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -11.352838433456421}, {\"asset\": \"EUR\", \"class_\": \"FRA\", \"path\": \"\", \"point\": \"JUN23\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 1723.4493214931488}, {\"asset\": \"EUR\", \"class_\": \"FRA\", \"path\": \"\", \"point\": \"JUN24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -21.151166704559326}, {\"asset\": \"EUR\", \"class_\": \"FRA\", \"path\": \"\", \"point\": \"MAR24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 1582.0171100387574}, {\"asset\": \"EUR\", \"class_\": \"FRA\", \"path\": \"\", \"point\": \"MAR25\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -11.660831447219849}, {\"asset\": \"EUR\", \"class_\": \"FRA\", \"path\": \"\", \"point\": \"SEP23\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 1721.6388081504822}, {\"asset\": \"EUR\", \"class_\": \"FRA\", \"path\": \"\", \"point\": \"SEP24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -0.4486804557800293}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 46.241724896240235}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -2.30587933921814}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.004267978286743164}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0006296958923339844}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -8.557510375976562e-05}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 1.7349243164062501e-06}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -124.18974158058167}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 2.164459228515625e-06}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -240.78980239791872}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 3.490447998046875e-07}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -97.98692370834351}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"60Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 4.573822021484375e-07}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -394.74861560783387}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"70Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 3.8795471191406255e-06}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -193.19905610961916}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"80Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -2.5165557861328127e-06}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -49540.849921316534}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -223.3832664279938}, {\"asset\": \"USD\", \"class_\": \"CASH OIS\", \"path\": \"\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -1.1246547832489013}, {\"asset\": \"USD\", \"class_\": \"CASH OIS\", \"path\": \"\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -1.1264001190185546}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"DEC23\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -3.814697265625e-10}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"DEC24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"DEC25\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -3.814697265625e-10}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"JUN23\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -3.814697265625e-10}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"JUN24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 3.814697265625e-10}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"JUN25\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"MAR24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"MAR25\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"MAR26\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"SEP23\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"SEP24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -3.814697265625e-10}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"SEP25\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 3.814697265625e-10}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 3.814697265625e-10}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -3.814697265625e-10}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -3.814697265625e-10}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 3.814697265625e-10}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -3.814697265625e-10}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -3.814697265625e-10}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -7.62939453125e-10}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 1.52587890625e-09}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 3.814697265625e-10}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -3.814697265625e-10}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 3.814697265625e-10}], \"unit\": {\"Basis Point\": -1.0, \"United States Dollar\": 1.0}}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request44b307c6f73d7e32328511a4bca9361f.json ================================================ { "request_id": "1_5y-10yIRFwdRate-Pricetodaymultiscenario+roll fwd scenario", "request_hash": "44b307c6f73d7e32328511a4bca9361f", "type": "MockCalc", "tests": [ "test_composite_multi_scenario" ], "mocked_data": "[[[[{\"$type\": \"Table\", \"rows\": [{\"label\": \"IR Curve(parallel=5bp,slope=0bp,pivot=0y,upper cutoff=0y,lower cutoff=0y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"Rate\": 1.0}, \"val\": 0.031117445871837086, \"children\": {}}}, {\"label\": \"IR Curve(parallel=0bp,slope=0bp,pivot=18y,upper cutoff=30y,lower cutoff=5y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"Rate\": 1.0}, \"val\": 0.0305677986145676, \"children\": {}}}]}], [{\"$type\": \"Table\", \"rows\": [{\"label\": \"IR Curve(parallel=5bp,slope=0bp,pivot=0y,upper cutoff=0y,lower cutoff=0y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"Rate\": 1.0}, \"val\": 0.0303497805957149, \"children\": {}}}, {\"label\": \"IR Curve(parallel=0bp,slope=0bp,pivot=18y,upper cutoff=30y,lower cutoff=5y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"Rate\": 1.0}, \"val\": 0.029813237933559444, \"children\": {}}}]}]], [[{\"$type\": \"Table\", \"rows\": [{\"label\": \"IR Curve(parallel=5bp,slope=0bp,pivot=0y,upper cutoff=0y,lower cutoff=0y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 16691080.077102333, \"children\": {}}}, {\"label\": \"IR Curve(parallel=0bp,slope=0bp,pivot=18y,upper cutoff=30y,lower cutoff=5y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 16462908.996666588, \"children\": {}}}]}], [{\"$type\": \"Table\", \"rows\": [{\"label\": \"IR Curve(parallel=5bp,slope=0bp,pivot=0y,upper cutoff=0y,lower cutoff=0y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 30426820.569981538, \"children\": {}}}, {\"label\": \"IR Curve(parallel=0bp,slope=0bp,pivot=18y,upper cutoff=30y,lower cutoff=5y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 30049072.800937932, \"children\": {}}}]}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request456dd772d92da1c02ed5ca0d1b3400f9.json ================================================ { "request_id": "5_AddScaledTradeAction1_call_2021-12-10Price2021Dec10_AddScaledTradeAction1_call_2021-12-09Price2021Dec09_AddScaledTradeAction1_call_2021-12-08Price2021Dec08_AddScaledTradeAction1_call_2021-12-07Price2021Dec07_AddScaledTradeAction1_call_2021-12-06Price2021Dec06", "request_hash": "456dd772d92da1c02ed5ca0d1b3400f9", "type": "MockCalc", "tests": [ "test_add_scaled_trade_action_with_quantity_signal" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":3068.691165117837}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":1932.9433173419666}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":1978.1597156127816}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":2007.5822355016437}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":2279.4069970354126}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request46ded08f0698a95e461efdd890e29421.json ================================================ { "request_id": "2_5y-10yIRDelta-Price2020Jan15_5y-10yIRDelta-Price2020Jan14", "request_hash": "46ded08f0698a95e461efdd890e29421", "type": "MockCalc", "tests": [ "test_diff_types_risk_measures" ], "mocked_data": "[[[[{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -1667.2435386363984}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -4.070281982421875e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -2462.7084261684417}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -3846.405987095642}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -2488.7893071540834}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -2519.677621138382}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -2488.380242152786}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -2517.527723149872}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -2489.5775452148437}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -2143.756374926758}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 1468.3243078819276}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": -51.812072123718266}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 0.00475096549987793}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.00017612953186035157}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": -7.365074157714844e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -1.247406005859375e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -5174.5843409442905}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -9.689331054687501e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 1000.1658372463227}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 6.641387939453125e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 605.7532153579713}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"60Y\", \"quoteStyle\": \"\", \"value\": -2.288818359375e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -8465.067800811768}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"70Y\", \"quoteStyle\": \"\", \"value\": 2.3651123046875002e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 62627.19566747055}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"80Y\", \"quoteStyle\": \"\", \"value\": -1.232147216796875e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 33412.22667660065}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": -8079.8609189914705}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -0.9749629940032959}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -0.9778602062225342}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -2.7306365966796878e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 1.1444091796875001e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 2.9535293579101565e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 1.0018539428710938e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 0.0006735057830810547}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 1.0027313232421875e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -2.0742034912109376e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -9.925079345703126e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": -3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": -3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": -3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": -3.814697265625e-10}], \"asset\": [-1667.2435386363984, -4.070281982421875e-06, -2462.7084261684417, -3846.405987095642, -2488.7893071540834, -2519.677621138382, -2488.380242152786, -2517.527723149872, -2489.5775452148437, -2143.756374926758, 1468.3243078819276, -51.812072123718266, 0.00475096549987793, 0.00017612953186035157, -7.365074157714844e-05, -1.247406005859375e-07, -5174.5843409442905, -9.689331054687501e-08, 1000.1658372463227, 6.641387939453125e-07, 605.7532153579713, -2.288818359375e-08, -8465.067800811768, 2.3651123046875002e-08, 62627.19566747055, -1.232147216796875e-07, 33412.22667660065, -8079.8609189914705, -0.9749629940032959, -0.9778602062225342, -2.7306365966796878e-05, 1.1444091796875001e-09, 2.9535293579101565e-05, 1.0018539428710938e-05, 0.0006735057830810547, 1.0027313232421875e-05, -2.0742034912109376e-05, -9.925079345703126e-06, 0.0, 3.814697265625e-10, -3.814697265625e-10, 3.814697265625e-10, 0.0, 0.0, -3.814697265625e-10, -7.62939453125e-10, 0.0, 0.0, 0.0, -3.814697265625e-10, -7.62939453125e-10, 3.814697265625e-10, 3.814697265625e-10, 7.62939453125e-10, -3.814697265625e-10], \"calculationTime\": 2672, \"queueingTime\": -146}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -1678.8433957893371}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -4.8713684082031255e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -2474.410205470276}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -3846.011891560364}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -2506.0381377838135}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -2529.1688358398437}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -2505.699619178009}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -2526.3692599868778}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -2506.79551877594}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -2155.1359160346988}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": -21468.502088909914}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 139139.37289584122}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 12231.370052542878}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -1991.4383884399415}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 407.6922475692749}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -34.1978889251709}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -5276.816720574188}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -0.17461036682128908}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 997.7741626136781}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 0.02245984573364258}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -1009.4126821357728}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"60Y\", \"quoteStyle\": \"\", \"value\": -0.001241937255859375}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -141.2341786392212}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"70Y\", \"quoteStyle\": \"\", \"value\": -5.01190185546875e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": -882.8048536857606}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"80Y\", \"quoteStyle\": \"\", \"value\": 3.5079956054687503e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": -1257.728958581543}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 6768.944076137543}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -2.8821612434387207}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -2.890725910949707}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -8.072128295898438e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 8.731155395507813e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 2.961578369140625e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 0.0019910003662109376}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 2.9642486572265625e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -6.131668090820313e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -2.934112548828125e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": -1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": -1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -2.2888183593750002e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -2.2888183593750002e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 2.2888183593750002e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}], \"asset\": [-1678.8433957893371, -4.8713684082031255e-06, -2474.410205470276, -3846.011891560364, -2506.0381377838135, -2529.1688358398437, -2505.699619178009, -2526.3692599868778, -2506.79551877594, -2155.1359160346988, -21468.502088909914, 139139.37289584122, 12231.370052542878, -1991.4383884399415, 407.6922475692749, -34.1978889251709, -5276.816720574188, -0.17461036682128908, 997.7741626136781, 0.02245984573364258, -1009.4126821357728, -0.001241937255859375, -141.2341786392212, -5.01190185546875e-05, -882.8048536857606, 3.5079956054687503e-06, -1257.728958581543, 6768.944076137543, -2.8821612434387207, -2.890725910949707, -8.072128295898438e-05, 3.0517578125e-09, 8.731155395507813e-05, 2.961578369140625e-05, 0.0019910003662109376, 2.9642486572265625e-05, -6.131668090820313e-05, -2.934112548828125e-05, 0.0, 1.52587890625e-09, -1.52587890625e-09, 1.52587890625e-09, 0.0, 0.0, -1.52587890625e-09, -2.2888183593750002e-09, 7.62939453125e-10, 0.0, 0.0, -7.62939453125e-10, -2.2888183593750002e-09, 7.62939453125e-10, 7.62939453125e-10, 2.2888183593750002e-09, -7.62939453125e-10], \"calculationTime\": 2672, \"queueingTime\": -146}]], [[{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 3155308.4756011004, \"children\": {}, \"calculationTime\": 42, \"queueingTime\": -146}], [{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 9328091.861150585, \"children\": {}, \"calculationTime\": 42, \"queueingTime\": -146}]]], [[[{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -1690.4274549369813}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -4.1370391845703126e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -2431.593033273697}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -3786.9843693626403}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -2482.780788569641}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -2595.327604275513}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -2482.307131084442}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -2469.640842456055}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -2483.5299607940674}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -2166.135343655777}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 1478.9622806194307}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": -51.95791416511536}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": -0.03469709815979004}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.0002544200897216797}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": -8.063964843750001e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -1.4389038085937501e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -5157.727432756425}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -1.865386962890625e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 990.278335987854}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 5.889892578125e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 736.5468615589142}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"60Y\", \"quoteStyle\": \"\", \"value\": -1.25885009765625e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -8696.432763245773}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"70Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 62440.1311512455}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"80Y\", \"quoteStyle\": \"\", \"value\": -1.461029052734375e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 33411.28173765144}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": -8116.772429734802}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -1.0129762042999269}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -1.0167831504821778}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -0.00028923263549804687}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 1.9073486328125e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 1.0453033447265626e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 1.0632324218750001e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 0.00028649139404296876}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 8.663177490234375e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": 0.00011826019287109375}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -1.0531234741210937e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": -3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": -3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": -3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [-1690.4274549369813, -4.1370391845703126e-06, -2431.593033273697, -3786.9843693626403, -2482.780788569641, -2595.327604275513, -2482.307131084442, -2469.640842456055, -2483.5299607940674, -2166.135343655777, 1478.9622806194307, -51.95791416511536, -0.03469709815979004, 0.0002544200897216797, -8.063964843750001e-05, -1.4389038085937501e-06, -5157.727432756425, -1.865386962890625e-07, 990.278335987854, 5.889892578125e-07, 736.5468615589142, -1.25885009765625e-08, -8696.432763245773, 1.52587890625e-08, 62440.1311512455, -1.461029052734375e-07, 33411.28173765144, -8116.772429734802, -1.0129762042999269, -1.0167831504821778, -0.00028923263549804687, 1.9073486328125e-09, 1.0453033447265626e-05, 1.0632324218750001e-05, 0.00028649139404296876, 8.663177490234375e-05, 0.00011826019287109375, -1.0531234741210937e-05, 0.0, 0.0, 3.814697265625e-10, 0.0, 0.0, 0.0, 0.0, -3.814697265625e-10, 0.0, -3.814697265625e-10, -3.814697265625e-10, 0.0, 3.814697265625e-10, 0.0, 0.0, -3.814697265625e-10, 0.0], \"calculationTime\": 0, \"queueingTime\": 4580}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -1702.1810805397035}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -5.011749267578125e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -2444.868805497742}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -3789.494718639374}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -2499.9662538970947}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -2599.2179568969727}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -2499.5690949165346}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -2481.1379633499146}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -2500.6865667884827}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -2175.6149885375976}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": -22053.57870413742}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 138585.68797151643}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 12498.341227219391}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -2039.943051603699}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 418.2003437355042}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -35.16492511367798}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -5264.257787085724}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -0.2032023864746094}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 991.6761070083619}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 0.023092861938476563}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -873.0462442237855}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"60Y\", \"quoteStyle\": \"\", \"value\": -0.0007514205932617188}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -619.1557262870789}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"70Y\", \"quoteStyle\": \"\", \"value\": -5.632858276367188e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": -348.2752563781738}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"80Y\", \"quoteStyle\": \"\", \"value\": 3.467559814453125e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": -1793.3002309722901}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 7300.043917005158}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -2.951691698455811}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -2.962784684753418}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -0.0008427909851074219}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 5.340576171875e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 3.0458831787109376e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 3.098297119140625e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 0.0008348014831542969}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 0.0002524337768554688}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": 0.00034459686279296876}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -3.0686187744140625e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [-1702.1810805397035, -5.011749267578125e-06, -2444.868805497742, -3789.494718639374, -2499.9662538970947, -2599.2179568969727, -2499.5690949165346, -2481.1379633499146, -2500.6865667884827, -2175.6149885375976, -22053.57870413742, 138585.68797151643, 12498.341227219391, -2039.943051603699, 418.2003437355042, -35.16492511367798, -5264.257787085724, -0.2032023864746094, 991.6761070083619, 0.023092861938476563, -873.0462442237855, -0.0007514205932617188, -619.1557262870789, -5.632858276367188e-05, -348.2752563781738, 3.467559814453125e-06, -1793.3002309722901, 7300.043917005158, -2.951691698455811, -2.962784684753418, -0.0008427909851074219, 5.340576171875e-09, 3.0458831787109376e-05, 3.098297119140625e-05, 0.0008348014831542969, 0.0002524337768554688, 0.00034459686279296876, -3.0686187744140625e-05, 0.0, 0.0, 1.52587890625e-09, 0.0, 0.0, 0.0, 0.0, -1.52587890625e-09, 0.0, -7.62939453125e-10, -7.62939453125e-10, 0.0, 1.52587890625e-09, 0.0, 0.0, -7.62939453125e-10, 0.0], \"calculationTime\": 0, \"queueingTime\": 4580}]], [[{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 3290317.2121213092, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 4580}], [{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 9587809.11540649, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 4580}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request472fd6073bc51283417f418843277e18.json ================================================ { "request_id": "4_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-09Price2021Dec10_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-08Price2021Dec09_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-07Price2021Dec08_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-06Price2021Dec07", "request_hash": "472fd6073bc51283417f418843277e18", "type": "MockCalc", "tests": [ "test_add_scaled_action_nav_with_transaction_costs" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":88.79973154828899}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":96.53735749867951}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":112.44563094452069}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":139.52667276440891}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request4a0c2426dcbba307e6747466921d0ebb.json ================================================ { "request_id": "1_swap1-swap2-swap3ResolvedInstrumentValues2020Oct14", "request_hash": "4a0c2426dcbba307e6747466921d0ebb", "type": "MockCalc", "tests": [ "test_results_with_resolution" ], "mocked_data": "[[[[{\"payOrReceive\": \"Pay\", \"terminationDate\": \"2030-10-16\", \"notionalCurrency\": \"USD\", \"effectiveDate\": \"2020-10-16\", \"principalExchange\": \"None\", \"floatingRateOption\": \"USD-LIBOR-BBA\", \"floatingRateDesignatedMaturity\": \"3m\", \"floatingRateFrequency\": \"3m\", \"floatingRateDayCountFraction\": \"ACT/360\", \"floatingRateBusinessDayConvention\": \"Modified Following\", \"fixedRateFrequency\": \"6m\", \"fixedRateDayCountFraction\": \"30/360\", \"fixedRateBusinessDayConvention\": \"Modified Following\", \"fee\": 0.0, \"feeCurrency\": \"USD\", \"feePaymentDate\": \"2020-10-16\", \"fixedFirstStub\": \"2020-10-16\", \"floatingFirstStub\": \"2020-10-16\", \"fixedLastStub\": \"2030-10-16\", \"floatingLastStub\": \"2030-10-16\", \"fixedHolidays\": \"New York,London\", \"floatingHolidays\": \"New York,London\", \"rollConvention\": \"Default\", \"assetClass\": \"Rates\", \"type\": \"Swap\", \"notionalAmount\": 100000000.0, \"fixedRate\": 0.007547018046590547, \"floatingRateSpread\": 0.0, \"$type\": \"LegDefinition\", \"calculationTime\": 11720, \"queueingTime\": -21334}], [{\"payOrReceive\": \"Pay\", \"terminationDate\": \"2030-10-14\", \"notionalCurrency\": \"GBP\", \"effectiveDate\": \"2020-10-14\", \"principalExchange\": \"None\", \"floatingRateOption\": \"GBP-LIBOR-BBA\", \"floatingRateDesignatedMaturity\": \"6m\", \"floatingRateFrequency\": \"6m\", \"floatingRateBusinessDayConvention\": \"Modified Following\", \"fixedRateFrequency\": \"6m\", \"fixedRateBusinessDayConvention\": \"Modified Following\", \"fee\": 0.0, \"feeCurrency\": \"GBP\", \"feePaymentDate\": \"2020-10-16\", \"fixedFirstStub\": \"2020-10-14\", \"floatingFirstStub\": \"2020-10-14\", \"fixedLastStub\": \"2030-10-14\", \"floatingLastStub\": \"2030-10-14\", \"fixedHolidays\": \"London\", \"floatingHolidays\": \"London\", \"rollConvention\": \"Default\", \"assetClass\": \"Rates\", \"type\": \"Swap\", \"notionalAmount\": 100000000.0, \"fixedRate\": 0.003991738063997746, \"floatingRateSpread\": 0.0, \"$type\": \"LegDefinition\", \"calculationTime\": 10682, \"queueingTime\": -21334}], [{\"payOrReceive\": \"Pay\", \"terminationDate\": \"2030-10-16\", \"notionalCurrency\": \"EUR\", \"effectiveDate\": \"2020-10-16\", \"principalExchange\": \"None\", \"floatingRateOption\": \"EUR-EURIBOR-Telerate\", \"floatingRateDesignatedMaturity\": \"6m\", \"floatingRateFrequency\": \"6m\", \"floatingRateDayCountFraction\": \"ACT/360\", \"floatingRateBusinessDayConvention\": \"Modified Following\", \"fixedRateFrequency\": \"1y\", \"fixedRateDayCountFraction\": \"30/360\", \"fixedRateBusinessDayConvention\": \"Modified Following\", \"fee\": 0.0, \"feeCurrency\": \"EUR\", \"feePaymentDate\": \"2020-10-16\", \"fixedFirstStub\": \"2020-10-16\", \"floatingFirstStub\": \"2020-10-16\", \"fixedLastStub\": \"2030-10-16\", \"floatingLastStub\": \"2030-10-16\", \"fixedHolidays\": \"Target\", \"floatingHolidays\": \"Target\", \"rollConvention\": \"Default\", \"assetClass\": \"Rates\", \"type\": \"Swap\", \"notionalAmount\": 100000000.0, \"fixedRate\": -0.0026700151512821816, \"floatingRateSpread\": 0.0, \"$type\": \"LegDefinition\", \"calculationTime\": 10987, \"queueingTime\": -21334}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request4be739b5768d699cb118e94e9c73a7d6.json ================================================ { "request_id": "3_swap1-swap2-swap3DollarPrice2021Feb11btf_swap1-swap2-swap3DollarPrice2021Feb10_swap1-swap2-swap3DollarPrice2021Feb09", "request_hash": "4be739b5768d699cb118e94e9c73a7d6", "type": "MockCalc", "tests": [ "test_backtothefuture_pricing" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"currency\": null, \"unit\": {\"United States Dollar\": 1.0}, \"dataQuality\": null, \"dataQualityMessages\": null, \"marketUpdateTime\": null, \"val\": 2472459.3385830466, \"children\": {}}], [{\"$type\": \"Risk\", \"currency\": null, \"unit\": {\"United States Dollar\": 1.0}, \"dataQuality\": null, \"dataQualityMessages\": null, \"marketUpdateTime\": null, \"val\": -7194183.863075359, \"children\": {}}], [{\"$type\": \"Risk\", \"currency\": null, \"unit\": {\"United States Dollar\": 1.0}, \"dataQuality\": null, \"dataQualityMessages\": null, \"marketUpdateTime\": null, \"val\": -16860827.064733766, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"currency\": null, \"unit\": {\"United States Dollar\": 1.0}, \"dataQuality\": null, \"dataQualityMessages\": null, \"marketUpdateTime\": null, \"val\": 2120920.8425061833, \"children\": {}}], [{\"$type\": \"Risk\", \"currency\": null, \"unit\": {\"United States Dollar\": 1.0}, \"dataQuality\": null, \"dataQualityMessages\": null, \"marketUpdateTime\": null, \"val\": -7551969.310161518, \"children\": {}}], [{\"$type\": \"Risk\", \"currency\": null, \"unit\": {\"United States Dollar\": 1.0}, \"dataQuality\": null, \"dataQualityMessages\": null, \"marketUpdateTime\": null, \"val\": -17224859.462829217, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"currency\": null, \"unit\": {\"United States Dollar\": 1.0}, \"dataQuality\": null, \"dataQualityMessages\": null, \"marketUpdateTime\": null, \"val\": 2101291.8889247905, \"children\": {}}], [{\"$type\": \"Risk\", \"currency\": null, \"unit\": {\"United States Dollar\": 1.0}, \"dataQuality\": null, \"dataQualityMessages\": null, \"marketUpdateTime\": null, \"val\": -7570654.602882478, \"children\": {}}], [{\"$type\": \"Risk\", \"currency\": null, \"unit\": {\"United States Dollar\": 1.0}, \"dataQuality\": null, \"dataQualityMessages\": null, \"marketUpdateTime\": null, \"val\": -17242601.09468975, \"children\": {}}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request4cc7e77d411ee5d32b4b68c559fd24eb.json ================================================ { "request_id": "3_Action1_swapResolvedInstrumentValues2021Dec10_Action1_swapResolvedInstrumentValues2021Dec08_Action1_swapResolvedInstrumentValues2021Dec06", "request_hash": "4cc7e77d411ee5d32b4b68c559fd24eb", "type": "MockCalc", "tests": [ "test_exit_action_emptyresults" ], "mocked_data": "[[[[{\"payOrReceive\": \"Receive\", \"terminationDate\": \"2031-12-14\", \"notionalCurrency\": \"USD\", \"effectiveDate\": \"2021-12-14\", \"principalExchange\": \"None\", \"floatingRateOption\": \"USD-LIBOR-BBA\", \"floatingRateDesignatedMaturity\": \"3m\", \"floatingRateFrequency\": \"3m\", \"floatingRateDayCountFraction\": \"ACT/360\", \"floatingRateBusinessDayConvention\": \"Modified Following\", \"fixedRateFrequency\": \"6m\", \"fixedRateDayCountFraction\": \"30/360\", \"fixedRateBusinessDayConvention\": \"Modified Following\", \"fee\": 0.0, \"feeCurrency\": \"USD\", \"feePaymentDate\": \"2021-12-14\", \"fixedFirstStub\": \"2021-12-14\", \"floatingFirstStub\": \"2021-12-14\", \"fixedLastStub\": \"2031-12-14\", \"floatingLastStub\": \"2031-12-14\", \"fixedHolidays\": \"New York,London\", \"floatingHolidays\": \"New York,London\", \"rollConvention\": \"Default\", \"assetClass\": \"Rates\", \"type\": \"Swap\", \"notionalAmount\": 100000.0, \"fixedRate\": 0.015378184493269978, \"floatingRateSpread\": 0.0, \"$type\": \"LegDefinition\"}]]], [[[{\"payOrReceive\": \"Receive\", \"terminationDate\": \"2031-12-10\", \"notionalCurrency\": \"USD\", \"effectiveDate\": \"2021-12-10\", \"principalExchange\": \"None\", \"floatingRateOption\": \"USD-LIBOR-BBA\", \"floatingRateDesignatedMaturity\": \"3m\", \"floatingRateFrequency\": \"3m\", \"floatingRateDayCountFraction\": \"ACT/360\", \"floatingRateBusinessDayConvention\": \"Modified Following\", \"fixedRateFrequency\": \"6m\", \"fixedRateDayCountFraction\": \"30/360\", \"fixedRateBusinessDayConvention\": \"Modified Following\", \"fee\": 0.0, \"feeCurrency\": \"USD\", \"feePaymentDate\": \"2021-12-10\", \"fixedFirstStub\": \"2021-12-10\", \"floatingFirstStub\": \"2021-12-10\", \"fixedLastStub\": \"2031-12-10\", \"floatingLastStub\": \"2031-12-10\", \"fixedHolidays\": \"New York,London\", \"floatingHolidays\": \"New York,London\", \"rollConvention\": \"Default\", \"assetClass\": \"Rates\", \"type\": \"Swap\", \"notionalAmount\": 100000.0, \"fixedRate\": 0.0160927871227299, \"floatingRateSpread\": 0.0, \"$type\": \"LegDefinition\"}]]], [[[{\"payOrReceive\": \"Receive\", \"terminationDate\": \"2031-12-08\", \"notionalCurrency\": \"USD\", \"effectiveDate\": \"2021-12-08\", \"principalExchange\": \"None\", \"floatingRateOption\": \"USD-LIBOR-BBA\", \"floatingRateDesignatedMaturity\": \"3m\", \"floatingRateFrequency\": \"3m\", \"floatingRateDayCountFraction\": \"ACT/360\", \"floatingRateBusinessDayConvention\": \"Modified Following\", \"fixedRateFrequency\": \"6m\", \"fixedRateDayCountFraction\": \"30/360\", \"fixedRateBusinessDayConvention\": \"Modified Following\", \"fee\": 0.0, \"feeCurrency\": \"USD\", \"feePaymentDate\": \"2021-12-08\", \"fixedFirstStub\": \"2021-12-08\", \"floatingFirstStub\": \"2021-12-08\", \"fixedLastStub\": \"2031-12-08\", \"floatingLastStub\": \"2031-12-08\", \"fixedHolidays\": \"New York,London\", \"floatingHolidays\": \"New York,London\", \"rollConvention\": \"Default\", \"assetClass\": \"Rates\", \"type\": \"Swap\", \"notionalAmount\": 100000.0, \"fixedRate\": 0.014959291321079982, \"floatingRateSpread\": 0.0, \"$type\": \"LegDefinition\"}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request4d50dbaed5c59d9f8ed4b824a5fa7b4c.json ================================================ { "request_id": "5_QuantityScaledAction2_TransactionAggType.MIN_callResolvedInstrumentValues2021Dec10_QuantityScaledAction2_TransactionAggType.MIN_callResolvedInstrumentValues2021Dec09_QuantityScaledAction2_TransactionAggType.MIN_callResolvedInstrumentValues2021Dec08_QuantityScaledAction2_TransactionAggType.MIN_callResolvedInstrumentValues2021Dec07_QuantityScaledAction2_TransactionAggType.MIN_callResolvedInstrumentValues2021Dec06", "request_hash": "4d50dbaed5c59d9f8ed4b824a5fa7b4c", "type": "MockCalc", "tests": [ "test_add_scaled_action_nav_with_transaction_costs" ], "mocked_data": "[[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-03-10\",\"expirySettleDate\":\"2022-03-14\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-14\",\"premiumSettlementDate\":\"2021-12-14\",\"settlementDate\":\"2021-12-14\",\"strikePrice\":4199.16,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-03-09\",\"expirySettleDate\":\"2022-03-11\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-13\",\"premiumSettlementDate\":\"2021-12-13\",\"settlementDate\":\"2021-12-13\",\"strikePrice\":4208.3,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-03-08\",\"expirySettleDate\":\"2022-03-10\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-10\",\"premiumSettlementDate\":\"2021-12-10\",\"settlementDate\":\"2021-12-10\",\"strikePrice\":4233.09,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-03-07\",\"expirySettleDate\":\"2022-03-09\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-09\",\"premiumSettlementDate\":\"2021-12-09\",\"settlementDate\":\"2021-12-09\",\"strikePrice\":4276.2,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-03-07\",\"expirySettleDate\":\"2022-03-09\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-08\",\"premiumSettlementDate\":\"2021-12-08\",\"settlementDate\":\"2021-12-08\",\"strikePrice\":4137.11,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request4e071d86397b95a0b0ad52aebb6b2e03.json ================================================ { "request_id": "1_5y-10yDollarPrice-Price2020Jan14", "request_hash": "4e071d86397b95a0b0ad52aebb6b2e03", "type": "MockCalc", "tests": [ "test_one_portfolio" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 3660454.298656369, \"children\": {}, \"calculationTime\": 20, \"queueingTime\": 20}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 10666368.872246133, \"children\": {}, \"calculationTime\": 20, \"queueingTime\": 20}]], [[{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 3290317.2121213092, \"children\": {}, \"calculationTime\": 20, \"queueingTime\": 20}], [{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 9587809.11540649, \"children\": {}, \"calculationTime\": 20, \"queueingTime\": 20}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request4fbbe5f6fe580bd5e55bbd60e8302c07.json ================================================ { "request_id": "15_Action1_GBP1y_2023-06-09Price2023Jun09_Action1_GBP1y_2023-06-08Price2023Jun09_Action1_GBP1y_2023-06-08Price2023Jun08_Action1_GBP1y_2023-06-07Price2023Jun09_Action1_GBP1y_2023-06-07Price2023Jun08_Action1_GBP1y_2023-06-07Price2023Jun07_Action1_GBP1y_2023-06-06Price2023Jun09_Action1_GBP1y_2023-06-06Price2023Jun08_Action1_GBP1y_2023-06-06Price2023Jun07_Action1_GBP1y_2023-06-06Price2023Jun06_Action1_GBP1y_2023-06-05Price2023Jun09_Action1_GBP1y_2023-06-05Price2023Jun08_Action1_GBP1y_2023-06-05Price2023Jun07_Action1_GBP1y_2023-06-05Price2023Jun06_Action1_GBP1y_2023-06-05Price2023Jun05", "request_hash": "4fbbe5f6fe580bd5e55bbd60e8302c07", "type": "MockCalc", "tests": [ "test_risk_scaled_transaction_cost" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":-4.547473508864641e-13}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":-9.40221064862044}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":0.0}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":1.9829239250811952}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":11.282068461530798}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":0.0}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":-15.792957429471244}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":-6.528814414079534}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":-17.75126222317931}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":-4.547473508864641e-13}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":-12.722407343208033}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":-3.491930985061117}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":-14.66196896423844}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":2.999120549381132}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"British Pound Sterling\":1.0},\"val\":-4.547473508864641e-13}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request5010511210568f2494e7d715ddca3fe5.json ================================================ { "request_id": "3_Action1_swap1_2021-12-10-Action1_swap1_2021-12-09Price2021Dec10_Action1_swap1_2021-12-08-Action1_swap1_2021-12-07Price2021Dec08_Action1_swap1_2021-12-06Price2021Dec06", "request_hash": "5010511210568f2494e7d715ddca3fe5", "type": "MockCalc", "tests": [ "test_exit_action_bytradename" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":0.0}],[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":75.69557051901756}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":-1.8189894035458565e-12}],[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":-646.199254763831}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":3.637978807091713e-12}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request535b30f63a7e89e8fa2e17fff4a92b2f.json ================================================ { "request_id": "1_5y-10y-5y-10y-5y-10y-5y-10yDollarPrice-Price2020Jan14", "request_hash": "535b30f63a7e89e8fa2e17fff4a92b2f", "type": "MockCalc", "tests": [ "test_nested_portfolio" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 3660454.298656369, \"children\": {}, \"calculationTime\": 34, \"queueingTime\": -616}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 10666368.872246133, \"children\": {}, \"calculationTime\": 34, \"queueingTime\": -616}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 7342905.369624435, \"children\": {}, \"calculationTime\": 23, \"queueingTime\": -616}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 15779322.606637968, \"children\": {}, \"calculationTime\": 23, \"queueingTime\": -616}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 20514.413462639244, \"children\": {}, \"calculationTime\": 21, \"queueingTime\": -616}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 48808.79806217953, \"children\": {}, \"calculationTime\": 21, \"queueingTime\": -616}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 9321685.065826869, \"children\": {}, \"calculationTime\": 36, \"queueingTime\": -616}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 19280407.561757438, \"children\": {}, \"calculationTime\": 36, \"queueingTime\": -616}]], [[{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 3290317.2121213092, \"children\": {}, \"calculationTime\": 34, \"queueingTime\": -616}], [{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 9587809.11540649, \"children\": {}, \"calculationTime\": 34, \"queueingTime\": -616}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 5644562.7450038865, \"children\": {}, \"calculationTime\": 23, \"queueingTime\": -616}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 12129718.693539664, \"children\": {}, \"calculationTime\": 23, \"queueingTime\": -616}], [{\"$type\": \"Risk\", \"unit\": {\"Japanese Yen\": 1.0}, \"val\": 2257946.840053587, \"children\": {}, \"calculationTime\": 21, \"queueingTime\": -616}], [{\"$type\": \"Risk\", \"unit\": {\"Japanese Yen\": 1.0}, \"val\": 5372206.792654425, \"children\": {}, \"calculationTime\": 21, \"queueingTime\": -616}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 9321685.065826869, \"children\": {}, \"calculationTime\": 36, \"queueingTime\": -616}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 19280407.561757438, \"children\": {}, \"calculationTime\": 36, \"queueingTime\": -616}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request53ced6371873432280aa8489c1b45f21.json ================================================ { "request_id": "3_Scaled_test_hedge_without_risk_2y_forward_2021-12-03_HedgeAction1_Priceable0FXDelta(aggregation_level:Type)-Price2021Dec03_Scaled_test_hedge_without_risk_2y_forward_2021-12-02_HedgeAction1_Priceable0FXDelta(aggregation_level:Type)-Price2021Dec02_Scaled_test_hedge_without_risk_2y_forward_2021-12-01_HedgeAction1_Priceable0FXDelta(aggregation_level:Type)-Price2021Dec01", "request_hash": "53ced6371873432280aa8489c1b45f21", "type": "MockCalc", "tests": [ "test_hedge_without_risk" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"children\":{},\"val\":-462.9241293150699}]],[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":1.862645149230957e-9}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"val\":-0.5291989737088443}]],[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":0.0}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"val\":-463.0912266293308}]],[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":9.313225746154785e-10}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request5678213359b65da6a1851eeaa706ff24.json ================================================ { "request_id": "10_Action1_GBP1y_2023-06-09IRDelta(aggregation_level:Type)2023Jul10_Action1_GBP1y_2023-06-09IRDelta(aggregation_level:Type)2023Jun09_Action1_GBP1y_2023-06-08IRDelta(aggregation_level:Type)2023Jul10_Action1_GBP1y_2023-06-08IRDelta(aggregation_level:Type)2023Jun08_Action1_GBP1y_2023-06-07IRDelta(aggregation_level:Type)2023Jul07_Action1_GBP1y_2023-06-07IRDelta(aggregation_level:Type)2023Jun07_Action1_GBP1y_2023-06-06IRDelta(aggregation_level:Type)2023Jul06_Action1_GBP1y_2023-06-06IRDelta(aggregation_level:Type)2023Jun06_Action1_GBP1y_2023-06-05IRDelta(aggregation_level:Type)2023Jul05_Action1_GBP1y_2023-06-05IRDelta(aggregation_level:Type)2023Jun05", "request_hash": "5678213359b65da6a1851eeaa706ff24", "type": "MockCalc", "tests": [ "test_risk_scaled_transaction_cost" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Basis Point\":-1.0,\"United States Dollar\":1.0},\"val\":-5.786457909828378}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Basis Point\":-1.0,\"United States Dollar\":1.0},\"val\":-6.195750035016184}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Basis Point\":-1.0,\"United States Dollar\":1.0},\"val\":-5.7860683600146325}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Basis Point\":-1.0,\"United States Dollar\":1.0},\"val\":-6.225115786250771}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Basis Point\":-1.0,\"United States Dollar\":1.0},\"val\":-5.749089966770727}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Basis Point\":-1.0,\"United States Dollar\":1.0},\"val\":-6.149968425756771}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Basis Point\":-1.0,\"United States Dollar\":1.0},\"val\":-5.719841404833272}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Basis Point\":-1.0,\"United States Dollar\":1.0},\"val\":-6.125841235537731}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Basis Point\":-1.0,\"United States Dollar\":1.0},\"val\":-5.735616599677713}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Basis Point\":-1.0,\"United States Dollar\":1.0},\"val\":-6.130257208007335}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request5711e73d9fdfbee2d67781cd43875f26.json ================================================ { "request_id": "1_5yIRDelta(aggregation_level:Asset)today", "request_hash": "5711e73d9fdfbee2d67781cd43875f26", "type": "MockCalc", "tests": [ "test_finite_difference_params" ], "mocked_data": "[[[[{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR\", \"asset\": \"EUR\", \"value\": 47977.94611100083}, {\"type\": \"IR\", \"asset\": \"USD\", \"value\": -5.116586968231202}], \"values\": [47977.94611100083, -5.116586968231202], \"calculationTime\": 1656, \"queueingTime\": 16}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request58c9a938f95c4444a0923d7c4b80aedf.json ================================================ { "request_id": "1_5y-10y-5y-10yDollarPrice-Price2020Jan14", "request_hash": "58c9a938f95c4444a0923d7c4b80aedf", "type": "MockCalc", "tests": [ "test_dated_risk_values", "test_nested_portfolio", "test_portfolio_overrides" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 3660454.298656369, \"children\": {}, \"calculationTime\": 21, \"queueingTime\": -155}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 10666368.872246133, \"children\": {}, \"calculationTime\": 21, \"queueingTime\": -155}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 7342905.369624435, \"children\": {}, \"calculationTime\": 23, \"queueingTime\": -155}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 15779322.606637968, \"children\": {}, \"calculationTime\": 23, \"queueingTime\": -155}]], [[{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 3290317.2121213092, \"children\": {}, \"calculationTime\": 21, \"queueingTime\": -155}], [{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 9587809.11540649, \"children\": {}, \"calculationTime\": 21, \"queueingTime\": -155}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 5644562.7450038865, \"children\": {}, \"calculationTime\": 23, \"queueingTime\": -155}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 12129718.693539664, \"children\": {}, \"calculationTime\": 23, \"queueingTime\": -155}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request5961d985671cd44ae28d46079a078af2.json ================================================ { "request_id": "10_QuantityScaledAction1_call_2021-12-10Price2021Dec13_QuantityScaledAction1_call_2021-12-10Price2021Dec10_QuantityScaledAction1_call_2021-12-09Price2021Dec10_QuantityScaledAction1_call_2021-12-09Price2021Dec09_QuantityScaledAction1_call_2021-12-08Price2021Dec09_QuantityScaledAction1_call_2021-12-08Price2021Dec08_QuantityScaledAction1_call_2021-12-07Price2021Dec08_QuantityScaledAction1_call_2021-12-07Price2021Dec07_QuantityScaledAction1_call_2021-12-06Price2021Dec07_QuantityScaledAction1_call_2021-12-06Price2021Dec06", "request_hash": "5961d985671cd44ae28d46079a078af2", "type": "MockCalc", "tests": [ "test_add_scaled_action_nav" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":66.55739980883622}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":78.89117688336509}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":73.40843195462492}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":85.44379354234701}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":70.93085789588493}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":89.33151346209694}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":62.00400617748216}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":87.93080662608884}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":186.53428843750459}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":107.48842748850868}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request5c61457b1908e376ae7737adc2e3b19c.json ================================================ { "request_id": "2_1y-3m-6m-5y-10yIRVega(aggregation_level:Asset)-Price2020Jan15_1y-3m-6m-5y-10yIRVega(aggregation_level:Asset)-Price2020Jan14", "request_hash": "5c61457b1908e376ae7737adc2e3b19c", "type": "MockCalc", "tests": [ "test_empty_calc_request" ], "mocked_data": "[[[[{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 18942.916708309745}], \"values\": [18942.916708309745], \"calculationTime\": 80, \"queueingTime\": -571}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 9546.7347591115}], \"values\": [9546.7347591115], \"calculationTime\": 80, \"queueingTime\": -571}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 13453.962925416756}], \"values\": [13453.962925416756], \"calculationTime\": 80, \"queueingTime\": -571}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 0.0}], \"values\": [0.0], \"calculationTime\": 80, \"queueingTime\": -571}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 0.0}], \"values\": [0.0], \"calculationTime\": 80, \"queueingTime\": -571}]], [[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 1162329.9377910192, \"children\": {}, \"calculationTime\": 27, \"queueingTime\": -571}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 542174.0278393007, \"children\": {}, \"calculationTime\": 27, \"queueingTime\": -571}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 790971.9423228333, \"children\": {}, \"calculationTime\": 27, \"queueingTime\": -571}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 9253581.340032147, \"children\": {}, \"calculationTime\": 27, \"queueingTime\": -571}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 19104807.27238474, \"children\": {}, \"calculationTime\": 27, \"queueingTime\": -571}]]], [[[{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 18951.379622470093}], \"values\": [18951.379622470093], \"calculationTime\": 96, \"queueingTime\": -384}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 9546.483379703379}], \"values\": [9546.483379703379], \"calculationTime\": 96, \"queueingTime\": -384}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 13453.388908411027}], \"values\": [13453.388908411027], \"calculationTime\": 96, \"queueingTime\": -384}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 0.0}], \"values\": [0.0], \"calculationTime\": 96, \"queueingTime\": -384}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 0.0}], \"values\": [0.0], \"calculationTime\": 96, \"queueingTime\": -384}]], [[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 1159341.7952729776, \"children\": {}, \"calculationTime\": 16, \"queueingTime\": -384}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 544183.8212896035, \"children\": {}, \"calculationTime\": 16, \"queueingTime\": -384}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 790300.4555458546, \"children\": {}, \"calculationTime\": 16, \"queueingTime\": -384}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 9321685.065826869, \"children\": {}, \"calculationTime\": 16, \"queueingTime\": -384}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 19280407.561757438, \"children\": {}, \"calculationTime\": 16, \"queueingTime\": -384}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request5d16500482e406ca27c4d57713fe3058.json ================================================ { "request_id": "2_5y-10y-5y-10yPrice2020Jan17_5y-10y-5y-10yPrice2020Jan16", "request_hash": "5d16500482e406ca27c4d57713fe3058", "type": "MockCalc", "tests": [ "test_adding_risk_results" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 3089860.4813384884, \"children\": {}, \"calculationTime\": 2499, \"queueingTime\": -9879}], [{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 9189413.662040716, \"children\": {}, \"calculationTime\": 2499, \"queueingTime\": -9879}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 5318377.541764214, \"children\": {}, \"calculationTime\": 3363, \"queueingTime\": -9879}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 11586637.421533648, \"children\": {}, \"calculationTime\": 3363, \"queueingTime\": -9879}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 3041774.635963849, \"children\": {}, \"calculationTime\": 3036, \"queueingTime\": -12089}], [{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 9109315.08610723, \"children\": {}, \"calculationTime\": 3036, \"queueingTime\": -12089}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 5296375.390821766, \"children\": {}, \"calculationTime\": 3525, \"queueingTime\": -12089}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 11492700.86880836, \"children\": {}, \"calculationTime\": 3525, \"queueingTime\": -12089}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request5dd997fbddd11bb0c5408ddd86b647d1.json ================================================ { "request_id": "5_Action1_SPX_optResolvedInstrumentValues2024Jun07_Action1_SPX_optResolvedInstrumentValues2024Jun06_Action1_SPX_optResolvedInstrumentValues2024Jun05_Action1_SPX_optResolvedInstrumentValues2024Jun04_Action1_SPX_optResolvedInstrumentValues2024Jun03", "request_hash": "5dd997fbddd11bb0c5408ddd86b647d1", "type": "MockCalc", "tests": [ "test_exit_transaction_costs" ], "mocked_data": "[[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2024-09-09\",\"expirySettleDate\":\"2024-09-10\",\"expirySettlementDays\":\"1b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":100.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"USD\",\"premiumPaymentDate\":\"2024-06-10\",\"premiumSettlementDate\":\"2024-06-10\",\"settlementDate\":\"2024-06-10\",\"strikePrice\":5356.89,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".SPX\",\"underlierCurrency\":\"USD\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2024-09-06\",\"expirySettleDate\":\"2024-09-09\",\"expirySettlementDays\":\"1b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":100.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"USD\",\"premiumPaymentDate\":\"2024-06-07\",\"premiumSettlementDate\":\"2024-06-07\",\"settlementDate\":\"2024-06-07\",\"strikePrice\":5349.56,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".SPX\",\"underlierCurrency\":\"USD\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2024-09-05\",\"expirySettleDate\":\"2024-09-06\",\"expirySettlementDays\":\"1b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":100.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"USD\",\"premiumPaymentDate\":\"2024-06-06\",\"premiumSettlementDate\":\"2024-06-06\",\"settlementDate\":\"2024-06-06\",\"strikePrice\":5328.35,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".SPX\",\"underlierCurrency\":\"USD\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2024-09-04\",\"expirySettleDate\":\"2024-09-05\",\"expirySettlementDays\":\"1b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":100.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"USD\",\"premiumPaymentDate\":\"2024-06-05\",\"premiumSettlementDate\":\"2024-06-05\",\"settlementDate\":\"2024-06-05\",\"strikePrice\":5260.53,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".SPX\",\"underlierCurrency\":\"USD\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2024-09-03\",\"expirySettleDate\":\"2024-09-04\",\"expirySettlementDays\":\"1b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":100.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"USD\",\"premiumPaymentDate\":\"2024-06-04\",\"premiumSettlementDate\":\"2024-06-04\",\"settlementDate\":\"2024-06-04\",\"strikePrice\":5267.43,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".SPX\",\"underlierCurrency\":\"USD\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request6314f68c62d451df39cff28d0105f250.json ================================================ { "request_id": "2_5y-10y-5y-10yPrice2020Jan15_5y-10y-5y-10yPrice2020Jan14", "request_hash": "6314f68c62d451df39cff28d0105f250", "type": "MockCalc", "tests": [ "test_adding_risk_results" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 3155308.4756011004, \"children\": {}, \"calculationTime\": 52, \"queueingTime\": -171}], [{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 9328091.861150585, \"children\": {}, \"calculationTime\": 52, \"queueingTime\": -171}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 5310827.561557852, \"children\": {}, \"calculationTime\": 48, \"queueingTime\": -171}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 11528844.938571109, \"children\": {}, \"calculationTime\": 48, \"queueingTime\": -171}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 3290317.2121213092, \"children\": {}, \"calculationTime\": 58, \"queueingTime\": -180}], [{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 9587809.11540649, \"children\": {}, \"calculationTime\": 58, \"queueingTime\": -180}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 5644562.7450038865, \"children\": {}, \"calculationTime\": 50, \"queueingTime\": -180}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 12129718.693539664, \"children\": {}, \"calculationTime\": 50, \"queueingTime\": -180}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request659985875ffd9309df838437a1a2763b.json ================================================ { "request_id": "1_IRBasisSwap-5y-10yIRBasis-Price2020Jan14", "request_hash": "659985875ffd9309df838437a1a2763b", "type": "MockCalc", "tests": [ "test_diff_types_risk_measures" ], "mocked_data": "[[[[{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"10Y2Y\", \"quoteStyle\": \"\", \"value\": 405773.8937584889}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"12Y3Y\", \"quoteStyle\": \"\", \"value\": -2.7200611018235445}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"15Y5Y\", \"quoteStyle\": \"\", \"value\": 0.4620983408390117}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"1B3M\", \"quoteStyle\": \"\", \"value\": 1.9142348062627196e-05}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"20Y5Y\", \"quoteStyle\": \"\", \"value\": -0.05881695695157414}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"25Y5Y\", \"quoteStyle\": \"\", \"value\": -0.0004348313974243736}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"2Y1Y\", \"quoteStyle\": \"\", \"value\": -0.04532005730201385}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"30Y10Y\", \"quoteStyle\": \"\", \"value\": 3.3729273530527494e-05}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"3Y1Y\", \"quoteStyle\": \"\", \"value\": -0.06414172854836668}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"40Y10Y\", \"quoteStyle\": \"\", \"value\": -1.923925516037272e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"4Y1Y\", \"quoteStyle\": \"\", \"value\": -0.08374533740142893}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"50Y10Y\", \"quoteStyle\": \"\", \"value\": 2.1080795956579692e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"5Y1Y\", \"quoteStyle\": \"\", \"value\": -0.10282040466133983}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"60Y10Y\", \"quoteStyle\": \"\", \"value\": 6.68770078622528e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"6Y1Y\", \"quoteStyle\": \"\", \"value\": -0.12230059635793628}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"70Y10Y\", \"quoteStyle\": \"\", \"value\": -1.8318484762269249e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"7Y1Y\", \"quoteStyle\": \"\", \"value\": 0.006799739159034514}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"8Y1Y\", \"quoteStyle\": \"\", \"value\": -1.4141260831853109}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"9Y1Y\", \"quoteStyle\": \"\", \"value\": 5.574206218636115}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -0.00418079191459558}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -0.0009141941589970257}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -0.0017561563032869175}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -0.00690739175706837}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -6.535531408466946e-05}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -0.004660697447200317}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -0.002458369731953079}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -0.007498851045334749}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"10Y2Y\", \"quoteStyle\": \"\", \"value\": -85.46635545868574}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"12Y3Y\", \"quoteStyle\": \"\", \"value\": -2.710048697822327}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"15Y5Y\", \"quoteStyle\": \"\", \"value\": 0.4609607774737547}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"1B6M\", \"quoteStyle\": \"\", \"value\": 4.7880060701380985e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"20Y5Y\", \"quoteStyle\": \"\", \"value\": -0.05868237908865136}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"25Y5Y\", \"quoteStyle\": \"\", \"value\": -0.000429893160394545}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"2Y1Y\", \"quoteStyle\": \"\", \"value\": -0.045561681992956175}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"30Y10Y\", \"quoteStyle\": \"\", \"value\": 3.197496361414087e-05}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"3Y1Y\", \"quoteStyle\": \"\", \"value\": -0.06406009498333481}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"40Y10Y\", \"quoteStyle\": \"\", \"value\": -3.5716199126435e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"4Y1Y\", \"quoteStyle\": \"\", \"value\": -0.08358323819592281}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"50Y10Y\", \"quoteStyle\": \"\", \"value\": 2.3358491151888296e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"5Y1Y\", \"quoteStyle\": \"\", \"value\": -0.10260589423554169}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"60Y10Y\", \"quoteStyle\": \"\", \"value\": 6.639239186325095e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"6Y1Y\", \"quoteStyle\": \"\", \"value\": -0.12201704753692034}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"70Y10Y\", \"quoteStyle\": \"\", \"value\": 4.26462079121612e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"7Y1Y\", \"quoteStyle\": \"\", \"value\": 0.006699602955160765}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"8Y1Y\", \"quoteStyle\": \"\", \"value\": -1.410156171921568}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"9Y1Y\", \"quoteStyle\": \"\", \"value\": 5.555144938701376}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -0.015517632057558189}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -0.00016462890102091226}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -0.016322404770140582}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -0.015997523050569753}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -8.432318382631872e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -0.0072544204277780015}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -0.006885186651978515}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -0.00707150696511475}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"10Y2Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"12Y3Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"15Y5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"20Y5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"25Y5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"2Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"30Y10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"3Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"40Y10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"4Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"5Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"6Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"7Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"8Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"9Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": 2.2616822297361757e-13}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -3.8646774625991646e-14}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 4.336808689942018e-23}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 1.945270854120107e-15}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 1.946160940735631e-15}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -4.927694172845998e-14}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 1.5846805638541905e-14}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": 2.0847655784854426e-14}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -1.9462577166215468e-15}], \"asset\": [405773.8937584889, -2.7200611018235445, 0.4620983408390117, 1.9142348062627196e-05, -0.05881695695157414, -0.0004348313974243736, -0.04532005730201385, 3.3729273530527494e-05, -0.06414172854836668, -1.923925516037272e-06, -0.08374533740142893, 2.1080795956579692e-06, -0.10282040466133983, 6.68770078622528e-07, -0.12230059635793628, -1.8318484762269249e-06, 0.006799739159034514, -1.4141260831853109, 5.574206218636115, -0.00418079191459558, -0.0009141941589970257, -0.0017561563032869175, -0.00690739175706837, -6.535531408466946e-05, -0.004660697447200317, -0.002458369731953079, -0.007498851045334749, -85.46635545868574, -2.710048697822327, 0.4609607774737547, 4.7880060701380985e-06, -0.05868237908865136, -0.000429893160394545, -0.045561681992956175, 3.197496361414087e-05, -0.06406009498333481, -3.5716199126435e-06, -0.08358323819592281, 2.3358491151888296e-06, -0.10260589423554169, 6.639239186325095e-07, -0.12201704753692034, 4.26462079121612e-07, 0.006699602955160765, -1.410156171921568, 5.555144938701376, -0.015517632057558189, -0.00016462890102091226, -0.016322404770140582, -0.015997523050569753, -8.432318382631872e-07, -0.0072544204277780015, -0.006885186651978515, -0.00707150696511475, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.2616822297361757e-13, -3.8646774625991646e-14, 4.336808689942018e-23, 1.945270854120107e-15, 1.946160940735631e-15, -4.927694172845998e-14, 1.5846805638541905e-14, 2.0847655784854426e-14, -1.9462577166215468e-15], \"calculationTime\": 20300, \"queueingTime\": -20584}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"10Y2Y\", \"quoteStyle\": \"\", \"value\": 9.52923478088379}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"12Y3Y\", \"quoteStyle\": \"\", \"value\": 0.05154130096435547}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"15Y5Y\", \"quoteStyle\": \"\", \"value\": -0.004792508697509766}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"1B3M\", \"quoteStyle\": \"\", \"value\": 5.800789187622071}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"20Y5Y\", \"quoteStyle\": \"\", \"value\": 9.450531005859375e-05}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"25Y5Y\", \"quoteStyle\": \"\", \"value\": 4.19158935546875e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"2Y1Y\", \"quoteStyle\": \"\", \"value\": -10602.449576880646}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"30Y10Y\", \"quoteStyle\": \"\", \"value\": 4.5776367187500004e-09}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"3Y1Y\", \"quoteStyle\": \"\", \"value\": -12469.135923092652}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"40Y10Y\", \"quoteStyle\": \"\", \"value\": 1.10626220703125e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"4Y1Y\", \"quoteStyle\": \"\", \"value\": -12228.274022883606}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"50Y10Y\", \"quoteStyle\": \"\", \"value\": 2.4490356445312503e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"5Y1Y\", \"quoteStyle\": \"\", \"value\": -12007.332722233583}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"60Y10Y\", \"quoteStyle\": \"\", \"value\": -6.8817138671875e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"6Y1Y\", \"quoteStyle\": \"\", \"value\": -13649.341682823944}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"70Y10Y\", \"quoteStyle\": \"\", \"value\": 1.910400390625e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"7Y1Y\", \"quoteStyle\": \"\", \"value\": -3834.8094675979614}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"8Y1Y\", \"quoteStyle\": \"\", \"value\": 842.6079226676941}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"9Y1Y\", \"quoteStyle\": \"\", \"value\": -159.07964895095827}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": 195.28271338806152}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 3325.0911272758485}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 182.37141670608523}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 185.94827760620117}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 160.28894634628296}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 213.49390620269776}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": 181.5859247871399}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -169.94579461669923}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"10Y2Y\", \"quoteStyle\": \"\", \"value\": 9.510745944213868}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"12Y3Y\", \"quoteStyle\": \"\", \"value\": 0.05111880798339844}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"15Y5Y\", \"quoteStyle\": \"\", \"value\": -0.004781258392333985}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"1B6M\", \"quoteStyle\": \"\", \"value\": 1.534271240234375e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"20Y5Y\", \"quoteStyle\": \"\", \"value\": 9.45343017578125e-05}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"25Y5Y\", \"quoteStyle\": \"\", \"value\": 4.180908203125e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"2Y1Y\", \"quoteStyle\": \"\", \"value\": -10664.253623651124}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"30Y10Y\", \"quoteStyle\": \"\", \"value\": -2.99072265625e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"3Y1Y\", \"quoteStyle\": \"\", \"value\": -12450.987770305634}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"40Y10Y\", \"quoteStyle\": \"\", \"value\": -1.96075439453125e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"4Y1Y\", \"quoteStyle\": \"\", \"value\": -12203.811459606171}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"50Y10Y\", \"quoteStyle\": \"\", \"value\": 4.11224365234375e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"5Y1Y\", \"quoteStyle\": \"\", \"value\": -11982.073376328279}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"60Y10Y\", \"quoteStyle\": \"\", \"value\": -1.64794921875e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"6Y1Y\", \"quoteStyle\": \"\", \"value\": -13618.282747435762}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"70Y10Y\", \"quoteStyle\": \"\", \"value\": 1.7890930175781251e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"7Y1Y\", \"quoteStyle\": \"\", \"value\": -3824.8424483169556}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"8Y1Y\", \"quoteStyle\": \"\", \"value\": 840.7348984207154}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"9Y1Y\", \"quoteStyle\": \"\", \"value\": -158.71503989257812}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -3627.3533278083805}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -31.419506301879885}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -3815.0634537918095}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -3738.1944226242067}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 2.8228759765625004e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -1696.5875288345337}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -1609.608328931427}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -1656.1133361984253}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"10Y2Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"12Y3Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"15Y5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"20Y5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"25Y5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"2Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"30Y10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"3Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"40Y10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"4Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"5Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"6Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"7Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"8Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"9Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": 5.192276766204834}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -0.8872367095947266}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 0.04465872573852539}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 0.044679160308837895}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -1.13127970123291}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 0.3638044273376465}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": 0.47861188125610354}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -0.04468138198852539}], \"asset\": [9.52923478088379, 0.05154130096435547, -0.004792508697509766, 5.800789187622071, 9.450531005859375e-05, 4.19158935546875e-06, -10602.449576880646, 4.5776367187500004e-09, -12469.135923092652, 1.10626220703125e-07, -12228.274022883606, 2.4490356445312503e-07, -12007.332722233583, -6.8817138671875e-07, -13649.341682823944, 1.910400390625e-06, -3834.8094675979614, 842.6079226676941, -159.07964895095827, 195.28271338806152, 3325.0911272758485, 182.37141670608523, 185.94827760620117, 160.28894634628296, 213.49390620269776, 181.5859247871399, -169.94579461669923, 9.510745944213868, 0.05111880798339844, -0.004781258392333985, 1.534271240234375e-06, 9.45343017578125e-05, 4.180908203125e-06, -10664.253623651124, -2.99072265625e-07, -12450.987770305634, -1.96075439453125e-07, -12203.811459606171, 4.11224365234375e-07, -11982.073376328279, -1.64794921875e-07, -13618.282747435762, 1.7890930175781251e-06, -3824.8424483169556, 840.7348984207154, -158.71503989257812, -3627.3533278083805, -31.419506301879885, -3815.0634537918095, -3738.1944226242067, 2.8228759765625004e-07, -1696.5875288345337, -1609.608328931427, -1656.1133361984253, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 5.192276766204834, -0.8872367095947266, 1.52587890625e-09, 0.04465872573852539, 0.044679160308837895, -1.13127970123291, 0.3638044273376465, 0.47861188125610354, -0.04468138198852539], \"calculationTime\": 6455, \"queueingTime\": -20584}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"10Y2Y\", \"quoteStyle\": \"\", \"value\": -26347.867820944215}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"12Y3Y\", \"quoteStyle\": \"\", \"value\": -2378.606844448853}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"15Y5Y\", \"quoteStyle\": \"\", \"value\": 446.0738113571167}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"1B3M\", \"quoteStyle\": \"\", \"value\": 12.465539062500001}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"20Y5Y\", \"quoteStyle\": \"\", \"value\": -78.54717943420411}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"25Y5Y\", \"quoteStyle\": \"\", \"value\": 5.848511454772949}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"2Y1Y\", \"quoteStyle\": \"\", \"value\": -9795.381138699342}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"30Y10Y\", \"quoteStyle\": \"\", \"value\": 0.04852867584228516}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"3Y1Y\", \"quoteStyle\": \"\", \"value\": -11674.271076853944}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"40Y10Y\", \"quoteStyle\": \"\", \"value\": -0.005189944458007812}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"4Y1Y\", \"quoteStyle\": \"\", \"value\": -11410.060714488221}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"50Y10Y\", \"quoteStyle\": \"\", \"value\": 0.0002031097412109375}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"5Y1Y\", \"quoteStyle\": \"\", \"value\": -11533.43322893219}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"60Y10Y\", \"quoteStyle\": \"\", \"value\": 1.2709045410156251e-05}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"6Y1Y\", \"quoteStyle\": \"\", \"value\": -11613.239750512696}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"70Y10Y\", \"quoteStyle\": \"\", \"value\": -4.2724609375e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"7Y1Y\", \"quoteStyle\": \"\", \"value\": -11663.5822681839}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"8Y1Y\", \"quoteStyle\": \"\", \"value\": -11911.988895561219}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"9Y1Y\", \"quoteStyle\": \"\", \"value\": -10881.180841523743}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": 398.9998337753296}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 3400.406100605774}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 391.11904293212893}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 392.71438133392337}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 369.90518065490727}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 421.64106157379155}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": 389.8599864440918}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": 33.230199980163576}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"10Y2Y\", \"quoteStyle\": \"\", \"value\": -26275.577643197634}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"12Y3Y\", \"quoteStyle\": \"\", \"value\": -2371.3087557586673}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"15Y5Y\", \"quoteStyle\": \"\", \"value\": 444.9697718139649}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"1B6M\", \"quoteStyle\": \"\", \"value\": 8.1353759765625e-05}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"20Y5Y\", \"quoteStyle\": \"\", \"value\": -78.36512499542236}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"25Y5Y\", \"quoteStyle\": \"\", \"value\": 5.835893811035157}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"2Y1Y\", \"quoteStyle\": \"\", \"value\": -9852.181544091798}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"30Y10Y\", \"quoteStyle\": \"\", \"value\": 0.048396337890625}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"3Y1Y\", \"quoteStyle\": \"\", \"value\": -11657.282032495117}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"40Y10Y\", \"quoteStyle\": \"\", \"value\": -0.005180480957031251}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"4Y1Y\", \"quoteStyle\": \"\", \"value\": -11387.251607081604}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"50Y10Y\", \"quoteStyle\": \"\", \"value\": 0.00020276031494140625}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"5Y1Y\", \"quoteStyle\": \"\", \"value\": -11509.102528015137}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"60Y10Y\", \"quoteStyle\": \"\", \"value\": 1.285400390625e-05}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"6Y1Y\", \"quoteStyle\": \"\", \"value\": -11587.251107495118}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"70Y10Y\", \"quoteStyle\": \"\", \"value\": -2.7923583984375e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"7Y1Y\", \"quoteStyle\": \"\", \"value\": -11636.209979219057}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"8Y1Y\", \"quoteStyle\": \"\", \"value\": -11882.081125758363}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"9Y1Y\", \"quoteStyle\": \"\", \"value\": -10852.30780966797}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -3351.3993125564575}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -29.416650483703616}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -3524.8292206497194}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -3453.8080968093873}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -3.6819458007812502e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -1567.5181828704835}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -1487.1560013534547}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -1530.0591721755982}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"10Y2Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"12Y3Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"15Y5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"20Y5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"25Y5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"2Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"30Y10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"3Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"40Y10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"4Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"5Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"6Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"7Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"8Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"9Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": 11.157791914367676}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -1.9066014846801758}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 0.09596806640625001}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 0.09601197967529297}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -2.431030561828613}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 0.7817869262695313}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": 1.0284990631103517}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -0.09601675415039063}], \"asset\": [-26347.867820944215, -2378.606844448853, 446.0738113571167, 12.465539062500001, -78.54717943420411, 5.848511454772949, -9795.381138699342, 0.04852867584228516, -11674.271076853944, -0.005189944458007812, -11410.060714488221, 0.0002031097412109375, -11533.43322893219, 1.2709045410156251e-05, -11613.239750512696, -4.2724609375e-07, -11663.5822681839, -11911.988895561219, -10881.180841523743, 398.9998337753296, 3400.406100605774, 391.11904293212893, 392.71438133392337, 369.90518065490727, 421.64106157379155, 389.8599864440918, 33.230199980163576, -26275.577643197634, -2371.3087557586673, 444.9697718139649, 8.1353759765625e-05, -78.36512499542236, 5.835893811035157, -9852.181544091798, 0.048396337890625, -11657.282032495117, -0.005180480957031251, -11387.251607081604, 0.00020276031494140625, -11509.102528015137, 1.285400390625e-05, -11587.251107495118, -2.7923583984375e-07, -11636.209979219057, -11882.081125758363, -10852.30780966797, -3351.3993125564575, -29.416650483703616, -3524.8292206497194, -3453.8080968093873, -3.6819458007812502e-06, -1567.5181828704835, -1487.1560013534547, -1530.0591721755982, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 11.157791914367676, -1.9066014846801758, 1.52587890625e-09, 0.09596806640625001, 0.09601197967529297, -2.431030561828613, 0.7817869262695313, 1.0284990631103517, -0.09601675415039063], \"calculationTime\": 6455, \"queueingTime\": -20584}]], [[{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 0.0, \"children\": {}, \"calculationTime\": 135, \"queueingTime\": -20584}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 5644562.7450038865, \"children\": {}, \"calculationTime\": 43, \"queueingTime\": -20584}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 12129718.693539664, \"children\": {}, \"calculationTime\": 43, \"queueingTime\": -20584}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request65dc6b58f9c22777f12c1d2971b23523.json ================================================ { "request_id": "1_swap1-swap2-swap3ResolvedInstrumentValues2020Oct15", "request_hash": "65dc6b58f9c22777f12c1d2971b23523", "type": "MockCalc", "tests": [ "test_results_with_resolution" ], "mocked_data": "[[[[{\"payOrReceive\": \"Pay\", \"terminationDate\": \"2030-10-19\", \"notionalCurrency\": \"USD\", \"effectiveDate\": \"2020-10-19\", \"principalExchange\": \"None\", \"floatingRateOption\": \"USD-LIBOR-BBA\", \"floatingRateDesignatedMaturity\": \"3m\", \"floatingRateFrequency\": \"3m\", \"floatingRateDayCountFraction\": \"ACT/360\", \"floatingRateBusinessDayConvention\": \"Modified Following\", \"fixedRateFrequency\": \"6m\", \"fixedRateDayCountFraction\": \"30/360\", \"fixedRateBusinessDayConvention\": \"Modified Following\", \"fee\": 0.0, \"feeCurrency\": \"USD\", \"feePaymentDate\": \"2020-10-19\", \"fixedFirstStub\": \"2020-10-19\", \"floatingFirstStub\": \"2020-10-19\", \"fixedLastStub\": \"2030-10-19\", \"floatingLastStub\": \"2030-10-19\", \"fixedHolidays\": \"New York,London\", \"floatingHolidays\": \"New York,London\", \"rollConvention\": \"Default\", \"assetClass\": \"Rates\", \"type\": \"Swap\", \"notionalAmount\": 100000000.0, \"fixedRate\": 0.00751200023594419, \"floatingRateSpread\": 0.0, \"$type\": \"LegDefinition\", \"calculationTime\": 646, \"queueingTime\": -751}], [{\"payOrReceive\": \"Pay\", \"terminationDate\": \"2030-10-15\", \"notionalCurrency\": \"GBP\", \"effectiveDate\": \"2020-10-15\", \"principalExchange\": \"None\", \"floatingRateOption\": \"GBP-LIBOR-BBA\", \"floatingRateDesignatedMaturity\": \"6m\", \"floatingRateFrequency\": \"6m\", \"floatingRateBusinessDayConvention\": \"Modified Following\", \"fixedRateFrequency\": \"6m\", \"fixedRateBusinessDayConvention\": \"Modified Following\", \"fee\": 0.0, \"feeCurrency\": \"GBP\", \"feePaymentDate\": \"2020-10-19\", \"fixedFirstStub\": \"2020-10-15\", \"floatingFirstStub\": \"2020-10-15\", \"fixedLastStub\": \"2030-10-15\", \"floatingLastStub\": \"2030-10-15\", \"fixedHolidays\": \"London\", \"floatingHolidays\": \"London\", \"rollConvention\": \"Default\", \"assetClass\": \"Rates\", \"type\": \"Swap\", \"notionalAmount\": 100000000.0, \"fixedRate\": 0.0035246723960970813, \"floatingRateSpread\": 0.0, \"$type\": \"LegDefinition\", \"calculationTime\": 142, \"queueingTime\": -751}], [{\"payOrReceive\": \"Pay\", \"terminationDate\": \"2030-10-19\", \"notionalCurrency\": \"EUR\", \"effectiveDate\": \"2020-10-19\", \"principalExchange\": \"None\", \"floatingRateOption\": \"EUR-EURIBOR-Telerate\", \"floatingRateDesignatedMaturity\": \"6m\", \"floatingRateFrequency\": \"6m\", \"floatingRateDayCountFraction\": \"ACT/360\", \"floatingRateBusinessDayConvention\": \"Modified Following\", \"fixedRateFrequency\": \"1y\", \"fixedRateDayCountFraction\": \"30/360\", \"fixedRateBusinessDayConvention\": \"Modified Following\", \"fee\": 0.0, \"feeCurrency\": \"EUR\", \"feePaymentDate\": \"2020-10-19\", \"fixedFirstStub\": \"2020-10-19\", \"floatingFirstStub\": \"2020-10-19\", \"fixedLastStub\": \"2030-10-19\", \"floatingLastStub\": \"2030-10-19\", \"fixedHolidays\": \"Target\", \"floatingHolidays\": \"Target\", \"rollConvention\": \"Default\", \"assetClass\": \"Rates\", \"type\": \"Swap\", \"notionalAmount\": 100000000.0, \"fixedRate\": -0.0029250862573455385, \"floatingRateSpread\": 0.0, \"$type\": \"LegDefinition\", \"calculationTime\": 3574, \"queueingTime\": -751}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request65e287923a4ebf427e6a646c99e200ca.json ================================================ { "request_id": "1_EUR6m10ypayerPricetoday", "request_hash": "65e287923a4ebf427e6a646c99e200ca", "type": "MockCalc", "tests": [ "test_aggregation_with_heterogeous_types" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 2972587.6580812754, \"children\": {}, \"calculationTime\": 454, \"queueingTime\": 77}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request66b092215599bb11f8a03a3ff6a4eb3a.json ================================================ { "request_id": "5_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-10Price2021Dec10_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-09Price2021Dec09_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-08Price2021Dec08_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-07Price2021Dec07_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-06Price2021Dec06", "request_hash": "66b092215599bb11f8a03a3ff6a4eb3a", "type": "MockCalc", "tests": [ "test_add_scaled_action_nav_with_transaction_costs" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":86.79973154828899}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":94.53735749867951}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":110.44563094452069}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":137.52667276440891}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":99.0}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request678cb7e6ab71f2441e6c2f5a917d37be.json ================================================ { "request_id": "5_QuantityScaledAction1_call-QuantityScaledAction1_putResolvedInstrumentValues2021Dec10_QuantityScaledAction1_call-QuantityScaledAction1_putResolvedInstrumentValues2021Dec09_QuantityScaledAction1_call-QuantityScaledAction1_putResolvedInstrumentValues2021Dec08_QuantityScaledAction1_call-QuantityScaledAction1_putResolvedInstrumentValues2021Dec07_QuantityScaledAction1_call-QuantityScaledAction1_putResolvedInstrumentValues2021Dec06", "request_hash": "678cb7e6ab71f2441e6c2f5a917d37be", "type": "MockCalc", "tests": [ "test_add_scaled_action" ], "mocked_data": "[[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-01-10\",\"expirySettleDate\":\"2022-01-12\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-14\",\"premiumSettlementDate\":\"2021-12-14\",\"settlementDate\":\"2021-12-14\",\"strikePrice\":4199.16,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}],[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-01-10\",\"expirySettleDate\":\"2022-01-12\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Put\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-14\",\"premiumSettlementDate\":\"2021-12-14\",\"settlementDate\":\"2021-12-14\",\"strikePrice\":4199.16,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-01-10\",\"expirySettleDate\":\"2022-01-12\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-13\",\"premiumSettlementDate\":\"2021-12-13\",\"settlementDate\":\"2021-12-13\",\"strikePrice\":4208.3,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}],[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-01-10\",\"expirySettleDate\":\"2022-01-12\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Put\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-13\",\"premiumSettlementDate\":\"2021-12-13\",\"settlementDate\":\"2021-12-13\",\"strikePrice\":4208.3,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-01-10\",\"expirySettleDate\":\"2022-01-12\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-10\",\"premiumSettlementDate\":\"2021-12-10\",\"settlementDate\":\"2021-12-10\",\"strikePrice\":4233.09,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}],[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-01-10\",\"expirySettleDate\":\"2022-01-12\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Put\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-10\",\"premiumSettlementDate\":\"2021-12-10\",\"settlementDate\":\"2021-12-10\",\"strikePrice\":4233.09,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-01-07\",\"expirySettleDate\":\"2022-01-11\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-09\",\"premiumSettlementDate\":\"2021-12-09\",\"settlementDate\":\"2021-12-09\",\"strikePrice\":4276.2,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"SQ\"}],[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-01-07\",\"expirySettleDate\":\"2022-01-11\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Put\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-09\",\"premiumSettlementDate\":\"2021-12-09\",\"settlementDate\":\"2021-12-09\",\"strikePrice\":4276.2,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"SQ\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-01-06\",\"expirySettleDate\":\"2022-01-10\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-08\",\"premiumSettlementDate\":\"2021-12-08\",\"settlementDate\":\"2021-12-08\",\"strikePrice\":4137.11,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}],[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-01-06\",\"expirySettleDate\":\"2022-01-10\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Put\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-08\",\"premiumSettlementDate\":\"2021-12-08\",\"settlementDate\":\"2021-12-08\",\"strikePrice\":4137.11,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request6942e2686a9fe4a5d47af94a897be7e6.json ================================================ { "request_id": "1_mcbFXSpot2020Jan14", "request_hash": "6942e2686a9fe4a5d47af94a897be7e6", "type": "MockCalc", "tests": [ "test_leg_valuations" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"val\": null, \"children\": {\"Legs_0\": 1.112625, \"Legs_1\": 1.3009500000000003}, \"calculationTime\": 10164, \"queueingTime\": 44}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request6a14f7b7044f04669b2c08775f555232.json ================================================ { "request_id": "5_Action1_SPX_opt_2024-06-07Price2024Jun07_Action1_SPX_opt_2024-06-06Price2024Jun06_Action1_SPX_opt_2024-06-05Price2024Jun05_Action1_SPX_opt_2024-06-04Price2024Jun04_Action1_SPX_opt_2024-06-03Price2024Jun03", "request_hash": "6a14f7b7044f04669b2c08775f555232", "type": "MockCalc", "tests": [ "test_exit_transaction_costs" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":16165.917959547747}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":16205.454284227522}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":16182.612789920946}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":16451.828621518223}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":16183.69179385694}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request6a7213dc0f42bdb7d52e861ee1ec6fc6.json ================================================ { "request_id": "2_5y-10yDollarPrice-Price2020Jan15_5y-10yDollarPrice-Price2020Jan14", "request_hash": "6a7213dc0f42bdb7d52e861ee1ec6fc6", "type": "MockCalc", "tests": [ "test_dated_risk_values" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 6914612.085193914, \"children\": {}, \"calculationTime\": 23, \"queueingTime\": 284}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 15010370.722182993, \"children\": {}, \"calculationTime\": 23, \"queueingTime\": 284}]], [[{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 5310827.561557852, \"children\": {}, \"calculationTime\": 23, \"queueingTime\": 284}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 11528844.938571109, \"children\": {}, \"calculationTime\": 23, \"queueingTime\": 284}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 7342905.369624435, \"children\": {}, \"calculationTime\": 922, \"queueingTime\": 26}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 15779322.606637968, \"children\": {}, \"calculationTime\": 922, \"queueingTime\": 26}]], [[{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 5644562.7450038865, \"children\": {}, \"calculationTime\": 922, \"queueingTime\": 26}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 12129718.693539664, \"children\": {}, \"calculationTime\": 922, \"queueingTime\": 26}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request6b1d7d732da252ae69acdc3a3c2f6eee.json ================================================ { "request_id": "1_HedgeAction1_Priceable0ResolvedInstrumentValues2021Dec02", "request_hash": "6b1d7d732da252ae69acdc3a3c2f6eee", "type": "MockCalc", "tests": [ "test_hedge_action_risk_trigger" ], "mocked_data": "[[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"FX\",\"buySell\":\"Buy\",\"forwardRate\":110.60249056,\"notionalAmount\":100000.0,\"notionalAmountInOtherCurrency\":-11060249.056,\"notionalCurrency\":\"USD\",\"pair\":\"USD JPY\",\"settlementDate\":\"2023-12-06\",\"type\":\"Forward\"}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request6c6a6e62e91685350853b59ec792b962.json ================================================ { "request_id": "8_test_hedge_without_risk_2y_forward_2021-12-03_HedgeAction1_Priceable0FXDelta(aggregation_level:Type)-Price2021Dec03_test_hedge_without_risk_2y_forward_2021-12-02_HedgeAction1_Priceable0FXDelta(aggregation_level:Type)-Price2021Dec03_test_hedge_without_risk_2y_forward_2021-12-02_HedgeAction1_Priceable0FXDelta(aggregation_level:Type)-Price2021Dec02_test_hedge_without_risk_2y_forward_2021-12-01_HedgeAction1_Priceable0FXDelta(aggregation_level:Type)-Price2021Dec02_test_hedge_without_risk_2y_forward_2021-12-01_HedgeAction1_Priceable0FXDelta(aggregation_level:Type)-Price2021Dec01_AddAction1_test_hedge_without_risk_2y_call_2021-12-03FXDelta(aggregation_level:Type)-Price2021Dec03_AddAction1_test_hedge_without_risk_2y_call_2021-12-02FXDelta(aggregation_level:Type)-Price2021Dec02_AddAction1_test_hedge_without_risk_2y_call_2021-12-01FXDelta(aggregation_level:Type)-Price2021Dec01", "request_hash": "6c6a6e62e91685350853b59ec792b962", "type": "MockCalc", "tests": [ "test_hedge_without_risk" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"children\":{},\"val\":884.9163677950855}]],[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":-1.862645149230957e-9}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"val\":884.4121274159988}]],[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":6683.710253389552}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"val\":885.3939510299824}]],[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":1.862645149230957e-9}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"val\":884.8913761903532}]],[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":6883.020442333072}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"val\":885.5439737817505}]],[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":-1.862645149230957e-9}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"val\":463.45274141458503}]],[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":-5.820766091346741e-11}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"val\":463.27915259553265}]],[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":3.4924596548080444e-9}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"val\":463.09122659749846}]],[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":0.0}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request6d08be7030b4cb7b9037efcdeb81972a.json ================================================ { "request_id": "1_1y-3m-5y-10yIRVega(currency:local)-Price2020Jan14", "request_hash": "6d08be7030b4cb7b9037efcdeb81972a", "type": "MockCalc", "tests": [ "test_empty_calc_request" ], "mocked_data": "[[[[{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;1Y\", \"quoteStyle\": \"\", \"value\": 18951.379622470093}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;3M\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [18951.379622470093, 0.0], \"calculationTime\": 98, \"queueingTime\": -330}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;3M\", \"quoteStyle\": \"\", \"value\": 9546.483379703379}], \"asset\": [0.0, 9546.483379703379], \"calculationTime\": 98, \"queueingTime\": -330}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;3M\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [0.0, 0.0], \"calculationTime\": 98, \"queueingTime\": -330}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;3M\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [0.0, 0.0], \"calculationTime\": 98, \"queueingTime\": -330}]], [[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 1159341.7952729776, \"children\": {}, \"calculationTime\": 22, \"queueingTime\": -330}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 544183.8212896035, \"children\": {}, \"calculationTime\": 22, \"queueingTime\": -330}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 9321685.065826869, \"children\": {}, \"calculationTime\": 22, \"queueingTime\": -330}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 19280407.561757438, \"children\": {}, \"calculationTime\": 22, \"queueingTime\": -330}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request6d7cd2a1c692ce0886cdee9506101e89.json ================================================ { "request_id": "1_5y-10yPricetodayparallel shift5bp", "request_hash": "6d7cd2a1c692ce0886cdee9506101e89", "type": "MockCalc", "tests": [ "test_adding_risk_results" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"Japanese Yen\": 1.0}, \"val\": 3364687.316191261, \"children\": {}}], [{\"$type\": \"Risk\", \"unit\": {\"Japanese Yen\": 1.0}, \"val\": 8760884.71457497, \"children\": {}}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request6eb85d48e09f538f150400127fd73054.json ================================================ { "request_id": "1_5y-10y-5y-10yPricetodaymultiscenario", "request_hash": "6eb85d48e09f538f150400127fd73054", "type": "MockCalc", "tests": [ "test_multi_scenario" ], "mocked_data": "[[[[{\"$type\": \"Table\", \"rows\": [{\"label\": \"ir curve(parallel=0bp,slope=0bp,pivot=18y,upper cutoff=30y,lower cutoff=5y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 8855060.120079419, \"children\": {}}}, {\"label\": \"ir curve(parallel=5bp,slope=0bp,pivot=0y,upper cutoff=0y,lower cutoff=0y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 8855060.120079419, \"children\": {}}}], \"calculationTime\": 0, \"queueingTime\": 6061}], [{\"$type\": \"Table\", \"rows\": [{\"label\": \"ir curve(parallel=0bp,slope=0bp,pivot=18y,upper cutoff=30y,lower cutoff=5y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 20513416.03191008, \"children\": {}}}, {\"label\": \"ir curve(parallel=5bp,slope=0bp,pivot=0y,upper cutoff=0y,lower cutoff=0y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 20513416.03191008, \"children\": {}}}], \"calculationTime\": 0, \"queueingTime\": 6061}], [{\"$type\": \"Table\", \"rows\": [{\"label\": \"ir curve(parallel=0bp,slope=0bp,pivot=18y,upper cutoff=30y,lower cutoff=5y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 11549606.309459362, \"children\": {}}}, {\"label\": \"ir curve(parallel=5bp,slope=0bp,pivot=0y,upper cutoff=0y,lower cutoff=0y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 11549606.309459362, \"children\": {}}}], \"calculationTime\": 0, \"queueingTime\": 6061}], [{\"$type\": \"Table\", \"rows\": [{\"label\": \"ir curve(parallel=0bp,slope=0bp,pivot=18y,upper cutoff=30y,lower cutoff=5y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 20891715.04796604, \"children\": {}}}, {\"label\": \"ir curve(parallel=5bp,slope=0bp,pivot=0y,upper cutoff=0y,lower cutoff=0y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 20891715.04796604, \"children\": {}}}], \"calculationTime\": 0, \"queueingTime\": 6061}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request6efd87b3691c5bc5d90f6e638748dc98.json ================================================ { "request_id": "3_10y@a+1-10y@a+2-10y@a+3DollarPrice-IRDelta2021Feb11_10y@a+1-10y@a+2-10y@a+3DollarPrice-IRDelta2021Feb10_10y@a+1-10y@a+2-10y@a+3DollarPrice-IRDelta2021Feb09", "request_hash": "6efd87b3691c5bc5d90f6e638748dc98", "type": "MockCalc", "tests": [ "test_historical_pricing" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": -96801.49023098126, \"children\": {}, \"calculationTime\": 1114, \"queueingTime\": -9737}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": -193602.98046196438, \"children\": {}, \"calculationTime\": 1114, \"queueingTime\": -9737}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": -290404.4706929475, \"children\": {}, \"calculationTime\": 1114, \"queueingTime\": -9737}]], [[{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -805.311073600316}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": 0.026521726512908936}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 0.38509267568588257}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC22\", \"quoteStyle\": \"\", \"value\": 0.1940447101354599}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 0.4990887902677059}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN22\", \"quoteStyle\": \"\", \"value\": 0.2761675855100155}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -1693.7312547266483}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR22\", \"quoteStyle\": \"\", \"value\": 0.31830959022045135}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": 0.42640997003316883}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP22\", \"quoteStyle\": \"\", \"value\": 0.17957641835808755}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 96807.25901854504}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": -0.14878419786691666}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 0.020726297050714493}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -0.0014786524713039399}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": -2.120528221130371e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 4.33996319770813e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": -3.0360817909240723e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": 2.7762307273209097}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -4.7492980957031254e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": -5.4013729095458985e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 3.938655095177889}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": -1.1175870895385742e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 4.738202461975813}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 5.758261899465323}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 6.685731759291888}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 7.586577286946774}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 9.283682127481699}], \"asset\": [-805.311073600316, 0.026521726512908936, 0.38509267568588257, 0.1940447101354599, 0.4990887902677059, 0.2761675855100155, -1693.7312547266483, 0.31830959022045135, 0.42640997003316883, 0.17957641835808755, 96807.25901854504, -0.14878419786691666, 0.020726297050714493, -0.0014786524713039399, -2.120528221130371e-05, 4.33996319770813e-06, -3.0360817909240723e-07, 2.7762307273209097, -4.7492980957031254e-08, -5.4013729095458985e-08, 3.938655095177889, -1.1175870895385742e-08, 4.738202461975813, 5.758261899465323, 6.685731759291888, 7.586577286946774, 9.283682127481699], \"calculationTime\": 2305, \"queueingTime\": -9737}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -805.0292672347308}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": 0.05341073218584061}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 0.7701787604093552}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC22\", \"quoteStyle\": \"\", \"value\": 0.3880831012606621}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 0.9981679515838624}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN22\", \"quoteStyle\": \"\", \"value\": 0.5523266969323158}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -1693.1769921314}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR22\", \"quoteStyle\": \"\", \"value\": 0.6366087635993958}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": 0.852810837328434}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP22\", \"quoteStyle\": \"\", \"value\": 0.3591461675286293}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 96814.02501881681}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": -0.29755831323862075}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 0.041443022894859315}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -0.002953332853317261}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": -4.296377897262574e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 8.110892772674561e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": -6.100177764892579e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": 5.552460715174675}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -6.053447723388672e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": -4.936456680297852e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 7.877309354031087}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": -8.392333984375e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 9.476404852235317}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 11.516527188944817}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 13.371437944471836}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 15.17329485062361}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 18.566948329102996}], \"asset\": [-805.0292672347308, 0.05341073218584061, 0.7701787604093552, 0.3880831012606621, 0.9981679515838624, 0.5523266969323158, -1693.1769921314, 0.6366087635993958, 0.852810837328434, 0.3591461675286293, 96814.02501881681, -0.29755831323862075, 0.041443022894859315, -0.002953332853317261, -4.296377897262574e-05, 8.110892772674561e-06, -6.100177764892579e-07, 5.552460715174675, -6.053447723388672e-08, -4.936456680297852e-08, 7.877309354031087, -8.392333984375e-09, 9.476404852235317, 11.516527188944817, 13.371437944471836, 15.17329485062361, 18.566948329102996], \"calculationTime\": 2305, \"queueingTime\": -9737}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -804.7474608654022}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": 0.08029973413944244}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 1.1552648488521577}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC22\", \"quoteStyle\": \"\", \"value\": 0.5821214905023575}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 1.4972471129179001}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN22\", \"quoteStyle\": \"\", \"value\": 0.8284858139276505}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -1692.622729536152}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR22\", \"quoteStyle\": \"\", \"value\": 0.9549079369783402}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": 1.279211701822281}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP22\", \"quoteStyle\": \"\", \"value\": 0.5387159157514573}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 96820.7910190914}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": -0.44633243045806886}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 0.06215974688529968}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -0.004428016042709351}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": -6.472134590148926e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 1.1885547637939454e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": -9.182929992675782e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": 8.328690705823899}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -6.985664367675782e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": -5.121231079101563e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 11.815963611936569}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": -7.4386596679687505e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 14.214607241582872}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 17.27479247841835}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 20.05714412870407}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 22.760012417101862}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 27.850214534449577}], \"asset\": [-804.7474608654022, 0.08029973413944244, 1.1552648488521577, 0.5821214905023575, 1.4972471129179001, 0.8284858139276505, -1692.622729536152, 0.9549079369783402, 1.279211701822281, 0.5387159157514573, 96820.7910190914, -0.44633243045806886, 0.06215974688529968, -0.004428016042709351, -6.472134590148926e-05, 1.1885547637939454e-05, -9.182929992675782e-07, 8.328690705823899, -6.985664367675782e-08, -5.121231079101563e-08, 11.815963611936569, -7.4386596679687505e-09, 14.214607241582872, 17.27479247841835, 20.05714412870407, 22.760012417101862, 27.850214534449577], \"calculationTime\": 2305, \"queueingTime\": -9737}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": -96728.70023898967, \"children\": {}, \"calculationTime\": 1205, \"queueingTime\": -10551}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": -193457.4004779756, \"children\": {}, \"calculationTime\": 1205, \"queueingTime\": -10551}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": -290186.1007169653, \"children\": {}, \"calculationTime\": 1205, \"queueingTime\": -10551}]], [[{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -916.5091912737072}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": 0.026300565338134768}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 0.41665069385766984}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC22\", \"quoteStyle\": \"\", \"value\": 0.19880937877893448}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 0.5054848771572114}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN22\", \"quoteStyle\": \"\", \"value\": 0.25592170003652576}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -1554.8376586949469}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR22\", \"quoteStyle\": \"\", \"value\": 0.3063728203952313}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": 0.4304523915052414}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP22\", \"quoteStyle\": \"\", \"value\": 0.19869017227888108}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 96733.96354325674}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": -0.15394060034155846}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 0.021710939705371857}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -0.0015380801618099214}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": -2.417340874671936e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 4.982578754425049e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": -3.147900104522705e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": 2.742340825498104}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -4.377365112304688e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": 7.450580596923828e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 3.8620595522224903}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 2.4211406707763672e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 4.778685899454356}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 5.732825960034132}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 6.762049614447355}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 7.5394701110243805}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 9.19527208339572}], \"asset\": [-916.5091912737072, 0.026300565338134768, 0.41665069385766984, 0.19880937877893448, 0.5054848771572114, 0.25592170003652576, -1554.8376586949469, 0.3063728203952313, 0.4304523915052414, 0.19869017227888108, 96733.96354325674, -0.15394060034155846, 0.021710939705371857, -0.0015380801618099214, -2.417340874671936e-05, 4.982578754425049e-06, -3.147900104522705e-07, 2.742340825498104, -4.377365112304688e-08, 7.450580596923828e-08, 3.8620595522224903, 2.4211406707763672e-08, 4.778685899454356, 5.732825960034132, 6.762049614447355, 7.5394701110243805, 9.19527208339572], \"calculationTime\": 2317, \"queueingTime\": -10551}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -916.2823945898533}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": 0.05316920578479767}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 0.8332948302865029}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC22\", \"quoteStyle\": \"\", \"value\": 0.3976125400543213}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 1.0109601654171945}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN22\", \"quoteStyle\": \"\", \"value\": 0.5118347620606423}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -1554.2859082594514}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR22\", \"quoteStyle\": \"\", \"value\": 0.6127349520206452}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": 0.860895443713665}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP22\", \"quoteStyle\": \"\", \"value\": 0.3973736437082291}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 96740.76227165012}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": -0.3078700136423111}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 0.04341244511604309}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -0.0030722403883934025}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": -4.896707534790039e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 9.347689151763917e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": -6.416916847229004e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": 5.484681073570251}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -5.773305892944336e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": 7.636547088623048e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 7.724118274641038}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 2.3281574249267578e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 9.557371976792814}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 11.465655620205403}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 13.524072258722782}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 15.079085125589371}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 18.39012288954258}], \"asset\": [-916.2823945898533, 0.05316920578479767, 0.8332948302865029, 0.3976125400543213, 1.0109601654171945, 0.5118347620606423, -1554.2859082594514, 0.6127349520206452, 0.860895443713665, 0.3973736437082291, 96740.76227165012, -0.3078700136423111, 0.04341244511604309, -0.0030722403883934025, -4.896707534790039e-05, 9.347689151763917e-06, -6.416916847229004e-07, 5.484681073570251, -5.773305892944336e-08, 7.636547088623048e-08, 7.724118274641038, 2.3281574249267578e-08, 9.557371976792814, 11.465655620205403, 13.524072258722782, 15.079085125589371, 18.39012288954258], \"calculationTime\": 2317, \"queueingTime\": -10551}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -916.0555979069471}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": 0.0800378434419632}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 1.2499389629840851}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC22\", \"quoteStyle\": \"\", \"value\": 0.5964157003879548}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 1.51643545088768}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN22\", \"quoteStyle\": \"\", \"value\": 0.7677478259563446}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -1553.7341578276873}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR22\", \"quoteStyle\": \"\", \"value\": 0.9190970929384232}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": 1.2913384949684144}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP22\", \"quoteStyle\": \"\", \"value\": 0.5960571160554886}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 96747.56100004353}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": -0.46179942786693573}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 0.06511395423412324}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -0.004606402492523194}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": -7.37579345703125e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 1.3709068298339844e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": -9.676456451416016e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": 8.227021319794655}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -7.450580596923828e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": 7.636547088623048e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 11.586176999855041}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 2.2339820861816408e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 14.336058055973053}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 17.19848528597355}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 20.286094904875757}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 22.61870013921261}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 27.584973697543145}], \"asset\": [-916.0555979069471, 0.0800378434419632, 1.2499389629840851, 0.5964157003879548, 1.51643545088768, 0.7677478259563446, -1553.7341578276873, 0.9190970929384232, 1.2913384949684144, 0.5960571160554886, 96747.56100004353, -0.46179942786693573, 0.06511395423412324, -0.004606402492523194, -7.37579345703125e-05, 1.3709068298339844e-05, -9.676456451416016e-07, 8.227021319794655, -7.450580596923828e-08, 7.636547088623048e-08, 11.586176999855041, 2.2339820861816408e-08, 14.336058055973053, 17.19848528597355, 20.286094904875757, 22.61870013921261, 27.584973697543145], \"calculationTime\": 2317, \"queueingTime\": -10551}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": -96719.32976884022, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 21152}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": -193438.65953768417, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 21152}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": -290157.98930652067, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 21152}]], [[{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -944.239487214017}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": 0.026856061071157455}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 0.3642875095844269}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC22\", \"quoteStyle\": \"\", \"value\": 0.20800674613714218}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 0.5072844112336636}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN22\", \"quoteStyle\": \"\", \"value\": 0.2753322413146496}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -1527.043964461422}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR22\", \"quoteStyle\": \"\", \"value\": 0.3281131787180901}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": 0.4565594755113125}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP22\", \"quoteStyle\": \"\", \"value\": 0.19727902300953867}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 96726.0880230302}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": -0.15429057087898254}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 0.02145049907565117}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -0.0014787493288517}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": -3.416184186935425e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 5.393290519714356e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": -3.520369529724121e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": 2.6789464335918427}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -1.9556283950805664e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": 2.2351741790771484e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 3.9166016820788387}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": -2.5147199630737305e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 4.740115999245644}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 5.753624555653334}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 6.698290654462577}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 7.600130343812705}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 9.191937875938416}], \"asset\": [-944.239487214017, 0.026856061071157455, 0.3642875095844269, 0.20800674613714218, 0.5072844112336636, 0.2753322413146496, -1527.043964461422, 0.3281131787180901, 0.4565594755113125, 0.19727902300953867, 96726.0880230302, -0.15429057087898254, 0.02145049907565117, -0.0014787493288517, -3.416184186935425e-05, 5.393290519714356e-06, -3.520369529724121e-07, 2.6789464335918427, -1.9556283950805664e-08, 2.2351741790771484e-08, 3.9166016820788387, -2.5147199630737305e-08, 4.740115999245644, 5.753624555653334, 6.698290654462577, 7.600130343812705, 9.191937875938416], \"calculationTime\": 0, \"queueingTime\": 21152}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -944.0053339265228}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": 0.053722518491744996}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 0.7285683490276337}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC22\", \"quoteStyle\": \"\", \"value\": 0.41600738371610646}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 1.0145594421744346}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN22\", \"quoteStyle\": \"\", \"value\": 0.5506562730312348}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -1526.488805373192}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR22\", \"quoteStyle\": \"\", \"value\": 0.6562161631822586}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": 0.9131101472377777}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP22\", \"quoteStyle\": \"\", \"value\": 0.39455169998407363}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 96732.87796938886}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": -0.3085706122279167}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 0.04289153963327408}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -0.002953442740440369}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": -6.897095441818237e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 1.0247337818145753e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": -6.733536720275879e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": 5.357892179870606}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -3.818273544311524e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": 2.2351741790771484e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 7.833202601408959}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": -2.5141239166259766e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 9.480231896042824}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 11.507252344858648}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 13.396554810917378}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 15.200407157659532}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 18.383796672332288}], \"asset\": [-944.0053339265228, 0.053722518491744996, 0.7285683490276337, 0.41600738371610646, 1.0145594421744346, 0.5506562730312348, -1526.488805373192, 0.6562161631822586, 0.9131101472377777, 0.39455169998407363, 96732.87796938886, -0.3085706122279167, 0.04289153963327408, -0.002953442740440369, -6.897095441818237e-05, 1.0247337818145753e-05, -6.733536720275879e-07, 5.357892179870606, -3.818273544311524e-08, 2.2351741790771484e-08, 7.833202601408959, -2.5141239166259766e-08, 9.480231896042824, 11.507252344858648, 13.396554810917378, 15.200407157659532, 18.383796672332288], \"calculationTime\": 0, \"queueingTime\": 21152}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -943.7711806381226}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": 0.08058897590637207}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 1.0928491847515107}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC22\", \"quoteStyle\": \"\", \"value\": 0.6240080194711686}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 1.5218344749927522}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN22\", \"quoteStyle\": \"\", \"value\": 0.8259803075551987}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -1525.9336462859155}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR22\", \"quoteStyle\": \"\", \"value\": 0.9843191504478455}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": 1.3696608170747757}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP22\", \"quoteStyle\": \"\", \"value\": 0.5918243778705597}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 96739.66791574938}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": -0.46285064983367924}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 0.06433258392810821}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -0.004428136157989502}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": -0.00010378005504608154}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 1.5102314949035646e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": -9.993314743041993e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": 8.036837926125527}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -5.5861473083496096e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": 2.608299255371094e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 11.749803520751}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": -2.4199485778808595e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 14.220347791910172}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 17.26088013687134}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 20.09481896739006}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 22.800683971500398}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 27.57565546500683}], \"asset\": [-943.7711806381226, 0.08058897590637207, 1.0928491847515107, 0.6240080194711686, 1.5218344749927522, 0.8259803075551987, -1525.9336462859155, 0.9843191504478455, 1.3696608170747757, 0.5918243778705597, 96739.66791574938, -0.46285064983367924, 0.06433258392810821, -0.004428136157989502, -0.00010378005504608154, 1.5102314949035646e-05, -9.993314743041993e-07, 8.036837926125527, -5.5861473083496096e-08, 2.608299255371094e-08, 11.749803520751, -2.4199485778808595e-08, 14.220347791910172, 17.26088013687134, 20.09481896739006, 22.800683971500398, 27.57565546500683], \"calculationTime\": 0, \"queueingTime\": 21152}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request6f8d6b283e5fe8b79e66975b2ed587a7.json ================================================ { "request_id": "1_1y-3m-6mIRAnnualImpliedVol2020Jan14", "request_hash": "6f8d6b283e5fe8b79e66975b2ed587a7", "type": "MockCalc", "tests": [ "test_unsupported_error_datums" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"Rate Normal Volatility\": 1.0}, \"val\": 0.006052933894743938, \"children\": {}, \"calculationTime\": 30, \"queueingTime\": 16}], [{\"$type\": \"Risk\", \"unit\": {\"Rate Normal Volatility\": 1.0}, \"val\": 0.005639384959057612, \"children\": {}, \"calculationTime\": 30, \"queueingTime\": 16}], [{\"$type\": \"Risk\", \"unit\": {\"Rate Normal Volatility\": 1.0}, \"val\": 0.005811521714097704, \"children\": {}, \"calculationTime\": 30, \"queueingTime\": 16}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request70a863d464f50abba7a86e6d46cb2873.json ================================================ { "request_id": "1_Swaption1-Swaption2IRVega(aggregation_level:Asset)2020Jan14", "request_hash": "70a863d464f50abba7a86e6d46cb2873", "type": "MockCalc", "tests": [ "test_adding_risk_results" ], "mocked_data": "[[[[{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 7888.473420243549}], \"values\": [7888.473420243549], \"calculationTime\": 1024, \"queueingTime\": 29}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 18376.24873330908}], \"values\": [18376.24873330908], \"calculationTime\": 1024, \"queueingTime\": 29}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request717adad42d5d576152b1f6d809a3c8da.json ================================================ { "request_id": "1_5y-10yCashflows2020Jan14", "request_hash": "717adad42d5d576152b1f6d809a3c8da", "type": "MockCalc", "tests": [ "test_cashflows_risk" ], "mocked_data": "[[[[{\"$type\": \"IRPCashflowTable\", \"cashflows\": [{\"accStart\": \"2022-05-19\", \"accEnd\": \"2023-05-19\", \"payDate\": \"2023-05-19\", \"payAmount\": 500000.0, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 1.0130496679840855, \"dayCountFraction\": 1.0, \"paymentType\": \"FIX\"}, {\"accStart\": \"2023-05-19\", \"accEnd\": \"2024-05-20\", \"payDate\": \"2024-05-20\", \"payAmount\": 501388.8888888889, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 1.0147804418053112, \"dayCountFraction\": 1.0027777777777778, \"paymentType\": \"FIX\"}, {\"accStart\": \"2024-05-20\", \"accEnd\": \"2025-05-19\", \"payDate\": \"2025-05-19\", \"payAmount\": 498611.1111111111, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 1.015139047660011, \"dayCountFraction\": 0.9972222222222222, \"paymentType\": \"FIX\"}, {\"accStart\": \"2025-05-19\", \"accEnd\": \"2026-05-19\", \"payDate\": \"2026-05-19\", \"payAmount\": 500000.0, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 1.0141762887966481, \"dayCountFraction\": 1.0, \"paymentType\": \"FIX\"}, {\"accStart\": \"2026-05-19\", \"accEnd\": \"2027-05-19\", \"payDate\": \"2027-05-19\", \"payAmount\": 500000.0, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 1.011715089984835, \"dayCountFraction\": 1.0, \"paymentType\": \"FIX\"}, {\"accStart\": \"2022-05-19\", \"accEnd\": \"2022-11-21\", \"setDate\": \"2022-05-17\", \"payDate\": \"2022-11-21\", \"payAmount\": -70828.59104772244, \"notional\": 100000000.0, \"rate\": -0.0013708759557623696, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.011747289115936, \"dayCountFraction\": 0.5166666666666667, \"paymentType\": \"Flt\"}, {\"accStart\": \"2022-11-21\", \"accEnd\": \"2023-05-19\", \"setDate\": \"2022-11-17\", \"payDate\": \"2023-05-19\", \"payAmount\": -36898.9210132765, \"notional\": 100000000.0, \"rate\": -0.0007421012047362871, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0130496679840855, \"dayCountFraction\": 0.49722222222222223, \"paymentType\": \"Flt\"}, {\"accStart\": \"2023-05-19\", \"accEnd\": \"2023-11-20\", \"setDate\": \"2023-05-17\", \"payDate\": \"2023-11-20\", \"payAmount\": -5157.170115011045, \"notional\": 100000000.0, \"rate\": -0.00010035574277859332, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0140974894630779, \"dayCountFraction\": 0.5138888888888888, \"paymentType\": \"Flt\"}, {\"accStart\": \"2023-11-20\", \"accEnd\": \"2024-05-20\", \"setDate\": \"2023-11-16\", \"payDate\": \"2024-05-20\", \"payAmount\": 27725.824430002707, \"notional\": 100000000.0, \"rate\": 0.0005484229008132404, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0147804418053112, \"dayCountFraction\": 0.5055555555555555, \"paymentType\": \"Flt\"}, {\"accStart\": \"2024-05-20\", \"accEnd\": \"2024-11-19\", \"setDate\": \"2024-05-16\", \"payDate\": \"2024-11-19\", \"payAmount\": 61187.90629491917, \"notional\": 100000000.0, \"rate\": 0.0012036965172770985, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0151194783475241, \"dayCountFraction\": 0.5083333333333333, \"paymentType\": \"Flt\"}, {\"accStart\": \"2024-11-19\", \"accEnd\": \"2025-05-19\", \"setDate\": \"2024-11-15\", \"payDate\": \"2025-05-19\", \"payAmount\": 89431.9077864747, \"notional\": 100000000.0, \"rate\": 0.0017787561769685575, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.015139047660011, \"dayCountFraction\": 0.5027777777777778, \"paymentType\": \"Flt\"}, {\"accStart\": \"2025-05-19\", \"accEnd\": \"2025-11-19\", \"setDate\": \"2025-05-15\", \"payDate\": \"2025-11-19\", \"payAmount\": 121238.05064725433, \"notional\": 100000000.0, \"rate\": 0.0023720488170114983, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0148291893765238, \"dayCountFraction\": 0.5111111111111111, \"paymentType\": \"Flt\"}, {\"accStart\": \"2025-11-19\", \"accEnd\": \"2026-05-19\", \"setDate\": \"2025-11-17\", \"payDate\": \"2026-05-19\", \"payAmount\": 150206.26167761008, \"notional\": 100000000.0, \"rate\": 0.0029875278565712504, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0141762887966481, \"dayCountFraction\": 0.5027777777777778, \"paymentType\": \"Flt\"}, {\"accStart\": \"2026-05-19\", \"accEnd\": \"2026-11-19\", \"setDate\": \"2026-05-15\", \"payDate\": \"2026-11-19\", \"payAmount\": 187044.77926168026, \"notional\": 100000000.0, \"rate\": 0.0036595717681633102, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0131348524711106, \"dayCountFraction\": 0.5111111111111111, \"paymentType\": \"Flt\"}, {\"accStart\": \"2026-11-19\", \"accEnd\": \"2027-05-19\", \"setDate\": \"2026-11-17\", \"payDate\": \"2027-05-19\", \"payAmount\": 221654.08434868802, \"notional\": 100000000.0, \"rate\": 0.004408589522957331, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.011715089984835, \"dayCountFraction\": 0.5027777777777778, \"paymentType\": \"Flt\"}], \"calculationTime\": 160, \"queueingTime\": 43}], [{\"$type\": \"IRPCashflowTable\", \"cashflows\": [{\"accStart\": \"2022-05-19\", \"accEnd\": \"2023-05-19\", \"payDate\": \"2023-05-19\", \"payAmount\": 500000.0, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 1.0130496679840855, \"dayCountFraction\": 1.0, \"paymentType\": \"FIX\"}, {\"accStart\": \"2023-05-19\", \"accEnd\": \"2024-05-20\", \"payDate\": \"2024-05-20\", \"payAmount\": 501388.8888888889, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 1.0147804418053112, \"dayCountFraction\": 1.0027777777777778, \"paymentType\": \"FIX\"}, {\"accStart\": \"2024-05-20\", \"accEnd\": \"2025-05-19\", \"payDate\": \"2025-05-19\", \"payAmount\": 498611.1111111111, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 1.015139047660011, \"dayCountFraction\": 0.9972222222222222, \"paymentType\": \"FIX\"}, {\"accStart\": \"2025-05-19\", \"accEnd\": \"2026-05-19\", \"payDate\": \"2026-05-19\", \"payAmount\": 500000.0, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 1.0141762887966481, \"dayCountFraction\": 1.0, \"paymentType\": \"FIX\"}, {\"accStart\": \"2026-05-19\", \"accEnd\": \"2027-05-19\", \"payDate\": \"2027-05-19\", \"payAmount\": 500000.0, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 1.011715089984835, \"dayCountFraction\": 1.0, \"paymentType\": \"FIX\"}, {\"accStart\": \"2027-05-19\", \"accEnd\": \"2028-05-19\", \"payDate\": \"2028-05-19\", \"payAmount\": 500000.0, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 1.0077444832736635, \"dayCountFraction\": 1.0, \"paymentType\": \"FIX\"}, {\"accStart\": \"2028-05-19\", \"accEnd\": \"2029-05-21\", \"payDate\": \"2029-05-21\", \"payAmount\": 502777.77777777775, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 1.0024606118259742, \"dayCountFraction\": 1.0055555555555555, \"paymentType\": \"FIX\"}, {\"accStart\": \"2029-05-21\", \"accEnd\": \"2030-05-20\", \"payDate\": \"2030-05-20\", \"payAmount\": 498611.1111111111, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 0.9960648625729001, \"dayCountFraction\": 0.9972222222222222, \"paymentType\": \"FIX\"}, {\"accStart\": \"2030-05-20\", \"accEnd\": \"2031-05-19\", \"payDate\": \"2031-05-19\", \"payAmount\": 498611.1111111111, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 0.9887564328731777, \"dayCountFraction\": 0.9972222222222222, \"paymentType\": \"FIX\"}, {\"accStart\": \"2031-05-19\", \"accEnd\": \"2032-05-19\", \"payDate\": \"2032-05-19\", \"payAmount\": 500000.0, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 0.9806521541949186, \"dayCountFraction\": 1.0, \"paymentType\": \"FIX\"}, {\"accStart\": \"2022-05-19\", \"accEnd\": \"2022-11-21\", \"setDate\": \"2022-05-17\", \"payDate\": \"2022-11-21\", \"payAmount\": -70828.59104772244, \"notional\": 100000000.0, \"rate\": -0.0013708759557623696, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.011747289115936, \"dayCountFraction\": 0.5166666666666667, \"paymentType\": \"Flt\"}, {\"accStart\": \"2022-11-21\", \"accEnd\": \"2023-05-19\", \"setDate\": \"2022-11-17\", \"payDate\": \"2023-05-19\", \"payAmount\": -36898.9210132765, \"notional\": 100000000.0, \"rate\": -0.0007421012047362871, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0130496679840855, \"dayCountFraction\": 0.49722222222222223, \"paymentType\": \"Flt\"}, {\"accStart\": \"2023-05-19\", \"accEnd\": \"2023-11-20\", \"setDate\": \"2023-05-17\", \"payDate\": \"2023-11-20\", \"payAmount\": -5157.170115011045, \"notional\": 100000000.0, \"rate\": -0.00010035574277859332, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0140974894630779, \"dayCountFraction\": 0.5138888888888888, \"paymentType\": \"Flt\"}, {\"accStart\": \"2023-11-20\", \"accEnd\": \"2024-05-20\", \"setDate\": \"2023-11-16\", \"payDate\": \"2024-05-20\", \"payAmount\": 27725.824430002707, \"notional\": 100000000.0, \"rate\": 0.0005484229008132404, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0147804418053112, \"dayCountFraction\": 0.5055555555555555, \"paymentType\": \"Flt\"}, {\"accStart\": \"2024-05-20\", \"accEnd\": \"2024-11-19\", \"setDate\": \"2024-05-16\", \"payDate\": \"2024-11-19\", \"payAmount\": 61187.90629491917, \"notional\": 100000000.0, \"rate\": 0.0012036965172770985, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0151194783475241, \"dayCountFraction\": 0.5083333333333333, \"paymentType\": \"Flt\"}, {\"accStart\": \"2024-11-19\", \"accEnd\": \"2025-05-19\", \"setDate\": \"2024-11-15\", \"payDate\": \"2025-05-19\", \"payAmount\": 89431.9077864747, \"notional\": 100000000.0, \"rate\": 0.0017787561769685575, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.015139047660011, \"dayCountFraction\": 0.5027777777777778, \"paymentType\": \"Flt\"}, {\"accStart\": \"2025-05-19\", \"accEnd\": \"2025-11-19\", \"setDate\": \"2025-05-15\", \"payDate\": \"2025-11-19\", \"payAmount\": 121238.05064725433, \"notional\": 100000000.0, \"rate\": 0.0023720488170114983, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0148291893765238, \"dayCountFraction\": 0.5111111111111111, \"paymentType\": \"Flt\"}, {\"accStart\": \"2025-11-19\", \"accEnd\": \"2026-05-19\", \"setDate\": \"2025-11-17\", \"payDate\": \"2026-05-19\", \"payAmount\": 150206.26167761008, \"notional\": 100000000.0, \"rate\": 0.0029875278565712504, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0141762887966481, \"dayCountFraction\": 0.5027777777777778, \"paymentType\": \"Flt\"}, {\"accStart\": \"2026-05-19\", \"accEnd\": \"2026-11-19\", \"setDate\": \"2026-05-15\", \"payDate\": \"2026-11-19\", \"payAmount\": 187044.77926168026, \"notional\": 100000000.0, \"rate\": 0.0036595717681633102, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0131348524711106, \"dayCountFraction\": 0.5111111111111111, \"paymentType\": \"Flt\"}, {\"accStart\": \"2026-11-19\", \"accEnd\": \"2027-05-19\", \"setDate\": \"2026-11-17\", \"payDate\": \"2027-05-19\", \"payAmount\": 221654.08434868802, \"notional\": 100000000.0, \"rate\": 0.004408589522957331, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.011715089984835, \"dayCountFraction\": 0.5027777777777778, \"paymentType\": \"Flt\"}, {\"accStart\": \"2027-05-19\", \"accEnd\": \"2027-11-19\", \"setDate\": \"2027-05-17\", \"payDate\": \"2027-11-19\", \"payAmount\": 261894.0207562703, \"notional\": 100000000.0, \"rate\": 0.005124013449579202, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0098877719690662, \"dayCountFraction\": 0.5111111111111111, \"paymentType\": \"Flt\"}, {\"accStart\": \"2027-11-19\", \"accEnd\": \"2028-05-19\", \"setDate\": \"2027-11-17\", \"payDate\": \"2028-05-19\", \"payAmount\": 291621.3636698251, \"notional\": 100000000.0, \"rate\": 0.00576833466599654, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0077444832736635, \"dayCountFraction\": 0.5055555555555555, \"paymentType\": \"Flt\"}, {\"accStart\": \"2028-05-19\", \"accEnd\": \"2028-11-20\", \"setDate\": \"2028-05-17\", \"payDate\": \"2028-11-20\", \"payAmount\": 327612.39756220474, \"notional\": 100000000.0, \"rate\": 0.006375160168778039, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0052411706862365, \"dayCountFraction\": 0.5138888888888888, \"paymentType\": \"Flt\"}, {\"accStart\": \"2028-11-20\", \"accEnd\": \"2029-05-21\", \"setDate\": \"2028-11-16\", \"payDate\": \"2029-05-21\", \"payAmount\": 350438.9598987467, \"notional\": 100000000.0, \"rate\": 0.0069317596463488364, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0024606118259742, \"dayCountFraction\": 0.5055555555555555, \"paymentType\": \"Flt\"}, {\"accStart\": \"2029-05-21\", \"accEnd\": \"2029-11-19\", \"setDate\": \"2029-05-17\", \"payDate\": \"2029-11-19\", \"payAmount\": 376999.0923760699, \"notional\": 100000000.0, \"rate\": 0.007457124904142043, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 0.9993898039067624, \"dayCountFraction\": 0.5055555555555555, \"paymentType\": \"Flt\"}, {\"accStart\": \"2029-11-19\", \"accEnd\": \"2030-05-20\", \"setDate\": \"2029-11-15\", \"payDate\": \"2030-05-20\", \"payAmount\": 401820.13595972955, \"notional\": 100000000.0, \"rate\": 0.007948090601401244, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 0.9960648625729001, \"dayCountFraction\": 0.5055555555555555, \"paymentType\": \"Flt\"}, {\"accStart\": \"2030-05-20\", \"accEnd\": \"2030-11-19\", \"setDate\": \"2030-05-16\", \"payDate\": \"2030-11-19\", \"payAmount\": 426267.6974761437, \"notional\": 100000000.0, \"rate\": 0.008385594048711024, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 0.9924939070664874, \"dayCountFraction\": 0.5083333333333333, \"paymentType\": \"Flt\"}, {\"accStart\": \"2030-11-19\", \"accEnd\": \"2031-05-19\", \"setDate\": \"2030-11-15\", \"payDate\": \"2031-05-19\", \"payAmount\": 441954.3973012363, \"notional\": 100000000.0, \"rate\": 0.00879025320599144, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 0.9887564328731777, \"dayCountFraction\": 0.5027777777777778, \"paymentType\": \"Flt\"}, {\"accStart\": \"2031-05-19\", \"accEnd\": \"2031-11-19\", \"setDate\": \"2031-05-15\", \"payDate\": \"2031-11-19\", \"payAmount\": 468753.4322965314, \"notional\": 100000000.0, \"rate\": 0.009171262805801703, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 0.9847665928808156, \"dayCountFraction\": 0.5111111111111111, \"paymentType\": \"Flt\"}, {\"accStart\": \"2031-11-19\", \"accEnd\": \"2032-05-19\", \"setDate\": \"2031-11-17\", \"payDate\": \"2032-05-19\", \"payAmount\": 481268.0671174574, \"notional\": 100000000.0, \"rate\": 0.009519588140784872, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 0.9806521541949186, \"dayCountFraction\": 0.5055555555555555, \"paymentType\": \"Flt\"}], \"calculationTime\": 160, \"queueingTime\": 43}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request73d0ff7060259d01f1bdbbd7acda5686.json ================================================ { "request_id": "1_5y-10y-5y-10yResolvedInstrumentValuestoday", "request_hash": "73d0ff7060259d01f1bdbbd7acda5686", "type": "MockCalc", "tests": [ "test_multi_scenario", "test_resolve_to_frame" ], "mocked_data": "[[[[{\"payOrReceive\": \"Pay\", \"terminationDate\": \"2027-05-19\", \"notionalCurrency\": \"EUR\", \"effectiveDate\": \"2022-05-19\", \"principalExchange\": \"None\", \"floatingRateOption\": \"EUR-EURIBOR-Telerate\", \"floatingRateDesignatedMaturity\": \"6m\", \"floatingRateFrequency\": \"6m\", \"floatingRateDayCountFraction\": \"ACT/360\", \"floatingRateBusinessDayConvention\": \"Modified Following\", \"fixedRateFrequency\": \"1y\", \"fixedRateDayCountFraction\": \"30/360\", \"fixedRateBusinessDayConvention\": \"Modified Following\", \"fee\": 0.0, \"feeCurrency\": \"EUR\", \"feePaymentDate\": \"2022-05-19\", \"fixedFirstStub\": \"2022-05-19\", \"floatingFirstStub\": \"2022-05-19\", \"fixedLastStub\": \"2027-05-19\", \"floatingLastStub\": \"2027-05-19\", \"fixedHolidays\": \"Target\", \"floatingHolidays\": \"Target\", \"rollConvention\": \"Default\", \"assetClass\": \"Rates\", \"type\": \"Swap\", \"fixedRate\": -0.005, \"notionalAmount\": 100000000.0, \"floatingRateSpread\": 0.0, \"$type\": \"LegDefinition\", \"calculationTime\": 99, \"queueingTime\": -88}], [{\"payOrReceive\": \"Pay\", \"terminationDate\": \"2032-05-19\", \"notionalCurrency\": \"EUR\", \"effectiveDate\": \"2022-05-19\", \"principalExchange\": \"None\", \"floatingRateOption\": \"EUR-EURIBOR-Telerate\", \"floatingRateDesignatedMaturity\": \"6m\", \"floatingRateFrequency\": \"6m\", \"floatingRateDayCountFraction\": \"ACT/360\", \"floatingRateBusinessDayConvention\": \"Modified Following\", \"fixedRateFrequency\": \"1y\", \"fixedRateDayCountFraction\": \"30/360\", \"fixedRateBusinessDayConvention\": \"Modified Following\", \"fee\": 0.0, \"feeCurrency\": \"EUR\", \"feePaymentDate\": \"2022-05-19\", \"fixedFirstStub\": \"2022-05-19\", \"floatingFirstStub\": \"2022-05-19\", \"fixedLastStub\": \"2032-05-19\", \"floatingLastStub\": \"2032-05-19\", \"fixedHolidays\": \"Target\", \"floatingHolidays\": \"Target\", \"rollConvention\": \"Default\", \"assetClass\": \"Rates\", \"type\": \"Swap\", \"fixedRate\": -0.005, \"notionalAmount\": 100000000.0, \"floatingRateSpread\": 0.0, \"$type\": \"LegDefinition\", \"calculationTime\": 99, \"queueingTime\": -88}], [{\"payOrReceive\": \"Pay\", \"terminationDate\": \"2027-05-17\", \"notionalCurrency\": \"GBP\", \"effectiveDate\": \"2022-05-17\", \"principalExchange\": \"None\", \"floatingRateOption\": \"GBP-SONIA-COMPOUND\", \"floatingRateDesignatedMaturity\": \"1y\", \"floatingRateFrequency\": \"1y\", \"floatingRateBusinessDayConvention\": \"Modified Following\", \"fixedRateFrequency\": \"1y\", \"fixedRateBusinessDayConvention\": \"Modified Following\", \"fee\": 0.0, \"feeCurrency\": \"GBP\", \"feePaymentDate\": \"2022-05-19\", \"fixedFirstStub\": \"2022-05-17\", \"floatingFirstStub\": \"2022-05-17\", \"fixedLastStub\": \"2027-05-17\", \"floatingLastStub\": \"2027-05-17\", \"fixedHolidays\": \"London\", \"floatingHolidays\": \"London\", \"rollConvention\": \"Default\", \"assetClass\": \"Rates\", \"type\": \"Swap\", \"fixedRate\": -0.005, \"notionalAmount\": 100000000.0, \"floatingRateSpread\": 0.0, \"$type\": \"LegDefinition\", \"calculationTime\": 91, \"queueingTime\": -88}], [{\"payOrReceive\": \"Pay\", \"terminationDate\": \"2032-05-17\", \"notionalCurrency\": \"GBP\", \"effectiveDate\": \"2022-05-17\", \"principalExchange\": \"None\", \"floatingRateOption\": \"GBP-SONIA-COMPOUND\", \"floatingRateDesignatedMaturity\": \"1y\", \"floatingRateFrequency\": \"1y\", \"floatingRateBusinessDayConvention\": \"Modified Following\", \"fixedRateFrequency\": \"1y\", \"fixedRateBusinessDayConvention\": \"Modified Following\", \"fee\": 0.0, \"feeCurrency\": \"GBP\", \"feePaymentDate\": \"2022-05-19\", \"fixedFirstStub\": \"2022-05-17\", \"floatingFirstStub\": \"2022-05-17\", \"fixedLastStub\": \"2032-05-17\", \"floatingLastStub\": \"2032-05-17\", \"fixedHolidays\": \"London\", \"floatingHolidays\": \"London\", \"rollConvention\": \"Default\", \"assetClass\": \"Rates\", \"type\": \"Swap\", \"fixedRate\": -0.005, \"notionalAmount\": 100000000.0, \"floatingRateSpread\": 0.0, \"$type\": \"LegDefinition\", \"calculationTime\": 91, \"queueingTime\": -88}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request77e65ffbd857c39cf14a9ad74279c7ba.json ================================================ { "request_id": "1_5y-10yPricetodaymultiscenario+roll fwd scenario", "request_hash": "77e65ffbd857c39cf14a9ad74279c7ba", "type": "MockCalc", "tests": [ "test_composite_multi_scenario" ], "mocked_data": "[[[[{\"$type\": \"Table\", \"rows\": [{\"label\": \"ir curve(parallel=0bp,slope=0bp,pivot=18y,upper cutoff=30y,lower cutoff=5y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 14737115.99264182, \"children\": {}}}, {\"label\": \"ir curve(parallel=5bp,slope=0bp,pivot=0y,upper cutoff=0y,lower cutoff=0y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 14973836.207151327, \"children\": {}}}], \"calculationTime\": 6101, \"queueingTime\": 179}], [{\"$type\": \"Table\", \"rows\": [{\"label\": \"ir curve(parallel=0bp,slope=0bp,pivot=18y,upper cutoff=30y,lower cutoff=5y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 28144265.885281473, \"children\": {}}}, {\"label\": \"ir curve(parallel=5bp,slope=0bp,pivot=0y,upper cutoff=0y,lower cutoff=0y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 28534715.532613628, \"children\": {}}}], \"calculationTime\": 6101, \"queueingTime\": 179}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request7833cccde5be44847d2834c320efac2b.json ================================================ { "request_id": "1_5y-10y-5y-10y-5y-10y-5y-10yIRDelta2020Jan14", "request_hash": "7833cccde5be44847d2834c320efac2b", "type": "MockCalc", "tests": [ "test_bucketed_risks" ], "mocked_data": "[[[[{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -1690.4274549369813}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -4.1370391845703126e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -2431.593033273697}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -3786.9843693626403}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -2482.780788569641}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -2595.327604275513}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -2482.307131084442}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -2469.640842456055}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -2483.5299607940674}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -2166.135343655777}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 1478.9622806194307}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": -51.95791416511536}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": -0.03469709815979004}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.0002544200897216797}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": -8.063964843750001e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -1.4389038085937501e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -5157.727432756425}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -1.865386962890625e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 990.278335987854}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 5.889892578125e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 736.5468615589142}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"60Y\", \"quoteStyle\": \"\", \"value\": -1.25885009765625e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -8696.432763245773}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"70Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 62440.1311512455}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"80Y\", \"quoteStyle\": \"\", \"value\": -1.461029052734375e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 33411.28173765144}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": -8116.772429734802}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -1.0129762042999269}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -1.0167831504821778}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -0.00028923263549804687}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 1.9073486328125e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 1.0453033447265626e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 1.0632324218750001e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 0.00028649139404296876}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 8.663177490234375e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": 0.00011826019287109375}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -1.0531234741210937e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": -3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": -3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": -3.814697265625e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [-1690.4274549369813, -4.1370391845703126e-06, -2431.593033273697, -3786.9843693626403, -2482.780788569641, -2595.327604275513, -2482.307131084442, -2469.640842456055, -2483.5299607940674, -2166.135343655777, 1478.9622806194307, -51.95791416511536, -0.03469709815979004, 0.0002544200897216797, -8.063964843750001e-05, -1.4389038085937501e-06, -5157.727432756425, -1.865386962890625e-07, 990.278335987854, 5.889892578125e-07, 736.5468615589142, -1.25885009765625e-08, -8696.432763245773, 1.52587890625e-08, 62440.1311512455, -1.461029052734375e-07, 33411.28173765144, -8116.772429734802, -1.0129762042999269, -1.0167831504821778, -0.00028923263549804687, 1.9073486328125e-09, 1.0453033447265626e-05, 1.0632324218750001e-05, 0.00028649139404296876, 8.663177490234375e-05, 0.00011826019287109375, -1.0531234741210937e-05, 0.0, 0.0, 3.814697265625e-10, 0.0, 0.0, 0.0, 0.0, -3.814697265625e-10, 0.0, -3.814697265625e-10, -3.814697265625e-10, 0.0, 3.814697265625e-10, 0.0, 0.0, -3.814697265625e-10, 0.0], \"calculationTime\": 4948, \"queueingTime\": -50737}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -1702.1810805397035}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -5.011749267578125e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -2444.868805497742}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -3789.494718639374}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -2499.9662538970947}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -2599.2179568969727}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -2499.5690949165346}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -2481.1379633499146}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -2500.6865667884827}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -2175.6149885375976}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": -22053.57870413742}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 138585.68797151643}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 12498.341227219391}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -2039.943051603699}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 418.2003437355042}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -35.16492511367798}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -5264.257787085724}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -0.2032023864746094}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 991.6761070083619}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 0.023092861938476563}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -873.0462442237855}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"60Y\", \"quoteStyle\": \"\", \"value\": -0.0007514205932617188}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -619.1557262870789}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"70Y\", \"quoteStyle\": \"\", \"value\": -5.632858276367188e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": -348.2752563781738}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"80Y\", \"quoteStyle\": \"\", \"value\": 3.467559814453125e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": -1793.3002309722901}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 7300.043917005158}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -2.951691698455811}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -2.962784684753418}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -0.0008427909851074219}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 5.340576171875e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 3.0458831787109376e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 3.098297119140625e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 0.0008348014831542969}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 0.0002524337768554688}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": 0.00034459686279296876}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -3.0686187744140625e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [-1702.1810805397035, -5.011749267578125e-06, -2444.868805497742, -3789.494718639374, -2499.9662538970947, -2599.2179568969727, -2499.5690949165346, -2481.1379633499146, -2500.6865667884827, -2175.6149885375976, -22053.57870413742, 138585.68797151643, 12498.341227219391, -2039.943051603699, 418.2003437355042, -35.16492511367798, -5264.257787085724, -0.2032023864746094, 991.6761070083619, 0.023092861938476563, -873.0462442237855, -0.0007514205932617188, -619.1557262870789, -5.632858276367188e-05, -348.2752563781738, 3.467559814453125e-06, -1793.3002309722901, 7300.043917005158, -2.951691698455811, -2.962784684753418, -0.0008427909851074219, 5.340576171875e-09, 3.0458831787109376e-05, 3.098297119140625e-05, 0.0008348014831542969, 0.0002524337768554688, 0.00034459686279296876, -3.0686187744140625e-05, 0.0, 0.0, 1.52587890625e-09, 0.0, 0.0, 0.0, 0.0, -1.52587890625e-09, 0.0, -7.62939453125e-10, -7.62939453125e-10, 0.0, 1.52587890625e-09, 0.0, 0.0, -7.62939453125e-10, 0.0], \"calculationTime\": 4948, \"queueingTime\": -50737}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"CASH\", \"point\": \"1 DAY\", \"quoteStyle\": \"\", \"value\": 1.953647787475586}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -2021.654846144867}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -2837.5144553802493}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -4316.762227175141}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -2870.856843566895}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -2882.6234098114014}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -2877.2317279357912}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -2914.1873167549134}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -2868.0600389816286}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -2484.2367954421998}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 1697.7549697944642}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": -58.98864248580933}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": -0.2841819961547852}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.020826010131835937}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": -0.0004878494262695313}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -2.7230072021484376e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -5492.3295814102175}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 6.9427490234375e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 408.04524015197757}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 3.44085693359375e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 836.7955548400879}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"60Y\", \"quoteStyle\": \"\", \"value\": 2.13623046875e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -10565.023507267762}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"70Y\", \"quoteStyle\": \"\", \"value\": 3.753662109375e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 69746.63193368455}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"80Y\", \"quoteStyle\": \"\", \"value\": -2.7313232421875e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 38275.40590349732}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": -9243.928722629547}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -2.0324371337890628}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -2.040075395965576}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -0.0005803176879882813}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 6.103515625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 2.09716796875e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 2.1333312988281252e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 0.0005748146057128906}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 0.00017381668090820314}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": 0.0002372772216796875}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -2.112884521484375e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": 7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": -7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [1.953647787475586, -2021.654846144867, -2837.5144553802493, -4316.762227175141, -2870.856843566895, -2882.6234098114014, -2877.2317279357912, -2914.1873167549134, -2868.0600389816286, -2484.2367954421998, 1697.7549697944642, -58.98864248580933, -0.2841819961547852, 0.020826010131835937, -0.0004878494262695313, -2.7230072021484376e-05, -5492.3295814102175, 6.9427490234375e-07, 408.04524015197757, 3.44085693359375e-07, 836.7955548400879, 2.13623046875e-07, -10565.023507267762, 3.753662109375e-07, 69746.63193368455, -2.7313232421875e-07, 38275.40590349732, -9243.928722629547, -2.0324371337890628, -2.040075395965576, -0.0005803176879882813, 6.103515625e-09, 2.09716796875e-05, 2.1333312988281252e-05, 0.0005748146057128906, 0.00017381668090820314, 0.0002372772216796875, -2.112884521484375e-05, 0.0, 7.62939453125e-10, 0.0, -7.62939453125e-10, 7.62939453125e-10, 0.0, 0.0, 7.62939453125e-10, 0.0, 0.0, 0.0, 0.0, -7.62939453125e-10, 0.0, -7.62939453125e-10, 0.0, 0.0], \"calculationTime\": 5636, \"queueingTime\": -50737}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"CASH\", \"point\": \"1 DAY\", \"quoteStyle\": \"\", \"value\": 4.230279399108887}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -2022.904193765259}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -2840.3832717819214}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -4316.708795300293}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -2875.0211539001466}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -2884.1831563629153}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -2882.340762391663}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -2916.8764971588134}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -2871.8497097885133}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -2485.547314588928}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": -25240.868663520814}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 154112.73422039187}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 13916.807480566407}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -2247.4538974853517}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 459.34853724365234}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -38.620221380615234}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -5539.929966027832}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -0.2598369415283203}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 539.0027855133056}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 0.031217132568359375}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -894.9394072052003}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"60Y\", \"quoteStyle\": \"\", \"value\": -0.0013491973876953126}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -973.3587597335816}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"70Y\", \"quoteStyle\": \"\", \"value\": -0.00011527862548828125}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": -999.7791904037476}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"80Y\", \"quoteStyle\": \"\", \"value\": 7.60345458984375e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": -2265.847222779846}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"GBP\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 7722.4546251083375}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -4.366976580810547}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -4.383388459777832}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -0.0012468933105468751}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 1.220703125e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 4.5059204101562504e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 4.583740234375e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 0.0012350708007812501}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 0.00037346954345703125}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": 0.0005098236083984375}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -4.539794921875e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": 3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": -3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [4.230279399108887, -2022.904193765259, -2840.3832717819214, -4316.708795300293, -2875.0211539001466, -2884.1831563629153, -2882.340762391663, -2916.8764971588134, -2871.8497097885133, -2485.547314588928, -25240.868663520814, 154112.73422039187, 13916.807480566407, -2247.4538974853517, 459.34853724365234, -38.620221380615234, -5539.929966027832, -0.2598369415283203, 539.0027855133056, 0.031217132568359375, -894.9394072052003, -0.0013491973876953126, -973.3587597335816, -0.00011527862548828125, -999.7791904037476, 7.60345458984375e-06, -2265.847222779846, 7722.4546251083375, -4.366976580810547, -4.383388459777832, -0.0012468933105468751, 1.220703125e-08, 4.5059204101562504e-05, 4.583740234375e-05, 0.0012350708007812501, 0.00037346954345703125, 0.0005098236083984375, -4.539794921875e-05, 0.0, 3.0517578125e-09, 0.0, -3.0517578125e-09, 3.0517578125e-09, 0.0, 0.0, 3.0517578125e-09, 0.0, 0.0, 0.0, 0.0, -3.0517578125e-09, 0.0, -3.0517578125e-09, 0.0, 0.0], \"calculationTime\": 5636, \"queueingTime\": -50737}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"CASH\", \"point\": \"6 MONTH\", \"quoteStyle\": \"\", \"value\": -0.003426488567888737}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": 1.8924474716186525e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP OIS\", \"point\": \"3M\", \"quoteStyle\": \"\", \"value\": 0.003687792980670929}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 3.150403499603272e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": -2.974271774291992e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 1.9371509552001955e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"18M\", \"quoteStyle\": \"\", \"value\": 9.85729679465294e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"1Y\", \"quoteStyle\": \"\", \"value\": -0.4156471497833729}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 1.4901161193847657e-12}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 5.0663948059082035e-11}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"2Y\", \"quoteStyle\": \"\", \"value\": -0.820788862092793}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 9.98377799987793e-11}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -1.2302738214492799}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -3.0547380447387697e-10}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": -1.630941105248034}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 453.43538317149137}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -0.023857910101115706}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 0.006205346961319447}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": -0.000968910138309002}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 9.131789207458497e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -0.005676868779957295}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -0.00569820346236229}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -1.620905101299286e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 1.3411045074462892e-11}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 5.8574974536895756e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 5.958378314971924e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 1.6055375337600708e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 4.854947328567505e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": 6.627455353736878e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -5.9011578559875494e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": -1.4901161193847657e-12}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": -1.4901161193847657e-12}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 1.4901161193847657e-12}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [-0.003426488567888737, 1.8924474716186525e-10, 0.003687792980670929, 3.150403499603272e-08, -2.974271774291992e-09, 1.9371509552001955e-10, 9.85729679465294e-05, -0.4156471497833729, 1.4901161193847657e-12, 5.0663948059082035e-11, -0.820788862092793, 9.98377799987793e-11, -1.2302738214492799, -3.0547380447387697e-10, -1.630941105248034, 453.43538317149137, -0.023857910101115706, 0.006205346961319447, -0.000968910138309002, 9.131789207458497e-06, -0.005676868779957295, -0.00569820346236229, -1.620905101299286e-06, 1.3411045074462892e-11, 5.8574974536895756e-08, 5.958378314971924e-08, 1.6055375337600708e-06, 4.854947328567505e-07, 6.627455353736878e-07, -5.9011578559875494e-08, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.4901161193847657e-12, -1.4901161193847657e-12, 0.0, 0.0, 0.0, 1.4901161193847657e-12, 0.0], \"calculationTime\": 3500, \"queueingTime\": -50737}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"CASH\", \"point\": \"6 MONTH\", \"quoteStyle\": \"\", \"value\": -0.004030118212103844}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -2.020597457885742e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP OIS\", \"point\": \"3M\", \"quoteStyle\": \"\", \"value\": 0.004338474157452583}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 904.949480861342}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": -0.008514890649914742}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 0.0012377174019813537}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"18M\", \"quoteStyle\": \"\", \"value\": 6.675305962562562e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"1Y\", \"quoteStyle\": \"\", \"value\": -0.49509316201210024}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -9.644158184528351e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": -4.789769649505615e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"2Y\", \"quoteStyle\": \"\", \"value\": -0.977774145168066}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 1.0386109352111817e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -1.4645683102697136}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -2.1666288375854492e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": -1.949539046701789}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -2.440646692687273}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -2.9439223373353482}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": -3.4519172049969438}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": -3.8954180422455074}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"JPY\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": -4.373908163827658}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -0.013506656792759896}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -0.013557417207956314}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -3.8565278053283695e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 2.980232238769531e-11}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 1.393646001815796e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 1.4176368713378907e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 3.819963335990906e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 1.1551111936569215e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": 1.5768259763717652e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -1.4040470123291015e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": -2.9802322387695314e-12}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": -2.9802322387695314e-12}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 2.9802322387695314e-12}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [-0.004030118212103844, -2.020597457885742e-09, 0.004338474157452583, 904.949480861342, -0.008514890649914742, 0.0012377174019813537, 6.675305962562562e-05, -0.49509316201210024, -9.644158184528351e-05, -4.789769649505615e-07, -0.977774145168066, 1.0386109352111817e-07, -1.4645683102697136, -2.1666288375854492e-09, -1.949539046701789, -2.440646692687273, -2.9439223373353482, -3.4519172049969438, -3.8954180422455074, -4.373908163827658, -0.013506656792759896, -0.013557417207956314, -3.8565278053283695e-06, 2.980232238769531e-11, 1.393646001815796e-07, 1.4176368713378907e-07, 3.819963335990906e-06, 1.1551111936569215e-06, 1.5768259763717652e-06, -1.4040470123291015e-07, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -2.9802322387695314e-12, -2.9802322387695314e-12, 0.0, 0.0, 0.0, 2.9802322387695314e-12, 0.0], \"calculationTime\": 3500, \"queueingTime\": -50737}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -1504.2859039421082}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -2.590055401611328}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -2121.598490221405}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -3276.2837234481813}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -2191.8162534729004}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -2179.781961048889}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -2202.504174131012}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -2259.9294067466735}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -2187.5757053924563}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -1880.7866588813783}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 1230.295327883911}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": -43.12170168762207}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": -0.10048918991088868}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.010470124053955079}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": -0.0010130615234375002}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -3.7934112548828126e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": 4.6714782714843755e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -4739.603673642731}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -1.800537109375e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": -2.89154052734375e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 236.5533310409546}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 2.2125244140625e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 35.29297794265747}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -8107.460819102478}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 51624.50817723465}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 27755.077500284577}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": -6767.362626778412}], \"asset\": [-1504.2859039421082, -2.590055401611328, -2121.598490221405, -3276.2837234481813, -2191.8162534729004, -2179.781961048889, -2202.504174131012, -2259.9294067466735, -2187.5757053924563, -1880.7866588813783, 1230.295327883911, -43.12170168762207, -0.10048918991088868, 0.010470124053955079, -0.0010130615234375002, -3.7934112548828126e-05, 4.6714782714843755e-06, -4739.603673642731, -1.800537109375e-07, -2.89154052734375e-07, 236.5533310409546, 2.2125244140625e-07, 35.29297794265747, -8107.460819102478, 51624.50817723465, 27755.077500284577, -6767.362626778412], \"calculationTime\": 4244, \"queueingTime\": -50737}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -1506.7299508651734}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -5.35614610748291}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -2126.2219676071168}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -3277.3818776916505}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -2191.4022634384155}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -2180.5296789871218}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -2201.9370928604126}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -2254.040299702454}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -2187.184297581482}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -1880.1977522125244}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": -19671.15702516327}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 110337.98680246582}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 10238.56742588501}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -1677.1803941467285}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 343.29863363342287}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -26.04604114227295}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": -1.3080369873046875}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -4735.228495040894}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 0.12988988952636718}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": -0.0036573211669921877}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 353.8679312362671}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": -0.0003820037841796875}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -1162.1089229263307}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -1156.4994524536132}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": -1113.1952495025635}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": -2356.65559788208}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 5125.081598124695}], \"asset\": [-1506.7299508651734, -5.35614610748291, -2126.2219676071168, -3277.3818776916505, -2191.4022634384155, -2180.5296789871218, -2201.9370928604126, -2254.040299702454, -2187.184297581482, -1880.1977522125244, -19671.15702516327, 110337.98680246582, 10238.56742588501, -1677.1803941467285, 343.29863363342287, -26.04604114227295, -1.3080369873046875, -4735.228495040894, 0.12988988952636718, -0.0036573211669921877, 353.8679312362671, -0.0003820037841796875, -1162.1089229263307, -1156.4994524536132, -1113.1952495025635, -2356.65559788208, 5125.081598124695], \"calculationTime\": 4244, \"queueingTime\": -50737}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request78dbb0af61a1e23a0f2614eb98bb46c0.json ================================================ { "request_id": "1_5y-10yDollarPrice-Pricetodaymultiscenario", "request_hash": "78dbb0af61a1e23a0f2614eb98bb46c0", "type": "MockCalc", "tests": [ "test_multi_scenario" ], "mocked_data": "[[[[{\"$type\": \"Table\", \"rows\": [{\"label\": \"ir curve(parallel=0bp,slope=0bp,pivot=18y,upper cutoff=30y,lower cutoff=5y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 14425955.584723271, \"children\": {}}}, {\"label\": \"ir curve(parallel=5bp,slope=0bp,pivot=0y,upper cutoff=0y,lower cutoff=0y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 14663567.24341668, \"children\": {}}}], \"calculationTime\": 27, \"queueingTime\": 123}], [{\"$type\": \"Table\", \"rows\": [{\"label\": \"ir curve(parallel=0bp,slope=0bp,pivot=18y,upper cutoff=30y,lower cutoff=5y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 27707181.40070747, \"children\": {}}}, {\"label\": \"ir curve(parallel=5bp,slope=0bp,pivot=0y,upper cutoff=0y,lower cutoff=0y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 28100037.66858688, \"children\": {}}}], \"calculationTime\": 27, \"queueingTime\": 123}]], [[{\"$type\": \"Table\", \"rows\": [{\"label\": \"ir curve(parallel=0bp,slope=0bp,pivot=18y,upper cutoff=30y,lower cutoff=5y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 14425955.584723271, \"children\": {}}}, {\"label\": \"ir curve(parallel=5bp,slope=0bp,pivot=0y,upper cutoff=0y,lower cutoff=0y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 14663567.24341668, \"children\": {}}}], \"calculationTime\": 27, \"queueingTime\": 123}], [{\"$type\": \"Table\", \"rows\": [{\"label\": \"ir curve(parallel=0bp,slope=0bp,pivot=18y,upper cutoff=30y,lower cutoff=5y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 27707181.40070747, \"children\": {}}}, {\"label\": \"ir curve(parallel=5bp,slope=0bp,pivot=0y,upper cutoff=0y,lower cutoff=0y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 28100037.66858688, \"children\": {}}}], \"calculationTime\": 27, \"queueingTime\": 123}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request78ee70188fa3e6dce58e75f8df9a701f.json ================================================ { "request_id": "1_5y-10y-5y-10yIRFwdRate-Pricetodaymultiscenario", "request_hash": "78ee70188fa3e6dce58e75f8df9a701f", "type": "MockCalc", "tests": [ "test_multi_scenario" ], "mocked_data": "[[[[{\"$type\": \"Table\", \"rows\": [{\"label\": \"ir curve(parallel=0bp,slope=0bp,pivot=18y,upper cutoff=30y,lower cutoff=5y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"Rate\": 1.0}, \"val\": 0.013165585678999989, \"children\": {}}}, {\"label\": \"ir curve(parallel=5bp,slope=0bp,pivot=0y,upper cutoff=0y,lower cutoff=0y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"Rate\": 1.0}, \"val\": 0.013165585678999989, \"children\": {}}}], \"calculationTime\": 742, \"queueingTime\": -5494}], [{\"$type\": \"Table\", \"rows\": [{\"label\": \"ir curve(parallel=0bp,slope=0bp,pivot=18y,upper cutoff=30y,lower cutoff=5y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"Rate\": 1.0}, \"val\": 0.01685949177199996, \"children\": {}}}, {\"label\": \"ir curve(parallel=5bp,slope=0bp,pivot=0y,upper cutoff=0y,lower cutoff=0y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"Rate\": 1.0}, \"val\": 0.01685949177199996, \"children\": {}}}], \"calculationTime\": 742, \"queueingTime\": -5494}], [{\"$type\": \"Table\", \"rows\": [{\"label\": \"ir curve(parallel=0bp,slope=0bp,pivot=18y,upper cutoff=30y,lower cutoff=5y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"Rate\": 1.0}, \"val\": 0.019478551278252466, \"children\": {}}}, {\"label\": \"ir curve(parallel=5bp,slope=0bp,pivot=0y,upper cutoff=0y,lower cutoff=0y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"Rate\": 1.0}, \"val\": 0.019478551278252466, \"children\": {}}}], \"calculationTime\": 825, \"queueingTime\": -5494}], [{\"$type\": \"Table\", \"rows\": [{\"label\": \"ir curve(parallel=0bp,slope=0bp,pivot=18y,upper cutoff=30y,lower cutoff=5y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"Rate\": 1.0}, \"val\": 0.018092053221825204, \"children\": {}}}, {\"label\": \"ir curve(parallel=5bp,slope=0bp,pivot=0y,upper cutoff=0y,lower cutoff=0y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"Rate\": 1.0}, \"val\": 0.018092053221825204, \"children\": {}}}], \"calculationTime\": 825, \"queueingTime\": -5494}]], [[{\"$type\": \"Table\", \"rows\": [{\"label\": \"ir curve(parallel=0bp,slope=0bp,pivot=18y,upper cutoff=30y,lower cutoff=5y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 8855060.120079419, \"children\": {}}}, {\"label\": \"ir curve(parallel=5bp,slope=0bp,pivot=0y,upper cutoff=0y,lower cutoff=0y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 8855060.120079419, \"children\": {}}}], \"calculationTime\": 742, \"queueingTime\": -5494}], [{\"$type\": \"Table\", \"rows\": [{\"label\": \"ir curve(parallel=0bp,slope=0bp,pivot=18y,upper cutoff=30y,lower cutoff=5y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 20513416.03191008, \"children\": {}}}, {\"label\": \"ir curve(parallel=5bp,slope=0bp,pivot=0y,upper cutoff=0y,lower cutoff=0y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 20513416.03191008, \"children\": {}}}], \"calculationTime\": 742, \"queueingTime\": -5494}], [{\"$type\": \"Table\", \"rows\": [{\"label\": \"ir curve(parallel=0bp,slope=0bp,pivot=18y,upper cutoff=30y,lower cutoff=5y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 11549606.309459362, \"children\": {}}}, {\"label\": \"ir curve(parallel=5bp,slope=0bp,pivot=0y,upper cutoff=0y,lower cutoff=0y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 11549606.309459362, \"children\": {}}}], \"calculationTime\": 825, \"queueingTime\": -5494}], [{\"$type\": \"Table\", \"rows\": [{\"label\": \"ir curve(parallel=0bp,slope=0bp,pivot=18y,upper cutoff=30y,lower cutoff=5y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 20891715.04796604, \"children\": {}}}, {\"label\": \"ir curve(parallel=5bp,slope=0bp,pivot=0y,upper cutoff=0y,lower cutoff=0y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 20891715.04796604, \"children\": {}}}], \"calculationTime\": 825, \"queueingTime\": -5494}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request7c05dd05540a5377e1487ef4f0c54c48.json ================================================ { "request_id": "15_QuantityScaledAction2_call_2021-12-10-QuantityScaledAction2_put_2021-12-10Price2021Dec10_QuantityScaledAction2_call_2021-12-09-QuantityScaledAction2_put_2021-12-09Price2021Dec10_QuantityScaledAction2_call_2021-12-09-QuantityScaledAction2_put_2021-12-09Price2021Dec09_QuantityScaledAction2_call_2021-12-08-QuantityScaledAction2_put_2021-12-08Price2021Dec10_QuantityScaledAction2_call_2021-12-08-QuantityScaledAction2_put_2021-12-08Price2021Dec09_QuantityScaledAction2_call_2021-12-08-QuantityScaledAction2_put_2021-12-08Price2021Dec08_QuantityScaledAction2_call_2021-12-07-QuantityScaledAction2_put_2021-12-07Price2021Dec10_QuantityScaledAction2_call_2021-12-07-QuantityScaledAction2_put_2021-12-07Price2021Dec09_QuantityScaledAction2_call_2021-12-07-QuantityScaledAction2_put_2021-12-07Price2021Dec08_QuantityScaledAction2_call_2021-12-07-QuantityScaledAction2_put_2021-12-07Price2021Dec07_QuantityScaledAction2_call_2021-12-06-QuantityScaledAction2_put_2021-12-06Price2021Dec10_QuantityScaledAction2_call_2021-12-06-QuantityScaledAction2_put_2021-12-06Price2021Dec09_QuantityScaledAction2_call_2021-12-06-QuantityScaledAction2_put_2021-12-06Price2021Dec08_QuantityScaledAction2_call_2021-12-06-QuantityScaledAction2_put_2021-12-06Price2021Dec07_QuantityScaledAction2_call_2021-12-06-QuantityScaledAction2_put_2021-12-06Price2021Dec06", "request_hash": "7c05dd05540a5377e1487ef4f0c54c48", "type": "MockCalc", "tests": [ "test_add_scaled_action" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":552.2382381835556}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":587.9178915317034}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":513.8590236823744}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":613.5866547793767}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":598.106554796429}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":634.8556191218281}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":416.4673095345929}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":689.9093135412368}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":496.5160052711945}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":706.9765281763965}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":625.3205942346785}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":663.6932908928651}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":252.44281170063715}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":818.6598940126252}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":324.6179228780844}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":827.8506831868941}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":434.02804324237513}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":765.1728515050585}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":615.5156463826219}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":644.6120328340485}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":808.8752194414801}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":400.10608323684266}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":903.7002456171963}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":431.9670886235174}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":1060.7304696512683}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":416.85862198905033}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":1305.7400190625322}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":359.6709132796199}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":752.4189924195607}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":781.0221408795115}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request7c0ed6f293c87c377c58c8d911aaa777.json ================================================ { "request_id": "4_QuantityScaledAction1_call_2021-12-09Price2021Dec10_QuantityScaledAction1_call_2021-12-08Price2021Dec09_QuantityScaledAction1_call_2021-12-07Price2021Dec08_QuantityScaledAction1_call_2021-12-06Price2021Dec07", "request_hash": "7c0ed6f293c87c377c58c8d911aaa777", "type": "MockCalc", "tests": [ "test_add_scaled_action_nav" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":83.47791729987946}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":97.16417775446901}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":122.37019698602136}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":173.53895000226558}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request7dde687744f571fd6650ad7399203ac5.json ================================================ { "request_id": "1_5yIRDelta(aggregation_level:Type, currency:local)-IRDelta(currency:local)today", "request_hash": "7dde687744f571fd6650ad7399203ac5", "type": "MockCalc", "tests": [ "test_finite_difference_params" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Basis Point\":-1.0,\"European Euro\":1.0},\"val\":43346.43065348282}]],[[{\"$type\":\"RiskVector\",\"asset\":[-111.91644246749878,-3.6281560195922853,-126.43895758895874,-64.35023115768433,-73.43292019195557,-1.914025471496582,-129.81101199569702,-71.29572164611817,-127.98115185852052,-63.52961735229493,1.068115234375e-7,-2.7465820312500003e-8,4.6539306640625e-8,3.0517578125e-8,-1.220703125e-8,-3.509521484375e-8,-758.5752531303406,-3.4332275390625004e-8,-1009.7807299606324,9.918212890625001e-9,45889.11573571015,-4.1961669921875004e-8,-0.017659200286865236,-2.3651123046875002e-8,-0.018838437652587893,1.52587890625e-9,0.0033511177062988283,-2.2888183593750002e-7],\"points\":[{\"asset\":\"EUR\",\"class_\":\"CASH\",\"path\":\"\",\"point\":\"CASH STUB\",\"quoteStyle\":\"\",\"type\":\"IR\"},{\"asset\":\"EUR\",\"class_\":\"CASH\",\"path\":\"\",\"point\":\"O/N\",\"quoteStyle\":\"\",\"type\":\"IR\"},{\"asset\":\"EUR\",\"class_\":\"FRA\",\"path\":\"\",\"point\":\"DEC25\",\"quoteStyle\":\"\",\"type\":\"IR\"},{\"asset\":\"EUR\",\"class_\":\"FRA\",\"path\":\"\",\"point\":\"DEC26\",\"quoteStyle\":\"\",\"type\":\"IR\"},{\"asset\":\"EUR\",\"class_\":\"FRA\",\"path\":\"\",\"point\":\"JUN26\",\"quoteStyle\":\"\",\"type\":\"IR\"},{\"asset\":\"EUR\",\"class_\":\"FRA\",\"path\":\"\",\"point\":\"JUN27\",\"quoteStyle\":\"\",\"type\":\"IR\"},{\"asset\":\"EUR\",\"class_\":\"FRA\",\"path\":\"\",\"point\":\"MAR26\",\"quoteStyle\":\"\",\"type\":\"IR\"},{\"asset\":\"EUR\",\"class_\":\"FRA\",\"path\":\"\",\"point\":\"MAR27\",\"quoteStyle\":\"\",\"type\":\"IR\"},{\"asset\":\"EUR\",\"class_\":\"FRA\",\"path\":\"\",\"point\":\"SEP25\",\"quoteStyle\":\"\",\"type\":\"IR\"},{\"asset\":\"EUR\",\"class_\":\"FRA\",\"path\":\"\",\"point\":\"SEP26\",\"quoteStyle\":\"\",\"type\":\"IR\"},{\"asset\":\"EUR\",\"class_\":\"SWAP\",\"path\":\"\",\"point\":\"10Y\",\"quoteStyle\":\"\",\"type\":\"IR\"},{\"asset\":\"EUR\",\"class_\":\"SWAP\",\"path\":\"\",\"point\":\"12Y\",\"quoteStyle\":\"\",\"type\":\"IR\"},{\"asset\":\"EUR\",\"class_\":\"SWAP\",\"path\":\"\",\"point\":\"15Y\",\"quoteStyle\":\"\",\"type\":\"IR\"},{\"asset\":\"EUR\",\"class_\":\"SWAP\",\"path\":\"\",\"point\":\"20Y\",\"quoteStyle\":\"\",\"type\":\"IR\"},{\"asset\":\"EUR\",\"class_\":\"SWAP\",\"path\":\"\",\"point\":\"25Y\",\"quoteStyle\":\"\",\"type\":\"IR\"},{\"asset\":\"EUR\",\"class_\":\"SWAP\",\"path\":\"\",\"point\":\"30Y\",\"quoteStyle\":\"\",\"type\":\"IR\"},{\"asset\":\"EUR\",\"class_\":\"SWAP\",\"path\":\"\",\"point\":\"3Y\",\"quoteStyle\":\"\",\"type\":\"IR\"},{\"asset\":\"EUR\",\"class_\":\"SWAP\",\"path\":\"\",\"point\":\"40Y\",\"quoteStyle\":\"\",\"type\":\"IR\"},{\"asset\":\"EUR\",\"class_\":\"SWAP\",\"path\":\"\",\"point\":\"4Y\",\"quoteStyle\":\"\",\"type\":\"IR\"},{\"asset\":\"EUR\",\"class_\":\"SWAP\",\"path\":\"\",\"point\":\"50Y\",\"quoteStyle\":\"\",\"type\":\"IR\"},{\"asset\":\"EUR\",\"class_\":\"SWAP\",\"path\":\"\",\"point\":\"5Y\",\"quoteStyle\":\"\",\"type\":\"IR\"},{\"asset\":\"EUR\",\"class_\":\"SWAP\",\"path\":\"\",\"point\":\"60Y\",\"quoteStyle\":\"\",\"type\":\"IR\"},{\"asset\":\"EUR\",\"class_\":\"SWAP\",\"path\":\"\",\"point\":\"6Y\",\"quoteStyle\":\"\",\"type\":\"IR\"},{\"asset\":\"EUR\",\"class_\":\"SWAP\",\"path\":\"\",\"point\":\"70Y\",\"quoteStyle\":\"\",\"type\":\"IR\"},{\"asset\":\"EUR\",\"class_\":\"SWAP\",\"path\":\"\",\"point\":\"7Y\",\"quoteStyle\":\"\",\"type\":\"IR\"},{\"asset\":\"EUR\",\"class_\":\"SWAP\",\"path\":\"\",\"point\":\"80Y\",\"quoteStyle\":\"\",\"type\":\"IR\"},{\"asset\":\"EUR\",\"class_\":\"SWAP\",\"path\":\"\",\"point\":\"8Y\",\"quoteStyle\":\"\",\"type\":\"IR\"},{\"asset\":\"EUR\",\"class_\":\"SWAP\",\"path\":\"\",\"point\":\"9Y\",\"quoteStyle\":\"\",\"type\":\"IR\"}],\"unit\":{\"Basis Point\":-1.0,\"European Euro\":1.0}}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request816449d2d775bc083cf300974bdc10af.json ================================================ { "request_id": "5_QuantityScaledAction2_call-QuantityScaledAction2_putResolvedInstrumentValues2021Dec10_QuantityScaledAction2_call-QuantityScaledAction2_putResolvedInstrumentValues2021Dec09_QuantityScaledAction2_call-QuantityScaledAction2_putResolvedInstrumentValues2021Dec08_QuantityScaledAction2_call-QuantityScaledAction2_putResolvedInstrumentValues2021Dec07_QuantityScaledAction2_call-QuantityScaledAction2_putResolvedInstrumentValues2021Dec06", "request_hash": "816449d2d775bc083cf300974bdc10af", "type": "MockCalc", "tests": [ "test_add_scaled_action" ], "mocked_data": "[[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-01-10\",\"expirySettleDate\":\"2022-01-12\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-14\",\"premiumSettlementDate\":\"2021-12-14\",\"settlementDate\":\"2021-12-14\",\"strikePrice\":4199.16,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}],[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-01-10\",\"expirySettleDate\":\"2022-01-12\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Put\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-14\",\"premiumSettlementDate\":\"2021-12-14\",\"settlementDate\":\"2021-12-14\",\"strikePrice\":4199.16,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-01-10\",\"expirySettleDate\":\"2022-01-12\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-13\",\"premiumSettlementDate\":\"2021-12-13\",\"settlementDate\":\"2021-12-13\",\"strikePrice\":4208.3,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}],[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-01-10\",\"expirySettleDate\":\"2022-01-12\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Put\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-13\",\"premiumSettlementDate\":\"2021-12-13\",\"settlementDate\":\"2021-12-13\",\"strikePrice\":4208.3,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-01-10\",\"expirySettleDate\":\"2022-01-12\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-10\",\"premiumSettlementDate\":\"2021-12-10\",\"settlementDate\":\"2021-12-10\",\"strikePrice\":4233.09,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}],[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-01-10\",\"expirySettleDate\":\"2022-01-12\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Put\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-10\",\"premiumSettlementDate\":\"2021-12-10\",\"settlementDate\":\"2021-12-10\",\"strikePrice\":4233.09,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-01-07\",\"expirySettleDate\":\"2022-01-11\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-09\",\"premiumSettlementDate\":\"2021-12-09\",\"settlementDate\":\"2021-12-09\",\"strikePrice\":4276.2,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"SQ\"}],[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-01-07\",\"expirySettleDate\":\"2022-01-11\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Put\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-09\",\"premiumSettlementDate\":\"2021-12-09\",\"settlementDate\":\"2021-12-09\",\"strikePrice\":4276.2,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"SQ\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-01-06\",\"expirySettleDate\":\"2022-01-10\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-08\",\"premiumSettlementDate\":\"2021-12-08\",\"settlementDate\":\"2021-12-08\",\"strikePrice\":4137.11,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}],[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-01-06\",\"expirySettleDate\":\"2022-01-10\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Put\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-08\",\"premiumSettlementDate\":\"2021-12-08\",\"settlementDate\":\"2021-12-08\",\"strikePrice\":4137.11,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request85a1eff8ed194c127249a682db2d27cf.json ================================================ { "request_id": "10_None_2023-06-09_HedgeAction1_test_hedge_transaction_costs_syn_fwd_call-None_2023-06-09_HedgeAction1_test_hedge_transaction_costs_syn_fwd_putEqDelta-Price2023Jun09_None_2023-06-08_HedgeAction1_test_hedge_transaction_costs_syn_fwd_call-None_2023-06-08_HedgeAction1_test_hedge_transaction_costs_syn_fwd_putEqDelta-Price2023Jun08_None_2023-06-07_HedgeAction1_test_hedge_transaction_costs_syn_fwd_call-None_2023-06-07_HedgeAction1_test_hedge_transaction_costs_syn_fwd_putEqDelta-Price2023Jun07_None_2023-06-06_HedgeAction1_test_hedge_transaction_costs_syn_fwd_call-None_2023-06-06_HedgeAction1_test_hedge_transaction_costs_syn_fwd_putEqDelta-Price2023Jun06_None_2023-06-05_HedgeAction1_test_hedge_transaction_costs_syn_fwd_call-None_2023-06-05_HedgeAction1_test_hedge_transaction_costs_syn_fwd_putEqDelta-Price2023Jun05_Action1_test_hedge_transaction_costs_SPX_opt_2023-06-05EqDelta-Price2023Jun09_Action1_test_hedge_transaction_costs_SPX_opt_2023-06-05EqDelta-Price2023Jun08_Action1_test_hedge_transaction_costs_SPX_opt_2023-06-05EqDelta-Price2023Jun07_Action1_test_hedge_transaction_costs_SPX_opt_2023-06-05EqDelta-Price2023Jun06_Action1_test_hedge_transaction_costs_SPX_opt_2023-06-05EqDelta-Price2023Jun05", "request_hash": "85a1eff8ed194c127249a682db2d27cf", "type": "MockCalc", "tests": [ "test_hedge_transaction_costs" ], "mocked_data": "[[[[{\"$type\":\"RiskVector\",\"asset\":[250942.15078805026],\"points\":[{\"asset\":\".SPX\",\"class_\":\"SPOT\",\"path\":\"\",\"point\":\"\",\"quoteStyle\":\"PROPSPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}],[{\"$type\":\"RiskVector\",\"asset\":[-179019.3476860351],\"points\":[{\"asset\":\".SPX\",\"class_\":\"SPOT\",\"path\":\"\",\"point\":\"\",\"quoteStyle\":\"PROPSPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":13743.434897876472}],[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":9325.32692736073}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[250221.17763365817],\"points\":[{\"asset\":\".SPX\",\"class_\":\"SPOT\",\"path\":\"\",\"point\":\"\",\"quoteStyle\":\"PROPSPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}],[{\"$type\":\"RiskVector\",\"asset\":[-178320.59153823234],\"points\":[{\"asset\":\".SPX\",\"class_\":\"SPOT\",\"path\":\"\",\"point\":\"\",\"quoteStyle\":\"PROPSPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":13688.86986758094}],[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":9171.301390968727}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[247903.37372674234],\"points\":[{\"asset\":\".SPX\",\"class_\":\"SPOT\",\"path\":\"\",\"point\":\"\",\"quoteStyle\":\"PROPSPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}],[{\"$type\":\"RiskVector\",\"asset\":[-179388.83958485746],\"points\":[{\"asset\":\".SPX\",\"class_\":\"SPOT\",\"path\":\"\",\"point\":\"\",\"quoteStyle\":\"PROPSPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":13767.126268402146}],[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":9456.91105887924}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[248622.92745106784],\"points\":[{\"asset\":\".SPX\",\"class_\":\"SPOT\",\"path\":\"\",\"point\":\"\",\"quoteStyle\":\"PROPSPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}],[{\"$type\":\"RiskVector\",\"asset\":[-180011.99962845596],\"points\":[{\"asset\":\".SPX\",\"class_\":\"SPOT\",\"path\":\"\",\"point\":\"\",\"quoteStyle\":\"PROPSPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":13940.394886672366}],[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":9583.589974375851}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[248326.09646873665],\"points\":[{\"asset\":\".SPX\",\"class_\":\"SPOT\",\"path\":\"\",\"point\":\"\",\"quoteStyle\":\"PROPSPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}],[{\"$type\":\"RiskVector\",\"asset\":[-181094.18813260697],\"points\":[{\"asset\":\".SPX\",\"class_\":\"SPOT\",\"path\":\"\",\"point\":\"\",\"quoteStyle\":\"PROPSPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":14321.1216389778}],[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":9970.814326808246}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[253640.18051819107],\"points\":[{\"asset\":\".SPX\",\"class_\":\"SPOT\",\"path\":\"\",\"point\":\"\",\"quoteStyle\":\"PROPSPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":13537.28554709237}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[243043.73882058455],\"points\":[{\"asset\":\".SPX\",\"class_\":\"SPOT\",\"path\":\"\",\"point\":\"\",\"quoteStyle\":\"PROPSPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":12640.832107981938}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[234862.23580496723],\"points\":[{\"asset\":\".SPX\",\"class_\":\"SPOT\",\"path\":\"\",\"point\":\"\",\"quoteStyle\":\"PROPSPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":12245.43442277743}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[243434.1592918547],\"points\":[{\"asset\":\".SPX\",\"class_\":\"SPOT\",\"path\":\"\",\"point\":\"\",\"quoteStyle\":\"PROPSPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":13281.487056281043}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[248326.09646873665],\"points\":[{\"asset\":\".SPX\",\"class_\":\"SPOT\",\"path\":\"\",\"point\":\"\",\"quoteStyle\":\"PROPSPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":14321.1216389778}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request878bab10c211dd44cb4553f6dc484f83.json ================================================ { "request_id": "1_HedgeAction1_Priceable0ResolvedInstrumentValues2021Dec01", "request_hash": "878bab10c211dd44cb4553f6dc484f83", "type": "MockCalc", "tests": [ "test_hedge_action_risk_trigger" ], "mocked_data": "[[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"FX\",\"buySell\":\"Buy\",\"forwardRate\":110.54466773,\"notionalAmount\":100000.0,\"notionalAmountInOtherCurrency\":-11054466.773,\"notionalCurrency\":\"USD\",\"pair\":\"USD JPY\",\"settlementDate\":\"2023-12-04\",\"type\":\"Forward\"}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request895686a32c6921f666fe79c10493bb1f.json ================================================ { "request_id": "2_IRBasisSwap-5y-10yIRBasis(aggregation_level:Asset)-Price2020Jan15_IRBasisSwap-5y-10yIRBasis(aggregation_level:Asset)-Price2020Jan14", "request_hash": "895686a32c6921f666fe79c10493bb1f", "type": "MockCalc", "tests": [ "test_diff_types_risk_measures" ], "mocked_data": "[[[[{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"value\": 405746.24355284683}, {\"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"value\": -83.64825653335737}, {\"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"value\": 3.805013457786055e-12}], \"values\": [405746.24355284683, -83.64825653335737, 3.805013457786055e-12], \"calculationTime\": 1651, \"queueingTime\": -4093}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"value\": -60270.4904072918}, {\"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"value\": -80410.08769885598}, {\"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"value\": 3.82544573135376}], \"values\": [-60270.4904072918, -80410.08769885598, 3.82544573135376], \"calculationTime\": 571, \"queueingTime\": -4093}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"value\": -114283.82405261078}, {\"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"value\": -134590.05976996155}, {\"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"value\": 8.304349964904786}], \"values\": [-114283.82405261078, -134590.05976996155, 8.304349964904786], \"calculationTime\": 571, \"queueingTime\": -4093}]], [[{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 0.0, \"children\": {}, \"calculationTime\": 2333, \"queueingTime\": -4093}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 5310827.561557852, \"children\": {}, \"calculationTime\": 45, \"queueingTime\": -4093}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 11528844.938571109, \"children\": {}, \"calculationTime\": 45, \"queueingTime\": -4093}]]], [[[{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"value\": 405775.5447549485}, {\"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"value\": -84.10992125279488}, {\"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"value\": 4.255939660413599e-12}], \"values\": [405775.5447549485, -84.10992125279488, 4.255939660413599e-12], \"calculationTime\": 16959, \"queueingTime\": -1427}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"value\": -59818.32957608109}, {\"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"value\": -80227.02056628113}, {\"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"value\": 4.060833167266846}], \"values\": [-59818.32957608109, -80227.02056628113, 4.060833167266846], \"calculationTime\": 578, \"queueingTime\": -1427}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"value\": -113025.88950285035}, {\"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"value\": -133582.29018813782}, {\"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"value\": 8.726409149169923}], \"values\": [-113025.88950285035, -133582.29018813782, 8.726409149169923], \"calculationTime\": 578, \"queueingTime\": -1427}]], [[{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 0.0, \"children\": {}, \"calculationTime\": 145, \"queueingTime\": -1427}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 5644562.7450038865, \"children\": {}, \"calculationTime\": 44, \"queueingTime\": -1427}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 12129718.693539664, \"children\": {}, \"calculationTime\": 44, \"queueingTime\": -1427}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request8ac2bf9b09cc9383988a934fe691692a.json ================================================ { "request_id": "3_AddAction1_test_hedge_without_risk_2y_callResolvedInstrumentValues2021Dec03_AddAction1_test_hedge_without_risk_2y_callResolvedInstrumentValues2021Dec02_AddAction1_test_hedge_without_risk_2y_callResolvedInstrumentValues2021Dec01", "request_hash": "8ac2bf9b09cc9383988a934fe691692a", "type": "MockCalc", "tests": [ "test_hedge_without_risk" ], "mocked_data": "[[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"FX\",\"buySell\":\"Buy\",\"callAmount\":\"100000\",\"callCurrency\":\"USD\",\"exerciseStyle\":\"Manual\",\"expirationDate\":\"2023-12-05\",\"expirationTime\":\"NYC\",\"methodOfSettlement\":\"Physical\",\"notionalAmount\":100000.0,\"notionalAmountInOtherCurrency\":11066383.893000003,\"notionalCurrency\":\"USD\",\"optionType\":\"Call\",\"pair\":\"USD JPY\",\"premium\":-4129.728932087256,\"premiumCurrency\":\"USD\",\"premiumPaymentDate\":\"2023-12-07\",\"putAmount\":\"11066383.89300000295\",\"putCurrency\":\"JPY\",\"settlementDate\":\"2023-12-07\",\"strikePrice\":110.66383893000003,\"type\":\"Option\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"FX\",\"buySell\":\"Buy\",\"callAmount\":\"100000\",\"callCurrency\":\"USD\",\"exerciseStyle\":\"Manual\",\"expirationDate\":\"2023-12-04\",\"expirationTime\":\"NYC\",\"methodOfSettlement\":\"Physical\",\"notionalAmount\":100000.0,\"notionalAmountInOtherCurrency\":11060249.056,\"notionalCurrency\":\"USD\",\"optionType\":\"Call\",\"pair\":\"USD JPY\",\"premium\":-4169.561517510556,\"premiumCurrency\":\"USD\",\"premiumPaymentDate\":\"2023-12-06\",\"putAmount\":\"11060249.055999999866\",\"putCurrency\":\"JPY\",\"settlementDate\":\"2023-12-06\",\"strikePrice\":110.60249056,\"type\":\"Option\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"FX\",\"buySell\":\"Buy\",\"callAmount\":\"100000\",\"callCurrency\":\"USD\",\"exerciseStyle\":\"Manual\",\"expirationDate\":\"2023-11-30\",\"expirationTime\":\"NYC\",\"methodOfSettlement\":\"Physical\",\"notionalAmount\":100000.0,\"notionalAmountInOtherCurrency\":11054466.772999996,\"notionalCurrency\":\"USD\",\"optionType\":\"Call\",\"pair\":\"USD JPY\",\"premium\":-4176.647676080006,\"premiumCurrency\":\"USD\",\"premiumPaymentDate\":\"2023-12-04\",\"putAmount\":\"11054466.772999996319\",\"putCurrency\":\"JPY\",\"settlementDate\":\"2023-12-04\",\"strikePrice\":110.54466772999997,\"type\":\"Option\"}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request8b7a3ed64b4a972e90f48ad347670813.json ================================================ { "request_id": "1_Scaled_test_hedge_action_risk_trigger_2y_forward_2021-12-02_HedgeAction1_Priceable0FXDelta(aggregation_level:Type)-Price2021Dec03", "request_hash": "8b7a3ed64b4a972e90f48ad347670813", "type": "MockCalc", "tests": [ "test_hedge_action_risk_trigger" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"children\":{},\"val\":-2.9250459610352664}]],[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":-22.105259611918882}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request8bc8f9c4da52c90d51b3e18e1300c6d4.json ================================================ { "request_id": "10_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-10EqVega2021Dec13_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-10EqVega2021Dec10_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-09EqVega2021Dec10_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-09EqVega2021Dec09_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-08EqVega2021Dec09_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-08EqVega2021Dec08_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-07EqVega2021Dec08_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-07EqVega2021Dec07_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-06EqVega2021Dec07_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-06EqVega2021Dec06", "request_hash": "8bc8f9c4da52c90d51b3e18e1300c6d4", "type": "MockCalc", "tests": [ "test_add_scaled_action_nav_with_transaction_costs" ], "mocked_data": "[[[[{\"$type\":\"RiskVector\",\"asset\":[3.0652036231421516],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[3.140530220097062],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[3.818796220935692],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[3.841952773637313],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[4.9966076339547],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[5.084747744802735],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[7.130969449888362],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[7.22883825172321],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[4.809497820635151],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[4.946246453141794],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request8bfa8a8950c0bdfc209c49e531739006.json ================================================ { "request_id": "2_5y-10y-5y-10yDollarPrice-Price2020Jan15_5y-10y-5y-10yDollarPrice-Price2020Jan14", "request_hash": "8bfa8a8950c0bdfc209c49e531739006", "type": "MockCalc", "tests": [ "test_dated_risk_values" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 3520200.8482332965, \"children\": {}, \"calculationTime\": 828, \"queueingTime\": -6603}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 10406829.359454261, \"children\": {}, \"calculationTime\": 828, \"queueingTime\": -6603}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 6914612.085193914, \"children\": {}, \"calculationTime\": 1130, \"queueingTime\": -6603}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 15010370.722182993, \"children\": {}, \"calculationTime\": 1130, \"queueingTime\": -6603}]], [[{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 3155308.4756011004, \"children\": {}, \"calculationTime\": 828, \"queueingTime\": -6603}], [{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 9328091.861150585, \"children\": {}, \"calculationTime\": 828, \"queueingTime\": -6603}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 5310827.561557852, \"children\": {}, \"calculationTime\": 1130, \"queueingTime\": -6603}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 11528844.938571109, \"children\": {}, \"calculationTime\": 1130, \"queueingTime\": -6603}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 3660454.298656369, \"children\": {}, \"calculationTime\": 933, \"queueingTime\": -7449}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 10666368.872246133, \"children\": {}, \"calculationTime\": 933, \"queueingTime\": -7449}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 7342905.369624435, \"children\": {}, \"calculationTime\": 957, \"queueingTime\": -7449}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 15779322.606637968, \"children\": {}, \"calculationTime\": 957, \"queueingTime\": -7449}]], [[{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 3290317.2121213092, \"children\": {}, \"calculationTime\": 933, \"queueingTime\": -7449}], [{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 9587809.11540649, \"children\": {}, \"calculationTime\": 933, \"queueingTime\": -7449}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 5644562.7450038865, \"children\": {}, \"calculationTime\": 957, \"queueingTime\": -7449}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 12129718.693539664, \"children\": {}, \"calculationTime\": 957, \"queueingTime\": -7449}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request8d2c2ca8df0fc30cb3152076a8af6016.json ================================================ { "request_id": "1_Swaption1-Swaption2IRDelta(aggregation_level:Asset, currency:local)-Price2020Jan14", "request_hash": "8d2c2ca8df0fc30cb3152076a8af6016", "type": "MockCalc", "tests": [ "test_adding_risk_results" ], "mocked_data": "[[[[{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR\", \"asset\": \"USD\", \"value\": -24852.34830894964}], \"values\": [-24852.34830894964], \"calculationTime\": 3499, \"queueingTime\": -4360}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR\", \"asset\": \"USD\", \"value\": -47440.98892296477}], \"values\": [-47440.98892296477], \"calculationTime\": 3499, \"queueingTime\": -4360}]], [[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 433852.43426229653, \"children\": {}, \"calculationTime\": 1093, \"queueingTime\": -4360}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 1105617.6195956036, \"children\": {}, \"calculationTime\": 1093, \"queueingTime\": -4360}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request90eccd55b0448292d8511e73985542d7.json ================================================ { "request_id": "2_1y-3m-5y-10yIRVega-Price2020Jan15_1y-3m-5y-10yIRVega-Price2020Jan14", "request_hash": "90eccd55b0448292d8511e73985542d7", "type": "MockCalc", "tests": [ "test_empty_calc_request" ], "mocked_data": "[[[[{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;1Y\", \"quoteStyle\": \"\", \"value\": 18942.916708309745}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;3M\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [18942.916708309745, 0.0], \"calculationTime\": 1320, \"queueingTime\": -7923}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;3M\", \"quoteStyle\": \"\", \"value\": 9546.7347591115}], \"asset\": [0.0, 9546.7347591115], \"calculationTime\": 1320, \"queueingTime\": -7923}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;3M\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [0.0, 0.0], \"calculationTime\": 1320, \"queueingTime\": -7923}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;3M\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [0.0, 0.0], \"calculationTime\": 1320, \"queueingTime\": -7923}]], [[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 1162329.9377910192, \"children\": {}, \"calculationTime\": 529, \"queueingTime\": -7923}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 542174.0278393007, \"children\": {}, \"calculationTime\": 529, \"queueingTime\": -7923}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 9253581.340032147, \"children\": {}, \"calculationTime\": 529, \"queueingTime\": -7923}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 19104807.27238474, \"children\": {}, \"calculationTime\": 529, \"queueingTime\": -7923}]]], [[[{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;1Y\", \"quoteStyle\": \"\", \"value\": 18951.379622470093}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;3M\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [18951.379622470093, 0.0], \"calculationTime\": 1534, \"queueingTime\": -295}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;3M\", \"quoteStyle\": \"\", \"value\": 9546.483379703379}], \"asset\": [0.0, 9546.483379703379], \"calculationTime\": 1534, \"queueingTime\": -295}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;3M\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [0.0, 0.0], \"calculationTime\": 1534, \"queueingTime\": -295}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;3M\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [0.0, 0.0], \"calculationTime\": 1534, \"queueingTime\": -295}]], [[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 1159341.7952729776, \"children\": {}, \"calculationTime\": 20, \"queueingTime\": -295}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 544183.8212896035, \"children\": {}, \"calculationTime\": 20, \"queueingTime\": -295}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 9321685.065826869, \"children\": {}, \"calculationTime\": 20, \"queueingTime\": -295}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 19280407.561757438, \"children\": {}, \"calculationTime\": 20, \"queueingTime\": -295}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request914b625be8999c92f5b0fb21cb5c39f4.json ================================================ { "request_id": "2_5y-10y-5y-10y-5y-10y-5y-10yPrice2020Jan15_5y-10y-5y-10y-5y-10y-5y-10yPrice2020Jan14", "request_hash": "914b625be8999c92f5b0fb21cb5c39f4", "type": "MockCalc", "tests": [ "test_dated_risk_values" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 3155308.4756011004, \"children\": {}, \"calculationTime\": 3205, \"queueingTime\": -37559}], [{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 9328091.861150585, \"children\": {}, \"calculationTime\": 3205, \"queueingTime\": -37559}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 5310827.561557852, \"children\": {}, \"calculationTime\": 3015, \"queueingTime\": -37559}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 11528844.938571109, \"children\": {}, \"calculationTime\": 3015, \"queueingTime\": -37559}], [{\"$type\": \"Risk\", \"unit\": {\"Japanese Yen\": 1.0}, \"val\": 2245537.5291603645, \"children\": {}, \"calculationTime\": 3189, \"queueingTime\": -37559}], [{\"$type\": \"Risk\", \"unit\": {\"Japanese Yen\": 1.0}, \"val\": 5335555.207899147, \"children\": {}, \"calculationTime\": 3189, \"queueingTime\": -37559}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 9253581.340032147, \"children\": {}, \"calculationTime\": 3419, \"queueingTime\": -37559}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 19104807.27238474, \"children\": {}, \"calculationTime\": 3419, \"queueingTime\": -37559}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 3290317.2121213092, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 14192}], [{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 9587809.11540649, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 14192}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 5644562.7450038865, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 14192}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 12129718.693539664, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 14192}], [{\"$type\": \"Risk\", \"unit\": {\"Japanese Yen\": 1.0}, \"val\": 2257946.840053587, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 14192}], [{\"$type\": \"Risk\", \"unit\": {\"Japanese Yen\": 1.0}, \"val\": 5372206.792654425, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 14192}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 9321685.065826869, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 14192}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 19280407.561757438, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 14192}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request91c941704f6cfc578724277dbb79d456.json ================================================ { "request_id": "1_5y-10y-5y-10yDollarPrice-Price-Theta2020Jan14", "request_hash": "91c941704f6cfc578724277dbb79d456", "type": "MockCalc", "tests": [ "test_nested_portfolio" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 3660454.298656369, \"children\": {}, \"calculationTime\": 21, \"queueingTime\": -649}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 10666368.872246133, \"children\": {}, \"calculationTime\": 21, \"queueingTime\": -649}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 7342905.369624435, \"children\": {}, \"calculationTime\": 23, \"queueingTime\": -649}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 15779322.606637968, \"children\": {}, \"calculationTime\": 23, \"queueingTime\": -649}]], [[{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 3290317.2121213092, \"children\": {}, \"calculationTime\": 21, \"queueingTime\": -649}], [{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 9587809.11540649, \"children\": {}, \"calculationTime\": 21, \"queueingTime\": -649}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 5644562.7450038865, \"children\": {}, \"calculationTime\": 23, \"queueingTime\": -649}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 12129718.693539664, \"children\": {}, \"calculationTime\": 23, \"queueingTime\": -649}]], [[{\"$type\": \"Error\", \"errorString\": \"LastError() was:\\nOld Griffin Valuations Wrapper only supports Leg level scenarios\\nthrown at _LIB BW Pricer(2275:17-2275:109)\\n. Trace url=http://tracing.boltweb.gs.com/zipkin/traces/c8e7277ee9425a86\", \"calculationTime\": 1289, \"queueingTime\": -649}], [{\"$type\": \"Error\", \"errorString\": \"LastError() was:\\nOld Griffin Valuations Wrapper only supports Leg level scenarios\\nthrown at _LIB BW Pricer(2275:17-2275:109)\\n. Trace url=http://tracing.boltweb.gs.com/zipkin/traces/c8e7277ee9425a86\", \"calculationTime\": 1289, \"queueingTime\": -649}], [{\"$type\": \"Error\", \"errorString\": \"LastError() was:\\nOld Griffin Valuations Wrapper only supports Leg level scenarios\\nthrown at _LIB BW Pricer(2275:17-2275:109)\\n. Trace url=http://tracing.boltweb.gs.com/zipkin/traces/c8e7277ee9425a86\", \"calculationTime\": 76, \"queueingTime\": -649}], [{\"$type\": \"Error\", \"errorString\": \"LastError() was:\\nOld Griffin Valuations Wrapper only supports Leg level scenarios\\nthrown at _LIB BW Pricer(2275:17-2275:109)\\n. Trace url=http://tracing.boltweb.gs.com/zipkin/traces/c8e7277ee9425a86\", \"calculationTime\": 76, \"queueingTime\": -649}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request9228e718c64b256d661a1d93909708c7.json ================================================ { "request_id": "5_Action1_GBP1yResolvedInstrumentValues2023Jun09_Action1_GBP1yResolvedInstrumentValues2023Jun08_Action1_GBP1yResolvedInstrumentValues2023Jun07_Action1_GBP1yResolvedInstrumentValues2023Jun06_Action1_GBP1yResolvedInstrumentValues2023Jun05", "request_hash": "9228e718c64b256d661a1d93909708c7", "type": "MockCalc", "tests": [ "test_risk_scaled_transaction_cost" ], "mocked_data": "[[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Rates\",\"effectiveDate\":\"2023-06-09\",\"fee\":0.0,\"feeCurrency\":\"GBP\",\"feePaymentDate\":\"2023-06-13\",\"fixedFirstStub\":\"2023-06-09\",\"fixedHolidays\":\"London\",\"fixedLastStub\":\"2024-06-09\",\"fixedRate\":0.053554913325556376,\"fixedRateAccrualConvention\":\"Adjusted\",\"fixedRateBusinessDayConvention\":\"Modified Following\",\"fixedRateFrequency\":\"1y\",\"floatingFirstStub\":\"2023-06-09\",\"floatingHolidays\":\"London\",\"floatingLastStub\":\"2024-06-09\",\"floatingRateAccrualConvention\":\"Adjusted\",\"floatingRateBusinessDayConvention\":\"Modified Following\",\"floatingRateDesignatedMaturity\":\"1y\",\"floatingRateFrequency\":\"1y\",\"floatingRateOption\":\"GBP-SONIA-COMPOUND\",\"floatingRateSpread\":0.0,\"notionalAmount\":50000.0,\"notionalCurrency\":\"GBP\",\"payOrReceive\":\"Receive\",\"principalExchange\":\"None\",\"rollConvention\":\"Default\",\"terminationDate\":\"2024-06-09\",\"type\":\"Swap\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Rates\",\"effectiveDate\":\"2023-06-08\",\"fee\":0.0,\"feeCurrency\":\"GBP\",\"feePaymentDate\":\"2023-06-12\",\"fixedFirstStub\":\"2023-06-08\",\"fixedHolidays\":\"London\",\"fixedLastStub\":\"2024-06-08\",\"fixedRate\":0.05333963168669434,\"fixedRateAccrualConvention\":\"Adjusted\",\"fixedRateBusinessDayConvention\":\"Modified Following\",\"fixedRateFrequency\":\"1y\",\"floatingFirstStub\":\"2023-06-08\",\"floatingHolidays\":\"London\",\"floatingLastStub\":\"2024-06-08\",\"floatingRateAccrualConvention\":\"Adjusted\",\"floatingRateBusinessDayConvention\":\"Modified Following\",\"floatingRateDesignatedMaturity\":\"1y\",\"floatingRateFrequency\":\"1y\",\"floatingRateOption\":\"GBP-SONIA-COMPOUND\",\"floatingRateSpread\":0.0,\"notionalAmount\":50000.0,\"notionalCurrency\":\"GBP\",\"payOrReceive\":\"Receive\",\"principalExchange\":\"None\",\"rollConvention\":\"Default\",\"terminationDate\":\"2024-06-08\",\"type\":\"Swap\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Rates\",\"effectiveDate\":\"2023-06-07\",\"fee\":0.0,\"feeCurrency\":\"GBP\",\"feePaymentDate\":\"2023-06-09\",\"fixedFirstStub\":\"2023-06-07\",\"fixedHolidays\":\"London\",\"fixedLastStub\":\"2024-06-07\",\"fixedRate\":0.05353723782654013,\"fixedRateAccrualConvention\":\"Adjusted\",\"fixedRateBusinessDayConvention\":\"Modified Following\",\"fixedRateFrequency\":\"1y\",\"floatingFirstStub\":\"2023-06-07\",\"floatingHolidays\":\"London\",\"floatingLastStub\":\"2024-06-07\",\"floatingRateAccrualConvention\":\"Adjusted\",\"floatingRateBusinessDayConvention\":\"Modified Following\",\"floatingRateDesignatedMaturity\":\"1y\",\"floatingRateFrequency\":\"1y\",\"floatingRateOption\":\"GBP-SONIA-COMPOUND\",\"floatingRateSpread\":0.0,\"notionalAmount\":50000.0,\"notionalCurrency\":\"GBP\",\"payOrReceive\":\"Receive\",\"principalExchange\":\"None\",\"rollConvention\":\"Default\",\"terminationDate\":\"2024-06-07\",\"type\":\"Swap\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Rates\",\"effectiveDate\":\"2023-06-06\",\"fee\":0.0,\"feeCurrency\":\"GBP\",\"feePaymentDate\":\"2023-06-08\",\"fixedFirstStub\":\"2023-06-06\",\"fixedHolidays\":\"London\",\"fixedLastStub\":\"2024-06-06\",\"fixedRate\":0.05313775364069198,\"fixedRateAccrualConvention\":\"Adjusted\",\"fixedRateBusinessDayConvention\":\"Modified Following\",\"fixedRateFrequency\":\"1y\",\"floatingFirstStub\":\"2023-06-06\",\"floatingHolidays\":\"London\",\"floatingLastStub\":\"2024-06-06\",\"floatingRateAccrualConvention\":\"Adjusted\",\"floatingRateBusinessDayConvention\":\"Modified Following\",\"floatingRateDesignatedMaturity\":\"1y\",\"floatingRateFrequency\":\"1y\",\"floatingRateOption\":\"GBP-SONIA-COMPOUND\",\"floatingRateSpread\":0.0,\"notionalAmount\":50000.0,\"notionalCurrency\":\"GBP\",\"payOrReceive\":\"Receive\",\"principalExchange\":\"None\",\"rollConvention\":\"Default\",\"terminationDate\":\"2024-06-06\",\"type\":\"Swap\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Rates\",\"effectiveDate\":\"2023-06-05\",\"fee\":0.0,\"feeCurrency\":\"GBP\",\"feePaymentDate\":\"2023-06-07\",\"fixedFirstStub\":\"2023-06-05\",\"fixedHolidays\":\"London\",\"fixedLastStub\":\"2024-06-05\",\"fixedRate\":0.053176241168925865,\"fixedRateAccrualConvention\":\"Adjusted\",\"fixedRateBusinessDayConvention\":\"Modified Following\",\"fixedRateFrequency\":\"1y\",\"floatingFirstStub\":\"2023-06-05\",\"floatingHolidays\":\"London\",\"floatingLastStub\":\"2024-06-05\",\"floatingRateAccrualConvention\":\"Adjusted\",\"floatingRateBusinessDayConvention\":\"Modified Following\",\"floatingRateDesignatedMaturity\":\"1y\",\"floatingRateFrequency\":\"1y\",\"floatingRateOption\":\"GBP-SONIA-COMPOUND\",\"floatingRateSpread\":0.0,\"notionalAmount\":50000.0,\"notionalCurrency\":\"GBP\",\"payOrReceive\":\"Receive\",\"principalExchange\":\"None\",\"rollConvention\":\"Default\",\"terminationDate\":\"2024-06-05\",\"type\":\"Swap\"}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request9484a9be138ecb74c7241db00fd05dcd.json ================================================ { "request_id": "2_1y-3m-6m-5y-10yIRVega(currency:local)-Price2020Jan15_1y-3m-6m-5y-10yIRVega(currency:local)-Price2020Jan14", "request_hash": "9484a9be138ecb74c7241db00fd05dcd", "type": "MockCalc", "tests": [ "test_empty_calc_request" ], "mocked_data": "[[[[{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;1Y\", \"quoteStyle\": \"\", \"value\": 18942.916708309745}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;3M\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;6M\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [18942.916708309745, 0.0, 0.0], \"calculationTime\": 0, \"queueingTime\": 1969}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;3M\", \"quoteStyle\": \"\", \"value\": 9546.7347591115}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;6M\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [0.0, 9546.7347591115, 0.0], \"calculationTime\": 0, \"queueingTime\": 1969}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;3M\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;6M\", \"quoteStyle\": \"\", \"value\": 13453.962925416756}], \"asset\": [0.0, 0.0, 13453.962925416756], \"calculationTime\": 0, \"queueingTime\": 1969}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;3M\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;6M\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [0.0, 0.0, 0.0], \"calculationTime\": 0, \"queueingTime\": 1969}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;3M\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;6M\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [0.0, 0.0, 0.0], \"calculationTime\": 0, \"queueingTime\": 1969}]], [[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 1162329.9377910192, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 1969}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 542174.0278393007, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 1969}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 790971.9423228333, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 1969}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 9253581.340032147, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 1969}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 19104807.27238474, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 1969}]]], [[[{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;1Y\", \"quoteStyle\": \"\", \"value\": 18951.379622470093}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;3M\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;6M\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [18951.379622470093, 0.0, 0.0], \"calculationTime\": 71, \"queueingTime\": -364}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;3M\", \"quoteStyle\": \"\", \"value\": 9546.483379703379}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;6M\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [0.0, 9546.483379703379, 0.0], \"calculationTime\": 71, \"queueingTime\": -364}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;3M\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;6M\", \"quoteStyle\": \"\", \"value\": 13453.388908411027}], \"asset\": [0.0, 0.0, 13453.388908411027], \"calculationTime\": 71, \"queueingTime\": -364}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;3M\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;6M\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [0.0, 0.0, 0.0], \"calculationTime\": 71, \"queueingTime\": -364}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;3M\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;6M\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [0.0, 0.0, 0.0], \"calculationTime\": 71, \"queueingTime\": -364}]], [[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 1159341.7952729776, \"children\": {}, \"calculationTime\": 15, \"queueingTime\": -364}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 544183.8212896035, \"children\": {}, \"calculationTime\": 15, \"queueingTime\": -364}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 790300.4555458546, \"children\": {}, \"calculationTime\": 15, \"queueingTime\": -364}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 9321685.065826869, \"children\": {}, \"calculationTime\": 15, \"queueingTime\": -364}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 19280407.561757438, \"children\": {}, \"calculationTime\": 15, \"queueingTime\": -364}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request95d170fa02a120482c2f0497c72d5716.json ================================================ { "request_id": "1_Scaled_test_hedge_without_risk_2y_forward_2021-12-01_HedgeAction1_Priceable0FXDelta(aggregation_level:Type)-Price2021Dec02", "request_hash": "95d170fa02a120482c2f0497c72d5716", "type": "MockCalc", "tests": [ "test_hedge_without_risk" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"children\":{},\"val\":-462.7499536218238}]],[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":-3599.444492554292}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request9a319113c6ca13dad7540c7fd90825ba.json ================================================ { "request_id": "1_Scaled_test_hedge_without_risk_2y_forward_2021-12-02_HedgeAction1_Priceable0FXDelta(aggregation_level:Type)-Price2021Dec03", "request_hash": "9a319113c6ca13dad7540c7fd90825ba", "type": "MockCalc", "tests": [ "test_hedge_without_risk" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"children\":{},\"val\":-0.528612138808171}]],[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":-3.994846138887624}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/request9da53bdb54ed741dd7b73e22896e610a.json ================================================ { "request_id": "1_HedgeAction1_Priceable0ResolvedInstrumentValues2021Dec03", "request_hash": "9da53bdb54ed741dd7b73e22896e610a", "type": "MockCalc", "tests": [ "test_hedge_action_risk_trigger" ], "mocked_data": "[[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"FX\",\"buySell\":\"Buy\",\"forwardRate\":110.66383893,\"notionalAmount\":100000.0,\"notionalAmountInOtherCurrency\":-11066383.893,\"notionalCurrency\":\"USD\",\"pair\":\"USD JPY\",\"settlementDate\":\"2023-12-07\",\"type\":\"Forward\"}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requesta3af76c3bf560d090aeb79bc6cdde208.json ================================================ { "request_id": "3_Scaled_test_hedge_action_risk_trigger_2y_forward_2021-12-03_HedgeAction1_Priceable0FXDelta(aggregation_level:Type)-Price2021Dec03_Scaled_test_hedge_action_risk_trigger_2y_forward_2021-12-02_HedgeAction1_Priceable0FXDelta(aggregation_level:Type)-Price2021Dec02_Scaled_test_hedge_action_risk_trigger_2y_forward_2021-12-01_HedgeAction1_Priceable0FXDelta(aggregation_level:Type)-Price2021Dec01", "request_hash": "a3af76c3bf560d090aeb79bc6cdde208", "type": "MockCalc", "tests": [ "test_hedge_action_risk_trigger" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"children\":{},\"val\":-465.2874709063326}]],[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":1.862645149230957e-9}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"val\":-2.9282931785701294}]],[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":0.0}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"val\":-463.0912266293308}]],[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":9.313225746154785e-10}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requesta473c17920ab76f366755aa775da6371.json ================================================ { "request_id": "1_5y-10yDollarPrice-Pricetodaycurve shift1bp", "request_hash": "a473c17920ab76f366755aa775da6371", "type": "MockCalc", "tests": [ "test_adding_risk_results" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 26025.90779468637, \"children\": {}}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 67765.57711148825, \"children\": {}}]], [[{\"$type\": \"Risk\", \"unit\": {\"Japanese Yen\": 1.0}, \"val\": 3364687.316191261, \"children\": {}}], [{\"$type\": \"Risk\", \"unit\": {\"Japanese Yen\": 1.0}, \"val\": 8760884.71457497, \"children\": {}}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requesta58fc4bd517e4a3f1a277f68287609f3.json ================================================ { "request_id": "5_Scaled_None_2023-06-09_HedgeAction1_test_hedge_transaction_costs_syn_fwd_call-Scaled_None_2023-06-09_HedgeAction1_test_hedge_transaction_costs_syn_fwd_putEqDelta-Price2023Jun09_Scaled_None_2023-06-08_HedgeAction1_test_hedge_transaction_costs_syn_fwd_call-Scaled_None_2023-06-08_HedgeAction1_test_hedge_transaction_costs_syn_fwd_putEqDelta-Price2023Jun08_Scaled_None_2023-06-07_HedgeAction1_test_hedge_transaction_costs_syn_fwd_call-Scaled_None_2023-06-07_HedgeAction1_test_hedge_transaction_costs_syn_fwd_putEqDelta-Price2023Jun07_Scaled_None_2023-06-06_HedgeAction1_test_hedge_transaction_costs_syn_fwd_call-Scaled_None_2023-06-06_HedgeAction1_test_hedge_transaction_costs_syn_fwd_putEqDelta-Price2023Jun06_Scaled_None_2023-06-05_HedgeAction1_test_hedge_transaction_costs_syn_fwd_call-Scaled_None_2023-06-05_HedgeAction1_test_hedge_transaction_costs_syn_fwd_putEqDelta-Price2023Jun05", "request_hash": "a58fc4bd517e4a3f1a277f68287609f3", "type": "MockCalc", "tests": [ "test_hedge_transaction_costs" ], "mocked_data": "[[[[{\"$type\":\"RiskVector\",\"asset\":[-884962.9002254619],\"points\":[{\"asset\":\".SPX\",\"class_\":\"SPOT\",\"path\":\"\",\"point\":\"\",\"quoteStyle\":\"PROPSPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}],[{\"$type\":\"RiskVector\",\"asset\":[631322.7197072709],\"points\":[{\"asset\":\".SPX\",\"class_\":\"SPOT\",\"path\":\"\",\"point\":\"\",\"quoteStyle\":\"PROPSPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":-48467.06688410098}],[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":-32886.33789609143}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[-845816.3395700445],\"points\":[{\"asset\":\".SPX\",\"class_\":\"SPOT\",\"path\":\"\",\"point\":\"\",\"quoteStyle\":\"PROPSPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}],[{\"$type\":\"RiskVector\",\"asset\":[602772.60074946],\"points\":[{\"asset\":\".SPX\",\"class_\":\"SPOT\",\"path\":\"\",\"point\":\"\",\"quoteStyle\":\"PROPSPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":-46272.141765711815}],[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":-31001.518916036668}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[-849792.5490741034],\"points\":[{\"asset\":\".SPX\",\"class_\":\"SPOT\",\"path\":\"\",\"point\":\"\",\"quoteStyle\":\"PROPSPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}],[{\"$type\":\"RiskVector\",\"asset\":[614930.3132691367],\"points\":[{\"asset\":\".SPX\",\"class_\":\"SPOT\",\"path\":\"\",\"point\":\"\",\"quoteStyle\":\"PROPSPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":-47192.58616442329}],[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":-32417.519916249337}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[-882123.5223812843],\"points\":[{\"asset\":\".SPX\",\"class_\":\"SPOT\",\"path\":\"\",\"point\":\"\",\"quoteStyle\":\"PROPSPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}],[{\"$type\":\"RiskVector\",\"asset\":[638689.3630894299],\"points\":[{\"asset\":\".SPX\",\"class_\":\"SPOT\",\"path\":\"\",\"point\":\"\",\"quoteStyle\":\"PROPSPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":-49461.046762220736}],[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":-34002.938634524086}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[-917211.0641140579],\"points\":[{\"asset\":\".SPX\",\"class_\":\"SPOT\",\"path\":\"\",\"point\":\"\",\"quoteStyle\":\"PROPSPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}],[{\"$type\":\"RiskVector\",\"asset\":[668884.9676453213],\"points\":[{\"asset\":\".SPX\",\"class_\":\"SPOT\",\"path\":\"\",\"point\":\"\",\"quoteStyle\":\"PROPSPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]],[[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":-52896.1369931871}],[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":-36827.95062147989}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requesta5eb6cf7f7a7e728b9137396b59150b0.json ================================================ { "request_id": "10_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-10EqVega2021Dec13_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-10EqVega2021Dec10_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-09EqVega2021Dec10_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-09EqVega2021Dec09_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-08EqVega2021Dec09_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-08EqVega2021Dec08_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-07EqVega2021Dec08_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-07EqVega2021Dec07_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-06EqVega2021Dec07_QuantityScaledAction2_TransactionAggType.MIN_call_2021-12-06EqVega2021Dec06", "request_hash": "a5eb6cf7f7a7e728b9137396b59150b0", "type": "MockCalc", "tests": [ "test_add_scaled_action_nav_with_transaction_costs" ], "mocked_data": "[[[[{\"$type\":\"RiskVector\",\"asset\":[5.435643823389837],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[5.569223383450153],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[5.918295860381768],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[5.9541834338646735],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[6.73622616962457],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[6.8550531347947254],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[8.302225174621563],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[8.416168844709755],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[5.061848653833149],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]],[[[{\"$type\":\"RiskVector\",\"asset\":[5.205772387075558],\"points\":[{\"asset\":\".STOXX50E\",\"class_\":\"ATM VOL\",\"path\":\"\",\"point\":\"MATCH:*\",\"quoteStyle\":\"SPREAD\",\"type\":\"RISK\"}],\"unit\":{\"United States Dollar\":1.0}}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestaa5e55819e3a43cca2b284c2119e7525.json ================================================ { "request_id": "15_QuantityScaledAction1_call_2021-12-10-QuantityScaledAction1_put_2021-12-10Price2021Dec10_QuantityScaledAction1_call_2021-12-09-QuantityScaledAction1_put_2021-12-09Price2021Dec10_QuantityScaledAction1_call_2021-12-09-QuantityScaledAction1_put_2021-12-09Price2021Dec09_QuantityScaledAction1_call_2021-12-08-QuantityScaledAction1_put_2021-12-08Price2021Dec10_QuantityScaledAction1_call_2021-12-08-QuantityScaledAction1_put_2021-12-08Price2021Dec09_QuantityScaledAction1_call_2021-12-08-QuantityScaledAction1_put_2021-12-08Price2021Dec08_QuantityScaledAction1_call_2021-12-07-QuantityScaledAction1_put_2021-12-07Price2021Dec10_QuantityScaledAction1_call_2021-12-07-QuantityScaledAction1_put_2021-12-07Price2021Dec09_QuantityScaledAction1_call_2021-12-07-QuantityScaledAction1_put_2021-12-07Price2021Dec08_QuantityScaledAction1_call_2021-12-07-QuantityScaledAction1_put_2021-12-07Price2021Dec07_QuantityScaledAction1_call_2021-12-06-QuantityScaledAction1_put_2021-12-06Price2021Dec10_QuantityScaledAction1_call_2021-12-06-QuantityScaledAction1_put_2021-12-06Price2021Dec09_QuantityScaledAction1_call_2021-12-06-QuantityScaledAction1_put_2021-12-06Price2021Dec08_QuantityScaledAction1_call_2021-12-06-QuantityScaledAction1_put_2021-12-06Price2021Dec07_QuantityScaledAction1_call_2021-12-06-QuantityScaledAction1_put_2021-12-06Price2021Dec06", "request_hash": "aa5e55819e3a43cca2b284c2119e7525", "type": "MockCalc", "tests": [ "test_add_scaled_action" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":78.89117688336509}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":83.98827021881476}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":73.40843195462492}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":87.65523639705381}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":85.44379354234701}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":90.69365987454688}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":59.495329933513275}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":98.55847336303383}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":70.93085789588493}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":100.99664688234236}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":89.33151346209694}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":94.8133272704093}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":36.06325881437674}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":116.95141343037503}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":46.37398898258349}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":118.26438331241344}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":62.00400617748216}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":109.3104073578655}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":87.93080662608884}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":92.08743326200693}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":115.5536027773543}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":57.15801189097753}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":129.1000350881709}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":61.709584089073914}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":151.53292423589548}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":59.551231712721474}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":186.53428843750459}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":51.38155903994571}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":107.48842748850868}],[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":111.57459155421593}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestaaedaa96766d75c664e8b3898af8365e.json ================================================ { "request_id": "5_Action1_swap1-Action1_swap2ResolvedInstrumentValues2021Dec10_Action1_swap1-Action1_swap2ResolvedInstrumentValues2021Dec09_Action1_swap1-Action1_swap2ResolvedInstrumentValues2021Dec08_Action1_swap1-Action1_swap2ResolvedInstrumentValues2021Dec07_Action1_swap1-Action1_swap2ResolvedInstrumentValues2021Dec06", "request_hash": "aaedaa96766d75c664e8b3898af8365e", "type": "MockCalc", "tests": [ "test_exit_action_bytradename" ], "mocked_data": "[[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Rates\",\"effectiveDate\":\"2021-12-14\",\"fee\":0.0,\"feeCurrency\":\"USD\",\"feePaymentDate\":\"2021-12-14\",\"fixedFirstStub\":\"2021-12-14\",\"fixedHolidays\":\"New York,London\",\"fixedLastStub\":\"2031-12-14\",\"fixedRate\":0.015378184493270055,\"fixedRateAccrualConvention\":\"Adjusted\",\"fixedRateBusinessDayConvention\":\"Modified Following\",\"fixedRateDayCountFraction\":\"30/360\",\"fixedRateFrequency\":\"6m\",\"floatingFirstStub\":\"2021-12-14\",\"floatingHolidays\":\"New York,London\",\"floatingLastStub\":\"2031-12-14\",\"floatingRateAccrualConvention\":\"Adjusted\",\"floatingRateBusinessDayConvention\":\"Modified Following\",\"floatingRateDesignatedMaturity\":\"3m\",\"floatingRateFrequency\":\"3m\",\"floatingRateOption\":\"USD-LIBOR-BBA\",\"floatingRateSpread\":0.0,\"notionalAmount\":100000.0,\"notionalCurrency\":\"USD\",\"payOrReceive\":\"Receive\",\"principalExchange\":\"None\",\"rollConvention\":\"Default\",\"terminationDate\":\"2031-12-14\",\"type\":\"Swap\"}],[{\"$type\":\"LegDefinition\",\"assetClass\":\"Rates\",\"effectiveDate\":\"2021-12-14\",\"fee\":0.0,\"feeCurrency\":\"USD\",\"feePaymentDate\":\"2021-12-14\",\"fixedFirstStub\":\"2021-12-14\",\"fixedHolidays\":\"New York,London\",\"fixedLastStub\":\"2026-12-14\",\"fixedRate\":0.013249326362380087,\"fixedRateAccrualConvention\":\"Adjusted\",\"fixedRateBusinessDayConvention\":\"Modified Following\",\"fixedRateDayCountFraction\":\"30/360\",\"fixedRateFrequency\":\"6m\",\"floatingFirstStub\":\"2021-12-14\",\"floatingHolidays\":\"New York,London\",\"floatingLastStub\":\"2026-12-14\",\"floatingRateAccrualConvention\":\"Adjusted\",\"floatingRateBusinessDayConvention\":\"Modified Following\",\"floatingRateDesignatedMaturity\":\"3m\",\"floatingRateFrequency\":\"3m\",\"floatingRateOption\":\"USD-LIBOR-BBA\",\"floatingRateSpread\":0.0,\"notionalAmount\":100000.0,\"notionalCurrency\":\"USD\",\"payOrReceive\":\"Pay\",\"principalExchange\":\"None\",\"rollConvention\":\"Default\",\"terminationDate\":\"2026-12-14\",\"type\":\"Swap\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Rates\",\"effectiveDate\":\"2021-12-13\",\"fee\":0.0,\"feeCurrency\":\"USD\",\"feePaymentDate\":\"2021-12-13\",\"fixedFirstStub\":\"2021-12-13\",\"fixedHolidays\":\"New York,London\",\"fixedLastStub\":\"2031-12-13\",\"fixedRate\":0.01545536426173,\"fixedRateAccrualConvention\":\"Adjusted\",\"fixedRateBusinessDayConvention\":\"Modified Following\",\"fixedRateDayCountFraction\":\"30/360\",\"fixedRateFrequency\":\"6m\",\"floatingFirstStub\":\"2021-12-13\",\"floatingHolidays\":\"New York,London\",\"floatingLastStub\":\"2031-12-13\",\"floatingRateAccrualConvention\":\"Adjusted\",\"floatingRateBusinessDayConvention\":\"Modified Following\",\"floatingRateDayCountFraction\":\"ACT/360\",\"floatingRateDesignatedMaturity\":\"3m\",\"floatingRateFrequency\":\"3m\",\"floatingRateOption\":\"USD-LIBOR-BBA\",\"floatingRateSpread\":0.0,\"notionalAmount\":100000.0,\"notionalCurrency\":\"USD\",\"payOrReceive\":\"Receive\",\"principalExchange\":\"None\",\"rollConvention\":\"Default\",\"terminationDate\":\"2031-12-13\",\"type\":\"Swap\"}],[{\"$type\":\"LegDefinition\",\"assetClass\":\"Rates\",\"effectiveDate\":\"2021-12-13\",\"fee\":0.0,\"feeCurrency\":\"USD\",\"feePaymentDate\":\"2021-12-13\",\"fixedFirstStub\":\"2021-12-13\",\"fixedHolidays\":\"New York,London\",\"fixedLastStub\":\"2026-12-13\",\"fixedRate\":0.013467818594809909,\"fixedRateAccrualConvention\":\"Adjusted\",\"fixedRateBusinessDayConvention\":\"Modified Following\",\"fixedRateDayCountFraction\":\"30/360\",\"fixedRateFrequency\":\"6m\",\"floatingFirstStub\":\"2021-12-13\",\"floatingHolidays\":\"New York,London\",\"floatingLastStub\":\"2026-12-13\",\"floatingRateAccrualConvention\":\"Adjusted\",\"floatingRateBusinessDayConvention\":\"Modified Following\",\"floatingRateDayCountFraction\":\"ACT/360\",\"floatingRateDesignatedMaturity\":\"3m\",\"floatingRateFrequency\":\"3m\",\"floatingRateOption\":\"USD-LIBOR-BBA\",\"floatingRateSpread\":0.0,\"notionalAmount\":100000.0,\"notionalCurrency\":\"USD\",\"payOrReceive\":\"Pay\",\"principalExchange\":\"None\",\"rollConvention\":\"Default\",\"terminationDate\":\"2026-12-13\",\"type\":\"Swap\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Rates\",\"effectiveDate\":\"2021-12-10\",\"fee\":0.0,\"feeCurrency\":\"USD\",\"feePaymentDate\":\"2021-12-10\",\"fixedFirstStub\":\"2021-12-10\",\"fixedHolidays\":\"New York,London\",\"fixedLastStub\":\"2031-12-10\",\"fixedRate\":0.016092787122729938,\"fixedRateAccrualConvention\":\"Adjusted\",\"fixedRateBusinessDayConvention\":\"Modified Following\",\"fixedRateDayCountFraction\":\"30/360\",\"fixedRateFrequency\":\"6m\",\"floatingFirstStub\":\"2021-12-10\",\"floatingHolidays\":\"New York,London\",\"floatingLastStub\":\"2031-12-10\",\"floatingRateAccrualConvention\":\"Adjusted\",\"floatingRateBusinessDayConvention\":\"Modified Following\",\"floatingRateDayCountFraction\":\"ACT/360\",\"floatingRateDesignatedMaturity\":\"3m\",\"floatingRateFrequency\":\"3m\",\"floatingRateOption\":\"USD-LIBOR-BBA\",\"floatingRateSpread\":0.0,\"notionalAmount\":100000.0,\"notionalCurrency\":\"USD\",\"payOrReceive\":\"Receive\",\"principalExchange\":\"None\",\"rollConvention\":\"Default\",\"terminationDate\":\"2031-12-10\",\"type\":\"Swap\"}],[{\"$type\":\"LegDefinition\",\"assetClass\":\"Rates\",\"effectiveDate\":\"2021-12-10\",\"fee\":0.0,\"feeCurrency\":\"USD\",\"feePaymentDate\":\"2021-12-10\",\"fixedFirstStub\":\"2021-12-10\",\"fixedHolidays\":\"New York,London\",\"fixedLastStub\":\"2026-12-10\",\"fixedRate\":0.013977882688659892,\"fixedRateAccrualConvention\":\"Adjusted\",\"fixedRateBusinessDayConvention\":\"Modified Following\",\"fixedRateDayCountFraction\":\"30/360\",\"fixedRateFrequency\":\"6m\",\"floatingFirstStub\":\"2021-12-10\",\"floatingHolidays\":\"New York,London\",\"floatingLastStub\":\"2026-12-10\",\"floatingRateAccrualConvention\":\"Adjusted\",\"floatingRateBusinessDayConvention\":\"Modified Following\",\"floatingRateDayCountFraction\":\"ACT/360\",\"floatingRateDesignatedMaturity\":\"3m\",\"floatingRateFrequency\":\"3m\",\"floatingRateOption\":\"USD-LIBOR-BBA\",\"floatingRateSpread\":0.0,\"notionalAmount\":100000.0,\"notionalCurrency\":\"USD\",\"payOrReceive\":\"Pay\",\"principalExchange\":\"None\",\"rollConvention\":\"Default\",\"terminationDate\":\"2026-12-10\",\"type\":\"Swap\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Rates\",\"effectiveDate\":\"2021-12-09\",\"fee\":0.0,\"feeCurrency\":\"USD\",\"feePaymentDate\":\"2021-12-09\",\"fixedFirstStub\":\"2021-12-09\",\"fixedHolidays\":\"New York,London\",\"fixedLastStub\":\"2031-12-09\",\"fixedRate\":0.015398724136309936,\"fixedRateAccrualConvention\":\"Adjusted\",\"fixedRateBusinessDayConvention\":\"Modified Following\",\"fixedRateDayCountFraction\":\"30/360\",\"fixedRateFrequency\":\"6m\",\"floatingFirstStub\":\"2021-12-09\",\"floatingHolidays\":\"New York,London\",\"floatingLastStub\":\"2031-12-09\",\"floatingRateAccrualConvention\":\"Adjusted\",\"floatingRateBusinessDayConvention\":\"Modified Following\",\"floatingRateDayCountFraction\":\"ACT/360\",\"floatingRateDesignatedMaturity\":\"3m\",\"floatingRateFrequency\":\"3m\",\"floatingRateOption\":\"USD-LIBOR-BBA\",\"floatingRateSpread\":0.0,\"notionalAmount\":100000.0,\"notionalCurrency\":\"USD\",\"payOrReceive\":\"Receive\",\"principalExchange\":\"None\",\"rollConvention\":\"Default\",\"terminationDate\":\"2031-12-09\",\"type\":\"Swap\"}],[{\"$type\":\"LegDefinition\",\"assetClass\":\"Rates\",\"effectiveDate\":\"2021-12-09\",\"fee\":0.0,\"feeCurrency\":\"USD\",\"feePaymentDate\":\"2021-12-09\",\"fixedFirstStub\":\"2021-12-09\",\"fixedHolidays\":\"New York,London\",\"fixedLastStub\":\"2026-12-09\",\"fixedRate\":0.013604541228709929,\"fixedRateAccrualConvention\":\"Adjusted\",\"fixedRateBusinessDayConvention\":\"Modified Following\",\"fixedRateDayCountFraction\":\"30/360\",\"fixedRateFrequency\":\"6m\",\"floatingFirstStub\":\"2021-12-09\",\"floatingHolidays\":\"New York,London\",\"floatingLastStub\":\"2026-12-09\",\"floatingRateAccrualConvention\":\"Adjusted\",\"floatingRateBusinessDayConvention\":\"Modified Following\",\"floatingRateDayCountFraction\":\"ACT/360\",\"floatingRateDesignatedMaturity\":\"3m\",\"floatingRateFrequency\":\"3m\",\"floatingRateOption\":\"USD-LIBOR-BBA\",\"floatingRateSpread\":0.0,\"notionalAmount\":100000.0,\"notionalCurrency\":\"USD\",\"payOrReceive\":\"Pay\",\"principalExchange\":\"None\",\"rollConvention\":\"Default\",\"terminationDate\":\"2026-12-09\",\"type\":\"Swap\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Rates\",\"effectiveDate\":\"2021-12-08\",\"fee\":0.0,\"feeCurrency\":\"USD\",\"feePaymentDate\":\"2021-12-08\",\"fixedFirstStub\":\"2021-12-08\",\"fixedHolidays\":\"New York,London\",\"fixedLastStub\":\"2031-12-08\",\"fixedRate\":0.01495929132108002,\"fixedRateAccrualConvention\":\"Adjusted\",\"fixedRateBusinessDayConvention\":\"Modified Following\",\"fixedRateDayCountFraction\":\"30/360\",\"fixedRateFrequency\":\"6m\",\"floatingFirstStub\":\"2021-12-08\",\"floatingHolidays\":\"New York,London\",\"floatingLastStub\":\"2031-12-08\",\"floatingRateAccrualConvention\":\"Adjusted\",\"floatingRateBusinessDayConvention\":\"Modified Following\",\"floatingRateDayCountFraction\":\"ACT/360\",\"floatingRateDesignatedMaturity\":\"3m\",\"floatingRateFrequency\":\"3m\",\"floatingRateOption\":\"USD-LIBOR-BBA\",\"floatingRateSpread\":0.0,\"notionalAmount\":100000.0,\"notionalCurrency\":\"USD\",\"payOrReceive\":\"Receive\",\"principalExchange\":\"None\",\"rollConvention\":\"Default\",\"terminationDate\":\"2031-12-08\",\"type\":\"Swap\"}],[{\"$type\":\"LegDefinition\",\"assetClass\":\"Rates\",\"effectiveDate\":\"2021-12-08\",\"fee\":0.0,\"feeCurrency\":\"USD\",\"feePaymentDate\":\"2021-12-08\",\"fixedFirstStub\":\"2021-12-08\",\"fixedHolidays\":\"New York,London\",\"fixedLastStub\":\"2026-12-08\",\"fixedRate\":0.013048688475290058,\"fixedRateAccrualConvention\":\"Adjusted\",\"fixedRateBusinessDayConvention\":\"Modified Following\",\"fixedRateDayCountFraction\":\"30/360\",\"fixedRateFrequency\":\"6m\",\"floatingFirstStub\":\"2021-12-08\",\"floatingHolidays\":\"New York,London\",\"floatingLastStub\":\"2026-12-08\",\"floatingRateAccrualConvention\":\"Adjusted\",\"floatingRateBusinessDayConvention\":\"Modified Following\",\"floatingRateDayCountFraction\":\"ACT/360\",\"floatingRateDesignatedMaturity\":\"3m\",\"floatingRateFrequency\":\"3m\",\"floatingRateOption\":\"USD-LIBOR-BBA\",\"floatingRateSpread\":0.0,\"notionalAmount\":100000.0,\"notionalCurrency\":\"USD\",\"payOrReceive\":\"Pay\",\"principalExchange\":\"None\",\"rollConvention\":\"Default\",\"terminationDate\":\"2026-12-08\",\"type\":\"Swap\"}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestad0c6bd76338f1bf498b16070275d04b.json ================================================ { "request_id": "1_FTSE_CallResolvedInstrumentValuestoday", "request_hash": "ad0c6bd76338f1bf498b16070275d04b", "type": "MockCalc", "tests": [ "test_instrument_resolve" ], "mocked_data": "[[[[{\"expirationDate\": \"2022-06-17\", \"optionType\": \"Call\", \"optionStyle\": \"European\", \"multiplier\": 1.0, \"settlementDate\": \"2022-05-19\", \"premium\": 0.0, \"premiumPaymentDate\": \"2022-05-19\", \"valuationTime\": \"SQ\", \"methodOfSettlement\": \"Cash\", \"underlierType\": \"RIC\", \"buySell\": \"Buy\", \"premiumCurrency\": \"GBP\", \"tradeAs\": \"OTC\", \"assetClass\": \"Equity\", \"type\": \"Option\", \"underlier\": \".FTSE\", \"strikePrice\": 7464.8, \"numberOfOptions\": 1.0, \"$type\": \"LegDefinition\", \"calculationTime\": 1026, \"queueingTime\": 276}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestaf28694ceb9c1d15492c7700c07306e6.json ================================================ { "request_id": "1_5y-10yIRAnnualImpliedVoltoday", "request_hash": "af28694ceb9c1d15492c7700c07306e6", "type": "MockCalc", "tests": [ "test_unsupported_error_datums" ], "mocked_data": "[[[[{\"$type\": \"Unsupported\", \"calculationTime\": 486, \"queueingTime\": 21}], [{\"$type\": \"Unsupported\", \"calculationTime\": 486, \"queueingTime\": 21}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestb0b099f6cc16db5524cb336b6373371e.json ================================================ { "request_id": "5_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-10Price2021Dec10_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-09Price2021Dec09_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-08Price2021Dec08_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-07Price2021Dec07_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-06Price2021Dec06", "request_hash": "b0b099f6cc16db5524cb336b6373371e", "type": "MockCalc", "tests": [ "test_add_scaled_action_nav_with_transaction_costs" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":43.224380829588995}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":56.182874587538684}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":77.95047367596334}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":115.08035388930729}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":93.12385921366753}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestb0b66e7402bd021f6f6f81f69bf9ef18.json ================================================ { "request_id": "1_5y-10yPricetodaymultiscenario", "request_hash": "b0b66e7402bd021f6f6f81f69bf9ef18", "type": "MockCalc", "tests": [ "test_multi_scenario" ], "mocked_data": "[[[[{\"$type\": \"Table\", \"rows\": [{\"label\": \"ir curve(parallel=0bp,slope=0bp,pivot=18y,upper cutoff=30y,lower cutoff=5y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 14425955.584723271, \"children\": {}}}, {\"label\": \"ir curve(parallel=5bp,slope=0bp,pivot=0y,upper cutoff=0y,lower cutoff=0y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 14663567.24341668, \"children\": {}}}], \"calculationTime\": 2069, \"queueingTime\": 502}], [{\"$type\": \"Table\", \"rows\": [{\"label\": \"ir curve(parallel=0bp,slope=0bp,pivot=18y,upper cutoff=30y,lower cutoff=5y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 27707181.40070747, \"children\": {}}}, {\"label\": \"ir curve(parallel=5bp,slope=0bp,pivot=0y,upper cutoff=0y,lower cutoff=0y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 28100037.66858688, \"children\": {}}}], \"calculationTime\": 2069, \"queueingTime\": 502}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestb112a918a2ece9a875d1c8d0856e1e68.json ================================================ { "request_id": "2_5y-10yIRBasis(aggregation_level:Asset)2020Jan15_5y-10yIRBasis(aggregation_level:Asset)2020Jan14", "request_hash": "b112a918a2ece9a875d1c8d0856e1e68", "type": "MockCalc", "tests": [ "test_bucketed_risks" ], "mocked_data": "[[[[{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR BASIS\", \"asset\": \"JPY OIS/JPY-3M\", \"value\": -449.7739185382754}, {\"type\": \"IR BASIS\", \"asset\": \"JPY-3M/JPY-6M\", \"value\": -456.0036470180169}, {\"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"value\": 0.011297343267500402}], \"values\": [-449.7739185382754, -456.0036470180169, 0.011297343267500402], \"calculationTime\": 3920, \"queueingTime\": 27}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR BASIS\", \"asset\": \"JPY OIS/JPY-3M\", \"value\": -884.0610834171742}, {\"type\": \"IR BASIS\", \"asset\": \"JPY-3M/JPY-6M\", \"value\": -896.4095613945515}, {\"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"value\": 0.02684328269660473}], \"values\": [-884.0610834171742, -896.4095613945515, 0.02684328269660473], \"calculationTime\": 3920, \"queueingTime\": 27}]]], [[[{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR BASIS\", \"asset\": \"JPY OIS/JPY-3M\", \"value\": -449.30442388910205}, {\"type\": \"IR BASIS\", \"asset\": \"JPY-3M/JPY-6M\", \"value\": -455.52893167063297}, {\"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"value\": 0.011345047548413277}], \"values\": [-449.30442388910205, -455.52893167063297, 0.011345047548413277], \"calculationTime\": 2015, \"queueingTime\": 20}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR BASIS\", \"asset\": \"JPY OIS/JPY-3M\", \"value\": -882.9184313941777}, {\"type\": \"IR BASIS\", \"asset\": \"JPY-3M/JPY-6M\", \"value\": -895.2552544714034}, {\"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"value\": 0.02699263792037964}], \"values\": [-882.9184313941777, -895.2552544714034, 0.02699263792037964], \"calculationTime\": 2015, \"queueingTime\": 20}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestb2ea00c2300d04ad523903f37a562dff.json ================================================ { "request_id": "2_AddAction1_test_hedge_without_risk_2y_call_2021-12-02-Scaled_test_hedge_without_risk_2y_forward_2021-12-01_HedgeAction1_Priceable0Price2021Dec03_AddAction1_test_hedge_without_risk_2y_call_2021-12-01Price2021Dec02", "request_hash": "b2ea00c2300d04ad523903f37a562dff", "type": "MockCalc", "tests": [ "test_hedge_without_risk" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":-1375.3354125070036}],[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":-7089.833271984011}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":1885.94041220285}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestb417b5930a480b4bb07a2793b782c32d.json ================================================ { "request_id": "57_Action1_Priceable0_2021-10-15Price2021Oct25_Action1_Priceable0_2021-10-15Price2021Oct22_Action1_Priceable0_2021-10-15Price2021Oct21_Action1_Priceable0_2021-10-15Price2021Oct20_Action1_Priceable0_2021-10-15Price2021Oct19_Action1_Priceable0_2021-10-15Price2021Oct18_Action1_Priceable0_2021-10-15Price2021Oct15_Action1_Priceable0_2021-10-14Price2021Oct25_Action1_Priceable0_2021-10-14Price2021Oct22_Action1_Priceable0_2021-10-14Price2021Oct21_Action1_Priceable0_2021-10-14Price2021Oct20_Action1_Priceable0_2021-10-14Price2021Oct19_Action1_Priceable0_2021-10-14Price2021Oct18_Action1_Priceable0_2021-10-14Price2021Oct15_Action1_Priceable0_2021-10-14Price2021Oct14_Action1_Priceable0_2021-10-13Price2021Oct25_Action1_Priceable0_2021-10-13Price2021Oct22_Action1_Priceable0_2021-10-13Price2021Oct21_Action1_Priceable0_2021-10-13Price2021Oct20_Action1_Priceable0_2021-10-13Price2021Oct19_Action1_Priceable0_2021-10-13Price2021Oct18_Action1_Priceable0_2021-10-13Price2021Oct15_Action1_Priceable0_2021-10-13Price2021Oct14_Action1_Priceable0_2021-10-13Price2021Oct13_Action1_Priceable0_2021-10-12Price2021Oct25_Action1_Priceable0_2021-10-12Price2021Oct22_Action1_Priceable0_2021-10-12Price2021Oct21_Action1_Priceable0_2021-10-12Price2021Oct20_Action1_Priceable0_2021-10-12Price2021Oct19_Action1_Priceable0_2021-10-12Price2021Oct18_Action1_Priceable0_2021-10-12Price2021Oct15_Action1_Priceable0_2021-10-12Price2021Oct14_Action1_Priceable0_2021-10-12Price2021Oct13_Action1_Priceable0_2021-10-12Price2021Oct12_Action1_Priceable0_2021-10-08Price2021Oct25_Action1_Priceable0_2021-10-08Price2021Oct22_Action1_Priceable0_2021-10-08Price2021Oct21_Action1_Priceable0_2021-10-08Price2021Oct20_Action1_Priceable0_2021-10-08Price2021Oct19_Action1_Priceable0_2021-10-08Price2021Oct18_Action1_Priceable0_2021-10-08Price2021Oct15_Action1_Priceable0_2021-10-08Price2021Oct14_Action1_Priceable0_2021-10-08Price2021Oct13_Action1_Priceable0_2021-10-08Price2021Oct12_Action1_Priceable0_2021-10-08Price2021Oct08_Action1_Priceable0_2021-10-07Price2021Oct25_Action1_Priceable0_2021-10-07Price2021Oct22_Action1_Priceable0_2021-10-07Price2021Oct21_Action1_Priceable0_2021-10-07Price2021Oct20_Action1_Priceable0_2021-10-07Price2021Oct19_Action1_Priceable0_2021-10-07Price2021Oct18_Action1_Priceable0_2021-10-07Price2021Oct15_Action1_Priceable0_2021-10-07Price2021Oct14_Action1_Priceable0_2021-10-07Price2021Oct13_Action1_Priceable0_2021-10-07Price2021Oct12_Action1_Priceable0_2021-10-07Price2021Oct08_Action1_Priceable0_2021-10-07Price2021Oct07", "request_hash": "b417b5930a480b4bb07a2793b782c32d", "type": "MockCalc", "tests": [ "test_mkt_trigger_data_sources" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 511717.63631535554, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 514258.8953000735, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 491462.88858659164, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 472674.4840585641, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 477018.18237907503, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 442248.9255025487, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 406953.822038989, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 511040.30427219707, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 515737.3702732436, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 490497.0197139296, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 468602.117036586, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 473745.0815234828, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 441019.8457782689, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 401868.87818351417, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 369895.7921804603, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 508905.2681313842, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 513445.4652330312, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 488403.917540667, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 466981.24154356646, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 472035.2605383064, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 439084.29096167436, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 400456.38552897424, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 368811.325039033, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 368435.90814839606, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 514317.19762446394, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 521933.9495005793, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 493331.35505754704, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 467210.89017176884, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 473440.67589496507, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 443534.128375974, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 399166.3603957, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 364572.0823197956, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 364401.66666740907, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 338137.79287985695, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 517012.62191837165, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 527680.0823576219, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 495904.7743752658, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 465402.38609642384, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 472718.33946297, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 446516.1756926084, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 397123.8743745926, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 360237.9658060162, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 360266.25483254035, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 330477.09724761586, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 336555.3515301244, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 521036.4292868591, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 533934.2072661034, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 500008.8591050575, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 466642.2539792273, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 474679.5005097987, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 450987.6620203175, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 398286.0208667001, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 360090.54581707786, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 360206.96918361774, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 328143.0405555605, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 331320.53284738935, \"children\": {}}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 333036.513511444, \"children\": {}}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestb90286425e6ad1f5a3e66eb9bfc5b528.json ================================================ { "request_id": "2_IRBasisSwap-5y-10yIRBasis-Price2020Jan15_IRBasisSwap-5y-10yIRBasis-Price2020Jan14", "request_hash": "b90286425e6ad1f5a3e66eb9bfc5b528", "type": "MockCalc", "tests": [ "test_diff_types_risk_measures" ], "mocked_data": "[[[[{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"10Y2Y\", \"quoteStyle\": \"\", \"value\": 405744.6194279584}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"12Y3Y\", \"quoteStyle\": \"\", \"value\": -2.7049966434646864}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"15Y5Y\", \"quoteStyle\": \"\", \"value\": 0.4586364130440048}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"1B3M\", \"quoteStyle\": \"\", \"value\": 1.6568529380081188e-05}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"20Y5Y\", \"quoteStyle\": \"\", \"value\": -0.0581540083809544}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"25Y5Y\", \"quoteStyle\": \"\", \"value\": -0.00046060085648987254}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"2Y1Y\", \"quoteStyle\": \"\", \"value\": -0.04601222562885948}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"30Y10Y\", \"quoteStyle\": \"\", \"value\": 2.8796041499730142e-05}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"3Y1Y\", \"quoteStyle\": \"\", \"value\": -0.06544752137885824}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"40Y10Y\", \"quoteStyle\": \"\", \"value\": -4.944848291894036e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"4Y1Y\", \"quoteStyle\": \"\", \"value\": -0.08590570713694583}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"50Y10Y\", \"quoteStyle\": \"\", \"value\": 2.0880404018247984e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"5Y1Y\", \"quoteStyle\": \"\", \"value\": -0.10546186429171317}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"60Y10Y\", \"quoteStyle\": \"\", \"value\": 2.924711642974108e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"6Y1Y\", \"quoteStyle\": \"\", \"value\": -0.12616944598341617}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"70Y10Y\", \"quoteStyle\": \"\", \"value\": -4.4622466194629843e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"7Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0048992460715503484}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"8Y1Y\", \"quoteStyle\": \"\", \"value\": -1.4293298199808782}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"9Y1Y\", \"quoteStyle\": \"\", \"value\": 5.563674117862115}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -0.004246504270044569}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -0.0014120342903164794}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -0.001764964046016145}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -0.007044020058876733}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -7.214288709508314e-05}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -0.004775869802779741}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -0.0025164864061987986}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -0.0075944115029706015}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"10Y2Y\", \"quoteStyle\": \"\", \"value\": -84.97845401369173}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"12Y3Y\", \"quoteStyle\": \"\", \"value\": -2.695562760522808}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"15Y5Y\", \"quoteStyle\": \"\", \"value\": 0.45756250306614404}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"1B6M\", \"quoteStyle\": \"\", \"value\": 5.490503449165322e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"20Y5Y\", \"quoteStyle\": \"\", \"value\": -0.058023613774304775}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"25Y5Y\", \"quoteStyle\": \"\", \"value\": -0.00046339946116316617}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"2Y1Y\", \"quoteStyle\": \"\", \"value\": -0.04652981228484072}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"30Y10Y\", \"quoteStyle\": \"\", \"value\": 2.783568842293267e-05}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"3Y1Y\", \"quoteStyle\": \"\", \"value\": -0.06535232516755368}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"40Y10Y\", \"quoteStyle\": \"\", \"value\": -1.5472355126181435e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"4Y1Y\", \"quoteStyle\": \"\", \"value\": -0.0857441228815066}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"50Y10Y\", \"quoteStyle\": \"\", \"value\": 2.88590949845703e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"5Y1Y\", \"quoteStyle\": \"\", \"value\": -0.10526258375287562}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"60Y10Y\", \"quoteStyle\": \"\", \"value\": 4.6320060017251623e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"6Y1Y\", \"quoteStyle\": \"\", \"value\": -0.12590371434695907}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"70Y10Y\", \"quoteStyle\": \"\", \"value\": 1.367775594226697e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"7Y1Y\", \"quoteStyle\": \"\", \"value\": 0.004813381776002139}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"8Y1Y\", \"quoteStyle\": \"\", \"value\": -1.4256236234467938}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"9Y1Y\", \"quoteStyle\": \"\", \"value\": 5.545869528312995}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -0.015408278234779556}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 0.0004233507977534858}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -0.016207716393130706}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -0.016064786268668047}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -1.4793317597132717e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -0.007578495348309474}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -0.007210944459505567}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -0.007527713041672761}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"10Y2Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"12Y3Y\", \"quoteStyle\": \"\", \"value\": 1.7347234759768072e-22}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"15Y5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"20Y5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"25Y5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"2Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"30Y10Y\", \"quoteStyle\": \"\", \"value\": -1.7347234759768072e-22}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"3Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"40Y10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"4Y1Y\", \"quoteStyle\": \"\", \"value\": -1.7347234759768072e-22}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"5Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"6Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"7Y1Y\", \"quoteStyle\": \"\", \"value\": -1.7347234759768072e-22}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"8Y1Y\", \"quoteStyle\": \"\", \"value\": 1.7347234759768072e-22}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"9Y1Y\", \"quoteStyle\": \"\", \"value\": -1.7347234759768072e-22}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": 2.4408351364385575e-13}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -1.1569121355359258e-14}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -1.7347234759768072e-22}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 1.7344995405232933e-14}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 5.784374715323004e-15}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 2.818628721916461e-13}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 5.784211130899219e-15}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -1.1566361583781326e-14}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -5.784683582837902e-15}], \"asset\": [405744.6194279584, -2.7049966434646864, 0.4586364130440048, 1.6568529380081188e-05, -0.0581540083809544, -0.00046060085648987254, -0.04601222562885948, 2.8796041499730142e-05, -0.06544752137885824, -4.944848291894036e-06, -0.08590570713694583, 2.0880404018247984e-06, -0.10546186429171317, 2.924711642974108e-06, -0.12616944598341617, -4.4622466194629843e-07, 0.0048992460715503484, -1.4293298199808782, 5.563674117862115, -0.004246504270044569, -0.0014120342903164794, -0.001764964046016145, -0.007044020058876733, -7.214288709508314e-05, -0.004775869802779741, -0.0025164864061987986, -0.0075944115029706015, -84.97845401369173, -2.695562760522808, 0.45756250306614404, 5.490503449165322e-06, -0.058023613774304775, -0.00046339946116316617, -0.04652981228484072, 2.783568842293267e-05, -0.06535232516755368, -1.5472355126181435e-06, -0.0857441228815066, 2.88590949845703e-07, -0.10526258375287562, 4.6320060017251623e-07, -0.12590371434695907, 1.367775594226697e-06, 0.004813381776002139, -1.4256236234467938, 5.545869528312995, -0.015408278234779556, 0.0004233507977534858, -0.016207716393130706, -0.016064786268668047, -1.4793317597132717e-06, -0.007578495348309474, -0.007210944459505567, -0.007527713041672761, 0.0, 1.7347234759768072e-22, 0.0, 0.0, 0.0, 0.0, -1.7347234759768072e-22, 0.0, 0.0, -1.7347234759768072e-22, 0.0, 0.0, -1.7347234759768072e-22, 1.7347234759768072e-22, -1.7347234759768072e-22, 2.4408351364385575e-13, -1.1569121355359258e-14, -1.7347234759768072e-22, 1.7344995405232933e-14, 5.784374715323004e-15, 2.818628721916461e-13, 5.784211130899219e-15, -1.1566361583781326e-14, -5.784683582837902e-15], \"calculationTime\": 33481, \"queueingTime\": -43543}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"10Y2Y\", \"quoteStyle\": \"\", \"value\": 9.258506819915771}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"12Y3Y\", \"quoteStyle\": \"\", \"value\": 0.057687837982177734}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"15Y5Y\", \"quoteStyle\": \"\", \"value\": -0.003837683868408203}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"1B3M\", \"quoteStyle\": \"\", \"value\": 5.551187644958496}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"20Y5Y\", \"quoteStyle\": \"\", \"value\": 7.232131958007813e-05}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"25Y5Y\", \"quoteStyle\": \"\", \"value\": 5.113983154296875e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"2Y1Y\", \"quoteStyle\": \"\", \"value\": -10589.849401908112}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"30Y10Y\", \"quoteStyle\": \"\", \"value\": 7.62939453125e-10}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"3Y1Y\", \"quoteStyle\": \"\", \"value\": -12511.677078327943}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"40Y10Y\", \"quoteStyle\": \"\", \"value\": -1.13677978515625e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"4Y1Y\", \"quoteStyle\": \"\", \"value\": -12320.90596242981}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"50Y10Y\", \"quoteStyle\": \"\", \"value\": -3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"5Y1Y\", \"quoteStyle\": \"\", \"value\": -12102.376926134491}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"60Y10Y\", \"quoteStyle\": \"\", \"value\": -4.32586669921875e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"6Y1Y\", \"quoteStyle\": \"\", \"value\": -13733.039604634858}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"70Y10Y\", \"quoteStyle\": \"\", \"value\": 1.35498046875e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"7Y1Y\", \"quoteStyle\": \"\", \"value\": -3815.2484860717777}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"8Y1Y\", \"quoteStyle\": \"\", \"value\": 844.3519143844605}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"9Y1Y\", \"quoteStyle\": \"\", \"value\": -159.42854584884645}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": 184.49854544830322}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 3212.9439492538454}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 172.08162244110108}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 174.20086592025757}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 150.11844921417236}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 203.36954039916992}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": 171.15208593444825}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -165.53709348754884}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"10Y2Y\", \"quoteStyle\": \"\", \"value\": 9.241225498962402}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"12Y3Y\", \"quoteStyle\": \"\", \"value\": 0.05727746963500977}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"15Y5Y\", \"quoteStyle\": \"\", \"value\": -0.0038296707153320313}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"1B6M\", \"quoteStyle\": \"\", \"value\": 1.90277099609375e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"20Y5Y\", \"quoteStyle\": \"\", \"value\": 7.240447998046875e-05}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"25Y5Y\", \"quoteStyle\": \"\", \"value\": 4.6844482421875e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"2Y1Y\", \"quoteStyle\": \"\", \"value\": -10706.922682094575}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"30Y10Y\", \"quoteStyle\": \"\", \"value\": -8.087158203125e-08}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"3Y1Y\", \"quoteStyle\": \"\", \"value\": -12492.927757463074}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"40Y10Y\", \"quoteStyle\": \"\", \"value\": -6.9427490234375e-08}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"4Y1Y\", \"quoteStyle\": \"\", \"value\": -12298.308817881012}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"50Y10Y\", \"quoteStyle\": \"\", \"value\": 4.06646728515625e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"5Y1Y\", \"quoteStyle\": \"\", \"value\": -12079.000949329376}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"60Y10Y\", \"quoteStyle\": \"\", \"value\": -4.0435791015625004e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"6Y1Y\", \"quoteStyle\": \"\", \"value\": -13704.458047653961}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"70Y10Y\", \"quoteStyle\": \"\", \"value\": 1.5045166015625001e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"7Y1Y\", \"quoteStyle\": \"\", \"value\": -3806.065706002045}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"8Y1Y\", \"quoteStyle\": \"\", \"value\": 842.6097994483948}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"9Y1Y\", \"quoteStyle\": \"\", \"value\": -159.08436201324463}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -3546.7088726379397}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 96.73977942810059}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -3730.995340700531}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -3696.9487045341493}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -2.3742675781250002e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -1744.2170980636597}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -1658.372558394623}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -1734.7148793701172}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"10Y2Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"12Y3Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"15Y5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"20Y5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"25Y5Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"2Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"30Y10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"3Y1Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"40Y10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"4Y1Y\", \"quoteStyle\": \"\", \"value\": -1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"5Y1Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"6Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"7Y1Y\", \"quoteStyle\": \"\", \"value\": -1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"8Y1Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"9Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": 1.7753519203186037}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -0.08414850082397461}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 0.1261595687866211}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 0.042072898101806644}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 2.0501417068481445}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 0.042071708679199224}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -0.0841284278869629}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -0.0420751449584961}], \"asset\": [9.258506819915771, 0.057687837982177734, -0.003837683868408203, 5.551187644958496, 7.232131958007813e-05, 5.113983154296875e-06, -10589.849401908112, 7.62939453125e-10, -12511.677078327943, -1.13677978515625e-07, -12320.90596242981, -3.0517578125e-09, -12102.376926134491, -4.32586669921875e-07, -13733.039604634858, 1.35498046875e-06, -3815.2484860717777, 844.3519143844605, -159.42854584884645, 184.49854544830322, 3212.9439492538454, 172.08162244110108, 174.20086592025757, 150.11844921417236, 203.36954039916992, 171.15208593444825, -165.53709348754884, 9.241225498962402, 0.05727746963500977, -0.0038296707153320313, 1.90277099609375e-06, 7.240447998046875e-05, 4.6844482421875e-06, -10706.922682094575, -8.087158203125e-08, -12492.927757463074, -6.9427490234375e-08, -12298.308817881012, 4.06646728515625e-07, -12079.000949329376, -4.0435791015625004e-07, -13704.458047653961, 1.5045166015625001e-06, -3806.065706002045, 842.6097994483948, -159.08436201324463, -3546.7088726379397, 96.73977942810059, -3730.995340700531, -3696.9487045341493, -2.3742675781250002e-06, -1744.2170980636597, -1658.372558394623, -1734.7148793701172, 1.52587890625e-09, 1.52587890625e-09, 0.0, 0.0, 1.52587890625e-09, 0.0, 0.0, 1.52587890625e-09, 0.0, -1.52587890625e-09, 1.52587890625e-09, 0.0, -1.52587890625e-09, 1.52587890625e-09, 0.0, 1.7753519203186037, -0.08414850082397461, 0.0, 0.1261595687866211, 0.042072898101806644, 2.0501417068481445, 0.042071708679199224, -0.0841284278869629, -0.0420751449584961], \"calculationTime\": 7810, \"queueingTime\": -43543}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"10Y2Y\", \"quoteStyle\": \"\", \"value\": -26591.257084970093}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"12Y3Y\", \"quoteStyle\": \"\", \"value\": -2374.027845069885}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"15Y5Y\", \"quoteStyle\": \"\", \"value\": 444.5891800796509}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"1B3M\", \"quoteStyle\": \"\", \"value\": 12.050723561096191}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"20Y5Y\", \"quoteStyle\": \"\", \"value\": -78.05707931365967}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"25Y5Y\", \"quoteStyle\": \"\", \"value\": 5.776280857849121}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"2Y1Y\", \"quoteStyle\": \"\", \"value\": -9822.967864016724}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"30Y10Y\", \"quoteStyle\": \"\", \"value\": 0.04644957427978516}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"3Y1Y\", \"quoteStyle\": \"\", \"value\": -11753.391259628297}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"40Y10Y\", \"quoteStyle\": \"\", \"value\": -0.004814881896972657}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"4Y1Y\", \"quoteStyle\": \"\", \"value\": -11536.157126277161}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"50Y10Y\", \"quoteStyle\": \"\", \"value\": 0.00019198455810546876}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"5Y1Y\", \"quoteStyle\": \"\", \"value\": -11664.837590922547}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"60Y10Y\", \"quoteStyle\": \"\", \"value\": 1.052398681640625e-05}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"6Y1Y\", \"quoteStyle\": \"\", \"value\": -11719.494330227662}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"70Y10Y\", \"quoteStyle\": \"\", \"value\": -1.0955810546875e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"7Y1Y\", \"quoteStyle\": \"\", \"value\": -11843.20195257721}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"8Y1Y\", \"quoteStyle\": \"\", \"value\": -11948.31284541321}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"9Y1Y\", \"quoteStyle\": \"\", \"value\": -10985.767816833497}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": 379.95214903717044}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 3293.4522864349365}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 372.4588083358765}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 372.46919907073976}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 351.1455139541626}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 402.96937806549073}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": 370.72622712402347}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": 28.05446554412842}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"10Y2Y\", \"quoteStyle\": \"\", \"value\": -26522.33792090149}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"12Y3Y\", \"quoteStyle\": \"\", \"value\": -2367.1302801849365}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"15Y5Y\", \"quoteStyle\": \"\", \"value\": 443.538028163147}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"1B6M\", \"quoteStyle\": \"\", \"value\": 8.1219482421875e-05}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"20Y5Y\", \"quoteStyle\": \"\", \"value\": -77.88495370025635}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"25Y5Y\", \"quoteStyle\": \"\", \"value\": 5.764439810180664}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"2Y1Y\", \"quoteStyle\": \"\", \"value\": -9931.615199047852}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"30Y10Y\", \"quoteStyle\": \"\", \"value\": 0.04633208465576172}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"3Y1Y\", \"quoteStyle\": \"\", \"value\": -11735.74545767212}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"40Y10Y\", \"quoteStyle\": \"\", \"value\": -0.004806358337402344}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"4Y1Y\", \"quoteStyle\": \"\", \"value\": -11515.014629562378}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"50Y10Y\", \"quoteStyle\": \"\", \"value\": 0.00019234619140625}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"5Y1Y\", \"quoteStyle\": \"\", \"value\": -11642.244660484315}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"60Y10Y\", \"quoteStyle\": \"\", \"value\": 1.0617065429687501e-05}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"6Y1Y\", \"quoteStyle\": \"\", \"value\": -11695.517557176208}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"70Y10Y\", \"quoteStyle\": \"\", \"value\": -5.92041015625e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"7Y1Y\", \"quoteStyle\": \"\", \"value\": -11817.364951347352}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"8Y1Y\", \"quoteStyle\": \"\", \"value\": -11920.420557855225}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"9Y1Y\", \"quoteStyle\": \"\", \"value\": -10958.77669172516}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -3289.848896138001}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 89.78869416503906}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -3460.7889487548828}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -3429.2080721206667}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -5.5999755859375e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -1617.8973011322023}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -1538.2698010055542}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -1609.093252522278}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"10Y2Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"12Y3Y\", \"quoteStyle\": \"\", \"value\": 3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"15Y5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"20Y5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"25Y5Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"2Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"30Y10Y\", \"quoteStyle\": \"\", \"value\": -1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"3Y1Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"40Y10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"4Y1Y\", \"quoteStyle\": \"\", \"value\": -3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"5Y1Y\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"6Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"7Y1Y\", \"quoteStyle\": \"\", \"value\": -3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"8Y1Y\", \"quoteStyle\": \"\", \"value\": 3.0517578125e-09}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"9Y1Y\", \"quoteStyle\": \"\", \"value\": -1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": 3.853967533874512}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -0.18267115783691407}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 0.2738695770263672}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 0.09133264465332032}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 4.450486401367188}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 0.09133005981445314}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -0.18262758331298828}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -0.09133751983642578}], \"asset\": [-26591.257084970093, -2374.027845069885, 444.5891800796509, 12.050723561096191, -78.05707931365967, 5.776280857849121, -9822.967864016724, 0.04644957427978516, -11753.391259628297, -0.004814881896972657, -11536.157126277161, 0.00019198455810546876, -11664.837590922547, 1.052398681640625e-05, -11719.494330227662, -1.0955810546875e-06, -11843.20195257721, -11948.31284541321, -10985.767816833497, 379.95214903717044, 3293.4522864349365, 372.4588083358765, 372.46919907073976, 351.1455139541626, 402.96937806549073, 370.72622712402347, 28.05446554412842, -26522.33792090149, -2367.1302801849365, 443.538028163147, 8.1219482421875e-05, -77.88495370025635, 5.764439810180664, -9931.615199047852, 0.04633208465576172, -11735.74545767212, -0.004806358337402344, -11515.014629562378, 0.00019234619140625, -11642.244660484315, 1.0617065429687501e-05, -11695.517557176208, -5.92041015625e-07, -11817.364951347352, -11920.420557855225, -10958.77669172516, -3289.848896138001, 89.78869416503906, -3460.7889487548828, -3429.2080721206667, -5.5999755859375e-06, -1617.8973011322023, -1538.2698010055542, -1609.093252522278, 1.52587890625e-09, 3.0517578125e-09, 0.0, 0.0, 1.52587890625e-09, 0.0, -1.52587890625e-09, 1.52587890625e-09, 0.0, -3.0517578125e-09, 1.52587890625e-09, 0.0, -3.0517578125e-09, 3.0517578125e-09, -1.52587890625e-09, 3.853967533874512, -0.18267115783691407, -1.52587890625e-09, 0.2738695770263672, 0.09133264465332032, 4.450486401367188, 0.09133005981445314, -0.18262758331298828, -0.09133751983642578], \"calculationTime\": 7810, \"queueingTime\": -43543}]], [[{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 0.0, \"children\": {}, \"calculationTime\": 6352, \"queueingTime\": -43543}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 5310827.561557852, \"children\": {}, \"calculationTime\": 1499, \"queueingTime\": -43543}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 11528844.938571109, \"children\": {}, \"calculationTime\": 1499, \"queueingTime\": -43543}]]], [[[{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"10Y2Y\", \"quoteStyle\": \"\", \"value\": 405773.8937584889}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"12Y3Y\", \"quoteStyle\": \"\", \"value\": -2.7200611018235445}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"15Y5Y\", \"quoteStyle\": \"\", \"value\": 0.4620983408390117}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"1B3M\", \"quoteStyle\": \"\", \"value\": 1.9142348062627196e-05}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"20Y5Y\", \"quoteStyle\": \"\", \"value\": -0.05881695695157414}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"25Y5Y\", \"quoteStyle\": \"\", \"value\": -0.0004348313974243736}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"2Y1Y\", \"quoteStyle\": \"\", \"value\": -0.04532005730201385}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"30Y10Y\", \"quoteStyle\": \"\", \"value\": 3.3729273530527494e-05}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"3Y1Y\", \"quoteStyle\": \"\", \"value\": -0.06414172854836668}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"40Y10Y\", \"quoteStyle\": \"\", \"value\": -1.923925516037272e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"4Y1Y\", \"quoteStyle\": \"\", \"value\": -0.08374533740142893}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"50Y10Y\", \"quoteStyle\": \"\", \"value\": 2.1080795956579692e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"5Y1Y\", \"quoteStyle\": \"\", \"value\": -0.10282040466133983}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"60Y10Y\", \"quoteStyle\": \"\", \"value\": 6.68770078622528e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"6Y1Y\", \"quoteStyle\": \"\", \"value\": -0.12230059635793628}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"70Y10Y\", \"quoteStyle\": \"\", \"value\": -1.8318484762269249e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"7Y1Y\", \"quoteStyle\": \"\", \"value\": 0.006799739159034514}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"8Y1Y\", \"quoteStyle\": \"\", \"value\": -1.4141260831853109}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"9Y1Y\", \"quoteStyle\": \"\", \"value\": 5.574206218636115}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -0.00418079191459558}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -0.0009141941589970257}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -0.0017561563032869175}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -0.00690739175706837}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -6.535531408466946e-05}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -0.004660697447200317}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -0.002458369731953079}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -0.007498851045334749}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"10Y2Y\", \"quoteStyle\": \"\", \"value\": -85.46635545868574}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"12Y3Y\", \"quoteStyle\": \"\", \"value\": -2.710048697822327}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"15Y5Y\", \"quoteStyle\": \"\", \"value\": 0.4609607774737547}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"1B6M\", \"quoteStyle\": \"\", \"value\": 4.7880060701380985e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"20Y5Y\", \"quoteStyle\": \"\", \"value\": -0.05868237908865136}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"25Y5Y\", \"quoteStyle\": \"\", \"value\": -0.000429893160394545}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"2Y1Y\", \"quoteStyle\": \"\", \"value\": -0.045561681992956175}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"30Y10Y\", \"quoteStyle\": \"\", \"value\": 3.197496361414087e-05}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"3Y1Y\", \"quoteStyle\": \"\", \"value\": -0.06406009498333481}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"40Y10Y\", \"quoteStyle\": \"\", \"value\": -3.5716199126435e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"4Y1Y\", \"quoteStyle\": \"\", \"value\": -0.08358323819592281}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"50Y10Y\", \"quoteStyle\": \"\", \"value\": 2.3358491151888296e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"5Y1Y\", \"quoteStyle\": \"\", \"value\": -0.10260589423554169}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"60Y10Y\", \"quoteStyle\": \"\", \"value\": 6.639239186325095e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"6Y1Y\", \"quoteStyle\": \"\", \"value\": -0.12201704753692034}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"70Y10Y\", \"quoteStyle\": \"\", \"value\": 4.26462079121612e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"7Y1Y\", \"quoteStyle\": \"\", \"value\": 0.006699602955160765}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"8Y1Y\", \"quoteStyle\": \"\", \"value\": -1.410156171921568}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"9Y1Y\", \"quoteStyle\": \"\", \"value\": 5.555144938701376}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -0.015517632057558189}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -0.00016462890102091226}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -0.016322404770140582}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -0.015997523050569753}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -8.432318382631872e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -0.0072544204277780015}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -0.006885186651978515}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -0.00707150696511475}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"10Y2Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"12Y3Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"15Y5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"20Y5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"25Y5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"2Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"30Y10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"3Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"40Y10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"4Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"5Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"6Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"7Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"8Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"9Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": 2.2616822297361757e-13}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -3.8646774625991646e-14}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 4.336808689942018e-23}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 1.945270854120107e-15}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 1.946160940735631e-15}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -4.927694172845998e-14}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 1.5846805638541905e-14}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": 2.0847655784854426e-14}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -1.9462577166215468e-15}], \"asset\": [405773.8937584889, -2.7200611018235445, 0.4620983408390117, 1.9142348062627196e-05, -0.05881695695157414, -0.0004348313974243736, -0.04532005730201385, 3.3729273530527494e-05, -0.06414172854836668, -1.923925516037272e-06, -0.08374533740142893, 2.1080795956579692e-06, -0.10282040466133983, 6.68770078622528e-07, -0.12230059635793628, -1.8318484762269249e-06, 0.006799739159034514, -1.4141260831853109, 5.574206218636115, -0.00418079191459558, -0.0009141941589970257, -0.0017561563032869175, -0.00690739175706837, -6.535531408466946e-05, -0.004660697447200317, -0.002458369731953079, -0.007498851045334749, -85.46635545868574, -2.710048697822327, 0.4609607774737547, 4.7880060701380985e-06, -0.05868237908865136, -0.000429893160394545, -0.045561681992956175, 3.197496361414087e-05, -0.06406009498333481, -3.5716199126435e-06, -0.08358323819592281, 2.3358491151888296e-06, -0.10260589423554169, 6.639239186325095e-07, -0.12201704753692034, 4.26462079121612e-07, 0.006699602955160765, -1.410156171921568, 5.555144938701376, -0.015517632057558189, -0.00016462890102091226, -0.016322404770140582, -0.015997523050569753, -8.432318382631872e-07, -0.0072544204277780015, -0.006885186651978515, -0.00707150696511475, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.2616822297361757e-13, -3.8646774625991646e-14, 4.336808689942018e-23, 1.945270854120107e-15, 1.946160940735631e-15, -4.927694172845998e-14, 1.5846805638541905e-14, 2.0847655784854426e-14, -1.9462577166215468e-15], \"calculationTime\": 8603, \"queueingTime\": -8888}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"10Y2Y\", \"quoteStyle\": \"\", \"value\": 9.52923478088379}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"12Y3Y\", \"quoteStyle\": \"\", \"value\": 0.05154130096435547}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"15Y5Y\", \"quoteStyle\": \"\", \"value\": -0.004792508697509766}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"1B3M\", \"quoteStyle\": \"\", \"value\": 5.800789187622071}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"20Y5Y\", \"quoteStyle\": \"\", \"value\": 9.450531005859375e-05}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"25Y5Y\", \"quoteStyle\": \"\", \"value\": 4.19158935546875e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"2Y1Y\", \"quoteStyle\": \"\", \"value\": -10602.449576880646}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"30Y10Y\", \"quoteStyle\": \"\", \"value\": 4.5776367187500004e-09}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"3Y1Y\", \"quoteStyle\": \"\", \"value\": -12469.135923092652}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"40Y10Y\", \"quoteStyle\": \"\", \"value\": 1.10626220703125e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"4Y1Y\", \"quoteStyle\": \"\", \"value\": -12228.274022883606}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"50Y10Y\", \"quoteStyle\": \"\", \"value\": 2.4490356445312503e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"5Y1Y\", \"quoteStyle\": \"\", \"value\": -12007.332722233583}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"60Y10Y\", \"quoteStyle\": \"\", \"value\": -6.8817138671875e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"6Y1Y\", \"quoteStyle\": \"\", \"value\": -13649.341682823944}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"70Y10Y\", \"quoteStyle\": \"\", \"value\": 1.910400390625e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"7Y1Y\", \"quoteStyle\": \"\", \"value\": -3834.8094675979614}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"8Y1Y\", \"quoteStyle\": \"\", \"value\": 842.6079226676941}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"9Y1Y\", \"quoteStyle\": \"\", \"value\": -159.07964895095827}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": 195.28271338806152}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 3325.0911272758485}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 182.37141670608523}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 185.94827760620117}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 160.28894634628296}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 213.49390620269776}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": 181.5859247871399}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -169.94579461669923}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"10Y2Y\", \"quoteStyle\": \"\", \"value\": 9.510745944213868}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"12Y3Y\", \"quoteStyle\": \"\", \"value\": 0.05111880798339844}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"15Y5Y\", \"quoteStyle\": \"\", \"value\": -0.004781258392333985}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"1B6M\", \"quoteStyle\": \"\", \"value\": 1.534271240234375e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"20Y5Y\", \"quoteStyle\": \"\", \"value\": 9.45343017578125e-05}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"25Y5Y\", \"quoteStyle\": \"\", \"value\": 4.180908203125e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"2Y1Y\", \"quoteStyle\": \"\", \"value\": -10664.253623651124}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"30Y10Y\", \"quoteStyle\": \"\", \"value\": -2.99072265625e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"3Y1Y\", \"quoteStyle\": \"\", \"value\": -12450.987770305634}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"40Y10Y\", \"quoteStyle\": \"\", \"value\": -1.96075439453125e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"4Y1Y\", \"quoteStyle\": \"\", \"value\": -12203.811459606171}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"50Y10Y\", \"quoteStyle\": \"\", \"value\": 4.11224365234375e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"5Y1Y\", \"quoteStyle\": \"\", \"value\": -11982.073376328279}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"60Y10Y\", \"quoteStyle\": \"\", \"value\": -1.64794921875e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"6Y1Y\", \"quoteStyle\": \"\", \"value\": -13618.282747435762}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"70Y10Y\", \"quoteStyle\": \"\", \"value\": 1.7890930175781251e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"7Y1Y\", \"quoteStyle\": \"\", \"value\": -3824.8424483169556}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"8Y1Y\", \"quoteStyle\": \"\", \"value\": 840.7348984207154}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"9Y1Y\", \"quoteStyle\": \"\", \"value\": -158.71503989257812}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -3627.3533278083805}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -31.419506301879885}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -3815.0634537918095}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -3738.1944226242067}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 2.8228759765625004e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -1696.5875288345337}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -1609.608328931427}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -1656.1133361984253}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"10Y2Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"12Y3Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"15Y5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"20Y5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"25Y5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"2Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"30Y10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"3Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"40Y10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"4Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"5Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"6Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"7Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"8Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"9Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": 5.192276766204834}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -0.8872367095947266}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 0.04465872573852539}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 0.044679160308837895}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -1.13127970123291}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 0.3638044273376465}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": 0.47861188125610354}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -0.04468138198852539}], \"asset\": [9.52923478088379, 0.05154130096435547, -0.004792508697509766, 5.800789187622071, 9.450531005859375e-05, 4.19158935546875e-06, -10602.449576880646, 4.5776367187500004e-09, -12469.135923092652, 1.10626220703125e-07, -12228.274022883606, 2.4490356445312503e-07, -12007.332722233583, -6.8817138671875e-07, -13649.341682823944, 1.910400390625e-06, -3834.8094675979614, 842.6079226676941, -159.07964895095827, 195.28271338806152, 3325.0911272758485, 182.37141670608523, 185.94827760620117, 160.28894634628296, 213.49390620269776, 181.5859247871399, -169.94579461669923, 9.510745944213868, 0.05111880798339844, -0.004781258392333985, 1.534271240234375e-06, 9.45343017578125e-05, 4.180908203125e-06, -10664.253623651124, -2.99072265625e-07, -12450.987770305634, -1.96075439453125e-07, -12203.811459606171, 4.11224365234375e-07, -11982.073376328279, -1.64794921875e-07, -13618.282747435762, 1.7890930175781251e-06, -3824.8424483169556, 840.7348984207154, -158.71503989257812, -3627.3533278083805, -31.419506301879885, -3815.0634537918095, -3738.1944226242067, 2.8228759765625004e-07, -1696.5875288345337, -1609.608328931427, -1656.1133361984253, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 5.192276766204834, -0.8872367095947266, 1.52587890625e-09, 0.04465872573852539, 0.044679160308837895, -1.13127970123291, 0.3638044273376465, 0.47861188125610354, -0.04468138198852539], \"calculationTime\": 8567, \"queueingTime\": -8888}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"10Y2Y\", \"quoteStyle\": \"\", \"value\": -26347.867820944215}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"12Y3Y\", \"quoteStyle\": \"\", \"value\": -2378.606844448853}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"15Y5Y\", \"quoteStyle\": \"\", \"value\": 446.0738113571167}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"1B3M\", \"quoteStyle\": \"\", \"value\": 12.465539062500001}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"20Y5Y\", \"quoteStyle\": \"\", \"value\": -78.54717943420411}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"25Y5Y\", \"quoteStyle\": \"\", \"value\": 5.848511454772949}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"2Y1Y\", \"quoteStyle\": \"\", \"value\": -9795.381138699342}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"30Y10Y\", \"quoteStyle\": \"\", \"value\": 0.04852867584228516}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"3Y1Y\", \"quoteStyle\": \"\", \"value\": -11674.271076853944}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"40Y10Y\", \"quoteStyle\": \"\", \"value\": -0.005189944458007812}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"4Y1Y\", \"quoteStyle\": \"\", \"value\": -11410.060714488221}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"50Y10Y\", \"quoteStyle\": \"\", \"value\": 0.0002031097412109375}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"5Y1Y\", \"quoteStyle\": \"\", \"value\": -11533.43322893219}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"60Y10Y\", \"quoteStyle\": \"\", \"value\": 1.2709045410156251e-05}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"6Y1Y\", \"quoteStyle\": \"\", \"value\": -11613.239750512696}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"70Y10Y\", \"quoteStyle\": \"\", \"value\": -4.2724609375e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"7Y1Y\", \"quoteStyle\": \"\", \"value\": -11663.5822681839}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"8Y1Y\", \"quoteStyle\": \"\", \"value\": -11911.988895561219}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP FWD\", \"point\": \"9Y1Y\", \"quoteStyle\": \"\", \"value\": -10881.180841523743}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": 398.9998337753296}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 3400.406100605774}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 391.11904293212893}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 392.71438133392337}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": 369.90518065490727}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 421.64106157379155}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": 389.8599864440918}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": 33.230199980163576}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"10Y2Y\", \"quoteStyle\": \"\", \"value\": -26275.577643197634}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"12Y3Y\", \"quoteStyle\": \"\", \"value\": -2371.3087557586673}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"15Y5Y\", \"quoteStyle\": \"\", \"value\": 444.9697718139649}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"1B6M\", \"quoteStyle\": \"\", \"value\": 8.1353759765625e-05}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"20Y5Y\", \"quoteStyle\": \"\", \"value\": -78.36512499542236}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"25Y5Y\", \"quoteStyle\": \"\", \"value\": 5.835893811035157}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"2Y1Y\", \"quoteStyle\": \"\", \"value\": -9852.181544091798}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"30Y10Y\", \"quoteStyle\": \"\", \"value\": 0.048396337890625}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"3Y1Y\", \"quoteStyle\": \"\", \"value\": -11657.282032495117}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"40Y10Y\", \"quoteStyle\": \"\", \"value\": -0.005180480957031251}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"4Y1Y\", \"quoteStyle\": \"\", \"value\": -11387.251607081604}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"50Y10Y\", \"quoteStyle\": \"\", \"value\": 0.00020276031494140625}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"5Y1Y\", \"quoteStyle\": \"\", \"value\": -11509.102528015137}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"60Y10Y\", \"quoteStyle\": \"\", \"value\": 1.285400390625e-05}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"6Y1Y\", \"quoteStyle\": \"\", \"value\": -11587.251107495118}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"70Y10Y\", \"quoteStyle\": \"\", \"value\": -2.7923583984375e-07}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"7Y1Y\", \"quoteStyle\": \"\", \"value\": -11636.209979219057}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"8Y1Y\", \"quoteStyle\": \"\", \"value\": -11882.081125758363}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP FWD\", \"point\": \"9Y1Y\", \"quoteStyle\": \"\", \"value\": -10852.30780966797}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -3351.3993125564575}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -29.416650483703616}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": -3524.8292206497194}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -3453.8080968093873}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -3.6819458007812502e-06}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -1567.5181828704835}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": -1487.1560013534547}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"class_\": \"SWAP IMM\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -1530.0591721755982}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"10Y2Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"12Y3Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"15Y5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"20Y5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"25Y5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"2Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"30Y10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"3Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"40Y10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"4Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"5Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"6Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"7Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"8Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"9Y1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP FWD RISK\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": 11.157791914367676}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -1.9066014846801758}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": 1.52587890625e-09}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"JUN20\", \"quoteStyle\": \"\", \"value\": 0.09596806640625001}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": 0.09601197967529297}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"MAR20\", \"quoteStyle\": \"\", \"value\": -2.431030561828613}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": 0.7817869262695313}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"SEP20\", \"quoteStyle\": \"\", \"value\": 1.0284990631103517}, {\"path\": \"\", \"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"class_\": \"SWAP IMM RISK\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -0.09601675415039063}], \"asset\": [-26347.867820944215, -2378.606844448853, 446.0738113571167, 12.465539062500001, -78.54717943420411, 5.848511454772949, -9795.381138699342, 0.04852867584228516, -11674.271076853944, -0.005189944458007812, -11410.060714488221, 0.0002031097412109375, -11533.43322893219, 1.2709045410156251e-05, -11613.239750512696, -4.2724609375e-07, -11663.5822681839, -11911.988895561219, -10881.180841523743, 398.9998337753296, 3400.406100605774, 391.11904293212893, 392.71438133392337, 369.90518065490727, 421.64106157379155, 389.8599864440918, 33.230199980163576, -26275.577643197634, -2371.3087557586673, 444.9697718139649, 8.1353759765625e-05, -78.36512499542236, 5.835893811035157, -9852.181544091798, 0.048396337890625, -11657.282032495117, -0.005180480957031251, -11387.251607081604, 0.00020276031494140625, -11509.102528015137, 1.285400390625e-05, -11587.251107495118, -2.7923583984375e-07, -11636.209979219057, -11882.081125758363, -10852.30780966797, -3351.3993125564575, -29.416650483703616, -3524.8292206497194, -3453.8080968093873, -3.6819458007812502e-06, -1567.5181828704835, -1487.1560013534547, -1530.0591721755982, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 11.157791914367676, -1.9066014846801758, 1.52587890625e-09, 0.09596806640625001, 0.09601197967529297, -2.431030561828613, 0.7817869262695313, 1.0284990631103517, -0.09601675415039063], \"calculationTime\": 8567, \"queueingTime\": -8888}]], [[{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 0.0, \"children\": {}, \"calculationTime\": 141, \"queueingTime\": -8888}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 5644562.7450038865, \"children\": {}, \"calculationTime\": 42, \"queueingTime\": -8888}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 12129718.693539664, \"children\": {}, \"calculationTime\": 42, \"queueingTime\": -8888}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestba369474db70c4ca2f99d1236c773532.json ================================================ { "request_id": "1_test_hedge_action_risk_trigger_2y_call_2021-12-01ResolvedInstrumentValues2021Dec01", "request_hash": "ba369474db70c4ca2f99d1236c773532", "type": "MockCalc", "tests": [ "test_hedge_action_risk_trigger" ], "mocked_data": "[[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"FX\",\"buySell\":\"Buy\",\"callAmount\":\"100000\",\"callCurrency\":\"USD\",\"exerciseStyle\":\"Manual\",\"expirationDate\":\"2023-11-30\",\"expirationTime\":\"NYC\",\"methodOfSettlement\":\"Physical\",\"notionalAmount\":100000.0,\"notionalAmountInOtherCurrency\":11054466.772999996,\"notionalCurrency\":\"USD\",\"optionType\":\"Call\",\"pair\":\"USD JPY\",\"premium\":-4176.647676080006,\"premiumCurrency\":\"USD\",\"premiumPaymentDate\":\"2023-12-04\",\"putAmount\":\"11054466.772999996319\",\"putCurrency\":\"JPY\",\"settlementDate\":\"2023-12-04\",\"strikePrice\":110.54466772999996,\"type\":\"Option\"}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestbac6a90eed6358050cc451b7346dbae5.json ================================================ { "request_id": "1_Action1_test_generic_engine_simple_2y_callResolvedInstrumentValues2021Dec01", "request_hash": "bac6a90eed6358050cc451b7346dbae5", "type": "MockCalc", "tests": [ "test_generic_engine_simple" ], "mocked_data": "[[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"FX\",\"buySell\":\"Buy\",\"callAmount\":\"100000\",\"callCurrency\":\"USD\",\"exerciseStyle\":\"Manual\",\"expirationDate\":\"2023-11-30\",\"expirationTime\":\"NYC\",\"methodOfSettlement\":\"Physical\",\"notionalAmount\":100000.0,\"notionalAmountInOtherCurrency\":11054466.772999996,\"notionalCurrency\":\"USD\",\"optionType\":\"Call\",\"pair\":\"USD JPY\",\"premium\":-4176.647676080006,\"premiumCurrency\":\"USD\",\"premiumPaymentDate\":\"2023-12-04\",\"putAmount\":\"11054466.772999996319\",\"putCurrency\":\"JPY\",\"settlementDate\":\"2023-12-04\",\"strikePrice\":110.54466772999997,\"type\":\"Option\"}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestbb865103df394a5176b211859dfca415.json ================================================ { "request_id": "1_Scaled_test_hedge_action_risk_trigger_2y_forward_2021-12-01_HedgeAction1_Priceable0Price2021Dec03", "request_hash": "bb865103df394a5176b211859dfca415", "type": "MockCalc", "tests": [ "test_hedge_action_risk_trigger" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":-7089.83327198308}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestc236b51cddef2fe8297064984e04c805.json ================================================ { "request_id": "1_5y-10yResolvedInstrumentValuestoday", "request_hash": "c236b51cddef2fe8297064984e04c805", "type": "MockCalc", "tests": [ "test_composite_multi_scenario", "test_multi_scenario", "test_portfolio_overrides", "test_resolve_to_frame" ], "mocked_data": "[[[[{\"payOrReceive\": \"Pay\", \"terminationDate\": \"2027-05-19\", \"notionalCurrency\": \"USD\", \"effectiveDate\": \"2022-05-19\", \"principalExchange\": \"None\", \"floatingRateOption\": \"USD-SOFR-COMPOUND\", \"floatingRateDesignatedMaturity\": \"1y\", \"floatingRateFrequency\": \"1y\", \"floatingRateBusinessDayConvention\": \"Modified Following\", \"fixedRateFrequency\": \"1y\", \"fixedRateBusinessDayConvention\": \"Modified Following\", \"fee\": 0.0, \"feeCurrency\": \"USD\", \"feePaymentDate\": \"2022-05-19\", \"fixedFirstStub\": \"2022-05-19\", \"floatingFirstStub\": \"2022-05-19\", \"fixedLastStub\": \"2027-05-19\", \"floatingLastStub\": \"2027-05-19\", \"fixedHolidays\": \"New York\", \"floatingHolidays\": \"New York\", \"rollConvention\": \"Default\", \"assetClass\": \"Rates\", \"type\": \"Swap\", \"notionalAmount\": 100000000.0, \"fixedRate\": -0.005, \"floatingRateSpread\": 0.0, \"$type\": \"LegDefinition\", \"calculationTime\": 58, \"queueingTime\": 231}], [{\"payOrReceive\": \"Pay\", \"terminationDate\": \"2032-05-19\", \"notionalCurrency\": \"USD\", \"effectiveDate\": \"2022-05-19\", \"principalExchange\": \"None\", \"floatingRateOption\": \"USD-SOFR-COMPOUND\", \"floatingRateDesignatedMaturity\": \"1y\", \"floatingRateFrequency\": \"1y\", \"floatingRateBusinessDayConvention\": \"Modified Following\", \"fixedRateFrequency\": \"1y\", \"fixedRateBusinessDayConvention\": \"Modified Following\", \"fee\": 0.0, \"feeCurrency\": \"USD\", \"feePaymentDate\": \"2022-05-19\", \"fixedFirstStub\": \"2022-05-19\", \"floatingFirstStub\": \"2022-05-19\", \"fixedLastStub\": \"2032-05-19\", \"floatingLastStub\": \"2032-05-19\", \"fixedHolidays\": \"New York\", \"floatingHolidays\": \"New York\", \"rollConvention\": \"Default\", \"assetClass\": \"Rates\", \"type\": \"Swap\", \"notionalAmount\": 100000000.0, \"fixedRate\": -0.005, \"floatingRateSpread\": 0.0, \"$type\": \"LegDefinition\", \"calculationTime\": 58, \"queueingTime\": 231}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestc39de7d52670682c117b785d51c8b4a6.json ================================================ { "request_id": "2_5yIRFwdRate-Price2020Jan15multiscenario_5yIRFwdRate-Price2020Jan14multiscenario", "request_hash": "c39de7d52670682c117b785d51c8b4a6", "type": "MockCalc", "tests": [ "test_historical_multi_scenario" ], "mocked_data": "[[[[{\"$type\": \"Table\", \"rows\": [{\"label\": \"IR Curve(parallel=5bp,slope=0bp,pivot=0y,upper cutoff=0y,lower cutoff=0y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"Rate\": 1.0}, \"val\": 0.016608986336539563, \"children\": {}}}, {\"label\": \"IR Curve(parallel=0bp,slope=0bp,pivot=18y,upper cutoff=30y,lower cutoff=5y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"Rate\": 1.0}, \"val\": 0.016082888336539595, \"children\": {}}}]}]], [[{\"$type\": \"Table\", \"rows\": [{\"label\": \"IR Curve(parallel=5bp,slope=0bp,pivot=0y,upper cutoff=0y,lower cutoff=0y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 10381697.1320309, \"children\": {}}}, {\"label\": \"IR Curve(parallel=0bp,slope=0bp,pivot=18y,upper cutoff=30y,lower cutoff=5y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 10144148.580168368, \"children\": {}}}]}]]], [[[{\"$type\": \"Table\", \"rows\": [{\"label\": \"IR Curve(parallel=5bp,slope=0bp,pivot=0y,upper cutoff=0y,lower cutoff=0y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"Rate\": 1.0}, \"val\": 0.016686828655216496, \"children\": {}}}, {\"label\": \"IR Curve(parallel=0bp,slope=0bp,pivot=18y,upper cutoff=30y,lower cutoff=5y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"Rate\": 1.0}, \"val\": 0.01616075865521661, \"children\": {}}}]}]], [[{\"$type\": \"Table\", \"rows\": [{\"label\": \"IR Curve(parallel=5bp,slope=0bp,pivot=0y,upper cutoff=0y,lower cutoff=0y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 10418941.63476517, \"children\": {}}}, {\"label\": \"IR Curve(parallel=0bp,slope=0bp,pivot=18y,upper cutoff=30y,lower cutoff=5y)\", \"value\": {\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 10181465.897207508, \"children\": {}}}]}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestc61016e904bc4d53f10bf8a9d42b7c84.json ================================================ { "request_id": "1_5y-10yPricetodayroll fwd scenario", "request_hash": "c61016e904bc4d53f10bf8a9d42b7c84", "type": "MockCalc", "tests": [ "test_adding_risk_results", "test_resolve_to_frame" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"Japanese Yen\": 1.0}, \"val\": 3363593.419528549, \"children\": {}}], [{\"$type\": \"Risk\", \"unit\": {\"Japanese Yen\": 1.0}, \"val\": 8765080.408820745, \"children\": {}}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestc61cdb727bd2ab602ece911831deb892.json ================================================ { "request_id": "3_test_hedge_action_risk_trigger_2y_call_2021-12-01FXDelta(aggregation_level:Type)-Price2021Dec03_test_hedge_action_risk_trigger_2y_call_2021-12-01FXDelta(aggregation_level:Type)-Price2021Dec02_test_hedge_action_risk_trigger_2y_call_2021-12-01FXDelta(aggregation_level:Type)-Price2021Dec01", "request_hash": "c61cdb727bd2ab602ece911831deb892", "type": "MockCalc", "tests": [ "test_hedge_action_risk_trigger" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"children\":{},\"val\":468.2125168392304}]],[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":537.6278704397264}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"val\":465.67824680096237}]],[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":1885.940412188589}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"val\":463.09122659749846}]],[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":0.0}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestc64a55ad8b16703d13479ff3b11b70ec.json ================================================ { "request_id": "1_5yIRDelta(currency:NOK)-IRDelta(currency:local)today", "request_hash": "c64a55ad8b16703d13479ff3b11b70ec", "type": "MockCalc", "tests": [ "test_finite_difference_params" ], "mocked_data": "[[[[{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -271.9612079223633}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": 6.530761718750001e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC22\", \"quoteStyle\": \"\", \"value\": -877.4708352600098}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC23\", \"quoteStyle\": \"\", \"value\": -493.7374934265137}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN22\", \"quoteStyle\": \"\", \"value\": -930.9146392578125}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN23\", \"quoteStyle\": \"\", \"value\": -490.57966947021487}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR23\", \"quoteStyle\": \"\", \"value\": -747.5386889770508}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR24\", \"quoteStyle\": \"\", \"value\": -302.8653449890137}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP22\", \"quoteStyle\": \"\", \"value\": -979.4686239379884}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP23\", \"quoteStyle\": \"\", \"value\": -451.92549525146484}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 1.776123046875e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 1.575927734375e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": -6.591796875e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -7.3974609375e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": -9.92431640625e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -8.77685546875e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -5237.040346295166}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -8.477783203125e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": -7166.424609960938}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": -1.7578125000000002e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 489584.12390561524}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"60Y\", \"quoteStyle\": \"\", \"value\": 6.793212890625001e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 1.1888234313964845}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"70Y\", \"quoteStyle\": \"\", \"value\": -1.5808105468750002e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": -0.3789893676757813}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"80Y\", \"quoteStyle\": \"\", \"value\": -1.085205078125e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 0.06347342529296875}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": -0.000212841796875}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -25.110150109863284}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -25.15535969848633}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"FRA\", \"point\": \"DEC22\", \"quoteStyle\": \"\", \"value\": -5.2563476562500005e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"FRA\", \"point\": \"DEC23\", \"quoteStyle\": \"\", \"value\": -0.0017835388183593752}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"FRA\", \"point\": \"JUN22\", \"quoteStyle\": \"\", \"value\": -0.0017071533203125001}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"FRA\", \"point\": \"JUN23\", \"quoteStyle\": \"\", \"value\": 0.00311363525390625}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"FRA\", \"point\": \"MAR23\", \"quoteStyle\": \"\", \"value\": -0.004376837158203125}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"FRA\", \"point\": \"MAR24\", \"quoteStyle\": \"\", \"value\": 9.765625e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"FRA\", \"point\": \"SEP22\", \"quoteStyle\": \"\", \"value\": 0.001523443603515625}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"FRA\", \"point\": \"SEP23\", \"quoteStyle\": \"\", \"value\": 0.001171160888671875}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 1.220703125e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": -1.220703125e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": -1.220703125e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": 1.220703125e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC22\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC23\", \"quoteStyle\": \"\", \"value\": -1.220703125e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC24\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN22\", \"quoteStyle\": \"\", \"value\": -1.220703125e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN23\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN24\", \"quoteStyle\": \"\", \"value\": -1.220703125e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR23\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR24\", \"quoteStyle\": \"\", \"value\": -1.220703125e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR25\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP22\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP23\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP24\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": -1.220703125e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": -1.220703125e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": -1.220703125e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 2.44140625e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": 1.220703125e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": -1.220703125e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -1.220703125e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": -1.220703125e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [-271.9612079223633, 6.530761718750001e-07, -877.4708352600098, -493.7374934265137, -930.9146392578125, -490.57966947021487, -747.5386889770508, -302.8653449890137, -979.4686239379884, -451.92549525146484, 1.776123046875e-06, 1.575927734375e-05, -6.591796875e-06, -7.3974609375e-06, -9.92431640625e-06, -8.77685546875e-06, -5237.040346295166, -8.477783203125e-06, -7166.424609960938, -1.7578125000000002e-06, 489584.12390561524, 6.793212890625001e-06, 1.1888234313964845, -1.5808105468750002e-06, -0.3789893676757813, -1.085205078125e-05, 0.06347342529296875, -0.000212841796875, -25.110150109863284, -25.15535969848633, -5.2563476562500005e-05, -0.0017835388183593752, -0.0017071533203125001, 0.00311363525390625, -0.004376837158203125, 9.765625e-08, 0.001523443603515625, 0.001171160888671875, 0.0, 0.0, 1.220703125e-08, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.220703125e-08, 0.0, 0.0, 0.0, -1.220703125e-08, 0.0, 0.0, 1.220703125e-08, 0.0, -1.220703125e-08, 0.0, -1.220703125e-08, 0.0, -1.220703125e-08, 0.0, -1.220703125e-08, 0.0, 0.0, 0.0, 0.0, 0.0, -1.220703125e-08, -1.220703125e-08, 0.0, 0.0, 0.0, -1.220703125e-08, 2.44140625e-08, 1.220703125e-08, 0.0, -1.220703125e-08, 0.0, -1.220703125e-08, -1.220703125e-08, 0.0, 0.0], \"calculationTime\": 3962, \"queueingTime\": 26}]], [[{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -29.053085066223147}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -2.4597726097106936}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC22\", \"quoteStyle\": \"\", \"value\": -85.79897988128663}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC23\", \"quoteStyle\": \"\", \"value\": -48.277585491180425}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN22\", \"quoteStyle\": \"\", \"value\": -91.02470782699585}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN23\", \"quoteStyle\": \"\", \"value\": -47.9688138923645}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR23\", \"quoteStyle\": \"\", \"value\": -73.09423214950561}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR24\", \"quoteStyle\": \"\", \"value\": -29.614132567596435}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP22\", \"quoteStyle\": \"\", \"value\": -95.77230989913942}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP23\", \"quoteStyle\": \"\", \"value\": -44.18921395263672}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 1.708984375e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 1.5426635742187501e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": -6.46209716796875e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -7.25555419921875e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": -9.7198486328125e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -8.5906982421875e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -512.0770984748841}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -8.300781250000001e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": -700.7320314613343}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": -1.7166137695312502e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 47871.46958023834}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"60Y\", \"quoteStyle\": \"\", \"value\": 6.6680908203125e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 0.11624299621582032}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"70Y\", \"quoteStyle\": \"\", \"value\": -1.556396484375e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": -0.03705752944946289}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"80Y\", \"quoteStyle\": \"\", \"value\": -1.06353759765625e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 0.006206422424316406}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": -2.0812225341796875e-05}], \"asset\": [-29.053085066223147, -2.4597726097106936, -85.79897988128663, -48.277585491180425, -91.02470782699585, -47.9688138923645, -73.09423214950561, -29.614132567596435, -95.77230989913942, -44.18921395263672, 1.708984375e-07, 1.5426635742187501e-06, -6.46209716796875e-07, -7.25555419921875e-07, -9.7198486328125e-07, -8.5906982421875e-07, -512.0770984748841, -8.300781250000001e-07, -700.7320314613343, -1.7166137695312502e-07, 47871.46958023834, 6.6680908203125e-07, 0.11624299621582032, -1.556396484375e-07, -0.03705752944946289, -1.06353759765625e-06, 0.006206422424316406, -2.0812225341796875e-05], \"calculationTime\": 3962, \"queueingTime\": 26}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestc9114e54aa19a8b53536ffee44a47797.json ================================================ { "request_id": "5_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-10Price2021Dec10_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-09Price2021Dec09_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-08Price2021Dec08_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-07Price2021Dec07_QuantityScaledAction2_TransactionAggType.MAX_call_2021-12-06Price2021Dec06", "request_hash": "c9114e54aa19a8b53536ffee44a47797", "type": "MockCalc", "tests": [ "test_add_scaled_action_nav_with_transaction_costs" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":48.94707237525772}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":61.00048258315881}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":81.92324141413035}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":118.12477756271826}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":94.06450425622982}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestc9537f6814eae8f18ee5fc96495e50bb.json ================================================ { "request_id": "1_swapIRVega(aggregation_level:Asset)today", "request_hash": "c9537f6814eae8f18ee5fc96495e50bb", "type": "MockCalc", "tests": [ "test_display_options" ], "mocked_data": "[[[[{\"$type\": \"RiskByClass\", \"classes\": [], \"values\": [], \"calculationTime\": 1693, \"queueingTime\": 289}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestca27b0fb75992d8bbf6e9c9f6f84ba73.json ================================================ { "request_id": "4_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-09Price2021Dec10_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-08Price2021Dec09_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-07Price2021Dec08_QuantityScaledAction2_TransactionAggType.SUM_call_2021-12-06Price2021Dec07", "request_hash": "ca27b0fb75992d8bbf6e9c9f6f84ba73", "type": "MockCalc", "tests": [ "test_add_scaled_action_nav_with_transaction_costs" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":52.773044571869995}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":68.1342727647409}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":94.09289661627552}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":131.2450730410533}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestcb0e3c415937bb510a07119d29d30f6c.json ================================================ { "request_id": "1_EUR3m10ypayerPricetoday", "request_hash": "cb0e3c415937bb510a07119d29d30f6c", "type": "MockCalc", "tests": [ "test_aggregation_with_heterogeous_types" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 2173028.0318295504, \"children\": {}, \"calculationTime\": 1091, \"queueingTime\": 67}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestcb5f0c0bf31052204b28b68fd4464ac2.json ================================================ { "request_id": "1_swap1IRFwdRate2020Oct15", "request_hash": "cb5f0c0bf31052204b28b68fd4464ac2", "type": "MockCalc", "tests": [ "test_single_instrument_new_mock" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"Rate\": 1.0}, \"val\": 0.007512000235944189, \"children\": {}, \"calculationTime\": 0, \"queueingTime\": 383}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestcc779554821681f119c9f516e46e3572.json ================================================ { "request_id": "1_5y-10y-5y-10yPrice2020Jan14", "request_hash": "cc779554821681f119c9f516e46e3572", "type": "MockCalc", "tests": [ "test_adding_risk_results", "test_dated_risk_values", "test_resolve_to_frame" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 3290317.2121213092, \"children\": {}, \"calculationTime\": 48, \"queueingTime\": -167}], [{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 9587809.11540649, \"children\": {}, \"calculationTime\": 48, \"queueingTime\": -167}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 5644562.7450038865, \"children\": {}, \"calculationTime\": 1464, \"queueingTime\": -167}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 12129718.693539664, \"children\": {}, \"calculationTime\": 1464, \"queueingTime\": -167}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestcce834252e64fa6189dbf804832284d6.json ================================================ { "request_id": "2_EUR 1y7y-EUR 2w10yPnlExplain2021Nov18_EUR 1y7y-EUR 2w10yResolvedInstrumentValues2021Nov18", "request_hash": "98d0ab099fb34bebe25fe3d2c24cd1bb", "type": "MockCalc", "tests": [ "test_aggregation_with_empty_measures" ], "mocked_data": "[[[[{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"CROSSES\", \"value\": -1370.9008814296685}, {\"asset\": \"CROSSES\", \"type\": \"IR\", \"value\": 0.006399066187441349}, {\"asset\": \"CROSSES\", \"type\": \"IR BASIS\", \"value\": 0.014245136640965939}, {\"asset\": \"CROSSES\", \"type\": \"IR CC\", \"value\": 3.2903626561164856e-06}, {\"asset\": \"CROSSES\", \"type\": \"IR JUMP\", \"value\": 3.2954849302768707e-06}, {\"asset\": \"EUR\", \"type\": \"IR\", \"value\": 410920.35261539975}, {\"asset\": \"EUR\", \"type\": \"IR CC\", \"value\": -0.08264087326824665}, {\"asset\": \"EUR\", \"type\": \"IR XC\", \"value\": -3.3034011721611023e-06}, {\"asset\": \"EUR\", \"type\": \"IR XC SPIKE\", \"value\": -3.293156623840332e-06}, {\"asset\": \"EUR OIS/EUR-3M\", \"type\": \"IR BASIS\", \"value\": -258.44979938166216}, {\"asset\": \"EUR-3M/EUR-6M\", \"type\": \"IR BASIS\", \"value\": -366.7981627667323}, {\"asset\": \"EUR-EURIBOR-TELERATE\", \"type\": \"IR VOL\", \"value\": 4201.962594814133}, {\"asset\": \"USD\", \"type\": \"IR\", \"value\": 0.05873745772987604}, {\"asset\": \"USD\", \"type\": \"IR CC\", \"value\": -3.2940879464149475e-06}, {\"asset\": \"USD\", \"type\": \"IR JUMP\", \"value\": -3.2940879464149475e-06}, {\"asset\": \"USD OIS/USD SOFR-1B\", \"type\": \"IR BASIS\", \"value\": -1.4794059097766876e-06}, {\"asset\": \"USD OIS/USD-3M\", \"type\": \"IR BASIS\", \"value\": -1.485925167798996e-06}, {\"asset\": \"USD OIS/USD-3M\", \"type\": \"IR SPIKE\", \"value\": -3.2978132367134094e-06}, {\"asset\": \"USD-LIBOR-BBA-3M\", \"type\": \"IR JUMP\", \"value\": -3.296881914138794e-06}, {\"asset\": \"USD/EUR\", \"type\": \"FX\", \"value\": -10306.127534352709}, {\"asset\": \"USD/EUR\", \"type\": \"FX FWD\", \"value\": 0.4980240114964545}], \"values\": [-1370.9008715432137, 0.006399066187441349, 0.014245136640965939, 3.2903626561164856e-06, 3.2954849302768707e-06, 410920.35261539975, -0.08264087326824665, -3.3034011721611023e-06, -3.293156623840332e-06, -258.44979938166216, -366.7981627667323, 4201.962594814133, 0.05873745772987604, -3.2940879464149475e-06, -3.2940879464149475e-06, -1.4794059097766876e-06, -1.485925167798996e-06, -3.2978132367134094e-06, -3.296881914138794e-06, -10306.127534352709, 0.4980240114964545]}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"CROSSES\", \"value\": -1928.531806293875}, {\"asset\": \"CROSSES\", \"type\": \"IR\", \"value\": 0.010343382135033607}, {\"asset\": \"CROSSES\", \"type\": \"IR BASIS\", \"value\": 0.019582354463636875}, {\"asset\": \"CROSSES\", \"type\": \"IR CC\", \"value\": 4.827044904232025e-06}, {\"asset\": \"CROSSES\", \"type\": \"IR JUMP\", \"value\": 4.834495484828949e-06}, {\"asset\": \"EUR\", \"type\": \"IR\", \"value\": 664188.2779106786}, {\"asset\": \"EUR\", \"type\": \"IR CC\", \"value\": -0.11099529545754194}, {\"asset\": \"EUR\", \"type\": \"IR XC\", \"value\": -4.847534000873566e-06}, {\"asset\": \"EUR\", \"type\": \"IR XC SPIKE\", \"value\": -4.8317015171051025e-06}, {\"asset\": \"EUR OIS/EUR-3M\", \"type\": \"IR BASIS\", \"value\": -493.3577497769147}, {\"asset\": \"EUR-3M/EUR-6M\", \"type\": \"IR BASIS\", \"value\": -325.4849054478109}, {\"asset\": \"EUR-EURIBOR-TELERATE\", \"type\": \"IR VOL\", \"value\": 77.1226749829948}, {\"asset\": \"USD\", \"type\": \"IR\", \"value\": 0.08924875129014254}, {\"asset\": \"USD\", \"type\": \"IR CC\", \"value\": -4.832632839679718e-06}, {\"asset\": \"USD\", \"type\": \"IR JUMP\", \"value\": -4.832632839679718e-06}, {\"asset\": \"USD OIS/USD SOFR-1B\", \"type\": \"IR BASIS\", \"value\": -6.16069883108139e-06}, {\"asset\": \"USD OIS/USD-3M\", \"type\": \"IR BASIS\", \"value\": -6.170943379402161e-06}, {\"asset\": \"USD OIS/USD-3M\", \"type\": \"IR SPIKE\", \"value\": -4.838220775127411e-06}, {\"asset\": \"USD-LIBOR-BBA-3M\", \"type\": \"IR JUMP\", \"value\": -4.837289452552795e-06}, {\"asset\": \"USD/EUR\", \"type\": \"FX\", \"value\": -15659.757689116523}, {\"asset\": \"USD/EUR\", \"type\": \"FX FWD\", \"value\": 0.7567280065268278}], \"values\": [-1928.5317917885259, 0.010343382135033607, 0.019582354463636875, 4.827044904232025e-06, 4.834495484828949e-06, 664188.2779106786, -0.11099529545754194, -4.847534000873566e-06, -4.8317015171051025e-06, -493.3577497769147, -325.4849054478109, 77.1226749829948, 0.08924875129014254, -4.832632839679718e-06, -4.832632839679718e-06, -6.16069883108139e-06, -6.170943379402161e-06, -4.838220775127411e-06, -4.837289452552795e-06, -15659.757689116523, 0.7567280065268278]}]]], [[[{\"$type\": \"LegDefinition\", \"assetClass\": \"Rates\", \"buySell\": \"Buy\", \"clearingHouse\": \"LCH\", \"effectiveDate\": \"2022-11-22\", \"expirationDate\": \"2022-11-18\", \"fee\": 0.0, \"feeCurrency\": \"EUR\", \"feePaymentDate\": \"2021-11-22\", \"fixedRateBusinessDayConvention\": \"Modified Following\", \"fixedRateDayCountFraction\": \"30/360\", \"fixedRateFrequency\": \"1y\", \"floatingRateBusinessDayConvention\": \"Modified Following\", \"floatingRateDayCountFraction\": \"ACT/360\", \"floatingRateDesignatedMaturity\": \"6m\", \"floatingRateFrequency\": \"6m\", \"floatingRateOption\": \"EUR-EURIBOR-TELERATE\", \"floatingRateSpread\": 0.0, \"notionalAmount\": 100000000.0, \"notionalCurrency\": \"EUR\", \"payOrReceive\": \"Receive\", \"premium\": 0.0, \"premiumPaymentDate\": \"2021-11-22\", \"settlement\": \"Phys.CLEARED\", \"strike\": 0.004972874655321323, \"terminationDate\": \"2029-11-22\", \"type\": \"Swaption\"}], [{\"$type\": \"LegDefinition\", \"assetClass\": \"Rates\", \"buySell\": \"Buy\", \"clearingHouse\": \"LCH\", \"effectiveDate\": \"2021-12-06\", \"expirationDate\": \"2021-12-02\", \"fee\": 0.0, \"feeCurrency\": \"EUR\", \"feePaymentDate\": \"2021-11-22\", \"fixedRateBusinessDayConvention\": \"Modified Following\", \"fixedRateDayCountFraction\": \"30/360\", \"fixedRateFrequency\": \"1y\", \"floatingRateBusinessDayConvention\": \"Modified Following\", \"floatingRateDayCountFraction\": \"ACT/360\", \"floatingRateDesignatedMaturity\": \"6m\", \"floatingRateFrequency\": \"6m\", \"floatingRateOption\": \"EUR-EURIBOR-TELERATE\", \"floatingRateSpread\": 0.0, \"notionalAmount\": 100000000.0, \"notionalCurrency\": \"EUR\", \"payOrReceive\": \"Receive\", \"premium\": 0.0, \"premiumPaymentDate\": \"2021-11-22\", \"settlement\": \"Phys.CLEARED\", \"strike\": 0.006793310392477121, \"terminationDate\": \"2031-12-06\", \"type\": \"Swaption\"}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestcea65b205ad7e24117413111443a5d21.json ================================================ { "request_id": "1_5y-10y-5y-10yCashflows2020Jan14", "request_hash": "cea65b205ad7e24117413111443a5d21", "type": "MockCalc", "tests": [ "test_cashflows_risk" ], "mocked_data": "[[[[{\"$type\": \"IRPCashflowTable\", \"cashflows\": [{\"accStart\": \"2022-05-19\", \"accEnd\": \"2023-05-19\", \"payDate\": \"2023-05-19\", \"payAmount\": 500000.0, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 1.0130496679840855, \"dayCountFraction\": 1.0, \"paymentType\": \"FIX\"}, {\"accStart\": \"2023-05-19\", \"accEnd\": \"2024-05-20\", \"payDate\": \"2024-05-20\", \"payAmount\": 501388.8888888889, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 1.0147804418053112, \"dayCountFraction\": 1.0027777777777778, \"paymentType\": \"FIX\"}, {\"accStart\": \"2024-05-20\", \"accEnd\": \"2025-05-19\", \"payDate\": \"2025-05-19\", \"payAmount\": 498611.1111111111, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 1.015139047660011, \"dayCountFraction\": 0.9972222222222222, \"paymentType\": \"FIX\"}, {\"accStart\": \"2025-05-19\", \"accEnd\": \"2026-05-19\", \"payDate\": \"2026-05-19\", \"payAmount\": 500000.0, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 1.0141762887966481, \"dayCountFraction\": 1.0, \"paymentType\": \"FIX\"}, {\"accStart\": \"2026-05-19\", \"accEnd\": \"2027-05-19\", \"payDate\": \"2027-05-19\", \"payAmount\": 500000.0, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 1.011715089984835, \"dayCountFraction\": 1.0, \"paymentType\": \"FIX\"}, {\"accStart\": \"2022-05-19\", \"accEnd\": \"2022-11-21\", \"setDate\": \"2022-05-17\", \"payDate\": \"2022-11-21\", \"payAmount\": -70828.59104772244, \"notional\": 100000000.0, \"rate\": -0.0013708759557623696, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.011747289115936, \"dayCountFraction\": 0.5166666666666667, \"paymentType\": \"Flt\"}, {\"accStart\": \"2022-11-21\", \"accEnd\": \"2023-05-19\", \"setDate\": \"2022-11-17\", \"payDate\": \"2023-05-19\", \"payAmount\": -36898.9210132765, \"notional\": 100000000.0, \"rate\": -0.0007421012047362871, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0130496679840855, \"dayCountFraction\": 0.49722222222222223, \"paymentType\": \"Flt\"}, {\"accStart\": \"2023-05-19\", \"accEnd\": \"2023-11-20\", \"setDate\": \"2023-05-17\", \"payDate\": \"2023-11-20\", \"payAmount\": -5157.170115011045, \"notional\": 100000000.0, \"rate\": -0.00010035574277859332, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0140974894630779, \"dayCountFraction\": 0.5138888888888888, \"paymentType\": \"Flt\"}, {\"accStart\": \"2023-11-20\", \"accEnd\": \"2024-05-20\", \"setDate\": \"2023-11-16\", \"payDate\": \"2024-05-20\", \"payAmount\": 27725.824430002707, \"notional\": 100000000.0, \"rate\": 0.0005484229008132404, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0147804418053112, \"dayCountFraction\": 0.5055555555555555, \"paymentType\": \"Flt\"}, {\"accStart\": \"2024-05-20\", \"accEnd\": \"2024-11-19\", \"setDate\": \"2024-05-16\", \"payDate\": \"2024-11-19\", \"payAmount\": 61187.90629491917, \"notional\": 100000000.0, \"rate\": 0.0012036965172770985, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0151194783475241, \"dayCountFraction\": 0.5083333333333333, \"paymentType\": \"Flt\"}, {\"accStart\": \"2024-11-19\", \"accEnd\": \"2025-05-19\", \"setDate\": \"2024-11-15\", \"payDate\": \"2025-05-19\", \"payAmount\": 89431.9077864747, \"notional\": 100000000.0, \"rate\": 0.0017787561769685575, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.015139047660011, \"dayCountFraction\": 0.5027777777777778, \"paymentType\": \"Flt\"}, {\"accStart\": \"2025-05-19\", \"accEnd\": \"2025-11-19\", \"setDate\": \"2025-05-15\", \"payDate\": \"2025-11-19\", \"payAmount\": 121238.05064725433, \"notional\": 100000000.0, \"rate\": 0.0023720488170114983, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0148291893765238, \"dayCountFraction\": 0.5111111111111111, \"paymentType\": \"Flt\"}, {\"accStart\": \"2025-11-19\", \"accEnd\": \"2026-05-19\", \"setDate\": \"2025-11-17\", \"payDate\": \"2026-05-19\", \"payAmount\": 150206.26167761008, \"notional\": 100000000.0, \"rate\": 0.0029875278565712504, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0141762887966481, \"dayCountFraction\": 0.5027777777777778, \"paymentType\": \"Flt\"}, {\"accStart\": \"2026-05-19\", \"accEnd\": \"2026-11-19\", \"setDate\": \"2026-05-15\", \"payDate\": \"2026-11-19\", \"payAmount\": 187044.77926168026, \"notional\": 100000000.0, \"rate\": 0.0036595717681633102, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0131348524711106, \"dayCountFraction\": 0.5111111111111111, \"paymentType\": \"Flt\"}, {\"accStart\": \"2026-11-19\", \"accEnd\": \"2027-05-19\", \"setDate\": \"2026-11-17\", \"payDate\": \"2027-05-19\", \"payAmount\": 221654.08434868802, \"notional\": 100000000.0, \"rate\": 0.004408589522957331, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.011715089984835, \"dayCountFraction\": 0.5027777777777778, \"paymentType\": \"Flt\"}], \"calculationTime\": 49, \"queueingTime\": -163}], [{\"$type\": \"IRPCashflowTable\", \"cashflows\": [{\"accStart\": \"2022-05-19\", \"accEnd\": \"2023-05-19\", \"payDate\": \"2023-05-19\", \"payAmount\": 500000.0, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 1.0130496679840855, \"dayCountFraction\": 1.0, \"paymentType\": \"FIX\"}, {\"accStart\": \"2023-05-19\", \"accEnd\": \"2024-05-20\", \"payDate\": \"2024-05-20\", \"payAmount\": 501388.8888888889, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 1.0147804418053112, \"dayCountFraction\": 1.0027777777777778, \"paymentType\": \"FIX\"}, {\"accStart\": \"2024-05-20\", \"accEnd\": \"2025-05-19\", \"payDate\": \"2025-05-19\", \"payAmount\": 498611.1111111111, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 1.015139047660011, \"dayCountFraction\": 0.9972222222222222, \"paymentType\": \"FIX\"}, {\"accStart\": \"2025-05-19\", \"accEnd\": \"2026-05-19\", \"payDate\": \"2026-05-19\", \"payAmount\": 500000.0, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 1.0141762887966481, \"dayCountFraction\": 1.0, \"paymentType\": \"FIX\"}, {\"accStart\": \"2026-05-19\", \"accEnd\": \"2027-05-19\", \"payDate\": \"2027-05-19\", \"payAmount\": 500000.0, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 1.011715089984835, \"dayCountFraction\": 1.0, \"paymentType\": \"FIX\"}, {\"accStart\": \"2027-05-19\", \"accEnd\": \"2028-05-19\", \"payDate\": \"2028-05-19\", \"payAmount\": 500000.0, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 1.0077444832736635, \"dayCountFraction\": 1.0, \"paymentType\": \"FIX\"}, {\"accStart\": \"2028-05-19\", \"accEnd\": \"2029-05-21\", \"payDate\": \"2029-05-21\", \"payAmount\": 502777.77777777775, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 1.0024606118259742, \"dayCountFraction\": 1.0055555555555555, \"paymentType\": \"FIX\"}, {\"accStart\": \"2029-05-21\", \"accEnd\": \"2030-05-20\", \"payDate\": \"2030-05-20\", \"payAmount\": 498611.1111111111, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 0.9960648625729001, \"dayCountFraction\": 0.9972222222222222, \"paymentType\": \"FIX\"}, {\"accStart\": \"2030-05-20\", \"accEnd\": \"2031-05-19\", \"payDate\": \"2031-05-19\", \"payAmount\": 498611.1111111111, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 0.9887564328731777, \"dayCountFraction\": 0.9972222222222222, \"paymentType\": \"FIX\"}, {\"accStart\": \"2031-05-19\", \"accEnd\": \"2032-05-19\", \"payDate\": \"2032-05-19\", \"payAmount\": 500000.0, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"EUR\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 0.9806521541949186, \"dayCountFraction\": 1.0, \"paymentType\": \"FIX\"}, {\"accStart\": \"2022-05-19\", \"accEnd\": \"2022-11-21\", \"setDate\": \"2022-05-17\", \"payDate\": \"2022-11-21\", \"payAmount\": -70828.59104772244, \"notional\": 100000000.0, \"rate\": -0.0013708759557623696, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.011747289115936, \"dayCountFraction\": 0.5166666666666667, \"paymentType\": \"Flt\"}, {\"accStart\": \"2022-11-21\", \"accEnd\": \"2023-05-19\", \"setDate\": \"2022-11-17\", \"payDate\": \"2023-05-19\", \"payAmount\": -36898.9210132765, \"notional\": 100000000.0, \"rate\": -0.0007421012047362871, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0130496679840855, \"dayCountFraction\": 0.49722222222222223, \"paymentType\": \"Flt\"}, {\"accStart\": \"2023-05-19\", \"accEnd\": \"2023-11-20\", \"setDate\": \"2023-05-17\", \"payDate\": \"2023-11-20\", \"payAmount\": -5157.170115011045, \"notional\": 100000000.0, \"rate\": -0.00010035574277859332, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0140974894630779, \"dayCountFraction\": 0.5138888888888888, \"paymentType\": \"Flt\"}, {\"accStart\": \"2023-11-20\", \"accEnd\": \"2024-05-20\", \"setDate\": \"2023-11-16\", \"payDate\": \"2024-05-20\", \"payAmount\": 27725.824430002707, \"notional\": 100000000.0, \"rate\": 0.0005484229008132404, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0147804418053112, \"dayCountFraction\": 0.5055555555555555, \"paymentType\": \"Flt\"}, {\"accStart\": \"2024-05-20\", \"accEnd\": \"2024-11-19\", \"setDate\": \"2024-05-16\", \"payDate\": \"2024-11-19\", \"payAmount\": 61187.90629491917, \"notional\": 100000000.0, \"rate\": 0.0012036965172770985, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0151194783475241, \"dayCountFraction\": 0.5083333333333333, \"paymentType\": \"Flt\"}, {\"accStart\": \"2024-11-19\", \"accEnd\": \"2025-05-19\", \"setDate\": \"2024-11-15\", \"payDate\": \"2025-05-19\", \"payAmount\": 89431.9077864747, \"notional\": 100000000.0, \"rate\": 0.0017787561769685575, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.015139047660011, \"dayCountFraction\": 0.5027777777777778, \"paymentType\": \"Flt\"}, {\"accStart\": \"2025-05-19\", \"accEnd\": \"2025-11-19\", \"setDate\": \"2025-05-15\", \"payDate\": \"2025-11-19\", \"payAmount\": 121238.05064725433, \"notional\": 100000000.0, \"rate\": 0.0023720488170114983, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0148291893765238, \"dayCountFraction\": 0.5111111111111111, \"paymentType\": \"Flt\"}, {\"accStart\": \"2025-11-19\", \"accEnd\": \"2026-05-19\", \"setDate\": \"2025-11-17\", \"payDate\": \"2026-05-19\", \"payAmount\": 150206.26167761008, \"notional\": 100000000.0, \"rate\": 0.0029875278565712504, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0141762887966481, \"dayCountFraction\": 0.5027777777777778, \"paymentType\": \"Flt\"}, {\"accStart\": \"2026-05-19\", \"accEnd\": \"2026-11-19\", \"setDate\": \"2026-05-15\", \"payDate\": \"2026-11-19\", \"payAmount\": 187044.77926168026, \"notional\": 100000000.0, \"rate\": 0.0036595717681633102, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0131348524711106, \"dayCountFraction\": 0.5111111111111111, \"paymentType\": \"Flt\"}, {\"accStart\": \"2026-11-19\", \"accEnd\": \"2027-05-19\", \"setDate\": \"2026-11-17\", \"payDate\": \"2027-05-19\", \"payAmount\": 221654.08434868802, \"notional\": 100000000.0, \"rate\": 0.004408589522957331, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.011715089984835, \"dayCountFraction\": 0.5027777777777778, \"paymentType\": \"Flt\"}, {\"accStart\": \"2027-05-19\", \"accEnd\": \"2027-11-19\", \"setDate\": \"2027-05-17\", \"payDate\": \"2027-11-19\", \"payAmount\": 261894.0207562703, \"notional\": 100000000.0, \"rate\": 0.005124013449579202, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0098877719690662, \"dayCountFraction\": 0.5111111111111111, \"paymentType\": \"Flt\"}, {\"accStart\": \"2027-11-19\", \"accEnd\": \"2028-05-19\", \"setDate\": \"2027-11-17\", \"payDate\": \"2028-05-19\", \"payAmount\": 291621.3636698251, \"notional\": 100000000.0, \"rate\": 0.00576833466599654, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0077444832736635, \"dayCountFraction\": 0.5055555555555555, \"paymentType\": \"Flt\"}, {\"accStart\": \"2028-05-19\", \"accEnd\": \"2028-11-20\", \"setDate\": \"2028-05-17\", \"payDate\": \"2028-11-20\", \"payAmount\": 327612.39756220474, \"notional\": 100000000.0, \"rate\": 0.006375160168778039, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0052411706862365, \"dayCountFraction\": 0.5138888888888888, \"paymentType\": \"Flt\"}, {\"accStart\": \"2028-11-20\", \"accEnd\": \"2029-05-21\", \"setDate\": \"2028-11-16\", \"payDate\": \"2029-05-21\", \"payAmount\": 350438.9598987467, \"notional\": 100000000.0, \"rate\": 0.0069317596463488364, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 1.0024606118259742, \"dayCountFraction\": 0.5055555555555555, \"paymentType\": \"Flt\"}, {\"accStart\": \"2029-05-21\", \"accEnd\": \"2029-11-19\", \"setDate\": \"2029-05-17\", \"payDate\": \"2029-11-19\", \"payAmount\": 376999.0923760699, \"notional\": 100000000.0, \"rate\": 0.007457124904142043, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 0.9993898039067624, \"dayCountFraction\": 0.5055555555555555, \"paymentType\": \"Flt\"}, {\"accStart\": \"2029-11-19\", \"accEnd\": \"2030-05-20\", \"setDate\": \"2029-11-15\", \"payDate\": \"2030-05-20\", \"payAmount\": 401820.13595972955, \"notional\": 100000000.0, \"rate\": 0.007948090601401244, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 0.9960648625729001, \"dayCountFraction\": 0.5055555555555555, \"paymentType\": \"Flt\"}, {\"accStart\": \"2030-05-20\", \"accEnd\": \"2030-11-19\", \"setDate\": \"2030-05-16\", \"payDate\": \"2030-11-19\", \"payAmount\": 426267.6974761437, \"notional\": 100000000.0, \"rate\": 0.008385594048711024, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 0.9924939070664874, \"dayCountFraction\": 0.5083333333333333, \"paymentType\": \"Flt\"}, {\"accStart\": \"2030-11-19\", \"accEnd\": \"2031-05-19\", \"setDate\": \"2030-11-15\", \"payDate\": \"2031-05-19\", \"payAmount\": 441954.3973012363, \"notional\": 100000000.0, \"rate\": 0.00879025320599144, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 0.9887564328731777, \"dayCountFraction\": 0.5027777777777778, \"paymentType\": \"Flt\"}, {\"accStart\": \"2031-05-19\", \"accEnd\": \"2031-11-19\", \"setDate\": \"2031-05-15\", \"payDate\": \"2031-11-19\", \"payAmount\": 468753.4322965314, \"notional\": 100000000.0, \"rate\": 0.009171262805801703, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 0.9847665928808156, \"dayCountFraction\": 0.5111111111111111, \"paymentType\": \"Flt\"}, {\"accStart\": \"2031-11-19\", \"accEnd\": \"2032-05-19\", \"setDate\": \"2031-11-17\", \"payDate\": \"2032-05-19\", \"payAmount\": 481268.0671174574, \"notional\": 100000000.0, \"rate\": 0.009519588140784872, \"spread\": 0.0, \"currency\": \"EUR\", \"index\": \"EUR-EURIBOR-Telerate\", \"indexTerm\": \"6m\", \"discountFactor\": 0.9806521541949186, \"dayCountFraction\": 0.5055555555555555, \"paymentType\": \"Flt\"}], \"calculationTime\": 49, \"queueingTime\": -163}], [{\"$type\": \"IRPCashflowTable\", \"cashflows\": [{\"accStart\": \"2022-05-17\", \"accEnd\": \"2023-05-17\", \"payDate\": \"2023-05-17\", \"payAmount\": 500000.0, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"GBP\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 0.9831521511713562, \"dayCountFraction\": 1.0, \"paymentType\": \"FIX\"}, {\"accStart\": \"2023-05-17\", \"accEnd\": \"2024-05-17\", \"payDate\": \"2024-05-17\", \"payAmount\": 501369.86301369866, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"GBP\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 0.9770323121788788, \"dayCountFraction\": 1.0027397260273974, \"paymentType\": \"FIX\"}, {\"accStart\": \"2024-05-17\", \"accEnd\": \"2025-05-19\", \"payDate\": \"2025-05-19\", \"payAmount\": 502739.72602739726, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"GBP\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 0.970489737681349, \"dayCountFraction\": 1.0054794520547945, \"paymentType\": \"FIX\"}, {\"accStart\": \"2025-05-19\", \"accEnd\": \"2026-05-18\", \"payDate\": \"2026-05-18\", \"payAmount\": 498630.1369863014, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"GBP\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 0.9636199527111986, \"dayCountFraction\": 0.9972602739726028, \"paymentType\": \"FIX\"}, {\"accStart\": \"2026-05-18\", \"accEnd\": \"2027-05-17\", \"payDate\": \"2027-05-17\", \"payAmount\": 498630.1369863014, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"GBP\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 0.9563340585137056, \"dayCountFraction\": 0.9972602739726028, \"paymentType\": \"FIX\"}, {\"accStart\": \"2022-05-17\", \"accEnd\": \"2023-05-17\", \"setDate\": \"2022-05-17\", \"payDate\": \"2023-05-17\", \"payAmount\": 545258.6052307584, \"notional\": 100000000.0, \"rate\": 0.005452586052307584, \"spread\": 0.0, \"currency\": \"GBP\", \"index\": \"GBP-SONIA-COMPOUND\", \"indexTerm\": \"1y\", \"discountFactor\": 0.9831521511713562, \"dayCountFraction\": 1.0, \"paymentType\": \"Flt\"}, {\"accStart\": \"2023-05-17\", \"accEnd\": \"2024-05-17\", \"setDate\": \"2023-05-17\", \"payDate\": \"2024-05-17\", \"payAmount\": 626370.1738614457, \"notional\": 100000000.0, \"rate\": 0.006246587799437915, \"spread\": 0.0, \"currency\": \"GBP\", \"index\": \"GBP-SONIA-COMPOUND\", \"indexTerm\": \"1y\", \"discountFactor\": 0.9770323121788788, \"dayCountFraction\": 1.0027397260273974, \"paymentType\": \"Flt\"}, {\"accStart\": \"2024-05-17\", \"accEnd\": \"2025-05-19\", \"setDate\": \"2024-05-17\", \"payDate\": \"2025-05-19\", \"payAmount\": 674151.8476187869, \"notional\": 100000000.0, \"rate\": 0.006704779955881668, \"spread\": 0.0, \"currency\": \"GBP\", \"index\": \"GBP-SONIA-COMPOUND\", \"indexTerm\": \"1y\", \"discountFactor\": 0.970489737681349, \"dayCountFraction\": 1.0054794520547945, \"paymentType\": \"Flt\"}, {\"accStart\": \"2025-05-19\", \"accEnd\": \"2026-05-18\", \"setDate\": \"2025-05-19\", \"payDate\": \"2026-05-18\", \"payAmount\": 712914.3549613693, \"notional\": 100000000.0, \"rate\": 0.007148729108815928, \"spread\": 0.0, \"currency\": \"GBP\", \"index\": \"GBP-SONIA-COMPOUND\", \"indexTerm\": \"1y\", \"discountFactor\": 0.9636199527111986, \"dayCountFraction\": 0.9972602739726028, \"paymentType\": \"Flt\"}, {\"accStart\": \"2026-05-18\", \"accEnd\": \"2027-05-17\", \"setDate\": \"2026-05-18\", \"payDate\": \"2027-05-17\", \"payAmount\": 761856.6057154386, \"notional\": 100000000.0, \"rate\": 0.00763949618368503, \"spread\": 0.0, \"currency\": \"GBP\", \"index\": \"GBP-SONIA-COMPOUND\", \"indexTerm\": \"1y\", \"discountFactor\": 0.9563340585137056, \"dayCountFraction\": 0.9972602739726028, \"paymentType\": \"Flt\"}], \"calculationTime\": 187, \"queueingTime\": -163}], [{\"$type\": \"IRPCashflowTable\", \"cashflows\": [{\"accStart\": \"2022-05-17\", \"accEnd\": \"2023-05-17\", \"payDate\": \"2023-05-17\", \"payAmount\": 500000.0, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"GBP\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 0.9831521511713562, \"dayCountFraction\": 1.0, \"paymentType\": \"FIX\"}, {\"accStart\": \"2023-05-17\", \"accEnd\": \"2024-05-17\", \"payDate\": \"2024-05-17\", \"payAmount\": 501369.86301369866, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"GBP\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 0.9770323121788788, \"dayCountFraction\": 1.0027397260273974, \"paymentType\": \"FIX\"}, {\"accStart\": \"2024-05-17\", \"accEnd\": \"2025-05-19\", \"payDate\": \"2025-05-19\", \"payAmount\": 502739.72602739726, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"GBP\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 0.970489737681349, \"dayCountFraction\": 1.0054794520547945, \"paymentType\": \"FIX\"}, {\"accStart\": \"2025-05-19\", \"accEnd\": \"2026-05-18\", \"payDate\": \"2026-05-18\", \"payAmount\": 498630.1369863014, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"GBP\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 0.9636199527111986, \"dayCountFraction\": 0.9972602739726028, \"paymentType\": \"FIX\"}, {\"accStart\": \"2026-05-18\", \"accEnd\": \"2027-05-17\", \"payDate\": \"2027-05-17\", \"payAmount\": 498630.1369863014, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"GBP\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 0.9563340585137056, \"dayCountFraction\": 0.9972602739726028, \"paymentType\": \"FIX\"}, {\"accStart\": \"2027-05-17\", \"accEnd\": \"2028-05-17\", \"payDate\": \"2028-05-17\", \"payAmount\": 501369.86301369866, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"GBP\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 0.9486271264614388, \"dayCountFraction\": 1.0027397260273974, \"paymentType\": \"FIX\"}, {\"accStart\": \"2028-05-17\", \"accEnd\": \"2029-05-17\", \"payDate\": \"2029-05-17\", \"payAmount\": 500000.0, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"GBP\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 0.9404286567161938, \"dayCountFraction\": 1.0, \"paymentType\": \"FIX\"}, {\"accStart\": \"2029-05-17\", \"accEnd\": \"2030-05-17\", \"payDate\": \"2030-05-17\", \"payAmount\": 500000.0, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"GBP\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 0.9318650862207328, \"dayCountFraction\": 1.0, \"paymentType\": \"FIX\"}, {\"accStart\": \"2030-05-17\", \"accEnd\": \"2031-05-19\", \"payDate\": \"2031-05-19\", \"payAmount\": 502739.72602739726, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"GBP\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 0.9232843632986942, \"dayCountFraction\": 1.0054794520547945, \"paymentType\": \"FIX\"}, {\"accStart\": \"2031-05-19\", \"accEnd\": \"2032-05-17\", \"payDate\": \"2032-05-17\", \"payAmount\": 498630.1369863014, \"notional\": 100000000.0, \"rate\": -0.005, \"currency\": \"GBP\", \"index\": \"NA\", \"indexTerm\": \"NA\", \"discountFactor\": 0.9148033004964009, \"dayCountFraction\": 0.9972602739726028, \"paymentType\": \"FIX\"}, {\"accStart\": \"2022-05-17\", \"accEnd\": \"2023-05-17\", \"setDate\": \"2022-05-17\", \"payDate\": \"2023-05-17\", \"payAmount\": 545258.6052307584, \"notional\": 100000000.0, \"rate\": 0.005452586052307584, \"spread\": 0.0, \"currency\": \"GBP\", \"index\": \"GBP-SONIA-COMPOUND\", \"indexTerm\": \"1y\", \"discountFactor\": 0.9831521511713562, \"dayCountFraction\": 1.0, \"paymentType\": \"Flt\"}, {\"accStart\": \"2023-05-17\", \"accEnd\": \"2024-05-17\", \"setDate\": \"2023-05-17\", \"payDate\": \"2024-05-17\", \"payAmount\": 626370.1738614457, \"notional\": 100000000.0, \"rate\": 0.006246587799437915, \"spread\": 0.0, \"currency\": \"GBP\", \"index\": \"GBP-SONIA-COMPOUND\", \"indexTerm\": \"1y\", \"discountFactor\": 0.9770323121788788, \"dayCountFraction\": 1.0027397260273974, \"paymentType\": \"Flt\"}, {\"accStart\": \"2024-05-17\", \"accEnd\": \"2025-05-19\", \"setDate\": \"2024-05-17\", \"payDate\": \"2025-05-19\", \"payAmount\": 674151.8476187869, \"notional\": 100000000.0, \"rate\": 0.006704779955881668, \"spread\": 0.0, \"currency\": \"GBP\", \"index\": \"GBP-SONIA-COMPOUND\", \"indexTerm\": \"1y\", \"discountFactor\": 0.970489737681349, \"dayCountFraction\": 1.0054794520547945, \"paymentType\": \"Flt\"}, {\"accStart\": \"2025-05-19\", \"accEnd\": \"2026-05-18\", \"setDate\": \"2025-05-19\", \"payDate\": \"2026-05-18\", \"payAmount\": 712914.3549613693, \"notional\": 100000000.0, \"rate\": 0.007148729108815928, \"spread\": 0.0, \"currency\": \"GBP\", \"index\": \"GBP-SONIA-COMPOUND\", \"indexTerm\": \"1y\", \"discountFactor\": 0.9636199527111986, \"dayCountFraction\": 0.9972602739726028, \"paymentType\": \"Flt\"}, {\"accStart\": \"2026-05-18\", \"accEnd\": \"2027-05-17\", \"setDate\": \"2026-05-18\", \"payDate\": \"2027-05-17\", \"payAmount\": 761856.6057154386, \"notional\": 100000000.0, \"rate\": 0.00763949618368503, \"spread\": 0.0, \"currency\": \"GBP\", \"index\": \"GBP-SONIA-COMPOUND\", \"indexTerm\": \"1y\", \"discountFactor\": 0.9563340585137056, \"dayCountFraction\": 0.9972602739726028, \"paymentType\": \"Flt\"}, {\"accStart\": \"2027-05-17\", \"accEnd\": \"2028-05-17\", \"setDate\": \"2027-05-17\", \"payDate\": \"2028-05-17\", \"payAmount\": 812430.0726055411, \"notional\": 100000000.0, \"rate\": 0.008102103183088046, \"spread\": 0.0, \"currency\": \"GBP\", \"index\": \"GBP-SONIA-COMPOUND\", \"indexTerm\": \"1y\", \"discountFactor\": 0.9486271264614388, \"dayCountFraction\": 1.0027397260273974, \"paymentType\": \"Flt\"}, {\"accStart\": \"2028-05-17\", \"accEnd\": \"2029-05-17\", \"setDate\": \"2028-05-17\", \"payDate\": \"2029-05-17\", \"payAmount\": 871780.0852508395, \"notional\": 100000000.0, \"rate\": 0.008717800852508395, \"spread\": 0.0, \"currency\": \"GBP\", \"index\": \"GBP-SONIA-COMPOUND\", \"indexTerm\": \"1y\", \"discountFactor\": 0.9404286567161938, \"dayCountFraction\": 1.0, \"paymentType\": \"Flt\"}, {\"accStart\": \"2029-05-17\", \"accEnd\": \"2030-05-17\", \"setDate\": \"2029-05-17\", \"payDate\": \"2030-05-17\", \"payAmount\": 918971.0637394155, \"notional\": 100000000.0, \"rate\": 0.009189710637394155, \"spread\": 0.0, \"currency\": \"GBP\", \"index\": \"GBP-SONIA-COMPOUND\", \"indexTerm\": \"1y\", \"discountFactor\": 0.9318650862207328, \"dayCountFraction\": 1.0, \"paymentType\": \"Flt\"}, {\"accStart\": \"2030-05-17\", \"accEnd\": \"2031-05-19\", \"setDate\": \"2030-05-17\", \"payDate\": \"2031-05-19\", \"payAmount\": 929369.4622295677, \"notional\": 100000000.0, \"rate\": 0.009243047785116955, \"spread\": 0.0, \"currency\": \"GBP\", \"index\": \"GBP-SONIA-COMPOUND\", \"indexTerm\": \"1y\", \"discountFactor\": 0.9232843632986942, \"dayCountFraction\": 1.0054794520547945, \"paymentType\": \"Flt\"}, {\"accStart\": \"2031-05-19\", \"accEnd\": \"2032-05-17\", \"setDate\": \"2031-05-19\", \"payDate\": \"2032-05-17\", \"payAmount\": 927091.4083596483, \"notional\": 100000000.0, \"rate\": 0.009296383627782187, \"spread\": 0.0, \"currency\": \"GBP\", \"index\": \"GBP-SONIA-COMPOUND\", \"indexTerm\": \"1y\", \"discountFactor\": 0.9148033004964009, \"dayCountFraction\": 0.9972602739726028, \"paymentType\": \"Flt\"}], \"calculationTime\": 187, \"queueingTime\": -163}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestcf7ff46417f372533eec9ff30e85e53a.json ================================================ { "request_id": "2_test_hedge_action_risk_trigger_2y_forward_2021-12-01_HedgeAction1_Priceable0FXDelta(aggregation_level:Type)-Price2021Dec02_test_hedge_action_risk_trigger_2y_forward_2021-12-01_HedgeAction1_Priceable0FXDelta(aggregation_level:Type)-Price2021Dec01", "request_hash": "cf7ff46417f372533eec9ff30e85e53a", "type": "MockCalc", "tests": [ "test_hedge_action_risk_trigger" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"children\":{},\"val\":884.8913761903532}]],[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":6883.020442333072}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"val\":885.5439737817505}]],[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":-1.862645149230957e-9}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestcfd7a039b31a326a7f59903d43a0d82f.json ================================================ { "request_id": "2_Action1_swap_2021-12-09Price2021Dec09_Action1_swap_2021-12-07Price2021Dec07", "request_hash": "cfd7a039b31a326a7f59903d43a0d82f", "type": "MockCalc", "tests": [ "test_exit_action_noarg" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":0.0}]]],[[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"United States Dollar\":1.0},\"val\":0.0}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestd5a11b24c7329de1b599df1a3e5dd3a5.json ================================================ { "request_id": "1_5y-10y-5y-10yPrice2020Jan16", "request_hash": "d5a11b24c7329de1b599df1a3e5dd3a5", "type": "MockCalc", "tests": [ "test_adding_risk_results" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 3041774.635963849, \"children\": {}, \"calculationTime\": 55, \"queueingTime\": -196}], [{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 9109315.08610723, \"children\": {}, \"calculationTime\": 55, \"queueingTime\": -196}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 5296375.390821766, \"children\": {}, \"calculationTime\": 3492, \"queueingTime\": -196}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 11492700.86880836, \"children\": {}, \"calculationTime\": 3492, \"queueingTime\": -196}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestd985fa8d172480fdad5e1d2593ad8bac.json ================================================ { "request_id": "4_Scaled_None_2023-06-08_HedgeAction1_test_hedge_transaction_costs_syn_fwd_call-Scaled_None_2023-06-08_HedgeAction1_test_hedge_transaction_costs_syn_fwd_putPrice2023Jun09_Scaled_None_2023-06-07_HedgeAction1_test_hedge_transaction_costs_syn_fwd_call-Scaled_None_2023-06-07_HedgeAction1_test_hedge_transaction_costs_syn_fwd_putPrice2023Jun08_Scaled_None_2023-06-06_HedgeAction1_test_hedge_transaction_costs_syn_fwd_call-Scaled_None_2023-06-06_HedgeAction1_test_hedge_transaction_costs_syn_fwd_putPrice2023Jun07_Scaled_None_2023-06-05_HedgeAction1_test_hedge_transaction_costs_syn_fwd_call-Scaled_None_2023-06-05_HedgeAction1_test_hedge_transaction_costs_syn_fwd_putPrice2023Jun06", "request_hash": "d985fa8d172480fdad5e1d2593ad8bac", "type": "MockCalc", "tests": [ "test_hedge_transaction_costs" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":-49383.77659177569}],[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":-29442.675462475407}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":-48685.59058475203}],[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":-30201.99182165767}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":-45734.494192928905}],[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":-34663.91900898249}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":-49056.16867956639}],[{\"$type\":\"Risk\",\"unit\":{\"United States Dollar\":1.0},\"val\":-36108.83459402832}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestd9f1692ebd3ee6eeae81298c1e555c97.json ================================================ { "request_id": "3_Action1_call_2021-12-01DollarPrice2021Dec03_Action1_call_2021-12-01DollarPrice2021Dec02_Action1_call_2021-12-01DollarPrice2021Dec01", "request_hash": "d9f1692ebd3ee6eeae81298c1e555c97", "type": "MockCalc", "tests": [ "test_generic_engine_custom_price_measure" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 250.24996521804488}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 262.04764680372136}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 291.4666891854834}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestdafae46704002ad2c1a2e8bc9a8f6bd4.json ================================================ { "request_id": "2_5y-10yPrice2020Jan15_5y-10yPrice2020Jan14", "request_hash": "dafae46704002ad2c1a2e8bc9a8f6bd4", "type": "MockCalc", "tests": [ "test_dated_risk_values" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"Japanese Yen\": 1.0}, \"val\": 2245537.5291603645, \"children\": {}, \"calculationTime\": 1368, \"queueingTime\": 17}], [{\"$type\": \"Risk\", \"unit\": {\"Japanese Yen\": 1.0}, \"val\": 5335555.207899147, \"children\": {}, \"calculationTime\": 1368, \"queueingTime\": 17}]]], [[[{\"$type\": \"Risk\", \"unit\": {\"Japanese Yen\": 1.0}, \"val\": 2257946.840053587, \"children\": {}, \"calculationTime\": 1086, \"queueingTime\": 13}], [{\"$type\": \"Risk\", \"unit\": {\"Japanese Yen\": 1.0}, \"val\": 5372206.792654425, \"children\": {}, \"calculationTime\": 1086, \"queueingTime\": 13}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestdb4285900a117c954874542ae9a61712.json ================================================ { "request_id": "1_1y-3m-5y-10yIRVega(aggregation_level:Asset)-Price2020Jan14", "request_hash": "db4285900a117c954874542ae9a61712", "type": "MockCalc", "tests": [ "test_empty_calc_request" ], "mocked_data": "[[[[{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 18951.379622470093}], \"values\": [18951.379622470093], \"calculationTime\": 130, \"queueingTime\": -375}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 9546.483379703379}], \"values\": [9546.483379703379], \"calculationTime\": 130, \"queueingTime\": -375}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 0.0}], \"values\": [0.0], \"calculationTime\": 130, \"queueingTime\": -375}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 0.0}], \"values\": [0.0], \"calculationTime\": 130, \"queueingTime\": -375}]], [[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 1159341.7952729776, \"children\": {}, \"calculationTime\": 24, \"queueingTime\": -375}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 544183.8212896035, \"children\": {}, \"calculationTime\": 24, \"queueingTime\": -375}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 9321685.065826869, \"children\": {}, \"calculationTime\": 24, \"queueingTime\": -375}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 19280407.561757438, \"children\": {}, \"calculationTime\": 24, \"queueingTime\": -375}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestdba69f5aa7819199c2345b2e872bf817.json ================================================ { "request_id": "1_5yIRDelta(currency:NOK)-Pricetoday", "request_hash": "dba69f5aa7819199c2345b2e872bf817", "type": "MockCalc", "tests": [ "test_finite_difference_params" ], "mocked_data": "[[[[{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -271.96120791625975}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": 6.4697265625e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC22\", \"quoteStyle\": \"\", \"value\": -877.4708352661133}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"DEC23\", \"quoteStyle\": \"\", \"value\": -493.7374934265137}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN22\", \"quoteStyle\": \"\", \"value\": -930.914639251709}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"JUN23\", \"quoteStyle\": \"\", \"value\": -490.57966947021487}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR23\", \"quoteStyle\": \"\", \"value\": -747.5386889587403}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"MAR24\", \"quoteStyle\": \"\", \"value\": -302.8653449707031}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP22\", \"quoteStyle\": \"\", \"value\": -979.4686239318848}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"FRA\", \"point\": \"SEP23\", \"quoteStyle\": \"\", \"value\": -451.92549525146484}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 1.77001953125e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 1.5765380859375002e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": -6.591796875e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": -7.403564453125e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": -9.942626953125001e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -8.770751953125e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -5237.040346295166}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": -8.477783203125e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": -7166.424609942627}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": -1.77001953125e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 489584.12390561524}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"60Y\", \"quoteStyle\": \"\", \"value\": 6.8115234375e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 1.1888234130859376}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"70Y\", \"quoteStyle\": \"\", \"value\": -1.568603515625e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": -0.37898936157226565}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"80Y\", \"quoteStyle\": \"\", \"value\": -1.085205078125e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 0.06347343139648438}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"EUR\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": -0.00021283569335937502}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -25.110150103759768}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -25.155359692382813}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"FRA\", \"point\": \"DEC22\", \"quoteStyle\": \"\", \"value\": -5.2496337890625005e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"FRA\", \"point\": \"DEC23\", \"quoteStyle\": \"\", \"value\": -0.001783465576171875}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"FRA\", \"point\": \"JUN22\", \"quoteStyle\": \"\", \"value\": -0.0017071594238281251}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"FRA\", \"point\": \"JUN23\", \"quoteStyle\": \"\", \"value\": 0.0031136230468750002}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"FRA\", \"point\": \"MAR23\", \"quoteStyle\": \"\", \"value\": -0.00437685546875}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"FRA\", \"point\": \"MAR24\", \"quoteStyle\": \"\", \"value\": 9.1552734375e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"FRA\", \"point\": \"SEP22\", \"quoteStyle\": \"\", \"value\": 0.001523443603515625}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"FRA\", \"point\": \"SEP23\", \"quoteStyle\": \"\", \"value\": 0.001171160888671875}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": -1.220703125e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": -1.220703125e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 1.220703125e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"NOK\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": 6.103515625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": 1.220703125e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC22\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC23\", \"quoteStyle\": \"\", \"value\": -1.8310546875000002e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC24\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN22\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN23\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN24\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR23\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR24\", \"quoteStyle\": \"\", \"value\": -1.220703125e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR25\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP22\", \"quoteStyle\": \"\", \"value\": -1.8310546875000002e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP23\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP24\", \"quoteStyle\": \"\", \"value\": -6.103515625e-09}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": 2.44140625e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": 1.220703125e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [-271.96120791625975, 6.4697265625e-07, -877.4708352661133, -493.7374934265137, -930.914639251709, -490.57966947021487, -747.5386889587403, -302.8653449707031, -979.4686239318848, -451.92549525146484, 1.77001953125e-06, 1.5765380859375002e-05, -6.591796875e-06, -7.403564453125e-06, -9.942626953125001e-06, -8.770751953125e-06, -5237.040346295166, -8.477783203125e-06, -7166.424609942627, -1.77001953125e-06, 489584.12390561524, 6.8115234375e-06, 1.1888234130859376, -1.568603515625e-06, -0.37898936157226565, -1.085205078125e-05, 0.06347343139648438, -0.00021283569335937502, -25.110150103759768, -25.155359692382813, -5.2496337890625005e-05, -0.001783465576171875, -0.0017071594238281251, 0.0031136230468750002, -0.00437685546875, 9.1552734375e-08, 0.001523443603515625, 0.001171160888671875, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.220703125e-08, -1.220703125e-08, 0.0, 1.220703125e-08, 0.0, 0.0, 0.0, 6.103515625e-09, 1.220703125e-08, 0.0, -1.8310546875000002e-08, 0.0, 0.0, 0.0, 0.0, 0.0, -1.220703125e-08, 0.0, -1.8310546875000002e-08, 0.0, -6.103515625e-09, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.44140625e-08, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.220703125e-08, 0.0], \"calculationTime\": 15006, \"queueingTime\": -56}]], [[{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 8855060.12007939, \"children\": {}, \"calculationTime\": 95, \"queueingTime\": -56}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requeste0b867518c8f5765d221460aa462b9a1.json ================================================ { "request_id": "1_5y-10yPrice2020Jan14", "request_hash": "e0b867518c8f5765d221460aa462b9a1", "type": "MockCalc", "tests": [ "test_adding_risk_results", "test_one_portfolio", "test_resolve_to_frame" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 3290317.2121213092, \"children\": {}, \"calculationTime\": 51, \"queueingTime\": 13}], [{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 9587809.11540649, \"children\": {}, \"calculationTime\": 51, \"queueingTime\": 13}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requeste0d1a0fc88ab2155bafe544f85f751bd.json ================================================ { "request_id": "1_IRBasisSwapIRBasis(aggregation_level:Asset)2020Jan14", "request_hash": "e0d1a0fc88ab2155bafe544f85f751bd", "type": "MockCalc", "tests": [ "test_bucketed_risks" ], "mocked_data": "[[[[{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"value\": 405775.5447549485}, {\"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"value\": -84.10992125279488}, {\"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"value\": 4.255939660413599e-12}], \"values\": [405775.5447549485, -84.10992125279488, 4.255939660413599e-12], \"calculationTime\": 14483, \"queueingTime\": 27}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requeste114a9f5fd19a44b1ec99394536dd4e9.json ================================================ { "request_id": "1_5yIRDeltatoday", "request_hash": "e114a9f5fd19a44b1ec99394536dd4e9", "type": "MockCalc", "tests": [ "test_filter_risk", "test_finite_difference_params" ], "mocked_data": "[[[[{\"$type\": \"RiskVector\", \"asset\": [-2482.333218728638, 6.993103027343751e-06, -169.06867140808106, -89.84899127044679, -2915.775609814453, -83.80368190460206, -97.65080366516113, -10.453177301025391, -561.9370819931031, -81.71789613189698, 1.708984375e-07, -2.8381347656250003e-07, 5.569458007812501e-07, 1.8768310546875e-07, 2.62451171875e-07, -6.347656250000001e-07, -1009.8713364639283, -6.500244140625e-07, -1347.1169664611816, -9.368896484375e-07, 47991.02100903931, 6.256103515625e-07, 0.3986614242553711, -1.50299072265625e-06, -0.1087613510131836, -6.2103271484375e-07, 0.006536679077148438, -5.6976318359375e-06, -4.865887203979493, -14.745114610290528, -3.0517578125e-09, 1.52587890625e-09, 0.0, 1.52587890625e-09, 0.0, 1.52587890625e-09, -1.52587890625e-09, 1.52587890625e-09, -1.52587890625e-09, 0.0, 0.0, 1.52587890625e-09, -1.52587890625e-09, -1.52587890625e-09, 0.0, -3.0517578125e-09, 4.5776367187500004e-09, 3.0517578125e-09, 1.52587890625e-09, 1.52587890625e-09, 0.0, 1.52587890625e-09, 0.0, 1.52587890625e-09, 1.52587890625e-09, 0.0, -1.52587890625e-09, 0.0], \"points\": [{\"asset\": \"EUR\", \"class_\": \"CASH\", \"path\": \"\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -2482.333218728638}, {\"asset\": \"EUR\", \"class_\": \"CASH\", \"path\": \"\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 6.993103027343751e-06}, {\"asset\": \"EUR\", \"class_\": \"FRA\", \"path\": \"\", \"point\": \"DEC23\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -169.06867140808106}, {\"asset\": \"EUR\", \"class_\": \"FRA\", \"path\": \"\", \"point\": \"DEC24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -89.84899127044679}, {\"asset\": \"EUR\", \"class_\": \"FRA\", \"path\": \"\", \"point\": \"JUN23\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -2915.775609814453}, {\"asset\": \"EUR\", \"class_\": \"FRA\", \"path\": \"\", \"point\": \"JUN24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -83.80368190460206}, {\"asset\": \"EUR\", \"class_\": \"FRA\", \"path\": \"\", \"point\": \"MAR24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -97.65080366516113}, {\"asset\": \"EUR\", \"class_\": \"FRA\", \"path\": \"\", \"point\": \"MAR25\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -10.453177301025391}, {\"asset\": \"EUR\", \"class_\": \"FRA\", \"path\": \"\", \"point\": \"SEP23\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -561.9370819931031}, {\"asset\": \"EUR\", \"class_\": \"FRA\", \"path\": \"\", \"point\": \"SEP24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -81.71789613189698}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 1.708984375e-07}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -2.8381347656250003e-07}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 5.569458007812501e-07}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 1.8768310546875e-07}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 2.62451171875e-07}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -6.347656250000001e-07}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -1009.8713364639283}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -6.500244140625e-07}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -1347.1169664611816}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -9.368896484375e-07}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 47991.02100903931}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"60Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 6.256103515625e-07}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.3986614242553711}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"70Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -1.50299072265625e-06}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -0.1087613510131836}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"80Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -6.2103271484375e-07}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.006536679077148438}, {\"asset\": \"EUR\", \"class_\": \"SWAP\", \"path\": \"\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -5.6976318359375e-06}, {\"asset\": \"USD\", \"class_\": \"CASH OIS\", \"path\": \"\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -4.865887203979493}, {\"asset\": \"USD\", \"class_\": \"CASH OIS\", \"path\": \"\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -14.745114610290528}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"DEC23\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -3.0517578125e-09}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"DEC24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 1.52587890625e-09}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"DEC25\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"JUN23\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 1.52587890625e-09}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"JUN24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"JUN25\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 1.52587890625e-09}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"MAR24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -1.52587890625e-09}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"MAR25\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 1.52587890625e-09}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"MAR26\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -1.52587890625e-09}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"SEP23\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"SEP24\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"FRA OIS\", \"path\": \"\", \"point\": \"SEP25\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 1.52587890625e-09}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -1.52587890625e-09}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -1.52587890625e-09}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -3.0517578125e-09}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 4.5776367187500004e-09}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 3.0517578125e-09}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 1.52587890625e-09}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 1.52587890625e-09}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 1.52587890625e-09}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 1.52587890625e-09}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 1.52587890625e-09}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": -1.52587890625e-09}, {\"asset\": \"USD\", \"class_\": \"SWAP OIS\", \"path\": \"\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"type\": \"IR\", \"value\": 0.0}], \"unit\": {\"Basis Point\": -1.0, \"United States Dollar\": 1.0}}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requeste335e05ef6904adc1c56772fef5b92c1.json ================================================ { "request_id": "3_HedgeAction1_Priceable0ResolvedInstrumentValues2021Dec03_HedgeAction1_Priceable0ResolvedInstrumentValues2021Dec02_HedgeAction1_Priceable0ResolvedInstrumentValues2021Dec01", "request_hash": "e335e05ef6904adc1c56772fef5b92c1", "type": "MockCalc", "tests": [ "test_hedge_without_risk" ], "mocked_data": "[[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"FX\",\"buySell\":\"Buy\",\"forwardRate\":110.66383893,\"notionalAmount\":100000.0,\"notionalAmountInOtherCurrency\":-11066383.893,\"notionalCurrency\":\"USD\",\"pair\":\"USD JPY\",\"settlementDate\":\"2023-12-07\",\"type\":\"Forward\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"FX\",\"buySell\":\"Buy\",\"forwardRate\":110.60249056,\"notionalAmount\":100000.0,\"notionalAmountInOtherCurrency\":-11060249.056,\"notionalCurrency\":\"USD\",\"pair\":\"USD JPY\",\"settlementDate\":\"2023-12-06\",\"type\":\"Forward\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"FX\",\"buySell\":\"Buy\",\"forwardRate\":110.54466773,\"notionalAmount\":100000.0,\"notionalAmountInOtherCurrency\":-11054466.773,\"notionalCurrency\":\"USD\",\"pair\":\"USD JPY\",\"settlementDate\":\"2023-12-04\",\"type\":\"Forward\"}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requeste6809f076a1e32621a6124e017302856.json ================================================ { "request_id": "1_location_test_swapPrice2022Jul11", "request_hash": "e6809f076a1e32621a6124e017302856", "type": "MockCalc", "tests": [ "test_different_nested_locations" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 7.450580596923828e-09, \"children\": {}}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requeste6d7f90423e2ce1c3be3b513f67aed72.json ================================================ { "request_id": "5_QuantityScaledAction1_call_2021-12-10Price2021Dec10_QuantityScaledAction1_call_2021-12-09Price2021Dec09_QuantityScaledAction1_call_2021-12-08Price2021Dec08_QuantityScaledAction1_call_2021-12-07Price2021Dec07_QuantityScaledAction1_call_2021-12-06Price2021Dec06", "request_hash": "e6d7f90423e2ce1c3be3b513f67aed72", "type": "MockCalc", "tests": [ "test_add_scaled_action_nav" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":83.47791729987946}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":97.16417775446901}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":122.37019698602137}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":173.53895000226558}]]],[[[{\"$type\":\"Risk\",\"unit\":{\"European Euro\":1.0},\"val\":100.0}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestf097e66ab00cfd403830fe9718723934.json ================================================ { "request_id": "1_test_hedge_action_risk_trigger_2y_forward_2021-12-03_HedgeAction1_Priceable0FXDelta(aggregation_level:Type)-Price2021Dec03", "request_hash": "f097e66ab00cfd403830fe9718723934", "type": "MockCalc", "tests": [ "test_hedge_action_risk_trigger" ], "mocked_data": "[[[[{\"$type\":\"Risk\",\"children\":{},\"val\":884.9163677950855}]],[[{\"$type\":\"Risk\",\"children\":{},\"unit\":{\"Japanese Yen\":1.0},\"val\":-3.725290298461914e-9}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestf1a8064322aba584bed2936c03ef96e7.json ================================================ { "request_id": "1_5y-10yIRDelta(aggregation_level:Asset, currency:local)-Price2020Jan14", "request_hash": "f1a8064322aba584bed2936c03ef96e7", "type": "MockCalc", "tests": [ "test_adding_risk_results" ], "mocked_data": "[[[[{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR\", \"asset\": \"EUR\", \"value\": 48938.31462251072}], \"values\": [48938.31462251072], \"calculationTime\": 3210, \"queueingTime\": -3337}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR\", \"asset\": \"EUR\", \"value\": 93545.25523047562}], \"values\": [93545.25523047562], \"calculationTime\": 3210, \"queueingTime\": -3337}]], [[{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 3290317.2121213092, \"children\": {}, \"calculationTime\": 936, \"queueingTime\": -3337}], [{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 9587809.11540649, \"children\": {}, \"calculationTime\": 936, \"queueingTime\": -3337}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestf3acb9755ef5e82153b1ee6c24deff72.json ================================================ { "request_id": "5_QuantityScaledAction2_TransactionAggType.MAX_callResolvedInstrumentValues2021Dec10_QuantityScaledAction2_TransactionAggType.MAX_callResolvedInstrumentValues2021Dec09_QuantityScaledAction2_TransactionAggType.MAX_callResolvedInstrumentValues2021Dec08_QuantityScaledAction2_TransactionAggType.MAX_callResolvedInstrumentValues2021Dec07_QuantityScaledAction2_TransactionAggType.MAX_callResolvedInstrumentValues2021Dec06", "request_hash": "f3acb9755ef5e82153b1ee6c24deff72", "type": "MockCalc", "tests": [ "test_add_scaled_action_nav_with_transaction_costs" ], "mocked_data": "[[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-03-10\",\"expirySettleDate\":\"2022-03-14\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-14\",\"premiumSettlementDate\":\"2021-12-14\",\"settlementDate\":\"2021-12-14\",\"strikePrice\":4199.16,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-03-09\",\"expirySettleDate\":\"2022-03-11\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-13\",\"premiumSettlementDate\":\"2021-12-13\",\"settlementDate\":\"2021-12-13\",\"strikePrice\":4208.3,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-03-08\",\"expirySettleDate\":\"2022-03-10\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-10\",\"premiumSettlementDate\":\"2021-12-10\",\"settlementDate\":\"2021-12-10\",\"strikePrice\":4233.09,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-03-07\",\"expirySettleDate\":\"2022-03-09\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-09\",\"premiumSettlementDate\":\"2021-12-09\",\"settlementDate\":\"2021-12-09\",\"strikePrice\":4276.2,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]],[[[{\"$type\":\"LegDefinition\",\"assetClass\":\"Equity\",\"buySell\":\"Buy\",\"expirationDate\":\"2022-03-07\",\"expirySettleDate\":\"2022-03-09\",\"expirySettlementDays\":\"2b\",\"methodOfSettlement\":\"Cash\",\"multiplier\":1.0,\"numberOfOptions\":1.0,\"optionStyle\":\"European\",\"optionType\":\"Call\",\"premium\":0.0,\"premiumCurrency\":\"EUR\",\"premiumPaymentDate\":\"2021-12-08\",\"premiumSettlementDate\":\"2021-12-08\",\"settlementDate\":\"2021-12-08\",\"strikePrice\":4137.11,\"tradeAs\":\"OTC\",\"type\":\"Option\",\"underlier\":\".STOXX50E\",\"underlierCurrency\":\"EUR\",\"underlierType\":\"RIC\",\"valuationTime\":\"MktClose\"}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestf48c727eb8f636d8d5aec677e3379e98.json ================================================ { "request_id": "1_5yPrice-Price(value:MYR)today", "request_hash": "f48c727eb8f636d8d5aec677e3379e98", "type": "MockCalc", "tests": [ "test_currency_params" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 8855060.12007939, \"children\": {}, \"calculationTime\": 49, \"queueingTime\": 269}]], [[{\"$type\": \"Risk\", \"unit\": {\"Malaysian Ringgit\": 1.0}, \"val\": 40400773.28824528, \"children\": {}, \"calculationTime\": 49, \"queueingTime\": 269}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestf58b3a2ad19557b466036841eb110ff0.json ================================================ { "request_id": "1_5y-10yDollarPrice2020Jan14", "request_hash": "f58b3a2ad19557b466036841eb110ff0", "type": "MockCalc", "tests": [ "test_nested_portfolio" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 3660454.298656369, \"children\": {}, \"calculationTime\": 119, \"queueingTime\": 149}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 15779322.606637968, \"children\": {}, \"calculationTime\": 124, \"queueingTime\": 149}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestf6793717b5017cc15a746e90ce585cc3.json ================================================ { "request_id": "2_1y-3m-5y-10yIRVega(aggregation_level:Asset, currency:local)-Price2020Jan15_1y-3m-5y-10yIRVega(aggregation_level:Asset, currency:local)-Price2020Jan14", "request_hash": "f6793717b5017cc15a746e90ce585cc3", "type": "MockCalc", "tests": [ "test_empty_calc_request" ], "mocked_data": "[[[[{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 18942.916708309745}], \"values\": [18942.916708309745], \"calculationTime\": 92, \"queueingTime\": 84}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 9546.7347591115}], \"values\": [9546.7347591115], \"calculationTime\": 92, \"queueingTime\": 84}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 0.0}], \"values\": [0.0], \"calculationTime\": 92, \"queueingTime\": 84}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 0.0}], \"values\": [0.0], \"calculationTime\": 92, \"queueingTime\": 84}]], [[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 1162329.9377910192, \"children\": {}, \"calculationTime\": 19, \"queueingTime\": 84}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 542174.0278393007, \"children\": {}, \"calculationTime\": 19, \"queueingTime\": 84}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 9253581.340032147, \"children\": {}, \"calculationTime\": 19, \"queueingTime\": 84}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 19104807.27238474, \"children\": {}, \"calculationTime\": 19, \"queueingTime\": 84}]]], [[[{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 18951.379622470093}], \"values\": [18951.379622470093], \"calculationTime\": 119, \"queueingTime\": -223}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 9546.483379703379}], \"values\": [9546.483379703379], \"calculationTime\": 119, \"queueingTime\": -223}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 0.0}], \"values\": [0.0], \"calculationTime\": 119, \"queueingTime\": -223}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"value\": 0.0}], \"values\": [0.0], \"calculationTime\": 119, \"queueingTime\": -223}]], [[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 1159341.7952729776, \"children\": {}, \"calculationTime\": 19, \"queueingTime\": -223}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 544183.8212896035, \"children\": {}, \"calculationTime\": 19, \"queueingTime\": -223}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 9321685.065826869, \"children\": {}, \"calculationTime\": 19, \"queueingTime\": -223}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 19280407.561757438, \"children\": {}, \"calculationTime\": 19, \"queueingTime\": -223}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestfa39f59c007048d21f5fcebb516a55c3.json ================================================ { "request_id": "1_1y-3m-6m-5y-10yIRVega-Price2020Jan14", "request_hash": "fa39f59c007048d21f5fcebb516a55c3", "type": "MockCalc", "tests": [ "test_empty_calc_request" ], "mocked_data": "[[[[{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;1Y\", \"quoteStyle\": \"\", \"value\": 18951.379622470093}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;3M\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;6M\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [18951.379622470093, 0.0, 0.0], \"calculationTime\": 1095, \"queueingTime\": -17439}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;3M\", \"quoteStyle\": \"\", \"value\": 9546.483379703379}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;6M\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [0.0, 9546.483379703379, 0.0], \"calculationTime\": 1095, \"queueingTime\": -17439}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;3M\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;6M\", \"quoteStyle\": \"\", \"value\": 13453.388908411027}], \"asset\": [0.0, 0.0, 13453.388908411027], \"calculationTime\": 1095, \"queueingTime\": -17439}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;3M\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;6M\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [0.0, 0.0, 0.0], \"calculationTime\": 1095, \"queueingTime\": -17439}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;1Y\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;3M\", \"quoteStyle\": \"\", \"value\": 0.0}, {\"path\": \"\", \"type\": \"IR VOL\", \"asset\": \"USD-LIBOR-BBA\", \"class_\": \"SWAPTION\", \"point\": \"5Y;6M\", \"quoteStyle\": \"\", \"value\": 0.0}], \"asset\": [0.0, 0.0, 0.0], \"calculationTime\": 1095, \"queueingTime\": -17439}]], [[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 1159341.7952729776, \"children\": {}, \"calculationTime\": 736, \"queueingTime\": -17439}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 544183.8212896035, \"children\": {}, \"calculationTime\": 736, \"queueingTime\": -17439}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 790300.4555458546, \"children\": {}, \"calculationTime\": 736, \"queueingTime\": -17439}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 9321685.065826869, \"children\": {}, \"calculationTime\": 736, \"queueingTime\": -17439}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 19280407.561757438, \"children\": {}, \"calculationTime\": 736, \"queueingTime\": -17439}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestfaea73719549e6087d5398b07d322976.json ================================================ { "request_id": "1_IRBasisSwap-5y-10yIRBasis(aggregation_level:Asset)-Price2020Jan14", "request_hash": "faea73719549e6087d5398b07d322976", "type": "MockCalc", "tests": [ "test_diff_types_risk_measures" ], "mocked_data": "[[[[{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"value\": 405775.5447549485}, {\"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"value\": -84.10992125279488}, {\"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"value\": 4.255939660413599e-12}], \"values\": [405775.5447549485, -84.10992125279488, 4.255939660413599e-12], \"calculationTime\": 4014, \"queueingTime\": -6948}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"value\": -59818.32957608109}, {\"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"value\": -80227.02056628113}, {\"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"value\": 4.060833167266846}], \"values\": [-59818.32957608109, -80227.02056628113, 4.060833167266846], \"calculationTime\": 1134, \"queueingTime\": -6948}], [{\"$type\": \"RiskByClass\", \"classes\": [{\"type\": \"IR BASIS\", \"asset\": \"GBP OIS/GBP-3M\", \"value\": -113025.88950285035}, {\"type\": \"IR BASIS\", \"asset\": \"GBP-3M/GBP-6M\", \"value\": -133582.29018813782}, {\"type\": \"IR BASIS\", \"asset\": \"USD OIS/USD-3M\", \"value\": 8.726409149169923}], \"values\": [-113025.88950285035, -133582.29018813782, 8.726409149169923], \"calculationTime\": 1134, \"queueingTime\": -6948}]], [[{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 0.0, \"children\": {}, \"calculationTime\": 2816, \"queueingTime\": -6948}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 5644562.7450038865, \"children\": {}, \"calculationTime\": 40, \"queueingTime\": -6948}], [{\"$type\": \"Risk\", \"unit\": {\"British Pound Sterling\": 1.0}, \"val\": 12129718.693539664, \"children\": {}, \"calculationTime\": 40, \"queueingTime\": -6948}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestfb80e8b3c71c17a77857b7df855d5b7c.json ================================================ { "request_id": "1_5yPricetoday", "request_hash": "fb80e8b3c71c17a77857b7df855d5b7c", "type": "MockCalc", "tests": [ "test_currency_params" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"European Euro\": 1.0}, \"val\": 8855060.12007939, \"children\": {}, \"calculationTime\": 99, \"queueingTime\": 42}]]]]" } ================================================ FILE: gs_quant/test/calc_cache/requestfce828ba2ccac19204c1194026c7390c.json ================================================ { "request_id": "1_swap_10y@10bp-swap_10y@20bp-swap_10y@30bpDollarPrice-IRDelta2020Oct15", "request_hash": "fce828ba2ccac19204c1194026c7390c", "type": "MockCalc", "tests": [ "test_portfolio" ], "mocked_data": "[[[[{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 6407334.04098904, \"children\": {}, \"calculationTime\": 1133, \"queueingTime\": -10164}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 5423406.8590423055, \"children\": {}, \"calculationTime\": 1133, \"queueingTime\": -10164}], [{\"$type\": \"Risk\", \"unit\": {\"United States Dollar\": 1.0}, \"val\": 4439479.67709557, \"children\": {}, \"calculationTime\": 1133, \"queueingTime\": -10164}]], [[{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -1639.6784271701813}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -1.7796128746032716}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -980.655580191803}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -21.586036534500124}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -28.878579223632816}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN22\", \"quoteStyle\": \"\", \"value\": -12.690484024810791}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -32.11917093200684}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR22\", \"quoteStyle\": \"\", \"value\": -15.622958218765259}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -23.513429999542236}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP22\", \"quoteStyle\": \"\", \"value\": -10.634451773071289}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 97937.50434597474}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 10.813013504791261}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": -1.5068936244964601}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.10787035026550293}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 0.0009228092193603516}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -0.00016845245361328125}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": 1.4420700073242187e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -182.04729224357607}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 2.5634765625000004e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": -3.8909912109375e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": -258.6725209606171}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 1.25885009765625e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -318.03045326957704}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -381.83558287734985}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": -445.12583647575383}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": -502.85174584388733}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": -612.4792485343934}], \"asset\": [-1639.6784271701813, -1.7796128746032716, -980.655580191803, -21.586036534500124, -28.878579223632816, -12.690484024810791, -32.11917093200684, -15.622958218765259, -23.513429999542236, -10.634451773071289, 97937.50434597474, 10.813013504791261, -1.5068936244964601, 0.10787035026550293, 0.0009228092193603516, -0.00016845245361328125, 1.4420700073242187e-05, -182.04729224357607, 2.5634765625000004e-07, -3.8909912109375e-08, -258.6725209606171, 1.25885009765625e-08, -318.03045326957704, -381.83558287734985, -445.12583647575383, -502.85174584388733, -612.4792485343934], \"calculationTime\": 2170, \"queueingTime\": -10164}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -1635.2538244144441}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -1.5062988487243654}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -975.0654988819123}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -18.27122632446289}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -24.443906997680664}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN22\", \"quoteStyle\": \"\", \"value\": -10.741698770523072}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -27.186864908981324}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR22\", \"quoteStyle\": \"\", \"value\": -13.22385443878174}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -19.90264452056885}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP22\", \"quoteStyle\": \"\", \"value\": -9.001396529006959}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 98007.49496660577}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 9.152537252044679}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": -1.2754898479461672}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.09130501899719239}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 0.0007811740875244141}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -0.00014251861572265624}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": 1.2205123901367188e-05}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -154.09162789268495}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 2.044677734375e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": -3.39508056640625e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": -218.95008361053468}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 1.1825561523437501e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -269.19285474510195}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -323.1999001693726}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": -376.7711307369232}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": -425.6325191196442}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": -518.4252589351654}], \"asset\": [-1635.2538244144441, -1.5062988487243654, -975.0654988819123, -18.27122632446289, -24.443906997680664, -10.741698770523072, -27.186864908981324, -13.22385443878174, -19.90264452056885, -9.001396529006959, 98007.49496660577, 9.152537252044679, -1.2754898479461672, 0.09130501899719239, 0.0007811740875244141, -0.00014251861572265624, 1.2205123901367188e-05, -154.09162789268495, 2.044677734375e-07, -3.39508056640625e-08, -218.95008361053468, 1.1825561523437501e-08, -269.19285474510195, -323.1999001693726, -376.7711307369232, -425.6325191196442, -518.4252589351654], \"calculationTime\": 2170, \"queueingTime\": -10164}], [{\"$type\": \"RiskVector\", \"points\": [{\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"CASH STUB\", \"quoteStyle\": \"\", \"value\": -1630.8292216583252}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"CASH\", \"point\": \"O/N\", \"quoteStyle\": \"\", \"value\": -1.2329848213195802}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC20\", \"quoteStyle\": \"\", \"value\": -969.4754175727845}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"DEC21\", \"quoteStyle\": \"\", \"value\": -14.956416115188599}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN21\", \"quoteStyle\": \"\", \"value\": -20.009234772109988}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"JUN22\", \"quoteStyle\": \"\", \"value\": -8.792913516998292}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR21\", \"quoteStyle\": \"\", \"value\": -22.254558886337282}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"MAR22\", \"quoteStyle\": \"\", \"value\": -10.824750659942627}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP21\", \"quoteStyle\": \"\", \"value\": -16.29185904083252}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"FRA\", \"point\": \"SEP22\", \"quoteStyle\": \"\", \"value\": -7.368341283798218}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"10Y\", \"quoteStyle\": \"\", \"value\": 98077.48558723679}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"12Y\", \"quoteStyle\": \"\", \"value\": 7.492060999679566}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"15Y\", \"quoteStyle\": \"\", \"value\": -1.0440860725402832}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"20Y\", \"quoteStyle\": \"\", \"value\": 0.07473968963623047}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"25Y\", \"quoteStyle\": \"\", \"value\": 0.0006395401000976563}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"30Y\", \"quoteStyle\": \"\", \"value\": -0.00011658401489257813}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"35Y\", \"quoteStyle\": \"\", \"value\": 9.988784790039063e-06}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"3Y\", \"quoteStyle\": \"\", \"value\": -126.13596354255677}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"40Y\", \"quoteStyle\": \"\", \"value\": 1.514434814453125e-07}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"45Y\", \"quoteStyle\": \"\", \"value\": -2.8228759765625e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"4Y\", \"quoteStyle\": \"\", \"value\": -179.2276462600708}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"50Y\", \"quoteStyle\": \"\", \"value\": 1.25885009765625e-08}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"5Y\", \"quoteStyle\": \"\", \"value\": -220.35525622100832}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"6Y\", \"quoteStyle\": \"\", \"value\": -264.5642174613953}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"7Y\", \"quoteStyle\": \"\", \"value\": -308.4164249977112}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"8Y\", \"quoteStyle\": \"\", \"value\": -348.41329239501954}, {\"path\": \"\", \"type\": \"IR\", \"asset\": \"USD\", \"class_\": \"SWAP\", \"point\": \"9Y\", \"quoteStyle\": \"\", \"value\": -424.371269336319}], \"asset\": [-1630.8292216583252, -1.2329848213195802, -969.4754175727845, -14.956416115188599, -20.009234772109988, -8.792913516998292, -22.254558886337282, -10.824750659942627, -16.29185904083252, -7.368341283798218, 98077.48558723679, 7.492060999679566, -1.0440860725402832, 0.07473968963623047, 0.0006395401000976563, -0.00011658401489257813, 9.988784790039063e-06, -126.13596354255677, 1.514434814453125e-07, -2.8228759765625e-08, -179.2276462600708, 1.25885009765625e-08, -220.35525622100832, -264.5642174613953, -308.4164249977112, -348.41329239501954, -424.371269336319], \"calculationTime\": 2170, \"queueingTime\": -10164}]]]]" } ================================================ FILE: gs_quant/test/config/test_options.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import gs_quant import pytest from gs_quant.config import DisplayOptions from gs_quant.markets.portfolio import Portfolio from gs_quant.risk import IRVega, AggregationLevel from gs_quant.instrument import IRSwap from gs_quant.test.utils.mock_calc import MockCalc def test_display_options(mocker): with MockCalc(mocker): res = Portfolio(IRSwap(name='swap'), name='port').calc(IRVega(aggregation_level=AggregationLevel.Asset)) assert gs_quant.config.display_options.show_na is False gs_quant.config.display_options.show_na = True assert gs_quant.config.display_options.show_na is True o = DisplayOptions(show_na=True) o2 = DisplayOptions(show_na=False) with pytest.raises(TypeError): _ = res.to_frame(display_options=True) df1 = res.to_frame(display_options=o) df2 = res.to_frame(display_options=o2) assert df1 is not None assert df2 is None # reset to make sure test works gs_quant.config.display_options = DisplayOptions() ================================================ FILE: gs_quant/test/data/__init__.py ================================================ ================================================ FILE: gs_quant/test/data/test_data_caching.py ================================================ """ Copyright 2023 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from unittest.mock import patch from pandas.testing import assert_frame_equal from gs_quant.api.api_cache import InMemoryApiRequestCache, CacheEvent from gs_quant.api.gs.data import GsDataApi, QueryType from gs_quant.data import Dataset, DataContext class _FakeSyncAPI: def __init__(self, session_cls): self._session_cls = session_cls def get(self, url, **kwargs): return self._session_cls._get(url, **kwargs) def post(self, url, **kwargs): return self._session_cls._post(url, **kwargs) class NotExpectedToBeCalledSession: redirect_to_mds = True @classmethod def _get(cls, url, **kwargs): if url == "/data/datasets/FXSPOT_STANDARD": return {"id": "FXSPOT_STANDARD"} else: raise Exception("Not expecting to be called at this point") @classmethod def _post(cls, url, **kwargs): raise Exception("Not expecting to be called at this point") @property def sync(self): return _FakeSyncAPI(NotExpectedToBeCalledSession) class MarketDataErrorSession: redirect_to_mds = True @classmethod def _post(cls, url, **kwargs): return { "requestId": "890", "responses": [{"queryResponse": [{"errorMessages": ["Test Failure"]}]}], } @property def sync(self): return _FakeSyncAPI(MarketDataErrorSession) class FakeSession: redirect_to_mds = True @classmethod def _get(cls, url, **kwargs): if url == "/data/catalog/FXSPOT_STANDARD": return { "id": "FXSPOT_STANDARD", "fields": { "date": {"type": "string", "format": "date"}, "assetId": {"type": "string"}, "spot": { "type": "number", }, "updateTime": {"type": "string", "format": "date-time"}, }, } elif url == "/data/datasets/FXSPOT_STANDARD": return {"id": "FXSPOT_STANDARD"} else: raise Exception("Need to mock _get request here") @classmethod def _post(cls, url, **kwargs): if url == "/data/FXSPOT_STANDARD/last/query": return { "requestId": "1234", "data": [ { "date": "2023-10-25", "assetId": "MATGYV0J9MPX534Z", "bbid": "USDJPY", "spot": 150.123, "updateTime": "2023-10-25T21:53:56Z", } ], } elif url == "/data/FXSPOT_STANDARD/query": return { "requestId": "5678", "data": [ { "date": "2023-10-26", "assetId": "MATGYV0J9MPX534Z", "bbid": "USDJPY", "spot": 152.234, "updateTime": "2023-10-26T21:53:56Z", } ], } elif url == "/data/measures": return { "requestId": "890", "responses": [ { "queryResponse": [ { "measure": "Curve", "dataSetIds": ["DATASET_FOO"], "entityTypes": ["ASSET"], "response": { "data": [ { "date": "2023-04-11", "assetId": "MATGYV0J9MPX534Z", "pricingLocation": "HKG", "name": "USDJPY", "spot": 133.0, }, { "date": "2023-04-11", "assetId": "MATGYV0J9MPX534Z", "pricingLocation": "LDN", "name": "USDJPY", "spot": 134.0, }, { "date": "2023-04-11", "assetId": "MATGYV0J9MPX534Z", "pricingLocation": "NYC", "name": "USDJPY", "spot": 136.0, }, ] }, } ] } ], } @property def sync(self): return _FakeSyncAPI(FakeSession) class TestDataApiCache: def setup_method(self, test_method): self.cache = InMemoryApiRequestCache() GsDataApi.set_api_request_cache(self.cache) def teardown_method(self, test_method): GsDataApi.set_api_request_cache(None) def test_last_data(self): ds = Dataset("FXSPOT_STANDARD") with patch.object(GsDataApi, "get_session", return_value=FakeSession()): df = ds.get_data_last(as_of=dt.date(2023, 10, 25), bbid="USDJPY") with patch.object(GsDataApi, "get_session", return_value=NotExpectedToBeCalledSession()): df2 = ds.get_data_last(dt.date(2023, 10, 25), bbid="USDJPY") assert not df.empty assert_frame_equal(df, df2) cache_events = self.cache.get_events() assert len(cache_events) == 4 assert cache_events[0][0] == CacheEvent.PUT assert cache_events[1][0] == CacheEvent.PUT assert cache_events[2][0] == CacheEvent.GET assert cache_events[3][0] == CacheEvent.GET def test_query_data(self): ds = Dataset("FXSPOT_STANDARD") with patch.object(GsDataApi, "get_session", return_value=FakeSession()): df = ds.get_data(dt.date(2023, 10, 26), dt.date(2023, 10, 26), bbid="USDJPY") with patch.object(GsDataApi, "get_session", return_value=NotExpectedToBeCalledSession()): df2 = ds.get_data(dt.date(2023, 10, 26), dt.date(2023, 10, 26), bbid="USDJPY") assert_frame_equal(df, df2) cache_events = self.cache.get_events() assert len(cache_events) == 4 assert cache_events[0][0] == CacheEvent.PUT assert cache_events[1][0] == CacheEvent.PUT assert cache_events[2][0] == CacheEvent.GET assert cache_events[3][0] == CacheEvent.GET def test_market_data(self): asset_id = "MATGYV0J9MPX534Z" with DataContext(dt.date(2023, 4, 11), dt.date(2023, 4, 11)): q = GsDataApi.build_market_data_query([asset_id], QueryType.SPOT) with patch.object(GsDataApi, "get_session", return_value=MarketDataErrorSession()): try: df = GsDataApi.get_market_data(q) except Exception: pass cache_events = self.cache.get_events() assert len(cache_events) == 0 with patch.object(GsDataApi, "get_session", return_value=FakeSession()): df = GsDataApi.get_market_data(q) with patch.object(GsDataApi, "get_session", return_value=NotExpectedToBeCalledSession()): df2 = GsDataApi.get_market_data(q) assert_frame_equal(df, df2) cache_events = self.cache.get_events() assert len(cache_events) == 2 assert cache_events[0][0] == CacheEvent.PUT assert cache_events[1][0] == CacheEvent.GET ================================================ FILE: gs_quant/test/data/test_data_coordinate.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import pytest from gs_quant.data import Dataset, DataCoordinate, DataMeasure, DataDimension def test_immutability(): dimensions = {DataDimension.TENOR: '1m', DataDimension.STRIKE_REFERENCE: 'Delta', DataDimension.RELATIVE_STRIKE: 50} coord1 = DataCoordinate(Dataset.GS.EDRVOL_PERCENT_STANDARD, DataMeasure.IMPLIED_VOLATILITY, dimensions) coord2 = DataCoordinate(Dataset.GS.EDRVOL_PERCENT_STANDARD, DataMeasure.IMPLIED_VOLATILITY, dimensions) assert id(coord1) != id(coord2) assert coord1 == coord2 with pytest.raises(AttributeError): coord1.dataset_id = 'test' with pytest.raises(AttributeError): coord1.dimensions = {} with pytest.raises(AttributeError): coord1.measure = 'test' dimensions[DataDimension.TENOR] = '2m' coord3 = DataCoordinate(Dataset.GS.EDRVOL_PERCENT_STANDARD, DataMeasure.IMPLIED_VOLATILITY, dimensions) assert id(coord1) != id(coord2) assert coord1 != coord3 def test_equals(): dimensions_a = { DataDimension.TENOR: '1m', DataDimension.STRIKE_REFERENCE: 'spot', DataDimension.RELATIVE_STRIKE: 0.8, } # Same dimensions but different order dimensions_b = { DataDimension.RELATIVE_STRIKE: 0.8, DataDimension.STRIKE_REFERENCE: 'spot', DataDimension.TENOR: '1m', } coord_a = DataCoordinate( dataset_id='EDRVOL_PERCENT_STOCK_STANDARD', measure=DataMeasure.IMPLIED_VOLATILITY, dimensions=dimensions_a ) coord_b = DataCoordinate( dataset_id='EDRVOL_PERCENT_STOCK_STANDARD', measure=DataMeasure.IMPLIED_VOLATILITY, dimensions=dimensions_b ) assert coord_a == coord_b assert coord_a == coord_b assert hash(coord_a) == hash(coord_b) def test_equals_measure_str(): coord_a = DataCoordinate(dataset_id='EDRVOL_PERCENT_STOCK_STANDARD', measure=DataMeasure.IMPLIED_VOLATILITY) coord_b = DataCoordinate(dataset_id='EDRVOL_PERCENT_STOCK_STANDARD', measure='impliedVolatility') assert coord_a == coord_b if __name__ == "__main__": pytest.main(args=["test_data_coordinate.py"]) ================================================ FILE: gs_quant/test/data/test_dataset.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import copy import datetime as dt import numpy as np import pandas as pd import pytest from gs_quant.api.gs.data import GsDataApi from gs_quant.data import Dataset from gs_quant.session import GsSession, Environment from gs_quant.target.data import Format, DataQuery from gs_quant.target.data import DataSetEntity from gs_quant.target.data import DataSetParameters from gs_quant.target.data import DataSetDimensions test_types = { 'date': 'date', 'assetId': 'string', 'askPrice': 'number', 'adjustedAskPrice': 'number', 'bidPrice': 'number', 'adjustedBidPrice': 'number', 'tradePrice': 'number', 'adjustedTradePrice': 'number', 'openPrice': 'number', 'adjustedOpenPrice': 'number', 'highPrice': 'number', 'lowPrice': 'number', 'adjustedHighPrice': 'number', 'adjustedLowPrice': 'number', 'updateTime': 'date-time', } test_data = [ { 'date': dt.date(2019, 1, 2), 'assetId': 'MA4B66MW5E27U8P32SB', 'askPrice': 2529, 'adjustedAskPrice': 2529, 'bidPrice': 2442.55, 'adjustedBidPrice': 2442.55, 'tradePrice': 2510.03, 'adjustedTradePrice': 2510.03, 'openPrice': 2476.96, 'adjustedOpenPrice': 2476.96, 'highPrice': 2519.49, 'lowPrice': 2467.47, 'adjustedHighPrice': 2519.49, 'adjustedLowPrice': 2467.47, 'updateTime': dt.datetime.strptime('2019-01-03T00:53:00Z', '%Y-%m-%dT%H:%M:%SZ'), }, { 'date': dt.date(2019, 1, 3), 'assetId': 'MA4B66MW5E27U8P32SB', 'askPrice': 2502.34, 'adjustedAskPrice': 2502.34, 'bidPrice': 2418.09, 'adjustedBidPrice': 2418.09, 'tradePrice': 2447.89, 'adjustedTradePrice': 2447.89, 'openPrice': 2491.92, 'adjustedOpenPrice': 2491.92, 'highPrice': 2493.14, 'lowPrice': 2443.96, 'adjustedHighPrice': 2493.14, 'adjustedLowPrice': 2443.96, 'updateTime': dt.datetime.strptime('2019-01-04T00:14:00Z', '%Y-%m-%dT%H:%M:%SZ'), }, { 'date': dt.date(2019, 1, 4), 'assetId': 'MA4B66MW5E27U8P32SB', 'askPrice': 2566.52, 'adjustedAskPrice': 2566.52, 'bidPrice': 2487.8, 'adjustedBidPrice': 2487.8, 'tradePrice': 2531.94, 'adjustedTradePrice': 2531.94, 'openPrice': 2474.33, 'adjustedOpenPrice': 2474.33, 'highPrice': 2538.07, 'lowPrice': 2474.33, 'adjustedHighPrice': 2538.07, 'adjustedLowPrice': 2474.33, 'updateTime': dt.datetime.strptime('2019-01-08T00:31:00Z', '%Y-%m-%dT%H:%M:%SZ'), }, { 'date': dt.date(2019, 1, 7), 'assetId': 'MA4B66MW5E27U8P32SB', 'askPrice': 2591.75, 'adjustedAskPrice': 2591.75, 'bidPrice': 2509.77, 'adjustedBidPrice': 2509.77, 'tradePrice': 2549.69, 'adjustedTradePrice': 2549.69, 'openPrice': 2535.61, 'adjustedOpenPrice': 2535.61, 'highPrice': 2566.16, 'lowPrice': 2524.56, 'adjustedHighPrice': 2566.16, 'adjustedLowPrice': 2524.56, 'updateTime': dt.datetime.strptime('2019-01-08T00:31:00Z', '%Y-%m-%dT%H:%M:%SZ'), }, { 'date': dt.date(2019, 1, 8), 'assetId': 'MA4B66MW5E27U8P32SB', 'askPrice': 2610.52, 'adjustedAskPrice': 2610.52, 'bidPrice': 2531.15, 'adjustedBidPrice': 2531.15, 'tradePrice': 2574.41, 'adjustedTradePrice': 2574.41, 'openPrice': 2568.11, 'adjustedOpenPrice': 2568.11, 'highPrice': 2579.82, 'lowPrice': 2547.56, 'adjustedHighPrice': 2579.82, 'adjustedLowPrice': 2547.56, 'updateTime': dt.datetime.strptime('2019-01-09T00:50:00Z', '%Y-%m-%dT%H:%M:%SZ'), }, { 'date': dt.date(2019, 1, 9), 'assetId': 'MA4B66MW5E27U8P32SB', 'askPrice': 2623.09, 'adjustedAskPrice': 2623.09, 'bidPrice': 2537.19, 'adjustedBidPrice': 2537.19, 'tradePrice': 2584.96, 'adjustedTradePrice': 2584.96, 'openPrice': 2580, 'adjustedOpenPrice': 2580, 'highPrice': 2595.32, 'lowPrice': 2568.89, 'adjustedHighPrice': 2595.32, 'adjustedLowPrice': 2568.89, 'updateTime': dt.datetime.strptime('2019-01-10T00:44:00Z', '%Y-%m-%dT%H:%M:%SZ'), }, ] tr_types = { 'time': 'date-time', 'assetId': 'string', 'askPrice': 'number', 'adjustedAskPrice': 'number', 'bidPrice': 'number', 'adjustedBidPrice': 'number', 'tradePrice': 'number', 'adjustedTradePrice': 'number', 'openPrice': 'number', 'adjustedOpenPrice': 'number', 'highPrice': 'number', 'adjustedLowPrice': 'number', 'lowPrice': 'number', 'adjustedHighPrice': 'number', } tr_data = [ { 'time': dt.datetime(2023, 5, 31, 14), 'assetId': 'MA4B66MW5E27U8P32SB', 'askPrice': 2529, 'adjustedAskPrice': 2529, 'bidPrice': 2442.55, 'adjustedBidPrice': 2442.55, 'tradePrice': 2510.03, 'adjustedTradePrice': 2510.03, 'openPrice': 2476.96, 'adjustedOpenPrice': 2476.96, 'highPrice': 2519.49, 'lowPrice': 2467.47, 'adjustedHighPrice': 2519.49, 'adjustedLowPrice': 2467.47, }, { 'time': dt.datetime(2023, 5, 31, 15), 'assetId': 'MA4B66MW5E27U8P32SB', 'askPrice': 2502.34, 'adjustedAskPrice': 2502.34, 'bidPrice': 2418.09, 'adjustedBidPrice': 2418.09, 'tradePrice': 2447.89, 'adjustedTradePrice': 2447.89, 'openPrice': 2491.92, 'adjustedOpenPrice': 2491.92, 'highPrice': 2493.14, 'lowPrice': 2443.96, 'adjustedHighPrice': 2493.14, 'adjustedLowPrice': 2443.96, }, {'time': dt.datetime(2023, 5, 31, 16), 'assetId': 'MA4B66MW5E27U8P32SB'}, { 'time': dt.datetime(2023, 5, 31, 17), 'assetId': 'MA4B66MW5E27U8P32SB', 'askPrice': 2566.52, 'adjustedAskPrice': 2566.52, 'bidPrice': 2487.8, 'adjustedBidPrice': 2487.8, 'tradePrice': 2531.94, 'adjustedTradePrice': 2531.94, 'openPrice': 2474.33, 'adjustedOpenPrice': 2474.33, 'highPrice': 2538.07, 'lowPrice': 2474.33, 'adjustedHighPrice': 2538.07, 'adjustedLowPrice': 2474.33, }, ] test_coverage_data = {'results': [{'gsid': 'gsid1'}]} def test_query_data(mocker): mock = mocker.patch("gs_quant.api.gs.data.GsDataApi.query_data", return_value=test_data) mocker.patch("gs_quant.api.gs.data.GsDataApi.get_types", return_value=test_types) dataset = Dataset(Dataset.TR.TREOD) data = dataset.get_data(dt.date(2019, 1, 2), dt.date(2019, 1, 9), assetId='MA4B66MW5E27U8P32SB') assert data.equals(GsDataApi.construct_dataframe_with_types(str(Dataset.TR.TREOD), test_data)) assert mock.call_count == 1 query = mock.call_args[0][0] assert type(query) is DataQuery assert query.empty_intervals is None def test_query_data_intervals(mocker): mock = mocker.patch("gs_quant.api.gs.data.GsDataApi.query_data", return_value=tr_data) mocker.patch("gs_quant.api.gs.data.GsDataApi.get_types", return_value=tr_types) dataset = Dataset(Dataset.TR.TREOD) data = dataset.get_data( dt.datetime(2023, 5, 31, 13), dt.datetime(2023, 5, 31, 17), assetId='MA4B66MW5E27U8P32SB', intervals=4, empty_intervals=True, ) assert data.equals(GsDataApi.construct_dataframe_with_types(str(Dataset.TR.TR), tr_data)) assert mock.call_count == 1 query = mock.call_args[0][0] assert type(query) is DataQuery assert query.empty_intervals is True def test_query_data_types(mocker): mocker.patch("gs_quant.api.gs.data.GsDataApi.query_data", return_value=test_data) mocker.patch("gs_quant.api.gs.data.GsDataApi.get_types", return_value=test_types) dataset = Dataset(Dataset.TR.TREOD) data = dataset.get_data(dt.date(2019, 1, 2), dt.date(2019, 1, 9), assetId='MA4B66MW5E27U8P32SB') assert data.equals(GsDataApi.construct_dataframe_with_types(str(Dataset.TR.TREOD), test_data)) def test_last_data(mocker): mocker.patch("gs_quant.api.gs.data.GsDataApi.last_data", return_value=[test_data[-1]]) mocker.patch("gs_quant.api.gs.data.GsDataApi.get_types", return_value=test_types) dataset = Dataset(Dataset.TR.TREOD) data = dataset.get_data_last(dt.date(2019, 1, 9), assetId='MA4B66MW5E27U8P32SB') assert data.equals(GsDataApi.construct_dataframe_with_types(str(Dataset.TR.TREOD), ([test_data[-1]]))) def test_get_data_series(mocker): field_value_maps = test_data mocker.patch("gs_quant.api.gs.data.GsDataApi.get_types", return_value=test_types) mocker.patch.object(GsDataApi, 'query_data', return_value=field_value_maps) mocker.patch.object(GsDataApi, 'symbol_dimensions', return_value=('assetId',)) dataset = Dataset(Dataset.TR.TREOD) series = dataset.get_data_series( 'tradePrice', dt.date(2019, 1, 2), dt.date(2019, 1, 9), assetId='MA4B66MW5E27U8P32SB' ) df = pd.DataFrame(test_data) index = pd.to_datetime(df.loc[:, 'date'].values) expected = pd.Series(index=index, data=df.loc[:, 'tradePrice'].values) expected = expected.rename_axis('date') pd.testing.assert_series_equal(series, expected) def test_get_coverage(mocker): mocker.patch("gs_quant.api.gs.data.GsDataApi.get_coverage", return_value=test_coverage_data) mocker.patch("gs_quant.api.gs.data.GsDataApi.get_types", return_value={'gsid': 'string'}) data = Dataset(Dataset.TR.TREOD).get_coverage() results = test_coverage_data["results"] gsid = GsDataApi.construct_dataframe_with_types(str(Dataset.TR.TREOD), results).get('gsid').get(0) assert data["results"][0]["gsid"] == gsid def test_construct_dataframe_with_types(mocker): mocker.patch("gs_quant.api.gs.data.GsDataApi.get_types", return_value=test_types) df = GsDataApi.construct_dataframe_with_types(str(Dataset.TR.TREOD), [test_data[0]]) assert np.issubdtype(df.index.dtype, np.datetime64) assert df['adjustedAskPrice'].dtype == np.int64 assert df['adjustedBidPrice'].dtype == np.float64 # pandas 3.0 uses StringDtype for strings, older versions use object assert df['assetId'].dtype == object or pd.api.types.is_string_dtype(df['assetId']) assert np.issubdtype(df['updateTime'].dtype, np.datetime64) def test_construct_dataframe_var_schema(mocker): mocker.patch("gs_quant.api.gs.data.GsDataApi.get_types", return_value=test_types) var_schema = copy.deepcopy(test_data) for i in range(len(var_schema)): if i % 2 == 1: prev = var_schema[i - 1] curr = var_schema[i] curr['difference_tradePrice'] = curr['tradePrice'] - prev['tradePrice'] df = GsDataApi.construct_dataframe_with_types(str(Dataset.TR.TREOD), var_schema, True) assert np.issubdtype(df['difference_tradePrice'].dtype, np.floating) def test_dataframe_with_mixed_date_type(mocker): mocker.patch.object(GsDataApi, 'get_types', return_value={'updateTime': 'date-time'}) df = GsDataApi.construct_dataframe_with_types( 'BBG_PER_SECURITY', [{'updateTime': '2022-02-24T19:25:28Z'}, {'updateTime': '2022-11-10T17:18:23.021494Z'}] ) assert df.empty is False def test_data_series_format(mocker): start = dt.date(2019, 1, 2) end = dt.datetime(2019, 1, 9) df = pd.DataFrame(test_data) index = pd.to_datetime(df.loc[:, 'date'].values) expected = pd.Series(index=index, data=df.loc[:, 'tradePrice'].values) expected = expected.rename_axis('date') # mock GsSession and data response mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mock_response = {'requestId': 'qwerty', 'data': test_data} mocker.patch.object(GsSession.current.sync, 'post', side_effect=lambda *args, **kwargs: mock_response) mocker.patch.object(GsSession.current.sync, 'get', return_value={"id": "TREOD"}) mocker.patch.object(GsDataApi, 'symbol_dimensions', return_value=('assetId',)) mocker.patch("gs_quant.api.gs.data.GsDataApi.get_types", return_value=test_types) actual = Dataset('TREOD').get_data_series(field='tradePrice', start=start, end=end, assetId='MA4B66MW5E27U8P32SB') pd.testing.assert_series_equal(actual, expected) assert len(GsSession.current.sync.post.mock_calls) == 1 name, args, kwargs = GsSession.current.sync.post.mock_calls[0] assert kwargs['payload'].format == Format.MessagePack assert kwargs['request_headers'] == {'Accept': 'application/msgpack'} assert args[0] == '/data/TREOD/query' GsSession.current.sync.post.reset_mock() actual = Dataset('TREOD').get_data_series( field='tradePrice', start=start, end=end, assetId='MA4B66MW5E27U8P32SB', format=Format.Json ) pd.testing.assert_series_equal(actual, expected) assert len(GsSession.current.sync.post.mock_calls) == 1 name, args, kwargs = GsSession.current.sync.post.mock_calls[0] assert kwargs['payload'].format == Format.Json assert 'request_headers' not in kwargs assert args[0] == '/data/TREOD/query' def test_get_data_bulk(mocker): df2 = pd.DataFrame() test_df = { 'date': {pd.Timestamp('20230302'): dt.date(2023, 3, 2)}, 'clusterRegion': {pd.Timestamp('20230302'): 'Asia Pacific'}, 'clusterClass': {pd.Timestamp('20230302'): '13'}, 'assetId': {pd.Timestamp('20230302'): 'MA4B66MW5E27U8P4ZFX'}, 'clusterDescription': {pd.Timestamp('20230302'): 'Small Trd Count, Hard to Complete'}, 'updateTime': { pd.Timestamp('20230302'): dt.datetime.strptime('2023-03-05T00:40:54.000Z', '%Y-%m-%dT%H:%M:%S.%fZ') }, } df = pd.DataFrame(test_df) df = df.set_index('date') coverage = pd.DataFrame({'clusterRegion': ['Asia Pacific']}) symbol_dimension = ('clusterRegion',) dataset_definition = DataSetEntity() dataset_definition.parameters = DataSetParameters() dataset_definition.parameters.history_date = dt.datetime(2017, 1, 2) dataset_definition.dimensions = DataSetDimensions() dataset_definition.dimensions.time_field = 'date' # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) # mock fetch_data function in utilities.py mock = mocker.patch("gs_quant.data.utilities.Utilities.fetch_data", return_value=df) # mock granular functions in get_dataset_parameter in utilities.py mocker.patch("gs_quant.api.gs.data.GsDataApi.symbol_dimensions", return_value=symbol_dimension) mocker.patch("gs_quant.api.gs.data.GsDataApi.get_definition", return_value=dataset_definition) # mock get_dataset_coverage function in utilities.py mocker.patch("gs_quant.data.dataset.Dataset.get_coverage", return_value=coverage) def handler(data_frame): nonlocal df2 df2 = data_frame.head(1) dataset_id = "EQTRADECLUSTERS" original_start = dt.datetime(2023, 3, 2, 0, 0, 0) final_end = dt.datetime(2023, 3, 2, 0, 0, 0) c = Dataset(dataset_id) c.get_data_bulk( original_start=original_start, final_end=final_end, request_batch_size=4, identifier="clusterRegion", handler=handler, ) assert mock.call_count == 1 assert df.equals(df2) if __name__ == "__main__": pytest.main(args=["test_dataset.py"]) ================================================ FILE: gs_quant/test/data/test_query.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import pytest from gs_quant.api.gs.data import GsDataApi, QueryType from gs_quant.data import DataContext def test_build_market_data_query(): asset_ids = ['MA4B66MW5E27U8P3295'] query_type = QueryType.IMPLIED_VOLATILITY where = {'tenor': '1y', 'strikeReference': 'delta', 'relativeStrike': 0.1} source = None real_time = False measure = "Curve" with DataContext(start=dt.date(2013, 1, 1), end=dt.date(2020, 2, 1)): queries = GsDataApi.build_market_data_query( asset_ids, query_type, where, source, real_time, measure, parallelize_queries=True ) payload = { 'entityIds': ['MA4B66MW5E27U8P3295'], 'queryType': 'Implied Volatility', 'where': {'tenor': '1y', 'strikeReference': 'delta', 'relativeStrike': 0.1}, 'source': 'any', 'frequency': 'End Of Day', 'measures': ['Curve'], } dates = [ {'startDate': dt.date(2013, 1, 1), 'endDate': dt.date(2014, 1, 1)}, {'startDate': dt.date(2014, 1, 1), 'endDate': dt.date(2015, 1, 1)}, {'startDate': dt.date(2015, 1, 1), 'endDate': dt.date(2016, 1, 1)}, {'startDate': dt.date(2016, 1, 1), 'endDate': dt.date(2016, 12, 31)}, {'startDate': dt.date(2016, 12, 31), 'endDate': dt.date(2017, 12, 31)}, {'startDate': dt.date(2017, 12, 31), 'endDate': dt.date(2018, 12, 31)}, {'startDate': dt.date(2018, 12, 31), 'endDate': dt.date(2019, 12, 31)}, {'startDate': dt.date(2019, 12, 31), 'endDate': dt.date(2020, 2, 1)}, ] expected = [{'queries': [{**payload, **date_range}]} for date_range in dates] assert expected == queries with DataContext(start=dt.date(2013, 1, 1), end=dt.date(2025, 1, 1)): queries = GsDataApi.build_market_data_query(asset_ids, query_type, where, source, real_time, measure) payload = { 'entityIds': ['MA4B66MW5E27U8P3295'], 'queryType': 'Implied Volatility', 'where': {'tenor': '1y', 'strikeReference': 'delta', 'relativeStrike': 0.1}, 'source': 'any', 'frequency': 'End Of Day', 'measures': ['Curve'], 'startDate': dt.date(2013, 1, 1), 'endDate': dt.date(2025, 1, 1), } expected = {'queries': [payload]} assert expected == queries if __name__ == '__main__': pytest.main(args=["test_queries.py"]) ================================================ FILE: gs_quant/test/datetime_/__init__.py ================================================ ================================================ FILE: gs_quant/test/datetime_/test_date.py ================================================ """ Copyright 2020 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from pytest import approx from gs_quant.datetime import DayCountConvention, day_count_fraction, has_feb_29, today, PaymentFrequency from gs_quant.common import PricingLocation def test_has_feb_29(): assert not has_feb_29(dt.date(2019, 1, 1), dt.date(2019, 12, 31)) assert has_feb_29(dt.date(2020, 1, 1), dt.date(2020, 12, 31)) assert has_feb_29(dt.date(2020, 2, 28), dt.date(2020, 3, 31)) assert not has_feb_29(dt.date(2020, 2, 29), dt.date(2020, 3, 31)) # first date is exclusive assert not has_feb_29(dt.date(2020, 1, 1), dt.date(2020, 2, 28)) assert has_feb_29(dt.date(2020, 1, 1), dt.date(2020, 2, 29)) # last date is inclusive assert has_feb_29(dt.date(2008, 1, 1), dt.date(2020, 12, 31)) def test_today_with_location(): for location in PricingLocation: assert today(location) is not None assert today(None) == dt.date.today() def test_day_count_fraction(): # 2017 is not a leap year start = dt.date(2017, 1, 1) end = dt.date(2017, 12, 31) assert day_count_fraction(start, end, DayCountConvention.ONE_ONE) == approx(1) assert day_count_fraction(start, end, DayCountConvention.ACTUAL_360) == approx(1.011111111111) assert day_count_fraction(start, end, DayCountConvention.ACTUAL_365F) == approx(0.997260273973) assert day_count_fraction(start, end, DayCountConvention.ACTUAL_365L) == approx(0.997260273973) # 2016 is a leap year start = dt.date(2015, 11, 12) end = dt.date(2017, 12, 15) assert day_count_fraction(start, end, DayCountConvention.ONE_ONE) == approx(1) assert day_count_fraction(start, end, DayCountConvention.ACTUAL_360) == approx(2.122222222222) assert day_count_fraction(start, end, DayCountConvention.ACTUAL_365F) == approx(2.093150684932) # End date is not leap year, so should use 365 assert day_count_fraction(start, end, DayCountConvention.ACTUAL_365L) == approx(2.093150684932) # Feb 29 is within range, so should use 366 assert day_count_fraction(start, end, DayCountConvention.ACTUAL_365L, PaymentFrequency.ANNUALLY) == approx( 2.087431693989 ) ================================================ FILE: gs_quant/test/datetime_/test_gscalendar.py ================================================ """ Copyright 2021 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from unittest import mock import pandas as pd from gs_quant.common import PricingLocation from gs_quant.data import Dataset from gs_quant.datetime import GsCalendar from gs_quant.test.api.test_risk import set_session MOCK_HOLIDAY = pd.DataFrame(index=[dt.datetime(1999, 9, 12)], data={'holiday': 'Labor Day'}) # Test GsCalendar initiated with single PricingLocation @mock.patch.object(Dataset, 'get_coverage', return_value=pd.DataFrame()) @mock.patch.object(Dataset, 'get_data', return_value=MOCK_HOLIDAY) def test_gs_calendar_single(mocker, _mocker_cov): set_session() nyc = PricingLocation.NYC GsCalendar.reset() days = GsCalendar(nyc).holidays assert days # Test GsCalendar initiated with tuple @mock.patch.object(Dataset, 'get_coverage', return_value=pd.DataFrame()) @mock.patch.object(Dataset, 'get_data', return_value=MOCK_HOLIDAY) def test_gs_calendar_tuple(mocker, _mocker_cov): set_session() locs = (PricingLocation.NYC, PricingLocation.LDN) days = GsCalendar(locs).holidays assert days ================================================ FILE: gs_quant/test/datetime_/test_point.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from gs_quant.datetime import point_sort_order def test_point_sort_order(): check_map = { 'o/n': 0, # O/N 'Jan20': 379, # EuroOrFraReg 'JAN20': 379, # EuroOrFraReg '1y': 365, # RDatePartReg '1Y': 365, # RDatePartReg '3m': 90, # RDatePartReg '-1f XC': -30, # CashFXReg '3x3': 90, # FRAxReg 'QE1-2020': 425, # SpikeQEReg 'JAN2021': 731, # MMMYYYYReg '9JAN2021': 739, # DDMMMYYYYReg '1.1y': 1.1 * 365, # FloatingYear 'K19': 120, # LYYReg 'JAN 20': 365, # MMMYYReg 'FFK9': 120, # FFFutReg 'ON GC': 0, # RepoGCReg '1 Week GC': 7, # RepoGCReg '1 week': 7, # RelativeReg '01Jan20': 365, # DDMMMYYReg '20Y;3M': 7300.0012328767125, } for input, expected in check_map.items(): day = dt.date(2019, 1, 1) actual = point_sort_order(input, day) assert expected == actual ================================================ FILE: gs_quant/test/datetime_/test_relative_date.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from unittest.mock import Mock import pandas as pd import pytest from testfixtures import Replacer from gs_quant.datetime.relative_date import RelativeDate holiday_calendar = [dt.date(2021, 1, 18)] def test_rule_parsing(): assert RelativeDate('A')._get_rules() == ['A'] assert RelativeDate('1d')._get_rules() == ['1d'] assert RelativeDate('-1d')._get_rules() == ['-1d'] assert RelativeDate('1y-1d')._get_rules() == ['1y', '-1d'] assert RelativeDate('-1y-1d')._get_rules() == ['-1y', '-1d'] assert RelativeDate('-1y+1d')._get_rules() == ['-1y', '1d'] # A -> first day of the year def test_rule_a_(): date: dt = RelativeDate('A', base_date=dt.date(2021, 1, 19)).apply_rule() assert date == dt.date(2021, 1, 1) # b -> Business days def test_rule_b(): date: dt = RelativeDate('-1b', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 15) date: dt = RelativeDate('+1b', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 20) # d -> gregorian calendar days def test_rule_d(): date: dt = RelativeDate('+1d', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 20) # e -> end of month def test_rule_e(): date: dt = RelativeDate('e', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 31) # M -> nth Monday def test_rule_m_(): date: dt = RelativeDate('1M', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 4) date: dt = RelativeDate('4M', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 25) # T -> nth Tuesday def test_rule_t_(): date: dt = RelativeDate('1T', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 5) date: dt = RelativeDate('4T', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 26) # W -> nth Wednesday def test_rule_w_(): date: dt = RelativeDate('1W', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 6) date: dt = RelativeDate('4W', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 27) # R -> nth Thursday def test_rule_r_(): date: dt = RelativeDate('1R', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 7) date: dt = RelativeDate('4R', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 28) # F -> nth Friday def test_rule_f_(): date: dt = RelativeDate('1F', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 1) date: dt = RelativeDate('4F', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 22) # V -> nth Saturday def test_rule_v_(): date: dt = RelativeDate('1V', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 2) date: dt = RelativeDate('4V', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 23) # Z -> nth Sunday def test_rule_z_(): date: dt = RelativeDate('1Z', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 3) date: dt = RelativeDate('4Z', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 24) # g -> nth week def test_rule_g(): date: dt = RelativeDate('1g', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 26) date: dt = RelativeDate('3g', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 2, 9) date: dt = RelativeDate('-1g', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 12) # N -> nth Monday relative to given date def test_rule_n_(): date: dt = RelativeDate('1N', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 25) # U -> nth Tuesday relative to given date def test_rule_u_(): date: dt = RelativeDate('1U', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 19) # X -> nth Wednesday relative to given date def test_rule_x_(): date: dt = RelativeDate('1X', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 20) # S -> nth Thursday relative to given date def test_rule_s_(): date: dt = RelativeDate('1S', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 21) # G -> nth Friday relative to given date def test_rule_g_(): date: dt = RelativeDate('1G', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 22) # I -> nth Saturday relative to given date def test_rule_i_(): date: dt = RelativeDate('1I', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 23) # P -> nth Sunday relative to given date def test_rule_p_(): date: dt = RelativeDate('1P', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 24) # w -> weeks offset def test_rule_w(): date: dt = RelativeDate('1w', base_date=dt.date(2022, 3, 30)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2022, 4, 6) date: dt = RelativeDate('1w', base_date=dt.date(2025, 10, 11)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2025, 10, 20) date: dt = RelativeDate('-1w', base_date=dt.date(2025, 10, 11)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2025, 10, 3) # k -> Relative years with no implicit USD calendar def test_rule_k(): date: dt = RelativeDate('1k', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2022, 1, 19) date: dt = RelativeDate('-1k', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2020, 1, 20) # r -> end of year def test_rule_r(): date: dt = RelativeDate('r', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 12, 31) # u -> add business days without implicit USD holidays def test_rule_u(): date: dt = RelativeDate('1u', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 20) date: dt = RelativeDate('2u', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 21) date: dt = RelativeDate('-2u', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 14) date: dt = RelativeDate('-0u', base_date=dt.date(2025, 11, 30)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2025, 11, 28) date: dt = RelativeDate('-1u', base_date=dt.date(2025, 11, 30)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2025, 11, 28) # v -> last business day of the month, gets last business day of month (number is months ahead) def test_rule_v(): date: dt = RelativeDate('2v', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 3, 31) # x -> gets last business day of month (ignores number) def test_rule_x(): date: dt = RelativeDate('x', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 29) # y -> Relative years with the result moved to the next week day if the day falls on a weekend specified by the weekmask def test_rule_y(): date: dt = RelativeDate('2y', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2023, 1, 19) date: dt = RelativeDate('-2y', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2019, 1, 21) # Returns four business days after the first business day on or after the 15th calendar day of the month def test_chaining(): date: dt = RelativeDate('J+14d+0u+4u', base_date=dt.date(2021, 1, 19)).apply_rule(holiday_calendar=holiday_calendar) assert date == dt.date(2021, 1, 22) # last calendar day of month shifted earlier to immediately preceding business day. i.e. last business day of the month def test_rule_e_minus_u(): date: dt = RelativeDate('e-0u', base_date=dt.date(2025, 11, 19)).apply_rule() assert date == dt.date(2025, 11, 28) def test_rule_roll_convention(): date: dt = RelativeDate('1w', base_date=dt.date(2025, 10, 11)).apply_rule() assert date == dt.date(2025, 10, 20) date: dt = RelativeDate('1w', base_date=dt.date(2025, 10, 11)).apply_rule(roll_convention='backward') assert date == dt.date(2025, 10, 17) def mock_holiday_data(*args, **kwargs): dq = args[1] ccies = dq.as_dict()['where'].get('currency', []) base_holidays = [{'date': '2021-12-27', 'currency': 'USD', 'description': "Xmas"}] if 'USD' in ccies: base_holidays.append({'date': '2022-04-11', 'currency': 'USD', 'description': "Birthday"}) return base_holidays test_types = {'date': 'date', 'exchange': 'string', 'currency': 'string', 'description': 'string', 'updateTime': 'date'} # test holiday calendar logic def test_currency_holiday_calendars(mocker): replace = Replacer() replace('gs_quant.api.gs.data.GsDataApi.query_data', Mock(side_effect=mock_holiday_data)) mocker.patch( "gs_quant.api.gs.data.GsDataApi.get_coverage", return_value=pd.DataFrame([['USD'], ['GBP']], columns=['currency']), ) mocker.patch("gs_quant.api.gs.data.GsDataApi.get_types", return_value=test_types) rdate = RelativeDate('-1b', base_date=dt.date(2022, 4, 12)) assert dt.date(2022, 4, 11) == rdate.apply_rule(currencies=[]) assert dt.date(2022, 4, 11) == rdate.apply_rule(currencies=['GBP']) assert dt.date(2022, 4, 8) == rdate.apply_rule(currencies=['USD']) assert dt.date(2022, 4, 11) == rdate.apply_rule() # No longer using USD by default replace.restore() if __name__ == "__main__": pytest.main(args=["test_relative_date.py"]) ================================================ FILE: gs_quant/test/datetime_/test_time.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import numpy as np from gs_quant.datetime import time_difference_as_string, SECS_IN_YEAR def test_time_difference_as_string(): check_map = { '4 Seconds': np.timedelta64(4, 's'), '1 Minute 5 Seconds': np.timedelta64(65, 's'), '1 Year': np.timedelta64(int(SECS_IN_YEAR), 's'), '1 Day 1 Minute 5 Seconds': np.timedelta64(86465, 's'), } for expected, input in check_map.items(): actual = time_difference_as_string(input) assert expected == actual ================================================ FILE: gs_quant/test/entities/test_entitlements.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from testfixtures import Replacer from testfixtures.mock import Mock from gs_quant.common import Entitlements as TargetEntitlements from gs_quant.entities.entitlements import Group, Entitlements, User, EntitlementBlock from gs_quant.target.groups import Group as TargetGroup from gs_quant.target.reports import User as TargetUser def get_fake_user(): user = TargetUser.from_dict( {'id': 'userId', 'email': 'jane.doe@gs.com', 'name': 'Jane Doe', 'company': 'Goldman Sachs Group'} ) replace = Replacer() mock = replace('gs_quant.api.gs.users.GsUsersApi.get_users', Mock()) mock.return_value = [user] user = User.get(email='jane.doe@gs.com') replace.restore() return user def get_fake_group(): group = TargetGroup.from_dict({'name': 'fakeGroup', 'id': 'groupId', 'tags': []}) replace = Replacer() mock = replace('gs_quant.api.gs.groups.GsGroupsApi.get_group', Mock()) mock.return_value = group group = Group.get(group_id='groupId') replace.restore() return group def test_to_target(): ent = Entitlements(edit=EntitlementBlock(users=[get_fake_user()], groups=[get_fake_group()])) as_target = ent.to_target() assert as_target.edit == ('guid:userId', 'group:groupId') def test_to_dict(): ent = Entitlements(edit=EntitlementBlock(users=[get_fake_user()], groups=[get_fake_group()])) as_dict = ent.to_dict() assert as_dict == {'edit': ('guid:userId', 'group:groupId')} def test_from_target(): replace = Replacer() mock = replace('gs_quant.api.gs.users.GsUsersApi.get_users', Mock()) mock.return_value = [ TargetUser.from_dict( {'id': 'userId', 'email': 'jane.doe@gs.com', 'name': 'Jane Doe', 'company': 'Goldman Sachs Group'} ) ] ent = Entitlements.from_target(TargetEntitlements(edit=('guid:userId', 'role:roleId'))) replace.restore() assert ent.edit.users == [get_fake_user()] assert ent.edit.roles == ['roleId'] def test_from_dict(): replace = Replacer() mock = replace('gs_quant.api.gs.users.GsUsersApi.get_users', Mock()) mock.return_value = [ TargetUser.from_dict( {'id': 'userId', 'email': 'jane.doe@gs.com', 'name': 'Jane Doe', 'company': 'Goldman Sachs Group'} ) ] ent = Entitlements.from_dict({'edit': ['guid:userId', 'role:roleId']}) replace.restore() assert ent.edit.users == [get_fake_user()] assert ent.edit.roles == ['roleId'] ================================================ FILE: gs_quant/test/entities/test_group.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from testfixtures import Replacer from testfixtures.mock import Mock from gs_quant.entities.entitlements import Group from gs_quant.target.groups import Group as TargetGroup def test_get(): group = TargetGroup.from_dict({'name': 'fakeGroup', 'id': 'groupId', 'tags': []}) replace = Replacer() mock = replace('gs_quant.api.gs.groups.GsGroupsApi.get_group', Mock()) mock.return_value = group assert Group.get('groupId').name == 'fakeGroup' replace.restore() def test_get_many(): groups = [ TargetGroup.from_dict({'name': 'fakeGroup', 'id': 'groupId', 'tags': []}), TargetGroup.from_dict({'name': 'fakeGroup2', 'id': 'groupId2', 'tags': []}), ] replace = Replacer() mock = replace('gs_quant.api.gs.groups.GsGroupsApi.get_groups', Mock()) mock.return_value = groups assert len(Group.get_many(group_ids=['groupId', 'groupId2'])) == 2 replace.restore() def test_save_update(): group = TargetGroup.from_dict({'name': 'fakeGroup', 'id': 'groupId', 'tags': []}) replace = Replacer() mock = replace('gs_quant.api.gs.groups.GsGroupsApi.update_group', Mock()) mock.return_value = group mock = replace('gs_quant.entities.entitlements.Group._group_exists', Mock()) mock.return_value = True g = Group(group_id='groupId', name='fakeGroup', tags=[]) assert g.save().name == 'fakeGroup' replace.restore() def test_save_create(): group = TargetGroup.from_dict({'name': 'fakeGroup', 'id': 'groupId', 'tags': []}) replace = Replacer() mock = replace('gs_quant.api.gs.groups.GsGroupsApi.create_group', Mock()) mock.return_value = group mock = replace('gs_quant.entities.entitlements.Group._group_exists', Mock()) mock.return_value = False g = Group(group_id='groupId', name='fakeGroup', tags=[]) assert g.save().name == 'fakeGroup' replace.restore() ================================================ FILE: gs_quant/test/entities/test_user.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from testfixtures import Replacer from testfixtures.mock import Mock from gs_quant.entities.entitlements import User from gs_quant.target.reports import User as TargetUser def test_get(): user = TargetUser.from_dict( {'id': 'userId', 'email': 'jane.doe@gs.com', 'name': 'Doe, Jane', 'company': 'Goldman Sachs Group'} ) replace = Replacer() mock = replace('gs_quant.api.gs.users.GsUsersApi.get_users', Mock()) mock.return_value = [user] assert User.get(name='Doe, Jane').id == 'userId' replace.restore() def test_get_many(): users = [ TargetUser.from_dict( {'id': 'userId', 'email': 'jane.doe@gs.com', 'name': 'Doe, Jane', 'company': 'Goldman Sachs Group'} ), TargetUser.from_dict( {'id': 'userId2', 'email': 'john.doe@gs.com', 'name': 'Doe, John', 'company': 'Goldman Sachs Group'} ), ] replace = Replacer() mock = replace('gs_quant.api.gs.users.GsUsersApi.get_users', Mock()) mock.return_value = users assert len(User.get_many(user_ids=['userId', 'userId2'])) == 2 replace.restore() ================================================ FILE: gs_quant/test/fixtures/__init__.py ================================================ ================================================ FILE: gs_quant/test/fixtures/content.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from base64 import b64encode from gs_quant.common import Entitlements from gs_quant.target.content import GetManyContentsResponse, ContentResponse, Content class ContentFixtures: _data = { 'id': 'some-id', 'version': 'some-version', 'name': 'some-name', 'entitlements': { 'view': ['some-view-entitlement'], 'edit': ['some-edit-entitlement'], 'delete': ['some-delete-token'], 'admin': ['some-admin-token'], }, 'createdById': 'some-created-by-id', 'createdTime': dt.datetime(2019, 5, 13), 'authors': [ { 'name': 'some-author-name', 'firstName': 'some-author-first-name', 'lastName': 'some-author-last-name', 'id': 'some-author-id', 'division': 'some-author-division', } ], 'lastUpdatedTime': dt.datetime(2019, 5, 14), 'content': {'body': b64encode(b'Hello world!'), 'mimeType': 'text/plain', 'encoding': 'UTF-8'}, 'channels': ['some-channel'], } @classmethod def _get_content_response(cls): return ContentResponse( id=cls._data['id'], version=cls._data['version'], name=cls._data['name'], entitlements=Entitlements( cls._data['entitlements']['view'], cls._data['entitlements']['edit'], cls._data['entitlements']['admin'], cls._data['entitlements']['delete'], ), created_by_id=cls._data['createdById'], created_time=cls._data['createdTime'], last_updated_time=cls._data['lastUpdatedTime'], channels=cls._data['channels'], content=Content( cls._data['content']['body'], cls._data['content']['mimeType'], cls._data['content']['encoding'] ), ) @classmethod def get_many_contents_response(cls, status: int = 200, message: str = "Ok"): content = cls._get_content_response() return GetManyContentsResponse(status, message, (content,)) ================================================ FILE: gs_quant/test/markets/__init__.py ================================================ ================================================ FILE: gs_quant/test/markets/test_baskets.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from typing import Dict from unittest import mock import pytest from gs_quant.api.gs.assets import GsAsset, GsAssetApi from gs_quant.api.gs.indices import GsIndexApi from gs_quant.api.gs.reports import GsReportApi from gs_quant.api.gs.users import GsUsersApi from gs_quant.common import ( AssetClass, AssetType, Entitlements as TargetEntitlements, PositionSet as TargetPositionSet, Position as TargetPosition, ReportParameters, XRef, ) from gs_quant.entities.entitlements import User from gs_quant.errors import MqError from gs_quant.markets.baskets import Basket, ErrorMessage from gs_quant.markets.indices_utils import ReturnType from gs_quant.markets.position_set import Position, PositionSet from gs_quant.session import GsSession, Environment from gs_quant.target.indices import CustomBasketsResponse, CustomBasketRiskParams from gs_quant.target.reports import Report, User as TargetUser # Helper mock value constants asset_1 = {'name': 'asset 1', 'id': 'id1', 'bbid': 'bbid1'} asset_2 = {'name': 'asset 2', 'id': 'id2', 'bbid': 'bbid2'} assets_data = [asset_1, asset_2] base_user = { 'name': 'First Last', 'email': 'ex@email.com', 'city': 'City A', 'company': 'Company A', 'country': 'Country A', 'region': 'Region A', } cb_response = CustomBasketsResponse('done', 'R1234567890', 'MA1234567890') gs_asset = GsAsset( asset_class=AssetClass.Equity, type_=AssetType.Custom_Basket, name='Test Basket', id_='MA1234567890', entitlements=TargetEntitlements(admin=['guid:user_abc']), xref=XRef(ticker='GSMBXXXX'), ) initial_price = {'price': 100} mqid = 'MA1234567890' name = 'Test Basket' positions = [Position('bbid1', asset_id='id1', quantity=100), Position('bbid2', asset_id='id2', quantity=200)] positions_weighted = positions = [ Position('bbid1', asset_id='id1', weight=0.4), Position('bbid2', asset_id='id2', weight=0.6), ] position_set = PositionSet(positions, divisor=1000) report = Report(mqid, 'asset', 'Basket Create', ReportParameters(), status='done') resolved_asset = {'GSMBXXXX': [{'id': mqid}]} target_positions = tuple([TargetPosition(asset_id='id1', quantity=100), TargetPosition(asset_id='id2', quantity=200)]) target_position_set = TargetPositionSet(target_positions, dt.date(2021, 1, 7), divisor=1000) ticker = 'GSMBXXXX' user_ea = {**base_user, 'id': 'user_abc', 'tokens': ['external', 'guid:user_abc']} # external, admin user_ena = {**base_user, 'id': 'user_xyz', 'tokens': ['external', 'guid:user_xyz']} # external, non admin user_ia = {**base_user, 'id': 'user_abc', 'tokens': ['internal', 'guid:user_abc']} # internal, admin @mock.patch.object(GsSession.__class__, 'default_value') def mock_session(mocker): """Mock GsSession helper""" mocker.return_value = GsSession.get(Environment.QA, 'client_id', 'secret') def mock_response(mocker, mock_object, mock_fn, mock_response): """Mock patch helper""" if mock_response is not None: mocker.patch.object(mock_object, mock_fn, return_value=mock_response) def mock_basket_init(mocker, user: Dict, existing: bool = True): """Mock basket initialization helper""" if existing: mock_response(mocker, GsAssetApi, 'resolve_assets', resolved_asset) mock_response(mocker, GsAssetApi, 'get_asset', gs_asset) mock_response(mocker, GsAssetApi, 'get_latest_positions', target_position_set) mock_response(mocker, GsAssetApi, 'get_many_assets_data', assets_data) mock_response(mocker, GsIndexApi, 'initial_price', initial_price) mock_response(mocker, GsReportApi, 'get_reports', [report]) mock_response(mocker, GsUsersApi, 'get_users', [TargetUser.from_dict(user)]) mock_response(mocker, GsUsersApi, 'get_current_user_info', user) def test_basket_error_messages(mocker): mock_session() # test non admin errors mock_basket_init(mocker, user_ena) basket = Basket.get(ticker) with pytest.raises(MqError, match=ErrorMessage.NON_ADMIN.value): basket.cancel_rebalance() with pytest.raises(MqError, match=ErrorMessage.NON_ADMIN.value): basket.allow_ca_restricted_assets = False # test non internal errors with pytest.raises(MqError, match=ErrorMessage.NON_INTERNAL.value): basket.flagship = False # test unmodifiable errors with pytest.raises(MqError, match=ErrorMessage.UNMODIFIABLE.value): basket.ticker = 'GSMBZZZZ' # test uninitialized errors mock_basket_init(mocker, user_ena, False) basket = Basket() with pytest.raises(MqError, match=ErrorMessage.UNINITIALIZED.value): basket.clone() with pytest.raises(MqError, match=ErrorMessage.UNINITIALIZED.value): basket.get_latest_rebalance_data() def test_basket_create(mocker): mock_session() mock_basket_init(mocker, user_ea, False) mock_response(mocker, GsIndexApi, 'validate_ticker', True) basket = Basket() basket.name = name basket.ticker = ticker basket.position_set = position_set basket.return_type = ReturnType.PRICE_RETURN mock_response(mocker, GsIndexApi, 'create', cb_response) mock_response(mocker, GsAssetApi, 'get_asset', gs_asset) mock_response(mocker, GsReportApi, 'get_report', report) mock_basket_init(mocker, user_ea) response = basket.create() GsIndexApi.create.assert_called() assert response == cb_response.as_dict() def test_basket_clone(mocker): mock_session() # test uninitialized errors mock_basket_init(mocker, user_ea, False) basket = Basket() with pytest.raises(MqError, match=ErrorMessage.UNINITIALIZED.value): basket.clone() # test clone mock_basket_init(mocker, user_ena) parent_basket = Basket.get(ticker) clone = parent_basket.clone() mock_basket_init(mocker, user_ea, False) parent_positions = [p.as_dict() for p in parent_basket.position_set.positions] clone_positions = [p.as_dict() for p in clone.position_set.positions] assert clone_positions == parent_positions assert clone.clone_parent_id == mqid assert clone.parent_basket == ticker def test_basket_edit(mocker): mock_session() # test errors mock_basket_init(mocker, user_ea, False) basket = Basket() with pytest.raises(MqError, match=ErrorMessage.UNINITIALIZED.value): basket.update() mock_basket_init(mocker, user_ena) basket = Basket.get(ticker) with pytest.raises(MqError, match=ErrorMessage.NON_ADMIN.value): basket.update() # test update mock_basket_init(mocker, user_ia) basket = Basket.get(ticker) basket.description = 'New Basket Description' gs_asset.description = 'New Basket Description' mock_response(mocker, GsIndexApi, 'edit', cb_response) mock_response(mocker, GsAssetApi, 'get_asset', gs_asset) mock_response(mocker, GsReportApi, 'get_report', report) mock_basket_init(mocker, user_ia) response = basket.update() GsIndexApi.edit.assert_called() assert response == cb_response.as_dict() assert basket.description == 'New Basket Description' gs_asset.description = None def test_basket_rebalance(mocker): mock_session() mock_basket_init(mocker, user_ia) basket = Basket.get(ticker) basket.allow_ca_restricted_assets = True mock_response(mocker, GsIndexApi, 'rebalance', cb_response) mock_response(mocker, GsAssetApi, 'get_asset', gs_asset) mock_response(mocker, GsReportApi, 'get_report', report) mock_basket_init(mocker, user_ia) response = basket.update() GsIndexApi.rebalance.assert_called() assert response == cb_response.as_dict() def test_basket_edit_and_rebalance(mocker): mock_session() mock_basket_init(mocker, user_ia) basket = Basket.get(ticker) basket.description = 'New Basket Description' gs_asset.description = 'New Basket Description' basket.initial_price = 2000000 mock_response(mocker, GsIndexApi, 'edit', cb_response) mock_response(mocker, GsReportApi, 'get_report', report) mock_response(mocker, GsIndexApi, 'rebalance', cb_response) mock_response(mocker, GsAssetApi, 'get_asset', gs_asset) mock_response(mocker, GsReportApi, 'get_report', report) mock_basket_init(mocker, user_ia) response = basket.update() GsIndexApi.edit.assert_called() GsIndexApi.rebalance.assert_called() assert response == cb_response.as_dict() assert basket.description == 'New Basket Description' gs_asset.description = None def test_basket_update_entitlements(mocker): mock_session() mock_basket_init(mocker, user_ia) basket = Basket.get(ticker) mock_response(mocker, GsUsersApi, 'get_users', [TargetUser.from_dict(user_ena)]) new_admin = User.get(user_id='user_xyz') basket.entitlements.admin.users += [new_admin] entitlements_response = TargetEntitlements(admin=['guid:user_abc', 'guid:user_xyz']) mock_response(mocker, GsAssetApi, 'update_asset_entitlements', entitlements_response) response = basket.update() GsAssetApi.update_asset_entitlements.assert_called() assert response == entitlements_response def test_upload_position_history(mocker): mock_session() # test errors mock_basket_init(mocker, user_ea, False) basket = Basket() with pytest.raises(MqError, match=ErrorMessage.UNINITIALIZED.value): basket.upload_position_history() mock_basket_init(mocker, user_ena) basket = Basket.get(ticker) with pytest.raises(MqError, match=ErrorMessage.NON_ADMIN.value): basket.upload_position_history() # test backcast mock_basket_init(mocker, user_ia) basket = Basket.get(ticker) pos_set_1 = PositionSet(positions_weighted, dt.date(2021, 1, 1)) pos_set_2 = PositionSet(positions_weighted, dt.date(2021, 3, 1)) pos_set_3 = PositionSet(positions_weighted, dt.date(2021, 5, 1)) mock_response(mocker, GsIndexApi, 'backcast', cb_response) response = basket.upload_position_history([pos_set_1, pos_set_2, pos_set_3]) GsIndexApi.backcast.assert_called() assert response == cb_response.as_dict() def test_update_risk_reports(mocker): mock_session() # test errors mock_basket_init(mocker, user_ea, False) basket = Basket() with pytest.raises(MqError, match=ErrorMessage.UNINITIALIZED.value): basket.add_factor_risk_report('AXUS4M', False) with pytest.raises(MqError, match=ErrorMessage.UNINITIALIZED.value): basket.delete_factor_risk_report('AXUS4M') mock_basket_init(mocker, user_ena) basket = Basket.get(ticker) with pytest.raises(MqError, match=ErrorMessage.NON_ADMIN.value): basket.add_factor_risk_report('AXUS4M', False) with pytest.raises(MqError, match=ErrorMessage.NON_ADMIN.value): basket.delete_factor_risk_report('AXUS4M') # test add/delete factor risk reports mock_basket_init(mocker, user_ea) basket = Basket.get(ticker) mock_response(mocker, GsIndexApi, 'update_risk_reports', {}) basket.add_factor_risk_report('AXUS4M', False) payload = CustomBasketRiskParams(risk_model='AXUS4M', fx_hedged=False) GsIndexApi.update_risk_reports.assert_called_with(payload) mock_response(mocker, GsIndexApi, 'update_risk_reports', {}) basket.delete_factor_risk_report('AXUS4M') payload = CustomBasketRiskParams(risk_model='AXUS4M', delete=True) GsIndexApi.update_risk_reports.assert_called_with(payload) ================================================ FILE: gs_quant/test/markets/test_close_market.py ================================================ """ Copyright 2024 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import json from freezegun import freeze_time from gs_quant.common import PricingLocation from gs_quant.json_encoder import JSONEncoder from gs_quant.markets import CloseMarket, PricingContext def test_close_market_dict(): mkt = CloseMarket(date=dt.date(2024, 4, 11)) assert json.dumps(mkt.to_dict(), cls=JSONEncoder) == json.dumps(mkt.market.to_dict(), cls=JSONEncoder) mkt = CloseMarket(date=dt.date(2024, 4, 11), location='LDN') assert json.dumps(mkt.to_dict(), cls=JSONEncoder) == json.dumps(mkt.market.to_dict(), cls=JSONEncoder) @freeze_time("2025-03-20 16:00:00", tz_offset=4) def test_close_market_roll(): # 8PM in LDN, 4PM in NYC CloseMarket.roll_hr_and_min = (17, 30) test_date = dt.date(2025, 3, 20) with PricingContext(test_date, market_data_location="NYC"): assert PricingContext.current.market == CloseMarket(test_date - dt.timedelta(days=1), PricingLocation.NYC) CloseMarket.roll_hr_and_min = (12, 00) with PricingContext(test_date, market_data_location="NYC"): assert PricingContext.current.market == CloseMarket(test_date, PricingLocation.NYC) @freeze_time("2025-03-20 22:00:00", tz_offset=4) def test_close_market_roll_diff_days(): # 2AM in LDN, 10PM yesterday in NYC CloseMarket.roll_hr_and_min = (23, 30) test_date = dt.date(2025, 3, 21) with PricingContext(test_date, market_data_location="NYC"): # requested a date in the future for NYC - roll back to yesterday and do not apply the roll logic assert PricingContext.current.market == CloseMarket(test_date - dt.timedelta(days=1), PricingLocation.NYC) test_date = dt.date(2025, 3, 20) with PricingContext(test_date, market_data_location="NYC"): # requested today NYC, we are not past the roll time so roll back to 2025-03-19 assert PricingContext.current.market == CloseMarket(test_date - dt.timedelta(days=1), PricingLocation.NYC) ================================================ FILE: gs_quant/test/markets/test_hedger.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import pytest import datetime as dt from gs_quant.api.gs.hedges import GsHedgeApi from gs_quant.markets.hedge import ( HedgeExclusions, HedgeConstraints, Constraint, Hedge, PerformanceHedge, PerformanceHedgeParameters, ) from gs_quant.markets.position_set import PositionSet, Position mock_hedge = PerformanceHedge(parameters=None) calculation_results = { 'hedgedTarget': { 'constituents': [ { 'country': 'United States', 'name': 'Apple Inc', 'transactionCost': 1.1936100899058002, 'sector': 'Information Technology', 'shares': 26, 'assetId': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW', 'currency': 'USD', 'industry': 'Technology Hardware, Storage & Peripherals', 'marginalCost': 0.09597615517006632, 'advPercentage': 0, 'notional': 3819.9199999999996, 'price': 146.92, 'weight': 0.08040829746809594, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Albemarle Corp', 'transactionCost': 5.661775350570679, 'sector': 'Materials', 'shares': -2.9210200815542167, 'assetId': 'MA4B66MW5E27U9XPVYM', 'bbid': 'ALB UN', 'currency': 'USD', 'industry': 'Chemicals', 'marginalCost': 0.078815239121909, 'advPercentage': 2e-06, 'notional': -661.3189464638747, 'price': 226.4, 'weight': -0.01392058749098281, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Advanced Micro Devices', 'transactionCost': 0.5830498039722443, 'sector': 'Information Technology', 'shares': -0.8120055824108264, 'assetId': 'MA4B66MW5E27U9YGMB9', 'bbid': 'AMD UW', 'currency': 'USD', 'industry': 'Semiconductors & Semiconductor Equipment', 'marginalCost': 0.0010543794559583631, 'advPercentage': 0, 'notional': -85.91019061906543, 'price': 105.8, 'weight': -0.0018083866056981923, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Best Buy Co Inc', 'transactionCost': 2.401727557182312, 'sector': 'Consumer Discretionary', 'shares': -4.535031177626968, 'assetId': 'MA4B66MW5E27UA39J2L', 'bbid': 'BBY UN', 'currency': 'USD', 'industry': 'Specialty Retail', 'marginalCost': 0.02401392704093342, 'advPercentage': 2e-06, 'notional': -474.99916554464863, 'price': 104.74, 'weight': -0.009998605782375405, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Citigroup Inc', 'transactionCost': 1.4510516652072596, 'sector': 'Financials', 'shares': -27.227187182634943, 'assetId': 'MA4B66MW5E27UA4ZCWT', 'bbid': 'C UN', 'currency': 'USD', 'industry': 'Banks', 'marginalCost': 0.05919571023849292, 'advPercentage': 2e-06, 'notional': -1938.0311836599553, 'price': 71.18, 'weight': -0.040795039665274604, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Cummins Inc', 'transactionCost': 3.3661617040634155, 'sector': 'Industrials', 'shares': -0.4130028393296445, 'assetId': 'MA4B66MW5E27UACX6RZ', 'bbid': 'CMI UN', 'currency': 'USD', 'industry': 'Machinery', 'marginalCost': 0.006662256400232119, 'advPercentage': 0, 'notional': -94.02422640178686, 'price': 227.66, 'weight': -0.0019791848954225427, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Citrix Systems Inc', 'transactionCost': 2.754676938056946, 'sector': 'Information Technology', 'shares': -2.204015152257958, 'assetId': 'MA4B66MW5E27UAE4LLL', 'bbid': 'CTXS UW', 'currency': 'USD', 'industry': 'Software', 'marginalCost': 0.014082314238438266, 'advPercentage': 2e-06, 'notional': -242.86042962730437, 'price': 110.19, 'weight': -0.005112147288085058, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Duke Realty Corp', 'transactionCost': 1.3627572059631348, 'sector': 'Real Estate', 'shares': -8.864060939026558, 'assetId': 'MA4B66MW5E27UAEP46J', 'bbid': 'DRE UN', 'currency': 'USD', 'industry': 'Equity Real Estate Investment Trusts (REITs)', 'marginalCost': 0.012421166613716166, 'advPercentage': 5e-06, 'notional': -433.0093768714474, 'price': 48.85, 'weight': -0.009114731926834648, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Davita Inc', 'transactionCost': 3.084942579269409, 'sector': 'Health Care', 'shares': -3.4880239796169494, 'assetId': 'MA4B66MW5E27UAEP4D8', 'bbid': 'DVA UN', 'currency': 'USD', 'industry': 'Health Care Providers & Services', 'marginalCost': 0.027406811709718284, 'advPercentage': 6e-06, 'notional': -422.05090153365086, 'price': 121, 'weight': -0.008884058942908718, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Electronic Arts Inc', 'transactionCost': 2.1669615507125854, 'sector': 'Communication Services', 'shares': -6.899047429867354, 'assetId': 'MA4B66MW5E27UAEP4H9', 'bbid': 'EA UW', 'currency': 'USD', 'industry': 'Entertainment', 'marginalCost': 0.040878606822697415, 'advPercentage': 3e-06, 'notional': -896.1862611397693, 'price': 129.9, 'weight': -0.01886448184060066, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Extra Space Storage Inc', 'transactionCost': 3.9990124702453613, 'sector': 'Real Estate', 'shares': -3.1030213327842304, 'assetId': 'MA4B66MW5E27UAFU2CS', 'bbid': 'EXR UN', 'currency': 'USD', 'industry': 'Equity Real Estate Investment Trusts (REITs)', 'marginalCost': 0.04508686837043359, 'advPercentage': 5e-06, 'notional': -535.6125122518861, 'price': 172.61, 'weight': -0.011274500568803494, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'General Motors Co', 'transactionCost': 1.0103281140327454, 'sector': 'Consumer Discretionary', 'shares': -3.0290208240423557, 'assetId': 'MA4B66MW5E27UAGYYVE', 'bbid': 'GM UN', 'currency': 'USD', 'industry': 'Automobiles', 'marginalCost': 0.003364583586707687, 'advPercentage': 0, 'notional': -158.20575763973224, 'price': 52.23, 'weight': -0.003330189014812112, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'The Goldman Sachs Group, Inc.', 'transactionCost': 3.2194154492372298, 'sector': 'Financials', 'shares': 51, 'assetId': 'MA4B66MW5E27UAHKG34', 'bbid': 'GS UN', 'currency': 'USD', 'industry': 'Capital Markets', 'marginalCost': 1.3508400095029636, 'advPercentage': 2.3e-05, 'notional': 19933.350000000002, 'price': 390.85, 'weight': 0.4195917025319039, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Hormel Foods Corp', 'transactionCost': 1.4352393746376038, 'sector': 'Consumer Staples', 'shares': -11.239077266890737, 'assetId': 'MA4B66MW5E27UAJ5XEZ', 'bbid': 'HRL UN', 'currency': 'USD', 'industry': 'Food Products', 'marginalCost': 0.013853571784092618, 'advPercentage': 4e-06, 'notional': -458.55435248914205, 'price': 40.8, 'weight': -0.009652446852352158, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Iron Mountain Inc', 'transactionCost': 2.417732357978821, 'sector': 'Real Estate', 'shares': -5.0810349313170065, 'assetId': 'MA4B66MW5E27UAJQF4M', 'bbid': 'IRM UN', 'currency': 'USD', 'industry': 'Equity Real Estate Investment Trusts (REITs)', 'marginalCost': 0.011447654789702045, 'advPercentage': 3e-06, 'notional': -224.93741640940388, 'price': 44.27, 'weight': -0.004734872638786235, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'JPMorgan Chase & Co', 'transactionCost': 1.2586397729172192, 'sector': 'Financials', 'shares': -12.604086651115832, 'assetId': 'MA4B66MW5E27UAKHZFD', 'bbid': 'JPM UN', 'currency': 'USD', 'industry': 'Banks', 'marginalCost': 0.054444447777798256, 'advPercentage': 1e-06, 'notional': -2054.970287597925, 'price': 163.04, 'weight': -0.043256576622880225, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Kroger Co', 'transactionCost': 1.2731897830963135, 'sector': 'Consumer Staples', 'shares': -1.730011893560012, 'assetId': 'MA4B66MW5E27UAKNZNV', 'bbid': 'KR UN', 'currency': 'USD', 'industry': 'Food & Staples Retailing', 'marginalCost': 0.0018536665905637035, 'advPercentage': 0, 'notional': -69.16587550452927, 'price': 39.98, 'weight': -0.001455923237190695, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Mosaic Co/the', 'transactionCost': 2.1279181241989136, 'sector': 'Materials', 'shares': -5.192035694429817, 'assetId': 'MA4B66MW5E27UAL9SLW', 'bbid': 'MOS UN', 'currency': 'USD', 'industry': 'Chemicals', 'marginalCost': 0.008339681547933048, 'advPercentage': 2e-06, 'notional': -186.18640000225324, 'price': 35.86, 'weight': -0.003919174075869411, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Morgan Stanley', 'transactionCost': 2.3784881788491172, 'sector': 'Financials', 'shares': -100.16368860962515, 'assetId': 'MA4B66MW5E27UAL9STW', 'bbid': 'MS UN', 'currency': 'USD', 'industry': 'Capital Markets', 'marginalCost': 0.5160781640859926, 'advPercentage': 1.6e-05, 'notional': -10307.845194816524, 'price': 102.91, 'weight': -0.2169773928982519, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'MSCI Inc', 'transactionCost': 7.908167362213135, 'sector': 'Financials', 'shares': -0.5740039461869635, 'assetId': 'MA4B66MW5E27UAL9SUH', 'bbid': 'MSCI UN', 'currency': 'USD', 'industry': 'Capital Markets', 'marginalCost': 0.06319486932947428, 'advPercentage': 2e-06, 'notional': -379.628989889672, 'price': 661.37, 'weight': -0.007991089014053052, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Nucor Corp', 'transactionCost': 3.182479500770569, 'sector': 'Materials', 'shares': -0.7970054792874738, 'assetId': 'MA4B66MW5E27UALNB48', 'bbid': 'NUE UN', 'currency': 'USD', 'industry': 'Metals & Mining', 'marginalCost': 0.005388287534627193, 'advPercentage': 0, 'notional': -80.43379296969185, 'price': 100.92, 'weight': -0.001693109895388968, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Paypal Holdings Inc', 'transactionCost': 1.4358487129211426, 'sector': 'Information Technology', 'shares': -0.5480037674398188, 'assetId': 'MA4B66MW5E27UAM94N9', 'bbid': 'PYPL UW', 'currency': 'USD', 'industry': 'IT Services', 'marginalCost': 0.004606334069169853, 'advPercentage': 0, 'notional': -152.40532776268802, 'price': 278.11, 'weight': -0.0032080915125093927, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Qualcomm Inc', 'transactionCost': 1.0424676537513733, 'sector': 'Information Technology', 'shares': -0.6250042968063627, 'assetId': 'MA4B66MW5E27UAM94Q4', 'bbid': 'QCOM UW', 'currency': 'USD', 'industry': 'Semiconductors & Semiconductor Equipment', 'marginalCost': 0.001836148888424059, 'advPercentage': 0, 'notional': -83.67557525643583, 'price': 133.88, 'weight': -0.0017613485481459143, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Tractor Supply Company', 'transactionCost': 3.904104232788086, 'sector': 'Consumer Discretionary', 'shares': -0.49000336869618827, 'assetId': 'MA4B66MW5E27UANEQ5C', 'bbid': 'TSCO UW', 'currency': 'USD', 'industry': 'Specialty Retail', 'marginalCost': 0.00848540990065989, 'advPercentage': 1e-06, 'notional': -103.2535098516608, 'price': 210.72, 'weight': -0.002173458851174191, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Twitter Inc', 'transactionCost': 1.2658634781837463, 'sector': 'Communication Services', 'shares': -8.874061007775461, 'assetId': 'MA4B66MW5E27UANLXU9', 'bbid': 'TWTR UN', 'currency': 'USD', 'industry': 'Interactive Media & Services', 'marginalCost': 0.015894775099317016, 'advPercentage': 1e-06, 'notional': -596.5143809426665, 'price': 67.22, 'weight': -0.012556468666054534, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Wells Fargo & Co', 'transactionCost': 1.3822112533240394, 'sector': 'Financials', 'shares': -43.79430107894055, 'assetId': 'MA4B66MW5E27UANT8X5', 'bbid': 'WFC UN', 'currency': 'USD', 'industry': 'Banks', 'marginalCost': 0.061059807755110544, 'advPercentage': 2e-06, 'notional': -2098.622907702831, 'price': 47.92, 'weight': -0.044175452636686034, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Dentsply Sirona Inc', 'transactionCost': 2.9051536321640015, 'sector': 'Health Care', 'shares': -5.308036491917078, 'assetId': 'MA4B66MW5E27UANZH4M', 'bbid': 'XRAY UW', 'currency': 'USD', 'industry': 'Health Care Equipment & Supplies', 'marginalCost': 0.01947929477213156, 'advPercentage': 5e-06, 'notional': -318.5352698799438, 'price': 60.01, 'weight': -0.006705082497692817, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Meta Platforms Inc-Class A', 'transactionCost': 0.8238705992698669, 'sector': 'Communication Services', 'shares': -0.7130049017966985, 'assetId': 'MA4B66MW5E3VLSECN', 'bbid': 'FB UW', 'currency': 'USD', 'industry': 'Interactive Media & Services', 'marginalCost': 0.004364390584540722, 'advPercentage': 0, 'notional': -251.6622101381627, 'price': 352.96, 'weight': -0.005297422420958517, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Tapestry Inc', 'transactionCost': 2.314427137374878, 'sector': 'Consumer Discretionary', 'shares': -7.397050853562664, 'assetId': 'MAAHJ4JYCKZ6P025', 'bbid': 'TPR UN', 'currency': 'USD', 'industry': 'Textiles, Apparel & Luxury Goods', 'marginalCost': 0.014220203034272804, 'advPercentage': 2e-06, 'notional': -291.88762668158273, 'price': 39.46, 'weight': -0.006144156713614223, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Lumen Technologies Inc', 'transactionCost': 3.95284104347229, 'sector': 'Communication Services', 'shares': -11.81608123370237, 'assetId': 'MAQQM1QHGJDFAVKN', 'bbid': 'LUMN UN', 'currency': 'USD', 'industry': 'Diversified Telecommunication Services', 'marginalCost': 0.012712411491036973, 'advPercentage': 1e-06, 'notional': -152.78193035177165, 'price': 12.93, 'weight': -0.003216018896593429, 'borrowCost': 40, }, ], 'transactionCost': 2.5770571473071144, }, 'hedge': { 'transactionCost': 2.2604819652681694, 'correlation': 0.936758446681233, 'backtestPerformance': [ ['2020-09-24', 1], ['2020-09-25', 1.0104116399850593], ['2020-09-28', 1.0307855197014755], ['2020-09-29', 1.0163931905275039], ['2020-09-30', 1.0303780678487708], ['2020-10-01', 1.02803072573049], ['2020-10-02', 1.0338706383971734], ['2020-10-05', 1.052525377568271], ['2020-10-06', 1.0340650510786302], ['2020-10-07', 1.0508356048581615], ['2020-10-08', 1.0603036151113474], ['2020-10-09', 1.060588304786846], ['2020-10-12', 1.0868635890594514], ['2020-10-13', 1.0747102379748288], ['2020-10-14', 1.0671267966260976], ['2020-10-15', 1.074639084395838], ['2020-10-16', 1.0765144407834903], ['2020-10-19', 1.0626904612955628], ['2020-10-20', 1.0698992568123844], ['2020-10-21', 1.0633001027846625], ['2020-10-22', 1.080365958503925], ['2020-10-23', 1.0836535809584429], ['2020-10-26', 1.0599791557462024], ['2020-10-27', 1.0398109665270256], ['2020-10-28', 1.013939716813292], ['2020-10-29', 1.025285647916773], ['2020-10-30', 1.0162452634880497], ['2020-11-02', 1.0361270021554296], ['2020-11-03', 1.0628292713233702], ['2020-11-04', 1.0643748693020345], ['2020-11-05', 1.0870688878363275], ['2020-11-06', 1.075680915811229], ['2020-11-09', 1.1310152350427867], ['2020-11-10', 1.1365085766625056], ['2020-11-11', 1.1456684425171284], ['2020-11-12', 1.1324848780700123], ['2020-11-13', 1.1496769513131484], ['2020-11-16', 1.174024423737768], ['2020-11-17', 1.1786976021533382], ['2020-11-18', 1.172198105516616], ['2020-11-19', 1.1796894313970685], ['2020-11-20', 1.1720675902607598], ['2020-11-23', 1.2042568842777668], ['2020-11-24', 1.2478109612205204], ['2020-11-25', 1.2465964960297171], ['2020-11-26', 1.2465964960297171], ['2020-11-27', 1.2482807167565497], ['2020-11-30', 1.2240604489911184], ['2020-12-01', 1.2414716222521844], ['2020-12-02', 1.2555764585251266], ['2020-12-03', 1.2530206749134576], ['2020-12-04', 1.2726021013511315], ['2020-12-07', 1.2651506324580064], ['2020-12-08', 1.2642771975229015], ['2020-12-09', 1.2678474010781646], ['2020-12-10', 1.2736979737616583], ['2020-12-11', 1.2573160543347301], ['2020-12-14', 1.244303488883378], ['2020-12-15', 1.2742786174980252], ['2020-12-16', 1.2779195844041837], ['2020-12-17', 1.283302631746248], ['2020-12-18', 1.276354699607117], ['2020-12-21', 1.3125686927823437], ['2020-12-22', 1.296299249328958], ['2020-12-23', 1.3187779432834859], ['2020-12-24', 1.3162583329954654], ['2020-12-25', 1.3162583329954654], ['2020-12-28', 1.3185892862798951], ['2020-12-29', 1.3111756560815606], ['2020-12-30', 1.3144825720696882], ['2020-12-31', 1.3268030683678376], ['2021-01-01', 1.3268030683678376], ['2021-01-04', 1.3159923682994483], ['2021-01-05', 1.3340647632947369], ['2021-01-06', 1.3909576803980703], ['2021-01-07', 1.4152280361405745], ['2021-01-08', 1.4174405809398316], ['2021-01-11', 1.4231526927862412], ['2021-01-12', 1.4293977756277825], ['2021-01-13', 1.4234850492339033], ['2021-01-14', 1.4397514671138192], ['2021-01-15', 1.409001702900938], ['2021-01-18', 1.409001702900938], ['2021-01-19', 1.4109828849695718], ['2021-01-20', 1.4115393763475617], ['2021-01-21', 1.4044664175144592], ['2021-01-22', 1.3987852008396073], ['2021-01-25', 1.3860808014247046], ['2021-01-26', 1.381350320662177], ['2021-01-27', 1.3454054796675197], ['2021-01-28', 1.3501857783066038], ['2021-01-29', 1.3175753005863033], ['2021-02-01', 1.3310146449999998], ['2021-02-02', 1.370111970146903], ['2021-02-03', 1.369846444923827], ['2021-02-04', 1.3936530582244067], ['2021-02-05', 1.4039277423247092], ['2021-02-08', 1.4268063574012315], ['2021-02-09', 1.4283534342175326], ['2021-02-10', 1.4357594389261352], ['2021-02-11', 1.428374052139035], ['2021-02-12', 1.4429251243112822], ['2021-02-15', 1.4429251243112822], ['2021-02-16', 1.461542741076252], ['2021-02-17', 1.4591638016742965], ['2021-02-18', 1.4472594444672997], ['2021-02-19', 1.4707489985599904], ['2021-02-22', 1.4755439199336886], ['2021-02-23', 1.4820692085583076], ['2021-02-24', 1.511687900368849], ['2021-02-25', 1.486336535951723], ['2021-02-26', 1.4617866716883874], ['2021-03-01', 1.5001983608496483], ['2021-03-02', 1.5085260984949034], ['2021-03-03', 1.5167425010055402], ['2021-03-04', 1.4954468749220593], ['2021-03-05', 1.5048325007869152], ['2021-03-08', 1.5144817178954568], ['2021-03-09', 1.5136338082053342], ['2021-03-10', 1.5471387857165224], ['2021-03-11', 1.552032591486371], ['2021-03-12', 1.5716887223548588], ['2021-03-15', 1.570807534341104], ['2021-03-16', 1.5528662067408492], ['2021-03-17', 1.5663385974304973], ['2021-03-18', 1.5673612323097021], ['2021-03-19', 1.5525802635601895], ['2021-03-22', 1.5409068482642276], ['2021-03-23', 1.507406937003066], ['2021-03-24', 1.5012740563038809], ['2021-03-25', 1.5192040583357755], ['2021-03-26', 1.529957344137103], ['2021-03-29', 1.5047630491396355], ['2021-03-30', 1.5222775022723902], ['2021-03-31', 1.508553987492476], ['2021-04-01', 1.5207815572746632], ['2021-04-02', 1.5207815572746632], ['2021-04-05', 1.5252366638449892], ['2021-04-06', 1.537238350504828], ['2021-04-07', 1.5392951360957752], ['2021-04-08', 1.5490368365166685], ['2021-04-09', 1.557915030807923], ['2021-04-12', 1.5565394043984706], ['2021-04-13', 1.5460141095481092], ['2021-04-14', 1.5595020548937684], ['2021-04-15', 1.565231199487692], ['2021-04-16', 1.5557047184416664], ['2021-04-19', 1.5616881940098946], ['2021-04-20', 1.5263160854925413], ['2021-04-21', 1.5522763213884536], ['2021-04-22', 1.5350915789121093], ['2021-04-23', 1.57158539249219], ['2021-04-26', 1.5810581974026183], ['2021-04-27', 1.5900787413814923], ['2021-04-28', 1.5955846620747325], ['2021-04-29', 1.6137105295110301], ['2021-04-30', 1.5845767863905935], ['2021-05-03', 1.5863705380999724], ['2021-05-04', 1.5882125781111578], ['2021-05-05', 1.6065603704288025], ['2021-05-06', 1.6344953097116117], ['2021-05-07', 1.6495251089353995], ['2021-05-10', 1.636644671135531], ['2021-05-11', 1.6146185343277637], ['2021-05-12', 1.5897950933090927], ['2021-05-13', 1.6122368879515665], ['2021-05-14', 1.6439450665420194], ['2021-05-17', 1.6545198576851718], ['2021-05-18', 1.6353279676761365], ['2021-05-19', 1.6209690936595618], ['2021-05-20', 1.6294848988625292], ['2021-05-21', 1.6477823771244906], ['2021-05-24', 1.665391178904659], ['2021-05-25', 1.6543811334627772], ['2021-05-26', 1.6631885248580318], ['2021-05-27', 1.6784653130973213], ['2021-05-28', 1.6828566766917903], ['2021-05-31', 1.6828566766917903], ['2021-06-01', 1.69991088376666], ['2021-06-02', 1.7039576789378863], ['2021-06-03', 1.7075602408335289], ['2021-06-04', 1.7186255598839562], ['2021-06-07', 1.712635392985795], ['2021-06-08', 1.7097444780540265], ['2021-06-09', 1.7005645001567697], ['2021-06-10', 1.6846521736086526], ['2021-06-11', 1.6913655615016792], ['2021-06-14', 1.6741903605872415], ['2021-06-15', 1.6671331966653065], ['2021-06-16', 1.6608467705374614], ['2021-06-17', 1.6182529345083716], ['2021-06-18', 1.5716734295918893], ['2021-06-21', 1.6025210119753992], ['2021-06-22', 1.6036761759261928], ['2021-06-23', 1.6088186017605215], ['2021-06-24', 1.6298499404572735], ['2021-06-25', 1.6475608618707231], ['2021-06-28', 1.6436992303035847], ['2021-06-29', 1.6583865823631618], ['2021-06-30', 1.6717505836553197], ['2021-07-01', 1.6772597783846594], ['2021-07-02', 1.6765190560701135], ['2021-07-05', 1.6765190560701135], ['2021-07-06', 1.6472838625986161], ['2021-07-07', 1.6456960834251377], ['2021-07-08', 1.6125483519004047], ['2021-07-09', 1.6558213788094323], ['2021-07-12', 1.683903334102053], ['2021-07-13', 1.6760296033419682], ['2021-07-14', 1.6751081114066049], ['2021-07-15', 1.6761821265670138], ['2021-07-16', 1.6519994975324561], ['2021-07-19', 1.6185386068423806], ['2021-07-20', 1.6608158082138411], ['2021-07-21', 1.6977232240293918], ['2021-07-22', 1.6910789251625107], ['2021-07-23', 1.6882778604361217], ['2021-07-26', 1.7079461771654998], ['2021-07-27', 1.705316911290549], ['2021-07-28', 1.7065513855574908], ['2021-07-29', 1.7147564423541488], ['2021-07-30', 1.7078630854421146], ['2021-08-02', 1.7098846370921508], ['2021-08-03', 1.7212246073309094], ['2021-08-04', 1.71284744352946], ['2021-08-05', 1.7319984849390486], ['2021-08-06', 1.7610784608955359], ['2021-08-09', 1.7685063176313183], ['2021-08-10', 1.788487648029903], ['2021-08-11', 1.8132548730727573], ['2021-08-12', 1.8211088797799007], ['2021-08-13', 1.80763324042271], ['2021-08-16', 1.7990148335838823], ['2021-08-17', 1.7821201492173437], ['2021-08-18', 1.7595999866259955], ['2021-08-19', 1.7402030612253945], ['2021-08-20', 1.75394804825027], ['2021-08-23', 1.774564369295308], ['2021-08-24', 1.791036747026845], ['2021-08-25', 1.808387370019545], ['2021-08-26', 1.7990128100684075], ['2021-08-27', 1.8230267889371115], ['2021-08-30', 1.8066029024558345], ['2021-08-31', 1.8011083700062123], ['2021-09-01', 1.7952270192942026], ['2021-09-02', 1.8042111959545828], ['2021-09-03', 1.7956456571882038], ['2021-09-06', 1.7956456571882038], ['2021-09-07', 1.7906973619966717], ['2021-09-08', 1.7790846820103143], ['2021-09-09', 1.782974909163946], ['2021-09-10', 1.774210004132321], ['2021-09-13', 1.7863371703894837], ['2021-09-14', 1.7607086022595286], ['2021-09-15', 1.7733155737318977], ['2021-09-16', 1.7661953659616862], ['2021-09-17', 1.75552495625101], ['2021-09-20', 1.7073319409830758], ['2021-09-21', 1.7036755701399784], ['2021-09-22', 1.7353221001527805], ['2021-09-23', 1.7749324312405845], ['2021-09-24', 1.772300109946803], ], 'cashWeight': 0, 'rSquared': 0.8279135592848923, 'annualizedReturn': 0.77299493701611, 'annualizedVolatility': 0.19788814828229934, 'hedgeRatio': 1, 'ridgeWeight': 0, 'backtestCorrelation': [ ['2020-11-26', 0.9015925151077675], ['2020-11-27', 0.8995307601350195], ['2020-11-30', 0.8976193597009464], ['2020-12-01', 0.899134905548527], ['2020-12-02', 0.8968844291011409], ['2020-12-03', 0.8983210353924714], ['2020-12-04', 0.8983135812975858], ['2020-12-07', 0.8981598956869762], ['2020-12-08', 0.896541180905539], ['2020-12-09', 0.896342085151086], ['2020-12-10', 0.8960079549884281], ['2020-12-11', 0.9022159150284723], ['2020-12-14', 0.9020984285750759], ['2020-12-15', 0.9108756715722117], ['2020-12-16', 0.9216167275658368], ['2020-12-17', 0.9283890799097579], ['2020-12-18', 0.9282606242969395], ['2020-12-21', 0.9168926673056201], ['2020-12-22', 0.9235486508155735], ['2020-12-23', 0.9276013093341673], ['2020-12-24', 0.9283372838050917], ['2020-12-25', 0.928171587486031], ['2020-12-28', 0.9116851149928049], ['2020-12-29', 0.9056422674201283], ['2020-12-30', 0.9059781317532566], ['2020-12-31', 0.9052396831652074], ['2021-01-01', 0.9155947769421426], ['2021-01-04', 0.9111654399535882], ['2021-01-05', 0.9125433947950563], ['2021-01-06', 0.9209363905792434], ['2021-01-07', 0.9201318271791072], ['2021-01-08', 0.9084929198870123], ['2021-01-11', 0.9086559298840635], ['2021-01-12', 0.8875612269095221], ['2021-01-13', 0.8789265648137641], ['2021-01-14', 0.8783291410335801], ['2021-01-15', 0.8943005663624369], ['2021-01-18', 0.8945248751426272], ['2021-01-19', 0.8705151985771736], ['2021-01-20', 0.8708271027477481], ['2021-01-21', 0.8650473745733117], ['2021-01-22', 0.8748613614663985], ['2021-01-25', 0.8725792954791373], ['2021-01-26', 0.873175463509665], ['2021-01-27', 0.8864366140951857], ['2021-01-28', 0.8845759397275269], ['2021-01-29', 0.8898805771751356], ['2021-02-01', 0.8920000543343564], ['2021-02-02', 0.9019976775814598], ['2021-02-03', 0.9029180582695069], ['2021-02-04', 0.9070217939463278], ['2021-02-05', 0.9007086970184902], ['2021-02-08', 0.9030258129623466], ['2021-02-09', 0.9032264783085702], ['2021-02-10', 0.9030829600702234], ['2021-02-11', 0.9010523188673754], ['2021-02-12', 0.9001497414661194], ['2021-02-15', 0.8972232205299802], ['2021-02-16', 0.897594089152695], ['2021-02-17', 0.8982148727150193], ['2021-02-18', 0.8998472164172846], ['2021-02-19', 0.8982607278993444], ['2021-02-22', 0.8921722036271721], ['2021-02-23', 0.8898626754094953], ['2021-02-24', 0.8947739256523193], ['2021-02-25', 0.8987596973059994], ['2021-02-26', 0.9134643435214955], ['2021-03-01', 0.918993929754835], ['2021-03-02', 0.9157408336361909], ['2021-03-03', 0.915275687497752], ['2021-03-04', 0.9186455889944436], ['2021-03-05', 0.9145426609376325], ['2021-03-08', 0.9134714758069424], ['2021-03-09', 0.9086630053014179], ['2021-03-10', 0.9120223954384556], ['2021-03-11', 0.9130148169582306], ['2021-03-12', 0.914739719013808], ['2021-03-15', 0.928463943323836], ['2021-03-16', 0.933583077072683], ['2021-03-17', 0.9330716807868986], ['2021-03-18', 0.9288147179435885], ['2021-03-19', 0.9303048507822332], ['2021-03-22', 0.9503491605860712], ['2021-03-23', 0.9543301426608973], ['2021-03-24', 0.9562100276004051], ['2021-03-25', 0.9558902159462764], ['2021-03-26', 0.9421183812187457], ['2021-03-29', 0.9304297698582624], ['2021-03-30', 0.9255377724225964], ['2021-03-31', 0.9288788520748432], ['2021-04-01', 0.9255245811924869], ['2021-04-02', 0.9253057356272613], ['2021-04-05', 0.9110845996065523], ['2021-04-06', 0.9125537209614435], ['2021-04-07', 0.9092805473148023], ['2021-04-08', 0.9106842967282306], ['2021-04-09', 0.9065743602103377], ['2021-04-12', 0.9064120996162333], ['2021-04-13', 0.9080039410233919], ['2021-04-14', 0.9058324442791906], ['2021-04-15', 0.9012633679437755], ['2021-04-16', 0.8817266337950646], ['2021-04-19', 0.879870971242904], ['2021-04-20', 0.8941573383350782], ['2021-04-21', 0.887962814825184], ['2021-04-22', 0.8883257914187292], ['2021-04-23', 0.8982033834669962], ['2021-04-26', 0.8978600104298144], ['2021-04-27', 0.8927525434765888], ['2021-04-28', 0.8906790878648955], ['2021-04-29', 0.8850838636802095], ['2021-04-30', 0.8752084867429241], ['2021-05-03', 0.8772331412633768], ['2021-05-04', 0.8720331661943858], ['2021-05-05', 0.8671463406412616], ['2021-05-06', 0.8823932789430973], ['2021-05-07', 0.8839329350180696], ['2021-05-10', 0.8866611547123624], ['2021-05-11', 0.8809173134162849], ['2021-05-12', 0.8872869281730444], ['2021-05-13', 0.8869823508564784], ['2021-05-14', 0.8953095635961708], ['2021-05-17', 0.8900795744712166], ['2021-05-18', 0.8932618180743701], ['2021-05-19', 0.8948100995042912], ['2021-05-20', 0.8934110525145609], ['2021-05-21', 0.894275704465274], ['2021-05-24', 0.8862129378804093], ['2021-05-25', 0.8887466640145859], ['2021-05-26', 0.8922999893294049], ['2021-05-27', 0.9071416941600038], ['2021-05-28', 0.9203476398072666], ['2021-05-31', 0.9190730350667695], ['2021-06-01', 0.9102728877414129], ['2021-06-02', 0.9132534478383805], ['2021-06-03', 0.9088133196000899], ['2021-06-04', 0.9156681278113125], ['2021-06-07', 0.9156223123920523], ['2021-06-08', 0.9145856716019339], ['2021-06-09', 0.9168585582176089], ['2021-06-10', 0.9163403851492076], ['2021-06-11', 0.9144967572849609], ['2021-06-14', 0.9141431719872722], ['2021-06-15', 0.9147777043798121], ['2021-06-16', 0.915538259640843], ['2021-06-17', 0.9303937239900952], ['2021-06-18', 0.9390592485374359], ['2021-06-21', 0.9373116830433493], ['2021-06-22', 0.9427449880436689], ['2021-06-23', 0.9407084724464195], ['2021-06-24', 0.9394464162725222], ['2021-06-25', 0.9280066991265317], ['2021-06-28', 0.9265038781758507], ['2021-06-29', 0.9275818290399418], ['2021-06-30', 0.9255991162602577], ['2021-07-01', 0.9138966034117193], ['2021-07-02', 0.9146142973869088], ['2021-07-05', 0.9181022970358077], ['2021-07-06', 0.9074933619483999], ['2021-07-07', 0.902025125731612], ['2021-07-08', 0.907076026609854], ['2021-07-09', 0.9174277412353735], ['2021-07-12', 0.9192217122691708], ['2021-07-13', 0.9155688050636652], ['2021-07-14', 0.914511919994974], ['2021-07-15', 0.9072827624461118], ['2021-07-16', 0.9147251729875863], ['2021-07-19', 0.9193336264588486], ['2021-07-20', 0.925798773703205], ['2021-07-21', 0.9267409583789746], ['2021-07-22', 0.9246635419626583], ['2021-07-23', 0.9235962637255246], ['2021-07-26', 0.9187195464946155], ['2021-07-27', 0.9179631491889088], ['2021-07-28', 0.9169395326411997], ['2021-07-29', 0.9174199380790581], ['2021-07-30', 0.9176683013254561], ['2021-08-02', 0.92034668699813], ['2021-08-03', 0.9207308184465794], ['2021-08-04', 0.9241614525891176], ['2021-08-05', 0.9252544039117194], ['2021-08-06', 0.9257068761979128], ['2021-08-09', 0.9269338494497388], ['2021-08-10', 0.9279078169690388], ['2021-08-11', 0.9320239670516519], ['2021-08-12', 0.9336249338456267], ['2021-08-13', 0.9340825531336873], ['2021-08-16', 0.9337740851496074], ['2021-08-17', 0.9352358574622675], ['2021-08-18', 0.9372444096991827], ['2021-08-19', 0.9298764811116637], ['2021-08-20', 0.9262131482920518], ['2021-08-23', 0.9276585645717137], ['2021-08-24', 0.92856395669229], ['2021-08-25', 0.9271659353349381], ['2021-08-26', 0.9366798112705848], ['2021-08-27', 0.939469558627736], ['2021-08-30', 0.939741517191791], ['2021-08-31', 0.9416728966796483], ['2021-09-01', 0.9523441724282465], ['2021-09-02', 0.9517066086773255], ['2021-09-03', 0.9520676724175039], ['2021-09-06', 0.9609901628723267], ['2021-09-07', 0.9600864061003563], ['2021-09-08', 0.9558863980743179], ['2021-09-09', 0.948472236169008], ['2021-09-10', 0.946294665243798], ['2021-09-13', 0.9452172483227433], ['2021-09-14', 0.9466756455002099], ['2021-09-15', 0.937395558117138], ['2021-09-16', 0.931622601567483], ['2021-09-17', 0.9210569221798822], ['2021-09-20', 0.9284521728561184], ['2021-09-21', 0.9272930667016914], ['2021-09-22', 0.9352279628015158], ['2021-09-23', 0.9315300597142235], ['2021-09-24', 0.936758446681233], ], 'trackingError': 0.09724809864706382, 'lassoWeight': 0, 'constituents': [ { 'country': 'United States', 'name': 'Dentsply Sirona Inc', 'transactionCost': 2.9051536321640015, 'sector': 'Health Care', 'correlation': 0.16678359890783784, 'shares': -5.308036491917078, 'assetId': 'MA4B66MW5E27UANZH4M', 'bbid': 'XRAY UW', 'currency': 'USD', 'industry': 'Health Care Equipment & Supplies', 'marginalCost': 0.03895858954426313, 'advPercentage': 5e-06, 'notional': -318.5352698799438, 'price': 60.01, 'weight': 0.013410164995385637, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Extra Space Storage Inc', 'transactionCost': 3.9990124702453613, 'sector': 'Real Estate', 'correlation': 0.06540203064772898, 'shares': -3.1030213327842304, 'assetId': 'MA4B66MW5E27UAFU2CS', 'bbid': 'EXR UN', 'currency': 'USD', 'industry': 'Equity Real Estate Investment Trusts (REITs)', 'marginalCost': 0.09017373674086719, 'advPercentage': 5e-06, 'notional': -535.6125122518861, 'price': 172.61, 'weight': 0.02254900113760699, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Kroger Co', 'transactionCost': 1.2731897830963135, 'sector': 'Consumer Staples', 'correlation': 0.12350867819558603, 'shares': -1.730011893560012, 'assetId': 'MA4B66MW5E27UAKNZNV', 'bbid': 'KR UN', 'currency': 'USD', 'industry': 'Food & Staples Retailing', 'marginalCost': 0.003707333181127408, 'advPercentage': 0, 'notional': -69.16587550452927, 'price': 39.98, 'weight': 0.002911846474381391, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'MSCI Inc', 'transactionCost': 7.908167362213135, 'sector': 'Financials', 'correlation': 0.24284241634131032, 'shares': -0.5740039461869635, 'assetId': 'MA4B66MW5E27UAL9SUH', 'bbid': 'MSCI UN', 'currency': 'USD', 'industry': 'Capital Markets', 'marginalCost': 0.1263897386589486, 'advPercentage': 2e-06, 'notional': -379.628989889672, 'price': 661.37, 'weight': 0.01598217802810611, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'General Motors Co', 'transactionCost': 1.0103281140327454, 'sector': 'Consumer Discretionary', 'correlation': 0.5028570129890573, 'shares': -3.0290208240423557, 'assetId': 'MA4B66MW5E27UAGYYVE', 'bbid': 'GM UN', 'currency': 'USD', 'industry': 'Automobiles', 'marginalCost': 0.006729167173415375, 'advPercentage': 0, 'notional': -158.20575763973224, 'price': 52.23, 'weight': 0.0066603780296242265, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Mosaic Co/the', 'transactionCost': 2.1279181241989136, 'sector': 'Materials', 'correlation': 0.6130275473664366, 'shares': -5.192035694429817, 'assetId': 'MA4B66MW5E27UAL9SLW', 'bbid': 'MOS UN', 'currency': 'USD', 'industry': 'Chemicals', 'marginalCost': 0.016679363095866095, 'advPercentage': 2e-06, 'notional': -186.18640000225324, 'price': 35.86, 'weight': 0.007838348151738823, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Meta Platforms Inc-Class A', 'transactionCost': 0.8238705992698669, 'sector': 'Communication Services', 'correlation': 0.12666374147291945, 'shares': -0.7130049017966985, 'assetId': 'MA4B66MW5E3VLSECN', 'bbid': 'FB UW', 'currency': 'USD', 'industry': 'Interactive Media & Services', 'marginalCost': 0.008728781169081446, 'advPercentage': 0, 'notional': -251.6622101381627, 'price': 352.96, 'weight': 0.010594844841917037, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Twitter Inc', 'transactionCost': 1.2658634781837463, 'sector': 'Communication Services', 'correlation': 0.15422853187220908, 'shares': -8.874061007775461, 'assetId': 'MA4B66MW5E27UANLXU9', 'bbid': 'TWTR UN', 'currency': 'USD', 'industry': 'Interactive Media & Services', 'marginalCost': 0.03178955019863404, 'advPercentage': 1e-06, 'notional': -596.5143809426665, 'price': 67.22, 'weight': 0.025112937332109075, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Paypal Holdings Inc', 'transactionCost': 1.4358487129211426, 'sector': 'Information Technology', 'correlation': 0.22583565660778615, 'shares': -0.5480037674398188, 'assetId': 'MA4B66MW5E27UAM94N9', 'bbid': 'PYPL UW', 'currency': 'USD', 'industry': 'IT Services', 'marginalCost': 0.009212668138339708, 'advPercentage': 0, 'notional': -152.40532776268802, 'price': 278.11, 'weight': 0.006416183025018788, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Hormel Foods Corp', 'transactionCost': 1.4352393746376038, 'sector': 'Consumer Staples', 'correlation': -0.03313295634963935, 'shares': -11.239077266890737, 'assetId': 'MA4B66MW5E27UAJ5XEZ', 'bbid': 'HRL UN', 'currency': 'USD', 'industry': 'Food Products', 'marginalCost': 0.027707143568185242, 'advPercentage': 4e-06, 'notional': -458.55435248914205, 'price': 40.8, 'weight': 0.019304893704704323, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Nucor Corp', 'transactionCost': 3.182479500770569, 'sector': 'Materials', 'correlation': 0.556129972455637, 'shares': -0.7970054792874738, 'assetId': 'MA4B66MW5E27UALNB48', 'bbid': 'NUE UN', 'currency': 'USD', 'industry': 'Metals & Mining', 'marginalCost': 0.010776575069254387, 'advPercentage': 0, 'notional': -80.43379296969185, 'price': 100.92, 'weight': 0.0033862197907779373, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Wells Fargo & Co', 'transactionCost': 1.3822112533240394, 'sector': 'Financials', 'correlation': 0.6178643696685713, 'shares': -43.79430107894055, 'assetId': 'MA4B66MW5E27UANT8X5', 'bbid': 'WFC UN', 'currency': 'USD', 'industry': 'Banks', 'marginalCost': 0.12211961551022109, 'advPercentage': 2e-06, 'notional': -2098.622907702831, 'price': 47.92, 'weight': 0.0883509052733721, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Cummins Inc', 'transactionCost': 3.3661617040634155, 'sector': 'Industrials', 'correlation': 0.6412824675287682, 'shares': -0.4130028393296445, 'assetId': 'MA4B66MW5E27UACX6RZ', 'bbid': 'CMI UN', 'currency': 'USD', 'industry': 'Machinery', 'marginalCost': 0.013324512800464238, 'advPercentage': 0, 'notional': -94.02422640178686, 'price': 227.66, 'weight': 0.003958369790845086, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'JPMorgan Chase & Co', 'transactionCost': 1.2586397729172192, 'sector': 'Financials', 'correlation': 0.8505015474586123, 'shares': -12.604086651115832, 'assetId': 'MA4B66MW5E27UAKHZFD', 'bbid': 'JPM UN', 'currency': 'USD', 'industry': 'Banks', 'marginalCost': 0.10888889555559653, 'advPercentage': 1e-06, 'notional': -2054.970287597925, 'price': 163.04, 'weight': 0.08651315324576048, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Lumen Technologies Inc', 'transactionCost': 3.95284104347229, 'sector': 'Communication Services', 'correlation': 0.35956668091011157, 'shares': -11.81608123370237, 'assetId': 'MAQQM1QHGJDFAVKN', 'bbid': 'LUMN UN', 'currency': 'USD', 'industry': 'Diversified Telecommunication Services', 'marginalCost': 0.02542482298207395, 'advPercentage': 1e-06, 'notional': -152.78193035177165, 'price': 12.93, 'weight': 0.00643203779318686, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Advanced Micro Devices', 'transactionCost': 0.5830498039722443, 'sector': 'Information Technology', 'correlation': 0.12884947696795268, 'shares': -0.8120055824108264, 'assetId': 'MA4B66MW5E27U9YGMB9', 'bbid': 'AMD UW', 'currency': 'USD', 'industry': 'Semiconductors & Semiconductor Equipment', 'marginalCost': 0.0021087589119167267, 'advPercentage': 0, 'notional': -85.91019061906543, 'price': 105.8, 'weight': 0.003616773211396386, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Morgan Stanley', 'transactionCost': 2.3784881788491172, 'sector': 'Financials', 'correlation': 0.9121008220093567, 'shares': -100.16368860962515, 'assetId': 'MA4B66MW5E27UAL9STW', 'bbid': 'MS UN', 'currency': 'USD', 'industry': 'Capital Markets', 'marginalCost': 1.0321563281719852, 'advPercentage': 1.6e-05, 'notional': -10307.845194816524, 'price': 102.91, 'weight': 0.43395478579650393, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Citigroup Inc', 'transactionCost': 1.4510516652072596, 'sector': 'Financials', 'correlation': 0.8189307897328992, 'shares': -27.227187182634943, 'assetId': 'MA4B66MW5E27UA4ZCWT', 'bbid': 'C UN', 'currency': 'USD', 'industry': 'Banks', 'marginalCost': 0.11839142047698586, 'advPercentage': 2e-06, 'notional': -1938.0311836599553, 'price': 71.18, 'weight': 0.08159007933054924, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Duke Realty Corp', 'transactionCost': 1.3627572059631348, 'sector': 'Real Estate', 'correlation': 0.1248871579141672, 'shares': -8.864060939026558, 'assetId': 'MA4B66MW5E27UAEP46J', 'bbid': 'DRE UN', 'currency': 'USD', 'industry': 'Equity Real Estate Investment Trusts (REITs)', 'marginalCost': 0.02484233322743233, 'advPercentage': 5e-06, 'notional': -433.0093768714474, 'price': 48.85, 'weight': 0.0182294638536693, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Electronic Arts Inc', 'transactionCost': 2.1669615507125854, 'sector': 'Communication Services', 'correlation': 0.18457789391303997, 'shares': -6.899047429867354, 'assetId': 'MA4B66MW5E27UAEP4H9', 'bbid': 'EA UW', 'currency': 'USD', 'industry': 'Entertainment', 'marginalCost': 0.08175721364539484, 'advPercentage': 3e-06, 'notional': -896.1862611397693, 'price': 129.9, 'weight': 0.037728963681201336, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Qualcomm Inc', 'transactionCost': 1.0424676537513733, 'sector': 'Information Technology', 'correlation': 0.37498752971441385, 'shares': -0.6250042968063627, 'assetId': 'MA4B66MW5E27UAM94Q4', 'bbid': 'QCOM UW', 'currency': 'USD', 'industry': 'Semiconductors & Semiconductor Equipment', 'marginalCost': 0.0036722977768481185, 'advPercentage': 0, 'notional': -83.67557525643583, 'price': 133.88, 'weight': 0.00352269709629183, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Albemarle Corp', 'transactionCost': 5.661775350570679, 'sector': 'Materials', 'correlation': 0.572692690460937, 'shares': -2.9210200815542167, 'assetId': 'MA4B66MW5E27U9XPVYM', 'bbid': 'ALB UN', 'currency': 'USD', 'industry': 'Chemicals', 'marginalCost': 0.157630478243818, 'advPercentage': 2e-06, 'notional': -661.3189464638747, 'price': 226.4, 'weight': 0.027841174981965627, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Tractor Supply Company', 'transactionCost': 3.904104232788086, 'sector': 'Consumer Discretionary', 'correlation': 0.07218103540747003, 'shares': -0.49000336869618827, 'assetId': 'MA4B66MW5E27UANEQ5C', 'bbid': 'TSCO UW', 'currency': 'USD', 'industry': 'Specialty Retail', 'marginalCost': 0.016970819801319783, 'advPercentage': 1e-06, 'notional': -103.2535098516608, 'price': 210.72, 'weight': 0.004346917702348384, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Davita Inc', 'transactionCost': 3.084942579269409, 'sector': 'Health Care', 'correlation': 0.20287653627709595, 'shares': -3.4880239796169494, 'assetId': 'MA4B66MW5E27UAEP4D8', 'bbid': 'DVA UN', 'currency': 'USD', 'industry': 'Health Care Providers & Services', 'marginalCost': 0.05481362341943657, 'advPercentage': 6e-06, 'notional': -422.05090153365086, 'price': 121, 'weight': 0.01776811788581744, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Citrix Systems Inc', 'transactionCost': 2.754676938056946, 'sector': 'Information Technology', 'correlation': -0.014004050045478335, 'shares': -2.204015152257958, 'assetId': 'MA4B66MW5E27UAE4LLL', 'bbid': 'CTXS UW', 'currency': 'USD', 'industry': 'Software', 'marginalCost': 0.028164628476876532, 'advPercentage': 2e-06, 'notional': -242.86042962730437, 'price': 110.19, 'weight': 0.010224294576170117, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Best Buy Co Inc', 'transactionCost': 2.401727557182312, 'sector': 'Consumer Discretionary', 'correlation': 0.45583415490693957, 'shares': -4.535031177626968, 'assetId': 'MA4B66MW5E27UA39J2L', 'bbid': 'BBY UN', 'currency': 'USD', 'industry': 'Specialty Retail', 'marginalCost': 0.04802785408186685, 'advPercentage': 2e-06, 'notional': -474.99916554464863, 'price': 104.74, 'weight': 0.019997211564750816, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Iron Mountain Inc', 'transactionCost': 2.417732357978821, 'sector': 'Real Estate', 'correlation': 0.4856832268445906, 'shares': -5.0810349313170065, 'assetId': 'MA4B66MW5E27UAJQF4M', 'bbid': 'IRM UN', 'currency': 'USD', 'industry': 'Equity Real Estate Investment Trusts (REITs)', 'marginalCost': 0.02289530957940409, 'advPercentage': 3e-06, 'notional': -224.93741640940388, 'price': 44.27, 'weight': 0.009469745277572472, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Tapestry Inc', 'transactionCost': 2.314427137374878, 'sector': 'Consumer Discretionary', 'correlation': 0.6555947816398136, 'shares': -7.397050853562664, 'assetId': 'MAAHJ4JYCKZ6P025', 'bbid': 'TPR UN', 'currency': 'USD', 'industry': 'Textiles, Apparel & Luxury Goods', 'marginalCost': 0.02844040606854561, 'advPercentage': 2e-06, 'notional': -291.88762668158273, 'price': 39.46, 'weight': 0.01228831342722845, 'borrowCost': 40, }, ], 'holdingError': 0.0744447922751884, }, 'benchmarks': [], 'target': { 'annualizedVolatility': 0.2245953385486387, 'backtestPerformance': [ ['2020-09-24', 1], ['2020-09-25', 1.0076306394460186], ['2020-09-28', 1.0295511006061422], ['2020-09-29', 1.0186692133468813], ['2020-09-30', 1.0388739557814626], ['2020-10-01', 1.0312010109422116], ['2020-10-02', 1.028915736274446], ['2020-10-05', 1.04359570772614], ['2020-10-06', 1.0339555621015752], ['2020-10-07', 1.0478951891716997], ['2020-10-08', 1.0651714582747391], ['2020-10-09', 1.067487286837617], ['2020-10-12', 1.1089121011443612], ['2020-10-13', 1.0889651082352152], ['2020-10-14', 1.0908265455374473], ['2020-10-15', 1.0793406312748108], ['2020-10-16', 1.066348958386379], ['2020-10-19', 1.0580790374426237], ['2020-10-20', 1.070545026648481], ['2020-10-21', 1.0487843858627914], ['2020-10-22', 1.0564518466695865], ['2020-10-23', 1.0535672455976932], ['2020-10-26', 1.0380051283537797], ['2020-10-27', 1.0193468830718104], ['2020-10-28', 0.9847340205087146], ['2020-10-29', 0.9938053936242638], ['2020-10-30', 0.977050891037759], ['2020-11-02', 0.9815023585256727], ['2020-11-03', 1.0157893128742363], ['2020-11-04', 1.0247361201097123], ['2020-11-05', 1.053383922227019], ['2020-11-06', 1.0458990013576899], ['2020-11-09', 1.0956900988927742], ['2020-11-10', 1.1051257684500482], ['2020-11-11', 1.114613144599051], ['2020-11-12', 1.0998987020862043], ['2020-11-13', 1.118260026182338], ['2020-11-16', 1.133563610467608], ['2020-11-17', 1.1407798137465894], ['2020-11-18', 1.1366111656467677], ['2020-11-19', 1.1389324782421015], ['2020-11-20', 1.1314099525787882], ['2020-11-23', 1.1461964709467716], ['2020-11-24', 1.1835262798752466], ['2020-11-25', 1.1814423475419396], ['2020-11-26', 1.1814423475419396], ['2020-11-27', 1.1780281456214312], ['2020-11-30', 1.163780629300559], ['2020-12-01', 1.1772494130126692], ['2020-12-02', 1.2002376936353105], ['2020-12-03', 1.191242313540938], ['2020-12-04', 1.2062583778388682], ['2020-12-07', 1.2047988417723452], ['2020-12-08', 1.207640354017798], ['2020-12-09', 1.2182464727878393], ['2020-12-10', 1.2275332900355913], ['2020-12-11', 1.2082224448913497], ['2020-12-14', 1.1981490607027552], ['2020-12-15', 1.2291134748161479], ['2020-12-16', 1.2343248725158311], ['2020-12-17', 1.2387747731373286], ['2020-12-18', 1.2254195872403797], ['2020-12-21', 1.287961060235829], ['2020-12-22', 1.2678667818835767], ['2020-12-23', 1.2914042491850333], ['2020-12-24', 1.292302847074621], ['2020-12-25', 1.292302847074621], ['2020-12-28', 1.315621736511042], ['2020-12-29', 1.3056016257805925], ['2020-12-30', 1.3090126939682685], ['2020-12-31', 1.3239355297144455], ['2021-01-01', 1.3239355297144455], ['2021-01-04', 1.3224086183920336], ['2021-01-05', 1.3493610710472062], ['2021-01-06', 1.3987925727398136], ['2021-01-07', 1.4319647016333785], ['2021-01-08', 1.4279934787019752], ['2021-01-11', 1.4367232749388321], ['2021-01-12', 1.469838996641421], ['2021-01-13', 1.4770128945271697], ['2021-01-14', 1.4926776415213325], ['2021-01-15', 1.4616630876826273], ['2021-01-18', 1.4616630876826273], ['2021-01-19', 1.4358591481103977], ['2021-01-20', 1.4295109888258917], ['2021-01-21', 1.4349746520185538], ['2021-01-22', 1.439535800155589], ['2021-01-25', 1.4220064821263625], ['2021-01-26', 1.4173810924662704], ['2021-01-27', 1.3814583295793816], ['2021-01-28', 1.3780872164853137], ['2021-01-29', 1.3522550733175958], ['2021-02-01', 1.3709195860652295], ['2021-02-02', 1.4215560080317566], ['2021-02-03', 1.4257301401640343], ['2021-02-04', 1.4535341847163137], ['2021-02-05', 1.4512520437813807], ['2021-02-08', 1.4781277199821679], ['2021-02-09', 1.4775330941772888], ['2021-02-10', 1.4915330456044296], ['2021-02-11', 1.4831722464085457], ['2021-02-12', 1.4996431461737505], ['2021-02-15', 1.4996431461737505], ['2021-02-16', 1.5177373195459527], ['2021-02-17', 1.5160670399464755], ['2021-02-18', 1.5024180665965226], ['2021-02-19', 1.5255982883551262], ['2021-02-22', 1.5227097701171932], ['2021-02-23', 1.5317740923338705], ['2021-02-24', 1.5764039318945835], ['2021-02-25', 1.5560158660893277], ['2021-02-26', 1.5234830186934991], ['2021-03-01', 1.5784972654263867], ['2021-03-02', 1.577134091644449], ['2021-03-03', 1.5848454247108927], ['2021-03-04', 1.56037723875832], ['2021-03-05', 1.5553334957651503], ['2021-03-08', 1.5722760223215777], ['2021-03-09', 1.5675166655829158], ['2021-03-10', 1.6109345339708374], ['2021-03-11', 1.6152872888745418], ['2021-03-12', 1.6402028151888879], ['2021-03-15', 1.6352045113217835], ['2021-03-16', 1.6210823443141926], ['2021-03-17', 1.6323778843072827], ['2021-03-18', 1.6359479894361848], ['2021-03-19', 1.619665113640902], ['2021-03-22', 1.6071325326123638], ['2021-03-23', 1.5751950944546225], ['2021-03-24', 1.5577386357137408], ['2021-03-25', 1.5663485666697723], ['2021-03-26', 1.5549856514207931], ['2021-03-29', 1.5487197526231282], ['2021-03-30', 1.5707765311614452], ['2021-03-31', 1.5553421135304377], ['2021-04-01', 1.5596306269110851], ['2021-04-02', 1.5596306269110851], ['2021-04-05', 1.5491561249199899], ['2021-04-06', 1.5638517650358437], ['2021-04-07', 1.565256460777806], ['2021-04-08', 1.5886066875425475], ['2021-04-09', 1.5926554703615445], ['2021-04-12', 1.593196039275071], ['2021-04-13', 1.5830725153611647], ['2021-04-14', 1.608829448940914], ['2021-04-15', 1.6266462869574803], ['2021-04-16', 1.6409768471984019], ['2021-04-19', 1.6454784544116277], ['2021-04-20', 1.5971649119068507], ['2021-04-21', 1.61150408991306], ['2021-04-22', 1.5906663334464066], ['2021-04-23', 1.6294760477048125], ['2021-04-26', 1.6469520922758942], ['2021-04-27', 1.6587059406956703], ['2021-04-28', 1.6629693842136615], ['2021-04-29', 1.6821838670733185], ['2021-04-30', 1.6600095735537992], ['2021-05-03', 1.6690417750089481], ['2021-05-04', 1.6582100274749996], ['2021-05-05', 1.6898043218876324], ['2021-05-06', 1.726507384249699], ['2021-05-07', 1.7471226456852775], ['2021-05-10', 1.7314484974926188], ['2021-05-11', 1.6945331247311817], ['2021-05-12', 1.6660819643490854], ['2021-05-13', 1.6881833985802592], ['2021-05-14', 1.7330302491395915], ['2021-05-17', 1.7325445205506251], ['2021-05-18', 1.7109131462442573], ['2021-05-19', 1.6858949901796618], ['2021-05-20', 1.6968254502978186], ['2021-05-21', 1.7188869294353843], ['2021-05-24', 1.730399480427093], ['2021-05-25', 1.7148890697749082], ['2021-05-26', 1.7263342455107282], ['2021-05-27', 1.738998443318212], ['2021-05-28', 1.7402307837544118], ['2021-05-31', 1.7402307837544118], ['2021-06-01', 1.7811918056020137], ['2021-06-02', 1.7867761175087098], ['2021-06-03', 1.8047762788959507], ['2021-06-04', 1.820470796352018], ['2021-06-07', 1.8073459398182243], ['2021-06-08', 1.795232495555971], ['2021-06-09', 1.7883555188560583], ['2021-06-10', 1.750957551238487], ['2021-06-11', 1.7699048833742117], ['2021-06-14', 1.7557834997998292], ['2021-06-15', 1.7475997565089554], ['2021-06-16', 1.7474399361345212], ['2021-06-17', 1.7128231564053855], ['2021-06-18', 1.6594909407700957], ['2021-06-21', 1.698039771770234], ['2021-06-22', 1.7014218529292136], ['2021-06-23', 1.71491570650398], ['2021-06-24', 1.7450904199436985], ['2021-06-25', 1.7445592522286677], ['2021-06-28', 1.7477211886561963], ['2021-06-29', 1.7665008660854071], ['2021-06-30', 1.795393099363613], ['2021-07-01', 1.7778849340310026], ['2021-07-02', 1.7800479931183184], ['2021-07-05', 1.7800479931183184], ['2021-07-06', 1.7670633711287582], ['2021-07-07', 1.7635073677976], ['2021-07-08', 1.7259174590440662], ['2021-07-09', 1.7809489413075301], ['2021-07-12', 1.8146271680534696], ['2021-07-13', 1.798889561770962], ['2021-07-14', 1.7997262684371165], ['2021-07-15', 1.7941662429598688], ['2021-07-16', 1.75574746187226], ['2021-07-19', 1.7074472377320196], ['2021-07-20', 1.755098779176028], ['2021-07-21', 1.7884918362342517], ['2021-07-22', 1.7881870807163354], ['2021-07-23', 1.7971260536197318], ['2021-07-26', 1.8053936242638626], ['2021-07-27', 1.7966364078647248], ['2021-07-28', 1.790553049004526], ['2021-07-29', 1.805162511467499], ['2021-07-30', 1.7949426252690068], ['2021-08-02', 1.807475206297545], ['2021-08-03', 1.8198934060777137], ['2021-08-04', 1.8090694928758457], ['2021-08-05', 1.835024635057221], ['2021-08-06', 1.8874496350376353], ['2021-08-09', 1.895298852348689], ['2021-08-10', 1.9266244291709742], ['2021-08-11', 1.9508074454358313], ['2021-08-12', 1.9614143476390804], ['2021-08-13', 1.9449810526678597], ['2021-08-16', 1.939386556129459], ['2021-08-17', 1.9239873929928122], ['2021-08-18', 1.8915336723509921], ['2021-08-19', 1.8713297133496192], ['2021-08-20', 1.8835544051274093], ['2021-08-23', 1.9089454753990176], ['2021-08-24', 1.937170223583999], ['2021-08-25', 1.952823219080041], ['2021-08-26', 1.9483161278343588], ['2021-08-27', 1.9795625779026347], ['2021-08-30', 1.964436832955583], ['2021-08-31', 1.96144960213344], ['2021-09-01', 1.9634340384493298], ['2021-09-02', 1.969112362340987], ['2021-09-03', 1.9576906896014072], ['2021-09-06', 1.9576906896014072], ['2021-09-07', 1.958603389288739], ['2021-09-08', 1.9341289358705023], ['2021-09-09', 1.9303324185444852], ['2021-09-10', 1.915549033909334], ['2021-09-13', 1.939824495292736], ['2021-09-14', 1.9146567034854105], ['2021-09-15', 1.9095581201676801], ['2021-09-16', 1.8880129235141936], ['2021-09-17', 1.8615955557400916], ['2021-09-20', 1.8019802057765602], ['2021-09-21', 1.7938285832472152], ['2021-09-22', 1.8372347001370162], ['2021-09-23', 1.8647621927668683], ['2021-09-24', 1.8609100516830828], ], 'constituents': [ { 'country': 'United States', 'name': 'Apple Inc', 'transactionCost': 1.1936100899058002, 'sector': 'Information Technology', 'shares': 26, 'assetId': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW', 'currency': 'USD', 'industry': 'Technology Hardware, Storage & Peripherals', 'marginalCost': 0.19195231034013271, 'advPercentage': 0, 'notional': 3819.9199999999996, 'price': 146.92, 'weight': 0.16081659493619194, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'The Goldman Sachs Group, Inc.', 'transactionCost': 3.2194154492372298, 'sector': 'Financials', 'shares': 51, 'assetId': 'MA4B66MW5E27UAHKG34', 'bbid': 'GS UN', 'currency': 'USD', 'industry': 'Capital Markets', 'marginalCost': 2.701680019005928, 'advPercentage': 2.3e-05, 'notional': 19933.350000000002, 'price': 390.85, 'weight': 0.8391834050638082, 'borrowCost': 40, }, ], 'assetId': '', 'transactionCost': 2.893632329346061, 'annualizedReturn': 0.861701827815391, }, } mock_results = { 'Portfolio': { 'Annualized Volatility': 0.2245953385486387, 'Backtest Performance': [ ['2020-09-24', 1], ['2020-09-25', 1.0076306394460186], ['2020-09-28', 1.0295511006061422], ['2020-09-29', 1.0186692133468813], ['2020-09-30', 1.0388739557814626], ['2020-10-01', 1.0312010109422116], ['2020-10-02', 1.028915736274446], ['2020-10-05', 1.04359570772614], ['2020-10-06', 1.0339555621015752], ['2020-10-07', 1.0478951891716997], ['2020-10-08', 1.0651714582747391], ['2020-10-09', 1.067487286837617], ['2020-10-12', 1.1089121011443612], ['2020-10-13', 1.0889651082352152], ['2020-10-14', 1.0908265455374473], ['2020-10-15', 1.0793406312748108], ['2020-10-16', 1.066348958386379], ['2020-10-19', 1.0580790374426237], ['2020-10-20', 1.070545026648481], ['2020-10-21', 1.0487843858627914], ['2020-10-22', 1.0564518466695865], ['2020-10-23', 1.0535672455976932], ['2020-10-26', 1.0380051283537797], ['2020-10-27', 1.0193468830718104], ['2020-10-28', 0.9847340205087146], ['2020-10-29', 0.9938053936242638], ['2020-10-30', 0.977050891037759], ['2020-11-02', 0.9815023585256727], ['2020-11-03', 1.0157893128742363], ['2020-11-04', 1.0247361201097123], ['2020-11-05', 1.053383922227019], ['2020-11-06', 1.0458990013576899], ['2020-11-09', 1.0956900988927742], ['2020-11-10', 1.1051257684500482], ['2020-11-11', 1.114613144599051], ['2020-11-12', 1.0998987020862043], ['2020-11-13', 1.118260026182338], ['2020-11-16', 1.133563610467608], ['2020-11-17', 1.1407798137465894], ['2020-11-18', 1.1366111656467677], ['2020-11-19', 1.1389324782421015], ['2020-11-20', 1.1314099525787882], ['2020-11-23', 1.1461964709467716], ['2020-11-24', 1.1835262798752466], ['2020-11-25', 1.1814423475419396], ['2020-11-26', 1.1814423475419396], ['2020-11-27', 1.1780281456214312], ['2020-11-30', 1.163780629300559], ['2020-12-01', 1.1772494130126692], ['2020-12-02', 1.2002376936353105], ['2020-12-03', 1.191242313540938], ['2020-12-04', 1.2062583778388682], ['2020-12-07', 1.2047988417723452], ['2020-12-08', 1.207640354017798], ['2020-12-09', 1.2182464727878393], ['2020-12-10', 1.2275332900355913], ['2020-12-11', 1.2082224448913497], ['2020-12-14', 1.1981490607027552], ['2020-12-15', 1.2291134748161479], ['2020-12-16', 1.2343248725158311], ['2020-12-17', 1.2387747731373286], ['2020-12-18', 1.2254195872403797], ['2020-12-21', 1.287961060235829], ['2020-12-22', 1.2678667818835767], ['2020-12-23', 1.2914042491850333], ['2020-12-24', 1.292302847074621], ['2020-12-25', 1.292302847074621], ['2020-12-28', 1.315621736511042], ['2020-12-29', 1.3056016257805925], ['2020-12-30', 1.3090126939682685], ['2020-12-31', 1.3239355297144455], ['2021-01-01', 1.3239355297144455], ['2021-01-04', 1.3224086183920336], ['2021-01-05', 1.3493610710472062], ['2021-01-06', 1.3987925727398136], ['2021-01-07', 1.4319647016333785], ['2021-01-08', 1.4279934787019752], ['2021-01-11', 1.4367232749388321], ['2021-01-12', 1.469838996641421], ['2021-01-13', 1.4770128945271697], ['2021-01-14', 1.4926776415213325], ['2021-01-15', 1.4616630876826273], ['2021-01-18', 1.4616630876826273], ['2021-01-19', 1.4358591481103977], ['2021-01-20', 1.4295109888258917], ['2021-01-21', 1.4349746520185538], ['2021-01-22', 1.439535800155589], ['2021-01-25', 1.4220064821263625], ['2021-01-26', 1.4173810924662704], ['2021-01-27', 1.3814583295793816], ['2021-01-28', 1.3780872164853137], ['2021-01-29', 1.3522550733175958], ['2021-02-01', 1.3709195860652295], ['2021-02-02', 1.4215560080317566], ['2021-02-03', 1.4257301401640343], ['2021-02-04', 1.4535341847163137], ['2021-02-05', 1.4512520437813807], ['2021-02-08', 1.4781277199821679], ['2021-02-09', 1.4775330941772888], ['2021-02-10', 1.4915330456044296], ['2021-02-11', 1.4831722464085457], ['2021-02-12', 1.4996431461737505], ['2021-02-15', 1.4996431461737505], ['2021-02-16', 1.5177373195459527], ['2021-02-17', 1.5160670399464755], ['2021-02-18', 1.5024180665965226], ['2021-02-19', 1.5255982883551262], ['2021-02-22', 1.5227097701171932], ['2021-02-23', 1.5317740923338705], ['2021-02-24', 1.5764039318945835], ['2021-02-25', 1.5560158660893277], ['2021-02-26', 1.5234830186934991], ['2021-03-01', 1.5784972654263867], ['2021-03-02', 1.577134091644449], ['2021-03-03', 1.5848454247108927], ['2021-03-04', 1.56037723875832], ['2021-03-05', 1.5553334957651503], ['2021-03-08', 1.5722760223215777], ['2021-03-09', 1.5675166655829158], ['2021-03-10', 1.6109345339708374], ['2021-03-11', 1.6152872888745418], ['2021-03-12', 1.6402028151888879], ['2021-03-15', 1.6352045113217835], ['2021-03-16', 1.6210823443141926], ['2021-03-17', 1.6323778843072827], ['2021-03-18', 1.6359479894361848], ['2021-03-19', 1.619665113640902], ['2021-03-22', 1.6071325326123638], ['2021-03-23', 1.5751950944546225], ['2021-03-24', 1.5577386357137408], ['2021-03-25', 1.5663485666697723], ['2021-03-26', 1.5549856514207931], ['2021-03-29', 1.5487197526231282], ['2021-03-30', 1.5707765311614452], ['2021-03-31', 1.5553421135304377], ['2021-04-01', 1.5596306269110851], ['2021-04-02', 1.5596306269110851], ['2021-04-05', 1.5491561249199899], ['2021-04-06', 1.5638517650358437], ['2021-04-07', 1.565256460777806], ['2021-04-08', 1.5886066875425475], ['2021-04-09', 1.5926554703615445], ['2021-04-12', 1.593196039275071], ['2021-04-13', 1.5830725153611647], ['2021-04-14', 1.608829448940914], ['2021-04-15', 1.6266462869574803], ['2021-04-16', 1.6409768471984019], ['2021-04-19', 1.6454784544116277], ['2021-04-20', 1.5971649119068507], ['2021-04-21', 1.61150408991306], ['2021-04-22', 1.5906663334464066], ['2021-04-23', 1.6294760477048125], ['2021-04-26', 1.6469520922758942], ['2021-04-27', 1.6587059406956703], ['2021-04-28', 1.6629693842136615], ['2021-04-29', 1.6821838670733185], ['2021-04-30', 1.6600095735537992], ['2021-05-03', 1.6690417750089481], ['2021-05-04', 1.6582100274749996], ['2021-05-05', 1.6898043218876324], ['2021-05-06', 1.726507384249699], ['2021-05-07', 1.7471226456852775], ['2021-05-10', 1.7314484974926188], ['2021-05-11', 1.6945331247311817], ['2021-05-12', 1.6660819643490854], ['2021-05-13', 1.6881833985802592], ['2021-05-14', 1.7330302491395915], ['2021-05-17', 1.7325445205506251], ['2021-05-18', 1.7109131462442573], ['2021-05-19', 1.6858949901796618], ['2021-05-20', 1.6968254502978186], ['2021-05-21', 1.7188869294353843], ['2021-05-24', 1.730399480427093], ['2021-05-25', 1.7148890697749082], ['2021-05-26', 1.7263342455107282], ['2021-05-27', 1.738998443318212], ['2021-05-28', 1.7402307837544118], ['2021-05-31', 1.7402307837544118], ['2021-06-01', 1.7811918056020137], ['2021-06-02', 1.7867761175087098], ['2021-06-03', 1.8047762788959507], ['2021-06-04', 1.820470796352018], ['2021-06-07', 1.8073459398182243], ['2021-06-08', 1.795232495555971], ['2021-06-09', 1.7883555188560583], ['2021-06-10', 1.750957551238487], ['2021-06-11', 1.7699048833742117], ['2021-06-14', 1.7557834997998292], ['2021-06-15', 1.7475997565089554], ['2021-06-16', 1.7474399361345212], ['2021-06-17', 1.7128231564053855], ['2021-06-18', 1.6594909407700957], ['2021-06-21', 1.698039771770234], ['2021-06-22', 1.7014218529292136], ['2021-06-23', 1.71491570650398], ['2021-06-24', 1.7450904199436985], ['2021-06-25', 1.7445592522286677], ['2021-06-28', 1.7477211886561963], ['2021-06-29', 1.7665008660854071], ['2021-06-30', 1.795393099363613], ['2021-07-01', 1.7778849340310026], ['2021-07-02', 1.7800479931183184], ['2021-07-05', 1.7800479931183184], ['2021-07-06', 1.7670633711287582], ['2021-07-07', 1.7635073677976], ['2021-07-08', 1.7259174590440662], ['2021-07-09', 1.7809489413075301], ['2021-07-12', 1.8146271680534696], ['2021-07-13', 1.798889561770962], ['2021-07-14', 1.7997262684371165], ['2021-07-15', 1.7941662429598688], ['2021-07-16', 1.75574746187226], ['2021-07-19', 1.7074472377320196], ['2021-07-20', 1.755098779176028], ['2021-07-21', 1.7884918362342517], ['2021-07-22', 1.7881870807163354], ['2021-07-23', 1.7971260536197318], ['2021-07-26', 1.8053936242638626], ['2021-07-27', 1.7966364078647248], ['2021-07-28', 1.790553049004526], ['2021-07-29', 1.805162511467499], ['2021-07-30', 1.7949426252690068], ['2021-08-02', 1.807475206297545], ['2021-08-03', 1.8198934060777137], ['2021-08-04', 1.8090694928758457], ['2021-08-05', 1.835024635057221], ['2021-08-06', 1.8874496350376353], ['2021-08-09', 1.895298852348689], ['2021-08-10', 1.9266244291709742], ['2021-08-11', 1.9508074454358313], ['2021-08-12', 1.9614143476390804], ['2021-08-13', 1.9449810526678597], ['2021-08-16', 1.939386556129459], ['2021-08-17', 1.9239873929928122], ['2021-08-18', 1.8915336723509921], ['2021-08-19', 1.8713297133496192], ['2021-08-20', 1.8835544051274093], ['2021-08-23', 1.9089454753990176], ['2021-08-24', 1.937170223583999], ['2021-08-25', 1.952823219080041], ['2021-08-26', 1.9483161278343588], ['2021-08-27', 1.9795625779026347], ['2021-08-30', 1.964436832955583], ['2021-08-31', 1.96144960213344], ['2021-09-01', 1.9634340384493298], ['2021-09-02', 1.969112362340987], ['2021-09-03', 1.9576906896014072], ['2021-09-06', 1.9576906896014072], ['2021-09-07', 1.958603389288739], ['2021-09-08', 1.9341289358705023], ['2021-09-09', 1.9303324185444852], ['2021-09-10', 1.915549033909334], ['2021-09-13', 1.939824495292736], ['2021-09-14', 1.9146567034854105], ['2021-09-15', 1.9095581201676801], ['2021-09-16', 1.8880129235141936], ['2021-09-17', 1.8615955557400916], ['2021-09-20', 1.8019802057765602], ['2021-09-21', 1.7938285832472152], ['2021-09-22', 1.8372347001370162], ['2021-09-23', 1.8647621927668683], ['2021-09-24', 1.8609100516830828], ], 'Constituents': [ { 'country': 'United States', 'name': 'Apple Inc', 'transactionCost': 1.1936100899058002, 'sector': 'Information Technology', 'shares': 26, 'assetId': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW', 'currency': 'USD', 'industry': 'Technology Hardware, Storage & Peripherals', 'marginalCost': 0.19195231034013271, 'advPercentage': 0, 'notional': 3819.9199999999996, 'price': 146.92, 'weight': 0.16081659493619194, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'The Goldman Sachs Group, Inc.', 'transactionCost': 3.2194154492372298, 'sector': 'Financials', 'shares': 51, 'assetId': 'MA4B66MW5E27UAHKG34', 'bbid': 'GS UN', 'currency': 'USD', 'industry': 'Capital Markets', 'marginalCost': 2.701680019005928, 'advPercentage': 2.3e-05, 'notional': 19933.350000000002, 'price': 390.85, 'weight': 0.8391834050638082, 'borrowCost': 40, }, ], 'Asset Id': '', 'Transaction Cost': 2.893632329346061, 'Annualized Return': 0.861701827815391, }, 'Hedge': { 'Transaction Cost': 2.2604819652681694, 'Correlation': 0.936758446681233, 'Backtest Performance': [ ['2020-09-24', 1], ['2020-09-25', 1.0104116399850593], ['2020-09-28', 1.0307855197014755], ['2020-09-29', 1.0163931905275039], ['2020-09-30', 1.0303780678487708], ['2020-10-01', 1.02803072573049], ['2020-10-02', 1.0338706383971734], ['2020-10-05', 1.052525377568271], ['2020-10-06', 1.0340650510786302], ['2020-10-07', 1.0508356048581615], ['2020-10-08', 1.0603036151113474], ['2020-10-09', 1.060588304786846], ['2020-10-12', 1.0868635890594514], ['2020-10-13', 1.0747102379748288], ['2020-10-14', 1.0671267966260976], ['2020-10-15', 1.074639084395838], ['2020-10-16', 1.0765144407834903], ['2020-10-19', 1.0626904612955628], ['2020-10-20', 1.0698992568123844], ['2020-10-21', 1.0633001027846625], ['2020-10-22', 1.080365958503925], ['2020-10-23', 1.0836535809584429], ['2020-10-26', 1.0599791557462024], ['2020-10-27', 1.0398109665270256], ['2020-10-28', 1.013939716813292], ['2020-10-29', 1.025285647916773], ['2020-10-30', 1.0162452634880497], ['2020-11-02', 1.0361270021554296], ['2020-11-03', 1.0628292713233702], ['2020-11-04', 1.0643748693020345], ['2020-11-05', 1.0870688878363275], ['2020-11-06', 1.075680915811229], ['2020-11-09', 1.1310152350427867], ['2020-11-10', 1.1365085766625056], ['2020-11-11', 1.1456684425171284], ['2020-11-12', 1.1324848780700123], ['2020-11-13', 1.1496769513131484], ['2020-11-16', 1.174024423737768], ['2020-11-17', 1.1786976021533382], ['2020-11-18', 1.172198105516616], ['2020-11-19', 1.1796894313970685], ['2020-11-20', 1.1720675902607598], ['2020-11-23', 1.2042568842777668], ['2020-11-24', 1.2478109612205204], ['2020-11-25', 1.2465964960297171], ['2020-11-26', 1.2465964960297171], ['2020-11-27', 1.2482807167565497], ['2020-11-30', 1.2240604489911184], ['2020-12-01', 1.2414716222521844], ['2020-12-02', 1.2555764585251266], ['2020-12-03', 1.2530206749134576], ['2020-12-04', 1.2726021013511315], ['2020-12-07', 1.2651506324580064], ['2020-12-08', 1.2642771975229015], ['2020-12-09', 1.2678474010781646], ['2020-12-10', 1.2736979737616583], ['2020-12-11', 1.2573160543347301], ['2020-12-14', 1.244303488883378], ['2020-12-15', 1.2742786174980252], ['2020-12-16', 1.2779195844041837], ['2020-12-17', 1.283302631746248], ['2020-12-18', 1.276354699607117], ['2020-12-21', 1.3125686927823437], ['2020-12-22', 1.296299249328958], ['2020-12-23', 1.3187779432834859], ['2020-12-24', 1.3162583329954654], ['2020-12-25', 1.3162583329954654], ['2020-12-28', 1.3185892862798951], ['2020-12-29', 1.3111756560815606], ['2020-12-30', 1.3144825720696882], ['2020-12-31', 1.3268030683678376], ['2021-01-01', 1.3268030683678376], ['2021-01-04', 1.3159923682994483], ['2021-01-05', 1.3340647632947369], ['2021-01-06', 1.3909576803980703], ['2021-01-07', 1.4152280361405745], ['2021-01-08', 1.4174405809398316], ['2021-01-11', 1.4231526927862412], ['2021-01-12', 1.4293977756277825], ['2021-01-13', 1.4234850492339033], ['2021-01-14', 1.4397514671138192], ['2021-01-15', 1.409001702900938], ['2021-01-18', 1.409001702900938], ['2021-01-19', 1.4109828849695718], ['2021-01-20', 1.4115393763475617], ['2021-01-21', 1.4044664175144592], ['2021-01-22', 1.3987852008396073], ['2021-01-25', 1.3860808014247046], ['2021-01-26', 1.381350320662177], ['2021-01-27', 1.3454054796675197], ['2021-01-28', 1.3501857783066038], ['2021-01-29', 1.3175753005863033], ['2021-02-01', 1.3310146449999998], ['2021-02-02', 1.370111970146903], ['2021-02-03', 1.369846444923827], ['2021-02-04', 1.3936530582244067], ['2021-02-05', 1.4039277423247092], ['2021-02-08', 1.4268063574012315], ['2021-02-09', 1.4283534342175326], ['2021-02-10', 1.4357594389261352], ['2021-02-11', 1.428374052139035], ['2021-02-12', 1.4429251243112822], ['2021-02-15', 1.4429251243112822], ['2021-02-16', 1.461542741076252], ['2021-02-17', 1.4591638016742965], ['2021-02-18', 1.4472594444672997], ['2021-02-19', 1.4707489985599904], ['2021-02-22', 1.4755439199336886], ['2021-02-23', 1.4820692085583076], ['2021-02-24', 1.511687900368849], ['2021-02-25', 1.486336535951723], ['2021-02-26', 1.4617866716883874], ['2021-03-01', 1.5001983608496483], ['2021-03-02', 1.5085260984949034], ['2021-03-03', 1.5167425010055402], ['2021-03-04', 1.4954468749220593], ['2021-03-05', 1.5048325007869152], ['2021-03-08', 1.5144817178954568], ['2021-03-09', 1.5136338082053342], ['2021-03-10', 1.5471387857165224], ['2021-03-11', 1.552032591486371], ['2021-03-12', 1.5716887223548588], ['2021-03-15', 1.570807534341104], ['2021-03-16', 1.5528662067408492], ['2021-03-17', 1.5663385974304973], ['2021-03-18', 1.5673612323097021], ['2021-03-19', 1.5525802635601895], ['2021-03-22', 1.5409068482642276], ['2021-03-23', 1.507406937003066], ['2021-03-24', 1.5012740563038809], ['2021-03-25', 1.5192040583357755], ['2021-03-26', 1.529957344137103], ['2021-03-29', 1.5047630491396355], ['2021-03-30', 1.5222775022723902], ['2021-03-31', 1.508553987492476], ['2021-04-01', 1.5207815572746632], ['2021-04-02', 1.5207815572746632], ['2021-04-05', 1.5252366638449892], ['2021-04-06', 1.537238350504828], ['2021-04-07', 1.5392951360957752], ['2021-04-08', 1.5490368365166685], ['2021-04-09', 1.557915030807923], ['2021-04-12', 1.5565394043984706], ['2021-04-13', 1.5460141095481092], ['2021-04-14', 1.5595020548937684], ['2021-04-15', 1.565231199487692], ['2021-04-16', 1.5557047184416664], ['2021-04-19', 1.5616881940098946], ['2021-04-20', 1.5263160854925413], ['2021-04-21', 1.5522763213884536], ['2021-04-22', 1.5350915789121093], ['2021-04-23', 1.57158539249219], ['2021-04-26', 1.5810581974026183], ['2021-04-27', 1.5900787413814923], ['2021-04-28', 1.5955846620747325], ['2021-04-29', 1.6137105295110301], ['2021-04-30', 1.5845767863905935], ['2021-05-03', 1.5863705380999724], ['2021-05-04', 1.5882125781111578], ['2021-05-05', 1.6065603704288025], ['2021-05-06', 1.6344953097116117], ['2021-05-07', 1.6495251089353995], ['2021-05-10', 1.636644671135531], ['2021-05-11', 1.6146185343277637], ['2021-05-12', 1.5897950933090927], ['2021-05-13', 1.6122368879515665], ['2021-05-14', 1.6439450665420194], ['2021-05-17', 1.6545198576851718], ['2021-05-18', 1.6353279676761365], ['2021-05-19', 1.6209690936595618], ['2021-05-20', 1.6294848988625292], ['2021-05-21', 1.6477823771244906], ['2021-05-24', 1.665391178904659], ['2021-05-25', 1.6543811334627772], ['2021-05-26', 1.6631885248580318], ['2021-05-27', 1.6784653130973213], ['2021-05-28', 1.6828566766917903], ['2021-05-31', 1.6828566766917903], ['2021-06-01', 1.69991088376666], ['2021-06-02', 1.7039576789378863], ['2021-06-03', 1.7075602408335289], ['2021-06-04', 1.7186255598839562], ['2021-06-07', 1.712635392985795], ['2021-06-08', 1.7097444780540265], ['2021-06-09', 1.7005645001567697], ['2021-06-10', 1.6846521736086526], ['2021-06-11', 1.6913655615016792], ['2021-06-14', 1.6741903605872415], ['2021-06-15', 1.6671331966653065], ['2021-06-16', 1.6608467705374614], ['2021-06-17', 1.6182529345083716], ['2021-06-18', 1.5716734295918893], ['2021-06-21', 1.6025210119753992], ['2021-06-22', 1.6036761759261928], ['2021-06-23', 1.6088186017605215], ['2021-06-24', 1.6298499404572735], ['2021-06-25', 1.6475608618707231], ['2021-06-28', 1.6436992303035847], ['2021-06-29', 1.6583865823631618], ['2021-06-30', 1.6717505836553197], ['2021-07-01', 1.6772597783846594], ['2021-07-02', 1.6765190560701135], ['2021-07-05', 1.6765190560701135], ['2021-07-06', 1.6472838625986161], ['2021-07-07', 1.6456960834251377], ['2021-07-08', 1.6125483519004047], ['2021-07-09', 1.6558213788094323], ['2021-07-12', 1.683903334102053], ['2021-07-13', 1.6760296033419682], ['2021-07-14', 1.6751081114066049], ['2021-07-15', 1.6761821265670138], ['2021-07-16', 1.6519994975324561], ['2021-07-19', 1.6185386068423806], ['2021-07-20', 1.6608158082138411], ['2021-07-21', 1.6977232240293918], ['2021-07-22', 1.6910789251625107], ['2021-07-23', 1.6882778604361217], ['2021-07-26', 1.7079461771654998], ['2021-07-27', 1.705316911290549], ['2021-07-28', 1.7065513855574908], ['2021-07-29', 1.7147564423541488], ['2021-07-30', 1.7078630854421146], ['2021-08-02', 1.7098846370921508], ['2021-08-03', 1.7212246073309094], ['2021-08-04', 1.71284744352946], ['2021-08-05', 1.7319984849390486], ['2021-08-06', 1.7610784608955359], ['2021-08-09', 1.7685063176313183], ['2021-08-10', 1.788487648029903], ['2021-08-11', 1.8132548730727573], ['2021-08-12', 1.8211088797799007], ['2021-08-13', 1.80763324042271], ['2021-08-16', 1.7990148335838823], ['2021-08-17', 1.7821201492173437], ['2021-08-18', 1.7595999866259955], ['2021-08-19', 1.7402030612253945], ['2021-08-20', 1.75394804825027], ['2021-08-23', 1.774564369295308], ['2021-08-24', 1.791036747026845], ['2021-08-25', 1.808387370019545], ['2021-08-26', 1.7990128100684075], ['2021-08-27', 1.8230267889371115], ['2021-08-30', 1.8066029024558345], ['2021-08-31', 1.8011083700062123], ['2021-09-01', 1.7952270192942026], ['2021-09-02', 1.8042111959545828], ['2021-09-03', 1.7956456571882038], ['2021-09-06', 1.7956456571882038], ['2021-09-07', 1.7906973619966717], ['2021-09-08', 1.7790846820103143], ['2021-09-09', 1.782974909163946], ['2021-09-10', 1.774210004132321], ['2021-09-13', 1.7863371703894837], ['2021-09-14', 1.7607086022595286], ['2021-09-15', 1.7733155737318977], ['2021-09-16', 1.7661953659616862], ['2021-09-17', 1.75552495625101], ['2021-09-20', 1.7073319409830758], ['2021-09-21', 1.7036755701399784], ['2021-09-22', 1.7353221001527805], ['2021-09-23', 1.7749324312405845], ['2021-09-24', 1.772300109946803], ], 'Cash Weight': 0, 'R Squared': 0.8279135592848923, 'Annualized Return': 0.77299493701611, 'Annualized Volatility': 0.19788814828229934, 'Hedge Ratio': 1, 'Ridge Weight': 0, 'Backtest Correlation': [ ['2020-11-26', 0.9015925151077675], ['2020-11-27', 0.8995307601350195], ['2020-11-30', 0.8976193597009464], ['2020-12-01', 0.899134905548527], ['2020-12-02', 0.8968844291011409], ['2020-12-03', 0.8983210353924714], ['2020-12-04', 0.8983135812975858], ['2020-12-07', 0.8981598956869762], ['2020-12-08', 0.896541180905539], ['2020-12-09', 0.896342085151086], ['2020-12-10', 0.8960079549884281], ['2020-12-11', 0.9022159150284723], ['2020-12-14', 0.9020984285750759], ['2020-12-15', 0.9108756715722117], ['2020-12-16', 0.9216167275658368], ['2020-12-17', 0.9283890799097579], ['2020-12-18', 0.9282606242969395], ['2020-12-21', 0.9168926673056201], ['2020-12-22', 0.9235486508155735], ['2020-12-23', 0.9276013093341673], ['2020-12-24', 0.9283372838050917], ['2020-12-25', 0.928171587486031], ['2020-12-28', 0.9116851149928049], ['2020-12-29', 0.9056422674201283], ['2020-12-30', 0.9059781317532566], ['2020-12-31', 0.9052396831652074], ['2021-01-01', 0.9155947769421426], ['2021-01-04', 0.9111654399535882], ['2021-01-05', 0.9125433947950563], ['2021-01-06', 0.9209363905792434], ['2021-01-07', 0.9201318271791072], ['2021-01-08', 0.9084929198870123], ['2021-01-11', 0.9086559298840635], ['2021-01-12', 0.8875612269095221], ['2021-01-13', 0.8789265648137641], ['2021-01-14', 0.8783291410335801], ['2021-01-15', 0.8943005663624369], ['2021-01-18', 0.8945248751426272], ['2021-01-19', 0.8705151985771736], ['2021-01-20', 0.8708271027477481], ['2021-01-21', 0.8650473745733117], ['2021-01-22', 0.8748613614663985], ['2021-01-25', 0.8725792954791373], ['2021-01-26', 0.873175463509665], ['2021-01-27', 0.8864366140951857], ['2021-01-28', 0.8845759397275269], ['2021-01-29', 0.8898805771751356], ['2021-02-01', 0.8920000543343564], ['2021-02-02', 0.9019976775814598], ['2021-02-03', 0.9029180582695069], ['2021-02-04', 0.9070217939463278], ['2021-02-05', 0.9007086970184902], ['2021-02-08', 0.9030258129623466], ['2021-02-09', 0.9032264783085702], ['2021-02-10', 0.9030829600702234], ['2021-02-11', 0.9010523188673754], ['2021-02-12', 0.9001497414661194], ['2021-02-15', 0.8972232205299802], ['2021-02-16', 0.897594089152695], ['2021-02-17', 0.8982148727150193], ['2021-02-18', 0.8998472164172846], ['2021-02-19', 0.8982607278993444], ['2021-02-22', 0.8921722036271721], ['2021-02-23', 0.8898626754094953], ['2021-02-24', 0.8947739256523193], ['2021-02-25', 0.8987596973059994], ['2021-02-26', 0.9134643435214955], ['2021-03-01', 0.918993929754835], ['2021-03-02', 0.9157408336361909], ['2021-03-03', 0.915275687497752], ['2021-03-04', 0.9186455889944436], ['2021-03-05', 0.9145426609376325], ['2021-03-08', 0.9134714758069424], ['2021-03-09', 0.9086630053014179], ['2021-03-10', 0.9120223954384556], ['2021-03-11', 0.9130148169582306], ['2021-03-12', 0.914739719013808], ['2021-03-15', 0.928463943323836], ['2021-03-16', 0.933583077072683], ['2021-03-17', 0.9330716807868986], ['2021-03-18', 0.9288147179435885], ['2021-03-19', 0.9303048507822332], ['2021-03-22', 0.9503491605860712], ['2021-03-23', 0.9543301426608973], ['2021-03-24', 0.9562100276004051], ['2021-03-25', 0.9558902159462764], ['2021-03-26', 0.9421183812187457], ['2021-03-29', 0.9304297698582624], ['2021-03-30', 0.9255377724225964], ['2021-03-31', 0.9288788520748432], ['2021-04-01', 0.9255245811924869], ['2021-04-02', 0.9253057356272613], ['2021-04-05', 0.9110845996065523], ['2021-04-06', 0.9125537209614435], ['2021-04-07', 0.9092805473148023], ['2021-04-08', 0.9106842967282306], ['2021-04-09', 0.9065743602103377], ['2021-04-12', 0.9064120996162333], ['2021-04-13', 0.9080039410233919], ['2021-04-14', 0.9058324442791906], ['2021-04-15', 0.9012633679437755], ['2021-04-16', 0.8817266337950646], ['2021-04-19', 0.879870971242904], ['2021-04-20', 0.8941573383350782], ['2021-04-21', 0.887962814825184], ['2021-04-22', 0.8883257914187292], ['2021-04-23', 0.8982033834669962], ['2021-04-26', 0.8978600104298144], ['2021-04-27', 0.8927525434765888], ['2021-04-28', 0.8906790878648955], ['2021-04-29', 0.8850838636802095], ['2021-04-30', 0.8752084867429241], ['2021-05-03', 0.8772331412633768], ['2021-05-04', 0.8720331661943858], ['2021-05-05', 0.8671463406412616], ['2021-05-06', 0.8823932789430973], ['2021-05-07', 0.8839329350180696], ['2021-05-10', 0.8866611547123624], ['2021-05-11', 0.8809173134162849], ['2021-05-12', 0.8872869281730444], ['2021-05-13', 0.8869823508564784], ['2021-05-14', 0.8953095635961708], ['2021-05-17', 0.8900795744712166], ['2021-05-18', 0.8932618180743701], ['2021-05-19', 0.8948100995042912], ['2021-05-20', 0.8934110525145609], ['2021-05-21', 0.894275704465274], ['2021-05-24', 0.8862129378804093], ['2021-05-25', 0.8887466640145859], ['2021-05-26', 0.8922999893294049], ['2021-05-27', 0.9071416941600038], ['2021-05-28', 0.9203476398072666], ['2021-05-31', 0.9190730350667695], ['2021-06-01', 0.9102728877414129], ['2021-06-02', 0.9132534478383805], ['2021-06-03', 0.9088133196000899], ['2021-06-04', 0.9156681278113125], ['2021-06-07', 0.9156223123920523], ['2021-06-08', 0.9145856716019339], ['2021-06-09', 0.9168585582176089], ['2021-06-10', 0.9163403851492076], ['2021-06-11', 0.9144967572849609], ['2021-06-14', 0.9141431719872722], ['2021-06-15', 0.9147777043798121], ['2021-06-16', 0.915538259640843], ['2021-06-17', 0.9303937239900952], ['2021-06-18', 0.9390592485374359], ['2021-06-21', 0.9373116830433493], ['2021-06-22', 0.9427449880436689], ['2021-06-23', 0.9407084724464195], ['2021-06-24', 0.9394464162725222], ['2021-06-25', 0.9280066991265317], ['2021-06-28', 0.9265038781758507], ['2021-06-29', 0.9275818290399418], ['2021-06-30', 0.9255991162602577], ['2021-07-01', 0.9138966034117193], ['2021-07-02', 0.9146142973869088], ['2021-07-05', 0.9181022970358077], ['2021-07-06', 0.9074933619483999], ['2021-07-07', 0.902025125731612], ['2021-07-08', 0.907076026609854], ['2021-07-09', 0.9174277412353735], ['2021-07-12', 0.9192217122691708], ['2021-07-13', 0.9155688050636652], ['2021-07-14', 0.914511919994974], ['2021-07-15', 0.9072827624461118], ['2021-07-16', 0.9147251729875863], ['2021-07-19', 0.9193336264588486], ['2021-07-20', 0.925798773703205], ['2021-07-21', 0.9267409583789746], ['2021-07-22', 0.9246635419626583], ['2021-07-23', 0.9235962637255246], ['2021-07-26', 0.9187195464946155], ['2021-07-27', 0.9179631491889088], ['2021-07-28', 0.9169395326411997], ['2021-07-29', 0.9174199380790581], ['2021-07-30', 0.9176683013254561], ['2021-08-02', 0.92034668699813], ['2021-08-03', 0.9207308184465794], ['2021-08-04', 0.9241614525891176], ['2021-08-05', 0.9252544039117194], ['2021-08-06', 0.9257068761979128], ['2021-08-09', 0.9269338494497388], ['2021-08-10', 0.9279078169690388], ['2021-08-11', 0.9320239670516519], ['2021-08-12', 0.9336249338456267], ['2021-08-13', 0.9340825531336873], ['2021-08-16', 0.9337740851496074], ['2021-08-17', 0.9352358574622675], ['2021-08-18', 0.9372444096991827], ['2021-08-19', 0.9298764811116637], ['2021-08-20', 0.9262131482920518], ['2021-08-23', 0.9276585645717137], ['2021-08-24', 0.92856395669229], ['2021-08-25', 0.9271659353349381], ['2021-08-26', 0.9366798112705848], ['2021-08-27', 0.939469558627736], ['2021-08-30', 0.939741517191791], ['2021-08-31', 0.9416728966796483], ['2021-09-01', 0.9523441724282465], ['2021-09-02', 0.9517066086773255], ['2021-09-03', 0.9520676724175039], ['2021-09-06', 0.9609901628723267], ['2021-09-07', 0.9600864061003563], ['2021-09-08', 0.9558863980743179], ['2021-09-09', 0.948472236169008], ['2021-09-10', 0.946294665243798], ['2021-09-13', 0.9452172483227433], ['2021-09-14', 0.9466756455002099], ['2021-09-15', 0.937395558117138], ['2021-09-16', 0.931622601567483], ['2021-09-17', 0.9210569221798822], ['2021-09-20', 0.9284521728561184], ['2021-09-21', 0.9272930667016914], ['2021-09-22', 0.9352279628015158], ['2021-09-23', 0.9315300597142235], ['2021-09-24', 0.936758446681233], ], 'Tracking Error': 0.09724809864706382, 'Lasso Weight': 0, 'Constituents': [ { 'country': 'United States', 'name': 'Dentsply Sirona Inc', 'transactionCost': 2.9051536321640015, 'sector': 'Health Care', 'correlation': 0.16678359890783784, 'shares': -5.308036491917078, 'assetId': 'MA4B66MW5E27UANZH4M', 'bbid': 'XRAY UW', 'currency': 'USD', 'industry': 'Health Care Equipment & Supplies', 'marginalCost': 0.03895858954426313, 'advPercentage': 5e-06, 'notional': -318.5352698799438, 'price': 60.01, 'weight': 0.013410164995385637, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Extra Space Storage Inc', 'transactionCost': 3.9990124702453613, 'sector': 'Real Estate', 'correlation': 0.06540203064772898, 'shares': -3.1030213327842304, 'assetId': 'MA4B66MW5E27UAFU2CS', 'bbid': 'EXR UN', 'currency': 'USD', 'industry': 'Equity Real Estate Investment Trusts (REITs)', 'marginalCost': 0.09017373674086719, 'advPercentage': 5e-06, 'notional': -535.6125122518861, 'price': 172.61, 'weight': 0.02254900113760699, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Kroger Co', 'transactionCost': 1.2731897830963135, 'sector': 'Consumer Staples', 'correlation': 0.12350867819558603, 'shares': -1.730011893560012, 'assetId': 'MA4B66MW5E27UAKNZNV', 'bbid': 'KR UN', 'currency': 'USD', 'industry': 'Food & Staples Retailing', 'marginalCost': 0.003707333181127408, 'advPercentage': 0, 'notional': -69.16587550452927, 'price': 39.98, 'weight': 0.002911846474381391, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'MSCI Inc', 'transactionCost': 7.908167362213135, 'sector': 'Financials', 'correlation': 0.24284241634131032, 'shares': -0.5740039461869635, 'assetId': 'MA4B66MW5E27UAL9SUH', 'bbid': 'MSCI UN', 'currency': 'USD', 'industry': 'Capital Markets', 'marginalCost': 0.1263897386589486, 'advPercentage': 2e-06, 'notional': -379.628989889672, 'price': 661.37, 'weight': 0.01598217802810611, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'General Motors Co', 'transactionCost': 1.0103281140327454, 'sector': 'Consumer Discretionary', 'correlation': 0.5028570129890573, 'shares': -3.0290208240423557, 'assetId': 'MA4B66MW5E27UAGYYVE', 'bbid': 'GM UN', 'currency': 'USD', 'industry': 'Automobiles', 'marginalCost': 0.006729167173415375, 'advPercentage': 0, 'notional': -158.20575763973224, 'price': 52.23, 'weight': 0.0066603780296242265, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Mosaic Co/the', 'transactionCost': 2.1279181241989136, 'sector': 'Materials', 'correlation': 0.6130275473664366, 'shares': -5.192035694429817, 'assetId': 'MA4B66MW5E27UAL9SLW', 'bbid': 'MOS UN', 'currency': 'USD', 'industry': 'Chemicals', 'marginalCost': 0.016679363095866095, 'advPercentage': 2e-06, 'notional': -186.18640000225324, 'price': 35.86, 'weight': 0.007838348151738823, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Meta Platforms Inc-Class A', 'transactionCost': 0.8238705992698669, 'sector': 'Communication Services', 'correlation': 0.12666374147291945, 'shares': -0.7130049017966985, 'assetId': 'MA4B66MW5E3VLSECN', 'bbid': 'FB UW', 'currency': 'USD', 'industry': 'Interactive Media & Services', 'marginalCost': 0.008728781169081446, 'advPercentage': 0, 'notional': -251.6622101381627, 'price': 352.96, 'weight': 0.010594844841917037, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Twitter Inc', 'transactionCost': 1.2658634781837463, 'sector': 'Communication Services', 'correlation': 0.15422853187220908, 'shares': -8.874061007775461, 'assetId': 'MA4B66MW5E27UANLXU9', 'bbid': 'TWTR UN', 'currency': 'USD', 'industry': 'Interactive Media & Services', 'marginalCost': 0.03178955019863404, 'advPercentage': 1e-06, 'notional': -596.5143809426665, 'price': 67.22, 'weight': 0.025112937332109075, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Paypal Holdings Inc', 'transactionCost': 1.4358487129211426, 'sector': 'Information Technology', 'correlation': 0.22583565660778615, 'shares': -0.5480037674398188, 'assetId': 'MA4B66MW5E27UAM94N9', 'bbid': 'PYPL UW', 'currency': 'USD', 'industry': 'IT Services', 'marginalCost': 0.009212668138339708, 'advPercentage': 0, 'notional': -152.40532776268802, 'price': 278.11, 'weight': 0.006416183025018788, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Hormel Foods Corp', 'transactionCost': 1.4352393746376038, 'sector': 'Consumer Staples', 'correlation': -0.03313295634963935, 'shares': -11.239077266890737, 'assetId': 'MA4B66MW5E27UAJ5XEZ', 'bbid': 'HRL UN', 'currency': 'USD', 'industry': 'Food Products', 'marginalCost': 0.027707143568185242, 'advPercentage': 4e-06, 'notional': -458.55435248914205, 'price': 40.8, 'weight': 0.019304893704704323, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Nucor Corp', 'transactionCost': 3.182479500770569, 'sector': 'Materials', 'correlation': 0.556129972455637, 'shares': -0.7970054792874738, 'assetId': 'MA4B66MW5E27UALNB48', 'bbid': 'NUE UN', 'currency': 'USD', 'industry': 'Metals & Mining', 'marginalCost': 0.010776575069254387, 'advPercentage': 0, 'notional': -80.43379296969185, 'price': 100.92, 'weight': 0.0033862197907779373, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Wells Fargo & Co', 'transactionCost': 1.3822112533240394, 'sector': 'Financials', 'correlation': 0.6178643696685713, 'shares': -43.79430107894055, 'assetId': 'MA4B66MW5E27UANT8X5', 'bbid': 'WFC UN', 'currency': 'USD', 'industry': 'Banks', 'marginalCost': 0.12211961551022109, 'advPercentage': 2e-06, 'notional': -2098.622907702831, 'price': 47.92, 'weight': 0.0883509052733721, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Cummins Inc', 'transactionCost': 3.3661617040634155, 'sector': 'Industrials', 'correlation': 0.6412824675287682, 'shares': -0.4130028393296445, 'assetId': 'MA4B66MW5E27UACX6RZ', 'bbid': 'CMI UN', 'currency': 'USD', 'industry': 'Machinery', 'marginalCost': 0.013324512800464238, 'advPercentage': 0, 'notional': -94.02422640178686, 'price': 227.66, 'weight': 0.003958369790845086, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'JPMorgan Chase & Co', 'transactionCost': 1.2586397729172192, 'sector': 'Financials', 'correlation': 0.8505015474586123, 'shares': -12.604086651115832, 'assetId': 'MA4B66MW5E27UAKHZFD', 'bbid': 'JPM UN', 'currency': 'USD', 'industry': 'Banks', 'marginalCost': 0.10888889555559653, 'advPercentage': 1e-06, 'notional': -2054.970287597925, 'price': 163.04, 'weight': 0.08651315324576048, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Lumen Technologies Inc', 'transactionCost': 3.95284104347229, 'sector': 'Communication Services', 'correlation': 0.35956668091011157, 'shares': -11.81608123370237, 'assetId': 'MAQQM1QHGJDFAVKN', 'bbid': 'LUMN UN', 'currency': 'USD', 'industry': 'Diversified Telecommunication Services', 'marginalCost': 0.02542482298207395, 'advPercentage': 1e-06, 'notional': -152.78193035177165, 'price': 12.93, 'weight': 0.00643203779318686, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Advanced Micro Devices', 'transactionCost': 0.5830498039722443, 'sector': 'Information Technology', 'correlation': 0.12884947696795268, 'shares': -0.8120055824108264, 'assetId': 'MA4B66MW5E27U9YGMB9', 'bbid': 'AMD UW', 'currency': 'USD', 'industry': 'Semiconductors & Semiconductor Equipment', 'marginalCost': 0.0021087589119167267, 'advPercentage': 0, 'notional': -85.91019061906543, 'price': 105.8, 'weight': 0.003616773211396386, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Morgan Stanley', 'transactionCost': 2.3784881788491172, 'sector': 'Financials', 'correlation': 0.9121008220093567, 'shares': -100.16368860962515, 'assetId': 'MA4B66MW5E27UAL9STW', 'bbid': 'MS UN', 'currency': 'USD', 'industry': 'Capital Markets', 'marginalCost': 1.0321563281719852, 'advPercentage': 1.6e-05, 'notional': -10307.845194816524, 'price': 102.91, 'weight': 0.43395478579650393, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Citigroup Inc', 'transactionCost': 1.4510516652072596, 'sector': 'Financials', 'correlation': 0.8189307897328992, 'shares': -27.227187182634943, 'assetId': 'MA4B66MW5E27UA4ZCWT', 'bbid': 'C UN', 'currency': 'USD', 'industry': 'Banks', 'marginalCost': 0.11839142047698586, 'advPercentage': 2e-06, 'notional': -1938.0311836599553, 'price': 71.18, 'weight': 0.08159007933054924, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Duke Realty Corp', 'transactionCost': 1.3627572059631348, 'sector': 'Real Estate', 'correlation': 0.1248871579141672, 'shares': -8.864060939026558, 'assetId': 'MA4B66MW5E27UAEP46J', 'bbid': 'DRE UN', 'currency': 'USD', 'industry': 'Equity Real Estate Investment Trusts (REITs)', 'marginalCost': 0.02484233322743233, 'advPercentage': 5e-06, 'notional': -433.0093768714474, 'price': 48.85, 'weight': 0.0182294638536693, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Electronic Arts Inc', 'transactionCost': 2.1669615507125854, 'sector': 'Communication Services', 'correlation': 0.18457789391303997, 'shares': -6.899047429867354, 'assetId': 'MA4B66MW5E27UAEP4H9', 'bbid': 'EA UW', 'currency': 'USD', 'industry': 'Entertainment', 'marginalCost': 0.08175721364539484, 'advPercentage': 3e-06, 'notional': -896.1862611397693, 'price': 129.9, 'weight': 0.037728963681201336, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Qualcomm Inc', 'transactionCost': 1.0424676537513733, 'sector': 'Information Technology', 'correlation': 0.37498752971441385, 'shares': -0.6250042968063627, 'assetId': 'MA4B66MW5E27UAM94Q4', 'bbid': 'QCOM UW', 'currency': 'USD', 'industry': 'Semiconductors & Semiconductor Equipment', 'marginalCost': 0.0036722977768481185, 'advPercentage': 0, 'notional': -83.67557525643583, 'price': 133.88, 'weight': 0.00352269709629183, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Albemarle Corp', 'transactionCost': 5.661775350570679, 'sector': 'Materials', 'correlation': 0.572692690460937, 'shares': -2.9210200815542167, 'assetId': 'MA4B66MW5E27U9XPVYM', 'bbid': 'ALB UN', 'currency': 'USD', 'industry': 'Chemicals', 'marginalCost': 0.157630478243818, 'advPercentage': 2e-06, 'notional': -661.3189464638747, 'price': 226.4, 'weight': 0.027841174981965627, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Tractor Supply Company', 'transactionCost': 3.904104232788086, 'sector': 'Consumer Discretionary', 'correlation': 0.07218103540747003, 'shares': -0.49000336869618827, 'assetId': 'MA4B66MW5E27UANEQ5C', 'bbid': 'TSCO UW', 'currency': 'USD', 'industry': 'Specialty Retail', 'marginalCost': 0.016970819801319783, 'advPercentage': 1e-06, 'notional': -103.2535098516608, 'price': 210.72, 'weight': 0.004346917702348384, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Davita Inc', 'transactionCost': 3.084942579269409, 'sector': 'Health Care', 'correlation': 0.20287653627709595, 'shares': -3.4880239796169494, 'assetId': 'MA4B66MW5E27UAEP4D8', 'bbid': 'DVA UN', 'currency': 'USD', 'industry': 'Health Care Providers & Services', 'marginalCost': 0.05481362341943657, 'advPercentage': 6e-06, 'notional': -422.05090153365086, 'price': 121, 'weight': 0.01776811788581744, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Citrix Systems Inc', 'transactionCost': 2.754676938056946, 'sector': 'Information Technology', 'correlation': -0.014004050045478335, 'shares': -2.204015152257958, 'assetId': 'MA4B66MW5E27UAE4LLL', 'bbid': 'CTXS UW', 'currency': 'USD', 'industry': 'Software', 'marginalCost': 0.028164628476876532, 'advPercentage': 2e-06, 'notional': -242.86042962730437, 'price': 110.19, 'weight': 0.010224294576170117, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Best Buy Co Inc', 'transactionCost': 2.401727557182312, 'sector': 'Consumer Discretionary', 'correlation': 0.45583415490693957, 'shares': -4.535031177626968, 'assetId': 'MA4B66MW5E27UA39J2L', 'bbid': 'BBY UN', 'currency': 'USD', 'industry': 'Specialty Retail', 'marginalCost': 0.04802785408186685, 'advPercentage': 2e-06, 'notional': -474.99916554464863, 'price': 104.74, 'weight': 0.019997211564750816, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Iron Mountain Inc', 'transactionCost': 2.417732357978821, 'sector': 'Real Estate', 'correlation': 0.4856832268445906, 'shares': -5.0810349313170065, 'assetId': 'MA4B66MW5E27UAJQF4M', 'bbid': 'IRM UN', 'currency': 'USD', 'industry': 'Equity Real Estate Investment Trusts (REITs)', 'marginalCost': 0.02289530957940409, 'advPercentage': 3e-06, 'notional': -224.93741640940388, 'price': 44.27, 'weight': 0.009469745277572472, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Tapestry Inc', 'transactionCost': 2.314427137374878, 'sector': 'Consumer Discretionary', 'correlation': 0.6555947816398136, 'shares': -7.397050853562664, 'assetId': 'MAAHJ4JYCKZ6P025', 'bbid': 'TPR UN', 'currency': 'USD', 'industry': 'Textiles, Apparel & Luxury Goods', 'marginalCost': 0.02844040606854561, 'advPercentage': 2e-06, 'notional': -291.88762668158273, 'price': 39.46, 'weight': 0.01228831342722845, 'borrowCost': 40, }, ], 'Holding Error': 0.0744447922751884, }, 'Hedged Portfolio': { 'Constituents': [ { 'country': 'United States', 'name': 'Apple Inc', 'transactionCost': 1.1936100899058002, 'sector': 'Information Technology', 'shares': 26, 'assetId': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW', 'currency': 'USD', 'industry': 'Technology Hardware, Storage & Peripherals', 'marginalCost': 0.09597615517006632, 'advPercentage': 0, 'notional': 3819.9199999999996, 'price': 146.92, 'weight': 0.08040829746809594, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Albemarle Corp', 'transactionCost': 5.661775350570679, 'sector': 'Materials', 'shares': -2.9210200815542167, 'assetId': 'MA4B66MW5E27U9XPVYM', 'bbid': 'ALB UN', 'currency': 'USD', 'industry': 'Chemicals', 'marginalCost': 0.078815239121909, 'advPercentage': 2e-06, 'notional': -661.3189464638747, 'price': 226.4, 'weight': -0.01392058749098281, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Advanced Micro Devices', 'transactionCost': 0.5830498039722443, 'sector': 'Information Technology', 'shares': -0.8120055824108264, 'assetId': 'MA4B66MW5E27U9YGMB9', 'bbid': 'AMD UW', 'currency': 'USD', 'industry': 'Semiconductors & Semiconductor Equipment', 'marginalCost': 0.0010543794559583631, 'advPercentage': 0, 'notional': -85.91019061906543, 'price': 105.8, 'weight': -0.0018083866056981923, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Best Buy Co Inc', 'transactionCost': 2.401727557182312, 'sector': 'Consumer Discretionary', 'shares': -4.535031177626968, 'assetId': 'MA4B66MW5E27UA39J2L', 'bbid': 'BBY UN', 'currency': 'USD', 'industry': 'Specialty Retail', 'marginalCost': 0.02401392704093342, 'advPercentage': 2e-06, 'notional': -474.99916554464863, 'price': 104.74, 'weight': -0.009998605782375405, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Citigroup Inc', 'transactionCost': 1.4510516652072596, 'sector': 'Financials', 'shares': -27.227187182634943, 'assetId': 'MA4B66MW5E27UA4ZCWT', 'bbid': 'C UN', 'currency': 'USD', 'industry': 'Banks', 'marginalCost': 0.05919571023849292, 'advPercentage': 2e-06, 'notional': -1938.0311836599553, 'price': 71.18, 'weight': -0.040795039665274604, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Cummins Inc', 'transactionCost': 3.3661617040634155, 'sector': 'Industrials', 'shares': -0.4130028393296445, 'assetId': 'MA4B66MW5E27UACX6RZ', 'bbid': 'CMI UN', 'currency': 'USD', 'industry': 'Machinery', 'marginalCost': 0.006662256400232119, 'advPercentage': 0, 'notional': -94.02422640178686, 'price': 227.66, 'weight': -0.0019791848954225427, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Citrix Systems Inc', 'transactionCost': 2.754676938056946, 'sector': 'Information Technology', 'shares': -2.204015152257958, 'assetId': 'MA4B66MW5E27UAE4LLL', 'bbid': 'CTXS UW', 'currency': 'USD', 'industry': 'Software', 'marginalCost': 0.014082314238438266, 'advPercentage': 2e-06, 'notional': -242.86042962730437, 'price': 110.19, 'weight': -0.005112147288085058, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Duke Realty Corp', 'transactionCost': 1.3627572059631348, 'sector': 'Real Estate', 'shares': -8.864060939026558, 'assetId': 'MA4B66MW5E27UAEP46J', 'bbid': 'DRE UN', 'currency': 'USD', 'industry': 'Equity Real Estate Investment Trusts (REITs)', 'marginalCost': 0.012421166613716166, 'advPercentage': 5e-06, 'notional': -433.0093768714474, 'price': 48.85, 'weight': -0.009114731926834648, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Davita Inc', 'transactionCost': 3.084942579269409, 'sector': 'Health Care', 'shares': -3.4880239796169494, 'assetId': 'MA4B66MW5E27UAEP4D8', 'bbid': 'DVA UN', 'currency': 'USD', 'industry': 'Health Care Providers & Services', 'marginalCost': 0.027406811709718284, 'advPercentage': 6e-06, 'notional': -422.05090153365086, 'price': 121, 'weight': -0.008884058942908718, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Electronic Arts Inc', 'transactionCost': 2.1669615507125854, 'sector': 'Communication Services', 'shares': -6.899047429867354, 'assetId': 'MA4B66MW5E27UAEP4H9', 'bbid': 'EA UW', 'currency': 'USD', 'industry': 'Entertainment', 'marginalCost': 0.040878606822697415, 'advPercentage': 3e-06, 'notional': -896.1862611397693, 'price': 129.9, 'weight': -0.01886448184060066, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Extra Space Storage Inc', 'transactionCost': 3.9990124702453613, 'sector': 'Real Estate', 'shares': -3.1030213327842304, 'assetId': 'MA4B66MW5E27UAFU2CS', 'bbid': 'EXR UN', 'currency': 'USD', 'industry': 'Equity Real Estate Investment Trusts (REITs)', 'marginalCost': 0.04508686837043359, 'advPercentage': 5e-06, 'notional': -535.6125122518861, 'price': 172.61, 'weight': -0.011274500568803494, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'General Motors Co', 'transactionCost': 1.0103281140327454, 'sector': 'Consumer Discretionary', 'shares': -3.0290208240423557, 'assetId': 'MA4B66MW5E27UAGYYVE', 'bbid': 'GM UN', 'currency': 'USD', 'industry': 'Automobiles', 'marginalCost': 0.003364583586707687, 'advPercentage': 0, 'notional': -158.20575763973224, 'price': 52.23, 'weight': -0.003330189014812112, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'The Goldman Sachs Group, Inc.', 'transactionCost': 3.2194154492372298, 'sector': 'Financials', 'shares': 51, 'assetId': 'MA4B66MW5E27UAHKG34', 'bbid': 'GS UN', 'currency': 'USD', 'industry': 'Capital Markets', 'marginalCost': 1.3508400095029636, 'advPercentage': 2.3e-05, 'notional': 19933.350000000002, 'price': 390.85, 'weight': 0.4195917025319039, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Hormel Foods Corp', 'transactionCost': 1.4352393746376038, 'sector': 'Consumer Staples', 'shares': -11.239077266890737, 'assetId': 'MA4B66MW5E27UAJ5XEZ', 'bbid': 'HRL UN', 'currency': 'USD', 'industry': 'Food Products', 'marginalCost': 0.013853571784092618, 'advPercentage': 4e-06, 'notional': -458.55435248914205, 'price': 40.8, 'weight': -0.009652446852352158, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Iron Mountain Inc', 'transactionCost': 2.417732357978821, 'sector': 'Real Estate', 'shares': -5.0810349313170065, 'assetId': 'MA4B66MW5E27UAJQF4M', 'bbid': 'IRM UN', 'currency': 'USD', 'industry': 'Equity Real Estate Investment Trusts (REITs)', 'marginalCost': 0.011447654789702045, 'advPercentage': 3e-06, 'notional': -224.93741640940388, 'price': 44.27, 'weight': -0.004734872638786235, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'JPMorgan Chase & Co', 'transactionCost': 1.2586397729172192, 'sector': 'Financials', 'shares': -12.604086651115832, 'assetId': 'MA4B66MW5E27UAKHZFD', 'bbid': 'JPM UN', 'currency': 'USD', 'industry': 'Banks', 'marginalCost': 0.054444447777798256, 'advPercentage': 1e-06, 'notional': -2054.970287597925, 'price': 163.04, 'weight': -0.043256576622880225, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Kroger Co', 'transactionCost': 1.2731897830963135, 'sector': 'Consumer Staples', 'shares': -1.730011893560012, 'assetId': 'MA4B66MW5E27UAKNZNV', 'bbid': 'KR UN', 'currency': 'USD', 'industry': 'Food & Staples Retailing', 'marginalCost': 0.0018536665905637035, 'advPercentage': 0, 'notional': -69.16587550452927, 'price': 39.98, 'weight': -0.001455923237190695, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Mosaic Co/the', 'transactionCost': 2.1279181241989136, 'sector': 'Materials', 'shares': -5.192035694429817, 'assetId': 'MA4B66MW5E27UAL9SLW', 'bbid': 'MOS UN', 'currency': 'USD', 'industry': 'Chemicals', 'marginalCost': 0.008339681547933048, 'advPercentage': 2e-06, 'notional': -186.18640000225324, 'price': 35.86, 'weight': -0.003919174075869411, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Morgan Stanley', 'transactionCost': 2.3784881788491172, 'sector': 'Financials', 'shares': -100.16368860962515, 'assetId': 'MA4B66MW5E27UAL9STW', 'bbid': 'MS UN', 'currency': 'USD', 'industry': 'Capital Markets', 'marginalCost': 0.5160781640859926, 'advPercentage': 1.6e-05, 'notional': -10307.845194816524, 'price': 102.91, 'weight': -0.2169773928982519, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'MSCI Inc', 'transactionCost': 7.908167362213135, 'sector': 'Financials', 'shares': -0.5740039461869635, 'assetId': 'MA4B66MW5E27UAL9SUH', 'bbid': 'MSCI UN', 'currency': 'USD', 'industry': 'Capital Markets', 'marginalCost': 0.06319486932947428, 'advPercentage': 2e-06, 'notional': -379.628989889672, 'price': 661.37, 'weight': -0.007991089014053052, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Nucor Corp', 'transactionCost': 3.182479500770569, 'sector': 'Materials', 'shares': -0.7970054792874738, 'assetId': 'MA4B66MW5E27UALNB48', 'bbid': 'NUE UN', 'currency': 'USD', 'industry': 'Metals & Mining', 'marginalCost': 0.005388287534627193, 'advPercentage': 0, 'notional': -80.43379296969185, 'price': 100.92, 'weight': -0.001693109895388968, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Paypal Holdings Inc', 'transactionCost': 1.4358487129211426, 'sector': 'Information Technology', 'shares': -0.5480037674398188, 'assetId': 'MA4B66MW5E27UAM94N9', 'bbid': 'PYPL UW', 'currency': 'USD', 'industry': 'IT Services', 'marginalCost': 0.004606334069169853, 'advPercentage': 0, 'notional': -152.40532776268802, 'price': 278.11, 'weight': -0.0032080915125093927, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Qualcomm Inc', 'transactionCost': 1.0424676537513733, 'sector': 'Information Technology', 'shares': -0.6250042968063627, 'assetId': 'MA4B66MW5E27UAM94Q4', 'bbid': 'QCOM UW', 'currency': 'USD', 'industry': 'Semiconductors & Semiconductor Equipment', 'marginalCost': 0.001836148888424059, 'advPercentage': 0, 'notional': -83.67557525643583, 'price': 133.88, 'weight': -0.0017613485481459143, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Tractor Supply Company', 'transactionCost': 3.904104232788086, 'sector': 'Consumer Discretionary', 'shares': -0.49000336869618827, 'assetId': 'MA4B66MW5E27UANEQ5C', 'bbid': 'TSCO UW', 'currency': 'USD', 'industry': 'Specialty Retail', 'marginalCost': 0.00848540990065989, 'advPercentage': 1e-06, 'notional': -103.2535098516608, 'price': 210.72, 'weight': -0.002173458851174191, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Twitter Inc', 'transactionCost': 1.2658634781837463, 'sector': 'Communication Services', 'shares': -8.874061007775461, 'assetId': 'MA4B66MW5E27UANLXU9', 'bbid': 'TWTR UN', 'currency': 'USD', 'industry': 'Interactive Media & Services', 'marginalCost': 0.015894775099317016, 'advPercentage': 1e-06, 'notional': -596.5143809426665, 'price': 67.22, 'weight': -0.012556468666054534, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Wells Fargo & Co', 'transactionCost': 1.3822112533240394, 'sector': 'Financials', 'shares': -43.79430107894055, 'assetId': 'MA4B66MW5E27UANT8X5', 'bbid': 'WFC UN', 'currency': 'USD', 'industry': 'Banks', 'marginalCost': 0.061059807755110544, 'advPercentage': 2e-06, 'notional': -2098.622907702831, 'price': 47.92, 'weight': -0.044175452636686034, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Dentsply Sirona Inc', 'transactionCost': 2.9051536321640015, 'sector': 'Health Care', 'shares': -5.308036491917078, 'assetId': 'MA4B66MW5E27UANZH4M', 'bbid': 'XRAY UW', 'currency': 'USD', 'industry': 'Health Care Equipment & Supplies', 'marginalCost': 0.01947929477213156, 'advPercentage': 5e-06, 'notional': -318.5352698799438, 'price': 60.01, 'weight': -0.006705082497692817, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Meta Platforms Inc-Class A', 'transactionCost': 0.8238705992698669, 'sector': 'Communication Services', 'shares': -0.7130049017966985, 'assetId': 'MA4B66MW5E3VLSECN', 'bbid': 'FB UW', 'currency': 'USD', 'industry': 'Interactive Media & Services', 'marginalCost': 0.004364390584540722, 'advPercentage': 0, 'notional': -251.6622101381627, 'price': 352.96, 'weight': -0.005297422420958517, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Tapestry Inc', 'transactionCost': 2.314427137374878, 'sector': 'Consumer Discretionary', 'shares': -7.397050853562664, 'assetId': 'MAAHJ4JYCKZ6P025', 'bbid': 'TPR UN', 'currency': 'USD', 'industry': 'Textiles, Apparel & Luxury Goods', 'marginalCost': 0.014220203034272804, 'advPercentage': 2e-06, 'notional': -291.88762668158273, 'price': 39.46, 'weight': -0.006144156713614223, 'borrowCost': 40, }, { 'country': 'United States', 'name': 'Lumen Technologies Inc', 'transactionCost': 3.95284104347229, 'sector': 'Communication Services', 'shares': -11.81608123370237, 'assetId': 'MAQQM1QHGJDFAVKN', 'bbid': 'LUMN UN', 'currency': 'USD', 'industry': 'Diversified Telecommunication Services', 'marginalCost': 0.012712411491036973, 'advPercentage': 1e-06, 'notional': -152.78193035177165, 'price': 12.93, 'weight': -0.003216018896593429, 'borrowCost': 40, }, ], 'Transaction Cost': 2.5770571473071144, }, } def test_hedge_exclusions_to_dict(): exclusions = HedgeExclusions( assets=['GS UN'], countries=['Mexico'], regions=['Europe'], sectors=['Utilities'], industries=['Airlines'] ) assert exclusions.to_dict() == { 'classificationConstraints': [ {'name': 'Mexico', 'min': 0, 'max': 0, 'type': 'Country'}, {'name': 'Europe', 'min': 0, 'max': 0, 'type': 'Region'}, {'name': 'Utilities', 'min': 0, 'max': 0, 'type': 'Sector'}, {'name': 'Airlines', 'min': 0, 'max': 0, 'type': 'Industry'}, ], 'assetConstraints': [{'min': 0, 'max': 0, 'assetId': 'GS UN'}], } def test_hedge_constraints_to_dict(): constraints = HedgeConstraints( sectors=[Constraint(constraint_name='Software', minimum=0, maximum=20)], esg=[Constraint(constraint_name='gPercentile', minimum=75, maximum=100)], ) assert constraints.to_dict() == { 'classificationConstraints': [{'name': 'Software', 'min': 0, 'max': 20, 'type': 'Sector'}], 'esgConstraints': [{'name': 'gPercentile', 'min': 75, 'max': 100}], } def test_format_hedge_calculate_results(): assert Hedge._format_hedge_calculate_results(calculation_results) == mock_results def get_mock_hedge(mocker): mocker.patch.object(PerformanceHedgeParameters, 'to_dict', return_value={}) mocker.patch.object(PerformanceHedgeParameters, 'resolve_identifiers_in_payload', return_value={}) mocker.patch.object(GsHedgeApi, 'calculate_hedge', return_value={'result': calculation_results}) hedge = PerformanceHedge( parameters=PerformanceHedgeParameters( initial_portfolio=PositionSet( positions=[Position(asset_id='fakeId', identifier='fakeId', quantity=1)], date=dt.date(2020, 1, 1) ), universe=[], exclusions=HedgeExclusions(), constraints=HedgeConstraints(), ) ) hedge.calculate() return hedge def test_get_constituents(mocker): hedge = get_mock_hedge(mocker) constituents = hedge.get_constituents() assert constituents.shape == (28, 16) def test_get_statistics(mocker): hedge = get_mock_hedge(mocker) constituents = hedge.get_statistics() assert constituents.shape == (7, 3) def test_get_backtest_performance(mocker): hedge = get_mock_hedge(mocker) constituents = hedge.get_backtest_performance() assert constituents.shape == (262, 2) if __name__ == '__main__': pytest.main(args=[__file__]) ================================================ FILE: gs_quant/test/markets/test_instrument.py ================================================ """ Copyright 2021 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.instrument import EqOption, FXMultiCrossBinary, FXMultiCrossBinaryLeg, Instrument from gs_quant.test.utils.mock_calc import MockCalc def test_instrument_resolve(mocker): with MockCalc(mocker): eq_option = EqOption('.FTSE', strike_price='ATMS', name='FTSE_Call') assert eq_option.unresolved is None eq_option.resolve() assert eq_option.unresolved is not None assert eq_option.strike_price == 7464.8 eq_option.expiration_date = '3m' assert eq_option.unresolved is not None assert eq_option.strike_price == 7464.8 def test_nested_leg_from_dict(): mcb = FXMultiCrossBinary(legs=(FXMultiCrossBinaryLeg(pair='USDJPY'), FXMultiCrossBinaryLeg(pair='GBPUSD'))) mcb_dict = mcb.to_dict() new_mcb = Instrument.from_dict(mcb_dict) assert new_mcb == mcb ================================================ FILE: gs_quant/test/markets/test_portfolio.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from unittest import mock import gs_quant.risk as risk import numpy as np import pandas as pd from gs_quant.api.gs.assets import GsAssetApi from gs_quant.api.gs.portfolios import GsPortfolioApi from gs_quant.common import PositionSet from gs_quant.datetime import business_day_offset from gs_quant.instrument import IRSwap, IRSwaption, CurveScenario from gs_quant.markets import ( HistoricalPricingContext, PricingContext, BackToTheFuturePricingContext, historical_risk_key, ) from gs_quant.markets.portfolio import Portfolio from gs_quant.risk.results import PortfolioPath, PortfolioRiskResult from gs_quant.session import Environment, GsSession from gs_quant.target.portfolios import Portfolio as MarqueePortfolio from gs_quant.test.utils.mock_calc import MockCalc def set_session(): from gs_quant.session import OAuth2Session OAuth2Session.init = mock.MagicMock(return_value=None) GsSession.use(Environment.PROD, 'client_id', 'secret') def test_portfolio(mocker): with MockCalc(mocker): with PricingContext(pricing_date=dt.date(2020, 10, 15)): swap1 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.001, name='swap_10y@10bp') swap2 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.002, name='swap_10y@20bp') swap3 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.003, name='swap_10y@30bp') portfolio = Portfolio((swap1, swap2, swap3)) prices: PortfolioRiskResult = portfolio.dollar_price() result = portfolio.calc((risk.DollarPrice, risk.IRDelta)) assert tuple(sorted(map(lambda x: round(x, 0), prices))) == (4439480.0, 5423407.0, 6407334.0) assert round(prices.aggregate(), 2) == 16270220.58 assert round(prices[0], 0) == 6407334.0 assert round(prices[swap2], 0) == 5423407.0 assert round(prices['swap_10y@30bp'], 0) == 4439480.0 assert tuple(map(lambda x: round(x, 0), result[risk.DollarPrice])) == (6407334.0, 5423407.0, 4439480.0) assert round(result[risk.DollarPrice].aggregate(), 0) == 16270221.0 assert round(result[risk.DollarPrice]['swap_10y@30bp'], 0) == 4439480.0 assert round(result[risk.DollarPrice]['swap_10y@30bp'], 0) == round( result['swap_10y@30bp'][risk.DollarPrice], 0 ) assert round(result[risk.IRDelta].aggregate().value.sum(), 0) == 278984.0 prices_only = result[risk.DollarPrice] assert tuple(map(lambda x: round(x, 0), prices)) == tuple(map(lambda x: round(x, 0), prices_only)) swap4 = IRSwap('Pay', '10y', 'USD', fixed_rate=-0.001, name='swap_10y@-10bp') portfolio.append(swap4) assert len(portfolio.instruments) == 4 extracted_swap = portfolio.pop('swap_10y@20bp') assert extracted_swap == swap2 assert len(portfolio.instruments) == 3 swap_dict = {'swap_5': swap1, 'swap_6': swap2, 'swap_7': swap3} portfolio = Portfolio(swap_dict) assert len(portfolio) == 3 # extend a portfolio with a portfolio new_portfolio = Portfolio([IRSwap(termination_date=x, name=x) for x in ['4y', '5y']]) portfolio.extend(new_portfolio) assert len(portfolio) == 5 # extend a portfolio with a list of trades portfolio.extend([IRSwap(termination_date=x, name=x) for x in ['6y', '7y']]) assert len(portfolio) == 7 def test_construction(): swap1 = IRSwap('Pay', '10y', 'USD') swap2 = IRSwap('Pay', '5y', 'USD') my_list = [swap1, swap2] my_tuple = (swap1, swap2) my_np_arr = np.array((swap1, swap2)) p1 = Portfolio(my_list) p2 = Portfolio(my_tuple) p3 = Portfolio(my_np_arr) assert len(p1) == 2 assert len(p2) == 2 assert len(p3) == 2 assert p1 == p2 assert p2 == p3 def test_historical_pricing(mocker): with MockCalc(mocker): swap1 = IRSwap('Pay', '10y', 'USD', fixed_rate='ATM+1', name='10y@a+1') swap2 = IRSwap('Pay', '10y', 'USD', fixed_rate='ATM+2', name='10y@a+2') swap3 = IRSwap('Pay', '10y', 'USD', fixed_rate='ATM+3', name='10y@a+3') portfolio = Portfolio((swap1, swap2, swap3)) dates = (dt.date(2021, 2, 9), dt.date(2021, 2, 10), dt.date(2021, 2, 11)) with HistoricalPricingContext(dates=dates) as hpc: risk_key = hpc._PricingContext__risk_key(risk.DollarPrice, swap1.provider) results = portfolio.calc((risk.DollarPrice, risk.IRDelta)) expected = risk.SeriesWithInfo( pd.Series( data=[-580315.9786130451, -580372.2014339305, -580808.9413858932], index=[dt.date(2021, 2, 9), dt.date(2021, 2, 10), dt.date(2021, 2, 11)], ), risk_key=historical_risk_key(risk_key), ) assert results.dates == dates actual = results[risk.DollarPrice].aggregate() assert actual.equals(expected) assert ( results[dt.date(2021, 2, 9)][risk.DollarPrice]['10y@a+1'] == results[risk.DollarPrice][dt.date(2021, 2, 9)]['10y@a+1'] ) assert ( results[dt.date(2021, 2, 9)][risk.DollarPrice]['10y@a+1'] == results[risk.DollarPrice]['10y@a+1'][dt.date(2021, 2, 9)] ) assert ( results[dt.date(2021, 2, 9)][risk.DollarPrice]['10y@a+1'] == results['10y@a+1'][risk.DollarPrice][dt.date(2021, 2, 9)] ) assert ( results[dt.date(2021, 2, 9)][risk.DollarPrice]['10y@a+1'] == results['10y@a+1'][dt.date(2021, 2, 9)][risk.DollarPrice] ) assert ( results[dt.date(2021, 2, 9)][risk.DollarPrice]['10y@a+1'] == results[dt.date(2021, 2, 9)]['10y@a+1'][risk.DollarPrice] ) def test_backtothefuture_pricing(mocker): with MockCalc(mocker): swap1 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.01, name='swap1') swap2 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.02, name='swap2') swap3 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.03, name='swap3') portfolio = Portfolio((swap1, swap2, swap3)) pricing_date = dt.date(2021, 2, 10) with PricingContext(pricing_date=pricing_date): with BackToTheFuturePricingContext( dates=business_day_offset(pricing_date, [-1, 0, 1], roll='forward'), name='btf' ) as hpc: risk_key = hpc._PricingContext__risk_key(risk.DollarPrice, swap1.provider) results = portfolio.calc(risk.DollarPrice) expected = risk.SeriesWithInfo( pd.Series( data=[-22711963.80864744, -22655907.930484552, -21582551.58922608], index=business_day_offset(pricing_date, [-1, 0, 1], roll='forward'), ), risk_key=historical_risk_key(risk_key), ) actual = results[risk.DollarPrice].aggregate() assert actual.equals(expected) def test_duplicate_instrument(mocker): with MockCalc(mocker): swap1 = IRSwap('Pay', '1y', 'EUR', name='EUR1y') swap2 = IRSwap('Pay', '2y', 'EUR', name='EUR2y') swap3 = IRSwap('Pay', '3y', 'EUR', name='EUR3y') portfolio = Portfolio((swap1, swap2, swap3, swap1)) assert portfolio.paths('EUR1y') == (PortfolioPath(0), PortfolioPath(3)) assert portfolio.paths('EUR2y') == (PortfolioPath(1),) with PricingContext(pricing_date=dt.date(2020, 10, 15)): fwds: PortfolioRiskResult = portfolio.calc(risk.IRFwdRate) assert tuple(map(lambda x: round(x, 6), fwds)) == (-0.005378, -0.005224, -0.00519, -0.005378) assert round(fwds.aggregate(), 6) == -0.02117 assert round(fwds[swap1], 6) == -0.005378 def test_nested_portfolios(mocker): swap1 = IRSwap('Pay', '10y', 'USD', name='USD-swap') swap2 = IRSwap('Pay', '10y', 'EUR', name='EUR-swap') swap3 = IRSwap('Pay', '10y', 'GBP', name='GBP-swap') swap4 = IRSwap('Pay', '10y', 'JPY', name='JPY-swap') swap5 = IRSwap('Pay', '10y', 'HUF', name='HUF-swap') swap6 = IRSwap('Pay', '10y', 'CHF', name='CHF-swap') portfolio2_1 = Portfolio((swap1, swap2, swap3), name='portfolio2_1') portfolio2_2 = Portfolio((swap1, swap2, swap3), name='portfolio2_2') portfolio1_1 = Portfolio((swap4, portfolio2_1), name='portfolio1_1') portfolio1_2 = Portfolio((swap5, portfolio2_2), name='USD-swap') portfolio = Portfolio((swap6, portfolio1_1, portfolio1_2), name='portfolio') assert portfolio.paths('USD-swap') == (PortfolioPath(2), PortfolioPath((1, 1, 0)), PortfolioPath((2, 1, 0))) def test_single_instrument(mocker): with MockCalc(mocker): swap1 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.0, name='10y@0') portfolio = Portfolio(swap1) assert portfolio.paths('10y@0') == (PortfolioPath(0),) with PricingContext(pricing_date=dt.date(2020, 10, 15)): prices: PortfolioRiskResult = portfolio.dollar_price() assert tuple(map(lambda x: round(x, 0), prices)) == (7391261.0,) assert round(prices.aggregate(), 0) == 7391261.0 assert round(prices[swap1], 0) == 7391261.0 def test_results_with_resolution(mocker): with MockCalc(mocker): swap1 = IRSwap('Pay', '10y', 'USD', name='swap1') swap2 = IRSwap('Pay', '10y', 'GBP', name='swap2') swap3 = IRSwap('Pay', '10y', 'EUR', name='swap3') portfolio = Portfolio((swap1, swap2, swap3)) with PricingContext(pricing_date=dt.date(2020, 10, 15)): result = portfolio.calc((risk.DollarPrice, risk.IRDelta)) # Check that we've got results assert result[swap1][risk.DollarPrice] is not None # Now resolve portfolio and assert that we can still get the result orig_swap1 = swap1.clone() with PricingContext(pricing_date=dt.date(2020, 10, 15)): portfolio.resolve() # Assert that the resolved swap is indeed different and that we can retrieve results by both assert swap1 != orig_swap1 assert result[swap1][risk.DollarPrice] is not None assert result[orig_swap1][risk.DollarPrice] is not None # Now reset the instruments and portfolio swap1 = IRSwap('Pay', '10y', 'USD', name='swap1') swap2 = IRSwap('Pay', '10y', 'GBP', name='swap2') swap3 = IRSwap('Pay', '10y', 'EUR', name='swap3') portfolio = Portfolio((swap1, swap2, swap3, swap1)) with PricingContext(dt.date(2020, 10, 14)): # Resolve under a different pricing date portfolio.resolve() assert portfolio.instruments[0].termination_date == dt.date(2030, 10, 16) assert portfolio.instruments[1].termination_date == dt.date(2030, 10, 14) assert round(swap1.fixed_rate, 4) == 0.0075 assert round(swap2.fixed_rate, 4) == 0.004 assert round(swap3.fixed_rate, 4) == -0.0027 # Assert that after resolution under a different context, we cannot retrieve the result try: _ = result[swap1][risk.DollarPrice] assert False except KeyError: assert True # Assert that if we resolve first in one context before pricing under a different context # we can slice the riskresult with the origin with CurveScenario(parallel_shift=5, name='parallel shift 5bp'): result2 = portfolio.calc((risk.DollarPrice, risk.IRDelta)) assert result2[swap1][risk.DollarPrice] is not None assert result2[orig_swap1][risk.DollarPrice] is not None # Resolve again and check we get the same values with PricingContext(dt.date(2020, 10, 14)): # Resolve under a different pricing date portfolio.resolve() assert portfolio.instruments[0].termination_date == dt.date(2030, 10, 16) assert portfolio.instruments[1].termination_date == dt.date(2030, 10, 14) assert round(swap1.fixed_rate, 4) == 0.0075 assert round(swap2.fixed_rate, 4) == 0.004 assert round(swap3.fixed_rate, 4) == -0.0027 def test_portfolio_overrides(mocker): swap_1 = IRSwap("Pay", "5y", "EUR", fixed_rate=-0.005, name="5y") swap_2 = IRSwap("Pay", "10y", "EUR", fixed_rate=-0.005, name="10y") swap_3 = IRSwap("Pay", "5y", "GBP", fixed_rate=-0.005, name="5y") swap_4 = IRSwap("Pay", "10y", "GBP", fixed_rate=-0.005, name="10y") eur_port = Portfolio([swap_1, swap_2], name="EUR") gbp_port = Portfolio([swap_3, swap_4], name="GBP") # override instruments after portfolio construction for idx in range(len(eur_port)): eur_port[idx].fixed_rate = eur_port[idx].fixed_rate - 0.0005 assert eur_port[swap_1] is not None with MockCalc(mocker): # override instruments after portfolio construction and resolution gbp_port.resolve() for idx in range(len(gbp_port)): gbp_port[idx].notional_amount = gbp_port[idx].notional_amount - 1 with PricingContext(dt.date(2020, 1, 14)): r1 = eur_port.calc(risk.Price) r2 = eur_port.calc((risk.Price, risk.DollarPrice)) r3 = gbp_port.calc(risk.Price) r4 = gbp_port.calc((risk.DollarPrice, risk.Price)) assert gbp_port[swap_3] is not None assert r1[eur_port[0]] is not None assert r1['5y'] is not None assert r1.to_frame() is not None assert r2[eur_port[0]] is not None assert r2[risk.Price][0] is not None assert r2[0][risk.Price] is not None assert r3[gbp_port[0]] is not None assert r3.to_frame() is not None assert r4[gbp_port[0]] is not None assert r4[risk.DollarPrice][0] is not None assert r4[0][risk.DollarPrice] is not None def test_from_frame(): swap = IRSwap('Receive', '3m', 'USD', fixed_rate=0, notional_amount=1) swaption = IRSwaption(notional_currency='GBP', expiration_date='10y', effective_date='0b') portfolio = Portfolio((swap, swaption)) port_df = portfolio.to_frame() new_port_df = Portfolio.from_frame(port_df) assert new_port_df[swap] == swap assert new_port_df[swaption] == swaption def test_single_instrument_new_mock(mocker): with MockCalc(mocker): with PricingContext(pricing_date=dt.date(2020, 10, 15)): swap1 = IRSwap('Pay', '10y', 'USD', name='swap1') portfolio = Portfolio(swap1) fwd: PortfolioRiskResult = portfolio.calc(risk.IRFwdRate) assert portfolio.paths('swap1') == (PortfolioPath(0),) assert tuple(map(lambda x: round(x, 6), fwd)) == (0.007512,) assert round(fwd.aggregate(), 2) == 0.01 assert round(fwd[swap1], 6) == 0.007512 def test_get_instruments(mocker): mocker.patch.object( GsPortfolioApi, 'get_position_dates', return_value=([dt.date(2021, 1, 2), dt.date(2021, 5, 6), dt.date(2021, 6, 1)]), ) mocker.patch.object( GsPortfolioApi, 'get_positions_for_date', return_value=(PositionSet(position_date=dt.date(2021, 6, 1), positions=[])), ) mocker.patch.object( GsPortfolioApi, 'get_portfolio', return_value=(MarqueePortfolio(id_='id', name='name', currency='USD')) ) mocker.patch.object(GsAssetApi, 'get_instruments_for_positions', return_value=([])) port = Portfolio.get(portfolio_id='id') port._get_instruments(dt.date(2021, 5, 14), False) GsPortfolioApi.get_positions_for_date.assert_called_with('id', dt.date(2021, 5, 6)) def test_clone(): old_p = Portfolio((IRSwap(name='c'), Portfolio((IRSwap(name='a'), IRSwap(name='b'))))) old_p.priceables[1].instruments[0].name = 'changed_name' new_p = old_p.clone() assert old_p['changed_name'] == () assert new_p['changed_name'] == IRSwap(name='changed_name') # Check clone is deep inst1 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.0, name='10y@0') inst2 = IRSwap('Pay', '5y', 'USD', fixed_rate=0.0, name='5y@0') port = Portfolio([inst1, Portfolio(inst2)]) copy = port.clone(True) # Make some edits to the copy copy[0].fixed_rate = 1.0 copy[1][0].fixed_rate = 2.0 # Check not modified in place assert port[0].fixed_rate == 0.0 assert port[1][0].fixed_rate == 0.0 ================================================ FILE: gs_quant/test/markets/test_portfolio_manager.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import copy import datetime as dt import pytest from gs_quant.api.gs.assets import GsAssetApi from gs_quant.api.gs.carbon import ( GsCarbonApi, CarbonTargetCoverageCategory, CarbonScope, CarbonEmissionsAllocationCategory, CarbonEmissionsIntensityType, CarbonCard, CarbonCoverageCategory, CarbonEntityType, ) from gs_quant.api.gs.esg import GsEsgApi, ESGMeasure from gs_quant.api.gs.portfolios import GsPortfolioApi from gs_quant.api.gs.reports import GsReportApi from gs_quant.api.gs.scenarios import GsFactorScenarioApi from gs_quant.entities.entitlements import Entitlements, User, EntitlementBlock from gs_quant.markets.portfolio_manager import PortfolioManager, ScenarioCalculationMeasure from gs_quant.markets.report import FactorRiskReport, PerformanceReport from gs_quant.markets.scenario import FactorScenario, HistoricalSimulationParameters, FactorScenarioType from gs_quant.models.risk_model import MacroRiskModel, FactorType, UniverseIdentifier, CoverageType, Term from gs_quant.session import GsSession, Environment from gs_quant.target.portfolios import Portfolio as TargetPortfolio from gs_quant.target.reports import ReportStatus, PositionSourceType, ReportType, Report import pandas as pd import numpy as np esg_data = { 'pricingDate': '2021-08-25', 'quintiles': [ { 'measure': 'gPercentile', 'results': [ { 'name': 'Q1', 'description': '<=80-100%', 'gross': 91.03557560284325, 'long': 91.03557560284325, 'short': 0, }, {'name': 'Q2', 'description': '<=60-80%', 'gross': 0, 'long': 0, 'short': 0}, {'name': 'Q3', 'description': '<=40-60%', 'gross': 0, 'long': 0, 'short': 0}, { 'name': 'Q4', 'description': '<=20-40%', 'gross': 8.955474904385047, 'long': 0, 'short': 8.955474904385047, }, {'name': 'Q5', 'description': '<=0-20%', 'gross': 0, 'long': 0, 'short': 0}, { 'name': 'Not Included', 'description': 'Not Included', 'gross': 0.008949492771697426, 'long': 0.008949492771697426, 'short': 0, }, ], } ], 'summary': { 'gross': { 'gPercentile': 91.71440761636107, 'gRegionalPercentile': 84.58263751763046, 'esPercentile': 90.07443582510577, 'esDisclosurePercentage': 78.50077574047954, 'esMomentumPercentile': 74.20073342736248, }, 'long': { 'gPercentile': 97.39, 'gRegionalPercentile': 92.62, 'esPercentile': 94.24, 'esDisclosurePercentage': 83.33, 'esMomentumPercentile': 76.85, }, 'short': { 'gPercentile': 34.02, 'gRegionalPercentile': 2.88, 'esPercentile': 47.73, 'esDisclosurePercentage': 29.41, 'esMomentumPercentile': 47.27, }, }, 'weightsBySector': [ {'name': 'Diversified Financials', 'gross': 91.03557560284325, 'long': 91.03557560284325, 'short': 0}, {'name': 'Automobile Manufacturers', 'gross': 8.955474904385047, 'long': 0, 'short': 8.955474904385047}, {'name': 'Total', 'gross': 99.99105050722832, 'long': 91.03557560284325, 'short': 8.955474904385047}, {'name': 'Not Included', 'gross': 0.008949492771697426, 'long': 0.008949492771697426, 'short': 0}, ], 'measuresBySector': [ { 'measure': 'gPercentile', 'results': [ {'name': 'Diversified Financials', 'gross': 97.39, 'long': 97.39, 'short': 0}, {'name': 'Automobile Manufacturers', 'gross': 34.02, 'long': 0, 'short': 34.02}, {'name': 'Total', 'gross': 91.71440761636107, 'long': 97.39, 'short': 34.02}, ], } ], 'weightsByRegion': [ {'name': 'N. America', 'gross': 99.99105050722832, 'long': 91.03557560284325, 'short': 8.955474904385047}, {'name': 'Total', 'gross': 99.99105050722832, 'long': 91.03557560284325, 'short': 8.955474904385047}, {'name': 'Not Included', 'gross': 0.008949492771697426, 'long': 0.008949492771697426, 'short': 0}, ], 'measuresByRegion': [ { 'measure': 'gPercentile', 'results': [ {'name': 'N. America', 'gross': 91.71440761636107, 'long': 97.39, 'short': 34.02}, {'name': 'Total', 'gross': 91.71440761636107, 'long': 97.39, 'short': 34.02}, ], } ], 'topTenRanked': [ { 'measure': 'gPercentile', 'results': [ {'assetId': 'MA4B66MW5E27UAHKG34', 'name': 'The Goldman Sachs Group, Inc.', 'value': 97.39}, {'assetId': 'MA4B66MW5E27UANEQ6R', 'name': 'Tesla Inc', 'value': 34.02}, ], } ], 'bottomTenRanked': [ { 'measure': 'gPercentile', 'results': [ {'assetId': 'MA4B66MW5E27UANEQ6R', 'name': 'Tesla Inc', 'value': 34.02}, {'assetId': 'MA4B66MW5E27UAHKG34', 'name': 'The Goldman Sachs Group, Inc.', 'value': 97.39}, ], } ], 'noESGData': { 'gross': { 'weight': 0.008949492771697426, 'assets': [{'assetId': 'MA4B66MW5E27UAL9SX6', 'name': 'Mustek LTD', 'weight': 0.008949492771697426}], }, 'long': { 'weight': 0.008949492771697426, 'assets': [{'assetId': 'MA4B66MW5E27UAL9SX6', 'name': 'Mustek LTD', 'weight': 0.008949492771697426}], }, 'short': {'weight': 0.0, 'assets': []}, }, } carbon_data = { "coverage": { "weights": { "portfolio": { "totalGHG": { "reportedVerified": 27.432014892432875, "reportedUnverified": 72.56798510756713, "estimated": 0, "missing": 0, }, "scope1": { "reportedVerified": 27.432014892432875, "reportedUnverified": 72.56798510756713, "estimated": 0, "missing": 0, }, "scope2": { "reportedVerified": 27.432014892432875, "reportedUnverified": 72.56798510756713, "estimated": 0, "missing": 0, }, }, "benchmark": { "totalGHG": { "reportedVerified": 27.432014892432875, "reportedUnverified": 72.56798510756713, "estimated": 0, "missing": 0, }, "scope1": { "reportedVerified": 27.432014892432875, "reportedUnverified": 72.56798510756713, "estimated": 0, "missing": 0, }, "scope2": { "reportedVerified": 27.432014892432875, "reportedUnverified": 72.56798510756713, "estimated": 0, "missing": 0, }, }, }, "numberOfCompanies": { "portfolio": { "totalGHG": {"reportedVerified": 50, "reportedUnverified": 50, "estimated": 0, "missing": 0}, "scope1": {"reportedVerified": 50, "reportedUnverified": 50, "estimated": 0, "missing": 0}, "scope2": {"reportedVerified": 50, "reportedUnverified": 50, "estimated": 0, "missing": 0}, }, "benchmark": { "totalGHG": {"reportedVerified": 50, "reportedUnverified": 50, "estimated": 0, "missing": 0}, "scope1": {"reportedVerified": 50, "reportedUnverified": 50, "estimated": 0, "missing": 0}, "scope2": {"reportedVerified": 50, "reportedUnverified": 50, "estimated": 0, "missing": 0}, }, }, }, "sbtiAndNetZeroTargets": { "capitalAllocated": { "scienceBasedTarget": { "portfolio": {"targetsSet": 0, "targetsNotSet": 0, "missing": 100}, "benchmark": {"targetsSet": 0, "targetsNotSet": 0, "missing": 100}, }, "netZeroEmissionsTarget": { "portfolio": {"targetsSet": 0, "targetsNotSet": 0, "missing": 100}, "benchmark": {"targetsSet": 0, "targetsNotSet": 0, "missing": 100}, }, }, "portfolioEmissions": { "scienceBasedTarget": { "portfolio": {"targetsSet": 0, "targetsNotSet": 0, "missing": 99.99999999999997}, "benchmark": {"targetsSet": 0, "targetsNotSet": 0, "missing": 99.99999999999997}, }, "netZeroEmissionsTarget": { "portfolio": {"targetsSet": 0, "targetsNotSet": 0, "missing": 99.99999999999997}, "benchmark": {"targetsSet": 0, "targetsNotSet": 0, "missing": 99.99999999999997}, }, }, }, "emissions": { "totalGHG": { "portfolio": [ { "year": "2016", "portfolioEmissions": 0.03161404863641, "emissionsIntensityRevenue": 5.165017590856768, "emissionsIntensityEnterpriseValue": 0.6417863895887255, "emissionsIntensityMarketCap": 2.136864492580658, }, { "year": "2017", "portfolioEmissions": 0.02557473780478448, "emissionsIntensityRevenue": 4.152366329764957, "emissionsIntensityEnterpriseValue": 0.5150082275283909, "emissionsIntensityMarketCap": 1.8566214684311595, }, { "year": "2018", "portfolioEmissions": 0.025883906301863414, "emissionsIntensityRevenue": 3.6355870431327695, "emissionsIntensityEnterpriseValue": 0.514601211522207, "emissionsIntensityMarketCap": 2.7567883404035856, }, { "year": "2019", "portfolioEmissions": 0.02488419915490657, "emissionsIntensityRevenue": 3.3508727584974998, "emissionsIntensityEnterpriseValue": 0.5058550746653679, "emissionsIntensityMarketCap": 1.915020016654778, }, { "year": "2021", "portfolioEmissions": 0.016784770256626273, "emissionsIntensityRevenue": 3.010184293088453, "emissionsIntensityEnterpriseValue": 0.3321381204145429, "emissionsIntensityMarketCap": 1.370060537473572, }, ], "benchmark": [ { "year": "2016", "portfolioEmissions": 0.03161404863641, "emissionsIntensityRevenue": 5.165017590856768, "emissionsIntensityEnterpriseValue": 0.6417863895887255, "emissionsIntensityMarketCap": 2.136864492580658, }, { "year": "2017", "portfolioEmissions": 0.02557473780478448, "emissionsIntensityRevenue": 4.152366329764957, "emissionsIntensityEnterpriseValue": 0.5150082275283909, "emissionsIntensityMarketCap": 1.8566214684311595, }, { "year": "2018", "portfolioEmissions": 0.025883906301863414, "emissionsIntensityRevenue": 3.6355870431327695, "emissionsIntensityEnterpriseValue": 0.514601211522207, "emissionsIntensityMarketCap": 2.7567883404035856, }, { "year": "2019", "portfolioEmissions": 0.02488419915490657, "emissionsIntensityRevenue": 3.3508727584974998, "emissionsIntensityEnterpriseValue": 0.5058550746653679, "emissionsIntensityMarketCap": 1.915020016654778, }, { "year": "2021", "portfolioEmissions": 0.016784770256626273, "emissionsIntensityRevenue": 3.010184293088453, "emissionsIntensityEnterpriseValue": 0.3321381204145429, "emissionsIntensityMarketCap": 1.370060537473572, }, ], }, "scope1": { "portfolio": [ { "year": "2016", "portfolioEmissions": 0.0016712755532904242, "emissionsIntensityRevenue": 0.27028638660867355, "emissionsIntensityEnterpriseValue": 0.0339529985038069, "emissionsIntensityMarketCap": 0.11170603861184672, }, { "year": "2017", "portfolioEmissions": 0.0015352826311627388, "emissionsIntensityRevenue": 0.24110061666622948, "emissionsIntensityEnterpriseValue": 0.030986711463004902, "emissionsIntensityMarketCap": 0.1064282069728648, }, { "year": "2018", "portfolioEmissions": 0.001582778525839715, "emissionsIntensityRevenue": 0.21882979028252364, "emissionsIntensityEnterpriseValue": 0.03149430343217062, "emissionsIntensityMarketCap": 0.16344626276984683, }, { "year": "2019", "portfolioEmissions": 0.0015833905465049197, "emissionsIntensityRevenue": 0.22245885390039408, "emissionsIntensityEnterpriseValue": 0.032053991311445595, "emissionsIntensityMarketCap": 0.1326706476709381, }, { "year": "2021", "portfolioEmissions": 0.0009713787667226322, "emissionsIntensityRevenue": 0.1766993497855956, "emissionsIntensityEnterpriseValue": 0.019200832225828832, "emissionsIntensityMarketCap": 0.08410551021114267, }, ], "benchmark": [ { "year": "2016", "portfolioEmissions": 0.0016712755532904242, "emissionsIntensityRevenue": 0.27028638660867355, "emissionsIntensityEnterpriseValue": 0.0339529985038069, "emissionsIntensityMarketCap": 0.11170603861184672, }, { "year": "2017", "portfolioEmissions": 0.0015352826311627388, "emissionsIntensityRevenue": 0.24110061666622948, "emissionsIntensityEnterpriseValue": 0.030986711463004902, "emissionsIntensityMarketCap": 0.1064282069728648, }, { "year": "2018", "portfolioEmissions": 0.001582778525839715, "emissionsIntensityRevenue": 0.21882979028252364, "emissionsIntensityEnterpriseValue": 0.03149430343217062, "emissionsIntensityMarketCap": 0.16344626276984683, }, { "year": "2019", "portfolioEmissions": 0.0015833905465049197, "emissionsIntensityRevenue": 0.22245885390039408, "emissionsIntensityEnterpriseValue": 0.032053991311445595, "emissionsIntensityMarketCap": 0.1326706476709381, }, { "year": "2021", "portfolioEmissions": 0.0009713787667226322, "emissionsIntensityRevenue": 0.1766993497855956, "emissionsIntensityEnterpriseValue": 0.019200832225828832, "emissionsIntensityMarketCap": 0.08410551021114267, }, ], }, "scope2": { "portfolio": [ { "year": "2016", "portfolioEmissions": 0.02994277308311958, "emissionsIntensityRevenue": 4.894731204248094, "emissionsIntensityEnterpriseValue": 0.6078333910849186, "emissionsIntensityMarketCap": 2.0251584539688117, }, { "year": "2017", "portfolioEmissions": 0.02403945517362174, "emissionsIntensityRevenue": 3.9112657130987274, "emissionsIntensityEnterpriseValue": 0.484021516065386, "emissionsIntensityMarketCap": 1.750193261458295, }, { "year": "2018", "portfolioEmissions": 0.0243011277760237, "emissionsIntensityRevenue": 3.4167572528502457, "emissionsIntensityEnterpriseValue": 0.4831069080900363, "emissionsIntensityMarketCap": 2.593342077633739, }, { "year": "2019", "portfolioEmissions": 0.02330080860840165, "emissionsIntensityRevenue": 3.1284139045971058, "emissionsIntensityEnterpriseValue": 0.4738010833539224, "emissionsIntensityMarketCap": 1.7823493689838397, }, { "year": "2021", "portfolioEmissions": 0.015813391489903638, "emissionsIntensityRevenue": 2.8334849433028575, "emissionsIntensityEnterpriseValue": 0.312937288188714, "emissionsIntensityMarketCap": 1.2859550272624294, }, ], "benchmark": [ { "year": "2016", "portfolioEmissions": 0.02994277308311958, "emissionsIntensityRevenue": 4.894731204248094, "emissionsIntensityEnterpriseValue": 0.6078333910849186, "emissionsIntensityMarketCap": 2.0251584539688117, }, { "year": "2017", "portfolioEmissions": 0.02403945517362174, "emissionsIntensityRevenue": 3.9112657130987274, "emissionsIntensityEnterpriseValue": 0.484021516065386, "emissionsIntensityMarketCap": 1.750193261458295, }, { "year": "2018", "portfolioEmissions": 0.0243011277760237, "emissionsIntensityRevenue": 3.4167572528502457, "emissionsIntensityEnterpriseValue": 0.4831069080900363, "emissionsIntensityMarketCap": 2.593342077633739, }, { "year": "2019", "portfolioEmissions": 0.02330080860840165, "emissionsIntensityRevenue": 3.1284139045971058, "emissionsIntensityEnterpriseValue": 0.4738010833539224, "emissionsIntensityMarketCap": 1.7823493689838397, }, { "year": "2021", "portfolioEmissions": 0.015813391489903638, "emissionsIntensityRevenue": 2.8334849433028575, "emissionsIntensityEnterpriseValue": 0.312937288188714, "emissionsIntensityMarketCap": 1.2859550272624294, }, ], }, }, "allocations": { "totalGHG": { "portfolio": { "gicsSector": [ { "name": "Information Technology", "portfolioEmissions": 0.00641159391374359, "capitalAllocated": 14294, }, {"name": "Financials", "portfolioEmissions": 0.010373176342882682, "capitalAllocated": 37813}, ], "gicsIndustry": [ { "name": "Technology Hardware & Equipment", "portfolioEmissions": 0.00641159391374359, "capitalAllocated": 14294, }, { "name": "Diversified Financials", "portfolioEmissions": 0.010373176342882682, "capitalAllocated": 37813, }, ], "region": [{"name": "Americas", "portfolioEmissions": 0.016784770256626273, "capitalAllocated": 52107}], }, "benchmark": { "gicsSector": [ { "name": "Information Technology", "portfolioEmissions": 0.00641159391374359, "capitalAllocated": 14294, }, {"name": "Financials", "portfolioEmissions": 0.010373176342882682, "capitalAllocated": 37813}, ], "gicsIndustry": [ { "name": "Technology Hardware & Equipment", "portfolioEmissions": 0.00641159391374359, "capitalAllocated": 14294, }, { "name": "Diversified Financials", "portfolioEmissions": 0.010373176342882682, "capitalAllocated": 37813, }, ], "region": [{"name": "Americas", "portfolioEmissions": 0.016784770256626273, "capitalAllocated": 52107}], }, }, "scope1": { "portfolio": { "gicsSector": [ { "name": "Information Technology", "portfolioEmissions": 0.000324399112301343, "capitalAllocated": 14294, }, {"name": "Financials", "portfolioEmissions": 0.0006469796544212892, "capitalAllocated": 37813}, ], "gicsIndustry": [ { "name": "Technology Hardware & Equipment", "portfolioEmissions": 0.000324399112301343, "capitalAllocated": 14294, }, { "name": "Diversified Financials", "portfolioEmissions": 0.0006469796544212892, "capitalAllocated": 37813, }, ], "region": [ {"name": "Americas", "portfolioEmissions": 0.0009713787667226322, "capitalAllocated": 52107} ], }, "benchmark": { "gicsSector": [ { "name": "Information Technology", "portfolioEmissions": 0.000324399112301343, "capitalAllocated": 14294, }, {"name": "Financials", "portfolioEmissions": 0.0006469796544212892, "capitalAllocated": 37813}, ], "gicsIndustry": [ { "name": "Technology Hardware & Equipment", "portfolioEmissions": 0.000324399112301343, "capitalAllocated": 14294, }, { "name": "Diversified Financials", "portfolioEmissions": 0.0006469796544212892, "capitalAllocated": 37813, }, ], "region": [ {"name": "Americas", "portfolioEmissions": 0.0009713787667226322, "capitalAllocated": 52107} ], }, }, "scope2": { "portfolio": { "gicsSector": [ { "name": "Information Technology", "portfolioEmissions": 0.006087194801442247, "capitalAllocated": 14294, }, {"name": "Financials", "portfolioEmissions": 0.009726196688461392, "capitalAllocated": 37813}, ], "gicsIndustry": [ { "name": "Technology Hardware & Equipment", "portfolioEmissions": 0.006087194801442247, "capitalAllocated": 14294, }, { "name": "Diversified Financials", "portfolioEmissions": 0.009726196688461392, "capitalAllocated": 37813, }, ], "region": [{"name": "Americas", "portfolioEmissions": 0.015813391489903638, "capitalAllocated": 52107}], }, "benchmark": { "gicsSector": [ { "name": "Information Technology", "portfolioEmissions": 0.006087194801442247, "capitalAllocated": 14294, }, {"name": "Financials", "portfolioEmissions": 0.009726196688461392, "capitalAllocated": 37813}, ], "gicsIndustry": [ { "name": "Technology Hardware & Equipment", "portfolioEmissions": 0.006087194801442247, "capitalAllocated": 14294, }, { "name": "Diversified Financials", "portfolioEmissions": 0.009726196688461392, "capitalAllocated": 37813, }, ], "region": [{"name": "Americas", "portfolioEmissions": 0.015813391489903638, "capitalAllocated": 52107}], }, }, }, "attribution": { "totalGHG": [ { "sector": "Information Technology", "weightPortfolio": 27.432014892432875, "weightBenchmark": 27.432014892432875, "weightComparison": 0, "emissionsIntensityEnterpriseValue": { "emissionsIntensityPortfolio": 0.4689622231002765, "emissionsIntensityBenchmark": 0.4689622231002765, "emissionsIntensityComparison": 0, "sectorAllocation": 0, "securityAllocation": 0, }, "emissionsIntensityMarketCap": { "emissionsIntensityPortfolio": 0.41664352605700944, "emissionsIntensityBenchmark": 0.41664352605700944, "emissionsIntensityComparison": 0, "sectorAllocation": 0, "securityAllocation": 0, }, "emissionsIntensityRevenue": { "emissionsIntensityPortfolio": 3.420131314973555, "emissionsIntensityBenchmark": 3.420131314973555, "emissionsIntensityComparison": 0, "sectorAllocation": 0, "securityAllocation": 0, }, }, { "sector": "Financials", "weightPortfolio": 72.56798510756713, "weightBenchmark": 72.56798510756713, "weightComparison": 0, "emissionsIntensityEnterpriseValue": { "emissionsIntensityPortfolio": 0.2804161273489338, "emissionsIntensityBenchmark": 0.2804161273489338, "emissionsIntensityComparison": 0, "sectorAllocation": 0, "securityAllocation": 0, }, "emissionsIntensityMarketCap": { "emissionsIntensityPortfolio": 1.730469464593566, "emissionsIntensityBenchmark": 1.730469464593566, "emissionsIntensityComparison": 0, "sectorAllocation": 0, "securityAllocation": 0, }, "emissionsIntensityRevenue": { "emissionsIntensityPortfolio": 2.855216881594373, "emissionsIntensityBenchmark": 2.855216881594373, "emissionsIntensityComparison": 0, "sectorAllocation": 0, "securityAllocation": 0, }, }, { "sector": "Total", "weightPortfolio": 100, "weightBenchmark": 100, "weightComparison": 0, "emissionsIntensityEnterpriseValue": { "emissionsIntensityPortfolio": 0.3321381204145429, "emissionsIntensityBenchmark": 0.3321381204145429, "emissionsIntensityComparison": 0, "sectorAllocation": 0, "securityAllocation": 0, }, "emissionsIntensityMarketCap": { "emissionsIntensityPortfolio": 1.370060537473572, "emissionsIntensityBenchmark": 1.370060537473572, "emissionsIntensityComparison": 0, "sectorAllocation": 0, "securityAllocation": 0, }, "emissionsIntensityRevenue": { "emissionsIntensityPortfolio": 3.010184293088453, "emissionsIntensityBenchmark": 3.010184293088453, "emissionsIntensityComparison": 0, "sectorAllocation": 0, "securityAllocation": 0, }, }, ], "scope1": [ { "sector": "Information Technology", "weightPortfolio": 27.432014892432875, "weightBenchmark": 27.432014892432875, "weightComparison": 0, "emissionsIntensityEnterpriseValue": { "emissionsIntensityPortfolio": 0.02372747415435862, "emissionsIntensityBenchmark": 0.02372747415435862, "emissionsIntensityComparison": 0, "sectorAllocation": 0, "securityAllocation": 0, }, "emissionsIntensityMarketCap": { "emissionsIntensityPortfolio": 0.021080372808589036, "emissionsIntensityBenchmark": 0.021080372808589036, "emissionsIntensityComparison": 0, "sectorAllocation": 0, "securityAllocation": 0, }, "emissionsIntensityRevenue": { "emissionsIntensityPortfolio": 0.17304395403975925, "emissionsIntensityBenchmark": 0.17304395403975925, "emissionsIntensityComparison": 0, "sectorAllocation": 0, "securityAllocation": 0, }, }, { "sector": "Financials", "weightPortfolio": 72.56798510756713, "weightBenchmark": 72.56798510756713, "weightComparison": 0, "emissionsIntensityEnterpriseValue": { "emissionsIntensityPortfolio": 0.01748967945491923, "emissionsIntensityBenchmark": 0.01748967945491923, "emissionsIntensityComparison": 0, "sectorAllocation": 0, "securityAllocation": 0, }, "emissionsIntensityMarketCap": { "emissionsIntensityPortfolio": 0.10793015554560705, "emissionsIntensityBenchmark": 0.10793015554560705, "emissionsIntensityComparison": 0, "sectorAllocation": 0, "securityAllocation": 0, }, "emissionsIntensityRevenue": { "emissionsIntensityPortfolio": 0.17808115569337823, "emissionsIntensityBenchmark": 0.17808115569337823, "emissionsIntensityComparison": 0, "sectorAllocation": 0, "securityAllocation": 0, }, }, { "sector": "Total", "weightPortfolio": 100, "weightBenchmark": 100, "weightComparison": 0, "emissionsIntensityEnterpriseValue": { "emissionsIntensityPortfolio": 0.019200832225828832, "emissionsIntensityBenchmark": 0.019200832225828832, "emissionsIntensityComparison": 0, "sectorAllocation": 0, "securityAllocation": 0, }, "emissionsIntensityMarketCap": { "emissionsIntensityPortfolio": 0.08410551021114267, "emissionsIntensityBenchmark": 0.08410551021114267, "emissionsIntensityComparison": 0, "sectorAllocation": 0, "securityAllocation": 0, }, "emissionsIntensityRevenue": { "emissionsIntensityPortfolio": 0.1766993497855956, "emissionsIntensityBenchmark": 0.1766993497855956, "emissionsIntensityComparison": 0, "sectorAllocation": 0, "securityAllocation": 0, }, }, ], "scope2": [ { "sector": "Information Technology", "weightPortfolio": 27.432014892432875, "weightBenchmark": 27.432014892432875, "weightComparison": 0, "emissionsIntensityEnterpriseValue": { "emissionsIntensityPortfolio": 0.44523474894591797, "emissionsIntensityBenchmark": 0.44523474894591797, "emissionsIntensityComparison": 0, "sectorAllocation": 0, "securityAllocation": 0, }, "emissionsIntensityMarketCap": { "emissionsIntensityPortfolio": 0.3955631532484204, "emissionsIntensityBenchmark": 0.3955631532484204, "emissionsIntensityComparison": 0, "sectorAllocation": 0, "securityAllocation": 0, }, "emissionsIntensityRevenue": { "emissionsIntensityPortfolio": 3.2470873609337954, "emissionsIntensityBenchmark": 3.2470873609337954, "emissionsIntensityComparison": 0, "sectorAllocation": 0, "securityAllocation": 0, }, }, { "sector": "Financials", "weightPortfolio": 72.56798510756713, "weightBenchmark": 72.56798510756713, "weightComparison": 0, "emissionsIntensityEnterpriseValue": { "emissionsIntensityPortfolio": 0.26292644789401454, "emissionsIntensityBenchmark": 0.26292644789401454, "emissionsIntensityComparison": 0, "sectorAllocation": 0, "securityAllocation": 0, }, "emissionsIntensityMarketCap": { "emissionsIntensityPortfolio": 1.6225393090479594, "emissionsIntensityBenchmark": 1.6225393090479594, "emissionsIntensityComparison": 0, "sectorAllocation": 0, "securityAllocation": 0, }, "emissionsIntensityRevenue": { "emissionsIntensityPortfolio": 2.6771357259009947, "emissionsIntensityBenchmark": 2.6771357259009947, "emissionsIntensityComparison": 0, "sectorAllocation": 0, "securityAllocation": 0, }, }, { "sector": "Total", "weightPortfolio": 100, "weightBenchmark": 100, "weightComparison": 0, "emissionsIntensityEnterpriseValue": { "emissionsIntensityPortfolio": 0.312937288188714, "emissionsIntensityBenchmark": 0.312937288188714, "emissionsIntensityComparison": 0, "sectorAllocation": 0, "securityAllocation": 0, }, "emissionsIntensityMarketCap": { "emissionsIntensityPortfolio": 1.2859550272624294, "emissionsIntensityBenchmark": 1.2859550272624294, "emissionsIntensityComparison": 0, "sectorAllocation": 0, "securityAllocation": 0, }, "emissionsIntensityRevenue": { "emissionsIntensityPortfolio": 2.8334849433028575, "emissionsIntensityBenchmark": 2.8334849433028575, "emissionsIntensityComparison": 0, "sectorAllocation": 0, "securityAllocation": 0, }, }, ], }, } scenario_data = { "scenarios": ["MS0VH86TEJGWDK8V"], "results": [ { "summary": { "estimatedPnl": -10570.100235985032, "estimatedPerformance": 5.867062741998796, "stressedMarketValue": -190730.10023598504, }, "factorPnl": [ { "name": "Currency", "factorExposure": -360320, "estimatedPnl": -3471.845645552283, "factors": [ { "name": "AED", "factorExposure": -180160.00000000003, "factorShock": 0.0027226443140238032, "estimatedPnl": -4.905115996145284, "byAsset": [ { "assetId": "MA4B66MW5E27U9VBB94", "name": "Apple Inc", "bbid": "AAPL UW", "factorExposure": 189689.99999999997, "estimatedPnl": 5.164583999271752, }, { "assetId": "MA4B66MW5E27UAL9SUX", "name": "Microsoft Corp", "bbid": "MSFT UW", "factorExposure": -369850, "estimatedPnl": -10.069699995417036, }, ], }, { "name": "BRL", "factorExposure": -180160, "factorShock": 1.9243675230662394, "estimatedPnl": -3466.9405295561373, "byAsset": [ { "assetId": "MA4B66MW5E27U9VBB94", "name": "Apple Inc", "bbid": "AAPL UW", "factorExposure": 189690, "estimatedPnl": 3650.3327545043494, }, { "assetId": "MA4B66MW5E27UAL9SUX", "name": "Microsoft Corp", "bbid": "MSFT UW", "factorExposure": -369850, "estimatedPnl": -7117.273284060487, }, ], }, ], }, { "name": "Industry", "factorExposure": -180160, "estimatedPnl": -6040.119337162676, "factors": [ { "name": "Biotechnology", "factorExposure": 189690, "factorShock": -7.941253726728414, "estimatedPnl": -15063.76419423113, "byAsset": [ { "assetId": "MA4B66MW5E27U9VBB94", "name": "Apple Inc", "bbid": "AAPL UW", "factorExposure": 189690, "estimatedPnl": -15063.76419423113, } ], }, { "name": "Health Care Providers & Services", "factorExposure": -369850, "factorShock": -2.4398120473349882, "estimatedPnl": 9023.644857068453, "byAsset": [ { "assetId": "MA4B66MW5E27UAL9SUX", "name": "Microsoft Corp", "bbid": "MSFT UW", "factorExposure": -369850, "estimatedPnl": 9023.644857068453, } ], }, ], }, { "name": "Style", "factorExposure": 375097.91196133004, "estimatedPnl": 5023.410933430805, "factors": [ { "name": "Dividend Yield", "factorExposure": 39292.92823909999, "factorShock": 0.6191212422128611, "estimatedPnl": 243.27086541572396, "byAsset": [ { "assetId": "MA4B66MW5E27U9VBB94", "name": "Apple Inc", "bbid": "AAPL UW", "factorExposure": -80909.0466669, "estimatedPnl": -500.9250947866948, }, { "assetId": "MA4B66MW5E27UAL9SUX", "name": "Microsoft Corp", "bbid": "MSFT UW", "factorExposure": 120201.97490599999, "estimatedPnl": 744.1959602024187, }, ], }, { "name": "Earnings Yield", "factorExposure": 28047.463723279994, "factorShock": 1.0996305646159277, "estimatedPnl": 308.41848370075127, "byAsset": [ { "assetId": "MA4B66MW5E27U9VBB94", "name": "Apple Inc", "bbid": "AAPL UW", "factorExposure": -14836.828701720004, "estimatedPnl": -163.15030322382168, }, { "assetId": "MA4B66MW5E27UAL9SUX", "name": "Microsoft Corp", "bbid": "MSFT UW", "factorExposure": 42884.292425, "estimatedPnl": 471.568786924573, }, ], }, { "name": "Exchange Rate Sensitivity", "factorExposure": 253936.74563119997, "factorShock": 0.1803193519871238, "estimatedPnl": 457.8970941793707, "byAsset": [ { "assetId": "MA4B66MW5E27U9VBB94", "name": "Apple Inc", "bbid": "AAPL UW", "factorExposure": 225295.87526399997, "estimatedPnl": 406.2520623297635, }, { "assetId": "MA4B66MW5E27UAL9SUX", "name": "Microsoft Corp", "bbid": "MSFT UW", "factorExposure": 28640.8703672, "estimatedPnl": 51.6450318496072, }, ], }, { "name": "Growth", "factorExposure": -44761.018989899996, "factorShock": -0.7552222730204394, "estimatedPnl": 338.0451850426333, "byAsset": [ { "assetId": "MA4B66MW5E27U9VBB94", "name": "Apple Inc", "bbid": "AAPL UW", "factorExposure": -26364.8708325, "estimatedPnl": 199.11337678010935, }, { "assetId": "MA4B66MW5E27UAL9SUX", "name": "Microsoft Corp", "bbid": "MSFT UW", "factorExposure": -18396.1481574, "estimatedPnl": 138.93180826252396, }, ], }, { "name": "Leverage", "factorExposure": 191868.018137, "factorShock": 0.167948580725219, "estimatedPnl": 322.2396133266973, "byAsset": [ { "assetId": "MA4B66MW5E27U9VBB94", "name": "Apple Inc", "bbid": "AAPL UW", "factorExposure": 52489.679485500004, "estimatedPnl": 88.1556717231137, }, { "assetId": "MA4B66MW5E27UAL9SUX", "name": "Microsoft Corp", "bbid": "MSFT UW", "factorExposure": 139378.3386515, "estimatedPnl": 234.0839416035836, }, ], }, { "name": "Liquidity", "factorExposure": 88641.57049540001, "factorShock": -1.2990094028817012, "estimatedPnl": -1151.4623355972578, "byAsset": [ { "assetId": "MA4B66MW5E27U9VBB94", "name": "Apple Inc", "bbid": "AAPL UW", "factorExposure": 19569.494045400003, "estimatedPnl": -254.20956774612065, }, { "assetId": "MA4B66MW5E27UAL9SUX", "name": "Microsoft Corp", "bbid": "MSFT UW", "factorExposure": 69072.07645000001, "estimatedPnl": -897.2527678511373, }, ], }, { "name": "Market Sensitivity", "factorExposure": 9435.966403949995, "factorShock": 1.8499630741892314, "estimatedPnl": 174.5618941659764, "byAsset": [ { "assetId": "MA4B66MW5E27U9VBB94", "name": "Apple Inc", "bbid": "AAPL UW", "factorExposure": 36458.8713591, "estimatedPnl": 674.4756574095036, }, { "assetId": "MA4B66MW5E27UAL9SUX", "name": "Microsoft Corp", "bbid": "MSFT UW", "factorExposure": -27022.904955150003, "estimatedPnl": -499.91376324352717, }, ], }, { "name": "Medium-Term Momentum", "factorExposure": -158802.2664814, "factorShock": -0.8293589360579001, "estimatedPnl": 1317.0407877259704, "byAsset": [ { "assetId": "MA4B66MW5E27U9VBB94", "name": "Apple Inc", "bbid": "AAPL UW", "factorExposure": 19560.973170600002, "estimatedPnl": -162.23067897025948, }, { "assetId": "MA4B66MW5E27UAL9SUX", "name": "Microsoft Corp", "bbid": "MSFT UW", "factorExposure": -178363.23965200002, "estimatedPnl": 1479.27146669623, }, ], }, { "name": "Profitability", "factorExposure": 123348.2983915, "factorShock": 0.44359069709951626, "estimatedPnl": 547.1615766952461, "byAsset": [ { "assetId": "MA4B66MW5E27U9VBB94", "name": "Apple Inc", "bbid": "AAPL UW", "factorExposure": 201297.377697, "estimatedPnl": 892.9364409691684, }, { "assetId": "MA4B66MW5E27UAL9SUX", "name": "Microsoft Corp", "bbid": "MSFT UW", "factorExposure": -77949.0793055, "estimatedPnl": -345.7748642739222, }, ], }, { "name": "Size", "factorExposure": -156279.36591819994, "factorShock": -1.2578893438789152, "estimatedPnl": 1965.8214905665745, "byAsset": [ { "assetId": "MA4B66MW5E27U9VBB94", "name": "Apple Inc", "bbid": "AAPL UW", "factorExposure": 170372.65707780002, "estimatedPnl": -2143.0994982650127, }, { "assetId": "MA4B66MW5E27UAL9SUX", "name": "Microsoft Corp", "bbid": "MSFT UW", "factorExposure": -326652.02299599996, "estimatedPnl": 4108.920988831587, }, ], }, { "name": "Value", "factorExposure": 38081.90757720001, "factorShock": 1.660035175814345, "estimatedPnl": 632.1730614026285, "byAsset": [ { "assetId": "MA4B66MW5E27U9VBB94", "name": "Apple Inc", "bbid": "AAPL UW", "factorExposure": -77920.3687998, "estimatedPnl": -1293.5055312009458, }, { "assetId": "MA4B66MW5E27UAL9SUX", "name": "Microsoft Corp", "bbid": "MSFT UW", "factorExposure": 116002.276377, "estimatedPnl": 1925.6785926035743, }, ], }, { "name": "Volatility", "factorExposure": -37712.3352478, "factorShock": 0.34937317545509217, "estimatedPnl": -131.75678319350888, "byAsset": [ { "assetId": "MA4B66MW5E27U9VBB94", "name": "Apple Inc", "bbid": "AAPL UW", "factorExposure": -74956.7850228, "estimatedPnl": -261.8789000532033, }, { "assetId": "MA4B66MW5E27UAL9SUX", "name": "Microsoft Corp", "bbid": "MSFT UW", "factorExposure": 37244.449775, "estimatedPnl": 130.12211685969444, }, ], }, ], }, { "name": "Country", "factorExposure": -180160, "estimatedPnl": -6081.546186700877, "factors": [ { "name": "United States", "factorExposure": -180160, "factorShock": 3.375636204873933, "estimatedPnl": -6081.546186700877, "byAsset": [ { "assetId": "MA4B66MW5E27U9VBB94", "name": "Apple Inc", "bbid": "AAPL UW", "factorExposure": 189690, "estimatedPnl": 6403.244317025364, }, { "assetId": "MA4B66MW5E27UAL9SUX", "name": "Microsoft Corp", "bbid": "MSFT UW", "factorExposure": -369850, "estimatedPnl": -12484.79050372624, }, ], } ], }, ], "bySectorAggregations": [ { "name": "Information Technology", "exposure": -2882560, "estimatedPnl": -10570.100235985032, "industries": [ {"name": "Software", "exposure": -5917600, "estimatedPnl": -3047.0113322484867}, { "name": "Technology Hardware, Storage & Peripherals", "exposure": 3035040, "estimatedPnl": -7523.088903736546, }, ], } ], "byRegionAggregations": [ {"name": "United States", "exposure": -2882560, "estimatedPnl": -10570.100235985032} ], "byDirectionAggregations": [ {"name": "LONG", "exposure": 3035040, "estimatedPnl": -7523.088903736546}, {"name": "SHORT", "exposure": -5917600, "estimatedPnl": -3047.0113322484867}, ], "byAsset": [ { "assetId": "MA4B66MW5E27U9VBB94", "name": "Apple Inc", "sector": "Information Technology", "industry": "Technology Hardware, Storage & Peripherals", "country": "United States", "direction": "LONG", "exposure": 3035040, "estimatedPnl": -7523.088903736545, "estimatedPerformance": -0.24787445647294748, }, { "assetId": "MA4B66MW5E27UAL9SUX", "name": "Microsoft Corp", "sector": "Information Technology", "industry": "Software", "country": "United States", "direction": "SHORT", "exposure": -5917600, "estimatedPnl": -3047.0113322484845, "estimatedPerformance": 0.05149066060985001, }, ], } ], } scenario_results = { 'byAsset': [ { 'assetId': 'MA4B66MW5E27U9VBB94', 'country': 'United States', 'direction': 'LONG', 'estimatedPerformance': -0.24787445647294748, 'estimatedPnl': -7523.088903736545, 'exposure': 3035040, 'industry': 'Technology Hardware, Storage & Peripherals', 'name': 'Apple Inc', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', 'sector': 'Information Technology', }, { 'assetId': 'MA4B66MW5E27UAL9SUX', 'country': 'United States', 'direction': 'SHORT', 'estimatedPerformance': 0.05149066060985001, 'estimatedPnl': -3047.0113322484845, 'exposure': -5917600, 'industry': 'Software', 'name': 'Microsoft Corp', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', 'sector': 'Information Technology', }, ], 'byDirectionAggregations': [ { 'direction': 'LONG', 'estimatedPnl': -7523.088903736546, 'exposure': 3035040, 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'direction': 'SHORT', 'estimatedPnl': -3047.0113322484867, 'exposure': -5917600, 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, ], 'byRegionAggregations': [ { 'country': 'United States', 'estimatedPnl': -10570.100235985032, 'exposure': -2882560, 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', } ], 'bySectorAggregations': [ { 'estimatedPnl': -3047.0113322484867, 'exposure': -5917600, 'industry': 'Software', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', 'sector': 'Information Technology', }, { 'estimatedPnl': -7523.088903736546, 'exposure': 3035040, 'industry': 'Technology Hardware, Storage & Peripherals', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', 'sector': 'Information Technology', }, ], 'factorPnl': [ { 'assetId': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW', 'estimatedPnl': 5.164583999271752, 'factor': 'AED', 'factorCategory': 'Currency', 'factorExposure': 189689.99999999997, 'factorShock': 0.0027226443140238032, 'name': 'Apple Inc', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27UAL9SUX', 'bbid': 'MSFT UW', 'estimatedPnl': -10.069699995417036, 'factor': 'AED', 'factorCategory': 'Currency', 'factorExposure': -369850.0, 'factorShock': 0.0027226443140238032, 'name': 'Microsoft Corp', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW', 'estimatedPnl': 3650.3327545043494, 'factor': 'BRL', 'factorCategory': 'Currency', 'factorExposure': 189690.0, 'factorShock': 1.9243675230662394, 'name': 'Apple Inc', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27UAL9SUX', 'bbid': 'MSFT UW', 'estimatedPnl': -7117.273284060487, 'factor': 'BRL', 'factorCategory': 'Currency', 'factorExposure': -369850.0, 'factorShock': 1.9243675230662394, 'name': 'Microsoft Corp', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW', 'estimatedPnl': -15063.76419423113, 'factor': 'Biotechnology', 'factorCategory': 'Industry', 'factorExposure': 189690.0, 'factorShock': -7.941253726728414, 'name': 'Apple Inc', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27UAL9SUX', 'bbid': 'MSFT UW', 'estimatedPnl': 9023.644857068453, 'factor': 'Health Care Providers & Services', 'factorCategory': 'Industry', 'factorExposure': -369850.0, 'factorShock': -2.4398120473349882, 'name': 'Microsoft Corp', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW', 'estimatedPnl': -500.9250947866948, 'factor': 'Dividend Yield', 'factorCategory': 'Style', 'factorExposure': -80909.0466669, 'factorShock': 0.6191212422128611, 'name': 'Apple Inc', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27UAL9SUX', 'bbid': 'MSFT UW', 'estimatedPnl': 744.1959602024187, 'factor': 'Dividend Yield', 'factorCategory': 'Style', 'factorExposure': 120201.97490599999, 'factorShock': 0.6191212422128611, 'name': 'Microsoft Corp', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW', 'estimatedPnl': -163.15030322382168, 'factor': 'Earnings Yield', 'factorCategory': 'Style', 'factorExposure': -14836.828701720004, 'factorShock': 1.0996305646159277, 'name': 'Apple Inc', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27UAL9SUX', 'bbid': 'MSFT UW', 'estimatedPnl': 471.568786924573, 'factor': 'Earnings Yield', 'factorCategory': 'Style', 'factorExposure': 42884.292425, 'factorShock': 1.0996305646159277, 'name': 'Microsoft Corp', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW', 'estimatedPnl': 406.2520623297635, 'factor': 'Exchange Rate Sensitivity', 'factorCategory': 'Style', 'factorExposure': 225295.87526399997, 'factorShock': 0.1803193519871238, 'name': 'Apple Inc', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27UAL9SUX', 'bbid': 'MSFT UW', 'estimatedPnl': 51.6450318496072, 'factor': 'Exchange Rate Sensitivity', 'factorCategory': 'Style', 'factorExposure': 28640.8703672, 'factorShock': 0.1803193519871238, 'name': 'Microsoft Corp', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW', 'estimatedPnl': 199.11337678010935, 'factor': 'Growth', 'factorCategory': 'Style', 'factorExposure': -26364.8708325, 'factorShock': -0.7552222730204394, 'name': 'Apple Inc', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27UAL9SUX', 'bbid': 'MSFT UW', 'estimatedPnl': 138.93180826252396, 'factor': 'Growth', 'factorCategory': 'Style', 'factorExposure': -18396.1481574, 'factorShock': -0.7552222730204394, 'name': 'Microsoft Corp', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW', 'estimatedPnl': 88.1556717231137, 'factor': 'Leverage', 'factorCategory': 'Style', 'factorExposure': 52489.679485500004, 'factorShock': 0.167948580725219, 'name': 'Apple Inc', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27UAL9SUX', 'bbid': 'MSFT UW', 'estimatedPnl': 234.0839416035836, 'factor': 'Leverage', 'factorCategory': 'Style', 'factorExposure': 139378.3386515, 'factorShock': 0.167948580725219, 'name': 'Microsoft Corp', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW', 'estimatedPnl': -254.20956774612065, 'factor': 'Liquidity', 'factorCategory': 'Style', 'factorExposure': 19569.494045400003, 'factorShock': -1.2990094028817012, 'name': 'Apple Inc', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27UAL9SUX', 'bbid': 'MSFT UW', 'estimatedPnl': -897.2527678511373, 'factor': 'Liquidity', 'factorCategory': 'Style', 'factorExposure': 69072.07645000001, 'factorShock': -1.2990094028817012, 'name': 'Microsoft Corp', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW', 'estimatedPnl': 674.4756574095036, 'factor': 'Market Sensitivity', 'factorCategory': 'Style', 'factorExposure': 36458.8713591, 'factorShock': 1.8499630741892314, 'name': 'Apple Inc', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27UAL9SUX', 'bbid': 'MSFT UW', 'estimatedPnl': -499.91376324352717, 'factor': 'Market Sensitivity', 'factorCategory': 'Style', 'factorExposure': -27022.904955150003, 'factorShock': 1.8499630741892314, 'name': 'Microsoft Corp', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW', 'estimatedPnl': -162.23067897025948, 'factor': 'Medium-Term Momentum', 'factorCategory': 'Style', 'factorExposure': 19560.973170600002, 'factorShock': -0.8293589360579001, 'name': 'Apple Inc', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27UAL9SUX', 'bbid': 'MSFT UW', 'estimatedPnl': 1479.27146669623, 'factor': 'Medium-Term Momentum', 'factorCategory': 'Style', 'factorExposure': -178363.23965200002, 'factorShock': -0.8293589360579001, 'name': 'Microsoft Corp', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW', 'estimatedPnl': 892.9364409691684, 'factor': 'Profitability', 'factorCategory': 'Style', 'factorExposure': 201297.377697, 'factorShock': 0.44359069709951626, 'name': 'Apple Inc', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27UAL9SUX', 'bbid': 'MSFT UW', 'estimatedPnl': -345.7748642739222, 'factor': 'Profitability', 'factorCategory': 'Style', 'factorExposure': -77949.0793055, 'factorShock': 0.44359069709951626, 'name': 'Microsoft Corp', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW', 'estimatedPnl': -2143.0994982650127, 'factor': 'Size', 'factorCategory': 'Style', 'factorExposure': 170372.65707780002, 'factorShock': -1.2578893438789152, 'name': 'Apple Inc', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27UAL9SUX', 'bbid': 'MSFT UW', 'estimatedPnl': 4108.920988831587, 'factor': 'Size', 'factorCategory': 'Style', 'factorExposure': -326652.02299599996, 'factorShock': -1.2578893438789152, 'name': 'Microsoft Corp', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW', 'estimatedPnl': -1293.5055312009458, 'factor': 'Value', 'factorCategory': 'Style', 'factorExposure': -77920.3687998, 'factorShock': 1.660035175814345, 'name': 'Apple Inc', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27UAL9SUX', 'bbid': 'MSFT UW', 'estimatedPnl': 1925.6785926035743, 'factor': 'Value', 'factorCategory': 'Style', 'factorExposure': 116002.276377, 'factorShock': 1.660035175814345, 'name': 'Microsoft Corp', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW', 'estimatedPnl': -261.8789000532033, 'factor': 'Volatility', 'factorCategory': 'Style', 'factorExposure': -74956.7850228, 'factorShock': 0.34937317545509217, 'name': 'Apple Inc', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27UAL9SUX', 'bbid': 'MSFT UW', 'estimatedPnl': 130.12211685969444, 'factor': 'Volatility', 'factorCategory': 'Style', 'factorExposure': 37244.449775, 'factorShock': 0.34937317545509217, 'name': 'Microsoft Corp', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW', 'estimatedPnl': 6403.244317025364, 'factor': 'United States', 'factorCategory': 'Country', 'factorExposure': 189690.0, 'factorShock': 3.375636204873933, 'name': 'Apple Inc', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, { 'assetId': 'MA4B66MW5E27UAL9SUX', 'bbid': 'MSFT UW', 'estimatedPnl': -12484.79050372624, 'factor': 'United States', 'factorCategory': 'Country', 'factorExposure': -369850.0, 'factorShock': 3.375636204873933, 'name': 'Microsoft Corp', 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', }, ], 'summary': [ { 'estimatedPerformance': 5.867062741998796, 'estimatedPnl': -10570.100235985032, 'scenarioId': 'MS0VH86TEJGWDK8V', 'scenarioName': 'US Presidential Election 2016', 'scenarioType': 'Factor Historical Simulation', 'stressedMarketValue': -190730.10023598504, } ], } def test_get_reports(mocker): mock_portfolio = TargetPortfolio(id='MP', currency='USD', name='Example Port') mock_reports = ( Report.from_dict( { 'id': 'PPAID', 'positionSourceType': 'Portfolio', 'positionSourceId': 'MP', 'type': 'Portfolio Performance Analytics', 'parameters': {'transactionCostModel': 'FIXED'}, 'percentageComplete': 0, 'status': 'new', } ), Report.from_dict( { 'id': 'PFRID', 'positionSourceType': 'Portfolio', 'positionSourceId': 'MP', 'type': 'Portfolio Factor Risk', 'parameters': {'riskModel': 'AXUS4M', 'transactionCostModel': 'FIXED'}, 'percentageComplete': 0, 'status': 'new', } ), ) mocker.patch.object(GsPortfolioApi, 'get_portfolio', return_value=mock_portfolio) mocker.patch.object(GsPortfolioApi, 'get_position_dates', return_value=[dt.date(2020, 1, 1)]) mocker.patch.object(GsPortfolioApi, 'get_positions_for_date', return_value=None) mocker.patch.object(GsPortfolioApi, 'get_reports', return_value=mock_reports) # run test pm = PortfolioManager('MP') reports = pm.get_reports() assert len(reports) == 2 assert isinstance(reports[0], PerformanceReport) assert isinstance(reports[1], FactorRiskReport) def test_get_schedule_dates(mocker): # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object( GsSession.current.sync, 'get', return_value={'startDate': '2019-01-01', 'endDate': '2020-01-02'} ) # run test pm = PortfolioManager('MP') dates = pm.get_schedule_dates() assert dates[1] == dt.date(2020, 1, 2) def test_set_entitlements(mocker): mock_portfolio = TargetPortfolio(id='MP', currency='USD', name='Example Port') entitlements = Entitlements( view=EntitlementBlock( users=[User(user_id='fakeId', name='Fake User', email='fake@gs.com', company='Goldman Sachs')] ) ) mocker.patch.object(GsSession, '_get', return_value=mock_portfolio) mocker.patch.object( GsPortfolioApi, 'get_position_dates', return_value=[dt.date(2020, 1, 2), dt.date(2020, 2, 1), dt.date(2020, 3, 1)], ) mocker.patch.object(GsPortfolioApi, 'get_positions_for_date', return_value=None) mocker.patch.object(GsPortfolioApi, 'update_portfolio', return_value='') # run test pm = PortfolioManager('MP') pm.set_entitlements(entitlements) def test_run_reports(mocker): mock_portfolio = TargetPortfolio(id='MP', currency='USD', name='Example Port') mock_reports = ( Report.from_dict( { 'id': 'PPAID', 'positionSourceType': 'Portfolio', 'positionSourceId': 'MP', 'type': 'Portfolio Performance Analytics', 'parameters': {'transactionCostModel': 'FIXED'}, 'percentageComplete': 0, 'status': 'new', } ), ) mock_report_jobs = ( { 'startDate': '2020-01-01', 'endDate': '2020-03-02', 'id': 'jobId1', 'createdTime': '2021-05-18T17:08:18.72Z', 'reportType': 'Portfolio Factor Risk', }, { 'startDate': '2020-05-01', 'endDate': '2020-07-02', 'id': 'jobId1', 'createdTime': '2020-05-18T17:08:18.72Z', 'reportType': 'Portfolio Factor Risk', }, ) mock_report_job = { 'startDate': '2020-01-01', 'endDate': '2020-03-02', 'id': 'jobId1', 'createdTime': '2021-05-18T17:08:18.72Z', 'reportType': 'Portfolio Factor Risk', 'status': 'done', } mock_results = [ { 'date': "2019-08-27", 'factor': "United States", 'factorCategory': "Country", 'pnl': -162.93571064768423, 'exposure': 19878.043518298073, 'sensitivity': 52.7507947211687, 'proportionOfRisk': 0.050898995661382604, } ] mocker.patch.object(GsPortfolioApi, 'get_portfolio', return_value=mock_portfolio) mocker.patch.object(GsPortfolioApi, 'schedule_reports', return_value='') mocker.patch.object(GsPortfolioApi, 'get_position_dates', return_value=[dt.date(2020, 1, 1)]) mocker.patch.object(GsPortfolioApi, 'get_positions_for_date', return_value=None) mocker.patch.object(GsPortfolioApi, 'get_reports', return_value=mock_reports) mocker.patch.object(GsReportApi, 'get_report_jobs', return_value=mock_report_jobs) mocker.patch.object(GsReportApi, 'get_report_job', return_value=mock_report_job) mocker.patch.object(GsReportApi, 'get_factor_risk_report_results', return_value=mock_results) # run test pm = PortfolioManager('MP') pm.run_reports(is_async=False) # noinspection DuplicatedCode def test_batched_schedule_reports(mocker): mocker.patch.object( GsPortfolioApi, 'get_position_dates', return_value=[dt.date(2022, 2, 1), dt.date(2022, 8, 1), dt.date(2022, 10, 1), dt.date(2022, 11, 1)], ) mocker.patch.object(GsPortfolioApi, 'schedule_reports') schedule_spy = mocker.spy(GsPortfolioApi, 'schedule_reports') pid = 'MP' pm = PortfolioManager(pid) pm.schedule_reports( start_date=dt.date(2022, 2, 1), end_date=dt.date(2022, 11, 10), backcast=False, months_per_batch=2 ) assert schedule_spy.call_count == 3, 'For the given positions, batch period of 2 months should result in 3 batches' batch_boundaries = [dt.date(2022, 2, 1), dt.date(2022, 8, 1), dt.date(2022, 10, 1), dt.date(2022, 11, 10)] for i in range(len(batch_boundaries) - 1): schedule_spy.assert_any_call(pid, batch_boundaries[i], batch_boundaries[i + 1], backcast=False) schedule_spy.reset_mock() pm.schedule_reports( start_date=dt.date(2022, 2, 1), end_date=dt.date(2022, 11, 10), backcast=False, months_per_batch=4 ) assert schedule_spy.call_count == 2, 'For the given positions, batch period of 4 months should result in 2 batches' batch_boundaries = [dt.date(2022, 2, 1), dt.date(2022, 8, 1), dt.date(2022, 11, 10)] for i in range(len(batch_boundaries) - 1): schedule_spy.assert_any_call(pid, batch_boundaries[i], batch_boundaries[i + 1], backcast=False) # noinspection DuplicatedCode def test_batched_schedule_reports_wo_dates(mocker): mocker.patch.object( GsPortfolioApi, 'get_position_dates', return_value=[dt.date(2022, 2, 1), dt.date(2022, 8, 1), dt.date(2022, 10, 1), dt.date(2022, 11, 1)], ) mocker.patch.object(GsPortfolioApi, 'get_schedule_dates', return_value=[dt.date(2022, 2, 1), dt.date(2022, 11, 10)]) mocker.patch.object(GsPortfolioApi, 'schedule_reports') schedule_spy = mocker.spy(GsPortfolioApi, 'schedule_reports') pid = 'MP' pm = PortfolioManager(pid) pm.schedule_reports(backcast=False, months_per_batch=2) assert schedule_spy.call_count == 3, 'For the given positions, batch period of 2 months should result in 3 batches' batch_boundaries = [dt.date(2022, 2, 1), dt.date(2022, 8, 1), dt.date(2022, 10, 1), dt.date(2022, 11, 10)] for i in range(len(batch_boundaries) - 1): schedule_spy.assert_any_call(pid, batch_boundaries[i], batch_boundaries[i + 1], backcast=False) def test_batched_schedule_validations(mocker): pm = PortfolioManager('PM') # start date should be before end date with pytest.raises(Exception): pm.schedule_reports(backcast=False, start_date=dt.date(2022, 2, 1), end_date=dt.date(2022, 1, 1)) mocker.patch.object(GsPortfolioApi, 'get_schedule_dates', return_value=[dt.date(2022, 2, 1), dt.date(2022, 11, 5)]) # start date should be before maximum possible end date with pytest.raises(Exception): pm.schedule_reports(backcast=False, start_date=dt.date(2022, 12, 1)) # end date should be after first possible start date with pytest.raises(Exception): pm.schedule_reports(backcast=False, end_date=dt.date(2022, 1, 1)) mocker.patch.object( GsPortfolioApi, 'get_position_dates', return_value=[dt.date(2022, 2, 1), dt.date(2022, 8, 1), dt.date(2022, 10, 1), dt.date(2022, 11, 1)], ) # start date should be on a position date with pytest.raises(Exception): pm.schedule_reports(backcast=False, start_date=dt.date(2022, 3, 1)) def test_esg_summary(mocker): mocker.patch.object(GsEsgApi, 'get_esg', return_value=esg_data) # run test pm = PortfolioManager('MP') summary = pm.get_esg_summary() assert all(summary.columns.values == ['gross', 'long', 'short']) def test_esg_quintiles(mocker): mocker.patch.object(GsEsgApi, 'get_esg', return_value=esg_data) # run test pm = PortfolioManager('MP') quintiles = pm.get_esg_quintiles(measure=ESGMeasure.G_PERCENTILE) assert all(quintiles.columns.values == ['description', 'gross', 'long', 'short']) def test_esg_by_sector(mocker): mocker.patch.object(GsEsgApi, 'get_esg', return_value=esg_data) # run test pm = PortfolioManager('MP') breakdown = pm.get_esg_by_region(measure=ESGMeasure.G_PERCENTILE) assert all(breakdown.columns.values == ['name', 'gross', 'long', 'short']) def test_esg_by_region(mocker): mocker.patch.object(GsEsgApi, 'get_esg', return_value=esg_data) # run test pm = PortfolioManager('MP') breakdown = pm.get_esg_by_region(measure=ESGMeasure.G_PERCENTILE) assert all(breakdown.columns.values == ['name', 'gross', 'long', 'short']) def test_esg_top_ten(mocker): mocker.patch.object(GsEsgApi, 'get_esg', return_value=esg_data) # run test pm = PortfolioManager('MP') ranked = pm.get_esg_top_ten(measure=ESGMeasure.G_PERCENTILE) assert ranked.size == 6 def test_esg_bottom_ten(mocker): mocker.patch.object(GsEsgApi, 'get_esg', return_value=esg_data) # run test pm = PortfolioManager('MP') ranked = pm.get_esg_bottom_ten(measure=ESGMeasure.G_PERCENTILE) assert ranked.size == 6 def test_carbon_coverage(mocker): mocker.patch.object(GsCarbonApi, 'get_carbon_analytics', return_value=carbon_data) pm = PortfolioManager('MP') coverage = pm.get_carbon_coverage(coverage_category=CarbonCoverageCategory.NUMBER_OF_COMPANIES) assert coverage.to_dict() == carbon_data.get(CarbonCard.COVERAGE.value).get( CarbonCoverageCategory.NUMBER_OF_COMPANIES.value ).get(CarbonEntityType.PORTFOLIO.value) def test_carbon_sbti_netzero_coverage(mocker): mocker.patch.object(GsCarbonApi, 'get_carbon_analytics', return_value=carbon_data) pm = PortfolioManager('MP') coverage = pm.get_carbon_sbti_netzero_coverage( target_coverage_category=CarbonTargetCoverageCategory.CAPITAL_ALLOCATED ) assert coverage.to_dict().get('scienceBasedTarget') == carbon_data.get( CarbonCard.SBTI_AND_NET_ZERO_TARGETS.value ).get(CarbonTargetCoverageCategory.CAPITAL_ALLOCATED.value).get('scienceBasedTarget').get( CarbonEntityType.PORTFOLIO.value ) def test_carbon_emissions(mocker): mocker.patch.object(GsCarbonApi, 'get_carbon_analytics', return_value=carbon_data) pm = PortfolioManager('MP') emissions = pm.get_carbon_emissions(scope=CarbonScope.SCOPE1) assert emissions.to_dict('records') == carbon_data.get(CarbonCard.EMISSIONS.value).get( CarbonScope.SCOPE1.value ).get(CarbonEntityType.PORTFOLIO.value) def test_carbon_emissions_allocation(mocker): mocker.patch.object(GsCarbonApi, 'get_carbon_analytics', return_value=carbon_data) pm = PortfolioManager('MP') emissions = pm.get_carbon_emissions_allocation(classification=CarbonEmissionsAllocationCategory.GICS_INDUSTRY) assert emissions.rename(columns={CarbonEmissionsAllocationCategory.GICS_INDUSTRY.value: 'name'}).to_dict( 'records' ) == carbon_data.get(CarbonCard.ALLOCATIONS.value).get(CarbonScope.TOTAL_GHG.value).get( CarbonEntityType.PORTFOLIO.value ).get(CarbonEmissionsAllocationCategory.GICS_INDUSTRY.value) def test_carbon_attribution_table(mocker): mocker.patch.object(GsCarbonApi, 'get_carbon_analytics', return_value=carbon_data) pm = PortfolioManager('MP') attribution = pm.get_carbon_attribution_table( benchmark_id='MA', scope=CarbonScope.SCOPE2, intensity_metric=CarbonEmissionsIntensityType.EI_REVENUE ) attribution = attribution.to_dict('records') for entry in attribution: entry[CarbonEmissionsIntensityType.EI_REVENUE.value] = { 'emissionsIntensityPortfolio': entry.pop('emissionsIntensityPortfolio'), 'emissionsIntensityBenchmark': entry.pop('emissionsIntensityBenchmark'), 'emissionsIntensityComparison': entry.pop('emissionsIntensityComparison'), 'sectorAllocation': entry.pop('sectorAllocation'), 'securityAllocation': entry.pop('securityAllocation'), } actual_table = [] for entry in carbon_data.get(CarbonCard.ATTRIBUTION.value).get(CarbonScope.SCOPE2.value): new_entry = copy.deepcopy(entry) new_entry.pop(CarbonEmissionsIntensityType.EI_ENTERPRISE_VALUE.value) new_entry.pop(CarbonEmissionsIntensityType.EI_MARKETCAP.value) actual_table.append(new_entry) assert attribution == actual_table def test_get_macro_exposure(mocker): portfolio_constituents = { "date": ["2022-05-02", "2022-05-02"], "assetId": ["mq1", "mq2"], "direction": ["LONG", "LONG"], "netExposure": [1000, 1000], } assets_data = [{"id": "mq1", "name": "asset1", "gsid": "1"}, {"id": "mq2", "name": "asset2", "gsid": "2"}] factor_sens = { "factor1": {("1", "2022-05-02"): 1, ("2", "2022-05-02"): 1}, "factor2": {("1", "2022-05-02"): 5, ("2", "2022-05-02"): 5}, } factor_data = { "identifier": ["15", "17"], "name": ["factor1", "factor2"], "type": ["Factor", "Factor"], "factorCategory": ["category1", "category2"], "factorCategoryId": ["c1", "c2"], } result = { ("Asset Information", "Asset Name"): {"1": "asset1", "2": "asset2", "Total Factor Exposure": np.nan}, ("Asset Information", "Notional"): {"1": 1000, "2": 1000, "Total Factor Exposure": 2000}, ("category2", "factor2"): {"1": 50.0, "2": 50.0, "Total Factor Exposure": 100.0}, ("category1", "factor1"): {"1": 10.0, "2": 10.0, "Total Factor Exposure": 20.0}, } pm = PortfolioManager("portfolioId") macro_model = MacroRiskModel( id_="fake_macro_model", name="fake_model", coverage=CoverageType.Region, vendor="fake_vendor", term=Term.Long, universe_identifier=UniverseIdentifier.sedol, version=0.1, ) fake_ppa = PerformanceReport( report_id='id', position_source_type=PositionSourceType.Portfolio, position_source_id='PORTFOLIOID', report_type=ReportType.Portfolio_Performance_Analytics, parameters=None, status=ReportStatus.done, ) mocker.patch.object(PortfolioManager, "get_performance_report", return_value=fake_ppa) mocker.patch.object( fake_ppa, "get_portfolio_constituents", return_value=pd.DataFrame.from_dict(portfolio_constituents) ) mocker.patch.object(GsAssetApi, "get_many_assets_data_scroll", return_value=assets_data) mocker.patch.object(macro_model, "get_factor_data", return_value=pd.DataFrame.from_dict(factor_data)) mocker.patch.object(macro_model, "get_universe_sensitivity", return_value=pd.DataFrame.from_dict(factor_sens)) exposure_df = pm.get_macro_exposure(model=macro_model, date=dt.date(2022, 5, 2), factor_type=FactorType.Factor) assert exposure_df.equals(pd.DataFrame.from_dict(result)) def test_get_factor_scenario_analytics(mocker): risk_report = FactorRiskReport(report_id='id', risk_model='RISK_MODEL') mocker.patch.object(PortfolioManager, "get_factor_risk_report", return_value=risk_report) mocker.patch.object(GsFactorScenarioApi, "calculate_scenario", return_value=scenario_data) pm = PortfolioManager("portfolioId") factor_scenario = FactorScenario( name='US Presidential Election 2016', description='Presidential Elections in 2016 when Donald Trump won', id_="MS0VH86TEJGWDK8V", type=FactorScenarioType.Factor_Historical_Simulation, parameters=HistoricalSimulationParameters(start_date=dt.date(2016, 1, 1), end_date=dt.date(2020, 1, 1)), ) scenario_analytics_data = pm.get_factor_scenario_analytics( scenarios=[factor_scenario], date=dt.date(2024, 3, 5), measures=[ ScenarioCalculationMeasure.SUMMARY, ScenarioCalculationMeasure.ESTIMATED_FACTOR_PNL, ScenarioCalculationMeasure.ESTIMATED_PNL_BY_SECTOR, ScenarioCalculationMeasure.ESTIMATED_PNL_BY_REGION, ScenarioCalculationMeasure.ESTIMATED_PNL_BY_DIRECTION, ScenarioCalculationMeasure.ESTIMATED_PNL_BY_ASSET, ], risk_model="RISK_MODEL", ) assert ( len( set(scenario_analytics_data.keys()) - { "summary", "factorPnl", "bySectorAggregations", "byRegionAggregations", "byDirectionAggregations", "byAsset", } ) == 0 ) for key, value in scenario_analytics_data.items(): result_df = pd.DataFrame(scenario_results[key]) result_df.columns = result_df.columns.map( lambda x: { "factorCategory": "Factor Category", "factor": "Factor", "sector": "Sector", "country": "Country", "industry": "Industry", "direction": "Direction", "scenarioId": "Scenario ID", "scenarioName": "Scenario Name", "scenarioType": "Scenario Type", "assetId": "Asset ID", "name": "Asset Name", "bbid": "BBID", "factorExposure": "Factor Exposure", "factorShock": "Factor Shock (%)", "exposure": "Exposure", "estimatedPnl": "Estimated Pnl", "estimatedPerformance": "Estimated Performance (%)", "stressedMarketValue": "Stressed Market Value", }.get(x, x) ) result_df = result_df.reindex(columns=value.columns.tolist()) pd.testing.assert_frame_equal(value, result_df) if __name__ == '__main__': pytest.main(args=[__file__]) ================================================ FILE: gs_quant/test/markets/test_position_set.py ================================================ """ Copyright 2024 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import pandas as pd import pytest from gs_quant.errors import MqValueError from gs_quant.markets.position_set import PositionSet, Position, GsPriceApi, PositionTag, PositionSetWeightingStrategy import gs_quant.markets.position_set as position_set_module from copy import deepcopy def test_position_resolve_many(mocker): unresolved_position_sets = [ PositionSet( date=dt.date(2024, 4, 30), reference_notional=1000, positions=[ Position(identifier='GS UN', weight=0.5, tags=[PositionTag(name="tag1", value="tagvalue1")]), Position(identifier='AAPL UW', weight=0.5, tags=[PositionTag(name="tag2", value="tagvalue2")]), ], ), PositionSet( date=dt.date(2024, 5, 1), reference_notional=1000, positions=[ Position(identifier='GS UN', weight=0.5, tags=[PositionTag(name="tag1", value="tagvalue1")]), Position(identifier='AAPL UW', weight=0.5, tags=[PositionTag(name="tag2", value="tagvalue2")]), ], ), ] resolved_positions = [ { "assetId": "MA4B66MW5E27UAHKG34", "name": "GS", "bbid": "GS UN", "tradingRestriction": True, "asOfDate": dt.datetime.strptime("2952-12-31", '%Y-%m-%d'), "startDate": dt.datetime.strptime("1952-01-01", '%Y-%m-%d'), "endDate": dt.datetime.strptime("2952-12-31", '%Y-%m-%d'), }, { "assetId": "MA4B66MW5E27U9VBB94", "name": "Apple", "bbid": "AAPL UW", "tradingRestriction": False, "asOfDate": dt.datetime.strptime("2952-12-31", '%Y-%m-%d'), "startDate": dt.datetime.strptime("1952-01-01", '%Y-%m-%d'), "endDate": dt.datetime.strptime("2952-12-31", '%Y-%m-%d'), }, ] xref_results = [ { "assetId": "MA4B66MW5E27UAHKG34", "bbid": "GS UN", "delisted": 'no', "startDate": "1952-01-01", "endDate": "2952-12-31", }, { "assetId": "MA4B66MW5E27U9VBB94", "bbid": "AAPL UW", "delisted": 'no', "startDate": "1952-01-01", "endDate": "2952-12-31", }, ] mocker.patch.object( position_set_module, "_get_asset_temporal_xrefs", return_value=(pd.DataFrame(xref_results), "bbid") ) mocker.patch.object(position_set_module, "_resolve_many_assets", return_value=pd.DataFrame(resolved_positions)) unresolved_position_sets_copy = deepcopy(unresolved_position_sets) PositionSet.resolve_many(unresolved_position_sets_copy) resolved_position_sets = [ PositionSet( date=dt.date(2024, 4, 30), reference_notional=1000, positions=[ Position( identifier='GS UN', asset_id="MA4B66MW5E27UAHKG34", name="GS", weight=0.5, tags=[PositionTag(name="tag1", value="tagvalue1")], ), Position( identifier='AAPL UW', asset_id="MA4B66MW5E27U9VBB94", name="Apple", weight=0.5, tags=[PositionTag(name="tag2", value="tagvalue2")], ), ], ), PositionSet( date=dt.date(2024, 5, 1), reference_notional=1000, positions=[ Position( identifier='GS UN', asset_id="MA4B66MW5E27UAHKG34", name="GS", weight=0.5, tags=[PositionTag(name="tag1", value="tagvalue1")], ), Position( identifier='AAPL UW', asset_id="MA4B66MW5E27U9VBB94", name="Apple", weight=0.5, tags=[PositionTag(name="tag2", value="tagvalue2")], ), ], ), ] for idx, pos_set in enumerate(unresolved_position_sets_copy): assert pos_set == resolved_position_sets[idx] # There are unresolved positions mocker.patch.object( position_set_module, "_get_asset_temporal_xrefs", return_value=(pd.DataFrame([xref_results[1]]), "bbid") ) mocker.patch.object(position_set_module, "_resolve_many_assets", return_value=pd.DataFrame([resolved_positions[1]])) unresolved_position_sets_copy = deepcopy(unresolved_position_sets) PositionSet.resolve_many(unresolved_position_sets_copy) expected_position_set = [ PositionSet( date=dt.date(2024, 4, 30), reference_notional=1000, positions=[ Position( identifier='AAPL UW', asset_id="MA4B66MW5E27U9VBB94", name="Apple", weight=0.5, tags=[PositionTag(name="tag2", value="tagvalue2")], ) ], unresolved_positions=[ Position(identifier='GS UN', name="GS", weight=0.5, tags=[PositionTag(name="tag1", value="tagvalue1")]) ], ), PositionSet( date=dt.date(2024, 5, 1), reference_notional=1000, positions=[ Position( identifier='AAPL UW', asset_id="MA4B66MW5E27U9VBB94", name="Apple", weight=0.5, tags=[PositionTag(name="tag2", value="tagvalue2")], ) ], unresolved_positions=[ Position(identifier='GS UN', name="GS", weight=0.5, tags=[PositionTag(name="tag1", value="tagvalue1")]) ], ), ] unresolved_position_sets_copy.sort(key=lambda x: x.date) expected_position_set.sort(key=lambda x: x.date) for idx, position_set in enumerate(unresolved_position_sets_copy): assert position_set == expected_position_set[idx] assert len(position_set.unresolved_positions) == 1 assert len(expected_position_set[idx].unresolved_positions) == 1 actual_unresolved_position = position_set.unresolved_positions[0] expected_unresolved_position = expected_position_set[idx].unresolved_positions[0] assert actual_unresolved_position.__eq__(expected_unresolved_position) @pytest.fixture() def position_sets_with_tags_and_notional(): return [ PositionSet( date=dt.date(2024, 4, 30), reference_notional=1000, positions=[ Position( identifier='GS UN', asset_id="MA4B66MW5E27UAHKG34", name="GS", weight=0.5, tags=[PositionTag(name="tag1", value="tagvalue1")], ), Position( identifier='AAPL UW', asset_id="MA4B66MW5E27U9VBB94", name="Apple", weight=0.5, tags=[PositionTag(name="tag2", value="tagvalue2")], ), ], ), PositionSet( date=dt.date(2024, 5, 1), reference_notional=1000, positions=[ Position( identifier='GS UN', asset_id="MA4B66MW5E27UAHKG34", name="GS", weight=0.5, tags=[PositionTag(name="tag1", value="tagvalue1")], ), Position( identifier='AAPL UW', asset_id="MA4B66MW5E27U9VBB94", name="Apple", weight=0.5, tags=[PositionTag(name="tag2", value="tagvalue2")], ), ], ), ] @pytest.fixture() def expected_position_pricing_result(): return [ { 'date': '2024-04-30', 'positions': [ { 'assetId': 'MA4B66MW5E27UAHKG34', 'weight': 0.42671, 'closePrice': 1000, 'fxClosePrice': 1, 'quantity': 1, 'notional': 1000, 'referenceWeight': 0.5, }, { 'assetId': 'MA4B66MW5E27U9VBB94', 'weight': 0.51099, 'closePrice': 1000, 'fxClosePrice': 1, 'quantity': 1, 'notional': 1000, 'referenceWeight': 0.5, }, ], 'targetNotional': 1000, }, { 'date': '2024-05-01', 'positions': [ { 'assetId': 'MA4B66MW5E27UAHKG34', 'weight': 0.42671, 'closePrice': 1000, 'fxClosePrice': 1, 'quantity': 1, 'notional': 1000, 'referenceWeight': 0.5, }, { 'assetId': 'MA4B66MW5E27U9VBB94', 'weight': 0.51099, 'closePrice': 1000, 'fxClosePrice': 1, 'quantity': 1, 'notional': 1000, 'referenceWeight': 0.5, }, ], 'targetNotional': 1000, }, ] def test_position_price_many(mocker, position_sets_with_tags_and_notional, expected_position_pricing_result): # All positions on each holding date are priced mocker.patch.object(GsPriceApi, "price_many_positions", return_value=expected_position_pricing_result) new_position_set_list = deepcopy(position_sets_with_tags_and_notional) PositionSet.price_many(new_position_set_list) expected_positions = { dt.date(2024, 4, 30): [ Position( identifier='GS UN', asset_id="MA4B66MW5E27UAHKG34", name="GS", weight=0.42671, quantity=1, notional=1000, tags=[PositionTag(name="tag1", value="tagvalue1")], ), Position( identifier='AAPL UW', asset_id="MA4B66MW5E27U9VBB94", name="Apple", weight=0.51099, quantity=1, notional=1000, tags=[PositionTag(name="tag2", value="tagvalue2")], ), ], dt.date(2024, 5, 1): [ Position( identifier='GS UN', asset_id="MA4B66MW5E27UAHKG34", name="GS", weight=0.42671, quantity=1, notional=1000, tags=[PositionTag(name="tag1", value="tagvalue1")], ), Position( identifier='AAPL UW', asset_id="MA4B66MW5E27U9VBB94", name="Apple", weight=0.51099, quantity=1, notional=1000, tags=[PositionTag(name="tag2", value="tagvalue2")], ), ], } [pos_set.positions.sort(key=lambda position: position.asset_id) for pos_set in new_position_set_list] [positions.sort(key=lambda position: position.asset_id) for positions in expected_positions.values()] for pos_set in new_position_set_list: assert pos_set.positions == expected_positions.get(pos_set.date) # weighting strategy passed is 'quantity' but positions do not have quantities with pytest.raises( MqValueError, match="Unable to price positions without position weights and daily reference notional or position quantities", ): PositionSet.price_many( position_sets_with_tags_and_notional, weighting_strategy=PositionSetWeightingStrategy.Quantity ) # Invalid weighting strategies with pytest.raises(MqValueError, match="Can only specify a weighting strategy of weight, notional, or quantity"): PositionSet.price_many( position_sets_with_tags_and_notional, weighting_strategy=PositionSetWeightingStrategy.Market_Capitalization ) # There are unpriced results mocker.patch.object(GsPriceApi, "price_many_positions", return_value=[expected_position_pricing_result[1]]) new_position_set_list = deepcopy(position_sets_with_tags_and_notional) PositionSet.price_many(new_position_set_list) first_position_set = new_position_set_list[0] positions = first_position_set.positions unpriced_positions = first_position_set.unpriced_positions assert not positions for position in unpriced_positions: if position.identifier == "GS UN": assert position.asset_id == "MA4B66MW5E27UAHKG34" assert position.weight == 0.5 assert not position.quantity assert not position.notional assert len(position.tags) == 1 tag = position.tags[0] assert tag.name == "tag1" assert tag.value == "tagvalue1" else: assert position.asset_id == "MA4B66MW5E27U9VBB94" assert position.weight == 0.5 assert not position.quantity assert not position.notional assert len(position.tags) == 1 tag = position.tags[0] assert tag.name == "tag2" assert tag.value == "tagvalue2" second_position_set = new_position_set_list[1] positions = second_position_set.positions unpriced_positions = second_position_set.unpriced_positions assert not unpriced_positions for position in positions: if position.identifier == "GS UN": assert position.asset_id == "MA4B66MW5E27UAHKG34" assert position.weight == 0.42671 assert position.quantity == 1 assert position.notional == 1000 assert len(position.tags) == 1 tag = position.tags[0] assert tag.name == "tag1" assert tag.value == "tagvalue1" else: assert position.asset_id == "MA4B66MW5E27U9VBB94" assert position.weight == 0.51099 assert position.quantity == 1 assert position.notional == 1000 assert len(position.tags) == 1 tag = position.tags[0] assert tag.name == "tag2" assert tag.value == "tagvalue2" # Positions do not have tags new_position_set_list = deepcopy(position_sets_with_tags_and_notional) position_sets_no_tags = [deepcopy(pos_set) for pos_set in new_position_set_list] for position_set_no_tag in position_sets_no_tags: [setattr(position, "tags", None) for position in position_set_no_tag.positions] mocker.patch.object(GsPriceApi, "price_many_positions", return_value=expected_position_pricing_result) PositionSet.price_many(position_sets_no_tags) for position_set in position_sets_no_tags: priced_positions = position_set.positions for position in priced_positions: if position.identifier == "GS UN": assert position.asset_id == "MA4B66MW5E27UAHKG34" assert position.weight == 0.42671 assert position.quantity == 1 assert position.notional == 1000 assert not position.tags else: assert position.asset_id == "MA4B66MW5E27U9VBB94" assert position.weight == 0.51099 assert position.quantity == 1 assert position.notional == 1000 assert not position.tags # Positions with different tags and common underlying asset ID different_tags_common_asset_id_positions = [ PositionSet( date=dt.date(2024, 4, 30), reference_notional=1000, positions=[ Position( identifier='GS UN', asset_id="MA4B66MW5E27UAHKG34", name="GS", weight=0.5, tags=[PositionTag(name="tag1", value="tagvalue1")], ), Position( identifier='GS UN', asset_id="MA4B66MW5E27UAHKG34", name="GS", weight=-0.8, tags=[PositionTag(name="tag2", value="tagvalue2")], ), Position(identifier='AAPL UW', asset_id="MA4B66MW5E27U9VBB94", name="Apple", weight=0.5, tags=None), ], ), PositionSet( date=dt.date(2024, 5, 1), reference_notional=1000, positions=[ Position( identifier='GS UN', asset_id="MA4B66MW5E27UAHKG34", name="GS", weight=0.5, tags=[PositionTag(name="tag1", value="tagvalue1")], ), Position( identifier='GS UN', asset_id="MA4B66MW5E27UAHKG34", name="GS", weight=-0.8, tags=[PositionTag(name="tag2", value="tagvalue2")], ), Position( identifier='AAPL UW', asset_id="MA4B66MW5E27U9VBB94", name="Apple", weight=0.5, tags=[PositionTag(name="tag3", value="tagvalue3")], ), ], ), ] same_assets_different_weights_pricing_results = [ { 'date': '2024-04-30', 'positions': [ { 'assetId': 'MA4B66MW5E27UAHKG34', 'weight': 0.38, 'closePrice': 1000, 'fxClosePrice': 1, 'quantity': 1, 'notional': 1000, 'referenceWeight': 0.5, }, { 'assetId': 'MA4B66MW5E27UAHKG34', 'weight': -0.68, 'closePrice': 1000, 'fxClosePrice': 1, 'quantity': -1.5, 'notional': -1000, 'referenceWeight': -0.8, }, { 'assetId': 'MA4B66MW5E27U9VBB94', 'weight': 0.41, 'closePrice': 1000, 'fxClosePrice': 1, 'quantity': 1, 'notional': 1000, 'referenceWeight': 0.5, }, ], 'targetNotional': 1000, }, { 'date': '2024-05-01', 'positions': [ { 'assetId': 'MA4B66MW5E27UAHKG34', 'weight': 0.42671, 'closePrice': 1000, 'fxClosePrice': 1, 'quantity': 1, 'notional': 1000, 'referenceWeight': 0.5, }, { 'assetId': 'MA4B66MW5E27UAHKG34', 'weight': -0.72, 'closePrice': 1000, 'fxClosePrice': 1, 'quantity': -1.5, 'notional': -1000, 'referenceWeight': -0.8, }, { 'assetId': 'MA4B66MW5E27U9VBB94', 'weight': 0.51099, 'closePrice': 1000, 'fxClosePrice': 1, 'quantity': 1, 'notional': 1000, 'referenceWeight': 0.5, }, ], 'targetNotional': 1000, }, ] mocker.patch.object(GsPriceApi, "price_many_positions", return_value=same_assets_different_weights_pricing_results) PositionSet.price_many(different_tags_common_asset_id_positions) for position_set in different_tags_common_asset_id_positions: if position_set.date == dt.date(2024, 4, 30): for position in position_set.positions: if position.identifier == "GS UN": assert position.asset_id == "MA4B66MW5E27UAHKG34" assert position.name == "GS" assert position.weight in [0.38, -0.68] assert position.quantity in [1, -1.5] assert position.notional in [-1000, 1000] if position.weight == 0.38: assert position.tags[0] == PositionTag(name="tag1", value="tagvalue1") else: assert position.tags[0] == PositionTag(name="tag2", value="tagvalue2") else: assert position.asset_id == "MA4B66MW5E27U9VBB94" assert position.name == "Apple" assert position.weight == 0.41 assert position.notional == 1000 assert not position.tags else: for position in position_set.positions: if position.identifier == "GS UN": assert position.asset_id == "MA4B66MW5E27UAHKG34" assert position.name == "GS" assert position.weight in [0.42671, -0.72] assert position.quantity in [1, -1.5] assert position.notional in [-1000, 1000] if position.weight == 0.42671: assert position.tags[0] == PositionTag(name="tag1", value="tagvalue1") else: assert position.tags[0] == PositionTag(name="tag2", value="tagvalue2") else: assert position.asset_id == "MA4B66MW5E27U9VBB94" assert position.name == "Apple" assert position.weight == 0.51099 assert position.notional == 1000 assert position.tags[0] == PositionTag(name="tag3", value="tagvalue3") # There are empty positions in some position sets different_tags_common_asset_id_positions = [ PositionSet(date=dt.date(2024, 4, 30), reference_notional=1000, positions=[]), PositionSet( date=dt.date(2024, 5, 1), reference_notional=1000, positions=[ Position( identifier='GS UN', asset_id="MA4B66MW5E27UAHKG34", name="GS", weight=0.5, tags=[PositionTag(name="tag1", value="tagvalue1")], ), Position( identifier='GS UN', asset_id="MA4B66MW5E27UAHKG34", name="GS", weight=-0.8, tags=[PositionTag(name="tag2", value="tagvalue2")], ), Position( identifier='AAPL UW', asset_id="MA4B66MW5E27U9VBB94", name="Apple", weight=0.5, tags=[PositionTag(name="tag3", value="tagvalue3")], ), ], ), ] mocker.patch.object( GsPriceApi, "price_many_positions", return_value=[same_assets_different_weights_pricing_results[1]] ) PositionSet.price_many(different_tags_common_asset_id_positions) for position_set in different_tags_common_asset_id_positions: if position_set.date == dt.date(2024, 4, 30): assert not position_set.positions assert not position_set.unpriced_positions else: for position in position_set.positions: if position.identifier == "GS UN": assert position.asset_id == "MA4B66MW5E27UAHKG34" assert position.name == "GS" assert position.weight in [0.42671, -0.72] assert position.quantity in [1, -1.5] assert position.notional in [-1000, 1000] if position.weight == 0.42671: assert position.tags[0] == PositionTag(name="tag1", value="tagvalue1") else: assert position.tags[0] == PositionTag(name="tag2", value="tagvalue2") else: assert position.asset_id == "MA4B66MW5E27U9VBB94" assert position.name == "Apple" assert position.weight == 0.51099 assert position.notional == 1000 assert position.tags[0] == PositionTag(name="tag3", value="tagvalue3") # No positions have tags position_set_list_no_tags = deepcopy(position_sets_with_tags_and_notional) for pos_set in position_set_list_no_tags: [setattr(position, 'tags', None) for position in pos_set.positions] mocker.patch.object(GsPriceApi, "price_many_positions", return_value=expected_position_pricing_result) PositionSet.price_many(position_set_list_no_tags) for position_set in position_set_list_no_tags: priced_positions = position_set.positions for position in priced_positions: if position.identifier == "GS UN": assert position.asset_id == "MA4B66MW5E27UAHKG34" assert position.name == "GS" assert position.weight == 0.42671 assert position.quantity == 1 assert position.notional == 1000 assert not position.tags else: assert position.asset_id == "MA4B66MW5E27U9VBB94" assert position.name == "Apple" assert position.weight == 0.51099 assert position.quantity == 1 assert position.notional == 1000 assert not position.tags # Positions with notional quantities positions_with_notional_quantities = [ PositionSet( date=dt.date(2024, 4, 30), positions=[ Position( identifier='GS UN', asset_id="MA4B66MW5E27UAHKG34", name="GS", notional=15000, tags=[PositionTag(name="tag1", value="tagvalue1")], ), Position( identifier='AAPL UW', asset_id="MA4B66MW5E27U9VBB94", name="Apple", notional=20000, tags=[PositionTag(name="tag2", value="tagvalue2")], ), ], ), PositionSet( date=dt.date(2024, 5, 1), positions=[ Position( identifier='GS UN', asset_id="MA4B66MW5E27UAHKG34", name="GS", notional=10000, tags=[PositionTag(name="tag1", value="tagvalue1")], ), Position( identifier='AAPL UW', asset_id="MA4B66MW5E27U9VBB94", name="Apple", notional=25000, tags=[PositionTag(name="tag2", value="tagvalue2")], ), ], ), ] expected_position_notionals_result = [ { "date": "2024-04-30", "positions": [ { "assetId": "MA4B66MW5E27UAHKG34", "notional": 15000.0, "closePrice": 1000, "fxClosePrice": 1, "quantity": 1, "referenceWeight": 0.7826, "weight": 0.7826, }, { "assetId": "MA4B66MW5E27U9VBB94", "notional": 20000.0, "closePrice": 1000, "fxClosePrice": 1, "quantity": 1, "referenceWeight": 0.26753, "weight": 0.26753, }, ], "targetNotional": 35000.0, }, { "date": "2024-05-01", "positions": [ { "assetId": "MA4B66MW5E27UAHKG34", "notional": 10000.0, "closePrice": 1000, "fxClosePrice": 1, "quantity": 1, "referenceWeight": 0.745637, "weight": 0.745637, }, { "assetId": "MA4B66MW5E27U9VBB94", "notional": 25000.0, "closePrice": 1000, "fxClosePrice": 1, "quantity": 1, "referenceWeight": 0.276364, "weight": 0.276364, }, ], "targetNotional": 35000.0, }, ] mocker.patch.object(GsPriceApi, "price_many_positions", return_value=expected_position_notionals_result) PositionSet.price_many(positions_with_notional_quantities) for position_set in positions_with_notional_quantities: priced_positions = position_set.positions for position in priced_positions: if position.identifier == "GS UN": assert position.asset_id == "MA4B66MW5E27UAHKG34" assert position.weight in [0.7826, 0.745637] assert position.quantity == 1 assert round(position.notional, 2) in [10000.0, 15000.0] assert position.tags[0] == PositionTag(name="tag1", value="tagvalue1") else: assert position.asset_id == "MA4B66MW5E27U9VBB94" assert position.weight in [0.26753, 0.276364] assert position.quantity == 1 assert round(position.notional, 2) in [20000.0, 25000.0] assert position.tags[0] == PositionTag(name="tag2", value="tagvalue2") ================================================ FILE: gs_quant/test/markets/test_pricing_context.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from time import sleep from unittest.mock import patch, ANY import pytest from freezegun import freeze_time from gs_quant import risk from gs_quant.api.gs.risk import GsRiskApi from gs_quant.api.risk import RiskApi from gs_quant.base import RiskKey from gs_quant.common import PayReceive, Currency, PricingLocation from gs_quant.datetime import business_day_offset, today from gs_quant.errors import MqValueError from gs_quant.instrument import EqOption, IRSwap from gs_quant.markets import PricingContext, CloseMarket, OverlayMarket, MarketDataCoordinate from gs_quant.markets.portfolio import Portfolio from gs_quant.risk import RollFwd from gs_quant.test.utils.mock_calc import MockCalc WEEKEND_DATE = dt.date(2022, 3, 19) class TestProvider: pass def test_pricing_context(mocker): swap1 = IRSwap(PayReceive.Pay, '1y', Currency.EUR, name='EUR1y') future_date = business_day_offset(dt.date.today(), 10, roll='forward') with MockCalc(mocker): with RollFwd(date='10b', realise_fwd=True): market = swap1.market() with pytest.raises(ValueError): # cannot pass in future date into pricing context, use RollFwd instead with PricingContext(pricing_date=future_date): _ = swap1.calc(risk.Price) # cannot pass in market dated in the future into pricing context, use RollFwd instead with PricingContext(market=CloseMarket(date=future_date)): _ = swap1.calc(risk.Price) with PricingContext( market=OverlayMarket(base_market=CloseMarket(date=future_date, location='NYC'), market_data=market.result()) ): _ = swap1.calc(risk.Price) def test_pricing_dates(): # May be on weekend but doesn't matter for basic test future_date = dt.date.today() + dt.timedelta(10) yesterday = dt.date.today() - dt.timedelta(1) pc = PricingContext(pricing_date=future_date, market=CloseMarket(yesterday)) assert pc is not None with pytest.raises(ValueError, match="pricing_date in the future"): PricingContext(pricing_date=future_date) @freeze_time(WEEKEND_DATE) def test_weekend_dates(): assert dt.date.today() == WEEKEND_DATE # Check mock worked next_monday = WEEKEND_DATE + dt.timedelta(2) prev_friday = WEEKEND_DATE - dt.timedelta(1) pc = PricingContext(pricing_date=next_monday) with pc: assert pc.market == CloseMarket(prev_friday, PricingLocation.LDN) def test_market_data_object(): coord_val_pair = [ { 'coordinate': { 'mkt_type': 'IR', 'mkt_asset': 'USD', 'mkt_class': 'Swap', 'mkt_point': ('5y',), 'mkt_quoting_style': 'ATMRate', }, 'value': 0.9973194889, }, { 'coordinate': { 'mkt_type': 'IR', 'mkt_asset': 'USD', 'mkt_class': 'Swap', 'mkt_point': ('40y',), 'mkt_quoting_style': 'ATMRate', }, 'value': 'redacted', }, ] coordinates = {MarketDataCoordinate.from_dict(dic['coordinate']): dic['value'] for dic in coord_val_pair} overlay_market = OverlayMarket(base_market=CloseMarket(), market_data=coordinates) assert overlay_market.coordinates[0] == MarketDataCoordinate.from_dict(coord_val_pair[0]['coordinate']) assert overlay_market.redacted_coordinates[0] == MarketDataCoordinate.from_dict(coord_val_pair[1]['coordinate']) def test_pricing_context_metadata(): assert len(PricingContext.path) == 0 assert not PricingContext.has_prior c1 = PricingContext(pricing_date=dt.date(2022, 6, 15)) c2 = PricingContext(pricing_date=dt.date(2022, 6, 16)) c3 = PricingContext(pricing_date=dt.date(2022, 6, 17)) PricingContext.current = PricingContext() assert len(PricingContext.path) == 1 PricingContext.current = c1 assert len(PricingContext.path) == 1 with c2: with pytest.raises(MqValueError): PricingContext.current = PricingContext() assert PricingContext.current == c2 assert PricingContext.has_prior and PricingContext.prior == c1 assert len(PricingContext.path) == 2 with c3: assert PricingContext.current == c3 assert PricingContext.has_prior and PricingContext.prior == c2 assert len(PricingContext.path) == 3 PricingContext.pop() assert len(PricingContext.path) == 0 def test_creation(): c1 = PricingContext(pricing_date=dt.date(2022, 6, 15)) # All props except for the initialised one are defaulted. Context is not useable as-is assert c1.market == CloseMarket(c1.pricing_date, 'LDN') assert c1.market_data_location == PricingLocation.LDN assert c1.is_batch is False assert c1.use_cache is False assert c1._max_concurrent == 1000 assert c1.provider is None assert c1._dates_per_batch == 1 assert c1.pricing_date == dt.date(2022, 6, 15) def test_inheritance(): c1 = PricingContext(pricing_date=dt.date(2022, 6, 16), market_data_location='NYC', provider=TestProvider) c2 = PricingContext(pricing_date=dt.date(2022, 7, 1)) c3 = PricingContext(use_historical_diddles_only=True) with c1: with c2: # pricing date is set assert c2.pricing_date == dt.date(2022, 7, 1) # market data location is inherited from c1 (the active context) assert c2.market_data_location == c1.market_data_location # provider is inherited assert c2.provider == c1.provider # all other props have default values assert c2.is_batch is False assert c2.use_cache is False assert c2._max_concurrent == 1000 assert not c2.use_historical_diddles_only assert c2._dates_per_batch == 1 with c3: # market data location is inherited from c1 (the active context) assert c3.market_data_location == c1.market_data_location # pricing date is inherited from c2 (the prior context) assert c3.pricing_date == c2.pricing_date # all other props have default values assert c3.is_batch is False assert c3.use_cache is False assert c3._max_concurrent == 1000 assert c3.use_historical_diddles_only assert c3._dates_per_batch == 1 def test_max_concurrent(): a = PricingContext() assert a._max_concurrent == 1000 # Default value b = PricingContext() b._max_concurrent = 2000 c = PricingContext() c._max_concurrent = 3000 assert b._max_concurrent == 2000 # setter working? with b: assert a._max_concurrent == 2000 # a was unset so defaults to inheriting b's value assert c._max_concurrent == 3000 # c was explicitly set so keep's it's value with a: assert PricingContext.current._max_concurrent == 2000 # should be same as above property accessor with c: assert PricingContext.current._max_concurrent == 3000 # should be same as above property accessor def test_dates_per_batch(): a = PricingContext() assert a._dates_per_batch == 1 b = PricingContext() b._dates_per_batch = 2 c = PricingContext() c._dates_per_batch = 3 assert b._dates_per_batch == 2 with b: assert a._dates_per_batch == 2 assert c._dates_per_batch == 3 with a: assert PricingContext.current._dates_per_batch == 2 with c: assert PricingContext.current._dates_per_batch == 3 def test_current_inheritance(): cur = PricingContext.current assert cur.market_data_location == PricingLocation.LDN with PricingContext() as pc: assert pc.market_data_location == PricingLocation.LDN PricingContext.current = PricingContext(market_data_location='TKO') # We can set props on the current so that props are inherited globally new_cur = PricingContext.current assert new_cur.market_data_location == PricingLocation.TKO assert PricingContext().market_data_location == new_cur.market_data_location with PricingContext(): with PricingContext() as pc2: assert pc2.market_data_location == new_cur.market_data_location # Exit the current PricingContext.pop() def test_cleanup(): c1 = PricingContext(pricing_date=dt.date(2022, 4, 6)) c2 = PricingContext(request_priority=5000) default_date = c2.pricing_date with c1: with c2: assert c2.request_priority == 5000 # pricing_date is inherited from c1 assert c2.pricing_date is not None assert c2.pricing_date == c1.pricing_date # on exit from c2, the pricing date would still show what it would be as inside c1 still assert c2.pricing_date == c1.pricing_date # pricing date is cleaned up on exit from c1. request priority remains set assert c2.request_priority == 5000 assert c2.pricing_date == default_date def test_market_props(): PricingContext.current = PricingContext() # Reset # market_data_location cannot conflict with market.location with pytest.raises(ValueError): PricingContext( market=CloseMarket(date=dt.date(2022, 4, 6), location='NYC'), pricing_date=dt.date(2022, 7, 4), market_data_location='TKO', ) # Default pricing date and market location are today and LDN, respectively pc = PricingContext() with pc: assert pc.market_data_location == PricingLocation.LDN assert pc.pricing_date == business_day_offset(today(PricingLocation.LDN), 0, roll='preceding') # pricing_date and market.date can be different pc = PricingContext(market=CloseMarket(date=dt.date(2022, 4, 6), location='NYC'), pricing_date=dt.date(2022, 7, 4)) with pc: assert pc.pricing_date == dt.date(2022, 7, 4) assert pc.market.date == dt.date(2022, 4, 6) # if market is not specified, it is inferred from pricing_date and market_data_location pc = PricingContext(pricing_date=dt.date(2022, 7, 4), market_data_location='TKO') cm = CloseMarket(date=pc.pricing_date, location=pc.market_data_location) assert pc.market.date == cm.date assert pc.market.location == cm.location # market is not inherited pc = PricingContext(market=CloseMarket(date=dt.date(2022, 4, 6), location='NYC'), pricing_date=dt.date(2022, 7, 4)) with pc: # pc gets market_data_location from its market assert pc.market_data_location == PricingLocation.NYC with PricingContext() as inner: # Inner inherits pricing_date assert inner.pricing_date == pc.pricing_date # Inner initialises Market with its own pricing_date cm = CloseMarket(date=inner.pricing_date) assert inner.market.date == cm.date # Inner also inherits market_data_location assert inner.market_data_location == pc.market_data_location # Inner uses its own market_data_location to build its market assert inner.market.location == inner.market_data_location def test_pricing_does_not_affect_context(mocker): # Pricing instruments and portfolios uses the current pricing context. This should not affect its properties swap1 = IRSwap(PayReceive.Pay, '1y', name='EUR1y') swap2 = IRSwap(PayReceive.Pay, '1y', name='EUR2y') port = Portfolio([swap1, swap2]) with MockCalc(mocker): cm = CloseMarket(date=dt.date(2022, 7, 5), location='TKO') pc = PricingContext(market_data_location='TKO', pricing_date=dt.date(2022, 4, 6), market=cm) assert pc.market_data_location == PricingLocation.TKO assert pc.pricing_date == dt.date(2022, 4, 6) assert pc.market == cm assert pc.is_batch is False with pc: assert pc.is_batch is False swap1.resolve() swap1.dollar_price() swap2.calc(risk.IRDelta) port.resolve() port.price() port.calc(risk.IRDelta) assert pc.market_data_location == PricingLocation.TKO assert pc.pricing_date == dt.date(2022, 4, 6) assert pc.market == cm assert pc.is_batch is False assert pc.market_data_location == PricingLocation.TKO assert pc.pricing_date == dt.date(2022, 4, 6) assert pc.market == cm assert pc.is_batch is False def test_different_nested_locations(mocker): s = IRSwap(name='location_test_swap') with MockCalc(mocker): with PricingContext(market_data_location='TKO', pricing_date=dt.date(2022, 7, 5)): with PricingContext(market_data_location='NYC', pricing_date=dt.date(2022, 7, 11)): # Outer context should make no difference in pricing nyc_price_nested = s.price() with PricingContext(market_data_location='NYC', pricing_date=dt.date(2022, 7, 11)): nyc_price = s.price() assert nyc_price_nested.result() == nyc_price.result() @patch.object(RiskApi, 'run') @patch.object(RiskApi, 'drain_queue') def test_async_behaviour(queue_mock, run_mock): s = IRSwap() # we do not actually run requests from the RiskApi run_mock.return_value = None # queue to drain should hence be empty queue_mock.return_value = True, [] with PricingContext(is_async=True): s.price() # sleep to give spawned threads time to call RiskApi.run sleep(1) # threads should see the _max_concurrent property of the PricingContext as 1000 even though it's exited run_mock.assert_called_with(ANY, ANY, 1000, ANY, timeout=ANY, span=ANY) def test_use_context_for_inheritance(): cur = PricingContext.current assert cur.active_context == cur with PricingContext(set_parameters_only=True) as pc1: # If we do price anything in a context meant to be used for inheritance, it will be used as active assert pc1.active_context == pc1 with PricingContext(set_parameters_only=True) as pc2: # This happens at any level assert pc2.active_context == pc2 with PricingContext() as pc3: # pc3 is the outermost entered context not meant to be used for inheritance only, so it becomes active assert pc3.active_context == pc3 with PricingContext() as pc4: assert pc4.active_context == pc3 @patch.object(PricingContext, '_calc') def test_provider(calc_mock): calc_mock.return_value = None pc = PricingContext(provider=None) inst = EqOption() inst.PROVIDER = TestProvider pc.calc(inst, None) calc_mock.assert_called_with(inst, RiskKey(TestProvider, None, None, ANY, None, None)) pc = PricingContext(provider=GsRiskApi) inst = EqOption() inst.PROVIDER = TestProvider pc.calc(inst, None) calc_mock.assert_called_with(inst, RiskKey(GsRiskApi, None, None, ANY, None, None)) ================================================ FILE: gs_quant/test/markets/test_report.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import pytest from gs_quant.api.gs.data import GsDataApi from gs_quant.api.gs.portfolios import Portfolio from gs_quant.api.gs.thematics import GsThematicApi from gs_quant.markets.report import FactorRiskReport, PerformanceReport, ThematicReport, flatten_results_into_df from gs_quant.session import GsSession, Environment from gs_quant.common import ReportParameters from gs_quant.target.portfolios import RiskAumSource from gs_quant.target.reports import PositionSourceType, ReportType, ReportStatus, Report fake_pfr = FactorRiskReport( risk_model_id='AXUS4M', fx_hedged=True, report_id='PFRID', position_source_type=PositionSourceType.Portfolio, position_source_id='PORTFOLIOID', report_type=ReportType.Portfolio_Factor_Risk, status=ReportStatus.done, ) fake_ppa = PerformanceReport( report_id='PPAID', position_source_type=PositionSourceType.Portfolio, position_source_id='PORTFOLIOID', report_type=ReportType.Portfolio_Performance_Analytics, parameters=None, status=ReportStatus.done, ) fake_pta = ThematicReport( report_id='PTAID', position_source_type=PositionSourceType.Portfolio, position_source_id='PORTFOLIOID', report_type=ReportType.Portfolio_Thematic_Analytics, parameters=None, status=ReportStatus.done, ) factor_risk_results = [ { 'date': '2021-01-02', 'factor': 'factor1', 'pnl': 123, 'proportionOfRisk': 100, 'exposure': 200, 'annualRisk': 3928, 'dailyRisk': 202, }, { 'date': '2021-01-03', 'factor': 'factor1', 'pnl': 124, 'proportionOfRisk': 200, 'exposure': 100, 'annualRisk': 392, 'dailyRisk': 21, }, { 'date': '2021-01-04', 'factor': 'factor1', 'pnl': 125, 'proportionOfRisk': 300, 'exposure': 150, 'annualRisk': 39, 'dailyRisk': 22, }, ] thematic_results = [ { "date": "2021-07-12", "reportId": "PTAID", "basketId": "MA01GPR89HZF1FZ5", "region": "Asia", "grossExposure": 3.448370345015856e8, "thematicExposure": 1.1057087573594835e8, "updateTime": "2021-07-20T23:43:38Z", }, { "date": "2021-07-13", "reportId": "PTAID", "basketId": "MA01GPR89HZF1FZ5", "region": "Asia", "grossExposure": 3.375772519907556e8, "thematicExposure": 1.0511196135243121e8, "updateTime": "2021-07-20T23:43:38Z", }, { "date": "2021-07-14", "reportId": "PTAID", "basketId": "MA01GPR89HZF1FZ5", "region": "Asia", "grossExposure": 3.321189950666118e8, "thematicExposure": 1.0089556961211234e8, "updateTime": "2021-07-20T23:43:38Z", }, { "date": "2021-07-15", "reportId": "PTAID", "basketId": "MA01GPR89HZF1FZ5", "region": "Asia", "grossExposure": 3.274071805135091e8, "thematicExposure": 9.706991264825605e7, "updateTime": "2021-07-20T23:43:38Z", }, ] custom_aum = { 'data': [ {'date': '2023-06-01', 'aum': 100}, {'date': '2023-06-02', 'aum': 100}, {'date': '2023-06-05', 'aum': 100}, {'date': '2023-06-06', 'aum': 100}, ] } ppa_data = [ {'date': "2023-06-01", 'reportId': "PPAID", 'netExposure': 2414214.74}, {'date': "2023-06-02", 'reportId': "PPAID", 'netExposure': 2414214.74}, {'date': "2023-06-05", 'reportId': "PPAID", 'netExposure': 2414214.74}, {'date': "2023-06-06", 'reportId': "PPAID", 'netExposure': 2414214.74}, {'date': "2023-06-07", 'reportId': "PPAID", 'netExposure': 2414214.74}, ] def test_get_performance_report(mocker): # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object( GsSession.current, '_get', return_value=Report( id='PPAID', position_source_type=PositionSourceType.Portfolio, position_source_id='PORTFOLIOID', parameters=None, type=ReportType.Portfolio_Performance_Analytics, status=ReportStatus.done, ), ) # run test response = PerformanceReport.get('PPAID') assert response.type == ReportType.Portfolio_Performance_Analytics def test_get_aum_source(mocker): portfolio = Portfolio(id='MP123', currency='USD', name='Example Port', aum_source=RiskAumSource.Gross) # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', return_value=portfolio) # run test response = fake_ppa.get_aum_source() assert response == RiskAumSource.Gross def test_get_custom_aum(mocker): # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', return_value=custom_aum) # run test response = fake_ppa.get_custom_aum() assert len(response) == 4 def test_get_aum(mocker): portfolio = Portfolio(id='MP123', currency='USD', name='Example Port', aum_source=RiskAumSource.Net) # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', return_value=portfolio) mocker.patch.object(GsDataApi, 'query_data', return_value=ppa_data) # run test response = fake_ppa.get_aum(start_date=dt.date(2023, 6, 1), end_date=dt.date(2023, 6, 7)) assert len(response) == 5 def test_get_risk_model_id(): assert fake_pfr.get_risk_model_id() == 'AXUS4M' def test_set_position_target(): factor_report = FactorRiskReport( report_id='PFRID', position_source_type=PositionSourceType.Portfolio, position_source_id='PORTFOLIOID', report_type=ReportType.Portfolio_Factor_Risk, parameters=ReportParameters(fx_hedged=True, risk_model='AXUS4M'), status=ReportStatus.done, ) factor_report.set_position_source('MA3FMSN9VNMD') assert factor_report.position_source_type == PositionSourceType.Asset assert factor_report.type == ReportType.Asset_Factor_Risk def test_get_factor_risk_report(mocker): # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object( GsSession.current, '_get', return_value=Report( id='PFRID', position_source_type=PositionSourceType.Portfolio, position_source_id='PORTFOLIOID', parameters=ReportParameters(risk_model='AXUS4M', fx_hedged=True), type=ReportType.Portfolio_Factor_Risk, status=ReportStatus.done, ), ) # run test response = FactorRiskReport.get('PFRID') assert response.type == ReportType.Portfolio_Factor_Risk def test_get_factor_pnl(mocker): # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', return_value=factor_risk_results) # run test response = fake_pfr.get_factor_pnl(factor_names=['factor1']) assert len(response) == 3 def test_get_factor_proportion_of_risk(mocker): # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', return_value=factor_risk_results) # run test response = fake_pfr.get_factor_proportion_of_risk(factor_names=['factor1']) assert len(response) == 3 def test_get_factor_exposure(mocker): # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', return_value=factor_risk_results) # run test response = fake_pfr.get_factor_exposure(factor_names=['factor1']) assert len(response) == 3 def test_get_annual_risk(mocker): # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', return_value=factor_risk_results) # run test response = fake_pfr.get_annual_risk('factor1') assert len(response) == 3 def test_get_daily_risk(mocker): # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', return_value=factor_risk_results) # run test response = fake_pfr.get_daily_risk('factor1') assert len(response) == 3 def test_get_measures(mocker): # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsDataApi, 'query_data', return_value=thematic_results) # run test response = fake_pta._get_measures(["grossExposure", "thematicExposure"]) assert len(response) == 4 def test_flatten_results_into_df(): thematics_endpoint_results = [ { "date": "2020-09-14", "topFiveThematicExposures": [ { "basketName": "GS TMT MegaCap Tech", "basketTicker": "GSTMTMEG", "basketRegion": "Americas", "basketCurrency": "USD", "basketId": "MA459FTE58S22HY4", "thematicExposure": 11251933.154873407, "thematicBeta": 1.438066493862124, }, { "basketName": "Hedge Fund VIP", "basketTicker": "GSTHHVIP", "basketRegion": "Americas", "basketCurrency": "USD", "basketId": "MAMGDVSWVXWHEHFQ", "thematicExposure": 5158237.284525287, "thematicBeta": 0.6592545568983715, }, { "basketName": "GS Asia Stay at Home", "basketTicker": "GSXASTAY", "basketRegion": "Asia", "basketCurrency": "USD", "basketId": "MA1C2Q2AAAT57AX7", "thematicExposure": 3878730.3538401644, "thematicBeta": 0.49572567520698857, }, { "basketName": "GS Secular Growth", "basketTicker": "GSXUSGRO", "basketRegion": "Americas", "basketCurrency": "USD", "basketId": "MAHQEJG82N4DNBCH", "thematicExposure": 3642083.749962656, "thematicBeta": 0.46548077886443323, }, { "basketName": "GS China 14th 5Year Plan", "basketTicker": "GSXAC5YP", "basketRegion": "Asia", "basketCurrency": "USD", "basketId": "MAGP7WNPCMN5MJWQ", "thematicExposure": 3347766.929109541, "thematicBeta": 0.42786527290441334, }, ], }, { "date": "2020-09-15", "topFiveThematicExposures": [ { "basketName": "GS TMT MegaCap Tech", "basketTicker": "GSTMTMEG", "basketRegion": "Americas", "basketCurrency": "USD", "basketId": "MA459FTE58S22HY4", "thematicExposure": 11304916.702270757, "thematicBeta": 1.4050953516801092, }, { "basketName": "Hedge Fund VIP", "basketTicker": "GSTHHVIP", "basketRegion": "Americas", "basketCurrency": "USD", "basketId": "MAMGDVSWVXWHEHFQ", "thematicExposure": 5241527.529878033, "thematicBeta": 0.651472820357501, }, { "basketName": "GS Asia Stay at Home", "basketTicker": "GSXASTAY", "basketRegion": "Asia", "basketCurrency": "USD", "basketId": "MA1C2Q2AAAT57AX7", "thematicExposure": 3868608.7685798025, "thematicBeta": 0.4808318664664322, }, { "basketName": "GS Secular Growth", "basketTicker": "GSXUSGRO", "basketRegion": "Americas", "basketCurrency": "USD", "basketId": "MAHQEJG82N4DNBCH", "thematicExposure": 3661450.0674766554, "thematicBeta": 0.4550839785654401, }, { "basketName": "GS China 14th 5Year Plan", "basketTicker": "GSXAC5YP", "basketRegion": "Asia", "basketCurrency": "USD", "basketId": "MAGP7WNPCMN5MJWQ", "thematicExposure": 3344638.716916008, "thematicBeta": 0.41570729249548893, }, ], }, { "date": "2020-09-16", "topFiveThematicExposures": [ { "basketName": "GS TMT MegaCap Tech", "basketTicker": "GSTMTMEG", "basketRegion": "Americas", "basketCurrency": "USD", "basketId": "MA459FTE58S22HY4", "thematicExposure": 11173613.76263776, "thematicBeta": 1.487220141961805, }, { "basketName": "Hedge Fund VIP", "basketTicker": "GSTHHVIP", "basketRegion": "Americas", "basketCurrency": "USD", "basketId": "MAMGDVSWVXWHEHFQ", "thematicExposure": 4800709.171521289, "thematicBeta": 0.6389796110065071, }, { "basketName": "GS Asia Stay at Home", "basketTicker": "GSXASTAY", "basketRegion": "Asia", "basketCurrency": "USD", "basketId": "MA1C2Q2AAAT57AX7", "thematicExposure": 3767199.2638762216, "thematicBeta": 0.501418318463317, }, { "basketName": "GS Secular Growth", "basketTicker": "GSXUSGRO", "basketRegion": "Americas", "basketCurrency": "USD", "basketId": "MAHQEJG82N4DNBCH", "thematicExposure": 3645814.9989552647, "thematicBeta": 0.4852619408094451, }, { "basketName": "GS US Stay at Home", "basketTicker": "GSXUSTAY", "basketRegion": "Americas", "basketCurrency": "USD", "basketId": "MA9B9TEMQ2RW16K9", "thematicExposure": 3354419.7766576866, "thematicBeta": 0.4464769198593304, }, ], }, ] df = flatten_results_into_df(thematics_endpoint_results) assert df.shape == (15, 8) thematics_endpoint_results = [ { "date": "2020-09-14", "allThematicExposures": [ { "basketName": "GS TMT MegaCap Tech", "basketTicker": "GSTMTMEG", "basketRegion": "Americas", "basketCurrency": "USD", "basketId": "MA459FTE58S22HY4", "thematicExposure": 11251933.154873407, "thematicBeta": 1.438066493862124, }, { "basketName": "Hedge Fund VIP", "basketTicker": "GSTHHVIP", "basketRegion": "Americas", "basketCurrency": "USD", "basketId": "MAMGDVSWVXWHEHFQ", "thematicExposure": 5158237.284525287, "thematicBeta": 0.6592545568983715, }, { "basketName": "GS Asia Stay at Home", "basketTicker": "GSXASTAY", "basketRegion": "Asia", "basketCurrency": "USD", "basketId": "MA1C2Q2AAAT57AX7", "thematicExposure": 3878730.3538401644, "thematicBeta": 0.49572567520698857, }, ], } ] df = flatten_results_into_df(thematics_endpoint_results) assert df.shape == (3, 8) def test_get_thematic_breakdown(mocker): thematics_endpoint_results = [ { "date": "2020-09-14", "thematicBreakdownByAsset": [ { "basketName": "GS Asia Stay at Home", "basketTicker": "GSXASTAY", "basketRegion": "Asia", "basketCurrency": "USD", "basketId": "MA1C2Q2AAAT57AX7", "thematicBeta": 0.49572567520698857, "thematicBreakdownByAsset": [ { "name": "Allstate Corp", "bbid": "ALL UN", "sector": "Financials", "industry": "Insurance", "region": "Americas", "country": "United States", "assetId": "MA4B66MW5E27U9YGM27", "beta": -0.19208866590832985, "thematicExposure": 38530.05709547743, }, { "name": "Allergan PLC", "bbid": "AGN UN", "sector": "None", "industry": "None", "region": "Europe", "country": "United States", "assetId": "MA4B66MW5E27U9XPVMG", "beta": 0.10085184726854478, "thematicExposure": -41210.418676042646, }, { "name": "Bristol-Myers Squibb Co", "bbid": "BMY UN", "sector": "Health Care", "industry": "Pharmaceuticals", "region": "Americas", "country": "United States", "assetId": "MA4B66MW5E27UA4479J", "beta": 0.02101418329131077, "thematicExposure": 19108.02476936883, }, { "name": "Bank of New York Mellon Corp", "bbid": "BK UN", "sector": "Financials", "industry": "Capital Markets", "region": "Americas", "country": "United States", "assetId": "MA4B66MW5E27UA39JL8", "beta": -0.060160353375185896, "thematicExposure": 11806.1366204509, }, ], } ], } ] mocker.patch.object(GsThematicApi, 'get_thematics', return_value=thematics_endpoint_results) df = fake_pta.get_thematic_breakdown(date=dt.date(2020, 9, 14), basket_id='MA1C2Q2AAAT57AX7') assert df.shape == (4, 9) if __name__ == '__main__': pytest.main(args=[__file__]) ================================================ FILE: gs_quant/test/markets/test_scenarios.py ================================================ """ Copyright 2024 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from unittest import mock import pytest from gs_quant.common import Entitlements as TargetEntitlements from gs_quant.entities.entitlements import Entitlements, EntitlementBlock from gs_quant.markets.scenario import FactorScenario, FactorScenarioType, FactorShockParameters, FactorShock from gs_quant.session import GsSession, Environment from gs_quant.target.risk import Scenario default_entitlements = TargetEntitlements(edit=(), view=(), admin=()) default_scenario_parameters = { "riskModel": "MODEL_ID", "propagateShocks": True, "factorShocks": [{"factor": "Factor 1", "shock": 5}, {"factor": "Factor 2", "shock": -5}], } mock_scenario_obj = Scenario( name="Scenario 1", description="Scenario 1", entitlements=default_entitlements, id_="MSCENARIO", parameters=default_scenario_parameters, type_=FactorScenarioType.Factor_Shock, ) def mock_factor_scenario(mocker): from gs_quant.session import OAuth2Session OAuth2Session.init = mock.MagicMock(return_value=None) GsSession.use(Environment.QA, 'client_id', 'secret') mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'post', return_value=mock_scenario_obj) mocker.patch.object(GsSession.current.sync, 'get', return_value=mock_scenario_obj) mocker.patch.object(GsSession.current.sync, 'put', return_value=mock_scenario_obj) return FactorScenario.get('MSCENARIO') def test_create_factor_scenario(mocker): mock_factor_scenario(mocker) mocker.patch.object(GsSession.current.sync, 'post', return_value=mock_scenario_obj) new_scenario = FactorScenario( name="Scenario 1", type=FactorScenarioType.Factor_Shock, parameters=FactorShockParameters( factor_shocks=[FactorShock(factor="Factor 1", shock=5), FactorShock(factor="Factor 2", shock=-5)], propagate_shocks=True, risk_model="MODEL_ID", ), entitlements=Entitlements(view=EntitlementBlock(), edit=EntitlementBlock(), admin=EntitlementBlock()), id_="MSCENARIO", description="Scenario 1", ) new_scenario.save() assert new_scenario.id == mock_scenario_obj.id assert new_scenario.name == mock_scenario_obj.name assert new_scenario.description == mock_scenario_obj.description assert new_scenario.entitlements == Entitlements.from_target(mock_scenario_obj.entitlements) assert new_scenario.parameters == FactorShockParameters.from_dict(mock_scenario_obj.parameters) assert new_scenario.type == mock_scenario_obj.type def test_update_scenario_entitlements(mocker): scenario = mock_factor_scenario(mocker) new_entitlements = Entitlements( view=EntitlementBlock(roles=["role:A"]), edit=EntitlementBlock(roles=["role:B"]), admin=EntitlementBlock() ) scenario.entitlements = new_entitlements scenario.save() mocker.patch.object(GsSession.current.sync, 'get', return_value=scenario) assert 'role:A' in scenario.entitlements.view.roles assert 'role:B' in scenario.entitlements.edit.roles if __name__ == "__main__": pytest.main([__file__]) ================================================ FILE: gs_quant/test/markets/test_securities.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import copy import datetime as dt import json from enum import Enum import pytest from gs_quant.base import EnumBase from gs_quant.api.gs.assets import GsAsset, GsAssetApi from gs_quant.errors import MqTypeError, MqValueError, MqRequestError from gs_quant.markets import PricingContext from gs_quant.markets.securities import ( SecurityMaster, AssetIdentifier, AssetType, ExchangeCode, SecurityMasterSource, SecurityIdentifier, Asset, SecMasterAsset, ) from gs_quant.common import AssetClass, AssetType as GsAssetType from gs_quant.session import GsSession, Environment def test_get_asset(mocker): marquee_id = 'MA1234567890' mock_response = GsAsset(asset_class=AssetClass.Equity, type_=GsAssetType.Single_Stock, name='Test Asset') # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', return_value=mock_response) asset = SecurityMaster.get_asset(marquee_id, AssetIdentifier.MARQUEE_ID) assert asset.name == "Test Asset" assert asset.get_type() == AssetType.STOCK asset = SecurityMaster.get_asset(marquee_id, AssetIdentifier.MARQUEE_ID, as_of=dt.date.today()) assert asset.name == "Test Asset" assert asset.get_type() == AssetType.STOCK asset = SecurityMaster.get_asset(marquee_id, AssetIdentifier.MARQUEE_ID, as_of=dt.datetime.utcnow()) assert asset.name == "Test Asset" assert asset.get_type() == AssetType.STOCK mock_response = GsAsset(asset_class=AssetClass.Equity, type_=GsAssetType.Index, name='Test Asset') mocker.patch.object(GsSession.current.sync, 'get', return_value=mock_response) asset = SecurityMaster.get_asset(marquee_id, AssetIdentifier.MARQUEE_ID) assert asset.name == "Test Asset" assert asset.get_type() == AssetType.INDEX mock_response = GsAsset(asset_class=AssetClass.Equity, type_=GsAssetType.Future, name='Test Asset') mocker.patch.object(GsSession.current.sync, 'get', return_value=mock_response) asset = SecurityMaster.get_asset(marquee_id, AssetIdentifier.MARQUEE_ID) assert asset.name == "Test Asset" assert asset.get_type() == AssetType.FUTURE mock_response = GsAsset(asset_class=AssetClass.Equity, type_=GsAssetType.ETF, name='Test Asset') mocker.patch.object(GsSession.current.sync, 'get', return_value=mock_response) asset = SecurityMaster.get_asset(marquee_id, AssetIdentifier.MARQUEE_ID) assert asset.name == "Test Asset" assert asset.get_type() == AssetType.ETF mock_response = GsAsset( asset_class=AssetClass.Equity, type_=GsAssetType.Custom_Basket, name='Test Asset', id_=marquee_id ) mocker.patch.object(GsSession.current.sync, 'get', return_value=mock_response) asset = SecurityMaster.get_asset(marquee_id, AssetIdentifier.MARQUEE_ID) assert asset.name == "Test Asset" assert asset.get_type() == AssetType.CUSTOM_BASKET mock_response = { 'results': (GsAsset(id=marquee_id, assetClass='Equity', type='Single Stock', name='Test 1'),), } mocker.patch.object(GsSession.current.sync, 'post', return_value=mock_response) asset = SecurityMaster.get_asset('GS.N', AssetIdentifier.REUTERS_ID) assert asset.name == "Test 1" assert asset.get_type() == AssetType.STOCK asset = SecurityMaster.get_asset('GS', AssetIdentifier.TICKER, exchange_code=ExchangeCode.NYSE) assert asset.name == "Test 1" assert asset.get_type() == AssetType.STOCK asset = SecurityMaster.get_asset('GS', AssetIdentifier.TICKER, asset_type=AssetType.STOCK) assert asset.name == "Test 1" assert asset.get_type() == AssetType.STOCK mocker.patch.object(GsSession.current.sync, 'post', return_value={'results': ()}) asset = SecurityMaster.get_asset(marquee_id, AssetIdentifier.REUTERS_ID) assert asset is None def test_asset_identifiers(mocker): marquee_id = 'MA1234567890' mocker.patch.object(GsSession, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret')) mock_response = GsAsset( asset_class=AssetClass.Equity, type_=GsAssetType.Custom_Basket, name='Test Asset', id_=marquee_id ) mocker.patch.object(GsSession.current.sync, 'get', return_value=mock_response) asset = SecurityMaster.get_asset(marquee_id, AssetIdentifier.MARQUEE_ID) mock_response = { 'xrefs': ( { 'startDate': '1952-01-01', 'endDate': '2018-12-31', 'identifiers': {'ric': '.GSTHHOLD', 'bbid': 'GSTHHOLD', 'cusip': '9EQ24FOLD', 'ticker': 'GSTHHOLD'}, }, { 'startDate': '2019-01-01', 'endDate': '2952-12-31', 'identifiers': { 'ric': '.GSTHHVIP', 'bbid': 'GSTHHVIP', 'cusip': '9EQ24FPE5', 'ticker': 'GSTHHVIP', }, }, ) } mocker.patch.object(GsSession.current.sync, 'get', return_value=mock_response) identifiers = asset.get_identifiers(dt.date.today()) assert identifiers[AssetIdentifier.REUTERS_ID.value] == '.GSTHHVIP' assert identifiers[AssetIdentifier.BLOOMBERG_ID.value] == 'GSTHHVIP' assert identifiers[AssetIdentifier.CUSIP.value] == '9EQ24FPE5' assert identifiers[AssetIdentifier.TICKER.value] == 'GSTHHVIP' assert asset.get_identifier(AssetIdentifier.REUTERS_ID, as_of=dt.date.today()) == '.GSTHHVIP' assert asset.get_identifier(AssetIdentifier.BLOOMBERG_ID, as_of=dt.date.today()) == 'GSTHHVIP' assert asset.get_identifier(AssetIdentifier.CUSIP, as_of=dt.date.today()) == '9EQ24FPE5' assert asset.get_identifier(AssetIdentifier.TICKER, as_of=dt.date.today()) == 'GSTHHVIP' market = PricingContext(dt.date(2018, 3, 1)) with market: identifiers = asset.get_identifiers() assert identifiers[AssetIdentifier.REUTERS_ID.value] == '.GSTHHOLD' assert identifiers[AssetIdentifier.BLOOMBERG_ID.value] == 'GSTHHOLD' assert identifiers[AssetIdentifier.CUSIP.value] == '9EQ24FOLD' assert identifiers[AssetIdentifier.TICKER.value] == 'GSTHHOLD' market = PricingContext(dt.date(2018, 3, 1)) with market: identifiers = asset.get_identifiers() assert identifiers[AssetIdentifier.REUTERS_ID.value] == '.GSTHHOLD' assert identifiers[AssetIdentifier.BLOOMBERG_ID.value] == 'GSTHHOLD' assert identifiers[AssetIdentifier.CUSIP.value] == '9EQ24FOLD' assert identifiers[AssetIdentifier.TICKER.value] == 'GSTHHOLD' def test_asset_types(mocker): class MockType(EnumBase, Enum): Foo = "Bar" ata = getattr(SecurityMaster, '_SecurityMaster__gs_asset_to_asset') assert ata is not None asset = GsAsset(AssetClass.Equity, None, 'Test Asset') mocker.patch.object(json, 'dumps', return_value='{}') # with pytest.raises(ValueError) as exc_info: # setattr(asset, 'type', MockType.Foo) # assert 'is not a valid AssetType' in str(exc_info.value) # reached exception at end of function with pytest.raises(AttributeError) as exc_info: ata(asset) assert "has no attribute 'value'" in str(exc_info.value) # reached exception at end of function class SecMasterContext: def __enter__(self): SecurityMaster.set_source(SecurityMasterSource.SECURITY_MASTER) def __exit__(self, exc_type, exc_value, traceback): SecurityMaster.set_source(SecurityMasterSource.ASSET_SERVICE) class AssetContext: def __enter__(self): self.previous = SecurityMaster._source SecurityMaster.set_source(SecurityMasterSource.ASSET_SERVICE) def __exit__(self, exc_type, exc_value, traceback): SecurityMaster.set_source(self.previous) def test_get_security(mocker): mocker.patch.object(GsSession, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret')) mock_response = { "results": [ { "name": "GOLDMAN SACHS GROUP INC (New York Stock)", "type": "Common Stock", "currency": "USD", "tags": [], "id": "GSPD901026E154", "assetClass": "Equity", "identifiers": { "gsid": 901026, "ric": "GS.N", "id": "GSPD901026E154", "cusip": "38141G10", "sedol": "2407966", "isin": "US38141G1040", "ticker": "GS", "bbid": "GS UN", "bcid": "GS US", "gss": "GS", "primeId": "1003232152", "assetId": "MA4B66MW5E27UAHKG34", }, "company": {"name": "GOLDMAN SACHS GROUP INC", "identifiers": {"gsCompanyId": 25998}}, "product": {"name": "GOLDMAN SACHS GROUP INC", "identifiers": {"gsid": 901026}}, "exchange": {"name": "New York Stock", "identifiers": {"gsExchangeId": 154}}, } ], "totalResults": 1, } mock_identifier_history_response = { "results": [ { "startDate": "2007-01-01", "endDate": "9999-99-99", "value": "GS", "updateTime": "2002-02-09T17:58:27.58Z", "type": "bbg", }, { "startDate": "2007-01-01", "endDate": "9999-99-99", "value": "GS", "updateTime": "2002-02-09T17:57:14.546Z", "type": "ticker", }, { "startDate": "2007-01-01", "endDate": "9999-99-99", "type": "assetId", "value": "MA4B66MW5E27UAHKG34", "updateTime": "2002-10-30T21:30:29.993Z", }, { "startDate": "2007-01-01", "endDate": "9999-99-99", "type": "ric", "value": "GS.N", "updateTime": "2002-10-30T21:30:29.993Z", "gsExchangeId": 154, }, ] } mocker.patch.object(GsSession.current.sync, 'get', side_effect=[mock_response, mock_identifier_history_response]) with SecMasterContext(): asset = SecurityMaster.get_asset('GS UN', SecurityIdentifier.BBID) assert isinstance(asset, SecMasterAsset) assert asset.get_marquee_id() == 'MA4B66MW5E27UAHKG34' ids = asset.get_identifiers() assert ids[SecurityIdentifier.BBG.value] == 'GS' assert ids[SecurityIdentifier.RIC.value] == 'GS.N' assert ids[SecurityIdentifier.GSID.value] == 901026 def test_get_security_fields(mocker): mock_response = { "results": [ { "name": "GOLDMAN SACHS GROUP INC (New York Stock)", "id": "GSPD901026E154", "type": "Common Stock", "currency": "USD", "tags": [], "assetClass": "Equity", "identifiers": { "gsid": 901026, "ric": "GS.N", "id": "GSPD901026E154", "cusip": "38141G10", "sedol": "2407966", "isin": "US38141G1040", "ticker": "GS", "bbid": "GS UN", "bcid": "GS US", "gss": "GS", "primeId": "1003232152", "assetId": "MA4B66MW5E27UAHKG34", }, "exchange": {"name": "New York Stock", "identifiers": {"gsExchangeId": 154}}, } ], "totalResults": 1, } mock_identifiers_response = { "results": [ { "startDate": "2007-01-01", "endDate": "9999-99-99", "value": "GS", "updateTime": "2002-02-09T17:58:27.58Z", "type": "bbg", }, { "startDate": "2007-01-01", "endDate": "9999-99-99", "value": "1003232152", "updateTime": "2003-01-16T15:22:54.1Z", "type": "primeId", }, { "startDate": "2007-01-01", "endDate": "9999-99-99", "type": "assetId", "value": "MA4B66MW5E27UAHKG34", "updateTime": "2002-10-30T21:30:29.993Z", }, { "startDate": "2007-01-01", "endDate": "9999-99-99", "type": "ric", "value": "GS.N", "updateTime": "2002-10-30T21:30:29.993Z", "gsExchangeId": 154, }, ], } mocker.patch.object(GsSession.current.sync, 'get', side_effect=[mock_response, mock_identifiers_response]) with SecMasterContext(): asset = SecurityMaster.get_asset('GS UN', SecurityIdentifier.BBID, fields=['name', 'id']) assert isinstance(asset, SecMasterAsset) assert asset.get_marquee_id() == 'MA4B66MW5E27UAHKG34' assert asset.name == 'GOLDMAN SACHS GROUP INC (New York Stock)' ids = asset.get_identifiers() assert ids[SecurityIdentifier.BBG.value] == 'GS' assert ids[SecurityIdentifier.RIC.value] == 'GS.N' assert ids[SecurityIdentifier.PRIMEID.value] == '1003232152' assert ids[SecurityIdentifier.ID.value] == 'GSPD901026E154' def test_get_identifiers(mocker): assets = { "results": [ {"id": "GSPD901026E154", "identifiers": {"bbid": "GS UN"}}, {"id": "GSPD14593E459", "identifiers": {"bbid": "AAPL UW"}}, ], "totalResults": 2, } ids_gs = { "results": [ { "startDate": "2021-01-01", "endDate": "9999-99-99", "value": "38141G10", "updateTime": "2002-02-09T17:54:27.99Z", "type": "cusip", }, { "startDate": "2021-01-01", "endDate": "9999-99-99", "value": "2407966", "updateTime": "2002-02-09T17:54:47.77Z", "type": "sedol", }, ] } ids_ap = { "results": [ { "startDate": "2021-01-01", "endDate": "9999-99-99", "value": "03783310", "updateTime": "2003-04-15T22:36:17.593Z", "type": "cusip", }, { "startDate": "2021-01-01", "endDate": "9999-99-99", "value": "2046251", "updateTime": "2003-04-15T22:36:17.6Z", "type": "sedol", }, ] } mocker.patch.object(GsSession.current.sync, 'get', side_effect=[assets, ids_gs, ids_ap]) with SecMasterContext(): identifiers = SecurityMaster.get_identifiers(['GS UN', 'AAPL UW'], SecurityIdentifier.BBID) assert 'GS UN' in identifiers assert 'AAPL UW' in identifiers assert identifiers['GS UN'] == ids_gs['results'] assert identifiers['AAPL UW'] == ids_ap['results'] def test_get_all_identifiers(mocker): p1 = { "results": [ { "type": "Common Stock", "id": "GSPD901026E154", "assetClass": "Equity", "identifiers": {"gsid": 901026, "ric": "GS.N", "id": "GSPD901026E154", "bbid": "GS UN"}, } ], "totalResults": 1, } p2 = { "results": [ { "type": "Common Stock", "id": "GSPD14593E459", "assetClass": "Equity", "identifiers": { "gsid": 14593, "ric": "AAPL.OQ", "id": "GSPD14593E459", "bbid": "AAPL UW", }, } ], "totalResults": 1, } p3 = {"results": [], "totalResults": 0} mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', side_effect=[p1, p2, p3]) with SecMasterContext(): output = SecurityMaster.get_all_identifiers(use_offset_key=False) assert len(output) == 2 assert output['GSPD901026E154'] == p1['results'][0]['identifiers'] assert output['GSPD14593E459'] == p2['results'][0]['identifiers'] mocker.patch.object(GsSession.current.sync, 'get', side_effect=[p1, p2, p3]) with SecMasterContext(): output = SecurityMaster.get_all_identifiers(id_type=SecurityIdentifier.BBID, use_offset_key=False) assert len(output) == 2 assert output['GS UN'] == p1['results'][0]['identifiers'] assert output['AAPL UW'] == p2['results'][0]['identifiers'] def test_get_all_identifiers_with_assetTypes_not_none(mocker): mock_etf = { "results": [ { "type": "ETF", "id": "mock_ETF_id", "assetClass": "Equity", "identifiers": {"gsid": 1111111, "ric": "mock_ETF_ric", "id": "mock_ETF_id", "bbid": "mock_ETF_bbid"}, } ], "totalResults": 1, } mock_stock = { "results": [ { "type": "Common Stock", "id": "mock_stock_id", "assetClass": "Equity", "identifiers": { "gsid": 222222, "ric": "mock_stock_ric", "bbid": "mock_stock_bbid", # id omitted from nested dict for testing }, } ], "totalResults": 1, } mock_etf_and_stock = {"results": mock_stock['results'] + mock_etf['results'], "totalResults": 2} def get_identifiers_byte(*args, **kwargs): types = kwargs['payload']['type'] stock_str = SecurityMaster.asset_type_to_str(asset_class=AssetClass.Equity, asset_type=AssetType.STOCK) if len(types) == 1 and AssetType.ETF.value in types: return mock_etf elif len(types) == 1 and stock_str in types: return mock_stock elif len(types) == 2 and stock_str in types and AssetType.ETF.value in types: return mock_etf_and_stock mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', side_effect=get_identifiers_byte) with SecMasterContext(): output = SecurityMaster.get_all_identifiers(AssetClass.Equity, types=[AssetType.ETF]) assert len(output) == 1 assert output['mock_ETF_id'] == mock_etf['results'][0]['identifiers'] mocker.patch.object(GsSession.current.sync, 'get', side_effect=get_identifiers_byte) with SecMasterContext(): output = SecurityMaster.get_all_identifiers(AssetClass.Equity, types=[AssetType.STOCK]) assert len(output) == 1 assert output['mock_stock_id'] == mock_stock['results'][0]['identifiers'] with SecMasterContext(): output = SecurityMaster.get_all_identifiers(AssetClass.Equity, types=[AssetType.STOCK, AssetType.ETF]) assert len(output) == 2 assert output['mock_ETF_id'] == mock_etf['results'][0]['identifiers'] assert output['mock_stock_id'] == mock_stock['results'][0]['identifiers'] def test_offset_key(mocker): p1 = { "results": [ { "type": "Common Stock", "id": "GSPD901026E154", "assetClass": "Equity", "identifiers": {"gsid": 901026, "ric": "GS.N", "id": "GSPD901026E154", "bbid": "GS UN"}, } ], "offsetKey": "qwerty", "totalResults": 1, } p2 = { "results": [ { "type": "Common Stock", "id": "GSPD14593E459", "assetClass": "Equity", "identifiers": { "gsid": 14593, "ric": "AAPL.OQ", "id": "GSPD14593E459", "bbid": "AAPL UW", }, } ], "offsetKey": "azerty", "totalResults": 1, } p3 = {"results": [], "totalResults": 0} limited = False hits = [0] * 3 def fetch(*args, **kwargs): nonlocal limited if not limited: limited = True raise MqRequestError(429, 'too many requests') offset_key = kwargs['payload'].get('offsetKey') if offset_key is None: hits[0] += 1 return p1 if offset_key == "qwerty": hits[1] += 1 return p2 if offset_key == "azerty": hits[2] += 1 return p3 mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', side_effect=fetch) with SecMasterContext(): output = SecurityMaster.get_all_identifiers(sleep=0) assert len(output) == 2 assert output['GSPD901026E154'] == p1['results'][0]['identifiers'] assert output['GSPD14593E459'] == p2['results'][0]['identifiers'] assert all(map(lambda x: x == 1, hits)) mocker.patch.object(GsSession.current.sync, 'get', side_effect=fetch) with SecMasterContext(): output = SecurityMaster.get_all_identifiers(id_type=SecurityIdentifier.BBID, sleep=0) assert len(output) == 2 assert output['GS UN'] == p1['results'][0]['identifiers'] assert output['AAPL UW'] == p2['results'][0]['identifiers'] assert all(map(lambda x: x == 2, hits)) mocker.patch.object(GsSession.current.sync, 'get', side_effect=fetch) with SecMasterContext(): gen = SecurityMaster.get_all_identifiers_gen(id_type=SecurityIdentifier.BBID, sleep=0) page = next(gen) assert len(page) == 1 assert page['GS UN'] == p1['results'][0]['identifiers'] page = next(gen) assert len(page) == 1 assert page['AAPL UW'] == p2['results'][0]['identifiers'] with pytest.raises(StopIteration): next(gen) assert all(map(lambda x: x == 3, hits)) def test_map_identifiers(mocker): mock1 = { "results": [ { "assetId": "MA4B66MW5E27U9VBB93", "outputType": "rcic", "outputValue": "AAPL.O", "startDate": "2021-10-11", "endDate": "2021-10-12", "input": "AAPL UN", }, { "assetId": "MARCRZHY163GQ4H3", "outputType": "ric", "outputValue": "AAPL.N", "startDate": "2021-10-11", "endDate": "2021-10-12", "input": "AAPL UN", }, { "assetId": "MA4B66MW5E27UAHKG34", "outputType": "ric", "outputValue": "GS.N", "startDate": "2021-10-11", "endDate": "2021-10-12", "input": "GS UN", }, { "outputType": "rcic", "outputValue": "GS", "startDate": "2021-10-11", "endDate": "2021-10-12", "input": "GS UN", }, { "outputType": "gsid", "outputValue": 14593, "startDate": "2021-10-11", "endDate": "2021-10-12", "input": "AAPL UN", }, { "outputType": "gsid", "outputValue": 901026, "startDate": "2021-10-11", "endDate": "2021-10-12", "input": "GS UN", }, ] } mock2 = copy.deepcopy(mock1) mock2["results"].extend( [ { "outputType": "bbg", "outputValue": "AAPL", "exchange": "UN", "compositeExchange": "US", "startDate": "2021-10-11", "endDate": "2021-10-12", "input": "AAPL UN", }, { "outputType": "bbg", "outputValue": "GS", "exchange": "UN", "compositeExchange": "US", "startDate": "2021-10-11", "endDate": "2021-10-12", "input": "GS UN", }, ] ) mocker.patch.object(GsSession.current.sync, 'get', side_effect=[mock2, mock2]) start = dt.date(2021, 10, 11) end = dt.date(2021, 10, 12) expected = { "2021-10-11": {"AAPL UN": {"ric": ["AAPL.N"], "gsid": [14593]}, "GS UN": {"ric": ["GS.N"], "gsid": [901026]}}, "2021-10-12": {"AAPL UN": {"ric": ["AAPL.N"], "gsid": [14593]}, "GS UN": {"ric": ["GS.N"], "gsid": [901026]}}, } with SecMasterContext(): actual = SecurityMaster.map_identifiers( SecurityIdentifier.BBID, ['GS UN', 'AAPL UN'], [SecurityIdentifier.RIC, SecurityIdentifier.GSID], start, end ) assert actual == expected expected = { "2021-10-11": { "AAPL UN": {"assetId": ["MARCRZHY163GQ4H3"], "gsid": [14593], "bbid": ["AAPL UN"]}, "GS UN": {"assetId": ["MA4B66MW5E27UAHKG34"], "gsid": [901026], "bbid": ["GS UN"]}, }, "2021-10-12": { "AAPL UN": {"assetId": ["MARCRZHY163GQ4H3"], "gsid": [14593], "bbid": ["AAPL UN"]}, "GS UN": {"assetId": ["MA4B66MW5E27UAHKG34"], "gsid": [901026], "bbid": ["GS UN"]}, }, } targets = [SecurityIdentifier.ASSET_ID, SecurityIdentifier.GSID, SecurityIdentifier.BBID] with SecMasterContext(): actual = SecurityMaster.map_identifiers(SecurityIdentifier.BBID, ['GS UN', 'AAPL UN'], targets, start, end) assert actual == expected def test_map_identifiers_change(mocker): mock = { "results": [ { "outputType": "bbg", "outputValue": "USAT", "exchange": "UW", "compositeExchange": "US", "startDate": "2021-01-01", "endDate": "2021-04-18", "input": "104563", }, { "outputType": "bbg", "outputValue": "CTLP", "exchange": "UW", "compositeExchange": "US", "startDate": "2021-04-19", "endDate": "2021-11-01", "input": "104563", }, { "assetId": "MAY8Z19T2WE6RVHG", "outputType": "rcic", "outputValue": "USAT.O", "startDate": "2021-01-01", "endDate": "2021-04-17", "input": "104563", }, { "assetId": "MA4B66MW5E27UANLYDS", "outputType": "ric", "outputValue": "USAT.OQ", "startDate": "2021-01-01", "endDate": "2021-04-17", "input": "104563", }, { "assetId": "MA2640YQADTHYZ4M", "outputType": "rcic", "outputValue": "CTLP.O", "startDate": "2021-04-19", "endDate": "2021-11-01", "input": "104563", }, { "assetId": "MAR754Z5RQYZ3V8E", "outputType": "ric", "outputValue": "CTLP.OQ", "startDate": "2021-04-19", "endDate": "2021-11-01", "input": "104563", }, # additional RICs omitted from test { "outputType": "gsid", "outputValue": 104563, "startDate": "2021-01-01", "endDate": "2021-04-18", "input": "104563", }, { "outputType": "gsid", "outputValue": 104563, "startDate": "2021-04-19", "endDate": "2021-11-01", "input": "104563", }, { "outputType": "isin", "outputValue": "US90328S5001", "startDate": "2021-01-01", "endDate": "2021-04-18", "input": "104563", }, { "outputType": "isin", "outputValue": "US1381031061", "startDate": "2021-04-19", "endDate": "2021-11-01", "input": "104563", }, ] } mocker.patch.object(GsSession.current.sync, 'get', side_effect=[mock]) start = dt.date(2021, 1, 1) end = dt.date(2021, 11, 1) expected = { "2021-04-16": {"104563": {"ric": ["USAT.OQ"], "gsid": [104563], "isin": ["US90328S5001"], "bcid": ["USAT US"]}}, "2021-04-19": {"104563": {"ric": ["CTLP.OQ"], "gsid": [104563], "isin": ["US1381031061"], "bcid": ["CTLP US"]}}, } targets = [SecurityIdentifier.RIC, SecurityIdentifier.GSID, SecurityIdentifier.ISIN, SecurityIdentifier.BCID] with SecMasterContext(): actual = SecurityMaster.map_identifiers(SecurityIdentifier.GSID, ['104563'], targets, start, end) for k, v in expected.items(): assert actual[k] == v def test_map_identifiers_empty(mocker): mock = {"results": []} mocker.patch.object(GsSession.current.sync, 'get', side_effect=[mock]) with SecMasterContext(): actual = SecurityMaster.map_identifiers(SecurityIdentifier.BBID, ['invalid id'], [SecurityIdentifier.RIC]) assert actual == {} def test_map_identifiers_eq_index(mocker): """ Test to ensure that gsq result does not append exchange or compositeExchange to Bcid and Bbid if secmaster api does not respond any (Mainly from mapping equity indices). """ mock = { "results": [ { "outputType": "bbg", "outputValue": "SPX", "startDate": "2022-03-17", "endDate": "2022-03-17", "input": "100", } ] } mocker.patch.object(GsSession.current.sync, 'get', side_effect=[mock]) with SecMasterContext(): actual = SecurityMaster.map_identifiers( SecurityIdentifier.GSID, ['100'], [SecurityIdentifier.BBID, SecurityIdentifier.BCID] ) assert actual == {'2022-03-17': {'100': {'bbid': ['SPX']}}} def test_secmaster_map_identifiers_with_passed_input_types(mocker): start = str(dt.date(2021, 10, 11)) end = str(dt.date(2021, 10, 12)) def mock_mapping_service_response_by_input_type(*args, **kwargs): ''' Mocks Secmaster api's response json based on payload's input_type, output_type, and ids provided ''' input_type = None for enum in SecurityIdentifier: if enum.value in kwargs['payload']: input_type = enum.value break output_types = kwargs['payload']['toIdentifiers'] mock_output = {'results': []} for id in kwargs['payload'][input_type]: for output_type in output_types: row = { "outputType": output_type, "outputValue": "mock output for " + id, "startDate": start, "endDate": end, "input": id, } if output_type in (SecurityIdentifier.BBID, SecurityIdentifier.BBG, SecurityIdentifier.BCID): row['exchange'] = 'mock-exchange' row['compositeExchange'] = 'mock-comp' mock_output['results'].append(row) return mock_output mocker.patch.object(GsSession.current.sync, 'get', side_effect=mock_mapping_service_response_by_input_type) with SecMasterContext(): mock_any_ids = ["mock-any-1", "mock-any-2"] any_to_cusip_results = SecurityMaster.map_identifiers( input_type=SecurityIdentifier.ANY, ids=mock_any_ids, output_types=[SecurityIdentifier.CUSIP] ) assert start in any_to_cusip_results.keys() for input_id in mock_any_ids: assert input_id in any_to_cusip_results[start].keys() assert SecurityIdentifier.CUSIP.value in any_to_cusip_results[start][input_id].keys() assert any_to_cusip_results == { "2021-10-11": { "mock-any-1": {"cusip": ["mock output for mock-any-1"]}, "mock-any-2": {"cusip": ["mock output for mock-any-2"]}, }, "2021-10-12": { "mock-any-1": {"cusip": ["mock output for mock-any-1"]}, "mock-any-2": {"cusip": ["mock output for mock-any-2"]}, }, } mock_cusip_ids = ["mock-cusip-input1", "mock-cusip-input2"] cusip_to_isin_result = SecurityMaster.map_identifiers( input_type=SecurityIdentifier.CUSIP, ids=mock_cusip_ids, output_types=[SecurityIdentifier.ISIN] ) assert start in cusip_to_isin_result.keys() for cusip_input_id in mock_cusip_ids: assert cusip_input_id in cusip_to_isin_result[start].keys() assert SecurityIdentifier.ISIN.value in cusip_to_isin_result[start][cusip_input_id].keys() assert cusip_to_isin_result == { "2021-10-11": { "mock-cusip-input1": {"isin": ["mock output for mock-cusip-input1"]}, "mock-cusip-input2": {"isin": ["mock output for mock-cusip-input2"]}, }, "2021-10-12": { "mock-cusip-input1": {"isin": ["mock output for mock-cusip-input1"]}, "mock-cusip-input2": {"isin": ["mock output for mock-cusip-input2"]}, }, } def test_secmaster_map_identifiers_return_array_results(mocker): """ Check if map endpoint returns multi-valued response in arrays """ mock = { "results": [ { "outputType": "bbg", "outputValue": "GS", "exchange": "UN", "compositeExchange": "US", "startDate": "2022-03-21", "endDate": "2022-03-21", "input": "38141G104", }, { "outputType": "cusip", "outputValue": "38141G104", "startDate": "2022-03-21", "endDate": "2022-03-21", "input": "38141G104", }, { "outputType": "bbg", "outputValue": "GOS", "exchange": "TH", "startDate": "2022-03-21", "endDate": "2022-03-21", "input": "38141G104", }, { "outputType": "bbg", "outputValue": "GSCHF", "exchange": "EU", "startDate": "2022-03-21", "endDate": "2022-03-21", "input": "38141G104", }, { "outputType": "bbg", "outputValue": "GSUSD", "exchange": "SE", "compositeExchange": "SW", "startDate": "2022-03-21", "endDate": "2022-03-21", "input": "38141G104", }, ] } mocker.patch.object(GsSession.current.sync, 'get', side_effect=[mock]) with SecMasterContext(): actual = SecurityMaster.map_identifiers( input_type=SecurityIdentifier.CUSIP, ids=['38141G104'], output_types=[SecurityIdentifier.BBID, SecurityIdentifier.BCID, SecurityIdentifier.CUSIP], ) assert actual == { '2022-03-21': { '38141G104': { 'bbid': ['GS UN', 'GOS TH', 'GSCHF EU', 'GSUSD SE'], 'bcid': ['GS US', 'GSUSD SW'], 'cusip': ['38141G104'], } } } def test_secmaster_get_asset_no_asset_id_response_should_fail(mocker): mock_response = { "results": [ { "name": "GOLDMAN SACHS GROUP INC (US Stock Exchange Composite)", "type": "Common Stock", "currency": "USD", "tags": [], "assetClass": "Equity", "identifiers": { "gsid": 901026, "cusip": "38141G104", "cusip8": "38141G10", "sedol": "2407966", "isin": "US38141G1040", "ticker": "GS", "bcid": "GS US", "primeId": "1003232152", "factSetRegionalId": "JLJ0VZ-R", "rcic": "GS", }, "exchange": {"name": "US Stock Exchange Composite", "identifiers": {"gsExchangeId": 161}}, "id": "GSPD901026E161", } ], "totalResults": 1, } mock_no_asset_id_response = {"results": []} mocker.patch.object(GsSession.current.sync, 'get', side_effect=[mock_response, mock_no_asset_id_response]) with SecMasterContext(): asset = SecurityMaster.get_asset(id_value="GS", id_type=SecurityIdentifier.TICKER) with pytest.raises(MqValueError): asset.get_marquee_id() def test_secmaster_get_asset_returning_secmasterassets(mocker): def assert_asset_common(asset: Asset) -> None: with pytest.raises(MqTypeError): asset.get_identifier(id_type=AssetIdentifier.BLOOMBERG_ID) # get_asset() should return Stock instance when type: Common Stock mock_equity_response = { "results": [ { "name": "GOLDMAN SACHS GROUP INC (New York Stock)", "type": "Common Stock", "currency": "USD", "tags": [], "assetClass": "Equity", "identifiers": { "gsid": 901026, "cusip": "38141G104", "cusip8": "38141G10", "sedol": "2407966", "isin": "US38141G1040", "ticker": "GS", "bbid": "GS UN", "bcid": "GS US", "primeId": "1003232152", "factSetRegionalId": "JLJ0VZ-R", "rcic": "GS", "ric": "GS.N", "assetId": "MA4B66MW5E27UAHKG34", }, "exchange": {"name": "New York Stock", "identifiers": {"gsExchangeId": 154}}, "id": "GSPD901026E154", } ], "totalResults": 1, } mock_eq_id_history_response = { "results": [ { "startDate": "2007-01-01", "endDate": "9999-99-99", "value": "GS", "updateTime": "2002-02-09T17:58:27.58Z", "type": "bbg", }, { "startDate": "2007-01-01", "endDate": "9999-99-99", "value": "38141G104", "updateTime": "2002-02-09T17:54:27.99Z", "type": "cusip", }, { "startDate": "2007-01-01", "endDate": "9999-99-99", "value": "38141G10", "updateTime": "2002-02-09T17:54:27.99Z", "type": "cusip8", }, { "startDate": "2007-01-01", "endDate": "9999-99-99", "value": "JLJ0VZ-R", "updateTime": "2021-08-16T08:41:43.586Z", "type": "factSetRegionalId", }, { "startDate": "2007-01-01", "endDate": "9999-99-99", "value": "US38141G1040", "updateTime": "2002-02-09T17:55:18.513Z", "type": "isin", }, { "startDate": "2007-01-01", "endDate": "9999-99-99", "value": "1003232152", "updateTime": "2003-01-16T15:22:54.1Z", "type": "primeId", }, { "startDate": "2007-01-01", "endDate": "9999-99-99", "value": "2407966", "updateTime": "2002-02-09T17:54:47.77Z", "type": "sedol", }, { "startDate": "2007-01-01", "endDate": "9999-99-99", "value": "GS", "updateTime": "2002-02-09T17:57:14.546Z", "type": "ticker", }, { "startDate": "2007-01-01", "endDate": "9999-99-99", "type": "assetId", "value": "MA4B66MW5E27UAHKG34", "updateTime": "2002-10-30T21:30:29.993Z", }, { "startDate": "2007-01-01", "endDate": "9999-99-99", "type": "ric", "value": "GS.N", "updateTime": "2002-10-30T21:30:29.993Z", "gsExchangeId": 154, }, { "startDate": "2007-01-01", "endDate": "9999-99-99", "value": "1003232152", "updateTime": "2003-01-16T15:22:54.1Z", "type": "primeId", }, ] } mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', side_effect=[mock_equity_response, mock_eq_id_history_response]) with SecMasterContext(): stock = SecurityMaster.get_asset(id_value=901026, id_type=SecurityIdentifier.GSID) assert isinstance(stock, SecMasterAsset) assert stock.get_type() == AssetType.COMMON_STOCK assert stock.get_marquee_id() == "MA4B66MW5E27UAHKG34" assert stock.get_identifier(id_type=SecurityIdentifier.BBG) == "GS" assert stock.get_identifier(id_type=SecurityIdentifier.ID) == "GSPD901026E154" assert stock.get_identifier(id_type=SecurityIdentifier.ASSET_ID) == "MA4B66MW5E27UAHKG34" assert stock.currency == "USD" assert stock.get_identifiers() == { 'assetId': 'MA4B66MW5E27UAHKG34', 'bbg': 'GS', 'cusip': '38141G104', 'cusip8': '38141G10', 'gsid': 901026, 'id': 'GSPD901026E154', 'isin': 'US38141G1040', 'primeId': '1003232152', 'ric': 'GS.N', 'sedol': '2407966', 'ticker': 'GS', } assert_asset_common(stock) # get_asset() should return Index instance when type: Equity Index mock_index_response = { "results": [ { "name": "S&P 500 INDEX", "type": "Equity Index", "currency": "USD", "tags": [], "assetClass": "Equity", "identifiers": { "gsid": 100, "ticker": "SPX", "bbid": "SPX", "ric": ".SPX", "assetId": "MA4B66MW5E27U8P32SB", }, "company": {"name": "S&P 500 Index", "identifiers": {"gsCompanyId": 10756}}, "id": "GSPD100", } ], "totalResults": 1, } mock_index_id_history_response = { "results": [ { "startDate": "2007-01-01", "endDate": "2012-08-24", "value": "SPX", "updateTime": "2012-08-25T23:27:53.44Z", "type": "bbg", }, { "startDate": "2012-08-25", "endDate": "2012-08-25", "value": "SPX", "updateTime": "2020-12-10T21:07:06.26Z", "type": "bbg", }, { "startDate": "2012-08-26", "endDate": "9999-99-99", "value": "SPX", "updateTime": "2012-08-27T01:48:07.046Z", "type": "bbg", }, { "startDate": "2007-01-01", "endDate": "2012-08-24", "value": "SPX", "updateTime": "2012-08-25T23:27:53.4Z", "type": "ticker", }, { "startDate": "2012-08-25", "endDate": "2012-08-25", "value": "SPX", "updateTime": "2020-12-10T21:06:44.82Z", "type": "ticker", }, { "startDate": "2012-08-26", "endDate": "9999-99-99", "value": "SPX", "updateTime": "2012-08-27T01:48:07.043Z", "type": "ticker", }, { "startDate": "2007-01-01", "endDate": "9999-99-99", "type": "assetId", "value": "MA4B66MW5E27U8P32SB", "updateTime": "2003-01-14T17:28:15.29Z", }, { "startDate": "2007-01-01", "endDate": "9999-99-99", "type": "ric", "value": ".SPX", "updateTime": "2003-01-14T17:28:15.29Z", "gsExchangeId": 0, }, ] } mocker.patch.object( GsSession.current.sync, 'get', side_effect=[mock_index_response, mock_index_id_history_response] ) with SecMasterContext(): index = SecurityMaster.get_asset(id_value=100, id_type=SecurityIdentifier.GSID) assert isinstance(index, SecMasterAsset) assert index.get_type() == AssetType.EQUITY_INDEX assert index.get_marquee_id() == "MA4B66MW5E27U8P32SB" assert index.get_identifier(id_type=SecurityIdentifier.RIC) == ".SPX" assert index.get_identifier(id_type=SecurityIdentifier.ID) == "GSPD100" assert index.get_identifier(id_type=SecurityIdentifier.ASSET_ID) == "MA4B66MW5E27U8P32SB" assert index.currency == "USD" assert index.get_identifiers() == { 'assetId': 'MA4B66MW5E27U8P32SB', 'bbg': 'SPX', 'gsid': 100, 'id': 'GSPD100', 'ric': '.SPX', 'ticker': 'SPX', } assert_asset_common(index) # get_asset() should return ETF instance when type: ETF mock_ETF_response = { "results": [ { "name": "ISHARES US TRANSPORTATION ET (BATS US Trading)", "type": "ETF", "currency": "USD", "tags": [], "assetClass": "Equity", "identifiers": { "gsid": 159943, "primeId": "355769575", "bbid": "IYT UF", "bcid": "IYT US", "ticker": "IYT", "isin": "US4642871929", "sedol": "2012423", "cusip": "464287192", "cusip8": "46428719", "rcic": "IYT", "ric": "IYT.Z", "assetId": "MAZ08H8QPDQ4T7SE", }, "exchange": {"name": "BATS US Trading", "identifiers": {"gsExchangeId": 535}}, "id": "GSPD159943E535", } ], "totalResults": 1, } mock_etf_id_history_response = { "results": [ { "startDate": "2007-01-01", "endDate": "9999-99-99", "value": "IYT", "updateTime": "2003-09-16T22:00:44.586Z", "type": "bbg", }, { "startDate": "2007-01-01", "endDate": "9999-99-99", "value": "464287192", "updateTime": "2003-09-16T22:00:44.506Z", "type": "cusip", }, { "startDate": "2007-01-01", "endDate": "9999-99-99", "value": "46428719", "updateTime": "2003-09-16T22:00:44.506Z", "type": "cusip8", }, { "startDate": "2007-01-01", "endDate": "9999-99-99", "value": "US4642871929", "updateTime": "2003-09-16T22:00:44.52Z", "type": "isin", }, { "startDate": "2007-01-01", "endDate": "9999-99-99", "value": "355769575", "updateTime": "2003-10-02T05:12:03.51Z", "type": "primeId", }, { "startDate": "2007-01-01", "endDate": "9999-99-99", "value": "2012423", "updateTime": "2003-10-10T23:49:00.12Z", "type": "sedol", }, { "startDate": "2007-01-01", "endDate": "9999-99-99", "value": "IYT", "updateTime": "2003-09-16T22:00:44.56Z", "type": "ticker", }, { "startDate": "2008-11-09", "endDate": "2017-08-01", "type": "assetId", "value": "MAZ08H8QPDQ4T7SE", "updateTime": "2017-08-02T05:36:56.823Z", }, { "startDate": "2017-08-03", "endDate": "9999-99-99", "type": "assetId", "value": "MAZ08H8QPDQ4T7SE", "updateTime": "2017-08-02T16:09:56.146Z", }, { "startDate": "2008-11-09", "endDate": "2017-08-01", "type": "ric", "value": "IYT.Z", "updateTime": "2017-08-02T05:36:56.823Z", "gsExchangeId": 535, }, { "startDate": "2017-08-03", "endDate": "9999-99-99", "type": "ric", "value": "IYT.Z", "updateTime": "2017-08-02T16:09:56.146Z", "gsExchangeId": 535, }, ] } mocker.patch.object(GsSession.current.sync, 'get', side_effect=[mock_ETF_response, mock_etf_id_history_response]) with SecMasterContext(): etf = SecurityMaster.get_asset(id_value=159943, id_type=SecurityIdentifier.GSID) assert isinstance(etf, SecMasterAsset) assert etf.get_type() == AssetType.ETF assert etf.get_marquee_id() == "MAZ08H8QPDQ4T7SE" assert etf.get_identifier(id_type=SecurityIdentifier.RIC) == "IYT.Z" assert etf.get_identifier(id_type=SecurityIdentifier.ID) == "GSPD159943E535" assert etf.get_identifier(id_type=SecurityIdentifier.ASSET_ID) == "MAZ08H8QPDQ4T7SE" assert etf.currency == "USD" assert etf.get_identifiers() == { "gsid": 159943, "primeId": "355769575", "bbg": "IYT", "ticker": "IYT", "isin": "US4642871929", "sedol": "2012423", "cusip": "464287192", "cusip8": "46428719", "ric": "IYT.Z", "assetId": "MAZ08H8QPDQ4T7SE", 'id': 'GSPD159943E535', } assert_asset_common(etf) # get_asset() should return Currency instance when type: Currency mock_currency_response = { "results": [ { "name": "USD U.S. DOLLAR", "type": "Currency", "currency": "USD", "tags": [], "assetClass": "Cash", "identifiers": {"gsid": 4007, "assetId": "MAZ7RWC904JYHYPS", "ticker": "USD"}, "id": "GSPD4007", } ], "totalResults": 1, } mock_currency_id_history_response = { "results": [ { "startDate": "2007-01-01", "endDate": "9999-99-99", "value": "USD", "updateTime": "2003-05-01T16:20:44.47Z", "type": "ticker", } ] } mocker.patch.object( GsSession.current.sync, 'get', side_effect=[mock_currency_response, mock_currency_id_history_response] ) with SecMasterContext(): currency = SecurityMaster.get_asset(id_value=4007, id_type=SecurityIdentifier.GSID) assert isinstance(currency, SecMasterAsset) assert currency.get_type() == AssetType.CURRENCY assert currency.get_marquee_id() == "MAZ7RWC904JYHYPS" assert currency.get_identifier(id_type=SecurityIdentifier.TICKER) == "USD" assert currency.get_identifier(id_type=SecurityIdentifier.ID) == "GSPD4007" assert currency.get_identifier(id_type=SecurityIdentifier.ASSET_ID) == "MAZ7RWC904JYHYPS" assert currency.get_identifiers() == { "gsid": 4007, "assetId": "MAZ7RWC904JYHYPS", "ticker": "USD", "id": "GSPD4007", } assert_asset_common(currency) def test_get_asset_get_data_series_with_range_over_many_asset_id_should_throw_mqerror(mocker): mock_asset = { "results": [ { "name": "ISHARES US TRANSPORTATION ET (BATS US Trading)", "type": "ETF", "currency": "USD", "tags": [], "assetClass": "Equity", "identifiers": {"assetId": "MAZ08H8QPDQ4T7SE"}, "exchange": {"name": "BATS US Trading", "identifiers": {"gsExchangeId": 535}}, "id": "GSPD159943E535", } ], "totalResults": 1, } mock_id_history_response = { "results": [ { "startDate": "2020-01-01", "endDate": "9999-99-99", "value": "marqueid 1", "updateTime": "2003-05-01T16:20:44.47Z", "type": "assetId", }, { "startDate": "2007-12-30", "endDate": "2019-12-31", "value": "marqueid 2", "updateTime": "2003-05-01T16:20:44.47Z", "type": "assetId", }, ] } mocker.patch.object(GsSession.current.sync, 'get', side_effect=[mock_asset, mock_id_history_response]) with SecMasterContext(): asset = SecurityMaster.get_asset(id_value=4007, id_type=SecurityIdentifier.GSID) with pytest.raises(MqValueError): asset.get_hloc_prices(start=dt.date(2007, 1, 1), end=dt.date(2022, 1, 1)) def test_map_identifiers_asset_service(mocker): response = {'AAPL UN': ['AAPL.N'], 'GS UN': ['GS.N']} mocker.patch.object(GsAssetApi, 'map_identifiers', side_effect=lambda *arg, **kwargs: response) expected = {"2021-10-11": {"AAPL UN": {"ric": ["AAPL.N"]}, "GS UN": {"ric": ["GS.N"]}}} with AssetContext(): actual = SecurityMaster.map_identifiers( SecurityIdentifier.BBID, ['GS UN', 'AAPL UN'], [SecurityIdentifier.RIC], as_of_date=dt.date(2021, 10, 11) ) assert actual == expected date_string = dt.date.today().strftime('%Y-%m-%d') expected2 = {date_string: expected["2021-10-11"]} with AssetContext(): actual2 = SecurityMaster.map_identifiers( SecurityIdentifier.BBID, ['GS UN', 'AAPL UN'], [SecurityIdentifier.RIC] ) assert actual2 == expected2 mocker.patch.object(GsAssetApi, 'map_identifiers', side_effect=lambda *arg, **kwargs: {}) with AssetContext(): actual = SecurityMaster.map_identifiers( SecurityIdentifier.BBID, ['invalid id'], [SecurityIdentifier.RIC], as_of_date=dt.date(2021, 10, 11) ) assert actual == {} def test_map_identifiers_asset_service_exceptions(): with pytest.raises(MqValueError): # multiple output types with AssetContext(): SecurityMaster.map_identifiers( SecurityIdentifier.BBID, ['GS UN', 'AAPL UN'], [SecurityIdentifier.RIC, SecurityIdentifier.GSID], as_of_date=dt.date(2021, 10, 11), ) with pytest.raises(MqValueError): # start date with AssetContext(): SecurityMaster.map_identifiers( SecurityIdentifier.BBID, ['GS UN', 'AAPL UN'], [SecurityIdentifier.RIC], start_date=dt.date(2021, 10, 11), ) with pytest.raises(MqValueError): # end date with AssetContext(): SecurityMaster.map_identifiers( SecurityIdentifier.BBID, ['GS UN', 'AAPL UN'], [SecurityIdentifier.RIC], end_date=dt.date(2021, 10, 11) ) with pytest.raises(MqValueError): # unsupported output type with AssetContext(): SecurityMaster.map_identifiers(SecurityIdentifier.BBID, ['GS UN', 'AAPL UN'], [SecurityIdentifier.BBG]) if __name__ == "__main__": pytest.main([__file__]) ================================================ FILE: gs_quant/test/mock_data_test_utils.py ================================================ """ Copyright 2024 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import pytest FAILED_TESTS = [] RUN_TESTS = [] LOGGED_EVENTS = [] def did_anything_fail(): return len(FAILED_TESTS) > 0 def did_anything_run(): return len(RUN_TESTS) > 0 def log_mock_data_event(event: str): LOGGED_EVENTS.append(event) def pytest_addoption(parser): parser.addoption('--fixmockdata', action='store_true') def pytest_configure(config): config.addinivalue_line("markers", "fixmockdata: marker to update mock file data") @pytest.hookimpl(tryfirst=True) def pytest_runtest_makereport(item, call) -> None: """Called to make report for the test for test item (the call phase). :param item: The item. """ if call.when == 'call': RUN_TESTS.append(item.nodeid) if call.excinfo: FAILED_TESTS.append((item.nodeid, item)) def pytest_terminal_summary(terminalreporter, exitstatus, config): terminalreporter.ensure_newline() terminalreporter.section("Mock File Summary") if not len(LOGGED_EVENTS): terminalreporter.write_line("Nothing Changed") for event in LOGGED_EVENTS: terminalreporter.write_line(event) def pytest_collection_modifyitems(items): LOGGED_EVENTS.clear() FAILED_TESTS.clear() for item in items: # Skip the integration tests unless explicitly asked for run_fix_mock_data = item.config.getoption("fixmockdata") # run with pytest . --fixmockdata if any(marker.name == 'fixmockdata' for marker in item.own_markers) and not run_fix_mock_data: item.add_marker(pytest.mark.skip(reason='Skipping Mock Data updating')) ================================================ FILE: gs_quant/test/models/__init__.py ================================================ """ Copyright 2020 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ ================================================ FILE: gs_quant/test/models/test_epidemiology.py ================================================ """ Copyright 2020 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import numpy as np from gs_quant.models.epidemiology import SIR, EpidemicModel, SEIR def test_SIR(): # Initialize a model beta_0 = 0.1 gamma_0 = 0.2 parameters, initial_conditions = SIR.get_parameters( 99, 1, 0, 100, beta=beta_0, gamma=gamma_0, S0_fixed=True, I0_fixed=False, R0_fixed=True, S0_max=10e6, I0_max=5e6, R0_max=10e6, ) sir = EpidemicModel(SIR, parameters=parameters, initial_conditions=initial_conditions) # Solve a problem with an independently checkable solution N = 100 S0 = 99 I0 = 1 R0 = 0 beta = 0.5 gamma = 0.25 days_ahead = 40 # days ahead to look at T = np.arange(days_ahead) forecast = sir.solve(T, (S0, I0, R0), (beta, gamma, N)) # get the curve data as if it were our data S_data = forecast[:, 0] I_data = forecast[:, 1] R_data = forecast[:, 2] data = np.array([S_data, I_data, R_data]).T # slightly perturbed parameters, to check if the fitting finds the original (optimal) parameters parameters, initial_conditions = SIR.get_parameters( S_data[0], I_data[0], R_data[0], N, beta=beta + 0.4, gamma=gamma + 0.2, S0_fixed=True, I0_fixed=True, R0_fixed=True, S0_max=10e6, I0_max=5e6, R0_max=10e6, ) sir = EpidemicModel(SIR, parameters=parameters, data=data, initial_conditions=initial_conditions) sir.fit() # check fitted parameters beta_fitted = sir.fitted_parameters['beta'] gamma_fitted = sir.fitted_parameters['gamma'] assert np.isclose(beta, beta_fitted) assert np.isclose(gamma, gamma_fitted) sir = EpidemicModel(SIR, parameters=parameters, data=data, initial_conditions=initial_conditions, fit_period=10) sir.fit() # check fitted parameters beta_fitted = sir.fitted_parameters['beta'] gamma_fitted = sir.fitted_parameters['gamma'] assert np.isclose(beta, beta_fitted) assert np.isclose(gamma, gamma_fitted) def test_SEIR(): # Initialize a model beta_0 = 0.1 gamma_0 = 0.2 sigma_0 = 0.1 parameters, initial_conditions = SEIR.get_parameters( 99, 1, 1, 0, 100, beta=beta_0, gamma=gamma_0, sigma=sigma_0, S0_fixed=True, E0_fixed=True, I0_fixed=False, R0_fixed=True, S0_max=10e6, I0_max=5e6, R0_max=10e6, ) sir = EpidemicModel(SEIR, parameters=parameters, initial_conditions=initial_conditions) # Solve a problem with an independently checkable solution N = 100 S0 = 99 E0 = 1 I0 = 1 R0 = 0 beta = 0.5 gamma = 0.25 sigma = 0.2 days_ahead = 40 # days ahead to look at T = np.arange(days_ahead) forecast = sir.solve(T, (S0, E0, I0, R0), (beta, gamma, sigma, N)) # get the curve data as if it were our data S_data = forecast[:, 0] E_data = forecast[:, 1] I_data = forecast[:, 2] R_data = forecast[:, 3] data = np.array([S_data, E_data, I_data, R_data]).T # slightly perturbed parameters, to check if the fitting finds the original (optimal) parameters parameters, initial_conditions = SEIR.get_parameters( S_data[0], E_data[0], I_data[0], R_data[0], N, beta=beta + 0.4, gamma=gamma + 0.2, sigma=sigma + 0.1, S0_fixed=True, E0_fixed=True, I0_fixed=True, R0_fixed=True, S0_max=10e6, I0_max=5e6, R0_max=10e6, ) seir = EpidemicModel(SEIR, parameters=parameters, data=data, initial_conditions=initial_conditions) seir.fit() # check fitted parameters beta_fitted = seir.fitted_parameters['beta'] gamma_fitted = seir.fitted_parameters['gamma'] sigma_fitted = seir.fitted_parameters['sigma'] assert np.isclose(beta, beta_fitted) assert np.isclose(gamma, gamma_fitted) assert np.isclose(sigma, sigma_fitted) seir = EpidemicModel(SEIR, parameters=parameters, data=data, initial_conditions=initial_conditions, fit_period=10) seir.fit() # check fitted parameters beta_fitted = seir.fitted_parameters['beta'] gamma_fitted = seir.fitted_parameters['gamma'] sigma_fitted = seir.fitted_parameters['sigma'] assert np.isclose(beta, beta_fitted) assert np.isclose(gamma, gamma_fitted) assert np.isclose(sigma, sigma_fitted) ================================================ FILE: gs_quant/test/models/test_risk_model.py ================================================ """ Copyright 2021 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from unittest import mock import pytest from pandas._testing import assert_frame_equal from gs_quant.common import Currency from gs_quant.models.risk_model import FactorRiskModel, MacroRiskModel, ReturnFormat, Unit from gs_quant.models.risk_model_utils import get_optional_data_as_dataframe, _map_measure_to_field_name from gs_quant.session import GsSession, Environment from gs_quant.target.risk_models import ( RiskModel as Risk_Model, RiskModelCoverage, RiskModelTerm, RiskModelUniverseIdentifier, RiskModelType, RiskModelDataAssetsRequest as DataAssetsRequest, RiskModelDataMeasure as Measure, RiskModelUniverseIdentifierRequest as UniverseIdentifier, ) empty_entitlements = {"execute": [], "edit": [], "view": [], "admin": [], "query": [], "upload": []} mock_risk_model_obj = Risk_Model( RiskModelCoverage.Country, 'model_id', 'Fake Risk Model', RiskModelTerm.Long, RiskModelUniverseIdentifier.gsid, 'GS', 1.0, universe_size=10000, entitlements=empty_entitlements, description='Test', expected_update_time='00:00:00', type=RiskModelType.Factor, ) mock_macro_risk_model_obj = Risk_Model( coverage=RiskModelCoverage.Country, id='macro_model_id', name='Fake Risk Model', term=RiskModelTerm.Long, universe_identifier=RiskModelUniverseIdentifier.gsid, vendor='GS', version=1.0, entitlements=empty_entitlements, description='Test', expected_update_time='00:00:00', type=RiskModelType.Macro, ) def mock_risk_model(mocker): from gs_quant.session import OAuth2Session OAuth2Session.init = mock.MagicMock(return_value=None) GsSession.use(Environment.QA, 'client_id', 'secret') mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'post', return_value=mock_risk_model_obj) mocker.patch.object(GsSession.current.sync, 'get', return_value=mock_risk_model_obj) mocker.patch.object(GsSession.current.sync, 'put', return_value=mock_risk_model_obj) return FactorRiskModel.get('model_id') def mock_macro_risk_model(mocker): from gs_quant.session import OAuth2Session OAuth2Session.init = mock.MagicMock(return_value=None) GsSession.use(Environment.QA, 'client_id', 'secret') mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'post', return_value=mock_macro_risk_model_obj) mocker.patch.object(GsSession.current.sync, 'get', return_value=mock_macro_risk_model_obj) mocker.patch.object(GsSession.current.sync, 'put', return_value=mock_macro_risk_model_obj) return MacroRiskModel.get('macro_model_id') def test_create_risk_model(mocker): mock_risk_model(mocker) risk_model_id = 'model_id' mocker.patch.object(GsSession.current.sync, 'post', return_value=mock_risk_model_obj) new_model = FactorRiskModel( risk_model_id, 'Fake Risk Model', RiskModelCoverage.Country, RiskModelTerm.Long, RiskModelUniverseIdentifier.gsid, 'GS', 0.1, universe_size=10000, entitlements={}, description='Test', expected_update_time=dt.datetime.strptime('00:00:00', '%H:%M:%S').time(), ) new_model.save() assert new_model.id == mock_risk_model_obj.id assert new_model.name == mock_risk_model_obj.name assert new_model.description == mock_risk_model_obj.description assert new_model.term == mock_risk_model_obj.term assert new_model.universe_size == mock_risk_model_obj.universe_size assert new_model.coverage == mock_risk_model_obj.coverage assert new_model.universe_identifier == mock_risk_model_obj.universe_identifier assert ( new_model.expected_update_time == dt.datetime.strptime(mock_risk_model_obj.expected_update_time, '%H:%M:%S').time() ) def test_update_risk_model_entitlements(mocker): new_model = mock_risk_model(mocker) new_entitlements = {"execute": ['guid:X'], "edit": [], "view": [], "admin": [], "query": [], "upload": []} new_model.entitlements = new_entitlements new_model.save() assert 'guid:X' in new_model.entitlements.get('execute') mocker.patch.object(GsSession.current.sync, 'get', return_value=new_model) new_model.entitlements = empty_entitlements new_model.save() new_entitlements = { "execute": ['guid:X'], "edit": [], "view": [], "admin": ['guid:XX'], "query": [], "upload": ['guid:XXX'], } new_model.entitlements = new_entitlements new_model.save() mocker.patch.object(GsSession.current.sync, 'get', return_value=new_model) assert 'guid:X' in new_model.entitlements.get('execute') assert 'guid:XX' in new_model.entitlements.get('admin') assert 'guid:XXX' in new_model.entitlements.get('upload') def test_update_risk_model(mocker): new_model = mock_risk_model(mocker) new_model.term = RiskModelTerm.Short new_model.save() mocker.patch.object(GsSession.current.sync, 'get', return_value=new_model) assert new_model.term == RiskModelTerm.Short new_model.description = 'Test risk model' new_model.save() mocker.patch.object(GsSession.current.sync, 'get', return_value=new_model) assert new_model.description == 'Test risk model' new_model.vendor = 'GS' new_model.save() mocker.patch.object(GsSession.current.sync, 'get', return_value=new_model) assert new_model.vendor == 'GS' new_model.term = RiskModelTerm.Medium new_model.save() mocker.patch.object(GsSession.current.sync, 'get', return_value=new_model) assert new_model.term == RiskModelTerm.Medium new_model.version = 0.1 new_model.save() mocker.patch.object(GsSession.current.sync, 'get', return_value=new_model) assert new_model.version == 0.1 new_model.universe_size = 10000 new_model.save() mocker.patch.object(GsSession.current.sync, 'get', return_value=new_model) assert new_model.universe_size == 10000 new_model.coverage = RiskModelCoverage.Global new_model.save() mocker.patch.object(GsSession.current.sync, 'get', return_value=new_model) assert new_model.coverage == RiskModelCoverage.Global new_model.name = 'TEST RISK MODEL' new_model.save() mocker.patch.object(GsSession.current.sync, 'get', return_value=new_model) assert new_model.name == 'TEST RISK MODEL' new_model.expected_update_time = dt.time(1, 0, 0) new_model.save() mocker.patch.object(GsSession.current.sync, 'get', return_value=new_model) assert new_model.expected_update_time == dt.time(1, 0, 0) new_model.type = RiskModelType.Thematic new_model.save() mocker.patch.object(GsSession.current.sync, 'get', return_value=new_model) assert new_model.type == RiskModelType.Thematic def test_get_r_squared(mocker): macro_model = mock_macro_risk_model(mocker) universe = ["904026", "232128", "24985", "160444"] query = { 'startDate': '2022-04-04', 'endDate': '2022-04-06', 'assets': DataAssetsRequest(UniverseIdentifier.gsid, universe), 'measures': [Measure.R_Squared, Measure.Asset_Universe], 'limitFactors': False, } results = { 'missingDates': ['2022-04-04', '2022-04-06'], 'results': [ { "date": "2022-04-05", "assetData": {"universe": ["904026", "232128", "24985", "160444"], "rSquared": [89.0, 45.0, 12.0, 5.0]}, } ], 'totalResults': 1, } r_squared_response = { '160444': {'2022-04-05': 5.0}, '232128': {'2022-04-05': 45.0}, '24985': {'2022-04-05': 12.0}, '904026': {'2022-04-05': 89.0}, } mocker.patch.object(GsSession.current.sync, 'post', return_value=results) # run test response = macro_model.get_r_squared( start_date=dt.date(2022, 4, 4), end_date=dt.date(2022, 4, 6), assets=DataAssetsRequest(UniverseIdentifier.gsid, universe), format=ReturnFormat.JSON, ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='macro_model_id'), query, timeout=200 ) assert response == r_squared_response def test_get_fair_value_gap_standard_deviation(mocker): macro_model = mock_macro_risk_model(mocker) universe = ["904026", "232128", "24985", "160444"] query = { 'startDate': '2022-04-04', 'endDate': '2022-04-06', 'assets': DataAssetsRequest(UniverseIdentifier.gsid, universe), 'measures': [Measure.Fair_Value_Gap_Standard_Deviation, Measure.Asset_Universe], 'limitFactors': False, } results = { 'missingDates': ['2022-04-04', '2022-04-06'], 'results': [ { "date": "2022-04-05", "assetData": { "universe": ["904026", "232128", "24985", "160444"], "fairValueGapStandardDeviation": [4.0, 5.0, 1.0, 7.0], }, } ], 'totalResults': 1, } fvg_response = { '160444': {'2022-04-05': 7.0}, '232128': {'2022-04-05': 5.0}, '24985': {'2022-04-05': 1.0}, '904026': {'2022-04-05': 4.0}, } mocker.patch.object(GsSession.current.sync, 'post', return_value=results) response = macro_model.get_fair_value_gap( start_date=dt.date(2022, 4, 4), end_date=dt.date(2022, 4, 6), assets=DataAssetsRequest(UniverseIdentifier.gsid, universe), format=ReturnFormat.JSON, ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='macro_model_id'), query, timeout=200 ) assert response == fvg_response def test_get_fair_value_gap_percent(mocker): macro_model = mock_macro_risk_model(mocker) universe = ["904026", "232128", "24985", "160444"] query = { 'startDate': '2022-04-04', 'endDate': '2022-04-06', 'assets': DataAssetsRequest(UniverseIdentifier.gsid, universe), 'measures': [Measure.Fair_Value_Gap_Percent, Measure.Asset_Universe], 'limitFactors': False, } results = { 'missingDates': ['2022-04-04', '2022-04-06'], 'results': [ { "date": "2022-04-05", "assetData": { "universe": ["904026", "232128", "24985", "160444"], "fairValueGapPercent": [90.0, 34.0, 8.0, 34.0], }, } ], 'totalResults': 1, } fvg_response = { '160444': {'2022-04-05': 34.0}, '232128': {'2022-04-05': 34.0}, '24985': {'2022-04-05': 8.0}, '904026': {'2022-04-05': 90.0}, } mocker.patch.object(GsSession.current.sync, 'post', return_value=results) response = macro_model.get_fair_value_gap( start_date=dt.date(2022, 4, 4), end_date=dt.date(2022, 4, 6), assets=DataAssetsRequest(UniverseIdentifier.gsid, universe), fair_value_gap_unit=Unit.PERCENT, format=ReturnFormat.JSON, ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='macro_model_id'), query, timeout=200 ) assert response == fvg_response @pytest.mark.parametrize( "statistical_measure, fieldKey", [ (Measure.Factor_Mean, _map_measure_to_field_name(Measure.Factor_Mean)), (Measure.Factor_Cross_Sectional_Mean, _map_measure_to_field_name(Measure.Factor_Cross_Sectional_Mean)), (Measure.Factor_Standard_Deviation, _map_measure_to_field_name(Measure.Factor_Standard_Deviation)), ( Measure.Factor_Cross_Sectional_Standard_Deviation, _map_measure_to_field_name(Measure.Factor_Cross_Sectional_Standard_Deviation), ), ], ) def test_get_statistical_factor_data(mocker, statistical_measure, fieldKey): risk_model = mock_risk_model(mocker) universe = ["904026", "232128", "24985", "160444"] query = { 'startDate': '2022-04-04', 'endDate': '2022-04-06', 'assets': DataAssetsRequest(UniverseIdentifier.gsid, universe), 'measures': [ statistical_measure, Measure.Factor_Name, Measure.Factor_Id, Measure.Universe_Factor_Exposure, Measure.Asset_Universe, ], 'limitFactors': True, } results = { 'missingDates': ['2022-04-04', '2022-04-06'], 'results': [ { "date": "2022-04-05", "factorData": [ {"factorId": "1", f"{fieldKey}": 89.0, "factorName": "Factor1"}, {"factorId": "2", f"{fieldKey}": 0.67, "factorName": "Factor2"}, ], 'assetData': { 'factorExposure': [ {'1': 0.2, '2': 0.3}, {'1': 0.02, '2': 0.03}, {'1': 6.2, '2': 3.0}, {'1': -6.2, '2': 0.3}, ], 'universe': ['904026', '232128', '24985', '160444'], }, } ], 'totalResults': 1, } expected_response = {'Factor1': {'2022-04-05': 89.0}, 'Factor2': {'2022-04-05': 0.67}} kwargs = { "start_date": dt.date(2022, 4, 4), "end_date": dt.date(2022, 4, 6), "assets": DataAssetsRequest(UniverseIdentifier.gsid, universe), "format": ReturnFormat.JSON, } field_key_to_getter_ref = { _map_measure_to_field_name(Measure.Factor_Mean): risk_model.get_factor_mean, _map_measure_to_field_name(Measure.Factor_Cross_Sectional_Mean): risk_model.get_factor_cross_sectional_mean, _map_measure_to_field_name(Measure.Factor_Standard_Deviation): risk_model.get_factor_standard_deviation, _map_measure_to_field_name( Measure.Factor_Cross_Sectional_Standard_Deviation ): risk_model.get_factor_cross_sectional_standard_deviation, } mocker.patch.object(GsSession.current.sync, 'post', return_value=results) actual_response = field_key_to_getter_ref[fieldKey](**kwargs) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='model_id'), query, timeout=200 ) assert actual_response == expected_response def test_get_factor_z_score(mocker): macro_model = mock_macro_risk_model(mocker) universe = ["904026", "232128", "24985", "160444"] query = { 'startDate': '2022-04-04', 'endDate': '2022-04-06', 'assets': DataAssetsRequest(UniverseIdentifier.gsid, universe), 'measures': [ Measure.Factor_Z_Score, Measure.Factor_Name, Measure.Factor_Id, Measure.Universe_Factor_Exposure, Measure.Asset_Universe, ], 'limitFactors': True, } results = { 'missingDates': ['2022-04-04', '2022-04-06'], 'results': [ { "date": "2022-04-05", "factorData": [ {"factorId": "1", "factorZScore": 1.5, "factorName": "Factor1"}, {"factorId": "2", "factorZScore": -1.0, "factorName": "Factor2"}, ], 'assetData': { 'factorExposure': [ {'1': 0.2, '2': 0.3}, {'1': 0.02, '2': 0.03}, {'1': 6.2, '2': 3.0}, {'1': -6.2, '2': 0.3}, ], 'universe': ['904026', '232128', '24985', '160444'], }, } ], 'totalResults': 1, } factor_z_score_response = {'Factor1': {'2022-04-05': 1.5}, 'Factor2': {'2022-04-05': -1.0}} mocker.patch.object(GsSession.current.sync, 'post', return_value=results) # run test response = macro_model.get_factor_z_score( start_date=dt.date(2022, 4, 4), end_date=dt.date(2022, 4, 6), assets=DataAssetsRequest(UniverseIdentifier.gsid, universe), format=ReturnFormat.JSON, ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='macro_model_id'), query, timeout=200 ) assert response == factor_z_score_response def test_get_predicted_beta(mocker): model = mock_risk_model(mocker) universe = ["904026", "232128", "24985", "160444"] query = { 'startDate': '2022-04-04', 'endDate': '2022-04-06', 'assets': DataAssetsRequest(UniverseIdentifier.gsid, universe), 'measures': [Measure.Predicted_Beta, Measure.Asset_Universe], 'limitFactors': False, } results = { 'missingDates': ['2022-04-04', '2022-04-06'], 'results': [ { "date": "2022-04-05", "assetData": { "universe": ["904026", "232128", "24985", "160444"], "predictedBeta": [0.4, 1.5, 1.2, 0.5], }, } ], 'totalResults': 1, } predicted_beta_response = { '160444': {'2022-04-05': 0.5}, '232128': {'2022-04-05': 1.5}, '24985': {'2022-04-05': 1.2}, '904026': {'2022-04-05': 0.4}, } mocker.patch.object(GsSession.current.sync, 'post', return_value=results) # run test response = model.get_predicted_beta( start_date=dt.date(2022, 4, 4), end_date=dt.date(2022, 4, 6), assets=DataAssetsRequest(UniverseIdentifier.gsid, universe), format=ReturnFormat.JSON, ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='model_id'), query, timeout=200 ) assert response == predicted_beta_response def test_get_global_predicted_beta(mocker): model = mock_risk_model(mocker) universe = ["904026", "232128", "24985", "160444"] query = { 'startDate': '2022-04-04', 'endDate': '2022-04-06', 'assets': DataAssetsRequest(UniverseIdentifier.gsid, universe), 'measures': [Measure.Global_Predicted_Beta, Measure.Asset_Universe], 'limitFactors': False, } results = { 'missingDates': ['2022-04-04', '2022-04-06'], 'results': [ { "date": "2022-04-05", "assetData": { "universe": ["904026", "232128", "24985", "160444"], "globalPredictedBeta": [0.4, 1.5, 1.2, 0.5], }, } ], 'totalResults': 1, } global_predicted_beta_response = { '160444': {'2022-04-05': 0.5}, '232128': {'2022-04-05': 1.5}, '24985': {'2022-04-05': 1.2}, '904026': {'2022-04-05': 0.4}, } mocker.patch.object(GsSession.current.sync, 'post', return_value=results) # run test response = model.get_global_predicted_beta( start_date=dt.date(2022, 4, 4), end_date=dt.date(2022, 4, 6), assets=DataAssetsRequest(UniverseIdentifier.gsid, universe), format=ReturnFormat.JSON, ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='model_id'), query, timeout=200 ) assert response == global_predicted_beta_response def test_get_estimation_universe_weights(mocker): model = mock_risk_model(mocker) universe = ["904026", "232128", "24985", "160444"] query = { 'startDate': '2022-04-04', 'endDate': '2022-04-06', 'assets': DataAssetsRequest(UniverseIdentifier.gsid, universe), 'measures': [Measure.Estimation_Universe_Weight, Measure.Asset_Universe], 'limitFactors': False, } results = { 'missingDates': ['2022-04-04', '2022-04-06'], 'results': [ { "date": "2022-04-05", "assetData": { "universe": ["904026", "232128", "24985", "160444"], "estimationUniverseWeight": [0.4, None, None, 0.6], }, } ], 'totalResults': 1, } estu_response = { '160444': {'2022-04-05': 0.6}, '232128': {'2022-04-05': None}, '24985': {'2022-04-05': None}, '904026': {'2022-04-05': 0.4}, } mocker.patch.object(GsSession.current.sync, 'post', return_value=results) # run test response = model.get_estimation_universe_weights( start_date=dt.date(2022, 4, 4), end_date=dt.date(2022, 4, 6), assets=DataAssetsRequest(UniverseIdentifier.gsid, universe), format=ReturnFormat.JSON, ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='model_id'), query, timeout=200 ) assert response == estu_response def test_get_daily_return(mocker): model = mock_risk_model(mocker) universe = ["904026", "232128", "24985", "160444"] query = { 'startDate': '2022-04-04', 'endDate': '2022-04-06', 'assets': DataAssetsRequest(UniverseIdentifier.gsid, universe), 'measures': [Measure.Daily_Return, Measure.Asset_Universe], 'limitFactors': False, } results = { 'missingDates': ['2022-04-04', '2022-04-06'], 'results': [ { "date": "2022-04-05", "assetData": { "universe": ["904026", "232128", "24985", "160444"], "dailyReturn": [-0.4, -1.5, 1.2, 0.5], }, } ], 'totalResults': 1, } daily_return_response = { '160444': {'2022-04-05': 0.5}, '232128': {'2022-04-05': -1.5}, '24985': {'2022-04-05': 1.2}, '904026': {'2022-04-05': -0.4}, } mocker.patch.object(GsSession.current.sync, 'post', return_value=results) # run test response = model.get_daily_return( start_date=dt.date(2022, 4, 4), end_date=dt.date(2022, 4, 6), assets=DataAssetsRequest(UniverseIdentifier.gsid, universe), format=ReturnFormat.JSON, ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='model_id'), query, timeout=200 ) assert response == daily_return_response def test_get_specific_return(mocker): model = mock_risk_model(mocker) universe = ["904026", "232128", "24985", "160444"] query = { 'startDate': '2022-04-04', 'endDate': '2022-04-06', 'assets': DataAssetsRequest(UniverseIdentifier.gsid, universe), 'measures': [Measure.Specific_Return, Measure.Asset_Universe], 'limitFactors': False, } results = { 'missingDates': ['2022-04-04', '2022-04-06'], 'results': [ { "date": "2022-04-05", "assetData": { "universe": ["904026", "232128", "24985", "160444"], "specificReturn": [0.5, 1.6, 1.4, 0.7], }, } ], 'totalResults': 1, } specific_return_response = { '160444': {'2022-04-05': 0.7}, '232128': {'2022-04-05': 1.6}, '24985': {'2022-04-05': 1.4}, '904026': {'2022-04-05': 0.5}, } mocker.patch.object(GsSession.current.sync, 'post', return_value=results) # run test response = model.get_specific_return( start_date=dt.date(2022, 4, 4), end_date=dt.date(2022, 4, 6), assets=DataAssetsRequest(UniverseIdentifier.gsid, universe), format=ReturnFormat.JSON, ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='model_id'), query, timeout=200 ) assert response == specific_return_response @pytest.mark.parametrize("aws_upload", [True, False]) def test_upload_risk_model_data(mocker, aws_upload): model = mock_risk_model(mocker) risk_model_data = { 'date': '2023-04-14', 'assetData': { 'universe': ['2407966', '2046251', 'USD'], 'specificRisk': [12.09, 45.12, 3.09], 'factorExposure': [{'1': 0.23, '2': 0.023}, {'1': 0.23}, {'3': 0.23, '2': 0.023}], 'totalRisk': [0.12, 0.45, 1.2], }, 'factorData': [ {'factorId': '1', 'factorName': 'USD', 'factorCategory': 'Currency', 'factorCategoryId': 'CUR'}, {'factorId': '2', 'factorName': 'ST', 'factorCategory': 'ST', 'factorCategoryId': 'ST'}, {'factorId': '3', 'factorName': 'IND', 'factorCategory': 'IND', 'factorCategoryId': 'IND'}, ], 'covarianceMatrix': [[0.089, 0.0123, 0.345], [0.0123, 3.45, 0.345], [0.345, 0.345, 1.23]], 'issuerSpecificCovariance': {'universeId1': ['2407966'], 'universeId2': ['2046251'], 'covariance': [0.03754]}, 'factorPortfolios': { 'universe': ['2407966', '2046251'], 'portfolio': [ {'factorId': 1, 'weights': [0.25, 0.75]}, {'factorId': 2, 'weights': [0.25, 0.75]}, {'factorId': 3, 'weights': [0.25, 0.75]}, ], }, } base_url = f"/risk/models/data/{model.id}?partialUpload=true" date = risk_model_data.get("date") max_asset_batch_size = 2 batched_asset_data = [ { "assetData": { key: value[i : i + max_asset_batch_size] for key, value in risk_model_data.get("assetData").items() }, "date": date, } for i in range(0, len(risk_model_data.get("assetData").get("universe")), max_asset_batch_size) ] max_asset_batch_size //= 2 batched_factor_portfolios = [ { "factorPortfolios": { key: ( value[i : i + max_asset_batch_size] if key in "universe" else [ { "factorId": factor_weights.get("factorId"), "weights": factor_weights.get("weights")[i : i + max_asset_batch_size], } for factor_weights in value ] ) for key, value in risk_model_data.get("factorPortfolios").items() }, "date": date, } for i in range(0, len(risk_model_data.get("factorPortfolios").get("universe")), max_asset_batch_size) ] expected_factor_data_calls = [ mock.call( f"{base_url}{'&awsUpload=true' if aws_upload else ''}", { "date": date, "factorData": risk_model_data.get("factorData"), "covarianceMatrix": risk_model_data.get("covarianceMatrix"), }, timeout=200, ) ] expected_asset_data_calls = [] for batch_num, batch_asset_payload in enumerate(batched_asset_data): final_upload_flag = 'true' if batch_num == len(batched_asset_data) - 1 else 'false' expected_asset_data_calls.append( mock.call( f"{base_url}&finalUpload={final_upload_flag}{'&awsUpload=true' if aws_upload else ''}", batch_asset_payload, timeout=200, ) ) expected_factor_portfolios_data_calls = [] for batch_num, batched_fp_payload in enumerate(batched_factor_portfolios): final_upload_flag = 'true' if batch_num == len(batched_factor_portfolios) - 1 else 'false' expected_factor_portfolios_data_calls.append( mock.call( f"{base_url}&finalUpload={final_upload_flag}{'&awsUpload=true' if aws_upload else ''}", batched_fp_payload, timeout=200, ) ) expected_isc_data_calls = [ mock.call( f"{base_url}&finalUpload=true{'&awsUpload=true' if aws_upload else ''}", {"issuerSpecificCovariance": risk_model_data.get("issuerSpecificCovariance"), "date": date}, timeout=200, ) ] expected_calls = ( expected_factor_data_calls + expected_asset_data_calls + expected_isc_data_calls + expected_factor_portfolios_data_calls ) # mock GsSession mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'post', return_value='Upload Successful') max_asset_batch_size = 2 model.upload_data(risk_model_data, max_asset_batch_size=max_asset_batch_size, aws_upload=aws_upload) call_args_list = GsSession.current.sync.post.call_args_list assert len(call_args_list) == len(expected_calls) assert call_args_list == expected_calls GsSession.current.sync.post.assert_has_calls(expected_calls, any_order=False) @pytest.mark.parametrize("days", [0, 30, 60, 90]) def test_get_bid_ask_spread(mocker, days): model = mock_risk_model(mocker) universe = ["2046251", "2588173"] assets = DataAssetsRequest(UniverseIdentifier.sedol, universe) measure_to_query = Measure.Bid_Ask_Spread if not days else Measure[f'Bid_Ask_Spread_{days}d'] query = { 'startDate': '2022-04-05', 'endDate': '2022-04-05', 'assets': assets, 'measures': [measure_to_query, Measure.Asset_Universe], 'limitFactors': False, } results = { 'results': [ { "date": "2022-04-05", "assetData": { "universe": ["2046251", "2588173"], f"bidAskSpread{'' if days == 0 else f'{days}d'}": [0.5, 1.6], }, } ], 'totalResults': 1, } bid_ask_spread_response = {'2588173': {'2022-04-05': 1.6}, '2046251': {'2022-04-05': 0.5}} mocker.patch.object(GsSession.current.sync, 'post', return_value=results) # run test response = model.get_bid_ask_spread( start_date=dt.date(2022, 4, 5), end_date=dt.date(2022, 4, 5), days=days, assets=assets, format=ReturnFormat.JSON ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='model_id'), query, timeout=200 ) assert response == bid_ask_spread_response @pytest.mark.parametrize("days", [0, 30, 60, 90]) def test_get_trading_volume(mocker, days): model = mock_risk_model(mocker) universe = ["2046251", "2588173"] assets = DataAssetsRequest(UniverseIdentifier.sedol, universe) measure_to_query = Measure.Trading_Volume if not days else Measure[f'Trading_Volume_{days}d'] query = { 'startDate': '2022-04-05', 'endDate': '2022-04-05', 'assets': assets, 'measures': [measure_to_query, Measure.Asset_Universe], 'limitFactors': False, } results = { 'results': [ { "date": "2022-04-05", "assetData": { "universe": ["2046251", "2588173"], f"tradingVolume{'' if days == 0 else f'{days}d'}": [0.5, 1.6], }, } ], 'totalResults': 1, } trading_volume_response = {'2588173': {'2022-04-05': 1.6}, '2046251': {'2022-04-05': 0.5}} mocker.patch.object(GsSession.current.sync, 'post', return_value=results) # run test response = model.get_trading_volume( start_date=dt.date(2022, 4, 5), end_date=dt.date(2022, 4, 5), days=days, assets=assets, format=ReturnFormat.JSON ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='model_id'), query, timeout=200 ) assert response == trading_volume_response @pytest.mark.parametrize("days", [0, 30]) def test_get_traded_value(mocker, days): model = mock_risk_model(mocker) universe = ["2046251", "2588173"] assets = DataAssetsRequest(UniverseIdentifier.sedol, universe) measure_to_query = Measure.Traded_Value_30d if not days else Measure[f'Traded_Value_{days}d'] query = { 'startDate': '2022-04-05', 'endDate': '2022-04-05', 'assets': assets, 'measures': [measure_to_query, Measure.Asset_Universe], 'limitFactors': False, } results = { 'results': [ {"date": "2022-04-05", "assetData": {"universe": ["2046251", "2588173"], "tradedValue30d": [0.5, 1.6]}} ], 'totalResults': 1, } trading_value_30d_response = {'2588173': {'2022-04-05': 1.6}, '2046251': {'2022-04-05': 0.5}} mocker.patch.object(GsSession.current.sync, 'post', return_value=results) # run test response = model.get_traded_value( start_date=dt.date(2022, 4, 5), end_date=dt.date(2022, 4, 5), days=days, assets=assets, format=ReturnFormat.JSON ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='model_id'), query, timeout=200 ) assert response == trading_value_30d_response @pytest.mark.parametrize("days", [0, 30, 60, 90]) def test_get_composite_volume(mocker, days): model = mock_risk_model(mocker) universe = ["2046251", "2588173"] assets = DataAssetsRequest(UniverseIdentifier.sedol, universe) measure_to_query = Measure.Composite_Volume if not days else Measure[f'Composite_Volume_{days}d'] query = { 'startDate': '2022-04-05', 'endDate': '2022-04-05', 'assets': assets, 'measures': [measure_to_query, Measure.Asset_Universe], 'limitFactors': False, } results = { 'results': [ { "date": "2022-04-05", "assetData": { "universe": ["2046251", "2588173"], f"compositeVolume{'' if days == 0 else f'{days}d'}": [0.5, 1.6], }, } ], 'totalResults': 1, } composite_volume_response = {'2588173': {'2022-04-05': 1.6}, '2046251': {'2022-04-05': 0.5}} mocker.patch.object(GsSession.current.sync, 'post', return_value=results) # run test response = model.get_composite_volume( start_date=dt.date(2022, 4, 5), end_date=dt.date(2022, 4, 5), days=days, assets=assets, format=ReturnFormat.JSON ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='model_id'), query, timeout=200 ) assert response == composite_volume_response @pytest.mark.parametrize("days", [0, 30]) def test_get_composite_value(mocker, days): model = mock_risk_model(mocker) universe = ["2046251", "2588173"] assets = DataAssetsRequest(UniverseIdentifier.sedol, universe) measure_to_query = Measure.Composite_Value_30d if not days else Measure[f'Composite_Value_{days}d'] query = { 'startDate': '2022-04-05', 'endDate': '2022-04-05', 'assets': assets, 'measures': [measure_to_query, Measure.Asset_Universe], 'limitFactors': False, } results = { 'results': [ {"date": "2022-04-05", "assetData": {"universe": ["2046251", "2588173"], "compositeValue30d": [0.5, 1.6]}} ], 'totalResults': 1, } composite_value_response = {'2588173': {'2022-04-05': 1.6}, '2046251': {'2022-04-05': 0.5}} mocker.patch.object(GsSession.current.sync, 'post', return_value=results) # run test response = model.get_composite_value( start_date=dt.date(2022, 4, 5), end_date=dt.date(2022, 4, 5), days=days, assets=assets, format=ReturnFormat.JSON ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='model_id'), query, timeout=200 ) assert response == composite_value_response def test_get_issuer_market_cap(mocker): model = mock_risk_model(mocker) universe = ["2046251", "2588173"] assets = DataAssetsRequest(UniverseIdentifier.sedol, universe) query = { 'startDate': '2022-04-05', 'endDate': '2022-04-05', 'assets': assets, 'measures': [Measure.Issuer_Market_Cap, Measure.Asset_Universe], 'limitFactors': False, } results = { 'results': [ { "date": "2022-04-05", "assetData": {"universe": ["2046251", "2588173"], "issuerMarketCap": [1000000000, 2000000000]}, } ], 'totalResults': 1, } issuer_market_cap_response = {'2588173': {'2022-04-05': 2000000000}, '2046251': {'2022-04-05': 1000000000}} mocker.patch.object(GsSession.current.sync, 'post', return_value=results) # run test response = model.get_issuer_market_cap( start_date=dt.date(2022, 4, 5), end_date=dt.date(2022, 4, 5), assets=assets, format=ReturnFormat.JSON ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='model_id'), query, timeout=200 ) assert response == issuer_market_cap_response def test_get_asset_price(mocker): model = mock_risk_model(mocker) universe = ["2046251", "2588173"] assets = DataAssetsRequest(UniverseIdentifier.sedol, universe) query = { 'startDate': '2022-04-05', 'endDate': '2022-04-05', 'assets': assets, 'measures': [Measure.Price, Measure.Asset_Universe], 'limitFactors': False, } results = { 'results': [{"date": "2022-04-05", "assetData": {"universe": ["2046251", "2588173"], "price": [100, 200]}}], 'totalResults': 1, } price_response = {'2588173': {'2022-04-05': 200}, '2046251': {'2022-04-05': 100}} mocker.patch.object(GsSession.current.sync, 'post', return_value=results) # run test response = model.get_asset_price( start_date=dt.date(2022, 4, 5), end_date=dt.date(2022, 4, 5), assets=assets, format=ReturnFormat.JSON ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='model_id'), query, timeout=200 ) assert response == price_response def test_get_asset_capitalization(mocker): model = mock_risk_model(mocker) universe = ["2046251", "2588173"] assets = DataAssetsRequest(UniverseIdentifier.sedol, universe) query = { 'startDate': '2022-04-05', 'endDate': '2022-04-05', 'assets': assets, 'measures': [Measure.Capitalization, Measure.Asset_Universe], 'limitFactors': False, } results = { 'results': [ { "date": "2022-04-05", "assetData": {"universe": ["2046251", "2588173"], "capitalization": [1000000000, 2000000000]}, } ], 'totalResults': 1, } capitalization_response = {'2588173': {'2022-04-05': 2000000000}, '2046251': {'2022-04-05': 1000000000}} mocker.patch.object(GsSession.current.sync, 'post', return_value=results) # run test response = model.get_asset_capitalization( start_date=dt.date(2022, 4, 5), end_date=dt.date(2022, 4, 5), assets=assets, format=ReturnFormat.JSON ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='model_id'), query, timeout=200 ) assert response == capitalization_response def test_get_currency(mocker): model = mock_risk_model(mocker) universe = ["2046251", "2588173"] assets = DataAssetsRequest(UniverseIdentifier.sedol, universe) query = { 'startDate': '2022-04-05', 'endDate': '2022-04-05', 'assets': assets, 'measures': [Measure.Currency, Measure.Asset_Universe], 'limitFactors': False, } results = { 'results': [ {"date": "2022-04-05", "assetData": {"universe": ["2046251", "2588173"], "currency": ["USD", "GBP"]}} ], 'totalResults': 1, } currency_response = {'2588173': {'2022-04-05': "GBP"}, '2046251': {'2022-04-05': "USD"}} mocker.patch.object(GsSession.current.sync, 'post', return_value=results) # run test response = model.get_currency( start_date=dt.date(2022, 4, 5), end_date=dt.date(2022, 4, 5), assets=assets, format=ReturnFormat.JSON ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='model_id'), query, timeout=200 ) assert response == currency_response def test_get_unadjusted_specific_risk(mocker): model = mock_risk_model(mocker) universe = ["2046251", "2588173"] assets = DataAssetsRequest(UniverseIdentifier.sedol, universe) query = { 'startDate': '2022-04-05', 'endDate': '2022-04-05', 'assets': assets, 'measures': [Measure.Unadjusted_Specific_Risk, Measure.Asset_Universe], 'limitFactors': False, } results = { 'results': [ { "date": "2022-04-05", "assetData": {"universe": ["2046251", "2588173"], "unadjustedSpecificRisk": [0.5, 1.6]}, } ], 'totalResults': 1, } unadjusted_specific_risk_response = {'2588173': {'2022-04-05': 1.6}, '2046251': {'2022-04-05': 0.5}} mocker.patch.object(GsSession.current.sync, 'post', return_value=results) # run test response = model.get_unadjusted_specific_risk( start_date=dt.date(2022, 4, 5), end_date=dt.date(2022, 4, 5), assets=assets, format=ReturnFormat.JSON ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='model_id'), query, timeout=200 ) assert response == unadjusted_specific_risk_response def test_get_dividend_yield(mocker): model = mock_risk_model(mocker) universe = ["2046251", "2588173"] assets = DataAssetsRequest(UniverseIdentifier.sedol, universe) query = { 'startDate': '2022-04-05', 'endDate': '2022-04-05', 'assets': assets, 'measures': [Measure.Dividend_Yield, Measure.Asset_Universe], 'limitFactors': False, } results = { 'results': [ {"date": "2022-04-05", "assetData": {"universe": ["2046251", "2588173"], "dividendYield": [0.5, 1.6]}} ], 'totalResults': 1, } dividend_yield_response = {'2588173': {'2022-04-05': 1.6}, '2046251': {'2022-04-05': 0.5}} mocker.patch.object(GsSession.current.sync, 'post', return_value=results) # run test response = model.get_dividend_yield( start_date=dt.date(2022, 4, 5), end_date=dt.date(2022, 4, 5), assets=assets, format=ReturnFormat.JSON ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='model_id'), query, timeout=200 ) assert response == dividend_yield_response def test_get_model_price(mocker): model = mock_macro_risk_model(mocker) universe = ["2046251", "2588173"] assets = DataAssetsRequest(UniverseIdentifier.sedol, universe) query = { 'startDate': '2022-04-05', 'endDate': '2022-04-05', 'assets': assets, 'measures': [Measure.Model_Price, Measure.Asset_Universe], 'limitFactors': False, } results = { 'results': [ {"date": "2022-04-05", "assetData": {"universe": ["2046251", "2588173"], "modelPrice": [100, 200]}} ], 'totalResults': 1, } model_price_response = {'2588173': {'2022-04-05': 200}, '2046251': {'2022-04-05': 100}} mocker.patch.object(GsSession.current.sync, 'post', return_value=results) # run test response = model.get_model_price( start_date=dt.date(2022, 4, 5), end_date=dt.date(2022, 4, 5), assets=assets, format=ReturnFormat.JSON ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='macro_model_id'), query, timeout=200 ) assert response == model_price_response def test_get_covariance_matrix(mocker): model = mock_risk_model(mocker) universe = ["2046251", "2588173"] assets = DataAssetsRequest(UniverseIdentifier.sedol, universe) query = { 'startDate': '2022-04-05', 'endDate': '2022-04-05', 'assets': assets, 'measures': [ Measure.Covariance_Matrix, Measure.Factor_Name, Measure.Factor_Id, Measure.Universe_Factor_Exposure, Measure.Asset_Universe, ], 'limitFactors': True, } results = { 'results': [ { "date": "2022-04-05", "factorData": [ { "factorId": "1", "factorName": "factor1", }, { "factorId": "2", "factorName": "factor2", }, { "factorId": "3", "factorName": "factor3", }, { "factorId": "4", "factorName": "factor4", }, ], "covarianceMatrix": [ [0.5, 0.6, 0.7, 0.8], [0.3, 0.7, 0.8, 0.9], [0.5, 0.6, 0.7, 0.8], [0.3, 0.7, 0.8, 0.9], ], } ], 'totalResults': 1, } covariance_matrix_response = [ { "date": "2022-04-05", "factorData": [ { "factorId": "1", "factorName": "factor1", }, { "factorId": "2", "factorName": "factor2", }, { "factorId": "3", "factorName": "factor3", }, { "factorId": "4", "factorName": "factor4", }, ], "covarianceMatrix": [ [0.5, 0.6, 0.7, 0.8], [0.3, 0.7, 0.8, 0.9], [0.5, 0.6, 0.7, 0.8], [0.3, 0.7, 0.8, 0.9], ], } ] mocker.patch.object(GsSession.current.sync, 'post', return_value=results) # run test response = model.get_covariance_matrix( start_date=dt.date(2022, 4, 5), end_date=dt.date(2022, 4, 5), assets=assets, format=ReturnFormat.JSON ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='model_id'), query, timeout=200 ) assert response == covariance_matrix_response def test_get_unadjusted_covariance_matrix(mocker): model = mock_risk_model(mocker) universe = ["2046251", "2588173"] assets = DataAssetsRequest(UniverseIdentifier.sedol, universe) query = { 'startDate': '2022-04-05', 'endDate': '2022-04-05', 'assets': assets, 'measures': [ Measure.Unadjusted_Covariance_Matrix, Measure.Factor_Name, Measure.Factor_Id, Measure.Universe_Factor_Exposure, Measure.Asset_Universe, ], 'limitFactors': True, } results = { 'results': [ { "date": "2022-04-05", "factorData": [ { "factorId": "1", "factorName": "factor1", }, { "factorId": "2", "factorName": "factor2", }, { "factorId": "3", "factorName": "factor3", }, { "factorId": "4", "factorName": "factor4", }, ], "unadjustedCovarianceMatrix": [ [0.5, 0.6, 0.7, 0.8], [0.3, 0.7, 0.8, 0.9], [0.5, 0.6, 0.7, 0.8], [0.3, 0.7, 0.8, 0.9], ], } ], 'totalResults': 1, } unadjusted_covariance_matrix_response = [ { "date": "2022-04-05", "factorData": [ { "factorId": "1", "factorName": "factor1", }, { "factorId": "2", "factorName": "factor2", }, { "factorId": "3", "factorName": "factor3", }, { "factorId": "4", "factorName": "factor4", }, ], "unadjustedCovarianceMatrix": [ [0.5, 0.6, 0.7, 0.8], [0.3, 0.7, 0.8, 0.9], [0.5, 0.6, 0.7, 0.8], [0.3, 0.7, 0.8, 0.9], ], } ] mocker.patch.object(GsSession.current.sync, 'post', return_value=results) # run test response = model.get_unadjusted_covariance_matrix( start_date=dt.date(2022, 4, 5), end_date=dt.date(2022, 4, 5), assets=assets, format=ReturnFormat.JSON ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='model_id'), query, timeout=200 ) assert response == unadjusted_covariance_matrix_response def test_get_pre_vra_covariance_matrix(mocker): model = mock_risk_model(mocker) universe = ["2046251", "2588173"] assets = DataAssetsRequest(UniverseIdentifier.sedol, universe) query = { 'startDate': '2022-04-05', 'endDate': '2022-04-05', 'assets': assets, 'measures': [ Measure.Pre_VRA_Covariance_Matrix, Measure.Factor_Name, Measure.Factor_Id, Measure.Universe_Factor_Exposure, Measure.Asset_Universe, ], 'limitFactors': True, } results = { 'results': [ { "date": "2022-04-05", "factorData": [ { "factorId": "1", "factorName": "factor1", }, { "factorId": "2", "factorName": "factor2", }, { "factorId": "3", "factorName": "factor3", }, { "factorId": "4", "factorName": "factor4", }, ], "preVRACovarianceMatrix": [ [0.5, 0.6, 0.7, 0.8], [0.3, 0.7, 0.8, 0.9], [0.5, 0.6, 0.7, 0.8], [0.3, 0.7, 0.8, 0.9], ], } ], 'totalResults': 1, } pre_vra_covariance_matrix_response = [ { "date": "2022-04-05", "factorData": [ { "factorId": "1", "factorName": "factor1", }, { "factorId": "2", "factorName": "factor2", }, { "factorId": "3", "factorName": "factor3", }, { "factorId": "4", "factorName": "factor4", }, ], "preVRACovarianceMatrix": [ [0.5, 0.6, 0.7, 0.8], [0.3, 0.7, 0.8, 0.9], [0.5, 0.6, 0.7, 0.8], [0.3, 0.7, 0.8, 0.9], ], } ] mocker.patch.object(GsSession.current.sync, 'post', return_value=results) # run test response = model.get_pre_vra_covariance_matrix( start_date=dt.date(2022, 4, 5), end_date=dt.date(2022, 4, 5), assets=assets, format=ReturnFormat.JSON ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='model_id'), query, timeout=200 ) assert response == pre_vra_covariance_matrix_response def test_get_risk_free_rate(mocker): model = mock_risk_model(mocker) query = { 'startDate': '2022-04-05', 'endDate': '2022-04-05', 'measures': [Measure.Risk_Free_Rate], 'assets': DataAssetsRequest(UniverseIdentifier.gsid, []), 'limitFactors': False, } currencies = ["EUR", "INR"] risk_free_rate = [1.08, 0.012] results = { 'results': [ { "date": "2022-04-05", "currencyRatesData": { "riskFreeRate": risk_free_rate, "currency": currencies, }, } ], 'totalResults': 1, } risk_free_rate_response = { "currency": {('2022-04-05', 0): 'EUR', ('2022-04-05', 1): 'INR'}, "riskFreeRate": {('2022-04-05', 0): 1.08, ('2022-04-05', 1): 0.012}, } mocker.patch.object(GsSession.current.sync, 'post', return_value=results) response = model.get_risk_free_rate( start_date=dt.date(2022, 4, 5), end_date=dt.date(2022, 4, 5), format=ReturnFormat.JSON ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='model_id'), query, timeout=200 ) assert response == risk_free_rate_response # fitler risk free rates by currency expected_filtered_rates = {"currency": {('2022-04-05', 1): 'INR'}, "riskFreeRate": {('2022-04-05', 1): 0.012}} actual_filtered_rates = model.get_risk_free_rate( start_date=dt.date(2022, 4, 5), end_date=dt.date(2022, 4, 5), currencies=[Currency.INR], format=ReturnFormat.JSON, ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='model_id'), query, timeout=200 ) assert actual_filtered_rates == expected_filtered_rates # test DataFrame return format expected_data_frame = get_optional_data_as_dataframe( [ { "date": "2022-04-05", "currencyRatesData": { "riskFreeRate": risk_free_rate, "currency": currencies, }, } ], "currencyRatesData", ) expected_data_frame = expected_data_frame.loc[expected_data_frame['currency'].isin(['INR'])] actual_data_frame = model.get_risk_free_rate( start_date=dt.date(2022, 4, 5), end_date=dt.date(2022, 4, 5), currencies=[Currency.INR] ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='model_id'), query, timeout=200 ) assert_frame_equal(expected_data_frame, actual_data_frame, check_like=True) def test_get_currency_exchange_rate(mocker): model = mock_risk_model(mocker) query = { 'startDate': '2022-04-05', 'endDate': '2022-04-05', 'measures': [Measure.Currency_Exchange_Rate], 'assets': DataAssetsRequest(UniverseIdentifier.gsid, []), 'limitFactors': False, } currencies = ["EUR", "INR"] currency_exchange_rate = [1.08, 0.012] results = { 'results': [ { "date": "2022-04-05", "currencyRatesData": { "exchangeRate": currency_exchange_rate, "currency": currencies, }, } ], 'totalResults': 1, } currency_exchange_rate_response = { "currency": {('2022-04-05', 0): 'EUR', ('2022-04-05', 1): 'INR'}, "exchangeRate": {('2022-04-05', 0): 1.08, ('2022-04-05', 1): 0.012}, } mocker.patch.object(GsSession.current.sync, 'post', return_value=results) response = model.get_currency_exchange_rate( start_date=dt.date(2022, 4, 5), end_date=dt.date(2022, 4, 5), format=ReturnFormat.JSON ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='model_id'), query, timeout=200 ) assert response == currency_exchange_rate_response # fitler risk free rates by currency expected_filtered_rates = {"currency": {('2022-04-05', 1): 'INR'}, "exchangeRate": {('2022-04-05', 1): 0.012}} actual_filtered_rates = model.get_currency_exchange_rate( start_date=dt.date(2022, 4, 5), end_date=dt.date(2022, 4, 5), currencies=[Currency.INR], format=ReturnFormat.JSON, ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='model_id'), query, timeout=200 ) assert actual_filtered_rates == expected_filtered_rates # test DataFrame return format expected_data_frame = get_optional_data_as_dataframe( [ { "date": "2022-04-05", "currencyRatesData": { "exchangeRate": currency_exchange_rate, "currency": currencies, }, } ], "currencyRatesData", ) expected_data_frame = expected_data_frame.loc[expected_data_frame['currency'].isin(['INR'])] actual_data_frame = model.get_currency_exchange_rate( start_date=dt.date(2022, 4, 5), end_date=dt.date(2022, 4, 5), currencies=[Currency.INR] ) GsSession.current.sync.post.assert_called_with( '/risk/models/data/{id}/query'.format(id='model_id'), query, timeout=200 ) assert_frame_equal(expected_data_frame, actual_data_frame, check_like=True) if __name__ == "__main__": pytest.main([__file__]) ================================================ FILE: gs_quant/test/models/test_risk_model_utils.py ================================================ """ Copyright 2021 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from unittest.mock import ANY import pytest from unittest import mock from gs_quant.session import GsSession, Environment from gs_quant.models.risk_model_utils import _upload_factor_data_if_present @pytest.mark.parametrize('total_factors', [100]) def test__upload_factor_data_if_present(mocker, total_factors: int): from gs_quant.session import OAuth2Session OAuth2Session.init = mock.MagicMock(return_value=None) GsSession.use(Environment.QA, 'client_id', 'secret') date = "2024-03-28" factor_data = [ { 'factorCategory': '1', 'factorName': f'Factor {i + 1}', 'factorCategoryId': 'z', 'factorReturn': 0.001 * (i + 1), } for i in range(total_factors) ] covariance_matrix = [[0 for i in range(total_factors)] for j in range(total_factors)] pre_vra_covariance_matrix = [[0 for i in range(total_factors)] for j in range(total_factors)] unadjusted_covariance_matrix = [[0 for i in range(total_factors)] for j in range(total_factors)] risk_model_data = { "factorData": factor_data, "covarianceMatrix": covariance_matrix, "preVRACovarianceMatrix": pre_vra_covariance_matrix, "unadjustedCovarianceMatrix": unadjusted_covariance_matrix, } # Ensuring GsSession._post receives the values that we pass to _upload_factor_data_if_present method def match_dictionaries(*args, **kwargs): url = args[0] actual_data = args[1] for key in risk_model_data: if key in ['preVRACovarianceMatrix', 'unadjustedCovarianceMatrix'] and 'awsUpload' not in url: # making sure new covariance matrices are only uploaded to AWS assert key not in actual_data continue assert key in actual_data assert risk_model_data[key] == actual_data[key] mocker.patch.object(GsSession.current.sync, 'post', return_value='success', side_effect=match_dictionaries) _upload_factor_data_if_present('TEST_RISK_MODEL', risk_model_data, date) GsSession.current.sync.post.assert_called_with( '/risk/models/data/TEST_RISK_MODEL?partialUpload=true', ANY, timeout=200 ) _upload_factor_data_if_present('TEST_RISK_MODEL', risk_model_data, date, aws_upload=True) GsSession.current.sync.post.assert_called_with( '/risk/models/data/TEST_RISK_MODEL?partialUpload=true&awsUpload=true', ANY, timeout=200 ) ================================================ FILE: gs_quant/test/resources/MA4B66MW5E27U8P32SB-xrefs.json ================================================ { "xrefs": [ { "startDate": "1952-01-01", "endDate": "2012-08-24", "identifiers": { "ric": ".SPX", "eid": "0", "gsid": "100", "cid": "10756", "bbid": "SPX", "ticker": "SPX", "secName": ".SPX" } }, { "startDate": "2012-08-25", "endDate": "2012-08-25", "identifiers": { "ric": ".SPX", "eid": "0", "gsid": "100", "cid": "10756", "ticker": "SPX", "secName": ".SPX" } }, { "startDate": "2012-08-26", "endDate": "2019-03-03", "identifiers": { "ric": ".SPX", "eid": "0", "gsid": "100", "cid": "10756", "bbid": "SPX", "cusip": "648815108", "gss": "SPX", "isin": "US78378X1072", "primeId": "1000043264", "ticker": "SPX", "wpk": "969003", "gsn": "152850", "secName": ".SPX", "mdapi": ".SPX" } }, { "startDate": "2019-03-04", "endDate": "2019-03-04", "identifiers": { "ric": ".SPX", "rcic": ".SPX", "eid": "0", "gsid": "100", "bbid": "SPX", "cusip": "648815108", "gss": "SPX", "isin": "US78378X1072", "primeId": "1000043264", "ticker": "SPX", "wpk": "969003", "gsn": "152850", "secName": ".SPX", "mdapi": ".SPX" } }, { "startDate": "2019-03-05", "endDate": "2019-05-16", "identifiers": { "ric": ".SPX", "rcic": ".SPX", "eid": "0", "gsid": "100", "bbid": "SPX", "delisted": "no", "cusip": "648815108", "gss": "SPX", "isin": "US78378X1072", "primeId": "1000043264", "ticker": "SPX", "wpk": "969003", "gsn": "152850", "secName": ".SPX", "mdapi": ".SPX" } }, { "startDate": "2019-05-17", "endDate": "2952-12-31", "identifiers": { "ric": ".SPX", "rcic": ".SPX", "eid": "0", "gsid": "100", "bbid": "SPX", "delisted": "no", "cusip": "648815108", "gss": "SPX", "isin": "US6488151084", "primeId": "1000043264", "ticker": "SPX", "wpk": "969003", "gsn": "152850", "secName": ".SPX", "mdapi": ".SPX" } } ] } ================================================ FILE: gs_quant/test/resources/MA4B66MW5E27U8P32SB.json ================================================ { "assetClass": "Equity", "classifications": { "riskCountryName": "United States", "riskCountryCode": "US", "countryName": "United States", "countryCode": "US", "isPrimary": true }, "currency": "USD", "description": "", "domains": {}, "exchange": "NYSE", "id": "MA4B66MW5E27U8P32SB", "identifiers": [ { "type": "MDAPI", "value": ".SPX" }, { "type": "GSID", "value": "100" }, { "type": "GSS", "value": "SPX" }, { "type": "CUS", "value": "648815108" }, { "type": "PRIMEID", "value": "1000043264" }, { "type": "GSN", "value": "152850" }, { "type": "WPK", "value": "969003" }, { "type": "RCIC", "value": ".SPX" }, { "type": "BID", "value": "SPX" }, { "type": "ISN", "value": "US6488151084" }, { "type": "RIC", "value": ".SPX" }, { "type": "SECNAME", "value": ".SPX" }, { "type": "TKR", "value": "SPX" }, { "type": "DELISTED", "value": "no" }, { "type": "EID", "value": "0" } ], "lastUpdatedById": "3549cd2c46c3468bae0d90b1f2b9e609", "lastUpdatedTime": "2019-08-09T23:12:43.11Z", "listed": true, "name": "S&P 500 Index", "ownerId": "3549cd2c46c3468bae0d90b1f2b9e609", "parameters": { "exchangeCurrency": "USD", "indexCalculationType": "Price Return", "indexDivisor": 8.32473196588017E9, "pricingLocation": "NYC" }, "rank": 0.9, "region": "Americas", "reportIds": [ "RP5QC00HE4XJTHHN", "RP7ERDJBAY0M8Z16", "RP7X1WWKWQD855E3", "RPFR7WPWZ6G8H8X7", "RP2SBMPYCQTHXVH3", "RPDR3N2F81716JV8", "RPVYX74EMDHY2ZM7" ], "shortName": "SPX", "styles": [], "tags": [ "Americas", "Equity", "Equity Option::LAL", "Equity Option::Listed", "Equity Option::OTC", "Index", "New York Stock Exchange", "NYC", "NYSE", "United States", "US" ], "type": "Index", "underliers": [ "MA4B66MW5E27U9VBB6A", "MA4B66MW5E27U9VBB86", "MA4B66MW5E27U9VBB8U", "MA4B66MW5E27U9VBB94", "MA4B66MW5E27U9VBBAX", "MA4B66MW5E27U9VBBB7", "MA4B66MW5E27U9VBBDQ", "MA4B66MW5E27U9VBBEQ", "MA4B66MW5E27U9VBBKA", "MA4B66MW5E27U9XPV7X", "MA4B66MW5E27U9XPV9J", "MA4B66MW5E27U9XPVA7", "MA4B66MW5E27U9XPVB5", "MA4B66MW5E27U9XPVBM", "MA4B66MW5E27U9XPVBT", "MA4B66MW5E27U9XPVDL", "MA4B66MW5E27U9XPVFF", "MA4B66MW5E27U9XPVG2", "MA4B66MW5E27U9XPVJ9", "MA4B66MW5E27U9XPVMG", "MA4B66MW5E27U9XPVSE", "MA4B66MW5E27U9XPVUF", "MA4B66MW5E27U9XPVUQ", "MA4B66MW5E27U9XPVUY", "MA4B66MW5E27U9XPVVM", "MA4B66MW5E27U9XPVYM", "MA4B66MW5E27U9XPW44", "MA4B66MW5E27U9YGLZT", "MA4B66MW5E27U9YGM27", "MA4B66MW5E27U9YGM2A", "MA4B66MW5E27U9YGM8P", "MA4B66MW5E27U9YGM9V", "MAYT9CYW273Y0CSH", "MA4B66MW5E27U9YGMB9", "MA4B66MW5E27U9YGMBE", "MA4B66MW5E27U9YGMC8", "MA4B66MW5E27U9YGMCG", "MA4B66MW5E27U9YGMEE", "MA4B66MW5E27U9YGMFT", "MA4B66MW5E27U9YGMGY", "MA4B66MW5E27U9YGMJD", "MA4B66MW5E27U9YGMLB", "MA4B66MW5E27U9YGMLN", "MA4B66MW5E27U9YGMN3", "MA4B66MW5E27U9YGMNE", "MA4B66MW5E27U9YGMNV", "MA4B66MW5E27U9YGMPR", "MA4B66MW5E27U9YGMQF", "MACGYFW21RBZQQZ9", "MA4B66MW5E27U9YGMWY", "MAKTAXP90KNJ2S8R", "MA4B66MW5E27U9ZPCXW", "MA4B66MW5E27U9ZPCZW", "MA4B66MW5E27U9ZPD5Q", "MA4B66MW5E27U9ZPD6K", "MA4B66MW5E27U9ZPD9G", "MA4B66MW5E27U9ZPDA8", "MA4B66MW5E27U9ZPDCJ", "MA4B66MW5E27U9ZPDEW", "MA4B66MW5E27U9ZPDFX", "MA4B66MW5E27U9ZPDGH", "MA4B66MW5E27UA39HX5", "MA4B66MW5E27UA39HZZ", "MA4B66MW5E27UA39J2L", "MA4B66MW5E27UA39J6X", "MA4B66MW5E27UA39J95", "MA4B66MW5E27UA39JAW", "MA925YJVVGJR2NJ3", "MA4B66MW5E27UA39JGE", "MA4B66MW5E27UA39JL8", "MA5BN3N8QV6AX7AV", "MA4B66MW5E27UA39JQC", "MA4B66MW5E27UA39JQR", "MA4B66MW5E27UA4479J", "MA4B66MW5E27UA447JC", "MA4B66MW5E27UA447M6", "MA4B66MW5E27UA447SQ", "MA4B66MW5E27UA447Y5", "MA4B66MW5E27UA447ZM", "MA4B66MW5E27UA4ZCWT", "MA4B66MW5E27UA4ZCZT", "MA4B66MW5E27UA4ZD22", "MA4B66MW5E27UA4ZD7D", "MA4B66MW5E27UA4ZD8S", "MAJXV577W5XJCXEH", "MAEN7JQGGJN2RZ68", "MA4B66MW5E27UA4ZDCH", "MA4B66MW5E27UA4ZDF4", "MA4B66MW5E27UA4ZDFF", "MA4B66MW5E27UA7V8VS", "MA4B66MW5E27UABWUZU", "MA4B66MW5E27UACX5UW", "MA4B66MW5E27UACX5WP", "MA4B66MW5E27UACX5XY", "MA4B66MW5E27UACX5YP", "MA4B66MW5E27UACX66J", "MA4B66MW5E27UACX6AZ", "MA4B66MW5E27UACX6BL", "MA4B66MW5E27UACX6CL", "MA4B66MW5E27UACX6F9", "MA4B66MW5E27UACX6JS", "MA4B66MW5E27UACX6PS", "MA4B66MW5E27UACX6Q7", "MA4B66MW5E27UACX6QV", "MA4B66MW5E27UACX6R8", "MA4B66MW5E27UACX6RJ", "MA4B66MW5E27UACX6RZ", "MA4B66MW5E27UADJ545", "MA4B66MW5E27UADJ55W", "MA4B66MW5E27UADJ58F", "MA4B66MW5E27UADJ5B5", "MA4B66MW5E27UADJ5BB", "MA4B66MW5E27UADJ5ES", "MA4B66MW5E27UADJ5EZ", "MA4B66MW5E27UADJ5GE", "MA4B66MW5E27UADJ5GM", "MA4B66MW5E27UADJ5HN", "MA3SY5QZMQ0J6T31", "MA4B66MW5E27UADJ5LP", "MA4B66MW5E27UADJ5RL", "MA4B66MW5E27UADJ5VJ", "MA4B66MW5E27UADJ5ZC", "MA4B66MW5E27UADJ5ZS", "MA4B66MW5E27UAE4LJF", "MA4B66MW5E27UAE4LKU", "MA18K69WEMJ34B7N", "MA4B66MW5E27UAE4LLL", "MA4B66MW5E27UAE4LQC", "MA4B66MW5E27UAE4LQY", "MA4B66MW5E27UAE4LTR", "MA4B66MW5E27UAE4LXX", "MA4B66MW5E27UAE4LZE", "MA4B66MW5E27UAE4M69", "MA4B66MW5E27UAE4M76", "MA4B66MW5E27UAE4MBG", "MA4B66MW5E27UAE4MBN", "MA4B66MW5E27UAE4MDD", "MA4B66MW5E27UAE4MDN", "MA4B66MW5E27UAE4MDU", "MA4B66MW5E27UAE4MG6", "MA4B66MW5E27UAE4MG9", "MA4B66MW5E27UAE4MGB", "MA4B66MW5E27UAE4MGG", "MA4B66MW5E27UAEP3WH", "MA4B66MW5E27UAEP3WT", "MA4B66MW5E27UAEP43R", "MA4B66MW5E27UAEP43W", "MA4B66MW5E27UAEP46J", "MA4B66MW5E27UAEP46Y", "MA4B66MW5E27UAEP4AW", "MA4B66MW5E27UAEP4CG", "MA4B66MW5E27UAEP4D8", "MA4B66MW5E27UAEP4DZ", "MA113C0S76ZGPRG3", "MA4B66MW5E27UAEP4H9", "MA4B66MW5E27UAEP4JY", "MA4B66MW5E27UAEP4LY", "MA4B66MW5E27UAEP4NL", "MA4B66MW5E27UAEP4T6", "MA4B66MW5E27UAF9K4X", "MA4B66MW5E27UAF9K64", "MA4B66MW5E27UAF9KDG", "MA4B66MW5E27UAF9KE6", "MA4B66MW5E27UAF9KLB", "MA4B66MW5E27UAF9KPQ", "MA4B66MW5E27UAF9KPV", "MA4B66MW5E27UAF9KT3", "MA4B66MW5E27UAF9KVQ", "MA4B66MW5E27UAF9KXK", "MA4B66MW5E27UAF9KY5", "MA4B66MW5E27UAFU24E", "MAD7GGECMBDVDX3Y", "MA4B66MW5E27UAFU28S", "MA4B66MW5E27UAFU2AF", "MA4B66MW5E27UAFU2CB", "MA4B66MW5E27UAFU2CD", "MA4B66MW5E27UAFU2CS", "MA4B66MW5E27UAFU2E9", "MA4B66MW5E27UAFU2FS", "MA4B66MW5E27UAFU2G9", "MA4B66MW5E3VLSECN", "MA4B66MW5E27UAFU2H5", "MA4B66MW5E27UAFU2M3", "MA4B66MW5E27UAFU2QP", "MA4B66MW5E27UAFU2QR", "MA4B66MW5E27UAFU2TN", "MA4B66MW5E27UAFU2XL", "MA4B66MW5E27UAFU2XU", "MA4B66MW5E27UAFU2XX", "MA4B66MW5E27UAFU2ZQ", "MA4B66MW5E27UAFU32M", "MA4B66MW5E27UAFU32R", "MA4B66MW5E27UAGEHA3", "MA4B66MW5E27UAGEHKZ", "MA4B66MW5E27UAGEHL4", "MA4B66MW5E27UAGEHN3", "MA4B66MW5E27UAGEHQK", "MA4B66MW5E27UAGEHU2", "MA4B66MW5E27UAGEHUC", "MA2N6PA2AH2E3P7E", "MA4B66MW5E27UAGEJ85", "MA4B66MW5E27UAGYYE3", "MA4B66MW5E27UAGYYP2", "MA4B66MW5E27UAGYYPM", "MAWHMJBDGTNSC4T5", "MA4B66MW5E27UAGYYV5", "MA4B66MW5E27UAGYYVE", "MA4B66MW5E27UAGYZ49", "MA4B66MW5E27UAGYZ4D", "MA4B66MW5E27UAGYZ5E", "MA4B66MW5E27UAGYZ6J", "MA4B66MW5E27UAGYZ79", "MA4B66MW5E27UAGYZAL", "MA4B66MW5E27UAHKG34", "MA4B66MW5E27UAHKGBS", "MA4B66MW5E27UAHKGET", "MA4B66MW5E27UAHKGGT", "MA4B66MW5E27UAHKGJ3", "MA4B66MW5E27UAHKGJH", "MA4B66MW5E27UAHKGKQ", "MA4B66MW5E27UAHKGM2", "MA4B66MW5E27UAHKGMA", "MA4B66MW5E27UAHKGR3", "MA4B66MW5E27UAHKGS3", "MA4B66MW5E27UAHKGUS", "MA4B66MW5E27UAHKGUZ", "MA4B66MW5E27UAHKGZC", "MA4B66MW5E27UAJ5XAE", "MA4B66MW5E27UAJ5XAZ", "MA4B66MW5E27UAJ5XBD", "MA4B66MW5E27UAJ5XCF", "MA4B66MW5E27UAJ5XCU", "MA4B66MW5E27UAJ5XDG", "MA4B66MW5E27UAJ5XEB", "MA4B66MW5E27UAJ5XEZ", "MA4B66MW5E27UAJ5XGX", "MA4B66MW5E27UAJ5XHK", "MA4B66MW5E27UAJ5XHW", "MA4B66MW5E27UAJ5XLL", "MA4B66MW5E27UAJ5XUL", "MA4B66MW5E27UAJ5XW7", "MA4B66MW5E27UAJ5Y38", "MA4B66MW5E27UAJ5Y4J", "MA4B66MW5E27UAJ5Y5G", "MA4B66MW5E27UAJQEH8", "MA4B66MW5E27UAJQENQ", "MA9AKYCQX77VZ6WT", "MA4B66MW5E27UAJQETU", "MA4B66MW5E27UAJQEUR", "MA4B66MW5E27UAJQEXZ", "MA4B66MW5E27UAJQEYZ", "MA4B66MW5E27UAJQEZ7", "MAA0T8B3BF5SXH13", "MA4B66MW5E27UAJQF32", "MA4B66MW5E27UAJQF4M", "MA4B66MW5E27UAJQF86", "MA4B66MW5E27UAJQF95", "MA4B66MW5E27UAKHYZ6", "MA4B66MW5E27UAKHZ3C", "MA4B66MW5E27UAKHZ7P", "MA0YAYRD3SDTMZKN", "MA4B66MW5E27UAKHZ9D", "MA4B66MW5E27UAKHZ9H", "MA4B66MW5E27UAKHZBQ", "MA4B66MW5E27UAKHZDF", "MA4B66MW5E27UAKHZDM", "MA4B66MW5E27UAKHZFD", "MA4B66MW5E27UAKHZJE", "MA4B66MW5E27UAKHZJX", "MA4B66MW5E27UAKNZAY", "MA4B66MW5E27UAKNZB4", "MA4B66MW5E27UAKNZD3", "MA4B66MW5E27UAKNZDR", "MA4B66MW5E27UAKNZFS", "MA4B66MW5E27UAKNZHB", "MA4B66MW5E27UAKNZHN", "MA4B66MW5E27UAKNZJ4", "MA4B66MW5E27UAKNZKD", "MA4B66MW5E27UAKNZNV", "MA4B66MW5E27UAKNZSC", "MA4B66MW5E27UAKNZSM", "MA4B66MW5E27UAKNZWA", "MA4B66MW5E27UAKNZZH", "MA4B66MW5E27UAKP24Y", "MA4B66MW5E27UAKP26F", "MA4B66MW5E27UAKP26Y", "MA4B66MW5E27UAKV9M9", "MAXD496KP4AEDCSY", "MA4B66MW5E27UAKV9P7", "MA4B66MW5E27UAKV9RS", "MA4B66MW5E27UAKV9SY", "MA4B66MW5E27UAKV9UB", "MA4B66MW5E27UAKV9UK", "MAVBHXKMQX3EKHEK", "MA4B66MW5E27UAKV9Y4", "MA4B66MW5E27UAKVA2A", "MA4B66MW5E27UAKVA79", "MAA101TE9H0JH58S", "MA4B66MW5E27UAKVA9P", "MA4B66MW5E27UAKVAAK", "MA4B66MW5E27UAKVAB4", "MA4B66MW5E27UAKVABA", "MA4B66MW5E27UAKVABN", "MA4B66MW5E27UAKVAFJ", "MA4B66MW5E27UAKVAGH", "MA4B66MW5E27UAL3HZC", "MA4B66MW5E27UAL3J2S", "MA4B66MW5E27UAL3J38", "MA4B66MW5E27UAL3J3M", "MA4B66MW5E27UAL3J73", "MA4B66MW5E27UAL3J7Z", "MA4B66MW5E27UAL3JD3", "MA4B66MW5E27UAL3JHC", "MA4B66MW5E27UAL3JK8", "MA4B66MW5E27UAL3JR2", "MA4B66MW5E27UAL3JS2", "MA4B66MW5E27UAL9SAE", "MA4B66MW5E27UAL9SCK", "MA4B66MW5E27UAL9SDB", "MA4B66MW5E27UAL9SH5", "MA4B66MW5E27UAL9SHW", "MA4B66MW5E27UAL9SLW", "MA4B66MW5E27UAL9SMW", "MA4B66MW5E27UAL9SRL", "MA4B66MW5E27UAL9SSM", "MA4B66MW5E27UAL9STW", "MA4B66MW5E27UAL9SUH", "MA4B66MW5E27UAL9SUX", "MA4B66MW5E27UAL9SVE", "MA4B66MW5E27UAL9SY2", "MA4B66MW5E27UAL9SYE", "MA4B66MW5E27UAL9T4D", "MA4B66MW5E27UAL9T8M", "MA4B66MW5E27UALG2LD", "MA4B66MW5E27UALG2SS", "MAWG55F0PXD7A3J3", "MA4B66MW5E27UALG2W2", "MA4B66MW5E27UALG2XD", "MA4B66MW5E27UALG2XY", "MA4B66MW5E27UALG33R", "MA4B66MW5E27UALG36F", "MA4B66MW5E27UALG396", "MA4B66MW5E27UALG39P", "MA4B66MW5E27UALG3AG", "MA4B66MW5E27UALG3DQ", "MA4B66MW5E27UALG3GB", "MA4B66MW5E27UALNAVR", "MA4B66MW5E27UALNAX8", "MA4B66MW5E27UALNAZ5", "MA4B66MW5E27UALNB3B", "MA4B66MW5E27UALNB48", "MA4B66MW5E27UALNB5V", "MA3FCE91DAF1853R", "MA4B66MW5E27UALNB96", "MA4B66MW5E27UALNB98", "MA4B66MW5E27UALNBCT", "MA4B66MW5E27UALNBLL", "MA4B66MW5E27UALNBNP", "MA4B66MW5E27UALUK7V", "MA4B66MW5E27UALUK9H", "MA4B66MW5E27UALUKFW", "MA4B66MW5E27UALUKMS", "MA4B66MW5E27UALUKNA", "MA4B66MW5E27UALUKPX", "MA4B66MW5E27UALUKW2", "MAMDDXVJYX77V2K9", "MA4B66MW5E27UALUL23", "MADSMWFNMPEVFDHT", "MA4B66MW5E27UALUL3R", "MA4B66MW5E27UALUL62", "MA4B66MW5E27UAM2TMU", "MA4B66MW5E27UAM2TPA", "MA4B66MW5E27UAM2TTN", "MA4B66MW5E27UAM2TTS", "MA4B66MW5E27UAM2TVT", "MA4B66MW5E27UAM2TYL", "MA4B66MW5E27UAM2U3E", "MA4B66MW5E27UAM2U4V", "MA4B66MW5E27UAM2U5K", "MA4B66MW5E27UAM2UBD", "MA4B66MW5E27UAM2UBT", "MA4B66MW5E27UAM2UFX", "MA4B66MW5E27UAM2ULM", "MA4B66MW5E27UAM949R", "MA4B66MW5E27UAM94CR", "MA4B66MW5E27UAM94JY", "MA4B66MW5E27UAM94LK", "MA4B66MW5E27UAM94M4", "MA4B66MW5E27UAM94N9", "MA4B66MW5E27UAM94Q4", "MA4B66MW5E27UAM94U2", "MA4B66MW5E27UAM954L", "MA4B66MW5E27UAM957G", "MA6TWYY4GV60PQE8", "MA4B66MW5E27UAMFCRR", "MA4B66MW5E27UAMFCVT", "MA4B66MW5E27UAMFCYN", "MA4B66MW5E27UAMFD3Q", "MA4B66MW5E27UAMFD4L", "MA4B66MW5E27UAMFD6F", "MA4B66MW5E27UAMFDB3", "MA4B66MW5E27UAMFDB6", "MA4B66MW5E27UAMFDBN", "MA4B66MW5E27UAMFDCA", "MA4B66MW5E27UAMFDGJ", "MA4B66MW5E27UAMFDJQ", "MA4B66MW5E27UAMMMH7", "MA4B66MW5E27UAMMMLA", "MA4B66MW5E27UAMMMP9", "MA4B66MW5E27UAMMMWG", "MA4B66MW5E27UAMTVUE", "MA4B66MW5E27UAMTW23", "MA4B66MW5E27UAMTW2L", "MA4B66MW5E27UAMTW6M", "MA4B66MW5E27UAMTW7E", "MA4B66MW5E27UAMTWEY", "MA4B66MW5E27UAMTWHV", "MA4B66MW5E27UAMTWKJ", "MA4B66MW5E27UAN26EW", "MA6GDCTX2TDGN7JF", "MA4B66MW5E27UAN26ME", "MA4B66MW5E27UAN26XN", "MA4B66MW5E27UAN2739", "MA4B66MW5E27UAN273Y", "MA4B66MW5E27UAN2748", "MA4B66MW5E27UAN8F39", "MA4B66MW5E27UAN8F3C", "MA4B66MW5E27UAN8F6A", "MA4B66MW5E27UAN8F6H", "MA4B66MW5E27UAN8F6P", "MA4B66MW5E27UAN8F8F", "MA4B66MW5E27UAN8F94", "MA4B66MW5E27UAN8FCC", "MA4B66MW5E27UAN8FJT", "MA4B66MW5E27UAN8FMC", "MA4B66MW5E27UAN8FRR", "MA4B66MW5E27UAN8FTS", "MA4B66MW5E27UANEPBN", "MA4B66MW5E27UANEPER", "MA4B66MW5E27UANEPLL", "MAG5N56MVQD35PMS", "MAAHJ4JYCKZ6P025", "MA4B66MW5E27UANEPYQ", "MA4B66MW5E27UANEQ29", "MA4B66MW5E27UANEQ3V", "MA4B66MW5E27UANEQ5C", "MA4B66MW5E27UANEQ6Y", "MA4B66MW5E27UANEQ7R", "MA4B66MW5E27UANLXPZ", "MA4B66MW5E27UANLXU9", "MA4B66MW5E27UANLXV6", "MA4B66MW5E27UANLXVE", "MA4B66MW5E27UANLXX3", "MAG4QZNWK6BP6EMD", "MAXD3RE70PKQ4RCD", "MA4B66MW5E27UANLY2X", "MA4B66MW5E27UANLY4U", "MA4B66MW5E27UANLY65", "MA4B66MW5E27UANLY8B", "MA4B66MW5E27UANLY9M", "MA4B66MW5E27UANLY9V", "MA4B66MW5E27UANLYC7", "MA4B66MW5E27UANLYCY", "MA4B66MW5E27UANLYDU", "MA4B66MW5E27UANLYH6", "MA4B66MW5E27UANLYJN", "MA4B66MW5E27UANLYLD", "MA4B66MW5E27UANT857", "MA4B66MW5E27UANT86N", "MA4B66MW5E27UANT8BJ", "MA4B66MW5E27UANT8CC", "MA4B66MW5E27UANT8DU", "MA4B66MW5E27UANT8J9", "MA4B66MW5E27UANT8JA", "MA4B66MW5E27UANT8JQ", "MA4B66MW5E27UANT8ME", "MA4B66MW5E27UANT8PM", "MA4B66MW5E27UANT8PW", "MA4B66MW5E27UANT8RL", "MA4B66MW5E27UANT8S7", "MA4B66MW5E27UANT8T5", "MA4B66MW5E27UANT8U4", "MA4B66MW5E27UANT8VC", "MAS47GV2JKGY4JRN", "MA4B66MW5E27UANT8X5", "MA4B66MW5E27UANZGHH", "MA4B66MW5E27UANZGMJ", "MA4B66MW5E27UANZGMM", "MA4B66MW5E27UANZGMS", "MA4B66MW5E27UANZGNN", "MA4B66MW5E27UANZGSH", "MA4B66MW5E27UANZGWE", "MA4B66MW5E27UANZGXP", "MA4B66MW5E27UANZGXW", "MA4B66MW5E27UANZGZJ", "MAPYGC47FYWV3TX6", "MA4B66MW5E27UANZH2W", "MA4B66MW5E27UANZH3T", "MA4B66MW5E27UANZH4M", "MA4B66MW5E27UANZH4Z", "MA4B66MW5E27UANZH69", "MA4B66MW5E27UANZHAW", "MA4B66MW5E27UANZHC4", "MA4B66MW5E27UANZHEZ", "MA4B66MW5E27UAP7QP7" ], "underlyingAssetIds": [ "MA4B66MW5E27U9VBB6A", "MA4B66MW5E27U9VBB86", "MA4B66MW5E27U9VBB8U", "MA4B66MW5E27U9VBB94", "MA4B66MW5E27U9VBBAX", "MA4B66MW5E27U9VBBB7", "MA4B66MW5E27U9VBBDQ", "MA4B66MW5E27U9VBBEQ", "MA4B66MW5E27U9VBBKA", "MA4B66MW5E27U9XPV7X", "MA4B66MW5E27U9XPV9J", "MA4B66MW5E27U9XPVA7", "MA4B66MW5E27U9XPVB5", "MA4B66MW5E27U9XPVBM", "MA4B66MW5E27U9XPVBT", "MA4B66MW5E27U9XPVDL", "MA4B66MW5E27U9XPVFF", "MA4B66MW5E27U9XPVG2", "MA4B66MW5E27U9XPVJ9", "MA4B66MW5E27U9XPVMG", "MA4B66MW5E27U9XPVSE", "MA4B66MW5E27U9XPVUF", "MA4B66MW5E27U9XPVUQ", "MA4B66MW5E27U9XPVUY", "MA4B66MW5E27U9XPVVM", "MA4B66MW5E27U9XPVYM", "MA4B66MW5E27U9XPW44", "MA4B66MW5E27U9YGLZT", "MA4B66MW5E27U9YGM27", "MA4B66MW5E27U9YGM2A", "MA4B66MW5E27U9YGM8P", "MA4B66MW5E27U9YGM9V", "MAYT9CYW273Y0CSH", "MA4B66MW5E27U9YGMB9", "MA4B66MW5E27U9YGMBE", "MA4B66MW5E27U9YGMC8", "MA4B66MW5E27U9YGMCG", "MA4B66MW5E27U9YGMEE", "MA4B66MW5E27U9YGMFT", "MA4B66MW5E27U9YGMGY", "MA4B66MW5E27U9YGMJD", "MA4B66MW5E27U9YGMLB", "MA4B66MW5E27U9YGMLN", "MA4B66MW5E27U9YGMN3", "MA4B66MW5E27U9YGMNE", "MA4B66MW5E27U9YGMNV", "MA4B66MW5E27U9YGMPR", "MA4B66MW5E27U9YGMQF", "MACGYFW21RBZQQZ9", "MA4B66MW5E27U9YGMWY", "MAKTAXP90KNJ2S8R", "MA4B66MW5E27U9ZPCXW", "MA4B66MW5E27U9ZPCZW", "MA4B66MW5E27U9ZPD5Q", "MA4B66MW5E27U9ZPD6K", "MA4B66MW5E27U9ZPD9G", "MA4B66MW5E27U9ZPDA8", "MA4B66MW5E27U9ZPDCJ", "MA4B66MW5E27U9ZPDEW", "MA4B66MW5E27U9ZPDFX", "MA4B66MW5E27U9ZPDGH", "MA4B66MW5E27UA39HX5", "MA4B66MW5E27UA39HZZ", "MA4B66MW5E27UA39J2L", "MA4B66MW5E27UA39J6X", "MA4B66MW5E27UA39J95", "MA4B66MW5E27UA39JAW", "MA925YJVVGJR2NJ3", "MA4B66MW5E27UA39JGE", "MA4B66MW5E27UA39JL8", "MA5BN3N8QV6AX7AV", "MA4B66MW5E27UA39JQC", "MA4B66MW5E27UA39JQR", "MA4B66MW5E27UA4479J", "MA4B66MW5E27UA447JC", "MA4B66MW5E27UA447M6", "MA4B66MW5E27UA447SQ", "MA4B66MW5E27UA447Y5", "MA4B66MW5E27UA447ZM", "MA4B66MW5E27UA4ZCWT", "MA4B66MW5E27UA4ZCZT", "MA4B66MW5E27UA4ZD22", "MA4B66MW5E27UA4ZD7D", "MA4B66MW5E27UA4ZD8S", "MAJXV577W5XJCXEH", "MAEN7JQGGJN2RZ68", "MA4B66MW5E27UA4ZDCH", "MA4B66MW5E27UA4ZDF4", "MA4B66MW5E27UA4ZDFF", "MA4B66MW5E27UA7V8VS", "MA4B66MW5E27UABWUZU", "MA4B66MW5E27UACX5UW", "MA4B66MW5E27UACX5WP", "MA4B66MW5E27UACX5XY", "MA4B66MW5E27UACX5YP", "MA4B66MW5E27UACX66J", "MA4B66MW5E27UACX6AZ", "MA4B66MW5E27UACX6BL", "MA4B66MW5E27UACX6CL", "MA4B66MW5E27UACX6F9", "MA4B66MW5E27UACX6JS", "MA4B66MW5E27UACX6PS", "MA4B66MW5E27UACX6Q7", "MA4B66MW5E27UACX6QV", "MA4B66MW5E27UACX6R8", "MA4B66MW5E27UACX6RJ", "MA4B66MW5E27UACX6RZ", "MA4B66MW5E27UADJ545", "MA4B66MW5E27UADJ55W", "MA4B66MW5E27UADJ58F", "MA4B66MW5E27UADJ5B5", "MA4B66MW5E27UADJ5BB", "MA4B66MW5E27UADJ5ES", "MA4B66MW5E27UADJ5EZ", "MA4B66MW5E27UADJ5GE", "MA4B66MW5E27UADJ5GM", "MA4B66MW5E27UADJ5HN", "MA3SY5QZMQ0J6T31", "MA4B66MW5E27UADJ5LP", "MA4B66MW5E27UADJ5RL", "MA4B66MW5E27UADJ5VJ", "MA4B66MW5E27UADJ5ZC", "MA4B66MW5E27UADJ5ZS", "MA4B66MW5E27UAE4LJF", "MA4B66MW5E27UAE4LKU", "MA18K69WEMJ34B7N", "MA4B66MW5E27UAE4LLL", "MA4B66MW5E27UAE4LQC", "MA4B66MW5E27UAE4LQY", "MA4B66MW5E27UAE4LTR", "MA4B66MW5E27UAE4LXX", "MA4B66MW5E27UAE4LZE", "MA4B66MW5E27UAE4M69", "MA4B66MW5E27UAE4M76", "MA4B66MW5E27UAE4MBG", "MA4B66MW5E27UAE4MBN", "MA4B66MW5E27UAE4MDD", "MA4B66MW5E27UAE4MDN", "MA4B66MW5E27UAE4MDU", "MA4B66MW5E27UAE4MG6", "MA4B66MW5E27UAE4MG9", "MA4B66MW5E27UAE4MGB", "MA4B66MW5E27UAE4MGG", "MA4B66MW5E27UAEP3WH", "MA4B66MW5E27UAEP3WT", "MA4B66MW5E27UAEP43R", "MA4B66MW5E27UAEP43W", "MA4B66MW5E27UAEP46J", "MA4B66MW5E27UAEP46Y", "MA4B66MW5E27UAEP4AW", "MA4B66MW5E27UAEP4CG", "MA4B66MW5E27UAEP4D8", "MA4B66MW5E27UAEP4DZ", "MA113C0S76ZGPRG3", "MA4B66MW5E27UAEP4H9", "MA4B66MW5E27UAEP4JY", "MA4B66MW5E27UAEP4LY", "MA4B66MW5E27UAEP4NL", "MA4B66MW5E27UAEP4T6", "MA4B66MW5E27UAF9K4X", "MA4B66MW5E27UAF9K64", "MA4B66MW5E27UAF9KDG", "MA4B66MW5E27UAF9KE6", "MA4B66MW5E27UAF9KLB", "MA4B66MW5E27UAF9KPQ", "MA4B66MW5E27UAF9KPV", "MA4B66MW5E27UAF9KT3", "MA4B66MW5E27UAF9KVQ", "MA4B66MW5E27UAF9KXK", "MA4B66MW5E27UAF9KY5", "MA4B66MW5E27UAFU24E", "MAD7GGECMBDVDX3Y", "MA4B66MW5E27UAFU28S", "MA4B66MW5E27UAFU2AF", "MA4B66MW5E27UAFU2CB", "MA4B66MW5E27UAFU2CD", "MA4B66MW5E27UAFU2CS", "MA4B66MW5E27UAFU2E9", "MA4B66MW5E27UAFU2FS", "MA4B66MW5E27UAFU2G9", "MA4B66MW5E3VLSECN", "MA4B66MW5E27UAFU2H5", "MA4B66MW5E27UAFU2M3", "MA4B66MW5E27UAFU2QP", "MA4B66MW5E27UAFU2QR", "MA4B66MW5E27UAFU2TN", "MA4B66MW5E27UAFU2XL", "MA4B66MW5E27UAFU2XU", "MA4B66MW5E27UAFU2XX", "MA4B66MW5E27UAFU2ZQ", "MA4B66MW5E27UAFU32M", "MA4B66MW5E27UAFU32R", "MA4B66MW5E27UAGEHA3", "MA4B66MW5E27UAGEHKZ", "MA4B66MW5E27UAGEHL4", "MA4B66MW5E27UAGEHN3", "MA4B66MW5E27UAGEHQK", "MA4B66MW5E27UAGEHU2", "MA4B66MW5E27UAGEHUC", "MA2N6PA2AH2E3P7E", "MA4B66MW5E27UAGEJ85", "MA4B66MW5E27UAGYYE3", "MA4B66MW5E27UAGYYP2", "MA4B66MW5E27UAGYYPM", "MAWHMJBDGTNSC4T5", "MA4B66MW5E27UAGYYV5", "MA4B66MW5E27UAGYYVE", "MA4B66MW5E27UAGYZ49", "MA4B66MW5E27UAGYZ4D", "MA4B66MW5E27UAGYZ5E", "MA4B66MW5E27UAGYZ6J", "MA4B66MW5E27UAGYZ79", "MA4B66MW5E27UAGYZAL", "MA4B66MW5E27UAHKG34", "MA4B66MW5E27UAHKGBS", "MA4B66MW5E27UAHKGET", "MA4B66MW5E27UAHKGGT", "MA4B66MW5E27UAHKGJ3", "MA4B66MW5E27UAHKGJH", "MA4B66MW5E27UAHKGKQ", "MA4B66MW5E27UAHKGM2", "MA4B66MW5E27UAHKGMA", "MA4B66MW5E27UAHKGR3", "MA4B66MW5E27UAHKGS3", "MA4B66MW5E27UAHKGUS", "MA4B66MW5E27UAHKGUZ", "MA4B66MW5E27UAHKGZC", "MA4B66MW5E27UAJ5XAE", "MA4B66MW5E27UAJ5XAZ", "MA4B66MW5E27UAJ5XBD", "MA4B66MW5E27UAJ5XCF", "MA4B66MW5E27UAJ5XCU", "MA4B66MW5E27UAJ5XDG", "MA4B66MW5E27UAJ5XEB", "MA4B66MW5E27UAJ5XEZ", "MA4B66MW5E27UAJ5XGX", "MA4B66MW5E27UAJ5XHK", "MA4B66MW5E27UAJ5XHW", "MA4B66MW5E27UAJ5XLL", "MA4B66MW5E27UAJ5XUL", "MA4B66MW5E27UAJ5XW7", "MA4B66MW5E27UAJ5Y38", "MA4B66MW5E27UAJ5Y4J", "MA4B66MW5E27UAJ5Y5G", "MA4B66MW5E27UAJQEH8", "MA4B66MW5E27UAJQENQ", "MA9AKYCQX77VZ6WT", "MA4B66MW5E27UAJQETU", "MA4B66MW5E27UAJQEUR", "MA4B66MW5E27UAJQEXZ", "MA4B66MW5E27UAJQEYZ", "MA4B66MW5E27UAJQEZ7", "MAA0T8B3BF5SXH13", "MA4B66MW5E27UAJQF32", "MA4B66MW5E27UAJQF4M", "MA4B66MW5E27UAJQF86", "MA4B66MW5E27UAJQF95", "MA4B66MW5E27UAKHYZ6", "MA4B66MW5E27UAKHZ3C", "MA4B66MW5E27UAKHZ7P", "MA0YAYRD3SDTMZKN", "MA4B66MW5E27UAKHZ9D", "MA4B66MW5E27UAKHZ9H", "MA4B66MW5E27UAKHZBQ", "MA4B66MW5E27UAKHZDF", "MA4B66MW5E27UAKHZDM", "MA4B66MW5E27UAKHZFD", "MA4B66MW5E27UAKHZJE", "MA4B66MW5E27UAKHZJX", "MA4B66MW5E27UAKNZAY", "MA4B66MW5E27UAKNZB4", "MA4B66MW5E27UAKNZD3", "MA4B66MW5E27UAKNZDR", "MA4B66MW5E27UAKNZFS", "MA4B66MW5E27UAKNZHB", "MA4B66MW5E27UAKNZHN", "MA4B66MW5E27UAKNZJ4", "MA4B66MW5E27UAKNZKD", "MA4B66MW5E27UAKNZNV", "MA4B66MW5E27UAKNZSC", "MA4B66MW5E27UAKNZSM", "MA4B66MW5E27UAKNZWA", "MA4B66MW5E27UAKNZZH", "MA4B66MW5E27UAKP24Y", "MA4B66MW5E27UAKP26F", "MA4B66MW5E27UAKP26Y", "MA4B66MW5E27UAKV9M9", "MAXD496KP4AEDCSY", "MA4B66MW5E27UAKV9P7", "MA4B66MW5E27UAKV9RS", "MA4B66MW5E27UAKV9SY", "MA4B66MW5E27UAKV9UB", "MA4B66MW5E27UAKV9UK", "MAVBHXKMQX3EKHEK", "MA4B66MW5E27UAKV9Y4", "MA4B66MW5E27UAKVA2A", "MA4B66MW5E27UAKVA79", "MAA101TE9H0JH58S", "MA4B66MW5E27UAKVA9P", "MA4B66MW5E27UAKVAAK", "MA4B66MW5E27UAKVAB4", "MA4B66MW5E27UAKVABA", "MA4B66MW5E27UAKVABN", "MA4B66MW5E27UAKVAFJ", "MA4B66MW5E27UAKVAGH", "MA4B66MW5E27UAL3HZC", "MA4B66MW5E27UAL3J2S", "MA4B66MW5E27UAL3J38", "MA4B66MW5E27UAL3J3M", "MA4B66MW5E27UAL3J73", "MA4B66MW5E27UAL3J7Z", "MA4B66MW5E27UAL3JD3", "MA4B66MW5E27UAL3JHC", "MA4B66MW5E27UAL3JK8", "MA4B66MW5E27UAL3JR2", "MA4B66MW5E27UAL3JS2", "MA4B66MW5E27UAL9SAE", "MA4B66MW5E27UAL9SCK", "MA4B66MW5E27UAL9SDB", "MA4B66MW5E27UAL9SH5", "MA4B66MW5E27UAL9SHW", "MA4B66MW5E27UAL9SLW", "MA4B66MW5E27UAL9SMW", "MA4B66MW5E27UAL9SRL", "MA4B66MW5E27UAL9SSM", "MA4B66MW5E27UAL9STW", "MA4B66MW5E27UAL9SUH", "MA4B66MW5E27UAL9SUX", "MA4B66MW5E27UAL9SVE", "MA4B66MW5E27UAL9SY2", "MA4B66MW5E27UAL9SYE", "MA4B66MW5E27UAL9T4D", "MA4B66MW5E27UAL9T8M", "MA4B66MW5E27UALG2LD", "MA4B66MW5E27UALG2SS", "MAWG55F0PXD7A3J3", "MA4B66MW5E27UALG2W2", "MA4B66MW5E27UALG2XD", "MA4B66MW5E27UALG2XY", "MA4B66MW5E27UALG33R", "MA4B66MW5E27UALG36F", "MA4B66MW5E27UALG396", "MA4B66MW5E27UALG39P", "MA4B66MW5E27UALG3AG", "MA4B66MW5E27UALG3DQ", "MA4B66MW5E27UALG3GB", "MA4B66MW5E27UALNAVR", "MA4B66MW5E27UALNAX8", "MA4B66MW5E27UALNAZ5", "MA4B66MW5E27UALNB3B", "MA4B66MW5E27UALNB48", "MA4B66MW5E27UALNB5V", "MA3FCE91DAF1853R", "MA4B66MW5E27UALNB96", "MA4B66MW5E27UALNB98", "MA4B66MW5E27UALNBCT", "MA4B66MW5E27UALNBLL", "MA4B66MW5E27UALNBNP", "MA4B66MW5E27UALUK7V", "MA4B66MW5E27UALUK9H", "MA4B66MW5E27UALUKFW", "MA4B66MW5E27UALUKMS", "MA4B66MW5E27UALUKNA", "MA4B66MW5E27UALUKPX", "MA4B66MW5E27UALUKW2", "MAMDDXVJYX77V2K9", "MA4B66MW5E27UALUL23", "MADSMWFNMPEVFDHT", "MA4B66MW5E27UALUL3R", "MA4B66MW5E27UALUL62", "MA4B66MW5E27UAM2TMU", "MA4B66MW5E27UAM2TPA", "MA4B66MW5E27UAM2TTN", "MA4B66MW5E27UAM2TTS", "MA4B66MW5E27UAM2TVT", "MA4B66MW5E27UAM2TYL", "MA4B66MW5E27UAM2U3E", "MA4B66MW5E27UAM2U4V", "MA4B66MW5E27UAM2U5K", "MA4B66MW5E27UAM2UBD", "MA4B66MW5E27UAM2UBT", "MA4B66MW5E27UAM2UFX", "MA4B66MW5E27UAM2ULM", "MA4B66MW5E27UAM949R", "MA4B66MW5E27UAM94CR", "MA4B66MW5E27UAM94JY", "MA4B66MW5E27UAM94LK", "MA4B66MW5E27UAM94M4", "MA4B66MW5E27UAM94N9", "MA4B66MW5E27UAM94Q4", "MA4B66MW5E27UAM94U2", "MA4B66MW5E27UAM954L", "MA4B66MW5E27UAM957G", "MA6TWYY4GV60PQE8", "MA4B66MW5E27UAMFCRR", "MA4B66MW5E27UAMFCVT", "MA4B66MW5E27UAMFCYN", "MA4B66MW5E27UAMFD3Q", "MA4B66MW5E27UAMFD4L", "MA4B66MW5E27UAMFD6F", "MA4B66MW5E27UAMFDB3", "MA4B66MW5E27UAMFDB6", "MA4B66MW5E27UAMFDBN", "MA4B66MW5E27UAMFDCA", "MA4B66MW5E27UAMFDGJ", "MA4B66MW5E27UAMFDJQ", "MA4B66MW5E27UAMMMH7", "MA4B66MW5E27UAMMMLA", "MA4B66MW5E27UAMMMP9", "MA4B66MW5E27UAMMMWG", "MA4B66MW5E27UAMTVUE", "MA4B66MW5E27UAMTW23", "MA4B66MW5E27UAMTW2L", "MA4B66MW5E27UAMTW6M", "MA4B66MW5E27UAMTW7E", "MA4B66MW5E27UAMTWEY", "MA4B66MW5E27UAMTWHV", "MA4B66MW5E27UAMTWKJ", "MA4B66MW5E27UAN26EW", "MA6GDCTX2TDGN7JF", "MA4B66MW5E27UAN26ME", "MA4B66MW5E27UAN26XN", "MA4B66MW5E27UAN2739", "MA4B66MW5E27UAN273Y", "MA4B66MW5E27UAN2748", "MA4B66MW5E27UAN8F39", "MA4B66MW5E27UAN8F3C", "MA4B66MW5E27UAN8F6A", "MA4B66MW5E27UAN8F6H", "MA4B66MW5E27UAN8F6P", "MA4B66MW5E27UAN8F8F", "MA4B66MW5E27UAN8F94", "MA4B66MW5E27UAN8FCC", "MA4B66MW5E27UAN8FJT", "MA4B66MW5E27UAN8FMC", "MA4B66MW5E27UAN8FRR", "MA4B66MW5E27UAN8FTS", "MA4B66MW5E27UANEPBN", "MA4B66MW5E27UANEPER", "MA4B66MW5E27UANEPLL", "MAG5N56MVQD35PMS", "MAAHJ4JYCKZ6P025", "MA4B66MW5E27UANEPYQ", "MA4B66MW5E27UANEQ29", "MA4B66MW5E27UANEQ3V", "MA4B66MW5E27UANEQ5C", "MA4B66MW5E27UANEQ6Y", "MA4B66MW5E27UANEQ7R", "MA4B66MW5E27UANLXPZ", "MA4B66MW5E27UANLXU9", "MA4B66MW5E27UANLXV6", "MA4B66MW5E27UANLXVE", "MA4B66MW5E27UANLXX3", "MAG4QZNWK6BP6EMD", "MAXD3RE70PKQ4RCD", "MA4B66MW5E27UANLY2X", "MA4B66MW5E27UANLY4U", "MA4B66MW5E27UANLY65", "MA4B66MW5E27UANLY8B", "MA4B66MW5E27UANLY9M", "MA4B66MW5E27UANLY9V", "MA4B66MW5E27UANLYC7", "MA4B66MW5E27UANLYCY", "MA4B66MW5E27UANLYDU", "MA4B66MW5E27UANLYH6", "MA4B66MW5E27UANLYJN", "MA4B66MW5E27UANLYLD", "MA4B66MW5E27UANT857", "MA4B66MW5E27UANT86N", "MA4B66MW5E27UANT8BJ", "MA4B66MW5E27UANT8CC", "MA4B66MW5E27UANT8DU", "MA4B66MW5E27UANT8J9", "MA4B66MW5E27UANT8JA", "MA4B66MW5E27UANT8JQ", "MA4B66MW5E27UANT8ME", "MA4B66MW5E27UANT8PM", "MA4B66MW5E27UANT8PW", "MA4B66MW5E27UANT8RL", "MA4B66MW5E27UANT8S7", "MA4B66MW5E27UANT8T5", "MA4B66MW5E27UANT8U4", "MA4B66MW5E27UANT8VC", "MAS47GV2JKGY4JRN", "MA4B66MW5E27UANT8X5", "MA4B66MW5E27UANZGHH", "MA4B66MW5E27UANZGMJ", "MA4B66MW5E27UANZGMM", "MA4B66MW5E27UANZGMS", "MA4B66MW5E27UANZGNN", "MA4B66MW5E27UANZGSH", "MA4B66MW5E27UANZGWE", "MA4B66MW5E27UANZGXP", "MA4B66MW5E27UANZGXW", "MA4B66MW5E27UANZGZJ", "MAPYGC47FYWV3TX6", "MA4B66MW5E27UANZH2W", "MA4B66MW5E27UANZH3T", "MA4B66MW5E27UANZH4M", "MA4B66MW5E27UANZH4Z", "MA4B66MW5E27UANZH69", "MA4B66MW5E27UANZHAW", "MA4B66MW5E27UANZHC4", "MA4B66MW5E27UANZHEZ", "MA4B66MW5E27UAP7QP7" ], "xref": { "ric": ".SPX", "rcic": ".SPX", "eid": "0", "gsid": "100", "bbid": "SPX", "delisted": "no", "cusip": "648815108", "gss": "SPX", "isin": "US6488151084", "primeId": "1000043264", "ticker": "SPX", "wpk": "969003", "gsn": "152850", "secName": ".SPX", "mdapi": ".SPX" } } ================================================ FILE: gs_quant/test/resources/MA4J1YB8XZP2BPT8-xrefs.json ================================================ { "xrefs": [ { "startDate": "1952-01-01", "endDate": "2952-12-31", "identifiers": { "ric": "COP", "bbid": "COP", "secName": "COP", "mdapi": "COP", "dollarCross": "COP/USD" } } ] } ================================================ FILE: gs_quant/test/resources/MA4J1YB8XZP2BPT8.json ================================================ { "assetClass": "Cash", "classifications": {}, "createdById": "3549cd2c46c3468bae0d90b1f2b9e609", "createdTime": "2017-05-25T20:31:31.349Z", "currency": "COP", "description": "Peso (COP)", "domains": {}, "exchange": "", "id": "MA4J1YB8XZP2BPT8", "identifiers": [ { "type": "RIC", "value": "COP" }, { "type": "BID", "value": "COP" }, { "type": "SECNAME", "value": "COP" }, { "type": "MDAPI", "value": "COP" }, { "type": "DOLLARCROSS", "value": "COP/USD" } ], "lastUpdatedById": "3549cd2c46c3468bae0d90b1f2b9e609", "lastUpdatedTime": "2019-08-12T14:00:40.909Z", "listed": true, "name": "COP", "ownerId": "3549cd2c46c3468bae0d90b1f2b9e609", "parameters": {}, "rank": 0.9, "region": "Americas", "shortName": "COP", "styles": [], "tags": [], "type": "Currency", "xref": { "ric": "COP", "bbid": "COP", "secName": "COP", "mdapi": "COP", "dollarCross": "COP/USD" } } ================================================ FILE: gs_quant/test/resources/MA66CZBQJST05XKG-xrefs.json ================================================ { "xrefs": [ { "startDate": "1952-01-01", "endDate": "2952-12-31", "identifiers": { "ric": "GBP", "bbid": "GBP", "secName": "GBP", "mdapi": "GBP", "dollarCross": "USD/GBP" } } ] } ================================================ FILE: gs_quant/test/resources/MA66CZBQJST05XKG.json ================================================ { "assetClass": "Cash", "classifications": {}, "createdById": "3549cd2c46c3468bae0d90b1f2b9e609", "createdTime": "2017-05-25T20:31:33.511Z", "currency": "GBP", "description": "Pound Sterling (GBP)", "domains": {}, "exchange": "", "id": "MA66CZBQJST05XKG", "identifiers": [ { "type": "RIC", "value": "GBP" }, { "type": "DOLLARCROSS", "value": "USD/GBP" }, { "type": "BID", "value": "GBP" }, { "type": "SECNAME", "value": "GBP" }, { "type": "MDAPI", "value": "GBP" } ], "lastUpdatedById": "3549cd2c46c3468bae0d90b1f2b9e609", "lastUpdatedTime": "2019-08-12T14:00:42.532Z", "listed": true, "name": "GBP", "ownerId": "3549cd2c46c3468bae0d90b1f2b9e609", "parameters": { "g10Currency": true }, "rank": 1.0, "region": "Europe", "shortName": "GBP", "styles": [], "tags": [], "type": "Currency", "xref": { "ric": "GBP", "bbid": "GBP", "secName": "GBP", "mdapi": "GBP", "dollarCross": "USD/GBP" } } ================================================ FILE: gs_quant/test/resources/MAJ6SEQH3GT0GA2Z-xrefs.json ================================================ { "xrefs": [ { "startDate": "1952-01-01", "endDate": "2952-12-31", "identifiers": { "ric": "KRW", "bbid": "KRW", "secName": "KRW", "mdapi": "KRW", "dollarCross": "KRW/USD" } } ] } ================================================ FILE: gs_quant/test/resources/MAJ6SEQH3GT0GA2Z.json ================================================ { "assetClass": "Cash", "classifications": { }, "createdById": "3549cd2c46c3468bae0d90b1f2b9e609", "createdTime": "2017-05-25T20:31:38.17Z", "currency": "KRW", "description": "Won (KRW)", "exchange": "", "id": "MAJ6SEQH3GT0GA2Z", "identifiers": [ { "type": "SECNAME", "value": "KRW" }, { "type": "MDAPI", "value": "KRW" }, { "type": "RIC", "value": "KRW" }, { "type": "BID", "value": "KRW" }, { "type": "DOLLARCROSS", "value": "KRW/USD" } ], "lastUpdatedById": "3549cd2c46c3468bae0d90b1f2b9e609", "lastUpdatedTime": "2019-12-04T15:00:56.848Z", "listed": true, "name": "KRW", "ownerId": "3549cd2c46c3468bae0d90b1f2b9e609", "parameters": { }, "rank": 0.9, "region": "Asia", "shortName": "KRW", "styles": [ ], "tags": [ ], "type": "Currency", "xref": { "ric": "KRW", "bbid": "KRW", "secName": "KRW", "mdapi": "KRW", "dollarCross": "KRW/USD" } } ================================================ FILE: gs_quant/test/resources/MAJNQPFGN1EBDHAE-xrefs.json ================================================ { "xrefs": [ { "startDate": "1952-01-01", "endDate": "2952-12-31", "identifiers": { "ric": "EUR", "bbid": "EUR", "secName": "EUR", "mdapi": "EUR", "dollarCross": "USD/EUR" } } ] } ================================================ FILE: gs_quant/test/resources/MAJNQPFGN1EBDHAE.json ================================================ { "assetClass": "Cash", "classifications": {}, "createdById": "3549cd2c46c3468bae0d90b1f2b9e609", "createdTime": "2017-05-25T20:31:33.229Z", "currency": "EUR", "description": "Euro (EUR)", "domains": {}, "exchange": "", "id": "MAJNQPFGN1EBDHAE", "identifiers": [ { "type": "SECNAME", "value": "EUR" }, { "type": "RIC", "value": "EUR" }, { "type": "DOLLARCROSS", "value": "USD/EUR" }, { "type": "BID", "value": "EUR" }, { "type": "MDAPI", "value": "EUR" } ], "lastUpdatedById": "3549cd2c46c3468bae0d90b1f2b9e609", "lastUpdatedTime": "2019-08-12T14:00:42.312Z", "listed": true, "name": "EUR", "ownerId": "3549cd2c46c3468bae0d90b1f2b9e609", "parameters": { "g10Currency": true }, "rank": 1.0, "region": "Europe", "shortName": "EUR", "styles": [], "tags": [], "type": "Currency", "xref": { "ric": "EUR", "bbid": "EUR", "secName": "EUR", "mdapi": "EUR", "dollarCross": "USD/EUR" } } ================================================ FILE: gs_quant/test/resources/MAK1FHKH5P5GJSHH-xrefs.json ================================================ { "xrefs": [ { "startDate": "1952-01-01", "endDate": "2952-12-31", "identifiers": { "ric": "JPY", "bbid": "JPY", "secName": "JPY", "mdapi": "JPY", "dollarCross": "JPY/USD" } } ] } ================================================ FILE: gs_quant/test/resources/MAK1FHKH5P5GJSHH.json ================================================ { "assetClass": "Cash", "classifications": {}, "createdById": "3549cd2c46c3468bae0d90b1f2b9e609", "createdTime": "2017-05-25T20:31:37.107Z", "currency": "JPY", "description": "Yen (JPY)", "domains": {}, "exchange": "", "id": "MAK1FHKH5P5GJSHH", "identifiers": [ { "type": "DOLLARCROSS", "value": "JPY/USD" }, { "type": "RIC", "value": "JPY" }, { "type": "SECNAME", "value": "JPY" }, { "type": "MDAPI", "value": "JPY" }, { "type": "BID", "value": "JPY" } ], "lastUpdatedById": "3549cd2c46c3468bae0d90b1f2b9e609", "lastUpdatedTime": "2019-08-12T14:00:45.84Z", "listed": true, "name": "JPY", "ownerId": "3549cd2c46c3468bae0d90b1f2b9e609", "parameters": { "g10Currency": true }, "rank": 1.0, "region": "Asia", "shortName": "JPY", "styles": [], "tags": [], "type": "Currency", "xref": { "ric": "JPY", "bbid": "JPY", "secName": "JPY", "mdapi": "JPY", "dollarCross": "JPY/USD" } } ================================================ FILE: gs_quant/test/resources/MARCRZHY163GQ4H3.json ================================================ { "assetClass": "Equity", "classifications": { "riskCountryName": "United States", "riskCountryCode": "US", "countryName": "United States", "countryCode": "US", "isPrimary": false, "isCountryPrimary": false }, "createdById": "3549cd2c46c3468bae0d90b1f2b9e609", "createdTime": "2018-10-11T17:38:54.725Z", "currency": "USD", "description": "", "domains": {}, "entitlements": { "edit": [ "guid:3549cd2c46c3468bae0d90b1f2b9e609" ], "view": [ "internal", "guid:3549cd2c46c3468bae0d90b1f2b9e609", "external" ], "admin": [ "guid:3549cd2c46c3468bae0d90b1f2b9e609" ] }, "exchange": "NYSE", "id": "MARCRZHY163GQ4H3", "identifiers": [ { "type": "MDAPI", "value": "AAPL.N" }, { "type": "TKR", "value": "AAPL" }, { "type": "RCIC", "value": "AAPL.O" }, { "type": "BCID", "value": "AAPL US" }, { "type": "RIC", "value": "AAPL.N" }, { "type": "SECNAME", "value": "AAPL.N" }, { "type": "SED", "value": "2046251" }, { "type": "PRIMARY_COUNTRY_RIC", "value": "AAPL.OQ" }, { "type": "BID", "value": "AAPL UN" }, { "type": "GSID", "value": "14593" }, { "type": "DELISTED", "value": "no" }, { "type": "EID", "value": "154" } ], "lastUpdatedById": "3549cd2c46c3468bae0d90b1f2b9e609", "lastUpdatedTime": "2021-01-27T08:16:59.862Z", "listed": true, "name": "Apple Inc", "ownerId": "3549cd2c46c3468bae0d90b1f2b9e609", "parameters": { "exchangeCurrency": "USD", "pricingLocation": "NYC" }, "rank": 0.555613884804, "region": "Americas", "shortName": "AAPL", "styles": [], "tags": [ "Americas", "Equity", "Equity Option::OTC", "New York Stock Exchange", "NYC", "NYSE", "Single Stock", "United States", "US" ], "type": "Single Stock", "underliers": [], "underlyingAssetIds": [], "xref": { "ric": "AAPL.N", "rcic": "AAPL.O", "eid": "154", "gsid": "14593", "bbid": "AAPL UN", "bcid": "AAPL US", "delisted": "no", "cusip": "037833100", "isin": "US0378331005", "sedol": "2046251", "ticker": "AAPL", "secName": "AAPL.N", "mdapi": "AAPL.N", "primaryCountryRic": "AAPL.OQ" } } ================================================ FILE: gs_quant/test/resources/MASGTFZYNA7PMQ3H.json ================================================ { "assetClass": "Equity", "classifications": { "riskCountryName": "United States", "riskCountryCode": "US", "countryName": "United States", "countryCode": "US", "isPrimary": false, "isCountryPrimary": false }, "createdById": "3549cd2c46c3468bae0d90b1f2b9e609", "createdTime": "2019-06-28T10:44:12.133Z", "currency": "USD", "description": "", "domains": {}, "entitlements": { "admin": [ "guid:3549cd2c46c3468bae0d90b1f2b9e609" ], "view": [ "internal", "guid:3549cd2c46c3468bae0d90b1f2b9e609", "external" ], "edit": [ "guid:3549cd2c46c3468bae0d90b1f2b9e609" ] }, "exchange": "NYSE", "id": "MASGTFZYNA7PMQ3H", "identifiers": [ { "type": "GSID", "value": "84788" }, { "type": "PRIMARY_COUNTRY_RIC", "value": "AMZN.OQ" }, { "type": "SECNAME", "value": "AMZN.N" }, { "type": "BID", "value": "AMZN UN" }, { "type": "RIC", "value": "AMZN.N" }, { "type": "SED", "value": "2000019" }, { "type": "MDAPI", "value": "AMZN.N" }, { "type": "RCIC", "value": "AMZN.O" }, { "type": "BCID", "value": "AMZN US" }, { "type": "DELISTED", "value": "no" }, { "type": "EID", "value": "154" }, { "type": "TKR", "value": "AMZN" } ], "lastUpdatedById": "3549cd2c46c3468bae0d90b1f2b9e609", "lastUpdatedTime": "2021-01-27T01:33:52.43Z", "listed": true, "name": "Amazon.com Inc", "ownerId": "3549cd2c46c3468bae0d90b1f2b9e609", "parameters": { "exchangeCurrency": "USD", "pricingLocation": "NYC" }, "rank": 0.54598986364, "region": "Americas", "shortName": "AMZN", "styles": [], "tags": [ "Americas", "Equity", "Equity Option::OTC", "New York Stock Exchange", "NYC", "NYSE", "Single Stock", "United States", "US" ], "type": "Single Stock", "underliers": [], "underlyingAssetIds": [], "xref": { "ric": "AMZN.N", "rcic": "AMZN.O", "eid": "154", "gsid": "84788", "bbid": "AMZN UN", "bcid": "AMZN US", "delisted": "no", "cusip": "023135106", "isin": "US0231351067", "sedol": "2000019", "ticker": "AMZN", "secName": "AMZN.N", "mdapi": "AMZN.N", "primaryCountryRic": "AMZN.OQ" } } ================================================ FILE: gs_quant/test/resources/MATGYV0J9MPX534Z-xrefs.json ================================================ { "xrefs": [ { "startDate": "1952-01-01", "endDate": "2952-12-31", "identifiers": { "bbid": "USDJPY", "secName": "JPY/USD", "cross": "JPY/USD", "mdapi": "JPY/USD" } } ] } ================================================ FILE: gs_quant/test/resources/MATGYV0J9MPX534Z.json ================================================ { "assetClass": "FX", "classifications": {}, "createdById": "3549cd2c46c3468bae0d90b1f2b9e609", "createdTime": "2017-06-16T13:54:38.452Z", "currency": "JPY", "description": "Japan Yen / United States Dollar", "domains": {}, "exchange": "", "id": "MATGYV0J9MPX534Z", "identifiers": [ { "type": "CROSS", "value": "JPY/USD" }, { "type": "SECNAME", "value": "JPY/USD" }, { "type": "BID", "value": "USDJPY" }, { "type": "MDAPI", "value": "JPY/USD" } ], "lastUpdatedById": "3549cd2c46c3468bae0d90b1f2b9e609", "lastUpdatedTime": "2019-08-20T14:07:02.45Z", "listed": true, "name": "JPY/USD", "ownerId": "3549cd2c46c3468bae0d90b1f2b9e609", "parameters": {}, "rank": 1, "region": "Global", "shortName": "JPY/USD", "styles": [], "tags": [ "JPYUSD", "USDJPY", "Execution" ], "type": "Cross", "xref": { "bbid": "USDJPY", "secName": "JPY/USD", "cross": "JPY/USD", "mdapi": "JPY/USD" } } ================================================ FILE: gs_quant/test/resources/MAYJPCVVF2RWXCES-xrefs.json ================================================ { "xrefs": [ { "startDate": "1952-01-01", "endDate": "2952-12-31", "identifiers": { "bbid": "JPYUSD", "secName": "USD/JPY", "cross": "USD/JPY", "mdapi": "USD/JPY" } } ] } ================================================ FILE: gs_quant/test/resources/MAYJPCVVF2RWXCES.json ================================================ { "assetClass": "FX", "classifications": {}, "createdById": "3549cd2c46c3468bae0d90b1f2b9e609", "createdTime": "2018-03-22T14:09:20.124Z", "currency": "USD", "description": "United States Dollar / Japan Yen", "exchange": "", "id": "MAYJPCVVF2RWXCES", "identifiers": [ { "type": "BID", "value": "JPYUSD" }, { "type": "SECNAME", "value": "USD/JPY" }, { "type": "MDAPI", "value": "USD/JPY" }, { "type": "CROSS", "value": "USD/JPY" } ], "lastUpdatedById": "3549cd2c46c3468bae0d90b1f2b9e609", "lastUpdatedTime": "2019-08-15T14:09:53.942Z", "listed": true, "name": "USD/JPY", "ownerId": "3549cd2c46c3468bae0d90b1f2b9e609", "parameters": {}, "rank": 1, "region": "Global", "shortName": "USD/JPY", "styles": [], "tags": [ "USDJPY", "JPYUSD" ], "type": "Cross", "xref": { "bbid": "JPYUSD", "secName": "USD/JPY", "cross": "USD/JPY", "mdapi": "USD/JPY" } } ================================================ FILE: gs_quant/test/resources/MAZ7RWC904JYHYPS-xrefs.json ================================================ { "xrefs": [ { "startDate": "1952-01-01", "endDate": "2952-12-31", "identifiers": { "ric": "USD", "bbid": "USD", "secName": "USD", "mdapi": "USD", "dollarCross": "USD/USD" } } ] } ================================================ FILE: gs_quant/test/resources/MAZ7RWC904JYHYPS.json ================================================ { "assetClass": "Cash", "classifications": {}, "createdById": "3549cd2c46c3468bae0d90b1f2b9e609", "createdTime": "2017-05-25T20:31:53.586Z", "currency": "USD", "description": "Dollar (USD)", "domains": {}, "exchange": "", "id": "MAZ7RWC904JYHYPS", "identifiers": [ { "type": "RIC", "value": "USD" }, { "type": "DOLLARCROSS", "value": "USD/USD" }, { "type": "BID", "value": "USD" }, { "type": "SECNAME", "value": "USD" }, { "type": "MDAPI", "value": "USD" } ], "lastUpdatedById": "3549cd2c46c3468bae0d90b1f2b9e609", "lastUpdatedTime": "2019-08-12T14:00:56.881Z", "listed": true, "name": "USD", "ownerId": "3549cd2c46c3468bae0d90b1f2b9e609", "parameters": { "g10Currency": true }, "rank": 1.0, "region": "Americas", "shortName": "USD", "styles": [], "tags": [], "type": "Currency", "xref": { "ric": "USD", "bbid": "USD", "secName": "USD", "mdapi": "USD", "dollarCross": "USD/USD" } } ================================================ FILE: gs_quant/test/resources/MIDASER_SPX_USD.csv ================================================ Date,SPX,USD,MIDASER 2-Jan-19,2510.03,192.533473,2510.03 3-Jan-19,2447.8899,192.5463086,2447.7226 4-Jan-19,2531.9399,192.559145,2531.6037 7-Jan-19,2549.6899,192.5976569,2548.845 8-Jan-19,2574.4099,192.6104967,2573.3869 9-Jan-19,2584.96,192.6233374,2583.7612 10-Jan-19,2596.6399,192.636179,2595.2634 11-Jan-19,2596.26,192.6490214,2594.7107 14-Jan-19,2582.6101,192.6875512,2580.55 15-Jan-19,2610.3,192.700397,2608.0458 16-Jan-19,2616.1001,192.7132437,2613.667 17-Jan-19,2635.96,192.7260913,2633.3342 18-Jan-19,2670.71,192.7389397,2667.874 22-Jan-19,2632.8999,192.7903367,2629.3926 23-Jan-19,2638.7,192.8031894,2635.0097 24-Jan-19,2642.3301,192.8160429,2638.4591 25-Jan-19,2664.76,192.8288973,2660.6802 28-Jan-19,2643.8501,192.8674631,2639.2702 29-Jan-19,2640,192.880321,2635.2508 30-Jan-19,2681.05,192.8931796,2676.0513 31-Jan-19,2704.1001,192.9060392,2698.88 1-Feb-19,2706.53,192.9188996,2701.1253 4-Feb-19,2724.8701,192.9574834,2718.8886 5-Feb-19,2737.7,192.9703472,2731.509 6-Feb-19,2731.6101,192.9832119,2725.2508 7-Feb-19,2706.05,192.9960774,2699.5686 8-Feb-19,2707.8799,193.0089438,2701.214 11-Feb-19,2709.8,193.0475456,2702.5892 12-Feb-19,2744.73,193.0604155,2737.246 13-Feb-19,2753.03,193.0732862,2745.341 14-Feb-19,2745.73,193.0861577,2737.8783 15-Feb-19,2775.6001,193.0990301,2767.4805 19-Feb-19,2779.76,193.1505232,2770.8902 20-Feb-19,2784.7,193.1633999,2775.6297 21-Feb-19,2774.8799,193.1762775,2765.6565 22-Feb-19,2792.6699,193.1891559,2783.2031 25-Feb-19,2796.1101,193.2277937,2786.075 26-Feb-19,2793.8999,193.2406756,2783.687 27-Feb-19,2792.3799,193.2535583,2781.9869 28-Feb-19,2784.49,193.2664419,2773.9409 1-Mar-19,2803.6899,193.2793263,2792.8832 4-Mar-19,2792.8101,193.3179821,2781.4867 5-Mar-19,2789.6499,193.33087,2778.1539 6-Mar-19,2771.45,193.3437587,2759.8437 7-Mar-19,2748.9299,193.3566483,2737.234 8-Mar-19,2743.0701,193.3695388,2731.2166 11-Mar-19,2783.3,193.4082127,2770.7265 12-Mar-19,2791.52,193.4211066,2778.7246 13-Mar-19,2810.9199,193.4340013,2797.8504 14-Mar-19,2808.48,193.4468969,2795.2353 15-Mar-19,2822.48,193.4597934,2808.9829 18-Mar-19,2832.9399,193.4984853,2818.831 19-Mar-19,2832.5701,193.5113852,2818.2751 20-Mar-19,2824.23,193.524286,2809.7892 21-Mar-19,2854.8799,193.5372413,2840.0943 22-Mar-19,2800.71,193.5501976,2786.0148 25-Mar-19,2798.3601,193.5890689,2783.1177 26-Mar-19,2818.46,193.6019749,2802.9226 27-Mar-19,2805.3701,193.6148817,2789.718 28-Mar-19,2815.4399,193.6278431,2799.5449 29-Mar-19,2834.3999,193.6408054,2818.2104 1-Apr-19,2867.1899,193.6800177,2850.2425 2-Apr-19,2867.24,193.6929835,2850.1014 3-Apr-19,2873.3999,193.7059501,2856.0337 4-Apr-19,2879.3899,193.7189177,2861.7963 5-Apr-19,2892.74,193.7318861,2874.8732 8-Apr-19,2895.77,193.7707939,2877.3072 9-Apr-19,2878.2,193.7837658,2859.6565 10-Apr-19,2888.21,193.7967385,2869.4106 11-Apr-19,2888.3201,193.8097121,2869.3279 12-Apr-19,2907.4099,193.8226866,2888.1001 15-Apr-19,2905.5801,193.8616127,2885.7024 16-Apr-19,2907.0601,193.8745906,2886.9791 17-Apr-19,2900.45,193.8875695,2880.2214 18-Apr-19,2905.03,193.900603,2884.5759 22-Apr-19,2907.97,193.9531204,2886.7139 23-Apr-19,2933.6799,193.9662661,2912.0402 24-Apr-19,2927.25,193.9794127,2905.4604 25-Apr-19,2926.1699,193.9925602,2904.1914 26-Apr-19,2939.8799,194.0057086,2917.6015 29-Apr-19,2943.03,194.0451564,2920.1346 30-Apr-19,2945.8301,194.0583623,2922.7141 1-May-19,2923.73,194.071569,2900.5885 2-May-19,2917.52,194.0847767,2894.2303 3-May-19,2945.6399,194.0977696,2921.932 6-May-19,2932.47,194.1365891,2908.2837 7-May-19,2884.05,194.1495316,2860.0692 8-May-19,2879.4199,194.1624749,2855.2869 9-May-19,2870.72,194.1753651,2846.4703 10-May-19,2881.3999,194.1882023,2856.8718 13-May-19,2811.8701,194.2267163,2787.3673 14-May-19,2834.4099,194.2395568,2809.5264 15-May-19,2850.96,194.2523982,2825.7454 16-May-19,2876.3201,194.2653484,2850.6929 17-May-19,2859.53,194.2782454,2833.8632 20-May-19,2840.23,194.3169392,2814.1719 21-May-19,2864.3601,194.3298397,2837.8939 22-May-19,2856.27,194.342741,2829.6901 23-May-19,2822.24,194.3555892,2795.7897 24-May-19,2826.0601,194.3684383,2799.3891 28-May-19,2802.3899,194.4198379,2775.2021 29-May-19,2783.02,194.4327452,2755.8359 30-May-19,2788.8601,194.4456534,2761.436 31-May-19,2752.0601,194.4585625,2724.8144 3-Jun-19,2744.45,194.4974542,2716.7347 4-Jun-19,2803.27,194.5103126,2774.7812 5-Jun-19,2826.1499,194.5231719,2797.2451 6-Jun-19,2843.49,194.536032,2814.2229 7-Jun-19,2873.3401,194.548839,2843.5805 10-Jun-19,2886.73,194.5872624,2856.2701 11-Jun-19,2885.72,194.6000727,2855.0827 12-Jun-19,2879.8401,194.6128839,2849.0773 13-Jun-19,2891.6399,194.6256959,2860.5635 14-Jun-19,2886.98,194.6385088,2855.7653 17-Jun-19,2889.6699,194.6767877,2857.8646 18-Jun-19,2917.75,194.689658,2885.4466 19-Jun-19,2926.46,194.702475,2893.8702 20-Jun-19,2954.1799,194.715293,2921.091 21-Jun-19,2950.46,194.7281117,2917.2204 24-Jun-19,2945.3501,194.7667328,2911.5895 25-Jun-19,2917.3799,194.779609,2883.7474 26-Jun-19,2913.78,194.7924861,2879.9984 27-Jun-19,2924.9199,194.8053641,2890.8187 28-Jun-19,2941.76,194.8182429,2907.2714 1-Jul-19,2964.3301,194.8572065,2928.9954 2-Jul-19,2973.01,194.8701429,2937.3774 3-Jul-19,2995.8201,194.8831342,2959.7182 5-Jul-19,2990.4099,194.9092269,2953.977 8-Jul-19,2975.95,194.9485336,2939.0975 9-Jul-19,2979.6299,194.9615843,2942.5351 10-Jul-19,2993.0701,194.9746359,2955.611 11-Jul-19,2999.9099,194.9876884,2962.1673 12-Jul-19,3013.77,195.0006876,2975.6556 15-Jul-19,3014.3,195.0393627,2975.5887 16-Jul-19,3004.04,195.0523653,2965.2621 17-Jul-19,2984.4199,195.065423,2945.6968 18-Jul-19,2995.1101,195.0784815,2956.0511 19-Jul-19,2976.6101,195.091541,2937.5944 22-Jul-19,2985.03,195.1307218,2945.314 23-Jul-19,3005.47,195.1437306,2965.2856 24-Jul-19,3019.5601,195.1567401,2978.9897 25-Jul-19,3003.6699,195.1697506,2963.1144 26-Jul-19,3025.8601,195.1827619,2984.8075 29-Jul-19,3020.97,195.2217985,2979.3867 30-Jul-19,3013.1799,195.2348132,2971.5053 31-Jul-19,2980.3799,195.2477747,2938.9616 1-Aug-19,2953.5601,195.2607912,2912.3186 2-Aug-19,2932.05,195.2723984,2890.9358 5-Aug-19,2844.74,195.3072219,2804.3345 6-Aug-19,2881.77,195.3187776,2840.6726 7-Aug-19,2883.98,195.330334,2842.683 8-Aug-19,2938.0901,195.3418368,2895.8509 9-Aug-19,2918.6499,195.3533402,2876.5196 12-Aug-19,2883.0901,195.3878526,2840.9649 13-Aug-19,2926.3201,195.3993588,2883.396 14-Aug-19,2840.6001,195.4108657,2798.7635 15-Aug-19,2847.6001,195.4223732,2805.4956 16-Aug-19,2888.6799,195.4339357,2845.8021 19-Aug-19,2923.6499,195.4686252,2879.7478 20-Aug-19,2900.51,195.4801904,2856.785 21-Aug-19,2924.4299,195.4917564,2880.1753 22-Aug-19,2922.95,195.5032686,2878.5481 23-Aug-19,2847.1101,195.5147816,2803.6909 26-Aug-19,2878.3799,195.5493226,2833.9884 27-Aug-19,2869.1599,195.5608382,2824.7438 28-Aug-19,2887.9399,195.5723546,2843.0667 29-Aug-19,2924.5801,195.5838716,2878.9701 30-Aug-19,2926.46,195.5953894,2880.6511 3-Sep-19,2906.27,195.6416803,2860.0955 4-Sep-19,2937.78,195.6532557,2890.9356 5-Sep-19,2976,195.6648319,2928.3751 6-Sep-19,2978.71,195.6764087,2930.8685 9-Sep-19,2978.4299,195.7109782,2930.0751 10-Sep-19,2979.3899,195.7225578,2930.8462 11-Sep-19,3000.9299,195.734138,2951.8618 12-Sep-19,3009.5701,195.745719,2960.186 13-Sep-19,3007.3899,195.7573006,2957.8665 16-Sep-19,2997.96,195.7922106,2948.0644 17-Sep-19,3005.7,195.8044477,2955.4913 18-Sep-19,3006.73,195.8169574,2956.3153 19-Sep-19,3006.79,195.8291959,2956.1896 20-Sep-19,2992.0701,195.8395314,2941.5613 23-Sep-19,2991.78,195.8705393,2940.8104 24-Sep-19,2966.6001,195.8808769,2915.9042 25-Sep-19,2984.8701,195.8912151,2933.7082 26-Sep-19,2977.6201,195.9015538,2926.4276 27-Sep-19,2961.79,195.9116209,2910.7193 30-Sep-19,2976.74,195.9414975,2924.9676 1-Oct-19,2940.25,195.9518388,2888.9578 2-Oct-19,2887.6101,195.9620719,2837.0854 3-Oct-19,2910.6299,195.9721421,2859.5566 4-Oct-19,2952.01,195.9821041,2900.0652 7-Oct-19,2938.79,196.011828,2886.6381 8-Oct-19,2893.0601,196.0217375,2841.5737 9-Oct-19,2919.3999,196.0316475,2867.3011 10-Oct-19,2938.1299,196.041558,2885.5519 11-Oct-19,2970.27,196.051469,2916.971 14-Oct-19,2966.1499,196.0812034,2912.4824 15-Oct-19,2995.6799,196.0911149,2941.3309 16-Oct-19,2989.6899,196.1014642,2935.2943 17-Oct-19,2997.95,196.111814,2943.2491 18-Oct-19,2986.2,196.1218919,2931.5623 21-Oct-19,3006.72,196.1521274,2951.2549 22-Oct-19,2995.99,196.1622074,2940.5712 23-Oct-19,3004.52,196.172288,2948.7923 24-Oct-19,3010.29,196.1823691,2954.3038 25-Oct-19,3022.55,196.1924507,2966.184 28-Oct-19,3039.4199,196.22237,2982.2869 29-Oct-19,3036.8899,196.2323447,2979.6528 30-Oct-19,3046.77,196.2422653,2989.1961 31-Oct-19,3037.5601,196.2521864,2980.0091 1-Nov-19,3066.9099,196.2607997,3008.6721 4-Nov-19,3078.27,196.2864772,3019.4228 5-Nov-19,3074.6201,196.2949829,3015.7118 6-Nov-19,3076.78,196.303489,3017.6997 7-Nov-19,3085.1799,196.311941,3025.8084 8-Nov-19,3093.0801,196.3203933,3033.4262 11-Nov-19,3087.01,196.3457514,3027.0814 12-Nov-19,3091.8401,196.354204,3031.6874 13-Nov-19,3094.04,196.3626582,3033.714 14-Nov-19,3096.6299,196.3711127,3036.1227 15-Nov-19,3120.46,196.3795676,3059.3565 18-Nov-19,3122.03,196.4049332,3060.5006 19-Nov-19,3120.1799,196.4133896,3058.5552 20-Nov-19,3108.46,196.4218463,3046.935 21-Nov-19,3103.54,196.4303033,3041.9813 22-Nov-19,3110.29,196.4387607,3048.4664 25-Nov-19,3133.6399,196.4641341,3070.9584 26-Nov-19,3140.52,196.4725929,3077.5687 27-Nov-19,3153.6299,196.4810522,3090.2832 29-Nov-19,3140.98,196.4979714,3077.6213 2-Dec-19,3113.8701,196.5235161,3050.6582 3-Dec-19,3093.2,196.5320321,3030.2755 4-Dec-19,3112.76,196.5404939,3049.3072 5-Dec-19,3117.4299,196.5489561,3053.7506 6-Dec-19,3145.9099,196.5574186,3081.5173 9-Dec-19,3135.96,196.5828073,3071.373 10-Dec-19,3132.52,196.5912713,3067.8717 11-Dec-19,3141.6299,196.5997356,3076.6615 12-Dec-19,3168.5701,196.6082003,3102.9121 13-Dec-19,3168.8,196.6166654,3103.0037 16-Dec-19,3191.45,196.6420617,3124.7825 17-Dec-19,3192.52,196.6505829,3125.6948 18-Dec-19,3191.1399,196.6590498,3124.209 19-Dec-19,3205.3701,196.667517,3138.0062 20-Dec-19,3221.22,196.6759847,3153.3878 23-Dec-19,3224.01,196.7013886,3155.7118 24-Dec-19,3223.3799,196.7098577,3154.9592 26-Dec-19,3239.9099,196.7267966,3170.8667 27-Dec-19,3240.02,196.7352668,3170.8379 30-Dec-19,3221.29,196.7606785,3152.0983 31-Dec-19,3230.78,196.7691501,3161.2487 ================================================ FILE: gs_quant/test/resources/SPX_50_icorr_in.csv ================================================ date,assetId,impliedVolatility 2020-08-03,MA4B66MW5E27UADJ5VJ,32.059328320199995 2020-08-04,MA4B66MW5E27UADJ5VJ,31.1381564321 2020-08-05,MA4B66MW5E27UADJ5VJ,29.2136129051 2020-08-05,MA4B66MW5E27UADJ5VJ,-1 2020-08-06,MA4B66MW5E27UADJ5VJ,30.3667884444 2020-08-07,MA4B66MW5E27UADJ5VJ,32.4291242869 2020-08-10,MA4B66MW5E27UADJ5VJ,32.9822266526 2020-08-11,MA4B66MW5E27UADJ5VJ,34.2772999432 2020-08-12,MA4B66MW5E27UADJ5VJ,30.2372996096 2020-08-13,MA4B66MW5E27UADJ5VJ,27.605939028299996 2020-08-14,MA4B66MW5E27UADJ5VJ,24.428182949099998 2020-08-17,MA4B66MW5E27UADJ5VJ,23.8755262187 2020-08-18,MA4B66MW5E27UADJ5VJ,22.5281636941 2020-08-19,MA4B66MW5E27UADJ5VJ,22.108805182399998 2020-08-20,MA4B66MW5E27UADJ5VJ,21.538459422699997 2020-08-21,MA4B66MW5E27UADJ5VJ,21.4757360101 2020-08-24,MA4B66MW5E27UADJ5VJ,22.6950218659 2020-08-25,MA4B66MW5E27UADJ5VJ,22.247796161100002 2020-08-26,MA4B66MW5E27UADJ5VJ,23.3344421284 2020-08-27,MA4B66MW5E27UADJ5VJ,24.7550947989 2020-08-28,MA4B66MW5E27UADJ5VJ,24.4952536669 2020-08-31,MA4B66MW5E27UADJ5VJ,25.7882698628 2020-08-03,MA4B66MW5E27UAM94Q4,36.7219978252 2020-08-04,MA4B66MW5E27UAM94Q4,36.0504251494 2020-08-05,MA4B66MW5E27UAM94Q4,33.4754821133 2020-08-06,MA4B66MW5E27UAM94Q4,33.7053260305 2020-08-07,MA4B66MW5E27UAM94Q4,35.221356445000005 2020-08-10,MA4B66MW5E27UAM94Q4,36.226518267500005 2020-08-11,MA4B66MW5E27UAM94Q4,37.759121398 2020-08-12,MA4B66MW5E27UAM94Q4,34.762575207000005 2020-08-13,MA4B66MW5E27UAM94Q4,34.378342167300005 2020-08-14,MA4B66MW5E27UAM94Q4,33.7138370863 2020-08-17,MA4B66MW5E27UAM94Q4,34.2377308066 2020-08-18,MA4B66MW5E27UAM94Q4,33.856144619599995 2020-08-19,MA4B66MW5E27UAM94Q4,33.055326742000005 2020-08-20,MA4B66MW5E27UAM94Q4,33.0824224171 2020-08-21,MA4B66MW5E27UAM94Q4,32.6930826992 2020-08-24,MA4B66MW5E27UAM94Q4,34.071619079 2020-08-25,MA4B66MW5E27UAM94Q4,33.8970763465 2020-08-26,MA4B66MW5E27UAM94Q4,32.9884027106 2020-08-27,MA4B66MW5E27UAM94Q4,35.1474442816 2020-08-28,MA4B66MW5E27UAM94Q4,35.101873706 2020-08-31,MA4B66MW5E27UAM94Q4,37.4891468519 2020-08-03,MA4B66MW5E27UAGYZ49,29.1456583948 2020-08-04,MA4B66MW5E27UAGYZ49,26.9608117279 2020-08-05,MA4B66MW5E27UAGYZ49,25.6071079962 2020-08-06,MA4B66MW5E27UAGYZ49,25.0612661871 2020-08-07,MA4B66MW5E27UAGYZ49,26.62766323 2020-08-10,MA4B66MW5E27UAGYZ49,26.8936255337 2020-08-11,MA4B66MW5E27UAGYZ49,28.232559675699996 2020-08-12,MA4B66MW5E27UAGYZ49,25.291533856199997 2020-08-13,MA4B66MW5E27UAGYZ49,26.1217764764 2020-08-14,MA4B66MW5E27UAGYZ49,24.9849848264 2020-08-17,MA4B66MW5E27UAGYZ49,25.567022689699996 2020-08-18,MA4B66MW5E27UAGYZ49,25.3354011802 2020-08-19,MA4B66MW5E27UAGYZ49,26.154226908099997 2020-08-20,MA4B66MW5E27UAGYZ49,25.8813754409 2020-08-21,MA4B66MW5E27UAGYZ49,26.986504313300003 2020-08-24,MA4B66MW5E27UAGYZ49,29.2637307663 2020-08-25,MA4B66MW5E27UAGYZ49,29.1791387387 2020-08-26,MA4B66MW5E27UAGYZ49,31.5275620317 2020-08-27,MA4B66MW5E27UAGYZ49,34.2235967129 2020-08-28,MA4B66MW5E27UAGYZ49,34.3869695032 2020-08-31,MA4B66MW5E27UAGYZ49,34.5570456695 2020-08-03,MA4B66MW5E27UAL9SUX,34.1680490617 2020-08-04,MA4B66MW5E27UAL9SUX,32.8075487555 2020-08-05,MA4B66MW5E27UAL9SUX,32.0615452497 2020-08-06,MA4B66MW5E27UAL9SUX,31.218561953000002 2020-08-07,MA4B66MW5E27UAL9SUX,32.3056351071 2020-08-10,MA4B66MW5E27UAL9SUX,33.6702978619 2020-08-11,MA4B66MW5E27UAL9SUX,34.1252902831 2020-08-12,MA4B66MW5E27UAL9SUX,30.6693127289 2020-08-13,MA4B66MW5E27UAL9SUX,30.926053258000003 2020-08-14,MA4B66MW5E27UAL9SUX,30.354150672699998 2020-08-17,MA4B66MW5E27UAL9SUX,29.076861884 2020-08-18,MA4B66MW5E27UAL9SUX,28.205456857599998 2020-08-19,MA4B66MW5E27UAL9SUX,27.797024015100003 2020-08-20,MA4B66MW5E27UAL9SUX,27.395934708800002 2020-08-21,MA4B66MW5E27UAL9SUX,28.0160622736 2020-08-24,MA4B66MW5E27UAL9SUX,29.2955355307 2020-08-25,MA4B66MW5E27UAL9SUX,29.804381783400004 2020-08-26,MA4B66MW5E27UAL9SUX,33.4782620755 2020-08-27,MA4B66MW5E27UAL9SUX,39.0260854309 2020-08-28,MA4B66MW5E27UAL9SUX,38.2732494829 2020-08-31,MA4B66MW5E27UAL9SUX,37.0371481421 2020-08-03,MA4B66MW5E27UADJ5GE,20.401557227799998 2020-08-04,MA4B66MW5E27UADJ5GE,20.2195046835 2020-08-05,MA4B66MW5E27UADJ5GE,19.5893138288 2020-08-06,MA4B66MW5E27UADJ5GE,18.945448659700002 2020-08-07,MA4B66MW5E27UADJ5GE,19.6405853843 2020-08-10,MA4B66MW5E27UADJ5GE,19.0604368141 2020-08-11,MA4B66MW5E27UADJ5GE,20.659754315100002 2020-08-12,MA4B66MW5E27UADJ5GE,19.2148923255 2020-08-13,MA4B66MW5E27UADJ5GE,19.6469173267 2020-08-14,MA4B66MW5E27UADJ5GE,18.9680919495 2020-08-17,MA4B66MW5E27UADJ5GE,19.080799558200003 2020-08-18,MA4B66MW5E27UADJ5GE,18.0389663406 2020-08-19,MA4B66MW5E27UADJ5GE,17.2436853667 2020-08-20,MA4B66MW5E27UADJ5GE,18.023913295099998 2020-08-21,MA4B66MW5E27UADJ5GE,17.9412626181 2020-08-24,MA4B66MW5E27UADJ5GE,18.317182605 2020-08-25,MA4B66MW5E27UADJ5GE,22.5756406281 2020-08-26,MA4B66MW5E27UADJ5GE,22.6475106995 2020-08-27,MA4B66MW5E27UADJ5GE,25.536643363099998 2020-08-28,MA4B66MW5E27UADJ5GE,25.8082212341 2020-08-31,MA4B66MW5E27UADJ5GE,27.8059472778 2020-08-03,MA4B66MW5E27U9YGMCG,28.750922812000002 2020-08-04,MA4B66MW5E27U9YGMCG,27.8417855464 2020-08-05,MA4B66MW5E27U9YGMCG,26.1711340309 2020-08-06,MA4B66MW5E27U9YGMCG,25.1480231243 2020-08-07,MA4B66MW5E27U9YGMCG,25.044349535 2020-08-10,MA4B66MW5E27U9YGMCG,25.5365731857 2020-08-11,MA4B66MW5E27U9YGMCG,25.8161128186 2020-08-12,MA4B66MW5E27U9YGMCG,24.517971221099998 2020-08-13,MA4B66MW5E27U9YGMCG,26.1501389167 2020-08-14,MA4B66MW5E27U9YGMCG,25.1309204841 2020-08-17,MA4B66MW5E27U9YGMCG,23.6885894292 2020-08-18,MA4B66MW5E27U9YGMCG,24.2647261087 2020-08-19,MA4B66MW5E27U9YGMCG,23.1890781007 2020-08-20,MA4B66MW5E27U9YGMCG,22.9788667607 2020-08-21,MA4B66MW5E27U9YGMCG,22.5607911428 2020-08-24,MA4B66MW5E27U9YGMCG,24.6505958822 2020-08-25,MA4B66MW5E27U9YGMCG,26.2388496468 2020-08-26,MA4B66MW5E27U9YGMCG,24.696970594699998 2020-08-27,MA4B66MW5E27U9YGMCG,26.4963583283 2020-08-28,MA4B66MW5E27U9YGMCG,24.723809343200003 2020-08-31,MA4B66MW5E27U9YGMCG,27.4663329506 2020-08-03,MA4B66MW5E27UAM94N9,35.5762578374 2020-08-04,MA4B66MW5E27UAM94N9,34.6581451204 2020-08-05,MA4B66MW5E27UAM94N9,33.7279642642 2020-08-06,MA4B66MW5E27UAM94N9,32.3511735104 2020-08-07,MA4B66MW5E27UAM94N9,33.9350573662 2020-08-10,MA4B66MW5E27UAM94N9,36.020310728 2020-08-11,MA4B66MW5E27UAM94N9,36.6462112203 2020-08-12,MA4B66MW5E27UAM94N9,34.5540036974 2020-08-13,MA4B66MW5E27UAM94N9,35.8718509147 2020-08-14,MA4B66MW5E27UAM94N9,34.068479867200004 2020-08-17,MA4B66MW5E27UAM94N9,34.159764939 2020-08-18,MA4B66MW5E27UAM94N9,33.870072737099996 2020-08-19,MA4B66MW5E27UAM94N9,33.7571612558 2020-08-20,MA4B66MW5E27UAM94N9,32.2171786412 2020-08-21,MA4B66MW5E27UAM94N9,32.0128401092 2020-08-24,MA4B66MW5E27UAM94N9,33.1426632605 2020-08-25,MA4B66MW5E27UAM94N9,33.0084643372 2020-08-26,MA4B66MW5E27UAM94N9,33.853333242400005 2020-08-27,MA4B66MW5E27UAM94N9,34.6099481196 2020-08-28,MA4B66MW5E27UAM94N9,35.5220916582 2020-08-31,MA4B66MW5E27UAM94N9,38.132732167 2020-08-03,MA4B66MW5E27U9YGMGY,34.5578566661 2020-08-04,MA4B66MW5E27U9YGMGY,35.5214192081 2020-08-05,MA4B66MW5E27U9YGMGY,34.2422098496 2020-08-06,MA4B66MW5E27U9YGMGY,33.2989087816 2020-08-07,MA4B66MW5E27U9YGMGY,34.0442026669 2020-08-10,MA4B66MW5E27U9YGMGY,35.342590358100004 2020-08-11,MA4B66MW5E27U9YGMGY,35.2655857605 2020-08-12,MA4B66MW5E27U9YGMGY,31.7945415052 2020-08-13,MA4B66MW5E27U9YGMGY,33.2471989772 2020-08-14,MA4B66MW5E27U9YGMGY,31.5522337503 2020-08-17,MA4B66MW5E27U9YGMGY,31.888449962499998 2020-08-18,MA4B66MW5E27U9YGMGY,36.856801710300005 2020-08-19,MA4B66MW5E27U9YGMGY,35.4813341276 2020-08-20,MA4B66MW5E27U9YGMGY,34.0230280386 2020-08-21,MA4B66MW5E27U9YGMGY,35.015184809400004 2020-08-24,MA4B66MW5E27U9YGMGY,37.653164841999995 2020-08-25,MA4B66MW5E27U9YGMGY,38.9461283263 2020-08-26,MA4B66MW5E27U9YGMGY,40.5169251595 2020-08-27,MA4B66MW5E27U9YGMGY,40.6188899796 2020-08-28,MA4B66MW5E27U9YGMGY,39.4578448807 2020-08-31,MA4B66MW5E27U9YGMGY,39.6204268274 2020-08-03,MA4B66MW5E27U9XPV7X,31.7637510144 2020-08-04,MA4B66MW5E27U9XPV7X,31.729475946 2020-08-05,MA4B66MW5E27U9XPV7X,29.552933519000003 2020-08-06,MA4B66MW5E27U9XPV7X,28.3363353031 2020-08-07,MA4B66MW5E27U9XPV7X,31.627824818599997 2020-08-10,MA4B66MW5E27U9XPV7X,32.970957378600005 2020-08-11,MA4B66MW5E27U9XPV7X,31.7505290281 2020-08-12,MA4B66MW5E27U9XPV7X,30.0187164171 2020-08-13,MA4B66MW5E27U9XPV7X,30.276149750300004 2020-08-14,MA4B66MW5E27U9XPV7X,30.696568036 2020-08-17,MA4B66MW5E27U9XPV7X,36.2464071687 2020-08-18,MA4B66MW5E27U9XPV7X,35.1648187833 2020-08-19,MA4B66MW5E27U9XPV7X,35.3170811102 2020-08-20,MA4B66MW5E27U9XPV7X,35.1923072587 2020-08-21,MA4B66MW5E27U9XPV7X,36.9930326676 2020-08-24,MA4B66MW5E27U9XPV7X,40.7113665259 2020-08-25,MA4B66MW5E27U9XPV7X,40.0483053886 2020-08-26,MA4B66MW5E27U9XPV7X,49.2506555083 2020-08-27,MA4B66MW5E27U9XPV7X,48.4916363854 2020-08-28,MA4B66MW5E27U9XPV7X,49.144642165 2020-08-31,MA4B66MW5E27U9XPV7X,49.571312741 2020-08-03,MA4B66MW5E27UAGYZ4D,29.2417705031 2020-08-04,MA4B66MW5E27UAGYZ4D,27.0928538432 2020-08-05,MA4B66MW5E27UAGYZ4D,25.7215033896 2020-08-06,MA4B66MW5E27UAGYZ4D,25.163137567299998 2020-08-07,MA4B66MW5E27UAGYZ4D,26.6871658682 2020-08-10,MA4B66MW5E27UAGYZ4D,26.9875788221 2020-08-11,MA4B66MW5E27UAGYZ4D,27.9653812736 2020-08-12,MA4B66MW5E27UAGYZ4D,25.317280800000002 2020-08-13,MA4B66MW5E27UAGYZ4D,26.2410915389 2020-08-14,MA4B66MW5E27UAGYZ4D,25.0654286598 2020-08-17,MA4B66MW5E27UAGYZ4D,25.6465943257 2020-08-18,MA4B66MW5E27UAGYZ4D,25.393898211799996 2020-08-19,MA4B66MW5E27UAGYZ4D,26.1762826497 2020-08-20,MA4B66MW5E27UAGYZ4D,26.042070348200003 2020-08-21,MA4B66MW5E27UAGYZ4D,27.206719116099997 2020-08-24,MA4B66MW5E27UAGYZ4D,29.445558499300002 2020-08-25,MA4B66MW5E27UAGYZ4D,29.212714042299996 2020-08-26,MA4B66MW5E27UAGYZ4D,31.3985535068 2020-08-27,MA4B66MW5E27UAGYZ4D,34.0320744273 2020-08-28,MA4B66MW5E27UAGYZ4D,34.2097353203 2020-08-31,MA4B66MW5E27UAGYZ4D,34.3990043394 2020-08-03,MAMDDXVJYX77V2K9,20.0559973983 2020-08-04,MAMDDXVJYX77V2K9,19.1882402243 2020-08-05,MAMDDXVJYX77V2K9,18.9826647551 2020-08-06,MAMDDXVJYX77V2K9,18.8985025041 2020-08-07,MAMDDXVJYX77V2K9,18.644528184000002 2020-08-10,MAMDDXVJYX77V2K9,19.1852593875 2020-08-11,MAMDDXVJYX77V2K9,19.6258501179 2020-08-12,MAMDDXVJYX77V2K9,18.7599981537 2020-08-13,MAMDDXVJYX77V2K9,18.9027056041 2020-08-14,MAMDDXVJYX77V2K9,18.6918580502 2020-08-17,MAMDDXVJYX77V2K9,18.8202033654 2020-08-18,MAMDDXVJYX77V2K9,17.484061347999997 2020-08-19,MAMDDXVJYX77V2K9,17.3812125228 2020-08-20,MAMDDXVJYX77V2K9,16.4681256794 2020-08-21,MAMDDXVJYX77V2K9,16.5347880198 2020-08-24,MAMDDXVJYX77V2K9,16.3560645283 2020-08-25,MAMDDXVJYX77V2K9,16.4482772289 2020-08-26,MAMDDXVJYX77V2K9,17.0461712593 2020-08-27,MAMDDXVJYX77V2K9,18.7092109856 2020-08-28,MAMDDXVJYX77V2K9,18.7202910524 2020-08-31,MAMDDXVJYX77V2K9,19.8119432133 2020-08-03,MA4B66MW5E27U9ZPD6K,36.4867121817 2020-08-04,MA4B66MW5E27U9ZPD6K,36.9225727592 2020-08-05,MA4B66MW5E27U9ZPD6K,36.240892953300005 2020-08-06,MA4B66MW5E27U9ZPD6K,36.2600225168 2020-08-07,MA4B66MW5E27U9ZPD6K,36.577072672999996 2020-08-10,MA4B66MW5E27U9ZPD6K,37.277453833 2020-08-11,MA4B66MW5E27U9ZPD6K,38.3123213112 2020-08-12,MA4B66MW5E27U9ZPD6K,36.0881969522 2020-08-13,MA4B66MW5E27U9ZPD6K,36.2786288816 2020-08-14,MA4B66MW5E27U9ZPD6K,35.8483698081 2020-08-17,MA4B66MW5E27U9ZPD6K,35.7536470334 2020-08-18,MA4B66MW5E27U9ZPD6K,34.0484475231 2020-08-19,MA4B66MW5E27U9ZPD6K,35.391019102499996 2020-08-20,MA4B66MW5E27U9ZPD6K,34.648079290599995 2020-08-21,MA4B66MW5E27U9ZPD6K,35.5718386798 2020-08-24,MA4B66MW5E27U9ZPD6K,36.608911018200004 2020-08-25,MA4B66MW5E27U9ZPD6K,35.1995840517 2020-08-26,MA4B66MW5E27U9ZPD6K,35.3762560996 2020-08-27,MA4B66MW5E27U9ZPD6K,37.9244541985 2020-08-28,MA4B66MW5E27U9ZPD6K,39.5686299963 2020-08-31,MA4B66MW5E27U9ZPD6K,39.199405642200006 2020-08-03,MA4B66MW5E27UACX6QV,31.843997685499996 2020-08-04,MA4B66MW5E27UACX6QV,32.0034067603 2020-08-05,MA4B66MW5E27UACX6QV,25.020124978600002 2020-08-06,MA4B66MW5E27UACX6QV,25.0151206543 2020-08-07,MA4B66MW5E27UACX6QV,25.119677436499998 2020-08-10,MA4B66MW5E27UACX6QV,26.622269666300003 2020-08-11,MA4B66MW5E27UACX6QV,25.597900407500003 2020-08-12,MA4B66MW5E27UACX6QV,24.4059790784 2020-08-13,MA4B66MW5E27UACX6QV,24.069803634 2020-08-14,MA4B66MW5E27UACX6QV,23.6187288551 2020-08-17,MA4B66MW5E27UACX6QV,24.1328338197 2020-08-18,MA4B66MW5E27UACX6QV,22.4751137539 2020-08-19,MA4B66MW5E27UACX6QV,21.956449105 2020-08-20,MA4B66MW5E27UACX6QV,21.8415198682 2020-08-21,MA4B66MW5E27UACX6QV,21.541181253300003 2020-08-24,MA4B66MW5E27UACX6QV,22.591761142 2020-08-25,MA4B66MW5E27UACX6QV,22.8356618939 2020-08-26,MA4B66MW5E27UACX6QV,22.9075616186 2020-08-27,MA4B66MW5E27UACX6QV,23.9738902808 2020-08-28,MA4B66MW5E27UACX6QV,24.5889014779 2020-08-31,MA4B66MW5E27UACX6QV,26.3081430677 2020-08-03,MA4B66MW5E27UAJQETU,31.2958779849 2020-08-04,MA4B66MW5E27UAJQETU,31.1774888957 2020-08-05,MA4B66MW5E27UAJQETU,29.717673099800002 2020-08-06,MA4B66MW5E27UAJQETU,29.1845887952 2020-08-07,MA4B66MW5E27UAJQETU,29.818009539700004 2020-08-10,MA4B66MW5E27UAJQETU,29.7932355428 2020-08-11,MA4B66MW5E27UAJQETU,31.1385090925 2020-08-12,MA4B66MW5E27UAJQETU,29.069061797200003 2020-08-13,MA4B66MW5E27UAJQETU,28.932624282699997 2020-08-14,MA4B66MW5E27UAJQETU,28.639665327899998 2020-08-17,MA4B66MW5E27UAJQETU,27.3364607789 2020-08-18,MA4B66MW5E27UAJQETU,26.0934184394 2020-08-19,MA4B66MW5E27UAJQETU,25.7851791305 2020-08-20,MA4B66MW5E27UAJQETU,26.845933223099998 2020-08-21,MA4B66MW5E27UAJQETU,26.071731001699998 2020-08-24,MA4B66MW5E27UAJQETU,27.388305253900004 2020-08-25,MA4B66MW5E27UAJQETU,26.8343895983 2020-08-26,MA4B66MW5E27UAJQETU,27.1502517246 2020-08-27,MA4B66MW5E27UAJQETU,28.20586302 2020-08-28,MA4B66MW5E27UAJQETU,28.916651795999996 2020-08-31,MA4B66MW5E27UAJQETU,31.0803676915 2020-08-03,MA4B66MW5E27UANLXV6,29.754443238400004 2020-08-04,MA4B66MW5E27UANLXV6,28.771766027 2020-08-05,MA4B66MW5E27UANLXV6,26.691992675199998 2020-08-06,MA4B66MW5E27UANLXV6,26.4810209992 2020-08-07,MA4B66MW5E27UANLXV6,28.0820986721 2020-08-10,MA4B66MW5E27UANLXV6,26.8129507068 2020-08-11,MA4B66MW5E27UANLXV6,27.7313839781 2020-08-12,MA4B66MW5E27UANLXV6,26.571767940199997 2020-08-13,MA4B66MW5E27UANLXV6,26.633450403199998 2020-08-14,MA4B66MW5E27UANLXV6,25.9943995142 2020-08-17,MA4B66MW5E27UANLXV6,25.8289047108 2020-08-18,MA4B66MW5E27UANLXV6,24.6726829411 2020-08-19,MA4B66MW5E27UANLXV6,24.9603068877 2020-08-20,MA4B66MW5E27UANLXV6,24.6844680825 2020-08-21,MA4B66MW5E27UANLXV6,24.0102128403 2020-08-24,MA4B66MW5E27UANLXV6,25.738151163 2020-08-25,MA4B66MW5E27UANLXV6,24.6283719672 2020-08-26,MA4B66MW5E27UANLXV6,24.9287537292 2020-08-27,MA4B66MW5E27UANLXV6,26.6345255095 2020-08-28,MA4B66MW5E27UANLXV6,27.8917257758 2020-08-31,MA4B66MW5E27UANLXV6,29.074584828 2020-08-03,MA4B66MW5E27U9VBB94,37.468100511500005 2020-08-04,MA4B66MW5E27U9VBB94,36.1883608826 2020-08-05,MA4B66MW5E27U9VBB94,32.3832866741 2020-08-06,MA4B66MW5E27U9VBB94,34.4964729831 2020-08-07,MA4B66MW5E27U9VBB94,35.4857458694 2020-08-10,MA4B66MW5E27U9VBB94,37.6298824643 2020-08-11,MA4B66MW5E27U9VBB94,38.2465802305 2020-08-12,MA4B66MW5E27U9VBB94,34.248123887800006 2020-08-13,MA4B66MW5E27U9VBB94,34.8111188129 2020-08-14,MA4B66MW5E27U9VBB94,34.835343484999996 2020-08-17,MA4B66MW5E27U9VBB94,35.3589344361 2020-08-18,MA4B66MW5E27U9VBB94,33.7631287033 2020-08-19,MA4B66MW5E27U9VBB94,33.0732333554 2020-08-20,MA4B66MW5E27U9VBB94,33.946000602 2020-08-21,MA4B66MW5E27U9VBB94,42.1643940429 2020-08-24,MA4B66MW5E27U9VBB94,48.3583861664 2020-08-25,MA4B66MW5E27U9VBB94,45.2850552494 2020-08-26,MA4B66MW5E27U9VBB94,51.5901744123 2020-08-27,MA4B66MW5E27U9VBB94,51.809533946200006 2020-08-28,MA4B66MW5E27U9VBB94,47.5321270835 2020-08-31,MA4B66MW5E27U9VBB94,50.6165924077 2020-08-03,MA4B66MW5E27UALG33R,38.7239086454 2020-08-04,MA4B66MW5E27UALG33R,39.667337611 2020-08-05,MA4B66MW5E27UALG33R,37.3513540516 2020-08-06,MA4B66MW5E27UALG33R,36.6628776956 2020-08-07,MA4B66MW5E27UALG33R,38.5055696354 2020-08-10,MA4B66MW5E27UALG33R,38.640515226400005 2020-08-11,MA4B66MW5E27UALG33R,38.463823716200004 2020-08-12,MA4B66MW5E27UALG33R,35.8626995341 2020-08-13,MA4B66MW5E27UALG33R,35.333865636700004 2020-08-14,MA4B66MW5E27UALG33R,34.5106016957 2020-08-17,MA4B66MW5E27UALG33R,35.3142380325 2020-08-18,MA4B66MW5E27UALG33R,35.950621869100004 2020-08-19,MA4B66MW5E27UALG33R,35.2450472964 2020-08-20,MA4B66MW5E27UALG33R,35.2626699539 2020-08-21,MA4B66MW5E27UALG33R,36.045116842199995 2020-08-24,MA4B66MW5E27UALG33R,38.1173292286 2020-08-25,MA4B66MW5E27UALG33R,37.5986691812 2020-08-26,MA4B66MW5E27UALG33R,45.600495480199996 2020-08-27,MA4B66MW5E27UALG33R,46.2809934675 2020-08-28,MA4B66MW5E27UALG33R,48.088613627 2020-08-31,MA4B66MW5E27UALG33R,51.3712150268 2020-08-03,MA4B66MW5E27UALNB5V,51.396189203999995 2020-08-04,MA4B66MW5E27UALNB5V,51.9240540815 2020-08-05,MA4B66MW5E27UALNB5V,49.825516613699996 2020-08-06,MA4B66MW5E27UALNB5V,49.144508203 2020-08-07,MA4B66MW5E27UALNB5V,49.8829989145 2020-08-10,MA4B66MW5E27UALNB5V,51.524084640199995 2020-08-11,MA4B66MW5E27UALNB5V,53.1189094272 2020-08-12,MA4B66MW5E27UALNB5V,48.5769572062 2020-08-13,MA4B66MW5E27UALNB5V,48.9581254987 2020-08-14,MA4B66MW5E27UALNB5V,49.3537609474 2020-08-17,MA4B66MW5E27UALNB5V,51.514128738800004 2020-08-18,MA4B66MW5E27UALNB5V,52.165929915099994 2020-08-19,MA4B66MW5E27UALNB5V,49.644179013400006 2020-08-20,MA4B66MW5E27UALNB5V,42.2523439124 2020-08-21,MA4B66MW5E27UALNB5V,43.785864562 2020-08-24,MA4B66MW5E27UALNB5V,44.7914874659 2020-08-25,MA4B66MW5E27UALNB5V,44.2846139161 2020-08-26,MA4B66MW5E27UALNB5V,45.5856932102 2020-08-27,MA4B66MW5E27UALNB5V,46.028710601200004 2020-08-28,MA4B66MW5E27UALNB5V,48.136116465099995 2020-08-31,MA4B66MW5E27UALNB5V,50.934381928899995 2020-08-03,MA4B66MW5E3VLSECN,33.9928973676 2020-08-04,MA4B66MW5E3VLSECN,33.3814489368 2020-08-05,MA4B66MW5E3VLSECN,32.0293720404 2020-08-06,MA4B66MW5E3VLSECN,32.934977587000006 2020-08-07,MA4B66MW5E3VLSECN,35.380882954600004 2020-08-10,MA4B66MW5E3VLSECN,36.1823219553 2020-08-11,MA4B66MW5E3VLSECN,38.1554977693 2020-08-12,MA4B66MW5E3VLSECN,34.348157395399994 2020-08-13,MA4B66MW5E3VLSECN,34.2755859528 2020-08-14,MA4B66MW5E3VLSECN,32.9239926865 2020-08-17,MA4B66MW5E3VLSECN,32.115436391 2020-08-18,MA4B66MW5E3VLSECN,33.8428080462 2020-08-19,MA4B66MW5E3VLSECN,33.4221750488 2020-08-20,MA4B66MW5E3VLSECN,32.6232184369 2020-08-21,MA4B66MW5E3VLSECN,33.6482212241 2020-08-24,MA4B66MW5E3VLSECN,37.000303153800004 2020-08-25,MA4B66MW5E3VLSECN,38.089247678 2020-08-26,MA4B66MW5E3VLSECN,48.0859662453 2020-08-27,MA4B66MW5E3VLSECN,47.6446817255 2020-08-28,MA4B66MW5E3VLSECN,46.3252311785 2020-08-31,MA4B66MW5E3VLSECN,45.1785348792 2020-08-03,MA4B66MW5E27UANZH3T,35.6411527063 2020-08-04,MA4B66MW5E27UANZH3T,34.7524080393 2020-08-05,MA4B66MW5E27UANZH3T,32.9936615519 2020-08-06,MA4B66MW5E27UANZH3T,32.9235647577 2020-08-07,MA4B66MW5E27UANZH3T,32.330212198699996 2020-08-10,MA4B66MW5E27UANZH3T,33.506727977400004 2020-08-11,MA4B66MW5E27UANZH3T,33.4961302475 2020-08-12,MA4B66MW5E27UANZH3T,32.400005296799996 2020-08-13,MA4B66MW5E27UANZH3T,32.8898624712 2020-08-14,MA4B66MW5E27UANZH3T,32.152383006700006 2020-08-17,MA4B66MW5E27UANZH3T,32.5652729273 2020-08-18,MA4B66MW5E27UANZH3T,31.996800811900002 2020-08-19,MA4B66MW5E27UANZH3T,31.089815583500002 2020-08-20,MA4B66MW5E27UANZH3T,31.8035570157 2020-08-21,MA4B66MW5E27UANZH3T,31.8473613806 2020-08-24,MA4B66MW5E27UANZH3T,31.581647526899996 2020-08-25,MA4B66MW5E27UANZH3T,31.719156476299997 2020-08-26,MA4B66MW5E27UANZH3T,34.4734184335 2020-08-27,MA4B66MW5E27UANZH3T,34.5536886541 2020-08-28,MA4B66MW5E27UANZH3T,32.9437371361 2020-08-31,MA4B66MW5E27UANZH3T,35.613643340399996 2020-08-03,MA4B66MW5E27UANEPLL,25.139089122 2020-08-04,MA4B66MW5E27UANEPLL,25.150698356899998 2020-08-05,MA4B66MW5E27UANEPLL,24.1584027738 2020-08-06,MA4B66MW5E27UANEPLL,24.9641618596 2020-08-07,MA4B66MW5E27UANEPLL,25.341412918400003 2020-08-10,MA4B66MW5E27UANEPLL,25.3513411558 2020-08-11,MA4B66MW5E27UANEPLL,25.8658692829 2020-08-12,MA4B66MW5E27UANEPLL,23.482260213700002 2020-08-13,MA4B66MW5E27UANEPLL,23.346236611000002 2020-08-14,MA4B66MW5E27UANEPLL,23.1744797766 2020-08-17,MA4B66MW5E27UANEPLL,24.5398209532 2020-08-18,MA4B66MW5E27UANEPLL,22.5665064017 2020-08-19,MA4B66MW5E27UANEPLL,22.364983256400002 2020-08-20,MA4B66MW5E27UANEPLL,23.082373145000002 2020-08-21,MA4B66MW5E27UANEPLL,22.2379473742 2020-08-24,MA4B66MW5E27UANEPLL,23.2663973258 2020-08-25,MA4B66MW5E27UANEPLL,23.285434237300002 2020-08-26,MA4B66MW5E27UANEPLL,23.4152877664 2020-08-27,MA4B66MW5E27UANEPLL,26.5942269425 2020-08-28,MA4B66MW5E27UANEPLL,26.6252728622 2020-08-31,MA4B66MW5E27UANEPLL,27.342388446900003 2020-08-03,MA4B66MW5E27UAE4MDU,24.505095851100002 2020-08-04,MA4B66MW5E27UAE4MDU,25.0329102903 2020-08-05,MA4B66MW5E27UAE4MDU,24.0596781373 2020-08-06,MA4B66MW5E27UAE4MDU,23.288780716599998 2020-08-07,MA4B66MW5E27UAE4MDU,23.465785323400002 2020-08-10,MA4B66MW5E27UAE4MDU,24.4330040471 2020-08-11,MA4B66MW5E27UAE4MDU,25.5651388817 2020-08-12,MA4B66MW5E27UAE4MDU,22.8813215259 2020-08-13,MA4B66MW5E27UAE4MDU,22.8079565536 2020-08-14,MA4B66MW5E27UAE4MDU,23.0479463262 2020-08-17,MA4B66MW5E27UAE4MDU,22.2693035253 2020-08-18,MA4B66MW5E27UAE4MDU,22.095031107900002 2020-08-19,MA4B66MW5E27UAE4MDU,22.2446632405 2020-08-20,MA4B66MW5E27UAE4MDU,21.6534913037 2020-08-21,MA4B66MW5E27UAE4MDU,21.427762053 2020-08-24,MA4B66MW5E27UAE4MDU,22.5262361986 2020-08-25,MA4B66MW5E27UAE4MDU,22.2819609043 2020-08-26,MA4B66MW5E27UAE4MDU,22.978499809200002 2020-08-27,MA4B66MW5E27UAE4MDU,25.3602626772 2020-08-28,MA4B66MW5E27UAE4MDU,25.282556955300002 2020-08-31,MA4B66MW5E27UAE4MDU,26.805582766300002 2020-08-03,MA4B66MW5E27UANLY8B,28.5228367629 2020-08-04,MA4B66MW5E27UANLY8B,29.0592454822 2020-08-05,MA4B66MW5E27UANLY8B,26.6638178971 2020-08-06,MA4B66MW5E27UANLY8B,26.8480519955 2020-08-07,MA4B66MW5E27UANLY8B,25.5358153919 2020-08-10,MA4B66MW5E27UANLY8B,26.5836308594 2020-08-11,MA4B66MW5E27UANLY8B,27.2995611877 2020-08-12,MA4B66MW5E27UANLY8B,24.6692363016 2020-08-13,MA4B66MW5E27UANLY8B,24.7485727198 2020-08-14,MA4B66MW5E27UANLY8B,24.345126311 2020-08-17,MA4B66MW5E27UANLY8B,24.8039814369 2020-08-18,MA4B66MW5E27UANLY8B,24.969633882100002 2020-08-19,MA4B66MW5E27UANLY8B,24.6092396475 2020-08-20,MA4B66MW5E27UANLY8B,24.1993665316 2020-08-21,MA4B66MW5E27UANLY8B,23.6044185056 2020-08-24,MA4B66MW5E27UANLY8B,25.990883329699997 2020-08-25,MA4B66MW5E27UANLY8B,24.4653568898 2020-08-26,MA4B66MW5E27UANLY8B,26.0116211536 2020-08-27,MA4B66MW5E27UANLY8B,27.110237876 2020-08-28,MA4B66MW5E27UANLY8B,28.090477961299996 2020-08-31,MA4B66MW5E27UANLY8B,30.3762004837 2020-08-03,MA4B66MW5E27UAL3HZC,23.5823586025 2020-08-04,MA4B66MW5E27UAL3HZC,22.9006838729 2020-08-05,MA4B66MW5E27UAL3HZC,21.0717859389 2020-08-06,MA4B66MW5E27UAL3HZC,21.842555391599998 2020-08-07,MA4B66MW5E27UAL3HZC,20.8658220148 2020-08-10,MA4B66MW5E27UAL3HZC,21.9350080918 2020-08-11,MA4B66MW5E27UAL3HZC,21.7178012013 2020-08-12,MA4B66MW5E27UAL3HZC,20.239683142500002 2020-08-13,MA4B66MW5E27UAL3HZC,20.5630997462 2020-08-14,MA4B66MW5E27UAL3HZC,19.9036298606 2020-08-17,MA4B66MW5E27UAL3HZC,20.6888440331 2020-08-18,MA4B66MW5E27UAL3HZC,18.964981709699998 2020-08-19,MA4B66MW5E27UAL3HZC,19.2873246915 2020-08-20,MA4B66MW5E27UAL3HZC,18.8466240242 2020-08-21,MA4B66MW5E27UAL3HZC,19.2509020117 2020-08-24,MA4B66MW5E27UAL3HZC,19.1507536355 2020-08-25,MA4B66MW5E27UAL3HZC,19.1640011469 2020-08-26,MA4B66MW5E27UAL3HZC,19.1244248922 2020-08-27,MA4B66MW5E27UAL3HZC,21.1390399298 2020-08-28,MA4B66MW5E27UAL3HZC,21.3412101671 2020-08-31,MA4B66MW5E27UAL3HZC,23.075075317499998 2020-08-03,MA4B66MW5E27UALG2XD,26.7674395023 2020-08-04,MA4B66MW5E27UALG2XD,24.7267350319 2020-08-05,MA4B66MW5E27UALG2XD,24.3604730578 2020-08-06,MA4B66MW5E27UALG2XD,24.0914133816 2020-08-07,MA4B66MW5E27UALG2XD,24.1266652282 2020-08-10,MA4B66MW5E27UALG2XD,24.9156446474 2020-08-11,MA4B66MW5E27UALG2XD,25.744241676500003 2020-08-12,MA4B66MW5E27UALG2XD,23.3606851368 2020-08-13,MA4B66MW5E27UALG2XD,23.9814849659 2020-08-14,MA4B66MW5E27UALG2XD,23.8306298558 2020-08-17,MA4B66MW5E27UALG2XD,24.413215838499998 2020-08-18,MA4B66MW5E27UALG2XD,23.2287693023 2020-08-19,MA4B66MW5E27UALG2XD,22.1373117554 2020-08-20,MA4B66MW5E27UALG2XD,22.2776889636 2020-08-21,MA4B66MW5E27UALG2XD,21.562953987100002 2020-08-24,MA4B66MW5E27UALG2XD,22.180796536 2020-08-25,MA4B66MW5E27UALG2XD,21.9615484325 2020-08-26,MA4B66MW5E27UALG2XD,22.0708755496 2020-08-27,MA4B66MW5E27UALG2XD,23.4057656619 2020-08-28,MA4B66MW5E27UALG2XD,24.7083501038 2020-08-31,MA4B66MW5E27UALG2XD,25.6887668733 2020-08-03,MA4B66MW5E27UALUL3R,18.4741481258 2020-08-04,MA4B66MW5E27UALUL3R,17.797150016499998 2020-08-05,MA4B66MW5E27UALUL3R,17.5807326822 2020-08-06,MA4B66MW5E27UALUL3R,17.1277428042 2020-08-07,MA4B66MW5E27UALUL3R,16.608196793999998 2020-08-10,MA4B66MW5E27UALUL3R,16.6104951717 2020-08-11,MA4B66MW5E27UALUL3R,17.8284357076 2020-08-12,MA4B66MW5E27UALUL3R,16.2645218723 2020-08-13,MA4B66MW5E27UALUL3R,17.4443682423 2020-08-14,MA4B66MW5E27UALUL3R,15.7958838075 2020-08-17,MA4B66MW5E27UALUL3R,16.474003555899998 2020-08-18,MA4B66MW5E27UALUL3R,15.782797406599999 2020-08-19,MA4B66MW5E27UALUL3R,15.458176316300001 2020-08-20,MA4B66MW5E27UALUL3R,15.5540339526 2020-08-21,MA4B66MW5E27UALUL3R,14.648782648600001 2020-08-24,MA4B66MW5E27UALUL3R,14.6924542067 2020-08-25,MA4B66MW5E27UALUL3R,14.5957329661 2020-08-26,MA4B66MW5E27UALUL3R,16.134286435699998 2020-08-27,MA4B66MW5E27UALUL3R,19.2698102812 2020-08-28,MA4B66MW5E27UALUL3R,17.5722900584 2020-08-31,MA4B66MW5E27UALUL3R,19.9886195223 2020-08-03,MA4B66MW5E27UAE4LQY,36.3024000067 2020-08-04,MA4B66MW5E27UAE4LQY,35.838158459 2020-08-05,MA4B66MW5E27UAE4LQY,32.9574461355 2020-08-06,MA4B66MW5E27UAE4LQY,32.7940476067 2020-08-07,MA4B66MW5E27UAE4LQY,32.6716410653 2020-08-10,MA4B66MW5E27UAE4LQY,31.524406645800003 2020-08-11,MA4B66MW5E27UAE4LQY,32.6780861162 2020-08-12,MA4B66MW5E27UAE4LQY,30.9870352807 2020-08-13,MA4B66MW5E27UAE4LQY,31.118571339899997 2020-08-14,MA4B66MW5E27UAE4LQY,30.6190578456 2020-08-17,MA4B66MW5E27UAE4LQY,30.7698312555 2020-08-18,MA4B66MW5E27UAE4LQY,31.730474000199997 2020-08-19,MA4B66MW5E27UAE4LQY,31.0714736909 2020-08-20,MA4B66MW5E27UAE4LQY,31.9261769097 2020-08-21,MA4B66MW5E27UAE4LQY,31.4681907851 2020-08-24,MA4B66MW5E27UAE4LQY,30.848221583 2020-08-25,MA4B66MW5E27UAE4LQY,31.045989782400003 2020-08-26,MA4B66MW5E27UAE4LQY,31.9361614137 2020-08-27,MA4B66MW5E27UAE4LQY,32.9621040749 2020-08-28,MA4B66MW5E27UAE4LQY,32.4920199695 2020-08-31,MA4B66MW5E27UAE4LQY,35.2689287702 2020-08-03,MA4B66MW5E27UAN8F94,22.4036255061 2020-08-04,MA4B66MW5E27UAN8F94,21.6133590936 2020-08-05,MA4B66MW5E27UAN8F94,21.265036998 2020-08-06,MA4B66MW5E27UAN8F94,21.062446003999998 2020-08-07,MA4B66MW5E27UAN8F94,21.0775131915 2020-08-10,MA4B66MW5E27UAN8F94,21.0826663872 2020-08-11,MA4B66MW5E27UAN8F94,20.942080422100002 2020-08-12,MA4B66MW5E27UAN8F94,20.1868293281 2020-08-13,MA4B66MW5E27UAN8F94,20.3317560994 2020-08-14,MA4B66MW5E27UAN8F94,19.2271290754 2020-08-17,MA4B66MW5E27UAN8F94,19.8119446167 2020-08-18,MA4B66MW5E27UAN8F94,19.8975026926 2020-08-19,MA4B66MW5E27UAN8F94,19.0150142628 2020-08-20,MA4B66MW5E27UAN8F94,19.223531327 2020-08-21,MA4B66MW5E27UAN8F94,16.6208159404 2020-08-24,MA4B66MW5E27UAN8F94,17.6179262944 2020-08-25,MA4B66MW5E27UAN8F94,17.906143431 2020-08-26,MA4B66MW5E27UAN8F94,18.035044802 2020-08-27,MA4B66MW5E27UAN8F94,18.3256686211 2020-08-28,MA4B66MW5E27UAN8F94,18.4730921361 2020-08-31,MA4B66MW5E27UAN8F94,19.322078052200002 2020-08-03,MA4B66MW5E27UA447M6,21.4894692479 2020-08-04,MA4B66MW5E27UA447M6,20.9414316194 2020-08-05,MA4B66MW5E27UA447M6,20.2947606177 2020-08-06,MA4B66MW5E27UA447M6,20.7917334953 2020-08-07,MA4B66MW5E27UA447M6,20.9421847986 2020-08-10,MA4B66MW5E27UA447M6,19.313511427599998 2020-08-11,MA4B66MW5E27UA447M6,19.998069791 2020-08-12,MA4B66MW5E27UA447M6,19.2484790361 2020-08-13,MA4B66MW5E27UA447M6,18.2138903643 2020-08-14,MA4B66MW5E27UA447M6,18.2548992505 2020-08-17,MA4B66MW5E27UA447M6,17.8738186159 2020-08-18,MA4B66MW5E27UA447M6,17.421443186999998 2020-08-19,MA4B66MW5E27UA447M6,17.2270412059 2020-08-20,MA4B66MW5E27UA447M6,17.2809844367 2020-08-21,MA4B66MW5E27UA447M6,17.0496380858 2020-08-24,MA4B66MW5E27UA447M6,18.062397313 2020-08-25,MA4B66MW5E27UA447M6,18.0358645929 2020-08-26,MA4B66MW5E27UA447M6,18.6675150585 2020-08-27,MA4B66MW5E27UA447M6,20.3467679317 2020-08-28,MA4B66MW5E27UA447M6,19.0504969773 2020-08-31,MA4B66MW5E27UA447M6,20.137281891700002 2020-08-03,MA4B66MW5E27UAKHZDF,19.9454962029 2020-08-04,MA4B66MW5E27UAKHZDF,20.1585503679 2020-08-05,MA4B66MW5E27UAKHZDF,20.0076011567 2020-08-06,MA4B66MW5E27UAKHZDF,19.4002195135 2020-08-07,MA4B66MW5E27UAKHZDF,19.2087737469 2020-08-10,MA4B66MW5E27UAKHZDF,18.8727693245 2020-08-11,MA4B66MW5E27UAKHZDF,20.3621613292 2020-08-12,MA4B66MW5E27UAKHZDF,18.0389599485 2020-08-13,MA4B66MW5E27UAKHZDF,19.0979309806 2020-08-14,MA4B66MW5E27UAKHZDF,18.7637469535 2020-08-17,MA4B66MW5E27UAKHZDF,18.5477915214 2020-08-18,MA4B66MW5E27UAKHZDF,17.929260668999998 2020-08-19,MA4B66MW5E27UAKHZDF,17.339471187 2020-08-20,MA4B66MW5E27UAKHZDF,17.6429490391 2020-08-21,MA4B66MW5E27UAKHZDF,17.547851514999998 2020-08-24,MA4B66MW5E27UAKHZDF,17.8161956543 2020-08-25,MA4B66MW5E27UAKHZDF,17.0150457507 2020-08-26,MA4B66MW5E27UAKHZDF,17.3423072617 2020-08-27,MA4B66MW5E27UAKHZDF,18.9377196739 2020-08-28,MA4B66MW5E27UAKHZDF,19.1913961485 2020-08-31,MA4B66MW5E27UAKHZDF,20.2893506367 2020-08-03,MA4B66MW5E27UALUL23,26.5448274577 2020-08-04,MA4B66MW5E27UALUL23,26.1022782303 2020-08-05,MA4B66MW5E27UALUL23,25.3577981602 2020-08-06,MA4B66MW5E27UALUL23,24.8376179052 2020-08-07,MA4B66MW5E27UALUL23,24.0732237402 2020-08-10,MA4B66MW5E27UALUL23,23.8129337777 2020-08-11,MA4B66MW5E27UALUL23,23.3450276628 2020-08-12,MA4B66MW5E27UALUL23,21.8552219363 2020-08-13,MA4B66MW5E27UALUL23,21.7213835639 2020-08-14,MA4B66MW5E27UALUL23,21.497699708000003 2020-08-17,MA4B66MW5E27UALUL23,21.942549028200002 2020-08-18,MA4B66MW5E27UALUL23,21.663491859 2020-08-19,MA4B66MW5E27UALUL23,20.9729282382 2020-08-20,MA4B66MW5E27UALUL23,21.1176966499 2020-08-21,MA4B66MW5E27UALUL23,21.477425198299997 2020-08-24,MA4B66MW5E27UALUL23,22.1056341557 2020-08-25,MA4B66MW5E27UALUL23,22.4447824493 2020-08-26,MA4B66MW5E27UALUL23,22.656046648 2020-08-27,MA4B66MW5E27UALUL23,22.8775383698 2020-08-28,MA4B66MW5E27UALUL23,25.641954427 2020-08-31,MA4B66MW5E27UALUL23,26.6658748762 2020-08-03,MA4B66MW5E27UAL9SRL,24.9173619643 2020-08-04,MA4B66MW5E27UAL9SRL,24.126289175900002 2020-08-05,MA4B66MW5E27UAL9SRL,23.8841194908 2020-08-06,MA4B66MW5E27UAL9SRL,23.7063995774 2020-08-07,MA4B66MW5E27UAL9SRL,23.3118307732 2020-08-10,MA4B66MW5E27UAL9SRL,23.5050134228 2020-08-11,MA4B66MW5E27UAL9SRL,24.5261028019 2020-08-12,MA4B66MW5E27UAL9SRL,22.2677843293 2020-08-13,MA4B66MW5E27UAL9SRL,21.4280714845 2020-08-14,MA4B66MW5E27UAL9SRL,21.8661350748 2020-08-17,MA4B66MW5E27UAL9SRL,21.0849401218 2020-08-18,MA4B66MW5E27UAL9SRL,21.6488210145 2020-08-19,MA4B66MW5E27UAL9SRL,20.4852583651 2020-08-20,MA4B66MW5E27UAL9SRL,21.109056659100002 2020-08-21,MA4B66MW5E27UAL9SRL,20.6586070209 2020-08-24,MA4B66MW5E27UAL9SRL,20.0260930283 2020-08-25,MA4B66MW5E27UAL9SRL,20.6342759558 2020-08-26,MA4B66MW5E27UAL9SRL,21.7335557338 2020-08-27,MA4B66MW5E27UAL9SRL,22.833762025400002 2020-08-28,MA4B66MW5E27UAL9SRL,22.252730314399997 2020-08-31,MA4B66MW5E27UAL9SRL,23.9221363685 2020-08-03,MA4B66MW5E27UANLY9V,29.0926614664 2020-08-04,MA4B66MW5E27UANLY9V,27.1753803422 2020-08-05,MA4B66MW5E27UANLY9V,26.340227126299997 2020-08-06,MA4B66MW5E27UANLY9V,26.492642310100003 2020-08-07,MA4B66MW5E27UANLY9V,26.091224021 2020-08-10,MA4B66MW5E27UANLY9V,28.2296175738 2020-08-11,MA4B66MW5E27UANLY9V,29.104670586199997 2020-08-12,MA4B66MW5E27UANLY9V,26.582752685 2020-08-13,MA4B66MW5E27UANLY9V,26.0813260655 2020-08-14,MA4B66MW5E27UANLY9V,26.571681709599996 2020-08-17,MA4B66MW5E27UANLY9V,27.1694201692 2020-08-18,MA4B66MW5E27UANLY9V,26.4298465606 2020-08-19,MA4B66MW5E27UANLY9V,25.9025990591 2020-08-20,MA4B66MW5E27UANLY9V,26.0831680534 2020-08-21,MA4B66MW5E27UANLY9V,24.7827442528 2020-08-24,MA4B66MW5E27UANLY9V,25.8037822051 2020-08-25,MA4B66MW5E27UANLY9V,25.4856423664 2020-08-26,MA4B66MW5E27UANLY9V,26.113407316900002 2020-08-27,MA4B66MW5E27UANLY9V,28.759383235399998 2020-08-28,MA4B66MW5E27UANLY9V,29.0188941318 2020-08-31,MA4B66MW5E27UANLY9V,29.2813205783 2020-08-03,MA4B66MW5E27UANLYJN,26.522416179199997 2020-08-04,MA4B66MW5E27UANLYJN,25.627622172 2020-08-05,MA4B66MW5E27UANLYJN,24.0553617128 2020-08-06,MA4B66MW5E27UANLYJN,24.7123721814 2020-08-07,MA4B66MW5E27UANLYJN,24.201565530099998 2020-08-10,MA4B66MW5E27UANLYJN,25.233288695 2020-08-11,MA4B66MW5E27UANLYJN,26.5285911264 2020-08-12,MA4B66MW5E27UANLYJN,24.42572098 2020-08-13,MA4B66MW5E27UANLYJN,24.8993763837 2020-08-14,MA4B66MW5E27UANLYJN,23.7200915663 2020-08-17,MA4B66MW5E27UANLYJN,24.2252571056 2020-08-18,MA4B66MW5E27UANLYJN,23.3454236427 2020-08-19,MA4B66MW5E27UANLYJN,23.426330695799997 2020-08-20,MA4B66MW5E27UANLYJN,23.0537084218 2020-08-21,MA4B66MW5E27UANLYJN,22.2429885492 2020-08-24,MA4B66MW5E27UANLYJN,22.4167580447 2020-08-25,MA4B66MW5E27UANLYJN,22.207621656900002 2020-08-26,MA4B66MW5E27UANLYJN,22.5319139279 2020-08-27,MA4B66MW5E27UANLYJN,24.0522377902 2020-08-28,MA4B66MW5E27UANLYJN,25.039721702 2020-08-31,MA4B66MW5E27UANLYJN,26.1145299188 2020-08-03,MA4B66MW5E27UANT8PM,17.4841313043 2020-08-04,MA4B66MW5E27UANT8PM,17.1679184877 2020-08-05,MA4B66MW5E27UANT8PM,17.1517443195 2020-08-06,MA4B66MW5E27UANT8PM,16.8935571186 2020-08-07,MA4B66MW5E27UANT8PM,17.0387727098 2020-08-10,MA4B66MW5E27UANT8PM,16.5122885854 2020-08-11,MA4B66MW5E27UANT8PM,17.2307832813 2020-08-12,MA4B66MW5E27UANT8PM,16.2537391163 2020-08-13,MA4B66MW5E27UANT8PM,16.3346361241 2020-08-14,MA4B66MW5E27UANT8PM,15.8300689407 2020-08-17,MA4B66MW5E27UANT8PM,15.5068766036 2020-08-18,MA4B66MW5E27UANT8PM,14.994667931399999 2020-08-19,MA4B66MW5E27UANT8PM,14.494235133899998 2020-08-20,MA4B66MW5E27UANT8PM,14.699328337199999 2020-08-21,MA4B66MW5E27UANT8PM,14.334550328599999 2020-08-24,MA4B66MW5E27UANT8PM,14.8881520697 2020-08-25,MA4B66MW5E27UANT8PM,15.153158962 2020-08-26,MA4B66MW5E27UANT8PM,15.125791469700001 2020-08-27,MA4B66MW5E27UANT8PM,17.244082383000002 2020-08-28,MA4B66MW5E27UANT8PM,17.2467389346 2020-08-31,MA4B66MW5E27UANT8PM,17.7692622778 2020-08-03,MA4B66MW5E27UAKHZFD,32.0013813641 2020-08-04,MA4B66MW5E27UAKHZFD,32.0674497802 2020-08-05,MA4B66MW5E27UAKHZFD,30.7995594829 2020-08-06,MA4B66MW5E27UAKHZFD,31.252053921799998 2020-08-07,MA4B66MW5E27UAKHZFD,31.970455897599997 2020-08-10,MA4B66MW5E27UAKHZFD,33.2128520859 2020-08-11,MA4B66MW5E27UAKHZFD,33.828479250499996 2020-08-12,MA4B66MW5E27UAKHZFD,32.991925051799996 2020-08-13,MA4B66MW5E27UAKHZFD,32.239652466 2020-08-14,MA4B66MW5E27UAKHZFD,32.6004525173 2020-08-17,MA4B66MW5E27UAKHZFD,31.5323569378 2020-08-18,MA4B66MW5E27UAKHZFD,31.442703661 2020-08-19,MA4B66MW5E27UAKHZFD,30.432373397899998 2020-08-20,MA4B66MW5E27UAKHZFD,30.0640658198 2020-08-21,MA4B66MW5E27UAKHZFD,29.189220876700002 2020-08-24,MA4B66MW5E27UAKHZFD,30.2725114044 2020-08-25,MA4B66MW5E27UAKHZFD,30.199499089699998 2020-08-26,MA4B66MW5E27UAKHZFD,31.191791129800002 2020-08-27,MA4B66MW5E27UAKHZFD,33.029916972 2020-08-28,MA4B66MW5E27UAKHZFD,33.1894097001 2020-08-31,MA4B66MW5E27UAKHZFD,33.9868911836 2020-08-03,MA4B66MW5E27UAKVAB4,28.781383963399996 2020-08-04,MA4B66MW5E27UAKVAB4,26.6038152489 2020-08-05,MA4B66MW5E27UAKVAB4,25.768417273100003 2020-08-06,MA4B66MW5E27UAKVAB4,26.082219415300003 2020-08-07,MA4B66MW5E27UAKVAB4,25.6673962918 2020-08-10,MA4B66MW5E27UAKVAB4,27.1726035902 2020-08-11,MA4B66MW5E27UAKVAB4,26.9311787078 2020-08-12,MA4B66MW5E27UAKVAB4,25.043404455700003 2020-08-13,MA4B66MW5E27UAKVAB4,25.816433333599996 2020-08-14,MA4B66MW5E27UAKVAB4,24.7640550573 2020-08-17,MA4B66MW5E27UAKVAB4,26.0022839799 2020-08-18,MA4B66MW5E27UAKVAB4,24.2462128325 2020-08-19,MA4B66MW5E27UAKVAB4,24.7474109221 2020-08-20,MA4B66MW5E27UAKVAB4,24.554559168 2020-08-21,MA4B66MW5E27UAKVAB4,23.331782268199998 2020-08-24,MA4B66MW5E27UAKVAB4,24.785529566799998 2020-08-25,MA4B66MW5E27UAKVAB4,24.1568496774 2020-08-26,MA4B66MW5E27UAKVAB4,24.1868912823 2020-08-27,MA4B66MW5E27UAKVAB4,26.071463805400004 2020-08-28,MA4B66MW5E27UAKVAB4,27.749229735300002 2020-08-31,MA4B66MW5E27UAKVAB4,30.2017582846 2020-08-03,MA4B66MW5E27UAE4MG6,35.2729116598 2020-08-04,MA4B66MW5E27UAE4MG6,34.9123036438 2020-08-05,MA4B66MW5E27UAE4MG6,27.380079367999997 2020-08-06,MA4B66MW5E27UAE4MG6,29.5305282344 2020-08-07,MA4B66MW5E27UAE4MG6,28.2699388053 2020-08-10,MA4B66MW5E27UAE4MG6,27.3122574453 2020-08-11,MA4B66MW5E27UAE4MG6,28.236848257699997 2020-08-12,MA4B66MW5E27UAE4MG6,27.5595159232 2020-08-13,MA4B66MW5E27UAE4MG6,26.8530473576 2020-08-14,MA4B66MW5E27UAE4MG6,27.310062821800003 2020-08-17,MA4B66MW5E27UAE4MG6,27.485745907499997 2020-08-18,MA4B66MW5E27UAE4MG6,27.134961102100004 2020-08-19,MA4B66MW5E27UAE4MG6,26.530397354500003 2020-08-20,MA4B66MW5E27UAE4MG6,25.8547756287 2020-08-21,MA4B66MW5E27UAE4MG6,25.6588190278 2020-08-24,MA4B66MW5E27UAE4MG6,26.2548276226 2020-08-25,MA4B66MW5E27UAE4MG6,25.657303145100002 2020-08-26,MA4B66MW5E27UAE4MG6,27.5801695031 2020-08-27,MA4B66MW5E27UAE4MG6,28.1085757496 2020-08-28,MA4B66MW5E27UAE4MG6,31.455273661499998 2020-08-31,MA4B66MW5E27UAE4MG6,32.0857962524 2020-08-03,MA4B66MW5E27U8P32SB,19.6120683137 2020-08-04,MA4B66MW5E27U8P32SB,18.986084133200002 2020-08-05,MA4B66MW5E27U8P32SB,17.9700836303 2020-08-06,MA4B66MW5E27U8P32SB,17.350952918500003 2020-08-07,MA4B66MW5E27U8P32SB,17.047662540399998 2020-08-10,MA4B66MW5E27U8P32SB,17.1335019934 2020-08-11,MA4B66MW5E27U8P32SB,19.5649846142 2020-08-12,MA4B66MW5E27U8P32SB,17.204233235300002 2020-08-13,MA4B66MW5E27U8P32SB,17.269626231 2020-08-14,MA4B66MW5E27U8P32SB,16.9673806164 2020-08-17,MA4B66MW5E27U8P32SB,16.7517135406 2020-08-18,MA4B66MW5E27U8P32SB,16.6814846898 2020-08-19,MA4B66MW5E27U8P32SB,17.3067401359 2020-08-20,MA4B66MW5E27U8P32SB,16.879793488 2020-08-21,MA4B66MW5E27U8P32SB,16.279648874899998 2020-08-24,MA4B66MW5E27U8P32SB,16.5472866995 2020-08-25,MA4B66MW5E27U8P32SB,16.4731881277 2020-08-26,MA4B66MW5E27U8P32SB,17.368938294 2020-08-27,MA4B66MW5E27U8P32SB,18.0331747448 2020-08-28,MA4B66MW5E27U8P32SB,16.7728713201 2020-08-31,MA4B66MW5E27U8P32SB,19.0377074698 2020-08-03,MA4B66MW5E27UAL3J7Z,29.522037417000004 2020-08-04,MA4B66MW5E27UAL3J7Z,29.5875348207 2020-08-05,MA4B66MW5E27UAL3J7Z,27.739803232 2020-08-06,MA4B66MW5E27UAL3J7Z,28.533850546900002 2020-08-07,MA4B66MW5E27UAL3J7Z,26.7789422314 2020-08-10,MA4B66MW5E27UAL3J7Z,28.496861884399998 2020-08-11,MA4B66MW5E27UAL3J7Z,27.8182491112 2020-08-12,MA4B66MW5E27UAL3J7Z,25.587331712 2020-08-13,MA4B66MW5E27UAL3J7Z,25.691881750399997 2020-08-14,MA4B66MW5E27UAL3J7Z,25.722516444600004 2020-08-17,MA4B66MW5E27UAL3J7Z,25.9950531386 2020-08-18,MA4B66MW5E27UAL3J7Z,27.084381421699998 2020-08-19,MA4B66MW5E27UAL3J7Z,26.408104674799997 2020-08-20,MA4B66MW5E27UAL3J7Z,27.0437847683 2020-08-21,MA4B66MW5E27UAL3J7Z,26.4664725931 2020-08-24,MA4B66MW5E27UAL3J7Z,27.572856640299996 2020-08-25,MA4B66MW5E27UAL3J7Z,23.0187035177 2020-08-26,MA4B66MW5E27UAL3J7Z,23.3821571199 2020-08-27,MA4B66MW5E27UAL3J7Z,25.564617289399997 2020-08-28,MA4B66MW5E27UAL3J7Z,26.3750349449 2020-08-31,MA4B66MW5E27UAL3J7Z,27.838417302200003 2020-08-03,MA4B66MW5E27UA4479J,36.8515643866 2020-08-04,MA4B66MW5E27UA4479J,35.5768504565 2020-08-05,MA4B66MW5E27UA4479J,33.0013192613 2020-08-06,MA4B66MW5E27UA4479J,25.8477456603 2020-08-07,MA4B66MW5E27UA4479J,24.1762054367 2020-08-10,MA4B66MW5E27UA4479J,24.5585804745 2020-08-11,MA4B66MW5E27UA4479J,23.6851495488 2020-08-12,MA4B66MW5E27UA4479J,23.2822669772 2020-08-13,MA4B66MW5E27UA4479J,23.6913186193 2020-08-14,MA4B66MW5E27UA4479J,22.8614304917 2020-08-17,MA4B66MW5E27UA4479J,23.575437483 2020-08-18,MA4B66MW5E27UA4479J,22.1248692397 2020-08-19,MA4B66MW5E27UA4479J,21.8819002517 2020-08-20,MA4B66MW5E27UA4479J,21.4932973019 2020-08-21,MA4B66MW5E27UA4479J,21.6668401141 2020-08-24,MA4B66MW5E27UA4479J,22.166180894900002 2020-08-25,MA4B66MW5E27UA4479J,21.7683245556 2020-08-26,MA4B66MW5E27UA4479J,22.1012543658 2020-08-27,MA4B66MW5E27UA4479J,23.5386101637 2020-08-28,MA4B66MW5E27UA4479J,23.1035916776 2020-08-31,MA4B66MW5E27UA4479J,24.0455426465 2020-08-03,MA4B66MW5E27UAKNZKD,23.2218970204 2020-08-04,MA4B66MW5E27UAKNZKD,22.316324836 2020-08-05,MA4B66MW5E27UAKNZKD,21.2185623646 2020-08-06,MA4B66MW5E27UAKNZKD,20.4046607436 2020-08-07,MA4B66MW5E27UAKNZKD,20.4044401526 2020-08-10,MA4B66MW5E27UAKNZKD,19.8225091254 2020-08-11,MA4B66MW5E27UAKNZKD,20.6910845046 2020-08-12,MA4B66MW5E27UAKNZKD,19.2118632386 2020-08-13,MA4B66MW5E27UAKNZKD,18.8800908739 2020-08-14,MA4B66MW5E27UAKNZKD,19.111484910199998 2020-08-17,MA4B66MW5E27UAKNZKD,19.1693999063 2020-08-18,MA4B66MW5E27UAKNZKD,18.1817459798 2020-08-19,MA4B66MW5E27UAKNZKD,18.914242957800003 2020-08-20,MA4B66MW5E27UAKNZKD,18.3895652361 2020-08-21,MA4B66MW5E27UAKNZKD,18.1808882649 2020-08-24,MA4B66MW5E27UAKNZKD,18.258989743500003 2020-08-25,MA4B66MW5E27UAKNZKD,18.0800489587 2020-08-26,MA4B66MW5E27UAKNZKD,18.4664483006 2020-08-27,MA4B66MW5E27UAKNZKD,20.2423448332 2020-08-28,MA4B66MW5E27UAKNZKD,21.2845331545 2020-08-31,MA4B66MW5E27UAKNZKD,22.0725595114 2020-08-03,MA4B66MW5E27U9VBBKA,24.5167052958 2020-08-04,MA4B66MW5E27U9VBBKA,23.7062539117 2020-08-05,MA4B66MW5E27U9VBBKA,22.778865899299998 2020-08-06,MA4B66MW5E27U9VBBKA,22.2284453573 2020-08-07,MA4B66MW5E27U9VBBKA,23.132197661600003 2020-08-10,MA4B66MW5E27U9VBBKA,23.5933897217 2020-08-11,MA4B66MW5E27U9VBBKA,24.708010054499997 2020-08-12,MA4B66MW5E27U9VBBKA,22.6719293562 2020-08-13,MA4B66MW5E27U9VBBKA,21.74205607 2020-08-14,MA4B66MW5E27U9VBBKA,21.709832590799998 2020-08-17,MA4B66MW5E27U9VBBKA,20.7930612617 2020-08-18,MA4B66MW5E27U9VBBKA,19.9863307931 2020-08-19,MA4B66MW5E27U9VBBKA,19.7233421376 2020-08-20,MA4B66MW5E27U9VBBKA,19.3782642288 2020-08-21,MA4B66MW5E27U9VBBKA,18.4870661174 2020-08-24,MA4B66MW5E27U9VBBKA,23.9847351572 2020-08-25,MA4B66MW5E27U9VBBKA,23.3307263997 2020-08-26,MA4B66MW5E27U9VBBKA,23.865798701400003 2020-08-27,MA4B66MW5E27U9VBBKA,26.4321641803 2020-08-28,MA4B66MW5E27U9VBBKA,27.268310842299996 2020-08-31,MA4B66MW5E27U9VBBKA,30.075632134699998 2020-08-03,MA4B66MW5E27UADJ5RL,38.6828764577 2020-08-04,MA4B66MW5E27UADJ5RL,37.6287945276 2020-08-05,MA4B66MW5E27UADJ5RL,35.7546241289 2020-08-06,MA4B66MW5E27UADJ5RL,36.3234296335 2020-08-07,MA4B66MW5E27UADJ5RL,38.457154027 2020-08-10,MA4B66MW5E27UADJ5RL,38.926381812600006 2020-08-11,MA4B66MW5E27UADJ5RL,38.9903740438 2020-08-12,MA4B66MW5E27UADJ5RL,37.7985935874 2020-08-13,MA4B66MW5E27UADJ5RL,37.3619495631 2020-08-14,MA4B66MW5E27UADJ5RL,37.2004577292 2020-08-17,MA4B66MW5E27UADJ5RL,37.481224407 2020-08-18,MA4B66MW5E27UADJ5RL,36.357387609199996 2020-08-19,MA4B66MW5E27UADJ5RL,36.0420502 2020-08-20,MA4B66MW5E27UADJ5RL,36.4537452442 2020-08-21,MA4B66MW5E27UADJ5RL,37.53083899 2020-08-24,MA4B66MW5E27UADJ5RL,36.9393476933 2020-08-25,MA4B66MW5E27UADJ5RL,37.721539033700004 2020-08-26,MA4B66MW5E27UADJ5RL,44.941530323399995 2020-08-27,MA4B66MW5E27UADJ5RL,41.125937932599996 2020-08-28,MA4B66MW5E27UADJ5RL,44.2859643073 2020-08-31,MA4B66MW5E27UADJ5RL,43.6471308342 2020-08-03,MA4B66MW5E27UAKV9P7,28.8953678439 2020-08-04,MA4B66MW5E27UAKV9P7,28.325966735699996 2020-08-05,MA4B66MW5E27UAKV9P7,26.820907762500003 2020-08-06,MA4B66MW5E27UAKV9P7,27.089465618800002 2020-08-07,MA4B66MW5E27UAKV9P7,26.5174348339 2020-08-10,MA4B66MW5E27UAKV9P7,27.0456335597 2020-08-11,MA4B66MW5E27UAKV9P7,26.344580903999997 2020-08-12,MA4B66MW5E27UAKV9P7,25.530829630200003 2020-08-13,MA4B66MW5E27UAKV9P7,25.376469015300003 2020-08-14,MA4B66MW5E27UAKV9P7,25.504923085199998 2020-08-17,MA4B66MW5E27UAKV9P7,24.6162467839 2020-08-18,MA4B66MW5E27UAKV9P7,24.7067403256 2020-08-19,MA4B66MW5E27UAKV9P7,24.0082105888 2020-08-20,MA4B66MW5E27UAKV9P7,23.8571038861 2020-08-21,MA4B66MW5E27UAKV9P7,25.527812667599996 2020-08-24,MA4B66MW5E27UAKV9P7,26.1235291484 2020-08-25,MA4B66MW5E27UAKV9P7,25.8839639961 2020-08-26,MA4B66MW5E27UAKV9P7,25.184185739800004 2020-08-27,MA4B66MW5E27UAKV9P7,26.826878958 2020-08-28,MA4B66MW5E27UAKV9P7,26.261889011 2020-08-31,MA4B66MW5E27UAKV9P7,28.2961856342 2020-08-03,MA4B66MW5E27UALG396,28.6420366942 2020-08-04,MA4B66MW5E27UALG396,28.3860315473 2020-08-05,MA4B66MW5E27UALG396,27.2579271858 2020-08-06,MA4B66MW5E27UALG396,25.8341743828 2020-08-07,MA4B66MW5E27UALG396,26.461773833000002 2020-08-10,MA4B66MW5E27UALG396,27.0520849503 2020-08-11,MA4B66MW5E27UALG396,27.902806770100003 2020-08-12,MA4B66MW5E27UALG396,25.712690541900002 2020-08-13,MA4B66MW5E27UALG396,25.8044767219 2020-08-14,MA4B66MW5E27UALG396,25.9516535964 2020-08-17,MA4B66MW5E27UALG396,25.606249968400004 2020-08-18,MA4B66MW5E27UALG396,25.9321753648 2020-08-19,MA4B66MW5E27UALG396,24.7489082977 2020-08-20,MA4B66MW5E27UALG396,25.5350399992 2020-08-21,MA4B66MW5E27UALG396,25.128699151099998 2020-08-24,MA4B66MW5E27UALG396,31.780435922099997 2020-08-25,MA4B66MW5E27UALG396,32.1351585416 2020-08-26,MA4B66MW5E27UALG396,32.3187515969 2020-08-27,MA4B66MW5E27UALG396,34.4450884251 2020-08-28,MA4B66MW5E27UALG396,35.8476130979 2020-08-31,MA4B66MW5E27UALG396,38.2314033276 2020-08-03,MA4B66MW5E27U9ZPDGH,36.4634340825 2020-08-04,MA4B66MW5E27U9ZPDGH,35.6284997172 2020-08-05,MA4B66MW5E27U9ZPDGH,33.690094776 2020-08-06,MA4B66MW5E27U9ZPDGH,33.7807513474 2020-08-07,MA4B66MW5E27U9ZPDGH,34.8606192219 2020-08-10,MA4B66MW5E27U9ZPDGH,35.0214062178 2020-08-11,MA4B66MW5E27U9ZPDGH,36.8608659 2020-08-12,MA4B66MW5E27U9ZPDGH,35.2844861532 2020-08-13,MA4B66MW5E27U9ZPDGH,34.7825178672 2020-08-14,MA4B66MW5E27U9ZPDGH,35.1299951075 2020-08-17,MA4B66MW5E27U9ZPDGH,34.2677849987 2020-08-18,MA4B66MW5E27U9ZPDGH,34.4807544186 2020-08-19,MA4B66MW5E27U9ZPDGH,34.0514173531 2020-08-20,MA4B66MW5E27U9ZPDGH,33.2009048206 2020-08-21,MA4B66MW5E27U9ZPDGH,32.7262830003 2020-08-24,MA4B66MW5E27U9ZPDGH,33.3316560273 2020-08-25,MA4B66MW5E27U9ZPDGH,33.4496640284 2020-08-26,MA4B66MW5E27U9ZPDGH,34.1213603817 2020-08-27,MA4B66MW5E27U9ZPDGH,35.7707413799 2020-08-28,MA4B66MW5E27U9ZPDGH,35.1081835219 2020-08-31,MA4B66MW5E27U9ZPDGH,36.8876027715 2020-08-03,MA4B66MW5E27U9VBBAX,31.260357395299998 2020-08-04,MA4B66MW5E27U9VBBAX,26.0936365688 2020-08-05,MA4B66MW5E27U9VBBAX,24.6196077191 2020-08-06,MA4B66MW5E27U9VBBAX,24.546401258299998 2020-08-07,MA4B66MW5E27U9VBBAX,25.1285285107 2020-08-10,MA4B66MW5E27U9VBBAX,24.7350512911 2020-08-11,MA4B66MW5E27U9VBBAX,24.8184094375 2020-08-12,MA4B66MW5E27U9VBBAX,22.632214275000003 2020-08-13,MA4B66MW5E27U9VBBAX,23.6393876038 2020-08-14,MA4B66MW5E27U9VBBAX,23.897435550500003 2020-08-17,MA4B66MW5E27U9VBBAX,24.1647206105 2020-08-18,MA4B66MW5E27U9VBBAX,23.6976432913 2020-08-19,MA4B66MW5E27U9VBBAX,22.2240126079 2020-08-20,MA4B66MW5E27U9VBBAX,22.488756502500003 2020-08-21,MA4B66MW5E27U9VBBAX,22.2374798711 2020-08-24,MA4B66MW5E27U9VBBAX,23.3315947951 2020-08-25,MA4B66MW5E27U9VBBAX,22.654831956 2020-08-26,MA4B66MW5E27U9VBBAX,21.979275453 2020-08-27,MA4B66MW5E27U9VBBAX,23.4438523485 2020-08-28,MA4B66MW5E27U9VBBAX,23.740756985799997 2020-08-31,MA4B66MW5E27U9VBBAX,23.4223256277 2020-08-03,MA4B66MW5E27UAHKGMA,30.1542378983 2020-08-04,MA4B66MW5E27UAHKGMA,30.433256756600002 2020-08-05,MA4B66MW5E27UAHKGMA,29.0914363385 2020-08-06,MA4B66MW5E27UAHKGMA,28.7880634405 2020-08-07,MA4B66MW5E27UAHKGMA,27.9976423079 2020-08-10,MA4B66MW5E27UAHKGMA,28.925173450000003 2020-08-11,MA4B66MW5E27UAHKGMA,30.9941118229 2020-08-12,MA4B66MW5E27UAHKGMA,28.1766883044 2020-08-13,MA4B66MW5E27UAHKGMA,28.5872002091 2020-08-14,MA4B66MW5E27UAHKGMA,28.4888422982 2020-08-17,MA4B66MW5E27UAHKGMA,28.135337739799997 2020-08-18,MA4B66MW5E27UAHKGMA,24.7316562446 2020-08-19,MA4B66MW5E27UAHKGMA,23.863452576700002 2020-08-20,MA4B66MW5E27UAHKGMA,23.2854876392 2020-08-21,MA4B66MW5E27UAHKGMA,22.994885721899998 2020-08-24,MA4B66MW5E27UAHKGMA,22.6913644248 2020-08-25,MA4B66MW5E27UAHKGMA,22.1297081507 2020-08-26,MA4B66MW5E27UAHKGMA,23.491386334 2020-08-27,MA4B66MW5E27UAHKGMA,25.2427555611 2020-08-28,MA4B66MW5E27UAHKGMA,25.751678132400002 2020-08-31,MA4B66MW5E27UAHKGMA,26.780812421900002 2020-08-03,MA4B66MW5E27U9VBBEQ,28.4866496536 2020-08-04,MA4B66MW5E27U9VBBEQ,26.785937735799997 2020-08-05,MA4B66MW5E27U9VBBEQ,25.1941256132 2020-08-06,MA4B66MW5E27U9VBBEQ,24.4256425466 2020-08-07,MA4B66MW5E27U9VBBEQ,24.6911628101 2020-08-10,MA4B66MW5E27U9VBBEQ,25.3075075414 2020-08-11,MA4B66MW5E27U9VBBEQ,25.431832457499997 2020-08-12,MA4B66MW5E27U9VBBEQ,23.3706756569 2020-08-13,MA4B66MW5E27U9VBBEQ,23.1788581929 2020-08-14,MA4B66MW5E27U9VBBEQ,23.4568359033 2020-08-17,MA4B66MW5E27U9VBBEQ,22.6981725766 2020-08-18,MA4B66MW5E27U9VBBEQ,22.0643225642 2020-08-19,MA4B66MW5E27U9VBBEQ,22.0456489946 2020-08-20,MA4B66MW5E27U9VBBEQ,21.710599677799998 2020-08-21,MA4B66MW5E27U9VBBEQ,21.4003711446 2020-08-24,MA4B66MW5E27U9VBBEQ,22.3438842509 2020-08-25,MA4B66MW5E27U9VBBEQ,22.5854035905 2020-08-26,MA4B66MW5E27U9VBBEQ,22.5950804007 2020-08-27,MA4B66MW5E27U9VBBEQ,43.945140995100004 2020-08-28,MA4B66MW5E27U9VBBEQ,29.2151068566 2020-08-31,MA4B66MW5E27U9VBBEQ,30.0205474315 2020-08-03,MA4B66MW5E27UANZGNN,28.229177490700003 2020-08-04,MA4B66MW5E27UANZGNN,29.251230388 2020-08-05,MA4B66MW5E27UANZGNN,30.127297906 2020-08-06,MA4B66MW5E27UANZGNN,29.1916094284 2020-08-07,MA4B66MW5E27UANZGNN,29.1649460408 2020-08-10,MA4B66MW5E27UANZGNN,29.834239376499998 2020-08-11,MA4B66MW5E27UANZGNN,31.093700697799996 2020-08-12,MA4B66MW5E27UANZGNN,28.383376047600002 2020-08-13,MA4B66MW5E27UANZGNN,28.7139446633 2020-08-14,MA4B66MW5E27UANZGNN,28.5594233626 2020-08-17,MA4B66MW5E27UANZGNN,27.755634650900003 2020-08-18,MA4B66MW5E27UANZGNN,23.380512787 2020-08-19,MA4B66MW5E27UANZGNN,22.0844164537 2020-08-20,MA4B66MW5E27UANZGNN,21.7478212678 2020-08-21,MA4B66MW5E27UANZGNN,21.883413668099998 2020-08-24,MA4B66MW5E27UANZGNN,20.867506033399998 2020-08-25,MA4B66MW5E27UANZGNN,21.0423677695 2020-08-26,MA4B66MW5E27UANZGNN,20.7405510655 2020-08-27,MA4B66MW5E27UANZGNN,27.954401563300003 2020-08-28,MA4B66MW5E27UANZGNN,30.948469751500003 2020-08-31,MA4B66MW5E27UANZGNN,31.1491983756 ================================================ FILE: gs_quant/test/resources/SPX_50_icorr_out.csv ================================================ date,value 2020-08-03,35.19184134437535 2020-08-04,34.34380735730206 2020-08-05,34.51726555117047 2020-08-06,31.714363153066717 2020-08-07,28.83708015224759 2020-08-08,28.257227332726938 2020-08-09,27.690625332698072 2020-08-10,27.13683289930194 2020-08-11,35.583091602280895 2020-08-12,32.37930333026746 2020-08-13,31.78960465198087 2020-08-14,31.891869451537243 2020-08-15,31.493413463437463 2020-08-16,31.09733269839318 2020-08-17,30.70360073605032 2020-08-18,30.85501330034845 2020-08-19,35.20715394703207 2020-08-20,34.01112123042673 2020-08-21,27.390717591341733 2020-08-22,26.06998932098279 2020-08-23,24.81851952820634 2020-08-24,23.6309974390261 2020-08-25,24.188718812527714 2020-08-26,22.078901463497903 2020-08-27,21.316055437176637 2020-08-28,18.728841000182346 2020-08-29,20.553251809774125 2020-08-30,22.396530345623557 2020-08-31,24.256525645155662 ================================================ FILE: gs_quant/test/resources/SPX_50_rcorr_in.csv ================================================ date,assetId,spot 2020-07-01,MAMDDXVJYX77V2K9,132.36 2020-07-02,MAMDDXVJYX77V2K9,132.85 2020-07-06,MAMDDXVJYX77V2K9,133.3 2020-07-07,MAMDDXVJYX77V2K9,133.57 2020-07-08,MAMDDXVJYX77V2K9,134.22 2020-07-09,MAMDDXVJYX77V2K9,132.71 2020-07-10,MAMDDXVJYX77V2K9,134.46 2020-07-13,MAMDDXVJYX77V2K9,134.91 2020-07-14,MAMDDXVJYX77V2K9,135.52 2020-07-15,MAMDDXVJYX77V2K9,133.69 2020-07-16,MAMDDXVJYX77V2K9,133.88 2020-07-17,MAMDDXVJYX77V2K9,134.66 2020-07-20,MAMDDXVJYX77V2K9,133.11 2020-07-21,MAMDDXVJYX77V2K9,134.43 2020-07-22,MAMDDXVJYX77V2K9,136.01 2020-07-23,MAMDDXVJYX77V2K9,137.08 2020-07-24,MAMDDXVJYX77V2K9,136.06 2020-07-27,MAMDDXVJYX77V2K9,137.67 2020-07-28,MAMDDXVJYX77V2K9,137.38 2020-07-29,MAMDDXVJYX77V2K9,137.93 2020-07-30,MAMDDXVJYX77V2K9,137.69 2020-07-31,MAMDDXVJYX77V2K9,137.66 2020-08-03,MAMDDXVJYX77V2K9,136.7 2020-08-04,MAMDDXVJYX77V2K9,137.47 2020-08-05,MAMDDXVJYX77V2K9,136.25 2020-08-06,MAMDDXVJYX77V2K9,135.86 2020-08-07,MAMDDXVJYX77V2K9,136.74 2020-08-10,MAMDDXVJYX77V2K9,135.98 2020-08-11,MAMDDXVJYX77V2K9,135.12 2020-08-12,MAMDDXVJYX77V2K9,137.8 2020-08-13,MAMDDXVJYX77V2K9,138.1 2020-08-14,MAMDDXVJYX77V2K9,137.56 2020-08-17,MAMDDXVJYX77V2K9,137.92 2020-08-18,MAMDDXVJYX77V2K9,138.11 2020-08-19,MAMDDXVJYX77V2K9,136.45 2020-08-20,MAMDDXVJYX77V2K9,136.72 2020-08-21,MAMDDXVJYX77V2K9,136.46 2020-08-24,MAMDDXVJYX77V2K9,137.64 2020-08-25,MAMDDXVJYX77V2K9,137.3 2020-08-26,MAMDDXVJYX77V2K9,138.47 2020-08-27,MAMDDXVJYX77V2K9,138.4 2020-08-28,MAMDDXVJYX77V2K9,139.94 2020-08-31,MAMDDXVJYX77V2K9,140.06 2020-07-01,MA4B66MW5E27U9YGMGY,2878.7 2020-07-02,MA4B66MW5E27U9YGMGY,2890.3 2020-07-06,MA4B66MW5E27U9YGMGY,3057.04 2020-07-07,MA4B66MW5E27U9YGMGY,3000.12 2020-07-08,MA4B66MW5E27U9YGMGY,3081.11 2020-07-09,MA4B66MW5E27U9YGMGY,3182.63 2020-07-10,MA4B66MW5E27U9YGMGY,3200.0 2020-07-13,MA4B66MW5E27U9YGMGY,3104.0 2020-07-14,MA4B66MW5E27U9YGMGY,3084.0 2020-07-15,MA4B66MW5E27U9YGMGY,3008.87 2020-07-16,MA4B66MW5E27U9YGMGY,2999.9 2020-07-17,MA4B66MW5E27U9YGMGY,2961.97 2020-07-20,MA4B66MW5E27U9YGMGY,3196.84 2020-07-21,MA4B66MW5E27U9YGMGY,3138.29 2020-07-22,MA4B66MW5E27U9YGMGY,3099.91 2020-07-23,MA4B66MW5E27U9YGMGY,2986.55 2020-07-24,MA4B66MW5E27U9YGMGY,3008.91 2020-07-27,MA4B66MW5E27U9YGMGY,3055.21 2020-07-28,MA4B66MW5E27U9YGMGY,3000.33 2020-07-29,MA4B66MW5E27U9YGMGY,3033.53 2020-07-30,MA4B66MW5E27U9YGMGY,3051.88 2020-07-31,MA4B66MW5E27U9YGMGY,3164.68 2020-08-03,MA4B66MW5E27U9YGMGY,3111.89 2020-08-04,MA4B66MW5E27U9YGMGY,3138.83 2020-08-05,MA4B66MW5E27U9YGMGY,3205.03 2020-08-06,MA4B66MW5E27U9YGMGY,3225.0 2020-08-07,MA4B66MW5E27U9YGMGY,3167.46 2020-08-10,MA4B66MW5E27U9YGMGY,3148.16 2020-08-11,MA4B66MW5E27U9YGMGY,3080.67 2020-08-12,MA4B66MW5E27U9YGMGY,3162.24 2020-08-13,MA4B66MW5E27U9YGMGY,3161.02 2020-08-14,MA4B66MW5E27U9YGMGY,3148.02 2020-08-17,MA4B66MW5E27U9YGMGY,3182.41 2020-08-18,MA4B66MW5E27U9YGMGY,3312.49 2020-08-19,MA4B66MW5E27U9YGMGY,3260.48 2020-08-20,MA4B66MW5E27U9YGMGY,3297.37 2020-08-21,MA4B66MW5E27U9YGMGY,3284.72 2020-08-24,MA4B66MW5E27U9YGMGY,3307.46 2020-08-25,MA4B66MW5E27U9YGMGY,3346.49 2020-08-26,MA4B66MW5E27U9YGMGY,3441.85 2020-08-27,MA4B66MW5E27U9YGMGY,3400.0 2020-08-28,MA4B66MW5E27U9YGMGY,3401.8 2020-08-31,MA4B66MW5E27U9YGMGY,3450.96 2020-07-01,MA4B66MW5E27UAJQETU,58.81 2020-07-02,MA4B66MW5E27UAJQETU,59.13 2020-07-06,MA4B66MW5E27UAJQETU,59.54 2020-07-07,MA4B66MW5E27UAJQETU,58.31 2020-07-08,MA4B66MW5E27UAJQETU,58.61 2020-07-09,MA4B66MW5E27UAJQETU,58.42 2020-07-10,MA4B66MW5E27UAJQETU,59.53 2020-07-13,MA4B66MW5E27UAJQETU,58.58 2020-07-14,MA4B66MW5E27UAJQETU,58.98 2020-07-15,MA4B66MW5E27UAJQETU,59.03 2020-07-16,MA4B66MW5E27UAJQETU,59.14 2020-07-17,MA4B66MW5E27UAJQETU,60.0 2020-07-20,MA4B66MW5E27UAJQETU,61.15 2020-07-21,MA4B66MW5E27UAJQETU,60.7 2020-07-22,MA4B66MW5E27UAJQETU,61.05 2020-07-23,MA4B66MW5E27UAJQETU,60.4 2020-07-24,MA4B66MW5E27UAJQETU,50.59 2020-07-27,MA4B66MW5E27UAJQETU,49.57 2020-07-28,MA4B66MW5E27UAJQETU,49.24 2020-07-29,MA4B66MW5E27UAJQETU,48.07 2020-07-30,MA4B66MW5E27UAJQETU,47.99 2020-07-31,MA4B66MW5E27UAJQETU,47.73 2020-08-03,MA4B66MW5E27UAJQETU,48.3 2020-08-04,MA4B66MW5E27UAJQETU,49.13 2020-08-05,MA4B66MW5E27UAJQETU,48.92 2020-08-06,MA4B66MW5E27UAJQETU,48.57 2020-08-07,MA4B66MW5E27UAJQETU,48.03 2020-08-10,MA4B66MW5E27UAJQETU,49.22 2020-08-11,MA4B66MW5E27UAJQETU,48.19 2020-08-12,MA4B66MW5E27UAJQETU,49.19 2020-08-13,MA4B66MW5E27UAJQETU,48.56 2020-08-14,MA4B66MW5E27UAJQETU,48.89 2020-08-17,MA4B66MW5E27UAJQETU,48.93 2020-08-18,MA4B66MW5E27UAJQETU,48.65 2020-08-19,MA4B66MW5E27UAJQETU,48.33 2020-08-20,MA4B66MW5E27UAJQETU,49.17 2020-08-21,MA4B66MW5E27UAJQETU,49.28 2020-08-24,MA4B66MW5E27UAJQETU,49.14 2020-08-25,MA4B66MW5E27UAJQETU,49.43 2020-08-26,MA4B66MW5E27UAJQETU,49.55 2020-08-27,MA4B66MW5E27UAJQETU,49.4 2020-08-28,MA4B66MW5E27UAJQETU,50.43 2020-08-31,MA4B66MW5E27UAJQETU,50.95 2020-07-01,MA4B66MW5E27UALNB5V,381.2 2020-07-02,MA4B66MW5E27UALNB5V,384.49 2020-07-06,MA4B66MW5E27UALNB5V,393.57 2020-07-07,MA4B66MW5E27UALNB5V,394.87 2020-07-08,MA4B66MW5E27UALNB5V,408.64 2020-07-09,MA4B66MW5E27UALNB5V,420.36 2020-07-10,MA4B66MW5E27UALNB5V,419.17 2020-07-13,MA4B66MW5E27UALNB5V,402.09 2020-07-14,MA4B66MW5E27UALNB5V,415.08 2020-07-15,MA4B66MW5E27UALNB5V,409.09 2020-07-16,MA4B66MW5E27UALNB5V,405.39 2020-07-17,MA4B66MW5E27UALNB5V,408.06 2020-07-20,MA4B66MW5E27UALNB5V,420.43 2020-07-21,MA4B66MW5E27UALNB5V,413.14 2020-07-22,MA4B66MW5E27UALNB5V,417.55 2020-07-23,MA4B66MW5E27UALNB5V,405.19 2020-07-24,MA4B66MW5E27UALNB5V,407.78 2020-07-27,MA4B66MW5E27UALNB5V,416.86 2020-07-28,MA4B66MW5E27UALNB5V,408.62 2020-07-29,MA4B66MW5E27UALNB5V,418.62 2020-07-30,MA4B66MW5E27UALNB5V,424.56 2020-07-31,MA4B66MW5E27UALNB5V,424.59 2020-08-03,MA4B66MW5E27UALNB5V,440.41 2020-08-04,MA4B66MW5E27UALNB5V,449.11 2020-08-05,MA4B66MW5E27UALNB5V,451.47 2020-08-06,MA4B66MW5E27UALNB5V,453.42 2020-08-07,MA4B66MW5E27UALNB5V,447.98 2020-08-10,MA4B66MW5E27UALNB5V,446.6 2020-08-11,MA4B66MW5E27UALNB5V,434.0 2020-08-12,MA4B66MW5E27UALNB5V,457.61 2020-08-13,MA4B66MW5E27UALNB5V,457.72 2020-08-14,MA4B66MW5E27UALNB5V,462.56 2020-08-17,MA4B66MW5E27UALNB5V,493.48 2020-08-18,MA4B66MW5E27UALNB5V,490.43 2020-08-19,MA4B66MW5E27UALNB5V,485.54 2020-08-20,MA4B66MW5E27UALNB5V,485.64 2020-08-21,MA4B66MW5E27UALNB5V,507.34 2020-08-24,MA4B66MW5E27UALNB5V,508.81 2020-08-25,MA4B66MW5E27UALNB5V,510.0 2020-08-26,MA4B66MW5E27UALNB5V,510.92 2020-08-27,MA4B66MW5E27UALNB5V,505.13 2020-08-28,MA4B66MW5E27UALNB5V,525.91 2020-08-31,MA4B66MW5E27UALNB5V,534.98 2020-07-01,MA4B66MW5E27UAM94Q4,89.78 2020-07-02,MA4B66MW5E27UAM94Q4,91.87 2020-07-06,MA4B66MW5E27UAM94Q4,92.92 2020-07-07,MA4B66MW5E27UAM94Q4,92.14 2020-07-08,MA4B66MW5E27UAM94Q4,93.53 2020-07-09,MA4B66MW5E27UAM94Q4,93.25 2020-07-10,MA4B66MW5E27UAM94Q4,92.51 2020-07-13,MA4B66MW5E27UAM94Q4,91.33 2020-07-14,MA4B66MW5E27UAM94Q4,92.38 2020-07-15,MA4B66MW5E27UAM94Q4,92.55 2020-07-16,MA4B66MW5E27UAM94Q4,91.2 2020-07-17,MA4B66MW5E27UAM94Q4,92.23 2020-07-20,MA4B66MW5E27UAM94Q4,93.17 2020-07-21,MA4B66MW5E27UAM94Q4,92.31 2020-07-22,MA4B66MW5E27UAM94Q4,92.6 2020-07-23,MA4B66MW5E27UAM94Q4,89.67 2020-07-24,MA4B66MW5E27UAM94Q4,88.89 2020-07-27,MA4B66MW5E27UAM94Q4,92.7 2020-07-28,MA4B66MW5E27UAM94Q4,91.45 2020-07-29,MA4B66MW5E27UAM94Q4,93.03 2020-07-30,MA4B66MW5E27UAM94Q4,107.19 2020-07-31,MA4B66MW5E27UAM94Q4,105.61 2020-08-03,MA4B66MW5E27UAM94Q4,109.59 2020-08-04,MA4B66MW5E27UAM94Q4,110.95 2020-08-05,MA4B66MW5E27UAM94Q4,111.39 2020-08-06,MA4B66MW5E27UAM94Q4,111.06 2020-08-07,MA4B66MW5E27UAM94Q4,108.25 2020-08-10,MA4B66MW5E27UAM94Q4,106.36 2020-08-11,MA4B66MW5E27UAM94Q4,108.83 2020-08-12,MA4B66MW5E27UAM94Q4,115.79 2020-08-13,MA4B66MW5E27UAM94Q4,113.41 2020-08-14,MA4B66MW5E27UAM94Q4,113.74 2020-08-17,MA4B66MW5E27UAM94Q4,112.18 2020-08-18,MA4B66MW5E27UAM94Q4,112.03 2020-08-19,MA4B66MW5E27UAM94Q4,111.04 2020-08-20,MA4B66MW5E27UAM94Q4,110.66 2020-08-21,MA4B66MW5E27UAM94Q4,113.0 2020-08-24,MA4B66MW5E27UAM94Q4,115.91 2020-08-25,MA4B66MW5E27UAM94Q4,115.95 2020-08-26,MA4B66MW5E27UAM94Q4,116.04 2020-08-27,MA4B66MW5E27UAM94Q4,116.02 2020-08-28,MA4B66MW5E27UAM94Q4,118.2 2020-08-31,MA4B66MW5E27UAM94Q4,119.1 2020-07-01,MA4B66MW5E27U9XPV7X,439.81 2020-07-02,MA4B66MW5E27U9XPV7X,442.95 2020-07-06,MA4B66MW5E27U9XPV7X,452.59 2020-07-07,MA4B66MW5E27U9XPV7X,449.36 2020-07-08,MA4B66MW5E27U9XPV7X,457.68 2020-07-09,MA4B66MW5E27U9XPV7X,460.84 2020-07-10,MA4B66MW5E27U9XPV7X,466.2 2020-07-13,MA4B66MW5E27U9XPV7X,442.47 2020-07-14,MA4B66MW5E27U9XPV7X,433.78 2020-07-15,MA4B66MW5E27U9XPV7X,433.01 2020-07-16,MA4B66MW5E27U9XPV7X,426.29 2020-07-17,MA4B66MW5E27U9XPV7X,432.42 2020-07-20,MA4B66MW5E27U9XPV7X,455.27 2020-07-21,MA4B66MW5E27U9XPV7X,444.28 2020-07-22,MA4B66MW5E27U9XPV7X,445.05 2020-07-23,MA4B66MW5E27U9XPV7X,431.74 2020-07-24,MA4B66MW5E27U9XPV7X,430.31 2020-07-27,MA4B66MW5E27U9XPV7X,437.1 2020-07-28,MA4B66MW5E27U9XPV7X,432.26 2020-07-29,MA4B66MW5E27U9XPV7X,436.3 2020-07-30,MA4B66MW5E27U9XPV7X,438.88 2020-07-31,MA4B66MW5E27U9XPV7X,444.32 2020-08-03,MA4B66MW5E27U9XPV7X,447.97 2020-08-04,MA4B66MW5E27U9XPV7X,446.92 2020-08-05,MA4B66MW5E27U9XPV7X,449.51 2020-08-06,MA4B66MW5E27U9XPV7X,464.11 2020-08-07,MA4B66MW5E27U9XPV7X,449.57 2020-08-10,MA4B66MW5E27U9XPV7X,443.29 2020-08-11,MA4B66MW5E27U9XPV7X,435.23 2020-08-12,MA4B66MW5E27U9XPV7X,445.36 2020-08-13,MA4B66MW5E27U9XPV7X,450.02 2020-08-14,MA4B66MW5E27U9XPV7X,447.6 2020-08-17,MA4B66MW5E27U9XPV7X,451.58 2020-08-18,MA4B66MW5E27U9XPV7X,463.51 2020-08-19,MA4B66MW5E27U9XPV7X,462.01 2020-08-20,MA4B66MW5E27U9XPV7X,478.48 2020-08-21,MA4B66MW5E27U9XPV7X,473.22 2020-08-24,MA4B66MW5E27U9XPV7X,476.3 2020-08-25,MA4B66MW5E27U9XPV7X,484.43 2020-08-26,MA4B66MW5E27U9XPV7X,528.49 2020-08-27,MA4B66MW5E27U9XPV7X,510.32 2020-08-28,MA4B66MW5E27U9XPV7X,516.44 2020-08-31,MA4B66MW5E27U9XPV7X,513.39 2020-07-01,MA4B66MW5E27UADJ5GE,304.75 2020-07-02,MA4B66MW5E27UADJ5GE,305.74 2020-07-06,MA4B66MW5E27UADJ5GE,311.49 2020-07-07,MA4B66MW5E27UADJ5GE,316.23 2020-07-08,MA4B66MW5E27UADJ5GE,316.32 2020-07-09,MA4B66MW5E27UADJ5GE,325.54 2020-07-10,MA4B66MW5E27UADJ5GE,326.23 2020-07-13,MA4B66MW5E27UADJ5GE,322.92 2020-07-14,MA4B66MW5E27UADJ5GE,328.0 2020-07-15,MA4B66MW5E27UADJ5GE,326.7 2020-07-16,MA4B66MW5E27UADJ5GE,326.27 2020-07-17,MA4B66MW5E27UADJ5GE,324.79 2020-07-20,MA4B66MW5E27UADJ5GE,326.51 2020-07-21,MA4B66MW5E27UADJ5GE,327.74 2020-07-22,MA4B66MW5E27UADJ5GE,328.3 2020-07-23,MA4B66MW5E27UADJ5GE,326.11 2020-07-24,MA4B66MW5E27UADJ5GE,325.78 2020-07-27,MA4B66MW5E27UADJ5GE,327.6 2020-07-28,MA4B66MW5E27UADJ5GE,327.57 2020-07-29,MA4B66MW5E27UADJ5GE,326.14 2020-07-30,MA4B66MW5E27UADJ5GE,324.82 2020-07-31,MA4B66MW5E27UADJ5GE,325.53 2020-08-03,MA4B66MW5E27UADJ5GE,329.32 2020-08-04,MA4B66MW5E27UADJ5GE,339.79 2020-08-05,MA4B66MW5E27UADJ5GE,339.97 2020-08-06,MA4B66MW5E27UADJ5GE,343.31 2020-08-07,MA4B66MW5E27UADJ5GE,340.91 2020-08-10,MA4B66MW5E27UADJ5GE,340.0 2020-08-11,MA4B66MW5E27UADJ5GE,332.43 2020-08-12,MA4B66MW5E27UADJ5GE,336.76 2020-08-13,MA4B66MW5E27UADJ5GE,335.7 2020-08-14,MA4B66MW5E27UADJ5GE,336.28 2020-08-17,MA4B66MW5E27UADJ5GE,339.96 2020-08-18,MA4B66MW5E27UADJ5GE,340.75 2020-08-19,MA4B66MW5E27UADJ5GE,340.9 2020-08-20,MA4B66MW5E27UADJ5GE,340.87 2020-08-21,MA4B66MW5E27UADJ5GE,344.61 2020-08-24,MA4B66MW5E27UADJ5GE,344.13 2020-08-25,MA4B66MW5E27UADJ5GE,344.71 2020-08-26,MA4B66MW5E27UADJ5GE,344.27 2020-08-27,MA4B66MW5E27UADJ5GE,346.36 2020-08-28,MA4B66MW5E27UADJ5GE,348.37 2020-08-31,MA4B66MW5E27UADJ5GE,347.66 2020-07-01,MA4B66MW5E27UADJ5VJ,46.06 2020-07-02,MA4B66MW5E27UADJ5VJ,45.63 2020-07-06,MA4B66MW5E27UADJ5VJ,46.42 2020-07-07,MA4B66MW5E27UADJ5VJ,45.63 2020-07-08,MA4B66MW5E27UADJ5VJ,45.81 2020-07-09,MA4B66MW5E27UADJ5VJ,46.7 2020-07-10,MA4B66MW5E27UADJ5VJ,46.66 2020-07-13,MA4B66MW5E27UADJ5VJ,45.93 2020-07-14,MA4B66MW5E27UADJ5VJ,46.26 2020-07-15,MA4B66MW5E27UADJ5VJ,46.4 2020-07-16,MA4B66MW5E27UADJ5VJ,45.78 2020-07-17,MA4B66MW5E27UADJ5VJ,46.75 2020-07-20,MA4B66MW5E27UADJ5VJ,46.97 2020-07-21,MA4B66MW5E27UADJ5VJ,47.02 2020-07-22,MA4B66MW5E27UADJ5VJ,46.9 2020-07-23,MA4B66MW5E27UADJ5VJ,47.41 2020-07-24,MA4B66MW5E27UADJ5VJ,46.4 2020-07-27,MA4B66MW5E27UADJ5VJ,47.19 2020-07-28,MA4B66MW5E27UADJ5VJ,46.28 2020-07-29,MA4B66MW5E27UADJ5VJ,46.71 2020-07-30,MA4B66MW5E27UADJ5VJ,46.44 2020-07-31,MA4B66MW5E27UADJ5VJ,47.1 2020-08-03,MA4B66MW5E27UADJ5VJ,47.16 2020-08-04,MA4B66MW5E27UADJ5VJ,47.67 2020-08-05,MA4B66MW5E27UADJ5VJ,47.33 2020-08-06,MA4B66MW5E27UADJ5VJ,47.77 2020-08-07,MA4B66MW5E27UADJ5VJ,47.43 2020-08-10,MA4B66MW5E27UADJ5VJ,47.73 2020-08-11,MA4B66MW5E27UADJ5VJ,47.19 2020-08-12,MA4B66MW5E27UADJ5VJ,48.1 2020-08-13,MA4B66MW5E27UADJ5VJ,42.72 2020-08-14,MA4B66MW5E27UADJ5VJ,42.5 2020-08-17,MA4B66MW5E27UADJ5VJ,42.09 2020-08-18,MA4B66MW5E27UADJ5VJ,41.98 2020-08-19,MA4B66MW5E27UADJ5VJ,41.87 2020-08-20,MA4B66MW5E27UADJ5VJ,42.31 2020-08-21,MA4B66MW5E27UADJ5VJ,42.25 2020-08-24,MA4B66MW5E27UADJ5VJ,42.18 2020-08-25,MA4B66MW5E27UADJ5VJ,41.96 2020-08-26,MA4B66MW5E27UADJ5VJ,42.25 2020-08-27,MA4B66MW5E27UADJ5VJ,42.29 2020-08-28,MA4B66MW5E27UADJ5VJ,42.2 2020-08-31,MA4B66MW5E27UADJ5VJ,42.22 2020-07-01,MA4B66MW5E3VLSECN,237.55 2020-07-02,MA4B66MW5E3VLSECN,233.42 2020-07-06,MA4B66MW5E3VLSECN,240.28 2020-07-07,MA4B66MW5E3VLSECN,240.86 2020-07-08,MA4B66MW5E3VLSECN,243.58 2020-07-09,MA4B66MW5E3VLSECN,244.5 2020-07-10,MA4B66MW5E3VLSECN,245.07 2020-07-13,MA4B66MW5E3VLSECN,239.0 2020-07-14,MA4B66MW5E3VLSECN,239.73 2020-07-15,MA4B66MW5E3VLSECN,240.28 2020-07-16,MA4B66MW5E3VLSECN,240.93 2020-07-17,MA4B66MW5E3VLSECN,242.03 2020-07-20,MA4B66MW5E3VLSECN,245.42 2020-07-21,MA4B66MW5E3VLSECN,241.75 2020-07-22,MA4B66MW5E3VLSECN,239.87 2020-07-23,MA4B66MW5E3VLSECN,232.6 2020-07-24,MA4B66MW5E3VLSECN,230.71 2020-07-27,MA4B66MW5E3VLSECN,233.5 2020-07-28,MA4B66MW5E3VLSECN,230.12 2020-07-29,MA4B66MW5E3VLSECN,233.29 2020-07-30,MA4B66MW5E3VLSECN,234.5 2020-07-31,MA4B66MW5E3VLSECN,253.67 2020-08-03,MA4B66MW5E3VLSECN,251.96 2020-08-04,MA4B66MW5E3VLSECN,249.83 2020-08-05,MA4B66MW5E3VLSECN,249.12 2020-08-06,MA4B66MW5E3VLSECN,265.28 2020-08-07,MA4B66MW5E3VLSECN,268.44 2020-08-10,MA4B66MW5E3VLSECN,263.0 2020-08-11,MA4B66MW5E3VLSECN,256.13 2020-08-12,MA4B66MW5E3VLSECN,259.89 2020-08-13,MA4B66MW5E3VLSECN,261.3 2020-08-14,MA4B66MW5E3VLSECN,261.24 2020-08-17,MA4B66MW5E3VLSECN,261.16 2020-08-18,MA4B66MW5E3VLSECN,262.34 2020-08-19,MA4B66MW5E3VLSECN,262.59 2020-08-20,MA4B66MW5E3VLSECN,269.01 2020-08-21,MA4B66MW5E3VLSECN,267.01 2020-08-24,MA4B66MW5E3VLSECN,271.39 2020-08-25,MA4B66MW5E3VLSECN,280.82 2020-08-26,MA4B66MW5E3VLSECN,303.91 2020-08-27,MA4B66MW5E3VLSECN,293.22 2020-08-28,MA4B66MW5E3VLSECN,293.66 2020-08-31,MA4B66MW5E3VLSECN,293.2 2020-07-01,MA4B66MW5E27U9ZPD6K,312.73 2020-07-02,MA4B66MW5E27U9ZPD6K,315.47 2020-07-06,MA4B66MW5E27U9ZPD6K,318.79 2020-07-07,MA4B66MW5E27U9ZPD6K,313.12 2020-07-08,MA4B66MW5E27U9ZPD6K,319.57 2020-07-09,MA4B66MW5E27U9ZPD6K,321.5 2020-07-10,MA4B66MW5E27U9ZPD6K,319.44 2020-07-13,MA4B66MW5E27U9ZPD6K,311.31 2020-07-14,MA4B66MW5E27U9ZPD6K,315.08 2020-07-15,MA4B66MW5E27U9ZPD6K,313.78 2020-07-16,MA4B66MW5E27U9ZPD6K,311.34 2020-07-17,MA4B66MW5E27U9ZPD6K,312.71 2020-07-20,MA4B66MW5E27U9ZPD6K,317.13 2020-07-21,MA4B66MW5E27U9ZPD6K,314.34 2020-07-22,MA4B66MW5E27U9ZPD6K,313.8 2020-07-23,MA4B66MW5E27U9ZPD6K,309.67 2020-07-24,MA4B66MW5E27U9ZPD6K,305.79 2020-07-27,MA4B66MW5E27U9ZPD6K,312.68 2020-07-28,MA4B66MW5E27U9ZPD6K,307.35 2020-07-29,MA4B66MW5E27U9ZPD6K,308.85 2020-07-30,MA4B66MW5E27U9ZPD6K,312.1 2020-07-31,MA4B66MW5E27U9ZPD6K,316.75 2020-08-03,MA4B66MW5E27U9ZPD6K,321.23 2020-08-04,MA4B66MW5E27U9ZPD6K,328.39 2020-08-05,MA4B66MW5E27U9ZPD6K,329.54 2020-08-06,MA4B66MW5E27U9ZPD6K,329.16 2020-08-07,MA4B66MW5E27U9ZPD6K,325.93 2020-08-10,MA4B66MW5E27U9ZPD6K,327.36 2020-08-11,MA4B66MW5E27U9ZPD6K,324.03 2020-08-12,MA4B66MW5E27U9ZPD6K,333.64 2020-08-13,MA4B66MW5E27U9ZPD6K,329.42 2020-08-14,MA4B66MW5E27U9ZPD6K,327.82 2020-08-17,MA4B66MW5E27U9ZPD6K,330.32 2020-08-18,MA4B66MW5E27U9ZPD6K,328.64 2020-08-19,MA4B66MW5E27U9ZPD6K,329.21 2020-08-20,MA4B66MW5E27U9ZPD6K,328.83 2020-08-21,MA4B66MW5E27U9ZPD6K,330.02 2020-08-24,MA4B66MW5E27U9ZPD6K,334.0 2020-08-25,MA4B66MW5E27U9ZPD6K,336.14 2020-08-26,MA4B66MW5E27U9ZPD6K,339.67 2020-08-27,MA4B66MW5E27U9ZPD6K,339.14 2020-08-28,MA4B66MW5E27U9ZPD6K,344.82 2020-08-31,MA4B66MW5E27U9ZPD6K,347.15 2020-07-01,MA4B66MW5E27UAGYZ49,1438.04 2020-07-02,MA4B66MW5E27UAGYZ49,1464.7 2020-07-06,MA4B66MW5E27UAGYZ49,1495.7 2020-07-07,MA4B66MW5E27UAGYZ49,1485.18 2020-07-08,MA4B66MW5E27UAGYZ49,1496.0 2020-07-09,MA4B66MW5E27UAGYZ49,1510.99 2020-07-10,MA4B66MW5E27UAGYZ49,1541.74 2020-07-13,MA4B66MW5E27UAGYZ49,1511.34 2020-07-14,MA4B66MW5E27UAGYZ49,1520.58 2020-07-15,MA4B66MW5E27UAGYZ49,1513.64 2020-07-16,MA4B66MW5E27UAGYZ49,1518.0 2020-07-17,MA4B66MW5E27UAGYZ49,1515.55 2020-07-20,MA4B66MW5E27UAGYZ49,1565.72 2020-07-21,MA4B66MW5E27UAGYZ49,1558.42 2020-07-22,MA4B66MW5E27UAGYZ49,1568.49 2020-07-23,MA4B66MW5E27UAGYZ49,1515.68 2020-07-24,MA4B66MW5E27UAGYZ49,1511.87 2020-07-27,MA4B66MW5E27UAGYZ49,1530.2 2020-07-28,MA4B66MW5E27UAGYZ49,1500.34 2020-07-29,MA4B66MW5E27UAGYZ49,1522.02 2020-07-30,MA4B66MW5E27UAGYZ49,1531.45 2020-07-31,MA4B66MW5E27UAGYZ49,1482.96 2020-08-03,MA4B66MW5E27UAGYZ49,1474.45 2020-08-04,MA4B66MW5E27UAGYZ49,1464.97 2020-08-05,MA4B66MW5E27UAGYZ49,1473.61 2020-08-06,MA4B66MW5E27UAGYZ49,1500.1 2020-08-07,MA4B66MW5E27UAGYZ49,1494.49 2020-08-10,MA4B66MW5E27UAGYZ49,1496.1 2020-08-11,MA4B66MW5E27UAGYZ49,1480.32 2020-08-12,MA4B66MW5E27UAGYZ49,1506.62 2020-08-13,MA4B66MW5E27UAGYZ49,1518.45 2020-08-14,MA4B66MW5E27UAGYZ49,1507.73 2020-08-17,MA4B66MW5E27UAGYZ49,1517.98 2020-08-18,MA4B66MW5E27UAGYZ49,1558.6 2020-08-19,MA4B66MW5E27UAGYZ49,1547.53 2020-08-20,MA4B66MW5E27UAGYZ49,1581.75 2020-08-21,MA4B66MW5E27UAGYZ49,1580.42 2020-08-24,MA4B66MW5E27UAGYZ49,1588.2 2020-08-25,MA4B66MW5E27UAGYZ49,1608.22 2020-08-26,MA4B66MW5E27UAGYZ49,1652.38 2020-08-27,MA4B66MW5E27UAGYZ49,1634.33 2020-08-28,MA4B66MW5E27UAGYZ49,1644.41 2020-08-31,MA4B66MW5E27UAGYZ49,1634.18 2020-07-01,MA4B66MW5E27UANLXV6,124.8 2020-07-02,MA4B66MW5E27UANLXV6,125.81 2020-07-06,MA4B66MW5E27UANLXV6,129.53 2020-07-07,MA4B66MW5E27UANLXV6,128.6 2020-07-08,MA4B66MW5E27UANLXV6,129.26 2020-07-09,MA4B66MW5E27UANLXV6,130.43 2020-07-10,MA4B66MW5E27UANLXV6,130.53 2020-07-13,MA4B66MW5E27UANLXV6,128.82 2020-07-14,MA4B66MW5E27UANLXV6,131.89 2020-07-15,MA4B66MW5E27UANLXV6,132.15 2020-07-16,MA4B66MW5E27UANLXV6,132.18 2020-07-17,MA4B66MW5E27UANLXV6,133.89 2020-07-20,MA4B66MW5E27UANLXV6,136.58 2020-07-21,MA4B66MW5E27UANLXV6,135.48 2020-07-22,MA4B66MW5E27UANLXV6,132.53 2020-07-23,MA4B66MW5E27UANLXV6,129.04 2020-07-24,MA4B66MW5E27UANLXV6,129.63 2020-07-27,MA4B66MW5E27UANLXV6,132.12 2020-07-28,MA4B66MW5E27UANLXV6,130.13 2020-07-29,MA4B66MW5E27UANLXV6,131.64 2020-07-30,MA4B66MW5E27UANLXV6,128.89 2020-07-31,MA4B66MW5E27UANLXV6,127.55 2020-08-03,MA4B66MW5E27UANLXV6,129.32 2020-08-04,MA4B66MW5E27UANLXV6,132.23 2020-08-05,MA4B66MW5E27UANLXV6,132.7 2020-08-06,MA4B66MW5E27UANLXV6,133.74 2020-08-07,MA4B66MW5E27UANLXV6,133.56 2020-08-10,MA4B66MW5E27UANLXV6,135.41 2020-08-11,MA4B66MW5E27UANLXV6,136.04 2020-08-12,MA4B66MW5E27UANLXV6,138.64 2020-08-13,MA4B66MW5E27UANLXV6,137.15 2020-08-14,MA4B66MW5E27UANLXV6,137.35 2020-08-17,MA4B66MW5E27UANLXV6,138.28 2020-08-18,MA4B66MW5E27UANLXV6,139.31 2020-08-19,MA4B66MW5E27UANLXV6,138.48 2020-08-20,MA4B66MW5E27UANLXV6,138.57 2020-08-21,MA4B66MW5E27UANLXV6,140.33 2020-08-24,MA4B66MW5E27UANLXV6,141.02 2020-08-25,MA4B66MW5E27UANLXV6,141.66 2020-08-26,MA4B66MW5E27UANLXV6,142.45 2020-08-27,MA4B66MW5E27UANLXV6,141.49 2020-08-28,MA4B66MW5E27UANLXV6,143.49 2020-08-31,MA4B66MW5E27UANLXV6,142.15 2020-07-01,MA4B66MW5E27UAM94N9,177.43 2020-07-02,MA4B66MW5E27UAM94N9,177.21 2020-07-06,MA4B66MW5E27UAM94N9,176.18 2020-07-07,MA4B66MW5E27UAM94N9,177.28 2020-07-08,MA4B66MW5E27UAM94N9,181.57 2020-07-09,MA4B66MW5E27UAM94N9,183.23 2020-07-10,MA4B66MW5E27UAM94N9,178.4 2020-07-13,MA4B66MW5E27UAM94N9,171.51 2020-07-14,MA4B66MW5E27UAM94N9,172.3 2020-07-15,MA4B66MW5E27UAM94N9,172.06 2020-07-16,MA4B66MW5E27UAM94N9,169.81 2020-07-17,MA4B66MW5E27UAM94N9,173.92 2020-07-20,MA4B66MW5E27UAM94N9,178.82 2020-07-21,MA4B66MW5E27UAM94N9,175.58 2020-07-22,MA4B66MW5E27UAM94N9,178.42 2020-07-23,MA4B66MW5E27UAM94N9,173.86 2020-07-24,MA4B66MW5E27UAM94N9,172.56 2020-07-27,MA4B66MW5E27UAM94N9,177.98 2020-07-28,MA4B66MW5E27UAM94N9,176.27 2020-07-29,MA4B66MW5E27UAM94N9,184.6 2020-07-30,MA4B66MW5E27UAM94N9,192.51 2020-07-31,MA4B66MW5E27UAM94N9,196.07 2020-08-03,MA4B66MW5E27UAM94N9,197.07 2020-08-04,MA4B66MW5E27UAM94N9,197.33 2020-08-05,MA4B66MW5E27UAM94N9,202.91 2020-08-06,MA4B66MW5E27UAM94N9,204.09 2020-08-07,MA4B66MW5E27UAM94N9,198.63 2020-08-10,MA4B66MW5E27UAM94N9,193.32 2020-08-11,MA4B66MW5E27UAM94N9,189.03 2020-08-12,MA4B66MW5E27UAM94N9,191.32 2020-08-13,MA4B66MW5E27UAM94N9,193.07 2020-08-14,MA4B66MW5E27UAM94N9,191.46 2020-08-17,MA4B66MW5E27UAM94N9,196.29 2020-08-18,MA4B66MW5E27UAM94N9,194.57 2020-08-19,MA4B66MW5E27UAM94N9,192.44 2020-08-20,MA4B66MW5E27UAM94N9,198.18 2020-08-21,MA4B66MW5E27UAM94N9,196.79 2020-08-24,MA4B66MW5E27UAM94N9,198.88 2020-08-25,MA4B66MW5E27UAM94N9,201.67 2020-08-26,MA4B66MW5E27UAM94N9,203.48 2020-08-27,MA4B66MW5E27UAM94N9,204.34 2020-08-28,MA4B66MW5E27UAM94N9,204.48 2020-08-31,MA4B66MW5E27UAM94N9,204.14 2020-07-01,MA4B66MW5E27UACX6QV,39.7 2020-07-02,MA4B66MW5E27UACX6QV,39.25 2020-07-06,MA4B66MW5E27UACX6QV,40.33 2020-07-07,MA4B66MW5E27UACX6QV,40.07 2020-07-08,MA4B66MW5E27UACX6QV,39.74 2020-07-09,MA4B66MW5E27UACX6QV,39.29 2020-07-10,MA4B66MW5E27UACX6QV,40.51 2020-07-13,MA4B66MW5E27UACX6QV,40.54 2020-07-14,MA4B66MW5E27UACX6QV,41.18 2020-07-15,MA4B66MW5E27UACX6QV,41.96 2020-07-16,MA4B66MW5E27UACX6QV,42.14 2020-07-17,MA4B66MW5E27UACX6QV,42.18 2020-07-20,MA4B66MW5E27UACX6QV,41.95 2020-07-21,MA4B66MW5E27UACX6QV,42.57 2020-07-22,MA4B66MW5E27UACX6QV,42.57 2020-07-23,MA4B66MW5E27UACX6QV,42.49 2020-07-24,MA4B66MW5E27UACX6QV,43.35 2020-07-27,MA4B66MW5E27UACX6QV,43.56 2020-07-28,MA4B66MW5E27UACX6QV,43.21 2020-07-29,MA4B66MW5E27UACX6QV,43.9 2020-07-30,MA4B66MW5E27UACX6QV,43.67 2020-07-31,MA4B66MW5E27UACX6QV,42.8 2020-08-03,MA4B66MW5E27UACX6QV,42.88 2020-08-04,MA4B66MW5E27UACX6QV,43.21 2020-08-05,MA4B66MW5E27UACX6QV,42.29 2020-08-06,MA4B66MW5E27UACX6QV,42.87 2020-08-07,MA4B66MW5E27UACX6QV,42.81 2020-08-10,MA4B66MW5E27UACX6QV,43.02 2020-08-11,MA4B66MW5E27UACX6QV,42.99 2020-08-12,MA4B66MW5E27UACX6QV,43.34 2020-08-13,MA4B66MW5E27UACX6QV,43.39 2020-08-14,MA4B66MW5E27UACX6QV,43.77 2020-08-17,MA4B66MW5E27UACX6QV,43.53 2020-08-18,MA4B66MW5E27UACX6QV,43.54 2020-08-19,MA4B66MW5E27UACX6QV,43.32 2020-08-20,MA4B66MW5E27UACX6QV,43.27 2020-08-21,MA4B66MW5E27UACX6QV,43.07 2020-08-24,MA4B66MW5E27UACX6QV,43.8 2020-08-25,MA4B66MW5E27UACX6QV,43.41 2020-08-26,MA4B66MW5E27UACX6QV,44.2 2020-08-27,MA4B66MW5E27UACX6QV,44.54 2020-08-28,MA4B66MW5E27UACX6QV,44.56 2020-08-31,MA4B66MW5E27UACX6QV,44.81 2020-07-01,MA4B66MW5E27U9YGMCG,255.12 2020-07-02,MA4B66MW5E27U9YGMCG,258.24 2020-07-06,MA4B66MW5E27U9YGMCG,256.25 2020-07-07,MA4B66MW5E27U9YGMCG,253.15 2020-07-08,MA4B66MW5E27U9YGMCG,251.59 2020-07-09,MA4B66MW5E27U9YGMCG,251.66 2020-07-10,MA4B66MW5E27U9YGMCG,249.04 2020-07-13,MA4B66MW5E27U9YGMCG,250.04 2020-07-14,MA4B66MW5E27U9YGMCG,253.09 2020-07-15,MA4B66MW5E27U9YGMCG,253.31 2020-07-16,MA4B66MW5E27U9YGMCG,255.2 2020-07-17,MA4B66MW5E27U9YGMCG,258.46 2020-07-20,MA4B66MW5E27U9YGMCG,260.95 2020-07-21,MA4B66MW5E27U9YGMCG,257.91 2020-07-22,MA4B66MW5E27U9YGMCG,256.98 2020-07-23,MA4B66MW5E27U9YGMCG,252.24 2020-07-24,MA4B66MW5E27U9YGMCG,247.98 2020-07-27,MA4B66MW5E27U9YGMCG,253.65 2020-07-28,MA4B66MW5E27U9YGMCG,255.27 2020-07-29,MA4B66MW5E27U9YGMCG,248.85 2020-07-30,MA4B66MW5E27U9YGMCG,246.14 2020-07-31,MA4B66MW5E27U9YGMCG,244.67 2020-08-03,MA4B66MW5E27U9YGMCG,247.36 2020-08-04,MA4B66MW5E27U9YGMCG,243.59 2020-08-05,MA4B66MW5E27U9YGMCG,241.47 2020-08-06,MA4B66MW5E27U9YGMCG,241.55 2020-08-07,MA4B66MW5E27U9YGMCG,240.69 2020-08-10,MA4B66MW5E27U9YGMCG,238.17 2020-08-11,MA4B66MW5E27U9YGMCG,234.65 2020-08-12,MA4B66MW5E27U9YGMCG,241.72 2020-08-13,MA4B66MW5E27U9YGMCG,240.46 2020-08-14,MA4B66MW5E27U9YGMCG,239.71 2020-08-17,MA4B66MW5E27U9YGMCG,243.01 2020-08-18,MA4B66MW5E27U9YGMCG,241.5 2020-08-19,MA4B66MW5E27U9YGMCG,240.49 2020-08-20,MA4B66MW5E27U9YGMCG,238.73 2020-08-21,MA4B66MW5E27U9YGMCG,237.64 2020-08-24,MA4B66MW5E27U9YGMCG,235.57 2020-08-25,MA4B66MW5E27U9YGMCG,248.22 2020-08-26,MA4B66MW5E27U9YGMCG,250.18 2020-08-27,MA4B66MW5E27U9YGMCG,252.81 2020-08-28,MA4B66MW5E27U9YGMCG,253.12 2020-08-31,MA4B66MW5E27U9YGMCG,253.32 2020-07-01,MA4B66MW5E27UALG33R,485.64 2020-07-02,MA4B66MW5E27UALG33R,476.89 2020-07-06,MA4B66MW5E27UALG33R,493.81 2020-07-07,MA4B66MW5E27UALG33R,493.16 2020-07-08,MA4B66MW5E27UALG33R,502.78 2020-07-09,MA4B66MW5E27UALG33R,507.76 2020-07-10,MA4B66MW5E27UALG33R,548.73 2020-07-13,MA4B66MW5E27UALG33R,525.5 2020-07-14,MA4B66MW5E27UALG33R,524.88 2020-07-15,MA4B66MW5E27UALG33R,523.26 2020-07-16,MA4B66MW5E27UALG33R,527.39 2020-07-17,MA4B66MW5E27UALG33R,492.99 2020-07-20,MA4B66MW5E27UALG33R,502.41 2020-07-21,MA4B66MW5E27UALG33R,490.1 2020-07-22,MA4B66MW5E27UALG33R,489.82 2020-07-23,MA4B66MW5E27UALG33R,477.58 2020-07-24,MA4B66MW5E27UALG33R,480.45 2020-07-27,MA4B66MW5E27UALG33R,495.65 2020-07-28,MA4B66MW5E27UALG33R,488.51 2020-07-29,MA4B66MW5E27UALG33R,484.48 2020-07-30,MA4B66MW5E27UALG33R,485.8 2020-07-31,MA4B66MW5E27UALG33R,488.88 2020-08-03,MA4B66MW5E27UALG33R,498.62 2020-08-04,MA4B66MW5E27UALG33R,509.64 2020-08-05,MA4B66MW5E27UALG33R,502.11 2020-08-06,MA4B66MW5E27UALG33R,509.08 2020-08-07,MA4B66MW5E27UALG33R,494.73 2020-08-10,MA4B66MW5E27UALG33R,483.38 2020-08-11,MA4B66MW5E27UALG33R,466.93 2020-08-12,MA4B66MW5E27UALG33R,475.47 2020-08-13,MA4B66MW5E27UALG33R,481.33 2020-08-14,MA4B66MW5E27UALG33R,482.68 2020-08-17,MA4B66MW5E27UALG33R,482.35 2020-08-18,MA4B66MW5E27UALG33R,491.87 2020-08-19,MA4B66MW5E27UALG33R,484.53 2020-08-20,MA4B66MW5E27UALG33R,497.9 2020-08-21,MA4B66MW5E27UALG33R,492.31 2020-08-24,MA4B66MW5E27UALG33R,488.81 2020-08-25,MA4B66MW5E27UALG33R,490.58 2020-08-26,MA4B66MW5E27UALG33R,547.53 2020-08-27,MA4B66MW5E27UALG33R,526.27 2020-08-28,MA4B66MW5E27UALG33R,523.89 2020-08-31,MA4B66MW5E27UALG33R,529.56 2020-07-01,MA4B66MW5E27UAGYZ4D,1442.0 2020-07-02,MA4B66MW5E27UAGYZ4D,1469.93 2020-07-06,MA4B66MW5E27UAGYZ4D,1499.65 2020-07-07,MA4B66MW5E27UAGYZ4D,1489.92 2020-07-08,MA4B66MW5E27UAGYZ4D,1503.6 2020-07-09,MA4B66MW5E27UAGYZ4D,1518.66 2020-07-10,MA4B66MW5E27UAGYZ4D,1539.01 2020-07-13,MA4B66MW5E27UAGYZ4D,1512.23 2020-07-14,MA4B66MW5E27UAGYZ4D,1520.86 2020-07-15,MA4B66MW5E27UAGYZ4D,1516.88 2020-07-16,MA4B66MW5E27UAGYZ4D,1514.92 2020-07-17,MA4B66MW5E27UAGYZ4D,1516.85 2020-07-20,MA4B66MW5E27UAGYZ4D,1563.84 2020-07-21,MA4B66MW5E27UAGYZ4D,1555.92 2020-07-22,MA4B66MW5E27UAGYZ4D,1564.85 2020-07-23,MA4B66MW5E27UAGYZ4D,1516.75 2020-07-24,MA4B66MW5E27UAGYZ4D,1508.21 2020-07-27,MA4B66MW5E27UAGYZ4D,1529.43 2020-07-28,MA4B66MW5E27UAGYZ4D,1503.65 2020-07-29,MA4B66MW5E27UAGYZ4D,1523.51 2020-07-30,MA4B66MW5E27UAGYZ4D,1538.37 2020-07-31,MA4B66MW5E27UAGYZ4D,1487.95 2020-08-03,MA4B66MW5E27UAGYZ4D,1482.76 2020-08-04,MA4B66MW5E27UAGYZ4D,1473.3 2020-08-05,MA4B66MW5E27UAGYZ4D,1479.09 2020-08-06,MA4B66MW5E27UAGYZ4D,1504.95 2020-08-07,MA4B66MW5E27UAGYZ4D,1498.37 2020-08-10,MA4B66MW5E27UAGYZ4D,1496.82 2020-08-11,MA4B66MW5E27UAGYZ4D,1480.54 2020-08-12,MA4B66MW5E27UAGYZ4D,1507.24 2020-08-13,MA4B66MW5E27UAGYZ4D,1516.65 2020-08-14,MA4B66MW5E27UAGYZ4D,1504.63 2020-08-17,MA4B66MW5E27UAGYZ4D,1516.24 2020-08-18,MA4B66MW5E27UAGYZ4D,1555.78 2020-08-19,MA4B66MW5E27UAGYZ4D,1544.61 2020-08-20,MA4B66MW5E27UAGYZ4D,1576.25 2020-08-21,MA4B66MW5E27UAGYZ4D,1575.57 2020-08-24,MA4B66MW5E27UAGYZ4D,1585.15 2020-08-25,MA4B66MW5E27UAGYZ4D,1605.85 2020-08-26,MA4B66MW5E27UAGYZ4D,1644.13 2020-08-27,MA4B66MW5E27UAGYZ4D,1628.52 2020-08-28,MA4B66MW5E27UAGYZ4D,1639.43 2020-08-31,MA4B66MW5E27UAGYZ4D,1629.53 2020-07-01,MA4B66MW5E27U9VBB94,91.0275 2020-07-02,MA4B66MW5E27U9VBB94,91.0275 2020-07-06,MA4B66MW5E27U9VBB94,93.4625 2020-07-07,MA4B66MW5E27U9VBB94,93.1725 2020-07-08,MA4B66MW5E27U9VBB94,95.3425 2020-07-09,MA4B66MW5E27U9VBB94,95.6825 2020-07-10,MA4B66MW5E27U9VBB94,95.92 2020-07-13,MA4B66MW5E27U9VBB94,95.4775 2020-07-14,MA4B66MW5E27U9VBB94,97.0575 2020-07-15,MA4B66MW5E27U9VBB94,97.725 2020-07-16,MA4B66MW5E27U9VBB94,96.5225 2020-07-17,MA4B66MW5E27U9VBB94,96.3275 2020-07-20,MA4B66MW5E27U9VBB94,98.3575 2020-07-21,MA4B66MW5E27U9VBB94,97.0 2020-07-22,MA4B66MW5E27U9VBB94,97.2725 2020-07-23,MA4B66MW5E27U9VBB94,92.845 2020-07-24,MA4B66MW5E27U9VBB94,92.615 2020-07-27,MA4B66MW5E27U9VBB94,94.81 2020-07-28,MA4B66MW5E27U9VBB94,93.2525 2020-07-29,MA4B66MW5E27U9VBB94,95.04 2020-07-30,MA4B66MW5E27U9VBB94,96.19 2020-07-31,MA4B66MW5E27U9VBB94,106.26 2020-08-03,MA4B66MW5E27U9VBB94,108.9375 2020-08-04,MA4B66MW5E27U9VBB94,109.665 2020-08-05,MA4B66MW5E27U9VBB94,110.0625 2020-08-06,MA4B66MW5E27U9VBB94,113.9025 2020-08-07,MA4B66MW5E27U9VBB94,111.1125 2020-08-10,MA4B66MW5E27U9VBB94,112.7275 2020-08-11,MA4B66MW5E27U9VBB94,109.375 2020-08-12,MA4B66MW5E27U9VBB94,113.01 2020-08-13,MA4B66MW5E27U9VBB94,115.01 2020-08-14,MA4B66MW5E27U9VBB94,114.9075 2020-08-17,MA4B66MW5E27U9VBB94,114.6075 2020-08-18,MA4B66MW5E27U9VBB94,115.5625 2020-08-19,MA4B66MW5E27U9VBB94,115.7075 2020-08-20,MA4B66MW5E27U9VBB94,118.275 2020-08-21,MA4B66MW5E27U9VBB94,124.37 2020-08-24,MA4B66MW5E27U9VBB94,125.8575 2020-08-25,MA4B66MW5E27U9VBB94,124.825 2020-08-26,MA4B66MW5E27U9VBB94,126.5225 2020-08-27,MA4B66MW5E27U9VBB94,125.01 2020-08-28,MA4B66MW5E27U9VBB94,124.8075 2020-08-31,MA4B66MW5E27U9VBB94,129.04 2020-07-01,MA4B66MW5E27UAL9SUX,204.7 2020-07-02,MA4B66MW5E27UAL9SUX,206.26 2020-07-06,MA4B66MW5E27UAL9SUX,210.7 2020-07-07,MA4B66MW5E27UAL9SUX,208.25 2020-07-08,MA4B66MW5E27UAL9SUX,212.83 2020-07-09,MA4B66MW5E27UAL9SUX,214.32 2020-07-10,MA4B66MW5E27UAL9SUX,213.67 2020-07-13,MA4B66MW5E27UAL9SUX,207.07 2020-07-14,MA4B66MW5E27UAL9SUX,208.35 2020-07-15,MA4B66MW5E27UAL9SUX,208.04 2020-07-16,MA4B66MW5E27UAL9SUX,203.92 2020-07-17,MA4B66MW5E27UAL9SUX,202.88 2020-07-20,MA4B66MW5E27UAL9SUX,211.6 2020-07-21,MA4B66MW5E27UAL9SUX,208.75 2020-07-22,MA4B66MW5E27UAL9SUX,211.75 2020-07-23,MA4B66MW5E27UAL9SUX,202.54 2020-07-24,MA4B66MW5E27UAL9SUX,201.3 2020-07-27,MA4B66MW5E27UAL9SUX,203.85 2020-07-28,MA4B66MW5E27UAL9SUX,202.02 2020-07-29,MA4B66MW5E27UAL9SUX,204.06 2020-07-30,MA4B66MW5E27UAL9SUX,203.9 2020-07-31,MA4B66MW5E27UAL9SUX,205.01 2020-08-03,MA4B66MW5E27UAL9SUX,216.54 2020-08-04,MA4B66MW5E27UAL9SUX,213.29 2020-08-05,MA4B66MW5E27UAL9SUX,212.94 2020-08-06,MA4B66MW5E27UAL9SUX,216.35 2020-08-07,MA4B66MW5E27UAL9SUX,212.48 2020-08-10,MA4B66MW5E27UAL9SUX,208.25 2020-08-11,MA4B66MW5E27UAL9SUX,203.38 2020-08-12,MA4B66MW5E27UAL9SUX,209.19 2020-08-13,MA4B66MW5E27UAL9SUX,208.7 2020-08-14,MA4B66MW5E27UAL9SUX,208.9 2020-08-17,MA4B66MW5E27UAL9SUX,210.28 2020-08-18,MA4B66MW5E27UAL9SUX,211.49 2020-08-19,MA4B66MW5E27UAL9SUX,209.7 2020-08-20,MA4B66MW5E27UAL9SUX,214.58 2020-08-21,MA4B66MW5E27UAL9SUX,213.02 2020-08-24,MA4B66MW5E27UAL9SUX,213.69 2020-08-25,MA4B66MW5E27UAL9SUX,216.47 2020-08-26,MA4B66MW5E27UAL9SUX,221.15 2020-08-27,MA4B66MW5E27UAL9SUX,226.58 2020-08-28,MA4B66MW5E27UAL9SUX,228.91 2020-08-31,MA4B66MW5E27UAL9SUX,225.53 2020-07-01,MA4B66MW5E27UAKV9P7,214.77 2020-07-02,MA4B66MW5E27UAKV9P7,221.26 2020-07-06,MA4B66MW5E27UAKV9P7,224.65 2020-07-07,MA4B66MW5E27UAKV9P7,224.53 2020-07-08,MA4B66MW5E27UAKV9P7,221.52 2020-07-09,MA4B66MW5E27UAKV9P7,221.95 2020-07-10,MA4B66MW5E27UAKV9P7,225.95 2020-07-13,MA4B66MW5E27UAKV9P7,225.82 2020-07-14,MA4B66MW5E27UAKV9P7,233.13 2020-07-15,MA4B66MW5E27UAKV9P7,237.63 2020-07-16,MA4B66MW5E27UAKV9P7,238.89 2020-07-17,MA4B66MW5E27UAKV9P7,242.6 2020-07-20,MA4B66MW5E27UAKV9P7,240.32 2020-07-21,MA4B66MW5E27UAKV9P7,244.0 2020-07-22,MA4B66MW5E27UAKV9P7,245.34 2020-07-23,MA4B66MW5E27UAKV9P7,245.98 2020-07-24,MA4B66MW5E27UAKV9P7,244.07 2020-07-27,MA4B66MW5E27UAKV9P7,246.86 2020-07-28,MA4B66MW5E27UAKV9P7,243.7 2020-07-29,MA4B66MW5E27UAKV9P7,244.71 2020-07-30,MA4B66MW5E27UAKV9P7,242.02 2020-07-31,MA4B66MW5E27UAKV9P7,245.11 2020-08-03,MA4B66MW5E27UAKV9P7,244.31 2020-08-04,MA4B66MW5E27UAKV9P7,243.85 2020-08-05,MA4B66MW5E27UAKV9P7,248.27 2020-08-06,MA4B66MW5E27UAKV9P7,246.66 2020-08-07,MA4B66MW5E27UAKV9P7,245.31 2020-08-10,MA4B66MW5E27UAKV9P7,243.34 2020-08-11,MA4B66MW5E27UAKV9P7,244.85 2020-08-12,MA4B66MW5E27UAKV9P7,248.63 2020-08-13,MA4B66MW5E27UAKV9P7,249.24 2020-08-14,MA4B66MW5E27UAKV9P7,248.5 2020-08-17,MA4B66MW5E27UAKV9P7,248.43 2020-08-18,MA4B66MW5E27UAKV9P7,246.88 2020-08-19,MA4B66MW5E27UAKV9P7,247.24 2020-08-20,MA4B66MW5E27UAKV9P7,245.31 2020-08-21,MA4B66MW5E27UAKV9P7,244.3 2020-08-24,MA4B66MW5E27UAKV9P7,248.91 2020-08-25,MA4B66MW5E27UAKV9P7,250.08 2020-08-26,MA4B66MW5E27UAKV9P7,252.88 2020-08-27,MA4B66MW5E27UAKV9P7,250.93 2020-08-28,MA4B66MW5E27UAKV9P7,253.57 2020-08-31,MA4B66MW5E27UAKV9P7,249.74 2020-07-01,MA4B66MW5E27U9VBBEQ,91.64 2020-07-02,MA4B66MW5E27U9VBBEQ,92.23 2020-07-06,MA4B66MW5E27U9VBBEQ,92.97 2020-07-07,MA4B66MW5E27U9VBBEQ,92.46 2020-07-08,MA4B66MW5E27U9VBBEQ,92.58 2020-07-09,MA4B66MW5E27U9VBBEQ,93.67 2020-07-10,MA4B66MW5E27U9VBBEQ,93.04 2020-07-13,MA4B66MW5E27U9VBBEQ,93.09 2020-07-14,MA4B66MW5E27U9VBBEQ,94.82 2020-07-15,MA4B66MW5E27U9VBBEQ,96.73 2020-07-16,MA4B66MW5E27U9VBBEQ,96.4 2020-07-17,MA4B66MW5E27U9VBBEQ,99.25 2020-07-20,MA4B66MW5E27U9VBBEQ,99.08 2020-07-21,MA4B66MW5E27U9VBBEQ,98.19 2020-07-22,MA4B66MW5E27U9VBBEQ,100.19 2020-07-23,MA4B66MW5E27U9VBBEQ,100.82 2020-07-24,MA4B66MW5E27U9VBBEQ,98.95 2020-07-27,MA4B66MW5E27U9VBBEQ,99.99 2020-07-28,MA4B66MW5E27U9VBBEQ,99.86 2020-07-29,MA4B66MW5E27U9VBBEQ,102.52 2020-07-30,MA4B66MW5E27U9VBBEQ,101.53 2020-07-31,MA4B66MW5E27U9VBBEQ,100.64 2020-08-03,MA4B66MW5E27U9VBBEQ,100.68 2020-08-04,MA4B66MW5E27U9VBBEQ,99.93 2020-08-05,MA4B66MW5E27U9VBBEQ,102.14 2020-08-06,MA4B66MW5E27U9VBBEQ,101.75 2020-08-07,MA4B66MW5E27U9VBBEQ,101.23 2020-08-10,MA4B66MW5E27U9VBBEQ,100.04 2020-08-11,MA4B66MW5E27U9VBBEQ,99.06 2020-08-12,MA4B66MW5E27U9VBBEQ,100.65 2020-08-13,MA4B66MW5E27U9VBBEQ,101.1 2020-08-14,MA4B66MW5E27U9VBBEQ,99.99 2020-08-17,MA4B66MW5E27U9VBBEQ,100.71 2020-08-18,MA4B66MW5E27U9VBBEQ,100.96 2020-08-19,MA4B66MW5E27U9VBBEQ,100.48 2020-08-20,MA4B66MW5E27U9VBBEQ,101.63 2020-08-21,MA4B66MW5E27U9VBBEQ,102.4 2020-08-24,MA4B66MW5E27U9VBBEQ,101.39 2020-08-25,MA4B66MW5E27U9VBBEQ,102.47 2020-08-26,MA4B66MW5E27U9VBBEQ,103.19 2020-08-27,MA4B66MW5E27U9VBBEQ,111.29 2020-08-28,MA4B66MW5E27U9VBBEQ,110.79 2020-08-31,MA4B66MW5E27U9VBBEQ,109.47 2020-07-01,MA4B66MW5E27U9ZPDGH,23.26 2020-07-02,MA4B66MW5E27U9ZPDGH,23.29 2020-07-06,MA4B66MW5E27U9ZPDGH,23.66 2020-07-07,MA4B66MW5E27U9ZPDGH,23.01 2020-07-08,MA4B66MW5E27U9ZPDGH,23.1 2020-07-09,MA4B66MW5E27U9ZPDGH,22.77 2020-07-10,MA4B66MW5E27U9ZPDGH,24.02 2020-07-13,MA4B66MW5E27U9ZPDGH,24.19 2020-07-14,MA4B66MW5E27U9ZPDGH,24.14 2020-07-15,MA4B66MW5E27U9ZPDGH,24.6 2020-07-16,MA4B66MW5E27U9ZPDGH,23.93 2020-07-17,MA4B66MW5E27U9ZPDGH,23.22 2020-07-20,MA4B66MW5E27U9ZPDGH,23.58 2020-07-21,MA4B66MW5E27U9ZPDGH,24.42 2020-07-22,MA4B66MW5E27U9ZPDGH,24.31 2020-07-23,MA4B66MW5E27U9ZPDGH,24.54 2020-07-24,MA4B66MW5E27U9ZPDGH,24.35 2020-07-27,MA4B66MW5E27U9ZPDGH,24.14 2020-07-28,MA4B66MW5E27U9ZPDGH,24.36 2020-07-29,MA4B66MW5E27U9ZPDGH,25.27 2020-07-30,MA4B66MW5E27U9ZPDGH,24.84 2020-07-31,MA4B66MW5E27U9ZPDGH,24.88 2020-08-03,MA4B66MW5E27U9ZPDGH,24.99 2020-08-04,MA4B66MW5E27U9ZPDGH,25.01 2020-08-05,MA4B66MW5E27U9ZPDGH,25.39 2020-08-06,MA4B66MW5E27U9ZPDGH,25.47 2020-08-07,MA4B66MW5E27U9ZPDGH,26.11 2020-08-10,MA4B66MW5E27U9ZPDGH,26.56 2020-08-11,MA4B66MW5E27U9ZPDGH,26.92 2020-08-12,MA4B66MW5E27U9ZPDGH,26.73 2020-08-13,MA4B66MW5E27U9ZPDGH,26.35 2020-08-14,MA4B66MW5E27U9ZPDGH,26.47 2020-08-17,MA4B66MW5E27U9ZPDGH,25.9 2020-08-18,MA4B66MW5E27U9ZPDGH,25.53 2020-08-19,MA4B66MW5E27U9ZPDGH,25.5 2020-08-20,MA4B66MW5E27U9ZPDGH,25.1 2020-08-21,MA4B66MW5E27U9ZPDGH,24.98 2020-08-24,MA4B66MW5E27U9ZPDGH,25.69 2020-08-25,MA4B66MW5E27U9ZPDGH,26.0 2020-08-26,MA4B66MW5E27U9ZPDGH,25.56 2020-08-27,MA4B66MW5E27U9ZPDGH,26.05 2020-08-28,MA4B66MW5E27U9ZPDGH,26.3 2020-08-31,MA4B66MW5E27U9ZPDGH,25.74 2020-07-01,MA4B66MW5E27UAL9SRL,78.12 2020-07-02,MA4B66MW5E27UAL9SRL,78.78 2020-07-06,MA4B66MW5E27UAL9SRL,79.58 2020-07-07,MA4B66MW5E27UAL9SRL,78.72 2020-07-08,MA4B66MW5E27UAL9SRL,77.92 2020-07-09,MA4B66MW5E27UAL9SRL,76.69 2020-07-10,MA4B66MW5E27UAL9SRL,76.73 2020-07-13,MA4B66MW5E27UAL9SRL,77.35 2020-07-14,MA4B66MW5E27UAL9SRL,78.25 2020-07-15,MA4B66MW5E27UAL9SRL,79.44 2020-07-16,MA4B66MW5E27UAL9SRL,79.4 2020-07-17,MA4B66MW5E27UAL9SRL,79.87 2020-07-20,MA4B66MW5E27UAL9SRL,79.41 2020-07-21,MA4B66MW5E27UAL9SRL,78.89 2020-07-22,MA4B66MW5E27UAL9SRL,78.78 2020-07-23,MA4B66MW5E27UAL9SRL,78.06 2020-07-24,MA4B66MW5E27UAL9SRL,77.1 2020-07-27,MA4B66MW5E27UAL9SRL,78.93 2020-07-28,MA4B66MW5E27UAL9SRL,79.69 2020-07-29,MA4B66MW5E27UAL9SRL,79.35 2020-07-30,MA4B66MW5E27UAL9SRL,78.99 2020-07-31,MA4B66MW5E27UAL9SRL,80.24 2020-08-03,MA4B66MW5E27UAL9SRL,82.54 2020-08-04,MA4B66MW5E27UAL9SRL,81.67 2020-08-05,MA4B66MW5E27UAL9SRL,81.64 2020-08-06,MA4B66MW5E27UAL9SRL,81.05 2020-08-07,MA4B66MW5E27UAL9SRL,81.02 2020-08-10,MA4B66MW5E27UAL9SRL,80.91 2020-08-11,MA4B66MW5E27UAL9SRL,80.91 2020-08-12,MA4B66MW5E27UAL9SRL,82.68 2020-08-13,MA4B66MW5E27UAL9SRL,83.54 2020-08-14,MA4B66MW5E27UAL9SRL,83.48 2020-08-17,MA4B66MW5E27UAL9SRL,84.76 2020-08-18,MA4B66MW5E27UAL9SRL,84.56 2020-08-19,MA4B66MW5E27UAL9SRL,85.03 2020-08-20,MA4B66MW5E27UAL9SRL,85.03 2020-08-21,MA4B66MW5E27UAL9SRL,84.98 2020-08-24,MA4B66MW5E27UAL9SRL,85.42 2020-08-25,MA4B66MW5E27UAL9SRL,85.68 2020-08-26,MA4B66MW5E27UAL9SRL,85.54 2020-08-27,MA4B66MW5E27UAL9SRL,85.82 2020-08-28,MA4B66MW5E27UAL9SRL,85.65 2020-08-31,MA4B66MW5E27UAL9SRL,85.27 2020-07-01,MA4B66MW5E27UA4479J,59.43 2020-07-02,MA4B66MW5E27UA4479J,59.14 2020-07-06,MA4B66MW5E27UA4479J,60.19 2020-07-07,MA4B66MW5E27UA4479J,60.29 2020-07-08,MA4B66MW5E27UA4479J,59.56 2020-07-09,MA4B66MW5E27UA4479J,58.11 2020-07-10,MA4B66MW5E27UA4479J,57.43 2020-07-13,MA4B66MW5E27UA4479J,57.75 2020-07-14,MA4B66MW5E27UA4479J,57.89 2020-07-15,MA4B66MW5E27UA4479J,59.27 2020-07-16,MA4B66MW5E27UA4479J,59.32 2020-07-17,MA4B66MW5E27UA4479J,60.39 2020-07-20,MA4B66MW5E27UA4479J,59.46 2020-07-21,MA4B66MW5E27UA4479J,59.8 2020-07-22,MA4B66MW5E27UA4479J,59.85 2020-07-23,MA4B66MW5E27UA4479J,59.87 2020-07-24,MA4B66MW5E27UA4479J,57.85 2020-07-27,MA4B66MW5E27UA4479J,58.05 2020-07-28,MA4B66MW5E27UA4479J,59.43 2020-07-29,MA4B66MW5E27UA4479J,59.15 2020-07-30,MA4B66MW5E27UA4479J,58.87 2020-07-31,MA4B66MW5E27UA4479J,58.66 2020-08-03,MA4B66MW5E27UA4479J,59.1 2020-08-04,MA4B66MW5E27UA4479J,59.47 2020-08-05,MA4B66MW5E27UA4479J,59.69 2020-08-06,MA4B66MW5E27UA4479J,61.33 2020-08-07,MA4B66MW5E27UA4479J,61.02 2020-08-10,MA4B66MW5E27UA4479J,61.82 2020-08-11,MA4B66MW5E27UA4479J,63.13 2020-08-12,MA4B66MW5E27UA4479J,63.64 2020-08-13,MA4B66MW5E27UA4479J,63.24 2020-08-14,MA4B66MW5E27UA4479J,63.16 2020-08-17,MA4B66MW5E27UA4479J,63.62 2020-08-18,MA4B66MW5E27UA4479J,63.03 2020-08-19,MA4B66MW5E27UA4479J,63.09 2020-08-20,MA4B66MW5E27UA4479J,62.22 2020-08-21,MA4B66MW5E27UA4479J,62.19 2020-08-24,MA4B66MW5E27UA4479J,62.41 2020-08-25,MA4B66MW5E27UA4479J,62.71 2020-08-26,MA4B66MW5E27UA4479J,62.28 2020-08-27,MA4B66MW5E27UA4479J,62.51 2020-08-28,MA4B66MW5E27UA4479J,62.37 2020-08-31,MA4B66MW5E27UA4479J,62.2 2020-07-01,MA4B66MW5E27UADJ5RL,191.89 2020-07-02,MA4B66MW5E27UADJ5RL,192.53 2020-07-06,MA4B66MW5E27UADJ5RL,197.72 2020-07-07,MA4B66MW5E27UADJ5RL,196.38 2020-07-08,MA4B66MW5E27UADJ5RL,200.28 2020-07-09,MA4B66MW5E27UADJ5RL,200.74 2020-07-10,MA4B66MW5E27UADJ5RL,198.88 2020-07-13,MA4B66MW5E27UADJ5RL,188.34 2020-07-14,MA4B66MW5E27UADJ5RL,189.56 2020-07-15,MA4B66MW5E27UADJ5RL,188.09 2020-07-16,MA4B66MW5E27UADJ5RL,185.51 2020-07-17,MA4B66MW5E27UADJ5RL,187.78 2020-07-20,MA4B66MW5E27UADJ5RL,195.09 2020-07-21,MA4B66MW5E27UADJ5RL,190.8 2020-07-22,MA4B66MW5E27UADJ5RL,191.31 2020-07-23,MA4B66MW5E27UADJ5RL,188.54 2020-07-24,MA4B66MW5E27UADJ5RL,188.49 2020-07-27,MA4B66MW5E27UADJ5RL,190.96 2020-07-28,MA4B66MW5E27UADJ5RL,189.5 2020-07-29,MA4B66MW5E27UADJ5RL,193.61 2020-07-30,MA4B66MW5E27UADJ5RL,191.7 2020-07-31,MA4B66MW5E27UADJ5RL,194.85 2020-08-03,MA4B66MW5E27UADJ5RL,203.19 2020-08-04,MA4B66MW5E27UADJ5RL,201.41 2020-08-05,MA4B66MW5E27UADJ5RL,202.64 2020-08-06,MA4B66MW5E27UADJ5RL,207.79 2020-08-07,MA4B66MW5E27UADJ5RL,201.05 2020-08-10,MA4B66MW5E27UADJ5RL,197.16 2020-08-11,MA4B66MW5E27UADJ5RL,191.99 2020-08-12,MA4B66MW5E27UADJ5RL,192.62 2020-08-13,MA4B66MW5E27UADJ5RL,195.14 2020-08-14,MA4B66MW5E27UADJ5RL,193.46 2020-08-17,MA4B66MW5E27UADJ5RL,196.74 2020-08-18,MA4B66MW5E27UADJ5RL,204.01 2020-08-19,MA4B66MW5E27UADJ5RL,205.11 2020-08-20,MA4B66MW5E27UADJ5RL,209.51 2020-08-21,MA4B66MW5E27UADJ5RL,207.53 2020-08-24,MA4B66MW5E27UADJ5RL,208.46 2020-08-25,MA4B66MW5E27UADJ5RL,216.05 2020-08-26,MA4B66MW5E27UADJ5RL,272.32 2020-08-27,MA4B66MW5E27UADJ5RL,276.32 2020-08-28,MA4B66MW5E27UADJ5RL,271.1 2020-08-31,MA4B66MW5E27UADJ5RL,272.65 2020-07-01,MA4B66MW5E27UANT8PM,54.67 2020-07-02,MA4B66MW5E27UANT8PM,54.79 2020-07-06,MA4B66MW5E27UANT8PM,55.24 2020-07-07,MA4B66MW5E27UANT8PM,55.04 2020-07-08,MA4B66MW5E27UANT8PM,55.38 2020-07-09,MA4B66MW5E27UANT8PM,53.94 2020-07-10,MA4B66MW5E27UANT8PM,54.49 2020-07-13,MA4B66MW5E27UANT8PM,54.45 2020-07-14,MA4B66MW5E27UANT8PM,55.44 2020-07-15,MA4B66MW5E27UANT8PM,55.06 2020-07-16,MA4B66MW5E27UANT8PM,55.78 2020-07-17,MA4B66MW5E27UANT8PM,56.3 2020-07-20,MA4B66MW5E27UANT8PM,55.87 2020-07-21,MA4B66MW5E27UANT8PM,55.84 2020-07-22,MA4B66MW5E27UANT8PM,55.75 2020-07-23,MA4B66MW5E27UANT8PM,55.85 2020-07-24,MA4B66MW5E27UANT8PM,56.85 2020-07-27,MA4B66MW5E27UANT8PM,56.87 2020-07-28,MA4B66MW5E27UANT8PM,57.48 2020-07-29,MA4B66MW5E27UANT8PM,57.45 2020-07-30,MA4B66MW5E27UANT8PM,57.3 2020-07-31,MA4B66MW5E27UANT8PM,57.48 2020-08-03,MA4B66MW5E27UANT8PM,57.24 2020-08-04,MA4B66MW5E27UANT8PM,57.91 2020-08-05,MA4B66MW5E27UANT8PM,57.54 2020-08-06,MA4B66MW5E27UANT8PM,57.83 2020-08-07,MA4B66MW5E27UANT8PM,58.53 2020-08-10,MA4B66MW5E27UANT8PM,58.99 2020-08-11,MA4B66MW5E27UANT8PM,58.51 2020-08-12,MA4B66MW5E27UANT8PM,58.6 2020-08-13,MA4B66MW5E27UANT8PM,58.52 2020-08-14,MA4B66MW5E27UANT8PM,58.79 2020-08-17,MA4B66MW5E27UANT8PM,58.78 2020-08-18,MA4B66MW5E27UANT8PM,59.18 2020-08-19,MA4B66MW5E27UANT8PM,59.05 2020-08-20,MA4B66MW5E27UANT8PM,58.96 2020-08-21,MA4B66MW5E27UANT8PM,58.99 2020-08-24,MA4B66MW5E27UANT8PM,59.57 2020-08-25,MA4B66MW5E27UANT8PM,59.42 2020-08-26,MA4B66MW5E27UANT8PM,59.46 2020-08-27,MA4B66MW5E27UANT8PM,59.43 2020-08-28,MA4B66MW5E27UANT8PM,59.26 2020-08-31,MA4B66MW5E27UANT8PM,59.27 2020-07-01,MA4B66MW5E27UAE4MDU,177.16 2020-07-02,MA4B66MW5E27UAE4MDU,179.68 2020-07-06,MA4B66MW5E27UAE4MDU,183.0 2020-07-07,MA4B66MW5E27UAE4MDU,182.04 2020-07-08,MA4B66MW5E27UAE4MDU,184.42 2020-07-09,MA4B66MW5E27UAE4MDU,185.47 2020-07-10,MA4B66MW5E27UAE4MDU,183.67 2020-07-13,MA4B66MW5E27UAE4MDU,182.41 2020-07-14,MA4B66MW5E27UAE4MDU,185.45 2020-07-15,MA4B66MW5E27UAE4MDU,188.34 2020-07-16,MA4B66MW5E27UAE4MDU,188.81 2020-07-17,MA4B66MW5E27UAE4MDU,190.95 2020-07-20,MA4B66MW5E27UAE4MDU,193.37 2020-07-21,MA4B66MW5E27UAE4MDU,195.0 2020-07-22,MA4B66MW5E27UAE4MDU,195.61 2020-07-23,MA4B66MW5E27UAE4MDU,199.22 2020-07-24,MA4B66MW5E27UAE4MDU,195.27 2020-07-27,MA4B66MW5E27UAE4MDU,198.98 2020-07-28,MA4B66MW5E27UAE4MDU,199.01 2020-07-29,MA4B66MW5E27UAE4MDU,202.13 2020-07-30,MA4B66MW5E27UAE4MDU,202.14 2020-07-31,MA4B66MW5E27UAE4MDU,203.8 2020-08-03,MA4B66MW5E27UAE4MDU,206.83 2020-08-04,MA4B66MW5E27UAE4MDU,206.07 2020-08-05,MA4B66MW5E27UAE4MDU,205.54 2020-08-06,MA4B66MW5E27UAE4MDU,205.69 2020-08-07,MA4B66MW5E27UAE4MDU,205.4 2020-08-10,MA4B66MW5E27UAE4MDU,202.05 2020-08-11,MA4B66MW5E27UAE4MDU,200.01 2020-08-12,MA4B66MW5E27UAE4MDU,205.92 2020-08-13,MA4B66MW5E27UAE4MDU,207.19 2020-08-14,MA4B66MW5E27UAE4MDU,204.86 2020-08-17,MA4B66MW5E27UAE4MDU,205.57 2020-08-18,MA4B66MW5E27UAE4MDU,207.68 2020-08-19,MA4B66MW5E27UAE4MDU,205.93 2020-08-20,MA4B66MW5E27UAE4MDU,205.66 2020-08-21,MA4B66MW5E27UAE4MDU,207.8 2020-08-24,MA4B66MW5E27UAE4MDU,205.56 2020-08-25,MA4B66MW5E27UAE4MDU,207.57 2020-08-26,MA4B66MW5E27UAE4MDU,208.19 2020-08-27,MA4B66MW5E27UAE4MDU,204.39 2020-08-28,MA4B66MW5E27UAE4MDU,205.64 2020-08-31,MA4B66MW5E27UAE4MDU,206.47 2020-07-01,MA4B66MW5E27UAKHZDF,140.38 2020-07-02,MA4B66MW5E27UAKHZDF,140.97 2020-07-06,MA4B66MW5E27UAKHZDF,142.98 2020-07-07,MA4B66MW5E27UAKHZDF,142.85 2020-07-08,MA4B66MW5E27UAKHZDF,143.27 2020-07-09,MA4B66MW5E27UAKHZDF,142.49 2020-07-10,MA4B66MW5E27UAKHZDF,142.37 2020-07-13,MA4B66MW5E27UAKHZDF,145.21 2020-07-14,MA4B66MW5E27UAKHZDF,147.92 2020-07-15,MA4B66MW5E27UAKHZDF,148.26 2020-07-16,MA4B66MW5E27UAKHZDF,149.25 2020-07-17,MA4B66MW5E27UAKHZDF,149.35 2020-07-20,MA4B66MW5E27UAKHZDF,149.6 2020-07-21,MA4B66MW5E27UAKHZDF,149.74 2020-07-22,MA4B66MW5E27UAKHZDF,150.01 2020-07-23,MA4B66MW5E27UAKHZDF,149.61 2020-07-24,MA4B66MW5E27UAKHZDF,148.12 2020-07-27,MA4B66MW5E27UAKHZDF,147.18 2020-07-28,MA4B66MW5E27UAKHZDF,146.83 2020-07-29,MA4B66MW5E27UAKHZDF,146.54 2020-07-30,MA4B66MW5E27UAKHZDF,146.84 2020-07-31,MA4B66MW5E27UAKHZDF,145.76 2020-08-03,MA4B66MW5E27UAKHZDF,147.35 2020-08-04,MA4B66MW5E27UAKHZDF,147.22 2020-08-05,MA4B66MW5E27UAKHZDF,148.4 2020-08-06,MA4B66MW5E27UAKHZDF,147.55 2020-08-07,MA4B66MW5E27UAKHZDF,148.6 2020-08-10,MA4B66MW5E27UAKHZDF,148.03 2020-08-11,MA4B66MW5E27UAKHZDF,146.97 2020-08-12,MA4B66MW5E27UAKHZDF,149.66 2020-08-13,MA4B66MW5E27UAKHZDF,148.0 2020-08-14,MA4B66MW5E27UAKHZDF,148.24 2020-08-17,MA4B66MW5E27UAKHZDF,148.99 2020-08-18,MA4B66MW5E27UAKHZDF,150.09 2020-08-19,MA4B66MW5E27UAKHZDF,150.39 2020-08-20,MA4B66MW5E27UAKHZDF,151.42 2020-08-21,MA4B66MW5E27UAKHZDF,152.76 2020-08-24,MA4B66MW5E27UAKHZDF,152.15 2020-08-25,MA4B66MW5E27UAKHZDF,152.06 2020-08-26,MA4B66MW5E27UAKHZDF,152.3 2020-08-27,MA4B66MW5E27UAKHZDF,152.98 2020-08-28,MA4B66MW5E27UAKHZDF,153.64 2020-08-31,MA4B66MW5E27UAKHZDF,153.41 2020-07-01,MA4B66MW5E27UAL3HZC,184.66 2020-07-02,MA4B66MW5E27UAL3HZC,183.52 2020-07-06,MA4B66MW5E27UAL3HZC,188.5 2020-07-07,MA4B66MW5E27UAL3HZC,185.82 2020-07-08,MA4B66MW5E27UAL3HZC,185.85 2020-07-09,MA4B66MW5E27UAL3HZC,184.33 2020-07-10,MA4B66MW5E27UAL3HZC,184.88 2020-07-13,MA4B66MW5E27UAL3HZC,184.92 2020-07-14,MA4B66MW5E27UAL3HZC,190.72 2020-07-15,MA4B66MW5E27UAL3HZC,191.77 2020-07-16,MA4B66MW5E27UAL3HZC,190.92 2020-07-17,MA4B66MW5E27UAL3HZC,191.48 2020-07-20,MA4B66MW5E27UAL3HZC,191.61 2020-07-21,MA4B66MW5E27UAL3HZC,192.98 2020-07-22,MA4B66MW5E27UAL3HZC,198.62 2020-07-23,MA4B66MW5E27UAL3HZC,197.55 2020-07-24,MA4B66MW5E27UAL3HZC,198.72 2020-07-27,MA4B66MW5E27UAL3HZC,201.25 2020-07-28,MA4B66MW5E27UAL3HZC,196.24 2020-07-29,MA4B66MW5E27UAL3HZC,196.21 2020-07-30,MA4B66MW5E27UAL3HZC,195.41 2020-07-31,MA4B66MW5E27UAL3HZC,194.28 2020-08-03,MA4B66MW5E27UAL3HZC,194.4 2020-08-04,MA4B66MW5E27UAL3HZC,199.36 2020-08-05,MA4B66MW5E27UAL3HZC,199.26 2020-08-06,MA4B66MW5E27UAL3HZC,203.18 2020-08-07,MA4B66MW5E27UAL3HZC,204.6 2020-08-10,MA4B66MW5E27UAL3HZC,204.12 2020-08-11,MA4B66MW5E27UAL3HZC,205.0 2020-08-12,MA4B66MW5E27UAL3HZC,206.02 2020-08-13,MA4B66MW5E27UAL3HZC,206.49 2020-08-14,MA4B66MW5E27UAL3HZC,207.03 2020-08-17,MA4B66MW5E27UAL3HZC,208.67 2020-08-18,MA4B66MW5E27UAL3HZC,210.32 2020-08-19,MA4B66MW5E27UAL3HZC,209.51 2020-08-20,MA4B66MW5E27UAL3HZC,209.88 2020-08-21,MA4B66MW5E27UAL3HZC,211.57 2020-08-24,MA4B66MW5E27UAL3HZC,212.62 2020-08-25,MA4B66MW5E27UAL3HZC,212.65 2020-08-26,MA4B66MW5E27UAL3HZC,213.76 2020-08-27,MA4B66MW5E27UAL3HZC,212.19 2020-08-28,MA4B66MW5E27UAL3HZC,214.91 2020-08-31,MA4B66MW5E27UAL3HZC,213.52 2020-07-01,MA4B66MW5E27UANEPLL,359.77 2020-07-02,MA4B66MW5E27UANEPLL,365.89 2020-07-06,MA4B66MW5E27UANEPLL,382.27 2020-07-07,MA4B66MW5E27UANEPLL,380.25 2020-07-08,MA4B66MW5E27UANEPLL,386.63 2020-07-09,MA4B66MW5E27UANEPLL,387.67 2020-07-10,MA4B66MW5E27UANEPLL,382.57 2020-07-13,MA4B66MW5E27UANEPLL,378.93 2020-07-14,MA4B66MW5E27UANEPLL,387.46 2020-07-15,MA4B66MW5E27UANEPLL,392.7 2020-07-16,MA4B66MW5E27UANEPLL,392.42 2020-07-17,MA4B66MW5E27UANEPLL,395.4 2020-07-20,MA4B66MW5E27UANEPLL,402.97 2020-07-21,MA4B66MW5E27UANEPLL,403.09 2020-07-22,MA4B66MW5E27UANEPLL,410.76 2020-07-23,MA4B66MW5E27UANEPLL,410.48 2020-07-24,MA4B66MW5E27UANEPLL,406.38 2020-07-27,MA4B66MW5E27UANEPLL,413.44 2020-07-28,MA4B66MW5E27UANEPLL,407.36 2020-07-29,MA4B66MW5E27UANEPLL,409.26 2020-07-30,MA4B66MW5E27UANEPLL,410.56 2020-07-31,MA4B66MW5E27UANEPLL,413.95 2020-08-03,MA4B66MW5E27UANEPLL,419.09 2020-08-04,MA4B66MW5E27UANEPLL,415.72 2020-08-05,MA4B66MW5E27UANEPLL,419.9 2020-08-06,MA4B66MW5E27UANEPLL,415.73 2020-08-07,MA4B66MW5E27UANEPLL,414.33 2020-08-10,MA4B66MW5E27UANEPLL,410.16 2020-08-11,MA4B66MW5E27UANEPLL,407.24 2020-08-12,MA4B66MW5E27UANEPLL,416.17 2020-08-13,MA4B66MW5E27UANEPLL,416.6 2020-08-14,MA4B66MW5E27UANEPLL,413.76 2020-08-17,MA4B66MW5E27UANEPLL,416.8 2020-08-18,MA4B66MW5E27UANEPLL,417.3 2020-08-19,MA4B66MW5E27UANEPLL,420.62 2020-08-20,MA4B66MW5E27UANEPLL,422.11 2020-08-21,MA4B66MW5E27UANEPLL,428.57 2020-08-24,MA4B66MW5E27UANEPLL,419.76 2020-08-25,MA4B66MW5E27UANEPLL,422.29 2020-08-26,MA4B66MW5E27UANEPLL,425.65 2020-08-27,MA4B66MW5E27UANEPLL,420.53 2020-08-28,MA4B66MW5E27UANEPLL,424.48 2020-08-31,MA4B66MW5E27UANEPLL,428.98 2020-07-01,MA4B66MW5E27UAKVAB4,301.17 2020-07-02,MA4B66MW5E27UAKVAB4,302.42 2020-07-06,MA4B66MW5E27UAKVAB4,305.57 2020-07-07,MA4B66MW5E27UAKVAB4,299.91 2020-07-08,MA4B66MW5E27UAKVAB4,300.15 2020-07-09,MA4B66MW5E27UAKVAB4,294.59 2020-07-10,MA4B66MW5E27UAKVAB4,295.68 2020-07-13,MA4B66MW5E27UAKVAB4,290.18 2020-07-14,MA4B66MW5E27UAKVAB4,298.95 2020-07-15,MA4B66MW5E27UAKVAB4,305.72 2020-07-16,MA4B66MW5E27UAKVAB4,299.35 2020-07-17,MA4B66MW5E27UAKVAB4,304.06 2020-07-20,MA4B66MW5E27UAKVAB4,311.96 2020-07-21,MA4B66MW5E27UAKVAB4,307.6 2020-07-22,MA4B66MW5E27UAKVAB4,313.37 2020-07-23,MA4B66MW5E27UAKVAB4,309.9 2020-07-24,MA4B66MW5E27UAKVAB4,306.92 2020-07-27,MA4B66MW5E27UAKVAB4,308.86 2020-07-28,MA4B66MW5E27UAKVAB4,306.33 2020-07-29,MA4B66MW5E27UAKVAB4,309.3 2020-07-30,MA4B66MW5E27UAKVAB4,309.0 2020-07-31,MA4B66MW5E27UAKVAB4,308.53 2020-08-03,MA4B66MW5E27UAKVAB4,312.59 2020-08-04,MA4B66MW5E27UAKVAB4,314.39 2020-08-05,MA4B66MW5E27UAKVAB4,328.0 2020-08-06,MA4B66MW5E27UAKVAB4,330.14 2020-08-07,MA4B66MW5E27UAKVAB4,328.02 2020-08-10,MA4B66MW5E27UAKVAB4,322.88 2020-08-11,MA4B66MW5E27UAKVAB4,328.54 2020-08-12,MA4B66MW5E27UAKVAB4,327.63 2020-08-13,MA4B66MW5E27UAKVAB4,326.19 2020-08-14,MA4B66MW5E27UAKVAB4,326.8 2020-08-17,MA4B66MW5E27UAKVAB4,330.23 2020-08-18,MA4B66MW5E27UAKVAB4,331.0 2020-08-19,MA4B66MW5E27UAKVAB4,332.4 2020-08-20,MA4B66MW5E27UAKVAB4,337.32 2020-08-21,MA4B66MW5E27UAKVAB4,337.1 2020-08-24,MA4B66MW5E27UAKVAB4,343.64 2020-08-25,MA4B66MW5E27UAKVAB4,347.66 2020-08-26,MA4B66MW5E27UAKVAB4,351.02 2020-08-27,MA4B66MW5E27UAKVAB4,356.0 2020-08-28,MA4B66MW5E27UAKVAB4,366.12 2020-08-31,MA4B66MW5E27UAKVAB4,358.19 2020-07-01,MA4B66MW5E27UAKHZFD,93.26 2020-07-02,MA4B66MW5E27UAKHZFD,92.66 2020-07-06,MA4B66MW5E27UAKHZFD,95.0 2020-07-07,MA4B66MW5E27UAKHZFD,92.32 2020-07-08,MA4B66MW5E27UAKHZFD,93.3 2020-07-09,MA4B66MW5E27UAKHZFD,91.28 2020-07-10,MA4B66MW5E27UAKHZFD,96.27 2020-07-13,MA4B66MW5E27UAKHZFD,97.65 2020-07-14,MA4B66MW5E27UAKHZFD,98.21 2020-07-15,MA4B66MW5E27UAKHZFD,99.73 2020-07-16,MA4B66MW5E27UAKHZFD,100.01 2020-07-17,MA4B66MW5E27UAKHZFD,98.16 2020-07-20,MA4B66MW5E27UAKHZFD,97.3 2020-07-21,MA4B66MW5E27UAKHZFD,99.41 2020-07-22,MA4B66MW5E27UAKHZFD,98.69 2020-07-23,MA4B66MW5E27UAKHZFD,98.98 2020-07-24,MA4B66MW5E27UAKHZFD,98.28 2020-07-27,MA4B66MW5E27UAKHZFD,96.9 2020-07-28,MA4B66MW5E27UAKHZFD,97.32 2020-07-29,MA4B66MW5E27UAKHZFD,99.68 2020-07-30,MA4B66MW5E27UAKHZFD,97.02 2020-07-31,MA4B66MW5E27UAKHZFD,96.64 2020-08-03,MA4B66MW5E27UAKHZFD,96.1 2020-08-04,MA4B66MW5E27UAKHZFD,95.55 2020-08-05,MA4B66MW5E27UAKHZFD,97.21 2020-08-06,MA4B66MW5E27UAKHZFD,97.24 2020-08-07,MA4B66MW5E27UAKHZFD,99.38 2020-08-10,MA4B66MW5E27UAKHZFD,100.64 2020-08-11,MA4B66MW5E27UAKHZFD,103.82 2020-08-12,MA4B66MW5E27UAKHZFD,102.94 2020-08-13,MA4B66MW5E27UAKHZFD,102.37 2020-08-14,MA4B66MW5E27UAKHZFD,102.41 2020-08-17,MA4B66MW5E27UAKHZFD,99.71 2020-08-18,MA4B66MW5E27UAKHZFD,98.32 2020-08-19,MA4B66MW5E27UAKHZFD,98.55 2020-08-20,MA4B66MW5E27UAKHZFD,97.37 2020-08-21,MA4B66MW5E27UAKHZFD,97.32 2020-08-24,MA4B66MW5E27UAKHZFD,100.06 2020-08-25,MA4B66MW5E27UAKHZFD,100.5 2020-08-26,MA4B66MW5E27UAKHZFD,99.09 2020-08-27,MA4B66MW5E27UAKHZFD,102.35 2020-08-28,MA4B66MW5E27UAKHZFD,102.77 2020-08-31,MA4B66MW5E27UAKHZFD,100.19 2020-07-01,MA4B66MW5E27UAL3J7Z,92.32 2020-07-02,MA4B66MW5E27UAL3J7Z,93.16 2020-07-06,MA4B66MW5E27UAL3J7Z,93.21 2020-07-07,MA4B66MW5E27UAL3J7Z,91.67 2020-07-08,MA4B66MW5E27UAL3J7Z,91.61 2020-07-09,MA4B66MW5E27UAL3J7Z,90.13 2020-07-10,MA4B66MW5E27UAL3J7Z,90.81 2020-07-13,MA4B66MW5E27UAL3J7Z,92.77 2020-07-14,MA4B66MW5E27UAL3J7Z,94.65 2020-07-15,MA4B66MW5E27UAL3J7Z,97.16 2020-07-16,MA4B66MW5E27UAL3J7Z,95.75 2020-07-17,MA4B66MW5E27UAL3J7Z,98.58 2020-07-20,MA4B66MW5E27UAL3J7Z,95.73 2020-07-21,MA4B66MW5E27UAL3J7Z,96.81 2020-07-22,MA4B66MW5E27UAL3J7Z,97.49 2020-07-23,MA4B66MW5E27UAL3J7Z,96.57 2020-07-24,MA4B66MW5E27UAL3J7Z,96.35 2020-07-27,MA4B66MW5E27UAL3J7Z,96.43 2020-07-28,MA4B66MW5E27UAL3J7Z,96.35 2020-07-29,MA4B66MW5E27UAL3J7Z,98.72 2020-07-30,MA4B66MW5E27UAL3J7Z,96.47 2020-07-31,MA4B66MW5E27UAL3J7Z,96.48 2020-08-03,MA4B66MW5E27UAL3J7Z,97.33 2020-08-04,MA4B66MW5E27UAL3J7Z,96.29 2020-08-05,MA4B66MW5E27UAL3J7Z,97.02 2020-08-06,MA4B66MW5E27UAL3J7Z,96.73 2020-08-07,MA4B66MW5E27UAL3J7Z,100.05 2020-08-10,MA4B66MW5E27UAL3J7Z,100.38 2020-08-11,MA4B66MW5E27UAL3J7Z,100.72 2020-08-12,MA4B66MW5E27UAL3J7Z,102.09 2020-08-13,MA4B66MW5E27UAL3J7Z,101.29 2020-08-14,MA4B66MW5E27UAL3J7Z,100.71 2020-08-17,MA4B66MW5E27UAL3J7Z,101.86 2020-08-18,MA4B66MW5E27UAL3J7Z,101.56 2020-08-19,MA4B66MW5E27UAL3J7Z,100.82 2020-08-20,MA4B66MW5E27UAL3J7Z,100.28 2020-08-21,MA4B66MW5E27UAL3J7Z,98.73 2020-08-24,MA4B66MW5E27UAL3J7Z,100.13 2020-08-25,MA4B66MW5E27UAL3J7Z,102.59 2020-08-26,MA4B66MW5E27UAL3J7Z,104.08 2020-08-27,MA4B66MW5E27UAL3J7Z,106.86 2020-08-28,MA4B66MW5E27UAL3J7Z,107.86 2020-08-31,MA4B66MW5E27UAL3J7Z,107.47 2020-07-01,MA4B66MW5E27UA447M6,177.99 2020-07-02,MA4B66MW5E27UA447M6,178.83 2020-07-06,MA4B66MW5E27UA447M6,182.72 2020-07-07,MA4B66MW5E27UA447M6,181.15 2020-07-08,MA4B66MW5E27UA447M6,181.2 2020-07-09,MA4B66MW5E27UA447M6,178.8 2020-07-10,MA4B66MW5E27UA447M6,182.9 2020-07-13,MA4B66MW5E27UA447M6,184.13 2020-07-14,MA4B66MW5E27UA447M6,189.71 2020-07-15,MA4B66MW5E27UA447M6,190.56 2020-07-16,MA4B66MW5E27UA447M6,191.01 2020-07-17,MA4B66MW5E27UA447M6,190.76 2020-07-20,MA4B66MW5E27UA447M6,190.48 2020-07-21,MA4B66MW5E27UA447M6,192.27 2020-07-22,MA4B66MW5E27UA447M6,192.1 2020-07-23,MA4B66MW5E27UA447M6,193.25 2020-07-24,MA4B66MW5E27UA447M6,194.52 2020-07-27,MA4B66MW5E27UA447M6,192.14 2020-07-28,MA4B66MW5E27UA447M6,194.12 2020-07-29,MA4B66MW5E27UA447M6,196.41 2020-07-30,MA4B66MW5E27UA447M6,194.3 2020-07-31,MA4B66MW5E27UA447M6,195.78 2020-08-03,MA4B66MW5E27UA447M6,199.26 2020-08-04,MA4B66MW5E27UA447M6,200.24 2020-08-05,MA4B66MW5E27UA447M6,203.62 2020-08-06,MA4B66MW5E27UA447M6,205.02 2020-08-07,MA4B66MW5E27UA447M6,209.48 2020-08-10,MA4B66MW5E27UA447M6,212.58 2020-08-11,MA4B66MW5E27UA447M6,212.66 2020-08-12,MA4B66MW5E27UA447M6,213.24 2020-08-13,MA4B66MW5E27UA447M6,211.98 2020-08-14,MA4B66MW5E27UA447M6,210.96 2020-08-17,MA4B66MW5E27UA447M6,206.78 2020-08-18,MA4B66MW5E27UA447M6,208.33 2020-08-19,MA4B66MW5E27UA447M6,207.21 2020-08-20,MA4B66MW5E27UA447M6,206.12 2020-08-21,MA4B66MW5E27UA447M6,207.43 2020-08-24,MA4B66MW5E27UA447M6,212.61 2020-08-25,MA4B66MW5E27UA447M6,213.35 2020-08-26,MA4B66MW5E27UA447M6,214.66 2020-08-27,MA4B66MW5E27UA447M6,216.87 2020-08-28,MA4B66MW5E27UA447M6,218.55 2020-08-31,MA4B66MW5E27UA447M6,218.04 2020-07-01,MA4B66MW5E27UANLY9V,166.88 2020-07-02,MA4B66MW5E27UANLY9V,169.08 2020-07-06,MA4B66MW5E27UANLY9V,172.16 2020-07-07,MA4B66MW5E27UANLY9V,169.78 2020-07-08,MA4B66MW5E27UANLY9V,169.04 2020-07-09,MA4B66MW5E27UANLY9V,165.99 2020-07-10,MA4B66MW5E27UANLY9V,166.39 2020-07-13,MA4B66MW5E27UANLY9V,169.17 2020-07-14,MA4B66MW5E27UANLY9V,171.89 2020-07-15,MA4B66MW5E27UANLY9V,174.39 2020-07-16,MA4B66MW5E27UANLY9V,176.87 2020-07-17,MA4B66MW5E27UANLY9V,181.25 2020-07-20,MA4B66MW5E27UANLY9V,177.39 2020-07-21,MA4B66MW5E27UANLY9V,178.66 2020-07-22,MA4B66MW5E27UANLY9V,179.38 2020-07-23,MA4B66MW5E27UANLY9V,175.0 2020-07-24,MA4B66MW5E27UANLY9V,176.12 2020-07-27,MA4B66MW5E27UANLY9V,176.82 2020-07-28,MA4B66MW5E27UANLY9V,174.41 2020-07-29,MA4B66MW5E27UANLY9V,177.98 2020-07-30,MA4B66MW5E27UANLY9V,172.8 2020-07-31,MA4B66MW5E27UANLY9V,173.35 2020-08-03,MA4B66MW5E27UANLY9V,172.06 2020-08-04,MA4B66MW5E27UANLY9V,173.68 2020-08-05,MA4B66MW5E27UANLY9V,177.47 2020-08-06,MA4B66MW5E27UANLY9V,179.61 2020-08-07,MA4B66MW5E27UANLY9V,182.93 2020-08-10,MA4B66MW5E27UANLY9V,186.85 2020-08-11,MA4B66MW5E27UANLY9V,190.35 2020-08-12,MA4B66MW5E27UANLY9V,191.83 2020-08-13,MA4B66MW5E27UANLY9V,189.72 2020-08-14,MA4B66MW5E27UANLY9V,191.92 2020-08-17,MA4B66MW5E27UANLY9V,193.87 2020-08-18,MA4B66MW5E27UANLY9V,190.7 2020-08-19,MA4B66MW5E27UANLY9V,190.95 2020-08-20,MA4B66MW5E27UANLY9V,189.92 2020-08-21,MA4B66MW5E27UANLY9V,191.57 2020-08-24,MA4B66MW5E27UANLY9V,192.56 2020-08-25,MA4B66MW5E27UANLY9V,194.9 2020-08-26,MA4B66MW5E27UANLY9V,194.32 2020-08-27,MA4B66MW5E27UANLY9V,194.44 2020-08-28,MA4B66MW5E27UANLY9V,195.13 2020-08-31,MA4B66MW5E27UANLY9V,192.44 2020-07-01,MA4B66MW5E27UALG2XD,246.26 2020-07-02,MA4B66MW5E27UALG2XD,246.4 2020-07-06,MA4B66MW5E27UALG2XD,246.96 2020-07-07,MA4B66MW5E27UALG2XD,246.72 2020-07-08,MA4B66MW5E27UALG2XD,253.27 2020-07-09,MA4B66MW5E27UALG2XD,251.99 2020-07-10,MA4B66MW5E27UALG2XD,259.6 2020-07-13,MA4B66MW5E27UALG2XD,256.24 2020-07-14,MA4B66MW5E27UALG2XD,262.55 2020-07-15,MA4B66MW5E27UALG2XD,259.0 2020-07-16,MA4B66MW5E27UALG2XD,268.38 2020-07-17,MA4B66MW5E27UALG2XD,275.38 2020-07-20,MA4B66MW5E27UALG2XD,274.33 2020-07-21,MA4B66MW5E27UALG2XD,276.33 2020-07-22,MA4B66MW5E27UALG2XD,280.42 2020-07-23,MA4B66MW5E27UALG2XD,280.97 2020-07-24,MA4B66MW5E27UALG2XD,280.25 2020-07-27,MA4B66MW5E27UALG2XD,277.63 2020-07-28,MA4B66MW5E27UALG2XD,280.04 2020-07-29,MA4B66MW5E27UALG2XD,281.44 2020-07-30,MA4B66MW5E27UALG2XD,282.34 2020-07-31,MA4B66MW5E27UALG2XD,280.7 2020-08-03,MA4B66MW5E27UALG2XD,277.59 2020-08-04,MA4B66MW5E27UALG2XD,285.05 2020-08-05,MA4B66MW5E27UALG2XD,283.78 2020-08-06,MA4B66MW5E27UALG2XD,282.81 2020-08-07,MA4B66MW5E27UALG2XD,287.78 2020-08-10,MA4B66MW5E27UALG2XD,283.64 2020-08-11,MA4B66MW5E27UALG2XD,277.79 2020-08-12,MA4B66MW5E27UALG2XD,284.59 2020-08-13,MA4B66MW5E27UALG2XD,284.4 2020-08-14,MA4B66MW5E27UALG2XD,280.5 2020-08-17,MA4B66MW5E27UALG2XD,282.99 2020-08-18,MA4B66MW5E27UALG2XD,283.05 2020-08-19,MA4B66MW5E27UALG2XD,282.34 2020-08-20,MA4B66MW5E27UALG2XD,282.11 2020-08-21,MA4B66MW5E27UALG2XD,282.41 2020-08-24,MA4B66MW5E27UALG2XD,282.63 2020-08-25,MA4B66MW5E27UALG2XD,281.31 2020-08-26,MA4B66MW5E27UALG2XD,279.0 2020-08-27,MA4B66MW5E27UALG2XD,278.88 2020-08-28,MA4B66MW5E27UALG2XD,279.55 2020-08-31,MA4B66MW5E27UALG2XD,279.17 2020-07-01,MA4B66MW5E27UALG396,97.4 2020-07-02,MA4B66MW5E27UALG396,98.43 2020-07-06,MA4B66MW5E27UALG396,99.95 2020-07-07,MA4B66MW5E27UALG396,97.07 2020-07-08,MA4B66MW5E27UALG396,98.84 2020-07-09,MA4B66MW5E27UALG396,96.99 2020-07-10,MA4B66MW5E27UALG396,97.99 2020-07-13,MA4B66MW5E27UALG396,96.46 2020-07-14,MA4B66MW5E27UALG396,96.76 2020-07-15,MA4B66MW5E27UALG396,98.54 2020-07-16,MA4B66MW5E27UALG396,97.26 2020-07-17,MA4B66MW5E27UALG396,96.28 2020-07-20,MA4B66MW5E27UALG396,95.65 2020-07-21,MA4B66MW5E27UALG396,98.36 2020-07-22,MA4B66MW5E27UALG396,98.91 2020-07-23,MA4B66MW5E27UALG396,98.3 2020-07-24,MA4B66MW5E27UALG396,98.43 2020-07-27,MA4B66MW5E27UALG396,97.21 2020-07-28,MA4B66MW5E27UALG396,96.27 2020-07-29,MA4B66MW5E27UALG396,96.97 2020-07-30,MA4B66MW5E27UALG396,96.82 2020-07-31,MA4B66MW5E27UALG396,97.61 2020-08-03,MA4B66MW5E27UALG396,98.33 2020-08-04,MA4B66MW5E27UALG396,97.33 2020-08-05,MA4B66MW5E27UALG396,100.94 2020-08-06,MA4B66MW5E27UALG396,100.45 2020-08-07,MA4B66MW5E27UALG396,101.86 2020-08-10,MA4B66MW5E27UALG396,105.41 2020-08-11,MA4B66MW5E27UALG396,105.12 2020-08-12,MA4B66MW5E27UALG396,105.22 2020-08-13,MA4B66MW5E27UALG396,106.52 2020-08-14,MA4B66MW5E27UALG396,106.43 2020-08-17,MA4B66MW5E27UALG396,105.66 2020-08-18,MA4B66MW5E27UALG396,106.97 2020-08-19,MA4B66MW5E27UALG396,108.39 2020-08-20,MA4B66MW5E27UALG396,108.01 2020-08-21,MA4B66MW5E27UALG396,109.75 2020-08-24,MA4B66MW5E27UALG396,111.83 2020-08-25,MA4B66MW5E27UALG396,111.51 2020-08-26,MA4B66MW5E27UALG396,111.53 2020-08-27,MA4B66MW5E27UALG396,110.84 2020-08-28,MA4B66MW5E27UALG396,112.29 2020-08-31,MA4B66MW5E27UALG396,111.89 2020-07-01,MA4B66MW5E27UAKNZKD,44.82 2020-07-02,MA4B66MW5E27UAKNZKD,44.88 2020-07-06,MA4B66MW5E27UAKNZKD,45.23 2020-07-07,MA4B66MW5E27UAKNZKD,45.21 2020-07-08,MA4B66MW5E27UAKNZKD,45.07 2020-07-09,MA4B66MW5E27UAKNZKD,43.91 2020-07-10,MA4B66MW5E27UAKNZKD,45.15 2020-07-13,MA4B66MW5E27UAKNZKD,45.25 2020-07-14,MA4B66MW5E27UAKNZKD,45.87 2020-07-15,MA4B66MW5E27UAKNZKD,46.4 2020-07-16,MA4B66MW5E27UAKNZKD,46.15 2020-07-17,MA4B66MW5E27UAKNZKD,46.82 2020-07-20,MA4B66MW5E27UAKNZKD,46.12 2020-07-21,MA4B66MW5E27UAKNZKD,47.2 2020-07-22,MA4B66MW5E27UAKNZKD,48.48 2020-07-23,MA4B66MW5E27UAKNZKD,48.28 2020-07-24,MA4B66MW5E27UAKNZKD,48.49 2020-07-27,MA4B66MW5E27UAKNZKD,48.48 2020-07-28,MA4B66MW5E27UAKNZKD,48.18 2020-07-29,MA4B66MW5E27UAKNZKD,48.02 2020-07-30,MA4B66MW5E27UAKNZKD,47.69 2020-07-31,MA4B66MW5E27UAKNZKD,47.24 2020-08-03,MA4B66MW5E27UAKNZKD,46.3 2020-08-04,MA4B66MW5E27UAKNZKD,46.69 2020-08-05,MA4B66MW5E27UAKNZKD,47.22 2020-08-06,MA4B66MW5E27UAKNZKD,47.48 2020-08-07,MA4B66MW5E27UAKNZKD,47.8 2020-08-10,MA4B66MW5E27UAKNZKD,47.72 2020-08-11,MA4B66MW5E27UAKNZKD,47.93 2020-08-12,MA4B66MW5E27UAKNZKD,48.43 2020-08-13,MA4B66MW5E27UAKNZKD,48.38 2020-08-14,MA4B66MW5E27UAKNZKD,48.45 2020-08-17,MA4B66MW5E27UAKNZKD,48.21 2020-08-18,MA4B66MW5E27UAKNZKD,48.42 2020-08-19,MA4B66MW5E27UAKNZKD,47.37 2020-08-20,MA4B66MW5E27UAKNZKD,47.35 2020-08-21,MA4B66MW5E27UAKNZKD,47.28 2020-08-24,MA4B66MW5E27UAKNZKD,47.97 2020-08-25,MA4B66MW5E27UAKNZKD,47.91 2020-08-26,MA4B66MW5E27UAKNZKD,48.16 2020-08-27,MA4B66MW5E27UAKNZKD,48.23 2020-08-28,MA4B66MW5E27UAKNZKD,49.83 2020-08-31,MA4B66MW5E27UAKNZKD,49.53 2020-07-01,MA4B66MW5E27UALUL23,33.74 2020-07-02,MA4B66MW5E27UALUL23,34.51 2020-07-06,MA4B66MW5E27UALUL23,34.51 2020-07-07,MA4B66MW5E27UALUL23,34.03 2020-07-08,MA4B66MW5E27UALUL23,33.75 2020-07-09,MA4B66MW5E27UALUL23,33.46 2020-07-10,MA4B66MW5E27UALUL23,33.83 2020-07-13,MA4B66MW5E27UALUL23,35.21 2020-07-14,MA4B66MW5E27UALUL23,35.23 2020-07-15,MA4B66MW5E27UALUL23,35.72 2020-07-16,MA4B66MW5E27UALUL23,35.6 2020-07-17,MA4B66MW5E27UALUL23,36.25 2020-07-20,MA4B66MW5E27UALUL23,36.5 2020-07-21,MA4B66MW5E27UALUL23,36.69 2020-07-22,MA4B66MW5E27UALUL23,38.56 2020-07-23,MA4B66MW5E27UALUL23,38.41 2020-07-24,MA4B66MW5E27UALUL23,37.66 2020-07-27,MA4B66MW5E27UALUL23,37.54 2020-07-28,MA4B66MW5E27UALUL23,39.02 2020-07-29,MA4B66MW5E27UALUL23,39.26 2020-07-30,MA4B66MW5E27UALUL23,38.74 2020-07-31,MA4B66MW5E27UALUL23,38.48 2020-08-03,MA4B66MW5E27UALUL23,38.35 2020-08-04,MA4B66MW5E27UALUL23,38.39 2020-08-05,MA4B66MW5E27UALUL23,38.45 2020-08-06,MA4B66MW5E27UALUL23,38.27 2020-08-07,MA4B66MW5E27UALUL23,38.45 2020-08-10,MA4B66MW5E27UALUL23,38.39 2020-08-11,MA4B66MW5E27UALUL23,37.79 2020-08-12,MA4B66MW5E27UALUL23,38.33 2020-08-13,MA4B66MW5E27UALUL23,38.17 2020-08-14,MA4B66MW5E27UALUL23,38.06 2020-08-17,MA4B66MW5E27UALUL23,38.35 2020-08-18,MA4B66MW5E27UALUL23,38.36 2020-08-19,MA4B66MW5E27UALUL23,38.26 2020-08-20,MA4B66MW5E27UALUL23,38.72 2020-08-21,MA4B66MW5E27UALUL23,38.88 2020-08-24,MA4B66MW5E27UALUL23,38.84 2020-08-25,MA4B66MW5E27UALUL23,38.41 2020-08-26,MA4B66MW5E27UALUL23,38.05 2020-08-27,MA4B66MW5E27UALUL23,37.86 2020-08-28,MA4B66MW5E27UALUL23,37.91 2020-08-31,MA4B66MW5E27UALUL23,37.79 2020-07-01,MA4B66MW5E27UANZH3T,43.71 2020-07-02,MA4B66MW5E27UANZH3T,44.08 2020-07-06,MA4B66MW5E27UANZH3T,44.39 2020-07-07,MA4B66MW5E27UANZH3T,43.24 2020-07-08,MA4B66MW5E27UANZH3T,43.14 2020-07-09,MA4B66MW5E27UANZH3T,41.36 2020-07-10,MA4B66MW5E27UANZH3T,42.65 2020-07-13,MA4B66MW5E27UANZH3T,42.66 2020-07-14,MA4B66MW5E27UANZH3T,44.07 2020-07-15,MA4B66MW5E27UANZH3T,44.63 2020-07-16,MA4B66MW5E27UANZH3T,44.28 2020-07-17,MA4B66MW5E27UANZH3T,43.52 2020-07-20,MA4B66MW5E27UANZH3T,42.5 2020-07-21,MA4B66MW5E27UANZH3T,44.65 2020-07-22,MA4B66MW5E27UANZH3T,43.61 2020-07-23,MA4B66MW5E27UANZH3T,43.7 2020-07-24,MA4B66MW5E27UANZH3T,43.43 2020-07-27,MA4B66MW5E27UANZH3T,44.07 2020-07-28,MA4B66MW5E27UANZH3T,43.55 2020-07-29,MA4B66MW5E27UANZH3T,44.03 2020-07-30,MA4B66MW5E27UANZH3T,41.87 2020-07-31,MA4B66MW5E27UANZH3T,42.08 2020-08-03,MA4B66MW5E27UANZH3T,42.25 2020-08-04,MA4B66MW5E27UANZH3T,43.47 2020-08-05,MA4B66MW5E27UANZH3T,43.85 2020-08-06,MA4B66MW5E27UANZH3T,43.64 2020-08-07,MA4B66MW5E27UANZH3T,43.44 2020-08-10,MA4B66MW5E27UANZH3T,44.51 2020-08-11,MA4B66MW5E27UANZH3T,44.97 2020-08-12,MA4B66MW5E27UANZH3T,44.09 2020-08-13,MA4B66MW5E27UANZH3T,43.01 2020-08-14,MA4B66MW5E27UANZH3T,43.2 2020-08-17,MA4B66MW5E27UANZH3T,42.64 2020-08-18,MA4B66MW5E27UANZH3T,42.43 2020-08-19,MA4B66MW5E27UANZH3T,41.96 2020-08-20,MA4B66MW5E27UANZH3T,41.32 2020-08-21,MA4B66MW5E27UANZH3T,41.01 2020-08-24,MA4B66MW5E27UANZH3T,42.22 2020-08-25,MA4B66MW5E27UANZH3T,40.88 2020-08-26,MA4B66MW5E27UANZH3T,40.01 2020-08-27,MA4B66MW5E27UANZH3T,39.74 2020-08-28,MA4B66MW5E27UANZH3T,40.69 2020-08-31,MA4B66MW5E27UANZH3T,39.94 2020-07-01,MA4B66MW5E27UAE4MG6,113.01 2020-07-02,MA4B66MW5E27UAE4MG6,112.18 2020-07-06,MA4B66MW5E27UAE4MG6,114.43 2020-07-07,MA4B66MW5E27UAE4MG6,113.63 2020-07-08,MA4B66MW5E27UAE4MG6,116.66 2020-07-09,MA4B66MW5E27UAE4MG6,116.81 2020-07-10,MA4B66MW5E27UAE4MG6,119.34 2020-07-13,MA4B66MW5E27UAE4MG6,116.22 2020-07-14,MA4B66MW5E27UAE4MG6,118.66 2020-07-15,MA4B66MW5E27UAE4MG6,120.9 2020-07-16,MA4B66MW5E27UAE4MG6,119.43 2020-07-17,MA4B66MW5E27UAE4MG6,118.65 2020-07-20,MA4B66MW5E27UAE4MG6,117.79 2020-07-21,MA4B66MW5E27UAE4MG6,118.62 2020-07-22,MA4B66MW5E27UAE4MG6,119.03 2020-07-23,MA4B66MW5E27UAE4MG6,118.12 2020-07-24,MA4B66MW5E27UAE4MG6,117.61 2020-07-27,MA4B66MW5E27UAE4MG6,116.31 2020-07-28,MA4B66MW5E27UAE4MG6,116.18 2020-07-29,MA4B66MW5E27UAE4MG6,115.61 2020-07-30,MA4B66MW5E27UAE4MG6,115.66 2020-07-31,MA4B66MW5E27UAE4MG6,116.94 2020-08-03,MA4B66MW5E27UAE4MG6,116.35 2020-08-04,MA4B66MW5E27UAE4MG6,117.29 2020-08-05,MA4B66MW5E27UAE4MG6,127.61 2020-08-06,MA4B66MW5E27UAE4MG6,130.82 2020-08-07,MA4B66MW5E27UAE4MG6,129.93 2020-08-10,MA4B66MW5E27UAE4MG6,128.79 2020-08-11,MA4B66MW5E27UAE4MG6,130.49 2020-08-12,MA4B66MW5E27UAE4MG6,131.79 2020-08-13,MA4B66MW5E27UAE4MG6,130.96 2020-08-14,MA4B66MW5E27UAE4MG6,130.53 2020-08-17,MA4B66MW5E27UAE4MG6,129.37 2020-08-18,MA4B66MW5E27UAE4MG6,128.92 2020-08-19,MA4B66MW5E27UAE4MG6,127.77 2020-08-20,MA4B66MW5E27UAE4MG6,128.12 2020-08-21,MA4B66MW5E27UAE4MG6,127.44 2020-08-24,MA4B66MW5E27UAE4MG6,130.69 2020-08-25,MA4B66MW5E27UAE4MG6,129.79 2020-08-26,MA4B66MW5E27UAE4MG6,132.18 2020-08-27,MA4B66MW5E27UAE4MG6,133.73 2020-08-28,MA4B66MW5E27UAE4MG6,135.54 2020-08-31,MA4B66MW5E27UAE4MG6,131.87 2020-07-01,MA4B66MW5E27UANZGNN,119.69 2020-07-02,MA4B66MW5E27UANZGNN,119.21 2020-07-06,MA4B66MW5E27UANZGNN,118.89 2020-07-07,MA4B66MW5E27UANZGNN,126.95 2020-07-08,MA4B66MW5E27UANZGNN,124.44 2020-07-09,MA4B66MW5E27UANZGNN,127.75 2020-07-10,MA4B66MW5E27UANZGNN,130.68 2020-07-13,MA4B66MW5E27UANZGNN,129.52 2020-07-14,MA4B66MW5E27UANZGNN,132.01 2020-07-15,MA4B66MW5E27UANZGNN,132.0 2020-07-16,MA4B66MW5E27UANZGNN,132.2 2020-07-17,MA4B66MW5E27UANZGNN,131.74 2020-07-20,MA4B66MW5E27UANZGNN,131.47 2020-07-21,MA4B66MW5E27UANZGNN,132.33 2020-07-22,MA4B66MW5E27UANZGNN,132.66 2020-07-23,MA4B66MW5E27UANZGNN,131.64 2020-07-24,MA4B66MW5E27UANZGNN,131.24 2020-07-27,MA4B66MW5E27UANZGNN,131.21 2020-07-28,MA4B66MW5E27UANZGNN,131.76 2020-07-29,MA4B66MW5E27UANZGNN,130.69 2020-07-30,MA4B66MW5E27UANZGNN,130.12 2020-07-31,MA4B66MW5E27UANZGNN,129.4 2020-08-03,MA4B66MW5E27UANZGNN,129.3 2020-08-04,MA4B66MW5E27UANZGNN,131.64 2020-08-05,MA4B66MW5E27UANZGNN,129.81 2020-08-06,MA4B66MW5E27UANZGNN,129.35 2020-08-07,MA4B66MW5E27UANZGNN,129.97 2020-08-10,MA4B66MW5E27UANZGNN,131.88 2020-08-11,MA4B66MW5E27UANZGNN,130.2 2020-08-12,MA4B66MW5E27UANZGNN,131.89 2020-08-13,MA4B66MW5E27UANZGNN,131.85 2020-08-14,MA4B66MW5E27UANZGNN,132.6 2020-08-17,MA4B66MW5E27UANZGNN,135.6 2020-08-18,MA4B66MW5E27UANZGNN,134.71 2020-08-19,MA4B66MW5E27UANZGNN,132.41 2020-08-20,MA4B66MW5E27UANZGNN,130.57 2020-08-21,MA4B66MW5E27UANZGNN,131.63 2020-08-24,MA4B66MW5E27UANZGNN,131.33 2020-08-25,MA4B66MW5E27UANZGNN,130.63 2020-08-26,MA4B66MW5E27UANZGNN,130.7 2020-08-27,MA4B66MW5E27UANZGNN,136.63 2020-08-28,MA4B66MW5E27UANZGNN,140.3 2020-08-31,MA4B66MW5E27UANZGNN,138.85 2020-07-01,MA4B66MW5E27UALUL3R,119.98 2020-07-02,MA4B66MW5E27UALUL3R,120.88 2020-07-06,MA4B66MW5E27UALUL3R,121.63 2020-07-07,MA4B66MW5E27UALUL3R,122.22 2020-07-08,MA4B66MW5E27UALUL3R,122.89 2020-07-09,MA4B66MW5E27UALUL3R,122.48 2020-07-10,MA4B66MW5E27UALUL3R,123.89 2020-07-13,MA4B66MW5E27UALUL3R,124.05 2020-07-14,MA4B66MW5E27UALUL3R,125.09 2020-07-15,MA4B66MW5E27UALUL3R,124.5 2020-07-16,MA4B66MW5E27UALUL3R,124.76 2020-07-17,MA4B66MW5E27UALUL3R,125.63 2020-07-20,MA4B66MW5E27UALUL3R,125.24 2020-07-21,MA4B66MW5E27UALUL3R,125.07 2020-07-22,MA4B66MW5E27UALUL3R,126.14 2020-07-23,MA4B66MW5E27UALUL3R,126.16 2020-07-24,MA4B66MW5E27UALUL3R,125.96 2020-07-27,MA4B66MW5E27UALUL3R,126.32 2020-07-28,MA4B66MW5E27UALUL3R,127.88 2020-07-29,MA4B66MW5E27UALUL3R,128.31 2020-07-30,MA4B66MW5E27UALUL3R,131.42 2020-07-31,MA4B66MW5E27UALUL3R,131.12 2020-08-03,MA4B66MW5E27UALUL3R,131.29 2020-08-04,MA4B66MW5E27UALUL3R,133.79 2020-08-05,MA4B66MW5E27UALUL3R,133.44 2020-08-06,MA4B66MW5E27UALUL3R,132.71 2020-08-07,MA4B66MW5E27UALUL3R,133.55 2020-08-10,MA4B66MW5E27UALUL3R,134.1 2020-08-11,MA4B66MW5E27UALUL3R,133.23 2020-08-12,MA4B66MW5E27UALUL3R,135.46 2020-08-13,MA4B66MW5E27UALUL3R,135.78 2020-08-14,MA4B66MW5E27UALUL3R,135.1 2020-08-17,MA4B66MW5E27UALUL3R,135.5 2020-08-18,MA4B66MW5E27UALUL3R,136.51 2020-08-19,MA4B66MW5E27UALUL3R,135.77 2020-08-20,MA4B66MW5E27UALUL3R,136.85 2020-08-21,MA4B66MW5E27UALUL3R,137.44 2020-08-24,MA4B66MW5E27UALUL3R,138.51 2020-08-25,MA4B66MW5E27UALUL3R,139.06 2020-08-26,MA4B66MW5E27UALUL3R,138.39 2020-08-27,MA4B66MW5E27UALUL3R,138.21 2020-08-28,MA4B66MW5E27UALUL3R,138.77 2020-08-31,MA4B66MW5E27UALUL3R,138.33 2020-07-01,MA4B66MW5E27UAN8F94,29.9 2020-07-02,MA4B66MW5E27UAN8F94,30.08 2020-07-06,MA4B66MW5E27UAN8F94,30.49 2020-07-07,MA4B66MW5E27UAN8F94,30.32 2020-07-08,MA4B66MW5E27UAN8F94,30.46 2020-07-09,MA4B66MW5E27UAN8F94,29.54 2020-07-10,MA4B66MW5E27UAN8F94,30.13 2020-07-13,MA4B66MW5E27UAN8F94,29.76 2020-07-14,MA4B66MW5E27UAN8F94,29.96 2020-07-15,MA4B66MW5E27UAN8F94,30.01 2020-07-16,MA4B66MW5E27UAN8F94,30.39 2020-07-17,MA4B66MW5E27UAN8F94,30.25 2020-07-20,MA4B66MW5E27UAN8F94,29.85 2020-07-21,MA4B66MW5E27UAN8F94,30.25 2020-07-22,MA4B66MW5E27UAN8F94,30.16 2020-07-23,MA4B66MW5E27UAN8F94,29.9 2020-07-24,MA4B66MW5E27UAN8F94,29.57 2020-07-27,MA4B66MW5E27UAN8F94,29.29 2020-07-28,MA4B66MW5E27UAN8F94,29.69 2020-07-29,MA4B66MW5E27UAN8F94,29.56 2020-07-30,MA4B66MW5E27UAN8F94,29.57 2020-07-31,MA4B66MW5E27UAN8F94,29.58 2020-08-03,MA4B66MW5E27UAN8F94,29.62 2020-08-04,MA4B66MW5E27UAN8F94,30.01 2020-08-05,MA4B66MW5E27UAN8F94,29.85 2020-08-06,MA4B66MW5E27UAN8F94,29.84 2020-08-07,MA4B66MW5E27UAN8F94,30.02 2020-08-10,MA4B66MW5E27UAN8F94,30.2 2020-08-11,MA4B66MW5E27UAN8F94,30.2 2020-08-12,MA4B66MW5E27UAN8F94,30.18 2020-08-13,MA4B66MW5E27UAN8F94,29.91 2020-08-14,MA4B66MW5E27UAN8F94,30.01 2020-08-17,MA4B66MW5E27UAN8F94,29.85 2020-08-18,MA4B66MW5E27UAN8F94,29.79 2020-08-19,MA4B66MW5E27UAN8F94,29.73 2020-08-20,MA4B66MW5E27UAN8F94,29.67 2020-08-21,MA4B66MW5E27UAN8F94,29.69 2020-08-24,MA4B66MW5E27UAN8F94,30.03 2020-08-25,MA4B66MW5E27UAN8F94,29.9 2020-08-26,MA4B66MW5E27UAN8F94,29.99 2020-08-27,MA4B66MW5E27UAN8F94,29.9 2020-08-28,MA4B66MW5E27UAN8F94,30.04 2020-08-31,MA4B66MW5E27UAN8F94,29.81 2020-07-01,MA4B66MW5E27UANLYJN,193.78 2020-07-02,MA4B66MW5E27UANLYJN,195.67 2020-07-06,MA4B66MW5E27UANLYJN,197.76 2020-07-07,MA4B66MW5E27UANLYJN,194.2 2020-07-08,MA4B66MW5E27UANLYJN,195.07 2020-07-09,MA4B66MW5E27UANLYJN,192.21 2020-07-10,MA4B66MW5E27UANLYJN,192.55 2020-07-13,MA4B66MW5E27UANLYJN,189.02 2020-07-14,MA4B66MW5E27UANLYJN,193.33 2020-07-15,MA4B66MW5E27UANLYJN,196.55 2020-07-16,MA4B66MW5E27UANLYJN,193.5 2020-07-17,MA4B66MW5E27UANLYJN,195.09 2020-07-20,MA4B66MW5E27UANLYJN,198.47 2020-07-21,MA4B66MW5E27UANLYJN,196.48 2020-07-22,MA4B66MW5E27UANLYJN,198.86 2020-07-23,MA4B66MW5E27UANLYJN,197.43 2020-07-24,MA4B66MW5E27UANLYJN,195.15 2020-07-27,MA4B66MW5E27UANLYJN,196.91 2020-07-28,MA4B66MW5E27UANLYJN,196.74 2020-07-29,MA4B66MW5E27UANLYJN,198.58 2020-07-30,MA4B66MW5E27UANLYJN,194.06 2020-07-31,MA4B66MW5E27UANLYJN,190.4 2020-08-03,MA4B66MW5E27UANLYJN,190.69 2020-08-04,MA4B66MW5E27UANLYJN,192.29 2020-08-05,MA4B66MW5E27UANLYJN,196.1 2020-08-06,MA4B66MW5E27UANLYJN,198.77 2020-08-07,MA4B66MW5E27UANLYJN,196.36 2020-08-10,MA4B66MW5E27UANLYJN,196.79 2020-08-11,MA4B66MW5E27UANLYJN,197.77 2020-08-12,MA4B66MW5E27UANLYJN,198.74 2020-08-13,MA4B66MW5E27UANLYJN,197.58 2020-08-14,MA4B66MW5E27UANLYJN,196.64 2020-08-17,MA4B66MW5E27UANLYJN,199.43 2020-08-18,MA4B66MW5E27UANLYJN,199.01 2020-08-19,MA4B66MW5E27UANLYJN,200.99 2020-08-20,MA4B66MW5E27UANLYJN,204.15 2020-08-21,MA4B66MW5E27UANLYJN,204.13 2020-08-24,MA4B66MW5E27UANLYJN,206.41 2020-08-25,MA4B66MW5E27UANLYJN,208.1 2020-08-26,MA4B66MW5E27UANLYJN,210.26 2020-08-27,MA4B66MW5E27UANLYJN,211.03 2020-08-28,MA4B66MW5E27UANLYJN,215.71 2020-08-31,MA4B66MW5E27UANLYJN,211.99 2020-07-01,MA4B66MW5E27U9VBBAX,99.14 2020-07-02,MA4B66MW5E27U9VBBAX,98.88 2020-07-06,MA4B66MW5E27U9VBBAX,99.01 2020-07-07,MA4B66MW5E27U9VBBAX,99.35 2020-07-08,MA4B66MW5E27U9VBBAX,99.28 2020-07-09,MA4B66MW5E27U9VBBAX,97.94 2020-07-10,MA4B66MW5E27U9VBBAX,96.83 2020-07-13,MA4B66MW5E27U9VBBAX,97.86 2020-07-14,MA4B66MW5E27U9VBBAX,98.87 2020-07-15,MA4B66MW5E27U9VBBAX,100.48 2020-07-16,MA4B66MW5E27U9VBBAX,99.92 2020-07-17,MA4B66MW5E27U9VBBAX,100.83 2020-07-20,MA4B66MW5E27U9VBBAX,99.54 2020-07-21,MA4B66MW5E27U9VBBAX,97.4 2020-07-22,MA4B66MW5E27U9VBBAX,97.7 2020-07-23,MA4B66MW5E27U9VBBAX,98.03 2020-07-24,MA4B66MW5E27U9VBBAX,97.11 2020-07-27,MA4B66MW5E27U9VBBAX,97.16 2020-07-28,MA4B66MW5E27U9VBBAX,96.71 2020-07-29,MA4B66MW5E27U9VBBAX,97.01 2020-07-30,MA4B66MW5E27U9VBBAX,96.04 2020-07-31,MA4B66MW5E27U9VBBAX,94.91 2020-08-03,MA4B66MW5E27U9VBBAX,95.94 2020-08-04,MA4B66MW5E27U9VBBAX,94.29 2020-08-05,MA4B66MW5E27U9VBBAX,93.25 2020-08-06,MA4B66MW5E27U9VBBAX,92.57 2020-08-07,MA4B66MW5E27U9VBBAX,92.92 2020-08-10,MA4B66MW5E27U9VBBAX,92.38 2020-08-11,MA4B66MW5E27U9VBBAX,92.67 2020-08-12,MA4B66MW5E27U9VBBAX,95.51 2020-08-13,MA4B66MW5E27U9VBBAX,94.68 2020-08-14,MA4B66MW5E27U9VBBAX,95.07 2020-08-17,MA4B66MW5E27U9VBBAX,96.35 2020-08-18,MA4B66MW5E27U9VBBAX,95.65 2020-08-19,MA4B66MW5E27U9VBBAX,96.1 2020-08-20,MA4B66MW5E27U9VBBAX,95.2 2020-08-21,MA4B66MW5E27U9VBBAX,94.86 2020-08-24,MA4B66MW5E27U9VBBAX,94.51 2020-08-25,MA4B66MW5E27U9VBBAX,94.06 2020-08-26,MA4B66MW5E27U9VBBAX,94.35 2020-08-27,MA4B66MW5E27U9VBBAX,94.3 2020-08-28,MA4B66MW5E27U9VBBAX,94.17 2020-08-31,MA4B66MW5E27U9VBBAX,95.77 2020-07-01,MA4B66MW5E27U8P32SB,3115.86 2020-07-02,MA4B66MW5E27U8P32SB,3130.01 2020-07-06,MA4B66MW5E27U8P32SB,3179.72 2020-07-07,MA4B66MW5E27U8P32SB,3145.32 2020-07-08,MA4B66MW5E27U8P32SB,3169.94 2020-07-09,MA4B66MW5E27U8P32SB,3152.05 2020-07-10,MA4B66MW5E27U8P32SB,3185.04 2020-07-13,MA4B66MW5E27U8P32SB,3155.22 2020-07-14,MA4B66MW5E27U8P32SB,3197.52 2020-07-15,MA4B66MW5E27U8P32SB,3226.56 2020-07-16,MA4B66MW5E27U8P32SB,3215.57 2020-07-17,MA4B66MW5E27U8P32SB,3224.73 2020-07-20,MA4B66MW5E27U8P32SB,3251.84 2020-07-21,MA4B66MW5E27U8P32SB,3257.3 2020-07-22,MA4B66MW5E27U8P32SB,3276.02 2020-07-23,MA4B66MW5E27U8P32SB,3235.66 2020-07-24,MA4B66MW5E27U8P32SB,3215.63 2020-07-27,MA4B66MW5E27U8P32SB,3239.41 2020-07-28,MA4B66MW5E27U8P32SB,3218.44 2020-07-29,MA4B66MW5E27U8P32SB,3258.44 2020-07-30,MA4B66MW5E27U8P32SB,3246.22 2020-07-31,MA4B66MW5E27U8P32SB,3271.12 2020-08-03,MA4B66MW5E27U8P32SB,3294.61 2020-08-04,MA4B66MW5E27U8P32SB,3306.51 2020-08-05,MA4B66MW5E27U8P32SB,3327.77 2020-08-06,MA4B66MW5E27U8P32SB,3349.16 2020-08-07,MA4B66MW5E27U8P32SB,3351.28 2020-08-10,MA4B66MW5E27U8P32SB,3360.47 2020-08-11,MA4B66MW5E27U8P32SB,3333.69 2020-08-12,MA4B66MW5E27U8P32SB,3380.35 2020-08-13,MA4B66MW5E27U8P32SB,3373.43 2020-08-14,MA4B66MW5E27U8P32SB,3372.85 2020-08-17,MA4B66MW5E27U8P32SB,3381.99 2020-08-18,MA4B66MW5E27U8P32SB,3389.78 2020-08-19,MA4B66MW5E27U8P32SB,3374.85 2020-08-20,MA4B66MW5E27U8P32SB,3385.51 2020-08-21,MA4B66MW5E27U8P32SB,3397.16 2020-08-24,MA4B66MW5E27U8P32SB,3431.28 2020-08-25,MA4B66MW5E27U8P32SB,3443.62 2020-08-26,MA4B66MW5E27U8P32SB,3478.73 2020-08-27,MA4B66MW5E27U8P32SB,3484.55 2020-08-28,MA4B66MW5E27U8P32SB,3508.01 2020-08-31,MA4B66MW5E27U8P32SB,3500.31 2020-07-01,MA4B66MW5E27UAHKGMA,248.15 2020-07-02,MA4B66MW5E27UAHKGMA,248.5 2020-07-06,MA4B66MW5E27UAHKGMA,249.55 2020-07-07,MA4B66MW5E27UAHKGMA,247.35 2020-07-08,MA4B66MW5E27UAHKGMA,249.17 2020-07-09,MA4B66MW5E27UAHKGMA,247.96 2020-07-10,MA4B66MW5E27UAHKGMA,250.11 2020-07-13,MA4B66MW5E27UAHKGMA,249.62 2020-07-14,MA4B66MW5E27UAHKGMA,257.79 2020-07-15,MA4B66MW5E27UAHKGMA,257.8 2020-07-16,MA4B66MW5E27UAHKGMA,258.08 2020-07-17,MA4B66MW5E27UAHKGMA,260.38 2020-07-20,MA4B66MW5E27UAHKGMA,260.17 2020-07-21,MA4B66MW5E27UAHKGMA,262.42 2020-07-22,MA4B66MW5E27UAHKGMA,265.17 2020-07-23,MA4B66MW5E27UAHKGMA,263.81 2020-07-24,MA4B66MW5E27UAHKGMA,265.31 2020-07-27,MA4B66MW5E27UAHKGMA,267.42 2020-07-28,MA4B66MW5E27UAHKGMA,265.28 2020-07-29,MA4B66MW5E27UAHKGMA,264.66 2020-07-30,MA4B66MW5E27UAHKGMA,266.31 2020-07-31,MA4B66MW5E27UAHKGMA,265.49 2020-08-03,MA4B66MW5E27UAHKGMA,266.18 2020-08-04,MA4B66MW5E27UAHKGMA,267.87 2020-08-05,MA4B66MW5E27UAHKGMA,267.48 2020-08-06,MA4B66MW5E27UAHKGMA,269.37 2020-08-07,MA4B66MW5E27UAHKGMA,271.64 2020-08-10,MA4B66MW5E27UAHKGMA,274.73 2020-08-11,MA4B66MW5E27UAHKGMA,274.92 2020-08-12,MA4B66MW5E27UAHKGMA,281.58 2020-08-13,MA4B66MW5E27UAHKGMA,281.66 2020-08-14,MA4B66MW5E27UAHKGMA,280.55 2020-08-17,MA4B66MW5E27UAHKGMA,288.24 2020-08-18,MA4B66MW5E27UAHKGMA,285.0 2020-08-19,MA4B66MW5E27UAHKGMA,282.86 2020-08-20,MA4B66MW5E27UAHKGMA,280.68 2020-08-21,MA4B66MW5E27UAHKGMA,283.23 2020-08-24,MA4B66MW5E27UAHKGMA,286.75 2020-08-25,MA4B66MW5E27UAHKGMA,286.13 2020-08-26,MA4B66MW5E27UAHKGMA,291.93 2020-08-27,MA4B66MW5E27UAHKGMA,288.63 2020-08-28,MA4B66MW5E27UAHKGMA,286.29 2020-08-31,MA4B66MW5E27UAHKGMA,285.04 2020-07-01,MA4B66MW5E27UANLY8B,297.73 2020-07-02,MA4B66MW5E27UANLY8B,298.26 2020-07-06,MA4B66MW5E27UANLY8B,302.81 2020-07-07,MA4B66MW5E27UANLY8B,296.65 2020-07-08,MA4B66MW5E27UANLY8B,298.36 2020-07-09,MA4B66MW5E27UANLY8B,291.16 2020-07-10,MA4B66MW5E27UANLY8B,291.23 2020-07-13,MA4B66MW5E27UANLY8B,299.71 2020-07-14,MA4B66MW5E27UANLY8B,308.52 2020-07-15,MA4B66MW5E27UANLY8B,304.07 2020-07-16,MA4B66MW5E27UANLY8B,307.15 2020-07-17,MA4B66MW5E27UANLY8B,306.53 2020-07-20,MA4B66MW5E27UANLY8B,303.46 2020-07-21,MA4B66MW5E27UANLY8B,305.11 2020-07-22,MA4B66MW5E27UANLY8B,306.71 2020-07-23,MA4B66MW5E27UANLY8B,302.97 2020-07-24,MA4B66MW5E27UANLY8B,300.79 2020-07-27,MA4B66MW5E27UANLY8B,298.6 2020-07-28,MA4B66MW5E27UANLY8B,299.93 2020-07-29,MA4B66MW5E27UANLY8B,306.68 2020-07-30,MA4B66MW5E27UANLY8B,305.23 2020-07-31,MA4B66MW5E27UANLY8B,302.78 2020-08-03,MA4B66MW5E27UANLY8B,303.61 2020-08-04,MA4B66MW5E27UANLY8B,304.5 2020-08-05,MA4B66MW5E27UANLY8B,312.47 2020-08-06,MA4B66MW5E27UANLY8B,314.06 2020-08-07,MA4B66MW5E27UANLY8B,317.03 2020-08-10,MA4B66MW5E27UANLY8B,319.1 2020-08-11,MA4B66MW5E27UANLY8B,315.55 2020-08-12,MA4B66MW5E27UANLY8B,322.27 2020-08-13,MA4B66MW5E27UANLY8B,321.52 2020-08-14,MA4B66MW5E27UANLY8B,323.7 2020-08-17,MA4B66MW5E27UANLY8B,320.51 2020-08-18,MA4B66MW5E27UANLY8B,316.81 2020-08-19,MA4B66MW5E27UANLY8B,315.4 2020-08-20,MA4B66MW5E27UANLY8B,313.33 2020-08-21,MA4B66MW5E27UANLY8B,314.14 2020-08-24,MA4B66MW5E27UANLY8B,308.84 2020-08-25,MA4B66MW5E27UANLY8B,312.22 2020-08-26,MA4B66MW5E27UANLY8B,308.82 2020-08-27,MA4B66MW5E27UANLY8B,311.67 2020-08-28,MA4B66MW5E27UANLY8B,314.37 2020-08-31,MA4B66MW5E27UANLY8B,312.55 2020-07-01,MA4B66MW5E27UAE4LQY,87.62 2020-07-02,MA4B66MW5E27UAE4LQY,88.31 2020-07-06,MA4B66MW5E27UAE4LQY,88.57 2020-07-07,MA4B66MW5E27UAE4LQY,86.31 2020-07-08,MA4B66MW5E27UAE4LQY,86.35 2020-07-09,MA4B66MW5E27UAE4LQY,82.74 2020-07-10,MA4B66MW5E27UAE4LQY,85.23 2020-07-13,MA4B66MW5E27UAE4LQY,85.4 2020-07-14,MA4B66MW5E27UAE4LQY,88.35 2020-07-15,MA4B66MW5E27UAE4LQY,88.89 2020-07-16,MA4B66MW5E27UAE4LQY,88.36 2020-07-17,MA4B66MW5E27UAE4LQY,87.19 2020-07-20,MA4B66MW5E27UAE4LQY,85.27 2020-07-21,MA4B66MW5E27UAE4LQY,91.39 2020-07-22,MA4B66MW5E27UAE4LQY,91.04 2020-07-23,MA4B66MW5E27UAE4LQY,91.01 2020-07-24,MA4B66MW5E27UAE4LQY,90.13 2020-07-27,MA4B66MW5E27UAE4LQY,91.04 2020-07-28,MA4B66MW5E27UAE4LQY,89.11 2020-07-29,MA4B66MW5E27UAE4LQY,90.07 2020-07-30,MA4B66MW5E27UAE4LQY,86.27 2020-07-31,MA4B66MW5E27UAE4LQY,83.94 2020-08-03,MA4B66MW5E27UAE4LQY,84.81 2020-08-04,MA4B66MW5E27UAE4LQY,86.49 2020-08-05,MA4B66MW5E27UAE4LQY,87.2 2020-08-06,MA4B66MW5E27UAE4LQY,87.47 2020-08-07,MA4B66MW5E27UAE4LQY,86.8 2020-08-10,MA4B66MW5E27UAE4LQY,89.73 2020-08-11,MA4B66MW5E27UAE4LQY,89.62 2020-08-12,MA4B66MW5E27UAE4LQY,90.72 2020-08-13,MA4B66MW5E27UAE4LQY,89.82 2020-08-14,MA4B66MW5E27UAE4LQY,90.35 2020-08-17,MA4B66MW5E27UAE4LQY,90.77 2020-08-18,MA4B66MW5E27UAE4LQY,87.63 2020-08-19,MA4B66MW5E27UAE4LQY,86.39 2020-08-20,MA4B66MW5E27UAE4LQY,84.81 2020-08-21,MA4B66MW5E27UAE4LQY,85.08 2020-08-24,MA4B66MW5E27UAE4LQY,87.2 2020-08-25,MA4B66MW5E27UAE4LQY,86.13 2020-08-26,MA4B66MW5E27UAE4LQY,84.78 2020-08-27,MA4B66MW5E27UAE4LQY,84.91 2020-08-28,MA4B66MW5E27UAE4LQY,85.63 2020-08-31,MA4B66MW5E27UAE4LQY,83.93 2020-07-01,MA4B66MW5E27U9VBBKA,214.59 2020-07-02,MA4B66MW5E27U9VBBKA,215.72 2020-07-06,MA4B66MW5E27U9VBBKA,217.55 2020-07-07,MA4B66MW5E27U9VBBKA,216.33 2020-07-08,MA4B66MW5E27U9VBBKA,218.89 2020-07-09,MA4B66MW5E27U9VBBKA,219.31 2020-07-10,MA4B66MW5E27U9VBBKA,220.26 2020-07-13,MA4B66MW5E27U9VBBKA,215.74 2020-07-14,MA4B66MW5E27U9VBBKA,219.16 2020-07-15,MA4B66MW5E27U9VBBKA,219.76 2020-07-16,MA4B66MW5E27U9VBBKA,219.11 2020-07-17,MA4B66MW5E27U9VBBKA,221.94 2020-07-20,MA4B66MW5E27U9VBBKA,223.43 2020-07-21,MA4B66MW5E27U9VBBKA,222.75 2020-07-22,MA4B66MW5E27U9VBBKA,224.23 2020-07-23,MA4B66MW5E27U9VBBKA,222.3 2020-07-24,MA4B66MW5E27U9VBBKA,221.44 2020-07-27,MA4B66MW5E27U9VBBKA,223.32 2020-07-28,MA4B66MW5E27U9VBBKA,222.18 2020-07-29,MA4B66MW5E27U9VBBKA,224.38 2020-07-30,MA4B66MW5E27U9VBBKA,223.98 2020-07-31,MA4B66MW5E27U9VBBKA,224.78 2020-08-03,MA4B66MW5E27U9VBBKA,227.18 2020-08-04,MA4B66MW5E27U9VBBKA,227.15 2020-08-05,MA4B66MW5E27U9VBBKA,228.7 2020-08-06,MA4B66MW5E27U9VBBKA,230.89 2020-08-07,MA4B66MW5E27U9VBBKA,231.56 2020-08-10,MA4B66MW5E27U9VBBKA,230.34 2020-08-11,MA4B66MW5E27U9VBBKA,228.57 2020-08-12,MA4B66MW5E27U9VBBKA,229.75 2020-08-13,MA4B66MW5E27U9VBBKA,231.73 2020-08-14,MA4B66MW5E27U9VBBKA,230.25 2020-08-17,MA4B66MW5E27U9VBBKA,232.62 2020-08-18,MA4B66MW5E27U9VBBKA,234.23 2020-08-19,MA4B66MW5E27U9VBBKA,234.63 2020-08-20,MA4B66MW5E27U9VBBKA,236.61 2020-08-21,MA4B66MW5E27U9VBBKA,237.49 2020-08-24,MA4B66MW5E27U9VBBKA,237.75 2020-08-25,MA4B66MW5E27U9VBBKA,238.21 2020-08-26,MA4B66MW5E27U9VBBKA,240.45 2020-08-27,MA4B66MW5E27U9VBBKA,240.87 2020-08-28,MA4B66MW5E27U9VBBKA,242.99 2020-08-31,MA4B66MW5E27U9VBBKA,239.93 ================================================ FILE: gs_quant/test/resources/SPX_50_rcorr_out.csv ================================================ date,value 2020-08-03,12.612392703577878 2020-08-04,12.314962189768957 2020-08-05,11.971388636314524 2020-08-06,10.177980371069935 2020-08-07,7.589150088869332 2020-08-10,6.406410586090888 2020-08-11,7.115958347867793 2020-08-12,8.099475991100048 2020-08-13,7.008542424903644 2020-08-14,5.730739361572441 2020-08-17,5.11276156285547 2020-08-18,4.768737141376301 2020-08-19,5.218747482529771 2020-08-20,5.98457406069639 2020-08-21,5.945671206944129 2020-08-24,4.68620449371249 2020-08-25,4.325563613009139 2020-08-26,3.874866143046865 2020-08-27,3.208829562109064 2020-08-28,2.1882935772226038 2020-08-31,3.5637399052763823 ================================================ FILE: gs_quant/test/resources/SPX_50_weights.csv ================================================ underlyingAssetId,netWeight MA4B66MW5E27U9VBB94,0.12894893912382632 MA4B66MW5E27UAL9SUX,0.10487058104642008 MA4B66MW5E27U9YGMGY,0.08865594407673963 MA4B66MW5E3VLSECN,0.04322960215959449 MA4B66MW5E27UAGYZ4D,0.02998051513728512 MA4B66MW5E27UAGYZ49,0.029305616495619514 MA4B66MW5E27UA447M6,0.025990928142842318 MA4B66MW5E27UAKHZDF,0.024782814864756905 MA4B66MW5E27UANLYJN,0.02193018971281102 MA4B66MW5E27UALUL3R,0.02099846234333268 MA4B66MW5E27UALNB5V,0.020178602444579943 MA4B66MW5E27UAKVAB4,0.019411260715533937 MA4B66MW5E27UAHKGMA,0.018797868660751563 MA4B66MW5E27UAKHZFD,0.018719024314371398 MA4B66MW5E27UANLY8B,0.018175442346871868 MA4B66MW5E27U9XPV7X,0.015166948128950638 MA4B66MW5E27UADJ5RL,0.015063066463534955 MA4B66MW5E27UANT8PM,0.015038623722803077 MA4B66MW5E27UAM94N9,0.014697344056385283 MA4B66MW5E27UAE4MG6,0.014605319206424208 MA4B66MW5E27UALG33R,0.014280961481262365 MA4B66MW5E27UAJQETU,0.013227511306088907 MA4B66MW5E27UAL9SRL,0.013197330451830622 MA4B66MW5E27UAN8F94,0.013023566863912107 MA4B66MW5E27UALUL23,0.012871556765436545 MA4B66MW5E27UACX6QV,0.01251466013497345 MA4B66MW5E27U9ZPDGH,0.012323504332084366 MA4B66MW5E27UANZGNN,0.012055494497452132 MAMDDXVJYX77V2K9,0.011916000867389665 MA4B66MW5E27U9VBBEQ,0.011873205804943766 MA4B66MW5E27UAKNZKD,0.011739404931032501 MA4B66MW5E27UADJ5VJ,0.01097886164510534 MA4B66MW5E27UANEPLL,0.010388751595799509 MA4B66MW5E27UANZH3T,0.010354945360530238 MA4B66MW5E27U9VBBAX,0.010349104642249253 MA4B66MW5E27UAL3HZC,0.009734986253575927 MA4B66MW5E27UAE4LQY,0.009608149578209565 MA4B66MW5E27UADJ5GE,0.009412221538712708 MA4B66MW5E27U9VBBKA,0.00937185354932531 MA4B66MW5E27U9YGMCG,0.009137182508975992 MA4B66MW5E27UAL3J7Z,0.008837377661943941 MA4B66MW5E27UA4479J,0.008629756496443786 MA4B66MW5E27U9ZPD6K,0.00851007293384439 MA4B66MW5E27UALG396,0.008507501070388817 MA4B66MW5E27UALG2XD,0.008378382410542949 MA4B66MW5E27UAM94Q4,0.008215227366905583 MA4B66MW5E27UAKV9P7,0.008042360199041528 MA4B66MW5E27UANLY9V,0.008007036769777267 MA4B66MW5E27UANLXV6,0.007999544164467066 MA4B66MW5E27UAE4MDU,0.007966393654319408 ================================================ FILE: gs_quant/test/resources/Sharpe_SPX_0175.csv ================================================ Date,ER,SR,SR10 1/2/2019,2510.03,, 1/3/2019,2447.767885,, 1/4/2019,2531.69889,5.737397246, 1/7/2019,2549.079649,4.449072897, 1/8/2019,2573.675706,9.336167073, 1/9/2019,2584.100661,10.67209822, 1/10/2019,2595.654903,12.14044669, 1/11/2019,2595.148777,10.44218799, 1/14/2019,2581.120256,5.133313128, 1/15/2019,2608.684612,7.910872116, 1/16/2019,2614.357823,8.121631827, 1/17/2019,2634.090551,10.08850727,12.18121931 1/18/2019,2668.712414,14.19001095,40.95684776 1/22/2019,2630.38301,6.056098812,5.846560783 1/23/2019,2636.055121,6.264733102,5.407659472 1/24/2019,2639.556951,6.307254628,4.368058482 1/25/2019,2661.858405,7.668831979,7.264197349 1/28/2019,2640.559894,5.197758374,5.817298719 1/29/2019,2636.581273,4.858262774,2.366850327 1/30/2019,2677.50294,6.79219761,5.938438523 1/31/2019,2700.422711,7.940600115,6.154833156 2/1/2019,2702.721162,7.86300812,2.666676135 2/4/2019,2720.66656,7.927911587,11.53596301 2/5/2019,2733.364001,8.444199831,17.2550762 2/6/2019,2727.141018,7.88248651,14.0502241 2/7/2019,2701.448131,6.299736045,4.115430446 2/8/2019,2703.146487,6.254433702,9.781774874 2/11/2019,2704.671688,5.776823851,9.666491167 2/12/2019,2739.469962,6.989064855,7.684225379 2/13/2019,2747.636537,7.214054079,6.420515139 2/14/2019,2740.20271,6.759336945,4.983149425 2/15/2019,2769.939337,7.785808095,7.713095632 2/19/2019,2773.559537,7.10916679,4.495246183 2/20/2019,2778.364409,7.185356987,6.127236086 2/21/2019,2768.408942,6.693636641,8.630741744 2/22/2019,2786.064052,7.20658927,13.99925938 2/25/2019,2789.096988,6.841433278,14.4099546 2/26/2019,2786.750866,6.673597887,6.48202346 2/27/2019,2785.095051,6.535607388,6.199502504 2/28/2019,2777.06941,6.190945631,5.83917926 3/1/2019,2796.133953,6.680636631,3.792550161 3/4/2019,2784.845282,5.956791728,2.009012869 3/5/2019,2781.54932,5.7991744,0.540084567 3/6/2019,2763.213812,5.201919926,-0.746311437 3/7/2019,2740.558989,4.525727655,-4.754985617 3/8/2019,2734.56556,4.337274277,-7.267350492 3/11/2019,2774.395429,4.945312265,-1.110104627 3/12/2019,2782.48013,5.091417812,-0.243274517 3/13/2019,2801.744331,5.480767999,2.504341292 3/14/2019,2799.167789,5.36978382,0.277759014 3/15/2019,2813.031266,5.640284457,3.647657774 3/18/2019,2823.079554,5.621848436,4.853717931 3/19/2019,2822.572042,5.564235953,7.718086565 3/20/2019,2814.094247,5.320596489,11.09480099 3/21/2019,2844.606859,5.885717662,22.23353648 3/22/2019,2790.29818,4.454225737,1.436908084 3/25/2019,2787.539843,4.234789558,0.409489639 3/26/2019,2807.503711,4.557826227,0.450464546 3/27/2019,2794.276803,4.268696541,-0.368955488 3/28/2019,2804.210231,4.417900045,-0.643597382 3/29/2019,2823.033369,4.715817253,-0.004074299 4/1/2019,2855.410019,5.07339745,2.666032514 4/2/2019,2855.320742,5.038323758,3.505142709 4/3/2019,2861.341262,5.113246277,1.260746024 4/4/2019,2867.191583,5.184526976,8.631023512 4/5/2019,2880.401713,5.380427768,26.87758889 4/8/2019,2883.009855,5.247939574,15.64792247 4/9/2019,2865.299088,4.887317953,12.55366635 4/10/2019,2875.169176,5.020441161,14.05752358 4/11/2019,2875.138876,4.990095794,9.117868524 4/12/2019,2894.088272,5.259209555,7.665310152 4/15/2019,2891.834475,5.060919816,7.798748866 4/16/2019,2893.173231,5.054332411,6.659651897 4/17/2019,2886.421816,4.91411799,3.597258413 4/18/2019,2890.860822,4.957165876,1.874084426 4/22/2019,2893.235955,4.79789368,1.81851237 4/23/2019,2918.804495,5.125405643,9.127845763 4/24/2019,2912.231986,4.995412634,6.812452806 4/25/2019,2911.009589,4.951515971,6.573183738 4/26/2019,2924.577344,5.120366057,5.242583371 4/29/2019,2927.298712,5.024662597,7.002256724 4/30/2019,2929.955748,5.039084317,7.446964837 5/1/2019,2907.712448,4.66592573,3.06080028 5/2/2019,2901.360322,4.55318402,1.440177493 5/3/2019,2929.338398,4.879486516,6.151386888 5/6/2019,2915.738926,4.564196982,-0.330056802 5/7/2019,2867.176375,3.804094762,-3.153069628 5/8/2019,2862.406078,3.72909792,-3.351272602 5/9/2019,2853.566206,3.603449349,-4.436562827 5/10/2019,2864.106558,3.712486892,-4.647841998 5/13/2019,2794.156553,2.682081289,-4.729242479 5/14/2019,2816.559665,2.895092284,-3.552130285 5/15/2019,2832.971982,3.051560717,-2.844970225 5/16/2019,2858.193493,3.290702093,-2.754588393 5/17/2019,2841.263572,3.089456883,-3.371339268 5/20/2019,2821.546557,2.813651182,-2.109546256 5/21/2019,2845.538591,3.030760539,-0.937878375 5/22/2019,2837.309251,2.935528837,-0.907318899 5/23/2019,2803.140404,2.558318405,-2.625002434 5/24/2019,2806.823312,2.58555756,0.946898175 5/28/2019,2782.6036,2.279079256,-2.164100208 5/29/2019,2763.097473,2.08835015,-3.999405729 5/30/2019,2768.802287,2.133012153,-4.930694066 5/31/2019,2731.866718,1.777955159,-5.946729146 6/3/2019,2723.855276,1.679183598,-5.581294117 6/4/2019,2782.541865,2.103881478,-2.659678821 6/5/2019,2805.285495,2.279181283,-1.54602109 6/6/2019,2822.488213,2.412132527,1.158841697 6/7/2019,2852.200087,2.6386267,3.162681941 6/10/2019,2865.170959,2.698024009,7.730326 6/11/2019,2864.020632,2.680915739,11.24944724 6/12/2019,2858.000453,2.622657748,9.73482906 6/13/2019,2869.660261,2.709360789,20.24305466 6/14/2019,2864.859795,2.661685323,36.58732897 6/17/2019,2867.128677,2.635912908,11.64870595 6/18/2019,2895.068307,2.838826395,19.16606611 6/19/2019,2903.636472,2.899386713,17.26000646 6/20/2019,2931.214114,3.099173512,15.21123798 6/21/2019,2927.350608,3.058686644,14.95275521 6/24/2019,2921.810433,2.964022575,10.45723532 6/25/2019,2893.697056,2.717971377,4.512919377 6/26/2019,2889.955339,2.681791627,2.385748199 6/27/2019,2900.953597,2.757579839,4.605128427 6/28/2019,2917.651513,2.874002181,8.467688925 7/1/2019,2939.792606,2.988565586,5.558272358 7/2/2019,2948.328407,3.044899498,6.070750379 7/3/2019,2970.993986,3.199708166,4.949335898 7/5/2019,2965.292525,3.126829262,4.659306823 7/8/2019,2950.396524,2.964145418,3.192179536 7/9/2019,2953.93176,2.982905921,7.966365616 7/10/2019,2967.227117,3.070166661,14.73097777 7/11/2019,2973.92142,3.111249212,14.27473256 7/12/2019,2987.635691,3.200922006,13.30402491 7/15/2019,2987.726183,3.155144134,8.230621072 7/16/2019,2977.319655,3.069918952,4.639458481 7/17/2019,2957.553525,2.912973662,-1.524611399 7/18/2019,2968.098649,2.978988559,0.421498926 7/19/2019,2949.453053,2.833321596,-0.149255599 7/22/2019,2957.438864,2.848537867,0.501551302 7/23/2019,2977.733759,2.974552959,1.385515258 7/24/2019,2991.677759,3.060701789,2.407770652 7/25/2019,2975.640775,2.937770909,-1.318417249 7/26/2019,2997.684963,3.072844726,1.350866681 7/29/2019,2992.353592,2.994935655,1.744389804 7/30/2019,2984.416639,2.933718723,3.352688469 7/31/2019,2951.470165,2.688070444,-1.48815175 8/1/2019,2924.505486,2.494236808,-2.022224109 8/2/2019,2902.85181,2.342741448,-4.328466769 8/5/2019,2815.114219,1.68381695,-4.522054442 8/6/2019,2852.005933,1.878395118,-3.901560517 8/7/2019,2854.075847,1.886919276,-3.727091993 8/8/2019,2908.045754,2.158059512,-2.615045294 8/9/2019,2888.46273,2.037137957,-3.235046962 8/12/2019,2852.477294,1.797830191,-3.305510899 8/13/2019,2895.567143,2.011180058,-1.736055839 8/14/2019,2809.704892,1.480331331,-2.474044237 8/15/2019,2816.566807,1.512768537,-2.086295734 8/16/2019,2857.508182,1.705975584,2.236689655 8/19/2019,2892.056916,1.851340048,1.967550838 8/20/2019,2868.774894,1.725297533,0.641887824 8/21/2019,2892.553797,1.837021998,-0.569088305 8/22/2019,2890.931738,1.825669175,0.108139514 8/23/2019,2814.94975,1.404434436,-1.375234357 8/26/2019,2845.804346,1.528332058,-1.48255279 8/27/2019,2836.444425,1.481024108,1.23971506 8/28/2019,2855.084952,1.563992111,2.404012208 8/29/2019,2891.584766,1.723214457,1.963951014 8/30/2019,2893.322499,1.728540318,0.076950447 9/3/2019,2872.563465,1.602928545,0.190298356 9/4/2019,2903.932188,1.737095582,0.576037512 9/5/2019,2942.009379,1.897659921,2.989598919 9/6/2019,2944.574712,1.906294928,11.56522726 9/9/2019,2943.860217,1.882853651,12.27648517 9/10/2019,2944.675433,1.883546765,14.87586888 9/11/2019,2966.070601,1.973657259,16.08780445 9/12/2019,2974.564923,2.008351081,10.34546432 9/13/2019,2972.238424,1.994528662,10.60992313 9/16/2019,2962.369946,1.929013034,13.70179959 9/17/2019,2969.964212,1.959336818,10.34642175 9/18/2019,2970.848102,1.96019486,4.21719707 9/19/2019,2970.761941,1.956749815,6.350357989 9/20/2019,2955.895878,1.886179176,2.702190127 9/23/2019,2955.169434,1.864162156,1.9577415 9/24/2019,2929.8441,1.746589955,-4.166570859 9/25/2019,2947.969891,1.819696396,-3.335353352 9/26/2019,2940.574793,1.784713514,-4.031512281 9/27/2019,2924.599947,1.711671501,-5.111794619 9/30/2019,2939.11802,1.755145101,-3.415420504 10/1/2019,2902.483317,1.588969073,-5.232043964 10/2/2019,2849.700488,1.351233709,-5.752138603 10/3/2019,2872.579918,1.439622075,-4.133190818 10/4/2019,2913.818529,1.594758687,-2.341533411 10/7/2019,2900.168028,1.523737838,-1.552286367 10/8/2019,2854.29527,1.326610536,-3.458172882 10/9/2019,2880.494435,1.424318367,-2.482382887 10/10/2019,2899.08252,1.493689506,-1.195479606 10/11/2019,2931.079794,1.611412031,-0.455553259 10/14/2019,2926.52653,1.578851675,1.379149689 10/15/2019,2955.912342,1.685678031,9.843206385 10/16/2019,2949.776719,1.659284015,7.508775004 10/17/2019,2957.891487,1.688115649,3.613613869 10/18/2019,2945.995753,1.638856137,5.108865091 10/21/2019,2966.080265,1.698657316,14.70205518 10/22/2019,2955.204105,1.654053467,11.18485218 10/23/2019,2963.588467,1.683413305,9.650880497 10/24/2019,2969.212414,1.702418318,5.066408114 10/25/2019,2981.32608,1.745334859,11.66704816 10/28/2019,2997.755192,1.790313202,6.656416125 10/29/2019,2995.077442,1.77781171,8.862065137 10/30/2019,3004.809916,1.811480808,9.623997272 10/31/2019,2995.451909,1.773356748,9.535930822 11/1/2019,3024.65405,1.874731388,13.4656661 11/4/2019,3035.566892,1.898308346,17.52951137 11/5/2019,3031.767354,1.88163384,15.21816116 11/6/2019,3033.777793,1.886593046,13.94788731 11/7/2019,3042.028127,1.914168077,12.80555585 11/8/2019,3049.778353,1.939872169,13.10648351 11/11/2019,3043.257179,1.899349088,9.500131165 11/12/2019,3047.937216,1.913855493,8.485152431 11/13/2019,3049.986818,1.918848667,11.45789141 11/14/2019,3052.426314,1.925233259,5.640502452 11/15/2019,3076.105883,2.004950595,12.63768548 11/18/2019,3077.220816,1.992340956,12.1166052 11/19/2019,3075.218951,1.982588032,11.07279475 11/20/2019,3063.347375,1.936538671,4.366137875 11/21/2019,3058.27627,1.91579795,1.61974586 11/22/2019,3064.875403,1.936676881,5.320243107 11/25/2019,3087.771719,1.998506923,7.563417536 11/26/2019,3094.499489,2.019497628,8.614824316 11/27/2019,3107.456725,2.061567466,10.95116207 11/29/2019,3094.500222,2.005374056,2.518096737 12/2/2019,3066.932263,1.888886717,-1.126922164 12/3/2019,3046.110794,1.812090144,-2.717117873 12/4/2019,3065.52043,1.874270276,0.209795082 12/5/2019,3070.039015,1.887438599,1.210253641 12/6/2019,3098.367474,1.976989432,3.379522363 12/9/2019,3087.958795,1.925650247,0.015883385 12/10/2019,3084.366353,1.911152182,-0.886607713 12/11/2019,3093.323977,1.938698204,-1.204740642 12/12/2019,3120.111459,2.0221114,2.577594462 12/13/2019,3120.187332,2.019977526,7.876659091 12/16/2019,3142.375215,2.075450874,16.346174 12/17/2019,3143.290075,2.076067406,14.58776383 12/18/2019,3141.754783,2.068511407,13.10719191 12/19/2019,3155.829858,2.111565723,9.640269371 12/20/2019,3171.523942,2.159526372,23.05148842 12/23/2019,3173.84418,2.150910398,22.71772354 12/24/2019,3173.057358,2.145825522,19.84994623 12/26/2019,3189.273973,2.187873635,14.33781111 12/27/2019,3189.226578,2.18519334,16.89994938 12/30/2019,3170.024075,2.104495085,4.202973659 12/31/2019,3179.357484,2.131684225,6.457810697 ================================================ FILE: gs_quant/test/resources/asset-query-USDJPY.json ================================================ { "totalResults": 1, "results": [ { "assetClass": "FX", "classifications": {}, "createdById": "3549cd2c46c3468bae0d90b1f2b9e609", "createdTime": "2017-06-16T13:54:38.452Z", "currency": "JPY", "description": "Japan Yen / United States Dollar", "exchange": "", "id": "MATGYV0J9MPX534Z", "identifiers": [ { "type": "CROSS", "value": "JPY/USD" }, { "type": "SECNAME", "value": "JPY/USD" }, { "type": "BID", "value": "USDJPY" }, { "type": "MDAPI", "value": "JPY/USD" } ], "lastUpdatedById": "3549cd2c46c3468bae0d90b1f2b9e609", "lastUpdatedTime": "2019-08-20T14:07:02.45Z", "listed": true, "name": "JPY/USD", "ownerId": "3549cd2c46c3468bae0d90b1f2b9e609", "parameters": {}, "rank": 1, "region": "Global", "shortName": "JPY/USD", "styles": [], "tags": [ "JPYUSD", "USDJPY", "Execution" ], "type": "Cross", "xref": { "bbid": "USDJPY", "secName": "JPY/USD", "cross": "JPY/USD", "mdapi": "JPY/USD" } } ] } ================================================ FILE: gs_quant/test/resources/nobbid-xrefs.json ================================================ { "xrefs": [ { "startDate": "1952-01-01", "endDate": "2952-12-31", "identifiers": { "secName": "USD/JPY", "cross": "USD/JPY", "mdapi": "USD/JPY" } } ] } ================================================ FILE: gs_quant/test/resources/nobbid.json ================================================ { "assetClass": "FX", "classifications": {}, "createdById": "3549cd2c46c3468bae0d90b1f2b9e609", "createdTime": "2018-03-22T14:09:20.124Z", "currency": "USD", "description": "United States Dollar / Japan Yen", "exchange": "", "id": "nobbid", "identifiers": [ { "type": "SECNAME", "value": "USD/JPY" }, { "type": "MDAPI", "value": "USD/JPY" }, { "type": "CROSS", "value": "USD/JPY" } ], "lastUpdatedById": "3549cd2c46c3468bae0d90b1f2b9e609", "lastUpdatedTime": "2019-08-15T14:09:53.942Z", "listed": true, "name": "USD/JPY", "ownerId": "3549cd2c46c3468bae0d90b1f2b9e609", "parameters": {}, "rank": 1, "region": "Global", "shortName": "USD/JPY", "styles": [], "tags": [ "USDJPY", "JPYUSD" ], "type": "Cross", "xref": { "secName": "USD/JPY", "cross": "USD/JPY", "mdapi": "USD/JPY" } } ================================================ FILE: gs_quant/test/risk/test_measures.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant import risk from gs_quant.instrument import IRSwap from gs_quant.risk import FloatWithInfo, DataFrameWithInfo, AggregationLevel from gs_quant.test.utils.mock_calc import MockCalc swap_1 = IRSwap("Pay", "5y", "EUR", fixed_rate=-0.005, name="5y") def test_currency_params(mocker): price = risk.Price myr_price = risk.Price(currency="MYR") with MockCalc(mocker): res1 = swap_1.calc(myr_price) res2 = swap_1.calc(price) res3 = swap_1.calc((price, myr_price)) assert res1 != res2 assert res3[price] == res2 assert res3[myr_price] == res1 def test_finite_difference_params(mocker): nok_delta = risk.IRDelta(currency="NOK") local_ccy_delta = risk.IRDelta(currency="local") local_aggregated_delta = risk.IRDelta(currency="local", aggregation_level=AggregationLevel.Type) with MockCalc(mocker): res3 = swap_1.calc(risk.IRDelta) res5 = swap_1.calc((risk.Price, nok_delta)) res6 = swap_1.calc((local_ccy_delta, nok_delta)) res7 = swap_1.calc(risk.IRDelta(aggregation_level=AggregationLevel.Asset)) res8 = swap_1.calc((local_ccy_delta, local_aggregated_delta)) assert isinstance(res5[risk.Price], FloatWithInfo) assert isinstance(res5[nok_delta], DataFrameWithInfo) assert res6[local_ccy_delta]["value"].size != res6[nok_delta]["value"].size assert res6[local_ccy_delta]["value"].size != res3["value"].size assert res6[nok_delta]["value"].size != res3["value"].size assert res7["mkt_asset"].size == 2 assert not isinstance(res8[local_ccy_delta], type(res8[local_aggregated_delta])) def test_risk_measure_setters(): base_delta = risk.IRDelta usd_delta = risk.IRDelta(currency="USD") assert usd_delta.parameters is not None assert usd_delta.parameters.currency == "USD" local_usd_delta = usd_delta(local_curve=True) assert local_usd_delta.parameters.local_curve assert usd_delta != local_usd_delta assert risk.IRDelta.parameters is None assert base_delta.parameters is None ================================================ FILE: gs_quant/test/risk/test_results.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import gs_quant.risk as risk import numpy as np import pytest from gs_quant.base import RiskKey from gs_quant.common import MarketDataPattern, RiskRequestParameters from gs_quant.instrument import IRSwap, IRBasisSwap, IRSwaption, FXMultiCrossBinary, FXMultiCrossBinaryLeg, CommodSwap from gs_quant.markets import HistoricalPricingContext, PricingContext, CloseMarket, MarketDataCoordinate from gs_quant.markets.portfolio import Portfolio from gs_quant.risk import MultiScenario, ResolvedInstrumentValues from gs_quant.risk import Price, RollFwd, CurveScenario, ErrorValue, DataFrameWithInfo, AggregationLevel, PnlExplain from gs_quant.risk.core import aggregate_risk, SeriesWithInfo, FloatWithInfo, StringWithInfo from gs_quant.risk.results import MultipleScenarioFuture from gs_quant.risk.results import MultipleScenarioResult from gs_quant.risk.transform import ResultWithInfoAggregator from gs_quant.test.utils.mock_calc import MockCalc curvescen1 = CurveScenario( market_data_pattern=MarketDataPattern('IR', 'USD'), parallel_shift=5, name='parallel shift5bp' ) curvescen2 = CurveScenario( market_data_pattern=MarketDataPattern('IR', 'USD'), curve_shift=1, tenor_start=5, tenor_end=30, name='curve shift1bp', ) rollfwd = RollFwd(date=dt.date(2020, 11, 3), name='roll fwd scenario') multiscenario = MultiScenario(scenarios=tuple((curvescen1, curvescen2)), name='multiscenario') def get_attributes(p, risks, ctx='PricingCtx1', resolve=False, no_frame=False): contexts = { 'Multiple': HistoricalPricingContext(dt.date(2020, 1, 14), dt.date(2020, 1, 15), market_data_location='LDN'), 'PricingCtx1': PricingContext(dt.date(2020, 1, 14), market_data_location='LDN'), 'Multiple2': HistoricalPricingContext(dt.date(2020, 1, 16), dt.date(2020, 1, 17), market_data_location='LDN'), 'PricingCtx2': PricingContext(dt.date(2020, 1, 16), market_data_location='NYC'), 'PricingCtx3': PricingContext(dt.date(2020, 1, 16), market_data_location='LDN'), 'RollFwd': rollfwd, 'CurveScen1': curvescen1, 'CurveScen2': curvescen2, 'MultiScen': multiscenario, } if resolve: p.resolve() if contexts.get(ctx): with contexts.get(ctx): res = p.calc(risks) elif ctx == 'Composite': with rollfwd, multiscenario: res = p.calc(risks) if not no_frame: frame = res.to_frame(None, None, None) return [col for col in frame.columns], res, frame else: return res swap_1 = IRSwap("Pay", "5y", "EUR", fixed_rate=-0.005, name="5y") swap_2 = IRSwap("Pay", "10y", "EUR", fixed_rate=-0.005, name="10y") swap_3 = IRSwap("Pay", "5y", "USD", fixed_rate=-0.005, name="5y") swap_4 = IRSwap("Pay", "10y", "USD", fixed_rate=-0.005, name="10y") swap_5 = IRSwap("Pay", "5y", "GBP", fixed_rate=-0.005, name="5y") swap_6 = IRSwap("Pay", "10y", "GBP", fixed_rate=-0.005, name="10y") swap_7 = IRSwap("Pay", "5y", "JPY", fixed_rate=-0.005, name="5y") swap_8 = IRSwap("Pay", "10y", "JPY", fixed_rate=-0.005, name="10y") commod_swap = CommodSwap(name="Test") eur_port = Portfolio([swap_1, swap_2], name="EUR") usd_port = Portfolio([swap_3, swap_4], name="USD") gbp_port = Portfolio([swap_5, swap_6], name="GBP") jpy_port = Portfolio([swap_7, swap_8], name='JPY') port1 = Portfolio([eur_port, gbp_port], name='EURGBP') port2 = Portfolio([jpy_port, usd_port], name='USDJPY') commod_port = Portfolio([commod_swap]) port = Portfolio([port1, port2]) swaption_port = Portfolio( [ IRSwaption("Receive", '5y', 'USD', expiration_date='2m', strike='atm', name='Swaption1'), IRSwaption("Receive", '10y', 'USD', expiration_date='3m', strike='atm', name='Swaption2'), ] ) bs = IRBasisSwap( termination_date="2y", notional_currency="GBP", notional_amount="$405392/bp", effective_date="10y", payer_rate_option="OIS", receiver_frequency="3m", name='IRBasisSwap', ) bs_port = Portfolio([bs]) mixed_port = Portfolio([bs_port, gbp_port]) swaption_1 = IRSwaption('Pay', '5y', 'USD', expiration_date='1y', name='1y') swaption_2 = IRSwaption('Pay', '5y', 'USD', expiration_date='3m', name='3m') swaption_3 = IRSwaption('Pay', '5y', 'USD', expiration_date='6m', name='6m') swaption_port1 = Portfolio((swaption_1, swaption_2, swaption_3)) swaption_port2 = Portfolio([swaption_1, swaption_2]) swap_port1 = Portfolio([swaption_port1, usd_port]) swap_port2 = Portfolio([swaption_port2, usd_port]) # Portfolio of swaption with an unmarked correlation swaption_port3 = Portfolio(IRSwaption('Pay', termination_date='6m', effective_date='2y', expiration_date='3y')) mcb = Portfolio(FXMultiCrossBinary(name='mcb', legs=(FXMultiCrossBinaryLeg(),)), name='mcb_port') def default_pivot_table_test(res, with_dates=''): port_depth = len(max(res.portfolio.all_paths, key=len)) pivot_df = res.to_frame() if with_dates == 'dated': assert pivot_df.index.name == 'dates' else: if with_dates == 'has_bucketed': if res.dates: assert pivot_df.index.names[-3:] == ['instrument_name', 'risk_measure', 'dates'] else: assert pivot_df.index.names[-2:] == ['instrument_name', 'risk_measure'] assert pivot_df.columns.values[-1] == 'value' else: if port_depth == 1: assert pivot_df.index.nlevels == port_depth if len(res._multi_scen_key) > 1: if len(res.risk_measures) > 1: assert pivot_df.columns.names == ['risk_measure', 'scenario'] else: assert pivot_df.columns.name == 'scenario' else: if port_depth == 1: assert pivot_df.columns.name == 'risk_measure' else: assert pivot_df.columns.names[-1] == 'risk_measure' def price_values_test(res, f, with_dates=''): port_depth = len(max(res.portfolio.all_paths, key=len)) + 1 # +1 for risk measure if with_dates == 'dated': port_depth += 1 # +1 for dates if len(res.risk_measures) > 1: res_val_map = [n for r in res for n in r[Price].values] if with_dates == 'dated' else [r[Price] for r in res] else: res_val_map = [n for r in res for n in r.values] if with_dates == 'dated' else list(res) f = f.replace('N/A', np.nan)[f['risk_measure'] == risk.Price].dropna(axis='columns') df_val_map = f['value'].values f = f.drop('value', axis=1) assert all(res_val_map == df_val_map) # check if price values are correctly tabulated assert port_depth == f.columns.size # check if index setting is correct def test_multi_scenario(mocker): with MockCalc(mocker): _, r1, f1 = get_attributes(usd_port, risk.Price, resolve=True, ctx='MultiScen') _, r2, f2 = get_attributes(usd_port, (risk.Price, risk.DollarPrice), resolve=True, ctx='MultiScen') _, r3, f3 = get_attributes(port1, (risk.IRFwdRate, risk.Price), resolve=True, ctx='MultiScen') _, r4, f4 = get_attributes(port1, risk.Price, resolve=True, ctx='MultiScen') default_pivot_table_test(r1) default_pivot_table_test(r2) default_pivot_table_test(r3) default_pivot_table_test(r4) # test slicing swap_res = r1[swap_3] swap_res_idx = r1[0] assert isinstance(swap_res, MultipleScenarioResult) assert swap_res == swap_res_idx multi_rm_and_scen_res = r3[curvescen1] multi_scen_res = r4[curvescen1] assert multi_rm_and_scen_res._multi_scen_key[0] == multi_scen_res._multi_scen_key[0] == curvescen1 # test futures futures = r1.futures assert len(futures) == 2 assert isinstance(futures[0], MultipleScenarioFuture) assert isinstance(futures[0].result(), MultipleScenarioResult) def test_historical_multi_scenario(mocker): with MockCalc(mocker): with HistoricalPricingContext(dt.date(2020, 1, 14), dt.date(2020, 1, 15), market_data_location='LDN'): with multiscenario: res = Portfolio(swap_3).price() res_multi_rm = Portfolio(swap_3).calc((risk.Price, risk.IRFwdRate)) default_pivot_table_test(res, with_dates='dated') default_pivot_table_test(res_multi_rm, with_dates='dated') # test slicing date_res = res[dt.date(2020, 1, 14)] assert all([isinstance(r, FloatWithInfo) for r in date_res.futures[0].result().values()]) date_res_2 = res_multi_rm[swap_3][risk.Price][dt.date(2020, 1, 14)] assert all([isinstance(r, FloatWithInfo) for r in date_res_2.values()]) scen_slice_res = res[curvescen2] assert all([isinstance(r, SeriesWithInfo) for r in scen_slice_res.futures[0].result().values()]) assert isinstance(res_multi_rm[swap_3][risk.Price][curvescen2], SeriesWithInfo) # test futures futures = res.futures assert isinstance(futures[0], MultipleScenarioFuture) assert isinstance(futures[0].result(), MultipleScenarioResult) def test_series_with_info_arithmetics(mocker): series_info = SeriesWithInfo([2.0, 4.0], [dt.date(2021, 4, 11), dt.date(2022, 4, 11)]) scaled = series_info * 100 assert isinstance(scaled, SeriesWithInfo) assert tuple(scaled.values) == (200.0, 400.0) def test_composite_multi_scenario(mocker): with MockCalc(mocker): c, res1, _ = get_attributes(usd_port, risk.Price, resolve=True, ctx='Composite') c2, res2, _ = get_attributes(eur_port, (risk.Price, risk.IRFwdRate), resolve=True, ctx='Composite') assert 'scenario' in c assert 'scenario' in c2 assert 'risk_measure' in c2 assert len(res1.risk_measures) == 1 assert len(res2.risk_measures) == 2 def test_one_portfolio(mocker): with MockCalc(mocker): _, r1, f1 = get_attributes(eur_port, risk.Price) _, r2, f2 = get_attributes(eur_port, (risk.Price, risk.DollarPrice)) _, _, f3 = get_attributes(Portfolio(swap_1, name='swap_1'), (risk.Price, risk.DollarPrice)) price_values_test(r1, f1) price_values_test(r2, f2) default_pivot_table_test(r1) default_pivot_table_test(r2) # test slicing # slice one risk measure sub_r1 = r1[risk.Price] assert sub_r1 == r1 # slice one instrument sub_r2 = r2[swap_1].to_frame().values[0] assert all(sub_r2 == f3['value'].values) # test aggregate agg_r1 = r1.aggregate().to_frame() agg_r2 = r2.aggregate().to_frame().values[0] assert agg_r1 == sum(f1['value'].values) assert all( agg_r2 == [sum(f2.loc[f2['risk_measure'] == rm]['value'].values) for rm in [risk.Price, risk.DollarPrice]] ) def test_dated_risk_values(mocker): with MockCalc(mocker): _, res1, frame1 = get_attributes(port, risk.Price, 'Multiple') _, res2, frame2 = get_attributes(port1, (risk.DollarPrice, risk.Price), 'Multiple') _, res3, frame3 = get_attributes(port1, risk.Price) _, res4, frame4 = get_attributes(port1, (risk.DollarPrice, risk.Price)) _, res5, frame5 = get_attributes(gbp_port, (risk.DollarPrice, risk.Price), 'Multiple') _, res6, frame6 = get_attributes(jpy_port, risk.Price, 'Multiple') price_values_test(res1, frame1, 'dated') price_values_test(res2, frame2, 'dated') price_values_test(res5, frame5, 'dated') price_values_test(res6, frame6, 'dated') default_pivot_table_test(res1, 'dated') default_pivot_table_test(res2, 'dated') default_pivot_table_test(res5, 'dated') default_pivot_table_test(res6, 'dated') # test slicing sub_res1 = res1[risk.Price] assert sub_res1 == res1 # slice one date slice_date_res2 = res2[dt.date(2020, 1, 14)] assert all(slice_date_res2.to_frame(None, None, None) == frame4) slice_date_res3 = slice_date_res2[risk.Price] assert all(slice_date_res3.to_frame(None, None, None) == frame3) # slice dates slice_date_res2 = res2[[dt.date(2020, 1, 14), dt.date(2020, 1, 15)]] assert all(slice_date_res2.to_frame(None, None, None) == frame2) # test aggregate agg_res5 = res5.aggregate().to_frame(None, None, None) def filter_lambda(x): return (x['risk_measure'] == risk.DollarPrice) & (x['dates'] == dt.date(2020, 1, 14)) manual_agg_r5 = frame5.loc[frame5.apply(filter_lambda, axis=1)]['value'].values.sum() filter_agg_res5 = agg_res5.loc[agg_res5.apply(filter_lambda, axis=1)]['value'].values[0] assert filter_agg_res5 == manual_agg_r5 sub_res6 = res6.aggregate().to_frame().loc[dt.date(2020, 1, 14)].values[0] manual_agg_r6 = frame6.loc[frame6['dates'] == dt.date(2020, 1, 14)]['value'].values.sum() assert sub_res6 == manual_agg_r6 def test_bucketed_risks(mocker): with MockCalc(mocker): _, res1, frame1 = get_attributes(eur_port, risk.IRDelta) _, res2, frame2 = get_attributes(port, risk.IRDelta) _, res3, frame3 = get_attributes(gbp_port, risk.IRDelta) _, res4, frame4 = get_attributes(port1, risk.IRDelta, 'Multiple') _, res5, frame5 = get_attributes(bs_port, risk.IRBasis(aggregation_level=AggregationLevel.Asset)) _, res6, frame6 = get_attributes(jpy_port, risk.IRBasis(aggregation_level=AggregationLevel.Asset), 'Multiple') _, res7, frame7 = get_attributes(commod_port, risk.CommodDelta, "Multiple") def check_depth(res, f, with_dates=''): temp_res = list(res)[0].drop('value', axis=1) port_depth = len(max(res.portfolio.all_paths, key=len)) + temp_res.columns.size port_depth = port_depth + 2 if with_dates == 'dated' else port_depth + 1 f_temp = f.drop('value', axis=1) assert port_depth == f_temp.columns.size check_depth(res1, frame1) check_depth(res2, frame2) check_depth(res3, frame3) check_depth(res4, frame4, 'dated') check_depth(res5, frame5) check_depth(res6, frame6, 'dated') check_depth(res7, frame7, 'dated') default_pivot_table_test(res1, 'has_bucketed') default_pivot_table_test(res2, 'has_bucketed') default_pivot_table_test(res3, 'has_bucketed') default_pivot_table_test(res4, 'has_bucketed') default_pivot_table_test(res5, 'has_bucketed') default_pivot_table_test(res6, 'has_bucketed') default_pivot_table_test(res7, 'has_bucketed') # test slicing # slice one portfolio sub_res2 = res2[gbp_port] assert all(sub_res2.to_frame() == res1.to_frame()) # slice one date sub_res4 = res4[dt.date(2020, 1, 14)] assert all(sub_res4[gbp_port].to_frame(None, None, None) == frame3) # slice dates sub_res4b = res4[[dt.date(2020, 1, 14), dt.date(2020, 1, 15)]] assert all(sub_res4b.to_frame(None, None, None) == frame4) # test aggregate agg_r1 = res1.aggregate().to_frame() manual_agg_f1 = frame1.loc[frame1['mkt_point'] == '5Y']['value'].sum() np.testing.assert_almost_equal(agg_r1.loc[agg_r1['mkt_point'] == '5Y']['value'].values[0], manual_agg_f1, 8) def filter_lambda(x): return (x['dates'] == dt.date(2020, 1, 14)) & (x['mkt_asset'] == 'JPY OIS/JPY-3M') agg_r6 = res6.aggregate().to_frame() filter_agg_r6 = agg_r6.loc[agg_r6.apply(filter_lambda, axis=1)]['value'].values[0] manual_agg_f6 = frame6.loc[frame6.apply(filter_lambda, axis=1)]['value'].values.sum() np.testing.assert_almost_equal(filter_agg_r6, manual_agg_f6, 8) assert isinstance(res7[dt.date(2020, 1, 14)].result().futures[0].result(), DataFrameWithInfo) assert res7[dt.date(2020, 1, 14)].to_frame()["mkt_type"].iloc[0] == "CMD NRG" def test_cashflows_risk(mocker): with MockCalc(mocker): _, _, frame1 = get_attributes(eur_port, risk.Cashflows) _, _, frame2 = get_attributes(port1, risk.Cashflows) assert 'payment_date' in frame1.columns.values assert 'payment_date' in frame2.columns.values assert np.unique(frame1.risk_measure.values)[0] == risk.Cashflows assert np.unique(frame2.risk_measure.values)[0] == risk.Cashflows assert 'instrument_name' in frame1.columns.values assert 'instrument_name' in frame2.columns.values assert 'portfolio_name_0' in frame2.columns.values def test_nested_portfolio(mocker): with MockCalc(mocker): cols1, res1, frame1 = get_attributes(port1, (risk.DollarPrice, risk.Price)) cols2, res2, frame2 = get_attributes(port, (risk.DollarPrice, risk.Price)) _, swap1_6_res, frame3 = get_attributes(Portfolio((swap_1, swap_6), name='swap_1_6'), risk.DollarPrice) _, res4, frame4 = get_attributes(port1, (risk.DollarPrice, risk.Price, risk.Theta)) price_values_test(res1, frame1) price_values_test(res2, frame2) dollar_eur_frame1 = frame1[(frame1['portfolio_name_0'] == 'EUR') & (frame1['risk_measure'] == risk.DollarPrice)][ 'value' ].values dollar_eur_frame2 = frame2[ (frame2['portfolio_name_0'] == 'EURGBP') & (frame2['portfolio_name_1'] == 'EUR') & (frame2['risk_measure'] == risk.DollarPrice) ]['value'].values default_pivot_table_test(res1) default_pivot_table_test(res2) # test slicing # slice multiple instruments slice_res2 = res1[[swap_1, swap_6]][risk.DollarPrice].to_frame(None, None, None)['value'].values assert all(slice_res2 == swap1_6_res.to_frame(None, None, None)['value'].values) sub_frame1 = res1[risk.DollarPrice][swap_1].to_frame() assert sub_frame1 == dollar_eur_frame1[0] assert sub_frame1 == dollar_eur_frame2[0] sub_frame2 = res2[eur_port][risk.DollarPrice].to_frame(None, None, None)['value'].values assert all(dollar_eur_frame1 == sub_frame2) assert all(dollar_eur_frame2 == sub_frame2) # slice multiple risk measures sub_res4 = res4[[risk.Price, risk.DollarPrice]] assert all(sub_res4.to_frame() == res1.to_frame()) def test_diff_types_risk_measures(mocker): # when risk results from scalar and bucketed risk measures are be to tabulated together with MockCalc(mocker): _, res1, frame1 = get_attributes(eur_port, (risk.Price, risk.IRDelta)) _, res2, frame2 = get_attributes(mixed_port, (risk.IRBasis, risk.Price)) _, res3, frame3 = get_attributes( mixed_port, (risk.IRBasis(aggregation_level=AggregationLevel.Asset), risk.Price) ) _, res4, frame4 = get_attributes(eur_port, (risk.IRDelta, risk.Price), 'Multiple') _, res5, frame5 = get_attributes(mixed_port, (risk.Price, risk.IRBasis), 'Multiple') _, res6, frame6 = get_attributes( mixed_port, (risk.IRBasis(aggregation_level=AggregationLevel.Asset), risk.Price), 'Multiple' ) price_values_test(res1, frame1) price_values_test(res2, frame2) price_values_test(res3, frame3) price_values_test(res4, frame4, 'dated') price_values_test(res5, frame5, 'dated') price_values_test(res6, frame6, 'dated') default_pivot_table_test(res1, 'has_bucketed') default_pivot_table_test(res2, 'has_bucketed') default_pivot_table_test(res3, 'has_bucketed') default_pivot_table_test(res4, 'has_bucketed') default_pivot_table_test(res5, 'has_bucketed') default_pivot_table_test(res6, 'has_bucketed') # test aggregate sub_res1 = res1.aggregate().to_frame() assert all(sub_res1.loc[risk.IRDelta]['value'].values == res1[risk.IRDelta].aggregate().to_frame()['value'].values) assert sub_res1.loc[risk.Price]['value'] == res1[risk.Price].aggregate().to_frame() def test_empty_calc_request(mocker): # when calc req is sent for rm that inst is insensitive to with MockCalc(mocker): _, r1, f1 = get_attributes(swap_port1, (risk.IRVega, risk.Price)) _, r2, f2 = get_attributes(swap_port2, (risk.IRVega(aggregation_level=AggregationLevel.Asset), risk.Price)) _, r3, f3 = get_attributes( swap_port1, (risk.IRVega(aggregation_level=AggregationLevel.Asset, currency='local'), risk.Price) ) _, r4, f4 = get_attributes(swap_port2, (risk.Price, risk.IRVega(currency='local'))) _, r5, f5 = get_attributes(swap_port2, (risk.Price, risk.IRVega), 'Multiple') _, r6, f6 = get_attributes( swap_port1, (risk.IRVega(aggregation_level=AggregationLevel.Asset), risk.Price), 'Multiple' ) _, r7, f7 = get_attributes( swap_port2, (risk.IRVega(aggregation_level=AggregationLevel.Asset, currency='local'), risk.Price), 'Multiple', ) _, r8, f8 = get_attributes(swap_port1, (risk.IRVega(currency='local'), risk.Price), 'Multiple') price_values_test(r1, f1) price_values_test(r2, f2) price_values_test(r3, f3) price_values_test(r4, f4) price_values_test(r5, f5, 'dated') price_values_test(r6, f6, 'dated') price_values_test(r7, f7, 'dated') price_values_test(r8, f8, 'dated') default_pivot_table_test(r1, 'has_bucketed') default_pivot_table_test(r2, 'has_bucketed') default_pivot_table_test(r3, 'has_bucketed') default_pivot_table_test(r4, 'has_bucketed') default_pivot_table_test(r5, 'has_bucketed') default_pivot_table_test(r6, 'has_bucketed') default_pivot_table_test(r7, 'has_bucketed') default_pivot_table_test(r8, 'has_bucketed') def test_adding_risk_results(mocker): with MockCalc(mocker): result1 = get_attributes(eur_port, risk.Price, no_frame=True) result2 = get_attributes( eur_port, (risk.IRDelta(aggregation_level=AggregationLevel.Asset, currency='local'), risk.Price), no_frame=True, ) result3 = get_attributes( swaption_port, (risk.IRDelta(aggregation_level=AggregationLevel.Asset, currency='local'), risk.Price), no_frame=True, ) result4 = get_attributes(port1, risk.Price, no_frame=True) result5 = get_attributes( swaption_port, risk.IRVega( aggregation_level=AggregationLevel.Asset, ), no_frame=True, ) result6 = get_attributes(jpy_port, (risk.Price,), 'RollFwd', no_frame=True) result7 = get_attributes(jpy_port, risk.Price, 'CurveScen1', no_frame=True) result8 = get_attributes(jpy_port, (risk.DollarPrice, risk.Price), 'CurveScen2', no_frame=True) # (2020, 1, 14) to (2020, 1, 15) result9 = get_attributes(port1, risk.Price, 'Multiple', no_frame=True) # (2020, 1, 16) to (2020, 1, 17) result10 = get_attributes(port1, risk.Price, 'Multiple2', no_frame=True) # (2020, 1, 14) result11 = get_attributes(port1, risk.Price, no_frame=True) # (2020, 1, 16), market_data_location='NYC' result12 = get_attributes(port1, risk.Price, 'PricingCtx2', no_frame=True) # (2020, 1, 16), market_data_location='LDN' result13 = get_attributes(port1, risk.Price, 'PricingCtx3', no_frame=True) # adding results with same portfolio but different risk measures add_1 = result3 + result5 # adding results with different portfolio but same risk measures add_2 = result2 + result3 # adding results with different portfolios and overlapping risk measures add_3 = result1 + result3 # adding results with different portfolios and different risk measures add_4 = result1 + result5 add_5 = result3 + result4 # adding dates add_6 = result9 + result13 add_7 = result11 + result13 add_8 = result10 + result11 add_9 = result9 + result10 default_pivot_table_test(add_1, 'has_bucketed') default_pivot_table_test(add_2, 'has_bucketed') default_pivot_table_test(add_3, 'has_bucketed') default_pivot_table_test(add_4, 'has_bucketed') default_pivot_table_test(add_5, 'has_bucketed') default_pivot_table_test(add_6, 'dated') default_pivot_table_test(add_7, 'dated') default_pivot_table_test(add_8, 'dated') default_pivot_table_test(add_9, 'dated') # throw value error when adding results where at least one particular value is being calculated twice # adding results with same portfolio but overlapping risk measures with pytest.raises(ValueError): _ = result1 + result2 # adding results with overlapping portfolios and different risk measures with pytest.raises(ValueError): _ = result2 + result4 # adding results with overlapping portfolios and same risk measures with pytest.raises(ValueError): _ = result1 + result4 # adding results with different scenarios with pytest.raises(ValueError): _ = result6 + result7 with pytest.raises(ValueError): _ = result7 + result8 # overlapping dates with pytest.raises(ValueError): _ = result9 + result11 with pytest.raises(ValueError): _ = result10 + result13 # adding results with different market locations with pytest.raises(ValueError): _ = result10 + result12 with pytest.raises(ValueError): _ = result9 + result12 with pytest.raises(ValueError): _ = result12 + result13 with pytest.raises(ValueError): _ = result11 + result12 def test_unsupported_error_datums(mocker): with MockCalc(mocker): f1 = eur_port.calc(risk.IRAnnualImpliedVol).to_frame() _, _, f2 = get_attributes(swap_port1, risk.IRAnnualImpliedVol) _, _, f3 = get_attributes(swaption_port1, risk.IRAnnualImpliedVol) _, _, f4 = get_attributes(swaption_port3, risk.IRAnnualImpliedVol) # assert that unsupported datums do not appear to_frame() assert f1 is None assert all(f2['value'] == f3['value']) # assert that errorvalue appears in to_frame() assert isinstance(f4['value'].values[0], ErrorValue) def test_resolution_of_error_trade(mocker): with MockCalc(mocker): error_trade = IRSwap(notional_currency='EUR', termination_date='10y', fixed_rate='bob') resolved_trade = error_trade.calc(ResolvedInstrumentValues) assert isinstance(resolved_trade, ErrorValue) try: _ = resolved_trade.fixed_rate # this should fail assert 1 == 2 except AttributeError as e: assert 'Error was' in str(e) def test_resolve_to_frame(mocker): # makes sure resolving portfolio doesn't break to_frame with MockCalc(mocker): _, r1, f1 = get_attributes(eur_port, risk.Price, resolve=True) _, r2, f2 = get_attributes(port1, risk.Price, resolve=True) _, r3, f3 = get_attributes(jpy_port, risk.Price, 'RollFwd', resolve=True) _, r4, f4 = get_attributes(port1, risk.Price, 'CurveScen1', resolve=True) def test_unnamed_portfolio(mocker): unnamed_1 = Portfolio((swap_1, swap_2)) unnamed_2 = Portfolio((swap_3, swap_4)) unnamed = Portfolio((unnamed_1, unnamed_2)) with MockCalc(mocker): res = unnamed.calc(risk.IRFwdRate) df = res.to_frame() assert len(df) == 2 assert list(df.index) == ['Portfolio_0', 'Portfolio_1'] assert list(df.columns) == ['5y', '10y'] def test_leg_valuations(mocker): with MockCalc(mocker): # children legs return values _, r1, f1 = get_attributes(mcb, risk.FXSpot) assert isinstance(r1.futures[0].result(), DataFrameWithInfo) assert 'path' in f1.columns def test_aggregation_with_heterogeous_types(mocker): with MockCalc(mocker): portfolio1 = Portfolio([IRSwaption('Pay', '10y', 'EUR', expiration_date='3m', name='EUR3m10ypayer')]) portfolio2 = Portfolio([IRSwaption('Pay', '10y', 'EUR', expiration_date='6m', name='EUR6m10ypayer')]) with PricingContext(csa_term='EUR-OIS', visible_to_gs=True): r1 = portfolio1.price() with PricingContext(csa_term='EUR-EuroSTR'): r2 = portfolio2.price() combined_result = r1 + r2 with pytest.raises(ValueError): combined_result.aggregate() assert isinstance(combined_result.aggregate(allow_mismatch_risk_keys=True), float) def test_aggregation_with_empty_measures(mocker): with MockCalc(mocker): swaptions = ( IRSwaption( notional_currency='EUR', termination_date='7y', expiration_date='1y', pay_or_receive='Receive', strike='ATM+35', name='EUR 1y7y', ), IRSwaption( notional_currency='EUR', termination_date='10y', expiration_date='2w', pay_or_receive='Receive', strike='ATM+50', name='EUR 2w10y', ), ) portfolio = Portfolio(swaptions) from_date = dt.date(2021, 11, 18) to_date = dt.date(2021, 11, 19) explain_2d = PnlExplain(CloseMarket(date=to_date)) with PricingContext(pricing_date=from_date, visible_to_gs=True): portfolio.resolve() result_explain = portfolio.calc(explain_2d) total_risk = aggregate_risk(result_explain[explain_2d])['value'].sum() risk_swaption_1 = result_explain[0]['value'].sum() risk_swaption_2 = result_explain[1]['value'].sum() assert total_risk == risk_swaption_1 + risk_swaption_2 def test_filter_risk(mocker): with MockCalc(mocker): result = swap_1.calc(risk.IRDelta) coord = MarketDataCoordinate.from_string('IR_EUR_SWAP_5Y') df = result.filter_by_coord(coord) assert len(result) > 1 assert len(df) == 1 def test_transformation(mocker): with MockCalc(mocker): ladder_res = usd_port.calc(risk.IRDelta) transformed_res = ladder_res.transform(ResultWithInfoAggregator()) np.testing.assert_almost_equal(transformed_res.aggregate(), ladder_res.to_frame()['value'].sum()) def test_aggregation_with_identical_trades(mocker): with MockCalc(mocker): swaptions = ( IRSwaption( notional_currency='EUR', termination_date='7y', expiration_date='1y', pay_or_receive='Receive', strike='ATM+35', name='trade_1', ), IRSwaption( notional_currency='EUR', termination_date='7y', expiration_date='1y', pay_or_receive='Receive', strike='ATM+35', name='trade_2', ), ) portfolio = Portfolio(swaptions) delta = portfolio.calc(risk.IRDelta) transformed_res = delta.transform(ResultWithInfoAggregator()) np.testing.assert_almost_equal(transformed_res.aggregate(), delta.to_frame()['value'].sum()) def test_scalar_with_info_on_instrument(): # Historically there was a problem with setting risk results that were a scalar with info on an instrument # This was because of how copy.deepcopy would try and pickle/unpickle the class. This test checks that we can set # properties and still to_dict and _to_json the class risk_key = RiskKey("provider", "the_date", "mkt", RiskRequestParameters(), None, None) fwi = FloatWithInfo( risk_key, 1.56, ) swi = StringWithInfo(risk_key, 'USD') swap = IRSwap(floating_rate_option=swi, fixed_rate=fwi) swap_dict = swap.to_dict() assert swap_dict["floatingRateOption"] == "USD" assert swap_dict["fixedRate"] == 1.56 assert swap.to_json() is not None def test_display_unit(): unit = {'A': 1, 'B': -1} risk_key = RiskKey("provider", "the_date", "mkt", RiskRequestParameters(), None, None) value = 1.0 float_with_unit = FloatWithInfo(risk_key, value, unit) assert float_with_unit.__repr__() == '1.0 (A/B)' unit = {'A': 1, 'B': -2} float_with_unit = FloatWithInfo(risk_key, value, unit) assert float_with_unit.__repr__() == '1.0 (A/B^2)' unit = {'A': 2} float_with_unit = FloatWithInfo(risk_key, value, unit) assert float_with_unit.__repr__() == '1.0 (A^2)' assert str(float_with_unit) == '1.0' ================================================ FILE: gs_quant/test/test_base.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from dataclasses import field, dataclass from enum import Enum from typing import Union, Tuple, Optional import gs_quant.base as base from gs_quant.base import handle_camel_case_args, Base, EnumBase from gs_quant.instrument.core import Security class TestEnum(EnumBase, Enum): Enum_1 = 'Enum_1' Enum_2 = 'Enum_2' @handle_camel_case_args @dataclass class BaseSubclass(Base): instance_attr: str = field(default=None) attr_1: Optional[str] = field(default=None) attr_2: Tuple[Union[float, str], ...] = field(default=()) attr_3: Union[Tuple[Optional[str], ...], str] = field(default=None) attr_4: Tuple[int] = field(default=None) attr_5: Tuple[int, str] = field(default=None) attr_6: TestEnum = field(default=None) def test_handle_camel_case_args(): # Handling camelcase args on init obj = BaseSubclass(instanceAttr="test") assert obj.instance_attr == "test" def test_base_getter(): obj = BaseSubclass(instance_attr="test") # Handling camelcase getter assert obj.instanceAttr == "test" def test_base_setter(): obj = BaseSubclass() # Handling camelcase setter obj.instanceAttr = "test" assert obj.instance_attr == "test" def test_setter_coercion(): base._is_supported_generic_cache = {} obj = BaseSubclass( instance_attr='test', attr_1=None, attr_2=('test', 1.0, 1), attr_3='test', attr_4=(1,), attr_5=(3, 'test'), attr_6=TestEnum.Enum_1, ) obj.attr_1 = 'test' # str with type hint str is unchanged obj.attr_1 = 'Enum_1' assert isinstance(obj.attr_1, str) # Enum with type hint str is unchanged obj.attr_1 = TestEnum.Enum_1 assert isinstance(obj.attr_1, TestEnum) obj.attr_2 = (1, 2, 3) obj.attr_3 = (None, 'test', None) obj.attr_4 = (1, 1, 1) obj.attr_5 = (0, 'test') # Enum with type hint Enum is unchanged obj.attr_6 = TestEnum.Enum_2 assert isinstance(obj.attr_6, TestEnum) # all handled as type matches, we do not get to the generic coercion/default case assert not base._is_supported_generic_cache # str with type hint Enum gets cast to Enum obj.attr_6 = 'Enum_1' assert isinstance(obj.attr_6, TestEnum) assert TestEnum in base._is_supported_generic_cache def test_security_from_dict(): # Input dictionary input_dict = { '$type': 'LegDefinition', 'bbid': 'TYU5 Comdty', 'calculationTime': 13379, 'properties': {'buysell': 'Buy', 'identifier': 'TYU5 Comdty', 'identifiertype': 'BloombergID', 'quantity': 1.0}, 'queueingTime': 157, } # Construct Security object security = Security.from_dict(input_dict) # Assertions assert security.bbid == 'TYU5 Comdty' ================================================ FILE: gs_quant/test/test_session.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import pickle from gs_quant.session import GsSession, Environment def test_session_pickle(): session = GsSession.get(Environment.PROD, 'fake_client_id', 'fake_secret') pk = pickle.dumps(session) unpk = pickle.loads(pk) assert unpk is not None ================================================ FILE: gs_quant/test/timeseries/__init__.py ================================================ ================================================ FILE: gs_quant/test/timeseries/multi_measure/__init__.py ================================================ ================================================ FILE: gs_quant/test/timeseries/multi_measure/test_commod.py ================================================ """ Copyright 2021 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import pandas as pd import pytest from gs_quant.timeseries import USE_DISPLAY_NAME from pandas.testing import assert_series_equal from testfixtures import Replacer from gs_quant.api.gs.data import MarketDataResponseFrame from gs_quant.data import DataContext from gs_quant.errors import MqError from gs_quant.markets.securities import CommodityNaturalGasHub, Cross from gs_quant import timeseries as tm _test_datasets = ('TEST_DATASET',) @pytest.mark.skipif(not USE_DISPLAY_NAME, reason="requires certain evnvar to run") def test_forward_price(): # Tests for US NG assets def mock_natgas_forward_price(_cls, _q, ignore_errors=False): d = { 'forwardPrice': [ 2.880, 2.844, 2.726, ], 'contract': [ "F21", "G21", "H21", ], } df = MarketDataResponseFrame(data=d, index=pd.to_datetime([dt.date(2019, 1, 2)] * 3)) df.dataset_ids = _test_datasets return df replace = Replacer() replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_natgas_forward_price) mock = CommodityNaturalGasHub('MA001', 'AGT') with DataContext(dt.date(2019, 1, 2), dt.date(2019, 1, 2)): actual = pd.Series(tm.forward_price(mock, price_method='GDD', contract_range='F21')) expected = pd.Series([2.880], index=[dt.date(2019, 1, 2)], name='price') assert_series_equal(expected, actual) with pytest.raises(MqError): tm.forward_price(Cross('MA002', 'USD/EUR'), price_method='GDD', contract_range='F21') replace.restore() if __name__ == "__main__": pytest.main(args=["test_commod.py"]) ================================================ FILE: gs_quant/test/timeseries/multi_measure/test_measure_registry.py ================================================ """ Copyright 2021 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import inspect from typing import Callable import pytest from gs_quant.timeseries.helper import USE_DISPLAY_NAME from gs_quant.timeseries.measure_registry import MultiMeasure @pytest.mark.skipif(not USE_DISPLAY_NAME, reason="requires certain evnvar to run") def test_registry(): from gs_quant.timeseries.measure_registry import registry assert len(registry) > 0 for name, mm in registry.items(): assert name == mm.display_name, 'registry key {} does not match with MultiMeasure display_name {}'.format( name, mm.display_name ) for cls, fns in mm.measure_map.items(): types = [t for fn in fns if fn.asset_type is not None for t in fn.asset_type] assert len(set(types)) == len(types), ( 'duplicate measures are defined for the same asset type in class ' + cls.value ) fn_types_excluded = [fn.asset_type_excluded for fn in fns if fn.asset_type_excluded is not None] assert len(fn_types_excluded) <= 1, ( 'more than one measure with asset_type_excluded is defined for asset class ' + cls.value ) types_excluded = [t for types_excluded in fn_types_excluded for t in types_excluded] assert len(types_excluded) == 0 or set(types).issubset(set(types_excluded)), ( 'the asset type scope overlaps in class ' + cls.value ) def test_no_duplicate_plot_measure_function_names(): # The 'plot_measure' decorator re-defines functions as 'MultiMeasures' import gs_quant.timeseries as timeseries members: list[tuple[str, MultiMeasure]] = inspect.getmembers(timeseries, lambda o: isinstance(o, MultiMeasure)) fns: set[Callable] = { fn for (_, multi_measure) in members for fns in multi_measure.measure_map.values() for fn in fns } fn_name_count: dict[str, int] = {} for fn in fns: fn_name_count[fn.__name__] = fn_name_count.get(fn.__name__, 0) + 1 dups: dict[str, int] = {fn_name: count for (fn_name, count) in fn_name_count.items() if count > 1} assert len(dups) == 0, ( f"The decorated functions' names should be unique! The following function names appeared more than once: {dups}." ) if __name__ == "__main__": pytest.main(args=["test_measure_registry.py"]) ================================================ FILE: gs_quant/test/timeseries/test_algebra.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import math import pytest import pandas as pd import numpy as np import datetime as dt from pandas.testing import assert_series_equal from gs_quant.errors import MqValueError, MqError from gs_quant.timeseries import ( algebra, Interpolate, filter_, FilterOperator, smooth_spikes, ThresholdType, repeat, and_, or_, not_, if_, weighted_sum, geometrically_aggregate, ) def test_add(): dates1 = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), ] dates2 = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), ] x = pd.Series([1.0, 1.0, 1.0, 1.0], index=dates1) y = pd.Series([1.0, 1.0, 1.0], index=dates2) result = algebra.add(x, y, Interpolate.INTERSECT) expected = pd.Series([2.0, 2.0, 2.0], index=dates2) assert_series_equal(result, expected, obj="Add intersect left") result = algebra.add(y, x, Interpolate.INTERSECT) expected = pd.Series([2.0, 2.0, 2.0], index=dates2) assert_series_equal(result, expected, obj="Add intersect right") result = algebra.add(x, y, Interpolate.NAN) expected = pd.Series([2.0, 2.0, 2.0, np.nan], index=dates1) assert_series_equal(result, expected, obj="Add NaN left") result = algebra.add(y, x, Interpolate.NAN) expected = pd.Series([2.0, 2.0, 2.0, np.nan], index=dates1) assert_series_equal(result, expected, obj="Add NaN right") result = algebra.add(x, y, Interpolate.ZERO) expected = pd.Series([2.0, 2.0, 2.0, 1.0], index=dates1) assert_series_equal(result, expected, obj="Add zero left") result = algebra.add(x, y, Interpolate.STEP) expected = pd.Series([2.0, 2.0, 2.0, 2.0], index=dates1) assert_series_equal(result, expected, obj="Add step right") result = algebra.add(x, 1) expected = pd.Series([2.0, 2.0, 2.0, 2.0], index=dates1) assert_series_equal(result, expected, obj="Add scalar left") result = algebra.add(1, x) expected = pd.Series([2.0, 2.0, 2.0, 2.0], index=dates1) assert_series_equal(result, expected, obj="Add scalar right") assert algebra.add(1, 2) == 3 def test_subtract(): dates1 = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), ] dates2 = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), ] x = pd.Series([1.0, 1.0, 1.0, 1.0], index=dates1) y = pd.Series([1.0, 1.0, 1.0], index=dates2) result = algebra.subtract(x, y, Interpolate.INTERSECT) expected = pd.Series([0.0, 0.0, 0.0], index=dates2) assert_series_equal(result, expected, obj="Subtract intersect left") result = algebra.subtract(y, x, Interpolate.INTERSECT) expected = pd.Series([0.0, 0.0, 0.0], index=dates2) assert_series_equal(result, expected, obj="Subtract intersect right") result = algebra.subtract(x, y, Interpolate.NAN) expected = pd.Series([0.0, 0.0, 0.0, np.nan], index=dates1) assert_series_equal(result, expected, obj="Subtract NaN left") result = algebra.subtract(y, x, Interpolate.NAN) expected = pd.Series([0.0, 0.0, 0.0, np.nan], index=dates1) assert_series_equal(result, expected, obj="Subtract NaN right") result = algebra.subtract(x, y, Interpolate.ZERO) expected = pd.Series([0.0, 0.0, 0.0, 1.0], index=dates1) assert_series_equal(result, expected, obj="Subtract zero left") result = algebra.subtract(x, y, Interpolate.STEP) expected = pd.Series([0.0, 0.0, 0.0, 0.0], index=dates1) assert_series_equal(result, expected, obj="Subtract step right") result = algebra.subtract(x, 1) expected = pd.Series([0.0, 0.0, 0.0, 0.0], index=dates1) assert_series_equal(result, expected, obj="Subtract scalar left") result = algebra.subtract(1, x) expected = pd.Series([0.0, 0.0, 0.0, 0.0], index=dates1) assert_series_equal(result, expected, obj="Subtract scalar right") assert algebra.subtract(1, 2) == -1 def test_multiply(): dates1 = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), ] dates2 = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), ] x = pd.Series([1.0, 2.0, 3.0, 4.0], index=dates1) y = pd.Series([2.0, 1.5, 2.0], index=dates2) result = algebra.multiply(x, y, Interpolate.INTERSECT) expected = pd.Series([2.0, 3.0, 6.0], index=dates2) assert_series_equal(result, expected, obj="Multiply intersect left") result = algebra.multiply(y, x, Interpolate.INTERSECT) expected = pd.Series([2.0, 3.0, 6.0], index=dates2) assert_series_equal(result, expected, obj="Multiply intersect right") result = algebra.multiply(x, y, Interpolate.NAN) expected = pd.Series([2.0, 3.0, 6.0, np.nan], index=dates1) assert_series_equal(result, expected, obj="Multiply NaN left") result = algebra.multiply(y, x, Interpolate.NAN) expected = pd.Series([2.0, 3.0, 6.0, np.nan], index=dates1) assert_series_equal(result, expected, obj="Multiply NaN right") result = algebra.multiply(x, y, Interpolate.ZERO) expected = pd.Series([2.0, 3.0, 6.0, 0.0], index=dates1) assert_series_equal(result, expected, obj="Multiply zero left") result = algebra.multiply(x, y, Interpolate.STEP) expected = pd.Series([2.0, 3.0, 6.0, 8.0], index=dates1) assert_series_equal(result, expected, obj="Multiply step left") result = algebra.multiply(x, 2.0) expected = pd.Series([2.0, 4.0, 6.0, 8.0], index=dates1) assert_series_equal(result, expected, obj="Multiply scalar left") result = algebra.multiply(2.0, x) expected = pd.Series([2.0, 4.0, 6.0, 8.0], index=dates1) assert_series_equal(result, expected, obj="Multiply scalar right") assert algebra.multiply(1, 2) == 2 def test_divide(): dates1 = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), ] dates2 = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), ] x = pd.Series([1.0, 2.0, 3.0, 4.0], index=dates1) y = pd.Series([2.0, 1.0, 2.0], index=dates2) result = algebra.divide(x, y, Interpolate.INTERSECT) expected = pd.Series([0.5, 2.0, 1.5], index=dates2) assert_series_equal(result, expected, obj="Divide intersect left") result = algebra.divide(y, x, Interpolate.INTERSECT) expected = pd.Series([2.0, 0.5, 2 / 3], index=dates2) assert_series_equal(result, expected, obj="Divide intersect right") result = algebra.divide(x, y, Interpolate.NAN) expected = pd.Series([0.5, 2.0, 1.5, np.nan], index=dates1) assert_series_equal(result, expected, obj="Divide NaN left") result = algebra.divide(y, x, Interpolate.NAN) expected = pd.Series([2.0, 0.5, 2 / 3, np.nan], index=dates1) assert_series_equal(result, expected, obj="Divide NaN right") result = algebra.divide(x, y, Interpolate.ZERO) expected = pd.Series([0.5, 2.0, 1.5, np.inf], index=dates1) assert_series_equal(result, expected, obj="Divide zero left") result = algebra.divide(x, y, Interpolate.STEP) expected = pd.Series([0.5, 2.0, 1.5, 2.0], index=dates1) assert_series_equal(result, expected, obj="Divide step left") result = algebra.divide(x, 2) expected = pd.Series([0.5, 1.0, 1.5, 2.0], index=dates1) assert_series_equal(result, expected, obj="Divide scalar left") result = algebra.divide(2, x) expected = pd.Series([2.0, 1.0, 2 / 3, 0.5], index=dates1) assert_series_equal(result, expected, obj="Divide scalar right") assert algebra.divide(1, 2) == 0.5 with pytest.raises(ZeroDivisionError): algebra.divide(1, 0) def test_floordiv(): dates1 = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), ] dates2 = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), ] x = pd.Series([1.0, 2.0, 3.0, 4.0], index=dates1) y = pd.Series([2.0, 1.0, 2.0], index=dates2) result = algebra.floordiv(x, y, Interpolate.INTERSECT) expected = pd.Series([0.0, 2.0, 1.0], index=dates2) assert_series_equal(result, expected, obj="Floor divide intersect left") result = algebra.floordiv(y, x, Interpolate.INTERSECT) expected = pd.Series([2.0, 0.0, 0.0], index=dates2) assert_series_equal(result, expected, obj="Floor divide intersect right") result = algebra.floordiv(x, y, Interpolate.NAN) expected = pd.Series([0.0, 2.0, 1.0, np.nan], index=dates1) assert_series_equal(result, expected, obj="Floor divide NaN left") result = algebra.floordiv(y, x, Interpolate.NAN) expected = pd.Series([2.0, 0.0, 0.0, np.nan], index=dates1) assert_series_equal(result, expected, obj="Floor divide NaN right") result = algebra.floordiv(x, y, Interpolate.ZERO) expected = pd.Series([0.0, 2.0, 1.0, np.floor_divide(1.0, 0.0)], index=dates1) assert_series_equal(result, expected, obj="Floor divide zero left") result = algebra.floordiv(x, y, Interpolate.STEP) expected = pd.Series([0.0, 2.0, 1.0, 2.0], index=dates1) assert_series_equal(result, expected, obj="Floor divide step left") result = algebra.floordiv(x, 2, Interpolate.STEP) expected = pd.Series([0.0, 1.0, 1.0, 2.0], index=dates1) assert_series_equal(result, expected, obj="Floor divide scalar left") result = algebra.floordiv(2, x, Interpolate.STEP) expected = pd.Series([2.0, 1.0, 0.0, 0.0], index=dates1) assert_series_equal(result, expected, obj="Floor divide scalar right") assert algebra.floordiv(3, 2) == 1.0 with pytest.raises(ZeroDivisionError): algebra.floordiv(1, 0) def test_exp(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), ] x = pd.Series([1.0, 2.0, 3.0], index=dates) result = algebra.exp(x) expected = pd.Series([np.exp(1), np.exp(2), np.exp(3)], index=dates) assert_series_equal(result, expected, obj="Exp") def test_log(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), ] x = pd.Series([1.0, 2.0, 3.0], index=dates) result = algebra.log(x) expected = pd.Series([np.log(1), np.log(2), np.log(3)], index=dates) assert_series_equal(result, expected, obj="Log") def test_power(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), ] x = pd.Series([1.0, 2.0, 3.0], index=dates) result = algebra.power(x, 2) expected = pd.Series([1.0, 4.0, 9.0], index=dates) assert_series_equal(result, expected, obj="Pow") def test_sqrt(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), ] x = pd.Series([1.0, 4.0, 9.0], index=dates) result = algebra.sqrt(x) expected = pd.Series([1.0, 2.0, 3.0], index=dates) assert_series_equal(result, expected, obj="Sqrt") actual = algebra.sqrt(9) assert isinstance(actual, int) assert 3 == actual assert math.sqrt(10) == algebra.sqrt(10) def test_abs(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), ] x = pd.Series([-1.0, 2.0, -3.0], index=dates) result = algebra.abs_(x) expected = pd.Series([1.0, 2.0, 3.0], index=dates) assert_series_equal(result, expected, obj="Abs") def test_floor(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), ] x = pd.Series([1.0, 2.0, 3.0], index=dates) result = algebra.floor(x, 2.0) expected = pd.Series([2.0, 2.0, 3.0], index=dates) assert_series_equal(result, expected, obj="Floor") def test_ceil(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), ] x = pd.Series([1.0, 2.0, 3.0], index=dates) result = algebra.ceil(x, 2.0) expected = pd.Series([1.0, 2.0, 2.0], index=dates) assert_series_equal(result, expected, obj="Ceil") def test_filter(): dates1 = [dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4)] all_pos = pd.Series([1.0, 1.0, 1.0, 1.0], index=dates1) with_null = pd.Series([1.0, np.nan, 1.0, 1.0], index=dates1) zero_neg_pos = pd.Series([-1.0, 0.0, 10.0, 1.0], index=dates1) result = filter_(all_pos) expected = all_pos assert_series_equal(result, expected, obj="zap: remove nulls when no nulls are in TS") result = filter_(all_pos, FilterOperator.EQUALS, 0) expected = all_pos assert_series_equal(result, expected, obj="zap: remove 0s when no 0s are in TS") result = filter_(with_null) expected = pd.Series([1.0, 1.0, 1.0], index=[dt.date(2019, 1, 1), dt.date(2019, 1, 3), dt.date(2019, 1, 4)]) assert_series_equal(result, expected, obj="zap: remove nulls in TS") result = filter_(zero_neg_pos, FilterOperator.EQUALS, 0) expected = pd.Series([-1.0, 10.0, 1.0], index=[dt.date(2019, 1, 1), dt.date(2019, 1, 3), dt.date(2019, 1, 4)]) assert_series_equal(result, expected, obj="zap: remove 0s in TS") result = filter_(zero_neg_pos, FilterOperator.GREATER, 0) expected = pd.Series([-1.0, 0.0], index=[dt.date(2019, 1, 1), dt.date(2019, 1, 2)]) assert_series_equal(result, expected, obj="zap: remove positive values in TS") result = filter_(zero_neg_pos, FilterOperator.LESS, 0) expected = pd.Series([0.0, 10.0, 1.0], index=[dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4)]) assert_series_equal(result, expected, obj="zap: remove negative values in TS") result = filter_(zero_neg_pos, FilterOperator.L_EQUALS, 0) expected = pd.Series([10.0, 1.0], index=[dt.date(2019, 1, 3), dt.date(2019, 1, 4)]) assert_series_equal(result, expected, obj="zap: remove values less than or eq to 0 in TS") result = filter_(zero_neg_pos, FilterOperator.G_EQUALS, 0) expected = pd.Series([-1.0], index=[dt.date(2019, 1, 1)]) assert_series_equal(result, expected, obj="zap: remove values greater than or eq to 0 in TS") result = filter_(zero_neg_pos, FilterOperator.N_EQUALS, 0) expected = pd.Series([0.0], index=[dt.date(2019, 1, 2)]) assert_series_equal(result, expected, obj="zap: remove all values but 0 in TS") with pytest.raises(MqValueError): filter_(zero_neg_pos, 0, 0) with pytest.raises(MqValueError): filter_(zero_neg_pos, 0) def test_filter_dates(): dates = [dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4)] all_pos = pd.Series([1.0, 1.0, 1.0, 1.0], index=dates) with_null = pd.Series([1.0, np.nan, 1.0, 1.0], index=dates) result = algebra.filter_dates(all_pos) expected = all_pos assert_series_equal(result, expected, obj="zap: remove nulls when no nulls are in TS") result = algebra.filter_dates(with_null) expected = pd.Series([1.0, 1.0, 1.0], index=[dt.date(2019, 1, 1), dt.date(2019, 1, 3), dt.date(2019, 1, 4)]) assert_series_equal(result, expected, obj="zap: remove nulls in TS") result = algebra.filter_dates(all_pos, FilterOperator.EQUALS, dt.date(2019, 1, 2)) expected = pd.Series([1.0, 1.0, 1.0], index=[dt.date(2019, 1, 1), dt.date(2019, 1, 3), dt.date(2019, 1, 4)]) assert_series_equal(result, expected, obj="zap: remove date in TS") result = algebra.filter_dates(all_pos, FilterOperator.EQUALS, [dt.date(2019, 1, 2), dt.date(2019, 1, 4)]) expected = pd.Series([1.0, 1.0], index=[dt.date(2019, 1, 1), dt.date(2019, 1, 3)]) assert_series_equal(result, expected, obj="zap: remove dates in TS") result = algebra.filter_dates(all_pos, FilterOperator.GREATER, dt.date(2019, 1, 2)) expected = pd.Series([1.0, 1.0], index=[dt.date(2019, 1, 1), dt.date(2019, 1, 2)]) assert_series_equal(result, expected, obj="zap: remove dates after certain date in TS") result = algebra.filter_dates(all_pos, FilterOperator.LESS, dt.date(2019, 1, 3)) expected = pd.Series([1.0, 1.0], index=[dt.date(2019, 1, 3), dt.date(2019, 1, 4)]) assert_series_equal(result, expected, obj="zap: remove dates before certain date in TS") result = algebra.filter_dates(all_pos, FilterOperator.L_EQUALS, dt.date(2019, 1, 2)) expected = pd.Series([1.0, 1.0], index=[dt.date(2019, 1, 3), dt.date(2019, 1, 4)]) assert_series_equal(result, expected, obj="zap: remove dates on or before certain date in TS") result = algebra.filter_dates(all_pos, FilterOperator.G_EQUALS, dt.date(2019, 1, 3)) expected = pd.Series([1.0, 1.0], index=[dt.date(2019, 1, 1), dt.date(2019, 1, 2)]) assert_series_equal(result, expected, obj="zap: remove dates on or after certain date in TS") result = algebra.filter_dates(all_pos, FilterOperator.N_EQUALS, dt.date(2019, 1, 2)) expected = pd.Series([1.0], index=[dt.date(2019, 1, 2)]) assert_series_equal(result, expected, obj="zap: remove all dates other than certain date in TS") result = algebra.filter_dates(all_pos, FilterOperator.N_EQUALS, [dt.date(2019, 1, 2), dt.date(2019, 1, 4)]) expected = pd.Series([1.0, 1.0], index=[dt.date(2019, 1, 2), dt.date(2019, 1, 4)]) assert_series_equal(result, expected, obj="zap: remove all dates other than certain dates in TS") with pytest.raises(MqValueError): algebra.filter_dates(all_pos, 0, 0) with pytest.raises(MqValueError): algebra.filter_dates(all_pos, 0) with pytest.raises(MqValueError): algebra.filter_dates(all_pos, FilterOperator.GREATER, [dt.date(2019, 1, 2), dt.date(2019, 1, 4)]) def test_smooth_spikes(): s = pd.Series([1, 3]) actual = smooth_spikes(s, 0.5) assert actual.empty sparse_index = pd.to_datetime(['2020-01-01', '2020-01-02', '2020-01-04', '2020-01-07']) s = pd.Series([8, 10.0, 8, 6.4], index=sparse_index) actual = smooth_spikes(s, 0.25) expected = pd.Series([10.0, 8], index=sparse_index[1:3]) assert_series_equal(actual, expected) s = pd.Series([8, 10.1, 8, 6.4], index=sparse_index) actual = smooth_spikes(s, 0.25) expected = pd.Series([8.0, 8], index=sparse_index[1:3]) assert_series_equal(actual, expected) s = pd.Series([0.1, 1.5, 0.2, 2], index=sparse_index) actual = smooth_spikes(s, threshold=1, threshold_type=ThresholdType.absolute) expected = pd.Series([0.15, 1.75], index=sparse_index[1:3]) assert_series_equal(actual, expected) s = pd.Series([10.1, 8, 12, 10], index=sparse_index) actual = smooth_spikes(s, 1, ThresholdType.absolute) expected = pd.Series([11.05, 9], index=sparse_index[1:3]) assert_series_equal(actual, expected) s = pd.Series([1, 2, 3, 4], index=sparse_index) actual = smooth_spikes(s, 0.25, ThresholdType.absolute) expected = pd.Series([2.0, 3.0], index=sparse_index[1:3]) assert_series_equal(actual, expected) s = pd.Series([1, 3, 2, 4], index=sparse_index) actual = smooth_spikes(s, 0.5, ThresholdType.absolute) expected = pd.Series([1.5, 3.5], index=sparse_index[1:3]) assert_series_equal(actual, expected) def test_repeat(): with pytest.raises(MqError): repeat(pd.Series, 0) with pytest.raises(MqError): repeat(pd.Series, 367) sparse_index = pd.to_datetime(['2020-01-01', '2020-01-02', '2020-01-04', '2020-01-07']) s = pd.Series([1, 2, 3, 4], index=sparse_index) actual = repeat(s) expected = pd.Series([1, 2, 2, 3, 3, 3, 4], index=pd.date_range(start='2020-01-01', end='2020-01-07', freq='D')) assert_series_equal(actual, expected) actual = repeat(s, 2) expected = pd.Series([1, 2, 3, 4], index=pd.date_range(start='2020-01-01', end='2020-01-07', freq='2D')) assert_series_equal(actual, expected) def test_and(): with pytest.raises(MqError): and_() with pytest.raises(MqError): and_(pd.Series(dtype=float)) with pytest.raises(MqError): and_(pd.Series(dtype=float), 1) with pytest.raises(MqError): and_(pd.Series([2]), pd.Series(dtype=float)) assert and_(pd.Series(dtype=float), pd.Series(dtype=float)).shape[0] == 0 a = pd.Series([0, 0, 0, 0, 1, 1, 1, 1]) b = pd.Series([0, 0, 1, 1, 0, 0, 1, 1]) c = pd.Series([0, 1, 0, 1, 0, 1, 0, 1]) assert_series_equal(and_(a, b), pd.Series([0] * 6 + [1] * 2), check_dtype=False) assert_series_equal(and_(a, b, c), pd.Series([0] * 7 + [1]), check_dtype=False) assert_series_equal(and_(pd.Series([0, 1]), pd.Series(dtype=float)), pd.Series([0] * 2), check_dtype=False) def test_or(): with pytest.raises(MqError): or_() with pytest.raises(MqError): or_(pd.Series(dtype=float)) with pytest.raises(MqError): or_(pd.Series(dtype=float), 1) with pytest.raises(MqError): or_(pd.Series([2]), pd.Series(dtype=float)) assert or_(pd.Series(dtype=float), pd.Series(dtype=float)).shape[0] == 0 a = pd.Series([0, 0, 0, 0, 1, 1, 1, 1]) b = pd.Series([0, 0, 1, 1, 0, 0, 1, 1]) c = pd.Series([0, 1, 0, 1, 0, 1, 0, 1]) assert_series_equal(or_(a, b), pd.Series([0] * 2 + [1] * 6), check_dtype=False) assert_series_equal(or_(a, b, c), pd.Series([0] + [1] * 7), check_dtype=False) assert_series_equal(or_(pd.Series([0, 1]), pd.Series(dtype=float)), pd.Series([0, 1]), check_dtype=False) def test_not(): with pytest.raises(MqError): not_(pd.Series([2])) assert not_(pd.Series(dtype=float)).shape[0] == 0 assert_series_equal(not_(pd.Series([0, 1])), pd.Series([1, 0]), check_dtype=False) def test_if(): with pytest.raises(MqError): if_(pd.Series([-1, 0]), 5, 6) with pytest.raises(MqError): if_(pd.Series([1, 0]), 5, '6') flags = pd.Series([0, 1]) truths = pd.Series([2, 2]) assert_series_equal(if_(flags, 2, 3), pd.Series([3, 2])) assert_series_equal(if_(flags, truths, pd.Series([3, 3])), pd.Series([3, 2])) assert_series_equal(if_(flags, truths, pd.Series([3], index=[100])), pd.Series([np.nan, 2]), check_dtype=False) def test_weighted_average(): empty = pd.Series(dtype=float) with pytest.raises(MqError): weighted_sum([empty, 3], [0.4, 0.6]) with pytest.raises(MqError): weighted_sum([empty, empty], [0.4, '.6']) with pytest.raises(MqError): weighted_sum([empty, empty], [0.4]) a = pd.Series([1, 2, 3, 4], index=pd.date_range('2020-01-01', periods=4, freq='D')) b = pd.Series([24, 27, 30], index=(pd.date_range('2020-01-01', periods=3, freq='D'))) actual = weighted_sum([a, b], [0.3, 0.6]) expected = pd.Series([16.333333, 18.666666, 21], index=pd.date_range('2020-01-01', periods=3)) expected.index.freq = None assert_series_equal(actual, expected) def test_geometrically_aggregate(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 5), ] x = pd.Series([None, 0.05, 0.04, -0.03, 0.12], index=dates) result = geometrically_aggregate(x) expected = pd.Series([None, 0.05, 0.09200000000000008, 0.05923999999999996, 0.18634879999999998], index=dates) assert_series_equal(result, expected) if __name__ == '__main__': pytest.main(args=["test_algebra.py"]) ================================================ FILE: gs_quant/test/timeseries/test_analysis.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import numpy as np import pandas as pd import pytest from gs_quant.errors import MqValueError from gs_quant.timeseries import first, last, last_value, count, Interpolate, compare, diff, lag, LagMode, repeat from gs_quant.timeseries.helper import FREQ_SECOND from pandas.testing import assert_series_equal def _normalize_index(idx): return idx.as_unit('ns') if hasattr(idx, 'as_unit') else idx def test_first(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), ] x = pd.Series([1.0, 2.0, 3.0, 4.0], index=dates) result = first(x) expected = pd.Series([1.0, 1.0, 1.0, 1.0], index=dates) assert_series_equal(result, expected, obj="First") def test_last(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), ] x = pd.Series([1.0, 2.0, 3.0, 4.0], index=dates) result = last(x) expected = pd.Series([4.0, 4.0, 4.0, 4.0], index=dates) assert_series_equal(result, expected, obj="First") y = pd.Series([1.0, 2.0, 3.0, np.nan], index=dates) result = last(y) expected = pd.Series([3.0, 3.0, 3.0, 3.0], index=dates) assert_series_equal(result, expected, obj="Last non-NA") def test_last_value(): with pytest.raises(MqValueError): last_value(pd.Series(dtype=float)) x = pd.Series([1.0, 2.0, 3.0, 4.0], index=(pd.date_range("2020-01-01", periods=4, freq="D"))) assert last_value(x) == 4.0 y = pd.Series([5]) assert last_value(y) == 5 y = pd.Series([1.0, 2.0, 3.0, np.nan], index=(pd.date_range("2020-01-01", periods=4, freq="D"))) assert last_value(y) == 3.0 def test_count(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), ] x = pd.Series([1.0, 2.0, 3.0, 4.0], index=dates) result = count(x) expected = pd.Series([1.0, 2.0, 3.0, 4.0], index=dates) assert_series_equal(result, expected, obj="Count") def test_compare(): dates1 = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), ] dates2 = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), ] x = pd.Series([1.0, 2.0, 2.0, 4.0], index=dates1) y = pd.Series([2.0, 1.0, 2.0], index=dates2) expected = pd.Series([-1.0, 1.0, 0.0], index=dates2) result = compare(x, y, method=Interpolate.INTERSECT) assert_series_equal(expected, result, obj="Compare series intersect") expected = pd.Series([1.0, -1.0, 0], index=dates2) result = compare(y, x, method=Interpolate.INTERSECT) assert_series_equal(expected, result, obj="Compare series intersect 2") expected = pd.Series([-1.0, 1.0, 0, 0], index=dates1) result = compare(x, y, method=Interpolate.NAN) assert_series_equal(expected, result, obj="Compare series nan") expected = pd.Series([-1.0, 1.0, 0, 1.0], index=dates1) result = compare(x, y, method=Interpolate.ZERO) assert_series_equal(expected, result, obj="Compare series zero") expected = pd.Series([-1.0, 1.0, 0, 1.0], index=dates1) result = compare(x, y, method=Interpolate.STEP) assert_series_equal(expected, result, obj="Compare series step") dates2 = [ dt.date(2019, 1, 2), dt.date(2019, 1, 4), dt.date(2019, 1, 6), ] dates1.append(dt.date(2019, 1, 5)) xp = pd.Series([1, 2, 3, 4, 5], index=pd.to_datetime(dates1)) yp = pd.Series([1, 4, 0], index=pd.to_datetime(dates2)) result = compare(xp, yp, Interpolate.TIME) dates1.append(dt.date(2019, 1, 6)) expected = pd.Series([0.0, 1.0, 1.0, 0.0, 1.0, 0.0], index=pd.to_datetime(dates1)) assert_series_equal(result, expected, obj="Compare series greater time") def test_diff(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), ] x = pd.Series([1.0, 2.0, 3.0, 4.0], index=dates) result = diff(x) expected = pd.Series([np.nan, 1.0, 1.0, 1.0], index=dates) assert_series_equal(result, expected, obj="Diff") result = diff(x, 2) expected = pd.Series([np.nan, np.nan, 2.0, 2.0], index=dates) assert_series_equal(result, expected, obj="Diff") empty = pd.Series(dtype=float) result = diff(empty) assert len(result) == 0 def test_lag(): dates = pd.date_range("2019-01-01", periods=4, freq="D") x = pd.Series([1.0, 2.0, 3.0, 4.0], index=dates) result = lag(x, '1m') expected = pd.Series([1.0, 2.0, 3.0, 4.0], index=_normalize_index(pd.date_range("2019-01-31", periods=4, freq="D"))) expected.index.freq = None assert_series_equal(result, expected, obj="Lag 1m") result = lag(x, '2d', LagMode.TRUNCATE) expected = pd.Series([1.0, 2.0], index=_normalize_index(pd.date_range("2019-01-03", periods=2, freq="D"))) expected.index.freq = None assert_series_equal(result, expected, obj="Lag 2d truncate") result = lag(x, mode=LagMode.TRUNCATE) expected = pd.Series([np.nan, 1.0, 2.0, 3.0], index=dates) expected.index.freq = None assert_series_equal(result, expected, obj="Lag") result = lag(x, 2, LagMode.TRUNCATE) expected = pd.Series([np.nan, np.nan, 1.0, 2.0], index=dates) expected.index.freq = None assert_series_equal(result, expected, obj="Lag 2") result = lag(x, 2, LagMode.EXTEND) expected = pd.Series( [np.nan, np.nan, 1.0, 2.0, 3.0, 4.0], index=_normalize_index(pd.date_range("2019-01-01", periods=6, freq="D")) ) assert_series_equal(result, expected, obj="Lag 2 Extend") result = lag(x, -2, LagMode.EXTEND) expected = pd.Series( [1.0, 2.0, 3.0, 4.0, np.nan, np.nan], index=_normalize_index(pd.date_range("2018-12-30", periods=6, freq="D")) ) assert_series_equal(result, expected, obj="Lag Negative 2 Extend") result = lag(x, 2) expected = pd.Series( [np.nan, np.nan, 1.0, 2.0, 3.0, 4.0], index=_normalize_index(pd.date_range("2019-01-01", periods=6, freq="D")) ) assert_series_equal(result, expected, obj="Lag 2 Default") y = pd.Series([0] * 4, index=pd.date_range('2020-01-01T00:00:00Z', periods=4, freq=FREQ_SECOND)) with pytest.raises(Exception): lag(y, 5, LagMode.EXTEND) z = pd.Series([10, 11, 12], index=pd.date_range('2020-02-28', periods=3, freq='D')) result = lag(z, '2y') expected = pd.Series([10, 12], index=_normalize_index(pd.date_range('2022-02-28', periods=2, freq='D'))) expected.index.freq = None assert_series_equal(result, expected, obj="Lag RDate 2y") # Test that business day offsets raise an error with pytest.raises(MqValueError, match="Business day offset '1b' is not supported"): lag(x, '1b') with pytest.raises(MqValueError, match="Business day offset '-2B' is not supported"): lag(x, '-2B') def test_repeat_empty_series(): # Test case for an empty series empty_series = pd.Series(dtype=float) result = repeat(empty_series) assert result.empty, "The result should be an empty series when input is empty." def test_lag_empty_series(): empty_series = pd.Series(dtype=float) result = lag(empty_series) assert result.empty, "The result should be an empty series when input is empty." ================================================ FILE: gs_quant/test/timeseries/test_backtesting.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import numpy as np import pandas as pd import pytest from pandas.testing import assert_series_equal from testfixtures import Replacer from testfixtures.mock import Mock from gs_quant.timeseries import VolReference from gs_quant.timeseries.backtesting import Basket, basket_series, MqValueError, MqTypeError, RebalFreq, DataContext def test_basket_series(): dates = [ dt.datetime(2019, 1, 1), dt.datetime(2019, 1, 2), dt.datetime(2019, 1, 3), dt.datetime(2019, 1, 4), dt.datetime(2019, 1, 5), dt.datetime(2019, 1, 6), ] x = pd.Series([100.0, 101, 103.02, 100.9596, 100.9596, 102.978792], index=dates) y = pd.Series([100.0, 100, 100, 100, 100, 100], index=dates) assert_series_equal(x, basket_series([x], [1])) assert_series_equal(x, basket_series([x, x], [0.5, 0.5])) assert_series_equal(x, basket_series([x, x, x], [1 / 3, 1 / 3, 1 / 3])) assert_series_equal(x, basket_series([x, y], [1, 0])) assert_series_equal(y, basket_series([x, y], [0, 1])) with pytest.raises(MqValueError): basket_series([x, y], [1]) with pytest.raises(MqTypeError): basket_series([1, 2, 3], [1]) dates = [ dt.datetime(2019, 1, 1), dt.datetime(2019, 1, 2), dt.datetime(2019, 1, 3), dt.datetime(2019, 1, 4), dt.datetime(2019, 1, 5), dt.datetime(2019, 1, 6), dt.datetime(2019, 2, 1), dt.datetime(2019, 2, 2), dt.datetime(2019, 2, 3), dt.datetime(2019, 2, 4), dt.datetime(2019, 2, 5), dt.datetime(2019, 2, 6), ] mreb = pd.Series( [100.0, 101, 103.02, 100.9596, 100.9596, 102.978792, 100.0, 101, 103.02, 100.9596, 100.9596, 102.978792], index=dates, ) assert_series_equal(mreb, basket_series([mreb], [1], rebal_freq=RebalFreq.MONTHLY)) dates = [ dt.datetime(2019, 1, 1), dt.datetime(2019, 1, 2), dt.datetime(2019, 1, 3), dt.datetime(2019, 1, 4), dt.datetime(2019, 1, 5), dt.datetime(2019, 1, 8), dt.datetime(2019, 1, 9), dt.datetime(2019, 1, 10), dt.datetime(2019, 1, 11), dt.datetime(2019, 1, 12), dt.datetime(2019, 1, 13), ] wreb = pd.Series([100.0, 105, 110, 115, 120, 125, 130, 135, 140, 145, 150], index=dates) wreb_2 = pd.Series([100.0, 105, 110, 115, 120, 125, 130, 135, 140, 145, 150], index=dates) ret_wreb = pd.Series([100.0, 110.0, 120.0, 130.0, 140.0, 150.0, 162.0, 174.0, 186.0, 198.0, 210.0], index=dates) assert_series_equal(ret_wreb, basket_series([wreb, wreb_2], [1, 1], rebal_freq=RebalFreq.WEEKLY)) def _mock_spot_data(): dates = pd.DatetimeIndex( [ dt.date(2021, 1, 1), dt.date(2021, 1, 2), dt.date(2021, 1, 3), dt.date(2021, 1, 4), dt.date(2021, 1, 5), dt.date(2021, 1, 6), ] ) x = pd.DataFrame({'spot': [100.0, 101, 103.02, 100.9596, 100.9596, 102.978792]}, index=dates) x['assetId'] = 'MA4B66MW5E27U9VBB94' y = pd.DataFrame({'spot': [100.0, 100, 100, 100, 100, 100]}, index=dates) y['assetId'] = 'MA4B66MW5E27UAL9SUX' return pd.concat([x, y]) def _mock_spot_data_feb(): dates_feb = pd.DatetimeIndex( [ dt.date(2021, 2, 1), dt.date(2021, 2, 2), dt.date(2021, 2, 3), dt.date(2021, 2, 4), dt.date(2021, 2, 5), dt.date(2021, 2, 6), ] ) x = pd.DataFrame({'spot': [100.0, 101.5, 106.02, 100.1, 105.3, 102.9]}, index=dates_feb) x['assetId'] = 'MA4B66MW5E27U9VBB94' y = pd.DataFrame({'spot': [100.0, 101.5, 100.02, 98.1, 95.3, 93.9]}, index=dates_feb) y['assetId'] = 'MA4B66MW5E27UAL9SUX' return pd.concat([x, y]) def test_basket_price(): with pytest.raises(MqValueError): Basket(['AAPL UW'], [0.1, 0.9], RebalFreq.MONTHLY) dates = pd.DatetimeIndex( [ dt.date(2021, 1, 1), dt.date(2021, 1, 2), dt.date(2021, 1, 3), dt.date(2021, 1, 4), dt.date(2021, 1, 5), dt.date(2021, 1, 6), ] ) dates_feb = pd.DatetimeIndex( [ dt.date(2021, 2, 1), dt.date(2021, 2, 2), dt.date(2021, 2, 3), dt.date(2021, 2, 4), dt.date(2021, 2, 5), dt.date(2021, 2, 6), ] ) replace = Replacer() mock_data = replace('gs_quant.timeseries.backtesting.ts.get_historical_and_last_for_measure', Mock()) mock_data.side_effect = [_mock_spot_data(), _mock_spot_data_feb()] mock_asset = replace('gs_quant.timeseries.backtesting.GsAssetApi.get_many_assets_data', Mock()) mock_asset.return_value = [ {'id': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW'}, {'id': 'MA4B66MW5E27UAL9SUX', 'bbid': 'MSFT UW'}, ] a_basket = Basket(['AAPL UW', 'MSFT UW'], [0.1, 0.9], RebalFreq.MONTHLY) expected = pd.Series([100.0, 100.1, 100.302, 100.09596, 100.09596, 100.297879], index=dates) with DataContext('2021-01-01', '2021-01-06'): actual = a_basket.price() assert_series_equal(actual, expected) expected = pd.Series([100.00, 101.50, 100.62, 98.30, 96.30, 94.80], index=dates_feb) with DataContext('2021-02-01', '2021-02-06'): actual = a_basket.price() assert_series_equal(actual, expected) mock_asset = replace('gs_quant.timeseries.backtesting.GsAssetApi.get_many_assets_data', Mock()) mock_asset.return_value = [{'id': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW'}] with pytest.raises(MqValueError): Basket(['AAPL UW', 'ABC'], [0.1, 0.9], RebalFreq.MONTHLY).price() with pytest.raises(NotImplementedError): a_basket.price(real_time=True) replace.restore() def test_basket_average_implied_vol(): replace = Replacer() dates = pd.DatetimeIndex( [ dt.date(2021, 1, 1), dt.date(2021, 1, 2), dt.date(2021, 1, 3), dt.date(2021, 1, 4), dt.date(2021, 1, 5), dt.date(2021, 1, 6), ] ) x = pd.DataFrame({'impliedVolatility': [30.0, 30.2, 29.8, 30.6, 30.1, 30.0]}, index=dates) x['assetId'] = 'MA4B66MW5E27U9VBB94' y = pd.DataFrame({'impliedVolatility': [20.0, 20.2, 20.3, 20.6, 21.1, 20.0]}, index=dates) y['assetId'] = 'MA4B66MW5E27UAL9SUX' implied_vol = pd.concat([x, y]) implied_vol.index.name = 'date' mock_spot = replace('gs_quant.timeseries.backtesting.ts.get_historical_and_last_for_measure', Mock()) mock_spot.side_effect = [implied_vol.rename(columns={'impliedVolatility': 'spot'})] mock_data = replace('gs_quant.api.utils.ThreadPoolManager.run_async', Mock()) mock_data.return_value = [implied_vol] mock_asset = replace('gs_quant.timeseries.backtesting.GsAssetApi.get_many_assets_data', Mock()) mock_asset.return_value = [ {'id': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW'}, {'id': 'MA4B66MW5E27UAL9SUX', 'bbid': 'MSFT UW'}, ] a_basket = Basket(['AAPL UW', 'MSFT UW'], [0.1, 0.9], RebalFreq.DAILY) expected = pd.Series([21.0, 21.2, 21.25, 21.6, 22.0, 21.0], index=dates) actual = a_basket.average_implied_volatility('6m', VolReference.DELTA_CALL, 50) assert_series_equal(actual, expected) with pytest.raises(NotImplementedError): a_basket.average_implied_volatility('6m', VolReference.DELTA_CALL, 50, real_time=True) mock_data.return_value = [pd.DataFrame(), pd.DataFrame()] expected = pd.Series(dtype=float) actual = a_basket.average_implied_volatility('3m', VolReference.FORWARD, 20) # no data for this assert_series_equal(expected, actual) replace.restore() def test_basket_average_realized_vol(): replace = Replacer() dates = pd.DatetimeIndex( [ dt.date(2021, 1, 1), dt.date(2021, 1, 2), dt.date(2021, 1, 3), dt.date(2021, 1, 4), dt.date(2021, 1, 5), dt.date(2021, 1, 6), ] ) dates_feb = pd.DatetimeIndex( [ dt.date(2021, 2, 1), dt.date(2021, 2, 2), dt.date(2021, 2, 3), dt.date(2021, 2, 4), dt.date(2021, 2, 5), dt.date(2021, 2, 6), ] ) mock_data = replace('gs_quant.timeseries.backtesting.ts.get_historical_and_last_for_measure', Mock()) mock_data.side_effect = [_mock_spot_data(), _mock_spot_data_feb(), _mock_spot_data_feb()] mock_asset = replace('gs_quant.timeseries.backtesting.GsAssetApi.get_many_assets_data', Mock()) mock_asset.return_value = [ {'id': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW'}, {'id': 'MA4B66MW5E27UAL9SUX', 'bbid': 'MSFT UW'}, ] a_basket = Basket(['AAPL UW', 'MSFT UW'], [0.1, 0.9], RebalFreq.DAILY) expected = pd.Series([1.1059, 4.4906, 2.2677, 2.2228], index=dates[2:]) with DataContext('2021-01-01', '2021-01-06'): actual = a_basket.average_realized_volatility('2d') assert_series_equal(actual, expected, atol=10e-3) expected = pd.Series([3.3088, 3.1754, 3.1754], index=dates[3:]) with DataContext('2021-01-01', '2021-01-06'): actual = a_basket.average_realized_volatility('3d') assert_series_equal(actual, expected) mock_data.assert_called_once() expected = pd.Series([34.81054014068537, 19.98982339010735, 19.08853721611424], index=dates_feb[3:]) with DataContext('2021-02-01', '2021-02-06'): actual = a_basket.average_realized_volatility('3d') assert_series_equal(actual, expected) with pytest.raises(NotImplementedError): a_basket.average_realized_volatility('2d', real_time=True) mock_get_last = replace('gs_quant.timeseries.measures.get_last_for_measure', Mock()) mock_get_last.return_value = None # Test case where ts.get_last_for_measure returns none with DataContext('2021-02-01', dt.date.today() + dt.timedelta(days=2)): a_basket.average_realized_volatility('2d') replace.restore() def _mock_vol_simple(): return pd.Series([1 for i in range(5)], index=pd.date_range('2021-09-01', '2021-09-05')) def _mock_data_simple(): a = pd.Series([1 for i in range(5)], index=pd.date_range('2021-09-01', '2021-09-05')) x = pd.DataFrame({'spot': a.tolist()}, index=a.index) x['assetId'] = 'XLC_MOCK_MQID' y = pd.DataFrame({'spot': a.tolist()}, index=a.index) y['assetId'] = 'XLB_MOCK_MQID' z = pd.DataFrame({'spot': (a**3).tolist()}, index=a.index) z['assetId'] = 'SPX_MOCK_MQID' return pd.concat([x, y, z]) def _mock_spot_data_identical(): dates = pd.DatetimeIndex( [ dt.date(2021, 1, 1), dt.date(2021, 1, 2), dt.date(2021, 1, 3), dt.date(2021, 1, 4), dt.date(2021, 1, 5), dt.date(2021, 1, 6), ] ) x = pd.DataFrame({'spot': [100.0, 101, 103.02, 100.9596, 100.9596, 102.978792]}, index=dates) x['assetId'] = 'MA4B66MW5E27U9VBB94' y = pd.DataFrame({'spot': [100.0, 101, 103.02, 100.9596, 100.9596, 102.978792]}, index=dates) y['assetId'] = 'MA4B66MW5E27UAL9SUX' return pd.concat([x, y]) def _mock_spot_data_corr(): dates = pd.DatetimeIndex( [ dt.date(2021, 1, 1), dt.date(2021, 1, 2), dt.date(2021, 1, 3), dt.date(2021, 1, 4), dt.date(2021, 1, 5), dt.date(2021, 1, 6), ] ) x = pd.DataFrame({'spot': [78, 9, 1003, 17, -12, 5], 'assetId': 'MA4B66MW5E27U9VBB94'}, index=dates) y = pd.DataFrame({'spot': [-33, 33, 15, 21, -3, 2], 'assetId': 'MA4B66MW5E27UAL9SUX'}, index=dates) z = pd.DataFrame({'spot': [86, 86, 56, 86, 86, 9], 'assetId': 'MA4B66MW5E27UANZH2M'}, index=dates) return pd.concat([x, y, z]) def test_basket_average_realized_vol_wts(): replace = Replacer() mock_data = replace('gs_quant.timeseries.backtesting.ts.get_historical_and_last_for_measure', Mock()) mock_data.side_effect = [_mock_data_simple()] mock_asset = replace('gs_quant.timeseries.backtesting.GsAssetApi.get_many_assets_data', Mock()) mock_asset.return_value = [ {'id': 'XLB_MOCK_MQID', 'bbid': 'XLP UP'}, {'id': 'XLC_MOCK_MQID', 'bbid': 'XLC UP'}, {'id': 'SPX_MOCK_MQID', 'bbid': 'SPX'}, ] mock_vol = replace('gs_quant.timeseries.backtesting.volatility', Mock()) mock_vol.side_effect = [_mock_vol_simple(), _mock_vol_simple() * 2, _mock_vol_simple() * 3, _mock_vol_simple() * 4] a_basket = Basket(['XLC UP', 'XLP UP', 'SPX'], [0.2, 0.3, 0.5], RebalFreq.DAILY) with DataContext(start=dt.date(2021, 9, 1), end=dt.date(2021, 9, 25)): av_realized_vol = a_basket.average_realized_volatility('2d') np.testing.assert_approx_equal(av_realized_vol.iloc[0], 2.3) replace.restore() def test_basket_average_realized_vol_intraday(): replace = Replacer() end_date = dt.date.today() start_date = end_date - dt.timedelta(days=4) a = pd.Series([1 for i in range(5)], index=pd.date_range(start_date, end_date)) z = pd.DataFrame({'spot': (a**3).tolist()}, index=a.index) z['assetId'] = 'SPX_MOCK_MQID' mock_data = replace('gs_quant.timeseries.backtesting.ts.get_historical_and_last_for_measure', Mock()) mock_data.side_effect = [z] mock_asset = replace('gs_quant.timeseries.backtesting.GsAssetApi.get_many_assets_data', Mock()) mock_asset.return_value = [{'id': 'SPX_MOCK_MQID', 'bbid': 'SPX'}] mock_today = replace('gs_quant.timeseries.get_last_for_measure', Mock()) mock_today.side_effect = [pd.DataFrame({'assetId': 'SPX_MOCK_MQID', 'spot': 5001.0}, index=[dt.datetime.now()])] a_basket = Basket(['SPX'], [1], RebalFreq.DAILY) with DataContext(start=start_date, end=dt.date.today()): avg_vol = a_basket.average_realized_volatility('2d') assert avg_vol.index[-1].date() == dt.date.today() replace.restore() def test_basket_average_realized_corr(): replace = Replacer() dates = pd.DatetimeIndex( [ dt.date(2021, 1, 1), dt.date(2021, 1, 2), dt.date(2021, 1, 3), dt.date(2021, 1, 4), dt.date(2021, 1, 5), dt.date(2021, 1, 6), ] ) mock_data = replace('gs_quant.timeseries.backtesting.ts.get_historical_and_last_for_measure', Mock()) mock_data.side_effect = [_mock_spot_data_identical(), _mock_spot_data_corr(), _mock_spot_data_identical()] mock_asset = replace('gs_quant.timeseries.backtesting.GsAssetApi.get_many_assets_data', Mock()) mock_asset.return_value = [ {'id': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW'}, {'id': 'MA4B66MW5E27UAL9SUX', 'bbid': 'MSFT UW'}, {'id': 'ID of a dUpLiCaTe AAPL', 'bbid': 'AAPL UW'}, ] a_basket = Basket(['AAPL UW', 'MSFT UW'], [0.1, 0.9], RebalFreq.DAILY) # Equal series have correlation of 1 with DataContext('2021-01-01', '2021-01-06'): expected = pd.Series([np.nan, np.nan, 1.0, 1.0, 1.0, 1.0], index=dates) result = a_basket.average_realized_correlation('2d') assert_series_equal(result, expected) mock_asset = replace('gs_quant.timeseries.backtesting.GsAssetApi.get_many_assets_data', Mock()) mock_asset.return_value = [ {'id': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW'}, {'id': 'MA4B66MW5E27UAL9SUX', 'bbid': 'MSFT UW'}, {'id': 'MA4B66MW5E27UANZH2M', 'bbid': 'XLP UP'}, {'id': 'ID of a dUpLiCaTe XLP UP', 'bbid': 'XLP UP'}, ] b_basket = Basket(['AAPL UW', 'MSFT UW', 'XLP UP'], [0.2, 0.3, 0.5], RebalFreq.DAILY) # Test with two different series with DataContext('2021-01-01', '2021-01-06'): result = b_basket.average_realized_correlation('5d') expected = pd.Series([np.nan, np.nan, np.nan, np.nan, np.nan, 0.26872959922887607], index=dates) assert_series_equal(result, expected) # Test correct error being thrown mock_asset = replace('gs_quant.timeseries.backtesting.GsAssetApi.get_many_assets_data', Mock()) mock_asset.return_value = [ {'id': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW'}, {'id': 'MA4B66MW5E27UAL9SUX', 'bbid': 'MSFT UW'}, {'id': 'MA4B66MW5E27UANZH2M', 'bbid': 'XLP UP'}, {'id': 'ID of a DuPlIcAtE MSFT UW', 'bbid': 'MSFT UW'}, ] with pytest.raises(NotImplementedError): with DataContext('2021-01-01', '2021-01-09'): result = b_basket.average_realized_correlation('5d', real_time=True) replace.restore() def test_basket_without_weights(): replace = Replacer() mock_data = replace('gs_quant.timeseries.backtesting.ts.get_historical_and_last_for_measure', Mock()) mock_data.side_effect = [_mock_spot_data(), _mock_spot_data(), _mock_spot_data(), _mock_spot_data()] mock_asset = replace('gs_quant.timeseries.backtesting.GsAssetApi.get_many_assets_data', Mock()) mock_asset.return_value = [ {'id': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW'}, {'id': 'MA4B66MW5E27UAL9SUX', 'bbid': 'MSFT UW'}, ] a_basket = Basket(['AAPL UW', 'MSFT UW'], [0.5, 0.5], RebalFreq.DAILY) b_basket = Basket(['AAPL UW', 'MSFT UW']) with DataContext('2021-01-01', '2021-01-06'): a_price = a_basket.price() b_price = b_basket.price() a_vol = a_basket.average_realized_volatility('2d') b_vol = b_basket.average_realized_volatility('2d') assert_series_equal(a_price, b_price) assert_series_equal(a_vol, b_vol) with pytest.raises(MqValueError): c_basket = Basket(['AAPL UW'], []) c_basket.price() replace.restore() def test_basket_avg_fwd_vol(): replace = Replacer() dates = pd.DatetimeIndex( [ dt.date(2021, 1, 1), dt.date(2021, 1, 2), dt.date(2021, 1, 3), dt.date(2021, 1, 4), dt.date(2021, 1, 5), dt.date(2021, 1, 6), ] ) x_1m = pd.DataFrame({'impliedVolatility': [30.0, 30.2, 29.8, 30.6, 30.1, 30.0]}, index=dates) x_1m['assetId'] = 'MA4B66MW5E27U9VBB94' x_1m['tenor'] = '1m' x_2m = pd.DataFrame({'impliedVolatility': [40.0, 41.2, 35.8, 31.6, 36.1, 37.0]}, index=dates) x_2m['assetId'] = 'MA4B66MW5E27U9VBB94' x_2m['tenor'] = '2m' y_1m = pd.DataFrame({'impliedVolatility': [20.0, 20.2, 20.3, 20.6, 21.1, 20.0]}, index=dates) y_1m['assetId'] = 'MA4B66MW5E27UAL9SUX' y_1m['tenor'] = '1m' y_2m = pd.DataFrame({'impliedVolatility': [21.0, 22.2, 23.3, 21.6, 23.3, 21.0]}, index=dates) y_2m['assetId'] = 'MA4B66MW5E27UAL9SUX' y_2m['tenor'] = '2m' implied_vol = pd.concat([x_1m, x_2m, y_1m, y_2m]) spot = implied_vol.rename(columns={'impliedVolatility': 'spot'}) mock_data = replace('gs_quant.timeseries.backtesting.ts.get_historical_and_last_for_measure', Mock()) mock_data.side_effect = [implied_vol, spot] mock_asset = replace('gs_quant.timeseries.backtesting.GsAssetApi.get_many_assets_data', Mock()) mock_asset.return_value = [ {'id': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW'}, {'id': 'MA4B66MW5E27UAL9SUX', 'bbid': 'MSFT UW'}, ] a_basket = Basket(['AAPL UW', 'MSFT UW'], [0.1, 0.9], RebalFreq.DAILY) expected = pd.Series([24.55488, 26.61354, 27.45295, 23.557069, 26.90214, 24.046239], index=dates) actual = a_basket.average_forward_vol('1m', '1m', VolReference.DELTA_CALL, 50) assert_series_equal(actual, expected) with pytest.raises(NotImplementedError): a_basket.average_forward_vol('1m', '1m', VolReference.DELTA_CALL, 50, real_time=True) mock_data.side_effect = [pd.DataFrame(), spot] expected = pd.Series(dtype=float) actual = a_basket.average_forward_vol('1m', '1m', VolReference.DELTA_CALL, 50) assert_series_equal(expected, actual) implied_vol = pd.concat([x_1m, x_2m, y_1m]) implied_vol.index.name = 'date' mock_data.side_effect = [implied_vol, spot] expected = pd.Series([np.nan] * 6, index=dates) actual = a_basket.average_forward_vol('1m', '1m', VolReference.DELTA_CALL, 50) assert_series_equal(expected, actual) replace.restore() if __name__ == '__main__': pytest.main(args=[__file__]) ================================================ FILE: gs_quant/test/timeseries/test_datetime.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from unittest import mock import numpy as np import pandas as pd import pytest from pandas.testing import assert_series_equal from gs_quant.data import Dataset from gs_quant.datetime import DayCountConvention, GsCalendar from gs_quant.datetime.relative_date import RelativeDate from gs_quant.errors import MqValueError, MqTypeError from gs_quant.test.api.test_risk import set_session from gs_quant.timeseries import ( Interpolate, align, interpolate, value, day, weekday, quarter, year, date_range, day_count_fractions, append, prepend, union, bucketize, AggregateFunction, day_count, align_calendar, AggregatePeriod, month, day_countdown, ) def test_basic(): assert type(RelativeDate('0d').apply_rule()) is dt.date def test_align(): dates1 = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 5), ] dates2 = [ dt.date(2019, 1, 2), dt.date(2019, 1, 4), dt.date(2019, 1, 6), ] x = pd.Series([1.0, 2.0, 3.0, 4.0, 5.0], index=dates1) y = pd.Series([20.0, 40.0, 60.0], index=dates2) expectedl = pd.Series([2.0, 4.0], index=[dt.date(2019, 1, 2), dt.date(2019, 1, 4)]) expectedr = pd.Series([20.0, 40.0], index=[dt.date(2019, 1, 2), dt.date(2019, 1, 4)]) result = align(x, y, Interpolate.INTERSECT) assert_series_equal(result[0], expectedl, obj="Align intersect left") assert_series_equal(result[1], expectedr, obj="Align intersect left") result = align(y, x, Interpolate.INTERSECT) assert_series_equal(result[0], expectedr, obj="Align intersect right") assert_series_equal(result[1], expectedl, obj="Align intersect right") union_dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 5), dt.date(2019, 1, 6), ] expected1 = pd.Series([1.0, 2.0, 3.0, 4.0, 5.0, np.nan], index=union_dates) expected2 = pd.Series([np.nan, 20.0, np.nan, 40.0, np.nan, 60.0], index=union_dates) result = align(x, y, Interpolate.NAN) assert_series_equal(result[0], expected1, obj="Align NaN left") assert_series_equal(result[1], expected2, obj="Align NaN left") result = align(y, x, Interpolate.NAN) assert_series_equal(result[0], expected2, obj="Align NaN right") assert_series_equal(result[1], expected1, obj="Align NaN right") expected1 = pd.Series([1.0, 2.0, 3.0, 4.0, 5.0, 0.0], index=union_dates) expected2 = pd.Series([0.0, 20.0, 0.0, 40.0, 0.0, 60.0], index=union_dates) result = align(x, y, Interpolate.ZERO) assert_series_equal(result[0], expected1, obj="Align zero left") assert_series_equal(result[1], expected2, obj="Align zero left") result = align(y, x, Interpolate.ZERO) assert_series_equal(result[0], expected2, obj="Align zero right") assert_series_equal(result[1], expected1, obj="Align zero right") expected1 = pd.Series([1.0, 2.0, 3.0, 4.0, 5.0, 5.0], index=union_dates) expected2 = pd.Series([20.0, 20.0, 20.0, 40.0, 40.0, 60.0], index=union_dates) result = align(x, y, Interpolate.STEP) assert_series_equal(result[0], expected1, obj="Align step left") assert_series_equal(result[1], expected2, obj="Align step left") result = align(y, x, Interpolate.STEP) assert_series_equal(result[0], expected2, obj="Align step left") assert_series_equal(result[1], expected1, obj="Align step left") xp = x.copy() yp = y.copy() xp.index = pd.to_datetime(xp.index) yp.index = pd.to_datetime(yp.index) up = pd.to_datetime(union_dates) expected1 = pd.Series([1.0, 2.0, 3.0, 4.0, 5.0, np.nan], index=up) expected2 = pd.Series([np.nan, 20.0, 30.0, 40.0, 50.0, 60.0], index=up) result = align(xp, yp, Interpolate.TIME) assert_series_equal(result[0], expected1, obj="Align time left") assert_series_equal(result[1], expected2, obj="Align time left") result = align(yp, xp, Interpolate.TIME) assert_series_equal(result[0], expected2, obj="Align time right") assert_series_equal(result[1], expected1, obj="Align time right") a = pd.Series([0, 100, 110], index=pd.DatetimeIndex(['2019-07-01', '2019-07-08', '2019-07-10'])) b = pd.Series([20, 60, 70], index=pd.DatetimeIndex(['2019-07-02', '2019-07-10', '2019-07-11'])) result = align(a, b, Interpolate.TIME) u_index = a.index.union(b.index) assert_series_equal(result[0], pd.Series([0, 100 / 7, 100, 110, np.nan], index=u_index)) assert_series_equal(result[1], pd.Series([np.nan, 20, 50, 60, 70], index=u_index)) result = align(x, 3) assert_series_equal(result[0], x, obj="Align scalar left") assert_series_equal(result[1], pd.Series(3, index=dates1), obj="Align scalar left") result = align(3, x) assert_series_equal(result[0], pd.Series(3, index=dates1), obj="Align scalar left") assert_series_equal(result[1], x, obj="Align scalar right") result = align(1, 2) assert result[0] == 1 assert result[1] == 2 with pytest.raises(MqValueError): align(x, x, "None") def test_interpolate(): dates = [ dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 5), dt.date(2019, 1, 7), ] x = pd.Series([2.0, 3.0, 5.0, 7.0], index=dates) result = interpolate(x, dates) assert_series_equal(result, x, obj="Interpolate series by dates") result = interpolate(x, x) assert_series_equal(result, x, obj="Interpolate series by series dates") result = interpolate(x) assert_series_equal(result, x, obj="Interpolate series default") select_dates = [ dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 7), ] result = interpolate(x, select_dates) expected = pd.Series([2.0, 3.0, 7.0], index=select_dates) assert_series_equal(result, expected, obj="Interpolate subset of dates") select_dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 4), dt.date(2019, 1, 5), dt.date(2019, 1, 6), dt.date(2019, 1, 7), dt.date(2019, 1, 8), ] intersect_dates = [ dt.date(2019, 1, 2), dt.date(2019, 1, 5), dt.date(2019, 1, 7), ] result = interpolate(x, select_dates, Interpolate.INTERSECT) expected = pd.Series([2.0, 5.0, 7.0], index=intersect_dates) assert_series_equal(result, expected, obj="Interpolate intersect") result = interpolate(x, select_dates, Interpolate.NAN) expected = pd.Series([np.nan, 2.0, np.nan, 5.0, np.nan, 7.0, np.nan], index=select_dates) assert_series_equal(result, expected, obj="Interpolate nan") result = interpolate(x, select_dates, Interpolate.ZERO) expected = pd.Series([0.0, 2.0, 0.0, 5.0, 0.0, 7.0, 0.0], index=select_dates) assert_series_equal(result, expected, obj="Interpolate zero") result = interpolate(x, select_dates, Interpolate.STEP) expected = pd.Series([2.0, 2.0, 2.0, 5.0, 5.0, 7.0, 7.0], index=select_dates) assert_series_equal(result, expected, obj="Interpolate step dates") result = interpolate(x, pd.Series(np.nan, select_dates), Interpolate.STEP) expected = pd.Series([2.0, 2.0, 2.0, 5.0, 5.0, 7.0, 7.0], index=select_dates) assert_series_equal(result, expected, obj="Interpolate step series") xnan = pd.Series([np.nan, 3.0, 5.0, 7.0], index=dates) result = interpolate(xnan, select_dates, Interpolate.STEP) expected = pd.Series([np.nan, np.nan, np.nan, 5.0, 5.0, 7.0, 7.0], index=select_dates) assert_series_equal(result, expected, obj="Interpolate flat nan start") x = pd.Series([2.0, 3.0, 5.0, 7.0], index=pd.DatetimeIndex(dates)) result = interpolate(x, select_dates, Interpolate.STEP) expected = pd.Series([2.0, 2.0, 2.0, 5.0, 5.0, 7.0, 7.0], index=pd.DatetimeIndex(select_dates)) assert_series_equal(result, expected, obj="Interpolate step dates to series with timestamps") with pytest.raises(MqValueError, match="Unknown intersection type: None"): interpolate(x, x, "None") with pytest.raises(MqValueError, match="Cannot perform step interpolation on an empty series"): interpolate(pd.Series(dtype=float), select_dates, Interpolate.STEP) def test_value(): dates = [ dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 5), dt.date(2019, 1, 7), ] x = pd.Series([2.0, 3.0, 5.0, 7.0], index=dates) result = value(x, dt.date(2019, 1, 3)) assert result == 3.0 result = value(x, dt.date(2019, 1, 5)) assert result == 5.0 result = value(x, dt.date(2019, 1, 4)) assert result == 3.0 result = value(x, dt.date(2019, 1, 4), Interpolate.INTERSECT) assert result is None result = value(x, dt.date(2019, 1, 4), Interpolate.STEP) assert result == 3.0 result = value(x, dt.date(2019, 1, 4), Interpolate.ZERO) assert result == 0.0 result = value(x, dt.date(2019, 1, 4), Interpolate.NAN) assert np.isnan(result) def test_day(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), ] x = pd.Series([1.0, 2.0, 3.0, 4.0], index=dates) result = day(x) expected = pd.Series([1, 2, 3, 4], index=dates) assert_series_equal(result, expected, obj="Day") def test_weekday(): dates = [ dt.date(2019, 1, 7), dt.date(2019, 1, 8), dt.date(2019, 1, 9), dt.date(2019, 1, 10), dt.date(2019, 1, 11), dt.date(2019, 1, 12), dt.date(2019, 1, 13), ] x = pd.Series([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0], index=dates) result = weekday(x) expected = pd.Series([0, 1, 2, 3, 4, 5, 6], index=dates) assert_series_equal(result, expected, obj="Weekday") def test_month(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 2, 1), dt.date(2019, 3, 1), dt.date(2019, 4, 1), ] x = pd.Series([1.0, 2.0, 3.0, 4.0], index=dates) result = month(x) expected = pd.Series([1, 2, 3, 4], index=dates) assert_series_equal(result, expected, obj="Month") def test_year(): dates = [ dt.date(2019, 1, 1), dt.date(2020, 1, 2), dt.date(2021, 1, 3), dt.date(2022, 1, 4), ] x = pd.Series([1.0, 2.0, 3.0, 4.0], index=dates) result = year(x) expected = pd.Series([2019, 2020, 2021, 2022], index=dates) assert_series_equal(result, expected, obj="Year") def test_quarter(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 4, 1), dt.date(2019, 7, 1), dt.date(2019, 10, 1), ] x = pd.Series([1.0, 2.0, 3.0, 4.0], index=dates) result = quarter(x) expected = pd.Series([1, 2, 3, 4], index=dates) assert_series_equal(result, expected, obj="Quarter") def test_day_count_fractions(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 5), dt.date(2019, 1, 6), ] x = pd.Series(dtype=float) assert_series_equal(x, day_count_fractions(x)) x = pd.Series([100.0, 101, 103.02, 100.9596, 100.9596, 102.978792], index=dates) result = day_count_fractions(x, DayCountConvention.ACTUAL_360) result2 = day_count_fractions(x.index, DayCountConvention.ACTUAL_360) dcf = 1 / 360 expected = pd.Series([np.nan, dcf, dcf, dcf, dcf, dcf], index=dates) assert_series_equal(result, expected, obj="ACT/360") assert_series_equal(result2, expected, obj="ACT/360") result = day_count_fractions(x, DayCountConvention.ACTUAL_365F) dcf = 1 / 365 expected = pd.Series([np.nan, dcf, dcf, dcf, dcf, dcf], index=dates) assert_series_equal(result, expected, obj="ACT/365") def test_date_range(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 5), dt.date(2019, 1, 6), ] values = [1.0, 2.0, 3.0, 4.0, 5.0, 7.0] s0 = pd.Series(values, index=dates) s1 = pd.Series(values, index=pd.date_range('2019-01-01', periods=6, freq='D').date) for x in [s0, s1]: assert (date_range(x, 0, 0) == x).all() assert (date_range(x, 0, 0, True) == x.iloc[:-2]).all() assert date_range(x, 0, dt.date(2019, 1, 3)).index[-1] == dt.date(2019, 1, 3) assert (date_range(x, 0, dt.date(2019, 1, 3)) == x.iloc[:3]).all() assert date_range(x, dt.date(2019, 1, 3), dt.date(2019, 1, 6)).index[0] == dt.date(2019, 1, 3) assert date_range(x, dt.date(2019, 1, 3), dt.date(2019, 1, 6)).index[-1] == dt.date(2019, 1, 6) assert (date_range(x, dt.date(2019, 1, 3), dt.date(2019, 1, 6)) == x.iloc[2:6]).all() y = pd.Series(values, index=pd.date_range('2020-10-23', periods=6, freq='D')) assert (date_range(y, 1, 1, True) == y.iloc[3:5]).all() with pytest.raises(MqValueError): date_range(pd.Series([1]), 0, 0) with pytest.raises(MqTypeError): date_range(pd.Series([1]), 0, 0, 'string') def test_append(): x = pd.Series([3.1, 4.1, 5.1], index=pd.date_range('2019-01-03', '2019-01-05')) y = pd.Series([1.0, 2.0, 3.0, 4.0, 5.0, 7.0], index=pd.date_range('2019-01-01', "2019-01-06")) assert_series_equal(append([]), pd.Series(dtype='float64'), obj='append empty') assert_series_equal(append([x]), x, obj='append one series') actual = append([x, y]) expected = pd.Series([3.1, 4.1, 5.1, 7.0], index=pd.date_range('2019-01-03', '2019-01-06')) assert_series_equal(actual, expected, obj='append two series') x = pd.Series([3.1, 4.1, 5.1], index=pd.date_range('2019-01-01 02:00', periods=3, freq='h')) y = pd.Series([1.0, 2.0, 3.0, 4.0, 5.0, 7.0], index=pd.date_range('2019-01-01', periods=6, freq='h')) actual = append([x, y]) expected = pd.Series([3.1, 4.1, 5.1, 7.0], index=pd.date_range('2019-01-01 02:00', periods=4, freq='h')) assert_series_equal(actual, expected, obj='append two real-time series') def test_prepend(): x = pd.Series([1.0, 2.0, 3.0, 4.0, 5.0, 7.0], index=pd.date_range('2019-01-01', "2019-01-06")) y = pd.Series([3.1, 4.1, 5.1], index=pd.date_range('2019-01-03', '2019-01-05')) assert_series_equal(prepend([]), pd.Series(dtype='float64'), obj='prepend empty') assert_series_equal(prepend([x]), x, obj='prepend one series') actual = prepend([x, y]) expected = pd.Series([1.0, 2.0, 3.1, 4.1, 5.1], index=pd.date_range('2019-01-01', '2019-01-05')) assert_series_equal(actual, expected, obj='prepend two series') x = pd.Series([1.0, 2.0, 3.0, 4.0, 5.0, 7.0], index=pd.date_range('2019-01-01', periods=6, freq='h')) y = pd.Series([3.1, 4.1, 5.1], index=pd.date_range('2019-01-01 02:00', periods=3, freq='h')) actual = prepend([x, y]) expected = pd.Series([1.0, 2.0, 3.1, 4.1, 5.1], index=pd.date_range('2019-01-01', periods=5, freq='h')) assert_series_equal(actual, expected, obj='prepend two real-time series') def test_union(): x = pd.Series([3.1, 4.1, np.nan], index=pd.date_range('2019-01-03', '2019-01-05')) y = pd.Series([1.0, np.nan, 3.0, 4.0, 5.0, 6.0], index=pd.date_range('2019-01-01', "2019-01-06")) z = pd.Series([60.0, 70.0], index=pd.date_range('2019-01-06', "2019-01-07")) assert_series_equal(union([]), pd.Series(dtype='float64'), obj='union empty') x.index.freq = None assert_series_equal(union([x]), x, obj='union of one series') actual = union([x, y, z]) expected = pd.Series([1.0, np.nan, 3.1, 4.1, 5.0, 6.0, 70], index=pd.date_range('2019-01-01', '2019-01-07')) assert_series_equal(actual, expected, check_freq=False, obj='union of three series') x = pd.Series([3.1, 4.1, np.nan], index=pd.date_range('2019-01-01 02:00', periods=3, freq='h')) y = pd.Series([1.0, np.nan, 3.0, 4.0, 5.0, 6.0], index=pd.date_range('2019-01-01', periods=6, freq='h')) actual = union([x, y]) expected = pd.Series([1.0, np.nan, 3.1, 4.1, 5.0, 6.0], index=pd.date_range('2019-01-01', periods=6, freq='h')) assert_series_equal(actual, expected, obj='union of two real-time series') def test_bucketize(): dates = pd.bdate_range(start='1/1/2021', end='4/23/2021') series = pd.Series(range(len(dates)), index=dates) actual = bucketize(series, AggregateFunction.MAX, AggregatePeriod.MONTH) expected_index = pd.DatetimeIndex( [dt.date(2021, 1, 31), dt.date(2021, 2, 28), dt.date(2021, 3, 31), dt.date(2021, 4, 23)] ) expected = pd.Series([20, 40, 63, 80], index=expected_index) actual.index.freq = None # Ignore the index freq assert_series_equal(actual, expected, check_index_type=False) def test_day_count(): assert day_count(dt.date(2021, 5, 7), dt.date(2021, 5, 10)) == 1 assert day_count(dt.date(2021, 5, 10), dt.date(2021, 5, 14)) == 4 assert day_count(dt.date(2021, 5, 10), dt.date(2021, 5, 17)) == 5 with pytest.raises(MqValueError): day_count(dt.date(2021, 5, 7), '2021-05-10') def test_day_countdown(): start_dt = dt.date(2021, 5, 7) end_dt = dt.date(2021, 5, 17) # calendar days (default) actual = day_countdown(end_dt, start_dt) expected_idx = pd.date_range(start=start_dt, end=end_dt, freq='D') expected = pd.Series(list(range(10, -1, -1)), index=expected_idx, dtype=np.int64) assert_series_equal(actual, expected, obj='day_countdown calendar days') # business days (Mon-Fri) actual_bus = day_countdown(end_dt, start_dt, business_days=True) expected_bus_idx = pd.bdate_range(start=start_dt, end=end_dt) expected_bus = pd.Series([6, 5, 4, 3, 2, 1, 0], index=expected_bus_idx, dtype=np.int64) assert_series_equal(actual_bus, expected_bus, obj='day_countdown business days') # start_date after end_date => empty actual_empty = day_countdown(end_dt, dt.date(2021, 5, 18)) assert_series_equal(actual_empty, pd.Series(dtype=np.int64), obj='day_countdown empty range') # type validation with pytest.raises(MqValueError): day_countdown(end_dt, '2021-05-07') with pytest.raises(MqValueError): day_countdown('2021-05-07', start_dt) # calendar single-day actual_single = day_countdown(dt.date(2021, 5, 17), dt.date(2021, 5, 17)) expected_single_idx = pd.date_range(start=dt.date(2021, 5, 17), end=dt.date(2021, 5, 17), freq='D') expected_single = pd.Series([0], index=expected_single_idx, dtype=np.int64) assert_series_equal(actual_single, expected_single, obj='day_countdown single day calendar') # business single-day on weekend weekend = dt.date(2021, 5, 15) # Saturday actual_weekend = day_countdown(weekend, weekend, business_days=True) expected_weekend = pd.Series(dtype=np.int64) assert_series_equal(actual_weekend, expected_weekend, obj='day_countdown single day business weekend') # business countdown over a weekend: Fri -> Mon fri = dt.date(2021, 5, 14) mon = dt.date(2021, 5, 17) actual_fri_mon = day_countdown(mon, fri, business_days=True) expected_fri_mon_idx = pd.bdate_range(start=fri, end=mon) expected_fri_mon = pd.Series([1, 0], index=expected_fri_mon_idx, dtype=np.int64) assert_series_equal(actual_fri_mon, expected_fri_mon, obj='day_countdown business cross weekend') # default start_date class _PatchedDate(dt.date): @classmethod def today(cls): return cls(2021, 5, 15) class _DtShim: date = _PatchedDate with mock.patch.dict(day_countdown.__globals__, {'dt': _DtShim}): actual_default = day_countdown(_PatchedDate(end_dt.year, end_dt.month, end_dt.day)) expected_default_idx = pd.date_range(start=dt.date(2021, 5, 15), end=dt.date(2021, 5, 17), freq='D') expected_default = pd.Series([2, 1, 0], index=expected_default_idx, dtype=np.int64) assert_series_equal(actual_default, expected_default, obj='day_countdown default start_date') @mock.patch.object(Dataset, 'get_coverage') @mock.patch.object(Dataset, 'get_data') def test_align_calendar(mocker_data, mocker_cov): dates = pd.date_range(start='1/1/2023', end='1/31/2023') series = pd.Series(range(len(dates)), index=dates) set_session() mocker_data.return_value = pd.DataFrame(index=[dt.datetime(2023, 1, 3)], data={'holiday': 'New Year'}) mocker_cov.return_value = pd.DataFrame() GsCalendar.reset() aligned_series = align_calendar(series, 'NYC') # Check if the holiday was removed assert dt.datetime(2023, 1, 3) not in aligned_series # Check the first saturday was removed assert dt.datetime(2023, 1, 7) not in aligned_series # Check full series length is correct assert aligned_series.size == 21 def test_bucketize_empty_series(): empty_series = pd.Series(dtype=float) result = bucketize(empty_series, AggregateFunction.MAX, AggregatePeriod.MONTH) assert result.empty, "The result should be an empty series when the input series is empty." if __name__ == "__main__": pytest.main(args=["test_datetime.py"]) ================================================ FILE: gs_quant/test/timeseries/test_econometrics.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import math import os from unittest.mock import Mock import numpy as np import pandas as pd import pytest from pandas.testing import assert_series_equal from testfixtures import Replacer from gs_quant.common import Currency as CurrencyEnum from gs_quant.datetime import DayCountConvention from gs_quant.errors import MqValueError, MqTypeError, MqError from gs_quant.markets.securities import Cash from gs_quant.timeseries import ( returns, prices, index, change, annualize, volatility, correlation, beta, max_drawdown, Returns, Window, Direction, generate_series, SeriesType, Interpolate, CurveType, ) from gs_quant.timeseries.econometrics import ( _get_ratio, excess_returns, RiskFreeRateCurrency, sharpe_ratio, excess_returns_, corr_swap_correlation, vol_swap_volatility, ) def test_returns(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 5), dt.date(2019, 1, 6), ] x = pd.Series(dtype=float) assert_series_equal(x, returns(x)) x = pd.Series([100.0, 101, 103.02, 100.9596, 100.9596, 102.978792], index=dates) result = returns(x) expected = pd.Series([np.nan, 0.01, 0.02, -0.02, 0.0, 0.02], index=dates) assert_series_equal(result, expected, obj="Simple returns default") result = returns(x, 1, Returns.SIMPLE) expected = pd.Series([np.nan, 0.01, 0.02, -0.02, 0.0, 0.02], index=dates) assert_series_equal(result, expected, obj="Simple returns") result = returns(x, 2, Returns.SIMPLE) expected = pd.Series([np.nan, np.nan, 0.0302, -0.0004, -0.0200, 0.0200], index=dates) assert_series_equal(result, expected, obj="Simple returns") result = returns(x, 1, Returns.LOGARITHMIC) expected = pd.Series([np.nan, 0.009950, 0.019803, -0.020203, 0.0, 0.019803], index=dates) assert_series_equal(result, expected, obj="Logarithmic returns", atol=1e-5) result = returns(x, 2, Returns.LOGARITHMIC) expected = pd.Series([np.nan, np.nan, 0.029753, -0.0004, -0.020203, 0.019803], index=dates) assert_series_equal(result, expected, obj="Logarithmic returns", atol=1e-5) result = returns(x, 1, Returns.ABSOLUTE) expected = pd.Series([np.nan, 1.0, 2.02, -2.0604, 0.0, 2.019192], index=dates) assert_series_equal(result, expected, obj="Absolute returns", atol=1e-5) result = returns(x, 2, Returns.ABSOLUTE) expected = pd.Series([np.nan, np.nan, 3.02, -0.0404, -2.0604, 2.019192], index=dates) assert_series_equal(result, expected, obj="Absolute returns", atol=1e-5) result = returns(x, '2d', Returns.ABSOLUTE) expected = pd.Series([np.nan, np.nan, 3.02, -0.0404, -2.0604, 2.019192], index=pd.DatetimeIndex(dates)) assert_series_equal(result, expected, obj="Absolute returns with relative date") with pytest.raises(MqValueError): returns(x, 1, "None") def test_prices(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 5), dt.date(2019, 1, 6), ] r = pd.Series(dtype=float) assert_series_equal(r, prices(r)) r = pd.Series([np.nan, 0.01, 0.02, -0.02, 0.0, 0.02], index=dates) result = prices(r) expected = pd.Series([1.0, 1.01, 1.0302, 1.009596, 1.009596, 1.02978792], index=dates) assert_series_equal(result, expected, obj="Simple price series default") result = prices(r, 100) expected = pd.Series([100.0, 101, 103.02, 100.9596, 100.9596, 102.978792], index=dates) assert_series_equal(result, expected, obj="Simple price series default") result = prices(r, 100, Returns.SIMPLE) expected = pd.Series([100.0, 101, 103.02, 100.9596, 100.9596, 102.978792], index=dates) assert_series_equal(result, expected, obj="Simple price series") r = pd.Series([np.nan, 0.009950, 0.019803, -0.020203, 0.0, 0.019803], index=dates) result = prices(r, 100, Returns.LOGARITHMIC) expected = pd.Series([100.0, 101, 103.02, 100.9596, 100.9596, 102.978792], index=dates) assert_series_equal(result, expected, obj="Logarithmic prices series") r = pd.Series([np.nan, 1.0, 2.02, -2.0604, 0.0, 2.019192], index=dates) result = prices(r, 100, Returns.ABSOLUTE) expected = pd.Series([100.0, 101, 103.02, 100.9596, 100.9596, 102.978792], index=dates) assert_series_equal(result, expected, obj="Absolute prices series") with pytest.raises(MqValueError): prices(r, 1, "None") def test_index(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 5), dt.date(2019, 1, 6), ] x = pd.Series([200, 202, 201, 203, 202, 201], index=dates) result = index(x) expected = pd.Series([1.000, 1.010, 1.005, 1.015, 1.010, 1.005], index=dates) assert_series_equal(result, expected, obj="Index series") with pytest.raises(MqValueError): x = pd.Series(range(6), index=dates) result = index(x) def test_change(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 5), dt.date(2019, 1, 6), ] x = pd.Series([200, 202, 201, 203, 202, 201.5], index=dates) result = change(x) expected = pd.Series([0.0, 2.0, 1.0, 3.0, 2.0, 1.5], index=dates) assert_series_equal(result, expected, obj="Change of series", check_series_type=False) def test_annualize(): daily_dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 1), dt.date(2019, 1, 1), ] daily_series = pd.Series([0.01, 0.02, -0.01], index=daily_dates) with pytest.raises(MqValueError): annualize(daily_series) daily_dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 5), dt.date(2019, 1, 6), ] daily_series = pd.Series([0.01, 0.02, -0.01, 0.03, 0, -0.01], index=daily_dates) result = annualize(daily_series) assert_series_equal(result, daily_series * math.sqrt(252), obj="Annualize daily") weekly_dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 8), dt.date(2019, 1, 15), dt.date(2019, 1, 22), dt.date(2019, 1, 29), dt.date(2019, 2, 6), ] weekly_series = pd.Series([0.01, 0.02, -0.01, 0.03, 0, -0.01], index=weekly_dates) result = annualize(weekly_series) assert_series_equal(result, weekly_series * math.sqrt(52), obj="Annualize weekly") semi_monthly_dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 15), dt.date(2019, 2, 1), dt.date(2019, 2, 15), dt.date(2019, 3, 1), dt.date(2019, 3, 15), ] semi_monthly_series = pd.Series([0.01, 0.02, -0.01, 0.03, 0, -0.01], index=semi_monthly_dates) result = annualize(semi_monthly_series) assert_series_equal(result, semi_monthly_series * math.sqrt(26), obj="Annualize semi-monthly") monthly_dates = [ dt.date(2019, 1, 1), dt.date(2019, 2, 1), dt.date(2019, 3, 1), dt.date(2019, 4, 1), dt.date(2019, 5, 1), dt.date(2019, 6, 1), ] monthly_series = pd.Series([0.01, 0.02, -0.01, 0.03, 0, -0.01], index=monthly_dates) result = annualize(monthly_series) assert_series_equal(result, monthly_series * math.sqrt(12), obj="Annualize monthly") quarterly_dates = [ dt.date(2019, 1, 1), dt.date(2019, 3, 1), dt.date(2019, 6, 1), dt.date(2019, 9, 1), dt.date(2020, 1, 1), dt.date(2020, 3, 1), ] quarterly_series = pd.Series([0.01, 0.02, -0.01, 0.03, 0, -0.01], index=quarterly_dates) result = annualize(quarterly_series) assert_series_equal(result, quarterly_series * math.sqrt(4), obj="Annualize quarterly") annual_dates = [ dt.date(2019, 1, 1), dt.date(2020, 1, 1), dt.date(2021, 1, 1), dt.date(2022, 1, 1), dt.date(2023, 1, 1), dt.date(2024, 1, 1), ] annual_series = pd.Series([0.01, 0.02, -0.01, 0.03, 0, -0.01], index=annual_dates) result = annualize(annual_series) assert_series_equal(result, annual_series, obj="Annualize annually") invalid_dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 3), dt.date(2019, 1, 6), dt.date(2019, 1, 9), dt.date(2019, 1, 12), dt.date(2019, 1, 13), ] invalid_series = pd.Series([0.01, 0.02, -0.01, 0.03, 0, -0.01], index=invalid_dates) with pytest.raises(MqValueError): annualize(invalid_series) def test_volatility(): x = pd.Series(dtype=float) assert_series_equal(x, volatility(x)) daily_dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 5), dt.date(2019, 1, 6), ] x = pd.Series([100.0, 101, 103.02, 100.9596, 100.9596, 102.978792], index=daily_dates) std = 0.016733200530681527 vol = std * math.sqrt(252) * 100 real_vol = volatility(x) assert real_vol.iloc[-1] == vol vol_already_returns = volatility(returns(x), returns_type=None) assert_series_equal(vol_already_returns, real_vol, obj="Volatility strdate") result = volatility(x, w="3d") expected = pd.Series([33.04542, 31.74902, 31.74902], index=daily_dates[3:]) assert_series_equal(result, expected, obj="Volatility strdate") result = volatility(x, w="3m") expected = pd.Series(dtype=float) assert_series_equal(pd.Series(dtype=float), expected, obj="Volatility strdate too large for series") def test_volatility_assume_zero_mean(): dates = pd.date_range(start='2020-01-01', periods=50, freq='D') prices = pd.Series( [ 100, 102, 101, 103, 102, 104, 103, 105, 104, 106, 105, 107, 106, 108, 107, 109, 108, 110, 109, 111, 110, 112, 111, 113, 112, 114, 113, 115, 114, 116, 115, 117, 116, 118, 117, 119, 118, 120, 119, 121, 120, 122, 121, 123, 122, 124, 123, 125, 124, 126, ], index=dates, ) window_size = 10 # zero-mean should give different result than standard volatility vol_zero_mean = volatility(prices, w=window_size, assume_zero_mean=True) vol_standard = volatility(prices, w=window_size, assume_zero_mean=False) assert not np.allclose(vol_zero_mean.dropna(), vol_standard.dropna()), ( "Zero-mean and standard volatility should differ" ) # manual calc check result = volatility(prices, w=window_size, returns_type=Returns.LOGARITHMIC, assume_zero_mean=True) log_returns = np.log(prices / prices.shift(1)).dropna() last_window_returns = log_returns.iloc[-window_size:].values expected_vol = np.sqrt(np.mean(last_window_returns**2)) * np.sqrt(252) * 100 assert abs(result.iloc[-1] - expected_vol) < 0.01, ( f"Manual calculation mismatch: {result.iloc[-1]} vs {expected_vol}" ) # single value window vol_single = volatility(prices, w=1, assume_zero_mean=True) assert not vol_single.empty, "Should handle window size of 1" # full series (no window) vol_full = volatility(prices, returns_type=Returns.LOGARITHMIC, assume_zero_mean=True) all_returns = np.log(prices / prices.shift(1)).dropna().values expected_full = np.sqrt(np.mean(all_returns**2)) * np.sqrt(252) * 100 assert abs(vol_full.iloc[-1] - expected_full) < 0.01, ( f"Full series calculation incorrect: {vol_full.iloc[-1]} vs {expected_full}" ) def test_volatility_annualization_factor(): rng = np.random.default_rng(100) dates = pd.date_range(start='2025-01-01', periods=252, freq='B') # Generate returns with ~20% annual volatility daily_vol = 0.20 / np.sqrt(252) returns = rng.normal(0, daily_vol, 252) prices = pd.Series(100 * np.exp(np.cumsum(returns)), index=dates) vol_auto = volatility(prices, returns_type=Returns.LOGARITHMIC) vol_252 = volatility(prices, returns_type=Returns.LOGARITHMIC, annualization_factor=252) vol_255 = volatility(prices, returns_type=Returns.LOGARITHMIC, annualization_factor=255) assert_series_equal(vol_auto, vol_252) expected_ratio_255 = np.sqrt(255 / 252) actual_ratio_255 = vol_255.iloc[-1] / vol_252.iloc[-1] np.testing.assert_almost_equal(actual_ratio_255, expected_ratio_255, decimal=5) vol_swap = volatility(prices, returns_type=Returns.LOGARITHMIC, annualization_factor=255, assume_zero_mean=True) assert not np.allclose(vol_swap.iloc[-1], vol_255.iloc[-1]), ( "Zero-mean volatility should differ from standard volatility" ) final_vol = vol_252.iloc[-1] assert 15 < final_vol < 25, "Volatility should be around 20%" def test_vol_swap_volatility(): rng = np.random.default_rng(100) dates = pd.date_range(start='2025-01-01', periods=50, freq='D') returns = rng.normal(0, 0.01, 49) prices = pd.Series(100 * np.exp(np.cumsum(np.concatenate([[0], returns]))), index=dates) # ramp-up must be w - 1 with pytest.raises(MqTypeError): vol_swap_volatility(prices, n_days=Window(22, 0)) for n_days in [3, 25, 49, 50]: # Windows and numbers get treated identically pd.testing.assert_series_equal( vol_swap_volatility(prices, n_days=n_days), vol_swap_volatility(prices, n_days=Window(n_days, n_days - 1)) ) result_wrapper = vol_swap_volatility(prices, n_days=22) result_direct = volatility(prices, Window(22, 21), Returns.LOGARITHMIC, 252, True) assert_series_equal(result_wrapper, result_direct) result_wrapper = vol_swap_volatility(prices, n_days=22, annualization_factor=365) result_direct = volatility(prices, Window(22, 21), Returns.LOGARITHMIC, 365, True) assert_series_equal(result_wrapper, result_direct) result_wrapper = vol_swap_volatility(prices, n_days=22, assume_zero_mean=False) result_direct = volatility(prices, Window(22, 21), Returns.LOGARITHMIC, 252, False) assert_series_equal(result_wrapper, result_direct) result_wrapper = vol_swap_volatility(prices) result_direct = volatility(prices, Window(50, 49), Returns.LOGARITHMIC, 252, True) assert_series_equal(result_wrapper, result_direct) def test_correlation(): x = pd.Series(dtype=float) assert_series_equal(pd.Series(dtype=float), correlation(x, x)) assert_series_equal(pd.Series(dtype=float), correlation(x, x, 1)) daily_dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 7), dt.date(2019, 1, 8), ] x = pd.Series([100.0, 101, 103.02, 100.9596, 100.9596, 102.978792], index=daily_dates) y = pd.Series([100.0, 101, 103.02, 100.9596, 100.9596, 102.978792], index=daily_dates) result = correlation(x, y) expected = pd.Series([np.nan, np.nan, 1.0, 1.0, 1.0, 1.0], index=daily_dates) assert_series_equal(result, expected) y = pd.Series([100.0, 102.0, 104.0, 101.0, 100.95, 100.0], index=daily_dates) result = correlation(x, y) expected = pd.Series([np.nan, np.nan, -1.0, 0.969025, 0.969254, 0.706042], index=daily_dates) assert_series_equal(result, expected) result = correlation(x, y, Window(2, 0)) expected = pd.Series( [np.nan, np.nan, -1.0000000000000435, 1.0, 0.9999999999999994, -1.0000000000000007], index=daily_dates ) assert_series_equal(result, expected) ret_x = returns(x) ret_y = returns(y) result = correlation(ret_x, ret_y, Window(2, 0), False) values = [np.nan, np.nan, -1.0000000000000435, 1.0, 0.9999999999999994, -1.0000000000000007] expected = pd.Series(values, index=daily_dates) assert_series_equal(result, expected) result = correlation(x, y, Window('2d', 0)) expected = pd.Series([np.nan, np.nan, -1.0, 1.0, np.nan, -1.0], index=daily_dates) assert_series_equal(result, expected) result = correlation(x, y, "2d") expected = pd.Series([-1, 1, np.nan, -1], index=daily_dates[2:]) assert_series_equal(result, expected, obj="Correlation strdate as window") result = correlation(x, y, "3m") expected = pd.Series(dtype=float, index=[]) assert_series_equal(result, expected, obj="Correlation strdate as window with too large of window") def test_correlation_returns(): x = generate_series(50, Direction.END_TODAY) y = generate_series(50, Direction.END_TODAY) base_corr = correlation(x, y) corr_simple = correlation(x, y, returns_type=Returns.SIMPLE) assert_series_equal(base_corr, corr_simple) # Log returns corr_log = correlation(x, y, returns_type=Returns.LOGARITHMIC) returns_x = returns(x, type=Returns.LOGARITHMIC) returns_y = returns(y, type=Returns.LOGARITHMIC) corr_log_manual = correlation(returns_x, returns_y, type_=SeriesType.RETURNS) assert_series_equal(corr_log, corr_log_manual) # Mixed returns type corr_log_abs = correlation(x, y, returns_type=(Returns.LOGARITHMIC, Returns.ABSOLUTE)) returns_y = returns(y, type=Returns.ABSOLUTE) corr_log_manual = correlation(returns_x, returns_y, type_=SeriesType.RETURNS) assert_series_equal(corr_log_abs, corr_log_manual) # Error cases with pytest.raises(MqValueError): correlation(x, y, returns_type=[Returns.SIMPLE]) with pytest.raises(MqTypeError): correlation(x, y, returns_type=["simple", "logarithmic"]) def test_corr_swap_correlation(): assert corr_swap_correlation(pd.Series(), pd.Series()).empty num_dates = 10 dates = pd.to_datetime(pd.date_range(start='2025-01-01', periods=num_dates)) rng = np.random.default_rng() x = pd.Series(rng.random(num_dates), index=dates) y = pd.Series(rng.random(num_dates), index=dates) # ramp-up must be w - 1 with pytest.raises(MqTypeError): corr_swap_correlation(x, y, n_days=Window(3, 0)) for n_days in [3, 5, 9, 10]: # Windows and numbers get treated identically pd.testing.assert_series_equal( corr_swap_correlation(x, y, n_days=n_days), corr_swap_correlation(x, y, n_days=Window(n_days - 1, n_days - 2)), ) # n_days left unspecified defaults to the number of price dates pd.testing.assert_series_equal( corr_swap_correlation(x, y, n_days=num_dates), corr_swap_correlation( x, y, ), ) for window_size in [3, 5, 9]: # assume_zero_mean=False should match correlation function corr = correlation(x, y, returns_type=Returns.LOGARITHMIC, w=Window(window_size, window_size - 1)) corr_swap_corr = corr_swap_correlation(x, y, n_days=window_size + 1, assume_zero_mean=False) pd.testing.assert_series_equal(corr, corr_swap_corr) # assume_zero_mean=True yields correct numbers that are different from assume_zero_mean=False zero_mean_corr_swap_corr = corr_swap_correlation(x, y, n_days=window_size + 1) assert not np.allclose(corr_swap_corr.values, zero_mean_corr_swap_corr.values, rtol=1e-10) zero_mean_corr_swap_corr = zero_mean_corr_swap_corr.iloc[1:] # drop the first NaN assert np.all(zero_mean_corr_swap_corr.between(-1.0, 1.0)) # manually verify the first valid correlation (originally at index 1, now at iloc[0]) ret_x = np.log(x / x.shift(1)).dropna() ret_y = np.log(y / y.shift(1)).dropna() window_x = ret_x.iloc[0:window_size].values window_y = ret_y.iloc[0:window_size].values denom = len(window_x) var_x = np.sum(window_x**2) / denom var_y = np.sum(window_y**2) / denom covar = np.sum(window_x * window_y) / denom if var_x > 0 and var_y > 0: expected_zero_mean_corr = covar / (np.sqrt(var_x) * np.sqrt(var_y)) actual_zero_mean_corr = zero_mean_corr_swap_corr.iloc[0] np.testing.assert_almost_equal( actual_zero_mean_corr, expected_zero_mean_corr, decimal=10, err_msg=f"Zero-mean correlation mismatch for window_size={window_size}", ) def test_beta(): x = pd.Series(dtype=float) assert_series_equal(pd.Series(dtype=float), beta(x, x)) assert_series_equal(pd.Series(dtype=float), beta(x, x, 1)) daily_dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 7), dt.date(2019, 1, 8), ] x = pd.Series([100.0, 101, 103.02, 100.9596, 100.9596, 102.978792], index=daily_dates) y = pd.Series([100.0, 101, 103.02, 100.9596, 100.9596, 102.978792], index=daily_dates) result = beta(x, y) expected = pd.Series([np.nan, np.nan, np.nan, 1.0, 1.0, 1.0], index=daily_dates) assert_series_equal(result, expected) y = pd.Series([100.0, 102.0, 104.0, 101.0, 100.95, 100.0], index=daily_dates) result = beta(x, y) expected = pd.Series([np.nan, np.nan, np.nan, 0.718146, 0.718919, 0.572201], index=daily_dates) assert_series_equal(result, expected) result = beta(x, y, Window(2, 0)) expected = pd.Series( [np.nan, np.nan, np.nan, 0.8255252918287954, 0.7054398925453326, -2.24327163719368], index=daily_dates ) assert_series_equal(result, expected) ret_x = returns(x) ret_y = returns(y) result = beta(ret_x, ret_y, Window(2, 0), False) expected = pd.Series( [np.nan, np.nan, np.nan, 0.8255252918287954, 0.7054398925453326, -2.24327163719368], index=daily_dates ) assert_series_equal(result, expected) result = beta(x, y, Window('2d', 0)) expected = pd.Series([np.nan, np.nan, np.nan, 0.8255252918287954, np.nan, -2.24327163719368], index=daily_dates) assert_series_equal(result, expected) result = beta(x, y, '2d') expected = pd.Series([np.nan, 0.8255252918287954, np.nan, -2.24327163719368], index=daily_dates[2:]) assert_series_equal(result, expected, obj="beta with strdate window") with pytest.raises(MqTypeError): beta(x, y, '2d', 1) def test_max_drawdown(): daily_dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 7), dt.date(2019, 1, 8), ] daily_dates = pd.to_datetime(daily_dates) series = pd.Series([1, 5, 5, 4, 4, 1], index=daily_dates) result = max_drawdown(series) expected = pd.Series([0.0, 0.0, 0.0, -0.2, -0.2, -0.8], index=daily_dates) assert_series_equal(result, expected, obj="Max drawdown") result = max_drawdown(series, Window(2, 0)) expected = pd.Series([0.0, 0.0, 0.0, -0.2, -0.2, -0.75], index=daily_dates) assert_series_equal(result, expected, obj="Max drawdown window 2") with pytest.raises(TypeError): max_drawdown(pd.Series([1, 5, 5, 4, 4, 1]), Window('2d', 0)) result = max_drawdown(series, Window('2d', 0)) expected = pd.Series([0.0, 0.0, 0.0, -0.2, 0.0, -0.75], index=daily_dates) assert_series_equal(result, expected, obj="Max drawdown window 2d") def test_excess_returns(): replace = Replacer() file = os.path.join(os.path.dirname(__file__), '..', 'resources', 'MIDASER_SPX_USD.csv') df = pd.read_csv(file) df.index = pd.to_datetime(df['Date']) market_data = replace('gs_quant.timeseries.econometrics.GsDataApi.get_market_data', Mock()) data = df.loc[:, ['USD']] data = data.rename(columns={'USD': 'spot'}) market_data.return_value = data with pytest.raises(Exception): excess_returns(df['SPX'], CurrencyEnum.AED) actual = excess_returns(df['SPX'], CurrencyEnum.USD) expected = df['MIDASER'] assert_series_equal(actual, expected, check_names=False) plot = excess_returns_(df['SPX'], RiskFreeRateCurrency.USD) assert_series_equal(plot, expected, check_names=False) actual = excess_returns(df['SPX'], Cash('MABCDE', 'T_SHARPE_USD')) assert_series_equal(actual, expected, check_names=False) actual = excess_returns(df['SPX'], 0.0175) file = os.path.join(os.path.dirname(__file__), '..', 'resources', 'Sharpe_SPX_0175.csv') expected = pd.read_csv(file).loc[:, 'ER'] np.testing.assert_array_almost_equal(actual.values, expected.values) market_data.return_value = pd.DataFrame() with pytest.raises(MqError): excess_returns(df['SPX'], CurrencyEnum.USD) replace.restore() def test_sharpe_ratio(): file = os.path.join(os.path.dirname(__file__), '..', 'resources', 'MIDASER_SPX_USD.csv') price_df = pd.read_csv(file) price_df.index = pd.to_datetime(price_df['Date']) file = os.path.join(os.path.dirname(__file__), '..', 'resources', 'Sharpe_SPX_0175.csv') er_df = pd.read_csv(file) er_df.index = pd.to_datetime(er_df['Date']) replace = Replacer() er = replace('gs_quant.timeseries.econometrics.excess_returns', Mock()) er.return_value = er_df['ER'] actual = _get_ratio(price_df['SPX'], 0.0175, 0, day_count_convention=DayCountConvention.ACTUAL_360) np.testing.assert_almost_equal(actual.values, er_df['SR'].values, decimal=5) actual = sharpe_ratio(price_df['SPX'], RiskFreeRateCurrency.USD) np.testing.assert_almost_equal(actual.values, er_df['SR'].values, decimal=5) actual = sharpe_ratio(price_df['SPX'][:], RiskFreeRateCurrency.USD, '1m') expected = pd.Series( [np.nan, np.nan, np.nan, 8.266434, 6.731811], index=pd.date_range(dt.date(2019, 2, 4), periods=5) ) np.testing.assert_almost_equal(actual[:5].values, expected.values, decimal=5) with pytest.raises(MqValueError): actual = sharpe_ratio(price_df['SPX'][:], RiskFreeRateCurrency.USD, 22, method=Interpolate.INTERSECT) replace.restore() actual = _get_ratio(price_df['SPX'], 0.0175, 10, day_count_convention=DayCountConvention.ACTUAL_360) np.testing.assert_almost_equal(actual.values, er_df['SR10'].values[10:], decimal=5) actual = _get_ratio( er_df['ER'], 0.0175, 0, day_count_convention=DayCountConvention.ACTUAL_360, curve_type=CurveType.EXCESS_RETURNS ) np.testing.assert_almost_equal(actual.values, er_df['SR'].values, decimal=5) if __name__ == "__main__": pytest.main(args=["test_econometrics.py"]) ================================================ FILE: gs_quant/test/timeseries/test_helper.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from enum import IntEnum from unittest import mock from unittest.mock import MagicMock from unittest.mock import Mock import pandas as pd import pytest from testfixtures import Replace, Replacer import gs_quant.timeseries as ts from gs_quant.data import DataContext from gs_quant.data import Dataset from gs_quant.errors import MqError, MqRequestError from gs_quant.session import GsSession from gs_quant.test.api.test_thread_manager import NullContextManager from gs_quant.timeseries.helper import ( _create_int_enum, _tenor_to_month, _month_to_tenor, plot_function, plot_measure, plot_method, normalize_window, Window, apply_ramp, check_forward_looking, get_df_with_retries, get_dataset_data_with_retries, _split_where_conditions, get_dataset_with_many_assets, ) # TODO test the instance of IntEnum when we have any. WeekDay = _create_int_enum( 'WeekDay', {'SUNDAY': 1, 'Monday': 2, 'TUESDAY': 3, 'WEDNESDAY': 4, 'THURSDAY': 5, 'Friday': 6, 'SATURDAY': 7} ) def test_int_enum(): assert ['a', 'b', 'c'][WeekDay.MONDAY] == 'c' assert len(list(WeekDay)) == 7 assert len(WeekDay) == 7 target = 'SUNDAY MONDAY TUESDAY WEDNESDAY THURSDAY FRIDAY SATURDAY' target = target.split() for i, weekday in enumerate(target, 1): e = WeekDay(i) assert isinstance(e, IntEnum) assert e.name == weekday assert e.value == i def test_tenor_to_month(): with pytest.raises(MqError): _tenor_to_month('1d') with pytest.raises(MqError): _tenor_to_month('2w') assert _tenor_to_month('3m') == 3 assert _tenor_to_month('4y') == 48 def test_month_to_tenor(): assert _month_to_tenor(36) == '3y' assert _month_to_tenor(18) == '18m' def test_get_dataset_with_many_assets(): # Mock the Dataset object mock_dataset = Mock(spec=Dataset) def foo(**kwargs) -> pd.DataFrame: return pd.DataFrame({"assetId": list(kwargs["assetId"]), "column2": [0] * len(kwargs["assetId"])}) mock_dataset.get_data = foo # Test parameters assets = [f'Asset{i}' for i in range(150)] start = dt.date(2023, 1, 1) end = dt.date(2023, 1, 31) # Call the function and inspect the result with ( mock.patch("gs_quant.api.utils.DataContext", MagicMock(spec=DataContext)) as mock_data_context, mock.patch("gs_quant.api.utils.GsSession", MagicMock(spec=GsSession)) as mock_gs_session, ): mock_data_context.current = mock_data_context mock_gs_session.current = mock_gs_session result = get_dataset_with_many_assets(ds=mock_dataset, assets=assets, start=start, end=end, batch_limit=100) # Assertions assert len(result) == 150 assert result['assetId'].tolist() == assets @plot_function def pf(): pass @plot_measure(('xyz',), asset_type=('abc',)) def pm(): pass @plot_method def pmt(arg): return arg def test_decorators(): assert pf.plot_function assert pm.plot_measure assert pmt.plot_method assert pm.asset_class == ('xyz',) assert pm.asset_type == ('abc',) assert pm.asset_type_excluded is None assert pmt(1, real_time=True) == 1 def test_normalize_window_defaults_window_if_none_passed(): x = ts.generate_series(10) w = normalize_window(x, None) assert w.w == 10 assert w.r == 0 def test_normalize_window_defaults_window_if_passed(): x = ts.generate_series(10) w = normalize_window(x, None, default_window=2) assert w.w == 2 assert w.r == 0 def test_normalize_window_handles_int(): x = ts.generate_series(10) w = normalize_window(x, 5) assert w.w == 5 assert w.r == 5 def test_normalize_window_handles_window_with_no_ramp(): x = ts.generate_series(10) w = normalize_window(x, Window(2, None)) assert w.w == 2 assert w.r == 2 def test_normalize_window_handles_window_with_no_size(): x = ts.generate_series(10) w = normalize_window(x, Window(None, 2)) assert w.w == 10 assert w.r == 2 def test_normalize_window_handles_ramp_greater_than_series_length(): with pytest.raises(ValueError): x = ts.generate_series(10) normalize_window(x, Window(2, 11)) def test_normalize_window_raises_error_on_window_of_size_zero(): with pytest.raises(ValueError): x = ts.generate_series(10) normalize_window(x, 0) with pytest.raises(ValueError): x = ts.generate_series(10) normalize_window(x, Window(0, 0)) def test_normalize_window_handles_ramp_of_size_zero(): x = ts.generate_series(10) w = normalize_window(x, Window(2, 0)) assert w.w == 2 assert w.r == 0 def test_normalize_window_str(): x = ts.generate_series(10) w = normalize_window(x, Window('1w', '2d')) assert w.w == pd.DateOffset(weeks=1) assert w.r == pd.DateOffset(days=2) def test_normalize_window_single_str(): x = ts.generate_series(10) w = normalize_window(x, "2d") assert w.w == pd.DateOffset(days=2) assert w.r == pd.DateOffset(days=2) x = ts.generate_series(90) w = normalize_window(x, "3m") assert w.w == pd.DateOffset(months=3) assert w.r == pd.DateOffset(months=3) x = ts.generate_series(10) w = normalize_window(x, "1h") assert w.w == pd.DateOffset(hours=1) assert w.r == pd.DateOffset(hours=1) def test_apply_ramp(): x = ts.generate_series(10) y = apply_ramp(x, Window(2, 2)) assert len(y) == 8 def test_apply_ramp_with_window_greater_than_series_length(): x = ts.generate_series(10) y = apply_ramp(x, Window(11, 2)) assert len(y) == 0 def test_apply_ramp_dateoffset(): x = pd.Series(range(10), index=pd.bdate_range('2020-02-17', freq='B', periods=10)) y = apply_ramp(x, Window(pd.DateOffset(weeks=1), pd.DateOffset(days=1))) assert len(y) == 9 def test_apply_ramp_raises_on_edge_cases(): with pytest.raises(ValueError): x = ts.generate_series(10) apply_ramp(x, Window(0, 0)) with pytest.raises(ValueError): x = ts.generate_series(10) apply_ramp(x, Window(-1, 0)) with pytest.raises(ValueError): x = ts.generate_series(10) apply_ramp(x, Window(2, -1)) with pytest.raises(ValueError): x = ts.generate_series(10) apply_ramp(x, Window(2, 11)) def test_get_df_with_retries(): start = dt.date(2020, 12, 1) end = dt.date(2020, 12, 2) counter = 0 def fetch0(): return pd.DataFrame() def fetch1(): nonlocal counter counter += 1 if counter > 1: return pd.DataFrame([['foo'], ['bar']]) return pd.DataFrame() def mock_apply(self, *_args, **_kwargs): return self.base_date - dt.timedelta(days=1) with Replace('gs_quant.timeseries.helper.RelativeDate.apply_rule', mock_apply): df = get_df_with_retries(fetch0, start, end, 'NYSE') assert df.empty df = get_df_with_retries(fetch1, start, end, 'NYSE', 0) assert df.empty df = get_df_with_retries(fetch1, start, end, 'NYSE', 1) assert not df.empty df = get_df_with_retries(fetch1, start, end, 'NYSE', 2) assert not df.empty def test_forward_looking(): today = dt.date.today() source = 'plottool' with DataContext(today, today + dt.timedelta(days=1)): assert check_forward_looking('1b', None) is None assert check_forward_looking(today, None) is None assert check_forward_looking(None, None) is None assert check_forward_looking('1b', source) is None assert check_forward_looking(today, source) is None assert check_forward_looking(None, source) is None with DataContext(today - dt.timedelta(days=1), today): assert check_forward_looking('1b', None) is None assert check_forward_looking(today, None) is None assert check_forward_looking(None, None) is None assert check_forward_looking('1b', source) is None assert check_forward_looking(today, source) is None with pytest.raises(MqError): check_forward_looking(None, source) def test_get_dataset_data_with_retries(): replace = Replacer() GsSession.current = MagicMock(return_calue=NullContextManager()) mock = replace('gs_quant.data.dataset.Dataset.get_data', Mock()) mock.side_effect = [ MqRequestError(400, message='Some other error'), MqRequestError(400, message='Some other error'), MqRequestError(400, message='Some other error'), ] dataset = Dataset(Dataset.TR.TREOD) with pytest.raises(MqRequestError): get_dataset_data_with_retries( dataset, start=dt.date(2000, 1, 2), end=dt.date(2019, 1, 9), assetId='MA4B66MW5E27U8P32SB', max_retries=0 ) replace.restore() def test_split_where_conditions(): where = dict(tenor=['1m', '2m'], strikeReference='delta_call', relativeStrike=25) expected = [ dict(tenor=['1m'], strikeReference='delta_call', relativeStrike=25), dict(tenor=['2m'], strikeReference='delta_call', relativeStrike=25), ] actual = _split_where_conditions(where) assert actual == expected where = dict(tenor='1m', strikeReference='delta_call', relativeStrike=25) expected = [dict(tenor='1m', strikeReference='delta_call', relativeStrike=25)] actual = _split_where_conditions(where) assert actual == expected where = dict() expected = [dict()] actual = _split_where_conditions(where) assert actual == expected where = dict(tenor=['1m', '2m'], strikeReference='delta_call', relativeStrike=[25, 50]) expected = [ dict(tenor=['1m'], strikeReference='delta_call', relativeStrike=[25]), dict(tenor=['1m'], strikeReference='delta_call', relativeStrike=[50]), dict(tenor=['2m'], strikeReference='delta_call', relativeStrike=[25]), dict(tenor=['2m'], strikeReference='delta_call', relativeStrike=[50]), ] actual = _split_where_conditions(where) assert actual == expected if __name__ == "__main__": pytest.main(args=["test_helper.py"]) def test_get_dataset_data_with_retries_recursive_split(): dataset = Mock() dataset.get_data = Mock( side_effect=[ MqRequestError(400, message="Some error occurred"), pd.DataFrame({"data": [1, 2]}), pd.DataFrame({"data": [3, 4]}), ] ) start = dt.date(2023, 1, 1) end = dt.date(2023, 1, 10) result = get_dataset_data_with_retries(dataset, start=start, end=end, max_retries=2) # Verify the recursive splitting logic assert not result.empty assert len(result) == 4 dataset.get_data.assert_called() ================================================ FILE: gs_quant/test/timeseries/test_measures.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import json import os from typing import Union, Dict from unittest import mock from unittest.mock import patch import numpy as np import pandas as pd import pytest from numpy.testing import assert_equal from pandas.testing import assert_series_equal from pandas.tseries.offsets import CustomBusinessDay from testfixtures import Replacer from testfixtures.mock import Mock, MagicMock import gs_quant.timeseries.measures as tm import gs_quant.timeseries.measures_rates as tm_rates from gs_quant.api.gs.assets import GsTemporalXRef, GsAssetApi, GsIdType, IdList, GsAsset from gs_quant.api.gs.data import GsDataApi, MarketDataResponseFrame from gs_quant.api.gs.data import QueryType from gs_quant.common import AssetType, XRef, PricingLocation, Currency as CurrEnum from gs_quant.data.core import DataContext from gs_quant.errors import MqError, MqValueError, MqTypeError from gs_quant.json_encoder import JSONEncoder from gs_quant.markets import PricingContext from gs_quant.markets.baskets import Basket as CustomBasket from gs_quant.markets.index import Index from gs_quant.markets.securities import ( AssetClass, Cross, Currency, SecurityMaster, Stock, Swap, CommodityNaturalGasHub, CommodityEUNaturalGasHub, AssetIdentifier, CommodityPowerAggregatedNodes, FutureMarket, DefaultSwap, ) from gs_quant.session import GsSession, Environment, OAuth2Session from gs_quant.test.timeseries.utils import mock_request from gs_quant.timeseries import Returns, ExtendedSeries, BenchmarkType _index = [pd.Timestamp('2019-01-01')] _index2 = [pd.Timestamp('2019-08-02')] _test_datasets = ('TEST_DATASET',) _test_datasets2 = ('TEST_DATASET2',) _test_datasets_rt = ('TEST_DATASET_RT',) def _make_index(dates, name=None): idx = pd.DatetimeIndex(dates) if hasattr(idx, 'as_unit'): idx = idx.as_unit('ns') if name is not None: idx.name = name return idx def mock_empty_market_data_response(): df = MarketDataResponseFrame() df.dataset_ids = () return df def map_identifiers_default_mocker( input_type: Union[GsIdType, str], output_type: Union[GsIdType, str], ids: IdList, as_of: dt.datetime = None, multimap: bool = False, limit: int = None, **kwargs, ) -> dict: if "USD-LIBOR-BBA" in ids: return {"USD-LIBOR-BBA": "MAPDB7QNB2TZVQ0E"} elif "EUR-EURIBOR-TELERATE" in ids: return {"EUR-EURIBOR-TELERATE": "MAJNQPFGN1EBDHAE"} elif "GBP-LIBOR-BBA" in ids: return {"GBP-LIBOR-BBA": "MAFYB8Z4R1377A19"} elif "JPY-LIBOR-BBA" in ids: return {"JPY-LIBOR-BBA": "MABMVE27EM8YZK33"} elif "EUR OIS" in ids: return {"EUR OIS": "MARFAGXDQRWM07Y2"} def map_identifiers_ois_mocker( input_type: Union[GsIdType, str], output_type: Union[GsIdType, str], ids: IdList, as_of: dt.datetime = None, multimap: bool = False, limit: int = None, **kwargs, ) -> dict: if "USD OIS" in ids: return {"USD OIS": "MA29GN9VZE0WK56V"} elif "EUR OIS" in ids: return {"EUR OIS": "MARFAGXDQRWM07Y2"} elif "SEK OIS" in ids: return {"SEK OIS": "MARSF2Y8GV5GHXYZ"} elif "AUD OIS" in ids: return {"AUD OIS": "MA2XT17D0P77A84G"} elif "JPY OIS" in ids: return {"JPY OIS": "MAY3XC3279QN7SHW"} elif "GBP OIS" in ids: return {"GBP OIS": "MAGP76C36BX2Q0YA"} elif "NOK OIS" in ids: return {"NOK OIS": "MAS7EYBPYZ4SQ4GG"} elif "CAD OIS" in ids: return {"CAD OIS": "MAX229TKVEZMZ4WT"} elif "NZD OIS" in ids: return {"NZD OIS": "MA48BHGWY71R4SAJ"} elif "CHF OIS" in ids: return {"CHF OIS": "MAK0CRRF9DPMECSJ"} def map_identifiers_swap_rate_mocker( input_type: Union[GsIdType, str], output_type: Union[GsIdType, str], ids: IdList, as_of: dt.datetime = None, multimap: bool = False, limit: int = None, **kwargs, ) -> dict: if "USD-3m" in ids: return {"USD-3m": "MAAXGV0GZTW4GFNC"} elif "EUR-6m" in ids: return {"EUR-6m": "MA5WM2QWRVMYKDK0"} elif "KRW" in ids: return {"KRW": 'MAJ6SEQH3GT0GA2Z'} def map_identifiers_inflation_mocker( input_type: Union[GsIdType, str], output_type: Union[GsIdType, str], ids: IdList, as_of: dt.datetime = None, multimap: bool = False, limit: int = None, **kwargs, ) -> dict: if "CPI-UKRPI" in ids: return {"CPI-UKRPI": "MAQ7ND0MBP2AVVQW"} elif "CPI-CPXTEMU" in ids: return {"CPI-CPXTEMU": "MAK1FHKH5P5GJSHH"} def map_identifiers_cross_basis_mocker( input_type: Union[GsIdType, str], output_type: Union[GsIdType, str], ids: IdList, as_of: dt.datetime = None, multimap: bool = False, limit: int = None, **kwargs, ) -> dict: if "USD-3m/JPY-3m" in ids: return {"USD-3m/JPY-3m": "MA99N6C1KF9078NM"} elif "EUR-3m/USD-3m" in ids: return {"EUR-3m/USD-3m": "MAXPKTXW2D4X6MFQ"} elif "GBP-3m/USD-3m" in ids: return {"GBP-3m/USD-3m": "MA8BZHQV3W32V63B"} def test_currency_to_default_ois_asset(mocker): mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', side_effect=mock_request) mocker.patch.object(GsAssetApi, 'map_identifiers', side_effect=map_identifiers_ois_mocker) asset_id_list = ["MAJNQPFGN1EBDHAE"] correct_mapping = ["MARFAGXDQRWM07Y2"] with PricingContext(dt.date.today()): for i in range(len(asset_id_list)): correct_id = tm.currency_to_default_ois_asset(asset_id_list[i]) assert correct_id == correct_mapping[i] # Test that the same id is returned when a TypeError is raised mocker.patch.object(GsAssetApi, 'map_identifiers', side_effect=TypeError('Test')) assert tm.currency_to_default_ois_asset('MAJNQPFGN1EBDHAE') == 'MAJNQPFGN1EBDHAE' def test_currency_to_default_benchmark_rate(mocker): mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', side_effect=mock_request) mocker.patch.object(GsAssetApi, 'map_identifiers', side_effect=map_identifiers_default_mocker) asset_id_list = [ "MAZ7RWC904JYHYPS", "MAJNQPFGN1EBDHAE", "MA66CZBQJST05XKG", "MAK1FHKH5P5GJSHH", "MA4J1YB8XZP2BPT8", "MA4B66MW5E27U8P32SB", ] correct_mapping = [ "MAPDB7QNB2TZVQ0E", "MAJNQPFGN1EBDHAE", "MAFYB8Z4R1377A19", "MABMVE27EM8YZK33", "MA4J1YB8XZP2BPT8", "MA4B66MW5E27U8P32SB", ] with PricingContext(dt.date.today()): for i in range(len(asset_id_list)): correct_id = tm.currency_to_default_benchmark_rate(asset_id_list[i]) assert correct_id == correct_mapping[i] def test_currency_to_default_swap_rate_asset(mocker): mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', side_effect=mock_request) mocker.patch.object(GsAssetApi, 'map_identifiers', side_effect=map_identifiers_swap_rate_mocker) asset_id_list = ['MAZ7RWC904JYHYPS', 'MAJNQPFGN1EBDHAE', 'MAJ6SEQH3GT0GA2Z'] correct_mapping = ['MAAXGV0GZTW4GFNC', 'MA5WM2QWRVMYKDK0', 'MAJ6SEQH3GT0GA2Z'] with PricingContext(dt.date.today()): for i in range(len(asset_id_list)): correct_id = tm.currency_to_default_swap_rate_asset(asset_id_list[i]) assert correct_id == correct_mapping[i] def test_currency_to_inflation_benchmark_rate(mocker): mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', side_effect=mock_request) mocker.patch.object(GsAssetApi, 'map_identifiers', side_effect=map_identifiers_inflation_mocker) asset_id_list = ["MA66CZBQJST05XKG", "MAK1FHKH5P5GJSHH", "MA4J1YB8XZP2BPT8"] correct_mapping = ["MAQ7ND0MBP2AVVQW", "MAK1FHKH5P5GJSHH", "MA4J1YB8XZP2BPT8"] with PricingContext(dt.date.today()): for i in range(len(asset_id_list)): correct_id = tm.currency_to_inflation_benchmark_rate(asset_id_list[i]) assert correct_id == correct_mapping[i] # Test that the same id is returned when a TypeError is raised mocker.patch.object(GsAssetApi, 'map_identifiers', side_effect=TypeError('Test')) assert tm.currency_to_inflation_benchmark_rate('MA66CZBQJST05XKG') == 'MA66CZBQJST05XKG' def test_cross_to_basis(mocker): mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', side_effect=mock_request) mocker.patch.object(GsAssetApi, 'map_identifiers', side_effect=map_identifiers_cross_basis_mocker) asset_id_list = ["MAYJPCVVF2RWXCES", "MA4B66MW5E27U8P32SB", "nobbid"] correct_mapping = ["MA99N6C1KF9078NM", "MA4B66MW5E27U8P32SB", "nobbid"] with PricingContext(dt.date.today()): for i in range(len(asset_id_list)): correct_id = tm.cross_to_basis(asset_id_list[i]) assert correct_id == correct_mapping[i] # Test that the same id is returned when a TypeError is raised mocker.patch.object(GsAssetApi, 'map_identifiers', side_effect=TypeError('Test')) assert tm.cross_to_basis('MAYJPCVVF2RWXCES') == 'MAYJPCVVF2RWXCES' def test_currency_to_tdapi_swap_rate_asset(mocker): replace = Replacer() mocker.patch.object( GsSession.__class__, 'current', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, '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 PricingContext(dt.date.today()): asset = Currency('MA25DW5ZGC1BSC8Y', 'NOK') bbid_mock.return_value = 'NOK' correct_id = tm_rates._currency_to_tdapi_swap_rate_asset(asset) asset = Currency('MAZ7RWC904JYHYPS', 'USD') bbid_mock.return_value = 'USD' correct_id = tm_rates._currency_to_tdapi_swap_rate_asset(asset) assert 'MAFRSWPAF5QPNTP2' == correct_id bbid_mock.return_value = 'CHF' correct_id = tm_rates._currency_to_tdapi_swap_rate_asset(asset) assert 'MAW25BGQJH9P6DPT' == correct_id bbid_mock.return_value = 'EUR' correct_id = tm_rates._currency_to_tdapi_swap_rate_asset(asset) assert 'MAA9MVX15AJNQCVG' == correct_id bbid_mock.return_value = 'GBP' correct_id = tm_rates._currency_to_tdapi_swap_rate_asset(asset) assert 'MA6QCAP9B7ABS9HA' == correct_id bbid_mock.return_value = 'JPY' correct_id = tm_rates._currency_to_tdapi_swap_rate_asset(asset) assert 'MAEE219J5ZP0ZKRK' == correct_id bbid_mock.return_value = 'SEK' correct_id = tm_rates._currency_to_tdapi_swap_rate_asset(asset) assert 'MAETMVTPNP3199A5' == correct_id bbid_mock.return_value = 'HKD' correct_id = tm_rates._currency_to_tdapi_swap_rate_asset(asset) assert 'MABRNGY8XRFVC36N' == correct_id bbid_mock.return_value = 'NZD' correct_id = tm_rates._currency_to_tdapi_swap_rate_asset(asset) assert 'MAH16NHE1HBN0FBZ' == correct_id bbid_mock.return_value = 'AUD' correct_id = tm_rates._currency_to_tdapi_swap_rate_asset(asset) assert 'MAY8147CRK0ZP53B' == correct_id bbid_mock.return_value = 'CAD' correct_id = tm_rates._currency_to_tdapi_swap_rate_asset(asset) assert 'MANJ8SS88WJ6N28Q' == correct_id bbid_mock.return_value = 'KRW' correct_id = tm_rates._currency_to_tdapi_swap_rate_asset(asset) assert 'MAP55AXG5SQVS6C5' == correct_id bbid_mock.return_value = 'INR' correct_id = tm_rates._currency_to_tdapi_swap_rate_asset(asset) assert 'MA20JHJXN1PD5HGE' == correct_id bbid_mock.return_value = 'CNY' correct_id = tm_rates._currency_to_tdapi_swap_rate_asset(asset) assert 'MA4K1D8HH2R0RQY5' == correct_id bbid_mock.return_value = 'SGD' correct_id = tm_rates._currency_to_tdapi_swap_rate_asset(asset) assert 'MA5CQFHYBPH9E5BS' == correct_id bbid_mock.return_value = 'DKK' correct_id = tm_rates._currency_to_tdapi_swap_rate_asset(asset) assert 'MAF131NKWVRESFYA' == correct_id asset = Currency('MA890', 'PLN') bbid_mock.return_value = 'PLN' assert 'MA890' == tm_rates._currency_to_tdapi_swap_rate_asset(asset) replace.restore() def test_currency_to_tdapi_basis_swap_rate_asset(mocker): replace = Replacer() mocker.patch.object( GsSession.__class__, 'current', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, '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 PricingContext(dt.date.today()): asset = Currency('MA890', 'EGP') bbid_mock.return_value = 'EGP' assert 'MA890' == tm_rates._currency_to_tdapi_basis_swap_rate_asset(asset) asset = Currency('MAZ7RWC904JYHYPS', 'USD') bbid_mock.return_value = 'USD' correct_id = tm_rates._currency_to_tdapi_basis_swap_rate_asset(asset) assert 'MAQB1PGEJFCET3GG' == correct_id bbid_mock.return_value = 'EUR' correct_id = tm_rates._currency_to_tdapi_basis_swap_rate_asset(asset) assert 'MAGRG2VT11GQ2RQ9' == correct_id bbid_mock.return_value = 'GBP' correct_id = tm_rates._currency_to_tdapi_basis_swap_rate_asset(asset) assert 'MAHCYNB3V75JC5Q8' == correct_id bbid_mock.return_value = 'JPY' correct_id = tm_rates._currency_to_tdapi_basis_swap_rate_asset(asset) assert 'MAXVRBEZCJVH0C4V' == correct_id bbid_mock.return_value = 'AUD' correct_id = tm_rates._currency_to_tdapi_basis_swap_rate_asset(asset) assert 'MAY8H7HCNZ85FJKM' == correct_id bbid_mock.return_value = 'NZD' correct_id = tm_rates._currency_to_tdapi_basis_swap_rate_asset(asset) assert 'MAWK15C0P3SM6C7Q' == correct_id bbid_mock.return_value = 'CHF' correct_id = tm_rates._currency_to_tdapi_basis_swap_rate_asset(asset) assert 'MA7ZHB9T0PF1SB96' == correct_id bbid_mock.return_value = 'DKK' correct_id = tm_rates._currency_to_tdapi_basis_swap_rate_asset(asset) assert 'MA2164KK5DMYA561' == correct_id bbid_mock.return_value = 'NOK' correct_id = tm_rates._currency_to_tdapi_basis_swap_rate_asset(asset) assert 'MAPXC5YBPZJZXYMZ' == correct_id bbid_mock.return_value = 'SEK' correct_id = tm_rates._currency_to_tdapi_basis_swap_rate_asset(asset) assert 'MAS2NJCYHDHP8P0X' == correct_id bbid_mock.return_value = 'CAD' correct_id = tm_rates._currency_to_tdapi_basis_swap_rate_asset(asset) assert 'MARVD2E65AWEXXBA' == correct_id 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.sync, '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 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, close_location=None, real_time=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 True except NotImplementedError: assert False try: val = tm_rates.swap_rate_calc(asset, swap_tenor='10x', benchmark_type='SOFR', real_time=True) assert False except MqValueError: assert True try: val = tm_rates.swap_rate_calc(asset, swap_tenor='10y', benchmark_type='SOFR', csa='CME', real_time=True) assert True except MqValueError: assert False try: # rate option with no defaults specified val = tm_rates.swap_rate_calc(asset, swap_tenor='10y', benchmark_type='NGN-LIBOR-SYNTHETIC', real_time=True) assert False except MqValueError: assert True try: # rate option with defaults specified val = tm_rates.swap_rate_calc(asset, swap_tenor='10y', benchmark_type='USD-SOFR-COMPOUND', real_time=True) assert True except MqValueError: assert False replace.restore() def my_mocked_mxapi_measure( cls, curve_type=None, curve_asset=None, curve_point=None, curve_tags=None, measure=None, start_time=None, end_time=None, request_id=None, close_location=None, real_time=None, ): d = {'column_name': [], 'timeStamp': []} df = MarketDataResponseFrame(pd.DataFrame(data=d)) df = df.set_index('timeStamp') return df def test_curve_measures(mocker): replace = Replacer() mocker.patch.object(GsDataApi, 'get_mxapi_curve_measure') bbid_mock = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) mocker.patch.object(GsDataApi, 'get_mxapi_curve_measure', side_effect=my_mocked_mxapi_measure) asset = Currency('MAZ7RWC904JYHYPS', 'USD') bbid_mock.return_value = 'USD' val = tm_rates.forward_rate(asset, forward_term='1m') assert len(val.keys()) == 0 val = tm_rates.discount_factor(asset, tenor='1m') assert len(val.keys()) == 0 val = tm_rates.instantaneous_forward_rate(asset, tenor='1m') assert len(val.keys()) == 0 val = tm_rates.index_forward_rate(asset, forward_start_tenor='1m', benchmark_type='LIBOR') assert len(val.keys()) == 0 try: val = tm_rates.forward_rate(asset) assert False except MqValueError: assert True try: val = tm_rates.discount_factor(asset) assert False except MqValueError: assert True try: val = tm_rates.index_forward_rate(asset) assert False except MqValueError: assert True try: val = tm_rates.instantaneous_forward_rate(asset) assert False except MqValueError: assert True try: # rate option with defaults specified val = tm_rates.index_forward_rate( asset, forward_start_tenor='10y', benchmark_type='USD-SOFR-COMPOUND', real_time=True ) assert True except MqValueError: assert False try: # rate option with no defaults specified val = tm_rates.index_forward_rate( asset, forward_start_tenor='10y', benchmark_type='NGN-LIBOR-SYNTHETIC', real_time=True ) assert False except MqValueError: assert True asset = Currency('MA890', 'EGP') bbid_mock.return_value = 'EGP' val = tm_rates.forward_rate(asset, forward_term='1m') assert len(val.keys()) == 0 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) assert tm_rates._ClearingHouse.LCH == tm_rates._check_clearing_house(None) invalid_ch = ['NYSE'] for ch in invalid_ch: with pytest.raises(MqError): tm_rates._check_clearing_house(ch) def test_get_swap_csa_terms(): euribor_index = tm_rates.CURRENCY_TO_SWAP_RATE_BENCHMARK['EUR'][BenchmarkType.EURIBOR.value] usd_libor_index = tm_rates.CURRENCY_TO_SWAP_RATE_BENCHMARK['USD'][BenchmarkType.LIBOR.value] fed_funds_index = tm_rates.CURRENCY_TO_SWAP_RATE_BENCHMARK['USD'][BenchmarkType.Fed_Funds.value] estr_index = tm_rates.CURRENCY_TO_SWAP_RATE_BENCHMARK['EUR'][BenchmarkType.EUROSTR.value] assert dict(csaTerms='USD-1') == tm_rates._get_swap_csa_terms('USD', fed_funds_index) assert dict(csaTerms='EUR-EuroSTR') == tm_rates._get_swap_csa_terms('EUR', estr_index) assert {} == tm_rates._get_swap_csa_terms('EUR', euribor_index) assert {} == tm_rates._get_swap_csa_terms('USD', usd_libor_index) def test_get_basis_swap_csa_terms(): euribor_index = tm_rates.CURRENCY_TO_SWAP_RATE_BENCHMARK['EUR'][BenchmarkType.EURIBOR.value] usd_libor_index = tm_rates.CURRENCY_TO_SWAP_RATE_BENCHMARK['USD'][BenchmarkType.LIBOR.value] fed_funds_index = tm_rates.CURRENCY_TO_SWAP_RATE_BENCHMARK['USD'][BenchmarkType.Fed_Funds.value] sofr_index = tm_rates.CURRENCY_TO_SWAP_RATE_BENCHMARK['USD'][BenchmarkType.SOFR.value] estr_index = tm_rates.CURRENCY_TO_SWAP_RATE_BENCHMARK['EUR'][BenchmarkType.EUROSTR.value] eonia_index = tm_rates.CURRENCY_TO_SWAP_RATE_BENCHMARK['EUR'][BenchmarkType.EONIA.value] assert dict(csaTerms='USD-1') == tm_rates._get_basis_swap_csa_terms('USD', fed_funds_index, sofr_index) assert dict(csaTerms='EUR-EuroSTR') == tm_rates._get_basis_swap_csa_terms('EUR', estr_index, eonia_index) assert {} == tm_rates._get_basis_swap_csa_terms('EUR', eonia_index, euribor_index) assert {} == tm_rates._get_basis_swap_csa_terms('USD', fed_funds_index, usd_libor_index) def test_match_floating_tenors(): swap_args = dict( asset_parameters_payer_rate_option=tm_rates.CURRENCY_TO_SWAP_RATE_BENCHMARK['USD']['LIBOR'], asset_parameters_payer_designated_maturity='12m', asset_parameters_receiver_rate_option=tm_rates.CURRENCY_TO_SWAP_RATE_BENCHMARK['USD']['SOFR'], asset_parameters_receiver_designated_maturity='1y', ) assert '1y' == tm_rates._match_floating_tenors(swap_args)['asset_parameters_receiver_designated_maturity'] swap_args = dict( asset_parameters_payer_rate_option=tm_rates.CURRENCY_TO_SWAP_RATE_BENCHMARK['USD']['SOFR'], asset_parameters_payer_designated_maturity='1y', asset_parameters_receiver_rate_option=tm_rates.CURRENCY_TO_SWAP_RATE_BENCHMARK['USD']['LIBOR'], asset_parameters_receiver_designated_maturity='12m', ) assert '1y' == tm_rates._match_floating_tenors(swap_args)['asset_parameters_payer_designated_maturity'] swap_args = dict( asset_parameters_payer_rate_option=tm_rates.CURRENCY_TO_SWAP_RATE_BENCHMARK['GBP']['SONIA'], asset_parameters_payer_designated_maturity='1y', asset_parameters_receiver_rate_option=tm_rates.CURRENCY_TO_SWAP_RATE_BENCHMARK['GBP']['LIBOR'], asset_parameters_receiver_designated_maturity='3m', ) assert '3m' == tm_rates._match_floating_tenors(swap_args)['asset_parameters_payer_designated_maturity'] swap_args = dict( asset_parameters_payer_rate_option=tm_rates.CURRENCY_TO_SWAP_RATE_BENCHMARK['GBP']['LIBOR'], asset_parameters_payer_designated_maturity='3m', asset_parameters_receiver_rate_option=tm_rates.CURRENCY_TO_SWAP_RATE_BENCHMARK['GBP']['SONIA'], asset_parameters_receiver_designated_maturity='1y', ) assert '3m' == tm_rates._match_floating_tenors(swap_args)['asset_parameters_receiver_designated_maturity'] swap_args = dict( asset_parameters_payer_rate_option=tm_rates.CURRENCY_TO_SWAP_RATE_BENCHMARK['USD']['LIBOR'], asset_parameters_payer_designated_maturity='3m', asset_parameters_receiver_rate_option=tm_rates.CURRENCY_TO_SWAP_RATE_BENCHMARK['USD']['LIBOR'], asset_parameters_receiver_designated_maturity='6m', ) assert swap_args == tm_rates._match_floating_tenors(swap_args) def test_get_term_struct_date(mocker): today = dt.datetime.today() biz_day = CustomBusinessDay() assert today == tm_rates._get_term_struct_date(tenor=today, index=today, business_day=biz_day) date_index = dt.datetime(2020, 7, 31, 0, 0) assert date_index == tm_rates._get_term_struct_date(tenor='2020-07-31', index=date_index, business_day=biz_day) assert date_index == tm_rates._get_term_struct_date(tenor='0b', index=date_index, business_day=biz_day) assert dt.datetime(2021, 7, 30, 0, 0) == tm_rates._get_term_struct_date( tenor='1y', index=date_index, business_day=biz_day ) def test_cross_stored_direction_for_fx_vol(mocker): mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', side_effect=mock_request) mocker.patch.object(GsSession.current.sync, 'post', side_effect=mock_request) asset_id_list = ["MAYJPCVVF2RWXCES", "MATGYV0J9MPX534Z"] correct_mapping = ["MATGYV0J9MPX534Z", "MATGYV0J9MPX534Z"] with PricingContext(dt.date.today()): for i in range(len(asset_id_list)): correct_id = tm.cross_stored_direction_for_fx_vol(asset_id_list[i]) assert correct_id == correct_mapping[i] def test_cross_to_usd_based_cross_for_fx_forecast(mocker): mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', side_effect=mock_request) mocker.patch.object(GsSession.current.sync, 'post', side_effect=mock_request) asset_id_list = ["MAYJPCVVF2RWXCES", "MATGYV0J9MPX534Z"] correct_mapping = ["MATGYV0J9MPX534Z", "MATGYV0J9MPX534Z"] with PricingContext(dt.date.today()): for i in range(len(asset_id_list)): correct_id = tm.cross_to_usd_based_cross(asset_id_list[i]) assert correct_id == correct_mapping[i] def test_cross_to_used_based_cross(mocker): mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', side_effect=mock_request) mocker.patch.object(GsSession.current.sync, 'post', side_effect=mock_request) mocker.patch.object(SecurityMaster, 'get_asset', side_effect=TypeError('unsupported')) replace = Replacer() bbid_mock = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) bbid_mock.return_value = 'HELLO' assert 'FUN' == tm.cross_to_usd_based_cross(Cross('FUN', 'EURUSD')) replace.restore() def test_cross_stored_direction(mocker): mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', side_effect=mock_request) mocker.patch.object(GsSession.current.sync, 'post', side_effect=mock_request) mocker.patch.object(SecurityMaster, 'get_asset', side_effect=TypeError('unsupported')) replace = Replacer() bbid_mock = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) bbid_mock.return_value = 'HELLO' assert 'FUN' == tm.cross_stored_direction_for_fx_vol(Cross('FUN', 'EURUSD')) replace.restore() def test_get_tdapi_rates_assets(mocker): mock_asset_1 = GsAsset(asset_class='Rates', id='MAW25BGQJH9P6DPT', type_='Swap', name='Test_asset') mock_asset_2 = GsAsset(asset_class='Rates', id='MAA9MVX15AJNQCVG', type_='Swap', name='Test_asset') mock_asset_3 = GsAsset(asset_class='Rates', id='MANQHVYC30AZFT7R', type_='BasisSwap', name='Test_asset') replace = Replacer() assets = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) assets.return_value = [mock_asset_1] assert 'MAW25BGQJH9P6DPT' == tm_rates._get_tdapi_rates_assets() replace.restore() assets = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) assets.return_value = [mock_asset_1, mock_asset_2] kwargs = dict(asset_parameters_termination_date='10y', asset_parameters_effective_date='0b') with pytest.raises(MqValueError): tm_rates._get_tdapi_rates_assets(**kwargs) replace.restore() assets = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) assets.return_value = [] with pytest.raises(MqValueError): tm_rates._get_tdapi_rates_assets() replace.restore() assets = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) assets.return_value = [mock_asset_1, mock_asset_2] kwargs = dict() assert ['MAW25BGQJH9P6DPT', 'MAA9MVX15AJNQCVG'] == tm_rates._get_tdapi_rates_assets(**kwargs) replace.restore() # test case will test matching sofr maturity with libor leg and flipping legs to get right asset kwargs = dict( type='BasisSwap', asset_parameters_termination_date='10y', asset_parameters_payer_rate_option=BenchmarkType.LIBOR, asset_parameters_payer_designated_maturity='3m', asset_parameters_receiver_rate_option=BenchmarkType.SOFR, asset_parameters_receiver_designated_maturity='1y', asset_parameters_clearing_house='lch', asset_parameters_effective_date='Spot', asset_parameters_notional_currency='USD', pricing_location='NYC', ) assets = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) assets.return_value = [mock_asset_3] assert 'MANQHVYC30AZFT7R' == tm_rates._get_tdapi_rates_assets(**kwargs) replace.restore() def test_get_swap_leg_defaults(): result_dict = dict( currency=CurrEnum.JPY, benchmark_type='JPY-LIBOR-BBA', floating_rate_tenor='6m', pricing_location=PricingLocation.TKO, ) defaults = tm_rates._get_swap_leg_defaults(CurrEnum.JPY) assert result_dict == defaults result_dict = dict( currency=CurrEnum.USD, benchmark_type='USD-LIBOR-BBA', floating_rate_tenor='3m', pricing_location=PricingLocation.NYC, ) defaults = tm_rates._get_swap_leg_defaults(CurrEnum.USD) assert result_dict == defaults result_dict = dict( currency=CurrEnum.EUR, benchmark_type='EUR-EURIBOR-TELERATE', floating_rate_tenor='6m', pricing_location=PricingLocation.LDN, ) defaults = tm_rates._get_swap_leg_defaults(CurrEnum.EUR) assert result_dict == defaults result_dict = dict( currency=CurrEnum.SEK, benchmark_type='SEK-STIBOR-SIDE', floating_rate_tenor='6m', pricing_location=PricingLocation.LDN, ) defaults = tm_rates._get_swap_leg_defaults(CurrEnum.SEK) assert result_dict == defaults def test_check_forward_tenor(): valid_tenors = [dt.date(2020, 1, 1), '1y', 'imm2', 'frb2', '1m', '0b'] for tenor in valid_tenors: assert tenor == tm_rates._check_forward_tenor(tenor) invalid_tenors = ['5yr', 'imm5', 'frb0'] for tenor in invalid_tenors: with pytest.raises(MqError): tm_rates._check_forward_tenor(tenor) def mock_commod(_cls, _q, ignore_errors=False): d = { 'price': [ 30, 30, 30, 30, 35.929686, 35.636039, 27.307498, 23.23177, 19.020833, 18.827291, 17.823749, 17.393958, 17.824999, 20.307603, 24.311249, 25.160103, 25.245728, 25.736873, 28.425206, 28.779789, 30.519996, 34.896348, 33.966973, 33.95489, 33.686348, 34.840307, 32.674163, 30.261665, 30, 30, 30, ] } df = MarketDataResponseFrame(data=d, index=pd.date_range('2019-05-01', periods=31, freq='h', tz=dt.timezone.utc)) df.dataset_ids = _test_datasets return df def mock_commod_dup(_cls, _q, ignore_errors=False): d = {'price': [35.929686, 35]} idx = pd.date_range('2019-05-01', periods=1, freq='h', tz=dt.timezone.utc) df = MarketDataResponseFrame(data=d, index=idx.repeat(2)) df.dataset_ids = _test_datasets return df def mock_forward_price(_cls, _q, ignore_errors=False): d = { 'forwardPrice': [ 22.0039, 24.8436, 24.8436, 11.9882, 14.0188, 11.6311, 18.9234, 21.3654, 21.3654, ], 'quantityBucket': [ "PEAK", "PEAK", "PEAK", "7X8", "7X8", "7X8", "2X16H", "2X16H", "2X16H", ], 'contract': [ "J20", "K20", "M20", "J20", "K20", "M20", "J20", "K20", "M20", ], } df = MarketDataResponseFrame(data=d, index=pd.to_datetime([dt.date(2019, 1, 2)] * 9)) df.dataset_ids = _test_datasets return df def mock_implied_volatility_elec(): d = { 'impliedVolatility': [ 0.3424, 0.3624, 0.4424, 0.4424, 0.4224, 0.5324, 0.3224, 0.3324, 0.3924, ], 'quantityBucket': [ "PEAK", "PEAK", "PEAK", "7X8", "7X8", "7X8", "2X16H", "2X16H", "2X16H", ], 'contract': [ "J20", "K20", "M20", "J20", "K20", "M20", "J20", "K20", "M20", ], } df = MarketDataResponseFrame(data=d, index=pd.to_datetime([dt.date(2019, 1, 2)] * 9)) df.dataset_ids = _test_datasets return df def mock_fair_price(_cls, _q, ignore_errors=False): d = { 'fairPrice': [ 2.880, 2.844, 2.726, ], 'contract': [ "F21", "G21", "H21", ], } df = MarketDataResponseFrame(data=d, index=pd.to_datetime([dt.date(2019, 1, 2)] * 3)) df.dataset_ids = _test_datasets return df def mock_eu_natgas_forward_price(_cls, _q, ignore_errors=False): d = {'forwardPrice': [15.65], 'contract': ["H21"]} df = MarketDataResponseFrame(data=d, index=pd.to_datetime([dt.date(2021, 1, 1)])) df.dataset_ids = _test_datasets return df def mock_natgas_forward_price(_cls, _q, ignore_errors=False): d = { 'forwardPrice': [ 2.880, 2.844, 2.726, ], 'contract': [ "F21", "G21", "H21", ], } df = MarketDataResponseFrame(data=d, index=pd.to_datetime([dt.date(2019, 1, 2)] * 3)) df.dataset_ids = _test_datasets return df def mock_natgas_implied_volatility(_cls, _q, ignore_errors=False): d = { 'impliedVolatility': [ 2.880, 2.844, 2.726, ], 'contract': [ "F21", "G21", "H21", ], } df = MarketDataResponseFrame(data=d, index=pd.to_datetime([dt.date(2019, 1, 2)] * 3)) df.dataset_ids = _test_datasets return df def mock_fair_price_swap(_cls, _q, ignore_errors=False): d = {'fairPrice': [2.880]} df = MarketDataResponseFrame(data=d, index=pd.to_datetime([dt.date(2019, 1, 2)])) df.dataset_ids = _test_datasets return df def mock_implied_volatility(_cls, _q, ignore_errors=False): d = { 'impliedVolatility': [ 2.880, 2.844, 2.726, ], 'contract': [ "F21", "G21", "H21", ], } df = MarketDataResponseFrame(data=d, index=pd.to_datetime([dt.date(2019, 1, 2)] * 3)) df.dataset_ids = _test_datasets return df def mock_missing_bucket_forward_price(_cls, _q, ignore_errors=False): d = { 'forwardPrice': [ 22.0039, 24.8436, 24.8436, 11.9882, 14.0188, 18.9234, 21.3654, 21.3654, ], 'quantityBucket': [ "PEAK", "PEAK", "PEAK", "7X8", "7X8", "2X16H", "2X16H", "2X16H", ], 'contract': [ "J20", "K20", "M20", "J20", "K20", "J20", "K20", "M20", ], } return pd.DataFrame(data=d, index=pd.to_datetime([dt.date(2019, 1, 2)] * 8)) def mock_missing_bucket_implied_volatility(): d = { 'impliedVolatility': [ 0.3424, 0.3624, 0.4424, 0.4424, 0.4224, 0.3224, 0.3324, 0.3924, ], 'quantityBucket': [ "PEAK", "PEAK", "PEAK", "7X8", "7X8", "2X16H", "2X16H", "2X16H", ], 'contract': [ "J20", "K20", "M20", "J20", "K20", "J20", "K20", "M20", ], } return pd.DataFrame(data=d, index=pd.to_datetime([dt.date(2019, 1, 2)] * 8)) def mock_fx_vol( _cls, asset_ids=None, query_type=None, where=None, source=None, real_time=None, request_id=None, parallelize_queries=False, ): d = { 'strikeReference': ['delta', 'spot', 'forward'], 'relativeStrike': [25, 100, 100], 'impliedVolatility': [5, 1, 2], 'forecast': [1.1, 1.1, 1.1], } if DataContext.current.end_date >= dt.date.today(): periods = 4 d['impliedVolatility'].append(3) d['strikeReference'].append('delta') d['relativeStrike'].append(100) d['forecast'].append(1.1) else: periods = 3 df = MarketDataResponseFrame(data=d, index=pd.date_range('2019-01-01', periods=periods, freq='D')) df.dataset_ids = _test_datasets df.index.freq = None return df def mock_fx_spot_fwd_3m(*args, **kwargs): d = pd.DataFrame( { 'spot': [1.18250, 1.18566, 1.18511], 'forwardPoint': [0.00234, 0.00234, 0.00235], 'tenor': ['3m', '3m', '3m'], 'date': [pd.Timestamp(2020, 9, 2), pd.Timestamp(2020, 9, 3), pd.Timestamp(2020, 9, 4)], 'settlementDate': [pd.Timestamp('2020-12-04'), pd.Timestamp('2020-12-08'), pd.Timestamp('2020-12-08')], } ) d = d.set_index('date') df = MarketDataResponseFrame(d) df.dataset_ids = _test_datasets return df def mock_fx_spot_fwd_2y(*args, **kwargs): d = pd.DataFrame( { 'spot': [1.18250, 1.18566, 1.18511], 'forwardPoint': [0.02009, 0.02015, 0.02064], 'tenor': ['2y', '2y', '2y'], 'date': [pd.Timestamp(2020, 9, 2), pd.Timestamp(2020, 9, 3), pd.Timestamp(2020, 9, 4)], 'settlementDate': [pd.Timestamp('2020-12-04'), pd.Timestamp('2020-12-08'), pd.Timestamp('2020-12-08')], } ) d = d.set_index('date') df = MarketDataResponseFrame(d) df.dataset_ids = _test_datasets return df def mock_fx_spot_fwd_3m_rt(*args, **kwargs): d = pd.DataFrame( { 'spot': [1.18250, 1.18566, 1.18511], 'forwardPoint': [0.00234, 0.00234, 0.00235], 'tenor': ['3m', '3m', '3m'], 'time': [pd.Timestamp(2020, 9, 2), pd.Timestamp(2020, 9, 3), pd.Timestamp(2020, 9, 4)], 'settlementDate': [pd.Timestamp('2020-12-04'), pd.Timestamp('2020-12-08'), pd.Timestamp('2020-12-08')], } ) d = d.set_index('time') df = MarketDataResponseFrame(d) df.dataset_ids = _test_datasets return df def mock_fx_spot_fwd_2y_rt(*args, **kwargs): d = pd.DataFrame( { 'spot': [1.18250, 1.18566, 1.18511], 'forwardPoint': [0.02009, 0.02015, 0.02064], 'tenor': ['2y', '2y', '2y'], 'time': [pd.Timestamp(2020, 9, 2), pd.Timestamp(2020, 9, 3), pd.Timestamp(2020, 9, 4)], 'settlementDate': [pd.Timestamp('2020-12-04'), pd.Timestamp('2020-12-08'), pd.Timestamp('2020-12-08')], } ) d = d.set_index('time') df = MarketDataResponseFrame(d) df.dataset_ids = _test_datasets return df def mock_fx_correlation(*args, **kwargs): d = pd.DataFrame( { 'impliedVolatility': [ 7.943208, 8.042599, 7.875325, 8.304180, 8.353483, 8.268724, 8.267971, 8.395843, 8.355239, ], 'bbid': ['EURUSD', 'EURUSD', 'EURUSD', 'EURJPY', 'EURJPY', 'EURJPY', 'USDJPY', 'USDJPY', 'USDJPY'], 'assetId': [ 'MAA0NE9QX2ABETG6', 'MAA0NE9QX2ABETG6', 'MAA0NE9QX2ABETG6', 'MAYPHS80JRWDJ8RC', 'MAYPHS80JRWDJ8RC', 'MAYPHS80JRWDJ8RC', 'MATGYV0J9MPX534Z', 'MATGYV0J9MPX534Z', 'MATGYV0J9MPX534Z', ], 'relativeStrike': [0, 0, 0, 0, 0, 0, 0, 0, 0], 'strikeReference': ['delta', 'delta', 'delta', 'delta', 'delta', 'delta', 'delta', 'delta', 'delta'], 'tenor': ['3m', '3m', '3m', '3m', '3m', '3m', '3m', '3m', '3m'], 'date': [ pd.Timestamp(2020, 9, 2), pd.Timestamp(2020, 9, 3), pd.Timestamp(2020, 9, 4), pd.Timestamp(2020, 9, 2), pd.Timestamp(2020, 9, 3), pd.Timestamp(2020, 9, 4), pd.Timestamp(2020, 9, 2), pd.Timestamp(2020, 9, 3), pd.Timestamp(2020, 9, 4), ], } ) d = d.set_index('date') df = MarketDataResponseFrame(d) df.dataset_ids = _test_datasets return df def mock_fx_forecast(_cls, _q, ignore_errors=False): d = {'fxForecast': [1.1, 1.1, 1.1]} df = MarketDataResponseFrame(data=d, index=_index * 3) df.dataset_ids = _test_datasets return df def mock_fx_forecast_time_series(_cls, _q, ignore_errors=False): d = {'relativePeriod': ['3m', '6m', 'EOY1', 'EOY2'], 'fxForecast': [1.18, 1.20, 1.25, 1.23]} df = MarketDataResponseFrame( data=d, index=pd.to_datetime([dt.date(2027, 1, 1), dt.date(2028, 1, 1), dt.date(2029, 1, 1), dt.date(2030, 1, 1)]), ) df.dataset_ids = _test_datasets return df def mock_fx_delta(_cls, _q, ignore_errors=False): d = { 'relativeStrike': [25, -25, 0], 'impliedVolatility': [1, 5, 2], 'forecast': [1.1, 1.1, 1.1], 'forwardPoint': [1, 1.1, 1.2], } df = MarketDataResponseFrame(data=d, index=_index * 3) df.dataset_ids = _test_datasets return df def mock_fx_empty(_cls, _q, ignore_errors=False): d = {'strikeReference': [], 'relativeStrike': [], 'impliedVolatility': []} df = MarketDataResponseFrame(data=d, index=[]) df.dataset_ids = _test_datasets return df def mock_fx_switch(_cls, _q, _n, ignore_errors=False): replace = Replacer() replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_fx_empty) replace.restore() return Cross('MA1889', 'ABC/XYZ') def mock_curr(_cls, _q, ignore_errors=False): d = { 'swapAnnuity': [1, 2, 3], 'swapRate': [1, 2, 3], 'basisSwapRate': [1, 2, 3], 'swaptionVol': [1, 2, 3], 'atmFwdRate': [1, 2, 3], 'midcurveVol': [1, 2, 3], 'capFloorVol': [1, 2, 3], 'spreadOptionVol': [1, 2, 3], 'inflationSwapRate': [1, 2, 3], 'midcurveAtmFwdRate': [1, 2, 3], 'capFloorAtmFwdRate': [1, 2, 3], 'spreadOptionAtmFwdRate': [1, 2, 3], 'strike': [0.25, 0.5, 0.75], } df = MarketDataResponseFrame(data=d, index=_index * 3) df.dataset_ids = _test_datasets return df def mock_cross(_cls, _q, ignore_errors=False): d = { 'basis': [1, 2, 3], } df = MarketDataResponseFrame(data=d, index=_index * 3) df.dataset_ids = _test_datasets return df def mock_eq(_cls, _q, ignore_errors=False): d = { 'relativeStrike': [0.75, 0.25, 0.5], 'impliedVolatility': [5, 1, 2], 'impliedCorrelation': [5, 1, 2], 'realizedCorrelation': [3.14, 2.71828, 1.44], 'averageImpliedVolatility': [5, 1, 2], 'averageImpliedVariance': [5, 1, 2], 'averageRealizedVolatility': [5, 1, 2], 'impliedVolatilityByDeltaStrike': [5, 1, 2], 'optionPremium': [5, 1, 2], 'absoluteStrike': [5, 1, 2], 'fundamentalMetric': [5, 1, 2], } df = MarketDataResponseFrame(data=d, index=_index * 3) df.dataset_ids = _test_datasets return df def mock_eq_vol( _cls, asset_ids=None, query_type=None, where=None, source=None, real_time=None, request_id=None, parallelize_queries=False, ): d = { 'impliedVolatility': [5, 1, 2], } if DataContext.current.end_date >= dt.date.today(): end = dt.datetime.now(dt.timezone.utc).date() periods = 4 d['impliedVolatility'].append(3) else: end = dt.datetime.now(dt.timezone.utc).date() - dt.timedelta(days=1) periods = 3 df = MarketDataResponseFrame(data=d, index=pd.date_range(end=end, periods=periods, freq='D')) df.dataset_ids = _test_datasets df.index.freq = None return df def mock_eq_vol_last_empty( _cls, asset_ids=None, query_type=None, where=None, source=None, real_time=None, request_id=None, parallelize_queries=False, ): d = { 'impliedVolatility': [5, 1, 2], } end = dt.date.today() - dt.timedelta(days=1) df = MarketDataResponseFrame(data=d, index=pd.date_range(end=end, periods=3, freq='D')) df.dataset_ids = _test_datasets return df def mock_eq_norm(_cls, _q, ignore_errors=False): d = {'relativeStrike': [-4.0, 4.0, 0], 'impliedVolatility': [5, 1, 2]} df = MarketDataResponseFrame(data=d, index=_index * 3) df.dataset_ids = _test_datasets return df def mock_eq_spot(_cls, _q, ignore_errors=False): d = {'relativeStrike': [0.75, 1.25, 1.0], 'impliedVolatility': [5, 1, 2]} df = MarketDataResponseFrame(data=d, index=_index * 3) df.dataset_ids = _test_datasets return df def mock_inc(_cls, _q, ignore_errors=False): d = {'relativeStrike': [0.25, 0.75], 'impliedVolatility': [5, 1]} df = MarketDataResponseFrame(data=d, index=_index * 2) df.dataset_ids = _test_datasets return df def mock_esg(_cls, _q, ignore_errors=False): d = { "esNumericScore": [2, 4, 6], "esNumericPercentile": [81.2, 75.4, 65.7], "esPolicyScore": [2, 4, 6], "esPolicyPercentile": [81.2, 75.4, 65.7], "esScore": [2, 4, 6], "esPercentile": [81.2, 75.4, 65.7], "esProductImpactScore": [2, 4, 6], "esProductImpactPercentile": [81.2, 75.4, 65.7], "gScore": [2, 4, 6], "gPercentile": [81.2, 75.4, 65.7], "esMomentumScore": [2, 4, 6], "esMomentumPercentile": [81.2, 75.4, 65.7], "gRegionalScore": [2, 4, 6], "gRegionalPercentile": [81.2, 75.4, 65.7], "controversyScore": [2, 4, 6], "controversyPercentile": [81.2, 75.4, 65.7], "esDisclosurePercentage": [49.2, 55.7, 98.4], } df = MarketDataResponseFrame(data=d, index=_index * 3) df.dataset_ids = _test_datasets return df def mock_index_positions_data(asset_id, start_date, end_date, fields=None, position_type=None): return [ { 'underlyingAssetId': 'MA3', 'netWeight': 0.1, 'positionType': 'close', 'assetId': 'MA890', 'positionDate': '2020-01-01', }, { 'underlyingAssetId': 'MA1', 'netWeight': 0.6, 'positionType': 'close', 'assetId': 'MA890', 'positionDate': '2020-01-01', }, { 'underlyingAssetId': 'MA2', 'netWeight': 0.3, 'positionType': 'close', 'assetId': 'MA890', 'positionDate': '2020-01-01', }, ] def mock_rating(_cls, _q, ignore_errors=False): d = {'rating': ['Buy', 'Sell', 'Buy', 'Neutral'], 'convictionList': [1, 0, 0, 0]} df = MarketDataResponseFrame( data=d, index=pd.to_datetime([dt.date(2020, 8, 13), dt.date(2020, 8, 14), dt.date(2020, 8, 17), dt.date(2020, 8, 18)]), ) df.dataset_ids = _test_datasets return df def mock_gsdeer_gsfeer(_cls, assetId, start_date): d = {'gsdeer': [1, 1.2, 1.1], 'gsfeer': [2, 1.8, 1.9], 'year': [2000, 2010, 2020], 'quarter': ['Q1', 'Q2', 'Q3']} df = MarketDataResponseFrame(data=d, index=_index * 3) return df def mock_factor_profile(_cls, _q, ignore_errors=False): d = { 'growthScore': [0.238, 0.234, 0.234, 0.230], 'financialReturnsScore': [0.982, 0.982, 0.982, 0.982], 'multipleScore': [0.204, 0.192, 0.190, 0.190], 'integratedScore': [0.672, 0.676, 0.676, 0.674], } df = MarketDataResponseFrame( data=d, index=pd.to_datetime([dt.date(2020, 8, 13), dt.date(2020, 8, 14), dt.date(2020, 8, 17), dt.date(2020, 8, 18)]), ) df.dataset_ids = _test_datasets return df def mock_commodity_forecast(_cls, _q, ignore_errors=False): d = { 'forecastPeriod': ['3m', '3m', '3m', '3m'], 'forecastType': ['spotReturn', 'spotReturn', 'spotReturn', 'spotReturn'], 'commodityForecast': [1700, 1400, 1500, 1600], } df = MarketDataResponseFrame( data=d, index=pd.to_datetime([dt.date(2020, 8, 13), dt.date(2020, 8, 14), dt.date(2020, 8, 17), dt.date(2020, 8, 18)]), ) df.dataset_ids = _test_datasets return df def mock_commodity_forecast_time_series(_cls, _q, ignore_errors=False): d = { 'forecastFrequency': ['Annual', 'Monthly', 'Quarterly', '3/6/12-Month Rolling'], 'forecastType': ['spot', 'spot', 'spot', 'spot'], 'commodityForecast': [56, 63, 77.75, 80], } df = MarketDataResponseFrame( data=d, index=pd.to_datetime([dt.date(2027, 1, 1), dt.date(2027, 1, 1), dt.date(2028, 1, 1), dt.date(2029, 1, 1)]), ) df.dataset_ids = _test_datasets return df def mock_cds_spread(_cls, _q, ignore_errors=False): d = { "spreadAt100": [0.000836], "spreadAt250": [0.000436], "spreadAt500": [0.00036], } df = MarketDataResponseFrame(d, index=_index2) return df def test_skew(): replace = Replacer() mock_spx = Index('MA890', AssetClass.Equity, 'SPX') replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_eq) actual = tm.skew(mock_spx, '1m', tm.SkewReference.DELTA, 25) assert_series_equal(pd.Series([2.0], index=_index, name='impliedVolatility'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_eq) actual = tm.skew(mock_spx, '1m', tm.SkewReference.DELTA, 25, tm.NormalizationMode.NORMALIZED) assert_series_equal(pd.Series([2.0], index=_index, name='impliedVolatility'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_eq) actual = tm.skew(mock_spx, '1m', tm.SkewReference.DELTA, 25, tm.NormalizationMode.OUTRIGHT) assert_series_equal(pd.Series([4], index=_index, name='impliedVolatility'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_eq_norm) actual = tm.skew(mock_spx, '1m', tm.SkewReference.NORMALIZED, 4) assert_series_equal(pd.Series([2.0], index=_index, name='impliedVolatility'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_eq_spot) actual = tm.skew(mock_spx, '1m', tm.SkewReference.SPOT, 25) assert_series_equal(pd.Series([2.0], index=_index, name='impliedVolatility'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) mock.return_value = mock_empty_market_data_response() actual = tm.skew(mock_spx, '1m', tm.SkewReference.SPOT, 25) assert actual.empty replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_inc) with pytest.raises(MqError): tm.skew(mock_spx, '1m', tm.SkewReference.DELTA, 25) replace.restore() with pytest.raises(MqError): tm.skew(mock_spx, '1m', None, 25) def test_skew_fx(): replace = Replacer() cross = Cross('MAA0NE9QX2ABETG6', 'USD/EUR') xrefs = replace('gs_quant.timeseries.measures.GsAssetApi.get_asset_xrefs', Mock()) xrefs.return_value = [ GsTemporalXRef( dt.date(2019, 1, 1), dt.date(2952, 12, 31), XRef( bbid='EURUSD', ), ) ] replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()).return_value = cross replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_fx_delta) mock = cross actual = tm.skew(mock, '1m', tm.SkewReference.DELTA, 25) assert_series_equal(pd.Series([4], index=_index, name='impliedVolatility'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets actual = tm.skew(mock, '1m', tm.SkewReference.DELTA, 25, tm.NormalizationMode.NORMALIZED) assert_series_equal(pd.Series([2.0], index=_index, name='impliedVolatility'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets actual = tm.skew(mock, '1m', tm.SkewReference.DELTA, 25, tm.NormalizationMode.OUTRIGHT) assert_series_equal(pd.Series([4], index=_index, name='impliedVolatility'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(MqError): tm.skew(mock, '1m', tm.SkewReference.DELTA, 25, real_time=True) with pytest.raises(MqError): tm.skew(mock, '1m', tm.SkewReference.SPOT, 25) with pytest.raises(MqError): tm.skew(mock, '1m', tm.SkewReference.FORWARD, 25) with pytest.raises(MqError): tm.skew(mock, '1m', tm.SkewReference.NORMALIZED, 25) with pytest.raises(MqError): tm.skew(mock, '1m', None, 25) replace.restore() def test_implied_vol(): replace = Replacer() mock_spx = Index('MA890', AssetClass.Equity, 'SPX') replace('gs_quant.timeseries.measures.get_historical_and_last_for_measure', mock_eq_vol) idx = pd.date_range(end=dt.datetime.now(dt.timezone.utc).date(), periods=4, freq='D') idx.freq = None actual = tm.implied_volatility(mock_spx, '1m', tm.VolReference.DELTA_CALL, 25) assert_series_equal(pd.Series([5, 1, 2, 3], index=idx, name='impliedVolatility'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets actual = tm.implied_volatility(mock_spx, '1m', tm.VolReference.DELTA_PUT, 75) assert_series_equal(pd.Series([5, 1, 2, 3], index=idx, name='impliedVolatility'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(MqError): tm.implied_volatility(mock_spx, '1m', tm.VolReference.DELTA_NEUTRAL) with pytest.raises(MqError): tm.implied_volatility(mock_spx, '1m', tm.VolReference.DELTA_CALL) with DataContext(dt.date(2020, 1, 11), dt.date(2021, 1, 11)): actual = tm.implied_volatility(mock_spx, '1m', tm.VolReference.DELTA_CALL, 25) actual.index.freq = None assert_series_equal(pd.Series([5, 1, 2], index=idx[:-1], name='impliedVolatility'), pd.Series(actual)) replace.restore() def test_merge_dataframes(): a = pd.DataFrame({'a': [1, 2, 3]}) b = pd.DataFrame({'a': [1, 5, 6]}) c = tm.merge_dataframes([a, b]) assert not len(c.dataset_ids) assert len(c) == 5 c = tm.merge_dataframes([a, None]) assert_series_equal(a['a'], c['a']) assert tm.merge_dataframes(None).empty def test_get_last_for_measure(): replace = Replacer() mock_market = replace('gs_quant.timeseries.measures._market_data_timed', Mock()) mock_market.return_value = pd.DataFrame( [1], index=pd.date_range('2020-01-01', periods=1, freq='D', tz=dt.timezone.utc) ) a = tm.get_last_for_measure(['blah'], QueryType.IMPLIED_VOLATILITY, {}) assert len(a) == 1 mock_market.return_value = pd.DataFrame() a = tm.get_last_for_measure(['blah'], QueryType.IMPLIED_VOLATILITY, {}) assert a is None mock_market.side_effect = [MqValueError()] a = tm.get_last_for_measure(['blah'], QueryType.IMPLIED_VOLATILITY, {}) assert a is None replace.restore() def test_ignore_errors(): with mock.patch.object(GsSession.current, '_post') as mocker: mocker.return_value = { 'requestId': 'rq1234', 'responses': [{'queryResponse': [{'dataSetIds': ['DS1'], 'errorMessages': ['this failed']}]}], } last_data = tm.get_last_for_measure(['blah'], QueryType.IMPLIED_VOLATILITY, {}, ignore_errors=True) assert last_data is None def test_tenor_month_to_year(): assert tm._tenor_month_to_year('1y') == '1y' assert tm._tenor_month_to_year('11m') == '11m' assert tm._tenor_month_to_year('12m') == '1y' assert tm._tenor_month_to_year('24m') == '2y' def test_implied_vol_no_last(): replace = Replacer() mock_spx = Index('MA890', AssetClass.Equity, 'SPX') idx = pd.date_range(end=dt.date.today() - dt.timedelta(days=1), periods=3, freq='D') replace('gs_quant.timeseries.measures.get_historical_and_last_for_measure', mock_eq_vol_last_empty) actual = tm.implied_volatility(mock_spx, '1m', tm.VolReference.DELTA_CALL, 25) assert_series_equal(pd.Series([5, 1, 2], index=idx, name='impliedVolatility'), pd.Series(actual)) actual = tm.implied_volatility(mock_spx, '1m', tm.VolReference.DELTA_PUT, 75) assert_series_equal(pd.Series([5, 1, 2], index=idx, name='impliedVolatility'), pd.Series(actual)) replace.restore() def test_implied_vol_fx(): replace = Replacer() mock = Cross('MAA0NE9QX2ABETG6', 'USD/EUR') xrefs = replace('gs_quant.timeseries.measures.GsAssetApi.get_asset_xrefs', Mock()) xrefs.return_value = [ GsTemporalXRef( dt.date(2019, 1, 1), dt.date(2952, 12, 31), XRef( bbid='EURUSD', ), ) ] replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()).return_value = mock # for different delta strikes replace('gs_quant.timeseries.measures.get_historical_and_last_for_measure', mock_fx_vol) actual = tm.implied_volatility(mock, '1m', tm.VolReference.DELTA_CALL, 25) expected = pd.Series([5, 1, 2, 3], index=pd.date_range('2019-01-01', periods=4, freq='D'), name='impliedVolatility') expected.index.freq = None assert_series_equal(expected, pd.Series(actual)) assert actual.dataset_ids == _test_datasets actual = tm.implied_volatility(mock, '1m', tm.VolReference.DELTA_PUT, 25) assert_series_equal(expected, pd.Series(actual)) assert actual.dataset_ids == _test_datasets actual = tm.implied_volatility(mock, '1m', tm.VolReference.DELTA_NEUTRAL) assert_series_equal(expected, pd.Series(actual)) assert actual.dataset_ids == _test_datasets actual = tm.implied_volatility(mock, '1m', tm.VolReference.FORWARD, 100) assert_series_equal(expected, pd.Series(actual)) assert actual.dataset_ids == _test_datasets actual = tm.implied_volatility(mock, '1m', tm.VolReference.SPOT, 100) assert_series_equal(expected, pd.Series(actual)) assert actual.dataset_ids == _test_datasets # NORMALIZED not supported with pytest.raises(MqError): tm.implied_volatility(mock, '1m', tm.VolReference.DELTA_CALL) with pytest.raises(MqError): tm.implied_volatility(mock, '1m', tm.VolReference.NORMALIZED, 25) with pytest.raises(MqError): tm.implied_volatility(mock, '1m', tm.VolReference.SPOT, 25) with pytest.raises(MqError): tm.implied_volatility(mock, '1m', tm.VolReference.FORWARD, 25) replace.restore() def test_fx_forecast(): replace = Replacer() mock = Cross('MAA0NE9QX2ABETG6', 'USD/EUR') xrefs = replace('gs_quant.timeseries.measures.GsAssetApi.get_asset_xrefs', Mock()) xrefs.return_value = [ GsTemporalXRef( dt.date(2019, 1, 1), dt.date(2952, 12, 31), XRef( bbid='EURUSD', ), ) ] replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()).return_value = mock replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_fx_forecast) actual = tm.fx_forecast(mock, '12m') assert_series_equal(pd.Series([1.1, 1.1, 1.1], index=_index * 3, name='fxForecast'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets actual = tm.fx_forecast(mock, '3m') assert_series_equal(pd.Series([1.1, 1.1, 1.1], index=_index * 3, name='fxForecast'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.fx_forecast(mock, '3m', real_time=True) replace.restore() def test_fx_forecast_inverse(): replace = Replacer() get_cross = replace('gs_quant.timeseries.measures.cross_to_usd_based_cross', Mock()) get_cross.return_value = "MATGYV0J9MPX534Z" replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_fx_forecast) mock = Cross("MAYJPCVVF2RWXCES", 'USD/JPY') actual = tm.fx_forecast(mock, '3m') assert_series_equal(pd.Series([1 / 1.1, 1 / 1.1, 1 / 1.1], index=_index * 3, name='fxForecast'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets replace.restore() def test_fx_forecast_time_series(): replace = Replacer() mock = Cross('MAA0NE9QX2ABETG6', 'USD/EUR') xrefs = replace('gs_quant.timeseries.measures.GsAssetApi.get_asset_xrefs', Mock()) xrefs.return_value = [ GsTemporalXRef( dt.date(2019, 1, 1), dt.date(2952, 12, 31), XRef( bbid='EURUSD', ), ) ] replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()).return_value = mock replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_fx_forecast_time_series) with pytest.raises(ValueError): tm.fx_forecast_time_series(100, tm._FxForecastTimeSeriesPeriodType.ANNUAL.value) with pytest.raises(ValueError): tm.fx_forecast_time_series(mock, 'Monthly') actual = tm.fx_forecast_time_series('MATGYV0J9MPX534Z', tm._FxForecastTimeSeriesPeriodType.ANNUAL.value) assert not pd.Series(actual).empty actual = tm.fx_forecast_time_series(mock, tm._FxForecastTimeSeriesPeriodType.SHORT_TERM.value) assert not pd.Series(actual).empty actual = tm.fx_forecast_time_series(mock, tm._FxForecastTimeSeriesPeriodType.ANNUAL.value) assert not pd.Series(actual).empty actual = tm.fx_forecast_time_series(mock, tm._FxForecastTimeSeriesPeriodType.ANNUAL) assert not pd.Series(actual).empty actual = tm.fx_forecast_time_series(mock, 'Annual') assert not pd.Series(actual).empty with pytest.raises(NotImplementedError): tm.fx_forecast_time_series(mock, tm._FxForecastTimeSeriesPeriodType.SHORT_TERM.value, real_time=True) replace.restore() def test_vol_smile(): replace = Replacer() mock_spx = Index('MA890', AssetClass.Equity, 'SPX') replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_eq) actual = tm.vol_smile(mock_spx, '1m', tm.VolSmileReference.FORWARD, '5d') assert_series_equal(pd.Series([5, 1, 2], index=[0.75, 0.25, 0.5], name='vol_smile'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets assert actual.name == 'vol_smile' actual = tm.vol_smile(mock_spx, '1m', tm.VolSmileReference.SPOT, '5d') assert_series_equal(pd.Series([5, 1, 2], index=[0.75, 0.25, 0.5], name='vol_smile'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets assert actual.name == 'vol_smile' market_mock = replace('gs_quant.timeseries.measures.get_df_with_retries', Mock()) market_mock.return_value = mock_empty_market_data_response() actual = tm.vol_smile(mock_spx, '1m', tm.VolSmileReference.SPOT, '1d') assert actual.empty assert actual.dataset_ids == () market_mock.assert_called_once() with pytest.raises(NotImplementedError): tm.vol_smile(mock_spx, '1m', tm.VolSmileReference.SPOT, '1d', real_time=True) replace.restore() def test_impl_corr(): replace = Replacer() mock_spx = Index('MA890', AssetClass.Equity, 'SPX') replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_eq) actual = tm.implied_correlation(mock_spx, '1m', tm.EdrDataReference.DELTA_CALL, 25) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='impliedCorrelation'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets actual = tm.implied_correlation(mock_spx, '1m', tm.EdrDataReference.DELTA_PUT, 75) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='impliedCorrelation'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.implied_correlation(..., '1m', tm.EdrDataReference.DELTA_PUT, 75, real_time=True) with pytest.raises(MqError): tm.implied_correlation(..., '1m', tm.EdrDataReference.DELTA_CALL, 50, '') replace.restore() def test_impl_corr_n(mocker): spx = Index('MA4B66MW5E27U8P32SB', AssetClass.Equity, 'SPX') with pytest.raises(MqValueError): tm.implied_correlation(spx, '1m', tm.EdrDataReference.DELTA_CALL, 0.5, composition_date=dt.date.today()) with pytest.raises(MqValueError): tm.implied_correlation(spx, '1m', tm.EdrDataReference.DELTA_CALL, 0.5, 200) resources = os.path.join(os.path.dirname(__file__), '..', 'resources') i_vol = pd.read_csv(os.path.join(resources, 'SPX_50_icorr_in.csv')) i_vol.index = pd.to_datetime(i_vol['date']) weights = pd.read_csv(os.path.join(resources, 'SPX_50_weights.csv')) weights = weights.set_index('underlyingAssetId') replace = Replacer() market_data = replace('gs_quant.timeseries.econometrics.GsDataApi.get_market_data', Mock()) market_data.return_value = i_vol constituents = replace('gs_quant.timeseries.measures._get_index_constituent_weights', Mock()) constituents.return_value = weights last_mock = replace('gs_quant.timeseries.measures.get_last_for_measure', Mock()) last_mock.return_value = None mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(OAuth2Session, '_authenticate', return_value=None) expected = pd.read_csv(os.path.join(resources, 'SPX_50_icorr_out.csv')) expected.index = pd.to_datetime(expected['date']) expected = expected['value'] actual = tm.implied_correlation( spx, '1m', tm.EdrDataReference.DELTA_CALL, 0.5, 50, dt.date(2020, 8, 31), source='PlotTool' ) expected = ExtendedSeries(expected) assert_series_equal(actual, expected, check_names=False) mask = i_vol.index < '2020-08-31' assert not all(mask) market_data.return_value = i_vol[mask] last_mock.return_value = i_vol[~mask] actual = tm.implied_correlation( spx, '1m', tm.EdrDataReference.DELTA_CALL, 0.5, 50, dt.date(2020, 8, 31), source='PlotTool' ) assert_series_equal(actual, expected, check_names=False) market_data.return_value = pd.DataFrame() last_mock.return_value = pd.DataFrame() actual = tm.implied_correlation( spx, '1m', tm.EdrDataReference.DELTA_CALL, 0.5, 50, dt.date(2020, 8, 31), source='PlotTool' ) assert actual.empty replace.restore() def test_implied_corr_basket(): replace = Replacer() dates = pd.DatetimeIndex( [ dt.date(2021, 1, 1), dt.date(2021, 1, 2), dt.date(2021, 1, 3), dt.date(2021, 1, 4), dt.date(2021, 1, 5), dt.date(2021, 1, 6), ] ) x = pd.DataFrame({'impliedVolatility': [0.1, 0.11, 0.12, 0.13, 0.14, 0.15]}, index=dates) x['assetId'] = 'MA4B66MW5E27U9VBB94' y = pd.DataFrame({'impliedVolatility': [0.2, 0.21, 0.22, 0.23, 0.24, 0.25]}, index=dates) y['assetId'] = 'MA4B66MW5E27UAL9SUX' z = pd.DataFrame({'impliedVolatility': [0.13, 0.21, 0.3, 0.31, 0.23, 0.24]}, index=dates) z['assetId'] = 'MA4B66MW5E27U8P32SB' implied_vol = pd.concat([x, y, z]) x = pd.DataFrame({'spot': [100.0, 101, 103.02, 100.9596, 100.9596, 102.978792]}, index=dates) x['assetId'] = 'MA4B66MW5E27U9VBB94' y = pd.DataFrame({'spot': [100.0, 100, 100, 100, 100, 100]}, index=dates) y['assetId'] = 'MA4B66MW5E27UAL9SUX' spot_data = pd.concat([x, y]) mock_data = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) mock_data.side_effect = [implied_vol, spot_data] mock_asset = replace('gs_quant.timeseries.backtesting.GsAssetApi.get_many_assets_data', Mock()) mock_asset.return_value = [ {'id': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW'}, {'id': 'MA4B66MW5E27UAL9SUX', 'bbid': 'MSFT UW'}, ] spx = Index('MA4B66MW5E27U8P32SB', AssetClass.Equity, 'SPX') a_basket = tm.Basket(['AAPL UW', 'MSFT UW'], [0.1, 0.9]) mock_data = replace('gs_quant.timeseries.get_historical_and_last_for_measure', Mock()) mock_data.return_value = spot_data actual = tm.implied_correlation_with_basket(spx, '1y', tm.EdrDataReference.DELTA_PUT, 25, a_basket) expected = pd.Series([-433.33333, 198.60510, 1065.90909, 986.28763, 100.0, 100.0], index=dates) expected = ExtendedSeries(expected) assert_series_equal(actual, expected) with pytest.raises(NotImplementedError): tm.implied_correlation_with_basket(spx, '1y', tm.EdrDataReference.DELTA_PUT, 25, a_basket, real_time=True) replace.restore() def test_realized_corr_basket(): replace = Replacer() dates = pd.DatetimeIndex( [ dt.date(2021, 1, 1), dt.date(2021, 1, 2), dt.date(2021, 1, 3), dt.date(2021, 1, 4), dt.date(2021, 1, 5), dt.date(2021, 1, 6), ] ) x = pd.DataFrame({'spot': [100.0, 101, 103.02, 100.9596, 100.9596, 102.978792]}, index=dates) x['assetId'] = 'MA4B66MW5E27U9VBB94' y = pd.DataFrame({'spot': [100.0, 99.5, 100.1, 101, 100.7, 100.6]}, index=dates) y['assetId'] = 'MA4B66MW5E27UAL9SUX' constituents_spot = pd.concat([x, y]) index_spot = pd.DataFrame({'spot': [100.0, 101, 102, 103, 103.3, 104]}, index=dates) index_spot['assetId'] = 'MA4B66MW5E27U8P32SB' mock_data = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) mock_data.side_effect = [index_spot, constituents_spot] mock_asset = replace('gs_quant.timeseries.backtesting.GsAssetApi.get_many_assets_data', Mock()) mock_asset.return_value = [ {'id': 'MA4B66MW5E27U9VBB94', 'bbid': 'AAPL UW'}, {'id': 'MA4B66MW5E27UAL9SUX', 'bbid': 'MSFT UW'}, ] mock_data = replace('gs_quant.timeseries.get_historical_and_last_for_measure', Mock()) mock_data.return_value = constituents_spot spx = Index('MA4B66MW5E27U8P32SB', AssetClass.Equity, 'SPX') a_basket = tm.Basket(['AAPL UW', 'MSFT UW'], [0.1, 0.9]) actual = tm.realized_correlation_with_basket(spx, '2d', a_basket) expected = pd.Series([np.nan, np.nan, -501.344109, -108.318770, -168.132382, 109.044958], index=dates) expected = ExtendedSeries(expected) assert_series_equal(actual, expected, check_dtype=False) with pytest.raises(NotImplementedError): tm.realized_correlation_with_basket(spx, '2d', a_basket, real_time=True) replace.restore() def test_real_corr(): spx = Index('MA4B66MW5E27U8P32SB', AssetClass.Equity, 'SPX') with pytest.raises(NotImplementedError): tm.realized_correlation(spx, '1m', real_time=True) replace = Replacer() replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_eq) actual = tm.realized_correlation(spx, '1m') assert_series_equal(pd.Series([3.14, 2.71828, 1.44], index=_index * 3), pd.Series(actual), check_names=False) assert actual.dataset_ids == _test_datasets replace.restore() def test_real_corr_missing(): spx = Index('MA4B66MW5E27U8P32SB', AssetClass.Equity, 'SPX') d = { 'assetId': ['MA4B66MW5E27U8P32SB'] * 3, 'spot': [3000, 3100, 3050], } df = MarketDataResponseFrame(data=d, index=pd.date_range('2020-08-01', periods=3, freq='D')) resources = os.path.join(os.path.dirname(__file__), '..', 'resources') weights = pd.read_csv(os.path.join(resources, 'SPX_50_weights.csv')).set_index('underlyingAssetId') replace = Replacer() replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', lambda *args, **kwargs: df) constituents = replace('gs_quant.timeseries.measures._get_index_constituent_weights', Mock()) constituents.return_value = weights last_mock = replace('gs_quant.timeseries.measures.get_last_for_measure', Mock()) last_mock.return_value = None with pytest.raises(MqValueError): tm.realized_correlation(spx, '1m', 50) replace.restore() def test_real_corr_n(): spx = Index('MA4B66MW5E27U8P32SB', AssetClass.Equity, 'SPX') with pytest.raises(MqValueError): tm.realized_correlation(spx, '1m', composition_date=dt.date.today()) with pytest.raises(MqValueError): tm.realized_correlation(spx, '1m', 200) mock_asset = GsAsset(asset_class='Equity', id='MA1234567890', type_='Custom Basket', name='test') mock_basket = CustomBasket(gs_asset=mock_asset, _finish_init=False) # Test baskets must have top n with pytest.raises(MqValueError): tm.realized_correlation(mock_basket, '1m', composition_date=dt.date.today()) mock_asset = GsAsset(asset_class='Equity', id='MA1234567890', type_='Custom Basket', name='test') mock_basket = CustomBasket(gs_asset=mock_asset, _finish_init=False) # Test baskets must have top n with pytest.raises(MqValueError): tm.realized_correlation(mock_basket, '1m', composition_date=None) resources = os.path.join(os.path.dirname(__file__), '..', 'resources') r_vol = pd.read_csv(os.path.join(resources, 'SPX_50_rcorr_in.csv')) r_vol.index = pd.to_datetime(r_vol['date']) weights = pd.read_csv(os.path.join(resources, 'SPX_50_weights.csv')).set_index('underlyingAssetId') replace = Replacer() market_data = replace('gs_quant.timeseries.econometrics.GsDataApi.get_market_data', Mock()) market_data.return_value = r_vol constituents = replace('gs_quant.timeseries.measures._get_index_constituent_weights', Mock()) constituents.return_value = weights last_mock = replace('gs_quant.timeseries.measures.get_last_for_measure', Mock()) last_mock.return_value = None expected = pd.read_csv(os.path.join(resources, 'SPX_50_rcorr_out.csv')) expected.index = pd.to_datetime(expected['date']) expected = ExtendedSeries(expected['value']) actual = tm.realized_correlation(spx, '1m', 50, dt.date(2020, 8, 31), source='PlotTool') assert_series_equal(actual, expected, check_names=False) mask = r_vol.index < '2020-08-31' assert not all(mask) market_data.return_value = r_vol[mask] last_mock.return_value = r_vol[~mask] actual = tm.realized_correlation(spx, '1m', 50, dt.date(2020, 8, 31), source='PlotTool') assert_series_equal(actual, expected, check_names=False) replace.restore() def test_cds_implied_vol(): replace = Replacer() mock_cds = Index('MA890', AssetClass.Equity, 'CDS') replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_eq) actual = tm.cds_implied_volatility(mock_cds, '1m', '5y', tm.CdsVolReference.DELTA_CALL, 10) assert_series_equal( pd.Series([5, 1, 2], index=_index * 3, name='impliedVolatilityByDeltaStrike'), pd.Series(actual) ) assert actual.dataset_ids == _test_datasets actual = tm.cds_implied_volatility(mock_cds, '1m', '5y', tm.CdsVolReference.FORWARD, 100) assert_series_equal( pd.Series([5, 1, 2], index=_index * 3, name='impliedVolatilityByDeltaStrike'), pd.Series(actual) ) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.cds_implied_volatility(..., '1m', '5y', tm.CdsVolReference.DELTA_PUT, 75, real_time=True) replace.restore() def test_implied_vol_credit(): replace = Replacer() mock_cds = Index('MA890', AssetClass.Equity, 'CDS') replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_eq) actual = tm.implied_volatility_credit(mock_cds, '1m', tm.CdsVolReference.DELTA_CALL, 10) assert_series_equal( pd.Series([5, 1, 2], index=_index * 3, name='impliedVolatilityByDeltaStrike'), pd.Series(actual) ) assert actual.dataset_ids == _test_datasets actual = tm.implied_volatility_credit(mock_cds, '1m', tm.CdsVolReference.DELTA_PUT, 10) assert_series_equal( pd.Series([5, 1, 2], index=_index * 3, name='impliedVolatilityByDeltaStrike'), pd.Series(actual) ) assert actual.dataset_ids == _test_datasets actual = tm.implied_volatility_credit(mock_cds, '1m', tm.CdsVolReference.FORWARD, 100) assert_series_equal( pd.Series([5, 1, 2], index=_index * 3, name='impliedVolatilityByDeltaStrike'), pd.Series(actual) ) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.implied_volatility_credit(..., '1m', tm.CdsVolReference.DELTA_PUT, 75, real_time=True) with pytest.raises(NotImplementedError): tm.implied_volatility_credit(..., '1m', "", 75) replace.restore() def test_absolute_strike_credit(): replace = Replacer() mock_cds = Index('MA890', AssetClass.Equity, 'CDS') replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_eq) actual = tm.absolute_strike_credit(mock_cds, '1m', tm.CdsVolReference.DELTA_CALL, 10) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='absoluteStrike'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets actual = tm.absolute_strike_credit(mock_cds, '1m', tm.CdsVolReference.DELTA_PUT, 10) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='absoluteStrike'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets actual = tm.absolute_strike_credit(mock_cds, '1m', tm.CdsVolReference.FORWARD, 100) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='absoluteStrike'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.absolute_strike_credit(..., '1m', tm.CdsVolReference.DELTA_PUT, 75, real_time=True) with pytest.raises(NotImplementedError): tm.absolute_strike_credit(..., '1m', "", 75) replace.restore() def test_option_premium_credit(): replace = Replacer() mock_cds = Index('MA890', AssetClass.Equity, 'CDS') replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_eq) actual = tm.option_premium_credit(mock_cds, '1m', tm.CdsVolReference.DELTA_CALL, 10) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='optionPremium'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets actual = tm.option_premium_credit(mock_cds, '1m', tm.CdsVolReference.DELTA_PUT, 10) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='optionPremium'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets actual = tm.option_premium_credit(mock_cds, '1m', tm.CdsVolReference.FORWARD, 100) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='optionPremium'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.option_premium_credit(..., '1m', tm.CdsVolReference.DELTA_PUT, 75, real_time=True) with pytest.raises(NotImplementedError): tm.option_premium_credit(..., '1m', "", 75) replace.restore() def test_cds_spreads(): replace = Replacer() replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_cds_spread) cds_asset = DefaultSwap('MAD6V3NP8ZB7HZTY', 'Lockheed_C_PFA_2y_USD') actual = tm.cds_spread(cds_asset, 100) assert_series_equal(pd.Series([0.000836], index=_index2, name='spreadAt100'), pd.Series(actual)) actual = tm.cds_spread(cds_asset, 250) assert_series_equal(pd.Series([0.000436], index=_index2, name='spreadAt250'), pd.Series(actual)) actual = tm.cds_spread(cds_asset, 500) assert_series_equal(pd.Series([0.00036], index=_index2, name='spreadAt500'), pd.Series(actual)) with pytest.raises(NotImplementedError): tm.cds_spread(cds_asset, 200) with pytest.raises(NotImplementedError): tm.cds_spread(cds_asset, 200, real_time=True) replace.restore() def test_avg_impl_vol(mocker): replace = Replacer() mock_spx = Index('MA890', AssetClass.Equity, 'SPX') replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_eq) last_mock = replace('gs_quant.timeseries.measures.get_last_for_measure', Mock()) last_mock.return_value = None actual = tm.average_implied_volatility(mock_spx, '1m', tm.EdrDataReference.DELTA_CALL, 25) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='averageImpliedVolatility'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets actual = tm.average_implied_volatility(mock_spx, '1m', tm.EdrDataReference.DELTA_PUT, 75) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='averageImpliedVolatility'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets replace.restore() df1 = pd.DataFrame( data={'impliedVolatility': [1, 2, 3], 'assetId': ['MA1', 'MA1', 'MA1']}, index=pd.date_range(start='2020-01-01', periods=3), ) df2 = pd.DataFrame( data={'impliedVolatility': [2, 3, 4], 'assetId': ['MA2', 'MA2', 'MA2']}, index=pd.date_range(start='2020-01-01', periods=3), ) df3 = pd.DataFrame( data={'impliedVolatility': [2, 5], 'assetId': ['MA3', 'MA3']}, index=pd.date_range(start='2020-01-01', periods=2), ) replace('gs_quant.api.gs.indices.GsIndexApi.get_positions_data', mock_index_positions_data) market_data_mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) mock_implied_vol = MarketDataResponseFrame(pd.concat([df1, df2, df3], join='inner')) mock_implied_vol.dataset_ids = _test_datasets market_data_mock.return_value = mock_implied_vol last_mock = replace('gs_quant.timeseries.measures.get_last_for_measure', Mock()) last_mock.return_value = None mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(OAuth2Session, '_authenticate', return_value=None) actual = tm.average_implied_volatility(mock_spx, '1m', tm.EdrDataReference.DELTA_CALL, 25, 3, '1d') expected = pd.Series([1.4, 2.6, 3.33333], index=pd.date_range(start='2020-01-01', periods=3)) expected.index.freq = None assert_series_equal(expected, pd.Series(actual), check_names=False) assert actual.dataset_ids == _test_datasets last_mock.return_value = pd.DataFrame( data={'impliedVolatility': [2], 'assetId': ['MA3']}, index=pd.date_range(start='2020-01-03', periods=1) ) actual = tm.average_implied_volatility(mock_spx, '1m', tm.EdrDataReference.DELTA_CALL, 25, 3, '1d') expected = pd.Series([1.4, 2.6, 3.2], index=pd.date_range(start='2020-01-01', periods=3)) expected.index.freq = None assert_series_equal(expected, pd.Series(actual), check_names=False) # Test weight_threshold paths mock_implied_vol = MarketDataResponseFrame(pd.concat([df2, df3], join='inner')) mock_implied_vol.dataset_ids = _test_datasets market_data_mock.return_value = mock_implied_vol with pytest.raises(MqValueError): tm.average_implied_volatility(mock_spx, '1m', tm.EdrDataReference.DELTA_PUT, 75, top_n_of_index=3) # Working case where MA3 is dropped mock_implied_vol = MarketDataResponseFrame(pd.concat([df1, df2], join='inner')) mock_implied_vol.dataset_ids = _test_datasets market_data_mock.return_value = mock_implied_vol last_mock.return_value = None tm.average_implied_volatility( mock_spx, '1m', tm.EdrDataReference.DELTA_PUT, 75, top_n_of_index=3, weight_threshold=0.11 ) mock_implied_vol = MarketDataResponseFrame(pd.concat([df1, df2], join='inner')) mock_implied_vol.dataset_ids = _test_datasets market_data_mock.return_value = mock_implied_vol with pytest.raises(MqValueError): tm.average_implied_volatility( mock_spx, '1m', tm.EdrDataReference.DELTA_PUT, 75, top_n_of_index=3, weight_threshold=0.01 ) with pytest.raises(MqValueError): tm.average_implied_volatility(mock_spx, '1m', tm.EdrDataReference.DELTA_PUT, 75, top_n_of_index=3) with pytest.raises(NotImplementedError): tm.average_implied_volatility(..., '1m', tm.EdrDataReference.DELTA_PUT, 75, real_time=True) with pytest.raises(MqValueError): tm.average_implied_volatility( mock_spx, '1m', tm.EdrDataReference.DELTA_PUT, 75, top_n_of_index=None, composition_date='1d' ) with pytest.raises(NotImplementedError): tm.average_implied_volatility(mock_spx, '1m', tm.EdrDataReference.DELTA_PUT, 75, top_n_of_index=101) replace.restore() def test_avg_realized_vol(): replace = Replacer() mock_spx = Index('MA890', AssetClass.Equity, 'SPX') replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_eq) actual = tm.average_realized_volatility(mock_spx, '1m') assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='averageRealizedVolatility'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets replace.restore() df1 = pd.DataFrame( data={'spot': [1, 2, 3], 'assetId': ['MA1', 'MA1', 'MA1']}, index=pd.date_range(start='2020-01-01', periods=3) ) df2 = pd.DataFrame( data={'spot': [2, 3, 4], 'assetId': ['MA2', 'MA2', 'MA2']}, index=pd.date_range(start='2020-01-01', periods=3) ) df3 = pd.DataFrame( data={'spot': [2, 2, 2], 'assetId': ['MA3', 'MA3', 'MA3']}, index=pd.date_range(start='2020-01-01', periods=3) ) mock_spot = MarketDataResponseFrame(pd.concat([df1, df2, df3], join='inner')) mock_spot.dataset_ids = _test_datasets replace('gs_quant.api.gs.indices.GsIndexApi.get_positions_data', mock_index_positions_data) last_mock = replace('gs_quant.timeseries.measures.get_last_for_measure', Mock()) last_mock.return_value = None market_data_mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) market_data_mock.return_value = mock_spot actual = tm.average_realized_volatility(mock_spx, '2d', Returns.SIMPLE, 3, '1d') expected = pd.Series( [392.874026], index=pd.date_range(start='2020-01-03', periods=1), name='averageRealizedVolatility' ) expected.index.freq = None assert_series_equal(expected, pd.Series(actual)) assert actual.dataset_ids == _test_datasets dfl = pd.DataFrame( data={'spot': [5, 5, 5], 'assetId': ['MA1', 'MA2', 'MA3']}, index=[pd.to_datetime('2020-01-04')] * 3 ) last_mock.return_value = dfl actual = tm.average_realized_volatility(mock_spx, '2d', Returns.SIMPLE, 3, '1d') expected = pd.Series( [392.874026, 308.686734], index=pd.to_datetime(['2020-01-03', '2020-01-04']), name='averageRealizedVolatility' ) assert_series_equal(expected, pd.Series(actual)) last_mock.return_value = None df4 = pd.DataFrame( data={'spot': [2, 2], 'assetId': ['MA3', 'MA3']}, index=pd.date_range(start='2020-01-01', periods=2) ) mock_spot_2 = MarketDataResponseFrame(pd.concat([df1, df2, df4], join='inner')) market_data_mock.return_value = mock_spot_2 actual = tm.average_realized_volatility(mock_spx, '2d', Returns.SIMPLE, 3, '1d') assert actual.dropna().empty with pytest.raises(NotImplementedError): tm.average_realized_volatility(mock_spx, '1w', real_time=True) with pytest.raises(MqValueError): tm.average_realized_volatility(mock_spx, '1w', composition_date='1d') with pytest.raises(MqValueError): tm.average_realized_volatility(mock_spx, '1w', Returns.SIMPLE) with pytest.raises(MqValueError): tm.average_realized_volatility(mock_spx, '1w', Returns.SIMPLE, 201) replace.restore() empty_positions_data_mock = replace('gs_quant.api.gs.indices.GsIndexApi.get_positions_data', Mock()) empty_positions_data_mock.return_value = [] with pytest.raises(MqValueError): tm.average_realized_volatility(mock_spx, '1w', Returns.SIMPLE, 5) replace.restore() def test_avg_impl_var(): replace = Replacer() mock_spx = Index('MA890', AssetClass.Equity, 'SPX') replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_eq) actual = tm.average_implied_variance(mock_spx, '1m', tm.EdrDataReference.DELTA_CALL, 25) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='averageImpliedVariance'), pd.Series(actual)) actual = tm.average_implied_variance(mock_spx, '1m', tm.EdrDataReference.DELTA_PUT, 75) assert actual.dataset_ids == _test_datasets assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='averageImpliedVariance'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.average_implied_variance(..., '1m', tm.EdrDataReference.DELTA_PUT, 75, real_time=True) replace.restore() def test_basis_swap_spread(mocker): replace = Replacer() args = dict( swap_tenor='10y', spread_benchmark_type=None, spread_tenor=None, reference_benchmark_type=None, reference_tenor=None, forward_tenor='0b', real_time=False, ) mock_nok = Currency('MA891', 'EGP') xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'EGP' args['asset'] = mock_nok with pytest.raises(NotImplementedError): tm_rates.basis_swap_spread(**args) mock_usd = Currency('MAZ7RWC904JYHYPS', 'USD') args['asset'] = mock_usd xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'USD' with pytest.raises(NotImplementedError): tm_rates.basis_swap_spread(..., '1y', real_time=True) args['swap_tenor'] = '5yr' with pytest.raises(MqValueError): tm_rates.basis_swap_spread(**args) args['swap_tenor'] = '6y' args['spread_tenor'] = '5yr' with pytest.raises(MqValueError): tm_rates.basis_swap_spread(**args) args['spread_tenor'] = '3m' args['reference_tenor'] = '5yr' with pytest.raises(MqValueError): tm_rates.basis_swap_spread(**args) args['reference_tenor'] = '6m' args['forward_tenor'] = '5yr' with pytest.raises(MqValueError): tm_rates.basis_swap_spread(**args) args['forward_tenor'] = None args['spread_benchmark_type'] = BenchmarkType.STIBOR with pytest.raises(MqValueError): tm_rates.basis_swap_spread(**args) args['spread_benchmark_type'] = BenchmarkType.LIBOR args['reference_benchmark_type'] = 'libor_3m' with pytest.raises(MqValueError): tm_rates.basis_swap_spread(**args) args['reference_benchmark_type'] = BenchmarkType.LIBOR xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'USD' identifiers = replace('gs_quant.timeseries.measures_rates._get_tdapi_rates_assets', Mock()) identifiers.return_value = {'MAQB1PGEJFCET3GG'} mocker.patch.object(GsDataApi, 'get_market_data', return_value=mock_curr(None, None)) actual = tm_rates.basis_swap_spread(**args) expected = tm.ExtendedSeries([1, 2, 3], index=_index * 3, name='basisSwapRate') expected.dataset_ids = _test_datasets assert_series_equal(expected, actual) assert actual.dataset_ids == expected.dataset_ids args['reference_benchmark_type'] = BenchmarkType.SOFR args['reference_tenor'] = '1y' args['reference_benchmark_type'] = BenchmarkType.LIBOR args['reference_tenor'] = '3m' xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'USD' identifiers = replace('gs_quant.timeseries.measures_rates._get_tdapi_rates_assets', Mock()) identifiers.return_value = {'MA06ATQ9CM0DCZFC'} mocker.patch.object(GsDataApi, 'get_market_data', return_value=mock_curr(None, None)) args['location'] = PricingLocation.NYC actual = tm_rates.basis_swap_spread(**args) expected = tm.ExtendedSeries([1, 2, 3], index=_index * 3, name='basisSwapRate') expected.dataset_ids = _test_datasets assert_series_equal(expected, actual) assert actual.dataset_ids == expected.dataset_ids replace.restore() def test_swap_rate(mocker): replace = Replacer() args = dict(swap_tenor='10y', benchmark_type=None, floating_rate_tenor=None, forward_tenor='0b', real_time=False) mock_usd = Currency('MAZ7RWC904JYHYPS', 'USD') args['asset'] = mock_usd xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'USD' args['swap_tenor'] = '5yr' with pytest.raises(MqValueError): tm_rates.swap_rate(**args) args['swap_tenor'] = '10y' args['floating_rate_tenor'] = '5yr' with pytest.raises(MqValueError): tm_rates.swap_rate(**args) args['floating_rate_tenor'] = '1y' args['forward_tenor'] = '5yr' with pytest.raises(MqValueError): tm_rates.swap_rate(**args) args['forward_tenor'] = None args['benchmark_type'] = BenchmarkType.STIBOR with pytest.raises(MqValueError): tm_rates.swap_rate(**args) args['benchmark_type'] = 'sonia' with pytest.raises(MqValueError): tm_rates.swap_rate(**args) args['benchmark_type'] = 'fed_funds' xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'USD' identifiers = replace('gs_quant.timeseries.measures_rates._get_tdapi_rates_assets', Mock()) identifiers.return_value = {'MAZ7RWC904JYHYPS'} mocker.patch.object(GsDataApi, 'get_market_data', return_value=mock_curr(None, None)) actual = tm_rates.swap_rate(**args) expected = tm.ExtendedSeries([1, 2, 3], index=_index * 3, name='swapRate') expected.dataset_ids = _test_datasets assert_series_equal(expected, actual) assert actual.dataset_ids == _test_datasets xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'EUR' identifiers = replace('gs_quant.timeseries.measures_rates._get_tdapi_rates_assets', Mock()) identifiers.return_value = {'MAJNQPFGN1EBDHAE'} mocker.patch.object(GsDataApi, 'get_market_data', return_value=mock_curr(None, None)) args['asset'] = Currency('MAJNQPFGN1EBDHAE', 'EUR') args['benchmark_type'] = 'estr' args['location'] = PricingLocation.LDN actual = tm_rates.swap_rate(**args) expected = tm.ExtendedSeries([1, 2, 3], index=_index * 3, name='swapRate') expected.dataset_ids = _test_datasets assert_series_equal(expected, actual) assert actual.dataset_ids == _test_datasets replace.restore() def test_swap_annuity(mocker): replace = Replacer() args = dict(swap_tenor='10y', benchmark_type=None, floating_rate_tenor=None, forward_tenor='0b', real_time=False) mock_nok = Currency('MA891', 'ACU') xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'ACU' args['asset'] = mock_nok with pytest.raises(NotImplementedError): tm_rates.swap_annuity(**args) mock_usd = Currency('MAZ7RWC904JYHYPS', 'USD') args['asset'] = mock_usd xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'USD' with pytest.raises(NotImplementedError): tm_rates.swap_annuity(..., '1y', real_time=True) args['swap_tenor'] = '5yr' with pytest.raises(MqValueError): tm_rates.swap_annuity(**args) args['swap_tenor'] = '10y' args['floating_rate_tenor'] = '5yr' with pytest.raises(MqValueError): tm_rates.swap_annuity(**args) args['floating_rate_tenor'] = '1y' args['forward_tenor'] = '5yr' with pytest.raises(MqValueError): tm_rates.swap_annuity(**args) args['forward_tenor'] = None args['benchmark_type'] = BenchmarkType.STIBOR with pytest.raises(MqValueError): tm_rates.swap_annuity(**args) args['benchmark_type'] = BenchmarkType.SOFR xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'USD' identifiers = replace('gs_quant.timeseries.measures_rates._get_tdapi_rates_assets', Mock()) identifiers.return_value = {'MAZ7RWC904JYHYPS'} mocker.patch.object(GsDataApi, 'get_market_data', return_value=mock_curr(None, None)) actual = tm_rates.swap_annuity(**args) expected = abs(tm.ExtendedSeries([1.0, 2.0, 3.0], index=_index * 3, name='swapAnnuity') * 1e4 / 1e8) expected.dataset_ids = _test_datasets assert_series_equal(expected, actual) assert actual.dataset_ids == expected.dataset_ids replace.restore() def test_swap_term_structure(): replace = Replacer() args = dict( benchmark_type=None, floating_rate_tenor=None, tenor_type=tm_rates._SwapTenorType.FORWARD_TENOR, tenor='0b', real_time=False, ) mock_nok = Currency('MA891', 'ACU') xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'ACU' args['asset'] = mock_nok with pytest.raises(NotImplementedError): tm_rates.swap_term_structure(**args) mock_usd = Currency('MAZ7RWC904JYHYPS', 'USD') args['asset'] = mock_usd xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'USD' with pytest.raises(NotImplementedError): tm_rates.swap_term_structure(..., '1y', real_time=True) args['floating_rate_tenor'] = '5yr' with pytest.raises(MqValueError): tm_rates.swap_term_structure(**args) args['floating_rate_tenor'] = '3m' args['tenor_type'] = 'expiry' with pytest.raises(MqValueError): tm_rates.swap_term_structure(**args) args['tenor_type'] = None args['tenor'] = '5yr' with pytest.raises(MqValueError): tm_rates.swap_term_structure(**args) args['tenor'] = None args['benchmark_type'] = BenchmarkType.STIBOR with pytest.raises(MqValueError): tm_rates.swap_term_structure(**args) args['benchmark_type'] = BenchmarkType.LIBOR bd_mock = replace('gs_quant.data.dataset.Dataset.get_data', Mock()) bd_mock.return_value = pd.DataFrame( data=dict(date="2020-04-10", exchange="NYC", description="Good Friday"), index=[pd.Timestamp('2020-04-10')] ) args['pricing_date'] = dt.date(2020, 4, 10) bd_mock = replace('gs_quant.datetime.gscalendar.GsCalendar.holidays', MagicMock()) bd_mock.__iter__.return_value = (dt.date(2020, 4, 10),) with pytest.raises(MqValueError): tm_rates.swap_term_structure(**args) args['pricing_date'] = None xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'USD' identifiers_empty = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) identifiers_empty.return_value = {} with pytest.raises(MqValueError): tm_rates.swap_term_structure(**args) identifiers = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) mock_asset = Currency('USD', name='USD') mock_asset.id = 'MAEMPCXQG3T716EX' mock_asset.exchange = 'OTC' identifiers.return_value = [mock_asset] d = { 'terminationTenor': ['1y', '2y', '3y', '4y'], 'swapRate': [1, 2, 3, 4], 'assetId': ['MAEMPCXQG3T716EX', 'MAFRSWPAF5QPNTP2', 'MA88BXZ3TCTXTFW1', 'MAC4KAG9B9ZAZHFT'], } pricing_date_mock = replace('gs_quant.timeseries.measures_rates._range_from_pricing_date', Mock()) pricing_date_mock.return_value = [dt.date(2019, 1, 1), dt.date(2019, 1, 1)] bd_mock.return_value = pd.DataFrame() market_data_mock = replace('gs_quant.timeseries.measures_rates._market_data_timed', Mock()) market_data_mock.return_value = pd.DataFrame() df = pd.DataFrame(data=d, index=_index * 4) assert tm_rates.swap_term_structure(**args).empty market_data_mock.return_value = df with DataContext('2019-01-01', '2025-01-01'): actual = tm_rates.swap_term_structure(**args) actual.dataset_ids = _test_datasets expected = tm.ExtendedSeries( [1, 2, 3, 4], index=pd.to_datetime(['2020-01-01', '2021-01-01', '2021-12-31', '2022-12-30']) ) expected.dataset_ids = _test_datasets assert_series_equal(expected, actual, check_names=False) assert actual.dataset_ids == expected.dataset_ids df = pd.DataFrame(data={'effectiveTenor': ['1y'], 'swapRate': [1], 'assetId': ['MAEMPCXQG3T716EX']}, index=_index) market_data_mock.return_value = df args['tenor_type'] = 'swap_tenor' args['tenor'] = '5y' args['location'] = PricingLocation.NYC with DataContext('2019-01-01', '2025-01-01'): actual = tm_rates.swap_term_structure(**args) actual.dataset_ids = _test_datasets expected = tm.ExtendedSeries([1], index=pd.to_datetime(['2020-01-01'])) expected.dataset_ids = _test_datasets assert_series_equal(expected, actual, check_names=False) assert actual.dataset_ids == expected.dataset_ids d = { 'effectiveTenor': ['1y', '2y', '3y', '4y'], 'swapRate': [1, 2, 3, 4], 'assetId': ['MAEMPCXQG3T716EX', 'MAFRSWPAF5QPNTP2', 'MA88BXZ3TCTXTFW1', 'MAC4KAG9B9ZAZHFT'], } df = pd.DataFrame(data=d, index=_index * 4) market_data_mock.return_value = df args['tenor_type'] = 'swap_tenor' args['tenor'] = '5yr' with pytest.raises(MqValueError): tm_rates.swap_term_structure(**args) args['tenor'] = '5y' market_data_mock.return_value = pd.DataFrame() df = pd.DataFrame(data=d, index=_index * 4) assert tm_rates.swap_term_structure(**args).empty market_data_mock.return_value = df with DataContext('2019-01-01', '2025-01-01'): actual = tm_rates.swap_term_structure(**args) actual.dataset_ids = _test_datasets expected = tm.ExtendedSeries( [1, 2, 3, 4], index=pd.to_datetime(['2020-01-01', '2021-01-01', '2021-12-31', '2022-12-30']) ) expected.dataset_ids = _test_datasets assert_series_equal(expected, actual, check_names=False) assert actual.dataset_ids == expected.dataset_ids with patch('gs_quant.datetime.GsCalendar.holidays', (dt.date(2023, 1, 1),)): args['pricing_date'] = dt.date(2023, 1, 1) with pytest.raises(MqValueError): tm_rates.swap_term_structure(**args) replace.restore() def test_basis_swap_term_structure(): replace = Replacer() range_mock = replace('gs_quant.timeseries.measures_rates._range_from_pricing_date', Mock()) range_mock.return_value = [dt.date(2019, 1, 1), dt.date(2019, 1, 1)] args = dict( spread_benchmark_type=None, spread_tenor=None, reference_benchmark_type=None, reference_tenor=None, tenor_type=tm_rates._SwapTenorType.FORWARD_TENOR, tenor='0b', real_time=False, ) mock_nok = Currency('MA891', 'EGP') xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'EGP' args['asset'] = mock_nok with pytest.raises(NotImplementedError): tm_rates.basis_swap_term_structure(**args) mock_usd = Currency('MAZ7RWC904JYHYPS', 'USD') args['asset'] = mock_usd xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'USD' with pytest.raises(NotImplementedError): tm_rates.basis_swap_term_structure(..., '1y', real_time=True) args['spread_tenor'] = '5yr' with pytest.raises(MqValueError): tm_rates.basis_swap_term_structure(**args) args['spread_tenor'] = '3m' args['reference_tenor'] = '5yr' with pytest.raises(MqValueError): tm_rates.basis_swap_term_structure(**args) args['reference_tenor'] = '6m' args['tenor_type'] = 'expiry' with pytest.raises(MqValueError): tm_rates.basis_swap_term_structure(**args) args['tenor_type'] = 'forward_tenor' args['tenor'] = '5yr' with pytest.raises(MqValueError): tm_rates.basis_swap_term_structure(**args) args['tenor'] = None args['spread_benchmark_type'] = BenchmarkType.STIBOR with pytest.raises(MqValueError): tm_rates.basis_swap_term_structure(**args) args['spread_benchmark_type'] = BenchmarkType.LIBOR args['reference_benchmark_type'] = BenchmarkType.STIBOR with pytest.raises(MqValueError): tm_rates.basis_swap_term_structure(**args) args['reference_benchmark_type'] = BenchmarkType.LIBOR bd_mock = replace('gs_quant.data.dataset.Dataset.get_data', Mock()) bd_mock.return_value = pd.DataFrame( data=dict(date="2020-04-10", exchange="NYC", description="Good Friday"), index=[pd.Timestamp('2020-04-10')] ) args['pricing_date'] = dt.date(2020, 4, 10) bd_mock = replace('gs_quant.datetime.gscalendar.GsCalendar.holidays', MagicMock()) bd_mock.__iter__.return_value = (dt.date(2020, 4, 10),) with pytest.raises(MqValueError): tm_rates.basis_swap_term_structure(**args) args['pricing_date'] = None xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'USD' identifiers_empty = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) identifiers_empty.return_value = {} with pytest.raises(MqValueError): tm_rates.basis_swap_term_structure(**args) identifiers = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) mock_asset = Currency('USD', name='USD') mock_asset.id = 'MAEMPCXQG3T716EX' mock_asset.exchange = 'OTC' identifiers.return_value = [mock_asset] d = { 'terminationTenor': ['1y', '2y', '3y', '4y'], 'basisSwapRate': [1, 2, 3, 4], 'assetId': ['MAEMPCXQG3T716EX', 'MAFRSWPAF5QPNTP2', 'MA88BXZ3TCTXTFW1', 'MAC4KAG9B9ZAZHFT'], } pricing_date_mock = replace('gs_quant.timeseries.measures_rates._range_from_pricing_date', Mock()) pricing_date_mock.return_value = [dt.date(2019, 1, 1), dt.date(2019, 1, 1)] bd_mock.return_value = pd.DataFrame() market_data_mock = replace('gs_quant.timeseries.measures_rates._market_data_timed', Mock()) market_data_mock.return_value = pd.DataFrame() assert tm_rates.basis_swap_term_structure(**args).empty df = pd.DataFrame(data=d, index=_index * 4) market_data_mock.return_value = df with DataContext('2019-01-01', '2025-01-01'): actual = tm_rates.basis_swap_term_structure(**args) actual.dataset_ids = _test_datasets expected = tm.ExtendedSeries( [1, 2, 3, 4], index=pd.to_datetime(['2020-01-01', '2021-01-01', '2021-12-31', '2022-12-30']) ) expected.dataset_ids = _test_datasets assert_series_equal(expected, actual, check_names=False) assert actual.dataset_ids == expected.dataset_ids d = { 'effectiveTenor': ['1y', '2y', '3y', '4y'], 'basisSwapRate': [1, 2, 3, 4], 'assetId': ['MAEMPCXQG3T716EX', 'MAFRSWPAF5QPNTP2', 'MA88BXZ3TCTXTFW1', 'MAC4KAG9B9ZAZHFT'], } bd_mock.return_value = pd.DataFrame() market_data_mock = replace('gs_quant.timeseries.measures_rates._market_data_timed', Mock()) df = pd.DataFrame(data=d, index=_index * 4) market_data_mock.return_value = df args['tenor_type'] = tm_rates._SwapTenorType.SWAP_TENOR args['tenor'] = '5y' with DataContext('2019-01-01', '2025-01-01'): actual = tm_rates.basis_swap_term_structure(**args) actual.dataset_ids = _test_datasets expected = tm.ExtendedSeries( [1, 2, 3, 4], index=pd.to_datetime(['2020-01-01', '2021-01-01', '2021-12-31', '2022-12-30']) ) expected.dataset_ids = _test_datasets assert_series_equal(expected, actual, check_names=False) assert actual.dataset_ids == expected.dataset_ids df = pd.DataFrame( data={'effectiveTenor': ['1y'], 'basisSwapRate': [1], 'assetId': ['MAEMPCXQG3T716EX']}, index=_index ) market_data_mock.return_value = df with DataContext('2019-01-01', '2025-01-01'): actual = tm_rates.basis_swap_term_structure(**args) actual.dataset_ids = _test_datasets expected = tm.ExtendedSeries([1], index=pd.to_datetime(['2020-01-01'])) expected.dataset_ids = _test_datasets assert_series_equal(expected, actual, check_names=False) assert actual.dataset_ids == expected.dataset_ids with patch('gs_quant.datetime.GsCalendar.holidays', (dt.date(2023, 1, 1),)): args['pricing_date'] = dt.date(2023, 1, 1) with pytest.raises(MqValueError): tm_rates.basis_swap_term_structure(**args) replace.restore() def test_cap_floor_vol(): replace = Replacer() mock_usd = Currency('MA890', 'USD') xrefs = replace('gs_quant.timeseries.measures.GsAssetApi.get_asset_xrefs', Mock()) xrefs.return_value = [ GsTemporalXRef( dt.date(2019, 1, 1), dt.date(2952, 12, 31), XRef( bbid='USD', ), ) ] identifiers = replace('gs_quant.timeseries.measures.GsAssetApi.map_identifiers', Mock()) identifiers.return_value = {'USD-LIBOR-BBA': 'MA123'} replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_curr) actual = tm.cap_floor_vol(mock_usd, '5y', 50) assert_series_equal(pd.Series([1, 2, 3], index=_index * 3, name='capFloorVol'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.cap_floor_vol(..., '5y', 50, real_time=True) replace.restore() def test_cap_floor_atm_fwd_rate(): replace = Replacer() mock_usd = Currency('MA890', 'USD') xrefs = replace('gs_quant.timeseries.measures.GsAssetApi.get_asset_xrefs', Mock()) xrefs.return_value = [ GsTemporalXRef( dt.date(2019, 1, 1), dt.date(2952, 12, 31), XRef( bbid='USD', ), ) ] identifiers = replace('gs_quant.timeseries.measures.GsAssetApi.map_identifiers', Mock()) identifiers.return_value = {'USD-LIBOR-BBA': 'MA123'} replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_curr) actual = tm.cap_floor_atm_fwd_rate(mock_usd, '5y') assert_series_equal(pd.Series([1, 2, 3], index=_index * 3, name='capFloorAtmFwdRate'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.cap_floor_atm_fwd_rate(..., '5y', real_time=True) replace.restore() def test_spread_option_vol(): replace = Replacer() mock_usd = Currency('MA890', 'USD') xrefs = replace('gs_quant.timeseries.measures.GsAssetApi.get_asset_xrefs', Mock()) xrefs.return_value = [ GsTemporalXRef( dt.date(2019, 1, 1), dt.date(2952, 12, 31), XRef( bbid='USD', ), ) ] identifiers = replace('gs_quant.timeseries.measures.GsAssetApi.map_identifiers', Mock()) identifiers.return_value = {'USD-LIBOR-BBA': 'MA123'} replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_curr) actual = tm.spread_option_vol(mock_usd, '3m', '10y', '5y', 50) assert_series_equal(pd.Series([1, 2, 3], index=_index * 3, name='spreadOptionVol'), pd.Series(actual)) # Check null/no identifier cases identifiers.return_value = {None: 'MA123'} actual = tm.spread_option_vol(mock_usd, '3m', '10y', '5y', 50) assert_series_equal(pd.Series([1, 2, 3], index=_index * 3, name='spreadOptionVol'), pd.Series(actual)) identifiers.return_value = {} with pytest.raises(MqValueError): tm.spread_option_vol(mock_usd, '3m', '10y', '5y', 50) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.spread_option_vol(..., '3m', '10y', '5y', 50, real_time=True) replace.restore() def test_spread_option_atm_fwd_rate(): replace = Replacer() mock_usd = Currency('MA890', 'USD') xrefs = replace('gs_quant.timeseries.measures.GsAssetApi.get_asset_xrefs', Mock()) xrefs.return_value = [ GsTemporalXRef( dt.date(2019, 1, 1), dt.date(2952, 12, 31), XRef( bbid='USD', ), ) ] identifiers = replace('gs_quant.timeseries.measures.GsAssetApi.map_identifiers', Mock()) identifiers.return_value = {'USD-LIBOR-BBA': 'MA123'} replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_curr) actual = tm.spread_option_atm_fwd_rate(mock_usd, '3m', '10y', '5y') assert_series_equal(pd.Series([1, 2, 3], index=_index * 3, name='spreadOptionAtmFwdRate'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.spread_option_atm_fwd_rate(..., '3m', '10y', '5y', real_time=True) replace.restore() def test_zc_inflation_swap_rate(): replace = Replacer() mock_gbp = Currency('MA890', 'GBP') xrefs = replace('gs_quant.timeseries.measures.GsAssetApi.get_asset_xrefs', Mock()) xrefs.return_value = [ GsTemporalXRef( dt.date(2019, 1, 1), dt.date(2952, 12, 31), XRef( bbid='GBP', ), ) ] identifiers = replace('gs_quant.timeseries.measures.GsAssetApi.map_identifiers', Mock()) identifiers.return_value = {'CPI-UKRPI': 'MA123'} replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_curr) actual = tm.zc_inflation_swap_rate(mock_gbp, '1y') assert_series_equal(pd.Series([1, 2, 3], index=_index * 3, name='inflationSwapRate'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.zc_inflation_swap_rate(..., '1y', real_time=True) replace.restore() def test_basis(): replace = Replacer() mock_jpyusd = Cross('MA890', 'USD/JPY') xrefs = replace('gs_quant.timeseries.measures.GsAssetApi.get_asset_xrefs', Mock()) xrefs.return_value = [ GsTemporalXRef( dt.date(2019, 1, 1), dt.date(2952, 12, 31), XRef( bbid='JPYUSD', ), ) ] identifiers = replace('gs_quant.timeseries.measures.GsAssetApi.map_identifiers', Mock()) identifiers.return_value = {'USD-3m/JPY-3m': 'MA123'} replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_cross) actual = tm.basis(mock_jpyusd, '1y') assert_series_equal(pd.Series([1, 2, 3], index=_index * 3, name='basis'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.basis(..., '1y', real_time=True) replace.restore() def test_td(): cases = { '3d': pd.DateOffset(days=3), '9w': pd.DateOffset(weeks=9), '2m': pd.DateOffset(months=2), '10y': pd.DateOffset(years=10), } for k, v in cases.items(): actual = tm._to_offset(k) assert v == actual, f'expected {v}, got actual {actual}' with pytest.raises(ValueError): tm._to_offset('5z') def test_pricing_range(): given = dt.date(2019, 4, 20) s, e = tm._range_from_pricing_date('NYSE', given) assert s == e == given class MockDate(dt.date): @classmethod def today(cls): return cls(2019, 5, 25) # mock replace = Replacer() cbd = replace('gs_quant.timeseries.measures._get_custom_bd', Mock()) cbd.return_value = pd.tseries.offsets.BusinessDay() today = replace('gs_quant.timeseries.measures.pd.Timestamp.today', Mock()) today.return_value = pd.Timestamp(2019, 5, 25) gold = dt.date dt.date = MockDate # cases s, e = tm._range_from_pricing_date('ANY') assert s == pd.Timestamp(2019, 5, 24) assert e == pd.Timestamp(2019, 5, 24) s, e = tm._range_from_pricing_date('ANY', '3m') assert s == pd.Timestamp(2019, 2, 22) assert e == pd.Timestamp(2019, 2, 24) s, e = tm._range_from_pricing_date('ANY', '3b') assert s == e == pd.Timestamp(2019, 5, 22) # restore dt.date = gold replace.restore() def test_var_swap_tenors(): session = GsSession.get(Environment.DEV, token='faux') replace = Replacer() get_mock = replace('gs_quant.session.GsSession._get', Mock()) get_mock.return_value = { 'data': [{'dataField': 'varSwap', 'filteredFields': [{'field': 'tenor', 'values': ['abc', 'xyc']}]}] } with session: actual = tm._var_swap_tenors(Index('MAXXX', AssetClass.Equity, 'XXX')) assert actual == ['abc', 'xyc'] get_mock.return_value = {'data': []} with pytest.raises(MqError): with session: tm._var_swap_tenors(Index('MAXXX', AssetClass.Equity, 'XXX')) replace.restore() def test_forward_var_term(): data = { 'varSwap': [21, 20.5, 20, 19.7, 20.3], 'date': [ dt.date(2023, 2, 15), dt.date(2023, 2, 22), dt.date(2023, 3, 1), dt.date(2023, 3, 8), dt.date(2023, 3, 15), ], } out = ExtendedSeries(data=data['varSwap'], name='varSwap', index=[pd.Timestamp(d) for d in data['date']]) out.dataset_ids = _test_datasets out.attrs = dict(latest=dt.date(2023, 1, 31)) replace = Replacer() market_mock = replace('gs_quant.timeseries.measures.var_term', Mock()) market_mock.return_value = out # Equity expected = pd.Series( [np.nan, 19.05194455628533, 18.02476051953111, 18.088723159586152, 22.74345522665404], name='forwardVarTerm', index=_make_index(['2023-02-15', '2023-02-22', '2023-03-01', '2023-03-08', '2023-03-15']), ) with DataContext('2023-01-31', '2024-07-31'): actual = tm.forward_var_term(Index('MA123', AssetClass.Equity, '123')) assert_series_equal(expected, pd.Series(actual)) assert actual.dataset_ids == _test_datasets market_mock.assert_called_once() # FX cross_mock = replace('gs_quant.timeseries.measures.cross_stored_direction_for_fx_vol', Mock()) cross_mock.return_value = 'EURUSD' with DataContext('2023-01-31', '2024-07-31'): actual_fx = tm.forward_var_term(Cross('ABCDE', 'EURUSD')) assert_series_equal(expected, pd.Series(actual_fx)) assert actual_fx.dataset_ids == _test_datasets # no data market_mock.reset_mock() market_mock.return_value = mock_empty_market_data_response() actual = tm.forward_var_term(Index('MA123', AssetClass.Equity, '123')) assert actual.empty replace.restore() # real-time with pytest.raises(NotImplementedError): tm.forward_var_term(..., real_time=True) def _mock_var_swap_data(_cls, q, ignore_errors=False): queries = q.get('queries', []) if len(queries) > 0 and 'Last' in queries[0]['measures']: return MarketDataResponseFrame({'varSwap': [4]}, index=[pd.Timestamp('2019-01-04T12:00:00Z')]) idx = pd.date_range(start="2019-01-01", periods=3, freq="D") data = {'varSwap': [1, 2, 3]} out = MarketDataResponseFrame(data=data, index=idx) out.dataset_ids = _test_datasets return out def test_var_swap(): replace = Replacer() replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', _mock_var_swap_data) expected = pd.Series([1, 2, 3, 4], name='varSwap', index=pd.date_range("2019-01-01", periods=4, freq="D")) expected.index.freq = None actual = tm.var_swap(Index('MA123', AssetClass.Equity, '123'), '1m') assert_series_equal(expected, pd.Series(actual)) assert actual.dataset_ids == _test_datasets market_mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) market_mock.return_value = mock_empty_market_data_response() actual = tm.var_swap(Index('MA123', AssetClass.Equity, '123'), '1m') assert actual.empty replace.restore() def _mock_var_swap_fwd(_cls, q, ignore_errors=False): queries = q.get('queries', []) if len(queries) > 0 and 'Last' in queries[0]['measures']: return MarketDataResponseFrame( {'varSwap': [4, 4.5], 'tenor': ['1y', '13m']}, index=[pd.Timestamp('2019-01-04T12:00:00Z')] * 2 ) idx = pd.date_range(start="2019-01-01", periods=3, freq="D") d1 = {'varSwap': [1, 2, 3], 'tenor': ['1y'] * 3} d2 = {'varSwap': [1.5, 2.5, 3.5], 'tenor': ['13m'] * 3} df1 = MarketDataResponseFrame(data=d1, index=idx) df2 = MarketDataResponseFrame(data=d2, index=idx) out = pd.concat([df1, df2]) out.dataset_ids = _test_datasets return out def _mock_var_swap_1t(_cls, q, ignore_errors=False): queries = q.get('queries', []) if len(queries) > 0 and 'Last' in queries[0]['measures']: return MarketDataResponseFrame( {'varSwap': [4, 4.5], 'tenor': ['1y', '13m']}, index=[pd.Timestamp('2019-01-04T12:00:00Z')] ) idx = pd.date_range(start="2019-01-01", periods=3, freq="D") d1 = {'varSwap': [1, 2, 3], 'tenor': ['1y'] * 3} df1 = MarketDataResponseFrame(data=d1, index=idx) df1.dataset_ids = _test_datasets return df1 def test_var_swap_fwd(): # bad input with pytest.raises(MqError): tm.var_swap(Index('MA123', AssetClass.Equity, '123'), '1m', 500) # regular replace = Replacer() replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', _mock_var_swap_fwd) tenors_mock = replace('gs_quant.timeseries.measures._var_swap_tenors', Mock()) tenors_mock.return_value = ['1m', '1y', '13m'] expected = pd.Series( [4.1533, 5.7663, 7.1589, 8.4410], name='varSwap', index=pd.date_range(start="2019-01-01", periods=4, freq="D") ) expected.index.freq = None actual = tm.var_swap(Index('MA123', AssetClass.Equity, '123'), '1m', '1y') assert_series_equal(expected, pd.Series(actual)) assert actual.dataset_ids == _test_datasets # no data market_mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) market_mock.return_value = mock_empty_market_data_response() actual = tm.var_swap(Index('MA123', AssetClass.Equity, '123'), '1m', '1y') assert actual.empty assert actual.dataset_ids == () # no data for a tenor replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', _mock_var_swap_1t) actual = tm.var_swap(Index('MA123', AssetClass.Equity, '123'), '1m', '1y') assert actual.empty assert actual.dataset_ids == () # no such tenors tenors_mock.return_value = [] actual = tm.var_swap(Index('MA123', AssetClass.Equity, '123'), '1m', '1y') assert actual.empty assert actual.dataset_ids == () # finish replace.restore() def _var_term_typical(): assert DataContext.current_is_set data = {'tenor': ['1w', '2w', '1y', '2y'], 'varSwap': [1, 2, 3, 4]} out = MarketDataResponseFrame(data=data, index=pd.DatetimeIndex(['2018-01-01'] * 4)) out.dataset_ids = _test_datasets replace = Replacer() market_mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) market_mock.side_effect = [pd.DataFrame(), out] actual = tm.var_term(Index('MA123', AssetClass.Equity, '123')) idx = _make_index(['2018-01-08', '2018-01-15', '2019-01-01', '2020-01-01'], name='varSwap') expected = pd.Series([1, 2, 3, 4], name='varSwap', index=idx) expected = expected.loc[pd.Timestamp(DataContext.current.start_date) : pd.Timestamp(DataContext.current.end_date)] if expected.empty: assert actual.empty else: assert_series_equal(expected, pd.Series(actual), check_names=False) assert actual.dataset_ids == _test_datasets replace.restore() return actual def _var_term_empty(): replace = Replacer() market_mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) market_mock.return_value = mock_empty_market_data_response() actual = tm.var_term(Index('MAXYZ', AssetClass.Equity, 'XYZ')) assert actual.empty assert actual.dataset_ids == () replace.restore() def _var_term_fwd(): idx = pd.date_range('2018-01-01', periods=2, freq='D') def mock_var_swap(_asset, tenor, _forward_start_date, **_kwargs): if tenor == '1m': series = tm.ExtendedSeries([1, 2], idx, name='varSwap') series.dataset_ids = _test_datasets elif tenor == '2m': series = tm.ExtendedSeries([3, 4], idx, name='varSwap') series.dataset_ids = _test_datasets else: series = tm.ExtendedSeries(dtype=float) series.dataset_ids = () return series replace = Replacer() market_mock = replace('gs_quant.timeseries.measures.var_swap', Mock()) market_mock.side_effect = mock_var_swap tenors_mock = replace('gs_quant.timeseries.measures._var_swap_tenors', Mock()) tenors_mock.return_value = ['1m', '2m', '3m'] actual = tm.var_term(Index('MA123', AssetClass.Equity, '123'), forward_start_date='1m') idx = _make_index(['2018-02-02', '2018-03-02'], name='varSwap') expected = pd.Series([2, 4], name='varSwap', index=idx) expected = expected.loc[pd.Timestamp(DataContext.current.start_date) : pd.Timestamp(DataContext.current.end_date)] if expected.empty: assert actual.empty else: assert_series_equal(expected, pd.Series(actual), check_names=False) assert actual.dataset_ids == _test_datasets market_mock.assert_called() replace.restore() return actual def _var_term_latest(): assert DataContext.current_is_set data = {'tenor': ['1w', '2w', '1y', '2y', '1w', '2w', '1y', '2y'], 'varSwap': [10, 20, 30, 40, 1, 2, 3, 4]} out = MarketDataResponseFrame(data=data, index=pd.date_range("2018-01-01", periods=8, freq="min")) out.dataset_ids = _test_datasets replace = Replacer() market_mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) market_mock.side_effect = [out.iloc[-1:], out] actual = tm.var_term(Index('MA123', AssetClass.Equity, '123')) idx = _make_index(['2018-01-08', '2018-01-15', '2019-01-01', '2020-01-01'], name='expirationDate') expected = pd.Series([1, 2, 3, 4], name='varSwap', index=idx) expected = expected.loc[pd.Timestamp(DataContext.current.start_date) : pd.Timestamp(DataContext.current.end_date)] if expected.empty: assert actual.empty else: assert_series_equal(expected, pd.Series(actual)) assert actual.dataset_ids == _test_datasets replace.restore() def test_var_term(): with DataContext('2018-01-01', '2019-01-01'): _var_term_typical() _var_term_empty() _var_term_fwd() _var_term_latest() with DataContext('2019-01-01', '2019-07-04'): _var_term_fwd() with DataContext('2018-01-16', '2018-12-31'): out = _var_term_typical() assert out.empty assert out.dataset_ids == _test_datasets with pytest.raises(MqError): tm.var_term(..., pricing_date=300) with pytest.raises(NotImplementedError): tm.var_term(..., real_time=True) def _mock_forward_helper(): idx = pd.DatetimeIndex([dt.date(2020, 5, 1), dt.date(2020, 5, 2)] * 4) data = { 'impliedVolatility': [2.1, 2, 3.1, 3, 4.1, 4, 5.1, 5], 'tenor': ['1m', '1m', '2m', '2m', '3m', '3m', '4m', '4m'], } out = MarketDataResponseFrame(data=data, index=idx) out.dataset_ids = _test_datasets return out def _mock_forward_vol_data(_cls, q, ignore_errors=False): queries = q.get('queries', []) if len(queries) > 0 and 'Last' in queries[0]['measures']: return MarketDataResponseFrame() return _mock_forward_helper() def _mock_forward_vol_data_with_last(_cls, q, ignore_errors=False): queries = q.get('queries', []) if len(queries) > 0 and 'Last' in queries[0]['measures']: idx = [pd.Timestamp('2020-05-03T12:00:00Z')] * 4 data = {'impliedVolatility': [2, 3, 4, 5], 'tenor': ['1m', '2m', '3m', '4m']} ids = _test_datasets_rt out = MarketDataResponseFrame(data=data, index=idx) out.dataset_ids = ids return out return _mock_forward_helper() def _mock_forward_vol_data_error(_cls, q, ignore_errors=False): queries = q.get('queries', []) if len(queries) > 0 and 'Last' in queries[0]['measures']: raise MqValueError('something happened') return _mock_forward_helper() def test_forward_vol(): idx = pd.DatetimeIndex([dt.date(2020, 5, 1), dt.date(2020, 5, 2)] * 4) data = { 'impliedVolatility': [2.1, 2, 3.1, 3, 4.1, 4, 5.1, 5], 'tenor': ['1m', '1m', '2m', '2m', '3m', '3m', '4m', '4m'], } out = MarketDataResponseFrame(data=data, index=idx) out.dataset_ids = _test_datasets replace = Replacer() market_mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) market_mock.side_effect = _mock_forward_vol_data # Equity expected = pd.Series([5.58659, 5.47723], name='forwardVol', index=_make_index(['2020-05-01', '2020-05-02'])) with DataContext('2020-01-01', '2020-09-01'): actual = tm.forward_vol(Index('MA123', AssetClass.Equity, '123'), '1m', '2m', tm.VolReference.SPOT, 100) assert_series_equal(expected, pd.Series(actual)) assert actual.dataset_ids == _test_datasets market_mock.assert_called_once() # FX cross_mock = replace('gs_quant.timeseries.measures.cross_stored_direction_for_fx_vol', Mock()) cross_mock.return_value = 'EURUSD' with DataContext('2020-01-01', '2020-09-01'): actual_fx = tm.forward_vol(Cross('ABCDE', 'EURUSD'), '1m', '2m', tm.VolReference.SPOT, 100) assert_series_equal(expected, pd.Series(actual_fx)) assert actual_fx.dataset_ids == _test_datasets # EQ with last market_mock.reset_mock() market_mock.side_effect = _mock_forward_vol_data_with_last expected = pd.Series( [5.58659, 5.47723, 5.47723], name='forwardVol', index=_make_index(['2020-05-01', '2020-05-02', '2020-05-03']) ) actual = tm.forward_vol(Index('MA123', AssetClass.Equity, '123'), '1m', '2m', tm.VolReference.SPOT, 100) assert_series_equal(expected, pd.Series(actual)) for t in _test_datasets + _test_datasets_rt: assert t in actual.dataset_ids assert market_mock.call_count == 2 # EQ with exception on last market_mock.reset_mock() market_mock.side_effect = _mock_forward_vol_data_error expected = pd.Series([5.58659, 5.47723], name='forwardVol', index=_make_index(['2020-05-01', '2020-05-02'])) actual = tm.forward_vol(Index('MA123', AssetClass.Equity, '123'), '1m', '2m', tm.VolReference.SPOT, 100) assert_series_equal(expected, pd.Series(actual)) assert market_mock.call_count == 2 # no data market_mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) market_mock.return_value = mock_empty_market_data_response() actual = tm.forward_vol(Index('MA123', AssetClass.Equity, '123'), '1m', '2m', tm.VolReference.SPOT, 100) assert actual.empty # no data for required tenor market_mock.reset_mock() market_mock.return_value = MarketDataResponseFrame( data={'impliedVolatility': [2.1, 3.1, 5.1], 'tenor': ['1m', '2m', '4m']}, index=[dt.date(2020, 5, 1)] * 3 ) with DataContext('2020-01-01', '2020-09-01'): actual = tm.forward_vol(Index('MA123', AssetClass.Equity, '123'), '1m', '2m', tm.VolReference.SPOT, 100) assert actual.empty # real-time with pytest.raises(NotImplementedError): tm.forward_vol(..., '1m', '2m', tm.VolReference.SPOT, 100, real_time=True) replace.restore() def test_forward_vol_term(): data = { 'impliedVolatility': [31, 27, 28, 21, 21.5], 'date': [ dt.date(2023, 2, 1), dt.date(2023, 2, 2), dt.date(2023, 2, 3), dt.date(2023, 2, 6), dt.date(2023, 2, 7), ], } out = ExtendedSeries( data=data['impliedVolatility'], name='impliedVolatility', index=[pd.Timestamp(d) for d in data['date']] ) out.dataset_ids = _test_datasets out.attrs = dict(latest=dt.date(2023, 1, 31)) replace = Replacer() market_mock = replace('gs_quant.timeseries.measures.vol_term', Mock()) market_mock.return_value = out # Equity expected = pd.Series( [np.nan, 18.51754260110053, 24.835557737474247, 14.242257163702778, 20.171543580777623], name='forwardVolTerm', index=_make_index(['2023-02-01', '2023-02-02', '2023-02-03', '2023-02-06', '2023-02-07']), ) with DataContext('2023-01-31', '2024-07-31'): actual = tm.forward_vol_term( Index('MA123', AssetClass.Equity, '123'), tm.VolReference.SPOT, 100, dt.date(2023, 1, 31) ) assert_series_equal(expected, pd.Series(actual)) assert actual.dataset_ids == _test_datasets market_mock.assert_called_once() # FX cross_mock = replace('gs_quant.timeseries.measures.cross_stored_direction_for_fx_vol', Mock()) cross_mock.return_value = 'EURUSD' with DataContext('2023-01-31', '2024-07-31'): actual_fx = tm.forward_vol_term(Cross('ABCDE', 'EURUSD'), tm.VolReference.SPOT, 100) assert_series_equal(expected, pd.Series(actual_fx)) assert actual_fx.dataset_ids == _test_datasets # no data market_mock.reset_mock() market_mock.return_value = mock_empty_market_data_response() actual = tm.forward_vol_term(Index('MA123', AssetClass.Equity, '123'), tm.VolReference.SPOT, 100) assert actual.empty replace.restore() # real-time with pytest.raises(NotImplementedError): tm.forward_vol_term(..., tm.VolReference.SPOT, 100, real_time=True) def test_get_latest_term_structure_data(): # Test latest_term_structure_data where no data is returned replace = Replacer() data = {'tenor': ['1w', '2w', '1y', '2y'], 'impliedVolatility': [1, 2, 3, 4]} out = MarketDataResponseFrame(data=data, index=pd.DatetimeIndex(['2018-01-01'] * 4)) out.dataset_ids = _test_datasets market_mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) market_mock.side_effect = [out, pd.DataFrame()] # test that the function runs without error when nothing is returned by second market_data_timed call tm._get_latest_term_structure_data('MA123', tm.QueryType.IMPLIED_VOLATILITY, {}, None, '', '') def _vol_term_typical(reference, value): assert DataContext.current_is_set data = {'tenor': ['1w', '2w', '1y', '2y'], 'impliedVolatility': [1, 2, 3, 4]} out = MarketDataResponseFrame(data=data, index=pd.DatetimeIndex(['2018-01-01'] * 4)) out.dataset_ids = _test_datasets data_expiry = {'expirationDate': ['2018-01-06', '2018-01-08'], 'impliedVolatilityByExpiration': [1.2, 1.1]} out_expiry = MarketDataResponseFrame(data=data_expiry, index=pd.DatetimeIndex(['2018-01-01'] * 2)) out_expiry.dataset_ids = _test_datasets2 replace = Replacer() market_mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) market_mock.side_effect = [pd.DataFrame(), pd.DataFrame(), out, out_expiry] actual = tm.vol_term(Index('MA123', AssetClass.Equity, '123'), reference, value) idx = _make_index(['2018-01-06', '2018-01-08', '2018-01-15', '2019-01-01', '2020-01-01'], name='expirationDate') expected = pd.Series([1.2, 1, 2, 3, 4], name='impliedVolatility', index=idx) expected = expected.loc[pd.Timestamp(DataContext.current.start_date) : pd.Timestamp(DataContext.current.end_date)] if expected.empty: assert actual.empty else: assert_series_equal(expected, pd.Series(actual)) assert set(actual.dataset_ids) == set(_test_datasets + _test_datasets2) replace.restore() return actual def _skew_term_typical(reference, value, normalization_mode=None): assert DataContext.current_is_set data = { 'tenor': ['10y', '10y', '10y', '13m', '13m', '13m'], 'impliedVolatility': [5, 10, 15, 50, 55, 60], 'relativeStrike': [0.25, 0.5, 0.75, 0.25, 0.5, 0.75], } out = MarketDataResponseFrame(data=data, index=pd.DatetimeIndex(['2022-01-11'] * 6)) out.dataset_ids = _test_datasets data_expiry = { 'expirationDate': ['2022-01-12', '2022-01-12', '2022-01-12'], 'impliedVolatilityByExpiration': [1000, 1500, 2000], 'relativeStrike': [0.25, 0.5, 0.75], } out_expiry = MarketDataResponseFrame(data=data_expiry, index=pd.DatetimeIndex(['2022-01-11'] * 3)) out_expiry.dataset_ids = _test_datasets2 replace = Replacer() gltsd_mock = replace('gs_quant.api.utils.ThreadPoolManager.run_async', Mock()) gltsd_mock.side_effect = [[pd.DataFrame(), pd.DataFrame()], [out, out_expiry]] market_mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) market_mock.side_effect = [out, out_expiry] actual = tm.skew_term( Index('MA123', AssetClass.Equity, '123'), reference, value, normalization_mode=normalization_mode ) idx = _make_index(['2022-01-12', '2023-02-13', '2032-01-12']) if normalization_mode is None: normalization_mode = tm.NormalizationMode.NORMALIZED expected = ( pd.Series([0.666667, 0.181818, 1], name='impliedVolatility', index=idx) if normalization_mode == tm.NormalizationMode.NORMALIZED else pd.Series([1000, 10, 10], name='impliedVolatility', index=idx) ) expected = expected.loc[pd.Timestamp(DataContext.current.start_date) : pd.Timestamp(DataContext.current.end_date)] if expected.empty: assert actual.empty else: assert_series_equal(expected, pd.Series(actual)) assert set(actual.dataset_ids) == set(_test_datasets + _test_datasets2) replace.restore() return actual def _skew_term_typical_fx(reference, value, normalization_mode=None): assert DataContext.current_is_set data = { 'tenor': ['10y', '10y', '10y', '13m', '13m', '13m'], 'impliedVolatility': [5, 10, 15, 50, 55, 60], 'relativeStrike': [0.25, 0.5, 0.75, 0.25, 0.5, 0.75], } out = MarketDataResponseFrame(data=data, index=pd.DatetimeIndex(['2022-01-11'] * 6)) out.dataset_ids = _test_datasets data_expiry = { 'expirationDate': ['2022-01-12', '2022-01-12', '2022-01-12'], 'impliedVolatilityByExpiration': [1000, 1500, 2000], 'relativeStrike': [0.25, 0.5, 0.75], } out_expiry = MarketDataResponseFrame(data=data_expiry, index=pd.DatetimeIndex(['2022-01-11'] * 3)) out_expiry.dataset_ids = _test_datasets2 replace = Replacer() gltsd_mock = replace('gs_quant.api.utils.ThreadPoolManager.run_async', Mock()) gltsd_mock.side_effect = [[pd.DataFrame(), pd.DataFrame()], [out, out_expiry]] market_mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) fx_asset_mock = replace('gs_quant.timeseries.measures.cross_stored_direction_for_fx_vol', Mock()) fx_asset_mock.return_value = 'MA123' market_mock.side_effect = [out, out_expiry] actual = tm.skew_term(Index('MA123', AssetClass.FX, '123'), reference, value, normalization_mode=normalization_mode) idx = _make_index(['2022-01-12', '2023-02-13', '2032-01-12']) if normalization_mode is None: normalization_mode = tm.NormalizationMode.OUTRIGHT expected = ( pd.Series([0.666667, 0.181818, 1], name='impliedVolatility', index=idx) if normalization_mode == tm.NormalizationMode.NORMALIZED else pd.Series([1000, 10, 10], name='impliedVolatility', index=idx) ) expected = expected.loc[pd.Timestamp(DataContext.current.start_date) : pd.Timestamp(DataContext.current.end_date)] if expected.empty: assert actual.empty else: assert_series_equal(expected, pd.Series(actual)) assert set(actual.dataset_ids) == set(_test_datasets + _test_datasets2) replace.restore() return actual def _vol_term_empty(): replace = Replacer() market_mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) market_mock.return_value = pd.DataFrame() mock = replace('gs_quant.timeseries.measures.get_df_with_retries', Mock()) mock.return_value = MarketDataResponseFrame() actual = tm.vol_term(Index('MAXYZ', AssetClass.Equity, 'XYZ'), tm.VolReference.DELTA_CALL, 777) assert actual.empty assert actual.dataset_ids == () replace.restore() def _skew_term_empty(): replace = Replacer() market_mock = replace('gs_quant.api.utils.ThreadPoolManager.run_async', Mock()) market_mock.side_effect = [[pd.DataFrame(), pd.DataFrame()], [pd.DataFrame(), pd.DataFrame()]] actual = tm.skew_term(Index('MAXYZ', AssetClass.Equity, 'XYZ'), tm.SkewReference.DELTA, 777) assert actual.empty assert actual.dataset_ids == () replace.restore() def _vol_term_latest(): assert DataContext.current_is_set data = { 'tenor': ['1w', '2w', '1y', '2y', '1w', '2w', '1y', '2y'], 'impliedVolatility': [10, 20, 30, 40, 1, 2, 3, 4], } out = MarketDataResponseFrame(data=data, index=pd.date_range("2018-01-01", periods=8, freq="min")) out.dataset_ids = _test_datasets replace = Replacer() market_mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) market_mock.side_effect = [out.iloc[-1:], out, pd.DataFrame(), pd.DataFrame()] actual = tm.vol_term(Index('MA123', AssetClass.Equity, '123'), tm.VolReference.SPOT, 100) idx = _make_index(['2018-01-08', '2018-01-15', '2019-01-01', '2020-01-01'], name='expirationDate') expected = pd.Series([1, 2, 3, 4], name='impliedVolatility', index=idx) expected = expected.loc[pd.Timestamp(DataContext.current.start_date) : pd.Timestamp(DataContext.current.end_date)] if expected.empty: assert actual.empty else: assert_series_equal(expected, pd.Series(actual)) assert actual.dataset_ids == _test_datasets replace.restore() return actual def test_vol_term(): with DataContext('2018-01-01', '2019-01-01'): _vol_term_typical(tm.VolReference.SPOT, 100) _vol_term_typical(tm.VolReference.NORMALIZED, 4) _vol_term_typical(tm.VolReference.DELTA_PUT, 50) _vol_term_empty() _vol_term_latest() with DataContext('2018-01-16', '2018-12-31'): out = _vol_term_typical(tm.VolReference.SPOT, 100) assert out.empty assert set(out.dataset_ids) == set(_test_datasets + _test_datasets2) with pytest.raises(NotImplementedError): tm.vol_term(..., tm.VolReference.SPOT, 100, real_time=True) with pytest.raises(MqError): tm.vol_term(Index('MA123', AssetClass.Equity, '123'), tm.VolReference.DELTA_NEUTRAL, 0) with DataContext('2020-01-01', '2021-01-01'): with pytest.raises(MqError, match='forward looking date range'): tm.vol_term(Index('MA123', AssetClass.Equity, '123'), tm.VolReference.SPOT, 100, source='plottool') with DataContext(dt.date.today(), dt.date.today()): with pytest.raises(MqError, match='forward looking date range'): tm.vol_term(Index('MA123', AssetClass.Equity, '123'), tm.VolReference.SPOT, 100, source='plottool') def test__get_skew_strikes(): # Test FX asset = Index('MA123', AssetClass.FX, '123') skew_strikes, buf = tm._get_skew_strikes(asset, tm.SkewReference.DELTA, 25) assert skew_strikes == [-25, 25, 0] and buf == 1 with pytest.raises(MqValueError): tm._get_skew_strikes(asset, tm.SkewReference.NORMALIZED, 50) # Test skew reference types asset = Index('MA123', AssetClass.Equity, '123') skew_strikes, _ = tm._get_skew_strikes(asset, tm.SkewReference.DELTA, 25) assert skew_strikes == [0.75, 0.25, 0.50] skew_strikes, _ = tm._get_skew_strikes(asset, tm.SkewReference.NORMALIZED, 25) assert skew_strikes == [-25, 25, 0] skew_strikes, _ = tm._get_skew_strikes(asset, tm.SkewReference.SPOT, 25) assert skew_strikes == [0.75, 1.25, 1.0] with pytest.raises(MqTypeError): tm._get_skew_strikes(asset, None, 50) def test__skew(): df = pd.DataFrame( {'relativeStrike': [0.25, 0.5, 0.75, 0.25, 0.5, 0.75], 'iv': [5, 10, 15, 100, 150, 200]}, index=['2021-01-01'] * 3 + ['2021-02-01'] * 3, ) q_strikes = [0.75, 0.25, 0.5] res = tm._skew(df, 'relativeStrike', 'iv', q_strikes, tm.NormalizationMode.OUTRIGHT) expected = tm.ExtendedSeries(pd.Series([10, 100], name='iv', index=_make_index(['2021-01-01', '2021-02-01']))) assert_series_equal(res, expected) res = tm._skew(df, 'relativeStrike', 'iv', q_strikes, tm.NormalizationMode.NORMALIZED) expected = tm.ExtendedSeries( pd.Series([1.0, 0.6666666666666666], name='iv', index=_make_index(['2021-01-01', '2021-02-01'])) ) assert_series_equal(res, expected) with pytest.raises(MqValueError): tm._skew( df.loc[df['relativeStrike'].isin([0.25, 0.5])], 'relativeStrike', 'iv', q_strikes, tm.NormalizationMode.NORMALIZED, ) def _skew_term_no_data(): replace = Replacer() market_mock = replace('gs_quant.api.utils.ThreadPoolManager.run_async', Mock()) market_mock.side_effect = [[pd.DataFrame(), pd.DataFrame()], [pd.DataFrame(), MarketDataResponseFrame()]] actual = tm.skew_term(Index('MAXYZ', AssetClass.Equity, 'XYZ'), tm.SkewReference.DELTA, 777) assert actual.empty assert actual.dataset_ids == () replace.restore() def test__skew_term_fetcher(): replace = Replacer() market_mock = replace('gs_quant.timeseries.measures._market_data_timed', Mock()) market_mock.side_effect = [MarketDataResponseFrame(), MqValueError(), MqValueError] actual = tm._skew_fetcher('ABC123', tm.QueryType.IMPLIED_VOLATILITY, {}, 'any', True, allow_exception=False) assert isinstance(actual, MarketDataResponseFrame) and actual.empty actual = tm._skew_fetcher('ABC123', tm.QueryType.IMPLIED_VOLATILITY, {}, 'any', True, allow_exception=True) assert isinstance(actual, MarketDataResponseFrame) and actual.empty with pytest.raises(MqValueError): tm._skew_fetcher('ABC123', tm.QueryType.IMPLIED_VOLATILITY, {}, 'any', True, allow_exception=False) replace.restore() def test_skew_term(): with DataContext('2018-01-01', '2040-01-01'): _skew_term_typical(tm.SkewReference.DELTA, 25) _skew_term_empty() _skew_term_no_data() with DataContext('2018-01-16', '2018-12-31'): out = _skew_term_typical(tm.SkewReference.DELTA, 25) assert out.empty assert set(out.dataset_ids) == set(_test_datasets + _test_datasets2) with DataContext('2018-01-16', '2018-12-31'): out = _skew_term_typical_fx(tm.SkewReference.DELTA, 25) assert out.empty with pytest.raises(NotImplementedError): tm.skew_term(..., tm.SkewReference.SPOT, 100, real_time=True) with DataContext('2020-01-01', '2021-01-01'): with pytest.raises(MqError, match='forward looking date range'): tm.skew_term(Index('MA123', AssetClass.Equity, '123'), tm.SkewReference.SPOT, 100, source='plottool') with DataContext(dt.date.today(), dt.date.today()): with pytest.raises(MqError, match='forward looking date range'): tm.skew_term(Index('MA123', AssetClass.Equity, '123'), tm.SkewReference.SPOT, 100, source='plottool') def _vol_term_fx(reference, value): assert DataContext.current_is_set data = {'tenor': ['1w', '2w', '1y', '2y'], 'impliedVolatility': [1, 2, 3, 4]} out = MarketDataResponseFrame(data=data, index=pd.DatetimeIndex(['2018-01-01'] * 4)) out.dataset_ids = _test_datasets data_expiry = {'expirationDate': ['2018-01-06', '2018-01-08'], 'impliedVolatilityByExpiration': [1.2, 1.1]} out_expiry = MarketDataResponseFrame(data=data_expiry, index=pd.DatetimeIndex(['2018-01-01'] * 2)) out_expiry.dataset_ids = _test_datasets2 replace = Replacer() market_mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) market_mock.side_effect = [out, out_expiry] cross_mock = replace('gs_quant.timeseries.measures.cross_stored_direction_for_fx_vol', Mock()) cross_mock.return_value = 'EURUSD' actual = tm.vol_term(Cross('ABCDE', 'EURUSD'), reference, value) idx = _make_index(['2018-01-06', '2018-01-08', '2018-01-15', '2019-01-01', '2020-01-01'], name='expirationDate') expected = pd.Series([1.2, 1, 2, 3, 4], name='impliedVolatility', index=idx) expected = expected.loc[pd.Timestamp(DataContext.current.start_date) : pd.Timestamp(DataContext.current.end_date)] if expected.empty: assert actual.empty else: assert_series_equal(expected, pd.Series(actual)) assert set(actual.dataset_ids) == set(_test_datasets + _test_datasets2) replace.restore() return actual def _vol_term_fx_no_expiry(reference, value): assert DataContext.current_is_set data = {'tenor': ['1w', '2w', '1y', '2y'], 'impliedVolatility': [1, 2, 3, 4]} out = MarketDataResponseFrame(data=data, index=pd.DatetimeIndex(['2018-01-01'] * 4)) out.dataset_ids = _test_datasets replace = Replacer() market_mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) market_mock.side_effect = [out, MqValueError('a')] cross_mock = replace('gs_quant.timeseries.measures.cross_stored_direction_for_fx_vol', Mock()) cross_mock.return_value = 'EURUSD' actual = tm.vol_term(Cross('ABCDE', 'EURUSD'), reference, value) idx = _make_index(['2018-01-08', '2018-01-15', '2019-01-01'], name='expirationDate') expected = pd.Series([1, 2, 3], name='impliedVolatility', index=idx) expected = expected.loc[pd.Timestamp(DataContext.current.start_date) : pd.Timestamp(DataContext.current.end_date)] if expected.empty: assert actual.empty else: assert_series_equal(expected, pd.Series(actual)) assert actual.dataset_ids == _test_datasets replace.restore() return actual def test_vol_term_fx(): with pytest.raises(MqError): tm.vol_term(Cross('MABLUE', 'BLUE'), tm.VolReference.SPOT, 50) with pytest.raises(MqError): tm.vol_term(Cross('MABLUE', 'BLUE'), tm.VolReference.NORMALIZED, 1) with pytest.raises(MqError): tm.vol_term(Cross('MABLUE', 'BLUE'), tm.VolReference.DELTA_NEUTRAL, 1) with DataContext('2018-01-01', '2019-01-01'): _vol_term_fx(tm.VolReference.DELTA_CALL, 50) with DataContext('2018-01-01', '2019-01-01'): _vol_term_fx(tm.VolReference.DELTA_PUT, 50) with DataContext('2018-01-01', '2019-01-01'): _vol_term_fx_no_expiry(tm.VolReference.DELTA_PUT, 50) def _fwd_term_typical(): assert DataContext.current_is_set data = {'tenor': ['1w', '2w', '1y', '2y'], 'forward': [1, 2, 3, 4]} out = MarketDataResponseFrame(data=data, index=pd.DatetimeIndex(['2018-01-01'] * 4)) out.dataset_ids = _test_datasets replace = Replacer() market_mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) market_mock.return_value = out actual = tm.fwd_term(Index('MA123', AssetClass.Equity, '123')) idx = _make_index(['2018-01-08', '2018-01-15', '2019-01-01', '2020-01-01'], name='expirationDate') expected = pd.Series([1, 2, 3, 4], name='forward', index=idx) expected = expected.loc[pd.Timestamp(DataContext.current.start_date) : pd.Timestamp(DataContext.current.end_date)] if expected.empty: assert actual.empty else: assert_series_equal(expected, pd.Series(actual)) assert actual.dataset_ids == _test_datasets market_mock.assert_called_once() replace.restore() return actual def _fx_fwd_term_typical(): assert DataContext.current_is_set fwd_data = {'tenor': ['1w', '2w', '1y', '2y'], 'forwardPoint': [1, 2, 3, 4]} out = MarketDataResponseFrame(data=fwd_data, index=pd.DatetimeIndex(['2018-01-01'] * 4)) out.dataset_ids = _test_datasets spot_data = {'spot': [100], 'assetId': 'MA123'} spot = MarketDataResponseFrame(data=spot_data, index=pd.DatetimeIndex(['2018-01-01'])) replace = Replacer() market_mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) market_mock.return_value = out fx_asset_mock = replace('gs_quant.timeseries.measures.cross_stored_direction_for_fx_vol', Mock()) fx_asset_mock.return_value = 'MA123' thread_mock = replace('gs_quant.api.utils.ThreadPoolManager.run_async', Mock()) thread_mock.return_value = [out, spot] actual = tm.fx_fwd_term(Index('MA123', AssetClass.FX, '123')) idx = _make_index(['2018-01-08', '2018-01-15', '2019-01-01', '2020-01-01'], name='expirationDate') expected = pd.Series([101, 102, 103, 104], name='forwardPoint', index=idx) expected = expected.loc[pd.Timestamp(DataContext.current.start_date) : pd.Timestamp(DataContext.current.end_date)] assert_series_equal(pd.Series(actual), expected) assert actual.dataset_ids == _test_datasets actual = tm.fx_fwd_term(Index('MA123', AssetClass.FX, '123'), fwd_type=tm.FXForwardType.POINTS) idx = _make_index(['2018-01-08', '2018-01-15', '2019-01-01', '2020-01-01'], name='expirationDate') expected = pd.Series([1, 2, 3, 4], name='forwardPoint', index=idx) expected = expected.loc[pd.Timestamp(DataContext.current.start_date) : pd.Timestamp(DataContext.current.end_date)] assert_series_equal(pd.Series(actual), expected) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.fx_fwd_term(..., real_time=True) replace.restore() def _fwd_term_empty(): replace = Replacer() market_mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) market_mock.return_value = mock_empty_market_data_response() actual = tm.fwd_term(Index('MAXYZ', AssetClass.Equity, 'XYZ')) assert actual.empty assert actual.dataset_ids == () market_mock.assert_called_once() replace.restore() def _fx_fwd_term_empty(): replace = Replacer() fx_asset_mock = replace('gs_quant.timeseries.measures.cross_stored_direction_for_fx_vol', Mock()) fx_asset_mock.return_value = 'MA123' thread_mock = replace('gs_quant.api.utils.ThreadPoolManager.run_async', Mock()) thread_mock.return_value = [pd.DataFrame(), pd.DataFrame()] actual = tm.fx_fwd_term(Index('MAXYZ', AssetClass.FX, 'XYZ')) assert actual.empty assert actual.dataset_ids == () replace.restore() def _fx_fwd_term(): with DataContext('2018-01-01', '2019-01-01'): _fx_fwd_term_typical() _fx_fwd_term_empty() def _fwd_term_equity(): with DataContext('2018-01-01', '2019-01-01'): _fwd_term_typical() _fwd_term_empty() with DataContext('2018-01-16', '2018-12-31'): out = _fwd_term_typical() assert out.empty assert out.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.fwd_term(..., real_time=True) def test_fwd_term(): _fx_fwd_term() _fwd_term_equity() def _carry_term_typical(): assert DataContext.current_is_set fwd_data = {'tenor': ['1w', '2w', '1y', '2y'], 'forwardPoint': [1, 2, 3, 4]} out = MarketDataResponseFrame(data=fwd_data, index=pd.DatetimeIndex(['2018-01-01'] * 4)) out.dataset_ids = _test_datasets spot_data = {'spot': [100], 'assetId': 'MA123'} spot = MarketDataResponseFrame(data=spot_data, index=pd.DatetimeIndex(['2018-01-01'])) replace = Replacer() market_mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) market_mock.return_value = out fx_asset_mock = replace('gs_quant.timeseries.measures.cross_stored_direction_for_fx_vol', Mock()) fx_asset_mock.return_value = 'MA123' thread_mock = replace('gs_quant.api.utils.ThreadPoolManager.run_async', Mock()) thread_mock.return_value = [out, spot] actual = tm.carry_term(Index('MA123', AssetClass.FX, '123'), annualized=tm.FXSpotCarry.DAILY) idx = _make_index(['2018-01-08', '2018-01-15', '2019-01-01', '2020-01-01'], name='expirationDate') expected = pd.Series([0.01, 0.02, 0.03, 0.04], name='carry', index=idx) expected = expected.loc[pd.Timestamp(DataContext.current.start_date) : pd.Timestamp(DataContext.current.end_date)] assert_series_equal(expected, pd.Series(actual)) assert actual.dataset_ids == _test_datasets actual = tm.carry_term(Index('MA123', AssetClass.FX, '123')) idx = _make_index(['2018-01-08', '2018-01-15', '2019-01-01', '2020-01-01'], name='expirationDate') expected = pd.Series([0.001667, 0.004714, 0.036105, 0.05621], name='carry', index=idx) expected = expected.loc[pd.Timestamp(DataContext.current.start_date) : pd.Timestamp(DataContext.current.end_date)] assert_series_equal(expected, pd.Series(actual), check_exact=False, atol=0.00001) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.carry_term(Index('MA123', AssetClass.FX, '123'), real_time=True) thread_mock.return_value = [pd.DataFrame(), pd.DataFrame()] actual = tm.carry_term(Index('MA123', AssetClass.FX, '123')) assert actual.empty replace.restore() def test_carry_term(): with DataContext('2018-01-01', '2019-01-01'): _carry_term_typical() def test_measure_request_safe(): def error(raise_error): if raise_error: raise MqValueError() with pytest.raises(MqValueError): tm.measure_request_safe('error', Index('MA123', AssetClass.Equity, '123'), error, None, True) tm.measure_request_safe('error', Index('MA123', AssetClass.Equity, '123'), error, None, False) def test_bucketize_price(): target = { '7x24': [27.323461], 'offpeak': [26.004816], 'peak': [27.982783], '7x8': [26.004816], '2x16h': [], 'monthly': [], 'CAISO 7x24': [26.953743375], 'CAISO peak': [29.547952562499997], 'MISO 7x24': [27.076390749999998], 'MISO offpeak': [25.263605624999997], } replace = Replacer() replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_commod) mock_pjm = Index('MA001', AssetClass.Commod, 'PJM') mock_caiso = Index('MA002', AssetClass.Commod, 'CAISO') mock_miso = Index('MA003', AssetClass.Commod, 'MISO') with DataContext(dt.date(2019, 5, 1), dt.date(2019, 5, 1)): bbid_mock = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) bbid_mock.return_value = 'MISO' actual = tm.bucketize_price(mock_miso, 'LMP', bucket='7x24') assert_series_equal( pd.Series(target['MISO 7x24'], index=[dt.date(2019, 5, 1)], name='price'), pd.Series(actual) ) actual = tm.bucketize_price(mock_miso, 'LMP', bucket='offpeak') assert_series_equal( pd.Series(target['MISO offpeak'], index=[dt.date(2019, 5, 1)], name='price'), pd.Series(actual) ) bbid_mock.return_value = 'CAISO' actual = tm.bucketize_price(mock_caiso, 'LMP', bucket='7x24') assert_series_equal( pd.Series(target['CAISO 7x24'], index=[dt.date(2019, 5, 1)], name='price'), pd.Series(actual) ) actual = tm.bucketize_price(mock_caiso, 'LMP', bucket='peak') assert_series_equal( pd.Series(target['CAISO peak'], index=[dt.date(2019, 5, 1)], name='price'), pd.Series(actual) ) bbid_mock.return_value = 'PJM' actual = tm.bucketize_price(mock_pjm, 'LMP', bucket='7x24') assert_series_equal(pd.Series(target['7x24'], index=[dt.date(2019, 5, 1)], name='price'), pd.Series(actual)) actual = tm.bucketize_price(mock_pjm, 'LMP', bucket='offpeak') assert_series_equal(pd.Series(target['offpeak'], index=[dt.date(2019, 5, 1)], name='price'), pd.Series(actual)) actual = tm.bucketize_price(mock_pjm, 'LMP', bucket='peak') assert_series_equal(pd.Series(target['peak'], index=[dt.date(2019, 5, 1)], name='price'), pd.Series(actual)) actual = tm.bucketize_price(mock_pjm, 'LMP', bucket='7x8') assert_series_equal(pd.Series(target['7x8'], index=[dt.date(2019, 5, 1)], name='price'), pd.Series(actual)) actual = tm.bucketize_price(mock_pjm, 'LMP', bucket='2x16h') assert_series_equal(pd.Series(target['2x16h'], index=[], dtype=float, name='price'), pd.Series(actual)) actual = tm.bucketize_price(mock_pjm, 'LMP', granularity='m', bucket='7X24') assert_series_equal(pd.Series(target['monthly'], index=[], dtype=float, name='price'), pd.Series(actual)) with pytest.raises(ValueError): tm.bucketize_price(mock_pjm, 'LMP', bucket='7X24', real_time=True) with pytest.raises(ValueError): tm.bucketize_price(mock_pjm, 'LMP', bucket='weekday') with pytest.raises(ValueError): tm.bucketize_price(mock_caiso, 'LMP', bucket='weekday') with pytest.raises(ValueError): tm.bucketize_price(mock_pjm, 'LMP', granularity='yearly') replace.restore() # No market data market_mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) market_mock.return_value = mock_empty_market_data_response() with DataContext(dt.date(2019, 1, 2), dt.date(2019, 1, 2)): bbid_mock = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) bbid_mock.return_value = 'MISO' actual = tm.bucketize_price(mock_miso, 'LMP', bucket='7x24') assert_series_equal(pd.Series(dtype='float64'), pd.Series(actual)) # Duplicate data in dataset replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_commod_dup) with DataContext(dt.date(2019, 1, 2), dt.date(2019, 1, 2)): bbid_mock = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) bbid_mock.return_value = 'MISO' with pytest.raises(ValueError): tm.bucketize_price(mock_miso, 'LMP', bucket='peak') replace.restore() def test_forward_price(): # US Power target = { '7x24': [19.46101], 'peak': [23.86745], 'J20 7x24': [18.11768888888889], 'J20-K20 7x24': [19.283921311475414], 'J20-K20 offpeak': [15.82870707070707], 'J20-K20 7x8': [13.020144262295084], } replace = Replacer() replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_forward_price) mock_spp = Index('MA001', AssetClass.Commod, 'SPP') with DataContext(dt.date(2019, 1, 2), dt.date(2019, 1, 2)): bbid_mock = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) bbid_mock.return_value = 'SPP' # Should return empty series as mark for '7x8' bucket is missing actual = tm.forward_price(mock_spp, price_method='LMP', contract_range='2Q20', bucket='7x24') assert_series_equal(pd.Series(target['7x24'], index=[dt.date(2019, 1, 2)], name='price'), pd.Series(actual)) actual = tm.forward_price(mock_spp, price_method='LMP', contract_range='J20', bucket='7x24') assert_series_equal(pd.Series(target['J20 7x24'], index=[dt.date(2019, 1, 2)], name='price'), pd.Series(actual)) actual = tm.forward_price(mock_spp, price_method='LMP', contract_range='2Q20', bucket='PEAK') assert_series_equal(pd.Series(target['peak'], index=[dt.date(2019, 1, 2)], name='price'), pd.Series(actual)) actual = tm.forward_price(mock_spp, price_method='LMP', contract_range='J20-K20', bucket='7x24') assert_series_equal( pd.Series(target['J20-K20 7x24'], index=[dt.date(2019, 1, 2)], name='price'), pd.Series(actual) ) actual = tm.forward_price(mock_spp, price_method='LMP', contract_range='J20-K20', bucket='offpeak') assert_series_equal( pd.Series(target['J20-K20 offpeak'], index=[dt.date(2019, 1, 2)], name='price'), pd.Series(actual) ) actual = tm.forward_price(mock_spp, price_method='LMP', contract_range='J20-K20', bucket='7x8') assert_series_equal( pd.Series(target['J20-K20 7x8'], index=[dt.date(2019, 1, 2)], name='price'), pd.Series(actual) ) actual = tm.forward_price(mock_spp, price_method='lmp', contract_range='2Q20', bucket='7x24') assert_series_equal(pd.Series(target['7x24'], index=[dt.date(2019, 1, 2)], name='price'), pd.Series(actual)) with pytest.raises(ValueError): tm.forward_price(mock_spp, price_method='LMP', contract_range='5Q20', bucket='PEAK') with pytest.raises(ValueError): tm.forward_price(mock_spp, price_method='LMP', contract_range='Invalid', bucket='PEAK') with pytest.raises(ValueError): tm.forward_price(mock_spp, price_method='LMP', contract_range='3H20', bucket='7x24') with pytest.raises(ValueError): tm.forward_price(mock_spp, price_method='LMP', contract_range='F20-I20', bucket='7x24') with pytest.raises(ValueError): tm.forward_price(mock_spp, price_method='LMP', contract_range='2H20', bucket='7x24', real_time=True) replace.restore() replace = Replacer() replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_missing_bucket_forward_price) bbid_mock = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) bbid_mock.return_value = 'SPP' actual = tm.forward_price(mock_spp, price_method='LMP', contract_range='2Q20', bucket='7x24') assert_series_equal(pd.Series(dtype=float, index=[]), pd.Series(actual), check_names=False) actual = tm.forward_price(mock_spp, price_method='LMP', contract_range='2Q20', bucket='PEAK') assert_series_equal(pd.Series(target['peak'], index=[dt.date(2019, 1, 2)], name='price'), pd.Series(actual)) actual = tm.forward_price(mock_spp, price_method='LMP', contract_range='J20-K20', bucket='7x24') assert_series_equal( pd.Series(target['J20-K20 7x24'], index=[dt.date(2019, 1, 2)], name='price'), pd.Series(actual) ) replace.restore() # No market data market_mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) market_mock.return_value = mock_empty_market_data_response() bbid_mock = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) bbid_mock.return_value = 'SPP' with DataContext(dt.date(2019, 1, 2), dt.date(2019, 1, 2)): actual = tm.forward_price(mock_spp, price_method='LMP', contract_range='2Q20', bucket='7x24') assert_series_equal(pd.Series(dtype='float64'), pd.Series(actual)) replace.restore() # Query for asset with no bbid replace = Replacer() replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_forward_price) mock_ice = CommodityPowerAggregatedNodes('MA001', 'ICE Mid C') with DataContext(dt.date(2019, 1, 2), dt.date(2019, 1, 2)): bbId_mock = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) bbId_mock.return_value = None ISO_mock = replace('gs_quant.entities.entity.Entity.get_entity', Mock()) ISO_mock.return_value = {'parameters': {'ISO': 'PJM'}} # Should work as other usecases where assetId has bbid actual = tm.forward_price(mock_ice, price_method='LMP', contract_range='2Q20', bucket='7x24') assert_series_equal(pd.Series(target['7x24'], index=[dt.date(2019, 1, 2)], name='price'), pd.Series(actual)) replace.restore() def test_implied_volatility_elec(): # US Power target = { '7x24': [0.403352], 'peak': [0.383025], 'J20 7x24': [0.372178], 'J20-K20 7x24': [0.3737661202185792], 'J20-K20 offpeak': [0.3922989898989899], 'J20-K20 7x8': [0.4322360655737705], } replace = Replacer() mock_get_data = replace('gs_quant.data.dataset.Dataset.get_data', Mock()) mock_get_data.return_value = mock_implied_volatility_elec() mock_spp = Index('MA001', AssetClass.Commod, 'SPP') with DataContext(dt.date(2019, 1, 2), dt.date(2019, 1, 2)): bbid_mock = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) bbid_mock.return_value = 'SPP' # Should return empty series as mark for '7x8' bucket is missing actual = tm.implied_volatility_elec(mock_spp, price_method='LMP', contract_range='2Q20', bucket='7x24') assert_series_equal(pd.Series(target['7x24'], index=[dt.date(2019, 1, 2)], name='price'), pd.Series(actual)) actual = tm.implied_volatility_elec(mock_spp, price_method='LMP', contract_range='J20', bucket='7x24') assert_series_equal(pd.Series(target['J20 7x24'], index=[dt.date(2019, 1, 2)], name='price'), pd.Series(actual)) actual = tm.implied_volatility_elec(mock_spp, price_method='LMP', contract_range='2Q20', bucket='PEAK') assert_series_equal(pd.Series(target['peak'], index=[dt.date(2019, 1, 2)], name='price'), pd.Series(actual)) actual = tm.implied_volatility_elec(mock_spp, price_method='LMP', contract_range='J20-K20', bucket='7x24') assert_series_equal( pd.Series(target['J20-K20 7x24'], index=[dt.date(2019, 1, 2)], name='price'), pd.Series(actual) ) actual = tm.implied_volatility_elec(mock_spp, price_method='LMP', contract_range='J20-K20', bucket='offpeak') assert_series_equal( pd.Series(target['J20-K20 offpeak'], index=[dt.date(2019, 1, 2)], name='price'), pd.Series(actual) ) actual = tm.implied_volatility_elec(mock_spp, price_method='LMP', contract_range='J20-K20', bucket='7x8') assert_series_equal( pd.Series(target['J20-K20 7x8'], index=[dt.date(2019, 1, 2)], name='price'), pd.Series(actual) ) actual = tm.implied_volatility_elec(mock_spp, price_method='lmp', contract_range='2Q20', bucket='7x24') assert_series_equal(pd.Series(target['7x24'], index=[dt.date(2019, 1, 2)], name='price'), pd.Series(actual)) with pytest.raises(ValueError): tm.implied_volatility_elec(mock_spp, price_method='LMP', contract_range='5Q20', bucket='PEAK') with pytest.raises(ValueError): tm.implied_volatility_elec(mock_spp, price_method='LMP', contract_range='Invalid', bucket='PEAK') with pytest.raises(ValueError): tm.implied_volatility_elec(mock_spp, price_method='LMP', contract_range='3H20', bucket='7x24') with pytest.raises(ValueError): tm.implied_volatility_elec(mock_spp, price_method='LMP', contract_range='F20-I20', bucket='7x24') with pytest.raises(ValueError): tm.implied_volatility_elec( mock_spp, price_method='LMP', contract_range='2H20', bucket='7x24', real_time=True ) replace.restore() replace = Replacer() mock_get_data = replace('gs_quant.data.dataset.Dataset.get_data', Mock()) mock_get_data.return_value = mock_missing_bucket_implied_volatility() bbid_mock = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) bbid_mock.return_value = 'SPP' actual = tm.implied_volatility_elec(mock_spp, price_method='LMP', contract_range='2Q20', bucket='7x24') assert_series_equal(pd.Series(dtype=float, index=[]), pd.Series(actual), check_names=False) actual = tm.implied_volatility_elec(mock_spp, price_method='LMP', contract_range='2Q20', bucket='PEAK') assert_series_equal(pd.Series(target['peak'], index=[dt.date(2019, 1, 2)], name='price'), pd.Series(actual)) actual = tm.implied_volatility_elec(mock_spp, price_method='LMP', contract_range='J20-K20', bucket='7x24') assert_series_equal( pd.Series(target['J20-K20 7x24'], index=[dt.date(2019, 1, 2)], name='price'), pd.Series(actual) ) replace.restore() # No market data mock_get_data = replace('gs_quant.data.dataset.Dataset.get_data', Mock()) mock_get_data.return_value = mock_empty_market_data_response() bbid_mock = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) bbid_mock.return_value = 'SPP' with DataContext(dt.date(2019, 1, 2), dt.date(2019, 1, 2)): actual = tm.implied_volatility_elec(mock_spp, price_method='LMP', contract_range='2Q20', bucket='7x24') assert_series_equal(pd.Series(dtype='float64'), pd.Series(actual)) replace.restore() def test_forward_price_ng(): # Tests for US NG assets replace = Replacer() replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_natgas_forward_price) mock = CommodityNaturalGasHub('MA001', 'AGT') with DataContext(dt.date(2019, 1, 2), dt.date(2019, 1, 2)): actual = pd.Series(tm.forward_price_ng(mock, price_method='GDD', contract_range='F21')) expected = pd.Series([2.880], index=[dt.date(2019, 1, 2)], name='price') assert_series_equal(expected, actual) actual = pd.Series(tm.forward_price_ng(mock, price_method='GDD', contract_range='F21-G21')) expected = pd.Series([2.8629152542372878], index=[dt.date(2019, 1, 2)], name='price') assert_series_equal(expected, actual) with pytest.raises(ValueError): tm.forward_price_ng(mock, price_method='GDD', contract_range='F21-I21') with pytest.raises(ValueError): tm.forward_price_ng(mock, price_method='GDD', contract_range='I21') with pytest.raises(MqTypeError): wrong_mock = Index('MA001', AssetClass.Commod, 'SPP') tm.forward_price_ng(wrong_mock, price_method='GDD', contract_range='I21') with pytest.raises(ValueError): tm.forward_price_ng(mock, price_method='GDD', contract_range='I21', real_time=True) # No market data market_mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) market_mock.return_value = mock_empty_market_data_response() with DataContext(dt.date(2019, 1, 2), dt.date(2019, 1, 2)): actual = tm.forward_price_ng(mock, price_method='LMP', contract_range='2Q20') assert_series_equal(pd.Series(dtype='float64'), pd.Series(actual)) replace.restore() # Tests for EU NG assets mock_EU_asset = CommodityEUNaturalGasHub('MA001', 'TTF') mock_EU_swap_asset = Swap('MA002', AssetClass.Commod, 'Swap NatGas TTF') mock_EU_swap_asset.id = "Mock_Id" assets = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) assets.return_value = [mock_EU_swap_asset] # Test for forward price fetch using instrument id with DataContext(dt.date(2021, 1, 1), dt.date(2021, 1, 1)): market_mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_eu_natgas_forward_price) expected = pd.Series([15.65], name='price', index=[dt.date(2021, 1, 1)]) actual = tm.forward_price_ng(mock_EU_asset, contract_range='H21') assert_series_equal(expected, pd.Series(actual)) # Test for empty market response with DataContext(dt.date(2021, 1, 1), dt.date(2021, 1, 1)): market_mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) market_mock.return_value = mock_empty_market_data_response() actual = tm.forward_price_ng(mock_EU_asset, contract_range='H21', price_method='USD') assert actual.empty # Test for no instruments found with DataContext(dt.date(2021, 1, 1), dt.date(2021, 1, 1)): assets.return_value = [] market_mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) market_mock.return_value = mock_empty_market_data_response() actual = tm.forward_price_ng(mock_EU_asset, contract_range='H21') assert actual.empty replace.restore() def test_implied_volatility_ng(): replace = Replacer() replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_natgas_implied_volatility) mock = CommodityNaturalGasHub('MA001', 'AGT') with DataContext(dt.date(2019, 1, 2), dt.date(2019, 1, 2)): actual = pd.Series(tm.implied_volatility_ng(mock, price_method='GDD', contract_range='F21')) expected = pd.Series([2.880], index=[dt.date(2019, 1, 2)], name='price') assert_series_equal(expected, actual) actual = pd.Series(tm.implied_volatility_ng(mock, price_method='GDD', contract_range='F21-G21')) expected = pd.Series([2.8629152542372878], index=[dt.date(2019, 1, 2)], name='price') assert_series_equal(expected, actual) with pytest.raises(ValueError): tm.implied_volatility_ng(mock, price_method='GDD', contract_range='F21-I21') with pytest.raises(ValueError): tm.implied_volatility_ng(mock, price_method='GDD', contract_range='I21') with pytest.raises(ValueError): tm.implied_volatility_ng(mock, price_method='GDD', contract_range='I21', real_time=True) # No market data market_mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) market_mock.return_value = mock_empty_market_data_response() with DataContext(dt.date(2019, 1, 2), dt.date(2019, 1, 2)): actual = tm.implied_volatility_ng(mock, price_method='LMP', contract_range='2Q20') assert_series_equal(pd.Series(dtype='float64'), pd.Series(actual)) replace.restore() def test_get_iso_data(): tz_map = {'MISO': 'US/Central', 'CAISO': 'US/Pacific'} for key in tz_map: assert tm._get_iso_data(key)[0] == tz_map[key] def test_string_to_date_interval(): assert tm._string_to_date_interval("K20")['start_date'] == dt.date(2020, 5, 1) assert tm._string_to_date_interval("K20")['end_date'] == dt.date(2020, 5, 31) assert tm._string_to_date_interval("k20")['start_date'] == dt.date(2020, 5, 1) assert tm._string_to_date_interval("k20")['end_date'] == dt.date(2020, 5, 31) assert tm._string_to_date_interval("Cal22")['start_date'] == dt.date(2022, 1, 1) assert tm._string_to_date_interval("Cal22")['end_date'] == dt.date(2022, 12, 31) assert tm._string_to_date_interval("Cal2012")['start_date'] == dt.date(2012, 1, 1) assert tm._string_to_date_interval("Cal2012")['end_date'] == dt.date(2012, 12, 31) assert tm._string_to_date_interval("Cal53")['start_date'] == dt.date(1953, 1, 1) assert tm._string_to_date_interval("Cal53")['end_date'] == dt.date(1953, 12, 31) assert tm._string_to_date_interval("2010")['start_date'] == dt.date(2010, 1, 1) assert tm._string_to_date_interval("2010")['end_date'] == dt.date(2010, 12, 31) assert tm._string_to_date_interval("3Q20")['start_date'] == dt.date(2020, 7, 1) assert tm._string_to_date_interval("3Q20")['end_date'] == dt.date(2020, 9, 30) assert tm._string_to_date_interval("2h2021")['start_date'] == dt.date(2021, 7, 1) assert tm._string_to_date_interval("2h2021")['end_date'] == dt.date(2021, 12, 31) assert tm._string_to_date_interval("3q20")['start_date'] == dt.date(2020, 7, 1) assert tm._string_to_date_interval("3q20")['end_date'] == dt.date(2020, 9, 30) assert tm._string_to_date_interval("2H2021")['start_date'] == dt.date(2021, 7, 1) assert tm._string_to_date_interval("2H2021")['end_date'] == dt.date(2021, 12, 31) assert tm._string_to_date_interval("Mar2021")['start_date'] == dt.date(2021, 3, 1) assert tm._string_to_date_interval("Mar2021")['end_date'] == dt.date(2021, 3, 31) assert tm._string_to_date_interval("March2021")['start_date'] == dt.date(2021, 3, 1) assert tm._string_to_date_interval("March2021")['end_date'] == dt.date(2021, 3, 31) assert tm._string_to_date_interval("5Q20") == "Invalid Quarter" assert tm._string_to_date_interval("HH2021") == "Invalid num" assert tm._string_to_date_interval("3H2021") == "Invalid Half Year" assert tm._string_to_date_interval("Cal2a") == "Invalid year" assert tm._string_to_date_interval("Marc201") == "Invalid date code" assert tm._string_to_date_interval("M1a2021") == "Invalid date code" assert tm._string_to_date_interval("Marcha2021") == "Invalid date code" assert tm._string_to_date_interval("I20") == "Invalid month" assert tm._string_to_date_interval("20") == "Unknown date code" def test_implied_vol_commod(): target = { 'F21': [2.880], 'F21-H21': [2.815756], } replace = Replacer() replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_implied_volatility) mock = Index('MA001', AssetClass.Commod, 'Option NG Exchange') with DataContext(dt.date(2019, 1, 2), dt.date(2019, 1, 2)): actual = tm.implied_volatility(mock, tenor='F21-H21') assert_series_equal(pd.Series(target['F21-H21'], index=[dt.date(2019, 1, 2)], name='price'), pd.Series(actual)) replace.restore() def test_fair_price(): target = { 'F21': [2.880], 'F21-H21': [2.815756], } replace = Replacer() replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_fair_price) mock = Index('MA001', AssetClass.Commod, 'Swap NG Exchange') mock2 = Swap('MA002', AssetClass.Commod, 'Swap Oil') with DataContext(dt.date(2019, 1, 2), dt.date(2019, 1, 2)): actual = tm.fair_price(mock, tenor='F21') assert_series_equal(pd.Series(target['F21'], index=[dt.date(2019, 1, 2)], name='price'), pd.Series(actual)) with pytest.raises(ValueError): tm.fair_price(mock, tenor=None) replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_fair_price_swap) with DataContext(dt.date(2019, 1, 2), dt.date(2019, 1, 2)): actual = tm.fair_price(mock2) assert_series_equal( pd.Series([2.880], index=_make_index(['2019-01-02']), name='fairPrice'), pd.Series(actual), ) replace.restore() def test_weighted_average_valuation_curve_for_calendar_strip(): target = { 'F21': [2.880], 'F21-H21': [2.815756], } replace = Replacer() replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_fair_price) mock = Index('MA001', AssetClass.Commod, 'Swap NG Exchange') with DataContext(dt.date(2019, 1, 2), dt.date(2019, 1, 2)): actual = tm._weighted_average_valuation_curve_for_calendar_strip( mock, contract_range='F21', query_type=QueryType.FAIR_PRICE, measure_field='fairPrice' ) assert_series_equal(pd.Series(target['F21'], index=[dt.date(2019, 1, 2)], name='price'), pd.Series(actual)) actual = tm._weighted_average_valuation_curve_for_calendar_strip( mock, contract_range='F21-H21', query_type=QueryType.FAIR_PRICE, measure_field='fairPrice' ) assert_series_equal(pd.Series(target['F21-H21'], index=[dt.date(2019, 1, 2)], name='price'), pd.Series(actual)) with pytest.raises(ValueError): tm._weighted_average_valuation_curve_for_calendar_strip( mock, contract_range='Invalid', query_type=QueryType.FAIR_PRICE, measure_field='fairPrice' ) with pytest.raises(ValueError): tm._weighted_average_valuation_curve_for_calendar_strip( mock, contract_range='F20-I20', query_type=QueryType.FAIR_PRICE, measure_field='fairPrice' ) with pytest.raises(ValueError): tm._weighted_average_valuation_curve_for_calendar_strip( mock, contract_range='3H20', query_type=QueryType.PRICE, measure_field='fairPrice' ) replace.restore() def test_fundamental_metrics(): replace = Replacer() mock_spx = Index('MA890', AssetClass.Equity, 'SPX') mock_rb = CustomBasket( GsAsset( AssetClass.Equity, AssetType.Research_Basket, 'GSTEST01', None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, {'flagship': True}, ) ) mock_non_flagship = CustomBasket( GsAsset( AssetClass.Equity, AssetType.Research_Basket, 'GSTEST01', None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, {'flagship': False}, ) ) mock_basket_no_parameters = CustomBasket(GsAsset(AssetClass.Equity, AssetType.Research_Basket, 'GSTEST01')) mock_basket_no_flagship_parameter = CustomBasket( GsAsset( AssetClass.Equity, AssetType.Research_Basket, 'GSTEST01', None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, {}, ) ) replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_eq) period = '1y' direction = tm.FundamentalMetricPeriodDirection.FORWARD actual = tm.dividend_yield(mock_spx, period, direction) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='fundamentalMetric'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.dividend_yield(..., period, direction, real_time=True) actual = tm.earnings_per_share(mock_spx, period, direction) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='fundamentalMetric'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.earnings_per_share(..., period, direction, real_time=True) actual = tm.earnings_per_share_positive(mock_spx, period, direction) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='fundamentalMetric'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.earnings_per_share_positive(..., period, direction, real_time=True) actual = tm.net_debt_to_ebitda(mock_spx, period, direction) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='fundamentalMetric'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.net_debt_to_ebitda(..., period, direction, real_time=True) actual = tm.price_to_book(mock_spx, period, direction) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='fundamentalMetric'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.price_to_book(..., period, direction, real_time=True) actual = tm.price_to_cash(mock_spx, period, direction) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='fundamentalMetric'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.price_to_cash(..., period, direction, real_time=True) actual = tm.price_to_earnings(mock_spx, period, direction) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='fundamentalMetric'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.price_to_earnings(..., period, direction, real_time=True) actual = tm.price_to_earnings_positive(mock_spx, period, direction) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='fundamentalMetric'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.price_to_earnings_positive(..., period, direction, real_time=True) actual = tm.price_to_sales(mock_spx, period, direction) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='fundamentalMetric'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.price_to_sales(..., period, direction, real_time=True) actual = tm.price_to_earnings_positive_exclusive(mock_spx, period, direction) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='fundamentalMetric'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.price_to_earnings_positive_exclusive(..., period, direction, real_time=True) actual = tm.return_on_equity(mock_spx, period, direction) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='fundamentalMetric'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.return_on_equity(..., period, direction, real_time=True) actual = tm.sales_per_share(mock_spx, period, direction) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='fundamentalMetric'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.sales_per_share(..., period, direction, real_time=True) actual = tm.current_constituents_dividend_yield(mock_rb, period, direction) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='fundamentalMetric'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.current_constituents_dividend_yield(..., period, direction, real_time=True) actual = tm.current_constituents_earnings_per_share(mock_rb, period, direction) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='fundamentalMetric'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.current_constituents_earnings_per_share(..., period, direction, real_time=True) actual = tm.current_constituents_earnings_per_share_positive(mock_rb, period, direction) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='fundamentalMetric'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.current_constituents_earnings_per_share_positive(..., period, direction, real_time=True) actual = tm.current_constituents_net_debt_to_ebitda(mock_rb, period, direction) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='fundamentalMetric'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.current_constituents_net_debt_to_ebitda(..., period, direction, real_time=True) actual = tm.current_constituents_price_to_book(mock_rb, period, direction) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='fundamentalMetric'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.current_constituents_price_to_book(..., period, direction, real_time=True) actual = tm.current_constituents_price_to_cash(mock_rb, period, direction) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='fundamentalMetric'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.current_constituents_price_to_cash(..., period, direction, real_time=True) actual = tm.current_constituents_price_to_earnings(mock_rb, period, direction) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='fundamentalMetric'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.current_constituents_price_to_earnings(..., period, direction, real_time=True) actual = tm.current_constituents_price_to_earnings_positive(mock_rb, period, direction) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='fundamentalMetric'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.current_constituents_price_to_earnings_positive(..., period, direction, real_time=True) actual = tm.current_constituents_price_to_sales(mock_rb, period, direction) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='fundamentalMetric'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.current_constituents_price_to_sales(..., period, direction, real_time=True) actual = tm.current_constituents_return_on_equity(mock_rb, period, direction) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='fundamentalMetric'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.current_constituents_return_on_equity(..., period, direction, real_time=True) actual = tm.current_constituents_sales_per_share(mock_rb, period, direction) assert_series_equal(pd.Series([5, 1, 2], index=_index * 3, name='fundamentalMetric'), pd.Series(actual)) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.current_constituents_sales_per_share(..., period, direction, real_time=True) with pytest.raises(NotImplementedError): tm.current_constituents_dividend_yield(mock_non_flagship, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_earnings_per_share(mock_non_flagship, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_earnings_per_share_positive(mock_non_flagship, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_net_debt_to_ebitda(mock_non_flagship, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_price_to_book(mock_non_flagship, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_price_to_cash(mock_non_flagship, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_price_to_earnings(mock_non_flagship, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_price_to_earnings_positive(mock_non_flagship, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_price_to_sales(mock_non_flagship, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_return_on_equity(mock_non_flagship, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_sales_per_share(mock_non_flagship, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_dividend_yield(mock_basket_no_parameters, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_earnings_per_share(mock_basket_no_parameters, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_earnings_per_share_positive(mock_basket_no_parameters, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_net_debt_to_ebitda(mock_basket_no_parameters, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_price_to_book(mock_basket_no_parameters, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_price_to_cash(mock_basket_no_parameters, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_price_to_earnings(mock_basket_no_parameters, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_price_to_earnings_positive(mock_basket_no_parameters, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_price_to_sales(mock_basket_no_parameters, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_return_on_equity(mock_basket_no_parameters, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_sales_per_share(mock_basket_no_parameters, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_dividend_yield(mock_basket_no_flagship_parameter, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_earnings_per_share(mock_basket_no_flagship_parameter, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_earnings_per_share_positive(mock_basket_no_flagship_parameter, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_net_debt_to_ebitda(mock_basket_no_flagship_parameter, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_price_to_book(mock_basket_no_flagship_parameter, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_price_to_cash(mock_basket_no_flagship_parameter, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_price_to_earnings(mock_basket_no_flagship_parameter, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_price_to_earnings_positive(mock_basket_no_flagship_parameter, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_price_to_sales(mock_basket_no_flagship_parameter, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_return_on_equity(mock_basket_no_flagship_parameter, period, direction) with pytest.raises(NotImplementedError): tm.current_constituents_sales_per_share(mock_basket_no_flagship_parameter, period, direction) replace.restore() def test_realized_volatility(): from gs_quant.timeseries.econometrics import volatility, Returns from gs_quant.timeseries.statistics import generate_series random = generate_series(100).rename('spot') window = 10 type_ = Returns.SIMPLE replace = Replacer() mock_data = replace('gs_quant.timeseries.measures.get_historical_and_last_for_measure', Mock()) return_value = MarketDataResponseFrame(random) return_value.dataset_ids = _test_datasets mock_data.return_value = return_value expected = volatility(random, window, type_) actual = tm.realized_volatility(Cross('MA123', 'ABCXYZ'), window, type_) assert_series_equal(expected, pd.Series(actual)) assert actual.dataset_ids == _test_datasets actual = tm.realized_volatility(Stock('MA4B66MW5E27U9VBB94', 'AAPL'), window, type_) assert_series_equal(expected, pd.Series(actual)) assert actual.dataset_ids == _test_datasets replace.restore() def test_esg_headline_metric(): replace = Replacer() mock_aapl = Stock('MA4B66MW5E27U9VBB94', 'AAPL') replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_esg) actual = tm.esg_headline_metric(mock_aapl, tm.EsgMetric.ENVIRONMENTAL_SOCIAL_NUMERIC_SCORE) assert_series_equal(pd.Series([2, 4, 6], index=_index * 3, name='esNumericScore'), pd.Series(actual)) actual = tm.esg_headline_metric(mock_aapl, tm.EsgMetric.ENVIRONMENTAL_SOCIAL_POLICY_SCORE) assert_series_equal(pd.Series([2, 4, 6], index=_index * 3, name='esPolicyScore'), pd.Series(actual)) actual = tm.esg_headline_metric(mock_aapl, tm.EsgMetric.ENVIRONMENTAL_SOCIAL_AGGREGATE_SCORE) assert_series_equal(pd.Series([2, 4, 6], index=_index * 3, name='esScore'), pd.Series(actual)) actual = tm.esg_headline_metric(mock_aapl, tm.EsgMetric.ENVIRONMENTAL_SOCIAL_PRODUCT_IMPACT_SCORE) assert_series_equal(pd.Series([2, 4, 6], index=_index * 3, name='esProductImpactScore'), pd.Series(actual)) actual = tm.esg_headline_metric(mock_aapl, tm.EsgMetric.GOVERNANCE_AGGREGATE_SCORE) assert_series_equal(pd.Series([2, 4, 6], index=_index * 3, name='gScore'), pd.Series(actual)) actual = tm.esg_headline_metric(mock_aapl, tm.EsgMetric.ENVIRONMENTAL_SOCIAL_MOMENTUM_SCORE) assert_series_equal(pd.Series([2, 4, 6], index=_index * 3, name='esMomentumScore'), pd.Series(actual)) actual = tm.esg_headline_metric(mock_aapl, tm.EsgMetric.GOVERNANCE_REGIONAL_SCORE) assert_series_equal(pd.Series([2, 4, 6], index=_index * 3, name='gRegionalScore'), pd.Series(actual)) actual = tm.esg_headline_metric(mock_aapl, tm.EsgMetric.CONTROVERSY_SCORE) assert_series_equal(pd.Series([2, 4, 6], index=_index * 3, name='controversyScore'), pd.Series(actual)) actual = tm.esg_headline_metric(mock_aapl, tm.EsgMetric.ENVIRONMENTAL_SOCIAL_NUMERIC_PERCENTILE) assert_series_equal(pd.Series([81.2, 75.4, 65.7], index=_index * 3, name='esNumericPercentile'), pd.Series(actual)) actual = tm.esg_headline_metric(mock_aapl, tm.EsgMetric.ENVIRONMENTAL_SOCIAL_POLICY_PERCENTILE) assert_series_equal(pd.Series([81.2, 75.4, 65.7], index=_index * 3, name='esPolicyPercentile'), pd.Series(actual)) actual = tm.esg_headline_metric(mock_aapl, tm.EsgMetric.ENVIRONMENTAL_SOCIAL_AGGREGATE_PERCENTILE) assert_series_equal(pd.Series([81.2, 75.4, 65.7], index=_index * 3, name='esPercentile'), pd.Series(actual)) actual = tm.esg_headline_metric(mock_aapl, tm.EsgMetric.ENVIRONMENTAL_SOCIAL_PRODUCT_IMPACT_PERCENTILE) assert_series_equal( pd.Series([81.2, 75.4, 65.7], index=_index * 3, name='esProductImpactPercentile'), pd.Series(actual) ) actual = tm.esg_headline_metric(mock_aapl, tm.EsgMetric.GOVERNANCE_AGGREGATE_PERCENTILE) assert_series_equal(pd.Series([81.2, 75.4, 65.7], index=_index * 3, name='gPercentile'), pd.Series(actual)) actual = tm.esg_headline_metric(mock_aapl, tm.EsgMetric.ENVIRONMENTAL_SOCIAL_MOMENTUM_PERCENTILE) assert_series_equal(pd.Series([81.2, 75.4, 65.7], index=_index * 3, name='esMomentumPercentile'), pd.Series(actual)) actual = tm.esg_headline_metric(mock_aapl, tm.EsgMetric.GOVERNANCE_REGIONAL_PERCENTILE) assert_series_equal(pd.Series([81.2, 75.4, 65.7], index=_index * 3, name='gRegionalPercentile'), pd.Series(actual)) actual = tm.esg_headline_metric(mock_aapl, tm.EsgMetric.CONTROVERSY_PERCENTILE) assert_series_equal( pd.Series([81.2, 75.4, 65.7], index=_index * 3, name='controversyPercentile'), pd.Series(actual) ) actual = tm.esg_headline_metric(mock_aapl, tm.EsgMetric.ENVIRONMENTAL_SOCIAL_DISCLOSURE) assert_series_equal( pd.Series([49.2, 55.7, 98.4], index=_index * 3, name='esDisclosurePercentage'), pd.Series(actual) ) with pytest.raises(NotImplementedError): tm.esg_headline_metric(mock_aapl, tm.EsgMetric.ENVIRONMENTAL_SOCIAL_NUMERIC_SCORE, real_time=True) replace.restore() def test_rating(): replace = Replacer() mock_aapl = Stock('MA4B66MW5E27U9VBB94', 'AAPL') replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_rating) actual = tm.rating(mock_aapl, tm._RatingMetric.RATING) assert_series_equal( pd.Series( [1, -1, 1, 0], index=pd.to_datetime( [dt.date(2020, 8, 13), dt.date(2020, 8, 14), dt.date(2020, 8, 17), dt.date(2020, 8, 18)] ), name='rating', ), pd.Series(actual), ) actual = tm.rating(mock_aapl, tm._RatingMetric.CONVICTION_LIST) assert_series_equal( pd.Series( [1, 0, 0, 0], index=pd.to_datetime( [dt.date(2020, 8, 13), dt.date(2020, 8, 14), dt.date(2020, 8, 17), dt.date(2020, 8, 18)] ), name='convictionList', ), pd.Series(actual), ) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.rating(mock_aapl, tm._RatingMetric.RATING, real_time=True) replace.restore() def test_fair_value(mocker): mocker.patch.object( GsSession.__class__, 'default_value', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) replace = Replacer() mock_usdeur = Cross('MAQB05GD31BA5HWV', 'USDEUR') mock_eurusd = Cross('MAA0NE9QX2ABETG6', "EURUSD") xrefs = replace('gs_quant.timeseries.measures.GsAssetApi.get_asset_xrefs', Mock()) xrefs.return_value = [ GsTemporalXRef( dt.date(2019, 1, 1), dt.date(2952, 12, 31), XRef( bbid='EURUSD', ), ) ] asset = replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()) asset.return_value = mock_usdeur replace('gs_quant.timeseries.measures.Dataset.get_data', mock_gsdeer_gsfeer) index = [dt.date(2000, 1, 1), dt.date(2010, 4, 1), dt.date(2020, 7, 1)] actual = tm.fair_value(mock_usdeur, tm.EquilibriumExchangeRateMetric.GSDEER) assert_series_equal(pd.Series([1, 1.2, 1.1], index=index, name='gsdeer'), pd.Series(actual)) actual = tm.fair_value(mock_usdeur, tm.EquilibriumExchangeRateMetric.GSFEER) assert_series_equal(pd.Series([2, 1.8, 1.9], index=index, name='gsfeer'), pd.Series(actual)) actual = tm.fair_value(mock_eurusd, tm.EquilibriumExchangeRateMetric.GSDEER) assert_series_equal(pd.Series([1 / 1, 1 / 1.2, 1 / 1.1], index=index, name='gsdeer'), pd.Series(actual)) actual = tm.fair_value(mock_eurusd, tm.EquilibriumExchangeRateMetric.GSFEER) assert_series_equal(pd.Series([1 / 2, 1 / 1.8, 1 / 1.9], index=index, name='gsfeer'), pd.Series(actual)) with pytest.raises(NotImplementedError): tm.fair_value(mock_usdeur, tm.EquilibriumExchangeRateMetric.GSDEER, real_time=True) replace.restore() def test_factor_profile(): replace = Replacer() mock_aapl = Stock('MA4B66MW5E27U9VBB94', 'AAPL') replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_factor_profile) actual = tm.factor_profile(mock_aapl, tm._FactorProfileMetric.GROWTH_SCORE) assert_series_equal( pd.Series( [0.238, 0.234, 0.234, 0.230], index=pd.to_datetime( [dt.date(2020, 8, 13), dt.date(2020, 8, 14), dt.date(2020, 8, 17), dt.date(2020, 8, 18)] ), name='growthScore', ), pd.Series(actual), ) assert actual.dataset_ids == _test_datasets actual = tm.factor_profile(mock_aapl, tm._FactorProfileMetric.FINANCIAL_RETURNS_SCORE) assert_series_equal( pd.Series( [0.982, 0.982, 0.982, 0.982], index=pd.to_datetime( [dt.date(2020, 8, 13), dt.date(2020, 8, 14), dt.date(2020, 8, 17), dt.date(2020, 8, 18)] ), name='financialReturnsScore', ), pd.Series(actual), ) assert actual.dataset_ids == _test_datasets actual = tm.factor_profile(mock_aapl, tm._FactorProfileMetric.MULTIPLE_SCORE) assert_series_equal( pd.Series( [0.204, 0.192, 0.190, 0.190], index=pd.to_datetime( [dt.date(2020, 8, 13), dt.date(2020, 8, 14), dt.date(2020, 8, 17), dt.date(2020, 8, 18)] ), name='multipleScore', ), pd.Series(actual), ) assert actual.dataset_ids == _test_datasets actual = tm.factor_profile(mock_aapl, tm._FactorProfileMetric.INTEGRATED_SCORE) assert_series_equal( pd.Series( [0.672, 0.676, 0.676, 0.674], index=pd.to_datetime( [dt.date(2020, 8, 13), dt.date(2020, 8, 14), dt.date(2020, 8, 17), dt.date(2020, 8, 18)] ), name='integratedScore', ), pd.Series(actual), ) assert actual.dataset_ids == _test_datasets with pytest.raises(NotImplementedError): tm.factor_profile(mock_aapl, tm._FactorProfileMetric.GROWTH_SCORE, real_time=True) replace.restore() def test_commodity_forecast(): replace = Replacer() mock_spgcsb = Index('MA74Y70Z4D4TBX9H', 'SPGCSB', 'GSCI Sugar') replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_commodity_forecast) actual = tm.commodity_forecast(mock_spgcsb, '3m', tm._CommodityForecastType.SPOT_RETURN) assert_series_equal( pd.Series( [1700, 1400, 1500, 1600], index=pd.to_datetime( [dt.date(2020, 8, 13), dt.date(2020, 8, 14), dt.date(2020, 8, 17), dt.date(2020, 8, 18)] ), name='commodityForecast', ), pd.Series(actual), ) with pytest.raises(NotImplementedError): tm.commodity_forecast(mock_spgcsb, '3m', tm._CommodityForecastType.SPOT_RETURN, real_time=True) replace.restore() def test_commodity_forecast_time_series(): mock_brent = Index('MA8MBQN6VHKZMW92', 'CO', 'Brent') replace = Replacer() replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_commodity_forecast_time_series) with pytest.raises(ValueError): tm.commodity_forecast_time_series( 100, forecastFrequency=tm._CommodityForecastTimeSeriesPeriodType.ANNUAL, forecastType=tm._CommodityForecastType.SPOT, ) actual = tm.commodity_forecast_time_series( 'MA8MBQN6VHKZMW92', forecastFrequency=tm._CommodityForecastTimeSeriesPeriodType.ANNUAL.value, forecastType=tm._CommodityForecastType.SPOT, ) assert not pd.Series(actual).empty actual = tm.commodity_forecast_time_series( mock_brent, forecastFrequency=tm._CommodityForecastTimeSeriesPeriodType.QUARTERLY.value, forecastType=tm._CommodityForecastType.SPOT, ) assert not pd.Series(actual).empty actual = tm.commodity_forecast_time_series( mock_brent, forecastFrequency=tm._CommodityForecastTimeSeriesPeriodType.MONTHLY.value, forecastType=tm._CommodityForecastType.SPOT, ) assert not pd.Series(actual).empty actual = tm.commodity_forecast_time_series( mock_brent, forecastFrequency=tm._CommodityForecastTimeSeriesPeriodType.SHORT_TERM.value, forecastType=tm._CommodityForecastType.SPOT, ) assert not pd.Series(actual).empty actual = tm.commodity_forecast_time_series( mock_brent, forecastFrequency=tm._CommodityForecastTimeSeriesPeriodType.ANNUAL, forecastType=tm._CommodityForecastType.SPOT, ) assert not pd.Series(actual).empty with pytest.raises(ValueError): tm.commodity_forecast_time_series( mock_brent, forecastFrequency='Annually', forecastType=tm._CommodityForecastType.SPOT ) with pytest.raises(NotImplementedError): tm.commodity_forecast_time_series( mock_brent, forecastFrequency=tm._CommodityForecastTimeSeriesPeriodType.ANNUAL, forecastType=tm._CommodityForecastType.SPOT, real_time=True, ) replace.restore() def test_fx_implied_correlation(): replace = Replacer() e1 = { 'last_updated_time': '2021-02-03T15:11:06.409Z', 'owner_id': 'qwerty', 'classifications': {}, 'description': 'Europe Euro to United States Dollar', 'last_updated_by_id': 'qwerty', 'id': 'MAA0NE9QX2ABETG6', 'listed': True, 'created_time': '2017-06-16T13:59:23.617Z', 'exchange': '', 'parameters': {}, 'type': 'Cross', 'entitlements': { 'edit': ['guid:qwerty'], 'view': ['internal', 'guid:qwerty', 'external'], 'admin': ['guid:qwerty'], }, 'name': 'EURUSD', 'region': 'Global', 'styles': [], 'short_name': 'EURUSD', 'identifiers': [ {'type': 'SECNAME', 'value': 'USD/EUR'}, {'type': 'BID', 'value': 'EURUSD'}, {'type': 'MDAPI', 'value': 'USD/EUR'}, {'type': 'CROSS', 'value': 'USD/EUR'}, ], 'rank': 1.0, 'asset_class': 'FX', 'xref': {'bbid': 'EURUSD', 'cross': 'USD/EUR', 'secName': 'USD/EUR', 'mdapi': 'USD/EUR'}, 'currency': 'USD', 'tags': ['USDEUR', 'EURUSD', 'Execution'], 'created_by_id': 'qwerty', } e2 = { 'last_updated_time': '2021-02-03T15:05:20.993Z', 'owner_id': 'azerty', 'classifications': {}, 'description': 'United States Dollar to Europe Euro', 'last_updated_by_id': 'azerty', 'id': 'MAQB05GD31BA5HWV', 'listed': True, 'created_time': '2017-06-16T13:52:21.396Z', 'exchange': '', 'parameters': {}, 'type': 'Cross', 'entitlements': { 'edit': ['guid:azerty'], 'view': ['internal', 'guid:azerty', 'external'], 'admin': ['guid:azerty'], }, 'name': 'USDEUR', 'region': 'Global', 'styles': [], 'short_name': 'USDEUR', 'rank': 1.0, 'asset_class': 'FX', 'xref': {'bbid': 'USDEUR', 'cross': 'EUR/USD', 'secName': 'EUR/USD', 'mdapi': 'EUR/USD'}, 'currency': 'EUR', 'tags': ['EURUSD', 'USDEUR'], 'created_by_id': 'azerty', } EURUSD = Cross('MAA0NE9QX2ABETG6', 'USD/EUR', entity=e1) USDEUR = Cross('MAQB05GD31BA5HWV', 'EUR/USD', entity=e2) USDJPY = Cross('MATGYV0J9MPX534Z', 'JPY/USD') JPYUSD = Cross('MAYJPCVVF2RWXCES', 'USD/JPY') EURGBP = Cross('MA3AMDKY4YJ83G1E', 'GBP/EUR') EURJPY = Cross('MAYPHS80JRWDJ8RC', 'JPY/EUR') JPYEUR = Cross('MAWVKC8Q6405ZWNE', 'EUR/JPY') bbid_to_asset_dict = { 'EURUSD': EURUSD, 'USDEUR': USDEUR, 'USDJPY': USDJPY, 'JPYUSD': JPYUSD, 'EURJPY': EURJPY, 'JPYEUR': JPYEUR, 'EURGBP': EURGBP, } mqid_to_asset_dict = { 'MAA0NE9QX2ABETG6': EURUSD, 'MAQB05GD31BA5HWV': USDEUR, 'MATGYV0J9MPX534Z': USDJPY, 'MAYJPCVVF2RWXCES': JPYUSD, 'MAYPHS80JRWDJ8RC': EURJPY, 'MAWVKC8Q6405ZWNE': JPYEUR, 'MA3AMDKY4YJ83G1E': EURGBP, } df = pd.DataFrame( { 'EURUSD USDJPY': [-0.47579170795443204, -0.4842168280011238, -0.48220935681227195], 'USDEUR USDJPY': [0.47579175, 0.48421678, 0.48220939], 'EURUSD JPYUSD': [0.47579175, 0.48421678, 0.48220939], 'USDEUR JPYUSD': [-0.47579175, -0.48421678, -0.48220939], 'USDJPY EURUSD': [-0.47579175, -0.48421678, -0.48220939], 'date': [pd.Timestamp('2020-09-02'), pd.Timestamp('2020-09-03'), pd.Timestamp('2020-09-04')], } ) df = df.set_index('date') def mock_get_asset(cls, asset_id, id_type: dict) -> Cross: if id_type == AssetIdentifier.BLOOMBERG_ID: return bbid_to_asset_dict[asset_id] else: return mqid_to_asset_dict[asset_id] def cross_to_bbid(asset, id_type) -> str: if asset == EURUSD: return 'EURUSD' elif asset == USDEUR: return 'USDEUR' elif asset == USDJPY: return 'USDJPY' elif asset == JPYUSD: return 'JPYUSD' elif asset == EURJPY: return 'EURJPY' elif asset == JPYEUR: return 'JPYEUR' elif asset == EURGBP: return 'EURGBP' replace('gs_quant.markets.securities.SecurityMaster.get_asset', mock_get_asset) replace('gs_quant.markets.securities.Asset.get_identifier', cross_to_bbid) replace('gs_quant.timeseries.measures._market_data_timed', mock_fx_correlation) with DataContext(dt.date(2020, 9, 2), dt.date(2020, 9, 4)): # supported q_1 = tm.fx_implied_correlation(EURUSD, USDJPY, '3m') q_2 = tm.fx_implied_correlation(USDEUR, USDJPY, '3m') q_3 = tm.fx_implied_correlation(EURUSD, JPYUSD, '3m') q_4 = tm.fx_implied_correlation(USDEUR, JPYUSD, '3m') q_5 = tm.fx_implied_correlation(USDJPY, EURUSD, '3m') assert_series_equal(df['EURUSD USDJPY'], pd.Series(q_1, name='EURUSD USDJPY')) assert_series_equal(df['USDEUR USDJPY'], pd.Series(q_2, name='USDEUR USDJPY')) assert_series_equal(df['EURUSD JPYUSD'], pd.Series(q_3, name='EURUSD JPYUSD')) assert_series_equal(df['USDEUR JPYUSD'], pd.Series(q_4, name='USDEUR JPYUSD')) assert_series_equal(df['USDJPY EURUSD'], pd.Series(q_5, name='USDJPY EURUSD')) replace('gs_quant.timeseries.measures._market_data_timed', lambda q, request_id=None: pd.DataFrame()) assert tm.fx_implied_correlation(USDJPY, EURUSD, '3m').empty # not supported with pytest.raises(NotImplementedError): tm.fx_implied_correlation(EURUSD, EURGBP, '3m', real_time=True) with pytest.raises(MqError): tm.fx_implied_correlation(USDJPY, EURGBP, '3m', real_time=False) with pytest.raises(MqError): mock_spx = Index('MA890', AssetClass.Equity, 'SPX') tm.fx_implied_correlation(USDJPY, mock_spx, '3m', real_time=False) replace.restore() def mock_forward_curve_peak(_cls, _q, ignore_errors=False): d = {'forwardPrice': [40.05], 'quantityBucket': ["PEAK"], 'contract': ["U20"]} df = MarketDataResponseFrame(data=d, index=pd.to_datetime([dt.date(2020, 8, 20)])) df.dataset_ids = _test_datasets return df def mock_forward_curve_peak_holiday(_cls, _q, ignore_errors=False): d = {'forwardPrice': [26.567302], 'quantityBucket': ["PEAK"], 'contract': ["U20"]} df = MarketDataResponseFrame(data=d, index=pd.to_datetime([dt.date(2020, 9, 4)])) df.dataset_ids = _test_datasets return df def mock_forward_curve_offpeak(_cls, _q, ignore_errors=False): d = {'forwardPrice': [30.9692, 44.9868], 'quantityBucket': ["7X8", "SUH1X16"], 'contract': ["U20", "U20"]} df = MarketDataResponseFrame(data=d, index=pd.to_datetime([dt.date(2020, 8, 20)] * 2)) df.dataset_ids = _test_datasets return df def mock_empty_forward_curve(_cls, _q, ignore_errors=False): df = MarketDataResponseFrame() df.dataset_ids = () return df def test_forward_curve(): # Set output and mock attributes for test target = {'Peak_CAISO': [40.05], 'OffPeak_CAISO': [34.4736], 'Peak_CAISO_holiday': [26.567302]} replace = Replacer() mock_CAISO = Index('MA001', AssetClass.Commod, 'CAISO') bbid_mock = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) bbid_mock.return_value = 'CAISO' mock_get_data = replace('gs_quant.data.dataset.Dataset.get_data_last', Mock()) with DataContext(dt.date(2020, 8, 20), dt.date(2020, 9, 20)): # Test for term structure for bucket: peak replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_forward_curve_peak) mock_get_data.return_value = pd.DataFrame(data=[0], index=[dt.date(2020, 8, 20)]) actual = tm.forward_curve(mock_CAISO, bucket='Peak', market_date='20200820') assert_series_equal( pd.Series(target['Peak_CAISO'], index=[dt.date(2020, 9, 1)], name='forwardPrice'), pd.Series(actual) ) # Test for holiday date query, which would fetch value for prev date replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_forward_curve_peak_holiday) mock_get_data.return_value = pd.DataFrame(data=[0], index=[dt.date(2020, 9, 4)]) actual = tm.forward_curve(mock_CAISO, bucket='Peak', market_date='20200907') assert_series_equal( pd.Series(target['Peak_CAISO_holiday'], index=[dt.date(2020, 9, 1)], name='forwardPrice'), pd.Series(actual) ) # Test for term structure for bucket: Off peak, which requires wtd avg computation replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_forward_curve_offpeak) mock_get_data.return_value = pd.DataFrame(data=[0], index=[dt.date(2020, 8, 25)]) actual = tm.forward_curve(mock_CAISO, bucket='OffPeak', market_date='20200825') assert_series_equal( pd.Series(target['OffPeak_CAISO'], index=[dt.date(2020, 9, 1)], name='forwardPrice'), pd.Series(actual) ) # Test for an empty data query result replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_empty_forward_curve) mock_get_data.return_value = pd.DataFrame(data=[0], index=[dt.date(2020, 8, 25)]) actual = tm.forward_curve(mock_CAISO, bucket='OffPeak', market_date='20200825') assert actual.empty # Test for intra day frequency error with pytest.raises(MqValueError): tm.forward_curve(mock_CAISO, bucket='7x24', market_date='20200820', real_time=True) # Test for future date query with pytest.raises(MqValueError): tm.forward_curve(mock_CAISO, bucket='7x24', market_date='20400820') # Test for weekend date query with pytest.raises(MqValueError): tm.forward_curve(mock_CAISO, bucket='7x24', market_date='20200822') # Test for term structure for market date beyond end date which is an invalid scenario with pytest.raises(MqValueError): tm.forward_curve(mock_CAISO, bucket='OffPeak', market_date='20201001') # Test for empty market date in parameters, will fail as end date for this test is in past with pytest.raises(MqValueError): actual = tm.forward_curve(mock_CAISO, bucket='Peak') # Test for invalid market_date data type with pytest.raises(MqTypeError): actual = tm.forward_curve(mock_CAISO, bucket='Peak', market_date=20201001) # Test for invalid market_date string format with pytest.raises(MqValueError): actual = tm.forward_curve(mock_CAISO, bucket='Peak', market_date='9, Jan 2020') # Test for default date always returning a weekday replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_forward_curve_peak) mock_todate = replace('pandas.Timestamp.today', Mock()) mock_todate.return_value = pd.Timestamp('2020-08-22') mock_get_data.return_value = pd.DataFrame(data=[0], index=[dt.date(2020, 8, 20)]) actual = tm.forward_curve(mock_CAISO, bucket='Peak') assert_series_equal( pd.Series(target['Peak_CAISO'], index=[dt.date(2020, 9, 1)], name='forwardPrice'), pd.Series(actual) ) replace.restore() def mock_us_gas_forward_curve(_cls, _q, ignore_errors=False): d = {'forwardPrice': [1.614, 1.685, 1.814], 'priceMethod': ["GDD", "GDD", "GDD"], 'contract': ["H24", "J24", "K24"]} date = dt.date(2020, 8, 20) df = MarketDataResponseFrame(data=d, index=pd.to_datetime([date, date, date])) df.dataset_ids = _test_datasets return df def test_us_gas_forward_curve(): replace = Replacer() asset = CommodityNaturalGasHub('MAA66', 'HENRY') mock_get_data = replace('gs_quant.data.dataset.Dataset.get_data_last', Mock()) expeted_output = [1.614, 1.685, 1.814] expected_dates = [dt.date(2024, 3, 1), dt.date(2024, 4, 1), dt.date(2024, 5, 1)] with DataContext(dt.date(2023, 12, 20), dt.date(2024, 5, 20)): # G24, H24, J24, K24 # Normal case with range contracts between (2023, 12, 20) - (2024, 5, 20) replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_us_gas_forward_curve) mock_get_data.return_value = pd.DataFrame(data=[0], index=[dt.date(2024, 2, 16)]) actual = tm.forward_curve_ng(asset, market_date='20240216') assert_series_equal(pd.Series(expeted_output, index=expected_dates, name='forwardPrice'), pd.Series(actual)) # market date is a holiday (2024, 2, 19) -> Washington's Birthday replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_us_gas_forward_curve) mock_get_data.return_value = pd.DataFrame(data=[0], index=[dt.date(2024, 2, 16)]) actual = tm.forward_curve_ng(asset, market_date='20240219') assert_series_equal(pd.Series(expeted_output, index=expected_dates, name='forwardPrice'), pd.Series(actual)) # Test for an empty data query result replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_empty_forward_curve) mock_get_data.return_value = pd.DataFrame(data=[0], index=[dt.date(2024, 2, 16)]) actual = tm.forward_curve_ng(asset, market_date='20240219') assert actual.empty # Test for default date always returning a weekday replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', mock_us_gas_forward_curve) mock_todate = replace('pandas.Timestamp.today', Mock()) mock_todate.return_value = pd.Timestamp('2024-02-18') mock_get_data.return_value = pd.DataFrame(data=[0], index=[dt.date(2020, 8, 20)]) actual = tm.forward_curve_ng(asset) assert_series_equal(pd.Series(expeted_output, index=expected_dates, name='forwardPrice'), pd.Series(actual)) # Test for intra day frequency error with pytest.raises(MqValueError): tm.forward_curve_ng(asset, market_date='20240216', real_time=True) # Test for future date query with pytest.raises(MqValueError): tm.forward_curve_ng(asset, market_date='20400820') # Test for weekend date query with pytest.raises(MqValueError): tm.forward_curve_ng(asset, market_date='20240217') # Test for term structure for market date beyond end date which is an invalid scenario with pytest.raises(MqValueError): tm.forward_curve_ng(asset, market_date='20240320') # Test for empty market date in parameters, will fail as end date for this test is in past with pytest.raises(MqValueError): mock_todate = replace('pandas.Timestamp.today', Mock()) mock_todate.return_value = pd.Timestamp('2024-10-18') actual = tm.forward_curve_ng(asset) # Test for invalid market_date data type with pytest.raises(MqTypeError): actual = tm.forward_curve_ng(asset, market_date=20201001) # Test for invalid market_date string format with pytest.raises(MqValueError): actual = tm.forward_curve_ng(asset, market_date='9, Jan 2020') def test_eu_ng_hub_to_swap(): # Test for Id provider to return an instrument replace = Replacer() mock_EU_asset = CommodityEUNaturalGasHub('MA001', 'TTF') mock_EU_swap_asset = Swap('MA002', AssetClass.Commod, 'Swap NatGas TTF') mock_EU_swap_asset.id = 'MA002' assets = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) assets.return_value = [mock_EU_swap_asset] actual = tm.eu_ng_hub_to_swap(mock_EU_asset) assert_equal(actual, 'MA002') # Test for Id provider when no instrument found, returns original asset id mock_EU_asset = CommodityEUNaturalGasHub('MA001', 'TTF') assets = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) assets.return_value = [] actual = tm.eu_ng_hub_to_swap(mock_EU_asset) assert_equal(actual, 'MA001') replace.restore() def test_settlement_price(): # Tests for settlement price function replace = Replacer() with DataContext(dt.date(2021, 6, 2), dt.date(2021, 6, 2)): # Test for EEX Asset Asset_Mock = FutureMarket('MA001', AssetClass.Commod, 'DEBM') EEX_mock = replace('gs_quant.entities.entity.Entity.get_entity', Mock()) EEX_mock.return_value = {'parameters': {'exchange': 'EEX', 'productGroup': 'PowerFutures'}} EEX_ds = replace('gs_quant.data.dataset.Dataset.get_data', Mock()) EEX_ds.return_value = pd.DataFrame( data=dict(settlementPrice=20.20, contract='K21'), index=[dt.date(2021, 6, 2)] ) actual = pd.Series(tm.settlement_price(Asset_Mock, contract='K21')) expected = pd.Series([20.20], index=[dt.date(2021, 6, 2)], name='settlementPrice') assert_series_equal(expected, actual) # Test for CarbonCredit Asset Asset_Mock = FutureMarket('MA001', AssetClass.Commod, 'RGGI V19') CC_mock = replace('gs_quant.entities.entity.Entity.get_entity', Mock()) CC_mock.return_value = {'parameters': {'exchange': 'ICE', 'productGroup': 'Physical Environment'}} CC_ds = replace('gs_quant.data.dataset.Dataset.get_data', Mock()) CC_ds.return_value = pd.DataFrame(data=dict(settlementPrice=21.21, contract='K21'), index=[dt.date(2021, 6, 2)]) actual = pd.Series(tm.settlement_price(Asset_Mock, contract='K21')) expected = pd.Series([21.21], index=[dt.date(2021, 6, 2)], name='settlementPrice') assert_series_equal(expected, actual) # Test for ICE Power Asset Asset_Mock = FutureMarket('MA001', AssetClass.Commod, 'GAB') CC_mock = replace('gs_quant.entities.entity.Entity.get_entity', Mock()) CC_mock.return_value = {'parameters': {'exchange': 'ICE', 'productGroup': 'PowerFutures'}} CC_ds = replace('gs_quant.data.dataset.Dataset.get_data', Mock()) CC_ds.return_value = pd.DataFrame(data=dict(settlementPrice=22.22, contract='K21'), index=[dt.date(2021, 6, 2)]) actual = pd.Series(tm.settlement_price(Asset_Mock, contract='K21')) expected = pd.Series([22.22], index=[dt.date(2021, 6, 2)], name='settlementPrice') assert_series_equal(expected, actual) # Test for NASDAQ Power Asset Asset_Mock = FutureMarket('MA001', AssetClass.Commod, 'ENOFUTBL') CC_mock = replace('gs_quant.entities.entity.Entity.get_entity', Mock()) CC_mock.return_value = {'parameters': {'exchange': 'NASDAQ', 'productGroup': 'PowerFutures'}} CC_ds = replace('gs_quant.data.dataset.Dataset.get_data', Mock()) CC_ds.return_value = pd.DataFrame(data=dict(settlementPrice=23.23, contract='K21'), index=[dt.date(2021, 6, 2)]) actual = pd.Series(tm.settlement_price(Asset_Mock, contract='K21')) expected = pd.Series([23.23], index=[dt.date(2021, 6, 2)], name='settlementPrice') assert_series_equal(expected, actual) # Test for empty result CC_ds.return_value = pd.DataFrame() actual = pd.Series(tm.settlement_price(Asset_Mock, contract='K21')) expected = pd.Series(dtype=float) assert_series_equal(expected, actual) # Test for asset with no exchange info Asset_Mock = FutureMarket('MA001', AssetClass.Commod, 'XYZ') empty_mock = replace('gs_quant.entities.entity.Entity.get_entity', Mock()) empty_mock.return_value = {'parameters': {}} with pytest.raises(MqTypeError): tm.settlement_price(Asset_Mock, contract='F21') # Test for asset with any only some asset params filled Asset_Mock = FutureMarket('MA001', AssetClass.Commod, 'XYZ') empty_mock = replace('gs_quant.entities.entity.Entity.get_entity', Mock()) empty_mock.return_value = {'parameters': {'exchange': 'OTHER_EXCHANGE'}} with pytest.raises(MqTypeError): tm.settlement_price(Asset_Mock, contract='F21') # Test for asset with any other exchange, currently not covered Asset_Mock = FutureMarket('MA001', AssetClass.Commod, 'XYZ') empty_mock = replace('gs_quant.entities.entity.Entity.get_entity', Mock()) empty_mock.return_value = {'parameters': {'exchange': 'OTHER_EXCHANGE', 'productGroup': 'OTHER_PRODUCT'}} with pytest.raises(MqTypeError): tm.settlement_price(Asset_Mock, contract='F21') # Test for real time data setting on query with pytest.raises(MqValueError): tm.settlement_price(Asset_Mock, contract='F21', real_time=True) replace.restore() def test_hloc_prices(): mock_spx = Index('MA890', AssetClass.Equity, 'SPX') with pytest.raises(MqValueError): tm.hloc_prices(mock_spx, real_time=True) replace = Replacer() empty_df = replace('gs_quant.timeseries.measures.Asset.get_hloc_prices', Mock()) empty_df.return_value = pd.DataFrame() with DataContext(dt.date(2021, 6, 2), dt.date(2021, 6, 2)): tm.hloc_prices(mock_spx, real_time=False) replace.restore() def test_thematic_model_exposure(): mock_asset = GsAsset(asset_class='Equity', id='MA1234567890', type_='Custom Basket', name='test') mock_basket = CustomBasket(gs_asset=mock_asset, _finish_init=False) with pytest.raises(MqValueError): tm.thematic_model_exposure(mock_basket, 'TICKER', real_time=True) replace = Replacer() empty_df = replace('gs_quant.timeseries.measures.PositionedEntity.get_thematic_exposure', Mock()) empty_df.return_value = pd.DataFrame() with DataContext(dt.date(2021, 6, 2), dt.date(2021, 6, 2)): tm.thematic_model_exposure(mock_basket, 'TICKER') replace.restore() def test_thematic_model_beta(): mock_asset = GsAsset(asset_class='Equity', id='MA1234567890', type_='Custom Basket', name='test') mock_basket = CustomBasket(gs_asset=mock_asset, _finish_init=False) with pytest.raises(MqValueError): tm.thematic_model_beta(mock_basket, 'TICKER', real_time=True) replace = Replacer() empty_df = replace('gs_quant.timeseries.measures.PositionedEntity.get_thematic_beta', Mock()) empty_df.return_value = pd.DataFrame() with DataContext(dt.date(2021, 6, 2), dt.date(2021, 6, 2)): tm.thematic_model_beta(mock_basket, 'TICKER') replace.restore() def test_thematic_model_beta_single_stock(): mock_asset = GsAsset(asset_class='Equity', id='MA4B66MW5E27U9VBB94', type_='Single Stock', name='test') mock_asset_entity: Dict = json.loads(json.dumps(mock_asset.as_dict(), cls=JSONEncoder)) mock_stock = Stock( mock_asset.id, mock_asset.name, mock_asset.exchange, mock_asset.currency, entity=mock_asset_entity ) with pytest.raises(MqValueError): tm.thematic_model_beta(mock_stock, 'TICKER', real_time=True) replace = Replacer() empty_df = replace('gs_quant.timeseries.measures.Stock.get_thematic_beta', Mock()) empty_df.return_value = pd.DataFrame() with DataContext(dt.date(2021, 6, 2), dt.date(2021, 6, 2)): tm.thematic_model_beta(mock_stock, 'TICKER') replace.restore() def test_retail_interest_agg(): mock_spx = Index('MA890', AssetClass.Equity, 'SPX', entity={'type': 'Index', 'underlying_asset_ids': []}) retail_df = pd.DataFrame( data={ 'shares': [10.0, 10.0], 'notional': [100.0, 100.0], 'underlyingSourceCategory': ['All', 'All'], 'impliedRetailShares': [5.0, 3.0], 'impliedRetailNotional': [60.0, 30.0], 'impliedRetailPctShares': [50.0, 30.0], 'impliedRetailPctNotional': [60.0, 30.0], 'assetId': ['MA4B66MW5E27UALNBLL', 'MA4B66MW5E27UALNBLL'], }, index=[pd.Timestamp('2021-12-20'), pd.Timestamp('2021-12-20')], ) retail_df_energy = pd.DataFrame( data={ 'shares': [10.0], 'notional': [100.0], 'underlyingSourceCategory': ['All'], 'impliedRetailShares': [5.0], 'impliedRetailNotional': [60.0], 'impliedRetailPctShares': [50.0], 'impliedRetailPctNotional': [60.0], 'assetId': ['MA4B66MW5E27UALNBLL'], }, index=[pd.Timestamp('2021-12-20')], ) replace = Replacer() mock_retail = replace('gs_quant.timeseries.measures.get_dataset_with_many_assets', Mock()) mock_retail.return_value = retail_df with pytest.raises(NotImplementedError): tm.retail_interest_agg( ..., data_source=tm.UnderlyingSourceCategory.ALL, sector=tm.GICSSector.ALL, real_time=True ) with pytest.raises(MqError): tm.retail_interest_agg(mock_spx) mock_spx.entity['underlying_asset_ids'] = ['MA4B66MW5E27U9VBB6A', 'MA4B66MW5E27U9VBB86', 'MA4B66MW5E27U9VBB8U'] positions_df = replace('gs_quant.timeseries.measures.PositionedEntity.get_positions_data', Mock()) positions_df.return_value = { 'id': ['MA4B66MW5E27UALNBLL', 'MA4B66MW5E27UALNBLL'], 'assetClassificationsGicsSector': ['Energy', 'Health Care'], 'positionDate': ['2012-12-20', '2012-12-20'], } expected_df = pd.DataFrame( data={'shares': [20.0], 'impliedRetailPctShares': [40.0], 'impliedRetailPctNotional': [60.0]}, index=[pd.Timestamp('2021-12-20')], ) assert_series_equal( ExtendedSeries(expected_df['shares']), tm.retail_interest_agg(mock_spx, tm.RetailMeasures.SHARES, tm.UnderlyingSourceCategory.ALL, tm.GICSSector.ALL), ) assert_series_equal( ExtendedSeries(expected_df['impliedRetailPctShares']), tm.retail_interest_agg( mock_spx, tm.RetailMeasures.RETAIL_PCT_SHARES, tm.UnderlyingSourceCategory.ALL, tm.GICSSector.ALL ), ) mock_retail.return_value = retail_df_energy assert_series_equal( ExtendedSeries(expected_df['impliedRetailPctNotional']), tm.retail_interest_agg( mock_spx, tm.RetailMeasures.RETAIL_PCT_NOTIONAL, tm.UnderlyingSourceCategory.ALL, tm.GICSSector.ENERGY ), ) mock_retail.return_value = pd.DataFrame(dtype=float) assert_series_equal( ExtendedSeries(dtype=float), tm.retail_interest_agg( mock_spx, tm.RetailMeasures.RETAIL_PCT_NOTIONAL, tm.UnderlyingSourceCategory.ALL, tm.GICSSector.ENERGY ), ) replace.restore() def test_s3_long_short_concentration(): replace = Replacer() # Mock Returns mock_asset = replace('gs_quant.markets.securities.Asset.get_marquee_id', Mock()) mock_asset.return_value = "MAF1701E17G167B0" mock_dataset = replace('gs_quant.data.dataset.Dataset.get_data', Mock()) mock_dataset.return_value = pd.DataFrame( { 'date': ['2025-02-12', '2025-02-13', '2025-02-14'], 'assetId': ['MAF1701E17G167B0', 'MAF1701E17G167B0', 'MAF1701E17G167B0'], 's3Metric': ['Long Crowding', 'Long Crowding', 'Long Crowding'], 'value': [0.337286, 0.337629, 0.338065], 'updateTime': ['2025-02-18 11:23:41+00:00', '2025-02-18 11:23:41+00:00', '2025-02-18 11:23:41+00:00'], } ) # Calling Tested Function with DataContext (since only available in PTP) with DataContext(dt.date(2025, 2, 10), dt.date(2025, 2, 17)): actual = tm.s3_long_short_concentration(mock_asset, tm.S3Metrics.LONG_CROWDING) assert_series_equal(ExtendedSeries(pd.Series([0.337286, 0.337629, 0.338065]), name='value'), actual) replace.restore() if __name__ == '__main__': pytest.main(args=["test_measures.py"]) ================================================ FILE: gs_quant/test/timeseries/test_measures_countries.py ================================================ """ Copyright 2020 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import pandas as pd import pytest from testfixtures import Replacer from testfixtures.mock import Mock import gs_quant.timeseries.measures_countries as mc from gs_quant.api.gs.data import MarketDataResponseFrame from gs_quant.data.core import DataContext def test_fci(): with pytest.raises(NotImplementedError): mc.fci('IN', real_time=True) data = {'fci': [101, 102, 103], 'realFCI': [100, 99, 98], 'realTWIContribution': [100, 100, 98]} idx = pd.date_range('2020-01-01', freq='D', periods=3) df = MarketDataResponseFrame(data=data, index=idx) df.dataset_ids = ('FCI',) replace = Replacer() mock = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) mock.return_value = df with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 3)): actual = mc.fci('IN') assert actual.index.equals(idx) assert all(actual.values == data['fci']) mock = replace('gs_quant.timeseries.measures.Dataset.get_data', Mock()) mock.return_value = df with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 3)): actual = mc.fci('IN', mc._FCI_MEASURE.REAL_FCI) assert actual.index.equals(idx) assert all(actual.values == data['realFCI']) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 3)): actual = mc.fci('IN', mc._FCI_MEASURE.REAL_TWI_CONTRIBUTION) assert actual.index.equals(idx) assert all(actual.values == data['realTWIContribution']) replace.restore() if __name__ == '__main__': pytest.main(args=[__file__]) ================================================ FILE: gs_quant/test/timeseries/test_measures_factset.py ================================================ """ Copyright 2020 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from unittest.mock import Mock import pandas as pd import pytest from pandas.testing import assert_series_equal from testfixtures import Replacer import gs_quant.timeseries.measures_factset as tm from gs_quant.api.gs.data import MarketDataResponseFrame from gs_quant.data import DataContext from gs_quant.errors import MqValueError from gs_quant.markets.index import Index from gs_quant.timeseries.measures_factset import ( EstimateItem, EstimateStatistic, EstimateBasis, FiscalPeriod, FundamentalMetric, FundamentalFormat, FundamentalBasis, RatingType, ) _index = [pd.Timestamp('2021-03-30')] _test_datasets = ('TEST_DATASET',) def mock_fe_estimate_af(_cls, bbid, start, end, feItem): d = { 'date': [dt.date(2022, 1, 8), dt.date(2022, 1, 12), dt.date(2022, 1, 13)], 'isin': ['US0378331005'] * 3, 'feItem': ['EPS'] * 3, 'fePerRel': [1.0] * 3, 'adjDate': [dt.date(2020, 8, 31)] * 3, 'currency': ['USD'] * 3, 'consEndDate': [dt.date(2022, 1, 11)] * 3, 'feItemDesc': ['Earnings Per Share'] * 3, 'feFpEnd': [dt.date(2022, 9, 30)] * 3, 'feDownAf': [10.0, 3.0, 3.0], 'feHighAf': [6.353628] * 3, 'feLowAf': [5.2] * 3, 'feMeanAf': [5.749689, 5.748439, 5.756439], 'feMedianAf': [5.745] * 3, 'feNumEstAf': [40] * 3, 'feStdDevAf': [0.259250, 0.258130, 0.258687], 'feUpAf': [15.0, 5.0, 6.0], 'fsymId': ['MH33D6-R'] * 3, 'bbid': ['AAPL UW'] * 3, } df = MarketDataResponseFrame(data=d) df.dataset_id = 'FE_BASIC_CONH_AF_GLOBAL' return df def mock_fe_estimate_qf(_cls, bbid, start, end, feItem): d = { 'date': [dt.date(2022, 1, 8), dt.date(2022, 1, 12), dt.date(2022, 1, 13)], 'isin': ['US0378331005'] * 3, 'feItem': ['EPS'] * 3, 'fePerRel': [1.0] * 3, 'adjDate': [dt.date(2020, 8, 31)] * 3, 'currency': ['USD'] * 3, 'consEndDate': [dt.date(2022, 1, 11)] * 3, 'feItemDesc': ['Earnings Per Share'] * 3, 'feFpEnd': [dt.date(2022, 9, 30)] * 3, 'feDownQf': [10.0, 3.0, 3.0], 'feHighQf': [6.353628] * 3, 'feLowQf': [5.2] * 3, 'feMeanQf': [5.749689, 5.748439, 5.756439], 'feMedianQf': [5.745] * 3, 'feNumEstQf': [40] * 3, 'feStdDevQf': [0.259250, 0.258130, 0.258687], 'feUpQf': [15.0, 5.0, 6.0], 'fsymId': ['MH33D6-R'] * 3, 'bbid': ['AAPL UW'] * 3, } df = MarketDataResponseFrame(data=d) df.dataset_id = 'FE_BASIC_CONH_QF_GLOBAL' return df def mock_fe_estimate_saf(_cls, bbid, start, end, feItem): d = { 'date': [dt.date(2022, 1, 8), dt.date(2022, 1, 12), dt.date(2022, 1, 13)], 'isin': ['US0378331005'] * 3, 'feItem': ['EPS'] * 3, 'fePerRel': [1.0] * 3, 'adjDate': [dt.date(2020, 8, 31)] * 3, 'currency': ['USD'] * 3, 'consEndDate': [dt.date(2022, 1, 11)] * 3, 'feItemDesc': ['Earnings Per Share'] * 3, 'feFpEnd': [dt.date(2022, 9, 30)] * 3, 'feDownSaf': [10.0, 3.0, 3.0], 'feHighSaf': [6.353628] * 3, 'feLowSaf': [5.2] * 3, 'feMeanSaf': [5.749689, 5.748439, 5.756439], 'feMedianSaf': [5.745] * 3, 'feNumEstSaf': [40] * 3, 'feStdDevSaf': [0.259250, 0.258130, 0.258687], 'feUpSaf': [15.0, 5.0, 6.0], 'fsymId': ['MH33D6-R'] * 3, 'bbid': ['AAPL UW'] * 3, } df = MarketDataResponseFrame(data=d) df.dataset_id = 'FE_BASIC_CONH_SAF_GLOBAL' return df def mock_fe_estimate_ntm(_cls, bbid, start, end, feItem): d = { 'date': [dt.date(2022, 1, 8), dt.date(2022, 1, 9), dt.date(2022, 1, 10)], 'isin': ['US0378331005'] * 3, 'feItem': ['EPS'] * 3, 'feHighNtm': [6.353628] * 3, 'feLowNtm': [5.2] * 3, 'feMeanNtm': [5.749689, 5.748439, 5.756439], 'feMedianNtm': [5.745] * 3, 'feHighStm': [6.353628] * 3, 'feLowStm': [5.2] * 3, 'feMeanStm': [5.749689, 5.748439, 5.756439], 'feMedianStm': [5.745] * 3, 'fsymId': ['MH33D6-R'] * 3, 'bbid': ['AAPL UW'] * 3, } df = MarketDataResponseFrame(data=d) df.dataset_id = 'FE_NTM' return df def mock_fe_estimate_lt(_cls, bbid, start, end, feItem): d = { 'date': [dt.date(2022, 1, 8), dt.date(2022, 1, 12), dt.date(2022, 1, 13)], 'isin': ['US0378331005'] * 3, 'feItem': ['PRICE_TGT'] * 3, 'adjDate': [dt.date(2020, 8, 31)] * 3, 'consEndDate': [dt.date(2022, 1, 11)] * 3, 'feDownLt': [10.0, 3.0, 3.0], 'feHighLt': [6.353628] * 3, 'feLowLt': [5.2] * 3, 'feMeanLt': [5.749689, 5.748439, 5.756439], 'feMedianLt': [5.745] * 3, 'feNumEstLt': [40] * 3, 'feStdDevLt': [0.259250, 0.258130, 0.258687], 'feUpLt': [15.0, 5.0, 6.0], 'fsymId': ['MH33D6-R'] * 3, 'bbid': ['AAPL UW'] * 3, } df = MarketDataResponseFrame(data=d) df.dataset_id = 'FE_BASIC_CONH_LT_GLOBAL' return df def mock_fe_actual(_cls, bbid, start, end, feItem): d = { 'date': [dt.date(2020, 1, 8), dt.date(2021, 1, 12), dt.date(2022, 1, 13)], 'isin': ['US0378331005'] * 3, 'feItem': ['EPS'] * 3, 'adjDate': [dt.date(2020, 8, 31)] * 3, 'currency': ['USD'] * 3, 'feFpEnd': [dt.date(2022, 9, 30), dt.date(2021, 9, 30), dt.date(2020, 9, 30)], 'feValue': [15.0, 5.0, 6.0], 'fsymId': ['MH33D6-R'] * 3, 'bbid': ['AAPL UW'] * 3, } df = MarketDataResponseFrame(data=d) df.dataset_id = 'FE_BASIC_ACT_AF_GLOBAL' return df def mock_fe_estimate_empty(_cls, bbid, start, end, feItem): df = MarketDataResponseFrame() df.dataset_id = 'FE_BASIC_CONH_SAF_GLOBAL' return df def mock_factset_fundamentals_empty(_cls, bbid, start, end): df = MarketDataResponseFrame() df.dataset_id = 'FF_BASIC_R_AF_GLOBAL' return df def mock_factset_fundamentals_basic(_cls, bbid, start, end): d = { 'date': [pd.Timestamp('2024-09-30 00:00:00')], 'isin': ['US0378331005'], 'adjDate': [pd.Timestamp('2020-08-31 00:00:00')], 'currency': ['USD'], 'ffEpsBasic': [6.109], 'fsymId': ['MH33D6-R'], 'bbid': ['AAPL UW'], } df = MarketDataResponseFrame(data=d) df.dataset_id = 'FF_BASIC_AF_GLOBAL' return df def mock_factset_fundamentals_basic_derived(_cls, bbid, start, end): d = { 'date': [pd.Timestamp('2024-09-30 00:00:00')], 'isin': ['US0378331005'], 'adjDate': [pd.Timestamp('2020-08-31 00:00:00')], 'currency': ['USD'], 'ffEbitdaOper': [6.109], 'fsymId': ['MH33D6-R'], 'bbid': ['AAPL UW'], } df = MarketDataResponseFrame(data=d) df.dataset_id = 'FF_BASIC_DER_AF_GLOBAL' return df def mock_factset_fundamentals_basic_restated(_cls, bbid, start, end): d = { 'date': [pd.Timestamp('2024-09-30 00:00:00')], 'isin': ['US0378331005'], 'adjDate': [pd.Timestamp('2020-08-31 00:00:00')], 'currency': ['USD'], 'ffEpsBasic': [6.109], 'fsymId': ['MH33D6-R'], 'bbid': ['AAPL UW'], } df = MarketDataResponseFrame(data=d) df.dataset_id = 'FF_BASIC_R_AF_GLOBAL' return df def mock_factset_ratings(_cls, bbid, start, end): d = { 'date': [pd.Timestamp('2024-12-31 00:00:00')] * 3, 'isin': ['US0378331005'] * 3, 'feItem': ['REC'] * 3, 'adjDate': [dt.date(2024, 12, 31)] * 3, 'consEndDate': [dt.date(2024, 12, 31)] * 3, 'feItemDesc': ['Recommendation'] * 3, 'feFpEnd': [dt.date(2024, 12, 31)] * 3, 'feBuy': [21.0, 3.0, 3.0], 'feHold': [14.0, 3.0, 3.0], 'feNoRec': [1.0, 3.0, 3.0], 'feOver': [10.0, 3.0, 3.0], 'feSell': [4.0, 3.0, 3.0], 'feUnder': [1.0, 3.0, 3.0], 'feTotal': [10.0, 3.0, 3.0], 'feMark': [1.57] * 3, 'fsymId': ['MH33D6-R'] * 3, 'bbid': ['AAPL UW'] * 3, } df = MarketDataResponseFrame(data=d) df.dataset_id = 'FE_BASIC_CONH_REC_GLOBAL' return df mock_asset = Index( id_='MA000', asset_class='Equity', name='test', currency='USD', exchange='NASD', ) def test_factset_estimates(): replace = Replacer() bbid_mock = replace('gs_quant.markets.securities.Asset.get_identifier', Mock()) bbid_mock.return_value = 'AAPL UW' with DataContext(start=dt.date(2022, 1, 1), end=dt.date(2025, 1, 31)): replace('gs_quant.data.Dataset.get_data', mock_fe_estimate_af) actual = tm.factset_estimates( mock_asset, metric=EstimateItem.EPS, statistic=EstimateStatistic.MEDIAN, report_basis=EstimateBasis.ANN, period=FiscalPeriod(2022), ) assert_series_equal( pd.Series( [5.745] * 4, index=pd.DatetimeIndex( ['2022-01-08', '2022-01-09', '2022-01-10', '2022-01-11'], dtype='datetime64[ns]', name='date', freq=None, ), name=EstimateStatistic.MEDIAN.value, ), pd.Series(actual), ) assert actual.dataset_ids == 'FE_BASIC_CONH_AF_GLOBAL' replace('gs_quant.data.Dataset.get_data', mock_fe_estimate_qf) actual = tm.factset_estimates( mock_asset, metric=EstimateItem.EPS, statistic=EstimateStatistic.MEDIAN, report_basis=EstimateBasis.QTR, period=FiscalPeriod(2022, 3), ) assert_series_equal( pd.Series( [5.745] * 4, index=pd.DatetimeIndex( ['2022-01-08', '2022-01-09', '2022-01-10', '2022-01-11'], dtype='datetime64[ns]', name='date', freq=None, ), name=EstimateStatistic.MEDIAN.value, ), pd.Series(actual), ) assert actual.dataset_ids == 'FE_BASIC_CONH_QF_GLOBAL' with pytest.raises(MqValueError): tm.factset_estimates( mock_asset, metric=EstimateItem.EPS, statistic=EstimateStatistic.MEDIAN, report_basis=EstimateBasis.QTR, period=FiscalPeriod(2022, 1), ) with pytest.raises(MqValueError): tm.factset_estimates( mock_asset, metric=EstimateItem.EPS, statistic=EstimateStatistic.MEDIAN, report_basis=EstimateBasis.QTR, period=FiscalPeriod(2022, -1), ) replace('gs_quant.data.Dataset.get_data', mock_fe_estimate_saf) actual = tm.factset_estimates( mock_asset, metric=EstimateItem.EPS, statistic=EstimateStatistic.MEDIAN, report_basis=EstimateBasis.SEMI, period=FiscalPeriod(2022, 2), ) assert_series_equal( pd.Series( [5.745] * 4, index=pd.DatetimeIndex( ['2022-01-08', '2022-01-09', '2022-01-10', '2022-01-11'], dtype='datetime64[ns]', name='date', freq=None, ), name=EstimateStatistic.MEDIAN.value, ), pd.Series(actual), ) assert actual.dataset_ids == 'FE_BASIC_CONH_SAF_GLOBAL' replace('gs_quant.data.Dataset.get_data', mock_fe_estimate_ntm) actual = tm.factset_estimates( mock_asset, metric=EstimateItem.EPS, statistic=EstimateStatistic.MEDIAN, report_basis=EstimateBasis.NTM ) assert_series_equal( pd.Series( [5.745] * 3, index=pd.DatetimeIndex( ['2022-01-08', '2022-01-09', '2022-01-10'], dtype='datetime64[ns]', name='date', freq=None ), name=EstimateStatistic.MEDIAN.value, ), pd.Series(actual), ) assert actual.dataset_ids == 'FE_NTM' actual = tm.factset_estimates( mock_asset, metric=EstimateItem.EPS, statistic=EstimateStatistic.MEDIAN, report_basis=EstimateBasis.STM ) assert_series_equal( pd.Series( [5.745] * 3, index=pd.DatetimeIndex( ['2022-01-08', '2022-01-09', '2022-01-10'], dtype='datetime64[ns]', name='date', freq=None ), name=EstimateStatistic.MEDIAN.value, ), pd.Series(actual), ) assert actual.dataset_ids == 'FE_NTM' replace('gs_quant.data.Dataset.get_data', mock_fe_estimate_lt) actual = tm.factset_estimates(mock_asset, metric=EstimateItem.PRICE_TGT, statistic=EstimateStatistic.MEDIAN) assert_series_equal( pd.Series( [5.745] * 4, index=pd.DatetimeIndex( ['2022-01-08', '2022-01-09', '2022-01-10', '2022-01-11'], dtype='datetime64[ns]', name='date', freq=None, ), name=EstimateStatistic.MEDIAN.value, ), pd.Series(actual), ) assert actual.dataset_ids == 'FE_BASIC_CONH_LT_GLOBAL' replace('gs_quant.data.Dataset.get_data', mock_fe_actual) actual = tm.factset_estimates( mock_asset, metric=EstimateItem.EPS, statistic=EstimateStatistic.ACTUAL, report_basis=EstimateBasis.ANN ) assert_series_equal( pd.Series( [15.0], index=pd.DatetimeIndex(['2022-09-30'], dtype='datetime64[ns]', name='date', freq=None), name=EstimateStatistic.ACTUAL.value, ), pd.Series(actual), ) assert actual.dataset_ids == 'FE_BASIC_ACT_AF_GLOBAL' with pytest.raises(MqValueError): tm.factset_estimates( mock_asset, metric=EstimateItem.EPS, statistic=EstimateStatistic.MEDIAN, report_basis=EstimateBasis.SEMI, period=FiscalPeriod(2022), ) with pytest.raises(MqValueError): tm.factset_estimates( mock_asset, metric=EstimateItem.EPS, statistic=EstimateStatistic.MEDIAN, report_basis=EstimateBasis.SEMI, period=FiscalPeriod(2022, -1), ) replace('gs_quant.data.Dataset.get_data', None) with pytest.raises(MqValueError): tm.factset_estimates( mock_asset, metric=EstimateItem.EPS, statistic=EstimateStatistic.MEDIAN, report_basis=EstimateBasis.SEMI, period=FiscalPeriod(2022, 1), ) replace('gs_quant.data.Dataset.get_data', mock_fe_estimate_af) actual = tm.factset_estimates( mock_asset, metric=EstimateItem.EPS, statistic=EstimateStatistic.MEDIAN, report_basis=EstimateBasis.ANN, period=1, ) assert_series_equal( pd.Series( [5.745] * 4, index=pd.DatetimeIndex( ['2022-01-08', '2022-01-09', '2022-01-10', '2022-01-11'], dtype='datetime64[ns]', name='date', freq=None, ), name=EstimateStatistic.MEDIAN.value, ), pd.Series(actual), ) assert actual.dataset_ids == 'FE_BASIC_CONH_AF_GLOBAL' # Get quarterly data without specifying period replace('gs_quant.data.Dataset.get_data', mock_fe_estimate_qf) with pytest.raises(MqValueError): tm.factset_estimates( mock_asset, metric=EstimateItem.EPS, statistic=EstimateStatistic.MEDIAN, report_basis=EstimateBasis.QTR, period=FiscalPeriod(2022), ) # Invalid estimate basis with pytest.raises(MqValueError): tm.factset_estimates( mock_asset, metric=EstimateItem.EPS, statistic=EstimateStatistic.MEDIAN, report_basis='INV', period=FiscalPeriod(2022, 1), ) # Invalid report basis for actuals with pytest.raises(MqValueError): tm.factset_estimates( mock_asset, metric=EstimateItem.EPS, statistic=EstimateStatistic.ACTUAL, report_basis=EstimateBasis.NTM, ) # Invalid metric for actuals with pytest.raises(MqValueError): tm.factset_estimates( mock_asset, metric=EstimateItem.PRICE_TGT, statistic=EstimateStatistic.ACTUAL, report_basis=EstimateBasis.ANN, ) # Get empty data response replace('gs_quant.data.Dataset.get_data', mock_fe_estimate_empty) with pytest.raises(MqValueError): tm.factset_estimates( mock_asset, metric=EstimateItem.EPS, statistic=EstimateStatistic.MEDIAN, report_basis=EstimateBasis.SEMI, period=FiscalPeriod(2022, 1), ) replace.restore() def test_factset_fundamentals(): replace = Replacer() bbid_mock = replace('gs_quant.markets.securities.Asset.get_identifier', Mock()) bbid_mock.return_value = 'AAPL UW' with DataContext(start=dt.date(2024, 9, 30), end=dt.date(2024, 9, 30)): replace('gs_quant.data.Dataset.get_data', mock_factset_fundamentals_basic) actual = tm.factset_fundamentals( mock_asset, metric=FundamentalMetric.EPS_BASIC, report_basis=FundamentalBasis.ANN, report_format=FundamentalFormat.NON_RESTATED, ) assert_series_equal( pd.Series( [6.109], index=pd.DatetimeIndex(['2024-09-30'], dtype='datetime64[ns]', name='date', freq=None), name=FundamentalMetric.EPS_BASIC.value, ), pd.Series(actual), ) assert actual.dataset_ids == 'FF_BASIC_AF_GLOBAL' replace('gs_quant.data.Dataset.get_data', mock_factset_fundamentals_basic_derived) actual = tm.factset_fundamentals( mock_asset, metric=FundamentalMetric.EBITDA_OPER, report_basis=FundamentalBasis.ANN, report_format=FundamentalFormat.NON_RESTATED, ) assert_series_equal( pd.Series( [6.109], index=pd.DatetimeIndex(['2024-09-30'], dtype='datetime64[ns]', name='date', freq=None), name=FundamentalMetric.EBITDA_OPER.value, ), pd.Series(actual), ) assert actual.dataset_ids == 'FF_BASIC_DER_AF_GLOBAL' replace('gs_quant.data.Dataset.get_data', mock_factset_fundamentals_basic_restated) actual = tm.factset_fundamentals( mock_asset, metric=FundamentalMetric.EPS_BASIC, report_basis=FundamentalBasis.ANN, report_format=FundamentalFormat.RESTATED, ) assert_series_equal( pd.Series( [6.109], index=pd.DatetimeIndex(['2024-09-30'], dtype='datetime64[ns]', name='date', freq=None), name=FundamentalMetric.EPS_BASIC.value, ), pd.Series(actual), ) assert actual.dataset_ids == 'FF_BASIC_R_AF_GLOBAL' # Get empty data response replace('gs_quant.data.Dataset.get_data', mock_factset_fundamentals_empty) with pytest.raises(MqValueError): tm.factset_fundamentals( mock_asset, metric=FundamentalMetric.EPS_BASIC, report_basis=FundamentalBasis.ANN, report_format=FundamentalFormat.RESTATED, ) replace.restore() def test_factset_ratings(): replace = Replacer() bbid_mock = replace('gs_quant.markets.securities.Asset.get_identifier', Mock()) bbid_mock.return_value = 'AAPL UW' with DataContext(start=dt.date(2024, 9, 30), end=dt.date(2024, 9, 30)): replace('gs_quant.data.Dataset.get_data', mock_factset_ratings) actual = tm.factset_ratings(mock_asset, rating_type=RatingType.BUY) assert_series_equal( pd.Series( [21.0, 3.0, 3.0], index=pd.DatetimeIndex([dt.date(2024, 12, 31)] * 3, dtype='datetime64[ns]', name='date', freq=None), name=RatingType.BUY.value, ), pd.Series(actual), ) assert actual.dataset_ids == 'FE_BASIC_CONH_REC_GLOBAL' replace.restore() def test_fiscal_period(): fp = FiscalPeriod.from_dict({'y': 2022, 'p': 1}) assert fp.y == 2022 assert fp.p == 1 assert fp.as_dict() == {'y': 2022, 'p': 1} if __name__ == '__main__': pytest.main(args=["test_measures_factset.py"]) ================================================ FILE: gs_quant/test/timeseries/test_measures_fx_vol.py ================================================ """ Copyright 2020 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import pandas as pd import pytest from pandas.testing import assert_series_equal from testfixtures import Replacer from testfixtures.mock import Mock import gs_quant.timeseries import gs_quant.timeseries.measures as tm_rates import gs_quant.timeseries.measures_fx_vol as tm_fxo import gs_quant.timeseries.measures_xccy as tm_xccy from gs_quant.api.gs.assets import GsAsset from gs_quant.api.gs.data import GsDataApi, MarketDataResponseFrame, QueryType from gs_quant.common import PricingLocation from gs_quant.data import DataContext from gs_quant.errors import MqError, MqValueError from gs_quant.markets import PricingContext from gs_quant.markets.securities import Bond, Cross, Currency from gs_quant.session import GsSession, Environment from gs_quant.test.timeseries.utils import mock_request from gs_quant.timeseries import Currency as CurrencyEnum, SecurityMaster, measures as tm from gs_quant.timeseries.measures_fx_vol import _currencypair_to_tdapi_fxo_asset, _currencypair_to_tdapi_fxfwd_asset from gs_quant.timeseries.measures_helper import VolReference _index = [pd.Timestamp('2021-03-30')] _test_datasets = ('TEST_DATASET',) def test_currencypair_to_tdapi_fxfwd_asset(): mock_eur = Cross('MA8RY265Q34P7TWZ', 'EURUSD') replace = Replacer() xrefs = replace('gs_quant.timeseries.measures_fx_vol._get_tdapi_fxo_assets', Mock()) xrefs.return_value = 'MA8RY265Q34P7TWZ' bbid_mock = replace('gs_quant.timeseries.measures_fx_vol.Asset.get_identifier', Mock()) bbid_mock.return_value = {'EURUSD'} assert _currencypair_to_tdapi_fxfwd_asset(mock_eur) == "MA8RY265Q34P7TWZ" replace.restore() def test_currencypair_to_tdapi_fxo_asset(mocker): replace = Replacer() mocker.patch.object( GsSession.__class__, 'current', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', side_effect=mock_request) mocker.patch.object(SecurityMaster, 'get_asset', side_effect=mock_request) bbid_mock = replace('gs_quant.timeseries.measures_fx_vol.Asset.get_identifier', Mock()) with PricingContext(dt.date.today()): cur = [ {"currency_assetId": "MAK1FHKH5P5GJSHH", "currency": "USDJPY", "id": "MAQ7YRZ4P94M9N9C"}, {"currency_assetId": "MA66CZBQJST05XKG", "currency": "GBPUSD", "id": "MAEHA6WVHJ2S3JY9"}, {"currency_assetId": "MAJNQPFGN1EBDHAE", "currency": "EURUSD", "id": "MAT1J37C9ZPMANFP"}, ] for c in cur: print(c) asset = Currency(c.get("currency_assetId"), c.get("currency")) bbid_mock.return_value = c.get("currency") mqid = _currencypair_to_tdapi_fxo_asset(asset) assert mqid == c.get("id") bbid_mock.return_value = None assert _currencypair_to_tdapi_fxo_asset(asset) == c.get("currency_assetId") replace.restore() def test_get_fxo_defaults(): result_dict = dict(under="AUD", over="JPY", expirationTime="NYC", premiumPaymentDate="Fwd Settle") defaults = tm_fxo._get_fxo_defaults("AUDJPY") assert result_dict == defaults result_dict = dict(under="CAD", over="JPY", expirationTime="NYC", premiumPaymentDate="Fwd Settle") defaults = tm_fxo._get_fxo_defaults("CADJPY") assert result_dict == defaults result_dict = dict(under="EUR", over="NOK", expirationTime="NYC", premiumPaymentDate="Fwd Settle") defaults = tm_fxo._get_fxo_defaults("EURNOK") assert result_dict == defaults result_dict = dict(under="GBP", over="USD", expirationTime="NYC", premiumPaymentDate="Fwd Settle") defaults = tm_fxo._get_fxo_defaults("GBPUSD") assert result_dict == defaults result_dict = dict(under="EUR", over="NZD", expirationTime="NYC", premiumPaymentDate="Fwd Settle") defaults = tm_fxo._get_fxo_defaults("EURNZD") assert result_dict == defaults def test_get_fxo_csa_terms(): assert dict(csaTerms='USD-1') == tm_fxo._get_fx_csa_terms() def test_check_valid_indices(): valid_indices = ['LIBOR'] for index in valid_indices: expected_rate_option_type = tm_xccy.CrossCurrencyRateOptionType[index] actual_rate_option_type = tm_xccy._check_crosscurrency_rateoption_type(CurrencyEnum.GBP, index) assert expected_rate_option_type == actual_rate_option_type invalid_indices = ['LIBORED', 'TestRateOption'] for index in invalid_indices: with pytest.raises(MqError): tm_xccy._check_crosscurrency_rateoption_type(CurrencyEnum.GBP, index) def test_cross_stored_direction_for_fx_vol(): replace = Replacer() mock_gbp = Cross('MA26QSMPX9990G66', 'GBPUSD') xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'GBPUSD' assert "MAEHA6WVHJ2S3JY9" == tm_fxo.cross_stored_direction_for_fx_vol(mock_gbp) replace.restore() mock_gbp_reversed = Cross('MA26QSMPX9990G67', 'USDGBP') xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.side_effect = ['USDGBP', 'GBPUSD'] assets = replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()) assets.return_value = mock_gbp assert "MAEHA6WVHJ2S3JY9" == tm_fxo.cross_stored_direction_for_fx_vol(mock_gbp_reversed) replace.restore() xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.side_effect = [mock_gbp, 'GBPUSD'] assets = replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()) assets.return_value = mock_gbp assert "MAEHA6WVHJ2S3JY9" == tm_fxo.cross_stored_direction_for_fx_vol(mock_gbp) replace.restore() def test_get_tdapi_fxo_assets(): mock_asset_1 = GsAsset(asset_class='FX', id='MAW8SAXPSKYA94E2', type_='Option', name='FX Forward Test_asset_1') mock_asset_2 = GsAsset(asset_class='FX', id='MATDD783JM1C2GGD', type_='Option', name='Test_asset') replace = Replacer() assets = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) assets.return_value = [mock_asset_1] kwargs = dict( asset_parameters_expiration_date='5y', asset_parameters_call_currency='USD', asset_parameters_put_currency='EUR' ) assert 'MAW8SAXPSKYA94E2' == tm_fxo._get_tdapi_fxo_assets(**kwargs) replace.restore() # Test case: Multiple assets, one with name starting with "FX Forward" assets = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) assets.return_value = [mock_asset_1, mock_asset_2] kwargs = dict( asset_parameters_expiration_date='5y', asset_parameters_call_currency='USD', asset_parameters_put_currency='EUR', name_prefix='FX Forward', ) assert 'MAW8SAXPSKYA94E2' == tm_fxo._get_tdapi_fxo_assets(**kwargs) replace.restore() # Test case: Multiple assets, none with name starting with "FX Forward" mock_asset_1.name = "Test_asset_1" assets = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) assets.return_value = [mock_asset_1, mock_asset_2] kwargs = dict( asset_parameters_expiration_date='5y', asset_parameters_call_currency='USD', asset_parameters_put_currency='EUR' ) with pytest.raises(MqValueError): tm_fxo._get_tdapi_fxo_assets(**kwargs) replace.restore() assets = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) assets.return_value = [] kwargs = dict( asset_parameters_expiration_date='5y', asset_parameters_call_currency='USD', asset_parameters_put_currency='EUR' ) with pytest.raises(MqValueError): tm_fxo._get_tdapi_fxo_assets(**kwargs) replace.restore() assets = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) assets.return_value = [mock_asset_1, mock_asset_2] kwargs = dict() assert ['MAW8SAXPSKYA94E2', 'MATDD783JM1C2GGD'] == tm_xccy._get_tdapi_crosscurrency_rates_assets(**kwargs) replace.restore() # test case will test matching sofr maturity with libor leg and flipping legs to get right asset kwargs = dict( Asset_class='FX', type='Option', asset_parameters_call_currency='USD', asset_parameters_put_currency='EUR', asset_parameters_expiration_date='1m', asset_parameters_expiration_time='NYC', asset_parameters_option_type='Put', asset_parameters_premium_payment_date='Fwd Settle', asset_parameters_strike_price_relative='10d', pricing_location='NYC', ) assets = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) assets.return_value = [mock_asset_1] assert 'MAW8SAXPSKYA94E2' == tm_fxo._get_tdapi_fxo_assets(**kwargs) replace.restore() def mock_curr(_cls, _q): d = {'impliedVolatility': [1, 2, 3], 'fwdPoints': [4, 5, 6], 'forwardPoint': [7, 8, 9]} df = MarketDataResponseFrame(data=d, index=_index * 3) df.dataset_ids = _test_datasets return df def mock_fx_spot_carry_3m(*args, **kwargs): """Mock Dataset.get_data for 3m tenor with fwdPoints column""" d = pd.DataFrame( { 'spot': [1.18250, 1.18566, 1.18511], 'fwdPoints': [0.00234, 0.00234, 0.00235], 'tenor': ['3m', '3m', '3m'], 'date': [pd.Timestamp(2020, 9, 2), pd.Timestamp(2020, 9, 3), pd.Timestamp(2020, 9, 4)], 'settlementDate': [pd.Timestamp('2020-12-04'), pd.Timestamp('2020-12-08'), pd.Timestamp('2020-12-08')], } ) d = d.set_index('date') df = MarketDataResponseFrame(d) df.dataset_ids = _test_datasets return df def mock_fx_spot_carry_2y(*args, **kwargs): """Mock Dataset.get_data for 2y tenor with fwdPoints column""" d = pd.DataFrame( { 'spot': [1.18250, 1.18566, 1.18511], 'fwdPoints': [0.02009, 0.02015, 0.02064], 'tenor': ['2y', '2y', '2y'], 'date': [pd.Timestamp(2020, 9, 2), pd.Timestamp(2020, 9, 3), pd.Timestamp(2020, 9, 4)], 'settlementDate': [pd.Timestamp('2020-12-04'), pd.Timestamp('2020-12-08'), pd.Timestamp('2020-12-08')], } ) d = d.set_index('date') df = MarketDataResponseFrame(d) df.dataset_ids = _test_datasets return df def test_fx_vol_measure(mocker): replace = Replacer() args = dict( expiry_tenor='1m', strike='ATMF', option_type='Put', expiration_location=None, location=None, premium_payment_date=None, ) mock_gbp = Cross('MA26QSMPX9990G66', 'GBPUSD') args['asset'] = mock_gbp xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'GBPUSD' args['expiry_tenor'] = '1yr' with pytest.raises(MqValueError): tm_fxo.implied_volatility_new(**args) args['expiry_tenor'] = '1m' args['real_time'] = True with pytest.raises(NotImplementedError): tm_fxo.implied_volatility_new(**args) args['real_time'] = False args['asset'] = Cross('MA666', 'USDAED') xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'AEDUSD' with pytest.raises(NotImplementedError): tm_fxo.implied_volatility_new(**args) args['asset'] = mock_gbp args['asset'] = Bond('MA667', 'TEST') with pytest.raises(NotImplementedError): tm_fxo.implied_volatility_new(**args) args['asset'] = mock_gbp args['asset'] = Cross('MA667', 'GBPUSD') xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'GBPUSD' identifiers = replace('gs_quant.timeseries.measures_fx_vol._get_tdapi_fxo_assets', Mock()) identifiers.return_value = {'MA7F5P92330NGKAR'} mocker.patch.object(GsDataApi, 'get_market_data', return_value=mock_curr(None, None)) actual = tm_fxo.implied_volatility_new(**args) expected = tm.ExtendedSeries([1, 2, 3], index=_index * 3, name='impliedVolatility') expected.dataset_ids = _test_datasets assert_series_equal(expected, actual) assert actual.dataset_ids == _test_datasets args['asset'] = Cross('MA667', 'USDCAD') args['location'] = PricingLocation.LDN args['premium_payment_date'] = 'Fwd Settle' args['expiration_location'] = 'NYC' xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'USDCAD' identifiers = replace('gs_quant.timeseries.measures_fx_vol._get_tdapi_fxo_assets', Mock()) identifiers.return_value = {'MA7F5P92330NGKAR'} mocker.patch.object(GsDataApi, 'get_market_data', return_value=mock_curr(None, None)) actual = tm_fxo.implied_volatility_new(**args) expected = tm.ExtendedSeries([1, 2, 3], index=_index * 3, name='impliedVolatility') expected.dataset_ids = _test_datasets assert_series_equal(expected, actual) assert actual.dataset_ids == _test_datasets args['option_type'] = 'Call' xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'GBPUSD' identifiers = replace('gs_quant.timeseries.measures_fx_vol._get_tdapi_fxo_assets', Mock()) identifiers.return_value = {'MA7F5P92330NGKAR'} mocker.patch.object(GsDataApi, 'get_market_data', return_value=mock_curr(None, None)) actual = tm_fxo.implied_volatility_new(**args) expected = tm.ExtendedSeries([1, 2, 3], index=_index * 3, name='impliedVolatility') expected.dataset_ids = _test_datasets assert_series_equal(expected, actual) assert actual.dataset_ids == _test_datasets replace.restore() def test_fwd_points(mocker): replace = Replacer() args = dict(settlement_date="6m", location='LDN') mock_eur = Cross('MAGZMXVM0J282ZTR', 'EURUSD') args['asset'] = mock_eur xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'EURUSD' args['settlement_date'] = '1yr' with pytest.raises(MqValueError): tm_fxo.fwd_points(**args) args['settlement_date'] = '6m' args['real_time'] = True xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'EURUSD' identifiers = replace('gs_quant.timeseries.measures_fx_vol._get_tdapi_fxo_assets', Mock()) identifiers.return_value = {'MAGZMXVM0J282ZTR'} mocker.patch.object(GsDataApi, 'get_market_data', return_value=mock_curr(None, None)) actual = tm_fxo.fwd_points(**args) expected = tm.ExtendedSeries([7, 8, 9], index=_index * 3, name='forwardPoint') expected.dataset_ids = _test_datasets assert_series_equal(expected, actual) assert actual.dataset_ids == _test_datasets args['real_time'] = False args['asset'] = Cross('MAGZMXVM0J282ZTR', 'EURUSD') xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'EURUSD' identifiers = replace('gs_quant.timeseries.measures_fx_vol._get_tdapi_fxo_assets', Mock()) identifiers.return_value = {'MAGZMXVM0J282ZTR'} mocker.patch.object(GsDataApi, 'get_market_data', return_value=mock_curr(None, None)) actual = tm_fxo.fwd_points(**args) expected = tm.ExtendedSeries([4, 5, 6], index=_index * 3, name='fwdPoints') expected.dataset_ids = _test_datasets assert_series_equal(expected, actual) assert actual.dataset_ids == _test_datasets args['location'] = None xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'EURUSD' identifiers = replace('gs_quant.timeseries.measures_fx_vol._get_tdapi_fxo_assets', Mock()) identifiers.return_value = {'MAGZMXVM0J282ZTR'} mocker.patch.object(GsDataApi, 'get_market_data', return_value=mock_curr(None, None)) actual = tm_fxo.fwd_points(**args) expected = tm.ExtendedSeries([4, 5, 6], index=_index * 3, name='fwdPoints') expected.dataset_ids = _test_datasets assert_series_equal(expected, actual) assert actual.dataset_ids == _test_datasets replace.restore() def mock_df(): d = { 'strikeVol': [5, 1, 2], } df = MarketDataResponseFrame(data=d, index=_index * 3) df.dataset_ids = "FX_VOLATILITY_SWAP" return df def test_vol_swap_strike_raises_exception(): with pytest.raises(NotImplementedError): mock_asset_1 = GsAsset(asset_class='FX', id='MAW8SAXPSKYA94E2', type_='Option', name='Test_asset') tm_fxo.vol_swap_strike(mock_asset_1, "none", "none", location=PricingLocation.LDN, real_time=True) def test_vol_swap_strike_unsupported_cross(): with pytest.raises(NotImplementedError): replace = Replacer() mock_asset_1 = Cross('MA667', 'USDPLN') xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'USDPLN' tm_fxo.vol_swap_strike(mock_asset_1, "none", "none", location=PricingLocation.LDN, real_time=False) replace.restore() def test_vol_swap_strike_matches_multiple_assets(): with pytest.raises(MqValueError): replace = Replacer() base = Cross('MA667', 'EURUSD') xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'EURUSD' mock_asset_1 = GsAsset(asset_class='FX', id='MA123', type_='VolatilitySwap', name='Test_asset') assets = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) assets.return_value = [mock_asset_1, mock_asset_1] tm_fxo.vol_swap_strike(base, None, location=PricingLocation.LDN, real_time=False) replace.restore() def test_vol_swap_strike_matches_no_assets(): with pytest.raises(MqValueError): replace = Replacer() base = Cross('MA667', 'EURUSD') xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'EURUSD' assets = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) assets.return_value = [] tm_fxo.vol_swap_strike(base, None, location=PricingLocation.LDN, real_time=False) replace.restore() def test_vol_swap_strike_matches_no_assets_when_expiry_tenor_is_not_none(): with pytest.raises(MqValueError): replace = Replacer() base = Cross('MA667', 'EURUSD') xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'EURUSD' mock_asset_1 = GsAsset( asset_class='FX', id='MA123', type_='VolatilitySwap', name='Test_asset', parameters={"lastFixingDate": "1y"} ) mock_asset_2 = GsAsset( asset_class='FX', id='MA123', type_='VolatilitySwap', name='Test_asset', parameters={"lastFixingDate": "1y"} ) assets = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) assets.return_value = [mock_asset_1, mock_asset_2] tm_fxo.vol_swap_strike(base, "10m", location=PricingLocation.LDN, real_time=False) replace.restore() def test_currencypair_to_tdapi_fx_vol_swap_asset(): replace = Replacer() base = Cross('MA667', 'EURUSD') xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'EURUSD' asset = tm_fxo._currencypair_to_tdapi_fx_vol_swap_asset(base) assert asset == "MA66A4X4PRTC3N7B" replace.restore() def test_vol_swap_strike(): replace = Replacer() base = Cross('MA667', 'EURUSD') xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'EURUSD' mock_asset_1 = GsAsset( asset_class='FX', id='MA123', type_='VolatilitySwap', name='Test_asset', parameters={"lastFixingDate": "1y"} ) mock_asset_2 = GsAsset( asset_class='FX', id='MA123', type_='VolatilitySwap', name='Test_asset', parameters={"lastFixingDate": "2y"} ) assets = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) assets.return_value = [mock_asset_1, mock_asset_2] mock_data = replace('gs_quant.timeseries.measures.GsDataApi.get_market_data', Mock()) mock_data.return_value = mock_df() actual = tm_fxo.vol_swap_strike(base, "1y", location=PricingLocation.LDN, real_time=False) assert_series_equal(tm_rates._extract_series_from_df(mock_df(), QueryType.STRIKE_VOL), actual) actual = tm_fxo.vol_swap_strike(base, "1y", None, real_time=False) assert_series_equal(tm_rates._extract_series_from_df(mock_df(), QueryType.STRIKE_VOL), actual) replace.restore() def test_implied_volatility_fxvol(mocker): replace = Replacer() args = dict(tenor="1y", location=None) mock_eur = Cross('MAGZMXVM0J282ZTR', 'EURUSD') args['asset'] = mock_eur xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'EURUSD' xrefs = replace('gs_quant.timeseries.measures_fx_vol._cross_stored_direction_helper', Mock()) xrefs.return_value = 'GBPUSD' assets = replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()) assets.return_value = mock_eur preprocess_impl_vol = replace('gs_quant.timeseries.measures_fx_vol._preprocess_implied_vol_strikes_fx', Mock()) replace('gs_quant.timeseries.measures_fx_vol._get_tdapi_fxo_assets', Mock()) mocker.patch.object(GsDataApi, 'get_market_data', return_value=mock_curr(None, None)) expected = tm.ExtendedSeries([1, 2, 3], index=_index * 3, name='impliedVolatility') args['strike_reference'] = VolReference.DELTA_CALL args['relative_strike'] = 50 preprocess_impl_vol.return_value = ['abc', 50] with pytest.raises(MqValueError): tm_fxo.implied_volatility_fxvol(**args) args['strike_reference'] = VolReference.DELTA_CALL args['relative_strike'] = 50 preprocess_impl_vol.return_value = ['delta', 50] actual = tm_fxo.implied_volatility_fxvol(**args) assert_series_equal(expected, actual) args['strike_reference'] = VolReference.DELTA_PUT args['relative_strike'] = 25 preprocess_impl_vol.return_value = ['delta', -25] actual = tm_fxo.implied_volatility_fxvol(**args) assert_series_equal(expected, actual) args['strike_reference'] = VolReference.DELTA_CALL args['relative_strike'] = 0 preprocess_impl_vol.return_value = ['delta', 0] actual = tm_fxo.implied_volatility_fxvol(**args) assert_series_equal(expected, actual) args['strike_reference'] = VolReference.SPOT args['relative_strike'] = 100 preprocess_impl_vol.return_value = ['spot', 100] actual = tm_fxo.implied_volatility_fxvol(**args) assert_series_equal(expected, actual) args['strike_reference'] = VolReference.FORWARD args['relative_strike'] = 100 preprocess_impl_vol.return_value = ['forward', 100] actual = tm_fxo.implied_volatility_fxvol(**args) assert_series_equal(expected, actual) args['strike_reference'] = VolReference.NORMALIZED args['relative_strike'] = 100 preprocess_impl_vol = replace('gs_quant.timeseries.measures_fx_vol._preprocess_implied_vol_strikes_fx', Mock()) preprocess_impl_vol.return_value = ['normalized', 0] with pytest.raises(MqValueError): tm_fxo.implied_volatility_fxvol(**args) xrefs = replace('gs_quant.timeseries.measures_fx_vol._cross_stored_direction_helper', Mock()) xrefs.return_value = 'EURUSD' args['strike_reference'] = VolReference.DELTA_CALL args['relative_strike'] = 50 preprocess_impl_vol.return_value = ['delta', 50] actual = tm_fxo.implied_volatility_fxvol(**args) assert_series_equal(expected, actual) xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = None with pytest.raises(MqValueError): tm_fxo.implied_volatility_fxvol(**args) replace.restore() if __name__ == '__main__': pytest.main(args=["test_measures_fx_vol.py"]) def test_spot_carry(mocker): replace = Replacer() mock = Cross('MAA0NE9QX2ABETG6', 'USD/EUR') xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'USDEUR' identifiers = replace('gs_quant.timeseries.measures_fx_vol._get_tdapi_fxo_assets', Mock()) identifiers.return_value = 'MAA0NE9QX2ABETG6' mocker.patch.object(GsDataApi, 'get_market_data', return_value=mock_curr(None, None)) mock_tdapi_asset_func = replace('gs_quant.timeseries.measures_fx_vol._currencypair_to_tdapi_fxfwd_asset', Mock()) mock_tdapi_asset_func.return_value = mock.get_marquee_id() df = pd.DataFrame( { '3m': [-0.001978858350951374, -0.0019735843327766817, -0.00198293829264794], '2y': [-0.016989429175475686, -0.016994753976688093, -0.017416104834150414], '3m_ann': [-0.007660096842392416, -0.007400941247912555, -0.0075142924774027195], 'date': [pd.Timestamp('2020-09-02'), pd.Timestamp('2020-09-03'), pd.Timestamp('2020-09-04')], 'settlementDate': [pd.Timestamp('2020-12-04'), pd.Timestamp('2020-12-08'), pd.Timestamp('2020-12-08')], } ) df = df.set_index('date') with DataContext(dt.date(2020, 9, 2), dt.date(2020, 9, 4)): replace('gs_quant.data.dataset.Dataset.get_data', mock_fx_spot_carry_3m) actual_3m = gs_quant.timeseries.measures_fx_vol.spot_carry(mock, '3m') assert_series_equal(df['3m'], pd.Series(actual_3m, name='3m')) actual_3m_ann = gs_quant.timeseries.measures_fx_vol.spot_carry(mock, '3m', tm.FXSpotCarry.ANNUALIZED) assert_series_equal(df['3m_ann'], pd.Series(actual_3m_ann, name='3m_ann')) with pytest.raises(MqValueError): gs_quant.timeseries.measures_fx_vol.spot_carry(mock, '13m') replace('gs_quant.data.dataset.Dataset.get_data', mock_fx_spot_carry_2y) actual_2y = gs_quant.timeseries.measures_fx_vol.spot_carry(mock, '2y') assert_series_equal(df['2y'], pd.Series(actual_2y, name='2y')) with pytest.raises(NotImplementedError): tm_fxo.spot_carry(mock, '3m', real_time=True) mocker.patch.object(GsDataApi, 'get_market_data', return_value=MarketDataResponseFrame()) with DataContext(dt.date(2020, 9, 2), dt.date(2020, 9, 4)): result = tm_fxo.spot_carry(mock, '3m') assert result.empty assert isinstance(result, pd.Series) def mock_empty_dataset(*args, **kwargs): return pd.DataFrame() mocker.patch.object(GsDataApi, 'get_market_data', return_value=mock_curr(None, None)) with DataContext(dt.date(2020, 9, 2), dt.date(2020, 9, 4)): replace('gs_quant.data.dataset.Dataset.get_data', mock_empty_dataset) result = tm_fxo.spot_carry(mock, '3m') assert result.empty assert isinstance(result, pd.Series) replace.restore() ================================================ FILE: gs_quant/test/timeseries/test_measures_inflation.py ================================================ """ Copyright 2020 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import pandas as pd import pytest from pandas._libs.tslibs.offsets import CustomBusinessDay from pandas.testing import assert_series_equal from testfixtures import Replacer from testfixtures.mock import Mock import gs_quant.timeseries.measures_inflation as tm from gs_quant.api.gs.assets import GsAsset from gs_quant.api.gs.data import GsDataApi, MarketDataResponseFrame from gs_quant.common import PricingLocation, Currency as CurrEnum from gs_quant.data import DataContext from gs_quant.errors import MqError, MqValueError from gs_quant.markets import PricingContext from gs_quant.markets.securities import Currency from gs_quant.session import GsSession, Environment from gs_quant.test.timeseries.utils import mock_request from gs_quant.timeseries import CurrencyEnum, SecurityMaster from gs_quant.timeseries.measures_inflation import ( _currency_to_tdapi_inflation_swap_rate_asset, INFLATION_RATES_DEFAULTS, TdapiInflationRatesDefaultsProvider, ) from gs_quant.timeseries.measures_rates import _ClearingHouse _index = [pd.Timestamp('2021-03-30')] _test_datasets = ('TEST_DATASET',) def test_get_floating_rate_option_for_benchmark_retuns_rate(): provider = TdapiInflationRatesDefaultsProvider(INFLATION_RATES_DEFAULTS) value = provider.get_index_for_benchmark(CurrencyEnum.GBP, "UKRPI") assert value == "CPI-UKRPI" def test_get_floating_rate_option_for_benchmark_retuns_rate_usd(): provider = TdapiInflationRatesDefaultsProvider(INFLATION_RATES_DEFAULTS) value = provider.get_index_for_benchmark(CurrencyEnum.USD, "CPURNSA") assert value == "CPI-CPURNSA" def test_currency_to_tdapi_inflation_swap_rate_asset(mocker): replace = Replacer() mocker.patch.object( GsSession.__class__, 'current', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', side_effect=mock_request) mocker.patch.object(SecurityMaster, 'get_asset', side_effect=mock_request) bbid_mock = replace('gs_quant.timeseries.measures_inflation.Asset.get_identifier', Mock()) with PricingContext(dt.date.today()): cur = [ {"currency_assetId": "MAK1FHKH5P5GJSHH", "currency": "JPY", "inflation_id": "MA1CENMCA88VXJ28"}, {"currency_assetId": "MA66CZBQJST05XKG", "currency": "GBP", "inflation_id": "MAW75DV9777630QN"}, {"currency_assetId": "MAJNQPFGN1EBDHAE", "currency": "EUR", "inflation_id": "MAJTD8XDA8EJZYRG"}, {"currency_assetId": "MAZ7RWC904JYHYPS", "currency": "USD", "inflation_id": "MA4016GCT3MDRYVY"}, ] for c in cur: print(c) asset = Currency(c.get("currency_assetId"), c.get("currency")) bbid_mock.return_value = c.get("currency") mqid = _currency_to_tdapi_inflation_swap_rate_asset(asset) assert mqid == c.get("inflation_id") bbid_mock.return_value = None assert _currency_to_tdapi_inflation_swap_rate_asset(asset) == c.get("currency_assetId") replace.restore() def test_get_inflation_swap_leg_defaults(): result_dict = dict(currency=CurrEnum.JPY, index_type='CPI-JCPNGENF', pricing_location=PricingLocation.TKO) defaults = tm._get_inflation_swap_leg_defaults(CurrEnum.JPY) assert result_dict == defaults result_dict = dict(currency=CurrEnum.USD, index_type='CPI-CPURNSA', pricing_location=PricingLocation.NYC) defaults = tm._get_inflation_swap_leg_defaults(CurrEnum.USD) assert result_dict == defaults result_dict = dict(currency=CurrEnum.EUR, index_type='CPI-CPXTEMU', pricing_location=PricingLocation.LDN) defaults = tm._get_inflation_swap_leg_defaults(CurrEnum.EUR) assert result_dict == defaults result_dict = dict(currency=CurrEnum.GBP, index_type='CPI-UKRPI', pricing_location=PricingLocation.LDN) defaults = tm._get_inflation_swap_leg_defaults(CurrEnum.GBP) assert result_dict == defaults def test_get_inflation_swap_csa_terms(): valid_ccy = ['EUR', 'GBP', 'USD'] for ccy in valid_ccy: assert dict(csaTerms=ccy + '-1') == tm._get_inflation_swap_csa_terms(ccy, tm.InflationIndexType.UKRPI.value) def test_check_valid_indices(): valid_indices = ['UKRPI'] for index in valid_indices: assert tm.InflationIndexType[index] == tm._check_inflation_index_type(CurrencyEnum.GBP, index) invalid_indices = ['UKHPI', 'TestCPI'] for index in invalid_indices: with pytest.raises(MqError): tm._check_inflation_index_type(CurrencyEnum.GBP, index) def test_get_tdapi_inflation_rates_assets(mocker): mock_asset_1 = GsAsset(asset_class='Rates', id='MA26QSMPX9990G66', type_='InflationSwap', name='Test_asset') mock_asset_2 = GsAsset(asset_class='Rates', id='MA44SBCHF192S6FR', type_='InflationSwap', name='Test_asset') replace = Replacer() assets = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) assets.return_value = [mock_asset_1] assert 'MA26QSMPX9990G66' == tm._get_tdapi_inflation_rates_assets() replace.restore() assets = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) assets.return_value = [mock_asset_1, mock_asset_2] kwargs = dict(asset_parameters_termination_date='5y', asset_parameters_effective_date='0b') with pytest.raises(MqValueError): tm._get_tdapi_inflation_rates_assets(**kwargs) replace.restore() assets = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) assets.return_value = [] kwargs = dict(asset_parameters_clearing_house='NONE', pricing_location='LDN') with pytest.raises(MqValueError): tm._get_tdapi_inflation_rates_assets(**kwargs) replace.restore() assets = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) assets.return_value = [mock_asset_1, mock_asset_2] kwargs = dict() assert ['MA26QSMPX9990G66', 'MA44SBCHF192S6FR'] == tm._get_tdapi_inflation_rates_assets(**kwargs) replace.restore() # test case will test matching sofr maturity with libor leg and flipping legs to get right asset kwargs = dict( type='InflationSwap', asset_parameters_termination_date='5y', asset_parameters_index=tm.InflationIndexType.UKRPI, asset_parameters_clearing_house='None', asset_parameters_effective_date='5y', asset_parameters_notional_currency='GBP', pricing_location='LDN', ) assets = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) assets.return_value = [mock_asset_1] assert 'MA26QSMPX9990G66' == tm._get_tdapi_inflation_rates_assets(**kwargs) replace.restore() def mock_curr(_cls, _q): d = { 'swapRate': [1, 2, 3], } df = MarketDataResponseFrame(data=d, index=_index * 3) df.dataset_ids = _test_datasets return df def test_inflation_swap_rate(mocker): replace = Replacer() args = dict(swap_tenor='5y', index_type='UKRPI', clearing_house='LCH', forward_tenor='5y', real_time=False) mock_gbp = Currency('MA26QSMPX9990G66', 'GBP') args['asset'] = mock_gbp xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'GBP' args['swap_tenor'] = '5yr' with pytest.raises(MqValueError): tm.inflation_swap_rate(**args) args['swap_tenor'] = '5y' args['forward_tenor'] = '5yr' with pytest.raises(MqValueError): tm.inflation_swap_rate(**args) args['forward_tenor'] = '5y' args['real_time'] = True with pytest.raises(NotImplementedError): tm.inflation_swap_rate(**args) args['real_time'] = False args['asset'] = Currency('MA666', 'AED') xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'AED' with pytest.raises(NotImplementedError): tm.inflation_swap_rate(**args) args['asset'] = mock_gbp xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'GBP' args['index_type'] = tm.InflationIndexType.TESTCPI with pytest.raises(MqValueError): tm.inflation_swap_rate(**args) args['index_type'] = tm.InflationIndexType.UKRPI xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'GBP' identifiers = replace('gs_quant.timeseries.measures_inflation._get_tdapi_inflation_rates_assets', Mock()) identifiers.return_value = {'MA26QSMPX9990G66'} mocker.patch.object(GsDataApi, 'get_market_data', return_value=mock_curr(None, None)) actual = tm.inflation_swap_rate(**args) expected = tm.ExtendedSeries([1, 2, 3], index=_index * 3, name='swapRate') expected.dataset_ids = _test_datasets assert_series_equal(expected, actual) assert actual.dataset_ids == _test_datasets xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'EUR' identifiers = replace('gs_quant.timeseries.measures_inflation._get_tdapi_inflation_rates_assets', Mock()) identifiers.return_value = {'MAZBW57ZPS54ET7K'} mocker.patch.object(GsDataApi, 'get_market_data', return_value=mock_curr(None, None)) args['asset'] = Currency('MAZBW57ZPS54ET7K', 'EUR') args['index_type'] = 'FRCPXTOB' args['location'] = PricingLocation.LDN actual = tm.inflation_swap_rate(**args) expected = tm.ExtendedSeries([1, 2, 3], index=_index * 3, name='swapRate') expected.dataset_ids = _test_datasets assert_series_equal(expected, actual) assert actual.dataset_ids == _test_datasets replace.restore() def test_inflation_swap_term(mocker): replace = Replacer() args = dict(forward_tenor='1y', pricing_date='0d', clearing_house=_ClearingHouse.LCH, real_time=False) class ObjectView(object): def __init__(self, d): self.__dict__ = d holidays = replace('gs_quant.datetime.GsCalendar.get', Mock(holidays=[])) holidays.return_value = ObjectView({'holidays': []}) bd_calendar = replace('gs_quant.timeseries.measures_inflation._get_custom_bd', Mock()) bd_calendar.return_value = CustomBusinessDay() pricing_date_mock = replace('gs_quant.timeseries.measures_inflation._range_from_pricing_date', Mock()) pricing_date_mock.return_value = [dt.date(2019, 1, 1), dt.date(2019, 1, 1)] mock_nok = Currency('MA891', 'PLN') xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'ACU' args['asset'] = mock_nok with pytest.raises(NotImplementedError): tm.inflation_swap_term(**args) mock_usd = Currency('MAZ7RWC904JYHYPS', 'USD') args['asset'] = mock_usd xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'USD' args['real_time'] = True with pytest.raises(NotImplementedError): tm.inflation_swap_term(**args) args['real_time'] = False args['forward_tenor'] = '5yr' with pytest.raises(MqValueError): tm.inflation_swap_term(**args) args['forward_tenor'] = '1y' bd_mock = replace('gs_quant.data.dataset.Dataset.get_data', Mock()) bd_mock.return_value = pd.DataFrame( data=dict(date="2020-04-10", exchange="NYC", description="Good Friday"), index=[pd.Timestamp('2020-04-10')] ) xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'USD' identifiers_empty = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) identifiers_empty.return_value = {} with pytest.raises(MqValueError): tm.inflation_swap_term(**args) identifiers = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) mock_asset = Currency('USD', name='USD') mock_asset.id = 'MAEMPCXQG3T716EX' mock_asset.exchange = 'OTC' identifiers.return_value = [mock_asset] d = { 'terminationTenor': ['1y', '2y', '3y', '4y'], 'swapRate': [1, 2, 3, 4], 'assetId': ['MAEMPCXQG3T716EX', 'MAFRSWPAF5QPNTP2', 'MA88BXZ3TCTXTFW1', 'MAC4KAG9B9ZAZHFT'], } bd_mock.return_value = pd.DataFrame() market_data_mock = replace('gs_quant.timeseries.measures_inflation._get_inflation_swap_data', Mock()) market_data_mock.return_value = pd.DataFrame() df = pd.DataFrame(data=d, index=_index * 4) actual = tm.inflation_swap_term(**args) assert actual.empty series_apply_mock = replace('gs_quant.timeseries.measures_inflation.pd.Series.apply', Mock()) series_apply_mock.return_value = pd.Series( [dt.date(2022, 3, 30), dt.date(2023, 3, 30), dt.date(2024, 3, 30), dt.date(2025, 3, 30)], index=df.index ) market_data_mock.return_value = df with DataContext('2019-01-01', '2026-01-01'): actual = tm.inflation_swap_term(**args) actual.dataset_ids = _test_datasets expected = tm.ExtendedSeries( [1, 2, 3, 4], index=[dt.date(2022, 3, 30), dt.date(2023, 3, 30), dt.date(2024, 3, 30), dt.date(2025, 3, 30)] ) expected.dataset_ids = _test_datasets assert_series_equal(expected, actual, check_names=False) assert actual.dataset_ids == expected.dataset_ids args['location'] = PricingLocation.NYC with DataContext('2019-01-01', '2026-01-01'): tm.inflation_swap_term(**args) holidays.return_value = ObjectView({'holidays': ['0d']}) with pytest.raises(MqValueError): tm.inflation_swap_term(**args) replace.restore() if __name__ == '__main__': pytest.main(args=["test_measures_inflation.py"]) ================================================ FILE: gs_quant/test/timeseries/test_measures_portfolios.py ================================================ """ Copyright 2020 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import copy import datetime as dt import numpy as np import pandas as pd import pytest from testfixtures import Replacer from testfixtures.mock import Mock import gs_quant.timeseries.measures_portfolios as mp from gs_quant.api.gs.assets import GsTemporalXRef from gs_quant.api.gs.data import MarketDataResponseFrame from gs_quant.data import DataCoordinate from gs_quant.data.core import DataContext from gs_quant.errors import MqValueError from gs_quant.markets.index import Index from gs_quant.markets.report import PerformanceReport, ThematicReport from gs_quant.markets.securities import Stock, Bond from gs_quant.models.risk_model import FactorRiskModel as Factor_Risk_Model from gs_quant.common import ReportParameters, XRef from gs_quant.target.reports import Report, PositionSourceType, ReportType from gs_quant.target.risk_models import RiskModel, RiskModelCoverage, RiskModelTerm, RiskModelUniverseIdentifier risk_model = RiskModel( coverage=RiskModelCoverage.Country, id_='model_id', name='Fake Risk Model', term=RiskModelTerm.Long, universe_identifier=RiskModelUniverseIdentifier.gsid, vendor='GS', version=1.0, ) factor_risk_report = Report( position_source_id='position source id', position_source_type=PositionSourceType.Portfolio, type_=ReportType.Portfolio_Factor_Risk, id_='report_id', parameters=ReportParameters(risk_model='risk_model_id'), status='new', ) asset_factor_risk_report = Report( position_source_id='position source id', position_source_type=PositionSourceType.Asset, type_=ReportType.Portfolio_Factor_Risk, id_='report_id', parameters=ReportParameters(risk_model='risk_model_id'), status='new', ) ppa_report = PerformanceReport( position_source_id='position source id', position_source_type=PositionSourceType.Portfolio, type_=ReportType.Portfolio_Performance_Analytics, id_='report_id', parameters=ReportParameters(risk_model='risk_model_id'), status='new', ) factor_data = [ { 'date': '2020-11-23', 'reportId': 'report_id', 'factor': 'Factor Name', 'factorCategory': 'CNT', 'pnl': 11.23, 'exposure': -11.23, 'proportionOfRisk': 1, }, { 'date': '2020-11-24', 'reportId': 'report_id', 'factor': 'Factor Name', 'factorCategory': 'CNT', 'pnl': 11.24, 'exposure': -11.24, 'proportionOfRisk': 2, }, { 'date': '2020-11-25', 'reportId': 'report_id', 'factor': 'Factor Name', 'factorCategory': 'CNT', 'pnl': 11.25, 'exposure': -11.25, 'proportionOfRisk': 3, }, { 'date': '2020-11-23', 'reportId': 'report_id', 'factor': 'Total', 'factorCategory': 'CNT', 'pnl': 19.23, 'exposure': -11.23, 'proportionOfRisk': 1, }, { 'date': '2020-11-24', 'reportId': 'report_id', 'factor': 'Total', 'factorCategory': 'CNT', 'pnl': 14.24, 'exposure': -11.24, 'proportionOfRisk': 2, }, { 'date': '2020-11-25', 'reportId': 'report_id', 'factor': 'Total', 'factorCategory': 'CNT', 'pnl': 21.25, 'exposure': -11.25, 'proportionOfRisk': 3, }, ] aum_data = {'2020-01-01': 1, '2020-01-02': 2, '2020-01-03': 1, '2020-01-04': 1, '2020-01-05': 1, '2020-01-06': 3} yield_data = { pd.Timestamp('2020-01-01'): 0.01, pd.Timestamp('2020-01-02'): 0.02, pd.Timestamp('2020-01-03'): 0.025, pd.Timestamp('2020-01-04'): 0.03, pd.Timestamp('2020-01-05'): 0.04, } thematic_data = [ { "date": "2021-07-12", "reportId": "PTAID", "basketId": "MA01GPR89HZF1FZ5", "region": "Asia", "grossExposure": 3.448370345015856e8, "thematicExposure": 2, "thematicBeta": 1, "updateTime": "2021-07-20T23:43:38Z", }, { "date": "2021-07-13", "reportId": "PTAID", "basketId": "MA01GPR89HZF1FZ5", "region": "Asia", "grossExposure": 3.375772519907556e8, "thematicExposure": 2, "thematicBeta": 1, "updateTime": "2021-07-20T23:43:38Z", }, { "date": "2021-07-14", "reportId": "PTAID", "basketId": "MA01GPR89HZF1FZ5", "region": "Asia", "grossExposure": 3.321189950666118e8, "thematicExposure": 2, "thematicBeta": 1, "updateTime": "2021-07-20T23:43:38Z", }, { "date": "2021-07-15", "reportId": "PTAID", "basketId": "MA01GPR89HZF1FZ5", "region": "Asia", "grossExposure": 3.274071805135091e8, "thematicExposure": 2, "thematicBeta": 1, "updateTime": "2021-07-20T23:43:38Z", }, ] aggregate_factor_data = [ { 'date': '2020-11-23', 'reportId': 'report_id', 'factor': 'Factor', 'factorCategory': 'CNT', 'pnl': 11.23, 'exposure': -11.23, 'proportionOfRisk': 1, 'dailyRisk': 1, 'annualRisk': 1, }, { 'date': '2020-11-24', 'reportId': 'report_id', 'factor': 'Factor', 'factorCategory': 'CNT', 'pnl': 11.24, 'exposure': -11.24, 'proportionOfRisk': 2, 'dailyRisk': 2, 'annualRisk': 2, }, { 'date': '2020-11-25', 'reportId': 'report_id', 'factor': 'Factor', 'factorCategory': 'CNT', 'pnl': 11.25, 'exposure': -11.25, 'proportionOfRisk': 3, 'dailyRisk': 3, 'annualRisk': 3, }, ] def mock_risk_model(): risk_model = RiskModel( coverage=RiskModelCoverage.Country, id_='model_id', name='Fake Risk Model', term=RiskModelTerm.Long, universe_identifier=RiskModelUniverseIdentifier.gsid, vendor='GS', version=1.0, ) replace = Replacer() # mock getting risk model entity() mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model', Mock()) mock.return_value = risk_model actual = Factor_Risk_Model.get(model_id='model_id') replace.restore() return actual def test_portfolio_factor_exposure(): replace = Replacer() # mock getting risk model entity() mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model', Mock()) mock.return_value = risk_model mock = replace('gs_quant.api.gs.reports.GsReportApi.get_report', Mock()) mock.return_value = factor_risk_report mock = replace('gs_quant.api.gs.reports.GsReportApi.get_reports', Mock()) mock.return_value = [factor_risk_report] # mock getting report factor data mock = replace('gs_quant.api.gs.reports.GsReportApi.get_factor_risk_report_results', Mock()) mock.return_value = factor_data # mock getting risk model dates mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model_dates', Mock()) mock.return_value = ['2010-01-01'] # mock getting risk model factor category mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_data', Mock()) mock.return_value = {'results': [{'factorData': [{'factorId': 'factor_id', 'factorCategory': 'Factor Name'}]}]} # mock getting risk model factor entity mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_factor_data', Mock()) mock.return_value = [ {'identifier': 'factor_id', 'type': 'Factor', 'name': 'Factor Name', 'factorCategory': 'Factor Name'} ] with DataContext(dt.date(2020, 11, 23), dt.date(2020, 11, 25)): actual = mp.portfolio_factor_exposure('report_id', 'risk_model_id', 'Factor Name') assert all(actual.values == [-11.23, -11.24, -11.25]) with pytest.raises(MqValueError): mp.portfolio_factor_exposure('report_id', 'risk_model_id', 'Wrong Factor Name') replace.restore() def test_portfolio_factor_pnl(): replace = Replacer() # mock getting risk model entity() mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model', Mock()) mock.return_value = risk_model mock = replace('gs_quant.api.gs.reports.GsReportApi.get_report', Mock()) mock.return_value = factor_risk_report mock = replace('gs_quant.api.gs.reports.GsReportApi.get_reports', Mock()) mock.return_value = [factor_risk_report] # mock getting risk model dates mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model_dates', Mock()) mock.return_value = ['2010-01-01'] # mock getting risk model factor category mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_data', Mock()) mock.return_value = {'results': [{'factorData': [{'factorId': 'factor_id', 'factorCategory': 'Factor Name'}]}]} # mock getting risk model factor entity mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_factor_data', Mock()) mock.return_value = [ {'identifier': 'factor_id', 'type': 'Factor', 'name': 'Factor Name', 'factorCategory': 'Factor Name'} ] with DataContext(dt.date(2020, 11, 23), dt.date(2020, 11, 25)): # mock getting report factor data mock = replace('gs_quant.api.gs.reports.GsReportApi.get_factor_risk_report_results', Mock()) mock.return_value = factor_data actual = mp.portfolio_factor_pnl('report_id', 'risk_model_id', 'Factor Name') assert all(actual.values == [11.23, 11.24, 11.25]) with DataContext(dt.date(2020, 11, 22), dt.date(2020, 11, 25)): # mock getting report factor data with first day set to 0 factor_data_copy = copy.copy(factor_data) factor_data_copy.insert( 0, { 'date': '2020-11-22', 'reportId': 'report_id', 'factor': 'Factor Name', 'factorCategory': 'CNT', 'pnl': 0, 'exposure': -11.23, 'proportionOfRisk': 1, }, ) mock = replace('gs_quant.api.gs.reports.GsReportApi.get_factor_risk_report_results', Mock()) mock.return_value = factor_data_copy actual = mp.portfolio_factor_pnl('report_id', 'risk_model_id', 'Factor Name') assert all(actual.values == [0.0, 11.23, 11.24, 11.25]) with pytest.raises(MqValueError): mp.portfolio_factor_pnl('report_id', 'risk_model_id', 'Wrong Factor Name') replace.restore() def test_portfolio_factor_proportion_of_risk(): replace = Replacer() # mock getting risk model entity() mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model', Mock()) mock.return_value = risk_model mock = replace('gs_quant.api.gs.reports.GsReportApi.get_report', Mock()) mock.return_value = factor_risk_report mock = replace('gs_quant.api.gs.reports.GsReportApi.get_reports', Mock()) mock.return_value = [factor_risk_report] # mock getting report factor data mock = replace('gs_quant.api.gs.reports.GsReportApi.get_factor_risk_report_results', Mock()) mock.return_value = factor_data # mock getting risk model dates mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model_dates', Mock()) mock.return_value = ['2010-01-01'] # mock getting risk model factor category mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_data', Mock()) mock.return_value = {'results': [{'factorData': [{'factorId': 'factor_id', 'factorCategory': 'Factor Name'}]}]} # mock getting risk model factor entity mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_factor_data', Mock()) mock.return_value = [ {'identifier': 'factor_id', 'type': 'Factor', 'name': 'Factor Name', 'factorCategory': 'Factor Name'} ] with DataContext(dt.date(2020, 11, 23), dt.date(2020, 11, 25)): actual = mp.portfolio_factor_proportion_of_risk('report_id', 'risk_model_id', 'Factor Name') assert all(actual.values == [1, 2, 3]) with pytest.raises(MqValueError): mp.portfolio_factor_proportion_of_risk('report_id', 'risk_model_id', 'Wrong Factor Name') replace.restore() def test_portfolio_thematic_exposure(): replace = Replacer() # mock getting PTA report mock = replace('gs_quant.markets.report.ThematicReport.get', Mock()) mock.return_value = ThematicReport(id='report_id') mock = replace('gs_quant.entities.entity.PositionedEntity.get_thematic_report', Mock()) mock.return_value = ThematicReport(id='report_id') # mock getting thematic exposure mock = replace('gs_quant.markets.report.ThematicReport.get_thematic_exposure', Mock()) mock.return_value = pd.DataFrame(thematic_data) # mock getting asset mock = Stock('MAA0NE9QX2ABETG6', 'Test Asset') xrefs = replace('gs_quant.timeseries.measures.GsAssetApi.get_asset_xrefs', Mock()) xrefs.return_value = [ GsTemporalXRef( dt.date(2019, 1, 1), dt.date(2952, 12, 31), XRef( ticker='basket_ticker', ), ) ] replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()).return_value = mock with DataContext(dt.date(2020, 7, 12), dt.date(2020, 7, 15)): actual = mp.portfolio_thematic_exposure('report_id', 'basket_ticker') assert all(actual.values == [2, 2, 2, 2]) replace.restore() def test_portfolio_pnl(): replace = Replacer() performance_report = PerformanceReport( report_id='RP1', position_source_type='Portfolio', position_source_id='MP1', report_type='Portfolio Performance Analytics', parameters=ReportParameters(transaction_cost_model='FIXED'), ) # mock PerformanceReport.get() mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = performance_report mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = performance_report # mock PerformanceReport.get_pnl() mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame.from_dict( {'date': ['2022-07-01', '2022-07-02', '2022-07-03'], 'pnl': [200, 400, 600]} ) with DataContext(dt.date(2022, 7, 1), dt.date(2022, 7, 3)): actual = mp.portfolio_pnl('RP1') assert all(actual.values == [200, 400, 600]) replace.restore() def test_aggregate_factor_support(): replace = Replacer() # mock getting risk model entity() mock = replace('gs_quant.entities.entity.PositionedEntity.get_factor_risk_report', Mock()) mock.return_value = risk_model mock = replace('gs_quant.api.gs.reports.GsReportApi.get_report', Mock()) mock.return_value = factor_risk_report mock = replace('gs_quant.api.gs.reports.GsReportApi.get_report', Mock()) mock.return_value = factor_risk_report # mock getting report factor data mock = replace('gs_quant.api.gs.reports.GsReportApi.get_factor_risk_report_results', Mock()) mock.return_value = aggregate_factor_data # mock getting risk model dates mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model_dates', Mock()) mock.return_value = ['2010-01-01'] # mock getting risk model factor category mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_data', Mock()) mock.return_value = {'results': [{'factorData': [{'factorId': 'factor_id', 'factorCategory': 'Factor Name'}]}]} # mock getting risk model factor entity mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_factor_data', Mock()) mock.return_value = [ {'identifier': 'factor_id', 'type': 'Factor', 'name': 'Factor Name', 'factorCategory': 'Factor Name'} ] with DataContext(dt.date(2020, 11, 23), dt.date(2020, 11, 25)): actual = mp.portfolio_factor_proportion_of_risk('portfolio_id', 'report_id', 'Factor') assert all(actual.values == [1, 2, 3]) with DataContext(dt.date(2020, 11, 23), dt.date(2020, 11, 25)): actual = mp.portfolio_daily_risk('portfolio_id', 'report_id', 'Factor') assert all(actual.values == [1, 2, 3]) with DataContext(dt.date(2020, 11, 23), dt.date(2020, 11, 25)): actual = mp.portfolio_annual_risk('portfolio_id', 'report_id', 'Factor') assert all(actual.values == [1, 2, 3]) with pytest.raises(MqValueError): mp.portfolio_daily_risk('portfolio_id', 'report_id', 'Factor Name') with pytest.raises(MqValueError): mp.portfolio_annual_risk('portfolio_id', 'report_id', 'Factor Name') replace.restore() def test_hit_rate(): replace = Replacer() data = { 'pnl': [0.2, 0, 1, -0.97], 'date': ['2020-01-01', '2020-01-01', '2020-01-02', '2020-01-02'], 'entryType': ['Holding', 'Holding', 'Holding', 'Holding'], 'assetId': ['asset1', 'asset2', 'asset1', 'asset2'], } timeseries = {'2020-01-02': 0.5} mock = replace('gs_quant.markets.report.PerformanceReport.get_portfolio_constituents', Mock()) mock.return_value = pd.DataFrame(data) mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() expected = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 2)): returned = mp.portfolio_hit_rate("test", 2) assert np.allclose(expected.dropna(), returned.dropna(), atol=1e-6) replace.restore() def test_max_drawdown(): replace = Replacer() data = {'2020-01-01': 1, '2020-01-02': 2, '2020-01-03': 1, '2020-01-04': 0} timeseries = { pd.Timestamp('2020-01-01 00:00:00'): np.nan, pd.Timestamp('2020-01-02 00:00:00'): np.nan, pd.Timestamp('2020-01-03 00:00:00'): np.nan, pd.Timestamp('2020-01-04 00:00:00'): -1, } mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = data mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() max_drawdown_series = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 4)): returned = mp.portfolio_max_drawdown("test", 4) assert max_drawdown_series.equals(returned) replace.restore() def test_max_recovery_period(): replace = Replacer() timeseries = { pd.Timestamp('2020-01-01 00:00:00'): np.nan, pd.Timestamp('2020-01-02 00:00:00'): np.nan, pd.Timestamp('2020-01-03 00:00:00'): np.nan, pd.Timestamp('2020-01-04 00:00:00'): np.nan, pd.Timestamp('2020-01-05 00:00:00'): np.nan, pd.Timestamp('2020-01-06 00:00:00'): 3, } mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() max_drawdown_series = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 6)): returned = mp.portfolio_max_recovery_period("test", 6) assert max_drawdown_series.equals(returned) replace.restore() def test_drawdown_length(): replace = Replacer() timeseries = { pd.Timestamp('2020-01-01 00:00:00'): np.nan, pd.Timestamp('2020-01-02 00:00:00'): np.nan, pd.Timestamp('2020-01-03 00:00:00'): np.nan, pd.Timestamp('2020-01-04 00:00:00'): np.nan, pd.Timestamp('2020-01-05 00:00:00'): np.nan, pd.Timestamp('2020-01-06 00:00:00'): 3, } mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() max_drawdown_series = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 6)): returned = mp.portfolio_drawdown_length("test", 6) assert max_drawdown_series.equals(returned) replace.restore() def test_standard_deviation(): replace = Replacer() data = {'pnl': [4, 5, 6], 'date': ['2020-01-01', '2020-01-02', '2020-01-03']} timeseries = {'2020-01-01': np.nan, '2020-01-02': np.nan, '2020-01-03': 1.41421} mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame(data) mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() expected = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 2)): returned = mp.portfolio_standard_deviation("test", '2d') assert np.allclose(expected.dropna(), returned.dropna(), atol=1e-6) replace.restore() def test_downside_risk(): replace = Replacer() data = {'pnl': [4, -7, 6], 'date': ['2020-01-01', '2020-01-02', '2020-01-03']} timeseries = {'2020-01-01': np.nan, '2020-01-02': np.nan, '2020-01-03': 5} mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame(data) mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() expected = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 2)): returned = mp.portfolio_downside_risk("test", 2) assert np.allclose(expected.dropna(), returned.dropna(), atol=1e-6) replace.restore() def test_semi_variance(): replace = Replacer() data = {'pnl': [4, -7, 6, 7], 'date': ['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04']} timeseries = {'2020-01-01': np.nan, '2020-01-02': np.nan, '2020-01-04': 8} mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame(data) mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() expected = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 2)): returned = mp.portfolio_semi_variance("test", 3) assert np.allclose(expected.dropna(), returned.dropna(), atol=1e-6) replace.restore() def test_kurtosis(): replace = Replacer() data = { 'pnl': [ -1, 0, 1, ], 'date': ['2020-01-01', '2020-01-02', '2020-01-03'], } timeseries = {'2020-01-01': np.nan, '2020-01-02': np.nan, '2020-01-03': -2} mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame(data) mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() expected = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 2)): returned = mp.portfolio_kurtosis("test", 2) assert np.allclose(expected.dropna(), returned.dropna(), atol=1e-6) replace.restore() def test_skewness(): replace = Replacer() data = { 'pnl': [ -1, 0, 2, ], 'date': ['2020-01-01', '2020-01-02', '2020-01-03'], } timeseries = {'2020-01-01': np.nan, '2020-01-02': np.nan, '2020-01-03': 0} mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame(data) mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() expected = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 2)): returned = mp.portfolio_skewness("test", 2) assert np.allclose(expected.dropna(), returned.dropna(), atol=1e-6) replace.restore() def test_realized_var(): replace = Replacer() data = { 'pnl': [ 1, 1, 1, ], 'date': ['2020-01-01', '2020-01-02', '2020-01-03'], } timeseries = {'2020-01-01': np.nan, '2020-01-02': np.nan, '2020-01-03': -0.52500} mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame(data) mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() expected = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 2)): returned = mp.portfolio_realized_var("test", 2) assert np.allclose(expected.dropna(), returned.dropna(), atol=1e-6) replace.restore() def test_bad_date(): replace = Replacer() data = { 'pnl': [ 1, 1, 1, ], 'date': ['2020-01-01', '2020-01-02', '2020-01-03'], } mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame(data) mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 2)): try: mp.portfolio_realized_var("test", "12p") except MqValueError: pass else: assert False replace.restore() def test_tracking_error(): replace = Replacer() data = { 'pnl': [0, 0.02, 0.01, 0.01, 0.02], 'date': ['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05'], } benchmark_data = { pd.Timestamp('2020-01-01'): 1, pd.Timestamp('2020-01-02'): 1.02, pd.Timestamp('2020-01-03'): 1.03, pd.Timestamp('2020-01-04'): 1.04, pd.Timestamp('2020-01-05'): 1.045, } timeseries = { pd.Timestamp('2020-01-01'): np.nan, pd.Timestamp('2020-01-02'): np.nan, pd.Timestamp('2020-01-03'): 0.00340, pd.Timestamp('2020-01-04'): 0.003603, pd.Timestamp('2020-01-05'): 0.010537, } mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame(data) mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()) mock.return_value = Index("test", 'test', "test") mock = replace('gs_quant.entities.entity.Entity.get_data_coordinate', Mock()) mock.return_value = DataCoordinate("test") mock = replace('gs_quant.data.coordinate.DataCoordinate.get_series', Mock()) mock.return_value = pd.Series(benchmark_data) mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() expected = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 2)): returned = mp.portfolio_tracking_error("test", 'test', 2) assert np.allclose(expected.dropna(), returned.dropna(), atol=1e-5) replace.restore() def test_tracking_error_bull(): replace = Replacer() data = { 'pnl': [0, 0.02, 0.01, 0.01, 0.02], 'date': ['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05'], } benchmark_data = { pd.Timestamp('2020-01-01'): 1, pd.Timestamp('2020-01-02'): 1.02, pd.Timestamp('2020-01-03'): 1.03, pd.Timestamp('2020-01-04'): 1.04, pd.Timestamp('2020-01-05'): 1.045, } timeseries = { pd.Timestamp('2020-01-01'): np.nan, pd.Timestamp('2020-01-02'): np.nan, pd.Timestamp('2020-01-03'): 0.71771, pd.Timestamp('2020-01-04'): 0.70357, pd.Timestamp('2020-01-05'): 0.34648, } mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame(data) mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()) mock.return_value = Index("test", 'test', "test") mock = replace('gs_quant.entities.entity.Entity.get_data_coordinate', Mock()) mock.return_value = DataCoordinate("test") mock = replace('gs_quant.data.coordinate.DataCoordinate.get_series', Mock()) mock.return_value = pd.Series(benchmark_data) mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() expected = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 2)): returned = mp.portfolio_tracking_error_bull("test", 'test', 2) assert np.allclose(expected.dropna(), returned.dropna(), atol=1e-6) replace.restore() def test_tracking_error_bear(): replace = Replacer() data = { 'pnl': [0, 0.02, 0.01, 0.01, 0.02], 'date': ['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05'], } benchmark_data = { pd.Timestamp('2020-01-01'): 1, pd.Timestamp('2020-01-02'): 1.02, pd.Timestamp('2020-01-03'): 1.03, pd.Timestamp('2020-01-04'): 1.04, pd.Timestamp('2020-01-05'): 1.045, } timeseries = { pd.Timestamp('2020-01-01'): np.nan, pd.Timestamp('2020-01-02'): np.nan, pd.Timestamp('2020-01-03'): np.nan, } mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame(data) mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()) mock.return_value = Index("test", 'test', "test") mock = replace('gs_quant.entities.entity.Entity.get_data_coordinate', Mock()) mock.return_value = DataCoordinate("test") mock = replace('gs_quant.data.coordinate.DataCoordinate.get_series', Mock()) mock.return_value = pd.Series(benchmark_data) mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() expected = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 2)): returned = mp.portfolio_tracking_error_bear("test", 'test', 2) assert np.allclose(expected.dropna(), returned.dropna(), atol=1e-5) replace.restore() def test_sharpe_ratio(): replace = Replacer() data = { 'pnl': [0, 0.02, 0.02, 0.01, 0.02], 'date': ['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05'], } benchmark_data = { pd.Timestamp('2020-01-01'): 0.02, pd.Timestamp('2020-01-02'): 0.02, pd.Timestamp('2020-01-03'): 0.03, pd.Timestamp('2020-01-04'): 0.04, pd.Timestamp('2020-01-05'): 0.045, } timeseries = { pd.Timestamp('2020-01-01'): np.nan, pd.Timestamp('2020-01-02'): np.nan, pd.Timestamp('2020-01-03'): np.nan, pd.Timestamp('2020-01-04'): 32.60231, pd.Timestamp('2020-01-05'): 33.77863, } aum_data = {'2020-01-01': 1.00, '2020-01-02': 1.035, '2020-01-03': 1.05, '2020-01-04': 1.06, '2020-01-05': 1.065} mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame(data) mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()) mock.return_value = Index("test", 'test', "test") mock = replace('gs_quant.entities.entity.Entity.get_data_coordinate', Mock()) mock.return_value = DataCoordinate("test") mock = replace('gs_quant.data.coordinate.DataCoordinate.get_series', Mock()) mock.return_value = pd.Series(benchmark_data) mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() expected = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 2)): returned = mp.portfolio_sharpe_ratio("test", 'test', 2) assert np.allclose(expected.dropna(), returned.dropna(), atol=1e-5) replace.restore() def test_calmar_ratio(): replace = Replacer() data = { 'pnl': [0, 0.02, 0.01, 0.04, 0.03], 'date': ['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05'], } yield_data = { pd.Timestamp('2020-01-01'): 0.01, pd.Timestamp('2020-01-02'): 0.02, pd.Timestamp('2020-01-03'): 0.03, pd.Timestamp('2020-01-04'): 0.03, pd.Timestamp('2020-01-05'): 0.03, } timeseries = {pd.Timestamp('2020-01-04 00:00:00'): 16.20658, pd.Timestamp('2020-01-05 00:00:00'): 30.31192} aum_data = {'2020-01-01': 1.00, '2020-01-02': 0.95, '2020-01-03': 1.05, '2020-01-04': 1.03, '2020-01-05': 1.065} mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame(data) mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()) mock.return_value = Bond( "test", 'test', ) mock = replace('gs_quant.entities.entity.Entity.get_data_coordinate', Mock()) mock.return_value = DataCoordinate("test") mock = replace('gs_quant.data.coordinate.DataCoordinate.get_series', Mock()) mock.return_value = pd.Series(yield_data) mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() expected = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 2)): returned = mp.portfolio_calmar_ratio("test", 3) assert np.allclose(expected.dropna(), returned.dropna(), atol=1e-5) replace.restore() def test_sortino_ratio(): replace = Replacer() data = { 'pnl': [0.02, 0.02, -0.01, 0.02, 0.01], 'date': ['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05'], } yield_data = { pd.Timestamp('2020-01-01'): 0.001, pd.Timestamp('2020-01-02'): 0.07, pd.Timestamp('2020-01-03'): 0.08, pd.Timestamp('2020-01-04'): 0.075, pd.Timestamp('2020-01-05'): 0.02, } timeseries = { pd.Timestamp('2020-01-04 00:00:00'): 15.063383, pd.Timestamp('2020-01-05 00:00:00'): 10.128712, } aum_data = {'2020-01-01': 1.00, '2020-01-02': 0.95, '2020-01-03': 0.9, '2020-01-04': 1.03, '2020-01-05': 1.065} mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame(data) mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()) mock.return_value = Bond( "test", 'test', ) mock = replace('gs_quant.entities.entity.Entity.get_data_coordinate', Mock()) mock.return_value = DataCoordinate("test") mock = replace('gs_quant.data.coordinate.DataCoordinate.get_series', Mock()) mock.return_value = pd.Series(yield_data) mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() expected = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 2)): returned = mp.portfolio_sortino_ratio("test", "test", 3) assert np.allclose(expected.dropna(), returned.dropna(), atol=1e-6) replace.restore() def test_sortino_ratio_index(): replace = Replacer() data = { 'pnl': [0.02, 0.02, -0.01, 0.02, 0.01], 'date': ['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05'], } pricing_data = { pd.Timestamp('2020-01-01'): 0.001, pd.Timestamp('2020-01-02'): 0.07, pd.Timestamp('2020-01-03'): 0.07, pd.Timestamp('2020-01-04'): 0.001, pd.Timestamp('2020-01-05'): 0.02, } timeseries = {pd.Timestamp('2020-01-05 00:00:00'): -8.355985} aum_data = {'2020-01-01': 1.00, '2020-01-02': 0.95, '2020-01-03': 0.9, '2020-01-04': 1.03, '2020-01-05': 1.065} mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame(data) mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()) mock.return_value = Index("test", 'test', "test") mock = replace('gs_quant.entities.entity.Entity.get_data_coordinate', Mock()) mock.return_value = DataCoordinate("test") mock = replace('gs_quant.data.coordinate.DataCoordinate.get_series', Mock()) mock.return_value = pd.Series(pricing_data) mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() expected = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2020, 1, 2)): returned = mp.portfolio_sortino_ratio("test", "test", 4) assert np.allclose(expected.dropna(), returned.dropna(), atol=1e-5) replace.restore() def test_jensen_alpha(): replace = Replacer() data = { 'pnl': [0.02, 0.02, 0.01, 0.02, 0.01], 'date': ['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05'], } yield_data = { pd.Timestamp('2020-01-01'): 0.01, pd.Timestamp('2020-01-02'): 0.02, pd.Timestamp('2020-01-03'): 0.025, pd.Timestamp('2020-01-04'): 0.03, pd.Timestamp('2020-01-05'): 0.04, } timeseries = {pd.Timestamp('2020-01-04 00:00:00'): 0.01814, pd.Timestamp('2020-01-05 00:00:00'): 0.00661} mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame(data) mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()) mock.return_value = Bond( "test", 'test', ) mock = replace('gs_quant.entities.entity.Entity.get_data_coordinate', Mock()) mock.return_value = DataCoordinate("test") mock = replace('gs_quant.data.coordinate.DataCoordinate.get_series', Mock()) mock.return_value = pd.Series(yield_data) mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() expected = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 2)): returned = mp.portfolio_jensen_alpha("test", "test", 4) assert np.allclose(expected.dropna(), returned.dropna(), atol=1e-5) replace.restore() def test_jensen_bull(): replace = Replacer() benchmark_data = { pd.Timestamp('2020-01-01'): 6, pd.Timestamp('2020-01-02'): 5, pd.Timestamp('2020-01-03'): 7, pd.Timestamp('2020-01-04'): 9, pd.Timestamp('2020-01-08'): 10, } data = { 'pnl': [0.02, 0.02, 0.01, 0.02, 0.01], 'date': ['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05'], } yield_data = { pd.Timestamp('2020-01-01'): 0.01, pd.Timestamp('2020-01-02'): 0.02, pd.Timestamp('2020-01-03'): 0.025, pd.Timestamp('2020-01-04'): 0.03, pd.Timestamp('2020-01-05'): 0.04, } timeseries = {pd.Timestamp('2020-01-04 00:00:00'): 0.02530} mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame(data) mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()) mock.return_value = Bond( "test", 'test', ) mock = replace('gs_quant.entities.entity.Entity.get_data_coordinate', Mock()) mock.return_value = DataCoordinate("test") replace( 'gs_quant.data.coordinate.DataCoordinate.get_series', Mock(side_effect=[pd.Series(benchmark_data), pd.Series(yield_data)]), ) mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() expected = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 2)): returned = mp.portfolio_jensen_alpha_bull("test", "test", 4) assert np.allclose(expected.dropna(), returned.dropna(), atol=1e-5) replace.restore() def test_jensen_alpha_bear(): replace = Replacer() benchmark_data = { pd.Timestamp('2020-01-01'): 6, pd.Timestamp('2020-01-02'): 5, pd.Timestamp('2020-01-03'): 7, pd.Timestamp('2020-01-04'): 9, pd.Timestamp('2020-01-08'): 10, } data = { 'pnl': [0.02, -0.02, -0.01, -0.02, -0.01], 'date': ['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05'], } yield_data = { pd.Timestamp('2020-01-01'): 0.01, pd.Timestamp('2020-01-02'): 0.02, pd.Timestamp('2020-01-03'): 0.025, pd.Timestamp('2020-01-04'): 0.03, pd.Timestamp('2020-01-05'): 0.04, } timeseries = {pd.Timestamp('2020-01-04 00:00:00'): -0.88850} mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame(data) mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()) mock.return_value = Bond( "test", 'test', ) mock = replace('gs_quant.entities.entity.Entity.get_data_coordinate', Mock()) mock.return_value = DataCoordinate("test") replace( 'gs_quant.data.coordinate.DataCoordinate.get_series', Mock(side_effect=[pd.Series(benchmark_data), pd.Series(yield_data)]), ) mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() expected = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 2)): returned = mp.portfolio_jensen_alpha_bear("test", "test", 4) assert np.allclose(expected.dropna(), returned.dropna(), atol=1e-5) replace.restore() def test_information_ratio(): replace = Replacer() benchmark_data = { pd.Timestamp('2020-01-01'): 6, pd.Timestamp('2020-01-02'): 5, pd.Timestamp('2020-01-03'): 7, pd.Timestamp('2020-01-04'): 9, } data = {'pnl': [0.02, 0.02, 0.01, 0.02], 'date': ['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04']} yield_data = { pd.Timestamp('2020-01-01'): 0.01, pd.Timestamp('2020-01-02'): 0.02, pd.Timestamp('2020-01-03'): 0.025, pd.Timestamp('2020-01-04'): 0.03, } timeseries = { pd.Timestamp('2020-01-02 00:00:00'): 0.748532, pd.Timestamp('2020-01-03 00:00:00'): -1.583948, pd.Timestamp('2020-01-04 00:00:00'): -1.065513, } mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame(data) mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()) mock.return_value = Bond( "test", 'test', ) mock = replace('gs_quant.entities.entity.Entity.get_data_coordinate', Mock()) mock.return_value = DataCoordinate("test") replace( 'gs_quant.data.coordinate.DataCoordinate.get_series', Mock(side_effect=[pd.Series(benchmark_data), pd.Series(yield_data)]), ) mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() expected = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 2)): returned = mp.portfolio_information_ratio("test", "test") assert np.allclose(expected.dropna(), returned.dropna(), atol=1e-5) replace.restore() def test_information_ratio_bull(): replace = Replacer() benchmark_data = { pd.Timestamp('2020-01-01'): 6, pd.Timestamp('2020-01-02'): 5, pd.Timestamp('2020-01-03'): 7, pd.Timestamp('2020-01-04'): 9, } data = {'pnl': [0.02, 0.02, 0.01, 0.02], 'date': ['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04']} yield_data = { pd.Timestamp('2020-01-01'): 0.01, pd.Timestamp('2020-01-02'): 0.02, pd.Timestamp('2020-01-03'): 0.025, pd.Timestamp('2020-01-04'): 0.03, } timeseries = {pd.Timestamp('2020-01-03 00:00:00'): -6.11050, pd.Timestamp('2020-01-04 00:00:00'): -4.11050} mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame(data) mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()) mock.return_value = Bond( "test", 'test', ) mock = replace('gs_quant.entities.entity.Entity.get_data_coordinate', Mock()) mock.return_value = DataCoordinate("test") replace( 'gs_quant.data.coordinate.DataCoordinate.get_series', Mock(side_effect=[pd.Series(benchmark_data), pd.Series(yield_data)]), ) mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() expected = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 2)): returned = mp.portfolio_information_ratio_bull("test", "test") assert np.allclose(expected.dropna(), returned.dropna(), atol=1e-5) replace.restore() def test_information_ratio_bear(): replace = Replacer() benchmark_data = { pd.Timestamp('2020-01-01'): 6, pd.Timestamp('2020-01-02'): 5, pd.Timestamp('2020-01-03'): 4, pd.Timestamp('2020-01-04'): 9, } data = {'pnl': [0.02, 0.02, 0.01, 0.02], 'date': ['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04']} yield_data = { pd.Timestamp('2020-01-01'): 0.01, pd.Timestamp('2020-01-02'): 0.02, pd.Timestamp('2020-01-03'): 0.025, pd.Timestamp('2020-01-04'): 0.03, } timeseries = {pd.Timestamp('2020-01-02 00:00:00'): 20.36364, pd.Timestamp('2020-01-03 00:00:00'): 22.36364} mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame(data) mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()) mock.return_value = Bond( "test", 'test', ) mock = replace('gs_quant.entities.entity.Entity.get_data_coordinate', Mock()) mock.return_value = DataCoordinate("test") replace( 'gs_quant.data.coordinate.DataCoordinate.get_series', Mock(side_effect=[pd.Series(benchmark_data), pd.Series(yield_data)]), ) mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() expected = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 2)): returned = mp.portfolio_information_ratio_bear("test", "test") assert np.allclose(expected.dropna(), returned.dropna(), atol=1e-5) replace.restore() def test_modigliani_ratio(): replace = Replacer() benchmark_data = { pd.Timestamp('2020-01-01'): 1, pd.Timestamp('2020-01-02'): 1.03, pd.Timestamp('2020-01-03'): 1.04, pd.Timestamp('2020-01-04'): 0.96, pd.Timestamp('2020-01-05'): 1.01, pd.Timestamp('2020-01-06'): 1.06, pd.Timestamp('2020-01-07'): 1.04, } data = { 'pnl': [ 0.02, 0.02, -0.01, -0.002, 0.003, 0.01, 0.002, ], 'date': ['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05', '2020-01-06', '2020-01-07'], } yield_data = { pd.Timestamp('2020-01-01'): 0.01, pd.Timestamp('2020-01-02'): 0.02, pd.Timestamp('2020-01-03'): 0.025, pd.Timestamp('2020-01-04'): 0.03, pd.Timestamp('2020-01-05'): 0.028, pd.Timestamp('2020-01-06'): 0.031, pd.Timestamp('2020-01-07'): 0.032, } timeseries = {pd.Timestamp('2020-01-05 00:00:00'): 0.31288, pd.Timestamp('2020-01-06 00:00:00'): 0.20278} mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame(data) mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()) mock.return_value = Bond( "test", 'test', ) mock = replace('gs_quant.entities.entity.Entity.get_data_coordinate', Mock()) mock.return_value = DataCoordinate("test") mock = replace( 'gs_quant.data.coordinate.DataCoordinate.get_series', Mock( side_effect=[ pd.Series(benchmark_data), pd.Series(yield_data), pd.Series(yield_data), pd.Series(yield_data), pd.Series(yield_data), ] ), ) mock.return_value = pd.Series(benchmark_data) mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() expected = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 2)): returned = mp.portfolio_modigliani_ratio("test", "test", "test", 4) assert np.allclose(expected.dropna(), returned.dropna(), atol=1e-5) replace.restore() def test_treynor_measure(): replace = Replacer() data = { 'pnl': [ 0.02, 0.02, 0.01, 0.002, 0.003, 0.01, 0.002, ], 'date': ['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05', '2020-01-06', '2020-01-07'], } yield_data = { pd.Timestamp('2020-01-01'): 0.01, pd.Timestamp('2020-01-02'): 0.02, pd.Timestamp('2020-01-03'): 0.025, pd.Timestamp('2020-01-04'): 0.03, pd.Timestamp('2020-01-05'): 0.028, pd.Timestamp('2020-01-06'): 0.031, pd.Timestamp('2020-01-07'): 0.032, } timeseries = { pd.Timestamp('2020-01-04 00:00:00'): -0.01426, } benchmark_data = { pd.Timestamp('2020-01-01'): 6, pd.Timestamp('2020-01-02'): 5, pd.Timestamp('2020-01-03'): 4, pd.Timestamp('2020-01-04'): 9, } mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame(data) mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()) mock.return_value = Bond( "test", 'test', ) mock = replace('gs_quant.entities.entity.Entity.get_data_coordinate', Mock()) mock.return_value = DataCoordinate("test") replace( 'gs_quant.data.coordinate.DataCoordinate.get_series', Mock(side_effect=[pd.Series(benchmark_data), pd.Series(yield_data)]), ) mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() expected = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 2)): returned = mp.portfolio_treynor_measure("test", 'test', 'test') assert np.allclose(expected.dropna(), returned.dropna(), atol=1e-5) replace.restore() def test_alpha(): replace = Replacer() benchmark_data = { pd.Timestamp('2020-01-01'): 1, pd.Timestamp('2020-01-02'): 1.03, pd.Timestamp('2020-01-03'): 1.04, pd.Timestamp('2020-01-04'): 0.96, pd.Timestamp('2020-01-05'): 1.01, pd.Timestamp('2020-01-06'): 1.06, pd.Timestamp('2020-01-07'): 1.04, } data = { 'pnl': [ 0.02, 0.02, 0.01, 0.002, 0.003, 0.01, 0.002, ], 'date': ['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05', '2020-01-06', '2020-01-07'], } yield_data = { pd.Timestamp('2020-01-01'): 0.01, pd.Timestamp('2020-01-02'): 0.02, pd.Timestamp('2020-01-03'): 0.025, pd.Timestamp('2020-01-04'): 0.03, pd.Timestamp('2020-01-05'): 0.028, pd.Timestamp('2020-01-06'): 0.031, pd.Timestamp('2020-01-07'): 0.032, } timeseries = { pd.Timestamp('2020-01-05 00:00:00'): -1.61319, pd.Timestamp('2020-01-06 00:00:00'): -3.73679, pd.Timestamp('2020-01-07 00:00:00'): -1.38878, } aum_data = { '2020-01-01': 1.00, '2020-01-02': 0.95, '2020-01-03': 0.9, '2020-01-04': 1.03, '2020-01-05': 1.065, '2020-01-06': 1.08, '2020-01-07': 1.072, } mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame(data) mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()) mock.return_value = Bond( "test", 'test', ) mock = replace('gs_quant.entities.entity.Entity.get_data_coordinate', Mock()) mock.return_value = DataCoordinate("test") replace( 'gs_quant.data.coordinate.DataCoordinate.get_series', Mock(side_effect=[pd.Series(benchmark_data), pd.Series(yield_data)]), ) mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() expected = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 2)): returned = mp.portfolio_alpha("test", "test", 4) assert np.allclose(expected.dropna(), returned.dropna(), atol=1e-6) replace.restore() def test_beta(): replace = Replacer() benchmark_data = { pd.Timestamp('2020-01-01'): 1, pd.Timestamp('2020-01-02'): 1.03, pd.Timestamp('2020-01-03'): 1.04, pd.Timestamp('2020-01-04'): 0.96, pd.Timestamp('2020-01-05'): 1.01, pd.Timestamp('2020-01-06'): 1.06, pd.Timestamp('2020-01-07'): 1.04, } data = { 'pnl': [ 0.02, 0.02, 0.01, 0.002, 0.003, 0.01, 0.002, ], 'date': ['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05', '2020-01-06', '2020-01-07'], } yield_data = { pd.Timestamp('2020-01-01'): 0.01, pd.Timestamp('2020-01-02'): 0.02, pd.Timestamp('2020-01-03'): 0.025, pd.Timestamp('2020-01-04'): 0.03, pd.Timestamp('2020-01-05'): 0.028, pd.Timestamp('2020-01-06'): 0.031, pd.Timestamp('2020-01-07'): 0.032, } timeseries = { pd.Timestamp('2020-01-05 00:00:00'): 0.00118, pd.Timestamp('2020-01-06 00:00:00'): 0.00070, pd.Timestamp('2020-01-07 00:00:00'): 0.00106, } mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame(data) mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()) mock.return_value = Bond( "test", 'test', ) mock = replace('gs_quant.entities.entity.Entity.get_data_coordinate', Mock()) mock.return_value = DataCoordinate("test") replace( 'gs_quant.data.coordinate.DataCoordinate.get_series', Mock(side_effect=[pd.Series(benchmark_data), pd.Series(yield_data)]), ) mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() expected = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 2)): returned = mp.portfolio_beta("test", "test", 4) assert np.allclose(expected.dropna(), returned.dropna(), atol=1e-5) replace.restore() def test_correlation(): replace = Replacer() benchmark_data = { pd.Timestamp('2020-01-01'): 1, pd.Timestamp('2020-01-02'): 1.03, pd.Timestamp('2020-01-03'): 1.04, pd.Timestamp('2020-01-04'): 0.96, pd.Timestamp('2020-01-05'): 1.01, pd.Timestamp('2020-01-06'): 1.06, pd.Timestamp('2020-01-07'): 1.04, } data = { 'pnl': [ 0.02, 0.02, 0.01, 0.002, 0.003, 0.01, 0.002, ], 'date': ['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05', '2020-01-06', '2020-01-07'], } yield_data = { pd.Timestamp('2020-01-01'): 0.01, pd.Timestamp('2020-01-02'): 0.02, pd.Timestamp('2020-01-03'): 0.025, pd.Timestamp('2020-01-04'): 0.03, pd.Timestamp('2020-01-05'): 0.028, pd.Timestamp('2020-01-06'): 0.031, pd.Timestamp('2020-01-07'): 0.032, } timeseries = {pd.Timestamp('2020-01-06 00:00:00'): 0.21846, pd.Timestamp('2020-01-07 00:00:00'): 0.40573} mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame(data) mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()) mock.return_value = Bond( "test", 'test', ) mock = replace('gs_quant.entities.entity.Entity.get_data_coordinate', Mock()) mock.return_value = DataCoordinate("test") replace( 'gs_quant.data.coordinate.DataCoordinate.get_series', Mock(side_effect=[pd.Series(benchmark_data), pd.Series(yield_data)]), ) mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() expected = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 2)): returned = mp.portfolio_correlation("test", "test", 4) assert np.allclose(expected.dropna(), returned.dropna(), atol=1e-5) replace.restore() def test_r_squared(): replace = Replacer() benchmark_data = { pd.Timestamp('2020-01-01'): 1, pd.Timestamp('2020-01-02'): 1.03, pd.Timestamp('2020-01-03'): 1.04, pd.Timestamp('2020-01-04'): 0.96, pd.Timestamp('2020-01-05'): 1.01, pd.Timestamp('2020-01-06'): 1.06, pd.Timestamp('2020-01-07'): 1.04, } data = { 'pnl': [ 0.02, 0.02, 0.01, 0.002, 0.003, 0.01, 0.002, ], 'date': ['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05', '2020-01-06', '2020-01-07'], } yield_data = { pd.Timestamp('2020-01-01'): 0.01, pd.Timestamp('2020-01-02'): 0.02, pd.Timestamp('2020-01-03'): 0.025, pd.Timestamp('2020-01-04'): 0.03, pd.Timestamp('2020-01-05'): 0.028, pd.Timestamp('2020-01-06'): 0.031, pd.Timestamp('2020-01-07'): 0.032, } timeseries = {pd.Timestamp('2020-01-05 00:00:00'): 0.04772, pd.Timestamp('2020-01-06 00:00:00'): 0.16462} mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame(data) mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()) mock.return_value = Bond( "test", 'test', ) mock = replace('gs_quant.entities.entity.Entity.get_data_coordinate', Mock()) mock.return_value = DataCoordinate("test") replace( 'gs_quant.data.coordinate.DataCoordinate.get_series', Mock(side_effect=[pd.Series(benchmark_data), pd.Series(yield_data)]), ) mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() expected = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 2)): returned = mp.portfolio_r_squared("test", "test", 4) assert np.allclose(expected.dropna(), returned.dropna(), atol=1e-5) replace.restore() def test_capture_ratio(): replace = Replacer() benchmark_data = { pd.Timestamp('2020-01-01'): 1, pd.Timestamp('2020-01-02'): 1.03, pd.Timestamp('2020-01-03'): 1.04, pd.Timestamp('2020-01-04'): 1.06, pd.Timestamp('2020-01-05'): 1.06, pd.Timestamp('2020-01-06'): 1.06, pd.Timestamp('2020-01-07'): 1.04, } data = { 'pnl': [ 0.02, 0.02, 0.01, 0.002, 0.003, 0.01, 0.002, ], 'date': ['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05', '2020-01-06', '2020-01-07'], } yield_data = { pd.Timestamp('2020-01-01'): 0.01, pd.Timestamp('2020-01-02'): 0.02, pd.Timestamp('2020-01-03'): 0.025, pd.Timestamp('2020-01-04'): 0.03, pd.Timestamp('2020-01-05'): 0.028, pd.Timestamp('2020-01-06'): 0.031, pd.Timestamp('2020-01-07'): 0.032, } timeseries = {pd.Timestamp('2020-01-05 00:00:00'): 0.50900, pd.Timestamp('2020-01-06 00:00:00'): 0.69110} mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum_data mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame(data) mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport() mock = replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()) mock.return_value = Bond( "test", 'test', ) mock = replace('gs_quant.entities.entity.Entity.get_data_coordinate', Mock()) mock.return_value = DataCoordinate("test") replace( 'gs_quant.data.coordinate.DataCoordinate.get_series', Mock(side_effect=[pd.Series(benchmark_data), pd.Series(yield_data)]), ) mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport() expected = pd.Series(timeseries) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 2)): returned = mp.portfolio_capture_ratio("test", "test", 4) assert np.allclose(expected.dropna(), returned.dropna(), atol=1e-5) replace.restore() def test_custom_aum(): data = {'aum': [101, 102, 103], 'date': ['2020-01-01', '2020-01-02', '2020-01-03']} idx = pd.date_range('2020-01-01', freq='D', periods=3) df = MarketDataResponseFrame(data=data, index=idx) df.dataset_ids = ('AUM',) replace = Replacer() # mock GsPortfolioApi.get_reports() mock = replace('gs_quant.api.gs.portfolios.GsPortfolioApi.get_custom_aum', Mock()) mock.return_value = df with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 3)): actual = mp.aum('MP1') assert actual.index.equals(idx) assert all(actual.values == data['aum']) replace.restore() if __name__ == '__main__': pytest.main(args=[__file__]) ================================================ FILE: gs_quant/test/timeseries/test_measures_rates.py ================================================ """ Copyright 2020 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from typing import Union import pandas as pd import pytest from numpy.testing import assert_allclose from pandas._testing import assert_series_equal from testfixtures import Replacer from testfixtures.mock import Mock import gs_quant.timeseries.measures as tm import gs_quant.timeseries.measures_rates as tm_rates from gs_quant.api.gs.assets import GsTemporalXRef, GsAssetApi from gs_quant.api.gs.data import MarketDataResponseFrame, QueryType from gs_quant.common import PricingLocation, XRef from gs_quant.data import Fields, Dataset from gs_quant.data.core import DataContext from gs_quant.errors import MqValueError, MqError from gs_quant.markets import PricingContext from gs_quant.markets.securities import Cross, Currency from gs_quant.session import GsSession, Environment from gs_quant.test.timeseries.test_measures import ( _test_datasets, map_identifiers_default_mocker, mock_empty_market_data_response, ) from gs_quant.test.timeseries.utils import mock_request from gs_quant.timeseries import ( TdapiRatesDefaultsProvider, SWAPTION_DEFAULTS, CurrencyEnum, SecurityMaster, ExtendedSeries, ) from gs_quant.timeseries.measures_rates import ( _swaption_build_asset_query, _currency_to_tdapi_swaption_rate_asset, _check_strike_reference, _pricing_location_normalized, _default_pricing_location, ) _index = [pd.Timestamp('2019-01-01')] def test_parse_meeting_date(): with pytest.raises(MqValueError): tm_rates.parse_meeting_date(5) assert tm_rates.parse_meeting_date('2019-09-01') == dt.date(2019, 9, 1) assert tm_rates.parse_meeting_date(dt.date(2019, 9, 1)) == dt.date(2019, 9, 1) replace = Replacer() # mock cbd = replace('gs_quant.timeseries.measures._get_custom_bd', Mock()) cbd.return_value = pd.tseries.offsets.BusinessDay() today = replace('gs_quant.timeseries.measures.pd.Timestamp.today', Mock()) today.return_value = pd.Timestamp(2019, 5, 25) # cases assert tm_rates.parse_meeting_date() == dt.date(2019, 5, 24) assert tm_rates.parse_meeting_date('3m') == dt.date(2019, 2, 24) assert tm_rates.parse_meeting_date('3b') == dt.date(2019, 5, 22) # restore replace.restore() def test_get_swaption_parameter_floating_rate_option_returns_default(): provider = TdapiRatesDefaultsProvider(SWAPTION_DEFAULTS) value = provider.get_swaption_parameter(CurrencyEnum.GBP, "floatingRateTenor") assert value == "6m" def test_get_swaption_parameter_floating_rate_option_returns_given_value(): provider = TdapiRatesDefaultsProvider(SWAPTION_DEFAULTS) value = provider.get_swaption_parameter(CurrencyEnum.GBP, "floatingRateTenor", "66m") assert value == "66m" def test_get_swaption_parameter_strike_reference_option_returns_default(): provider = TdapiRatesDefaultsProvider(SWAPTION_DEFAULTS) value = provider.get_swaption_parameter(CurrencyEnum.GBP, "strikeReference") assert value == "ATM" def test_get_swaption_parameter_strike_reference_option_returns_given_value(): provider = TdapiRatesDefaultsProvider(SWAPTION_DEFAULTS) value = provider.get_swaption_parameter(CurrencyEnum.GBP, "strikeReference", "ATM+666") assert value == "ATM+666" def test_get_floating_rate_option_for_benchmark_retuns_rate(): provider = TdapiRatesDefaultsProvider(SWAPTION_DEFAULTS) value = provider.get_floating_rate_option_for_benchmark(CurrencyEnum.GBP, "LIBOR") assert value == "GBP-LIBOR-BBA" def test_get_floating_rate_option_for_benchmark_retuns_rate_usd(): provider = TdapiRatesDefaultsProvider(SWAPTION_DEFAULTS) value = provider.get_floating_rate_option_for_benchmark(CurrencyEnum.USD, "LIBOR") assert value == "USD-LIBOR-BBA" def test_check_strike_reference_string(): data_input = "ATM" output = _check_strike_reference(data_input) assert data_input == output def test_check_strike_reference_zero(): data_input = 0 output = _check_strike_reference(data_input) assert output == "ATM" def test_check_strike_reference_spot(): data_input = "spot" output = _check_strike_reference(data_input) assert output == "ATM" def test_check_strike_reference_string_positive(): data_input = "ATM+25" output = _check_strike_reference(data_input) assert output == "ATM+25" def test_check_strike_reference_string_negtive(): data_input = "ATM-25" output = _check_strike_reference(data_input) assert output == "ATM-25" def test_check_strike_reference_string_fractional(): data_input = "ATM-25.5" output = _check_strike_reference(data_input) assert output == "ATM-25.5" def test_check_strike_reference_numeric_fractional(): data_input = 12.5 output = _check_strike_reference(data_input) assert output == "ATM+12.5" def test_check_strike_reference_numeric(): data_input = -20 output = _check_strike_reference(data_input) assert output == "ATM-20" def test_check_strike_reference_throws(): data_input = "DUXA" with pytest.raises(MqValueError): _check_strike_reference(data_input) def test_check_strike_reference_list(): data_input = ["ATM+20", "ATM-20"] output = _check_strike_reference(data_input) assert output == data_input def test_check_strike_reference_invalid_list(): data_input = ["ATM+20", "MTM-20"] with pytest.raises(MqValueError): _check_strike_reference(data_input) def test_pricing_location_normalized(): assert _pricing_location_normalized(PricingLocation.LDN, CurrencyEnum.USD) == PricingLocation.LDN assert _pricing_location_normalized(PricingLocation.TKO, CurrencyEnum.USD) == PricingLocation.TKO assert _pricing_location_normalized(PricingLocation.LDN, CurrencyEnum.HKD) == PricingLocation.LDN assert _pricing_location_normalized(PricingLocation.TKO, CurrencyEnum.HKD) == PricingLocation.HKG def test_default_pricing_location(): assert _default_pricing_location(CurrencyEnum.USD) == PricingLocation.NYC with pytest.raises(MqValueError): _default_pricing_location(CurrencyEnum.EGP) def test_currency_to_tdapi_swaption_rate_asset_retuns_throws(): replace = Replacer() replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()).return_value = "ZAR" asset = Currency("MA1", "ZAR") assert _currency_to_tdapi_swaption_rate_asset(asset) == "MA1" replace.restore() def test_currency_to_tdapi_midcurve_asset(): replace = Replacer() replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()).return_value = 'ZAR' asset = Currency('MA1', 'ZAR') assert tm_rates._currency_to_tdapi_midcurve_asset(asset) == 'MA1' replace.restore() def test_currency_to_tdapi_swaption_rate_asset_retuns_asset_id(mocker): replace = Replacer() mocker.patch.object( GsSession.__class__, 'current', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, '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 PricingContext(dt.date.today()): cur = [ {"currency_assetId": "MAK1FHKH5P5GJSHH", "currency": "JPY", "swaption_id": "MATT7CA7PRA4B8YB"}, {"currency_assetId": "MA66CZBQJST05XKG", "currency": "GBP", "swaption_id": "MAX2SBXZRPYR3NTY"}, {"currency_assetId": "MAPSDDS072PHYMVQ", "currency": "AUD", "swaption_id": "MAQHSC1PAF4X5H4B"}, {"currency_assetId": "MAJNQPFGN1EBDHAE", "currency": "EUR", "swaption_id": "MAZB3PAH8JFVVT80"}, {"currency_assetId": "MAZ7RWC904JYHYPS", "currency": "USD", "swaption_id": "MAY0X3KRD4AN77E2"}, ] for c in cur: print(c) asset = Currency(c.get("currency_assetId"), c.get("currency")) bbid_mock.return_value = c.get("currency") mqid = _currency_to_tdapi_swaption_rate_asset(asset) assert mqid == c.get("swaption_id") bbid_mock.return_value = None assert _currency_to_tdapi_swaption_rate_asset(asset) == c.get("currency_assetId") replace.restore() def test_swaption_build_asset_query_throws_on_invalid_tenor(): with pytest.raises(MqValueError): _swaption_build_asset_query(CurrencyEnum.USD, expiration_tenor="Abc") def test_swaption_build_asset_query_usd(): defautls = _swaption_build_asset_query(CurrencyEnum.USD) assert defautls["asset_parameters_floating_rate_option"] == "USD-LIBOR-BBA" assert defautls["asset_parameters_clearing_house"] == "LCH" assert defautls["asset_parameters_termination_date"] == "5y" assert defautls["asset_parameters_expiration_date"] == "1y" assert defautls["asset_parameters_effective_date"] == "0b" assert defautls["asset_parameters_strike"] == "ATM" def test_swaption_build_asset_query_strike_reference(): defautls = _swaption_build_asset_query(CurrencyEnum.USD, None, None, None, None, "ATM+50") assert defautls["asset_parameters_floating_rate_option"] == "USD-LIBOR-BBA" assert defautls["asset_parameters_clearing_house"] == "LCH" assert defautls["asset_parameters_floating_rate_designated_maturity"] == "3m" assert defautls["asset_parameters_termination_date"] == "5y" assert defautls["asset_parameters_expiration_date"] == "1y" assert defautls["asset_parameters_effective_date"] == "0b" assert defautls["asset_parameters_strike"] == "ATM+50" def test_swaption_build_asset_query_clearing_house(): defautls = _swaption_build_asset_query(CurrencyEnum.USD, None, None, None, "12m", "ATM+50", None, "ABC") assert defautls["asset_parameters_floating_rate_option"] == "USD-LIBOR-BBA" assert defautls["asset_parameters_floating_rate_designated_maturity"] == "12m" assert defautls["asset_parameters_clearing_house"] == "ABC" assert defautls["asset_parameters_termination_date"] == "5y" assert defautls["asset_parameters_expiration_date"] == "1y" assert defautls["asset_parameters_effective_date"] == "0b" assert defautls["asset_parameters_strike"] == "ATM+50" def test_swaption_build_asset_query_custom(): defautls = _swaption_build_asset_query(CurrencyEnum.USD, "LIBOR", "12y", "66y", "12m", "ATM+50", "120y", "ABC") assert defautls["asset_parameters_floating_rate_option"] == "USD-LIBOR-BBA" assert defautls["asset_parameters_floating_rate_designated_maturity"] == "12m" assert defautls["asset_parameters_clearing_house"] == "ABC" assert defautls["asset_parameters_termination_date"] == "120y" assert defautls["asset_parameters_expiration_date"] == "66y" assert defautls["asset_parameters_effective_date"] == "12y" assert defautls["asset_parameters_strike"] == "ATM+50" def test_swaption_build_asset_query_custom_throws(): with pytest.raises(MqValueError): _swaption_build_asset_query(CurrencyEnum.USD, "NIBOR", "12y", "66y", "12m", "ATM+50", "120y", "ABC") def test_swaption_swaption_vol_term2_returns_data(): replace = Replacer() df = MarketDataResponseFrame( data=dict(expirationTenor=['1m', '6m', '1y'], terminationTenor=['1y', '2y', '3y'], swaptionVol=[1, 2, 3]), index=_index * 3, ) replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()).return_value = "GBP" replace('gs_quant.timeseries.measures_rates._get_tdapi_rates_assets', Mock(), Mock()).return_value = [ "MADWG3WHCKNE1DJA", "MAH6JK3TZJJGFQ65", ] replace('gs_quant.timeseries.measures_rates._range_from_pricing_date', Mock(), Mock()).return_value = [ dt.date(2020, 1, 2), dt.date(2020, 1, 2), ] replace('gs_quant.timeseries.measures_rates._market_data_timed', Mock()).return_value = df with DataContext('2019-01-01', '2025-01-01'): actual = tm_rates.swaption_vol_term(Currency("GBP", name="GBP"), tm.SwaptionTenorType.SWAP_MATURITY, '5y', 0) expected = pd.Series([1, 2, 3], index=pd.to_datetime(['2019-02-01', '2019-07-01', '2020-01-01'])) assert_series_equal(expected, pd.Series(actual), check_names=False) with DataContext('2019-01-01', '2025-01-01'): actual = tm_rates.swaption_vol_term(Currency("GBP", name="GBP"), tm.SwaptionTenorType.OPTION_EXPIRY, '5y', 0) expected = pd.Series([1, 2, 3], index=pd.to_datetime(['2020-01-01', '2021-01-01', '2021-12-31'])) assert_series_equal(expected, pd.Series(actual), check_names=False) replace.restore() def test_swaption_swaption_vol_term2_returns_empty(): replace = Replacer() df = ExtendedSeries(dtype=float) replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()).return_value = "GBP" replace('gs_quant.timeseries.measures_rates._get_tdapi_rates_assets', Mock(), Mock()).return_value = [ "MADWG3WHCKNE1DJA", "MAH6JK3TZJJGFQ65", ] replace('gs_quant.timeseries.measures_rates._range_from_pricing_date', Mock(), Mock()).return_value = [ dt.date(2020, 1, 2), dt.date(2020, 1, 2), ] replace('gs_quant.timeseries.measures_rates._market_data_timed', Mock()).return_value = df with DataContext('2019-01-01', '2025-01-01'): actual = tm_rates.swaption_vol_term(Currency("GBP", name="GBP"), tm.SwaptionTenorType.SWAP_MATURITY, '5y', 0) assert_series_equal(ExtendedSeries(dtype=float), actual, check_names=False) replace.restore() def test_swaption_swaption_vol_term2_throws(): with pytest.raises(NotImplementedError): tm_rates.swaption_vol_term( Currency("GBP", name="GBP"), tm.SwaptionTenorType.SWAP_MATURITY, '5y', 0, real_time=True ) def test_swaption_vol_smile2_returns_data(): replace = Replacer() test_data = dict(strikeRelative=["ATM", "ATM+50", "ATM+100"], swaptionVol=[1, 2, 3]) df = MarketDataResponseFrame(data=test_data, index=_index * 3) replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()).return_value = "GBP" replace('gs_quant.timeseries.measures_rates._get_tdapi_rates_assets', Mock(), Mock()).return_value = [ "MADWG3WHCKNE1DJA", "MAH6JK3TZJJGFQ65", ] replace('gs_quant.timeseries.measures_rates._range_from_pricing_date', Mock(), Mock()).return_value = [ dt.date(2020, 1, 2), dt.date(2020, 1, 2), ] replace('gs_quant.timeseries.measures_rates._market_data_timed', Mock()).return_value = df with DataContext('2019-01-01', '2025-01-01'): actual = tm_rates.swaption_vol_smile(Currency("GBP", name="GBP"), '3m', '10y') assert_series_equal(pd.Series([1, 2, 3], index=[0.0, 50.0, 100.0]), pd.Series(actual)) replace.restore() def test_swaption_vol_smile2_returns_no_data(): replace = Replacer() df = ExtendedSeries(dtype=float) replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()).return_value = "GBP" replace('gs_quant.timeseries.measures_rates._get_tdapi_rates_assets', Mock(), Mock()).return_value = [ "MADWG3WHCKNE1DJA", "MAH6JK3TZJJGFQ65", ] replace('gs_quant.timeseries.measures_rates._range_from_pricing_date', Mock(), Mock()).return_value = [ dt.date(2020, 1, 2), dt.date(2020, 1, 2), ] replace('gs_quant.timeseries.measures_rates._market_data_timed', Mock()).return_value = df with DataContext('2019-01-01', '2025-01-01'): actual = tm_rates.swaption_vol_smile(Currency("GBP", name="GBP"), '3m', '10y') assert_series_equal(ExtendedSeries(dtype=float), actual) replace.restore() def test_swaption_vol_smile2_returns_throws(): with pytest.raises(NotImplementedError): tm_rates.swaption_vol_smile(Currency("GBP", name="GBP"), "1m", "1m", real_time=True) def test_swaption_vol2_return_data(): replace = Replacer() test_data = dict(swaptionVol=[1, 2, 3]) df = MarketDataResponseFrame(data=test_data, index=[dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3)]) replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()).return_value = "GBP" replace('gs_quant.timeseries.measures_rates._get_tdapi_rates_assets', Mock(), Mock()).return_value = [ "MADWG3WHCKNE1DJA", "MAH6JK3TZJJGFQ65", ] replace('gs_quant.timeseries.measures_rates._range_from_pricing_date', Mock(), Mock()).return_value = [ dt.date(2019, 1, 2), dt.date(2019, 1, 5), ] replace('gs_quant.timeseries.measures_rates._market_data_timed', Mock()).return_value = df actual = tm_rates.swaption_vol(Currency("GBP", name="GBP")) assert_series_equal(tm._extract_series_from_df(df, QueryType.SWAPTION_VOL), actual) replace.restore() def test_swaption_vol2_return__empty_data(): replace = Replacer() df = ExtendedSeries(dtype=float) replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()).return_value = "GBP" replace('gs_quant.timeseries.measures_rates._get_tdapi_rates_assets', Mock(), Mock()).return_value = [ "MADWG3WHCKNE1DJA", "MAH6JK3TZJJGFQ65", ] replace('gs_quant.timeseries.measures_rates._range_from_pricing_date', Mock(), Mock()).return_value = [ dt.date(2019, 1, 2), dt.date(2019, 1, 5), ] replace('gs_quant.timeseries.measures_rates._market_data_timed', Mock()).return_value = df actual = tm_rates.swaption_vol(Currency("GBP", name="GBP")) assert_series_equal(ExtendedSeries(dtype=float), actual) replace.restore() def test_swaption_annuity_return_data(): replace = Replacer() test_data = dict(swaptionAnnuity=[1, 2, 3]) df = MarketDataResponseFrame(data=test_data, index=[dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3)]) replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()).return_value = "GBP" replace('gs_quant.timeseries.measures_rates._get_tdapi_rates_assets', Mock(), Mock()).return_value = [ "MADWG3WHCKNE1DJA", "MAH6JK3TZJJGFQ65", ] replace('gs_quant.timeseries.measures_rates._range_from_pricing_date', Mock(), Mock()).return_value = [ dt.date(2019, 1, 2), dt.date(2019, 1, 5), ] replace('gs_quant.timeseries.measures_rates._market_data_timed', Mock()).return_value = df actual = tm_rates.swaption_annuity(Currency("GBP", name="GBP")) assert_series_equal(tm._extract_series_from_df(df, QueryType.SWAPTION_ANNUITY), actual) replace.restore() def test_swaption_premium_return_data(): replace = Replacer() test_data = dict(swaptionPremium=[1, 2, 3]) df = MarketDataResponseFrame(data=test_data, index=[dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3)]) replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()).return_value = "GBP" replace( 'gs_quant.timeseries.measures_rates._get_tdapi_rates_assets', Mock(), Mock() ).return_value = "MADWG3WHCKNE1DJA" replace('gs_quant.timeseries.measures_rates._range_from_pricing_date', Mock(), Mock()).return_value = [ dt.date(2019, 1, 2), dt.date(2019, 1, 5), ] replace('gs_quant.timeseries.measures_rates._market_data_timed', Mock()).return_value = df actual = tm_rates.swaption_premium(Currency("GBP", name="GBP")) assert_series_equal(tm._extract_series_from_df(df, QueryType.SWAPTION_PREMIUM), actual) replace.restore() def test_swaption_premium_throws_for_realtime(): with pytest.raises(NotImplementedError): tm_rates.swaption_premium(Currency("GBP", name="GBP"), real_time=True) def test__check_forward_tenor_returns_None(): assert tm_rates._check_forward_tenor(None) is None def test__check_forward_tenor_returns_0b(): assert tm_rates._check_forward_tenor("spot") == '0b' def test_swaption_premium_throws_for_unsupported_ccy(): replace = Replacer() replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()).return_value = "ZAR" with pytest.raises(NotImplementedError): tm_rates.swaption_premium(Currency("KRW", name="KRW")) replace.restore() def test_swaption_atmFwdRate_return_data(): replace = Replacer() test_data = dict(atmFwdRate=[1, 2, 3]) df = MarketDataResponseFrame(data=test_data, index=[dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3)]) replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()).return_value = "GBP" replace('gs_quant.timeseries.measures_rates._get_tdapi_rates_assets', Mock(), Mock()).return_value = [ "MADWG3WHCKNE1DJA", "MAH6JK3TZJJGFQ65", ] replace('gs_quant.timeseries.measures_rates._range_from_pricing_date', Mock(), Mock()).return_value = [ dt.date(2019, 1, 2), dt.date(2019, 1, 5), ] replace('gs_quant.timeseries.measures_rates._market_data_timed', Mock()).return_value = df actual = tm_rates.swaption_atm_fwd_rate(Currency("GBP", name="GBP")) assert_series_equal(tm._extract_series_from_df(df, QueryType.ATM_FWD_RATE), actual) replace.restore() def test_midcurve_atmFwdRate_return_data(): replace = Replacer() test_data = dict(midcurveAtmFwdRate=[1, 2, 3]) df = MarketDataResponseFrame(data=test_data, index=[dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3)]) replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()).return_value = "GBP" replace('gs_quant.timeseries.measures_rates._get_tdapi_rates_assets', Mock(), Mock()).return_value = [ "MADWG3WHCKNE1DJA", "MAH6JK3TZJJGFQ65", ] replace('gs_quant.timeseries.measures_rates._range_from_pricing_date', Mock(), Mock()).return_value = [ dt.date(2019, 1, 2), dt.date(2019, 1, 5), ] replace('gs_quant.timeseries.measures_rates._market_data_timed', Mock()).return_value = df actual = tm_rates.midcurve_atm_fwd_rate(Currency("GBP", name="GBP"), "1y", "1y", "1y") assert_series_equal(tm._extract_series_from_df(df, QueryType.MIDCURVE_ATM_FWD_RATE), actual) replace.restore() def test_midcurve_annuity_return_data(): replace = Replacer() test_data = dict(midcurveAnnuity=[1, 2, 3]) df = MarketDataResponseFrame(data=test_data, index=[dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3)]) replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()).return_value = "GBP" replace('gs_quant.timeseries.measures_rates._get_tdapi_rates_assets', Mock(), Mock()).return_value = [ "MADWG3WHCKNE1DJA", "MAH6JK3TZJJGFQ65", ] replace('gs_quant.timeseries.measures_rates._range_from_pricing_date', Mock(), Mock()).return_value = [ dt.date(2019, 1, 2), dt.date(2019, 1, 5), ] replace('gs_quant.timeseries.measures_rates._market_data_timed', Mock()).return_value = df actual = tm_rates.midcurve_annuity(Currency("GBP", name="GBP"), "1y", "1y", "1y", 0) assert_series_equal(tm._extract_series_from_df(df, QueryType.MIDCURVE_ANNUITY), actual) replace.restore() def test_midcurve_premium_return_data(): replace = Replacer() test_data = dict(midcurvePremium=[1, 2, 3]) df = MarketDataResponseFrame(data=test_data, index=[dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3)]) replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()).return_value = "GBP" replace('gs_quant.timeseries.measures_rates._get_tdapi_rates_assets', Mock(), Mock()).return_value = [ "MADWG3WHCKNE1DJA", "MAH6JK3TZJJGFQ65", ] replace('gs_quant.timeseries.measures_rates._range_from_pricing_date', Mock(), Mock()).return_value = [ dt.date(2019, 1, 2), dt.date(2019, 1, 5), ] replace('gs_quant.timeseries.measures_rates._market_data_timed', Mock()).return_value = df actual = tm_rates.midcurve_premium(Currency("GBP", name="GBP"), "1y", "1y", "1y", 0) assert_series_equal(tm._extract_series_from_df(df, QueryType.MIDCURVE_PREMIUM), actual) replace.restore() def test_midcurve_vol_return_data(): replace = Replacer() test_data = dict(midcurveVol=[1, 2, 3]) df = MarketDataResponseFrame(data=test_data, index=[dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3)]) replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()).return_value = "GBP" replace('gs_quant.timeseries.measures_rates._get_tdapi_rates_assets', Mock(), Mock()).return_value = [ "MADWG3WHCKNE1DJA", "MAH6JK3TZJJGFQ65", ] replace('gs_quant.timeseries.measures_rates._range_from_pricing_date', Mock(), Mock()).return_value = [ dt.date(2019, 1, 2), dt.date(2019, 1, 5), ] replace('gs_quant.timeseries.measures_rates._market_data_timed', Mock()).return_value = df actual = tm_rates.midcurve_vol(Currency("GBP", name="GBP"), "1y", "1y", "1y", 0) assert_series_equal(tm._extract_series_from_df(df, QueryType.MIDCURVE_VOL), actual) replace.restore() def test_cross_to_fxfwd_xcswp_asset(mocker): replace = Replacer() mocker.patch.object( GsSession.__class__, 'current', return_value=GsSession.get(Environment.DEV, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, '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()) correct_mapping = { 'EURUSD': {"id": 'MA1VJC1E3SZW8E4S', "crossID": "MAA9MVX15AJNQCVG"}, 'GBPUSD': {"id": 'MA3JTR4HSC63H4V6', "crossID": "MA58R87SPRMKKE7Z"}, 'AUDUSD': {"id": 'MAD4VBRWYXFSY1N4', "crossID": "MAJTN2XJVF97SYJK"}, 'NZDUSD': {"id": 'MA1YHQMZVTM3VBWT', "crossID": "MANQ8REB1VJ4JFTR"}, 'USDSEK': {"id": 'MA2APZREBGDMME83', "crossID": "MA4ZD5CZGC3Y6JZD"}, 'USDNOK': {"id": 'MA0K3W6FKH6K1KJE', "crossID": "MAZ0P16RPG5P0MVF"}, 'USDDKK': {"id": 'MA328HZB86DYSWSJ', "crossID": "MABCHYGJ1TCBCQE4"}, 'USDCAD': {"id": 'MAT8JNEE2GN5NES6', "crossID": "MAP8G81B2KHTYR07"}, 'USDCHF': {"id": 'MABNGGTNB9A0TKCG', "crossID": "MAMPQHNP1A26RS1C"}, 'USDJPY': {"id": 'MAMZ9YG8AF3HQ18C', "crossID": "MAYJPCVVF2RWXCES"}, } with PricingContext(dt.date.today()): for cross in correct_mapping: asset = Cross(correct_mapping[cross]["crossID"], cross) bbid_mock.return_value = cross correct_id = tm_rates._cross_to_fxfwd_xcswp_asset(asset) assert correct_mapping[cross]["id"] == correct_id replace.restore() def test_ois_fxfwd_xcswap_measures(mocker): replace = Replacer() dict_fns = { "oisXccy": {"fn": tm_rates.ois_xccy, "queryType": QueryType.OIS_XCCY}, "oisXccyExSpike": {"fn": tm_rates.ois_xccy_ex_spike, "queryType": QueryType.OIS_XCCY_EX_SPIKE}, "usdOis": {"fn": tm_rates.usd_ois, "queryType": QueryType.USD_OIS}, "nonUsdOis": {"fn": tm_rates.non_usd_ois, "queryType": QueryType.NON_USD_OIS}, } args = dict(tenor='3y', real_time=False) for key in dict_fns: fn = dict_fns[key]["fn"] mock_jpy = Cross('MAYJPCVVF2RWXCES', 'USD/JPY') xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'JPYUSD' args['asset'] = mock_jpy with pytest.raises(NotImplementedError): fn(**args) mock_eur = Cross('MAA0NE9QX2ABETG6', 'EUR/USD') args['asset'] = mock_eur xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'EURUSD' with pytest.raises(NotImplementedError): fn(..., real_time=True) args['tenor'] = '5yr' with pytest.raises(MqValueError): fn(**args) args['tenor'] = '3y' test_data = {key: [1, 2, 3]} df = MarketDataResponseFrame( data=test_data, index=[dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3)] ) identifiers = replace('gs_quant.timeseries.measures_rates._get_tdapi_rates_assets', Mock()) identifiers.return_value = {'MAA9MVX15AJNQCVG'} # Test on EURUSD only replace('gs_quant.timeseries.measures_rates._range_from_pricing_date', Mock(), Mock()).return_value = [ dt.date(2019, 1, 2), dt.date(2019, 1, 5), ] replace('gs_quant.timeseries.measures_rates._market_data_timed', Mock()).return_value = df expected = tm_rates._extract_series_from_df(df, dict_fns[key]["queryType"]) actual = fn(**args) assert_series_equal(expected, actual) replace.restore() def get_data_policy_rate_expectation_mocker( start: Union[dt.date, dt.datetime] = None, end: Union[dt.date, dt.datetime] = None, as_of: dt.datetime = None, since: dt.datetime = None, fields: Union[str, Fields] = None, asset_id_type: str = None, **kwargs, ) -> pd.DataFrame: if 'meetingNumber' in kwargs: if kwargs['meetingNumber'] == 0: return mock_meeting_spot() elif 'meetingDate' in kwargs: if kwargs['meetingDate'] == dt.date(2019, 10, 24): return mock_meeting_spot() return mock_meeting_expectation() def mock_meeting_expectation(): data_dict = MarketDataResponseFrame( { 'date': [dt.date(2019, 12, 6)], 'assetId': ['MARFAGXDQRWM07Y2'], 'location': ['NYC'], 'rateType': ['Meeting Forward'], 'startingDate': [dt.date(2020, 1, 29)], 'endingDate': [dt.date(2020, 1, 29)], 'meetingNumber': [2], 'valuationDate': [dt.date(2019, 12, 6)], 'meetingDate': [dt.date(2020, 1, 23)], 'value': [-0.004550907771], } ) data_dict.dataset_ids = _test_datasets return data_dict def mock_meeting_spot(): data_dict = MarketDataResponseFrame( { 'date': [dt.date(2019, 12, 6)], 'assetId': ['MARFAGXDQRWM07Y2'], 'location': ['NYC'], 'rateType': ['Meeting Forward'], 'startingDate': [dt.date(2019, 10, 30)], 'endingDate': [dt.date(2019, 12, 18)], 'meetingNumber': [0], 'valuationDate': [dt.date(2019, 12, 6)], 'meetingDate': [dt.date(2019, 10, 24)], 'value': [-0.004522570525], } ) data_dict.dataset_ids = _test_datasets return data_dict def mock_meeting_absolute(): data_dict = MarketDataResponseFrame( { 'date': [dt.date(2019, 12, 6), dt.date(2019, 12, 6)], 'assetId': ['MARFAGXDQRWM07Y2', 'MARFAGXDQRWM07Y2'], 'location': ['NYC', 'NYC'], 'rateType': ['Meeting Forward', 'Meeting Forward'], 'startingDate': [dt.date(2019, 10, 30), dt.date(2020, 1, 29)], 'endingDate': [dt.date(2019, 10, 30), dt.date(2020, 1, 29)], 'meetingNumber': [0, 2], 'valuationDate': [dt.date(2019, 12, 6), dt.date(2019, 12, 6)], 'meetingDate': [dt.date(2019, 10, 24), dt.date(2020, 1, 23)], 'value': [-0.004522570525, -0.004550907771], } ) data_dict.dataset_ids = _test_datasets return data_dict def mock_ois_spot(): data_dict = MarketDataResponseFrame( { 'date': [dt.date(2019, 12, 6)], 'assetId': ['MARFAGXDQRWM07Y2'], 'location': ['NYC'], 'rateType': ['Spot'], 'startingDate': [dt.date(2019, 12, 6)], 'endingDate': [dt.date(2019, 12, 7)], 'meetingNumber': [-1], 'valuationDate': [dt.date(2019, 12, 6)], 'meetingDate': [dt.date(2019, 12, 6)], 'value': [-0.00455], } ) data_dict.dataset_ids = _test_datasets return data_dict def test_get_default_ois_benchmark(mocker): assert tm_rates._get_default_ois_benchmark(CurrencyEnum.USD) == tm_rates.BenchmarkTypeCB.Fed_Funds assert tm_rates._get_default_ois_benchmark(CurrencyEnum.EUR) == tm_rates.BenchmarkTypeCB.EUROSTR assert tm_rates._get_default_ois_benchmark(CurrencyEnum.GBP) == tm_rates.BenchmarkTypeCB.SONIA def test_policy_rate_term_structure(mocker): target = { 'meeting_absolute': -0.004550907771, 'meeting_relative': -0.00002833724599999969, 'eoy_absolute': -0.003359767756, 'eoy_relative': 0.001162802769, 'spot': -0.00455, } mock_eur = Currency('MARFAGXDQRWM07Y2', 'EUR') with DataContext(dt.date(2019, 12, 6), dt.date(2019, 12, 6)): replace = Replacer() xrefs = replace('gs_quant.timeseries.measures.GsAssetApi.get_asset_xrefs', Mock()) xrefs.return_value = [ GsTemporalXRef( dt.date(2019, 1, 1), dt.date(2952, 12, 31), XRef( bbid='EUR', ), ) ] mocker.patch.object(GsAssetApi, 'map_identifiers', side_effect=map_identifiers_default_mocker) mock_get_data = replace('gs_quant.data.dataset.Dataset.get_data', Mock()) mock_get_data.return_value = mock_meeting_absolute() actual_abs = tm_rates.policy_rate_term_structure( mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.ABSOLUTE, dt.date(2019, 12, 6) ) assert target['meeting_absolute'] == actual_abs.loc[dt.date(2020, 1, 23)] assert actual_abs.dataset_ids == (Dataset.GS.CENTRAL_BANK_WATCH,) actual_rel = tm_rates.policy_rate_term_structure( mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.RELATIVE, dt.date(2019, 12, 6) ) assert target['meeting_relative'] == actual_rel.loc[dt.date(2020, 1, 23)] assert actual_rel.dataset_ids == (Dataset.GS.CENTRAL_BANK_WATCH,) mock_get_data.return_value = mock_ois_spot() actual_spot = tm_rates.policy_rate_term_structure( mock_eur, tm_rates.EventType.SPOT, tm_rates.RateType.ABSOLUTE, dt.date(2019, 12, 6) ) assert target['spot'] == actual_spot.loc[dt.date(2019, 12, 6)] assert actual_spot.dataset_ids == (Dataset.GS.CENTRAL_BANK_WATCH,) with pytest.raises(MqError): tm_rates.policy_rate_term_structure(mock_eur, 'meeting') with pytest.raises(MqError): tm_rates.policy_rate_term_structure(mock_eur, tm_rates.EventType.MEETING, 'normalized', '2019-09-01') with pytest.raises(MqError): tm_rates.policy_rate_term_structure(mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.ABSOLUTE, 5) with pytest.raises(ValueError): tm_rates.policy_rate_term_structure( mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.ABSOLUTE, '01-09-2019' ) with pytest.raises(MqError): tm_rates.policy_rate_term_structure(mock_eur, tm_rates.EventType.SPOT, tm_rates.RateType.RELATIVE) with pytest.raises(NotImplementedError): tm_rates.policy_rate_term_structure( mock_eur, tm_rates.EventType.SPOT, tm_rates.RateType.ABSOLUTE, real_time=True ) mock_get_data = replace('gs_quant.data.dataset.Dataset.get_data', Mock()) mock_get_data.return_value = pd.DataFrame() assert_series_equal( tm_rates.policy_rate_term_structure(mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.ABSOLUTE), pd.Series(dtype=float, name='value'), ) mock_get_data = replace('gs_quant.timeseries.measures_rates.policy_rate_term_structure_rt', Mock()) mock_get_data.return_value = pd.DataFrame() assert tm_rates.policy_rate_term_structure( mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.ABSOLUTE, "Intraday" ).empty df_without_meeting_date = pd.DataFrame({'value': [1.5, 2.0], 'rateType': ['Meeting', 'Meeting']}) mock_get_data = replace('gs_quant.data.dataset.Dataset.get_data', Mock()) mock_get_data.return_value = df_without_meeting_date result = tm_rates.policy_rate_term_structure( mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.ABSOLUTE, dt.date(2019, 12, 6) ) assert_series_equal(result, pd.Series(dtype=float, name='value')) replace.restore() def test_policy_rate_expectation(mocker): target = { 'meeting_number_absolute': -0.004550907771, 'meeting_number_relative': -0.000028337246, 'meeting_date_relative': -0.000028337246, 'meeting_number_spot': -0.004522570525, } mock_eur = Currency('MARFAGXDQRWM07Y2', 'EUR') with DataContext(dt.date(2019, 12, 6), dt.date(2019, 12, 6)): replace = Replacer() xrefs = replace('gs_quant.timeseries.measures.GsAssetApi.get_asset_xrefs', Mock()) xrefs.return_value = [ GsTemporalXRef( dt.date(2019, 1, 1), dt.date(2952, 12, 31), XRef( bbid='EUR', ), ) ] mocker.patch.object(GsAssetApi, 'map_identifiers', side_effect=map_identifiers_default_mocker) mock_get_data = replace('gs_quant.data.dataset.Dataset.get_data', Mock()) mock_get_data.return_value = mock_meeting_spot() actual_num = tm_rates.policy_rate_expectation(mock_eur, tm_rates.EventType.SPOT, tm_rates.RateType.ABSOLUTE, 0) assert target['meeting_number_spot'] == actual_num.loc[dt.date(2019, 12, 6)] assert actual_num.dataset_ids == (Dataset.GS.CENTRAL_BANK_WATCH,) mocker.patch.object(Dataset, 'get_data', side_effect=get_data_policy_rate_expectation_mocker) actual_num = tm_rates.policy_rate_expectation( mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.ABSOLUTE, 2 ) assert target['meeting_number_absolute'] == actual_num.loc[dt.date(2019, 12, 6)] assert actual_num.dataset_ids == (Dataset.GS.CENTRAL_BANK_WATCH,) actual_date = tm_rates.policy_rate_expectation( mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.ABSOLUTE, dt.date(2020, 1, 23) ) assert target['meeting_number_absolute'] == actual_date.loc[dt.date(2019, 12, 6)] assert actual_date.dataset_ids == (Dataset.GS.CENTRAL_BANK_WATCH,) actual_num = tm_rates.policy_rate_expectation( mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.RELATIVE, 2 ) assert_allclose( [target['meeting_number_relative']], [actual_num.loc[dt.date(2019, 12, 6)]], rtol=1e-9, atol=1e-15 ) assert actual_num.dataset_ids == (Dataset.GS.CENTRAL_BANK_WATCH,) actual_num = tm_rates.policy_rate_expectation( mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.ABSOLUTE, 0 ) assert target['meeting_number_spot'] == actual_num.loc[dt.date(2019, 12, 6)] assert actual_num.dataset_ids == (Dataset.GS.CENTRAL_BANK_WATCH,) actual_date = tm_rates.policy_rate_expectation( mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.ABSOLUTE, '2019-10-24' ) assert target['meeting_number_spot'] == actual_date.loc[dt.date(2019, 12, 6)] assert actual_date.dataset_ids == (Dataset.GS.CENTRAL_BANK_WATCH,) mocker.patch.object( Dataset, 'get_data', side_effect=[mock_meeting_expectation(), mock_empty_market_data_response()] ) with pytest.raises(MqError): tm_rates.policy_rate_expectation(mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.RELATIVE, 2) with pytest.raises(MqError): tm_rates.policy_rate_expectation(mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.ABSOLUTE, 5.5) with pytest.raises(MqError): tm_rates.policy_rate_expectation(mock_eur, 'meeting', tm_rates.RateType.ABSOLUTE, 5) with pytest.raises(MqError): tm_rates.policy_rate_expectation(mock_eur, tm_rates.EventType.MEETING, 'normalized', dt.date(2019, 9, 1)) with pytest.raises(MqError): tm_rates.policy_rate_expectation(mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.RELATIVE, -2) mock_get_data = replace('gs_quant.data.dataset.Dataset.get_data', Mock()) mock_get_data.return_value = pd.DataFrame() with pytest.raises(MqError): tm_rates.policy_rate_expectation(mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.ABSOLUTE, 2) mock_get_data = replace('gs_quant.timeseries.measures_rates.policy_rate_expectation_rt', Mock()) mock_get_data.return_value = pd.DataFrame() assert tm_rates.policy_rate_expectation( mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.ABSOLUTE, 2, real_time=True ).empty replace.restore() def mock_policy_rt_spot(): data_dict = MarketDataResponseFrame( { 'assetId': ['MAV2B3A3D0R369QE'], 'pricingLocation': ['LDN'], 'csaTerms': ['EUR-EuroSTR'], 'rate': [-0.0049], 'effectiveDate': [dt.date(2022, 3, 23)], 'terminationDate': [dt.date(2022, 4, 20)], 'annuity': [-778.1652098], 'updateTime': [dt.datetime(2022, 4, 14, 0, 0, 0)], 'currency': ['EUR'], }, index=pd.Index([dt.datetime(2022, 4, 14, 0, 0, 0)], name='time'), ) data_dict.dataset_ids = _test_datasets return data_dict def mock_policy_rate_expectation_rt_meeting(): data_dict = MarketDataResponseFrame( { 'assetId': ['MA9PXTY5DC1JHV5Y'], 'pricingLocation': ['LDN'], 'csaTerms': ['EUR-EuroSTR'], 'rate': [-0.005337848], 'effectiveDate': [dt.date(2022, 7, 27)], 'terminationDate': [dt.date(2022, 9, 14)], 'annuity': [-1169.016923], 'updateTime': [dt.datetime(2022, 4, 14, 0, 0, 0)], 'currency': ['EUR'], }, index=pd.Index([dt.datetime(2022, 4, 14, 0, 0, 0)], name='time'), ) data_dict.dataset_ids = _test_datasets return data_dict def mock_policy_term_rt_meeting(): data_dict = MarketDataResponseFrame( { 'assetId': ['MANRCY4GA0WWT1QC', 'MA9PXTY5DC1JHV5Y'], 'pricingLocation': ['LDN', 'LDN'], 'csaTerms': ['EUR-EuroSTR', 'EUR-EuroSTR'], 'rate': [-0.004585702, -0.005337848], 'effectiveDate': [dt.date(2022, 6, 15), dt.date(2022, 7, 27)], 'terminationDate': [dt.date(2022, 7, 27), dt.date(2022, 9, 14)], 'annuity': [-1364.124069, -1169.016923], 'updateTime': [dt.datetime(2022, 4, 14, 0, 0, 0), dt.datetime(2022, 4, 14, 0, 0, 0)], 'currency': ['EUR', 'EUR'], }, index=pd.Index([dt.datetime(2022, 4, 14, 0, 0, 0), dt.datetime(2022, 4, 14, 0, 0, 0)], name='time'), ) data_dict.dataset_ids = _test_datasets return data_dict def mock_policy_term_rt_meeting_series(): data_dict = MarketDataResponseFrame( { 'assetId': ['MA9PXTY5DC1JHV5Y'], 'pricingLocation': ['LDN'], 'csaTerms': ['EUR-EuroSTR'], 'rate': [-0.005337848], 'effectiveDate': [dt.date(2022, 7, 27)], 'terminationDate': [dt.date(2022, 9, 14)], 'annuity': [-1169.016923], 'updateTime': [dt.datetime(2022, 4, 14, 0, 0, 0)], 'currency': ['EUR'], }, index=pd.Index([dt.datetime(2022, 4, 14, 0, 0, 0), dt.datetime(2022, 4, 14, 0, 0, 0)], name='time'), ) data_dict.dataset_ids = _test_datasets return data_dict def mock_policy_rate_empty_join_df(): data_dict = MarketDataResponseFrame( { 'assetId': ['MA000', 'MA000'], 'pricingLocation': ['USD', 'USD'], 'csaTerms': ['USD-SOFR-1', 'USD-SOFR-1'], 'rate': [-0.004585702, -0.005337848], 'effectiveDate': [dt.date(2022, 6, 15), dt.date(2022, 7, 27)], 'terminationDate': [dt.date(2022, 7, 27), dt.date(2022, 9, 14)], 'annuity': [-1364.124069, -1169.016923], 'updateTime': [dt.datetime(2022, 4, 14, 0, 0, 0), dt.datetime(2022, 4, 14, 0, 0, 0)], 'currency': ['EUR', 'EUR'], }, index=pd.Index([dt.datetime(2022, 4, 14, 0, 0, 0), dt.datetime(2022, 4, 14, 0, 0, 0)], name='time'), ) data_dict.dataset_ids = _test_datasets return data_dict def get_data_policy_rate_term_rt_series_mocker(**kwargs) -> pd.DataFrame: if len(kwargs['assetId']) == 1: return mock_policy_rt_spot() else: return mock_policy_term_rt_meeting_series() def get_data_policy_rate_term_rt_mocker(**kwargs) -> pd.DataFrame: if len(kwargs['assetId']) == 1: return mock_policy_rt_spot() else: return mock_policy_term_rt_meeting() def get_data_empty_spot_mocker(**kwargs) -> pd.DataFrame: if len(kwargs['assetId']) == 1: return pd.DataFrame() else: return mock_policy_term_rt_meeting() def get_data_empty_join_mocker(**kwargs) -> pd.DataFrame: if len(kwargs['assetId']) == 1: return mock_policy_rate_empty_join_df() else: return mock_policy_term_rt_meeting() def get_cb_swap_assets_mocker(allow_many=True, **kwargs) -> list: if isinstance(kwargs['asset_parameters_termination_date'], str) and isinstance( kwargs['asset_parameters_effective_date'], str ): return ['MAV2B3A3D0R369QE'] else: return [ 'MANRCY4GA0WWT1QC', 'MA9PXTY5DC1JHV5Y', 'MAACR86DGE1CMK9Y', 'MASSRBDHKSSDAS36', 'MAZAH4WR524BQ7ZN', 'MAY2RN50CWETF7HV', 'MAV2B3A3D0R369QE', ] def test_policy_rate_term_structure_rt(mocker): target = {'meeting_absolute': -0.005337848, 'meeting_relative': -0.00043784800000000023, 'spot': -0.0049} mock_eur = Currency('MARFAGXDQRWM07Y2', 'EUR') mock_zar = Currency("MA1", "ZAR") with DataContext(dt.date(2020, 2, 14), dt.date(2020, 3, 14)): replace = Replacer() xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'ZAR' cbd = replace('gs_quant.timeseries.measures._get_custom_bd', Mock()) cbd.return_value = pd.tseries.offsets.BusinessDay() replace('gs_quant.timeseries.measures_rates._get_tdapi_rates_assets', get_cb_swap_assets_mocker) mocker.patch.object(Dataset, 'get_data', side_effect=get_data_policy_rate_term_rt_mocker) with pytest.raises(MqValueError): tm_rates.policy_rate_term_structure_rt( mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.RELATIVE, None ) replace.restore() with DataContext(dt.date(2021, 4, 14), dt.date(2024, 4, 14)): replace = Replacer() xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'ZAR' cbd = replace('gs_quant.timeseries.measures._get_custom_bd', Mock()) cbd.return_value = pd.tseries.offsets.BusinessDay() replace('gs_quant.timeseries.measures_rates._get_tdapi_rates_assets', get_cb_swap_assets_mocker) with pytest.raises(MqError): tm_rates.policy_rate_term_structure_rt( mock_zar, tm_rates.EventType.MEETING, tm_rates.RateType.ABSOLUTE, ) xrefs.return_value = 'EUR' with pytest.raises(MqError): tm_rates.policy_rate_term_structure_rt( mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.ABSOLUTE, tm_rates.BenchmarkTypeCB.SOFR ) with pytest.raises(MqError): tm_rates.policy_rate_term_structure_rt(mock_eur, tm_rates.EventType.EOY, tm_rates.RateType.ABSOLUTE) with pytest.raises(MqError): tm_rates.policy_rate_term_structure_rt(mock_eur, tm_rates.EventType.SPOT, tm_rates.RateType.RELATIVE) mock_get_data = replace('gs_quant.data.dataset.Dataset.get_data', Mock()) mock_get_data.return_value = mock_policy_term_rt_meeting() mock_get_data_coverage = replace('gs_quant.data.dataset.Dataset.get_coverage', Mock()) mock_get_data_coverage.return_value = pd.DataFrame(columns=['exchange']) actual_abs = tm_rates.policy_rate_term_structure_rt( mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.ABSOLUTE, None ) assert target['meeting_absolute'] == actual_abs.loc[pd.Timestamp(dt.date(2022, 7, 27))] mocker.patch.object(Dataset, 'get_data', side_effect=get_data_policy_rate_term_rt_mocker) actual_rel = tm_rates.policy_rate_term_structure_rt( mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.RELATIVE, None ) assert target['meeting_relative'] == actual_rel.loc[pd.Timestamp(dt.date(2022, 7, 27))] mock_get_data.return_value = mock_policy_rt_spot() actual_spot = tm_rates.policy_rate_term_structure_rt( mock_eur, tm_rates.EventType.SPOT, tm_rates.RateType.ABSOLUTE, None ) assert target['spot'] == actual_spot.loc[dt.datetime(2022, 4, 14).isoformat()] mocker.patch.object(Dataset, 'get_data', side_effect=get_data_empty_join_mocker) assert tm_rates.policy_rate_term_structure_rt( mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.RELATIVE ).empty mocker.patch.object(Dataset, 'get_data', side_effect=get_data_empty_spot_mocker) with pytest.raises(MqError): tm_rates.policy_rate_term_structure_rt(mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.RELATIVE) mock_get_data = replace('gs_quant.data.dataset.Dataset.get_data', Mock()) mock_get_data.return_value = pd.DataFrame() with pytest.raises(MqError): tm_rates.policy_rate_term_structure_rt(mock_eur, tm_rates.EventType.SPOT, tm_rates.RateType.ABSOLUTE) assert tm_rates.policy_rate_term_structure_rt( mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.RELATIVE ).empty replace.restore() def get_data_policy_rate_exp_rt_mocker(**kwargs) -> pd.DataFrame: if kwargs['assetId'] == 'MA000SPOT': return mock_policy_rt_spot() else: return mock_policy_rate_expectation_rt_meeting() def policy_exp_empty_spot_mocker(**kwargs) -> pd.DataFrame: if kwargs['assetId'] == 'MA000SPOT': return pd.DataFrame() else: return mock_policy_rate_expectation_rt_meeting() def test_get_swap_from_meeting_date(mocker): eur = CurrencyEnum.EUR benchmark = tm_rates.BenchmarkTypeCB.EUROSTR with pytest.raises(MqValueError): tm_rates._get_swap_from_meeting_date(eur, benchmark, 'ecb30') with pytest.raises(MqValueError): tm_rates._get_swap_from_meeting_date(eur, benchmark, 'frb1') replace = Replacer() cbd = replace('gs_quant.timeseries.measures_rates.get_cb_meeting_swap', Mock()) cbd.return_value = 'MA123' assert 'MA123' == tm_rates._get_swap_from_meeting_date(eur, benchmark, 0) assert 'MA123' == tm_rates._get_swap_from_meeting_date(eur, benchmark, 1) assert 'MA123' == tm_rates._get_swap_from_meeting_date(eur, benchmark, 'ecb0') assert 'MA123' == tm_rates._get_swap_from_meeting_date(eur, benchmark, 'ecb1') replace.restore() def test_policy_rate_expectation_rt(mocker): target = {'meeting_absolute': -0.005337848, 'meeting_relative': -0.00043784800000000023, 'spot': -0.0049} mock_eur = Currency('MARFAGXDQRWM07Y2', 'EUR') mock_zar = Currency("MA1", "ZAR") with DataContext(dt.date(2020, 2, 14), dt.date(2020, 3, 14)): replace = Replacer() xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'ZAR' cbd = replace('gs_quant.timeseries.measures._get_custom_bd', Mock()) cbd.return_value = pd.tseries.offsets.BusinessDay() mock_swap = replace('gs_quant.timeseries.measures_rates._get_tdapi_rates_assets', Mock()) mock_swap.return_value = 'MA000SPOT' mock_swap = replace('gs_quant.timeseries.measures_rates._get_swap_from_meeting_date', Mock()) mock_swap.return_value = 'MAV2B3A3D0R369QE' with pytest.raises(MqError): tm_rates.policy_rate_expectation_rt(mock_eur, tm_rates.EventType.SPOT, tm_rates.RateType.ABSOLUTE) replace.restore() with DataContext(dt.date(2021, 4, 14), dt.date(2024, 4, 14)): replace = Replacer() xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'ZAR' cbd = replace('gs_quant.timeseries.measures._get_custom_bd', Mock()) cbd.return_value = pd.tseries.offsets.BusinessDay() mock_swap = replace('gs_quant.timeseries.measures_rates._get_tdapi_rates_assets', Mock()) mock_swap.return_value = 'MA000SPOT' mock_swap = replace('gs_quant.timeseries.measures_rates._get_swap_from_meeting_date', Mock()) mock_swap.return_value = 'MAV2B3A3D0R369QE' with pytest.raises(MqError): tm_rates.policy_rate_expectation_rt( mock_zar, tm_rates.EventType.MEETING, tm_rates.RateType.ABSOLUTE, ) xrefs.return_value = 'EUR' mocker.patch.object(Dataset, 'get_data', side_effect=policy_exp_empty_spot_mocker) with pytest.raises(MqError): tm_rates.policy_rate_expectation_rt(mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.RELATIVE) mocker.patch.object(Dataset, 'get_data', side_effect=get_data_policy_rate_exp_rt_mocker) with pytest.raises(MqError): tm_rates.policy_rate_expectation_rt(mock_eur, tm_rates.EventType.EOY, tm_rates.RateType.ABSOLUTE) actual_abs = tm_rates.policy_rate_expectation_rt( mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.ABSOLUTE, 2 ) assert target['meeting_absolute'] == actual_abs.loc[dt.datetime(2022, 4, 14)] actual_rel = tm_rates.policy_rate_expectation_rt( mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.RELATIVE, 2 ) assert target['meeting_relative'] == actual_rel.loc[dt.datetime(2022, 4, 14)] mock_swap.return_value = 'MA000SPOT' actual_spot = tm_rates.policy_rate_expectation_rt(mock_eur, tm_rates.EventType.SPOT, tm_rates.RateType.ABSOLUTE) assert target['spot'] == actual_spot.loc[dt.datetime(2022, 4, 14).isoformat()] with pytest.raises(MqError): tm_rates.policy_rate_expectation_rt(mock_eur, tm_rates.EventType.SPOT, tm_rates.RateType.RELATIVE) mock_get_data = replace('gs_quant.data.dataset.Dataset.get_data', Mock()) mock_get_data.return_value = pd.DataFrame() with pytest.raises(MqError): tm_rates.policy_rate_expectation_rt(mock_eur, tm_rates.EventType.SPOT, tm_rates.RateType.ABSOLUTE) with pytest.raises(MqError): tm_rates.policy_rate_expectation_rt(mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.RELATIVE) with pytest.raises(MqError): tm_rates.policy_rate_expectation_rt(mock_eur, tm_rates.EventType.SPOT, tm_rates.RateType.RELATIVE) mock_get_data = replace('gs_quant.data.dataset.Dataset.get_data', Mock()) mock_get_data.return_value = pd.Series(dtype=float) with pytest.raises(MqError): tm_rates.policy_rate_expectation_rt(mock_eur, tm_rates.EventType.MEETING, tm_rates.RateType.RELATIVE) replace.restore() def test_get_cb_meeting_swap(mocker): replace = Replacer() mock_swap = replace('gs_quant.timeseries.measures_rates._get_tdapi_rates_assets', Mock()) mock_swap.return_value = 'MA000SPOT' with pytest.raises(MqValueError): tm_rates.get_cb_meeting_swap(CurrencyEnum.USD, tm_rates.BenchmarkTypeCB.SOFR, '1y', '125y') assert tm_rates.get_cb_meeting_swap(CurrencyEnum.USD, tm_rates.BenchmarkTypeCB.SOFR, '0b', '1y') == 'MA000SPOT' replace.restore() if __name__ == '__main__': pytest.main(args=["test_measures_rates.py"]) ================================================ FILE: gs_quant/test/timeseries/test_measures_reports.py ================================================ """ Copyright 2020 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import copy import datetime as dt import math import pandas as pd import pytest from testfixtures import Replacer from testfixtures.mock import Mock import gs_quant.timeseries.measures_reports as mr from gs_quant.api.gs.assets import GsTemporalXRef from gs_quant.api.gs.data import MarketDataResponseFrame from gs_quant.common import ReportParameters, XRef from gs_quant.data.core import DataContext from gs_quant.errors import MqValueError from gs_quant.markets.report import PerformanceReport, ThematicReport, CustomAUMDataPoint from gs_quant.markets.securities import Stock from gs_quant.models.risk_model import FactorRiskModel as Factor_Risk_Model from gs_quant.target.portfolios import RiskAumSource from gs_quant.target.reports import Report, PositionSourceType, ReportType from gs_quant.target.risk_models import RiskModel, RiskModelCoverage, RiskModelTerm, RiskModelUniverseIdentifier risk_model = RiskModel( coverage=RiskModelCoverage.Country, id_='model_id', name='Fake Risk Model', term=RiskModelTerm.Long, universe_identifier=RiskModelUniverseIdentifier.gsid, vendor='GS', version=1.0, ) factor_risk_report = Report( position_source_id='position source id', position_source_type=PositionSourceType.Portfolio, type_=ReportType.Portfolio_Factor_Risk, id_='report_id', parameters=ReportParameters(risk_model='risk_model_id'), status='new', ) asset_factor_risk_report = Report( position_source_id='position source id', position_source_type=PositionSourceType.Asset, type_=ReportType.Portfolio_Factor_Risk, id_='report_id', parameters=ReportParameters(risk_model='risk_model_id'), status='new', ) ppa_report = PerformanceReport( position_source_id='position source id', position_source_type=PositionSourceType.Portfolio, type_=ReportType.Portfolio_Performance_Analytics, id_='report_id', parameters=ReportParameters(risk_model='risk_model_id'), status='new', ) factor_data = [ { 'date': '2020-11-23', 'reportId': 'report_id', 'factor': 'Factor Name', 'factorCategory': 'CNT', 'pnl': 11.23, 'exposure': -11.23, 'proportionOfRisk': 1, }, { 'date': '2020-11-24', 'reportId': 'report_id', 'factor': 'Factor Name', 'factorCategory': 'CNT', 'pnl': 11.24, 'exposure': -11.24, 'proportionOfRisk': 2, }, { 'date': '2020-11-25', 'reportId': 'report_id', 'factor': 'Factor Name', 'factorCategory': 'CNT', 'pnl': 11.25, 'exposure': -11.25, 'proportionOfRisk': 3, }, { 'date': '2020-11-23', 'reportId': 'report_id', 'factor': 'Total', 'factorCategory': 'CNT', 'pnl': 19.23, 'exposure': -11.23, 'proportionOfRisk': 1, }, { 'date': '2020-11-24', 'reportId': 'report_id', 'factor': 'Total', 'factorCategory': 'CNT', 'pnl': 14.24, 'exposure': -11.24, 'proportionOfRisk': 2, }, { 'date': '2020-11-25', 'reportId': 'report_id', 'factor': 'Total', 'factorCategory': 'CNT', 'pnl': 21.25, 'exposure': -11.25, 'proportionOfRisk': 3, }, ] factor_exposure_data = [ { 'date': '2024-03-14', 'reportId': 'report_id', 'factor': 'Factor_1', 'factorCategory': 'Style', 'pnl': 19.23, 'exposure': 100, 'proportionOfRisk': 1, }, { 'date': '2024-03-14', 'reportId': 'report_id', 'factor': 'Factor_2', 'factorCategory': 'Style', 'pnl': 14.24, 'exposure': 100, 'proportionOfRisk': 2, }, { 'date': '2024-03-14', 'reportId': 'report_id', 'factor': 'Factor_3', 'factorCategory': 'Style', 'pnl': 21.25, 'exposure': 100, 'proportionOfRisk': 3, }, ] factor_return_data = [ {"date": "2024-03-13", "factor": "Factor_1", "return": 2}, {"date": "2024-03-13", "factor": "Factor_2", "return": 3}, {"date": "2024-03-13", "factor": "Factor_3", "return": 2}, {"date": "2024-03-14", "factor": "Factor_1", "return": 2}, {"date": "2024-03-14", "factor": "Factor_2", "return": 3}, {"date": "2024-03-14", "factor": "Factor_3", "return": 2}, ] aggregate_factor_data = [ { 'date': '2020-11-23', 'reportId': 'report_id', 'factor': 'Factor', 'factorCategory': 'CNT', 'pnl': 11.23, 'exposure': -11.23, 'proportionOfRisk': 1, 'dailyRisk': 1, 'annualRisk': 1, }, { 'date': '2020-11-24', 'reportId': 'report_id', 'factor': 'Factor', 'factorCategory': 'CNT', 'pnl': 11.24, 'exposure': -11.24, 'proportionOfRisk': 2, 'dailyRisk': 2, 'annualRisk': 2, }, { 'date': '2020-11-25', 'reportId': 'report_id', 'factor': 'Factor', 'factorCategory': 'CNT', 'pnl': 11.25, 'exposure': -11.25, 'proportionOfRisk': 3, 'dailyRisk': 3, 'annualRisk': 3, }, ] constituents_data_l_s = { 'assetId': ["MA1", "MA1", "MA1", "MA2", "MA2", "MA2"], 'quantity': [-1.0, -2.0, -3.0, 1.0, 2.0, 3.0], 'netExposure': [-1.0, -2.0, -3.0, 1.0, 2.0, 3.0], 'pnl': [0.0, -1.0, -1.0, 0.0, 1.0, 1.0], 'date': ['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-02', '2020-01-03', '2020-01-04'], } pnl_data_l_s = { 'quantity': [-1.0, -2.0, -3.0, -1.0, -2.0, -3.0, 1.0, 2.0, 3.0, 1.0, 2.0, 3.0], 'pnl': [0.0, -1.0, -1.0, 0.0, -1.0, -1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0], 'date': [ '2020-01-02', '2020-01-03', '2020-01-04', '2020-01-02', '2020-01-03', '2020-01-04', '2020-01-02', '2020-01-03', '2020-01-04', '2020-01-02', '2020-01-03', '2020-01-04', ], } constituents_data = { 'netExposure': [1.0, 2.0, 3.0], 'assetId': ["MA", "MA", "MA"], 'quantity': [1.0, 1.0, 1.0], 'pnl': [0.0, 1.0, 1.0], 'date': ['2020-01-02', '2020-01-03', '2020-01-04'], } constituents_data_s = { 'netExposure': [-1.0, -2.0, -3.0], 'assetId': ["MA", "MA", "MA"], 'quantity': [-1.0, -1.0, -1.0], 'pnl': [0.0, 1.0, 1.0], 'date': ['2020-01-02', '2020-01-03', '2020-01-04'], } thematic_data = [ { "date": "2021-07-12", "reportId": "PTAID", "basketId": "MA01GPR89HZF1FZ5", "region": "Asia", "grossExposure": 3.448370345015856e8, "thematicExposure": 2, "thematicBeta": 1, "updateTime": "2021-07-20T23:43:38Z", }, { "date": "2021-07-13", "reportId": "PTAID", "basketId": "MA01GPR89HZF1FZ5", "region": "Asia", "grossExposure": 3.375772519907556e8, "thematicExposure": 2, "thematicBeta": 1, "updateTime": "2021-07-20T23:43:38Z", }, { "date": "2021-07-14", "reportId": "PTAID", "basketId": "MA01GPR89HZF1FZ5", "region": "Asia", "grossExposure": 3.321189950666118e8, "thematicExposure": 2, "thematicBeta": 1, "updateTime": "2021-07-20T23:43:38Z", }, { "date": "2021-07-15", "reportId": "PTAID", "basketId": "MA01GPR89HZF1FZ5", "region": "Asia", "grossExposure": 3.274071805135091e8, "thematicExposure": 2, "thematicBeta": 1, "updateTime": "2021-07-20T23:43:38Z", }, ] def compute_geometric_aggregation_calculations(aum: dict, pnl: dict, dates: list): daily_returns = [0] for i in range(2, len(dates)): daily_returns.append(pnl[dates[i]] / aum[dates[i - 1]]) expected_values = [daily_returns[0]] for i in range(1, len(daily_returns)): expected_values.append((1 + expected_values[i - 1]) * (1 + daily_returns[i]) - 1) return [v * 100 for v in expected_values] def mock_risk_model(): risk_model = RiskModel( coverage=RiskModelCoverage.Country, id_='model_id', name='Fake Risk Model', term=RiskModelTerm.Long, universe_identifier=RiskModelUniverseIdentifier.gsid, vendor='GS', version=1.0, ) replace = Replacer() # mock getting risk model entity() mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model', Mock()) mock.return_value = risk_model actual = Factor_Risk_Model.get(model_id='model_id') replace.restore() return actual def test_factor_exposure(): replace = Replacer() # mock getting risk model entity() mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model', Mock()) mock.return_value = risk_model mock = replace('gs_quant.api.gs.reports.GsReportApi.get_report', Mock()) mock.return_value = factor_risk_report # mock getting report factor data mock = replace('gs_quant.api.gs.reports.GsReportApi.get_factor_risk_report_results', Mock()) mock.return_value = factor_data # mock getting risk model dates mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model_dates', Mock()) mock.return_value = ['2010-01-01'] # mock getting risk model factor category mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_data', Mock()) mock.return_value = {'results': [{'factorData': [{'factorId': 'factor_id', 'factorCategory': 'Factor Name'}]}]} # mock getting risk model factor entity mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_factor_data', Mock()) mock.return_value = [ {'identifier': 'factor_id', 'type': 'Factor', 'name': 'Factor Name', 'factorCategory': 'Factor Name'} ] with DataContext(dt.date(2020, 11, 23), dt.date(2020, 11, 25)): actual = mr.factor_exposure('report_id', 'Factor Name') assert all(actual.values == [-11.23, -11.24, -11.25]) with pytest.raises(MqValueError): mr.factor_exposure('report_id', 'Wrong Factor Name') replace.restore() def test_factor_exposure_percent(): replace = Replacer() # mock getting risk model entity() mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model', Mock()) mock.return_value = risk_model mock = replace('gs_quant.api.gs.reports.GsReportApi.get_report', Mock()) mock.return_value = factor_risk_report # mock getting report factor data mock = replace('gs_quant.api.gs.reports.GsReportApi.get_factor_risk_report_results', Mock()) mock.return_value = factor_data # mock getting risk model dates mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model_dates', Mock()) mock.return_value = ['2010-01-01'] # mock getting risk model factor category mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_data', Mock()) mock.return_value = {'results': [{'factorData': [{'factorId': 'factor_id', 'factorCategory': 'Factor Name'}]}]} # mock getting risk model factor entity mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_factor_data', Mock()) mock.return_value = [ {'identifier': 'factor_id', 'type': 'Factor', 'name': 'Factor Name', 'factorCategory': 'Factor Name'} ] # mock getting performance report mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = ppa_report # mock getting aum source mock = replace('gs_quant.markets.report.PerformanceReport.get_aum_source', Mock()) mock.return_value = RiskAumSource.Custom_AUM with DataContext(dt.date(2020, 11, 23), dt.date(2020, 11, 25)): # mock getting aum mock = replace('gs_quant.markets.report.PerformanceReport.get_custom_aum', Mock()) mock.return_value = [ CustomAUMDataPoint(date=dt.date(2020, 11, 23), aum=2), CustomAUMDataPoint(date=dt.date(2020, 11, 24), aum=2), CustomAUMDataPoint(date=dt.date(2020, 11, 25), aum=2), ] actual = mr.factor_exposure('report_id', 'Factor Name', 'Percent') assert all(actual.values == [-11.23 * 50, -11.24 * 50, -11.25 * 50]) with pytest.raises(MqValueError): # mock getting aum with missing data mock = replace('gs_quant.markets.report.PerformanceReport.get_custom_aum', Mock()) mock.return_value = [ CustomAUMDataPoint(date=dt.date(2020, 11, 23), aum=2), CustomAUMDataPoint(date=dt.date(2020, 11, 25), aum=2), ] mr.factor_exposure('report_id', 'Factor Name', 'Percent') replace.restore() def test_factor_pnl(): replace = Replacer() # mock getting risk model entity() mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model', Mock()) mock.return_value = risk_model mock = replace('gs_quant.api.gs.reports.GsReportApi.get_report', Mock()) mock.return_value = factor_risk_report # mock getting risk model dates mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model_dates', Mock()) mock.return_value = ['2010-01-01'] # mock getting risk model factor category mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_data', Mock()) mock.return_value = {'results': [{'factorData': [{'factorId': 'factor_id', 'factorCategory': 'Factor Name'}]}]} # mock getting risk model factor entity mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_factor_data', Mock()) mock.return_value = [ {'identifier': 'factor_id', 'type': 'Factor', 'name': 'Factor Name', 'factorCategory': 'Factor Name'} ] with DataContext(dt.date(2020, 11, 23), dt.date(2020, 11, 25)): # mock getting report factor data mock = replace('gs_quant.api.gs.reports.GsReportApi.get_factor_risk_report_results', Mock()) mock.return_value = factor_data actual = mr.factor_pnl('report_id', 'Factor Name') assert all(actual.values == [11.23, 11.24, 11.25]) with DataContext(dt.date(2020, 11, 22), dt.date(2020, 11, 25)): # mock getting report factor data with first day set to 0 factor_data_copy = copy.copy(factor_data) factor_data_copy.insert( 0, { 'date': '2020-11-22', 'reportId': 'report_id', 'factor': 'Factor Name', 'factorCategory': 'CNT', 'pnl': 0, 'exposure': -11.23, 'proportionOfRisk': 1, }, ) mock = replace('gs_quant.api.gs.reports.GsReportApi.get_factor_risk_report_results', Mock()) mock.return_value = factor_data_copy actual = mr.factor_pnl('report_id', 'Factor Name') assert all(actual.values == [0.0, 11.23, 11.24, 11.25]) with pytest.raises(MqValueError): mr.factor_pnl('report_id', 'Wrong Factor Name') replace.restore() def test_factor_pnl_percent(): replace = Replacer() aum = {'2020-11-22': 200, '2020-11-23': 400, '2020-11-24': 400, '2020-11-25': 400} # mock getting risk model entity() mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model', Mock()) mock.return_value = risk_model mock = replace('gs_quant.api.gs.reports.GsReportApi.get_report', Mock()) mock.return_value = factor_risk_report # mock getting report factor data mock = replace('gs_quant.api.gs.reports.GsReportApi.get_factor_risk_report_results', Mock()) mock.return_value = factor_data # mock getting risk model dates mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model_dates', Mock()) mock.return_value = ['2010-01-01'] # mock getting risk model factor category mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_data', Mock()) mock.return_value = {'results': [{'factorData': [{'factorId': 'factor_id', 'factorCategory': 'Factor Name'}]}]} # mock getting risk model factor entity mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_factor_data', Mock()) mock.return_value = [ {'identifier': 'factor_id', 'type': 'Factor', 'name': 'Factor Name', 'factorCategory': 'Factor Name'} ] mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum # Each team uses pnl today/aum yesterday expected_values = [0, 0, 2.8844318, 5.7471343] with DataContext(dt.date(2020, 11, 23), dt.date(2020, 11, 25)): # mock getting aum source mock = replace('gs_quant.markets.report.PerformanceReport.get_aum_source', Mock()) mock.return_value = RiskAumSource.Long # mock getting performance report mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport(id='ID') # mock getting aum mock = replace('gs_quant.markets.report.PerformanceReport.get_long_exposure', Mock()) mock.return_value = pd.DataFrame.from_dict({'date': aum.keys(), 'longExposure': aum.values()}) actual = mr.factor_pnl('report_id', 'Factor Name', 'Percent') assert all([a == pytest.approx(e) for a, e in zip(actual.values, expected_values)]) with DataContext(dt.date(2020, 11, 23), dt.date(2020, 11, 25)): # mock getting aum source mock = replace('gs_quant.markets.report.PerformanceReport.get_aum_source', Mock()) mock.return_value = RiskAumSource.Short # mock getting performance report mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport(id='ID') # mock getting aum mock = replace('gs_quant.markets.report.PerformanceReport.get_short_exposure', Mock()) mock.return_value = pd.DataFrame.from_dict({'date': aum.keys(), 'shortExposure': aum.values()}) actual = mr.factor_pnl('report_id', 'Factor Name', 'Percent') assert all([a == pytest.approx(e) for a, e in zip(actual.values, expected_values)]) with DataContext(dt.date(2020, 11, 23), dt.date(2020, 11, 25)): # mock getting aum source mock = replace('gs_quant.markets.report.PerformanceReport.get_aum_source', Mock()) mock.return_value = RiskAumSource.Gross # mock getting performance report mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport(id='ID') # mock getting aum mock = replace('gs_quant.markets.report.PerformanceReport.get_gross_exposure', Mock()) mock.return_value = pd.DataFrame.from_dict({'date': aum.keys(), 'grossExposure': aum.values()}) actual = mr.factor_pnl('report_id', 'Factor Name', 'Percent') assert all([a == pytest.approx(e) for a, e in zip(actual.values, expected_values)]) with DataContext(dt.date(2020, 11, 23), dt.date(2020, 11, 25)): # mock getting aum source mock = replace('gs_quant.markets.report.PerformanceReport.get_aum_source', Mock()) mock.return_value = RiskAumSource.Net # mock getting performance report mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport(id='ID') # mock getting aum mock = replace('gs_quant.markets.report.PerformanceReport.get_net_exposure', Mock()) mock.return_value = pd.DataFrame.from_dict({'date': aum.keys(), 'netExposure': aum.values()}) actual = mr.factor_pnl('report_id', 'Factor Name', 'Percent') assert all([a == pytest.approx(e) for a, e in zip(actual.values, expected_values)]) replace.restore() def test_asset_factor_pnl_percent(): replace = Replacer() # mock getting risk model entity() mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model', Mock()) mock.return_value = risk_model mock = replace('gs_quant.api.gs.reports.GsReportApi.get_report', Mock()) mock.return_value = asset_factor_risk_report # mock getting report factor data mock = replace('gs_quant.api.gs.reports.GsReportApi.get_factor_risk_report_results', Mock()) mock.return_value = factor_data # mock getting risk model dates mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model_dates', Mock()) mock.return_value = ['2010-01-01'] # mock getting risk model factor category mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_data', Mock()) mock.return_value = {'results': [{'factorData': [{'factorId': 'factor_id', 'factorCategory': 'Factor Name'}]}]} # mock getting risk model factor entity mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_factor_data', Mock()) mock.return_value = [ {'identifier': 'factor_id', 'type': 'Factor', 'name': 'Factor Name', 'factorCategory': 'Factor Name'} ] with pytest.raises(MqValueError): mr.factor_pnl('report_id', 'Factor Name', 'Percent') replace.restore() def test_factor_proportion_of_risk(): replace = Replacer() # mock getting risk model entity() mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model', Mock()) mock.return_value = risk_model mock = replace('gs_quant.api.gs.reports.GsReportApi.get_report', Mock()) mock.return_value = factor_risk_report # mock getting report factor data mock = replace('gs_quant.api.gs.reports.GsReportApi.get_factor_risk_report_results', Mock()) mock.return_value = factor_data # mock getting risk model dates mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model_dates', Mock()) mock.return_value = ['2010-01-01'] # mock getting risk model factor category mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_data', Mock()) mock.return_value = {'results': [{'factorData': [{'factorId': 'factor_id', 'factorCategory': 'Factor Name'}]}]} # mock getting risk model factor entity mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_factor_data', Mock()) mock.return_value = [ {'identifier': 'factor_id', 'type': 'Factor', 'name': 'Factor Name', 'factorCategory': 'Factor Name'} ] with DataContext(dt.date(2020, 11, 23), dt.date(2020, 11, 25)): actual = mr.factor_proportion_of_risk('report_id', 'Factor Name') assert all(actual.values == [1, 2, 3]) with pytest.raises(MqValueError): mr.factor_proportion_of_risk('report_id', 'Wrong Factor Name') replace.restore() def test_get_factor_data(): replace = Replacer() mock = replace('gs_quant.api.gs.reports.GsReportApi.get_report', Mock()) mock.return_value = ppa_report with pytest.raises(MqValueError): mr.factor_proportion_of_risk('report_id', 'Factor Name') replace.restore() def test_aggregate_factor_support(): replace = Replacer() # mock getting risk model entity() mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model', Mock()) mock.return_value = risk_model mock = replace('gs_quant.api.gs.reports.GsReportApi.get_report', Mock()) mock.return_value = factor_risk_report # mock getting report factor data mock = replace('gs_quant.api.gs.reports.GsReportApi.get_factor_risk_report_results', Mock()) mock.return_value = aggregate_factor_data # mock getting risk model dates mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model_dates', Mock()) mock.return_value = ['2010-01-01'] # mock getting risk model factor category mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_data', Mock()) mock.return_value = {'results': [{'factorData': [{'factorId': 'factor_id', 'factorCategory': 'Factor Name'}]}]} # mock getting risk model factor entity mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_factor_data', Mock()) mock.return_value = [ {'identifier': 'factor_id', 'type': 'Factor', 'name': 'Factor Name', 'factorCategory': 'Factor Name'} ] with DataContext(dt.date(2020, 11, 23), dt.date(2020, 11, 25)): actual = mr.factor_proportion_of_risk('report_id', 'Factor') assert all(actual.values == [1, 2, 3]) with DataContext(dt.date(2020, 11, 23), dt.date(2020, 11, 25)): actual = mr.daily_risk('report_id', 'Factor') assert all(actual.values == [1, 2, 3]) with DataContext(dt.date(2020, 11, 23), dt.date(2020, 11, 25)): actual = mr.annual_risk('report_id', 'Factor') assert all(actual.values == [1, 2, 3]) with pytest.raises(MqValueError): mr.daily_risk('report_id', 'Factor Name') with pytest.raises(MqValueError): mr.annual_risk('report_id', 'Factor Name') replace.restore() def test_normalized_performance(): idx = pd.date_range('2020-01-02', freq='D', periods=3) replace = Replacer() expected = { None: pd.Series(data=[1, 2, 3], index=idx, name='normalizedPerformance', dtype='float64'), "Long": pd.Series(data=[1, 2, 3], index=idx, name='normalizedPerformance', dtype='float64'), } mock = replace('gs_quant.api.gs.portfolios.GsPortfolioApi.get_reports', Mock()) mock.return_value = [ Report.from_dict( { 'id': 'RP1', 'positionSourceType': 'Portfolio', 'positionSourceId': 'MP1', 'type': 'Portfolio Performance Analytics', 'parameters': {'transactionCostModel': 'FIXED'}, } ) ] # mock PerformanceReport.get_portfolio_constituents() mock = replace('gs_quant.markets.report.PerformanceReport.get_portfolio_constituents', Mock()) mock.return_value = MarketDataResponseFrame(data=constituents_data) # mock PerformanceReport.get() mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport( report_id='RP1', position_source_type='Portfolio', position_source_id='MP1', report_type='Portfolio Performance Analytics', parameters=ReportParameters(transaction_cost_model='FIXED'), ) for k, v in expected.items(): with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 3)): actual = mr.normalized_performance('MP1', k) assert all(actual.values == v.values) replace.restore() def test_normalized_performance_short(): idx = pd.date_range('2020-01-02', freq='D', periods=3) replace = Replacer() expected = { "Short": pd.Series(data=[1, 1 / 2, 1 / 3], index=idx, name='normalizedPerformance', dtype='float64'), "Long": pd.Series(data=[1, 2, 3], index=idx, name='normalizedPerformance', dtype='float64'), None: pd.Series( data=[1, (2 + 1 / 2) / 2, (3 + 1 / 3) / 2], index=idx, name='normalizedPerformance', dtype='float64' ), } mock = replace('gs_quant.api.gs.portfolios.GsPortfolioApi.get_reports', Mock()) mock.return_value = [ Report.from_dict( { 'id': 'RP1', 'positionSourceType': 'Portfolio', 'positionSourceId': 'MP1', 'type': 'Portfolio Performance Analytics', 'parameters': {'transactionCostModel': 'FIXED'}, } ) ] # mock PerformanceReport.get_portfolio_constituents() mock = replace('gs_quant.markets.report.PerformanceReport.get_portfolio_constituents', Mock()) mock.return_value = MarketDataResponseFrame(data=constituents_data_l_s) # mock PerformanceReport.get() mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport( report_id='RP1', position_source_type='Portfolio', position_source_id='MP1', report_type='Portfolio Performance Analytics', parameters=ReportParameters(transaction_cost_model='FIXED'), ) for k, v in expected.items(): with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 3)): actual = mr.normalized_performance('MP1', k) assert all((actual.values - v.values) < 0.01) replace.restore() def test_get_long_pnl(): idx = pd.date_range('2020-01-02', freq='D', periods=3) replace = Replacer() expected = pd.Series(data=[0, 2, 2], index=idx, name='longPnl', dtype='float64') mock = replace('gs_quant.api.gs.portfolios.GsPortfolioApi.get_reports', Mock()) mock.return_value = [ Report.from_dict( { 'id': 'RP1', 'positionSourceType': 'Portfolio', 'positionSourceId': 'MP1', 'type': 'Portfolio Performance Analytics', 'parameters': {'transactionCostModel': 'FIXED'}, } ) ] # mock PerformanceReport.get_portfolio_constituents() mock = replace('gs_quant.markets.report.PerformanceReport.get_portfolio_constituents', Mock()) mock.return_value = MarketDataResponseFrame(data=pnl_data_l_s) # mock PerformanceReport.get() mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport( report_id='RP1', position_source_type='Portfolio', position_source_id='MP1', report_type='Portfolio Performance Analytics', parameters=ReportParameters(transaction_cost_model='FIXED'), ) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 3)): actual = mr.long_pnl('MP1') assert all(actual.values == expected.values) replace.restore() def test_get_short_pnl(): idx = pd.date_range('2020-01-02', freq='D', periods=3) replace = Replacer() expected = pd.Series(data=[0, -2, -2], index=idx, name='shortPnl', dtype='float64') mock = replace('gs_quant.api.gs.portfolios.GsPortfolioApi.get_reports', Mock()) mock.return_value = [ Report.from_dict( { 'id': 'RP1', 'positionSourceType': 'Portfolio', 'positionSourceId': 'MP1', 'type': 'Portfolio Performance Analytics', 'parameters': {'transactionCostModel': 'FIXED'}, } ) ] # mock PerformanceReport.get_portfolio_constituents() mock = replace('gs_quant.markets.report.PerformanceReport.get_portfolio_constituents', Mock()) mock.return_value = MarketDataResponseFrame(data=pnl_data_l_s) # mock PerformanceReport.get() mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport( report_id='RP1', position_source_type='Portfolio', position_source_id='MP1', report_type='Portfolio Performance Analytics', parameters=ReportParameters(transaction_cost_model='FIXED'), ) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 3)): actual = mr.short_pnl('MP1') assert all(actual.values == expected.values) replace.restore() def test_get_short_pnl_empty(): replace = Replacer() expected = pd.Series(dtype=float) mock = replace('gs_quant.api.gs.portfolios.GsPortfolioApi.get_reports', Mock()) mock.return_value = [ Report.from_dict( { 'id': 'RP1', 'positionSourceType': 'Portfolio', 'positionSourceId': 'MP1', 'type': 'Portfolio Performance Analytics', 'parameters': {'transactionCostModel': 'FIXED'}, } ) ] # mock PerformanceReport.get_portfolio_constituents() mock = replace('gs_quant.markets.report.PerformanceReport.get_portfolio_constituents', Mock()) mock.return_value = MarketDataResponseFrame(data=constituents_data) # mock PerformanceReport.get() mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport( report_id='RP1', position_source_type='Portfolio', position_source_id='MP1', report_type='Portfolio Performance Analytics', parameters=ReportParameters(transaction_cost_model='FIXED'), ) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 3)): actual = mr.short_pnl('MP1') assert all(actual.values == expected.values) replace.restore() def test_get_long_pnl_empty(): replace = Replacer() expected = pd.Series(dtype=float) mock = replace('gs_quant.api.gs.portfolios.GsPortfolioApi.get_reports', Mock()) mock.return_value = [ Report.from_dict( { 'id': 'RP1', 'positionSourceType': 'Portfolio', 'positionSourceId': 'MP1', 'type': 'Portfolio Performance Analytics', 'parameters': {'transactionCostModel': 'FIXED'}, } ) ] # mock PerformanceReport.get_portfolio_constituents() mock = replace('gs_quant.markets.report.PerformanceReport.get_portfolio_constituents', Mock()) mock.return_value = MarketDataResponseFrame(data=constituents_data_s) # mock PerformanceReport.get() mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport( report_id='RP1', position_source_type='Portfolio', position_source_id='MP1', report_type='Portfolio Performance Analytics', parameters=ReportParameters(transaction_cost_model='FIXED'), ) with DataContext(dt.date(2020, 1, 1), dt.date(2019, 1, 3)): actual = mr.long_pnl('MP1') assert all(actual.values == expected.values) replace.restore() def test_thematic_exposure(): replace = Replacer() # mock getting PTA report mock = replace('gs_quant.markets.report.ThematicReport.get', Mock()) mock.return_value = ThematicReport(id='report_id') # mock getting thematic exposure mock = replace('gs_quant.markets.report.ThematicReport.get_thematic_exposure', Mock()) mock.return_value = pd.DataFrame(thematic_data) # mock getting asset mock = Stock('MAA0NE9QX2ABETG6', 'Test Asset') xrefs = replace('gs_quant.timeseries.measures.GsAssetApi.get_asset_xrefs', Mock()) xrefs.return_value = [ GsTemporalXRef( dt.date(2019, 1, 1), dt.date(2952, 12, 31), XRef( ticker='basket_ticker', ), ) ] replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()).return_value = mock with DataContext(dt.date(2020, 7, 12), dt.date(2020, 7, 15)): actual = mr.thematic_exposure('report_id', 'basket_ticker') assert all(actual.values == [2, 2, 2, 2]) replace.restore() def test_thematic_beta(): replace = Replacer() # mock getting PTA report mock = replace('gs_quant.markets.report.ThematicReport.get', Mock()) mock.return_value = ThematicReport(id='report_id') # mock getting thematic exposure mock = replace('gs_quant.markets.report.ThematicReport.get_thematic_betas', Mock()) mock.return_value = pd.DataFrame(thematic_data) # mock getting asset mock = Stock('MAA0NE9QX2ABETG6', 'Test Asset') xrefs = replace('gs_quant.timeseries.measures.GsAssetApi.get_asset_xrefs', Mock()) xrefs.return_value = [ GsTemporalXRef( dt.date(2019, 1, 1), dt.date(2952, 12, 31), XRef( ticker='basket_ticker', ), ) ] replace('gs_quant.markets.securities.SecurityMaster.get_asset', Mock()).return_value = mock with DataContext(dt.date(2020, 7, 12), dt.date(2020, 7, 15)): actual = mr.thematic_beta('report_id', 'basket_ticker') assert all(actual.values == [1, 1, 1, 1]) replace.restore() def test_aum(): replace = Replacer() # mock PerformanceReport.get() mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport( report_id='RP1', position_source_type='Portfolio', position_source_id='MP1', report_type='Portfolio Performance Analytics', parameters=ReportParameters(transaction_cost_model='FIXED'), ) # mock PerformanceReport.get() mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = {'2022-07-01': 100, '2022-07-02': 200} with DataContext(dt.date(2022, 7, 1), dt.date(2022, 7, 3)): actual = mr.aum('RP1') assert all(actual.values == [100, 200]) replace.restore() def test_pnl(): replace = Replacer() # mock PerformanceReport.get() mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport( report_id='RP1', position_source_type='Portfolio', position_source_id='MP1', report_type='Portfolio Performance Analytics', parameters=ReportParameters(transaction_cost_model='FIXED'), ) # mock PerformanceReport.get_pnl() mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame.from_dict( {'date': ['2022-07-01', '2022-07-02', '2022-07-03'], 'pnl': [200, 400, 600]} ) with DataContext(dt.date(2022, 7, 1), dt.date(2022, 7, 3)): actual = mr.pnl('RP1') assert all(actual.values == [200, 400, 600]) replace.restore() def test_pnl_percent(): replace = Replacer() # mock PerformanceReport.get() mock = replace('gs_quant.markets.report.PerformanceReport.get', Mock()) mock.return_value = PerformanceReport( report_id='RP1', position_source_type='Portfolio', position_source_id='MP1', report_type='Portfolio Performance Analytics', parameters=ReportParameters(transaction_cost_model='FIXED'), ) pnl = {'2022-07-01': 200, '2022-07-02': 400, '2022-07-03': 600} aum = {'2022-06-30': 2000, '2022-07-01': 3000, '2022-07-02': 3000, '2022-07-03': 4000} # mock PerformanceReport.get_pnl() mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock()) mock.return_value = pd.DataFrame.from_dict({'date': list(pnl.keys()), 'pnl': list(pnl.values())}) mock = replace('gs_quant.markets.report.PerformanceReport.get_aum_source', Mock()) mock.return_value = RiskAumSource.Long # mock getting performance report mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock()) mock.return_value = PerformanceReport(id='ID') mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock()) mock.return_value = aum # Each team uses pnl today/aum yesterday expected_values = compute_geometric_aggregation_calculations(aum, pnl, list(aum.keys())) with DataContext(dt.date(2022, 7, 1), dt.date(2022, 7, 3)): # mock getting aum mock = replace('gs_quant.markets.report.PerformanceReport.get_long_exposure', Mock()) mock.return_value = pd.DataFrame.from_dict({'date': aum.keys(), 'longExposure': aum.values()}) actual = mr.pnl('report_id', 'Percent') assert all([a == pytest.approx(e) for a, e in zip(actual.values, expected_values)]) # with one day's aum missed to test forward filling logic with DataContext(dt.date(2022, 7, 1), dt.date(2022, 7, 3)): # mock getting aum with one day missed to test forward filling logic aum = {'2022-06-30': 2000, '2022-07-01': 3000, '2022-07-03': 4000} mock = replace('gs_quant.markets.report.PerformanceReport.get_long_exposure', Mock()) mock.return_value = pd.DataFrame.from_dict({'date': aum.keys(), 'longExposure': aum.values()}) actual = mr.pnl('report_id', 'Percent') assert all([a == pytest.approx(e) for a, e in zip(actual.values, expected_values)]) replace.restore() def test_historical_simulation_estimated_pnl(): replace = Replacer() # mock getting report mock = replace('gs_quant.api.gs.reports.GsReportApi.get_report', Mock()) mock.return_value = factor_risk_report # mock getting report factor data mock = replace('gs_quant.api.gs.reports.GsReportApi.get_factor_risk_report_results', Mock()) mock.return_value = factor_exposure_data # mock sending data request mock = replace('gs_quant.api.gs.data.GsDataApi.execute_query', Mock()) mock.return_value = {"data": factor_return_data} with DataContext(dt.date(2024, 3, 13), dt.date(2024, 3, 14)): actual = mr.historical_simulation_estimated_pnl('report_id') actual_values = actual.to_list() assert all([math.isclose(x, actual_values[i]) for i, x in enumerate([7.0, 14.17])]) replace.restore() def test_historical_simulation_estimated_factor_attribution(): replace = Replacer() # mock getting report mock = replace('gs_quant.api.gs.reports.GsReportApi.get_report', Mock()) mock.return_value = factor_risk_report # mock getting report factor data mock = replace('gs_quant.api.gs.reports.GsReportApi.get_factor_risk_report_results', Mock()) mock.return_value = factor_exposure_data # mock sending data request mock = replace('gs_quant.api.gs.data.GsDataApi.execute_query', Mock()) mock.return_value = {"data": [data for data in factor_return_data if data['factor'] == 'Factor_1']} with DataContext(dt.date(2024, 3, 13), dt.date(2024, 3, 14)): actual = mr.historical_simulation_estimated_factor_attribution('report_id', "Factor_1") actual_values = actual.to_list() assert all([math.isclose(x, actual_values[i]) for i, x in enumerate([2.0, 4.04])]) replace.restore() if __name__ == '__main__': pytest.main(args=[__file__]) ================================================ FILE: gs_quant/test/timeseries/test_measures_risk_models.py ================================================ """ Copyright 2020 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from math import sqrt import numpy as np import pandas as pd import pytest from testfixtures import Replacer from testfixtures.mock import Mock import gs_quant.timeseries.measures_risk_models as mrm from gs_quant.data.core import DataContext from gs_quant.errors import MqValueError from gs_quant.models.risk_model import FactorRiskModel as Factor_Risk_Model from gs_quant.markets.securities import Stock from gs_quant.target.risk_models import ( RiskModel, RiskModelCoverage, RiskModelTerm, RiskModelUniverseIdentifier, RiskModelType, ) mock_risk_model_obj = RiskModel( id_='model_id', name='Fake Risk Model', coverage=RiskModelCoverage.Country, term=RiskModelTerm.Long, universe_identifier=RiskModelUniverseIdentifier.gsid, vendor='GS', version=1.0, type_=RiskModelType.Factor, ) mock_risk_model_data = { 'totalResults': 2, 'missingDates': [], 'results': [ { 'date': '2020-01-01', 'factorData': [ {'factorId': '1', 'factorCategory': 'Style'}, {'factorId': '2', 'factorCategory': 'Style'}, {'factorId': '3', 'factorCategory': 'Style'}, ], }, { 'date': '2020-01-02', 'factorData': [ {'factorId': '1', 'factorCategory': 'Style'}, {'factorId': '2', 'factorCategory': 'Style'}, {'factorId': '3', 'factorCategory': 'Style'}, ], }, { 'date': '2020-01-03', 'factorData': [ {'factorId': '1', 'factorCategory': 'Style'}, {'factorId': '2', 'factorCategory': 'Style'}, {'factorId': '3', 'factorCategory': 'Style'}, ], }, ], } mock_risk_model_factor_data_intraday = { 'totalResults': 2, 'missingDates': [], 'results': [ { 'time': '2020-01-01T10:10:10Z', 'factor': 'Factor Name', 'factorCategory': 'Style', "factorId": "factor_id", "factorReturn": 1.022, }, { 'time': '2020-01-01T10:10:20Z', 'factor': 'Factor Name 1', 'factorCategory': 'Style', "factorId": "factor_id_1", "factorReturn": 1.033, }, ], } mock_risk_model_factor_data = [ { 'identifier': 'factor_id', 'type': 'Factor', 'name': "Factor Name", } ] mock_covariance_curve = {'2020-01-01': 1.01, '2020-01-02': 1.02, '2020-01-03': 1.03} mock_volatility_curve = {'2020-01-01': sqrt(1.01), '2020-01-02': sqrt(1.02), '2020-01-03': sqrt(1.03)} mock_correlation_curve = {'2020-01-01': 1.0, '2020-01-02': 1.0, '2020-01-03': 1.0} def mock_risk_model(): replace = Replacer() # mock getting risk model entity() mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model', Mock()) mock.return_value = mock_risk_model_obj actual = Factor_Risk_Model.get(model_id='model_id') replace.restore() return actual def test_risk_model_measure(): replace = Replacer() # mock getting risk model entity() mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model', Mock()) mock.return_value = mock_risk_model_obj # mock getting risk model factor entity mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_data', Mock()) mock.return_value = mock_risk_model_data # mock getting risk model factor entity mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_factor_data', Mock()) mock.return_value = mock_risk_model_factor_data # mock getting asset gsid mock = replace('gs_quant.markets.securities.Asset.get_identifier', Mock()) mock.return_value = '14593' # mock getting risk model dates mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model_dates', Mock()) mock.return_value = ['2020-01-01', '2020-01-02', '2020-01-03'] # mock getting risk model data mock = replace('gs_quant.models.risk_model.MarqueeRiskModel.get_data', Mock()) mock.return_value = { 'totalResults': 3, 'missingDates': [], 'results': [ {'date': '2024-08-19', 'assetData': {'universe': ['14593'], 'bidAskSpread30d': [0.1]}}, {'date': '2024-08-20', 'assetData': {'universe': ['14593'], 'bidAskSpread30d': [0.2]}}, {'date': '2024-08-21', 'assetData': {'universe': ['14593'], 'bidAskSpread30d': [0.3]}}, ], } with DataContext(dt.date(2020, 1, 1), dt.date(2020, 1, 3)): actual = mrm.risk_model_measure( Stock(id_='id', name='Fake Asset'), 'model_id', mrm.ModelMeasureString.BID_AKS_SPREAD_30D ) assert all(actual.values == [0.1, 0.2, 0.3]) with pytest.raises(AttributeError): mrm.risk_model_measure(Stock(id_='id', name='Fake Asset'), 'model_id', 'Wrong Factor Name') replace.restore() def test_factor_zscore(): replace = Replacer() # mock getting risk model entity() mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model', Mock()) mock.return_value = mock_risk_model_obj # mock getting risk model factor entity mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_data', Mock()) mock.return_value = mock_risk_model_data # mock getting risk model factor entity mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_factor_data', Mock()) mock.return_value = mock_risk_model_factor_data # mock getting asset gsid mock = replace('gs_quant.markets.securities.Asset.get_identifier', Mock()) mock.return_value = '12345' # mock getting risk model dates mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model_dates', Mock()) mock.return_value = ['2020-01-01', '2020-01-02', '2020-01-03'] # mock getting risk model data mock = replace('gs_quant.models.risk_model.MarqueeRiskModel.get_data', Mock()) mock.return_value = { 'results': [ {'date': '2020-01-01', 'assetData': {'factorExposure': [{'factor_id': 1.01, 'factor_id_1': 1.23}]}}, {'date': '2020-01-02', 'assetData': {'factorExposure': [{'factor_id': 1.02, 'factor_id_1': 1.23}]}}, {'date': '2020-01-03', 'assetData': {'factorExposure': [{'factor_id': 1.03, 'factor_id_1': 1.23}]}}, ] } with DataContext(dt.date(2020, 1, 1), dt.date(2020, 1, 3)): actual = mrm.factor_zscore(Stock(id_='id', name='Fake Asset'), 'model_id', 'Factor Name') assert all(actual.values == [1.01, 1.02, 1.03]) with pytest.raises(MqValueError): mrm.factor_zscore(Stock(id_='id', name='Fake Asset'), 'model_id', 'Wrong Factor Name') replace.restore() def test_factor_covariance(): replace = Replacer() # mock getting risk model entity() mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model', Mock()) mock.return_value = mock_risk_model_obj # mock getting risk model dates mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model_dates', Mock()) mock.return_value = ['2020-01-01', '2020-01-02', '2020-01-03'] # mock getting risk model factor entity mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_data', Mock()) mock.return_value = mock_risk_model_data # mock getting risk model factor entity mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_factor_data', Mock()) mock.return_value = mock_risk_model_factor_data # mock getting covariances mock = replace('gs_quant.markets.factor.Factor.covariance', Mock()) mock.return_value = mock_covariance_curve with DataContext(dt.date(2020, 1, 1), dt.date(2020, 1, 3)): actual = mrm.factor_covariance(mock_risk_model(), 'Factor Name', 'Factor Name') assert all(actual.values == [1.01, 1.02, 1.03]) with pytest.raises(MqValueError): mrm.factor_covariance(mock_risk_model(), 'Wrong Factor Name', 'Factor Name') replace.restore() def test_factor_volatility(): replace = Replacer() # mock getting risk model factor entity mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_data', Mock()) mock.return_value = mock_risk_model_data # mock getting risk model factor entity mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_factor_data', Mock()) mock.return_value = mock_risk_model_factor_data # mock getting risk model entity() mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model', Mock()) mock.return_value = mock_risk_model_obj # mock getting risk model dates mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model_dates', Mock()) mock.return_value = ['2020-01-01', '2020-01-02', '2020-01-03'] # mock getting covariances mock = replace('gs_quant.markets.factor.Factor.volatility', Mock()) mock.return_value = mock_volatility_curve with DataContext(dt.date(2020, 1, 1), dt.date(2020, 1, 3)): actual = mrm.factor_volatility(mock_risk_model(), 'Factor Name') assert all(actual.values == [sqrt(1.01) * 100, sqrt(1.02) * 100, sqrt(1.03) * 100]) with pytest.raises(MqValueError): mrm.factor_covariance(mock_risk_model(), 'Wrong Factor Name', 'Factor Name') replace.restore() def test_factor_correlation(): replace = Replacer() # mock getting risk model factor entity mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_data', Mock()) mock.return_value = mock_risk_model_data # mock getting risk model factor entity mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_factor_data', Mock()) mock.return_value = mock_risk_model_factor_data # mock getting risk model entity() mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model', Mock()) mock.return_value = mock_risk_model_obj # mock getting risk model dates mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model_dates', Mock()) mock.return_value = ['2020-01-01', '2020-01-02', '2020-01-03'] # mock getting covariances mock = replace('gs_quant.markets.factor.Factor.correlation', Mock()) mock.return_value = mock_correlation_curve with DataContext(dt.date(2020, 1, 1), dt.date(2020, 1, 3)): actual = mrm.factor_correlation(mock_risk_model(), 'Factor Name', 'Factor Name') assert all(actual.values == [1, 1, 1]) replace.restore() def test_factor_performance(): replace = Replacer() # mock getting risk model factor entity mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_data', Mock()) mock.return_value = mock_risk_model_data # mock getting risk model factor entity mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_factor_data', Mock()) mock.return_value = mock_risk_model_factor_data # mock getting risk model entity() mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model', Mock()) mock.return_value = mock_risk_model_obj # mock getting risk model dates mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model_dates', Mock()) mock.return_value = ['2020-01-01', '2020-01-02', '2020-01-03'] # mock getting factor returns mock = replace('gs_quant.markets.factor.Factor.returns', Mock()) mock.return_value = pd.DataFrame.from_dict(mock_covariance_curve, orient='index', columns=['return']) with DataContext(dt.date(2020, 1, 1), dt.date(2020, 1, 3)): actual = mrm.factor_performance(mock_risk_model(), 'Factor Name') assert len(actual.values) == 3 replace.restore() def test_factor_returns_intraday(): replace = Replacer() # mock getting risk model factor entity mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_factor_data', Mock()) mock.return_value = mock_risk_model_factor_data # mock getting risk model entity() mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model', Mock()) mock.return_value = mock_risk_model_obj # mock getting factor returns mock = replace('gs_quant.markets.factor.Factor.intraday_returns', Mock()) mock.return_value = ( pd.DataFrame(mock_risk_model_factor_data_intraday.get('results')) .set_index('time') .drop(columns=["factorCategory", "factor", "factorId"], errors='ignore') ) with DataContext(dt.datetime(2025, 1, 1, 0, 0, 0), dt.datetime(2025, 1, 1, 23, 59, 59)): actual = mrm.factor_returns_intraday(mock_risk_model(), 'Factor Name') assert len(actual.values) == 2 replace.restore() def test_factor_returns_percentile(): replace = Replacer() # mock getting risk model factor entity mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_factor_data', Mock()) mock.return_value = mock_risk_model_factor_data # mock getting risk model entity() mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model', Mock()) mock.return_value = mock_risk_model_obj mock_risk_model_factor_returns_data = { "results": [ { 'date': '2025-01-10', "factorData": [ { 'factorName': 'Factor Name', 'factorCategory': 'Style', "factorId": "factor_id", "factorReturn": 1.022, } ], }, { 'date': '2025-01-09', "factorData": [ { 'factorName': 'Factor Name', 'factorCategory': 'Style', "factorId": "factor_id", "factorReturn": 2.033, } ], }, { 'date': '2025-01-08', "factorData": [ { 'factorName': 'Factor Name', 'factorCategory': 'Style', "factorId": "factor_id", "factorReturn": 3.044, } ], }, { 'date': '2025-01-07', "factorData": [ { 'factorName': 'Factor Name', 'factorCategory': 'Style', "factorId": "factor_id", "factorReturn": 4.055, } ], }, { 'date': '2025-01-06', "factorData": [ { 'factorName': 'Factor Name', 'factorCategory': 'Style', "factorId": "factor_id", "factorReturn": 5.066, } ], }, { 'date': '2025-01-05', "factorData": [ { 'factorName': 'Factor Name', 'factorCategory': 'Style', "factorId": "factor_id", "factorReturn": 6.077, } ], }, { 'date': '2025-01-04', "factorData": [ { 'factorName': 'Factor Name', 'factorCategory': 'Style', "factorId": "factor_id", "factorReturn": 7.088, } ], }, ] } expected_percentile_value = np.percentile( np.array( [f['factorReturn'] for data in mock_risk_model_factor_returns_data['results'] for f in data['factorData']] ), 90, ) mock = replace('gs_quant.api.gs.risk_models.GsFactorRiskModelApi.get_risk_model_data', Mock()) mock.return_value = mock_risk_model_factor_returns_data start_time = dt.datetime(2025, 1, 10, 0, 0, 0) end_time = dt.datetime(2025, 1, 10, 23, 59, 59) expected_series = pd.Series( expected_percentile_value, index=pd.date_range(start=start_time, end=end_time, freq='2h') ) with DataContext(start_time, end_time): actual = mrm.factor_returns_percentile(mock_risk_model(), 'Factor Name', n_percentile=90) assert expected_series.equals(actual) replace.restore() if __name__ == '__main__': pytest.main(args=[__file__]) ================================================ FILE: gs_quant/test/timeseries/test_measures_xccy.py ================================================ """ Copyright 2020 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import pandas as pd import pytest from pandas.testing import assert_series_equal from testfixtures import Replacer from testfixtures.mock import Mock import gs_quant.timeseries.measures_xccy as tm from gs_quant.api.gs.assets import GsAsset from gs_quant.api.gs.data import GsDataApi, MarketDataResponseFrame from gs_quant.common import PricingLocation, Currency as CurrEnum from gs_quant.errors import MqError, MqValueError from gs_quant.markets import PricingContext from gs_quant.markets.securities import Bond, Cross, Currency from gs_quant.session import GsSession, Environment from gs_quant.test.timeseries.utils import mock_request from gs_quant.timeseries import CurrencyEnum, SecurityMaster, CrossCurrencyRateOptionType from gs_quant.timeseries.measures_xccy import ( _currency_to_tdapi_crosscurrency_swap_rate_asset, CROSSCURRENCY_RATES_DEFAULTS, TdapiCrossCurrencyRatesDefaultsProvider, ) _index = [pd.Timestamp('2021-03-30')] _test_datasets = ('TEST_DATASET',) def test_get_floating_rate_option_for_benchmark_retuns_rate(): provider = TdapiCrossCurrencyRatesDefaultsProvider(CROSSCURRENCY_RATES_DEFAULTS) value = provider.get_rateoption_for_benchmark(CurrencyEnum.GBP, "LIBOR") assert value == "GBP-LIBOR-BBA" def test_get_floating_rate_option_for_benchmark_retuns_rate_usd(): provider = TdapiCrossCurrencyRatesDefaultsProvider(CROSSCURRENCY_RATES_DEFAULTS) value = provider.get_rateoption_for_benchmark(CurrencyEnum.USD, "LIBOR") assert value == "USD-LIBOR-BBA" def test_currency_to_tdapi_xccy_swap_rate_asset(mocker): replace = Replacer() mocker.patch.object( GsSession.__class__, 'current', return_value=GsSession.get(Environment.QA, 'client_id', 'secret') ) mocker.patch.object(GsSession.current.sync, 'get', side_effect=mock_request) mocker.patch.object(SecurityMaster, 'get_asset', side_effect=mock_request) bbid_mock = replace('gs_quant.timeseries.measures_xccy.Asset.get_identifier', Mock()) with PricingContext(dt.date.today()): cur = [ {"currency_assetId": "MAK1FHKH5P5GJSHH", "currency": "JPY", "xccy_id": "MAFMW4HJC5TDE51H"}, {"currency_assetId": "MA66CZBQJST05XKG", "currency": "GBP", "xccy_id": "MATDD783JM1C2GGD"}, {"currency_assetId": "MAJNQPFGN1EBDHAE", "currency": "EUR", "xccy_id": "MAW8SAXPSKYA94E2"}, ] for c in cur: print(c) asset = Currency(c.get("currency_assetId"), c.get("currency")) bbid_mock.return_value = c.get("currency") mqid = _currency_to_tdapi_crosscurrency_swap_rate_asset(asset) assert mqid == c.get("xccy_id") bbid_mock.return_value = None assert _currency_to_tdapi_crosscurrency_swap_rate_asset(asset) == c.get("currency_assetId") replace.restore() def test_get_crosscurrency_swap_leg_defaults(): result_dict = dict( currency=CurrEnum.JPY, rateOption="JPY-LIBOR-BBA", designatedMaturity="3m", pricing_location=PricingLocation.TKO ) defaults = tm._get_crosscurrency_swap_leg_defaults(CurrEnum.JPY, tm.CrossCurrencyRateOptionType.LIBOR) assert result_dict == defaults result_dict = dict( currency=CurrEnum.EUR, rateOption="EUR-EURIBOR-TELERATE", designatedMaturity="3m", pricing_location=PricingLocation.LDN, ) defaults = tm._get_crosscurrency_swap_leg_defaults(CurrEnum.EUR, tm.CrossCurrencyRateOptionType.LIBOR) assert result_dict == defaults result_dict = dict( currency=CurrEnum.EUR, rateOption="EUR-EONIA-OIS-COMPOUND", designatedMaturity="3m", pricing_location=PricingLocation.LDN, ) defaults = tm._get_crosscurrency_swap_leg_defaults(CurrEnum.EUR, tm.CrossCurrencyRateOptionType.OIS) assert result_dict == defaults result_dict = dict( currency=CurrEnum.GBP, rateOption="GBP-LIBOR-BBA", designatedMaturity="3m", pricing_location=PricingLocation.LDN ) defaults = tm._get_crosscurrency_swap_leg_defaults(CurrEnum.GBP, tm.CrossCurrencyRateOptionType.LIBOR) assert result_dict == defaults result_dict = dict( currency=CurrEnum.GBP, rateOption="GBP-LIBOR-BBA", designatedMaturity="3m", pricing_location=PricingLocation.LDN ) defaults = tm._get_crosscurrency_swap_leg_defaults(CurrEnum.GBP, None) assert result_dict == defaults def test_get_crosscurrency_swap_csa_terms(): valid_ccy = ['EUR', 'GBP', 'JPY'] for ccy in valid_ccy: assert dict(csaTerms=ccy + '-1') == tm._get_crosscurrency_swap_csa_terms( ccy, tm.CrossCurrencyRateOptionType.LIBOR.value ) def test_check_valid_indices(): valid_indices = ['LIBOR'] for index in valid_indices: assert tm.CrossCurrencyRateOptionType[index] == tm._check_crosscurrency_rateoption_type(CurrencyEnum.GBP, index) invalid_indices = ['LIBORED', 'TestRateOption'] for index in invalid_indices: with pytest.raises(MqError): tm._check_crosscurrency_rateoption_type(CurrencyEnum.GBP, index) def test_get_tdapi_crosscurrency_rates_assets(mocker): mock_asset_1 = GsAsset(asset_class='Rates', id='MAW8SAXPSKYA94E2', type_='XccySwapMTM', name='Test_asset') mock_asset_2 = GsAsset(asset_class='Rates', id='MATDD783JM1C2GGD', type_='XccySwapMTM', name='Test_asset') replace = Replacer() assets = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) assets.return_value = [mock_asset_1] assert 'MAW8SAXPSKYA94E2' == tm._get_tdapi_crosscurrency_rates_assets() replace.restore() assets = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) assets.return_value = [mock_asset_1, mock_asset_2] kwargs = dict(asset_parameters_termination_date='5y', asset_parameters_effective_date='0b') with pytest.raises(MqValueError): tm._get_tdapi_crosscurrency_rates_assets(**kwargs) replace.restore() assets = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) assets.return_value = [] kwargs = dict( asset_parameters_clearing_house='NONE', asset_parameters_payer_rate_option="EUR-EURIBOR-TELERATE", asset_parameters_payer_currency='EUR', asset_parameters_payer_designated_maturity='3m', asset_parameters_receiver_rate_option="USD-LIBOR-BBA", asset_parameters_receiver_currency='USD', asset_parameters_receiver_designated_maturity='3m', pricing_location='LDN', ) with pytest.raises(MqValueError): tm._get_tdapi_crosscurrency_rates_assets(**kwargs) replace.restore() assets = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) assets.return_value = [mock_asset_1, mock_asset_2] kwargs = dict() assert ['MAW8SAXPSKYA94E2', 'MATDD783JM1C2GGD'] == tm._get_tdapi_crosscurrency_rates_assets(**kwargs) replace.restore() # test case will test matching sofr maturity with libor leg and flipping legs to get right asset kwargs = dict( type='XccySwapMTM', asset_parameters_termination_date='5y', asset_parameters_payer_rate_option="EUR-EURIBOR-TELERATE", asset_parameters_payer_currency="EUR", asset_parameters_payer_designated_maturity='3m', asset_parameters_receiver_rate_option="USD-LIBOR-BBA", asset_parameters_receiver_currency="USD", asset_parameters_receiver_designated_maturity='3m', asset_parameters_clearing_house='None', asset_parameters_effective_date='5y', pricing_location='LDN', ) assets = replace('gs_quant.timeseries.measures.GsAssetApi.get_many_assets', Mock()) assets.return_value = [mock_asset_1] assert 'MAW8SAXPSKYA94E2' == tm._get_tdapi_crosscurrency_rates_assets(**kwargs) replace.restore() def mock_curr(_cls, _q): d = { 'xccySwapSpread': [1, 2, 3], } df = MarketDataResponseFrame(data=d, index=_index * 3) df.dataset_ids = _test_datasets return df def test_crosscurrency_swap_rate(mocker): replace = Replacer() args = dict(swap_tenor='5y', rateoption_type='LIBOR', clearing_house='LCH', forward_tenor='5y', real_time=False) mock_gbp = Currency('MA26QSMPX9990G66', 'GBP') args['asset'] = mock_gbp xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'GBP' xrefs = replace('gs_quant.timeseries.measures.SecurityMaster.get_asset', Mock()) mock_usd = Currency('MA26QSMPX9990G63', 'USD') xrefs.return_value = mock_usd args['swap_tenor'] = '5yr' with pytest.raises(MqValueError): tm.crosscurrency_swap_rate(**args) args['swap_tenor'] = '5y' args['forward_tenor'] = '5yr' with pytest.raises(MqValueError): tm.crosscurrency_swap_rate(**args) args['forward_tenor'] = '5y' args['real_time'] = True with pytest.raises(NotImplementedError): tm.crosscurrency_swap_rate(**args) args['real_time'] = False args['asset'] = Currency('MA666', 'AED') xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'AED' with pytest.raises(NotImplementedError): tm.crosscurrency_swap_rate(**args) args['asset'] = mock_gbp args['asset'] = Bond('MA667', 'TEST') with pytest.raises(MqValueError): tm.crosscurrency_swap_rate(**args) args['asset'] = mock_gbp args['asset'] = Cross('MA667', 'USDAED') xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.side_effect = ['AED', 'USD'] with pytest.raises(NotImplementedError): tm.crosscurrency_swap_rate(**args) args['asset'] = Cross('MA667', 'USDAED') xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.side_effect = ['USD', 'AED'] with pytest.raises(NotImplementedError): tm.crosscurrency_swap_rate(**args) args['asset'] = Cross('MA667', 'USDGBP') xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'GBP' xrefs = replace('gs_quant.timeseries.measures_xccy._check_crosscurrency_rateoption_type', Mock()) xrefs.side_effect = [tm.CrossCurrencyRateOptionType.LIBOR, tm.CrossCurrencyRateOptionType.OIS] with pytest.raises(MqValueError): tm.crosscurrency_swap_rate(**args) replace.restore() xrefs = replace('gs_quant.timeseries.measures.SecurityMaster.get_asset', Mock()) xrefs.return_value = mock_usd args['asset'] = Cross('MA667', 'USDGBP') xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'GBP' identifiers = replace('gs_quant.timeseries.measures_xccy._get_tdapi_crosscurrency_rates_assets', Mock()) identifiers.return_value = {'MA26QSMPX9990G66'} mocker.patch.object(GsDataApi, 'get_market_data', return_value=mock_curr(None, None)) actual = tm.crosscurrency_swap_rate(**args) expected = tm.ExtendedSeries([1, 2, 3], index=_index * 3, name='xccySwapSpread') expected.dataset_ids = _test_datasets assert_series_equal(expected, actual) assert actual.dataset_ids == _test_datasets args['asset'] = Cross('MA667', 'USDCAD') args['location'] = PricingLocation.LDN xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'CAD' identifiers = replace('gs_quant.timeseries.measures_xccy._get_tdapi_crosscurrency_rates_assets', Mock()) identifiers.return_value = {'MA26QSMPX9990G66'} mocker.patch.object(GsDataApi, 'get_market_data', return_value=mock_curr(None, None)) actual = tm.crosscurrency_swap_rate(**args) expected = tm.ExtendedSeries([1, 2, 3], index=_index * 3, name='xccySwapSpread') expected.dataset_ids = _test_datasets assert_series_equal(expected, actual) assert actual.dataset_ids == _test_datasets args['asset'] = mock_gbp xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'GBP' args['rateoption_type'] = CrossCurrencyRateOptionType.TestRateOption with pytest.raises(MqValueError): tm.crosscurrency_swap_rate(**args) args['rateoption_type'] = CrossCurrencyRateOptionType.LIBOR xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'GBP' identifiers = replace('gs_quant.timeseries.measures_xccy._get_tdapi_crosscurrency_rates_assets', Mock()) identifiers.return_value = {'MA26QSMPX9990G66'} mocker.patch.object(GsDataApi, 'get_market_data', return_value=mock_curr(None, None)) actual = tm.crosscurrency_swap_rate(**args) expected = tm.ExtendedSeries([1, 2, 3], index=_index * 3, name='xccySwapSpread') expected.dataset_ids = _test_datasets assert_series_equal(expected, actual) assert actual.dataset_ids == _test_datasets xrefs = replace('gs_quant.timeseries.measures.Asset.get_identifier', Mock()) xrefs.return_value = 'EUR' identifiers = replace('gs_quant.timeseries.measures_xccy._get_tdapi_crosscurrency_rates_assets', Mock()) identifiers.return_value = {'MAZBW57ZPS54ET7K'} mocker.patch.object(GsDataApi, 'get_market_data', return_value=mock_curr(None, None)) args['asset'] = Currency('MAZBW57ZPS54ET7K', 'EUR') args['rateoption_type'] = 'OIS' actual = tm.crosscurrency_swap_rate(**args) expected = tm.ExtendedSeries([1, 2, 3], index=_index * 3, name='xccySwapSpread') expected.dataset_ids = _test_datasets assert_series_equal(expected, actual) assert actual.dataset_ids == _test_datasets replace.restore() if __name__ == '__main__': pytest.main(args=["test_measures_xccy.py"]) ================================================ FILE: gs_quant/test/timeseries/test_rolling.py ================================================ """ Copyright 2022 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import pytest import time import numpy as np import pandas as pd from gs_quant.timeseries.helper import rolling_offset class Timer: def __init__(self, name): self.name = name def __enter__(self): self.start = time.perf_counter() def __exit__(self, exc_type, exc_val, exc_tb): print(f'{self.name} ran in {(time.perf_counter() - self.start) * 1000:.2f}ms') @pytest.mark.parametrize( "frequency,count,unit", [('D', 22, 'days'), ('D', 4, 'weeks'), ('D', 3, 'months'), ('D', 1, 'years'), ('s', 12, 'hours')], ) def test_rolling_date_offset(frequency, count, unit): length = 1000 rng = np.random.default_rng() values = [rng.random() if rng.random() > 0.1 else np.nan for _ in range(length)] s = pd.Series(values, index=pd.date_range(end=dt.datetime.now(), freq=frequency, periods=length)) offset = pd.DateOffset(**{unit: count}) print(f'\nseries frequency: {frequency}, offset: {count}{unit}') with Timer('simple rolling'): expected = pd.Series( [np.nanmean(s.loc[(s.index > idx - offset) & (s.index <= idx)]) for idx in s.index], index=s.index ) with Timer('rolling with method name'): a1 = rolling_offset(s, offset, np.nanmean, 'mean') pd.testing.assert_series_equal(expected, a1, obj="Pandas mean") with Timer('rolling without method name'): a2 = rolling_offset(s, offset, np.nanmean) pd.testing.assert_series_equal(expected, a2, obj="generic mean") if __name__ == "__main__": pytest.main(args=["test_rolling.py", "-s"]) ================================================ FILE: gs_quant/test/timeseries/test_statistics.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import numpy as np import pandas as pd import pytest import scipy.stats.mstats as stats from pandas.testing import assert_series_equal from scipy.integrate import odeint from gs_quant.data import DataContext from gs_quant.errors import MqTypeError, MqValueError, MqError from gs_quant.timeseries import normalize_window, Window, Returns, returns from gs_quant.timeseries.statistics import ( Direction, IntradayDirection, generate_series, generate_series_intraday, LinearRegression, RollingLinearRegression, min_, max_, range_, mean, median, mode, sum_, product, std, exponential_std, var, cov, zscores, winsorize, percentiles, percentile, SIRModel, SEIRModel, MeanType, ) def _random_series(days=365, nans=10): assert nans < days rng = np.random.default_rng() values = rng.random(days) nan_indexes = np.floor(rng.random(nans) * len(values)).astype(np.int_) for i in nan_indexes: values[i] = np.nan return pd.Series(values, index=pd.date_range(start="2021-01-01", periods=days, freq='D')) def _rolling_1m_test(fn, pandas_fn: str): x = _random_series() window = normalize_window(x, Window('1m', 0)) result = fn(x, window) values = (getattr(x.loc[(x.index > idx - window.w) & (x.index <= idx)], pandas_fn)() for idx in x.index) expected = pd.Series(values, index=x.index) assert_series_equal(result, expected, obj=f"{pandas_fn} with date window 1m") def test_generate_series(): x = generate_series(100) assert len(x) == 100 assert x.index[0] == dt.date.today() assert x.iloc[0] == 100 x = generate_series(100, Direction.END_TODAY) assert len(x) == 100 assert x.index[-1] == dt.date.today() assert x.iloc[0] == 100 def test_generate_series_intraday(): # Test START_INTRADAY_NOW (default) x = generate_series_intraday(100) assert len(x) == 100 assert isinstance(x.index, pd.DatetimeIndex) assert x.iloc[0] == 100 # Check that first timestamp is close to now (within 1 minute) now = pd.Timestamp.now().floor('min') assert abs((x.index[0] - now).total_seconds()) < 60 # Check that observations are 1 minute apart assert (x.index[-1] - x.index[0]).total_seconds() == 99 * 60 # Test END_INTRADAY_NOW x = generate_series_intraday(100, IntradayDirection.END_INTRADAY_NOW) assert len(x) == 100 assert isinstance(x.index, pd.DatetimeIndex) assert x.iloc[0] == 100 # Check that last timestamp is close to now (within 1 minute) now = pd.Timestamp.now().floor('min') assert abs((x.index[-1] - now).total_seconds()) < 60 # Check that observations are 1 minute apart assert (x.index[-1] - x.index[0]).total_seconds() == 99 * 60 def test_min(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 7), dt.date(2019, 1, 8), ] x = pd.Series([3.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=dates) z = pd.Series([2.0, 5.0, 4.0, 0.0, 1.0, 3.0], index=dates) result = min_([x, z], Window('2d', 0)) expected = pd.Series([2.0, 2.0, 2.0, 0.0, 1.0, 1.0], index=dates) assert_series_equal(result, expected, obj="Minimum list 2d") result = min_(x) expected = pd.Series([3.0, 2.0, 2.0, 1.0, 1.0, 1.0], index=dates) assert_series_equal(result, expected, obj="Minimum") result = min_(x, Window(1, 0)) expected = pd.Series([3.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=dates) assert_series_equal(result, expected, obj="Minimum window 1") result = min_(x, Window(2, 0)) expected = pd.Series([3.0, 2.0, 2.0, 1.0, 1.0, 3.0], index=dates) assert_series_equal(result, expected, obj="Minimum window 2") result = min_(x, Window('1w', 0)) expected = pd.Series([3.0, 2.0, 2.0, 1.0, 1.0, 1.0], index=dates) assert_series_equal(result, expected, obj="Minimum with window 1w") y = pd.Series([4.0, np.nan, 4.0, 2.0, 2.0, 5.0], index=dates) result = min_([x, y], Window(2, 0)) expected = pd.Series([3.0, 2.0, 2.0, 1.0, 1.0, 2.0], index=dates) assert_series_equal(result, expected, obj="Minimum of multiple series") result = min_(x, "2d") expected = pd.Series([2.0, 1.0, 3.0, 3.0], index=dates[2:]) assert_series_equal(result, expected, obj="Minimum with strdate window") result = min_(x, "1d") expected = pd.Series([2.0, 3.0, 1.0, 3.0, 6.0], index=dates[1:]) assert_series_equal(result, expected, obj="Minimum with strdate window 2") ranges = pd.date_range('20220101', periods=6, freq='40min') y = pd.Series([4.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=ranges) result = min_(y, '2h') expected = pd.Series([1.0, 1.0, 1.0], index=ranges[3:]) assert_series_equal(result, expected, obj="Minimum with string window 2h") _rolling_1m_test(min_, 'min') def test_max(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 7), dt.date(2019, 1, 8), ] x = pd.Series([3.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=dates) z = pd.Series([1.0, 0.0, 4.0, 3.5, 7.0, 8.0], index=dates) result = max_([x, z], Window('2d', 0)) expected = pd.Series([3.0, 3.0, 4.0, 4.0, 7.0, 8.0], index=dates) assert_series_equal(result, expected, obj="Maximum list 2d") result = max_(x) expected = pd.Series([3.0, 3.0, 3.0, 3.0, 3.0, 6.0], index=dates) assert_series_equal(result, expected, obj="Maximum") result = max_(x, Window(1, 0)) expected = pd.Series([3.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=dates) assert_series_equal(result, expected, obj="Maximum window 1") result = max_(x, Window(2, 0)) expected = pd.Series([3.0, 3.0, 3.0, 3.0, 3.0, 6.0], index=dates) assert_series_equal(result, expected, obj="Maximum window 2") result = max_(x, Window('2d', 0)) expected = pd.Series([3.0, 3.0, 3.0, 3.0, 3.0, 6.0], index=dates) assert_series_equal(result, expected, obj="Maximum window 1w") y = pd.Series([4.0, np.nan, 4.0, 2.0, 2.0, 5.0], index=dates) result = max_([x, y], Window(2, 0)) expected = pd.Series([4.0, 4.0, 4.0, 4.0, 3.0, 6.0], index=dates) assert_series_equal(result, expected, obj="Maximum of multiple series") s = pd.Series([-3.0, -2.0, 3.0, -1.0, -3.0, 6.0], index=dates) t = pd.Series([0, 0], index=dates[0:2]) result = max_([s, t], 1) expected = pd.Series([0.0, 3, 0, 0, 6], index=dates[1:]) assert_series_equal(result, expected, obj="Maximum with constant") ranges = pd.date_range('20220101', periods=6, freq='30min') y = pd.Series([4.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=ranges) result = max_(y, '1h') expected = pd.Series([3.0, 3.0, 3.0, 6.0], index=ranges[2:]) assert_series_equal(result, expected, obj="Maximum with string window 1h") _rolling_1m_test(max_, 'max') def test_range(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 7), dt.date(2019, 1, 8), ] x = pd.Series([3.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=dates) result = range_(x) expected = pd.Series([0.0, 1.0, 1.0, 2.0, 2.0, 5.0], index=dates) assert_series_equal(result, expected, obj="Range") result = range_(x, Window(1, 0)) expected = pd.Series([0.0, 0.0, 0.0, 0.0, 0.0, 0.0], index=dates) assert_series_equal(result, expected, obj="Range window 1") result = range_(x, Window(2, 0)) expected = pd.Series([0.0, 1.0, 1.0, 2.0, 2.0, 3.0], index=dates) assert_series_equal(result, expected, obj="Range window 2") result = range_(x, Window('1w', 0)) expected = pd.Series([0.0, 1.0, 1.0, 2.0, 2.0, 5.0], index=dates) assert_series_equal(result, expected, obj="Range window 1w") def test_mean(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 7), dt.date(2019, 1, 8), ] x = pd.Series([3.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=dates) result = mean(x) expected = pd.Series([3.0, 2.5, 8 / 3, 2.25, 2.4, 3.0], index=dates) assert_series_equal(result, expected, obj="Mean") result = mean(x, Window(1, 0)) expected = pd.Series([3.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=dates) assert_series_equal(result, expected, obj="Mean window 1") result = mean(x, Window(2, 0)) expected = pd.Series([3.0, 2.5, 2.5, 2.0, 2.0, 4.5], index=dates) assert_series_equal(result, expected, obj="Mean window 2") result = mean(x, Window('1w', 0)) expected = pd.Series([3.0, 2.5, 8 / 3, 2.25, 2.4, 3.0], index=dates) assert_series_equal(result, expected, obj="Mean window 1w") y = pd.Series([4.0, np.nan, 4.0, 2.0, 2.0, 5.0], index=dates) result = mean([x, y], Window(2, 0)) expected = pd.Series([3.5, 3.0, 3.0, 2.5, 2.0, 4.0], index=dates) assert_series_equal(result, expected, obj="Mean of multiple series") result = mean([x, y], Window('2d', 0)) expected = pd.Series([3.5, 3, 3, 2.5, 2.5, 4], index=dates) assert_series_equal(result, expected, obj="Mean of multiple series by date offset") result = mean(y, Window(2, 0)) expected = pd.Series([4.0, 4.0, 4.0, 3.0, 2.0, 3.5], index=dates) assert_series_equal(result, expected, obj="Mean of single series with nan") def test_quadratic_mean(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 7), dt.date(2019, 1, 8), ] x = pd.Series([3.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=dates) result = mean(x, mean_type=MeanType.QUADRATIC) expected = pd.Series( [ np.sqrt(9.0), np.sqrt((9.0 + 4.0) / 2), np.sqrt((9.0 + 4.0 + 9.0) / 3), np.sqrt((9.0 + 4.0 + 9.0 + 1.0) / 4), np.sqrt((9.0 + 4.0 + 9.0 + 1.0 + 9.0) / 5), np.sqrt((9.0 + 4.0 + 9.0 + 1.0 + 9.0 + 36.0) / 6), ], index=dates, ) assert_series_equal(result, expected, obj="Quadratic mean") result = mean(x, Window(1, 0), mean_type=MeanType.QUADRATIC) expected = pd.Series([3.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=dates) assert_series_equal(result, expected, obj="Quadratic mean window 1") result = mean(x, Window(2, 0), mean_type=MeanType.QUADRATIC) expected = pd.Series( [ np.sqrt(9.0), np.sqrt((9.0 + 4.0) / 2), np.sqrt((4.0 + 9.0) / 2), np.sqrt((9.0 + 1.0) / 2), np.sqrt((1.0 + 9.0) / 2), np.sqrt((9.0 + 36.0) / 2), ], index=dates, ) assert_series_equal(result, expected, obj="Quadratic mean window 2") result = mean(x, Window('1w', 0), mean_type=MeanType.QUADRATIC) expected = pd.Series( [ np.sqrt(9.0), np.sqrt((9.0 + 4.0) / 2), np.sqrt((9.0 + 4.0 + 9.0) / 3), np.sqrt((9.0 + 4.0 + 9.0 + 1.0) / 4), np.sqrt((9.0 + 4.0 + 9.0 + 1.0 + 9.0) / 5), np.sqrt((4.0 + 9.0 + 1.0 + 9.0 + 36.0) / 5), ], index=dates, ) assert_series_equal(result, expected, obj="Quadratic mean window 1w") y = pd.Series([4.0, np.nan, 4.0, 2.0, 2.0, 5.0], index=dates) result = mean([x, y], Window(2, 0), mean_type=MeanType.QUADRATIC) expected = pd.Series( [ np.sqrt((9.0 + 16.0) / 2), np.sqrt((9.0 + 4.0 + 16.0) / 3), np.sqrt((4.0 + 9.0 + 16.0) / 3), np.sqrt((9.0 + 1.0 + 16.0 + 4.0) / 4), np.sqrt((1.0 + 9.0 + 4.0 + 4.0) / 4), np.sqrt((9.0 + 36.0 + 4.0 + 25.0) / 4), ], index=dates, ) assert_series_equal(result, expected, obj="Quadratic mean of multiple series") result = mean([x, y], Window('2d', 0), mean_type=MeanType.QUADRATIC) expected = pd.Series( [ np.sqrt((9.0 + 16.0) / 2), np.sqrt((9.0 + 4.0 + 16.0) / 3), np.sqrt((4.0 + 9.0 + 16.0) / 3), np.sqrt((9.0 + 1.0 + 16.0 + 4.0) / 4), np.sqrt((9.0 + 4.0) / 2), np.sqrt((9.0 + 36.0 + 4.0 + 25.0) / 4), ], index=dates, ) assert_series_equal(result, expected, obj="Quadratic mean of multiple series by date offset") result = mean(y, Window(2, 0), mean_type=MeanType.QUADRATIC) expected = pd.Series( [ np.sqrt(16.0), np.sqrt(16.0), np.sqrt(16.0), np.sqrt((16.0 + 4.0) / 2), np.sqrt((4.0 + 4.0) / 2), np.sqrt((4.0 + 25.0) / 2), ], index=dates, ) assert_series_equal(result, expected, obj="Quadratic mean of single series with nan") z = pd.Series([-3.0, 4.0, -5.0], index=dates[:3]) result = mean(z, Window(2, 0), mean_type=MeanType.QUADRATIC) expected = pd.Series([np.sqrt(9.0), np.sqrt((9.0 + 16.0) / 2), np.sqrt((16.0 + 25.0) / 2)], index=dates[:3]) assert_series_equal(result, expected, obj="Quadratic mean with negative values") def test_median(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 7), dt.date(2019, 1, 8), ] x = pd.Series([3.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=dates) result = median(x) expected = pd.Series([3.0, 2.5, 3.0, 2.5, 3.0, 3.0], index=dates) assert_series_equal(result, expected, obj="Median") result = median(x, Window(1, 0)) expected = pd.Series([3.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=dates) assert_series_equal(result, expected, obj="Median window 1") result = median(x, Window(2, 0)) expected = pd.Series([3.0, 2.5, 2.5, 2.0, 2.0, 4.5], index=dates) assert_series_equal(result, expected, obj="Median window 2") result = median(x, Window('1w', 0)) expected = pd.Series([3.0, 2.5, 3.0, 2.5, 3.0, 3.0], index=dates) assert_series_equal(result, expected, obj="Median window 1w") _rolling_1m_test(median, 'median') def test_mode(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 7), dt.date(2019, 1, 8), ] x = pd.Series([3.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=dates) result = mode(x) expected = pd.Series([3.0, 2.0, 3.0, 3.0, 3.0, 3.0], index=dates) assert_series_equal(result, expected, obj="mode") result = mode(x, Window(1, 0)) expected = pd.Series([3.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=dates) assert_series_equal(result, expected, obj="mode window 1") result = mode(x, Window(2, 0)) expected = pd.Series([3.0, 2.0, 2.0, 1.0, 1.0, 3.0], index=dates) assert_series_equal(result, expected, obj="mode window 2") result = mode(x, Window('1w', 0)) expected = pd.Series([3.0, 2.0, 3.0, 3.0, 3.0, 3.0], index=dates) assert_series_equal(result, expected, obj="Mode window 1w") x = _random_series() window = normalize_window(x, Window('1m', 0)) result = mode(x, window) values = (stats.mode(x.loc[(x.index > idx - window.w) & (x.index <= idx)]).mode[0] for idx in x.index) expected = pd.Series(values, index=x.index) assert_series_equal(result, expected, obj="Mode window 1m") def test_sum(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 7), dt.date(2019, 1, 8), ] x = pd.Series([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], index=dates) result = sum_(x) expected = pd.Series([1.0, 3.0, 6.0, 10, 15, 21], index=dates) assert_series_equal(result, expected, obj="Summation") result = sum_(x, Window(2, 0)) expected = pd.Series([1.0, 3.0, 5.0, 7.0, 9.0, 11.0], index=dates) assert_series_equal(result, expected, obj="Summation") result = sum_(x, Window('1w', 0)) expected = pd.Series([1.0, 3.0, 6.0, 10.0, 15.0, 20.0], index=dates) assert_series_equal(result, expected, obj="Sum window 1w") y = pd.Series([4.0, np.nan, 4.0, 2.0, 2.0, 5.0], index=dates) result = sum_([x, y], Window(2, 0)) expected = pd.Series([5.0, 7.0, 9.0, 13.0, 13.0, 18.0], index=dates) assert_series_equal(result, expected, obj="Sum of multiple series") result = sum_(y, Window('2d', 0)) expected = pd.Series([4, 4, 4, 6, 2, 7], index=dates, dtype=np.double) assert_series_equal(result, expected, obj="Sum with nan input") def test_product(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 7), dt.date(2019, 1, 8), ] x = pd.Series([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], index=dates) result = product(x) expected = pd.Series([1.0, 2.0, 6.0, 24, 120, 720], index=dates) assert_series_equal(result, expected, obj="Product") result = product(x, Window(2, 0)) expected = pd.Series([1.0, 2.0, 6.0, 12.0, 20.0, 30.0], index=dates) assert_series_equal(result, expected, obj="Product") result = product(x, Window('1w', 0)) expected = pd.Series([1.0, 2.0, 6.0, 24.0, 120.0, 720.0], index=dates) assert_series_equal(result, expected, obj="Product window 1w") _rolling_1m_test(product, 'prod') def test_std(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 7), dt.date(2019, 1, 8), ] x = pd.Series([3.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=dates) result = std(x) expected = pd.Series([np.nan, 0.707106, 0.577350, 0.957427, 0.894427, 1.673320], index=dates) assert_series_equal(result, expected, obj="std") result = std(x, Window(2, 0)) expected = pd.Series([np.nan, 0.707106, 0.707106, 1.414214, 1.414214, 2.121320], index=dates) assert_series_equal(result, expected, obj="std window 2") result = std(x, Window('1w', 0)) expected = pd.Series([np.nan, 0.707106, 0.577350, 0.957427, 0.894427, 1.870828], index=dates) assert_series_equal(result, expected, obj="std window 1w") assert std(pd.Series(dtype=float)).empty def test_exponential_std(): def exp_std_calc(ts, alpha=0.75): std = ts * 0 for i in range(1, len(ts)): weights = (1 - alpha) * alpha ** np.arange(i, -1, -1) weights[0] /= 1 - alpha x = ts.to_numpy()[: i + 1] ema = sum(weights * x) / sum(weights) debias_fact = sum(weights) ** 2 / (sum(weights) ** 2 - sum(weights**2)) var = debias_fact * sum(weights * (x - ema) ** 2) / sum(weights) std.iloc[i] = np.sqrt(var) std.iloc[0] = np.nan return std dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 7), dt.date(2019, 1, 8), ] x = pd.Series([3.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=dates) result = exponential_std(x) expected = exp_std_calc(x) assert_series_equal(result, expected, obj="Exponentially weighted standard deviation") result = exponential_std(x, 0.8) expected = exp_std_calc(x, 0.8) assert_series_equal(result, expected, obj="Exponentially weighted standard deviation weight 1") def test_var(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 7), dt.date(2019, 1, 8), ] x = pd.Series([3.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=dates) result = var(x) expected = pd.Series([np.nan, 0.500000, 0.333333, 0.916667, 0.800000, 2.800000], index=dates) assert_series_equal(result, expected, obj="var") result = var(x, Window(2, 0)) expected = pd.Series([np.nan, 0.5, 0.5, 2.0, 2.0, 4.5], index=dates) assert_series_equal(result, expected, obj="var window 2") result = var(x, Window('1w', 0)) expected = pd.Series([np.nan, 0.500000, 0.333333, 0.916666, 0.800000, 3.500000], index=dates) assert_series_equal(result, expected, obj="var window 1w") _rolling_1m_test(var, 'var') def test_cov(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 7), dt.date(2019, 1, 8), ] x = pd.Series([3.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=dates) y = pd.Series([3.5, 1.8, 2.9, 1.2, 3.1, 5.9], index=dates) result = cov(x, y) expected = pd.Series([np.nan, 0.850000, 0.466667, 0.950000, 0.825000, 2.700000], index=dates) assert_series_equal(result, expected, obj="cov") result = cov(x, y, Window(2, 0)) expected = pd.Series([np.nan, 0.850000, 0.549999, 1.7000000, 1.900000, 4.200000], index=dates) assert_series_equal(result, expected, obj="cov window 2") result = cov(x, y, Window('1w', 0)) expected = pd.Series([np.nan, 0.850000, 0.466667, 0.950000, 0.825000, 3.375000], index=dates) assert_series_equal(result, expected, obj="cov window 1w") def test_zscores(): with pytest.raises(MqValueError): zscores(pd.Series(range(5)), "2d") assert_series_equal(zscores(pd.Series(dtype=float)), pd.Series(dtype=float)) assert_series_equal(zscores(pd.Series(dtype=float), 1), pd.Series(dtype=float)) assert_series_equal(zscores(pd.Series([1])), pd.Series([0.0])) assert_series_equal(zscores(pd.Series([1]), Window(1, 0)), pd.Series([0.0])) dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 7), dt.date(2019, 1, 8), ] x = pd.Series([3.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=dates) result = zscores(x) expected = pd.Series([0.000000, -0.597614, 0.000000, -1.195229, 0.000000, 1.792843], index=dates) assert_series_equal(result, expected, obj="z-score") assert_series_equal(result, (x - x.mean()) / x.std(), obj="full series zscore") result = zscores(x, Window(2, 0)) expected = pd.Series([0.0, -0.707107, 0.707107, -0.707107, 0.707107, 0.707107], index=dates) assert_series_equal(result, expected, obj="z-score window 2") assert_series_equal(zscores(x, Window(5, 5)), zscores(x, 5)) result = zscores(x, Window('1w', 0)) expected = pd.Series([0.0, -0.707106, 0.577350, -1.305582, 0.670820, 1.603567], index=dates) assert_series_equal(result, expected, obj="z-score window 1w") result = zscores(x, '1w') expected = pd.Series([1.603567], index=dates[-1:]) assert_series_equal(result, expected, obj='z-score window string 1w') result = zscores(x, '1m') expected = pd.Series(dtype=float, index=[]) assert_series_equal(result, expected, obj="z-score window too large") def test_winsorize(): assert_series_equal(winsorize(pd.Series(dtype=float)), pd.Series(dtype=float)) x = generate_series(10000) # You must use absolute returns here, generate_series uses random absolute returns and as such has a decent chance # of going negative on a sample of 10k, if it goes negative the relative return will be garbage and test can fail r = returns(x, type=Returns.ABSOLUTE) for limit in [1.0, 2.0]: mu = r.mean() sigma = r.std() b_upper = mu + sigma * limit * 1.001 b_lower = mu - sigma * limit * 1.001 assert True in r.ge(b_upper).values assert True in r.le(b_lower).values wr = winsorize(r, limit) assert True not in wr.ge(b_upper).values assert True not in wr.le(b_lower).values def test_percentiles(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 7), dt.date(2019, 1, 8), ] x = pd.Series([3.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=dates) y = pd.Series([3.5, 1.8, 2.9, 1.2, 3.1, 6.0], index=dates) y_with_mismatched_index = pd.Series([3.5, 1.8, 2.9, 1.2, 3.1], index=dates[:-1]) assert_series_equal(percentiles(pd.Series(dtype=float), y), pd.Series(dtype=float)) assert_series_equal(percentiles(x, pd.Series(dtype=float)), pd.Series(dtype=float)) assert_series_equal(percentiles(x, y, Window(7, 0)), pd.Series(dtype=float)) result = percentiles(x, y, 2) expected = pd.Series([50.0, 50.0, 100.0, 75.0], index=dates[2:]) assert_series_equal(result, expected, obj="percentiles with window length 2") result = percentiles(x, y, Window(2, 0)) expected = pd.Series([100.0, 0.0, 50.0, 50.0, 100.0, 75.0], index=dates) assert_series_equal(result, expected, obj="percentiles with window 2 and ramp 0") result = percentiles(x, y_with_mismatched_index, Window(2, 0)) expected = pd.Series([100.0, 0.0, 50.0, 50.0, 100.0], index=dates[:-1]) assert_series_equal(result, expected, obj="percentiles with mismatched y index") result = percentiles(x, y, Window('1w', 0)) expected = pd.Series([100.0, 0.0, 33.333333, 25.0, 100.0, 90.0], index=dates) assert_series_equal(result, expected, obj="percentiles with window 1w") result = percentiles(x, y, Window('1w', '3d')) expected = pd.Series([25.0, 100.0, 90.0], index=dates[3:]) assert_series_equal(result, expected, obj="percentiles with window 1w and ramp 3d") result = percentiles(x, y, Window(5, '3d')) expected = pd.Series([25.0, 100.0, 90.0], index=dates[3:]) assert_series_equal(result, expected, obj="percentiles with window 5 and ramp 3d") result = percentiles(x) expected = pd.Series([50.0, 25.0, 66.667, 12.500, 70.0, 91.667], index=dates) assert_series_equal(result, expected, obj="percentiles over historical values") result = percentiles(x, y) expected = pd.Series([100.0, 0.0, 33.333, 25.0, 100.0, 91.667], index=dates) assert_series_equal(result, expected, obj="percentiles without window length", rtol=1e-3) with pytest.raises(ValueError): percentiles(x, pd.Series(dtype=float), Window(6, 1)) def test_percentile(): with pytest.raises(MqError): percentile(pd.Series(dtype=float), -1) with pytest.raises(MqError): percentile(pd.Series(dtype=float), 100.1) with pytest.raises(MqTypeError): percentile(pd.Series(range(5), index=range(5)), 90, "2d") for n in range(0, 101, 5): assert percentile(pd.Series(x * 10 for x in range(0, 11)), n) == n x = percentile(pd.Series(x for x in range(0, 5)), 50, 2) assert_series_equal(x, pd.Series([1.5, 2.5, 3.5], index=pd.RangeIndex(2, 5))) x = percentile(pd.Series(dtype=float), 90, "1d") assert_series_equal(x, pd.Series(dtype=float), obj="Percentile with empty series") def test_percentile_str(): today = dt.datetime.now() days = pd.date_range(today, periods=12, freq='D') start = pd.Series([29, 56, 82, 13, 35, 53, 25, 23, 21, 12, 15, 9], index=days) actual = percentile(start, 2, '10d') expected = pd.Series([12.18, 9.54], index=pd.date_range(today + dt.timedelta(days=10), periods=2, freq='D')) assert_series_equal(actual, expected) actual = percentile(start, 50, '1w') expected = percentile(start, 50, 7) assert_series_equal(actual, expected) def test_regression(): x1 = pd.Series([0.0, 1.0, 4.0, 9.0, 16.0, 25.0, np.nan], index=pd.date_range('2019-1-1', periods=7), name='x1') x2 = pd.Series([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0], index=pd.date_range('2019-1-1', periods=8)) y = pd.Series([10.0, 14.0, 20.0, 28.0, 38.0, 50.0, 60.0], index=pd.date_range('2019-1-1', periods=7)) with pytest.raises(MqTypeError): LinearRegression([x1, x2], y, 1) regression = LinearRegression([x1, x2], y, True) np.testing.assert_almost_equal(regression.coefficient(0), 10.0) np.testing.assert_almost_equal(regression.coefficient(1), 1.0) np.testing.assert_almost_equal(regression.coefficient(2), 3.0) np.testing.assert_almost_equal(regression.r_squared(), 1.0) expected = pd.Series([10.0, 14.0, 20.0, 28.0, 38.0, 50.0], index=pd.date_range('2019-1-1', periods=6)) assert_series_equal(regression.fitted_values(), expected) dates_predict = [dt.date(2019, 2, 1), dt.date(2019, 2, 2)] predicted = regression.predict( [pd.Series([2.0, 3.0], index=dates_predict), pd.Series([6.0, 7.0], index=dates_predict)] ) expected = pd.Series([30.0, 34.0], index=dates_predict) assert_series_equal(predicted, expected) np.testing.assert_almost_equal(regression.standard_deviation_of_errors(), 0) def test_rolling_linear_regression(): x1 = pd.Series([0.0, 1.0, 4.0, 9.0, 16.0, 25.0, np.nan], index=pd.date_range('2019-1-1', periods=7), name='x1') x2 = pd.Series([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0], index=pd.date_range('2019-1-1', periods=8)) y = pd.Series([10.0, 14.0, 20.0, 28.0, 28.0, 40.0, 60.0], index=pd.date_range('2019-1-1', periods=7)) with pytest.raises(MqValueError): RollingLinearRegression([x1, x2], y, 3, True) with pytest.raises(MqTypeError): RollingLinearRegression([x1, x2], y, 4, 1) regression = RollingLinearRegression([x1, x2], y, 4, True) expected = pd.Series([np.nan, np.nan, np.nan, 10.0, 2.5, 19.0], index=pd.date_range('2019-1-1', periods=6)) assert_series_equal(regression.coefficient(0), expected, check_names=False) expected = pd.Series([np.nan, np.nan, np.nan, 1.0, -1.5, 1.0], index=pd.date_range('2019-1-1', periods=6)) assert_series_equal(regression.coefficient(1), expected, check_names=False) expected = pd.Series([np.nan, np.nan, np.nan, 3.0, 12.5, -1.0], index=pd.date_range('2019-1-1', periods=6)) assert_series_equal(regression.coefficient(2), expected, check_names=False) expected = pd.Series([np.nan, np.nan, np.nan, 1.0, 0.964029, 0.901961], index=pd.date_range('2019-1-1', periods=6)) assert_series_equal(regression.r_squared(), expected, check_names=False) expected = pd.Series([np.nan, np.nan, np.nan, 28.0, 28.5, 39.0], index=pd.date_range('2019-1-1', periods=6)) assert_series_equal(regression.fitted_values(), expected, check_names=False) expected = pd.Series([np.nan, np.nan, np.nan, 0.0, 2.236068, 4.472136], index=pd.date_range('2019-1-1', periods=6)) assert_series_equal(regression.standard_deviation_of_errors(), expected, check_names=False) def test_sir_model(): n = 1000 d = 100 i0 = 100 r0 = 0 s0 = n beta = 0.5 gamma = 0.25 t = np.linspace(0, d, d) def deriv(y, t_loc, n_loc, beta_loc, gamma_loc): s, i, r = y dsdt = -beta_loc * s * i / n_loc didt = beta_loc * s * i / n_loc - gamma_loc * i drdt = gamma_loc * i return dsdt, didt, drdt def get_series(beta_loc, gamma_loc): # Initial conditions vector y0 = s0, i0, r0 # Integrate the SIR equations over the time grid, t. ret = odeint(deriv, y0, t, args=(n, beta_loc, gamma_loc)) s, i, r = ret.T dr = pd.date_range(dt.date.today(), dt.date.today() + dt.timedelta(days=d - 1)) return pd.Series(s, dr), pd.Series(i, dr), pd.Series(r, dr) (s, i, r) = get_series(beta, gamma) sir = SIRModel(beta, gamma, s, i, r, n) assert abs(sir.beta() - beta) < 0.01 assert abs(sir.gamma() - gamma) < 0.01 beta = 0.4 gamma = 0.25 (s, i, r) = get_series(0.4, 0.25) s_predict = sir.predict_s() i_predict = sir.predict_i() r_predict = sir.predict_r() assert s_predict.size == d assert i_predict.size == d assert r_predict.size == d with pytest.raises(MqTypeError): SIRModel(beta, gamma, s, i, r, n, fit=0) sir = SIRModel(beta, gamma, s, i, r, n, fit=False) assert sir.beta() == beta assert sir.gamma() == gamma sir1 = SIRModel(beta, gamma, s, i, r, n, fit=False) with DataContext(end=dt.date.today() + dt.timedelta(days=d - 1)): sir2 = SIRModel(beta, gamma, s.iloc[0], i, r.iloc[0], n, fit=False) assert sir1.beta() == sir1.beta() assert sir2.gamma() == sir2.gamma() assert (sir1.predict_i() == sir2.predict_i()).all() assert (sir1.predict_r() == sir2.predict_r()).all() assert (sir1.predict_s() == sir2.predict_s()).all() def test_seir_model(): n = 1000 d = 100 e0 = 1 i0 = 1 r0 = 0 s0 = n beta = 0.5 gamma = 0.2 sigma = 1 t = np.linspace(0, d, d) def deriv(y, t_loc, n_loc, beta_loc, gamma_loc, sigma_loc): s, e, i, r = y dsdt = -beta_loc * s * i / n_loc dedt = beta_loc * s * i / n_loc - sigma_loc * e didt = sigma_loc * e - gamma * i drdt = gamma_loc * i return dsdt, dedt, didt, drdt def get_series(beta_loc, gamma_loc, sigma_loc): # Initial conditions vector y0 = s0, e0, i0, r0 # Integrate the SEIR equations over the time grid, t. ret = odeint(deriv, y0, t, args=(n, beta_loc, gamma_loc, sigma_loc)) s, e, i, r = ret.T dr = pd.date_range(dt.date.today(), dt.date.today() + dt.timedelta(days=d - 1)) return pd.Series(s, dr), pd.Series(e, dr), pd.Series(i, dr), pd.Series(r, dr) (s, e, i, r) = get_series(beta, gamma, sigma) seir = SEIRModel(beta, gamma, sigma, s, e, i, r, n) assert abs(seir.beta() - beta) < 0.01 assert abs(seir.gamma() - gamma) < 0.01 assert abs(seir.sigma() - sigma) < 0.01 s_predict = seir.predict_s() e_predict = seir.predict_e() i_predict = seir.predict_i() r_predict = seir.predict_i() assert s_predict.size == d assert e_predict.size == d assert i_predict.size == d assert r_predict.size == d with pytest.raises(MqTypeError): SEIRModel(beta, gamma, sigma, s, e, i, r, n, fit=0) seir = SEIRModel(beta, gamma, sigma, s, e, i, r, n, fit=False) assert seir.beta() == beta assert seir.gamma() == gamma assert seir.sigma() == sigma seir1 = SEIRModel(beta, gamma, sigma, s, e, i, r, n, fit=False) with DataContext(end=dt.date.today() + dt.timedelta(days=d - 1)): seir2 = SEIRModel(beta, gamma, sigma, s.iloc[0], e.iloc[0], i, r.iloc[0], n, fit=False) assert seir1.beta() == seir1.beta() assert seir2.gamma() == seir2.gamma() assert seir2.sigma() == seir2.sigma() assert (seir1.predict_i() == seir2.predict_i()).all() assert (seir1.predict_e() == seir2.predict_e()).all() assert (seir1.predict_r() == seir2.predict_r()).all() assert (seir1.predict_s() == seir2.predict_s()).all() if __name__ == "__main__": pytest.main(args=["test_statistics.py"]) ================================================ FILE: gs_quant/test/timeseries/test_tca.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on ans "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from unittest.mock import Mock import pandas as pd from testfixtures import Replacer import gs_quant.timeseries.tca as tm from gs_quant.common import AssetClass from gs_quant.markets.index import Index def test_covariance(): mock_spx_1 = Index('MA890', AssetClass.Equity, 'SPX', entity={'type': 'Index', 'underlying_asset_ids': []}) mock_spx_2 = Index('MA890', AssetClass.Equity, 'SPX', entity={'type': 'Index', 'underlying_asset_ids': []}) replace = Replacer() mock_data = pd.DataFrame( data={ "assetId": ["MA00S9PEKCD2NQBD"], "bucketStart": ["19:30:00"], "bucketEnd": ["20:00:00"], "asset2Id": ["MAZYGB8GAYB9ZFQ1"], "covariance": [5.0717e-6], }, index=[pd.Timestamp('2021-12-20'), pd.Timestamp('2021-12-20')], ) mock_request = replace('gs_quant.data.dataset.Dataset.get_data', Mock()) mock_request.return_value = mock_data tm.covariance(mock_spx_1, mock_spx_2, '19:30:00', '20:00:00') replace.restore() ================================================ FILE: gs_quant/test/timeseries/test_technicals.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import numpy as np import pandas as pd import pytest from pandas.testing import assert_series_equal from gs_quant.errors import MqValueError from gs_quant.timeseries import ( moving_average, Window, smoothed_moving_average, macd, bollinger_bands, relative_strength_index, exponential_moving_average, exponential_volatility, exponential_spread_volatility, trend, SeasonalModel, Frequency, seasonally_adjusted, ) def test_moving_average(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 5), dt.date(2019, 1, 6), ] x = pd.Series([3.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=dates) result = moving_average(x) expected = pd.Series([3.0, 2.5, 8 / 3, 2.25, 2.4, 3.0], index=dates) assert_series_equal(result, expected, obj="Moving average") result = moving_average(x, Window(1, 0)) expected = pd.Series([3.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=dates) assert_series_equal(result, expected, obj="Moving average window 1") result = moving_average(x, Window(2, 0)) expected = pd.Series([3.0, 2.5, 2.5, 2.0, 2.0, 4.5], index=dates) assert_series_equal(result, expected, obj="Moving average window 2") result = moving_average(x, "2d") expected = pd.Series([2.5, 2, 2, 4.5], index=dates[2:]) assert_series_equal(result, expected, obj="Moving average strdate window") def test_smoothed_moving_average(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 5), dt.date(2019, 1, 6), ] x = pd.Series([3.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=dates) result = smoothed_moving_average(x) expected = pd.Series([3.00000, 2.83333, 2.86111, 2.55093, 2.62577, 3.18814], index=dates) assert_series_equal(result, expected, obj="Smoothed moving average") result = smoothed_moving_average(x, Window(1, 0)) expected = pd.Series([3.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=dates) assert_series_equal(result, expected, obj="Smoothed moving average window 1") result = smoothed_moving_average(x, Window(2, 0)) expected = pd.Series([3.00000, 2.50000, 2.75000, 1.87500, 2.43750, 4.21875], index=dates) assert_series_equal(result, expected, obj="Smoothed moving average window 2") result = smoothed_moving_average(x, "2d") expected = pd.Series([2.5, 1.75, 2.375, 4.1875], index=dates[2:]) assert_series_equal(result, expected, obj="Smoothed moving average window strdate") result = smoothed_moving_average(x, "1m") expected = pd.Series(dtype=float) assert_series_equal(result, expected, obj="Smoothed moving average with wider window than series") def test_macd(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 5), dt.date(2019, 1, 6), ] x = pd.Series([3.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=dates) assert (macd(x, 10, 10) == 0).all() assert (macd(x, 10, 12, s=1) == macd(x, 10, 12)).all() expected = pd.Series([0, -0.166667, 0.027778, -0.282407, 0.093364, 0.624871], index=dates) assert_series_equal(macd(x, 2, 3), expected) def test_bollinger_bands(): dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 5), dt.date(2019, 1, 6), ] x = pd.Series([3.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=dates) expected_low = pd.Series([np.nan, 1.085786, 1.511966, 0.335146, 0.611146, -0.346640], index=dates) expected_high = pd.Series([np.nan, 3.914214, 3.821367, 4.164854, 4.188854, 6.346640], index=dates) result = bollinger_bands(x) low = result[0].squeeze() high = result[1].squeeze() assert_series_equal(low, expected_low, check_names=False, obj="Bollinger bands low") assert_series_equal(high, expected_high, check_names=False, obj="Bollinger bands high") result = bollinger_bands(x, "2d") print(result) def test_relative_strength_index(): dates = [ dt.date(2020, 1, 2), dt.date(2020, 1, 3), dt.date(2020, 1, 6), dt.date(2020, 1, 7), dt.date(2020, 1, 8), dt.date(2020, 1, 9), dt.date(2020, 1, 10), dt.date(2020, 1, 13), dt.date(2020, 1, 14), dt.date(2020, 1, 15), dt.date(2020, 1, 16), dt.date(2020, 1, 17), dt.date(2020, 1, 21), dt.date(2020, 1, 22), dt.date(2020, 1, 23), dt.date(2020, 1, 24), dt.date(2020, 1, 27), dt.date(2020, 1, 28), dt.date(2020, 1, 29), dt.date(2020, 1, 30), dt.date(2020, 1, 31), dt.date(2020, 2, 3), ] SPX_values = [ 3257.8501, 3234.8501, 3246.28, 3237.1799, 3253.05, 3274.7, 3265.3501, 3288.1299, 3283.1499, 3289.29, 3316.8101, 3329.6201, 3320.79, 3321.75, 3325.54, 3295.47, 3243.6299, 3276.24, 3273.3999, 3283.6599, 3225.52, 3248.9199, ] target_vals = [66.35899, 50.99377, 57.63855, 56.91475, 58.92162, 45.88014, 50.61763] w = 14 SPX = pd.Series(data=SPX_values, index=dates) expected = pd.Series(data=target_vals, index=dates[w + 1 :]) result = relative_strength_index(SPX, w) assert_series_equal(result, expected, check_names=False, obj="Relative Strength Index") increasing_series = pd.Series(np.arange(1, 23, 1), index=dates) expected = pd.Series(data=np.ones(7) * 100, index=dates[15:]) result = relative_strength_index(increasing_series, w) assert_series_equal(result, expected, check_names=False, obj="Relative Strength Index") result = relative_strength_index(SPX, "2w") print(result) def test_exponential_moving_average(): def ema_by_hand(ts, alpha=0.75): R = ts.copy() R *= 0 R.iloc[0] = ts.iloc[0] for i in range(1, len(ts)): R.iloc[i] = alpha * R.iloc[i - 1] + (1 - alpha) * ts.iloc[i] return R dates = [ dt.date(2019, 1, 1), dt.date(2019, 1, 2), dt.date(2019, 1, 3), dt.date(2019, 1, 4), dt.date(2019, 1, 5), dt.date(2019, 1, 6), ] x = pd.Series([3.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=dates) result = exponential_moving_average(x) expected = ema_by_hand(x) assert_series_equal(result, expected, obj="Exponential moving average") result = exponential_moving_average(x, 0.6) expected = ema_by_hand(x, 0.6) assert_series_equal(result, expected, obj="Exponential moving average weight 1") result = exponential_moving_average(x, 0) expected = x assert_series_equal(result, expected, obj="Exponential moving average weight 2") def test_exponential_volatility(): dates = pd.date_range('2019-1-1', periods=6) x = pd.Series([3.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=dates) result = exponential_volatility(x) expected = pd.Series([np.nan, np.nan, 935.41, 810.31, 1958.56, 1710.02], index=dates) assert_series_equal(result, expected, obj="Exponential volatility") def test_exponential_spread_volatility(): dates = pd.date_range('2019-1-1', periods=6) x = pd.Series([3.0, 2.0, 3.0, 1.0, 3.0, 6.0], index=dates) result = exponential_spread_volatility(x) expected = pd.Series([np.nan, np.nan, 22.4499, 20.5757, 28.6067, 34.2183], index=dates) assert_series_equal(result, expected, obj="Exponential spread volatility") def test_trend(): short_dates = pd.date_range('2021-01-01', '2021-01-05', freq='D') short_x = pd.Series(range(len(short_dates)), index=short_dates) with pytest.raises(MqValueError): trend(short_x) # Too few datapoints long_dates = pd.date_range('2017-01-01', '2021-01-01', freq='D') long_x = pd.Series(range(len(long_dates)), index=long_dates) res = trend(long_x) # Should not be all NaN, make sure it's correct length assert len(res) == len(long_x) assert np.isclose(res.iloc[int(len(res) / 2)], long_x.iloc[int(len(res) / 2)], 0.1) assert res.notna().any() with pytest.raises(ValueError): trend(long_x, SeasonalModel.MULTIPLICATIVE) # Should not be all NaN, make sure it's correct length res = trend(long_x + 1, SeasonalModel.MULTIPLICATIVE) # Should not be all NaN, make sure it's correct length assert len(res) == len(long_x) assert np.isclose((res.iloc[int(len(res) / 2)] - 1), long_x.iloc[int(len(res) / 2)], 0.1) assert res.notna().any() with pytest.raises(MqValueError): x = pd.Series(range(10)) trend(x) def test_seasonality_adjusted(): # Test that correctly runs with different frequencies for pfreq in ['B', 'D', 'W', 'ME', 'QE', 'YE']: for freq in [Frequency.YEAR, Frequency.QUARTER, Frequency.MONTH, Frequency.WEEK]: dates = pd.date_range('2019-01-01', '2021-01-05', freq=pfreq) series = pd.Series(range(len(dates)), index=dates) + 1 try: seasonally_adjusted(series, SeasonalModel.ADDITIVE, freq) seasonally_adjusted(series, SeasonalModel.MULTIPLICATIVE, freq) except MqValueError: pass with pytest.raises(MqValueError): x = pd.Series(range(10)) seasonally_adjusted(x) if __name__ == "__main__": pytest.main(args=["test_technicals.py"]) ================================================ FILE: gs_quant/test/timeseries/test_timeseries.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import inspect import re import types import pandas as pd import pytest from gs_quant.markets.securities import Asset from gs_quant.timeseries import Window dummy_series = pd.Series( [ 191.63, 184.31, 184.09, 179.67, 178.83, 176.8, 176.7, 175.92, 172.77, 168.01, 171.5, 169.25, 168.41, 160.05, 156.35, 162.93, 165.41, 163.03, 167.05, 172.03, 169.51, 175.05, 176.02, 175.37, 176.47, 176.0, 176.93, 178.72, 179.91, 197.08, 199.09, 202.54, ] ) dummy_series2 = pd.Series( [ 2790.37, 2700.06, 2695.95, 2633.08, 2637.72, 2636.78, 2651.07, 2650.54, 2599.95, 2545.94, 2546.16, 2506.96, 2467.42, 2416.62, 2351.1, 2467.7, 2488.83, 2485.74, 2506.85, 2510.03, 2447.89, 2531.94, 2549.69, 2574.41, 2584.96, 2596.64, 2596.26, 2582.61, 2610.3, 2616.1, 2635.96, 2670.71, ] ) @pytest.fixture(scope='module') def ts_map(): return { k: v for k, v in globals().items() if isinstance(v, types.FunctionType) and (hasattr(v, 'plot_function') or hasattr(v, 'plot_measure') or hasattr(v, 'plot_measure_entity')) } def test_have_docstrings(ts_map): for k, v in ts_map.items(): assert v.__doc__ def test_window_to_from_dict(): window = Window(w=1, r=2) window_dict = window.as_dict() assert window_dict['w'] == 1 assert window_dict['r'] == 2 window_dict = {'w': 1, 'r': 2} window = Window.from_dict(window_dict) assert window.w == 1 assert window.r == 2 def test_docstrings(ts_map): for k, v in ts_map.items(): print('testing function', k) params = set() has_return = False others = 0 lines = [x.strip() for x in v.__doc__.splitlines()] for line in lines: if not line: continue print(line) if line.startswith(':param'): params.add(re.split('[:\\s]+', line)[2]) elif line.startswith(':return:'): has_return = True else: others += 1 assert params == set(inspect.signature(v).parameters.keys()), 'all parameters documented' assert has_return, 'return value is documented' assert others >= 1, 'at least one line description' def test_annotations(ts_map): for k, v in ts_map.items(): print('testing function', k) annotations = v.__annotations__ assert annotations, 'has annotations' assert 'return' in annotations, 'specifies return type' assert set(inspect.signature(v).parameters.keys()) | {'return'} == set(annotations.keys()), ( 'specifies parameter types' ) def _check_measure_args(params, request_required, fn_name): param = params.popitem() name = param[1].name if request_required: assert name == 'request_id' if request_required or name == 'request_id': assert param[1].kind == inspect.Parameter.KEYWORD_ONLY param = params.popitem() assert param[1].name == 'real_time' assert param[1].kind == inspect.Parameter.KEYWORD_ONLY param = params.popitem() assert param[1].name == 'source' assert param[1].kind == inspect.Parameter.KEYWORD_ONLY counter = 0 while len(params) > 0: param = params.popitem() assert param[1].kind == inspect.Parameter.POSITIONAL_OR_KEYWORD, f'wrong parameter type on {fn_name}' if param[1].annotation == Asset: counter += 1 assert counter < 2, 'no more than 1 extra asset parameter allowed' def test_measures(ts_map): for k, v in ts_map.items(): if not hasattr(v, 'plot_measure'): continue params = inspect.signature(v).parameters.copy() param = params.popitem(last=False) assert param[1].name == 'asset' assert param[1].annotation == Asset assert param[1].kind == inspect.Parameter.POSITIONAL_OR_KEYWORD _check_measure_args(params, False, v.__name__) def test_measures_on_entities(ts_map): for k, v in ts_map.items(): if not hasattr(v, 'plot_measure_entity'): continue params = inspect.signature(v).parameters.copy() param = params.popitem(last=False) assert param[1].name == f'{v.entity_type.value}_id' assert param[1].annotation is str assert param[1].kind == inspect.Parameter.POSITIONAL_OR_KEYWORD _check_measure_args(params, True, v.__name__) if __name__ == '__main__': pytest.main(args=[__file__]) ================================================ FILE: gs_quant/test/timeseries/utils.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import json import pathlib from typing import Union from gs_quant.base import Base def __unpack(results: Union[dict, list], cls: type) -> Union[Base, tuple, dict]: if issubclass(cls, Base): if isinstance(results, list): return tuple(None if r is None else cls.from_dict(r) for r in results) else: return None if results is None else cls.from_dict(results) else: if isinstance(results, list): return tuple(cls(**r) for r in results) else: return cls(**results) def handle_response(res, cls=None): if cls: if isinstance(res, dict) and 'results' in res: res['results'] = __unpack(res['results'], cls) else: res = __unpack(res, cls) return res def mock_request(path, payload=None, cls=None): response = {} "/assets/{id}/xrefs" if '/assets' in path: if path == "/assets/query": bbid = payload.where.bbid with open(pathlib.Path(__file__).parents[1] / f'resources/asset-query-{bbid}.json') as response: response = handle_response(json.load(response), cls) else: split = path.split("/") asset_id = split[2] if len(split) == 3: with open(pathlib.Path(__file__).parents[1] / f'resources/{asset_id}.json') as response: response = handle_response(json.load(response), cls) else: if "xrefs" in path: with open(pathlib.Path(__file__).parents[1] / f'resources/{asset_id}-xrefs.json') as response: response = handle_response(json.load(response), cls) return response ================================================ FILE: gs_quant/test/tracing/__init__.py ================================================ ================================================ FILE: gs_quant/test/tracing/test_tracing.py ================================================ """ Copyright 2025 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import pytest from plotly.graph_objs import Figure from gs_quant.errors import MqError from gs_quant.tracing import Tracer def make_zero_duration(spans): """ Helper function to set the duration of a span to zero """ for span in spans: span.unwrap()._end_time = span.start_time def test_tracer_tags(): Tracer.reset() with Tracer('Some work') as scope: scope.span.set_tag('user', 'martin') spans = Tracer.get_spans() assert len(spans) == 1 assert 'user' in spans[0].tags assert spans[0].tags['user'] == 'martin' def test_tracer_events(): Tracer.reset() with Tracer('Some work') as scope: scope.span.log_kv({"my_event": "yikes"}) scope.span.add_event("Woo hoo!") spans = Tracer.get_spans() assert len(spans) == 1 assert len(spans[0].events) == 2 e1 = spans[0].events[0] e2 = spans[0].events[1] assert e1.name == "log" # default name if "event" not specified assert "my_event" in e1.attributes assert abs(e1.timestamp_sec - dt.datetime.now().timestamp()) < 2 # to make sure we're used right scale factor assert e2.name == "Woo hoo!" assert e2.attributes == {} def test_tracer_print(): Tracer.reset() with Tracer('A'): with Tracer('B'): pass with Tracer('C'): with Tracer('D'): pass try: with Tracer('E'): raise ValueError("test error handle") except Exception: pass with Tracer('F'): pass # Force elapsed time to 0 to make sure no spurious tiny times make_zero_duration(Tracer.get_spans()) tracer_str, _ = Tracer.print(reset=True) expected = '\n'.join( [ 'A 0.0 ms', '* B 0.0 ms', '* C 0.0 ms', '* * D 0.0 ms', '* * E 0.0 ms [Error]', 'F 0.0 ms', ] ) assert tracer_str == expected def test_tracer_plot(): Tracer.reset() with Tracer("A") as scope: scope.span.set_tag("hello", "world") with Tracer("B"): Tracer.record_exception(ValueError("ah")) fig = Tracer.plot(True, False) assert isinstance(fig, Figure) def test_gather_when_multi_traces(): Tracer.reset() with Tracer("A") as first_scope: first_scope.span.set_tag("hello", "world") first_trace_id = first_scope.span.trace_id with Tracer("B1"): pass with Tracer("B2"): pass with Tracer("C") as second_scope: second_trace_id = second_scope.span.trace_id with Tracer("B1"): pass data1, total = Tracer.gather_data(False, trace_id=first_trace_id) data2, total = Tracer.gather_data(False, trace_id=second_trace_id) assert len(data1) == 3 assert len(data2) == 2 def test_tracer_wrapped_error(): Tracer.reset() with pytest.raises(MqError, match='Unable to calculate: Outer Thing'): with Tracer('Outer Thing', wrap_exceptions=True): with Tracer( 'Inner Thing', ): raise KeyError('meaningless error') spans = Tracer.get_spans() assert 'error' in spans[0].tags assert 'error' in spans[1].tags Tracer.reset() with pytest.raises(MqError, match='Unable to calculate: Inner Thing'): with Tracer('Outer Thing', wrap_exceptions=True): with Tracer('Inner Thing', wrap_exceptions=True): raise KeyError('meaningless error') assert 'error' in spans[0].tags assert 'error' in spans[1].tags Tracer.reset() def test_active_span(): Tracer.reset() inactive = Tracer.active_span() # We get a noop/non-recording span assert inactive is not None assert inactive.span_id is not None assert inactive.trace_id is not None assert inactive.is_recording() is False inactive.set_tag("dummy", "tag") # should be a no-op, but not throw inactive.add_event("Dummy event") # should be a no-op, but not throw with Tracer.activate_span(inactive) as scope: # This shouldn't have done anything material, since it's a non-recording span assert scope.span is not None assert scope.span.span_id is not None assert scope.span.trace_id is not None assert scope.span.is_recording() is False with Tracer('Outer') as scope: scoped_span = scope.span active_span = Tracer.active_span() # should be same as scoped_span, just another way to get it assert active_span.span_id == scoped_span.span_id assert active_span.trace_id == scoped_span.trace_id assert len(Tracer.get_spans()) == 1 # only "Outer" did anything def test_span_activation(): Tracer.reset() with Tracer('parent') as parent_scope: outer_span = parent_scope.span with Tracer('child-1') as child1_scope: with Tracer.activate_span(outer_span): with Tracer('child-2') as inner_scope: # Since we re-activated the outer span, child-2's parent should be the outer "parent" span assert inner_scope.span is not None assert inner_scope.span.parent_id == outer_span.span_id # Now that we've exited the re-activated context, this span will be nested under child-1 with Tracer('nested-child') as nested_child: assert nested_child.span.parent_id == child1_scope.span.span_id # This is a shortcut of with Tracer.activate_span(outer_span), Tracer('child-3') with Tracer('child-3', parent_span=outer_span) as another_inner_scope: assert another_inner_scope.span is not None assert another_inner_scope.span.parent_id == outer_span.span_id # Again we should be back in the "child-1" context with Tracer('another-nested-child') as nested_child: assert nested_child.span.parent_id == child1_scope.span.span_id # Force elapsed time to 0 to make sure no spurious tiny times make_zero_duration(Tracer.get_spans()) tracer_str, _ = Tracer.print(reset=True) expected = '\n'.join( [ 'parent 0.0 ms', '* child-2 0.0 ms', '* child-3 0.0 ms', '* child-1 0.0 ms', '* * nested-child 0.0 ms', '* * another-nested-child 0.0 ms', ] ) assert tracer_str == expected def test_inject_extract(): Tracer.reset() with Tracer('A') as scope: span_a = scope.span scope.span.set_tag('user', 'bob') fake_http_headers = {} Tracer.inject(fake_http_headers) spans = Tracer.get_spans() assert len(spans) == 1 assert 'user' in spans[0].tags assert spans[0].tags['user'] == 'bob' assert len(fake_http_headers) > 0 # we're agnostic to the inject/extractor, so long as it's done something ctx = Tracer.extract(fake_http_headers) with Tracer.start_active_span('B', child_of=ctx) as scope: assert scope.span.parent_id == span_a.span_id with Tracer('C', parent_span=ctx) as scope: assert scope.span.parent_id == span_a.span_id def test_ignore_active_span(): with Tracer('A') as scope_a: with Tracer.start_active_span('B', ignore_active_span=True) as scope_b: assert scope_b.span.parent_id is None with Tracer.start_active_span('C') as scope_c: assert scope_c.span.parent_id == scope_a.span.span_id @pytest.mark.asyncio async def test_callback_in_scope(): import asyncio Tracer.reset() finished = asyncio.Future() def my_callback_on_complete(future): with Tracer('Callback Work') as scope: scope.span.set_tag('callback', 'yes') assert future.result() == "done" finished.set_result(True) async def some_async_work(): with Tracer('Async Work') as scope: scope.span.set_tag('async', 'yes') return "done" with Tracer('Main Work') as main_scope: main_scope.span.set_tag('main', 'yes') task = asyncio.create_task(some_async_work()) task.add_done_callback(Tracer.in_scope(my_callback_on_complete)) await task await finished assert finished.result() spans = Tracer.get_spans() assert len(spans) == 4 Tracer.print() main_span = next(span for span in spans if span.operation_name == 'Main Work') async_span = next(span for span in spans if span.operation_name == 'Async Work') callback_span = next(span for span in spans if span.operation_name == 'callback') callback_work_span = next(span for span in spans if span.operation_name == 'Callback Work') assert main_span.tags['main'] == 'yes' assert async_span.tags['async'] == 'yes' assert async_span.parent_id == main_span.span_id assert callback_span.parent_id == main_span.span_id assert callback_work_span.parent_id == callback_span.span_id ================================================ FILE: gs_quant/test/utils/__init__.py ================================================ from .mock_calc import * from .mock_data import * ================================================ FILE: gs_quant/test/utils/datagrid_test_utils.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import json import pathlib from gs_quant.common import Currency from gs_quant.markets.securities import Stock def _read_entity(entity): with open(pathlib.Path(__file__).parents[1] / f'resources/{entity}.json') as entity: return json.loads(entity.read()) def get_test_entity(entity_id: str): entity = _read_entity(entity_id) return Stock(id_=entity_id, name=entity['name'], currency=Currency.USD, entity=entity) ================================================ FILE: gs_quant/test/utils/mock_calc.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import inspect from pathlib import Path from gs_quant.api.gs.risk import GsRiskApi from gs_quant.common import CompositeScenario from gs_quant.datetime import business_day_offset from gs_quant.test.utils.mock_request import MockRequest def get_risk_request_id(requests): """ This is not a formal equality of the risk request as it covers only the names of core components. When a formal eq function is provided on risk_request then this should be replaced with something derived from that. :param requests: a collection of RiskRequests :type requests: tuple of RiskRequest :return: hash :rtype: str """ identifier = str(len(requests)) for request in requests: for pos in request.positions: if pos.instrument.name is None: raise ValueError('Positions must have names to be mocked') identifier += '_' identifier += '-'.join([pos.instrument.name for pos in request.positions]) identifier += '-'.join([r.__repr__() for r in request.measures]) date = request.pricing_and_market_data_as_of[0].pricing_date.strftime('%Y%b%d') today_pre = business_day_offset(dt.date.today(), 0, roll='preceding').strftime('%Y%b%d') today_post = business_day_offset(dt.date.today(), 0, roll='following').strftime('%Y%b%d') identifier += 'today' if date in (today_post, today_pre) else date if request.scenario is not None: if isinstance(request.scenario.scenario, CompositeScenario): underlying_scenarios = request.scenario.scenario.scenarios if any([scen.name is None for scen in underlying_scenarios]): raise RuntimeError('Please provide unique names for your scenarios for testing') identifier += '+'.join(sorted([r.name for r in underlying_scenarios])) else: if request.scenario.scenario.name is None: raise RuntimeError('Please provide unique names for your scenarios for testing') identifier += request.scenario.scenario.name return identifier class MockCalc(MockRequest): api = GsRiskApi method = '_exec' api_method = GsRiskApi._exec def __init__(self, mocker, save_files=False, paths=None, application='gs-quant'): super().__init__(mocker, save_files, paths, application) self.paths = ( paths if paths else Path( next( filter(lambda x: x.code_context and self.__class__.__name__ in x.code_context[0], inspect.stack()) ).filename ).parents[1] ) def mock_calc_create_files(self, *args, **kwargs): import orjson # never leave a side_effect calling this function. Call it once to create the files, check them in # and switch to mock_calc def get_json(*i_args, **i_kwargs): this_json = self.api_method(*i_args, **i_kwargs) # Post process the json a bit to remove timing info that makes spurious diffs for d in [d for a in this_json for b in a for c in b for d in c]: for key, val in list(d.items()): if val is None or key in ['queueingTime', 'calculationTime']: del d[key] return this_json result = get_json(*args, **kwargs) result_json = orjson.dumps( result, option=orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_NON_STR_KEYS | orjson.OPT_SORT_KEYS ) request_id = self.get_request_id(args, kwargs) self.create_files(request_id, result_json) return result def get_request_id(self, args, kwargs): request = kwargs.get('request') or args[0] request_id = get_risk_request_id(request) return request_id ================================================ FILE: gs_quant/test/utils/mock_data.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import inspect from pathlib import Path from gs_quant.api.gs.data import GsDataApi from gs_quant.test.utils.mock_request import MockRequest class MockData(MockRequest): api = GsDataApi method = 'query_data' api_method = GsDataApi.query_data def __init__(self, mocker, save_files=False, paths=None, application='gs-quant'): super().__init__(mocker, save_files, paths, application) self.paths = ( paths if paths else Path( next( filter(lambda x: x.code_context and self.__class__.__name__ in x.code_context[0], inspect.stack()) ).filename ).parents[1] ) def mock_calc_create_files(self, *args, **kwargs): import orjson def get_json(*i_args, **i_kwargs): this_json = self.api_method(*i_args, **i_kwargs) return this_json result = get_json(*args, **kwargs) result_json = orjson.dumps( result, option=orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_NON_STR_KEYS | orjson.OPT_SORT_KEYS ) request_id = self.get_request_id(args, kwargs) self.create_files(request_id, result_json) return result def get_request_id(self, args, kwargs): query = args[0].as_dict() parts = [] relevant_data_query_params = [ k for k in ["start_date", "start_time", "end_date", "end_time", "as_of_time", "since", "dates", "where"] if k in query.keys() ] def stringify_values(v): return ','.join(v) if isinstance(v, list) else str(v) for k in sorted(relevant_data_query_params): v = query[k] if k != 'where': v_str = stringify_values(v) else: v_str = '_'.join([f'{where_k}:{stringify_values(where_v)}' for where_k, where_v in v.items()]) parts.append(f'{k}:{v_str}') query_str = '_'.join(parts) return f'{args[1]}_{query_str}' ================================================ FILE: gs_quant/test/utils/mock_request.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import hashlib import json import logging import os from abc import abstractmethod from os.path import exists from pathlib import Path from typing import List, Dict, Tuple, Any, NamedTuple from unittest import mock from gs_quant.errors import MqUninitialisedError from gs_quant.session import GsSession, Environment from gs_quant.test.mock_data_test_utils import log_mock_data_event logger = logging.getLogger("mock_request") class MockFileKey: def __init__(self, request_id): self.request_id = request_id.decode('utf-8') if isinstance(request_id, bytes) else request_id self.request_hash = self.get_hash(self.request_id) self.file_name = f'request{self.request_hash}.json' @staticmethod def get_hash(request_id: str): id_bytes = request_id.encode('utf-8') return hashlib.md5(id_bytes).hexdigest() def __eq__(self, other): return ( self.file_name == other.file_name and self.request_id == other.request_id and self.request_hash == other.request_hash ) def __hash__(self): return hash((self.file_name, self.request_id, self.request_hash)) def __repr__(self): return f"MockFileKey({self.request_hash})" class MockFileRecord(NamedTuple): """a docstring""" foo: int bar: str used_in_tests: set class MockRequest: __looked_at_files = {} __cached_test_data = {} __saved_files = set() api = None method = None api_method = None def __init__(self, mocker, save_files=False, paths=None, application='gs-quant'): # do not save tests with save_files = True self.save_files = save_files self.mocker = mocker self.paths = paths self.application = application if any([attr is None for attr in [self.api, self.method, self.api_method]]): raise NotImplementedError('Mock Subclasses must implement api and method properties') def __enter__(self): if self.save_files: GsSession.use(Environment.PROD, None, None, application=self.application) self.mocker.patch.object( self.api, self.method, side_effect=self.mock_calc_create_new_files if str(self.save_files).casefold() == 'new' else self.mock_calc_check_and_create_files, ) else: try: _ = GsSession.current except MqUninitialisedError: from gs_quant.session import OAuth2Session OAuth2Session._authenticate = mock.MagicMock(return_value=None) GsSession.use(Environment.PROD, 'fake_client_id', 'fake_secret', application=self.application) self.mocker.patch.object(self.api, self.method, side_effect=self.mock_calc) def full_file_path(self, mock_file_key: MockFileKey): return self.build_file_path(self.paths, mock_file_key) @staticmethod def build_file_path(base_path, mock_file_key: MockFileKey): return base_path / 'calc_cache' / mock_file_key.file_name def mock_calc(self, *args, **kwargs): request_id = self.get_request_id(args, kwargs) mfk = MockFileKey(request_id) file_path = self.full_file_path(mfk) if not exists(file_path): logger.error( f'Unable to find mock test date for {self.__class__.__name__}:\n' f'File {file_path} not found. Key: {request_id}' ) raise FileNotFoundError(f'File {mfk.file_name} not found in {self.paths / "calc_cache"}') existing_meta, mock_data = self._load_mock_data_from_file(file_path) self._record_file_used_in_test(mfk) return mock_data def _record_file_used_in_test(self, mfk: MockFileKey): test_name = self._get_current_test_name() file_deets = MockRequest.__looked_at_files.setdefault(self.paths, dict()) file_deets.setdefault(mfk, (self.__class__.__name__, set()))[1].add(test_name) @staticmethod def _get_current_test_name(): return os.environ.get('PYTEST_CURRENT_TEST').split(':')[-1].split(' ')[0] @classmethod def _load_mock_data_from_file(cls, file_path): meta = {} data = cls.__cached_test_data.get(file_path) if data is None: with open(file_path) as json_data: data = json.load(json_data) cls.__cached_test_data[file_path] = data if isinstance(data, dict) and 'mocked_data' in data: meta = data data = json.loads(data['mocked_data']) return meta, data @classmethod def _make_mock_file_data(cls, mfk: MockFileKey, cls_name, mock_data_str, used_in_tests): return { 'request_id': mfk.request_id, 'request_hash': mfk.request_hash, 'type': cls_name, 'tests': used_in_tests, 'mocked_data': mock_data_str, } def create_files(self, request_id, result_json): cls_name = self.__class__.__name__ ct = self._get_current_test_name() mfk = MockFileKey(request_id) file_path = self.full_file_path(mfk) mock_data = self._make_mock_file_data(mfk, cls_name, result_json.decode('utf-8'), [ct]) with open(file_path, 'w') as file_data: MockRequest.__saved_files.add(request_id) file_data.write(json.dumps(mock_data, indent=4)) log_mock_data_event(f'Created {mfk.file_name}.') self._record_file_used_in_test(mfk) @abstractmethod def mock_calc_create_files(self, *args, **kwargs): ... def mock_calc_check_and_create_files(self, *args, **kwargs): # Check if we have already saved this file in this test run, in which case skip re-saving it request_id = self.get_request_id(args, kwargs) if request_id in MockRequest.__saved_files: res = self.mock_calc(*args, **kwargs) # This file was remade this session so we need to reindex it here self._reindex_single_file(MockFileKey(request_id)) return res return self.mock_calc_create_files(*args, **kwargs) def mock_calc_create_new_files(self, *args, **kwargs): request_id = self.get_request_id(args, kwargs) full_file_path = self.full_file_path(MockFileKey(request_id)) file_exists = exists(full_file_path) if file_exists: return self.mock_calc(*args, **kwargs) else: return self.mock_calc_create_files(*args, **kwargs) def __exit__(self, exc_type, exc_val, exc_tb): pass @staticmethod def get_file_summary() -> Dict[Path, Dict[MockFileKey, Tuple[List[str], Tuple[str, str, str]]]]: return MockRequest.__looked_at_files @classmethod def get_unused_files(cls, log=False) -> Tuple[Dict[str, Any], ...]: file_summary = cls.get_file_summary() unused_files = [] for path, files_used in file_summary.items(): files_names_used = set(m.file_name for m in files_used.keys()) for test_file in os.listdir(path / 'calc_cache'): if test_file.endswith('.json') and test_file not in files_names_used: full_path = path / 'calc_cache' / test_file try: unused_meta, _ = cls._load_mock_data_from_file(full_path) except Exception: unused_meta = {'tests': ['UNKNOWN']} if log: log_mock_data_event(f'Noticed UNUSED {test_file}.') unused_details = { 'file': test_file, 'full_path': full_path, 'tests': tuple(unused_meta.get('tests', [])), } unused_files.append(unused_details) return tuple(unused_files) @classmethod def remove_unused_files(cls): unused_files = cls.get_unused_files() for file in unused_files: try: log_mock_data_event(f'Removing {file["file"]}. As not used.') os.remove(file['full_path']) except Exception: continue @classmethod def reindex_test_files(cls, report_only=False, log=False) -> Tuple[str, ...]: reindex_files = [] file_summary = cls.get_file_summary() for path, files_used in file_summary.items(): for mock_file_key, (cls_name, now_used) in files_used.items(): updated = cls._update_single_file_with_new_tests(path, mock_file_key, now_used, report_only, log) if updated: reindex_files.append(mock_file_key.file_name) return tuple(reindex_files) @classmethod def _update_single_file_with_new_tests(cls, base_path, mock_file_key, new_tests, report_only, log): full_file_path = cls.build_file_path(base_path, mock_file_key) existing_meta, data = cls._load_mock_data_from_file(full_file_path) existing_used = tuple(existing_meta.get('tests', [])) sorted_tests = tuple(sorted(new_tests)) if existing_used == sorted_tests: return False if report_only: if log: missing = set(existing_used) - set(sorted_tests) extra = set(sorted_tests) - set(existing_used) missing_txt = ( f" {len(missing)} test(s) are listed in the file but weren't used: {missing}." if len(missing) else "" ) extra_txt = ( f" {len(extra)} test(s) that accessed this file were NOT listed in the file : {extra}." if len(extra) else "" ) log_mock_data_event(f'Noticed BAD INDEX {mock_file_key.file_name}. {missing_txt}{extra_txt}') return True new_data = cls._make_mock_file_data(mock_file_key, existing_meta['type'], json.dumps(data), sorted_tests) # update the cached in memory test data with new contents cls.__cached_test_data[full_file_path] = new_data # Write the updated mock file with open(full_file_path, 'w') as out_file: out_file.write(json.dumps(new_data, indent=4)) log_mock_data_event(f'Updated {mock_file_key.file_name}. As {existing_used} != {sorted_tests}') return True def _reindex_single_file(self, mock_file_key: MockFileKey): file_summary = self.get_file_summary() by_path = file_summary.get(self.paths, dict()) mock_file_usage = by_path.get(mock_file_key) if mock_file_usage is None: return test_used = mock_file_usage[1] self._update_single_file_with_new_tests(self.paths, mock_file_key, test_used, report_only=False, log=True) @staticmethod def get_saved_files() -> List[str]: return list(MockRequest.__saved_files) @abstractmethod def get_request_id(self, args, kwargs): ... ================================================ FILE: gs_quant/test/utils/test_utilities.py ================================================ """ Copyright 2023 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import unittest import datetime as dt from gs_quant.data.utilities import SecmasterXrefFormatter class TestSecmasterXrefFormatter(unittest.TestCase): def setUp(self): """Set up test fixtures before each test method.""" self.formatter = SecmasterXrefFormatter() def test_event_creation_and_priority(self): """Test Event dataclass creation and priority assignment.""" start_event = SecmasterXrefFormatter.Event( date="2023-01-01", event_type=SecmasterXrefFormatter.EventType.START, record={"type": "CUSIP", "value": "12345", "startDate": "2023-01-01", "endDate": "2023-12-31"}, ) end_event = SecmasterXrefFormatter.Event( date="2023-01-01", event_type=SecmasterXrefFormatter.EventType.END, record={"type": "ISIN", "value": "US12345", "startDate": "2023-01-01", "endDate": "2023-12-31"}, ) self.assertEqual(start_event.priority, 0) self.assertEqual(end_event.priority, 1) def test_date_sort_key(self): """Test date sorting functionality.""" normal_date = "2023-01-01" infinity_date = SecmasterXrefFormatter.INFINITY_DATE normal_key = SecmasterXrefFormatter._date_sort_key(normal_date) infinity_key = SecmasterXrefFormatter._date_sort_key(infinity_date) self.assertEqual(normal_key, dt.datetime(2023, 1, 1)) self.assertEqual(infinity_key, dt.datetime(9999, 12, 31)) self.assertTrue(infinity_key > normal_key) def test_add_one_day(self): """Test adding one day to various date scenarios.""" # Normal date result = SecmasterXrefFormatter._add_one_day("2023-01-01") self.assertEqual(result, "2023-01-02") # End of month result = SecmasterXrefFormatter._add_one_day("2023-01-31") self.assertEqual(result, "2023-02-01") # End of year result = SecmasterXrefFormatter._add_one_day("2023-12-31") self.assertEqual(result, "2024-01-01") # Leap year result = SecmasterXrefFormatter._add_one_day("2024-02-28") self.assertEqual(result, "2024-02-29") # Infinity date result = SecmasterXrefFormatter._add_one_day(SecmasterXrefFormatter.INFINITY_DATE) self.assertIsNone(result) # Invalid date result = SecmasterXrefFormatter._add_one_day("invalid-date") self.assertIsNone(result) def test_subtract_one_day(self): """Test subtracting one day from various date scenarios.""" # Normal date result = SecmasterXrefFormatter._subtract_one_day("2023-01-02") self.assertEqual(result, "2023-01-01") # Beginning of month result = SecmasterXrefFormatter._subtract_one_day("2023-02-01") self.assertEqual(result, "2023-01-31") # Beginning of year result = SecmasterXrefFormatter._subtract_one_day("2024-01-01") self.assertEqual(result, "2023-12-31") # Invalid date (should return original) result = SecmasterXrefFormatter._subtract_one_day("invalid-date") self.assertEqual(result, "invalid-date") def test_create_events(self): """Test event creation from records.""" records = [ {"type": "CUSIP", "value": "12345", "startDate": "2023-01-01", "endDate": "2023-06-30"}, { "type": "ISIN", "value": "US12345", "startDate": "2023-07-01", "endDate": SecmasterXrefFormatter.INFINITY_DATE, }, ] events = SecmasterXrefFormatter._create_events(records) # Should have 3 events: 2 start events + 1 end event (no end event for infinity) self.assertEqual(len(events), 3) # Check start events start_events = [e for e in events if e.event_type == SecmasterXrefFormatter.EventType.START] self.assertEqual(len(start_events), 2) # Check end events end_events = [e for e in events if e.event_type == SecmasterXrefFormatter.EventType.END] self.assertEqual(len(end_events), 1) self.assertEqual(end_events[0].date, "2023-07-01") # Day after end date def test_convert_empty_data(self): """Test conversion with empty data.""" result = SecmasterXrefFormatter.convert({}) self.assertEqual(result, {}) result = SecmasterXrefFormatter.convert({"entity1": []}) self.assertEqual(result, {"entity1": {"xrefs": []}}) def test_convert_simple_case(self): """Test conversion with a simple single identifier case.""" data = {"entity1": [{"type": "CUSIP", "value": "12345", "startDate": "2023-01-01", "endDate": "2023-12-31"}]} result = SecmasterXrefFormatter.convert(data) expected = { "entity1": { "xrefs": [{"startDate": "2023-01-01", "endDate": "2023-12-31", "identifiers": {"CUSIP": "12345"}}] } } self.assertEqual(result, expected) def test_convert_infinity_marker_normalization(self): """Test that infinity markers are converted to infinity dates.""" data = { "entity1": [ { "type": "CUSIP", "value": "12345", "startDate": "2023-01-01", "endDate": SecmasterXrefFormatter.INFINITY_MARKER, } ] } result = SecmasterXrefFormatter.convert(data) expected = { "entity1": { "xrefs": [ { "startDate": "2023-01-01", "endDate": SecmasterXrefFormatter.INFINITY_DATE, "identifiers": {"CUSIP": "12345"}, } ] } } self.assertEqual(result, expected) def test_convert_overlapping_periods(self): """Test conversion with overlapping identifier periods.""" data = { "entity1": [ {"type": "CUSIP", "value": "12345", "startDate": "2023-01-01", "endDate": "2023-06-30"}, {"type": "ISIN", "value": "US12345", "startDate": "2023-03-01", "endDate": "2023-09-30"}, ] } result = SecmasterXrefFormatter.convert(data) xrefs = result["entity1"]["xrefs"] # Should have 3 periods self.assertEqual(len(xrefs), 3) # Period 1: Only CUSIP (Jan 1 - Feb 28) self.assertEqual(xrefs[0]["startDate"], "2023-01-01") self.assertEqual(xrefs[0]["endDate"], "2023-02-28") self.assertEqual(xrefs[0]["identifiers"], {"CUSIP": "12345"}) # Period 2: Both CUSIP and ISIN (Mar 1 - Jun 30) self.assertEqual(xrefs[1]["startDate"], "2023-03-01") self.assertEqual(xrefs[1]["endDate"], "2023-06-30") self.assertEqual(xrefs[1]["identifiers"], {"CUSIP": "12345", "ISIN": "US12345"}) # Period 3: Only ISIN (Jul 1 - Sep 30) self.assertEqual(xrefs[2]["startDate"], "2023-07-01") self.assertEqual(xrefs[2]["endDate"], "2023-09-30") self.assertEqual(xrefs[2]["identifiers"], {"ISIN": "US12345"}) def test_convert_adjacent_periods(self): """Test conversion with adjacent periods (no gaps).""" data = { "entity1": [ {"type": "CUSIP", "value": "12345", "startDate": "2023-01-01", "endDate": "2023-06-30"}, {"type": "CUSIP", "value": "67890", "startDate": "2023-07-01", "endDate": "2023-12-31"}, ] } result = SecmasterXrefFormatter.convert(data) xrefs = result["entity1"]["xrefs"] # Should have 2 periods with no gaps self.assertEqual(len(xrefs), 2) self.assertEqual(xrefs[0]["startDate"], "2023-01-01") self.assertEqual(xrefs[0]["endDate"], "2023-06-30") self.assertEqual(xrefs[0]["identifiers"], {"CUSIP": "12345"}) self.assertEqual(xrefs[1]["startDate"], "2023-07-01") self.assertEqual(xrefs[1]["endDate"], "2023-12-31") self.assertEqual(xrefs[1]["identifiers"], {"CUSIP": "67890"}) def test_convert_same_date_start_end(self): """Test conversion when identifiers start and end on the same date.""" data = { "entity1": [ {"type": "CUSIP", "value": "12345", "startDate": "2023-01-01", "endDate": "2023-06-30"}, {"type": "ISIN", "value": "US12345", "startDate": "2023-06-30", "endDate": "2023-12-31"}, ] } result = SecmasterXrefFormatter.convert(data) xrefs = result["entity1"]["xrefs"] # Should handle the overlap correctly self.assertTrue(len(xrefs) >= 1) def test_convert_multiple_entities(self): """Test conversion with multiple entities.""" data = { "entity1": [{"type": "CUSIP", "value": "12345", "startDate": "2023-01-01", "endDate": "2023-12-31"}], "entity2": [{"type": "ISIN", "value": "US67890", "startDate": "2023-01-01", "endDate": "2023-12-31"}], } result = SecmasterXrefFormatter.convert(data) self.assertIn("entity1", result) self.assertIn("entity2", result) self.assertEqual(len(result["entity1"]["xrefs"]), 1) self.assertEqual(len(result["entity2"]["xrefs"]), 1) self.assertEqual(result["entity1"]["xrefs"][0]["identifiers"], {"CUSIP": "12345"}) self.assertEqual(result["entity2"]["xrefs"][0]["identifiers"], {"ISIN": "US67890"}) def test_event_sorting(self): """Test that events are sorted correctly by date and priority.""" events = [ SecmasterXrefFormatter.Event("2023-01-02", SecmasterXrefFormatter.EventType.START, {}), SecmasterXrefFormatter.Event("2023-01-01", SecmasterXrefFormatter.EventType.END, {}), SecmasterXrefFormatter.Event("2023-01-01", SecmasterXrefFormatter.EventType.START, {}), SecmasterXrefFormatter.Event("2023-01-02", SecmasterXrefFormatter.EventType.END, {}), ] events.sort(key=lambda e: (SecmasterXrefFormatter._date_sort_key(e.date), e.priority)) # Should be ordered: 2023-01-01 START, 2023-01-01 END, 2023-01-02 START, 2023-01-02 END self.assertEqual(events[0].date, "2023-01-01") self.assertEqual(events[0].event_type, SecmasterXrefFormatter.EventType.START) self.assertEqual(events[1].date, "2023-01-01") self.assertEqual(events[1].event_type, SecmasterXrefFormatter.EventType.END) self.assertEqual(events[2].date, "2023-01-02") self.assertEqual(events[2].event_type, SecmasterXrefFormatter.EventType.START) self.assertEqual(events[3].date, "2023-01-02") self.assertEqual(events[3].event_type, SecmasterXrefFormatter.EventType.END) if __name__ == '__main__': # Run the tests unittest.main() ================================================ FILE: gs_quant/test/utils/test_utils.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import json import pathlib from json.encoder import JSONEncoder import pytest from gs_quant.test.mock_data_test_utils import did_anything_fail, did_anything_run from gs_quant.test.utils.mock_request import MockRequest @pytest.mark.second_to_last @pytest.mark.fixmockdata def test_fix_mock_data(): # This will only be run if you use "--fixmockdata" option to pytest if did_anything_fail(): pytest.skip("Skipping test as another test failed") if not did_anything_run(): pytest.skip("Skipping test as nothing ran or not setup correctly") MockRequest.reindex_test_files() MockRequest.remove_unused_files() @pytest.mark.order("last") def test_mock_data_file_sanity(): # Important that this test runs last, it asserts all the test files are used so we can cleanup unused ones saved_files = MockRequest.get_saved_files() assert [] == saved_files, 'Did you accidentally commit with save_files=True?!' tests_passed = not did_anything_fail() suffix = ( '(Other tests FAILED this is probably a red herring)' if not tests_passed else 'Run pytest with --fixmockdata to fix this!' ) bad_index = MockRequest.reindex_test_files(report_only=True, log=tests_passed) assert bad_index == tuple(), f'Files with bad index. {suffix}' unused_files = MockRequest.get_unused_files(log=tests_passed) assert unused_files == tuple(), f'Cleanup your unused test files! {suffix}' def _remove_unwanted(json_text): json_dict = json.loads(json_text) if "asOfTime" in json_dict: del json_dict["asOfTime"] return json_dict def load_json_from_resource(test_file_name, json_file_name): with open(pathlib.Path(__file__).parents[1] / f'resources/{test_file_name}/{json_file_name}') as json_data: return json.load(json_data) def mock_request(method, path, payload, test_file_name): queries = { 'assetsDataGSNWithRic': '{"asOfTime": "2019-05-16T21:18:18.294Z", "limit": 4, "where": {"ric": ["GS.N"]}, "fields": ["ric", "id"]}', 'assetsDataGSNWithId': '{"limit": 4, "fields": ["id", "ric"], "where": {"id": ["123456MW5E27U123456"]}}', 'assetsDataSPXWithRic': '{"where": {"ric": [".SPX"]}, "limit": 4, "fields": ["ric", "id"]}', 'assetsDataSPXWithId': '{"limit": 4, "fields": ["id", "ric"], "where": {"id": ["456123MW5E27U123456"]}}', 'dataQueryRic': '{"fields": ["adjustedTradePrice"],' ' "format": "MessagePack", "where": {"assetId": ["123456MW5E27U123456"]}}', 'dataQuerySPX': '{"fields": ["adjustedTradePrice"], "format": "MessagePack", "where": {"assetId": ["456123MW5E27U123456"]}}', } payload = _remove_unwanted(json.dumps(payload, cls=JSONEncoder) if payload else '{}') if method == 'GET': if path == '/data/datasets/TREOD': return load_json_from_resource(test_file_name, 'datasets_treod_response.json') elif method == 'POST': if path == '/assets/data/query': if payload == _remove_unwanted(queries['assetsDataGSNWithRic']) or payload == _remove_unwanted( queries['assetsDataGSNWithId'] ): return load_json_from_resource(test_file_name, 'assets_data_query_response_gsn.json') elif payload == _remove_unwanted(queries['assetsDataSPXWithRic']) or payload == _remove_unwanted( queries['assetsDataSPXWithId'] ): return load_json_from_resource(test_file_name, 'assets_data_query_response_spx.json') elif path == '/data/TREOD/query': if payload == _remove_unwanted(queries['dataQueryRic']): return load_json_from_resource(test_file_name, 'treod_query_response_gsn.json') elif payload == _remove_unwanted(queries['dataQuerySPX']): return load_json_from_resource(test_file_name, 'treod_query_response_spx.json') raise Exception(f'Unhandled request. Method: {method}, Path: {path}, payload: {payload} not recognized.') ================================================ FILE: gs_quant/timeseries/__init__.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from .algebra import * from .analysis import * from .backtesting import * from .datetime import * from .econometrics import * from .helper import * from .measures import * from .measures_countries import * from .measures_fx_vol import * from .measures_inflation import * from .measures_portfolios import * from .measures_rates import * from .measures_reports import * from .measures_risk_models import * from .measures_xccy import * from .measures_factset import * from .statistics import * from .tca import ( covariance ) from .technicals import * __name__ = 'timeseries' ================================================ FILE: gs_quant/timeseries/algebra.py ================================================ # Copyright 2018 Goldman Sachs. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # # # Chart Service will attempt to make public functions (not prefixed with _) from this module available. Such functions # should be fully documented: docstrings should describe parameters and the return value, and provide a 1-line # description. Type annotations should be provided for parameters. import datetime as dt import math from enum import Enum from functools import reduce from numbers import Real from typing import Union, Optional, List import numpy as np import pandas as pd from .datetime import align from .helper import plot_function, Interpolate from ..errors import MqValueError, MqTypeError """ Algebra library contains basic numerical and algebraic operations, including addition, division, multiplication, division and other functions on timeseries """ class FilterOperator(Enum): LESS = 'less_than' GREATER = 'greater_than' L_EQUALS = 'l_equals' G_EQUALS = 'g_equals' EQUALS = 'equals' N_EQUALS = 'not_equals' @plot_function def add( x: Union[pd.Series, Real], y: Union[pd.Series, Real], method: Interpolate = Interpolate.STEP ) -> Union[pd.Series, Real]: """ Add two series or scalars :param x: timeseries or scalar :param y: timeseries or scalar :param method: interpolation method (default: step). Only used when both x and y are timeseries :return: timeseries of x + y or sum of the given real numbers **Usage** Add two series or scalar variables with the given interpolation method :math:`R_t = X_t + Y_t` Alignment operators: ========= ======================================================================== Method Behavior ========= ======================================================================== intersect Resultant series only has values on the intersection of dates. Values for dates present in only one series will be ignored nan Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as nan in the other series, and therefore in the resultant series zero Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as zero in the other series step Resultant series has values on the union of dates in both series. Values for dates only available in one series will be interpolated via step function in the other series time Resultant series have values on the union of dates / times. Missing values surrounded by valid values will be interpolated given length of interval. Input series must use DateTimeIndex. ========= ======================================================================== **Examples** Add two series: >>> a = generate_series(100) >>> b = generate_series(100) >>> add(a, b, Interpolate.STEP) **See also** :func:`subtract` """ if isinstance(x, Real) and isinstance(y, Real): return x + y [x_align, y_align] = align(x, y, method) return x_align.add(y_align) @plot_function def subtract( x: Union[pd.Series, Real], y: Union[pd.Series, Real], method: Interpolate = Interpolate.STEP ) -> Union[pd.Series, Real]: """ Add two series or scalars :param x: timeseries or scalar :param y: timeseries or scalar :param method: index alignment operator (default: intersect). Only used when both x and y are timeseries :return: timeseries of x - y or difference between the given real numbers **Usage** Subtracts one series or scalar from another applying the given interpolation method :math:`R_t = X_t - Y_t` Alignment operators: ========= ======================================================================== Method Behavior ========= ======================================================================== intersect Resultant series only has values on the intersection of dates. Values for dates present in only one series will be ignored nan Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as nan in the other series, and therefore in the resultant series zero Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as zero in the other series step Resultant series has values on the union of dates in both series. Values for dates only available in one series will be interpolated via step function in the other series time Resultant series have values on the union of dates / times. Missing values surrounded by valid values will be interpolated given length of interval. Input series must use DateTimeIndex. ========= ======================================================================== **Examples** Subtract one series from another: >>> a = generate_series(100) >>> b = generate_series(100) >>> subtract(a, b, Interpolate.STEP) **See also** :func:`add` """ # Determine how we want to handle observations prior to start date if isinstance(x, Real) and isinstance(y, Real): return x - y [x_align, y_align] = align(x, y, method) return x_align.subtract(y_align) @plot_function def multiply( x: Union[pd.Series, Real], y: Union[pd.Series, Real], method: Interpolate = Interpolate.STEP ) -> Union[pd.Series, Real]: """ Multiply two series or scalars :param x: timeseries or scalar :param y: timeseries or scalar :param method: interpolation method (default: step). Only used when both x and y are timeseries :return: timeseries of x * y or product of the given real numbers **Usage** Multiply two series or scalar variables applying the given interpolation method :math:`R_t = X_t \\times Y_t` Alignment operators: ========= ======================================================================== Method Behavior ========= ======================================================================== intersect Resultant series only has values on the intersection of dates. Values for dates present in only one series will be ignored nan Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as nan in the other series, and therefore in the resultant series zero Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as zero in the other series step Resultant series has values on the union of dates in both series. Values for dates only available in one series will be interpolated via step function in the other series time Resultant series have values on the union of dates / times. Missing values surrounded by valid values will be interpolated given length of interval. Input series must use DateTimeIndex. ========= ======================================================================== **Examples** Multiply two series: >>> a = generate_series(100) >>> b = generate_series(100) >>> multiply(a, b, Interpolate.STEP) **See also** :func:`divide` """ if isinstance(x, Real) and isinstance(y, Real): return x * y [x_align, y_align] = align(x, y, method) return x_align.multiply(y_align) @plot_function def divide( x: Union[pd.Series, Real], y: Union[pd.Series, Real], method: Interpolate = Interpolate.STEP ) -> Union[pd.Series, Real]: """ Divide two series or scalars :param x: timeseries or scalar :param y: timeseries or scalar :param method: interpolation method (default: step). Only used when both x and y are timeseries :return: timeseries of x / y or quotient of the given real numbers **Usage** Divide two series or scalar variables applying the given interpolation method :math:`R_t = X_t / Y_t` Alignment operators: ========= ======================================================================== Method Behavior ========= ======================================================================== intersect Resultant series only has values on the intersection of dates. Values for dates present in only one series will be ignored nan Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as nan in the other series, and therefore in the resultant series zero Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as zero in the other series step Resultant series has values on the union of dates in both series. Values for dates only available in one series will be interpolated via step function in the other series time Resultant series have values on the union of dates / times. Missing values surrounded by valid values will be interpolated given length of interval. Input series must use DateTimeIndex. ========= ======================================================================== **Examples** Divide two series: >>> a = generate_series(100) >>> b = generate_series(100) >>> divide(a, b, Interpolate.STEP) **See also** :func:`multiply` """ if isinstance(x, Real) and isinstance(y, Real): return x / y [x_align, y_align] = align(x, y, method) return x_align.divide(y_align) @plot_function def floordiv( x: Union[pd.Series, Real], y: Union[pd.Series, Real], method: Interpolate = Interpolate.STEP ) -> Union[pd.Series, Real]: """ Floor divide two series or scalars :param x: timeseries or scalar :param y: timeseries or scalar :param method: interpolation method (default: step). Only used for operating two series :return: timeseries of x // y or quotient of the floor division of the given real numbers **Usage** Divide two series or scalar variables applying the given interpolation method :math:`R_t = X_t / Y_t` Alignment operators: ========= ======================================================================== Method Behavior ========= ======================================================================== intersect Resultant series only has values on the intersection of dates. Values for dates present in only one series will be ignored nan Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as nan in the other series, and therefore in the resultant series zero Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as zero in the other series step Resultant series has values on the union of dates in both series. Values for dates only available in one series will be interpolated via step function in the other series ========= ======================================================================== **Examples** Floor divide two series: >>> a = generate_series(100) >>> b = generate_series(100) >>> floordiv(a, b, Interpolate.STEP) **See also** :func:`divide` """ if isinstance(x, Real) and isinstance(y, Real): return x // y [x_align, y_align] = align(x, y, method) return x_align.floordiv(y_align) @plot_function def exp(x: pd.Series) -> pd.Series: """ Exponential of series :param x: timeseries :return: exponential of each element **Usage** For each element in the series, :math:`X_t`, raise :math:`e` (Euler's number) to the power of :math:`X_t`. Euler's number is the base of the natural logarithm, :math:`ln`. :math:`R_t = e^{X_t}` **Examples** Raise :math:`e` to the power :math:`1`. Returns Euler's number, approximately 2.71828 >>> exp(1) **See also** :func:`log` """ return np.exp(x) @plot_function def log(x: pd.Series) -> pd.Series: """ Natural logarithm of series :param x: timeseries :return: series with exponential of each element **Usage** For each element in the series, :math:`X_t`, return the natural logarithm :math:`ln` of :math:`X_t` The natural logarithm is the logarithm in base :math:`e`. :math:`R_t = log(X_t)` This function is the inverse of the exponential function. More information on `logarithms `_ **Examples** Take natural logarithm of 3 >>> log(3) **See also** :func:`exp` """ return np.log(x) @plot_function def power(x: pd.Series, y: float = 1) -> pd.Series: """ Raise each element in series to power :param x: timeseries :param y: value :return: date-based time series of square roots **Usage** Raise each value in time series :math:`X_t` to the power :math:`y`: :math:`R_t = X_t^{y}` **Examples** Generate price series and raise each value to the power 2: >>> prices = generate_series(100) >>> power(prices, 2) **See also** :func:`sqrt` """ return np.power(x, y) @plot_function def sqrt(x: Union[Real, pd.Series]) -> Union[Real, pd.Series]: """ Square root of (a) each element in a series or (b) a real number :param x: date-based time series of prices or real number :return: date-based time series of square roots or square root of given number **Usage** Return the square root of each value in time series :math:`X_t`: :math:`R_t = \\sqrt{X_t}` **Examples** Generate price series and take square root of each value: >>> prices = generate_series(100) >>> sqrt(prices) **See also** :func:`pow` """ if isinstance(x, pd.Series): return np.sqrt(x) result = math.sqrt(x) # return int if result is integral (should work for values up to 2**53) return round(result) if round(result) == result else result @plot_function def abs_(x: pd.Series) -> pd.Series: """ Absolute value of each element in series :param x: date-based time series of prices :return: date-based time series of absolute value **Usage** Return the absolute value of :math:`X`. For each value in time series :math:`X_t`, return :math:`X_t` if :math:`X_t` is greater than or equal to 0; otherwise return :math:`-X_t`: :math:`R_t = |X_t|` Equivalent to :math:`R_t = sqrt{X_t^2}` **Examples** Generate price series and take absolute value of :math:`X_t-100` >>> prices = generate_series(100) - 100 >>> abs_(prices) **See also** :func:`exp` :func:`sqrt` """ return abs(x) @plot_function def floor(x: pd.Series, value: float = 0) -> pd.Series: """ Floor series at minimum value :param x: date-based time series of prices :param value: minimum value :return: date-based time series of maximum value **Usage** Returns series where all values are greater than or equal to the minimum value. :math:`R_t = max(X_t, value)` See `Floor and Ceil functions `_ for more details **Examples** Generate price series and floor all values at 100 >>> prices = generate_series(100) >>> floor(prices, 100) **See also** :func:`ceil` """ assert x.index.is_monotonic_increasing return x.apply(lambda y: max(y, value)) @plot_function def ceil(x: pd.Series, value: float = 0) -> pd.Series: """ Cap series at maximum value :param x: date-based time series of prices :param value: maximum value :return: date-based time series of maximum value **Usage** Returns series where all values are less than or equal to the maximum value. :math:`R_t = min(X_t, value)` See `Floor and Ceil functions `_ for more details **Examples** Generate price series and floor all values at 100 >>> prices = generate_series(100) >>> floor(prices, 100) **See also** :func:`floor` """ assert x.index.is_monotonic_increasing return x.apply(lambda y: min(y, value)) @plot_function def filter_(x: pd.Series, operator: Optional[FilterOperator] = None, value: Optional[Real] = None) -> pd.Series: """ Removes values where comparison with the operator and value combination results in true, defaults to removing missing values from the series :param x: timeseries :param operator: FilterOperator describing logic for value removal, e.g 'less_than' :param value: number indicating value(s) to remove from the series :return: timeseries with specified values removed **Usage** Remove each value determined by operator and value from timeseries where that expression yields true **Examples** Remove 0 from time series >>> prices = generate_series(100) >>> filter_(prices, FilterOperator.EQUALS, 0) Remove positive numbers from time series >>> prices = generate_series(100) >>> filter_(prices, FilterOperator.GREATER, 0) Remove missing values from time series >>> prices = generate_series(100) >>> filter_(prices) """ if value is None and operator is None: x = x.dropna(axis=0, how='any') elif value is None: raise MqValueError('No value is specified for the operator') else: if operator == FilterOperator.EQUALS: remove = x == value elif operator == FilterOperator.GREATER: remove = x > value elif operator == FilterOperator.LESS: remove = x < value elif operator == FilterOperator.L_EQUALS: remove = x <= value elif operator == FilterOperator.G_EQUALS: remove = x >= value elif operator == FilterOperator.N_EQUALS: remove = x != value else: if not isinstance(operator, str): operator = str(operator) raise MqValueError('Unexpected operator: ' + operator) x = x.drop(x[remove].index) return x @plot_function def filter_dates( x: pd.Series, operator: Optional[FilterOperator] = None, dates: Union[List[dt.date], dt.date] = None ) -> pd.Series: """ Removes dates where comparison with the operator and dates combination results in true, defaults to removing missing values from the series :param x: timeseries :param operator: FilterOperator describing logic for date removal, e.g 'less_than' :param dates: date or list of dates to remove from the series :return: timeseries with specified dates removed **Usage** Remove each date determined by operator and date from timeseries where that expression yields true **Examples** Remove today from time series >>> prices = generate_series(100) >>> filter_dates(prices, FilterOperator.EQUALS, date.today()) Remove dates before today from time series >>> prices = generate_series(100) >>> filter_dates(prices, FilterOperator.LESS, date.today()) """ if dates is None and operator is None: x = x.dropna(axis=0, how='any') elif dates is None: raise MqValueError('No date is specified for the operator') elif isinstance(dates, list) and operator not in [FilterOperator.EQUALS, FilterOperator.N_EQUALS]: raise MqValueError('Operator does not work for list of dates') else: if operator == FilterOperator.EQUALS: dates = dates if isinstance(dates, list) else [dates] x = x.loc[~x.index.isin(dates)] elif operator == FilterOperator.N_EQUALS: dates = dates if isinstance(dates, list) else [dates] x = x.loc[x.index.isin(dates)] elif operator == FilterOperator.GREATER: x = x.loc[x.index <= dates] elif operator == FilterOperator.LESS: x = x.loc[x.index >= dates] elif operator == FilterOperator.L_EQUALS: x = x.loc[x.index > dates] elif operator == FilterOperator.G_EQUALS: x = x.loc[x.index < dates] else: if not isinstance(operator, str): operator = str(operator) raise MqValueError('Unexpected operator: ' + operator) return x def _sum_boolean_series(*series): if not 2 <= len(series) <= 100: raise MqValueError('expected between 2 and 100 arguments') for s in series: if not isinstance(s, pd.Series): raise MqTypeError('all arguments must be series') if not all(map(lambda a: a in (0, 1), s.values)): raise MqValueError(f'cannot perform operation on series with value(s) other than 1 and 0: {s.values}') current = series[0].add(series[1], fill_value=0) for s in series[2:]: current = current.add(s, fill_value=0) return current @plot_function def and_(*series: pd.Series) -> pd.Series: """ Logical "and" of two or more boolean series. :param series: input series :return: result series (of numeric type, with booleans represented as 1s and 0s) """ s = _sum_boolean_series(*series) return (s == len(series)).astype(int) @plot_function def or_(*series: pd.Series) -> pd.Series: """ Logical "or" of two or more boolean series. :param series: input series :return: result series (of numeric type, with booleans represented as 1s and 0s) """ s = _sum_boolean_series(*series) return (s > 0).astype(int) @plot_function def not_(series: pd.Series) -> pd.Series: """ Logical negation of a single boolean series. :param series: single input series :return: result series (of numeric type, with booleans represented as 1s and 0s) """ if not all(map(lambda a: a in (0, 1), series.values)): raise MqValueError(f'cannot negate series with value(s) other than 1 and 0: {series.values}') return series.replace([0, 1], [1, 0]) @plot_function def if_(flags: pd.Series, x: Union[pd.Series, float], y: Union[pd.Series, float]) -> pd.Series: """ Returns a series s. For i in the index of flags, s[i] = x[i] if flags[i] == 1 else y[i]. :param flags: series of 1s and 0s :param x: values to use when flag is 1 :param y: values to use when flag is 0 :return: result series **Usage** Returns a series based off the given conditional series. If the condition is true it shows the first series's value else it shows the second series's value. **PlotTool Example** if(SPX.spot() > 4000, SPX.spot(), GSTHHVIP.spot()) The above expression would show SPX.spot() if the spot price is above 4000 else it shows GSTHHVIP.spot(). """ if not all(map(lambda a: a in (0, 1), flags.values)): raise MqValueError(f'cannot perform "if" on series with value(s) other than 1 and 0: {flags.values}') def ensure_series(s): if isinstance(s, (float, int)): return flags, pd.Series([s] * flags.shape[0], index=flags.index) elif isinstance(s, pd.Series): return flags.align(s) else: raise MqTypeError('expected a number or series') x_flags, x = ensure_series(x) y_flags, y = ensure_series(y) return pd.concat([x[x_flags == 1], y[y_flags == 0]]).sort_index() @plot_function def weighted_sum(series: List[pd.Series], weights: list) -> pd.Series: """ Calculate a weighted sum. :param series: list of time series :param weights: list of weights :return: time series of weighted average **Usage** Calculate a weighted sum e.g. for a basket. **Examples** Generate price series and get a sum (weights 70%/30%). >>> prices1 = generate_series(100) >>> prices2 = generate_series(100) >>> mybasket = weighted_sum([prices1, prices2], [0.7, 0.3]) **See also** :func:`basket` """ if not all(isinstance(x, pd.Series) for x in series): raise MqTypeError("expected a list of time series") if not all(isinstance(y, (float, int)) for y in weights): raise MqTypeError("expected a list of number for weights") if len(weights) != len(series): raise MqValueError("must have one weight for each time series") # for input series, get the intersection of their calendars cal = pd.DatetimeIndex( reduce( np.intersect1d, (curve.index for curve in series), ) ) # reindex inputs and calculate series = [s.reindex(cal) for s in series] weights = [pd.Series(w, index=cal) for w in weights] return sum(series[i] * weights[i] for i in range(len(series))) / sum(weights) @plot_function def geometrically_aggregate(series: pd.Series) -> pd.Series: """ Geometrically aggregate a series. :param series: list of time series :return: time series of geometrically aggregated results **Usage** Used to aggregate daily returns when expressed as weights """ return series.add(1).cumprod() - 1 ================================================ FILE: gs_quant/timeseries/analysis.py ================================================ # Copyright 2018 Goldman Sachs. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # # # Chart Service will attempt to make public functions (not prefixed with _) from this module available. Such functions # should be fully documented: docstrings should describe parameters and the return value, and provide a 1-line # description. Type annotations should be provided for parameters. import re from enum import Enum from numbers import Real from typing import Union import pandas as pd from gs_quant.datetime import relative_date_add from . import align from .helper import plot_function, Window, Interpolate from ..errors import MqValueError """ Timeseries analysis library contains functions used to analyze properties of timeseries, including laging, differencing, autocorrelation, co-integration and other operations """ class ThresholdType(str, Enum): percentage = "percentage" absolute = "absolute" @plot_function def smooth_spikes( x: pd.Series, threshold: float, threshold_type: ThresholdType = ThresholdType.percentage ) -> pd.Series: """ Smooth out the spikes of a series. If a point is larger/smaller than (1 +/- threshold) times both neighbors, replace it with the average of those neighbours. Note: the first and last points in the input series are dropped. :param x: timeseries :param threshold: minimum increment to trigger filter :param threshold_type: type of threshold check :return: smoothed timeseries **Usage** Returns series where values that exceed the threshold relative to both neighbors are replaced. **Examples** Generate price series and smooth spikes over a threshold of 0.5. >>> prices = generate_series(100) >>> smooth_spikes(prices, 0.5) **See also** :func:`exponential_moving_average` """ def check_percentage(previous, current, next_, multiplier) -> bool: current_higher = current > previous * multiplier and current > next_ * multiplier current_lower = previous > current * multiplier and next_ > current * multiplier return current_higher or current_lower def check_absolute(previous, current, next_, absolute) -> bool: current_higher = current > previous + absolute and current > next_ + absolute current_lower = previous > current + absolute and next_ > current + absolute return current_higher or current_lower if len(x) < 3: return pd.Series(dtype=float) threshold_value, check_spike = ( (threshold, check_absolute) if threshold_type == ThresholdType.absolute else ((1 + threshold), check_percentage) ) result = x.astype(float) current, next_ = x.iloc[0:2] for i in range(1, len(x) - 1): previous = current current = next_ next_ = x.iloc[i + 1] if check_spike(previous, current, next_, threshold_value): result.iloc[i] = (previous + next_) / 2 return result[1:-1] @plot_function def repeat(x: pd.Series, n: int = 1) -> pd.Series: """ Repeats values for days where data is missing. For any date with missing data, the last recorded value is used. Optionally downsamples the result such that there are data points every n days. :param x: date-based timeseries :param n: desired frequency of output :return: a timeseries that has been forward-filled, and optionally downsampled **Usage** Fill missing values with last seen value e.g. to combine daily with weekly or monthly data. """ if not 0 < n < 367: raise MqValueError('n must be between 0 and 367') if x.empty: return x index = pd.date_range(freq=f'{n}D', start=x.index[0], end=x.index[-1]) return x.reindex(index, method='ffill') @plot_function def first(x: pd.Series) -> pd.Series: """ First value of series :param x: time series :return: time series of first value **Usage** Return series with first value of `X` for all dates: :math:`R_t = X_0` where :math:`X_0` is the first value in the series **Examples** Last value of series: >>> series = generate_series(100) >>> returns = first(series) **See also** :func:`last` """ return pd.Series(x.iloc[0], x.index) @plot_function def last(x: pd.Series) -> pd.Series: """ Last value of series (as a series) :param x: time series :return: time series of last value **Usage** Return series with last value of `X` for all dates: :math:`R_t = X_T` where :math:`X_T` is the last value in the series **Examples** Last value of series: >>> series = generate_series(100) >>> returns = last(series) **See also** :func:`first` """ return pd.Series(x.dropna().iloc[-1], x.index) @plot_function def last_value(x: pd.Series) -> Union[int, float]: """ Last value of series (as a scalar) :param x: time series :return: last value **Usage** Return a scalar value :math:`X_T` where T is the last index value in the series. **Examples** Last value of series: >>> series = generate_series(100) >>> lv = last_value(series) **See also** :func:`last` """ if x.empty: raise MqValueError("cannot get last value of an empty series") return x.dropna().iloc[-1] @plot_function def count(x: pd.Series) -> pd.Series: """ Count observations in series :param x: time series :return: number of observations **Usage** Count the number of valid observations in a series: :math:`R_t = R_{t-1} + 1` if :math:`X_t` is not NaN, and :math:`R_t = R_{t-1} + 0` if :math:`X_t` is NaN **Examples** Count observations in series: >>> series = generate_series(100) >>> count = count(series) **See also** :func:`sum` """ return x.rolling(x.size, 0).count() @plot_function def diff(x: pd.Series, obs: Union[Window, int, str] = 1) -> pd.Series: """ Diff observations with given lag :param x: time series of prices :param obs: number of observations to lag or relative date e.g. 3d, 1w, 1m :return: date-based time series of return **Usage** Compute the difference in series values over a given lag: :math:`R_t = X_t - X_{t-obs}` where :math:`obs` is the number of observations to lag series in diff function **Examples** Diff prices levels: >>> series = generate_series(100) >>> returns = diff(series) **See also** :func:`lag` """ ret_series = x - lag(x, obs, LagMode.TRUNCATE) return ret_series @plot_function def compare( x: Union[pd.Series, Real], y: Union[pd.Series, Real], method: Interpolate = Interpolate.STEP ) -> Union[pd.Series, Real]: """ Compare two series or scalars against each other :param x: timeseries or scalar :param y: timeseries or scalar :param method: interpolation method (default: step). Only used when both x and y are timeseries :return: binary timeseries with the result of x relation y or the comparison of the given real numbers **Usage** Compare two series or scalar variables applying the given interpolation method in case indices of :math:`x` and :math:`y` differ. Returns a signal with values of 1, 0, or -1. If :math:`X_t > Y_t`, then :math:`R_t = 1`. If :math:`X_t = Y_t`, then :math:`R_t = 0`. If :math:`X_t < Y_t`, then :math:`R_t = -1`. Alignment operators: ========= ======================================================================== Method Behavior ========= ======================================================================== intersect Resultant series only has values on the intersection of dates. Values for dates present in only one series will be ignored nan Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as nan in the other series, and therefore in the resultant series zero Resultant series has values on the union of dates in both series. Values for dates only available in one series will be treated as zero in the other series step Resultant series has values on the union of dates in both series. Values for dates only available in one series will be interpolated via step function in the other series time Resultant series have values on the union of dates / times. Missing values surrounded by valid values will be interpolated given length of interval. Input series must use DateTimeIndex. ========= ======================================================================== """ x, y = align(x, y, method) return (x > y) * 1.0 + (x < y) * -1.0 class LagMode(Enum): TRUNCATE = "truncate" EXTEND = "extend" @plot_function def lag(x: pd.Series, obs: Union[Window, int, str] = 1, mode: LagMode = LagMode.EXTEND) -> pd.Series: """ Lag timeseries by a number of observations or a relative date. :param x: timeseries of prices :param obs: non-zero integer (number of observations) or relative date e.g. "-90d", "1d", "1m", "1y" :param mode: whether to extend series index (into the future) :return: date-based time series of return **Usage** Shift the series backwards by a specified number of observations: :math:`R_t = X_{t-obs}` where :math:`obs` is the number of observations to lag series **Examples** Lag series by 2 observations: >>> prices = generate_series(100) >>> lagged = lag(prices, 2) Lag series by 1 year: >>> prices = generate_series(100) >>> lagged = lag(prices, '1y') **See also** :func:`diff` """ if isinstance(obs, str): # Check for business day pattern and raise an error if re.search('[bB]', obs): raise MqValueError( f"Business day offset '{obs}' is not supported. " "If your dataset is indexed on business days, use an integer offset instead (e.g., 1, 2, -1)." ) end = pd.Timestamp(x.index[-1]) y = x.copy() # avoid mutating the provided series match = re.fullmatch('(\\d+)y', obs) if match: y.index += pd.DateOffset(years=int(match.group(1))) y = y.groupby(y.index).first() if hasattr(y.index, 'as_unit'): y.index = y.index.as_unit('ns') else: _idx = pd.DatetimeIndex([(i + pd.DateOffset(relative_date_add(obs))).date() for i in y.index]) y.index = _idx.as_unit('ns') if hasattr(_idx, 'as_unit') else _idx if mode == LagMode.EXTEND: return y return y[:end] obs = getattr(obs, 'w', obs) # Determine how we want to handle observations prior to start date if mode == LagMode.EXTEND: if x.empty: return x if x.index.resolution != 'day': raise MqValueError(f'unable to extend index with resolution {x.index.resolution}') kwargs = {'periods': abs(obs) + 1, 'freq': 'D'} if obs > 0: kwargs['start'] = x.index[-1] else: kwargs['end'] = x.index[0] x = x.reindex(x.index.union(pd.date_range(**kwargs))) if hasattr(x.index, 'as_unit'): x.index = x.index.as_unit('ns') return x.shift(obs) ================================================ FILE: gs_quant/timeseries/backtesting.py ================================================ """ Copyright 2020 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import logging from typing import Optional, Union import numpy as np import pandas as pd import datetime as dt from functools import partial, reduce from numbers import Real from dateutil.relativedelta import relativedelta as rdelta from pydash import chunk from gs_quant.timeseries.econometrics import volatility, correlation from gs_quant.timeseries.measures_helper import VolReference, preprocess_implied_vol_strikes_eq from gs_quant import timeseries as ts from .algebra import sqrt from .helper import ( _create_enum, _tenor_to_month, _month_to_tenor, requires_session, Returns, plot_function, plot_method, Window, ) from ..api.gs.assets import GsAssetApi from ..api.gs.data import GsDataApi, MarketDataResponseFrame, QueryType from ..api.utils import ThreadPoolManager from ..data import DataContext from ..data.log import log_debug from ..errors import MqTypeError, MqValueError _logger = logging.getLogger(__name__) RebalFreq = _create_enum('RebalFreq', ['Daily', 'Weekly', 'Monthly']) ReturnType = _create_enum('ReturnType', ['excess_return']) def backtest_basket( series: list, weights: list, costs: list = None, rebal_freq: RebalFreq = RebalFreq.DAILY, ): num_assets = len(series) costs = costs or [0] * num_assets weights = weights or [1 / num_assets] * num_assets if not all(isinstance(x, pd.Series) for x in series): raise MqTypeError("expected a list of series") if len(weights) != num_assets or len(weights) != len(costs): raise MqValueError("series, weights, and cost lists must have the same length") # For all inputs which are Pandas series, get the intersection of their calendars cal = pd.DatetimeIndex( reduce( np.intersect1d, (curve.index for curve in series + weights + costs if isinstance(curve, pd.Series)), ) ) # Reindex inputs and convert to pandas dataframes series = pd.concat([curve.reindex(cal) for curve in series], axis=1) weights = pd.concat([pd.Series(w, index=cal) for w in weights], axis=1) costs = pd.concat([pd.Series(c, index=cal) for c in costs], axis=1) if rebal_freq == RebalFreq.DAILY: rebal_dates = cal else: if rebal_freq == RebalFreq.WEEKLY: # Get hypothetical weekly rebalances num_rebals = ((cal[-1] - cal[0]).days) // 7 rebal_dates = [cal[0] + i * rdelta(weeks=1) for i in range(num_rebals + 1)] else: # Get hypothetical monthly rebalances num_rebals = (cal[-1].year - cal[0].year) * 12 + cal[-1].month - cal[0].month rebal_dates = [cal[0] + i * rdelta(months=1) for i in range(num_rebals + 1)] # Convert the hypothetical weekly/monthly rebalance dates to actual calendar days rebal_dates = [min(cal[cal >= date]) for date in rebal_dates if date < max(cal)] n = len(cal) output_arr = np.zeros(n) units_arr = np.zeros((n, num_assets)) actual_weights_arr = np.zeros((n, num_assets)) # Initialize backtest output_arr[0] = 100 units_arr[0,] = output_arr[0] * weights.values[0,] / series.values[0,] actual_weights_arr[0,] = weights.values[0,] # Run backtest prev_rebal = 0 for i, date in enumerate(cal[1:], 1): # Update performance output_arr[i] = output_arr[i - 1] + np.dot(units_arr[i - 1,], series.values[i,] - series.values[i - 1,]) actual_weights_arr[i,] = ( weights.values[prev_rebal,] * (series.values[i,] / series.values[prev_rebal,]) * (output_arr[prev_rebal] / output_arr[i]) ) # Rebalance on rebal_dates if date in rebal_dates: # Compute costs output_arr[i] -= ( np.dot(costs.values[i,], np.abs(weights.values[i,] - actual_weights_arr[i,])) * output_arr[i] ) # Rebalance units_arr[i,] = output_arr[i] * weights.values[i,] / series.values[i,] prev_rebal = i actual_weights_arr[i,] = weights.values[i,] else: units_arr[i,] = units_arr[i - 1,] # Copy results back to pandas objects output = pd.Series(output_arr, index=cal) actual_weights = pd.DataFrame(actual_weights_arr, index=cal, columns=series.columns) return output, actual_weights @plot_function def basket_series( series: list, weights: list = None, costs: list = None, rebal_freq: RebalFreq = RebalFreq.DAILY, return_type: ReturnType = ReturnType.EXCESS_RETURN, ) -> pd.Series: """ Calculates a basket return series. :param series: list of time series of instrument prices :param weights: list of weights (defaults to evenly weight series) :param costs: list of execution costs in decimal (defaults to costs of 0) :param rebal_freq: rebalancing frequency - Daily, Weekly or Monthly (defaults to Daily) :param return_type: return type of underlying instruments - only excess return is supported :return: time series of the resulting basket **Usage** Calculates a basket return series. **Examples** Generate price series and combine them in a basket (weights 70%/30%) which rebalances monthly and assumes execution costs 5bps and 10bps each time the constituents are traded. >>> prices1 = generate_series(100) >>> prices2 = generate_series(100) >>> mybasket = basket_series([prices1, prices2], [0.7, 0.3], [0.0005, 0.001], monthly) **See also** :func:`prices` """ return backtest_basket(series, weights, costs, rebal_freq)[0] class Basket: """ Construct a basket of stocks :param stocks: list of stock bloomberg ids :param weights: list of weights (defaults to evenly weight stocks) :param rebal_freq: rebalancing frequency - Daily or Monthly (defaults to daily) """ def __init__(self, stocks: list, weights: list = None, rebal_freq: RebalFreq = RebalFreq.DAILY): if weights and len(weights) and len(stocks) != len(weights): # make sure that they passed in an array for weig raise MqValueError("Stocks and weights must have the same length if both specified.") self.bbids = stocks self.rebal_freq = rebal_freq self.weights = weights self._marquee_ids = None self.start = DataContext.current.start_date self.end = DataContext.current.end_date self._spot_data = None self._returns = None self._actual_weights = None def _reset(self): self.start = DataContext.current.start_date self.end = DataContext.current.end_date self._spot_data = None self._returns = None self._actual_weights = None def get_marquee_ids(self): if self._marquee_ids is None: # Assets sorted by increasing rank assets = reversed( GsAssetApi.get_many_assets_data( bbid=self.bbids, fields=('id', 'bbid', 'rank'), limit=2 * len(self.bbids), order_by=['>rank'] ) ) # If duplicate assets exist, asset_dict will contain a entry for the one with the higher rank (relevant) assets_dict = {entry['bbid']: entry['id'] for entry in assets} if len(assets_dict) != len(set(self.bbids)): not_found = set(assets_dict).symmetric_difference(self.bbids) raise MqValueError(f'Unable to find stocks: {", ".join(not_found)}') self._marquee_ids = [assets_dict[bbid] for bbid in self.bbids] return self._marquee_ids def _ensure_spot_data(self, request_id: Optional[str] = None): if self.start != DataContext.current.start_date or self.end != DataContext.current.end_date: self._reset() if self._spot_data is None: q = GsDataApi.build_market_data_query(self.get_marquee_ids(), QueryType.SPOT) log_debug(request_id, _logger, 'q %s', q) spot_data = ts.get_historical_and_last_for_measure( self.get_marquee_ids(), QueryType.SPOT, {}, request_id=request_id ) dataset_ids = getattr(spot_data, 'dataset_ids', ()) df = spot_data.set_index([spot_data.index, 'assetId']) df = df[~df.index.duplicated(keep='last')].spot.unstack() df = df[self._marquee_ids] self._spot_data = MarketDataResponseFrame(df) self._spot_data.dataset_ids = dataset_ids def _ensure_backtest(self, request_id: Optional[str] = None): if self.start != DataContext.current.start_date or self.end != DataContext.current.end_date: self._reset() if self._returns is None or self._actual_weights is None: spot_df = self.get_spot_data(request_id=request_id) spot_df = spot_df.dropna() spot_series = [spot_df[asset_id] for asset_id in spot_df] results = backtest_basket(spot_series, self.weights, rebal_freq=self.rebal_freq) self._returns = results[0] actual_weights = results[1] actual_weights.columns = self.get_marquee_ids() self._actual_weights = actual_weights def get_returns(self, request_id: Optional[str] = None): self._ensure_backtest(request_id=request_id) return self._returns def get_actual_weights(self, request_id: Optional[str] = None): self._ensure_backtest(request_id=request_id) return self._actual_weights def get_spot_data(self, request_id: Optional[str] = None): self._ensure_spot_data(request_id=request_id) return self._spot_data @requires_session @plot_method def price(self, *, real_time: bool = False, request_id: Optional[str] = None) -> pd.Series: """ Weighted average price :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: time series of the average price """ if real_time: raise NotImplementedError('real-time basket price not implemented') return self.get_returns(request_id=request_id) @requires_session @plot_method def average_implied_volatility( self, tenor: str, strike_reference: VolReference, relative_strike: Real, *, real_time: bool = False, request_id: Optional[str] = None, source: Optional[str] = None, ) -> pd.Series: """ Weighted average implied volatility :param tenor: relative date representation of expiration date e.g. 1m :param strike_reference: reference for strike level :param relative_strike: strike relative to reference :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :param source: name of function caller :return: time series of the average implied volatility """ if real_time: raise NotImplementedError('real-time basket implied vol not implemented') ref_string, relative_strike = preprocess_implied_vol_strikes_eq(strike_reference, relative_strike) log_debug( request_id, _logger, 'where tenor=%s, strikeReference=%s, relativeStrike=%s', tenor, ref_string, relative_strike, ) where = dict(tenor=tenor, strikeReference=ref_string, relativeStrike=relative_strike) tasks = [] for i, chunked_assets in enumerate(chunk(self.get_marquee_ids(), 3)): query = GsDataApi.build_market_data_query( chunked_assets, QueryType.IMPLIED_VOLATILITY, where=where, source=source, real_time=real_time ) tasks.append(partial(GsDataApi.get_market_data, query, request_id)) results = ThreadPoolManager.run_async(tasks) vol_data = pd.concat(results) actual_weights = self.get_actual_weights(request_id) # Add in today's data today = dt.date.today() if ( not real_time and DataContext.current.end_date >= today and (vol_data.empty or today not in vol_data.index.date) ): vol_data = ts.append_last_for_measure( vol_data, self.get_marquee_ids(), QueryType.IMPLIED_VOLATILITY, where, source=source, request_id=request_id, ) vol_data.index = vol_data.index.rename('date') # Below transformations will throw errors if vol_data is empty if vol_data.empty: return pd.Series(dtype=float) vols = vol_data.pivot_table('impliedVolatility', ['date'], 'assetId') vols.reindex(self.get_marquee_ids(), axis=1) vols.index.name = None # Necessary when current values appended - set weights index to match vols index actual_weights = actual_weights.reindex(vols.index).ffill() return actual_weights.mul(vols).sum(axis=1, skipna=False) @requires_session @plot_method def average_realized_volatility( self, tenor: str, returns_type: Returns = Returns.LOGARITHMIC, *, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Weighted average realized volatility :param tenor: relative date representation of expiration date e.g. 1m :param returns_type: returns type :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: time series of the average realized volatility """ if real_time: raise NotImplementedError('real-time basket realized vol not implemented') spot_df = self.get_spot_data(request_id=request_id) actual_weights = self.get_actual_weights(request_id=request_id) vols = [volatility(spot_df[asset_id], Window(tenor, tenor), returns_type) for asset_id in spot_df] vols = pd.concat(vols, axis=1) vols.columns = list(spot_df) # Necessary when current values appended - set weights index to match vols index actual_weights = actual_weights.reindex(vols.index).ffill() return actual_weights.mul(vols, axis=1).sum(axis=1, skipna=False) @requires_session @plot_method def average_realized_correlation( self, w: Union[Window, int, str] = Window(None, 0), *, real_time: bool = False, request_id: Optional[str] = None ) -> pd.Series: """ Weighted average realized correlation. Computes the correlation between each of the basket's constituents and returns their average, weighted by their representation in the basket. :param w: Window, int, or str: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request ID, if any :return: time series of the average realized correlation """ # Calculated using method (a) from here: http://www.nematrian.com/MeasuringAverageStockCorrelation # Could be sped-up by using another estimate method, such as (b) if real_time: raise NotImplementedError('real-time basket realized corr not implemented') spot_df = self.get_spot_data(request_id=request_id) actual_weights = self.get_actual_weights(request_id=request_id) tot = pd.Series(np.zeros_like(spot_df.iloc[:, 0]), index=spot_df.index) tot_wt = pd.Series(np.zeros_like(spot_df.iloc[:, 0]), index=spot_df.index) # Iterate through and compute pairwise correlation between spot price series, # averaging at the end. for i in range(len(spot_df.columns)): for j in range(i + 1, len(spot_df.columns)): corr = correlation(spot_df.iloc[:, i], spot_df.iloc[:, j], w) wt = actual_weights.iloc[:, i] * actual_weights.iloc[:, j] tot += corr * wt tot_wt += wt return pd.to_numeric(tot / tot_wt, errors='coerce') @requires_session @plot_method def average_forward_vol( self, tenor: str, forward_start_date: str, strike_reference: VolReference, relative_strike: Real, *, real_time: bool = False, request_id: Optional[str] = None, source: Optional[str] = None, ) -> pd.Series: """ Weighted average forward volatility. :param tenor: relative date representation of expiration date e.g. 1m :param forward_start_date: forward start date e.g. 2m, 1y :param strike_reference: reference for strike level :param relative_strike: strike relative to reference :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :param source: name of function caller :return: average forward volatility """ if real_time: raise NotImplementedError('real-time basket forward vol not implemented') ref_string, relative_strike = preprocess_implied_vol_strikes_eq(strike_reference, relative_strike) t1_month = _tenor_to_month(forward_start_date) t2_month = _tenor_to_month(tenor) + t1_month t1 = _month_to_tenor(t1_month) t2 = _month_to_tenor(t2_month) log_debug( request_id, _logger, 'where tenor=%s, strikeReference=%s, relativeStrike=%s', f'{t1},{t2}', ref_string, relative_strike, ) where = dict(tenor=[t1, t2], strikeReference=[ref_string], relativeStrike=[relative_strike]) asset_ids = self.get_marquee_ids() vol_data = ts.get_historical_and_last_for_measure( asset_ids, QueryType.IMPLIED_VOLATILITY, where, source=source, request_id=request_id ) # Below transformations will throw errors if vol_data is empty if vol_data.empty: return pd.Series(dtype=float) grouped_by_asset_ids = vol_data.groupby('assetId') s = {} for asset_id, df in grouped_by_asset_ids: grouped_by_tenor = df.groupby('tenor') try: sg = grouped_by_tenor.get_group(t1)['impliedVolatility'] lg = grouped_by_tenor.get_group(t2)['impliedVolatility'] except KeyError: log_debug(request_id, _logger, 'no data for one or more tenors') series = pd.Series(dtype=float, name='forwardVol') else: series = pd.Series( sqrt((t2_month * lg**2 - t1_month * sg**2) / _tenor_to_month(tenor)), name='forwardVol' ) s[asset_id] = series vols = pd.DataFrame(s) actual_weights = self.get_actual_weights(request_id) # Necessary when current values appended - set weights index to match vols index actual_weights = actual_weights.reindex(pd.DatetimeIndex(vols.index)).ffill() return actual_weights.mul(vols).sum(axis=1, skipna=False) ================================================ FILE: gs_quant/timeseries/datetime.py ================================================ # Copyright 2018 Goldman Sachs. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # # # Chart Service will attempt to make public functions (not prefixed with _) from this module available. Such functions # should be fully documented: docstrings should describe parameters and the return value, and provide a 1-line # description. Type annotations should be provided for parameters. import datetime as dt from enum import Enum from numbers import Real from typing import Any, Union, List import numpy as np import pandas as pd from .helper import ( _create_enum, Interpolate, plot_function, requires_session, FREQ_MONTH_END, FREQ_QUARTER_END, FREQ_YEAR_END, ) from ..datetime import GsCalendar from ..datetime.date import DayCountConvention, PaymentFrequency, day_count_fraction from ..datetime.date import date_range as _date_range from ..errors import MqValueError, MqTypeError """ Date and time manipulation for timeseries, including date or time shifting, calendar operations, curve alignment and interpolation operations. Includes sampling operations based on daif dates[0]te or time manipulation """ AggregateFunction: Union[Union[type, Enum], Any] = _create_enum( 'AggregateFunction', ['max', 'min', 'mean', 'sum', 'first', 'last'] ) AggregatePeriod = _create_enum('AggregatePeriod', ['week', 'month', 'quarter', 'year']) def __interpolate_step(x: pd.Series, dates: pd.Series = None) -> pd.Series: if x.empty: raise MqValueError('Cannot perform step interpolation on an empty series') first_date = pd.Timestamp(dates.index[0]) if isinstance(x.index[0], pd.Timestamp) else dates.index[0] # locate previous valid date or take first value from series prev = x.index[0] if first_date < x.index[0] else x.index[x.index.get_indexer([first_date], method='pad')] current = x[prev] curve = x.align( dates, 'right', )[0] # only need values from dates for knot in curve.items(): if np.isnan(knot[1]): curve[knot[0]] = current else: current = knot[1] return curve @plot_function def align( x: Union[pd.Series, Real], y: Union[pd.Series, Real], method: Interpolate = Interpolate.INTERSECT ) -> Union[List[pd.Series], List[Real]]: """ Align dates of two series or scalars :param x: first timeseries or scalar :param y: second timeseries or scalar :param method: interpolation method (default: intersect). Only used when both x and y are timeseries :return: timeseries with specified dates or two scalars from the input **Usage** Align the dates of two series using the specified interpolation method. Returns two series with dates based on the method of interpolation, for example, can be used to intersect the dates of two series, union dates with a defined manner to compute missing values. Interpolation methods: ========= ======================================================================== Type Behavior ========= ======================================================================== intersect Resultant series only have values on the intersection of dates /times. nan Resultant series have values on the union of dates /times. Values will be NaN for dates or times only present in the other series zero Resultant series have values on the union of dates / times. Values will be zero for dates or times only present in the other series step Resultant series have values on the union of dates / times. Each series will use the value of the previous valid point if requested date does not exist. Values prior to the first date will be equivalent to the first available value time Resultant series have values on the union of dates / times. Missing values surrounded by valid values will be interpolated given length of interval. Input series must use DateTimeIndex. ========= ======================================================================== **Examples** Stepwize interpolation of series based on dates in second series: >>> a = generate_series(100) >>> b = generate_series(100) >>> align(a, b) **See also** :func:`sub` """ if isinstance(x, Real) and isinstance(y, Real): return [x, y] if isinstance(x, Real): return [pd.Series(x, index=y.index), y] if isinstance(y, Real): return [x, pd.Series(y, index=x.index)] if method == Interpolate.INTERSECT: return x.align(y, 'inner') if method == Interpolate.NAN: return x.align(y, 'outer') if method == Interpolate.ZERO: return x.align(y, 'outer', fill_value=0) if method == Interpolate.TIME: new_x, new_y = x.align(y, 'outer') new_x = new_x.interpolate('time', limit_area='inside') new_y = new_y.interpolate('time', limit_area='inside') return [new_x, new_y] if method == Interpolate.STEP: new_x, new_y = x.align(y, 'outer') new_x = new_x.ffill().bfill() new_y = new_y.ffill().bfill() return [new_x, new_y] else: raise MqValueError('Unknown intersection type: ' + method) @plot_function def interpolate( x: pd.Series, dates: Union[List[dt.date], List[dt.time], pd.Series] = None, method: Interpolate = Interpolate.INTERSECT, ) -> pd.Series: """ Interpolate over specified dates or times :param x: timeseries to interpolate :param dates: array of dates/times or another series to interpolate :param method: interpolation method (default: intersect) :return: timeseries with specified dates **Usage** Interpolate the series X over the dates specified by the dates parameter. This can be an array of dates or another series, in which case the index of the series will be used to specify dates Interpolation methods: ========= ======================================================================== Type Behavior ========= ======================================================================== intersect Resultant series only has values on the intersection of dates /times. Will only contain intersection of valid dates / times in the series nan Resultant series only has values on the intersection of dates /times. Value will be NaN for dates not present in the series zero Resultant series has values on all requested dates / times. The series will have a value of zero where the requested date or time was not present in the series step Resultant series has values on all requested dates / times. The series will use the value of the previous valid point if requested date does not exist. Values prior to the first date will be equivalent to the first available value ========= ======================================================================== **Examples** Stepwize interpolation of series based on dates in second series: >>> a = generate_series(100) >>> b = generate_series(100) >>> interpolate(a, b, Interpolate.INTERSECT) **See also** :func:`sub` """ if dates is None: dates = x if isinstance(dates, pd.Series): align_series = dates else: align_series = pd.Series(np.nan, dates) if method == Interpolate.INTERSECT: return x.align(align_series, 'inner')[0] if method == Interpolate.NAN: return x.align(align_series, 'right')[0] if method == Interpolate.ZERO: align_series = pd.Series(0.0, dates) return x.align(align_series, 'right', fill_value=0)[0] if method == Interpolate.STEP: return __interpolate_step(x, align_series) else: raise MqValueError('Unknown intersection type: ' + method) @plot_function def value(x: pd.Series, date: Union[dt.date, dt.time], method: Interpolate = Interpolate.STEP) -> pd.Series: """ Value at specified date or time :param x: timeseries :param date: requested date or time :param method: interpolation method (default: step) :return: value at specified date or time **Usage** Returns the value of series X at the specified date: :math:`Y_t = X_{date}` If the requested date or time is not present in the series, the value function will return the value from the previous available date or time by default. Caller can specify other interpolation styles via the method param: Interpolation methods: ========= ======================================================================== Type Behavior ========= ======================================================================== intersect Only returns a value for valid dates nan Value will be NaN for dates not present in the series zero Value will be zero for dates not present in the series step Value of the previous valid point if requested date does not exist. Values prior to the first date will be equivalent to the first available value ========= ======================================================================== **Examples** Value of series on 5Mar18: >>> a = generate_series(100) >>> value(a, date(2019, 1, 3) **See also** :func:`interpolate` """ values = interpolate(x, [date], method) return None if values.empty else values.iloc[0] @plot_function def day(x: pd.Series) -> pd.Series: """ Day of each value in series :param x: time series :return: day of observations **Usage** Returns the day as a numeric value for each observation in the series: :math:`Y_t = day(t)` Day of the time or date is the integer day number within the month, e.g. 1-31 **Examples** Day for observations in series: >>> series = generate_series(100) >>> days = day(series) **See also** :func:`month` :func:`year` """ return pd.Series(pd.to_datetime(x.index.to_series()).dt.day, dtype=np.int64) @plot_function def month(x: pd.Series) -> pd.Series: """ Month of each value in series :param x: time series :return: month of observations **Usage** Returns the month as a numeric value for each observation in the series: :math:`Y_t = month(t)` Month of the time or date is the integer month number, e.g. 1-12 **Examples** Day for observations in series: >>> series = generate_series(100) >>> days = month(series) **See also** :func:`day` :func:`year` """ return pd.Series(pd.to_datetime(x.index.to_series()).dt.month, dtype=np.int64) @plot_function def year(x: pd.Series) -> pd.Series: """ Year of each value in series :param x: time series :return: year of observations **Usage** Returns the year as a numeric value for each observation in the series: :math:`Y_t = year(t)` Year of the time or date is the integer year number, e.g. 2019, 2020 **Examples** Year for observations in series: >>> series = generate_series(100) >>> days = year(series) **See also** :func:`day` :func:`month` """ return pd.Series(pd.to_datetime(x.index.to_series()).dt.year, dtype=np.int64) @plot_function def quarter(x: pd.Series) -> pd.Series: """ Quarter of each value in series :param x: time series :return: quarter of observations **Usage** Returns the quarter as a numeric value for each observation in the series: :math:`Y_t = quarter(t)` Quarter of the time or date is the integer quarter number, e.g. 1, 2, 3, 4 **Examples** Quarter for observations in series: >>> series = generate_series(100) >>> days = quarter(series) **See also** :func:`day` :func:`month` """ return pd.Series(pd.to_datetime(x.index.to_series()).dt.quarter, dtype=np.int64) @plot_function def weekday(x: pd.Series) -> pd.Series: """ Weekday of each value in series :param x: time series :return: weekday of observations **Usage** Returns the weekday as a numeric value for each observation in the series: :math:`Y_t = weekday(t)` Weekday of the time or date is the integer day of the week, e.g. 0-6, where 0 represents Monday **Examples** Weekday for observations in series: >>> series = generate_series(100) >>> days = weekday(series) **See also** :func:`day` :func:`month` """ return pd.Series(pd.to_datetime(x.index.to_series()).dt.weekday, dtype=np.int64) @plot_function def day_count_fractions( dates: Union[List[dt.date], pd.Series], convention: DayCountConvention = DayCountConvention.ACTUAL_360, frequency: PaymentFrequency = PaymentFrequency.MONTHLY, ) -> pd.Series: """ Day count fractions between dates in series :param dates: time series or array of dates :param convention: day count convention (default: Actual/360 ISDA) :param frequency: payment frequency of instrument (optional) :return: series of day count fractions **Usage** Returns the day count fraction between dates in the series :math:`Y_t = DCF(t_{-1}, t)` Default is Actual/360 per ISDA specification: :math:`Y_t = \frac{Days(t_{-1}, t)}{360}` For a full list of available conventions, see `Day Count Conventions `_. For more information on day count conventions, see the `day count conventions `_ page on Wikipedia **Examples** Weekday for observations in series: >>> series = generate_series(100) >>> days = day_count_fractions(series) **See also** :func:`day` :func:`month` :func:`year` """ if isinstance(dates, pd.Series): date_list = list(dates.index) else: date_list = dates if len(date_list) < 2: return pd.Series(dtype=float) start_dates = date_list[0:-1] end_dates = date_list[1 : len(date_list)] dcfs = map(lambda a, b: day_count_fraction(a, b, convention, frequency), start_dates, end_dates) return pd.Series(data=[np.nan] + list(dcfs), index=date_list[0 : len(date_list)]) @plot_function def date_range( x: pd.Series, start_date: Union[dt.date, int], end_date: Union[dt.date, int], weekdays_only: bool = False ) -> pd.Series: """ Create a time series from a (sub-)range of dates in an existing time series. :param x: time series :param start_date: start date for the sliced time series. If integer, number of observations after the first :param end_date: end date for the sliced time series. If integer, number of observations before the last :param weekdays_only: whether to include only weekdays in the sliced ranges :return: sliced time series **Usage** Returns a restricted ("sliced") time series based on start and end dates: :math:`Y_t = R_t |_{start < t < end}` **Examples** Slice the first and last week of a time series: >>> series = generate_series(100) >>> sliced_series = date_range(series,7,7) create time series with the absolute date: >>> date_range(series, dt.date(2021,1,1), dt.date(2021,12,30)) **See also** :func:`day` :func: `lag` """ if not isinstance(weekdays_only, bool): raise MqTypeError('expected a boolean value for "weekdays_only"') if not (isinstance(x.index, pd.DatetimeIndex) or all(map(lambda a: isinstance(a, dt.date), x.index.values))): raise MqValueError('input is not a time series') if isinstance(start_date, int): start_date = x.index[start_date] if isinstance(end_date, int): end_date = x.index[-(1 + end_date)] try: start_date = start_date.date() end_date = end_date.date() except AttributeError: pass if weekdays_only: week_mask = None wd = start_date.weekday() if wd > 4: start_date += dt.timedelta(days=7 - wd) else: week_mask = tuple([True] * 7) date_list = list(_date_range(start_date, end_date, week_mask=week_mask)) if isinstance(x.index, pd.DatetimeIndex): date_list = [pd.Timestamp(d) for d in date_list] return x.loc[x.index.isin(date_list)] @plot_function def append(series: List[pd.Series]) -> pd.Series: """ Append data series :param series: an array of timeseries :return: concatenated timeseries **Usage** For input series [:math:`x_1`, :math:`x_2`, ... , :math:`x_n`], takes data from series :math:`x_i` until that series runs out, then appends data from `x_{i+1}` until that series runs out. **Examples** Append two series: >>> x = generate_series(100) >>> y = generate_series(100) >>> append([x, y]) **See also** :func:`prepend` """ if not len(series): return pd.Series(dtype='float64') res = series[0].copy() for i in range(1, len(series)): cur = series[i] start = res.index[-1] res = pd.concat([res, cur.loc[cur.index > start]]) return res @plot_function def prepend(x: List[pd.Series]) -> pd.Series: """ Prepend data series :param x: an array of timeseries :return: concatenated timeseries **Usage** For input series [:math:`x_1`, :math:`x_2`, ... , :math:`X_n`], takes data from series :math:`X_i` until the first date for which :math:`X_{i+1}` has data, useful when a higher quality series has a shorter history than a lower quality series. **Examples** Prepend two series: >>> x = generate_series(100) >>> y = generate_series(100) >>> prepend([x, y]) **See also** :func:`union` """ if not x: return pd.Series(dtype='float64') if len(x) == 1: return x[0].copy() parts = [] for i in range(len(x)): this = x[i] if i == len(x) - 1: parts.append(this) else: end = x[i + 1].index[0] parts.append(this.loc[this.index < end]) return pd.concat(parts) @plot_function def union(x: List[pd.Series]) -> pd.Series: """ Fill in missing dates or times of one series with another :param x: an array of timeseries :return: combined series **Usage** Starting from :math:`i=1`, takes points from series :math:`x_i`. Where points are missing from :math:`x_i`, returns points from :math:`x_{i+1}`. **Examples** Union of two series: >>> x = generate_series(100) >>> y = generate_series(100) >>> union([x, y]) **See also** :func:`prepend` """ if len(x): res = pd.Series(dtype='float64', index=x[0].index) for series in x: res = res.combine_first(series) else: res = pd.Series(dtype='float64') return res @plot_function def bucketize(series: pd.Series, aggregate_function: AggregateFunction, period: AggregatePeriod) -> pd.Series: """ Bucketize a series and apply aggregate function to each bucket :param series: input series :param aggregate_function: function to use for aggregating data in each bucket :param period: size of each bucket :return: output series **Usage** Bucketize a series and apply aggregate function to each bucket. The result will be indexed by the end date of each bucket. **Examples** Monthly mean of a series: >>> x = generate_series(100) >>> bucketize(x, AggregateFunction.MEAN, AggregatePeriod.MONTH) **See also** :func:`moving_average` """ series.index = pd.to_datetime(series.index) period_char = period.value[0].upper() frequency_map = {'W': 'W', 'M': FREQ_MONTH_END, 'Q': FREQ_QUARTER_END, 'Y': FREQ_YEAR_END} frequency = frequency_map.get(period_char, period_char) agg = aggregate_function.value result = series.resample(frequency).apply(agg) if result.empty: return result last_timestamp = min(series.index[-1], result.index[-1]) idx = result.index[:-1].insert(len(result) - 1, last_timestamp) result.index = idx return result @plot_function def day_count(first: dt.date, second: dt.date) -> int: """ Counts the number of business days between two dates. Monday through Friday are considered to be business days. :param first: first date :param second: second date :return: number of business days between first and second """ if not (isinstance(first, dt.date) and isinstance(second, dt.date)): raise MqValueError('inputs must be dates') return np.busday_count(first, second) @plot_function def day_countdown(end_date: dt.date, start_date: dt.date = None, business_days: bool = False) -> pd.Series: """Create a series counting down the number of days from each date to ``end_date``. :param end_date: last date / countdown target :param start_date: first date in the generated series (default: today) :param business_days: whether to use business days (default False) :return: timeseries of day counts **Usage** The returned series is indexed by dates from ``start_date`` to ``end_date`` (inclusive). Values are integers giving the number of days between the index date and ``end_date``. If ``business_days`` is True, the index will include only Monday through Friday as business days and the counts will be business-day counts. If False (default), the index will include all calendar days and the counts will be calendar-day counts. **Examples** Calendar-day countdown from 7May21 to 17May21: >>> day_countdown(Date(2035, 5, 17), Date(2025, 5, 7)) Business-day countdown (Mon-Fri only): >>> day_countdown(Date(2035, 5, 17), Date(2025, 5, 7), True) Calendar-day countdown starting today: >>> day_countdown(Date(2035, 5, 17)) """ if start_date is None: start_date = dt.date.today() if not isinstance(start_date, dt.date): raise MqValueError('start_date must be a date') if not isinstance(end_date, dt.date): raise MqValueError('end_date must be a date') if start_date > end_date: return pd.Series(dtype=np.int64) if business_days: idx = pd.bdate_range(start=start_date, end=end_date) if idx.empty: return pd.Series(dtype=np.int64) # busday_count is [start, end) so end_date itself is 0 start_days = idx.values.astype('datetime64[D]') end_day = np.datetime64(end_date).astype('datetime64[D]') values = np.busday_count(start_days, end_day).astype(np.int64) else: idx = pd.date_range(start=start_date, end=end_date, freq='D') start_days = idx.values.astype('datetime64[D]') end_day = np.datetime64(end_date).astype('datetime64[D]') values = (end_day - start_days).astype(np.int64) return pd.Series(values, index=idx) @requires_session @plot_function def align_calendar(series: pd.Series, calendar: str) -> pd.Series: """ Aligns a series to a given calendar removing values in the holiday calendar and the week mask (i.e. remove Saturday and Sunday) :param series: data series to align :param calendar: calendar to align the series (i.e. NYC, NYSE, USD) :return: timeseries of the data aligned to a calendar **Examples** >>> x = generate_series(100) >>> align_calendar(x, 'NYC') """ gs_calendar = GsCalendar.get(calendar) cbd = pd.tseries.offsets.CustomBusinessDay(calendar=gs_calendar.business_day_calendar()) filtered_series = series[ series.index.isin(pd.date_range(start=series.first_valid_index(), end=series.last_valid_index(), freq=cbd)) ] return filtered_series ================================================ FILE: gs_quant/timeseries/econometrics.py ================================================ # Copyright 2018 Goldman Sachs. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # # # Chart Service will attempt to make public functions (not prefixed with _) from this module available. Such functions # should be fully documented: docstrings should describe parameters and the return value, and provide a 1-line # description. Type annotations should be provided for parameters. import math from enum import IntEnum, Enum from typing import Union, Optional import numpy as np import pandas as pd from gs_quant.api.gs.data import GsDataApi, QueryType from gs_quant.common import Currency from gs_quant.markets.securities import Asset from .analysis import LagMode, lag from .datetime import align, interpolate from .helper import ( CurveType, Interpolate, Window, normalize_window, apply_ramp, plot_session_function, plot_function, Returns, SeriesType, ) from .statistics import std, product, sum_, mean, MeanType from ..data import DataContext from ..datetime import DayCountConvention, day_count_fraction from ..errors import MqValueError, MqTypeError """ Econometrics timeseries library is for standard economic and time series analytics operations, including returns, diffs, lags, volatilities and other numerical operations which are generally finance-oriented """ class AnnualizationFactor(IntEnum): DAILY = 252 WEEKLY = 52 SEMI_MONTHLY = 26 MONTHLY = 12 QUARTERLY = 4 ANNUALLY = 1 class SharpeAssets(Enum): USD = 'MAP35DA6K5B1YXGX' AUD = 'MAFRZWJ790MQY0EW' CHF = 'MAS0NN4ZX7NYXB36' EUR = 'MA95W0N1214395N8' GBP = 'MA41ZEFTWR8Q7HBM' JPY = 'MA8GXV3SJ0TXH1JV' SEK = 'MAGNZZY0GJ4TATNG' def excess_returns_pure(price_series: pd.Series, spot_curve: pd.Series) -> pd.Series: curve, bench_curve = align(price_series, spot_curve, Interpolate.INTERSECT) e_returns = [curve.iloc[0]] for i in range(1, len(curve)): multiplier = 1 + curve.iloc[i] / curve.iloc[i - 1] - bench_curve.iloc[i] / bench_curve.iloc[i - 1] e_returns.append(e_returns[-1] * multiplier) return pd.Series(e_returns, index=curve.index) def excess_returns( price_series: pd.Series, benchmark_or_rate: Union[Asset, Currency, float], *, day_count_convention=DayCountConvention.ACTUAL_360, ) -> pd.Series: if isinstance(benchmark_or_rate, float): er = [price_series.iloc[0]] for j in range(1, len(price_series)): fraction = day_count_fraction(price_series.index[j - 1], price_series.index[j], day_count_convention) er.append(er[-1] + price_series.iloc[j] - price_series.iloc[j - 1] * (1 + benchmark_or_rate * fraction)) return pd.Series(er, index=price_series.index) if isinstance(benchmark_or_rate, Currency): try: marquee_id = SharpeAssets[benchmark_or_rate.value].value except KeyError: raise MqValueError(f"unsupported currency {benchmark_or_rate}") else: marquee_id = benchmark_or_rate.get_marquee_id() with DataContext(price_series.index[0], price_series.index[-1]): q = GsDataApi.build_market_data_query([marquee_id], QueryType.SPOT) df = GsDataApi.get_market_data(q) if df.empty: raise MqValueError(f'could not retrieve risk-free rate {marquee_id}') df = df[~df.index.duplicated(keep='first')] # handle bad data (duplicate rows) return excess_returns_pure(price_series, df['spot']) def _annualized_return( levels: pd.Series, rolling: Union[int, pd.DateOffset], interpolation_method: Interpolate = Interpolate.NAN ) -> pd.Series: if isinstance(rolling, pd.DateOffset): starting = [tstamp - rolling for tstamp in levels.index] levels = interpolate(levels, method=interpolation_method) points = list( map( lambda d, v, i: pow(v / levels.get(i, np.nan), 365.25 / (d - i).days) - 1, levels.index[1:], levels.values[1:], starting[1:], ) ) else: if interpolation_method is not Interpolate.NAN: raise MqValueError( f'If w is not a relative date, method must be nan. You specified method: {interpolation_method.value}.' ) starting = [0] * rolling starting.extend([a for a in range(1, len(levels) - rolling + 1)]) points = list( map( lambda d, v, i: pow(v / levels.iloc[i], 365.25 / (d - levels.index[i]).days) - 1, levels.index[1:], levels.values[1:], starting[1:], ) ) points.insert(0, 0) return pd.Series(points, index=levels.index) def get_ratio_pure( er: pd.Series, w: Union[Window, int, str], interpolation_method: Interpolate = Interpolate.NAN ) -> pd.Series: w = normalize_window(er, w or None) # continue to support 0 as an input for window ann_return = _annualized_return(er, w.w, interpolation_method=interpolation_method) long_enough = (er.index[-1] - w.w) >= er.index[0] if isinstance(w.w, pd.DateOffset) else w.w < len(er) ann_vol = volatility(er, w).iloc[1:] if long_enough else volatility(er) result = ann_return / ann_vol * 100 return apply_ramp(result, w) def _get_ratio( input_series: pd.Series, benchmark_or_rate: Union[Asset, float, str], w: Union[Window, int, str], *, day_count_convention: DayCountConvention, curve_type: CurveType = CurveType.PRICES, interpolation_method: Interpolate = Interpolate.NAN, ) -> pd.Series: if curve_type == CurveType.PRICES: er = excess_returns(input_series, benchmark_or_rate, day_count_convention=day_count_convention) else: assert curve_type == CurveType.EXCESS_RETURNS er = input_series return get_ratio_pure(er, w, interpolation_method) class RiskFreeRateCurrency(Enum): USD = "USD" AUD = "AUD" CHF = "CHF" EUR = "EUR" GBP = "GBP" JPY = "JPY" SEK = "SEK" _USD = "usd" _AUD = "aud" _CHF = "chf" _EUR = "eur" _GBP = "gbp" _JPY = "jpy" _SEK = "sek" @plot_session_function def excess_returns_(price_series: pd.Series, currency: RiskFreeRateCurrency = RiskFreeRateCurrency.USD) -> pd.Series: """ Calculate excess returns :param price_series: price series :param currency: currency for risk-free rate, defaults to USD :return: excess returns **Usage** Given a price series P and risk-free rate R, excess returns E are defined as: :math:`E_t = E_{t-1} + P_t - P_{t-1} * (1 + R * (D_t - D_{t-1}) / 360)` The `Actual/360 `_ day count convention is used. **Examples** Get excess returns from a price series. >>> er = excess_returns(generate_series(100), USD) """ return excess_returns(price_series, Currency(currency.value), day_count_convention=DayCountConvention.ACTUAL_360) @plot_session_function def sharpe_ratio( series: pd.Series, currency: RiskFreeRateCurrency = RiskFreeRateCurrency.USD, w: Union[Window, int, str] = None, curve_type: CurveType = CurveType.PRICES, method: Interpolate = Interpolate.NAN, ) -> pd.Series: """ Calculate Sharpe ratio :param series: series of prices or excess returns for an asset :param currency: currency for risk-free rate, defaults to USD :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :param curve_type: whether input series is of prices or excess returns, defaults to prices :param method: interpolation method (default: nan). Used to calculate returns on dates without data (i.e. weekends) when window is a relative date. Defaults to no interpolation. :return: Sharpe ratio **Usage** Given a price series P, risk-free rate R, and window of size w returns the rolling `Sharpe ratio `_ S: :math:`S_t = \\frac{(E_t / E_{t-w+1})^{365.25 / (D_t - D_{t-w})}-1}{volatility(E, w)_t}` Excess returns E are defined as: :math:`E_t = E_{t-1} + P_t - P_{t-1} * (1 + R * (D_t - D_{t-1}) / 360)` where D is the date for a data point. The `Actual/360 `_ day count convention is used. **Examples** Get rolling sharpe ratio of a price series (with window of 22). >>> sr = sharpe_ratio(generate_series(365, END_TODAY), USD, 22, PRICES) **See also** :func:`volatility` """ return _get_ratio( series, Currency(currency.value), w, day_count_convention=DayCountConvention.ACTUAL_360, curve_type=curve_type, interpolation_method=method, ) @plot_function def returns(series: pd.Series, obs: Union[Window, int, str] = 1, type: Returns = Returns.SIMPLE) -> pd.Series: """ Calculate returns from price series :param series: time series of prices :param obs: number of observations or relative date e.g. 3d, 1w, 1m ( relative date should be of pattern \d+[dmywDMYW] ). :param type: returns type: simple, logarithmic or absolute :return: date-based time series of return **Usage** Compute returns series from price levels, based on the value of *type*: =========== ============================= Type Description =========== ============================= simple Simple arithmetic returns logarithmic Logarithmic returns absolute Absolute returns =========== ============================= *Simple* Simple geometric change in asset prices, which can be aggregated across assets :math:`Y_t = \\frac{X_t}{X_{t-obs}} - 1` where :math:`X_t` is the asset price at time :math:`t` *Logarithmic* Natural logarithm of asset price changes, which can be aggregated through time :math:`Y_t = log(X_t) - log(X_{t-obs})` where :math:`X_t` is the asset price at time :math:`t` *Absolute* Absolute change in asset prices :math:`Y_t = X_t - X_{t-obs}` where :math:`X_t` is the asset price at time :math:`t` **Examples** Generate price series and take compute returns >>> prices = generate_series(100) >>> returns = returns(prices) **See also** :func:`prices` """ if series.size < 1: return series shifted_series = lag(series, obs, LagMode.TRUNCATE) if isinstance(obs, str) and not isinstance(series.index, pd.DatetimeIndex): series = series.copy() series.index = pd.DatetimeIndex(series.index) if type == Returns.SIMPLE: ret_series = series / shifted_series - 1 elif type == Returns.LOGARITHMIC: ret_series = series.apply(math.log) - shifted_series.apply(math.log) elif type == Returns.ABSOLUTE: ret_series = series - shifted_series else: raise MqValueError('Unknown returns type (use simple / logarithmic / absolute)') return ret_series @plot_function def prices(series: pd.Series, initial: int = 1, type: Returns = Returns.SIMPLE) -> pd.Series: """ Calculate price levels from returns series :param series: time series of returns :param initial: initial price level :param type: returns type: simple, logarithmic or absolute :return: date-based time series of return **Usage** Compute price levels from returns series, based on the value of *type*: =========== ============================= Type Description =========== ============================= simple Simple arithmetic returns logarithmic Logarithmic returns absolute Absolute returns =========== ============================= *Simple* Compute asset price series from simple returns: :math:`Y_t = (1 + X_{t-1}) Y_{t-1}` where :math:`X_t` is the asset price at time :math:`t` and :math:`Y_0 = initial` *Logarithmic* Compute asset price series from logarithmic returns: :math:`Y_t = e^{X_{t-1}} Y_{t-1}` where :math:`X_t` is the asset price at time :math:`t` and :math:`Y_0 = initial` *Absolute* Compute asset price series from absolute returns: :math:`Y_t = X_{t-1} + Y_{t-1}` where :math:`X_t` is the asset price at time :math:`t` and :math:`Y_0 = initial` **Examples** Generate price series and take compute returns >>> series = generate_series(100) >>> returns = prices(returns(series)) **See also** :func:`returns` :func:`product` :func:`exp` """ if series.size < 1: return series if type == Returns.SIMPLE: return product(1 + series) * initial elif type == Returns.LOGARITHMIC: return product(series.apply(math.exp)) * initial elif type == Returns.ABSOLUTE: return sum_(series) + initial else: raise MqValueError('Unknown returns type (use simple / Logarithmic / absolute)') @plot_function def index(x: pd.Series, initial: int = 1) -> pd.Series: """ Geometric series normalization :param x: time series :param initial: initial value :return: normalized time series **Usage** Divides every value in x by the initial value of x: :math:`Y_t = initial * X_t / X_0` where :math:`X_0` is the first value in the series **Examples** Normalize series to 1: >>> series = generate_series(100) >>> returns = index(series) **See also** :func:`returns` """ i = x.first_valid_index() if not x[i]: raise MqValueError( 'Divide by zero error. Ensure that the first value of series passed to index(...) is non-zero' ) return pd.Series(dtype=float) if i is None else initial * x / x[i] @plot_function def change(x: pd.Series) -> pd.Series: """ Arithmetic series normalization :param x: time series :return: normalized time series **Usage** Compute difference of every value from the initial value of x: :math:`Y_t = X_t - X_0` where :math:`X_0` is the first value in the series **Examples** Change in level from initial value: >>> series = generate_series(100) >>> returns = change(series) **See also** :func:`index` """ return x - x.iloc[0] def _get_annualization_factor(x): prev_idx = x.index[0] distances = [] for idx, value in x.iloc[1:].items(): d = (idx - prev_idx).days if d == 0: raise MqValueError('multiple data points on same date') distances.append(d) prev_idx = idx average_distance = np.average(distances) if average_distance < 2.1: factor = AnnualizationFactor.DAILY elif 6 <= average_distance < 8: factor = AnnualizationFactor.WEEKLY elif 14 <= average_distance < 17: factor = AnnualizationFactor.SEMI_MONTHLY elif 25 <= average_distance < 35: factor = AnnualizationFactor.MONTHLY elif 85 <= average_distance < 97: factor = AnnualizationFactor.QUARTERLY elif 360 <= average_distance < 386: factor = AnnualizationFactor.ANNUALLY else: raise MqValueError('Cannot infer annualization factor, average distance: ' + str(average_distance)) return factor @plot_function def annualize(x: pd.Series) -> pd.Series: """ Annualize series based on sample observation frequency :param x: time series of prices :return: date-based time series of annualized values **Usage** Based on number of days between observations, will determine an annualization factor and then adjust values accordingly. Useful for annualizing daily or monthly returns :math:`Y_t = X_t * \\sqrt{F}` Annualization factors as follows, based on period implied by observations: ========= ============================= Period Annualization Factor (F) ========= ============================= Daily :math:`252` Weekly :math:`52` Bi-Weekly :math:`26` Monthly :math:`12` Quarterly :math:`4` Annually :math:`1` ========= ============================= **Examples** Annualize daily returns series: >>> prices = generate_series(100) >>> ann = annualize(returns(prices)) **See also** :func:`returns` """ factor: int = _get_annualization_factor(x) return x * math.sqrt(factor) @plot_function def volatility( x: pd.Series, w: Union[Window, int, str] = Window(None, 0), returns_type: Optional[Returns] = Returns.SIMPLE, annualization_factor: Optional[int] = None, assume_zero_mean: bool = False, ) -> pd.Series: """ Realized volatility of price or return series :param x: time series of prices or returns (see returns_type parameter) :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :param returns_type: returns type: simple, logarithmic or absolute. If None, x is assumed to be a returns series. :param annualization_factor: Annualization factor for volatility scaling. If None (default), auto-detects based on data frequency (252 for daily, 52 for weekly, etc.). :param assume_zero_mean: if True, assumes zero mean for returns (uses population formula with N denominator). if False (default), uses sample statistics (N-1 denominator). :return: date-based time series of annualized volatility (in percent) **Usage** Calculate rolling annualized realized volatility of a price series over a given window. Annual volatility of 20% is returned as 20.0: **Standard volatility (assume_zero_mean=False, default):** :math:`Y_t = \\sqrt{\\frac{1}{N-1} \\sum_{i=t-w+1}^t (R_t - \\overline{R_t})^2} * \\sqrt{252} * 100` where N is the number of observations in each rolling window :math:`w`, :math:`R_t` is the return on time :math:`t` based on *returns_type* **Zero-mean volatility (assume_zero_mean=True):** Uses root-mean-square (RMS) with population formula: :math:`Y_t = \\sqrt{\\frac{1}{N} \\sum_{i=t-w+1}^t R_i^2} \\times \\sqrt{252} \\times 100` This assumes $$\\overline{R} = 0$$ and uses $$N$$ (not $$N-1$$) in the denominator. This is the standard convention for: - Correlation swap volatility calculations (to match zero-mean covariance) - Variance swap realized volatility - Pre-demeaned returns or zero-drift processes **Returns types:** =========== ======================================================= Type Description =========== ======================================================= simple Simple geometric change in asset prices: :math:`R_t = \\frac{X_t}{X_{t-1}} - 1` where :math:`X_t` is the asset price at time :math:`t` logarithmic Natural logarithm of asset price changes: :math:`R_t = log(X_t) - log(X_{t-1})` where :math:`X_t` is the asset price at time :math:`t` absolute Absolute change in asset prices: :math:`Y_t = X_t - X_{t-obs}` where :math:`X_t` is the asset price at time :math:`t` NULL Input x is already a returns series; no conversion is performed =========== ======================================================= and :math:`\\overline{R_t}` is the mean value over the same window: :math:`\\overline{R_t} = \\frac{\\sum_{i=t-w+1}^{t} R_t}{N}` If window is not provided, computes realized volatility over the full series. **Annualization:** If `annualization_factor` is None, the factor is auto-detected based on average time between observations: ================== ============================= Data Frequency Annualization Factor (F) ================== ============================= Daily $$252$$ Weekly $$52$$ Semi-Monthly $$26$$ Monthly $$12$$ Quarterly $$4$$ Annually $$1$$ ================== ============================= **Examples** Compute rolling :math:`1` month (:math:`22` business day) annualized volatility of price series >>> series = generate_series(100) >>> vol_series = volatility(series, 22) >>> vol_series = volatility(series, Window(22, 30)) Compute volatility with logarithmic returns: >>> vol_log = volatility(series, 22, returns_type=Returns.LOGARITHMIC) Compute volatility from a returns series directly: >>> returns_series = returns(series, Returns.SIMPLE) >>> vol_from_returns = volatility(returns_series, 22, returns_type=None) Compute zero-mean volatility: >>> vol_zero_mean = volatility(series, 22, assume_zero_mean=True) Compute FX volatility with 365-day annualization: >>> vol_fx = volatility(fx_series, 22, annualization_factor=365) **See also** :func:`std` :func:`annualize` :func:`returns` :func:`mean` :func:`corr_swap_correlation` """ w = normalize_window(x, w) if x.size < 1: return x ret = returns(x, type=returns_type) if returns_type is not None else x window = Window(w.w, 0) vol = mean(ret, window, MeanType.QUADRATIC) if assume_zero_mean else std(ret, window) annualized_vol = vol * math.sqrt(annualization_factor) if annualization_factor is not None else annualize(vol) return apply_ramp(annualized_vol.mul(100), w) @plot_function def vol_swap_volatility( prices: pd.Series, n_days: Union[int, Window] = None, annualization_factor: int = 252, assume_zero_mean: bool = True ) -> pd.Series: """ Rolling volatility of a price series for volatility swap pricing :param prices: price series :param n_days: number of price days in the rolling window. Defaults to length of series if not provided. :param annualization_factor: number of periods per year for annualization. Default is 252 :param assume_zero_mean: if False, uses sample mean in volatility calculation (standard volatility). if True (default), assumes zero mean for returns (volatility swap convention) :return: date-based time series of annualized volatility in percentage terms **Usage** Calculate rolling realized volatility, :math:`\\sigma_t` of a price series over a given window using logarithmic returns. **When assume_zero_mean=True (default):** Computes zero-mean volatility (volatility swap convention): :math:`\\sigma_t = \\sqrt{AF \\times \\frac{1}{N}\\sum_{i=1}^{N} R_i^2}` where :math:`AF` is the annualization factor (default 252), :math:`N` is the number of returns (n_days - 1), and :math:`R_i` are the logarithmic returns: :math:`R_i = \\ln\\left(\\frac{P_i}{P_{i-1}}\\right)` This assumes :math:`\\overline{R} = 0`, which is the standard convention for volatility swap payoff calculations. The divisor is :math:`N` (not :math:`N-1`). **When assume_zero_mean=False:** Computes standard sample volatility: :math:`\\sigma_t = \\sqrt{AF \\times \\frac{1}{N-1}\\sum_{i=1}^{N} (R_i - \\overline{R})^2}` where :math:`\\overline{R}` is the sample mean over the window, and the divisor is :math:`N-1` (Bessel's correction for unbiased estimation). **Examples** Compute rolling 22 price day (21 return) volatility swap volatility: >>> series = generate_series(100) >>> vol_swap = vol_swap_volatility(series, n_days=22) Compute with 365-day annualization instead of default 252: >>> vol_252 = vol_swap_volatility(series, n_days=22, annualization_factor=365) Compute volatility over the full series: >>> full_vol = vol_swap_volatility(series) # n_days defaults to series length **See also** :func:`volatility` :func:`returns` """ if n_days is None: n_days = len(prices) if isinstance(n_days, Window): if n_days.r != n_days.w - 1: raise MqTypeError('Ramp-up must be the size of the window minus 1, e.g. Window(4, 3).') window = n_days else: window = Window(n_days, n_days - 1) return volatility(prices, window, Returns.LOGARITHMIC, annualization_factor, assume_zero_mean) @plot_function def correlation( x: pd.Series, y: pd.Series, w: Union[Window, int, str] = Window(None, 0), type_: SeriesType = SeriesType.PRICES, returns_type: Returns = Returns.SIMPLE, assume_zero_mean: bool = False, ) -> pd.Series: """ Rolling correlation of two price series :param x: price series :param y: price series :param w: Window, int, or str: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :param type_: type of both input series: prices or returns :param returns_type: Method to calculate returns when type_ is PRICES, simple, logarithmic or absolute. You can also pass two methods to calculate returns for x and y respectively. :param assume_zero_mean: if False (default), uses sample mean in correlation calculation (standard Pearson correlation). if True, assumes zero mean for returns (correlation swap convention) :return: date-based time series of correlation **Usage** Calculate rolling `realized correlation `_, :math:`\\rho_t` of two price series over a given window: :math:`\\rho_t = \\frac{\\sum_{i=t-w+1}^t (R_t - \\overline{R_t})(Y_t - \\overline{S_t})}{(N-1)\\sigma R_t\\sigma S_t}` where N is the number of observations in each rolling window, :math:`w`, and :math:`R_t` and :math:`S_t` are the simple returns for each series on time :math:`t` If prices are provided then returns are calculated based on the returns_type e.g. for simple returns: :math:`R_t = \\frac{X_t}{X_{t-1}} - 1` and :math:`S_t = \\frac{Y_t}{Y_{t-1}} - 1` If returns are provided: :math:`R_t = X_t` and :math:`S_t = Y_t` :math:`\\overline{R_t}`, :math:`\\overline{S_t}` are the mean values, and :math:`\\sigma R_{t}` and :math:`\\sigma S_{t}` are the sample standard deviations, of series :math:`R_t` and :math:`S_t` over the same window If window is not provided, computes realized correlation over the full series **Examples** Compute rolling :math:`1` month (:math:`22` business day) correlation of price series >>> series1 = generate_series(100) >>> series2 = generate_series(100) >>> corr = correlation(series1, series2, 22) **See also** :func:`std` :func:`returns` """ w = normalize_window(x, w) if x.size < 1: return x given_prices = type_ == SeriesType.PRICES if given_prices: if isinstance(returns_type, (tuple, list)): if len(returns_type) != 2: raise MqValueError('Expected a list of length 2 for "returns_type"') if not all(isinstance(r, Returns) for r in returns_type): raise MqTypeError('Expected a list of Returns for "returns_type"') returns_type_x, returns_type_y = returns_type else: returns_type_x = returns_type_y = returns_type ret_1 = returns(x, type=returns_type_x) ret_2 = returns(y, type=returns_type_y) else: ret_1 = x ret_2 = y aligned = pd.DataFrame({'r1': ret_1, 'r2': ret_2}).dropna() clean_ret1 = aligned['r1'] clean_ret2 = aligned['r2'] if assume_zero_mean: def daily_vols(returns): vols = volatility(returns, w, returns_type=None, assume_zero_mean=True) annualization_factor = _get_annualization_factor(returns) result = vols.div(math.sqrt(annualization_factor) * 100) return result zero_mean_cov = mean(clean_ret1 * clean_ret2, w) vols_1 = daily_vols(clean_ret1) vols_2 = daily_vols(clean_ret2) corr = pd.Series(zero_mean_cov / (vols_1 * vols_2), index=clean_ret1.index) else: if isinstance(w.w, pd.DateOffset): if isinstance(clean_ret1.index, pd.DatetimeIndex): values = [ clean_ret1.loc[(clean_ret1.index > idx - w.w) & (clean_ret1.index <= idx)].corr(clean_ret2) for idx in clean_ret1.index ] else: values = [ clean_ret1.loc[(clean_ret1.index > (idx - w.w).date()) & (clean_ret1.index <= idx)].corr(clean_ret2) for idx in clean_ret1.index ] corr = pd.Series(values, index=clean_ret1.index) else: corr = clean_ret1.rolling(w.w, 0).corr(clean_ret2) return apply_ramp(interpolate(corr, x, Interpolate.NAN), w) @plot_function def corr_swap_correlation( x: pd.Series, y: pd.Series, n_days: Union[int, Window] = None, assume_zero_mean: bool = True ) -> pd.Series: """ Rolling correlation of two price series for correlation swap pricing :param x: price series :param y: price series :param n_days: number of price days in the rolling window. Defaults to length of series if not provided. Note: the returns window size will be n_days - 1 :param assume_zero_mean: if False, uses sample mean in correlation calculation (Pearson correlation). if True (default), assumes zero mean for returns (correlation swap convention) :return: date-based time series of correlation **Usage** Calculate rolling `realized correlation `_, :math:`\\rho_t` of two price series over a given window using logarithmic returns. **When assume_zero_mean=False (default):** Computes standard Pearson correlation: :math:`\\rho_t = \\frac{\\sum_{i=t-w+1}^t (R_i - \\overline{R})(S_i - \\overline{S})}{(w-1)\\sigma_R \\sigma_S}` where :math:`w` is the window size (n_days - 1), :math:`R_i` and :math:`S_i` are the logarithmic returns: :math:`R_i = \\ln\\left(\\frac{X_i}{X_{i-1}}\\right)` and :math:`S_i = \\ln\\left(\\frac{Y_i}{Y_{i-1}}\\right)` :math:`\\overline{R}` and :math:`\\overline{S}` are the sample means, and :math:`\\sigma_R` and :math:`\\sigma_S` are the sample standard deviations over the window. **When assume_zero_mean=True:** Computes zero-mean correlation (correlation swap convention): :math:`\\rho_t = \\frac{\\sum_{i=t-w+1}^t R_i S_i}{(w-1)\\sqrt{\\sum_{i=t-w+1}^t R_i^2} \\sqrt{\\sum_{i=t-w+1}^t S_i^2}}` This assumes :math:`\\overline{R} = \\overline{S} = 0`, which is the standard convention for correlation swap payoff calculations. **Examples** Compute rolling :math:`22` price day (21 return) standard Pearson correlation: >>> series1 = generate_series(100) >>> series2 = generate_series(100) >>> corr = corr_swap_correlation(series1, series2, n_days=22, assume_zero_mean=False) Compute zero-mean correlation for correlation swap pricing: >>> corr_swap = corr_swap_correlation(series1, series2, n_days=22) Compute correlation over the full series: >>> full_corr = corr_swap_correlation(series1, series2) # n_days defaults to series length **See also** :func:`correlation` :func:`returns` :func:`volatility` """ if n_days is None: n_days = min(len(x), len(y)) if isinstance(n_days, Window): if n_days.r != n_days.w - 1: raise MqTypeError('Ramp-up must be the size of the window minus 1, e.g. Window(4, 3).') window = n_days else: window = Window(n_days - 1, n_days - 2) return correlation(x, y, window, SeriesType.PRICES, Returns.LOGARITHMIC, assume_zero_mean) @plot_function def beta(x: pd.Series, b: pd.Series, w: Union[Window, int, str] = Window(None, 0), prices: bool = True) -> pd.Series: """ Rolling beta of price series and benchmark :param x: time series of prices :param b: time series of benchmark prices :param w: Window, int, or str: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :param prices: True if input series are prices, False if they are returns :return: date-based time series of beta **Usage** Calculate rolling `beta `_, :math:`\\beta_t` of a series to a benchmark over a given window: :math:`R_t = \\alpha_t + \\beta S_t + \\epsilon_t` Calculated as: :math:`\\beta_t = \\frac{\\sum_{i=t-w+1}^t Cov(R_t, S_t)}{Var(S_t)}` where N is the number of observations in each rolling window, :math:`w`, and :math:`R_t` and :math:`S_t` are the simple returns for each series on time :math:`t`: :math:`R_t = \\frac{X_t}{X_{t-1}} - 1` and :math:`S_t = \\frac{b_t}{b_{t-1}} - 1` If prices = False, assumes returns are provided: :math:`R_t = X_t` and :math:`S_t = b_t` :math:`Cov(R_t, S_t)` and :math:`Var(S_t)` are the covariance and variance of the series :math:`R_t` and :math:`S_t` over the same window If window is not provided, computes beta over the full series **Examples** Compute rolling :math:`1` month (:math:`22` business day) beta of two price series >>> series = generate_series(100) >>> benchmark = generate_series(100) >>> b = beta(series, benchmark, 22) **See also** :func:`var` :func:`cov` :func:`correlation` :func:`returns` """ if not isinstance(prices, bool): raise MqTypeError('expected a boolean value for "prices"') w = normalize_window(x, w) ret_series = returns(x) if prices else x ret_benchmark = returns(b) if prices else b if isinstance(w.w, pd.DateOffset): series_index = ret_series.index.intersection(ret_benchmark.index) size = len(series_index) ret_series = ret_series.loc[series_index] benchmark_series = ret_benchmark.loc[series_index] ret_values = np.array(ret_series.values, dtype=np.double) benchmark_values = np.array(benchmark_series.values, dtype=np.double) cov_results = np.empty(size, dtype=np.double) var_results = np.empty(size, dtype=np.double) offset = w.w start = 0 for i in range(1, size): min_index_value = (series_index[i] - offset).date() for idx in range(start, i + 1): if series_index[idx] > min_index_value: start = idx break sub_benchmark_values = benchmark_values[start : i + 1] var_results[i] = np.var(sub_benchmark_values, ddof=1) cov_results[i] = np.cov(ret_values[start : i + 1], sub_benchmark_values, ddof=1)[0][1] result = pd.Series(cov_results / var_results, index=series_index, dtype=np.double) else: cov = ret_series.rolling(w.w, 0).cov(ret_benchmark) result = cov / ret_benchmark.rolling(w.w, 0).var() # do not compute initial values as they may be extreme when sample size is small result[0:3] = np.nan return apply_ramp(interpolate(result, x, Interpolate.NAN), w) @plot_function def max_drawdown(x: pd.Series, w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Compute the maximum peak to trough drawdown over a rolling window as a ratio. i.e. if the max drawdown for a period is 20%, this function will return -0.2. :param x: time series :param w: Window, int, or str: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: time series of rolling maximum drawdown **Examples** Compute the maximum peak to trough `drawdown `_ >>> series = generate_series(100) >>> max_drawdown(series) **See also** :func:`returns` """ w = normalize_window(x, w) if isinstance(w.w, pd.DateOffset): if pd.api.types.is_datetime64_dtype(x.index): scores = pd.Series( [x[idx] / x.loc[(x.index > (idx - w.w)) & (x.index <= idx)].max() - 1 for idx in x.index], index=x.index ) result = pd.Series( [scores.loc[(scores.index > (idx - w.w)) & (scores.index <= idx)].min() for idx in scores.index], index=scores.index, ) else: raise TypeError('Please pass in list of dates as index') else: rolling_max = x.rolling(w.w, 0).max() result = (x / rolling_max - 1).rolling(w.w, 0).min() return apply_ramp(result, w) ================================================ FILE: gs_quant/timeseries/helper.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import inspect import logging import os import re from enum import Enum, IntEnum from functools import wraps, partial from typing import Optional, Union, List, Iterable, Callable import numpy as np import pandas as pd from gs_quant.api.gs.data import QueryType from gs_quant.api.utils import ThreadPoolManager from gs_quant.data import DataContext, Dataset from gs_quant.datetime.relative_date import RelativeDate from gs_quant.entities.entity import EntityType from gs_quant.errors import MqValueError, MqRequestError from gs_quant.timeseries.measure_registry import register_measure _PD_VERSION_MATCH = re.match(r'^(\d+)\.(\d+)', pd.__version__) _PD_VERSION = (int(_PD_VERSION_MATCH.group(1)), int(_PD_VERSION_MATCH.group(2))) if _PD_VERSION_MATCH else (0, 0) if _PD_VERSION >= (2, 2): FREQ_MONTH_END = 'ME' FREQ_QUARTER_END = 'QE' FREQ_YEAR_END = 'YE' FREQ_HOUR = 'h' FREQ_SECOND = 's' FREQ_BUSINESS_MONTH_END = 'BME' FREQ_BUSINESS_QUARTER_END = 'BQE' FREQ_BUSINESS_YEAR_END = 'BYE' else: # pragma: no cover FREQ_MONTH_END = 'M' FREQ_QUARTER_END = 'Q' FREQ_YEAR_END = 'Y' FREQ_HOUR = 'H' FREQ_SECOND = 'S' FREQ_BUSINESS_MONTH_END = 'BM' FREQ_BUSINESS_QUARTER_END = 'BQ' FREQ_BUSINESS_YEAR_END = 'BY' ENABLE_DISPLAY_NAME = 'GSQ_ENABLE_MEASURE_DISPLAY_NAME' USE_DISPLAY_NAME = os.environ.get(ENABLE_DISPLAY_NAME) == "1" _logger = logging.getLogger(__name__) class Entitlement(Enum): INTERNAL = 'internal' try: from quant_extensions.timeseries.rolling import rolling_apply except ImportError as e: _logger.debug('unable to import rolling_apply extension: %s', e) def rolling_apply(s: pd.Series, offset: pd.DateOffset, function: Callable[[np.ndarray], float]) -> pd.Series: if isinstance(s.index, pd.DatetimeIndex): values = [function(s.loc[(s.index > (idx - offset)) & (s.index <= idx)]) for idx in s.index] else: values = [function(s.loc[(s.index > (idx - offset).date()) & (s.index <= idx)]) for idx in s.index] return pd.Series(values, index=s.index, dtype=np.double) def _create_enum(name, members): return Enum(name, {n.upper(): n.lower() for n in members}, module=__name__) def _create_int_enum(name, mappings): return IntEnum(name, {k.upper(): v for k, v in mappings.items()}) def _to_offset(tenor: str) -> pd.DateOffset: import re matcher = re.fullmatch('(\\d+)([hdwmy])', tenor) if not matcher: raise MqValueError('invalid tenor ' + tenor) ab = matcher.group(2) if ab == 'h': name = 'hours' elif ab == 'd': name = 'days' elif ab == 'w': name = 'weeks' elif ab == 'm': name = 'months' else: assert ab == 'y' name = 'years' kwarg = {name: int(matcher.group(1))} return pd.DateOffset(**kwarg) def _tenor_to_month(relative_date: str) -> int: matcher = re.fullmatch('([1-9]\\d*)([my])', relative_date) if matcher: mag = int(matcher.group(1)) return mag if matcher.group(2) == 'm' else mag * 12 raise MqValueError('invalid input: relative date must be in months or years') Interpolate = _create_enum('Interpolate', ['intersect', 'step', 'nan', 'zero', 'time']) Returns = _create_enum('Returns', ['simple', 'logarithmic', 'absolute']) SeriesType = _create_enum('SeriesType', ['prices', 'returns']) CurveType = _create_enum('CurveType', ['prices', 'excess_returns']) class Window: """ Create a Window with size and ramp up to use. :param w: window size :param r: ramp up value. Defaults to the window size. :return: new window object **Usage** The window size and ramp up value can either the number of observations or a string representation of the time period. **Examples** Window size is :math:`22` observations and the ramp up value is :math:`10`: >>> Window(22, 10) Window size is one month and the ramp up size is one week: >>> Window('1m', '1w') """ def __init__(self, w: Union[int, str, None] = None, r: Union[int, str, None] = None): self.w = w self.r = w if r is None else r def as_dict(self): return {'w': self.w, 'r': self.r} @classmethod def from_dict(cls, obj): return Window(w=obj.get('w'), r=obj.get('r')) def _check_window(series_length: int, window: Window): if series_length > 0 and isinstance(window.w, int) and isinstance(window.r, int): if window.w <= 0: raise MqValueError('Window value must be greater than zero.') if window.r > series_length or window.r < 0: raise MqValueError('Ramp value must be less than the length of the series and greater than zero.') def apply_ramp(x: pd.Series, window: Window) -> pd.Series: _check_window(len(x), window) if isinstance(window.w, int) and window.w > len(x): # does not restrict window size when it is a DataOffset return pd.Series(dtype=float) if isinstance(window.r, pd.DateOffset): if np.issubdtype(x.index, dt.date): return x.loc[(x.index[0] + window.r).date() :] else: return x.loc[(x.index[0] + window.r).to_pydatetime() :] else: return x[window.r :] def normalize_window( x: Union[pd.Series, pd.DataFrame], window: Union[Window, int, str, None], default_window: int = None ) -> Window: if default_window is None: default_window = len(x) if isinstance(window, int): window = Window(window, window) elif isinstance(window, str): window = Window(_to_offset(window), _to_offset(window)) else: if window is None: window = Window(default_window, 0) else: if isinstance(window.w, str): window = Window(_to_offset(window.w), window.r) if isinstance(window.r, str): window = Window(window.w, _to_offset(window.r)) if window.w is None: window = Window(default_window, window.r) _check_window(default_window, window) return window def plot_function(fn): # Indicates that fn should be exported to plottool as a pure function. fn.plot_function = True return fn def plot_session_function(fn): fn.plot_function = True fn.requires_session = True return fn def check_forward_looking(pricing_date, source, name="function"): if pricing_date is not None or source != 'plottool': return if DataContext.current.end_date <= dt.date.today(): msg = ( f'{name}() requires a forward looking date range e.g. [0d, 3y]. ' 'Please update the date range via the date picker.' ) raise MqValueError(msg) def plot_measure( asset_class: tuple, asset_type: Optional[tuple] = None, dependencies: Optional[List[QueryType]] = tuple(), asset_type_excluded: Optional[tuple] = None, display_name: Optional[str] = None, entitlements: Optional[List[Entitlement]] = [], ): # Indicates that fn should be exported to plottool as a member function / pseudo-measure. # Set category to None for no restrictions, else provide a tuple of allowed values. def decorator(fn): assert isinstance(asset_class, tuple) assert len(asset_class) >= 1 assert asset_type is None or isinstance(asset_type, tuple) assert asset_type_excluded is None or isinstance(asset_type_excluded, tuple) assert asset_type is None or asset_type_excluded is None fn.plot_measure = True fn.entity_type = EntityType.ASSET fn.asset_class = asset_class fn.asset_type = asset_type fn.asset_type_excluded = asset_type_excluded fn.dependencies = dependencies fn.entitlements = entitlements if USE_DISPLAY_NAME: fn.display_name = display_name multi_measure = register_measure(fn) multi_measure.entity_type = EntityType.ASSET return multi_measure else: return fn return decorator def plot_measure_entity(entity_type: EntityType, dependencies: Optional[Iterable[QueryType]] = tuple()): def decorator(fn): assert isinstance(entity_type, EntityType) if dependencies is not None: assert isinstance(dependencies, Iterable) assert all(isinstance(x, QueryType) for x in dependencies) fn.plot_measure_entity = True fn.entity_type = entity_type fn.dependencies = tuple(dependencies) # immutable return fn return decorator def requires_session(fn): fn.requires_session = True return fn def plot_method(fn): # Indicates that fn should be exported to plottool as a method. fn.plot_method = True # Allows fn to accept and ignore real_time argument even if it is not defined in the signature @wraps(fn) def ignore_extra_argument(*args, **kwargs): for arg in ('real_time', 'interval', 'time_filter'): if arg not in inspect.signature(fn).parameters: kwargs.pop(arg, None) return fn(*args, **kwargs) return ignore_extra_argument def log_return(logger: logging.Logger, message): def outer(fn): @wraps(fn) def inner(*args, **kwargs): response = fn(*args, **kwargs) logger.debug('%s: %s', message, response) return response return inner return outer def get_df_with_retries(fetcher, start_date, end_date, exchange, retries=1): """ Loads results from Data Service by calling fetcher function. Shifts query date range back by business days until result is not empty or retry limit reached. This is a fallback feature in case a data upload is late. Measure implementations should be written such that retries are usually not required. :param fetcher: a no-argument function runs a data query and returns a DataFrame :param start_date: initial start date for query :param end_date: initial end date for query :param exchange: exchange to use for holiday calendar :param retries: maximum number of retries :return: DataFrame """ retries = max(retries, 0) while retries > -1: with DataContext(start_date, end_date): result = fetcher() if not result.empty: break kwargs = {'exchanges': [exchange]} if exchange else {} # no need to include any part of the previous date range since it's known to be empty end_date = RelativeDate('-1b', base_date=start_date).apply_rule(**kwargs) start_date = end_date retries -= 1 return result def get_dataset_data_with_retries( dataset: Dataset, *, start: dt.date, end: dt.date, count: int = 0, max_retries: int = 5, **kwargs ) -> pd.DataFrame: try: data = dataset.get_data(start=start, end=end, **kwargs) except MqRequestError as e: if count < max_retries: mid = start + (end - start) / 2 count += 1 first_half = partial(get_dataset_data_with_retries, dataset, start=start, end=mid, count=count, **kwargs) mid = mid + dt.timedelta(days=1) second_half = partial(get_dataset_data_with_retries, dataset, start=mid, end=end, count=count, **kwargs) results = ThreadPoolManager.run_async([first_half, second_half]) first_half_results, second_half_results = results[0], results[1] data = pd.concat([first_half_results, second_half_results]).sort_index() else: raise e return data def get_dataset_with_many_assets( ds: Dataset, *, assets: List[str], start: dt.date, end: dt.date, batch_limit: int = 100, **kwargs ) -> pd.DataFrame: tasks = [ partial(ds.get_data, assetId=assets[i : i + batch_limit], start=start, end=end, return_type=None, **kwargs) for i in range(0, len(assets), batch_limit) ] results = ThreadPoolManager.run_async(tasks) return pd.concat(results) def _month_to_tenor(months: int) -> str: return f'{months // 12}y' if months % 12 == 0 else f'{months}m' def _split_where_conditions(where): la = [dict()] for k, v in where.items(): lb = [] while len(la) > 0: temp = la.pop() if isinstance(v, list): for cv in v: clone = temp.copy() clone[k] = [cv] lb.append(clone) else: lb.append(dict(**temp, **{k: v})) la = lb return la def _pandas_roll(s: pd.Series, window_str: str, method_name: str): return getattr(s.rolling(window_str), method_name)() def rolling_offset( s: pd.Series, offset: pd.DateOffset, function: Callable[[np.ndarray], float], method_name: str = None ) -> pd.Series: """ Perform rolling window calculations. If offset has a fixed frequency and method name is provided, will use `Series.rolling< https://pandas.pydata.org/docs/reference/api/pandas.Series.rolling.html>`_ for best performance. :param s: time series :param offset: window size as a date offset :param function: function to call on each window :param method_name: name of method to call on each window (must be a method on Rolling object) :return: result time series """ # frequencies that can be passed to Series.rolling fixed = {'hour': 'h', 'hours': 'h', 'day': 'D', 'days': 'D'} if method_name and len(offset.kwds) == 1: freq, count = offset.kwds.popitem() if freq in fixed: window_str = f'{count}{fixed[freq]}' if np.issubdtype(s.index, np.datetime64): return _pandas_roll(s, window_str, method_name) else: t = s.copy(deep=False) t.index = pd.to_datetime(t.index) # needed for Series.rolling return pd.Series(_pandas_roll(t, window_str, method_name), index=s.index) return rolling_apply(s, offset, function) ================================================ FILE: gs_quant/timeseries/measure_registry.py ================================================ """ Copyright 2021 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import re from gs_quant.errors import MqError registry = {} class MultiMeasure: def __init__(self, display_name): self.display_name = display_name self.measure_map = {} def get_fn(self, asset): asset_class = asset.asset_class asset_type = asset.get_type() fns = self.measure_map.get(asset_class, ()) def canonicalize(word): pruned = re.sub(r"[^\w]", "", word) return pruned.casefold() canonicalized = canonicalize(asset_type.value) for fn in fns: if (fn.asset_type is None or canonicalized in map(lambda x: canonicalize(x.value), fn.asset_type)) and ( fn.asset_type_excluded is None or canonicalized not in map(lambda x: canonicalize(x.value), fn.asset_type_excluded) ): return fn raise MqError( "No measure {} defined for asset class {} and type {}".format(self.display_name, asset_class, asset_type) ) def __call__(self, asset, *args, **kwargs): fn = self.get_fn(asset) return fn(asset, *args, **kwargs) def register(self, function): for asset_class in function.asset_class: self.measure_map[asset_class] = self.measure_map.get(asset_class, ()) + (function,) def register_measure(fn): display_name = fn.display_name if fn.display_name else fn.__name__ multi_measure = registry.get(display_name) if multi_measure is None: multi_measure = registry[display_name] = MultiMeasure(display_name) multi_measure.register(fn) return multi_measure ================================================ FILE: gs_quant/timeseries/measures.py ================================================ # Copyright 2019 Goldman Sachs. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # # Plot Service will make use of appropriately decorated functions in this module. import calendar import datetime as dt import logging import re from collections import namedtuple from enum import Enum, auto from functools import partial from numbers import Real from typing import Union, Optional, Tuple, List import cachetools.func import inflection import numpy as np import pandas as pd from dateutil import tz from dateutil.relativedelta import relativedelta from gs_quant.api.gs.assets import GsAssetApi, GsIdType from gs_quant.api.gs.data import MarketDataResponseFrame, QueryType, GsDataApi from gs_quant.api.gs.indices import GsIndexApi from gs_quant.api.utils import ThreadPoolManager from gs_quant.common import AssetClass, AssetType, PricingLocation from gs_quant.data import Dataset from gs_quant.data.core import DataContext, IntervalFrequency from gs_quant.data.fields import Fields, DataMeasure from gs_quant.data.log import log_debug, log_warning from gs_quant.datetime import DAYS_IN_YEAR from gs_quant.datetime.gscalendar import GsCalendar from gs_quant.datetime.point import relative_date_add from gs_quant.entities.entity import PositionedEntity from gs_quant.errors import MqValueError, MqTypeError from gs_quant.markets.securities import Asset, AssetIdentifier, AssetType as SecAssetType, SecurityMaster, Stock from gs_quant.timeseries import Basket, RelativeDate, Returns, Window, sqrt, volatility from gs_quant.timeseries.helper import ( FREQ_MONTH_END, _month_to_tenor, _split_where_conditions, _tenor_to_month, _to_offset, check_forward_looking, get_dataset_with_many_assets, get_df_with_retries, log_return, plot_measure, ) from gs_quant.timeseries.measures_helper import EdrDataReference, VolReference, preprocess_implied_vol_strikes_eq from pandas.tseries.holiday import ( AbstractHolidayCalendar, Holiday, USLaborDay, USMemorialDay, USThanksgivingDay, sunday_to_monday, ) from pydash import chunk, flatten, get GENERIC_DATE = Union[dt.date, str] ASSET_SPEC = Union[Asset, str] TD_ONE = dt.timedelta(days=1) def _normalize_dtidx(idx): if not isinstance(idx, pd.DatetimeIndex): idx = pd.DatetimeIndex(idx) return idx.as_unit('ns') if hasattr(idx, 'as_unit') else idx _logger = logging.getLogger(__name__) MeasureDependency: namedtuple = namedtuple("MeasureDependency", ["id_provider", "query_type"]) class ExtendedSeries(pd.Series): _internal_names = pd.Series._internal_names + ['dataset_ids'] _internal_names_set = set(_internal_names) @property def _constructor(self): return ExtendedSeries def __finalize__(self, other, method=None, **kwargs): # Call the parent class's __finalize__ method super().__finalize__(other, method) # Copy custom attributes from the other DataFrame if isinstance(other, ExtendedSeries) and hasattr(other, 'dataset_ids'): self.dataset_ids = getattr(other, 'dataset_ids', None) return self # TODO: get NERC Calendar from SecDB class NercCalendar(AbstractHolidayCalendar): rules = [ Holiday('New Years Day', month=1, day=1, observance=sunday_to_monday), USMemorialDay, Holiday('July 4th', month=7, day=4, observance=sunday_to_monday), USLaborDay, USThanksgivingDay, Holiday('Christmas', month=12, day=25, observance=sunday_to_monday), ] class UnderlyingSourceCategory(Enum): ALL = 'All' EDGX = 'EDGX' TRF = 'TRF' class GICSSector(Enum): INFORMATION_TECHNOLOGY = 'Information Technology' ENERGY = 'Energy' MATERIALS = 'Materials' INDUSTRIALS = 'Industrials' CONSUMER_DISCRETIONARY = 'Consumer Discretionary' CONSUMER_STAPLES = 'Consumer Staples' HEALTH_CARE = 'Health Care' FINANCIALS = 'Financials' COMMUNICATION_SERVICES = 'Communication Services' REAL_ESTATE = 'Real Estate' UTILITIES = 'Utilities' ALL = 'All' class RetailMeasures(Enum): RETAIL_PCT_SHARES = 'impliedRetailPctShares' RETAIL_PCT_NOTIONAL = 'impliedRetailPctNotional' RETAIL_SHARES = 'impliedRetailShares' RETAIL_NOTIONAL = 'impliedRetailNotional' SHARES = 'shares' NOTIONAL = 'notional' RETAIL_BUY_NOTIONAL = 'impliedRetailBuyNotional' RETAIL_BUY_PCT_NOTIONAL = 'impliedRetailBuyPctNotional' RETAIL_BUY_PCT_SHARES = 'impliedRetailBuyPctShares' RETAIL_BUY_SHARES = 'impliedRetailBuyShares' RETAIL_SELL_NOTIONAL = 'impliedRetailSellNotional' RETAIL_SELL_PCT_NOTIONAL = 'impliedRetailSellPctNotional' RETAIL_SELL_PCT_SHARES = 'impliedRetailSellPctShares' RETAIL_SELL_SHARES = 'impliedRetailSellShares' class SkewReference(Enum): DELTA = 'delta' NORMALIZED = 'normalized' SPOT = 'spot' FORWARD = 'forward' class NormalizationMode(Enum): NORMALIZED = 'normalized' OUTRIGHT = 'outright' class CdsVolReference(Enum): DELTA_CALL = 'delta_call' DELTA_PUT = 'delta_put' FORWARD = 'forward' class VolSmileReference(Enum): SPOT = 'spot' FORWARD = 'forward' DELTA = 'delta' NORMALIZED = 'normalized' class FXForwardType(Enum): POINTS = 'points' OUTRIGHT = 'outright' class FxForecastHorizon(Enum): THREE_MONTH = '3m' SIX_MONTH = '6m' TWELVE_MONTH = '12m' EOY1 = 'EOY1' EOY2 = 'EOY2' EOY3 = 'EOY3' EOY4 = 'EOY4' class _FxForecastTimeSeriesPeriodType(Enum): SHORT_TERM = '3/6/12-Month' ANNUAL = 'Annual' class FundamentalMetricPeriodDirection(Enum): FORWARD = 'forward' TRAILING = 'trailing' class RatesConversionType(Enum): DEFAULT_BENCHMARK_RATE = auto() DEFAULT_SWAP_RATE_ASSET = auto() INFLATION_BENCHMARK_RATE = auto() CROSS_CURRENCY_BASIS = auto() OIS_BENCHMARK_RATE = auto() class EsgMetric(Enum): ENVIRONMENTAL_SOCIAL_AGGREGATE_SCORE = 'es_score' ENVIRONMENTAL_SOCIAL_AGGREGATE_PERCENTILE = 'es_percentile' ENVIRONMENTAL_SOCIAL_DISCLOSURE = 'es_disclosure_percentage' ENVIRONMENTAL_SOCIAL_NUMERIC_SCORE = 'es_numeric_score' ENVIRONMENTAL_SOCIAL_NUMERIC_PERCENTILE = 'es_numeric_percentile' ENVIRONMENTAL_SOCIAL_POLICY_SCORE = 'es_policy_score' ENVIRONMENTAL_SOCIAL_POLICY_PERCENTILE = 'es_policy_percentile' ENVIRONMENTAL_SOCIAL_PRODUCT_IMPACT_SCORE = 'es_product_impact_score' ENVIRONMENTAL_SOCIAL_PRODUCT_IMPACT_PERCENTILE = 'es_product_impact_percentile' GOVERNANCE_AGGREGATE_SCORE = 'g_score' GOVERNANCE_AGGREGATE_PERCENTILE = 'g_percentile' GOVERNANCE_REGIONAL_SCORE = 'g_regional_score' GOVERNANCE_REGIONAL_PERCENTILE = 'g_regional_percentile' ENVIRONMENTAL_SOCIAL_MOMENTUM_SCORE = 'es_momentum_score' ENVIRONMENTAL_SOCIAL_MOMENTUM_PERCENTILE = 'es_momentum_percentile' CONTROVERSY_SCORE = 'controversy_score' CONTROVERSY_PERCENTILE = 'controversy_percentile' class SwaptionTenorType(Enum): OPTION_EXPIRY = 'option_expiry' SWAP_MATURITY = 'swap_maturity' class EquilibriumExchangeRateMetric(Enum): GSDEER = 'gsdeer' GSFEER = 'gsfeer' class _FactorProfileMetric(Enum): GROWTH_SCORE = 'growthScore' FINANCIAL_RETURNS_SCORE = 'financialReturnsScore' MULTIPLE_SCORE = 'multipleScore' INTEGRATED_SCORE = 'integratedScore' class _CommodityForecastType(Enum): SPOT = 'spot' SPOT_RETURN = 'spotReturn' ROLL_RETURN = 'rollReturn' TOTAL_RETURN = 'totalReturn' class _CommodityForecastTimeSeriesPeriodType(Enum): SHORT_TERM = '3/6/12-Month Rolling' MONTHLY = 'Monthly' QUARTERLY = 'Quarterly' ANNUAL = 'Annual' class _RatingMetric(Enum): RATING = 'rating' CONVICTION_LIST = 'convictionList' class EUNatGasDataReference(Enum): # Reference data as on the asset parameters, will distinguish Heren(USD) and ICE(EUR/ GBP) Swaps TTF_ICE_CommodityReferencePrice = 'ICE Data: FUTURES REPORT: ICE Endex Dutch TTF Gas Base Load Futures' NBP_ICE_CommodityReferencePrice = 'UK Monthly-Ice' TTF_Heren_CommodityReferencePrice = 'TTF-Day Ahead and Weekend Unweighted Average Price-Heren' NBP_Heren_CommodityReferencePrice = 'NBP-Day Ahead and Weekend Unweighted Average Price-Heren' # Add map for currency to priceMethod USD = "Heren" ICE = "ICE" class EUPowerDataReference(Enum): ICE_EXCHANGE = 'ICE' EEX_EXCHANGE = 'EEX' NASDAQ_EXCHANGE = 'NASDAQ' CARBON_CREDIT_DATASET = 'COMMOD_US_ELEC_CARBON_CREDITS_FUTURES' CARBON_CREDIT_PRODUCT = 'Physical Environment' EU_POWER_EXCHANGE_DATASET = 'COMMOD_EU_POWER_EXCHANGE_DATA' EU_POWER_PRODUCT = 'PowerFutures' class FXSpotCarry(Enum): ANNUALIZED = 'annualized' DAILY = 'daily' ESG_METRIC_TO_QUERY_TYPE = { "esNumericScore": QueryType.ES_NUMERIC_SCORE, "esNumericPercentile": QueryType.ES_NUMERIC_PERCENTILE, "esPolicyScore": QueryType.ES_POLICY_SCORE, "esPolicyPercentile": QueryType.ES_POLICY_PERCENTILE, "esScore": QueryType.ES_SCORE, "esPercentile": QueryType.ES_PERCENTILE, "esProductImpactScore": QueryType.ES_PRODUCT_IMPACT_SCORE, "esProductImpactPercentile": QueryType.ES_PRODUCT_IMPACT_PERCENTILE, "gScore": QueryType.G_SCORE, "gPercentile": QueryType.G_PERCENTILE, "esMomentumScore": QueryType.ES_MOMENTUM_SCORE, "esMomentumPercentile": QueryType.ES_MOMENTUM_PERCENTILE, "gRegionalScore": QueryType.G_REGIONAL_SCORE, "gRegionalPercentile": QueryType.G_REGIONAL_PERCENTILE, "esDisclosurePercentage": QueryType.ES_DISCLOSURE_PERCENTAGE, "controversyScore": QueryType.CONTROVERSY_SCORE, "controversyPercentile": QueryType.CONTROVERSY_PERCENTILE, } CURRENCY_TO_OIS_RATE_BENCHMARK = { 'AUD': 'AUD OIS', 'USD': 'USD OIS', 'EUR': 'EUR OIS', 'GBP': 'GBP OIS', 'JPY': 'JPY OIS', 'CAD': 'CAD OIS', 'NOK': 'NOK OIS', 'NZD': 'NZD OIS', 'SEK': 'SEK OIS', 'CHF': 'CHF OIS', } CURRENCY_TO_DEFAULT_RATE_BENCHMARK = { 'USD': 'USD-LIBOR-BBA', 'EUR': 'EUR-EURIBOR-Telerate', 'GBP': 'GBP-LIBOR-BBA', 'JPY': 'JPY-LIBOR-BBA', } CURRENCY_TO_INFLATION_RATE_BENCHMARK = {'GBP': 'CPI-UKRPI', 'EUR': 'CPI-CPXTEMU'} CROSS_TO_CROSS_CURRENCY_BASIS = { 'JPYUSD': 'USD-3m/JPY-3m', 'USDJPY': 'USD-3m/JPY-3m', 'USDEUR': 'EUR-3m/USD-3m', 'EURUSD': 'EUR-3m/USD-3m', 'USDGBP': 'GBP-3m/USD-3m', 'GBPUSD': 'GBP-3m/USD-3m', } _FACTOR_PROFILE_METRIC_TO_QUERY_TYPE = { 'growthScore': QueryType.GROWTH_SCORE, 'financialReturnsScore': QueryType.FINANCIAL_RETURNS_SCORE, 'multipleScore': QueryType.MULTIPLE_SCORE, 'integratedScore': QueryType.INTEGRATED_SCORE, } _RATING_METRIC_TO_QUERY_TYPE = {'rating': QueryType.RATING, 'convictionList': QueryType.CONVICTION_LIST} _COMMOD_CONTRACT_MONTH_CODES = "FGHJKMNQUVXZ" _COMMOD_CONTRACT_MONTH_CODES_DICT = {k: v for k, v in enumerate(_COMMOD_CONTRACT_MONTH_CODES)} def _asset_from_spec(asset_spec: ASSET_SPEC) -> Asset: return ( asset_spec if isinstance(asset_spec, Asset) else SecurityMaster.get_asset(asset_spec, AssetIdentifier.MARQUEE_ID) ) def _cross_stored_direction_helper(bbid): if not re.fullmatch('[A-Z]{6}', bbid): raise TypeError('not a cross that can be reversed') legit_usd_cross = str.startswith(bbid, "USD") and not str.endswith(bbid, ("EUR", "GBP", "NZD", "AUD")) legit_eur_cross = str.startswith(bbid, "EUR") legit_jpy_cross = str.endswith(bbid, "JPY") and not str.startswith(bbid, ("KRW", "IDR", "CLP", "COP")) odd_cross = bbid in ("EURUSD", "GBPUSD", "NZDUSD", "AUDUSD", "JPYKRW", "JPYIDR", "JPYCLP", "JPYCOP") return bbid if any([legit_usd_cross, legit_eur_cross, legit_jpy_cross, odd_cross]) else bbid[3:] + bbid[:3] def cross_stored_direction_for_fx_vol(asset_spec: ASSET_SPEC, *, return_asset=False) -> Union[str, Asset]: asset = _asset_from_spec(asset_spec) result = asset try: if asset.asset_class is AssetClass.FX: bbid = asset.get_identifier(AssetIdentifier.BLOOMBERG_ID) if bbid is not None: cross = _cross_stored_direction_helper(bbid) if cross != bbid: cross_asset = SecurityMaster.get_asset(cross, AssetIdentifier.BLOOMBERG_ID) result = cross_asset except TypeError: result = asset return result if return_asset else result.get_marquee_id() def cross_to_usd_based_cross(asset_spec: ASSET_SPEC) -> str: asset = _asset_from_spec(asset_spec) asset_id = asset.get_marquee_id() result_id = asset_id try: if asset.asset_class is AssetClass.FX: bbid = asset.get_identifier(AssetIdentifier.BLOOMBERG_ID) if bbid is not None and not str.startswith(bbid, "USD"): if not re.fullmatch('[A-Z]{6}', bbid): raise TypeError('not a cross that can be reversed') cross = bbid[3:] + bbid[:3] cross_asset = SecurityMaster.get_asset(cross, AssetIdentifier.BLOOMBERG_ID) result_id = cross_asset.get_marquee_id() except TypeError: result_id = asset_id return result_id def currency_to_default_benchmark_rate(asset_spec: ASSET_SPEC) -> str: asset = _asset_from_spec(asset_spec) asset_id = asset.get_marquee_id() try: result = convert_asset_for_rates_data_set(asset, RatesConversionType.DEFAULT_BENCHMARK_RATE) except TypeError: result = asset_id return result def currency_to_default_ois_asset(asset_spec: ASSET_SPEC) -> str: asset = _asset_from_spec(asset_spec) try: result = convert_asset_for_rates_data_set(asset, RatesConversionType.OIS_BENCHMARK_RATE) except TypeError: result = asset.get_marquee_id() return result def currency_to_default_swap_rate_asset(asset_spec: ASSET_SPEC) -> str: asset = _asset_from_spec(asset_spec) return convert_asset_for_rates_data_set(asset, RatesConversionType.DEFAULT_SWAP_RATE_ASSET) def currency_to_inflation_benchmark_rate(asset_spec: ASSET_SPEC) -> str: asset = _asset_from_spec(asset_spec) try: result = convert_asset_for_rates_data_set(asset, RatesConversionType.INFLATION_BENCHMARK_RATE) except TypeError: result = asset.get_marquee_id() return result def cross_to_basis(asset_spec: ASSET_SPEC) -> str: asset = _asset_from_spec(asset_spec) try: result = convert_asset_for_rates_data_set(asset, RatesConversionType.CROSS_CURRENCY_BASIS) except TypeError: result = asset.get_marquee_id() return result def convert_asset_for_rates_data_set(from_asset: Asset, c_type: RatesConversionType) -> str: try: bbid = from_asset.get_identifier(AssetIdentifier.BLOOMBERG_ID) if bbid is None: return from_asset.get_marquee_id() if c_type is RatesConversionType.DEFAULT_BENCHMARK_RATE: to_asset = CURRENCY_TO_DEFAULT_RATE_BENCHMARK[bbid] elif c_type is RatesConversionType.DEFAULT_SWAP_RATE_ASSET: to_asset = ( (bbid + '-3m') if bbid == "USD" else (bbid + '-6m') if bbid in ['GBP', 'EUR', 'CHF', 'SEK'] else bbid ) elif c_type is RatesConversionType.INFLATION_BENCHMARK_RATE: to_asset = CURRENCY_TO_INFLATION_RATE_BENCHMARK[bbid] elif c_type is RatesConversionType.OIS_BENCHMARK_RATE: to_asset = CURRENCY_TO_OIS_RATE_BENCHMARK[bbid] else: to_asset = CROSS_TO_CROSS_CURRENCY_BASIS[bbid] identifiers = GsAssetApi.map_identifiers(GsIdType.mdapi, GsIdType.id, [to_asset]) if to_asset in identifiers: return identifiers[to_asset] if None in identifiers: return identifiers[None] raise MqValueError('Unable to map identifier.') except KeyError: logging.info('Unsupported currency or cross') return from_asset.get_marquee_id() def _get_custom_bd(exchange): from pandas.tseries.offsets import CustomBusinessDay calendar = GsCalendar.get(exchange).business_day_calendar() return CustomBusinessDay(calendar=calendar) @log_return(_logger, 'trying pricing dates') def _range_from_pricing_date(exchange, pricing_date: Optional[GENERIC_DATE] = None, buffer: int = 0): if isinstance(pricing_date, dt.date): return pricing_date, pricing_date today = pd.Timestamp.today().normalize() bd = _get_custom_bd(exchange) if pricing_date is None: t1 = today - bd return t1 - (buffer * bd), t1 assert isinstance(pricing_date, str) matcher = re.fullmatch('(\\d+)b', pricing_date) if matcher: start = end = today - bd * int(matcher.group(1)) else: end = today - dt.timedelta(days=relative_date_add(pricing_date, True)) start = end - bd return start, end def _market_data_timed(q, request_id=None, ignore_errors: bool = False): args = [q, request_id] if request_id else [q] return GsDataApi.get_market_data(*args, ignore_errors=ignore_errors) def _extract_series_from_df(df: pd.DataFrame, query_type: QueryType, handle_missing_column=False): col_name = query_type.value.replace(' ', '') col_name = col_name[0].lower() + col_name[1:] if df.empty or (handle_missing_column and col_name not in df.columns): series = ExtendedSeries(dtype=float) else: series = ExtendedSeries(df[col_name], index=df.index) series.dataset_ids = getattr(df, 'dataset_ids', ()) return series def _fundamentals_md_query( mqid: str, period: str, period_direction: FundamentalMetricPeriodDirection, metric: str, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: q = GsDataApi.build_market_data_query( [mqid], QueryType.FUNDAMENTAL_METRIC, where=dict(metric=metric, period=period, periodDirection=period_direction.value), source=source, real_time=real_time, ) q['queries'][0]['vendor'] = 'Goldman Sachs' log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, QueryType.FUNDAMENTAL_METRIC) @plot_measure( (AssetClass.FX, AssetClass.Equity, AssetClass.Commod), None, [MeasureDependency(id_provider=cross_stored_direction_for_fx_vol, query_type=QueryType.IMPLIED_VOLATILITY)], asset_type_excluded=(AssetType.CommodityNaturalGasHub,), ) def skew( asset: Asset, tenor: str, strike_reference: SkewReference, distance: Real, normalization_mode: NormalizationMode = None, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Difference in implied volatility of equidistant out-of-the-money put and call options. :param asset: asset object loaded from security master :param tenor: relative date representation of expiration date e.g. 1m :param strike_reference: reference for strike level (for equities) :param distance: distance from at-the-money option :param normalization_mode: "normalized" to divide skew by ATM implied volatility or "outright" to leave off this normalization. By default, we normalize skew for equities and commodities but not for FX assets. :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: skew curve """ asset_id = asset.get_marquee_id() kwargs = {} if real_time and asset.asset_class in (AssetClass.FX, AssetClass.Commod): raise MqValueError(f'real-time skew not supported for asset {asset_id}') if asset.asset_class == AssetClass.FX: asset_id = cross_stored_direction_for_fx_vol(asset_id) if normalization_mode is None: normalization_mode = NormalizationMode.OUTRIGHT if strike_reference == SkewReference.DELTA: q_strikes = [0 - distance, distance, 0] else: raise MqValueError('strike_reference has to be delta to get skew for FX options') else: assert asset.asset_class in (AssetClass.Equity, AssetClass.Commod) if normalization_mode is None: normalization_mode = NormalizationMode.NORMALIZED if strike_reference in (SkewReference.DELTA, None): b = 50 elif strike_reference == SkewReference.NORMALIZED: b = 0 else: b = 100 if strike_reference in (SkewReference.DELTA, None): # using delta call strikes so X DP is represented as (100 - X) DC for Equity options q_strikes = [100 - distance, distance, b] else: q_strikes = [b - distance, b + distance, b] if not strike_reference: raise MqTypeError('strike reference required for equities') if strike_reference != SkewReference.NORMALIZED: q_strikes = [x / 100 for x in q_strikes] kwargs['strikeReference'] = strike_reference.value column = 'relativeStrike' kwargs[column] = q_strikes log_debug(request_id, _logger, 'where tenor=%s and %s', tenor, kwargs) where = dict(tenor=tenor, **kwargs) q = GsDataApi.build_market_data_query( [asset_id], QueryType.IMPLIED_VOLATILITY, where=where, source=source, real_time=real_time ) log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) dataset_ids = getattr(df, 'dataset_ids', ()) if df.empty: series = ExtendedSeries(dtype=float) else: curves = {k: v for k, v in df.groupby(column)} if len(curves) < 3: raise MqValueError('skew not available for given inputs') series = [curves[qs]['impliedVolatility'] for qs in q_strikes] ext_series = ( ((series[0] - series[1]) / series[2]) if normalization_mode == NormalizationMode.NORMALIZED else (series[0] - series[1]) ) series = ExtendedSeries(ext_series) series.dataset_ids = dataset_ids return series @plot_measure((AssetClass.Credit,), (AssetType.Index,), [QueryType.IMPLIED_VOLATILITY_BY_DELTA_STRIKE]) def cds_implied_volatility( asset: Asset, expiry: str, tenor: str, strike_reference: CdsVolReference, relative_strike: Real, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Volatility of a cds index implied by observations of market prices. :param asset: asset object loaded from security master :param expiry: relative date representation of expiration date on the option e.g. 3m :param tenor: relative date representation of expiration date e.g. 1m :param strike_reference: reference for strike level :param relative_strike: strike relative to reference :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: implied volatility curve """ if real_time: raise NotImplementedError('realtime cds_implied_volatility not implemented') delta_strike = "ATMF" if strike_reference is CdsVolReference.FORWARD else "{}DC".format(relative_strike) option_type = "payer" if strike_reference is CdsVolReference.DELTA_CALL else "receiver" _logger.debug( 'where expiry=%s, tenor=%s, deltaStrike=%s, optionType=%s, location=NYC', expiry, tenor, delta_strike, option_type, ) q = GsDataApi.build_market_data_query( [asset.get_marquee_id()], QueryType.IMPLIED_VOLATILITY_BY_DELTA_STRIKE, where=dict(expiry=expiry, tenor=tenor, deltaStrike=delta_strike, optionType=option_type, location='NYC'), source=source, real_time=real_time, ) log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, QueryType.IMPLIED_VOLATILITY_BY_DELTA_STRIKE) @plot_measure((AssetClass.Credit,), (AssetType.Index,), [QueryType.OPTION_PREMIUM], display_name='option_premium') def option_premium_credit( asset: Asset, expiry: str, strike_reference: CdsVolReference, relative_strike: Real, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Option premium of a cds index for a given expiry and delta strike. :param asset: asset object loaded from security master :param expiry: relative date representation of expiration date on the option e.g. 3m :param strike_reference: reference for strike level :param relative_strike: strike relative to reference :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: option premium curve """ if real_time: raise NotImplementedError('realtime option premium not implemented in credit options') if strike_reference is CdsVolReference.FORWARD: delta_strike = 'ATMF' elif strike_reference is CdsVolReference.DELTA_CALL: delta_strike = "{}DC".format(relative_strike) elif strike_reference is CdsVolReference.DELTA_PUT: delta_strike = "{}DP".format(relative_strike) else: raise NotImplementedError('Option Type: {} not implemented in credit', strike_reference) _logger.debug('where expiry=%s, deltaStrike=%s', expiry, delta_strike) q = GsDataApi.build_market_data_query( [asset.get_marquee_id()], QueryType.OPTION_PREMIUM, where=dict( expiry=expiry, deltaStrike=delta_strike, ), source=source, real_time=real_time, ) log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, QueryType.OPTION_PREMIUM) @plot_measure((AssetClass.Credit,), (AssetType.Index,), [QueryType.ABSOLUTE_STRIKE], display_name='absolute_strike') def absolute_strike_credit( asset: Asset, expiry: str, strike_reference: CdsVolReference, relative_strike: Real, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Absolute strike of a cds index for a given expiry and delta strike. :param asset: asset object loaded from security master :param expiry: relative date representation of expiration date on the option e.g. 3m :param strike_reference: reference for strike level :param relative_strike: strike relative to reference :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: absolute strike curve """ if real_time: raise NotImplementedError('realtime absolute strike not implemented in credit options') if strike_reference is CdsVolReference.FORWARD: delta_strike = 'ATMF' elif strike_reference is CdsVolReference.DELTA_CALL: delta_strike = "{}DC".format(relative_strike) elif strike_reference is CdsVolReference.DELTA_PUT: delta_strike = "{}DP".format(relative_strike) else: raise NotImplementedError('Option Type: {} not implemented in credit', strike_reference) _logger.debug('where expiry=%s, deltaStrike=%s', expiry, delta_strike) q = GsDataApi.build_market_data_query( [asset.get_marquee_id()], QueryType.ABSOLUTE_STRIKE, where=dict( expiry=expiry, deltaStrike=delta_strike, ), source=source, real_time=real_time, ) log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, QueryType.ABSOLUTE_STRIKE) @plot_measure( (AssetClass.Credit,), (AssetType.Index,), [QueryType.IMPLIED_VOLATILITY_BY_DELTA_STRIKE], display_name='implied_volatility', ) def implied_volatility_credit( asset: Asset, expiry: str, strike_reference: CdsVolReference, relative_strike: Real, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Volatility of a cds index implied by observations of market prices. :param asset: asset object loaded from security master :param expiry: relative date representation of expiration date on the option e.g. 3m :param strike_reference: reference for strike level :param relative_strike: strike relative to reference :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: implied volatility curve """ if real_time: raise NotImplementedError('realtime implied_volatility not implemented in credit options') if strike_reference is CdsVolReference.FORWARD: delta_strike = 'ATMF' elif strike_reference is CdsVolReference.DELTA_CALL: delta_strike = "{}DC".format(relative_strike) elif strike_reference is CdsVolReference.DELTA_PUT: delta_strike = "{}DP".format(relative_strike) else: raise NotImplementedError('Option Type: {} not implemented in credit', strike_reference) _logger.debug('where expiry=%s, deltaStrike=%s', expiry, delta_strike) q = GsDataApi.build_market_data_query( [asset.get_marquee_id()], QueryType.IMPLIED_VOLATILITY_BY_DELTA_STRIKE, where=dict( expiry=expiry, deltaStrike=delta_strike, ), source=source, real_time=real_time, ) log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, QueryType.IMPLIED_VOLATILITY_BY_DELTA_STRIKE) @plot_measure((AssetClass.Credit,), (AssetType.Default_Swap,), [QueryType.CDS_SPREAD_100], display_name='spread') def cds_spread( asset: Asset, spread: int, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None ) -> pd.Series: """ CDS Spread levels. :param asset: asset object loaded from security master :param spread: in bps 100, 250, 500 :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: spread curve """ if real_time: raise NotImplementedError('realtime spread not implemented for cds spreads') _logger.debug('where spread=%s', spread) if spread == 100: qt = QueryType.CDS_SPREAD_100 elif spread == 250: qt = QueryType.CDS_SPREAD_250 elif spread == 500: qt = QueryType.CDS_SPREAD_500 else: raise NotImplementedError("Spread {} not implemented".format(spread)) q = GsDataApi.build_market_data_query( [asset.get_marquee_id()], qt, where=dict(pricingLocation="NYC"), source=source, real_time=real_time ) log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, qt) @plot_measure( ( AssetClass.Equity, AssetClass.Commod, ), None, [MeasureDependency(id_provider=cross_stored_direction_for_fx_vol, query_type=QueryType.IMPLIED_VOLATILITY)], asset_type_excluded=(AssetType.CommodityNaturalGasHub,), ) def implied_volatility( asset: Asset, tenor: str, strike_reference: VolReference = None, relative_strike: Real = None, parallelize_queries: bool = True, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Volatility of an asset implied by observations of market prices. :param asset: asset object loaded from security master :param tenor: relative date representation of expiration date e.g. 1m or absolute calendar strips e.g. 'Cal20', 'F20-G20' :param strike_reference: reference for strike level. Forward is used for ATMF. Default market convention for Equity implied vols is forward :param relative_strike: strike relative to reference :param parallelize_queries: send parallel queries to the measures API chunked over an interval of a year based on the date context :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: implied volatility curve """ if asset.asset_class == AssetClass.Commod and asset.get_type() not in (SecAssetType.ETF, SecAssetType.STOCK): return _weighted_average_valuation_curve_for_calendar_strip( asset, tenor, QueryType.IMPLIED_VOLATILITY, "impliedVolatility" ) if asset.asset_class == AssetClass.FX: asset_id = cross_stored_direction_for_fx_vol(asset.get_marquee_id()) ref_string, relative_strike = _preprocess_implied_vol_strikes_fx(strike_reference, relative_strike) else: asset_id = asset.get_marquee_id() ref_string, relative_strike = preprocess_implied_vol_strikes_eq(strike_reference, relative_strike) log_debug( request_id, _logger, 'where tenor=%s, strikeReference=%s, relativeStrike=%s', tenor, ref_string, relative_strike ) tenor = _tenor_month_to_year(tenor) where = dict(tenor=tenor, strikeReference=ref_string, relativeStrike=relative_strike) # Parallel calls when fetching / appending last results df = get_historical_and_last_for_measure( [asset_id], QueryType.IMPLIED_VOLATILITY, where, source=source, real_time=real_time, request_id=request_id, parallelize_queries=parallelize_queries, ) s = _extract_series_from_df(df, QueryType.IMPLIED_VOLATILITY) return s def _tenor_month_to_year(tenor: str): matched = re.fullmatch('(\\d+)m', tenor) if matched: month = int(matched[1]) if month % 12 == 0: return str(int(month / 12)) + 'y' return tenor @plot_measure( (AssetClass.Commod,), (AssetType.CommodityNaturalGasHub,), [QueryType.IMPLIED_VOLATILITY], display_name='implied_volatility', ) def implied_volatility_ng( asset: Asset, contract_range: str = 'F20', price_method: str = 'GDD', *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Volatility of an asset implied by observations of market prices. :param asset: asset object loaded from security master :param price_method: price method - GDD/FERC/Exchange for US NG assets: Default value = GDD :param contract_range: e.g. inputs - 'Cal20', 'F20-G20', '2Q20', '2H20', 'Cal20-Cal21': Default Value = F20 :param source: name of function caller: default source = None :param real_time: whether to retrieve intraday data instead of EOD: default value = False :param request_id: service request id, if any :return: implied volatility curve """ if real_time: raise MqValueError('Use daily frequency instead of intraday') (start_contract_range, end_contract_range) = _get_start_and_end_dates(contract_range=contract_range) dates_contract_range = get_contract_range(start_contract_range, end_contract_range, None) weights = dates_contract_range.groupby('contract_month').size() weights = pd.DataFrame({'contract': weights.index, 'weight': weights.values}) start, end = DataContext.current.start_date, DataContext.current.end_date contracts_to_query = weights['contract'].unique().tolist() where = dict(contract=contracts_to_query, priceMethod=price_method) with DataContext(start, end): q = GsDataApi.build_market_data_query( [asset.get_marquee_id()], QueryType.IMPLIED_VOLATILITY, where=where, source=source, real_time=False ) data = _market_data_timed(q, request_id) dataset_ids = getattr(data, 'dataset_ids', ()) if data.empty: result = ExtendedSeries(dtype='float64') else: data['dates'] = data.index.date keys = ['contract', 'dates'] result = _merge_curves_by_weighted_average(data, weights, keys, "impliedVolatility") result.dataset_ids = dataset_ids return result def _preprocess_implied_vol_strikes_fx(strike_reference: VolReference = None, relative_strike: Real = None): if relative_strike is None and strike_reference != VolReference.DELTA_NEUTRAL: raise MqValueError('Relative strike must be provided if your strike reference is not delta_neutral') if strike_reference in (VolReference.FORWARD, VolReference.SPOT) and relative_strike != 100: raise MqValueError('Relative strike must be 100 for Spot or Forward strike reference') if strike_reference not in VolReference or strike_reference == VolReference.NORMALIZED: raise MqValueError('strikeReference: ' + strike_reference.value + ' not supported for FX') if strike_reference == VolReference.DELTA_NEUTRAL: if relative_strike is None: relative_strike = 0 elif relative_strike != 0: raise MqValueError('relative_strike must be 0 for delta_neutral') if strike_reference == VolReference.DELTA_PUT: relative_strike *= -1 ref_string = ( "delta" if strike_reference in (VolReference.DELTA_CALL, VolReference.DELTA_PUT, VolReference.DELTA_NEUTRAL) else strike_reference.value ) return ref_string, relative_strike def _check_top_n(top_n): if top_n is None: return try: float(top_n) except (ValueError, TypeError): raise MqValueError(f'top_n should be a number, not a {type(top_n).__name__}') @plot_measure( (AssetClass.Equity,), ( AssetType.Index, AssetType.ETF, ), [QueryType.IMPLIED_CORRELATION], ) def implied_correlation( asset: Asset, tenor: str, strike_reference: EdrDataReference, relative_strike: Real, top_n_of_index: Optional[int] = None, composition_date: Optional[GENERIC_DATE] = None, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Correlation of an asset implied by observations of market prices. :param asset: asset object loaded from security master :param tenor: relative date representation of expiration date e.g. 1m :param strike_reference: reference for strike level :param relative_strike: strike relative to reference :param top_n_of_index: the number of top constituents to take into account :param composition_date: YYYY-MM-DD or relative days before today e.g. 1d, 1m, 1y; defaults to most recent date available :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: implied correlation curve """ if real_time: raise NotImplementedError('realtime implied_correlation not implemented') if top_n_of_index is None and composition_date is not None: raise MqValueError('specify top_n_of_index to get the implied correlation of top constituents') _check_top_n(top_n_of_index) if top_n_of_index is not None and top_n_of_index > 100: raise MqValueError('maximum number of constituents exceeded while using top_n_of_index') if strike_reference == EdrDataReference.DELTA_PUT: relative_strike = abs(100 - relative_strike) relative_strike = relative_strike / 100 delta_types = (EdrDataReference.DELTA_CALL, EdrDataReference.DELTA_PUT) strike_ref = "delta" if strike_reference in delta_types else strike_reference.value log_debug( request_id, _logger, 'where tenor=%s, strikeReference=%s, relativeStrike=%s', tenor, strike_ref, relative_strike ) mqid = asset.get_marquee_id() where = dict(tenor=tenor, strikeReference=strike_ref, relativeStrike=relative_strike) if top_n_of_index is None: q = GsDataApi.build_market_data_query( [mqid], QueryType.IMPLIED_CORRELATION, where=where, source=source, real_time=real_time ) log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, QueryType.IMPLIED_CORRELATION) # results for top n constituents = _get_index_constituent_weights(asset, top_n_of_index, composition_date) asset_ids = constituents.index.to_list() + [mqid] df = get_historical_and_last_for_measure( asset_ids=asset_ids, query_type=QueryType.IMPLIED_VOLATILITY, where=where, source=source, real_time=real_time, request_id=request_id, ) if df.empty: return pd.Series(dtype=float) dataset_ids = getattr(df, 'dataset_ids', ()) df = df[['assetId', 'impliedVolatility']] constituents_weights = pd.DataFrame(constituents.to_dict()['netWeight'], index=df.index.unique()) series = _calculate_implied_correlation(mqid, df, constituents_weights, request_id) series.dataset_ids = dataset_ids return series def _calculate_implied_correlation(index_mqid, vol_df, constituents_weights, request_id): full_index = pd.date_range(start=vol_df.index.min(), end=vol_df.index.max(), freq='D') w = constituents_weights.reindex(full_index, method='ffill') all_groups = [] for name, group in vol_df.groupby('assetId'): before = group.shape[0] group = group[~group.index.duplicated(keep='first')] after = group.shape[0] if before > after: log_debug(request_id, _logger, 'removed %d duplicates for %s', before - after, name) g = group.reindex(full_index) g['assetId'] = name g['impliedVolatility'] = g['impliedVolatility'].interpolate() all_groups.append(g) df = pd.concat(all_groups) df['impliedVolatility'] /= 100 match = df['assetId'] == index_mqid df_asset = df.loc[match] df_rest = df.loc[~match] def calculate_vol(group): assets, vols = group['assetId'], group['impliedVolatility'] weights_on_date = w.loc[group.name].to_dict() weights = np.empty(len(vols.index)) for i, asset_id in enumerate(assets): weights[i] = weights_on_date[asset_id] total_weight = weights.sum() parts = np.array([vol * weight / total_weight for vol, weight in zip(vols, weights)]) return pd.Series([parts.sum(), np.power(parts, 2).sum()], index=['first', 'second']) inter = df_rest.groupby(df_rest.index).apply(calculate_vol) values = (pow(df_asset['impliedVolatility'], 2) - inter['second']) / (pow(inter['first'], 2) - inter['second']) series = ExtendedSeries(values * 100) return series @plot_measure( (AssetClass.Equity,), ( AssetType.Index, AssetType.ETF, ), [QueryType.IMPLIED_CORRELATION], ) def implied_correlation_with_basket( asset: Asset, tenor: str, strike_reference: EdrDataReference, relative_strike: Real, basket: Basket, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Implied correlation between index and stock basket. :param asset: asset object loaded from security master :param tenor: relative date representation of expiration date e.g. 1m :param strike_reference: reference for strike level :param relative_strike: strike relative to reference :param basket: a basket of stocks. Basket rebalancing does not apply to the calculation :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: implied correlation curve """ if real_time: raise NotImplementedError('realtime implied_correlation not implemented') if strike_reference == EdrDataReference.DELTA_PUT: relative_strike = abs(100 - relative_strike) relative_strike = relative_strike / 100 delta_types = (EdrDataReference.DELTA_CALL, EdrDataReference.DELTA_PUT) strike_ref = "delta" if strike_reference in delta_types else strike_reference.value log_debug( request_id, _logger, 'where tenor=%s, strikeReference=%s, relativeStrike=%s', tenor, strike_ref, relative_strike ) index_mqid = asset.get_marquee_id() where = dict(tenor=tenor, strikeReference=strike_ref, relativeStrike=relative_strike) query = GsDataApi.build_market_data_query( basket.get_marquee_ids() + [index_mqid], QueryType.IMPLIED_VOLATILITY, where=where, source=source, real_time=real_time, ) log_debug(request_id, _logger, 'q %s', query) df = _market_data_timed(query, request_id) dataset_ids = getattr(df, 'dataset_ids', ()) df = df[['assetId', 'impliedVolatility']] actual_weights = basket.get_actual_weights(request_id=request_id) series = _calculate_implied_correlation(index_mqid, df, actual_weights, request_id) series.dataset_ids = dataset_ids return series @plot_measure( (AssetClass.Equity,), ( AssetType.Index, AssetType.ETF, ), [QueryType.IMPLIED_CORRELATION], ) def realized_correlation_with_basket( asset: Asset, tenor: str, basket: Basket, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Realized correlation between index and stock basket. :param asset: asset object loaded from security master :param tenor: relative date representation of expiration date e.g. 1m :param basket: a basket of stocks. Basket rebalancing does not apply to the calculation :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: realized correlation curve """ if real_time: raise NotImplementedError('realtime implied_correlation not implemented') dataset_ids = set() index_mqid = asset.get_marquee_id() query = GsDataApi.build_market_data_query([index_mqid], QueryType.SPOT, source=source, real_time=real_time) log_debug(request_id, _logger, 'q %s', query) index_spot = _market_data_timed(query, request_id) dataset_ids.update(getattr(index_spot, 'dataset_ids', ())) actual_weights = basket.get_actual_weights(request_id=request_id) constituents_spot = basket.get_spot_data(request_id=request_id) dataset_ids.update(getattr(constituents_spot, 'dataset_ids', ())) vols = constituents_spot.apply(lambda col: volatility(constituents_spot[col.name], Window(tenor, tenor)) / 100) weighted_vols = actual_weights.mul(vols) idx_vol = volatility(index_spot['spot'], Window(tenor, tenor)) / 100 s1 = weighted_vols.sum(axis=1, skipna=False) s2 = weighted_vols.apply(lambda x: x * x).sum(axis=1, skipna=False) values = (idx_vol * idx_vol - s2) / (s1 * s1 - s2) * 100 series = ExtendedSeries(values) series.dataset_ids = dataset_ids return series @plot_measure( (AssetClass.Equity,), ( AssetType.Index, AssetType.ETF, ), [QueryType.AVERAGE_IMPLIED_VOLATILITY, QueryType.IMPLIED_VOLATILITY], ) def average_implied_volatility( asset: Asset, tenor: str, strike_reference: EdrDataReference, relative_strike: Real, top_n_of_index: Optional[int] = None, composition_date: Optional[GENERIC_DATE] = None, weight_threshold: Optional[Real] = None, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Historical weighted average implied volatility of the top constituents of an equity index. If top_n_of_index and composition_date are not provided, the average is of all constituents based on the weights at each evaluation date. If implied volatility is missing for a constituent, that is ignored. The average result is normalized by dividing by the sum of weights of the constituents that have implied volatility data. :param asset: asset object loaded from security master :param tenor: relative date representation of expiration date e.g. 1m :param strike_reference: reference for strike level :param relative_strike: strike relative to reference :param top_n_of_index: the number of top constituents to take into account :param composition_date: YYYY-MM-DD or relative days before today e.g. 1d, 1m, 1y; defaults to the most recent date available :param weight_threshold threshold of total constituent weights to drop if data is missing and top_n_of_index is passed in :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: average implied volatility curve """ if real_time: raise NotImplementedError('realtime average_implied_volatility not implemented') if top_n_of_index is None and composition_date is not None: raise MqValueError('Specify top_n_of_index to get the average implied volatility of top constituents') _check_top_n(top_n_of_index) if top_n_of_index is not None and top_n_of_index > 100: raise NotImplementedError( 'Maximum number of constituents exceeded. Do not use top_n_of_index to calculate on the full list' ) if top_n_of_index is not None: constituents = _get_index_constituent_weights(asset, top_n_of_index, composition_date) ref_string, relative_strike = preprocess_implied_vol_strikes_eq( VolReference(strike_reference.value), relative_strike ) tenor = _tenor_month_to_year(tenor) log_debug( request_id, _logger, 'where tenor=%s, strikeReference=%s, relativeStrike=%s', tenor, ref_string, relative_strike, ) where = dict(tenor=tenor, strikeReference=ref_string, relativeStrike=relative_strike) asset_ids = constituents.index.to_list() df = get_historical_and_last_for_measure( asset_ids=asset_ids, query_type=QueryType.IMPLIED_VOLATILITY, where=where, source=source, real_time=real_time, request_id=request_id, ignore_errors=True, ) if not df.empty: asset_to_weight = constituents['netWeight'].to_dict() missing_assets = set(asset_ids).difference(set(df['assetId'].unique())) if len(missing_assets) and not weight_threshold: msg = '' for asset in missing_assets: msg += f'{asset} ({asset_to_weight[asset]:.2f})' raise MqValueError( f'Unable to calculate average_implied_volatility due to missing implied vols for assets {msg}. ' f'Try adding a weight_threshold to skip these assets.' ) if weight_threshold: total_missing_weight = sum(asset_to_weight[missing_asset] for missing_asset in missing_assets) if total_missing_weight > weight_threshold: msg = '' for asset in missing_assets: msg += f'{asset} ({asset_to_weight[asset]:.2f})' raise MqValueError( f'Unable to calculate average_implied_volatility due to missing implied vols for assets {msg}. ' f'Try increasing the weight_threshold to skip these assets.' ) constituents.drop(index=list(missing_assets)) def calculate_avg_vol(group, weights): assets, vols = group['assetId'], group['impliedVolatility'] asset_weights = [weights[asset_id] for asset_id in assets] total_weight = sum(asset_weights) return sum([vol * weight / total_weight for vol, weight in zip(vols, asset_weights)]) weights = constituents.to_dict()['netWeight'] average_vol = df.groupby(df.index).apply(calculate_avg_vol, weights) series = ExtendedSeries(average_vol, name='averageImpliedVolatility') series.dataset_ids = getattr(df, 'dataset_ids', ()) return series if strike_reference == EdrDataReference.DELTA_PUT: relative_strike = abs(100 - relative_strike) relative_strike = relative_strike / 100 delta_types = (EdrDataReference.DELTA_CALL, EdrDataReference.DELTA_PUT) strike_ref = "delta" if strike_reference in delta_types else strike_reference.value _logger.debug('where tenor=%s, strikeReference=%s, relativeStrike=%s', tenor, strike_ref, relative_strike) mqid = asset.get_marquee_id() where = dict(tenor=tenor, strikeReference=strike_ref, relativeStrike=relative_strike) # no append last here, since there is no real-time avg implied vol dataset q = GsDataApi.build_market_data_query( [mqid], QueryType.AVERAGE_IMPLIED_VOLATILITY, where=where, source=source, real_time=real_time ) _logger.debug('q %s', q) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, QueryType.AVERAGE_IMPLIED_VOLATILITY) @plot_measure( (AssetClass.Equity,), ( AssetType.Index, AssetType.ETF, ), [QueryType.AVERAGE_IMPLIED_VARIANCE], ) def average_implied_variance( asset: Asset, tenor: str, strike_reference: EdrDataReference, relative_strike: Real, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Historical weighted average implied variance for the underlying assets of an equity index. :param asset: asset object loaded from security master :param tenor: relative date representation of expiration date e.g. 1m :param strike_reference: reference for strike level :param relative_strike: strike relative to reference :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: average implied variance curve """ if real_time: raise NotImplementedError('realtime average_implied_variance not implemented') if strike_reference == EdrDataReference.DELTA_PUT: relative_strike = abs(100 - relative_strike) relative_strike = relative_strike / 100 delta_types = (EdrDataReference.DELTA_CALL, EdrDataReference.DELTA_PUT) strike_ref = "delta" if strike_reference in delta_types else strike_reference.value log_debug( request_id, _logger, 'where tenor=%s, strikeReference=%s, relativeStrike=%s', tenor, strike_ref, relative_strike ) mqid = asset.get_marquee_id() where = dict(tenor=tenor, strikeReference=strike_ref, relativeStrike=relative_strike) q = GsDataApi.build_market_data_query( [mqid], QueryType.AVERAGE_IMPLIED_VARIANCE, where=where, source=source, real_time=real_time ) log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, QueryType.AVERAGE_IMPLIED_VARIANCE) @plot_measure( (AssetClass.Equity,), ( AssetType.Index, AssetType.ETF, ), [QueryType.AVERAGE_REALIZED_VOLATILITY, QueryType.SPOT], ) def average_realized_volatility( asset: Asset, tenor: str, returns_type: Returns = Returns.LOGARITHMIC, top_n_of_index: int = None, composition_date: Optional[GENERIC_DATE] = None, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Historical weighted average realized volatility for the underlying assets of an equity index. If top_n_of_index and composition_date are not provided, the average is of all constituents based on the weights at each evaluation date :param asset: asset object loaded from security master :param tenor: relative date representation of expiration date e.g. 1m :param returns_type: returns type (default logarithmic) :param top_n_of_index: the number of top constituents to take into account (must be specified when returns_type is not simple) :param composition_date: YYYY-MM-DD or relative days before today e.g. 1d, 1m, 1y; defaults to the most recent date available :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: average realized volatility curve """ if real_time: raise NotImplementedError('realtime average_realized_volatility not implemented') if top_n_of_index is None and composition_date is not None: raise MqValueError('Specify top_n_of_index to get the average realized volatility of top constituents') if top_n_of_index is None and returns_type is not Returns.LOGARITHMIC: raise MqValueError(f'top_n_of_index argument must be specified when using returns type {returns_type.value}') _check_top_n(top_n_of_index) if top_n_of_index is not None and top_n_of_index > 200: raise MqValueError('Maximum number of 200 constituents exceeded') if top_n_of_index is not None: constituents = _get_index_constituent_weights(asset, top_n_of_index, composition_date) assets = constituents.index.to_list() q = GsDataApi.build_market_data_query(assets, QueryType.SPOT, source=source, real_time=real_time) log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) if not real_time and DataContext.current.end_date >= dt.date.today(): df = append_last_for_measure(df, assets, QueryType.SPOT, None, source=source, request_id=request_id) grouped = df.groupby('assetId') weighted_vols = [] for underlying_id, weight in zip(constituents.index, constituents['netWeight']): filtered = grouped.get_group(underlying_id) if underlying_id in grouped.indices else pd.DataFrame() filtered = filtered.loc[~filtered.index.duplicated(keep='last')] vol = ( ExtendedSeries(dtype=float) if filtered.empty else ExtendedSeries(volatility(filtered['spot'], Window(tenor, tenor), returns_type)) ) weighted_vols.append(vol * weight) vol_df = pd.concat(weighted_vols, axis=1).ffill() series = ( ExtendedSeries(vol_df.sum(axis=1, min_count=top_n_of_index), name='averageRealizedVolatility') if len(weighted_vols) else ExtendedSeries(dtype=float) ) series.dataset_ids = getattr(df, 'dataset_ids', ()) return series q = GsDataApi.build_market_data_query( [asset.get_marquee_id()], QueryType.AVERAGE_REALIZED_VOLATILITY, where={'tenor': tenor}, source=source, real_time=real_time, ) log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, QueryType.AVERAGE_REALIZED_VOLATILITY) def _get_index_constituent_weights( asset: Asset, top_n_of_index: Optional[int] = None, composition_date: Optional[GENERIC_DATE] = None ): start, end = _range_from_pricing_date(asset.exchange, composition_date, buffer=1) mqid = asset.get_marquee_id() positions_data = GsIndexApi.get_positions_data(mqid, start, end, ['netWeight']) if not len(positions_data): raise MqValueError('Unable to get constituents of {} between {} and {}'.format(mqid, start, end)) constituents = pd.DataFrame(positions_data)[['underlyingAssetId', 'netWeight', 'positionDate']] constituents = constituents.set_index('positionDate') latest = constituents.index.max() _logger.info('selected composition date %s', latest) constituents = constituents.loc[latest] constituents = constituents.sort_values(by=['netWeight'], ascending=False) constituents = constituents[:top_n_of_index] if top_n_of_index is not None else constituents total_weight = constituents['netWeight'].sum() constituents['netWeight'] = constituents['netWeight'] / total_weight constituents = constituents.set_index('underlyingAssetId') return constituents @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [MeasureDependency(id_provider=currency_to_default_benchmark_rate, query_type=QueryType.CAP_FLOOR_VOL)], ) def cap_floor_vol( asset: Asset, expiration_tenor: str, relative_strike: float, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ GS end-of-day implied normal volatility for cap and floor vol matrices. :param asset: asset object loaded from security master :param expiration_tenor: relative date representation of expiration date on the option e.g. 3m :param relative_strike: strike level relative to at the money e.g. 10 :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: cap and floor implied normal volatility curve """ if real_time: raise NotImplementedError('realtime cap_floor_vol not implemented') rate_benchmark_mqid = convert_asset_for_rates_data_set(asset, RatesConversionType.DEFAULT_BENCHMARK_RATE) _logger.debug('where expiry=%s, strike=%s', expiration_tenor, relative_strike) q = GsDataApi.build_market_data_query( [rate_benchmark_mqid], QueryType.CAP_FLOOR_VOL, where=dict(expiry=expiration_tenor, strike=relative_strike), source=source, real_time=real_time, ) log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, QueryType.CAP_FLOOR_VOL) @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [MeasureDependency(id_provider=currency_to_default_benchmark_rate, query_type=QueryType.CAP_FLOOR_ATM_FWD_RATE)], ) def cap_floor_atm_fwd_rate( asset: Asset, expiration_tenor: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ GS end-of-day at-the-money forward rate for cap and floor matrices. :param asset: asset object loaded from security master :param expiration_tenor: relative date representation of expiration date on the option e.g. 3m :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: cap and floor atm forward rate curve """ if real_time: raise NotImplementedError('realtime cap_floor_atm_fwd_rate not implemented') q = GsDataApi.build_market_data_query( [convert_asset_for_rates_data_set(asset, RatesConversionType.DEFAULT_BENCHMARK_RATE)], QueryType.CAP_FLOOR_ATM_FWD_RATE, where=dict(expiry=expiration_tenor, strike=0), source=source, real_time=real_time, ) log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, QueryType.CAP_FLOOR_ATM_FWD_RATE) @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [MeasureDependency(id_provider=currency_to_default_benchmark_rate, query_type=QueryType.SPREAD_OPTION_VOL)], ) def spread_option_vol( asset: Asset, expiration_tenor: str, long_tenor: str, short_tenor: str, relative_strike: float, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ GS end-of-day implied normal volatility for spread option vol matrices. :param asset: asset object loaded from security master :param expiration_tenor: relative date representation of expiration date on the option e.g. 3m :param long_tenor: relative date representation of the instrument's tenor date e.g. 1y :param short_tenor: relative date representation of the instrument's tenor date e.g. 1y :param relative_strike: strike level relative to at the money e.g. 10 :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: spread option implied normal volatility curve """ if real_time: raise NotImplementedError('realtime spread_option_vol not implemented') rate_benchmark_mqid = convert_asset_for_rates_data_set(asset, RatesConversionType.DEFAULT_BENCHMARK_RATE) _logger.debug( 'where expiry=%s, longTenor=%s, shortTenor=%s, strike=%s', expiration_tenor, long_tenor, short_tenor, relative_strike, ) q = GsDataApi.build_market_data_query( [rate_benchmark_mqid], QueryType.SPREAD_OPTION_VOL, where=dict(expiry=expiration_tenor, longTenor=long_tenor, shortTenor=short_tenor, strike=relative_strike), source=source, real_time=real_time, ) log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, QueryType.SPREAD_OPTION_VOL) @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [ MeasureDependency( id_provider=currency_to_default_benchmark_rate, query_type=QueryType.SPREAD_OPTION_ATM_FWD_RATE ) ], ) def spread_option_atm_fwd_rate( asset: Asset, expiration_tenor: str, long_tenor: str, short_tenor: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ GS end-of-day At-the-money forward rate for spread option vol matrices. :param asset: asset object loaded from security master :param expiration_tenor: relative date representation of expiration date on the option e.g. 3m :param long_tenor: relative date representation of the instrument's tenor date e.g. 1y :param short_tenor: relative date representation of the instrument's tenor date e.g. 1y :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: spread option at-the-money forward rate curve """ if real_time: raise NotImplementedError('realtime spread_option_atm_fwd_rate not implemented') q = GsDataApi.build_market_data_query( [convert_asset_for_rates_data_set(asset, RatesConversionType.DEFAULT_BENCHMARK_RATE)], QueryType.SPREAD_OPTION_ATM_FWD_RATE, where=dict(expiry=expiration_tenor, longTenor=long_tenor, shortTenor=short_tenor, strike=0), source=source, real_time=real_time, ) log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, QueryType.SPREAD_OPTION_ATM_FWD_RATE) @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [MeasureDependency(id_provider=currency_to_inflation_benchmark_rate, query_type=QueryType.INFLATION_SWAP_RATE)], ) def zc_inflation_swap_rate( asset: Asset, termination_tenor: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ GS end-of-day zero coupon inflation swap break-even rate. :param asset: asset object loaded from security master :param termination_tenor: relative date representation of the instrument's expiration date e.g. 1y :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: zero coupon inflation swap break-even rate curve """ if real_time: raise NotImplementedError('realtime zc_inflation_swap_rate not implemented') infl_rate_benchmark_mqid = convert_asset_for_rates_data_set(asset, RatesConversionType.INFLATION_BENCHMARK_RATE) _logger.debug('where tenor=%s', termination_tenor) q = GsDataApi.build_market_data_query( [infl_rate_benchmark_mqid], QueryType.INFLATION_SWAP_RATE, where=dict(tenor=termination_tenor), source=source, real_time=real_time, ) log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, QueryType.INFLATION_SWAP_RATE) @plot_measure( (AssetClass.FX,), (AssetType.Cross,), [MeasureDependency(id_provider=cross_to_basis, query_type=QueryType.BASIS)] ) def basis( asset: Asset, termination_tenor: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ GS end-of-day cross-currency basis swap spread. :param asset: asset object loaded from security master :param termination_tenor: relative date representation of the instrument's expiration date e.g. 1y :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: cross-currency basis swap spread curve """ if real_time: raise NotImplementedError('realtime basis not implemented') basis_mqid = convert_asset_for_rates_data_set(asset, RatesConversionType.CROSS_CURRENCY_BASIS) _logger.debug('where tenor=%s', termination_tenor) q = GsDataApi.build_market_data_query( [basis_mqid], QueryType.BASIS, where=dict(tenor=termination_tenor), source=source, real_time=real_time ) log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, QueryType.BASIS) @plot_measure( (AssetClass.FX,), (AssetType.Cross,), [MeasureDependency(id_provider=cross_to_usd_based_cross, query_type=QueryType.FX_FORECAST)], ) def fx_forecast( asset: Asset, relativePeriod: FxForecastHorizon = FxForecastHorizon.THREE_MONTH, relative_period: Optional[FxForecastHorizon] = None, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ FX forecasts made by Global Investment Research (GIR) macro analysts. :param asset: asset object loaded from security master :param relativePeriod: (deprecated) Forecast horizon. One of: 3m, 6m, 12m, EOY1, EOY2, EOY3, EOY4 :param relative_period: Forecast horizon. One of: 3m, 6m, 12m, EOY1, EOY2, EOY3, EOY4 :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: FX forecast curve """ if real_time: raise NotImplementedError('realtime fx_forecast not implemented') if relativePeriod: log_warning(request_id, _logger, "'relativePeriod' is deprecated, please use 'relative_period' instead.") cross_mqid = asset.get_marquee_id() usd_based_cross_mqid = cross_to_usd_based_cross(cross_mqid) query_type = QueryType.FX_FORECAST q = GsDataApi.build_market_data_query( [usd_based_cross_mqid], query_type, where=dict(relativePeriod=relative_period or relativePeriod), source=source, real_time=real_time, ) log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) series = _extract_series_from_df(df, query_type) if cross_mqid != usd_based_cross_mqid: series = 1 / series series.dataset_ids = getattr(df, 'dataset_ids', ()) return series @plot_measure( (AssetClass.FX,), (AssetType.Cross,), [MeasureDependency(id_provider=cross_to_usd_based_cross, query_type=QueryType.FX_FORECAST)], ) def fx_forecast_time_series( asset: Union[Asset, str], forecastFrequency: Union[str, _FxForecastTimeSeriesPeriodType] = _FxForecastTimeSeriesPeriodType.ANNUAL, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Short and long-term FX forecast time series. :param asset: Asset object or string representing the asset ID :param forecastFrequency: Forecast period type (e.g., Annual) :param source: Name of the function caller :param real_time: Whether to retrieve intraday data instead of EOD :param request_id: Service request ID, if any :return: Forecast time series for the Cross Asset """ if real_time: raise NotImplementedError('realtime fx_forecast_time_series not implemented') if isinstance(asset, str): cross_mqid = asset elif isinstance(asset, Asset): cross_mqid = asset.get_marquee_id() else: raise ValueError("Asset must be of type Asset or str") if isinstance(forecastFrequency, Enum): forecastFrequency = forecastFrequency.value usd_based_cross_mqid = cross_to_usd_based_cross(cross_mqid) query_type = QueryType.FX_FORECAST col_name = query_type.value.replace(' ', '') col_name = col_name[0].lower() + col_name[1:] q = GsDataApi.build_market_data_query([usd_based_cross_mqid], query_type, source=source, real_time=real_time) log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) df.index.name = 'date' df = df.reset_index().sort_values(by='date').groupby('relativePeriod')[['date', col_name]].last() df = df.reset_index() if forecastFrequency == _FxForecastTimeSeriesPeriodType.SHORT_TERM.value: df = df[df['relativePeriod'].isin(['3m', '6m', '12m'])].drop('date', axis=1) df['date'] = df['relativePeriod'].apply(lambda x: pd.Timestamp.today() + relativedelta(months=int(x[:-1]))) df = df[['date', col_name]].sort_values(by='date').set_index('date') elif forecastFrequency == _FxForecastTimeSeriesPeriodType.ANNUAL.value: df = df[df['relativePeriod'].str.startswith('EOY')].drop('date', axis=1) df['date'] = df['relativePeriod'].apply( lambda x: pd.Timestamp(f"{pd.Timestamp.today().year + int(x[3:])}-01-01") ) df = df[['date', col_name]].sort_values(by='date').set_index('date') else: raise ValueError("Invalid forecastFrequency. Must be one of '3/6/12-Month', 'Annual'.") series = _extract_series_from_df(df, query_type) if cross_mqid != usd_based_cross_mqid: series = 1 / series series.dataset_ids = getattr(df, 'dataset_ids', ()) return series @plot_measure((AssetClass.Equity, AssetClass.FX), None, [QueryType.IMPLIED_VOLATILITY]) def forward_vol( asset: Asset, tenor: str, forward_start_date: str, strike_reference: VolReference, relative_strike: Real, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Forward volatility. :param asset: asset object loaded from security master :param tenor: relative date representation of expiration date e.g. 1m :param forward_start_date: forward start date e.g. 2m, 1y :param strike_reference: reference for strike level :param relative_strike: strike relative to reference :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: forward volatility curve """ if real_time: raise NotImplementedError('real-time forward vol not implemented') if asset.asset_class == AssetClass.FX: sr_string, relative_strike = _preprocess_implied_vol_strikes_fx(strike_reference, relative_strike) asset_id = cross_stored_direction_for_fx_vol(asset) else: sr_string, relative_strike = preprocess_implied_vol_strikes_eq(strike_reference, relative_strike) asset_id = asset.get_marquee_id() t1_month = _tenor_to_month(forward_start_date) t2_month = _tenor_to_month(tenor) + t1_month t1 = _month_to_tenor(t1_month) t2 = _month_to_tenor(t2_month) log_debug( request_id, _logger, 'where strikeReference=%s, relativeStrike=%s, tenor=%s', sr_string, relative_strike, f'{t1},{t2}', ) where = dict(tenor=[t1, t2], strikeReference=[sr_string], relativeStrike=[relative_strike]) q = GsDataApi.build_market_data_query( [asset_id], QueryType.IMPLIED_VOLATILITY, where=where, source=source, real_time=real_time ) dataset_ids = [] log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) dataset_ids.extend(getattr(df, 'dataset_ids', ())) now = dt.date.today() if not real_time and DataContext.current.end_date >= now: where_list = _split_where_conditions(where) delta = dt.timedelta(days=1) with DataContext(now - delta, now + delta): net = {"queries": []} for wl in where_list: last_q = GsDataApi.build_market_data_query( [asset_id], QueryType.IMPLIED_VOLATILITY, where=wl, source=source, real_time=True, measure='Last' ) net["queries"].extend(last_q["queries"]) log_debug(request_id, _logger, 'last q %s', net) try: df_l = _market_data_timed(net, request_id) dataset_ids.extend(getattr(df_l, 'dataset_ids', ())) except Exception as e: _logger.warning('error loading Last of implied vol', exc_info=e) else: if df_l.empty: _logger.warning('no data for Last of implied vol') else: df_l.index = df_l.index.tz_convert(None).normalize() if df_l.index.max() > df.index.max(): df = pd.concat([df, df_l]) if df.empty: series = ExtendedSeries(dtype=float, name='forwardVol') else: grouped = df.groupby(Fields.TENOR.value) try: sg = grouped.get_group(t1)['impliedVolatility'] lg = grouped.get_group(t2)['impliedVolatility'] except KeyError: log_debug(request_id, _logger, 'no data for one or more tenors') series = ExtendedSeries(dtype=float, name='forwardVol') else: series = ExtendedSeries( sqrt((t2_month * lg**2 - t1_month * sg**2) / _tenor_to_month(tenor)), name='forwardVol' ) series.index = _normalize_dtidx(series.index) series.dataset_ids = tuple(dataset_ids) return series def _process_forward_vol_term(asset: Asset, vol_series: pd.Series, vol_col: str, series_name: str) -> pd.Series: if vol_series.empty: return ExtendedSeries(dtype=float, name=series_name) else: cbd = _get_custom_bd(asset.exchange) vol_df = pd.DataFrame(vol_series) latest = ( vol_series.attrs['latest'].date() if isinstance(vol_series.attrs['latest'], pd.Timestamp) else vol_series.attrs['latest'] ) vol_df['calTimeToExp'] = vol_df.apply(lambda row: (row.name.date() - latest).days / DAYS_IN_YEAR, axis=1) vol_df['timeToExp'] = vol_df.apply( lambda row: np.busday_count(latest, row.name.date(), weekmask=cbd.weekmask, holidays=cbd.holidays) / 252, axis=1, ) vol_df['multiplier'] = sqrt(vol_df['calTimeToExp'] / vol_df['timeToExp']) vol_df['fwdVol'] = sqrt( ( vol_df['timeToExp'] * (vol_df[vol_col] * vol_df['multiplier']) ** 2 - vol_df['timeToExp'].shift(1) * (vol_df[vol_col].shift(1) * vol_df['multiplier'].shift(1)) ** 2 ) / (vol_df['timeToExp'] - vol_df['timeToExp'].shift(1)) ) ext_series = ExtendedSeries(vol_df['fwdVol'], name=series_name)[ pd.Timestamp(DataContext.current.start_date) : pd.Timestamp(DataContext.current.end_date) ] ext_series.index = _normalize_dtidx(ext_series.index) ext_series.dataset_ids = getattr(vol_series, 'dataset_ids', ()) ext_series = ext_series.sort_index() return ext_series @plot_measure((AssetClass.Equity, AssetClass.FX), None, [QueryType.IMPLIED_VOLATILITY]) def forward_vol_term( asset: Asset, strike_reference: VolReference, relative_strike: Real, pricing_date: Optional[GENERIC_DATE] = None, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Forward volatility term structure. :param asset: asset object loaded from security master :param strike_reference: reference for strike level :param relative_strike: strike relative to reference :param pricing_date: YYYY-MM-DD or relative days before today e.g. 1d, 1m, 1y :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: forward volatility term structure """ vt = vol_term( asset=asset, strike_reference=strike_reference, relative_strike=relative_strike, pricing_date=pricing_date, source=source, real_time=real_time, request_id=request_id, ) series = _process_forward_vol_term(asset, vt, 'impliedVolatility', 'forwardVolTerm') return series def _get_skew_strikes(asset: Asset, strike_reference: SkewReference, distance: Real) -> Tuple[list, int]: """ Calculates strike references necessary for calculating skew. :param asset: asset object loaded from security master. :param strike_reference: strike reference type. :param distance: strike relative to reference. :return: list of relative strikes for computation, in specific order. """ if asset.asset_class == AssetClass.FX: buffer = 1 # FX vol data is loaded later if strike_reference == SkewReference.DELTA: q_strikes = [0 - distance, distance, 0] else: raise MqValueError('strike_reference has to be delta to get skew for FX options') else: assert asset.asset_class in (AssetClass.Equity, AssetClass.Commod) buffer = 0 if strike_reference == SkewReference.DELTA: b = 50 q_strikes = [100 - distance, distance, b] elif strike_reference == SkewReference.NORMALIZED: b = 0 q_strikes = [b - distance, b + distance, b] elif strike_reference: b = 100 q_strikes = [b - distance, b + distance, b] else: raise MqTypeError("strike_reference required for equities") if strike_reference != SkewReference.NORMALIZED: q_strikes = [x / 100 for x in q_strikes] return q_strikes, buffer def _skew( df: MarketDataResponseFrame, relative_strike_col, iv_col, q_strikes, normalization_mode: NormalizationMode ) -> ExtendedSeries: """ Calculates skew using the data in df and returns it as a series. :param df: Dataframe with a column of relative strikes, a column of implied volatility, and a datetime index. :param relative_strike_col: name of the column in df with relative strikes. :param iv_col: name of the column in df with implied volatilities. :param q_strikes: length-three array of floats returned by _get_skew_strikes :param normalization_mode: "normalized" to divide skew by ATM implied volatility or "outright" to leave off this normalization. By default, we normalize skew for equities and commodities but not for FX assets. """ curves = {k: v for k, v in df.groupby(relative_strike_col)} if len(curves) < 3: raise MqValueError('Skew not available for given inputs') series = [curves[qs][iv_col] for qs in q_strikes] ext_series = ( ((series[0] - series[1]) / series[2]) if normalization_mode == NormalizationMode.NORMALIZED else (series[0] - series[1]) ) series = ExtendedSeries(ext_series) series.index = _normalize_dtidx(series.index) return series def _skew_fetcher(asset_id, query_type, where, source, real_time, request_id=None, allow_exception=False): """ Helper function to get skew data. Called by the skew function and placed here to be able to be tested to ensure 100% test coverage. :param asset_id: Marquee asset ID :param query_type: QueryType to be sent to measures service :param where: dictionary containing query :param source: string source :param real_time: whether we want real time tdata :param request_id: for debugging purposes """ try: q = GsDataApi.build_market_data_query([asset_id], query_type, where=where, source=source, real_time=real_time) log_debug(request_id, _logger, 'q %s', q) return _market_data_timed(q, request_id) except MqValueError as m: if allow_exception: log_warning(request_id, _logger, "Ignoring expiry data fetching since there's no data for the givenasset") return MarketDataResponseFrame() raise m @plot_measure( (AssetClass.FX, AssetClass.Equity, AssetClass.Commod), None, [MeasureDependency(id_provider=cross_stored_direction_for_fx_vol, query_type=QueryType.IMPLIED_VOLATILITY)], asset_type_excluded=(AssetType.CommodityNaturalGasHub,), ) def skew_term( asset: Asset, strike_reference: SkewReference, distance: Real, pricing_date: Optional[GENERIC_DATE] = None, normalization_mode: NormalizationMode = None, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Skew term structure. Uses most recent date available if pricing_date is not provided. If pricing_date falls on a weekend or a holiday, uses the previous business day as the pricing date. :param asset: asset object loaded from security master :param strike_reference: reference for strike level :param distance: strike relative to reference :param pricing_date: YYYY-MM-DD or relative days before today e.g. 1d, 1m, 1y :param normalization_mode: "normalized" to divide skew by ATM implied volatility or "outright" to leave off this normalization. By default, we normalize skew for equities and commodities but not for FX assets. :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: volatility term structure """ # Validate params if real_time: raise NotImplementedError('realtime skew term not implemented') # TODO check_forward_looking(pricing_date, source, 'skew_term') if asset.asset_class == AssetClass.FX: asset_id = cross_stored_direction_for_fx_vol(asset) if normalization_mode is None: normalization_mode = NormalizationMode.OUTRIGHT else: asset_id = asset.get_marquee_id() if normalization_mode is None: normalization_mode = NormalizationMode.NORMALIZED q_strikes, buffer = _get_skew_strikes(asset, strike_reference, distance) start, end = _range_from_pricing_date(asset.exchange, pricing_date, buffer=buffer) df = df_expiry = pd.DataFrame() dataset_ids = set() where = {'strikeReference': strike_reference.value, 'relativeStrike': q_strikes} if asset.asset_class == AssetClass.Equity and (pricing_date is None or end >= dt.date.today()): # intraday data_requests = [ partial( _get_latest_term_structure_data, asset_id, QueryType.IMPLIED_VOLATILITY, where=where, groupby=['tenor', 'relativeStrike'], source=source, request_id=request_id, ), partial( _get_latest_term_structure_data, asset_id, QueryType.IMPLIED_VOLATILITY_BY_EXPIRATION, where=where, groupby=['expirationDate', 'relativeStrike'], source=source, request_id=request_id, ), ] df, df_expiry = ThreadPoolManager.run_async(data_requests) dataset_ids.update(getattr(df, 'dataset_ids', ())) dataset_ids.update(getattr(df_expiry, 'dataset_ids', ())) if df.empty and df_expiry.empty: # historical data_requests = [ partial( get_df_with_retries, partial(_skew_fetcher, asset_id, QueryType.IMPLIED_VOLATILITY, where, source, real_time, request_id), start_date=start, end_date=end, exchange=asset.exchange, ), partial( _skew_fetcher, asset_id, QueryType.IMPLIED_VOLATILITY_BY_EXPIRATION, where, source, real_time, request_id, allow_exception=True, ), ] df, df_expiry = ThreadPoolManager.run_async(data_requests) dataset_ids.update(getattr(df, 'dataset_ids', ())) # only if df_expiry not empty if not df_expiry.empty: dataset_ids.update(getattr(df_expiry, 'dataset_ids', ())) p_date = df.index.max() if not df.empty else pricing_date _logger.info('selected pricing date %s', p_date) # Handle tenor DF if df.empty: series = ExtendedSeries(dtype=float) else: df = df.loc[p_date] df.index = pd.DatetimeIndex(df.index) if not isinstance(df.index, pd.DatetimeIndex) else df.index df.index = pd.DatetimeIndex( [ RelativeDate(df['tenor'].iloc[i], df.index.date[i]).apply_rule(exchange=asset.exchange) for i in range(len(df)) ] ) series = _skew(df, 'relativeStrike', 'impliedVolatility', q_strikes, normalization_mode) # Add additional data from expiry DF if not df_expiry.empty: df_expiry = df_expiry.loc[p_date] df_expiry.index = df_expiry['expirationDate'] series_expiry = _skew( df_expiry, 'relativeStrike', 'impliedVolatilityByExpiration', q_strikes, normalization_mode ) series_expiry = series_expiry[~series_expiry.index.isin(series.index)] series = pd.concat([series, series_expiry]) series.name = "impliedVolatility" series = series.sort_index() series = ExtendedSeries( series.loc[pd.Timestamp(DataContext.current.start_date) : pd.Timestamp(DataContext.current.end_date)] ) series.dataset_ids = tuple(dataset_ids) return series @plot_measure( (AssetClass.Equity, AssetClass.Commod, AssetClass.FX), None, [QueryType.IMPLIED_VOLATILITY], asset_type_excluded=(AssetType.CommodityNaturalGasHub,), ) def vol_term( asset: Asset, strike_reference: VolReference, relative_strike: Real, pricing_date: Optional[GENERIC_DATE] = None, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Volatility term structure. Defaults to use the most recent date with live data if available. A specific pricing_date can also be provided. :param asset: asset object loaded from security master :param strike_reference: reference for strike level :param relative_strike: strike relative to reference :param pricing_date: YYYY-MM-DD or relative days before today e.g. 1d, 1m, 1y :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: volatility term structure """ if real_time: raise NotImplementedError('realtime forward term not implemented') # TODO check_forward_looking(pricing_date, source, 'vol_term') if asset.asset_class == AssetClass.FX: sr_string, relative_strike = _preprocess_implied_vol_strikes_fx(strike_reference, relative_strike) asset_id = cross_stored_direction_for_fx_vol(asset) buffer = 1 # FX vol data is loaded later else: sr_string, relative_strike = preprocess_implied_vol_strikes_eq(strike_reference, relative_strike) asset_id = asset.get_marquee_id() buffer = 0 start, end = _range_from_pricing_date(asset.exchange, pricing_date, buffer=buffer) _logger.debug('where strikeReference=%s, relativeStrike=%s', sr_string, relative_strike) where = dict(strikeReference=sr_string, relativeStrike=relative_strike) df = df_expiry = pd.DataFrame() dataset_ids = set() today = dt.date.today() if asset.asset_class == AssetClass.Equity and (pricing_date is None or end >= today): # use intraday data df = _get_latest_term_structure_data(asset_id, QueryType.IMPLIED_VOLATILITY, where, 'tenor', source, request_id) df_expiry = _get_latest_term_structure_data( asset_id, QueryType.IMPLIED_VOLATILITY_BY_EXPIRATION, where, 'expirationDate', source, request_id ) dataset_ids.update(getattr(df, 'dataset_ids', ())) dataset_ids.update(getattr(df_expiry, 'dataset_ids', ())) if df.empty and df_expiry.empty: def fetcher(query_type): q = GsDataApi.build_market_data_query( [asset_id], query_type, where=where, source=source, real_time=real_time ) log_debug(request_id, _logger, 'q %s', q) return _market_data_timed(q, request_id) df = get_df_with_retries( partial(fetcher, QueryType.IMPLIED_VOLATILITY), start_date=start, end_date=end, exchange=asset.exchange ) try: df_expiry = get_df_with_retries( partial(fetcher, QueryType.IMPLIED_VOLATILITY_BY_EXPIRATION), start_date=start, end_date=end, exchange=asset.exchange, ) except MqValueError: log_warning(request_id, _logger, "Ignoring expiry data fetching since there's no data for the given asset") pass # Allow to fail since it's not required to compute end result dataset_ids.update(getattr(df, 'dataset_ids', ())) # only if df_expiry not empty if not df_expiry.empty: dataset_ids.update(getattr(df_expiry, 'dataset_ids', ())) latest = df.index.union(df_expiry.index).max() if not df_expiry.empty else df.index.max() _logger.info('selected pricing date %s', latest) if df.empty: series = ExtendedSeries(dtype='float64') else: df = df.loc[latest] cbd = _get_custom_bd(asset.exchange) df = df.assign( expirationDate=pd.DatetimeIndex([ts + _to_offset(t) + cbd - cbd for ts, t in zip(df.index, df['tenor'])]) ) series = df.set_index('expirationDate')['impliedVolatility'] series.index = _normalize_dtidx(series.index) if df_expiry.empty: series_expiry = pd.Series(dtype='float64') else: df_expiry = df_expiry.loc[latest] series_expiry = df_expiry.set_index('expirationDate')['impliedVolatilityByExpiration'] series_expiry.index = _normalize_dtidx(series_expiry.index) extra = series_expiry[~series_expiry.index.isin(series.index)] if not extra.empty: series = pd.concat([series, extra]) series.name = "impliedVolatility" series = series.sort_index() series = series.loc[pd.Timestamp(DataContext.current.start_date) : pd.Timestamp(DataContext.current.end_date)] series = ExtendedSeries(series) series.attrs = dict(latest=latest) series.dataset_ids = tuple(dataset_ids) return series @plot_measure((AssetClass.Equity,), None, [QueryType.IMPLIED_VOLATILITY]) def vol_smile( asset: Asset, tenor: str, strike_reference: VolSmileReference, pricing_date: Optional[GENERIC_DATE] = None, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Volatility smile of an asset implied by observations of market prices. :param asset: asset object loaded from security master :param tenor: relative date representation of expiration date e.g. 1m :param strike_reference: reference for strike level :param pricing_date: YYYY-MM-DD or relative days before today e.g. 1d, 1m, 1y :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: implied volatility smile """ if real_time: raise NotImplementedError('realtime vol_smile not implemented') mqid = asset.get_marquee_id() start, end = _range_from_pricing_date(asset.exchange, pricing_date) def fetcher(): q = GsDataApi.build_market_data_query( [mqid], QueryType.IMPLIED_VOLATILITY, where=dict(tenor=tenor, strikeReference=strike_reference.value), source=source, real_time=real_time, ) log_debug(request_id, _logger, 'q %s', q) return _market_data_timed(q, request_id) df = get_df_with_retries(fetcher, start_date=start, end_date=end, exchange=asset.exchange) dataset_ids = getattr(df, 'dataset_ids', ()) if df.empty: series = ExtendedSeries(dtype=float) else: latest = df.index.max() _logger.info('selected pricing date %s', latest) df = df.loc[latest] vols = df['impliedVolatility'].values strikes = df['relativeStrike'].values series = ExtendedSeries(vols, index=strikes) series.dataset_ids = dataset_ids series.name = 'vol_smile' # used to indicate returned series has a float index return series def measure_request_safe(parent_fn_name, asset, fn, request_id, *args, **kwargs): try: return fn(*args, **kwargs) except MqValueError as e: error_msg = e.args[0] if len(e.args) else 'No error message provided.' log_debug(request_id, _logger, f'measure_request_safe: {error_msg}') raise MqValueError(f'{parent_fn_name} not available for {asset.name}.') @plot_measure((AssetClass.Equity, AssetClass.Commod), None, [QueryType.FORWARD]) def fwd_term( asset: Asset, pricing_date: Optional[GENERIC_DATE] = None, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Forward term structure. Uses most recent date available if pricing_date is not provided. If pricing_date falls on a weekend or holiday, uses the previous business day as pricing date. :param asset: asset object loaded from security master :param pricing_date: YYYY-MM-DD or relative days before today e.g. 1d, 1m, 1y (default today) :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: forward term structure """ if real_time: raise NotImplementedError('realtime forward term not implemented') # TODO asset_id = asset.get_marquee_id() query_type, where = QueryType.FORWARD, dict(strikeReference='forward', relativeStrike=1) forward_col_name = 'forward' check_forward_looking(pricing_date, source, 'fwd_term') cbd = _get_custom_bd(asset.exchange) start, end = _range_from_pricing_date(asset.exchange, pricing_date) start = start + cbd - cbd # get previous business day's pricing if start, end on a non-BD with DataContext(start, end): q = GsDataApi.build_market_data_query([asset_id], query_type, where=where, source=source, real_time=real_time) log_debug(request_id, _logger, 'q %s', q) df = measure_request_safe('fwd_term', asset, _market_data_timed, request_id, q, request_id) dataset_ids = getattr(df, 'dataset_ids', ()) if df.empty: series = ExtendedSeries(dtype=float) else: latest = df.index.max() _logger.info('selected pricing date %s', latest) df = df.loc[latest] df.loc[:, 'expirationDate'] = pd.DatetimeIndex( [ts + _to_offset(t) + cbd - cbd for ts, t in zip(df.index, df['tenor'])] ) df = df.set_index('expirationDate') df.index = _normalize_dtidx(df.index) df = df.sort_index() df = df.loc[pd.Timestamp(DataContext.current.start_date) : pd.Timestamp(DataContext.current.end_date)] series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(df[forward_col_name]) series.dataset_ids = dataset_ids return series @plot_measure((AssetClass.FX,), None, [QueryType.SPOT, QueryType.FORWARD_POINT], display_name='fwd_term') def fx_fwd_term( asset: Asset, pricing_date: Optional[GENERIC_DATE] = None, fwd_type: FXForwardType = FXForwardType.OUTRIGHT, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Forward term structure. Uses most recent date available if pricing_date is not provided. If pricing_date falls on a weekend or holiday, uses the previous business day as pricing date. :param asset: asset object loaded from security master :param pricing_date: YYYY-MM-DD or relative days before today e.g. 1d, 1m, 1y (default today) :param fwd_type: applicable only to FX forward term. Either "points" for forward points or "outright" (default outright) :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: forward term structure """ if real_time: raise NotImplementedError('realtime forward term not implemented') # TODO assert asset.asset_class == AssetClass.FX asset_id = cross_stored_direction_for_fx_vol(asset) forward_col_name = 'forwardPoint' query_type, where = QueryType.FORWARD_POINT, {} get_spot = fwd_type == FXForwardType.OUTRIGHT check_forward_looking(pricing_date, source, 'fwd_term') cbd = _get_custom_bd(asset.exchange) start, end = _range_from_pricing_date(asset.exchange, pricing_date) start = start + cbd - cbd # get previous business day's pricing if start, end on a non-BD dataset_ids = set() with DataContext(start, end): q = GsDataApi.build_market_data_query([asset_id], query_type, where=where, source=source, real_time=real_time) if get_spot: # Add in spot data for FX outright mode q_spot = GsDataApi.build_market_data_query( [asset_id], QueryType.SPOT, where={}, source=source, real_time=real_time ) data_requests = [ partial(_market_data_timed, q, request_id), partial(_market_data_timed, q_spot, request_id), ] df, spot_df = measure_request_safe( 'fwd_term', asset, ThreadPoolManager.run_async, request_id, data_requests ) dataset_ids.update(getattr(df, 'dataset_ids', ()) + getattr(spot_df, 'dataset_ids', ())) else: log_debug(request_id, _logger, 'q %s', q) df = measure_request_safe('fwd_term', asset, _market_data_timed, request_id, q, request_id) dataset_ids.update(getattr(df, 'dataset_ids', ())) dataset_ids = getattr(df, 'dataset_ids', ()) if df.empty: series = ExtendedSeries(dtype=float) else: latest = df.index.max() _logger.info('selected pricing date %s', latest) df = df.loc[latest] df.loc[:, 'expirationDate'] = pd.DatetimeIndex( [ts + _to_offset(t) + cbd - cbd for ts, t in zip(df.index, df['tenor'])] ) df = df.set_index('expirationDate') df.index = _normalize_dtidx(df.index) df = df.sort_index() df = df.loc[pd.Timestamp(DataContext.current.start_date) : pd.Timestamp(DataContext.current.end_date)] if get_spot: spot = spot_df.loc[latest]['spot'] df[forward_col_name] = df[forward_col_name] + spot series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(df[forward_col_name]) series.dataset_ids = tuple(dataset_ids) return series @plot_measure((AssetClass.FX,), None, [QueryType.FORWARD_POINT, QueryType.SPOT]) def carry_term( asset: Asset, pricing_date: Optional[GENERIC_DATE] = None, annualized: FXSpotCarry = FXSpotCarry.ANNUALIZED, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Carry term structure. Uses most recent date available if pricing_date is not provided. If pricing_date falls on a weekend or holiday, uses the previous business day as pricing date. Carry is FORWARD POINT / SPOT at pricing date for FX assets. :param asset: asset object loaded from security master :param pricing_date: YYYY-MM-DD or relative days before today e.g. 1d, 1m, 1y (default today) :param annualized: whether or not to annualize returned carry :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: forward term structure """ if real_time: raise NotImplementedError('realtime forward term not implemented') # TODO asset_id = cross_stored_direction_for_fx_vol(asset) forward_col_name = 'forwardPoint' check_forward_looking(pricing_date, source, 'fwd_term') cbd = _get_custom_bd(asset.exchange) start, end = _range_from_pricing_date(asset.exchange, pricing_date) start = start + cbd - cbd # get previous business day's pricing if start, end on a non-BD with DataContext(start, end): q = GsDataApi.build_market_data_query( [asset_id], QueryType.FORWARD_POINT, where={}, source=source, real_time=real_time ) # setting pricing location as NYC as we have forward points only for NYC close q_spot = GsDataApi.build_market_data_query( [asset_id], QueryType.SPOT, where={'pricingLocation': 'NYC'}, source=source, real_time=real_time ) data_requests = [partial(_market_data_timed, q, request_id), partial(_market_data_timed, q_spot, request_id)] df, spot_df = measure_request_safe('fwd_term', asset, ThreadPoolManager.run_async, request_id, data_requests) dataset_ids = set(getattr(df, 'dataset_ids', ()) + getattr(spot_df, 'dataset_ids', ())) if df.empty: series = ExtendedSeries(dtype=float) else: latest = df.index.max() _logger.info('selected pricing date %s', latest) df = df.loc[latest] df = df.assign( expirationDate=pd.DatetimeIndex([ts + _to_offset(t) + cbd - cbd for ts, t in zip(df.index, df['tenor'])]) ) df = df.set_index('expirationDate') df.index = _normalize_dtidx(df.index) df = df.sort_index() df = df.loc[pd.Timestamp(DataContext.current.start_date) : pd.Timestamp(DataContext.current.end_date)] spot = spot_df.loc[latest]['spot'] if annualized == FXSpotCarry.ANNUALIZED: df['carry'] = df[forward_col_name] * np.sqrt((df.index.to_series() - latest).dt.days / 252) / spot else: df['carry'] = df[forward_col_name] / spot series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(df['carry']) series.name = 'carry' series.dataset_ids = tuple(dataset_ids) return series @cachetools.func.ttl_cache() # fine as long as availability is not different between users def _var_swap_tenors(asset: Asset, request_id=None): from gs_quant.session import GsSession aid = asset.get_marquee_id() body = GsSession.current.sync.get(f"/data/markets/{aid}/availability") log_debug(request_id, _logger, 'Queried market availability (%s) for %s', body.get('requestId'), aid) for r in body['data']: if r['dataField'] == Fields.VAR_SWAP.value: for f in r['filteredFields']: if f['field'] == Fields.TENOR.value: return f['values'] raise MqValueError("var swap is not available for " + aid) @plot_measure((AssetClass.Equity,), None, [QueryType.VAR_SWAP]) def forward_var_term( asset: Asset, pricing_date: Optional[GENERIC_DATE] = None, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Forward variance swap term structure. :param asset: asset object loaded from security master :param pricing_date: YYYY-MM-DD or relative days before today e.g. 1d, 1m, 1y :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: forward variance swap term structure """ vt = var_term(asset=asset, pricing_date=pricing_date, source=source, real_time=real_time, request_id=request_id) series = _process_forward_vol_term(asset, vt, Fields.VAR_SWAP.value, 'forwardVarTerm') return series def _get_latest_term_structure_data(asset_id, query_type, where, groupby, source, request_id): today = dt.date.today() query_end = today + dt.timedelta(days=1) with DataContext(today, query_end): q_l = GsDataApi.build_market_data_query( [asset_id], query_type, where=where, source=source, real_time=True, measure='Last' ) log_debug(request_id, _logger, 'q_l %s', q_l) df_l = _market_data_timed(q_l, request_id) if df_l.empty: _logger.warning('no data for last of %s', query_type.value) return df_l with DataContext(df_l.index[-1] - dt.timedelta(hours=1), query_end): q_r = GsDataApi.build_market_data_query([asset_id], query_type, where=where, source=source, real_time=True) log_debug(request_id, _logger, 'q_r %s', q_r) df_r = _market_data_timed(q_r, request_id) if df_r.empty: _logger.warning('no data for last of %s', query_type.value) return df_r dataset_ids = getattr(df_r, 'dataset_ids', ()) df_r['date'] = df_r.index.date df_r = df_r.groupby(groupby, as_index=False).last() df_r = df_r.set_index('date') df_r.dataset_ids = dataset_ids return df_r @plot_measure((AssetClass.Equity, AssetClass.Commod), None, [QueryType.VAR_SWAP]) def var_term( asset: Asset, pricing_date: Optional[str] = None, forward_start_date: Optional[str] = None, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Variance swap term structure. Uses most recent date available if pricing_date is not provided. :param asset: asset object loaded from security master :param pricing_date: relative days before today e.g. 3d, 2m, 1y :param forward_start_date: forward start date e.g. 2m, 1y; defaults to none :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: variance swap term structure """ if real_time: raise NotImplementedError('real-time var term not implemented') if not (pricing_date is None or isinstance(pricing_date, str) or isinstance(pricing_date, dt.date)): raise MqTypeError('pricing_date should be a relative date') check_forward_looking(pricing_date, source, 'var_term') start, end = _range_from_pricing_date(asset.exchange, pricing_date) dataset_ids = set() if forward_start_date: with DataContext(start, end): tenors = _var_swap_tenors(asset, request_id) sub_frames = [] for t in tenors: diff = _tenor_to_month(t) - _tenor_to_month(forward_start_date) if diff < 1: continue t1 = _month_to_tenor(diff) c = var_swap(asset, t1, forward_start_date, source=source, real_time=real_time) dataset_ids.update(getattr(c, 'dataset_ids', ())) c = c.to_frame() if not c.empty: c['tenor'] = t1 sub_frames.append(c) df = pd.concat(sub_frames) else: asset_id = asset.get_marquee_id() today = dt.date.today() df = pd.DataFrame() if asset.asset_class == AssetClass.Equity and (pricing_date is None or end >= today): # try intraday data df = _get_latest_term_structure_data(asset_id, QueryType.VAR_SWAP, None, 'tenor', source, request_id) dataset_ids.update(getattr(df, 'dataset_ids', ())) if df.empty: with DataContext(start, end): q = GsDataApi.build_market_data_query([asset_id], QueryType.VAR_SWAP, source=source, real_time=False) log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) dataset_ids.update(getattr(df, 'dataset_ids', ())) if df.empty: series = ExtendedSeries(dtype=float) else: latest = df.index.max() _logger.info('selected pricing date %s', latest) df = df.loc[latest] cbd = _get_custom_bd(asset.exchange) df.loc[:, Fields.EXPIRATION_DATE.value] = pd.DatetimeIndex( [ts + _to_offset(t) + cbd - cbd for ts, t in zip(df.index, df[Fields.TENOR.value])] ) df = df.set_index(Fields.EXPIRATION_DATE.value) df.index = _normalize_dtidx(df.index) df = df.sort_index() df = df.loc[pd.Timestamp(DataContext.current.start_date) : pd.Timestamp(DataContext.current.end_date)] series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(df[Fields.VAR_SWAP.value]) series.attrs = dict(latest=latest) series.dataset_ids = tuple(dataset_ids) return series def _get_var_swap_df(asset: Asset, where, source, real_time): ids = [] q = GsDataApi.build_market_data_query( [asset.get_marquee_id()], QueryType.VAR_SWAP, where=where, source=source, real_time=real_time ) _logger.debug('q %s', q) result = _market_data_timed(q) ids.extend(getattr(result, 'dataset_ids', tuple())) now = dt.date.today() if not real_time and DataContext.current.end_date >= now: where_list = _split_where_conditions(where) delta = dt.timedelta(days=1) with DataContext(now - delta, now + delta): net = {"queries": []} for wl in where_list: qp = GsDataApi.build_market_data_query( [asset.get_marquee_id()], QueryType.VAR_SWAP, where=wl, source=source, real_time=True, measure='Last', ) net["queries"].extend(qp["queries"]) _logger.debug('last q %s', net) try: df_l = _market_data_timed(net) ids.extend(getattr(df_l, 'dataset_ids', tuple())) except Exception as e: _logger.warning('error loading Last of var_swap', exc_info=e) else: if df_l.empty: _logger.warning('no data for Last of var_swap') else: df_l.index = df_l.index.tz_convert(None).normalize() if df_l.index.max() > result.index.max(): result = pd.concat([result, df_l]) result.dataset_ids = tuple(ids) return result @plot_measure( ( AssetClass.Equity, AssetClass.Commod, ), None, [QueryType.VAR_SWAP], ) def var_swap( asset: Asset, tenor: str, forward_start_date: Optional[str] = None, *, source: str = None, real_time: bool = False ) -> pd.Series: """ Strike such that the price of an uncapped variance swap on the underlying index is zero at inception. If forward start date is provided, then the result is a forward starting variance swap. :param asset: asset object loaded from security master :param tenor: relative date representation of expiration date e.g. 1m :param forward_start_date: forward start date e.g. 2m, 1y; defaults to none :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :return: implied volatility curve """ if forward_start_date is None: _logger.debug('where tenor=%s', tenor) where = dict(tenor=[tenor]) df = _get_var_swap_df(asset, where, source, real_time) series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(df[Fields.VAR_SWAP.value]) series.dataset_ids = getattr(df, 'dataset_ids', ()) return series else: if not isinstance(forward_start_date, str): raise MqTypeError('forward_start_date must be a relative date') x = _tenor_to_month(tenor) y = _tenor_to_month(forward_start_date) z = x + y yt = _month_to_tenor(y) zt = _month_to_tenor(z) tenors = _var_swap_tenors(asset) if yt not in tenors or zt not in tenors: series = ExtendedSeries(dtype=float) series.dataset_ids = () return series _logger.debug('where tenor=%s', f'{yt},{zt}') where = dict(tenor=[yt, zt]) df = _get_var_swap_df(asset, where, source, real_time) dataset_ids = getattr(df, 'dataset_ids', ()) if df.empty: series = ExtendedSeries(dtype=float) else: grouped = df.groupby(Fields.TENOR.value) try: yg = grouped.get_group(yt)[Fields.VAR_SWAP.value] zg = grouped.get_group(zt)[Fields.VAR_SWAP.value] except KeyError: _logger.debug('no data for one or more tenors') series = ExtendedSeries(dtype=float) series.dataset_ids = () return series series = ExtendedSeries(sqrt((z * zg**2 - y * yg**2) / x)) series.dataset_ids = dataset_ids return series def _get_iso_data(region: str): timezone = 'US/Eastern' peak_start = 7 peak_end = 23 weekends = [5, 6] if region in ['MISO', 'ERCOT', 'SPP']: timezone = 'US/Central' peak_start = 6 peak_end = 22 if region == 'CAISO': timezone = 'US/Pacific' weekends = [6] peak_start = 6 peak_end = 22 return timezone, peak_start, peak_end, weekends def _get_qbt_mapping(bucket, region): # Finds combo bucket for power by mapping of the quantity bucket weekend_offpeak = "SUH1X16" if region == 'CAISO' else "2X16H" QBT_mapping = { "OFFPEAK": [weekend_offpeak, "7X8"], "7X16": ["PEAK", weekend_offpeak], "7X24": ["PEAK", "7X8", weekend_offpeak], } return QBT_mapping[bucket.upper()] if bucket.upper() in QBT_mapping else [bucket.upper()] def _get_weight_for_bucket(asset, start_contract_range, end_contract_range, bucket): # Find contracts and holidays in the date range bbid = Asset.get_identifier(asset, AssetIdentifier.BLOOMBERG_ID) if bbid is None: # Get ISO from parameters region = asset.get_entity()['parameters']['ISO'] else: region = bbid.split(" ")[0] timezone = _get_iso_data(region)[0] dates_contract_range = get_contract_range(start_contract_range, end_contract_range, timezone) holidays = NercCalendar().holidays(start=start_contract_range, end=end_contract_range).date # Get number of hours for a given bucket for each contract weights = [] buckets_QBT = _get_qbt_mapping(bucket, region) for bucket_QBT in buckets_QBT: df_bucket = _filter_by_bucket(dates_contract_range, bucket_QBT, holidays, region) weights_df = df_bucket.groupby('contract_month').size() weights_df = pd.DataFrame({'contract': weights_df.index, 'weight': weights_df.values}) weights_df['quantityBucket'] = bucket_QBT weights.append(weights_df) weights = pd.concat(weights) return weights def _filter_by_bucket(df, bucket, holidays, region): # TODO: get frequency definition from SecDB timezone, peak_start, peak_end, weekends = _get_iso_data(region) if bucket.lower() == '7x24': pass # offpeak: 11pm-7am & weekend & holiday elif bucket.lower() == 'offpeak': df = df.loc[ df['date'].isin(holidays) | df['day'].isin(weekends) | ( ~df['date'].isin(holidays) & ~df['day'].isin(weekends) & ((df['hour'] < peak_start) | (df['hour'] > peak_end - 1)) ) ] # peak: 7am to 11pm on weekdays elif bucket.lower() == 'peak': df = df.loc[ (~df['date'].isin(holidays)) & (~df['day'].isin(weekends)) & (df['hour'] > peak_start - 1) & (df['hour'] < peak_end) ] # 7x8: 11pm to 7am elif bucket.lower() == '7x8': df = df.loc[(df['hour'] < peak_start) | (df['hour'] > peak_end - 1)] # 2x16h: weekends & holidays elif bucket.lower() == '2x16h' or bucket.lower() == 'suh1x16': df = df.loc[ ((df['date'].isin(holidays)) | df['day'].isin(weekends)) & ((df['hour'] > peak_start - 1) & (df['hour'] < peak_end)) ] else: raise MqValueError('Invalid bucket: ' + bucket + '. Expected Value: peak, offpeak, 7x24, 7x8, 2x16h.') return df def _string_to_date_interval(interval: str): """ Slang Date::Interval implementation Accept months (e.g. F07), quarters (e.g. 4Q06), half-years (e.g. 1H07), and years (e.g. Cal07 or 2007) :param interval: date-interval :return: start and end date """ if interval[-2:].isdigit(): YS = interval[-2:] year = int("20" + YS) if int(YS) <= 51 else int("19" + YS) else: return "Invalid year" if len(interval) > 4 and interval[-4:].isdigit(): YS = interval[-4:] year = int(YS) start_year = dt.date(year, 1, 1) if len(interval) == 1 + len(YS): if interval[0].upper() in _COMMOD_CONTRACT_MONTH_CODES: month_index = _COMMOD_CONTRACT_MONTH_CODES.index(interval[0].upper()) + 1 start_date = dt.date(year, month_index, 1) end_date = dt.date(year, month_index, calendar.monthrange(year, month_index)[1]) else: return "Invalid month" elif (len(interval) == 2 + len(YS) and interval.isdigit()) or ( interval.casefold().startswith("Cal".casefold()) and len(interval) == 3 + len(YS) ): start_date = dt.date(year, 1, 1) end_date = dt.date(year, 12, 31) elif len(interval) == 2 + len(YS): if interval[0].isdigit(): num = int(interval[0]) else: return "Invalid num" if interval[1].upper() == "Q": if 1 <= num <= 4: start_date = start_year + relativedelta(months=+(3 * (num - 1))) end_date = start_year + relativedelta(months=+(3 * num), days=-1) else: return "Invalid Quarter" if interval[1].upper() == "H": if 1 <= num <= 2: start_date = start_year + relativedelta(months=+(6 * (num - 1))) end_date = start_year + relativedelta(months=+(6 * num), days=-1) else: return "Invalid Half Year" elif len(interval) >= 3 + len(YS): left = interval[0 : len(interval) - len(YS)] if left.isalpha(): if left in calendar.month_name: month_index = {v: k for k, v in enumerate(calendar.month_name)}[left] elif left in calendar.month_abbr: month_index = {v: k for k, v in enumerate(calendar.month_abbr)}[left] else: return "Invalid date code" start_date = dt.date(year, month_index, 1) end_date = dt.date(year, month_index, calendar.monthrange(year, month_index)[1]) else: return "Invalid date code" else: return "Unknown date code" return {'start_date': start_date, 'end_date': end_date} def _merge_curves_by_weighted_average(forwards_data, weights, keys, measure_column): """ Merges price curve with weights curve :param forwards_data: Pricing data :param weights: dataframe with each pricing data point associated with a weight :param keys: keys to join dataframes - columns that uniquely identify each datapoint :param measure_column: dataset measure column name :return: """ # Using cartesian product of the date range and weights dataframe with keys # as this gives us an exhaustive list of data points required for the data to be complete # i.e a composite key comprised of date and keys dates = pd.DataFrame(data=forwards_data['dates'].unique(), columns=["dates"]) weights = pd.merge(weights.assign(key=0), dates.assign(key=0), on='key').drop('key', axis=1) # Left join on the weights dataframe using the composite key result_df = pd.merge(weights, forwards_data, on=keys, how='left') result_df['weighted_price'] = result_df['weight'] * result_df[measure_column] # Filtering dates that have missing buckets or contracts. # Dates with any null values due to unmatched rows in the right table with raw data will be removed as a result. null_dates = set(result_df[result_df[measure_column].isna()]['dates']) result_df = result_df[~result_df['dates'].isin(null_dates)] result_df = result_df.groupby('dates').agg({'weight': 'sum', 'weighted_price': 'sum'}) result_df['price'] = result_df['weighted_price'] / result_df['weight'] result = ExtendedSeries(result_df['price'], index=result_df.index) result = result.rename_axis(None, axis='index') return result def _weighted_average_valuation_curve_for_calendar_strip(asset, contract_range, query_type, measure_field): """ :param asset: marquee asset :param contract_range: tenor in case of instruments, calendar strip for underliers :param query_type: dataset query type :param measure_field: dataset measure column name :return: """ start_date_interval = _string_to_date_interval(contract_range.split("-")[0]) if isinstance(start_date_interval, str): raise MqValueError(start_date_interval) start_contract_range = start_date_interval['start_date'] if "-" in contract_range: end_date_interval = _string_to_date_interval(contract_range.split("-")[1]) if isinstance(end_date_interval, str): raise MqValueError(end_date_interval) end_contract_range = end_date_interval['end_date'] else: end_contract_range = start_date_interval['end_date'] dates_contract_range = get_contract_range(start_contract_range, end_contract_range, None) weights = dates_contract_range.groupby('contract_month').size() weights = pd.DataFrame({'contract': weights.index, 'weight': weights.values}) start, end = DataContext.current.start_date, DataContext.current.end_date contracts_to_query = weights['contract'].unique().tolist() where = dict(contract=contracts_to_query) with DataContext(start, end): q = GsDataApi.build_market_data_query( [asset.get_marquee_id()], query_type, where=where, source=None, real_time=False ) data = _market_data_timed(q) dataset_ids = getattr(data, 'dataset_ids', ()) data['dates'] = data.index.date print('q %s', q) keys = ['contract', 'dates'] result = _merge_curves_by_weighted_average(data, weights, keys, measure_field) result.dataset_ids = dataset_ids return result @plot_measure((AssetClass.Commod,), None, [QueryType.FAIR_PRICE]) def fair_price( asset: Asset, tenor: str = None, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None ) -> pd.Series: """' Fair Price for Swap Instrument :param asset: asset object loaded from security master :param tenor: e.g. inputs - 'Cal20', 'F20-G20', '2Q20', '2H20', 'Cal20-Cal21': Default Value = F20 :param source: name of function caller: default source = None :param real_time: whether to retrieve intraday data instead of EOD: default value = False :param request_id: service request id, if any :return: Fair Price """ asset_type = asset.get_type() if asset_type == SecAssetType.INDEX: if tenor is None: raise MqValueError('Contract month range query input is not specified') return _weighted_average_valuation_curve_for_calendar_strip(asset, tenor, QueryType.FAIR_PRICE, "fairPrice") asset_id = asset.get_marquee_id() q = GsDataApi.build_market_data_query([asset_id], QueryType.FAIR_PRICE, source=source, real_time=real_time) log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) series = _extract_series_from_df(df, QueryType.FAIR_PRICE) series.index = _normalize_dtidx(series.index) return series def _get_start_and_end_dates(contract_range: str) -> (int, int): start_date_interval = _string_to_date_interval(contract_range.split("-")[0]) if isinstance(start_date_interval, str): raise MqValueError(start_date_interval) start_contract_range = start_date_interval['start_date'] if "-" in contract_range: end_date_interval = _string_to_date_interval(contract_range.split("-")[1]) if isinstance(end_date_interval, str): raise MqValueError(end_date_interval) end_contract_range = end_date_interval['end_date'] else: end_contract_range = start_date_interval['end_date'] return (start_contract_range, end_contract_range) def get_weights_for_contracts(contract_range: str) -> dict: (start_contract_range, end_contract_range) = _get_start_and_end_dates(contract_range=contract_range) dates_contract_range = get_contract_range(start_contract_range, end_contract_range, None) weights = dates_contract_range.groupby('contract_month').size() return weights def _forward_price_natgas( asset: Asset, price_method: str = 'GDD', contract_range: str = None, *, source: str = None, real_time: bool = False ) -> pd.Series: """' US NG Forward Price :param asset: asset object loaded from security master :param price_method: type of index settled against - GDD/FERC :param contract_range: e.g. inputs - 'Cal20', 'F20-G20', '2Q20', '2H20', 'Cal20-Cal21': Default Value = F20 :param source: name of function caller: default source = None :param real_time: whether to retrieve intraday data instead of EOD: default value = False :return: Forward Price """ weights = get_weights_for_contracts(contract_range) weights = pd.DataFrame({'contract': weights.index, 'weight': weights.values}) contracts_to_query = weights['contract'].unique().tolist() start, end = DataContext.current.start_date, DataContext.current.end_date where = dict(contract=contracts_to_query, priceMethod=price_method.upper()) with DataContext(start, end): q = GsDataApi.build_market_data_query( [asset.get_marquee_id()], QueryType.FORWARD_PRICE, where=where, source=source, real_time=False ) _logger.debug('q %s', q) data = _market_data_timed(q) dataset_ids = getattr(data, 'dataset_ids', ()) if data.empty: result = ExtendedSeries(dtype='float64') else: data['dates'] = data.index.date keys = ['contract', 'dates'] result = _merge_curves_by_weighted_average(data, weights, keys, "forwardPrice") result.dataset_ids = dataset_ids return result def _forward_price_elec( asset: Asset, price_method: str = 'LMP', bucket: str = 'PEAK', contract_range: str = 'F20', *, source: str = None, real_time: bool = False, ) -> pd.Series: """ Us Power Forward Prices :param asset: asset object loaded from security master :param price_method: price method between LMP, MCP, SPP, Physical: Default value = LMP :param bucket: bucket type among '7x24', 'peak', 'offpeak', '2x16h', '7x16' and '7x8': Default value = 7x24 :param contract_range: e.g. inputs - 'Cal20', 'F20-G20', '2Q20', '2H20', 'Cal20-Cal21': Default Value = F20 :param source: name of function caller: default source = None :param real_time: whether to retrieve intraday data instead of EOD: default value = False :return: Us Power Forward Prices """ (start_contract_range, end_contract_range) = _get_start_and_end_dates(contract_range=contract_range) weights = _get_weight_for_bucket(asset, start_contract_range, end_contract_range, bucket) start, end = DataContext.current.start_date, DataContext.current.end_date contracts_to_query = weights['contract'].unique().tolist() quantitybuckets_to_query = weights['quantityBucket'].unique().tolist() where = dict(priceMethod=price_method.upper(), quantityBucket=quantitybuckets_to_query, contract=contracts_to_query) with DataContext(start, end): def _query_fwd(asset_, where_, source_=None): q = GsDataApi.build_market_data_query( [asset_.get_marquee_id()], QueryType.FORWARD_PRICE, where=where_, source=source_, real_time=False ) _logger.debug('q %s', q) return _market_data_timed(q) forwards_data = _query_fwd(asset, where, source) if forwards_data.empty: # For cases where uppercase priceMethod dont fetch data, try user input as is where['priceMethod'] = price_method forwards_data = _query_fwd(asset, where, source) dataset_ids = getattr(forwards_data, 'dataset_ids', ()) if forwards_data.empty: result = ExtendedSeries(dtype='float64') else: forwards_data['dates'] = forwards_data.index.date keys = ['quantityBucket', 'contract', 'dates'] measure_column = 'forwardPrice' result = _merge_curves_by_weighted_average(forwards_data, weights, keys, measure_column) result.dataset_ids = dataset_ids return result @plot_measure( (AssetClass.Commod,), ( AssetType.Index, AssetType.CommodityPowerAggregatedNodes, AssetType.CommodityPowerNode, ), [QueryType.FORWARD_PRICE], ) def forward_price( asset: Asset, price_method: str = 'LMP', bucket: str = 'PEAK', contract_range: str = 'F20', *, source: str = None, real_time: bool = False, ) -> pd.Series: """ Forward Prices :param asset: asset object loaded from security master :param price_method: price method between LMP, MCP, SPP for US Power assets and GDD, FERC for US NG assets: Default value = LMP :param bucket: bucket type among '7x24', 'peak', 'offpeak', '2x16h', '7x16' and '7x8': Default value = 7x24 :param contract_range: e.g. inputs - 'Cal20', 'F20-G20', '2Q20', '2H20', 'Cal20-Cal21': Default Value = F20 :param source: name of function caller: default source = None :param real_time: whether to retrieve intraday data instead of EOD: default value = False :return: Forward Prices """ if real_time: raise MqValueError('Use daily frequency instead of intraday') return _forward_price_elec(asset, price_method, bucket, contract_range, source=source) @plot_measure( (AssetClass.Commod,), ( AssetType.Index, AssetType.CommodityPowerAggregatedNodes, AssetType.CommodityPowerNode, ), [QueryType.IMPLIED_VOLATILITY], ) def implied_volatility_elec( asset: Asset, price_method: str = 'LMP', bucket: str = 'PEAK', contract_range: str = 'F20', *, source: str = None, real_time: bool = False, ) -> pd.Series: """ Implied Volatility :param asset: asset object loaded from security master :param price_method: price method between LMP, MCP, SPP for US Power assets :param bucket: bucket type among '7x24', 'peak', 'offpeak', '2x16h', '7x16' and '7x8': Default value = 7x24 :param contract_range: e.g. inputs - 'Cal20', 'F20-G20', '2Q20', '2H20', 'Cal20-Cal21': Default Value = F20 :param source: name of function caller: default source = None :param real_time: whether to retrieve intraday data instead of EOD: default value = False :return: Forward Prices """ if real_time: raise MqValueError('Use daily frequency instead of intraday') (start_contract_range, end_contract_range) = _get_start_and_end_dates(contract_range=contract_range) weights = _get_weight_for_bucket(asset, start_contract_range, end_contract_range, bucket) start, end = DataContext.current.start_date, DataContext.current.end_date contracts_to_query = weights['contract'].unique().tolist() quantitybuckets_to_query = weights['quantityBucket'].unique().tolist() where = dict(priceMethod=price_method.upper(), quantityBucket=quantitybuckets_to_query, contract=contracts_to_query) ds = Dataset('COMMOD_US_ELEC_ENERGY_IMPLIED_VOLATILITY') implied_vols_data = ds.get_data(assetId=[asset.get_marquee_id()], where=where, start=start, end=end) _logger.debug('query for %s', where) if implied_vols_data.empty: result = ExtendedSeries(dtype='float64') else: implied_vols_data['dates'] = implied_vols_data.index.date keys = ['quantityBucket', 'contract', 'dates'] measure_column = 'impliedVolatility' result = _merge_curves_by_weighted_average(implied_vols_data, weights, keys, measure_column) result.dataset_ids = ds.id return result def eu_ng_hub_to_swap(asset_spec: ASSET_SPEC) -> str: asset = _asset_from_spec(asset_spec) asset_ref_price = EUNatGasDataReference[asset.name + "_ICE_CommodityReferencePrice"].value result_id = '' try: # Search for atleast one instrument for the given asset using asset parameter if asset.asset_class is AssetClass.Commod and asset.get_type() == SecAssetType.COMMODITY_EU_NATURAL_GAS_HUB: kwargs = dict(asset_parameters_commodity_reference_price=asset_ref_price) instruments = GsAssetApi.get_many_assets(**kwargs) result_id = instruments[0].id except IndexError: result_id = asset.get_marquee_id() return result_id @plot_measure( (AssetClass.Commod,), (AssetType.CommodityNaturalGasHub, AssetType.CommodityEUNaturalGasHub), [MeasureDependency(id_provider=eu_ng_hub_to_swap, query_type=QueryType.FORWARD_PRICE)], display_name='forward_price', ) def forward_price_ng( asset: Asset, contract_range: str = 'F20', price_method: str = 'GDD', *, source: str = None, real_time: bool = False ) -> pd.Series: """ Forward Prices :param asset: asset object loaded from security master :param price_method: For US NG assets:GDD/FERC/EXCHANGE. For EU NG assets:USD/None. Ignore for native currency. :param contract_range: e.g. inputs - 'Cal20', 'F20-G20', '2Q20', '2H20', 'Cal20-Cal21': Default Value = F20 :param source: name of function caller: default source = None :param real_time: whether to retrieve intraday data instead of EOD: default value = False :return: Forward Prices """ if real_time: raise MqValueError('Use daily frequency instead of intraday') if asset.get_type() == SecAssetType.COMMODITY_NATURAL_GAS_HUB: return _forward_price_natgas(asset, price_method, contract_range, source=source) if asset.get_type() == SecAssetType.COMMODITY_EU_NATURAL_GAS_HUB: # If no currency mentioned, defaults to GDD from parent method if price_method == 'GDD': price_method = 'ICE' return _forward_price_eu_natgas(asset, contract_range, price_method) else: raise MqTypeError('The forward_price_ng is not supported for this asset') def get_contract_range(start_contract_range, end_contract_range, timezone): if timezone: df = pd.date_range( start_contract_range, end_contract_range + dt.timedelta(days=1), None, 'h', timezone, False, None, 'left' ).to_frame() df['hour'] = df.index.hour df['day'] = df.index.dayofweek else: df = pd.date_range( start=start_contract_range, end=end_contract_range, ).to_frame() df['date'] = df.index.date df['month'] = df.index.month - 1 df['year'] = df.index.year df['contract_month'] = df['month'].map(_COMMOD_CONTRACT_MONTH_CODES_DICT) + df['year'].astype(str).str[2:] return df @plot_measure( (AssetClass.Commod,), ( AssetType.Index, AssetType.CommodityPowerAggregatedNodes, AssetType.CommodityPowerNode, ), [QueryType.PRICE], ) def bucketize_price( asset: Asset, price_method: str, bucket: str = '7x24', granularity: str = 'daily', *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """' Bucketized COMMOD_US_ELEC_ENERGY_PRICES :param asset: asset object loaded from security master :param price_method: price method between LMP, MCP, SPP, energy, loss, congestion: Default value = LMP :param bucket: bucket type among '7x24', 'peak', 'offpeak', '2x16h' and '7x8': Default value = 7x24 :param granularity: daily or monthly: default value = daily :param source: name of function caller: default source = None :param real_time: whether to retrieve intraday data instead of EOD: default value = False :param request_id: service request id, if any :return: Bucketized elec energy prices """ if real_time: raise MqValueError('Bucketize function returns aggregated daily data') # create granularity indicator if granularity.lower() in ['daily', 'd']: granularity = 'D' elif granularity.lower() in ['monthly', 'm']: granularity = FREQ_MONTH_END else: raise MqValueError('Invalid granularity: ' + granularity + '. Expected Value: daily or monthly.') bbid = Asset.get_identifier(asset, AssetIdentifier.BLOOMBERG_ID) region = bbid.split(" ")[0] timezone = _get_iso_data(region)[0] to_zone = tz.gettz('UTC') from_zone = tz.gettz(timezone) # Start date and end date are considered to be in ISO's local timezone start_date, end_date = DataContext.current.start_date, DataContext.current.end_date holidays = NercCalendar().holidays(start=start_date, end=end_date).date # Start time is constructed by combining start date with 00:00:00 timestamp # in local time and then converted to UTC time # End time is constructed by combining end date with 23:59:59 timestamp # in local time and then converted to UTC time start_time = dt.datetime.combine(start_date, dt.datetime.min.time(), tzinfo=from_zone).astimezone(to_zone) end_time = dt.datetime.combine(end_date, dt.datetime.max.time(), tzinfo=from_zone).astimezone(to_zone) where = dict(priceMethod=price_method.upper()) with DataContext(start_time, end_time): def _query_prices(asset_, where_): q = GsDataApi.build_market_data_query( [asset_.get_marquee_id()], QueryType.PRICE, where=where_, source=source, real_time=True ) log_debug(request_id, _logger, 'q %s', q) return _market_data_timed(q, request_id) df = _query_prices(asset, where) if df.empty: # For cases where uppercase priceMethod don't fetch data, try user input as is where['priceMethod'] = price_method df = _query_prices(asset, where) dataset_ids = getattr(df, 'dataset_ids', ()) if df.empty: series = ExtendedSeries(dtype=float) else: df = df.tz_convert(timezone) df['month'] = df.index.to_period('M') df['date'] = df.index.date df['day'] = df.index.dayofweek df['hour'] = df.index.hour df['timestamp'] = df.index # This will remove any duplicate prices uploaded with the same timestamp df = df.drop_duplicates() # freq is the frequency at which the ISO publishes data for e.g. 15 min, 1hr freq = int(min(np.diff(df.index).astype('timedelta64[s]') / np.timedelta64(1, 's'))) if freq == 0: raise MqValueError('Duplicate data rows probable for this period') # checking missing data points ref_hour_range = pd.date_range( str(start_date), str(end_date + dt.timedelta(days=1)), None, str(freq) + "s", timezone, False, None, 'left' ) missing_hours = ref_hour_range[~ref_hour_range.isin(df.index)] missing_dates = np.unique(missing_hours.date) missing_months = np.unique(np.array(missing_dates, dtype='M8[D]').astype('M8[M]')).astype('str') # drop dates and months which have missing data df = df.loc[(~df['date'].isin(missing_dates))] if granularity == FREQ_MONTH_END: df = df.loc[(~df['month'].astype('str').isin(missing_months))] df = _filter_by_bucket(df, bucket, holidays, region) df = df['price'].resample(granularity).mean() df.index = df.index.date df = df.loc[start_date:end_date] series = ExtendedSeries(df) series.dataset_ids = dataset_ids return series @plot_measure((AssetClass.Equity,), None, [QueryType.FUNDAMENTAL_METRIC]) def dividend_yield( asset: Asset, period: str, period_direction: FundamentalMetricPeriodDirection, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Dividend Yield of the single stock or the asset-weighted average of dividend yields of a composite's underliers. 1y forward: time-weighted average of one fiscal year (FY1) and two fiscal year (FY2) fwd-looking estimates. 2y forward: time-weighted average of two fiscal year (FY2) and three fiscal year (FY3) fwd-looking estimates. 3y forward: time-weighted average of three fiscal year (FY3) and four fiscal year (FY4) fwd-looking estimates. 1y trailing: time-weighted average of latest reported fiscal year (FY0) data and one fiscal year (FY1) fwd-looking estimate. :param asset: asset object loaded from security master :param period: the relative fiscal period from now. e.g. 1y :param period_direction: whether the period is forward-looking or backward-looking e.g. forward :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: dividend yield """ if real_time: raise NotImplementedError('real-time dividend_yield not implemented') mqid = asset.get_marquee_id() metric = "Dividend Yield" _logger.debug('where assetId=%s, metric=%s, period=%s, periodDirection=%s', mqid, metric, period, period_direction) q = GsDataApi.build_market_data_query( [mqid], QueryType.FUNDAMENTAL_METRIC, where=dict(metric=metric, period=period, periodDirection=period_direction.value), source=source, real_time=real_time, ) q['queries'][0]['vendor'] = 'Goldman Sachs' log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, QueryType.FUNDAMENTAL_METRIC) @plot_measure( (AssetClass.Equity,), (AssetType.Research_Basket, AssetType.Custom_Basket, AssetType.Equity_Basket, AssetType.ETF, AssetType.Index), [QueryType.FUNDAMENTAL_METRIC], ) def earnings_per_share( asset: Asset, period: str, period_direction: FundamentalMetricPeriodDirection, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Earnings Per Share (EPS) of the single stock or the asset-weighted average EPS of a composite's underliers. 1y forward: time-weighted average of one fiscal year (FY1) and two fiscal year (FY2) fwd-looking estimates. 2y forward: time-weighted average of two fiscal year (FY2) and three fiscal year (FY3) fwd-looking estimates. 3y forward: time-weighted average of three fiscal year (FY3) and four fiscal year (FY4) fwd-looking estimates. 1y trailing: time-weighted average of latest reported fiscal year (FY0) data and one fiscal year (FY1) fwd-looking estimate. :param asset: asset object loaded from security master :param period: the relative fiscal period from now. e.g. 1y :param period_direction: whether the period is forward-looking or backward-looking e.g. forward :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: earnings per share """ if real_time: raise NotImplementedError('real-time earnings_per_share not implemented') mqid = asset.get_marquee_id() metric = "Earnings per Share" _logger.debug('where assetId=%s, metric=%s, period=%s, periodDirection=%s', mqid, metric, period, period_direction) q = GsDataApi.build_market_data_query( [mqid], QueryType.FUNDAMENTAL_METRIC, where=dict(metric=metric, period=period, periodDirection=period_direction.value), source=source, real_time=real_time, ) q['queries'][0]['vendor'] = 'Goldman Sachs' log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, QueryType.FUNDAMENTAL_METRIC) @plot_measure((AssetClass.Equity,), None, [QueryType.FUNDAMENTAL_METRIC]) def earnings_per_share_positive( asset: Asset, period: str, period_direction: FundamentalMetricPeriodDirection, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Earnings Per Share Positive of the single stock or the asset-weighted average EPSP of a composite's underliers. 1y forward: time-weighted average of one fiscal year (FY1) and two fiscal year (FY2) fwd-looking estimates. 2y forward: time-weighted average of two fiscal year (FY2) and three fiscal year (FY3) fwd-looking estimates. 3y forward: time-weighted average of three fiscal year (FY3) and four fiscal year (FY4) fwd-looking estimates. 1y trailing: time-weighted average of latest reported fiscal year (FY0) data and one fiscal year (FY1) fwd-looking estimate. :param asset: asset object loaded from security master :param period: the relative fiscal period from now. e.g. 1y :param period_direction: whether the period is forward-looking or backward-looking e.g. forward :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: earnings per share positive """ if real_time: raise NotImplementedError('real-time earnings_per_share_positive not implemented') mqid = asset.get_marquee_id() metric = "Earnings per Share Positive" _logger.debug('where assetId=%s, metric=%s, period=%s, periodDirection=%s', mqid, metric, period, period_direction) q = GsDataApi.build_market_data_query( [mqid], QueryType.FUNDAMENTAL_METRIC, where=dict(metric=metric, period=period, periodDirection=period_direction.value), source=source, real_time=real_time, ) q['queries'][0]['vendor'] = 'Goldman Sachs' log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, QueryType.FUNDAMENTAL_METRIC) @plot_measure((AssetClass.Equity,), None, [QueryType.FUNDAMENTAL_METRIC]) def net_debt_to_ebitda( asset: Asset, period: str, period_direction: FundamentalMetricPeriodDirection, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Net Debt to EBITDA of the single stock or the asset-weighted average value of a composite's underliers. 1y forward: time-weighted average of one fiscal year (FY1) and two fiscal year (FY2) fwd-looking estimates. 2y forward: time-weighted average of two fiscal year (FY2) and three fiscal year (FY3) fwd-looking estimates. 3y forward: time-weighted average of three fiscal year (FY3) and four fiscal year (FY4) fwd-looking estimates. 1y trailing: time-weighted average of latest reported fiscal year (FY0) data and one fiscal year (FY1) fwd-looking estimate. :param asset: asset object loaded from security master :param period: the relative fiscal period from now. e.g. 1y :param period_direction: whether the period is forward-looking or backward-looking e.g. forward :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: Net Debt to EBITDA """ if real_time: raise NotImplementedError('real-time net_debt_to_ebitda not implemented') mqid = asset.get_marquee_id() metric = "Net Debt to EBITDA" _logger.debug('where assetId=%s, metric=%s, period=%s, periodDirection=%s', mqid, metric, period, period_direction) q = GsDataApi.build_market_data_query( [mqid], QueryType.FUNDAMENTAL_METRIC, where=dict(metric=metric, period=period, periodDirection=period_direction.value), source=source, real_time=real_time, ) q['queries'][0]['vendor'] = 'Goldman Sachs' log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, QueryType.FUNDAMENTAL_METRIC) @plot_measure((AssetClass.Equity,), None, [QueryType.FUNDAMENTAL_METRIC]) def price_to_book( asset: Asset, period: str, period_direction: FundamentalMetricPeriodDirection, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Price to Book of the single stock or the asset-weighted average value of a composite's underliers. 1y forward: time-weighted average of one fiscal year (FY1) and two fiscal year (FY2) fwd-looking estimates. 2y forward: time-weighted average of two fiscal year (FY2) and three fiscal year (FY3) fwd-looking estimates. 3y forward: time-weighted average of three fiscal year (FY3) and four fiscal year (FY4) fwd-looking estimates. 1y trailing: time-weighted average of latest reported fiscal year (FY0) data and one fiscal year (FY1) fwd-looking estimate. :param asset: asset object loaded from security master :param period: the relative fiscal period from now. e.g. 1y :param period_direction: whether the period is forward-looking or backward-looking e.g. forward :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: Price to Book """ if real_time: raise NotImplementedError('real-time price_to_book not implemented') mqid = asset.get_marquee_id() metric = "Price to Book" _logger.debug('where assetId=%s, metric=%s, period=%s, periodDirection=%s', mqid, metric, period, period_direction) q = GsDataApi.build_market_data_query( [mqid], QueryType.FUNDAMENTAL_METRIC, where=dict(metric=metric, period=period, periodDirection=period_direction.value), source=source, real_time=real_time, ) q['queries'][0]['vendor'] = 'Goldman Sachs' log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, QueryType.FUNDAMENTAL_METRIC) @plot_measure((AssetClass.Equity,), None, [QueryType.FUNDAMENTAL_METRIC]) def price_to_cash( asset: Asset, period: str, period_direction: FundamentalMetricPeriodDirection, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Price to Cash of the single stock or the asset-weighted average value of a composite's underliers. 1y forward: time-weighted average of one fiscal year (FY1) and two fiscal year (FY2) fwd-looking estimates. 2y forward: time-weighted average of two fiscal year (FY2) and three fiscal year (FY3) fwd-looking estimates. 3y forward: time-weighted average of three fiscal year (FY3) and four fiscal year (FY4) fwd-looking estimates. 1y trailing: time-weighted average of latest reported fiscal year (FY0) data and one fiscal year (FY1) fwd-looking estimate. :param asset: asset object loaded from security master :param period: the relative fiscal period from now. e.g. 1y :param period_direction: whether the period is forward-looking or backward-looking e.g. forward :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: Price to Cash """ if real_time: raise NotImplementedError('real-time price_to_cash not implemented') mqid = asset.get_marquee_id() metric = "Price to Cash" _logger.debug('where assetId=%s, metric=%s, period=%s, periodDirection=%s', mqid, metric, period, period_direction) q = GsDataApi.build_market_data_query( [mqid], QueryType.FUNDAMENTAL_METRIC, where=dict(metric=metric, period=period, periodDirection=period_direction.value), source=source, real_time=real_time, ) q['queries'][0]['vendor'] = 'Goldman Sachs' log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, QueryType.FUNDAMENTAL_METRIC) @plot_measure((AssetClass.Equity,), None, [QueryType.FUNDAMENTAL_METRIC]) def price_to_earnings( asset: Asset, period: str, period_direction: FundamentalMetricPeriodDirection, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Price to Earnings of the single stock or the asset-weighted average value of a composite's underliers. 1y forward: time-weighted average of one fiscal year (FY1) and two fiscal year (FY2) fwd-looking estimates. 2y forward: time-weighted average of two fiscal year (FY2) and three fiscal year (FY3) fwd-looking estimates. 3y forward: time-weighted average of three fiscal year (FY3) and four fiscal year (FY4) fwd-looking estimates. 1y trailing: time-weighted average of latest reported fiscal year (FY0) data and one fiscal year (FY1) fwd-looking estimate. :param asset: asset object loaded from security master :param period: the relative fiscal period from now. e.g. 1y :param period_direction: whether the period is forward-looking or backward-looking e.g. forward :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: Price to Earnings """ if real_time: raise NotImplementedError('real-time price_to_earnings not implemented') mqid = asset.get_marquee_id() metric = "Price to Earnings" _logger.debug('where assetId=%s, metric=%s, period=%s, periodDirection=%s', mqid, metric, period, period_direction) q = GsDataApi.build_market_data_query( [mqid], QueryType.FUNDAMENTAL_METRIC, where=dict(metric=metric, period=period, periodDirection=period_direction.value), source=source, real_time=real_time, ) q['queries'][0]['vendor'] = 'Goldman Sachs' log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, QueryType.FUNDAMENTAL_METRIC) @plot_measure((AssetClass.Equity,), None, [QueryType.FUNDAMENTAL_METRIC]) def price_to_earnings_positive( asset: Asset, period: str, period_direction: FundamentalMetricPeriodDirection, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Price to Earnings Positive of the single stock or the asset-weighted average value of a composite's underliers. 1y forward: time-weighted average of one fiscal year (FY1) and two fiscal year (FY2) fwd-looking estimates. 2y forward: time-weighted average of two fiscal year (FY2) and three fiscal year (FY3) fwd-looking estimates. 3y forward: time-weighted average of three fiscal year (FY3) and four fiscal year (FY4) fwd-looking estimates. 1y trailing: time-weighted average of latest reported fiscal year (FY0) data and one fiscal year (FY1) fwd-looking estimate. :param asset: asset object loaded from security master :param period: the relative fiscal period from now. e.g. 1y :param period_direction: whether the period is forward-looking or backward-looking e.g. forward :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: Price to Earnings Positive """ if real_time: raise NotImplementedError('real-time price_to_earnings_positive not implemented') mqid = asset.get_marquee_id() metric = "Price to Earnings Positive" _logger.debug('where assetId=%s, metric=%s, period=%s, periodDirection=%s', mqid, metric, period, period_direction) q = GsDataApi.build_market_data_query( [mqid], QueryType.FUNDAMENTAL_METRIC, where=dict(metric=metric, period=period, periodDirection=period_direction.value), source=source, real_time=real_time, ) q['queries'][0]['vendor'] = 'Goldman Sachs' log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, QueryType.FUNDAMENTAL_METRIC) @plot_measure((AssetClass.Equity,), None, [QueryType.FUNDAMENTAL_METRIC]) def price_to_earnings_positive_exclusive( asset: Asset, period: str, period_direction: FundamentalMetricPeriodDirection, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Price to Earnings Positive of the single stock or the asset-weighted average value of a composite's underliers excluding negative EPS stocks. 1y forward: time-weighted average of one fiscal year (FY1) and two fiscal year (FY2) fwd-looking estimates. 2y forward: time-weighted average of two fiscal year (FY2) and three fiscal year (FY3) fwd-looking estimates. 3y forward: time-weighted average of three fiscal year (FY3) and four fiscal year (FY4) fwd-looking estimates. 1y trailing: time-weighted average of latest reported fiscal year (FY0) data and one fiscal year (FY1) fwd-looking estimate. :param asset: asset object loaded from security master :param period: the relative fiscal period from now. e.g. 1y :param period_direction: whether the period is forward-looking or backward-looking e.g. forward :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: Price to Earnings Positive Exclusive """ if real_time: raise NotImplementedError('real-time price_to_earnings_positive_exclusive not implemented') mqid = asset.get_marquee_id() metric = DataMeasure.PRICE_TO_EARNINGS_POSITIVE_EXCLUSIVE.value _logger.debug('where assetId=%s, metric=%s, period=%s, periodDirection=%s', mqid, metric, period, period_direction) return _fundamentals_md_query(mqid, period, period_direction, metric, source, real_time, request_id) @plot_measure((AssetClass.Equity,), None, [QueryType.FUNDAMENTAL_METRIC]) def price_to_sales( asset: Asset, period: str, period_direction: FundamentalMetricPeriodDirection, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Price to Sales of the single stock or the asset-weighted average value of a composite's underliers. 1y forward: time-weighted average of one fiscal year (FY1) and two fiscal year (FY2) fwd-looking estimates. 2y forward: time-weighted average of two fiscal year (FY2) and three fiscal year (FY3) fwd-looking estimates. 3y forward: time-weighted average of three fiscal year (FY3) and four fiscal year (FY4) fwd-looking estimates. 1y trailing: time-weighted average of latest reported fiscal year (FY0) data and one fiscal year (FY1) fwd-looking estimate. :param asset: asset object loaded from security master :param period: the relative fiscal period from now. e.g. 1y :param period_direction: whether the period is forward-looking or backward-looking e.g. forward :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: Price to Sales """ if real_time: raise NotImplementedError('real-time price_to_sales not implemented') mqid = asset.get_marquee_id() metric = "Price to Sales" _logger.debug('where assetId=%s, metric=%s, period=%s, periodDirection=%s', mqid, metric, period, period_direction) q = GsDataApi.build_market_data_query( [mqid], QueryType.FUNDAMENTAL_METRIC, where=dict(metric=metric, period=period, periodDirection=period_direction.value), source=source, real_time=real_time, ) q['queries'][0]['vendor'] = 'Goldman Sachs' log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, QueryType.FUNDAMENTAL_METRIC) @plot_measure((AssetClass.Equity,), None, [QueryType.FUNDAMENTAL_METRIC]) def return_on_equity( asset: Asset, period: str, period_direction: FundamentalMetricPeriodDirection, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Return on Equity of the single stock or the asset-weighted average value of a composite's underliers. 1y forward: time-weighted average of one fiscal year (FY1) and two fiscal year (FY2) fwd-looking estimates. 2y forward: time-weighted average of two fiscal year (FY2) and three fiscal year (FY3) fwd-looking estimates. 3y forward: time-weighted average of three fiscal year (FY3) and four fiscal year (FY4) fwd-looking estimates. 1y trailing: time-weighted average of latest reported fiscal year (FY0) data and one fiscal year (FY1) fwd-looking estimate. :param asset: asset object loaded from security master :param period: the relative fiscal period from now. e.g. 1y :param period_direction: whether the period is forward-looking or backward-looking e.g. forward :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: Return on Equity """ if real_time: raise NotImplementedError('real-time return_on_equity not implemented') mqid = asset.get_marquee_id() metric = "Return on Equity" _logger.debug('where assetId=%s, metric=%s, period=%s, periodDirection=%s', mqid, metric, period, period_direction) q = GsDataApi.build_market_data_query( [mqid], QueryType.FUNDAMENTAL_METRIC, where=dict(metric=metric, period=period, periodDirection=period_direction.value), source=source, real_time=real_time, ) q['queries'][0]['vendor'] = 'Goldman Sachs' log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, QueryType.FUNDAMENTAL_METRIC) @plot_measure((AssetClass.Equity,), None, [QueryType.FUNDAMENTAL_METRIC]) def sales_per_share( asset: Asset, period: str, period_direction: FundamentalMetricPeriodDirection, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Sales per Share of the single stock or the asset-weighted average value of a composite's underliers. 1y forward: time-weighted average of one fiscal year (FY1) and two fiscal year (FY2) fwd-looking estimates. 2y forward: time-weighted average of two fiscal year (FY2) and three fiscal year (FY3) fwd-looking estimates. 3y forward: time-weighted average of three fiscal year (FY3) and four fiscal year (FY4) fwd-looking estimates. 1y trailing: time-weighted average of latest reported fiscal year (FY0) data and one fiscal year (FY1) fwd-looking estimate. :param asset: asset object loaded from security master :param period: the relative fiscal period from now. e.g. 1y :param period_direction: whether the period is forward-looking or backward-looking e.g. forward :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: Sales per Share """ if real_time: raise NotImplementedError('real-time sales_per_share not implemented') mqid = asset.get_marquee_id() metric = "Sales per Share" _logger.debug('where assetId=%s, metric=%s, period=%s, periodDirection=%s', mqid, metric, period, period_direction) q = GsDataApi.build_market_data_query( [mqid], QueryType.FUNDAMENTAL_METRIC, where=dict(metric=metric, period=period, periodDirection=period_direction.value), source=source, real_time=real_time, ) q['queries'][0]['vendor'] = 'Goldman Sachs' log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, QueryType.FUNDAMENTAL_METRIC) @plot_measure( (AssetClass.Equity,), ( AssetType.Research_Basket, AssetType.Custom_Basket, ), [QueryType.FUNDAMENTAL_METRIC], ) def current_constituents_dividend_yield( asset: Asset, period: str, period_direction: FundamentalMetricPeriodDirection, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Current Constituents Dividend Yield of the asset-weighted average of dividend yields of a composite's underliers. 1y forward: time-weighted average of one fiscal year (FY1) and two fiscal year (FY2) fwd-looking estimates. 2y forward: time-weighted average of two fiscal year (FY2) and three fiscal year (FY3) fwd-looking estimates. 3y forward: time-weighted average of three fiscal year (FY3) and four fiscal year (FY4) fwd-looking estimates. 1y trailing: time-weighted average of latest reported fiscal year (FY0) data and one fiscal year (FY1) fwd-looking estimate. :param asset: asset object loaded from security master :param period: the relative fiscal period from now. e.g. 1y :param period_direction: whether the period is forward-looking or backward-looking e.g. forward :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: current constituents dividend yield """ if real_time: raise NotImplementedError('real-time current_constituents_dividend_yield not implemented') if not asset.get_entity().get('parameters', {'flagship': False}).get('flagship', False): raise NotImplementedError( 'current_constituents_dividend_yield not implemented for this basket. Only available for flagship baskets' ) mqid = asset.get_marquee_id() metric = DataMeasure.CURRENT_CONSTITUENTS_DIVIDEND_YIELD.value _logger.debug('where assetId=%s, metric=%s, period=%s, periodDirection=%s', mqid, metric, period, period_direction) return _fundamentals_md_query(mqid, period, period_direction, metric, source, real_time, request_id) @plot_measure( (AssetClass.Equity,), ( AssetType.Research_Basket, AssetType.Custom_Basket, ), [QueryType.FUNDAMENTAL_METRIC], ) def current_constituents_earnings_per_share( asset: Asset, period: str, period_direction: FundamentalMetricPeriodDirection, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Current Constituents Earnings Per Share (EPS) of the asset-weighted average EPS of a composite's underliers. 1y forward: time-weighted average of one fiscal year (FY1) and two fiscal year (FY2) fwd-looking estimates. 2y forward: time-weighted average of two fiscal year (FY2) and three fiscal year (FY3) fwd-looking estimates. 3y forward: time-weighted average of three fiscal year (FY3) and four fiscal year (FY4) fwd-looking estimates. 1y trailing: time-weighted average of latest reported fiscal year (FY0) data and one fiscal year (FY1) fwd-looking estimate. :param asset: asset object loaded from security master :param period: the relative fiscal period from now. e.g. 1y :param period_direction: whether the period is forward-looking or backward-looking e.g. forward :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: current constituents earnings per share """ if real_time: raise NotImplementedError('real-time current_constituents_earnings_per_share not implemented') if not asset.get_entity().get('parameters', {'flagship': False}).get('flagship', False): raise NotImplementedError( 'current_constituents_earnings_per_share not implemented for this basket. ' 'Only available for flagship baskets' ) mqid = asset.get_marquee_id() metric = DataMeasure.CURRENT_CONSTITUENTS_EARNINGS_PER_SHARE.value _logger.debug('where assetId=%s, metric=%s, period=%s, periodDirection=%s', mqid, metric, period, period_direction) return _fundamentals_md_query(mqid, period, period_direction, metric, source, real_time, request_id) @plot_measure( (AssetClass.Equity,), ( AssetType.Research_Basket, AssetType.Custom_Basket, ), [QueryType.FUNDAMENTAL_METRIC], ) def current_constituents_earnings_per_share_positive( asset: Asset, period: str, period_direction: FundamentalMetricPeriodDirection, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Current Constituents Earnings Per Share Positive of the asset-weighted average EPSP of a composite's underliers. 1y forward: time-weighted average of one fiscal year (FY1) and two fiscal year (FY2) fwd-looking estimates. 2y forward: time-weighted average of two fiscal year (FY2) and three fiscal year (FY3) fwd-looking estimates. 3y forward: time-weighted average of three fiscal year (FY3) and four fiscal year (FY4) fwd-looking estimates. 1y trailing: time-weighted average of latest reported fiscal year (FY0) data and one fiscal year (FY1) fwd-looking estimate. :param asset: asset object loaded from security master :param period: the relative fiscal period from now. e.g. 1y :param period_direction: whether the period is forward-looking or backward-looking e.g. forward :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: current constituents earnings per share positive """ if real_time: raise NotImplementedError('real-time current_constituents_earnings_per_share_positive not implemented') if not asset.get_entity().get('parameters', {'flagship': False}).get('flagship', False): raise NotImplementedError( 'current_constituents_earnings_per_share_positive not implemented for this basket. ' 'Only available for flagship baskets' ) mqid = asset.get_marquee_id() metric = DataMeasure.CURRENT_CONSTITUENTS_EARNINGS_PER_SHARE_POSITIVE.value _logger.debug('where assetId=%s, metric=%s, period=%s, periodDirection=%s', mqid, metric, period, period_direction) return _fundamentals_md_query(mqid, period, period_direction, metric, source, real_time, request_id) @plot_measure( (AssetClass.Equity,), ( AssetType.Research_Basket, AssetType.Custom_Basket, ), [QueryType.FUNDAMENTAL_METRIC], ) def current_constituents_net_debt_to_ebitda( asset: Asset, period: str, period_direction: FundamentalMetricPeriodDirection, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Current Constituents Net Debt to EBITDA of the asset-weighted average value of a composite's underliers. 1y forward: time-weighted average of one fiscal year (FY1) and two fiscal year (FY2) fwd-looking estimates. 2y forward: time-weighted average of two fiscal year (FY2) and three fiscal year (FY3) fwd-looking estimates. 3y forward: time-weighted average of three fiscal year (FY3) and four fiscal year (FY4) fwd-looking estimates. 1y trailing: time-weighted average of latest reported fiscal year (FY0) data and one fiscal year (FY1) fwd-looking estimate. :param asset: asset object loaded from security master :param period: the relative fiscal period from now. e.g. 1y :param period_direction: whether the period is forward-looking or backward-looking e.g. forward :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: Current Constituents Net Debt to EBITDA """ if real_time: raise NotImplementedError('real-time current_constituents_net_debt_to_ebitda not implemented') if not asset.get_entity().get('parameters', {'flagship': False}).get('flagship', False): raise NotImplementedError( 'current_constituents_net_debt_to_ebitda not implemented for this basket. ' 'Only available for flagship baskets' ) mqid = asset.get_marquee_id() metric = DataMeasure.CURRENT_CONSTITUENTS_NET_DEBT_TO_EBITDA.value _logger.debug('where assetId=%s, metric=%s, period=%s, periodDirection=%s', mqid, metric, period, period_direction) return _fundamentals_md_query(mqid, period, period_direction, metric, source, real_time, request_id) @plot_measure( (AssetClass.Equity,), ( AssetType.Research_Basket, AssetType.Custom_Basket, ), [QueryType.FUNDAMENTAL_METRIC], ) def current_constituents_price_to_book( asset: Asset, period: str, period_direction: FundamentalMetricPeriodDirection, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Current Constituents Price to Book of the asset-weighted average value of a composite's underliers. 1y forward: time-weighted average of one fiscal year (FY1) and two fiscal year (FY2) fwd-looking estimates. 2y forward: time-weighted average of two fiscal year (FY2) and three fiscal year (FY3) fwd-looking estimates. 3y forward: time-weighted average of three fiscal year (FY3) and four fiscal year (FY4) fwd-looking estimates. 1y trailing: time-weighted average of latest reported fiscal year (FY0) data and one fiscal year (FY1) fwd-looking estimate. :param asset: asset object loaded from security master :param period: the relative fiscal period from now. e.g. 1y :param period_direction: whether the period is forward-looking or backward-looking e.g. forward :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: Current Constituents Price to Book """ if real_time: raise NotImplementedError('real-time current_constituents_price_to_book not implemented') if not asset.get_entity().get('parameters', {'flagship': False}).get('flagship', False): raise NotImplementedError( 'current_constituents_price_to_book not implemented for this basket. Only available for flagship baskets' ) mqid = asset.get_marquee_id() metric = DataMeasure.CURRENT_CONSTITUENTS_PRICE_TO_BOOK.value _logger.debug('where assetId=%s, metric=%s, period=%s, periodDirection=%s', mqid, metric, period, period_direction) return _fundamentals_md_query(mqid, period, period_direction, metric, source, real_time, request_id) @plot_measure( (AssetClass.Equity,), ( AssetType.Research_Basket, AssetType.Custom_Basket, ), [QueryType.FUNDAMENTAL_METRIC], ) def current_constituents_price_to_cash( asset: Asset, period: str, period_direction: FundamentalMetricPeriodDirection, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Current Constituents Price to Cash of the asset-weighted average value of a composite's underliers. 1y forward: time-weighted average of one fiscal year (FY1) and two fiscal year (FY2) fwd-looking estimates. 2y forward: time-weighted average of two fiscal year (FY2) and three fiscal year (FY3) fwd-looking estimates. 3y forward: time-weighted average of three fiscal year (FY3) and four fiscal year (FY4) fwd-looking estimates. 1y trailing: time-weighted average of latest reported fiscal year (FY0) data and one fiscal year (FY1) fwd-looking estimate. :param asset: asset object loaded from security master :param period: the relative fiscal period from now. e.g. 1y :param period_direction: whether the period is forward-looking or backward-looking e.g. forward :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: Current Constituents Price to Cash """ if real_time: raise NotImplementedError('real-time current_constituents_price_to_cash not implemented') if not asset.get_entity().get('parameters', {'flagship': False}).get('flagship', False): raise NotImplementedError( 'current_constituents_price_to_cash not implemented for this basket. Only available for flagship baskets' ) mqid = asset.get_marquee_id() metric = DataMeasure.CURRENT_CONSTITUENTS_PRICE_TO_CASH.value _logger.debug('where assetId=%s, metric=%s, period=%s, periodDirection=%s', mqid, metric, period, period_direction) return _fundamentals_md_query(mqid, period, period_direction, metric, source, real_time, request_id) @plot_measure( (AssetClass.Equity,), ( AssetType.Research_Basket, AssetType.Custom_Basket, ), [QueryType.FUNDAMENTAL_METRIC], ) def current_constituents_price_to_earnings( asset: Asset, period: str, period_direction: FundamentalMetricPeriodDirection, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Current Constituents Price to Earnings of the asset-weighted average value of a composite's underliers. 1y forward: time-weighted average of one fiscal year (FY1) and two fiscal year (FY2) fwd-looking estimates. 2y forward: time-weighted average of two fiscal year (FY2) and three fiscal year (FY3) fwd-looking estimates. 3y forward: time-weighted average of three fiscal year (FY3) and four fiscal year (FY4) fwd-looking estimates. 1y trailing: time-weighted average of latest reported fiscal year (FY0) data and one fiscal year (FY1) fwd-looking estimate. :param asset: asset object loaded from security master :param period: the relative fiscal period from now. e.g. 1y :param period_direction: whether the period is forward-looking or backward-looking e.g. forward :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: Current Constituents Price to Earnings """ if real_time: raise NotImplementedError('real-time current_constituents_price_to_earnings not implemented') if not asset.get_entity().get('parameters', {'flagship': False}).get('flagship', False): raise NotImplementedError( 'current_constituents_price_to_earnings not implemented for this basket. ' 'Only available for flagship baskets' ) mqid = asset.get_marquee_id() metric = DataMeasure.CURRENT_CONSTITUENTS_PRICE_TO_EARNINGS.value _logger.debug('where assetId=%s, metric=%s, period=%s, periodDirection=%s', mqid, metric, period, period_direction) return _fundamentals_md_query(mqid, period, period_direction, metric, source, real_time, request_id) @plot_measure( (AssetClass.Equity,), ( AssetType.Research_Basket, AssetType.Custom_Basket, ), [QueryType.FUNDAMENTAL_METRIC], ) def current_constituents_price_to_earnings_positive( asset: Asset, period: str, period_direction: FundamentalMetricPeriodDirection, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Current Constituents Price to Earnings Positive of the asset-weighted average value of a composite's underliers. 1y forward: time-weighted average of one fiscal year (FY1) and two fiscal year (FY2) fwd-looking estimates. 2y forward: time-weighted average of two fiscal year (FY2) and three fiscal year (FY3) fwd-looking estimates. 3y forward: time-weighted average of three fiscal year (FY3) and four fiscal year (FY4) fwd-looking estimates. 1y trailing: time-weighted average of latest reported fiscal year (FY0) data and one fiscal year (FY1) fwd-looking estimate. :param asset: asset object loaded from security master :param period: the relative fiscal period from now. e.g. 1y :param period_direction: whether the period is forward-looking or backward-looking e.g. forward :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: Current Constituents Price to Earnings Positive """ if real_time: raise NotImplementedError('real-time current_constituents_price_to_earnings_positive not implemented') if not asset.get_entity().get('parameters', {'flagship': False}).get('flagship', False): raise NotImplementedError( 'current_constituents_price_to_earnings_positive not implemented for this basket. ' 'Only available for flagship baskets' ) mqid = asset.get_marquee_id() metric = DataMeasure.CURRENT_CONSTITUENTS_PRICE_TO_EARNINGS_POSITIVE.value _logger.debug('where assetId=%s, metric=%s, period=%s, periodDirection=%s', mqid, metric, period, period_direction) return _fundamentals_md_query(mqid, period, period_direction, metric, source, real_time, request_id) @plot_measure( (AssetClass.Equity,), ( AssetType.Research_Basket, AssetType.Custom_Basket, ), [QueryType.FUNDAMENTAL_METRIC], ) def current_constituents_price_to_sales( asset: Asset, period: str, period_direction: FundamentalMetricPeriodDirection, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Current Constituents Price to Sales of the asset-weighted average value of a composite's underliers. 1y forward: time-weighted average of one fiscal year (FY1) and two fiscal year (FY2) fwd-looking estimates. 2y forward: time-weighted average of two fiscal year (FY2) and three fiscal year (FY3) fwd-looking estimates. 3y forward: time-weighted average of three fiscal year (FY3) and four fiscal year (FY4) fwd-looking estimates. 1y trailing: time-weighted average of latest reported fiscal year (FY0) data and one fiscal year (FY1) fwd-looking estimate. :param asset: asset object loaded from security master :param period: the relative fiscal period from now. e.g. 1y :param period_direction: whether the period is forward-looking or backward-looking e.g. forward :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: Current Constituents Price to Sales """ if real_time: raise NotImplementedError('real-time current_constituents_price_to_sales not implemented') if not asset.get_entity().get('parameters', {'flagship': False}).get('flagship', False): raise NotImplementedError( 'current_constituents_price_to_sales not implemented for this basket. Only available for flagship baskets' ) mqid = asset.get_marquee_id() metric = DataMeasure.CURRENT_CONSTITUENTS_PRICE_TO_SALES.value _logger.debug('where assetId=%s, metric=%s, period=%s, periodDirection=%s', mqid, metric, period, period_direction) return _fundamentals_md_query(mqid, period, period_direction, metric, source, real_time, request_id) @plot_measure( (AssetClass.Equity,), ( AssetType.Research_Basket, AssetType.Custom_Basket, ), [QueryType.FUNDAMENTAL_METRIC], ) def current_constituents_return_on_equity( asset: Asset, period: str, period_direction: FundamentalMetricPeriodDirection, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Current Constituents Return on Equity of the asset-weighted average value of a composite's underliers. 1y forward: time-weighted average of one fiscal year (FY1) and two fiscal year (FY2) fwd-looking estimates. 2y forward: time-weighted average of two fiscal year (FY2) and three fiscal year (FY3) fwd-looking estimates. 3y forward: time-weighted average of three fiscal year (FY3) and four fiscal year (FY4) fwd-looking estimates. 1y trailing: time-weighted average of latest reported fiscal year (FY0) data and one fiscal year (FY1) fwd-looking estimate. :param asset: asset object loaded from security master :param period: the relative fiscal period from now. e.g. 1y :param period_direction: whether the period is forward-looking or backward-looking e.g. forward :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: Current Constituents Return on Equity """ if real_time: raise NotImplementedError('real-time current_constituents_return_on_equity not implemented') if not asset.get_entity().get('parameters', {'flagship': False}).get('flagship', False): raise NotImplementedError( 'current_constituents_return_on_equity not implemented for this basket. Only available for flagship baskets' ) mqid = asset.get_marquee_id() metric = DataMeasure.CURRENT_CONSTITUENTS_RETURN_ON_EQUITY.value _logger.debug('where assetId=%s, metric=%s, period=%s, periodDirection=%s', mqid, metric, period, period_direction) return _fundamentals_md_query(mqid, period, period_direction, metric, source, real_time, request_id) @plot_measure( (AssetClass.Equity,), ( AssetType.Research_Basket, AssetType.Custom_Basket, ), [QueryType.FUNDAMENTAL_METRIC], ) def current_constituents_sales_per_share( asset: Asset, period: str, period_direction: FundamentalMetricPeriodDirection, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Current Constituents Sales per Share of the asset-weighted average value of a composite's underliers. 1y forward: time-weighted average of one fiscal year (FY1) and two fiscal year (FY2) fwd-looking estimates. 2y forward: time-weighted average of two fiscal year (FY2) and three fiscal year (FY3) fwd-looking estimates. 3y forward: time-weighted average of three fiscal year (FY3) and four fiscal year (FY4) fwd-looking estimates. 1y trailing: time-weighted average of latest reported fiscal year (FY0) data and one fiscal year (FY1) fwd-looking estimate. :param asset: asset object loaded from security master :param period: the relative fiscal period from now. e.g. 1y :param period_direction: whether the period is forward-looking or backward-looking e.g. forward :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: Current Constituents Sales per Share """ if real_time: raise NotImplementedError('real-time current_constituents_sales_per_share not implemented') if not asset.get_entity().get('parameters', {'flagship': False}).get('flagship', False): raise NotImplementedError( 'current_constituents_sales_per_share not implemented for this basket. Only available for flagship baskets' ) mqid = asset.get_marquee_id() metric = DataMeasure.CURRENT_CONSTITUENTS_SALES_PER_SHARE.value _logger.debug('where assetId=%s, metric=%s, period=%s, periodDirection=%s', mqid, metric, period, period_direction) return _fundamentals_md_query(mqid, period, period_direction, metric, source, real_time, request_id) @plot_measure( (AssetClass.Equity,), (AssetType.Index, AssetType.ETF, AssetType.Custom_Basket, AssetType.Research_Basket), [QueryType.REALIZED_CORRELATION], ) def realized_correlation( asset: Asset, tenor: str, top_n_of_index: Optional[int] = None, composition_date: Optional[GENERIC_DATE] = None, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Realized correlation of an asset. :param asset: asset object loaded from security master :param tenor: relative date representation of expiration date e.g. 1m :param top_n_of_index: the number of top constituents to take into account :param composition_date: YYYY-MM-DD or relative days before today e.g. 1d, 1m, 1y; defaults to most recent date available :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: realized correlation curve """ if real_time: raise NotImplementedError('realtime realized_correlation not implemented') if top_n_of_index is None: if composition_date is not None: raise MqValueError('specify top_n_of_index to get realized correlation of top constituents') if asset.get_type() in (SecAssetType.CUSTOM_BASKET, SecAssetType.RESEARCH_BASKET): raise MqValueError('Baskets must have top_n_of_index set') _check_top_n(top_n_of_index) if top_n_of_index is not None and top_n_of_index > 100: raise MqValueError('number of constituents (top_n_of_index) must be <= 100') mqid = asset.get_marquee_id() _logger.debug('where tenor=%s', tenor) where = dict(tenor=tenor) if top_n_of_index is None: # no append last b/c there is no real-time Realized Correlation dataset q = GsDataApi.build_market_data_query( [mqid], QueryType.REALIZED_CORRELATION, where=where, source=source, real_time=real_time ) log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, QueryType.REALIZED_CORRELATION) # results for top n constituents = _get_index_constituent_weights(asset, top_n_of_index, composition_date) head_start = DataContext.current.start_date - dt.timedelta(days=relative_date_add(tenor, True) + 1) with DataContext(head_start, DataContext.current.end_date): asset_ids = constituents.index.to_list() + [mqid] q = GsDataApi.build_market_data_query(asset_ids, QueryType.SPOT, source=source, real_time=real_time) log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) if not real_time and DataContext.current.end_date >= dt.date.today(): df = append_last_for_measure(df, asset_ids, QueryType.SPOT, None, source=source, request_id=request_id) match = df['assetId'] == mqid df_asset = df.loc[match] df_rest = df.loc[~match] grouped = df_rest.groupby('assetId') weighted_vols = [] for underlying_id, weight in zip(constituents.index, constituents['netWeight']): filtered = grouped.get_group(underlying_id) if underlying_id in grouped.indices else pd.DataFrame() filtered = filtered.loc[~filtered.index.duplicated(keep='last')] if not filtered.empty: vol = volatility(filtered['spot'], Window(tenor, tenor)) / 100 assert not vol.empty, f'insufficient history to calculate for {tenor}' weighted_vols.append(vol * weight) if len(weighted_vols) == 0: raise MqValueError(f'no data for constituents of {asset}') # expected number of data points for each pricing date. All assets required, i.e. do not calculate # if constituent is missing on that day min_no_of_assets = top_n_of_index s1 = pd.concat(weighted_vols, axis=1).sum(axis=1, min_count=min_no_of_assets).dropna() s2 = pd.concat(map(lambda x: x * x, weighted_vols), axis=1).sum(axis=1, min_count=min_no_of_assets).dropna() idx_vol = volatility(df_asset['spot'], Window(tenor, tenor)) / 100 idx_vol = idx_vol[idx_vol.index.isin(s1.index)] values = (idx_vol * idx_vol - s2) / (s1 * s1 - s2) * 100 series = ExtendedSeries(values) series.dataset_ids = getattr(df, 'dataset_ids', ()) return series @plot_measure( (AssetClass.Commod, AssetClass.Equity, AssetClass.FX, AssetClass.Credit, AssetClass.Rates), None, [QueryType.SPOT], asset_type_excluded=( AssetType.CommodityEUNaturalGasHub, AssetType.CommodityNaturalGasHub, ), ) def realized_volatility( asset: Asset, w: Union[Window, int, str] = Window(None, 0), returns_type: Returns = Returns.LOGARITHMIC, pricing_location: Optional[PricingLocation] = None, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Realized volatility for an asset. :param asset: asset object loaded from security master :param w: number of observations to use; defaults to length of series. If string is provided, should be in relative date form (e.g. "1d", "1w", "1m", "1y", etc). :param returns_type: returns type: logarithmic or simple :param pricing_location: EOD Location if multiple available Example - "HKG", "LDN", "NYC" :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: realized volatility curve """ if asset.asset_class != AssetClass.FX or real_time: q = GsDataApi.build_market_data_query( [asset.get_marquee_id()], QueryType.SPOT, source=source, real_time=real_time ) log_debug(request_id, _logger, 'q %s', q) df = get_historical_and_last_for_measure( [asset.get_marquee_id()], QueryType.SPOT, {}, source=source, real_time=real_time, request_id=request_id ) else: location = pricing_location if pricing_location else PricingLocation.NYC where = dict(pricingLocation=location.value) q = GsDataApi.build_market_data_query( [asset.get_marquee_id()], QueryType.SPOT, where=where, source=source, real_time=real_time ) log_debug(request_id, _logger, 'q %s', q) df = get_historical_and_last_for_measure( [asset.get_marquee_id()], QueryType.SPOT, where, source=source, real_time=real_time, request_id=request_id ) spot = df['spot'] spot = spot[~spot.index.duplicated(keep='first')] series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(volatility(spot, w, returns_type)) series.dataset_ids = getattr(df, 'dataset_ids', ()) return series @plot_measure((AssetClass.Equity,), None, [QueryType.ES_SCORE]) def esg_headline_metric( asset: Asset, metricName: EsgMetric = EsgMetric.ENVIRONMENTAL_SOCIAL_AGGREGATE_SCORE, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Environmental, Social, and Governance (ESG) scores and percentiles for a broad set of companies across the globe, including, but not limited to, companies covered by Global Investment Research (GIR) analysts. :param asset: asset object loaded from security master :param metricName: Name of the metric :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: esg data of the asset for the field requested """ if real_time: raise NotImplementedError('real-time esg_headline_metric not implemented') mqid = asset.get_marquee_id() query_metric = inflection.camelize(metricName.value, False) query_type = ESG_METRIC_TO_QUERY_TYPE.get(query_metric) _logger.debug('where assetId=%s, metric=%s, query_type=%s', mqid, query_metric, query_type.value) q = GsDataApi.build_market_data_query([mqid], query_type, source=source, real_time=real_time) log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(df[query_metric]) series.dataset_ids = getattr(df, 'dataset_ids', ()) return series @plot_measure((AssetClass.Equity,), (AssetType.Single_Stock,), [QueryType.RATING]) def rating( asset: Asset, metric: _RatingMetric = _RatingMetric.RATING, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Analyst Rating, which may take on the following values {'Sell': -1, 'Neutral': 0, 'Buy': 1} Conviction List, which is true if the security is on the Conviction Buy List or false otherwise. Securities with a convictionList value equal to true are by definition a subset of the securities with a rating equal to Buy. *Note that there may be periods when stock coverage has been suspended, or a stock is not actively rated by GIR for legal or policy reasons. The lack of an active rating for those dates may not be visible when graphed because the default visualization connects individual data points where an active rating did exist and does not indicate whether there was a time gap in active coverage between such points. Please refer to the Data Sheet tab for the specific dates and values that are available, or access the relevant research reports on the research portal to see rating history. :param asset: asset object loaded from security master :param metric: Name of metric. Either rating or conviction list :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: ratings in numeric form ( 'Buy' : 1, 'Neutral' : 0, 'Sell' : -1 ) """ if real_time: raise NotImplementedError('real-time rating not implemented') mqid = asset.get_marquee_id() query_type = _RATING_METRIC_TO_QUERY_TYPE[metric.value] _logger.debug('where assetId=%s, metric=%s, query_type=%s', mqid, metric.value, query_type.value) q = GsDataApi.build_market_data_query([mqid], query_type, source=source, real_time=real_time) log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) series = _extract_series_from_df(df, query_type) if query_type == QueryType.RATING: series = series.replace(['Buy', 'Sell', 'Neutral'], [1, -1, 0]).astype('int64') return series @plot_measure( (AssetClass.FX,), (AssetType.Cross,), [MeasureDependency(id_provider=cross_to_usd_based_cross, query_type=QueryType.FAIR_VALUE)], ) def fair_value( asset: Asset, metric: EquilibriumExchangeRateMetric = EquilibriumExchangeRateMetric.GSDEER, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ GSDEER and GSFEER quarterly estimates for currency fair values made by Global Investment Research (GIR) macro analysts. :param asset: asset object loaded from security master :param metric: Name of metric. One of gsdeer, gsfeer :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: gsdeer/gsfeer data of the asset for the field requested """ if real_time: raise NotImplementedError('real-time fair_value not implemented') mqid = asset.get_marquee_id() usd_based_cross_mqid = cross_to_usd_based_cross(mqid) ds = Dataset('GSDEER_GSFEER') start_date = dt.date.today() - pd.tseries.offsets.BDay(1) df = ds.get_data(assetId=usd_based_cross_mqid, start_date=start_date) latest_date = df.index.max() df = df.loc[latest_date] df.index = df.apply(lambda x: dt.date(int(x.year), (int(x.quarter[-1]) - 1) * 3 + 1, 1), axis=1) series = _extract_series_from_df(df, metric) if mqid != usd_based_cross_mqid: series = 1 / series series.dataset_ids = getattr(df, 'dataset_ids', ()) return series @plot_measure((AssetClass.Equity,), (AssetType.Single_Stock,), [QueryType.GROWTH_SCORE]) def factor_profile( asset: Asset, metric: _FactorProfileMetric = _FactorProfileMetric.GROWTH_SCORE, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ This dataset consists of Goldman Sachs Investment Profile ("IP") percentiles for US and Canadian securities covered by Goldman Sachs GIR analysts. Beginning in mid-2017, IP was renamed GS Factor Profile; the dataset provided hereunder refers to both IP and GS Factor Profile percentiles, and is referred to herein as "GS Factor Profile". Percentiles are generated daily, and historical values are available going back to January 1, 2009. *Note that there may be periods when stock coverage has been suspended, or a stock is not actively rated by GIR for legal or policy reasons. The lack of an active rating for those dates may not be visible when graphed because the default visualization connects individual data points where an active rating did exist and does not indicate whether there was a time gap in active coverage between such points. Please refer to the Data Sheet tab for the specific dates and values that are available, or access the relevant research reports on the research portal to see rating history. :param asset: asset object loaded from security master :param metric: Growth Score, i.e. Growth percentile relative to Americas coverage universe (a higher score means faster growth); Financial Returns Score, i.e. Financial Returns percentile relative to Americas coverage universe (a higher score means stronger financial returns); Multiple Score, i.e. Multiple percentile relative to Americas coverage universe (a higher score means more expensive); Integrated Score, i.e. Average of the Growth, Financial Returns and (1-Multiple) percentile (a higher score means more attractive) :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: Factor Profile data of the asset for the field requested """ if real_time: raise NotImplementedError('real-time factor_profile not implemented') mqid = asset.get_marquee_id() query_type = _FACTOR_PROFILE_METRIC_TO_QUERY_TYPE.get(metric.value) _logger.debug('where assetId=%s, metric=%s, query=%s', mqid, metric.value, query_type.value) q = GsDataApi.build_market_data_query([mqid], query_type, source=source, real_time=real_time) log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) series = _extract_series_from_df(df, query_type) return series @plot_measure( (AssetClass.Commod,), ( AssetType.Commodity, AssetType.Index, ), [QueryType.COMMODITY_FORECAST], ) def commodity_forecast( asset: Asset, forecastPeriod: str = "3m", forecastType: _CommodityForecastType = _CommodityForecastType.SPOT_RETURN, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Short and long-term commodities forecast. :param asset: asset object loaded from security master :param forecastPeriod: Relative, e.g. 3m, 6m, 12m; Quarterly (up to next two years), e.g. 2020Q3; Annual (up to next ten years), e.g. 2023 :param forecastType: Type of return for commodity indices, spot for individual commodities. One of spotReturn/totalReturn/rollReturn/spot :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: Forecast Value of the commodity or index as requested """ if real_time: raise NotImplementedError('real-time commodity_forecast not implemented') mqid = asset.get_marquee_id() query_type = QueryType.COMMODITY_FORECAST _logger.debug( 'where assetId=%s, forecastPeriod=%s, forecastType=%s, query_type=%s', mqid, forecastPeriod, forecastType, query_type.value, ) q = GsDataApi.build_market_data_query( [mqid], query_type, where=dict(forecastPeriod=str(forecastPeriod), forecastType=forecastType), source=source, real_time=real_time, ) log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) series = _extract_series_from_df(df, query_type) return series @plot_measure( (AssetClass.Commod,), ( AssetType.Commodity, AssetType.Index, ), [QueryType.COMMODITY_FORECAST], ) def commodity_forecast_time_series( asset: Union[Asset, str], forecastFrequency: Union[ str, _CommodityForecastTimeSeriesPeriodType ] = _CommodityForecastTimeSeriesPeriodType.ANNUAL, forecastType: _CommodityForecastType = _CommodityForecastType.SPOT, forecastHorizonYears: int = 12, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Short and long-term commodities forecast time series. :param asset: Asset object or string representing the asset ID :param forecastFrequency: Forecast period type (e.g., annual, quarterly) :param forecastType: Type of return for commodity indices :param forecastHorizonYears: Length of the forecast period in years :param source: Name of the function caller :param real_time: Whether to retrieve intraday data instead of EOD :param request_id: Service request ID, if any :return: Forecast time series for the commodity or index """ if real_time: raise NotImplementedError('real-time commodity_forecast not implemented') if isinstance(asset, str): mqid = asset elif isinstance(asset, Asset): mqid = asset.get_marquee_id() else: raise ValueError("Asset must be of type Asset or str") if isinstance(forecastFrequency, Enum): forecastFrequency = forecastFrequency.value query_type = QueryType.COMMODITY_FORECAST col_name = query_type.value.replace(' ', '') col_name = col_name[0].lower() + col_name[1:] today = dt.date.today() start_year = today.year end_year = start_year + forecastHorizonYears if forecastFrequency == _CommodityForecastTimeSeriesPeriodType.SHORT_TERM.value: periods = ["3m", "6m", "12m"] elif forecastFrequency == _CommodityForecastTimeSeriesPeriodType.MONTHLY.value: periods = [ f"{date.year}M{date.month}" for date in pd.date_range(start=f"{start_year}-01-01", end=f"{end_year}-01-01", freq="MS") ] elif forecastFrequency == _CommodityForecastTimeSeriesPeriodType.QUARTERLY.value: periods = pd.period_range(start=f"{start_year}-01-01", end=f"{end_year}-01-01", freq="Q").strftime("%YQ%q") elif forecastFrequency == _CommodityForecastTimeSeriesPeriodType.ANNUAL.value: periods = pd.date_range(start=f"{start_year}-01-01", end=f"{end_year}-01-01", freq="YS").strftime("%Y") else: raise ValueError( "Invalid forecastFrequency. Must be one of '3/6/12-Month Rolling', 'Monthly', 'Quarterly', 'Annual'." ) results = [] for period in periods: _logger.debug( 'where assetId=%s, forecastPeriod=%s, forecastType=%s, query_type=%s', mqid, period, forecastType, query_type.value, ) q = GsDataApi.build_market_data_query( [mqid], query_type, where=dict(forecastPeriod=period, forecastType=forecastType), source=source, real_time=real_time, ) log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id) if not df.empty: last_value = df[col_name].iloc[-1] if 'm' in period: months = int(period[:-1]) start_of_period = pd.Timestamp.today() + relativedelta(months=months) elif "Q" in period: year = int(period[:4]) quarter = int(period[-1]) start_of_period = pd.Timestamp(f"{year}-{(quarter - 1) * 3 + 1:02d}-01") elif "M" in period: month = int(period[5:]) start_of_period = pd.Timestamp(period[:4] + "-01-01") start_of_period = start_of_period.replace(month=month) else: start_of_period = pd.Timestamp(period[:4] + "-01-01") results.append({'date': start_of_period.strftime('%Y-%m-%d'), col_name: last_value}) df = pd.DataFrame(results).set_index('date') series = _extract_series_from_df(df, query_type) return series def _get_marketdate_validation(market_date, start_date, end_date, timezone=None): """ Validates the market date provided by the user @param market_date: date string provided by the user @param start_date: passed form data context @param end_date: passed form data context @param timezone: passed for power forward curve @return: modifies market_date, start_date if applied """ if not isinstance(market_date, str): raise MqTypeError('Market date should be of string data type as \'YYYYMMDD\'') # If no date entered by user, assign last weekday or convert entered string format to date object if len(market_date) == 0: market_date = pd.Timestamp.today().date() while market_date.weekday() > 4: market_date -= dt.timedelta(days=1) else: try: market_date = dt.datetime.strptime(market_date, "%Y%m%d").date() except ValueError: raise MqValueError('Market date should be of format as \'YYYYMMDD\'') # Check if market date is future date for the given asset if timezone: today_date = pd.Timestamp.today(tz=timezone).date() else: today_date = pd.Timestamp.today().date() if market_date > today_date: raise MqValueError('Market date cannot be a future date') # Check if market date is weekend if market_date.weekday() > 4: raise MqValueError('Market date cannot be a weekend') # Check if market date is within end and start date ranges if market_date > end_date: raise MqValueError('Market date should be within end date for query') if market_date > start_date: start_date = market_date return market_date, start_date @plot_measure( (AssetClass.Commod,), ( AssetType.Index, AssetType.CommodityPowerAggregatedNodes, AssetType.CommodityPowerNode, ), [QueryType.FORWARD_PRICE], ) def forward_curve( asset: Asset, bucket: str = 'PEAK', market_date: str = "", *, source: str = None, real_time: bool = False ) -> pd.Series: """ Forward Curve for Futures Data :param asset: asset object loaded from security master :param bucket: bucket type among '7x24', 'Peak', 'Offpeak', '2x16h', '7x16' and '7x8': Default value = Peak :param market_date: Date for which forward curve to be plotted, format-YYYYMMDD: default value = today :param source: name of function caller: default source = None :param real_time: whether to retrieve intraday data instead of EOD: default value = False :return: Term structure or price projection for future contracts for a given date """ if real_time: raise MqValueError('Forward Curve uses daily frequency instead of intraday') # Validations for date inputs start, end = DataContext.current.start_date, DataContext.current.end_date timezone = _get_iso_data(asset.name.split()[0])[0] market_date, start = _get_marketdate_validation(market_date, start, end, timezone) # Check if market date do not have an entry, a holiday or value not entered yet, if so get prev date value ds = Dataset('COMMOD_US_ELEC_ENERGY_FORWARD_PRICES') last_data = ds.get_data_last(as_of=market_date) last_date = pd.Timestamp(last_data.index.unique().values[0]) if market_date > last_date.date(): market_date = last_date # Find valid contracts and buckets for query, use until end of month for end date weights = _get_weight_for_bucket(asset, start, end + pd.tseries.offsets.MonthEnd(0), bucket) contracts_to_query = weights['contract'].unique().tolist() quantitybuckets_to_query = weights['quantityBucket'].unique().tolist() # Get data for given asset for given market date for valid contracts and buckets where = dict(quantityBucket=quantitybuckets_to_query, contract=contracts_to_query) with DataContext(market_date, market_date): q = GsDataApi.build_market_data_query( [asset.get_marquee_id()], QueryType.FORWARD_PRICE, where=where, source=None, real_time=False ) forward_price = _market_data_timed(q) dataset_ids = getattr(forward_price, 'dataset_ids', ()) # If no data received if forward_price.empty: return ExtendedSeries(dtype=float) # Get plot dates for valid contract_months forward_dates_to_plot = dict() for month in contracts_to_query: start_date_month = _string_to_date_interval(month)["start_date"] forward_dates_to_plot[month] = start_date_month # Create a dataframe with required columns forward_curve_plot = { 'contract': forward_price['contract'], 'forwardPrice': forward_price['forwardPrice'], 'quantityBucket': forward_price['quantityBucket'], } df = pd.DataFrame(forward_curve_plot) df = df.reset_index(drop=True) # Compute wtd average if required for combo buckets is_wtd_avg_required = True if len(quantitybuckets_to_query) > 1 else False if is_wtd_avg_required: df = pd.merge(df, weights, how='left', on=['contract', 'quantityBucket']) df['forwardPrice'] = df['forwardPrice'] * df['weight'] df = df.groupby('contract').agg({'weight': 'sum', 'forwardPrice': 'sum'}) df['forwardPrice'] = df['forwardPrice'] / df['weight'] df = df.reset_index() # Map contract to plot date and format output df['contract'] = df['contract'].map(forward_dates_to_plot) df = df.sort_values(by=['contract']) df = df.set_index('contract') # Create Extended Series for return result = ExtendedSeries(df['forwardPrice'], index=df.index) result = result.rename_axis(None, axis='index') result.dataset_ids = dataset_ids return result @plot_measure( (AssetClass.Commod,), (AssetType.CommodityNaturalGasHub,), [QueryType.FORWARD_PRICE], display_name='forward_curve' ) def forward_curve_ng( asset: Asset, price_method: str = 'GDD', market_date: str = "", *, source: str = None, real_time: bool = False ) -> pd.Series: """ Forward Curve for Us gas Data :param asset: asset object loaded from security master :param price_method: Price method for forward curve for us gas eg: GDD :param market_date: Date for which forward curve to be plotted, format-YYYYMMDD: default value = today :param source: name of function caller: default source = None :param real_time: whether to retrieve intraday data instead of EOD: default value = False :return: Term structure or price projection for future contracts for a given date """ if real_time: raise MqValueError('Forward Curve uses daily frequency instead of intraday') # Validations for date inputs start, end = DataContext.current.start_date, DataContext.current.end_date market_date, start = _get_marketdate_validation(market_date=market_date, start_date=start, end_date=end) # Check if market date do not have an entry, a holiday or value not entered yet, if so get prev date value ds = Dataset('COMMOD_US_NG_FORWARD_PRICES') last_data = ds.get_data_last(as_of=market_date) last_date = pd.Timestamp(last_data.index.unique().values[0]) if market_date > last_date.date(): market_date = last_date # Find valid contracts, use until end of month for end date contracts_to_query_df = get_contract_range(start, end, timezone=None) contracts_to_query = contracts_to_query_df['contract_month'].unique().tolist() # Get data for given asset for given market date for valid contracts and buckets where = dict(contract=contracts_to_query, priceMethod=price_method.upper()) with DataContext(market_date, market_date): q = GsDataApi.build_market_data_query( [asset.get_marquee_id()], QueryType.FORWARD_PRICE, where=where, source=None, real_time=False ) forward_price = _market_data_timed(q) dataset_ids = getattr(forward_price, 'dataset_ids', ()) # If no data received if forward_price.empty: return ExtendedSeries(dtype=float) # Get plot dates for valid contract_months forward_dates_to_plot = dict() for month in contracts_to_query: start_date_month = _string_to_date_interval(month)["start_date"] forward_dates_to_plot[month] = start_date_month # Create a dataframe with required columns forward_curve_plot = { 'contract': forward_price['contract'], 'forwardPrice': forward_price['forwardPrice'], 'priceMethod': forward_price['priceMethod'], } df = pd.DataFrame(forward_curve_plot) df = df.reset_index(drop=True) # Map contract to plot date and format output df['contract'] = df['contract'].map(forward_dates_to_plot) df = df.sort_values(by=['contract']) df = df.set_index('contract') # Create Extended Series for return result = ExtendedSeries(df['forwardPrice'], index=df.index) result = result.rename_axis(None, axis='index') result.dataset_ids = dataset_ids return result def _forward_price_eu_natgas( asset: Asset, contract_range: str = 'F20', price_method: str = 'ICE', *, source: str = None, real_time: bool = False ) -> pd.Series: """' EU NG Forward Price - function to map the assets to right instruments and fetch the forward prices for given hub for required contracts :param asset: asset object loaded from security master :param contract_range: e.g. inputs - Default Value = F20 :param price_method: Fixing source for the prices - ICE/ Heren User to enter USD for the Heren prices. Whereas ICE prices are in EUR /GBP for diff assets so will be default :param source: name of function caller: default source = None :param real_time: whether to retrieve intraday data instead of EOD: default value = False :return: Forward Price """ # Get weights for each contract month within range weights = get_weights_for_contracts(contract_range) weights = pd.DataFrame({'contract': weights.index, 'weight': weights.values}) contracts_to_query = weights['contract'].unique().tolist() assets_to_query = [] price_method = EUNatGasDataReference[price_method].value ref_price_key = '_'.join([asset.name, price_method, "CommodityReferencePrice"]) asset_commod_ref_price = EUNatGasDataReference[ref_price_key].value # Find all assets to query for each of these contract months for contract in contracts_to_query: kwargs = dict( asset_parameters_commodity_reference_price=asset_commod_ref_price, asset_parameters_start=contract ) try: instruments = GsAssetApi.get_many_assets(**kwargs) if len(instruments) == 1: assets_to_query.append(instruments[0].id) else: raise ValueError except ValueError: _logger.debug('Cannot find asset or found multiple assets for: %s', contract) # Get data for each asset for the specified duration q = GsDataApi.build_market_data_query(assets_to_query, QueryType.FORWARD_PRICE, source=source, real_time=False) _logger.debug('q %s', q) df = _market_data_timed(q) dataset_ids = getattr(df, 'dataset_ids', ()) # Get weighted average of each contract month if df.empty: result = ExtendedSeries(dtype='float64') else: df['dates'] = df.index.date result = _merge_curves_by_weighted_average(df, weights, ['contract', 'dates'], "forwardPrice") result.dataset_ids = dataset_ids return result @plot_measure((AssetClass.FX,), None, [QueryType.IMPLIED_VOLATILITY]) def fx_implied_correlation( asset: Asset, asset_2: Asset, tenor: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ FX implied correlation. Uses most recent date available if pricing_date is not provided. :param asset: asset object loaded from security master :param asset_2: FX cross used to calculate implied correlation eg. EURUSD.fx_implied_correlation(USDJPY, 3m) :param tenor: relative date representation of expiration date e.g. 1m :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: FX implied correlation curve """ if real_time: raise NotImplementedError('realtime fx_implied_correlation not implemented') if asset_2.asset_class != AssetClass.FX: raise MqValueError('Only FX crosses supported for fx_implied_correlation') def get_bbid(asset): try: return next(o for o in asset._Entity__entity['identifiers'] if o['type'] == 'BID')['value'] except Exception as e: log_debug(request_id, _logger, 'could not get BBID from asset', exc_info=e) return asset.get_identifier(AssetIdentifier.BLOOMBERG_ID) cross_1 = get_bbid(asset) cross_2 = get_bbid(asset_2) invert_factor = 1 # checks ccys = [cross_1[:3], cross_1[3:], cross_2[:3], cross_2[3:]] if len(ccys) <= len(set(ccys)): raise MqValueError('crosses must have common currency') # corr cross cross_3, base_ccy = '', '' for ccy in ccys: if ccys.count(ccy) == 1: cross_3 += ccy if ccys.count(ccy) == 2: base_ccy = ccy # marquee ids new_asset_1 = cross_stored_direction_for_fx_vol(asset, return_asset=True) new_asset_2 = cross_stored_direction_for_fx_vol(asset_2, return_asset=True) id_1 = new_asset_1.get_marquee_id() id_2 = new_asset_2.get_marquee_id() id_3 = SecurityMaster.get_asset( _cross_stored_direction_helper(cross_3), AssetIdentifier.BLOOMBERG_ID ).get_marquee_id() # normal crosses c_1 = get_bbid(new_asset_1) if c_1 != cross_1: invert_factor *= -1 c_2 = get_bbid(new_asset_2) if c_2 != cross_2: invert_factor *= -1 if c_1[:3] == base_ccy and c_2[:3] != base_ccy: invert_factor *= -1 if c_1[3:] == base_ccy and c_2[3:] != base_ccy: invert_factor *= -1 where = dict(tenor=tenor, strikeReference='delta', relativeStrike=0) q = GsDataApi.build_market_data_query( [id_1, id_2, id_3], QueryType.IMPLIED_VOLATILITY, where=where, source=source, real_time=real_time ) log_debug(request_id, _logger, 'q %s', q) df = _market_data_timed(q, request_id=request_id) if df.empty: return pd.Series(dtype=float) df1 = df.loc[df['assetId'] == id_1] df2 = df.loc[df['assetId'] == id_2] df3 = df.loc[df['assetId'] == id_3] col = 'impliedVolatility' corr = invert_factor * (df1[col] * df1[col] + df2[col] * df2[col] - df3[col] * df3[col]) / (2 * df1[col] * df2[col]) series = ExtendedSeries(corr, name='impCorr') series.dataset_ids = getattr(df, 'dataset_ids', ()) return series def get_last_for_measure( asset_ids: List[str], query_type, where, *, source: str = None, request_id: Optional[str] = None, ignore_errors: bool = False, ): now = dt.date.today() delta = dt.timedelta(days=2) with DataContext(now - delta, now + delta): q_l = GsDataApi.build_market_data_query( asset_ids, query_type, where=where, source=source, real_time=True, measure='Last' ) log_debug(request_id, _logger, 'q_l %s', q_l) try: df_l = _market_data_timed(q_l, request_id, ignore_errors=ignore_errors) except Exception as e: log_warning(request_id, _logger, f'unable to get last of {query_type}', exc_info=e) else: if df_l.empty: log_warning(request_id, _logger, f'no data for last of {query_type}') else: df_l.index = df_l.index.tz_convert(None).normalize() return df_l return None def merge_dataframes(dataframes: List[pd.DataFrame]): if dataframes is None: return pd.DataFrame() result = pd.concat(dataframes) result["dummy"] = result.index # drop_duplicates ignores indexes, so we need to include it as a column first result = result.drop_duplicates().drop(columns=["dummy"]) result = result.sort_index(kind='mergesort') # mergesort for its "stable" sort functionality setattr(result, 'dataset_ids', tuple(set(flatten(get(df, 'dataset_ids', ()) for df in dataframes)))) return result def append_last_for_measure( df: pd.DataFrame, asset_ids: List[str], query_type, where, *, source: str = None, request_id: Optional[str] = None ): df_l = get_last_for_measure(asset_ids, query_type, where, source=source, request_id=request_id) if df_l is None: return df result = pd.concat([df, df_l]) result["dummy"] = result.index # drop_duplicates ignores indexes, so we need to include it as a column first result = result.drop_duplicates().drop(columns=["dummy"]) result.dataset_ids = tuple(set(getattr(df, 'dataset_ids', ())).union(getattr(df_l, 'dataset_ids', ()))) return result def get_market_data_tasks( asset_ids: List[str], query_type, where: dict, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, chunk_size: int = 5, ignore_errors: bool = False, parallelize_queries: bool = False, ) -> list: tasks = [] for i, chunked_assets in enumerate(chunk(asset_ids, chunk_size)): queries = GsDataApi.build_market_data_query( chunked_assets, query_type, where=where, source=source, real_time=real_time, parallelize_queries=parallelize_queries, ) queries = [queries] if not isinstance(queries, list) else queries tasks.extend(partial(_market_data_timed, query, request_id, ignore_errors=ignore_errors) for query in queries) return tasks def get_historical_and_last_for_measure( asset_ids: List[str], query_type, where: dict, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, chunk_size: int = 5, ignore_errors: bool = False, parallelize_queries: bool = False, ): tasks = get_market_data_tasks( asset_ids, query_type, where, source=source, real_time=real_time, request_id=request_id, chunk_size=chunk_size, ignore_errors=ignore_errors, parallelize_queries=parallelize_queries, ) if not real_time and DataContext.current.end_date >= dt.date.today(): where_list = _split_where_conditions(where) for w in where_list: tasks.append( partial( get_last_for_measure, asset_ids=asset_ids, query_type=query_type, where=w, source=source, request_id=request_id, ignore_errors=ignore_errors, ) ) results = ThreadPoolManager.run_async(tasks) return merge_dataframes(results) @plot_measure( (AssetClass.Commod,), (AssetType.FutureMarket,), [QueryType.SETTLEMENT_PRICE], ) def settlement_price(asset: Asset, contract: str = 'F22', *, source: str = None, real_time: bool = False) -> pd.Series: """ Settlement price from exchanges for Commod FutureMarket :param asset: asset object loaded from security master :param contract: contract month for price. Ex: F22 :param source: name of function caller: default source = None :param real_time: whether to retrieve intraday data instead of EOD: default value = False :return: Settlement_Price from Dataset """ if real_time: raise MqValueError('Use daily frequency instead of intraday') # Find the right dataset based on the asset try: asset_parameters = asset.get_entity()['parameters'] if len(asset_parameters): asset_exchange = asset_parameters['exchange'] asset_product = asset_parameters['productGroup'] if ( asset_exchange == EUPowerDataReference.ICE_EXCHANGE.value and asset_product == EUPowerDataReference.CARBON_CREDIT_PRODUCT.value ): ds = Dataset(EUPowerDataReference.CARBON_CREDIT_DATASET.value) elif ( asset_exchange == EUPowerDataReference.EEX_EXCHANGE.value or asset_exchange == EUPowerDataReference.ICE_EXCHANGE.value or asset_exchange == EUPowerDataReference.NASDAQ_EXCHANGE.value ) and asset_product == EUPowerDataReference.EU_POWER_PRODUCT.value: ds = Dataset(EUPowerDataReference.EU_POWER_EXCHANGE_DATASET.value) else: raise MqTypeError( 'Assets of exchange %s and group %s not handled currently', asset_exchange, asset_product ) else: raise MqTypeError("No required parameter information on asset schema") except (TypeError, ValueError, KeyError) as e: _logger.warning('Error while fetching asset information to choose dataset', exc_info=e) raise MqTypeError('Error: %s', str(e)) where = dict(assetId=asset.get_marquee_id(), contract=contract) start, end = DataContext.current.start_date, DataContext.current.end_date result = ds.get_data(where=where, start=start, end=end) _logger.debug('q - Data queried from %s', ds.id) if result.empty: result = ExtendedSeries(dtype='float64') else: result = ExtendedSeries(result['settlementPrice']) result.dataset_ids = ds.id return result @plot_measure((AssetClass.Equity,), (AssetType.Index, AssetType.Single_Stock), [QueryType.SPOT]) def hloc_prices( asset: Asset, interval_frequency: IntervalFrequency = IntervalFrequency.DAILY, *, source: str = None, real_time: bool = False, ) -> pd.DataFrame: """ Get the hloc (high, low, open, and close) prices of the given asset. :param asset: asset object loaded from security master :param interval_frequency: frequency of the hloc :param source: name of function caller: default source = None :param real_time: whether to retrieve intraday data instead of EOD, real time is currently not supported :return: DataFrame with high, low, open, close columns """ if real_time: raise MqValueError('Use daily frequency instead of intraday.') start, end = DataContext.current.start_date, DataContext.current.end_date return asset.get_hloc_prices(start, end, interval_frequency) @plot_measure( (AssetClass.Equity,), (AssetType.Custom_Basket, AssetType.Research_Basket, AssetType.Index, AssetType.ETF), [QueryType.THEMATIC_MODEL_BETA], ) def thematic_model_exposure( asset: Asset, basket_identifier: str, notional: int = 10000000, *, source: str = None, real_time: bool = False ) -> pd.Series: """ Thematic exposure of an asset to a requested GS thematic flagship basket :param asset: asset object loaded from security master :param basket_identifier: identifer of the requested basket (ticker, bbid, etc.) :param notional: Optional notional, will default to $10mm for if not entered :param source: name of function caller: default source = None :param real_time: whether to retrieve intraday data instead of EOD, real time is currently not supported :return: Timeseries of daily thematic exposure of asset to requested flagship basket """ if real_time: raise MqValueError('Use daily frequency instead of intraday.') start, end = DataContext.current.start_date, DataContext.current.end_date thematic_exposures = PositionedEntity.get_thematic_exposure(asset, basket_identifier, notional, start, end) return _extract_series_from_df(thematic_exposures, QueryType.THEMATIC_EXPOSURE) @plot_measure( (AssetClass.Equity,), (AssetType.Custom_Basket, AssetType.Research_Basket, AssetType.Index, AssetType.ETF, AssetType.Single_Stock), [QueryType.THEMATIC_MODEL_BETA], ) def thematic_model_beta( asset: Asset, basket_identifier: str, *, source: str = None, real_time: bool = False ) -> pd.Series: """ Thematic beta values of an asset to a requested GS thematic flagship basket :param asset: asset object loaded from security master :param basket_identifier: identifer of the requested basket (ticker, bbid, etc.) :param source: name of function caller: default source = None :param real_time: whether to retrieve intraday data instead of EOD, real time is currently not supported :return: Timeseries of daily thematic beta of asset to requested flagship basket """ if real_time: raise MqValueError('Use daily frequency instead of intraday.') start, end = DataContext.current.start_date, DataContext.current.end_date if asset.get_type().value == AssetType.Single_Stock.value: thematic_betas = Stock.get_thematic_beta(asset, basket_identifier, start, end) else: thematic_betas = PositionedEntity.get_thematic_beta(asset, basket_identifier, start, end) return _extract_series_from_df(thematic_betas, QueryType.THEMATIC_BETA) @plot_measure( (AssetClass.Equity,), (AssetType.Custom_Basket, AssetType.Research_Basket, AssetType.Index, AssetType.ETF), [QueryType.SPOT], ) def retail_interest_agg( asset: Asset, measure: RetailMeasures = RetailMeasures.RETAIL_PCT_SHARES, data_source: UnderlyingSourceCategory = UnderlyingSourceCategory.ALL, sector: GICSSector = GICSSector.ALL, *, source: str = None, real_time: bool = False, ) -> pd.Series: """ Aggregates retail interest for specified Equity asset with underliers. :param sector: Any valid GICSSector like Information Technology, Energy etc. or All :param asset: Any index, basket, ETF with underliers. :param measure: Measures listed on https://marquee.gs.com/s/developer/datasets/RETAIL_FLOW_DAILY_V2_PREMIUM. :param data_source: One of EDGX (on Exchange), TRF (Off Exchange), ALL. :param source: name of function caller: default source = None :param real_time: whether to retrieve intraday data instead of EOD, real time is currently not supported :return: historical retail interest """ if real_time: raise NotImplementedError('realtime retail_interest_agg not implemented') start, end = DataContext.current.start_date, DataContext.current.end_date underliers = asset.entity['underlying_asset_ids'] if len(underliers) == 0: raise MqValueError('Specified asset does not have underliers, single names have separate retail measures') if sector != GICSSector.ALL: data = pd.DataFrame( PositionedEntity.get_positions_data( asset, RelativeDate('-2b', base_date=end).apply_rule(), end, fields=['assetClassificationsGicsSector'] ) ) underliers = list(data[data['assetClassificationsGicsSector'] == sector.value]['id'].drop_duplicates().values) ds = Dataset(Dataset.GS.RETAIL_FLOW_DAILY_V2_PREMIUM.value) retail_data = get_dataset_with_many_assets( ds, assets=underliers, start=start, end=end, underlyingSourceCategory=data_source ) if retail_data.empty: return ExtendedSeries(dtype=float) if 'Pct' in measure.value: measures_to_sum = [measure.value.replace('Pct', '')] if 'Shares' in measure.value: measures_to_sum.append(RetailMeasures.SHARES.value) else: measures_to_sum.append(RetailMeasures.NOTIONAL.value) category_sums = retail_data.groupby([retail_data.index])[measures_to_sum].agg("sum") category_sums[measure.value] = 100 * category_sums[measures_to_sum[0]] / category_sums[measures_to_sum[1]] category_sums.drop(columns=measures_to_sum) else: category_sums = retail_data.groupby([retail_data.index])[[measure.value]].agg(sum) series = ExtendedSeries(category_sums[measure.value], name=measure.value) series.dataset_ids = ds.id return series class S3Metrics(Enum): LONG_CROWDING = ("Long Crowding",) SHORT_CROWDING = ("Short Crowding",) LONG_SHORT_CROWDING_RATION = ("Long Short Crowding Ratio",) CURRENT_CONSTITUENTS_LONG_CROWDING = ("Current Constituents Long Crowding",) CURRENT_CONSTITUENTS_SHORT_CROWDING = ("Current Constituents Short Crowding",) CURRENT_CONSTITUENTS_LONG_SHORT_CROWDING = ("Current Constituents Long Short Crowding Ratio",) CROWDING_NET_EXPOSURE = ("Crowding Net Exposure",) CURRENT_CONSTITUENTS_CROWDING_NET_EXPOSURES = "Current Constituents Crowding Net Exposure" @plot_measure( (AssetClass.Equity,), ( AssetType.Custom_Basket, AssetType.Research_Basket, ), ) def s3_long_short_concentration( asset: Asset, s3Metric: S3Metrics = S3Metrics.LONG_CROWDING, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Retrieves the specified metric from the S3 Partners Long/Short Concentration Aggregated Data dataset for a given asset and plots it. :param asset: asset object loaded from security master :param s3Metric: The aggregated S3 metric to retrieve (e.g., "Long Crowding", "Short Crowding"). :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: time series of the specified metric values """ ds = Dataset('S3_BASKETS_AGG') start = DataContext.current.start_date end = DataContext.current.end_date # Query the timeseries from the dataset: df = ds.get_data(start, end, assetId=asset.get_marquee_id(), s3Metric=s3Metric) # Extract the timeseries and format it for PTP return _extract_series_from_df(df, QueryType.S3_AGGREGATE_DATA) ================================================ FILE: gs_quant/timeseries/measures_countries.py ================================================ """ Copyright 2020 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import logging from enum import Enum from typing import Optional import pandas as pd import inflection from gs_quant.api.gs.data import QueryType, GsDataApi from gs_quant.data import Dataset from gs_quant.data.core import DataContext from gs_quant.entities.entity import EntityType from gs_quant.timeseries import plot_measure_entity from gs_quant.timeseries.measures import _market_data_timed, _extract_series_from_df, ExtendedSeries LOGGER = logging.getLogger(__name__) class _FCI_MEASURE(Enum): FCI = "fci" LONG_RATES_CONTRIBUTION = "long_rates_contribution" SHORT_RATES_CONTRIBUTION = "short_rates_contribution" CORPORATE_SPREAD_CONTRIBUTION = "corporate_spread_contribution" SOVEREIGN_SPREAD_CONTRIBUTION = "sovereign_spread_contribution" EQUITIES_CONTRIBUTION = "equities_contribution" REAL_LONG_RATES_CONTRIBUTION = "real_long_rates_contribution" REAL_SHORT_RATES_CONTRIBUTION = "real_short_rates_contribution" DWI_CONTRIBUTION = "dwi_contribution" REAL_FCI = "real_fci" REAL_TWI_CONTRIBUTION = "real_twi_contribution" TWI_CONTRIBUTION = "twi_contribution" @plot_measure_entity(EntityType.COUNTRY, [QueryType.FCI]) def fci( country_id: str, measure: _FCI_MEASURE = _FCI_MEASURE.FCI, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Daily Financial Conditions Index (FCI) for each of the world's large economies and many smaller ones, as well as aggregate FCIs for regions. :param country_id: id of country/region :param measure: FCI metric to retrieve :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: FCI metric value """ if real_time: raise NotImplementedError('real-time FCI data is not available') type_ = QueryType(inflection.titleize(measure.value)) start, end = DataContext.current.start_date, DataContext.current.end_date if measure == _FCI_MEASURE.REAL_FCI or measure == _FCI_MEASURE.REAL_TWI_CONTRIBUTION: ds = Dataset('FCI') df = ds.get_data(geographyId=country_id, start=start, end=end) if measure == _FCI_MEASURE.REAL_FCI: measure = 'realFCI' else: measure = 'realTWIContribution' series = ExtendedSeries(dtype=float) if (measure not in df.columns) else ExtendedSeries(df[measure]) series.dataset_ids = ('FCI',) return series q = GsDataApi.build_market_data_query([country_id], query_type=type_, source=source, real_time=real_time) df = _market_data_timed(q, request_id) return _extract_series_from_df(df, type_, True) ================================================ FILE: gs_quant/timeseries/measures_factset.py ================================================ """ Copyright 2020 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt from enum import Enum from typing import Union, Optional import pandas as pd from gs_quant.common import AssetClass, AssetType from gs_quant.data import DataContext, Dataset from gs_quant.errors import MqValueError from gs_quant.markets.securities import AssetIdentifier, Asset from gs_quant.timeseries import RelativeDate from gs_quant.timeseries.helper import plot_measure from gs_quant.timeseries.measures import ExtendedSeries class EstimateItem(Enum): EPS = 'Earnings Per Share' EPS_C = 'Consolidated EPS' EPS_P = 'Standalone Earnings Per Share' SALES = 'Sales' SALES_C = 'Consolidated Sales' SALES_P = 'Standalone Sales' DPS = 'Declared Dividends Per Share' CFPS = 'Cash Flow Per Share' PRICE_TGT = 'Target Price' EPS_LTG = 'Long Term Growth' AFFO = 'Adjusted Funds From Operations' ASSETS = 'Total Assets' BFNG = 'Net Income Reported' BPS = 'Book Value Per Share' BPS_TANG = 'Tangible Book Value Per Share' CAPEX = 'Capital Expenditure' CF_FIN = 'Cash Flow From Financing' CF_INV = 'Cash Flow From Investing' CF_OP = 'Cash Flow From Operations' CURRENT_ASSETS = 'Current Assets' CURRENT_LIABILITIES = 'Current Liabilities' DEFREVENUE_LT = 'Deferred Revenue Long Term' DEFREVENUE_ST = 'Deferred Revenue Short Term' DEP_AMORT = 'Depreciation and Amortization' EBIT = 'EBIT' EBIT_ADJ = 'BEIT Adjusted' EBIT_C = 'Consolidated EBIT' EBIT_P = 'Standalone EBIT' EBITA = 'EBITA' EBITDA = 'EBITDA' EBITDA_ADJ = 'EBITDA Adjusted' EBITDA_REP = 'EBITDA Reported' EBITDAR = 'EBITDAR' EBITR = 'EBIT Reported' EPS_EX_XORD = 'Earnings Per Share Excluding Exceptionals' EPS_GAAP = 'Reported Earnings Per Share' EPS_NONGAAP = 'Earnings Per Share Non GAAP' EPSAD = 'Diluted Adjusted EPS' EPSRD = 'Diluted Reported EPS' FCF = 'Free Cash Flow' FCFPS = 'Free Cash Flow Per Share' FFO = 'Funds From Operations' G_A_EXP = 'General and Administrative Expense' GW_TOT = 'Total Goodwill' HEPSB = 'Headline Basic EPS' HEPSD = 'Headline Diluted EPS' INC_GROSS = 'Gross Income' INT_EXP = 'Interest Expense' INVENTORIES = 'Inventories' MAINT_CAPEX = 'Maintenance CAPEX' NDT = 'Net Debt' NET = 'Net Profit' NET_C = 'Consolidated Net Income' NET_P = 'Standalone Net Income' NET_SALES = 'Net Sales' NETBG = 'Net Profit Adjusted' ORGANICGROWTH = 'Organic Growth' PTI = 'Pre Tax Profit' PTI_C = 'Consolidated Pretax Income' PTIAG = 'Pre Tax Profit Reported' PTP_P = 'Standalone Pretax Income' PTPA = 'Pre Tax Income Adjusted' RD_EXP = 'Research and Development' REV_TOT = 'Total Revenues' S_M_EXP = 'Selling and Marketing Expense' SGA = 'Selling General and Administrative Expense' SH_EQUITY = 'Shareholders Equity' SOE = 'Stock Option Expense' TAX_EXPENSE = 'Tax Expense' TOTAL_DEBT = 'Total Debt' class EstimateStatistic(Enum): MEAN = 'Mean' MEDIAN = 'Median' HIGH = 'High' LOW = 'Low' ACTUAL = 'Actual' class EstimateBasis(Enum): ANN = 'Annual' QTR = 'Quarterly' SEMI = 'Semi annual' NTM = 'Next Twelve Months' STM = 'Second Twelve Months' class FundamentalBasis(Enum): # todo: add LTM as a choice and routing logic to LTM global datasets ANN = 'Annual' QTR = 'Quarterly' SEMI = 'Semi annual' class FundamentalBasicItem(Enum): SALES = 'SalesRevenue' NON_INT_INC = 'Non Interest Income' OPER_EXP_OTH = 'Other Operating Expense' OPER_INC = 'Operating Income' LOAN_LOSS_PROV = 'Loan Loss Provision' PTX_XORD_CHRG = 'Extraordinary Charge Pretax' PTX_INC = 'Pretax Income' MIN_INT_EXP = 'Minority Interest Expense' NET_INCOME = 'Net Income incl Discontinued Operations' CASH_ST = 'Cash Short Term Investments' CASH_DUE_FR_BK = 'Total Cash Due from Banks' PREM_RECEIV = 'Premium Balance Receivables' INVEN = 'Inventories' ASSETS_OTH_INTANG = 'Other Assets Intangible' ASSETS = 'Total Assets' LIABS_CURR = 'Total Current Liabilities' PAY_ACCT = 'Accounts Payable' INS_LIABS_POL = 'Insurance Policy Liabilities Insurance' PROV_RISK = 'Provision for Risks Charges' LIABS_XMIN_INT_ACCUM = 'Liabilities excl Minority Interest' COM_EQ = 'Common Equity Total' DEBT_LT_CONV = 'Convertible Debt' TIER1_CAP = 'Tier 1 Capital' INT_PAY = 'Interest Payable' SECS_INVEST = 'Investment Securities' DEP_EXP_CF = 'Depreciation Depletion Amortization Cash Flow' NON_CASH = 'Other Funds' XORD_CF = 'Extraordinary Item' ACQ_BUS_CF = 'Net Assets from Acquisitions' LOAN_DECR_CF = 'Decrease in Loans' INVEST_ACTIV_CF = 'Investing Activities Other Uses' DIV_CF = 'Cash Dividends Paid' DEPS_INCR_CF = 'Increase in Deposits' CHG_CASH_CF = 'Net Change in Cash' EBIT_OPER_PS = 'Operating Profit Per Share' BPS = 'Book Value Per Share' COM_SHS_OUT_EPS_DIL = 'Common Shares Used to Calculate EPS Fully Diluted' PRICE_CLOSE_FP = 'Market Price Fiscal Period Close' ACTG_STANDARD = 'Code indicating the accounting standards that the company follows' CONSOL_NET_INC = 'Consolidated Net Income' INCR_FED_HOME_ADV_CF = 'Federal Home Loan Advances Increase Decrease' COM_SHS_OUT_EPS = 'Common Shares Used to Calculate EPS' NOTES_RECEIV_LT = 'Long Term Note Receivable' DFD_TAX = 'Deferred Taxes' LIABS_SHLDRS_EQ = 'Liabilities Stockholders Equity Total' INVEST_AFF = 'Investment in Unconsolidated Subsidiaries' INVEST_RE = 'Real Estate Assets' RSRV_NONEQ = 'Non Equity Reserves' RECEIV_ST = 'Total Accounts Receivable Short Term' DPS_GROSS = 'Dividends Per Share Gross' RSRV_CHG = 'Reserves Increase Decrease' DFD_TAX_CR = 'Deferred Tax Liabilities' COM_EQ_RETAIN_EARN = 'Retained Earnings' INVEN_LIFO = 'Inventories LIFO Reserve' LOAN_CHG_CF = 'Proceeds from Loans' DEP_CHG_CF = 'Change in Deposits' DPS_ALL = 'Dividends Per Share Normal Extra and Special Dividends' MISC_NET_OTH = 'Other After Tax Income Expense' INT_INC = 'Interest Income' GROSS_INC = 'Gross Income' OPER_EXP_TOT = 'Operating Expenses Total' INT_EXP_TOT = 'Total Interest Expense Banks' NON_INT_EXP = 'Non Interest Expense' PTX_XORD_CR = 'Extraordinary Credit Pretax' INC_TAX = 'Income Taxes' EQ_AFF_INC = 'Equity in Earnings of Affiliates' DIV_PFD = 'Preferred Dividends' CASH_ONLY = 'Cash Only' BK_INVEST_TOT = 'Total Investments Banks' RECEIV_TOT = 'Total Accounts Receivable' ASSETS_CURR = 'Total Current Assets' INTANG = 'Intangible Assets' DEPS = 'Total Deposits' DEBT_ST = 'Short Term Debt incl Current Portion of LTD' ACCR_EXP_XPAYR = 'Accrued Expenses Excl Accrued Payroll' DEBT_LT = 'Long Term Debt' DFD_TAX_ITC = 'Deferred Taxes Investment Tax Credits' PFD_STK = 'Preferred Stock Carrying Value' DEBT = 'Total Debt' SHLDRS_EQ = 'Total Shareholders Equity' TIER2_CAP = 'Tier 2 Capital' SECS_CUSTODY = 'Securities In Custody' NET_INC_CF = 'Net Income Cash Flow' DFD_TAX_CF = 'Deferred Taxes Investment Tax Credit' FUNDS_OPER_GROSS = 'Funds from Operations' WKCAP_CHG = 'Changes in Working Capital' LOAN_INCR_CF = 'Increase in Loans' SALE_ASSETS_BUS_CF = 'Sale of Fixed Assets Businesses' INVEST_SOURCES_CF = 'Investing Activities Other Sources' DEPS_DECR_CF = 'Decrease in Deposits' FOR_EXCH_CF = 'Exchange Rate Effect' DPS = 'Dividends Per Share' EPS_REPORTED = 'Earnings Per Share As Reported' COM_SHS_OUT_EPS_BASIC = 'Common Shares Used to Calculate EPS Basic' COM_SHS_OUT = 'Common Shares Outstanding' INVEST_YLD_5YAVG = 'Yield On Investment 5 Year Average' FY_LENGTH_DAYS = 'Fiscal Period Length Days' EQ_TOT = 'Total Equity' EPS_BASIC = 'EPS Basic Before Extraordinaries' EXP_TOT = 'Total Expense' PAY_TAX = 'Income Tax Payable' MIN_INT_ACCUM = 'Accumulated Minority Interest Total' LOAN_NET = 'Net Loans' CUST_ACCEPT = 'Customer Liability on Acceptances' PPE_NET = 'Net Property Plant Equipment' DEPS_CUST = 'Total Customer Deposits' ADJ_NET_OTH = 'Other After Tax Adjustments' DIV_PAY_OUT_PS = 'Dividend Payout Per Share' DFD_TAX_DB = 'Deferred Tax Assets' RECEIV_INT = 'Interest Receivables' CASH_RESTR = 'Restricted Cash' RENT_INC = 'Rental Income' INVEST_ACTIV_OTH = 'Investing Activities Other Funds' ASSETS_NONPERF = 'Total Non Performing Assets' BK_COM_EQ_TIER1_TOT = 'Common Equity Tier 1 Total' class FundamentalBasicDerivedItem(Enum): COGS = 'Cost of Goods Sold COGS including Depreciation Amortization' INT_EXP_NET = 'Interest Expense' NET_INC_BASIC = 'Net Income Avail to Common Basic' NON_OPER_INC = 'Non Operating Income Expense' SGA_OTH_EXP = 'Selling General Admin Expense Other' OPER_INC_AFT_INT = 'Operating Income After Interest Expense' ASSETS_CURR_OTH = 'Other Current Assets' ASSETS_OTH = 'Other Assets' LIABS = 'Total Liabilities' CAPEX = 'Capital Expenditures Total' INVEST_USES_CF = 'Other Uses' STK_CHG_CF = 'Change in Capital Stock' FIN_ACTIV_OTH_CF = 'Other Funds Financing Cashflow' MISC_FUNDS_CF = 'Miscellaneous Funds' EPS_DIL_BEF_UNUSUAL = 'EPS Diluted Before Unusual Expense' COM_EQ_ASSETS = 'Common Equity Total Assets' DEBT_ASSETS = 'Total Debt Total Assets' GROSS_MGN = 'Gross Profit Margin' NET_MGN = 'Net Margin' PAY_OUT_RATIO = 'Dividend Payout Earnings Total Dollar' ROA = 'Return On Average Assets' ROTC = 'Return On Average Total Capital' NET_DEBT = 'Net Debt' INT_INC_NET = 'Net Interest Income' TURN_RATE = 'Turnover Rate' EPS_DIL = 'EPS Fully Diluted' LIABS_CURR_OTH = 'Other Current Liabilities' PBK = 'Price To Book Value Closing Price As Of Last Trading Day' PE = 'Price To Earnings Closing Price As Of Fiscal Period End' EBIT_OPER = 'EBIT Operating Income' ENTRPR_VAL = 'Enterprise Value Using Diluted Shares' FREE_CF_MINUS_DIV = 'Free Cash Flow to Equity Minus Dividends' EARN_PERST = 'Earnings Persistence' UNUSUAL_EXP = 'Unusual Expense' NET_INC = 'Net Income' INT_INC_AFT_PROV = 'Net Interest Income after Loan Loss Provision' LOSS_CLAIM_RSRV = 'Losses Claims Reserves' OPER_INC_BEF_INT = 'Operating Income Before Interest Expense' ASSET_TURN = 'Asset Turnover' INVEST_ADV = 'Total Investments and Advances' LIABS_OTH = 'Other Liabilities' OPER_CF = 'Net Operating Cash Flow' INVEST_PURCH_SALE_CF = 'PurchaseSale of Investments' INVEST_CF = 'Net Investing Cash Flow' DEBT_CF = 'IssuanceReduction of Debt Net' FIN_CF = 'Net Financing Cash Flow' FREE_CF_FCFE = 'Free Cash Flow to Equity' CAPEX_SALES = 'Capital Expenditure Total Sales' CURR_RATIO = 'Current Ratio' EBITDA_OPER = 'EBITDA' MKT_VAL = 'Market Value using Period End Price' OPER_MGN = 'Operating Margin' QUICK_RATIO = 'Quick Ratio' ROE = 'Return On Average Total Equity' TCAP = 'Total Capital' PTX_MGN = 'Pretax Margin' INT_MGN = 'Net Interest Margin' BPS_TANG = 'Book Value Per Share Tangible' SGA = 'Selling General Administrative Expense' SALES_PS = 'Sales Per Share' PCF = 'Price to Cash Flow Ratio Closing Price as of Fiscal Period End' PSALES = 'Price To Sales Closing Price As Of Last Trading Day' NET_INC_AFT_XORD = 'Net Income After Extraordinary Items' class FundamentalAdvancedItem(Enum): ACCR_EXP_CF = 'Other Accruals' AMORT_CF = 'Amortization of Intangible Assets' AMORT_INTANG = 'Amortization of Intangibles' ASSETS_OTH_TANG = 'Tangible Other Assets' AUD_FEES = 'Auditor Fees' BONDS_BELOW_INVGR = 'Bonds Below Investment Grade' COM_EQ_APIC = 'Additional Paid In Capital Capital Surplus incl Deferred Compensation' COM_EQ_GL = 'Unrealized GainLoss Marketable Securities' COM_EQ_OTH_COMPR_ADJ_OTH = 'Accumulated Other Comp Income Other Adjustments' COM_EQ_UNEARN_COMP = 'Comprehensive Income Unearned Compensation' COM_SHS_OUT_SECS = 'Common Shares Outstanding Security' COM_SHS_TRADE_WK = 'Common Shares Traded Weekly' COMMISS_INC = 'Commission Fee Income' CURN_DOC = 'Code representing the currency in which the companys financial statements are presented' DEBT_LT_NON_CONV = 'Non Convertible Debt' DEP_EXP = 'Depreciation' DEP_EXP_XAMORT_CF = 'DepreciationDepletion excl Amortization' DEPS_FOR = 'Foreign Office Deposits' DEPS_UNSPEC = 'Unspecified Deposits' DFD_TAX_ASSETS_ST = 'Deferred Income Tax Current Asset' DISC_OPER_INC = 'Discontinued Operations Income' DIV_PFD_CF = 'Preferred Dividends Cash Flow' DPS_SECS = 'Dividends Per Share Security' EPS = 'Earnings Per Share Fiscal Period' EPS_CONTIN_OPER = 'Earnings Per Share from Continuing Operations Fiscal' EPS_SECS = 'Earnings Per Share Security' EPS_XORD = 'Earnings Per Share incl Extraordinary Items Fiscal' FED_FUNDS = 'Federal Funds Sold' FOR_EXCH_BS = 'Exchange Rate Used For Translating Balance Sheet' FOR_EXCH_IS = 'Exchange Rate used for Translating Income Statement Cash Flow' GW = 'Net Goodwill' GW_GROSS = 'Goodwill Gross' INC_TAX_CURR_FOR = 'Income Tax Current Foreign' INC_TAX_DFD_FOR = 'Income Tax Deferred Foreign' INS_INVEST_INC = 'Total Investment Income Insurance' INS_LIABS_OTH = 'Other Insurance Liabilities' INS_RSRV = 'Insurance Reserves Insurance' INT_EXP_FED_REPOS = 'Interest Expense Federal Funds' INT_INC_DEPS = 'Interest Income Bank Deposits' INT_INC_FED_REPOS = 'Interest Income Government Securities' INT_INC_OTH = 'Other Interest or Dividend Income' INTANG_OTH_GROSS = 'Other Intangible Assets Gross' INVEN_FG = 'Inventories Finished Goods' INVEN_WIP = 'Inventories Work In Progress' INVEST_BONDS = 'Bonds' INVEST_EQ = 'Total Equity Securities Investment' INVEST_INC = 'Investment Income' INVEST_MTGE = 'Mortgage Policy Other Loans' INVEST_PFD_NRED = 'Non Redeemable Preferred Stock Investment' INVEST_SUB = 'Investment In Unconsolidated Subsidiaries' LABOR_EXP = 'Labor Related Expense' LIABS_PS = 'Long Term Liabilities Per Share' LOAN_BK = 'Interbank Loans' LOAN_COMML_INDL = 'Commercial Industrial Loans' LOAN_FOR = 'Foreign Loans' LOAN_LOSS_ACT = 'Net Loan Losses' LOAN_NONPERF = 'Non Performing Loans' MATL_EXP = 'Material Expense' MIN_PENS_LIABS_ADJ = 'Minimum Pension Liability Adjustment' NET_CAP_REQUIRE = 'Net Capital Requirement' OPER_EXP = 'Total Operating Expense Financial Services' OPER_INC_INTL = 'International Operating Income' OPT_WRNT_CF = 'Proceeds from Stock Options' PAY_ACCT_CF = 'Accounts Payable Cash Flow' PAY_TAX_DFD_TAX = 'Income Tax Payable Plus Short Term Deferred Tax' PFD_STK_ESOP_GTD = 'ESOP Guarantees Preferred Stock' PFD_STK_RED = 'Redeemable Preferred Stock' PPE_DEP_BLDG = 'Accumulated Depreciation Buildings' PPE_DEP_EQUIP = 'Accumulated Depreciation Machinery Equipment' PPE_DEP_LEASED_PROP = 'Accumulated Depreciation Leased Property' PPE_DEP_OTH = 'Accumulated Depreciation Other Property Plant Equip' PPE_DEP_TRANS_EQUIP = 'Accumulated Depreciation Transportation Equipment' PPE_GROSS_CONSTR = 'Property Plant Equipment Construction in Progress' PPE_GROSS_LAND = 'Property Plant Equipment Land Improvements' PPE_GROSS_LEASES = 'Property Plant Equipment Leases' PPE_GROSS_SOFT_EQUIP = 'Property Plant Equipment Computer Software and Equipment' PREM_UNEARN = 'Unearned Premiums' REAL_GAIN = 'Realized Securities Gain' RECEIV_NET = 'Accounts Receivables Net' RESTRUCT_PS = 'Restructuring Exp Per Share Net Of Tax Fiscal' RSRV_APPR_OTH = 'Other Appropriated Reserves' RSRV_UNAPPR = 'Unappropriated Free Reserves' SALES_UNCON = 'UnconsolidatedParent Company Sales' SECS_GAIN = 'Securities Gain' SECS_OTH = 'Other Securities' SECS_TREAS = 'Treasury Securities' SHS_CLOSELY_HELD_PCT = 'Closely Held Shares' STK_SPLIT_RATIO = 'Stock SplitDividend Ratio' TOT_INVEST_RET = 'Total Investment Return' TRADE_INC = 'Trading Account Income' TREAS_STK = 'Treasury Stock Common incl ESOP' TRUST_INC = 'Trust Income' VOLUME_TRADE = 'Trading Volume' WKCAP_ASSETS_OTH = 'Other AssetLiabilities' XORD_PS = 'Extraordinary CreditCharge Per Share' CASH_GENERIC = 'Cash Equivalents Generic' REINS_ADJ_RSRV = 'Reinsurance Adjustment Reserves' COMPR_INC_PENS_LIABS = 'Comprehensive Income Pension Liability' GW_IMPAIR = 'Impairment Of Goodwill' OPER_LEASE_EXP = 'RentalOperating Lease Expense' PENS_ASSETS_BONDS = 'Pension Plan Asset Allocation Bonds' PENS_ASSETS_LT = 'Pension Long Term Assets' PENS_ASSETS_PROP = 'Pension Plan Asset Allocation Property' PENS_BNFIT_OBLIG_PROJ = 'Pension Projected Benefit Obligation' PENS_COST_INT = 'Pension Interest Cost' PENS_COST_SERV_PRIOR = 'Pension Prior Service Costs' PENS_DISCOUNT_RATE = 'Pension Discount Rate' PENS_EXP_NPP_NET_OTH = 'Pension Other Periodic Pension IncomeExpense' PENS_GAIN_SETTLE = 'Pension GainsLosses from Settlements' PENS_LIABS_NET = 'Pension Net LiabilityAsset' PENS_ROA = 'Pension Expected Return on Plan Assets for Period' PENS_ROA_LT = 'Pension Expected Long Term Rate of Return on Assets' PPE_IMPAIR = 'Property Plant Equipment Impairment' ITC = 'Income Tax Credits' CAP_RATIO_TIER1 = 'Capital Adequacy Ratio Tier 1 Capital' COMP_SOFT = 'Computer Software' OPER_LEASE_COMMIT_5YR = 'Lease Commitments Over 5 Years' OPER_LEASE_COMMIT_YR2 = 'Lease Commitments Year 2' OPER_LEASE_COMMIT_YR4 = 'Lease Commitments Year 4' STK_OPT_EXP = 'Stock Option Compensation Expense' PRICE_SECS = 'Market Price Year End' PBK_SECS = 'Price to Book Security Level' BPS_SECS = 'Books Value Per Share Security Level' HLTH_FUNDED_STATUS = 'Healthcare Data Funded Status' DEBT_BONDS_CONV_SR = 'Bonds Senior Convertible' DEBT_BONDS_CONV_SUBORD_JR = 'Bonds Junior Subordinated Convertible' DEBT_BONDS_COVERED = 'Bonds Covered' DEBT_BONDS_MTGE1 = 'Bonds First Mortgage' DEBT_BONDS_OTH = 'Bonds Other Borrowings' DEBT_BONDS_SECURE_LIEN2 = 'Bonds Sec 2nd Lien' DEBT_BONDS_SUBORD = 'Bonds Subordinated' DEBT_BONDS_SUBORD_SR = 'Bonds Senior Subordinated' DEBT_CDO = 'Collateralized Debt Obligations' DEBT_LOAN_ADV_FFCB = 'FFCB Advances' DEBT_LOAN_FACIL_DIP = 'Term Loans DIP Facility' DEBT_LOAN_OTH = 'Loans Other Borrowings' DEBT_LOAN_SECURE_LIEN1 = 'Term Loans Sec 1st Lien' DEBT_LOAN_SECURE_LIEN3 = 'Term Loans Sec 3rd Lien' DEBT_LOAN_TOT = 'Total Term Loans' DEBT_LOAN_UNSEC_DELAY_DRAW = 'Term Loans Unsecured Delayed Drawdown' DEBT_LT_CONV_PFD = 'Preferreds Convertible LT Debt' DEBT_LT_REVOLV_FACIL_AB = 'Asset Backed Facility' DEBT_LT_REVOLV_SECURE = 'Revolvers Sec Line' DEBT_LT_REVOLV_SECURE_CURR = 'Short Term Revolver LT Debt Secured' DEBT_LT_REVOLV_TOT = 'Total Revolving Credit' DEBT_LT_REVOLV_UNSECURE_CP = 'Commercial Paper LT Debt Unsecured' DEBT_LT_REVLV_UNSEC_FCL_OTH = 'Revolvers Other Unsecured Facility' DEBT_NOTES_BONDS_TOT = 'Total NotesBonds' DEBT_NOTES_SURPLUS_SUBORD = 'Bonds Subordinate Surplus Notes' DEBT_OTH_CAPL = 'Capital Leases' DEBT_OTH_TOT = 'Total Other Debt' DEBT_RECEIV_CR_CARD = 'Credit Card Receivables' DEBT_RECEIV_MTGE_COMML = 'Commercial Mortgage Receivables' DEBT_RECEIV_OTH = 'Other Receivables' DEBT_REVOLV_UNSECURE_CURR = 'Short Term Borrowings from LT Unsecured Revolvers' DEBT_ST_ADJ = 'Adjustments Short Term' DEBT_ST_ADV_FHLB = 'ST FHLB Advances' DEBT_ST_CURR_PORT = 'Current Portion of LTD' DEBT_ST_NOTES_PAY = 'Notes Payable' DEBT_ST_REPOS = 'Repurchase Agreements' DEBT_ST_SECURE_OTH = 'Other Borrowings Secured' DEBT_ST_STN_BANK = 'Bank STNs' DEBT_ST_UNSECURE_OTH = 'Other Borrowings Unsecured' DEBT_TRUST_PFD = 'Preferreds Trust' IMPAIR_PPE = 'Fixed Asset Impairment' XCEPT_PROV = 'Exceptional Provisions' GW_WDOWN = 'Goodwill Write Off' IMPAIR_INTANG_OTH = 'Other Intangible Asset Impairment' OTH_UNUSUAL_EXP = 'Other Unusual Expense' INT_INC_NON_OPER = 'Non Operating Interest Income' MISC_EXP_NON_OPER = 'Other Income Expense' INT_CAP = 'Interest Capitalized' PREP_EXP = 'Prepaid Expenses' PPE_GROSS = 'Property Plant Equipment Gross' PAY_DIV = 'Dividends Payable' LIABS_CURR_MISC = 'Miscellaneous Current Liabilities' INVEST_SALE_CF = 'SaleMaturity of Investments' STK_PURCH_CF = 'Repurchase of Common Preferred Stock' DEBT_ST_CF = 'Change in Current Debt' CAPEX_OTH = 'Capital Expenditures Other Assets' DEBT_LT_REDUCT_CF = 'Reduction of LT Debt' FIN_SOURCES_CF = 'Other Financing Activities Sources' DFD_INC = 'Deferred Income' INVEST_PURCH_CF = 'Purchase of Investments' DEP_AMORT_EXP = 'Depreciation Amortization' OPER_INC_OTH = 'Other Operating Income' ORDINARY_INC = 'Ordinary Income Japan' HLTH_PBO = 'Healthcare Projected Benefit Obligation' HLTH_UNREC_SERV_COST = 'Healthcare Unrecognized Prior Service Cost' HLTH_ROA_EXPECTED = 'Healthcare Expected Return on Plan Assets' HLTH_ROA_LT = 'Healthcare Expected Long Term Return Rate on Pension Assets' HLTH_INT_COST = 'Healthcare Interest Cost' HLTH_GL_SETTLE = 'Healthcare GainLoss from Settlements' HLTH_NPP_EXP = 'Health Care Expense' DEBT_ST_LT_TOT = 'Total Short Term and Long Term Debt' DEBT_ST_TOT = 'Total Short Term Debt' LOAN_LOSS_RECOV = 'Loan Losses Recoveries' PREM_EARN = 'Premiums Earned' STK_OPT_CF = 'Stock Based Compensation Cash Flow' PENS_ASSETS_BEG_BAL = 'Beg Balance Assets' PENS_ASSETS_INT_INC = 'Interest IncomeExpected Return on Pension Assets' PENS_BNFIT_GL_ACTRL = 'Actuarial LSGN' PENS_ASSETS_CONTR = 'Contributions' PENS_BNFIT_PTCP_CONTR = 'Benefits Participant Contributions' PENS_BNFIT_PAID = 'Benefits Paid' PENS_BNFIT_ACQ_DVST = 'Pension Benefits AcquisitionDivestiture' PENS_EXCH_RATE_DIFF = 'Pension Assets Exchange Rate Difference' PENS_BNFIT_EXCHRATE_DIFF = 'Benefits Exchange Rate Diff' PENS_BNFIT_OTH_CHG = 'Other Benefits Changes' PENS_ASSETS_TOTAL = 'Asset Allocation Total' PENS_BNFT_SRVCST_PRIOR_CURR = 'Pension Benefits CurrentPrior Service Cost' PENS_BNFIT_SERVCOST_PRIOR = 'Benefits Prior Service Cost' PENS_BNFIT_INT_COST = 'Benefits Interest Cost' APBO_ALLOC_CCE = 'APBO Allocation Cash Cash Equivalents' APBO_ALLOC_DEBT = 'APBO Asset Allocation Debt' APBO_ALLOC_OTH = 'APBO Asset Allocation Other' HLTH_CONTR = 'APBO Contributions' HLTH_PTCP_CONTR = 'APBO Health Participant Contributions' HLTH_BNFIT_PAID = 'APBO Health Benefits Paid' HLTH_ACQ_DVST = 'APBO Health AcquisitionDivestiture' HLTH_CRTL_SETTLE_AMEND = 'APBO Health CurtailSettleAmend' HLTH_EXCH_RATE_DIFF = 'APBO Health Exchange Rate Difference' HLTH_OTH_CHG = 'APBO Health Other Changes' HLTH_ACTRL_GL = 'APBO Health Actuarial GNLS' HLTH_ACT_RET_ASSET = 'APBO Actual Return Assets' HLTH_APBO_SRVCST_PRIOR_CURR = 'APBO CurrentPrior Service Cost' HLTH_APBO_SERVCOST_PRIOR = 'APBO Prior Service Cost' HLTH_SERV_COST_TOT = 'APBO Service Cost Total' HLTH_GL_ACTRL = 'Healthcare Actuarial LSGN' UNREAL_GL_TOT = 'Unrealized Investment GainLoss Total' DFD_INC_CURR = 'Deferred Income Current' TAX_CHG_EFF = 'Effect of Tax Code Changes' GA_EXP = 'General Administrative Expense' MKT_EXP = 'Marketing Expense' LIABS_OPER_LEASE_CURR = 'Operating Lease Liabilities Current Portion' PPE_NET_OWNED = 'Property Plant and Equipment Owned net' DEBT_LT_XLEASE = 'Long Term Debt Excluding Lease Liabilities' AMORT_EXP_LEASE = 'Amortization of Leased Assets' FRANK_BAL_CF = 'Franking Balance for Australian Companies' OPER_LEASE_COMMIT_2YR_5YR = 'Operating Lease Commitments 2 to 5 Years' DIR_FIN_LEASE = 'Sales and Direct Financing Leases' ACCEL_DEP = 'Accelerated Depreciation' EARLY_TERM_CONTRACT = 'Early Termination of Contracts' UNREAL_GL_PROP = 'Unrealized GainLoss Investment Property' UNREAL_GL_DERIV = 'Unrealized GainLoss HedgesDerivatives' UNREAL_GL_OTH = 'Unrealized GainLoss Other Intangibles' ACTG_CHG_PS = 'Accounting Change Per Share Cum Effect Fiscal' AMORT_DFD_CHRG = 'Amortization of Deferred Charges' ASSETS_INTL = 'International Assets' ASSETS_SEP_ACCTS = 'Separate Variable Account Assets' BDEBT = 'Bad Debt Doubtful Accounts' CAP_LEASE = 'Capitalized Lease Obligations' COM_EQ_FOR_EXCH = 'Cumulative Translation Adjustment' COM_EQ_HEDG_GL = 'Comprehensive Income Hedging GainLoss' COM_EQ_PAR = 'Common Stock ParCarry Value' COM_SHS_AUTH = 'Number Of Shares Authorized' COM_SHS_TRADE = 'Shares Total Common Traded' COM_STK_ESOP = 'ESOP Debt Guarantee' COST_DEBT = 'Weighted Cost Of Debt' DEBT_LT_CURR = 'Current Portion Of Long Term Debt' DEBT_LT_XCAP = 'Long Term Debt Excluding Capitalized Leases' DEP_EXP_UNCON = 'UnconsolidatedParent Company Depreciation' DEPS_DEMAND = 'Demand Deposits' DEPS_SAV = 'SavingsTime Deposits' DFD_CHRG = 'Deferred Charges' DFD_TAX_XITC_CF = 'Deferred Taxes excl Investment Tax Credits' DISC_OPER_PS = 'Discontinued Operations Per Share Total' EMP_NUM = 'Number of employees' EPS_AFT_XORD = 'Earnings Per Share After Extraordinary Items' EPS_HEADLINE_UK = 'Headline Earnings Per Share UK' EPS_UNCON = 'UnconsolidatedParent Company Earnings Per Share' EQUIP_EXP = 'Equipment Expense' FOR_CURN_ADJ = 'Foreign Currency Adjustment Net' FOR_EXCH_INC = 'Foreign Exchange Gains' GAIN_SALE_ASSETS_NET = 'GainsLoss On Disposal Of Assets' GW_AMORT = 'Accumulated Goodwill Amortization' INC_TAX_CURR_DOM = 'Income Tax Current Domestic' INC_TAX_DFD_DOM = 'Income Tax Deferred Domestic incl local' INC_UNEARN = 'Unearned Income' INS_INVEST_TOT = 'Total Investment Assets Insurance' INS_LT_RSRV = 'Long Term Insurance Reserves' INT_EXP_DEPS = 'Interest Expense On Bank Deposits' INT_EXP_OTH_BORR = 'Other Borrowed Funds' INT_INC_FED_FUNDS = 'Interest Income Federal Funds' INT_INC_LOAN = 'Interest Income Loans' INTANG_OTH_AMORT = 'Accumulated Other Intangible Amortization' INVEN_CF = 'Inventories Cash Flow' INVEN_MATL = 'Inventories Raw Materials' INVEST = 'Investments Total' INVEST_COM_EQ = 'Common Stocks' INVEST_FIX_INC = 'Total Fixed Income Securities Investment' INVEST_LT_OTH = 'Other Investments Long Term' INVEST_OTH = 'Other Investments' INVEST_PFD_RED = 'Redeemable Preferred Stock Investment' ITC_CF = 'Investment Tax Credits' LIABS_CURR_DFD_TAX = 'Short Term Deferred Income Tax' LIFE_INS = 'Life Insurance In Force' LOAN_BRKR = 'Broker Financial Institution Loans' LOAN_CONS = 'Consumer Installment Loans' LOAN_LEASE_FIN = 'Lease Financing Loans' LOAN_MTGE = 'Real Estate Mortgage Loans' LOAN_OTH = 'UnspecifiedOther Loans' MBS = 'Mortgage Backed Securities' MON_CORRECT = 'Monetary Correction' NUM_SHRHLDRS = 'Number of Shareholders' OPER_INC_BEF_DEP = 'Operating Income Before Depreciation And Amortization' OPER_PROV = 'Operating Provisions' PAR_PS = 'Par Value' PAY_TAX_CF = 'Income Taxes Payable' PFD_STK_ESOP = 'Preferred Stock Issues for ESOP' PFD_STK_NRED = 'Non Redeemable Preferred Stock' POL_CLAIMS = 'Policy Claims' PPE_DEP_CONSTR = 'Accumulated Depreciation Construction in Progress' PPE_DEP_LAND = 'Accumulated Depreciation Land Improvements' PPE_DEP_LEASES = 'Accumulated Depreciation Leases' PPE_DEP_SOFT_EQUIP = 'Accumulated Depreciation Computer Software and Equipment' PPE_GROSS_BLDG = 'Property Plant Equipment Buildings' PPE_GROSS_EQUIP = 'Property Plant Equipment Machinery Equipment' PPE_GROSS_LEASED_PROP = 'Property Plant Equipment Leased Property' PPE_GROSS_OTH = 'Other Property Plant Equipment' PPE_GROSS_TRANS_EQUIP = 'Property Plant Equipment Transportation Equipment' PREM_WRITTEN = 'Net Premiums Written' RECEIV_CF = 'Accounts Receivable' RESTRUCT_EXP = 'Restructuring Expense' RESTRUCT_PS_PTAX = 'Restructuring Expense Per Share Pretax' RSRV_REVAL = 'Revaluation Reserves' SALES_INTL = 'International Sales' SECS_FED = 'Federal Agency Securities' SECS_MUNI = 'State Municipal Securities' SECS_RESALE = 'Securities Bought Under Resale Agreements' SHS_CLOSELY_HELD = 'Shares Number Of Closely Held' STK_SALE_XOPT_CF = 'Other Proceeds from SaleIssuance of Stock' TAX_NON_INC = 'Taxes Other than Income Taxes' TRADE_ACCT = 'Trading Account Securities' TREAS_SHS = 'Treasury Shares Number of Common Reacquired' TRUST_COMMISS_INC = 'Trust Fiduciary Income Commissions Fees' US_GAAP_AVAIL = 'US Information Available GAAP' VOLUME_WK_AVG = 'Trading Volume Weekly Average' WKCAP_PS = 'Working Capital Per Share' XORD_PS_PTAX = 'Extraordinary CreditCharge Per Share Pretax' ASSETS_RISK_WGHT = 'Risk Weighted Assets' INVEN_PROG_PAYMT = 'Inventories Progress Payments Other' FIX_ASSETS_IMPAIR = 'Impairment Of Financial Fixed Assets' INTANG_OTH_IMPAIR = 'Impairment Of Other Intangibles' RESTATE_IND = 'Flag indicating whether restated data exists' PENS_ABO = 'Pension Accumulated Benefit Obligation' PENS_ASSETS_EQ = 'Pension Plan Asset Allocation Equities' PENS_ASSETS_OTH = 'Pension Plan Asset Allocation Other' PENS_ASSETS_VAL = 'Pension Fair Value of Plan Assets' PENS_BNFIT_RETIR_POST = 'PensionPost Retirement Benefits' PENS_COST_SERV = 'Pension Service Costs' PENS_COST_SERV_PRIOR_UNREC = 'Pension Unrecognized Prior Service Cost' PENS_EXP_NPP_NET = 'Pension Expense' PENS_FUND_ADJ_OTH = 'Pension Other Adjustments To Funded Status For Net PensionPost' PENS_GAIN_UNREC = 'Pension Unrecognized Net Actuarial GainLoss' PENS_LIABS_UNFUNDED = 'Unfunded Pension Liability Supplementary' PENS_ROA_ACT = 'Pension Actual Return on Plan Assets' PENS_FUNDED_STATUS = 'Pension Funded Status' TAX_CF = 'Cash Taxes Paid' ACTG_CHG = 'Cumulative Effect of Accounting Change' CAP_RATIO_TOT = 'Capital Adequacy Ratio Total Capital' INT_CF = 'Interest Paid Cash Flow' OPER_LEASE_COMMIT_YR1 = 'Lease Commitments Year 1' OPER_LEASE_COMMIT_YR3 = 'Lease Commitments Year 3' OPER_LEASE_COMMIT_YR5 = 'Lease Commitments Year 5' STK_OPT_EXP_ADJ = 'Stock Option Based Compensation Exp Adjusted To Net Income' MKT_VAL_SECS = 'Market Value Security Level' PE_SECS = 'Price to Earnings Security Level' HLTH_BNFIT_UNFUNDED = 'Healthcare Data Unfunded Defined Benefits' DEBT_BONDS_CONV_JR = 'Bonds Junior Convertible' DEBT_BONDS_CONV_SUBORD = 'Bonds Subordinated Convertible' DEBT_BONDS_CONV_SUBORD_SR = 'Bonds Senior Subordinated Convertible' DEBT_BONDS_JR = 'Bonds Junior' DEBT_BONDS_MUNI = 'Bonds Municipal Taxable' DEBT_BONDS_SECURE_LIEN1 = 'Bonds Sec 1st Lien' DEBT_BONDS_SECURE_LIEN3 = 'Bonds Sec 3rd Lien' DEBT_BONDS_SUBORD_JR = 'Bonds Junior Subordinated' DEBT_BONDS_UNSECURE_SR = 'Bonds Senior Unsecured' DEBT_GTD_FDIC = 'FDIC Guaranteed Debt TLGP' DEBT_LOAN_ADV_FHLB = 'FHLB Advances' DEBT_LOAN_MTGE = 'Mortgage Loans' DEBT_LOAN_SECURE_DELAY_DRAW = 'Term Loans Sec Delayed Drawdown' DEBT_LOAN_SECURE_LIEN2 = 'Term Loans Sec 2nd Lien' DEBT_LOAN_SECURE_PLACE_PRIV = 'Private Placement Notes Secured' DEBT_LOAN_UNSECURE = 'Term Loans Unsecured' DEBT_LOAN_UNSEC_PLACE_PRIV = 'Private Placement Notes Unsecured' DEBT_LT_REPOS = 'Repurchase Agreement LT Debt' DEBT_LT_REVOLV_FACIL_DIP = 'Revolvers DIP Facility' DEBT_LT_REVOLV_SECURE_CP = 'Commercial Paper LT Debt Secured' DEBT_LT_REVLV_SEC_FACIL_OTH = 'Revolvers Other Sec Facility' DEBT_LT_REVOLV_UNSECURE = 'Revolvers Unsecured' DEBT_LT_REVLV_UNSEC_CURR = 'Short Term Revolver LT Debt Unsecured' DEBT_LT_STRAIGHT_PFD = 'Preferreds Straight LT Debt' DEBT_NOTES_SURPLUS_SR = 'Bonds Senior Surplus Notes' DEBT_OTH_ADJ = 'Adjustments Other' DEBT_OTH_LT_CURR = 'Current Portion of Other LTD' DEBT_RECEIV_AUTO = 'Auto Receivables' DEBT_RECEIV_EQUIP_TRUST = 'Equipment Trust Receivables' DEBT_RECEIV_MTGE_RES = 'Residential Mortgage Receivables' DEBT_REVOLV_SECURE_CURR = 'Short Term Borrowings from LT Sec Rev' DEBT_SEC_FACIL_SWING_SBRD = 'Swing Line Sub Facility Secured' DEBT_ST_ADV_FFCB = 'ST FFCB Advances' DEBT_ST_BORR_FED_RSRV = 'Federal Reserve Borrowings TAF' DEBT_ST_GTD_FDIC_CP = 'Commercial Paper FDIC Gtd TLGP' DEBT_ST_PURCH_FED_FUNDS = 'Fed Funds Purchased' DEBT_ST_SECURE_ABS_CP = 'Commercial Paper Secured ABS' DEBT_ST_SECURE_REVOLV = 'Short Term Revolver Secured' DEBT_ST_UNSECURE_CP = 'Commercial Paper Unsecured' DEBT_ST_UNSECURE_REVOLV = 'Short Term Revolver Unsecured' DEBT_UNSEC_FACIL_SWING_SBRD = 'Swing Line Sub Facility Unsecured' FIN_ASSETS_IMPAIR = 'Financial Asset Impairment' REORG_RESTRUCT_EXP = 'Reorganization and Restructuring Expense' LEGAL_CLAIM_EXP = 'Legal Claim Expense' UNREAL_INVEST_GL = 'Unrealized Investment GainLoss' DISC_OPER = 'Discontinued Operations' EQ_AFF_INC_PTX = 'Pretax Equity in Earnings of Affiliates' INT_EXP_DEBT = 'Gross Interest Expense' XORD_ITEMS = 'Extraordinary GainsLosses from Sale of Asset' ASSETS_CURR_MISC = 'Miscellaneous Current Assets' PPE_DEP = 'Accumulated Depreciation' ACCR_PAYR = 'Accrued Payroll' DFD_TAX_RSRV = 'Deferred Tax Liability Untaxed Reserves' DIV_COM_CF = 'Common Dividends' STK_SALE_CF = 'Sale of Common Preferred Stock' CAPEX_FIX = 'Capital Expenditures Fixed Assets' DEBT_LT_ISS_CF = 'Issuance of LT Debt' FIN_USES_CF = 'Other Financing Activities Uses' LIABS_OTH_XDFD_REV = 'Other Liabilities excluding Deferred Revenue' LOAN_RSRV = 'Loan Loss Allowances Reserves' COGS_XDEP = 'COGS excluding DA' RD_EXP = 'Research Development' INVEST_ST_OTH = 'Other Short Term Investments' HLTH_ASSETS_PLAN_FAIR_VAL = 'Healthcare Fair Value of Plan Assets' HLTH_ABO = 'Healthcare Accumulated Benefit Obligation' HLTH_ROA_ACT = 'Healthcare Actual Return on Plan Assets' HLTH_DISCOUNT_RATE = 'Healthcare Discount Rate' HLTH_SERV_COST = 'Healthcare Service Costs' HLTH_AMORT_SERV_COST = 'Healthcare Amortization of Prior Service Costs' HLTH_OTH_NPP_EXP = 'Healthcare Net Periodic Plan Expense Other' DEBT_LT_NET = 'Net Long Term Debt' DEBT_LT_TOT = 'Total Long Term Debt' LOAN_LOSS_NET = 'Loan Losses Net' LOSS_CLAIM_EXP = 'Losses Benefits Adjustments' UNDERWRITING_EXP = 'Underwriting Commissions' PENS_BNFIT_PBO_BB = 'Beg Balance PBO' PENS_ASSETS_ACTRL_GL = 'Actuarial GNLS' PENS_ASSETS_ACT_RET = 'Actual Return Assets' PENS_ASSETS_EMPLR_CONTR = 'Assets Employer Contributions' PENS_ASSETS_BNFIT_PAID = 'Assets Benefits Paid' PENS_ASSETS_ACQ_DVST = 'Pension Assets AcquisitionDivestiture' PENS_ASSETS_AMEND = 'Pension Assets CurtailmentSettlementAmendment' PENS_COST_EXCH_RATE_DIFF = 'Pension Benefit Cost Exchange Rate Difference' PENS_ASSETS_OTH_CHG = 'Other Changes' PENS_BNFIT_SETTLE = 'CurtailmentSettlement' PENS_ASSETS_CCE = 'Asset Allocation CCE' PENS_BNFIT_SERVCOST_CURR = 'Pension Benefits Current Service Cost' PENS_COST_SERV_TOT = 'Service Cost Total' PENS_COST_FIN = 'Finance CostIncome' APBO_ALLOC_EQ = 'APBO Asset Allocation Equity' APBO_ALLOC_PROP = 'APBO Asset Allocation Property' APBO_ALLOC_TOT = 'APBO Asset Allocation Total' HLTH_EMPLR_CONTR = 'APBO Health Employer Contributions' HLTH_APBO_PTCP_CONTR = 'APBO Participant Contribution' HLTH_APBO_BNFIT_PAID = 'APBO Benefits Paid' HLTH_APBO_ACQ_DIV = 'APBO AcquisitionDivestiture' HLTH_APBO_CURTAILM_SETTLEM = 'APBO CurtailmentsSettlements' HLTH_APBO_EXCH_RATE_DIFF = 'APBO Exchange Rate Difference' HLTH_APBO_OTH_CHG = 'APBO Other Changes' HLTH_APBO_ACTRL_GL = 'APBO Actuarial LSGN' HLTH_APBO_LIAB_BB = 'APBO Beg Balance Liabilities' HLTH_APBO_SERVCOST_CURR = 'APBO Current Service Cost' HLTH_APBO_INT_COST = 'APBO Interest Cost' HLTH_FIN_COST = 'APBO Finance CostInc' HLTH_PERIOD_EXCH_RATE_DIFF = 'APBO Exchange Rate Difference Period' ADV_EXP = 'Advertising Expense' SALE_PPE_CF = 'Sale of Property Plant and Equipment' ACQ_PROCESS_RD = 'Acquired In Process RD' SELL_EXP = 'Selling Marketing Expense' LIABS_OPER_LEASE = 'Operating Lease Liabilities' ASSETS_LEASE_NET = 'Operating Lease Right of Use Assets' DEBT_ST_XOPER = 'Short Term Debt and Current Portion of Long Term Debt Excluding Lease Liabilities' DEBT_LT_XOPER = 'Long Term Debt excl Operating Lease Liabilities' INT_EXP_LEASE = 'Interest Expense on Leased Assets' ASSETS_NONPERF_OTH = 'Other Non Performing Assets' OPER_LEASE_COMMIT_TOT = 'Total Operating Lease Commitments' DEBT_OTH_CF = 'Other Cash Flow Debt' CALAMITOUS_EVENT = 'Calamitous Event' EPS_HEADLINE_UK_DIL = 'Diluted Headline Earnings Per Share' UNREAL_GL_BIO_ASSETS = 'Unrealized GainLoss Biological Assets' UNREAL_GL_INVEST = 'Unrealized GainLoss Investments' class FundamentalAdvancedDerivedItem(Enum): ACCR_EXP = 'Accrued Expenses' ASSETS_EQ = 'Assets To Equity End Of Period' ASSETS_OTH_TOT = 'Other Assets Total' BNFIT_LOSS_RSRV_TCAP = 'Benefit Loss Reserves Of Total Capital' CAPEX_5YGR = 'Capital Spending 5Yr Growth Rate' CAPEX_FIX_ASSETS = 'Capital Expenditure Gross Fixed Assets' CASH_CURR_ASSETS = 'Cash Equivalents Total Current Assets' CASH_DIV_COVG_RATIO = 'Cash Dividend Coverage Ratio' CASH_SECS_DEPS = 'Cash Securities Total Deposits' CF_SALES = 'Cash FlowSales' COGS_SALES = 'Cost of Goods Sold Sales' COM_EQ_GR = 'Equity 1 Year Annual Growth' DEBT_COM_EQ = 'Debt Total Common Equity' DEBT_EQ = 'Total Debt Total Equity' DEBT_ST_X_CURR_PORT = 'Short Term Debt' DEP_ACCUM_FIX_ASSETS = 'Accumulated Depreciation Gross Fixed Assets' DFD_TAX_ASSETS_LT = 'Deferred Income Tax Non Current Asset' DIV_YLD = 'Dividend Yield Close' DPS_GR = 'Dividends Per Share 1 Year Annual Growth' EARN_ASSETS_EFF = 'Efficiency Of Earning Assets' EARN_YLD = 'Earnings Yield Close' EBIT_OPER_ROA = 'Return On Average Assets EBIT' EBITDA_OPER_MGN = 'EBITDA Margin' EMP_GR = 'Employees 1 Year Annual Growth' ENTRPR_VAL_EBITDA_OPER = 'Enterprise Value To EBITDA' EPS_BASIC_GR = 'EPS Basic Before Extras Change' FED_REPOS_ASSET = 'Federal Funds Sold Securities Purchased' EBIT_OPER_FIX_CHRG_COVG = 'Fixed Charge Coverage Ratio' FOR_ASSETS_PCT = 'Foreign Assets Total Assets' FOR_INC_PCT = 'Foreign Income Total Income' FOR_ROA = 'Foreign Return On Assets' FREE_PS_CF = 'Cash Flow Per Share Diluted Free' INC_ADJ = 'Income Adjustment' INC_TAX_CURR = 'Income Tax Current Total' INS_RSRV_GR = 'Insurance Reserves 1 Year Annual Growth' INT_EXP_IB_LIABS = 'Interest Expense Total Interest Bearing Liabilities' INT_INC_AVG_DEPS = 'Net Interest Income Average Deposits' INTANG_OTH = 'Net Other Intangibles' INVEN_DAYS = 'Inventories Days Held' INVEST_ASSETS_DEPS = 'Invested Assets Total Deposits' INVEST_ASSETS_LOAN_DEPS = 'Invested Assets Loans Total Deposits' INVEST_CAP = 'Invested Capital Total' INVEST_LT = 'Long Term Investments' INVEST_ST_TOT = 'Total Short Term Investments' INVEST_YLD = 'Yield On Investment' LOAN_LOSS_ACTUAL_RSRV = 'Actual Loan Losses Reserves For Loan Losses' LOAN_LOSS_PCT = 'Net Loan Losses Total Loans' LOAN_LOSS_RSRV_ASSETS = 'Loan Loss Reserves Total Assets' LOAN_LOSS_RSRV_TCAP = 'Loan Loss Reserves Total Capital' LOSS_EXP_RATIO = 'Loss Expense Ratios Combined' LTD_COM_EQ = 'Long Term Debt Common Equity' MIN_INT_TCAP = 'Minority Interest Total Capital' MKT_VAL_PUBLIC = 'Market Capitalization Public' NET_INC_BASIC_AFT_XORD = 'Net Income Avail To Common Basic After Extraordinaries' NET_INC_BEF_XORD_GR = 'Net Income Before Extras 1 Year Growth' NET_INC_DIL_AFT_XORD = 'Net Income Avail To Common Fully Diluted After Extraordinaries' NET_INT_INC_EARN_ASSETS = 'Net Interest Income Earning Assets' NON_INT_INC_REV = 'Non Interest Income Total Revs' NONPERF_LOAN_COM_EQ = 'Non Performing Loans Common Equity' NONPERF_LOAN_PCT = 'Non Performing Loans Total Loans' OPER_INC_AFT_UNUSUAL = 'Operating Income After Unusual Items' OPER_INC_PREM_EARN = 'Operating Income Premiums Earned' OPER_INC_TCAP = 'Operating Income Total Capital' PAY_ACCT_SALES = 'Accounts PayableSales' PPE_NET_BLDG = 'Net Property Plant Equipment Buildings' PPE_NET_EQUIP = 'Net Property Plant Equipment Machinery Equipment' PPE_NET_LEASED_PROP = 'Net Property Plant Equipment Leased Property' PPE_NET_OTH = 'Net Property Plant Equipment Other Property Plant Equipment' PPE_NET_TRANS_EQUIP = 'Net Property Plant Equipment Transportation Equipment' RD_SALES = 'Research Development Sales' RECEIV_GROSS = 'Accounts Receivables Gross' RECEIV_TURN = 'Receivables Turnover' REINVEST_RATE = 'Reinvestment Rate' ROCE = 'Return On Common Equity' ROIC = 'Return On Average Invested Capital' SALES_GR = 'Sales Net 1 Year Growth' SALES_PER_EMP = 'Sales Per Employee' SALES_WKCAP = 'Sales Net Working Capital' SECS_RE_CAP = 'Equity Securities Real Estate Capital' SGA_OTH = 'Other Selling General Administrative Expense' SHS_FLOAT = 'Shares Outstanding Less Closely Held Shares Float' TAX_RATE = 'Tax Rate' TCAP_DEPS = 'Capital Total Total Deposits' UNEARN_PREM_TCAP = 'Unearned Premium Total Capital' UT_GROSS_INC = 'Gross Income Income Before Interest Charges' UT_OPERATION_EXP = 'Operation Expenses Total' WKCAP_PCT = 'Working Capital Total Capital' XORD_DISC = 'Extraordinaries Discontinued Operations' INVEST_RECEIV_LT = 'Investments Long Term Receivables' EBIT_BEF_UNUSUAL = 'Earnings Before Interest Taxes Unusual Expense' EPS_DIL_AFT_XORD = 'EPS Diluted After Extraordinaries' PBK_TANG = 'Price To Tangible Book Value' PFCF = 'Price To Free Cash Flow' PSALES_DIL = 'Price To Sales Diluted' TANG_ASSETS_DEBT = 'Tangible Assets To Total Debt' NET_INC_DIL_BEF_UNUSUAL = 'Net Income Before Unusual Expense' LENDING_MISC = 'Miscellaneous Lending' TANG_FIX_ASSETS_XRE = 'Tangible Fixed Assets' TRADE_INC_NET = 'Net Trading Income' BK_OPER_EXP_OTH = 'Bank Other Operating Expense' BK_OPER_INC_TOT = 'Bank Total Operating Income' COMMISS_INC_NET = 'Net Commission Income' LOAN_GROSS = 'Gross Loans' LOAN_DEPS = 'Loans Total Total Deposits' CF_ROIC = 'Cash Flow Return on Average Invested Capital' LIABS_DISC_OPER = 'Total Liabilities of Discontinued Operations' FSCORE = 'Piotroski F Score' IMPAIR = 'Impairments Total' RESTRUCT_DEBT = 'Restructuring of Debt' ASSETS_COM_EQ = 'Assets To Common Equity' ASSETS_GR = 'Assets Total 1 Year Growth' ASSETS_PER_EMP = 'Assets Per Employee' BPS_GR = 'Book Value Per Share 1 Year Annual Growth' CAPEX_ASSETS = 'Capital Expenditure Total Assets' CAPEX_PS_CF = 'Capital Expenditures Per Share' CASH_DIV_CF = 'Cash DividendsCash Flow' CASH_ROCE = 'Cash Earnings Return On Equity' CF_PS_GR = 'Cash Flow Per Share 1 Year Growth' CLAIMS_NET_PREM = 'Claims Claim Expense Net Premiums Written' COM_EQ_DEPS = 'Equity Total Deposits' COM_EQ_TCAP = 'Equity Total Capital' DEBT_ENTRPR_VAL = 'Total Debt To Enterprise Value' DEBT_LT_CF = 'Change in Long Term Debt' DEMAND_DEPS_PCT = 'Demand Deposits Total Deposits' DEPS_ASSETS = 'Deposits Total Total Assets' DIL_ADJ = 'Dilution Adjustment' DIV_YLD_SECS = 'Dividend Yield Security' EARN_ASSETS_AVAIL_FUNDS = 'Earning Assets Total Available Funds' EARN_ASSETS_PCT = 'Earning Assets Total Assets' EBIT_OPER_MGN = 'EBIT Margin' EBITDA_CF = 'EBITDA From Cash Flow' EFF_INT_RATE = 'Interest Rate Estimated Average' ENTRPR_VAL_EBIT_OPER = 'Enterprise Value To EBIT' ENTRPR_VAL_SALES = 'Enterprise Value To Sales' EXP_RATIO = 'Expense Ratio' FIX_ASSETS_COM_EQ = 'Fixed Assets Common Equity' FOR_ASSET_TURN = 'Foreign Asset Turnover' FOR_DEPS_PCT = 'Foreign Office Deposits Total Deposits' FOR_NET_MGN = 'Foreign Income Margin' FOR_SALES_PCT = 'Foreign Sales Total Sales' GROSS_CF_DEBT = 'Cash Flow Gross Total Debt' INC_SUND = 'Sundry RevenueIncome' INC_TAX_DFD = 'Deferred Income Taxes' EBIT_OPER_INT_COVG = 'Pretax Interest Coverage Ratio' INT_EXP_OTH = 'Other Interest Expense Banks' INT_INC_EARN_ASSETS = 'Interest Income Total Earning Assets' INVEN_CURR_ASSETS = 'Inventories Total Current Assets' INVEN_TURN = 'Inventory Turnover' INVEST_ASSETS_LIABS = 'Invested Assets Liabilities' INVEST_ASSETS_PCT = 'Invested Assets Total Assets' INVEST_INC_INVEST_ASSETS = 'Investment Income Invested Assets' INVEST_PROP = 'Investment Property' INVEST_TCAP = 'Investments Total Total Capital' LOAN_GR = 'Loans 1 Year Growth Rate' LOAN_LOSS_COVG = 'Loan Loss Coverage Ratio' LOAN_LOSS_PROV_PCT = 'Loan Losses Provision Total Loans' LOAN_LOSS_RSRV_PCT = 'Loan Losses Reserves Total Loans' LOAN_TCAP = 'Loans Total Total Capital' LOSS_RATIO = 'Loss Ratio' LTD_TCAP = 'Long Term Debt Total Capital' MKT_VAL_GR = 'Market Value 1 Year Growth' NET_CF_DEBT = 'Cash Flow Net Total Debt' NET_INC_BASIC_BEFT_XORD = 'Income Before Extraordinary Items Available For Common' NET_INC_DIL = 'Net Income Avail To Common Fully Diluted' NET_INC_PER_EMP = 'Net IncomeEmployee Most Recent Fy' NET_MGN_GR = 'Net Margin 1 Year Growth Rate' NON_OPER_EXP = 'Non Operating Expense' NONPERF_LOAN_LOSS_RSRV = 'Non Performing Loans Loan Loss Reserves' OPER_CF_FIX_CHRG = 'Operating Cash Flow To Fixed Charges Interest Dividend' OPER_INC_GR = 'Operating Income 1 Year Growth' OPER_INC_PREM_WRITTEN = 'Operating Income Net Premiums Written' OPER_PS_NET_CF = 'Cash Flow From Operations Per Share Net' PFD_STK_TCAP = 'Preferred Stock Total Capital' PPE_NET_CONSTR = 'Net Property Plant Equipment Construction In Progress' PPE_NET_LAND = 'Net Property Plant Equipment Land Improvements' PPE_NET_LEASES = 'Net Property Plant Equipment Leases' PPE_NET_SOFT_EQUIP = 'Net Property Plant Equipment Computer Software And Equipment' PREM_WRITTEN_COM_EQ = 'Net Premium Written Equity' RECEIV_CURR_ASSETS = 'Receivables Current Assets' RECEIV_ST_OTH = 'Other Receivables Short Term' RECEIV_TURN_DAYS = 'Accounts Receivables Days' ROA_PTX = 'Return On Average Assets Pretax' ROEA = 'Return On Earning Assets' SALES_FIX_ASSETS = 'Sales Net Gross Fixed Assets' SALES_INVEN_TURN = 'Sales To Inventory Turnover' SALES_PS_GR = 'Sales Per Share 1 Year Growth' SAV_DEPS_PCT = 'Savings Deposits Total Deposits' SECS_RE_INVEST_ASSETS = 'Equity Securities Real Estate Invested Assets' SGA_SALES = 'Selling General Admin Expense Sales' SPEC_ITEMS = 'Special Items' TCAP_ASSETS = 'Capital Total Total Assets' TOT_DEBT_TCAP_STD = 'Total Debt Total Capital' US_GAAP_ADJ = 'US GAAP Adjustment' UT_NON_OPER_INC_OTH = 'Nonoperating Income Net Other' WKCAP = 'Working Capital' XORD = 'Extraordinary Items Including Accounting Change' ZSCORE = 'ALTMANS Z SCORE' DEBT_SERV = 'Debt Service Ratio Including ST Debt Pretax' EBITDA_BEF_UNUSUAL = 'EBITDA Before Unusual Expenses' EPS_DIL_GR = 'EPS Diluted Before Extras Change' PE_DIL = 'Price To Earnings Diluted' PFCF_DIL = 'Diluted Price To Free Cash Flow' STD_DEBT = 'Short Term Debt Total Debt' PAY_TURN_DAYS = 'Days of Payables Outstanding' INT_INC_MISC = 'Miscellaneous Interest Income' RE_INVEST = 'Real Estate Investment' FIN_INVEST_TOT = 'Total Investments for Financial' BK_OPER_INC_OTH = 'Bank Other Operating Income' BK_OPER_EXP_TOT = 'Bank Total Operating Expense' BK_NON_OPER_INC = 'Bank Non Operating Income Expense' ACCT_PAY_OPER = 'Creditors' LOAN_ASSETS = 'Loans Total Total Assets' ASSETS_DISC_OPER = 'Total Assets of Discontinued Operations' LIABS_LEASE = 'Capital and Operating Lease Obligations' FCF_YLD = 'Free Cash Flow Yield' OTH_XCEPT_CHRG = 'Exceptional Charges Other' fundamental_basic_dict = {item.name: item.value for item in FundamentalBasicItem} fundamental_basic_derived_dict = {item.name: item.value for item in FundamentalBasicDerivedItem} fundamental_advanced_dict = {item.name: item.value for item in FundamentalAdvancedItem} fundamental_advanced_derived_dict = {item.name: item.value for item in FundamentalAdvancedDerivedItem} FundamentalMetric = Enum( 'FundamentalMetric', { **fundamental_basic_dict, **fundamental_basic_derived_dict, **fundamental_advanced_dict, **fundamental_advanced_derived_dict, }, ) class FundamentalFormat(Enum): RESTATED = 'Restated' NON_RESTATED = 'Non Restated' class RatingType(Enum): BUY = 'Buy' OVERWEIGHT = 'Overweight' HOLD = 'Hold' UNDERWEIGHT = 'Underweight' SELL = 'Sell' NONE = 'No Recommendations' TOTAL = 'Total' SCORE = 'Numeric Score' class FiscalPeriod: """ Create an abosolute fiscal period with year and period. :param y: year :param p: period :return: fiscal period object **Usage** The year combined with period can determine the fiscal period of the estimate. **Examples** Year is :math:`2025` and period is :math:`2`: >>> FiscalPeriod(2025, 2) """ def __init__(self, y: Union[int, str, None] = None, p: Union[int, str, None] = None): self.y = y self.p = p def as_dict(self): return {'y': self.y, 'p': self.p} @classmethod def from_dict(cls, obj): return FiscalPeriod(y=obj.get('y'), p=obj.get('p')) BASIC_MEASURES = [ EstimateItem.EPS, EstimateItem.EPS_C, EstimateItem.EPS_P, EstimateItem.SALES, EstimateItem.SALES_C, EstimateItem.SALES_P, EstimateItem.DPS, EstimateItem.CFPS, EstimateItem.PRICE_TGT, EstimateItem.EPS_LTG, ] LT_MEASURES = [EstimateItem.PRICE_TGT, EstimateItem.EPS_LTG] BASIS_TO_DATASET = { EstimateBasis.ANN: 'AF', EstimateBasis.QTR: 'QF', EstimateBasis.SEMI: 'SAF', EstimateBasis.NTM: 'NTM', EstimateBasis.STM: 'NTM', } BASIS_TO_FIELD = { EstimateBasis.ANN: 'Af', EstimateBasis.QTR: 'Qf', EstimateBasis.SEMI: 'Saf', EstimateBasis.NTM: 'Ntm', EstimateBasis.STM: 'Stm', } FF_BASIS_TO_DATASET = {FundamentalBasis.ANN: 'AF', FundamentalBasis.QTR: 'QF', FundamentalBasis.SEMI: 'SAF'} FF_BASIS_TO_FIELD = {FundamentalBasis.ANN: 'Af', FundamentalBasis.QTR: 'Qf', FundamentalBasis.SEMI: 'Saf'} RATING_TO_FIELD = { RatingType.BUY: 'feBuy', RatingType.OVERWEIGHT: 'feOver', RatingType.HOLD: 'feHold', RatingType.UNDERWEIGHT: 'feUnder', RatingType.SELL: 'feSell', RatingType.NONE: 'feNoRec', RatingType.TOTAL: 'feTotal', RatingType.SCORE: 'feMark', } @plot_measure((AssetClass.Equity,), (AssetType.Single_Stock,)) # TO DO add query type def factset_estimates( asset: Asset, metric: EstimateItem = EstimateItem.EPS, statistic: EstimateStatistic = EstimateStatistic.MEAN, report_basis: EstimateBasis = EstimateBasis.ANN, period: Union[int, FiscalPeriod, None] = 1, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ FactSet estimates for single stocks. :param asset: asset object loaded from security master :param metric: metric for estimate e.g. EPS :param statistic: type of estimate to show e.g. Mean :param report_basis: reporting basis of the estimate e.g. Annual :param period: rolling window for the estimate e.g. 1 period ahead or fixed non-rolling window e.g. FiscalPeriod(2025, 2) :param source: name of function caller: default source = None :param real_time: whether to retrieve intraday data instead of EOD, real time is currently not supported :param request_id: service request id, if any :return: FactSet Estimates """ if not isinstance(report_basis, EstimateBasis): raise MqValueError('Invalid Estimate Basis argument') start, end = DataContext.current.start_date, DataContext.current.end_date start_new = RelativeDate('-1y', base_date=start).apply_rule() basic = 'BASIC' if metric in BASIC_MEASURES else 'ADVANCED' column_prefix = '' if metric in BASIC_MEASURES else 'Adv' consensus = 'CONH' if statistic != EstimateStatistic.ACTUAL else 'ACT' basis_ds = 'LT' if metric in LT_MEASURES else BASIS_TO_DATASET[report_basis] basis_cl = 'Lt' if metric in LT_MEASURES else BASIS_TO_FIELD[report_basis] if report_basis in [EstimateBasis.NTM, EstimateBasis.STM]: ds_id = f'FE_{basis_ds}' else: ds_id = f'FE_{basic}_{consensus}_{basis_ds}_GLOBAL' ds = Dataset(ds_id) bbid = asset.get_identifier(AssetIdentifier.BLOOMBERG_ID) try: df = ds.get_data(bbid=bbid, start=start_new, end=end, feItem=metric.name) except Exception as e: raise MqValueError(f'Could not query dataset {ds_id} because of {e}') if df.empty: raise MqValueError(f'No data found for {metric.value} for {asset.get_identifier(AssetIdentifier.BLOOMBERG_ID)}') df = df.reset_index() if statistic == EstimateStatistic.ACTUAL: if report_basis in [EstimateBasis.NTM, EstimateBasis.STM]: raise MqValueError('NTM and STM are not supported for actual values') elif metric in LT_MEASURES: raise MqValueError(f'No actual data for {metric.value}') else: df = df[['feFpEnd', 'feValue']] df = df.rename(columns={'feFpEnd': 'date'}) df['date'] = pd.to_datetime(df['date']) column = 'feValue' elif report_basis not in [EstimateBasis.NTM, EstimateBasis.STM]: if metric in LT_MEASURES: pass elif isinstance(period, int): df = df[df['fePerRel'] == period] else: if report_basis == EstimateBasis.ANN: fiscal_period_start = dt.datetime(period.y, 1, 1) fiscal_period_end = dt.datetime(period.y, 12, 31) elif report_basis == EstimateBasis.QTR: if isinstance(period.p, int) and period.p not in [1, 2, 3, 4]: raise MqValueError('Period number has to be one of 1,2,3 or 4 for quarterly basis') elif period.p is None: raise MqValueError( 'Please specify the period as an integer between 1 and 4 like FiscalPeriod(2022, 4) ' 'for 2022Q4 estimate' ) fiscal_period_start = dt.datetime(period.y, (period.p - 1) * 3 + 1, 1) fiscal_period_end = fiscal_period_start + pd.DateOffset(months=3) - pd.DateOffset(days=1) fiscal_period_end = pd.to_datetime(fiscal_period_end) elif report_basis == EstimateBasis.SEMI: if isinstance(period.p, int) and period.p not in [1, 2]: raise MqValueError('Period number has to be 1 or 2 for semi-annual basis') elif period.p is None: raise MqValueError( 'Please specify the period as 1 or 2 like FiscalPeriod(2022, 2) for 2022H2 estimate' ) fiscal_period_start = dt.datetime(period.y, (period.p - 1) * 6 + 1, 1) fiscal_period_end = fiscal_period_start + pd.DateOffset(months=6) - pd.DateOffset(days=1) fiscal_period_end = pd.to_datetime(fiscal_period_end) df['feFpEnd'] = pd.to_datetime(df['feFpEnd']) df = df[(df['feFpEnd'] >= fiscal_period_start) & (df['feFpEnd'] <= fiscal_period_end)] if df.empty: raise MqValueError('No Data returned for selected fiscal period') df = df.fillna({'consEndDate': end}) df['date_range'] = df.apply(lambda row: pd.date_range(row['date'], row['consEndDate']), axis=1) df = df.explode('date_range').drop(columns=['date', 'consEndDate']).rename(columns={'date_range': 'date'}) column = f'fe{column_prefix}{statistic.value}{basis_cl}' else: df['date'] = pd.to_datetime(df['date']) column = f'fe{statistic.value}{basis_cl}' df = df[df['date'] >= pd.to_datetime(start)] df = df.sort_values(by='date', ascending=True).set_index('date') series = ExtendedSeries(df[column], name=statistic.value) _idx = pd.DatetimeIndex(series.index) series.index = _idx.as_unit('ns') if hasattr(_idx, 'as_unit') else _idx series.dataset_ids = ds.id return series @plot_measure((AssetClass.Equity,), (AssetType.Single_Stock,)) # TO DO add query type def factset_fundamentals( asset: Asset, metric: FundamentalMetric = FundamentalMetric.EPS_BASIC, report_basis: FundamentalBasis = FundamentalBasis.ANN, report_format: FundamentalFormat = FundamentalFormat.NON_RESTATED, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ FactSet fundamentals for single stocks. :param asset: asset object loaded from security master :param metric: metric for estimate e.g. EPS :param report_basis: reporting basis of the estimate e.g. Annual :param report_format: restated or non-restated :param source: name of function caller: default source = None :param real_time: whether to retrieve intraday data instead of EOD, real time is currently not supported :param request_id: service request id, if any :return: FactSet Fundamentals """ start, end = DataContext.current.start_date, DataContext.current.end_date start_new = RelativeDate('-1y', base_date=start).apply_rule() basic = 'BASIC' if metric.name in {**fundamental_basic_dict, **fundamental_basic_derived_dict} else 'ADVANCED' derived = '_DER' if metric.name in {**fundamental_basic_derived_dict, **fundamental_advanced_derived_dict} else '' if report_format == FundamentalFormat.RESTATED: restated = '_R' else: restated = '' ds_id = f'FF_{basic}{derived}{restated}_{FF_BASIS_TO_DATASET[report_basis]}_GLOBAL' ds = Dataset(ds_id) df = ds.get_data(bbid=asset.get_identifier(AssetIdentifier.BLOOMBERG_ID), start=start_new, end=end) if df.empty: raise MqValueError(f'No data found for {metric.value} for {asset.get_identifier(AssetIdentifier.BLOOMBERG_ID)}') df = df.reset_index() column = 'ff' + metric.name.replace('_', ' ').title().replace(' ', '') df = df[['date', column]] date_range = pd.date_range(start=start_new, end=end, freq='D') date_df = pd.DataFrame({'date': date_range}) df = pd.merge(date_df, df, on='date', how='left') df[column] = df[column].ffill() df = df[df['date'] >= pd.to_datetime(start)] df = df.sort_values(by='date', ascending=True).set_index('date') series = ExtendedSeries(df[column], name=metric.value) _idx = pd.DatetimeIndex(series.index) series.index = _idx.as_unit('ns') if hasattr(_idx, 'as_unit') else _idx series.dataset_ids = ds.id return series @plot_measure((AssetClass.Equity,), (AssetType.Single_Stock,)) # TO DO add query type def factset_ratings( asset: Asset, rating_type: RatingType = RatingType.BUY, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ FactSet consensus ratings for single stocks, shows number of brokers for the rating type or a standardized numeric value representing the consensus of broker recommendations. :param asset: asset object loaded from security master :param rating_type: type of broker rating e.g. Buy :param source: name of function caller: default source = None :param real_time: whether to retrieve intraday data instead of EOD, real time is currently not supported :param request_id: service request id, if any :return: FactSet Ratings """ start, end = DataContext.current.start_date, DataContext.current.end_date start_new = RelativeDate('-1y', base_date=start).apply_rule() ds_id = 'FE_BASIC_CONH_REC_GLOBAL' ds = Dataset(ds_id) df = ds.get_data(bbid=asset.get_identifier(AssetIdentifier.BLOOMBERG_ID), start=start_new, end=end) df = df.reset_index().fillna({'consEndDate': end}) df['date_range'] = df.apply(lambda row: pd.date_range(row['date'], row['consEndDate']), axis=1) df = df.explode('date_range').drop(columns=['date', 'consEndDate']).rename(columns={'date_range': 'date'}) df = df[df['date'] >= pd.to_datetime(start)].sort_values(by='date', ascending=True).set_index('date') series = ExtendedSeries(df[RATING_TO_FIELD[rating_type]], name=rating_type.value) _idx = pd.DatetimeIndex(series.index) series.index = _idx.as_unit('ns') if hasattr(_idx, 'as_unit') else _idx series.dataset_ids = ds.id return series ================================================ FILE: gs_quant/timeseries/measures_fx_vol.py ================================================ """ Copyright 2020 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import logging from enum import Enum from numbers import Real from typing import Union, Optional import pandas as pd from gs_quant.api.gs.assets import GsAssetApi from gs_quant.api.gs.data import QueryType, GsDataApi from gs_quant.common import AssetClass, AssetType, PricingLocation from gs_quant.data import DataContext, Dataset from gs_quant.errors import MqValueError from gs_quant.markets.securities import AssetIdentifier, Asset, SecurityMaster from gs_quant.timeseries import ASSET_SPEC, plot_measure, MeasureDependency, FXSpotCarry from gs_quant.timeseries import ExtendedSeries, measures_rates as tm_rates from gs_quant.timeseries.measures import ( _asset_from_spec, _market_data_timed, _cross_stored_direction_helper, _preprocess_implied_vol_strikes_fx, _tenor_month_to_year, ) from gs_quant.timeseries.measures_helper import VolReference _logger = logging.getLogger(__name__) class OptionType(Enum): CALL = 'Call' PUT = 'Put' STRADDLE = 'Straddle' class TdapiFXDefaultsProvider: # flag to indicate that a given property should not be included in asset query EMPTY_PROPERTY = "null" def __init__(self, defaults: dict): self.defaults = defaults def get_defaults_for_cross(self, cross: str): return dict(self.defaults.get(cross)) FX_DEFAULTS = { "AUDCAD": {"under": "AUD", "over": "CAD", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "AUDJPY": {"under": "AUD", "over": "JPY", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "AUDUSD": {"under": "AUD", "over": "USD", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "BRLJPY": {"under": "BRL", "over": "JPY", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "CADJPY": {"under": "CAD", "over": "JPY", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "CADMXN": {"under": "CAD", "over": "MXN", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "CHFJPY": {"under": "CHF", "over": "JPY", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "CNHJPY": {"under": "CNH", "over": "JPY", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "EURAUD": {"under": "EUR", "over": "AUD", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "EURBRL": {"under": "EUR", "over": "BRL", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "EURCAD": {"under": "EUR", "over": "CAD", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "EURCHF": {"under": "EUR", "over": "CHF", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "EURCLP": {"under": "EUR", "over": "CLP", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "EURCNH": {"under": "EUR", "over": "CNH", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "EURCZK": {"under": "EUR", "over": "CZK", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "EURGBP": {"under": "EUR", "over": "GBP", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "EURHUF": {"under": "EUR", "over": "HUF", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "EURILS": {"under": "EUR", "over": "ILS", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "EURINR": {"under": "EUR", "over": "INR", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "EURJPY": {"under": "EUR", "over": "JPY", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "EURKRW": {"under": "EUR", "over": "KRW", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "EURMXN": {"under": "EUR", "over": "MXN", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "EURNOK": {"under": "EUR", "over": "NOK", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "EURNZD": {"under": "EUR", "over": "NZD", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "EURPLN": {"under": "EUR", "over": "PLN", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "EURRUB": {"under": "EUR", "over": "RUB", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "EURSEK": {"under": "EUR", "over": "SEK", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "EURTRY": {"under": "EUR", "over": "TRY", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "EURUSD": {"under": "EUR", "over": "USD", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "EURZAR": {"under": "EUR", "over": "ZAR", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "GBPJPY": {"under": "GBP", "over": "JPY", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "GBPUSD": {"under": "GBP", "over": "USD", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "HUFJPY": {"under": "HUF", "over": "JPY", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "ILSJPY": {"under": "ILS", "over": "JPY", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "INRJPY": {"under": "INR", "over": "JPY", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "JPYKRW": {"under": "JPY", "over": "KRW", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "MXNJPY": {"under": "MXN", "over": "JPY", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "NOKJPY": {"under": "NOK", "over": "JPY", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "NZDJPY": {"under": "NZD", "over": "JPY", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "NZDUSD": {"under": "NZD", "over": "USD", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "PLNJPY": {"under": "PLN", "over": "JPY", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "RUBJPY": {"under": "RUB", "over": "JPY", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "SEKJPY": {"under": "SEK", "over": "JPY", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "USDBRL": {"under": "USD", "over": "BRL", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "USDCAD": {"under": "USD", "over": "CAD", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "USDCHF": {"under": "USD", "over": "CHF", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "USDCLP": {"under": "USD", "over": "CLP", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "USDCNH": {"under": "USD", "over": "CNH", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "USDCOP": {"under": "USD", "over": "COP", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "USDHUF": {"under": "USD", "over": "HUF", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "USDIDR": {"under": "USD", "over": "IDR", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "USDILS": {"under": "USD", "over": "ILS", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "USDINR": {"under": "USD", "over": "INR", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "USDJPY": {"under": "USD", "over": "JPY", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "USDKRW": {"under": "USD", "over": "KRW", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "USDMXN": {"under": "USD", "over": "MXN", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "USDNOK": {"under": "USD", "over": "NOK", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "USDPHP": {"under": "USD", "over": "PHP", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "USDPLN": {"under": "USD", "over": "PLN", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "USDRUB": {"under": "USD", "over": "RUB", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "USDSEK": {"under": "USD", "over": "SEK", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "USDSGD": {"under": "USD", "over": "SGD", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "USDTRY": {"under": "USD", "over": "TRY", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "USDTWD": {"under": "USD", "over": "TWD", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, "USDZAR": {"under": "USD", "over": "ZAR", "expirationTime": "NYC", "premiumPaymentDate": "Fwd Settle"}, } fx_defaults_provider = TdapiFXDefaultsProvider(FX_DEFAULTS) FX_VOL_SWAP_DEFAULTS = [ "EURUSD", "GBPUSD", "USDCHF", "DKKUSD", "NOKUSD", "SEKUSD", "USDCAD", "USDJPY", "AUDUSD", "NZDUSD", "USDCNH", "INRUSD", "USDSGD", ] CURRENCY_TO_DUMMY_FFO_BBID = { 'AUDCAD': 'MA4TJRN9MBET5NKS', 'AUDJPY': 'MAH5X6NHGRBKMWPE', 'AUDUSD': 'MAN0G5EB00H14W0S', 'BRLJPY': 'MA1WP7QREXS9WZVQ', 'CADJPY': 'MAVHN1MPE1X9JW52', 'CADMXN': 'MAE2415EP4XX6ACZ', 'CHFJPY': 'MAJ7VVAY60D3E2WE', 'CNHJPY': 'MAEF5B5SYGCADZQJ', 'EURAUD': 'MAN5S7K3N5K7TAYQ', 'EURBRL': 'MA9CQ07TPZFWR37M', 'EURCAD': 'MA46Z26SSKTC26X0', 'EURCHF': 'MAX77PXQC2B09S30', 'EURCLP': 'MAYYHB7XGPS49B5P', 'EURCNH': 'MA6S2DN8YER1ZWNK', 'EURCZK': 'MAH68Y664M87S2WK', 'EURGBP': 'MAXGQ18G9245FMFN', 'EURHUF': 'MAQY2AFQGAGMWA86', 'EURILS': 'MA80D8MWZJA884M7', 'EURINR': 'MA9M0AD4XF4PZDJ4', 'EURJPY': 'MATCYWGHQCM89K72', 'EURKRW': 'MATRZZCW5H78ZMNX', 'EURMXN': 'MAH2P631H1JXERTK', 'EURNOK': 'MA3WQVT62D4C0ZQ8', 'EURNZD': 'MA28186ESNB3HR6A', 'EURPLN': 'MAFEVSPC80GP6PJX', 'EURRUB': 'MANABN4EAX6YYB3D', 'EURSEK': 'MA0FCJ0QXH4F73VE', 'EURTRY': 'MAY54D9K3PYCT18A', 'EURUSD': 'MAT1J37C9ZPMANFP', 'EURZAR': 'MAPNZ5Z1X18YNKN3', 'GBPJPY': 'MATJS76YB4JSCSCA', 'GBPUSD': 'MAEHA6WVHJ2S3JY9', 'HUFJPY': 'MA98M1V3YEMS5XEB', 'ILSJPY': 'MA6S47WRX6F498JB', 'INRJPY': 'MAJEZ70FKX95F5M7', 'JPYKRW': 'MA3WF2A5FHGBVJ6X', 'MXNJPY': 'MABS05Q0848GQCQW', 'NOKJPY': 'MAF5TG7GKB5ZCWDY', 'NZDJPY': 'MABZBJSAPM9KHASD', 'NZDUSD': 'MA18GEASNDN55AHS', 'PLNJPY': 'MAN2CFHW1Z4XFXQ3', 'RUBJPY': 'MAHT6HYTYXEK2QY1', 'SEKJPY': 'MAA0W4P56NZEGZSC', 'USDBRL': 'MA2CKWJWZXA3A3QQ', 'USDCAD': 'MAXWBP82QZ3B2304', 'USDCHF': 'MANPCKXE2H0FQMFC', 'USDCLP': 'MAS2G31EZJDWEXQ9', 'USDCNH': 'MA141AAE2NWP5BHN', 'USDCOP': 'MAPWBD5FQP0W303V', 'USDHUF': 'MAQEDRH3T69WCA8J', 'USDIDR': 'MAFKA004WSQEZ6KR', 'USDILS': 'MAS2AM5ENGQ00MP5', 'USDINR': 'MA8JP0HYF8CQZCYG', 'USDJPY': 'MAQ7YRZ4P94M9N9C', 'USDKRW': 'MAVZG5BFM5VVTYMD', 'USDMXN': 'MAPAVBP6KQMQJZP3', 'USDNOK': 'MAFT0XXQASPQ7X1J', 'USDPHP': 'MA5N37TB77VX7RCR', 'USDPLN': 'MACNEGC1Q81HK2QP', 'USDRUB': 'MAXMSS4B5YXE935S', 'USDSEK': 'MAE4YZ9B2KKVM67J', 'USDSGD': 'MA9DWW9GPFT7H1A9', 'USDTRY': 'MA4YKBCXZ4W341YC', 'USDTWD': 'MAANPDQCDNKV6G2X', 'USDZAR': 'MA9T8EZ1GXEGY8C3', } CURRENCY_TO_DUMMY_FFO_BBID_VOL_SWAPS = { "EURUSD": "MA66A4X4PRTC3N7B", "GBPUSD": "MA808D9BQ4E396C4", "USDCHF": "MAPGXTC189B2111M", "DKKUSD": "MABCHYGJ1TCBCQE4", "NOKUSD": "MAZ0P16RPG5P0MVF", "SEKUSD": "MA4ZD5CZGC3Y6JZD", "USDCAD": "MAZPF9SV1M6ZSGFH", "USDJPY": "MA3Y28M97R55G16Y", "AUDUSD": "MAAEVQXHFBKNDYRP", "NZDUSD": "MA9RP21MYV18TW3E", "USDCNH": "MAQEB18HP88FEP5G", "INRUSD": "MAMC996297G5GEAM", "USDSGD": "MAFCHSQN3NH77G17", } def _currencypair_to_tdapi_fxfwd_asset(asset_spec: ASSET_SPEC) -> str: asset = _asset_from_spec(asset_spec) bbid = asset.get_identifier(AssetIdentifier.BLOOMBERG_ID) kwargs = dict( asset_class='FX', type='Forward', asset_parameters_pair=bbid, asset_parameters_settlement_date='1y', ) mqid = _get_tdapi_fxo_assets(**kwargs) return mqid def _currencypair_to_tdapi_fxo_asset(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 = CURRENCY_TO_DUMMY_FFO_BBID.get(bbid, asset.get_marquee_id()) return result def _currencypair_to_tdapi_fx_vol_swap_asset(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 = CURRENCY_TO_DUMMY_FFO_BBID_VOL_SWAPS.get(bbid, asset.get_marquee_id()) return result def _get_tdapi_fxo_assets(**kwargs) -> Union[str, list]: # sanitize input for asset query. if "pricing_location" in kwargs: del kwargs["pricing_location"] name_prefix = kwargs.pop("name_prefix", None) assets = GsAssetApi.get_many_assets(**kwargs) if len(assets) > 1: if name_prefix: for asset in assets: if asset.name.startswith(name_prefix): return asset.id raise MqValueError('Specified arguments match multiple assets' + str(kwargs)) elif len(assets) == 0: raise MqValueError('Specified arguments did not match any asset in the dataset' + str(kwargs)) else: return assets[0].id def get_fxo_asset( asset: Asset, expiry_tenor: str, strike: str, option_type: str = None, expiration_location: str = None, premium_payment_date: str = None, ) -> str: cross = asset.get_identifier(AssetIdentifier.BLOOMBERG_ID) if cross not in FX_DEFAULTS.keys(): raise NotImplementedError('Data not available for {} FX Vanilla options'.format(cross)) defaults = _get_fxo_defaults(cross) if not (tm_rates._is_valid_relative_date_tenor(expiry_tenor)): raise MqValueError('invalid expiry ' + expiry_tenor) if expiration_location is None: _ = defaults["expirationTime"] else: _ = expiration_location if premium_payment_date is None: premium_date = defaults["premiumPaymentDate"] else: premium_date = premium_payment_date if option_type == "Put": call_ccy = defaults["over"] put_ccy = defaults["under"] else: call_ccy = defaults["under"] put_ccy = defaults["over"] kwargs = dict( asset_class='FX', type='Option', asset_parameters_call_currency=call_ccy, asset_parameters_put_currency=put_ccy, asset_parameters_expiration_date=expiry_tenor, asset_parameters_option_type=option_type, asset_parameters_premium_payment_date=premium_date, asset_parameters_strike_price_relative=strike, ) return _get_tdapi_fxo_assets(**kwargs) def _get_tdapi_fxo_assets_vol_swaps(**kwargs) -> Union[str, list]: # sanitize input for asset query. expiry_tenor = kwargs.get("expiry_tenor") ignore_list = ["expiry_tenor", "pricing_location"] inputs = {k: v for k, v in kwargs.items() if k not in ignore_list} assets = GsAssetApi.get_many_assets(**inputs) # For vol swaps we are not restricting assets using a filter # as asset service isn't setup for the parameters we pass in # instead query all assets and apply the filter in code here if len(assets) == 0: raise MqValueError('No assets found matching search criteria' + str(kwargs)) if expiry_tenor is not None: for asset in assets: if asset.parameters["lastFixingDate"].lower() == expiry_tenor.lower(): return asset.id raise MqValueError('Specified arguments did not match any asset in the dataset' + str(kwargs)) def cross_stored_direction_for_fx_vol(asset_spec: ASSET_SPEC) -> Union[str, Asset]: asset = _asset_from_spec(asset_spec) result = asset try: if asset.asset_class is AssetClass.FX: bbid = asset.get_identifier(AssetIdentifier.BLOOMBERG_ID) if bbid is not None: cross = _cross_stored_direction_helper(bbid) if cross != bbid: cross_asset = SecurityMaster.get_asset(cross, AssetIdentifier.BLOOMBERG_ID) result = cross_asset except TypeError: result = asset return _currencypair_to_tdapi_fxo_asset(result) def _get_fxo_defaults(cross: str) -> dict: return fx_defaults_provider.get_defaults_for_cross(cross) def _get_fx_csa_terms() -> dict: return dict(csaTerms='USD-1') def _get_fx_vol_swap_data( asset: Asset, expiry_tenor: str, strike_type: str = None, location: PricingLocation = None, source: str = None, real_time: bool = False, query_type: QueryType = QueryType.STRIKE_VOL, ) -> pd.DataFrame: if real_time: raise NotImplementedError('realtime FX Vol swap data not implemented') cross = asset.get_identifier(AssetIdentifier.BLOOMBERG_ID) if cross not in FX_VOL_SWAP_DEFAULTS: raise NotImplementedError('Data not available for {} FX Vol Swaps'.format(cross)) kwargs = dict( asset_class='FX', type='VolatilitySwap', expiry_tenor=expiry_tenor, asset_parameters_pair=cross, # asset_parameters_strike_vol=strike_type ) fxv_mqid = _get_tdapi_fxo_assets_vol_swaps(**kwargs) if location is None: pricing_location = PricingLocation.NYC else: pricing_location = PricingLocation(location) where = dict(pricingLocation=pricing_location.value) q = GsDataApi.build_market_data_query([fxv_mqid], query_type, where=where, source=source, real_time=real_time) _logger.debug('q %s', q) df = _market_data_timed(q) return df def _get_fxfwd_data( asset: Asset, settlement_date: str, location: str = None, source: str = None, real_time: bool = False, query_type: QueryType = QueryType.FWD_POINTS, ) -> pd.DataFrame: if real_time: mqid = asset.get_identifier(AssetIdentifier.MARQUEE_ID) q = GsDataApi.build_market_data_query( [mqid], QueryType.FORWARD_POINT, source=source, real_time=real_time, where={'tenor': settlement_date} ) _logger.debug('q %s', q) df = _market_data_timed(q) return df cross = asset.get_identifier(AssetIdentifier.BLOOMBERG_ID) if not (tm_rates._is_valid_relative_date_tenor(settlement_date)): raise MqValueError('invalid settlements date ' + settlement_date) kwargs = dict( asset_class='FX', type='Forward', asset_parameters_pair=cross, asset_parameters_settlement_date=settlement_date, name_prefix='FX Forward', ) mqid = _get_tdapi_fxo_assets(**kwargs) if location is None: pricing_location = PricingLocation.NYC else: pricing_location = PricingLocation(location) where = dict(pricingLocation=pricing_location.value) q = GsDataApi.build_market_data_query([mqid], query_type, where=where, source=source, real_time=real_time) _logger.debug('q %s', q) df = _market_data_timed(q) return df def _get_fxo_data( asset: Asset, expiry_tenor: str, strike: str, option_type: str = None, expiration_location: str = None, location: PricingLocation = None, premium_payment_date: str = None, source: str = None, real_time: bool = False, query_type: QueryType = QueryType.IMPLIED_VOLATILITY, ) -> pd.DataFrame: if real_time: raise NotImplementedError('realtime FX Option data not implemented') asset_mqid = get_fxo_asset( asset=asset, expiry_tenor=expiry_tenor, strike=strike, option_type=option_type, expiration_location=expiration_location, premium_payment_date=premium_payment_date, ) if location is None: pricing_location = PricingLocation.NYC else: pricing_location = PricingLocation(location) where = dict(pricingLocation=pricing_location.value) # _logger.debug(f'where asset= {rate_mqid}, swap_tenor={swap_tenor}, index={defaults["index_type"]}, ' # f'forward_tenor={forward_tenor}, pricing_location={pricing_location.value}, ' # f'clearing_house={clearing_house.value}, notional_currency={currency.name}') q = GsDataApi.build_market_data_query([asset_mqid], query_type, where=where, source=source, real_time=real_time) _logger.debug('q %s', q) df = _market_data_timed(q) return df def implied_volatility_new( asset: Asset, expiry_tenor: str, strike: str, option_type: str = None, expiration_location: str = None, location: PricingLocation = None, premium_payment_date: str = None, *, source: str = None, real_time: bool = False, ) -> pd.Series: """ GS end-of-day FX vanilla implied volatilities across major crosses. :param asset: asset object loaded from security master :param expiry_tenor: relative date representation of expiration date e.g. 1m :param strike: option strike :param option_type: option type (e.g. Put, Call or Straddle ) :param expiration_location: location indicating the time of the option expiry :param location: Example - "TKO", "LDN", "NYC" :param premium_payment_date: payment date of the premium Example: Fwd Settle vs Spot :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :return: swap rate curve """ df = _get_fxo_data( asset=asset, expiry_tenor=expiry_tenor, strike=strike, option_type=option_type, expiration_location=expiration_location, location=location, premium_payment_date=premium_payment_date, source=source, real_time=real_time, query_type=QueryType.IMPLIED_VOLATILITY, ) series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(df['impliedVolatility']) series.dataset_ids = getattr(df, 'dataset_ids', ()) return series """ New Implementation """ @plot_measure( (AssetClass.FX,), (AssetType.Cross,), [MeasureDependency(id_provider=cross_stored_direction_for_fx_vol, query_type=QueryType.IMPLIED_VOLATILITY)], display_name="implied_volatility", ) def implied_volatility_fxvol( asset: Asset, tenor: str, strike_reference: VolReference = None, relative_strike: Real = None, location: Optional[PricingLocation] = None, legacy_implementation: bool = False, *, source: str = None, real_time: bool = False, ) -> pd.Series: """ Volatility of an asset implied by observations of market prices. :param asset: asset object loaded from security master :param tenor: relative date representation of expiration date e.g. 1m or absolute calendar strips e.g. 'Cal20', 'F20-G20' :param strike_reference: reference for strike level. Forward is used for ATMF. Default market convention for FX implied vols is delta neutral :param relative_strike: strike relative to reference :param location: location of the data snapshot Example - "HKG", "LDN", "NYC" :param legacy_implementation: Deprecated (supplied values are ignored) :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :return: implied volatility curve """ bbid = asset.get_identifier(AssetIdentifier.BLOOMBERG_ID) if bbid is not None: cross = _cross_stored_direction_helper(bbid) if cross != bbid: cross_asset = SecurityMaster.get_asset(cross, AssetIdentifier.BLOOMBERG_ID) if strike_reference.value == VolReference.DELTA_CALL.value: strike_reference = VolReference.DELTA_PUT elif strike_reference.value == VolReference.DELTA_PUT.value: strike_reference = VolReference.DELTA_CALL else: cross_asset = asset else: raise MqValueError('Badly setup cross ' + asset.name) ref_string, relative_strike = _preprocess_implied_vol_strikes_fx(strike_reference, relative_strike) if ref_string == 'delta': if relative_strike == 0: strike = 'DN' option_type = OptionType.CALL.value else: if relative_strike > 0: strike = str(relative_strike) + 'D' option_type = OptionType.CALL.value else: strike = str(-relative_strike) + 'D' option_type = OptionType.PUT.value elif ref_string == VolReference.SPOT.value: strike = 'Spot' option_type = OptionType.CALL.value elif ref_string == VolReference.FORWARD.value: strike = 'ATMF' option_type = OptionType.CALL.value else: raise MqValueError('unknown strike_reference and relative_strike combination') tenor = _tenor_month_to_year(tenor) s = implied_volatility_new( cross_asset, tenor, strike, option_type, location=location, source=source, real_time=real_time ) return s @plot_measure( (AssetClass.FX,), (AssetType.Cross,), [MeasureDependency(id_provider=_currencypair_to_tdapi_fxfwd_asset, query_type=QueryType.FWD_POINTS)], display_name="forward_point", ) def fwd_points( asset: Asset, settlement_date: str, location: str = None, *, source: str = None, real_time: bool = False ) -> pd.Series: """ GS End-of-day FX forward points for G3, G10, and EM crosses. :param asset: asset object loaded from security master :param settlement_date: relative date representation of expiration date e.g. 1m :param location: Example - "TKO", "LDN", "NYC" :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :return: fwd points """ df = _get_fxfwd_data( asset=asset, settlement_date=settlement_date, location=location, source=source, real_time=real_time, query_type=QueryType.FWD_POINTS, ) if real_time: series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(df['forwardPoint']) else: series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(df['fwdPoints']) series.dataset_ids = getattr(df, 'dataset_ids', ()) return series @plot_measure( (AssetClass.FX,), (AssetType.Cross,), [MeasureDependency(id_provider=_currencypair_to_tdapi_fx_vol_swap_asset, query_type=QueryType.STRIKE_VOL)], ) def vol_swap_strike( asset: Asset, expiry_tenor: str, strike_type: str = None, location: PricingLocation = None, *, source: str = None, real_time: bool = False, ) -> pd.Series: """ GS end-of-day FX Vol Swaps volatilities across major crosses. :param asset: asset object loaded from security master :param expiry_tenor: relative date representation of expiration date e.g. 1m :param strike_type: option type (e.g. Put, Call or Straddle ) :param location: Example - "TKO", "LDN", "NYC" :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :return: curve of vol swap strike """ df = _get_fx_vol_swap_data( asset=asset, expiry_tenor=expiry_tenor, strike_type=strike_type, location=location, source=source, real_time=real_time, query_type=QueryType.STRIKE_VOL, ) series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(df['strikeVol']) series.dataset_ids = getattr(df, 'dataset_ids', ()) return series @plot_measure((AssetClass.FX,), None, [QueryType.FORWARD_POINT]) def spot_carry( asset: Asset, tenor: str, annualized: FXSpotCarry = FXSpotCarry.DAILY, pricing_location: Optional[PricingLocation] = None, *, source: str = None, real_time: bool = False, ) -> pd.Series: """ Calculates carry using forward term structure i.e forwardpoints/spot with option to return it in annualized terms. :param asset: asset object loaded from security master :param tenor: relative date representation of expiration date e.g. 1m :param annualized: whether to annualize carry, one of annualized or daily :param pricing_location: EOD location if multiple available example - "HKG", "LDN", "NYC" :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :return: FX spot carry curve """ if real_time: raise NotImplementedError('realtime spot_carry not implemented') if tenor not in [ '1m', '2m', '3m', '4m', '5m', '6m', '7m', '8m', '9m', '10m', '11m', '1y', '15m', '18m', '21m', '2y', ]: raise MqValueError('tenor not included in dataset') cross = asset.get_identifier(AssetIdentifier.BLOOMBERG_ID) kwargs = dict( asset_class='FX', type='Forward', asset_parameters_pair=cross, asset_parameters_settlement_date=tenor, name_prefix='FX Forward', ) mqid = _get_tdapi_fxo_assets(**kwargs) q = GsDataApi.build_market_data_query([mqid], QueryType.FWD_POINTS, source=source, real_time=real_time) _logger.debug('q %s', q) df = _market_data_timed(q) if df.empty: return pd.Series(dtype=float) dataset_ids = getattr(df, 'dataset_ids', ()) ds = Dataset(dataset_ids[0]) location = pricing_location if pricing_location else PricingLocation.NYC start, end = DataContext.current.start_date, DataContext.current.end_date mq_df = ds.get_data(asset_id=mqid, start=start, end=end, pricingLocation=location.value) if mq_df.empty: return pd.Series(dtype=float) mq_df = mq_df.reset_index() mq_df['ann_factor'] = mq_df.apply(lambda x: 360 / (x.settlementDate - x.date).days, axis=1) mq_df = mq_df.set_index('date') if annualized == FXSpotCarry.ANNUALIZED: mq_df['carry'] = -1 * mq_df['ann_factor'] * mq_df['fwdPoints'] / mq_df['spot'] else: mq_df['carry'] = -1 * mq_df['fwdPoints'] / mq_df['spot'] series = ExtendedSeries(mq_df['carry'], name='spotCarry') series.dataset_ids = dataset_ids return series ================================================ FILE: gs_quant/timeseries/measures_helper.py ================================================ """ Copyright 2021 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from enum import Enum from numbers import Real from gs_quant.errors import MqValueError class EdrDataReference(Enum): DELTA_CALL = 'delta_call' DELTA_PUT = 'delta_put' SPOT = 'spot' class VolReference(Enum): DELTA_CALL = 'delta_call' DELTA_PUT = 'delta_put' DELTA_NEUTRAL = 'delta_neutral' NORMALIZED = 'normalized' SPOT = 'spot' FORWARD = 'forward' def preprocess_implied_vol_strikes_eq(strike_reference: VolReference = None, relative_strike: Real = None): if relative_strike is None and strike_reference != VolReference.DELTA_NEUTRAL: raise MqValueError('Relative strike must be provided if your strike reference is not delta_neutral') if strike_reference == VolReference.DELTA_NEUTRAL: raise MqValueError('delta_neutral strike reference is not supported for equities.') if strike_reference == VolReference.DELTA_PUT: relative_strike = abs(100 - relative_strike) relative_strike = relative_strike if strike_reference == VolReference.NORMALIZED else relative_strike / 100 ref_string = ( "delta" if strike_reference in (VolReference.DELTA_CALL, VolReference.DELTA_PUT, VolReference.DELTA_NEUTRAL) else strike_reference.value ) return ref_string, relative_strike ================================================ FILE: gs_quant/timeseries/measures_inflation.py ================================================ """ Copyright 2020 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import logging from collections import OrderedDict from enum import Enum from typing import Optional, Union import pandas as pd from gs_quant.api.gs.assets import GsAssetApi from gs_quant.api.gs.data import QueryType, GsDataApi from gs_quant.common import Currency as CurrencyEnum, AssetClass, AssetType, PricingLocation from gs_quant.data import DataContext from gs_quant.datetime import GsCalendar from gs_quant.errors import MqValueError from gs_quant.markets.securities import AssetIdentifier, Asset from gs_quant.timeseries import ASSET_SPEC, plot_measure, MeasureDependency, GENERIC_DATE from gs_quant.timeseries import ExtendedSeries, measures_rates as tm_rates, check_forward_looking from gs_quant.timeseries.measures import _asset_from_spec, _market_data_timed, _range_from_pricing_date, _get_custom_bd from gs_quant.timeseries.measures_rates import _get_term_struct_date _logger = logging.getLogger(__name__) class InflationIndexType(Enum): AUCPI = 'AUCPI' BECPHLTH = 'BECPHLTH' CACPI = 'CACPI' CPALBE = 'CPALBE' CPALEMU = 'CPALEMU' CPUPAXFE = 'CPUPAXFE' CPURNSA = 'CPURNSA' CPUS = 'CPUS' CPXTEMU = 'CPXTEMU' DNCPINEW = 'DNCPINEW' FRCPXTOB = 'FRCPXTOB' GKCPIUHL = 'GKCPIUHL' GKCPNEWL = 'GKCPNEWL' GRCP2010 = 'GRCP2010' GRCPTK = 'GRCPTK' IECPALL = 'IECPALL' IECPEUI = 'IECPEUI' IECPINEW = 'IECPINEW' ILCPI = 'ILCPI' INFINFY = 'INFINFY' ISCPIL = 'ISCPIL' ITCPI = 'ITCPI' ITCPNICT = 'ITCPNICT' JCPNGENF = 'JCPNGENF' KRCPI = 'KRCPI' MXNInfl = 'MXNInfl' NECPIND = 'NECPIND' NOCPI = 'NOCPI' POCPILB = 'POCPILB' PPFXFDE = 'PPFXFDE' RUCP2000 = 'RUCP2000' SACPI = 'SACPI' SPCPEU = 'SPCPEU' SPIPC = 'SPIPC' SWCPI = 'SWCPI' UKCPI = 'UKCPI' UKCPIH = 'UKCPIH' UKRPI = 'UKRPI' TESTCPI = 'TESTCPI' class TdapiInflationRatesDefaultsProvider: # flag to indicate that a given property should not be included in asset query EMPTY_PROPERTY = "null" def __init__(self, defaults: dict): self.defaults = defaults benchmark_mappings = {} for k, v in defaults.get("CURRENCIES").items(): for e in v: benchmark_mappings[k] = {e.get("IndexType"): e.get('Index')} self.defaults['MAPPING'] = benchmark_mappings def get_index_for_benchmark(self, currency: CurrencyEnum, benchmark: str): return self.defaults.get("MAPPING").get(currency.value).get(benchmark) INFLATION_RATES_DEFAULTS = { "CURRENCIES": { "EUR": [ {"IndexType": "CPXTEMU", "Index": "CPI-CPXTEMU", "pricingLocation": ["LDN"]}, {"IndexType": "FRCPXTOB", "Index": "CPI-FRCPXTOB", "pricingLocation": ["LDN"]}, ], "USD": [{"IndexType": "CPURNSA", "Index": "CPI-CPURNSA", "pricingLocation": ["NYC"]}], "GBP": [{"IndexType": "UKRPI", "Index": "CPI-UKRPI", "pricingLocation": ["LDN"]}], "JPY": [{"IndexType": "JCPNGENF", "Index": "CPI-JCPNGENF", "pricingLocation": ["TKO"]}], }, "COMMON": {"fixedRate": "ATM", "clearingHouse": "LCH", "terminationDate": "5y", "effectiveDate": "0b"}, } inflationRates_defaults_provider = TdapiInflationRatesDefaultsProvider(INFLATION_RATES_DEFAULTS) CURRENCY_TO_INDEX_BENCHMARK = { 'AUD': OrderedDict([('AUCPI', 'CPI-AUCPI')]), 'CAD': OrderedDict([('CACPI', 'CPI-CACPI')]), 'DKK': OrderedDict([('DNCPINEW', 'CPI-DNCPINEW')]), 'EUR': OrderedDict( [ ('CPXTEMU', 'CPI-CPXTEMU'), ('BECPHLTH', 'CPI-BECPHLTH'), ('CPALBE', 'CPI-CPALBE'), ('CPALEMU', 'CPI-CPALEMU'), ('FRCPXTOB', 'CPI-FRCPXTOB'), ('GKCPIUHL', 'CPI-GKCPIUHL'), ('GKCPNEWL', 'CPI-GKCPNEWL'), ('GRCP2010', 'CPI-GRCP2010'), ('GRCPTK', 'CPI-GRCPTK'), ('IECPALL', 'CPI-IECPALL'), ('IECPEUI', 'CPI-IECPEUI'), ('IECPINEW', 'CPI-IECPINEW'), ('ITCPI', 'CPI-ITCPI'), ('ITCPNICT', 'CPI-ITCPNICT'), ('MXNInfl', 'CPI-MXNInfl'), ('NECPIND', 'CPI-NECPIND'), ('SPCPEU', 'CPI-SPCPEU'), ('SPIPC', 'CPI-SPIPC'), ] ), 'GBP': OrderedDict([('UKRPI', 'CPI-UKRPI'), ('UKCPI', 'CPI-UKCPI'), ('UKCPIH', 'CPI-UKCPIH')]), 'ILS': OrderedDict([('ILCPI', 'CPI-ILCPI'), ('ISCPIL', 'CPI-ISCPIL')]), 'INR': OrderedDict([('INFINFY', 'CPI-INFINFY')]), 'JPY': OrderedDict([('JCPNGENF', 'CPI-JCPNGENF')]), 'KOR': OrderedDict([('KRCPI', 'CPI-KRCPI')]), 'NOK': OrderedDict([('NOCPI', 'CPI-NOCPI')]), 'PLN': OrderedDict([('POCPILB', 'CPI-POCPILB')]), 'RUB': OrderedDict([('RUCP2000', 'CPI-RUCP2000')]), 'SEK': OrderedDict([('SWCPI', 'CPI-SWCPI')]), 'USD': OrderedDict( [('CPURNSA', 'CPI-CPURNSA'), ('CPUPAXFE', 'CPI-CPUPAXFE'), ('CPUS', 'CPI-CPUS'), ('PPFXFDE', 'CPI-PPFXFDE')] ), 'ZAR': OrderedDict([('SACPI', 'CPI-SACPI')]), } CURRENCY_TO_DUMMY_INFLATION_SWAP_BBID = { 'EUR': 'MAJTD8XDA8EJZYRG', 'GBP': 'MAW75DV9777630QN', 'JPY': 'MA1CENMCA88VXJ28', 'USD': 'MA4016GCT3MDRYVY', } def _currency_to_tdapi_inflation_swap_rate_asset(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 = CURRENCY_TO_DUMMY_INFLATION_SWAP_BBID.get(bbid, asset.get_marquee_id()) return result def _get_tdapi_inflation_rates_assets(allow_many=False, **kwargs) -> Union[str, list]: # sanitize input for asset query. if "pricing_location" in kwargs: del kwargs["pricing_location"] assets = GsAssetApi.get_many_assets(**kwargs) if len(assets) == 0 and ('asset_parameters_clearing_house' in kwargs): # test without the clearing house if kwargs['asset_parameters_clearing_house'] == tm_rates._ClearingHouse.NONE.value: del kwargs['asset_parameters_clearing_house'] assets = GsAssetApi.get_many_assets(**kwargs) if len(assets) > 1: # term structure measures need multiple assets if ( ('asset_parameters_termination_date' not in kwargs) or ('asset_parameters_effective_date' not in kwargs) or allow_many ): return [asset.id for asset in assets] else: raise MqValueError('Specified arguments match multiple assets') elif len(assets) == 0: raise MqValueError('Specified arguments did not match any asset in the dataset' + str(kwargs)) else: return assets[0].id def _check_inflation_index_type(currency, benchmark_type: Union[InflationIndexType, str]) -> InflationIndexType: if isinstance(benchmark_type, str): if benchmark_type.upper() in InflationIndexType.__members__: benchmark_type = InflationIndexType[benchmark_type.upper()] else: raise MqValueError( benchmark_type + ' is not a valid index, pick one among ' + ', '.join([x.value for x in InflationIndexType]) ) if ( isinstance(benchmark_type, InflationIndexType) and benchmark_type.value not in CURRENCY_TO_INDEX_BENCHMARK[currency.value].keys() ): raise MqValueError('%s is not supported for %s', benchmark_type.value, currency.value) else: return benchmark_type def _get_inflation_swap_leg_defaults(currency: CurrencyEnum, benchmark_type: InflationIndexType = None) -> dict: pricing_location = tm_rates.CURRENCY_TO_PRICING_LOCATION.get(currency, PricingLocation.LDN) # default benchmark types if benchmark_type is None: benchmark_type = InflationIndexType(str(list(CURRENCY_TO_INDEX_BENCHMARK[currency.value].keys())[0])) benchmark_type_input = CURRENCY_TO_INDEX_BENCHMARK[currency.value].get( benchmark_type.value, "CPI-" + benchmark_type.value ) return dict(currency=currency, index_type=benchmark_type_input, pricing_location=pricing_location) def _get_inflation_swap_csa_terms(curr: str, inflationindextype: str) -> dict: return dict(csaTerms=curr + '-1') def _get_inflation_swap_data( asset: Asset, swap_tenor: str, index_type: str = None, forward_tenor: Optional[GENERIC_DATE] = None, clearing_house: tm_rates._ClearingHouse = None, source: str = None, real_time: bool = False, query_type: QueryType = QueryType.SWAP_RATE, location: PricingLocation = None, allow_many=False, request_id: Optional[str] = None, ) -> pd.DataFrame: if real_time: raise NotImplementedError('realtime inflation swap data not implemented') currency = CurrencyEnum(asset.get_identifier(AssetIdentifier.BLOOMBERG_ID)) if currency.value not in CURRENCY_TO_INDEX_BENCHMARK.keys(): raise NotImplementedError('Data not available for {} inflation swap rates'.format(currency.value)) index_type = _check_inflation_index_type(currency, index_type) clearing_house = tm_rates._check_clearing_house(clearing_house) defaults = _get_inflation_swap_leg_defaults(currency, index_type) if not (tm_rates._is_valid_relative_date_tenor(swap_tenor)): raise MqValueError('invalid swap tenor ' + swap_tenor) forward_tenor = tm_rates._check_forward_tenor(forward_tenor) fixed_rate = 'ATM' kwargs = dict( asset_class='Rates', type='InflationSwap', asset_parameters_termination_date=swap_tenor, asset_parameters_index=defaults['index_type'], asset_parameters_fixed_rate=fixed_rate, asset_parameters_clearing_house=clearing_house.value, asset_parameters_effective_date=forward_tenor, asset_parameters_notional_currency=currency.name, ) rate_mqids = _get_tdapi_inflation_rates_assets(allow_many=allow_many, **kwargs) if location is None: pricing_location = tm_rates._default_pricing_location(currency) else: pricing_location = PricingLocation(location) pricing_location = tm_rates._pricing_location_normalized(pricing_location, currency) where = dict(pricingLocation=pricing_location.value) entity_ids = [rate_mqids] if not isinstance(rate_mqids, list) else rate_mqids _logger.debug( f'where asset= {rate_mqids}, swap_tenor={swap_tenor}, index={defaults["index_type"]}, ' f'forward_tenor={forward_tenor}, pricing_location={pricing_location.value}, ' f'clearing_house={clearing_house.value}, notional_currency={currency.name}, ' f'request_id={request_id}' ) q = GsDataApi.build_market_data_query(entity_ids, query_type, where=where, source=source, real_time=real_time) _logger.debug(f'q: {q}, request_id: {request_id}') df = _market_data_timed(q) return df @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [MeasureDependency(id_provider=_currency_to_tdapi_inflation_swap_rate_asset, query_type=QueryType.SWAP_RATE)], ) def inflation_swap_rate( asset: Asset, swap_tenor: str, index_type: str = None, forward_tenor: Optional[GENERIC_DATE] = None, clearing_house: tm_rates._ClearingHouse = None, location: PricingLocation = None, *, source: str = None, real_time: bool = False, ) -> pd.Series: """ GS end-of-day Zero Coupon Inflation Swap 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 index_type: benchmark type e.g. UKRPI :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", "EUREX", "JSCC", "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 """ df = _get_inflation_swap_data( asset=asset, swap_tenor=swap_tenor, index_type=index_type, forward_tenor=forward_tenor, clearing_house=clearing_house, source=source, real_time=real_time, query_type=QueryType.SWAP_RATE, location=location, ) series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(df['swapRate']) series.dataset_ids = getattr(df, 'dataset_ids', ()) return series @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [MeasureDependency(id_provider=_currency_to_tdapi_inflation_swap_rate_asset, query_type=QueryType.SWAP_RATE)], ) def inflation_swap_term( asset: Asset, index_type: str = None, forward_tenor: Optional[GENERIC_DATE] = None, pricing_date: Optional[GENERIC_DATE] = None, clearing_house: tm_rates._ClearingHouse = None, location: PricingLocation = None, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Forward term structure of GS end-of-day inflation swaps. :param asset: asset object loaded from security master :param index_type: benchmark type e.g. UKRPI :param forward_tenor: absolute / relative date representation of forward starting point. Only 'Spot' for spot starting swaps currently supported. :param pricing_date: YYYY-MM-DD or relative date :param clearing_house: Example - "LCH", "EUREX", "JSCC", "CME" :param location: Example - "TKO", "LDN", "NYC" :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: inflation swap forward term curve """ currency = CurrencyEnum(asset.get_identifier(AssetIdentifier.BLOOMBERG_ID)) if currency.value not in CURRENCY_TO_INDEX_BENCHMARK.keys(): raise NotImplementedError('Data not available for {} inflation swap rates'.format(currency.value)) if location is None: location = tm_rates._default_pricing_location(currency) else: location = PricingLocation(location) calendar = location.value if pricing_date is not None and pricing_date in list(GsCalendar.get(calendar).holidays): raise MqValueError('Specified pricing date is a holiday in {} calendar'.format(calendar)) start, end = _range_from_pricing_date(calendar, pricing_date) with DataContext(start, end): df = _get_inflation_swap_data( asset=asset, swap_tenor=None, index_type=index_type, forward_tenor=forward_tenor, clearing_house=clearing_house, source=source, real_time=real_time, query_type=QueryType.SWAP_RATE, location=location, allow_many=True, request_id=request_id, ) if df.empty: series = ExtendedSeries(dtype=float) else: latest = df.index.max() # TODO: add forward tenor to latest. Technically series index should be latest + forwardTenor + terminationTenor # TODO: but that would make it hard to compare term structure btwn different forwardTenors # TODO: may be implemented some day when plot can handle different x-axes # TODO: As-is, this is consistent with other swap term measures axis handling _logger.info('selected pricing date %s', latest) df = df.loc[latest] biz_day = _get_custom_bd(calendar) df['expirationDate'] = df['terminationTenor'].apply(_get_term_struct_date, args=(latest, biz_day)) df = df.set_index('expirationDate') df = df.sort_index() df = df.loc[DataContext.current.start_date : DataContext.current.end_date] series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(df['swapRate']) series.dataset_ids = getattr(df, 'dataset_ids', ()) if series.empty: # Raise descriptive error if no data returned + historical date context check_forward_looking(None, source, 'inflation_swap_term') return series ================================================ FILE: gs_quant/timeseries/measures_portfolios.py ================================================ """ Copyright 2020 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import logging from typing import Optional, Union import pandas as pd import gs_quant.timeseries.measures_reports as ReportMeasures from gs_quant.api.gs.data import QueryType from gs_quant.api.gs.portfolios import GsPortfolioApi from gs_quant.entities.entity import EntityType from gs_quant.markets.portfolio_manager import PortfolioManager from gs_quant.timeseries import plot_measure_entity from gs_quant.timeseries.measures import _extract_series_from_df LOGGER = logging.getLogger(__name__) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.AUM]) def aum( portfolio_id: str, start_date: dt.date = None, end_date: dt.date = None, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Returns the Custom AUM uploaded for the portfolio :param portfolio_id: id of portfolio :param start_date: start date for getting aum :param end_date: end date for getting aum :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: portfolio aum values """ data = GsPortfolioApi.get_custom_aum(portfolio_id, start_date, end_date) df = data if isinstance(data, pd.DataFrame) else pd.DataFrame.from_records(data) df = df.set_index(pd.DatetimeIndex(df['date'])) return _extract_series_from_df(df, QueryType.AUM, True) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.FACTOR_EXPOSURE]) def portfolio_factor_exposure( portfolio_id: str, risk_model_id: str, factor_name: str, unit: str = 'Notional', benchmark_id: str = None, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Factor exposure data associated with a factor in a factor risk report :param portfolio_id: portfolio id :param risk_model_id: factor risk model id :param factor_name: factor name :param unit: unit in which the timeseries is returned (Notional or Percent) :param benchmark_id: optional benchmark id :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: Timeseries of factor exposure for requested factor """ pm = PortfolioManager(portfolio_id) report = pm.get_factor_risk_report(risk_model_id=risk_model_id, benchmark_id=benchmark_id) return ReportMeasures.factor_exposure(report.id, factor_name, unit) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.FACTOR_PNL]) def portfolio_factor_pnl( portfolio_id: str, risk_model_id: str, factor_name: str, unit: str = 'Notional', benchmark_id: str = None, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Factor PnL data associated with a factor in a factor risk report :param portfolio_id: portfolio id :param risk_model_id: factor risk model id :param factor_name: factor name :param unit: unit in which the timeseries is returned (Notional or Percent) :param benchmark_id: optional benchmark id :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: Timeseries of factor pnl for requested factor """ pm = PortfolioManager(portfolio_id) report = pm.get_factor_risk_report(risk_model_id=risk_model_id, benchmark_id=benchmark_id) return ReportMeasures.factor_pnl(report.id, factor_name, unit) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.FACTOR_PROPORTION_OF_RISK]) def portfolio_factor_proportion_of_risk( portfolio_id: str, risk_model_id: str, factor_name: str, benchmark_id: str = None, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Factor proportion of risk data associated with a factor in a factor risk report :param portfolio_id: portfolio id :param risk_model_id: factor risk model id :param factor_name: factor name :param benchmark_id: optional benchmark id :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: Timeseries of factor proportion of risk for requested factor """ pm = PortfolioManager(portfolio_id) report = pm.get_factor_risk_report(risk_model_id=risk_model_id, benchmark_id=benchmark_id) return ReportMeasures.factor_proportion_of_risk(report.id, factor_name) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.DAILY_RISK]) def portfolio_daily_risk( portfolio_id: str, risk_model_id: str, factor_name: str = 'Total', benchmark_id: str = None, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Daily risk data associated with a factor in a factor risk report :param portfolio_id: portfolio id :param risk_model_id: factor risk model id :param factor_name: factor name (must be "Factor", "Specific", or "Total") :param benchmark_id: optional benchmark id :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: Timeseries of daily risk for requested factor """ pm = PortfolioManager(portfolio_id) report = pm.get_factor_risk_report(risk_model_id=risk_model_id, benchmark_id=benchmark_id) return ReportMeasures.daily_risk(report.id, factor_name) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.ANNUAL_RISK]) def portfolio_annual_risk( portfolio_id: str, risk_model_id: str, factor_name: str = 'Total', benchmark_id: str = None, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Annual risk data associated with a factor in a factor risk report :param portfolio_id: portfolio id :param risk_model_id: factor risk model id :param factor_name: factor name (must be "Factor", "Specific", or "Total") :param benchmark_id: optional benchmark id :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: Timeseries of daily risk for requested factor """ pm = PortfolioManager(portfolio_id) report = pm.get_factor_risk_report(risk_model_id=risk_model_id, benchmark_id=benchmark_id) return ReportMeasures.annual_risk(report.id, factor_name) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.THEMATIC_EXPOSURE]) def portfolio_thematic_exposure( portfolio_id: str, basket_ticker: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Thematic exposure of a portfolio to a requested GS thematic flagship basket :param portfolio_id: portfolio id :param basket_ticker: ticker for thematic basket :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: Timeseries of daily thematic beta of portfolio to requested flagship basket """ pm = PortfolioManager(portfolio_id) thematic_report = pm.get_thematic_report() return ReportMeasures.thematic_exposure(thematic_report.id, basket_ticker) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.PNL]) def portfolio_pnl( portfolio_id: str, unit: str = 'Notional', *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ PNL from all holdings, if unit is Percent, geometrically aggregate over time frame :param portfolio_id: id of portfolio :param unit: by default uses mode as Notional, but can also be set to Percent :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: portfolio pnl """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.pnl(performance_report.id, unit) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.HIT_RATE]) def portfolio_hit_rate( portfolio_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Create a time series of the hit rate of a portfolio over a given rolling period :param portfolio_id: id of portfolio :param rolling_window: size of window to use :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: portfolio hit rate """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.hit_rate(performance_report.id, rolling_window) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.MAX_DRAWDOWN]) def portfolio_max_drawdown( portfolio_id: str, rolling_window: int, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Create a time series of the max drawdown of a portfolio over a given rolling period :param portfolio_id: id of portfolio :param rolling_window: size of window to use :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of portfolio hit rate """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.portfolio_max_drawdown(performance_report.id, rolling_window) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.MAX_DRAWDOWN]) def portfolio_drawdown_length( portfolio_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ The length in days between the peak and the trough of the maximum drawdown :param portfolio_id: id of portfolio :param rolling_window: size of window to use :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of the drawdown length """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.drawdown_length(performance_report.id, rolling_window) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.MAX_DRAWDOWN]) def portfolio_max_recovery_period( portfolio_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ The maximum number of days used to reach a previously broken price level over the stated timeframe. :param portfolio_id: id of portfolio :param rolling_window: size of window to use :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of max recovery period """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.max_recovery_period(performance_report.id, rolling_window) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.STANDARD_DEVIATION]) def portfolio_standard_deviation( portfolio_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Volatility of the total return over the stated time frame. :param portfolio_id: id of portfolio :param rolling_window: the rolling window to look back on :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of standard deviation """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.standard_deviation(performance_report.id, rolling_window) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.DOWNSIDE_RISK]) def portfolio_downside_risk( portfolio_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Volatility of the periodic returns that are lower than the mean return over the stated time frame. Standard deviation is calculated using all returns, downside risk is calculated using only the returns that are below the mean. :param portfolio_id: id of portfolio :param rolling_window: the rolling window to look back on :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of downside_risk """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.downside_risk(performance_report.id, rolling_window) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.DOWNSIDE_RISK]) def portfolio_semi_variance( portfolio_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Volatility of the periodic returns that are lower than the mean return over the stated time frame. Standard deviation is calculated using all returns, semi variance is calculated using only the returns that are below 0. :param portfolio_id: id of portfolio :param rolling_window: the rolling window to look back on :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of downside_risk """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.semi_variance(performance_report.id, rolling_window) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.KURTOSIS]) def portfolio_kurtosis( portfolio_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Kurtosis measure the peakedness or flatness of the return distribution over the state time frame. In a flat distribution the average value is more likely to occur. :param portfolio_id: id of portfolio :param rolling_window: the rolling window to look back on :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of kurtosis """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.kurtosis(performance_report.id, rolling_window) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.SKEWNESS]) def portfolio_skewness( portfolio_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Skewness measures the degree of symmetry of the return distribution over the stated time frame. If the left tail is more pronounced than the right tail, it is said the return have negative skewness. :param portfolio_id: id of portfolio :param rolling_window: the rolling window to look back on :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of skewness """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.skewness(performance_report.id, rolling_window) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.REALIZED_VAR]) def portfolio_realized_var( portfolio_id: str, rolling_window: Union[int, str], confidence_interval: float = 0.95, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ The maximum expected loss of the portfolios, calculated using the natural distribution of returns over a stated time frame and is based on a confidence level. :param portfolio_id: id of portfolio :param rolling_window: the rolling window to look back on :param confidence_interval: the rolling window to look back on :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of realized var """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.realized_var(performance_report.id, rolling_window, confidence_interval) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.TRACKING_ERROR]) def portfolio_tracking_error( portfolio_id: str, benchmark_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ The standard deviation of the excess return relative to the benchmark over the state time frame. Tracking Error = sqrt( sum((ExcessRi - ExcessRmean)^2) / (N-1) ) Where: ExcessRi = excess return in period i ExcessRmean = mean excess return during bull markets N = number of periods :param portfolio_id: id of portfolio :param rolling_window: the rolling window to look back on :param benchmark_id: the rolling window to look back on :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of tracking error """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.tracking_error(performance_report.id, benchmark_id, rolling_window) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.TRACKING_ERROR_BEAR]) def portfolio_tracking_error_bear( portfolio_id: str, benchmark_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ The standard deviation of the excess return relative to the benchmark over the state time frame, only counting the periods when benchmark returns were negative. Bear Tracking Error = sqrt( sum((ExcessRi - ExcessRmeanBear)^2) / (N-1) ) for all when Benchmark Return < 0 Where: ExcessRi = excess return in period i ExcessRmeanBear = mean excess return during bull markets N = number of periods :param portfolio_id: id of portfolio :param rolling_window: the rolling window to look back on :param benchmark_id: the rolling window to look back on :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of tracking error """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.tracking_error_bear(performance_report.id, benchmark_id, rolling_window) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.TRACKING_ERROR_BULL]) def portfolio_tracking_error_bull( portfolio_id: str, benchmark_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ The standard deviation of the excess return relative to the benchmark over the state time frame, only counting the periods when benchmark returns were positive. Bull Tracking Error = sqrt( sum((ExcessRi - ExcessRmeanBull)^2) / (N-1) ) for all when Benchmark Return > 0 Where: ExcessRi = excess return in period i ExcessRmeanBull = mean excess return during bull markets N = number of periods :param portfolio_id: id of portfolio :param rolling_window: the rolling window to look back on :param benchmark_id: the rolling window to look back on :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of tracking error """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.tracking_error_bull(performance_report.id, benchmark_id, rolling_window) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.SHARPE_RATIO]) def portfolio_sharpe_ratio( portfolio_id: str, risk_free_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Risk-adjusted measure that calculates the excess return over the risk free rate per unit of volatility Sharpe Ratio = (Rp - Rf) / σp Where: Rp = Portfolio Return Rf = Risk-Free Rate σp = Standard Deviation of Portfolio Return. :param portfolio_id: id of portfolio :param risk_free_id: id of risk-free asset :param rolling_window: size of the rolling window to look back on :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of sharpe ratio """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.portfolio_sharpe_ratio(performance_report.id, risk_free_id, rolling_window) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.CALMAR_RATIO]) def portfolio_calmar_ratio( portfolio_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Risk-adjusted performance metric that divides an investment strategy’s annualized rate of return by its maximum drawdown, focusing specifically on the strategy’s return per unit of downside risk. Calmar Ratio = Annualized Return / Max Drawdown :param portfolio_id: id of portfolio :param rolling_window: size of the rolling window to look back on :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of calmar ratio """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.calmar_ratio(performance_report.id, rolling_window) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.SORTINO_RATIO]) def portfolio_sortino_ratio( portfolio_id: str, comparison_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Risk-adjusted measure that calculates the excess return over the benchmark per unit of semi-variance Sortino Ratio = (Rp - Rb) / Downside Deviation Where: Rp = Portfolio Return Rb = Benchmark Return Downside Deviation = Standard Deviation of negative asset returns. :param portfolio_id: id of portfolio :param comparison_id: id of security to compare to :param rolling_window: size of the rolling window to look back on :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of the sortino ratio """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.sortino_ratio(performance_report.id, comparison_id, rolling_window) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.INFORMATION_RATIO]) def portfolio_information_ratio( portfolio_id: str, benchmark_id: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Risk-adjusted measure that calculates the excess return over the benchmark, per unit of tracking error volatility. It measures the consistency with which the portfolio is beating the benchmark. Information Ratio = (Rp - Rb) / Tracking Error Where: Rp = Portfolio Return Rb = Benchmark Return Tracking Error = Standard Deviation of the difference between the portfolio and benchmark returns. :param portfolio_id: id of portfolio :param benchmark_id: id of benchmark asset :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of the information ratio """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.information_ratio(performance_report.id, benchmark_id) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.INFORMATION_RATIO]) def portfolio_information_ratio_bull( portfolio_id: str, benchmark_id: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Risk-adjusted measure that calculates the excess return over the benchmark, per unit of tracking error volatility, counting only the periods when the benchmark return was positive. Information Ratio = (Rp - Rb) / Tracking Error Where: Rp = Portfolio Return Rb = Benchmark Return Tracking Error = Standard Deviation of the difference between the portfolio and benchmark returns. :param portfolio_id: id of portfolio :param benchmark_id: id of benchmark asset :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of the information ratio """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.information_ratio_bull(performance_report.id, benchmark_id) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.INFORMATION_RATIO]) def portfolio_information_ratio_bear( portfolio_id: str, benchmark_id: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Risk-adjusted measure that calculates the excess return over the benchmark, per unit of tracking error volatility, counting only the periods when the benchmark return was negative Information Ratio = (Rp - Rb) / Tracking Error Where: Rp = Portfolio Return Rb = Benchmark Return Tracking Error = Standard Deviation of the difference between the portfolio and benchmark returns. :param portfolio_id: id of portfolio :param benchmark_id: id of benchmark asset :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of the information ratio """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.information_ratio_bear(performance_report.id, benchmark_id) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.MODIGLIANI_RATIO]) def portfolio_modigliani_ratio( portfolio_id: str, benchmark_id: str, risk_free_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Modigliani RAP measures how much the portfolio would have returns if it had had the same risk as the benchmark. It is a linear transformation of the Sharpe Ratio, but the results are expressed in terms of performance. M2 = (Sharpe Ratio * σb) + Rf Where: Sharpe Ratio = Portfolio Sharpe Ratio σb = Standard Deviation of the Benchmark Return Rf = Risk Free Rate. :param portfolio_id: id of portfolio :param benchmark_id: id of benchmark asset :param risk_free_id: id of risk free asset :param rolling_window: size of window to use :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of the modigliani ratio """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.modigliani_ratio(performance_report.id, benchmark_id, risk_free_id, rolling_window) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.TREYNOR_RATIO]) def portfolio_treynor_measure( portfolio_id: str, risk_free_id: str, benchmark_id: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Risk-adjusted measure that calculates the excess return over the risk free rate per unit of beta relative to the benchmark. This is useful for assessing the excess return from each unit of systematic risk. Treynor Ratio = (Rp - Rf) / Beta Where: Rp = Portfolio Return Rf = Risk-Free Rate Beta = Portfolio Beta :param portfolio_id: id of portfolio :param risk_free_id: id of risk-free asset :param benchmark_id: id of benchmark asset :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of the treynor measure """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.treynor_measure(performance_report.id, risk_free_id, benchmark_id) def portfolio_jensen_alpha( portfolio_id: str, benchmark_id: str, risk_free_id: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Risk-Adjusted measure that calculates the actual return of the portfolio over and above the return predicted by the CAPM, given the portfolios beta and benchmark returns. Jensen's Alpha = Rp - [Rf + Beta * (Rb - Rf)] Where: Rp = Portfolio Return Rf = Risk-Free Rate Beta = Portfolio Beta Rb = Benchmark Return :param portfolio_id: id of portfolio :param risk_free_id: id of risk-free asset :param benchmark_id: id of benchmark asset :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of jensen alpha """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.jensen_alpha(performance_report.id, benchmark_id, risk_free_id) def portfolio_jensen_alpha_bear( portfolio_id: str, benchmark_id: str, risk_free_id: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Risk-Adjusted measure that calculates the actual return of the portfolio over and above the return predicted by the CAPM, given the portfolios beta and benchmark returns. calculated based only on the periods when the benchmark return was negative Jensen's Alpha = Rp - [Rf + Beta * (Rb - Rf)] Where: Rp = Portfolio Return Rf = Risk-Free Rate Beta = Portfolio Beta Rb = Benchmark Return :param portfolio_id: id of portfolio :param risk_free_id: id of risk-free asset :param benchmark_id: id of benchmark asset :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of jensen alpha """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.jensen_alpha_bear(performance_report.id, benchmark_id, risk_free_id) def portfolio_jensen_alpha_bull( portfolio_id: str, benchmark_id: str, risk_free_id: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Risk-Adjusted measure that calculates the actual return of the portfolio over and above the return predicted by the CAPM, given the portfolios beta and benchmark returns. calculated based only on the periods when the benchmark return was positive Jensen's Alpha = Rp - [Rf + Beta * (Rb - Rf)] Where: Rp = Portfolio Return Rf = Risk-Free Rate Beta = Portfolio Beta Rb = Benchmark Return :param portfolio_id: id of portfolio :param risk_free_id: id of risk-free asset :param benchmark_id: id of benchmark asset :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of jensen alpha """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.jensen_alpha_bull(performance_report.id, benchmark_id, risk_free_id) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.ALPHA]) def portfolio_alpha( portfolio_id: str, benchmark_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ This is the intercept from a regression analysis, where the portfolio's returns are regressed against the benchmark's returns. It represents the portion of the portfolio's return that is not explained by the benchmark's performance :param portfolio_id: id of portfolio :param rolling_window: size of the rolling window to look back on :param benchmark_id: id of benchmark asset :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of the beta """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.alpha(performance_report.id, benchmark_id, rolling_window) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.BETA]) def portfolio_beta( portfolio_id: str, benchmark_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Beta defined as the slope of the regression between report pnl and benchmark pnl :param portfolio_id: id of portfolio :param rolling_window: size of the rolling window to look back on :param benchmark_id: id of benchmark asset :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of the beta """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.portfolio_beta(performance_report.id, benchmark_id, rolling_window) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.CORRELATION]) def portfolio_correlation( portfolio_id: str, benchmark_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Correlation coefficient between the portfolio and the benchmark returns over the stated time frame :param portfolio_id: id of portfolio :param rolling_window: size of the rolling window to look back on :param benchmark_id: id of benchmark asset :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of the correlation coefficient """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.portfolio_correlation(performance_report.id, benchmark_id, rolling_window) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.R_SQUARED]) def portfolio_r_squared( portfolio_id: str, benchmark_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ A measure of how well the portfolio's performance correlates with the performance of the benchmark, and thus a measure of what portion of its performance may be explained by the performance of the benchmark. :param portfolio_id: id of portfolio :param rolling_window: size of the rolling window to look back on :param benchmark_id: id of benchmark asset :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: portfolio r squared """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.r_squared(performance_report.id, benchmark_id, rolling_window) @plot_measure_entity(EntityType.PORTFOLIO, [QueryType.CAPTURE_RATIO]) def portfolio_capture_ratio( portfolio_id: str, benchmark_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ A measure of how well portfolio is doing relative to the benchmark, defined as the ratio of the portfolio return to the benchmark return calculated on a daily basis and averaged over the stated time period. :param portfolio_id: id of portfolio :param benchmark_id: id of benchmark asset :param rolling_window: size of the rolling window to look back on :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: portfolio capture ratio """ pm = PortfolioManager(portfolio_id) performance_report = pm.get_performance_report() return ReportMeasures.capture_ratio(performance_report.id, benchmark_id, rolling_window) ================================================ FILE: gs_quant/timeseries/measures_rates.py ================================================ """ Copyright 2020 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import logging import re from collections import OrderedDict from enum import Enum from typing import Optional, Union, Dict, List import pandas as pd from gs_quant.api.gs.assets import GsAssetApi from gs_quant.api.gs.data import QueryType, GsDataApi from gs_quant.common import Currency as CurrencyEnum, AssetClass, AssetType, PricingLocation, SwapClearingHouse from gs_quant.data import DataContext, Dataset from gs_quant.datetime.gscalendar import GsCalendar from gs_quant.errors import MqValueError from gs_quant.instrument import IRSwap from gs_quant.markets.securities import AssetIdentifier, Asset 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, Entitlement from gs_quant.timeseries.measures import ( _market_data_timed, _range_from_pricing_date, _get_custom_bd, ExtendedSeries, SwaptionTenorType, _extract_series_from_df, GENERIC_DATE, _asset_from_spec, ASSET_SPEC, MeasureDependency, _logger, ) # TODO: Use gs_quant object class _ClearingHouse(Enum): LCH = 'LCH' EUREX = 'EUREX' JSCC = 'JSCC' CME = 'CME' NONE = 'NONE' class _SwapTenorType(Enum): FORWARD_TENOR = 'forward_tenor' SWAP_TENOR = 'swap_tenor' class EventType(Enum): MEETING = 'Meeting Forward' EOY = 'EOY Forward' SPOT = 'Spot' class RateType(Enum): ABSOLUTE = 'absolute' RELATIVE = 'relative' CCY_TO_CB = {'EUR': 'ecb', 'USD': 'frb', 'GBP': 'mpc'} CENTRAL_BANK_WATCH_START_DATE = dt.date(2016, 1, 1) class TdapiRatesDefaultsProvider: # flag to indicate that a given property should not be included in asset query EMPTY_PROPERTY = "null" def __init__(self, defaults: dict): self.defaults = defaults benchmark_mappings = {} for k, v in defaults.get("CURRENCIES").items(): benchmark_mappings[k] = {e.get("benchmarkType"): e.get('floatingRateOption') for e in v} self.defaults['MAPPING'] = benchmark_mappings def is_supported(self, currency: CurrencyEnum): return currency.value in self.defaults.get("CURRENCIES").keys() def get_floating_rate_option_for_benchmark(self, currency: CurrencyEnum, benchmark: str): return self.defaults.get("MAPPING").get(currency.value).get(benchmark) def get_swaption_parameter(self, currency, field, value=None): if value == self.EMPTY_PROPERTY: return None if value is not None: return value if isinstance(currency, str): currency_name = currency else: currency_name = currency.value for entry in [self.defaults.get("CURRENCIES").get(currency_name)[0], self.defaults.get("COMMON")]: if field in entry: value = entry[field][0] if isinstance(entry[field], list) else entry[field] return value SWAPTION_DEFAULTS = { "CURRENCIES": { "AUD": [ { "benchmarkType": "BBR", "floatingRateOption": "AUD-BBR-BBSW", "floatingRateTenor": ["6m", "3m"], "assetIdForAvailabilityCheck": "MAQHSC1PAF4X5H4B", "pricingLocation": ["TKO"], } ], "EUR": [ { "benchmarkType": "LIBOR", "floatingRateOption": "EUR-EURIBOR-TELERATE", "floatingRateTenor": ["6m", "3m"], "assetIdForAvailabilityCheck": "MAZB3PAH8JFVVT80", "pricingLocation": ["LDN"], }, { "benchmarkType": "EURIBOR", "floatingRateOption": "EUR-EURIBOR-TELERATE", "floatingRateTenor": ["6m", "3m"], "assetIdForAvailabilityCheck": "MAZB3PAH8JFVVT80", "pricingLocation": ["LDN"], }, ], "GBP": [ { "benchmarkType": "LIBOR", "floatingRateOption": "GBP-LIBOR-BBA", "floatingRateTenor": ["6m", "3m"], "assetIdForAvailabilityCheck": "MAX2SBXZRPYR3NTY", "pricingLocation": ["LDN"], }, { "benchmarkType": "SONIA", "floatingRateOption": "GBP-SONIA-COMPOUND", "floatingRateTenor": ["1y", "6m", "3m"], "assetIdForAvailabilityCheck": "MAQC2E5J9X6WGGCJ", "pricingLocation": ["LDN"], }, ], "JPY": [ { "benchmarkType": "LIBOR", "floatingRateOption": "JPY-LIBOR-BBA", "floatingRateTenor": ["6m"], "assetIdForAvailabilityCheck": "MATT7CA7PRA4B8YB", "pricingLocation": ["TKO"], } ], "KRW": [ { "benchmarkType": "KSDA", "floatingRateOption": "KRW-CD-KSDA-BLOOMBERG", "floatingRateTenor": ["3m"], "assetIdForAvailabilityCheck": "MAMNSGB00G4ZCWMP", "pricingLocation": ["TKO"], } ], "NZD": [ { "benchmarkType": "BBR", "floatingRateOption": "NZD-BBR-FRA", "floatingRateTenor": ["3m"], "assetIdForAvailabilityCheck": "MAHGK129ZCWCEG33", "pricingLocation": ["TKO"], } ], "USD": [ { "benchmarkType": "LIBOR", "floatingRateOption": "USD-LIBOR-BBA", "floatingRateTenor": ["3m", "6m"], "assetIdForAvailabilityCheck": "MAY0X3KRD4AN77E2", "strikeReference": ["ATM"], "pricingLocation": ["NYC"], }, { "benchmarkType": "SOFR", "floatingRateOption": "USD-SOFR-COMPOUND", "floatingRateTenor": ["1y", "6m", "3m"], "assetIdForAvailabilityCheck": "MANYJ1AWNEX5C7FY", "strikeReference": ["ATM"], "pricingLocation": ["NYC"], }, ], }, "COMMON": { "strikeReference": "ATM", "clearingHouse": "LCH", "terminationTenor": "5y", "expirationTenor": "1y", "effectiveDate": "0b", }, } swaptions_defaults_provider = TdapiRatesDefaultsProvider(SWAPTION_DEFAULTS) CURRENCY_TO_SWAP_RATE_BENCHMARK = { 'AUD': OrderedDict([('BBR', 'AUD-BBR-BBSW'), ('AONIA', 'AUD-AONIA-OIS-COMPOUND')]), 'BRL': {'CDI': 'BRR-CDI-COMPOUNDED'}, 'CAD': OrderedDict([('CDOR', 'CAD-BA-CDOR'), ('CORRA', 'CAD-CORRA-OIS-COMP')]), 'CHF': OrderedDict([('LIBOR', 'CHF-LIBOR-BBA'), ('SARON', 'CHF-SARON-OIS-COMPOUND')]), 'CLP': {'TNA': 'CLP-ICP-CAMARA'}, 'CNY': {'REPO': 'CNY-REPO RATE'}, 'COP': {'IBR': 'COP-IBR-ON'}, 'CZK': {'PRIBOR': 'CZK-PRIBOR-PRBO'}, 'DKK': OrderedDict([('CIBOR', 'DKK-CIBOR2-DKNA13'), ('OIS', 'DKK-DKKOIS-OIS-COMPOUND')]), 'EUR': OrderedDict( [('EURIBOR', 'EUR-EURIBOR-TELERATE'), ('EONIA', 'EUR-EONIA-OIS-COMPOUND'), ('EUROSTR', 'EUR-EUROSTR-COMPOUND')] ), 'GBP': OrderedDict([('LIBOR', 'GBP-LIBOR-BBA'), ('SONIA', 'GBP-SONIA-COMPOUND')]), 'HKD': {'HIBOR': 'HKD-HIBOR-HKAB'}, 'HUF': {'BIBOR': 'HUF-BIBOR-BUB'}, 'ILS': {'TELBOR': 'ILS-TELBOR-FCI'}, 'INR': {'MIBOR': 'INR-MIBOR-OIS-COMPOUND'}, 'JPY': OrderedDict([('LIBOR', 'JPY-LIBOR-BBA'), ('TONA', 'JPY-TONA-OIS-COMPOUND')]), 'KRW': {'KSDA': 'KRW-CD-KSDA-BLOOMBERG'}, 'MXN': {'TIIE': 'MXN-TIIE-FX'}, 'NOK': OrderedDict([('NIBOR', 'NOK-NIBOR-BBA'), ('NOWA', 'NOK-NOWA-OIS-COMPOUND')]), 'NZD': OrderedDict([('BBR', 'NZD-BBR-FRA'), ('NZIONA', 'NZD-NZIONA-OIS-COMPOUND')]), 'PLN': {'WIBOR': 'PLZ-WIBOR-WIBO'}, 'RUB': {'MOSPRIME': 'RUB-MOSPRIME-NFEA'}, 'SEK': OrderedDict([('STIBOR', 'SEK-STIBOR-SIDE'), ('SIOR', 'SEK-SIOR-OIS-COMPOUND')]), 'SGD': OrderedDict([('SOR', 'SGD-SOR-VWAP'), ('SORA', 'SGD-SORA-COMPOUND')]), 'THB': {'THOR': 'THB-THOR-COMPOUND'}, 'USD': OrderedDict( [('LIBOR', 'USD-LIBOR-BBA'), ('Fed_Funds', 'USD-Federal Funds-H.15-OIS-COMP'), ('SOFR', 'USD-SOFR-COMPOUND')] ), 'ZAR': {'JIBAR': 'ZAR-JIBAR-SAFEX'}, } # TODO Join into single object. BENCHMARK_TO_DEFAULT_FLOATING_RATE_TENORS = { 'BRR-CDI-COMPOUNDED': '1b', 'AUD-BBR-BBSW': '6m', 'AUD-AONIA-OIS-COMPOUND': '1y', 'CAD-BA-CDOR': '3m', 'CAD-CORRA-OIS-COMP': '3m', 'CHF-LIBOR-BBA': '6m', 'CHF-SARON-OIS-COMPOUND': '1y', 'CLP-ICP-CAMARA': '1b', 'CNY-REPO RATE': '1w', 'COP-IBR-ON': '1b', 'CZK-PRIBOR-PRBO': '6m', 'DKK-CIBOR2-DKNA13': '6m', 'DKK-DKKOIS-OIS-COMPOUND': '1y', 'EUR-EURIBOR-TELERATE': '6m', 'EUR-EUROSTR-COMPOUND': '1y', 'EUR-EONIA-OIS-COMPOUND': '1y', 'GBP-LIBOR-BBA': '6m', 'GBP-SONIA-COMPOUND': '1y', 'HKD-HIBOR-HKAB': '3m', 'HUF-BIBOR-BUB': '6m', 'INR-MIBOR-OIS-COMPOUND': '6m', 'ILS-TELBOR-FCI': '3m', 'JPY-LIBOR-BBA': '6m', 'JPY-TONA-OIS-COMPOUND': '1y', 'KRW-CD-KSDA-BLOOMBERG': '3m', 'MXN-TIIE-FX': '28d', 'NOK-NIBOR-BBA': '6m', 'NOK-NOWA-OIS-COMPOUND': '1y', 'NZD-BBR-FRA': '3m', 'NZD-NZIONA-OIS-COMPOUND': '1y', 'PLZ-WIBOR-WIBO': '6m', 'RUB-MOSPRIME-NFEA': '3m', 'SEK-STIBOR-SIDE': '6m', 'SEK-SIOR-OIS-COMPOUND': '1y', 'SGD-SOR-VWAP': '6m', 'SGD-SORA-COMPOUND': '3m', 'THB-THOR-COMPOUND': '3m', 'USD-LIBOR-BBA': '3m', 'USD-Federal Funds-H.15-OIS-COMP': '1y', 'USD-SOFR-COMPOUND': '1y', 'ZAR-JIBAR-SAFEX': '3m', } CURRENCY_TO_PRICING_LOCATION = { CurrencyEnum.JPY: PricingLocation.TKO, CurrencyEnum.USD: PricingLocation.NYC, CurrencyEnum.AUD: PricingLocation.TKO, CurrencyEnum.NZD: PricingLocation.TKO, CurrencyEnum.CNY: PricingLocation.HKG, CurrencyEnum.HKD: PricingLocation.HKG, CurrencyEnum.INR: PricingLocation.HKG, CurrencyEnum.KRW: PricingLocation.HKG, CurrencyEnum.SGD: PricingLocation.HKG, CurrencyEnum.CAD: PricingLocation.NYC, CurrencyEnum.EUR: PricingLocation.LDN, CurrencyEnum.GBP: PricingLocation.LDN, CurrencyEnum.CHF: PricingLocation.LDN, CurrencyEnum.DKK: PricingLocation.LDN, CurrencyEnum.NOK: PricingLocation.LDN, CurrencyEnum.SEK: PricingLocation.LDN, CurrencyEnum.BRL: PricingLocation.NYC, CurrencyEnum.COP: PricingLocation.NYC, CurrencyEnum.CLP: PricingLocation.NYC, CurrencyEnum.MXN: PricingLocation.NYC, } CURRENCY_TO_DUMMY_SWAP_BBID = { 'CHF': 'MAW25BGQJH9P6DPT', 'EUR': 'MAA9MVX15AJNQCVG', 'GBP': 'MA6QCAP9B7ABS9HA', 'JPY': 'MAEE219J5ZP0ZKRK', 'SEK': 'MAETMVTPNP3199A5', 'USD': 'MAFRSWPAF5QPNTP2', 'DKK': 'MAF131NKWVRESFYA', 'NOK': 'MA25DW5ZGC1BSC8Y', 'HKD': 'MABRNGY8XRFVC36N', 'NZD': 'MAH16NHE1HBN0FBZ', 'AUD': 'MAY8147CRK0ZP53B', 'CNY': 'MA4K1D8HH2R0RQY5', 'CAD': 'MANJ8SS88WJ6N28Q', 'KRW': 'MAP55AXG5SQVS6C5', 'INR': 'MA20JHJXN1PD5HGE', 'SGD': 'MA5CQFHYBPH9E5BS', 'BRL': 'MATPPVN02HJ4M9NS', 'COP': 'MADA0AGFQ65CTMWF', 'CLP': 'MAP8A0SHH9Q86SXC', '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', 'GBPUSD': 'MA3JTR4HSC63H4V6', 'AUDUSD': 'MAD4VBRWYXFSY1N4', 'NZDUSD': 'MA1YHQMZVTM3VBWT', 'USDSEK': 'MA2APZREBGDMME83', 'USDNOK': 'MA0K3W6FKH6K1KJE', 'USDDKK': 'MA328HZB86DYSWSJ', 'USDCAD': 'MAT8JNEE2GN5NES6', 'USDCHF': 'MABNGGTNB9A0TKCG', 'USDJPY': 'MAMZ9YG8AF3HQ18C', } CURRENCY_TO_CSA_DEFAULT_MAP = {'USD': 'USD-SOFR', 'EUR': 'EUR-EUROSTR'} def _pricing_location_normalized(location: PricingLocation, ccy: CurrencyEnum) -> PricingLocation: if location == PricingLocation.HKG or location == PricingLocation.TKO: if ccy in CURRENCY_TO_PRICING_LOCATION.keys() and PricingLocation.HKG == CURRENCY_TO_PRICING_LOCATION.get( ccy, PricingLocation.LDN ): return PricingLocation.HKG else: return PricingLocation.TKO else: return location def _default_pricing_location(ccy: CurrencyEnum) -> PricingLocation: if ccy in CURRENCY_TO_PRICING_LOCATION.keys(): return CURRENCY_TO_PRICING_LOCATION.get(ccy, PricingLocation.LDN) else: raise MqValueError('No default location set for currency ' + ccy.value + ', please provide one.') def _cross_to_fxfwd_xcswp_asset(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 = CROSS_BBID_TO_DUMMY_OISXCCY_ASSET.get(bbid, asset.get_marquee_id()) return result def _currency_to_tdapi_swap_rate_asset(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 = CURRENCY_TO_DUMMY_SWAP_BBID.get(bbid, asset.get_marquee_id()) return result def _currency_to_tdapi_swap_rate_asset_for_intraday(asset_spec: ASSET_SPEC) -> str: return 'MACF6R4J5FY4KGBZ' 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) if bbid is None or (allowed_bbids and bbid not in allowed_bbids): return asset.get_marquee_id() try: result = swaptions_defaults_provider.get_swaption_parameter(bbid, "assetIdForAvailabilityCheck") except TypeError: logging.info("No assetIdForAvailabilityCheck for" + bbid) return asset.get_marquee_id() return result def _currency_to_tdapi_midcurve_asset(asset_spec: ASSET_SPEC) -> str: return _currency_to_tdapi_asset_base(asset_spec, ['GBP', 'EUR', 'USD']) def _currency_to_tdapi_swaption_rate_asset(asset_spec: ASSET_SPEC) -> str: return _currency_to_tdapi_asset_base(asset_spec) def _currency_to_tdapi_basis_swap_rate_asset(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 if bbid == 'EUR': result = 'MAGRG2VT11GQ2RQ9' elif bbid == 'GBP': result = 'MAHCYNB3V75JC5Q8' elif bbid == 'JPY': result = 'MAXVRBEZCJVH0C4V' elif bbid == 'USD': result = 'MAQB1PGEJFCET3GG' elif bbid == 'CAD': result = 'MARVD2E65AWEXXBA' elif bbid == 'AUD': result = 'MAY8H7HCNZ85FJKM' elif bbid == 'NZD': result = 'MAWK15C0P3SM6C7Q' elif bbid == 'SEK': result = 'MAS2NJCYHDHP8P0X' elif bbid == 'NOK': result = 'MAPXC5YBPZJZXYMZ' elif bbid == 'DKK': result = 'MA2164KK5DMYA561' elif bbid == 'CHF': result = 'MA7ZHB9T0PF1SB96' else: return asset.get_marquee_id() return result def _match_floating_tenors(swap_args) -> dict: payer_index = swap_args['asset_parameters_payer_rate_option'] receiver_index = swap_args['asset_parameters_receiver_rate_option'] if payer_index != receiver_index: if 'SOFR' in payer_index: swap_args['asset_parameters_payer_designated_maturity'] = swap_args[ 'asset_parameters_receiver_designated_maturity' ] if swap_args['asset_parameters_payer_designated_maturity'] == "12m": swap_args['asset_parameters_payer_designated_maturity'] = "1y" elif 'SOFR' in receiver_index: swap_args['asset_parameters_receiver_designated_maturity'] = swap_args[ 'asset_parameters_payer_designated_maturity' ] if swap_args['asset_parameters_receiver_designated_maturity'] == "12m": swap_args['asset_parameters_receiver_designated_maturity'] = "1y" elif 'LIBOR' in payer_index or 'EURIBOR' in payer_index or 'STIBOR' in payer_index: swap_args['asset_parameters_receiver_designated_maturity'] = swap_args[ 'asset_parameters_payer_designated_maturity' ] elif 'LIBOR' in receiver_index or 'EURIBOR' in receiver_index or 'STIBOR' in receiver_index: swap_args['asset_parameters_payer_designated_maturity'] = swap_args[ 'asset_parameters_receiver_designated_maturity' ] return swap_args def _get_tdapi_rates_assets(allow_many=False, **kwargs) -> Union[str, list]: # sanitize input for asset query. if "pricing_location" in kwargs: del kwargs["pricing_location"] assets = GsAssetApi.get_many_assets(**kwargs) # change order of basis swap legs and check if swap in dataset if len(assets) == 0 and ('asset_parameters_payer_rate_option' in kwargs): # flip legs kwargs['asset_parameters_payer_rate_option'], kwargs['asset_parameters_receiver_rate_option'] = ( kwargs['asset_parameters_receiver_rate_option'], kwargs['asset_parameters_payer_rate_option'], ) ( kwargs['asset_parameters_payer_designated_maturity'], kwargs['asset_parameters_receiver_designated_maturity'], ) = ( kwargs['asset_parameters_receiver_designated_maturity'], kwargs['asset_parameters_payer_designated_maturity'], ) assets = GsAssetApi.get_many_assets(**kwargs) if len(assets) > 1: # term structure measures need multiple assets if ( ('asset_parameters_termination_date' not in kwargs) or ('asset_parameters_effective_date' not in kwargs) or allow_many ): return [asset.id for asset in assets] else: raise MqValueError('Specified arguments match multiple assets') elif len(assets) == 0: raise MqValueError('Specified arguments did not match any asset in the dataset' + str(kwargs)) else: return assets[0].id def _check_forward_tenor(forward_tenor) -> GENERIC_DATE: if isinstance(forward_tenor, dt.date): return forward_tenor elif forward_tenor in ['Spot', 'spot', 'SPOT']: return '0b' elif not ( _is_valid_relative_date_tenor(forward_tenor) or re.fullmatch('(imm[1-4]|frb[1-9]|ecb[1-9])', forward_tenor) ): raise MqValueError('invalid forward tenor ' + forward_tenor) else: return forward_tenor class BenchmarkType(Enum): LIBOR = 'LIBOR' EURIBOR = 'EURIBOR' EUROSTR = 'EUROSTR' STIBOR = 'STIBOR' OIS = 'OIS' CDKSDA = 'CDKSDA' SOFR = 'SOFR' SARON = 'SARON' EONIA = 'EONIA' SONIA = 'SONIA' TONA = 'TONA' Fed_Funds = 'Fed_Funds' NIBOR = 'NIBOR' CIBOR = 'CIBOR' BBR = 'BBR' BA = 'BA' KSDA = 'KSDA' REPO = 'REPO' SOR = 'SOR' HIBOR = 'HIBOR' MIBOR = 'MIBOR' CDOR = 'CDOR' CDI = 'CDI' TNA = 'TNA' IBR = 'IBR' TIIE = 'TIIE' AONIA = 'AONIA' NZIONA = 'NZIONA' NOWA = 'NOWA' CORRA = 'CORRA' SIOR = 'SIOR' SORA = 'SORA' def _check_benchmark_type( currency, benchmark_type: Union[BenchmarkType, str], nothrow: bool = False ) -> Union[BenchmarkType, str]: if isinstance(benchmark_type, str): if benchmark_type.upper() in BenchmarkType.__members__: benchmark_type = BenchmarkType[benchmark_type.upper()] elif benchmark_type in ['fed_funds', 'Fed_Funds', 'FED_FUNDS']: benchmark_type = BenchmarkType.Fed_Funds elif benchmark_type in ['estr', 'ESTR', 'eurostr', 'EuroStr']: benchmark_type = BenchmarkType.EUROSTR elif not nothrow: raise MqValueError( f'{benchmark_type} is not valid, pick one among ' + ', '.join([x.value for x in BenchmarkType]) ) else: return benchmark_type if ( isinstance(benchmark_type, BenchmarkType) and benchmark_type.value not in CURRENCY_TO_SWAP_RATE_BENCHMARK[currency.value].keys() ): raise MqValueError(f'{benchmark_type.value} is not supported for {currency.value}') else: return benchmark_type def _check_clearing_house(clearing_house: Union[_ClearingHouse, str]) -> _ClearingHouse: if isinstance(clearing_house, str) and clearing_house.upper() in _ClearingHouse.__members__: clearing_house = _ClearingHouse[clearing_house.upper()] if clearing_house is None: return _ClearingHouse.LCH elif isinstance(clearing_house, _ClearingHouse): return clearing_house else: raise MqValueError( 'invalid clearing house: ' + clearing_house + ' choose one among ' + ', '.join([ch.value for ch in _ClearingHouse]) ) def _check_tenor_type(tenor_type: _SwapTenorType) -> _SwapTenorType: if isinstance(tenor_type, str) and tenor_type.upper() in _SwapTenorType.__members__: tenor_type = _SwapTenorType[tenor_type.upper()] if tenor_type is None: return _SwapTenorType.FORWARD_TENOR elif isinstance(tenor_type, _SwapTenorType): return tenor_type else: raise MqValueError( 'invalid tenor_type: ' + tenor_type + ' choose one among ' + ', '.join([ch.value for ch in _SwapTenorType]) ) def _check_term_structure_tenor(tenor_type: _SwapTenorType, tenor: str) -> Dict: if tenor_type == _SwapTenorType.FORWARD_TENOR: tenor = _check_forward_tenor(tenor) tenor_to_plot = 'terminationTenor' tenor_dataset_field = 'asset_parameters_effective_date' elif not re.fullmatch('(\\d+)([bdwmy])', tenor) or re.fullmatch('(frb[1-9])', tenor): raise MqValueError('invalid swap tenor ' + tenor) else: tenor_to_plot = 'effectiveTenor' tenor_dataset_field = 'asset_parameters_termination_date' return dict(tenor=tenor, tenor_to_plot=tenor_to_plot, tenor_dataset_field=tenor_dataset_field) def _get_benchmark_type(currency: CurrencyEnum, benchmark_type: BenchmarkType = None): if benchmark_type is None: if currency == CurrencyEnum.EUR: benchmark_type = BenchmarkType.EURIBOR elif currency == CurrencyEnum.SEK: benchmark_type = BenchmarkType.STIBOR else: benchmark_type = BenchmarkType(str(list(CURRENCY_TO_SWAP_RATE_BENCHMARK[currency.value].keys())[0])) benchmark_type_input = CURRENCY_TO_SWAP_RATE_BENCHMARK[currency.value][benchmark_type.value] return benchmark_type_input def _get_swap_leg_defaults( currency: CurrencyEnum, benchmark_type: Union[BenchmarkType, str] = None, floating_rate_tenor: str = None ) -> dict: pricing_location = CURRENCY_TO_PRICING_LOCATION.get(currency, PricingLocation.LDN) # default benchmark types if not isinstance(benchmark_type, str): benchmark_type_input = _get_benchmark_type(currency, benchmark_type) else: benchmark_type_input = benchmark_type # default floating index if floating_rate_tenor is None: if benchmark_type_input in BENCHMARK_TO_DEFAULT_FLOATING_RATE_TENORS: floating_rate_tenor = BENCHMARK_TO_DEFAULT_FLOATING_RATE_TENORS[benchmark_type_input] else: raise MqValueError(f"{benchmark_type_input} has no default fixing tenor, please specify one") return dict( currency=currency, benchmark_type=benchmark_type_input, floating_rate_tenor=floating_rate_tenor, pricing_location=pricing_location, ) def _get_swap_csa_terms(curr: str, benchmark_type: str) -> dict: euribor_index = CURRENCY_TO_SWAP_RATE_BENCHMARK['EUR'][BenchmarkType.EURIBOR.value] usd_libor_index = CURRENCY_TO_SWAP_RATE_BENCHMARK['USD'][BenchmarkType.LIBOR.value] estr_index = CURRENCY_TO_SWAP_RATE_BENCHMARK['EUR'][BenchmarkType.EUROSTR.value] if benchmark_type in [euribor_index, usd_libor_index]: return {} elif benchmark_type == estr_index: return dict(csaTerms=curr + '-EuroSTR') else: return dict(csaTerms=curr + '-1') def _get_basis_swap_csa_terms(curr: str, payer_benchmark: str, receiver_benchmark: str) -> dict: benchmarks = [payer_benchmark, receiver_benchmark] euribor_index: str = CURRENCY_TO_SWAP_RATE_BENCHMARK['EUR'][BenchmarkType.EURIBOR.value] usd_libor_index: str = CURRENCY_TO_SWAP_RATE_BENCHMARK['USD'][BenchmarkType.LIBOR.value] estr_index: str = CURRENCY_TO_SWAP_RATE_BENCHMARK['EUR'][BenchmarkType.EUROSTR.value] if (euribor_index in benchmarks) or (usd_libor_index in benchmarks): return {} # different csaTerms after SOFR and ESTR transitions for a given asset elif estr_index in benchmarks: return dict(csaTerms=curr + '-EuroSTR') else: return dict(csaTerms=curr + '-1') def _get_swap_data( asset: Asset, swap_tenor: str, benchmark_type: str = None, floating_rate_tenor: str = None, forward_tenor: Optional[GENERIC_DATE] = None, clearing_house: _ClearingHouse = None, source: str = None, real_time: bool = False, location: PricingLocation = None, query_type: QueryType = QueryType.SWAP_RATE, ) -> pd.DataFrame: if real_time and not (query_type == QueryType.SWAP_RATE): raise NotImplementedError('realtime swap_rate not implemented for anything but rates') currency = CurrencyEnum(asset.get_identifier(AssetIdentifier.BLOOMBERG_ID)) if currency.value not in CURRENCY_TO_SWAP_RATE_BENCHMARK.keys(): raise NotImplementedError('Data not available for {} swap rates'.format(currency.value)) 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) or re.fullmatch('(frb[1-9]|ecb[1-6])', forward_tenor)): raise MqValueError('invalid swap tenor ' + swap_tenor) if not re.fullmatch('(\\d+)([bdwmy])', defaults['floating_rate_tenor']): raise MqValueError( 'invalid floating rate tenor ' + defaults['floating_rate_tenor'] + ' for index: ' + defaults['benchmark_type'] ) forward_tenor = _check_forward_tenor(forward_tenor) fixed_rate = 'ATM' if location is None: pricing_location = _default_pricing_location(currency) else: pricing_location = PricingLocation(location) kwargs = dict( asset_class='Rates', type='Swap', asset_parameters_termination_date=swap_tenor, asset_parameters_floating_rate_option=defaults['benchmark_type'], asset_parameters_fixed_rate=fixed_rate, asset_parameters_clearing_house=clearing_house.value, asset_parameters_floating_rate_designated_maturity=defaults['floating_rate_tenor'], asset_parameters_effective_date=forward_tenor, asset_parameters_notional_currency=currency.name, ) rate_mqid = _get_tdapi_rates_assets(**kwargs) _logger.debug( 'where asset= %s, swap_tenor=%s, benchmark_type=%s, floating_rate_tenor=%s, forward_tenor=%s, ' 'pricing_location=%s', rate_mqid, swap_tenor, defaults['benchmark_type'], defaults['floating_rate_tenor'], forward_tenor, pricing_location.value, ) pricing_location = _pricing_location_normalized(pricing_location, currency) where = dict(pricingLocation=pricing_location.value) q = GsDataApi.build_market_data_query([rate_mqid], query_type, where=where, source=source, real_time=real_time) _logger.debug('q %s', q) df = _market_data_timed(q) 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, csa: str = None, real_time: bool = False, location: PricingLocation = None, ) -> pd.DataFrame: currency = CurrencyEnum(asset.get_identifier(AssetIdentifier.BLOOMBERG_ID)) benchmark_type = _check_benchmark_type(currency, benchmark_type, True) clearing_house = SwapClearingHouse.LCH if csa in ['EUREX', 'JSCC', 'CME']: clearing_house = SwapClearingHouse(csa) 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=clearing_house, 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.effective_date = forward_tenor _logger.debug(f'where builder={builder.as_dict()}') location = location or PricingLocation.NYC q = GsDataApi.get_mxapi_backtest_data(builder, close_location=location.value, real_time=real_time, csa=csa) return q def _get_term_struct_date(tenor: Union[str, dt.datetime], index: dt.datetime, business_day) -> dt.datetime: if isinstance(tenor, (dt.datetime, dt.date)): return tenor try: year, month, day = tenor.split('-') return dt.datetime(int(year), int(month), int(day)) except ValueError: if tenor == '0b': return index + business_day - business_day else: return index + _to_offset(tenor) + business_day - business_day @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [MeasureDependency(id_provider=_currency_to_tdapi_swap_rate_asset, query_type=QueryType.SWAP_ANNUITY)], ) def swap_annuity( asset: Asset, swap_tenor: str, benchmark_type: str = None, floating_rate_tenor: str = None, forward_tenor: Optional[GENERIC_DATE] = None, clearing_house: _ClearingHouse = None, location: PricingLocation = None, *, source: str = None, real_time: bool = False, ) -> pd.Series: """ GS end-of-day Fixed-Floating interest rate swap(IRS) annuity values in years for paying leg 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", "EUREX", "JSCC", "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: annuity of swap """ df = _get_swap_data( asset=asset, swap_tenor=swap_tenor, benchmark_type=benchmark_type, floating_rate_tenor=floating_rate_tenor, forward_tenor=forward_tenor, clearing_house=clearing_house, source=source, real_time=real_time, query_type=QueryType.SWAP_ANNUITY, location=location, ) series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(abs(df['swapAnnuity'] * 1e4 / 1e8)) series.dataset_ids = getattr(df, 'dataset_ids', ()) return series @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [MeasureDependency(id_provider=_currency_to_tdapi_swaption_rate_asset, query_type=QueryType.SWAPTION_PREMIUM)], ) def swaption_premium( asset: Asset, expiration_tenor: str = None, termination_tenor: str = None, relative_strike: str = None, benchmark_type: str = None, floating_rate_tenor: str = None, clearing_house: str = None, location: PricingLocation = None, *, source: str = None, real_time: bool = False, ) -> pd.Series: """ GS end-of-day premium for swaption. :param asset: asset object loaded from security master :param expiration_tenor: relative date representation of expiration date on the option e.g. 3m :param termination_tenor: relative date representation of the instrument's expiration date e.g. 1y :param relative_strike: strike level relative to at the money e.g. 10 or ATM+10 :param benchmark_type: benchmark type e.g. LIBOR :param floating_rate_tenor: floating index rate :param clearing_house: Example - "LCH", "EUREX", "JSCC", "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: swaption implied normal volatility curve """ df = _get_swaption_measure( asset, benchmark_type, floating_rate_tenor, "0b", expiration_tenor, termination_tenor, relative_strike, clearing_house, source=source, real_time=real_time, start=DataContext.current.start_date, end=DataContext.current.end_date, query_type=QueryType.SWAPTION_PREMIUM, location=location, ) return _extract_series_from_df(df, QueryType.SWAPTION_PREMIUM) @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [MeasureDependency(id_provider=_currency_to_tdapi_swaption_rate_asset, query_type=QueryType.SWAPTION_ANNUITY)], ) def swaption_annuity( asset: Asset, expiration_tenor: str = None, termination_tenor: str = None, relative_strike: float = None, benchmark_type: str = None, floating_rate_tenor: str = None, clearing_house: str = None, location: PricingLocation = None, *, source: str = None, real_time: bool = False, ) -> pd.Series: """ GS end-of-day annuity for swaption. :param asset: asset object loaded from security master :param expiration_tenor: relative date representation of expiration date on the option e.g. 3m :param termination_tenor: relative date representation of the instrument's expiration date e.g. 1y :param relative_strike: strike level relative to at the money e.g. 10 or ATM+10 :param benchmark_type: benchmark type e.g. LIBOR :param floating_rate_tenor: floating index rate :param clearing_house: Example - "LCH", "EUREX", "JSCC", "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: swaption implied normal volatility curve """ df = _get_swaption_measure( asset, benchmark_type, floating_rate_tenor, "0b", expiration_tenor, termination_tenor, relative_strike, clearing_house, source=source, real_time=real_time, start=DataContext.current.start_date, end=DataContext.current.end_date, query_type=QueryType.SWAPTION_ANNUITY, location=location, ) return _extract_series_from_df(df, QueryType.SWAPTION_ANNUITY) @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [MeasureDependency(id_provider=_currency_to_tdapi_midcurve_asset, query_type=QueryType.MIDCURVE_PREMIUM)], ) def midcurve_premium( asset: Asset, expiration_tenor: str, forward_tenor: str, termination_tenor: str, relative_strike: float = None, benchmark_type: str = None, floating_rate_tenor: str = None, clearing_house: str = None, location: PricingLocation = None, *, source: str = None, real_time: bool = False, ) -> pd.Series: """ GS end-of-day premium for midcurve :param asset: asset object loaded from security master :param expiration_tenor: relative date representation of expiration date on the option e.g. 3m :param forward_tenor: relative date representation of swap's start date after option expiry e.g. 2y :param termination_tenor: relative date representation of the instrument's expiration date e.g. 1y :param relative_strike: strike level relative to at the money e.g. 10 or ATM+10 :param benchmark_type: benchmark type e.g. LIBOR :param floating_rate_tenor: floating index rate :param clearing_house: Example - "LCH", "EUREX", "JSCC", "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: swaption implied normal volatility curve """ df = _get_swaption_measure( asset, benchmark_type, floating_rate_tenor, forward_tenor, expiration_tenor, termination_tenor, relative_strike, clearing_house, source=source, real_time=real_time, start=DataContext.current.start_date, end=DataContext.current.end_date, query_type=QueryType.MIDCURVE_PREMIUM, location=location, ) return _extract_series_from_df(df, QueryType.MIDCURVE_PREMIUM) @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [MeasureDependency(id_provider=_currency_to_tdapi_midcurve_asset, query_type=QueryType.MIDCURVE_ANNUITY)], ) def midcurve_annuity( asset: Asset, expiration_tenor: str, forward_tenor: str, termination_tenor: str, relative_strike: float = None, benchmark_type: str = None, floating_rate_tenor: str = None, clearing_house: str = None, location: PricingLocation = None, *, source: str = None, real_time: bool = False, ) -> pd.Series: """ GS end-of-day annuity for midcurve. :param asset: asset object loaded from security master :param expiration_tenor: relative date representation of expiration date on the option e.g. 3m :param forward_tenor: relative date representation of swap's start date after option expiry e.g. 2y :param termination_tenor: relative date representation of the instrument's expiration date e.g. 1y :param relative_strike: strike level relative to at the money e.g. 10 or ATM+10 :param benchmark_type: benchmark type e.g. LIBOR :param floating_rate_tenor: floating index rate :param clearing_house: Example - "LCH", "EUREX", "JSCC", "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: swaption implied normal volatility curve """ df = _get_swaption_measure( asset, benchmark_type, floating_rate_tenor, forward_tenor, expiration_tenor, termination_tenor, relative_strike, clearing_house, source=source, real_time=real_time, start=DataContext.current.start_date, end=DataContext.current.end_date, query_type=QueryType.MIDCURVE_ANNUITY, location=location, ) return _extract_series_from_df(df, QueryType.MIDCURVE_ANNUITY) @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [MeasureDependency(id_provider=_currency_to_tdapi_swaption_rate_asset, query_type=QueryType.ATM_FWD_RATE)], ) def swaption_atm_fwd_rate( asset: Asset, expiration_tenor: str = None, termination_tenor: str = None, benchmark_type: str = None, floating_rate_tenor: str = None, clearing_house: str = None, location: PricingLocation = None, *, source: str = None, real_time: bool = False, ) -> pd.Series: """ GS end-of-day atm forward rate for swaption vol matrices. :param asset: asset object loaded from security master :param expiration_tenor: relative date representation of expiration date on the option e.g. 3m :param termination_tenor: relative date representation of the instrument's expiration date e.g. 1y :param benchmark_type: benchmark type e.g. LIBOR :param floating_rate_tenor: floating index rate :param clearing_house: Example - "LCH", "EUREX", "JSCC", "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: swaption implied normal volatility curve """ df = _get_swaption_measure( asset, benchmark_type=benchmark_type, floating_rate_tenor=floating_rate_tenor, effective_date="0b", expiration_tenor=expiration_tenor, termination_tenor=termination_tenor, clearing_house=clearing_house, source=source, real_time=real_time, start=DataContext.current.start_date, end=DataContext.current.end_date, query_type=QueryType.ATM_FWD_RATE, location=location, ) return _extract_series_from_df(df, QueryType.ATM_FWD_RATE) @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [MeasureDependency(id_provider=_currency_to_tdapi_swaption_rate_asset, query_type=QueryType.SWAPTION_VOL)], ) def swaption_vol( asset: Asset, expiration_tenor: str = None, termination_tenor: str = None, relative_strike: float = None, benchmark_type: str = None, floating_rate_tenor: str = None, clearing_house: str = None, location: PricingLocation = None, *, source: str = None, real_time: bool = False, ) -> pd.Series: """ GS end-of-day implied normal volatility for swaption vol matrices. :param asset: asset object loaded from security master :param expiration_tenor: relative date representation of expiration date on the option e.g. 3m :param termination_tenor: relative date representation of the instrument's expiration date e.g. 1y :param relative_strike: strike level relative to at the money e.g. 10 or ATM+10 :param benchmark_type: benchmark type e.g. LIBOR :param floating_rate_tenor: floating index rate :param clearing_house: Example - "LCH", "EUREX", "JSCC", "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: swaption implied normal volatility curve """ df = _get_swaption_measure( asset, benchmark_type, floating_rate_tenor, "0b", expiration_tenor, termination_tenor, relative_strike, clearing_house, source=source, real_time=real_time, query_type=QueryType.SWAPTION_VOL, start=DataContext.current.start_date, end=DataContext.current.end_date, location=location, ) return _extract_series_from_df(df, QueryType.SWAPTION_VOL) @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [MeasureDependency(id_provider=_currency_to_tdapi_midcurve_asset, query_type=QueryType.MIDCURVE_VOL)], ) def midcurve_vol( asset: Asset, expiration_tenor: str, forward_tenor: str, termination_tenor: str, relative_strike: float = None, benchmark_type: str = None, floating_rate_tenor: str = None, clearing_house: str = None, location: PricingLocation = None, *, source: str = None, real_time: bool = False, ) -> pd.Series: """ GS end-of-day implied normal volatility for swaption vol matrices. :param asset: asset object loaded from security master :param expiration_tenor: relative date representation of expiration date on the option e.g. 3m :param forward_tenor: relative date representation of swap's start date after option expiry e.g. 2y :param termination_tenor: relative date representation of the instrument's expiration date e.g. 1y :param relative_strike: strike level relative to at the money e.g. 10 or ATM+10 :param benchmark_type: benchmark type e.g. LIBOR :param floating_rate_tenor: floating index rate :param clearing_house: Example - "LCH", "EUREX", "JSCC", "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: swaption implied normal volatility curve """ df = _get_swaption_measure( asset, benchmark_type, floating_rate_tenor, forward_tenor, expiration_tenor, termination_tenor, relative_strike, clearing_house, source=source, real_time=real_time, query_type=QueryType.MIDCURVE_VOL, start=DataContext.current.start_date, end=DataContext.current.end_date, location=location, ) return _extract_series_from_df(df, QueryType.MIDCURVE_VOL) @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [MeasureDependency(id_provider=_currency_to_tdapi_midcurve_asset, query_type=QueryType.MIDCURVE_ATM_FWD_RATE)], ) def midcurve_atm_fwd_rate( asset: Asset, expiration_tenor: str, forward_tenor: str, termination_tenor: str, benchmark_type: str = None, floating_rate_tenor: str = None, clearing_house: str = None, location: PricingLocation = None, *, source: str = None, real_time: bool = False, ) -> pd.Series: """ GS end-of-day atm forward rate for swaption vol matrices. :param asset: asset object loaded from security master :param expiration_tenor: relative date representation of expiration date on the option e.g. 3m :param forward_tenor: relative date representation of swap's start date after option expiry e.g. 2y :param termination_tenor: relative date representation of the instrument's expiration date e.g. 1y :param benchmark_type: benchmark type e.g. LIBOR :param floating_rate_tenor: floating index rate :param clearing_house: Example - "LCH", "EUREX", "JSCC", "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: swaption implied normal volatility curve """ df = _get_swaption_measure( asset, benchmark_type=benchmark_type, floating_rate_tenor=floating_rate_tenor, effective_date=forward_tenor, expiration_tenor=expiration_tenor, termination_tenor=termination_tenor, clearing_house=clearing_house, source=source, real_time=real_time, start=DataContext.current.start_date, end=DataContext.current.end_date, query_type=QueryType.MIDCURVE_ATM_FWD_RATE, location=location, ) return _extract_series_from_df(df, QueryType.MIDCURVE_ATM_FWD_RATE) def _get_swaption_measure( asset: Asset, benchmark_type: str = None, floating_rate_tenor: str = None, effective_date: str = None, expiration_tenor: str = None, termination_tenor: str = None, strike_reference: [str, int] = None, clearing_house: str = None, start: str = DataContext.current.start_date, end: str = DataContext.current.end_date, source: str = None, real_time: bool = False, allow_many: bool = False, query_type: QueryType = QueryType.SWAPTION_PREMIUM, location: PricingLocation = None, ) -> pd.Series: if real_time: raise NotImplementedError(f'realtime {query_type.value} not implemented') currency = CurrencyEnum(asset.get_identifier(AssetIdentifier.BLOOMBERG_ID)) if not swaptions_defaults_provider.is_supported(currency): raise NotImplementedError(f'Data not available for {currency.value} {query_type.value}') query = _swaption_build_asset_query( currency, benchmark_type, effective_date, expiration_tenor, floating_rate_tenor, strike_reference, termination_tenor, clearing_house, ) _logger.debug(query) rate_mqid = _get_tdapi_rates_assets(**query, allow_many=allow_many) if isinstance(rate_mqid, str): rate_mqid = [rate_mqid] if location is None: pricing_location = _default_pricing_location(currency) else: pricing_location = PricingLocation(location) pricing_location = _pricing_location_normalized(pricing_location, currency) where = dict(pricingLocation=pricing_location.value) with DataContext(start, end): q = GsDataApi.build_market_data_query(rate_mqid, query_type, where=where, source=source, real_time=real_time) _logger.debug('q %s', q) df = _market_data_timed(q) return df def _swaption_build_asset_query( currency, benchmark_type=None, effective_date=None, expiration_tenor=None, floating_rate_tenor=None, strike_reference=None, termination_tenor=None, clearinghouse=None, ): benchmark_type = swaptions_defaults_provider.get_swaption_parameter(currency, 'benchmarkType', benchmark_type) floating_rate_option = swaptions_defaults_provider.get_floating_rate_option_for_benchmark(currency, benchmark_type) if floating_rate_option is None: raise MqValueError( "Invalid benchmark type {}: cannot map benchmark_type with underlying rate for {}".format( benchmark_type, currency ) ) floating_rate_tenor = swaptions_defaults_provider.get_swaption_parameter( currency, "floatingRateTenor", floating_rate_tenor ) strike_reference = swaptions_defaults_provider.get_swaption_parameter(currency, 'strikeReference', strike_reference) termination_tenor = swaptions_defaults_provider.get_swaption_parameter( currency, 'terminationTenor', termination_tenor ) effective_date = swaptions_defaults_provider.get_swaption_parameter(currency, 'effectiveDate', effective_date) expiration_tenor = swaptions_defaults_provider.get_swaption_parameter(currency, 'expirationTenor', expiration_tenor) for tenor in [termination_tenor, expiration_tenor]: if not _is_valid_relative_date_tenor(tenor): raise MqValueError('invalid tenor ' + tenor + ' for index: ' + benchmark_type) forward_tenor = _check_forward_tenor(effective_date) strike_reference = _check_strike_reference(strike_reference) clearinghouse = swaptions_defaults_provider.get_swaption_parameter(currency, 'clearingHouse', clearinghouse) query = dict(asset_class='Rates', type='Swaption', asset_parameters_notional_currency=currency.name) if termination_tenor is not None: query["asset_parameters_termination_date"] = termination_tenor if floating_rate_option is not None: query["asset_parameters_floating_rate_option"] = floating_rate_option if floating_rate_tenor is not None: query["asset_parameters_floating_rate_designated_maturity"] = floating_rate_tenor if expiration_tenor is not None: query["asset_parameters_expiration_date"] = expiration_tenor if strike_reference is not None: query["asset_parameters_strike"] = strike_reference if clearinghouse is not None: query["asset_parameters_clearing_house"] = clearinghouse if forward_tenor is not None: query["asset_parameters_effective_date"] = forward_tenor if expiration_tenor is not None: query["asset_parameters_notional_currency"] = currency.name return query def _check_strike_reference(strike_reference): if strike_reference is None: return None if isinstance(strike_reference, float) or isinstance(strike_reference, int): if strike_reference == 0: strike_reference = "ATM" else: strike_reference = ('ATM%+f' % strike_reference).rstrip('0').rstrip(".") elif isinstance(strike_reference, str) and strike_reference.upper() == "SPOT": strike_reference = "ATM" if isinstance(strike_reference, list): to_check = strike_reference else: to_check = [strike_reference] for s in to_check: if not re.fullmatch(r"ATM|ATM[-+]?([0-9]*\.[0-9]+|[0-9]+)", s): raise MqValueError('invalid strike reference ' + s) return strike_reference def _is_valid_relative_date_tenor(tenor): if tenor is None: return True if re.fullmatch('(\\d+)([bdwmy])', tenor): return True else: return False @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [MeasureDependency(id_provider=_currency_to_tdapi_swaption_rate_asset, query_type=QueryType.SWAPTION_VOL)], ) def swaption_vol_smile( asset: Asset, expiration_tenor: str, termination_tenor: str, pricing_date: Optional[GENERIC_DATE] = None, benchmark_type: str = None, floating_rate_tenor: str = None, clearing_house: str = None, location: PricingLocation = None, *, source: str = None, real_time: bool = False, ) -> pd.Series: """ GS end-of-day implied normal volatility for swaption vol matrices. :param asset: asset object loaded from security master :param expiration_tenor: relative date representation of expiration date on the option e.g. 3m :param termination_tenor: relative date representation of the instrument's expiration date e.g. 1y :param pricing_date: YYYY-MM-DD or relative date :param benchmark_type: benchmark type e.g. LIBOR :param floating_rate_tenor: floating index rate :param clearing_house: Example - "LCH", "EUREX", "JSCC", "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: swaption implied normal volatility curve """ if real_time: raise NotImplementedError('realtime swaption_vol not implemented') _logger.debug('where expiry=%s, tenor=%s', expiration_tenor, termination_tenor) currency = CurrencyEnum(asset.get_identifier(AssetIdentifier.BLOOMBERG_ID)) if location is None: location = PricingLocation(swaptions_defaults_provider.get_swaption_parameter(currency, "pricingLocation")) start, end = _range_from_pricing_date(location, pricing_date) df = _get_swaption_measure( asset, expiration_tenor=expiration_tenor, termination_tenor=termination_tenor, strike_reference=TdapiRatesDefaultsProvider.EMPTY_PROPERTY, source=source, query_type=QueryType.SWAPTION_VOL, benchmark_type=benchmark_type, floating_rate_tenor=floating_rate_tenor, clearing_house=clearing_house, location=location, start=start, end=end, allow_many=True, ) dataset_ids = getattr(df, 'dataset_ids', ()) if df.empty: series = ExtendedSeries(dtype=float) else: # convert string ATM+20 to numerical value 20 df["strikeRelative"] = df["strikeRelative"].apply(lambda d: float(d.split("ATM")[1] if d != "ATM" else 0)) latest = df.index.max() _logger.info('selected pricing date %s', latest) df = df.loc[latest] df = df.set_index('strikeRelative') df = df.sort_index() series = ExtendedSeries(df['swaptionVol'].values, index=df.index.values) series.dataset_ids = dataset_ids return series @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [MeasureDependency(id_provider=_currency_to_tdapi_swaption_rate_asset, query_type=QueryType.SWAPTION_VOL)], ) def swaption_vol_term( asset: Asset, tenor_type: SwaptionTenorType, tenor: str, relative_strike: float, pricing_date: Optional[GENERIC_DATE] = None, benchmark_type: str = None, floating_rate_tenor: str = None, clearing_house: str = None, location: PricingLocation = None, *, source: str = None, real_time: bool = False, ) -> pd.Series: """ Term structure of GS end-of-day implied normal volatility for swaption vol matrices. :param asset: an asset :param tenor_type: specifies which type of tenor will be fixed, one of OPTION_EXPIRATION or SWAP_MATURITY :param tenor: relative date representation of the instrument's expiration date e.g. 1y :param relative_strike: strike level relative to at the money e.g. 10 :param pricing_date: YYYY-MM-DD or relative date :param benchmark_type: benchmark type e.g. LIBOR :param floating_rate_tenor: floating index rate :param clearing_house: Example - "LCH", "EUREX", "JSCC", "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: swaption implied normal volatility term structure """ if real_time: raise NotImplementedError('realtime swaption_vol not implemented') currency = CurrencyEnum(asset.get_identifier(AssetIdentifier.BLOOMBERG_ID)) if location is None: location = PricingLocation(swaptions_defaults_provider.get_swaption_parameter(currency, "pricingLocation")) start, end = _range_from_pricing_date(location.value, pricing_date) if tenor_type == SwaptionTenorType.OPTION_EXPIRY: tenor_to_plot = 'terminationTenor' df = _get_swaption_measure( asset, expiration_tenor=tenor, termination_tenor=TdapiRatesDefaultsProvider.EMPTY_PROPERTY, strike_reference=relative_strike, query_type=QueryType.SWAPTION_VOL, benchmark_type=benchmark_type, floating_rate_tenor=floating_rate_tenor, clearing_house=clearing_house, location=location, source=source, start=start, end=end, allow_many=True, ) else: tenor_to_plot = 'expirationTenor' df = _get_swaption_measure( asset, expiration_tenor=TdapiRatesDefaultsProvider.EMPTY_PROPERTY, termination_tenor=tenor, strike_reference=relative_strike, benchmark_type=benchmark_type, floating_rate_tenor=floating_rate_tenor, clearing_house=clearing_house, query_type=QueryType.SWAPTION_VOL, source=source, start=start, end=end, allow_many=True, ) dataset_ids = getattr(df, 'dataset_ids', ()) if df.empty: series = ExtendedSeries(dtype=float) else: latest = df.index.max() _logger.info('selected pricing date %s', latest) df = df.loc[latest] business_day = _get_custom_bd(asset.exchange) df = df.assign( expirationDate=pd.DatetimeIndex( [ts + _to_offset(t) + business_day - business_day for ts, t in zip(df.index, df[tenor_to_plot])] ) ) df = df.set_index('expirationDate') df.index = pd.DatetimeIndex(df.index) df = df.sort_index() df = df.loc[pd.Timestamp(DataContext.current.start_date) : pd.Timestamp(DataContext.current.end_date)] series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(df['swaptionVol']) series.dataset_ids = dataset_ids if series.empty: # Raise descriptive error if no data returned + historical date context check_forward_looking(None, source, 'swaption_vol_term') return series @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [MeasureDependency(id_provider=_currency_to_tdapi_swap_rate_asset, query_type=QueryType.SWAP_RATE)], ) def swap_rate( asset: Asset, swap_tenor: str, benchmark_type: str = None, floating_rate_tenor: str = None, forward_tenor: Optional[GENERIC_DATE] = None, clearing_house: _ClearingHouse = None, location: PricingLocation = None, *, source: str = None, real_time: bool = False, ) -> pd.Series: """ GS end-of-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", "EUREX", "JSCC", "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 """ df = _get_swap_data( asset=asset, swap_tenor=swap_tenor, benchmark_type=benchmark_type, floating_rate_tenor=floating_rate_tenor, forward_tenor=forward_tenor, clearing_house=clearing_house, source=source, real_time=real_time, query_type=QueryType.SWAP_RATE, location=location, ) series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(df['swapRate']) series.dataset_ids = getattr(df, 'dataset_ids', ()) return series @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [MeasureDependency(id_provider=_currency_to_tdapi_swap_rate_asset_for_intraday, query_type=QueryType.SPOT)], ) def swap_rate_calc( asset: Asset, swap_tenor: str, benchmark_type: str = None, floating_rate_tenor: str = None, forward_tenor: Optional[GENERIC_DATE] = None, csa: str = None, location: PricingLocation = None, *, source: str = None, real_time: bool = False, ) -> pd.Series: """ GS intra-day Fixed-Floating interest rate swap (IRS) curves across major currencies. This API runs on-the-fly calculations :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 csa: Collateral type or clearing house for cleared swaps - set to Default for standard domestic funding. :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 """ 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, csa=csa, real_time=real_time, location=location, ) series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(df['ATMRate']) series.dataset_ids = () return series @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [MeasureDependency(id_provider=_currency_to_tdapi_swap_rate_asset_for_intraday, query_type=QueryType.SPOT)], ) def forward_rate( asset: Asset, forward_start_tenor: str = None, forward_term: str = None, csa: str = None, close_location: str = None, *, source: str = None, real_time: bool = False, ) -> pd.Series: """ GS Forward Rate across major currencies. This API computes forward rates off stored forward/discount curves :param asset: asset object loaded from security master :param forward_start_tenor: Relative date of start of forward e.g. 1y :param forward_term: Term of forward, e.g. 3m :param csa: Collateral code of curve, e.g. GBP-1. If set to default, default CSA is chosen :param close_location: For EOD data, gives location of close :param source: name of function caller :param real_time: whether to retrieve intra-day data instead of EOD :return: annualised instantaneous forward rate """ currency = CurrencyEnum(asset.get_identifier(AssetIdentifier.BLOOMBERG_ID)) csa = csa or 'Default' close_location = close_location or 'NYC' if not forward_term: raise MqValueError("Forward rate term not specified") if not forward_start_tenor: forward_start_tenor = '0d' measure = f'FR:{forward_start_tenor}:{forward_term}' df = GsDataApi.get_mxapi_curve_measure( 'DISCOUNT CURVE', currency.value, [], [csa], measure, close_location=close_location, real_time=real_time ) series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(df[measure]) series.dataset_ids = () return series @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [MeasureDependency(id_provider=_currency_to_tdapi_swap_rate_asset_for_intraday, query_type=QueryType.SPOT)], entitlements=[Entitlement.INTERNAL], ) def discount_factor( asset: Asset, tenor: str = None, csa: str = None, close_location: str = None, *, source: str = None, real_time: bool = False, ) -> pd.Series: """ GS Discount Factor across major currencies. This API computes discount factor off stored forward/discount curves :param asset: asset object loaded from security master :param tenor: tenor of discount factor e.g. 1m :param csa: Collateral code of curve to fetch discount factor, e.g. GBP-1. If not specified, default CSA is chosen :param close_location: For EOD data, gives location of close :param source: name of function caller :param real_time: whether to retrieve intra-day data instead of EOD :return: annualised instantaneous forward rate """ currency = CurrencyEnum(asset.get_identifier(AssetIdentifier.BLOOMBERG_ID)) close_location = close_location or 'NYC' if not tenor: raise MqValueError("Discount Curve start and end date not specified") csa = csa or 'Default' measure = f'DF:{tenor}' df = GsDataApi.get_mxapi_curve_measure( 'DISCOUNT CURVE', currency.value, [], [csa], measure, close_location=close_location, real_time=real_time ) series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(df[measure]) series.dataset_ids = () return series @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [MeasureDependency(id_provider=_currency_to_tdapi_swap_rate_asset_for_intraday, query_type=QueryType.SPOT)], ) def instantaneous_forward_rate( asset: Asset, tenor: str = None, csa: str = None, close_location: str = None, *, source: str = None, real_time: bool = False, ) -> pd.Series: """ GS Floating Rate Benchmark annualised instantaneous forward rates across major currencies. This API computes IFR off stored forward/discount curves :param asset: asset object loaded from security master :param tenor: tenor of IFR e.g. 1m :param csa: Collateral code of curve to fetch IFR. Set to default for default collateral :param close_location: For EOD data, gives location of close :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :return: annualised instantaneous forward rate """ currency = CurrencyEnum(asset.get_identifier(AssetIdentifier.BLOOMBERG_ID)) close_location = close_location or 'NYC' if not tenor: raise MqValueError("Forward rate tenor not specified") csa = csa or 'Default' measure = f'IFR:{tenor}' df = GsDataApi.get_mxapi_curve_measure( 'DISCOUNT CURVE', currency.value, [], [csa], measure, close_location=close_location, real_time=real_time ) series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(df[measure]) series.dataset_ids = () return series @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [MeasureDependency(id_provider=_currency_to_tdapi_swap_rate_asset_for_intraday, query_type=QueryType.SPOT)], ) def index_forward_rate( asset: Asset, forward_start_tenor: str = None, benchmark_type: str = None, fixing_tenor: str = None, close_location: str = None, *, source: str = None, real_time: bool = False, ) -> pd.Series: """ GS annualised forward rates across floating rate benchmark This API computes index forward rate off stored index forward curves :param asset: asset object loaded from security master :param forward_start_tenor: relative start rate of forward e.g. 1m :param benchmark_type: benchmark type of floating rate option e.g. LIBOR :param fixing_tenor: Fixing tenor of the given benchmark type. Leave empty to use default e.g. 3m :param close_location: For EOD data, gives location of close :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :return: annualised forward rate """ currency = CurrencyEnum(asset.get_identifier(AssetIdentifier.BLOOMBERG_ID)) close_location = close_location or 'NYC' if not forward_start_tenor: raise MqValueError("Forward rate start date not specified") benchmark_type = _check_benchmark_type(currency, benchmark_type, True) if not isinstance(benchmark_type, str): benchmark_type_input = _get_benchmark_type(currency, benchmark_type) else: benchmark_type_input = benchmark_type if fixing_tenor is None: if benchmark_type_input not in BENCHMARK_TO_DEFAULT_FLOATING_RATE_TENORS: raise MqValueError( "Please provide fixing tenor: " + f"default fixing tenor not specified for {benchmark_type_input}" ) fixing_tenor = BENCHMARK_TO_DEFAULT_FLOATING_RATE_TENORS[benchmark_type_input] measure = f'FR:{forward_start_tenor}:{fixing_tenor}' df = GsDataApi.get_mxapi_curve_measure( 'INDEX CURVE', benchmark_type_input, [fixing_tenor], [f'{currency.value}-1'], measure, close_location=close_location, real_time=real_time, ) series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(df[measure]) 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, location: PricingLocation = None, ) -> dict: currency = CurrencyEnum(asset.get_identifier(AssetIdentifier.BLOOMBERG_ID)) if currency.value not in ['JPY', 'EUR', 'USD', 'GBP', 'CHF', 'DKK', 'NOK', 'SEK', 'CAD', 'AUD', 'NZD']: raise NotImplementedError('Data not available for {} basis swap rates'.format(currency.value)) clearing_house = _check_clearing_house(clearing_house) spread_benchmark_type = _check_benchmark_type(currency, spread_benchmark_type) reference_benchmark_type = _check_benchmark_type(currency, reference_benchmark_type) # default benchmark types legs_w_defaults = dict() legs_w_defaults['spread'] = _get_swap_leg_defaults(currency, spread_benchmark_type, spread_tenor) legs_w_defaults['reference'] = _get_swap_leg_defaults(currency, reference_benchmark_type, reference_tenor) for key, leg in legs_w_defaults.items(): if not re.fullmatch('(\\d+)([bdwmy])', leg['floating_rate_tenor']): raise MqValueError( 'invalid floating rate tenor ' + leg['floating_rate_tenor'] + ' index: ' + leg['benchmark_type'] ) forward_tenor = _check_forward_tenor(forward_tenor) if location is None: pricing_location = PricingLocation(legs_w_defaults['spread']['pricing_location'].value) else: pricing_location = PricingLocation(location) pricing_location = _pricing_location_normalized(pricing_location, currency) kwargs = dict( type='BasisSwap', asset_parameters_payer_rate_option=legs_w_defaults['spread']['benchmark_type'], asset_parameters_payer_designated_maturity=legs_w_defaults['spread']['floating_rate_tenor'], asset_parameters_receiver_rate_option=legs_w_defaults['reference']['benchmark_type'], asset_parameters_receiver_designated_maturity=legs_w_defaults['reference']['floating_rate_tenor'], asset_parameters_clearing_house=clearing_house.value, asset_parameters_effective_date=forward_tenor, asset_parameters_notional_currency=currency.name, pricing_location=pricing_location, ) kwargs = _match_floating_tenors(kwargs) return kwargs @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [MeasureDependency(id_provider=_currency_to_tdapi_basis_swap_rate_asset, query_type=QueryType.BASIS_SWAP_RATE)], ) def basis_swap_spread( asset: Asset, swap_tenor: str = '1y', 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, location: PricingLocation = None, *, source: str = None, real_time: bool = False, ) -> pd.Series: """ GS end-of-day Floating-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 spread_benchmark_type: benchmark type of spread leg on which basis spread is added e.g. LIBOR :param spread_tenor: relative date representation of expiration date of paying leg e.g. 1m :param reference_benchmark_type: benchmark type of reference leg e.g. LIBOR :param reference_tenor: relative date representation of expiration date of reference leg e.g. 1m :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", "EUREX", "JSCC", "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 real_time: raise NotImplementedError('realtime basis_swap_rate not implemented') if not (re.fullmatch('(\\d+)([bdwmy])', swap_tenor) or re.fullmatch('(frb[1-9])', forward_tenor)): raise MqValueError('invalid swap tenor ' + swap_tenor) kwargs = _get_basis_swap_kwargs( asset=asset, spread_benchmark_type=spread_benchmark_type, spread_tenor=spread_tenor, reference_benchmark_type=reference_benchmark_type, reference_tenor=reference_tenor, forward_tenor=forward_tenor, clearing_house=clearing_house, location=location, ) kwargs['asset_parameters_termination_date'] = swap_tenor rate_mqid = _get_tdapi_rates_assets(**kwargs) _logger.debug( 'where asset=%s, swap_tenor=%s, spread_benchmark_type=%s, spread_tenor=%s, ' 'reference_benchmark_type=%s, reference_tenor=%s, forward_tenor=%s, pricing_location=%s ', rate_mqid, swap_tenor, kwargs['asset_parameters_payer_rate_option'], kwargs['asset_parameters_payer_designated_maturity'], kwargs['asset_parameters_receiver_rate_option'], kwargs['asset_parameters_receiver_designated_maturity'], kwargs['asset_parameters_effective_date'], kwargs['pricing_location'], ) where = {'pricingLocation': kwargs['pricing_location'].value} q = GsDataApi.build_market_data_query( [rate_mqid], QueryType.BASIS_SWAP_RATE, where=where, source=source, real_time=real_time ) _logger.debug('q %s', q) df = _market_data_timed(q) series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(df['basisSwapRate']) series.dataset_ids = getattr(df, 'dataset_ids', ()) return series @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [MeasureDependency(id_provider=_currency_to_tdapi_swap_rate_asset, query_type=QueryType.SWAP_RATE)], ) def swap_term_structure( asset: Asset, benchmark_type: str = None, floating_rate_tenor: str = None, tenor_type: _SwapTenorType = None, tenor: Optional[GENERIC_DATE] = None, clearing_house: _ClearingHouse = None, location: PricingLocation = None, pricing_date: Optional[GENERIC_DATE] = None, *, source: str = None, real_time: bool = False, ) -> pd.Series: """ GS end-of-day Fixed-Floating interest rate swap (IRS) term structure across major currencies. :param asset: asset object loaded from security master :param benchmark_type: benchmark type e.g. LIBOR :param floating_rate_tenor: floating index rate :param tenor_type: specify which tenor should be fixed, SWAP_TENOR or FORWARD_TENOR :param tenor: absolute / relative date representation of forward starting point or swap maturity :param clearing_house: Example - "LCH", "EUREX", "JSCC", "CME" :param location: Example - "TKO", "LDN", "NYC" :param pricing_date: YYYY-MM-DD or relative date :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :return: swap rate term structure """ if real_time: raise NotImplementedError('realtime swap_rate not implemented') currency = asset.get_identifier(AssetIdentifier.BLOOMBERG_ID) currency = CurrencyEnum(currency) if currency.value not in CURRENCY_TO_SWAP_RATE_BENCHMARK.keys(): raise NotImplementedError('Data not available for {} swap rates'.format(currency.value)) clearing_house = _check_clearing_house(clearing_house) benchmark_type = _check_benchmark_type(currency, benchmark_type) tenor_type = _check_tenor_type(tenor_type) tenor_dict = _check_term_structure_tenor(tenor_type=tenor_type, tenor=tenor) defaults = _get_swap_leg_defaults(currency, benchmark_type, floating_rate_tenor) if not re.fullmatch('(\\d+)([bdwmy])', defaults['floating_rate_tenor']): raise MqValueError( 'invalid floating rate tenor ' + defaults['floating_rate_tenor'] + ' for index: ' + defaults['benchmark_type'] ) if location is None: pricing_location = _default_pricing_location(currency) else: pricing_location = location pricing_location = _pricing_location_normalized(pricing_location, currency) calendar = pricing_location.value if pricing_date is not None and pricing_date in list(GsCalendar.get(calendar).holidays): raise MqValueError('Specified pricing date is a holiday in {} calendar'.format(calendar)) fixed_rate = 'ATM' where = dict(pricingLocation=pricing_location.value) kwargs = dict( type='Swap', asset_parameters_floating_rate_option=defaults['benchmark_type'], asset_parameters_fixed_rate=fixed_rate, asset_parameters_clearing_house=clearing_house.value, asset_parameters_floating_rate_designated_maturity=defaults['floating_rate_tenor'], asset_parameters_notional_currency=currency.name, pricing_location=pricing_location.value, ) kwargs[tenor_dict['tenor_dataset_field']] = tenor_dict['tenor'] rate_mqids = _get_tdapi_rates_assets(allow_many=True, **kwargs) if isinstance(rate_mqids, str): rate_mqids = [rate_mqids] _logger.debug('assets returned %s', ', '.join(rate_mqids)) _logger.debug( 'where benchmark_type=%s, floating_rate_tenor=%s, %s=%s, pricing_location=%s', defaults['benchmark_type'], defaults['floating_rate_tenor'], tenor_type.value, tenor_dict['tenor'], defaults['pricing_location'].value, ) start, end = _range_from_pricing_date(calendar, pricing_date) with DataContext(start, end): q = GsDataApi.build_market_data_query( rate_mqids, QueryType.SWAP_RATE, where=where, source=source, real_time=real_time ) _logger.debug('q %s', q) df = _market_data_timed(q) if df.empty: series = ExtendedSeries(dtype=float) else: latest = df.index.max() _logger.info('selected pricing date %s', latest) df = df.loc[latest] biz_day = _get_custom_bd(calendar) col_to_plot = tenor_dict['tenor_to_plot'] if isinstance(df, pd.Series): series = ExtendedSeries( pd.Series( df['swapRate'], index=pd.DatetimeIndex([_get_term_struct_date(df[col_to_plot], latest, biz_day)]) ) ) series = series.loc[ pd.Timestamp(DataContext.current.start_date) : pd.Timestamp(DataContext.current.end_date) ] else: if col_to_plot == 'effectiveTenor': df = df[~df[col_to_plot].isin(['imm1', 'imm2', 'imm3', 'imm4'])] df['expirationDate'] = df[col_to_plot].apply(_get_term_struct_date, args=(latest, biz_day)) df = df.set_index('expirationDate') df.index = pd.DatetimeIndex(df.index) df = df.sort_index() df = df.loc[pd.Timestamp(DataContext.current.start_date) : pd.Timestamp(DataContext.current.end_date)] series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(df['swapRate']) series.dataset_ids = getattr(df, 'dataset_ids', ()) if series.empty: # Raise descriptive error if no data returned + date context is in the past check_forward_looking(None, source, 'swaption_vol_term') return series @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [MeasureDependency(id_provider=_currency_to_tdapi_basis_swap_rate_asset, query_type=QueryType.BASIS_SWAP_RATE)], ) def basis_swap_term_structure( asset: Asset, spread_benchmark_type: str = None, spread_tenor: str = None, reference_benchmark_type: str = None, reference_tenor: str = None, tenor_type: _SwapTenorType = None, tenor: Optional[GENERIC_DATE] = None, clearing_house: _ClearingHouse = None, location: PricingLocation = None, pricing_date: Optional[GENERIC_DATE] = None, *, source: str = None, real_time: bool = False, ) -> pd.Series: """ GS end-of-day Floating-Floating interest rate swap (IRS) term structure across major currencies. :param asset: asset object loaded from security master :param spread_benchmark_type: benchmark type of spread leg on which basis spread is added e.g. LIBOR :param spread_tenor: relative date representation of expiration date of spread leg e.g. 1m :param reference_benchmark_type: benchmark type of reference leg e.g. LIBOR :param reference_tenor: relative date representation of expiration date of reference leg e.g. 1m :param tenor_type: specify which tenor should be fixed, SWAP_TENOR or FORWARD_TENOR :param tenor: absolute / relative date representation of forward starting point or swap maturity :param clearing_house: Example - "LCH", "EUREX", "JSCC", "CME" :param location: Example - "TKO", "LDN", "NYC" :param pricing_date: YYYY-MM-DD or relative date :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :return: swap rate curve """ if real_time: raise NotImplementedError('realtime basis_swap_rate not implemented') tenor_type = _check_tenor_type(tenor_type) tenor_dict = _check_term_structure_tenor(tenor_type=tenor_type, tenor=tenor) kwargs = _get_basis_swap_kwargs( asset=asset, spread_benchmark_type=spread_benchmark_type, spread_tenor=spread_tenor, reference_benchmark_type=reference_benchmark_type, reference_tenor=reference_tenor, clearing_house=clearing_house, location=location, ) kwargs[tenor_dict['tenor_dataset_field']] = tenor_dict['tenor'] calendar = kwargs['pricing_location'].value if pricing_date is not None and pricing_date in list(GsCalendar.get(calendar).holidays): raise MqValueError('Specified pricing date is a holiday in {} calendar'.format(calendar)) rate_mqids = _get_tdapi_rates_assets(allow_many=True, **kwargs) if isinstance(rate_mqids, str): # single asset returned rate_mqids = [rate_mqids] _logger.debug('assets returned %s', ', '.join(rate_mqids)) _logger.debug( 'where spread_benchmark_type=%s, spread_tenor=%s, reference_benchmark_type=%s, ' 'reference_tenor=%s, %s=%s, pricing_location=%s ', kwargs['asset_parameters_payer_rate_option'], kwargs['asset_parameters_payer_designated_maturity'], kwargs['asset_parameters_receiver_rate_option'], kwargs['asset_parameters_receiver_designated_maturity'], kwargs[tenor_dict['tenor_dataset_field']], tenor_dict['tenor'], kwargs['pricing_location'].value, ) where = {'pricingLocation': kwargs['pricing_location'].value} start, end = _range_from_pricing_date(calendar, pricing_date) with DataContext(start, end): q = GsDataApi.build_market_data_query( rate_mqids, QueryType.BASIS_SWAP_RATE, where=where, source=source, real_time=real_time ) _logger.debug('q %s', q) df = _market_data_timed(q) if df.empty: series = ExtendedSeries(dtype=float) else: latest = df.index.max() _logger.info('selected pricing date %s', latest) df = df.loc[latest] biz_day = _get_custom_bd(calendar) col_to_plot = tenor_dict['tenor_to_plot'] if isinstance(df, pd.Series): # single asset returned series = ExtendedSeries( pd.Series( df['basisSwapRate'], index=pd.DatetimeIndex([_get_term_struct_date(df[col_to_plot], latest, biz_day)]), ) ) series = series.loc[ pd.Timestamp(DataContext.current.start_date) : pd.Timestamp(DataContext.current.end_date) ] else: if col_to_plot == 'effectiveTenor': # for forward term structure imm date assets df = df[~df[col_to_plot].isin(['imm1', 'imm2', 'imm3', 'imm4'])] df['expirationDate'] = df[col_to_plot].apply(_get_term_struct_date, args=(latest, biz_day)) df = df.set_index('expirationDate') df.index = pd.DatetimeIndex(df.index) df = df.sort_index() df = df.loc[pd.Timestamp(DataContext.current.start_date) : pd.Timestamp(DataContext.current.end_date)] series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(df['basisSwapRate']) series.dataset_ids = getattr(df, 'dataset_ids', ()) if series.empty: # Raise descriptive error if no data returned + historical date context check_forward_looking(None, source, 'swaption_vol_term') return series def _get_fxfwd_xccy_swp_rates_data( asset: Asset, tenor: str, real_time: bool = False, source: str = None, query_type: QueryType = None ) -> pd.DataFrame: if real_time: raise NotImplementedError('realtime not implemented') pair = asset.get_identifier(AssetIdentifier.BLOOMBERG_ID) if pair not in CROSS_BBID_TO_DUMMY_OISXCCY_ASSET.keys(): raise NotImplementedError('Data not available for pair: ' + str(pair)) if not (re.fullmatch('(\\d+)([wfmy])', tenor)): raise MqValueError('invalid tenor: ' + tenor) remap_tenor = tenor.replace('m', 'f') currency = pair.replace('USD', '') price_location_defaults = CURRENCY_TO_PRICING_LOCATION.get(currency, PricingLocation.LDN) kwargs = dict(type='Forward', asset_parameters_settlement_date=remap_tenor, asset_parameters_pair=pair) rate_mqid = _get_tdapi_rates_assets(**kwargs) _logger.debug( 'where asset= %s (%s), ois_xccy_tenor=%s, pricing_location=%s', rate_mqid, pair, tenor, price_location_defaults ) q = GsDataApi.build_market_data_query([rate_mqid], query_type, source=source, real_time=real_time) _logger.debug('q %s', q) df = _market_data_timed(q) return df @plot_measure( (AssetClass.FX,), (AssetType.Forward, AssetType.Cross), [MeasureDependency(id_provider=_cross_to_fxfwd_xcswp_asset, query_type=QueryType.OIS_XCCY)], ) def ois_xccy(asset: Asset, tenor: str = None, *, source: str = None, real_time: bool = False) -> pd.Series: """ GS end-of-day OIS Xccy spreads curves across G10 cross currencies. :param asset: asset object loaded from security master :param tenor: relative date representation of expiration date e.g. 1w :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :return: ois xccy spread curve """ df = _get_fxfwd_xccy_swp_rates_data( asset=asset, tenor=tenor, query_type=QueryType.OIS_XCCY, source=source, real_time=real_time ) series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(df['oisXccy']) series.dataset_ids = getattr(df, 'dataset_ids', ()) return series @plot_measure( (AssetClass.FX,), (AssetType.Forward, AssetType.Cross), [MeasureDependency(id_provider=_cross_to_fxfwd_xcswp_asset, query_type=QueryType.OIS_XCCY_EX_SPIKE)], ) def ois_xccy_ex_spike(asset: Asset, tenor: str = None, *, source: str = None, real_time: bool = False) -> pd.Series: """ GS end-of-day OIS Xccy spreads curves excluding spikes across G10 cross currencies. :param asset: asset object loaded from security master :param tenor: relative date representation of expiration date e.g. 1w :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :return: ois xccy spread curve excluding spikes """ df = _get_fxfwd_xccy_swp_rates_data( asset=asset, tenor=tenor, query_type=QueryType.OIS_XCCY_EX_SPIKE, source=source, real_time=real_time ) series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(df['oisXccyExSpike']) series.dataset_ids = getattr(df, 'dataset_ids', ()) return series @plot_measure( (AssetClass.FX,), (AssetType.Forward, AssetType.Cross), [MeasureDependency(id_provider=_cross_to_fxfwd_xcswp_asset, query_type=QueryType.NON_USD_OIS)], ) def non_usd_ois(asset: Asset, tenor: str = None, *, source: str = None, real_time: bool = False) -> pd.Series: """ GS end-of-day non domestic USD ois rate curve for G10 cross currencies. :param asset: asset object loaded from security master :param tenor: relative date representation of expiration date e.g. 1w :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :return: non usd ois domestic rate curve (from cross) """ df = _get_fxfwd_xccy_swp_rates_data( asset=asset, tenor=tenor, query_type=QueryType.NON_USD_OIS, source=source, real_time=real_time ) series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(df['nonUsdOis']) series.dataset_ids = getattr(df, 'dataset_ids', ()) return series @plot_measure( (AssetClass.FX,), (AssetType.Forward, AssetType.Cross), [MeasureDependency(id_provider=_cross_to_fxfwd_xcswp_asset, query_type=QueryType.USD_OIS)], ) def usd_ois(asset: Asset, tenor: str = None, *, source: str = None, real_time: bool = False) -> pd.Series: """ GS end-of-day USD domestic ois rates curves across G10 cross currencies. :param asset: asset object loaded from security master :param tenor: relative date representation of expiration date e.g. 1w :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :return: usd ois domestic rate curve (from cross) """ df = _get_fxfwd_xccy_swp_rates_data( asset=asset, tenor=tenor, query_type=QueryType.USD_OIS, source=source, real_time=real_time ) series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(df['usdOis']) series.dataset_ids = getattr(df, 'dataset_ids', ()) return series class BenchmarkTypeCB(Enum): EUROSTR = 'EUROSTR' SOFR = 'SOFR' EONIA = 'EONIA' SONIA = 'SONIA' Fed_Funds = 'Fed_Funds' def get_cb_swaps_kwargs(currency: CurrencyEnum, benchmark_type: BenchmarkTypeCB) -> Dict: benchmark_type = _check_benchmark_type(currency, benchmark_type) clearing_house = _check_clearing_house(None) defaults = _get_swap_leg_defaults(currency, benchmark_type) possible_swap_tenors = [f"{CCY_TO_CB[currency.value]}{i}" for i in range(0, 20)] possible_fwd_tenors = [f"{CCY_TO_CB[currency.value]}{i}" for i in range(0, 20)] possible_fwd_tenors.append('0b') fixed_rate = 'ATM' kwargs = dict( asset_class='Rates', type='Swap', asset_parameters_floating_rate_option=defaults['benchmark_type'], asset_parameters_fixed_rate=fixed_rate, asset_parameters_clearing_house=clearing_house.value, # asset_parameters_floating_rate_designated_maturity=defaults['floating_rate_tenor'], asset_parameters_termination_date=possible_swap_tenors, asset_parameters_effective_date=possible_fwd_tenors, asset_parameters_notional_currency=currency.value, ) return kwargs def get_cb_meeting_swaps(currency: CurrencyEnum, benchmark_type: BenchmarkTypeCB) -> List: kwargs = get_cb_swaps_kwargs(currency=currency, benchmark_type=benchmark_type) return _get_tdapi_rates_assets(allow_many=True, **kwargs) def get_cb_meeting_swap( currency: CurrencyEnum, benchmark_type: BenchmarkTypeCB, forward_tenor: str, swap_tenor: str ) -> str: kwargs = get_cb_swaps_kwargs(currency=currency, benchmark_type=benchmark_type) if not ( re.fullmatch(f"({CCY_TO_CB[currency.value]}[0-9]|1[0-9])", swap_tenor) or re.fullmatch(f"({CCY_TO_CB[currency.value]}[0-9]|1[0-9]|0b)", forward_tenor) ): raise MqValueError('invalid swap tenor ' + swap_tenor) kwargs['asset_parameters_termination_date'] = swap_tenor kwargs['asset_parameters_effective_date'] = forward_tenor return _get_tdapi_rates_assets(**kwargs) def get_cb_swap_data(currency: CurrencyEnum, rate_mqids: list = None): ds = Dataset(Dataset.GS.IR_SWAP_RATES_INTRADAY_CALC_BANK) pricing_location = _default_pricing_location(currency) pricing_location = _pricing_location_normalized(pricing_location, currency) cbw_df = ds.get_data( assetId=rate_mqids, pricingLocation=pricing_location.value, startTime=DataContext.current.start_time, endTime=DataContext.current.end_time, ) return cbw_df @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [MeasureDependency(id_provider=currency_to_default_ois_asset, query_type=QueryType.CENTRAL_BANK_SWAP_RATE)], ) def policy_rate_term_structure( asset: Asset, event_type: EventType = EventType.MEETING, rate_type: RateType = RateType.ABSOLUTE, valuation_date: Optional[GENERIC_DATE] = None, *, source: str = None, real_time: bool = False, ) -> pd.Series: """ Forward Policy Rate expectations for future Central Bank meetings or End Of Year Dates as of specified date. :param asset: Currency :param event_type: Spot= Effective OIS/Policy rate, Meeting = Forward Expectations across all future CB meetings, EOY = Forward Expectations at End of Year Dates :param rate_type: One of (absolute, relative), where relative = forward - spot rate to show what hikes/cuts are priced in by the market. :param valuation_date: reference date on which all future expectations are calculated, Eg. 3m: for 3 months ago, 2022-05-02 : for expectations as of 02May22, Intraday: how expectations are changing intraday. :param source: name of function caller: default source = None :param real_time: whether to retrieve intraday data instead of EOD: default value = False :return: OIS Swap Rate Term structure for swaps structured between consecutive CB meeting dates """ check_forward_looking(valuation_date, source, 'policy_rate_expectation') if real_time: raise NotImplementedError( 'change end date to +10y in date picker and specify ' 'valuation_date = "Intraday" for real-time policy rate term structure' ) if not isinstance(event_type, EventType): raise MqValueError('event_type must be one of Spot, Meeting Forward and EOY Forward') if not isinstance(rate_type, RateType): raise MqValueError('event_type must be either absolute or relative') if isinstance(valuation_date, str) and valuation_date.lower() == "intraday": return policy_rate_term_structure_rt( asset=asset, event_type=event_type, rate_type=rate_type, benchmark_type=None, source=source ) else: valuation_date = parse_meeting_date(valuation_date) mqid = convert_asset_for_rates_data_set(asset, RatesConversionType.OIS_BENCHMARK_RATE) _logger.debug( 'where assetId=%s, metric=Central Bank Swap Rate, event_type=%s, event_type=%s, valuation date=%s', mqid, event_type, rate_type, str(valuation_date), ) ds = Dataset(Dataset.GS.CENTRAL_BANK_WATCH) if event_type == EventType.SPOT: if rate_type == RateType.RELATIVE: raise MqValueError('rate_type must be absolute for event_type = Spot') else: df = ds.get_data(assetId=[mqid], rateType=event_type, start=CENTRAL_BANK_WATCH_START_DATE) else: df = ds.get_data( assetId=[mqid], rateType=event_type, valuationDate=valuation_date, start=CENTRAL_BANK_WATCH_START_DATE ) if df.empty: return pd.Series(dtype=float, name='value') if rate_type == RateType.RELATIVE: # df = remove_dates_with_null_entries(df) spot = df[df['meetingNumber'] == 0]['value'][0] df['value'] = df['value'] - spot try: df = df.reset_index() df = df.set_index('meetingDate') series = ExtendedSeries(df['value']) series.dataset_ids = (Dataset.GS.CENTRAL_BANK_WATCH,) except KeyError: # No data returned from ds.get_data series = pd.Series(dtype=float, name='value') return series @plot_measure( (AssetClass.Cash,), (AssetType.Currency,), [MeasureDependency(id_provider=currency_to_default_ois_asset, query_type=QueryType.POLICY_RATE_EXPECTATION)], ) def policy_rate_expectation( asset: Asset, event_type: EventType = EventType.MEETING, rate_type: RateType = RateType.ABSOLUTE, meeting_date: Union[dt.date, int, str] = 0, *, source: str = None, real_time: bool = False, ) -> pd.Series: """' Evolution of OIS/policy rate expectations for a given meeting date or end of year date. :param asset: asset object loaded from security master :param meeting_date: Actual meeting date eg. Date(2022-04-02) or meeting number standing today : 0 for last, 1 for next , 2 for meeting after next and so on :param rate_type: One of (absolute, relative), where relative = forward - spot rate to show what hikes/cuts are priced in by the market for specified meeting date. :param event_type: Spot= Effective OIS/Policy rate, Meeting = Evolution of Rate Expectations for a given meeting date, EOY = Evolution of Rate Expectations for specified End of Year Date. :param source: name of function caller: default source = None :param real_time: whether to retrieve intraday data instead of EOD: default value = False :return: Historical policy rate expectations for a given CB meeting date or an End of Year date. """ if not isinstance(event_type, EventType): raise MqValueError('invalid event_type specified, Meeting Forward, Spot or EOY Forward allowed') if not isinstance(rate_type, RateType): raise MqValueError('event_type must be either absolute or relative') if not isinstance(meeting_date, (dt.date, str, int)): raise MqValueError('valuation_date must be of type datetime.date or string YYYY-MM-DD or integer') if real_time: return policy_rate_expectation_rt(asset, event_type, rate_type, meeting_date=meeting_date, benchmark_type=None) else: mqid = convert_asset_for_rates_data_set(asset, RatesConversionType.OIS_BENCHMARK_RATE) _logger.debug( 'where assetId=%s, metric=Policy Rate Expectation, meeting_date=%s, event_type=%s', mqid, str(meeting_date), rate_type, ) ds = Dataset(Dataset.GS.CENTRAL_BANK_WATCH) if event_type == EventType.SPOT: cbw_df = ds.get_data(assetId=[mqid], rateType=event_type, start=CENTRAL_BANK_WATCH_START_DATE) elif isinstance(meeting_date, int): meeting_number = meeting_date if meeting_number < 0 or meeting_number > 20: raise MqValueError( 'meeting_number has to be an integer between 0 and 20 where 0 is the ' 'last meeting and 1 is the next meeting' ) cbw_df = ds.get_data( assetId=[mqid], rateType=event_type, meetingNumber=meeting_number, start=CENTRAL_BANK_WATCH_START_DATE ) else: meeting_date = parse_meeting_date(meeting_date) cbw_df = ds.get_data( assetId=[mqid], rateType=event_type, meetingDate=meeting_date, start=CENTRAL_BANK_WATCH_START_DATE ) if cbw_df.empty: raise MqValueError('meeting date specified returned no data') if rate_type == RateType.RELATIVE: spot_df = ds.get_data( assetId=[mqid], rateType=event_type, meetingNumber=0, start=CENTRAL_BANK_WATCH_START_DATE ).rename(columns={'value': 'spotValue'}) if spot_df.empty: raise MqValueError('no spot data returned to rebase') joined_df = cbw_df.merge(spot_df, on=['date', 'assetId', 'rateType', 'location', 'valuationDate'], how='inner') joined_df = joined_df.set_index('valuationDate') joined_df['relValue'] = joined_df['value'] - joined_df['spotValue'] series = ExtendedSeries(joined_df['relValue']) else: cbw_df = cbw_df.set_index('valuationDate') series = ExtendedSeries(cbw_df['value']) series.dataset_ids = (Dataset.GS.CENTRAL_BANK_WATCH,) return series def parse_meeting_date(valuation_date: Optional[GENERIC_DATE] = None): if isinstance(valuation_date, str): if len(valuation_date.split('-')) == 3: year, month, day = valuation_date.split('-') return dt.date(int(year), int(month), int(day)) else: start, valuation_date = _range_from_pricing_date('USD', valuation_date) return valuation_date.date() if isinstance(valuation_date, pd.Timestamp) else valuation_date elif isinstance(valuation_date, dt.date) or valuation_date is None: start, valuation_date = _range_from_pricing_date(None, valuation_date) return valuation_date.date() if isinstance(valuation_date, pd.Timestamp) else valuation_date else: raise MqValueError( 'valuation_date must be of type datetime.date or string YYYY-MM-DD or relative date strings like 1m, 1y' ) def _get_default_ois_benchmark(currency: CurrencyEnum) -> BenchmarkTypeCB: if currency == CurrencyEnum.USD: return BenchmarkTypeCB.Fed_Funds elif currency == CurrencyEnum.GBP: return BenchmarkTypeCB.SONIA elif currency == CurrencyEnum.EUR: return BenchmarkTypeCB.EUROSTR def _check_cb_ccy_benchmark_rt(asset: Asset, benchmark_type: BenchmarkTypeCB) -> tuple: bbid = asset.get_identifier(AssetIdentifier.BLOOMBERG_ID) currency = CurrencyEnum(bbid) if currency not in [CurrencyEnum.EUR, CurrencyEnum.GBP, CurrencyEnum.USD]: raise MqValueError('Only EUR, GBP and USD are supported for real time Central Bank swap data') if benchmark_type is None: benchmark_type = _get_default_ois_benchmark(currency) if ( isinstance(benchmark_type, BenchmarkTypeCB) and benchmark_type.value not in CURRENCY_TO_SWAP_RATE_BENCHMARK[currency.value].keys() ): raise MqValueError('%s is not supported for %s', benchmark_type.value, currency.value) return currency, benchmark_type def _get_swap_from_meeting_date( currency: CurrencyEnum, benchmark_type: BenchmarkTypeCB, meeting_date: Union[dt.date, int, str] ) -> str: if isinstance(meeting_date, int): if meeting_date == 0: forward_tenor = '0b' else: forward_tenor = f"{CCY_TO_CB[currency.value]}{meeting_date}" swap_tenor = f"{CCY_TO_CB[currency.value]}{str(meeting_date + 1)}" elif isinstance(meeting_date, str) and re.fullmatch(f"({CCY_TO_CB[currency.value]}[0-9]|1[0-9])", meeting_date): if meeting_date[-1] == '0': forward_tenor = '0b' else: forward_tenor = f"{CCY_TO_CB[currency.value]}{meeting_date[-1]}" swap_tenor = f"{CCY_TO_CB[currency.value]}{str(int(meeting_date[-1]) + 1)}" else: raise MqValueError( 'only meeting dates of the type ecb1, mpc1, frb1 or an integer 1,2...20 for next' ' meeting are available for real time central bank data' ) rate_mqid = get_cb_meeting_swap(currency, benchmark_type, forward_tenor=forward_tenor, swap_tenor=swap_tenor) return rate_mqid def policy_rate_expectation_rt( asset: Asset, event_type: EventType = EventType.MEETING, rate_type: RateType = RateType.ABSOLUTE, meeting_date: Union[dt.date, int, str] = 0, benchmark_type: BenchmarkTypeCB = None, ): currency, benchmark_type = _check_cb_ccy_benchmark_rt(asset, benchmark_type) rate_mqid = _get_swap_from_meeting_date(currency, benchmark_type, meeting_date) cbw_df = get_cb_swap_data(currency=currency, rate_mqids=rate_mqid) if cbw_df.empty: raise MqValueError('meeting date specified returned no data') if event_type == EventType.SPOT: if rate_type == RateType.ABSOLUTE: series = ExtendedSeries(cbw_df['rate']) series.dataset_ids = (Dataset.GS.IR_SWAP_RATES_INTRADAY_CALC_BANK,) return series else: raise MqValueError('rate_type must be absolute for event_type = Spot') elif event_type == EventType.MEETING: if rate_type == RateType.RELATIVE: spot_id = get_cb_meeting_swap(currency, benchmark_type, '0b', f"{CCY_TO_CB[currency.value]}1") spot_df = get_cb_swap_data(currency, spot_id) if spot_df.empty: raise MqValueError('no spot data returned to rebase') joined_df = cbw_df.merge( spot_df, on=['time', 'pricingLocation', 'csaTerms', 'currency'], how='inner', suffixes=['_meeting', '_spot'], ) joined_df['rate'] = joined_df['rate_meeting'] - joined_df['rate_spot'] series = ExtendedSeries(joined_df['rate']) else: series = ExtendedSeries(cbw_df['rate']) series.dataset_ids = (Dataset.GS.IR_SWAP_RATES_INTRADAY_CALC_BANK,) return series else: raise MqValueError("Real Time End of Year pricing is not supported") def policy_rate_term_structure_rt( asset: Asset, event_type: EventType = EventType.MEETING, rate_type: RateType = RateType.ABSOLUTE, benchmark_type: BenchmarkTypeCB = None, source: str = None, ): currency, benchmark_type = _check_cb_ccy_benchmark_rt(asset, benchmark_type) if event_type == EventType.SPOT: if rate_type == RateType.RELATIVE: raise MqValueError('rate_type must be absolute for event_type = Spot') else: mqid = get_cb_meeting_swap( currency, benchmark_type=benchmark_type, forward_tenor='0b', swap_tenor=f"{CCY_TO_CB[currency.value]}1" ) spot_df = get_cb_swap_data(currency, [mqid]) if spot_df.empty: raise MqValueError('no spot data returned') series = ExtendedSeries(spot_df['rate']) series.dataset_ids = (Dataset.GS.IR_SWAP_RATES_INTRADAY_CALC_BANK,) return series elif event_type == EventType.MEETING: mqids = get_cb_meeting_swaps(currency, benchmark_type=benchmark_type) cbw_df = get_cb_swap_data(currency, rate_mqids=mqids) if cbw_df.empty: return ExtendedSeries(dtype=float) else: raise MqValueError("Real Time End of Year pricing is not supported") if rate_type == RateType.RELATIVE: spot_id = get_cb_meeting_swap( currency, benchmark_type=benchmark_type, forward_tenor='0b', swap_tenor=f"{CCY_TO_CB[currency.value]}1" ) spot_df = get_cb_swap_data(currency, spot_id) if spot_df.empty: raise MqValueError('no spot data returned to rebase') joined_df = cbw_df.merge( spot_df, on=['time', 'pricingLocation', 'csaTerms', 'currency'], how='inner', suffixes=['_meeting', '_spot'] ) joined_df['rate'] = joined_df['rate_meeting'] - joined_df['rate_spot'] joined_df = joined_df.rename(columns={'effectiveDate_meeting': 'effectiveDate'}) else: joined_df = cbw_df if joined_df.empty: series = ExtendedSeries(dtype=float) else: latest = joined_df.index.max() _logger.info('selected pricing date %s', latest) joined_df = joined_df.loc[latest] biz_day = _get_custom_bd(_default_pricing_location(currency).value) # col_to_plot = 'effectiveTenor' col_to_plot = 'effectiveDate' joined_df.loc[:, 'expirationDate'] = joined_df[col_to_plot].apply(_get_term_struct_date, args=(latest, biz_day)) joined_df = joined_df.set_index('expirationDate') joined_df.index = pd.DatetimeIndex(joined_df.index) joined_df = joined_df.sort_index() joined_df = joined_df.loc[ pd.Timestamp(DataContext.current.start_date) : pd.Timestamp(DataContext.current.end_date) ] series = ExtendedSeries(dtype=float) if joined_df.empty else ExtendedSeries(joined_df['rate']) series.dataset_ids = getattr(joined_df, 'dataset_ids', ()) if series.empty: # Raise descriptive error if no data returned + date context is in the past check_forward_looking(None, source, 'policy_rate_term_structure') return series ================================================ FILE: gs_quant/timeseries/measures_reports.py ================================================ """ Copyright 2020 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import re from enum import Enum from typing import Optional, Union, List import pandas as pd import numpy as np import math import scipy.stats as stats from pandas.tseries.offsets import BDay from pydash import decapitalize from gs_quant.api.gs.data import QueryType, GsDataApi, DataQuery from gs_quant.data import DataMeasure from gs_quant.data.core import DataContext from gs_quant.entities.entity import EntityType from gs_quant.errors import MqValueError from gs_quant.markets.portfolio_manager import PortfolioManager from gs_quant.markets.report import ( FactorRiskReport, PerformanceReport, ThematicReport, ReturnFormat, format_aum_for_return_calculation, get_pnl_percent, get_factor_pnl_percent_for_single_factor, ) from gs_quant.markets.securities import Bond from gs_quant.models.risk_model import FactorRiskModel from gs_quant.target.reports import PositionSourceType from gs_quant.timeseries import plot_measure_entity, beta, correlation, max_drawdown from gs_quant.timeseries.algebra import geometrically_aggregate from gs_quant.timeseries.measures import _extract_series_from_df, SecurityMaster, AssetIdentifier class Unit(Enum): NOTIONAL = 'Notional' PERCENT = 'Percent' @plot_measure_entity(EntityType.REPORT, [QueryType.FACTOR_EXPOSURE]) def factor_exposure( report_id: str, factor_name: str, unit: str = 'Notional', *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Factor exposure data associated with a factor in a factor risk report :param report_id: factor risk report id :param factor_name: factor name :param unit: unit in which the timeseries is returned (Notional or Percent) :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: Timeseries of factor exposure for requested factor """ return _get_factor_data(report_id, factor_name, QueryType.FACTOR_EXPOSURE, Unit(unit)) @plot_measure_entity(EntityType.REPORT, [QueryType.FACTOR_PNL]) def factor_pnl( report_id: str, factor_name: str, unit: str = 'Notional', *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Factor PnL data associated with a factor in a factor risk report :param report_id: factor risk report id :param factor_name: factor name :param unit: unit in which the timeseries is returned (Notional or Percent) :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: Timeseries of factor pnl for requested factor """ return _get_factor_data(report_id, factor_name, QueryType.FACTOR_PNL, Unit(unit)) @plot_measure_entity(EntityType.REPORT, [QueryType.FACTOR_PROPORTION_OF_RISK]) def factor_proportion_of_risk( report_id: str, factor_name: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None ) -> pd.Series: """ Factor proportion of risk data associated with a factor in a factor risk report :param report_id: factor risk report id :param factor_name: factor name :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: Timeseries of factor proportion of risk for requested factor """ return _get_factor_data(report_id, factor_name, QueryType.FACTOR_PROPORTION_OF_RISK) @plot_measure_entity(EntityType.REPORT, [QueryType.DAILY_RISK]) def daily_risk( report_id: str, factor_name: str = 'Total', *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Daily risk data associated with a factor in a factor risk report :param report_id: factor risk report id :param factor_name: factor name (must be "Factor", "Specific", or "Total") :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: Timeseries of daily risk for requested factor """ return _get_factor_data(report_id, factor_name, QueryType.DAILY_RISK) @plot_measure_entity(EntityType.REPORT, [QueryType.ANNUAL_RISK]) def annual_risk( report_id: str, factor_name: str = 'Total', *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Annual risk data associated with a factor in a factor risk report :param report_id: factor risk report id :param factor_name: factor name (must be "Factor", "Specific", or "Total") :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: Timeseries of daily risk for requested factor """ return _get_factor_data(report_id, factor_name, QueryType.ANNUAL_RISK) @plot_measure_entity(EntityType.REPORT, [QueryType.PNL]) def normalized_performance( report_id: str, leg: str = None, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None ) -> pd.Series: """ Returns the Normalized Performance of a performance report based on AUM source :param report_id: id of performance report :param leg: short or long :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: portfolio normalized performance **Usage** Returns the normalized performance of the portfolio. :math:`NP(L/S)_{t} = SUM( PNL(L/S)_{t}/ ( EXP(L/S)_{t} ) - cPNL(L/S)_{t-1) ) if ( EXP(L/S)_{t} ) > 0 else: 1/ SUM( PNL(L/S)_{t}/ ( EXP(L/S)_{t} ) - cPNL(L/S)_{t-1) )` For each leg, short and long, then: :math:`NP_{t} = NP(L)_{t} * SUM(EXP(L)) / SUM(GROSS_EXP) + NP(S)_{t} * SUM(EXP(S)) / SUM(GROSS_EXP) + 1` If leg is short, set SUM(EXP(L)) to 0, if leg is long, set SUM(EXP(S)) to 0 where :math:`cPNL(L/S)_{t-1}` is your performance reports cumulative long or short PNL at date t-1 where :math:`PNL(L/S)_{t}` is your performance reports long or short pnl at date t where :math:`GROSS_EXP_{t}` is portfolio gross exposure on date t where :math:`EXP(L/S)_{t}` is the long or short exposure on date t """ start_date = DataContext.current.start_time end_date = DataContext.current.end_time start_date = (start_date - BDay(1)).date() end_date = end_date.date() performance_report = PerformanceReport.get(report_id) constituent_data = performance_report.get_portfolio_constituents( fields=['assetId', 'pnl', 'quantity', 'netExposure'], start_date=start_date, end_date=end_date ).set_index('date') if leg: if leg.lower() == "long": constituent_data = constituent_data[constituent_data['quantity'] > 0] if leg.lower() == "short": constituent_data = constituent_data[constituent_data['quantity'] < 0] # Split into long and short and aggregate across dates long_side = _return_metrics( constituent_data[constituent_data['quantity'] > 0], list(constituent_data.index.unique()), "long" ) short_side = _return_metrics( constituent_data[constituent_data['quantity'] < 0], list(constituent_data.index.unique()), "short" ) short_exposure = sum(abs(short_side['exposure'])) long_exposure = sum(long_side['exposure']) gross_exposure = short_exposure + long_exposure long_side['longRetWeighted'] = (long_side['longMetrics'] - 1) * (long_exposure / gross_exposure) short_side['shortRetWeighted'] = (short_side['shortMetrics'] - 1) * (short_exposure / gross_exposure) combined = long_side[['longRetWeighted']].join(short_side[['shortRetWeighted']], how='inner') combined['normalizedPerformance'] = combined['longRetWeighted'] + combined['shortRetWeighted'] + 1 return pd.Series(combined['normalizedPerformance'], name="normalizedPerformance").dropna() @plot_measure_entity(EntityType.REPORT, [QueryType.PNL]) def long_pnl( report_id: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None ) -> pd.Series: """ PNL from long holdings :param report_id: id of performance report :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: portfolio long pnl """ start_date = DataContext.current.start_time.date() end_date = DataContext.current.end_time.date() performance_report = PerformanceReport.get(report_id) constituent_data = performance_report.get_portfolio_constituents( fields=['pnl', 'quantity'], start_date=start_date, end_date=end_date ).set_index('date') long_leg = constituent_data[constituent_data['quantity'] > 0]['pnl'] long_leg = long_leg.groupby(level=0).sum() return pd.Series(long_leg, name="longPnl") @plot_measure_entity(EntityType.REPORT, [QueryType.PNL]) def short_pnl( report_id: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None ) -> pd.Series: """ PNL from short holdings :param report_id: id of performance report :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: portfolio short pnl """ start_date = DataContext.current.start_time.date() end_date = DataContext.current.end_time.date() performance_report = PerformanceReport.get(report_id) constituent_data = performance_report.get_portfolio_constituents( fields=['pnl', 'quantity'], start_date=start_date, end_date=end_date ).set_index('date') short_leg = constituent_data[constituent_data['quantity'] < 0]['pnl'] short_leg = short_leg.groupby(level=0).sum() return pd.Series(short_leg, name="shortPnl") @plot_measure_entity(EntityType.REPORT, [QueryType.THEMATIC_EXPOSURE]) def thematic_exposure( report_id: str, basket_ticker: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None ) -> pd.Series: """ Thematic exposure of a portfolio to a requested GS thematic flagship basket :param report_id: portfolio thematic analytics report id :param basket_ticker: ticker for thematic basket :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: Timeseries of daily thematic beta of portfolio to requested flagship basket """ thematic_report = ThematicReport.get(report_id) asset = SecurityMaster.get_asset(basket_ticker, AssetIdentifier.TICKER) df = thematic_report.get_thematic_exposure( start_date=DataContext.current.start_date, end_date=DataContext.current.end_date, basket_ids=[asset.get_marquee_id()], ) if not df.empty: df = df.set_index('date') df.index = pd.to_datetime(df.index) return _extract_series_from_df(df, QueryType.THEMATIC_EXPOSURE) @plot_measure_entity(EntityType.REPORT, [QueryType.THEMATIC_EXPOSURE]) def thematic_beta( report_id: str, basket_ticker: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None ) -> pd.Series: """ Thematic beta values of a portfolio to a requested GS thematic flagship basket :param report_id: portfolio thematic analytics report id :param basket_ticker: ticker for thematic basket :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: Timeseries of daily thematic beta of portfolio to requested flagship basket """ thematic_report = ThematicReport.get(report_id) asset = SecurityMaster.get_asset(basket_ticker, AssetIdentifier.TICKER) df = thematic_report.get_thematic_betas( start_date=DataContext.current.start_date, end_date=DataContext.current.end_date, basket_ids=[asset.get_marquee_id()], ) if not df.empty: df = df.set_index('date') df.index = pd.to_datetime(df.index) return _extract_series_from_df(df, QueryType.THEMATIC_BETA) @plot_measure_entity(EntityType.REPORT, [QueryType.AUM]) def aum(report_id: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None) -> pd.Series: """ AUM of the portfolio :param report_id: id of performance report :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: portfolio long pnl """ start_date = DataContext.current.start_time.date() end_date = DataContext.current.end_time.date() performance_report = PerformanceReport.get(report_id) aum_curve = performance_report.get_aum(start_date=start_date, end_date=end_date) aum_dict = [{'date': key, 'aum': aum_curve[key]} for key in aum_curve] # Create and return timeseries df = pd.DataFrame(aum_dict) if not df.empty: df = df.set_index('date') df.index = pd.to_datetime(df.index) return _extract_series_from_df(df, QueryType.AUM) @plot_measure_entity(EntityType.REPORT, [QueryType.PNL]) def pnl( report_id: str, unit: str = 'Notional', *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ PNL from all holdings, if unit is Percent, geometrically aggregate over time frame :param report_id: id of performance report :param unit: by default uses mode as Notional, but can also be set to Percent :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: portfolio pnl """ start_date = DataContext.current.start_time.date() end_date = DataContext.current.end_time.date() performance_report = PerformanceReport.get(report_id) pnl_df = performance_report.get_pnl(start_date, end_date) if unit == Unit.PERCENT.value: return get_pnl_percent(performance_report, pnl_df, 'pnl', start_date, end_date) else: pnl_df = pnl_df.set_index('date') return pd.Series(pnl_df['pnl'], name="pnl") @plot_measure_entity(EntityType.REPORT) def historical_simulation_estimated_pnl( report_id: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None ) -> pd.Series: """ Estimated Pnl from replaying a historical simulation scenario on your latest positions :param report_id: id of performance report :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: portfolio estimated pnl """ factor_attributed_pnl = _replay_historical_factor_moves_on_latest_positions(report_id, []) total_factor_attributed_pnl = factor_attributed_pnl.apply(np.sum, axis=1).to_frame("estimatedPnl") total_factor_attributed_pnl.index = pd.to_datetime(total_factor_attributed_pnl.index) return total_factor_attributed_pnl.squeeze() @plot_measure_entity(EntityType.REPORT) def historical_simulation_estimated_factor_attribution( report_id: str, factor_name: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None ) -> pd.Series: """ Estimated Pnl attributed to the factor after replaying a historical simulation scenario on a portfolio's latest positions :param report_id: id of performance report :param factor_name: name of the factor :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: portfolio estimated pnl """ factor_attributed_pnl = _replay_historical_factor_moves_on_latest_positions(report_id, [factor_name]) factor_attributed_pnl.index = pd.to_datetime(factor_attributed_pnl.index) return factor_attributed_pnl.squeeze() @plot_measure_entity(EntityType.REPORT) def hit_rate( report_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ The hit rate of a portfolio is a percentage of positions that have generated positive returns over a given period. :param report_id: id of performance report :param rolling_window: size of rolling window :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of the hit rate """ start_date = DataContext.current.start_time.date() end_date = DataContext.current.end_time.date() performance_report = PerformanceReport.get(report_id) window = _parse_window(rolling_window) portfolio_constituents = performance_report.get_portfolio_constituents( fields=['date', 'pnl'], start_date=start_date, end_date=end_date ).set_index('date') portfolio_constituents = portfolio_constituents[portfolio_constituents['entryType'] == 'Holding'] portfolio_constituents = portfolio_constituents.sort_values('date') pivot_constituents = portfolio_constituents.pivot_table(index='date', columns='assetId', values='pnl') rolling_compound = (pivot_constituents).rolling(window=window).sum() positive_count = (rolling_compound > 0).sum(axis=1) valid_assets = rolling_compound.count(axis=1) return positive_count / valid_assets @plot_measure_entity(EntityType.REPORT) def portfolio_max_drawdown( report_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ The largest drop from peak to trough in a sub-period over the stated timeframe :param report_id: id of performance report :param rolling_window: size of rolling window :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: portfolio estimated pnl """ aum_series = aum(report_id) rolling_window = _parse_window(rolling_window) max_drawdown_series = aum_series.rolling(window=rolling_window).apply(_max_drawdown, raw=False) return max_drawdown_series @plot_measure_entity(EntityType.REPORT) def drawdown_length( report_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ The length in days between the peak and the trough of the maximum drawdown :param report_id: id of performance report :param rolling_window: size of rolling window :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of the drawdown length """ aum_series = aum(report_id) rolling_window = _parse_window(rolling_window) drawdown_length_series = aum_series.rolling(window=rolling_window).apply(_drawdown_length, raw=False) return drawdown_length_series @plot_measure_entity(EntityType.REPORT) def max_recovery_period( report_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ The maximum number of days used to reach a previously broken price level over the stated timeframe. :param report_id: id of performance report :param rolling_window: size of rolling window :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of max recovery period """ aum_series = aum(report_id) rolling_window = _parse_window(rolling_window) recovery_period_series = aum_series.rolling(window=rolling_window).apply(_max_recovery_period, raw=False) return recovery_period_series def _max_recovery_period(series): peak = series.iloc[0] recovery_periods = [] current_drawdown = 0 in_drawdown = False for val in series[1:]: if val < peak: in_drawdown = True current_drawdown += 1 else: if in_drawdown: recovery_periods.append(current_drawdown) in_drawdown = False peak = val current_drawdown = 0 return max(recovery_periods) if recovery_periods else 0 def _drawdown_length(series): peak = series.iloc[0] drawdown_start = None max_drawdown_length = 0 current_length = 0 for value in series: if value >= peak: # Reset when a new peak is found peak = value drawdown_start = None current_length = 0 else: # Track the drawdown length if drawdown_start is None: drawdown_start = value current_length += 1 max_drawdown_length = max(max_drawdown_length, current_length) return max_drawdown_length @plot_measure_entity(EntityType.REPORT) def standard_deviation( report_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Volatility of the total return over the stated time frame. :param report_id: id of performance report :param rolling_window: length of window :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of standard deviation """ portfolio_pnl = _get_daily_pnl(report_id) rolling_window = _parse_window(rolling_window) rolling_std = portfolio_pnl.rolling(window=rolling_window).std() return rolling_std @plot_measure_entity(EntityType.REPORT) def downside_risk( report_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Volatility of the periodic returns that are lower than the mean return over the stated time frame. Standard deviation is calculated using all returns, downside risk is calculated using only the returns that are below the mean. :param report_id: id of performance report :param rolling_window: length of window :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of downside_risk """ portfolio_pnl = _get_daily_pnl(report_id) rolling_window = _parse_window(rolling_window) downside_risk_series = portfolio_pnl.rolling(window=rolling_window).apply(_rolling_downside_risk, raw=False) return downside_risk_series @plot_measure_entity(EntityType.REPORT) def semi_variance( report_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Volatility of the periodic returns that are lower than the mean return over the stated time frame. Standard deviation is calculated using all returns, semi variance is calculated using only the returns that are below 0. :param report_id: id of performance report :param rolling_window: length of window :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of downside_risk """ portfolio_pnl = _get_daily_pnl(report_id) rolling_window = _parse_window(rolling_window) semi_variance_series = portfolio_pnl.rolling(window=rolling_window).apply(_rolling_semi_variance, raw=False) return semi_variance_series @plot_measure_entity(EntityType.REPORT) def kurtosis( report_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Kurtosis measure the peakedness or flatness of the return distribution over the state time frame. In a flat distribution the average value is more likely to occur. :param report_id: id of performance report :param rolling_window: length of window :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of kurtosis """ portfolio_pnl = _get_daily_pnl(report_id) rolling_window = _parse_window(rolling_window) rolling_kurtosis = portfolio_pnl.rolling(window=rolling_window).apply( lambda x: stats.kurtosis(x, fisher=True, bias=False), raw=False ) return rolling_kurtosis @plot_measure_entity(EntityType.REPORT) def skewness( report_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Skewness measures the degree of symmetry of the return distribution over the stated time frame. If the left tail is more pronounced than the right tail, it is said the return have negative skewness. :param report_id: id of performance report :param rolling_window: length of window :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of skewness """ portfolio_pnl = _get_daily_pnl(report_id) rolling_window = _parse_window(rolling_window) rolling_skewness = portfolio_pnl.rolling(window=rolling_window).apply( lambda x: stats.skew(x, bias=False), raw=False ) return rolling_skewness @plot_measure_entity(EntityType.REPORT) def realized_var( report_id: str, rolling_window: Union[int, str], confidence_interval: float = 0.95, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ The maximum expected loss of the portfolios, calculated using the natural distribution of returns over a stated time frame and is based on a confidence level. :param report_id: id of performance report :param rolling_window: length of window :param confidence_interval: confidence interval for variance :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of realized var """ portfolio_pnl = _get_daily_pnl(report_id) rolling_window = _parse_window(rolling_window) realized_variance = portfolio_pnl.rolling(window=rolling_window).quantile(1 - confidence_interval) realized_variance = realized_variance * -1 return realized_variance @plot_measure_entity(EntityType.REPORT) def tracking_error( report_id: str, benchmark_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ The standard deviation of the excess return relative to the benchmark over the state time frame. Tracking Error = sqrt( sum((ExcessRi - ExcessRmean)^2) / (N-1) ) Where: ExcessRi = excess return in period i ExcessRmean = mean excess return during bull markets N = number of periods :param report_id: id of performance report :param rolling_window: length of window :param benchmark_id: id of benchmark :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of tracking error """ start_date = DataContext.current.start_time.date() end_date = DataContext.current.end_time.date() rolling_window = _parse_window(rolling_window) benchmark_returns = _get_benchmark_daily_return(benchmark_id, start_date, end_date) portfolio_returns = _get_daily_pnl(report_id) active_return = portfolio_returns - benchmark_returns rolling_error = active_return.rolling(window=rolling_window).std() return rolling_error @plot_measure_entity(EntityType.REPORT) def tracking_error_bear( report_id: str, benchmark_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ The standard deviation of the excess return relative to the benchmark over the state time frame, only counting the periods when benchmark returns were negative. Bear Tracking Error = sqrt( sum((ExcessRi - ExcessRmeanBear)^2) / (N-1) ) for all when Benchmark Return < 0 Where: ExcessRi = excess return in period i ExcessRmeanBear = mean excess return during bull markets N = number of periods :param report_id: id of performance report :param rolling_window: length of window :param benchmark_id: id of benchmark :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of tracking error """ start_date = DataContext.current.start_time.date() end_date = DataContext.current.end_time.date() rolling_window = _parse_window(rolling_window) benchmark_returns = _get_benchmark_return(benchmark_id, start_date, end_date) portfolio_returns = _get_daily_pnl(report_id) is_bear = benchmark_returns <= 0 active_return = portfolio_returns - benchmark_returns rolling_error_bear = active_return.rolling(window=rolling_window).apply( lambda x: np.std(x, ddof=1) if is_bear.loc[x.index[-1]] else np.nan, raw=False ) return rolling_error_bear @plot_measure_entity(EntityType.REPORT) def tracking_error_bull( report_id: str, benchmark_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ The standard deviation of the excess return relative to the benchmark over the state time frame, only counting the periods when benchmark returns were positive. Bull Tracking Error = sqrt( sum((ExcessRi - ExcessRmeanBull)^2) / (N-1) ) for all when Benchmark Return > 0 Where: ExcessRi = excess return in period i ExcessRmeanBull = mean excess return during bull markets N = number of periods :param report_id: id of performance report :param rolling_window: length of window :param benchmark_id: id of benchmark :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of tracking error """ start_date = DataContext.current.start_time.date() end_date = DataContext.current.end_time.date() rolling_window = _parse_window(rolling_window) benchmark_returns = _get_benchmark_return(benchmark_id, start_date, end_date) portfolio_pnl = _get_daily_pnl(report_id) is_bull = benchmark_returns > 0 active_return = portfolio_pnl - benchmark_returns rolling_error_bull = active_return.rolling(window=rolling_window).apply( lambda x: np.std(x, ddof=1) if is_bull.loc[x.index[-1]] else np.nan, raw=False ) return rolling_error_bull @plot_measure_entity(EntityType.REPORT) def portfolio_sharpe_ratio( report_id: str, risk_free_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Risk-adjusted measure that calculates the excess return over the risk free rate per unit of volatility Sharpe Ratio = (Rp - Rf) / σp Where: Rp = Portfolio Return Rf = Risk-Free Rate σp = Standard Deviation of Portfolio Return. :param report_id: id of performance report :param risk_free_id: id of risk-free asset :param rolling_window: length of window :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of sharpe ratio """ start_date = DataContext.current.start_time.date() end_date = DataContext.current.end_time.date() window = _parse_window(rolling_window) portfolio_pnl = _get_daily_pnl(report_id) risk_free_rate_daily = (1 + _get_risk_free_rate(risk_free_id, start_date, end_date)) ** (1 / 252) - 1 daily_excess = (portfolio_pnl - risk_free_rate_daily).dropna() rolling_mean = daily_excess.rolling(window).mean() rolling_std = daily_excess.rolling(window).std() portfolio_sharpe_ratio_series = (rolling_mean / rolling_std) * np.sqrt(252) return portfolio_sharpe_ratio_series @plot_measure_entity(EntityType.REPORT) def calmar_ratio( report_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Risk-adjusted performance metric that divides an investment strategy’s annualized rate of return by its maximum drawdown, focusing specifically on the strategy’s return per unit of downside risk. Calmar Ratio = Annualized Return / Max Drawdown :param report_id: id of performance report :param rolling_window: size of window to use :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of the calmar ratio """ rolling_window = _parse_window(rolling_window) portfolio_pnl = _get_daily_pnl(report_id) cumulative_return = (1 + portfolio_pnl) ** np.sqrt(252) - 1 cumulative_return.index = pd.to_datetime(cumulative_return.index) aum_series = aum(report_id) max_drawdown_series = max_drawdown(aum_series, rolling_window) calmar = cumulative_return / max_drawdown_series.abs() return calmar @plot_measure_entity(EntityType.REPORT) def sortino_ratio( report_id: str, benchmark_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Risk-adjusted measure that calculates the excess return over the benchmark per unit of semi-variance Sortino Ratio = (Rp - Rb) / Downside Deviation Where: Rp = Portfolio Return Rb = Benchmark Return Downside Deviation = Standard Deviation of negative asset returns. :param report_id: id of performance report :param benchmark_id: id of benchmark asset, can be security or risk free :param rolling_window: size of window to use :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: portfolio estimated pnl """ start_date = DataContext.current.start_time.date() end_date = DataContext.current.end_time.date() rolling_window = _parse_window(rolling_window) portfolio_pnl = _get_daily_pnl(report_id) security = SecurityMaster.get_asset(benchmark_id, AssetIdentifier.MARQUEE_ID) if isinstance(security, Bond): benchmark_pnl = (1 + _get_risk_free_rate(benchmark_id, start_date, end_date)) ** (1 / 252) - 1 else: benchmark_pnl = _get_benchmark_daily_return(benchmark_id, start_date, end_date) excess = (portfolio_pnl - benchmark_pnl).dropna() sortino = excess.rolling(window=rolling_window).apply(lambda x: _compute_sortino(x), raw=False) return sortino @plot_measure_entity(EntityType.REPORT) def jensen_alpha( report_id: str, benchmark_id: str, risk_free_id: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Risk-Adjusted measure that calculates the actual return of the portfolio over and above the return predicted by the CAPM, given the portfolios beta and benchmark returns. Jensen's Alpha = Rp - [Rf + Beta * (Rb - Rf)] Where: Rp = Portfolio Return Rf = Risk-Free Rate Beta = Portfolio Beta Rb = Benchmark Return :param report_id: id of performance report :param benchmark_id: id of benchmark asset :param risk_free_id: id of risk-free asset :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of jensen alpha """ start_date = DataContext.current.start_time.date() end_date = DataContext.current.end_time.date() portfolio_pnl = _get_daily_pnl(report_id) benchmark_pnl = _get_benchmark_daily_return(benchmark_id, start_date, end_date) risk_free_rate = (1 + _get_risk_free_rate(risk_free_id, start_date, end_date)) ** (1 / 252) - 1 portfolio_beta_series = beta(portfolio_pnl, benchmark_pnl, prices=False) jensen = portfolio_pnl - (risk_free_rate + portfolio_beta_series * (benchmark_pnl - risk_free_rate)) return jensen @plot_measure_entity(EntityType.REPORT) def jensen_alpha_bear( report_id: str, benchmark_id: str, risk_free_id: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Risk-Adjusted measure that calculates the actual return of the portfolio over and above the return predicted by the CAPM, given the portfolios beta and benchmark returns. calculated based only on the periods when the benchmark return was negative Jensen's Alpha = Rp - [Rf + Beta * (Rb - Rf)] Where: Rp = Portfolio Return Rf = Risk-Free Rate Beta = Portfolio Beta Rb = Benchmark Return :param report_id: id of performance report :param benchmark_id: id of benchmark asset :param risk_free_id: id of risk-free asset :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of jensen alpha """ start_date = DataContext.current.start_time.date() end_date = DataContext.current.end_time.date() portfolio_pnl = _get_daily_pnl(report_id) benchmark_pnl = _get_benchmark_daily_return(benchmark_id, start_date, end_date) risk_free_rate = (1 + _get_risk_free_rate(risk_free_id, start_date, end_date)) ** (1 / 252) - 1 portfolio_beta_series = beta(portfolio_pnl, benchmark_pnl, prices=False) benchmark_pnl = benchmark_pnl[benchmark_pnl < 0] jensen = portfolio_pnl - (risk_free_rate + portfolio_beta_series * (benchmark_pnl - risk_free_rate)) return jensen @plot_measure_entity(EntityType.REPORT) def jensen_alpha_bull( report_id: str, benchmark_id: str, risk_free_id: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Risk-Adjusted measure that calculates the actual return of the portfolio over and above the return predicted by the CAPM, given the portfolios beta and benchmark returns. calculated based only on the periods when the benchmark return was positive Jensen's Alpha = Rp - [Rf + Beta * (Rb - Rf)] Where: Rp = Portfolio Return Rf = Risk-Free Rate Beta = Portfolio Beta Rb = Benchmark Return :param report_id: id of performance report :param benchmark_id: id of benchmark asset :param risk_free_id: id of risk-free asset :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of jensen alpha """ start_date = DataContext.current.start_time.date() end_date = DataContext.current.end_time.date() portfolio_pnl = _get_daily_pnl(report_id) benchmark_pnl = _get_benchmark_daily_return(benchmark_id, start_date, end_date) risk_free_rate = (1 + _get_risk_free_rate(risk_free_id, start_date, end_date)) ** (1 / 252) - 1 portfolio_beta_series = beta(portfolio_pnl, benchmark_pnl, prices=False) benchmark_pnl = benchmark_pnl[benchmark_pnl > 0] jensen = portfolio_pnl - (risk_free_rate + portfolio_beta_series * (benchmark_pnl - risk_free_rate)) return jensen @plot_measure_entity(EntityType.REPORT) def information_ratio( report_id: str, benchmark_id: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None ) -> pd.Series: """ Risk-adjusted measure that calculates the excess return over the benchmark, per unit of tracking error volatility. It measures the consistency with which the portfolio is beating the benchmark. Information Ratio = (Rp - Rb) / Tracking Error Where: Rp = Portfolio Return Rb = Benchmark Return Tracking Error = Standard Deviation of the difference between the portfolio and benchmark returns. :param report_id: id of performance report :param benchmark_id: id of benchmark asset :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of the information ratio """ start_date = DataContext.current.start_time.date() end_date = DataContext.current.end_time.date() portfolio_pnl = _get_daily_pnl(report_id) benchmark_pnl = _get_benchmark_daily_return(benchmark_id, start_date, end_date) portfolio_excess = portfolio_pnl - benchmark_pnl portfolio_std = np.std(portfolio_excess) information = portfolio_excess / portfolio_std return information @plot_measure_entity(EntityType.REPORT) def information_ratio_bear( report_id: str, benchmark_id: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None ) -> pd.Series: """ Risk-adjusted measure that calculates the excess return over the benchmark, per unit of tracking error volatility, counting only the periods when the benchmark return was negative Information Ratio = (Rp - Rb) / Tracking Error Where: Rp = Portfolio Return Rb = Benchmark Return Tracking Error = Standard Deviation of the difference between the portfolio and benchmark returns. :param report_id: id of performance report :param benchmark_id: id of benchmark asset :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of the information ratio """ start_date = DataContext.current.start_time.date() end_date = DataContext.current.end_time.date() portfolio_pnl = _get_daily_pnl(report_id) benchmark_pnl = _get_benchmark_daily_return(benchmark_id, start_date, end_date) benchmark_pnl = benchmark_pnl[benchmark_pnl < 0] portfolio_excess = portfolio_pnl - benchmark_pnl portfolio_std = np.std(portfolio_excess) information = portfolio_excess / portfolio_std return information @plot_measure_entity(EntityType.REPORT) def information_ratio_bull( report_id: str, benchmark_id: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None ) -> pd.Series: """ Risk-adjusted measure that calculates the excess return over the benchmark, per unit of tracking error volatility, counting only the periods when the benchmark return was positive. Information Ratio = (Rp - Rb) / Tracking Error Where: Rp = Portfolio Return Rb = Benchmark Return Tracking Error = Standard Deviation of the difference between the portfolio and benchmark returns. :param report_id: id of performance report :param benchmark_id: id of benchmark asset :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: portfolio estimated pnl """ start_date = DataContext.current.start_time.date() end_date = DataContext.current.end_time.date() portfolio_pnl = _get_daily_pnl(report_id) benchmark_pnl = _get_benchmark_daily_return(benchmark_id, start_date, end_date) benchmark_pnl = benchmark_pnl[benchmark_pnl > 0] portfolio_excess = portfolio_pnl - benchmark_pnl portfolio_std = np.std(portfolio_excess) information = portfolio_excess / portfolio_std return information @plot_measure_entity(EntityType.REPORT) def modigliani_ratio( report_id: str, benchmark_id: str, risk_free_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Modigliani RAP measures how much the portfolio would have returns if it had had the same risk as the benchmark. It is a linear transformation of the Sharpe Ratio, but the results are expressed in terms of performance. M2 = (Sharpe Ratio * σb) + Rf Where: Sharpe Ratio = Portfolio Sharpe Ratio σb = Standard Deviation of the Benchmark Return Rf = Risk Free Rate. :param report_id: id of performance report :param benchmark_id: id of benchmark asset :param risk_free_id: id of risk free asset :param rolling_window: size of window to use :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of the modigliani ratio """ start_date = DataContext.current.start_time.date() end_date = DataContext.current.end_time.date() rolling_window = _parse_window(rolling_window) benchmark_return = _get_benchmark_daily_return(benchmark_id, start_date, end_date) risk_free_rates = (1 + _get_risk_free_rate(risk_free_id, start_date, end_date)) ** (1 / 252) - 1 sharpe_series = portfolio_sharpe_ratio(report_id, risk_free_id, rolling_window) benchmark_excess = benchmark_return - risk_free_rates std_benchmark = benchmark_excess.dropna().rolling(window=rolling_window).std() modigliani = (sharpe_series * std_benchmark) + risk_free_rates return modigliani @plot_measure_entity(EntityType.REPORT) def treynor_measure( report_id: str, risk_free_id: str, benchmark_id: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Risk-adjusted measure that calculates the excess return over the risk free rate per unit of beta relative to the benchmark. This is useful for assessing the excess return from each unit of systematic risk. Treynor Ratio = (Rp - Rf) / Beta Where: Rp = Portfolio Return Rf = Risk-Free Rate Beta = Portfolio Beta :param report_id: id of performance report :param risk_free_id: id of risk-free asset :param benchmark_id: id of benchmark asset :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: portfolio estimated pnl """ start_date = DataContext.current.start_time.date() end_date = DataContext.current.end_time.date() portfolio_return = _get_daily_pnl(report_id) security = SecurityMaster.get_asset(benchmark_id, AssetIdentifier.MARQUEE_ID) spot_data_coordinate = security.get_data_coordinate(DataMeasure.SPOT_PRICE) benchmark_pricing = spot_data_coordinate.get_series(start=start_date, end=end_date) benchmark_pricing = benchmark_pricing.sort_index() portfolio_return = (1 + portfolio_return) ** np.sqrt(252) - 1 risk_free_rate = _get_risk_free_rate(risk_free_id, start_date, end_date) beta_series = beta(aum(report_id), benchmark_pricing) treynor_series = (portfolio_return - risk_free_rate) / beta_series return treynor_series @plot_measure_entity(EntityType.REPORT) def alpha( report_id: str, benchmark_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ This is the intercept from a regression analysis, where the portfolio's returns are regressed against the benchmark's returns. It represents the portion of the portfolio's return that is not explained by the benchmark's performance. :param report_id: id of performance report :param benchmark_id: id of benchmark asset :param rolling_window: size of window to use :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of the beta """ start_date = DataContext.current.start_time.date() end_date = DataContext.current.end_time.date() rolling_window = _parse_window(rolling_window) portfolio_pnl = _get_daily_pnl(report_id) benchmark_pnl = _get_benchmark_return(benchmark_id, start_date, end_date) intercepts = {} benchmark_pnl = benchmark_pnl.reindex(portfolio_pnl.index) for i in range(rolling_window, len(portfolio_pnl) + 1): portfolio_window = portfolio_pnl.iloc[i - rolling_window : i] benchmark_window = benchmark_pnl.iloc[i - rolling_window : i] slope, intercept, *_ = stats.linregress(portfolio_window, benchmark_window) intercepts[portfolio_pnl.index[i - 1]] = intercept alpha_series = pd.Series(intercepts) return alpha_series @plot_measure_entity(EntityType.REPORT) def portfolio_beta( report_id: str, benchmark_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Beta defined as the slope of the regression between report pnl and benchmark pnl :param report_id: id of performance report :param benchmark_id: id of benchmark asset :param rolling_window: size of window to use :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of the beta """ start_date = DataContext.current.start_time.date() end_date = DataContext.current.end_time.date() rolling_window = _parse_window(rolling_window) portfolio_return = _get_daily_pnl(report_id) benchmark_pnl = _get_benchmark_return(benchmark_id, start_date, end_date) return beta(portfolio_return, benchmark_pnl, rolling_window, prices=False) @plot_measure_entity(EntityType.REPORT) def portfolio_correlation( report_id: str, benchmark_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Correlation coefficient between the portfolio and the benchmark returns over the stated time frame :param report_id: id of performance report :param benchmark_id: id of benchmark asset :param rolling_window: size of window to use :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: time series of the correlation coefficient """ start_date = DataContext.current.start_time.date() end_date = DataContext.current.end_time.date() rolling_window = _parse_window(rolling_window) portfolio_pnl = aum(report_id) portfolio_pnl.index = pd.to_datetime(portfolio_pnl.index) security = SecurityMaster.get_asset(benchmark_id, AssetIdentifier.MARQUEE_ID) spot_data_coordinate = security.get_data_coordinate(DataMeasure.SPOT_PRICE) benchmark_pricing = spot_data_coordinate.get_series(start=start_date, end=end_date) benchmark_pricing = benchmark_pricing.sort_index() return correlation(portfolio_pnl, benchmark_pricing, rolling_window) @plot_measure_entity(EntityType.REPORT) def capture_ratio( report_id: str, benchmark_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ A measure of how well portfolio is doing relative to the benchmark, defined as the ratio of the portfolio return to the benchmark return calculated on a daily basis and averaged over the stated time period. :param report_id: id of performance report :param benchmark_id: id of benchmark asset :param rolling_window: size of window to use :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: portfolio capture ratio """ start_date = DataContext.current.start_time.date() end_date = DataContext.current.end_time.date() rolling_window = _parse_window(rolling_window) benchmark_return = _get_benchmark_daily_return(benchmark_id, start_date, end_date) portfolio_return = _get_daily_pnl(report_id) avg_portfolio = portfolio_return.rolling(window=rolling_window).mean() avg_benchmark = benchmark_return.rolling(window=rolling_window).mean() capture_ratio_series = avg_portfolio / avg_benchmark return capture_ratio_series @plot_measure_entity(EntityType.REPORT) def r_squared( report_id: str, benchmark_id: str, rolling_window: Union[int, str], *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ A measure of how well the portfolio's performance correlates with the performance of the benchmark, and thus a measure of what portion of its performance may be explained by the performance of the benchmark. :param report_id: id of performance report :param benchmark_id: id of benchmark :param rolling_window: size of rolling window :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: portfolio r squared """ rolling_window = _parse_window(rolling_window) r_values = portfolio_correlation(report_id, benchmark_id, rolling_window) r_values_squared = r_values**2 return r_values_squared def _get_benchmark_return(benchmark_id, start_date, end_date): security = SecurityMaster.get_asset(benchmark_id, AssetIdentifier.MARQUEE_ID) spot_data_coordinate = security.get_data_coordinate(DataMeasure.SPOT_PRICE) benchmark_pricing = spot_data_coordinate.get_series(start=start_date, end=end_date) benchmark_pricing = benchmark_pricing.sort_index() start_price = benchmark_pricing.iloc[0] benchmark_returns = (benchmark_pricing / start_price) - 1 return benchmark_returns * 100 def _get_benchmark_daily_return(benchmark_id, start_date, end_date): security = SecurityMaster.get_asset(benchmark_id, AssetIdentifier.MARQUEE_ID) spot_data_coordinate = security.get_data_coordinate(DataMeasure.SPOT_PRICE) benchmark_pricing = spot_data_coordinate.get_series(start=start_date, end=end_date) benchmark_pricing = benchmark_pricing.sort_index() return benchmark_pricing.pct_change() def _max_drawdown(series): running_max = series.cummax() drawdown = (series - running_max) / running_max max_drawdown_ts = drawdown.cummin() return max_drawdown_ts.iloc[-1] def _compute_sortino(series): downside = series[series < 0] downside_std = np.sqrt((downside**2).mean()) * np.sqrt(252) mean_excess = series.mean() * 252 return mean_excess / downside_std if downside_std != 0 else np.nan def _rolling_downside_risk(series): negative_devs = series[series < series.mean()] - series.mean() semi_variance_series = (negative_devs**2).mean() return np.sqrt(semi_variance_series) def _rolling_semi_variance(series): negative_devs = series[series < 0] - series.mean() semi_variance_series = (negative_devs**2).mean() return np.sqrt(semi_variance_series) def _get_daily_pnl(report_id: str) -> pd.Series: portfolio_return = pnl(report_id) portfolio_return.index = pd.to_datetime(portfolio_return.index) portfolio_return = portfolio_return / aum(report_id).shift(1) return portfolio_return def _get_risk_free_rate(risk_free_id, start_date, end_date): risk_free = SecurityMaster.get_asset(risk_free_id, AssetIdentifier.MARQUEE_ID) risk_free_pricing_data = risk_free.get_data_coordinate('yield') risk_free_rate = risk_free_pricing_data.get_series(start=start_date, end=end_date) return risk_free_rate.drop_duplicates() def _replay_historical_factor_moves_on_latest_positions( report_id: str, factors: List[str] ) -> Union[pd.Series, pd.DataFrame]: start_date = DataContext.current.start_time.date() end_date = DataContext.current.end_time.date() risk_report = FactorRiskReport.get(report_id) risk_model_id = risk_report.get_risk_model_id() # Get data in batches of 365 days date_range = pd.bdate_range(start_date, end_date) batches = np.array_split([d.date() for d in date_range.tolist()], math.ceil(len(date_range.tolist()) / 365)) data_query_results = [] query = {"riskModel": risk_model_id} if factors: query.update({"factor": factors}) for batch in batches: data_query_results += GsDataApi.execute_query( 'RISK_MODEL_FACTOR', DataQuery(where=query, start_date=batch[0], end_date=batch[-1]) ).get('data', []) return_data = pd.DataFrame(data_query_results).pivot(columns="factor", index="date", values="return").sort_index() return_data_aggregated = (return_data / 100).apply(geometrically_aggregate) latest_report_date = risk_report.latest_end_date factor_exposures = risk_report.get_results( start_date=latest_report_date, end_date=latest_report_date, return_format=ReturnFormat.JSON ) factor_exposure_df = ( pd.DataFrame(factor_exposures).pivot(columns="factor", index="date", values="exposure").sort_index() ) factor_exposure_df = factor_exposure_df.reindex(columns=return_data_aggregated.columns) factor_attributed_pnl_values = return_data_aggregated.values * factor_exposure_df.values factor_attributed_pnl = pd.DataFrame( factor_attributed_pnl_values, index=return_data_aggregated.index, columns=return_data_aggregated.columns ) return factor_attributed_pnl def _parse_window(window: Union[int, str]): if isinstance(window, int): return window match = re.fullmatch(r'(\d+)([dwmy])', window.strip().lower()) if match is None: raise MqValueError('Invalid window format, please end with one of: ["d", "w", "m", "y"]') value, unit = match.groups() value = int(value) unit_multipliers = {'d': 1, 'w': 5, 'm': 22, 'y': 252} return value * unit_multipliers[unit] def _get_factor_data(report_id: str, factor_name: str, query_type: QueryType, unit: Unit = Unit.NOTIONAL) -> pd.Series: # Check params report = FactorRiskReport.get(report_id) if factor_name not in ['Factor', 'Specific', 'Total']: if query_type in [QueryType.DAILY_RISK, QueryType.ANNUAL_RISK]: raise MqValueError('Please pick a factor name from the following: ["Total", "Factor", "Specific"]') model = FactorRiskModel.get(report.get_risk_model_id()) factor = model.get_factor(factor_name) factor_name = factor.name # Extract relevant data for each date col_name = query_type.value.replace(' ', '') col_name = decapitalize(col_name) data_type = decapitalize(col_name[6:]) if col_name.startswith('factor') else col_name include_total_factor = factor_name != 'Total' and unit == Unit.PERCENT and query_type == QueryType.FACTOR_PNL factors_to_query = [factor_name, 'Total'] if include_total_factor else [factor_name] factor_data = report.get_results( factors=factors_to_query, start_date=DataContext.current.start_date, end_date=DataContext.current.end_date, return_format=ReturnFormat.JSON, ) total_data = [d for d in factor_data if d.get(data_type) is not None and d.get('factor') == 'Total'] factor_data = [d for d in factor_data if d.get(data_type) is not None and d.get('factor') == factor_name] if unit == Unit.PERCENT: if report.position_source_type != PositionSourceType.Portfolio: raise MqValueError('Unit can only be set to percent for portfolio reports') pm = PortfolioManager(report.position_source_id) tags = dict((tag.name, tag.value) for tag in report.parameters.tags) if report.parameters.tags else None performance_report = pm.get_performance_report(tags=tags) start_date = dt.datetime.strptime(min([d['date'] for d in factor_data]), '%Y-%m-%d').date() end_date = dt.datetime.strptime(max([d['date'] for d in factor_data]), '%Y-%m-%d').date() if query_type == QueryType.FACTOR_PNL: # Factor Pnl needs to be geometrically aggregated if unit is % aum_df = format_aum_for_return_calculation(performance_report, start_date, end_date) return get_factor_pnl_percent_for_single_factor(factor_data, total_data, aum_df, start_date) else: aum = performance_report.get_aum(start_date=start_date, end_date=end_date) for data in factor_data: if aum.get(data['date']) is None: raise MqValueError('Cannot convert to percent: Missing AUM on some dates in the date range') factor_exposures = [ {'date': d['date'], col_name: d[data_type] / aum.get(d['date']) * 100} for d in factor_data ] else: factor_exposures = [{'date': d['date'], col_name: d[data_type]} for d in factor_data] # Create and return timeseries df = pd.DataFrame(factor_exposures) if not df.empty: df = df.set_index('date') df.index = pd.to_datetime(df.index) return _extract_series_from_df(df, query_type) def _return_metrics(one_leg: pd.DataFrame, dates: list, name: str): if one_leg.empty: return pd.DataFrame(index=dates, data={f'{name}Metrics': [0 for d in dates], "exposure": [0 for d in dates]}) one_leg = one_leg.groupby(one_leg.index).agg(pnl=('pnl', 'sum'), exposure=('netExposure', 'sum')) one_leg['cumulativePnl'] = one_leg['pnl'].cumsum(axis=0) one_leg['normalizedExposure'] = one_leg['exposure'] - one_leg['cumulativePnl'] one_leg.iloc[0, one_leg.columns.get_loc('cumulativePnl')] = 0 one_leg[f'{name}Metrics'] = one_leg['cumulativePnl'] / one_leg['normalizedExposure'] + 1 one_leg[f'{name}Metrics'] = ( 1 / one_leg[f'{name}Metrics'] if one_leg['exposure'].iloc[-1] < 0 else one_leg[f'{name}Metrics'] ) return one_leg ================================================ FILE: gs_quant/timeseries/measures_risk_models.py ================================================ """ Copyright 2020 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from enum import Enum from typing import Dict, Optional, Union import pandas as pd from pydash import decapitalize from gs_quant.api.gs.data import QueryType from gs_quant.api.gs.risk_models import IntradayFactorDataSource from gs_quant.common import AssetClass, AssetType from gs_quant.data.core import DataContext from gs_quant.entities.entity import EntityType from gs_quant.markets.factor import ReturnFormat from gs_quant.markets.securities import Asset, AssetIdentifier from gs_quant.models.risk_model import FactorRiskModel, MarqueeRiskModel from gs_quant.target.risk_models import ( RiskModelDataMeasure, RiskModelDataAssetsRequest, RiskModelUniverseIdentifierRequest, ) from gs_quant.timeseries import plot_measure_entity, plot_measure, prices from .statistics import percentile from gs_quant.timeseries.measures import _extract_series_from_df import datetime as dt ModelMeasureStr = { 'Asset Universe': RiskModelDataMeasure.Asset_Universe, 'Historical Beta': RiskModelDataMeasure.Historical_Beta, 'Total Risk': RiskModelDataMeasure.Total_Risk, 'Specific Risk': RiskModelDataMeasure.Specific_Risk, 'Specific Return': RiskModelDataMeasure.Specific_Return, 'Daily Returns': RiskModelDataMeasure.Daily_Return, 'Estimation Universe Weight': RiskModelDataMeasure.Estimation_Universe_Weight, 'Residual Variance': RiskModelDataMeasure.Residual_Variance, 'Predicted Beta': RiskModelDataMeasure.Predicted_Beta, 'Global Predicted Beta': RiskModelDataMeasure.Global_Predicted_Beta, 'Universe Factor Exposure': RiskModelDataMeasure.Universe_Factor_Exposure, 'R Squared': RiskModelDataMeasure.R_Squared, 'Fair Value Gap Percent': RiskModelDataMeasure.Fair_Value_Gap_Percent, 'Fair Value Gap Standard Deviation': RiskModelDataMeasure.Fair_Value_Gap_Standard_Deviation, 'Bid Ask Spread': RiskModelDataMeasure.Bid_Ask_Spread, 'Bid Ask Spread 30d': RiskModelDataMeasure.Bid_Ask_Spread_30d, 'Bid Ask Spread 60d': RiskModelDataMeasure.Bid_Ask_Spread_60d, 'Bid Ask Spread 90d': RiskModelDataMeasure.Bid_Ask_Spread_90d, 'Trading Volume': RiskModelDataMeasure.Trading_Volume, 'Trading Volume 30d': RiskModelDataMeasure.Trading_Volume_30d, 'Trading Volume 60d': RiskModelDataMeasure.Trading_Volume_60d, 'Trading Volume 90d': RiskModelDataMeasure.Trading_Volume_90d, 'Trading Value 30d': RiskModelDataMeasure.Traded_Value_30d, 'Composite Volume': RiskModelDataMeasure.Composite_Volume, 'Composite Volume 30d': RiskModelDataMeasure.Composite_Volume_30d, 'Composite Volume 60d': RiskModelDataMeasure.Composite_Volume_60d, 'Composite Volume 90d': RiskModelDataMeasure.Composite_Volume_90d, 'Composite Value 30d': RiskModelDataMeasure.Composite_Value_30d, 'Composite Issuer Market Cap': RiskModelDataMeasure.Issuer_Market_Cap, 'Composite Price': RiskModelDataMeasure.Price, 'Composite Model Price': RiskModelDataMeasure.Model_Price, 'Composite Capitalization': RiskModelDataMeasure.Capitalization, 'Composite Currency': RiskModelDataMeasure.Currency, 'Composite Unadjusted Specific Risk': RiskModelDataMeasure.Unadjusted_Specific_Risk, 'Dividend Yield': RiskModelDataMeasure.Dividend_Yield, } class ModelMeasureString(Enum): ASSET_UNIVERSE = 'Asset Universe' HISTORICAL_BETA = 'Historical Beta' TOTAL_RISK = 'Total Risk' SPECIFIC_RISK = 'Specific Risk' SPECIFIC_RETURNS = 'Specific Return' DAILY_RETURNS = 'Daily Returns' ESTIMATION_UNVERSE_WEIGHT = 'Estimation Universe Weight' RESIDUAL_VARIANCE = 'Residual Variance' PREDICTED_BETA = 'Predicted Beta' GLOBAL_PREDICTED_BETA = 'Global Predicted Beta' UNIVERSE_FACTOR_EXPOSURE = 'Universe Factor Exposure' R_SQUARED = 'R Squared' FAIR_VALUE_GAP_PERCENT = 'Fair Value Gap Percent' FAIR_VALUE_GAP_STANDARD_DEVIATION = 'Fair Value Gap Standard Deviation' BID_ASK_SPREAD = 'Bid Ask Spread' BID_AKS_SPREAD_30D = 'Bid Ask Spread 30d' BID_AKS_SPREAD_60D = 'Bid Ask Spread 60d' BID_AKS_SPREAD_90D = 'Bid Ask Spread 90d' TRADING_VOLUME = 'Trading Volume' TRADING_VOLUME_30D = 'Trading Volume 30d' TRADING_VOLUME_60D = 'Trading Volume 60d' TRADING_VOLUME_90D = 'Trading Volume 90d' TRADING_VALUE_30D = 'Trading Value 30d' COMPOSITE_VOLUME = 'Composite Volume' COMPOSITE_VOLUME_30D = 'Composite Volume 30d' COMPOSITE_VOLUME_60D = 'Composite Volume 60d' COMPOSITE_VOLUME_90D = 'Composite Volume 90d' COMPOSITE_VALUE_30d = 'Composite Value 30d' COMPOSITE_ISSUER_MARKET_CAP = 'Composite Issuer Market Cap' COMPOSITE_PRICE = 'Composite Price' COMPOSITE_MODEL_PRICE = 'Composite Model Price' COMPOSITE_CAPITALIZATION = 'Composite Capitalization' COMPOSITE_CURRENCY = 'Composite Currency' COMPOSITE_UNADJUSTED_SPECIFIC_RISK = 'Composite Unadjusted Specific Risk' DIVIDEND_YIELD = 'Dividend Yield' @plot_measure((AssetClass.Equity,), (AssetType.Single_Stock,)) def risk_model_measure( asset: Asset, risk_model_id: str, risk_model_measure_selected: ModelMeasureString = ModelMeasureString.HISTORICAL_BETA, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Retrieve risk model measures for a given asset. :param asset: Asset object loaded from security master :param risk_model_id: ID of the risk model :param risk_model_measure_selected: Selected risk model measure :param source: Name of function caller :param real_time: Whether to retrieve intraday data instead of EOD :param request_id: Service request ID, if any :return: Risk model measure data for the asset """ model = MarqueeRiskModel.get(risk_model_id) gsid = asset.get_identifier(AssetIdentifier.GSID) risk_model_measure_selected = ModelMeasureStr[risk_model_measure_selected.value] query_results = model.get_data( measures=[risk_model_measure_selected, RiskModelDataMeasure.Asset_Universe], start_date=DataContext.current.start_time, end_date=DataContext.current.end_time, assets=RiskModelDataAssetsRequest(identifier=RiskModelUniverseIdentifierRequest.gsid, universe=[gsid]), limit_factors=False, ).get('results', []) measures = {} for result in query_results: if result: measure_name = set(result.get('assetData', {}).keys()).difference({'universe'}) exposures = result.get('assetData', {}).get(measure_name.pop(), []) if exposures: measures[result['date']] = exposures[0] return __format_plot_measure_results(measures, QueryType.FACTOR_EXPOSURE) @plot_measure((AssetClass.Equity,), (AssetType.Single_Stock,)) def factor_zscore( asset: Asset, risk_model_id: str, factor_name: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Asset factor exposure (in the form of z-scores) for a factor using specified risk model :param asset: asset object loaded from security master :param risk_model_id: requested risk model id :param factor_name: requested factor name :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: Time-series of asset factor exposure across available risk model dates """ model = MarqueeRiskModel.get(risk_model_id) factor = model.get_factor(factor_name) gsid = asset.get_identifier(AssetIdentifier.GSID) # Query risk model data query_results = model.get_data( measures=[ RiskModelDataMeasure.Factor_Name, RiskModelDataMeasure.Universe_Factor_Exposure, RiskModelDataMeasure.Asset_Universe, ], start_date=DataContext.current.start_time, end_date=DataContext.current.end_time, assets=RiskModelDataAssetsRequest(identifier=RiskModelUniverseIdentifierRequest.gsid, universe=[gsid]), factors=[factor], ).get('results', []) # Get the factor data from query results z_scores = {} for result in query_results: exposures = result.get('assetData', {}).get('factorExposure', []) if exposures: z_scores[result['date']] = exposures[0].get(factor.id) return __format_plot_measure_results(z_scores, QueryType.FACTOR_EXPOSURE) @plot_measure_entity(EntityType.RISK_MODEL, [QueryType.FACTOR_RETURN]) def factor_covariance( risk_model_id: str, factor_name_1: str, factor_name_2: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Covariance time-series between two factors in a risk model :param risk_model_id: risk model entity :param factor_name_1: first factor name :param factor_name_2: second factor name :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: Time-series of covariances between the two factors across available risk model dates """ model = FactorRiskModel.get(risk_model_id) factor_1 = model.get_factor(factor_name_1) factor_2 = model.get_factor(factor_name_2) covariance_curve = factor_1.covariance( factor_2, DataContext.current.start_date, DataContext.current.end_date, ReturnFormat.JSON ) return __format_plot_measure_results(covariance_curve, QueryType.COVARIANCE) @plot_measure_entity(EntityType.RISK_MODEL, [QueryType.FACTOR_RETURN]) def factor_volatility( risk_model_id: str, factor_name: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Volatility timeseries for a factor in a risk model :param risk_model_id: risk model entity :param factor_name: factor name :param source: name of function caller :param real_time: whether to retrieve intra-day data instead of EOD :param request_id: server request id :return: Time-series of a factor's volatility across available risk model dates """ model = FactorRiskModel.get(risk_model_id) factor = model.get_factor(factor_name) volatility = factor.volatility(DataContext.current.start_date, DataContext.current.end_date, ReturnFormat.JSON) return __format_plot_measure_results(volatility, QueryType.VOLATILITY, multiplier=100) @plot_measure_entity(EntityType.RISK_MODEL, [QueryType.FACTOR_RETURN]) def factor_correlation( risk_model_id: str, factor_name_1: str, factor_name_2: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Correlation time-series between two factors in a risk model :param risk_model_id: risk model entity :param factor_name_1: first factor name :param factor_name_2: second factor name :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: Time-series of correlations between the two factors across available risk model dates """ model = FactorRiskModel.get(risk_model_id) factor_1 = model.get_factor(factor_name_1) factor_2 = model.get_factor(factor_name_2) correlation = factor_1.correlation( factor_2, DataContext.current.start_date, DataContext.current.end_date, ReturnFormat.JSON ) return __format_plot_measure_results(correlation, QueryType.CORRELATION) @plot_measure_entity(EntityType.RISK_MODEL, [QueryType.FACTOR_RETURN]) def factor_performance( risk_model_id: str, factor_name: str, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Factor returns as a price time-series for a factor in a risk model :param risk_model_id: risk model entity :param factor_name: factor name :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: Time-series of factor returns as a price series across available risk model dates """ model = FactorRiskModel.get(risk_model_id) factor = model.get_factor(factor_name) factor_returns_df = factor.returns(DataContext.current.start_date, DataContext.current.end_date) factor_returns_series = factor_returns_df.squeeze() / 100 return prices(factor_returns_series, 100) @plot_measure_entity(EntityType.RISK_MODEL, []) def factor_returns_intraday( risk_model_id: str, factor_name: str, data_source: Union[IntradayFactorDataSource, str] = None, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Factor returns as a time-series for a factor in a risk model :param risk_model_id: risk model entity :param factor_name: factor name :param data_source: requested data source :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: server request id :return: Time-series of factor returns as a series across different timestamps """ model = FactorRiskModel.get(risk_model_id) factor = model.get_factor(factor_name) factor_returns_df = factor.intraday_returns( DataContext.current.start_time, DataContext.current.end_time, data_source ) return factor_returns_df.squeeze() @plot_measure_entity(EntityType.RISK_MODEL, []) def factor_returns_percentile( risk_model_id: str, factor_name: str, lookback_days: int = 10, n_percentile: float = 90.0, *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Percentile of factor returns over a lookback period for a factor in a risk model """ start_date = (DataContext.current.start_time - dt.timedelta(days=lookback_days)).date() end_date = DataContext.current.end_time.date() model = FactorRiskModel.get(risk_model_id) factor = model.get_factor(factor_name) factor_returns_df = factor.returns(start_date=start_date, end_date=end_date, format=ReturnFormat.DATA_FRAME) percentile_value = percentile(factor_returns_df.squeeze(), n_percentile) # Return the percentile value as pandas series for the entire time range from start_time to end_time return pd.Series( percentile_value, index=pd.date_range(start=DataContext.current.start_time, end=DataContext.current.end_time, freq='2h'), ) def __format_plot_measure_results(time_series: Dict, query_type: QueryType, multiplier=1, handle_missing_column=False): """Create and return panda series expected for a plot measure""" col_name = query_type.value.replace(' ', '') col_name = decapitalize(col_name) time_series_list = [{'date': k, col_name: v * multiplier} for k, v in time_series.items()] df = pd.DataFrame(time_series_list) if not df.empty: df = df.set_index('date') df.index = pd.to_datetime(df.index) return _extract_series_from_df(df, query_type, handle_missing_column) ================================================ FILE: gs_quant/timeseries/measures_xccy.py ================================================ """ Copyright 2020 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import logging from collections import OrderedDict from enum import Enum from typing import Optional, Union, Dict import pandas as pd from gs_quant.api.gs.assets import GsAssetApi from gs_quant.api.gs.data import QueryType, GsDataApi from gs_quant.errors import MqValueError from gs_quant.markets.securities import AssetIdentifier, Asset, SecurityMaster from gs_quant.common import Currency as CurrencyEnum, AssetClass, AssetType, PricingLocation from gs_quant.timeseries import ASSET_SPEC, plot_measure, MeasureDependency, GENERIC_DATE from gs_quant.timeseries.measures import _asset_from_spec, _market_data_timed, ExtendedSeries from gs_quant.timeseries import measures_rates as tm_rates _logger = logging.getLogger(__name__) class CrossCurrencyRateOptionType(Enum): LIBOR = 'LIBOR' OIS = 'OIS' EUROSTR = 'EUROSTR' SOFR = 'SOFR' SOFRVLIBOR = 'SOFRVLIBOR' TestRateOption = 'TestRateOption' class TdapiCrossCurrencyRatesDefaultsProvider: # flag to indicate that a given property should not be included in asset query EMPTY_PROPERTY = "null" def __init__(self, defaults: dict): self.defaults = defaults benchmark_mappings = {} maturity_mappings = {} for k, v in defaults.get("CURRENCIES").items(): benchmark_mappings[k] = {} maturity_mappings[k] = {} for e in v: benchmark_mappings[k][e.get("BenchmarkType")] = e.get('rateOption') maturity_mappings[k][e.get("BenchmarkType")] = e.get('designatedMaturity') self.defaults['MAPPING'] = benchmark_mappings self.defaults['MATURITIES'] = maturity_mappings def get_rateoption_for_benchmark(self, currency: CurrencyEnum, benchmark: str): return self.defaults.get("MAPPING").get(currency.value).get(benchmark) def get_maturity_for_benchmark(self, currency: CurrencyEnum, benchmark: str): return self.defaults.get("MATURITIES").get(currency.value).get(benchmark) CROSSCURRENCY_RATES_DEFAULTS = { "CURRENCIES": { "AUD": [ { "BenchmarkType": "LIBOR", "rateOption": "AUD-BBR-BBSW", "designatedMaturity": "3m", "pricingLocation": ["TKO"], }, { "BenchmarkType": "SOFR", "rateOption": "AUD-AONIA-OIS-COMPOUND", "designatedMaturity": "3m", "pricingLocation": ["TKO"], }, { "BenchmarkType": "SOFRVLIBOR", "rateOption": "AUD-BBR-BBSW", "designatedMaturity": "3m", "pricingLocation": ["TKO"], }, ], "CAD": [ { "BenchmarkType": "LIBOR", "rateOption": "CAD-BA-CDOR", "designatedMaturity": "3m", "pricingLocation": ["NYC"], }, { "BenchmarkType": "SOFR", "rateOption": "CAD-BA-CDOR", "designatedMaturity": "3m", "pricingLocation": ["NYC"], }, ], "CHF": [ { "BenchmarkType": "LIBOR", "rateOption": "CHF-LIBOR-BBA", "designatedMaturity": "3m", "pricingLocation": ["LDN"], }, { "BenchmarkType": "SOFR", "rateOption": "CHF-SARON-OIS-COMPOUND", "designatedMaturity": "3m", "pricingLocation": ["LDN"], }, { "BenchmarkType": "SOFRVLIBOR", "rateOption": "CHF-LIBOR-BBA", "designatedMaturity": "3m", "pricingLocation": ["LDN"], }, ], "DKK": [ { "BenchmarkType": "LIBOR", "rateOption": "DKK-CIBOR2-DKNA13", "designatedMaturity": "3m", "pricingLocation": ["LDN"], }, { "BenchmarkType": "SOFR", "rateOption": "DKK-CIBOR2-DKNA13", "designatedMaturity": "3m", "pricingLocation": ["LDN"], }, ], "EUR": [ { "BenchmarkType": "LIBOR", "rateOption": "EUR-EURIBOR-TELERATE", "designatedMaturity": "3m", "pricingLocation": ["LDN"], }, { "BenchmarkType": "OIS", "rateOption": "EUR-EONIA-OIS-COMPOUND", "designatedMaturity": "3m", "pricingLocation": ["LDN"], }, { "BenchmarkType": "SOFR", "rateOption": "EUR-EUROSTR-COMPOUND", "designatedMaturity": "3m", "pricingLocation": ["LDN"], }, ], "GBP": [ { "BenchmarkType": "LIBOR", "rateOption": "GBP-LIBOR-BBA", "designatedMaturity": "3m", "pricingLocation": ["LDN"], }, { "BenchmarkType": "OIS", "rateOption": "GBP-SONIA-COMPOUND", "designatedMaturity": "3m", "pricingLocation": ["LDN"], }, { "BenchmarkType": "SOFR", "rateOption": "GBP-SONIA-COMPOUND", "designatedMaturity": "3m", "pricingLocation": ["LDN"], }, ], "JPY": [ { "BenchmarkType": "LIBOR", "rateOption": "JPY-LIBOR-BBA", "designatedMaturity": "3m", "pricingLocation": ["TKO"], }, { "BenchmarkType": "OIS", "rateOption": "JPY-TONA-OIS-COMPOUND", "designatedMaturity": "3m", "pricingLocation": ["TKO"], }, { "BenchmarkType": "SOFR", "rateOption": "JPY-TONA-OIS-COMPOUND", "designatedMaturity": "3m", "pricingLocation": ["TKO"], }, ], "NOK": [ { "BenchmarkType": "LIBOR", "rateOption": "NOK-NIBOR-BBA", "designatedMaturity": "3m", "pricingLocation": ["LDN"], }, { "BenchmarkType": "SOFR", "rateOption": "NOK-NIBOR-BBA", "designatedMaturity": "3m", "pricingLocation": ["LDN"], }, ], "NZD": [ { "BenchmarkType": "LIBOR", "rateOption": "NZD-BBR-FRA", "designatedMaturity": "3m", "pricingLocation": ["TKO"], }, { "BenchmarkType": "SOFR", "rateOption": "NZD-NZIONA-OIS-COMPOUND", "designatedMaturity": "3m", "pricingLocation": ["TKO"], }, { "BenchmarkType": "SOFRVLIBOR", "rateOption": "NZD-BBR-FRA", "designatedMaturity": "3m", "pricingLocation": ["TKO"], }, ], "SEK": [ { "BenchmarkType": "LIBOR", "rateOption": "SEK-STIBOR-SIDE", "designatedMaturity": "3m", "pricingLocation": ["LDN"], }, { "BenchmarkType": "SOFR", "rateOption": "SEK-STIBOR-SIDE", "designatedMaturity": "3m", "pricingLocation": ["LDN"], }, ], "SGD": [ { "BenchmarkType": "SOFR", "rateOption": "SGD-SORA-COMPOUND", "designatedMaturity": "3m", "pricingLocation": ["TKO"], }, ], "USD": [ { "BenchmarkType": "LIBOR", "rateOption": "USD-LIBOR-BBA", "designatedMaturity": "3m", "pricingLocation": ["NYC"], }, { "BenchmarkType": "OIS", "rateOption": "USD-FEDERAL FUNDS-H.15-OIS-COMP", "designatedMaturity": "3m", "pricingLocation": ["NYC"], }, { "BenchmarkType": "SOFR", "rateOption": "USD-SOFR-COMPOUND", "designatedMaturity": "3m", "pricingLocation": ["NYC"], }, { "BenchmarkType": "SOFRVLIBOR", "rateOption": "USD-SOFR-COMPOUND", "designatedMaturity": "3m", "pricingLocation": ["NYC"], }, ], }, "COMMON": {"payerSpread": "ATM", "clearingHouse": "NONE", "terminationTenor": "5y", "effectiveDate": "0b"}, } crossCurrencyRates_defaults_provider = TdapiCrossCurrencyRatesDefaultsProvider(CROSSCURRENCY_RATES_DEFAULTS) CURRENCY_TO_XCCY_SWAP_RATE_BENCHMARK = { 'AUD': OrderedDict([('LIBOR', 'AUD-BBR-BBSW'), ('SOFR', 'AUD-AONIA-OIS-COMPOUND'), ('SOFRVLIBOR', 'AUD-BBR-BBSW')]), 'CAD': OrderedDict([('LIBOR', 'CAD-BA-CDOR'), ('SOFR', 'CAD-BA-CDOR')]), 'CHF': OrderedDict( [ ('LIBOR', 'CHF-LIBOR-BBA'), ('OIS', 'CHF-SARON-OIS-COMPOUND'), ('SOFR', 'CHF-SARON-OIS-COMPOUND'), ('SOFRVLIBOR', 'CHF-LIBOR-BBA'), ] ), 'DKK': OrderedDict([('LIBOR', 'DKK-CIBOR2-DKNA13'), ('SOFR', 'DKK-CIBOR2-DKNA13')]), 'EUR': OrderedDict( [('LIBOR', 'EUR-EURIBOR-TELERATE'), ('OIS', 'EUR-EONIA-OIS-COMPOUND'), ('SOFR', 'EUR-EUROSTR-COMPOUND')] ), 'GBP': OrderedDict([('LIBOR', 'GBP-LIBOR-BBA'), ('OIS', 'GBP-SONIA-COMPOUND'), ('SOFR', 'GBP-SONIA-COMPOUND')]), 'JPY': OrderedDict( [('LIBOR', 'JPY-LIBOR-BBA'), ('OIS', 'JPY-TONA-OIS-COMPOUND'), ('SOFR', 'JPY-TONA-OIS-COMPOUND')] ), 'NOK': OrderedDict([('LIBOR', 'NOK-NIBOR-BBA'), ('SOFR', 'NOK-NIBOR-BBA')]), 'NZD': OrderedDict([('LIBOR', 'NZD-BBR-FRA'), ('SOFR', 'NZD-NZIONA-OIS-COMPOUND'), ('SOFRVLIBOR', 'NZD-BBR-FRA')]), 'SEK': OrderedDict([('LIBOR', 'SEK-STIBOR-SIDE'), ('SOFR', 'SEK-STIBOR-SIDE')]), 'SGD': OrderedDict([('SOFR', 'SGD-SORA-COMPOUND')]), 'USD': OrderedDict( [ ('LIBOR', 'USD-LIBOR-BBA'), ('OIS', 'USD-FEDERAL FUNDS-H.15-OIS-COMP'), ('SOFR', 'USD-SOFR-COMPOUND'), ('SOFRVLIBOR', 'USD-SOFR-COMPOUND'), ] ), } CURRENCY_TO_DUMMY_CROSSCURRENCY_SWAP_BBID = { 'EUR': 'MAW8SAXPSKYA94E2', 'GBP': 'MATDD783JM1C2GGD', 'JPY': 'MAFMW4HJC5TDE51H', 'CHF': 'MA5YSMR2VN8A5T0N', 'NOK': 'MA44T4N363CWAQWN', 'DKK': 'MAF8ZEM0Q7R2D0R3', 'SEK': 'MADQDFZH81GX4NDP', 'AUD': 'MAHN46BVXFAZ354E', 'NZD': 'MAW1MNKPQWYJPX3A', 'CAD': 'MAPRDGWMZSQJ8CZ0', 'SGD': 'MA7B3JT80Q617KGQ', } def _currency_to_tdapi_crosscurrency_swap_rate_asset(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 = CURRENCY_TO_DUMMY_CROSSCURRENCY_SWAP_BBID.get(bbid, asset.get_marquee_id()) return result def _get_tdapi_crosscurrency_rates_assets(allow_many=False, **kwargs) -> Union[str, list]: # sanitize input for asset query. if "pricing_location" in kwargs: del kwargs["pricing_location"] assets = GsAssetApi.get_many_assets(**kwargs) # change order of basis swap legs and check if swap in dataset if len(assets) == 0 and ('asset_parameters_payer_rate_option' in kwargs): # flip legs kwargs['asset_parameters_payer_rate_option'], kwargs['asset_parameters_receiver_rate_option'] = ( kwargs['asset_parameters_receiver_rate_option'], kwargs['asset_parameters_payer_rate_option'], ) if 'asset_parameters_payer_designated_maturity' in kwargs: ( kwargs['asset_parameters_payer_designated_maturity'], kwargs['asset_parameters_receiver_designated_maturity'], ) = ( kwargs['asset_parameters_receiver_designated_maturity'], kwargs['asset_parameters_payer_designated_maturity'], ) if 'asset_parameters_payer_currency' in kwargs: kwargs['asset_parameters_payer_currency'], kwargs['asset_parameters_receiver_currency'] = ( kwargs['asset_parameters_receiver_currency'], kwargs['asset_parameters_payer_currency'], ) assets = GsAssetApi.get_many_assets(**kwargs) if len(assets) == 0 and ('asset_parameters_clearing_house' in kwargs): # test without the clearing house if kwargs['asset_parameters_clearing_house'] == tm_rates._ClearingHouse.NONE.value: del kwargs['asset_parameters_clearing_house'] assets = GsAssetApi.get_many_assets(**kwargs) # change order of basis swap legs and check if swap in dataset if len(assets) == 0 and ('asset_parameters_payer_rate_option' in kwargs): # flip legs kwargs['asset_parameters_payer_rate_option'], kwargs['asset_parameters_receiver_rate_option'] = ( kwargs['asset_parameters_receiver_rate_option'], kwargs['asset_parameters_payer_rate_option'], ) if 'asset_parameters_payer_designated_maturity' in kwargs: ( kwargs['asset_parameters_payer_designated_maturity'], kwargs['asset_parameters_receiver_designated_maturity'], ) = ( kwargs['asset_parameters_receiver_designated_maturity'], kwargs['asset_parameters_payer_designated_maturity'], ) if 'asset_parameters_payer_currency' in kwargs: kwargs['asset_parameters_payer_currency'], kwargs['asset_parameters_receiver_currency'] = ( kwargs['asset_parameters_receiver_currency'], kwargs['asset_parameters_payer_currency'], ) assets = GsAssetApi.get_many_assets(**kwargs) if len(assets) > 1: # term structure measures need multiple assets if ( ('asset_parameters_termination_date' not in kwargs) or ('asset_parameters_effective_date' not in kwargs) or allow_many ): return [asset.id for asset in assets] else: raise MqValueError('Specified arguments match multiple assets') elif len(assets) == 0: raise MqValueError('Specified arguments did not match any asset in the dataset' + str(kwargs)) else: return assets[0].id def _check_crosscurrency_rateoption_type( currency, benchmark_type: Union[CrossCurrencyRateOptionType, str] ) -> CrossCurrencyRateOptionType: if isinstance(benchmark_type, str): if benchmark_type.upper() in CrossCurrencyRateOptionType.__members__: benchmark_type = CrossCurrencyRateOptionType[benchmark_type.upper()] else: raise MqValueError( benchmark_type.upper() + ' is not valid, pick one among ' + ', '.join([x.value for x in CrossCurrencyRateOptionType]) ) if ( isinstance(benchmark_type, CrossCurrencyRateOptionType) and benchmark_type.value not in CURRENCY_TO_XCCY_SWAP_RATE_BENCHMARK[currency.value].keys() ): raise MqValueError('%s is not supported for %s', benchmark_type.value, currency.value) else: return benchmark_type def _get_crosscurrency_swap_leg_defaults( currency: CurrencyEnum, benchmark_type: CrossCurrencyRateOptionType = None ) -> Dict: pricing_location = tm_rates.CURRENCY_TO_PRICING_LOCATION.get(currency, PricingLocation.LDN) # default benchmark types if benchmark_type is None: benchmark_type = CrossCurrencyRateOptionType( str(list(CURRENCY_TO_XCCY_SWAP_RATE_BENCHMARK[currency.value].keys())[0]) ) benchmark_type_input = CURRENCY_TO_XCCY_SWAP_RATE_BENCHMARK[currency.value].get(benchmark_type.value, "") designated_maturity = crossCurrencyRates_defaults_provider.get_maturity_for_benchmark( currency, benchmark_type.value ) return dict( currency=currency, rateOption=benchmark_type_input, designatedMaturity=designated_maturity, pricing_location=pricing_location, ) def _get_crosscurrency_swap_csa_terms(curr: str, crosscurrencyindextype: str) -> dict: return dict(csaTerms=curr + '-1') def _get_crosscurrency_swap_data( asset1: Asset, asset2: Asset, swap_tenor: str, rateoption_type: str = None, forward_tenor: Optional[GENERIC_DATE] = None, clearing_house: tm_rates._ClearingHouse = None, source: str = None, real_time: bool = False, query_type: QueryType = QueryType.SWAP_RATE, location: PricingLocation = None, ) -> pd.DataFrame: if real_time: raise NotImplementedError('realtime swap_rate not implemented for anything but rates') currency1 = CurrencyEnum(asset1.get_identifier(AssetIdentifier.BLOOMBERG_ID)) currency2 = CurrencyEnum(asset2.get_identifier(AssetIdentifier.BLOOMBERG_ID)) if currency1.value not in CURRENCY_TO_XCCY_SWAP_RATE_BENCHMARK.keys(): raise NotImplementedError('Data not available for {} crosscurrency swap rates'.format(currency1.value)) if currency2.value not in CURRENCY_TO_XCCY_SWAP_RATE_BENCHMARK.keys(): raise NotImplementedError('Data not available for {} crosscurrency swap rates'.format(currency2.value)) rateoption_type1 = _check_crosscurrency_rateoption_type(currency1, rateoption_type) rateoption_type2 = _check_crosscurrency_rateoption_type(currency2, rateoption_type) if rateoption_type1 != rateoption_type2: raise MqValueError('The two currencies do not both support the rate Option type ' + rateoption_type) rateoption_type = rateoption_type1 clearing_house = tm_rates._check_clearing_house(clearing_house) defaults1 = _get_crosscurrency_swap_leg_defaults(currency1, rateoption_type) defaults2 = _get_crosscurrency_swap_leg_defaults(currency2, rateoption_type) if not (tm_rates._is_valid_relative_date_tenor(swap_tenor)): raise MqValueError('invalid swap tenor ' + swap_tenor) if defaults1["pricing_location"] == PricingLocation.NYC: default_location = defaults2["pricing_location"] currency = currency2 else: default_location = defaults1["pricing_location"] currency = currency1 if location is None: pricing_location = PricingLocation(default_location) else: pricing_location = PricingLocation(location) pricing_location = tm_rates._pricing_location_normalized(pricing_location, currency) where = dict(pricingLocation=pricing_location.value) forward_tenor = tm_rates._check_forward_tenor(forward_tenor) fixed_rate = 'ATM' kwargs = dict( asset_class='Rates', type='XccySwapMTM', asset_parameters_termination_date=swap_tenor, asset_parameters_effective_date=forward_tenor, asset_parameters_payer_spread=fixed_rate, # asset_parameters_payer_currency=defaults1['currency'].value, asset_parameters_payer_rate_option=defaults1['rateOption'], # asset_parameters_payer_designated_maturity=defaults1['designatedMaturity'], # asset_parameters_receiver_currency=defaults2['currency'].value, asset_parameters_receiver_rate_option=defaults2['rateOption'], # asset_parameters_receiver_designated_maturity=defaults2['designatedMaturity'], asset_parameters_clearing_house=clearing_house.value, pricing_location=pricing_location, ) rate_mqid = _get_tdapi_crosscurrency_rates_assets(**kwargs) _logger.debug( f'where asset= {rate_mqid}, swap_tenor={swap_tenor}, forward_tenor={forward_tenor}, ' f'payer_currency={defaults1["currency"].value}, payer_rate_option={defaults1["rateOption"]}, ' f'payer_designated_maturity={defaults1["designatedMaturity"]}, ' f'receiver_currency={defaults2["currency"].value}, receiver_rate_option={defaults2["rateOption"]}, ' f'receiver_designated_maturity={defaults2["designatedMaturity"]}, ' f'clearing_house={clearing_house.value}, pricing_location={pricing_location.value}' ) q = GsDataApi.build_market_data_query([rate_mqid], query_type, where=where, source=source, real_time=real_time) _logger.debug('q %s', q) df = _market_data_timed(q) return df @plot_measure( ( AssetClass.Cash, AssetClass.FX, ), ( AssetType.Currency, AssetType.Cross, ), [ MeasureDependency( id_provider=_currency_to_tdapi_crosscurrency_swap_rate_asset, query_type=QueryType.XCCY_SWAP_SPREAD ) ], ) def crosscurrency_swap_rate( asset: Asset, swap_tenor: str, rateoption_type: str = None, forward_tenor: Optional[GENERIC_DATE] = None, clearing_house: tm_rates._ClearingHouse = None, location: PricingLocation = None, *, source: str = None, real_time: bool = False, ) -> pd.Series: """ GS end-of-day Zero Coupon CrossCurrency Swap 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 rateoption_type: benchmark type e.g. LIBOR :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", "EUREX", "JSCC", "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 asset.get_type().value == AssetType.Cross.value: pair = asset.name [under, over] = [pair[i : i + 3] for i in range(0, 6, 3)] asset1 = SecurityMaster.get_asset(under, AssetIdentifier.BLOOMBERG_ID) asset2 = SecurityMaster.get_asset(over, AssetIdentifier.BLOOMBERG_ID) elif asset.get_type().value == AssetType.Currency.value: asset1 = asset asset2 = SecurityMaster.get_asset("USD", AssetIdentifier.BLOOMBERG_ID) else: raise MqValueError('Asset type not supported ' + asset.get_type().value) df = _get_crosscurrency_swap_data( asset1=asset1, asset2=asset2, swap_tenor=swap_tenor, rateoption_type=rateoption_type, forward_tenor=forward_tenor, clearing_house=clearing_house, source=source, real_time=real_time, query_type=QueryType.XCCY_SWAP_SPREAD, location=location, ) series = ExtendedSeries(dtype=float) if df.empty else ExtendedSeries(df['xccySwapSpread']) series.dataset_ids = getattr(df, 'dataset_ids', ()) return series ================================================ FILE: gs_quant/timeseries/statistics.py ================================================ # Copyright 2018 Goldman Sachs. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # # # Marquee Plot Service will attempt to make public functions (not prefixed with _) from this module available. # Such functions should be fully documented: docstrings should describe parameters and the return value, and provide # a 1-line description. Type annotations should be provided for parameters. import datetime as dt from enum import Enum from typing import List, Union, Optional import numpy as np import pandas as pd import scipy.stats.mstats as stats import statsmodels.api as sm from scipy.stats import percentileofscore from statsmodels.regression.rolling import RollingOLS from .algebra import ceil, floor from .datetime import interpolate from .helper import ( Window, normalize_window, rolling_offset, apply_ramp, plot_function, rolling_apply, Interpolate, plot_method, ) from ..data import DataContext from ..errors import MqValueError, MqTypeError from ..models.epidemiology import SIR, SEIR, EpidemicModel """ Stats library is for basic arithmetic and statistical operations on timeseries. These include basic algebraic operations, probability and distribution analysis. Generally not finance-specific routines. """ try: from quant_extensions.timeseries.statistics import rolling_std except ImportError: def rolling_std(x: pd.Series, offset: pd.DateOffset) -> pd.Series: size = len(x) index = x.index results = np.empty(size, dtype=np.double) results[0] = np.nan values = np.array(x.values, dtype=np.double) # put data into np arrays to save time on slicing later start = 0 for i in range(1, size): for j in range(start, i + 1): if pd.Timestamp(index[j]) > index[i] - offset: start = j break section = values[start : i + 1] results[i] = np.std(section[section == section], ddof=1) return pd.Series(results, index=index, dtype=np.double) def _concat_series(series: List[pd.Series]): curves = [] constants = {} k = 0 for s in series: if s.min() != s.max(): curves.append(s) else: constants[f'temp{k}'] = s.min() k += 1 return pd.concat(curves, axis=1).assign(**constants) @plot_function def min_(x: Union[pd.Series, List[pd.Series]], w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Minimum value of series over given window :param x: series: a timeseries or an array of timeseries :param w: window: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative time duration like '1m', '1d', etc. Window size defaults to length of series. :return: timeseries of minimum value **Usage** Returns the minimum value of the series over each window. If :math:`x` is a series: :math:`R_t = min(X_{t-w+1}:X_t)` where :math:`w` is the size of the rolling window. If :math:`x` is an array of series: :math:`R_t = min(X_{1, t-w+1}:X_{n, t})` where :math:`w` is the size of the rolling window, and :math:`n` is the number of series. If window is not provided, returns the minimum value over the full series. If the window size is greater than the available data, will return minimum of available values. If :math:`w` is a string, it should be a relative time duration such as '1m', '5d', etc. The available frequency strings can be found below: ============== ================ Frequency Description ============== ================ y one year m one month w one week d one day h one hour ============== ================ **Examples** Minimum value of price series over the last :math:`22` observations: >>> prices = generate_series(100) >>> min_(prices, 22) **See also** :func:`max_` """ if isinstance(x, list): x = _concat_series(x).min(axis=1) w = normalize_window(x, w) assert x.index.is_monotonic_increasing, "series index is monotonic increasing" if isinstance(w.w, pd.DateOffset): values = ( rolling_offset(x, w.w, np.nanmin, 'min') if isinstance(x, pd.Series) else [x.loc[(x.index > (idx - w.w).datetime()) & (x.index <= idx)].min() for idx in x.index] ) return apply_ramp(pd.Series(values, index=x.index, dtype=np.dtype(float)), w) else: return apply_ramp(x.rolling(w.w, 0).min(), w) @plot_function def max_(x: Union[pd.Series, List[pd.Series]], w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Maximum value of series over given window :param x: series: a timeseries or an array of timeseries :param w: window: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative time duration like '1m', '5d', etc. Window size defaults to length of series. :return: timeseries of maximum value **Usage** Returns the maximum value of the series over each window. If :math:`x` is a series: :math:`R_t = max(X_{t-w+1}:X_t)` where :math:`w` is the size of the rolling window. If :math:`x` is an array of series: :math:`R_t = max(X_{1, t-w+1}:X_{n, t})` where :math:`w` is the size of the rolling window, and :math:`n` is the number of series. If window is not provided, returns the maximum value over the full series. If the window size is greater than the available data, will return maximum of available values. If :math:`w` is a string, it should be a relative time duration such as '1m', '5d', etc. The available frequency strings can be found below: ============== ================ Frequency Description ============== ================ y one year m one month w one week d one day h one hour ============== ================ **Examples** Maximum value of price series over the last :math:`22` observations: >>> prices = generate_series(100) >>> max_(prices, 22) **See also** :func:`min_` """ if isinstance(x, list): x = _concat_series(x).max(axis=1) w = normalize_window(x, w) assert x.index.is_monotonic_increasing, "series index is monotonic increasing" if isinstance(w.w, pd.DateOffset): values = ( rolling_offset(x, w.w, np.nanmax, 'max') if isinstance(x, pd.Series) else [x.loc[(x.index > idx - w.w) & (x.index <= idx)].max() for idx in x.index] ) return apply_ramp(pd.Series(values, index=x.index, dtype=np.dtype(float)), w) else: return apply_ramp(x.rolling(w.w, 0).max(), w) @plot_function def range_(x: pd.Series, w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Range of series over given window :param x: series: timeseries :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: timeseries of range **Usage** Returns the range of the series (max - min) over rolling window: :math:`R_t = max(X_{t-w+1}:X_t) - min(X_{t-w+1}:X_t)` where :math:`w` is the size of the rolling window. If window is not provided, returns the range over the full series. If the window size is greater than the available data, will return range of all available values. **Examples** Range of price series over the last :math:`22` observations: >>> prices = generate_series(100) >>> range_(prices, 22) **See also** :func:`min_` :func:`max_` """ w = normalize_window(x, w) assert x.index.is_monotonic_increasing, "series index is monotonic increasing" max_v = max_(x, Window(w.w, 0)) min_v = min_(x, Window(w.w, 0)) return apply_ramp(max_v - min_v, w) class MeanType(Enum): ARITHMETIC = 'arithmetic' QUADRATIC = 'quadratic' @plot_function def mean( x: Union[pd.Series, List[pd.Series]], w: Union[Window, int, str] = Window(None, 0), mean_type: MeanType = MeanType.ARITHMETIC, ) -> pd.Series: """ Arithmetic mean of series over given window :param x: series: a timeseries or an array of timeseries :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: timeseries of mean value :param mean_type: type of mean to compute - arithmetic or quadratic, arithmetic by default **Usage** Calculates `arithmetic mean `_ of the series over a rolling window If a timeseries is provided: :math:`R_t = \\frac{\\sum_{i=t-w+1}^{t} X_i}{N}` where :math:`N` is the number of observations in each rolling window, :math:`w`. If an array of timeseries is provided: :math:`R_t = \\frac{\\sum_{i=t-w+1}^{t} {\\sum_{j=1}^{n}} X_{ij}}{N}` where :math:`n` is the number of series, and :math:`N` is the number of observations in each rolling window, :math:`w`. If window is not provided, computes rolling mean over the full series. If the window size is greater than the available data, will return mean of available values. **Examples** Generate price series and compute mean over :math:`22` observations >>> prices = generate_series(100) >>> mean(prices, 22) **See also** :func:`median` :func:`mode` """ if isinstance(x, list): x = pd.concat(x, axis=1) w = normalize_window(x, w) assert x.index.is_monotonic_increasing, "series index is monotonic increasing" if mean_type is MeanType.QUADRATIC: x = x**2 if isinstance(w.w, pd.DateOffset): if isinstance(x, pd.Series): values = rolling_offset(x, w.w, np.nanmean, 'mean') else: values = [np.nanmean(x.loc[(x.index > (idx - w.w).date()) & (x.index <= idx)]) for idx in x.index] else: if isinstance(x, pd.Series): values = x.rolling(w.w, 0).mean() # faster than slicing in Python else: values = [np.nanmean(x.iloc[max(idx - w.w + 1, 0) : idx + 1]) for idx in range(0, len(x))] result = pd.Series(values, index=x.index, dtype=np.dtype(float)) if mean_type is MeanType.QUADRATIC: result = np.sqrt(result) return apply_ramp(result, w) @plot_function def median(x: pd.Series, w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Median value of series over given window :param x: series: timeseries :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: timeseries of median value **Usage** Computes the `median `_ value over a given window. For each window, this function will return the middle value when all elements in the window are sorted. If the number of observations in the window is even, will return the average of the middle two values. If the window size is greater than the available data, will return median of available values: :math:`d = \\frac{w-1}{2}` :math:`R_t = \\frac{X_{\\lfloor t-d \\rfloor} + X_{\\lceil t-d \\rceil}}{2}` where :math:`w` is the size of the rolling window. If window is not provided, computes median over the full series **Examples** Generate price series and compute median over :math:`22` observations >>> prices = generate_series(100) >>> median(prices, 22) **See also** :func:`mean` :func:`mode` """ w = normalize_window(x, w) assert x.index.is_monotonic_increasing, "series index is monotonic increasing" if isinstance(w.w, pd.DateOffset): values = ( rolling_offset(x, w.w, np.nanmedian, 'median') if isinstance(x, pd.Series) else [x.loc[(x.index > (idx - w.w).date()) & (x.index <= idx)].median() for idx in x.index] ) return apply_ramp(pd.Series(values, index=x.index, dtype=np.dtype(float)), w) else: return apply_ramp(x.rolling(w.w, 0).median(), w) @plot_function def mode(x: pd.Series, w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Most common value in series over given window :param x: series: timeseries :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: timeseries of mode value **Usage** Computes the `mode `_ over a given window. For each window, this function will return the most common value of all elements in the window. If there are multiple values with the same frequency of occurrence, will return the smallest value. If window is not provided, computes mode over the full series. **Examples** Generate price series and compute mode over :math:`22` observations >>> prices = generate_series(100) >>> mode(prices, 22) **See also** :func:`mean` :func:`median` """ w = normalize_window(x, w) assert x.index.is_monotonic_increasing, "series index is monotonic increasing" if isinstance(w.w, pd.DateOffset): values = ( rolling_apply(x, w.w, lambda a: stats.mode(a).mode[0]) if isinstance(x, pd.Series) else [stats.mode(x.loc[(x.index > (idx - w.w).date()) & (x.index <= idx)]).mode[0] for idx in x.index] ) return apply_ramp(pd.Series(values, index=x.index, dtype=np.dtype(float)), w) else: return apply_ramp(x.rolling(w.w, 0).apply(lambda y: stats.mode(y).mode, raw=True), w) @plot_function def sum_(x: Union[pd.Series, List[pd.Series]], w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Rolling sum of series over given window :param x: series: a timeseries or an array of timeseries :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: timeseries of rolling sum **Usage** Calculate the sum of observations over a given rolling window. If :math:`x` is a series: :math:`R_t = \\sum_{i=t-w+1}^{t} X_i` where :math:`w` is the size of the rolling window. If :math:`x` is an array of series: :math:`R_t = \\sum_{i=t-w+1}^{t} \\sum_{j=1}^{n} X_{ij}` where :math:`w` is the size of the rolling window and :math:`n` is the number of series If window is not provided, computes sum over the full series. If the window size is greater than the available data, will return sum of available values. **Examples** Generate price series and compute rolling sum over :math:`22` observations >>> prices = generate_series(100) >>> sum_(prices, 22) **See also** :func:`product` """ if isinstance(x, list): x = pd.concat(x, axis=1).sum(axis=1) w = normalize_window(x, w) assert x.index.is_monotonic_increasing if isinstance(w.w, pd.DateOffset): assert isinstance(x, pd.Series), 'expected a series' values = rolling_offset(x, w.w, np.nansum, 'sum') return apply_ramp(values, w) else: return apply_ramp(x.rolling(w.w, 0).sum(), w) @plot_function def product(x: pd.Series, w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Rolling product of series over given window :param x: series: timeseries :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: timeseries of rolling product **Usage** Calculate the product of observations over a given rolling window. For each time, :math:`t`, returns the value of all observations from :math:`t-w+1` to :math:`t` multiplied together: :math:`R_t = \\prod_{i=t-w+1}^{t} X_i` where :math:`w` is the size of the rolling window. If window is not provided, computes product over the full series **Examples** Generate price series and compute rolling sum over :math:`22` observations >>> prices = generate_series(100) >>> product(1+returns(prices)) **See also** :func:`sum_` """ w = normalize_window(x, w) assert x.index.is_monotonic_increasing if isinstance(w.w, pd.DateOffset): values = ( rolling_offset(x, w.w, np.nanprod, 'prod') if isinstance(x, pd.Series) else [x.loc[(x.index > (idx - w.w).date()) & (x.index <= idx)].prod() for idx in x.index] ) return apply_ramp(pd.Series(values, index=x.index, dtype=np.dtype(float)), w) else: return apply_ramp(x.rolling(w.w, 0).agg(pd.Series.prod), w) @plot_function def std(x: pd.Series, w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Rolling standard deviation of series over given window :param x: series: timeseries :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: timeseries of standard deviation **Usage** Provides `unbiased estimator `_ of sample `standard deviation `_ over a rolling window: :math:`R_t = \\sqrt{\\frac{1}{N-1} \\sum_{i=t-w+1}^t (X_i - \\overline{X_t})^2}` where :math:`N` is the number of observations in each rolling window, :math:`w`, and :math:`\\overline{X_t}` is the mean value over the same window: :math:`\\overline{X_t} = \\frac{\\sum_{i=t-w+1}^{t} X_i}{N}` If window is not provided, computes standard deviation over the full series **Examples** Generate price series and compute standard deviation of returns over :math:`22` observations >>> prices = generate_series(100) >>> std(returns(prices), 22) **See also** :func:`sum` :func:`mean` :func:`var` """ if x.empty: return x w = normalize_window(x, w) assert x.index.is_monotonic_increasing, "series index is monotonic increasing" if isinstance(w.w, pd.DateOffset): return apply_ramp(rolling_std(x, w.w), w) else: return apply_ramp(x.rolling(w.w, 0).std(), w) @plot_function def exponential_std(x: pd.Series, beta: float = 0.75) -> pd.Series: """ Exponentially weighted standard deviation :param x: time series :param beta: how much to weigh the previous price in the time series, thus controlling how much importance we place on the (more distant) past. Must be between 0 (inclusive) and 1 (exclusive) :return: time series of standard deviation of the input series **Usage** Provides an unbiased estimator of `exponentially weighted standard deviation `_ of a series [:math:`X_0`, :math:`X_1`, :math:`X_2`, ...]: :math:`S_t = \\sqrt{[EWMA(X_t^2) - EWMA(X_t)^2] * DF_t}` where :math:`EWMA(X_t)` is the `exponential moving average `_ at :math:`t` (see function :func:`exponential_moving_average`), :math:`DF_t` is the debiasing factor (see `Weighted sample variance `_ for further details): :math:`DF_t = \\frac{(\\sum_{i=0}^t w_i)^2} {(\\sum_{i=0}^t w_i)^2 - \\sum_{i=0}^t w_i^2}` where :math:`w_i` is the weight assigned to :math:`i` th observation: :math:`w_i = (1-\\beta)\\beta^i` for i>> prices = generate_series(100) >>> exponential_std(returns(prices), 0.9) **See also** :func:`std` :func:`var` :func:`exponential_moving_average` """ return x.ewm(alpha=1 - beta, adjust=False).std() @plot_function def var(x: pd.Series, w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Rolling variance of series over given window :param x: series: timeseries :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: timeseries of variance **Usage** Provides `unbiased estimator `_ of sample `variance `_ over a rolling window: :math:`R_t = \\frac{1}{N-1} \\sum_{i=t-w+1}^t (X_i - \\overline{X_t})^2` where :math:`N` is the number of observations in each rolling window, :math:`w`, and :math:`\\overline{X_t}` is the mean value over the same window: :math:`\\overline{X_t} = \\frac{\\sum_{i=t-w+1}^{t} X_i}{N}` If window is not provided, computes variance over the full series **Examples** Generate price series and compute variance of returns over :math:`22` observations >>> prices = generate_series(100) >>> var(returns(prices), 22) **See also** :func:`var` :func:`mean` :func:`std` """ w = normalize_window(x, w) assert x.index.is_monotonic_increasing, "series index is monotonic increasing" if isinstance(w.w, pd.DateOffset): values = ( rolling_offset(x, w.w, lambda a: np.nanvar(a, ddof=1), 'var') if isinstance(x, pd.Series) else [x.loc[(x.index > (idx - w.w).date()) & (x.index <= idx)].var() for idx in x.index] ) return apply_ramp(pd.Series(values, index=x.index, dtype=np.dtype(float)), w) else: return apply_ramp(x.rolling(w.w, 0).var(), w) @plot_function def cov(x: pd.Series, y: pd.Series, w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Rolling co-variance of series over given window :param x: series: timeseries :param y: series: timeseries :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: timeseries of covariance **Usage** Provides `unbiased estimator `_ of sample `co-variance `_ over a rolling window: :math:`R_t = \\frac{1}{N-1} \\sum_{i=t-w+1}^t (X_i - \\overline{X_t}) (Y_i - \\overline{Y_t})` where :math:`N` is the number of observations in each rolling window, :math:`w`, and :math:`\\overline{X_t}` and :math:`\\overline{Y_t}` represent the sample mean of series :math:`X_t` and :math:`Y_t` over the same window: :math:`\\overline{X_t} = \\frac{\\sum_{i=t-w+1}^{t} X_i}{N}` and :math:`\\overline{Y_t} = \\frac{\\sum_{i=t-w+1}^{t} Y_i}{N}` If window is not provided, computes variance over the full series **Examples** Generate price series and compute variance of returns over :math:`22` observations >>> prices_x = generate_series(100) >>> prices_y = generate_series(100) >>> cov(returns(prices_x) returns(prices_y), 22) **See also** :func:`sum` :func:`mean` :func:`var` """ w = normalize_window(x, w) assert x.index.is_monotonic_increasing, "series index is monotonic increasing" if isinstance(w.w, pd.DateOffset): values = [x.loc[(x.index > (idx - w.w).date()) & (x.index <= idx)].cov(y) for idx in x.index] return apply_ramp(pd.Series(values, index=x.index, dtype=np.dtype(float)), w) else: return apply_ramp(x.rolling(w.w, 0).cov(y), w) def _zscore(x): if x.size == 1: return 0 return stats.zscore(x, ddof=1)[-1] @plot_function def zscores(x: pd.Series, w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Rolling z-scores over a given window :param x: time series of prices :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: timeseries of z-scores **Usage** Calculate `standard score `_ of each value in series over given window. Standard deviation and sample mean are computed over the specified rolling window, then element is normalized to provide a rolling z-score: :math:`R_t = \\frac { X_t - \\mu }{ \\sigma }` Where :math:`\\mu` and :math:`\\sigma` are sample mean and standard deviation over the given window If window is not provided, computes z-score relative to mean and standard deviation over the full series **Examples** Generate price series and compute z-score of returns over :math:`22` observations >>> prices = generate_series(100) >>> zscores(returns(prices), 22) **See also** :func:`mean` :func:`std` """ if x.size < 1: return x if isinstance(w, int): w = normalize_window(x, w) elif isinstance(w, str): if not (isinstance(x.index, pd.DatetimeIndex) or isinstance(x.index[0], dt.date)): raise MqValueError("When string is passed window index must be a DatetimeIndex or of type datetime.date") w = normalize_window(x, w) if not w.w: if x.size == 1: return pd.Series([0.0], index=x.index, dtype=np.dtype(float)) clean_series = x.dropna() zscore_series = pd.Series(stats.zscore(clean_series, ddof=1), clean_series.index, dtype=np.dtype(float)) return interpolate(zscore_series, x, Interpolate.NAN) if not isinstance(w.w, int): w = normalize_window(x, w) dt_idx = pd.DatetimeIndex(x.index).date values = [_zscore(x.loc[(dt_idx > (idx - w.w).date()) & (dt_idx <= idx)]) for idx in dt_idx] return apply_ramp(pd.Series(values, index=x.index, dtype=np.dtype(float)), w) else: return apply_ramp(x.rolling(w.w, 0).apply(_zscore, raw=False), w) @plot_function def winsorize(x: pd.Series, limit: float = 2.5, w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Limit extreme values in series :param x: time series of prices :param limit: max z-score of values :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: timeseries of winsorized values **Usage** Cap and floor values in the series which have a z-score greater or less than provided value. This function will restrict the distribution of values. Calculates the sample standard deviation and adjusts values which fall outside the specified range to be equal to the upper or lower limits Lower and upper limits are defined as: :math:`upper = \\mu + \\sigma \\times limit` :math:`lower = \\mu - \\sigma \\times limit` Where :math:`\\mu` and :math:`\\sigma` are sample mean and standard deviation. The series is restricted by: :math:`R_t = max( min( X_t, upper), lower )` See `winsorising `_ for additional information **Examples** Generate price series and winsorize z-score of returns over :math:`22` observations >>> prices = generate_series(100) >>> winsorize(zscore(returns(prices), 22)) **See also** :func:`zscore` :func:`mean` :func:`std` """ w = normalize_window(x, w) if x.size < 1: return x assert w.w, "window is not 0" mu = x.mean() sigma = x.std() high = mu + sigma * limit low = mu - sigma * limit ret = ceil(x, high) ret = floor(ret, low) return apply_ramp(ret, w) class Direction(Enum): START_TODAY = 'start_today' END_TODAY = 'end_today' @plot_function def generate_series(length: int, direction: Direction = Direction.START_TODAY) -> pd.Series: """ Generate sample timeseries :param length: number of observations :param direction: whether generated series should start from today or end on today :return: date-based time series of randomly generated prices **Usage** Create timeseries from returns generated from a normally distributed random variables (IDD). Length determines the number of observations to be generated. Assume random variables :math:`R` which follow a normal distribution with mean :math:`0` and standard deviation of :math:`1` :math:`R \\sim N(0, 1)` The timeseries is generated from these random numbers through: :math:`X_t = (1 + R)X_{t-1}` **Examples** Generate price series with 100 observations starting from today's date: >>> prices = generate_series(100) **See also** :func:`numpy.random.normal()` """ levels = [100] first = dt.date.today() if direction == Direction.END_TODAY: first -= dt.timedelta(days=length - 1) dates = [first] rng = np.random.default_rng() for i in range(length - 1): levels.append(levels[i] * 1 + rng.standard_normal()) dates.append(dt.date.fromordinal(dates[i].toordinal() + 1)) return pd.Series(data=levels, index=dates, dtype=np.dtype(float)) class IntradayDirection(Enum): START_INTRADAY_NOW = 'start_intraday_now' END_INTRADAY_NOW = 'end_intraday_now' @plot_function def generate_series_intraday( length: int, direction: IntradayDirection = IntradayDirection.START_INTRADAY_NOW ) -> pd.Series: """ Generate sample intraday timeseries with minute-level granularity :param length: number of observations (minutes) :param direction: whether generated series should start from or end at current timestamp :return: datetime-based time series of randomly generated prices at 1-minute intervals **Usage** Create intraday timeseries from returns generated from normally distributed random variables (IID). Length determines the number of minute-level observations to be generated. The series can either start from the current timestamp (START_INTRADAY_NOW) or end at the current timestamp (END_INTRADAY_NOW). Assume random variables :math:`R` which follow a normal distribution with mean :math:`0` and standard deviation of :math:`0.001` (scaled for intraday volatility): :math:`R \\sim N(0, 0.001)` The timeseries is generated from these random numbers through: :math:`X_t = (1 + R)X_{t-1}` where each observation is spaced 1 minute apart. **Examples** Generate intraday price series with 120 observations (2 hours) starting from current time: >>> prices = generate_series_intraday(120) Generate intraday price series for a full day (24 hours) ending at current time: >>> prices = generate_series_intraday(1440, IntradayDirection.END_INTRADAY_NOW) **See also** :func:`generate_series` :func:`numpy.random.normal()` """ levels = [100] first = pd.Timestamp.now().floor('min') if direction == IntradayDirection.END_INTRADAY_NOW: first -= pd.Timedelta(minutes=length - 1) times = [first] rng = np.random.default_rng() for i in range(length - 1): levels.append(levels[i] * 1 + rng.standard_normal()) times.append(times[i] + pd.Timedelta(minutes=1)) return pd.Series(data=levels, index=times, dtype=np.dtype(float)) @plot_function def percentiles(x: pd.Series, y: Optional[pd.Series] = None, w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Rolling percentiles over given window :param x: value series :param y: distribution series :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: timeseries of percentiles **Usage** Calculate `percentile rank `_ of :math:`y` in the sample distribution of :math:`x` over a rolling window of length :math:`w`: :math:`R_t = \\frac{\\sum_{i=t-N+1}^{t}{[X_i<{Y_t}]}+0.5\\sum_{i=t-N+1}^{t}{[X_i={Y_t}]}}{N}\\times100\\%` Where :math:`N` is the number of observations in a rolling window. If :math:`y` is not provided (or is NULL), calculates percentiles of :math:`x` over its historical values. If window length :math:`w` is not provided, uses an ever-growing history of values. If :math:`w` is greater than the available data size, returns empty. **Examples** Compute percentile ranks of a series in the sample distribution of a second series over :math:`22` observations >>> a = generate_series(100) >>> b = generate_series(100) >>> percentiles(a, b, 22) **See also** :func:`zscores` """ if x.empty: return x if y is None: y = x.copy() w = normalize_window(y, w) if isinstance(w.r, int) and w.r > len(y): raise ValueError('Ramp value must be less than the length of the series y.') if isinstance(w.w, int) and w.w > len(x): return pd.Series(dtype=float) res = pd.Series(dtype=np.dtype(float)) convert_to_date = not isinstance(x.index, pd.DatetimeIndex) if isinstance(w.w, pd.DateOffset): for idx, val in y.items(): sample = x.loc[(x.index > ((idx - w.w).date() if convert_to_date else idx - w.w)) & (x.index <= idx)] res.loc[idx] = percentileofscore(sample, val, kind='mean') elif not y.empty: min_periods = 0 if isinstance(w.r, pd.DateOffset) else w.r rolling_window = x[: y.index[-1]].rolling(w.w, min_periods) percentile_on_x_index = rolling_window.apply( lambda a: percentileofscore(a, y[a.index[-1] :].iloc[0], kind="mean") ) joined_index = pd.concat([x, y], axis=1).index res = percentile_on_x_index.reindex(joined_index, method="ffill")[y.index] return apply_ramp(res, w) @plot_function def percentile(x: pd.Series, n: float, w: Union[Window, int, str] = None) -> Union[pd.Series, float]: """ Returns the nth percentile of a series. :param x: series :param n: percentile :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. :return: nth percentile **Usage** Calculates the `nth percentile rank `_ of :math:`x`. Rolling nth percentile is returned if a window is specified, else a scalar for nth percentile over the entire series. **Example** Compute the 90th percentile of a series. >>> a = generate_series(100) >>> percentile(a, 90) """ if not 0 <= n <= 100: raise MqValueError('percentile must be in range [0, 100]') x = x.dropna() if x.size < 1: return x if w is None: return np.percentile(x.values, n) n /= 100 w = normalize_window(x, w) if isinstance(w.w, pd.DateOffset): try: if isinstance(x.index, pd.DatetimeIndex): values = [x.loc[(x.index > (idx - w.w)) & (x.index <= idx)].quantile(n) for idx in x.index] else: values = [x.loc[(x.index > (idx - w.w).date()) & (x.index <= idx)].quantile(n) for idx in x.index] except TypeError: raise MqTypeError(f'cannot use relative dates with index {x.index}') res = pd.Series(values, index=x.index, dtype=np.dtype(float)) else: res = x.rolling(w.w, 0).quantile(n) return apply_ramp(res, w) class LinearRegression: """ Fit an Ordinary least squares (OLS) linear regression model. :param X: observations of the explanatory variable(s) :param y: observations of the dependent variable :param fit_intercept: whether to calculate intercept in the model **Usage** Fit `OLS Model `_ based on observations of the explanatory variables(s) X and the dependent variable y. If X and y are not aligned, only use the intersection of dates/times. **Examples** Run a linear regression on y vs. x1 and x2 and compute the R squared: >>> x1 = generate_series(100) >>> x2 = generate_series(100) >>> y = generate_series(100) >>> r = LinearRegression([x1, x2], y, True) >>> r.r_squared() """ def __init__(self, X: Union[pd.Series, List[pd.Series]], y: pd.Series, fit_intercept: bool = True): if not isinstance(fit_intercept, bool): raise MqTypeError('expected a boolean value for "fit_intercept"') df = pd.concat(X, axis=1) if isinstance(X, list) else X.to_frame() df = sm.add_constant(df) if fit_intercept else df df.columns = range(len(df.columns)) if fit_intercept else range(1, len(df.columns) + 1) df = df[~df.isin([np.nan, np.inf, -np.inf]).any(axis=1)] # filter out nan and inf y = y[~y.isin([np.nan, np.inf, -np.inf])] df_aligned, y_aligned = df.align(y, 'inner', axis=0) # align series self._index_scope = range(0, len(df.columns)) if fit_intercept else range(1, len(df.columns) + 1) self._res = sm.OLS(y_aligned, df_aligned).fit() self._fit_intercept = fit_intercept @plot_method def coefficient(self, i: int) -> float: """ Estimated coefficient. :param i: 0 for intercept (available if intercept is used), 1 for regression slope :return: estimated coefficient of the i-th predictor """ return self._res.params[i] @plot_method def r_squared(self) -> float: """ Coefficient of determination (R Squared) :return: R Squared """ return self._res.rsquared @plot_method def fitted_values(self) -> pd.Series: """ Fitted values computed by evaluating the regression model on the original input X. :return: fitted values """ return self._res.fittedvalues @plot_method def predict(self, X_predict: Union[pd.Series, List[pd.Series]]) -> pd.Series: """ Use the model for prediction. :param X_predict: the values for which to predict :return: predicted values """ df = pd.concat(X_predict, axis=1) if isinstance(X_predict, list) else X_predict.to_frame() return self._res.predict(sm.add_constant(df) if self._fit_intercept else df) @plot_method def standard_deviation_of_errors(self) -> float: """ Standard deviation of the error term. :return: standard deviation of the error term """ return np.sqrt(self._res.mse_resid) class RollingLinearRegression: """ Fit a rolling ordinary least squares (OLS) linear regression model. :param X: observations of the explanatory variable(s) :param y: observations of the dependant variable :param w: number of observations in each rolling window. Must be larger than the number of observations or explanatory variables :param fit_intercept: whether to calculate intercept in the model **Usage** Fit `OLS Model `_ based on observations of the explanatory variables(s) X and the dependant variable y across a rolling window with fixed number of observations. The parameters of each rolling window are stored at the end of each window. If X and y are not aligned, only use the intersection of dates/times. **Examples** Run linear regressions on y vs. x1 and x2 in a rolling window of 22 observations and compute the R Squared: >>> x1 = generate_series(100) >>> x2 = generate_series(100) >>> y = generate_series(100) >>> r = RollingLinearRegression([x1, x2], y, 22) >>> r.r_squared() """ def __init__(self, X: Union[pd.Series, List[pd.Series]], y: pd.Series, w: int, fit_intercept: bool = True): if not isinstance(fit_intercept, bool): raise MqTypeError('expected a boolean value for "fit_intercept"') df = pd.concat(X, axis=1) if isinstance(X, list) else X.to_frame() df = sm.add_constant(df) if fit_intercept else df df.columns = range(len(df.columns)) if fit_intercept else range(1, len(df.columns) + 1) if w <= len(df.columns): raise MqValueError('Window length must be larger than the number of explanatory variables') df = df[~df.isin([np.nan, np.inf, -np.inf]).any(axis=1)] # filter out nan and inf y = y[~y.isin([np.nan, np.inf, -np.inf])] df_aligned, y_aligned = df.align(y, 'inner', axis=0) # align series self._X = df_aligned.copy() self._res = RollingOLS(y_aligned, df_aligned, w).fit() @plot_method def coefficient(self, i: int) -> pd.Series: """ Estimated coefficients. :param i: 0 for intercept (available if intercept is used), 1 for regression slope. :return: estimated coefficients of the i-th predictor """ return self._res.params[i] @plot_method def r_squared(self) -> pd.Series: """ Coefficients of determination (R Squared) of rolling regressions. :return: R Squared """ return self._res.rsquared @plot_method def fitted_values(self) -> pd.Series: """ Fitted values at the end of each rolling window. :return: fitted values """ comp = self._X.mul(self._res.params.values) return comp.sum(axis=1, min_count=len(comp.columns)) @plot_method def standard_deviation_of_errors(self) -> pd.Series: """ Standard deviations of the error terms. :return: standard deviations of the error terms """ return np.sqrt(self._res.mse_resid) class SIRModel: """SIR Compartmental model for transmission of infectious disease :param beta: transmission rate of the infection :param gamma: recovery rate of the infection :param s: number of susceptible individuals in population :param i: number of infectious individuals in population :param r: number of recovered individuals in population :param n: total population size :param end_date: end date for the evolution of the model :param fit: whether to fit the model to the data :param fit_period: on how many days back to fit the model **Usage** Fit `SIR Model `_ based on the population in each compartment over a given time period. The SIR models the movement of individuals between three compartments: susceptible (S), infected (I), and resistant (R). The model calibrates parameters : =========== ======================================================= Parameter Description =========== ======================================================= S0 initial susceptible individuals I0 initial infected individuals R0 initial recovered individuals beta Transmission rate from susceptible to infected gamma Immunity rate from infected to resistant =========== ======================================================= The parameters beta and gamma model how fast people move from being susceptible to infected (beta), and subsequently from infected to resistant (gamma). This model can be used to forecast the populations of each compartment once calibrated """ def __init__( self, beta: float = None, gamma: float = None, s: Union[pd.Series, float] = None, i: Union[pd.Series, float] = None, r: Union[pd.Series, float] = None, n: Union[pd.Series, float] = None, fit: bool = True, fit_period: int = None, ): if not isinstance(fit, bool): raise MqTypeError('expected a boolean value for "fit"') n = n.dropna().iloc[0] if isinstance(n, pd.Series) else n n = 100 if n is None else n fit = False if s is None and i is None and r is None else fit s = n if s is None else s i = 1 if i is None else i r = 0 if r is None else r data_start = [ts.index.min().date() for ts in [s, i, r] if isinstance(ts, pd.Series)] data_start.append(DataContext.current.start_date) start_date = max(data_start) data_end = [ts.index.max().date() for ts in [s, i, r] if isinstance(ts, pd.Series)] data_end.append(DataContext.current.end_date) end_date = max(data_end) self.s = s if isinstance(s, pd.Series) else pd.Series([s]) self.i = i if isinstance(i, pd.Series) else pd.Series([i]) self.r = r if isinstance(r, pd.Series) else pd.Series([r]) self.n = n self.beta_init = beta self.gamma_init = gamma self.fit = fit self.fit_period = fit_period self.beta_fixed = not (self.fit or (self.beta_init is None)) self.gamma_fixed = not (self.fit or (self.gamma_init is None)) lens = [len(x) for x in (self.s, self.i, self.r)] dtype = float if max(lens) == min(lens) else object data = np.array([self.s, self.i, self.r], dtype=dtype).T beta_init = self.beta_init if self.beta_init is not None else 0.9 gamma_init = self.gamma_init if self.gamma_init is not None else 0.01 parameters, initial_conditions = SIR.get_parameters( self.s.iloc[0], self.i.iloc[0], self.r.iloc[0], n, beta=beta_init, gamma=gamma_init, beta_fixed=self.beta_fixed, gamma_fixed=self.gamma_fixed, S0_fixed=True, I0_fixed=True, R0_fixed=True, ) self.parameters = parameters self._model = EpidemicModel( SIR, parameters=parameters, data=data, initial_conditions=initial_conditions, fit_period=self.fit_period ) if self.fit: self._model.fit(verbose=False) t = np.arange((end_date - start_date).days + 1) predict = self._model.solve(t, (self.s0(), self.i0(), self.r0()), (self.beta(), self.gamma(), n)) predict_dates = pd.date_range(start_date, end_date) self._model.s_predict = pd.Series(predict[:, 0], predict_dates) self._model.i_predict = pd.Series(predict[:, 1], predict_dates) self._model.r_predict = pd.Series(predict[:, 2], predict_dates) @plot_method def s0(self) -> float: """ Model calibration for initial susceptible individuals :return: initial susceptible individuals """ if self.fit: return self._model.fitted_parameters['S0'] return self.parameters['S0'].value @plot_method def i0(self) -> float: """ Model calibration for initial infectious individuals :return: initial infectious individuals """ if self.fit: return self._model.fitted_parameters['I0'] return self.parameters['I0'].value @plot_method def r0(self) -> float: """ Model calibration for initial recovered individuals :return: initial recovered individuals """ if self.fit: return self._model.fitted_parameters['R0'] return self.parameters['R0'].value @plot_method def beta(self) -> float: """ Model calibration for transmission rate (susceptible to infected) :return: beta """ if self.fit: return self._model.fitted_parameters['beta'] return self.parameters['beta'].value @plot_method def gamma(self) -> float: """ Model calibration for immunity (infected to resistant) :return: beta """ if self.fit: return self._model.fitted_parameters['gamma'] return self.parameters['gamma'].value @plot_method def predict_s(self) -> pd.Series: """ Model calibration for susceptible individuals through time :return: susceptible predict """ return self._model.s_predict @plot_method def predict_i(self) -> pd.Series: """ Model calibration for infected individuals through time :return: infected predict """ return self._model.i_predict @plot_method def predict_r(self) -> pd.Series: """ Model calibration for recovered individuals through time :return: infected predict """ return self._model.r_predict class SEIRModel(SIRModel): """SEIR Compartmental model for transmission of infectious disease :param beta: transmission rate of the infection :param gamma: recovery rate of the infection :param sigma: immunity rate from exposed to infected :param s: number of susceptible individuals in population :param e: number of exposed individuals in population :param i: number of infectious individuals in population :param r: number of recovered individuals in population :param n: total population size :param end_date: end date for the evolution of the model :param fit: whether to fit the model to the data :param fit_period: on how many days back to fit the model **Usage** Fit `SEIR Model `_ based on the population in each compartment over a given time period. The SEIR models the movement of individuals between four compartments: susceptible (S), exposed (E), infected (I), and resistant (R). The model calibrates parameters : =========== ======================================================= Parameter Description =========== ======================================================= S0 initial susceptible individuals E0 initial exposed individuals I0 initial infected individuals R0 initial recovered individuals beta Transmission rate from susceptible to exposed gamma Immunity rate from infected to resistant sigma Immunity rate from exposed to infected =========== ======================================================= The parameters beta, gamma, and sigma, model how fast people move from being susceptible to exposed (beta), from exposed to infected (sigma), and subsequently from infected to resistant (gamma). This model can be used to predict the populations of each compartment once calibrated. """ def __init__( self, beta: float = None, gamma: float = None, sigma: float = None, s: Union[pd.Series, float] = None, e: Union[pd.Series, float] = None, i: Union[pd.Series, float] = None, r: Union[pd.Series, float] = None, n: Union[pd.Series, float] = None, fit: bool = True, fit_period: int = None, ): if not isinstance(fit, bool): raise MqTypeError('expected a boolean value for "fit"') n = n.dropna().iloc[0] if isinstance(n, pd.Series) else n n = 100 if n is None else n fit = False if all(state is None for state in (s, e, i, r)) else fit s = n if s is None else s e = 1 if e is None else e i = 1 if i is None else i r = 0 if r is None else r data_start = [ts.index.min().date() for ts in [s, i, r] if isinstance(ts, pd.Series)] data_start.append(DataContext.current.start_date) start_date = max(data_start) data_end = [ts.index.max().date() for ts in [s, i, r] if isinstance(ts, pd.Series)] data_end.append(DataContext.current.end_date) end_date = max(data_end) self.s = s if isinstance(s, pd.Series) else pd.Series([s]) self.e = e if isinstance(e, pd.Series) else pd.Series([e]) self.i = i if isinstance(i, pd.Series) else pd.Series([i]) self.r = r if isinstance(r, pd.Series) else pd.Series([r]) self.n = n self.beta_init = beta self.gamma_init = gamma self.sigma_init = sigma self.fit = fit self.fit_period = fit_period self.beta_fixed = not (self.fit or (self.beta is None)) self.gamma_fixed = not (self.fit or (self.gamma is None)) self.sigma_fixed = not (self.fit or (self.sigma is None)) lens = [len(x) for x in (self.s, self.e, self.i, self.r)] dtype = float if max(lens) == min(lens) else object data = np.array([self.s, self.e, self.i, self.r], dtype=dtype).T beta_init = self.beta_init if self.beta_init is not None else 0.9 gamma_init = self.gamma_init if self.gamma_init is not None else 0.01 sigma_init = self.sigma_init if self.sigma_init is not None else 0.2 parameters, initial_conditions = SEIR.get_parameters( self.s.iloc[0], self.e.iloc[0], self.i.iloc[0], self.r.iloc[0], n, beta=beta_init, gamma=gamma_init, sigma=sigma_init, beta_fixed=self.beta_fixed, gamma_fixed=self.gamma_fixed, sigma_fixed=self.sigma_fixed, S0_fixed=True, I0_fixed=True, R0_fixed=True, E0_fixed=True, S0_max=5e6, I0_max=5e6, E0_max=10e6, R0_max=10e6, ) self.parameters = parameters self._model = EpidemicModel( SEIR, parameters=parameters, data=data, initial_conditions=initial_conditions, fit_period=self.fit_period ) if self.fit: self._model.fit(verbose=False) t = np.arange((end_date - start_date).days + 1) predict = self._model.solve( t, (self.s0(), self.e0(), self.i0(), self.r0()), (self.beta(), self.gamma(), self.sigma(), n) ) predict_dates = pd.date_range(start_date, end_date) self._model.s_predict = pd.Series(predict[:, 0], predict_dates) self._model.e_predict = pd.Series(predict[:, 1], predict_dates) self._model.i_predict = pd.Series(predict[:, 2], predict_dates) self._model.r_predict = pd.Series(predict[:, 3], predict_dates) @plot_method def e0(self) -> float: """ Model calibration for initial exposed individuals :return: initial exposed individuals """ if self.fit: return self._model.fitted_parameters['E0'] return self.parameters['E0'].value @plot_method def beta(self) -> float: """ Model calibration for transmission rate (susceptible to exposed) :return: beta """ if self.fit: return self._model.fitted_parameters['beta'] return self.parameters['beta'].value @plot_method def gamma(self) -> float: """ Model calibration for immunity (infected to resistant) :return: gamma """ if self.fit: return self._model.fitted_parameters['gamma'] return self.parameters['gamma'].value @plot_method def sigma(self) -> float: """ Model calibration for infection rate (exposed to infected) :return: sigma """ if self.fit: return self._model.fitted_parameters['sigma'] return self.parameters['sigma'].value @plot_method def predict_e(self) -> pd.Series: """ Model calibration for exposed individuals through time :return: exposed predict """ return self._model.e_predict ================================================ FILE: gs_quant/timeseries/tca.py ================================================ """ Copyright 2019 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from typing import Optional import pandas as pd from gs_quant.api.gs.data import QueryType from gs_quant.common import AssetClass from gs_quant.data import Dataset, DataContext from gs_quant.markets.securities import Asset from gs_quant.timeseries import plot_measure from gs_quant.timeseries.measures import _extract_series_from_df @plot_measure((AssetClass.Equity,), None, []) def covariance( asset: Asset, asset_2: Asset, bucket_start: str = '"08:00:00"', bucket_end: str = '"08:30:00"', *, source: str = None, real_time: bool = False, request_id: Optional[str] = None, ) -> pd.Series: """ Provides an estimates of the covariances between stocks in the three major equity markets - US, EMEA and Japan - using an advanced machine learning technique. :param asset: asset to calculate covariance :param asset_2: asset to calculate covariance :param bucket_start: start time of bucket i.e. '08:00:00' :param bucket_end: end time of bucket i.e. '08:30:00' :param source: name of function caller :param real_time: whether to retrieve intraday data instead of EOD :param request_id: service request id, if any :return: Series of the covariance between two stocks """ start, end = DataContext.current.start_date, DataContext.current.end_date ds = Dataset(Dataset.GS.QES_INTRADAY_COVARIANCE) where = dict( assetId=asset.get_marquee_id(), asset2Id=asset_2.get_marquee_id(), bucketStart=bucket_start, bucketEnd=bucket_end, ) data = ds.get_data(start=start, end=end, where=where) series = _extract_series_from_df(data, QueryType.COVARIANCE) series.dataset_ids = (Dataset.GS.QES_INTRADAY_COVARIANCE.value,) return series ================================================ FILE: gs_quant/timeseries/technicals.py ================================================ # Copyright 2018 Goldman Sachs. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # # # Marquee Plot Service will attempt to make public functions (not prefixed with _) from this module available. # Such functions should be fully documented: docstrings should describe parameters and the return value, and provide # a 1-line description. Type annotations should be provided for parameters. from enum import Enum from typing import Union import pandas as pd import statsmodels.tsa.seasonal from gs_quant.timeseries import diff, annualize, returns from .algebra import subtract from .helper import Window, plot_function, normalize_window, apply_ramp from .statistics import mean, std, exponential_std from ..errors import MqValueError """ Technicals library is for technical analysis functions on timeseries, including moving averages, volatility indicators and and other numerical operations which are finance-oriented for analyzing statistical properties of trading activity, such as price movement and volume changes """ class Seasonality(Enum): MONTH = 'month' QUARTER = 'quarter' class SeasonalModel(Enum): ADDITIVE = 'additive' MULTIPLICATIVE = 'multiplicative' class Frequency(Enum): WEEK = 'week' MONTH = 'month' QUARTER = 'quarter' YEAR = 'year' @plot_function def moving_average(x: pd.Series, w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Moving average over specified window :param x: time series of prices :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: date-based time series of return **Usage** Simple arithmetic moving average over the specified window (number of observations). Shorter windows will be more reactive to changes in the asset price, but more volatile. Larger windows will be smoother but less reactive to near term changes in asset prices. :math:`R_t = \\frac{\\sum_{i=t-w+1}^{t} X_t}{N}` where N is the number of observations in each rolling window, :math:`w`. If window is not provided, computes rolling mean over the full series Equivalent to ``mean`` **Examples** Generate price series with 100 observations starting from today's date: >>> prices = generate_series(100) >>> moving_average(prices, 22) **See also** :func:`mean` """ w = normalize_window(x, w) return apply_ramp(mean(x, Window(w.w, 0)), w) @plot_function def bollinger_bands(x: pd.Series, w: Union[Window, int, str] = Window(None, 0), k: float = 2) -> pd.DataFrame: """ Bollinger bands with given window and width :param x: time series of prices :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :param k: band width in standard deviations (default: 2) :return: date-based time series of return **Usage** Standard deviation bands around the moving average of asset price level. Bollinger bands can be used to determine a range around the price level which responds to local volatility changes. Returns two series, upper, :math:`u_t` and lower, :math:`l_t` :math:`u_t = \\bar{X_t} + k\\sigma_t` :math:`l_t = \\bar{X_t} - k\\sigma_t` where :math:`\\bar{X_t}` is the moving average over specified window, and :math:`\\sigma_t` is the rolling standard deviation over the specified window See `Bollinger Bands `_ for more information **Examples** Compute bollinger bands around :math:`20` day moving average at :math:`2` standard deviations: >>> prices = generate_series(100) >>> bollinger_bands(prices, 20, 2) **See also** :func:`moving_average` :func:`std` """ w = normalize_window(x, w) avg = moving_average(x, w) sigma_t = std(x, w) upper = avg + k * sigma_t lower = avg - k * sigma_t return pd.concat([lower, upper], axis=1) @plot_function def smoothed_moving_average(x: pd.Series, w: Union[Window, int, str] = Window(None, 0)) -> pd.Series: """ Smoothed moving average over specified window :param x: time series of prices :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: date-based time series of return **Usage** A modified moving average (MMA), running moving average (RMA), or smoothed moving average (SMMA) is defined as: :math:`P_{MM,today} = \\frac{(N-1)P_{MM,yesterday} + P_today}{N}` where N is the number of observations in each rolling window, :math:`w`. If window is not provided, computes rolling mean over the full series See `Modified moving average `_ for more information **Examples** Generate price series with 100 observations starting from today's date: >>> prices = generate_series(100) >>> smoothed_moving_average(prices, 22) **See also** :func:`mean` :func:'moving_average' """ w = normalize_window(x, w) window_size = w.w ramp = w.r means = apply_ramp(mean(x, Window(window_size, 0)), w) if means.size < 1: return pd.Series(dtype=float) initial_moving_average = means.iloc[0] if (isinstance(ramp, int) and ramp > 0) or isinstance(ramp, pd.DateOffset): x = apply_ramp(x, w) smoothed_moving_averages = x.copy() smoothed_moving_averages *= 0 smoothed_moving_averages.iloc[0] = initial_moving_average for i in range(1, len(x)): if isinstance(window_size, int): window_num_elem = window_size else: window_num_elem = len(x[(x.index > (x.index[i] - window_size).date()) & (x.index <= x.index[i])]) smoothed_moving_averages.iloc[i] = ( (window_num_elem - 1) * smoothed_moving_averages.iloc[i - 1] + x.iloc[i] ) / window_num_elem return smoothed_moving_averages @plot_function def relative_strength_index(x: pd.Series, w: Union[Window, int, str] = 14) -> pd.DataFrame: """ Relative Strength Index :param x: time series of prices :param w: Window or int: size of window and ramp up to use. e.g. Window(22, 10) where 22 is the window size and 10 the ramp up value. If w is a string, it should be a relative date like '1m', '1d', etc. Window size defaults to length of series. :return: date-based time series of RSI **Usage** The RSI computes momentum as the ratio of higher closes to lower closes: stocks which have had more or stronger positive changes have a higher RSI than stocks which have had more or stronger negative changes. See `RSI `_ for more information **Examples** Compute relative strength index over a :math:`14` day window: >>> prices = generate_series(100) >>> relative_strength_index(prices, 14) **See also** :func:`moving_average` :func:`std` :func:`smoothed_moving_average` """ w = normalize_window(x, w) one_period_change = diff(x, 1)[1:] gains = one_period_change.copy() losses = one_period_change.copy() gains[gains < 0] = 0 losses[losses > 0] = 0 losses[losses < 0] *= -1 moving_avg_gains = smoothed_moving_average(gains, w) moving_avg_losses = smoothed_moving_average(losses, w) rsi_len = len(moving_avg_gains) rsi = moving_avg_gains.copy() rsi *= 0 for index in range(0, rsi_len): if moving_avg_losses.iloc[index] == 0: rsi.iloc[index] = 100 else: relative_strength = moving_avg_gains.iloc[index] / moving_avg_losses.iloc[index] rsi.iloc[index] = 100 - (100 / (1 + relative_strength)) return rsi @plot_function def exponential_moving_average(x: pd.Series, beta: float = 0.75) -> pd.Series: """ Exponentially weighted moving average :param x: time series of prices :param beta: how much to weigh the previous observations in the time series, thus controlling how much importance we place on the (more distant) past. Must be between 0 (inclusive) and 1 (exclusive) :return: date-based time series of return **Usage** The exponential(ly weighted) moving average (EMA) of a series [:math:`X_0`, :math:`X_1`, :math:`X_2`, ...], is defined as: :math:`Y_0 = X_0` :math:`Y_t = \\beta \\cdot Y_{t-1} + (1 - \\beta) \\cdot X_t` where :math:`\\beta` is the weight we place on the previous average. See `Exponential moving average `_ for more information **Examples** Generate price series with 100 observations starting from today's date: >>> prices = generate_series(100) >>> exponential_moving_average(prices, 0.9) **See also** :func:`mean` :func:`moving_average` :func:`smoothed_moving_average` """ return x.ewm(alpha=1 - beta, adjust=False).mean() @plot_function def macd(x: pd.Series, m: int = 12, n: int = 26, s: int = 1) -> pd.Series: """ Moving average convergence divergence (MACD). Moving average convergence divergence (MACD) is a trend-following momentum indicator that shows the relationship between two moving averages of a timeseries. It is the result of subtracting the exponential moving average of `x` with a period of :math:`m` from the exponential moving average of :math:`x` with a period of :math:`n`. Optionally, specify :math:`s` to apply an exponential moving average to the resulting series with a period of :math:`s` (default 1, equivalent to no exponential moving average). :param x: time series :param m: period of first, short exponential moving average (default 12) :param n: period of second, long exponential moving average (default 26) :param s: optional smoothing parameter (default 1) :return: date-based time series of return **Usage** The exponential(ly weighted) moving average (EMA) of a series [:math:`X_0`, :math:`X_1`, :math:`X_2`, ...], is defined as: :math:`Y_0 = X_0` :math:`Y_t = \\beta \\cdot Y_{t-1} + (1 - \\beta) \\cdot X_t` where :math:`\\beta = \\frac{2}{\\text{period} + 1}` is the weight we place on the previous average. The MACD of a series is defined as :math:`\\text{EMA}(\\text{EMA}(X, M) - \\text{EMA}(X, N), S)` **Examples** Generate price series with 100 observations starting from today's date: >>> prices = generate_series(100) >>> macd(prices, 12, 26) **See also** :func:`exponential_moving_average` :func:`moving_average` :func:`smoothed_moving_average` """ a = x.ewm(adjust=False, span=m).mean() b = x.ewm(adjust=False, span=n).mean() return subtract(a, b).ewm(adjust=False, span=s).mean() @plot_function def exponential_volatility(x: pd.Series, beta: float = 0.75) -> pd.Series: """ Exponentially weighted volatility :param x: time series of prices :param beta: how much to weigh the previous price in the time series, thus controlling how much importance we place on the (more distant) past. Must be between 0 (inclusive) and 1 (exclusive) :return: date-based time series of exponential volatility of the input series **Usage** Calculates the exponentially weighted standard deviation of the return of the input series, and annualizes the standard deviation **Examples** Generate price series and compute exponentially weighted standard deviation of returns >>> prices = generate_series(100) >>> exponential_volatility(prices, 0.9) The above is equivalent to >>> annualize(exponential_std(returns(prices), 0.9)) * 100 **See also** :func:`volatility` :func:`exponential_std` :func:`exponential_spread_volatility` """ return annualize(exponential_std(returns(x), beta)).mul(100) @plot_function def exponential_spread_volatility(x: pd.Series, beta: float = 0.75) -> pd.Series: """ Exponentially weighted spread volatility :param x: time series of prices :param beta: how much to weigh the previous price in the time series, thus controlling how much importance we place on the (more distant) past. Must be between 0 (inclusive) and 1 (exclusive) :return: date-based time series of exponential spread volatility of the input series **Usage** Exponentially weights the daily differences of the input series, calculates the annualized standard deviation **Examples** Generate price series and compute exponentially weighted standard deviation of returns >>> prices = generate_series(100) >>> exponential_volatility(prices, 0.9) The above is equivalent to >>> annualize(exponential_std(diff(prices, 1), 0.9)) **See also** :func:`volatility` :func:`exponential_std` :func:`exponential_volatility` """ return annualize(exponential_std(diff(x, 1), beta)) def _freq_to_period(x: pd.Series, freq: Frequency = Frequency.YEAR): """ Given input series x with a DateTimeIndex and a desired temporal frequency (period), returns x with all NaNs forward-filled (according to x's index's DateTime frequency) and the number of data points in a period. freq should be the length of time in which x's cycles repeat. For example: yearly retail sales cycle, yearly temperature fluctuation cycle. For example: 1) If x is a daily series and freq = YEARLY, then there are 365 data points in a period; 2) If x is a monthly series and freq = QUARTERLY, then there are 3 data points in a period. Freq parameter only applies when data frequency is: 'B' and frequency == Weekly --> period = 5 'B' and frequency == Monthly --> convert to 'D' and period = 30 'D' and frequency == Weekly --> period = 7 'D' and frequency == Monthly --> period = 30 'M' and frequency == Quarterly --> Period = 3 'W' and frequency == Quarterly --> period = 13 """ if not isinstance(x.index, pd.DatetimeIndex): raise MqValueError("Series must have a pandas.DateTimeIndex.") pfreq = getattr(getattr(x, 'index', None), 'inferred_freq', None) # Some older versions of statsmodels don't handle some of the newer pandas frequencies, so we manually adjust them pfreq = 'MS' if pfreq in ('ME', 'M') else pfreq # Convert Month[End] into MonthlyStart pfreq = 'QS' if pfreq in ('QE-DEC', 'QE') else pfreq # Convert Quarter[End] into QuarterlyStart period = None if pfreq is None else statsmodels.tsa.seasonal.freq_to_period(pfreq) if period in [7, None]: # daily x = x.asfreq('D', method='ffill') if freq == Frequency.YEAR: return x, 365 elif freq == Frequency.QUARTER: return x, 91 elif freq == Frequency.MONTH: return x, 30 else: return x, 7 elif period == 5: # business day if freq == Frequency.YEAR: return x.asfreq('D', method='ffill'), 365 if freq == Frequency.QUARTER: return x.asfreq('D', method='ffill'), 91 elif freq == Frequency.MONTH: return x.asfreq('D', method='ffill'), 30 else: # freq == Frequency.WEEKLY: return x.asfreq('B', method='ffill'), 5 elif period == 52: # weekly frequency x = x.asfreq('W', method='ffill') if freq == Frequency.YEAR: return x, period elif freq == Frequency.QUARTER: return x, 13 elif freq == Frequency.MONTH: return x, 4 else: raise MqValueError(f'Frequency {freq.value} not compatible with series with frequency {pfreq}.') elif period == 12: # monthly frequency x = x.asfreq('ME', method='ffill') if freq == Frequency.YEAR: return x, period elif freq == Frequency.QUARTER: return x, 3 else: raise MqValueError(f'Frequency {freq.value} not compatible with series with frequency {pfreq}.') return x, period def _seasonal_decompose(x: pd.Series, method: SeasonalModel = SeasonalModel.ADDITIVE, freq: Frequency = Frequency.YEAR): x, period = _freq_to_period(x, freq) if x.shape[0] < 2 * period: # Replace ValueError in seasonal_decompose with more descriptive error raise MqValueError(f"Series must have two complete cycles to be analyzed. Series has only {x.shape[0]} dpts.") decompose_obj = statsmodels.tsa.seasonal.seasonal_decompose(x, period=period, model=method.value) return decompose_obj @plot_function def seasonally_adjusted( x: pd.Series, method: SeasonalModel = SeasonalModel.ADDITIVE, freq: Frequency = Frequency.YEAR ) -> pd.Series: """ Seasonally adjusted series :param x: time series with at least two years worth of data. :param method: 'additive' or 'multiplicative'. Type of seasonal model to use. 'multiplicative' is appropriate when the magnitude of the series's values affect the magnitude of seasonal swings; 'additive' is appropriate when seasonal swings' sizes are independent of the series's values. :param freq: 'year', 'quarter', 'month', or 'week'. Period in which full cycle occurs (i.e. the "period" of a wave). :return: date-based time series of seasonally-adjusted input series. **Usage** Uses a centered moving average and convolution to decompose the input series into seasonal, trend, and residual components. This function returns the series with the seasonal component removed. If using the default additive model: :math:`Y_t = X_t - S_t` If using the multiplicative model: :math:`Y_t = X_t / S_t` **Examples** Generate price series and compute seasonally-adjusted series. >>> prices = generate_series(1000) >>> seasonally_adjusted(prices) **See also** :func:`trend` """ decompose_obj = _seasonal_decompose(x, method, freq) if method == SeasonalModel.ADDITIVE: return decompose_obj.trend + decompose_obj.resid else: return decompose_obj.trend * decompose_obj.resid @plot_function def trend(x: pd.Series, method: SeasonalModel = SeasonalModel.ADDITIVE, freq: Frequency = Frequency.YEAR) -> pd.Series: """ Trend of series with seasonality and residuals removed. :param x: time series with at least two years worth of data. :param method: 'additive' or 'multiplicative'. Type of seasonal model to use. 'multiplicative' is appropriate when the magnitude of the series's values affect the magnitude of seasonal swings; 'additive' is appropriate when seasonal swings' sizes are independent of the series's values. :param freq: 'year', 'quarter', 'month', or 'week'. Period in which full cycle occurs (i.e. the "period" of a wave). :return: date-based time series with trend of input series. **Usage** Uses a centered moving average and convolution to decompose the input series into seasonal, trend, and residual components. This function returns the trend component. If using the default additive model: :math:`Y_t = X_t - S_t - R_t` If using the multiplicative model: :math:`Y_t = X_t / (S_t * R_t)` **Examples** Generate price series and compute its trend. >>> prices = generate_series(1000) >>> trend(prices) **See also** :func:`seasonally_adjusted` """ decompose_obj = _seasonal_decompose(x, method, freq) return decompose_obj.trend ================================================ FILE: gs_quant/tracing/__init__.py ================================================ """ Copyright 2022 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from .tracing import * __name__ = 'tracing' ================================================ FILE: gs_quant/tracing/tracing.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import datetime as dt import logging import traceback from contextlib import ContextDecorator from enum import Enum from typing import Tuple, Optional, Sequence, Mapping, Union, Callable import pandas as pd from opentelemetry import trace, context from opentelemetry.context import Context from opentelemetry.propagate import extract, inject, set_global_textmap from opentelemetry.propagators.textmap import TextMapPropagator from opentelemetry.sdk.trace import ( TracerProvider, SynchronousMultiSpanProcessor, ReadableSpan, Span, Event, SpanProcessor, ) from opentelemetry.sdk.trace.export import SimpleSpanProcessor, SpanExporter from opentelemetry.trace import Tracer as OtelTracer, SpanContext, INVALID_SPAN from opentelemetry.trace import format_trace_id, format_span_id from gs_quant.errors import MqWrappedError _logger = logging.getLogger(__name__) class Tags(Enum): HTTP_METHOD = 'http.method' HTTP_URL = 'http.url' HTTP_STATUS_CODE = 'http.status_code' CONTENT_LENGTH = 'content.length' class SpanConsumer(SpanExporter): _instance = None @staticmethod def get_instance(): if SpanConsumer._instance is None: SpanConsumer._instance = SpanConsumer() return SpanConsumer._instance @staticmethod def get_spans() -> Sequence['TracingSpan']: return SpanConsumer.get_instance()._collected_spans @staticmethod def reset(): SpanConsumer.get_instance()._collected_spans = [] @staticmethod def manually_record(spans: Sequence['TracingSpan']): SpanConsumer.get_instance()._collected_spans.extend(spans) def __init__(self): self._collected_spans = [] def export(self, spans: Sequence[ReadableSpan]) -> None: self._collected_spans.extend(TracingSpan(span) for span in spans) class TracingContext: def __init__(self, ctx: SpanContext): self._context = ctx class TracingEvent: def __init__(self, ot_event: Event): self._event = ot_event @property def name(self) -> str: return self._event.name @property def timestamp(self) -> int: """ Timestamp in ns """ return self._event.timestamp @property def timestamp_sec(self) -> float: """ Timestamp in seconds """ return self._event.timestamp / 1e9 @property def attributes(self) -> Mapping[str, any]: return self._event.attributes class TracingScope: def __init__(self, token, span: Optional[Span], finish_on_close: bool = True): self._token = token self._span = TracingSpan(span) if span else NonRecordingTracingSpan(INVALID_SPAN) self._finish_on_close = finish_on_close def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): if exc_val: Tracer.record_exception(exc_val, self._span, exc_tb) self.close() def close(self): if self._token: context.detach(self._token) if self._finish_on_close: return self._span.end() @property def span(self) -> 'TracingSpan': return self._span class TracingSpan: def __init__(self, span: Span, endpoint: Optional[str] = None): self._span = span self._endpoint = endpoint def unwrap(self): return self._span @property def context(self): return self def end(self): self._span.end() def is_recording(self): return self._span.is_recording() @property def operation_name(self) -> str: return self._span.name def transportable(self, endpoint_override: Optional[str] = None) -> 'TransportableSpan': return TransportableSpan(self, endpoint_override or self._endpoint) @property def endpoint(self) -> str: return self._endpoint @endpoint.setter def endpoint(self, endpoint): self._endpoint = endpoint @property def trace_id(self) -> str: return format_trace_id(self._span.get_span_context().trace_id) def is_error(self) -> bool: return self._span.attributes.get('error', False) @property def start_time(self) -> int: """ Start time in ns """ return self._span.start_time @property def end_time(self) -> Optional[int]: """ End time in ns """ return self._span.end_time @property def duration(self) -> float: """ Duration of the span in milliseconds, or None if the span is not finished. """ unwrapped = self._span return (unwrapped.end_time - unwrapped.start_time) / 1e6 if unwrapped.end_time else None @property def span_id(self) -> str: return format_span_id(self._span.get_span_context().span_id) @property def parent_id(self) -> Optional[str]: parent = self._span.parent return format_span_id(parent.span_id) if parent else None @property def tags(self) -> Mapping[str, any]: return self._span.attributes @property def events(self) -> Sequence[TracingEvent]: return tuple(TracingEvent(event) for event in self._span.events) def set_tag(self, key: Union[Enum, str], value: Union[bool, str, bytes, int, float, dt.date]) -> 'TracingSpan': if value is None: return self if isinstance(value, dt.date): value = value.isoformat() elif isinstance(value, Enum): value = value.value if isinstance(key, Enum): key = key.value self._span.set_attribute(key, value) return self def add_event( self, name: str, attributes: Optional[Mapping[str, any]] = None, timestamp: Optional[float] = None ) -> 'TracingSpan': converted_timestamp = int(timestamp * 1e9) if timestamp else None self._span.add_event(name, attributes, converted_timestamp) return self def log_kv(self, key_values: Mapping[str, any], timestamp=None) -> 'TracingSpan': converted_timestamp = int(timestamp * 1e9) if timestamp else None event_name = "log" if key_values is None or "event" not in key_values else key_values["event"] self._span.add_event(event_name, key_values, converted_timestamp) return self class NonRecordingTracingSpan(TracingSpan): def __init__(self, span: Span): super().__init__(span) self._span = span def end(self): pass @property def operation_name(self) -> str: return "Non-Recording Span" @property def parent_id(self) -> Optional[str]: return None @property def tags(self) -> Mapping[str, any]: return dict() @property def start_time(self) -> int: return 0 @property def end_time(self) -> Optional[int]: return None @property def duration(self) -> float: return 0 NOOP_TRACING_SCOPE = TracingScope(None, None) class TransportableSpan(TracingSpan): """ A transportable span is a representation of a finished TracingSpan that can be pickled. """ def __init__(self, span: TracingSpan, endpoint: Optional[str] = None): super().__init__(None, endpoint or span.endpoint) self._operation_name = span.operation_name self._trace_id = span.trace_id self._span_id = span.span_id self._parent_id = span.parent_id self._tags = dict(span.tags) self._start_time = span.start_time self._end_time = span.end_time self._events = tuple(TransportableTracingEvent(event) for event in span.events) def transportable(self, endpoint_override: Optional[str] = None) -> 'TransportableSpan': if not endpoint_override or endpoint_override == self._endpoint: return self else: return TransportableSpan(self, endpoint_override or self._endpoint) def end(self): pass def is_recording(self): return self._end_time is None @property def operation_name(self) -> str: return self._operation_name @property def trace_id(self) -> str: return self._trace_id def is_error(self) -> bool: return self._tags.get('error', False) @property def start_time(self) -> int: """ Start time in ns """ return self._start_time @property def end_time(self) -> Optional[int]: """ End time in ns """ return self._end_time @property def duration(self) -> float: """ Duration of the span in milliseconds, or None if the span is not finished. """ return (self._end_time - self._start_time) / 1e6 if self._end_time else None @property def span_id(self) -> str: return self._span_id @property def parent_id(self) -> Optional[str]: return self._parent_id @property def tags(self) -> Mapping[str, any]: return self._tags @property def events(self) -> Sequence[TracingEvent]: return self._events def set_tag(self, key: Union[Enum, str], value: Union[bool, str, bytes, int, float, dt.date]) -> 'TracingSpan': return self def add_event( self, name: str, attributes: Optional[Mapping[str, any]] = None, timestamp: Optional[float] = None ) -> 'TracingSpan': return self def log_kv(self, key_values: Mapping[str, any], timestamp=None) -> 'TracingSpan': return self class TransportableTracingEvent(TracingEvent): def __init__(self, event: TracingEvent): super().__init__(None) self._name = event.name self._timestamp = event.timestamp self._attributes = dict(event.attributes) @property def name(self) -> str: return self._name @property def timestamp(self) -> int: """ Timestamp in ns """ return self._timestamp @property def timestamp_sec(self) -> float: """ Timestamp in seconds """ return self._timestamp / 1e9 @property def attributes(self) -> Mapping[str, any]: return self._attributes class TracerFactory: __tracer_instance = None _extra_span_processors = [] def get(self) -> OtelTracer: if TracerFactory.__tracer_instance is None: # Define which OpenTelemetry Tracer provider implementation to use. span_processor = SynchronousMultiSpanProcessor() span_processor.add_span_processor(SimpleSpanProcessor(SpanConsumer.get_instance())) for extra_processor in TracerFactory._extra_span_processors: span_processor.add_span_processor(extra_processor) trace.set_tracer_provider(TracerProvider(active_span_processor=span_processor)) # Create an OpenTelemetry Tracer. otel_tracer = trace.get_tracer(__name__) TracerFactory.__tracer_instance = otel_tracer return TracerFactory.__tracer_instance @staticmethod def preregister_span_processor(processor: SpanProcessor): if TracerFactory.__tracer_instance is not None: _logger.error("Can't add span consumer after tracer has been created") else: TracerFactory._extra_span_processors.append(processor) class Tracer(ContextDecorator): __factory = TracerFactory() def __init__( self, label: str = 'Execution', print_on_exit: bool = False, threshold: int = None, wrap_exceptions=False, parent_span: Optional[Union[TracingSpan, TracingContext]] = None, ): self.__print_on_exit = print_on_exit self.__label = label self.__threshold = threshold self.wrap_exceptions = wrap_exceptions self._parent_span = parent_span if isinstance(parent_span, TracingSpan) else None self._parent_ctx = parent_span if isinstance(parent_span, TracingContext) else None def __enter__(self): if self._parent_span: self.__parent_scope = Tracer.activate_span(self._parent_span) self.__scope = Tracer.start_active_span(self.__label, child_of=self._parent_ctx) return self.__scope def __exit__(self, exc_type, exc_value, exc_tb): if exc_value: self.record_exception(exc_value, self.__scope.span, exc_tb) self.__scope.close() if self._parent_span: self.__parent_scope.close() if self.wrap_exceptions and exc_type is not None and not exc_type == MqWrappedError: raise MqWrappedError(f'Unable to calculate: {self.__label}') from exc_value @staticmethod def get_instance() -> OtelTracer: return Tracer.__factory.get() @staticmethod def set_factory(factory: TracerFactory): Tracer.__factory = factory @staticmethod def active_span(): current_span = trace.get_current_span() if current_span is None or not current_span.is_recording(): return NonRecordingTracingSpan(INVALID_SPAN) else: return TracingSpan(current_span) @staticmethod def set_propagator_format(propagator_format: TextMapPropagator): set_global_textmap(propagator_format) @staticmethod def inject(carrier): span = trace.get_current_span() if span is not None and span.is_recording(): try: inject(carrier) except Exception: _logger.error("Error injecting trace context", exc_info=True) @staticmethod def extract(carrier) -> TracingContext: try: return TracingContext(extract(carrier)) except Exception: _logger.error("Error extracting trace context", exc_info=True) @staticmethod def activate_span(span: TracingSpan = None, finish_on_close: bool = False) -> Optional[TracingScope]: """ Activates the current span :return: The current span """ if span is None or not span.is_recording(): return NOOP_TRACING_SCOPE ctx = trace.set_span_in_context(span.unwrap()) token = context.attach(ctx) return TracingScope(token, span.unwrap(), finish_on_close=finish_on_close) @staticmethod def start_active_span( operation_name: str, child_of: Optional[TracingContext] = None, ignore_active_span: bool = False, finish_on_close: bool = True, ) -> TracingScope: ctx = Context() if ignore_active_span else child_of._context if child_of else None span = Tracer.get_instance().start_span(operation_name, context=ctx) # Set as the implicit current context # Creates a Context object with parent set as current span ctx = trace.set_span_in_context(span) token = context.attach(ctx) return TracingScope(token, span, finish_on_close) @staticmethod def record_exception(e, span: TracingSpan = None, exc_tb=None): span = span or TracingSpan(trace.get_current_span()) if span is not None: try: span.set_tag('error', True) span.log_kv( { 'event': 'error', 'message': str(e), 'error.object': str(e), 'error.kind': type(e).__name__, 'stack': Tracer.__format_traceback(type(e), e, exc_tb), } ) except Exception: pass @staticmethod def __format_traceback(exc_type, exc_value, exc_tb=None): if exc_value is None: return '' try: return ''.join(traceback.format_exception(exc_type, exc_value, exc_tb, limit=10)) except Exception: return '' @staticmethod def reset(): SpanConsumer.reset() @staticmethod def get_spans() -> Sequence[TracingSpan]: return SpanConsumer.get_spans() @staticmethod def plot(reset=False, show=True): try: import plotly.express as px except ImportError: _logger.warning('Package "plotly" required to visualise the trace, printing instead') Tracer.print(reset) return color_list = ('#2b76f7', '#5a8efb', '#79aaff', '#89bbff', '#cdddff') error_color = 'rgb(244, 127, 114)' ordered_spans, _ = Tracer.gather_data(False) span_df = pd.DataFrame.from_records( [ ( f'#{i}', f'{s.operation_name} {int(s.duration):,.0f}ms', dt.datetime.fromtimestamp(s.start_time / 1e9), dt.datetime.fromtimestamp(s.end_time / 1e9), '\n '.join([f'{k}={v}' for k, v in s.tags.items()]) if s.tags else '', ) for i, (depth, s) in enumerate(ordered_spans) ], columns=['id', 'operation', 'start', 'end', 'tags'], ) color_map = { f'#{i}': error_color if 'error' in s.tags else color_list[depth % len(color_list)] for i, (depth, s) in enumerate(ordered_spans) } fig = px.timeline( data_frame=span_df, width=1000, height=40 + 30 * len(span_df), x_start="start", x_end="end", y="id", hover_data='tags', text='operation', color='id', color_discrete_map=color_map, ) fig.update_layout( showlegend=False, yaxis_visible=False, yaxis_showticklabels=False, paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)', margin={'l': 0, 't': 0, 'b': 0, 'r': 0}, ) if reset: Tracer.reset() if show: fig.show() else: return fig @staticmethod def gather_data(as_string: bool = True, root_id: Optional[str] = None, trace_id: Optional[str] = None): spans = Tracer.get_spans() spans_by_parent = {} for span in reversed(spans): if trace_id and span.trace_id != trace_id: continue spans_by_parent.setdefault(span.parent_id, []).append(span) def _build_tree(parent_span, depth): if as_string: name = f'{"* " * depth}{parent_span.operation_name}' elapsed = (parent_span.end_time - parent_span.start_time) / 1000000 error = " [Error]" if parent_span.is_error() else "" lines.append(f'{name:<50}{elapsed:>8.1f} ms{error}') else: lines.append((depth, parent_span)) for child_span in reversed(spans_by_parent.get(parent_span.span_id, [])): _build_tree(child_span, depth + 1) total = 0 lines = [] # By default, we look for the span with no parent, but this might not always be what we want for span in reversed(spans_by_parent.get(root_id, [])): _build_tree(span, 0) total += (span.end_time - span.start_time) / 1000000 if as_string: tracing_str = '\n'.join(lines) return tracing_str, total else: return lines, total @staticmethod def print(reset=True, root_id=None, trace_id=None): tracing_str, total = Tracer.gather_data(root_id=root_id, trace_id=trace_id) str_id = trace_id or root_id or "" _logger.warning(f'Tracing Info: {str_id}\n{tracing_str}\n{"-" * 61}\nTOTAL:{total:>52.1f} ms') if reset: Tracer.reset() return tracing_str, total @staticmethod def in_scope(func: Callable, operation_name='callback') -> Callable: """ for using with futures or callbacks that would otherwise lose the tracing context e.g. >>>my_future.add_done_callback(my_callback) >>>my_other_future.add_done_callback(lambda fut: my_callback(fut.result()) ` becomes: >>>my_future.add_done_callback(Tracer.in_scope(my_callback)) >>>my_other_future.add_done_callback(Tracer.in_scope(lambda fut: my_callback(fut.result()) ) """ span_carrier = {} Tracer.inject(span_carrier) def wrapper(*args, **kwargs): span_ctx = Tracer.extract(span_carrier) with Tracer(operation_name, parent_span=span_ctx): return func(*args, **kwargs) return wrapper def parse_tracing_line_args(line: str) -> Tuple[Optional[str], bool]: stripped = tuple(s for s in line.split(' ') if s != '') if len(stripped) > 0 and stripped[0] in ('chart', 'plot', 'graph'): return tuple(stripped[1:]) if len(stripped[1:]) else None, True return stripped if len(stripped) else None, False try: # Attempt to import/register some jupyter magic import gs_quant_internal.tracing.jupyter # noqa except ImportError: try: from IPython.core.magic import register_cell_magic from IPython import get_ipython @register_cell_magic("trace") def trace_ipython_cell(line, cell): """Wraps the execution of a cell in a tracer call and prints""" span_name, show_chart = parse_tracing_line_args(line) if cell is None: return line with Tracer(label=span_name): res = get_ipython().run_cell(cell) if res.error_in_exec: Tracer.record_exception(res.error_in_exec) if show_chart: Tracer.plot(True) else: Tracer.print(True) return None except Exception: pass ================================================ FILE: gs_quant/workflow/__init__.py ================================================ """ Copyright 2023 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from gs_quant.target.workflow_quote import * # noqa from .workflow import * # noqa ================================================ FILE: gs_quant/workflow/workflow.py ================================================ """ Copyright 2023 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ from typing import Optional, Tuple from dataclasses_json import global_config from gs_quant.json_convertors import decode_hedge_type, decode_hedge_types from gs_quant.target.workflow_quote import HedgeTypes # noqa - We need to import this one from target global_config.decoders[Optional[HedgeTypes]] = decode_hedge_type global_config.decoders[HedgeTypes] = decode_hedge_type global_config.decoders[Optional[Tuple[HedgeTypes, ...]]] = decode_hedge_types ================================================ FILE: pyproject.toml ================================================ [build-system] requires = ["setuptools>=61.0", "versioneer[toml]==0.28"] build-backend = "setuptools.build_meta" [project] name = "gs_quant" dynamic = ["version"] authors = [ {name = "Goldman Sachs", email = "developer@gs.com"} ] description = "Goldman Sachs Quant" readme = "README.md" license = {text = "Apache-2.0"} requires-python = ">=3.9" classifiers = [ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Operating System :: OS Independent", "License :: OSI Approved :: Apache Software License", ] dependencies = [ "aenum", "backoff", "backports.zoneinfo; python_version < '3.9'", "cachetools", "certifi", "dataclasses_json", "deprecation", "inflection", "lmfit", "more_itertools", "msgpack", "nest-asyncio", "numpy>1.17.0,<2.4.0", "opentelemetry-api", "opentelemetry-sdk", "pandas>=1.4", "pydash<7.0.0", "python-dateutil>=2.7.0", "requests", "httpx>=0.28.1", "scipy>=1.2.0", "statsmodels>=0.13.0", "tqdm", "websockets", ] [project.optional-dependencies] turbo = [ "quant-extensions", ] notebook = [ "jupyter", "matplotlib", "seaborn", "treelib", ] test = [ "pytest", "pytest-cov", "pytest-mock", "pytest-order", "pytest-asyncio", "testfixtures", "nbconvert", "nbformat", "plotly", "freezegun", "ruff", ] develop = [ "wheel", "sphinx", "sphinx_rtd_theme", "sphinx_autodoc_typehints", "pytest", "pytest-cov", "pytest-mock", "pytest-order", "testfixtures", ] [project.urls] Homepage = "https://marquee.gs.com" [dependency-groups] test = [ "pytest", "pytest-cov", "pytest-mock", "pytest-order", "pytest-asyncio", "testfixtures", "nbconvert", "nbformat", "plotly", "freezegun", "ruff", ] dev = [ "wheel", "sphinx", "sphinx_rtd_theme", "sphinx_autodoc_typehints", "pytest", "pytest-cov", "pytest-mock", "pytest-order", "testfixtures", ] [tool.setuptools] include-package-data = true [tool.setuptools.dynamic] version = {attr = "gs_quant.__version__"} [tool.setuptools.packages.find] include = ["gs_quant*"] [tool.setuptools.package-data] gs_quant = [ "config.ini", "resources/asset-service.json", "resources/data-service.json", "resources/risk-service.json", "resources/certificates/*", "examples/*", "guides/*", "tutorials/*", "docs/*", "_version.py", ] [tool.versioneer] VCS = "git" style = "pep440" versionfile_source = "gs_quant/_version.py" versionfile_build = "gs_quant/_version.py" tag_prefix = "release-" parentdir_prefix = "gs_quant-" [tool.ruff] # Setting can be seen here # https://docs.astral.sh/ruff/settings/ # Line lenght override line-length = 120 indent-width = 4 # In addition to the standard set of exclusions, omit all tests, plus a specific file. extend-exclude = ["_version.py", "versioneer.py", "*.json", "target", "__init__.py", "content", "documentation", "datacube"] # Assume at least Python 3.10, code will be compatible with 3.10 or later target-version = "py310" [tool.ruff.format] # Keep the quotes as they are quote-style = "preserve" skip-magic-trailing-comma = false [tool.ruff.lint.isort] combine-as-imports = true [tool.ruff.lint] # We add the tidy import selection to pickup banned-api extend-select = [ "TID251", # see flake8-tidy-imports.banned-api below "ICN", # import conventions, e.g. import pandas as pd "NPY", # Mainly for numpy 2.0 deprecation check ] # e.g. block # from pandas import DataFrame, Series # instead do # import pandas as pd # pd.DataFrame(), pd.Series() [tool.ruff.lint.flake8-import-conventions] banned-from = [ "pandas", "numpy", "datetime" ] # The default list include "pandas as pd", "numpy as np" extend-aliases = { "datetime" = "dt" } [tool.ruff.lint.flake8-tidy-imports.banned-api] "gs_quant.target.common".msg = "Use gs_quant.common instead of gs_quant.target.common" "gs_quant.target.instrument".msg = "Use gs_quant.instrument instead of gs_quant.target.instrument" "gs_quant.target.workflow_quote".msg = "Use gs_quant.workflow instead of gs_quant.target.workflow_quote" "pytz".msg = "Use zoneinfo package or datetime.timezone instead of pytz" # When we support a minimium version of python 3.10 #"typing.List".msg = "Use built-in list instead of typing.List" #"typing.Tuple".msg = "Use built-in tuple instead of typing.Tuple" #"typing.Dict".msg = "Use built-in dict instead of typing.Dict" #"typing.Set".msg = "Use built-in set instead of typing.set" [tool.uv] default-groups = ["test", "dev"] native-tls = true [[tool.uv.index]] name = "aws" url = "https://pypi.aws.site.gs.com/repository/pypi-group/simple" default = true [tool.uv.pip] index-url = "https://pypi.aws.site.gs.com/repository/pypi-group/simple" emit-index-url = true universal = true ================================================ FILE: requirements.txt ================================================ . gs_quant[notebook] ================================================ FILE: setup.cfg ================================================ # See the docstring in versioneer.py for instructions. Note that you must # re-run 'versioneer.py setup' after changing this section, and commit the # resulting files. [flake8] max-line-length = 120 ignore = F403,F405,W605,W504 exclude = holodeck target __init__.py common.py core.py base.py assets.py datacube ================================================ FILE: setup.py ================================================ """ Copyright 2018 Goldman Sachs. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import setuptools import versioneer setuptools.setup( version=versioneer.get_version(), cmdclass=versioneer.get_cmdclass(), ) ================================================ FILE: versioneer.py ================================================ # Version: 0.28 """The Versioneer - like a rocketeer, but for versions. The Versioneer ============== * like a rocketeer, but for versions! * https://github.com/python-versioneer/python-versioneer * Brian Warner * License: Public Domain (Unlicense) * Compatible with: Python 3.7, 3.8, 3.9, 3.10 and pypy3 * [![Latest Version][pypi-image]][pypi-url] * [![Build Status][travis-image]][travis-url] This is a tool for managing a recorded version number in setuptools-based python projects. The goal is to remove the tedious and error-prone "update the embedded version string" step from your release process. Making a new release should be as easy as recording a new tag in your version-control system, and maybe making new tarballs. ## Quick Install Versioneer provides two installation modes. The "classic" vendored mode installs a copy of versioneer into your repository. The experimental build-time dependency mode is intended to allow you to skip this step and simplify the process of upgrading. ### Vendored mode * `pip install versioneer` to somewhere in your $PATH * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is available, so you can also use `conda install -c conda-forge versioneer` * add a `[tool.versioneer]` section to your `pyproject.toml` or a `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md)) * Note that you will need to add `tomli; python_version < "3.11"` to your build-time dependencies if you use `pyproject.toml` * run `versioneer install --vendor` in your source tree, commit the results * verify version information with `python setup.py version` ### Build-time dependency mode * `pip install versioneer` to somewhere in your $PATH * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is available, so you can also use `conda install -c conda-forge versioneer` * add a `[tool.versioneer]` section to your `pyproject.toml` or a `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md)) * add `versioneer` (with `[toml]` extra, if configuring in `pyproject.toml`) to the `requires` key of the `build-system` table in `pyproject.toml`: ```toml [build-system] requires = ["setuptools", "versioneer[toml]"] build-backend = "setuptools.build_meta" ``` * run `versioneer install --no-vendor` in your source tree, commit the results * verify version information with `python setup.py version` ## Version Identifiers Source trees come from a variety of places: * a version-control system checkout (mostly used by developers) * a nightly tarball, produced by build automation * a snapshot tarball, produced by a web-based VCS browser, like github's "tarball from tag" feature * a release tarball, produced by "setup.py sdist", distributed through PyPI Within each source tree, the version identifier (either a string or a number, this tool is format-agnostic) can come from a variety of places: * ask the VCS tool itself, e.g. "git describe" (for checkouts), which knows about recent "tags" and an absolute revision-id * the name of the directory into which the tarball was unpacked * an expanded VCS keyword ($Id$, etc) * a `_version.py` created by some earlier build step For released software, the version identifier is closely related to a VCS tag. Some projects use tag names that include more than just the version string (e.g. "myproject-1.2" instead of just "1.2"), in which case the tool needs to strip the tag prefix to extract the version identifier. For unreleased software (between tags), the version identifier should provide enough information to help developers recreate the same tree, while also giving them an idea of roughly how old the tree is (after version 1.2, before version 1.3). Many VCS systems can report a description that captures this, for example `git describe --tags --dirty --always` reports things like "0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the 0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has uncommitted changes). The version identifier is used for multiple purposes: * to allow the module to self-identify its version: `myproject.__version__` * to choose a name and prefix for a 'setup.py sdist' tarball ## Theory of Operation Versioneer works by adding a special `_version.py` file into your source tree, where your `__init__.py` can import it. This `_version.py` knows how to dynamically ask the VCS tool for version information at import time. `_version.py` also contains `$Revision$` markers, and the installation process marks `_version.py` to have this marker rewritten with a tag name during the `git archive` command. As a result, generated tarballs will contain enough information to get the proper version. To allow `setup.py` to compute a version too, a `versioneer.py` is added to the top level of your source tree, next to `setup.py` and the `setup.cfg` that configures it. This overrides several distutils/setuptools commands to compute the version when invoked, and changes `setup.py build` and `setup.py sdist` to replace `_version.py` with a small static file that contains just the generated version data. ## Installation See [INSTALL.md](./INSTALL.md) for detailed installation instructions. ## Version-String Flavors Code which uses Versioneer can learn about its version string at runtime by importing `_version` from your main `__init__.py` file and running the `get_versions()` function. From the "outside" (e.g. in `setup.py`), you can import the top-level `versioneer.py` and run `get_versions()`. Both functions return a dictionary with different flavors of version information: * `['version']`: A condensed version string, rendered using the selected style. This is the most commonly used value for the project's version string. The default "pep440" style yields strings like `0.11`, `0.11+2.g1076c97`, or `0.11+2.g1076c97.dirty`. See the "Styles" section below for alternative styles. * `['full-revisionid']`: detailed revision identifier. For Git, this is the full SHA1 commit id, e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac". * `['date']`: Date and time of the latest `HEAD` commit. For Git, it is the commit date in ISO 8601 format. This will be None if the date is not available. * `['dirty']`: a boolean, True if the tree has uncommitted changes. Note that this is only accurate if run in a VCS checkout, otherwise it is likely to be False or None * `['error']`: if the version string could not be computed, this will be set to a string describing the problem, otherwise it will be None. It may be useful to throw an exception in setup.py if this is set, to avoid e.g. creating tarballs with a version string of "unknown". Some variants are more useful than others. Including `full-revisionid` in a bug report should allow developers to reconstruct the exact code being tested (or indicate the presence of local changes that should be shared with the developers). `version` is suitable for display in an "about" box or a CLI `--version` output: it can be easily compared against release notes and lists of bugs fixed in various releases. The installer adds the following text to your `__init__.py` to place a basic version in `YOURPROJECT.__version__`: from ._version import get_versions __version__ = get_versions()['version'] del get_versions ## Styles The setup.cfg `style=` configuration controls how the VCS information is rendered into a version string. The default style, "pep440", produces a PEP440-compliant string, equal to the un-prefixed tag name for actual releases, and containing an additional "local version" section with more detail for in-between builds. For Git, this is TAG[+DISTANCE.gHEX[.dirty]] , using information from `git describe --tags --dirty --always`. For example "0.11+2.g1076c97.dirty" indicates that the tree is like the "1076c97" commit but has uncommitted changes (".dirty"), and that this commit is two revisions ("+2") beyond the "0.11" tag. For released software (exactly equal to a known tag), the identifier will only contain the stripped tag, e.g. "0.11". Other styles are available. See [details.md](details.md) in the Versioneer source tree for descriptions. ## Debugging Versioneer tries to avoid fatal errors: if something goes wrong, it will tend to return a version of "0+unknown". To investigate the problem, run `setup.py version`, which will run the version-lookup code in a verbose mode, and will display the full contents of `get_versions()` (including the `error` string, which may help identify what went wrong). ## Known Limitations Some situations are known to cause problems for Versioneer. This details the most significant ones. More can be found on Github [issues page](https://github.com/python-versioneer/python-versioneer/issues). ### Subprojects Versioneer has limited support for source trees in which `setup.py` is not in the root directory (e.g. `setup.py` and `.git/` are *not* siblings). The are two common reasons why `setup.py` might not be in the root: * Source trees which contain multiple subprojects, such as [Buildbot](https://github.com/buildbot/buildbot), which contains both "master" and "slave" subprojects, each with their own `setup.py`, `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI distributions (and upload multiple independently-installable tarballs). * Source trees whose main purpose is to contain a C library, but which also provide bindings to Python (and perhaps other languages) in subdirectories. Versioneer will look for `.git` in parent directories, and most operations should get the right version string. However `pip` and `setuptools` have bugs and implementation details which frequently cause `pip install .` from a subproject directory to fail to find a correct version string (so it usually defaults to `0+unknown`). `pip install --editable .` should work correctly. `setup.py install` might work too. Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in some later version. [Bug #38](https://github.com/python-versioneer/python-versioneer/issues/38) is tracking this issue. The discussion in [PR #61](https://github.com/python-versioneer/python-versioneer/pull/61) describes the issue from the Versioneer side in more detail. [pip PR#3176](https://github.com/pypa/pip/pull/3176) and [pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve pip to let Versioneer work correctly. Versioneer-0.16 and earlier only looked for a `.git` directory next to the `setup.cfg`, so subprojects were completely unsupported with those releases. ### Editable installs with setuptools <= 18.5 `setup.py develop` and `pip install --editable .` allow you to install a project into a virtualenv once, then continue editing the source code (and test) without re-installing after every change. "Entry-point scripts" (`setup(entry_points={"console_scripts": ..})`) are a convenient way to specify executable scripts that should be installed along with the python package. These both work as expected when using modern setuptools. When using setuptools-18.5 or earlier, however, certain operations will cause `pkg_resources.DistributionNotFound` errors when running the entrypoint script, which must be resolved by re-installing the package. This happens when the install happens with one version, then the egg_info data is regenerated while a different version is checked out. Many setup.py commands cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into a different virtualenv), so this can be surprising. [Bug #83](https://github.com/python-versioneer/python-versioneer/issues/83) describes this one, but upgrading to a newer version of setuptools should probably resolve it. ## Updating Versioneer To upgrade your project to a new release of Versioneer, do the following: * install the new Versioneer (`pip install -U versioneer` or equivalent) * edit `setup.cfg` and `pyproject.toml`, if necessary, to include any new configuration settings indicated by the release notes. See [UPGRADING](./UPGRADING.md) for details. * re-run `versioneer install --[no-]vendor` in your source tree, to replace `SRC/_version.py` * commit any changed files ## Future Directions This tool is designed to make it easily extended to other version-control systems: all VCS-specific components are in separate directories like src/git/ . The top-level `versioneer.py` script is assembled from these components by running make-versioneer.py . In the future, make-versioneer.py will take a VCS name as an argument, and will construct a version of `versioneer.py` that is specific to the given VCS. It might also take the configuration arguments that are currently provided manually during installation by editing setup.py . Alternatively, it might go the other direction and include code from all supported VCS systems, reducing the number of intermediate scripts. ## Similar projects * [setuptools_scm](https://github.com/pypa/setuptools_scm/) - a non-vendored build-time dependency * [minver](https://github.com/jbweston/miniver) - a lightweight reimplementation of versioneer * [versioningit](https://github.com/jwodder/versioningit) - a PEP 518-based setuptools plugin ## License To make Versioneer easier to embed, all its code is dedicated to the public domain. The `_version.py` that it creates is also in the public domain. Specifically, both are released under the "Unlicense", as described in https://unlicense.org/. [pypi-image]: https://img.shields.io/pypi/v/versioneer.svg [pypi-url]: https://pypi.python.org/pypi/versioneer/ [travis-image]: https://img.shields.io/travis/com/python-versioneer/python-versioneer.svg [travis-url]: https://travis-ci.com/github/python-versioneer/python-versioneer """ # pylint:disable=invalid-name,import-outside-toplevel,missing-function-docstring # pylint:disable=missing-class-docstring,too-many-branches,too-many-statements # pylint:disable=raise-missing-from,too-many-lines,too-many-locals,import-error # pylint:disable=too-few-public-methods,redefined-outer-name,consider-using-with # pylint:disable=attribute-defined-outside-init,too-many-arguments import configparser import errno import json import os import re import subprocess import sys from pathlib import Path from typing import Callable, Dict import functools have_tomllib = True if sys.version_info >= (3, 11): import tomllib else: try: import tomli as tomllib except ImportError: have_tomllib = False class VersioneerConfig: """Container for Versioneer configuration parameters.""" def get_root(): """Get the project root directory. We require that all commands are run from the project root, i.e. the directory that contains setup.py, setup.cfg, and versioneer.py . """ root = os.path.realpath(os.path.abspath(os.getcwd())) setup_py = os.path.join(root, "setup.py") versioneer_py = os.path.join(root, "versioneer.py") if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): # allow 'python path/to/setup.py COMMAND' root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0]))) setup_py = os.path.join(root, "setup.py") versioneer_py = os.path.join(root, "versioneer.py") if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): err = ("Versioneer was unable to run the project root directory. " "Versioneer requires setup.py to be executed from " "its immediate directory (like 'python setup.py COMMAND'), " "or in a way that lets it use sys.argv[0] to find the root " "(like 'python path/to/setup.py COMMAND').") raise VersioneerBadRootError(err) try: # Certain runtime workflows (setup.py install/develop in a setuptools # tree) execute all dependencies in a single python process, so # "versioneer" may be imported multiple times, and python's shared # module-import table will cache the first one. So we can't use # os.path.dirname(__file__), as that will find whichever # versioneer.py was first imported, even in later projects. my_path = os.path.realpath(os.path.abspath(__file__)) me_dir = os.path.normcase(os.path.splitext(my_path)[0]) vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0]) if me_dir != vsr_dir and "VERSIONEER_PEP518" not in globals(): print("Warning: build in %s is using versioneer.py from %s" % (os.path.dirname(my_path), versioneer_py)) except NameError: pass return root def get_config_from_root(root): """Read the project setup.cfg file to determine Versioneer config.""" # This might raise OSError (if setup.cfg is missing), or # configparser.NoSectionError (if it lacks a [versioneer] section), or # configparser.NoOptionError (if it lacks "VCS="). See the docstring at # the top of versioneer.py for instructions on writing your setup.cfg . root = Path(root) pyproject_toml = root / "pyproject.toml" setup_cfg = root / "setup.cfg" section = None if pyproject_toml.exists() and have_tomllib: try: with open(pyproject_toml, 'rb') as fobj: pp = tomllib.load(fobj) section = pp['tool']['versioneer'] except (tomllib.TOMLDecodeError, KeyError): pass if not section: parser = configparser.ConfigParser() with open(setup_cfg) as cfg_file: parser.read_file(cfg_file) parser.get("versioneer", "VCS") # raise error if missing section = parser["versioneer"] cfg = VersioneerConfig() cfg.VCS = section['VCS'] cfg.style = section.get("style", "") cfg.versionfile_source = section.get("versionfile_source") cfg.versionfile_build = section.get("versionfile_build") cfg.tag_prefix = section.get("tag_prefix") if cfg.tag_prefix in ("''", '""', None): cfg.tag_prefix = "" cfg.parentdir_prefix = section.get("parentdir_prefix") cfg.verbose = section.get("verbose") return cfg class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" # these dictionaries contain VCS-specific tools LONG_VERSION_PY: Dict[str, str] = {} HANDLERS: Dict[str, Dict[str, Callable]] = {} def register_vcs_handler(vcs, method): # decorator """Create decorator to mark a method as the handler of a VCS.""" def decorate(f): """Store f in HANDLERS[vcs][method].""" HANDLERS.setdefault(vcs, {})[method] = f return f return decorate def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None): """Call the given command(s).""" assert isinstance(commands, list) process = None popen_kwargs = {} if sys.platform == "win32": # This hides the console window if pythonw.exe is used startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW popen_kwargs["startupinfo"] = startupinfo for command in commands: try: dispcmd = str([command] + args) # remember shell=False, so use git.cmd on windows, not just git process = subprocess.Popen([command] + args, cwd=cwd, env=env, stdout=subprocess.PIPE, stderr=(subprocess.PIPE if hide_stderr else None), **popen_kwargs) break except OSError: e = sys.exc_info()[1] if e.errno == errno.ENOENT: continue if verbose: print("unable to run %s" % dispcmd) print(e) return None, None else: if verbose: print("unable to find command, tried %s" % (commands,)) return None, None stdout = process.communicate()[0].strip().decode() if process.returncode != 0: if verbose: print("unable to run %s (error)" % dispcmd) print("stdout was %s" % stdout) return None, process.returncode return stdout, process.returncode LONG_VERSION_PY['git'] = r''' # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag # feature). Distribution tarballs (built by setup.py sdist) and build # directories (produced by setup.py build) will contain a much shorter file # that just contains the computed version number. # This file is released into the public domain. # Generated by versioneer-0.28 # https://github.com/python-versioneer/python-versioneer """Git implementation of _version.py.""" import errno import os import re import subprocess import sys from typing import Callable, Dict import functools def get_keywords(): """Get the keywords needed to look up the version information.""" # these strings will be replaced by git during git-archive. # setup.py/versioneer.py will grep for the variable names, so they must # each be defined on a line of their own. _version.py will just call # get_keywords(). git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s" git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s" git_date = "%(DOLLAR)sFormat:%%ci%(DOLLAR)s" keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} return keywords class VersioneerConfig: """Container for Versioneer configuration parameters.""" def get_config(): """Create, populate and return the VersioneerConfig() object.""" # these strings are filled in when 'setup.py versioneer' creates # _version.py cfg = VersioneerConfig() cfg.VCS = "git" cfg.style = "%(STYLE)s" cfg.tag_prefix = "%(TAG_PREFIX)s" cfg.parentdir_prefix = "%(PARENTDIR_PREFIX)s" cfg.versionfile_source = "%(VERSIONFILE_SOURCE)s" cfg.verbose = False return cfg class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" LONG_VERSION_PY: Dict[str, str] = {} HANDLERS: Dict[str, Dict[str, Callable]] = {} def register_vcs_handler(vcs, method): # decorator """Create decorator to mark a method as the handler of a VCS.""" def decorate(f): """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f return f return decorate def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None): """Call the given command(s).""" assert isinstance(commands, list) process = None popen_kwargs = {} if sys.platform == "win32": # This hides the console window if pythonw.exe is used startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW popen_kwargs["startupinfo"] = startupinfo for command in commands: try: dispcmd = str([command] + args) # remember shell=False, so use git.cmd on windows, not just git process = subprocess.Popen([command] + args, cwd=cwd, env=env, stdout=subprocess.PIPE, stderr=(subprocess.PIPE if hide_stderr else None), **popen_kwargs) break except OSError: e = sys.exc_info()[1] if e.errno == errno.ENOENT: continue if verbose: print("unable to run %%s" %% dispcmd) print(e) return None, None else: if verbose: print("unable to find command, tried %%s" %% (commands,)) return None, None stdout = process.communicate()[0].strip().decode() if process.returncode != 0: if verbose: print("unable to run %%s (error)" %% dispcmd) print("stdout was %%s" %% stdout) return None, process.returncode return stdout, process.returncode def versions_from_parentdir(parentdir_prefix, root, verbose): """Try to determine the version from the parent directory name. Source tarballs conventionally unpack into a directory that includes both the project name and a version string. We will also support searching up two directory levels for an appropriately named parent directory """ rootdirs = [] for _ in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): return {"version": dirname[len(parentdir_prefix):], "full-revisionid": None, "dirty": False, "error": None, "date": None} rootdirs.append(root) root = os.path.dirname(root) # up a level if verbose: print("Tried directories %%s but none started with prefix %%s" %% (str(rootdirs), parentdir_prefix)) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") @register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs): """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from # _version.py. keywords = {} try: with open(versionfile_abs, "r") as fobj: for line in fobj: if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["full"] = mo.group(1) if line.strip().startswith("git_date ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["date"] = mo.group(1) except OSError: pass return keywords @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): """Get version information from git keywords.""" if "refnames" not in keywords: raise NotThisMethod("Short version file found") date = keywords.get("date") if date is not None: # Use only the last line. Previous lines may contain GPG signature # information. date = date.splitlines()[-1] # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601 # -like" string, which we must then edit to make compliant), because # it's been around since git-1.5.3, and it's too difficult to # discover which version we're using, or to work around using an # older one. date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") refs = {r.strip() for r in refnames.strip("()").split(",")} # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " tags = {r[len(TAG):] for r in refs if r.startswith(TAG)} if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %%d # expansion behaves like git log --decorate=short and strips out the # refs/heads/ and refs/tags/ prefixes that would let us distinguish # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". tags = {r for r in refs if re.search(r'\d', r)} if verbose: print("discarding '%%s', no digits" %% ",".join(refs - tags)) if verbose: print("likely tags: %%s" %% ",".join(sorted(tags))) for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] # Filter out refs that exactly match prefix or that don't start # with a number once the prefix is stripped (mostly a concern # when prefix is '') if not re.match(r'\d', r): continue if verbose: print("picking %%s" %% r) return {"version": r, "full-revisionid": keywords["full"].strip(), "dirty": False, "error": None, "date": date} # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: print("no suitable tags, using unknown + full revision id") return {"version": "0+unknown", "full-revisionid": keywords["full"].strip(), "dirty": False, "error": "no suitable tags", "date": None} @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* expanded, and _version.py hasn't already been rewritten with a short version string, meaning we're inside a checked out source tree. """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] # GIT_DIR can interfere with correct operation of Versioneer. # It may be intended to be passed to the Versioneer-versioned project, # but that should not change where we get our version from. env = os.environ.copy() env.pop("GIT_DIR", None) runner = functools.partial(runner, env=env) _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=not verbose) if rc != 0: if verbose: print("Directory %%s not under git control" %% root) raise NotThisMethod("'git rev-parse --git-dir' returned error") # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) describe_out, rc = runner(GITS, [ "describe", "--tags", "--dirty", "--always", "--long", "--match", f"{tag_prefix}[[:digit:]]*" ], cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() pieces = {} pieces["long"] = full_out pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], cwd=root) # --abbrev-ref was added in git-1.6.3 if rc != 0 or branch_name is None: raise NotThisMethod("'git rev-parse --abbrev-ref' returned error") branch_name = branch_name.strip() if branch_name == "HEAD": # If we aren't exactly on a branch, pick a branch which represents # the current commit. If all else fails, we are on a branchless # commit. branches, rc = runner(GITS, ["branch", "--contains"], cwd=root) # --contains was added in git-1.5.4 if rc != 0 or branches is None: raise NotThisMethod("'git branch --contains' returned error") branches = branches.split("\n") # Remove the first line if we're running detached if "(" in branches[0]: branches.pop(0) # Strip off the leading "* " from the list of branches. branches = [branch[2:] for branch in branches] if "master" in branches: branch_name = "master" elif not branches: branch_name = None else: # Pick the first branch that is returned. Good or bad. branch_name = branches[0] pieces["branch"] = branch_name # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out # look for -dirty suffix dirty = git_describe.endswith("-dirty") pieces["dirty"] = dirty if dirty: git_describe = git_describe[:git_describe.rindex("-dirty")] # now we have TAG-NUM-gHEX or HEX if "-" in git_describe: # TAG-NUM-gHEX mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: # unparsable. Maybe git-describe is misbehaving? pieces["error"] = ("unable to parse git-describe output: '%%s'" %% describe_out) return pieces # tag full_tag = mo.group(1) if not full_tag.startswith(tag_prefix): if verbose: fmt = "tag '%%s' doesn't start with prefix '%%s'" print(fmt %% (full_tag, tag_prefix)) pieces["error"] = ("tag '%%s' doesn't start with prefix '%%s'" %% (full_tag, tag_prefix)) return pieces pieces["closest-tag"] = full_tag[len(tag_prefix):] # distance: number of commits since tag pieces["distance"] = int(mo.group(2)) # commit: short hex revision ID pieces["short"] = mo.group(3) else: # HEX: no tags pieces["closest-tag"] = None out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root) pieces["distance"] = len(out.split()) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() date = runner(GITS, ["show", "-s", "--format=%%ci", "HEAD"], cwd=root)[0].strip() # Use only the last line. Previous lines may contain GPG signature # information. date = date.splitlines()[-1] pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces def plus_or_dot(pieces): """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" def render_pep440(pieces): """Build up version string, with post-release "local version identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty Exceptions: 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += plus_or_dot(pieces) rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0+untagged.%%d.g%%s" %% (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_branch(pieces): """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . The ".dev0" means not master branch. Note that .dev0 sorts backwards (a feature branch will appear "older" than the master branch). Exceptions: 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: if pieces["branch"] != "master": rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0" if pieces["branch"] != "master": rendered += ".dev0" rendered += "+untagged.%%d.g%%s" %% (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def pep440_split_post(ver): """Split pep440 version string at the post-release segment. Returns the release segments before the post-release and the post-release version number (or -1 if no post-release segment is present). """ vc = str.split(ver, ".post") return vc[0], int(vc[1] or 0) if len(vc) == 2 else None def render_pep440_pre(pieces): """TAG[.postN.devDISTANCE] -- No -dirty. Exceptions: 1: no tags. 0.post0.devDISTANCE """ if pieces["closest-tag"]: if pieces["distance"]: # update the post release segment tag_version, post_version = pep440_split_post(pieces["closest-tag"]) rendered = tag_version if post_version is not None: rendered += ".post%%d.dev%%d" %% (post_version + 1, pieces["distance"]) else: rendered += ".post0.dev%%d" %% (pieces["distance"]) else: # no commits, use the tag as the version rendered = pieces["closest-tag"] else: # exception #1 rendered = "0.post0.dev%%d" %% pieces["distance"] return rendered def render_pep440_post(pieces): """TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that .dev0 sorts backwards (a dirty tree will appear "older" than the corresponding clean one), but you shouldn't be releasing software with -dirty anyways. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%%d" %% pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%%s" %% pieces["short"] else: # exception #1 rendered = "0.post%%d" %% pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += "+g%%s" %% pieces["short"] return rendered def render_pep440_post_branch(pieces): """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . The ".dev0" means not master branch. Exceptions: 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%%d" %% pieces["distance"] if pieces["branch"] != "master": rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%%s" %% pieces["short"] if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0.post%%d" %% pieces["distance"] if pieces["branch"] != "master": rendered += ".dev0" rendered += "+g%%s" %% pieces["short"] if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_old(pieces): """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%%d" %% pieces["distance"] if pieces["dirty"]: rendered += ".dev0" else: # exception #1 rendered = "0.post%%d" %% pieces["distance"] if pieces["dirty"]: rendered += ".dev0" return rendered def render_git_describe(pieces): """TAG[-DISTANCE-gHEX][-dirty]. Like 'git describe --tags --dirty --always'. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render_git_describe_long(pieces): """TAG-DISTANCE-gHEX[-dirty]. Like 'git describe --tags --dirty --always -long'. The distance/hash is unconditional. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render(pieces, style): """Render the given version pieces into the requested style.""" if pieces["error"]: return {"version": "unknown", "full-revisionid": pieces.get("long"), "dirty": None, "error": pieces["error"], "date": None} if not style or style == "default": style = "pep440" # the default if style == "pep440": rendered = render_pep440(pieces) elif style == "pep440-branch": rendered = render_pep440_branch(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) elif style == "pep440-post-branch": rendered = render_pep440_post_branch(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) elif style == "git-describe": rendered = render_git_describe(pieces) elif style == "git-describe-long": rendered = render_git_describe_long(pieces) else: raise ValueError("unknown style '%%s'" %% style) return {"version": rendered, "full-revisionid": pieces["long"], "dirty": pieces["dirty"], "error": None, "date": pieces.get("date")} def get_versions(): """Get version information or return default if unable to do so.""" # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have # __file__, we can work backwards from there to the root. Some # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which # case we can only use expanded keywords. cfg = get_config() verbose = cfg.verbose try: return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose) except NotThisMethod: pass try: root = os.path.realpath(__file__) # versionfile_source is the relative path from the top of the source # tree (where the .git directory might live) to this file. Invert # this to find the root from __file__. for _ in cfg.versionfile_source.split('/'): root = os.path.dirname(root) except NameError: return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to find root of source tree", "date": None} try: pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) return render(pieces, cfg.style) except NotThisMethod: pass try: if cfg.parentdir_prefix: return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) except NotThisMethod: pass return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to compute version", "date": None} ''' @register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs): """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from # _version.py. keywords = {} try: with open(versionfile_abs, "r") as fobj: for line in fobj: if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["full"] = mo.group(1) if line.strip().startswith("git_date ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["date"] = mo.group(1) except OSError: pass return keywords @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): """Get version information from git keywords.""" if "refnames" not in keywords: raise NotThisMethod("Short version file found") date = keywords.get("date") if date is not None: # Use only the last line. Previous lines may contain GPG signature # information. date = date.splitlines()[-1] # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 # -like" string, which we must then edit to make compliant), because # it's been around since git-1.5.3, and it's too difficult to # discover which version we're using, or to work around using an # older one. date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") refs = {r.strip() for r in refnames.strip("()").split(",")} # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " tags = {r[len(TAG):] for r in refs if r.startswith(TAG)} if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %d # expansion behaves like git log --decorate=short and strips out the # refs/heads/ and refs/tags/ prefixes that would let us distinguish # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". tags = {r for r in refs if re.search(r'\d', r)} if verbose: print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: print("likely tags: %s" % ",".join(sorted(tags))) for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] # Filter out refs that exactly match prefix or that don't start # with a number once the prefix is stripped (mostly a concern # when prefix is '') if not re.match(r'\d', r): continue if verbose: print("picking %s" % r) return {"version": r, "full-revisionid": keywords["full"].strip(), "dirty": False, "error": None, "date": date} # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: print("no suitable tags, using unknown + full revision id") return {"version": "0+unknown", "full-revisionid": keywords["full"].strip(), "dirty": False, "error": "no suitable tags", "date": None} @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* expanded, and _version.py hasn't already been rewritten with a short version string, meaning we're inside a checked out source tree. """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.exe", r"I:\sw\gs\git-1.16\dist\win\cmd\git.exe"] # GIT_DIR can interfere with correct operation of Versioneer. # It may be intended to be passed to the Versioneer-versioned project, # but that should not change where we get our version from. env = os.environ.copy() env.pop("GIT_DIR", None) runner = functools.partial(runner, env=env) _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=not verbose) if rc != 0: if verbose: print("Directory %s not under git control" % root) raise NotThisMethod("'git rev-parse --git-dir' returned error") # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) describe_out, rc = runner(GITS, [ "describe", "--tags", "--dirty", "--always", "--long", "--match", f"{tag_prefix}[[:digit:]]*" ], cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() pieces = {} pieces["long"] = full_out pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], cwd=root) # --abbrev-ref was added in git-1.6.3 if rc != 0 or branch_name is None: raise NotThisMethod("'git rev-parse --abbrev-ref' returned error") branch_name = branch_name.strip() if branch_name == "HEAD": # If we aren't exactly on a branch, pick a branch which represents # the current commit. If all else fails, we are on a branchless # commit. branches, rc = runner(GITS, ["branch", "--contains"], cwd=root) # --contains was added in git-1.5.4 if rc != 0 or branches is None: raise NotThisMethod("'git branch --contains' returned error") branches = branches.split("\n") # Remove the first line if we're running detached if "(" in branches[0]: branches.pop(0) # Strip off the leading "* " from the list of branches. branches = [branch[2:] for branch in branches] if "master" in branches: branch_name = "master" elif not branches: branch_name = None else: # Pick the first branch that is returned. Good or bad. branch_name = branches[0] pieces["branch"] = branch_name # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out # look for -dirty suffix dirty = git_describe.endswith("-dirty") pieces["dirty"] = dirty if dirty: git_describe = git_describe[:git_describe.rindex("-dirty")] # now we have TAG-NUM-gHEX or HEX if "-" in git_describe: # TAG-NUM-gHEX mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: # unparsable. Maybe git-describe is misbehaving? pieces["error"] = ("unable to parse git-describe output: '%s'" % describe_out) return pieces # tag full_tag = mo.group(1) if not full_tag.startswith(tag_prefix): if verbose: fmt = "tag '%s' doesn't start with prefix '%s'" print(fmt % (full_tag, tag_prefix)) pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" % (full_tag, tag_prefix)) return pieces pieces["closest-tag"] = full_tag[len(tag_prefix):] # distance: number of commits since tag pieces["distance"] = int(mo.group(2)) # commit: short hex revision ID pieces["short"] = mo.group(3) else: # HEX: no tags pieces["closest-tag"] = None out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root) pieces["distance"] = len(out.split()) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip() # Use only the last line. Previous lines may contain GPG signature # information. date = date.splitlines()[-1] pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces def do_vcs_install(versionfile_source, ipy): """Git-specific installation logic for Versioneer. For Git, this means creating/changing .gitattributes to mark _version.py for export-subst keyword substitution. """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.exe", r"I:\sw\gs\git-1.16\dist\win\cmd\git.exe"] files = [versionfile_source] if ipy: files.append(ipy) if "VERSIONEER_PEP518" not in globals(): try: my_path = __file__ if my_path.endswith((".pyc", ".pyo")): my_path = os.path.splitext(my_path)[0] + ".py" versioneer_file = os.path.relpath(my_path) except NameError: versioneer_file = "versioneer.py" files.append(versioneer_file) present = False try: with open(".gitattributes", "r") as fobj: for line in fobj: if line.strip().startswith(versionfile_source): if "export-subst" in line.strip().split()[1:]: present = True break except OSError: pass if not present: with open(".gitattributes", "a+") as fobj: fobj.write(f"{versionfile_source} export-subst\n") files.append(".gitattributes") run_command(GITS, ["add", "--"] + files) def versions_from_parentdir(parentdir_prefix, root, verbose): """Try to determine the version from the parent directory name. Source tarballs conventionally unpack into a directory that includes both the project name and a version string. We will also support searching up two directory levels for an appropriately named parent directory """ rootdirs = [] for _ in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): return {"version": dirname[len(parentdir_prefix):], "full-revisionid": None, "dirty": False, "error": None, "date": None} rootdirs.append(root) root = os.path.dirname(root) # up a level if verbose: print("Tried directories %s but none started with prefix %s" % (str(rootdirs), parentdir_prefix)) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") SHORT_VERSION_PY = """ # This file was generated by 'versioneer.py' (0.28) from # revision-control system data, or from the parent directory name of an # unpacked source archive. Distribution tarballs contain a pre-generated copy # of this file. import json version_json = ''' %s ''' # END VERSION_JSON def get_versions(): return json.loads(version_json) """ def versions_from_file(filename): """Try to determine the version from _version.py if present.""" try: with open(filename) as f: contents = f.read() except OSError: raise NotThisMethod("unable to read _version.py") mo = re.search(r"version_json = '''\n(.*)''' # END VERSION_JSON", contents, re.M | re.S) if not mo: mo = re.search(r"version_json = '''\r\n(.*)''' # END VERSION_JSON", contents, re.M | re.S) if not mo: raise NotThisMethod("no version_json in _version.py") return json.loads(mo.group(1)) def write_to_version_file(filename, versions): """Write the given version number to the given _version.py file.""" os.unlink(filename) contents = json.dumps(versions, sort_keys=True, indent=1, separators=(",", ": ")) with open(filename, "w") as f: f.write(SHORT_VERSION_PY % contents) print("set %s to '%s'" % (filename, versions["version"])) def plus_or_dot(pieces): """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" def render_pep440(pieces): """Build up version string, with post-release "local version identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty Exceptions: 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += plus_or_dot(pieces) rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_branch(pieces): """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . The ".dev0" means not master branch. Note that .dev0 sorts backwards (a feature branch will appear "older" than the master branch). Exceptions: 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: if pieces["branch"] != "master": rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0" if pieces["branch"] != "master": rendered += ".dev0" rendered += "+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def pep440_split_post(ver): """Split pep440 version string at the post-release segment. Returns the release segments before the post-release and the post-release version number (or -1 if no post-release segment is present). """ vc = str.split(ver, ".post") return vc[0], int(vc[1] or 0) if len(vc) == 2 else None def render_pep440_pre(pieces): """TAG[.postN.devDISTANCE] -- No -dirty. Exceptions: 1: no tags. 0.post0.devDISTANCE """ if pieces["closest-tag"]: if pieces["distance"]: # update the post release segment tag_version, post_version = pep440_split_post(pieces["closest-tag"]) rendered = tag_version if post_version is not None: rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"]) else: rendered += ".post0.dev%d" % (pieces["distance"]) else: # no commits, use the tag as the version rendered = pieces["closest-tag"] else: # exception #1 rendered = "0.post0.dev%d" % pieces["distance"] return rendered def render_pep440_post(pieces): """TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that .dev0 sorts backwards (a dirty tree will appear "older" than the corresponding clean one), but you shouldn't be releasing software with -dirty anyways. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%s" % pieces["short"] else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += "+g%s" % pieces["short"] return rendered def render_pep440_post_branch(pieces): """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . The ".dev0" means not master branch. Exceptions: 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["branch"] != "master": rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%s" % pieces["short"] if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["branch"] != "master": rendered += ".dev0" rendered += "+g%s" % pieces["short"] if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_old(pieces): """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" return rendered def render_git_describe(pieces): """TAG[-DISTANCE-gHEX][-dirty]. Like 'git describe --tags --dirty --always'. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render_git_describe_long(pieces): """TAG-DISTANCE-gHEX[-dirty]. Like 'git describe --tags --dirty --always -long'. The distance/hash is unconditional. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render(pieces, style): """Render the given version pieces into the requested style.""" if pieces["error"]: return {"version": "unknown", "full-revisionid": pieces.get("long"), "dirty": None, "error": pieces["error"], "date": None} if not style or style == "default": style = "pep440" # the default if style == "pep440": rendered = render_pep440(pieces) elif style == "pep440-branch": rendered = render_pep440_branch(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) elif style == "pep440-post-branch": rendered = render_pep440_post_branch(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) elif style == "git-describe": rendered = render_git_describe(pieces) elif style == "git-describe-long": rendered = render_git_describe_long(pieces) else: raise ValueError("unknown style '%s'" % style) return {"version": rendered, "full-revisionid": pieces["long"], "dirty": pieces["dirty"], "error": None, "date": pieces.get("date")} class VersioneerBadRootError(Exception): """The project root directory is unknown or missing key files.""" def get_versions(verbose=False): """Get the project version from whatever source is available. Returns dict with two keys: 'version' and 'full'. """ if "versioneer" in sys.modules: # see the discussion in cmdclass.py:get_cmdclass() del sys.modules["versioneer"] root = get_root() cfg = get_config_from_root(root) assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg" handlers = HANDLERS.get(cfg.VCS) assert handlers, "unrecognized VCS '%s'" % cfg.VCS verbose = verbose or cfg.verbose assert cfg.versionfile_source is not None, \ "please set versioneer.versionfile_source" assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix" versionfile_abs = os.path.join(root, cfg.versionfile_source) # extract version from first of: _version.py, VCS command (e.g. 'git # describe'), parentdir. This is meant to work for developers using a # source checkout, for users of a tarball created by 'setup.py sdist', # and for users of a tarball/zipball created by 'git archive' or github's # download-from-tag feature or the equivalent in other VCSes. get_keywords_f = handlers.get("get_keywords") from_keywords_f = handlers.get("keywords") if get_keywords_f and from_keywords_f: try: keywords = get_keywords_f(versionfile_abs) ver = from_keywords_f(keywords, cfg.tag_prefix, verbose) if verbose: print("got version from expanded keyword %s" % ver) return ver except NotThisMethod: pass try: ver = versions_from_file(versionfile_abs) if verbose: print("got version from file %s %s" % (versionfile_abs, ver)) return ver except NotThisMethod: pass from_vcs_f = handlers.get("pieces_from_vcs") if from_vcs_f: try: pieces = from_vcs_f(cfg.tag_prefix, root, verbose) ver = render(pieces, cfg.style) if verbose: print("got version from VCS %s" % ver) return ver except NotThisMethod: pass try: if cfg.parentdir_prefix: ver = versions_from_parentdir(cfg.parentdir_prefix, root, verbose) if verbose: print("got version from parentdir %s" % ver) return ver except NotThisMethod: pass if verbose: print("unable to compute version") return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to compute version", "date": None} def get_version(): """Get the short version string for this project.""" return get_versions()["version"] def get_cmdclass(cmdclass=None): """Get the custom setuptools subclasses used by Versioneer. If the package uses a different cmdclass (e.g. one from numpy), it should be provide as an argument. """ if "versioneer" in sys.modules: del sys.modules["versioneer"] # this fixes the "python setup.py develop" case (also 'install' and # 'easy_install .'), in which subdependencies of the main project are # built (using setup.py bdist_egg) in the same python process. Assume # a main project A and a dependency B, which use different versions # of Versioneer. A's setup.py imports A's Versioneer, leaving it in # sys.modules by the time B's setup.py is executed, causing B to run # with the wrong versioneer. Setuptools wraps the sub-dep builds in a # sandbox that restores sys.modules to it's pre-build state, so the # parent is protected against the child's "import versioneer". By # removing ourselves from sys.modules here, before the child build # happens, we protect the child from the parent's versioneer too. # Also see https://github.com/python-versioneer/python-versioneer/issues/52 cmds = {} if cmdclass is None else cmdclass.copy() # we add "version" to setuptools from setuptools import Command class cmd_version(Command): description = "report generated version string" user_options = [] boolean_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): vers = get_versions(verbose=True) print("Version: %s" % vers["version"]) print(" full-revisionid: %s" % vers.get("full-revisionid")) print(" dirty: %s" % vers.get("dirty")) print(" date: %s" % vers.get("date")) if vers["error"]: print(" error: %s" % vers["error"]) cmds["version"] = cmd_version # we override "build_py" in setuptools # # most invocation pathways end up running build_py: # distutils/build -> build_py # distutils/install -> distutils/build ->.. # setuptools/bdist_wheel -> distutils/install ->.. # setuptools/bdist_egg -> distutils/install_lib -> build_py # setuptools/install -> bdist_egg ->.. # setuptools/develop -> ? # pip install: # copies source tree to a tempdir before running egg_info/etc # if .git isn't copied too, 'git describe' will fail # then does setup.py bdist_wheel, or sometimes setup.py install # setup.py egg_info -> ? # pip install -e . and setuptool/editable_wheel will invoke build_py # but the build_py command is not expected to copy any files. # we override different "build_py" commands for both environments if 'build_py' in cmds: _build_py = cmds['build_py'] else: from setuptools.command.build_py import build_py as _build_py class cmd_build_py(_build_py): def run(self): root = get_root() cfg = get_config_from_root(root) versions = get_versions() _build_py.run(self) if getattr(self, "editable_mode", False): # During editable installs `.py` and data files are # not copied to build_lib return # now locate _version.py in the new build/ directory and replace # it with an updated value if cfg.versionfile_build: target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build) print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, versions) cmds["build_py"] = cmd_build_py if 'build_ext' in cmds: _build_ext = cmds['build_ext'] else: from setuptools.command.build_ext import build_ext as _build_ext class cmd_build_ext(_build_ext): def run(self): root = get_root() cfg = get_config_from_root(root) versions = get_versions() _build_ext.run(self) if self.inplace: # build_ext --inplace will only build extensions in # build/lib<..> dir with no _version.py to write to. # As in place builds will already have a _version.py # in the module dir, we do not need to write one. return # now locate _version.py in the new build/ directory and replace # it with an updated value if not cfg.versionfile_build: return target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build) if not os.path.exists(target_versionfile): print(f"Warning: {target_versionfile} does not exist, skipping " "version update. This can happen if you are running build_ext " "without first running build_py.") return print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, versions) cmds["build_ext"] = cmd_build_ext if "cx_Freeze" in sys.modules: # cx_freeze enabled? from cx_Freeze.dist import build_exe as _build_exe # nczeczulin reports that py2exe won't like the pep440-style string # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g. # setup(console=[{ # "version": versioneer.get_version().split("+", 1)[0], # FILEVERSION # "product_version": versioneer.get_version(), # ... class cmd_build_exe(_build_exe): def run(self): root = get_root() cfg = get_config_from_root(root) versions = get_versions() target_versionfile = cfg.versionfile_source print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, versions) _build_exe.run(self) os.unlink(target_versionfile) with open(cfg.versionfile_source, "w") as f: LONG = LONG_VERSION_PY[cfg.VCS] f.write(LONG % {"DOLLAR": "$", "STYLE": cfg.style, "TAG_PREFIX": cfg.tag_prefix, "PARENTDIR_PREFIX": cfg.parentdir_prefix, "VERSIONFILE_SOURCE": cfg.versionfile_source, }) cmds["build_exe"] = cmd_build_exe del cmds["build_py"] if 'py2exe' in sys.modules: # py2exe enabled? try: from py2exe.setuptools_buildexe import py2exe as _py2exe except ImportError: from py2exe.distutils_buildexe import py2exe as _py2exe class cmd_py2exe(_py2exe): def run(self): root = get_root() cfg = get_config_from_root(root) versions = get_versions() target_versionfile = cfg.versionfile_source print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, versions) _py2exe.run(self) os.unlink(target_versionfile) with open(cfg.versionfile_source, "w") as f: LONG = LONG_VERSION_PY[cfg.VCS] f.write(LONG % {"DOLLAR": "$", "STYLE": cfg.style, "TAG_PREFIX": cfg.tag_prefix, "PARENTDIR_PREFIX": cfg.parentdir_prefix, "VERSIONFILE_SOURCE": cfg.versionfile_source, }) cmds["py2exe"] = cmd_py2exe # sdist farms its file list building out to egg_info if 'egg_info' in cmds: _egg_info = cmds['egg_info'] else: from setuptools.command.egg_info import egg_info as _egg_info class cmd_egg_info(_egg_info): def find_sources(self): # egg_info.find_sources builds the manifest list and writes it # in one shot super().find_sources() # Modify the filelist and normalize it root = get_root() cfg = get_config_from_root(root) self.filelist.append('versioneer.py') if cfg.versionfile_source: # There are rare cases where versionfile_source might not be # included by default, so we must be explicit self.filelist.append(cfg.versionfile_source) self.filelist.sort() self.filelist.remove_duplicates() # The write method is hidden in the manifest_maker instance that # generated the filelist and was thrown away # We will instead replicate their final normalization (to unicode, # and POSIX-style paths) from setuptools import unicode_utils normalized = [unicode_utils.filesys_decode(f).replace(os.sep, '/') for f in self.filelist.files] manifest_filename = os.path.join(self.egg_info, 'SOURCES.txt') with open(manifest_filename, 'w') as fobj: fobj.write('\n'.join(normalized)) cmds['egg_info'] = cmd_egg_info # we override different "sdist" commands for both environments if 'sdist' in cmds: _sdist = cmds['sdist'] else: from setuptools.command.sdist import sdist as _sdist class cmd_sdist(_sdist): def run(self): versions = get_versions() self._versioneer_generated_versions = versions # unless we update this, the command will keep using the old # version self.distribution.metadata.version = versions["version"] return _sdist.run(self) def make_release_tree(self, base_dir, files): root = get_root() cfg = get_config_from_root(root) _sdist.make_release_tree(self, base_dir, files) # now locate _version.py in the new base_dir directory # (remembering that it may be a hardlink) and replace it with an # updated value target_versionfile = os.path.join(base_dir, cfg.versionfile_source) print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, self._versioneer_generated_versions) cmds["sdist"] = cmd_sdist return cmds CONFIG_ERROR = """ setup.cfg is missing the necessary Versioneer configuration. You need a section like: [versioneer] VCS = git style = pep440 versionfile_source = src/myproject/_version.py versionfile_build = myproject/_version.py tag_prefix = parentdir_prefix = myproject- You will also need to edit your setup.py to use the results: import versioneer setup(version=versioneer.get_version(), cmdclass=versioneer.get_cmdclass(), ...) Please read the docstring in ./versioneer.py for configuration instructions, edit setup.cfg, and re-run the installer or 'python versioneer.py setup'. """ SAMPLE_CONFIG = """ # See the docstring in versioneer.py for instructions. Note that you must # re-run 'versioneer.py setup' after changing this section, and commit the # resulting files. [versioneer] #VCS = git #style = pep440 #versionfile_source = #versionfile_build = #tag_prefix = #parentdir_prefix = """ OLD_SNIPPET = """ from ._version import get_versions __version__ = get_versions()['version'] del get_versions """ INIT_PY_SNIPPET = """ from . import {0} __version__ = {0}.get_versions()['version'] """ def do_setup(): """Do main VCS-independent setup function for installing Versioneer.""" root = get_root() try: cfg = get_config_from_root(root) except (OSError, configparser.NoSectionError, configparser.NoOptionError) as e: if isinstance(e, (OSError, configparser.NoSectionError)): print("Adding sample versioneer config to setup.cfg", file=sys.stderr) with open(os.path.join(root, "setup.cfg"), "a") as f: f.write(SAMPLE_CONFIG) print(CONFIG_ERROR, file=sys.stderr) return 1 print(" creating %s" % cfg.versionfile_source) with open(cfg.versionfile_source, "w") as f: LONG = LONG_VERSION_PY[cfg.VCS] f.write(LONG % {"DOLLAR": "$", "STYLE": cfg.style, "TAG_PREFIX": cfg.tag_prefix, "PARENTDIR_PREFIX": cfg.parentdir_prefix, "VERSIONFILE_SOURCE": cfg.versionfile_source, }) ipy = os.path.join(os.path.dirname(cfg.versionfile_source), "__init__.py") if os.path.exists(ipy): try: with open(ipy, "r") as f: old = f.read() except OSError: old = "" module = os.path.splitext(os.path.basename(cfg.versionfile_source))[0] snippet = INIT_PY_SNIPPET.format(module) if OLD_SNIPPET in old: print(" replacing boilerplate in %s" % ipy) with open(ipy, "w") as f: f.write(old.replace(OLD_SNIPPET, snippet)) elif snippet not in old: print(" appending to %s" % ipy) with open(ipy, "a") as f: f.write(snippet) else: print(" %s unmodified" % ipy) else: print(" %s doesn't exist, ok" % ipy) ipy = None # Make VCS-specific changes. For git, this means creating/changing # .gitattributes to mark _version.py for export-subst keyword # substitution. do_vcs_install(cfg.versionfile_source, ipy) return 0 def scan_setup_py(): """Validate the contents of setup.py against Versioneer's expectations.""" found = set() setters = False errors = 0 with open("setup.py", "r") as f: for line in f.readlines(): if "import versioneer" in line: found.add("import") if "versioneer.get_cmdclass()" in line: found.add("cmdclass") if "versioneer.get_version()" in line: found.add("get_version") if "versioneer.VCS" in line: setters = True if "versioneer.versionfile_source" in line: setters = True if len(found) != 3: print("") print("Your setup.py appears to be missing some important items") print("(but I might be wrong). Please make sure it has something") print("roughly like the following:") print("") print(" import versioneer") print(" setup( version=versioneer.get_version(),") print(" cmdclass=versioneer.get_cmdclass(), ...)") print("") errors += 1 if setters: print("You should remove lines like 'versioneer.VCS = ' and") print("'versioneer.versionfile_source = ' . This configuration") print("now lives in setup.cfg, and should be removed from setup.py") print("") errors += 1 return errors def setup_command(): """Set up Versioneer and exit with appropriate error code.""" errors = do_setup() errors += scan_setup_py() sys.exit(1 if errors else 0) if __name__ == "__main__": cmd = sys.argv[1] if cmd == "setup": setup_command()